From 9d0b1dbb2dc87df1b1bd7fd30a3f6132aa31ea7d Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 31 Aug 2021 20:22:49 +0800 Subject: [PATCH 0001/2647] upload chart revion to s3 storage Signed-off-by: allenshen --- .../core/environment/service/environment.go | 3 + .../aslan/core/service/service/helm.go | 79 +++++++++++++++++++ .../core/service/taskplugin/deploy.go | 50 +++++++++--- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 83eaaff8..ec66eabb 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2526,6 +2526,9 @@ func updateProductVariable(productName, envName string, productResp *commonmodel if err != nil { continue } + + //获取指定版本的chart信息 + wg.Add(1) go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service) { defer wg.Done() diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 4a173c9c..69b435a6 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -18,6 +18,8 @@ package service import ( "fmt" + fsutil "github.com/koderover/zadig/pkg/util/fs" + "io/fs" "os" "path/filepath" "regexp" @@ -282,6 +284,7 @@ func CreateOrUpdateHelmService(args *HelmServiceReq, log *zap.SugaredLogger) err Name: serviceName, Version: chartVersion, ValuesYaml: valuesYaml, + }, } @@ -302,6 +305,14 @@ func CreateOrUpdateHelmService(args *HelmServiceReq, log *zap.SugaredLogger) err return } + // save chart files with revision to disk and upload to s3 + serviceNameWithRevision := fmt.Sprintf("%s-%d", serviceName, rev) + if err = commonservice.SaveAndUploadService(args.ProductName, serviceNameWithRevision, fsTree); err != nil { + log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", args.ProductName, serviceNameWithRevision, err) + err = e.ErrCreateTemplate.AddDesc(err.Error()) + return + } + // we need to update the project sequentially mux.Lock() defer mux.Unlock() @@ -468,6 +479,13 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { return fmt.Errorf("get next helm service revision error: %v", err) } + //store chart with revision info to local and upload to s3 + serviceNameWithRevision := fmt.Sprintf("%s-%d", helmServiceInfo.ServiceName, rev) + if err := commonservice.SaveAndUploadService(args.ProductName, serviceNameWithRevision, + os.DirFS(config.LocalServicePath(args.ProductName, helmServiceInfo.ServiceName))); err != nil { + return e.ErrUpdateTemplate.AddDesc(err.Error()) + } + preServiceTmpl.Revision = rev if err := commonrepo.NewServiceColl().Delete(helmServiceInfo.ServiceName, setting.HelmDeployType, args.ProductName, setting.ProductStatusDeleting, preServiceTmpl.Revision); err != nil { log.Errorf("helmService.update delete %s error: %v", helmServiceInfo.ServiceName, err) @@ -477,6 +495,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { log.Errorf("helmService.update serviceName:%s error:%v", helmServiceInfo.ServiceName, err) return e.ErrUpdateTemplate.AddDesc(err.Error()) } + } for _, serviceName := range serviceNames { @@ -586,3 +605,63 @@ func recursionGetImageByColon(jsonValues map[string]interface{}) ([]string, []*m } return banList.List(), ret } + +func saveInMemoryFilesToDisk(projectName, serviceName string, fileTree fs.FS) error { + root := config.LocalServicePath(projectName, serviceName) + + // remove existing files + err := os.RemoveAll(root) + if err != nil { + return err + } + + return fsutil.SaveToDisk(fileTree, root) +} + +//func uploadFilesToS3(projectName, serviceName string, fileTree fs.FS) error { +// fileName := fmt.Sprintf("%s.tar.gz", serviceName) +// return uploadFilesToS3Impl(projectName, serviceName, fileName, fileTree, nil) +//} +// +//func uploadFilesToS3ByPrevision(projectName, serviceName string, revision int64, fileTree fs.FS, s3Storage *s3service.S3) error { +// filename := fmt.Sprintf("%s-%d.tar.gz", serviceName, revision) +// return uploadFilesToS3Impl(projectName, serviceName, filename, fileTree, s3Storage) +//} +// +//func uploadFilesToS3Impl(projectName, serviceName, fileName string, fileTree fs.FS, s3StorageIns *s3service.S3) error { +// tmpDir := os.TempDir() +// tarball := filepath.Join(tmpDir, fileName) +// var err error +// if err = fsutil.Tar(fileTree, tarball); err != nil { +// log.Errorf("Failed to archive tarball %s, err: %s", tarball, err) +// return err +// } +// s3Storage := s3StorageIns +// if s3Storage == nil { +// s3Storage, err = s3service.FindDefaultS3() +// if err != nil { +// log.Errorf("Failed to find default s3, err:%v", err) +// return err +// } +// } +// forcedPathStyle := true +// if s3Storage.Provider == setting.ProviderSourceAli { +// forcedPathStyle = false +// } +// client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) +// if err != nil { +// log.Errorf("Failed to get s3 client, err: %s", err) +// return err +// } +// s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, config.ObjectStorageServicePath(projectName, serviceName)) +// objectKey := s3Storage.GetObjectPath(fileName) +// if err = client.Upload(s3Storage.Bucket, tarball, objectKey); err != nil { +// log.Errorf("Failed to upload file %s to s3, err: %s", tarball, err) +// return err +// } +// if err = os.Remove(tarball); err != nil { +// log.Errorf("Failed to remove file %s, err: %s", tarball, err) +// } +// +// return nil +//} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 0d38c8b1..5ac54b86 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/koderover/zadig/pkg/tool/log" "path/filepath" "regexp" "strings" @@ -288,7 +289,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } } } - productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) if err != nil { err = errors.WithMessagef( @@ -297,6 +297,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * p.Task.Namespace, p.Task.ServiceName) return } + renderInfo, err = p.getRenderSet(ctx, productInfo.Render.Name, productInfo.Render.Revision) if err != nil { err = errors.WithMessagef( @@ -396,10 +397,30 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Timeout: time.Second * DeployTimeout, } - path, err := p.downloadService(pipelineTask.ProductName, pipelineTask.ServiceName, pipelineTask.StorageURI) - if err != nil { + //获取此环境中 service的运行版本 + serviceInfoRevision, errGetService := p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, + p.Task.ProductName, p.Task.ServiceRevision) + if errGetService != nil { err = errors.WithMessagef( - err, + errGetService, + "failed to get service revision %s/%s/%d", + p.Task.Namespace, p.Task.ServiceName, p.Task.ServiceRevision) + return + } + + p.Log.Infof("###############[namespace:%v][EnvName:%v][servicename:%v][serviceRevision:%v]" + + "[pipelineTask.ProductName:%v][pipelineTask.ServiceName:%v][pipelineTask.PipelineName:%v] get" + + " service revision info %v", + p.Task.Namespace, p.Task.EnvName, + p.Task.ServiceName, p.Task.ServiceRevision, + pipelineTask.ProductName, pipelineTask.ServiceName,pipelineTask.PipelineName, + serviceInfoRevision.HelmChart.Name) + + path, errDownload := p.downloadService(pipelineTask.ProductName, pipelineTask.ServiceName, + nil, p.Task.ServiceRevision) + if errDownload != nil { + err = errors.WithMessagef( + errDownload, "failed to download service %s/%s", p.Task.Namespace, p.Task.ServiceName) return @@ -448,25 +469,31 @@ func (p *DeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs) (* return prod, nil } -func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, productName string) (*types.ServiceTmpl, error) { +func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, + productName string, revision int64) (*types.ServiceTmpl, error) { url := fmt.Sprintf("/api/service/services/%s/%s", name, serviceType) s := &types.ServiceTmpl{} - _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParam("productName", productName)) + _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ + "productName": productName, + "revision": fmt.Sprintf("%v", revision), + })) if err != nil { return nil, err } return s, nil } -func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI string) (string, error) { +//从s3上下载指定版本的chart打包文件 +func (p *DeployTaskPlugin) downloadService(productName, serviceName string, s3Storage *s3.S3, revision int64) (string, + error) { logger := p.Log base := configbase.LocalServicePath(productName, serviceName) - s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) - if err != nil { - return "", err - } + //s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) + //if err != nil { + // return "", err + //} tarball := fmt.Sprintf("%s.tar.gz", serviceName) tarFilePath := filepath.Join(base, tarball) @@ -480,6 +507,7 @@ func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI p.Log.Errorf("failed to create s3 client, err: %+v", err) return "", err } + log.Infof("############## the tar ball is %v / %v", tarball, s3Storage.GetObjectPath(tarball)) if err = client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return "", err -- Gitee From dbde3e21322a1439699c194cd27efc7829b77d9b Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 1 Sep 2021 17:09:58 +0800 Subject: [PATCH 0002/2647] update chart version store and load logic Signed-off-by: allenshen --- pkg/microservice/aslan/config/config.go | 4 ++ .../aslan/core/common/service/helm.go | 25 ++++++++++++- .../core/environment/service/environment.go | 37 ++++++++++++++----- .../aslan/core/service/service/helm.go | 15 ++++---- 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 22a72f44..e5d876f2 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -233,3 +233,7 @@ func ObjectStorageServicePath(project, service string) string { func LocalServicePath(project, service string) string { return configbase.LocalServicePath(project, service) } + +func ServiceNameWithRevision(serviceName string, revision int64) string { + return fmt.Sprintf("%s-%d", serviceName, revision) +} diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 08a7d195..fe7cf3d6 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -59,6 +59,22 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { return preLoadServiceManifestsFromSource(svc) } +func PreloadServiceManifestsByRevision(base string, svc *commonmodels.Service) error { + ok, err := fsutil.DirExists(base) + if err != nil { + log.Errorf("Failed to check if dir %s is exiting, err: %s", base, err) + return err + } + if ok { + return nil + } + + //download chart info by revision + serviceNameWithRevision := config.ServiceNameWithRevision(svc.ServiceName, svc.Revision) + s3Base := config.ObjectStorageServicePath(svc.ProductName, svc.ServiceName) + return fsservice.DownloadAndExtractFilesFromS3(serviceNameWithRevision, base, s3Base, log.SugaredLogger()) +} + func DownloadServiceManifests(base, projectName, serviceName string) error { s3Base := config.ObjectStorageServicePath(projectName, serviceName) return fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) @@ -67,10 +83,17 @@ func DownloadServiceManifests(base, projectName, serviceName string) error { func SaveAndUploadService(projectName, serviceName string, fileTree fs.FS) error { localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) - return fsservice.SaveAndUploadFiles(fileTree, serviceName, localBase, s3Base, log.SugaredLogger()) } +// SaveAndUploadServiceWithRevision save file to local and upload to s3 +func SaveAndUploadServiceWithRevision(projectName, serviceName string, fileTree fs.FS, revision int64) error { + serviceNameWithRevision := config.ServiceNameWithRevision(serviceName, revision) + localBase := config.LocalServicePath(projectName, serviceNameWithRevision) + s3Base := config.ObjectStorageServicePath(projectName, serviceName) + return fsservice.SaveAndUploadFiles(fileTree, serviceNameWithRevision, localBase, s3Base, log.SugaredLogger()) +} + func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { tree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName}, diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ec66eabb..fe41cc5b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2513,7 +2513,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel for groupIndex, services := range productResp.Services { var wg sync.WaitGroup groupServices := make([]*commonmodels.ProductService, 0) - for _, service := range services { + for serviceIndex, service := range services { if renderChart, isExist := renderChartMap[service.ServiceName]; isExist { opt := &commonrepo.ServiceFindOption{ ServiceName: service.ServiceName, @@ -2526,17 +2526,36 @@ func updateProductVariable(productName, envName string, productResp *commonmodel if err != nil { continue } - - //获取指定版本的chart信息 - wg.Add(1) - go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service) { + go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service, serviceIndex int) { defer wg.Done() - base := config.LocalServicePath(currentService.ProductName, currentService.ServiceName) - if err = commonservice.PreLoadServiceManifests(base, currentService); err != nil { - return + serviceNameWithRevision := config.ServiceNameWithRevision(currentService.ServiceName, currentService.Revision) + base := config.LocalServicePath(currentService.ProductName, serviceNameWithRevision) + if err := commonservice.PreloadServiceManifestsByRevision(base, currentService); err != nil { + // use the latest version when it fails to download the specific version + base = config.LocalServicePath(currentService.ProductName, currentService.ServiceName) + if err = commonservice.PreLoadServiceManifests(base, currentService); err != nil { + log.Errorf("failed to load chart info for service %v", currentService.ServiceName) + return + } + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + currentService.Revision, currentService.ServiceName) + + findOpt := &commonrepo.ServiceFindOption{ + ServiceName: serviceObj.ServiceName, + Type: serviceObj.Type, + ProductName: productName, + ExcludeStatus: setting.ProductStatusDeleting, + } + latestService, err := commonrepo.NewServiceColl().Find(findOpt) + if err != nil { + log.Errorf("failed to get service:%v with latest version", serviceObj.ServiceName) + return + } + services[serviceIndex].Revision = latestService.Revision } + chartSpec := helmclient.ChartSpec{ ReleaseName: fmt.Sprintf("%s-%s", productResp.Namespace, tmpRenderChart.ServiceName), ChartName: fmt.Sprintf("%s/%s", productResp.Namespace, tmpRenderChart.ServiceName), @@ -2554,7 +2573,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel errList = multierror.Append(errList, err) log.Errorf("install helm chart error :%+v", err) } - }(renderChart, serviceObj) + }(renderChart, serviceObj, serviceIndex) } groupServices = append(groupServices, service) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index ffcb900c..3a9b2abf 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -285,7 +285,6 @@ func CreateOrUpdateHelmService(args *HelmServiceReq, log *zap.SugaredLogger) err Name: serviceName, Version: chartVersion, ValuesYaml: valuesYaml, - }, } @@ -301,15 +300,16 @@ func CreateOrUpdateHelmService(args *HelmServiceReq, log *zap.SugaredLogger) err // save files to disk and upload them to s3 if err1 = commonservice.SaveAndUploadService(args.ProductName, serviceName, fsTree); err1 != nil { - log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", args.ProductName, serviceName, err1) + log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, + args.ProductName, err1) err = e.ErrCreateTemplate.AddDesc(err1.Error()) return } // save chart files with revision to disk and upload to s3 - serviceNameWithRevision := fmt.Sprintf("%s-%d", serviceName, rev) - if err = commonservice.SaveAndUploadService(args.ProductName, serviceNameWithRevision, fsTree); err != nil { - log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", args.ProductName, serviceNameWithRevision, err) + if err = commonservice.SaveAndUploadServiceWithRevision(args.ProductName, serviceName, fsTree, rev); err != nil { + log.Errorf("Failed to save or upload files for service %s with revision %v in project %s, error: %s", + serviceName, rev, args.ProductName, err) err = e.ErrCreateTemplate.AddDesc(err.Error()) return } @@ -481,9 +481,8 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { } //store chart with revision info to local and upload to s3 - serviceNameWithRevision := fmt.Sprintf("%s-%d", helmServiceInfo.ServiceName, rev) - if err := commonservice.SaveAndUploadService(args.ProductName, serviceNameWithRevision, - os.DirFS(config.LocalServicePath(args.ProductName, helmServiceInfo.ServiceName))); err != nil { + if err := commonservice.SaveAndUploadServiceWithRevision(args.ProductName, helmServiceInfo.ServiceName, + os.DirFS(config.LocalServicePath(args.ProductName, helmServiceInfo.ServiceName)), rev); err != nil { return e.ErrUpdateTemplate.AddDesc(err.Error()) } -- Gitee From 97d1d1e1b7076647c408710cc3a170c3928ea91b Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 2 Sep 2021 18:03:46 +0800 Subject: [PATCH 0003/2647] update enviroment varibale update and workflow logic --- pkg/config/config.go | 4 + pkg/microservice/aslan/config/config.go | 6 +- .../aslan/core/common/service/helm.go | 2 +- .../aslan/core/environment/handler/router.go | 2 + .../aslan/core/environment/handler/service.go | 16 +++ .../core/environment/service/environment.go | 3 +- .../aslan/core/environment/service/service.go | 30 ++++ .../aslan/core/service/service/helm.go | 64 +-------- .../core/service/taskplugin/deploy.go | 132 ++++++++++++------ pkg/tool/helmclient/helmclient.go | 2 +- 10 files changed, 151 insertions(+), 110 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 00fc752a..1a1d5a46 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -191,6 +191,10 @@ func LocalServicePath(project, service string) string { return filepath.Join(DataPath(), project, service) } +func LocalServicePathWithRevision(project, service, revision string) string { + return filepath.Join(DataPath(), project, service, revision) +} + func LocalTemplatePath(name, kind string) string { return filepath.Join(DataPath(), "templates", kind, name) } diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index e5d876f2..76e207db 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -231,7 +231,11 @@ func ObjectStorageServicePath(project, service string) string { } func LocalServicePath(project, service string) string { - return configbase.LocalServicePath(project, service) + return configbase.LocalServicePathWithRevision(project, service, "latest") +} + +func LocalServicePathWithRevision(project, service string, revision int64) string { + return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("%d", revision)) } func ServiceNameWithRevision(serviceName string, revision int64) string { diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index fe7cf3d6..ab1a01d5 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -89,7 +89,7 @@ func SaveAndUploadService(projectName, serviceName string, fileTree fs.FS) error // SaveAndUploadServiceWithRevision save file to local and upload to s3 func SaveAndUploadServiceWithRevision(projectName, serviceName string, fileTree fs.FS, revision int64) error { serviceNameWithRevision := config.ServiceNameWithRevision(serviceName, revision) - localBase := config.LocalServicePath(projectName, serviceNameWithRevision) + localBase := config.LocalServicePathWithRevision(projectName, serviceName, revision) s3Base := config.ObjectStorageServicePath(projectName, serviceName) return fsservice.SaveAndUploadFiles(fileTree, serviceNameWithRevision, localBase, s3Base, log.SugaredLogger()) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 694b8643..2508f016 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -118,6 +118,8 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/services/:serviceName", GetService) environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateService) + environments.PUT("/:productName/services/:serviceName/updateRevision", UpdateServiceRevision) + //environments.PUT("/:productName/services/:serviceName/updateRevision", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateServiceRevision) environments.POST("/:productName/services/:serviceName/restart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, RestartService) environments.POST("/:productName/services/:serviceName/restartNew", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID, permission.TestUpdateEnvUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, RestartNewService) environments.POST("/:productName/services/:serviceName/scale/:number", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, ScaleService) diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index 0a13c5cf..831f36fa 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -20,6 +20,7 @@ import ( "fmt" "strconv" + "github.com/koderover/zadig/pkg/tool/log" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" @@ -76,6 +77,21 @@ func UpdateService(c *gin.Context) { ctx.Err = service.UpdateService(args, ctx.Logger) } +// update revison of a service in product +func UpdateServiceRevision(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新Revision", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), fmt.Sprintf("%s,%s", permission.TestEnvManageUUID, permission.ProdEnvManageUUID), "", ctx.Logger) + + revision, err := strconv.ParseInt(c.DefaultQuery("revision", "0"), 10, 64) + if err != nil || revision == 0 { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") + return + } + log.Infof("############# [envName %v][productName %v][serviceName %v]", c.Query("envName"), c.Param("productName"), c.Param("serviceName")) + ctx.Err = service.UpdateServiceRevision(c.Query("envName"), c.Param("productName"), c.Param("serviceName"), revision, ctx.Logger) +} + func RestartNewService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index fe41cc5b..ac72321b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2530,8 +2530,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service, serviceIndex int) { defer wg.Done() - serviceNameWithRevision := config.ServiceNameWithRevision(currentService.ServiceName, currentService.Revision) - base := config.LocalServicePath(currentService.ProductName, serviceNameWithRevision) + base := config.LocalServicePathWithRevision(currentService.ProductName, currentService.ServiceName, currentService.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, currentService); err != nil { // use the latest version when it fails to download the specific version base = config.LocalServicePath(currentService.ProductName, currentService.ServiceName) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index de8cdf5e..d30d0b2f 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -412,6 +412,36 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return nil } +func UpdateServiceRevision(envName, productName, serviceName string, targetRevision int64, log *zap.SugaredLogger) (err error) { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + productInfo, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + groupIndex := -1 + var target []*commonmodels.ProductService + for gIndex, serviceGroup := range productInfo.Services { + for _, service := range serviceGroup { + if service.ServiceName == serviceName { + groupIndex = gIndex + target = serviceGroup + service.Revision = targetRevision + break + } + } + } + + if groupIndex < 0 { + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("servce: %v not found", serviceName)) + } + + err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, target) + if err != nil { + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + return nil +} + func queryPodsStatus(namespace, productName, serviceName string, kubeClient client.Client, log *zap.SugaredLogger) (string, string, []string) { ls := labels.Set{} if productName != "" { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 3a9b2abf..06fec0b0 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -18,8 +18,6 @@ package service import ( "fmt" - fsutil "github.com/koderover/zadig/pkg/util/fs" - "io/fs" "os" "path/filepath" "regexp" @@ -605,64 +603,4 @@ func recursionGetImageByColon(jsonValues map[string]interface{}) ([]string, []*m } } return banList.List(), ret -} - -func saveInMemoryFilesToDisk(projectName, serviceName string, fileTree fs.FS) error { - root := config.LocalServicePath(projectName, serviceName) - - // remove existing files - err := os.RemoveAll(root) - if err != nil { - return err - } - - return fsutil.SaveToDisk(fileTree, root) -} - -//func uploadFilesToS3(projectName, serviceName string, fileTree fs.FS) error { -// fileName := fmt.Sprintf("%s.tar.gz", serviceName) -// return uploadFilesToS3Impl(projectName, serviceName, fileName, fileTree, nil) -//} -// -//func uploadFilesToS3ByPrevision(projectName, serviceName string, revision int64, fileTree fs.FS, s3Storage *s3service.S3) error { -// filename := fmt.Sprintf("%s-%d.tar.gz", serviceName, revision) -// return uploadFilesToS3Impl(projectName, serviceName, filename, fileTree, s3Storage) -//} -// -//func uploadFilesToS3Impl(projectName, serviceName, fileName string, fileTree fs.FS, s3StorageIns *s3service.S3) error { -// tmpDir := os.TempDir() -// tarball := filepath.Join(tmpDir, fileName) -// var err error -// if err = fsutil.Tar(fileTree, tarball); err != nil { -// log.Errorf("Failed to archive tarball %s, err: %s", tarball, err) -// return err -// } -// s3Storage := s3StorageIns -// if s3Storage == nil { -// s3Storage, err = s3service.FindDefaultS3() -// if err != nil { -// log.Errorf("Failed to find default s3, err:%v", err) -// return err -// } -// } -// forcedPathStyle := true -// if s3Storage.Provider == setting.ProviderSourceAli { -// forcedPathStyle = false -// } -// client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) -// if err != nil { -// log.Errorf("Failed to get s3 client, err: %s", err) -// return err -// } -// s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, config.ObjectStorageServicePath(projectName, serviceName)) -// objectKey := s3Storage.GetObjectPath(fileName) -// if err = client.Upload(s3Storage.Bucket, tarball, objectKey); err != nil { -// log.Errorf("Failed to upload file %s to s3, err: %s", tarball, err) -// return err -// } -// if err = os.Remove(tarball); err != nil { -// log.Errorf("Failed to remove file %s, err: %s", tarball, err) -// } -// -// return nil -//} +} \ No newline at end of file diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 5ac54b86..09f2c26b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -20,12 +20,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/koderover/zadig/pkg/tool/log" + "os" "path/filepath" "regexp" "strings" "time" + fsutil "github.com/koderover/zadig/pkg/util/fs" + "github.com/pkg/errors" "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" @@ -397,41 +399,52 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Timeout: time.Second * DeployTimeout, } - //获取此环境中 service的运行版本 - serviceInfoRevision, errGetService := p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, - p.Task.ProductName, p.Task.ServiceRevision) - if errGetService != nil { - err = errors.WithMessagef( - errGetService, - "failed to get service revision %s/%s/%d", - p.Task.Namespace, p.Task.ServiceName, p.Task.ServiceRevision) - return - } + //p.Log.Infof("###############[namespace:%v][EnvName:%v][servicename:%v][serviceRevision:%v]"+ + // "[pipelineTask.ProductName:%v][pipelineTask.ServiceName:%v][pipelineTask.PipelineName:%v] get", + // p.Task.Namespace, p.Task.EnvName, + // p.Task.ServiceName, p.Task.ServiceRevision, + // pipelineTask.ProductName, pipelineTask.ServiceName, pipelineTask.PipelineName) - p.Log.Infof("###############[namespace:%v][EnvName:%v][servicename:%v][serviceRevision:%v]" + - "[pipelineTask.ProductName:%v][pipelineTask.ServiceName:%v][pipelineTask.PipelineName:%v] get" + - " service revision info %v", - p.Task.Namespace, p.Task.EnvName, - p.Task.ServiceName, p.Task.ServiceRevision, - pipelineTask.ProductName, pipelineTask.ServiceName,pipelineTask.PipelineName, - serviceInfoRevision.HelmChart.Name) + targetRevision := int64(0) - path, errDownload := p.downloadService(pipelineTask.ProductName, pipelineTask.ServiceName, - nil, p.Task.ServiceRevision) + path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, + pipelineTask.StorageURI, p.Task.ServiceRevision) if errDownload != nil { - err = errors.WithMessagef( - errDownload, - "failed to download service %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return + path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, + pipelineTask.StorageURI, 0) + if errDownload != nil { + err = errors.WithMessagef( + errDownload, + "failed to download service %s/%s", + p.Task.Namespace, p.Task.ServiceName) + _ = os.Remove(path) + return + } + + //获取当前service的最新revision + latestService, errGetService := p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, + pipelineTask.ProductName, 0) + if errGetService != nil { + err = errors.WithMessagef( + errDownload, + "failed to get latest service %s/%s", + p.Task.Namespace, p.Task.ServiceName) + _ = os.Remove(path) + } + + //当前实际的revision + targetRevision = latestService.Revision } + p.Log.Infof("################ the path of chart is %v targetRevision %v", path, targetRevision) + if err = helmClient.InstallOrUpgradeChart(context.Background(), &chartSpec, &helmclient.ChartOption{ ChartPath: path}, p.Log); err != nil { err = errors.WithMessagef( err, - "failed to Install helm chart %s/%s", + "failed to install helm chart %s/%s", p.Task.Namespace, p.Task.ServiceName) + _ = os.Remove(path) return } @@ -448,6 +461,17 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ChartInfos: renderInfo.ChartInfos, }) + // update product.service.revision when use the latest revision + if targetRevision > 0 && targetRevision != p.Task.ServiceRevision { + p.Log.Infof("############# update product.service.revision, current %v target %v", + p.Task.ServiceRevision, targetRevision) + err = p.updateServiceRevision(ctx, p.Task.ServiceName, pipelineTask.ProductName, p.Task.EnvName, p.Task.ServiceRevision) + if err != nil { + p.Log.Infof("############# update service revison fail [env %v][productName %v][serviceName %v] err %v", p.Task.EnvName, p.Task.ProductName, p.Task.ServiceName, err) + } + } + + //TODO 检查task的revision 和 product中是否吻合 } return } @@ -476,7 +500,7 @@ func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, s := &types.ServiceTmpl{} _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ "productName": productName, - "revision": fmt.Sprintf("%v", revision), + "revision": fmt.Sprintf("%v", revision), })) if err != nil { return nil, err @@ -484,35 +508,59 @@ func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, return s, nil } -//从s3上下载指定版本的chart打包文件 -func (p *DeployTaskPlugin) downloadService(productName, serviceName string, s3Storage *s3.S3, revision int64) (string, - error) { +func (p *DeployTaskPlugin) updateServiceRevision(ctx context.Context, name, productName, envName string, + revision int64) error { + url := fmt.Sprintf("/api/environment/environments/%s/services/%s/updateRevision", productName, name) + + _, err := p.httpClient.Put(url, httpclient.SetQueryParams(map[string]string{ + "productName": productName, + "envName": envName, + "revision": fmt.Sprintf("%v", revision), + })) + return err +} + +// download chart info of specific version, use the latest version if fails +func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { logger := p.Log - base := configbase.LocalServicePath(productName, serviceName) - //s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) - //if err != nil { - // return "", err - //} + s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) + if err != nil { + return "", err + } + + fileName := serviceName + if revision > 0 { + fileName = fmt.Sprintf("%s-%d", serviceName, revision) + } + tarball := fmt.Sprintf("%s.tar.gz", fileName) + localBase := configbase.LocalServicePath(productName, serviceName) + tarFilePath := filepath.Join(localBase, tarball) + + if exists, _ := fsutil.FileExists(tarFilePath); exists { + return tarFilePath, nil + } - tarball := fmt.Sprintf("%s.tar.gz", serviceName) - tarFilePath := filepath.Join(base, tarball) s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, configbase.ObjectStorageServicePath(productName, serviceName)) forcedPathStyle := true if s3Storage.Provider == setting.ProviderSourceAli { forcedPathStyle = false } - client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err != nil { - p.Log.Errorf("failed to create s3 client, err: %+v", err) - return "", err + client, err1 := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err1 != nil { + p.Log.Errorf("failed to create s3 client, err: %+v", err1) + return "", err1 } - log.Infof("############## the tar ball is %v / %v", tarball, s3Storage.GetObjectPath(tarball)) if err = client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) + _ = os.Remove(tarFilePath) return "", err } + if exist, _ := fsutil.FileExists(tarFilePath); !exist { + return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) + } + return tarFilePath, nil } diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index d540cc9b..e2f1fb09 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -109,7 +109,7 @@ func newClient(options *Options, clientGetter genericclioptions.RESTClientGetter settings.Namespace(), os.Getenv("HELM_DRIVER"), func(format string, v ...interface{}) { - log.Infof(format, v) + log.Infof(format, v...) }, ) if err != nil { -- Gitee From d8ae394125bd47b41b5849cc8d1590e47d8a596a Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 2 Sep 2021 21:01:59 +0800 Subject: [PATCH 0004/2647] remove useless code --- .../aslan/core/environment/handler/service.go | 2 - .../aslan/core/environment/service/service.go | 10 +++-- .../core/service/taskplugin/deploy.go | 38 +++++++++++-------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index 831f36fa..1e84bafe 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -20,7 +20,6 @@ import ( "fmt" "strconv" - "github.com/koderover/zadig/pkg/tool/log" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" @@ -88,7 +87,6 @@ func UpdateServiceRevision(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") return } - log.Infof("############# [envName %v][productName %v][serviceName %v]", c.Query("envName"), c.Param("productName"), c.Param("serviceName")) ctx.Err = service.UpdateServiceRevision(c.Query("envName"), c.Param("productName"), c.Param("serviceName"), revision, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index d30d0b2f..f2d1615c 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -418,24 +418,28 @@ func UpdateServiceRevision(envName, productName, serviceName string, targetRevis if err != nil { return e.ErrUpdateEnv.AddDesc(err.Error()) } + groupIndex := -1 - var target []*commonmodels.ProductService + var targetGroup []*commonmodels.ProductService for gIndex, serviceGroup := range productInfo.Services { for _, service := range serviceGroup { if service.ServiceName == serviceName { groupIndex = gIndex - target = serviceGroup + targetGroup = serviceGroup service.Revision = targetRevision break } } + if groupIndex >= 0 { + break + } } if groupIndex < 0 { return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("servce: %v not found", serviceName)) } - err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, target) + err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, targetGroup) if err != nil { return e.ErrUpdateEnv.AddDesc(err.Error()) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 09f2c26b..bf705d7e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -311,6 +311,20 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * for _, chartInfo := range renderInfo.ChartInfos { chartInfoMap[chartInfo.ServiceName] = chartInfo } + + curRevisionInProduct := int64(0) + for _, serviceGroup := range productInfo.Services { + for _, service := range serviceGroup { + if service.ServiceName == p.Task.ServiceName { + curRevisionInProduct = service.Revision + break + } + } + if curRevisionInProduct > 0 { + break + } + } + if renderChart, isExist = chartInfoMap[p.Task.ServiceName]; isExist { yamlValuesByte, err = yaml.YAMLToJSON([]byte(renderChart.ValuesYaml)) if err != nil { @@ -399,16 +413,14 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Timeout: time.Second * DeployTimeout, } - //p.Log.Infof("###############[namespace:%v][EnvName:%v][servicename:%v][serviceRevision:%v]"+ - // "[pipelineTask.ProductName:%v][pipelineTask.ServiceName:%v][pipelineTask.PipelineName:%v] get", - // p.Task.Namespace, p.Task.EnvName, - // p.Task.ServiceName, p.Task.ServiceRevision, - // pipelineTask.ProductName, pipelineTask.ServiceName, pipelineTask.PipelineName) - - targetRevision := int64(0) + //task执行时候 product.service.revision 可能已经更新,需要使用当前环境中的service.revision + targetRevision := p.Task.ServiceRevision + if curRevisionInProduct > 0 && curRevisionInProduct != p.Task.ServiceRevision { + targetRevision = curRevisionInProduct + } path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, - pipelineTask.StorageURI, p.Task.ServiceRevision) + pipelineTask.StorageURI, targetRevision) if errDownload != nil { path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, 0) @@ -436,8 +448,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * targetRevision = latestService.Revision } - p.Log.Infof("################ the path of chart is %v targetRevision %v", path, targetRevision) - if err = helmClient.InstallOrUpgradeChart(context.Background(), &chartSpec, &helmclient.ChartOption{ ChartPath: path}, p.Log); err != nil { err = errors.WithMessagef( @@ -463,15 +473,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * // update product.service.revision when use the latest revision if targetRevision > 0 && targetRevision != p.Task.ServiceRevision { - p.Log.Infof("############# update product.service.revision, current %v target %v", - p.Task.ServiceRevision, targetRevision) - err = p.updateServiceRevision(ctx, p.Task.ServiceName, pipelineTask.ProductName, p.Task.EnvName, p.Task.ServiceRevision) + err = p.updateServiceRevision(ctx, p.Task.ServiceName, pipelineTask.ProductName, p.Task.EnvName, targetRevision) if err != nil { - p.Log.Infof("############# update service revison fail [env %v][productName %v][serviceName %v] err %v", p.Task.EnvName, p.Task.ProductName, p.Task.ServiceName, err) + p.Log.Errorf("update service version fail [env:%v][productName:%v][serviceName:%v], err %v", p.Task.EnvName, p.Task.ProductName, p.Task.ServiceName, err) } } - - //TODO 检查task的revision 和 product中是否吻合 } return } -- Gitee From 8ec6dba4745d6553be2b21ee31033f6237f3909b Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 3 Sep 2021 11:32:03 +0800 Subject: [PATCH 0005/2647] add permission request to api Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/router.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 2508f016..c90a18bb 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -118,8 +118,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/services/:serviceName", GetService) environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateService) - environments.PUT("/:productName/services/:serviceName/updateRevision", UpdateServiceRevision) - //environments.PUT("/:productName/services/:serviceName/updateRevision", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateServiceRevision) + environments.PUT("/:productName/services/:serviceName/updateRevision", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateServiceRevision) environments.POST("/:productName/services/:serviceName/restart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, RestartService) environments.POST("/:productName/services/:serviceName/restartNew", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID, permission.TestUpdateEnvUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, RestartNewService) environments.POST("/:productName/services/:serviceName/scale/:number", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, ScaleService) -- Gitee From 75041df447d452eb044a9fefd24df00d1de27fcc Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sun, 5 Sep 2021 18:08:16 +0800 Subject: [PATCH 0006/2647] test labeler Signed-off-by: landylee007 --- pkg/microservice/aslan/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/server/server.go b/pkg/microservice/aslan/server/server.go index cbbeb01f..042c6020 100644 --- a/pkg/microservice/aslan/server/server.go +++ b/pkg/microservice/aslan/server/server.go @@ -52,7 +52,7 @@ func Serve(ctx context.Context) error { defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Errorf("Failed to stop server, error: %s", err) + log.Errorf("Failed to stop server, error msg: %s", err) } }() -- Gitee From 1f5818f4f82e44972777d74d43bab0820b7d8339 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sun, 5 Sep 2021 18:38:59 +0800 Subject: [PATCH 0007/2647] test labeler Signed-off-by: landylee007 --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index 16e71a8d..d1f62cc5 100644 --- a/action.yml +++ b/action.yml @@ -16,3 +16,4 @@ inputs: runs: using: 'node12' main: 'dist/index.js' + -- Gitee From 9df74200dd706533dc22873bf8065cdd4f8cf974 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sun, 5 Sep 2021 18:41:35 +0800 Subject: [PATCH 0008/2647] test labeler Signed-off-by: landylee007 --- pkg/microservice/cron/core/service/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 2fdddf7c..bca00324 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -119,7 +119,7 @@ func (schedule *Schedule) Validate() error { return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) } if schedule.Number <= 0 { - return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0", e.InvalidFormatErrMsg) + return fmt.Errorf("%s 间隔循环时间间隔不能小于等于 0", e.InvalidFormatErrMsg) } //if schedule.Frequency == FrequencyMinutes && schedule.Number < 1 { // log.Info("minimum schedule minutes must >= 30") -- Gitee From 3f4eee1f60a8d91ad285d1b725fbc825f7a610cf Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sun, 5 Sep 2021 18:44:44 +0800 Subject: [PATCH 0009/2647] test labeler Signed-off-by: landylee007 --- pkg/microservice/cron/core/service/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index bca00324..a801b9b6 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -119,7 +119,7 @@ func (schedule *Schedule) Validate() error { return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) } if schedule.Number <= 0 { - return fmt.Errorf("%s 间隔循环时间间隔不能小于等于 0", e.InvalidFormatErrMsg) + return fmt.Errorf("%s 间隔循环时间间隔不能小于等于 0", e.InvalidFormatErrMsg) } //if schedule.Frequency == FrequencyMinutes && schedule.Number < 1 { // log.Info("minimum schedule minutes must >= 30") -- Gitee From 7b653ef715dd191755d124f0761af20776d1772e Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sun, 5 Sep 2021 18:49:41 +0800 Subject: [PATCH 0010/2647] test labeler Signed-off-by: landylee007 --- pkg/microservice/aslan/server/server.go | 2 +- pkg/microservice/cron/core/service/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/server/server.go b/pkg/microservice/aslan/server/server.go index d132539c..cbbeb01f 100644 --- a/pkg/microservice/aslan/server/server.go +++ b/pkg/microservice/aslan/server/server.go @@ -52,7 +52,7 @@ func Serve(ctx context.Context) error { defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Errorf("Failed to stop server, error msgs: %s", err) + log.Errorf("Failed to stop server, error: %s", err) } }() diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index a801b9b6..2fdddf7c 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -119,7 +119,7 @@ func (schedule *Schedule) Validate() error { return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) } if schedule.Number <= 0 { - return fmt.Errorf("%s 间隔循环时间间隔不能小于等于 0", e.InvalidFormatErrMsg) + return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0", e.InvalidFormatErrMsg) } //if schedule.Frequency == FrequencyMinutes && schedule.Number < 1 { // log.Info("minimum schedule minutes must >= 30") -- Gitee From ab9d86df132a7fc5c8803d88c98414e932fbe64f Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sun, 5 Sep 2021 18:58:07 +0800 Subject: [PATCH 0011/2647] test labeler Signed-off-by: landylee007 --- pkg/microservice/aslan/server/server.go | 2 +- pkg/microservice/cron/core/service/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/server/server.go b/pkg/microservice/aslan/server/server.go index cbbeb01f..1fffdca8 100644 --- a/pkg/microservice/aslan/server/server.go +++ b/pkg/microservice/aslan/server/server.go @@ -52,7 +52,7 @@ func Serve(ctx context.Context) error { defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Errorf("Failed to stop server, error: %s", err) + log.Errorf("Failed to stop server, error : %s", err) } }() diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 2fdddf7c..c631b870 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -119,7 +119,7 @@ func (schedule *Schedule) Validate() error { return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) } if schedule.Number <= 0 { - return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0", e.InvalidFormatErrMsg) + return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0 ", e.InvalidFormatErrMsg) } //if schedule.Frequency == FrequencyMinutes && schedule.Number < 1 { // log.Info("minimum schedule minutes must >= 30") -- Gitee From 52f2cdcbe6397a482a73904643831c10176f4e1c Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 23 Sep 2021 11:50:55 +0800 Subject: [PATCH 0012/2647] update contianer parse logic Signed-off-by: allenshen --- .../core/common/repository/models/service.go | 12 +- .../aslan/core/common/service/service.go | 57 +++++ .../core/environment/service/environment.go | 95 +++++++- .../aslan/core/environment/service/product.go | 5 +- .../aslan/core/project/service/product.go | 5 +- .../aslan/core/service/service/helm.go | 93 +++----- .../core/service/taskplugin/deploy.go | 197 ++++++++-------- .../core/service/types/service_tmpl.go | 12 +- pkg/util/yaml/path_search.go | 213 ++++++++++++++++++ pkg/util/yaml/path_search_test.go | 148 ++++++++++++ 10 files changed, 668 insertions(+), 169 deletions(-) create mode 100644 pkg/util/yaml/path_search.go create mode 100644 pkg/util/yaml/path_search_test.go diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 2262937e..e92bf9c1 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -92,10 +92,18 @@ type Commit struct { Message string `bson:"message" json:"message"` } +// ImagePathSpec paths in yaml used to parse image +type ImagePathSpec struct { + RepoPath string `bson:"repo_path,omitempty" json:"repoPath,omitempty"` + ImagePath string `bson:"image_path,omitempty" json:"imagePath,omitempty"` + TagPath string `bson:"tag_path,omitempty" json:"tagPath,omitempty"` +} + // Container ... type Container struct { - Name string `bson:"name" json:"name"` - Image string `bson:"image" json:"image"` + Name string `bson:"name" json:"name"` + Image string `bson:"image" json:"image"` + ImagePathSpec *ImagePathSpec `bson:"image_path_spec,omitempty" json:"imagePathSpec,omitempty"` } // ServiceTmplPipeResp ...router diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index f076b8dc..0190dbfa 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -19,6 +19,7 @@ package service import ( "encoding/json" "fmt" + "regexp" "strings" "go.uber.org/zap" @@ -81,6 +82,10 @@ type ServiceProductMap struct { GerritRemoteName string `json:"gerrit_remote_name,omitempty"` } +var ( + imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) +) + // ListServiceTemplate 列出服务模板 func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTmplResp, error) { var err error @@ -507,3 +512,55 @@ func getAddressFromPath(path, owner, repo string, logger *zap.Logger) string { return res[0] } + +// get values form source flat map +// convert map[k]absolutePath to map[k]value +func getValuesByPath(paths map[string]string, flatMap map[string]interface{}) map[string]interface{} { + ret := make(map[string]interface{}) + for k, path := range paths { + if value, ok := flatMap[path]; ok { + ret[k] = value + } else { + ret[k] = nil + } + } + return ret +} + +// GeneImageUri generate valid image uri +func GeneImageUri(pathData map[string]string, flatMap map[string]interface{}) string { + valuesMap := getValuesByPath(pathData, flatMap) + ret := "" + if repo, ok := valuesMap["repo"]; ok { + ret = fmt.Sprintf("%v", repo) + ret = strings.TrimSuffix(ret, "/") + } + if image, ok := valuesMap["image"]; ok { + imageStr := fmt.Sprintf("%v", image) + if ret == "" { + ret = imageStr + } else { + ret = fmt.Sprintf("%s/%s", ret, imageStr) + } + } + if tag, ok := valuesMap["tag"]; ok { + tagStr := fmt.Sprintf("%v", tag) + if tagStr != "" { + ret = fmt.Sprintf("%s:%s", ret, tagStr) + } + } + return ret +} + +func ExtractImageName(imageUrl string) string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + if exNames[i] == "image" { + return matchedStr + } + } + } + return "" +} diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 381aabbb..7ace7707 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -45,6 +45,7 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -62,6 +63,7 @@ import ( "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) const ( @@ -117,7 +119,7 @@ type UpdateMultiHelmProductArg struct { ProductName string `json:"productName"` EnvNames []string `json:"envNames"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` - ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented + ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented } func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { @@ -812,8 +814,9 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, + Name: c.Name, + Image: c.Image, + ImagePathSpec: c.ImagePathSpec, } serviceResp.Containers = append(serviceResp.Containers, container) } @@ -1217,6 +1220,79 @@ func UpdateMultiHelmProduct(envNames []string, updateType, productName string, u return envStatuses } +func fillContainerParseInfo(prod *commonmodels.Product) error { + rendersetMap := make(map[string]string) + for _, singleData := range prod.ChartInfos { + rendersetMap[singleData.ServiceName] = singleData.ValuesYaml + } + + patterns := []map[string]string{ + {"image": "repository", "tag": "tag"}, + {"image": "repository"}, + } + + fillHappen := false + + // fill the parse info + for _, serviceGroup := range prod.Services { + for _, singleService := range serviceGroup { + findEmptySpec := false + for _, container := range singleService.Containers { + if container.ImagePathSpec == nil { + findEmptySpec = true + break + } + } + if !findEmptySpec { + continue + } + flatMap, err := converter.YamlToFlatMap([]byte(rendersetMap[singleService.ServiceName])) + if err != nil { + return err + } + for _, container := range singleService.Containers { + if container.ImagePathSpec != nil { + continue + } + err = findImageByContainerName(flatMap, patterns, container) + if err != nil { + return err + } + fillHappen = true + } + } + } + + if fillHappen { + err := commonrepo.NewProductColl().Update(prod) + if err != nil { + return err + } + } + return nil +} + +// find match rule +func findImageByContainerName(flatMap map[string]interface{}, patterns []map[string]string, container *models.Container) error { + matchedPath, err := yamlutil.SearchByPattern(flatMap, patterns) + if err != nil { + return err + } + for _, searchResult := range matchedPath { + imageUrl := commonservice.GeneImageUri(searchResult, flatMap) + if container.Name != commonservice.ExtractImageName(imageUrl) { + continue + } + container.ImagePathSpec = &models.ImagePathSpec{ + RepoPath: searchResult["repo"], + ImagePath: searchResult["image"], + TagPath: searchResult["tag"], + } + break + } + return nil +} + func GetProductInfo(username, envName, productName string, log *zap.SugaredLogger) (*commonmodels.Product, error) { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) @@ -1234,6 +1310,11 @@ func GetProductInfo(username, envName, productName string, log *zap.SugaredLogge } prod.ChartInfos = renderSet.ChartInfos + err = fillContainerParseInfo(prod) + if err != nil { + log.Errorf("fill helm service parse data fail, productName %s err %s", productName, err.Error()) + } + return prod, nil } @@ -2763,10 +2844,10 @@ func updateProductVariable(productName, envName string, productResp *commonmodel for _, service := range services { if renderChart, isExist := renderChartMap[service.ServiceName]; isExist { opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: productName, + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: productName, //ExcludeStatus: setting.ProductStatusDeleting, } serviceObj, err := commonrepo.NewServiceColl().Find(opt) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 7b41c99d..01bc352a 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -148,8 +148,9 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, + Name: c.Name, + Image: c.Image, + ImagePathSpec: c.ImagePathSpec, } serviceResp.Containers = append(serviceResp.Containers, container) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 6f728fe6..475362ab 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -575,8 +575,9 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, + Name: c.Name, + Image: c.Image, + ImagePathSpec: c.ImagePathSpec, } serviceResp.Containers = append(serviceResp.Containers, container) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index f48c356f..0e91a028 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -21,16 +21,16 @@ import ( "io/fs" "os" "path/filepath" - "regexp" "strings" "sync" "time" "github.com/27149chen/afero" "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/util/converter" "github.com/otiai10/copy" + "github.com/pkg/errors" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/yaml" @@ -452,10 +452,12 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg if err = commonrepo.NewServiceColl().Delete(args.ServiceName, setting.HelmDeployType, args.ProductName, setting.ProductStatusDeleting, rev); err != nil { logger.Warnf("Failed to delete stale service %s with revision %d, err: %s", args.ServiceName, rev, err) } - containerList := recursionGetImage(valuesMap) - if len(containerList) == 0 { - _, containerList = recursionGetImageByColon(valuesMap) + + containerList, err := parseContainers(valuesMap) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse service from yaml") } + serviceObj := &models.Service{ ServiceName: args.ServiceName, Type: setting.HelmDeployType, @@ -566,10 +568,11 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { return e.ErrCreateTemplate.AddDesc("values.yaml解析失败") } - containerList := recursionGetImage(valuesMap) - if len(containerList) == 0 { - _, containerList = recursionGetImageByColon(valuesMap) + containerList, err := parseContainers(valuesMap) + if err != nil { + return e.ErrUpdateTemplate.AddErr(errors.Wrapf(err, "failed to parse images from yaml")) } + preServiceTmpl.Containers = containerList preServiceTmpl.HelmChart.ValuesYaml = helmServiceInfo.FileContent @@ -682,57 +685,31 @@ func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, } } -// 递归通过repository和tag获取服务组件 -func recursionGetImage(jsonValues map[string]interface{}) []*models.Container { - ret := make([]*models.Container, 0) - for jsonKey, jsonValue := range jsonValues { - if levelMap, ok := jsonValue.(map[string]interface{}); ok { - ret = append(ret, recursionGetImage(levelMap)...) - } else if repository, isStr := jsonValue.(string); isStr { - if strings.Contains(jsonKey, "repository") { - serviceContainer := new(models.Container) - if imageTag, isExist := jsonValues["tag"]; isExist { - if imageTag != "" { - serviceContainer.Image = fmt.Sprintf("%s:%v", repository, imageTag) - imageStr := strings.Split(repository, "/") - if len(imageStr) > 1 { - serviceContainer.Name = imageStr[len(imageStr)-1] - } - ret = append(ret, serviceContainer) - } - } - } - } +func parseContainers(nested map[string]interface{}) ([]*models.Container, error) { + patterns := []map[string]string{ + {"image": "repository", "tag": "tag"}, + {"image": "repository"}, + } + flatMap, err := converter.Flatten(nested) + if err != nil { + return nil, err + } + matchedPath, err := yamlutil.SearchByPattern(flatMap, patterns) + if err != nil { + return nil, err } - return ret -} - -func recursionGetImageByColon(jsonValues map[string]interface{}) ([]string, []*models.Container) { - imageRegEx := regexp.MustCompile(config.ImageRegexString) ret := make([]*models.Container, 0) - banList := sets.NewString() - - for _, jsonValue := range jsonValues { - if levelMap, ok := jsonValue.(map[string]interface{}); ok { - imageList, recursiveRet := recursionGetImageByColon(levelMap) - ret = append(ret, recursiveRet...) - banList.Insert(imageList...) - } else if imageName, isStr := jsonValue.(string); isStr { - if strings.Contains(imageName, ":") && imageRegEx.MatchString(imageName) && - !strings.Contains(imageName, "http") && !strings.Contains(imageName, "https") { - serviceContainer := new(models.Container) - serviceContainer.Image = imageName - imageArr := strings.Split(imageName, "/") - if len(imageArr) >= 1 { - imageTagArr := strings.Split(imageArr[len(imageArr)-1], ":") - serviceContainer.Name = imageTagArr[0] - } - if !banList.Has(imageName) { - banList.Insert(imageName) - ret = append(ret, serviceContainer) - } - } - } + for _, searchResult := range matchedPath { + imageUrl := commonservice.GeneImageUri(searchResult, flatMap) + ret = append(ret, &models.Container{ + Name: commonservice.ExtractImageName(imageUrl), + Image: imageUrl, + ImagePathSpec: &models.ImagePathSpec{ + RepoPath: searchResult["repo"], + ImagePath: searchResult["image"], + TagPath: searchResult["tag"], + }, + }) } - return banList.List(), ret + return ret, nil } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 99c9e575..e28659b6 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -47,9 +47,12 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" + "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) // InitializeDeployTaskPlugin to initiate deploy task plugin and return ref @@ -84,12 +87,12 @@ func (p *DeployTaskPlugin) SetAckFunc(func()) { const ( // DeployTimeout ... - DeployTimeout = 60 * 10 // 10 minutes - ImageRegexString = "^[a-zA-Z0-9.:\\/-]+$" + DeployTimeout = 60 * 10 // 10 minutes + imageUrlParseRegexString = `(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?` ) var ( - ImageRegex = regexp.MustCompile(ImageRegexString) + imageParseRegex = regexp.MustCompile(imageUrlParseRegexString) ) // Init ... @@ -376,9 +379,29 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * productInfo.Render.Name, productInfo.Render.Revision) return } + + var targetContainer *types.Container + for _, serviceGroup := range productInfo.Services { + for _, service := range serviceGroup { + if service.ServiceName == p.Task.ServiceName { + for _, container := range service.Containers { + if container.Name == p.Task.ContainerName { + targetContainer = container + } + } + } + } + } + + if targetContainer == nil { + err = fmt.Errorf("failed to find container %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + return + } + for _, chartInfo := range renderInfo.ChartInfos { chartInfoMap[chartInfo.ServiceName] = chartInfo } + if renderChart, isExist = chartInfoMap[p.Task.ServiceName]; isExist { yamlValuesByte, err = yaml.YAMLToJSON([]byte(renderChart.ValuesYaml)) if err != nil { @@ -398,53 +421,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } - //找到需要替换的镜像 - p.ReplaceImage = "" - p.recursionReplaceImage(currentValuesYamlMap, p.Task.Image) - if p.ReplaceImage != "" { - //先替换镜像名称 - var oldImageName, newImageName, newImageTag string - oldImageArr := strings.Split(p.ReplaceImage, ":") - newImageArr := strings.Split(p.Task.Image, ":") - - if len(oldImageArr) < 2 || len(newImageArr) < 2 { - err = errors.WithMessagef( - err, - "image is invalid %s/%s", - p.ReplaceImage, p.Task.Image) - return - } - - oldImageName = oldImageArr[0] - newImageName = newImageArr[0] - newImageTag = newImageArr[1] - //根据value找到对应的key - currentChartInfoMap := util.GetJSONData(currentValuesYamlMap) - sameKeyMap := make(map[string]interface{}) - for mapKey, mapValue := range currentChartInfoMap { - if mapValue == oldImageName { - sameKeyMap[mapKey] = newImageName - imageTag := strings.Replace(mapKey, ".repository", ".tag", 1) - sameKeyMap[imageTag] = newImageTag - } - } - - replaceMap := util.ReplaceMapValue(currentValuesYamlMap, sameKeyMap) - replaceValuesYaml, err = util.JSONToYaml(replaceMap) - if err != nil { - err = errors.WithMessagef( - err, - "failed to jsonToYaml %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - } else { - p.recursionReplaceImageByColon(currentValuesYamlMap, p.Task.Image) - if p.ReplaceImage != "" { - replaceValuesYaml = renderChart.ValuesYaml - replaceValuesYaml = strings.Replace(replaceValuesYaml, p.ReplaceImage, p.Task.Image, -1) - } + replaceValuesYaml, err = p.replaceImage(targetContainer.ImagePathSpec, currentValuesYamlMap, p.Task.Image) + if err != nil { + log.Error("failed to replace image: %s", p.Task.Image) } + if replaceValuesYaml != "" { helmClient, err = helmtool.NewClientFromRestConf(p.restConfig, p.Task.Namespace) if err != nil { @@ -596,62 +577,86 @@ func (p *DeployTaskPlugin) updateRenderSet(ctx context.Context, args *types.Rend return err } -// 递归循环找到要替换的镜像的标签 -func (p *DeployTaskPlugin) recursionReplaceImage(jsonValues map[string]interface{}, image string) { - for jsonKey, jsonValue := range jsonValues { - levelMap := p.isMap(jsonValue) - if levelMap != nil { - p.recursionReplaceImage(levelMap, image) - } else if repository, isStr := jsonValue.(string); isStr { - if !strings.Contains(jsonKey, "repository") { - continue - } - if imageTag, isExist := jsonValues["tag"]; isExist { - if imageTag == "" { - continue - } - currentImageName := strings.Split(image, ":")[0] - currentImageNameArr := strings.Split(currentImageName, "/") - serviceName := currentImageNameArr[len(currentImageNameArr)-1] - if strings.Contains(repository, serviceName) { - p.Log.Infof("find replace imageName:%s imageTag:%v", repository, imageTag) - p.ReplaceImage = fmt.Sprintf("%s:%v", repository, imageTag) - return - } - } - } +// replace image defines in yaml by new version +func (p *DeployTaskPlugin) replaceImage(imagePathSpec *types.ImagePathSpec, valuesMap map[string]interface{}, imageUri string) (string, error) { + if imagePathSpec == nil { + return "", errors.New("image path parse info is nil") + } + flatMap, err := converter.Flatten(valuesMap) + if err != nil { + return "", err + } + foundResult, err := yamlutil.SearchByPattern(flatMap, []map[string]string{{"repo": imagePathSpec.RepoPath, "image": imagePathSpec.ImagePath, "tag": imagePathSpec.TagPath}}) + if err != nil { + return "", err + } + if len(foundResult) != 1 { + return "", errors.New("can't parse image ptah") + } + replaceValuesMap, err := assignImageData(imageUri, foundResult[0]) + if err != nil { + return "", err } + replaceMap := util.ReplaceMapValue(valuesMap, replaceValuesMap) + replacedValuesYaml, err := util.JSONToYaml(replaceMap) + if err != nil { + return "", err + } + return replacedValuesYaml, nil } -// 递归循环找到根据包含冒号要替换的镜像的标签 -func (p *DeployTaskPlugin) recursionReplaceImageByColon(jsonValues map[string]interface{}, image string) { - for _, jsonValue := range jsonValues { - levelMap := p.isMap(jsonValue) - if levelMap != nil { - p.recursionReplaceImageByColon(levelMap, image) - } else if repository, isStr := jsonValue.(string); isStr { - if strings.Contains(repository, ":") && ImageRegex.MatchString(repository) { - currentImageName := strings.Split(image, ":")[0] - currentImageNameArr := strings.Split(currentImageName, "/") - serviceName := currentImageNameArr[len(currentImageNameArr)-1] - if strings.Contains(repository, serviceName) { - p.Log.Infof("find replace image:%s", repository) - p.ReplaceImage = repository - return - } +// assign image url data into match data +// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path +func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { + ret := make(map[string]interface{}) + // total image url assigned into one single value + if len(matchData) == 1 { + for _, v := range matchData { + ret[v] = imageUrl + } + return ret, nil + } + + resolvedImageUrl := resolveImageUrl(imageUrl) + + // image url assigned into repo+image+tag + if len(matchData) == 3 { + ret[matchData["repo"]] = strings.TrimSuffix(resolvedImageUrl["repo"], "/") + ret[matchData["image"]] = resolvedImageUrl["image"] + ret[matchData["tag"]] = resolvedImageUrl["tag"] + return ret, nil + } + + // image url assigned into repo/image + tag + if tagPath, ok := matchData["tag"]; ok { + ret[tagPath] = resolvedImageUrl["tag"] + for k, imagePath := range matchData { + if k == "tag" { + continue } + ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl["repo"], resolvedImageUrl["image"]) + break } + return ret, nil + } else { + // image url assigned into repo + image(tag) + ret[matchData["repo"]] = strings.TrimSuffix(resolvedImageUrl["repo"], "/") + ret[matchData["image"]] = fmt.Sprintf("%s:%s", resolvedImageUrl["image"], resolvedImageUrl["tag"]) + return ret, nil } } -func (p *DeployTaskPlugin) isMap(yamlMap interface{}) map[string]interface{} { - switch value := yamlMap.(type) { - case map[string]interface{}: - return value - default: - return nil +// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx +func resolveImageUrl(imageUrl string) map[string]string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + result := make(map[string]string) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + result[exNames[i]] = matchedStr + } } - return nil + return result } // Wait ... diff --git a/pkg/microservice/warpdrive/core/service/types/service_tmpl.go b/pkg/microservice/warpdrive/core/service/types/service_tmpl.go index cbdb7e57..1d127b5f 100644 --- a/pkg/microservice/warpdrive/core/service/types/service_tmpl.go +++ b/pkg/microservice/warpdrive/core/service/types/service_tmpl.go @@ -89,10 +89,18 @@ type Commit struct { Message string `bson:"message" json:"message"` } +// ImagePathSpec paths in yaml used to parse image +type ImagePathSpec struct { + RepoPath string `bson:"repo_path,omitempty" json:"repoPath,omitempty"` + ImagePath string `bson:"image_path,omitempty" json:"imagePath,omitempty"` + TagPath string `bson:"tag_path,omitempty" json:"tagPath,omitempty"` +} + // Container ... type Container struct { - Name string `bson:"name" json:"name"` - Image string `bson:"image" json:"image"` + Name string `bson:"name" json:"name"` + Image string `bson:"image" json:"image"` + ImagePathSpec *ImagePathSpec `bson:"image_path_spec,omitempty" json:"imagePathSpec,omitempty"` } type PmHealthCheck struct { diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go new file mode 100644 index 00000000..da6149d3 --- /dev/null +++ b/pkg/util/yaml/path_search.go @@ -0,0 +1,213 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + "fmt" + "strconv" + "strings" +) + +func getKey(prefix, k string) string { + if prefix == "" { + return k + } + return fmt.Sprintf("%s.%s", prefix, k) +} + +type singlePathSearchInfo struct { + pathName string + relativePath string + absolutePath string + value interface{} +} + +type pathSearchRuntime struct { + pattern map[string]string + prefix string + foundSet map[string]*singlePathSearchInfo // path => singlePathSearchInfo +} + +func (isr *pathSearchRuntime) checkAllFinish() bool { + for _, singlePath := range isr.pattern { + if singlePath == "" { + continue + } + if _, ok := isr.foundSet[singlePath]; !ok { + return false + } + } + return true +} + +type pathSearcher struct { + pattern map[string]string + resultSet map[string]*pathSearchRuntime //prefix => search result +} + +func (isr *pathSearcher) handleKV(k string, v interface{}) { + expectPaths, preMatch := isr.pattern, false + for _, ep := range expectPaths { + if strings.HasSuffix(k, ep) { + preMatch = true + break + } + } + if !preMatch { + return + } + + //优先匹配当前已经match了一部分path的情形 + for prefix, fs := range isr.resultSet { + if fs.checkAllFinish() { + continue + } + if !strings.HasPrefix(k, prefix) { + continue + } + match := false + for pathName, ep := range expectPaths { + //已经找到了相关的实际path pass + if _, ok := fs.foundSet[ep]; ok { + continue + } else { + if !strings.HasSuffix(k, ep) { + continue + } + if getKey(prefix, ep) != k { + break + } + fs.foundSet[ep] = &singlePathSearchInfo{ + pathName: pathName, + relativePath: ep, + absolutePath: k, + value: v, + } + match = true + } + } + if match { + return + } + } + + for pathName, ep := range expectPaths { + if !strings.HasSuffix(k, ep) { + continue + } + prefix := strings.TrimSuffix(k, ep) + prefix = strings.Trim(prefix, ".") + fs := &pathSearchRuntime{ + pattern: expectPaths, + prefix: prefix, + foundSet: map[string]*singlePathSearchInfo{ + ep: &singlePathSearchInfo{ + pathName: pathName, + relativePath: ep, + absolutePath: k, + value: v, + }, + }, + } + isr.resultSet[prefix] = fs + return + } +} + +func generateDFSMap(prefix string, source map[string]interface{}, outputMap map[string]string) { + for k, v := range source { + if subMap, ok := v.(map[string]interface{}); ok { + generateDFSMap(getKey(prefix, k), subMap, outputMap) + } else { + switch o := v.(type) { + case int: + outputMap[getKey(prefix, k)] = strconv.Itoa(o) + case string: + outputMap[getKey(prefix, k)] = o + } + } + } +} + +func preFilterSearchPaths(configs []map[string]string) []map[string]string { + ret := make([]map[string]string, 0) + for _, singleConfig := range configs { + cfg := make(map[string]string) + for k, v := range singleConfig { + if k == "" || v == "" { + continue + } + cfg[k] = v + } + ret = append(ret, cfg) + } + return ret +} + +func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) []*pathSearcher { + patterns = preFilterSearchPaths(patterns) + if len(patterns) == 0 { + return nil + } + rtSlice := make([]*pathSearcher, 0) + for _, cfg := range patterns { + rt := &pathSearcher{ + pattern: cfg, + resultSet: make(map[string]*pathSearchRuntime), + } + rtSlice = append(rtSlice, rt) + } + for k, v := range sourceMap { + for _, rt := range rtSlice { + rt.handleKV(k, v) + } + } + return rtSlice +} + +func mergeResults(isrList []*pathSearcher) []map[string]string { + ret := make([]map[string]string, 0) + usedPath := make(map[string]int) + for _, isr := range isrList { + for _, fSet := range isr.resultSet { + retSet := make(map[string]string) + if !fSet.checkAllFinish() { + continue + } + duplicateCheckPass := true + for _, searchInfo := range fSet.foundSet { + if _, ok := usedPath[searchInfo.absolutePath]; ok { + duplicateCheckPass = false + break + } + retSet[searchInfo.pathName] = searchInfo.absolutePath + usedPath[searchInfo.absolutePath] = 1 + } + if duplicateCheckPass { + ret = append(ret, retSet) + } + } + } + return ret +} + +// SearchByPattern find all matched absolute paths from yaml by the pattern appointed +func SearchByPattern(flatMap map[string]interface{}, pattern []map[string]string) ([]map[string]string, error) { + foundResult := searchPaths(pattern, flatMap) + ret := mergeResults(foundResult) + return ret, nil +} diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go new file mode 100644 index 00000000..7b01a231 --- /dev/null +++ b/pkg/util/yaml/path_search_test.go @@ -0,0 +1,148 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + "github.com/koderover/zadig/pkg/util/converter" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/stretchr/testify/assert" +) + +var testYaml1 = ` +env: dev +image: + repository: go-sample-site + tag: "0.2.1" +imagePullSecrets: + - name: default-secret +` + +var testYaml2 = ` +env: dev +svc1: + image: + repository: go-sample-site + tag: "0.2.1" +svc2: + image: + repository: go-sample-site-2 + tag: "0.2.2" +imagePullSecrets: + - name: default-secret +` + +var testYaml3 = ` +env: dev +svc1: + image: + repository: go-sample-site:0.2.1 +svc2: + image: + repository: go-sample-site-2:0.2.2 +svc3: + image: + repository: go-sample-site-3:0.2.3 +imagePullSecrets: + - name: default-secret +` + +var testYaml4 = ` +env: dev +svc1: + image: + repository: go-sample-site + tag: 0.2.1 +svc2: + image: + repository: go-sample-site-2:0.2.2 +svc3: + image: + repository: go-sample-site-3:0.2.3 +svc4: + image: + repositoryNew: go-sample-site-3 + tagNew: 0.2.4 +svc5: + second: + image: + repositorySpec: go-sample-site-3 + tagNew: 0.2.4 +imagePullSecrets: + - name: default-secret +` + +var err error +var matedPaths []map[string]string + +var _ = Describe("Testing search", func() { + Context("search matched paths from yaml", func() { + It("single match", func() { + pattern := []map[string]string{ + {"image": "repository", "tag": "tag"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) + matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(matedPaths).To(Equal([]map[string]interface{}{{"image": "image.repository", "tag": "image.tag"}})) + }) + + It("multiple match", func() { + pattern := []map[string]string{ + {"image": "repository", "tag": "tag"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) + matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(len(matedPaths)).To(Equal(2)) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository", "tag": "svc2.image.tag"}) + }) + + It("multiple match pattern 3", func() { + pattern := []map[string]string{ + {"image": "repository"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml3)) + matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(len(matedPaths)).To(Equal(3)) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc3.image.repository"}) + }) + + It("multiple match pattern complex", func() { + pattern := []map[string]string{ + {"image": "repository", "tag": "tag"}, + {"image": "repository"}, + {"image": "repositoryNew", "tag": "tagNew"}, + {"image": "image.repositorySpec", "tag": "tagNew"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml4)) + matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(len(matedPaths)).To(Equal(5)) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc3.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}) + assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}) + }) + }) +}) -- Gitee From 2dd90044b852fa2f3c7553c804b074481499c11c Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 23 Sep 2021 21:02:42 +0800 Subject: [PATCH 0013/2647] support edit custom image rule Signed-off-by: allenshen --- .../repository/models/template/product.go | 49 +++-- .../repository/mongodb/template/product.go | 27 +-- .../aslan/core/common/service/service.go | 29 +++ .../core/environment/service/environment.go | 5 +- .../aslan/core/project/handler/product.go | 40 ++++ .../aslan/core/project/handler/router.go | 2 + .../aslan/core/project/service/product.go | 179 +++++++++++++++++- .../aslan/core/service/service/helm.go | 88 +++++---- .../core/service/taskplugin/deploy.go | 6 +- pkg/util/yaml/path_search_test.go | 65 +++++-- 10 files changed, 409 insertions(+), 81 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 687cb73a..9fde6e0d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -21,26 +21,27 @@ import "strings" // Vars不做保存,只做input参数 // product_name 当前项目主键 type Product struct { - ProjectName string `bson:"project_name" json:"project_name"` - ProductName string `bson:"product_name" json:"product_name"` - Revision int64 `bson:"revision" json:"revision"` - CreateTime int64 `bson:"create_time" json:"create_time"` - Teams []*Team `bson:"teams" json:"teams"` - Team string `bson:"team" json:"team"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - Enabled bool `bson:"enabled" json:"enabled"` - Visibility string `bson:"visibility" json:"visibility"` - Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` - Services [][]string `bson:"services" json:"services"` - SharedServices []*ServiceInfo `bson:"shared_services,omitempty" json:"shared_services,omitempty"` - Vars []*RenderKV `bson:"vars" json:"vars"` - EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` - ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` - UserIDs []int `bson:"user_ids" json:"user_ids"` - TeamID int `bson:"team_id" json:"team_id"` - Description string `bson:"description,omitempty" json:"desc,omitempty"` - ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` + ProjectName string `bson:"project_name" json:"project_name"` + ProductName string `bson:"product_name" json:"product_name"` + Revision int64 `bson:"revision" json:"revision"` + CreateTime int64 `bson:"create_time" json:"create_time"` + Teams []*Team `bson:"teams" json:"teams"` + Team string `bson:"team" json:"team"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + Enabled bool `bson:"enabled" json:"enabled"` + Visibility string `bson:"visibility" json:"visibility"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Services [][]string `bson:"services" json:"services"` + SharedServices []*ServiceInfo `bson:"shared_services,omitempty" json:"shared_services,omitempty"` + Vars []*RenderKV `bson:"vars" json:"vars"` + EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` + ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` + UserIDs []int `bson:"user_ids" json:"user_ids"` + TeamID int `bson:"team_id" json:"team_id"` + Description string `bson:"description,omitempty" json:"desc,omitempty"` + ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` + ImageMatchRules []*ImageMatchRules `bson:"image_match_rules,omitempty" json:"image_match_rules,omitempty"` // onboarding状态,0表示onboarding完成,1、2、3、4代表当前onboarding所在的步骤 OnboardingStatus int `bson:"onboarding_status" json:"onboarding_status"` // CI场景的onboarding流程创建的ci工作流id,用于前端跳转 @@ -135,6 +136,14 @@ type ForkProject struct { ProductName string `json:"product_name"` } +type ImageMatchRules struct { + Repo string `bson:"repo,omitempty"` + Image string `bson:"image,omitempty"` + Tag string `bson:"tag,omitempty"` + InUse bool `bson:"in_use,omitempty"` + PresetId int `bson:"preset_id,omitempty"` +} + func (Product) TableName() string { return "template_product" } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index ae98d486..2d251194 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -165,19 +165,20 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { query := bson.M{"product_name": productName} change := bson.M{"$set": bson.M{ - "project_name": strings.TrimSpace(args.ProjectName), - "revision": args.Revision, - "services": args.Services, - "update_time": time.Now().Unix(), - "update_by": args.UpdateBy, - "teams": args.Teams, - "enabled": args.Enabled, - "description": args.Description, - "visibility": args.Visibility, - "user_ids": args.UserIDs, - "team_id": args.TeamID, - "timeout": args.Timeout, - "shared_services": args.SharedServices, + "project_name": strings.TrimSpace(args.ProjectName), + "revision": args.Revision, + "services": args.Services, + "update_time": time.Now().Unix(), + "update_by": args.UpdateBy, + "teams": args.Teams, + "enabled": args.Enabled, + "description": args.Description, + "visibility": args.Visibility, + "user_ids": args.UserIDs, + "team_id": args.TeamID, + "timeout": args.Timeout, + "shared_services": args.SharedServices, + "image_match_rules": args.ImageMatchRules, }} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 0190dbfa..4abf6572 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -22,10 +22,14 @@ import ( "regexp" "strings" + "github.com/koderover/zadig/pkg/util/converter" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -564,3 +568,28 @@ func ExtractImageName(imageUrl string) string { } return "" } + +func ParseContainers(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { + flatMap, err := converter.Flatten(nested) + if err != nil { + return nil, err + } + matchedPath, err := yamlutil.SearchByPattern(flatMap, patterns) + if err != nil { + return nil, err + } + ret := make([]*models.Container, 0) + for _, searchResult := range matchedPath { + imageUrl := GeneImageUri(searchResult, flatMap) + ret = append(ret, &models.Container{ + Name: ExtractImageName(imageUrl), + Image: imageUrl, + ImagePathSpec: &models.ImagePathSpec{ + RepoPath: searchResult["repo"], + ImagePath: searchResult["image"], + TagPath: searchResult["tag"], + }, + }) + } + return ret, nil +} diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7ace7707..0bb89d83 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1226,9 +1226,10 @@ func fillContainerParseInfo(prod *commonmodels.Product) error { rendersetMap[singleData.ServiceName] = singleData.ValuesYaml } + // for old data, preset rules should be capable patterns := []map[string]string{ - {"image": "repository", "tag": "tag"}, - {"image": "repository"}, + {"image": "image.repository", "tag": "image.tag"}, + {"image": "image"}, } fillHappen := false diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 72a348d5..74fe1986 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -181,3 +181,43 @@ func UnForkProduct(c *gin.Context) { ctx.Err = projectservice.UnForkProduct(ctx.User.ID, ctx.Username, c.Param("productName"), c.Query("workflowName"), c.Query("envName"), ctx.RequestID, ctx.Logger) } + +func GetCustomMatchRules(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + if c.Param("name") == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") + return + } + + ctx.Resp, ctx.Err = projectservice.GetCustomMatchRules(c.Param("name"), ctx.Logger) +} + +func CreateOrUpdateMatchRules(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + if c.Param("name") == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") + return + } + + internalhandler.InsertOperationLog(c, ctx.Username, c.Param("name"), "更新", "工程管理-项目", c.Param("name"), permission.SuperUserUUID, "", ctx.Logger) + + //args := make([]*projectservice.ImageParseData, 0) + args := new(projectservice.CustomParseDataArgs) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateOrUpdateMatchRules c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam + return + } + if err = json.Unmarshal(data, &args); err != nil { + log.Errorf("CreateOrUpdateMatchRules json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam + return + } + + ctx.Err = projectservice.UpdateCustomMatchRules(c.Param("name"), ctx.Username, args.Rules) +} diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index e40f4e46..2efebb5a 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -51,6 +51,8 @@ func (*Router) Inject(router *gin.RouterGroup) { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) product.GET("", ListProductTemplate) + product.GET("/:name/match-rules", GetCustomMatchRules) + product.PUT("/:name/match-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 475362ab..7b109479 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -17,15 +17,16 @@ limitations under the License. package service import ( - "errors" "fmt" "strconv" "strings" "sync" "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -43,6 +44,18 @@ import ( "github.com/koderover/zadig/pkg/types/permission" ) +type CustomParseDataArgs struct { + Rules []*ImageParseData `json:"rules"` +} + +type ImageParseData struct { + Repo string `json:"repo,omitempty"` + Image string `json:"image,omitempty"` + Tag string `json:"tag,omitempty"` + InUse bool `json:"inUse,omitempty"` + PresetId int `json:"presetId,omitempty"` +} + func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*template.Product, error) { resp, err := templaterepo.NewProductColl().Find(productName) if err != nil { @@ -867,3 +880,167 @@ func ListTemplatesHierachy(userName string, userID int, superUser bool, log *zap } return resp, nil } + +func GetCustomMatchRules(productName string, log *zap.SugaredLogger) ([]*ImageParseData, error) { + productInfo, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + log.Errorf("query product:%s fail, err:%s", productName, err.Error()) + return nil, fmt.Errorf("failed to find product %s", productName) + } + ret := make([]*ImageParseData, 0, len(productInfo.ImageMatchRules)) + for _, singleData := range productInfo.ImageMatchRules { + ret = append(ret, &ImageParseData{ + Repo: singleData.Repo, + Image: singleData.Image, + Tag: singleData.Tag, + InUse: singleData.InUse, + PresetId: singleData.PresetId, + }) + } + // use preset data if rules are never edited + if len(ret) == 0 { + ret = append(ret, &ImageParseData{ + Repo: "", + Image: "image.repository", + Tag: "image.tag", + InUse: true, + PresetId: 1, + }, &ImageParseData{ + Image: "image", + InUse: true, + PresetId: 2, + }) + } + return ret, nil +} + +func UpdateCustomMatchRules(productName string, userName string, matchRules []*ImageParseData) error { + productInfo, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + log.Errorf("query product:%s fail, err:%s", productName, err.Error()) + return fmt.Errorf("failed to find product %s", productName) + } + + if len(matchRules) == 0 { + return errors.New("match rules can't be empty") + } + haveInUse := false + for _, rule := range matchRules { + if rule.InUse { + haveInUse = true + break + } + } + if !haveInUse { + return errors.New("no rule is selected to be used") + } + + imageRulesToSave := make([]*template.ImageMatchRules, 0) + for _, singleData := range matchRules { + if singleData.Repo == "" && singleData.Image == "" && singleData.Tag == "" { + continue + } + imageRulesToSave = append(imageRulesToSave, &template.ImageMatchRules{ + Repo: singleData.Repo, + Image: singleData.Image, + Tag: singleData.Tag, + InUse: singleData.InUse, + PresetId: singleData.PresetId, + }) + } + + productInfo.ImageMatchRules = imageRulesToSave + productInfo.UpdateBy = userName + + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) + if err != nil { + return err + } + err = reParseServices(userName, services, imageRulesToSave) + if err != nil { + return err + } + + err = templaterepo.NewProductColl().Update(productName, productInfo) + if err != nil { + log.Errorf("failed to update product:%s, err:%s", productName, err.Error()) + return fmt.Errorf("failed to store match rules") + } + + return nil +} + +// reparse values.yaml for each service +func reParseServices(userName string, serviceList []*commonmodels.Service, matchRules []*template.ImageMatchRules) error { + patterns := make([]map[string]string, 0) + for _, rule := range matchRules { + if !rule.InUse { + continue + } + patterns = append(patterns, map[string]string{ + "repo": rule.Repo, + "image": rule.Image, + "tag": rule.Tag, + }) + } + + updatedServiceTmpls := make([]*commonmodels.Service, 0) + + var err error + for _, serviceTmpl := range serviceList { + if serviceTmpl.Type != setting.HelmDeployType || serviceTmpl.HelmChart == nil { + continue + } + valuesYaml := serviceTmpl.HelmChart.ValuesYaml + + valuesMap := make(map[string]interface{}) + err = yaml.Unmarshal([]byte(valuesYaml), &valuesMap) + if err != nil { + err = errors.Wrapf(err, "failed to unmarshal values.yamf for service %s", serviceTmpl.ServiceName) + break + } + + serviceTmpl.Containers, err = commonservice.ParseContainers(valuesMap, patterns) + if err != nil { + break + } + + if len(serviceTmpl.Containers) == 0 { + log.Warnf("service:%s containers is empty after parse, pattern %v, valuesYaml %s", serviceTmpl.ServiceName, patterns, valuesYaml) + } + + serviceTmpl.CreateBy = userName + serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, serviceTmpl.ServiceName, serviceTmpl.ProductName) + rev, errRevision := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) + if errRevision != nil { + err = fmt.Errorf("get next helm service revision error: %v", errRevision) + break + } + serviceTmpl.Revision = rev + if err = commonrepo.NewServiceColl().Delete(serviceTmpl.ServiceName, setting.HelmDeployType, serviceTmpl.ProductName, setting.ProductStatusDeleting, serviceTmpl.Revision); err != nil { + log.Errorf("helmService.update delete %s error: %v", serviceTmpl.ServiceName, err) + break + } + + if err = commonrepo.NewServiceColl().Create(serviceTmpl); err != nil { + log.Errorf("helmService.update serviceName:%s error:%v", serviceTmpl.ServiceName, err) + err = e.ErrUpdateTemplate.AddDesc(err.Error()) + break + } + + updatedServiceTmpls = append(updatedServiceTmpls, serviceTmpl) + } + + // roll back all template services if error occurs + if err != nil { + for _, serviceTmpl := range updatedServiceTmpls { + if err = commonrepo.NewServiceColl().Delete(serviceTmpl.ServiceName, setting.HelmDeployType, serviceTmpl.ProductName, setting.ProductStatusDeleting, serviceTmpl.Revision); err != nil { + log.Errorf("helmService.update delete %s error: %v", serviceTmpl.ServiceName, err) + continue + } + } + return err + } + + return nil +} diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 0e91a028..476ad816 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -27,7 +27,6 @@ import ( "github.com/27149chen/afero" "github.com/hashicorp/go-multierror" - "github.com/koderover/zadig/pkg/util/converter" "github.com/otiai10/copy" "github.com/pkg/errors" "go.uber.org/zap" @@ -194,6 +193,36 @@ func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs } } +// get patterns used to parse images from yaml +func getServiceParsePatterns(productName string) ([]map[string]string, error) { + productInfo, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + return nil, err + } + ret := make([]map[string]string, 0) + for _, rule := range productInfo.ImageMatchRules { + if !rule.InUse { + continue + } + ret = append(ret, map[string]string{ + "repo": rule.Repo, + "image": rule.Image, + "tag": rule.Tag, + }) + } + + // rules are never edited, use preset rules + if len(ret) == 0 { + ret = append(ret, map[string]string{ + "image": "image.repository", "tag": "image.tag", + }, map[string]string{ + "image": "image", + }) + } + + return ret, nil +} + func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { @@ -267,6 +296,12 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return err } + patterns, err := getServiceParsePatterns(projectName) + if err != nil { + logger.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", args.Name, projectName, err) + return errors.New("failed to get image parse patterns") + } + svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ @@ -279,6 +314,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe Repo: templateArgs.Repo, Branch: templateArgs.Branch, }, + patterns, logger, ) if err != nil { @@ -360,6 +396,13 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } + patterns, err := getServiceParsePatterns(projectName) + if err != nil { + log.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", args.Name, projectName, err) + finalErr = errors.New("failed to get image parse patterns") + return + } + svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ @@ -373,6 +416,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC Branch: repoArgs.Branch, RepoLink: repoLink, }, + patterns, log, ) if err != nil { @@ -429,7 +473,7 @@ func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([] return content, valuesMap, nil } -func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*models.Service, error) { +func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, matchPatterns []map[string]string, logger *zap.SugaredLogger) (*models.Service, error) { chartName, chartVersion, err := readChartYAML(fsTree, filepath.Base(args.FilePath), logger) if err != nil { logger.Errorf("Failed to read chart.yaml, err %s", err) @@ -453,7 +497,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg logger.Warnf("Failed to delete stale service %s with revision %d, err: %s", args.ServiceName, rev, err) } - containerList, err := parseContainers(valuesMap) + containerList, err := commonservice.ParseContainers(valuesMap, matchPatterns) if err != nil { return nil, errors.Wrapf(err, "failed to parse service from yaml") } @@ -535,6 +579,7 @@ func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileIn return fis, nil } +// UpdateHelmService TODO need to be deprecated func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { var serviceNames []string for _, helmServiceInfo := range args.HelmServiceInfos { @@ -568,7 +613,13 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { return e.ErrCreateTemplate.AddDesc("values.yaml解析失败") } - containerList, err := parseContainers(valuesMap) + patterns, err := getServiceParsePatterns(preServiceTmpl.ProductName) + if err != nil { + log.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", preServiceTmpl.ServiceName, preServiceTmpl.ProductName, err) + return errors.New("failed to get image parse patterns") + } + + containerList, err := commonservice.ParseContainers(valuesMap, patterns) if err != nil { return e.ErrUpdateTemplate.AddErr(errors.Wrapf(err, "failed to parse images from yaml")) } @@ -684,32 +735,3 @@ func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, log.Errorf("helmService.Create CreateHelmRenderSet error: %v", err) } } - -func parseContainers(nested map[string]interface{}) ([]*models.Container, error) { - patterns := []map[string]string{ - {"image": "repository", "tag": "tag"}, - {"image": "repository"}, - } - flatMap, err := converter.Flatten(nested) - if err != nil { - return nil, err - } - matchedPath, err := yamlutil.SearchByPattern(flatMap, patterns) - if err != nil { - return nil, err - } - ret := make([]*models.Container, 0) - for _, searchResult := range matchedPath { - imageUrl := commonservice.GeneImageUri(searchResult, flatMap) - ret = append(ret, &models.Container{ - Name: commonservice.ExtractImageName(imageUrl), - Image: imageUrl, - ImagePathSpec: &models.ImagePathSpec{ - RepoPath: searchResult["repo"], - ImagePath: searchResult["image"], - TagPath: searchResult["tag"], - }, - }) - } - return ret, nil -} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index e28659b6..0540aa80 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -586,7 +586,11 @@ func (p *DeployTaskPlugin) replaceImage(imagePathSpec *types.ImagePathSpec, valu if err != nil { return "", err } - foundResult, err := yamlutil.SearchByPattern(flatMap, []map[string]string{{"repo": imagePathSpec.RepoPath, "image": imagePathSpec.ImagePath, "tag": imagePathSpec.TagPath}}) + foundResult, err := yamlutil.SearchByPattern(flatMap, []map[string]string{{ + "repo": imagePathSpec.RepoPath, + "image": imagePathSpec.ImagePath, + "tag": imagePathSpec.TagPath, + }}) if err != nil { return "", err } diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 7b01a231..24791da8 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -87,6 +87,38 @@ imagePullSecrets: - name: default-secret ` +var testYaml5 = ` +# Default values for go-sample-site. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +env: dev + +ingressClassName: koderover-admin-nginx + +image: + repository: ccr.ccs.tencentyun.com/trial/go-sample-site + pullPolicy: IfNotPresent + tag: "0.1.0" + +testSpec: + imageNew: + repo: ccr.ccs.tencentyun.com/trial/go-sample-site-new + pullPolicy: IfNotPresent + tag: "0.2.1" + +imagePullSecrets: + - name: default-registry-secret + +nameOverride: "" +fullnameOverride: "" + +service: + type: ClusterIP + port: 8080 + +` + var err error var matedPaths []map[string]string @@ -99,7 +131,7 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - Expect(matedPaths).To(Equal([]map[string]interface{}{{"image": "image.repository", "tag": "image.tag"}})) + Expect(matedPaths).To(Equal([]map[string]string{{"image": "image.repository", "tag": "image.tag"}})) }) It("multiple match", func() { @@ -110,8 +142,8 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(2)) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository", "tag": "svc2.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc2.image.repository", "tag": "svc2.image.tag"}) }) It("multiple match pattern 3", func() { @@ -122,9 +154,9 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(3)) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc3.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc1.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc2.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc3.image.repository"}) }) It("multiple match pattern complex", func() { @@ -138,11 +170,22 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(5)) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc3.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc2.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc3.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}) + }) + + It("multiple match pattern complex2", func() { + pattern := []map[string]string{ + {"image": "imageNew.repo", "tag": "imageNew.tag"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml5)) + matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(len(matedPaths)).To(Equal(1)) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "testSpec.imageNew.repo", "tag": "testSpec.imageNew.tag"}) }) }) }) -- Gitee From 5ca9dc9d5c66ad4dc31047fec972e0c5596e11e5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 23 Sep 2021 21:12:23 +0800 Subject: [PATCH 0014/2647] optmize code Signed-off-by: allenshen --- .../core/environment/service/environment.go | 2 +- .../aslan/core/service/service/helm.go | 2 +- pkg/util/yaml/path_search.go | 19 +++----------- pkg/util/yaml/path_search_test.go | 26 +++++++++---------- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7ace7707..f6c9b5be 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1228,7 +1228,7 @@ func fillContainerParseInfo(prod *commonmodels.Product) error { patterns := []map[string]string{ {"image": "repository", "tag": "tag"}, - {"image": "repository"}, + {"image": "image"}, } fillHappen := false diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 0e91a028..99488374 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -688,7 +688,7 @@ func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, func parseContainers(nested map[string]interface{}) ([]*models.Container, error) { patterns := []map[string]string{ {"image": "repository", "tag": "tag"}, - {"image": "repository"}, + {"image": "image"}, } flatMap, err := converter.Flatten(nested) if err != nil { diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index da6149d3..354dc594 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -18,7 +18,6 @@ package yaml import ( "fmt" - "strconv" "strings" ) @@ -128,21 +127,7 @@ func (isr *pathSearcher) handleKV(k string, v interface{}) { } } -func generateDFSMap(prefix string, source map[string]interface{}, outputMap map[string]string) { - for k, v := range source { - if subMap, ok := v.(map[string]interface{}); ok { - generateDFSMap(getKey(prefix, k), subMap, outputMap) - } else { - switch o := v.(type) { - case int: - outputMap[getKey(prefix, k)] = strconv.Itoa(o) - case string: - outputMap[getKey(prefix, k)] = o - } - } - } -} - +// ignore empty patterns func preFilterSearchPaths(configs []map[string]string) []map[string]string { ret := make([]map[string]string, 0) for _, singleConfig := range configs { @@ -158,6 +143,7 @@ func preFilterSearchPaths(configs []map[string]string) []map[string]string { return ret } +// build pathSearcher object for every pattern func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) []*pathSearcher { patterns = preFilterSearchPaths(patterns) if len(patterns) == 0 { @@ -179,6 +165,7 @@ func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) return rtSlice } +// merge results and filter duplicates paths func mergeResults(isrList []*pathSearcher) []map[string]string { ret := make([]map[string]string, 0) usedPath := make(map[string]int) diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 7b01a231..03e660e1 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -17,11 +17,11 @@ limitations under the License. package yaml import ( - "github.com/koderover/zadig/pkg/util/converter" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "github.com/stretchr/testify/assert" + + "github.com/koderover/zadig/pkg/util/converter" ) var testYaml1 = ` @@ -99,7 +99,7 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - Expect(matedPaths).To(Equal([]map[string]interface{}{{"image": "image.repository", "tag": "image.tag"}})) + Expect(matedPaths).To(Equal([]map[string]string{{"image": "image.repository", "tag": "image.tag"}})) }) It("multiple match", func() { @@ -110,8 +110,8 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(2)) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository", "tag": "svc2.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc2.image.repository", "tag": "svc2.image.tag"}) }) It("multiple match pattern 3", func() { @@ -122,9 +122,9 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(3)) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc3.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc1.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc2.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc3.image.repository"}) }) It("multiple match pattern complex", func() { @@ -138,11 +138,11 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(5)) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc2.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc3.image.repository"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}) - assert.Contains(GinkgoT(), matedPaths, map[string]interface{}{"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc1.image.repository", "tag": "svc1.image.tag"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc2.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc3.image.repository"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}) + assert.Contains(GinkgoT(), matedPaths, map[string]string{"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}) }) }) }) -- Gitee From 4483e5dd8a2062f5be79fc0452beccf7458cb32f Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 26 Sep 2021 14:23:38 +0800 Subject: [PATCH 0015/2647] optimize code Signed-off-by: allenshen --- .../core/common/repository/models/service.go | 6 +-- .../aslan/core/common/service/service.go | 35 +++++++++--- .../core/environment/service/environment.go | 40 +++++++------- .../aslan/core/environment/service/product.go | 6 +-- .../aslan/core/project/service/product.go | 6 +-- .../aslan/core/service/service/helm.go | 15 +++--- .../core/service/taskplugin/deploy.go | 30 ++++++----- pkg/util/yaml/path_search.go | 54 +++++++++++++------ 8 files changed, 120 insertions(+), 72 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index e92bf9c1..9042493c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -101,9 +101,9 @@ type ImagePathSpec struct { // Container ... type Container struct { - Name string `bson:"name" json:"name"` - Image string `bson:"image" json:"image"` - ImagePathSpec *ImagePathSpec `bson:"image_path_spec,omitempty" json:"imagePathSpec,omitempty"` + Name string `bson:"name" json:"name"` + Image string `bson:"image" json:"image"` + ImagePath *ImagePathSpec `bson:"image_path_spec,omitempty" json:"imagePathSpec,omitempty"` } // ServiceTmplPipeResp ...router diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 0190dbfa..f72f60a2 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -18,6 +18,7 @@ package service import ( "encoding/json" + "errors" "fmt" "regexp" "strings" @@ -35,6 +36,7 @@ import ( "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type yamlPreview struct { @@ -509,7 +511,6 @@ func getAddressFromPath(path, owner, repo string, logger *zap.Logger) string { logger.With(zap.String("path", path), zap.String("owner", owner), zap.String("repo", repo)).DPanic("Invalid path") return "" } - return res[0] } @@ -527,14 +528,22 @@ func getValuesByPath(paths map[string]string, flatMap map[string]interface{}) ma return ret } -// GeneImageUri generate valid image uri -func GeneImageUri(pathData map[string]string, flatMap map[string]interface{}) string { +// GeneImageURI generate valid image uri, legal formats: +// {repo} +// {repo}/{image} +// {repo}/{image}:{tag} +// {repo}:{tag} +// {image}:{tag} +// {image} +func GeneImageURI(pathData map[string]string, flatMap map[string]interface{}) (string, error) { valuesMap := getValuesByPath(pathData, flatMap) ret := "" + // if repo value is set, use as repo if repo, ok := valuesMap["repo"]; ok { ret = fmt.Sprintf("%v", repo) ret = strings.TrimSuffix(ret, "/") } + // if image value is set, append to repo, if repo is not set, image values represents repo+image if image, ok := valuesMap["image"]; ok { imageStr := fmt.Sprintf("%v", image) if ret == "" { @@ -543,17 +552,22 @@ func GeneImageUri(pathData map[string]string, flatMap map[string]interface{}) st ret = fmt.Sprintf("%s/%s", ret, imageStr) } } + if ret == "" { + return "", errors.New("") + } + // if tag is set, append to current uri, if not set ignore if tag, ok := valuesMap["tag"]; ok { tagStr := fmt.Sprintf("%v", tag) if tagStr != "" { ret = fmt.Sprintf("%s:%s", ret, tagStr) } } - return ret + return ret, nil } -func ExtractImageName(imageUrl string) string { - subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) +// ExtractImageName extract image name from total image uri +func ExtractImageName(imageURI string) string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) exNames := imageParseRegex.SubexpNames() for i, matchedStr := range subMatchAll { if i != 0 && matchedStr != "" && matchedStr != ":" { @@ -564,3 +578,12 @@ func ExtractImageName(imageUrl string) string { } return "" } + +// SearchImagesByPresetRules parse images from flat yaml map with preset rules +func SearchImagesByPresetRules(flatMap map[string]interface{}) ([]map[string]string, error) { + patterns := []map[string]string{ + {"image": "repository", "tag": "tag"}, + {"image": "image"}, + } + return yamlutil.SearchByPattern(flatMap, patterns) +} diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f6c9b5be..5dbd33ef 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -60,10 +60,10 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) const ( @@ -814,9 +814,9 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, - ImagePathSpec: c.ImagePathSpec, + Name: c.Name, + Image: c.Image, + ImagePath: c.ImagePath, } serviceResp.Containers = append(serviceResp.Containers, container) } @@ -1226,11 +1226,6 @@ func fillContainerParseInfo(prod *commonmodels.Product) error { rendersetMap[singleData.ServiceName] = singleData.ValuesYaml } - patterns := []map[string]string{ - {"image": "repository", "tag": "tag"}, - {"image": "image"}, - } - fillHappen := false // fill the parse info @@ -1238,7 +1233,7 @@ func fillContainerParseInfo(prod *commonmodels.Product) error { for _, singleService := range serviceGroup { findEmptySpec := false for _, container := range singleService.Containers { - if container.ImagePathSpec == nil { + if container.ImagePath == nil { findEmptySpec = true break } @@ -1250,11 +1245,16 @@ func fillContainerParseInfo(prod *commonmodels.Product) error { if err != nil { return err } + matchedPath, err := commonservice.SearchImagesByPresetRules(flatMap) + if err != nil { + log.Errorf("failed to parse images from service:%s in product:%s", singleService.ServiceName, singleService.ProductName) + continue + } for _, container := range singleService.Containers { - if container.ImagePathSpec != nil { + if container.ImagePath != nil { continue } - err = findImageByContainerName(flatMap, patterns, container) + err = findImageByContainerName(flatMap, matchedPath, container) if err != nil { return err } @@ -1273,17 +1273,17 @@ func fillContainerParseInfo(prod *commonmodels.Product) error { } // find match rule -func findImageByContainerName(flatMap map[string]interface{}, patterns []map[string]string, container *models.Container) error { - matchedPath, err := yamlutil.SearchByPattern(flatMap, patterns) - if err != nil { - return err - } +func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[string]string, container *models.Container) error { for _, searchResult := range matchedPath { - imageUrl := commonservice.GeneImageUri(searchResult, flatMap) - if container.Name != commonservice.ExtractImageName(imageUrl) { + imageURI, err := commonservice.GeneImageURI(searchResult, flatMap) + if err != nil { + log.Error("GeneImageURI fail, err %s", err.Error()) + continue + } + if container.Name != commonservice.ExtractImageName(imageURI) { continue } - container.ImagePathSpec = &models.ImagePathSpec{ + container.ImagePath = &models.ImagePathSpec{ RepoPath: searchResult["repo"], ImagePath: searchResult["image"], TagPath: searchResult["tag"], diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 01bc352a..f8c9f744 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -148,9 +148,9 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, - ImagePathSpec: c.ImagePathSpec, + Name: c.Name, + Image: c.Image, + ImagePath: c.ImagePath, } serviceResp.Containers = append(serviceResp.Containers, container) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 475362ab..a90836c9 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -575,9 +575,9 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, - ImagePathSpec: c.ImagePathSpec, + Name: c.Name, + Image: c.Image, + ImagePath: c.ImagePath, } serviceResp.Containers = append(serviceResp.Containers, container) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 99488374..fe820fd0 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -686,25 +686,26 @@ func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, } func parseContainers(nested map[string]interface{}) ([]*models.Container, error) { - patterns := []map[string]string{ - {"image": "repository", "tag": "tag"}, - {"image": "image"}, - } flatMap, err := converter.Flatten(nested) if err != nil { return nil, err } - matchedPath, err := yamlutil.SearchByPattern(flatMap, patterns) + + matchedPath, err := commonservice.SearchImagesByPresetRules(flatMap) if err != nil { return nil, err } + ret := make([]*models.Container, 0) for _, searchResult := range matchedPath { - imageUrl := commonservice.GeneImageUri(searchResult, flatMap) + imageUrl, err := commonservice.GeneImageURI(searchResult, flatMap) + if err != nil { + return nil, err + } ret = append(ret, &models.Container{ Name: commonservice.ExtractImageName(imageUrl), Image: imageUrl, - ImagePathSpec: &models.ImagePathSpec{ + ImagePath: &models.ImagePathSpec{ RepoPath: searchResult["repo"], ImagePath: searchResult["image"], TagPath: searchResult["tag"], diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index e28659b6..be5fb453 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -50,9 +50,7 @@ import ( "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" - "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) // InitializeDeployTaskPlugin to initiate deploy task plugin and return ref @@ -577,23 +575,27 @@ func (p *DeployTaskPlugin) updateRenderSet(ctx context.Context, args *types.Rend return err } +func getValidMatchData(spec *types.ImagePathSpec) map[string]string { + ret := make(map[string]string) + if spec.RepoPath != "" { + ret["repo"] = spec.RepoPath + } + if spec.ImagePath != "" { + ret["image"] = spec.ImagePath + } + if spec.TagPath != "" { + ret["tag"] = spec.TagPath + } + return ret +} + // replace image defines in yaml by new version func (p *DeployTaskPlugin) replaceImage(imagePathSpec *types.ImagePathSpec, valuesMap map[string]interface{}, imageUri string) (string, error) { if imagePathSpec == nil { return "", errors.New("image path parse info is nil") } - flatMap, err := converter.Flatten(valuesMap) - if err != nil { - return "", err - } - foundResult, err := yamlutil.SearchByPattern(flatMap, []map[string]string{{"repo": imagePathSpec.RepoPath, "image": imagePathSpec.ImagePath, "tag": imagePathSpec.TagPath}}) - if err != nil { - return "", err - } - if len(foundResult) != 1 { - return "", errors.New("can't parse image ptah") - } - replaceValuesMap, err := assignImageData(imageUri, foundResult[0]) + getValidMatchData := getValidMatchData(imagePathSpec) + replaceValuesMap, err := assignImageData(imageUri, getValidMatchData) if err != nil { return "", err } diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index 354dc594..8f09c6b0 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -17,6 +17,7 @@ limitations under the License. package yaml import ( + "errors" "fmt" "strings" ) @@ -41,12 +42,12 @@ type pathSearchRuntime struct { foundSet map[string]*singlePathSearchInfo // path => singlePathSearchInfo } -func (isr *pathSearchRuntime) checkAllFinish() bool { - for _, singlePath := range isr.pattern { +func (searchRuntime *pathSearchRuntime) checkAllFinish() bool { + for _, singlePath := range searchRuntime.pattern { if singlePath == "" { continue } - if _, ok := isr.foundSet[singlePath]; !ok { + if _, ok := searchRuntime.foundSet[singlePath]; !ok { return false } } @@ -127,28 +128,28 @@ func (isr *pathSearcher) handleKV(k string, v interface{}) { } } -// ignore empty patterns -func preFilterSearchPaths(configs []map[string]string) []map[string]string { - ret := make([]map[string]string, 0) +// check if contains empty patterns +func preFilterSearchPaths(configs []map[string]string) error { for _, singleConfig := range configs { - cfg := make(map[string]string) for k, v := range singleConfig { if k == "" || v == "" { - continue + return errors.New("empty pattern name or pattern path is not supported") } - cfg[k] = v } - ret = append(ret, cfg) } - return ret + return nil } // build pathSearcher object for every pattern -func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) []*pathSearcher { - patterns = preFilterSearchPaths(patterns) +func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) ([]*pathSearcher, error) { if len(patterns) == 0 { - return nil + return nil, nil } + err := preFilterSearchPaths(patterns) + if err != nil { + return nil, err + } + rtSlice := make([]*pathSearcher, 0) for _, cfg := range patterns { rt := &pathSearcher{ @@ -162,7 +163,7 @@ func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) rt.handleKV(k, v) } } - return rtSlice + return rtSlice, nil } // merge results and filter duplicates paths @@ -193,8 +194,29 @@ func mergeResults(isrList []*pathSearcher) []map[string]string { } // SearchByPattern find all matched absolute paths from yaml by the pattern appointed +// input pattern: []map{name => relative-path} +// output: app paths found which match the relative-path rule, []{map[name]=>absolute-path} +// eg: sourceYaml: +// ---------- yaml begin +//env: dev +//svc1: +// image: +// repository: go-sample-site +// tag: "0.2.1" +//svc2: +// image: +// repository: go-sample-site-2 +// tag: "0.2.2" +//imagePullSecrets: +// - name: default-secret +// ----------- yaml end +// pattern: []{"image": "repository", "tag": "tag"} +// output: []{{"image": "svc1.image.repository", "tag": "svc1.image.tag"}, {"image": "svc2.image.repository", "tag": "svc2.image.tag"}} func SearchByPattern(flatMap map[string]interface{}, pattern []map[string]string) ([]map[string]string, error) { - foundResult := searchPaths(pattern, flatMap) + foundResult, err := searchPaths(pattern, flatMap) + if err != nil { + return nil, err + } ret := mergeResults(foundResult) return ret, nil } -- Gitee From ddc097d675a87ddb998daaa436bea24b8356ec3b Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 26 Sep 2021 17:01:28 +0800 Subject: [PATCH 0016/2647] update upgrade assistant logic and refactor code Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 147 ++++++++++++++++++ .../internal/upgradepath/versions.go | 4 + .../core/common/repository/models/service.go | 8 +- .../aslan/core/common/service/service.go | 78 ++++++++-- .../core/environment/service/environment.go | 83 +--------- .../aslan/core/project/service/product.go | 16 +- .../aslan/core/service/service/helm.go | 57 +------ .../core/service/taskplugin/deploy.go | 55 ++++--- .../core/service/types/service_tmpl.go | 12 +- pkg/setting/consts.go | 7 +- version/version.go | 2 +- 11 files changed, 272 insertions(+), 197 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/150.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go new file mode 100644 index 00000000..82a3944f --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -0,0 +1,147 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrate + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util/converter" +) + +func init() { + upgradepath.AddHandler(upgradepath.V140, upgradepath.V150, V140ToV150) +} + +// V140ToV150 fill image path data for old data in product.services.containers +// use preset rules as patterns: {"image": "repository", "tag": "tag"}, {"image": "image"} +func V140ToV150() error { + products, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ + ExcludeStatus: setting.ProductStatusDeleting, + Source: setting.SourceFromHelm, + }) + if err != nil { + return err + } + + for _, product := range products { + renderSetName := commonservice.GetProductEnvNamespace(product.EnvName, product.ProductName) + renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: product.Render.Revision} + renderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) + if err != nil { + log.Errorf("find helm renderset[%s] error: %v", renderSetName, err) + continue + } + product.ChartInfos = renderSet.ChartInfos + + err = fillImageParseInfo(product) + if err != nil { + log.Error("failed to fill image parse info for product %s, err %s", product.ProductName, err.Error()) + continue + } + //log.Infof("fill data for product %s successfully", product.ProductName) + } + return nil +} + +func missContainerImagePath(container *commonmodels.Container) bool { + if container.ImagePath == nil { + return true + } + return container.ImagePath.Repo == "" && container.ImagePath.Image == "" && container.ImagePath.Tag == "" +} + +func fillImageParseInfo(prod *commonmodels.Product) error { + rendersetMap := make(map[string]string) + for _, singleData := range prod.ChartInfos { + rendersetMap[singleData.ServiceName] = singleData.ValuesYaml + } + + fillHappen := false + + // fill the parse info + serviceMap := prod.GetServiceMap() + for _, singleService := range serviceMap { + findEmptySpec := false + for _, container := range singleService.Containers { + if missContainerImagePath(container) { + findEmptySpec = true + break + } + } + if !findEmptySpec { + continue + } + flatMap, err := converter.YamlToFlatMap([]byte(rendersetMap[singleService.ServiceName])) + if err != nil { + log.Errorf("get flat map fail for service: %s.%s, err %s", prod.ProductName, singleService.ServiceName, err.Error()) + continue + } + matchedPath, err := commonservice.ParseImagesByPresetRules(flatMap) + if err != nil { + log.Errorf("failed to parse images from service:%s.%s in product:%s", prod.ProductName, singleService.ServiceName, singleService.ProductName) + continue + } + for _, container := range singleService.Containers { + if !missContainerImagePath(container) { + continue + } + err = findImageByContainerName(flatMap, matchedPath, container) + if err != nil { + log.Errorf("failed to find image pathL %s.%s, err: %s", prod.ProductName, singleService.ServiceName, err.Error()) + continue + } + fillHappen = true + } + } + + if fillHappen { + err := commonrepo.NewProductColl().Update(prod) + if err != nil { + return err + } + } + return nil +} + +// find match rule +func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[string]string, container *models.Container) error { + for _, searchResult := range matchedPath { + imageURI, err := commonservice.GeneImageURI(searchResult, flatMap) + if err != nil { + log.Error("GeneImageURI fail, err %s", err.Error()) + continue + } + if container.Name != commonservice.ExtractImageName(imageURI) { + continue + } + container.ImagePath = &models.ImagePathSpec{ + Repo: searchResult[setting.PathSearchComponentRepo], + Image: searchResult[setting.PathSearchComponentImage], + Tag: searchResult[setting.PathSearchComponentTag], + } + return nil + } + return fmt.Errorf("faild to find image for contianer %s", container.Image) +} diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index ef94c04a..ea75cd54 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -26,11 +26,15 @@ const ( Latest = iota + 1 V130 V131 + V140 + V150 ) var versionMap versions = map[string]int{ "1.3.0": V130, "1.3.1": V131, + "1.4.0": V140, + "1.5.0": V150, } type versions map[string]int diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 9042493c..3c1e8368 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -94,16 +94,16 @@ type Commit struct { // ImagePathSpec paths in yaml used to parse image type ImagePathSpec struct { - RepoPath string `bson:"repo_path,omitempty" json:"repoPath,omitempty"` - ImagePath string `bson:"image_path,omitempty" json:"imagePath,omitempty"` - TagPath string `bson:"tag_path,omitempty" json:"tagPath,omitempty"` + Repo string `bson:"repo,omitempty" json:"repo,omitempty"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + Tag string `bson:"tag,omitempty" json:"tag,omitempty"` } // Container ... type Container struct { Name string `bson:"name" json:"name"` Image string `bson:"image" json:"image"` - ImagePath *ImagePathSpec `bson:"image_path_spec,omitempty" json:"imagePathSpec,omitempty"` + ImagePath *ImagePathSpec `bson:"image_path,omitempty" json:"imagePath,omitempty"` } // ServiceTmplPipeResp ...router diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index b99d0b9d..27ae4507 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -29,6 +29,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -37,6 +38,7 @@ import ( "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util/converter" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -88,6 +90,10 @@ type ServiceProductMap struct { var ( imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) + presetPatterns = []map[string]string{ + {setting.PathSearchComponentImage: "repository", setting.PathSearchComponentTag: "tag"}, + {setting.PathSearchComponentImage: "image"}, + } ) // ListServiceTemplate 列出服务模板 @@ -541,12 +547,12 @@ func GeneImageURI(pathData map[string]string, flatMap map[string]interface{}) (s valuesMap := getValuesByPath(pathData, flatMap) ret := "" // if repo value is set, use as repo - if repo, ok := valuesMap["repo"]; ok { + if repo, ok := valuesMap[setting.PathSearchComponentRepo]; ok { ret = fmt.Sprintf("%v", repo) ret = strings.TrimSuffix(ret, "/") } // if image value is set, append to repo, if repo is not set, image values represents repo+image - if image, ok := valuesMap["image"]; ok { + if image, ok := valuesMap[setting.PathSearchComponentImage]; ok { imageStr := fmt.Sprintf("%v", image) if ret == "" { ret = imageStr @@ -558,7 +564,7 @@ func GeneImageURI(pathData map[string]string, flatMap map[string]interface{}) (s return "", errors.New("") } // if tag is set, append to current uri, if not set ignore - if tag, ok := valuesMap["tag"]; ok { + if tag, ok := valuesMap[setting.PathSearchComponentTag]; ok { tagStr := fmt.Sprintf("%v", tag) if tagStr != "" { ret = fmt.Sprintf("%s:%s", ret, tagStr) @@ -581,7 +587,7 @@ func ExtractImageName(imageURI string) string { return "" } -func ParseContainers(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { +func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { return nil, err @@ -600,20 +606,66 @@ func ParseContainers(nested map[string]interface{}, patterns []map[string]string Name: ExtractImageName(imageUrl), Image: imageUrl, ImagePath: &models.ImagePathSpec{ - RepoPath: searchResult["repo"], - ImagePath: searchResult["image"], - TagPath: searchResult["tag"], + Repo: searchResult[setting.PathSearchComponentRepo], + Image: searchResult[setting.PathSearchComponentImage], + Tag: searchResult[setting.PathSearchComponentTag], }, }) } return ret, nil } -// SearchImagesByPresetRules parse images from flat yaml map with preset rules -func SearchImagesByPresetRules(flatMap map[string]interface{}) ([]map[string]string, error) { - patterns := []map[string]string{ - {"image": "repository", "tag": "tag"}, - {"image": "image"}, +func ParseImagesByRules(nested map[string]interface{}, matchRules []*template.ImageMatchRules) ([]*models.Container, error) { + patterns := make([]map[string]string, 0) + for _, rule := range matchRules { + if !rule.InUse { + continue + } + patterns = append(patterns, map[string]string{ + setting.PathSearchComponentRepo: rule.Repo, + setting.PathSearchComponentImage: rule.Image, + setting.PathSearchComponentTag: rule.Tag, + }) + } + return parseImagesByPattern(nested, patterns) +} + +// get patterns used to parse images from yaml +func getServiceParsePatterns(productName string) ([]map[string]string, error) { + productInfo, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + return nil, err + } + ret := make([]map[string]string, 0) + for _, rule := range productInfo.ImageMatchRules { + if !rule.InUse { + continue + } + ret = append(ret, map[string]string{ + setting.PathSearchComponentRepo: rule.Repo, + setting.PathSearchComponentImage: rule.Image, + setting.PathSearchComponentTag: rule.Tag, + }) + } + + // rules are never edited, use preset rules + if len(ret) == 0 { + ret = presetPatterns + } + return ret, nil +} + +// ParseImagesForProductService for product service +func ParseImagesForProductService(nested map[string]interface{}, serviceName, productName string) ([]*models.Container, error) { + patterns, err := getServiceParsePatterns(productName) + if err != nil { + log.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", serviceName, productName, err) + return nil, errors.New("failed to get image parse patterns") } - return yamlutil.SearchByPattern(flatMap, patterns) + return parseImagesByPattern(nested, patterns) +} + +// ParseImagesByPresetRules parse images from flat yaml map with preset rules +func ParseImagesByPresetRules(flatMap map[string]interface{}) ([]map[string]string, error) { + return yamlutil.SearchByPattern(flatMap, presetPatterns) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5f5cd5e0..3cc01386 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -45,7 +45,6 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -60,7 +59,6 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" @@ -1220,79 +1218,6 @@ func UpdateMultiHelmProduct(envNames []string, updateType, productName string, u return envStatuses } -func fillContainerParseInfo(prod *commonmodels.Product) error { - rendersetMap := make(map[string]string) - for _, singleData := range prod.ChartInfos { - rendersetMap[singleData.ServiceName] = singleData.ValuesYaml - } - - fillHappen := false - - // fill the parse info - for _, serviceGroup := range prod.Services { - for _, singleService := range serviceGroup { - findEmptySpec := false - for _, container := range singleService.Containers { - if container.ImagePath == nil { - findEmptySpec = true - break - } - } - if !findEmptySpec { - continue - } - flatMap, err := converter.YamlToFlatMap([]byte(rendersetMap[singleService.ServiceName])) - if err != nil { - return err - } - matchedPath, err := commonservice.SearchImagesByPresetRules(flatMap) - if err != nil { - log.Errorf("failed to parse images from service:%s in product:%s", singleService.ServiceName, singleService.ProductName) - continue - } - for _, container := range singleService.Containers { - if container.ImagePath != nil { - continue - } - err = findImageByContainerName(flatMap, matchedPath, container) - if err != nil { - return err - } - fillHappen = true - } - } - } - - if fillHappen { - err := commonrepo.NewProductColl().Update(prod) - if err != nil { - return err - } - } - return nil -} - -// find match rule -func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[string]string, container *models.Container) error { - for _, searchResult := range matchedPath { - imageURI, err := commonservice.GeneImageURI(searchResult, flatMap) - if err != nil { - log.Error("GeneImageURI fail, err %s", err.Error()) - continue - } - if container.Name != commonservice.ExtractImageName(imageURI) { - continue - } - container.ImagePath = &models.ImagePathSpec{ - RepoPath: searchResult["repo"], - ImagePath: searchResult["image"], - TagPath: searchResult["tag"], - } - break - } - return nil -} - func GetProductInfo(username, envName, productName string, log *zap.SugaredLogger) (*commonmodels.Product, error) { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) @@ -1310,10 +1235,10 @@ func GetProductInfo(username, envName, productName string, log *zap.SugaredLogge } prod.ChartInfos = renderSet.ChartInfos - err = fillContainerParseInfo(prod) - if err != nil { - log.Errorf("fill helm service parse data fail, productName %s err %s", productName, err.Error()) - } + //err = FillImageParseInfo(prod) + //if err != nil { + // log.Errorf("fill helm service parse data fail, productName %s err %s", productName, err.Error()) + //} return prod, nil } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index f68f552f..12ca5b05 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -972,18 +972,6 @@ func UpdateCustomMatchRules(productName string, userName string, matchRules []*I // reparse values.yaml for each service func reParseServices(userName string, serviceList []*commonmodels.Service, matchRules []*template.ImageMatchRules) error { - patterns := make([]map[string]string, 0) - for _, rule := range matchRules { - if !rule.InUse { - continue - } - patterns = append(patterns, map[string]string{ - "repo": rule.Repo, - "image": rule.Image, - "tag": rule.Tag, - }) - } - updatedServiceTmpls := make([]*commonmodels.Service, 0) var err error @@ -1000,13 +988,13 @@ func reParseServices(userName string, serviceList []*commonmodels.Service, match break } - serviceTmpl.Containers, err = commonservice.ParseContainers(valuesMap, patterns) + serviceTmpl.Containers, err = commonservice.ParseImagesByRules(valuesMap, matchRules) if err != nil { break } if len(serviceTmpl.Containers) == 0 { - log.Warnf("service:%s containers is empty after parse, pattern %v, valuesYaml %s", serviceTmpl.ServiceName, patterns, valuesYaml) + log.Warnf("service:%s containers is empty after parse, valuesYaml %s", serviceTmpl.ServiceName, valuesYaml) } serviceTmpl.CreateBy = userName diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 476ad816..b65c3169 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -193,36 +193,6 @@ func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs } } -// get patterns used to parse images from yaml -func getServiceParsePatterns(productName string) ([]map[string]string, error) { - productInfo, err := templaterepo.NewProductColl().Find(productName) - if err != nil { - return nil, err - } - ret := make([]map[string]string, 0) - for _, rule := range productInfo.ImageMatchRules { - if !rule.InUse { - continue - } - ret = append(ret, map[string]string{ - "repo": rule.Repo, - "image": rule.Image, - "tag": rule.Tag, - }) - } - - // rules are never edited, use preset rules - if len(ret) == 0 { - ret = append(ret, map[string]string{ - "image": "image.repository", "tag": "image.tag", - }, map[string]string{ - "image": "image", - }) - } - - return ret, nil -} - func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { @@ -296,12 +266,6 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return err } - patterns, err := getServiceParsePatterns(projectName) - if err != nil { - logger.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", args.Name, projectName, err) - return errors.New("failed to get image parse patterns") - } - svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ @@ -314,7 +278,6 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe Repo: templateArgs.Repo, Branch: templateArgs.Branch, }, - patterns, logger, ) if err != nil { @@ -396,13 +359,6 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } - patterns, err := getServiceParsePatterns(projectName) - if err != nil { - log.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", args.Name, projectName, err) - finalErr = errors.New("failed to get image parse patterns") - return - } - svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ @@ -416,7 +372,6 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC Branch: repoArgs.Branch, RepoLink: repoLink, }, - patterns, log, ) if err != nil { @@ -473,7 +428,7 @@ func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([] return content, valuesMap, nil } -func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, matchPatterns []map[string]string, logger *zap.SugaredLogger) (*models.Service, error) { +func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*models.Service, error) { chartName, chartVersion, err := readChartYAML(fsTree, filepath.Base(args.FilePath), logger) if err != nil { logger.Errorf("Failed to read chart.yaml, err %s", err) @@ -497,7 +452,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, matc logger.Warnf("Failed to delete stale service %s with revision %d, err: %s", args.ServiceName, rev, err) } - containerList, err := commonservice.ParseContainers(valuesMap, matchPatterns) + containerList, err := commonservice.ParseImagesForProductService(valuesMap, args.ServiceName, args.ProductName) if err != nil { return nil, errors.Wrapf(err, "failed to parse service from yaml") } @@ -613,13 +568,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { return e.ErrCreateTemplate.AddDesc("values.yaml解析失败") } - patterns, err := getServiceParsePatterns(preServiceTmpl.ProductName) - if err != nil { - log.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", preServiceTmpl.ServiceName, preServiceTmpl.ProductName, err) - return errors.New("failed to get image parse patterns") - } - - containerList, err := commonservice.ParseContainers(valuesMap, patterns) + containerList, err := commonservice.ParseImagesForProductService(valuesMap, preServiceTmpl.ServiceName, preServiceTmpl.ProductName) if err != nil { return e.ErrUpdateTemplate.AddErr(errors.Wrapf(err, "failed to parse images from yaml")) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index be5fb453..30412152 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -419,7 +419,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } - replaceValuesYaml, err = p.replaceImage(targetContainer.ImagePathSpec, currentValuesYamlMap, p.Task.Image) + replaceValuesYaml, err = p.replaceImage(targetContainer.ImagePath, currentValuesYamlMap, p.Task.Image) if err != nil { log.Error("failed to replace image: %s", p.Task.Image) } @@ -577,14 +577,14 @@ func (p *DeployTaskPlugin) updateRenderSet(ctx context.Context, args *types.Rend func getValidMatchData(spec *types.ImagePathSpec) map[string]string { ret := make(map[string]string) - if spec.RepoPath != "" { - ret["repo"] = spec.RepoPath + if spec.Repo != "" { + ret[setting.PathSearchComponentRepo] = spec.Repo } - if spec.ImagePath != "" { - ret["image"] = spec.ImagePath + if spec.Image != "" { + ret[setting.PathSearchComponentImage] = spec.Image } - if spec.TagPath != "" { - ret["tag"] = spec.TagPath + if spec.Tag != "" { + ret[setting.PathSearchComponentTag] = spec.Tag } return ret } @@ -609,6 +609,7 @@ func (p *DeployTaskPlugin) replaceImage(imagePathSpec *types.ImagePathSpec, valu // assign image url data into match data // matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path +// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { ret := make(map[string]interface{}) // total image url assigned into one single value @@ -621,31 +622,35 @@ func assignImageData(imageUrl string, matchData map[string]string) (map[string]i resolvedImageUrl := resolveImageUrl(imageUrl) - // image url assigned into repo+image+tag + // image url assigned into repo/image+tag if len(matchData) == 3 { - ret[matchData["repo"]] = strings.TrimSuffix(resolvedImageUrl["repo"], "/") - ret[matchData["image"]] = resolvedImageUrl["image"] - ret[matchData["tag"]] = resolvedImageUrl["tag"] + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] + ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] return ret, nil } - // image url assigned into repo/image + tag - if tagPath, ok := matchData["tag"]; ok { - ret[tagPath] = resolvedImageUrl["tag"] - for k, imagePath := range matchData { - if k == "tag" { - continue + if len(matchData) == 2 { + // image url assigned into repo/image + tag + if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { + ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] + for k, imagePath := range matchData { + if k == setting.PathSearchComponentTag { + continue + } + ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) + break } - ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl["repo"], resolvedImageUrl["image"]) - break + return ret, nil + } else { + // image url assigned into repo + image(tag) + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) + return ret, nil } - return ret, nil - } else { - // image url assigned into repo + image(tag) - ret[matchData["repo"]] = strings.TrimSuffix(resolvedImageUrl["repo"], "/") - ret[matchData["image"]] = fmt.Sprintf("%s:%s", resolvedImageUrl["image"], resolvedImageUrl["tag"]) - return ret, nil } + + return nil, fmt.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) } // parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx diff --git a/pkg/microservice/warpdrive/core/service/types/service_tmpl.go b/pkg/microservice/warpdrive/core/service/types/service_tmpl.go index 1d127b5f..db74920c 100644 --- a/pkg/microservice/warpdrive/core/service/types/service_tmpl.go +++ b/pkg/microservice/warpdrive/core/service/types/service_tmpl.go @@ -91,16 +91,16 @@ type Commit struct { // ImagePathSpec paths in yaml used to parse image type ImagePathSpec struct { - RepoPath string `bson:"repo_path,omitempty" json:"repoPath,omitempty"` - ImagePath string `bson:"image_path,omitempty" json:"imagePath,omitempty"` - TagPath string `bson:"tag_path,omitempty" json:"tagPath,omitempty"` + Repo string `bson:"repo,omitempty" json:"repo,omitempty"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + Tag string `bson:"tag,omitempty" json:"tag,omitempty"` } // Container ... type Container struct { - Name string `bson:"name" json:"name"` - Image string `bson:"image" json:"image"` - ImagePathSpec *ImagePathSpec `bson:"image_path_spec,omitempty" json:"imagePathSpec,omitempty"` + Name string `bson:"name" json:"name"` + Image string `bson:"image" json:"image"` + ImagePath *ImagePathSpec `bson:"image_path,omitempty" json:"imagePath,omitempty"` } type PmHealthCheck struct { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 8b469cf2..5643355e 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -460,7 +460,12 @@ const ( // helm related const ( ValuesYamlSourceFreeEdit = "freeEdit" - ValuesYamlSourceGitRepo = "gitRepo" + ValuesYamlSourceGitRepo = "gitRepo" + + // components used to search image paths from yaml + PathSearchComponentRepo = "repo" + PathSearchComponentImage = "image" + PathSearchComponentTag = "tag" ) // Aliyun specific stuff diff --git a/version/version.go b/version/version.go index 28527c95..c9539103 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.4.0" + Version = "1.5.0" BuildNumber = "0" GitCommit = "" ) -- Gitee From dfd28ade58bf93a35a8d4587d98ebdd3e7687f99 Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 26 Sep 2021 17:33:06 +0800 Subject: [PATCH 0017/2647] refactor code Signed-off-by: allenshen --- .../aslan/core/common/service/service.go | 18 ++++++++++++-- .../aslan/core/project/handler/product.go | 1 - .../aslan/core/project/service/product.go | 24 +++++++------------ 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 27ae4507..03f541dd 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -91,7 +91,7 @@ type ServiceProductMap struct { var ( imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) presetPatterns = []map[string]string{ - {setting.PathSearchComponentImage: "repository", setting.PathSearchComponentTag: "tag"}, + {setting.PathSearchComponentImage: "image.repository", setting.PathSearchComponentTag: "image.tag"}, {setting.PathSearchComponentImage: "image"}, } ) @@ -561,7 +561,7 @@ func GeneImageURI(pathData map[string]string, flatMap map[string]interface{}) (s } } if ret == "" { - return "", errors.New("") + return "", errors.New("image name not found") } // if tag is set, append to current uri, if not set ignore if tag, ok := valuesMap[setting.PathSearchComponentTag]; ok { @@ -669,3 +669,17 @@ func ParseImagesForProductService(nested map[string]interface{}, serviceName, pr func ParseImagesByPresetRules(flatMap map[string]interface{}) ([]map[string]string, error) { return yamlutil.SearchByPattern(flatMap, presetPatterns) } + +func GetPresetRules() []*template.ImageMatchRules { + ret := make([]*template.ImageMatchRules, 0, len(presetPatterns)) + for id, pattern := range presetPatterns { + ret = append(ret, &template.ImageMatchRules{ + Repo: pattern[setting.PathSearchComponentRepo], + Image: pattern[setting.PathSearchComponentImage], + Tag: pattern[setting.PathSearchComponentTag], + InUse: true, + PresetId: id + 1, + }) + } + return ret +} diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 232afa60..306b606a 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -205,7 +205,6 @@ func CreateOrUpdateMatchRules(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.Username, c.Param("name"), "更新", "工程管理-项目", c.Param("name"), permission.SuperUserUUID, "", ctx.Logger) - //args := make([]*projectservice.ImageParseData, 0) args := new(projectservice.CustomParseDataArgs) data, err := c.GetRawData() if err != nil { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 12ca5b05..32f16ab9 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -887,8 +887,14 @@ func GetCustomMatchRules(productName string, log *zap.SugaredLogger) ([]*ImagePa log.Errorf("query product:%s fail, err:%s", productName, err.Error()) return nil, fmt.Errorf("failed to find product %s", productName) } - ret := make([]*ImageParseData, 0, len(productInfo.ImageMatchRules)) - for _, singleData := range productInfo.ImageMatchRules { + + rules := productInfo.ImageMatchRules + if len(rules) == 0 { + rules = commonservice.GetPresetRules() + } + + ret := make([]*ImageParseData, 0, len(rules)) + for _, singleData := range rules { ret = append(ret, &ImageParseData{ Repo: singleData.Repo, Image: singleData.Image, @@ -897,20 +903,6 @@ func GetCustomMatchRules(productName string, log *zap.SugaredLogger) ([]*ImagePa PresetId: singleData.PresetId, }) } - // use preset data if rules are never edited - if len(ret) == 0 { - ret = append(ret, &ImageParseData{ - Repo: "", - Image: "image.repository", - Tag: "image.tag", - InUse: true, - PresetId: 1, - }, &ImageParseData{ - Image: "image", - InUse: true, - PresetId: 2, - }) - } return ret, nil } -- Gitee From 3c25447706982396e8a01e0fca06f7ef70a4780d Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 26 Sep 2021 21:18:17 +0800 Subject: [PATCH 0018/2647] optimize code Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 6 +++ .../internal/upgradepath/dag_test.go | 22 +++++----- .../repository/models/template/product.go | 44 +++++++++---------- .../aslan/core/common/service/service.go | 12 ++--- .../core/environment/service/environment.go | 5 --- .../aslan/core/project/handler/router.go | 4 +- .../aslan/core/project/service/product.go | 12 ++--- pkg/util/yaml/path_search_test.go | 32 ++++++++------ 8 files changed, 71 insertions(+), 66 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index dee215fd..ae815443 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -32,6 +32,7 @@ import ( func init() { upgradepath.AddHandler(upgradepath.V140, upgradepath.V150, V140ToV150) + upgradepath.AddHandler(upgradepath.V150, upgradepath.V140, V150ToV140) } // V140ToV150 fill image path data for old data in product.services.containers @@ -64,6 +65,11 @@ func V140ToV150() error { return nil } +func V150ToV140() error { + log.Info("Rollback data from 1.5.0 to 1.4.0") + return nil +} + func missContainerImagePath(container *commonmodels.Container) bool { if container.ImagePath == nil { return true diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go b/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go index aac1f9d3..1a999f64 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go @@ -57,26 +57,26 @@ var _ = Describe("Testing dag", func() { Context("upgradeWithBestPath finds a short path", func() { BeforeEach(func() { - AddHandler(0, 1, func() error { - called0To1 = true - return nil - }) + //AddHandler(0, 1, func() error { + // called0To1 = true + // return nil + //}) AddHandler(1, 2, func() error { called1To2 = true return nil }) - AddHandler(0, 2, func() error { - called0To2 = true - return nil - }) + //AddHandler(0, 2, func() error { + // called0To2 = true + // return nil + //}) }) It("should upgrade cross version", func() { err := upgradeWithBestPath(0, 2) Expect(err).ShouldNot(HaveOccurred()) - Expect(called0To1).To(BeFalse()) - Expect(called1To2).To(BeFalse()) - Expect(called0To2).To(BeTrue()) + Expect(called0To1).To(BeTrue()) + Expect(called1To2).To(BeTrue()) + Expect(called0To2).To(BeFalse()) }) }) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 9fde6e0d..db33d384 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -21,27 +21,27 @@ import "strings" // Vars不做保存,只做input参数 // product_name 当前项目主键 type Product struct { - ProjectName string `bson:"project_name" json:"project_name"` - ProductName string `bson:"product_name" json:"product_name"` - Revision int64 `bson:"revision" json:"revision"` - CreateTime int64 `bson:"create_time" json:"create_time"` - Teams []*Team `bson:"teams" json:"teams"` - Team string `bson:"team" json:"team"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - Enabled bool `bson:"enabled" json:"enabled"` - Visibility string `bson:"visibility" json:"visibility"` - Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` - Services [][]string `bson:"services" json:"services"` - SharedServices []*ServiceInfo `bson:"shared_services,omitempty" json:"shared_services,omitempty"` - Vars []*RenderKV `bson:"vars" json:"vars"` - EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` - ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` - UserIDs []int `bson:"user_ids" json:"user_ids"` - TeamID int `bson:"team_id" json:"team_id"` - Description string `bson:"description,omitempty" json:"desc,omitempty"` - ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` - ImageMatchRules []*ImageMatchRules `bson:"image_match_rules,omitempty" json:"image_match_rules,omitempty"` + ProjectName string `bson:"project_name" json:"project_name"` + ProductName string `bson:"product_name" json:"product_name"` + Revision int64 `bson:"revision" json:"revision"` + CreateTime int64 `bson:"create_time" json:"create_time"` + Teams []*Team `bson:"teams" json:"teams"` + Team string `bson:"team" json:"team"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + Enabled bool `bson:"enabled" json:"enabled"` + Visibility string `bson:"visibility" json:"visibility"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Services [][]string `bson:"services" json:"services"` + SharedServices []*ServiceInfo `bson:"shared_services,omitempty" json:"shared_services,omitempty"` + Vars []*RenderKV `bson:"vars" json:"vars"` + EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` + ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` + UserIDs []int `bson:"user_ids" json:"user_ids"` + TeamID int `bson:"team_id" json:"team_id"` + Description string `bson:"description,omitempty" json:"desc,omitempty"` + ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` + ImageSearchingRules []*ImageSearchingRule `bson:"image_searching_rules,omitempty" json:"image_searching_rules,omitempty"` // onboarding状态,0表示onboarding完成,1、2、3、4代表当前onboarding所在的步骤 OnboardingStatus int `bson:"onboarding_status" json:"onboarding_status"` // CI场景的onboarding流程创建的ci工作流id,用于前端跳转 @@ -136,7 +136,7 @@ type ForkProject struct { ProductName string `json:"product_name"` } -type ImageMatchRules struct { +type ImageSearchingRule struct { Repo string `bson:"repo,omitempty"` Image string `bson:"image,omitempty"` Tag string `bson:"tag,omitempty"` diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 03f541dd..483ad3db 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -522,7 +522,7 @@ func getAddressFromPath(path, owner, repo string, logger *zap.Logger) string { return res[0] } -// get values form source flat map +// get values from source flat map // convert map[k]absolutePath to map[k]value func getValuesByPath(paths map[string]string, flatMap map[string]interface{}) map[string]interface{} { ret := make(map[string]interface{}) @@ -615,7 +615,7 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s return ret, nil } -func ParseImagesByRules(nested map[string]interface{}, matchRules []*template.ImageMatchRules) ([]*models.Container, error) { +func ParseImagesByRules(nested map[string]interface{}, matchRules []*template.ImageSearchingRule) ([]*models.Container, error) { patterns := make([]map[string]string, 0) for _, rule := range matchRules { if !rule.InUse { @@ -637,7 +637,7 @@ func getServiceParsePatterns(productName string) ([]map[string]string, error) { return nil, err } ret := make([]map[string]string, 0) - for _, rule := range productInfo.ImageMatchRules { + for _, rule := range productInfo.ImageSearchingRules { if !rule.InUse { continue } @@ -670,10 +670,10 @@ func ParseImagesByPresetRules(flatMap map[string]interface{}) ([]map[string]stri return yamlutil.SearchByPattern(flatMap, presetPatterns) } -func GetPresetRules() []*template.ImageMatchRules { - ret := make([]*template.ImageMatchRules, 0, len(presetPatterns)) +func GetPresetRules() []*template.ImageSearchingRule { + ret := make([]*template.ImageSearchingRule, 0, len(presetPatterns)) for id, pattern := range presetPatterns { - ret = append(ret, &template.ImageMatchRules{ + ret = append(ret, &template.ImageSearchingRule{ Repo: pattern[setting.PathSearchComponentRepo], Image: pattern[setting.PathSearchComponentImage], Tag: pattern[setting.PathSearchComponentTag], diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index bcf0d3b6..6587609b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1407,11 +1407,6 @@ func GetProductInfo(username, envName, productName string, log *zap.SugaredLogge } prod.ChartInfos = renderSet.ChartInfos - //err = FillImageParseInfo(prod) - //if err != nil { - // log.Errorf("fill helm service parse data fail, productName %s err %s", productName, err.Error()) - //} - return prod, nil } diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index e957e2f3..44acf676 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -51,8 +51,8 @@ func (*Router) Inject(router *gin.RouterGroup) { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) product.GET("", ListProductTemplate) - product.GET("/:name/match-rules", GetCustomMatchRules) - product.PUT("/:name/match-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) + product.GET("/:name/searching-rules", GetCustomMatchRules) + product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 32f16ab9..68c5964e 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -888,7 +888,7 @@ func GetCustomMatchRules(productName string, log *zap.SugaredLogger) ([]*ImagePa return nil, fmt.Errorf("failed to find product %s", productName) } - rules := productInfo.ImageMatchRules + rules := productInfo.ImageSearchingRules if len(rules) == 0 { rules = commonservice.GetPresetRules() } @@ -927,12 +927,12 @@ func UpdateCustomMatchRules(productName string, userName string, matchRules []*I return errors.New("no rule is selected to be used") } - imageRulesToSave := make([]*template.ImageMatchRules, 0) + imageRulesToSave := make([]*template.ImageSearchingRule, 0) for _, singleData := range matchRules { if singleData.Repo == "" && singleData.Image == "" && singleData.Tag == "" { continue } - imageRulesToSave = append(imageRulesToSave, &template.ImageMatchRules{ + imageRulesToSave = append(imageRulesToSave, &template.ImageSearchingRule{ Repo: singleData.Repo, Image: singleData.Image, Tag: singleData.Tag, @@ -941,7 +941,7 @@ func UpdateCustomMatchRules(productName string, userName string, matchRules []*I }) } - productInfo.ImageMatchRules = imageRulesToSave + productInfo.ImageSearchingRules = imageRulesToSave productInfo.UpdateBy = userName services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) @@ -963,7 +963,7 @@ func UpdateCustomMatchRules(productName string, userName string, matchRules []*I } // reparse values.yaml for each service -func reParseServices(userName string, serviceList []*commonmodels.Service, matchRules []*template.ImageMatchRules) error { +func reParseServices(userName string, serviceList []*commonmodels.Service, matchRules []*template.ImageSearchingRule) error { updatedServiceTmpls := make([]*commonmodels.Service, 0) var err error @@ -1014,7 +1014,7 @@ func reParseServices(userName string, serviceList []*commonmodels.Service, match // roll back all template services if error occurs if err != nil { for _, serviceTmpl := range updatedServiceTmpls { - if err = commonrepo.NewServiceColl().Delete(serviceTmpl.ServiceName, setting.HelmDeployType, serviceTmpl.ProductName, setting.ProductStatusDeleting, serviceTmpl.Revision); err != nil { + if err = commonrepo.NewServiceColl().Delete(serviceTmpl.ServiceName, setting.HelmDeployType, serviceTmpl.ProductName, "", serviceTmpl.Revision); err != nil { log.Errorf("helmService.update delete %s error: %v", serviceTmpl.ServiceName, err) continue } diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 8142abd4..bdf48b38 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -140,9 +140,11 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - Expect(len(matedPaths)).To(Equal(2)) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc1.image.repository", "tag": "svc1.image.tag"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc2.image.repository", "tag": "svc2.image.tag"})) + + Expect(matedPaths).Should(ConsistOf([]map[string]string{ + {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, + {"image": "svc2.image.repository", "tag": "svc2.image.tag"}, + })) }) It("multiple match pattern 3", func() { @@ -152,10 +154,11 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml3)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - Expect(len(matedPaths)).To(Equal(3)) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc1.image.repository"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc2.image.repository"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc3.image.repository"})) + Expect(matedPaths).Should(ConsistOf([]map[string]string{ + {"image": "svc1.image.repository"}, + {"image": "svc2.image.repository"}, + {"image": "svc3.image.repository"}, + })) }) It("multiple match pattern complex", func() { @@ -168,13 +171,14 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml4)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - Expect(len(matedPaths)).To(Equal(5)) + Expect(matedPaths).Should(ConsistOf([]map[string]string{ + {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, + {"image": "svc2.image.repository"}, + {"image": "svc3.image.repository"}, + {"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}, + {"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}, + })) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc1.image.repository", "tag": "svc1.image.tag"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc2.image.repository"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc3.image.repository"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"})) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"})) }) It("multiple match pattern complex2", func() { @@ -185,7 +189,7 @@ var _ = Describe("Testing search", func() { matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) Expect(len(matedPaths)).To(Equal(1)) - Expect(matedPaths).Should(ContainElement(map[string]string{"image": "testSpec.imageNew.repo", "tag": "testSpec.imageNew.tag"})) + Expect(matedPaths).To(Equal([]map[string]string{{"image": "testSpec.imageNew.repo", "tag": "testSpec.imageNew.tag"}})) }) }) }) -- Gitee From 9a31aa34510b6935b9e67d7ec6f7f94a3d14a87d Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 26 Sep 2021 21:38:27 +0800 Subject: [PATCH 0019/2647] recover code Signed-off-by: allenshen --- .../internal/upgradepath/dag_test.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go b/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go index 1a999f64..aac1f9d3 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/dag_test.go @@ -57,26 +57,26 @@ var _ = Describe("Testing dag", func() { Context("upgradeWithBestPath finds a short path", func() { BeforeEach(func() { - //AddHandler(0, 1, func() error { - // called0To1 = true - // return nil - //}) + AddHandler(0, 1, func() error { + called0To1 = true + return nil + }) AddHandler(1, 2, func() error { called1To2 = true return nil }) - //AddHandler(0, 2, func() error { - // called0To2 = true - // return nil - //}) + AddHandler(0, 2, func() error { + called0To2 = true + return nil + }) }) It("should upgrade cross version", func() { err := upgradeWithBestPath(0, 2) Expect(err).ShouldNot(HaveOccurred()) - Expect(called0To1).To(BeTrue()) - Expect(called1To2).To(BeTrue()) - Expect(called0To2).To(BeFalse()) + Expect(called0To1).To(BeFalse()) + Expect(called1To2).To(BeFalse()) + Expect(called0To2).To(BeTrue()) }) }) -- Gitee From 69eb39f436d84d458f0c857ded06dbcfd5327e1f Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 29 Sep 2021 22:09:24 +0800 Subject: [PATCH 0020/2647] add environment values logic Signed-off-by: allenshen --- .../common/repository/models/render_set.go | 19 +- .../aslan/core/common/service/render.go | 45 +++-- .../core/environment/handler/environment.go | 72 ++----- .../core/environment/handler/renderset.go | 46 +++++ .../aslan/core/environment/handler/router.go | 12 +- .../core/environment/service/environment.go | 188 ++++++++++-------- .../service/environment_creator.go | 20 +- .../core/environment/service/renderset.go | 155 +++++++++++++-- .../core/service/taskplugin/deploy.go | 2 +- .../warpdrive/core/service/types/renderset.go | 19 +- pkg/tool/helmclient/helmclient.go | 21 +- 11 files changed, 393 insertions(+), 206 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/render_set.go b/pkg/microservice/aslan/core/common/repository/models/render_set.go index 5899120f..f114671b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/models/render_set.go @@ -28,15 +28,16 @@ type RenderSet struct { Name string `bson:"name" json:"name"` Revision int64 `bson:"revision" json:"revision"` // 可以为空,空时为产品模板默认的渲染集,非空时为环境的渲染集 - EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` - ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - KVs []*templatemodels.RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` - ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + KVs []*templatemodels.RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` + ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` } func (RenderSet) TableName() string { diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 110d4249..5c0b7531 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -48,14 +48,23 @@ type KVPair struct { Value string `json:"value"` } +type YamlData struct { + YamlSource string `json:"yamlSource,omitempty"` + GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` + ValuesYAML string `json:"valuesYAML,omitempty"` +} + type RenderChartArg struct { - EnvName string `json:"envName,omitempty"` - ServiceName string `json:"serviceName,omitempty"` - ChartVersion string `json:"chartVersion,omitempty"` - YamlSource string `json:"yamlSource,omitempty"` - GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` - OverrideValues []*KVPair `json:"overrideValues,omitempty"` - ValuesYAML string `json:"valuesYAML,omitempty"` + EnvName string `json:"envName,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + ChartVersion string `json:"chartVersion,omitempty"` + OverrideValues []*KVPair `json:"overrideValues,omitempty"` + *YamlData +} + +type RendersetArg struct { + DefaultValues *YamlData `json:"defaultValues"` + *RenderChartArg } func (args *RenderChartArg) toOverrideValueString() string { @@ -110,14 +119,20 @@ func (args *RenderChartArg) fromCustomValueYaml(customValuesYaml *templatemodels if customValuesYaml == nil { return } - args.YamlSource = customValuesYaml.YamlSource + switch customValuesYaml.YamlSource { case setting.ValuesYamlSourceFreeEdit: - args.ValuesYAML = customValuesYaml.YamlContent + args.YamlData = &YamlData{ + YamlSource: customValuesYaml.YamlSource, + ValuesYAML: customValuesYaml.YamlContent, + } case setting.ValuesYamlSourceGitRepo: - args.ValuesYAML = "" + args.YamlData = &YamlData{ + YamlSource: customValuesYaml.YamlSource, + ValuesYAML: "", + } if customValuesYaml.GitRepoConfig != nil { - args.GitRepoConfig = &RepoConfig{ + args.YamlData.GitRepoConfig = &RepoConfig{ CodehostID: customValuesYaml.GitRepoConfig.CodehostID, Owner: customValuesYaml.GitRepoConfig.Owner, Repo: customValuesYaml.GitRepoConfig.Repo, @@ -134,7 +149,11 @@ func (args *RenderChartArg) FillRenderChartModel(chart *templatemodels.RenderCha chart.ServiceName = args.ServiceName chart.ChartVersion = version chart.OverrideValues = args.toOverrideValueString() - chart.OverrideYaml = args.toCustomValuesYaml() + if args.YamlData != nil { + chart.OverrideYaml = args.toCustomValuesYaml() + } else { + chart.OverrideYaml = nil + } } // LoadFromRenderChartModel load from render chart model @@ -256,7 +275,7 @@ func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) e if rs != nil && err == nil { // 已经存在渲染配置集 // 判断是否有修改 - if rs.HelmRenderDiff(args) { + if rs.DefaultValues != args.DefaultValues || rs.HelmRenderDiff(args) { args.IsDefault = rs.IsDefault } else { return nil diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 27b0814e..02400d4b 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -228,21 +228,7 @@ func UpdateProductRecycleDay(c *gin.Context) { ctx.Err = service.UpdateProductRecycleDay(envName, productName, recycleDay) } -func UpdateHelmProduct(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - envName := c.Query("envName") - updateType := c.Query("updateType") - productName := c.Param("productName") - - ctx.Err = service.UpdateHelmProduct(productName, envName, updateType, ctx.Username, ctx.RequestID, nil, ctx.Logger) - if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product %s %s: %v", envName, productName, ctx.Err) - } -} - -func UpdateHelmProductRenderCharts(c *gin.Context) { +func UpdateHelmProductRenderset(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -258,7 +244,7 @@ func UpdateHelmProductRenderCharts(c *gin.Context) { return } - arg := new(service.EnvRenderChartArg) + arg := new(service.EnvRendersetArg) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) @@ -268,37 +254,39 @@ func UpdateHelmProductRenderCharts(c *gin.Context) { } internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "更新环境变量", "", permission.TestEnvManageUUID, string(data), ctx.Logger) - ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.Username, ctx.RequestID, arg.ChartValues, ctx.Logger) + ctx.Err = service.UpdateHelmProductRenderset(productName, envName, ctx.Username, ctx.RequestID, arg, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) } } -func UpdateHelmProductVariable(c *gin.Context) { +func UpdateHelmProductRenderCharts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") productName := c.Param("productName") + if productName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + return + } + + envName := c.Query("envName") + if envName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") + return + } - args := new(ChartInfoArgs) + arg := new(service.EnvRenderChartArg) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) } - if err = json.Unmarshal(data, args); err != nil { + if err = json.Unmarshal(data, arg); err != nil { log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "helm集成环境变量", "", permission.TestEnvManageUUID, string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.BindJSON(args); err != nil { - ctx.Logger.Error(err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } + internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "更新环境变量", "", permission.TestEnvManageUUID, string(data), ctx.Logger) - ctx.Err = service.UpdateHelmProductVariable(productName, envName, ctx.Username, ctx.RequestID, args.ChartInfos, args.ChartInfos, ctx.Logger) + ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.Username, ctx.RequestID, arg, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) } @@ -330,30 +318,6 @@ func UpdateMultiHelmEnv(c *gin.Context) { ) } -func UpdateMultiHelmProduct(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(UpdateEnvs) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateMultiHelmProduct c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateMultiHelmProduct json.Unmarshal err : %v", err) - } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新helm环境", "集成环境", strings.Join(args.EnvNames, ","), permission.TestEnvManageUUID, string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.BindJSON(args); err != nil { - ctx.Logger.Error(err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - - ctx.Resp = service.UpdateMultiHelmProduct(args.EnvNames, args.UpdateType, c.Param("productName"), ctx.User.ID, ctx.User.IsSuperUser, ctx.RequestID, ctx.Logger) -} - func GetProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 47378a0c..c970a372 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -47,6 +47,23 @@ func GetServiceRenderCharts(c *gin.Context) { ctx.Resp, ctx.Err = service.GetRenderCharts(c.Query("productName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) } +func GetProductDefaultValues(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + if c.Query("productName") == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") + return + } + + if c.Query("envName") == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") + return + } + + ctx.Resp, ctx.Err = service.GetDefaultValues(c.Query("productName"), c.Query("envName"), ctx.Logger) +} + func CreateOrUpdateRenderChart(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -75,3 +92,32 @@ func CreateOrUpdateRenderChart(c *gin.Context) { ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) } + +func CreateOrUpdateRenderset(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + if c.Query("productName") == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") + return + } + + if c.Query("envName") == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") + return + } + + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) + } + + args := new(commonservice.RendersetArg) + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + } + internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), fmt.Sprintf("%s,%s", permission.TestEnvManageUUID, permission.ProdEnvManageUUID), string(data), ctx.Logger) + + ctx.Err = service.CreateOrUpdateRenderset(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 65afabdc..782d2f56 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -107,6 +107,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.POST("/:productName", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProduct) environments.PUT("/:productName/envRecycle", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) + + environments.PUT("/:productName/renderset", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) + //TODO need to be deprecated environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) @@ -132,9 +135,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) // need to be deprecated - environments.PUT("/:productName/helmEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProduct) - environments.PUT("/:productName/helmEnvVariable", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductVariable) - environments.PUT("/:productName/updateMultiEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateMultiHelmProduct) + //environments.PUT("/:productName/helmEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProduct) + //environments.PUT("/:productName/helmEnvVariable", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductVariable) + //environments.PUT("/:productName/updateMultiEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateMultiHelmProduct) } // --------------------------------------------------------------------------------------- @@ -143,6 +146,9 @@ func (*Router) Inject(router *gin.RouterGroup) { rendersets := router.Group("rendersets") { rendersets.GET("/renderchart", GetServiceRenderCharts) + rendersets.GET("/default-values", GetProductDefaultValues) + rendersets.PUT("/renderset", CreateOrUpdateRenderset) + //TODO need to deprecated rendersets.PUT("/renderchart", CreateOrUpdateRenderChart) // create or update renderchart when onBoarding } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7db9849f..be01f76d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -106,11 +106,17 @@ type EnvRenderChartArg struct { ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } +type EnvRendersetArg struct { + DefaultValues *commonservice.YamlData `json:"defaultValues"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` +} + type CreateHelmProductArg struct { - ProductName string `json:"productName"` - EnvName string `json:"envName"` - ClusterID string `json:"clusterID"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + ProductName string `json:"productName"` + EnvName string `json:"envName"` + ClusterID string `json:"clusterID"` + DefaultValues *commonservice.YamlData `json:"defaultValues"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } type UpdateMultiHelmProductArg struct { @@ -1015,24 +1021,34 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l for _, latestChart := range productObj.ChartInfos { if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { - yamlContent, err := generateValuesYaml(singleCV, log) + err := generateValuesYaml(singleCV.YamlData, log) if err != nil { return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", singleCV.ServiceName, err.Error())) } - singleCV.ValuesYAML = yamlContent singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) } } + // default values + var defaultValuesYaml string + if args.DefaultValues != nil { + err = generateValuesYaml(args.DefaultValues, log) + if err != nil { + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content err %s", err.Error())) + } + defaultValuesYaml = args.DefaultValues.ValuesYAML + } + // insert renderset info into db if len(productObj.ChartInfos) > 0 { err = commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ - Name: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName), - EnvName: args.EnvName, - ProductTmpl: args.ProductName, - UpdateBy: userName, - IsDefault: false, - ChartInfos: productObj.ChartInfos, + Name: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName), + EnvName: args.EnvName, + ProductTmpl: args.ProductName, + UpdateBy: userName, + IsDefault: false, + DefaultValues: defaultValuesYaml, + ChartInfos: productObj.ChartInfos, }, log) if err != nil { log.Errorf("rennderset create fail when creating helm product, productName: %s", args.ProductName) @@ -1123,7 +1139,66 @@ func checkOverrideValuesChange(source *template.RenderChart, args *commonservice return false } -func UpdateHelmProductRenderCharts(productName, envName, userName, requestID string, renderCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { + renderSetName := commonservice.GetProductEnvNamespace(envName, productName) + + opt := &commonrepo.RenderSetFindOption{Name: renderSetName} + productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil || productRenderset == nil { + if err != nil { + log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) + } + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) + } + + // render charts need to be updated + updatedRcList := make([]*template.RenderChart, 0) + updatedRCMap := make(map[string]*template.RenderChart) + + // default values + if args.DefaultValues != nil { + err := generateValuesYaml(args.DefaultValues, log) + if err != nil { + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content err %s", err.Error())) + } + // all charts need to be updated when environment's default value changed + if args.DefaultValues.ValuesYAML != productRenderset.DefaultValues { + for _, curRenderChart := range productRenderset.ChartInfos { + updatedRCMap[curRenderChart.ServiceName] = curRenderChart + } + } + productRenderset.DefaultValues = args.DefaultValues.ValuesYAML + } + + for _, requestRenderChart := range args.ChartValues { + err := generateValuesYaml(requestRenderChart.YamlData, log) + if err != nil { + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) + } + + // update renderset info + for _, curRenderChart := range productRenderset.ChartInfos { + if curRenderChart.ServiceName != requestRenderChart.ServiceName { + continue + } + if !checkOverrideValuesChange(curRenderChart, requestRenderChart) { + continue + } + requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) + updatedRCMap[curRenderChart.ServiceName] = curRenderChart + break + } + } + + for _, updatedRc := range updatedRCMap { + updatedRcList = append(updatedRcList, updatedRc) + } + + return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcList, productRenderset, log) +} + +// UpdateHelmProductRenderCharts TODO need to be deprecated +func UpdateHelmProductRenderCharts(productName, envName, userName, requestID string, args *EnvRenderChartArg, log *zap.SugaredLogger) error { renderSetName := commonservice.GetProductEnvNamespace(envName, productName) @@ -1139,12 +1214,11 @@ func UpdateHelmProductRenderCharts(productName, envName, userName, requestID str // render charts need to be updated updatedRcs := make([]*template.RenderChart, 0) - for _, requestRenderChart := range renderCharts { - yamlContent, err := generateValuesYaml(requestRenderChart, log) + for _, requestRenderChart := range args.ChartValues { + err := generateValuesYaml(requestRenderChart.YamlData, log) if err != nil { return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) } - requestRenderChart.ValuesYAML = yamlContent // update renderset info for _, curRenderChart := range productRenderset.ChartInfos { @@ -1160,10 +1234,10 @@ func UpdateHelmProductRenderCharts(productName, envName, userName, requestID str } } - return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcs, productRenderset.ChartInfos, log) + return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcs, productRenderset, log) } -func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs, allRcs []*template.RenderChart, log *zap.SugaredLogger) error { +func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -1178,11 +1252,12 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, if err = commonservice.CreateHelmRenderSet( &commonmodels.RenderSet{ - Name: productResp.Namespace, - EnvName: envName, - ProductTmpl: productName, - UpdateBy: username, - ChartInfos: allRcs, + Name: productResp.Namespace, + EnvName: envName, + ProductTmpl: productName, + UpdateBy: username, + DefaultValues: renderset.DefaultValues, + ChartInfos: renderset.ChartInfos, }, log, ); err != nil { @@ -1201,10 +1276,10 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, } productResp.Render.Revision = renderSet.Revision - return updateHelmProductVariable(productResp, oldRenderVersion, username, requestID, log) + return updateHelmProductVariable(productResp, renderSet, oldRenderVersion, username, requestID, log) } -func updateHelmProductVariable(productResp *commonmodels.Product, oldRenderVersion int64, userName, requestID string, log *zap.SugaredLogger) error { +func updateHelmProductVariable(productResp *commonmodels.Product, renderset *commonmodels.RenderSet, oldRenderVersion int64, userName, requestID string, log *zap.SugaredLogger) error { envName, productName := productResp.EnvName, productResp.ProductName @@ -1215,7 +1290,7 @@ func updateHelmProductVariable(productResp *commonmodels.Product, oldRenderVersi } go func() { - err := updateProductVariable(productName, envName, productResp, log) + err := updateProductVariable(productName, envName, productResp, renderset, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -1341,55 +1416,6 @@ func UpdateMultipleHelmEnv(userName, requestID string, userID int, superUser boo return envStatuses, nil } -// UpdateMultiHelmProduct TODO need to be deprecated -func UpdateMultiHelmProduct(envNames []string, updateType, productName string, userID int, superUser bool, requestID string, log *zap.SugaredLogger) []*EnvStatus { - mutexUpdateMultiHelm.Lock() - defer func() { - mutexUpdateMultiHelm.Unlock() - }() - - envStatuses := make([]*EnvStatus, 0) - productsRevison, err := ListProductsRevision(productName, "", userID, superUser, log) - if err != nil { - log.Errorf("UpdateMultiHelmProduct ListProductsRevision err:%v", err) - return envStatuses - } - productMap := make(map[string]*ProductRevision) - for _, productRevison := range productsRevison { - if productRevison.ProductName == productName && sets.NewString(envNames...).Has(productRevison.EnvName) && productRevison.Updatable { - productMap[productRevison.EnvName] = productRevison - if len(productMap) == len(envNames) { - break - } - } - } - - for envName := range productMap { - err = UpdateHelmProduct(productName, envName, updateType, setting.SystemUser, requestID, nil, log) - if err != nil { - log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) - return envStatuses - } - } - - productResps := make([]*ProductResp, 0) - for _, envName := range envNames { - productResp, err := GetProduct(setting.SystemUser, envName, productName, log) - if err == nil && productResp != nil { - productResps = append(productResps, productResp) - } - } - - for _, productResp := range productResps { - if productResp.Error != "" { - envStatuses = append(envStatuses, &EnvStatus{EnvName: productResp.EnvName, Status: setting.ProductStatusFailed, ErrMessage: productResp.Error}) - continue - } - envStatuses = append(envStatuses, &EnvStatus{EnvName: productResp.EnvName, Status: productResp.Status}) - } - return envStatuses -} - func GetProductInfo(username, envName, productName string, log *zap.SugaredLogger) (*commonmodels.Product, error) { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) @@ -2466,7 +2492,7 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpdateHelmChart(user, envName, requestID string, args *commonmodels.Product, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { +func installOrUpdateHelmChart(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { var ( err error wg sync.WaitGroup @@ -2539,7 +2565,7 @@ func installOrUpdateHelmChart(user, envName, requestID string, args *commonmodel return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderset.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, @@ -2731,7 +2757,7 @@ func updateProductGroup(productName, envName, updateType string, productResp *co return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderSet.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, @@ -2931,7 +2957,7 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte) ([]byte, error) return yaml.Marshal(latestValuesMap) } -func updateProductVariable(productName, envName string, productResp *commonmodels.Product, log *zap.SugaredLogger) error { +func updateProductVariable(productName, envName string, productResp *commonmodels.Product, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { restConfig, err := kube.GetRESTConfig(productResp.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) @@ -2962,7 +2988,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } serviceObj, err := commonrepo.NewServiceColl().Find(opt) if err != nil { - log.Errorf("finded to find service %s, err %s", service.ServiceName, err.Error()) + log.Errorf("failed to find service %s, err %s", service.ServiceName, err.Error()) continue } wg.Add(1) @@ -2984,7 +3010,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(tmpRenderChart.ValuesYaml, tmpRenderChart.GetOverrideYaml(), tmpRenderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(tmpRenderChart.ValuesYaml, renderset.DefaultValues, tmpRenderChart.GetOverrideYaml(), tmpRenderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index d1cefcf7..1a0f683a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -151,9 +151,9 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P } // renderset may exist before product created, by setting values.yaml content - var renderSet *models.RenderSet + var renderset *models.RenderSet if args.Render == nil || args.Render.Revision == 0 { - renderSet, _, err = commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ + renderset, _, err = commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ Name: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName), }) @@ -162,15 +162,15 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } // if env renderset is predefined, set render info - if renderSet != nil { + if renderset != nil { args.Render = &models.RenderInfo{ ProductTmpl: args.ProductName, - Name: renderSet.Name, - Revision: renderSet.Revision, + Name: renderset.Name, + Revision: renderset.Revision, } // user renderchart from renderset chartInfoMap := make(map[string]*template.RenderChart) - for _, renderChart := range renderSet.ChartInfos { + for _, renderChart := range renderset.ChartInfos { chartInfoMap[renderChart.ServiceName] = renderChart } @@ -189,8 +189,8 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } - if renderSet == nil { - renderSet, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) + if renderset == nil { + renderset, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) if err != nil { log.Errorf("[%s][P:%s] find product renderset error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddDesc(err.Error()) @@ -198,7 +198,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P } // 设置产品render revision - args.Render.Revision = renderSet.Revision + args.Render.Revision = renderset.Revision // 记录服务当前对应render版本 setServiceRender(args) @@ -215,7 +215,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P eventStart := time.Now().Unix() - go installOrUpdateHelmChart(user, args.EnvName, requestID, args, eventStart, helmClient, log) + go installOrUpdateHelmChart(user, args.EnvName, requestID, args, renderset, eventStart, helmClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 41076039..5a7a2a35 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -36,6 +36,10 @@ import ( yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) +type DefaultValuesResp struct { + DefaultValues string `json:"defaultValues"` +} + func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*commonservice.RenderChartArg, error) { renderSetName := commonservice.GetProductEnvNamespace(envName, productName) @@ -77,6 +81,26 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL return ret, nil } +func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*DefaultValuesResp, error) { + renderSetName := commonservice.GetProductEnvNamespace(envName, productName) + + ret := &DefaultValuesResp{} + + opt := &commonrepo.RenderSetFindOption{ + Name: renderSetName, + } + rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil { + return nil, err + } + + if !existed { + return ret, nil + } + ret.DefaultValues = rendersetObj.DefaultValues + return ret, nil +} + // validate yaml content func validateYamlContent(yamlContent string) error { tMap := map[string]interface{}{} @@ -88,12 +112,15 @@ func validateYamlContent(yamlContent string) error { return nil } -func generateValuesYaml(args *commonservice.RenderChartArg, log *zap.SugaredLogger) (string, error) { - if args.YamlSource == setting.ValuesYamlSourceFreeEdit { - return args.ValuesYAML, validateYamlContent(args.ValuesYAML) - } else if args.YamlSource == setting.ValuesYamlSourceGitRepo { - if args.GitRepoConfig == nil { - return "", errors.New("invalid repo config") +func generateValuesYaml(yamlData *commonservice.YamlData, log *zap.SugaredLogger) error { + if yamlData == nil { + return nil + } + if yamlData.YamlSource == setting.ValuesYamlSourceFreeEdit { + return validateYamlContent(yamlData.ValuesYAML) + } else if yamlData.YamlSource == setting.ValuesYamlSourceGitRepo { + if yamlData.GitRepoConfig == nil { + return errors.New("invalid repo config") } var ( allValues []byte @@ -102,20 +129,20 @@ func generateValuesYaml(args *commonservice.RenderChartArg, log *zap.SugaredLogg err error ) - for index, filePath := range args.GitRepoConfig.ValuesPaths { + for index, filePath := range yamlData.GitRepoConfig.ValuesPaths { wg.Add(1) go func(index int, path string) { defer wg.Done() fileContent, err1 := fsservice.DownloadFileFromSource( &fsservice.DownloadFromSourceArgs{ - CodehostID: args.GitRepoConfig.CodehostID, - Owner: args.GitRepoConfig.Owner, - Repo: args.GitRepoConfig.Repo, + CodehostID: yamlData.GitRepoConfig.CodehostID, + Owner: yamlData.GitRepoConfig.Owner, + Repo: yamlData.GitRepoConfig.Repo, Path: path, - Branch: args.GitRepoConfig.Branch, + Branch: yamlData.GitRepoConfig.Branch, }) if err1 != nil { - err = errors.Errorf("fail to download file from git, err: %s, path: %s, repo: %v", err1.Error(), path, *args.GitRepoConfig) + err = errors.Errorf("fail to download file from git, err: %s, path: %s, repo: %v", err1.Error(), path, *yamlData.GitRepoConfig) return } fileContentMap.Store(index, fileContent) @@ -124,21 +151,108 @@ func generateValuesYaml(args *commonservice.RenderChartArg, log *zap.SugaredLogg wg.Wait() if err != nil { - return "", err + return err } - allValueYamls := make([][]byte, len(args.GitRepoConfig.ValuesPaths), len(args.GitRepoConfig.ValuesPaths)) - for i := 0; i < len(args.GitRepoConfig.ValuesPaths); i++ { + allValueYamls := make([][]byte, len(yamlData.GitRepoConfig.ValuesPaths), len(yamlData.GitRepoConfig.ValuesPaths)) + for i := 0; i < len(yamlData.GitRepoConfig.ValuesPaths); i++ { contentObj, _ := fileContentMap.Load(i) allValueYamls[i] = contentObj.([]byte) } allValues, err = yamlutil.Merge(allValueYamls) if err != nil { - return "", errors.Errorf("failed to merge yaml files, repo: %v", *args.GitRepoConfig) + return errors.Errorf("failed to merge yaml files, repo: %v", *yamlData.GitRepoConfig) + } + yamlData.ValuesYAML = string(allValues) + } + return nil +} + +func CreateOrUpdateRenderset(productName, envName string, args *commonservice.RendersetArg, userName, requestID string, log *zap.SugaredLogger) error { + serviceName := args.ServiceName + + serviceOpt := &commonrepo.ServiceFindOption{ + ProductName: productName, + ServiceName: serviceName, + Type: setting.HelmDeployType, + } + serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) + if err != nil { + return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) + } + if serviceObj == nil { + return e.ErrCreateRenderSet.AddDesc("service not found") + } + + if serviceObj.HelmChart == nil { + return e.ErrCreateRenderSet.AddDesc("missing helm chart info") + } + + if args.RenderChartArg != nil && args.RenderChartArg.YamlData != nil { + err := generateValuesYaml(args.RenderChartArg.YamlData, log) + if err != nil { + return e.ErrCreateRenderSet.AddDesc(err.Error()) + } + } + if args.DefaultValues != nil && args.DefaultValues.YamlSource != "" { + err := generateValuesYaml(args.DefaultValues, log) + if err != nil { + return e.ErrCreateRenderSet.AddDesc(err.Error()) + } + } + + renderSetName := commonservice.GetProductEnvNamespace(envName, productName) + + opt := &commonrepo.RenderSetFindOption{Name: renderSetName} + curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil { + return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) + } + + if !found { + curRenderset = &commonmodels.RenderSet{ + Name: renderSetName, + EnvName: envName, + ProductTmpl: productName, + UpdateBy: userName, + IsDefault: false, } - return string(allValues), nil } - return "", nil + + //update or insert service values.yaml + if args.RenderChartArg != nil { + var targetChartInfo *template.RenderChart + for _, singleChart := range curRenderset.ChartInfos { + if singleChart.ServiceName != serviceName { + continue + } + args.RenderChartArg.FillRenderChartModel(singleChart, singleChart.ChartVersion) + targetChartInfo = singleChart + break + } + if targetChartInfo == nil { + targetChartInfo = new(template.RenderChart) + targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml + args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) + curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) + } + } + + //update environment's defaults.yaml + if args.DefaultValues != nil { + curRenderset.DefaultValues = args.DefaultValues.ValuesYAML + } + + //create new renderset with increased revision + err = commonservice.CreateHelmRenderSet( + curRenderset, + log, + ) + + if err != nil { + return e.ErrCreateRenderSet.AddDesc(err.Error()) + } + return err } func CreateOrUpdateChartValues(productName, envName string, args *commonservice.RenderChartArg, userName, requestID string, log *zap.SugaredLogger) error { @@ -148,7 +262,7 @@ func CreateOrUpdateChartValues(productName, envName string, args *commonservice. serviceOpt := &commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, - Type: setting.HelmDeployType, + Type: setting.HelmDeployType, } serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) if err != nil { @@ -162,11 +276,10 @@ func CreateOrUpdateChartValues(productName, envName string, args *commonservice. return e.ErrCreateRenderSet.AddDesc("missing helm chart info") } - yamlContent, err := generateValuesYaml(args, log) + err = generateValuesYaml(args.YamlData, log) if err != nil { return e.ErrCreateRenderSet.AddDesc(err.Error()) } - args.ValuesYAML = yamlContent renderSetName := commonservice.GetProductEnvNamespace(envName, productName) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 30412152..ed8f9696 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -452,7 +452,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(replaceValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(replaceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/microservice/warpdrive/core/service/types/renderset.go b/pkg/microservice/warpdrive/core/service/types/renderset.go index 0552cad5..dba87417 100644 --- a/pkg/microservice/warpdrive/core/service/types/renderset.go +++ b/pkg/microservice/warpdrive/core/service/types/renderset.go @@ -22,15 +22,16 @@ type RenderSet struct { Name string `bson:"name" json:"name"` Revision int64 `bson:"revision" json:"revision"` // 可以为空,空时为产品模板默认的渲染集,非空时为环境的渲染集 - EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` - ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - KVs []*RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` - ChartInfos []*RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + KVs []*RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` + ChartInfos []*RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` } //// RenderChart ... diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index f786d63c..707219cd 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -49,14 +49,25 @@ type KV struct { // MergeOverrideValues merge values.yaml and override values // overrideYaml used for -f option // overrideValues used for --set option -func MergeOverrideValues(valuesYaml, overrideYaml, overrideValues string) (string, error) { +func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { - if overrideYaml == "" && overrideValues == "" { + if defaultValues == "" && overrideYaml == "" && overrideValues == "" { return valuesYaml, nil } + // merge default.yaml and values.yaml + // values.yaml has high precedence + if defaultValues != "" { + bs, err := yamlutil.Merge([][]byte{[]byte(defaultValues), []byte(valuesYaml)}) + if err != nil { + return "", err + } + valuesYaml = string(bs) + } + // merge files for -f option - values, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(overrideYaml)}) + // overrideYaml has higher precedence + valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(overrideYaml)}) if err != nil { return "", err } @@ -72,13 +83,13 @@ func MergeOverrideValues(valuesYaml, overrideYaml, overrideValues string) (strin kvStr = append(kvStr, fmt.Sprintf("%s=%s", kv.Key, kv.Value)) } // override values for --set option - err = strvals.ParseInto(strings.Join(kvStr, ","), values) + err = strvals.ParseInto(strings.Join(kvStr, ","), valuesMap) if err != nil { return "", err } } - bs, err := yaml.Marshal(values) + bs, err := yaml.Marshal(valuesMap) if err != nil { return "", err } -- Gitee From 9d45c5484ef4de10dd70ff37d5308cb14f7a0ece Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 30 Sep 2021 16:10:38 +0800 Subject: [PATCH 0021/2647] add logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 33 ++++++++++--------- .../core/environment/service/renderset.go | 2 ++ .../core/service/taskplugin/deploy.go | 2 +- pkg/tool/helmclient/helmclient.go | 29 +++++----------- 4 files changed, 29 insertions(+), 37 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 507c1453..ab5e6613 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1032,7 +1032,7 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l } // default values - var defaultValuesYaml string + defaultValuesYaml := "" if args.DefaultValues != nil { err = generateValuesYaml(args.DefaultValues, log) if err != nil { @@ -1095,7 +1095,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str } //对比当前环境中的环境变量和默认的环境变量 go func() { - err := updateProductGroup(productName, envName, updateType, productResp, currentProductService, overrideCharts, log) + err := updateProductGroup(username, productName, envName, updateType, productResp, currentProductService, overrideCharts, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -2574,7 +2574,7 @@ func installOrUpdateHelmChart(user, envName, requestID string, args *commonmodel return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderset.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderset.DefaultValues, renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, @@ -2668,7 +2668,7 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func updateProductGroup(productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func updateProductGroup(username, productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { var ( renderChartMap = make(map[string]*template.RenderChart) productServiceMap = make(map[string]*commonmodels.ProductService) @@ -2715,7 +2715,7 @@ func updateProductGroup(productName, envName, updateType string, productResp *co } //比较当前环境中的变量和系统默认的最新变量 - renderSet, err := diffRenderSet(productName, envName, updateType, productResp, overrideCharts, log) + renderSet, err := diffRenderSet(username, productName, envName, updateType, productResp, overrideCharts, log) if err != nil { return e.ErrUpdateEnv.AddDesc("对比环境中的value.yaml和系统默认的value.yaml失败") } @@ -2766,7 +2766,7 @@ func updateProductGroup(productName, envName, updateType string, productResp *co return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderSet.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderSet.DefaultValues, renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, @@ -2813,7 +2813,7 @@ func updateProductGroup(productName, envName, updateType string, productResp *co } // diffRenderSet 对比环境中的renderSet的值和服务的最新的renderSet的值 -func diffRenderSet(productName, envName, updateType string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { +func diffRenderSet(username, productName, envName, updateType string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { productTemp, err := templaterepo.NewProductColl().Find(productName) if err != nil { log.Errorf("[ProductTmpl.find] err: %v", err) @@ -2840,7 +2840,7 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm } newChartInfos := make([]*template.RenderChart, 0) - renderSetName := productResp.Namespace + defaultValues := "" switch updateType { case UpdateTypeSystem: for _, serviceNameGroup := range productTemp.Services { @@ -2851,12 +2851,13 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm } } case UpdateTypeEnv: - renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: productResp.Render.Revision} + renderSetOpt := &commonrepo.RenderSetFindOption{Name: productResp.Render.Name, Revision: productResp.Render.Revision} currentEnvRenderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err } + defaultValues = currentEnvRenderSet.DefaultValues // 环境里面的变量 currentEnvRenderSetMap := make(map[string]*template.RenderChart) @@ -2903,10 +2904,12 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm if err = commonservice.CreateHelmRenderSet( &commonmodels.RenderSet{ - Name: renderSetName, - EnvName: envName, - ProductTmpl: productName, - ChartInfos: newChartInfos, + Name: productResp.Render.Name, + EnvName: envName, + ProductTmpl: productName, + ChartInfos: newChartInfos, + DefaultValues: defaultValues, + UpdateBy: username, }, log, ); err != nil { @@ -2914,7 +2917,7 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm return nil, err } - renderSet, err := FindHelmRenderSet(productName, renderSetName, log) + renderSet, err := FindHelmRenderSet(productName, productResp.Render.Name, log) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err @@ -3019,7 +3022,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel return } - mergedValuesYaml, err := helmtool.MergeOverrideValues(tmpRenderChart.ValuesYaml, renderset.DefaultValues, tmpRenderChart.GetOverrideYaml(), tmpRenderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderset.DefaultValues, tmpRenderChart.ValuesYaml, tmpRenderChart.GetOverrideYaml(), tmpRenderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 84e8c90c..09f05b0a 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -241,6 +241,8 @@ func CreateOrUpdateRenderset(productName, envName string, args *commonservice.Re //update environment's defaults.yaml if args.DefaultValues != nil { curRenderset.DefaultValues = args.DefaultValues.ValuesYAML + } else { + curRenderset.DefaultValues = "" } //create new renderset with increased revision diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index e27c88d1..97a24e3c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -441,7 +441,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } // merge override values and kvs into service's yaml - mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err = helmtool.MergeOverrideValues(renderInfo.DefaultValues, serviceValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 707219cd..6d482000 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -46,32 +46,19 @@ type KV struct { Value string `json:"value"` } -// MergeOverrideValues merge values.yaml and override values -// overrideYaml used for -f option -// overrideValues used for --set option -func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { +// MergeOverrideValues merge override yaml and override kvs +// override yaml used for -f option +// override kvs used for --set option +func MergeOverrideValues(defaultValues, valuesYaml, overrideYaml, overrideValues string) (string, error) { - if defaultValues == "" && overrideYaml == "" && overrideValues == "" { - return valuesYaml, nil - } - - // merge default.yaml and values.yaml - // values.yaml has high precedence - if defaultValues != "" { - bs, err := yamlutil.Merge([][]byte{[]byte(defaultValues), []byte(valuesYaml)}) - if err != nil { - return "", err - } - valuesYaml = string(bs) - } - - // merge files for -f option - // overrideYaml has higher precedence - valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(overrideYaml)}) + // merge files for helm -f option + // precedence from low to high: defaultValues valuesYaml overrideYaml + valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(defaultValues), []byte(valuesYaml), []byte(overrideYaml)}) if err != nil { return "", err } + // merge kv values for helm --set option if overrideValues != "" { kvList := make([]*KV, 0) err = json.Unmarshal([]byte(overrideValues), &kvList) -- Gitee From baf6cb15552a81daac3fb4d5715ee64a2d8325e5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 30 Sep 2021 20:18:06 +0800 Subject: [PATCH 0022/2647] fix panic bug Signed-off-by: allenshen --- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 97a24e3c..bb0714cf 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -399,6 +399,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } + if targetContainer.ImagePath == nil { + err = errors.Errorf("failded to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + return + } + for _, chartInfo := range renderInfo.ChartInfos { if chartInfo.ServiceName == p.Task.ServiceName { renderChart = chartInfo -- Gitee From 504e618e0f31b901eba3a3d50bafb360fbf98bf3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 30 Sep 2021 20:52:05 +0800 Subject: [PATCH 0023/2647] update render override logic Signed-off-by: allenshen --- .../warpdrive/core/service/taskplugin/deploy.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index bb0714cf..c2a29243 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -530,9 +530,10 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * // TODO too dangerous to override entire renderset! err = p.updateRenderSet(ctx, &types.RenderSet{ - Name: renderInfo.Name, - Revision: renderInfo.Revision, - ChartInfos: renderInfo.ChartInfos, + Name: renderInfo.Name, + Revision: renderInfo.Revision, + DefaultValues: renderInfo.DefaultValues, + ChartInfos: renderInfo.ChartInfos, }) if err != nil { err = errors.WithMessagef( -- Gitee From e5f02f23eb2716582eddd177dc8f2546de7ccc0f Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 8 Oct 2021 10:31:28 +0800 Subject: [PATCH 0024/2647] update image path search logic Signed-off-by: allenshen --- pkg/util/yaml/path_search.go | 8 +- pkg/util/yaml/path_search_lcp.go | 394 ++++++++++++++++++++++++++++++ pkg/util/yaml/path_search_test.go | 327 ++++++++++++++++++++----- 3 files changed, 667 insertions(+), 62 deletions(-) create mode 100644 pkg/util/yaml/path_search_lcp.go diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index 8f09c6b0..480c4d76 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -59,7 +59,7 @@ type pathSearcher struct { resultSet map[string]*pathSearchRuntime //prefix => search result } -func (isr *pathSearcher) handleKV(k string, v interface{}) { +func (isr *pathSearcher) handleKV(k string) { expectPaths, preMatch := isr.pattern, false for _, ep := range expectPaths { if strings.HasSuffix(k, ep) { @@ -95,7 +95,6 @@ func (isr *pathSearcher) handleKV(k string, v interface{}) { pathName: pathName, relativePath: ep, absolutePath: k, - value: v, } match = true } @@ -119,7 +118,6 @@ func (isr *pathSearcher) handleKV(k string, v interface{}) { pathName: pathName, relativePath: ep, absolutePath: k, - value: v, }, }, } @@ -158,9 +156,9 @@ func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) } rtSlice = append(rtSlice, rt) } - for k, v := range sourceMap { + for k := range sourceMap { for _, rt := range rtSlice { - rt.handleKV(k, v) + rt.handleKV(k) } } return rtSlice, nil diff --git a/pkg/util/yaml/path_search_lcp.go b/pkg/util/yaml/path_search_lcp.go new file mode 100644 index 00000000..0417bb8d --- /dev/null +++ b/pkg/util/yaml/path_search_lcp.go @@ -0,0 +1,394 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + "fmt" + "math" + "strings" + + "k8s.io/utils/strings/slices" +) + +const ( + separator = "." +) + +type patternDataMap map[string]map[string]*patternData + +func (pdm patternDataMap) addPatternMap(name, rPath, aPath string) *patternData { + pData := &patternData{ + name: name, + relativePath: rPath, + absolutePath: aPath, + } + if _, ok := pdm[name]; !ok { + pdm[name] = make(map[string]*patternData) + } + pdm[name][aPath] = pData + return pData +} + +func (pdm patternDataMap) getAPathByName(name string) []string { + ret := make([]string, 0) + if pData, ok := pdm[name]; ok { + for _, data := range pData { + ret = append(ret, data.absolutePath) + } + } + return ret +} + +type patternData struct { + name string + relativePath string + absolutePath string + usedPrefix string +} + +type pathResult struct { + name string + absolutePath string +} + +type pathResultSet struct { + resultSet []*pathResult + prefix string +} + +// paths share the same longest prefix +type relativePathSet struct { + paths []string +} + +type relativePathChecker struct { + relativePaths map[string]*relativePathSet +} + +func strSliceContainsAnother(source, target []string) bool { + for _, str := range target { + if !slices.Contains(source, str) { + return false + } + } + return true +} + +// check if the input path set contains relative paths +func (checker *relativePathChecker) checkMatchRelativePath(paths []string) bool { + for _, path := range paths { + if relativePath, ok := checker.relativePaths[path]; ok { + if strSliceContainsAnother(paths, relativePath.paths) { + return true + } else { + return false + } + } + } + return true +} + +func newRelativeChecker(rPaths [][]string) *relativePathChecker { + ret := &relativePathChecker{relativePaths: map[string]*relativePathSet{}} + for _, pathSet := range rPaths { + relativeData := &relativePathSet{ + paths: pathSet, + } + for _, path := range pathSet { + ret.relativePaths[path] = relativeData + } + } + return ret +} + +func longestCommonPrefix(strs []string) string { + n := len(strs) + // 输入为空直接返回空 + if n == 0 { + return "" + // 只输入一个字符串那么它自己就是最长公共后缀 + } else if n == 1 { + return strs[0] + // 如果基准字符串是空,那么直接返回空 + } else if len(strs[0]) == 0 { + return "" + } + // 最长公共前缀再长也不可能比最短的输入字符串还要长对吧。输入源越多,基准字符串不是最短字符串的可能性越大。 + // 因此进行初步优化,求输入源中最短的那个字符串长度,然后把基准字符串按该长度重新截取 + minStrLen := math.MaxInt32 + for i := 0; i < n; i++ { + if minStrLen > len(strs[i]) { + minStrLen = len(strs[i]) + } + } + prefix := strs[0][0:minStrLen] + // 标志位,如果为true说明在其它所有字符串中都找到了相同的前缀 + var allFound bool + for { + allFound = true + // 循环其它字符串 + for i := 1; i < n; i++ { + // 查询是否包含基准字符串,因为求的是前缀,所以index值为0 + if strings.Index(strs[i], prefix) != 0 { + // 不包含的话削掉基准字符串的尾端以便开展下次查询工作 + prefix = prefix[0 : len(prefix)-1] + // 标志位置为false说明查询失败 + allFound = false + // 跳出吧,也没必要再查其它字符串了 + break + } + } + // 如果查询成功,跳出大循环,直接返回当前基准字符串就行了。所以该算法最好的情况就是第一次查询就成功。 + // 如果基准字符串都被砍光了,说明没有最长公共后缀,所以该算法的最差的情况就是最后一个字符串和基准字符串完全没有公共前缀,而中间的字符串都能找到公共前缀,这会导致循环minStrLen * (n - 1)次。 + if allFound || len(prefix) == 0 { + break + } + } + return prefix +} + +func findCombinations(arr [][]string, checker *relativePathChecker) [][]string { + ret := make([][]string, 0) + n := len(arr) + indices := make([]int, n, n) + for { + singleSlice := make([]string, 0) + for i := 0; i < n; i++ { + singleSlice = append(singleSlice, arr[i][indices[i]]) + } + if len(singleSlice) > 0 { + if checker.checkMatchRelativePath(singleSlice) { + ret = append(ret, singleSlice) + } + } + + // find the rightmost array that has more + // elements left after the current element + // in that array + next := n - 1 + for { + if next >= 0 && (indices[next]+1 >= len(arr[next])) { + next-- + } else { + break + } + } + if next < 0 { + break + } + // if found move to next element in that + // array + indices[next] = indices[next] + 1 + + // for all arrays to the right of this + // array current index again points to + // first element + for i := next + 1; i < n; i++ { + indices[i] = 0 + } + } + + return ret +} + +func toPathResult(prefix string, path []string, aPatternData map[string]*patternData) *pathResultSet { + ret := &pathResultSet{ + prefix: prefix, + } + for _, aPath := range path { + pData, _ := aPatternData[aPath] + if len(pData.usedPrefix) <= len(prefix) { + pData.usedPrefix = prefix + } + ret.resultSet = append(ret.resultSet, &pathResult{ + name: pData.name, + absolutePath: aPath, + }) + } + return ret +} + +func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData) []map[string]string { + ret := make([]map[string]string, 0) + for _, singleSet := range resultSets { + retMap := make(map[string]string) + + useLongestPrefix := true + for _, result := range singleSet.resultSet { + pData := aPatternData[result.absolutePath] + if len(pData.usedPrefix) != len(singleSet.prefix) { + useLongestPrefix = false + break + } + } + if !useLongestPrefix { + continue + } + + for _, result := range singleSet.resultSet { + retMap[result.name] = result.absolutePath + } + ret = append(ret, retMap) + } + return ret +} + +func (isr *pathSearcher) checkRelativePath(k string) { + expectPaths, preMatch := isr.pattern, false + for _, ep := range expectPaths { + if strings.HasSuffix(k, ep) { + preMatch = true + break + } + } + if !preMatch { + return + } + + //优先匹配当前已经match了一部分path的情形 + for prefix, fs := range isr.resultSet { + if fs.checkAllFinish() { + continue + } + if !strings.HasPrefix(k, prefix) { + continue + } + match := false + for pathName, ep := range expectPaths { + //已经找到了相关的实际path pass + if _, ok := fs.foundSet[ep]; ok { + continue + } else { + if !strings.HasSuffix(k, ep) { + continue + } + if getKey(prefix, ep) != k { + break + } + fs.foundSet[ep] = &singlePathSearchInfo{ + pathName: pathName, + relativePath: ep, + absolutePath: k, + } + match = true + } + } + if match { + return + } + } + + for pathName, ep := range expectPaths { + if !strings.HasSuffix(k, ep) { + continue + } + prefix := strings.TrimSuffix(k, ep) + prefix = strings.Trim(prefix, ".") + fs := &pathSearchRuntime{ + pattern: expectPaths, + prefix: prefix, + foundSet: map[string]*singlePathSearchInfo{ + ep: &singlePathSearchInfo{ + pathName: pathName, + relativePath: ep, + absolutePath: k, + }, + }, + } + isr.resultSet[prefix] = fs + return + } +} + +func (isr *pathSearcher) getRelativePath() [][]string { + ret := make([][]string, 0) + for _, fSet := range isr.resultSet { + // multi keys share the same prefix + if len(fSet.foundSet) <= 1 { + continue + } + set := make([]string, 0) + for _, searchInfo := range fSet.foundSet { + set = append(set, searchInfo.absolutePath) + } + ret = append(ret, set) + } + return ret +} + +func searchByLCP(flatMap map[string]interface{}, pattern map[string]string) ([]map[string]string, error) { + + patternDataMap := patternDataMap{} + allPatternData := make(map[string]*patternData) // absolutePath => patternData + relativeSearcher := &pathSearcher{ + pattern: pattern, + resultSet: make(map[string]*pathSearchRuntime), + } + + for absolutePath := range flatMap { + for patternName, relativePath := range pattern { + if relativePath != absolutePath && !strings.HasSuffix(absolutePath, fmt.Sprintf(".%s", relativePath)) { + continue + } + pData := patternDataMap.addPatternMap(patternName, relativePath, absolutePath) + allPatternData[absolutePath] = pData + } + relativeSearcher.checkRelativePath(absolutePath) + } + + relativePaths := relativeSearcher.getRelativePath() + fmt.Printf("the relative paths is %v \n", relativePaths) + checker := newRelativeChecker(relativePaths) + + allKeys := make([][]string, 0) + for name, _ := range patternDataMap { + pathList := patternDataMap.getAPathByName(name) + if len(pathList) == 0 { + return nil, nil + } + allKeys = append(allKeys, pathList) + } + + allCombinations := findCombinations(allKeys, checker) + + results := make([]*pathResultSet, 0) + + for _, cb := range allCombinations { + lp := longestCommonPrefix(cb) + result := toPathResult(lp, cb, allPatternData) + results = append(results, result) + } + + return processResults(results, allPatternData), nil +} + +func SearchByPatternLCP(flatMap map[string]interface{}, patterns []map[string]string) ([]map[string]string, error) { + err := preFilterSearchPaths(patterns) + if err != nil { + return nil, err + } + + ret := make([]map[string]string, 0) + for _, singlePattern := range patterns { + pRet, err := searchByLCP(flatMap, singlePattern) + if err != nil { + return nil, err + } + ret = append(ret, pRet...) + } + return ret, nil +} diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index bdf48b38..2b1fbd17 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -115,81 +115,294 @@ fullnameOverride: "" service: type: ClusterIP port: 8080 +` + +var testYaml6 = ` +env: dev + +ingressClassName: koderover-admin-nginx + +global: + hub: ccr.ccs.tencentyun.com/trial + tag: latest + +testSpec: + imageNew: + image: go-sample-site-new + pullPolicy: IfNotPresent + +svc1: + image: svc1-image + +svc2: + image: svc2-image + +svc3: + image: svc3-image + +imagePullSecrets: + - name: default-registry-secret + +nameOverride: "" +fullnameOverride: "" + +service: + type: ClusterIP + port: 8080 +` + +var testYaml7 = ` +env: dev + +ingressClassName: koderover-admin-nginx + +repoData1: + global: + hub: ccr.ccs.tencentyun.com/trial + tag: latest + +testSpec: + imageNew: + image: go-sample-site-new + pullPolicy: IfNotPresent + +svc1: + image: svc1-image + +svc2: + image: svc2-image + +imagePullSecrets: + - name: default-registry-secret + +nameOverride: "" +fullnameOverride: "" + +service: + type: ClusterIP + port: 8080 +` + +var testYaml8 = ` +env: dev + +ingressClassName: koderover-admin-nginx + +repoData1: + global: + hub: ccr.ccs.tencentyun.com/trial + +testSpec: + image: + image: go-sample-site-new + pullPolicy: IfNotPresent + tag: 0.1.0 +svc1: + image: + image: svc1-image + tag: 0.2.0 + +svc2: + image: + image: svc2-image + tag: 0.3.0 + +imagePullSecrets: + - name: default-registry-secret + +nameOverride: "" +fullnameOverride: "" + +service: + type: ClusterIP + port: 8080 +` + +var testYaml9 = ` +env: dev + +ingressClassName: koderover-admin-nginx + +repoData1: + global: + hub: ccr.ccs.tencentyun.com/trial + +svc1: + image: + service: + image: service-image + tagInfo: + tag: 0.1.0 + repo: + ccr.ccs.tencentyun.com/trial + +svc2: + image: + service: + image: service-image-2 + tagInfo: + tag: 0.2.0 + repo: + ccr.ccs.tencentyun.com/trial + +imagePullSecrets: + - name: default-registry-secret + +nameOverride: "" +fullnameOverride: "" + +service: + type: ClusterIP + port: 8080 ` var err error var matedPaths []map[string]string +var lcpErr error +var lcpMatedPaths []map[string]string var _ = Describe("Testing search", func() { Context("search matched paths from yaml", func() { - It("single match", func() { - pattern := []map[string]string{ - {"image": "repository", "tag": "tag"}, - } - flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) - matedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(matedPaths).To(Equal([]map[string]string{{"image": "image.repository", "tag": "image.tag"}})) - }) + //It("single match", func() { + // pattern := []map[string]string{ + // {"image": "repository", "tag": "tag"}, + // } + // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) + // matedPaths, err = SearchByPattern(flatMap, pattern) + // Expect(err).NotTo(HaveOccurred()) + // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + // Expect(lcpErr).NotTo(HaveOccurred()) + // Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + // Expect(matedPaths).To(Equal([]map[string]string{{"image": "image.repository", "tag": "image.tag"}})) + //}) + // + //It("multiple match", func() { + // pattern := []map[string]string{ + // {"image": "repository", "tag": "tag"}, + // } + // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) + // matedPaths, err = SearchByPattern(flatMap, pattern) + // Expect(err).NotTo(HaveOccurred()) + // + // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + // Expect(lcpErr).NotTo(HaveOccurred()) + // Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + // + // Expect(matedPaths).Should(ConsistOf([]map[string]string{ + // {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, + // {"image": "svc2.image.repository", "tag": "svc2.image.tag"}, + // })) + //}) + // + //It("multiple match pattern 3", func() { + // pattern := []map[string]string{ + // {"image": "repository"}, + // } + // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml3)) + // matedPaths, err = SearchByPattern(flatMap, pattern) + // Expect(err).NotTo(HaveOccurred()) + // + // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + // Expect(lcpErr).NotTo(HaveOccurred()) + // Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + // + // Expect(matedPaths).Should(ConsistOf([]map[string]string{ + // {"image": "svc1.image.repository"}, + // {"image": "svc2.image.repository"}, + // {"image": "svc3.image.repository"}, + // })) + //}) + // + //It("multiple match pattern complex", func() { + // pattern := []map[string]string{ + // {"image": "repository", "tag": "tag"}, + // {"image": "repository"}, + // {"image": "repositoryNew", "tag": "tagNew"}, + // {"image": "image.repositorySpec", "tag": "tagNew"}, + // } + // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml4)) + // matedPaths, err = SearchByPattern(flatMap, pattern) + // Expect(err).NotTo(HaveOccurred()) + // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + // Expect(lcpErr).NotTo(HaveOccurred()) + // Expect(matedPaths).Should(ConsistOf([]map[string]string{ + // {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, + // {"image": "svc2.image.repository"}, + // {"image": "svc3.image.repository"}, + // {"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}, + // {"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}, + // })) + // + // Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + // {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, + // {"image": "svc1.image.repository"}, + // {"image": "svc2.image.repository"}, + // {"image": "svc3.image.repository"}, + // {"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}, + // {"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}, + // })) + // + //}) + // + //It("multiple match pattern complex2", func() { + // pattern := []map[string]string{ + // {"image": "image", "tag": "global.tag", "repo": "global.hub"}, + // } + // flatMap, err := converter.YamlToFlatMap([]byte(testYaml6)) + // matedPaths, err = SearchByPattern(flatMap, pattern) + // Expect(err).NotTo(HaveOccurred()) + // + // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + // Expect(lcpErr).NotTo(HaveOccurred()) + // + // Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + // {"image": "testSpec.imageNew.image", "repo": "global.hub", "tag": "global.tag"}, + // {"image": "svc1.image", "repo": "global.hub", "tag": "global.tag"}, + // {"image": "svc2.image", "repo": "global.hub", "tag": "global.tag"}, + // {"image": "svc3.image", "repo": "global.hub", "tag": "global.tag"}, + // })) + //}) + // + //It("multiple match pattern complex 3", func() { + // pattern := []map[string]string{ + // {"image": "image", "tag": "global.tag", "repo": "global.hub"}, + // } + // flatMap, err := converter.YamlToFlatMap([]byte(testYaml7)) + // matedPaths, err = SearchByPattern(flatMap, pattern) + // Expect(err).NotTo(HaveOccurred()) + // + // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + // Expect(lcpErr).NotTo(HaveOccurred()) + // + // Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + // {"image": "testSpec.imageNew.image", "repo": "repoData1.global.hub", "tag": "repoData1.global.tag"}, + // {"image": "svc1.image", "repo": "repoData1.global.hub", "tag": "repoData1.global.tag"}, + // {"image": "svc2.image", "repo": "repoData1.global.hub", "tag": "repoData1.global.tag"}, + // })) + //}) - It("multiple match", func() { + It("multiple match pattern complex 4", func() { pattern := []map[string]string{ - {"image": "repository", "tag": "tag"}, + {"image": "image", "tag": "tag", "repo": "repo"}, } - flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml9)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - Expect(matedPaths).Should(ConsistOf([]map[string]string{ - {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, - {"image": "svc2.image.repository", "tag": "svc2.image.tag"}, - })) - }) + lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + Expect(lcpErr).NotTo(HaveOccurred()) - It("multiple match pattern 3", func() { - pattern := []map[string]string{ - {"image": "repository"}, - } - flatMap, _ := converter.YamlToFlatMap([]byte(testYaml3)) - matedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(matedPaths).Should(ConsistOf([]map[string]string{ - {"image": "svc1.image.repository"}, - {"image": "svc2.image.repository"}, - {"image": "svc3.image.repository"}, - })) - }) - - It("multiple match pattern complex", func() { - pattern := []map[string]string{ - {"image": "repository", "tag": "tag"}, - {"image": "repository"}, - {"image": "repositoryNew", "tag": "tagNew"}, - {"image": "image.repositorySpec", "tag": "tagNew"}, - } - flatMap, _ := converter.YamlToFlatMap([]byte(testYaml4)) - matedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(matedPaths).Should(ConsistOf([]map[string]string{ - {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, - {"image": "svc2.image.repository"}, - {"image": "svc3.image.repository"}, - {"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}, - {"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}, + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "testSpec.image.image", "repo": "repoData1.global.hub", "tag": "testSpec.image.tag"}, + {"image": "svc1.image.image", "repo": "repoData1.global.hub", "tag": "svc1.image.tag"}, + {"image": "svc2.image.image", "repo": "repoData1.global.hub", "tag": "svc2.image.tag"}, })) + //Expect(lcpMatedPaths).Should(ContainElement( + // ConsistOf(map[string]string{"image": "testSpec.image.image", "repo": "repoData1.global.hub", "tag": "repoData1.image.tag"}), + //)) }) - It("multiple match pattern complex2", func() { - pattern := []map[string]string{ - {"image": "imageNew.repo", "tag": "imageNew.tag"}, - } - flatMap, _ := converter.YamlToFlatMap([]byte(testYaml5)) - matedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(len(matedPaths)).To(Equal(1)) - Expect(matedPaths).To(Equal([]map[string]string{{"image": "testSpec.imageNew.repo", "tag": "testSpec.imageNew.tag"}})) - }) }) }) -- Gitee From 30620816fc4a4a4afafaa73da070b2ab2a64cabf Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 8 Oct 2021 11:34:31 +0800 Subject: [PATCH 0025/2647] remove useless code Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/router.go | 8 ++------ .../aslan/core/environment/service/environment.go | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 782d2f56..a469766a 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -109,8 +109,6 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.PUT("/:productName/envRecycle", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) environments.PUT("/:productName/renderset", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) - //TODO need to be deprecated - environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) environments.PUT("/:productName", gin2.IsHavePermission([]string{permission.TestEnvShareUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, EnvShare) @@ -134,10 +132,8 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) - // need to be deprecated - //environments.PUT("/:productName/helmEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProduct) - //environments.PUT("/:productName/helmEnvVariable", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductVariable) - //environments.PUT("/:productName/updateMultiEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateMultiHelmProduct) + //TODO need to be deprecated + environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 52d68f9e..65fa38a4 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2895,7 +2895,6 @@ func diffRenderSet(username, productName, envName, updateType string, productRes for serviceName, latestChartInfo := range tmpLatestChartInfoMap { if currentChartInfo, ok := tmpCurrentChartInfoMap[serviceName]; ok { //拿当前环境values.yaml的key的value去替换服务里面的values.yaml的相同的key的value - //TODO values.yaml should be the same in both currentChartInfo and latestChartInfo, since values.yaml can't be edited directly newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml)) if err != nil { log.Errorf("Failed to override values, err: %s", err) -- Gitee From 945cc311d9c48a186869c5a555b75eeff3c11227 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 13 Oct 2021 15:12:33 +0800 Subject: [PATCH 0026/2647] add list projects in picket Signed-off-by: lou --- .../picket/client/aslan/client.go | 41 +++++++++++++ .../picket/client/aslan/project.go | 19 ++++++ .../picket/client/opa/evaluate.go | 17 ++++-- pkg/microservice/picket/client/opa/input.go | 23 +++++++ .../core/evaluation/service/evaluate.go | 60 ++++++------------ .../picket/core/filter/handler/project.go | 15 +++++ .../picket/core/filter/handler/router.go | 30 +++++++++ .../picket/core/filter/service/project.go | 61 +++++++++++++++++++ pkg/microservice/picket/server/rest/router.go | 2 + .../core/service/bundle/exemption_urls.go | 4 ++ .../bundle/resource_endpoint_mapping.go | 4 ++ pkg/tool/httpclient/request_options.go | 7 +++ 12 files changed, 235 insertions(+), 48 deletions(-) create mode 100644 pkg/microservice/picket/client/aslan/client.go create mode 100644 pkg/microservice/picket/client/aslan/project.go create mode 100644 pkg/microservice/picket/client/opa/input.go create mode 100644 pkg/microservice/picket/core/filter/handler/project.go create mode 100644 pkg/microservice/picket/core/filter/handler/router.go create mode 100644 pkg/microservice/picket/core/filter/service/project.go diff --git a/pkg/microservice/picket/client/aslan/client.go b/pkg/microservice/picket/client/aslan/client.go new file mode 100644 index 00000000..42302493 --- /dev/null +++ b/pkg/microservice/picket/client/aslan/client.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aslan + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.AslanServiceAddress() + + c := httpclient.New( + httpclient.SetHostURL(host + "/api"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go new file mode 100644 index 00000000..80e67757 --- /dev/null +++ b/pkg/microservice/picket/client/aslan/project.go @@ -0,0 +1,19 @@ +package aslan + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) ListProjects(header http.Header, qs url.Values) ([]byte, error) { + url := "/project/projects" + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/client/opa/evaluate.go b/pkg/microservice/picket/client/opa/evaluate.go index f5920ae6..d0fb31f9 100644 --- a/pkg/microservice/picket/client/opa/evaluate.go +++ b/pkg/microservice/picket/client/opa/evaluate.go @@ -7,20 +7,27 @@ import ( "github.com/koderover/zadig/pkg/tool/httpclient" ) +type InputGenerator func() (*Input, error) + // Evaluate evaluates the query with the given input and return a json response which has a field called "result" -func (c *Client) Evaluate(query string, input interface{}) ([]byte, error) { +func (c *Client) Evaluate(query string, result interface{}, ig InputGenerator) error { + input, err := ig() + if err != nil { + return err + } + url := "v1/data" - req := struct{ + req := struct { Input interface{} `json:"input"` }{ Input: input, } queryURL := fmt.Sprintf("%s/%s", url, strings.ReplaceAll(query, ".", "/")) - res, err := c.Post(queryURL, httpclient.SetBody(req)) + _, err = c.Post(queryURL, httpclient.SetBody(req), httpclient.SetResult(result)) if err != nil { - return nil, err + return err } - return res.Body(), nil + return nil } diff --git a/pkg/microservice/picket/client/opa/input.go b/pkg/microservice/picket/client/opa/input.go new file mode 100644 index 00000000..df28516d --- /dev/null +++ b/pkg/microservice/picket/client/opa/input.go @@ -0,0 +1,23 @@ +package opa + +type Input struct { + ParsedQuery *ParseQuery `json:"parsed_query"` + ParsedPath []string `json:"parsed_path"` + Attributes *Attributes `json:"attributes"` +} +type ParseQuery struct { + ProjectName []string `json:"projectName"` +} + +type Attributes struct { + Request *Request `json:"request"` +} + +type Request struct { + HTTP *HTTPSpec `json:"http"` +} + +type HTTPSpec struct { + Method string `json:"method"` + Headers map[string]string `json:"headers"` +} diff --git a/pkg/microservice/picket/core/evaluation/service/evaluate.go b/pkg/microservice/picket/core/evaluation/service/evaluate.go index 4de488cb..867e059e 100644 --- a/pkg/microservice/picket/core/evaluation/service/evaluate.go +++ b/pkg/microservice/picket/core/evaluation/service/evaluate.go @@ -17,66 +17,36 @@ limitations under the License. package service import ( - "encoding/json" "net/http" "strings" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/picket/client/opa" + "github.com/koderover/zadig/pkg/setting" ) -type input struct { - ParsedQuery parseQuery `json:"parsed_query"` - ParsedPath []string `json:"parsed_path"` - Attributes attributes `json:"attributes"` -} -type parseQuery struct { - ProjectName []string `json:"projectName"` -} - -type attributes struct { - Request request `json:"request"` -} - -type request struct { - Http HTTP `json:"http"` -} - -type HTTP struct { - Method string `json:"method"` - Headers map[string]string `json:"headers"` -} - -type opaRes struct { +type evaluateResult struct { Result bool `json:"result"` } func Evaluate(header http.Header, projectName string, grantsReq []GrantReq, logger *zap.SugaredLogger) ([]GrantRes, error) { - // 拼接参数,请求opa - authorization := header.Get("authorization") - opaHeaders := map[string]string{} - opaHeaders["authorization"] = authorization var grantsRes []GrantRes opaClient := opa.NewDefault() // 对于每一个action+endpoint 都去请求opa for _, v := range grantsReq { parsedPath := strings.Split(strings.Trim(v.EndPoint, "/"), "/") - input := generateOPAInput(projectName, parsedPath, v.Method, opaHeaders) - res, err := opaClient.Evaluate("rbac.allow", input) + res := &evaluateResult{} + err := opaClient.Evaluate( + "rbac.allow", res, + func() (*opa.Input, error) { return generateOPAInput(header, projectName, v.Method, parsedPath), nil }) if err != nil { logger.Errorf("opa evaluate endpoint: %v method: %v err: %s", v.EndPoint, v.Method, err) grantsRes = append(grantsRes, GrantRes{v, false}) continue } - var opaR opaRes - if err := json.Unmarshal(res, &opaR); err != nil { - logger.Errorf("opa res Unmarshal err %s", err) - grantsRes = append(grantsRes, GrantRes{v, false}) - continue - } - grantsRes = append(grantsRes, GrantRes{v, opaR.Result}) + grantsRes = append(grantsRes, GrantRes{v, res.Result}) } return grantsRes, nil } @@ -91,16 +61,20 @@ type GrantRes struct { Allow bool `json:"allow"` } -func generateOPAInput(projectName string, parsedPath []string, method string, head map[string]string) input { - return input{ - ParsedQuery: parseQuery{ +func generateOPAInput(header http.Header, projectName, method string, parsedPath []string) *opa.Input { + authorization := header.Get(strings.ToLower(setting.AuthorizationHeader)) + headers := map[string]string{} + headers[strings.ToLower(setting.AuthorizationHeader)] = authorization + + return &opa.Input{ + ParsedQuery: &opa.ParseQuery{ ProjectName: []string{projectName}, }, ParsedPath: parsedPath, - Attributes: attributes{ - Request: request{Http: HTTP{ + Attributes: &opa.Attributes{ + Request: &opa.Request{HTTP: &opa.HTTPSpec{ Method: method, - Headers: head, + Headers: headers, }}, }, } diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go new file mode 100644 index 00000000..a4f38ab7 --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListProjects(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListProjects(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go new file mode 100644 index 00000000..715ca28a --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -0,0 +1,30 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + roles := router.Group("projects") + { + roles.GET("", ListProjects) + } +} diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go new file mode 100644 index 00000000..e267487e --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -0,0 +1,61 @@ +package service + +import ( + "net/http" + "net/url" + "strings" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + "github.com/koderover/zadig/pkg/microservice/picket/client/opa" + "github.com/koderover/zadig/pkg/setting" +) + +type allowedProjectsData struct { + Result []string `json:"result"` +} + +func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { + names, err := getAllowedProjects(header, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + + for _, name := range names { + qs.Add("names", name) + } + + aslanClient := aslan.New() + + return aslanClient.ListProjects(header, qs) +} + +func getAllowedProjects(headers http.Header, logger *zap.SugaredLogger) ([]string, error) { + res := &allowedProjectsData{} + + opaClient := opa.NewDefault() + err := opaClient.Evaluate("rbac.user_projects", res, func() (*opa.Input, error) { return generateOPAInput(headers), nil }) + if err != nil { + logger.Errorf("opa evaluation failed, err: %s", err) + return nil, err + } + + return res.Result, nil + +} + +func generateOPAInput(header http.Header) *opa.Input { + authorization := header.Get(strings.ToLower(setting.AuthorizationHeader)) + headers := map[string]string{} + headers[strings.ToLower(setting.AuthorizationHeader)] = authorization + + return &opa.Input{ + Attributes: &opa.Attributes{ + Request: &opa.Request{HTTP: &opa.HTTPSpec{ + Headers: headers, + }}, + }, + } +} diff --git a/pkg/microservice/picket/server/rest/router.go b/pkg/microservice/picket/server/rest/router.go index 6eece402..3253c5d4 100644 --- a/pkg/microservice/picket/server/rest/router.go +++ b/pkg/microservice/picket/server/rest/router.go @@ -20,11 +20,13 @@ import ( "github.com/gin-gonic/gin" evaluationhandler "github.com/koderover/zadig/pkg/microservice/picket/core/evaluation/handler" + filterhandler "github.com/koderover/zadig/pkg/microservice/picket/core/filter/handler" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { for _, r := range []injector{ new(evaluationhandler.Router), + new(filterhandler.Router), } { r.Inject(router.Group("/api/v1")) } diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 62d041a4..e3d7b0ad 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -1,5 +1,9 @@ package bundle +// TODO: Policy Service should not care about the policy details of a certain service, instead, a service which wants +// to be managed by the Policy Service should register its rules to the Policy Service so that the Policy Service knows +// how to make a decision without being aware of the detailed rules. + type exemptionURLs struct { Global rules `json:"global"` // global urls are only controlled by AuthN, and it is visible for all users Namespaced rules `json:"namespaced"` // global urls are only controlled by AuthN, and it is visible for users under certain projects diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index 2b328dd4..02eee398 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -1,5 +1,9 @@ package bundle +// TODO: Policy Service should not care about the policy details of a certain service, instead, a service which wants +// to be managed by the Policy Service should register its rules to the Policy Service so that the Policy Service knows +// how to make a decision without being aware of the detailed rules. + var mappings = map[string]map[string][]*rule{ "Workflow": workflowMapping, } diff --git a/pkg/tool/httpclient/request_options.go b/pkg/tool/httpclient/request_options.go index dde8bfd6..afc19279 100644 --- a/pkg/tool/httpclient/request_options.go +++ b/pkg/tool/httpclient/request_options.go @@ -17,6 +17,7 @@ limitations under the License. package httpclient import ( + "net/http" "net/url" "github.com/go-resty/resty/v2" @@ -42,6 +43,12 @@ func SetHeader(header, value string) RequestFunc { } } +func SetHeadersFromHTTPHeader(header http.Header) RequestFunc { + return func(r *resty.Request) { + r.Header = header + } +} + func SetQueryParamsFromValues(params url.Values) RequestFunc { return func(r *resty.Request) { r.SetQueryParamsFromValues(params) -- Gitee From 6fa5f22b279c012ad48998025b07643ee5e1ffa2 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:04:05 +0800 Subject: [PATCH 0027/2647] authv2 list project (#373) * list project Signed-off-by: mouuii * list project Signed-off-by: mouuii * list project Signed-off-by: mouuii * list project Signed-off-by: mouuii * code review Signed-off-by: mouuii Co-authored-by: mouuii --- .../repository/mongodb/template/product.go | 36 +++------ .../aslan/core/project/handler/project.go | 8 +- .../aslan/core/project/service/project.go | 75 +++++++++++++------ 3 files changed, 70 insertions(+), 49 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 9e330693..a9e7cd8f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -71,47 +71,35 @@ func (c *ProductColl) FindProjectName(project string) (*template.Product, error) return resp, err } -func (c *ProductColl) List() ([]*template.Product, error) { - var resp []*template.Product - - cursor, err := c.Collection.Find(context.TODO(), bson.M{}) +func (c *ProductColl) ListProjectsByNames(projects []string) ([]*template.Product, error) { + resp := make([]*template.Product, 0) + ctx := context.TODO() + query := bson.M{"project_name": bson.M{"$in": projects}} + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } - err = cursor.All(context.TODO(), &resp) + + err = cursor.All(ctx, &resp) if err != nil { return nil, err } - return resp, nil } -func (c *ProductColl) ListNames() ([]string, error) { - var res []struct { - ProductName string `bson:"product_name"` - } - - opts := options.Find() - projection := bson.D{ - {"product_name", 1}, - } - opts.SetProjection(projection) +func (c *ProductColl) List() ([]*template.Product, error) { + var resp []*template.Product - cursor, err := c.Collection.Find(context.TODO(), bson.M{}, opts) + cursor, err := c.Collection.Find(context.TODO(), bson.M{}) if err != nil { return nil, err } - err = cursor.All(context.TODO(), &res) + err = cursor.All(context.TODO(), &resp) if err != nil { return nil, err } - var names []string - for _, r := range res { - names = append(names, r.ProductName) - } - - return names, nil + return resp, nil } type ProductListOpt struct { diff --git a/pkg/microservice/aslan/core/project/handler/project.go b/pkg/microservice/aslan/core/project/handler/project.go index 32bb3949..d3a3769f 100644 --- a/pkg/microservice/aslan/core/project/handler/project.go +++ b/pkg/microservice/aslan/core/project/handler/project.go @@ -25,8 +25,9 @@ import ( ) type projectListArgs struct { - IgnoreNoEnvs bool `json:"ignoreNoEnvs" form:"ignoreNoEnvs"` - Verbosity string `json:"verbosity" form:"verbosity"` + IgnoreNoEnvs bool `json:"ignoreNoEnvs" form:"ignoreNoEnvs"` + Verbosity string `json:"verbosity" form:"verbosity"` + Names []string `json:"names" form:"names"` } func ListProjects(c *gin.Context) { @@ -38,9 +39,8 @@ func ListProjects(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Resp, ctx.Err = projectservice.ListProjects( - &projectservice.ProjectListOptions{IgnoreNoEnvs: args.IgnoreNoEnvs, Verbosity: projectservice.QueryVerbosity(args.Verbosity)}, + &projectservice.ProjectListOptions{IgnoreNoEnvs: args.IgnoreNoEnvs, Verbosity: projectservice.QueryVerbosity(args.Verbosity), Projects: args.Names}, ctx.Logger, ) } diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 7da39d95..0d8e5113 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -17,13 +17,11 @@ limitations under the License. package service import ( - "fmt" - "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" ) type QueryVerbosity string @@ -37,10 +35,13 @@ const ( type ProjectListOptions struct { IgnoreNoEnvs bool Verbosity QueryVerbosity + Projects []string } type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation + Alias string + Desc string } type ProjectBriefRepresentation struct { @@ -55,7 +56,7 @@ type ProjectMinimalRepresentation struct { func ListProjects(opts *ProjectListOptions, logger *zap.SugaredLogger) (interface{}, error) { switch opts.Verbosity { case VerbosityDetailed: - return nil, fmt.Errorf("not Implemented") + return listDetailedProjectInfos(opts, logger) case VerbosityBrief: return listBriefProjectInfos(opts, logger) case VerbosityMinimal: @@ -65,15 +66,55 @@ func ListProjects(opts *ProjectListOptions, logger *zap.SugaredLogger) (interfac } } -func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectBriefRepresentation, error) { - var res []*ProjectBriefRepresentation +func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) (res []*ProjectDetailedRepresentation, err error) { + nameWithEnvs, err := mongodb.NewProductColl().ListProjects() + if err != nil { + logger.Errorf("Failed to list projects, err: %s", err) + return nil, err + } - names, err := templaterepo.NewProductColl().ListNames() + nameSet := sets.NewString() + for _, name := range opts.Projects { + nameSet.Insert(name) + } + + nameWithEnvsSet := sets.NewString() + for _, nameWithEnv := range nameWithEnvs { + // nameWithEnvs may contain projects which are already deleted. + if !nameSet.Has(nameWithEnv.ProjectName) { + continue + } + res = append(res, &ProjectDetailedRepresentation{ + ProjectBriefRepresentation: &ProjectBriefRepresentation{ + ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: nameWithEnv.ProjectName}, + Envs: nameWithEnv.Envs, + }, + }) + nameWithEnvsSet.Insert(nameWithEnv.ProjectName) + } + + projects, err := template.NewProductColl().ListProjectsByNames(opts.Projects) if err != nil { - logger.Errorf("Failed to list project names, err: %s", err) return nil, err } + if !opts.IgnoreNoEnvs { + for _, project := range projects { + if !nameWithEnvsSet.Has(project.ProjectName) { + res = append(res, &ProjectDetailedRepresentation{ + ProjectBriefRepresentation: &ProjectBriefRepresentation{ + ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: project.ProjectName}, + }, + Alias: project.ProductName, + Desc: project.Description, + }) + } + } + } + return res, nil +} + +func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) (res []*ProjectBriefRepresentation, err error) { nameWithEnvs, err := mongodb.NewProductColl().ListProjects() if err != nil { logger.Errorf("Failed to list projects, err: %s", err) @@ -81,7 +122,7 @@ func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) } nameSet := sets.NewString() - for _, name := range names { + for _, name := range opts.Projects { nameSet.Insert(name) } @@ -99,7 +140,7 @@ func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) } if !opts.IgnoreNoEnvs { - for _, name := range names { + for _, name := range opts.Projects { if !nameWithEnvsSet.Has(name) { res = append(res, &ProjectBriefRepresentation{ ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, @@ -111,17 +152,9 @@ func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) return res, nil } -func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { - var res []*ProjectMinimalRepresentation - - names, err := templaterepo.NewProductColl().ListNames() - if err != nil { - logger.Errorf("Failed to list project names, err: %s", err) - return nil, err - } - +func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) (res []*ProjectMinimalRepresentation, err error) { if !opts.IgnoreNoEnvs { - for _, name := range names { + for _, name := range opts.Projects { res = append(res, &ProjectMinimalRepresentation{Name: name}) } @@ -129,7 +162,7 @@ func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger } nameSet := sets.NewString() - for _, name := range names { + for _, name := range opts.Projects { nameSet.Insert(name) } -- Gitee From 99270e46401a823b4b149dffc6657be08f68dd0d Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 13 Oct 2021 18:25:23 +0800 Subject: [PATCH 0028/2647] fix policy (#378) * fix policy Signed-off-by: lou * add more comments Signed-off-by: lou * update user_projects Signed-off-by: lou * typo Signed-off-by: lou --- .../policy/core/service/bundle/opa_bundle.go | 37 +++++++-- .../core/service/bundle/rego/authz.rego | 48 ++++++++---- .../bundle/rego/test_data/bindings/data.json | 76 ++++++++++++++++--- 3 files changed, 129 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index ca22f71f..bdaed41e 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -88,7 +88,12 @@ type rule struct { type roleBinding struct { User string `json:"user"` - RoleRefs roleRefs `json:"role_refs"` + Bindings bindings `json:"bindings"` +} + +type binding struct { + Namespace string `json:"namespace"` + RoleRefs roleRefs `json:"role_refs"` } type roleRef struct { @@ -164,6 +169,14 @@ func (o roles) Less(i, j int) bool { return o[i].Namespace < o[j].Namespace } +type bindings []*binding + +func (o bindings) Len() int { return len(o) } +func (o bindings) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o bindings) Less(i, j int) bool { + return o[i].Namespace < o[j].Namespace +} + type roleRefs []*roleRef func (o roleRefs) Len() int { return len(o) } @@ -256,22 +269,30 @@ func generateOPARoles(roles []*models.Role) *opaRoles { return data } -func generateOPARoleBindings(bindings []*models.RoleBinding) *opaRoleBindings { +func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { data := &opaRoleBindings{} - userRoleMap := make(map[string][]*roleRef) + userRoleMap := make(map[string]map[string][]*roleRef) - for _, rb := range bindings { + for _, rb := range rbs { for _, s := range rb.Subjects { if s.Kind == models.UserKind { - userRoleMap[s.Name] = append(userRoleMap[s.Name], &roleRef{Name: rb.RoleRef.Name, Namespace: rb.RoleRef.Namespace}) + if _, ok := userRoleMap[s.Name]; !ok { + userRoleMap[s.Name] = make(map[string][]*roleRef) + } + userRoleMap[s.Name][rb.Namespace] = append(userRoleMap[s.Name][rb.Namespace], &roleRef{Name: rb.RoleRef.Name, Namespace: rb.RoleRef.Namespace}) } } } - for k, v := range userRoleMap { - sort.Sort(roleRefs(v)) - data.RoleBindings = append(data.RoleBindings, &roleBinding{User: k, RoleRefs: v}) + for u, nb := range userRoleMap { + var bindingsData []*binding + for n, b := range nb { + sort.Sort(roleRefs(b)) + bindingsData = append(bindingsData, &binding{Namespace: n, RoleRefs: b}) + } + sort.Sort(bindings(bindingsData)) + data.RoleBindings = append(data.RoleBindings, &roleBinding{User: u, Bindings: bindingsData}) } sort.Sort(data.RoleBindings) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index b3119d41..6f58574d 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -2,6 +2,14 @@ package rbac import input.attributes.request.http as http_request +# Policy rule definitions in rbac style which is consumed by OPA server. +# you can use it to: +# 1. decide if a request is allowed by querying: rbac.allow +# 2. get all accessible proejcts for an authenticated user by querying: rbac.user_projects +# 3. check if a user is system admin by querying: rbac.user_is_admin +# 4. check if a user is project admin by querying: rbac.user_is_project_admin + + # By default, deny requests. default allow = false @@ -71,22 +79,40 @@ projcet_name := pn { pn := input.parsed_query.projectName[0] } -roles[role] { +# get all projects which are visible by current user +user_projects[project] { some i data.bindings.role_bindings[i].user == claims.name - role_refs := data.bindings.role_bindings[i].role_refs - role := role_refs[_] + project := data.bindings.role_bindings[i].bindings[_].namespace +} + +# get all projects which are visible by all users (the user name is "*") +user_projects[project] { + some i + data.bindings.role_bindings[i].user == "*" + project := data.bindings.role_bindings[i].bindings[_].namespace } -# only global roles and roles under the given project are allowed. +# if user is system admin, return all projects +user_projects[project] { + user_is_admin + project := data.bindings.role_bindings[_].bindings[_].namespace +} + +# only roles under the given project are allowed allewed_roles[role_ref] { - role_ref := roles[_] - role_ref.namespace == "" + some i + data.bindings.role_bindings[i].user == claims.name + data.bindings.role_bindings[i].bindings[j].namespace == projcet_name + role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } +# if the proejct is visible by all users (the user name is "*"), the bound roles are also allowed allewed_roles[role_ref] { - role_ref := roles[_] - role_ref.namespace == projcet_name + some i + data.bindings.role_bindings[i].user == "*" + project := data.bindings.role_bindings[i].bindings[_].namespace == projcet_name + role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } user_is_granted[grant] { @@ -99,12 +125,6 @@ user_is_granted[grant] { grant := data.roles.roles[i].rules[_] } -# get all projects which are visible by current user -user_projects[project] { - project := roles[_].namespace - project != "" -} - claims := payload { # TODO: Verify the signature on the Bearer token. The certificate can be # hardcoded into the policy, and it could also be loaded via data or diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json index 0512dd84..36187c9c 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json @@ -2,24 +2,80 @@ "role_bindings": [ { "user": "alice", - "role_refs": [ + "bindings": [ { - "name": "reader1", - "namespace": "project1" + "namespace": "project1", + "role_refs": [ + { + "name": "reader1", + "namespace": "project1" + }, + { + "name": "reader2", + "namespace": "" + } + ] }, { - "name": "reader", - "namespace": "project2" + "namespace": "project2", + "role_refs": [ + { + "name": "reader", + "namespace": "project2" + }, + { + "name": "reader3", + "namespace": "project2" + } + ] + } + ] + }, + { + "user": "*", + "bindings": [ + { + "namespace": "project3", + "role_refs": [ + { + "name": "view", + "namespace": "" + } + ] }, { - "name": "reader3", - "namespace": "project2" + "namespace": "project1", + "role_refs": [ + { + "name": "view", + "namespace": "" + } + ] + } + ] + }, + { + "user": "bob", + "bindings": [ + { + "namespace": "project4", + "role_refs": [ + { + "name": "view", + "namespace": "" + } + ] }, { - "name": "reader2", - "namespace": "" + "namespace": "project5", + "role_refs": [ + { + "name": "view", + "namespace": "" + } + ] } ] } ] -} \ No newline at end of file +} -- Gitee From cf3bbd462ac2ed89155846cf6f3325b4eb03dde6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Oct 2021 19:33:16 +0800 Subject: [PATCH 0029/2647] refactor list workflows Signed-off-by: mouuii --- .../common/repository/mongodb/workflow.go | 19 +++++++ .../aslan/core/workflow/handler/router.go | 2 +- .../aslan/core/workflow/handler/workflow.go | 4 +- .../workflow/service/workflow/workflow.go | 50 ++----------------- 4 files changed, 26 insertions(+), 49 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index 4f482802..c7bbc880 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -172,3 +172,22 @@ func (c *WorkflowColl) ListWithScheduleEnabled() ([]*models.Workflow, error) { return resp, nil } + +func (c *WorkflowColl) ListWorkflowsByProjects(projects []string) ([]*models.Workflow, error) { + resp := make([]*models.Workflow, 0) + query := bson.M{"product_tmpl_name": bson.M{ + "$in": projects, + }} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index fa511c59..c006c918 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -108,7 +108,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflow.POST("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateWorkflow) workflow.PUT("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowUpdateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) - workflow.GET("/testName/:testName", ListAllWorkflows) + workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) workflow.DELETE("/:name", GetProductNameByWorkflow, gin2.IsHavePermission([]string{permission.WorkflowDeleteUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, DeleteWorkflow) workflow.GET("/preset/:productName", PreSetWorkflow) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index 729fcf3f..2c67cda4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -121,11 +121,11 @@ func ListWorkflows(c *gin.Context) { ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), ctx.User.ID, ctx.Logger) } -func ListAllWorkflows(c *gin.Context) { +func ListTestWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListAllWorkflows(c.Param("testName"), ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListAllWorkflows(c.Param("testName"), ctx.User.ID, c.QueryArray("projects"), ctx.Logger) } // FindWorkflow find a workflow diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index ec44581e..d88a0631 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -33,10 +33,8 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/types/permission" ) var mut sync.Mutex @@ -490,51 +488,11 @@ func findWorkflowStat(workflow *commonmodels.Workflow, workflowStats []*commonmo return 0, 0, 0 } -func ListAllWorkflows(testName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { - allWorkflows := make([]*commonmodels.Workflow, 0) - workflows := make([]*commonmodels.Workflow, 0) - var err error - if superUser { - allWorkflows, err = commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) - if err != nil { - log.Errorf("Workflow.List error: %v", err) - return allWorkflows, e.ErrListWorkflow.AddDesc(err.Error()) - } - } else { - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - productNameMap, err := poetryCtl.GetUserProject(userID, log) - if err != nil { - log.Errorf("ListAllWorkflows GetUserProject error: %v", err) - return nil, fmt.Errorf("ListAllWorkflows GetUserProject error: %v", err) - } - productNames := make([]string, 0) - for productName, roleIDs := range productNameMap { - roleID := roleIDs[0] - if roleID == setting.RoleOwnerID { - productNames = append(productNames, productName) - } else { - uuids, err := poetryCtl.GetUserPermissionUUIDs(roleID, productName, log) - if err != nil { - log.Errorf("ListAllWorkflows GetUserPermissionUUIDs error: %v", err) - return nil, fmt.Errorf("ListAllWorkflows GetUserPermissionUUIDs error: %v", err) - } - - ids := sets.NewString(uuids...) - if ids.Has(permission.WorkflowUpdateUUID) { - productNames = append(productNames, productName) - } - } - } - for _, productName := range productNames { - tempWorkflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{ProductName: productName}) - if err != nil { - log.Errorf("ListAllWorkflows Workflow.List error: %v", err) - return nil, fmt.Errorf("ListAllWorkflows Workflow.List error: %v", err) - } - allWorkflows = append(allWorkflows, tempWorkflows...) - } +func ListAllWorkflows(testName string, userID int, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { + allWorkflows, err := commonrepo.NewWorkflowColl().ListWorkflowsByProjects(projects) + if err != nil { + return nil, err } - for _, workflow := range allWorkflows { if workflow.TestStage != nil { testNames := sets.NewString(workflow.TestStage.TestNames...) -- Gitee From 2e1c4c9ccbe4bd3e4fe52554ad8e4cdcdbea743a Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Oct 2021 20:10:01 +0800 Subject: [PATCH 0030/2647] refactor list workflows Signed-off-by: mouuii --- .../picket/client/aslan/workflow.go | 19 ++++++++++++++ .../picket/core/filter/handler/router.go | 4 +++ .../picket/core/filter/handler/workflow.go | 15 +++++++++++ .../picket/core/filter/service/workflow.go | 25 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 pkg/microservice/picket/client/aslan/workflow.go create mode 100644 pkg/microservice/picket/core/filter/handler/workflow.go create mode 100644 pkg/microservice/picket/core/filter/service/workflow.go diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go new file mode 100644 index 00000000..9b16631f --- /dev/null +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -0,0 +1,19 @@ +package aslan + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) ListTestProjects(testName string, header http.Header, qs url.Values) ([]byte, error) { + url := "/workflow/workflow/testName/" + testName + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 715ca28a..9e8aea85 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -27,4 +27,8 @@ func (*Router) Inject(router *gin.RouterGroup) { { roles.GET("", ListProjects) } + workflows := router.Group("workflows") + { + workflows.GET("testName/:testName", ListTestWorkflows) + } } diff --git a/pkg/microservice/picket/core/filter/handler/workflow.go b/pkg/microservice/picket/core/filter/handler/workflow.go new file mode 100644 index 00000000..2a93f977 --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/workflow.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListTestWorkflows(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + testName := c.Param("testName") + ctx.Resp, ctx.Err = service.ListTestWorkflows(testName, c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go new file mode 100644 index 00000000..78a7a185 --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -0,0 +1,25 @@ +package service + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + "go.uber.org/zap" +) + +func ListTestWorkflows(testName string, header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { + names, err := getAllowedProjects(header, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + + for _, name := range names { + qs.Add("projects", name) + } + + aslanClient := aslan.New() + + return aslanClient.ListTestProjects(testName, header, qs) +} -- Gitee From e7979e35446c1131c9167c1f5295561a53030ef0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Oct 2021 20:21:13 +0800 Subject: [PATCH 0031/2647] refactor list workflows Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/handler/router.go | 2 ++ pkg/microservice/aslan/core/workflow/handler/router.go | 1 + 2 files changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 8a503162..3a364b4f 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -42,6 +42,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) + // replace by picket product.GET("", ListProductTemplate) product.GET("/:name/searching-rules", GetCustomMatchRules) product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) @@ -65,6 +66,7 @@ func (*Router) Inject(router *gin.RouterGroup) { project := router.Group("projects") { + // request by picket project.GET("", ListProjects) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index c006c918..9f267b73 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -108,6 +108,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflow.POST("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateWorkflow) workflow.PUT("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowUpdateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) + // request by picket workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) workflow.DELETE("/:name", GetProductNameByWorkflow, gin2.IsHavePermission([]string{permission.WorkflowDeleteUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, DeleteWorkflow) -- Gitee From 802dcad25343837bbc299c1dbe61034810b2b852 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Oct 2021 14:38:06 +0800 Subject: [PATCH 0032/2647] rename listallworkflows Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/workflow.go | 2 +- .../aslan/core/workflow/service/workflow/workflow.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index 2c67cda4..e7433928 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -125,7 +125,7 @@ func ListTestWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListAllWorkflows(c.Param("testName"), ctx.User.ID, c.QueryArray("projects"), ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListTestWorkflows(c.Param("testName"), ctx.User.ID, c.QueryArray("projects"), ctx.Logger) } // FindWorkflow find a workflow diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index d88a0631..3a143e59 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -488,7 +488,7 @@ func findWorkflowStat(workflow *commonmodels.Workflow, workflowStats []*commonmo return 0, 0, 0 } -func ListAllWorkflows(testName string, userID int, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { +func ListTestWorkflows(testName string, userID int, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { allWorkflows, err := commonrepo.NewWorkflowColl().ListWorkflowsByProjects(projects) if err != nil { return nil, err -- Gitee From f920542a5bea8914280262a7c0a74481fd9e980d Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 14 Oct 2021 14:44:37 +0800 Subject: [PATCH 0033/2647] add global rolebinding & add new policy user_visible_projects and user_allowed_projects Signed-off-by: lou --- .../policy/core/handler/role_binding.go | 13 ++++ .../policy/core/handler/router.go | 5 ++ .../core/service/bundle/rego/authz.rego | 59 ++++++++++++++----- .../bundle/rego/test_data/bindings/data.json | 9 +++ .../bundle/rego/test_data/roles/data.json | 10 ++++ .../policy/core/service/role_binding.go | 5 -- 6 files changed, 81 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 33b73e8f..5ceca300 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -42,3 +42,16 @@ func CreateRoleBinding(c *gin.Context) { ctx.Err = service.CreateRoleBinding(projectName, args, ctx.Logger) } + +func CreateGlobalRoleBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.RoleBinding{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.CreateRoleBinding("", args, ctx.Logger) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 32cd3db6..89799a37 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -38,6 +38,11 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings.POST("", CreateRoleBinding) } + globalRoleBindings := router.Group("global-rolebindings") + { + globalRoleBindings.POST("", CreateGlobalRoleBinding) + } + bundles := router.Group("bundles") { bundles.GET("/:name", DownloadBundle) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 6f58574d..8cf17649 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -5,9 +5,10 @@ import input.attributes.request.http as http_request # Policy rule definitions in rbac style which is consumed by OPA server. # you can use it to: # 1. decide if a request is allowed by querying: rbac.allow -# 2. get all accessible proejcts for an authenticated user by querying: rbac.user_projects -# 3. check if a user is system admin by querying: rbac.user_is_admin -# 4. check if a user is project admin by querying: rbac.user_is_project_admin +# 2. get all visible projects for an authenticated user by querying: rbac.user_visible_projects +# 3. get all allowed projects for a certain action(method+endpoint) for an authenticated user by querying: rbac.user_allowed_projects +# 4. check if a user is system admin by querying: rbac.user_is_admin +# 5. check if a user is project admin by querying: rbac.user_is_project_admin # By default, deny requests. @@ -45,7 +46,7 @@ allow { user_is_admin { some role - allewed_roles[role] + all_roles[role] role.name == "admin" role.namespace == "" @@ -53,10 +54,10 @@ user_is_admin { user_is_project_admin { some role - allewed_roles[role] + allowed_roles[role] role.name == "admin" - role.namespace == projcet_name + role.namespace == project_name } url_is_public { @@ -72,10 +73,10 @@ url_is_exempted { url_is_exempted { data.exemptions.namespaced[_].method == http_request.method glob.match(trim(data.exemptions.namespaced[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) - user_projects[_] == projcet_name + user_projects[_] == project_name } -projcet_name := pn { +project_name := pn { pn := input.parsed_query.projectName[0] } @@ -93,31 +94,59 @@ user_projects[project] { project := data.bindings.role_bindings[i].bindings[_].namespace } +# all projects which are allowed by current user +user_allowed_projects[project] { + some project + user_projects[project] + not user_is_admin + allow with project_name as project +} + # if user is system admin, return all projects -user_projects[project] { +user_allowed_projects[project] { + project := "*" user_is_admin - project := data.bindings.role_bindings[_].bindings[_].namespace +} + +# all projects which are visible by current user +user_visible_projects[project] { + some project + user_projects[project] + not user_is_admin +} + +# if user is system admin, return all projects +user_visible_projects[project] { + project := "*" + user_is_admin +} + + +all_roles[role_ref] { + some i + data.bindings.role_bindings[i].user == claims.name + role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } # only roles under the given project are allowed -allewed_roles[role_ref] { +allowed_roles[role_ref] { some i data.bindings.role_bindings[i].user == claims.name - data.bindings.role_bindings[i].bindings[j].namespace == projcet_name + data.bindings.role_bindings[i].bindings[j].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } # if the proejct is visible by all users (the user name is "*"), the bound roles are also allowed -allewed_roles[role_ref] { +allowed_roles[role_ref] { some i data.bindings.role_bindings[i].user == "*" - project := data.bindings.role_bindings[i].bindings[_].namespace == projcet_name + project := data.bindings.role_bindings[i].bindings[_].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } user_is_granted[grant] { some role_ref - allewed_roles[role_ref] + allowed_roles[role_ref] some i data.roles.roles[i].name == role_ref.name diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json index 36187c9c..fb99ca38 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json @@ -3,6 +3,15 @@ { "user": "alice", "bindings": [ + { + "namespace": "", + "role_refs": [ + { + "name": "admin", + "namespace": "" + } + ] + }, { "namespace": "project1", "role_refs": [ diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json index 68ce3d2f..d86936b5 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json @@ -70,6 +70,16 @@ } ] }, + { + "name": "view", + "namespace": "project3", + "rules": [ + { + "method": "GET", + "endpoint": "/api/articles" + } + ] + }, { "name": "reader2", "namespace": "project1", diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 8300729c..a7cd5339 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -33,11 +33,6 @@ type RoleBinding struct { } func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { - if ns == "" { - logger.Errorf("Namespace is empty") - return fmt.Errorf("empty namespace") - } - nsRole := ns if rb.Global { nsRole = "" -- Gitee From 6595ca2de74a5b52855422c83cb1dd5d6bf0bf4e Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Oct 2021 14:47:07 +0800 Subject: [PATCH 0034/2647] remove workflow has permission Signed-off-by: mouuii --- .../service/workflow/workflow_task.go | 94 +------------------ 1 file changed, 1 insertion(+), 93 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index ddb3b4f2..1983ba6b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -28,7 +28,6 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -45,7 +44,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util" ) @@ -250,7 +248,7 @@ func EnsureBuildResp(mb *commonmodels.Build) { func ListBuildDetail(name, targets string, log *zap.SugaredLogger) ([]*commonmodels.Build, error) { opt := &commonrepo.BuildListOption{ - Name: name, + Name: name, } if len(strings.TrimSpace(targets)) != 0 { @@ -440,11 +438,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } args.IsParallel = workflow.IsParallel - if !HasPermission(workflow.ProductTmplName, workflow.EnvName, args, userID, superUser, log) { - log.Warnf("该工作流[%s]绑定的环境您没有权限,用户[%s]不能执行该工作流!", workflow.Name, taskCreator) - return nil, e.ErrCreateTask.AddDesc("该工作流绑定的环境您没有更新环境或者环境管理权限,不能执行该工作流!") - } - var env *commonmodels.Product if args.Namespace != "" { // 处理namespace,避免开头或者结尾出现多余的逗号 @@ -854,86 +847,6 @@ func AddDataToArgs(args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) return nil } -func HasPermission(productName, envName string, args *commonmodels.WorkflowTaskArgs, userID int, superUser bool, log *zap.SugaredLogger) bool { - //排除触发器触发和工作流没有绑定环境的情况 - if userID == permission.AnonymousUserID || envName == "" { - return true - } - // 只有测试任务的情况 - if len(args.Target) == 0 && len(args.Tests) > 0 { - return true - } - //权限判断 - if superUser { - return true - } - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - productNameMap, err := poetryClient.GetUserProject(userID, log) - if err != nil { - log.Errorf("Collection.Product.List GetUserProject error: %v", err) - return false - } - //判断环境是类生产环境还是测试环境 - isProd := false - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - prod, _ := commonrepo.NewProductColl().Find(opt) - if prod != nil && prod.ClusterID != "" { - if cluster, _ := commonrepo.NewK8SClusterColl().Get(prod.ClusterID); cluster != nil { - isProd = cluster.Production - } - } - for _, roleID := range productNameMap[productName] { - if roleID == poetry.ProjectOwner { - return true - } - if envRolePermissions, _ := poetryClient.ListEnvRolePermission(productName, envName, roleID, log); len(envRolePermissions) > 0 { - for _, envRolePermission := range envRolePermissions { - if envRolePermission.PermissionUUID == permission.TestEnvManageUUID { - return true - } - } - } else { - if !isProd { - if poetryClient.HasOperatePermission(productName, permission.TestUpdateEnvUUID, userID, superUser, log) { - return true - } - if poetryClient.HasOperatePermission(productName, permission.TestEnvManageUUID, userID, superUser, log) { - return true - } - - envRolePermissions, _ := poetryClient.ListEnvRolePermission(productName, envName, 0, log) - for _, envRolePermission := range envRolePermissions { - if envRolePermission.PermissionUUID == permission.TestEnvManageUUID { - return true - } - } - } else { - if poetryClient.HasOperatePermission(productName, permission.ProdEnvManageUUID, userID, superUser, log) { - return true - } - - envRolePermissions, _ := poetryClient.ListEnvRolePermission(productName, envName, 0, log) - for _, envRolePermission := range envRolePermissions { - if envRolePermission.PermissionUUID == permission.ProdEnvManageUUID { - return true - } - } - } - } - } - // 如果该项目设置过all-users,判断all-users的权限 - productRole, _ := poetryClient.ListRoles(productName, log) - if productRole != nil { - permissionUUIDs, _ := poetryClient.GetUserPermissionUUIDs(productRole.ID, productName, log) - if isProd && sets.NewString(permissionUUIDs...).Has(permission.ProdEnvManageUUID) { - return true - } else if !isProd && sets.NewString(permissionUUIDs...).Has(permission.TestEnvManageUUID) { - return true - } - } - return false -} - func dealWithNamespace(args *commonmodels.WorkflowTaskArgs) { args.Namespace = strings.TrimPrefix(args.Namespace, ",") args.Namespace = strings.TrimSuffix(args.Namespace, ",") @@ -1313,11 +1226,6 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator return nil, e.ErrFindWorkflow.AddDesc(err.Error()) } - if !HasPermission(workflow.ProductTmplName, workflow.EnvName, args, userID, superUser, log) { - log.Warnf("该工作流[%s]绑定的环境您没有权限,用户[%s]不能执行该工作流!", workflow.Name, taskCreator) - return nil, e.ErrCreateTask.AddDesc("该工作流绑定的环境您没有更新环境或者环境管理权限,不能执行该工作流") - } - var env *commonmodels.Product if args.Namespace != "" { // 查找要部署的环境 -- Gitee From 7e8bd9470ca53fdfea4970e05b82184ad9e9c412 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Oct 2021 14:50:34 +0800 Subject: [PATCH 0035/2647] remove user k8s config verb limit, now to * Signed-off-by: mouuii --- pkg/microservice/aslan/core/setting/service/user.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index 45ef8f90..269ff39b 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -45,7 +45,6 @@ import ( krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/types/permission" ) type kubeCfgTmplArgs struct { @@ -237,12 +236,8 @@ func ensureServiceAccount(namespace, username string, log *zap.SugaredLogger) er } func ensureUserRole(namespace, username, productName string, userID int, superUser bool, log *zap.SugaredLogger) error { - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) roleName := fmt.Sprintf("%s-role", username) - verbs := []string{"get", "list", "watch"} - if poetryClient.HasOperatePermission(productName, permission.TestEnvManageUUID, userID, superUser, log) { - verbs = []string{"*"} - } + verbs := []string{"*"} role := &rbacv1beta1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: roleName, -- Gitee From 1b4a6100ba99acc32742c76137d8227dd40c9d51 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Oct 2021 14:54:01 +0800 Subject: [PATCH 0036/2647] delete contributor permission Signed-off-by: mouuii --- .../aslan/core/project/service/product.go | 36 ----------- pkg/shared/poetry/user.go | 61 ------------------- 2 files changed, 97 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 36ee1b2a..ca67b4f2 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -41,7 +41,6 @@ import ( "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/types/permission" ) type CustomParseDataArgs struct { @@ -533,23 +532,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } func ForkProduct(userID int, username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - // first check if the product have contributor role, if not, create one - if !poetryClient.ContributorRoleExist(args.ProductName, log) { - err := poetryClient.CreateContributorRole(args.ProductName, log) - if err != nil { - log.Errorf("Cannot create contributor role for product: %s, the error is: %v", args.ProductName, err) - return e.ErrForkProduct.AddDesc(err.Error()) - } - } - - // Give contributor role to this user - // first look for roleID - roleID := poetryClient.GetContributorRoleID(args.ProductName, log) - if roleID < 0 { - log.Errorf("Failed to get contributor Role ID from poetry client") - return e.ErrForkProduct.AddDesc("Failed to get contributor Role ID from poetry client") - } prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { @@ -622,24 +604,6 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj return e.ErrForkProduct.AddDesc(errMsg) } - userList, _ := poetryClient.ListPermissionUsers(args.ProductName, roleID, poetry.ProjectType, log) - newUserList := append(userList, userID) - err = poetryClient.UpdateUserRole(roleID, poetry.ProjectType, args.ProductName, newUserList, log) - if err != nil { - log.Errorf("Failed to update user role, the error is: %v", err) - return e.ErrForkProduct.AddDesc(fmt.Sprintf("Failed to update user role, the error is: %v", err)) - } - - err = poetryClient.CreateUserEnvPermission(&poetry.UserEnvPermission{ - UserID: userID, - ProductName: args.ProductName, - EnvName: args.EnvName, - PermissionUUIDs: []string{permission.TestEnvListUUID, permission.TestEnvManageUUID}, - }) - if err != nil { - return e.ErrForkProduct.AddDesc(fmt.Sprintf("Failed to create env permission for user: %s", username)) - } - workflowPreset, err := workflowservice.PreSetWorkflow(args.ProductName, log) if err != nil { errMsg := fmt.Sprintf("Failed to get workflow preset info, the error is: %+v", err) diff --git a/pkg/shared/poetry/user.go b/pkg/shared/poetry/user.go index db5d5353..6885299f 100644 --- a/pkg/shared/poetry/user.go +++ b/pkg/shared/poetry/user.go @@ -20,13 +20,11 @@ import ( "fmt" "strconv" "strings" - "time" "go.uber.org/zap" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/types/permission" ) //roleType @@ -288,65 +286,6 @@ func (c *Client) ListProductPermissionUsers(productName, permissionID string, lo return resp, nil } -func (c *Client) CreateContributorRole(product string, log *zap.SugaredLogger) error { - // create role first - url := "/directory/roles" - req := &Role{ - IsDisabled: false, - Name: setting.RoleContributor, - ProductName: product, - RoleType: ProjectType, - UpdateBy: setting.SystemUser, - UpdateAt: time.Now().Unix(), - } - - r := &Role{} - _, err := c.Post(url, httpclient.SetBody(req), httpclient.SetResult(r)) - if err != nil { - log.Errorf("CreateContributorRole failed, error: %v", err) - return err - } - - // 然后创建role product - roleProductURL := "/directory/roleProduct" - roleProductReq := RoleProduct{ - RoleID: r.ID, - ProductName: product, - } - - _, err = c.Post(roleProductURL, httpclient.SetBody(roleProductReq)) - if err != nil { - log.Errorf("CreateContributorRole failed, error: %v", err) - return err - } - permissionList := []string{ - permission.WorkflowTaskUUID, - permission.WorkflowListUUID, - permission.TestEnvDeleteUUID, - permission.TestEnvManageUUID, - permission.TestEnvListUUID, - permission.TestUpdateEnvUUID, - permission.BuildListUUID, - permission.TestListUUID, - permission.ServiceTemplateListUUID, - } - permissionReq := RolePermissionReq{ - RoleID: r.ID, - RoleType: r.RoleType, - PermissionUUID: permissionList, - ProductName: product, - } - rolePermissionURL := "/directory/rolePermission" - - _, err = c.Post(rolePermissionURL, httpclient.SetBody(permissionReq)) - if err != nil { - log.Errorf("CreateContributorRole failed, error: %v", err) - return err - } - - return nil -} - // ListRoles 根据项目里面的角色名称获取对应的角色ID func (c *Client) ListRoles(productName string, log *zap.SugaredLogger) (*Role, error) { url := "/directory/roles" -- Gitee From cf6b971d14bb5af1b538c39f35fc8bc6bd34dedc Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Oct 2021 18:09:03 +0800 Subject: [PATCH 0037/2647] refactor code Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/handler/router.go | 2 -- pkg/microservice/aslan/core/workflow/handler/router.go | 1 - pkg/microservice/picket/client/aslan/workflow.go | 5 +++-- pkg/microservice/picket/core/filter/service/workflow.go | 5 +++-- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 3a364b4f..8a503162 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -42,7 +42,6 @@ func (*Router) Inject(router *gin.RouterGroup) { { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) - // replace by picket product.GET("", ListProductTemplate) product.GET("/:name/searching-rules", GetCustomMatchRules) product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) @@ -66,7 +65,6 @@ func (*Router) Inject(router *gin.RouterGroup) { project := router.Group("projects") { - // request by picket project.GET("", ListProjects) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 9f267b73..c006c918 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -108,7 +108,6 @@ func (*Router) Inject(router *gin.RouterGroup) { workflow.POST("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateWorkflow) workflow.PUT("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowUpdateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) - // request by picket workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) workflow.DELETE("/:name", GetProductNameByWorkflow, gin2.IsHavePermission([]string{permission.WorkflowDeleteUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, DeleteWorkflow) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 9b16631f..f4ebc8c1 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -1,14 +1,15 @@ package aslan import ( + "fmt" "net/http" "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" ) -func (c *Client) ListTestProjects(testName string, header http.Header, qs url.Values) ([]byte, error) { - url := "/workflow/workflow/testName/" + testName +func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.Values) ([]byte, error) { + url := fmt.Sprintf("/workflow/workflow/testName/%s", testName) res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index 78a7a185..2f426173 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -4,8 +4,9 @@ import ( "net/http" "net/url" - "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" ) func ListTestWorkflows(testName string, header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { @@ -21,5 +22,5 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge aslanClient := aslan.New() - return aslanClient.ListTestProjects(testName, header, qs) + return aslanClient.ListTestWorkflows(testName, header, qs) } -- Gitee From 57379599068c28ef4c9e89a0542e8a6b99772b90 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 14 Oct 2021 19:32:22 +0800 Subject: [PATCH 0038/2647] improve list projects api Signed-off-by: lou --- .../core/common/repository/mongodb/product.go | 13 +- .../repository/mongodb/template/product.go | 53 ++++- .../aslan/core/project/handler/product.go | 13 -- .../aslan/core/project/handler/project.go | 9 +- .../aslan/core/project/handler/router.go | 1 - .../aslan/core/project/service/product.go | 203 ------------------ .../aslan/core/project/service/project.go | 161 ++++++++------ 7 files changed, 158 insertions(+), 295 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 564fdc79..a71f4243 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -180,10 +180,15 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { return ret, nil } -func (c *ProductColl) ListProjects() ([]*projectEnvs, error) { +func (c *ProductColl) ListProjectsInNames(names []string) ([]*projectEnvs, error) { var res []*projectEnvs - pipeline := []bson.M{ - { + var pipeline []bson.M + if len(names) > 0 { + pipeline = append(pipeline, bson.M{"$match": bson.M{"product_name": bson.M{"$in": names}}}) + } + + pipeline = append(pipeline, + bson.M{ "$group": bson.M{ "_id": bson.M{ "product_name": "$product_name", @@ -192,7 +197,7 @@ func (c *ProductColl) ListProjects() ([]*projectEnvs, error) { "envs": bson.M{"$push": "$env_name"}, }, }, - } + ) cursor, err := c.Aggregate(context.TODO(), pipeline) if err != nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index a9e7cd8f..5f620e25 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -31,6 +31,12 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) +type ProjectInfo struct { + Name string `bson:"product_name"` + Alias string `bson:"project_name"` + Desc string `bson:"description"` +} + type ProductColl struct { *mongo.Collection @@ -71,20 +77,53 @@ func (c *ProductColl) FindProjectName(project string) (*template.Product, error) return resp, err } -func (c *ProductColl) ListProjectsByNames(projects []string) ([]*template.Product, error) { - resp := make([]*template.Product, 0) - ctx := context.TODO() - query := bson.M{"project_name": bson.M{"$in": projects}} - cursor, err := c.Collection.Find(ctx, query) +func (c *ProductColl) ListNames(inNames []string) ([]string, error) { + res, err := c.ListProjects(inNames, bson.D{ + {"product_name", 1}, + }) + if err != nil { + return nil, err + } + + var names []string + for _, r := range res { + names = append(names, r.Name) + } + + return names, nil +} + +func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error) { + return c.ListProjects(inNames, bson.D{ + {"product_name", 1}, + {"project_name", 1}, + {"description", 1}, + }) +} + +func (c *ProductColl) ListProjects(inNames []string, projection bson.D) ([]*ProjectInfo, error) { + opts := options.Find() + filter := bson.M{} + if len(inNames) > 0 { + filter["product_name"] = bson.M{"$in": inNames} + } + + if len(projection) > 0 { + opts.SetProjection(projection) + } + + cursor, err := c.Collection.Find(context.TODO(), filter, opts) if err != nil { return nil, err } - err = cursor.All(ctx, &resp) + var res []*ProjectInfo + err = cursor.All(context.TODO(), &res) if err != nil { return nil, err } - return resp, nil + + return res, nil } func (c *ProductColl) List() ([]*template.Product, error) { diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index b119ffb7..ddd8ee3b 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -47,19 +47,6 @@ func GetProductTemplateServices(c *gin.Context) { ctx.Resp, ctx.Err = projectservice.GetProductTemplateServices(productTemplatName, ctx.Logger) } -//ListProductTemplate 产品分页信息 -func ListProductTemplate(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - productType := c.DefaultQuery("productType", "normal") - if productType != "openSource" { - ctx.Resp, ctx.Err = projectservice.ListProductTemplate(ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) - return - } - ctx.Resp, ctx.Err = projectservice.ListOpenSourceProduct(ctx.Logger) -} - func CreateProductTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/project/handler/project.go b/pkg/microservice/aslan/core/project/handler/project.go index d3a3769f..f7836262 100644 --- a/pkg/microservice/aslan/core/project/handler/project.go +++ b/pkg/microservice/aslan/core/project/handler/project.go @@ -39,8 +39,15 @@ func ListProjects(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } + + productType := c.DefaultQuery("productType", "normal") + if productType == "openSource" { + ctx.Resp, ctx.Err = projectservice.ListOpenSourceProduct(ctx.Logger) + return + } + ctx.Resp, ctx.Err = projectservice.ListProjects( - &projectservice.ProjectListOptions{IgnoreNoEnvs: args.IgnoreNoEnvs, Verbosity: projectservice.QueryVerbosity(args.Verbosity), Projects: args.Names}, + &projectservice.ProjectListOptions{IgnoreNoEnvs: args.IgnoreNoEnvs, Verbosity: projectservice.QueryVerbosity(args.Verbosity), Names: args.Names}, ctx.Logger, ) } diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 8a503162..3af33b42 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -42,7 +42,6 @@ func (*Router) Inject(router *gin.RouterGroup) { { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) - product.GET("", ListProductTemplate) product.GET("/:name/searching-rules", GetCustomMatchRules) product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 36ee1b2a..6af53ef5 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -20,7 +20,6 @@ import ( "fmt" "strconv" "strings" - "sync" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" @@ -74,208 +73,6 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te return resp, nil } -// ListProductTemplate 列出产品模板分页 -func ListProductTemplate(userID int, superUser bool, log *zap.SugaredLogger) ([]*template.Product, error) { - var ( - err error - errorList = &multierror.Error{} - resp = make([]*template.Product, 0) - tmpls = make([]*template.Product, 0) - productTmpls = make([]*template.Product, 0) - productNameMap = make(map[string][]int64) - productMap = make(map[string]*template.Product) - wg sync.WaitGroup - mu sync.Mutex - maxRoutineNum = 20 // 协程池最大协程数量 - ch = make(chan int, maxRoutineNum) // 控制协程数量 - ) - - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - - tmpls, err = templaterepo.NewProductColl().List() - if err != nil { - log.Errorf("ProfuctTmpl.List error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - - for _, product := range tmpls { - if superUser { - product.Role = setting.RoleAdmin - product.PermissionUUIDs = []string{} - product.ShowProject = true - continue - } - productMap[product.ProductName] = product - } - - if !superUser { - productNameMap, err = poetryCtl.GetUserProject(userID, log) - if err != nil { - log.Errorf("ProfuctTmpl.List GetUserProject error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - - // 优先处理客户有明确关联关系的项目 - for productName, roleIDs := range productNameMap { - wg.Add(1) - ch <- 1 - // 临时复制range获取的数据,避免重复操作最后一条数据 - tmpProductName := productName - tmpRoleIDs := roleIDs - - go func(tmpProductName string, tmpRoleIDs []int64) { - defer func() { - <-ch - wg.Done() - }() - - roleID := tmpRoleIDs[0] - product, err := templaterepo.NewProductColl().Find(tmpProductName) - if err != nil { - errorList = multierror.Append(errorList, err) - log.Errorf("ProfuctTmpl.List error: %v", err) - return - } - uuids, err := poetryCtl.GetUserPermissionUUIDs(roleID, tmpProductName, log) - if err != nil { - errorList = multierror.Append(errorList, err) - log.Errorf("ProfuctTmpl.List GetUserPermissionUUIDs error: %v", err) - return - } - if roleID == setting.RoleOwnerID { - product.Role = setting.RoleOwner - product.PermissionUUIDs = []string{} - } else { - product.Role = setting.RoleUser - product.PermissionUUIDs = uuids - } - product.ShowProject = true - mu.Lock() - productTmpls = append(productTmpls, product) - delete(productMap, tmpProductName) - mu.Unlock() - }(tmpProductName, tmpRoleIDs) - } - - wg.Wait() - if errorList.ErrorOrNil() != nil { - return resp, errorList - } - - // 增加项目里面设置过all-users的权限处理 - for _, product := range productMap { - wg.Add(1) - ch <- 1 - // 临时复制range获取的数据,避免重复操作最后一条数据 - tmpProduct := product - - go func(tmpProduct *template.Product) { - defer func() { - <-ch - wg.Done() - }() - - productRole, _ := poetryCtl.ListRoles(tmpProduct.ProductName, log) - if productRole != nil { - uuids, err := poetryCtl.GetUserPermissionUUIDs(productRole.ID, tmpProduct.ProductName, log) - if err != nil { - errorList = multierror.Append(errorList, err) - log.Errorf("ProfuctTmpl.List GetUserPermissionUUIDs error: %v", err) - return - } - - tmpProduct.Role = setting.RoleUser - tmpProduct.PermissionUUIDs = uuids - tmpProduct.ShowProject = true - mu.Lock() - productTmpls = append(productTmpls, tmpProduct) - delete(productMap, tmpProduct.ProductName) - mu.Unlock() - } - }(tmpProduct) - } - wg.Wait() - if errorList.ErrorOrNil() != nil { - return resp, errorList - } - - // 最后处理剩余的项目 - for _, product := range productMap { - wg.Add(1) - ch <- 1 - // 临时复制range获取的数据,避免重复操作最后一条数据 - tmpProduct := product - - go func(tmpProduct *template.Product) { - defer func() { - <-ch - wg.Done() - }() - - var uuids []string - uuids, err = poetryCtl.GetUserPermissionUUIDs(setting.RoleUserID, "", log) - if err != nil { - errorList = multierror.Append(errorList, err) - log.Errorf("ProfuctTmpl.List GetUserPermissionUUIDs error: %v", err) - return - } - tmpProduct.Role = setting.RoleUser - tmpProduct.PermissionUUIDs = uuids - tmpProduct.ShowProject = false - mu.Lock() - productTmpls = append(productTmpls, tmpProduct) - mu.Unlock() - }(tmpProduct) - } - wg.Wait() - if errorList.ErrorOrNil() != nil { - return resp, errorList - } - // 先清空tmpls中的管理员角色数据后,再插入普通用户角色的数据 - tmpls = make([]*template.Product, 0) - tmpls = append(tmpls, productTmpls...) - } - - err = FillProductTemplateVars(tmpls, log) - if err != nil { - return resp, err - } - - for _, tmpl := range tmpls { - wg.Add(1) - ch <- 1 - - go func(tmpTmpl *template.Product) { - defer func() { - <-ch - wg.Done() - }() - - tmpTmpl.TotalServiceNum, err = commonrepo.NewServiceColl().Count(tmpTmpl.ProductName) - if err != nil { - errorList = multierror.Append(errorList, err) - return - } - - tmpTmpl.TotalEnvNum, err = commonrepo.NewProductColl().Count(tmpTmpl.ProductName) - if err != nil { - errorList = multierror.Append(errorList, err) - return - } - - mu.Lock() - resp = append(resp, tmpTmpl) - mu.Unlock() - }(tmpl) - } - wg.Wait() - if errorList.ErrorOrNil() != nil { - return resp, errorList - } - - return resp, nil -} - func ListOpenSourceProduct(log *zap.SugaredLogger) ([]*template.Product, error) { opt := &templaterepo.ProductListOpt{ IsOpensource: "true", diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 0d8e5113..ac5ed373 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" ) type QueryVerbosity string @@ -30,18 +30,20 @@ const ( VerbosityDetailed QueryVerbosity = "detailed" // all information VerbosityBrief QueryVerbosity = "brief" // short information or a summary VerbosityMinimal QueryVerbosity = "minimal" // very little information, usually only a resource identifier + + allProjects = "*" ) type ProjectListOptions struct { IgnoreNoEnvs bool Verbosity QueryVerbosity - Projects []string + Names []string } type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation - Alias string - Desc string + Alias string `json:"alias"` + Desc string `json:"desc"` } type ProjectBriefRepresentation struct { @@ -53,6 +55,14 @@ type ProjectMinimalRepresentation struct { Name string `json:"name"` } +func (o *ProjectListOptions) InNames() []string { + if len(o.Names) == 0 || (len(o.Names) == 1 && o.Names[0] == allProjects) { + return []string{} + } + + return o.Names +} + func ListProjects(opts *ProjectListOptions, logger *zap.SugaredLogger) (interface{}, error) { switch opts.Verbosity { case VerbosityDetailed: @@ -66,95 +76,82 @@ func ListProjects(opts *ProjectListOptions, logger *zap.SugaredLogger) (interfac } } -func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) (res []*ProjectDetailedRepresentation, err error) { - nameWithEnvs, err := mongodb.NewProductColl().ListProjects() +func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectDetailedRepresentation, error) { + var res []*ProjectDetailedRepresentation + + nameSet, nameMap, err := getProjects(opts) if err != nil { logger.Errorf("Failed to list projects, err: %s", err) return nil, err } - nameSet := sets.NewString() - for _, name := range opts.Projects { - nameSet.Insert(name) + nameWithEnvSet, nameWithEnvMap, err := getProjectsWithEnvs(opts) + if err != nil { + logger.Errorf("Failed to list projects, err: %s", err) + return nil, err } - nameWithEnvsSet := sets.NewString() - for _, nameWithEnv := range nameWithEnvs { - // nameWithEnvs may contain projects which are already deleted. - if !nameSet.Has(nameWithEnv.ProjectName) { - continue - } + desiredSet := nameSet + if opts.IgnoreNoEnvs { + desiredSet = nameSet.Intersection(nameWithEnvSet) + } + + for name := range desiredSet { + info := nameMap[name] res = append(res, &ProjectDetailedRepresentation{ ProjectBriefRepresentation: &ProjectBriefRepresentation{ - ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: nameWithEnv.ProjectName}, - Envs: nameWithEnv.Envs, + ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, + Envs: nameWithEnvMap[name], }, + Alias: info.Alias, + Desc: info.Desc, }) - nameWithEnvsSet.Insert(nameWithEnv.ProjectName) } - projects, err := template.NewProductColl().ListProjectsByNames(opts.Projects) + return res, nil +} + +func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectBriefRepresentation, error) { + var res []*ProjectBriefRepresentation + + nameSet, _, err := getProjects(opts) if err != nil { + logger.Errorf("Failed to list projects, err: %s", err) return nil, err } - if !opts.IgnoreNoEnvs { - for _, project := range projects { - if !nameWithEnvsSet.Has(project.ProjectName) { - res = append(res, &ProjectDetailedRepresentation{ - ProjectBriefRepresentation: &ProjectBriefRepresentation{ - ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: project.ProjectName}, - }, - Alias: project.ProductName, - Desc: project.Description, - }) - } - } - } - - return res, nil -} -func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) (res []*ProjectBriefRepresentation, err error) { - nameWithEnvs, err := mongodb.NewProductColl().ListProjects() + nameWithEnvSet, nameWithEnvMap, err := getProjectsWithEnvs(opts) if err != nil { logger.Errorf("Failed to list projects, err: %s", err) return nil, err } - nameSet := sets.NewString() - for _, name := range opts.Projects { - nameSet.Insert(name) + desiredSet := nameSet + if opts.IgnoreNoEnvs { + desiredSet = nameSet.Intersection(nameWithEnvSet) } - nameWithEnvsSet := sets.NewString() - for _, nameWithEnv := range nameWithEnvs { - // nameWithEnvs may contain projects which are already deleted. - if !nameSet.Has(nameWithEnv.ProjectName) { - continue - } + for name := range desiredSet { res = append(res, &ProjectBriefRepresentation{ - ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: nameWithEnv.ProjectName}, - Envs: nameWithEnv.Envs, + ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, + Envs: nameWithEnvMap[name], }) - nameWithEnvsSet.Insert(nameWithEnv.ProjectName) - } - - if !opts.IgnoreNoEnvs { - for _, name := range opts.Projects { - if !nameWithEnvsSet.Has(name) { - res = append(res, &ProjectBriefRepresentation{ - ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, - }) - } - } } return res, nil } -func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) (res []*ProjectMinimalRepresentation, err error) { +func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { + var res []*ProjectMinimalRepresentation + + names, err := templaterepo.NewProductColl().ListNames(opts.InNames()) + if err != nil { + logger.Errorf("Failed to list project names, err: %s", err) + return nil, err + } + if !opts.IgnoreNoEnvs { - for _, name := range opts.Projects { + for _, name := range names { res = append(res, &ProjectMinimalRepresentation{Name: name}) } @@ -162,23 +159,55 @@ func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger } nameSet := sets.NewString() - for _, name := range opts.Projects { + for _, name := range names { nameSet.Insert(name) } - nameWithEnvs, err := mongodb.NewProductColl().ListProjects() + nameWithEnvSet, _, err := getProjectsWithEnvs(opts) if err != nil { logger.Errorf("Failed to list projects, err: %s", err) return nil, err } - for _, nameWithEnv := range nameWithEnvs { + for name := range nameWithEnvSet { // nameWithEnvs may contain projects which are already deleted. - if !nameSet.Has(nameWithEnv.ProjectName) { + if !nameSet.Has(name) { continue } - res = append(res, &ProjectMinimalRepresentation{Name: nameWithEnv.ProjectName}) + res = append(res, &ProjectMinimalRepresentation{Name: name}) } return res, nil } + +func getProjectsWithEnvs(opts *ProjectListOptions) (sets.String, map[string][]string, error) { + nameWithEnvs, err := mongodb.NewProductColl().ListProjectsInNames(opts.InNames()) + if err != nil { + return nil, nil, err + } + + nameSet := sets.NewString() + nameMap := make(map[string][]string) + for _, nameWithEnv := range nameWithEnvs { + nameSet.Insert(nameWithEnv.ProjectName) + nameMap[nameWithEnv.ProjectName] = nameWithEnv.Envs + } + + return nameSet, nameMap, nil +} + +func getProjects(opts *ProjectListOptions) (sets.String, map[string]*templaterepo.ProjectInfo, error) { + res, err := templaterepo.NewProductColl().ListProjectBriefs(opts.InNames()) + if err != nil { + return nil, nil, err + } + + nameSet := sets.NewString() + nameMap := make(map[string]*templaterepo.ProjectInfo) + for _, r := range res { + nameSet.Insert(r.Name) + nameMap[r.Name] = r + } + + return nameSet, nameMap, nil +} -- Gitee From 9dae5a77d1286bca1e72d536505511283a6e2456 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 14 Oct 2021 21:18:07 +0800 Subject: [PATCH 0039/2647] update after review Signed-off-by: lou --- .../core/common/repository/mongodb/template/product.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 5f620e25..948f4283 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -78,7 +78,7 @@ func (c *ProductColl) FindProjectName(project string) (*template.Product, error) } func (c *ProductColl) ListNames(inNames []string) ([]string, error) { - res, err := c.ListProjects(inNames, bson.D{ + res, err := c.listProjects(inNames, bson.D{ {"product_name", 1}, }) if err != nil { @@ -94,14 +94,14 @@ func (c *ProductColl) ListNames(inNames []string) ([]string, error) { } func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error) { - return c.ListProjects(inNames, bson.D{ + return c.listProjects(inNames, bson.D{ {"product_name", 1}, {"project_name", 1}, {"description", 1}, }) } -func (c *ProductColl) ListProjects(inNames []string, projection bson.D) ([]*ProjectInfo, error) { +func (c *ProductColl) listProjects(inNames []string, projection bson.D) ([]*ProjectInfo, error) { opts := options.Find() filter := bson.M{} if len(inNames) > 0 { -- Gitee From c6e1d3fb3ea4a248ae0c375ed29960058a3d5e3f Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 14 Oct 2021 21:37:34 +0800 Subject: [PATCH 0040/2647] remove auth Signed-off-by: lou --- .../aslan/core/build/handler/router.go | 2 - .../aslan/core/code/handler/router.go | 2 - .../aslan/core/common/handler/token.go | 42 ---------- .../aslan/core/cron/handler/router.go | 3 - .../aslan/core/delivery/handler/router.go | 1 - .../aslan/core/environment/handler/router.go | 2 - .../aslan/core/log/handler/router.go | 4 - .../aslan/core/multicluster/handler/router.go | 2 - .../aslan/core/project/handler/router.go | 2 - .../aslan/core/service/handler/router.go | 3 +- .../aslan/core/setting/handler/router.go | 4 - .../aslan/core/system/handler/router.go | 2 - .../core/templatestore/handler/router.go | 5 -- .../aslan/core/workflow/handler/router.go | 2 - .../core/workflow/testing/handler/router.go | 2 - pkg/microservice/aslan/server/rest/router.go | 17 +--- pkg/middleware/gin/auth.go | 84 ------------------- 17 files changed, 2 insertions(+), 177 deletions(-) delete mode 100644 pkg/microservice/aslan/core/common/handler/token.go delete mode 100644 pkg/middleware/gin/auth.go diff --git a/pkg/microservice/aslan/core/build/handler/router.go b/pkg/microservice/aslan/core/build/handler/router.go index f55bd25c..1e6af9d8 100644 --- a/pkg/microservice/aslan/core/build/handler/router.go +++ b/pkg/microservice/aslan/core/build/handler/router.go @@ -26,8 +26,6 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - build := router.Group("build") { build.GET("/:name", FindBuildModule) diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 6b0a22f8..99ac66ca 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -26,8 +26,6 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - codehost := router.Group("codehost") { codehost.GET("", GetCodeHostList) diff --git a/pkg/microservice/aslan/core/common/handler/token.go b/pkg/microservice/aslan/core/common/handler/token.go deleted file mode 100644 index 7fee3260..00000000 --- a/pkg/microservice/aslan/core/common/handler/token.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/setting" -) - -func GetToken(c *gin.Context) { - authorization := c.Request.Header.Get(setting.AuthorizationHeader) - if authorization != "" { - if strings.HasPrefix(authorization, setting.UserAPIKey+" ") { - token := strings.Split(authorization, " ")[1] - c.JSON(http.StatusOK, gin.H{"token": token}) - return - } - } else if token, err := c.Cookie("TOKEN"); err == nil { - c.JSON(http.StatusOK, gin.H{"token": token}) - return - } - - c.JSON(http.StatusBadRequest, gin.H{"message": "illegal request"}) -} diff --git a/pkg/microservice/aslan/core/cron/handler/router.go b/pkg/microservice/aslan/core/cron/handler/router.go index 9d06de00..29bb3f48 100644 --- a/pkg/microservice/aslan/core/cron/handler/router.go +++ b/pkg/microservice/aslan/core/cron/handler/router.go @@ -18,14 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) // --------------------------------------------------------------------------------------- // 定时任务管理接口 diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index cb6b92e5..25b9641c 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -26,7 +26,6 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) deliveryArtifact := router.Group("artifacts") { diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index a1d89054..b0056e6d 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -26,8 +26,6 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - // --------------------------------------------------------------------------------------- // Kube配置管理接口 ConfigMap // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/log/handler/router.go b/pkg/microservice/aslan/core/log/handler/router.go index 03bda972..2fcfcb08 100644 --- a/pkg/microservice/aslan/core/log/handler/router.go +++ b/pkg/microservice/aslan/core/log/handler/router.go @@ -18,15 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - { router.GET("/pods/:name", GetContainerLogs) } diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index fc4ba737..ddfc60ac 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -31,8 +31,6 @@ func (*Router) Inject(router *gin.RouterGroup) { Agent.GET("/:id/agent.yaml", GetClusterYaml("/api/hub")) } - router.Use(gin2.Auth()) - Cluster := router.Group("clusters") { Cluster.GET("", ListClusters) diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 3af33b42..f1d90712 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -26,8 +26,6 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - // 查看自定义变量是否被引用 render := router.Group("renders") { diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 461d2e0b..9bfbd2ad 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -18,6 +18,7 @@ package handler import ( "github.com/gin-gonic/gin" + gin2 "github.com/koderover/zadig/pkg/middleware/gin" "github.com/koderover/zadig/pkg/types/permission" ) @@ -25,8 +26,6 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - harbor := router.Group("harbor") { harbor.GET("/project", ListHarborProjects) diff --git a/pkg/microservice/aslan/core/setting/handler/router.go b/pkg/microservice/aslan/core/setting/handler/router.go index 2b4eef2e..1148a608 100644 --- a/pkg/microservice/aslan/core/setting/handler/router.go +++ b/pkg/microservice/aslan/core/setting/handler/router.go @@ -18,15 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - router.Use(gin2.Auth()) - user := router.Group("user") { user.GET("/kube/config", GetUserKubeConfig) diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index f85abb7d..83a49719 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -30,8 +30,6 @@ func (*Router) Inject(router *gin.RouterGroup) { proxy.GET("/config", GetProxyConfig) } - router.Use(gin2.Auth()) - // --------------------------------------------------------------------------------------- // 安装脚本管理接口 // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 120a55ff..3dea48db 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -18,16 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" - - ginmiddleware "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - - router.Use(ginmiddleware.Auth()) - // --------------------------------------------------------------------------------------- // chart templates // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index c006c918..20467e5c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -34,8 +34,6 @@ func (*Router) Inject(router *gin.RouterGroup) { webhook.POST("", ProcessWebHook) } - router.Use(gin2.Auth()) - build := router.Group("build") { build.GET("/:name/:version/to/subtasks", BuildModuleToSubTasks) diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/router.go b/pkg/microservice/aslan/core/workflow/testing/handler/router.go index e0701d22..48f4e4c0 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/router.go @@ -32,8 +32,6 @@ func (*Router) Inject(router *gin.RouterGroup) { testReport.GET("", GetHTMLTestReport) } - router.Use(gin2.Auth()) - // --------------------------------------------------------------------------------------- // 系统测试接口 // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 1339a6f9..989cfd36 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -17,8 +17,6 @@ limitations under the License. package rest import ( - "github.com/gin-contrib/sessions" - "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" swaggerfiles "github.com/swaggo/files" ginswagger "github.com/swaggo/gin-swagger" @@ -38,7 +36,6 @@ import ( templatehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/handler" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" - gin2 "github.com/koderover/zadig/pkg/middleware/gin" // Note: have to load docs for swagger to work. See https://blog.csdn.net/weixin_43249914/article/details/103035711 _ "github.com/koderover/zadig/pkg/microservice/aslan/server/rest/doc" @@ -54,14 +51,6 @@ import ( // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @BasePath /api/aslan func (s *engine) injectRouterGroup(router *gin.RouterGroup) { - store := cookie.NewStore([]byte("C12f4d5957k345e9g6e86c117e1bfu5kndjevf8u")) - cookieOption := sessions.Options{ - Path: "/", - HttpOnly: true, - } - store.Options(cookieOption) - router.Use(sessions.Sessions("ASLAN", store)) - Auth := gin2.Auth() // --------------------------------------------------------------------------------------- // 对外公共接口 // --------------------------------------------------------------------------------------- @@ -77,12 +66,8 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { // no auth required, should not be exposed via poetry-api-proxy or will fail router.GET("/api/hub/connect", multiclusterhandler.ClusterConnectFromAgent) - router.GET("/api/kodespace/downloadUrl", Auth, commonhandler.GetToolDownloadURL) + router.GET("/api/kodespace/downloadUrl", commonhandler.GetToolDownloadURL) - jwt := router.Group("/api/token", Auth) - { - jwt.GET("", commonhandler.GetToken) - } for name, r := range map[string]injector{ "/api/project": new(projecthandler.Router), "/api/code": new(codehosthandler.Router), diff --git a/pkg/middleware/gin/auth.go b/pkg/middleware/gin/auth.go deleted file mode 100644 index 51578163..00000000 --- a/pkg/middleware/gin/auth.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gin - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" - "github.com/koderover/zadig/pkg/tool/log" -) - -// Auth 返回会判断 Session 的中间件 -func Auth() gin.HandlerFunc { - return func(c *gin.Context) { - authorization := c.Request.Header.Get(setting.AuthorizationHeader) - if authorization != "" { - if strings.Contains(authorization, setting.RootAPIKey) { - token := strings.Split(authorization, " ") - if len(token) == 2 && token[1] == config.PoetryAPIRootKey() { - c.Set(setting.SessionUsername, "fake-user") - c.Set(setting.SessionUser, &poetry.UserInfo{Name: "fake-user", IsSuperUser: true, IsAdmin: true, OrganizationID: 1}) - c.Next() - return - } - } - if strings.Contains(authorization, setting.TIMERAPIKEY) { - token := strings.Split(authorization, " ") - if len(token) == 2 && token[1] == config.PoetryAPIRootKey() { - c.Set(setting.SessionUsername, "timer") - c.Set(setting.SessionUser, &poetry.UserInfo{Name: "timer", IsSuperUser: true, IsAdmin: true, OrganizationID: 1}) - c.Next() - return - } - } - if strings.Contains(authorization, setting.UserAPIKey) { - token := strings.Split(authorization, " ") - if len(token) == 2 { - userInfo, err := poetry.GetUserDetailByToken(config.PoetryServiceAddress(), token[1]) - if err != nil { - log.Errorf("get user detail err :%v", err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "get user detail error"}) - return - } - c.Set(setting.SessionUsername, userInfo.Name) - c.Set(setting.SessionUser, userInfo) - c.Next() - return - } - } - } else if token, err := c.Cookie("TOKEN"); err == nil { - userInfo, err := poetry.GetUserDetailByToken(config.PoetryServiceAddress(), token) - if err != nil { - log.Errorf("get user detail err :%v", err) - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "get user detail error"}) - return - } - c.Set(setting.SessionUsername, userInfo.Name) - c.Set(setting.SessionUser, userInfo) - c.Next() - return - } - - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "auth failed"}) - } -} -- Gitee From e06cd2139f35cf5155e3cf6de06a44bccd02d579 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 15 Oct 2021 10:18:08 +0800 Subject: [PATCH 0041/2647] fix privilege escalation Signed-off-by: lou --- pkg/microservice/policy/core/handler/role_binding.go | 6 ++++-- pkg/microservice/policy/core/handler/router.go | 4 ++-- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 2 +- pkg/microservice/policy/core/service/role_binding.go | 2 ++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 5ceca300..4fe58657 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -43,7 +43,7 @@ func CreateRoleBinding(c *gin.Context) { ctx.Err = service.CreateRoleBinding(projectName, args, ctx.Logger) } -func CreateGlobalRoleBinding(c *gin.Context) { +func CreateSystemRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -53,5 +53,7 @@ func CreateGlobalRoleBinding(c *gin.Context) { return } - ctx.Err = service.CreateRoleBinding("", args, ctx.Logger) + args.Global = false + + ctx.Err = service.CreateRoleBinding(service.SystemScope, args, ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 89799a37..1ee47aa5 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -38,9 +38,9 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings.POST("", CreateRoleBinding) } - globalRoleBindings := router.Group("global-rolebindings") + systemRoleBindings := router.Group("system-rolebindings") { - globalRoleBindings.POST("", CreateGlobalRoleBinding) + systemRoleBindings.POST("", CreateSystemRoleBinding) } bundles := router.Group("bundles") diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 8cf17649..2ea88dca 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -49,7 +49,7 @@ user_is_admin { all_roles[role] role.name == "admin" - role.namespace == "" + role.namespace == "*" } user_is_project_admin { diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index a7cd5339..21d11f4a 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -32,6 +32,8 @@ type RoleBinding struct { Global bool `json:"global"` } +const SystemScope = "*" + func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { nsRole := ns if rb.Global { -- Gitee From 59a6a3322fa897b28099d5d6c66c30dd9764134d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 15 Oct 2021 10:50:29 +0800 Subject: [PATCH 0042/2647] add list tar interface Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/s3.go | 8 +++ .../core/common/repository/models/workflow.go | 11 ++-- .../repository/mongodb/delivery_artifact.go | 30 ++++++++++ .../aslan/core/system/handler/router.go | 2 + .../aslan/core/system/handler/s3.go | 18 ++++++ .../aslan/core/system/service/s3.go | 60 +++++++++++++++++++ .../workflow/service/workflow/workflow.go | 4 +- 7 files changed, 126 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/s3.go b/pkg/microservice/aslan/core/common/repository/models/s3.go index 23a2be10..51fb7c81 100644 --- a/pkg/microservice/aslan/core/common/repository/models/s3.go +++ b/pkg/microservice/aslan/core/common/repository/models/s3.go @@ -35,6 +35,14 @@ type S3Storage struct { Provider int8 `bson:"provider" json:"provider"` } +type TarInfo struct { + Host string `bson:"host" json:"host"` + Name string `bson:"name" json:"name"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + FileName string `bson:"file_name" json:"file_name"` +} + func (s S3Storage) TableName() string { return "s3storage" } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 0eaa8dc8..ddebfac1 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -339,10 +339,13 @@ type DeployEnv struct { } type ArtifactArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` + Name string `bson:"name" json:"name"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` + TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` + FileName int64 `bson:"file_name,omitempty" json:"file_name,omitempty"` } type VersionArgs struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index 85cfa6f6..ac9c075d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -187,3 +187,33 @@ func (c *DeliveryArtifactColl) Update(args *DeliveryArtifactArgs) error { _, err := c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) return err } + +func (c *DeliveryArtifactColl) ListTars(args *DeliveryArtifactArgs) ([]*models.DeliveryArtifact, error) { + if args == nil { + return nil, errors.New("nil delivery_artifact args") + } + + resp := make([]*models.DeliveryArtifact, 0) + query := bson.M{} + if args.Name != "" { + query["name"] = args.Name + } + + if args.Type != "" { + query["type"] = args.Type + } + + if args.Source != "" { + query["source"] = args.Source + } + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index f85abb7d..67148304 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -80,6 +80,8 @@ func (*Router) Inject(router *gin.RouterGroup) { s3storage.GET("/:id", GetS3Storage) s3storage.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateS3Storage) s3storage.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteS3Storage) + + s3storage.POST("/:id/release/tars/search", ListTars) } //系统清理缓存 diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index 8c2c5732..5cea1ca4 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" @@ -117,3 +118,20 @@ func DeleteS3Storage(c *gin.Context) { ctx.Err = service.DeleteS3Storage(ctx.Username, c.Param("id"), ctx.Logger) } + +type ListTarsOption struct { + Names []string `json:"names"` +} + +func ListTars(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(ListTarsOption) + if err := c.ShouldBindWith(args, binding.JSON); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.ListTars(c.Param("id"), args.Names, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 89f26377..d3da84a0 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -17,7 +17,13 @@ limitations under the License. package service import ( + "strconv" + "strings" + "sync" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/wait" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -95,3 +101,57 @@ func GetS3Storage(id string, logger *zap.SugaredLogger) (*commonmodels.S3Storage return store, nil } + +func ListTars(id string, serviceNames []string, logger *zap.SugaredLogger) ([]*commonmodels.TarInfo, error) { + store, err := commonrepo.NewS3StorageColl().Find(id) + if err != nil { + logger.Errorf("can't find store by id %s", id) + return nil, err + } + var ( + wg wait.Group + mutex sync.RWMutex + tarInfos = make([]*commonmodels.TarInfo, 0) + ) + + for _, serviceName := range serviceNames { + wg.Start(func() { + deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ + Name: serviceName, + Type: string(config.File), + Source: string(config.WorkflowType), + }) + if err != nil { + logger.Errorf("ListTars err:%s", err) + return + } + for _, deliveryArtifact := range deliveryArtifacts { + activities, _, err := commonrepo.NewDeliveryActivityColl().List(&commonrepo.DeliveryActivityArgs{ArtifactID: deliveryArtifact.ID.Hex()}) + if err != nil { + logger.Errorf("deliveryActivity.list err:%s", err) + return + } + urlArr := strings.Split(activities[0].URL, "/") + workflowName := urlArr[len(urlArr)-2] + taskIDStr := urlArr[len(urlArr)-1] + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + logger.Errorf("string convert to int err:%s", err) + return + } + + mutex.Lock() + tarInfos = append(tarInfos, &commonmodels.TarInfo{ + Host: store.Endpoint, + Name: serviceName, + FileName: deliveryArtifact.Image, + WorkflowName: workflowName, + TaskID: int64(taskID), + }) + mutex.Unlock() + } + }) + } + wg.Wait() + return tarInfos, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index ec44581e..13a92e8e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -62,9 +62,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { workflowNames := []string{productName + "-workflow-dev", productName + "-workflow-qa", productName + "-workflow-ops"} // 云主机场景不创建ops工作流 - if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.BasicFacility == "cloud_host" { - workflowNames = []string{productName + "-workflow-dev", productName + "-workflow-qa"} - } else if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { + if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { workflowNames = []string{productName + "-workflow-dev"} } -- Gitee From 10ef4d0b8e4ae442369bd5bb3d72705e6dbd0754 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 15 Oct 2021 11:51:18 +0800 Subject: [PATCH 0043/2647] add registered urls Signed-off-by: lou --- .../core/service/bundle/exemption_urls.go | 1 + .../policy/core/service/bundle/opa_bundle.go | 42 +++++-------------- .../core/service/bundle/rego/authz.rego | 9 ++++ .../rego/test_data/exemptions/data.json | 6 +++ .../bundle/resource_endpoint_mapping.go | 2 +- 5 files changed, 28 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index e3d7b0ad..06b47190 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -8,6 +8,7 @@ type exemptionURLs struct { Global rules `json:"global"` // global urls are only controlled by AuthN, and it is visible for all users Namespaced rules `json:"namespaced"` // global urls are only controlled by AuthN, and it is visible for users under certain projects Public rules `json:"public"` // public urls are not controlled by AuthN and AuthZ + Registered rules `json:"registered"` // registered urls are the entire list of urls which are controlled by AuthZ, which means that if an url is not in this list, it is not controlled by AuthZ } type policyRule struct { diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index bdaed41e..97118273 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -196,36 +196,6 @@ func (o roleBindings) Less(i, j int) bool { return o[i].User < o[j].User } -func generateOPAResourceRoles(roles []*models.Role) *opaRoles { - data := &opaRoles{} - - for _, ro := range roles { - opaRole := &role{Name: ro.Name, Namespace: ro.Namespace} - for _, r := range ro.Rules { - if len(r.Verbs) == 1 && r.Verbs[0] == models.MethodAll { - r.Verbs = AllActions - } - for _, res := range r.Resources { - if mapping, ok := mappings[res]; !ok { - continue - } else { - for _, v := range r.Verbs { - opaRole.Rules = append(opaRole.Rules, mapping[v]...) - } - } - - } - } - - sort.Sort(opaRole.Rules) - data.Roles = append(data.Roles, opaRole) - } - - sort.Sort(data.Roles) - - return data -} - func generateOPARoles(roles []*models.Role) *opaRoles { data := &opaRoles{} @@ -237,7 +207,7 @@ func generateOPARoles(roles []*models.Role) *opaRoles { r.Verbs = AllActions } for _, res := range r.Resources { - if mapping, ok := mappings[res]; !ok { + if mapping, ok := resourceActionMappings[res]; !ok { continue } else { for _, v := range r.Verbs { @@ -341,6 +311,16 @@ func generateOPAExemptionURLs() *exemptionURLs { sort.Sort(data.Public) + for _, resourceMappings := range resourceActionMappings { + for _, rs := range resourceMappings { + for _, r := range rs { + data.Registered = append(data.Registered, r) + } + } + } + + sort.Sort(data.Registered) + return data } diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 2ea88dca..79686a41 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -76,6 +76,15 @@ url_is_exempted { user_projects[_] == project_name } +url_is_exempted { + not url_is_registered +} + +url_is_registered { + data.exemptions.registered[_].method == http_request.method + glob.match(trim(data.exemptions.registered[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) +} + project_name := pn { pn := input.parsed_query.projectName[0] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json index 866aa979..271edede 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json @@ -16,5 +16,11 @@ "method": "POST", "endpoint": "/api/aslan/webhook" } + ], + "registered": [ + { + "method": "GET", + "endpoint": "/api/articles" + } ] } diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index 02eee398..8a44c46b 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -4,7 +4,7 @@ package bundle // to be managed by the Policy Service should register its rules to the Policy Service so that the Policy Service knows // how to make a decision without being aware of the detailed rules. -var mappings = map[string]map[string][]*rule{ +var resourceActionMappings = map[string]map[string][]*rule{ "Workflow": workflowMapping, } -- Gitee From cca0db890fbdf686ece4c8bdfa284838c8166b8a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 15 Oct 2021 14:41:18 +0800 Subject: [PATCH 0044/2647] optimize list tar interface Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/s3.go | 2 +- .../core/common/repository/models/workflow.go | 14 ++- .../aslan/core/system/handler/router.go | 1 - .../aslan/core/system/service/s3.go | 29 +++-- .../aslan/core/workflow/handler/build.go | 8 +- .../service/workflow/pipeline_task.go | 4 +- .../workflow/service/workflow/service_task.go | 7 +- .../service/workflow/workflow_task.go | 100 +++++++++++------- 8 files changed, 111 insertions(+), 54 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/s3.go b/pkg/microservice/aslan/core/common/repository/models/s3.go index 51fb7c81..c0381b27 100644 --- a/pkg/microservice/aslan/core/common/repository/models/s3.go +++ b/pkg/microservice/aslan/core/common/repository/models/s3.go @@ -36,7 +36,7 @@ type S3Storage struct { } type TarInfo struct { - Host string `bson:"host" json:"host"` + URL string `bson:"url" json:"url"` Name string `bson:"name" json:"name"` WorkflowName string `bson:"workflow_name" json:"workflow_name"` TaskID int64 `bson:"task_id" json:"task_id"` diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index ddebfac1..924759d7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -345,7 +345,7 @@ type ArtifactArgs struct { Deploy []DeployEnv `bson:"deploy" json:"deploy"` WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` - FileName int64 `bson:"file_name,omitempty" json:"file_name,omitempty"` + FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` } type VersionArgs struct { @@ -355,6 +355,18 @@ type VersionArgs struct { Labels []string `bson:"labels" json:"labels"` } +type BuildModuleArgs struct { + BuildName string + Target string + ServiceName string + ProductName string + Variables []*KeyVal + ENV *Product + WorkflowName string + TaskID int64 + FileName string +} + func (Workflow) TableName() string { return "workflow" } diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 67148304..b95d5f83 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -80,7 +80,6 @@ func (*Router) Inject(router *gin.RouterGroup) { s3storage.GET("/:id", GetS3Storage) s3storage.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateS3Storage) s3storage.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteS3Storage) - s3storage.POST("/:id/release/tars/search", ListTars) } diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index d3da84a0..62786432 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -103,16 +103,29 @@ func GetS3Storage(id string, logger *zap.SugaredLogger) (*commonmodels.S3Storage } func ListTars(id string, serviceNames []string, logger *zap.SugaredLogger) ([]*commonmodels.TarInfo, error) { - store, err := commonrepo.NewS3StorageColl().Find(id) + var ( + wg wait.Group + mutex sync.RWMutex + tarInfos = make([]*commonmodels.TarInfo, 0) + store *commonmodels.S3Storage + defaultS3 s3.S3 + defaultURL string + err error + ) + + store, err = commonrepo.NewS3StorageColl().Find(id) if err != nil { - logger.Errorf("can't find store by id %s", id) + logger.Errorf("can't find store by id:%s err:%s", id, err) + return nil, err + } + defaultS3 = s3.S3{ + S3Storage: store, + } + defaultURL, err = defaultS3.GetEncryptedURL() + if err != nil { + logger.Errorf("defaultS3 GetEncryptedURL err:%s", err) return nil, err } - var ( - wg wait.Group - mutex sync.RWMutex - tarInfos = make([]*commonmodels.TarInfo, 0) - ) for _, serviceName := range serviceNames { wg.Start(func() { @@ -142,7 +155,7 @@ func ListTars(id string, serviceNames []string, logger *zap.SugaredLogger) ([]*c mutex.Lock() tarInfos = append(tarInfos, &commonmodels.TarInfo{ - Host: store.Endpoint, + URL: defaultURL, Name: serviceName, FileName: deliveryArtifact.Image, WorkflowName: workflowName, diff --git a/pkg/microservice/aslan/core/workflow/handler/build.go b/pkg/microservice/aslan/core/workflow/handler/build.go index c3c2b7f6..79d6e283 100644 --- a/pkg/microservice/aslan/core/workflow/handler/build.go +++ b/pkg/microservice/aslan/core/workflow/handler/build.go @@ -19,6 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -26,8 +27,11 @@ import ( func BuildModuleToSubTasks(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - - resp, err := workflow.BuildModuleToSubTasks(c.Param("name"), "", "", c.Query("productName"), nil, nil, ctx.Logger) + args := &models.BuildModuleArgs{ + BuildName: c.Param("name"), + ProductName: c.Query("productName"), + } + resp, err := workflow.BuildModuleToSubTasks(args, ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index cafcd8e0..7a1fdf80 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -68,7 +68,9 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C // 如果用户使用预定义编译配置, 则从编译模块配置中生成SubTasks if pipeline.BuildModuleVer != "" { - subTasks, err := BuildModuleToSubTasks("", pipeline.Target, "", "", nil, nil, log) + subTasks, err := BuildModuleToSubTasks(&commonmodels.BuildModuleArgs{ + Target: pipeline.Target, + }, log) if err != nil { return nil, e.ErrCreateTask.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index a6aa7d81..b15a1f18 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -215,7 +215,12 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge } stages := make([]*commonmodels.Stage, 0) - subTasks, err := BuildModuleToSubTasks("", args.ServiceName, args.ServiceName, args.ProductName, nil, nil, log) + buildModuleArgs := &commonmodels.BuildModuleArgs{ + Target: args.ServiceName, + ServiceName: args.ServiceName, + ProductName: args.ProductName, + } + subTasks, err := BuildModuleToSubTasks(buildModuleArgs, log) if err != nil { return nil, e.ErrCreateTask.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 0f4d32dc..9b51d0f4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -250,7 +250,7 @@ func EnsureBuildResp(mb *commonmodels.Build) { func ListBuildDetail(name, targets string, log *zap.SugaredLogger) ([]*commonmodels.Build, error) { opt := &commonrepo.BuildListOption{ - Name: name, + Name: name, } if len(strings.TrimSpace(targets)) != 0 { @@ -504,7 +504,14 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, var subTasks []map[string]interface{} var err error if target.JenkinsBuildArgs == nil { - subTasks, err = BuildModuleToSubTasks("", target.Name, target.ServiceName, target.ProductName, target.Envs, env, log) + buildModuleArgs := &commonmodels.BuildModuleArgs{ + Target: target.Name, + ServiceName: target.ServiceName, + ProductName: target.ProductName, + Variables: target.Envs, + ENV: env, + } + subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) } else { subTasks, err = JenkinsBuildModuleToSubTasks(&JenkinsBuildOption{ Target: target.Name, @@ -1362,34 +1369,49 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator stages := make([]*commonmodels.Stage, 0) for _, artifact := range args.Artifact { subTasks := make([]map[string]interface{}, 0) - artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) - if err != nil { - log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) - return nil, e.ErrCreateTask.AddErr(err) - } - subTasks = append(subTasks, artifactSubtask) - if env != nil { - // 生成部署的subtask - for _, deployEnv := range artifact.Deploy { - deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) - if err != nil { - log.Errorf("deploy env to subtask error: %v", err) - return nil, err - } - - if workflow.ResetImage { - resetImageTask, err := resetImageTaskToSubTask(deployEnv, env) + // image artifact deploy + if artifact.Image != "" { + artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) + if err != nil { + log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) + } + subTasks = append(subTasks, artifactSubtask) + if env != nil { + // 生成部署的subtask + for _, deployEnv := range artifact.Deploy { + deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) if err != nil { - log.Errorf("resetImageTaskToSubTask deploy env:[%s] err:%v ", deployEnv.Env, err) + log.Errorf("deploy env to subtask error: %v", err) return nil, err } - if resetImageTask != nil { - subTasks = append(subTasks, resetImageTask) + + if workflow.ResetImage { + resetImageTask, err := resetImageTaskToSubTask(deployEnv, env) + if err != nil { + log.Errorf("resetImageTaskToSubTask deploy env:[%s] err:%v ", deployEnv.Env, err) + return nil, err + } + if resetImageTask != nil { + subTasks = append(subTasks, resetImageTask) + } } - } - subTasks = append(subTasks, deployTask) + subTasks = append(subTasks, deployTask) + } + } + } else if artifact.FileName != "" { + buildModuleArgs := &commonmodels.BuildModuleArgs{ + Target: artifact.Name, + ServiceName: artifact.ServiceName, + ProductName: args.ProductTmplName, + ENV: env, } + subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) + } + if err != nil { + log.Errorf("buildModuleToSubTasks target:[%s] err:%v", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) } // 生成分发的subtask @@ -1547,25 +1569,25 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator return resp, nil } -func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, envs []*commonmodels.KeyVal, pro *commonmodels.Product, log *zap.SugaredLogger) ([]map[string]interface{}, error) { +func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredLogger) ([]map[string]interface{}, error) { var ( subTasks = make([]map[string]interface{}, 0) serviceTmpl *commonmodels.Service ) opt := &commonrepo.BuildListOption{ - Name: moduleName, - ServiceName: serviceName, - ProductName: productName, + Name: args.BuildName, + ServiceName: args.ServiceName, + ProductName: args.ProductName, } - if len(target) > 0 { - opt.Targets = []string{target} + if len(args.Target) > 0 { + opt.Targets = []string{args.Target} } - if pro != nil { + if args.ENV != nil { serviceTmpl, _ = commonservice.GetServiceTemplate( - target, setting.PMDeployType, productName, setting.ProductStatusDeleting, 0, log, + args.Target, setting.PMDeployType, args.ProductName, setting.ProductStatusDeleting, 0, log, ) } @@ -1584,8 +1606,8 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, - ServiceName: target, - Service: serviceName, + ServiceName: args.Target, + Service: args.ServiceName, JobCtx: task.JobCtx{}, ImageID: module.PreBuild.ImageID, BuildOS: module.PreBuild.BuildOS, @@ -1610,7 +1632,7 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, } if serviceTmpl != nil { - build.Namespace = pro.Namespace + build.Namespace = args.ENV.Namespace build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) for _, envConfig := range serviceTmpl.EnvConfigs { @@ -1626,8 +1648,8 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, build.EnvHostInfo = envHost } - if pro != nil { - build.EnvName = pro.EnvName + if args.ENV != nil { + build.EnvName = args.ENV.EnvName } if build.InstallItems == nil { @@ -1674,9 +1696,9 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, build.JobCtx.EnvVars = make([]*commonmodels.KeyVal, 0) } - if len(envs) > 0 { + if len(args.Variables) > 0 { for _, envVar := range build.JobCtx.EnvVars { - for _, overwrite := range envs { + for _, overwrite := range args.Variables { if overwrite.Key == envVar.Key && overwrite.Value != setting.MaskValue { envVar.Value = overwrite.Value envVar.IsCredential = overwrite.IsCredential -- Gitee From 084bca5f04e75f01694b606a82930b4d1dbfda29 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 15 Oct 2021 15:09:50 +0800 Subject: [PATCH 0045/2647] artifact args add url field Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/repository/models/workflow.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 924759d7..3946005e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -346,6 +346,7 @@ type ArtifactArgs struct { WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` + URL string `bson:"url,omitempty" json:"url,omitempty"` } type VersionArgs struct { -- Gitee From b353727535d941299e79e5a7ef382e1f9451dc93 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 15 Oct 2021 21:33:09 +0800 Subject: [PATCH 0046/2647] add policy registration apis Signed-off-by: lou --- .../core/handler/policy_registration.go | 29 ++++++ .../policy/core/handler/router.go | 7 ++ .../policy/core/repository/models/policy.go | 40 +++++++++ .../policy/core/repository/models/role.go | 14 +-- .../repository/mongodb/policy_registration.go | 88 ++++++++++++++++++ .../policy/core/service/bundle/opa_bundle.go | 54 +++++------ .../bundle/resource_endpoint_mapping.go | 70 ++++++++++++--- .../core/service/policy_registration.go | 89 +++++++++++++++++++ pkg/microservice/policy/core/service/role.go | 8 +- 9 files changed, 340 insertions(+), 59 deletions(-) create mode 100644 pkg/microservice/policy/core/handler/policy_registration.go create mode 100644 pkg/microservice/policy/core/repository/models/policy.go create mode 100644 pkg/microservice/policy/core/repository/mongodb/policy_registration.go create mode 100644 pkg/microservice/policy/core/service/policy_registration.go diff --git a/pkg/microservice/policy/core/handler/policy_registration.go b/pkg/microservice/policy/core/handler/policy_registration.go new file mode 100644 index 00000000..c8983244 --- /dev/null +++ b/pkg/microservice/policy/core/handler/policy_registration.go @@ -0,0 +1,29 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/policy/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateOrUpdatePolicyRegistration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + args.Resource = c.Param("resourceName") + + ctx.Err = service.CreateOrUpdatePolicyRegistration(args, ctx.Logger) +} + +func GetPolicyRegistrationDefinitions(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetPolicyRegistrationDefinitions(ctx.Logger) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 1ee47aa5..3fcd58a2 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -47,4 +47,11 @@ func (*Router) Inject(router *gin.RouterGroup) { { bundles.GET("/:name", DownloadBundle) } + + policyRegistrations := router.Group("policies") + { + policyRegistrations.PUT("/:resourceName", CreateOrUpdatePolicyRegistration) + policyRegistrations.GET("", GetPolicyRegistrationDefinitions) + } + } diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go new file mode 100644 index 00000000..6797f8fe --- /dev/null +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +type Policy struct { + Resource string `bson:"resource" json:"resource"` + Alias string `bson:"alias" json:"alias"` + Description string `bson:"description" json:"description"` + Rules []*PolicyRule `bson:"rules" json:"rules"` +} + +type PolicyRule struct { + Action string `bson:"action" json:"action"` + Alias string `bson:"alias" json:"alias"` + Description string `bson:"description" json:"description"` + Rules []*ActionRule `bson:"rules" json:"rules"` +} + +type ActionRule struct { + Method string `bson:"method" json:"method"` + Endpoint string `bson:"endpoint" json:"endpoint"` +} + +func (Policy) TableName() string { + return "policy" +} diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index a49233a1..60279b11 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -24,16 +24,16 @@ const ( // Role is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding. // for a cluster scoped Role, namespace is empty. type Role struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Rules []*PolicyRule `bson:"rules" json:"rules"` - Kind string `bson:"kind" json:"kind"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Rules []*Rule `bson:"rules" json:"rules"` + Kind string `bson:"kind" json:"kind"` } -// PolicyRule holds information that describes a policy rule, but does not contain information +// Rule holds information that describes a policy rule, but does not contain information // about whom the rule applies to. // If Kind is "resource", verbs are resource actions, while resources are resource names -type PolicyRule struct { +type Rule struct { // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. Verbs []string `bson:"verbs" json:"verbs"` @@ -42,5 +42,5 @@ type PolicyRule struct { } func (Role) TableName() string { - return "policy_role" + return "role" } diff --git a/pkg/microservice/policy/core/repository/mongodb/policy_registration.go b/pkg/microservice/policy/core/repository/mongodb/policy_registration.go new file mode 100644 index 00000000..344486d0 --- /dev/null +++ b/pkg/microservice/policy/core/repository/mongodb/policy_registration.go @@ -0,0 +1,88 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type PolicyColl struct { + *mongo.Collection + + coll string +} + +func NewPolicyColl() *PolicyColl { + name := models.Policy{}.TableName() + return &PolicyColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *PolicyColl) GetCollectionName() string { + return c.coll +} + +func (c *PolicyColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"resource": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *PolicyColl) List() ([]*models.Policy, error) { + var res []*models.Policy + + ctx := context.Background() + cursor, err := c.Collection.Find(ctx, bson.M{}) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *PolicyColl) UpdateOrCreate(obj *models.Policy) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + query := bson.M{"resource": obj.Resource} + opts := options.Replace().SetUpsert(true) + _, err := c.ReplaceOne(context.TODO(), query, obj, opts) + + return err +} diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 97118273..2eb7431e 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -41,24 +41,14 @@ const ( ) const ( - MethodView = "VIEW" - MethodGet = http.MethodGet - MethodPost = http.MethodPost - MethodPut = http.MethodPut - MethodPatch = http.MethodPatch - MethodDelete = http.MethodDelete - ActionCreate = "create" - ActionDelete = "delete" - ActionDeleteCollection = "deletecollection" - ActionGet = "get" - ActionList = "list" - ActionPatch = "patch" - ActionUpdate = "update" - ActionWatch = "watch" + MethodGet = http.MethodGet + MethodPost = http.MethodPost + MethodPut = http.MethodPut + MethodPatch = http.MethodPatch + MethodDelete = http.MethodDelete ) var AllMethods = []string{MethodGet, MethodPost, MethodPut, MethodPatch, MethodDelete} -var AllActions = []string{ActionCreate, ActionDelete, ActionDeleteCollection, ActionGet, ActionList, ActionPatch, ActionUpdate, ActionWatch} var cacheFS afero.Fs @@ -196,25 +186,16 @@ func (o roleBindings) Less(i, j int) bool { return o[i].User < o[j].User } -func generateOPARoles(roles []*models.Role) *opaRoles { +func generateOPARoles(roles []*models.Role, policies []*models.Policy) *opaRoles { data := &opaRoles{} + resourceMappings := getResourceActionMappings(policies) for _, ro := range roles { opaRole := &role{Name: ro.Name, Namespace: ro.Namespace} if ro.Kind == models.KindResource { for _, r := range ro.Rules { - if len(r.Verbs) == 1 && r.Verbs[0] == models.MethodAll { - r.Verbs = AllActions - } for _, res := range r.Resources { - if mapping, ok := resourceActionMappings[res]; !ok { - continue - } else { - for _, v := range r.Verbs { - opaRole.Rules = append(opaRole.Rules, mapping[v]...) - } - } - + opaRole.Rules = append(opaRole.Rules, resourceMappings.GetRules(res, r.Verbs)...) } } } else { @@ -270,7 +251,7 @@ func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { return data } -func generateOPAExemptionURLs() *exemptionURLs { +func generateOPAExemptionURLs(policies []*models.Policy) *exemptionURLs { data := &exemptionURLs{} for _, r := range globalURLs { @@ -311,7 +292,8 @@ func generateOPAExemptionURLs() *exemptionURLs { sort.Sort(data.Public) - for _, resourceMappings := range resourceActionMappings { + resourceMappings := getResourceActionMappings(policies) + for _, resourceMappings := range resourceMappings { for _, rs := range resourceMappings { for _, r := range rs { data.Registered = append(data.Registered, r) @@ -339,21 +321,25 @@ func GenerateOPABundle() error { log.Info("Generating OPA bundle") defer log.Info("OPA bundle is generated") - roles, err := mongodb.NewRoleColl().List() + rs, err := mongodb.NewRoleColl().List() if err != nil { log.Errorf("Failed to list roles, err: %s", err) } - bindings, err := mongodb.NewRoleBindingColl().List() + bs, err := mongodb.NewRoleBindingColl().List() if err != nil { log.Errorf("Failed to list roleBindings, err: %s", err) } + ps, err := mongodb.NewPolicyColl().List() + if err != nil { + log.Errorf("Failed to list policies, err: %s", err) + } data := &opaData{ {data: generateOPAManifest(), path: manifestPath}, {data: generateOPAPolicy(), path: policyPath}, - {data: generateOPARoles(roles), path: rolesPath}, - {data: generateOPARoleBindings(bindings), path: rolebindingsPath}, - {data: generateOPAExemptionURLs(), path: exemptionPath}, + {data: generateOPARoles(rs, ps), path: rolesPath}, + {data: generateOPARoleBindings(bs), path: rolebindingsPath}, + {data: generateOPAExemptionURLs(ps), path: exemptionPath}, } return data.save() diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index 8a44c46b..def928aa 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -1,20 +1,62 @@ package bundle -// TODO: Policy Service should not care about the policy details of a certain service, instead, a service which wants -// to be managed by the Policy Service should register its rules to the Policy Service so that the Policy Service knows -// how to make a decision without being aware of the detailed rules. +import ( + "k8s.io/apimachinery/pkg/util/sets" -var resourceActionMappings = map[string]map[string][]*rule{ - "Workflow": workflowMapping, + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" +) + +type resourceActionMappings map[string]map[string][]*rule + +func (m resourceActionMappings) GetRules(resource string, actions []string) []*rule { + mappings, ok := m[resource] + if !ok { + return nil + } + + all := false + if len(actions) == 1 && actions[0] == models.MethodAll { + all = true + } + actionSet := sets.NewString(actions...) + var res []*rule + for action, r := range mappings { + if all || actionSet.Has(action) { + res = append(res, r...) + } + } + + return res } -var workflowMapping = map[string][]*rule{ - ActionCreate: { - {Method: MethodPost, Endpoint: "/api/aslan/workflow/workflow"}, - {Method: MethodPost, Endpoint: "/api/aslan/workflow/v2/pipelines"}, - }, - ActionDelete: { - {Method: MethodPost, Endpoint: "/api/aslan/workflow/workflow/?*"}, - {Method: MethodPost, Endpoint: "/api/aslan/workflow/v2/pipelines/?*"}, - }, +//var resourceActionMappings = map[string]map[string][]*rule{ +// "Workflow": workflowMapping, +//} +// +//var workflowMapping = map[string][]*rule{ +// ActionCreate: { +// {Method: MethodPost, Endpoint: "/api/aslan/workflow/workflow"}, +// {Method: MethodPost, Endpoint: "/api/aslan/workflow/v2/pipelines"}, +// }, +// ActionDelete: { +// {Method: MethodPost, Endpoint: "/api/aslan/workflow/workflow/?*"}, +// {Method: MethodPost, Endpoint: "/api/aslan/workflow/v2/pipelines/?*"}, +// }, +//} + +func getResourceActionMappings(policies []*models.Policy) resourceActionMappings { + data := make(resourceActionMappings) + for _, p := range policies { + if _, ok := data[p.Resource]; !ok { + data[p.Resource] = make(map[string][]*rule) + } + + for _, r := range p.Rules { + for _, ar := range r.Rules { + data[p.Resource][r.Action] = append(data[p.Resource][r.Action], &rule{Method: ar.Method, Endpoint: ar.Endpoint}) + } + } + } + + return data } diff --git a/pkg/microservice/policy/core/service/policy_registration.go b/pkg/microservice/policy/core/service/policy_registration.go new file mode 100644 index 00000000..f940b115 --- /dev/null +++ b/pkg/microservice/policy/core/service/policy_registration.go @@ -0,0 +1,89 @@ +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" +) + +type Policy struct { + Resource string `json:"resource"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*PolicyRule `json:"rules"` +} + +type PolicyRule struct { + Action string `json:"action"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*ActionRule `json:"rules"` +} + +type ActionRule struct { + Method string `json:"method"` + Endpoint string `json:"endpoint"` +} + +type PolicyDefinition struct { + Resource string `json:"resource"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*PolicyRuleDefinition `json:"rules"` +} + +type PolicyRuleDefinition struct { + Action string `json:"action"` + Alias string `json:"alias"` + Description string `json:"description"` +} + +func CreateOrUpdatePolicyRegistration(p *Policy, _ *zap.SugaredLogger) error { + obj := &models.Policy{ + Resource: p.Resource, + Alias: p.Alias, + Description: p.Description, + } + + for _, r := range p.Rules { + rule := &models.PolicyRule{ + Action: r.Action, + Alias: r.Alias, + Description: r.Description, + } + for _, ar := range r.Rules { + rule.Rules = append(rule.Rules, &models.ActionRule{Method: ar.Method, Endpoint: ar.Endpoint}) + } + obj.Rules = append(obj.Rules, rule) + } + + return mongodb.NewPolicyColl().UpdateOrCreate(obj) +} + +func GetPolicyRegistrationDefinitions(_ *zap.SugaredLogger) ([]*PolicyDefinition, error) { + policies, err := mongodb.NewPolicyColl().List() + if err != nil { + return nil, err + } + + var res []*PolicyDefinition + for _, p := range policies { + pd := &PolicyDefinition{ + Resource: p.Resource, + Alias: p.Alias, + Description: p.Description, + } + for _, r := range p.Rules { + pd.Rules = append(pd.Rules, &PolicyRuleDefinition{ + Action: r.Action, + Alias: r.Alias, + Description: r.Description, + }) + } + + res = append(res, pd) + } + + return res, nil +} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 2e330ebe..fb7b69ad 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -24,11 +24,11 @@ import ( ) type Role struct { - Name string `json:"name"` - Rules []*PolicyRule `json:"rules"` + Name string `json:"name"` + Rules []*Rule `json:"rules"` } -type PolicyRule struct { +type Rule struct { Verbs []string `json:"verbs"` Resources []string `json:"resources"` } @@ -40,7 +40,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { } for _, r := range role.Rules { - obj.Rules = append(obj.Rules, &models.PolicyRule{ + obj.Rules = append(obj.Rules, &models.Rule{ Verbs: r.Verbs, Resources: r.Resources, }) -- Gitee From 8d4fd87a38583c957c1f3b3b70239ffebc8e158c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 11:23:01 +0800 Subject: [PATCH 0047/2647] download artifact file Signed-off-by: liu deyi --- .../common/repository/models/task/build.go | 10 ++++- .../core/common/repository/models/workflow.go | 3 +- .../service/workflow/workflow_task.go | 31 ++++++++++---- .../reaper/core/service/meta/types.go | 18 +++++--- .../reaper/core/service/reaper/script.go | 41 +++++++++++++++++++ .../core/service/taskplugin/build.go | 10 +++++ .../warpdrive/core/service/taskplugin/job.go | 8 ++++ .../warpdrive/core/service/types/reaper.go | 18 +++++--- .../core/service/types/task/build.go | 10 ++++- .../core/service/types/task/model.go | 32 +++++++-------- 10 files changed, 141 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 47f18074..02430c37 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -56,7 +56,15 @@ type Build struct { BuildStatus *BuildStatus `bson:"build_status,omitempty" json:"build_status,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` // Get the host bound to the environment of the cloud host service configuration - EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` +} + +type ArtifactInfo struct { + URL string `bson:"url" json:"url"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + FileName string `bson:"file_name" json:"file_name"` } //type Item struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3946005e..b6dce086 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -362,10 +362,11 @@ type BuildModuleArgs struct { ServiceName string ProductName string Variables []*KeyVal - ENV *Product + Env *Product WorkflowName string TaskID int64 FileName string + URL string } func (Workflow) TableName() string { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 9b51d0f4..175b08e2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -509,7 +509,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, ServiceName: target.ServiceName, ProductName: target.ProductName, Variables: target.Envs, - ENV: env, + Env: env, } subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) } else { @@ -1402,10 +1402,14 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } } else if artifact.FileName != "" { buildModuleArgs := &commonmodels.BuildModuleArgs{ - Target: artifact.Name, - ServiceName: artifact.ServiceName, - ProductName: args.ProductTmplName, - ENV: env, + Target: artifact.Name, + ServiceName: artifact.ServiceName, + ProductName: args.ProductTmplName, + Env: env, + URL: artifact.URL, + WorkflowName: artifact.WorkflowName, + TaskID: artifact.TaskID, + FileName: artifact.FileName, } subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) } @@ -1585,7 +1589,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL opt.Targets = []string{args.Target} } - if args.ENV != nil { + if args.Env != nil { serviceTmpl, _ = commonservice.GetServiceTemplate( args.Target, setting.PMDeployType, args.ProductName, setting.ProductStatusDeleting, 0, log, ) @@ -1632,7 +1636,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } if serviceTmpl != nil { - build.Namespace = args.ENV.Namespace + build.Namespace = args.Env.Namespace build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) for _, envConfig := range serviceTmpl.EnvConfigs { @@ -1648,8 +1652,8 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.EnvHostInfo = envHost } - if args.ENV != nil { - build.EnvName = args.ENV.EnvName + if args.Env != nil { + build.EnvName = args.Env.EnvName } if build.InstallItems == nil { @@ -1732,6 +1736,15 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Caches = module.Caches + if args.FileName != "" { + build.ArtifactInfo = &task.ArtifactInfo{ + URL: args.URL, + WorkflowName: args.WorkflowName, + TaskID: args.TaskID, + FileName: args.FileName, + } + } + bst, err := build.ToSubTask() if err != nil { return subTasks, e.ErrConvertSubTasks.AddErr(err) diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 7dfd4337..3151b477 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -106,11 +106,19 @@ type Context struct { // IgnoreCache ignore docker build cache [runtime] IgnoreCache bool `yaml:"ignore_cache"` - StorageEndpoint string `yaml:"storage_endpoint"` - StorageAK string `yaml:"storage_ak"` - StorageSK string `yaml:"storage_sk"` - StorageBucket string `yaml:"storage_bucket"` - StorageProvider int `yaml:"storage_provider"` + StorageEndpoint string `yaml:"storage_endpoint"` + StorageAK string `yaml:"storage_ak"` + StorageSK string `yaml:"storage_sk"` + StorageBucket string `yaml:"storage_bucket"` + StorageProvider int `yaml:"storage_provider"` + ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` +} + +type ArtifactInfo struct { + URL string `yaml:"url"` + WorkflowName string `yaml:"workflow_name"` + TaskID int64 `yaml:"task_id"` + FileName string `yaml:"file_name"` } // DockerBuildCtx ... diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index bbe55ba3..c6736482 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -199,6 +199,10 @@ func (r *Reaper) createReadme(file string) error { } func (r *Reaper) runScripts() error { + // artifact deploy + if r.Ctx.ArtifactInfo != nil { + return r.downloadArtifactFile() + } if len(r.Ctx.Scripts) == 0 { return nil } @@ -373,3 +377,40 @@ func (r *Reaper) RunPMDeployScripts() error { return cmd.Run() } + +func (r *Reaper) downloadArtifactFile() error { + var err error + var store *s3.S3 + if store, err = s3.NewS3StorageFromEncryptedURI(r.Ctx.ArtifactInfo.URL); err != nil { + log.Errorf("Archive failed to create s3 storage %s", r.Ctx.ArtifactInfo.URL) + return err + } + if store.Subfolder != "" { + store.Subfolder = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, r.Ctx.ArtifactInfo.WorkflowName, r.Ctx.ArtifactInfo.TaskID, "file") + } else { + store.Subfolder = fmt.Sprintf("%s/%d/%s", r.Ctx.ArtifactInfo.WorkflowName, r.Ctx.ArtifactInfo.TaskID, "file") + } + + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + log.Errorf("s3 create client err:%s", err) + return err + } + files, err := s3client.ListFiles(store.Bucket, store.GetObjectPath(r.Ctx.ArtifactInfo.FileName), false) + if err != nil { + log.Errorf("s3 list file err:%s", err) + return err + } + if len(files) > 0 { + objectKey := store.GetObjectPath(files[0]) + if err = s3client.Download(store.Bucket, objectKey, fmt.Sprintf("%s/%s", r.ActiveWorkspace, r.Ctx.ArtifactInfo.FileName)); err != nil { + log.Errorf("s3 download file err:%s", err) + return err + } + } + return nil +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index e9f614f9..cd0afdb3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -135,6 +135,16 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) } + // ARTIFACT + var artifactKeysVar *task.KeyVal + if p.Task.ArtifactInfo != nil { + pipelineTask.ArtifactInfo = p.Task.ArtifactInfo + artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.ArtifactInfo.FileName), IsCredential: false} + } else if p.Task.JobCtx.FileArchiveCtx != nil { + artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s/%s", pipelineCtx.Workspace, p.Task.JobCtx.FileArchiveCtx.FileLocation, p.Task.JobCtx.FileArchiveCtx.FileName), IsCredential: false} + } + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace for _, repo := range p.Task.JobCtx.Builds { repoName := strings.Replace(repo.RepoName, "-", "_", -1) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index d0ddc2ad..8c566d90 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -309,6 +309,14 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ctx.StorageSK = pipelineTask.ConfigPayload.S3Storage.Sk ctx.StorageBucket = pipelineTask.ConfigPayload.S3Storage.Bucket ctx.StorageProvider = pipelineTask.ConfigPayload.S3Storage.Provider + if pipelineTask.ArtifactInfo != nil { + ctx.ArtifactInfo = &types.ArtifactInfo{ + URL: pipelineTask.ArtifactInfo.URL, + WorkflowName: pipelineTask.ArtifactInfo.WorkflowName, + TaskID: pipelineTask.ArtifactInfo.TaskID, + FileName: pipelineTask.ArtifactInfo.FileName, + } + } return ctx } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 8ac5c01c..df689bea 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -105,11 +105,19 @@ type Context struct { // IgnoreCache ignore docker build cache [runtime] IgnoreCache bool `yaml:"ignore_cache"` - StorageEndpoint string `yaml:"storage_endpoint"` - StorageAK string `yaml:"storage_ak"` - StorageSK string `yaml:"storage_sk"` - StorageBucket string `yaml:"storage_bucket"` - StorageProvider int8 `yaml:"storage_provider"` + StorageEndpoint string `yaml:"storage_endpoint"` + StorageAK string `yaml:"storage_ak"` + StorageSK string `yaml:"storage_sk"` + StorageBucket string `yaml:"storage_bucket"` + StorageProvider int8 `yaml:"storage_provider"` + ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` +} + +type ArtifactInfo struct { + URL string `yaml:"url"` + WorkflowName string `yaml:"workflow_name"` + TaskID int64 `yaml:"task_id"` + FileName string `yaml:"file_name"` } // Proxy 翻墙配置信息 diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index eed15c97..fb3e0e68 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -54,7 +54,15 @@ type Build struct { BuildStatus *BuildStatus `bson:"build_status,omitempty" json:"build_status,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` // Get the host bound to the environment of the cloud host service configuration - EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` +} + +type ArtifactInfo struct { + URL string `bson:"url" json:"url"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + FileName string `bson:"file_name" json:"file_name"` } type Item struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 8fc7400a..2dd1cd95 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -65,25 +65,21 @@ type Task struct { // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ConfigPayload 系统配置信息 - ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - // OrgID 单租户ID - OrgID int `bson:"org_id,omitempty" json:"org_id,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + OrgID int `bson:"org_id,omitempty" json:"org_id,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - - RwLock sync.Mutex `bson:"-" json:"-"` - - ResetImage bool `json:"resetImage" bson:"resetImage"` - - TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `json:"resetImage" bson:"resetImage"` + TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info" json:"artifact_info"` } type RenderInfo struct { -- Gitee From ce42ac24355488504e5a29624bde92ef162ba831 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 14:39:11 +0800 Subject: [PATCH 0048/2647] modify workspace Signed-off-by: liu deyi --- .../core/workflow/service/workflow/workflow_task.go | 9 ++++----- .../warpdrive/core/service/taskplugin/build.go | 8 ++++++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 175b08e2..810cd024 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1396,7 +1396,6 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator subTasks = append(subTasks, resetImageTask) } } - subTasks = append(subTasks, deployTask) } } @@ -1412,10 +1411,10 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator FileName: artifact.FileName, } subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) - } - if err != nil { - log.Errorf("buildModuleToSubTasks target:[%s] err:%v", artifact.Name, err) - return nil, e.ErrCreateTask.AddErr(err) + if err != nil { + log.Errorf("buildModuleToSubTasks target:[%s] err:%s", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) + } } // 生成分发的subtask diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index cd0afdb3..f7585412 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -137,11 +137,15 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe // ARTIFACT var artifactKeysVar *task.KeyVal + var workspace = "/workspace" + if pipelineTask.ConfigPayload.ClassicBuild { + workspace = pipelineCtx.Workspace + } if p.Task.ArtifactInfo != nil { pipelineTask.ArtifactInfo = p.Task.ArtifactInfo - artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.ArtifactInfo.FileName), IsCredential: false} + artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s", workspace, p.Task.ArtifactInfo.FileName), IsCredential: false} } else if p.Task.JobCtx.FileArchiveCtx != nil { - artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s/%s", pipelineCtx.Workspace, p.Task.JobCtx.FileArchiveCtx.FileLocation, p.Task.JobCtx.FileArchiveCtx.FileName), IsCredential: false} + artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s/%s", workspace, p.Task.JobCtx.FileArchiveCtx.FileLocation, p.Task.JobCtx.FileArchiveCtx.FileName), IsCredential: false} } p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) -- Gitee From 30e303c67f8fd57d4744f8f15b80b2c07efdf2a5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 15:06:01 +0800 Subject: [PATCH 0049/2647] optimize create artifact logic Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 810cd024..7206c28f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1469,6 +1469,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator SubTasks: subTasks, ServiceName: artifact.Name, ConfigPayload: configPayload, + TaskArgs: workFlowArgsToTaskArgs(artifact.Name, args), WorkflowArgs: args, } sort.Sort(ByTaskKind(task.SubTasks)) -- Gitee From b2e64f5768de66a245ff6993dd2a86dc45a3c55e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 15:16:46 +0800 Subject: [PATCH 0050/2647] optimize create artifact logic Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/reaper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index e720fb2c..59adbf1f 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -390,12 +390,12 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } // 将上面生成的统计结果文件上传到S3 if err = r.archiveTestFiles(); err != nil { - log.Errorf("archiveFiles err %v", err) + log.Errorf("archiveTestFiles err %v", err) return err } // 将HTML测试报告上传到S3 if err = r.archiveHTMLTestReportFile(); err != nil { - log.Errorf("archiveFiles err %v", err) + log.Errorf("archiveHTMLTestReportFile err %v", err) return err } -- Gitee From 48ce2dc3bb8669fd89757f9200db856f70c8eb6c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 15:45:03 +0800 Subject: [PATCH 0051/2647] add artifact sort Signed-off-by: liu deyi --- .../core/common/repository/mongodb/delivery_artifact.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index ac9c075d..b52df632 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -207,7 +207,10 @@ func (c *DeliveryArtifactColl) ListTars(args *DeliveryArtifactArgs) ([]*models.D query["source"] = args.Source } - cursor, err := c.Collection.Find(context.TODO(), query) + opt := options.Find(). + SetSort(bson.D{{"created_time", -1}}) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) if err != nil { return nil, err } -- Gitee From 11e31a36c52b07657d5135919fbc6daf017fd743 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 15:52:35 +0800 Subject: [PATCH 0052/2647] modify artifact deploy logic Signed-off-by: liu deyi --- .../reaper/core/service/reaper/reaper.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 59adbf1f..681e173e 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -403,14 +403,16 @@ func (r *Reaper) AfterExec(upStreamErr error) error { // should archive file first, since compress cache will clean the workspace if upStreamErr == nil { - if err = r.archiveS3Files(); err != nil { - log.Errorf("archiveFiles err %v", err) - return err - } - // 运行构建后置脚本 - if err = r.RunPostScripts(); err != nil { - log.Errorf("RunPostScripts err %v", err) - return err + if r.Ctx.ArtifactInfo == nil { + if err = r.archiveS3Files(); err != nil { + log.Errorf("archiveFiles err %v", err) + return err + } + // 运行构建后置脚本 + if err = r.RunPostScripts(); err != nil { + log.Errorf("RunPostScripts err %v", err) + return err + } } // 运行物理机部署脚本 -- Gitee From 543539ca3276571c04970b971e6207ec0a740d57 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 16:22:16 +0800 Subject: [PATCH 0053/2647] modify subtask logic Signed-off-by: liu deyi --- .../workflow/service/workflow/workflow_task.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 7206c28f..8df5e19a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1369,14 +1369,14 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator stages := make([]*commonmodels.Stage, 0) for _, artifact := range args.Artifact { subTasks := make([]map[string]interface{}, 0) + artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) + if err != nil { + log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) + } + subTasks = append(subTasks, artifactSubtask) // image artifact deploy if artifact.Image != "" { - artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) - if err != nil { - log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) - return nil, e.ErrCreateTask.AddErr(err) - } - subTasks = append(subTasks, artifactSubtask) if env != nil { // 生成部署的subtask for _, deployEnv := range artifact.Deploy { @@ -1410,11 +1410,12 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator TaskID: artifact.TaskID, FileName: artifact.FileName, } - subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) + buildSubtasks, err := BuildModuleToSubTasks(buildModuleArgs, log) if err != nil { log.Errorf("buildModuleToSubTasks target:[%s] err:%s", artifact.Name, err) return nil, e.ErrCreateTask.AddErr(err) } + subTasks = append(subTasks, buildSubtasks...) } // 生成分发的subtask -- Gitee From 8013e6deb2c101ad9484f64e3fc550cc7a4b1993 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 17:45:01 +0800 Subject: [PATCH 0054/2647] add artifact deploy stage Signed-off-by: liu deyi --- pkg/microservice/aslan/config/consts.go | 1 + .../service/workflow/workflow_task.go | 148 ++++++- pkg/microservice/warpdrive/config/const.go | 1 + .../service/taskcontroller/task_helper.go | 1 + .../service/taskplugin/artifact_deploy.go | 369 ++++++++++++++++++ .../core/service/taskplugin/build.go | 18 +- 6 files changed, 519 insertions(+), 19 deletions(-) create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index f6a1b9df..36376093 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -119,6 +119,7 @@ const ( TaskBuild TaskType = "buildv2" TaskJenkinsBuild TaskType = "jenkins_build" TaskArtifact TaskType = "artifact" + TaskArtifactDeploy TaskType = "artifact_deploy" TaskDeploy TaskType = "deploy" TaskTestingV2 TaskType = "testingv2" TaskDistributeToS3 TaskType = "distribute2kodo" diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 8df5e19a..0d72f7d7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1369,14 +1369,14 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator stages := make([]*commonmodels.Stage, 0) for _, artifact := range args.Artifact { subTasks := make([]map[string]interface{}, 0) - artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) - if err != nil { - log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) - return nil, e.ErrCreateTask.AddErr(err) - } - subTasks = append(subTasks, artifactSubtask) // image artifact deploy if artifact.Image != "" { + artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) + if err != nil { + log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) + } + subTasks = append(subTasks, artifactSubtask) if env != nil { // 生成部署的subtask for _, deployEnv := range artifact.Deploy { @@ -1410,7 +1410,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator TaskID: artifact.TaskID, FileName: artifact.FileName, } - buildSubtasks, err := BuildModuleToSubTasks(buildModuleArgs, log) + buildSubtasks, err := ArtifactDeployToSubTasks(buildModuleArgs, log) if err != nil { log.Errorf("buildModuleToSubTasks target:[%s] err:%s", artifact.Name, err) return nil, e.ErrCreateTask.AddErr(err) @@ -1737,6 +1737,138 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Caches = module.Caches + bst, err := build.ToSubTask() + if err != nil { + return subTasks, e.ErrConvertSubTasks.AddErr(err) + } + subTasks = append(subTasks, bst) + } + + return subTasks, nil +} + +func ArtifactDeployToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredLogger) ([]map[string]interface{}, error) { + var ( + subTasks = make([]map[string]interface{}, 0) + ) + + opt := &commonrepo.BuildListOption{ + Name: args.BuildName, + ServiceName: args.ServiceName, + ProductName: args.ProductName, + } + + if len(args.Target) > 0 { + opt.Targets = []string{args.Target} + } + + modules, err := commonrepo.NewBuildColl().List(opt) + if err != nil { + return nil, e.ErrConvertSubTasks.AddErr(err) + } + + for _, module := range modules { + build := &task.Build{ + TaskType: config.TaskArtifactDeploy, + Enabled: true, + InstallItems: module.PreBuild.Installs, + ServiceName: args.Target, + Service: args.ServiceName, + JobCtx: task.JobCtx{}, + ImageID: module.PreBuild.ImageID, + BuildOS: module.PreBuild.BuildOS, + ImageFrom: module.PreBuild.ImageFrom, + ResReq: module.PreBuild.ResReq, + Timeout: module.Timeout, + } + + // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 + if module.PreBuild.ImageID != "" { + basicImage, err := commonrepo.NewBasicImageColl().Find(module.PreBuild.ImageID) + if err != nil { + log.Errorf("BasicImage.Find failed, id:%s, err:%v", module.PreBuild.ImageID, err) + } else { + build.BuildOS = basicImage.Value + } + } + + if build.ImageFrom == "" { + build.ImageFrom = commonmodels.ImageFromKoderover + } + + if args.Env != nil { + build.EnvName = args.Env.EnvName + } + + if build.InstallItems == nil { + build.InstallItems = make([]*commonmodels.Item, 0) + } + + build.JobCtx.Builds = module.Repos + if len(build.JobCtx.Builds) == 0 { + build.JobCtx.Builds = make([]*types.Repository, 0) + } + + build.JobCtx.BuildSteps = []*task.BuildStep{} + if module.Scripts != "" { + build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: module.Scripts}) + } + + if module.PMDeployScripts != "" && build.ServiceType == setting.PMDeployType { + build.JobCtx.PMDeployScripts = module.PMDeployScripts + } + + if len(module.SSHs) > 0 && build.ServiceType == setting.PMDeployType { + privateKeys := make([]*task.SSH, 0) + for _, sshID := range module.SSHs { + //私钥信息可能被更新,而构建中存储的信息是旧的,需要根据id获取最新的私钥信息 + latestKeyInfo, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ID: sshID}) + if err != nil || latestKeyInfo == nil { + log.Errorf("PrivateKey.Find failed, id:%s, err:%v", sshID, err) + continue + } + ssh := new(task.SSH) + ssh.Name = latestKeyInfo.Name + ssh.UserName = latestKeyInfo.UserName + ssh.IP = latestKeyInfo.IP + ssh.PrivateKey = latestKeyInfo.PrivateKey + + privateKeys = append(privateKeys, ssh) + } + build.JobCtx.SSHs = privateKeys + } + + build.JobCtx.EnvVars = module.PreBuild.Envs + + if len(module.PreBuild.Envs) == 0 { + build.JobCtx.EnvVars = make([]*commonmodels.KeyVal, 0) + } + + if len(args.Variables) > 0 { + for _, envVar := range build.JobCtx.EnvVars { + for _, overwrite := range args.Variables { + if overwrite.Key == envVar.Key && overwrite.Value != setting.MaskValue { + envVar.Value = overwrite.Value + envVar.IsCredential = overwrite.IsCredential + break + } + } + } + } + + build.JobCtx.UploadPkg = module.PreBuild.UploadPkg + build.JobCtx.CleanWorkspace = module.PreBuild.CleanWorkspace + build.JobCtx.EnableProxy = module.PreBuild.EnableProxy + + if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { + build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ + WorkDir: module.PostBuild.DockerBuild.WorkDir, + DockerFile: module.PostBuild.DockerBuild.DockerFile, + BuildArgs: module.PostBuild.DockerBuild.BuildArgs, + } + } + build.JobCtx.Caches = module.Caches + if args.FileName != "" { build.ArtifactInfo = &task.ArtifactInfo{ URL: args.URL, @@ -1778,7 +1910,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e switch pre.TaskType { - case config.TaskBuild: + case config.TaskBuild, config.TaskArtifactDeploy: t, err := base.ToBuildTask(subTask) fmtBuildsTask(t, log) if err != nil { diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 79acdadf..6e4fcaca 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -21,6 +21,7 @@ type TaskType string const ( TaskPipeline TaskType = "pipeline" TaskBuild TaskType = "buildv2" + TaskArtifactDeploy TaskType = "artifact_deploy" TaskJenkinsBuild TaskType = "jenkins_build" TaskArtifact TaskType = "artifact" TaskDeploy TaskType = "deploy" diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 867b82e3..b0a13595 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -540,6 +540,7 @@ func initTaskPlugins(execHandler *ExecHandler) { pluginConf := map[config.TaskType]plugins.Initiator{ config.TaskJira: plugins.InitializeJiraTaskPlugin, config.TaskBuild: plugins.InitializeBuildTaskPlugin, + config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, config.TaskDeploy: plugins.InitializeDeployTaskPlugin, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go new file mode 100644 index 00000000..c3a06aaf --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -0,0 +1,369 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +const ( + // ArtifactDeployTaskV2Timeout ... + ArtifactDeployTaskV2Timeout = 60 * 60 * 1 // 60 minutes +) + +// InitializeArtifactTaskPlugin to initialize build task plugin, and return reference +func InitializeArtifactTaskPlugin(taskType config.TaskType) TaskPlugin { + return &ArtifactDeployTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +// BuildTaskPlugin is Plugin, name should be compatible with task type +type ArtifactDeployTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.Build + Log *zap.SugaredLogger + + ack func() +} + +func (p *ArtifactDeployTaskPlugin) SetAckFunc(ack func()) { + p.ack = ack +} + +// Init ... +func (p *ArtifactDeployTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl + p.FileName = filename +} + +func (p *ArtifactDeployTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *ArtifactDeployTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *ArtifactDeployTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +// TaskTimeout ... +func (p *ArtifactDeployTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = ArtifactDeployTaskV2Timeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *ArtifactDeployTaskPlugin) SetBuildStatusCompleted(status config.Status) { + p.Task.BuildStatus.Status = status + p.Task.BuildStatus.EndTime = time.Now().Unix() +} + +func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + envName := pipelineTask.WorkflowArgs.Namespace + envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameVar) + + taskIDVar := &task.KeyVal{Key: "TASK_ID", Value: strconv.FormatInt(pipelineTask.TaskID, 10), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) + + privateKeys := sets.String{} + for _, privateKey := range pipelineTask.ConfigPayload.PrivateKeys { + privateKeys.Insert(privateKey.Name) + } + + privateKeysVar := &task.KeyVal{Key: "AGENTS", Value: strings.Join(privateKeys.List(), ","), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, privateKeysVar) + + // env host ips + for envName, HostIPs := range p.Task.EnvHostInfo { + envHostKeysVar := &task.KeyVal{Key: envName + "_HOST_IPs", Value: strings.Join(HostIPs, ","), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) + } + + // ARTIFACT + if p.Task.ArtifactInfo != nil { + var workspace = "/workspace" + if pipelineTask.ConfigPayload.ClassicBuild { + workspace = pipelineCtx.Workspace + } + pipelineTask.ArtifactInfo = p.Task.ArtifactInfo + artifactKeysVar := &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s", workspace, p.Task.ArtifactInfo.FileName), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) + } + + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + for _, repo := range p.Task.JobCtx.Builds { + repoName := strings.Replace(repo.RepoName, "-", "_", -1) + if len(repo.Branch) > 0 { + branchVar := &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, branchVar) + } + + if len(repo.Tag) > 0 { + tagVar := &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, tagVar) + } + + if repo.PR > 0 { + prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) + } + } + + jobCtx := JobCtxBuilder{ + JobName: p.JobName, + PipelineCtx: pipelineCtx, + ArchiveFile: p.Task.JobCtx.PackageFile, + JobCtx: p.Task.JobCtx, + Installs: p.Task.InstallCtx, + } + + if p.Task.BuildStatus == nil { + p.Task.BuildStatus = &task.BuildStatus{} + } + + p.Task.BuildStatus.Status = config.StatusRunning + p.Task.BuildStatus.StartTime = time.Now().Unix() + p.ack() + + jobCtxBytes, err := yaml.Marshal(jobCtx.BuildReaperContext(pipelineTask, serviceName)) + if err != nil { + msg := fmt.Sprintf("cannot reaper.Context data: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + if err := createJobConfigMap( + p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient); err != nil { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.Log.Infof("succeed to create cm for build job %s", p.JobName) + + jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) + if p.Task.ImageFrom == setting.ImageFromCustom { + jobImage = p.Task.BuildOS + } + + //Resource request default value is LOW + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + if err != nil { + msg := fmt.Sprintf("create build job context error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + job.Namespace = p.KubeNamespace + + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("delete build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + msg := fmt.Sprintf("create secret error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + if err := updater.CreateJob(job, p.kubeClient); err != nil { + msg := fmt.Sprintf("create build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.Log.Infof("succeed to create build job %s", p.JobName) +} + +// Wait ... +func (p *ArtifactDeployTaskPlugin) Wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + p.SetBuildStatusCompleted(status) + + if status == config.StatusPassed { + if p.Task.DockerBuildStatus == nil { + p.Task.DockerBuildStatus = &task.DockerBuildStatus{} + } + + p.Task.DockerBuildStatus.StartTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusRunning + p.ack() + + select { + case <-ctx.Done(): + p.Task.DockerBuildStatus.EndTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusCancelled + p.Task.TaskStatus = config.StatusCancelled + p.ack() + return + case <-time.After(time.Duration(rand.Int()%2) * time.Second): + p.Task.DockerBuildStatus.EndTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusPassed + } + } + + p.SetStatus(status) +} + +// Complete ... +func (p *ArtifactDeployTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + // 清理用户取消和超时的任务 + defer func() { + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return + } + }() + + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + if err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + return + } + + p.Task.LogFile = p.FileName +} + +// SetTask ... +func (p *ArtifactDeployTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToBuildTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *ArtifactDeployTaskPlugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *ArtifactDeployTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *ArtifactDeployTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *ArtifactDeployTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *ArtifactDeployTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *ArtifactDeployTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *ArtifactDeployTaskPlugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index f7585412..4da6b071 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -136,18 +136,14 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } // ARTIFACT - var artifactKeysVar *task.KeyVal - var workspace = "/workspace" - if pipelineTask.ConfigPayload.ClassicBuild { - workspace = pipelineCtx.Workspace - } - if p.Task.ArtifactInfo != nil { - pipelineTask.ArtifactInfo = p.Task.ArtifactInfo - artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s", workspace, p.Task.ArtifactInfo.FileName), IsCredential: false} - } else if p.Task.JobCtx.FileArchiveCtx != nil { - artifactKeysVar = &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s/%s", workspace, p.Task.JobCtx.FileArchiveCtx.FileLocation, p.Task.JobCtx.FileArchiveCtx.FileName), IsCredential: false} + if p.Task.JobCtx.FileArchiveCtx != nil { + var workspace = "/workspace" + if pipelineTask.ConfigPayload.ClassicBuild { + workspace = pipelineCtx.Workspace + } + artifactKeysVar := &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s/%s", workspace, p.Task.JobCtx.FileArchiveCtx.FileLocation, p.Task.JobCtx.FileArchiveCtx.FileName), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) } - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace for _, repo := range p.Task.JobCtx.Builds { -- Gitee From 3e0d74562a9f3bbc6f3a29c1ef2785b55b5e3d48 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 18:00:16 +0800 Subject: [PATCH 0055/2647] list env Signed-off-by: mouuii --- .../core/environment/handler/environment.go | 11 +++++++ .../aslan/core/environment/handler/router.go | 2 +- .../core/environment/service/environment.go | 32 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 18a493e9..291faed0 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -75,6 +75,17 @@ func ListProducts(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), c.Query("envType"), ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) } +func ListProductsV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.ListProductsV3(c.Query("productName"), ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) +} + func AutoCreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index b0056e6d..162038f5 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -94,7 +94,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- environments := router.Group("environments") { - environments.GET("", ListProducts) + environments.GET("", ListProductsV3) environments.POST("/:productName/auto", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID}, permission.ParamType), AutoCreateProduct) environments.PUT("/:productName/autoUpdate", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, AutoUpdateProduct) environments.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProduct) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7931ff5e..7fb6a716 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -148,6 +148,38 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar return nil } +func ListProductsV3(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam}) + if err != nil { + log.Errorf("[%s] Collections.Product.List error: %v", userName, err) + return resp, e.ErrListEnvs.AddDesc(err.Error()) + } + + for _, prod := range products { + product := &ProductResp{ + ID: prod.ID.Hex(), + ProductName: prod.ProductName, + EnvName: prod.EnvName, + Namespace: prod.Namespace, + Vars: prod.Vars[:], + IsPublic: prod.IsPublic, + ClusterID: prod.ClusterID, + UpdateTime: prod.UpdateTime, + UpdateBy: prod.UpdateBy, + RecycleDay: prod.RecycleDay, + Render: prod.Render, + Source: prod.Source, + } + err = FillProductVars(products, log) + if err != nil { + return resp, err + } + resp = append(resp, product) + } + sort.SliceStable(resp, func(i, j int) bool { return resp[i].ProductName < resp[j].ProductName }) + return nil, nil +} + func ListProducts(productNameParam, envType string, userName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ProductResp, error) { var ( err error -- Gitee From 1c2209cd5065bf7ea4806dd2ad606d132b60eb07 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 18:02:57 +0800 Subject: [PATCH 0056/2647] list env Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 291faed0..412d981f 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -83,7 +83,7 @@ func ListProductsV3(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListProductsV3(c.Query("productName"), ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = service.ListProductsV3(c.Query("productName"), ctx.User.Name, ctx.Logger) } func AutoCreateProduct(c *gin.Context) { -- Gitee From 592d491429b46d42987265e06f98b9aa0e3bb03e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 18:31:29 +0800 Subject: [PATCH 0057/2647] modify artifact deploy stage Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 0d72f7d7..2422514c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1814,11 +1814,11 @@ func ArtifactDeployToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.Sugar build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: module.Scripts}) } - if module.PMDeployScripts != "" && build.ServiceType == setting.PMDeployType { + if module.PMDeployScripts != "" { build.JobCtx.PMDeployScripts = module.PMDeployScripts } - if len(module.SSHs) > 0 && build.ServiceType == setting.PMDeployType { + if len(module.SSHs) > 0 { privateKeys := make([]*task.SSH, 0) for _, sshID := range module.SSHs { //私钥信息可能被更新,而构建中存储的信息是旧的,需要根据id获取最新的私钥信息 -- Gitee From e8561523237e88eb2ba7b128804ecbf80e1d57ee Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 18:51:12 +0800 Subject: [PATCH 0058/2647] optimize artifact deploy stage Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 1 + .../service/workflow/workflow_task.go | 139 +----------------- 2 files changed, 7 insertions(+), 133 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index b6dce086..4ad2b2a6 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -367,6 +367,7 @@ type BuildModuleArgs struct { TaskID int64 FileName string URL string + TaskType string } func (Workflow) TableName() string { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 2422514c..67a43734 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1409,8 +1409,9 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator WorkflowName: artifact.WorkflowName, TaskID: artifact.TaskID, FileName: artifact.FileName, + TaskType: string(config.TaskArtifactDeploy), } - buildSubtasks, err := ArtifactDeployToSubTasks(buildModuleArgs, log) + buildSubtasks, err := BuildModuleToSubTasks(buildModuleArgs, log) if err != nil { log.Errorf("buildModuleToSubTasks target:[%s] err:%s", artifact.Name, err) return nil, e.ErrCreateTask.AddErr(err) @@ -1622,6 +1623,10 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL Registries: registries, } + if args.TaskType != "" { + build.TaskType = config.TaskArtifactDeploy + } + // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 if module.PreBuild.ImageID != "" { basicImage, err := commonrepo.NewBasicImageColl().Find(module.PreBuild.ImageID) @@ -1737,138 +1742,6 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Caches = module.Caches - bst, err := build.ToSubTask() - if err != nil { - return subTasks, e.ErrConvertSubTasks.AddErr(err) - } - subTasks = append(subTasks, bst) - } - - return subTasks, nil -} - -func ArtifactDeployToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredLogger) ([]map[string]interface{}, error) { - var ( - subTasks = make([]map[string]interface{}, 0) - ) - - opt := &commonrepo.BuildListOption{ - Name: args.BuildName, - ServiceName: args.ServiceName, - ProductName: args.ProductName, - } - - if len(args.Target) > 0 { - opt.Targets = []string{args.Target} - } - - modules, err := commonrepo.NewBuildColl().List(opt) - if err != nil { - return nil, e.ErrConvertSubTasks.AddErr(err) - } - - for _, module := range modules { - build := &task.Build{ - TaskType: config.TaskArtifactDeploy, - Enabled: true, - InstallItems: module.PreBuild.Installs, - ServiceName: args.Target, - Service: args.ServiceName, - JobCtx: task.JobCtx{}, - ImageID: module.PreBuild.ImageID, - BuildOS: module.PreBuild.BuildOS, - ImageFrom: module.PreBuild.ImageFrom, - ResReq: module.PreBuild.ResReq, - Timeout: module.Timeout, - } - - // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 - if module.PreBuild.ImageID != "" { - basicImage, err := commonrepo.NewBasicImageColl().Find(module.PreBuild.ImageID) - if err != nil { - log.Errorf("BasicImage.Find failed, id:%s, err:%v", module.PreBuild.ImageID, err) - } else { - build.BuildOS = basicImage.Value - } - } - - if build.ImageFrom == "" { - build.ImageFrom = commonmodels.ImageFromKoderover - } - - if args.Env != nil { - build.EnvName = args.Env.EnvName - } - - if build.InstallItems == nil { - build.InstallItems = make([]*commonmodels.Item, 0) - } - - build.JobCtx.Builds = module.Repos - if len(build.JobCtx.Builds) == 0 { - build.JobCtx.Builds = make([]*types.Repository, 0) - } - - build.JobCtx.BuildSteps = []*task.BuildStep{} - if module.Scripts != "" { - build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: module.Scripts}) - } - - if module.PMDeployScripts != "" { - build.JobCtx.PMDeployScripts = module.PMDeployScripts - } - - if len(module.SSHs) > 0 { - privateKeys := make([]*task.SSH, 0) - for _, sshID := range module.SSHs { - //私钥信息可能被更新,而构建中存储的信息是旧的,需要根据id获取最新的私钥信息 - latestKeyInfo, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ID: sshID}) - if err != nil || latestKeyInfo == nil { - log.Errorf("PrivateKey.Find failed, id:%s, err:%v", sshID, err) - continue - } - ssh := new(task.SSH) - ssh.Name = latestKeyInfo.Name - ssh.UserName = latestKeyInfo.UserName - ssh.IP = latestKeyInfo.IP - ssh.PrivateKey = latestKeyInfo.PrivateKey - - privateKeys = append(privateKeys, ssh) - } - build.JobCtx.SSHs = privateKeys - } - - build.JobCtx.EnvVars = module.PreBuild.Envs - - if len(module.PreBuild.Envs) == 0 { - build.JobCtx.EnvVars = make([]*commonmodels.KeyVal, 0) - } - - if len(args.Variables) > 0 { - for _, envVar := range build.JobCtx.EnvVars { - for _, overwrite := range args.Variables { - if overwrite.Key == envVar.Key && overwrite.Value != setting.MaskValue { - envVar.Value = overwrite.Value - envVar.IsCredential = overwrite.IsCredential - break - } - } - } - } - - build.JobCtx.UploadPkg = module.PreBuild.UploadPkg - build.JobCtx.CleanWorkspace = module.PreBuild.CleanWorkspace - build.JobCtx.EnableProxy = module.PreBuild.EnableProxy - - if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { - build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ - WorkDir: module.PostBuild.DockerBuild.WorkDir, - DockerFile: module.PostBuild.DockerBuild.DockerFile, - BuildArgs: module.PostBuild.DockerBuild.BuildArgs, - } - } - build.JobCtx.Caches = module.Caches - if args.FileName != "" { build.ArtifactInfo = &task.ArtifactInfo{ URL: args.URL, -- Gitee From 1aed3a7993f44b047149093edc1af61612ceeb68 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 18 Oct 2021 19:27:32 +0800 Subject: [PATCH 0059/2647] add register example --- pkg/config/config.go | 9 ++++ pkg/microservice/aslan/core/service.go | 22 +++++++++ .../aslan/core/workflow/handler/router.go | 32 +++++++++++++ .../core/service/bundle/exemption_urls.go | 8 ++++ .../policy/core/service/bundle/opa_bundle.go | 10 ++++ pkg/setting/types.go | 5 ++ pkg/shared/client/policy/client.go | 47 +++++++++++++++++++ pkg/shared/client/policy/policy.go | 34 ++++++++++++++ 8 files changed, 167 insertions(+) create mode 100644 pkg/shared/client/policy/client.go create mode 100644 pkg/shared/client/policy/policy.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 69855b83..36aed966 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -168,6 +168,15 @@ func OPAServiceAddress() string { return GetServiceAddress(s.Name, s.Port) } +func PolicyServiceInfo() *setting.ServiceInfo { + return GetServiceByCode(setting.Policy) +} + +func PolicyServiceAddress() string { + s := PolicyServiceInfo() + return GetServiceAddress(s.Name, s.Port) +} + func GetServiceAddress(name string, port int32) string { return fmt.Sprintf("http://%s:%d", name, port) } diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index b6c26bf7..460a1fdb 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -33,8 +33,10 @@ import ( environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -43,6 +45,10 @@ const ( webhookController = iota ) +type policyGetter interface { + Policies() *policy.Policy +} + type Controller interface { Run(workers int, stopCh <-chan struct{}) } @@ -67,6 +73,20 @@ func StartControllers(stopCh <-chan struct{}) { wg.Wait() } +func registerPolicies() { + policyClient := policy.New() + + for _, r := range []policyGetter{ + new(workflowhandler.Router), + } { + err := policyClient.CreateOrUpdatePolicy(r.Policies()) + if err != nil { + // should not have happened here + log.DPanic(err) + } + } +} + func Start(ctx context.Context) { log.Init(&log.Config{ Level: commonconfig.LogLevel(), @@ -87,6 +107,8 @@ func Start(ctx context.Context) { environmentservice.ResetProductsStatus() + registerPolicies() + go StartControllers(ctx.Done()) } diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 20467e5c..b1b1afe7 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -17,14 +17,46 @@ limitations under the License. package handler import ( + "encoding/json" + "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} +var policies = ` +{ + "resource": "Workflow", + "alias": "工作流", + "description": "", + "rules": [{ + "action": "get_workflow", + "alias": "查看工作流", + "description": "", + "rules": [{ + "method": "GET", + "endpoint": "api/aslan/workflow/workflow" + }] + }] +} +` + +func (*Router) Policies() *policy.Policy { + res := &policy.Policy{} + err := json.Unmarshal([]byte(policies), res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + return res +} + func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- // 对外公共接口 diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 06b47190..84d419a2 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -48,3 +48,11 @@ var publicURLs = []*policyRule{ Endpoints: []string{"api/aslan/kodespace/downloadUrl"}, }, } + +// actions which are allowed for system admins only. +var adminURLs = []*policyRule{ + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/cluster/clusters"}, + }, +} diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 2eb7431e..3089d1c6 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -300,6 +300,16 @@ func generateOPAExemptionURLs(policies []*models.Policy) *exemptionURLs { } } } + for _, r := range adminURLs { + if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { + r.Methods = AllMethods + } + for _, method := range r.Methods { + for _, endpoint := range r.Endpoints { + data.Registered = append(data.Registered, &rule{Method: method, Endpoint: endpoint}) + } + } + } sort.Sort(data.Registered) diff --git a/pkg/setting/types.go b/pkg/setting/types.go index dd56e0cf..dd1f1315 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -47,6 +47,7 @@ const ( WarpDrive // 10 Minio // 11 OPA // 12 + Policy // 13 ) type ServiceInfo struct { @@ -103,4 +104,8 @@ var Services = map[int]*ServiceInfo{ Name: "opa", Port: 8181, }, + Policy: { + Name: "policy", + Port: 80, + }, } diff --git a/pkg/shared/client/policy/client.go b/pkg/shared/client/policy/client.go new file mode 100644 index 00000000..7406675b --- /dev/null +++ b/pkg/shared/client/policy/client.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package policy + +import ( + "time" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +const retryInterval = 200 * time.Millisecond + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.PolicyServiceAddress() + + c := httpclient.New( + httpclient.SetHostURL(host+"/api/v1"), + httpclient.SetRetryCount(100), + httpclient.SetRetryWaitTime(retryInterval), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go new file mode 100644 index 00000000..3faba0b1 --- /dev/null +++ b/pkg/shared/client/policy/policy.go @@ -0,0 +1,34 @@ +package policy + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Policy struct { + Resource string `json:"resource"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*Rule `json:"rules"` +} + +type Rule struct { + Action string `json:"action"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*ActionRule `json:"rules"` +} + +type ActionRule struct { + Method string `json:"method"` + Endpoint string `json:"endpoint"` +} + +func (c *Client) CreateOrUpdatePolicy(p *Policy) error { + url := fmt.Sprintf("/policies/%s", p.Resource) + + _, err := c.Put(url, httpclient.SetBody(p)) + + return err +} -- Gitee From 84c20a1479cbce7b8906ca8981a93e55b5378c4d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 19:39:33 +0800 Subject: [PATCH 0060/2647] modify field Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 24 +++++++++---------- .../core/workflow/handler/workflow_task.go | 12 +++++----- .../service/webhook/github_workflow_task.go | 2 +- .../service/workflow/workflow_task.go | 6 ++--- .../core/service/types/task/model.go | 12 ++++++---- 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 4ad2b2a6..b9ac61c2 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -140,18 +140,18 @@ type WorkflowTaskArgs struct { ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` Description string `bson:"description,omitempty" json:"description,omitempty"` //为了兼容老数据,namespace可能会存多个环境名称,用逗号隔开 - Namespace string `bson:"namespace" json:"namespace"` - BaseNamespace string `bson:"base_namespace,omitempty" json:"base_namespace,omitempty"` - EnvRecyclePolicy string `bson:"env_recycle_policy,omitempty" json:"env_recycle_policy,omitempty"` - EnvUpdatePolicy string `bson:"env_update_policy,omitempty" json:"env_update_policy,omitempty"` - Target []*TargetArgs `bson:"targets" json:"targets"` - Artifact []*ArtifactArgs `bson:"artifact_args" json:"artifact_args"` - Tests []*TestArgs `bson:"tests" json:"tests"` - VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` - ReqID string `bson:"req_id" json:"req_id"` - RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` - DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` - WorklowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` + Namespace string `bson:"namespace" json:"namespace"` + BaseNamespace string `bson:"base_namespace,omitempty" json:"base_namespace,omitempty"` + EnvRecyclePolicy string `bson:"env_recycle_policy,omitempty" json:"env_recycle_policy,omitempty"` + EnvUpdatePolicy string `bson:"env_update_policy,omitempty" json:"env_update_policy,omitempty"` + Target []*TargetArgs `bson:"targets" json:"targets"` + Artifact []*ArtifactArgs `bson:"artifact_args" json:"artifact_args"` + Tests []*TestArgs `bson:"tests" json:"tests"` + VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` + ReqID string `bson:"req_id" json:"req_id"` + RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` + WorkflowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache IgnoreCache bool `json:"ignore_cache" bson:"ignore_cache"` // Ignore workspace cache and reset volume diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 4ec37261..74b3b708 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -105,11 +105,11 @@ func CreateWorkflowTask(c *gin.Context) { return } - if args.WorklowTaskCreator != setting.CronTaskCreator && args.WorklowTaskCreator != setting.WebhookTaskCreator { - args.WorklowTaskCreator = ctx.Username + if args.WorkflowTaskCreator != setting.CronTaskCreator && args.WorkflowTaskCreator != setting.WebhookTaskCreator { + args.WorkflowTaskCreator = ctx.Username } - ctx.Resp, ctx.Err = workflow.CreateWorkflowTask(args, args.WorklowTaskCreator, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateWorkflowTask(args, args.WorkflowTaskCreator, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) // 发送通知 if ctx.Err != nil { @@ -138,11 +138,11 @@ func CreateArtifactWorkflowTask(c *gin.Context) { return } - if args.WorklowTaskCreator != setting.CronTaskCreator && args.WorklowTaskCreator != setting.WebhookTaskCreator { - args.WorklowTaskCreator = ctx.Username + if args.WorkflowTaskCreator != setting.CronTaskCreator && args.WorkflowTaskCreator != setting.WebhookTaskCreator { + args.WorkflowTaskCreator = ctx.Username } - ctx.Resp, ctx.Err = workflow.CreateArtifactWorkflowTask(args, args.WorklowTaskCreator, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateArtifactWorkflowTask(args, args.WorkflowTaskCreator, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) } // ListWorkflowTasksResult workflowtask分页信息 diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index a90c906f..9292683b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -150,7 +150,7 @@ type workflowArgsFactory struct { func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, repo *types.Repository) *commonmodels.WorkflowTaskArgs { workflow := waf.workflow args.WorkflowName = workflow.Name - args.WorklowTaskCreator = setting.WebhookTaskCreator + args.WorkflowTaskCreator = setting.WebhookTaskCreator args.ProductTmplName = workflow.ProductTmplName args.ReqID = waf.reqID var targetMap map[string][]commonmodels.DeployEnv diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 67a43734..2e97f8f9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1188,7 +1188,7 @@ func addSecurityToSubTasks() (map[string]interface{}, error) { } func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTaskArgs) *commonmodels.TaskArgs { - resp := &commonmodels.TaskArgs{PipelineName: workflowArgs.WorkflowName, TaskCreator: workflowArgs.WorklowTaskCreator} + resp := &commonmodels.TaskArgs{PipelineName: workflowArgs.WorkflowName, TaskCreator: workflowArgs.WorkflowTaskCreator} for _, build := range workflowArgs.Target { if build.Name == target { if build.Build != nil { @@ -1212,7 +1212,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } testArgs := args.Tests - testCreator := args.WorklowTaskCreator + testCreator := args.WorkflowTaskCreator registries, err := commonservice.ListRegistryNamespaces(log) if err != nil { @@ -1930,7 +1930,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { if pt.TaskArgs == nil { - pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorklowTaskCreator} + pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorkflowTaskCreator} } registry := pt.ConfigPayload.RepoConfigs[pt.WorkflowArgs.RegistryID] t.Image = fmt.Sprintf("%s/%s/%s", util.GetURLHostName(registry.RegAddr), registry.Namespace, t.Image) diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 2dd1cd95..e17dbde8 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -166,10 +166,14 @@ type TestArgs struct { } type ArtifactArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` + Name string `bson:"name" json:"name"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image" json:"image"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` + TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` + FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` + URL string `bson:"url,omitempty" json:"url,omitempty"` } type VersionArgs struct { -- Gitee From e320757775b27e1363bcc65a3e8bc79daddf9709 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 19:45:10 +0800 Subject: [PATCH 0061/2647] list projects Signed-off-by: mouuii --- .../picket/core/filter/service/project.go | 13 +++--- .../picket/core/filter/service/workflow.go | 42 ++++++++++++++++++- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index e267487e..134a6294 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -17,7 +17,7 @@ type allowedProjectsData struct { } func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { - names, err := getAllowedProjects(header, logger) + names, err := getVisibleProjects(header, logger) if err != nil { logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err @@ -32,30 +32,31 @@ func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) return aslanClient.ListProjects(header, qs) } -func getAllowedProjects(headers http.Header, logger *zap.SugaredLogger) ([]string, error) { +func getVisibleProjects(headers http.Header, logger *zap.SugaredLogger) ([]string, error) { res := &allowedProjectsData{} - opaClient := opa.NewDefault() - err := opaClient.Evaluate("rbac.user_projects", res, func() (*opa.Input, error) { return generateOPAInput(headers), nil }) + err := opaClient.Evaluate("rbac.user_visible_projects", res, func() (*opa.Input, error) { return generateOPAInput(headers, "", ""), nil }) if err != nil { logger.Errorf("opa evaluation failed, err: %s", err) return nil, err } return res.Result, nil - } -func generateOPAInput(header http.Header) *opa.Input { +func generateOPAInput(header http.Header, method string, endpoint string) *opa.Input { authorization := header.Get(strings.ToLower(setting.AuthorizationHeader)) headers := map[string]string{} + parsedPath := strings.Split(strings.Trim(endpoint, "/"), "/") headers[strings.ToLower(setting.AuthorizationHeader)] = authorization return &opa.Input{ Attributes: &opa.Attributes{ Request: &opa.Request{HTTP: &opa.HTTPSpec{ Headers: headers, + Method: method, }}, }, + ParsedPath: parsedPath, } } diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index 2f426173..3c2de310 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -5,12 +5,18 @@ import ( "net/url" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + "github.com/koderover/zadig/pkg/microservice/picket/client/opa" ) func ListTestWorkflows(testName string, header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { - names, err := getAllowedProjects(header, logger) + rules := []*rule{{ + method: "/api/aslan/workflow/workflow", + endpoint: "PUT", + }} + names, err := getAllowedProjectsWithUpdateWorkflowsPermission(header, rules, logger) if err != nil { logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err @@ -24,3 +30,37 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge return aslanClient.ListTestWorkflows(testName, header, qs) } + +type rule struct { + method string + endpoint string +} + +func getAllowedProjectsWithUpdateWorkflowsPermission(headers http.Header, rules []*rule, logger *zap.SugaredLogger) (projects []string, err error) { + resps := [][]string{} + for _, v := range rules { + res := &allowedProjectsData{} + opaClient := opa.NewDefault() + err := opaClient.Evaluate("rbac.user_allowed_projects", res, func() (*opa.Input, error) { + return generateOPAInput(headers, v.method, v.endpoint), nil + }) + if err != nil { + logger.Errorf("opa evaluation failed, err: %s", err) + return nil, err + } + resps = append(resps, res.Result) + } + return intersect(resps), nil +} + +func intersect(s [][]string) []string { + tmp := sets.String{} + for i, v := range s { + t := sets.NewString(v...) + if i == 0 { + tmp = t + } + tmp = t.Intersection(tmp) + } + return tmp.List() +} -- Gitee From 88f1f10b14c07c4e27007021a515a1eca03dc6ab Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 19:53:36 +0800 Subject: [PATCH 0062/2647] list projects Signed-off-by: mouuii --- .../picket/core/filter/service/workflow.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index 3c2de310..c05c6260 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -54,12 +54,12 @@ func getAllowedProjectsWithUpdateWorkflowsPermission(headers http.Header, rules } func intersect(s [][]string) []string { - tmp := sets.String{} - for i, v := range s { + if len(s) == 0 { + return nil + } + tmp := sets.NewString(s[0]...) + for _, v := range s[1:] { t := sets.NewString(v...) - if i == 0 { - tmp = t - } tmp = t.Intersection(tmp) } return tmp.List() -- Gitee From 79ebeb475289a94b9a65cf577febd388c2dab2c3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 20:02:48 +0800 Subject: [PATCH 0063/2647] list env Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/env.go | 19 ++++++++ .../picket/core/filter/handler/env.go | 15 ++++++ .../picket/core/filter/handler/router.go | 4 ++ .../picket/core/filter/service/env.go | 46 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 pkg/microservice/picket/client/aslan/env.go create mode 100644 pkg/microservice/picket/core/filter/handler/env.go create mode 100644 pkg/microservice/picket/core/filter/service/env.go diff --git a/pkg/microservice/picket/client/aslan/env.go b/pkg/microservice/picket/client/aslan/env.go new file mode 100644 index 00000000..6927e4d3 --- /dev/null +++ b/pkg/microservice/picket/client/aslan/env.go @@ -0,0 +1,19 @@ +package aslan + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) ListEnvs(header http.Header, qs url.Values) ([]byte, error) { + url := "/environment/environments" + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/env.go b/pkg/microservice/picket/core/filter/handler/env.go new file mode 100644 index 00000000..d650b2ec --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/env.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListEnvs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListEnvs(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 9e8aea85..51ca67c7 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -31,4 +31,8 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflows.GET("testName/:testName", ListTestWorkflows) } + envs := router.Group("environments") + { + envs.GET("", ListEnvs) + } } diff --git a/pkg/microservice/picket/core/filter/service/env.go b/pkg/microservice/picket/core/filter/service/env.go new file mode 100644 index 00000000..1acdc963 --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/env.go @@ -0,0 +1,46 @@ +package service + +import ( + "errors" + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/microservice/picket/client/opa" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" +) + +func ListEnvs(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { + allow, err := opaAllow(header, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + if !allow { + return nil, errors.New("have no list env permission") + } + aslanClient := aslan.New() + + return aslanClient.ListProjects(header, qs) +} + +func opaAllow(headers http.Header, logger *zap.SugaredLogger) (bool, error) { + res := &opaAllowResp{} + + opaClient := opa.NewDefault() + err := opaClient.Evaluate("rbac.allow", res, func() (*opa.Input, error) { + return generateOPAInput(headers, "GET", "api/aslan/environment/enviromments"), nil + }) + if err != nil { + logger.Errorf("opa evaluation failed, err: %s", err) + return false, err + } + + return res.Result, nil +} + +type opaAllowResp struct { + Result bool `json:"result"` +} -- Gitee From afc9c0bb7c7aca0d0a0bd66a5f1eb01acd17bb20 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 20:10:11 +0800 Subject: [PATCH 0064/2647] list env Signed-off-by: mouuii --- .../aslan/core/environment/service/environment.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7fb6a716..4868d97a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -170,6 +170,12 @@ func ListProductsV3(productNameParam string, userName string, log *zap.SugaredLo Render: prod.Render, Source: prod.Source, } + if product.ClusterID != "" { + cluster, _ := commonrepo.NewK8SClusterColl().Get(product.ClusterID) + if cluster != nil && cluster.Production { + product.IsProd = true + } + } err = FillProductVars(products, log) if err != nil { return resp, err -- Gitee From 1fa0b59b4936377292da7aa476f6e2f6165a180a Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 20:11:18 +0800 Subject: [PATCH 0065/2647] list env Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/env.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/env.go b/pkg/microservice/picket/core/filter/service/env.go index 1acdc963..60588323 100644 --- a/pkg/microservice/picket/core/filter/service/env.go +++ b/pkg/microservice/picket/core/filter/service/env.go @@ -13,7 +13,7 @@ import ( ) func ListEnvs(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { - allow, err := opaAllow(header, logger) + allow, err := opaAllowEnvList(header, logger) if err != nil { logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err @@ -26,7 +26,7 @@ func ListEnvs(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]b return aslanClient.ListProjects(header, qs) } -func opaAllow(headers http.Header, logger *zap.SugaredLogger) (bool, error) { +func opaAllowEnvList(headers http.Header, logger *zap.SugaredLogger) (bool, error) { res := &opaAllowResp{} opaClient := opa.NewDefault() -- Gitee From 061c50330a58839550a7278ade80e6a381ad19c0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 20:17:46 +0800 Subject: [PATCH 0066/2647] list env Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/env.go b/pkg/microservice/picket/core/filter/service/env.go index 60588323..ca1dce32 100644 --- a/pkg/microservice/picket/core/filter/service/env.go +++ b/pkg/microservice/picket/core/filter/service/env.go @@ -23,7 +23,7 @@ func ListEnvs(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]b } aslanClient := aslan.New() - return aslanClient.ListProjects(header, qs) + return aslanClient.ListEnvs(header, qs) } func opaAllowEnvList(headers http.Header, logger *zap.SugaredLogger) (bool, error) { -- Gitee From e57e3bd849d1491b9d3c05a70f1d0464abec7910 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 20:26:37 +0800 Subject: [PATCH 0067/2647] add pipeline name field Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 2e97f8f9..a8fc02f8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1465,6 +1465,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator // 填充subtask之间关联内容 task := &task.Task{ + PipelineName: args.WorkflowName, TaskID: nextTaskID, TaskCreator: taskCreator, ReqID: args.ReqID, -- Gitee From a03008d0b9b2c491f95ae44f2a6e6b9e2efd08d0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 18 Oct 2021 20:31:45 +0800 Subject: [PATCH 0068/2647] add product name field Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index a8fc02f8..c029f317 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1465,6 +1465,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator // 填充subtask之间关联内容 task := &task.Task{ + ProductName: args.ProductTmplName, PipelineName: args.WorkflowName, TaskID: nextTaskID, TaskCreator: taskCreator, -- Gitee From 360094f03339d4649786160a49ecb8fbf58afbe7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 20:43:54 +0800 Subject: [PATCH 0069/2647] list env Signed-off-by: mouuii --- .../core/environment/handler/environment.go | 25 +- .../core/environment/service/environment.go | 342 +----------------- .../picket/core/filter/service/env.go | 3 +- 3 files changed, 3 insertions(+), 367 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 412d981f..3557ab3c 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -52,29 +52,6 @@ type NamespaceResource struct { Ingresses []resource.Ingress `json:"ingresses"` } -// ListProducts list all product information -// Args: projectName, which is formerly known as productName, is the primary key of the project in our system -func ListProducts(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - if c.Query("projectName") != "" { - isProduction := c.Query("production") - var envFilter string - switch isProduction { - case "true": - envFilter = setting.ProdENV - case "false": - envFilter = setting.TestENV - default: - envFilter = isProduction - } - ctx.Resp, ctx.Err = service.ListProductsV2(c.Query("projectName"), envFilter, ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) - return - } - ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), c.Query("envType"), ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) -} - func ListProductsV3(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -83,7 +60,7 @@ func ListProductsV3(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListProductsV3(c.Query("productName"), ctx.User.Name, ctx.Logger) + ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), ctx.User.Name, ctx.Logger) } func AutoCreateProduct(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4868d97a..dbcbcd17 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -59,7 +59,6 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" @@ -148,7 +147,7 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar return nil } -func ListProductsV3(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { +func ListProducts(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam}) if err != nil { log.Errorf("[%s] Collections.Product.List error: %v", userName, err) @@ -186,181 +185,6 @@ func ListProductsV3(productNameParam string, userName string, log *zap.SugaredLo return nil, nil } -func ListProducts(productNameParam, envType string, userName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ProductResp, error) { - var ( - err error - testResp []*ProductResp - prodResp []*ProductResp - products = make([]*commonmodels.Product, 0) - productNameMap map[string][]int64 - productNamespaces = sets.NewString() - ) - resp := make([]*ProductResp, 0) - - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - - // 获取所有产品 - if superUser { - products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam}) - if err != nil { - log.Errorf("[%s] Collections.Product.List error: %v", userName, err) - return resp, e.ErrListEnvs.AddDesc(err.Error()) - } - } else { - //项目下所有公开环境 - publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true}) - if err != nil { - log.Errorf("Collection.Product.List List product error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - for _, publicProduct := range publicProducts { - if productNameParam == "" { - products = append(products, publicProduct) - productNamespaces.Insert(publicProduct.Namespace) - } else if publicProduct.ProductName == productNameParam { - products = append(products, publicProduct) - productNamespaces.Insert(publicProduct.Namespace) - } - } - - productNameMap, err = poetryCtl.GetUserProject(userID, log) - if err != nil { - log.Errorf("Collection.Product.List GetUserProject error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - for productName, roleIDs := range productNameMap { - //用户关联角色所关联的环境 - for _, roleID := range roleIDs { - if roleID == setting.RoleOwnerID { - tmpProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - for _, product := range tmpProducts { - if productNameParam == "" { - if !productNamespaces.Has(product.Namespace) { - products = append(products, product) - } - } else if product.ProductName == productNameParam { - if !productNamespaces.Has(product.Namespace) { - products = append(products, product) - } - } - } - } else { - productMap := make(map[string]int) - // 先列出环境-用户授权 - userEnvPermissionList, err := poetryCtl.GetUserEnvPermission(userID, log) - if err != nil { - log.Errorf("failed to get user env permission, err: %v", err) - return resp, err - } - for _, userEnvPermission := range userEnvPermissionList { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: userEnvPermission.EnvName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - continue - } - if productNameParam == "" { - if !productNamespaces.Has(product.Namespace) && userEnvPermission.PermissionUUID == permission.TestEnvListUUID { - products = append(products, product) - productNamespaces = productNamespaces.Insert(product.Namespace) - productMap[product.EnvName] = 1 - } - } else if product.ProductName == productNameParam { - if !productNamespaces.Has(product.Namespace) && userEnvPermission.PermissionUUID == permission.TestEnvManageUUID { - products = append(products, product) - productNamespaces = productNamespaces.Insert(product.Namespace) - productMap[product.EnvName] = 1 - } - } - } - // 再获取环境-角色授权 - roleEnvPermissions, err := poetryCtl.ListEnvRolePermission(productName, "", roleID, log) - if err != nil { - log.Errorf("Collection.Product.List ListRoleEnvs error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - for _, roleEnvPermission := range roleEnvPermissions { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: roleEnvPermission.EnvName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - continue - } - if productNameParam == "" { - if !productNamespaces.Has(product.Namespace) && roleEnvPermission.PermissionUUID == permission.TestEnvListUUID { - products = append(products, product) - productNamespaces.Insert(product.Namespace) - } - } else if product.ProductName == productNameParam { - if !productNamespaces.Has(product.Namespace) && roleEnvPermission.PermissionUUID == permission.TestEnvManageUUID { - products = append(products, product) - productNamespaces.Insert(product.Namespace) - } - } - } - } - } - } - } - - err = FillProductVars(products, log) - if err != nil { - return resp, err - } - - for _, prod := range products { - product := &ProductResp{ - ID: prod.ID.Hex(), - ProductName: prod.ProductName, - EnvName: prod.EnvName, - Namespace: prod.Namespace, - Vars: prod.Vars[:], - IsPublic: prod.IsPublic, - ClusterID: prod.ClusterID, - UpdateTime: prod.UpdateTime, - UpdateBy: prod.UpdateBy, - RecycleDay: prod.RecycleDay, - Render: prod.Render, - Source: prod.Source, - } - - if prod.ClusterID != "" { - cluster, _ := commonrepo.NewK8SClusterColl().Get(prod.ClusterID) - if cluster != nil && cluster.Production { - product.IsProd = true - operatorPerm := poetryCtl.HasOperatePermission(prod.ProductName, permission.ProdEnvManageUUID, userID, superUser, log) - viewPerm := poetryCtl.HasOperatePermission(prod.ProductName, permission.ProdEnvListUUID, userID, superUser, log) - if envType == "" && (operatorPerm || viewPerm) { - prodResp = append(prodResp, product) - } else if envType == setting.ProdENV { - prodResp = append(prodResp, product) - } - } else if cluster != nil && !cluster.Production { - product.IsProd = false - testResp = append(testResp, product) - } - } else { - product.IsProd = false - testResp = append(testResp, product) - } - } - switch envType { - case setting.ProdENV: - resp = append(resp, prodResp...) - case setting.TestENV: - resp = append(resp, testResp...) - default: - resp = append(resp, prodResp...) - resp = append(resp, testResp...) - } - - sort.SliceStable(resp, func(i, j int) bool { return resp[i].ProductName < resp[j].ProductName }) - - return resp, nil -} - type ListProductsRespV2 struct { ClusterName string `json:"clusterName"` Production bool `json:"production"` @@ -369,170 +193,6 @@ type ListProductsRespV2 struct { Source string `json:"source"` } -// Args: projectName, which is formerly known as productName, is the primary key of the project in our system -func ListProductsV2(projectName, envFilter string, userName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ListProductsRespV2, error) { - var ( - err error - testResp []*ListProductsRespV2 - prodResp []*ListProductsRespV2 - products = make([]*commonmodels.Product, 0) - productNameMap map[string][]int64 - productNamespaces = sets.NewString() - ) - ret := make([]*ListProductsRespV2, 0) - - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - - // 获取所有产品 - if superUser { - products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName}) - if err != nil { - log.Errorf("[%s] Collections.Product.List error: %v", userName, err) - return ret, e.ErrListEnvs.AddDesc(err.Error()) - } - } else { - //项目下所有公开环境 - publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true}) - if err != nil { - log.Errorf("Collection.Product.List List product error: %v", err) - return ret, e.ErrListProducts.AddDesc(err.Error()) - } - for _, publicProduct := range publicProducts { - if projectName == "" { - products = append(products, publicProduct) - productNamespaces.Insert(publicProduct.Namespace) - } else if publicProduct.ProductName == projectName { - products = append(products, publicProduct) - productNamespaces.Insert(publicProduct.Namespace) - } - } - - productNameMap, err = poetryCtl.GetUserProject(userID, log) - if err != nil { - log.Errorf("Collection.Product.List GetUserProject error: %v", err) - return ret, e.ErrListProducts.AddDesc(err.Error()) - } - for productName, roleIDs := range productNameMap { - //用户关联角色所关联的环境 - for _, roleID := range roleIDs { - if roleID == setting.RoleOwnerID { - tmpProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - return ret, e.ErrListProducts.AddDesc(err.Error()) - } - for _, product := range tmpProducts { - if projectName == "" { - if !productNamespaces.Has(product.Namespace) { - products = append(products, product) - } - } else if product.ProductName == projectName { - if !productNamespaces.Has(product.Namespace) { - products = append(products, product) - } - } - } - } else { - productMap := make(map[string]int) - // 先列出环境-用户授权 - userEnvPermissionList, err := poetryCtl.GetUserEnvPermission(userID, log) - if err != nil { - log.Errorf("failed to get user env permission, err: %v", err) - return ret, err - } - for _, userEnvPermission := range userEnvPermissionList { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: userEnvPermission.EnvName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - continue - } - if projectName == "" { - if !productNamespaces.Has(product.Namespace) && userEnvPermission.PermissionUUID == permission.TestEnvListUUID { - products = append(products, product) - productNamespaces = productNamespaces.Insert(product.Namespace) - productMap[product.EnvName] = 1 - } - } else if product.ProductName == projectName { - if !productNamespaces.Has(product.Namespace) && userEnvPermission.PermissionUUID == permission.TestEnvManageUUID { - products = append(products, product) - productNamespaces = productNamespaces.Insert(product.Namespace) - productMap[product.EnvName] = 1 - } - } - } - // 再获取环境-角色授权 - roleEnvPermissions, err := poetryCtl.ListEnvRolePermission(productName, "", roleID, log) - if err != nil { - log.Errorf("Collection.Product.List ListRoleEnvs error: %v", err) - return ret, e.ErrListProducts.AddDesc(err.Error()) - } - for _, roleEnvPermission := range roleEnvPermissions { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: roleEnvPermission.EnvName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - continue - } - if projectName == "" { - if !productNamespaces.Has(product.Namespace) && roleEnvPermission.PermissionUUID == permission.TestEnvListUUID { - products = append(products, product) - productNamespaces.Insert(product.Namespace) - } - } else if product.ProductName == projectName { - if !productNamespaces.Has(product.Namespace) && roleEnvPermission.PermissionUUID == permission.TestEnvManageUUID { - products = append(products, product) - productNamespaces.Insert(product.Namespace) - } - } - } - } - } - } - } - - for _, product := range products { - item := &ListProductsRespV2{ - Name: product.EnvName, - ProjectName: product.ProductName, - Source: product.Source, - } - if product.ClusterID != "" { - cluster, _ := commonrepo.NewK8SClusterColl().Get(product.ClusterID) - if cluster != nil && cluster.Production { - item.Production = true - item.ClusterName = cluster.Name - operatorPerm := poetryCtl.HasOperatePermission(product.ProductName, permission.ProdEnvManageUUID, userID, superUser, log) - viewPerm := poetryCtl.HasOperatePermission(product.ProductName, permission.ProdEnvListUUID, userID, superUser, log) - if envFilter == "" && (operatorPerm || viewPerm) { - prodResp = append(prodResp, item) - } else if envFilter == setting.ProdENV { - prodResp = append(prodResp, item) - } - } else if cluster != nil && !cluster.Production { - item.Production = false - item.ClusterName = cluster.Name - testResp = append(testResp, item) - } - } else { - item.Production = false - testResp = append(testResp, item) - } - } - - switch envFilter { - case setting.ProdENV: - ret = append(ret, prodResp...) - case setting.TestENV: - ret = append(ret, testResp...) - default: - ret = append(ret, prodResp...) - ret = append(ret, testResp...) - } - - sort.SliceStable(ret, func(i, j int) bool { return ret[i].ProjectName < ret[j].ProjectName }) - - return ret, nil -} - func FillProductVars(products []*commonmodels.Product, log *zap.SugaredLogger) error { for _, product := range products { if product.Source == setting.SourceFromExternal || product.Source == setting.SourceFromHelm { diff --git a/pkg/microservice/picket/core/filter/service/env.go b/pkg/microservice/picket/core/filter/service/env.go index ca1dce32..47c35999 100644 --- a/pkg/microservice/picket/core/filter/service/env.go +++ b/pkg/microservice/picket/core/filter/service/env.go @@ -5,11 +5,10 @@ import ( "net/http" "net/url" - "github.com/koderover/zadig/pkg/microservice/picket/client/opa" - "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + "github.com/koderover/zadig/pkg/microservice/picket/client/opa" ) func ListEnvs(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { -- Gitee From 31aa29b0295c94df0392c97112a35933b35347c2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 20:44:48 +0800 Subject: [PATCH 0070/2647] list env Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/environment.go | 2 +- pkg/microservice/aslan/core/environment/handler/router.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 3557ab3c..38b3a708 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -52,7 +52,7 @@ type NamespaceResource struct { Ingresses []resource.Ingress `json:"ingresses"` } -func ListProductsV3(c *gin.Context) { +func ListProducts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 162038f5..b0056e6d 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -94,7 +94,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- environments := router.Group("environments") { - environments.GET("", ListProductsV3) + environments.GET("", ListProducts) environments.POST("/:productName/auto", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID}, permission.ParamType), AutoCreateProduct) environments.PUT("/:productName/autoUpdate", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, AutoUpdateProduct) environments.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProduct) -- Gitee From 9ce13947cfb861c339d8f73dd5340ce5601de487 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 21:12:03 +0800 Subject: [PATCH 0071/2647] remove resv2 Signed-off-by: mouuii --- .../aslan/core/environment/service/environment.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index dbcbcd17..ee898dcc 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -185,14 +185,6 @@ func ListProducts(productNameParam string, userName string, log *zap.SugaredLogg return nil, nil } -type ListProductsRespV2 struct { - ClusterName string `json:"clusterName"` - Production bool `json:"production"` - Name string `json:"name"` - ProjectName string `json:"projectName"` - Source string `json:"source"` -} - func FillProductVars(products []*commonmodels.Product, log *zap.SugaredLogger) error { for _, product := range products { if product.Source == setting.SourceFromExternal || product.Source == setting.SourceFromHelm { -- Gitee From 252e1f26e1c46dd0fe4e91f60c076f7c2b138419 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 21:20:15 +0800 Subject: [PATCH 0072/2647] delete env Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/env.go | 19 ------------------- .../picket/core/filter/handler/env.go | 15 --------------- .../picket/core/filter/handler/router.go | 4 ---- 3 files changed, 38 deletions(-) delete mode 100644 pkg/microservice/picket/client/aslan/env.go delete mode 100644 pkg/microservice/picket/core/filter/handler/env.go diff --git a/pkg/microservice/picket/client/aslan/env.go b/pkg/microservice/picket/client/aslan/env.go deleted file mode 100644 index 6927e4d3..00000000 --- a/pkg/microservice/picket/client/aslan/env.go +++ /dev/null @@ -1,19 +0,0 @@ -package aslan - -import ( - "net/http" - "net/url" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -func (c *Client) ListEnvs(header http.Header, qs url.Values) ([]byte, error) { - url := "/environment/environments" - - res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) - if err != nil { - return nil, err - } - - return res.Body(), nil -} diff --git a/pkg/microservice/picket/core/filter/handler/env.go b/pkg/microservice/picket/core/filter/handler/env.go deleted file mode 100644 index d650b2ec..00000000 --- a/pkg/microservice/picket/core/filter/handler/env.go +++ /dev/null @@ -1,15 +0,0 @@ -package handler - -import ( - "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" - internalhandler "github.com/koderover/zadig/pkg/shared/handler" -) - -func ListEnvs(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.ListEnvs(c.Request.Header, c.Request.URL.Query(), ctx.Logger) -} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 51ca67c7..9e8aea85 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -31,8 +31,4 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflows.GET("testName/:testName", ListTestWorkflows) } - envs := router.Group("environments") - { - envs.GET("", ListEnvs) - } } -- Gitee From 64a1b0cc0f1789479c9a313598d9248d078ced63 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 21:21:04 +0800 Subject: [PATCH 0073/2647] delete env Signed-off-by: mouuii --- .../picket/core/filter/service/env.go | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 pkg/microservice/picket/core/filter/service/env.go diff --git a/pkg/microservice/picket/core/filter/service/env.go b/pkg/microservice/picket/core/filter/service/env.go deleted file mode 100644 index 47c35999..00000000 --- a/pkg/microservice/picket/core/filter/service/env.go +++ /dev/null @@ -1,45 +0,0 @@ -package service - -import ( - "errors" - "net/http" - "net/url" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" - "github.com/koderover/zadig/pkg/microservice/picket/client/opa" -) - -func ListEnvs(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { - allow, err := opaAllowEnvList(header, logger) - if err != nil { - logger.Errorf("Failed to get allowed project names, err: %s", err) - return nil, err - } - if !allow { - return nil, errors.New("have no list env permission") - } - aslanClient := aslan.New() - - return aslanClient.ListEnvs(header, qs) -} - -func opaAllowEnvList(headers http.Header, logger *zap.SugaredLogger) (bool, error) { - res := &opaAllowResp{} - - opaClient := opa.NewDefault() - err := opaClient.Evaluate("rbac.allow", res, func() (*opa.Input, error) { - return generateOPAInput(headers, "GET", "api/aslan/environment/enviromments"), nil - }) - if err != nil { - logger.Errorf("opa evaluation failed, err: %s", err) - return false, err - } - - return res.Result, nil -} - -type opaAllowResp struct { - Result bool `json:"result"` -} -- Gitee From 802dc08df786ad089cb9a9bc292805b385c7a844 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 18 Oct 2021 21:43:49 +0800 Subject: [PATCH 0074/2647] delete env Signed-off-by: mouuii --- .../aslan/core/project/service/product.go | 12 ++++++++++++ pkg/shared/client/policy/policy.go | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 40fca5d9..73819973 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -37,6 +37,7 @@ import ( environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -330,6 +331,17 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug func ForkProduct(userID int, username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { + policyClient := policy.New() + err := policyClient.RoleBinding(args.ProductName, policy.RoleBinding{ + Name: args.ProductName, + User: username, + Role: "Contributor", + Global: true, + }) + if err != nil { + log.Error("rolebinding error") + return e.ErrForkProduct + } prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { errMsg := fmt.Sprintf("[ProductTmpl.Find] %s error: %v", args.ProductName, err) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 3faba0b1..c868d905 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -32,3 +32,16 @@ func (c *Client) CreateOrUpdatePolicy(p *Policy) error { return err } + +type RoleBinding struct { + Name string + User string + Role string + Global bool +} + +func (c *Client) RoleBinding(projectName string, roleBinding RoleBinding) error { + url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) + _, err := c.Post(url, httpclient.SetBody(roleBinding)) + return err +} -- Gitee From 1c19fefe1b2e061dd2b91dd9fb2160ce6d0c3931 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 09:55:38 +0800 Subject: [PATCH 0075/2647] fork product Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 73819973..f7bb6990 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -333,7 +333,7 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj policyClient := policy.New() err := policyClient.RoleBinding(args.ProductName, policy.RoleBinding{ - Name: args.ProductName, + Name: fmt.Sprintf("%s-%s", args.ProductName, username), User: username, Role: "Contributor", Global: true, -- Gitee From 61f5f6ffe1f071edefae9b0dcdea404d456c98eb Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 10:11:45 +0800 Subject: [PATCH 0076/2647] fork product Signed-off-by: mouuii --- .../aslan/core/environment/service/environment.go | 2 +- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- pkg/setting/consts.go | 6 ++++++ pkg/shared/client/policy/policy.go | 5 +++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ee898dcc..2af4b539 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -182,7 +182,7 @@ func ListProducts(productNameParam string, userName string, log *zap.SugaredLogg resp = append(resp, product) } sort.SliceStable(resp, func(i, j int) bool { return resp[i].ProductName < resp[j].ProductName }) - return nil, nil + return resp, nil } func FillProductVars(products []*commonmodels.Product, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index f7bb6990..cae44203 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -332,10 +332,10 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug func ForkProduct(userID int, username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { policyClient := policy.New() - err := policyClient.RoleBinding(args.ProductName, policy.RoleBinding{ + err := policyClient.CreateRoleBinding(args.ProductName, &policy.RoleBinding{ Name: fmt.Sprintf("%s-%s", args.ProductName, username), User: username, - Role: "Contributor", + Role: setting.Contributor, Global: true, }) if err != nil { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 95ca280b..0dfa6e85 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -484,3 +484,9 @@ const ( ) const ChartTemplatesPath = "charts" + +type RoleType string + +const ( + Contributor RoleType = "Contributor" +) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index c868d905..0a89b0ed 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -3,6 +3,7 @@ package policy import ( "fmt" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -36,11 +37,11 @@ func (c *Client) CreateOrUpdatePolicy(p *Policy) error { type RoleBinding struct { Name string User string - Role string + Role setting.RoleType Global bool } -func (c *Client) RoleBinding(projectName string, roleBinding RoleBinding) error { +func (c *Client) CreateRoleBinding(projectName string, roleBinding *RoleBinding) error { url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) _, err := c.Post(url, httpclient.SetBody(roleBinding)) return err -- Gitee From 736d83f1258d89ebc81a53f76669181d6f701733 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 10:19:02 +0800 Subject: [PATCH 0077/2647] fork product Signed-off-by: mouuii --- .../aslan/core/project/service/product.go | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index cae44203..fa322b7c 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -331,17 +331,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug func ForkProduct(userID int, username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { - policyClient := policy.New() - err := policyClient.CreateRoleBinding(args.ProductName, &policy.RoleBinding{ - Name: fmt.Sprintf("%s-%s", args.ProductName, username), - User: username, - Role: setting.Contributor, - Global: true, - }) - if err != nil { - log.Error("rolebinding error") - return e.ErrForkProduct - } prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { errMsg := fmt.Sprintf("[ProductTmpl.Find] %s error: %v", args.ProductName, err) @@ -453,6 +442,17 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj CreateBy: username, UpdateBy: username, } + policyClient := policy.New() + err = policyClient.CreateRoleBinding(args.ProductName, &policy.RoleBinding{ + Name: fmt.Sprintf("%s-%s", args.ProductName, username), + User: username, + Role: setting.Contributor, + Global: true, + }) + if err != nil { + log.Error("rolebinding error") + return e.ErrForkProduct + } return workflowservice.CreateWorkflow(workflowArgs, log) } -- Gitee From 4808d0c6610df4005c2ed7b5cdd0f013da18d3a0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 10:31:23 +0800 Subject: [PATCH 0078/2647] fork product Signed-off-by: mouuii --- .../core/common/repository/mongodb/product.go | 24 +++++++++++-------- .../core/common/service/template_product.go | 2 +- .../core/environment/service/environment.go | 4 +--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index a71f4243..3611104d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -39,15 +39,16 @@ type ProductFindOptions struct { // ClusterId is a primitive.ObjectID{}.Hex() type ProductListOptions struct { - EnvName string - Name string - IsPublic bool - ClusterID string - IsSort bool - ExcludeStatus string - ExcludeSource string - Source string - InProjects []string + EnvName string + Name string + IsPublic bool + ClusterID string + IsSortByUpdateTime bool + IsSortByProductName bool + ExcludeStatus string + ExcludeSource string + Source string + InProjects []string } type projectEnvs struct { @@ -164,9 +165,12 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { ctx := context.Background() opts := options.Find() - if opt.IsSort { + if opt.IsSortByUpdateTime { opts.SetSort(bson.D{{"update_time", -1}}) } + if opt.IsSortByProductName { + opts.SetSort(bson.D{{"product_name", 1}}) + } cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 94dd0dc5..2523538f 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -101,7 +101,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Testing.List err : %v", err) } - totalEnvs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSort: true}) + totalEnvs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByUpdateTime: true}) if err != nil { return resp, fmt.Errorf("Product.List err : %v", err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 2af4b539..30c25cc6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -22,7 +22,6 @@ import ( "fmt" "path/filepath" "regexp" - "sort" "strings" "sync" "time" @@ -148,7 +147,7 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar } func ListProducts(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam}) + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam, IsSortByProductName: true}) if err != nil { log.Errorf("[%s] Collections.Product.List error: %v", userName, err) return resp, e.ErrListEnvs.AddDesc(err.Error()) @@ -181,7 +180,6 @@ func ListProducts(productNameParam string, userName string, log *zap.SugaredLogg } resp = append(resp, product) } - sort.SliceStable(resp, func(i, j int) bool { return resp[i].ProductName < resp[j].ProductName }) return resp, nil } -- Gitee From f66027665cc02e3b055df9b311600d914cda03d9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 19 Oct 2021 11:22:12 +0800 Subject: [PATCH 0079/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/handler/router.go | 2 +- pkg/microservice/aslan/core/system/handler/s3.go | 5 ++--- pkg/microservice/aslan/core/system/service/s3.go | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index b95d5f83..64c99dc5 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -80,7 +80,7 @@ func (*Router) Inject(router *gin.RouterGroup) { s3storage.GET("/:id", GetS3Storage) s3storage.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateS3Storage) s3storage.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteS3Storage) - s3storage.POST("/:id/release/tars/search", ListTars) + s3storage.POST("/:id/releases/search", ListTars) } //系统清理缓存 diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index 5cea1ca4..8819a323 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -23,7 +23,6 @@ import ( "io/ioutil" "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" @@ -128,10 +127,10 @@ func ListTars(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := new(ListTarsOption) - if err := c.ShouldBindWith(args, binding.JSON); err != nil { + if err := c.ShouldBindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - ctx.Resp, ctx.Err = service.ListTars(c.Param("id"), args.Names, ctx.Logger) + ctx.Resp, ctx.Err = service.ListTars(c.Param("id"), c.Query("kind"), args.Names, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 62786432..d5c77ece 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -21,10 +21,10 @@ import ( "strings" "sync" - "github.com/koderover/zadig/pkg/microservice/aslan/config" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" @@ -102,7 +102,7 @@ func GetS3Storage(id string, logger *zap.SugaredLogger) (*commonmodels.S3Storage return store, nil } -func ListTars(id string, serviceNames []string, logger *zap.SugaredLogger) ([]*commonmodels.TarInfo, error) { +func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) ([]*commonmodels.TarInfo, error) { var ( wg wait.Group mutex sync.RWMutex @@ -131,7 +131,7 @@ func ListTars(id string, serviceNames []string, logger *zap.SugaredLogger) ([]*c wg.Start(func() { deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ Name: serviceName, - Type: string(config.File), + Type: kind, Source: string(config.WorkflowType), }) if err != nil { -- Gitee From b878ec4c60172b476922e1df4d7935b349be6630 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 19 Oct 2021 13:57:49 +0800 Subject: [PATCH 0080/2647] build add productName field Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/build.go | 7 ++++--- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + .../warpdrive/core/service/types/task/build.go | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 02430c37..c8211297 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -26,9 +26,10 @@ import ( ) type Build struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ProductName string `bson:"product_name" json:"product_name"` // 新增一个service表示服务名称 Service string `bson:"service" json:"service"` // 该名称实际为服务组件名称 diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c029f317..ed6a7649 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1623,6 +1623,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL ResReq: module.PreBuild.ResReq, Timeout: module.Timeout, Registries: registries, + ProductName: args.ProductName, } if args.TaskType != "" { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index fb3e0e68..bb8c4b69 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -24,9 +24,10 @@ import ( ) type Build struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ProductName string `bson:"product_name" json:"product_name"` // 新增一个service表示服务名称 Service string `bson:"service" json:"service"` // 该名称实际为服务组件名称 -- Gitee From f6e7126bfef5fe4c4abfb28c6d9c413ee8c296b0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 14:47:47 +0800 Subject: [PATCH 0081/2647] patch service Signed-off-by: mouuii --- .../repository/mongodb/template/product.go | 20 ++++++++++++++++ .../aslan/core/project/handler/product.go | 24 +++++++++++++++++++ .../aslan/core/project/handler/router.go | 1 + .../aslan/core/project/service/product.go | 8 +++++++ 4 files changed, 53 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 948f4283..e45dde4b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -209,6 +209,26 @@ func (c *ProductColl) Create(args *template.Product) error { return err } +// Update UpdateChoreographyService +func (c *ProductColl) UpdateChoreographyService(productName string, args *template.Product) error { + // avoid panic issue + if args == nil { + return errors.New("nil ProductTmpl") + } + + args.ProjectName = strings.TrimSpace(args.ProjectName) + + query := bson.M{"product_name": productName} + change := bson.M{"$set": bson.M{ + "services": args.Services, + "update_time": time.Now().Unix(), + "update_by": args.UpdateBy, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + // Update existing ProductTmpl func (c *ProductColl) Update(productName string, args *template.Product) error { // avoid panic issue diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index ddd8ee3b..b91b2a4f 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -133,6 +133,30 @@ func UpdateProject(c *gin.Context) { ctx.Err = projectservice.UpdateProject(productName, args, ctx.Logger) } +func UpdateChoreographyService(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(template.Product) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateChoreographyService c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("UpdateChoreographyService json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-项目服务编排", args.ProductName, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid ProductTmpl json args") + return + } + + args.UpdateBy = ctx.Username + ctx.Err = projectservice.UpdateChoreographyService(c.Param("name"), args, ctx.Logger) +} + func DeleteProductTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index f1d90712..c1badd25 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -47,6 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) + product.PATCH("/:name", UpdateChoreographyService) } openSource := router.Group("opensource") diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index fa322b7c..22bc8232 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -126,6 +126,14 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err return } +func UpdateChoreographyService(name string, args *template.Product, log *zap.SugaredLogger) (err error) { + if err = templaterepo.NewProductColl().UpdateChoreographyService(name, args); err != nil { + log.Errorf("UpdateChoreographyService error: %v", err) + return e.ErrUpdateProduct + } + return nil +} + // UpdateProductTemplate 更新产品模板 func UpdateProductTemplate(name string, args *template.Product, log *zap.SugaredLogger) (err error) { kvs := args.Vars -- Gitee From bcc68aeb17cfbcf1ef4baacbcf52846c6342728d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 19 Oct 2021 15:04:06 +0800 Subject: [PATCH 0082/2647] workflow add storage_id field Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/repository/models/workflow.go | 1 + pkg/microservice/cron/core/service/types.go | 1 + pkg/microservice/warpdrive/core/service/types/task/model.go | 1 + 3 files changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index b9ac61c2..86c7a161 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -150,6 +150,7 @@ type WorkflowTaskArgs struct { VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` ReqID string `bson:"req_id" json:"req_id"` RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` WorkflowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 2fdddf7c..96c0c907 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -260,6 +260,7 @@ type WorkflowTaskArgs struct { VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` ReqID string `bson:"req_id" json:"req_id"` RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` WorklowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index e17dbde8..8c635017 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -133,6 +133,7 @@ type WorkflowTaskArgs struct { VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` ReqID string `bson:"req_id" json:"req_id"` RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` WorklowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache -- Gitee From 1162bf8396d8b61a2745c2663df2bcca9ea90239 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 15:17:26 +0800 Subject: [PATCH 0083/2647] patch service Signed-off-by: mouuii --- .../repository/mongodb/template/product.go | 12 +++------ .../aslan/core/project/handler/product.go | 26 +++++++++---------- .../aslan/core/project/handler/router.go | 2 +- .../aslan/core/project/service/product.go | 4 +-- 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index e45dde4b..c9db30fd 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -210,19 +210,13 @@ func (c *ProductColl) Create(args *template.Product) error { } // Update UpdateChoreographyService -func (c *ProductColl) UpdateChoreographyService(productName string, args *template.Product) error { - // avoid panic issue - if args == nil { - return errors.New("nil ProductTmpl") - } - - args.ProjectName = strings.TrimSpace(args.ProjectName) +func (c *ProductColl) UpdateOrchestrationService(productName string, services [][]string, updateBy string) error { query := bson.M{"product_name": productName} change := bson.M{"$set": bson.M{ - "services": args.Services, + "services": services, "update_time": time.Now().Unix(), - "update_by": args.UpdateBy, + "update_by": updateBy, }} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index b91b2a4f..49fcdf8a 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -133,28 +133,28 @@ func UpdateProject(c *gin.Context) { ctx.Err = projectservice.UpdateProject(productName, args, ctx.Logger) } -func UpdateChoreographyService(c *gin.Context) { +type UpdateOrchestrationServiceReq struct { + Services [][]string `json:"services"` +} + +func UpdateOrchestrationService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(template.Product) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateChoreographyService c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateChoreographyService json.Unmarshal err : %v", err) + projectName := c.Param("name") + if projectName == "" { + ctx.Err = e.ErrInvalidParam + return } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-项目服务编排", args.ProductName, string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + internalhandler.InsertOperationLog(c, ctx.Username, projectName, "更新", "项目管理-项目服务编排", projectName, "", ctx.Logger) + args := new(UpdateOrchestrationServiceReq) if err := c.BindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid ProductTmpl json args") + ctx.Err = e.ErrInvalidParam.AddDesc("invalid UpdateOrchestrationServiceReq json args") return } - args.UpdateBy = ctx.Username - ctx.Err = projectservice.UpdateChoreographyService(c.Param("name"), args, ctx.Logger) + ctx.Err = projectservice.UpdateOrchestrationService(projectName, args.Services, ctx.Username, ctx.Logger) } func DeleteProductTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index c1badd25..fd57329e 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -47,7 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) - product.PATCH("/:name", UpdateChoreographyService) + product.PATCH("/:name", UpdateOrchestrationService) } openSource := router.Group("opensource") diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 22bc8232..2eecdc71 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -126,8 +126,8 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err return } -func UpdateChoreographyService(name string, args *template.Product, log *zap.SugaredLogger) (err error) { - if err = templaterepo.NewProductColl().UpdateChoreographyService(name, args); err != nil { +func UpdateOrchestrationService(name string, services [][]string, updateBy string, log *zap.SugaredLogger) (err error) { + if err = templaterepo.NewProductColl().UpdateOrchestrationService(name, services, updateBy); err != nil { log.Errorf("UpdateChoreographyService error: %v", err) return e.ErrUpdateProduct } -- Gitee From 6baf7b23e8e5be57519010b343c29fb13e8d8192 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 15:19:23 +0800 Subject: [PATCH 0084/2647] patch service Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/template/product.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index c9db30fd..580f63f4 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -209,7 +209,6 @@ func (c *ProductColl) Create(args *template.Product) error { return err } -// Update UpdateChoreographyService func (c *ProductColl) UpdateOrchestrationService(productName string, services [][]string, updateBy string) error { query := bson.M{"product_name": productName} -- Gitee From 7496ef7878d92dbc7fbed4d6749a892d19dcb692 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 15:55:07 +0800 Subject: [PATCH 0085/2647] patch service Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/template/product.go | 2 +- pkg/microservice/aslan/core/project/handler/product.go | 4 ++-- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 580f63f4..a533511f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -209,7 +209,7 @@ func (c *ProductColl) Create(args *template.Product) error { return err } -func (c *ProductColl) UpdateOrchestrationService(productName string, services [][]string, updateBy string) error { +func (c *ProductColl) UpdateServiceOrchestration(productName string, services [][]string, updateBy string) error { query := bson.M{"product_name": productName} change := bson.M{"$set": bson.M{ diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 49fcdf8a..1c11a0d1 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -137,7 +137,7 @@ type UpdateOrchestrationServiceReq struct { Services [][]string `json:"services"` } -func UpdateOrchestrationService(c *gin.Context) { +func UpdateServiceOrchestration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -154,7 +154,7 @@ func UpdateOrchestrationService(c *gin.Context) { return } - ctx.Err = projectservice.UpdateOrchestrationService(projectName, args.Services, ctx.Username, ctx.Logger) + ctx.Err = projectservice.UpdateServiceOrchestration(projectName, args.Services, ctx.Username, ctx.Logger) } func DeleteProductTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 2eecdc71..4004cadf 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -126,8 +126,8 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err return } -func UpdateOrchestrationService(name string, services [][]string, updateBy string, log *zap.SugaredLogger) (err error) { - if err = templaterepo.NewProductColl().UpdateOrchestrationService(name, services, updateBy); err != nil { +func UpdateServiceOrchestration(name string, services [][]string, updateBy string, log *zap.SugaredLogger) (err error) { + if err = templaterepo.NewProductColl().UpdateServiceOrchestration(name, services, updateBy); err != nil { log.Errorf("UpdateChoreographyService error: %v", err) return e.ErrUpdateProduct } -- Gitee From 1fe8bfa06683cc507b8b6b7d5f8a13bcf3501d8e Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 19 Oct 2021 16:44:19 +0800 Subject: [PATCH 0086/2647] trivial Signed-off-by: lou --- pkg/microservice/policy/core/handler/router.go | 6 +++++- pkg/microservice/policy/core/service.go | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 3fcd58a2..19769fd1 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -51,7 +51,11 @@ func (*Router) Inject(router *gin.RouterGroup) { policyRegistrations := router.Group("policies") { policyRegistrations.PUT("/:resourceName", CreateOrUpdatePolicyRegistration) - policyRegistrations.GET("", GetPolicyRegistrationDefinitions) + } + + policyDefinitions := router.Group("policy-definitions") + { + policyDefinitions.GET("", GetPolicyRegistrationDefinitions) } } diff --git a/pkg/microservice/policy/core/service.go b/pkg/microservice/policy/core/service.go index 4fc79dce..3576ee08 100644 --- a/pkg/microservice/policy/core/service.go +++ b/pkg/microservice/policy/core/service.go @@ -90,6 +90,7 @@ func initDatabase(ctx context.Context) { for _, r := range []indexer{ mongodb.NewRoleColl(), mongodb.NewRoleBindingColl(), + mongodb.NewPolicyColl(), } { wg.Add(1) go func(r indexer) { -- Gitee From 70fbb4cec89a953a88d3f6f28e6b85c17686f59e Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 16:54:06 +0800 Subject: [PATCH 0087/2647] patch service Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 4004cadf..8ee30f86 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -129,7 +129,7 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err func UpdateServiceOrchestration(name string, services [][]string, updateBy string, log *zap.SugaredLogger) (err error) { if err = templaterepo.NewProductColl().UpdateServiceOrchestration(name, services, updateBy); err != nil { log.Errorf("UpdateChoreographyService error: %v", err) - return e.ErrUpdateProduct + return e.ErrUpdateProduct.AddErr(err) } return nil } -- Gitee From 6b1f498b2c29bde32c2a410799ba515c492d2a7c Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 17:56:40 +0800 Subject: [PATCH 0088/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 33 +++++++++++++++++++ .../policy/core/handler/router.go | 4 +++ .../policy/core/repository/mongodb/role.go | 17 ++++++++-- pkg/microservice/policy/core/service/role.go | 30 +++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 25d51344..e96bdec7 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -43,6 +43,19 @@ func CreateRole(c *gin.Context) { ctx.Err = service.CreateRole(projectName, args, ctx.Logger) } +func ListRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + ctx.Resp, ctx.Err = service.ListRole(projectName) + return +} + func CreateGlobalRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -55,3 +68,23 @@ func CreateGlobalRole(c *gin.Context) { ctx.Err = service.CreateRole("", args, ctx.Logger) } + +func ListGlobalRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListGlobalRole(ctx.Logger) + return +} + +func DeleteRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + name := c.Param("name") + if name == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args name can't be empty") + return + } + ctx.Err = service.DeleteGlobalRole(c.Param("name"), ctx.Logger) + return +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 3fcd58a2..e49800d4 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,11 +26,15 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) + roles.GET("", ListRole) + roles.DELETE("/:name", DeleteRole) } globalRoles := router.Group("global-roles") { globalRoles.POST("", CreateGlobalRole) + globalRoles.GET("", ListGlobalRole) + globalRoles.DELETE("/:name", DeleteRole) } roleBindings := router.Group("rolebindings") diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index da8595c9..060d2c79 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -77,11 +77,18 @@ func (c *RoleColl) Get(ns, name string) (*models.Role, bool, error) { return res, true, nil } -func (c *RoleColl) List() ([]*models.Role, error) { +type ListOpt struct { + ProjectSpace string +} + +func (c *RoleColl) List(projectName string) ([]*models.Role, error) { var res []*models.Role ctx := context.Background() - cursor, err := c.Collection.Find(ctx, bson.M{}) + query := bson.M{} + query["namespace"] = projectName + + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } @@ -103,3 +110,9 @@ func (c *RoleColl) Create(obj *models.Role) error { return err } + +func (c *RoleColl) Delete(name string) error { + query := bson.M{"name": name} + _, err := c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index fb7b69ad..e71ecf63 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -48,3 +48,33 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().Create(obj) } + +func ListRole(projectName string) (roles []*Role, err error) { + projectRoles, err := mongodb.NewRoleColl().List(projectName) + if err != nil { + return nil, err + } + for _, v := range projectRoles { + roles = append(roles, &Role{ + Name: v.Name, + }) + } + return roles, nil +} + +func ListGlobalRole(_ *zap.SugaredLogger) (roles []*Role, err error) { + globalRoles, err := mongodb.NewRoleColl().List("") + if err != nil { + return nil, err + } + for _, v := range globalRoles { + roles = append(roles, &Role{ + Name: v.Name, + }) + } + return roles, nil +} + +func DeleteGlobalRole(name string, _ *zap.SugaredLogger) (err error) { + return mongodb.NewRoleColl().Delete(name) +} -- Gitee From 091325f0ba70978ce1a33fcadedf6549728c13a9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 17:58:26 +0800 Subject: [PATCH 0089/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/repository/mongodb/role.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 060d2c79..339acf2b 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -77,10 +77,6 @@ func (c *RoleColl) Get(ns, name string) (*models.Role, bool, error) { return res, true, nil } -type ListOpt struct { - ProjectSpace string -} - func (c *RoleColl) List(projectName string) ([]*models.Role, error) { var res []*models.Role -- Gitee From 716c738b0bcaf91cd936d187a9090c6adb542944 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 19 Oct 2021 18:39:05 +0800 Subject: [PATCH 0090/2647] remove permission Signed-off-by: lou --- .../aslan/core/build/handler/router.go | 9 +- .../aslan/core/code/handler/router.go | 3 +- .../aslan/core/delivery/handler/router.go | 3 +- .../aslan/core/environment/handler/router.go | 49 +++-- .../aslan/core/multicluster/handler/router.go | 12 +- .../aslan/core/project/handler/router.go | 13 +- .../aslan/core/service/handler/router.go | 15 +- .../aslan/core/system/handler/router.go | 74 +++---- .../aslan/core/workflow/handler/router.go | 29 ++- .../core/workflow/testing/handler/router.go | 7 +- pkg/microservice/podexec/core/service/auth.go | 150 -------------- pkg/microservice/podexec/server/server.go | 2 - pkg/middleware/gin/permission.go | 190 ------------------ pkg/types/permission/permission.go | 48 +---- 14 files changed, 104 insertions(+), 500 deletions(-) delete mode 100644 pkg/microservice/podexec/core/service/auth.go delete mode 100644 pkg/middleware/gin/permission.go diff --git a/pkg/microservice/aslan/core/build/handler/router.go b/pkg/microservice/aslan/core/build/handler/router.go index 1e6af9d8..090d6516 100644 --- a/pkg/microservice/aslan/core/build/handler/router.go +++ b/pkg/microservice/aslan/core/build/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -30,10 +29,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { build.GET("/:name", FindBuildModule) build.GET("", ListBuildModules) - build.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.BuildManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateBuildModule) - build.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.BuildManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateBuildModule) - build.DELETE("", gin2.IsHavePermission([]string{permission.BuildDeleteUUID}, permission.QueryType), gin2.UpdateOperationLogStatus, DeleteBuildModule) - build.POST("/targets", gin2.IsHavePermission([]string{permission.BuildManageUUID}, permission.QueryType), gin2.UpdateOperationLogStatus, UpdateBuildTargets) + build.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateBuildModule) + build.PUT("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateBuildModule) + build.DELETE("", gin2.UpdateOperationLogStatus, DeleteBuildModule) + build.POST("/targets", gin2.UpdateOperationLogStatus, UpdateBuildTargets) } target := router.Group("targets") diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 99ac66ca..6d6ee4a7 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -42,7 +41,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- workspace := router.Group("workspace") { - workspace.DELETE("", GetProductNameByWorkspacePipeline, gin2.IsHavePermission([]string{permission.WorkflowUpdateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CleanWorkspace) + workspace.DELETE("", GetProductNameByWorkspacePipeline, gin2.UpdateOperationLogStatus, CleanWorkspace) workspace.GET("/file", GetWorkspaceFile) workspace.GET("/git/:codehostId/:repoName/:branchName/:remoteName", GetGitRepoInfo) diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 25b9641c..362ce68c 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -46,7 +45,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { deliveryRelease.GET("/:id", GetDeliveryVersion) deliveryRelease.GET("", ListDeliveryVersion) - deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.IsHavePermission([]string{permission.ReleaseDeleteUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) + deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) } deliveryPackage := router.Group("packages") diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index b0056e6d..d2ffbbf1 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -32,8 +31,8 @@ func (*Router) Inject(router *gin.RouterGroup) { configmaps := router.Group("configmaps") { configmaps.GET("", ListConfigMaps) - configmaps.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateConfigMap) - configmaps.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, RollBackConfigMap) + configmaps.PUT("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateConfigMap) + configmaps.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, RollBackConfigMap) } // --------------------------------------------------------------------------------------- @@ -58,7 +57,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- export := router.Group("export") { - export.GET("/service", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.QueryType), ExportYaml) + export.GET("/service", ExportYaml) // export.GET("/pipelines/:name", ExportBuildYaml) } @@ -67,8 +66,8 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- image := router.Group("image") { - image.POST("/deployment", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID, permission.TestUpdateEnvUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) - image.POST("/statefulset", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID, permission.TestUpdateEnvUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) + image.POST("/deployment", gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) + image.POST("/statefulset", gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) } // 查询环境创建时的服务和变量信息 @@ -84,7 +83,7 @@ func (*Router) Inject(router *gin.RouterGroup) { kube.GET("/events", ListKubeEvents) kube.POST("/pods", ListServicePods) - kube.DELETE("/pods/:podName", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID, permission.TestUpdateEnvUUID}, permission.QueryType), gin2.UpdateOperationLogStatus, DeletePod) + kube.DELETE("/pods/:podName", gin2.UpdateOperationLogStatus, DeletePod) kube.GET("/pods/:podName/events", ListPodEvents) kube.GET("/workloads", ListWorkloads) } @@ -95,43 +94,43 @@ func (*Router) Inject(router *gin.RouterGroup) { environments := router.Group("environments") { environments.GET("", ListProducts) - environments.POST("/:productName/auto", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID}, permission.ParamType), AutoCreateProduct) - environments.PUT("/:productName/autoUpdate", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, AutoUpdateProduct) - environments.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProduct) + environments.POST("/:productName/auto", AutoCreateProduct) + environments.PUT("/:productName/autoUpdate", gin2.UpdateOperationLogStatus, AutoUpdateProduct) + environments.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateProduct) - environments.POST("/:productName/helm", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateHelmProduct) - environments.PUT("/:productName/multiHelmEnv", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) + environments.POST("/:productName/helm", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateHelmProduct) + environments.PUT("/:productName/multiHelmEnv", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) - environments.POST("/:productName", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProduct) - environments.PUT("/:productName/envRecycle", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) - environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) + environments.POST("/:productName", gin2.UpdateOperationLogStatus, UpdateProduct) + environments.PUT("/:productName/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) + environments.PUT("/:productName/renderchart", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) - environments.PUT("/:productName", gin2.IsHavePermission([]string{permission.TestEnvShareUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, EnvShare) + environments.PUT("/:productName", gin2.UpdateOperationLogStatus, EnvShare) environments.GET("/:productName", GetProduct) environments.GET("/:productName/productInfo", GetProductInfo) environments.GET("/:productName/ingressInfo", GetProductIngress) environments.GET("/:productName/helmRenderCharts", ListRenderCharts) - environments.DELETE("/:productName", gin2.IsHavePermission([]string{permission.TestEnvDeleteUUID, permission.ProdEnvDeleteUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProduct) + environments.DELETE("/:productName", gin2.UpdateOperationLogStatus, DeleteProduct) environments.GET("/:productName/groups", ListGroups) environments.GET("/:productName/workloads", ListWorkloadsInEnv) environments.GET("/:productName/services/:serviceName", GetService) - environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateService) - environments.POST("/:productName/services/:serviceName/restart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, RestartService) - environments.POST("/:productName/services/:serviceName/restartNew", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID, permission.TestUpdateEnvUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, RestartNewService) - environments.POST("/:productName/services/:serviceName/scale/:number", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, ScaleService) - environments.POST("/:productName/services/:serviceName/scaleNew/:number", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, ScaleNewService) + environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.UpdateOperationLogStatus, UpdateService) + environments.POST("/:productName/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) + environments.POST("/:productName/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) + environments.POST("/:productName/services/:serviceName/scale/:number", gin2.UpdateOperationLogStatus, ScaleService) + environments.POST("/:productName/services/:serviceName/scaleNew/:number", gin2.UpdateOperationLogStatus, ScaleNewService) environments.GET("/:productName/services/:serviceName/containers/:container/namespaces/:namespace", GetServiceContainer) environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) // need to be deprecated - environments.PUT("/:productName/helmEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProduct) - environments.PUT("/:productName/helmEnvVariable", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductVariable) - environments.PUT("/:productName/updateMultiEnv", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateMultiHelmProduct) + environments.PUT("/:productName/helmEnv", gin2.UpdateOperationLogStatus, UpdateHelmProduct) + environments.PUT("/:productName/helmEnvVariable", gin2.UpdateOperationLogStatus, UpdateHelmProductVariable) + environments.PUT("/:productName/updateMultiEnv", gin2.UpdateOperationLogStatus, UpdateMultiHelmProduct) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index ddfc60ac..7d03a0a4 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -36,10 +34,10 @@ func (*Router) Inject(router *gin.RouterGroup) { Cluster.GET("", ListClusters) Cluster.GET("/:id", GetCluster) - Cluster.POST("", gin2.RequireSuperAdminAuth, CreateCluster) - Cluster.PUT("/:id", gin2.RequireSuperAdminAuth, UpdateCluster) - Cluster.DELETE("/:id", gin2.RequireSuperAdminAuth, DeleteCluster) - Cluster.PUT("/:id/disconnect", gin2.RequireSuperAdminAuth, DisconnectCluster) - Cluster.PUT("/:id/reconnect", gin2.RequireSuperAdminAuth, ReconnectCluster) + Cluster.POST("", CreateCluster) + Cluster.PUT("/:id", UpdateCluster) + Cluster.DELETE("/:id", DeleteCluster) + Cluster.PUT("/:id/disconnect", DisconnectCluster) + Cluster.PUT("/:id/reconnect", ReconnectCluster) } } diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index f1d90712..2744410a 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -41,12 +40,12 @@ func (*Router) Inject(router *gin.RouterGroup) { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) product.GET("/:name/searching-rules", GetCustomMatchRules) - product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) - product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) - product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) - product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) - product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) - product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) + product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) + product.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateProductTemplate) + product.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProductTemplate) + product.PUT("/:name/:status", gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) + product.PUT("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateProject) + product.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProductTemplate) } openSource := router.Group("opensource") diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 9bfbd2ad..3fabc8e8 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -40,8 +39,8 @@ func (*Router) Inject(router *gin.RouterGroup) { helm.GET("/:productName/:serviceName/serviceModule", GetHelmServiceModule) helm.GET("/:productName/:serviceName/filePath", GetFilePath) helm.GET("/:productName/:serviceName/fileContent", GetFileContent) - helm.POST("/services", gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ParamType), CreateOrUpdateHelmService) - helm.PUT("/:productName", gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ParamType), UpdateHelmService) + helm.POST("/services", CreateOrUpdateHelmService) + helm.PUT("/:productName", UpdateHelmService) } k8s := router.Group("services") @@ -49,10 +48,10 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.GET("", ListServiceTemplate) k8s.GET("/:name/:type", GetServiceTemplate) k8s.GET("/:name", GetServiceTemplateOption) - k8s.POST("", GetServiceTemplateProductName, gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateServiceTemplate) - k8s.PUT("", GetServiceTemplateObjectProductName, gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateServiceTemplate) + k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) + k8s.PUT("", GetServiceTemplateObjectProductName, gin2.UpdateOperationLogStatus, UpdateServiceTemplate) k8s.PUT("/yaml/validator", YamlValidator) - k8s.DELETE("/:name/:type", gin2.IsHavePermission([]string{permission.ServiceTemplateDeleteUUID}, permission.QueryType), gin2.UpdateOperationLogStatus, DeleteServiceTemplate) + k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) } @@ -77,7 +76,7 @@ func (*Router) Inject(router *gin.RouterGroup) { pm := router.Group("pm") { - pm.POST("/:productName", gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, CreatePMService) - pm.PUT("/:productName", gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) + pm.POST("/:productName", gin2.UpdateOperationLogStatus, CreatePMService) + pm.PUT("/:productName", gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) } } diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 83a49719..f0175bbc 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -35,11 +35,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- install := router.Group("install") { - install.POST("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreateInstall) - install.PUT("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateInstall) + install.POST("", gin2.UpdateOperationLogStatus, CreateInstall) + install.PUT("", gin2.UpdateOperationLogStatus, UpdateInstall) install.GET("/:name/:version", GetInstall) install.GET("", ListInstalls) - install.PUT("/delete", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteInstall) + install.PUT("/delete", gin2.UpdateOperationLogStatus, DeleteInstall) } // --------------------------------------------------------------------------------------- @@ -49,9 +49,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { proxyManage.GET("", ListProxies) proxyManage.GET("/:id", GetProxy) - proxyManage.POST("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreateProxy) - proxyManage.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateProxy) - proxyManage.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteProxy) + proxyManage.POST("", gin2.UpdateOperationLogStatus, CreateProxy) + proxyManage.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateProxy) + proxyManage.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteProxy) proxyManage.POST("/connectionTest", TestConnection) } @@ -61,11 +61,11 @@ func (*Router) Inject(router *gin.RouterGroup) { registry.GET("", ListRegistries) // 获取默认的镜像仓库配置,用于kodespace CLI调用 registry.GET("/namespaces/default", GetDefaultRegistryNamespace) - registry.GET("/namespaces", gin2.RequireSuperAdminAuth, ListRegistryNamespaces) - registry.POST("/namespaces", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreateRegistryNamespace) - registry.PUT("/namespaces/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateRegistryNamespace) + registry.GET("/namespaces", ListRegistryNamespaces) + registry.POST("/namespaces", gin2.UpdateOperationLogStatus, CreateRegistryNamespace) + registry.PUT("/namespaces/:id", gin2.UpdateOperationLogStatus, UpdateRegistryNamespace) - registry.DELETE("/namespaces/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteRegistryNamespace) + registry.DELETE("/namespaces/:id", gin2.UpdateOperationLogStatus, DeleteRegistryNamespace) registry.GET("/release/repos", ListAllRepos) registry.POST("/images", ListImages) registry.GET("/images/repos/:name", ListRepoImages) @@ -74,10 +74,10 @@ func (*Router) Inject(router *gin.RouterGroup) { s3storage := router.Group("s3storage") { s3storage.GET("", ListS3Storage) - s3storage.POST("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreateS3Storage) + s3storage.POST("", gin2.UpdateOperationLogStatus, CreateS3Storage) s3storage.GET("/:id", GetS3Storage) - s3storage.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateS3Storage) - s3storage.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteS3Storage) + s3storage.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateS3Storage) + s3storage.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteS3Storage) } //系统清理缓存 @@ -93,8 +93,8 @@ func (*Router) Inject(router *gin.RouterGroup) { github := router.Group("githubApp") { github.GET("", GetGithubApp) - github.POST("", gin2.RequireSuperAdminAuth, CreateGithubApp) - github.DELETE("/:id", gin2.RequireSuperAdminAuth, DeleteGithubApp) + github.POST("", CreateGithubApp) + github.DELETE("/:id", DeleteGithubApp) } // --------------------------------------------------------------------------------------- @@ -102,13 +102,13 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- jenkins := router.Group("jenkins") { - jenkins.POST("/integration", gin2.RequireSuperAdminAuth, CreateJenkinsIntegration) + jenkins.POST("/integration", CreateJenkinsIntegration) jenkins.GET("/integration", ListJenkinsIntegration) - jenkins.PUT("/integration/:id", gin2.RequireSuperAdminAuth, UpdateJenkinsIntegration) - jenkins.DELETE("/integration/:id", gin2.RequireSuperAdminAuth, DeleteJenkinsIntegration) - jenkins.POST("/user/connection", gin2.RequireSuperAdminAuth, TestJenkinsConnection) - jenkins.GET("/jobNames", gin2.RequireSuperAdminAuth, ListJobNames) - jenkins.GET("/buildArgs/:jobName", gin2.RequireSuperAdminAuth, ListJobBuildArgs) + jenkins.PUT("/integration/:id", UpdateJenkinsIntegration) + jenkins.DELETE("/integration/:id", DeleteJenkinsIntegration) + jenkins.POST("/user/connection", TestJenkinsConnection) + jenkins.GET("/jobNames", ListJobNames) + jenkins.GET("/buildArgs/:jobName", ListJobBuildArgs) } //系统配额 @@ -128,9 +128,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { basicImages.GET("", ListBasicImages) basicImages.GET("/:id", GetBasicImage) - basicImages.POST("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreateBasicImage) - basicImages.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdateBasicImage) - basicImages.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeleteBasicImage) + basicImages.POST("", gin2.UpdateOperationLogStatus, CreateBasicImage) + basicImages.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateBasicImage) + basicImages.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteBasicImage) } // --------------------------------------------------------------------------------------- @@ -139,9 +139,9 @@ func (*Router) Inject(router *gin.RouterGroup) { integration := router.Group("helm") { integration.GET("", ListHelmRepos) - integration.POST("", gin2.RequireSuperAdminAuth, CreateHelmRepo) - integration.PUT("/:id", gin2.RequireSuperAdminAuth, UpdateHelmRepo) - integration.DELETE("/:id", gin2.RequireSuperAdminAuth, DeleteHelmRepo) + integration.POST("", CreateHelmRepo) + integration.PUT("/:id", UpdateHelmRepo) + integration.DELETE("/:id", DeleteHelmRepo) } // --------------------------------------------------------------------------------------- @@ -151,9 +151,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { privateKey.GET("", ListPrivateKeys) privateKey.GET("/:id", GetPrivateKey) - privateKey.POST("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreatePrivateKey) - privateKey.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdatePrivateKey) - privateKey.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeletePrivateKey) + privateKey.POST("", gin2.UpdateOperationLogStatus, CreatePrivateKey) + privateKey.PUT("/:id", gin2.UpdateOperationLogStatus, UpdatePrivateKey) + privateKey.DELETE("/:id", gin2.UpdateOperationLogStatus, DeletePrivateKey) } notification := router.Group("notification") @@ -169,18 +169,18 @@ func (*Router) Inject(router *gin.RouterGroup) { announcement := router.Group("announcement") { - announcement.POST("", gin2.RequireSuperAdminAuth, CreateAnnouncement) - announcement.PUT("/update", gin2.RequireSuperAdminAuth, UpdateAnnouncement) - announcement.GET("/all", gin2.RequireSuperAdminAuth, PullAllAnnouncement) + announcement.POST("", CreateAnnouncement) + announcement.PUT("/update", UpdateAnnouncement) + announcement.GET("/all", PullAllAnnouncement) announcement.GET("", PullNotifyAnnouncement) - announcement.DELETE("/:id", gin2.RequireSuperAdminAuth, DeleteAnnouncement) + announcement.DELETE("/:id", DeleteAnnouncement) } operation := router.Group("operation") { - operation.GET("", gin2.RequireSuperAdminAuth, GetOperationLogs) - operation.POST("", gin2.RequireSuperAdminAuth, AddSystemOperationLog) - operation.PUT("/:id", gin2.RequireSuperAdminAuth, UpdateOperationLog) + operation.GET("", GetOperationLogs) + operation.POST("", AddSystemOperationLog) + operation.PUT("/:id", UpdateOperationLog) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index b1b1afe7..25b9bd66 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -24,7 +24,6 @@ import ( gin2 "github.com/koderover/zadig/pkg/middleware/gin" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -89,10 +88,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { pipeline.GET("", ListPipelines) pipeline.GET("/:name", GetPipeline) - pipeline.POST("", GetPipelineProductName, gin2.IsHavePermission([]string{permission.WorkflowCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpsertPipeline) - pipeline.POST("/old/:old/new/:new", GetProductNameByPipeline, gin2.IsHavePermission([]string{permission.WorkflowCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CopyPipeline) - pipeline.PUT("/rename/:old/:new", GetProductNameByPipeline, gin2.IsHavePermission([]string{permission.WorkflowUpdateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, RenamePipeline) - pipeline.DELETE("/:name", GetProductNameByPipeline, gin2.IsHavePermission([]string{permission.WorkflowDeleteUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, DeletePipeline) + pipeline.POST("", GetPipelineProductName, gin2.UpdateOperationLogStatus, UpsertPipeline) + pipeline.POST("/old/:old/new/:new", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, CopyPipeline) + pipeline.PUT("/rename/:old/:new", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, RenamePipeline) + pipeline.DELETE("/:name", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, DeletePipeline) } // --------------------------------------------------------------------------------------- @@ -109,11 +108,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- taskV2 := router.Group("v2/tasks") { - taskV2.POST("", GetProductNameByPipelineTask, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreatePipelineTask) + taskV2.POST("", GetProductNameByPipelineTask, gin2.UpdateOperationLogStatus, CreatePipelineTask) taskV2.GET("/max/:max/start/:start/pipelines/:name", ListPipelineTasksResult) taskV2.GET("/id/:id/pipelines/:name", GetPipelineTask) - taskV2.POST("/id/:id/pipelines/:name/restart", GetProductNameByPipeline, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, RestartPipelineTask) - taskV2.DELETE("/id/:id/pipelines/:name", GetProductNameByPipeline, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CancelTaskV2) + taskV2.POST("/id/:id/pipelines/:name/restart", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, RestartPipelineTask) + taskV2.DELETE("/id/:id/pipelines/:name", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, CancelTaskV2) taskV2.GET("/pipelines/:name/products", ListPipelineUpdatableProductNames) taskV2.GET("/file", GetPackageFile) taskV2.GET("/workflow/:pipelineName/taskId/:taskId", GetArtifactFile) @@ -135,12 +134,12 @@ func (*Router) Inject(router *gin.RouterGroup) { workflow := router.Group("workflow") { workflow.POST("/:productName/auto", AutoCreateWorkflow) - workflow.POST("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateWorkflow) - workflow.PUT("", GetWorkflowProductName, gin2.IsHavePermission([]string{permission.WorkflowUpdateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateWorkflow) + workflow.POST("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateWorkflow) + workflow.PUT("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) - workflow.DELETE("/:name", GetProductNameByWorkflow, gin2.IsHavePermission([]string{permission.WorkflowDeleteUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, DeleteWorkflow) + workflow.DELETE("/:name", GetProductNameByWorkflow, gin2.UpdateOperationLogStatus, DeleteWorkflow) workflow.GET("/preset/:productName", PreSetWorkflow) workflow.PUT("/old/:old/new/:new", CopyWorkflow) @@ -154,12 +153,12 @@ func (*Router) Inject(router *gin.RouterGroup) { //todo 修改权限的uuid workflowtask.GET("/targets/:productName/:namespace", GetWorkflowArgs) workflowtask.GET("/preset/:namespace/:workflowName", PresetWorkflowArgs) - workflowtask.POST("", GetWorkflowTaskProductName, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateWorkflowTask) - workflowtask.PUT("", GetWorkflowTaskProductName, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) + workflowtask.POST("", GetWorkflowTaskProductName, gin2.UpdateOperationLogStatus, CreateWorkflowTask) + workflowtask.PUT("", GetWorkflowTaskProductName, gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) workflowtask.GET("/max/:max/start/:start/pipelines/:name", ListWorkflowTasksResult) workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) - workflowtask.POST("/id/:id/pipelines/:name/restart", GetWorkflowTaskProductNameByTask, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, RestartWorkflowTask) - workflowtask.DELETE("/id/:id/pipelines/:name", GetWorkflowTaskProductNameByTask, gin2.IsHavePermission([]string{permission.WorkflowTaskUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) + workflowtask.POST("/id/:id/pipelines/:name/restart", GetWorkflowTaskProductNameByTask, gin2.UpdateOperationLogStatus, RestartWorkflowTask) + workflowtask.DELETE("/id/:id/pipelines/:name", GetWorkflowTaskProductNameByTask, gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) } serviceTask := router.Group("servicetask") diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/router.go b/pkg/microservice/aslan/core/workflow/testing/handler/router.go index 48f4e4c0..ccc94293 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/router.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/types/permission" ) type Router struct{} @@ -47,11 +46,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- tester := router.Group("test") { - tester.POST("", GetTestProductName, gin2.IsHavePermission([]string{permission.TestManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateTestModule) - tester.PUT("", GetTestProductName, gin2.IsHavePermission([]string{permission.TestManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateTestModule) + tester.POST("", GetTestProductName, gin2.UpdateOperationLogStatus, CreateTestModule) + tester.PUT("", GetTestProductName, gin2.UpdateOperationLogStatus, UpdateTestModule) tester.GET("", ListTestModules) tester.GET("/:name", GetTestModule) - tester.DELETE("/:name", gin2.IsHavePermission([]string{permission.TestDeleteUUID}, permission.QueryType), gin2.UpdateOperationLogStatus, DeleteTestModule) + tester.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteTestModule) } testStat := router.Group("teststat") diff --git a/pkg/microservice/podexec/core/service/auth.go b/pkg/microservice/podexec/core/service/auth.go deleted file mode 100644 index e45b87fe..00000000 --- a/pkg/microservice/podexec/core/service/auth.go +++ /dev/null @@ -1,150 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "encoding/json" - "net/http" - "strconv" - "strings" - - "github.com/gorilla/mux" - "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" - - configbase "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/microservice/podexec/config" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" - "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/types/permission" -) - -func AuthMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - url := r.URL.Path - if strings.Contains("/api/health/", url) { - next.ServeHTTP(w, r) - return - } - - authorization := r.Header.Get(setting.AuthorizationHeader) - if authorization != "" { - if strings.Contains(authorization, setting.RootAPIKey) { - token := strings.Split(authorization, " ") - if len(token) == 2 && token[1] == config.PoetryAPIRootKey() { - next.ServeHTTP(w, r) - } - return - } - - if strings.Contains(authorization, setting.UserAPIKey) { - token := strings.Split(authorization, " ") - if len(token) == 2 { - //根据token获取用户 - userInfo, err := poetry.GetUserDetailByToken(configbase.PoetryServiceAddress(), token[1]) - if err != nil { - w.WriteHeader(http.StatusUnauthorized) - _ = json.NewEncoder(w).Encode(&EndpointResponse{ResultCode: http.StatusUnauthorized, ErrorMsg: "auth failed"}) - return - } - - r.Header.Set("userId", strconv.Itoa(userInfo.ID)) - r.Header.Set("isSuperUser", strconv.FormatBool(userInfo.IsSuperUser)) - next.ServeHTTP(w, r) - } - return - } - } else if token, err := r.Cookie("TOKEN"); err == nil { - userInfo, err := poetry.GetUserDetailByToken(configbase.PoetryServiceAddress(), token.Value) - if err != nil { - log.Errorf("PoetryRequest err:%v", err) - w.WriteHeader(http.StatusUnauthorized) - _ = json.NewEncoder(w).Encode(&EndpointResponse{ResultCode: http.StatusUnauthorized, ErrorMsg: "auth failed"}) - return - } - - r.Header.Set("userId", strconv.Itoa(userInfo.ID)) - r.Header.Set("isSuperUser", strconv.FormatBool(userInfo.IsSuperUser)) - next.ServeHTTP(w, r) - } - }) -} - -func PermissionMiddleware(next http.Handler) http.Handler { - logger := log.SugaredLogger() - - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - url := r.URL.Path - if strings.Contains("/api/health/", url) { - next.ServeHTTP(w, r) - return - } - pathParams := mux.Vars(r) - productName := pathParams["productName"] - userIDStr := r.Header.Get("userId") - isSuperUserStr := r.Header.Get("isSuperUser") - isSuperUser, parseErr := strconv.ParseBool(isSuperUserStr) - if isSuperUser { - next.ServeHTTP(w, r) - return - } - userID, atoiErr := strconv.Atoi(userIDStr) - if parseErr != nil || atoiErr != nil { - w.WriteHeader(http.StatusForbidden) - _ = json.NewEncoder(w).Encode(&EndpointResponse{ResultCode: http.StatusForbidden, ErrorMsg: "forbidden!"}) - return - } - - queryList := r.URL.Query() - clusterID := queryList.Get("clusterId") - if clusterID == "" { - isHaveTestEnvManagePermission := PoetryRequestPermission(userID, permission.TestEnvManageUUID, productName, logger) - if isHaveTestEnvManagePermission { - next.ServeHTTP(w, r) - return - } - } else { - isHaveProdEnvManagePermission := PoetryRequestPermission(userID, permission.ProdEnvManageUUID, productName, logger) - if isHaveProdEnvManagePermission { - next.ServeHTTP(w, r) - return - } - } - - w.WriteHeader(http.StatusForbidden) - _ = json.NewEncoder(w).Encode(&EndpointResponse{ResultCode: http.StatusForbidden, ErrorMsg: "forbidden!"}) - }) -} - -func PoetryRequestPermission(userID int, permissionUUID, productName string, logger *zap.SugaredLogger) bool { - poetryClient := poetry.New(configbase.PoetryServiceAddress(), config.PoetryAPIRootKey()) - if poetryClient.HasOperatePermission(productName, permissionUUID, userID, false, logger) { - return true - } - - uuids, err := poetryClient.GetUserPermissionUUIDs(setting.RoleUserID, "", logger) - if err != nil { - log.Errorf("GetUserPermissionUUIDs error: %v", err) - return false - } - if sets.NewString(uuids...).Has(permissionUUID) { - return true - } - - return false -} diff --git a/pkg/microservice/podexec/server/server.go b/pkg/microservice/podexec/server/server.go index 55f12b92..09eb0fda 100644 --- a/pkg/microservice/podexec/server/server.go +++ b/pkg/microservice/podexec/server/server.go @@ -43,8 +43,6 @@ func Serve(ctx context.Context) error { _ = json.NewEncoder(w).Encode(map[string]string{"message": "success"}) }) router.HandleFunc("/api/{productName}/{namespace}/{podName}/{containerName}/podExec", service.ServeWs) - router.Use(service.AuthMiddleware) - router.Use(service.PermissionMiddleware) server := &http.Server{ Addr: "0.0.0.0:27000", diff --git a/pkg/middleware/gin/permission.go b/pkg/middleware/gin/permission.go deleted file mode 100644 index 8f5a690c..00000000 --- a/pkg/middleware/gin/permission.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gin - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" - "github.com/koderover/zadig/pkg/types/permission" - "github.com/koderover/zadig/pkg/util/ginzap" -) - -// RequireSuperAdminAuth require super user role -func RequireSuperAdminAuth(c *gin.Context) { - log := ginzap.WithContext(c).Sugar() - username := c.GetString(setting.SessionUsername) - user, err := authUser(username, c) - if err != nil { - log.Errorf("authUser(%s) failed, %v", username, err) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - if user.IsSuperUser { - c.Next() - return - } - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"message": "Require super user permission"}) -} - -//判断用户是否有某个权限 -func IsHavePermission(permissionUUIDs []string, paramType int) gin.HandlerFunc { - return func(c *gin.Context) { - log := ginzap.WithContext(c).Sugar() - username := c.GetString(setting.SessionUsername) - user, err := authUser(username, c) - if err != nil { - log.Errorf("authUser(%s) failed, %v", username, err) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - - if user.IsSuperUser { - c.Next() - return - } - - var productName string - poetryClient := poetry.New(config.PoetryServiceAddress(), config.PoetryAPIRootKey()) - - if paramType == permission.ParamType { - productName = c.Param("name") - if productName == "" { - productName = c.Param("productName") - } - } else if paramType == permission.QueryType { - productName = c.Query("productName") - if productName == "" { - productName = c.Query("productTmpl") - } - } else if paramType == permission.ContextKeyType { - productName = c.GetString("productName") - } - - if productName == "" { - log.Errorf("productName is null") - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - - //其他权限 - permissionUUID := "" - if len(permissionUUIDs) == 1 { - permissionUUID = permissionUUIDs[0] - } else if len(permissionUUIDs) == 2 || len(permissionUUIDs) == 3 { - envType := c.Query("envType") - switch envType { - case setting.ProdENV: - permissionUUID = permissionUUIDs[1] - default: - permissionUUID = permissionUUIDs[0] - } - } - - //判断用户是否有操作的权限uuid - if rolePermissionMap, err := poetryClient.GetUserPermissionUUIDMap(productName, permissionUUID, user.ID, log); err == nil { - if rolePermissionMap["isContain"] { - c.Next() - return - } - } else { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - - //判断用户是否有环境授权权限uuid - if roleEnvPermissions, err := poetryClient.ListEnvRolePermission(productName, "", 0, log); err == nil { - for _, roleEnvPermission := range roleEnvPermissions { - if roleEnvPermission.PermissionUUID == permissionUUID { - c.Next() - return - } - } - } else { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - - if len(permissionUUIDs) == 3 { - permissionUUID = permissionUUIDs[2] - if rolePermissionMap, err := poetryClient.GetUserPermissionUUIDMap(productName, permissionUUID, user.ID, log); err == nil { - if rolePermissionMap["isContain"] { - c.Next() - return - } - } else { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - } - - // 判断项目里面是否有all-users设置,以及该有的权限 - productRole, _ := poetryClient.ListRoles(productName, log) - if productRole != nil { - uuids, err := poetryClient.GetUserPermissionUUIDs(productRole.ID, productName, log) - if err != nil { - log.Errorf("GetUserPermissionUUIDs error: %v", err) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - if sets.NewString(uuids...).Has(permissionUUID) { - c.Next() - return - } - } - - //判断普通用户的默认权限是否包含 - uuids, err := poetryClient.GetUserPermissionUUIDs(setting.RoleUserID, "", log) - if err != nil { - log.Errorf("GetUserPermissionUUIDs error: %v", err) - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"message": "Authentication failed."}) - return - } - if sets.NewString(uuids...).Has(permissionUUID) { - c.Next() - return - } - - c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"message": "对不起,您没有权限访问!"}) - } -} - -func authUser(username interface{}, c *gin.Context) (*permission.User, error) { - - username, ok := username.(string) - if !ok || username == "" { - return nil, fmt.Errorf("empty user id") - } - - userInfo, isExist := c.Get(setting.SessionUser) - user := new(poetry.UserInfo) - if isExist { - user = userInfo.(*poetry.UserInfo) - } - //说明用户可能已经删除 - if user.Name == "" { - return nil, fmt.Errorf("user %s has been disabled", username) - } - - return poetry.ConvertUserInfo(user), nil -} diff --git a/pkg/types/permission/permission.go b/pkg/types/permission/permission.go index abbbfaf7..b80b724b 100644 --- a/pkg/types/permission/permission.go +++ b/pkg/types/permission/permission.go @@ -17,50 +17,6 @@ limitations under the License. package permission const ( - SuperUserUUID = "00000" //超级用户权限 - //产品工作流相关 - WorkflowTaskUUID = "30001" //产品工作流(执行任务、重启任务、取消任务) - WorkflowUpdateUUID = "30002" //产品工作流(更新) - WorkflowCreateUUID = "30003" //产品工作流(创建) - WorkflowDeleteUUID = "30004" //产品工作流(删除) - WorkflowListUUID = "30005" //产品工作流(查看) - WorkflowDeliveryUUID = "30006" //产品工作流(添加交付) - - //集成环境(测试环境) - TestEnvCreateUUID = "40001" //创建环境 - TestEnvDeleteUUID = "40002" //删除环境 - TestEnvManageUUID = "40003" //环境管理(环境更新、单服务更新、服务伸缩、服务重启、configmap更新、configmap回滚、重启实例、yaml导出) - TestEnvListUUID = "40004" //环境查看 - TestEnvShareUUID = "40005" //环境授权 - TestUpdateEnvUUID = "40010" //更新环境 - - //集成环境(生产环境) - ProdEnvListUUID = "40006" //环境查看 - ProdEnvCreateUUID = "40007" //创建环境 - ProdEnvManageUUID = "40008" //环境管理(环境更新、单服务更新、服务伸缩、服务重启、configMap更新、configMap回滚、重启实例、yaml导出) - ProdEnvDeleteUUID = "40009" //删除环境 - - //原项目管理相关 - //构建管理 - BuildDeleteUUID = "50007" //删除构建配置 - BuildManageUUID = "50008" //构建配置管理(创建构建、修改构建、修改服务组件) - BuildListUUID = "50019" //构建配置查看 - - //服务管理 - ServiceTemplateEditUUID = "50022" //修改服务编排 - ServiceTemplateManageUUID = "50023" //服务模板管理(创建服务模板、修改服务模板) - ServiceTemplateDeleteUUID = "50024" //删除服务模板 - ServiceTemplateListUUID = "50025" //查看服务模板 - - //测试管理 - TestDeleteUUID = "50010" //删除测试 - TestManageUUID = "50011" //测试管理(创建测试、修改测试) - TestListUUID = "50020" //查看测试 - - //交付中心 - ReleaseDeleteUUID = "70001" //交付中心删除版本 - - ParamType = 1 - QueryType = 2 - ContextKeyType = 3 + WorkflowUpdateUUID = "30002" //产品工作流(更新) + TestManageUUID = "50011" //测试管理(创建测试、修改测试) ) -- Gitee From ec00c105d9eaa21727d26213984c506fb84f7132 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 18:40:24 +0800 Subject: [PATCH 0091/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 7 ++++++- pkg/microservice/policy/core/repository/mongodb/role.go | 5 ++++- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 2 +- pkg/microservice/policy/core/service/role.go | 6 +++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index e96bdec7..64c927d1 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -85,6 +85,11 @@ func DeleteRole(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("args name can't be empty") return } - ctx.Err = service.DeleteGlobalRole(c.Param("name"), ctx.Logger) + projectName := c.Query("projectName") + if name == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + ctx.Err = service.DeleteRole(name, projectName, ctx.Logger) return } diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 339acf2b..48c9db65 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -107,8 +107,11 @@ func (c *RoleColl) Create(obj *models.Role) error { return err } -func (c *RoleColl) Delete(name string) error { +func (c *RoleColl) Delete(name string, projectName string) error { query := bson.M{"name": name} + if projectName != "" { + query["namespace"] = projectName + } _, err := c.DeleteOne(context.TODO(), query) return err } diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 3089d1c6..d7838739 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -331,7 +331,7 @@ func GenerateOPABundle() error { log.Info("Generating OPA bundle") defer log.Info("OPA bundle is generated") - rs, err := mongodb.NewRoleColl().List() + rs, err := mongodb.NewRoleColl().List("") if err != nil { log.Errorf("Failed to list roles, err: %s", err) } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index e71ecf63..2d3625eb 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -76,5 +76,9 @@ func ListGlobalRole(_ *zap.SugaredLogger) (roles []*Role, err error) { } func DeleteGlobalRole(name string, _ *zap.SugaredLogger) (err error) { - return mongodb.NewRoleColl().Delete(name) + return mongodb.NewRoleColl().Delete(name, "") +} + +func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) (err error) { + return mongodb.NewRoleColl().Delete(name, projectName) } -- Gitee From 92c6c8ec34a808a8c76b1d1985ae195636d7ff02 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 18:54:37 +0800 Subject: [PATCH 0092/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 8 +++---- .../policy/core/handler/router.go | 4 ++-- .../policy/core/repository/mongodb/role.go | 2 +- pkg/microservice/policy/core/service/role.go | 21 ++----------------- 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 64c927d1..0a330720 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -43,7 +43,7 @@ func CreateRole(c *gin.Context) { ctx.Err = service.CreateRole(projectName, args, ctx.Logger) } -func ListRole(c *gin.Context) { +func ListRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -52,7 +52,7 @@ func ListRole(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") return } - ctx.Resp, ctx.Err = service.ListRole(projectName) + ctx.Resp, ctx.Err = service.ListRoles(projectName) return } @@ -69,11 +69,11 @@ func CreateGlobalRole(c *gin.Context) { ctx.Err = service.CreateRole("", args, ctx.Logger) } -func ListGlobalRole(c *gin.Context) { +func ListGlobalRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListGlobalRole(ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoles("") return } diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index e49800d4..75d74555 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,14 +26,14 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) - roles.GET("", ListRole) + roles.GET("", ListRoles) roles.DELETE("/:name", DeleteRole) } globalRoles := router.Group("global-roles") { globalRoles.POST("", CreateGlobalRole) - globalRoles.GET("", ListGlobalRole) + globalRoles.GET("", ListGlobalRoles) globalRoles.DELETE("/:name", DeleteRole) } diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 48c9db65..aeb448cb 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -107,7 +107,7 @@ func (c *RoleColl) Create(obj *models.Role) error { return err } -func (c *RoleColl) Delete(name string, projectName string) error { +func (c *RoleColl) DeleteBy(name string, projectName string) error { query := bson.M{"name": name} if projectName != "" { query["namespace"] = projectName diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 2d3625eb..b55c85e5 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -25,7 +25,7 @@ import ( type Role struct { Name string `json:"name"` - Rules []*Rule `json:"rules"` + Rules []*Rule `json:"rules,omitempty"` } type Rule struct { @@ -49,7 +49,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().Create(obj) } -func ListRole(projectName string) (roles []*Role, err error) { +func ListRoles(projectName string) (roles []*Role, err error) { projectRoles, err := mongodb.NewRoleColl().List(projectName) if err != nil { return nil, err @@ -62,23 +62,6 @@ func ListRole(projectName string) (roles []*Role, err error) { return roles, nil } -func ListGlobalRole(_ *zap.SugaredLogger) (roles []*Role, err error) { - globalRoles, err := mongodb.NewRoleColl().List("") - if err != nil { - return nil, err - } - for _, v := range globalRoles { - roles = append(roles, &Role{ - Name: v.Name, - }) - } - return roles, nil -} - -func DeleteGlobalRole(name string, _ *zap.SugaredLogger) (err error) { - return mongodb.NewRoleColl().Delete(name, "") -} - func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) (err error) { return mongodb.NewRoleColl().Delete(name, projectName) } -- Gitee From 76cc998ad651e9ab101e94daff6883de96faf298 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 18:56:29 +0800 Subject: [PATCH 0093/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 4 ++-- pkg/microservice/policy/core/service/role.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 0a330720..4f2bd323 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -52,7 +52,7 @@ func ListRoles(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") return } - ctx.Resp, ctx.Err = service.ListRoles(projectName) + ctx.Resp, ctx.Err = service.ListRoles(projectName, ctx.Logger) return } @@ -73,7 +73,7 @@ func ListGlobalRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListRoles("") + ctx.Resp, ctx.Err = service.ListRoles("", ctx.Logger) return } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index b55c85e5..5eadea35 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -49,7 +49,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().Create(obj) } -func ListRoles(projectName string) (roles []*Role, err error) { +func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { projectRoles, err := mongodb.NewRoleColl().List(projectName) if err != nil { return nil, err @@ -63,5 +63,5 @@ func ListRoles(projectName string) (roles []*Role, err error) { } func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) (err error) { - return mongodb.NewRoleColl().Delete(name, projectName) + return mongodb.NewRoleColl().DeleteBy(name, projectName) } -- Gitee From 3f1f68b68ffc1008bbcf835521fa39842c63faab Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 19:13:13 +0800 Subject: [PATCH 0094/2647] roles policy service Signed-off-by: mouuii --- .../policy/core/repository/mongodb/role.go | 20 +++++++++++++++++-- .../policy/core/service/bundle/opa_bundle.go | 2 +- pkg/microservice/policy/core/service/role.go | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index aeb448cb..1e57e22c 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -77,7 +77,24 @@ func (c *RoleColl) Get(ns, name string) (*models.Role, bool, error) { return res, true, nil } -func (c *RoleColl) List(projectName string) ([]*models.Role, error) { +func (c *RoleColl) List() ([]*models.Role, error) { + var res []*models.Role + + ctx := context.Background() + + cursor, err := c.Collection.Find(ctx, bson.M{}) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *RoleColl) ListBy(projectName string) ([]*models.Role, error) { var res []*models.Role ctx := context.Background() @@ -93,7 +110,6 @@ func (c *RoleColl) List(projectName string) ([]*models.Role, error) { if err != nil { return nil, err } - return res, nil } diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index d7838739..3089d1c6 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -331,7 +331,7 @@ func GenerateOPABundle() error { log.Info("Generating OPA bundle") defer log.Info("OPA bundle is generated") - rs, err := mongodb.NewRoleColl().List("") + rs, err := mongodb.NewRoleColl().List() if err != nil { log.Errorf("Failed to list roles, err: %s", err) } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 5eadea35..fb3e42a4 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -50,7 +50,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { } func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { - projectRoles, err := mongodb.NewRoleColl().List(projectName) + projectRoles, err := mongodb.NewRoleColl().ListBy(projectName) if err != nil { return nil, err } -- Gitee From 7db96b6faf5bbdc73766e448b4c2ecf80ec9c94a Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 19:17:42 +0800 Subject: [PATCH 0095/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 4 ---- pkg/microservice/policy/core/repository/mongodb/role.go | 6 ++---- pkg/microservice/policy/core/service/role.go | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 4f2bd323..a8fad57f 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -81,10 +81,6 @@ func DeleteRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - if name == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args name can't be empty") - return - } projectName := c.Query("projectName") if name == "" { ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 1e57e22c..28ff780d 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -123,11 +123,9 @@ func (c *RoleColl) Create(obj *models.Role) error { return err } -func (c *RoleColl) DeleteBy(name string, projectName string) error { +func (c *RoleColl) Delete(name string, projectName string) error { query := bson.M{"name": name} - if projectName != "" { - query["namespace"] = projectName - } + query["namespace"] = projectName _, err := c.DeleteOne(context.TODO(), query) return err } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index fb3e42a4..72a97457 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -63,5 +63,5 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err err } func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) (err error) { - return mongodb.NewRoleColl().DeleteBy(name, projectName) + return mongodb.NewRoleColl().Delete(name, projectName) } -- Gitee From b7c5e39523e07ed9df188e513d886759a73bc053 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 19:32:04 +0800 Subject: [PATCH 0096/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/repository/mongodb/role.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 28ff780d..a8130617 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -98,8 +98,7 @@ func (c *RoleColl) ListBy(projectName string) ([]*models.Role, error) { var res []*models.Role ctx := context.Background() - query := bson.M{} - query["namespace"] = projectName + query := bson.M{"namespace": projectName} cursor, err := c.Collection.Find(ctx, query) if err != nil { @@ -124,8 +123,7 @@ func (c *RoleColl) Create(obj *models.Role) error { } func (c *RoleColl) Delete(name string, projectName string) error { - query := bson.M{"name": name} - query["namespace"] = projectName + query := bson.M{"name": name, "namespace": projectName} _, err := c.DeleteOne(context.TODO(), query) return err } -- Gitee From ad5f7804c402e8f3bed790bf1845bac669fb687f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 19:35:18 +0800 Subject: [PATCH 0097/2647] roles policy service Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index a8fad57f..d202a15c 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -82,10 +82,6 @@ func DeleteRole(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") projectName := c.Query("projectName") - if name == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } ctx.Err = service.DeleteRole(name, projectName, ctx.Logger) return } -- Gitee From 01a69357291d7553e3a5fb71eb494843ddc9d14d Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 19 Oct 2021 20:05:19 +0800 Subject: [PATCH 0098/2647] remove auth check Signed-off-by: lou --- pkg/microservice/aslan/config/config.go | 4 -- .../core/common/service/collie/client.go | 9 +--- .../aslan/core/common/service/git/hook.go | 2 +- .../aslan/core/common/service/product.go | 2 +- .../core/common/service/template_product.go | 6 +-- .../aslan/core/common/service/utils.go | 2 +- .../core/environment/service/environment.go | 2 +- .../aslan/core/environment/service/product.go | 2 +- .../core/environment/service/revision.go | 2 +- .../aslan/core/project/service/product.go | 10 ++--- .../aslan/core/setting/service/user.go | 2 +- .../aslan/core/system/service/capacity.go | 2 +- .../workflow/service/workflow/nsq_handlers.go | 4 +- .../service/workflow/pipeline_controller.go | 2 +- .../service/workflow/pipeline_task.go | 2 +- .../service/workflow/workflow_task.go | 2 +- pkg/microservice/cron/config/config.go | 8 ---- .../cron/core/service/client/caller.go | 4 +- .../cron/core/service/client/client.go | 4 +- .../cron/core/service/client/collie_client.go | 9 +--- .../cron/core/service/client/cronjob.go | 4 +- .../cron/core/service/client/env.go | 6 --- .../cron/core/service/client/operation.go | 2 - .../cron/core/service/client/pipeline.go | 2 - .../cron/core/service/client/sonar.go | 4 -- .../cron/core/service/client/testing.go | 2 - .../cron/core/service/client/workflow.go | 2 - .../cron/core/service/client/workflow_test.go | 42 ------------------ .../core/service/scheduler/cronjob_handler.go | 5 --- .../cron/core/service/scheduler/scheduler.go | 6 +-- .../core/service/taskplugin/build.go | 2 +- .../core/service/taskplugin/deploy.go | 2 - .../warpdrive/core/service/taskplugin/jira.go | 2 +- .../core/service/taskplugin/security.go | 3 -- pkg/middleware/gin/audit_log.go | 2 +- pkg/setting/consts.go | 3 -- pkg/shared/client/aslan/client.go | 19 +++----- pkg/shared/client/aslanx/client.go | 9 +--- pkg/shared/codehost/codehost.go | 2 +- pkg/shared/handler/base.go | 2 +- pkg/shared/poetry/auth.go | 43 ------------------- pkg/shared/poetry/client.go | 9 +--- pkg/shared/poetry/jira.go | 4 +- 43 files changed, 49 insertions(+), 208 deletions(-) delete mode 100644 pkg/microservice/cron/core/service/client/workflow_test.go delete mode 100644 pkg/shared/poetry/auth.go diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index d27203b6..d9243f8c 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -78,10 +78,6 @@ func PoetryAPIServer() string { return configbase.PoetryServiceAddress() } -func PoetryAPIRootKey() string { - return viper.GetString(setting.ENVPoetryAPIRootKey) -} - func CollieAPIAddress() string { return configbase.CollieServiceAddress() } diff --git a/pkg/microservice/aslan/core/common/service/collie/client.go b/pkg/microservice/aslan/core/common/service/collie/client.go index a2b94c6a..05c9beaf 100644 --- a/pkg/microservice/aslan/core/common/service/collie/client.go +++ b/pkg/microservice/aslan/core/common/service/collie/client.go @@ -17,27 +17,22 @@ limitations under the License. package collie import ( - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type Client struct { *httpclient.Client - host string - token string + host string } -func New(host, token string) *Client { +func New(host string) *Client { c := httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(token), httpclient.SetHostURL(host), ) return &Client{ Client: c, host: host, - token: token, } } diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index dcff7666..6450ef31 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -29,7 +29,7 @@ var secret string func GetHookSecret() string { once.Do(func() { - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) org, err := poetryClient.GetOrganization(poetry.DefaultOrganization) if err != nil { log.Errorf("failed to find default organization: %v", err) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index c80b5637..8028e268 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -72,7 +72,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Infof("[%s] delete product %s", username, productInfo.Namespace) LogProductStats(username, setting.DeleteProductEvent, productName, requestID, eventStart, log) - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) switch productInfo.Source { case setting.SourceFromHelm: diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 2523538f..bb4b4671 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -60,7 +60,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P if strings.Contains(features, string(config.FreestyleType)) { // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 collieAPIAddress := config.CollieAPIAddress() - cl := collie.New(collieAPIAddress, config.PoetryAPIRootKey()) + cl := collie.New(collieAPIAddress) if resp.ProductFeature != nil && resp.ProductFeature.DevelopHabit == "yaml" && resp.OnboardingStatus == setting.OnboardingStatusSecond && collieAPIAddress != "" { ciPipelines, err := cl.ListCIPipelines(productName, log) if err != nil { @@ -118,7 +118,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P if strings.Contains(features, string(config.FreestyleType)) { collieAPIAddress := config.CollieAPIAddress() - cl := collie.New(collieAPIAddress, config.PoetryAPIRootKey()) + cl := collie.New(collieAPIAddress) totalFreeStyles, err = cl.ListCIPipelines(productName, log) if err != nil { log.Errorf("GetProductTemplate freestyle.List err : %v", err) @@ -169,7 +169,7 @@ func GetFeatures(log *zap.SugaredLogger) (string, error) { featuresByteKey := []byte("features") featuresByteValue, err := cache.Get(featuresByteKey) if err != nil { - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryCtl := poetry.New(config.PoetryAPIServer()) fs, err := poetryCtl.ListFeatures() if err != nil { return "", err diff --git a/pkg/microservice/aslan/core/common/service/utils.go b/pkg/microservice/aslan/core/common/service/utils.go index 742826e8..0c659c8b 100644 --- a/pkg/microservice/aslan/core/common/service/utils.go +++ b/pkg/microservice/aslan/core/common/service/utils.go @@ -76,7 +76,7 @@ func SendFailedTaskMessage(username, productName, name, requestID string, workfl } // 如果是timer创建的任务,通知需要发送给该项目下有编辑工作流权限的用户 - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) users, _ := poetryClient.ListProductPermissionUsers(productName, perm, log) for _, user := range users { SendMessage(user, title, content, requestID, log) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 30c25cc6..f8f87b97 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -127,7 +127,7 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar return fmt.Errorf("UpdateProductPublic error: %v", err) } - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryCtl := poetry.New(config.PoetryAPIServer()) if !args.IsPublic { //把公开设置成不公开 _, err := poetryCtl.AddEnvRolePermission(productName, args.EnvName, args.PermissionUUIDs, args.RoleID, log) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 52909896..f4bee00c 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -74,7 +74,7 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { } } - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) users, _ := poetryClient.ListProductPermissionUsers("", "", log) for _, user := range users { commonservice.SendMessage(user, title, content, requestID, log) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index e17cc768..a75fb92f 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -58,7 +58,7 @@ func ListProductsRevision(productName, envName string, userID int, superUser boo productNamespaces.Insert(publicProduct.Namespace) } - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryCtl := poetry.New(config.PoetryAPIServer()) productNameMap, err = poetryCtl.GetUserProject(userID, log) if err != nil { log.Errorf("Collection.Product.List GetUserProject error: %v", err) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 8ee30f86..b4b9ab7a 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -206,7 +206,7 @@ func UpdateProductTmplStatus(productName, onboardingStatus string, log *zap.Suga // UpdateProject 更新项目 func UpdateProject(name string, args *template.Product, log *zap.SugaredLogger) (err error) { - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryCtl := poetry.New(config.PoetryAPIServer()) //创建团建和项目之间的关系 _, err = poetryCtl.AddProductTeam(args.ProductName, args.TeamID, args.UserIDs, log) @@ -242,7 +242,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } } - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryCtl := poetry.New(config.PoetryAPIServer()) //删除项目团队信息 if err = poetryCtl.DeleteProductTeam(productName, log); err != nil { @@ -284,7 +284,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug log.Errorf("DeleteProductTemplate productName %s getFeatures err: %v", productName, err) } if strings.Contains(features, string(config.FreestyleType)) { - collieClient := collie.New(config.CollieAPIAddress(), config.PoetryAPIRootKey()) + collieClient := collie.New(config.CollieAPIAddress()) if err = collieClient.DeleteCIPipelines(productName, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) } @@ -466,7 +466,7 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj } func UnForkProduct(userID int, username, productName, workflowName, envName, requestID string, log *zap.SugaredLogger) error { - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) if userEnvPermissions, _ := poetryClient.ListUserEnvPermission(productName, userID, log); len(userEnvPermissions) > 0 { if err := poetryClient.DeleteUserEnvPermission(productName, username, userID, log); err != nil { return e.ErrUnForkProduct.AddDesc(fmt.Sprintf("Failed to delete env permission for userID: %d, env: %s, productName: %s, the error is: %+v", userID, username, productName, err)) @@ -626,7 +626,7 @@ func ListTemplatesHierachy(userName string, userID int, superUser bool, log *zap return nil, e.ErrListProducts.AddDesc(err.Error()) } } else { - productNameMap, err := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()).GetUserProject(userID, log) + productNameMap, err := poetry.New(config.PoetryAPIServer()).GetUserProject(userID, log) if err != nil { log.Errorf("ProfuctTmpl.List GetUserProject error: %v", err) return resp, e.ErrListProducts.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index 269ff39b..03ba10c3 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -88,7 +88,7 @@ func GetUserKubeConfig(userName string, userID int, superUser bool, log *zap.Sug productEnvs = append(productEnvs, publicProduct) namespaceSet.Insert(publicProduct.Namespace) } - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) productNameMap, err = poetryClient.GetUserProject(userID, log) if err != nil { log.Errorf("GetUserKubeConfig Collection.Product.List GetUserProject error: %v", err) diff --git a/pkg/microservice/aslan/core/system/service/capacity.go b/pkg/microservice/aslan/core/system/service/capacity.go index fba668bf..79c1a3dd 100644 --- a/pkg/microservice/aslan/core/system/service/capacity.go +++ b/pkg/microservice/aslan/core/system/service/capacity.go @@ -180,7 +180,7 @@ func sendSyscapNotify(handleErr error, totalCleanTasks *int) { IsRead: false, } - poetryClient := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryAPIServer()) users, _ := poetryClient.ListProductPermissionUsers("", "", log.SugaredLogger()) for _, user := range users { notifyInfo.Receiver = user diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 03b5b0a7..0da283c8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -69,7 +69,7 @@ type TaskAckHandler struct { log *zap.SugaredLogger } -func NewTaskAckHandler(poetryServer, poetryRootKey string, maxInFlight int, log *zap.SugaredLogger) *TaskAckHandler { +func NewTaskAckHandler(poetryServer string, maxInFlight int, log *zap.SugaredLogger) *TaskAckHandler { return &TaskAckHandler{ queue: NewPipelineQueue(log), ptColl: commonrepo.NewTaskColl(), @@ -79,7 +79,7 @@ func NewTaskAckHandler(poetryServer, poetryRootKey string, maxInFlight int, log deliveryArtifactColl: commonrepo.NewDeliveryArtifactColl(), deliveryActivityColl: commonrepo.NewDeliveryActivityColl(), TestTaskStatColl: commonrepo.NewTestTaskStatColl(), - PoetryClient: poetry.New(poetryServer, poetryRootKey), + PoetryClient: poetry.New(poetryServer), messages: make(chan *nsq.Message, maxInFlight*10), log: log, } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go index 7329538d..f4c4cb96 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go @@ -43,7 +43,7 @@ func SubScribeNSQ() error { // init ack consumer ackConfig := nsqservice.Config() ackConfig.MaxInFlight = 50 - ackHandler := NewTaskAckHandler(config.PoetryAPIServer(), config.PoetryAPIRootKey(), ackConfig.MaxInFlight, logger) + ackHandler := NewTaskAckHandler(config.PoetryAPIServer(), ackConfig.MaxInFlight, logger) err := nsqservice.SubScribe(setting.TopicAck, "ack", 1, ackConfig, ackHandler) if err != nil { logger.Errorf("ack subscription failed, the error is: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 63cbc48d..7bf36d19 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -163,7 +163,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } } - jiraInfo, _ := poetry.GetJiraInfo(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + jiraInfo, _ := poetry.GetJiraInfo(config.PoetryAPIServer()) if jiraInfo != nil { jiraTask, err := AddPipelineJiraSubTask(pipeline, log) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 1983ba6b..d6a4744a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -569,7 +569,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, subTasks = append(subTasks, distributeTasks...) } - jiraInfo, _ := poetry.GetJiraInfo(config.PoetryAPIServer(), config.PoetryAPIRootKey()) + jiraInfo, _ := poetry.GetJiraInfo(config.PoetryAPIServer()) if jiraInfo != nil { jiraTask, err := AddJiraSubTask("", target.Name, target.ServiceName, args.ProductTmplName, log) if err != nil { diff --git a/pkg/microservice/cron/config/config.go b/pkg/microservice/cron/config/config.go index b044a633..95f8c080 100644 --- a/pkg/microservice/cron/config/config.go +++ b/pkg/microservice/cron/config/config.go @@ -25,18 +25,10 @@ import ( "github.com/koderover/zadig/pkg/setting" ) -func RootToken() string { - return viper.GetString(setting.ENVRootToken) -} - func CollieAPI() string { return configbase.CollieServiceAddress() } -func CollieToken() string { - return RootToken() -} - func NsqLookupAddrs() []string { return strings.Split(viper.GetString(setting.ENVNsqLookupAddrs), ",") } diff --git a/pkg/microservice/cron/core/service/client/caller.go b/pkg/microservice/cron/core/service/client/caller.go index dca82f37..2f144cd1 100644 --- a/pkg/microservice/cron/core/service/client/caller.go +++ b/pkg/microservice/cron/core/service/client/caller.go @@ -24,8 +24,6 @@ import ( "net/http" "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/setting" ) func (c *Client) ScheduleCall(api string, args interface{}, log *zap.SugaredLogger) error { @@ -41,7 +39,7 @@ func (c *Client) ScheduleCall(api string, args interface{}, log *zap.SugaredLogg log.Errorf("create post request error : %v", err) return err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.TIMERAPIKEY, c.Token)) + var resp *http.Response resp, err = c.Conn.Do(request) if err == nil { diff --git a/pkg/microservice/cron/core/service/client/client.go b/pkg/microservice/cron/core/service/client/client.go index 7d41290c..ab416baa 100644 --- a/pkg/microservice/cron/core/service/client/client.go +++ b/pkg/microservice/cron/core/service/client/client.go @@ -23,17 +23,15 @@ import ( type Client struct { APIBase string - Token string Conn *http.Client } // NewAslanClient is to get aslan client func -func NewAslanClient(host, token string) *Client { +func NewAslanClient(host string) *Client { jar, _ := cookiejar.New(nil) c := &Client{ APIBase: host, - Token: token, Conn: &http.Client{Transport: http.DefaultTransport, Jar: jar}, } diff --git a/pkg/microservice/cron/core/service/client/collie_client.go b/pkg/microservice/cron/core/service/client/collie_client.go index 413a8151..dbf6a118 100644 --- a/pkg/microservice/cron/core/service/client/collie_client.go +++ b/pkg/microservice/cron/core/service/client/collie_client.go @@ -17,27 +17,22 @@ limitations under the License. package client import ( - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type CollieClient struct { *httpclient.Client - host string - token string + host string } -func NewCollieClient(host, token string) *CollieClient { +func NewCollieClient(host string) *CollieClient { c := httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(token), httpclient.SetHostURL(host), ) return &CollieClient{ Client: c, host: host, - token: token, } } diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 2509b2f5..c1c727cf 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -29,7 +29,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/rsa" ) @@ -169,7 +168,7 @@ func (c *Client) sendRequest(url string) error { if err != nil { return err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.TIMERAPIKEY, c.Token)) + resp, err := c.Conn.Do(request) if err == nil { defer func() { _ = resp.Body.Close() }() @@ -183,7 +182,6 @@ func (c *Client) sendPostRequest(url string, body io.Reader, log *zap.SugaredLog log.Errorf("create post request error : %v", err) return "", err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.TIMERAPIKEY, c.Token)) var resp *http.Response resp, err = c.Conn.Do(request) if err != nil { diff --git a/pkg/microservice/cron/core/service/client/env.go b/pkg/microservice/cron/core/service/client/env.go index 468b0190..b50274e0 100644 --- a/pkg/microservice/cron/core/service/client/env.go +++ b/pkg/microservice/cron/core/service/client/env.go @@ -27,7 +27,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" ) func (c *Client) ListEnvs(log *zap.SugaredLogger) ([]*service.ProductRevision, error) { @@ -44,7 +43,6 @@ func (c *Client) ListEnvs(log *zap.SugaredLogger) ([]*service.ProductRevision, e return nil, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() @@ -71,7 +69,6 @@ func (c *Client) GetEnvService(productName, envName string, log *zap.SugaredLogg return nil, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() @@ -99,7 +96,6 @@ func (c *Client) GetService(serviceName, productName, serviceType string, revisi return nil, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() @@ -129,7 +125,6 @@ func (c *Client) UpdateService(args *service.ServiceTmplObject, log *zap.Sugared return err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() @@ -154,7 +149,6 @@ func (c *Client) GetHostInfo(hostID string, log *zap.SugaredLogger) (*service.Pr return nil, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() diff --git a/pkg/microservice/cron/core/service/client/operation.go b/pkg/microservice/cron/core/service/client/operation.go index d7f1b484..94f2db1b 100644 --- a/pkg/microservice/cron/core/service/client/operation.go +++ b/pkg/microservice/cron/core/service/client/operation.go @@ -26,7 +26,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" ) func (c *Client) GetWebHookUser(log *zap.SugaredLogger) (*service.DomainWebHookUser, error) { @@ -42,7 +41,6 @@ func (c *Client) GetWebHookUser(log *zap.SugaredLogger) (*service.DomainWebHookU return domainWebHookUser, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() diff --git a/pkg/microservice/cron/core/service/client/pipeline.go b/pkg/microservice/cron/core/service/client/pipeline.go index dacbe0ba..0d1aba96 100644 --- a/pkg/microservice/cron/core/service/client/pipeline.go +++ b/pkg/microservice/cron/core/service/client/pipeline.go @@ -26,7 +26,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" ) func (c *Client) ListPipelines(log *zap.SugaredLogger) ([]*service.Pipeline, error) { @@ -40,7 +39,6 @@ func (c *Client) ListPipelines(log *zap.SugaredLogger) ([]*service.Pipeline, err return nil, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() diff --git a/pkg/microservice/cron/core/service/client/sonar.go b/pkg/microservice/cron/core/service/client/sonar.go index d97e9884..4b50f102 100644 --- a/pkg/microservice/cron/core/service/client/sonar.go +++ b/pkg/microservice/cron/core/service/client/sonar.go @@ -24,7 +24,6 @@ import ( "go.uber.org/zap" configbase "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/setting" ) func (c *Client) InitPullSonarStatScheduler(log *zap.SugaredLogger) error { @@ -45,7 +44,6 @@ func (c *Client) InitPullSonarTestsMeasure(log *zap.SugaredLogger) error { log.Errorf("create post request error : %v", err) return err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var resp *http.Response resp, err = c.Conn.Do(request) @@ -76,7 +74,6 @@ func (c *Client) InitPullSonarDeliveryMeasure(log *zap.SugaredLogger) error { log.Errorf("create post request error : %v", err) return err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var resp *http.Response resp, err = c.Conn.Do(request) @@ -107,7 +104,6 @@ func (c *Client) InitPullSonarRepos(log *zap.SugaredLogger) error { log.Errorf("create post request error : %v", err) return err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var resp *http.Response resp, err = c.Conn.Do(request) diff --git a/pkg/microservice/cron/core/service/client/testing.go b/pkg/microservice/cron/core/service/client/testing.go index bb7e50d9..4154a010 100644 --- a/pkg/microservice/cron/core/service/client/testing.go +++ b/pkg/microservice/cron/core/service/client/testing.go @@ -26,7 +26,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" ) func (c *Client) ListTests(log *zap.SugaredLogger) ([]*service.TestingOpt, error) { @@ -39,7 +38,6 @@ func (c *Client) ListTests(log *zap.SugaredLogger) ([]*service.TestingOpt, error return nil, err } - request.Header.Set("Authorization", fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) var ret *http.Response ret, err = c.Conn.Do(request) diff --git a/pkg/microservice/cron/core/service/client/workflow.go b/pkg/microservice/cron/core/service/client/workflow.go index 894a0687..6884d9f5 100644 --- a/pkg/microservice/cron/core/service/client/workflow.go +++ b/pkg/microservice/cron/core/service/client/workflow.go @@ -26,7 +26,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" ) // ListWorkflows ... @@ -71,6 +70,5 @@ func (c *Client) genListWorkFlowReq(log *zap.SugaredLogger) (*http.Request, erro return nil, err } - request.Header.Set(setting.AuthorizationHeader, fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) return request, nil } diff --git a/pkg/microservice/cron/core/service/client/workflow_test.go b/pkg/microservice/cron/core/service/client/workflow_test.go deleted file mode 100644 index 434dfc7a..00000000 --- a/pkg/microservice/cron/core/service/client/workflow_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package client - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/tool/log" -) - -const ( - testAPIHost = "os.koderover.com" - testAPIToken = "token" -) - -func TestGenListWorkFlowReq(t *testing.T) { - client := NewAslanClient( - testAPIHost, - testAPIToken, - ) - req, _ := client.genListWorkFlowReq(log.NopSugaredLogger()) - assert.Equal(t, fmt.Sprintf("%s/workflow/workflow", testAPIHost), req.URL.Path) - assert.Equal(t, fmt.Sprintf("%s %s", setting.RootAPIKey, testAPIToken), req.Header.Get(setting.AuthorizationHeader)) -} diff --git a/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go b/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go index a3a678fc..28448974 100644 --- a/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go +++ b/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go @@ -69,14 +69,10 @@ func InitExistedCronjob(client *client.Client, scheduler *cronlib.CronSchduler) failsafeJobList []*service.Cronjob ) listAPI := fmt.Sprintf("%s/cron/cronjob", client.APIBase) - header := http.Header{} - header.Set("Authorization", fmt.Sprintf("%s %s", setting.TIMERAPIKEY, client.Token)) // failsafe function: get enabled workflow and register them failsafeAPI := fmt.Sprintf("%s/cron/cronjob/failsafe", client.APIBase) cl := httpclient.New( - httpclient.SetAuthScheme(setting.TIMERAPIKEY), - httpclient.SetAuthToken(client.Token), httpclient.SetRetryCount(100), httpclient.SetRetryWaitTime(PullInterval), ) @@ -304,7 +300,6 @@ func (h *CronjobHandler) stopCronjob(name, ptype string) error { var jobList []*service.Cronjob listAPI := fmt.Sprintf("%s/cron/cronjob/type/%s/name/%s", h.aslanCli.APIBase, ptype, name) header := http.Header{} - header.Set("Authorization", fmt.Sprintf("%s %s", setting.TIMERAPIKEY, h.aslanCli.Token)) resp, err := util.SendRequest(listAPI, "GET", header, nil) if err != nil { log.Errorf("Failed to get job list, the error is: %v, reconsuming", err) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 5ab7e046..5f45ae44 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -83,8 +83,8 @@ const ( func NewCronClient() *CronClient { nsqLookupAddrs := config.NsqLookupAddrs() - aslanCli := client.NewAslanClient(fmt.Sprintf("%s/api", configbase.AslanServiceAddress()), config.RootToken()) - collieCli := client.NewCollieClient(config.CollieAPI(), config.CollieToken()) + aslanCli := client.NewAslanClient(fmt.Sprintf("%s/api", configbase.AslanServiceAddress())) + collieCli := client.NewCollieClient(config.CollieAPI()) //初始化nsq config := nsq.NewConfig() // 注意 WD_POD_NAME 必须使用 Downward API 配置环境变量 @@ -160,7 +160,7 @@ func (c *CronClient) Init() { } func getFeatures() (string, error) { - cl := poetry.New(configbase.PoetryServiceAddress(), config.RootToken()) + cl := poetry.New(configbase.PoetryServiceAddress()) fs, err := cl.ListFeatures() if err != nil { return "", err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index e9f614f9..1867fd45 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -162,7 +162,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe Installs: p.Task.InstallCtx, } - poetryClient := poetry.New(configbase.PoetryServiceAddress(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(configbase.PoetryServiceAddress()) if fs, err := poetryClient.ListFeatures(); err == nil { pipelineTask.Features = fs } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 529ad8e3..6fcad93f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -58,8 +58,6 @@ func InitializeDeployTaskPlugin(taskType config.TaskType) TaskPlugin { kubeClient: krkubeclient.Client(), restConfig: krkubeclient.RESTConfig(), httpClient: httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(config.PoetryAPIRootKey()), httpclient.SetHostURL(configbase.AslanServiceAddress()), ), } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go index f544f50e..892100fd 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go @@ -340,7 +340,7 @@ func (p *JiraPlugin) SetEndTime() { func (p *JiraPlugin) getJiraIssue(pipelineTask *task.Task, key string) (*task.JiraIssue, error) { jiraIssue := new(task.JiraIssue) - poetryClient := poetry.New(configbase.PoetryServiceAddress(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(configbase.PoetryServiceAddress()) jiraInfo, err := poetryClient.GetJiraInfo() if err != nil { return nil, fmt.Errorf("getJiraInfo [%s] error: %v", key, err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/security.go b/pkg/microservice/warpdrive/core/service/taskplugin/security.go index 43ac5a4f..b03ab81a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/security.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/security.go @@ -29,7 +29,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -39,8 +38,6 @@ func InitializeSecurityPlugin(taskType config.TaskType) TaskPlugin { Name: taskType, errorChan: make(chan error, 1), httpClient: httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(config.PoetryAPIRootKey()), httpclient.SetHostURL(configbase.AslanServiceAddress()), ), } diff --git a/pkg/middleware/gin/audit_log.go b/pkg/middleware/gin/audit_log.go index d4c79a4a..e3c1a6c0 100644 --- a/pkg/middleware/gin/audit_log.go +++ b/pkg/middleware/gin/audit_log.go @@ -31,7 +31,7 @@ func UpdateOperationLogStatus(c *gin.Context) { if c.GetString("operationLogID") == "" { return } - err := aslan.New(config.AslanServiceAddress(), config.PoetryAPIRootKey()).UpdateAuditLog(c.GetString("operationLogID"), c.Writer.Status(), log) + err := aslan.New(config.AslanServiceAddress()).UpdateAuditLog(c.GetString("operationLogID"), c.Writer.Status(), log) if err != nil { log.Errorf("UpdateOperation err:%v", err) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0dfa6e85..3d79e2cd 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -248,9 +248,6 @@ const ( const ( SessionUsername = "Username" SessionUser = "User" - UserAPIKey = "X-API-KEY" - RootAPIKey = "X-ROOT-API-KEY" - TIMERAPIKEY = "X-TIMER-API-KEY" AuthorizationHeader = "Authorization" ) diff --git a/pkg/shared/client/aslan/client.go b/pkg/shared/client/aslan/client.go index c34553ca..ab453625 100644 --- a/pkg/shared/client/aslan/client.go +++ b/pkg/shared/client/aslan/client.go @@ -17,43 +17,38 @@ limitations under the License. package aslan import ( - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type Client struct { *httpclient.Client - host string - token string + host string + token string external bool } -func New(host, token string) *Client { +func New(host string) *Client { c := httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(token), - httpclient.SetHostURL(host+"/api"), + httpclient.SetHostURL(host + "/api"), ) return &Client{ Client: c, host: host, - token: token, } } func NewExternal(host, token string) *Client { c := httpclient.New( - httpclient.SetAuthScheme(setting.UserAPIKey), httpclient.SetAuthToken(token), httpclient.SetHostURL(host+"/api/aslan"), ) return &Client{ - Client: c, - host: host, - token: token, + Client: c, + host: host, + token: token, external: true, } } diff --git a/pkg/shared/client/aslanx/client.go b/pkg/shared/client/aslanx/client.go index 05caf54c..04089462 100644 --- a/pkg/shared/client/aslanx/client.go +++ b/pkg/shared/client/aslanx/client.go @@ -17,27 +17,22 @@ limitations under the License. package aslanx import ( - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type Client struct { *httpclient.Client - host string - token string + host string } -func New(host, token string) *Client { +func New(host string) *Client { c := httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(token), httpclient.SetHostURL(host), ) return &Client{ Client: c, host: host, - token: token, } } diff --git a/pkg/shared/codehost/codehost.go b/pkg/shared/codehost/codehost.go index 67b8d5f7..a266fd4a 100644 --- a/pkg/shared/codehost/codehost.go +++ b/pkg/shared/codehost/codehost.go @@ -63,7 +63,7 @@ type Detail struct { } func GetCodeHostList() ([]*poetry.CodeHost, error) { - poetryClient := poetry.New(config.PoetryServiceAddress(), config.PoetryAPIRootKey()) + poetryClient := poetry.New(config.PoetryServiceAddress()) return poetryClient.ListCodeHosts() } diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index c59719d5..26aff580 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -78,7 +78,7 @@ func JSONResponse(c *gin.Context, ctx *Context) { // InsertOperationLog 插入操作日志 func InsertOperationLog(c *gin.Context, username, productName, method, function, detail, requestBody string, logger *zap.SugaredLogger) { - operationLogID, err := aslan.New(config.AslanServiceAddress(), config.PoetryAPIRootKey()).AddAuditLog(username, productName, method, function, detail, requestBody, logger) + operationLogID, err := aslan.New(config.AslanServiceAddress()).AddAuditLog(username, productName, method, function, detail, requestBody, logger) if err != nil { logger.Errorf("InsertOperation err:%v", err) } diff --git a/pkg/shared/poetry/auth.go b/pkg/shared/poetry/auth.go deleted file mode 100644 index 20165958..00000000 --- a/pkg/shared/poetry/auth.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "fmt" - - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type userView struct { - User *UserInfo `json:"info"` -} - -func GetUserDetailByToken(poetryHost, token string) (*UserInfo, error) { - url := "/directory/user/detail" - - cl := httpclient.New(httpclient.SetHostURL(poetryHost)) - - //根据token获取用户 - userViewInfo := &userView{} - _, err := cl.Get(url, httpclient.SetHeader(setting.AuthorizationHeader, fmt.Sprintf("%s %s", setting.UserAPIKey, token)), httpclient.SetResult(userViewInfo)) - if err != nil { - return nil, err - } - - return userViewInfo.User, nil -} diff --git a/pkg/shared/poetry/client.go b/pkg/shared/poetry/client.go index 323db52a..47f93d43 100644 --- a/pkg/shared/poetry/client.go +++ b/pkg/shared/poetry/client.go @@ -17,7 +17,6 @@ limitations under the License. package poetry import ( - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -30,20 +29,16 @@ const ( type Client struct { *httpclient.Client - host string - token string + host string } -func New(host, token string) *Client { +func New(host string) *Client { c := httpclient.New( - httpclient.SetAuthScheme(setting.RootAPIKey), - httpclient.SetAuthToken(token), httpclient.SetHostURL(host), ) return &Client{ Client: c, host: host, - token: token, } } diff --git a/pkg/shared/poetry/jira.go b/pkg/shared/poetry/jira.go index 88fd8d72..8cb13177 100644 --- a/pkg/shared/poetry/jira.go +++ b/pkg/shared/poetry/jira.go @@ -42,7 +42,7 @@ func (c *Client) GetJiraInfo() (*JiraInfo, error) { return jira, nil } -func GetJiraInfo(host, token string) (*JiraInfo, error) { - c := New(host, token) +func GetJiraInfo(host string) (*JiraInfo, error) { + c := New(host) return c.GetJiraInfo() } -- Gitee From cfd654dbc92781cfc102d31736c6437799ad5700 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 21:01:28 +0800 Subject: [PATCH 0099/2647] rolebings Signed-off-by: mouuii --- .../policy/core/handler/role_binding.go | 34 +++++++++++++++++++ .../policy/core/handler/router.go | 3 ++ .../core/repository/mongodb/role_binding.go | 11 ++++-- .../policy/core/service/role_binding.go | 18 ++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 4fe58657..2efd3081 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -43,6 +43,33 @@ func CreateRoleBinding(c *gin.Context) { ctx.Err = service.CreateRoleBinding(projectName, args, ctx.Logger) } +func ListRoleBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Resp, ctx.Err = service.ListRoleBindings(projectName, ctx.Logger) +} + +func DeleteRoleBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + name := c.Param("name") + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Err = service.DeleteRoleBinding(name, projectName, ctx.Logger) +} + func CreateSystemRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -57,3 +84,10 @@ func CreateSystemRoleBinding(c *gin.Context) { ctx.Err = service.CreateRoleBinding(service.SystemScope, args, ctx.Logger) } + +func ListSystemRoleBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListRoleBindings(service.SystemScope, ctx.Logger) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 65828241..6f6c1ad7 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -40,11 +40,14 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings := router.Group("rolebindings") { roleBindings.POST("", CreateRoleBinding) + roleBindings.GET("", ListRoleBindings) + roleBindings.DELETE("/:name", DeleteRoleBinding) } systemRoleBindings := router.Group("system-rolebindings") { systemRoleBindings.POST("", CreateSystemRoleBinding) + systemRoleBindings.GET("", ListSystemRoleBindings) } bundles := router.Group("bundles") diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 570d53f6..de48f68e 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -61,11 +61,12 @@ func (c *RoleBindingColl) EnsureIndex(ctx context.Context) error { return err } -func (c *RoleBindingColl) List() ([]*models.RoleBinding, error) { +func (c *RoleBindingColl) List(projectName string) ([]*models.RoleBinding, error) { var res []*models.RoleBinding ctx := context.Background() - cursor, err := c.Collection.Find(ctx, bson.M{}) + query := bson.M{"namespace": projectName} + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } @@ -78,6 +79,12 @@ func (c *RoleBindingColl) List() ([]*models.RoleBinding, error) { return res, nil } +func (c *RoleBindingColl) Delete(name string, projectName string) error { + query := bson.M{"name": name, "namespace": projectName} + _, err := c.DeleteOne(context.TODO(), query) + return err +} + func (c *RoleBindingColl) Create(obj *models.RoleBinding) error { if obj == nil { return fmt.Errorf("nil object") diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 21d11f4a..49474ba4 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -60,3 +60,21 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Create(obj) } + +func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { + modelRoleBindings, err := mongodb.NewRoleBindingColl().List(ns) + if err != nil { + return nil, err + } + for _, v := range modelRoleBindings { + roleBindings = append(roleBindings, &RoleBinding{ + Name: v.Name, + Role: v.RoleRef.Name, + }) + } + return +} + +func DeleteRoleBinding(name string, projectName string, logger *zap.SugaredLogger) error { + return mongodb.NewRoleBindingColl().Delete(name, projectName) +} -- Gitee From fde7feec4ce3450928ca927cc7fae1810a2b270b Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 21:03:02 +0800 Subject: [PATCH 0100/2647] rolebings Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role_binding.go | 8 ++++++++ pkg/microservice/policy/core/handler/router.go | 1 + 2 files changed, 9 insertions(+) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 2efd3081..525621e7 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -70,6 +70,14 @@ func DeleteRoleBinding(c *gin.Context) { ctx.Err = service.DeleteRoleBinding(name, projectName, ctx.Logger) } +func DeleteSystemRoleBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + name := c.Param("name") + ctx.Err = service.DeleteRoleBinding(name, service.SystemScope, ctx.Logger) +} + func CreateSystemRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 6f6c1ad7..72133fbb 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -48,6 +48,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { systemRoleBindings.POST("", CreateSystemRoleBinding) systemRoleBindings.GET("", ListSystemRoleBindings) + systemRoleBindings.DELETE("/:name", DeleteSystemRoleBinding) } bundles := router.Group("bundles") -- Gitee From 6c869eb9f433d486bbf75f1c09f78d769641ee1f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 21:11:27 +0800 Subject: [PATCH 0101/2647] rolebings Signed-off-by: mouuii --- .../policy/core/service/role_binding.go | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 49474ba4..98794cfd 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -32,6 +32,13 @@ type RoleBinding struct { Global bool `json:"global"` } +type RoleBindingResp struct { + Name string + Namespace string + Subjects []string + Role string +} + const SystemScope = "*" func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { @@ -61,15 +68,20 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Create(obj) } -func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { +func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBindingResp, err error) { modelRoleBindings, err := mongodb.NewRoleBindingColl().List(ns) if err != nil { return nil, err } for _, v := range modelRoleBindings { - roleBindings = append(roleBindings, &RoleBinding{ - Name: v.Name, - Role: v.RoleRef.Name, + users := []string{} + for _, vv := range v.Subjects { + users = append(users, vv.Name) + } + roleBindings = append(roleBindings, &RoleBindingResp{ + Name: v.Name, + Role: v.RoleRef.Name, + Subjects: users, }) } return -- Gitee From 6cbc0c67694fcca571a1738581a73f6091f9eb69 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 21:15:28 +0800 Subject: [PATCH 0102/2647] rolebings Signed-off-by: mouuii --- .../policy/core/service/role_binding.go | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 98794cfd..4b560fb7 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -32,13 +32,6 @@ type RoleBinding struct { Global bool `json:"global"` } -type RoleBindingResp struct { - Name string - Namespace string - Subjects []string - Role string -} - const SystemScope = "*" func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { @@ -68,7 +61,7 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Create(obj) } -func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBindingResp, err error) { +func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { modelRoleBindings, err := mongodb.NewRoleBindingColl().List(ns) if err != nil { return nil, err @@ -78,10 +71,11 @@ func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*Rol for _, vv := range v.Subjects { users = append(users, vv.Name) } - roleBindings = append(roleBindings, &RoleBindingResp{ - Name: v.Name, - Role: v.RoleRef.Name, - Subjects: users, + roleBindings = append(roleBindings, &RoleBinding{ + Name: v.Name, + Role: v.RoleRef.Name, + User: v.Subjects[0].Name, + Global: (v.Namespace == ""), }) } return -- Gitee From ad8d5d59e0123de1d2e907912d5b247d8fc1376d Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Oct 2021 21:21:11 +0800 Subject: [PATCH 0103/2647] rolebings Signed-off-by: mouuii --- .../core/repository/mongodb/role_binding.go | 19 ++++++++++++++++++- .../policy/core/service/role_binding.go | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index de48f68e..dec62194 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -61,7 +61,24 @@ func (c *RoleBindingColl) EnsureIndex(ctx context.Context) error { return err } -func (c *RoleBindingColl) List(projectName string) ([]*models.RoleBinding, error) { +func (c *RoleBindingColl) List() ([]*models.RoleBinding, error) { + var res []*models.RoleBinding + + ctx := context.Background() + cursor, err := c.Collection.Find(ctx, bson.M{}) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *RoleBindingColl) ListBy(projectName string) ([]*models.RoleBinding, error) { var res []*models.RoleBinding ctx := context.Background() diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 4b560fb7..dd9e3bb7 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -62,7 +62,7 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er } func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { - modelRoleBindings, err := mongodb.NewRoleBindingColl().List(ns) + modelRoleBindings, err := mongodb.NewRoleBindingColl().ListBy(ns) if err != nil { return nil, err } -- Gitee From 76ad65d05e7eebfc6cb3f8071829cd9513b53db0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 11:31:28 +0800 Subject: [PATCH 0104/2647] public Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 4 ++-- pkg/microservice/policy/core/handler/router.go | 8 ++++---- pkg/microservice/policy/core/service/role.go | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index d202a15c..008e581c 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -56,7 +56,7 @@ func ListRoles(c *gin.Context) { return } -func CreateGlobalRole(c *gin.Context) { +func CreatePublicRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -69,7 +69,7 @@ func CreateGlobalRole(c *gin.Context) { ctx.Err = service.CreateRole("", args, ctx.Logger) } -func ListGlobalRoles(c *gin.Context) { +func ListPublicRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 72133fbb..6abf653c 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -30,11 +30,11 @@ func (*Router) Inject(router *gin.RouterGroup) { roles.DELETE("/:name", DeleteRole) } - globalRoles := router.Group("global-roles") + PublicRoles := router.Group("public-roles") { - globalRoles.POST("", CreateGlobalRole) - globalRoles.GET("", ListGlobalRoles) - globalRoles.DELETE("/:name", DeleteRole) + PublicRoles.POST("", CreatePublicRole) + PublicRoles.GET("", ListPublicRoles) + PublicRoles.DELETE("/:name", DeleteRole) } roleBindings := router.Group("rolebindings") diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 72a97457..f6d493d2 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -25,6 +25,7 @@ import ( type Role struct { Name string `json:"name"` + Kind string `json:"name"` Rules []*Rule `json:"rules,omitempty"` } @@ -37,6 +38,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, Namespace: ns, + Kind: role.Kind, } for _, r := range role.Rules { -- Gitee From 4dce1220e8982fbff583ef5ea51d7fa7edb0fc0e Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 11:47:34 +0800 Subject: [PATCH 0105/2647] system role Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 29 +++++++++++++++++++ .../policy/core/handler/router.go | 7 +++++ 2 files changed, 36 insertions(+) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 008e581c..6bdd5655 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -85,3 +85,32 @@ func DeleteRole(c *gin.Context) { ctx.Err = service.DeleteRole(name, projectName, ctx.Logger) return } + +func CreateSystemRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Role{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.CreateRole("*", args, ctx.Logger) +} + +func ListSystemRoles(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListRoles("*", ctx.Logger) + return +} + +func DeleteSystemRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + name := c.Param("name") + ctx.Err = service.DeleteRole(name, "*", ctx.Logger) + return +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 6abf653c..ee52f065 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -37,6 +37,13 @@ func (*Router) Inject(router *gin.RouterGroup) { PublicRoles.DELETE("/:name", DeleteRole) } + SystemRoles := router.Group("system-roles") + { + SystemRoles.POST("", CreateSystemRole) + SystemRoles.GET("", ListSystemRoles) + SystemRoles.DELETE("/:name", DeleteSystemRole) + } + roleBindings := router.Group("rolebindings") { roleBindings.POST("", CreateRoleBinding) -- Gitee From a329e635408667fc8752d4aac9260b874e0e4476 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 20 Oct 2021 14:35:45 +0800 Subject: [PATCH 0106/2647] fix share service bug Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/deploy.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 1fdb65f5..d835cc36 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -195,7 +195,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ) serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, p.Task.ProductName) if err != nil { - return + // Maybe it is a share service, the entity is not under the project + serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, "") + if err != nil { + return + } } if serviceInfo.WorkloadType == "" { selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() -- Gitee From 665e04b26ba2341a3a44096bdc67a2008d98f050 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 20 Oct 2021 14:43:40 +0800 Subject: [PATCH 0107/2647] fix compile error Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/renderset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 2df6179c..43eea34e 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -115,7 +115,7 @@ func CreateOrUpdateRenderset(c *gin.Context) { log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), fmt.Sprintf("%s,%s", permission.TestEnvManageUUID, permission.ProdEnvManageUUID), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) ctx.Err = service.CreateOrUpdateRenderset(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) } -- Gitee From ac871de7e27a5fbcb0a873f27481fe848ad55015 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 14:52:21 +0800 Subject: [PATCH 0108/2647] remove user in handler Signed-off-by: lou --- go.mod | 2 +- go.sum | 24 +-- .../aslan/core/build/handler/build.go | 12 +- .../aslan/core/code/handler/workspace.go | 8 +- .../repository/models/favorite_pipeline.go | 2 +- .../repository/models/task/release_image.go | 9 -- .../repository/mongodb/favorite_pipeline.go | 4 +- .../aslan/core/common/service/pipeline.go | 2 +- .../aslan/core/delivery/handler/product.go | 2 +- .../aslan/core/delivery/handler/version.go | 2 +- .../core/environment/handler/configmap.go | 8 +- .../core/environment/handler/environment.go | 52 +++--- .../aslan/core/environment/handler/image.go | 27 +--- .../aslan/core/environment/handler/kube.go | 2 +- .../core/environment/handler/renderset.go | 4 +- .../core/environment/handler/revision.go | 2 +- .../aslan/core/environment/handler/service.go | 12 +- .../core/environment/service/configmap.go | 11 +- .../core/environment/service/environment.go | 149 +----------------- .../core/environment/service/revision.go | 4 +- .../core/multicluster/handler/clusters.go | 8 +- .../aslan/core/project/handler/product.go | 30 ++-- .../aslan/core/project/handler/render.go | 27 +--- .../aslan/core/project/service/product.go | 14 +- .../aslan/core/service/handler/helm.go | 4 +- .../aslan/core/service/handler/loader.go | 2 +- .../aslan/core/service/handler/service.go | 24 +-- .../aslan/core/setting/handler/user.go | 2 +- .../aslan/core/setting/service/user.go | 9 +- .../aslan/core/system/handler/announcement.go | 10 +- .../aslan/core/system/handler/basic_images.go | 10 +- .../aslan/core/system/handler/helm.go | 4 +- .../aslan/core/system/handler/install.go | 10 +- .../aslan/core/system/handler/jenkins.go | 4 +- .../aslan/core/system/handler/notify.go | 14 +- .../aslan/core/system/handler/private_key.go | 10 +- .../aslan/core/system/handler/proxy.go | 10 +- .../aslan/core/system/handler/registry.go | 10 +- .../aslan/core/system/handler/s3.go | 12 +- .../workflow/handler/favorate_pipeline.go | 10 +- .../aslan/core/workflow/handler/pipeline.go | 16 +- .../core/workflow/handler/pipeline_status.go | 2 +- .../core/workflow/handler/pipeline_task.go | 18 +-- .../aslan/core/workflow/handler/sse.go | 12 +- .../aslan/core/workflow/handler/workflow.go | 18 +-- .../core/workflow/handler/workflow_task.go | 22 +-- .../service/webhook/codehub_workflow_task.go | 3 +- .../service/webhook/gerrit_workflow_task.go | 3 +- .../service/webhook/github_workflow_task.go | 3 +- .../service/webhook/gitlab_workflow_task.go | 5 +- .../core/workflow/service/webhook/ilyshin.go | 3 +- .../workflow/service/workflow/pipeline.go | 2 +- .../service/workflow/pipeline_status.go | 2 +- .../workflow/service/workflow/workflow.go | 4 +- .../service/workflow/workflow_task.go | 4 +- .../workflow/testing/handler/test_task.go | 14 +- .../core/workflow/testing/handler/testing.go | 10 +- pkg/setting/consts.go | 2 - pkg/shared/handler/base.go | 58 +++++-- pkg/shared/poetry/types.go | 23 --- pkg/types/permission/user.go | 40 ----- 61 files changed, 280 insertions(+), 546 deletions(-) delete mode 100644 pkg/types/permission/user.go diff --git a/go.mod b/go.mod index 28512627..111b18db 100644 --- a/go.mod +++ b/go.mod @@ -22,11 +22,11 @@ require ( github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/garyburd/redigo v1.6.2 // indirect - github.com/gin-contrib/sessions v0.0.3 github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 github.com/gorilla/handlers v1.5.1 // indirect diff --git a/go.sum b/go.sum index 08ed6a88..8ba24eb1 100644 --- a/go.sum +++ b/go.sum @@ -121,11 +121,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= -github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= -github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= -github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I= @@ -259,15 +256,12 @@ github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= -github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI= -github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -339,10 +333,8 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= @@ -403,6 +395,8 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -437,7 +431,6 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -480,18 +473,12 @@ github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyyc github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= -github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -590,7 +577,6 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -615,7 +601,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -645,7 +630,6 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= @@ -662,7 +646,6 @@ github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnL github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -826,7 +809,6 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= @@ -1161,7 +1143,6 @@ golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1375,7 +1356,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/pkg/microservice/aslan/core/build/handler/build.go b/pkg/microservice/aslan/core/build/handler/build.go index 5cf11416..9ae54606 100644 --- a/pkg/microservice/aslan/core/build/handler/build.go +++ b/pkg/microservice/aslan/core/build/handler/build.go @@ -56,7 +56,7 @@ func CreateBuildModule(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateBuildModule json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "项目管理-构建", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-构建", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) err = c.BindJSON(args) @@ -65,7 +65,7 @@ func CreateBuildModule(c *gin.Context) { return } - ctx.Err = buildservice.CreateBuild(ctx.Username, args, ctx.Logger) + ctx.Err = buildservice.CreateBuild(ctx.UserName, args, ctx.Logger) } func UpdateBuildModule(c *gin.Context) { @@ -80,7 +80,7 @@ func UpdateBuildModule(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateBuildModule json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-构建", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-构建", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) err = c.BindJSON(args) @@ -88,7 +88,7 @@ func UpdateBuildModule(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid Build args") return } - ctx.Err = buildservice.UpdateBuild(ctx.Username, args, ctx.Logger) + ctx.Err = buildservice.UpdateBuild(ctx.UserName, args, ctx.Logger) } func DeleteBuildModule(c *gin.Context) { @@ -97,7 +97,7 @@ func DeleteBuildModule(c *gin.Context) { name := c.Query("name") productName := c.Query("productName") - internalhandler.InsertOperationLog(c, ctx.Username, productName, "删除", "项目管理-构建", name, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "删除", "项目管理-构建", name, "", ctx.Logger) if name == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty Name") @@ -126,7 +126,7 @@ func UpdateBuildTargets(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - internalhandler.InsertOperationLog(c, ctx.Username, c.Query("productName"), "更新", "项目管理-服务组件", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "更新", "项目管理-服务组件", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { diff --git a/pkg/microservice/aslan/core/code/handler/workspace.go b/pkg/microservice/aslan/core/code/handler/workspace.go index ae46b2e0..1d980ace 100644 --- a/pkg/microservice/aslan/core/code/handler/workspace.go +++ b/pkg/microservice/aslan/core/code/handler/workspace.go @@ -31,7 +31,7 @@ import ( func GetProductNameByWorkspacePipeline(c *gin.Context) { ctx := internalhandler.NewContext(c) - pipeline, err := commonservice.GetPipelineInfo(ctx.User.ID, c.Query("pipelineName"), ctx.Logger) + pipeline, err := commonservice.GetPipelineInfo(c.Query("pipelineName"), ctx.Logger) if err != nil { log.Errorf("GetProductNameByWorkspacePipeline err : %v", err) return @@ -43,13 +43,13 @@ func GetProductNameByWorkspacePipeline(c *gin.Context) { func CleanWorkspace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "清理", "单服务工作流-工作目录", c.Query("pipelineName"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "清理", "单服务工作流-工作目录", c.Query("pipelineName"), "", ctx.Logger) name := c.Query("pipelineName") if name == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty pipeline name") return } - ctx.Err = service.CleanWorkspace(ctx.Username, name, ctx.Logger) + ctx.Err = service.CleanWorkspace(ctx.UserName, name, ctx.Logger) } func GetWorkspaceFile(c *gin.Context) { @@ -69,7 +69,7 @@ func GetWorkspaceFile(c *gin.Context) { return } - fileRealPath, err := service.GetWorkspaceFilePath(ctx.Username, name, file, ctx.Logger) + fileRealPath, err := service.GetWorkspaceFilePath(ctx.UserName, name, file, ctx.Logger) if err != nil { c.JSON(e.ErrorMessage(err)) c.Abort() diff --git a/pkg/microservice/aslan/core/common/repository/models/favorite_pipeline.go b/pkg/microservice/aslan/core/common/repository/models/favorite_pipeline.go index 5e1ab74e..b0bca8b7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/favorite_pipeline.go +++ b/pkg/microservice/aslan/core/common/repository/models/favorite_pipeline.go @@ -20,7 +20,7 @@ import "go.mongodb.org/mongo-driver/bson/primitive" type Favorite struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - UserID int `bson:"user_id" json:"user_id"` + UserID string `bson:"user_id" json:"user_id"` ProductName string `bson:"product_name" json:"product_name"` Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go index c697c08b..e113a715 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go @@ -23,15 +23,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) -//type RepoImage struct { -// RepoID string `json:"repo_id" bson:"repo_id"` -// Name string `json:"name" bson:"name" yaml:"name"` -// Username string `json:"-" yaml:"username"` -// Password string `json:"-" yaml:"password"` -// Host string `json:"host" yaml:"host"` -// Namespace string `json:"namespace" yaml:"namespace"` -//} - type ReleaseImageItem struct { RepoID string `bson:"id" json:"id"` Image string `bson:"image" json:"image"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/favorite_pipeline.go b/pkg/microservice/aslan/core/common/repository/mongodb/favorite_pipeline.go index fb20f134..54e747df 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/favorite_pipeline.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/favorite_pipeline.go @@ -31,7 +31,7 @@ import ( ) type FavoriteArgs struct { - UserID int + UserID string ProductName string Name string Type string @@ -99,7 +99,7 @@ func (c *FavoriteColl) List(args *FavoriteArgs) ([]*models.Favorite, error) { return resp, err } -func (c *FavoriteColl) Find(userID int, name, Type string) (*models.Favorite, error) { +func (c *FavoriteColl) Find(userID, name, Type string) (*models.Favorite, error) { resp := new(models.Favorite) query := bson.M{"user_id": userID, "name": name} if Type != "" { diff --git a/pkg/microservice/aslan/core/common/service/pipeline.go b/pkg/microservice/aslan/core/common/service/pipeline.go index 3fc47b6c..dd7b9e15 100644 --- a/pkg/microservice/aslan/core/common/service/pipeline.go +++ b/pkg/microservice/aslan/core/common/service/pipeline.go @@ -111,7 +111,7 @@ func DeletePipeline(pipelineName, requestID string, isDeletingProductTmpl bool, return nil } -func GetPipelineInfo(userID int, pipelineName string, log *zap.SugaredLogger) (*commonmodels.Pipeline, error) { +func GetPipelineInfo(pipelineName string, log *zap.SugaredLogger) (*commonmodels.Pipeline, error) { resp, err := mongodb.NewPipelineColl().Find(&mongodb.PipelineFindOption{Name: pipelineName}) if err != nil { log.Error(err) diff --git a/pkg/microservice/aslan/core/delivery/handler/product.go b/pkg/microservice/aslan/core/delivery/handler/product.go index 59eb70b2..ccdf392a 100644 --- a/pkg/microservice/aslan/core/delivery/handler/product.go +++ b/pkg/microservice/aslan/core/delivery/handler/product.go @@ -49,5 +49,5 @@ func GetProductByDeliveryInfo(c *gin.Context) { return } - ctx.Resp, ctx.Err = deliveryservice.GetProductByDeliveryInfo(ctx.Username, releaseID, ctx.Logger) + ctx.Resp, ctx.Err = deliveryservice.GetProductByDeliveryInfo(ctx.UserName, releaseID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 28e26ff0..6fa62021 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -343,7 +343,7 @@ func ListPackagesVersion(c *gin.Context) { func DeleteDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "删除", "版本交付", fmt.Sprintf("主键ID:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "删除", "版本交付", fmt.Sprintf("主键ID:%s", c.Param("id")), "", ctx.Logger) //params validate ID := c.Param("id") diff --git a/pkg/microservice/aslan/core/environment/handler/configmap.go b/pkg/microservice/aslan/core/environment/handler/configmap.go index 644e74b5..97699732 100644 --- a/pkg/microservice/aslan/core/environment/handler/configmap.go +++ b/pkg/microservice/aslan/core/environment/handler/configmap.go @@ -55,7 +55,7 @@ func UpdateConfigMap(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateConfigMap json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "集成环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "集成环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -74,7 +74,7 @@ func UpdateConfigMap(c *gin.Context) { // return //} - ctx.Err = service.UpdateConfigMap(args.EnvName, args, ctx.User.Name, ctx.User.ID, ctx.Logger) + ctx.Err = service.UpdateConfigMap(args.EnvName, args, ctx.UserName, ctx.UserID, ctx.Logger) } func RollBackConfigMap(c *gin.Context) { @@ -89,7 +89,7 @@ func RollBackConfigMap(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("RollBackConfigMap json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "回滚", "集成环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "回滚", "集成环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -102,5 +102,5 @@ func RollBackConfigMap(c *gin.Context) { return } - ctx.Err = service.RollBackConfigMap(args.EnvName, args, ctx.User.Name, ctx.User.ID, ctx.Logger) + ctx.Err = service.RollBackConfigMap(args.EnvName, args, ctx.UserName, ctx.UserID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 38b3a708..47051cef 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -60,7 +60,7 @@ func ListProducts(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), ctx.User.Name, ctx.Logger) + ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), ctx.UserName, ctx.Logger) } func AutoCreateProduct(c *gin.Context) { @@ -82,7 +82,7 @@ func AutoUpdateProduct(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("AutoUpdateProduct json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "自动更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "自动更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -92,7 +92,7 @@ func AutoUpdateProduct(c *gin.Context) { } force, _ := strconv.ParseBool(c.Query("force")) - ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Param("productName"), ctx.User.ID, ctx.User.IsSuperUser, ctx.RequestID, force, ctx.Logger) + ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Param("productName"), ctx.UserID, false, ctx.RequestID, force, ctx.Logger) } func CreateHelmProduct(c *gin.Context) { @@ -119,10 +119,10 @@ func CreateHelmProduct(c *gin.Context) { return } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( - ctx.Username, ctx.RequestID, args, ctx.Logger, + ctx.UserName, ctx.RequestID, args, ctx.Logger, ) } @@ -138,7 +138,7 @@ func CreateProduct(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateProduct json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -151,9 +151,9 @@ func CreateProduct(c *gin.Context) { return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.CreateProduct( - ctx.Username, ctx.RequestID, args, ctx.Logger, + ctx.UserName, ctx.RequestID, args, ctx.Logger, ) } @@ -172,7 +172,7 @@ func UpdateProduct(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProduct json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, productName, "更新", "集成环境", envName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -182,7 +182,7 @@ func UpdateProduct(c *gin.Context) { force, _ := strconv.ParseBool(c.Query("force")) // update product asynchronously - ctx.Err = service.UpdateProductV2(envName, productName, ctx.Username, ctx.RequestID, force, args.Vars, ctx.Logger) + ctx.Err = service.UpdateProductV2(envName, productName, ctx.UserName, ctx.RequestID, force, args.Vars, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product %s %s: %v", envName, productName, ctx.Err) } @@ -196,7 +196,7 @@ func UpdateProductRecycleDay(c *gin.Context) { productName := c.Param("productName") recycleDayStr := c.Query("recycleDay") - internalhandler.InsertOperationLog(c, ctx.Username, productName, "更新", "集成环境-环境回收", envName, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境-环境回收", envName, "", ctx.Logger) var ( recycleDay int @@ -223,7 +223,7 @@ func UpdateHelmProduct(c *gin.Context) { updateType := c.Query("updateType") productName := c.Param("productName") - ctx.Err = service.UpdateHelmProduct(productName, envName, updateType, ctx.Username, ctx.RequestID, nil, ctx.Logger) + ctx.Err = service.UpdateHelmProduct(productName, envName, updateType, ctx.UserName, ctx.RequestID, nil, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product %s %s: %v", envName, productName, ctx.Err) } @@ -253,9 +253,9 @@ func UpdateHelmProductRenderCharts(c *gin.Context) { if err = json.Unmarshal(data, arg); err != nil { log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "更新环境变量", "", string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "更新环境变量", "", string(data), ctx.Logger) - ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.Username, ctx.RequestID, arg.ChartValues, ctx.Logger) + ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.UserName, ctx.RequestID, arg.ChartValues, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) } @@ -276,7 +276,7 @@ func UpdateHelmProductVariable(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "helm集成环境变量", "", string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "helm集成环境变量", "", string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -285,7 +285,7 @@ func UpdateHelmProductVariable(c *gin.Context) { return } - ctx.Err = service.UpdateHelmProductVariable(productName, envName, ctx.Username, ctx.RequestID, args.ChartInfos, args.ChartInfos, ctx.Logger) + ctx.Err = service.UpdateHelmProductVariable(productName, envName, ctx.UserName, ctx.RequestID, args.ChartInfos, args.ChartInfos, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) } @@ -310,10 +310,10 @@ func UpdateMultiHelmEnv(c *gin.Context) { } args.ProductName = c.Param("productName") - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) ctx.Resp, ctx.Err = service.UpdateMultipleHelmEnv( - ctx.Username, ctx.RequestID, ctx.User.ID, ctx.User.IsSuperUser, args, ctx.Logger, + ctx.RequestID, ctx.UserID, false, args, ctx.Logger, ) } @@ -329,7 +329,7 @@ func UpdateMultiHelmProduct(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateMultiHelmProduct json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新helm环境", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新helm环境", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -338,7 +338,7 @@ func UpdateMultiHelmProduct(c *gin.Context) { return } - ctx.Resp = service.UpdateMultiHelmProduct(args.EnvNames, args.UpdateType, c.Param("productName"), ctx.User.ID, ctx.User.IsSuperUser, ctx.RequestID, ctx.Logger) + ctx.Resp = service.UpdateMultiHelmProduct(args.EnvNames, args.UpdateType, c.Param("productName"), ctx.UserID, false, ctx.RequestID, ctx.Logger) } func GetProduct(c *gin.Context) { @@ -348,7 +348,7 @@ func GetProduct(c *gin.Context) { envName := c.Query("envName") productName := c.Param("productName") - ctx.Resp, ctx.Err = service.GetProduct(ctx.Username, envName, productName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetProduct(ctx.UserName, envName, productName, ctx.Logger) } func GetProductInfo(c *gin.Context) { @@ -358,7 +358,7 @@ func GetProductInfo(c *gin.Context) { envName := c.Query("envName") productName := c.Param("productName") - ctx.Resp, ctx.Err = service.GetProductInfo(ctx.Username, envName, productName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetProductInfo(ctx.UserName, envName, productName, ctx.Logger) } func GetProductIngress(c *gin.Context) { @@ -366,7 +366,7 @@ func GetProductIngress(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() productName := c.Param("productName") - ctx.Resp, ctx.Err = service.GetProductIngress(ctx.Username, productName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetProductIngress(ctx.UserName, productName, ctx.Logger) } func ListRenderCharts(c *gin.Context) { @@ -420,8 +420,8 @@ func DeleteProduct(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() envName := c.Query("envName") - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "删除", "集成环境", envName, "", ctx.Logger) - ctx.Err = commonservice.DeleteProduct(ctx.Username, envName, c.Param("productName"), ctx.RequestID, ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "删除", "集成环境", envName, "", ctx.Logger) + ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, c.Param("productName"), ctx.RequestID, ctx.Logger) } func EnvShare(c *gin.Context) { @@ -437,7 +437,7 @@ func EnvShare(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateProduct json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, productName, "更新", "集成环境-环境授权", args.EnvName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境-环境授权", args.EnvName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if productName == "" { diff --git a/pkg/microservice/aslan/core/environment/handler/image.go b/pkg/microservice/aslan/core/environment/handler/image.go index d6703188..45315376 100644 --- a/pkg/microservice/aslan/core/environment/handler/image.go +++ b/pkg/microservice/aslan/core/environment/handler/image.go @@ -49,7 +49,7 @@ func UpdateStatefulSetContainerImage(c *gin.Context) { } internalhandler.InsertOperationLog( - c, ctx.Username, args.ProductName, + c, ctx.UserName, args.ProductName, "更新", "集成环境-服务镜像", fmt.Sprintf("环境名称:%s,服务名称:%s,StatefulSet:%s", args.EnvName, args.ServiceName, args.Name), string(data), ctx.Logger) @@ -80,7 +80,7 @@ func UpdateDeploymentContainerImage(c *gin.Context) { } internalhandler.InsertOperationLog( - c, ctx.Username, args.ProductName, + c, ctx.UserName, args.ProductName, "更新", "集成环境-服务镜像", fmt.Sprintf("环境名称:%s,服务名称:%s,Deployment:%s", args.EnvName, args.ServiceName, args.Name), string(data), ctx.Logger) @@ -94,26 +94,3 @@ func UpdateDeploymentContainerImage(c *gin.Context) { ctx.Err = service.UpdateContainerImage(ctx.RequestID, args, ctx.Logger) } - -//func UpdateContainerImage(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// -// args := new(service.UpdateContainerImageArgs) -// data, err := c.GetRawData() -// if err != nil { -// log.Errorf("UpdateContainerImage c.GetRawData() err : %v", err) -// } -// if err = json.Unmarshal(data, args); err != nil { -// log.Errorf("UpdateContainerImage json.Unmarshal err : %v", err) -// } -// internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "集成环境-服务镜像", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), fmt.Sprintf("%s,%s", permission.TestEnvManageUUID, permission.ProdEnvManageUUID), string(data), ctx.Logger) -// c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) -// -// if err := c.BindJSON(args); err != nil { -// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) -// return -// } -// -// ctx.Err = service.CtrlMgr.UpdateContainerImage(args.EnvName, args.ProductName, args, ctx.Logger) -//} diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index c35ea7bb..af5bfcc0 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -83,7 +83,7 @@ func DeletePod(c *gin.Context) { envName := c.Query("envName") productName := c.Query("productName") - internalhandler.InsertOperationLog(c, ctx.Username, c.Query("productName"), "重启", "集成环境-服务实例", fmt.Sprintf("环境名称:%s,pod名称:%s", c.Query("envName"), c.Param("podName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "重启", "集成环境-服务实例", fmt.Sprintf("环境名称:%s,pod名称:%s", c.Query("envName"), c.Param("podName")), "", ctx.Logger) ctx.Err = service.DeletePod(envName, productName, podName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 7ede1b44..b7b6e6e8 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -69,7 +69,7 @@ func CreateOrUpdateRenderChart(c *gin.Context) { log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) - ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) + ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.UserName, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/revision.go b/pkg/microservice/aslan/core/environment/handler/revision.go index 77ed70d3..f35be2c0 100644 --- a/pkg/microservice/aslan/core/environment/handler/revision.go +++ b/pkg/microservice/aslan/core/environment/handler/revision.go @@ -32,5 +32,5 @@ func ListProductsRevision(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProductsRevisionByFacility(c.Query("basicFacility"), ctx.Logger) return } - ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("productName"), c.Query("envName"), ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("productName"), c.Query("envName"), ctx.UserID, false, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index 6c5cffd8..3405c7c0 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -49,14 +49,14 @@ func RestartService(c *gin.Context) { ServiceName: c.Param("serviceName"), } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "重启", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "重启", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) ctx.Err = service.RestartService(args.EnvName, args, ctx.Logger) } func UpdateService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) svcRev := new(service.SvcRevision) if err := c.BindJSON(svcRev); err != nil { @@ -70,7 +70,7 @@ func UpdateService(c *gin.Context) { ServiceName: c.Param("serviceName"), ServiceType: c.Param("serviceType"), ServiceRev: svcRev, - UpdateBy: ctx.Username, + UpdateBy: ctx.UserName, } ctx.Err = service.UpdateService(args, ctx.Logger) @@ -89,7 +89,7 @@ func RestartNewService(c *gin.Context) { } internalhandler.InsertOperationLog( - c, ctx.Username, + c, ctx.UserName, c.Param("productName"), "重启", "集成环境-服务", @@ -116,7 +116,7 @@ func ScaleNewService(c *gin.Context) { name := c.Query("name") internalhandler.InsertOperationLog( - c, ctx.Username, + c, ctx.UserName, c.Param("productName"), "伸缩", "集成环境-服务", @@ -142,7 +142,7 @@ func ScaleNewService(c *gin.Context) { func ScaleService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "伸缩", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "伸缩", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) number, err := strconv.Atoi(c.Param("number")) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 045d2338..bbb634c5 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -19,7 +19,6 @@ package service import ( "fmt" "sort" - "strconv" "strings" "time" @@ -146,7 +145,7 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*configM return res, nil } -func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName string, userID int, log *zap.SugaredLogger) error { +func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID string, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: args.ProductName, EnvName: args.EnvName, @@ -191,7 +190,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName string, cfg.Data = args.Data // 记录修改configmap的用户 cfg.Labels[setting.UpdateBy] = kube.MakeSafeLabelValue(userName) - cfg.Labels[setting.UpdateByID] = fmt.Sprintf("%d", userID) + cfg.Labels[setting.UpdateByID] = userID cfg.Labels[setting.UpdateTime] = time.Now().Format("20060102150405") cfg.Labels[setting.DirtyLabel] = setting.LabelValueTrue @@ -200,7 +199,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName string, as = make(map[string]string) } as[setting.ModifiedByAnnotation] = userName - as[setting.EditorIDAnnotation] = strconv.Itoa(userID) + as[setting.EditorIDAnnotation] = userID as[setting.LastUpdateTimeAnnotation] = util.FormatTime(time.Now()) cfg.SetAnnotations(as) @@ -223,7 +222,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName string, return nil } -func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName string, userID int, log *zap.SugaredLogger) error { +func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, userID string, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: args.ProductName, EnvName: args.EnvName, @@ -260,7 +259,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName str destinSrc.Data = srcCfg.Data destinSrc.Labels[setting.UpdateBy] = kube.MakeSafeLabelValue(userName) - destinSrc.Labels[setting.UpdateByID] = fmt.Sprintf("%d", userID) + destinSrc.Labels[setting.UpdateByID] = userID destinSrc.Labels[setting.UpdateTime] = time.Now().Format("20060102150405") // 回滚时显示回滚版本的时间 if updateTime, ok := srcCfg.Labels[setting.UpdateTime]; ok { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f8f87b97..4f00fed9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -234,7 +234,7 @@ func AutoCreateProduct(productName, envType, requestID string, log *zap.SugaredL var mutexAutoUpdate sync.RWMutex -func AutoUpdateProduct(envNames []string, productName string, userID int, superUser bool, requestID string, force bool, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func AutoUpdateProduct(envNames []string, productName, userID string, superUser bool, requestID string, force bool, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexAutoUpdate.Lock() defer func() { mutexAutoUpdate.Unlock() @@ -952,7 +952,7 @@ func updateHelmProductVariable(productResp *commonmodels.Product, oldRenderVersi var mutexUpdateMultiHelm sync.RWMutex -func UpdateMultipleHelmEnv(userName, requestID string, userID int, superUser bool, args *UpdateMultiHelmProductArg, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func UpdateMultipleHelmEnv(requestID, userID string, superUser bool, args *UpdateMultiHelmProductArg, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexUpdateMultiHelm.Lock() defer func() { mutexUpdateMultiHelm.Unlock() @@ -1040,7 +1040,7 @@ func UpdateMultipleHelmEnv(userName, requestID string, userID int, superUser boo } // UpdateMultiHelmProduct TODO need to be deprecated -func UpdateMultiHelmProduct(envNames []string, updateType, productName string, userID int, superUser bool, requestID string, log *zap.SugaredLogger) []*EnvStatus { +func UpdateMultiHelmProduct(envNames []string, updateType, productName, userID string, superUser bool, requestID string, log *zap.SugaredLogger) []*EnvStatus { mutexUpdateMultiHelm.Lock() defer func() { mutexUpdateMultiHelm.Unlock() @@ -1373,149 +1373,6 @@ func getProjectType(productName string) string { return projectType } -// createGroup create or update services in service group -//func createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client, log *zap.SugaredLogger) error { -// log.Infof("[Namespace:%s][Product:%s] createGroup", envName, productName) -// updatableServiceNameList := make([]string, 0) -// -// // 异步创建无依赖的服务 -// errList := &multierror.Error{ -// ErrorFormat: func(es []error) string { -// points := make([]string, len(es)) -// for i, err := range es { -// points[i] = fmt.Sprintf("%v", err) -// } -// -// return strings.Join(points, "\n") -// }, -// } -// -// opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} -// prod, err := commonrepo.NewProductColl().Find(opt) -// if err != nil { -// errList = multierror.Append(errList, err) -// } -// -// var wg sync.WaitGroup -// var lock sync.Mutex -// var resources []*unstructured.Unstructured -// -// for i := range group { -// if group[i].Type == setting.K8SDeployType { -// // 只有在service有Pod的时候,才需要等待pod running或者等待pod succeed -// // 比如在group中,如果service下仅有configmap/service/ingress这些yaml的时候,不需要waitServicesRunning -// wg.Add(1) -// updatableServiceNameList = append(updatableServiceNameList, group[i].ServiceName) -// go func(svc *commonmodels.ProductService) { -// defer wg.Done() -// items, err := upsertService(false, prod, svc, nil, renderSet, kubeClient, log) -// if err != nil { -// lock.Lock() -// switch e := err.(type) { -// case *multierror.Error: -// errList = multierror.Append(errList, e.Errors...) -// default: -// errList = multierror.Append(errList, e) -// } -// lock.Unlock() -// } -// -// // concurrent array append -// lock.Lock() -// resources = append(resources, items...) -// lock.Unlock() -// }(group[i]) -// } else if group[i].Type == setting.PMDeployType { -// //更新非k8s服务 -// if len(group[i].EnvConfigs) > 0 { -// serviceTempl, err := commonservice.GetServiceTemplate(group[i].ServiceName, setting.PMDeployType, productName, setting.ProductStatusDeleting, group[i].Revision, log) -// if err != nil { -// errList = multierror.Append(errList, err) -// } -// if serviceTempl != nil { -// oldEnvConfigs := serviceTempl.EnvConfigs -// for _, currentEnvConfig := range group[i].EnvConfigs { -// envConfig := &commonmodels.EnvConfig{ -// EnvName: currentEnvConfig.EnvName, -// HostIDs: currentEnvConfig.HostIDs, -// } -// oldEnvConfigs = append(oldEnvConfigs, envConfig) -// } -// -// args := &commonservice.ServiceTmplBuildObject{ -// ServiceTmplObject: &commonservice.ServiceTmplObject{ -// ProductName: serviceTempl.ProductName, -// ServiceName: serviceTempl.ServiceName, -// Visibility: serviceTempl.Visibility, -// Revision: serviceTempl.Revision, -// Type: serviceTempl.Type, -// Username: username, -// HealthChecks: serviceTempl.HealthChecks, -// EnvConfigs: oldEnvConfigs, -// EnvStatuses: []*commonmodels.EnvStatus{}, -// From: "createEnv", -// }, -// Build: &commonmodels.Build{Name: serviceTempl.BuildName}, -// } -// -// if err := commonservice.UpdatePmServiceTemplate(username, args, log); err != nil { -// errList = multierror.Append(errList, err) -// } -// } -// } -// var latestRevision int64 = group[i].Revision -// // 获取最新版本的服务 -// if latestServiceTempl, _ := commonservice.GetServiceTemplate(group[i].ServiceName, setting.PMDeployType, productName, setting.ProductStatusDeleting, 0, log); latestServiceTempl != nil { -// latestRevision = latestServiceTempl.Revision -// } -// // 更新环境 -// if latestRevision > group[i].Revision { -// // 更新产品服务 -// for _, serviceGroup := range prod.Services { -// for j, service := range serviceGroup { -// if service.ServiceName == group[i].ServiceName && service.Type == setting.PMDeployType { -// serviceGroup[j].Revision = latestRevision -// } -// } -// } -// if err := commonrepo.NewProductColl().Update(prod); err != nil { -// log.Errorf("[%s][%s] Product.Update error: %v", envName, productName, err) -// errList = multierror.Append(errList, err) -// } -// } -// if _, err = commonservice.CreateServiceTask(&commonmodels.ServiceTaskArgs{ -// ProductName: productName, -// ServiceName: group[i].ServiceName, -// Revision: latestRevision, -// EnvNames: []string{envName}, -// ServiceTaskCreator: username, -// }, log); err != nil { -// errList = multierror.Append(errList, err) -// } -// } -// } -// -// wg.Wait() -// -// // 如果创建依赖服务组有返回错误, 停止等待 -// if err := errList.ErrorOrNil(); err != nil { -// return err -// } -// -// if err := waitResourceRunning(kubeClient, prod.Namespace, resources, config.ServiceStartTimeout(), log); err != nil { -// log.Errorf( -// "service group %s/%+v doesn't start in %d seconds: %v", -// prod.Namespace, -// updatableServiceNameList, config.ServiceStartTimeout(), err) -// -// err = e.ErrUpdateEnv.AddErr( -// errors.Errorf(e.StartPodTimeout+"\n %s", "["+strings.Join(updatableServiceNameList, "], [")+"]")) -// return err -// } -// -// return nil -//} - // upsertService 创建或者更新服务, 更新服务之前先创建服务需要的配置 func upsertService(isUpdate bool, env *commonmodels.Product, service *commonmodels.ProductService, prevSvc *commonmodels.ProductService, diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index a75fb92f..5ac15c61 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -32,7 +32,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListProductsRevision(productName, envName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ProductRevision, error) { +func ListProductsRevision(productName, envName, userID string, superUser bool, log *zap.SugaredLogger) ([]*ProductRevision, error) { var ( err error prodRevs = make([]*ProductRevision, 0) @@ -59,7 +59,7 @@ func ListProductsRevision(productName, envName string, userID int, superUser boo } poetryCtl := poetry.New(config.PoetryAPIServer()) - productNameMap, err = poetryCtl.GetUserProject(userID, log) + productNameMap, err = poetryCtl.GetUserProject(1, log) if err != nil { log.Errorf("Collection.Product.List GetUserProject error: %v", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index c421e307..d84ea096 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -60,7 +60,7 @@ func CreateCluster(c *gin.Context) { } args.CreatedAt = time.Now().Unix() - args.CreatedBy = ctx.Username + args.CreatedBy = ctx.UserName ctx.Resp, ctx.Err = service.CreateCluster(args, ctx.Logger) } @@ -87,21 +87,21 @@ func DeleteCluster(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DeleteCluster(ctx.Username, c.Param("id"), ctx.Logger) + ctx.Err = service.DeleteCluster(ctx.UserName, c.Param("id"), ctx.Logger) } func DisconnectCluster(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DisconnectCluster(ctx.Username, c.Param("id"), ctx.Logger) + ctx.Err = service.DisconnectCluster(ctx.UserName, c.Param("id"), ctx.Logger) } func ReconnectCluster(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.ReconnectCluster(ctx.Username, c.Param("id"), ctx.Logger) + ctx.Err = service.ReconnectCluster(ctx.UserName, c.Param("id"), ctx.Logger) } func ClusterConnectFromAgent(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 1c11a0d1..1a1eb777 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -59,14 +59,14 @@ func CreateProductTemplate(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateProductTemplate json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "项目管理-项目", args.ProductName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-项目", args.ProductName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid ProductTmpl json args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = projectservice.CreateProductTemplate(args, ctx.Logger) } @@ -83,7 +83,7 @@ func UpdateProductTemplate(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProductTemplate json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-项目环境模板或变量", args.ProductName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-项目环境模板或变量", args.ProductName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -91,7 +91,7 @@ func UpdateProductTemplate(c *gin.Context) { return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = projectservice.UpdateProductTemplate(c.Param("name"), args, ctx.Logger) } @@ -117,14 +117,14 @@ func UpdateProject(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProject json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-项目", args.ProductName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-项目", args.ProductName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid ProductTmpl json args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName productName := c.Query("productName") if productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty") @@ -146,7 +146,7 @@ func UpdateServiceOrchestration(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - internalhandler.InsertOperationLog(c, ctx.Username, projectName, "更新", "项目管理-项目服务编排", projectName, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "项目管理-项目服务编排", projectName, "", ctx.Logger) args := new(UpdateOrchestrationServiceReq) if err := c.BindJSON(args); err != nil { @@ -154,22 +154,22 @@ func UpdateServiceOrchestration(c *gin.Context) { return } - ctx.Err = projectservice.UpdateServiceOrchestration(projectName, args.Services, ctx.Username, ctx.Logger) + ctx.Err = projectservice.UpdateServiceOrchestration(projectName, args.Services, ctx.UserName, ctx.Logger) } func DeleteProductTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("name"), "删除", "项目管理-项目", c.Param("name"), "", ctx.Logger) - ctx.Err = projectservice.DeleteProductTemplate(ctx.Username, c.Param("name"), ctx.RequestID, ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("name"), "删除", "项目管理-项目", c.Param("name"), "", ctx.Logger) + ctx.Err = projectservice.DeleteProductTemplate(ctx.UserName, c.Param("name"), ctx.RequestID, ctx.Logger) } func ListTemplatesHierachy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = projectservice.ListTemplatesHierachy(ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = projectservice.ListTemplatesHierachy(ctx.UserName, ctx.UserID, false, ctx.Logger) } func ForkProduct(c *gin.Context) { @@ -182,14 +182,14 @@ func ForkProduct(c *gin.Context) { return } args.ProductName = c.Param("productName") - ctx.Err = projectservice.ForkProduct(ctx.User.ID, ctx.Username, ctx.RequestID, args, ctx.Logger) + ctx.Err = projectservice.ForkProduct(ctx.UserName, ctx.RequestID, args, ctx.Logger) } func UnForkProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = projectservice.UnForkProduct(ctx.User.ID, ctx.Username, c.Param("productName"), c.Query("workflowName"), c.Query("envName"), ctx.RequestID, ctx.Logger) + ctx.Err = projectservice.UnForkProduct(ctx.UserID, ctx.UserName, c.Param("productName"), c.Query("workflowName"), c.Query("envName"), ctx.RequestID, ctx.Logger) } func GetCustomMatchRules(c *gin.Context) { @@ -213,7 +213,7 @@ func CreateOrUpdateMatchRules(c *gin.Context) { return } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("name"), "更新", "工程管理-项目", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("name"), "更新", "工程管理-项目", c.Param("name"), "", ctx.Logger) args := new(projectservice.CustomParseDataArgs) data, err := c.GetRawData() @@ -228,5 +228,5 @@ func CreateOrUpdateMatchRules(c *gin.Context) { return } - ctx.Err = projectservice.UpdateCustomMatchRules(c.Param("name"), ctx.Username, args.Rules) + ctx.Err = projectservice.UpdateCustomMatchRules(c.Param("name"), ctx.UserName, args.Rules) } diff --git a/pkg/microservice/aslan/core/project/handler/render.go b/pkg/microservice/aslan/core/project/handler/render.go index 86f259fd..0061ae33 100644 --- a/pkg/microservice/aslan/core/project/handler/render.go +++ b/pkg/microservice/aslan/core/project/handler/render.go @@ -60,31 +60,6 @@ func GetRenderSetInfo(c *gin.Context) { ctx.Resp, ctx.Err = service.GetRenderSetInfo(c.Param("name"), revision) } -//func ValidateRenderSet(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// _, ctx.Err = projectservice.ValidateRenderSet(c.Param("productName"), c.Param("renderName"), "", ctx.Logger) -//} -// -//func RelateRender(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// ctx.Err = projectservice.RelateRender(c.Param("productName"), c.Param("renderName"), ctx.Logger) -//} -// -//func CreateRenderSet(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// -// args := new(models.RenderSet) -// ctx.Err = c.BindJSON(args) -// if ctx.Err != nil { -// return -// } -// args.UpdateBy = ctx.Username -// ctx.Err = projectservice.CreateRenderSet(args, ctx.Logger) -//} - func UpdateRenderSet(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -94,6 +69,6 @@ func UpdateRenderSet(c *gin.Context) { if ctx.Err != nil { return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.UpdateRenderSet(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 2adb3bcc..7c867f32 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -414,7 +414,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return nil } -func ForkProduct(userID int, username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { +func ForkProduct(username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { @@ -542,10 +542,10 @@ func ForkProduct(userID int, username, requestID string, args *template.ForkProj return workflowservice.CreateWorkflow(workflowArgs, log) } -func UnForkProduct(userID int, username, productName, workflowName, envName, requestID string, log *zap.SugaredLogger) error { +func UnForkProduct(userID string, username, productName, workflowName, envName, requestID string, log *zap.SugaredLogger) error { poetryClient := poetry.New(config.PoetryAPIServer()) - if userEnvPermissions, _ := poetryClient.ListUserEnvPermission(productName, userID, log); len(userEnvPermissions) > 0 { - if err := poetryClient.DeleteUserEnvPermission(productName, username, userID, log); err != nil { + if userEnvPermissions, _ := poetryClient.ListUserEnvPermission(productName, 1, log); len(userEnvPermissions) > 0 { + if err := poetryClient.DeleteUserEnvPermission(productName, username, 1, log); err != nil { return e.ErrUnForkProduct.AddDesc(fmt.Sprintf("Failed to delete env permission for userID: %d, env: %s, productName: %s, the error is: %+v", userID, username, productName, err)) } } @@ -559,7 +559,7 @@ func UnForkProduct(userID int, username, productName, workflowName, envName, req } if roleID := poetryClient.GetContributorRoleID(productName, log); roleID > 0 { - err := poetryClient.DeleteUserRole(roleID, poetry.ProjectType, userID, productName, log) + err := poetryClient.DeleteUserRole(roleID, poetry.ProjectType, 1, productName, log) if err != nil { log.Errorf("Failed to Delete user from role candidate, the error is: %v", err) return e.ErrUnForkProduct.AddDesc(err.Error()) @@ -689,7 +689,7 @@ type ContainerInfo struct { Label string `bson:"label" json:"label"` } -func ListTemplatesHierachy(userName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ProductInfo, error) { +func ListTemplatesHierachy(userName, userID string, superUser bool, log *zap.SugaredLogger) ([]*ProductInfo, error) { var ( err error resp = make([]*ProductInfo, 0) @@ -703,7 +703,7 @@ func ListTemplatesHierachy(userName string, userID int, superUser bool, log *zap return nil, e.ErrListProducts.AddDesc(err.Error()) } } else { - productNameMap, err := poetry.New(config.PoetryAPIServer()).GetUserProject(userID, log) + productNameMap, err := poetry.New(config.PoetryAPIServer()).GetUserProject(1, log) if err != nil { log.Errorf("ProfuctTmpl.List GetUserProject error: %v", err) return resp, e.ErrListProducts.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index d36fed1a..c57b789b 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -64,7 +64,7 @@ func CreateOrUpdateHelmService(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") return } - args.CreatedBy = ctx.Username + args.CreatedBy = ctx.UserName ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("productName"), args, ctx.Logger) } @@ -78,7 +78,7 @@ func UpdateHelmService(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmServiceArgs json args") return } - args.CreateBy = ctx.Username + args.CreateBy = ctx.UserName args.ProductName = c.Param("productName") ctx.Err = svcservice.UpdateHelmService(args, ctx.Logger) diff --git a/pkg/microservice/aslan/core/service/handler/loader.go b/pkg/microservice/aslan/core/service/handler/loader.go index 7a9e6406..00b5675f 100644 --- a/pkg/microservice/aslan/core/service/handler/loader.go +++ b/pkg/microservice/aslan/core/service/handler/loader.go @@ -85,7 +85,7 @@ func LoadServiceTemplate(c *gin.Context) { remoteName := c.Query("remoteName") repoOwner := c.Query("repoOwner") - ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.Username, codehostID, repoOwner, repoName, repoUUID, branchName, remoteName, args, ctx.Logger) + ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, repoName, repoUUID, branchName, remoteName, args, ctx.Logger) } // ValidateServiceUpdate ... diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 683f74aa..cfbd6680 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -82,16 +82,16 @@ func CreateServiceTemplate(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateServiceTemplate json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid ServiceTmpl json args") return } - args.CreateBy = ctx.Username + args.CreateBy = ctx.UserName - ctx.Resp, ctx.Err = svcservice.CreateServiceTemplate(ctx.Username, args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateServiceTemplate(ctx.UserName, args, ctx.Logger) } func UpdateServiceTemplate(c *gin.Context) { @@ -107,10 +107,10 @@ func UpdateServiceTemplate(c *gin.Context) { log.Errorf("UpdateServiceTemplate json.Unmarshal err : %v", err) } if args.Username != "system" { - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) } c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - args.Username = ctx.Username + args.Username = ctx.UserName ctx.Err = svcservice.UpdateServiceTemplate(args) } @@ -139,7 +139,7 @@ func DeleteServiceTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Query("productName"), "删除", "项目管理-服务", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "删除", "项目管理-服务", c.Param("name"), "", ctx.Logger) ctx.Err = svcservice.DeleteServiceTemplate(c.Param("name"), c.Param("type"), c.Query("productName"), c.DefaultQuery("isEnvTemplate", "true"), c.DefaultQuery("visibility", "public"), ctx.Logger) } @@ -178,7 +178,7 @@ func UpdateWorkloads(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Err = service.UpdateWorkloads(c, ctx.RequestID, ctx.Username, product, env, *args, ctx.Logger) + ctx.Err = service.UpdateWorkloads(c, ctx.RequestID, ctx.UserName, product, env, *args, ctx.Logger) } func CreateK8sWorkloads(c *gin.Context) { @@ -190,7 +190,7 @@ func CreateK8sWorkloads(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid K8sWorkloadsArgs args") return } - ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.Username, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) + ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) } func ListAvailablePublicServices(c *gin.Context) { @@ -243,7 +243,7 @@ func CreatePMService(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreatePMService json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "项目管理-物理机部署服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Revision), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "新增", "项目管理-物理机部署服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Revision), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -280,7 +280,7 @@ func CreatePMService(c *gin.Context) { } } - ctx.Err = svcservice.CreatePMService(ctx.Username, args, ctx.Logger) + ctx.Err = svcservice.CreatePMService(ctx.UserName, args, ctx.Logger) } func UpdatePmServiceTemplate(c *gin.Context) { @@ -295,7 +295,7 @@ func UpdatePmServiceTemplate(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdatePmServiceTemplate json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "项目管理-主机服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Revision), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "项目管理-主机服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Revision), "", ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) for _, heathCheck := range args.ServiceTmplObject.HealthChecks { @@ -322,5 +322,5 @@ func UpdatePmServiceTemplate(c *gin.Context) { } } } - ctx.Err = commonservice.UpdatePmServiceTemplate(ctx.Username, args, ctx.Logger) + ctx.Err = commonservice.UpdatePmServiceTemplate(ctx.UserName, args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/setting/handler/user.go b/pkg/microservice/aslan/core/setting/handler/user.go index c453d691..bdaf0f48 100644 --- a/pkg/microservice/aslan/core/setting/handler/user.go +++ b/pkg/microservice/aslan/core/setting/handler/user.go @@ -27,5 +27,5 @@ func GetUserKubeConfig(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.UserName, ctx.UserID, false, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index 03ba10c3..a2d19e0e 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -57,7 +57,7 @@ type kubeCfgTmplArgs struct { ClientKeyBase64 string } -func GetUserKubeConfig(userName string, userID int, superUser bool, log *zap.SugaredLogger) (string, error) { +func GetUserKubeConfig(userName, userID string, superUser bool, log *zap.SugaredLogger) (string, error) { username := strings.ToLower(userName) username = config.NameSpaceRegex.ReplaceAllString(username, "-") var ( @@ -89,7 +89,7 @@ func GetUserKubeConfig(userName string, userID int, superUser bool, log *zap.Sug namespaceSet.Insert(publicProduct.Namespace) } poetryClient := poetry.New(config.PoetryAPIServer()) - productNameMap, err = poetryClient.GetUserProject(userID, log) + productNameMap, err = poetryClient.GetUserProject(1, log) if err != nil { log.Errorf("GetUserKubeConfig Collection.Product.List GetUserProject error: %v", err) return "", e.ErrListProducts.AddDesc(err.Error()) @@ -138,7 +138,6 @@ func GetUserKubeConfig(userName string, userID int, superUser bool, log *zap.Sug ) for _, productEnv := range productEnvs { namespace := productEnv.Namespace - productName := productEnv.ProductName if _, found, err := getter.GetNamespace(namespace, krkubeclient.Client()); err != nil || !found { log.Error(err) @@ -152,7 +151,7 @@ func GetUserKubeConfig(userName string, userID int, superUser bool, log *zap.Sug wg.Done() <-pool }() - if err := ensureUserRole(namespace, username, productName, userID, superUser, log); err != nil { + if err := ensureUserRole(namespace, username, log); err != nil { log.Error(err) errList = multierror.Append(errList, err) } @@ -235,7 +234,7 @@ func ensureServiceAccount(namespace, username string, log *zap.SugaredLogger) er return nil } -func ensureUserRole(namespace, username, productName string, userID int, superUser bool, log *zap.SugaredLogger) error { +func ensureUserRole(namespace, username string, _ *zap.SugaredLogger) error { roleName := fmt.Sprintf("%s-role", username) verbs := []string{"*"} role := &rbacv1beta1.Role{ diff --git a/pkg/microservice/aslan/core/system/handler/announcement.go b/pkg/microservice/aslan/core/system/handler/announcement.go index 739b84ca..30227aa7 100644 --- a/pkg/microservice/aslan/core/system/handler/announcement.go +++ b/pkg/microservice/aslan/core/system/handler/announcement.go @@ -35,7 +35,7 @@ func CreateAnnouncement(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid notify args") return } - ctx.Err = service.CreateAnnouncement(ctx.Username, args, ctx.Logger) + ctx.Err = service.CreateAnnouncement(ctx.UserName, args, ctx.Logger) } func UpdateAnnouncement(c *gin.Context) { @@ -48,21 +48,21 @@ func UpdateAnnouncement(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid notify args") return } - ctx.Err = service.UpdateAnnouncement(ctx.Username, args.ID.Hex(), args, ctx.Logger) + ctx.Err = service.UpdateAnnouncement(ctx.UserName, args.ID.Hex(), args, ctx.Logger) } func PullAllAnnouncement(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.PullAllAnnouncement(ctx.Username, ctx.Logger) + ctx.Resp, ctx.Err = service.PullAllAnnouncement(ctx.UserName, ctx.Logger) } func PullNotifyAnnouncement(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.PullNotifyAnnouncement(ctx.Username, ctx.Logger) + ctx.Resp, ctx.Err = service.PullNotifyAnnouncement(ctx.UserName, ctx.Logger) } func DeleteAnnouncement(c *gin.Context) { @@ -71,5 +71,5 @@ func DeleteAnnouncement(c *gin.Context) { ID := c.Param("id") - ctx.Err = service.DeleteAnnouncement(ctx.Username, ID, ctx.Logger) + ctx.Err = service.DeleteAnnouncement(ctx.UserName, ID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/basic_images.go b/pkg/microservice/aslan/core/system/handler/basic_images.go index 5e4a7ebf..be11a670 100644 --- a/pkg/microservice/aslan/core/system/handler/basic_images.go +++ b/pkg/microservice/aslan/core/system/handler/basic_images.go @@ -58,7 +58,7 @@ func CreateBasicImage(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateBasicImage json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "新增", "基础镜像", fmt.Sprintf("label:%s", args.Label), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "基础镜像", fmt.Sprintf("label:%s", args.Label), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -66,7 +66,7 @@ func CreateBasicImage(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid BasicImage args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.CreateBasicImage(args, ctx.Logger) } @@ -83,7 +83,7 @@ func UpdateBasicImage(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateBasicImage json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "更新", "基础镜像", fmt.Sprintf("id:%s", args.ID), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "基础镜像", fmt.Sprintf("id:%s", args.ID), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -91,7 +91,7 @@ func UpdateBasicImage(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid BasicImage args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.UpdateBasicImage(c.Param("id"), args, ctx.Logger) } @@ -100,7 +100,7 @@ func DeleteBasicImage(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "基础镜像", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "基础镜像", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) ctx.Err = service.DeleteBasicImage(c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 92879148..31a84a53 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -44,7 +44,7 @@ func CreateHelmRepo(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid helmRepo json args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName if _, err := url.Parse(args.URL); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid url") return @@ -61,7 +61,7 @@ func UpdateHelmRepo(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid helmRepo json args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.UpdateHelmRepo(c.Param("id"), args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/install.go b/pkg/microservice/aslan/core/system/handler/install.go index f2347d9f..35532d16 100644 --- a/pkg/microservice/aslan/core/system/handler/install.go +++ b/pkg/microservice/aslan/core/system/handler/install.go @@ -44,14 +44,14 @@ func CreateInstall(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateInstall json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "新增", "系统设置-应用设置", fmt.Sprintf("应用名称:%s,应用版本:%s", args.Name, args.Version), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统设置-应用设置", fmt.Sprintf("应用名称:%s,应用版本:%s", args.Name, args.Version), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid Install args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.CreateInstall(args, ctx.Logger) } @@ -68,14 +68,14 @@ func UpdateInstall(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateInstall json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "更新", "系统设置-应用设置", fmt.Sprintf("应用名称:%s,应用版本:%s", args.Name, args.Version), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统设置-应用设置", fmt.Sprintf("应用名称:%s,应用版本:%s", args.Name, args.Version), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid Install args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName name := args.Name version := args.Version if name == "" || version == "" { @@ -116,7 +116,7 @@ func DeleteInstall(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("DeleteInstall json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "系统设置-应用设置", fmt.Sprintf("应用名称:%s,应用版本:%s", args.Name, args.Version), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "系统设置-应用设置", fmt.Sprintf("应用名称:%s,应用版本:%s", args.Name, args.Version), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { diff --git a/pkg/microservice/aslan/core/system/handler/jenkins.go b/pkg/microservice/aslan/core/system/handler/jenkins.go index 20e79621..3f062081 100644 --- a/pkg/microservice/aslan/core/system/handler/jenkins.go +++ b/pkg/microservice/aslan/core/system/handler/jenkins.go @@ -36,7 +36,7 @@ func CreateJenkinsIntegration(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid jenkinsIntegration json args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName if _, err := url.Parse(args.URL); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid url") return @@ -60,7 +60,7 @@ func UpdateJenkinsIntegration(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid jenkinsIntegration json args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.UpdateJenkinsIntegration(c.Param("id"), args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/notify.go b/pkg/microservice/aslan/core/system/handler/notify.go index 5093cea2..f654e8a1 100644 --- a/pkg/microservice/aslan/core/system/handler/notify.go +++ b/pkg/microservice/aslan/core/system/handler/notify.go @@ -31,7 +31,7 @@ func PullNotify(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.PullNotify(ctx.Username, ctx.Logger) + ctx.Resp, ctx.Err = service.PullNotify(ctx.UserName, ctx.Logger) } type readNotificationsArgs struct { @@ -48,7 +48,7 @@ func ReadNotify(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid readnotificationsargs") return } - ctx.Err = service.ReadNotify(ctx.Username, args.IDs, ctx.Logger) + ctx.Err = service.ReadNotify(ctx.UserName, args.IDs, ctx.Logger) } type deleteNotificationsArgs struct { @@ -65,7 +65,7 @@ func DeleteNotifies(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid args") return } - ctx.Err = service.DeleteNotifies(ctx.Username, args.IDs, ctx.Logger) + ctx.Err = service.DeleteNotifies(ctx.UserName, args.IDs, ctx.Logger) } func UpsertSubscription(c *gin.Context) { @@ -78,7 +78,7 @@ func UpsertSubscription(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid subscription args") return } - ctx.Err = service.UpsertSubscription(ctx.Username, args, ctx.Logger) + ctx.Err = service.UpsertSubscription(ctx.UserName, args, ctx.Logger) } func UpdateSubscribe(c *gin.Context) { @@ -96,7 +96,7 @@ func UpdateSubscribe(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid notification type") return } - ctx.Err = service.UpdateSubscribe(ctx.Username, notifytype, args, ctx.Logger) + ctx.Err = service.UpdateSubscribe(ctx.UserName, notifytype, args, ctx.Logger) } func Unsubscribe(c *gin.Context) { @@ -107,12 +107,12 @@ func Unsubscribe(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid notification type") return } - ctx.Err = service.Unsubscribe(ctx.Username, notifytype, ctx.Logger) + ctx.Err = service.Unsubscribe(ctx.UserName, notifytype, ctx.Logger) } func ListSubscriptions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListSubscriptions(ctx.Username, ctx.Logger) + ctx.Resp, ctx.Err = service.ListSubscriptions(ctx.UserName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index f2b32a18..93377b12 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -58,7 +58,7 @@ func CreatePrivateKey(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreatePrivateKey json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "新增", "资源管理-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "资源管理-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -66,7 +66,7 @@ func CreatePrivateKey(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid PrivateKey args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.CreatePrivateKey(args, ctx.Logger) } @@ -83,7 +83,7 @@ func UpdatePrivateKey(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdatePrivateKey json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "更新", "资源管理-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "资源管理-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -91,7 +91,7 @@ func UpdatePrivateKey(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid PrivateKey args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.UpdatePrivateKey(c.Param("id"), args, ctx.Logger) } @@ -100,6 +100,6 @@ func DeletePrivateKey(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "资源管理-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "资源管理-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/proxy.go b/pkg/microservice/aslan/core/system/handler/proxy.go index 2f0ed260..896bca8b 100644 --- a/pkg/microservice/aslan/core/system/handler/proxy.go +++ b/pkg/microservice/aslan/core/system/handler/proxy.go @@ -69,7 +69,7 @@ func CreateProxy(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateProxy json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "新增", "代理", fmt.Sprintf("server:%s:%d", args.Address, args.Port), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "代理", fmt.Sprintf("server:%s:%d", args.Address, args.Port), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -77,7 +77,7 @@ func CreateProxy(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid proxy args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.CreateProxy(args, ctx.Logger) } @@ -94,7 +94,7 @@ func UpdateProxy(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProxy json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "更新", "代理", fmt.Sprintf("id:%s", args.ID), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "代理", fmt.Sprintf("id:%s", args.ID), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -102,7 +102,7 @@ func UpdateProxy(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid proxy args") return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = service.UpdateProxy(c.Param("id"), args, ctx.Logger) } @@ -111,7 +111,7 @@ func DeleteProxy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "代理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "代理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) ctx.Err = service.DeleteProxy(c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 61e7f23b..7a419d0c 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -79,7 +79,7 @@ func CreateRegistryNamespace(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateRegistryNamespace json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "新增", "系统设置-Registry", fmt.Sprintf("提供商:%s,Namespace:%s", args.RegProvider, args.Namespace), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统设置-Registry", fmt.Sprintf("提供商:%s,Namespace:%s", args.RegProvider, args.Namespace), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -92,7 +92,7 @@ func CreateRegistryNamespace(c *gin.Context) { return } - ctx.Err = service.CreateRegistryNamespace(ctx.Username, args, ctx.Logger) + ctx.Err = service.CreateRegistryNamespace(ctx.UserName, args, ctx.Logger) } func UpdateRegistryNamespace(c *gin.Context) { @@ -107,7 +107,7 @@ func UpdateRegistryNamespace(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateRegistryNamespace json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "更新", "系统设置-Registry", fmt.Sprintf("提供商:%s,Namespace:%s", args.RegProvider, args.Namespace), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统设置-Registry", fmt.Sprintf("提供商:%s,Namespace:%s", args.RegProvider, args.Namespace), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -120,14 +120,14 @@ func UpdateRegistryNamespace(c *gin.Context) { return } - ctx.Err = service.UpdateRegistryNamespace(ctx.Username, c.Param("id"), args, ctx.Logger) + ctx.Err = service.UpdateRegistryNamespace(ctx.UserName, c.Param("id"), args, ctx.Logger) } func DeleteRegistryNamespace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "系统设置-Registry", fmt.Sprintf("registry ID:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "系统设置-Registry", fmt.Sprintf("registry ID:%s", c.Param("id")), "", ctx.Logger) ctx.Err = service.DeleteRegistryNamespace(c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index 8c2c5732..f7f0aa2d 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -51,7 +51,7 @@ func CreateS3Storage(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateS3Storage json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "新增", "系统设置-对象存储", fmt.Sprintf("地址:%s", c.GetString("s3StorageEndpoint")), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统设置-对象存储", fmt.Sprintf("地址:%s", c.GetString("s3StorageEndpoint")), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -67,7 +67,7 @@ func CreateS3Storage(c *gin.Context) { return } - ctx.Err = service.CreateS3Storage(ctx.Username, args, ctx.Logger) + ctx.Err = service.CreateS3Storage(ctx.UserName, args, ctx.Logger) } func GetS3Storage(c *gin.Context) { @@ -89,7 +89,7 @@ func UpdateS3Storage(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateS3Storage json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "更新", "系统设置-对象存储", fmt.Sprintf("地址:%s", c.GetString("s3StorageEndpoint")), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统设置-对象存储", fmt.Sprintf("地址:%s", c.GetString("s3StorageEndpoint")), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -106,14 +106,14 @@ func UpdateS3Storage(c *gin.Context) { } id := c.Param("id") - ctx.Err = service.UpdateS3Storage(ctx.Username, id, args, ctx.Logger) + ctx.Err = service.UpdateS3Storage(ctx.UserName, id, args, ctx.Logger) } func DeleteS3Storage(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "系统设置-对象存储", fmt.Sprintf("s3Storage ID:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "系统设置-对象存储", fmt.Sprintf("s3Storage ID:%s", c.Param("id")), "", ctx.Logger) - ctx.Err = service.DeleteS3Storage(ctx.Username, c.Param("id"), ctx.Logger) + ctx.Err = service.DeleteS3Storage(ctx.UserName, c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go b/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go index bbc4dc59..f4d5a4c2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go +++ b/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go @@ -41,7 +41,7 @@ func ListFavoritePipelines(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("type can't be empty!") return } - ctx.Resp, ctx.Err = workflow.ListFavoritePipelines(&commonrepo.FavoriteArgs{UserID: ctx.User.ID, ProductName: productName, Type: workflowType}) + ctx.Resp, ctx.Err = workflow.ListFavoritePipelines(&commonrepo.FavoriteArgs{UserID: ctx.UserID, ProductName: productName, Type: workflowType}) } func DeleteFavoritePipeline(c *gin.Context) { @@ -55,8 +55,8 @@ func DeleteFavoritePipeline(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("productName or name or type can't be empty!") return } - internalhandler.InsertOperationLog(c, ctx.Username, productName, "删除", "收藏工作流", workflowName, "", ctx.Logger) - ctx.Err = workflow.DeleteFavoritePipeline(&commonrepo.FavoriteArgs{UserID: ctx.User.ID, ProductName: productName, Type: workflowType, Name: workflowName}) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "删除", "收藏工作流", workflowName, "", ctx.Logger) + ctx.Err = workflow.DeleteFavoritePipeline(&commonrepo.FavoriteArgs{UserID: ctx.UserID, ProductName: productName, Type: workflowType, Name: workflowName}) } func CreateFavoritePipeline(c *gin.Context) { @@ -71,14 +71,14 @@ func CreateFavoritePipeline(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateFavoritePipeline json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "收藏工作流", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "收藏工作流", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid Favorite json args") return } - args.UserID = ctx.User.ID + args.UserID = ctx.UserID ctx.Err = workflow.CreateFavoritePipeline(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline.go b/pkg/microservice/aslan/core/workflow/handler/pipeline.go index cf79ec3e..ca327963 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline.go @@ -54,7 +54,7 @@ func GetProductNameByPipeline(c *gin.Context) { if pipelineName == "" { pipelineName = c.Param("name") } - pipeline, err := workflow.GetPipeline(ctx.User.ID, pipelineName, ctx.Logger) + pipeline, err := workflow.GetPipeline(ctx.UserID, pipelineName, ctx.Logger) if err != nil { log.Errorf("GetProductNameByPipeline err : %v", err) return @@ -85,7 +85,7 @@ func GetPipeline(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.GetPipeline(ctx.User.ID, c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = workflow.GetPipeline(ctx.UserID, c.Param("name"), ctx.Logger) } // UpsertPipeline create a new pipeline @@ -101,7 +101,7 @@ func UpsertPipeline(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpsertPipeline json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "单服务-工作流", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "单服务-工作流", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil || len(args.Name) == 0 { @@ -109,7 +109,7 @@ func UpsertPipeline(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("invalid pipeline json args: %v", err)) return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = workflow.UpsertPipeline(args, ctx.Logger) } @@ -118,8 +118,8 @@ func CopyPipeline(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "复制", "单服务-工作流", fmt.Sprintf("old:%s,new:%s", c.Param("old"), c.Param("new")), "", ctx.Logger) - ctx.Err = workflow.CopyPipeline(c.Param("old"), c.Param("new"), ctx.Username, ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "复制", "单服务-工作流", fmt.Sprintf("old:%s,new:%s", c.Param("old"), c.Param("new")), "", ctx.Logger) + ctx.Err = workflow.CopyPipeline(c.Param("old"), c.Param("new"), ctx.UserName, ctx.Logger) } // RenamePipeline rename pipeline @@ -127,7 +127,7 @@ func RenamePipeline(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "修改", "单服务-工作流", fmt.Sprintf("old:%s,new:%s", c.Param("old"), c.Param("new")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "修改", "单服务-工作流", fmt.Sprintf("old:%s,new:%s", c.Param("old"), c.Param("new")), "", ctx.Logger) ctx.Err = workflow.RenamePipeline(c.Param("old"), c.Param("new"), ctx.Logger) } @@ -136,6 +136,6 @@ func DeletePipeline(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "删除", "单服务-工作流", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "删除", "单服务-工作流", c.Param("name"), "", ctx.Logger) ctx.Err = commonservice.DeletePipeline(c.Param("name"), ctx.RequestID, false, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_status.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_status.go index be25decb..a171d0e0 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_status.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_status.go @@ -28,7 +28,7 @@ func ListPipelinesPreview(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListPipelinesPreview(ctx.User.ID, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListPipelinesPreview(ctx.UserID, ctx.Logger) } // find task by commitId diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go index 40b5f9dc..476b8412 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go @@ -47,7 +47,7 @@ func GetProductNameByPipelineTask(c *gin.Context) { return } - pipeline, err := workflow.GetPipeline(ctx.User.ID, args.PipelineName, ctx.Logger) + pipeline, err := workflow.GetPipeline(ctx.UserID, args.PipelineName, ctx.Logger) if err != nil { log.Errorf("GetProductNameByPipelineTask err : %v", err) return @@ -70,7 +70,7 @@ func CreatePipelineTask(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreatePipelineTask json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "新增", "单服务-工作流task", args.PipelineName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "新增", "单服务-工作流task", args.PipelineName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -78,7 +78,7 @@ func CreatePipelineTask(c *gin.Context) { return } if args.TaskCreator == "" { - args.TaskCreator = ctx.Username + args.TaskCreator = ctx.UserName } args.ReqID = ctx.RequestID @@ -86,7 +86,7 @@ func CreatePipelineTask(c *gin.Context) { // 发送通知 if ctx.Err != nil { - commonservice.SendFailedTaskMessage(ctx.Username, args.ProductName, args.PipelineName, ctx.RequestID, config.SingleType, ctx.Err, ctx.Logger) + commonservice.SendFailedTaskMessage(ctx.UserName, args.ProductName, args.PipelineName, ctx.RequestID, config.SingleType, ctx.Err, ctx.Logger) } } @@ -125,7 +125,7 @@ func GetPipelineTask(c *gin.Context) { func RestartPipelineTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "重启", "单服务-工作流task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "重启", "单服务-工作流task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -133,20 +133,20 @@ func RestartPipelineTask(c *gin.Context) { return } - ctx.Err = workflow.RestartPipelineTaskV2(ctx.Username, taskID, c.Param("name"), config.SingleType, ctx.Logger) + ctx.Err = workflow.RestartPipelineTaskV2(ctx.UserName, taskID, c.Param("name"), config.SingleType, ctx.Logger) } func CancelTaskV2(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "取消", "单服务-工作流task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "取消", "单服务-工作流task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - ctx.Err = commonservice.CancelTaskV2(ctx.Username, c.Param("name"), taskID, config.SingleType, ctx.RequestID, ctx.Logger) + ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.SingleType, ctx.RequestID, ctx.Logger) } // ListPipelineUpdatableProductNames 启动任务时检查部署环境 @@ -154,7 +154,7 @@ func ListPipelineUpdatableProductNames(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListPipelineUpdatableProductNames(ctx.Username, c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListPipelineUpdatableProductNames(ctx.UserName, c.Param("name"), ctx.Logger) } func GetPackageFile(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/handler/sse.go b/pkg/microservice/aslan/core/workflow/handler/sse.go index a83214fa..3ba90e70 100644 --- a/pkg/microservice/aslan/core/workflow/handler/sse.go +++ b/pkg/microservice/aslan/core/workflow/handler/sse.go @@ -46,14 +46,14 @@ func GetPipelineTaskSSE(c *gin.Context) { err := wait.PollImmediateUntil(time.Second, func() (bool, error) { res, err := workflow.GetPipelineTaskV2(taskID, c.Param("name"), config.SingleType, ctx.Logger) if err != nil { - ctx.Logger.Errorf("[%s] GetPipelineTaskSSE error: %v", ctx.Username, err) + ctx.Logger.Errorf("[%s] GetPipelineTaskSSE error: %v", ctx.UserName, err) return false, err } msgChan <- res if time.Since(startTime).Minutes() == float64(60) { - ctx.Logger.Warnf("[%s] Query GetPipelineTaskSSE API over 60 minutes", ctx.Username) + ctx.Logger.Warnf("[%s] Query GetPipelineTaskSSE API over 60 minutes", ctx.UserName) } return false, nil @@ -74,7 +74,7 @@ func RunningPipelineTasksSSE(c *gin.Context) { msgChan <- workflow.RunningPipelineTasks() if time.Since(startTime).Minutes() == float64(60) { - ctx.Logger.Warnf("[%s] Query RunningPipelineTasksSSE API over 60 minutes", ctx.Username) + ctx.Logger.Warnf("[%s] Query RunningPipelineTasksSSE API over 60 minutes", ctx.UserName) } }, time.Second) }, ctx.Logger) @@ -89,7 +89,7 @@ func PendingPipelineTasksSSE(c *gin.Context) { msgChan <- workflow.PendingPipelineTasks() if time.Since(startTime).Minutes() == float64(60) { - ctx.Logger.Warnf("[%s] Query PendingPipelineTasksSSE API over 60 minutes", ctx.Username) + ctx.Logger.Warnf("[%s] Query PendingPipelineTasksSSE API over 60 minutes", ctx.UserName) } }, time.Second) }, ctx.Logger) @@ -116,14 +116,14 @@ func GetWorkflowTaskSSE(c *gin.Context) { err := wait.PollImmediateUntil(time.Second, func() (bool, error) { res, err := workflow.GetPipelineTaskV2(taskID, c.Param("name"), workflowTypeString, ctx.Logger) if err != nil { - ctx.Logger.Errorf("[%s] GetPipelineTaskSSE error: %v", ctx.Username, err) + ctx.Logger.Errorf("[%s] GetPipelineTaskSSE error: %v", ctx.UserName, err) return false, err } msgChan <- res if time.Since(startTime).Minutes() == float64(60) { - ctx.Logger.Warnf("[%s] Query GetPipelineTaskSSE API over 60 minutes", ctx.Username) + ctx.Logger.Warnf("[%s] Query GetPipelineTaskSSE API over 60 minutes", ctx.UserName) } return false, nil diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index e7433928..724265f2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -78,15 +78,15 @@ func CreateWorkflow(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateWorkflow json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductTmplName, "新增", "工作流", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductTmplName, "新增", "工作流", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - args.UpdateBy = ctx.Username - args.CreateBy = ctx.Username + args.UpdateBy = ctx.UserName + args.CreateBy = ctx.UserName ctx.Err = workflow.CreateWorkflow(args, ctx.Logger) } @@ -103,14 +103,14 @@ func UpdateWorkflow(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateWorkflow json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductTmplName, "更新", "工作流", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductTmplName, "更新", "工作流", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - args.UpdateBy = ctx.Username + args.UpdateBy = ctx.UserName ctx.Err = workflow.UpdateWorkflow(args, ctx.Logger) } @@ -118,14 +118,14 @@ func ListWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), ctx.User.ID, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), ctx.UserID, ctx.Logger) } func ListTestWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListTestWorkflows(c.Param("testName"), ctx.User.ID, c.QueryArray("projects"), ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListTestWorkflows(c.Param("testName"), c.QueryArray("projects"), ctx.Logger) } // FindWorkflow find a workflow @@ -140,7 +140,7 @@ func DeleteWorkflow(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "删除", "工作流", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "删除", "工作流", c.Param("name"), "", ctx.Logger) ctx.Err = commonservice.DeleteWorkflow(c.Param("name"), ctx.RequestID, false, ctx.Logger) } @@ -155,5 +155,5 @@ func CopyWorkflow(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = workflow.CopyWorkflow(c.Param("old"), c.Param("new"), ctx.Username, ctx.Logger) + ctx.Err = workflow.CopyWorkflow(c.Param("old"), c.Param("new"), ctx.UserName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 4ec37261..bb3bb851 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -97,7 +97,7 @@ func CreateWorkflowTask(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateWorkflowTask json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductTmplName, "新增", "工作流-task", args.WorkflowName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductTmplName, "新增", "工作流-task", args.WorkflowName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { @@ -106,14 +106,14 @@ func CreateWorkflowTask(c *gin.Context) { } if args.WorklowTaskCreator != setting.CronTaskCreator && args.WorklowTaskCreator != setting.WebhookTaskCreator { - args.WorklowTaskCreator = ctx.Username + args.WorklowTaskCreator = ctx.UserName } - ctx.Resp, ctx.Err = workflow.CreateWorkflowTask(args, args.WorklowTaskCreator, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateWorkflowTask(args, args.WorklowTaskCreator, ctx.Logger) // 发送通知 if ctx.Err != nil { - commonservice.SendFailedTaskMessage(ctx.Username, args.ProductTmplName, args.WorkflowName, ctx.RequestID, config.WorkflowType, ctx.Err, ctx.Logger) + commonservice.SendFailedTaskMessage(ctx.UserName, args.ProductTmplName, args.WorkflowName, ctx.RequestID, config.WorkflowType, ctx.Err, ctx.Logger) } } @@ -130,7 +130,7 @@ func CreateArtifactWorkflowTask(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateArtifactWorkflowTask json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductTmplName, "新增", "工作流-task", args.WorkflowName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductTmplName, "新增", "工作流-task", args.WorkflowName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { @@ -139,10 +139,10 @@ func CreateArtifactWorkflowTask(c *gin.Context) { } if args.WorklowTaskCreator != setting.CronTaskCreator && args.WorklowTaskCreator != setting.WebhookTaskCreator { - args.WorklowTaskCreator = ctx.Username + args.WorklowTaskCreator = ctx.UserName } - ctx.Resp, ctx.Err = workflow.CreateArtifactWorkflowTask(args, args.WorklowTaskCreator, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateArtifactWorkflowTask(args, args.WorklowTaskCreator, ctx.Logger) } // ListWorkflowTasksResult workflowtask分页信息 @@ -188,7 +188,7 @@ func GetWorkflowTask(c *gin.Context) { func RestartWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "重启", "工作流-task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "重启", "工作流-task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -196,18 +196,18 @@ func RestartWorkflowTask(c *gin.Context) { return } - ctx.Err = workflow.RestartPipelineTaskV2(ctx.Username, taskID, c.Param("name"), config.WorkflowType, ctx.Logger) + ctx.Err = workflow.RestartPipelineTaskV2(ctx.UserName, taskID, c.Param("name"), config.WorkflowType, ctx.Logger) } func CancelWorkflowTaskV2(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.GetString("productName"), "取消", "工作流-task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "取消", "工作流-task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - ctx.Err = commonservice.CancelTaskV2(ctx.Username, c.Param("name"), taskID, config.WorkflowType, ctx.RequestID, ctx.Logger) + ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.WorkflowType, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go index 9a0eda6d..7b9ea253 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go @@ -30,7 +30,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/codehub" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/types/permission" ) type codehubMergeEventMatcher struct { @@ -200,7 +199,7 @@ func TriggerWorkflowByCodehubEvent(event interface{}, baseURI, requestID string, args.RepoOwner = item.MainRepo.RepoOwner args.RepoName = item.MainRepo.RepoName // 3. create task with args - if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log); err != nil { + if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { log.Errorf("failed to create workflow task when receive push event %v due to %v ", event, err) mErr = multierror.Append(mErr, err) } else { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index 7bbf9e29..c83e09c8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -37,7 +37,6 @@ import ( "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/types/permission" ) type patchsetCreatedEvent struct { @@ -382,7 +381,7 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base workflowArgs.RepoOwner = item.MainRepo.RepoOwner workflowArgs.RepoName = item.MainRepo.RepoName - if resp, err := workflowservice.CreateWorkflowTask(workflowArgs, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log); err != nil { + if resp, err := workflowservice.CreateWorkflowTask(workflowArgs, setting.WebhookTaskCreator, log); err != nil { log.Errorf("TriggerWorkflowByGerritEvent failed to create workflow task when receive push event %v due to %v ", event, err) errorList = multierror.Append(errorList, err) } else { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index a90c906f..dcefdc6c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -34,7 +34,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/types/permission" ) const SplitSymbol = "&" @@ -298,7 +297,7 @@ func TriggerWorkflowByGithubEvent(event interface{}, baseURI, deliveryID, reques args.HookPayload = hookPayload // 3. create task with args - if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log); err != nil { + if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { log.Errorf("failed to create workflow task when receive push event due to %v ", err) mErr = multierror.Append(mErr, err) } else { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index a58c4f5c..3b7a1f4c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -40,7 +40,6 @@ import ( gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util" ) @@ -275,7 +274,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, args.RepoName = item.MainRepo.RepoName // 3. create task with args if item.WorkflowArgs.BaseNamespace == "" { - if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log); err != nil { + if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { log.Errorf("failed to create workflow task when receive push event %v due to %v ", event, err) mErr = multierror.Append(mErr, err) // 单独创建一条通知,展示任务创建失败的错误信息 @@ -447,7 +446,7 @@ func CreateEnvAndTaskByPR(workflowArgs *commonmodels.WorkflowTaskArgs, prID int, } workflowArgs.Namespace = envName - taskResp, err := workflowservice.CreateWorkflowTask(workflowArgs, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log) + taskResp, err := workflowservice.CreateWorkflowTask(workflowArgs, setting.WebhookTaskCreator, log) if err != nil { return fmt.Errorf("CreateEnvAndTaskByPR CreateWorkflowTask err:%v ", err) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go b/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go index 491d1514..22fde091 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go @@ -36,7 +36,6 @@ import ( "github.com/koderover/zadig/pkg/shared/codehost" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/ilyshin" - "github.com/koderover/zadig/pkg/types/permission" ) func ProcessIlyshinHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { @@ -268,7 +267,7 @@ func TriggerWorkflowByIlyshinEvent(event interface{}, baseURI, requestID string, args.RepoOwner = item.MainRepo.RepoOwner args.RepoName = item.MainRepo.RepoName // 3. create task with args - if _, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log); err != nil { + if _, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { log.Errorf("failed to create workflow task when receive push event %v due to %s ", event, err) mErr = multierror.Append(mErr, err) } else { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline.go index 345edc27..8ba33b4a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline.go @@ -42,7 +42,7 @@ func ListPipelines(log *zap.SugaredLogger) ([]*commonmodels.Pipeline, error) { return resp, nil } -func GetPipeline(userID int, pipelineName string, log *zap.SugaredLogger) (*commonmodels.Pipeline, error) { +func GetPipeline(userID, pipelineName string, log *zap.SugaredLogger) (*commonmodels.Pipeline, error) { resp, err := commonrepo.NewPipelineColl().Find(&commonrepo.PipelineFindOption{Name: pipelineName}) if err != nil { log.Error(err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_status.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_status.go index 3f50ddaf..3d468e7e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_status.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_status.go @@ -50,7 +50,7 @@ type PipelinePreview struct { TotalSuccess int `bson:"-" json:"total_success"` } -func ListPipelinesPreview(userID int, log *zap.SugaredLogger) ([]*PipelinePreview, error) { +func ListPipelinesPreview(userID string, log *zap.SugaredLogger) ([]*PipelinePreview, error) { resp := make([]*PipelinePreview, 0) pipelineList, err := commonrepo.NewPipelineColl().List(&commonrepo.PipelineListOption{}) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 3a143e59..bf65a5bb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -422,7 +422,7 @@ func validateWorkflowHookNames(w *commonmodels.Workflow) error { return validateHookNames(names) } -func ListWorkflows(queryType string, userID int, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { +func ListWorkflows(queryType, userID string, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) if err != nil { log.Errorf("Workflow.List error: %v", err) @@ -488,7 +488,7 @@ func findWorkflowStat(workflow *commonmodels.Workflow, workflowStats []*commonmo return 0, 0, 0 } -func ListTestWorkflows(testName string, userID int, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { +func ListTestWorkflows(testName string, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { allWorkflows, err := commonrepo.NewWorkflowColl().ListWorkflowsByProjects(projects) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 033257df..f94144fb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -410,7 +410,7 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) return resp, nil } -func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, userID int, superUser bool, log *zap.SugaredLogger) (*CreateTaskResp, error) { +func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, log *zap.SugaredLogger) (*CreateTaskResp, error) { if args == nil { return nil, fmt.Errorf("args should not be nil") } @@ -1211,7 +1211,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * return resp, nil } -func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, userID int, superUser bool, log *zap.SugaredLogger) (*CreateTaskResp, error) { +func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, log *zap.SugaredLogger) (*CreateTaskResp, error) { if args == nil { return nil, fmt.Errorf("args should not be nil") } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go b/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go index 80ebf491..099cebcf 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go @@ -49,7 +49,7 @@ func CreateTestTask(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateTestTask json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "测试-task", fmt.Sprintf("%s-%s", args.TestName, "job"), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "测试-task", fmt.Sprintf("%s-%s", args.TestName, "job"), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindWith(&args, binding.JSON); err != nil { @@ -58,13 +58,13 @@ func CreateTestTask(c *gin.Context) { } if args.TestTaskCreator != setting.CronTaskCreator && args.TestTaskCreator != setting.WebhookTaskCreator { - args.TestTaskCreator = ctx.Username + args.TestTaskCreator = ctx.UserName } ctx.Resp, ctx.Err = service.CreateTestTask(args, ctx.Logger) // 发送通知 if ctx.Err != nil { - commonservice.SendFailedTaskMessage(ctx.Username, args.ProductName, args.TestName, ctx.RequestID, config.TestType, ctx.Err, ctx.Logger) + commonservice.SendFailedTaskMessage(ctx.UserName, args.ProductName, args.TestName, ctx.RequestID, config.TestType, ctx.Err, ctx.Logger) } } @@ -72,7 +72,7 @@ func RestartTestTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "重启", "测试任务", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "重启", "测试任务", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -80,18 +80,18 @@ func RestartTestTask(c *gin.Context) { return } - ctx.Err = workflowservice.RestartPipelineTaskV2(ctx.Username, taskID, c.Param("name"), config.TestType, ctx.Logger) + ctx.Err = workflowservice.RestartPipelineTaskV2(ctx.UserName, taskID, c.Param("name"), config.TestType, ctx.Logger) } func CancelTestTaskV2(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "取消", "测试任务", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "取消", "测试任务", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - ctx.Err = commonservice.CancelTaskV2(ctx.Username, c.Param("name"), taskID, config.TestType, ctx.RequestID, ctx.Logger) + ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.TestType, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/testing.go b/pkg/microservice/aslan/core/workflow/testing/handler/testing.go index 24c35774..7b70976d 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/testing.go @@ -59,7 +59,7 @@ func CreateTestModule(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateTestModule json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "项目管理-测试", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-测试", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) err = c.BindJSON(args) @@ -68,7 +68,7 @@ func CreateTestModule(c *gin.Context) { return } - ctx.Err = service.CreateTesting(ctx.Username, args, ctx.Logger) + ctx.Err = service.CreateTesting(ctx.UserName, args, ctx.Logger) } func UpdateTestModule(c *gin.Context) { @@ -83,7 +83,7 @@ func UpdateTestModule(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateTestModule json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "更新", "项目管理-测试", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-测试", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) err = c.BindJSON(args) @@ -92,7 +92,7 @@ func UpdateTestModule(c *gin.Context) { return } - ctx.Err = service.UpdateTesting(ctx.Username, args, ctx.Logger) + ctx.Err = service.UpdateTesting(ctx.UserName, args, ctx.Logger) } func ListTestModules(c *gin.Context) { @@ -119,7 +119,7 @@ func DeleteTestModule(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Query("productName"), "删除", "项目管理-测试", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "删除", "项目管理-测试", c.Param("name"), "", ctx.Logger) name := c.Param("name") if name == "" { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 3d79e2cd..24b3b28d 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -246,8 +246,6 @@ const ( ) const ( - SessionUsername = "Username" - SessionUser = "User" AuthorizationHeader = "Authorization" ) diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index 26aff580..001400a6 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -17,7 +17,11 @@ limitations under the License. package handler import ( + "encoding/base64" + "encoding/json" + "fmt" "reflect" + "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" @@ -25,8 +29,6 @@ import ( "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/aslan" - "github.com/koderover/zadig/pkg/shared/poetry" - "github.com/koderover/zadig/pkg/types/permission" "github.com/koderover/zadig/pkg/util/ginzap" ) @@ -35,32 +37,56 @@ type Context struct { Logger *zap.SugaredLogger Err error Resp interface{} - Username string - User *permission.User + UserName string + UserID string RequestID string } +type jwtClaims struct { + Name string `json:"name"` + UID string `json:"uid"` +} + func NewContext(c *gin.Context) *Context { + logger := ginzap.WithContext(c).Sugar() + var claims jwtClaims + + token := c.GetHeader(setting.AuthorizationHeader) + if len(token) > 0 { + var err error + claims, err = getUserFromJWT(token) + if err != nil { + logger.Warnf("Failed to get user from token, err: %s", err) + } + } + return &Context{ - Username: currentUsername(c), - User: currentUser(c), + UserName: claims.Name, + UserID: claims.UID, Logger: ginzap.WithContext(c).Sugar(), RequestID: c.GetString(setting.RequestID), } } -// CurrentUser return current session user -func currentUser(c *gin.Context) *permission.User { - userInfo, isExist := c.Get(setting.SessionUser) - user, ok := userInfo.(*poetry.UserInfo) - if isExist && ok { - return poetry.ConvertUserInfo(user) +func getUserFromJWT(token string) (jwtClaims, error) { + cs := jwtClaims{} + + parts := strings.Split(token, ".") + if len(parts) != 3 { + return cs, fmt.Errorf("compact JWS format must have three parts") + } + + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return cs, err + } + + err = json.Unmarshal(payload, &cs) + if err != nil { + return cs, err } - return permission.AnonymousUser -} -func currentUsername(c *gin.Context) (username string) { - return c.GetString(setting.SessionUsername) + return cs, nil } func JSONResponse(c *gin.Context, ctx *Context) { diff --git a/pkg/shared/poetry/types.go b/pkg/shared/poetry/types.go index 3434f500..7d33b3a4 100644 --- a/pkg/shared/poetry/types.go +++ b/pkg/shared/poetry/types.go @@ -16,8 +16,6 @@ limitations under the License. package poetry -import "github.com/koderover/zadig/pkg/types/permission" - const ( ProjectOwner = 3 ) @@ -57,24 +55,3 @@ type EnvRolePermission struct { PermissionUUID string `json:"permissionUUID"` RoleName string `json:"roleName"` } - -func ConvertUserInfo(userInfo *UserInfo) *permission.User { - if userInfo == nil { - return nil - } - user := new(permission.User) - user.ID = userInfo.ID - user.Name = userInfo.Name - user.Email = userInfo.Email - user.Password = userInfo.Password - user.Phone = userInfo.Phone - user.IsAdmin = userInfo.IsAdmin - user.IsSuperUser = userInfo.IsSuperUser - user.IsTeamLeader = userInfo.IsTeamLeader - user.OrganizationID = userInfo.OrganizationID - user.Directory = userInfo.Directory - user.LastLoginAt = userInfo.LastLoginAt - user.CreatedAt = userInfo.CreatedAt - user.UpdatedAt = userInfo.UpdatedAt - return user -} diff --git a/pkg/types/permission/user.go b/pkg/types/permission/user.go deleted file mode 100644 index 24b3b5f6..00000000 --- a/pkg/types/permission/user.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package permission - -const ( - AnonymousUserID = 0 - AnonymousUserName = "Anonymous" -) - -type User struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Password string `json:"password"` - Phone string `json:"phone"` - IsAdmin bool `json:"isAdmin"` - IsSuperUser bool `json:"isSuperUser"` - IsTeamLeader bool `json:"isTeamLeader"` - OrganizationID int `json:"organization_id"` - Directory string `json:"directory"` - LastLoginAt int64 `json:"lastLogin"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -var AnonymousUser = &User{ID: AnonymousUserID, Name: AnonymousUserName} -- Gitee From fdf5f1d84e868d9f5d3e8faf3848e4b640efa349 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 15:00:53 +0800 Subject: [PATCH 0109/2647] fix go.mod Signed-off-by: lou --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 111b18db..76bc8f27 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/gin-gonic/gin v1.7.2 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 github.com/gorilla/handlers v1.5.1 // indirect diff --git a/go.sum b/go.sum index 8ba24eb1..bc09eeb7 100644 --- a/go.sum +++ b/go.sum @@ -395,8 +395,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -- Gitee From 9fb6c8bdcc09d15d7f468677954fd87deae6a3c7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 16:10:42 +0800 Subject: [PATCH 0110/2647] code refactor Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 8 ++++++++ pkg/microservice/policy/core/handler/router.go | 16 ++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 6bdd5655..c5589a51 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -86,6 +86,14 @@ func DeleteRole(c *gin.Context) { return } +func DeletePublicRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + name := c.Param("name") + ctx.Err = service.DeleteRole(name, "", ctx.Logger) + return +} + func CreateSystemRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index ee52f065..9508bcce 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -30,18 +30,18 @@ func (*Router) Inject(router *gin.RouterGroup) { roles.DELETE("/:name", DeleteRole) } - PublicRoles := router.Group("public-roles") + publicRoles := router.Group("public-roles") { - PublicRoles.POST("", CreatePublicRole) - PublicRoles.GET("", ListPublicRoles) - PublicRoles.DELETE("/:name", DeleteRole) + publicRoles.POST("", CreatePublicRole) + publicRoles.GET("", ListPublicRoles) + publicRoles.DELETE("/:name", DeletePublicRole) } - SystemRoles := router.Group("system-roles") + systemRoles := router.Group("system-roles") { - SystemRoles.POST("", CreateSystemRole) - SystemRoles.GET("", ListSystemRoles) - SystemRoles.DELETE("/:name", DeleteSystemRole) + systemRoles.POST("", CreateSystemRole) + systemRoles.GET("", ListSystemRoles) + systemRoles.DELETE("/:name", DeleteSystemRole) } roleBindings := router.Group("rolebindings") -- Gitee From cd9972fdece02344ded8f905f26564ff24861d31 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 16:32:47 +0800 Subject: [PATCH 0111/2647] add user filter Signed-off-by: lou --- pkg/microservice/policy/core/handler/role_binding.go | 4 ++-- .../policy/core/repository/mongodb/role_binding.go | 7 ++++++- pkg/microservice/policy/core/service/role_binding.go | 11 ++++------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 525621e7..87443742 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -53,7 +53,7 @@ func ListRoleBindings(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.ListRoleBindings(projectName, ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoleBindings(projectName, c.Query("user"), ctx.Logger) } func DeleteRoleBinding(c *gin.Context) { @@ -97,5 +97,5 @@ func ListSystemRoleBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListRoleBindings(service.SystemScope, ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoleBindings(service.SystemScope, c.Query("user"), ctx.Logger) } diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index dec62194..722050fe 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -78,11 +78,16 @@ func (c *RoleBindingColl) List() ([]*models.RoleBinding, error) { return res, nil } -func (c *RoleBindingColl) ListBy(projectName string) ([]*models.RoleBinding, error) { +func (c *RoleBindingColl) ListBy(projectName, user string) ([]*models.RoleBinding, error) { var res []*models.RoleBinding ctx := context.Background() query := bson.M{"namespace": projectName} + if user != "" { + query["subjects.name"] = user + query["subjects.kind"] = models.UserKind + } + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index dd9e3bb7..c89adb9a 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -61,21 +61,18 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Create(obj) } -func ListRoleBindings(ns string, logger *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { - modelRoleBindings, err := mongodb.NewRoleBindingColl().ListBy(ns) +func ListRoleBindings(ns, user string, _ *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { + modelRoleBindings, err := mongodb.NewRoleBindingColl().ListBy(ns, user) if err != nil { return nil, err } + for _, v := range modelRoleBindings { - users := []string{} - for _, vv := range v.Subjects { - users = append(users, vv.Name) - } roleBindings = append(roleBindings, &RoleBinding{ Name: v.Name, Role: v.RoleRef.Name, User: v.Subjects[0].Name, - Global: (v.Namespace == ""), + Global: v.Namespace == "", }) } return -- Gitee From 2ab773bae7a0fe8078019c5bc7dc56ba6b5425f3 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 17:20:39 +0800 Subject: [PATCH 0112/2647] remove poetry calls Signed-off-by: lou --- .../aslan/core/common/service/product.go | 19 --- .../core/environment/handler/environment.go | 2 +- .../aslan/core/environment/handler/router.go | 2 +- .../core/environment/service/environment.go | 51 +++--- .../aslan/core/project/service/product.go | 15 -- pkg/shared/poetry/actions.go | 156 ------------------ pkg/shared/poetry/permission.go | 36 ---- pkg/shared/poetry/team.go | 61 ------- pkg/shared/poetry/token.go | 36 ---- pkg/shared/poetry/types.go | 57 ------- 10 files changed, 27 insertions(+), 408 deletions(-) delete mode 100644 pkg/shared/poetry/actions.go delete mode 100644 pkg/shared/poetry/permission.go delete mode 100644 pkg/shared/poetry/team.go delete mode 100644 pkg/shared/poetry/token.go delete mode 100644 pkg/shared/poetry/types.go diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 8028e268..a62713d3 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -26,7 +26,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -34,7 +33,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/updater" @@ -72,8 +70,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Infof("[%s] delete product %s", username, productInfo.Namespace) LogProductStats(username, setting.DeleteProductEvent, productName, requestID, eventStart, log) - poetryClient := poetry.New(config.PoetryAPIServer()) - switch productInfo.Source { case setting.SourceFromHelm: err = mongodb.NewProductColl().Delete(envName, productName) @@ -81,11 +77,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("Product.Delete error: %v", err) } - _, err = poetryClient.DeleteEnvRolePermission(productName, envName, log) - if err != nil { - log.Errorf("DeleteEnvRole error: %v", err) - } - go func() { var err error defer func() { @@ -133,11 +124,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("Product.Delete error: %v", err) } - _, err = poetryClient.DeleteEnvRolePermission(productName, envName, log) - if err != nil { - log.Errorf("DeleteEnvRole error: %v", err) - } - // 删除workload数据 tempProduct, err := template.NewProductColl().Find(productName) if err != nil { @@ -208,11 +194,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if err != nil { log.Errorf("Product.Delete error: %v", err) } - - _, err = poetryClient.DeleteEnvRolePermission(productName, envName, log) - if err != nil { - log.Errorf("DeleteEnvRole error: %v", err) - } }() } return nil diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 47051cef..c146c8a8 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -455,7 +455,7 @@ func EnvShare(c *gin.Context) { return } - ctx.Err = service.UpdateProductPublic(productName, args, ctx.Logger) + //ctx.Err = service.UpdateProductPublic(productName, args, ctx.Logger) } func ListGroups(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index d2ffbbf1..d68c536a 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -106,7 +106,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.PUT("/:productName/renderchart", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) - environments.PUT("/:productName", gin2.UpdateOperationLogStatus, EnvShare) + //environments.PUT("/:productName", gin2.UpdateOperationLogStatus, EnvShare) environments.GET("/:productName", GetProduct) environments.GET("/:productName/productInfo", GetProductInfo) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4f00fed9..22450245 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -52,7 +52,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/wrapper" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -120,31 +119,31 @@ type UpdateMultiHelmProductArg struct { ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented } -func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { - err := commonrepo.NewProductColl().UpdateIsPublic(args.EnvName, productName, args.IsPublic) - if err != nil { - log.Errorf("UpdateProductPublic error: %v", err) - return fmt.Errorf("UpdateProductPublic error: %v", err) - } - - poetryCtl := poetry.New(config.PoetryAPIServer()) - if !args.IsPublic { //把公开设置成不公开 - _, err := poetryCtl.AddEnvRolePermission(productName, args.EnvName, args.PermissionUUIDs, args.RoleID, log) - if err != nil { - log.Errorf("UpdateProductPublic AddEnvRole error: %v", err) - return fmt.Errorf("UpdateProductPublic AddEnvRole error: %v", err) - } - return nil - } - //把不公开设成公开 删除原来环境绑定的角色 - _, err = poetryCtl.DeleteEnvRolePermission(productName, args.EnvName, log) - if err != nil { - log.Errorf("UpdateProductPublic DeleteEnvRole error: %v", err) - return fmt.Errorf("UpdateProductPublic DeleteEnvRole error: %v", err) - } - - return nil -} +//func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { +// err := commonrepo.NewProductColl().UpdateIsPublic(args.EnvName, productName, args.IsPublic) +// if err != nil { +// log.Errorf("UpdateProductPublic error: %v", err) +// return fmt.Errorf("UpdateProductPublic error: %v", err) +// } +// +// poetryCtl := poetry.New(config.PoetryAPIServer()) +// if !args.IsPublic { //把公开设置成不公开 +// _, err := poetryCtl.AddEnvRolePermission(productName, args.EnvName, args.PermissionUUIDs, args.RoleID, log) +// if err != nil { +// log.Errorf("UpdateProductPublic AddEnvRole error: %v", err) +// return fmt.Errorf("UpdateProductPublic AddEnvRole error: %v", err) +// } +// return nil +// } +// //把不公开设成公开 删除原来环境绑定的角色 +// _, err = poetryCtl.DeleteEnvRolePermission(productName, args.EnvName, log) +// if err != nil { +// log.Errorf("UpdateProductPublic DeleteEnvRole error: %v", err) +// return fmt.Errorf("UpdateProductPublic DeleteEnvRole error: %v", err) +// } +// +// return nil +//} func ListProducts(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam, IsSortByProductName: true}) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 7c867f32..21e45e26 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -212,13 +212,6 @@ func UpdateProject(name string, args *template.Product, log *zap.SugaredLogger) if err != nil { return e.ErrInvalidParam.AddDesc(err.Error()) } - poetryCtl := poetry.New(config.PoetryAPIServer()) - //创建团建和项目之间的关系 - _, err = poetryCtl.AddProductTeam(args.ProductName, args.TeamID, args.UserIDs, log) - if err != nil { - log.Errorf("Project.Create AddProductTeam error: %v", err) - return e.ErrUpdateProduct.AddDesc(err.Error()) - } err = templaterepo.NewProductColl().Update(name, args) if err != nil { @@ -319,14 +312,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } } - poetryCtl := poetry.New(config.PoetryAPIServer()) - - //删除项目团队信息 - if err = poetryCtl.DeleteProductTeam(productName, log); err != nil { - log.Errorf("productTeam.Delete error: %v", err) - return e.ErrDeleteProduct - } - envs, _ := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) for _, env := range envs { if err = commonrepo.NewProductColl().UpdateStatus(env.EnvName, productName, setting.ProductStatusDeleting); err != nil { diff --git a/pkg/shared/poetry/actions.go b/pkg/shared/poetry/actions.go deleted file mode 100644 index bca86041..00000000 --- a/pkg/shared/poetry/actions.go +++ /dev/null @@ -1,156 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "fmt" - "strconv" - - "go.uber.org/zap" - - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -func (c *Client) AddEnvRolePermission(productName, envName string, permissionUUIDs []string, roleID int, log *zap.SugaredLogger) (int, error) { - url := "/directory/roleEnv" - req := map[string]interface{}{ - "productName": productName, - "envName": envName, - "permissionUUIDs": permissionUUIDs, - "roleId": roleID, - } - - rm := &ResponseMessage{} - _, err := c.Post(url, httpclient.SetBody(req), httpclient.SetResult(rm)) - if err != nil { - log.Errorf("AddEnvRolePermission error: %v", err) - return 0, e.ErrListUsers.AddDesc(err.Error()) - } - - if rm.ResultCode == 0 { - return 1, nil - } - - return 0, e.ErrListUsers.AddDesc(fmt.Sprintf("ResultCode: %d", rm.ResultCode)) -} - -func (c *Client) DeleteEnvRolePermission(productName, envName string, log *zap.SugaredLogger) (int, error) { - url := "/directory/roleEnv" - qs := map[string]string{ - "productName": productName, - "envName": envName, - } - - rm := &ResponseMessage{} - _, err := c.Delete(url, httpclient.SetQueryParams(qs), httpclient.SetResult(rm)) - if err != nil { - log.Errorf("DeleteEnvRolePermission error: %v", err) - return 0, e.ErrListUsers.AddDesc(err.Error()) - } - - if rm.ResultCode == 0 { - return 1, nil - } - return 0, e.ErrListUsers.AddDesc(fmt.Sprintf("ResultCode: %d", rm.ResultCode)) -} - -func (c *Client) ListEnvRolePermission(productName, envName string, roleID int64, log *zap.SugaredLogger) ([]*EnvRolePermission, error) { - url := "/directory/roleEnv" - qs := map[string]string{ - "productName": productName, - "envName": envName, - "roleId": strconv.Itoa(int(roleID)), - } - - res := make([]*EnvRolePermission, 0) - _, err := c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&res)) - if err != nil { - log.Errorf("GetEnvRolePermission error: %v", err) - return res, e.ErrListUsers.AddDesc(err.Error()) - } - - return res, nil -} - -func (c *Client) AddProductTeam(productName string, teamID int, userIDs []int, log *zap.SugaredLogger) (int, error) { - url := "/directory/teamProduct" - req := map[string]interface{}{ - "productName": productName, - "teamId": teamID, - "userIds": userIDs, - } - - rm := &ResponseMessage{} - _, err := c.Post(url, httpclient.SetBody(req), httpclient.SetResult(rm)) - if err != nil { - log.Errorf("AddProductTeam error: %v", err) - return 0, e.ErrCreateProductTeam.AddDesc(err.Error()) - } - - if rm.ResultCode == 0 { - return 1, nil - } - return 0, e.ErrCreateProductTeam.AddDesc(fmt.Sprintf("ResultCode: %d", rm.ResultCode)) -} - -func (c *Client) DeleteProductTeam(productName string, log *zap.SugaredLogger) error { - url := "/directory/teamProduct" - - rm := &ResponseMessage{} - _, err := c.Delete(url, httpclient.SetQueryParam("productName", productName), httpclient.SetResult(rm)) - if err != nil { - log.Errorf("DeleteProductTeam error: %v", err) - return e.ErrDeleteProductTeam.AddDesc(err.Error()) - } - if rm.ResultCode == 0 { - return nil - } - return e.ErrDeleteProductTeam.AddDesc(fmt.Sprintf("ResultCode: %d", rm.ResultCode)) -} - -func (c *Client) HasOperatePermission(productName, permissionUUID string, userID int, isSuperUser bool, log *zap.SugaredLogger) bool { - if isSuperUser { - return true - } - - res, err := c.GetUserPermissionUUIDMap(productName, permissionUUID, userID, log) - if err != nil { - return false - } - - return res["isContain"] -} - -func (c *Client) GetUserPermissionUUIDMap(productName, permissionUUID string, userID int, log *zap.SugaredLogger) (map[string]bool, error) { - url := "/directory/userPermissionRelation" - qs := map[string]string{ - "productName": productName, - "userId": strconv.Itoa(userID), - "permissionUUID": permissionUUID, - "permissionType": "2", - } - - res := make(map[string]bool) - _, err := c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&res)) - if err != nil { - log.Errorf("GetUserPermissionUUIDMap error: %v", err) - return nil, err - } - - return res, nil -} diff --git a/pkg/shared/poetry/permission.go b/pkg/shared/poetry/permission.go deleted file mode 100644 index c0f1d079..00000000 --- a/pkg/shared/poetry/permission.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type UserEnvPermission struct { - UserID int `json:"userId"` - ProductName string `json:"productName"` - EnvName string `json:"envName"` - PermissionUUIDs []string `json:"permissionUUIDs"` -} - -func (c *Client) CreateUserEnvPermission(p *UserEnvPermission) error { - url := "/directory/userEnvPermission" - _, err := c.Post(url, httpclient.SetBody(p)) - - return err - -} diff --git a/pkg/shared/poetry/team.go b/pkg/shared/poetry/team.go deleted file mode 100644 index 4b7d096d..00000000 --- a/pkg/shared/poetry/team.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "fmt" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Team struct { - ID int `json:"id"` - OrgID int `json:"orgId"` - Name string `json:"name"` - Desc string `json:"desc"` - IsTeamLeader bool `json:"isTeamLeader"` - Users []*UserInfo `json:"leaders"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -func (c *Client) ListTeams(orgID int, log *zap.SugaredLogger) ([]*Team, error) { - url := "/directory/teamss/search" - resp := make([]*Team, 0) - _, err := c.Get(url, httpclient.SetQueryParam("orgId", fmt.Sprintf("%d", orgID)), httpclient.SetResult(&resp)) - - if err != nil { - log.Errorf("ListTeams error: %v", err) - return nil, err - } - - return resp, nil -} - -func (c *Client) GetTeam(teamID int) (*Team, error) { - url := fmt.Sprintf("/directory/teams/%d", teamID) - resp := new(Team) - _, err := c.Get(url, httpclient.SetResult(&resp)) - - if err != nil { - return nil, err - } - - return resp, nil -} diff --git a/pkg/shared/poetry/token.go b/pkg/shared/poetry/token.go deleted file mode 100644 index bf31b540..00000000 --- a/pkg/shared/poetry/token.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import "github.com/koderover/zadig/pkg/tool/httpclient" - -type signature struct { - Token string `json:"token"` - Username string `json:"username"` -} - -func (c *Client) SignatureCheck(username, token string) error { - url := "/directory/token/check" - - s := &signature{Username: username, Token: token} - _, err := c.Post(url, httpclient.SetBody(s)) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/shared/poetry/types.go b/pkg/shared/poetry/types.go deleted file mode 100644 index 7d33b3a4..00000000 --- a/pkg/shared/poetry/types.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -const ( - ProjectOwner = 3 -) - -type User interface { - GetName() string - GetEmail() string - GetPhone() string -} - -type UserInfo struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Password string `json:"password"` - Phone string `json:"phone"` - IsAdmin bool `json:"isAdmin"` - IsSuperUser bool `json:"isSuperUser"` - IsTeamLeader bool `json:"isTeamLeader"` - OrganizationID int `json:"organization_id"` - Directory string `json:"directory"` - LastLoginAt int64 `json:"lastLogin"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - -type ResponseMessage struct { - ResultCode int `json:"resultCode"` - ErrorMsg string `json:"errorMsg"` -} - -type EnvRolePermission struct { - ID int64 `json:"id"` - ProductName string `json:"productName"` - EnvName string `json:"envName"` - RoleID int64 `json:"roleId"` - PermissionUUID string `json:"permissionUUID"` - RoleName string `json:"roleName"` -} -- Gitee From b6cb4d07bc62f0cc12a460a504e2fb10c66905eb Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 20 Oct 2021 18:03:00 +0800 Subject: [PATCH 0113/2647] fix create env bug Signed-off-by: liu deyi --- .../core/environment/service/environment.go | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7931ff5e..8a130328 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1941,26 +1941,37 @@ func upsertService(isUpdate bool, env *commonmodels.Product, u.SetAPIVersion(setting.APIVersionAppsV1) u.SetLabels(kube.MergeLabels(labels, u.GetLabels())) - podLabels, _, _ := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") - err := unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") + podLabels, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") + if err != nil { + podLabels = map[string]string{} + } + err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") if err != nil { // should not have happened - panic(err) + errList = multierror.Append(errList, fmt.Errorf("merge label failed err:%s", err)) + continue } - podAnnotations, _, _ := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "annotations") + podAnnotations, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "annotations") + if err != nil { + podAnnotations = map[string]string{} + } err = unstructured.SetNestedStringMap(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") if err != nil { - // should not have happened - panic(err) + errList = multierror.Append(errList, fmt.Errorf("merge annotation failed err:%s", err)) + continue } // Inject selector: s-product and s-service - selector, _, _ := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") + selector, _, err := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") + if err != nil { + selector = map[string]string{} + } err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") if err != nil { // should not have happened - panic(err) + errList = multierror.Append(errList, fmt.Errorf("merge selector failed err:%s", err)) + continue } jsonData, err := u.MarshalJSON() -- Gitee From 4fab2fc1aa9d75171c3a31258ebf82c940541448 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 20 Oct 2021 18:45:05 +0800 Subject: [PATCH 0114/2647] fix create env bug Signed-off-by: liu deyi --- .../aslan/core/environment/service/environment.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 8a130328..fccb8dc3 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1943,7 +1943,7 @@ func upsertService(isUpdate bool, env *commonmodels.Product, podLabels, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") if err != nil { - podLabels = map[string]string{} + podLabels = nil } err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") if err != nil { @@ -1954,7 +1954,7 @@ func upsertService(isUpdate bool, env *commonmodels.Product, podAnnotations, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "annotations") if err != nil { - podAnnotations = map[string]string{} + podAnnotations = nil } err = unstructured.SetNestedStringMap(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") if err != nil { @@ -1965,7 +1965,7 @@ func upsertService(isUpdate bool, env *commonmodels.Product, // Inject selector: s-product and s-service selector, _, err := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") if err != nil { - selector = map[string]string{} + selector = nil } err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") if err != nil { -- Gitee From 901bb5e383b013193db4180987fe6944670d9781 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 18:49:01 +0800 Subject: [PATCH 0115/2647] remove superuser Signed-off-by: mouuii --- .../core/environment/handler/revision.go | 2 +- .../core/environment/service/environment.go | 12 +-- .../core/environment/service/revision.go | 69 +---------------- .../aslan/core/project/handler/product.go | 2 +- .../aslan/core/project/service/product.go | 47 +++--------- .../aslan/core/setting/handler/user.go | 2 +- .../aslan/core/setting/service/user.go | 75 +++---------------- pkg/setting/consts.go | 3 +- pkg/shared/client/policy/policy.go | 6 ++ 9 files changed, 41 insertions(+), 177 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/revision.go b/pkg/microservice/aslan/core/environment/handler/revision.go index f35be2c0..e27eb50b 100644 --- a/pkg/microservice/aslan/core/environment/handler/revision.go +++ b/pkg/microservice/aslan/core/environment/handler/revision.go @@ -32,5 +32,5 @@ func ListProductsRevision(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProductsRevisionByFacility(c.Query("basicFacility"), ctx.Logger) return } - ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("productName"), c.Query("envName"), ctx.UserID, false, ctx.Logger) + ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("productName"), c.Query("envName"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4f00fed9..e026ec5f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -234,7 +234,7 @@ func AutoCreateProduct(productName, envType, requestID string, log *zap.SugaredL var mutexAutoUpdate sync.RWMutex -func AutoUpdateProduct(envNames []string, productName, userID string, superUser bool, requestID string, force bool, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func AutoUpdateProduct(envNames []string, productName, requestID string, force bool, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexAutoUpdate.Lock() defer func() { mutexAutoUpdate.Unlock() @@ -271,7 +271,7 @@ func AutoUpdateProduct(envNames []string, productName, userID string, superUser } } - productsRevison, err := ListProductsRevision(productName, "", userID, superUser, log) + productsRevison, err := ListProductsRevision(productName, "", log) if err != nil { log.Errorf("AutoUpdateProduct ListProductsRevision err:%v", err) return envStatuses, err @@ -952,7 +952,7 @@ func updateHelmProductVariable(productResp *commonmodels.Product, oldRenderVersi var mutexUpdateMultiHelm sync.RWMutex -func UpdateMultipleHelmEnv(requestID, userID string, superUser bool, args *UpdateMultiHelmProductArg, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexUpdateMultiHelm.Lock() defer func() { mutexUpdateMultiHelm.Unlock() @@ -961,7 +961,7 @@ func UpdateMultipleHelmEnv(requestID, userID string, superUser bool, args *Updat envNames, productName := args.EnvNames, args.ProductName envStatuses := make([]*EnvStatus, 0) - productsRevision, err := ListProductsRevision(productName, "", userID, superUser, log) + productsRevision, err := ListProductsRevision(productName, "", log) if err != nil { log.Errorf("UpdateMultiHelmProduct ListProductsRevision err:%v", err) return envStatuses, err @@ -1040,14 +1040,14 @@ func UpdateMultipleHelmEnv(requestID, userID string, superUser bool, args *Updat } // UpdateMultiHelmProduct TODO need to be deprecated -func UpdateMultiHelmProduct(envNames []string, updateType, productName, userID string, superUser bool, requestID string, log *zap.SugaredLogger) []*EnvStatus { +func UpdateMultiHelmProduct(envNames []string, updateType, productName string, requestID string, log *zap.SugaredLogger) []*EnvStatus { mutexUpdateMultiHelm.Lock() defer func() { mutexUpdateMultiHelm.Unlock() }() envStatuses := make([]*EnvStatus, 0) - productsRevison, err := ListProductsRevision(productName, "", userID, superUser, log) + productsRevison, err := ListProductsRevision(productName, "", log) if err != nil { log.Errorf("UpdateMultiHelmProduct ListProductsRevision err:%v", err) return envStatuses diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 5ac15c61..ffa22b27 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -20,82 +20,17 @@ import ( "fmt" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListProductsRevision(productName, envName, userID string, superUser bool, log *zap.SugaredLogger) ([]*ProductRevision, error) { - var ( - err error - prodRevs = make([]*ProductRevision, 0) - products = make([]*commonmodels.Product, 0) - productNameMap map[string][]int64 - productNamespaces = sets.NewString() - ) - if superUser { - products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ExcludeStatus: setting.ProductStatusDeleting, Name: productName, EnvName: envName}) - if err != nil { - log.Errorf("Collection.Product.List error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - } else { - //项目下所有公开环境 - publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true, ExcludeStatus: setting.ProductStatusDeleting, Name: productName, EnvName: envName}) - if err != nil { - log.Errorf("Collection.Product.List List product error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - for _, publicProduct := range publicProducts { - products = append(products, publicProduct) - productNamespaces.Insert(publicProduct.Namespace) - } - - poetryCtl := poetry.New(config.PoetryAPIServer()) - productNameMap, err = poetryCtl.GetUserProject(1, log) - if err != nil { - log.Errorf("Collection.Product.List GetUserProject error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - for productName, roleIDs := range productNameMap { - //用户关联角色所关联的环境 - for _, roleID := range roleIDs { - if roleID == setting.RoleOwnerID { - tmpProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ExcludeStatus: setting.ProductStatusDeleting, Name: productName, EnvName: envName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - for _, product := range tmpProducts { - if !productNamespaces.Has(product.Namespace) { - products = append(products, product) - } - } - } else { - roleEnvs, err := poetryCtl.ListRoleEnvs(productName, envName, roleID, log) - if err != nil { - log.Errorf("Collection.Product.List ListRoleEnvs error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - for _, roleEnv := range roleEnvs { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: roleEnv.EnvName}) - if err != nil { - log.Errorf("Collection.Product.List Find product error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - products = append(products, product) - } - } - } - } - } +func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) (prodRevs []*ProductRevision, err error) { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName}) // 获取所有服务模板最新模板信息 allServiceTmpls, err := commonrepo.NewServiceColl().ListAllRevisions() diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 1a1eb777..1f78b1b4 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -169,7 +169,7 @@ func ListTemplatesHierachy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = projectservice.ListTemplatesHierachy(ctx.UserName, ctx.UserID, false, ctx.Logger) + ctx.Resp, ctx.Err = projectservice.ListTemplatesHierachy(ctx.UserName, ctx.Logger) } func ForkProduct(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 7c867f32..c704db82 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -529,7 +529,7 @@ func ForkProduct(username, requestID string, args *template.ForkProject, log *za } policyClient := policy.New() err = policyClient.CreateRoleBinding(args.ProductName, &policy.RoleBinding{ - Name: fmt.Sprintf("%s-%s", args.ProductName, username), + Name: fmt.Sprintf(setting.ContributorRoleBindingFmt, args.ProductName, username), User: username, Role: setting.Contributor, Global: true, @@ -543,13 +543,6 @@ func ForkProduct(username, requestID string, args *template.ForkProject, log *za } func UnForkProduct(userID string, username, productName, workflowName, envName, requestID string, log *zap.SugaredLogger) error { - poetryClient := poetry.New(config.PoetryAPIServer()) - if userEnvPermissions, _ := poetryClient.ListUserEnvPermission(productName, 1, log); len(userEnvPermissions) > 0 { - if err := poetryClient.DeleteUserEnvPermission(productName, username, 1, log); err != nil { - return e.ErrUnForkProduct.AddDesc(fmt.Sprintf("Failed to delete env permission for userID: %d, env: %s, productName: %s, the error is: %+v", userID, username, productName, err)) - } - } - if _, err := workflowservice.FindWorkflow(workflowName, log); err == nil { err = commonservice.DeleteWorkflow(workflowName, requestID, false, log) if err != nil { @@ -558,14 +551,12 @@ func UnForkProduct(userID string, username, productName, workflowName, envName, } } - if roleID := poetryClient.GetContributorRoleID(productName, log); roleID > 0 { - err := poetryClient.DeleteUserRole(roleID, poetry.ProjectType, 1, productName, log) - if err != nil { - log.Errorf("Failed to Delete user from role candidate, the error is: %v", err) - return e.ErrUnForkProduct.AddDesc(err.Error()) - } + policyClient := policy.New() + err := policyClient.DeleteRoleBinding(fmt.Sprintf(setting.ContributorRoleBindingFmt, productName, username), productName) + if err != nil { + log.Error("rolebinding delete error") + return e.ErrForkProduct } - if err := commonservice.DeleteProduct(username, envName, productName, requestID, log); err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { @@ -689,33 +680,17 @@ type ContainerInfo struct { Label string `bson:"label" json:"label"` } -func ListTemplatesHierachy(userName, userID string, superUser bool, log *zap.SugaredLogger) ([]*ProductInfo, error) { +func ListTemplatesHierachy(userName string, log *zap.SugaredLogger) ([]*ProductInfo, error) { var ( err error resp = make([]*ProductInfo, 0) productTmpls = make([]*template.Product, 0) ) - if superUser { - productTmpls, err = templaterepo.NewProductColl().List() - if err != nil { - log.Errorf("[%s] ProductTmpl.List error: %v", userName, err) - return nil, e.ErrListProducts.AddDesc(err.Error()) - } - } else { - productNameMap, err := poetry.New(config.PoetryAPIServer()).GetUserProject(1, log) - if err != nil { - log.Errorf("ProfuctTmpl.List GetUserProject error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - for productName := range productNameMap { - product, err := templaterepo.NewProductColl().Find(productName) - if err != nil { - log.Errorf("ProfuctTmpl.List error: %v", err) - return resp, e.ErrListProducts.AddDesc(err.Error()) - } - productTmpls = append(productTmpls, product) - } + productTmpls, err = templaterepo.NewProductColl().List() + if err != nil { + log.Errorf("[%s] ProductTmpl.List error: %v", userName, err) + return nil, e.ErrListProducts.AddDesc(err.Error()) } for _, productTmpl := range productTmpls { diff --git a/pkg/microservice/aslan/core/setting/handler/user.go b/pkg/microservice/aslan/core/setting/handler/user.go index bdaf0f48..0dc66b84 100644 --- a/pkg/microservice/aslan/core/setting/handler/user.go +++ b/pkg/microservice/aslan/core/setting/handler/user.go @@ -27,5 +27,5 @@ func GetUserKubeConfig(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.UserName, ctx.UserID, false, ctx.Logger) + ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.UserName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index a2d19e0e..11eea83a 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -40,7 +40,6 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/wrapper" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -57,75 +56,23 @@ type kubeCfgTmplArgs struct { ClientKeyBase64 string } -func GetUserKubeConfig(userName, userID string, superUser bool, log *zap.SugaredLogger) (string, error) { +func GetUserKubeConfig(userName string, log *zap.SugaredLogger) (string, error) { username := strings.ToLower(userName) username = config.NameSpaceRegex.ReplaceAllString(username, "-") var ( - err error - productEnvs = make([]*commonmodels.Product, 0) - productNameMap map[string][]int64 + err error + productEnvs = make([]*commonmodels.Product, 0) ) - if superUser { - productEnvs, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{}) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - // 只管理同集群的资源,且排除状态为Terminating的namespace - productEnvs = filterProductWithoutExternalCluster(productEnvs) - } else { - //项目下所有公开环境 - publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true}) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List List product error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - // 只管理同集群的资源,且排除状态为Terminating的namespace - filterPublicProductEnvs := filterProductWithoutExternalCluster(publicProducts) - namespaceSet := sets.NewString() - for _, publicProduct := range filterPublicProductEnvs { - productEnvs = append(productEnvs, publicProduct) - namespaceSet.Insert(publicProduct.Namespace) - } - poetryClient := poetry.New(config.PoetryAPIServer()) - productNameMap, err = poetryClient.GetUserProject(1, log) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List GetUserProject error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - for productName, roleIDs := range productNameMap { - //用户关联角色所关联的环境 - for _, roleID := range roleIDs { - if roleID == setting.RoleOwnerID { - tmpProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List product error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - for _, product := range tmpProducts { - if !namespaceSet.Has(product.Namespace) { - productEnvs = append(productEnvs, product) - } - } - } else { - roleEnvs, err := poetryClient.ListRoleEnvs(productName, "", roleID, log) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List ListRoleEnvs error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - for _, roleEnv := range roleEnvs { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: roleEnv.EnvName}) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List Find product error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - productEnvs = append(productEnvs, product) - } - } - } - } + productEnvs, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{}) + if err != nil { + log.Errorf("GetUserKubeConfig Collection.Product.List error: %v", err) + return "", e.ErrListProducts.AddDesc(err.Error()) } + + // 只管理同集群的资源,且排除状态为Terminating的namespace + productEnvs = filterProductWithoutExternalCluster(productEnvs) + saNamespace := config.Namespace() if err := ensureServiceAccount(saNamespace, username, log); err != nil { return "", err diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 24b3b28d..bc1457a1 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -483,5 +483,6 @@ const ChartTemplatesPath = "charts" type RoleType string const ( - Contributor RoleType = "Contributor" + Contributor RoleType = "Contributor" + ContributorRoleBindingFmt string = "contributor-project:%s-user:%s" ) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 0a89b0ed..10f01e74 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -46,3 +46,9 @@ func (c *Client) CreateRoleBinding(projectName string, roleBinding *RoleBinding) _, err := c.Post(url, httpclient.SetBody(roleBinding)) return err } + +func (c *Client) DeleteRoleBinding(name string, projectName string) error { + url := fmt.Sprintf("/rolebindings/%s?projectName=%s", name, projectName) + _, err := c.Delete(url) + return err +} -- Gitee From 2dfd8280f5b9c0eab044685ae0a53318180afff7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 19:15:30 +0800 Subject: [PATCH 0116/2647] exemption_urls Signed-off-by: mouuii --- .../core/service/bundle/exemption_urls.go | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 84d419a2..51c57ffb 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -55,4 +55,128 @@ var adminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/aslan/cluster/clusters"}, }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/cluster/clusters/?*"}, + }, + { + Methods: []string{"POST", "PUT"}, + Endpoints: []string{"api/aslan/project/products"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/aslan/project/products/?*"}, + }, + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/aslan/cluster/clusters/?*/disconnect"}, + }, + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/aslan/cluster/clusters/?*/reconnect"}, + }, + { + Methods: []string{"POST", "PUT"}, + Endpoints: []string{"api/aslan/system/install"}, + }, + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/aslan/system/install/delete"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/proxyManage"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/proxyManage/?*"}, + }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/aslan/system/registry/namespaces"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/registry/namespaces/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/s3storage"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/s3storage/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/githubApp"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/aslan/system/githubApp/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/jenkins/integration"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/jenkins/integration/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/jenkins/integration/user/connection"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/basicImages"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/basicImages/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/helm"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/helm/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/privateKey"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/privateKey/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/announcement"}, + }, + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/aslan/system/announcement/update"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/system/announcement/all"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/aslan/system/announcement/?*"}, + }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/aslan/system/operation"}, + }, + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/aslan/system/operation/?*"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/system/proxy/config"}, + }, } -- Gitee From ffd8fea6f007c22274748ff43cbd0eb0657e8858 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 19:19:21 +0800 Subject: [PATCH 0117/2647] build Signed-off-by: mouuii --- .../aslan/core/environment/handler/environment.go | 6 +++--- pkg/microservice/aslan/core/project/service/product.go | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index c146c8a8..49aa2668 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -92,7 +92,7 @@ func AutoUpdateProduct(c *gin.Context) { } force, _ := strconv.ParseBool(c.Query("force")) - ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Param("productName"), ctx.UserID, false, ctx.RequestID, force, ctx.Logger) + ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Param("productName"), ctx.RequestID, force, ctx.Logger) } func CreateHelmProduct(c *gin.Context) { @@ -313,7 +313,7 @@ func UpdateMultiHelmEnv(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) ctx.Resp, ctx.Err = service.UpdateMultipleHelmEnv( - ctx.RequestID, ctx.UserID, false, args, ctx.Logger, + ctx.RequestID, args, ctx.Logger, ) } @@ -338,7 +338,7 @@ func UpdateMultiHelmProduct(c *gin.Context) { return } - ctx.Resp = service.UpdateMultiHelmProduct(args.EnvNames, args.UpdateType, c.Param("productName"), ctx.UserID, false, ctx.RequestID, ctx.Logger) + ctx.Resp = service.UpdateMultiHelmProduct(args.EnvNames, args.UpdateType, c.Param("productName"), ctx.RequestID, ctx.Logger) } func GetProduct(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 232e3283..ee01dfe0 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -40,7 +40,6 @@ import ( workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) -- Gitee From 17993c87200798c7adbd08225d67ff51756a0e78 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 20 Oct 2021 19:43:26 +0800 Subject: [PATCH 0118/2647] fix create env bug Signed-off-by: liu deyi --- .../core/environment/service/environment.go | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index fccb8dc3..3908c6b6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1947,9 +1947,8 @@ func upsertService(isUpdate bool, env *commonmodels.Product, } err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") if err != nil { - // should not have happened - errList = multierror.Append(errList, fmt.Errorf("merge label failed err:%s", err)) - continue + log.Errorf("merge label failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") } podAnnotations, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "annotations") @@ -1958,8 +1957,8 @@ func upsertService(isUpdate bool, env *commonmodels.Product, } err = unstructured.SetNestedStringMap(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") if err != nil { - errList = multierror.Append(errList, fmt.Errorf("merge annotation failed err:%s", err)) - continue + log.Errorf("merge annotation failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") } // Inject selector: s-product and s-service @@ -1967,11 +1966,11 @@ func upsertService(isUpdate bool, env *commonmodels.Product, if err != nil { selector = nil } + err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") if err != nil { - // should not have happened - errList = multierror.Append(errList, fmt.Errorf("merge selector failed err:%s", err)) - continue + log.Errorf("merge selector failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") } jsonData, err := u.MarshalJSON() @@ -3048,3 +3047,20 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } return errList.ErrorOrNil() } + +func setFieldValueIsNotExist(obj map[string]interface{}, value interface{}, fields ...string) map[string]interface{} { + m := obj + for _, field := range fields[:len(fields)-1] { + if val, ok := m[field]; ok { + if valMap, ok := val.(map[string]interface{}); ok { + m = valMap + } else { + newVal := make(map[string]interface{}) + m[field] = newVal + m = newVal + } + } + } + m[fields[len(fields)-1]] = value + return obj +} -- Gitee From aba3d19a5527ab0115a53f5066d74b9a2c9c1f38 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 20 Oct 2021 20:16:14 +0800 Subject: [PATCH 0119/2647] fix service deploy bug Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/service.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 93dcba90..18bc6ddf 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -204,15 +204,14 @@ func (c *ServiceColl) Find(opt *ServiceFindOption) (*models.Service, error) { if opt.ServiceName == "" { return nil, fmt.Errorf("ServiceName is empty") } - if opt.ProductName == "" { - return nil, fmt.Errorf("ProductName is empty") - } query := bson.M{} query["service_name"] = opt.ServiceName - query["product_name"] = opt.ProductName - service := new(models.Service) + if opt.ProductName != "" { + query["product_name"] = opt.ProductName + } + service := new(models.Service) if opt.Type != "" { query["type"] = opt.Type } -- Gitee From 7ab861e5bd42cc0d56305b1969df5054e8d3ddf2 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 20:47:56 +0800 Subject: [PATCH 0120/2647] add policy Signed-off-by: lou --- .../aslan/core/workflow/handler/policy.yaml | 10 ++++++ .../workflow/handler/policy_definitions.go | 24 ++++++++++++++ .../aslan/core/workflow/handler/router.go | 32 ------------------- .../policy/core/service/bundle/opa_policy.go | 2 -- 4 files changed, 34 insertions(+), 34 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/handler/policy.yaml create mode 100644 pkg/microservice/aslan/core/workflow/handler/policy_definitions.go diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml new file mode 100644 index 00000000..6a086c35 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -0,0 +1,10 @@ +resource: Workflow +alias: "工作流" +description: "" +rules: + - action: get_workflow + alias: "查看工作流" + description: "" + rules: + - method: GET + endpoint: "api/aslan/workflow/workflow" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go new file mode 100644 index 00000000..a434ba1d --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go @@ -0,0 +1,24 @@ +package handler + +import ( + _ "embed" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" +) + +//go:embed policy.yaml +var policyDefinitions []byte + +func (*Router) Policies() *policy.Policy { + res := &policy.Policy{} + err := yaml.Unmarshal(policyDefinitions, res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + return res +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 25b9bd66..091767d4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -17,45 +17,13 @@ limitations under the License. package handler import ( - "encoding/json" - "github.com/gin-gonic/gin" gin2 "github.com/koderover/zadig/pkg/middleware/gin" - "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/tool/log" ) type Router struct{} -var policies = ` -{ - "resource": "Workflow", - "alias": "工作流", - "description": "", - "rules": [{ - "action": "get_workflow", - "alias": "查看工作流", - "description": "", - "rules": [{ - "method": "GET", - "endpoint": "api/aslan/workflow/workflow" - }] - }] -} -` - -func (*Router) Policies() *policy.Policy { - res := &policy.Policy{} - err := json.Unmarshal([]byte(policies), res) - if err != nil { - // should not have happened here - log.DPanic(err) - } - - return res -} - func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- // 对外公共接口 diff --git a/pkg/microservice/policy/core/service/bundle/opa_policy.go b/pkg/microservice/policy/core/service/bundle/opa_policy.go index 33af2e18..8b1cd4e3 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_policy.go +++ b/pkg/microservice/policy/core/service/bundle/opa_policy.go @@ -18,8 +18,6 @@ package bundle import ( _ "embed" - - _ "github.com/koderover/zadig/pkg/util/testing" ) //go:embed rego/authz.rego -- Gitee From 844fcb253fc7de27e201eeabfc79f22906bc8640 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Oct 2021 21:13:26 +0800 Subject: [PATCH 0121/2647] api Signed-off-by: mouuii --- .../aslan/core/workflow/handler/policy.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 6a086c35..b7d2aa5f 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -8,3 +8,23 @@ rules: rules: - method: GET endpoint: "api/aslan/workflow/workflow" + - method: GET + endpoint: "/api/aslan/workflow/workflow/find/?*" + - method: GET + endpoint: "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*" + - method: GET + endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" + - method: GET + endpoint: "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*" + - method: GET + endpoint: "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*" + - method: GET + endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*" + - method: GET + endpoint: "/api/aslan/logs/log/workflow/:name/tasks/:taskid/tests/test/service/:serviceName" + - method: GET + endpoint: "/api/aslan/logs/log/workflow/:name/tasks/:taskid/tests/test/service/:testName" + - method: GET + endpoint: "/api/aslan/testing/itreport/workflow/:workflowName/id/:taskid/names/:workflowName-:taskid-test/service/:testName" + - method: GET + endpoint: "/api/aslan/testing/itreport/workflow/:workflowName/id/:taskid/names/:workflowName-:taskid-test/service/:testName" -- Gitee From e3fd7e8a667625285ace1fc878bf8d7b436a02d2 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 21:17:23 +0800 Subject: [PATCH 0122/2647] add public urls Signed-off-by: lou --- .../core/service/bundle/exemption_urls.go | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 51c57ffb..11ee4884 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -45,20 +45,20 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/kodespace/downloadUrl"}, + Endpoints: []string{"api/aslan/testing/report"}, }, -} - -// actions which are allowed for system admins only. -var adminURLs = []*policyRule{ { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/cluster/clusters"}, + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/cluster/agent/?*/agent.yaml"}, }, { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/cluster/clusters/?*"}, + Methods: []string{"GET"}, + Endpoints: []string{"api/podexec/health"}, }, +} + +// actions which are allowed for admins only. +var adminURLs = []*policyRule{ { Methods: []string{"POST", "PUT"}, Endpoints: []string{"api/aslan/project/products"}, @@ -67,6 +67,14 @@ var adminURLs = []*policyRule{ Methods: []string{"DELETE"}, Endpoints: []string{"api/aslan/project/products/?*"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/cluster/clusters"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/cluster/clusters/?*"}, + }, { Methods: []string{"PUT"}, Endpoints: []string{"api/aslan/cluster/clusters/?*/disconnect"}, -- Gitee From afc4ccca4f1217e1220203a3b356708d77467583 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 20 Oct 2021 21:57:50 +0800 Subject: [PATCH 0123/2647] remove poetry calls Signed-off-by: lou --- .../aslan/core/common/service/utils.go | 19 +- .../aslan/core/environment/service/product.go | 17 +- .../aslan/core/system/service/capacity.go | 26 +- pkg/shared/poetry/user.go | 357 ------------------ pkg/types/permission/permission.go | 22 -- 5 files changed, 26 insertions(+), 415 deletions(-) delete mode 100644 pkg/shared/poetry/user.go delete mode 100644 pkg/types/permission/permission.go diff --git a/pkg/microservice/aslan/core/common/service/utils.go b/pkg/microservice/aslan/core/common/service/utils.go index 0c659c8b..010bdc95 100644 --- a/pkg/microservice/aslan/core/common/service/utils.go +++ b/pkg/microservice/aslan/core/common/service/utils.go @@ -28,9 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/notify" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/types/permission" ) func SendMessage(sender, title, content, requestID string, log *zap.SugaredLogger) { @@ -54,11 +52,6 @@ func SendMessage(sender, title, content, requestID string, log *zap.SugaredLogge func SendFailedTaskMessage(username, productName, name, requestID string, workflowType config.PipelineType, err error, log *zap.SugaredLogger) { title := "创建工作流任务失败" - perm := permission.WorkflowUpdateUUID - if workflowType == config.TestType { - title = "创建测试任务失败" - perm = permission.TestManageUUID - } errStr := err.Error() _, messageMap := e.ErrorMessage(err) @@ -75,12 +68,12 @@ func SendFailedTaskMessage(username, productName, name, requestID string, workfl return } - // 如果是timer创建的任务,通知需要发送给该项目下有编辑工作流权限的用户 - poetryClient := poetry.New(config.PoetryAPIServer()) - users, _ := poetryClient.ListProductPermissionUsers(productName, perm, log) - for _, user := range users { - SendMessage(user, title, content, requestID, log) - } + //// 如果是timer创建的任务,通知需要发送给该项目下有编辑工作流权限的用户 + //poetryClient := poetry.New(config.PoetryAPIServer()) + //users, _ := poetryClient.ListProductPermissionUsers(productName, perm, log) + //for _, user := range users { + // SendMessage(user, title, content, requestID, log) + //} } func SendErrorMessage(sender, title, requestID string, err error, log *zap.SugaredLogger) { diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index f4bee00c..c04f3cdf 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -31,7 +31,6 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) @@ -61,24 +60,24 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { } if time.Now().Unix()-product.UpdateTime > int64(60*60*24*product.RecycleDay) { - title := "系统清理产品信息" - content := fmt.Sprintf("环境 [%s] 已经连续%d天没有使用, 系统已自动删除该环境, 如有需要请重新创建。", product.EnvName, product.RecycleDay) + //title := "系统清理产品信息" + //content := fmt.Sprintf("环境 [%s] 已经连续%d天没有使用, 系统已自动删除该环境, 如有需要请重新创建。", product.EnvName, product.RecycleDay) if err := commonservice.DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { log.Errorf("[%s][P:%s] delete product error: %v", product.EnvName, product.ProductName, err) // 如果有错误,重试删除 if err := commonservice.DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { - content = fmt.Sprintf("系统自动清理环境 [%s] 失败,请手动删除环境。", product.ProductName) + //content = fmt.Sprintf("系统自动清理环境 [%s] 失败,请手动删除环境。", product.ProductName) log.Errorf("[%s][P:%s] retry delete product error: %v", product.EnvName, product.ProductName, err) } } - poetryClient := poetry.New(config.PoetryAPIServer()) - users, _ := poetryClient.ListProductPermissionUsers("", "", log) - for _, user := range users { - commonservice.SendMessage(user, title, content, requestID, log) - } + //poetryClient := poetry.New(config.PoetryAPIServer()) + //users, _ := poetryClient.ListProductPermissionUsers("", "", log) + //for _, user := range users { + // commonservice.SendMessage(user, title, content, requestID, log) + //} log.Warnf("[%s] product %s deleted", product.EnvName, product.ProductName) } diff --git a/pkg/microservice/aslan/core/system/service/capacity.go b/pkg/microservice/aslan/core/system/service/capacity.go index 79c1a3dd..37858978 100644 --- a/pkg/microservice/aslan/core/system/service/capacity.go +++ b/pkg/microservice/aslan/core/system/service/capacity.go @@ -25,10 +25,8 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/notify" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" @@ -173,19 +171,19 @@ func sendSyscapNotify(handleErr error, totalCleanTasks *int) { content.Content = fmt.Sprintf("清理时间: %s, 状态: 失败, 内容: %v", now, handleErr) } - notifyInfo := &commonmodels.Notify{ - Type: config.Message, - Content: content, - CreateTime: time.Now().Unix(), - IsRead: false, - } + //notifyInfo := &commonmodels.Notify{ + // Type: config.Message, + // Content: content, + // CreateTime: time.Now().Unix(), + // IsRead: false, + //} - poetryClient := poetry.New(config.PoetryAPIServer()) - users, _ := poetryClient.ListProductPermissionUsers("", "", log.SugaredLogger()) - for _, user := range users { - notifyInfo.Receiver = user - notify.NewNotifyClient().CreateNotify(user, notifyInfo) - } + //poetryClient := poetry.New(config.PoetryAPIServer()) + //users, _ := poetryClient.ListProductPermissionUsers("", "", log.SugaredLogger()) + //for _, user := range users { + // notifyInfo.Receiver = user + // notify.NewNotifyClient().CreateNotify(user, notifyInfo) + //} } func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, dryRun bool) error { diff --git a/pkg/shared/poetry/user.go b/pkg/shared/poetry/user.go deleted file mode 100644 index 6885299f..00000000 --- a/pkg/shared/poetry/user.go +++ /dev/null @@ -1,357 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "fmt" - "strconv" - "strings" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -//roleType -const ( - ProjectType = 2 - AllUsersRoleName = "all-users" -) - -type UserRole struct { - RoleID int64 `json:"roleId"` - UserID int `json:"userId"` - ProductName string `json:"productName"` -} - -type RoleProduct struct { - RoleID int64 `json:"roleId"` - ProductName string `json:"productName"` - Role Role `json:"role"` -} - -type UpdateUserRoleReq struct { - ProductName string `json:"productName"` - RoleID int64 `json:"roleId"` - RoleType int `json:"roleType"` - UserIDList []int `json:"userIds"` -} - -type RolePermission struct { - RoleID int64 `json:"roleId"` - PermissionUUID string `json:"permissionUUID"` -} - -type RoleEnv struct { - RoleID int `json:"roleId"` - EnvName string `json:"envName"` - ProductName string `json:"productName"` -} - -type RolePermissionReq struct { - RoleID int64 `json:"roleId"` - PermissionUUID []string `json:"permissionUUIDs"` - RoleType int `json:"roleType"` - ProductName string `json:"productName"` -} - -type Role struct { - ID int64 `json:"id"` - IsDisabled bool `json:"isDisabled"` - Name string `json:"name"` - ProductName string `json:"productName"` - RoleType int `json:"roleType"` - UpdateBy string `json:"updateBy"` - UpdateAt int64 `json:"updatedAt"` -} - -type UserEnv struct { - ProductName string `json:"productName"` - EnvName string `json:"envName"` - UserID int `json:"userId"` - PermissionUUID string `json:"permissionUUID"` -} - -func (c *Client) ListUserRoles(userID, roleType int, log *zap.SugaredLogger) ([]*UserRole, error) { - url := "/directory/roleUser" - - ur := make([]*UserRole, 0) - _, err := c.Get(url, httpclient.SetResult(&ur), httpclient.SetQueryParam("roleType", strconv.Itoa(roleType)), httpclient.SetQueryParam("userId", strconv.Itoa(userID))) - if err != nil { - log.Errorf("ListUserRoles error: %v", err) - return nil, err - } - - return ur, nil -} - -func (c *Client) ListPermissionUsers(productName string, roleID int64, roleType int, log *zap.SugaredLogger) ([]int, error) { - url := "/directory/roleUser" - qs := map[string]string{ - "productName": productName, - "roleType": strconv.Itoa(roleType), - "roleId": strconv.Itoa(int(roleID)), - } - - ur := make([]*UserRole, 0) - _, err := c.Get(url, httpclient.SetResult(&ur), httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("ListPermissionUsers error: %v", err) - return nil, err - } - - var ret []int - for _, userRole := range ur { - ret = append(ret, userRole.UserID) - } - return ret, nil -} - -func (c *Client) UpdateUserRole(roleID int64, roleType int, productName string, userIDList []int, log *zap.SugaredLogger) error { - url := "/directory/roleUser" - req := &UpdateUserRoleReq{ - ProductName: productName, - RoleID: roleID, - RoleType: roleType, - UserIDList: userIDList, - } - - _, err := c.Post(url, httpclient.SetBody(req)) - if err != nil { - log.Errorf("UpdateUserRole failed, error: %v", err) - return err - } - - return nil -} - -func (c *Client) DeleteUserRole(roleID int64, roleType, userID int, productName string, log *zap.SugaredLogger) error { - url := "/directory/roleUser" - qs := map[string]string{ - "productName": productName, - "roleType": strconv.Itoa(roleType), - "roleId": strconv.Itoa(int(roleID)), - "userId": strconv.Itoa(userID), - } - - _, err := c.Delete(url, httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("DeleteUserRole error: %v", err) - return err - } - return nil -} - -func (c *Client) ListRoleProjects(roleID int64, log *zap.SugaredLogger) ([]*RoleProduct, error) { - url := "/directory/roleProduct" - - rp := make([]*RoleProduct, 0) - _, err := c.Get(url, httpclient.SetResult(&rp), httpclient.SetQueryParam("roleId", strconv.Itoa(int(roleID)))) - if err != nil { - log.Errorf("ListRoleProjects error: %v", err) - return nil, err - } - - return rp, nil -} - -func (c *Client) ContributorRoleExist(productName string, log *zap.SugaredLogger) bool { - url := "/directory/roleProduct" - - rp := make([]*RoleProduct, 0) - _, err := c.Get(url, httpclient.SetResult(&rp), httpclient.SetQueryParam("productName", productName)) - if err != nil { - log.Errorf("Failed to list role project, error: %v", err) - return false - } - - for _, roleProduct := range rp { - if roleProduct.Role.Name == setting.RoleContributor { - return true - } - } - return false -} - -func (c *Client) GetContributorRoleID(productName string, log *zap.SugaredLogger) int64 { - url := "/directory/roleProduct" - - rp := make([]*RoleProduct, 0) - _, err := c.Get(url, httpclient.SetResult(&rp), httpclient.SetQueryParam("productName", productName)) - if err != nil { - log.Errorf("Failed to list role project, sendRequest error: %v", err) - return -1 - } - - for _, roleProduct := range rp { - if roleProduct.Role.Name == setting.RoleContributor { - return roleProduct.RoleID - } - } - return -1 -} - -func (c *Client) ListRolePermissions(roleID int64, roleType int, productName string, log *zap.SugaredLogger) ([]*RolePermission, error) { - url := "/directory/rolePermission" - qs := map[string]string{ - "productName": productName, - "roleType": strconv.Itoa(roleType), - "roleId": strconv.Itoa(int(roleID)), - } - - rp := make([]*RolePermission, 0) - _, err := c.Get(url, httpclient.SetResult(&rp), httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("ListRolePermissions error: %v", err) - return nil, err - } - - return rp, nil -} - -func (c *Client) ListRoleEnvs(productName, envName string, roleID int64, log *zap.SugaredLogger) ([]*RoleEnv, error) { - url := "/directory/roleEnv" - qs := map[string]string{ - "productName": productName, - "envName": envName, - "roleId": strconv.Itoa(int(roleID)), - } - - re := make([]*RoleEnv, 0) - _, err := c.Get(url, httpclient.SetResult(&re), httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("ListRoleEnvs error: %v", err) - return nil, err - } - - return re, nil -} - -func (c *Client) GetUserProject(userID int, log *zap.SugaredLogger) (map[string][]int64, error) { - productNameMap := make(map[string][]int64) - userRoles, err := c.ListUserRoles(userID, ProjectType, log) - if err != nil { - log.Errorf("GetUserProject ListUserRoles error: %v", err) - return productNameMap, fmt.Errorf("GetUserProject ListUserRoles error: %v", err) - } - for _, userRole := range userRoles { - productNameMap[userRole.ProductName] = append(productNameMap[userRole.ProductName], userRole.RoleID) - } - return productNameMap, nil -} - -func (c *Client) GetUserPermissionUUIDs(roleID int64, productName string, log *zap.SugaredLogger) ([]string, error) { - permissionUUIDs := make([]string, 0) - rolePermissions, err := c.ListRolePermissions(roleID, ProjectType, productName, log) - if err != nil { - log.Errorf("GetUserPermission ListRolePermissions error: %v", err) - return []string{}, fmt.Errorf("GetUserPermission ListRolePermissions error: %v", err) - } - for _, rolePermission := range rolePermissions { - permissionUUIDs = append(permissionUUIDs, rolePermission.PermissionUUID) - } - return permissionUUIDs, nil -} - -// ListProductPermissionUsers productName和permissionId为空时只返回所有管理员用户,不为空则返回所有管理员用户和有该权限的普通用户 -func (c *Client) ListProductPermissionUsers(productName, permissionID string, log *zap.SugaredLogger) ([]string, error) { - url := "/directory/productUser" - qs := map[string]string{ - "productName": productName, - "permissionId": permissionID, - } - - resp := make([]string, 0) - _, err := c.Get(url, httpclient.SetResult(&resp), httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("ListProductPermissionUsers error: %v", err) - return nil, err - } - - return resp, nil -} - -// ListRoles 根据项目里面的角色名称获取对应的角色ID -func (c *Client) ListRoles(productName string, log *zap.SugaredLogger) (*Role, error) { - url := "/directory/roles" - qs := map[string]string{ - "name": AllUsersRoleName, - "productName": productName, - "roleType": strconv.Itoa(ProjectType), - } - - resp := make([]*Role, 0) - _, err := c.Get(url, httpclient.SetResult(&resp), httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("ListRoles error: %v", err) - return nil, err - } - - if len(resp) == 0 { - return nil, fmt.Errorf("ListRoles Not Found") - } - - return resp[0], nil -} - -func (c *Client) ListUserEnvPermission(productName string, userID int, log *zap.SugaredLogger) ([]*UserEnv, error) { - url := "/directory/userEnvPermission" - qs := map[string]string{ - "productName": productName, - "userId": strconv.Itoa(userID), - } - - resp := make([]*UserEnv, 0) - _, err := c.Get(url, httpclient.SetResult(&resp), httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("ListUserEnvPermission error: %v", err) - return nil, err - } - - return resp, nil -} - -func (c *Client) DeleteUserEnvPermission(productName, username string, userID int, log *zap.SugaredLogger) error { - url := "/directory/userEnvPermission" - qs := map[string]string{ - "productName": productName, - "envName": strings.ToLower(username), - "userId": strconv.Itoa(userID), - } - - _, err := c.Delete(url, httpclient.SetQueryParams(qs)) - if err != nil { - log.Errorf("DeleteUserEnvPermission error: %v", err) - return err - } - - return nil -} - -func (c *Client) GetUserEnvPermission(userID int, log *zap.SugaredLogger) ([]*UserEnv, error) { - url := "/directory/userEnvPermission" - - resp := make([]*UserEnv, 0) - _, err := c.Get(url, httpclient.SetResult(&resp), httpclient.SetQueryParam("userId", strconv.Itoa(userID))) - if err != nil { - log.Errorf("UserEnvPermission request error: %v", err) - return nil, err - } - - return resp, nil -} diff --git a/pkg/types/permission/permission.go b/pkg/types/permission/permission.go deleted file mode 100644 index b80b724b..00000000 --- a/pkg/types/permission/permission.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package permission - -const ( - WorkflowUpdateUUID = "30002" //产品工作流(更新) - TestManageUUID = "50011" //测试管理(创建测试、修改测试) -) -- Gitee From 456d70ff7d6f3a568ef97646a96bb1677b7697ef Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 21 Oct 2021 11:00:38 +0800 Subject: [PATCH 0124/2647] add more fields Signed-off-by: lou --- .../common/repository/mongodb/template/product.go | 12 +++++++++--- .../aslan/core/project/service/project.go | 14 ++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 295703d0..a4312f18 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -32,9 +32,12 @@ import ( ) type ProjectInfo struct { - Name string `bson:"product_name"` - Alias string `bson:"project_name"` - Desc string `bson:"description"` + Name string `bson:"product_name"` + Alias string `bson:"project_name"` + Desc string `bson:"description"` + UpdatedAt int64 `bson:"update_time"` + UpdatedBy string `bson:"update_by"` + OnboardStatus int `bson:"onboarding_status"` } type ProductColl struct { @@ -98,6 +101,9 @@ func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error {"product_name", 1}, {"project_name", 1}, {"description", 1}, + {"update_time", 1}, + {"update_by", 1}, + {"onboarding_status", 1}, }) } diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index ac5ed373..ea99a0b0 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -42,8 +42,11 @@ type ProjectListOptions struct { type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation - Alias string `json:"alias"` - Desc string `json:"desc"` + Alias string `json:"alias"` + Desc string `json:"desc"` + UpdatedAt int64 `bson:"updatedAt"` + UpdatedBy string `bson:"updatedBy"` + Onboard bool `bson:"onboard"` } type ProjectBriefRepresentation struct { @@ -103,8 +106,11 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, Envs: nameWithEnvMap[name], }, - Alias: info.Alias, - Desc: info.Desc, + Alias: info.Alias, + Desc: info.Desc, + UpdatedAt: info.UpdatedAt, + UpdatedBy: info.UpdatedBy, + Onboard: info.OnboardStatus != 0, }) } -- Gitee From 8c893deeee88e1bdba502e9c9abba940d9a79602 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 21 Oct 2021 11:16:03 +0800 Subject: [PATCH 0125/2647] typo Signed-off-by: lou --- pkg/microservice/aslan/core/project/service/project.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index ea99a0b0..046e45e2 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -44,9 +44,9 @@ type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation Alias string `json:"alias"` Desc string `json:"desc"` - UpdatedAt int64 `bson:"updatedAt"` - UpdatedBy string `bson:"updatedBy"` - Onboard bool `bson:"onboard"` + UpdatedAt int64 `json:"updatedAt"` + UpdatedBy string `json:"updatedBy"` + Onboard bool `json:"onboard"` } type ProjectBriefRepresentation struct { -- Gitee From 4b9be89ca73c76ade2f5952fa11b96ee783efde2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 11:43:02 +0800 Subject: [PATCH 0126/2647] add environment handler Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 88 +++++++++++++++++++ .../environment/handler/policy_definitions.go | 24 +++++ pkg/microservice/aslan/core/service.go | 2 + .../aslan/core/workflow/handler/policy.yaml | 40 +++++++-- 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/handler/policy.yaml create mode 100644 pkg/microservice/aslan/core/environment/handler/policy_definitions.go diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml new file mode 100644 index 00000000..e716e26a --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -0,0 +1,88 @@ +resource: Environment +alias: "集成环境" +description: "" +rules: + - action: get_environment + alias: "查看环境" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/environment/environments" + - method: GET + endpoint: "/api/aslan/environment/environments/?*" + - method: GET + endpoint: "/api/aslan/environment/revision/products" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/groups" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/services/?*" + - method: GET + endpoint: "/api/aslan/environment/kube/workloads" + - method: GET + endpoint: "/api/aslan/service/workloads" + - action: create_environment + alias: "新建环境" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/environment/environments" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/helm" + - method: POST + endpoint: "/api/aslan/service/workloads" + - action: config_environment + alias: "配置环境" + description: "" + rules: + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/autoUpdate" + - method: POST + endpoint: "/api/aslan/environment/environments/?*" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/envRecycle" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/renderchart" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/multiHelmEnv" + - method: PUT + endpoint: "/api/aslan/service/workloads" + - action: update_environment + alias: "更新环境" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/environment/image/deployment" + - method: POST + endpoint: "/api/aslan/environment/image/statefulset" + - method: DELETE + endpoint: "/api/aslan/environment/kube/pods/?*" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" + - method: PUT + endpoint: "/api/aslan/environment/configmaps" + - method: POST + endpoint: "/api/aslan/environment/configmaps" + - method: GET + endpoint: "/api/aslan/environment/export/service" + - method: GET + endpoint: "/api/aslan/environment/kube/pods/?*/events" + - method: GET + endpoint: "/api/aslan/environment/export/service" + - method: GET + endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/services/?*/:?*" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/scale/?*" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew/?*" + - method: PUT + endpoint: " /api/podexec/?*/?*/?*/?*/podExec" + - action: delete_environment + alias: "删除环境" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/environment/environments/?*" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go new file mode 100644 index 00000000..a434ba1d --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go @@ -0,0 +1,24 @@ +package handler + +import ( + _ "embed" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" +) + +//go:embed policy.yaml +var policyDefinitions []byte + +func (*Router) Policies() *policy.Policy { + res := &policy.Policy{} + err := yaml.Unmarshal(policyDefinitions, res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + return res +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 460a1fdb..176bb1d2 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -30,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" + environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" @@ -78,6 +79,7 @@ func registerPolicies() { for _, r := range []policyGetter{ new(workflowhandler.Router), + new(environmenthandler.Router), } { err := policyClient.CreateOrUpdatePolicy(r.Policies()) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index b7d2aa5f..d8158377 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -21,10 +21,40 @@ rules: - method: GET endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*" - method: GET - endpoint: "/api/aslan/logs/log/workflow/:name/tasks/:taskid/tests/test/service/:serviceName" + endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" - method: GET - endpoint: "/api/aslan/logs/log/workflow/:name/tasks/:taskid/tests/test/service/:testName" + endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" - method: GET - endpoint: "/api/aslan/testing/itreport/workflow/:workflowName/id/:taskid/names/:workflowName-:taskid-test/service/:testName" - - method: GET - endpoint: "/api/aslan/testing/itreport/workflow/:workflowName/id/:taskid/names/:workflowName-:taskid-test/service/:testName" + endpoint: "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*" + - action: edit_workflow + alias: "编辑工作流" + description: "" + rules: + - method: PUT + endpoint: "api/aslan/workflow/workflow" + - action: create_workflow + alias: "新建工作流" + description: "" + rules: + - method: POST + endpoint: "api/aslan/workflow/workflow" + - method: PUT + endpoint: "/api/aslan/workflow/workflow/old/?*/new/?*" + - action: delete_workflow + alias: "删除工作流" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/workflow/workflow/?*" + - action: run_workflow + alias: "执行工作流" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/workflow/workflow/?*" + - method: PUT + endpoint: "/api/aslan/workflow/workflowtask" + - method: POST + endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" + - method: DELETE + endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" \ No newline at end of file -- Gitee From 060b94b6e91af1ef72c9be1c1dd1af6ff568f0c0 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 21 Oct 2021 14:24:16 +0800 Subject: [PATCH 0127/2647] fix list envs Signed-off-by: lou --- pkg/microservice/aslan/core/environment/handler/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 49aa2668..187c4e11 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -60,7 +60,7 @@ func ListProducts(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), ctx.UserName, ctx.Logger) + ctx.Resp, ctx.Err = service.ListProducts(c.Query("projectName"), ctx.UserName, ctx.Logger) } func AutoCreateProduct(c *gin.Context) { -- Gitee From cd2fa55a2663bd3943b700db9e702d033e8bd447 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 21 Oct 2021 14:44:48 +0800 Subject: [PATCH 0128/2647] rename project qs Signed-off-by: lou --- .../aslan/core/build/handler/build.go | 10 ++-- .../aslan/core/build/handler/router.go | 4 +- .../aslan/core/build/handler/target.go | 2 +- .../aslan/core/delivery/handler/version.go | 6 +-- .../core/environment/handler/configmap.go | 2 +- .../core/environment/handler/environment.go | 2 +- .../aslan/core/environment/handler/export.go | 2 +- .../aslan/core/environment/handler/kube.go | 8 ++-- .../core/environment/handler/renderset.go | 8 ++-- .../core/environment/handler/revision.go | 2 +- .../aslan/core/environment/handler/router.go | 10 ++-- .../aslan/core/log/handler/log.go | 2 +- .../aslan/core/log/handler/sse.go | 4 +- .../aslan/core/project/handler/product.go | 2 +- .../aslan/core/project/handler/router.go | 6 +-- .../aslan/core/service/handler/helm.go | 2 +- .../aslan/core/service/handler/service.go | 18 +++---- .../aslan/core/system/handler/operation.go | 2 +- .../aslan/core/workflow/handler/build.go | 2 +- .../workflow/handler/favorate_pipeline.go | 2 +- .../workflow/testing/handler/test_detail.go | 2 +- .../core/workflow/testing/handler/testing.go | 8 ++-- pkg/middleware/gin/product_name.go | 47 ------------------- 23 files changed, 53 insertions(+), 100 deletions(-) delete mode 100644 pkg/middleware/gin/product_name.go diff --git a/pkg/microservice/aslan/core/build/handler/build.go b/pkg/microservice/aslan/core/build/handler/build.go index 9ae54606..b335d85f 100644 --- a/pkg/microservice/aslan/core/build/handler/build.go +++ b/pkg/microservice/aslan/core/build/handler/build.go @@ -34,14 +34,14 @@ func FindBuildModule(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = buildservice.FindBuild(c.Param("name"), c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = buildservice.FindBuild(c.Param("name"), c.Query("projectName"), ctx.Logger) } func ListBuildModules(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = buildservice.ListBuild(c.Query("name"), c.Query("targets"), c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = buildservice.ListBuild(c.Query("name"), c.Query("targets"), c.Query("projectName"), ctx.Logger) } func CreateBuildModule(c *gin.Context) { @@ -96,7 +96,7 @@ func DeleteBuildModule(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Query("name") - productName := c.Query("productName") + productName := c.Query("projectName") internalhandler.InsertOperationLog(c, ctx.UserName, productName, "删除", "项目管理-构建", name, "", ctx.Logger) if name == "" { @@ -126,7 +126,7 @@ func UpdateBuildTargets(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "更新", "项目管理-服务组件", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "更新", "项目管理-服务组件", args.Name, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -134,5 +134,5 @@ func UpdateBuildTargets(c *gin.Context) { return } - ctx.Err = buildservice.UpdateBuildTargets(args.Name, c.Query("productName"), args.Targets, ctx.Logger) + ctx.Err = buildservice.UpdateBuildTargets(args.Name, c.Query("projectName"), args.Targets, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/build/handler/router.go b/pkg/microservice/aslan/core/build/handler/router.go index 090d6516..44811e7d 100644 --- a/pkg/microservice/aslan/core/build/handler/router.go +++ b/pkg/microservice/aslan/core/build/handler/router.go @@ -29,8 +29,8 @@ func (*Router) Inject(router *gin.RouterGroup) { { build.GET("/:name", FindBuildModule) build.GET("", ListBuildModules) - build.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateBuildModule) - build.PUT("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateBuildModule) + build.POST("", gin2.UpdateOperationLogStatus, CreateBuildModule) + build.PUT("", gin2.UpdateOperationLogStatus, UpdateBuildModule) build.DELETE("", gin2.UpdateOperationLogStatus, DeleteBuildModule) build.POST("/targets", gin2.UpdateOperationLogStatus, UpdateBuildTargets) } diff --git a/pkg/microservice/aslan/core/build/handler/target.go b/pkg/microservice/aslan/core/build/handler/target.go index 71f5e160..2978c402 100644 --- a/pkg/microservice/aslan/core/build/handler/target.go +++ b/pkg/microservice/aslan/core/build/handler/target.go @@ -27,7 +27,7 @@ func ListDeployTarget(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = buildservice.ListDeployTarget(c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = buildservice.ListDeployTarget(c.Query("projectName"), ctx.Logger) } func ListBuildModulesForProduct(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 6fa62021..920e2e52 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -144,7 +144,7 @@ func ListDeliveryVersion(c *gin.Context) { version := new(commonrepo.DeliveryVersionArgs) version.OrgID = orgID - version.ProductName = c.Query("productName") + version.ProductName = c.Query("projectName") version.WorkflowName = c.Query("workflowName") version.TaskID = taskID version.PerPage = perPage @@ -295,7 +295,7 @@ func ListPackagesVersion(c *gin.Context) { } version := &commonrepo.DeliveryVersionArgs{ OrgID: orgID, - ProductName: c.Query("productName"), + ProductName: c.Query("projectName"), } deliveryVersions, err := deliveryservice.FindDeliveryVersion(version, ctx.Logger) if err != nil { @@ -382,7 +382,7 @@ func ListDeliveryServiceNames(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Query("productName") + productName := c.Query("projectName") orgIDStr := c.Query("orgId") orgID, err := strconv.Atoi(orgIDStr) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/handler/configmap.go b/pkg/microservice/aslan/core/environment/handler/configmap.go index 97699732..bb75e30f 100644 --- a/pkg/microservice/aslan/core/environment/handler/configmap.go +++ b/pkg/microservice/aslan/core/environment/handler/configmap.go @@ -36,7 +36,7 @@ func ListConfigMaps(c *gin.Context) { args := &service.ListConfigMapArgs{ EnvName: c.Query("envName"), - ProductName: c.Query("productName"), + ProductName: c.Query("projectName"), ServiceName: c.Query("serviceName"), } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 187c4e11..69657515 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -399,7 +399,7 @@ func GetEstimatedRenderCharts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Query("productName") + productName := c.Query("projectName") if productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") } diff --git a/pkg/microservice/aslan/core/environment/handler/export.go b/pkg/microservice/aslan/core/environment/handler/export.go index 6f0a9917..7419254f 100644 --- a/pkg/microservice/aslan/core/environment/handler/export.go +++ b/pkg/microservice/aslan/core/environment/handler/export.go @@ -29,7 +29,7 @@ func ExportYaml(c *gin.Context) { serviceName := c.Query("serviceName") envName := c.Query("envName") - productName := c.Query("productName") + productName := c.Query("projectName") ctx.Resp = service.ExportYaml(envName, productName, serviceName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index af5bfcc0..1e4c0c48 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -38,7 +38,7 @@ func ListKubeEvents(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() envName := c.Query("envName") - productName := c.Query("productName") + productName := c.Query("projectName") name := c.Query("name") rtype := c.Query("type") @@ -81,9 +81,9 @@ func DeletePod(c *gin.Context) { podName := c.Param("podName") envName := c.Query("envName") - productName := c.Query("productName") + productName := c.Query("projectName") - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "重启", "集成环境-服务实例", fmt.Sprintf("环境名称:%s,pod名称:%s", c.Query("envName"), c.Param("podName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "集成环境-服务实例", fmt.Sprintf("环境名称:%s,pod名称:%s", c.Query("envName"), c.Param("podName")), "", ctx.Logger) ctx.Err = service.DeletePod(envName, productName, podName, ctx.Logger) } @@ -92,7 +92,7 @@ func ListPodEvents(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() envName := c.Query("envName") - productName := c.Query("productName") + productName := c.Query("projectName") podName := c.Param("podName") ctx.Resp, ctx.Err = service.ListPodEvents(envName, productName, podName, ctx.Logger) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index b7b6e6e8..851f0332 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -32,7 +32,7 @@ func GetServiceRenderCharts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - if c.Query("productName") == "" { + if c.Query("projectName") == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") return } @@ -42,14 +42,14 @@ func GetServiceRenderCharts(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GetRenderCharts(c.Query("productName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetRenderCharts(c.Query("projectName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) } func CreateOrUpdateRenderChart(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - if c.Query("productName") == "" { + if c.Query("projectName") == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") return } @@ -71,5 +71,5 @@ func CreateOrUpdateRenderChart(c *gin.Context) { } internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) - ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.UserName, ctx.RequestID, ctx.Logger) + ctx.Err = service.CreateOrUpdateChartValues(c.Query("projectName"), c.Query("envName"), args, ctx.UserName, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/revision.go b/pkg/microservice/aslan/core/environment/handler/revision.go index e27eb50b..fa35d689 100644 --- a/pkg/microservice/aslan/core/environment/handler/revision.go +++ b/pkg/microservice/aslan/core/environment/handler/revision.go @@ -32,5 +32,5 @@ func ListProductsRevision(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProductsRevisionByFacility(c.Query("basicFacility"), ctx.Logger) return } - ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("productName"), c.Query("envName"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("projectName"), c.Query("envName"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index d68c536a..5005916b 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -31,8 +31,8 @@ func (*Router) Inject(router *gin.RouterGroup) { configmaps := router.Group("configmaps") { configmaps.GET("", ListConfigMaps) - configmaps.PUT("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateConfigMap) - configmaps.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, RollBackConfigMap) + configmaps.PUT("", gin2.UpdateOperationLogStatus, UpdateConfigMap) + configmaps.POST("", gin2.UpdateOperationLogStatus, RollBackConfigMap) } // --------------------------------------------------------------------------------------- @@ -96,10 +96,10 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("", ListProducts) environments.POST("/:productName/auto", AutoCreateProduct) environments.PUT("/:productName/autoUpdate", gin2.UpdateOperationLogStatus, AutoUpdateProduct) - environments.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateProduct) + environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) - environments.POST("/:productName/helm", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateHelmProduct) - environments.PUT("/:productName/multiHelmEnv", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) + environments.POST("/:productName/helm", gin2.UpdateOperationLogStatus, CreateHelmProduct) + environments.PUT("/:productName/multiHelmEnv", gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) environments.POST("/:productName", gin2.UpdateOperationLogStatus, UpdateProduct) environments.PUT("/:productName/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 6627420d..648d8588 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -94,7 +94,7 @@ func GetContainerLogs(c *gin.Context) { podName := c.Param("name") containerName := c.Query("container") envName := c.Query("envName") - productName := c.Query("productName") + productName := c.Query("projectName") tailLines, err := strconv.ParseInt(c.Query("tailLines"), 10, 64) if err != nil { diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index 7293d436..2ac7c832 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -39,7 +39,7 @@ func GetContainerLogsSSE(c *gin.Context) { } envName := c.Query("envName") - productName := c.Query("productName") + productName := c.Query("projectName") internalhandler.Stream(c, func(ctx context.Context, streamChan chan interface{}) { logservice.ContainerLogStream(ctx, streamChan, envName, productName, c.Param("podName"), c.Param("containerName"), true, tails, logger) @@ -102,7 +102,7 @@ func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { ServiceName: c.Param("serviceName"), PipelineType: string(config.WorkflowType), EnvName: c.Query("envName"), - ProductName: c.Query("productName"), + ProductName: c.Query("projectName"), } internalhandler.Stream(c, func(ctx1 context.Context, streamChan chan interface{}) { diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 1f78b1b4..dee7e863 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -125,7 +125,7 @@ func UpdateProject(c *gin.Context) { return } args.UpdateBy = ctx.UserName - productName := c.Query("productName") + productName := c.Query("projectName") if productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty") return diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index bc9bdc53..d0131340 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -40,11 +40,11 @@ func (*Router) Inject(router *gin.RouterGroup) { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) product.GET("/:name/searching-rules", GetCustomMatchRules) - product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) - product.POST("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, CreateProductTemplate) + product.PUT("/:name/searching-rules", gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) + product.POST("", gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) - product.PUT("", gin2.StoreProductName, gin2.UpdateOperationLogStatus, UpdateProject) + product.PUT("", gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProductTemplate) product.PATCH("/:name", UpdateServiceOrchestration) } diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index c57b789b..0ce5f006 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -66,7 +66,7 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy = ctx.UserName - ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("productName"), args, ctx.Logger) + ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("projectName"), args, ctx.Logger) } func UpdateHelmService(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index cfbd6680..cf747551 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -39,13 +39,13 @@ func ListServiceTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = commonservice.ListServiceTemplate(c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = commonservice.ListServiceTemplate(c.Query("projectName"), ctx.Logger) } func ListWorkloadTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = commonservice.ListWorkloadTemplate(c.Query("productName"), c.Query("env"), ctx.Logger) + ctx.Resp, ctx.Err = commonservice.ListWorkloadTemplate(c.Query("projectName"), c.Query("env"), ctx.Logger) } func GetServiceTemplate(c *gin.Context) { @@ -56,7 +56,7 @@ func GetServiceTemplate(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") return } - ctx.Resp, ctx.Err = commonservice.GetServiceTemplate(c.Param("name"), c.Param("type"), c.Query("productName"), setting.ProductStatusDeleting, revision, ctx.Logger) + ctx.Resp, ctx.Err = commonservice.GetServiceTemplate(c.Param("name"), c.Param("type"), c.Query("projectName"), setting.ProductStatusDeleting, revision, ctx.Logger) } func GetServiceTemplateOption(c *gin.Context) { @@ -67,7 +67,7 @@ func GetServiceTemplateOption(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") return } - ctx.Resp, ctx.Err = svcservice.GetServiceTemplateOption(c.Param("name"), c.Query("productName"), revision, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.GetServiceTemplateOption(c.Param("name"), c.Query("projectName"), revision, ctx.Logger) } func CreateServiceTemplate(c *gin.Context) { @@ -139,9 +139,9 @@ func DeleteServiceTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "删除", "项目管理-服务", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "删除", "项目管理-服务", c.Param("name"), "", ctx.Logger) - ctx.Err = svcservice.DeleteServiceTemplate(c.Param("name"), c.Param("type"), c.Query("productName"), c.DefaultQuery("isEnvTemplate", "true"), c.DefaultQuery("visibility", "public"), ctx.Logger) + ctx.Err = svcservice.DeleteServiceTemplate(c.Param("name"), c.Param("type"), c.Query("projectName"), c.DefaultQuery("isEnvTemplate", "true"), c.DefaultQuery("visibility", "public"), ctx.Logger) } func ListServicePort(c *gin.Context) { @@ -152,7 +152,7 @@ func ListServicePort(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") return } - ctx.Resp, ctx.Err = svcservice.ListServicePort(c.Param("name"), c.Param("type"), c.Query("productName"), setting.ProductStatusDeleting, revision, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.ListServicePort(c.Param("name"), c.Param("type"), c.Query("projectName"), setting.ProductStatusDeleting, revision, ctx.Logger) } type K8sWorkloadsArgs struct { @@ -172,7 +172,7 @@ func UpdateWorkloads(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid UpdateWorkloadsArgs") return } - product := c.Query("productName") + product := c.Query("projectName") env := c.Query("env") if product == "" || env == "" { ctx.Err = e.ErrInvalidParam @@ -197,7 +197,7 @@ func ListAvailablePublicServices(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = svcservice.ListAvailablePublicServices(c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = svcservice.ListAvailablePublicServices(c.Query("projectName"), ctx.Logger) } func GetServiceTemplateProductName(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/operation.go b/pkg/microservice/aslan/core/system/handler/operation.go index d4d97b77..10547c2d 100644 --- a/pkg/microservice/aslan/core/system/handler/operation.go +++ b/pkg/microservice/aslan/core/system/handler/operation.go @@ -51,7 +51,7 @@ func GetOperationLogs(c *gin.Context) { args := &service.OperationLogArgs{ Username: c.Query("username"), - ProductName: c.Query("product_name"), + ProductName: c.Query("projectName"), Function: c.Query("function"), Status: status, PerPage: perPage, diff --git a/pkg/microservice/aslan/core/workflow/handler/build.go b/pkg/microservice/aslan/core/workflow/handler/build.go index c3c2b7f6..2fed933d 100644 --- a/pkg/microservice/aslan/core/workflow/handler/build.go +++ b/pkg/microservice/aslan/core/workflow/handler/build.go @@ -27,7 +27,7 @@ func BuildModuleToSubTasks(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - resp, err := workflow.BuildModuleToSubTasks(c.Param("name"), "", "", c.Query("productName"), nil, nil, ctx.Logger) + resp, err := workflow.BuildModuleToSubTasks(c.Param("name"), "", "", c.Query("projectName"), nil, nil, ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go b/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go index f4d5a4c2..0574ffd5 100644 --- a/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go +++ b/pkg/microservice/aslan/core/workflow/handler/favorate_pipeline.go @@ -35,7 +35,7 @@ func ListFavoritePipelines(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Query("productName") + productName := c.Query("projectName") workflowType := c.Query("type") if workflowType == "" { ctx.Err = e.ErrInvalidParam.AddDesc("type can't be empty!") diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/test_detail.go b/pkg/microservice/aslan/core/workflow/testing/handler/test_detail.go index 02295c3e..492db2a9 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/test_detail.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/test_detail.go @@ -27,5 +27,5 @@ func ListDetailTestModules(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListTestingDetails(c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListTestingDetails(c.Query("projectName"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/testing.go b/pkg/microservice/aslan/core/workflow/testing/handler/testing.go index 7b70976d..33837f80 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/testing.go @@ -99,7 +99,7 @@ func ListTestModules(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListTestingOpt(c.Query("productName"), c.Query("testType"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListTestingOpt(c.Query("projectName"), c.Query("testType"), ctx.Logger) } func GetTestModule(c *gin.Context) { @@ -112,14 +112,14 @@ func GetTestModule(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("empty Name") return } - ctx.Resp, ctx.Err = service.GetTesting(name, c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetTesting(name, c.Query("projectName"), ctx.Logger) } func DeleteTestModule(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("productName"), "删除", "项目管理-测试", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "删除", "项目管理-测试", c.Param("name"), "", ctx.Logger) name := c.Param("name") if name == "" { @@ -127,7 +127,7 @@ func DeleteTestModule(c *gin.Context) { return } - ctx.Err = service.DeleteTestModule(name, c.Query("productName"), ctx.RequestID, ctx.Logger) + ctx.Err = service.DeleteTestModule(name, c.Query("projectName"), ctx.RequestID, ctx.Logger) } func GetHTMLTestReport(c *gin.Context) { diff --git a/pkg/middleware/gin/product_name.go b/pkg/middleware/gin/product_name.go deleted file mode 100644 index d66bff40..00000000 --- a/pkg/middleware/gin/product_name.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gin - -import ( - "bytes" - "encoding/json" - "io/ioutil" - - "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/tool/log" -) - -type productName struct { - ProductName string `json:"product_name"` -} - -func StoreProductName(c *gin.Context) { - pn := &productName{} - data, err := c.GetRawData() - if err != nil { - log.Errorf("c.GetRawData() err : %v", err) - return - } - if err = json.Unmarshal(data, pn); err != nil { - log.Errorf("json.Unmarshal err : %v", err) - return - } - c.Set("productName", pn.ProductName) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - c.Next() -} -- Gitee From c2f6408a8a4e470805cd3f7e8982f23a6f2a6e41 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 14:52:01 +0800 Subject: [PATCH 0129/2647] add service handler Signed-off-by: mouuii --- pkg/microservice/aslan/core/service.go | 2 + .../aslan/core/service/handler/policy.yaml | 92 +++++++++++++++++++ .../service/handler/policy_definitions.go | 24 +++++ 3 files changed, 118 insertions(+) create mode 100644 pkg/microservice/aslan/core/service/handler/policy.yaml create mode 100644 pkg/microservice/aslan/core/service/handler/policy_definitions.go diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 176bb1d2..d42799ce 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -32,6 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + servicehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/service/handler" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" @@ -80,6 +81,7 @@ func registerPolicies() { for _, r := range []policyGetter{ new(workflowhandler.Router), new(environmenthandler.Router), + new(servicehandler.Router), } { err := policyClient.CreateOrUpdatePolicy(r.Policies()) if err != nil { diff --git a/pkg/microservice/aslan/core/service/handler/policy.yaml b/pkg/microservice/aslan/core/service/handler/policy.yaml new file mode 100644 index 00000000..31ce7a5f --- /dev/null +++ b/pkg/microservice/aslan/core/service/handler/policy.yaml @@ -0,0 +1,92 @@ +resource: Service +alias: "服务" +description: "" +rules: + - action: get_service + alias: "查看服务" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/service/services" + - method: GET + endpoint: "/api/aslan/service/services/?*/k8s" + - method: GET + endpoint: "/api/aslan/project/products/?*/services" + - method: GET + endpoint: "/api/aslan/service/services/?*" + - method: GET + endpoint: "/api/aslan/service/name" + - method: GET + endpoint: "/api/aslan/project/products/?*/searching-rules" + - method: GET + endpoint: "/api/aslan/service/helm/?*/?*/filePath" + - method: GET + endpoint: "/api/aslan/service/helm/?*/?*/fileContent" + - method: GET + endpoint: "/api/aslan/service/helm/?*/?*/serviceModule" + - action: edit_service + alias: "编辑服务" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/service/helm/services" + - method: POST + endpoint: "/api/aslan/service/helm/?*" + - method: PUT + endpoint: "/api/aslan/service/services" + - method: PUT + endpoint: " /api/aslan/service/services/yaml/validator" + - method: PUT + endpoint: "/api/aslan/service/pm/?*" + - method: PUT + endpoint: "/api/aslan/project/products/?*" + - method: PATCH + endpoint: "/api/aslan/project/products/?*" + - method: PUT + endpoint: "/api/aslan/project/products/?*/searching-rules" + - action: create_service + alias: "新建服务" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/service/pm/?*" + - method: POST + endpoint: "/api/aslan/service/services" + - action: delete_service + alias: "删除服务" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/service/services/?*/?*" + - method: DELETE + endpoint: "/api/aslan/service/services/?*/pm" + - action: get_build + alias: "查看构建配置" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/build/build" + - method: GET + endpoint: "/api/aslan/build/build/?*" + - method: GET + endpoint: "/api/aslan/build/targets" + - action: create_build + alias: "创建构建配置" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/build/build" + - action: edit_build + alias: "编辑构建配置" + description: "" + rules: + - method: PUT + endpoint: "/api/aslan/build/build" + - method: POST + endpoint: "/api/aslan/build/targets" + - action: delete_build + alias: "删除构建配置" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/build/build" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/service/handler/policy_definitions.go b/pkg/microservice/aslan/core/service/handler/policy_definitions.go new file mode 100644 index 00000000..a434ba1d --- /dev/null +++ b/pkg/microservice/aslan/core/service/handler/policy_definitions.go @@ -0,0 +1,24 @@ +package handler + +import ( + _ "embed" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" +) + +//go:embed policy.yaml +var policyDefinitions []byte + +func (*Router) Policies() *policy.Policy { + res := &policy.Policy{} + err := yaml.Unmarshal(policyDefinitions, res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + return res +} -- Gitee From 69a4e3833ff9bfbde520efeac9e2de2cc4ab4250 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 15:06:00 +0800 Subject: [PATCH 0130/2647] add delivery handler Signed-off-by: mouuii --- .../aslan/core/delivery/handler/policy.yaml | 16 +++++++++ .../delivery/handler/policy_definitions.go | 24 +++++++++++++ pkg/microservice/aslan/core/service.go | 4 +++ .../core/workflow/testing/handler/policy.yaml | 34 +++++++++++++++++++ .../testing/handler/policy_definitions.go | 24 +++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 pkg/microservice/aslan/core/delivery/handler/policy.yaml create mode 100644 pkg/microservice/aslan/core/delivery/handler/policy_definitions.go create mode 100644 pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml create mode 100644 pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml new file mode 100644 index 00000000..b6d2a5b6 --- /dev/null +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -0,0 +1,16 @@ +resource: Delivery +alias: "交付" +description: "" +rules: + - action: get_delivery + alias: "查看版本" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/delivery/releases" + - action: delete_delivery + alias: "删除版本" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/delivery/releases/?*" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go new file mode 100644 index 00000000..a434ba1d --- /dev/null +++ b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go @@ -0,0 +1,24 @@ +package handler + +import ( + _ "embed" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" +) + +//go:embed policy.yaml +var policyDefinitions []byte + +func (*Router) Policies() *policy.Policy { + res := &policy.Policy{} + err := yaml.Unmarshal(policyDefinitions, res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + return res +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index d42799ce..a99f21bc 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -30,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" + deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" servicehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/service/handler" @@ -37,6 +38,7 @@ import ( systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" @@ -82,6 +84,8 @@ func registerPolicies() { new(workflowhandler.Router), new(environmenthandler.Router), new(servicehandler.Router), + new(testinghandler.Router), + new(deliveryhandler.Router), } { err := policyClient.CreateOrUpdatePolicy(r.Policies()) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml new file mode 100644 index 00000000..b7479262 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -0,0 +1,34 @@ +resource: Test +alias: "测试" +description: "" +rules: + - action: get_test + alias: "查看测试" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/testing/test" + - method: GET + endpoint: "/api/aslan/testing/test/?*" + - method: GET + endpoint: "/api/aslan/testing/testdetail" + - method: GET + endpoint: "/api/aslan/workflow/workflow/testName/?*" + - action: edit_test + alias: "编辑测试" + description: "" + rules: + - method: PUT + endpoint: "/api/aslan/testing/test" + - action: delete_test + alias: "删除测试" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/testing/test/?*" + - action: create_test + alias: "新建测试" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/testing/test" diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go new file mode 100644 index 00000000..a434ba1d --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go @@ -0,0 +1,24 @@ +package handler + +import ( + _ "embed" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" +) + +//go:embed policy.yaml +var policyDefinitions []byte + +func (*Router) Policies() *policy.Policy { + res := &policy.Policy{} + err := yaml.Unmarshal(policyDefinitions, res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + return res +} -- Gitee From 72f0e7ee8beadbd93431de4892a1e0f85545bbc5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 21 Oct 2021 15:29:09 +0800 Subject: [PATCH 0131/2647] fix build bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/pm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index a41b7146..0365592f 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -90,7 +90,7 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug } // Confirm whether the build exists - build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: args.Build.Name}) + build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: args.Build.Name, ProductName: args.Build.ProductName}) if err != nil { if err := commonservice.CreateBuild(username, args.Build, log); err != nil { log.Errorf("pmService.Create build %s error: %v", args.Build.Name, err) -- Gitee From 89757967e7efe3c1e6bcf25da448aa076f1606a7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 15:55:15 +0800 Subject: [PATCH 0132/2647] rename Signed-off-by: mouuii --- .../aslan/core/delivery/handler/policy.yaml | 2 +- .../aslan/core/environment/handler/policy.yaml | 10 +++++----- .../aslan/core/service/handler/policy.yaml | 4 ++-- .../aslan/core/workflow/handler/policy.yaml | 2 ++ .../aslan/core/workflow/testing/handler/policy.yaml | 2 +- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml index b6d2a5b6..38084a4f 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -1,5 +1,5 @@ resource: Delivery -alias: "交付" +alias: "交付中心" description: "" rules: - action: get_delivery diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index e716e26a..4247adc8 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -3,7 +3,7 @@ alias: "集成环境" description: "" rules: - action: get_environment - alias: "查看环境" + alias: "查看集成环境" description: "" rules: - method: GET @@ -21,7 +21,7 @@ rules: - method: GET endpoint: "/api/aslan/service/workloads" - action: create_environment - alias: "新建环境" + alias: "新建集成环境" description: "" rules: - method: POST @@ -31,7 +31,7 @@ rules: - method: POST endpoint: "/api/aslan/service/workloads" - action: config_environment - alias: "配置环境" + alias: "配置集成环境" description: "" rules: - method: PUT @@ -47,7 +47,7 @@ rules: - method: PUT endpoint: "/api/aslan/service/workloads" - action: update_environment - alias: "更新环境" + alias: "管理服务实例" description: "" rules: - method: POST @@ -81,7 +81,7 @@ rules: - method: PUT endpoint: " /api/podexec/?*/?*/?*/?*/podExec" - action: delete_environment - alias: "删除环境" + alias: "删除集成环境" description: "" rules: - method: DELETE diff --git a/pkg/microservice/aslan/core/service/handler/policy.yaml b/pkg/microservice/aslan/core/service/handler/policy.yaml index 31ce7a5f..8e4a722a 100644 --- a/pkg/microservice/aslan/core/service/handler/policy.yaml +++ b/pkg/microservice/aslan/core/service/handler/policy.yaml @@ -1,5 +1,5 @@ resource: Service -alias: "服务" +alias: "项目配置" description: "" rules: - action: get_service @@ -71,7 +71,7 @@ rules: - method: GET endpoint: "/api/aslan/build/targets" - action: create_build - alias: "创建构建配置" + alias: "新建构建配置" description: "" rules: - method: POST diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index d8158377..19a4de91 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -20,6 +20,8 @@ rules: endpoint: "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*" - method: GET endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*" + - method: GET + endpoint: "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*" - method: GET endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" - method: GET diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml index b7479262..320fcded 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -1,5 +1,5 @@ resource: Test -alias: "测试" +alias: "质量管理" description: "" rules: - action: get_test -- Gitee From 8a6ba7fb5b2253a786d22e79d9f1053312707046 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 15:59:01 +0800 Subject: [PATCH 0133/2647] rename Signed-off-by: mouuii --- .../aslan/core/{service => project}/handler/policy.yaml | 0 .../core/{service => project}/handler/policy_definitions.go | 0 pkg/microservice/aslan/core/service.go | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename pkg/microservice/aslan/core/{service => project}/handler/policy.yaml (100%) rename pkg/microservice/aslan/core/{service => project}/handler/policy_definitions.go (100%) diff --git a/pkg/microservice/aslan/core/service/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml similarity index 100% rename from pkg/microservice/aslan/core/service/handler/policy.yaml rename to pkg/microservice/aslan/core/project/handler/policy.yaml diff --git a/pkg/microservice/aslan/core/service/handler/policy_definitions.go b/pkg/microservice/aslan/core/project/handler/policy_definitions.go similarity index 100% rename from pkg/microservice/aslan/core/service/handler/policy_definitions.go rename to pkg/microservice/aslan/core/project/handler/policy_definitions.go diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index a99f21bc..5ef918f4 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -33,7 +33,7 @@ import ( deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" - servicehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/service/handler" + projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" @@ -83,7 +83,7 @@ func registerPolicies() { for _, r := range []policyGetter{ new(workflowhandler.Router), new(environmenthandler.Router), - new(servicehandler.Router), + new(projecthandler.Router), new(testinghandler.Router), new(deliveryhandler.Router), } { -- Gitee From 8320d9c2b35ca0a6c982f4561ef2adf670427488 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 21 Oct 2021 16:06:44 +0800 Subject: [PATCH 0134/2647] fix tar file rule bug Signed-off-by: liu deyi --- .../core/workflow/service/workflow/pipeline_validation.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index f233e98f..797cbd47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -533,7 +533,11 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, deliver } switch deliveryType { case config.TarResourceType: - return replaceVariable(customTarRule, candidate) + newTarRule := replaceVariable(customTarRule, candidate) + if strings.Contains(newTarRule, ":") { + return strings.Replace(newTarRule, ":", "-", -1) + } + return newTarRule default: return replaceVariable(customImageRule, candidate) } -- Gitee From 20eb4084490795405b3155407522d6616b68bad1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 16:09:12 +0800 Subject: [PATCH 0135/2647] rename Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 4247adc8..b86fe1dd 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -71,7 +71,7 @@ rules: - method: GET endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" - method: PUT - endpoint: "/api/aslan/environment/environments/?*/services/?*/:?*" + endpoint: "/api/aslan/environment/environments/?*/services/?*/?*" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" - method: POST diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 19a4de91..6a488c63 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -53,7 +53,7 @@ rules: description: "" rules: - method: POST - endpoint: "/api/aslan/workflow/workflow/?*" + endpoint: "/api/aslan/workflow/workflowtask" - method: PUT endpoint: "/api/aslan/workflow/workflowtask" - method: POST -- Gitee From 10a087d63426c94afd01e2e750a2ca56a3863d3a Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 21 Oct 2021 16:10:26 +0800 Subject: [PATCH 0136/2647] rename Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index b86fe1dd..1fe633c7 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -46,7 +46,7 @@ rules: endpoint: "/api/aslan/environment/environments/?*/multiHelmEnv" - method: PUT endpoint: "/api/aslan/service/workloads" - - action: update_environment + - action: manage_environment alias: "管理服务实例" description: "" rules: -- Gitee From e00fca8673710faae6184dec5e40d6485bb1545d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 21 Oct 2021 22:09:15 +0800 Subject: [PATCH 0137/2647] modify reaper Signed-off-by: liu deyi --- microservice-demo-k8s-env-dev.yaml | 66 ++++++++++++++++++++ pkg/microservice/reaper/executor/executor.go | 8 ++- 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 microservice-demo-k8s-env-dev.yaml diff --git a/microservice-demo-k8s-env-dev.yaml b/microservice-demo-k8s-env-dev.yaml new file mode 100644 index 00000000..531cf13a --- /dev/null +++ b/microservice-demo-k8s-env-dev.yaml @@ -0,0 +1,66 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + kubectl.kubernetes.io/last-applied-configuration: | + {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"koderover-nginx","nginx.ingress.kubernetes.io/proxy-body-size":"100m","nginx.ingress.kubernetes.io/proxy-connect-timeout":"300","nginx.ingress.kubernetes.io/proxy-read-timeout":"300","nginx.ingress.kubernetes.io/proxy-send-timeout":"300"},"labels":{"app.kubernetes.io/instance":"frontend","app.kubernetes.io/name":"demo","s-product":"microservice-demo-k8s","s-service":"frontend"},"name":"frontend","namespace":"microservice-demo-k8s-env-dev"},"spec":{"rules":[{"host":"dev-microservice.test.8slan.com","http":{"paths":[{"backend":{"serviceName":"frontend","servicePort":80},"path":"/"}]}}]}} + kubernetes.io/ingress.class: koderover-nginx + nginx.ingress.kubernetes.io/proxy-body-size: 100m + nginx.ingress.kubernetes.io/proxy-connect-timeout: "300" + nginx.ingress.kubernetes.io/proxy-read-timeout: "300" + nginx.ingress.kubernetes.io/proxy-send-timeout: "300" + creationTimestamp: "2021-10-21T11:53:19Z" + generation: 2 + labels: + app.kubernetes.io/instance: frontend + app.kubernetes.io/name: demo + managedFields: + - apiVersion: extensions/v1beta1 + fieldsType: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + .: {} + f:kubectl.kubernetes.io/last-applied-configuration: {} + f:kubernetes.io/ingress.class: {} + f:nginx.ingress.kubernetes.io/proxy-body-size: {} + f:nginx.ingress.kubernetes.io/proxy-connect-timeout: {} + f:nginx.ingress.kubernetes.io/proxy-read-timeout: {} + f:nginx.ingress.kubernetes.io/proxy-send-timeout: {} + f:labels: + .: {} + f:app.kubernetes.io/instance: {} + f:app.kubernetes.io/name: {} + f:s-product: {} + f:s-service: {} + f:spec: + f:rules: {} + manager: aslan + operation: Update + time: "2021-10-21T11:53:19Z" + - apiVersion: networking.k8s.io/v1beta1 + fieldsType: FieldsV1 + fieldsV1: + f:status: + f:loadBalancer: + f:ingress: {} + manager: nginx-ingress-controller + operation: Update + time: "2021-10-21T11:53:47Z" + name: frontend + namespace: mouuii-external + resourceVersion: "1726174826" + selfLink: /apis/networking.k8s.io/v1/namespaces/microservice-demo-k8s-env-dev/ingresses/frontend + uid: 3ae8383a-0568-4abd-b4b7-3fe74910cb7a +spec: + rules: + - host: microservice-demo.test.8slan.com + http: + paths: + - backend: + service: + name: frontend + port: + number: 80 + path: / + pathType: ImplementationSpecific diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 15af0387..76ca7a0c 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -54,9 +54,13 @@ func Execute() error { if err = r.BeforeExec(); err != nil { log.Fatal(err) } - if err = r.Exec(); err != nil { - errs = multierror.Append(errs, err) + + if r.Ctx.ArtifactInfo == nil { + if err = r.Exec(); err != nil { + errs = multierror.Append(errs, err) + } } + if err = r.AfterExec(err); err != nil { errs = multierror.Append(errs, err) } -- Gitee From 036e594574f0a135c341081875939246c679265e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 21 Oct 2021 22:09:27 +0800 Subject: [PATCH 0138/2647] modify reaper Signed-off-by: liu deyi --- microservice-demo-k8s-env-dev.yaml | 66 ------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 microservice-demo-k8s-env-dev.yaml diff --git a/microservice-demo-k8s-env-dev.yaml b/microservice-demo-k8s-env-dev.yaml deleted file mode 100644 index 531cf13a..00000000 --- a/microservice-demo-k8s-env-dev.yaml +++ /dev/null @@ -1,66 +0,0 @@ -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - annotations: - kubectl.kubernetes.io/last-applied-configuration: | - {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"koderover-nginx","nginx.ingress.kubernetes.io/proxy-body-size":"100m","nginx.ingress.kubernetes.io/proxy-connect-timeout":"300","nginx.ingress.kubernetes.io/proxy-read-timeout":"300","nginx.ingress.kubernetes.io/proxy-send-timeout":"300"},"labels":{"app.kubernetes.io/instance":"frontend","app.kubernetes.io/name":"demo","s-product":"microservice-demo-k8s","s-service":"frontend"},"name":"frontend","namespace":"microservice-demo-k8s-env-dev"},"spec":{"rules":[{"host":"dev-microservice.test.8slan.com","http":{"paths":[{"backend":{"serviceName":"frontend","servicePort":80},"path":"/"}]}}]}} - kubernetes.io/ingress.class: koderover-nginx - nginx.ingress.kubernetes.io/proxy-body-size: 100m - nginx.ingress.kubernetes.io/proxy-connect-timeout: "300" - nginx.ingress.kubernetes.io/proxy-read-timeout: "300" - nginx.ingress.kubernetes.io/proxy-send-timeout: "300" - creationTimestamp: "2021-10-21T11:53:19Z" - generation: 2 - labels: - app.kubernetes.io/instance: frontend - app.kubernetes.io/name: demo - managedFields: - - apiVersion: extensions/v1beta1 - fieldsType: FieldsV1 - fieldsV1: - f:metadata: - f:annotations: - .: {} - f:kubectl.kubernetes.io/last-applied-configuration: {} - f:kubernetes.io/ingress.class: {} - f:nginx.ingress.kubernetes.io/proxy-body-size: {} - f:nginx.ingress.kubernetes.io/proxy-connect-timeout: {} - f:nginx.ingress.kubernetes.io/proxy-read-timeout: {} - f:nginx.ingress.kubernetes.io/proxy-send-timeout: {} - f:labels: - .: {} - f:app.kubernetes.io/instance: {} - f:app.kubernetes.io/name: {} - f:s-product: {} - f:s-service: {} - f:spec: - f:rules: {} - manager: aslan - operation: Update - time: "2021-10-21T11:53:19Z" - - apiVersion: networking.k8s.io/v1beta1 - fieldsType: FieldsV1 - fieldsV1: - f:status: - f:loadBalancer: - f:ingress: {} - manager: nginx-ingress-controller - operation: Update - time: "2021-10-21T11:53:47Z" - name: frontend - namespace: mouuii-external - resourceVersion: "1726174826" - selfLink: /apis/networking.k8s.io/v1/namespaces/microservice-demo-k8s-env-dev/ingresses/frontend - uid: 3ae8383a-0568-4abd-b4b7-3fe74910cb7a -spec: - rules: - - host: microservice-demo.test.8slan.com - http: - paths: - - backend: - service: - name: frontend - port: - number: 80 - path: / - pathType: ImplementationSpecific -- Gitee From ee5189345282e02250a743798f2014393441d9b0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 21 Oct 2021 22:24:19 +0800 Subject: [PATCH 0139/2647] modify reaper Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/reaper.go | 5 +++++ pkg/microservice/reaper/core/service/reaper/script.go | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 681e173e..fdf015bc 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -413,6 +413,11 @@ func (r *Reaper) AfterExec(upStreamErr error) error { log.Errorf("RunPostScripts err %v", err) return err } + } else { + if err = r.downloadArtifactFile(); err != nil { + log.Errorf("download archiveFiles err %v", err) + return err + } } // 运行物理机部署脚本 diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index c6736482..9a175f43 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -199,10 +199,6 @@ func (r *Reaper) createReadme(file string) error { } func (r *Reaper) runScripts() error { - // artifact deploy - if r.Ctx.ArtifactInfo != nil { - return r.downloadArtifactFile() - } if len(r.Ctx.Scripts) == 0 { return nil } -- Gitee From 13cfcef1350fd7ca1001127f136e321ba27a1a41 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 11:07:31 +0800 Subject: [PATCH 0140/2647] fix loop bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index d5c77ece..d5ebc62b 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -21,6 +21,8 @@ import ( "strings" "sync" + "github.com/koderover/zadig/pkg/tool/log" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" @@ -128,12 +130,14 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) } for _, serviceName := range serviceNames { + newServiceName := serviceName wg.Start(func() { deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ - Name: serviceName, + Name: newServiceName, Type: kind, Source: string(config.WorkflowType), }) + log.Infof("serviceName:%s", newServiceName) if err != nil { logger.Errorf("ListTars err:%s", err) return @@ -156,7 +160,7 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) mutex.Lock() tarInfos = append(tarInfos, &commonmodels.TarInfo{ URL: defaultURL, - Name: serviceName, + Name: newServiceName, FileName: deliveryArtifact.Image, WorkflowName: workflowName, TaskID: int64(taskID), -- Gitee From 7ca909adb2f3e490c1f245a958f54ddc29291e12 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 11:12:57 +0800 Subject: [PATCH 0141/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index d5ebc62b..86f8d1f3 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -21,8 +21,6 @@ import ( "strings" "sync" - "github.com/koderover/zadig/pkg/tool/log" - "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" @@ -137,7 +135,6 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) Type: kind, Source: string(config.WorkflowType), }) - log.Infof("serviceName:%s", newServiceName) if err != nil { logger.Errorf("ListTars err:%s", err) return -- Gitee From 3948a0704064deb3fbe8c08a8ed14dea1af41cc0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 22 Oct 2021 11:33:07 +0800 Subject: [PATCH 0142/2647] add environment variable logic Signed-off-by: allenshen --- .../repository/models/template/product.go | 24 +- .../aslan/core/common/service/render.go | 51 +---- .../core/environment/handler/environment.go | 66 +++++- .../core/environment/handler/renderset.go | 64 ++++++ .../aslan/core/environment/handler/router.go | 10 +- .../core/environment/service/environment.go | 209 +++++++++++------- .../core/environment/service/renderset.go | 11 - .../aslan/core/project/handler/router.go | 2 +- .../aslan/core/service/service/helm.go | 86 +++---- .../aslan/core/service/service/types.go | 14 +- .../core/service/types/environment.go | 25 +-- 11 files changed, 326 insertions(+), 236 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 97f772e1..80b8d53f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -105,10 +105,10 @@ type GitRepoConfig struct { } type CustomYaml struct { - YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` - YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` - ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` + //YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` + YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` + //GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` + //ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` } // RenderChart ... @@ -231,22 +231,6 @@ func (r *RenderKV) RemoveDupServices() { r.Services = result } -func (rc *RenderChart) DiffOverrideYaml(other *RenderChart) bool { - if rc.OverrideYaml == nil { - if other.OverrideYaml != nil { - return true - } - return false - } - if other.OverrideYaml == nil { - return true - } - if rc.OverrideYaml.YamlSource != other.OverrideYaml.YamlSource || rc.OverrideYaml.YamlContent != other.OverrideYaml.YamlContent { - return true - } - return false -} - func (rc *RenderChart) GetOverrideYaml() string { if rc.OverrideYaml == nil { return "" diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index b7128324..9ff52e90 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -59,7 +59,8 @@ type RenderChartArg struct { ServiceName string `json:"serviceName,omitempty"` ChartVersion string `json:"chartVersion,omitempty"` OverrideValues []*KVPair `json:"overrideValues,omitempty"` - *YamlData + OverrideYaml string `json:"overrideYaml,omitempty"` + //*YamlData } type RendersetArg struct { @@ -67,7 +68,7 @@ type RendersetArg struct { *RenderChartArg } -func (args *RenderChartArg) toOverrideValueString() string { +func (args *RenderChartArg) ToOverrideValueString() string { if len(args.OverrideValues) == 0 { return "" } @@ -93,23 +94,9 @@ func (args *RenderChartArg) fromOverrideValueString(valueStr string) { } func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.CustomYaml { - switch args.YamlSource { - case setting.ValuesYamlSourceFreeEdit: + if len(args.OverrideYaml) > 0 { return &templatemodels.CustomYaml{ - YamlSource: args.YamlSource, - YamlContent: args.ValuesYAML, - } - case setting.ValuesYamlSourceGitRepo: - return &templatemodels.CustomYaml{ - YamlSource: args.YamlSource, - YamlContent: args.ValuesYAML, - ValuesPaths: args.GitRepoConfig.ValuesPaths, - GitRepoConfig: &templatemodels.GitRepoConfig{ - CodehostID: args.GitRepoConfig.CodehostID, - Owner: args.GitRepoConfig.Owner, - Repo: args.GitRepoConfig.Repo, - Branch: args.GitRepoConfig.Branch, - }, + YamlContent: args.OverrideYaml, } } return nil @@ -119,37 +106,15 @@ func (args *RenderChartArg) fromCustomValueYaml(customValuesYaml *templatemodels if customValuesYaml == nil { return } - - switch customValuesYaml.YamlSource { - case setting.ValuesYamlSourceFreeEdit: - args.YamlData = &YamlData{ - YamlSource: customValuesYaml.YamlSource, - ValuesYAML: customValuesYaml.YamlContent, - } - case setting.ValuesYamlSourceGitRepo: - args.YamlData = &YamlData{ - YamlSource: customValuesYaml.YamlSource, - ValuesYAML: "", - } - if customValuesYaml.GitRepoConfig != nil { - args.YamlData.GitRepoConfig = &RepoConfig{ - CodehostID: customValuesYaml.GitRepoConfig.CodehostID, - Owner: customValuesYaml.GitRepoConfig.Owner, - Repo: customValuesYaml.GitRepoConfig.Repo, - Branch: customValuesYaml.GitRepoConfig.Branch, - ValuesPaths: customValuesYaml.ValuesPaths, - } - } - - } + args.OverrideYaml = customValuesYaml.YamlContent } // FillRenderChartModel fill render chart model func (args *RenderChartArg) FillRenderChartModel(chart *templatemodels.RenderChart, version string) { chart.ServiceName = args.ServiceName chart.ChartVersion = version - chart.OverrideValues = args.toOverrideValueString() - if args.YamlData != nil { + chart.OverrideValues = args.ToOverrideValueString() + if len(args.OverrideYaml) > 0 { chart.OverrideYaml = args.toCustomValuesYaml() } else { chart.OverrideYaml = nil diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index ea9a0ad9..53e57b6a 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -75,6 +75,14 @@ func ListProducts(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProducts(c.Query("productName"), c.Query("envType"), ctx.User.Name, ctx.User.ID, ctx.User.IsSuperUser, ctx.Logger) } +// GetProductStatus List product status +func GetProductStatus(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetProductStatus(c.Query("productName"), ctx.Logger) +} + func AutoCreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -116,25 +124,34 @@ func CreateHelmProduct(c *gin.Context) { return } - args := new(service.CreateHelmProductArg) + productName := c.Param("productName") + + createArgs := make([]*service.CreateHelmProductArg, 0) data, err := c.GetRawData() if err != nil { - log.Errorf("CreateProduct c.GetRawData() err : %v", err) + log.Errorf("CreateHelmProduct c.GetRawData() err : %v", err) + } else if err = json.Unmarshal(data, &createArgs); err != nil { + log.Errorf("CreateHelmProduct json.Unmarshal err : %v", err) } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateProduct json.Unmarshal err : %v", err) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } - args.ProductName = c.Param("productName") - if args.EnvName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") - return + envNameList := make([]string, 0) + for _, arg := range createArgs { + if arg.EnvName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName is empty") + return + } + arg.ProductName = productName + envNameList = append(envNameList, arg.EnvName) } - internalhandler.InsertOperationLog(c, ctx.Username, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.Username, productName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( - ctx.Username, ctx.RequestID, args, ctx.Logger, + productName, ctx.Username, ctx.RequestID, createArgs, ctx.Logger, ) } @@ -183,6 +200,8 @@ func UpdateProduct(c *gin.Context) { } if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProduct json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } internalhandler.InsertOperationLog(c, ctx.Username, productName, "更新", "集成环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -227,6 +246,31 @@ func UpdateProductRecycleDay(c *gin.Context) { ctx.Err = service.UpdateProductRecycleDay(envName, productName, recycleDay) } +func EstimatedValues(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + productName := c.Param("productName") + if productName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + return + } + + serviceName := c.Query("serviceName") + if serviceName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("serviceName can't be empty!") + return + } + + arg := new(service.EstimateValuesArg) + if err := c.ShouldBindQuery(arg); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Resp, ctx.Err = service.GeneEstimatedValues(productName, c.Query("envName"), serviceName, c.Query("format"), arg, ctx.Logger) +} + func UpdateHelmProductRenderset(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -378,11 +422,13 @@ func GetEstimatedRenderCharts(c *gin.Context) { productName := c.Query("productName") if productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + return } envName := c.Query("envName") if envName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") + return } ctx.Resp, ctx.Err = service.GetEstimatedRenderCharts(productName, envName, c.Query("serviceName"), ctx.Logger) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 43eea34e..00b3a538 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -18,6 +18,13 @@ package handler import ( "encoding/json" + "fmt" + "strconv" + "strings" + + yamlutil "github.com/koderover/zadig/pkg/util/yaml" + + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/gin-gonic/gin" @@ -114,8 +121,65 @@ func CreateOrUpdateRenderset(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) ctx.Err = service.CreateOrUpdateRenderset(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) } + +func GetYamlContent(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + var err error + codehostID := 0 + + codehostIDStr := c.Query("codehostID") + if len(codehostIDStr) > 0 { + codehostID, err = strconv.Atoi(codehostIDStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("cannot convert codehost id to int") + return + } + } + + if codehostID == 0 && len(c.Query("repoLink")) == 0 { + ctx.Err = e.ErrInvalidParam.AddDesc("neither codehost nor repo link is specified") + return + } + + if len(c.Query("valuesPaths")) == 0 { + ctx.Err = e.ErrInvalidParam.AddDesc("paths can't be empty") + return + } + + pathArr := strings.Split(c.Query("valuesPaths"), ",") + + contentArr := make([][]byte, 0) + for _, path := range pathArr { + fileContent, err := fsservice.DownloadFileFromSource( + &fsservice.DownloadFromSourceArgs{ + CodehostID: codehostID, + Owner: c.Query("owner"), + Repo: c.Query("repo"), + Path: path, + Branch: c.Query("branch"), + RepoLink: c.Query("repoLink"), + }) + if err != nil { + ctx.Err = fmt.Errorf("failed to download file from path %s, err %s", path, err) + return + } + if len(fileContent) > 0 { + contentArr = append(contentArr, fileContent) + } + } + + ret, err := yamlutil.Merge(contentArr) + if err != nil { + ctx.Err = fmt.Errorf("failed to merge files, err %s", err) + return + } + ctx.Resp = string(ret) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 410bef3e..fb527646 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -97,16 +97,19 @@ func (*Router) Inject(router *gin.RouterGroup) { environments := router.Group("environments") { environments.GET("", ListProducts) + environments.GET("/:productName/status", GetProductStatus) environments.POST("/:productName/auto", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID}, permission.ParamType), AutoCreateProduct) + environments.PUT("/:productName/autoUpdate", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, AutoUpdateProduct) environments.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProduct) - environments.POST("/:productName/helm", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateHelmProduct) - environments.PUT("/:productName/multiHelmEnv", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) + environments.POST("/:productName/helm", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateHelmProduct) + environments.PUT("/:productName/multiHelmEnv", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) environments.POST("/:productName", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProduct) environments.PUT("/:productName/envRecycle", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) + environments.POST("/:productName/estimated-values", EstimatedValues) environments.PUT("/:productName/renderset", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) @@ -142,6 +145,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { rendersets.GET("/renderchart", GetServiceRenderCharts) rendersets.GET("/default-values", GetProductDefaultValues) + rendersets.GET("/yamlContent", GetYamlContent) + + //TODO need to deprecated rendersets.PUT("/renderset", CreateOrUpdateRenderset) //TODO need to deprecated rendersets.PUT("/renderchart", CreateOrUpdateRenderChart) // create or update renderchart when onBoarding diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 6a67b55e..5d287516 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -47,6 +47,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -104,12 +105,18 @@ type ProductParams struct { PermissionUUIDs []string `json:"permissionUUIDs"` } +type EstimateValuesArg struct { + DefaultValues string `json:"defaultValues"` + OverrideYaml string `json:"overrideYaml"` + OverrideValues []*commonservice.KVPair `json:"overrideValues,omitempty"` +} + type EnvRenderChartArg struct { ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } type EnvRendersetArg struct { - DefaultValues *commonservice.YamlData `json:"defaultValues"` + DefaultValues string `json:"defaultValues"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } @@ -118,7 +125,7 @@ type CreateHelmProductArg struct { EnvName string `json:"envName"` Namespace string `json:"namespace"` ClusterID string `json:"clusterID"` - DefaultValues *commonservice.YamlData `json:"defaultValues"` + DefaultValues string `json:"defaultValues"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } @@ -129,6 +136,10 @@ type UpdateMultiHelmProductArg struct { ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented } +type RawYamlResp struct { + YamlContent string `json:"yamlContent"` +} + func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { err := commonrepo.NewProductColl().UpdateIsPublic(args.EnvName, productName, args.IsPublic) if err != nil { @@ -155,6 +166,27 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar return nil } +func GetProductStatus(envType string, log *zap.SugaredLogger) ([]*EnvStatus, error) { + //项目下所有公开环境 + publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true}) + if err != nil { + log.Errorf("Collection.Product.List List product error: %v", err) + return nil, e.ErrListProducts.AddDesc(err.Error()) + } + envStatusSlice := make([]*EnvStatus, 0) + for _, publicProduct := range publicProducts { + envStatus := &EnvStatus{ + EnvName: publicProduct.EnvName, + Status: publicProduct.Status, + } + if len(publicProduct.Error) > 0 { + envStatus.ErrMessage = publicProduct.Error + } + envStatusSlice = append(envStatusSlice, envStatus) + } + return envStatusSlice, err +} + func ListProducts(productNameParam, envType string, userName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ProductResp, error) { var ( err error @@ -936,13 +968,13 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return nil } -func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, log *zap.SugaredLogger) error { - templateProduct, err := templaterepo.NewProductColl().Find(args.ProductName) +func CreateHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { + templateProduct, err := templaterepo.NewProductColl().Find(productName) if err != nil || templateProduct == nil { if err != nil { - log.Errorf("failed to query product %s, err %s ", args.ProductName, err.Error()) + log.Errorf("failed to query product %s, err %s ", productName, err.Error()) } - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", args.ProductName)) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", productName)) } err = commonservice.FillProductTemplateValuesYamls(templateProduct, log) @@ -950,23 +982,9 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l return e.ErrCreateEnv.AddDesc(err.Error()) } - productObj := &commonmodels.Product{ - ProductName: args.ProductName, - Revision: 1, - Enabled: false, - EnvName: args.EnvName, - UpdateBy: userName, - IsPublic: true, - ClusterID: args.ClusterID, - Namespace: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName, args.Namespace), - Source: setting.SourceFromHelm, - IsOpenSource: templateProduct.IsOpensource, - ChartInfos: templateProduct.ChartInfos, - IsForkedProduct: false, - } - + // generate service group data allServiceInfoMap := templateProduct.AllServiceInfoMap() - + var serviceGroup [][]*commonmodels.ProductService for _, names := range templateProduct.Services { servicesResp := make([]*commonmodels.ProductService, 0) for _, serviceName := range names { @@ -1002,60 +1020,64 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l } servicesResp = append(servicesResp, serviceResp) } - productObj.Services = append(productObj.Services, servicesResp) + serviceGroup = append(serviceGroup, servicesResp) } - // extract values.yaml from request and save into db - serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(args.ProductName) - if err != nil { - log.Infof("query services from product: %s fail, error %s", args.ProductName, err.Error()) - return e.ErrCreateEnv.AddDesc("failed to query services") + errList := new(multierror.Error) + for _, arg := range args { + err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, arg, log) + if err != nil { + errList = multierror.Append(errList, err) + } } + return errList.ErrorOrNil() +} - serviceMap := make(map[string]*commonmodels.Service) - for _, singleService := range serviceList { - serviceMap[singleService.ServiceName] = singleService +func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][]*commonmodels.ProductService, requestID, userName string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { + productObj := &commonmodels.Product{ + ProductName: templateProduct.ProductName, + Revision: 1, + Enabled: false, + EnvName: arg.EnvName, + UpdateBy: userName, + Services: serviceGroup, + IsPublic: true, + ClusterID: arg.ClusterID, + Namespace: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + Source: setting.SourceFromHelm, + IsOpenSource: templateProduct.IsOpensource, + ChartInfos: templateProduct.ChartInfos, + IsForkedProduct: false, } customChartValueMap := make(map[string]*commonservice.RenderChartArg) - for _, singleCV := range args.ChartValues { + for _, singleCV := range arg.ChartValues { customChartValueMap[singleCV.ServiceName] = singleCV } for _, latestChart := range productObj.ChartInfos { if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { - err := generateValuesYaml(singleCV.YamlData, log) - if err != nil { - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", singleCV.ServiceName, err.Error())) - } singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) } } // default values - defaultValuesYaml := "" - if args.DefaultValues != nil { - err = generateValuesYaml(args.DefaultValues, log) - if err != nil { - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content err %s", err.Error())) - } - defaultValuesYaml = args.DefaultValues.ValuesYAML - } + defaultValuesYaml := arg.DefaultValues // insert renderset info into db if len(productObj.ChartInfos) > 0 { - err = commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ - Name: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName, args.Namespace), - EnvName: args.EnvName, - ProductTmpl: args.ProductName, + err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ + Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + EnvName: arg.EnvName, + ProductTmpl: arg.ProductName, UpdateBy: userName, IsDefault: false, DefaultValues: defaultValuesYaml, ChartInfos: productObj.ChartInfos, }, log) if err != nil { - log.Errorf("rennderset create fail when creating helm product, productName: %s", args.ProductName) - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", args.ProductName)) + log.Errorf("rennderset create fail when creating helm product, productName: %s", arg.ProductName) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", arg.ProductName)) } } @@ -1132,11 +1154,56 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } +func GeneEstimatedValues(productName, envName, serviceName, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { + var opt *commonrepo.RenderSetFindOption + if len(envName) > 0 { + renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") + opt = &commonrepo.RenderSetFindOption{Name: renderSetName} + } else { + opt = &commonrepo.RenderSetFindOption{Name: productName} + } + productRenderset, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil { + log.Errorf("failed to get renderset, productName %s evnName %s", productName, envName) + } + if !existed { + return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find renderset, envName: %s", envName)) + } + var targetChart *templatemodels.RenderChart + for _, chartInfo := range productRenderset.ChartInfos { + if chartInfo.ServiceName == serviceName { + targetChart = chartInfo + break + } + } + if targetChart == nil { + return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find chart info, serviceName: %s", serviceName)) + } + tempArg := &commonservice.RenderChartArg{OverrideValues: arg.OverrideValues} + mergeValues, err := helmtool.MergeOverrideValues(arg.DefaultValues, targetChart.ValuesYaml, arg.OverrideYaml, tempArg.ToOverrideValueString()) + if err != nil { + return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to merge values, err %s", err)) + } + + type YamlCountResp struct { + YamlContent string `json:"yamlContent"` + } + + switch format { + case "flatMap": + mapData, err := converter.YamlToFlatMap([]byte(mergeValues)) + if err != nil { + return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to generate flat map , err %s", err)) + } + return mapData, nil + default: + return &YamlCountResp{YamlContent: mergeValues}, nil + } +} + // check if override values or yaml content changes func checkOverrideValuesChange(source *template.RenderChart, args *commonservice.RenderChartArg) bool { - tmpRenderCharts := &template.RenderChart{} - args.FillRenderChartModel(tmpRenderCharts, "") - if source.OverrideValues != tmpRenderCharts.OverrideValues || source.DiffOverrideYaml(tmpRenderCharts) { + if source.OverrideValues != args.ToOverrideValueString() || source.GetOverrideYaml() != args.OverrideYaml { return true } return false @@ -1158,27 +1225,15 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string updatedRcList := make([]*template.RenderChart, 0) updatedRCMap := make(map[string]*template.RenderChart) - // default values - if args.DefaultValues != nil { - err := generateValuesYaml(args.DefaultValues, log) - if err != nil { - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content err %s", err.Error())) - } - // all charts need to be updated when environment's default value changed - if args.DefaultValues.ValuesYAML != productRenderset.DefaultValues { - for _, curRenderChart := range productRenderset.ChartInfos { - updatedRCMap[curRenderChart.ServiceName] = curRenderChart - } + // default values change + if args.DefaultValues != productRenderset.DefaultValues { + for _, curRenderChart := range productRenderset.ChartInfos { + updatedRCMap[curRenderChart.ServiceName] = curRenderChart } - productRenderset.DefaultValues = args.DefaultValues.ValuesYAML + productRenderset.DefaultValues = args.DefaultValues } for _, requestRenderChart := range args.ChartValues { - err := generateValuesYaml(requestRenderChart.YamlData, log) - if err != nil { - return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) - } - // update renderset info for _, curRenderChart := range productRenderset.ChartInfos { if curRenderChart.ServiceName != requestRenderChart.ServiceName { @@ -1218,11 +1273,6 @@ func UpdateHelmProductRenderCharts(productName, envName, userName, requestID str updatedRcs := make([]*template.RenderChart, 0) for _, requestRenderChart := range args.ChartValues { - err := generateValuesYaml(requestRenderChart.YamlData, log) - if err != nil { - return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) - } - // update renderset info for _, curRenderChart := range productRenderset.ChartInfos { if curRenderChart.ServiceName != requestRenderChart.ServiceName { @@ -1381,13 +1431,6 @@ func UpdateMultipleHelmEnv(userName, requestID string, userID int, superUser boo serviceMap[singleService.ServiceName] = singleService } - for _, requestRenderChart := range args.ChartValues { - err := generateValuesYaml(requestRenderChart.YamlData, log) - if err != nil { - return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) - } - } - // extract values.yaml and update renderset for envName, _ := range productMap { renderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ @@ -2866,7 +2909,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes //拿当前环境values.yaml的key的value去替换服务里面的values.yaml的相同的key的value newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml)) if err != nil { - log.Errorf("Failed to override values, err: %s", err) + log.Errorf("Failed to override values for service %s, err: %s", serviceName, err) } else { latestChartInfo.ValuesYaml = string(newValuesYaml) } diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 09f05b0a..5886c1d6 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -188,12 +188,6 @@ func CreateOrUpdateRenderset(productName, envName string, args *commonservice.Re return e.ErrCreateRenderSet.AddDesc("missing helm chart info") } - if args.RenderChartArg != nil && args.RenderChartArg.YamlData != nil { - err := generateValuesYaml(args.RenderChartArg.YamlData, log) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) - } - } if args.DefaultValues != nil && args.DefaultValues.YamlSource != "" { err := generateValuesYaml(args.DefaultValues, log) if err != nil { @@ -278,11 +272,6 @@ func CreateOrUpdateChartValues(productName, envName string, args *commonservice. return e.ErrCreateRenderSet.AddDesc("missing helm chart info") } - err = generateValuesYaml(args.YamlData, log) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) - } - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") opt := &commonrepo.RenderSetFindOption{Name: renderSetName} diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 8a503162..17639f46 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -44,7 +44,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.GET("/:name/services", GetProductTemplateServices) product.GET("", ListProductTemplate) product.GET("/:name/searching-rules", GetCustomMatchRules) - product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) + product.PUT("/:name/searching-rules", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 0458b966..8ffe6e9c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -228,20 +228,20 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe if len(defaultValues) > 0 { values = append(values, defaultValues) } - for _, path := range templateArgs.ValuesPaths { - v, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ - CodehostID: templateArgs.CodehostID, - Owner: templateArgs.Owner, - Repo: templateArgs.Repo, - Path: path, - Branch: templateArgs.Branch, - }) - if err != nil { - return err - } - - values = append(values, v) - } + //for _, path := range templateArgs.ValuesPaths { + // v, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ + // CodehostID: templateArgs.CodehostID, + // Owner: templateArgs.Owner, + // Repo: templateArgs.Repo, + // Path: path, + // Branch: templateArgs.Branch, + // }) + // if err != nil { + // return err + // } + // + // values = append(values, v) + //} if len(templateArgs.ValuesYAML) > 0 { values = append(values, []byte(templateArgs.ValuesYAML)) @@ -276,18 +276,18 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ - ServiceName: args.Name, - FilePath: to, - ProductName: projectName, - CreateBy: args.CreatedBy, - CodehostID: templateArgs.CodehostID, - Owner: templateArgs.Owner, - Repo: templateArgs.Repo, - Branch: templateArgs.Branch, + ServiceName: args.Name, + FilePath: to, + ProductName: projectName, + CreateBy: args.CreatedBy, + //CodehostID: templateArgs.CodehostID, + //Owner: templateArgs.Owner, + //Repo: templateArgs.Repo, + //Branch: templateArgs.Branch, Source: setting.SourceFromChartTemplate, HelmTemplateName: templateArgs.TemplateName, - ValuePaths: templateArgs.ValuesPaths, - ValuesYaml: templateArgs.ValuesYAML, + //ValuePaths: templateArgs.ValuesPaths, + ValuesYaml: templateArgs.ValuesYAML, }, logger, ) @@ -481,23 +481,27 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { } case setting.SourceFromChartTemplate: var yamlData *template.CustomYaml - if args.CodehostID > 0 { - yamlData = &template.CustomYaml{ - YamlSource: setting.ValuesYamlSourceGitRepo, - YamlContent: "", - GitRepoConfig: &templatemodels.GitRepoConfig{ - CodehostID: args.CodehostID, - Owner: args.Owner, - Repo: args.Repo, - Branch: args.Branch, - }, - ValuesPaths: args.ValuePaths, - } - } else if len(args.ValuesYaml) > 0 { - yamlData = &template.CustomYaml{ - YamlSource: setting.ValuesYamlSourceFreeEdit, - YamlContent: args.ValuesYaml, - } + //if args.CodehostID > 0 { + // yamlData = &template.CustomYaml{ + // YamlSource: setting.ValuesYamlSourceGitRepo, + // YamlContent: "", + // GitRepoConfig: &templatemodels.GitRepoConfig{ + // CodehostID: args.CodehostID, + // Owner: args.Owner, + // Repo: args.Repo, + // Branch: args.Branch, + // }, + // ValuesPaths: args.ValuePaths, + // } + //} else if len(args.ValuesYaml) > 0 { + // yamlData = &template.CustomYaml{ + // YamlSource: setting.ValuesYamlSourceFreeEdit, + // YamlContent: args.ValuesYaml, + // } + //} + + yamlData = &template.CustomYaml{ + YamlContent: args.ValuesYaml, } return &models.CreateFromChartTemplate{ diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index bdfdcd5b..3de9c8e7 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -57,13 +57,13 @@ type CreateFromPublicRepo struct { } type CreateFromChartTemplate struct { - CodehostID int `json:"codehostID"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Branch string `json:"branch"` - TemplateName string `json:"templateName"` - ValuesPaths []string `json:"valuesPaths"` - ValuesYAML string `json:"valuesYAML"` + //CodehostID int `json:"codehostID"` + //Owner string `json:"owner"` + //Repo string `json:"repo"` + //Branch string `json:"branch"` + TemplateName string `json:"templateName"` + //ValuesPaths []string `json:"valuesPaths"` + ValuesYAML string `json:"valuesYAML"` } func PublicRepoToPrivateRepoArgs(args *CreateFromPublicRepo) (*CreateFromRepo, error) { diff --git a/pkg/microservice/warpdrive/core/service/types/environment.go b/pkg/microservice/warpdrive/core/service/types/environment.go index dcb576db..5de5d860 100644 --- a/pkg/microservice/warpdrive/core/service/types/environment.go +++ b/pkg/microservice/warpdrive/core/service/types/environment.go @@ -67,19 +67,19 @@ type GitRepoConfig struct { Branch string `bson:"branch,omitempty"` } -type OverrideYaml struct { +type CustomYaml struct { YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"gitRepoConfig,omitempty"` + GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` } type RenderChart struct { - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` - ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` - OverrideYaml *OverrideYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` - OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` + ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` + OverrideYaml *CustomYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` + OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` } type ProductAuth struct { @@ -98,17 +98,6 @@ type Service struct { EnvConfigs []*EnvConfig `bson:"-" json:"env_configs,omitempty"` } -//type EnvConfig struct { -// EnvName string `json:"env_name"` -// HostIDs []string `json:"host_ids"` -//} -// -//// Container ... -//type Container struct { -// Name string `bson:"name" json:"name"` -// Image string `bson:"image" json:"image"` -//} - // Config ... type Config struct { ConfigName string `bson:"config_name" json:"config_name"` -- Gitee From ea24cec64401ab807156d5209cb819f2bd430dee Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 11:59:41 +0800 Subject: [PATCH 0143/2647] edit role Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 19 ++++++++++++ .../policy/core/handler/router.go | 1 + .../policy/core/repository/mongodb/role.go | 29 +++++++++++++++++++ pkg/microservice/policy/core/service/role.go | 17 +++++++++++ 4 files changed, 66 insertions(+) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index c5589a51..dd7e1de2 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -43,6 +43,25 @@ func CreateRole(c *gin.Context) { ctx.Err = service.CreateRole(projectName, args, ctx.Logger) } +func UpdateRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Role{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Err = service.UpdateRole(projectName, args, ctx.Logger) +} + func ListRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 9508bcce..6a15a00f 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,6 +26,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) + roles.PUT("", UpdateRole) roles.GET("", ListRoles) roles.DELETE("/:name", DeleteRole) } diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index a8130617..f67e3106 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -18,6 +18,7 @@ package mongodb import ( "context" + "errors" "fmt" "go.mongodb.org/mongo-driver/bson" @@ -127,3 +128,31 @@ func (c *RoleColl) Delete(name string, projectName string) error { _, err := c.DeleteOne(context.TODO(), query) return err } + +func (c *RoleColl) UpdateProjectRole(obj *models.Role) error { + // avoid panic issue + if obj == nil { + return errors.New("nil Role") + } + + query := bson.M{"name": obj.Name, "namespace": obj.Namespace} + change := bson.M{"$set": bson.M{ + "rules": obj.Rules, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *RoleColl) UpdatePublicRole(obj *models.Role) error { + // avoid panic issue + if obj == nil { + return errors.New("nil Role") + } + + query := bson.M{"name": obj.Name, "namespace": ""} + change := bson.M{"$set": bson.M{ + "rules": obj.Rules, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index f6d493d2..fd71688a 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -51,6 +51,23 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().Create(obj) } +func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { + obj := &models.Role{ + Name: role.Name, + Namespace: ns, + Kind: role.Kind, + } + + for _, r := range role.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Resources: r.Resources, + }) + } + + return mongodb.NewRoleColl().UpdateProjectRole(obj) +} + func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { projectRoles, err := mongodb.NewRoleColl().ListBy(projectName) if err != nil { -- Gitee From 968d3cf1db2a8cc7a704101cc41808d41c0a4255 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 12:01:27 +0800 Subject: [PATCH 0144/2647] edit role Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 13 +++++++++++++ pkg/microservice/policy/core/handler/router.go | 1 + pkg/microservice/policy/core/service/role.go | 16 ++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index dd7e1de2..919686ea 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -62,6 +62,19 @@ func UpdateRole(c *gin.Context) { ctx.Err = service.UpdateRole(projectName, args, ctx.Logger) } +func UpdatePublicRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Role{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.UpdatePublicRole(args, ctx.Logger) +} + func ListRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 6a15a00f..4a585e12 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -35,6 +35,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { publicRoles.POST("", CreatePublicRole) publicRoles.GET("", ListPublicRoles) + publicRoles.PUT("", UpdatePublicRole) publicRoles.DELETE("/:name", DeletePublicRole) } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index fd71688a..461110dc 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -68,6 +68,22 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().UpdateProjectRole(obj) } +func UpdatePublicRole(role *Role, _ *zap.SugaredLogger) error { + obj := &models.Role{ + Name: role.Name, + Kind: role.Kind, + } + + for _, r := range role.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Resources: r.Resources, + }) + } + + return mongodb.NewRoleColl().UpdatePublicRole(obj) +} + func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { projectRoles, err := mongodb.NewRoleColl().ListBy(projectName) if err != nil { -- Gitee From f6f88a37c01f3153eda2baf2771e6369e1ed6eb0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 13:49:46 +0800 Subject: [PATCH 0145/2647] modify list tar interface Signed-off-by: liu deyi --- .../repository/mongodb/delivery_artifact.go | 31 +++++++++++-------- .../aslan/core/system/service/s3.go | 7 +++-- .../workflow/service/workflow/nsq_handlers.go | 3 +- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index b52df632..e0e0cc75 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -32,19 +32,20 @@ import ( ) type DeliveryArtifactArgs struct { - ID string `json:"id"` - Image string `json:"image"` - Name string `json:"name"` - Type string `json:"type"` - RepoName string `json:"repo_name"` - Branch string `json:"branch"` - Source string `json:"source"` - ImageHash string `json:"image_hash"` - ImageTag string `json:"image_tag"` - ImageDigest string `json:"image_digest"` - PerPage int `json:"per_page"` - Page int `json:"page"` - IsFuzzyQuery bool `json:"is_fuzzy_query"` + ID string `json:"id"` + Image string `json:"image"` + Name string `json:"name"` + Type string `json:"type"` + RepoName string `json:"repo_name"` + Branch string `json:"branch"` + Source string `json:"source"` + ImageHash string `json:"image_hash"` + ImageTag string `json:"image_tag"` + ImageDigest string `json:"image_digest"` + PerPage int `json:"per_page"` + Page int `json:"page"` + IsFuzzyQuery bool `json:"is_fuzzy_query"` + PackageStorageURI string `json:"package_storage_uri"` } type DeliveryArtifactColl struct { @@ -207,6 +208,10 @@ func (c *DeliveryArtifactColl) ListTars(args *DeliveryArtifactArgs) ([]*models.D query["source"] = args.Source } + if args.PackageStorageURI != "" { + query["package_storage_uri"] = args.PackageStorageURI + } + opt := options.Find(). SetSort(bson.D{{"created_time", -1}}) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 86f8d1f3..aaa2eb5a 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -131,9 +131,10 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) newServiceName := serviceName wg.Start(func() { deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ - Name: newServiceName, - Type: kind, - Source: string(config.WorkflowType), + Name: newServiceName, + Type: kind, + Source: string(config.WorkflowType), + PackageStorageURI: defaultURL, }) if err != nil { logger.Errorf("ListTars err:%s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 70542680..029dd499 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -244,8 +244,7 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifact.ImageTag = buildInfo.JobCtx.FileArchiveCtx.FileName deliveryArtifact.Type = string(config.File) deliveryArtifact.PackageFileLocation = buildInfo.JobCtx.FileArchiveCtx.FileLocation - storageInfo, _ := s3.NewS3StorageFromEncryptedURI(pt.StorageURI) - deliveryArtifact.PackageStorageURI = storageInfo.Endpoint + deliveryArtifact.PackageStorageURI = pt.StorageURI deliveryArtifactArray = append(deliveryArtifactArray, deliveryArtifact) } -- Gitee From e3fe9f0c88c48f35edb993ce10ece66fb585cd16 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 22 Oct 2021 13:52:07 +0800 Subject: [PATCH 0146/2647] annotating useless code Signed-off-by: allenshen --- .../aslan/core/common/service/render.go | 19 +- .../core/environment/handler/environment.go | 62 +-- .../core/environment/handler/renderset.go | 180 +++---- .../aslan/core/environment/handler/router.go | 6 +- .../core/environment/service/environment.go | 66 +-- .../core/environment/service/renderset.go | 438 +++++++++--------- 6 files changed, 394 insertions(+), 377 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 9ff52e90..e318374e 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -48,11 +48,11 @@ type KVPair struct { Value string `json:"value"` } -type YamlData struct { - YamlSource string `json:"yamlSource,omitempty"` - GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` - ValuesYAML string `json:"valuesYAML,omitempty"` -} +//type YamlData struct { +//YamlSource string `json:"yamlSource,omitempty"` +//GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` +//ValuesYAML string `json:"valuesYAML,omitempty"` +//} type RenderChartArg struct { EnvName string `json:"envName,omitempty"` @@ -60,13 +60,12 @@ type RenderChartArg struct { ChartVersion string `json:"chartVersion,omitempty"` OverrideValues []*KVPair `json:"overrideValues,omitempty"` OverrideYaml string `json:"overrideYaml,omitempty"` - //*YamlData } -type RendersetArg struct { - DefaultValues *YamlData `json:"defaultValues"` - *RenderChartArg -} +//type RendersetArg struct { +// DefaultValues *YamlData `json:"defaultValues"` +// *RenderChartArg +//} func (args *RenderChartArg) ToOverrideValueString() string { if len(args.OverrideValues) == 0 { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 53e57b6a..3e8ae217 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -303,37 +303,37 @@ func UpdateHelmProductRenderset(c *gin.Context) { } } -func UpdateHelmProductRenderCharts(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - productName := c.Param("productName") - if productName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") - return - } - - envName := c.Query("envName") - if envName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") - return - } - - arg := new(service.EnvRenderChartArg) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, arg); err != nil { - log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) - } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "helm集成环境变量", "", string(data), ctx.Logger) - - ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.Username, ctx.RequestID, arg, ctx.Logger) - if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) - } -} +//func UpdateHelmProductRenderCharts(c *gin.Context) { +// ctx := internalhandler.NewContext(c) +// defer func() { internalhandler.JSONResponse(c, ctx) }() +// +// productName := c.Param("productName") +// if productName == "" { +// ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") +// return +// } +// +// envName := c.Query("envName") +// if envName == "" { +// ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") +// return +// } +// +// arg := new(service.EnvRenderChartArg) +// data, err := c.GetRawData() +// if err != nil { +// log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) +// } +// if err = json.Unmarshal(data, arg); err != nil { +// log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) +// } +// internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "helm集成环境变量", "", string(data), ctx.Logger) +// +// ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.Username, ctx.RequestID, arg, ctx.Logger) +// if ctx.Err != nil { +// ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) +// } +//} func UpdateMultiHelmEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 00b3a538..14361ce0 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -17,22 +17,21 @@ limitations under the License. package handler import ( - "encoding/json" "fmt" "strconv" "strings" + "sync" yamlutil "github.com/koderover/zadig/pkg/util/yaml" + "github.com/pkg/errors" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/gin-gonic/gin" - commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/log" ) func GetServiceRenderCharts(c *gin.Context) { @@ -69,64 +68,64 @@ func GetProductDefaultValues(c *gin.Context) { ctx.Resp, ctx.Err = service.GetDefaultValues(c.Query("productName"), c.Query("envName"), ctx.Logger) } -func CreateOrUpdateRenderChart(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - if c.Query("productName") == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") - return - } - - if c.Query("envName") == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") - return - } - - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) - } - - args := new(commonservice.RenderChartArg) - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) - - ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) -} - -func CreateOrUpdateRenderset(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - if c.Query("productName") == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") - return - } - - if c.Query("envName") == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") - return - } - - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) - } - - args := new(commonservice.RendersetArg) - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) - - ctx.Err = service.CreateOrUpdateRenderset(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) -} +//func CreateOrUpdateRenderChart(c *gin.Context) { +// ctx := internalhandler.NewContext(c) +// defer func() { internalhandler.JSONResponse(c, ctx) }() +// +// if c.Query("productName") == "" { +// ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") +// return +// } +// +// if c.Query("envName") == "" { +// ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") +// return +// } +// +// data, err := c.GetRawData() +// if err != nil { +// log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) +// } +// +// args := new(commonservice.RenderChartArg) +// if err = json.Unmarshal(data, args); err != nil { +// log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) +// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) +// } +// internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) +// +// ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) +//} + +//func CreateOrUpdateRenderset(c *gin.Context) { +// ctx := internalhandler.NewContext(c) +// defer func() { internalhandler.JSONResponse(c, ctx) }() +// +// if c.Query("productName") == "" { +// ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") +// return +// } +// +// if c.Query("envName") == "" { +// ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") +// return +// } +// +// data, err := c.GetRawData() +// if err != nil { +// log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) +// } +// +// args := new(commonservice.RendersetArg) +// if err = json.Unmarshal(data, args); err != nil { +// log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) +// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) +// return +// } +// internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) +// +// ctx.Err = service.CreateOrUpdateRenderset(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) +//} func GetYamlContent(c *gin.Context) { ctx := internalhandler.NewContext(c) @@ -157,29 +156,54 @@ func GetYamlContent(c *gin.Context) { pathArr := strings.Split(c.Query("valuesPaths"), ",") contentArr := make([][]byte, 0) - for _, path := range pathArr { - fileContent, err := fsservice.DownloadFileFromSource( - &fsservice.DownloadFromSourceArgs{ - CodehostID: codehostID, - Owner: c.Query("owner"), - Repo: c.Query("repo"), - Path: path, - Branch: c.Query("branch"), - RepoLink: c.Query("repoLink"), - }) - if err != nil { - ctx.Err = fmt.Errorf("failed to download file from path %s, err %s", path, err) - return - } - if len(fileContent) > 0 { - contentArr = append(contentArr, fileContent) - } + + var ( + fileContentMap sync.Map + wg sync.WaitGroup + owner = c.Query("owner") + repo = c.Query("repo") + branch = c.Query("branch") + repoLink = c.Query("repoLink") + ) + + for index, filePath := range pathArr { + wg.Add(1) + go func(index int, path string) { + defer wg.Done() + fileContent, err1 := fsservice.DownloadFileFromSource( + &fsservice.DownloadFromSourceArgs{ + CodehostID: codehostID, + Owner: owner, + Repo: repo, + Path: path, + Branch: branch, + RepoLink: repoLink, + }) + if err1 != nil { + err = errors.Errorf("fail to download file from git, err: %s, path: %s", err1.Error(), path) + return + } + fileContentMap.Store(index, fileContent) + }(index, filePath) + } + wg.Wait() + + if err != nil { + ctx.Err = err + return } + allValueYamls := make([][]byte, len(pathArr), len(pathArr)) + for i := 0; i < len(pathArr); i++ { + contentObj, _ := fileContentMap.Load(i) + allValueYamls[i] = contentObj.([]byte) + contentArr = append(contentArr, contentObj.([]byte)) + } ret, err := yamlutil.Merge(contentArr) if err != nil { ctx.Err = fmt.Errorf("failed to merge files, err %s", err) return } + ctx.Resp = string(ret) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index fb527646..031143e5 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -135,7 +135,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) //TODO need to be deprecated - environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) + //environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) } // --------------------------------------------------------------------------------------- @@ -148,9 +148,9 @@ func (*Router) Inject(router *gin.RouterGroup) { rendersets.GET("/yamlContent", GetYamlContent) //TODO need to deprecated - rendersets.PUT("/renderset", CreateOrUpdateRenderset) + //rendersets.PUT("/renderset", CreateOrUpdateRenderset) //TODO need to deprecated - rendersets.PUT("/renderchart", CreateOrUpdateRenderChart) // create or update renderchart when onBoarding + //rendersets.PUT("/renderchart", CreateOrUpdateRenderChart) // create or update renderchart when onBoarding } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5d287516..19abba06 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1256,39 +1256,39 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string } // UpdateHelmProductRenderCharts TODO need to be deprecated -func UpdateHelmProductRenderCharts(productName, envName, userName, requestID string, args *EnvRenderChartArg, log *zap.SugaredLogger) error { - - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - - opt := &commonrepo.RenderSetFindOption{Name: renderSetName} - productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) - if err != nil || productRenderset == nil { - if err != nil { - log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) - } - return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) - } - - // render charts need to be updated - updatedRcs := make([]*template.RenderChart, 0) - - for _, requestRenderChart := range args.ChartValues { - // update renderset info - for _, curRenderChart := range productRenderset.ChartInfos { - if curRenderChart.ServiceName != requestRenderChart.ServiceName { - continue - } - if !checkOverrideValuesChange(curRenderChart, requestRenderChart) { - continue - } - requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) - updatedRcs = append(updatedRcs, curRenderChart) - break - } - } - - return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcs, productRenderset, log) -} +//func UpdateHelmProductRenderCharts(productName, envName, userName, requestID string, args *EnvRenderChartArg, log *zap.SugaredLogger) error { +// +// renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") +// +// opt := &commonrepo.RenderSetFindOption{Name: renderSetName} +// productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) +// if err != nil || productRenderset == nil { +// if err != nil { +// log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) +// } +// return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) +// } +// +// // render charts need to be updated +// updatedRcs := make([]*template.RenderChart, 0) +// +// for _, requestRenderChart := range args.ChartValues { +// // update renderset info +// for _, curRenderChart := range productRenderset.ChartInfos { +// if curRenderChart.ServiceName != requestRenderChart.ServiceName { +// continue +// } +// if !checkOverrideValuesChange(curRenderChart, requestRenderChart) { +// continue +// } +// requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) +// updatedRcs = append(updatedRcs, curRenderChart) +// break +// } +// } +// +// return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcs, productRenderset, log) +//} func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 5886c1d6..8bedec2c 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -19,21 +19,15 @@ package service import ( "fmt" "strings" - "sync" "github.com/pkg/errors" "go.uber.org/zap" - "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "github.com/koderover/zadig/pkg/setting" - e "github.com/koderover/zadig/pkg/tool/errors" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type DefaultValuesResp struct { @@ -102,222 +96,222 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def } // validate yaml content -func validateYamlContent(yamlContent string) error { - tMap := map[string]interface{}{} - if err := yaml.Unmarshal([]byte(yamlContent), &tMap); err != nil { - if err != nil { - return errors.New("yaml content illegal") - } - } - return nil -} - -func generateValuesYaml(yamlData *commonservice.YamlData, log *zap.SugaredLogger) error { - if yamlData == nil { - return nil - } - if yamlData.YamlSource == setting.ValuesYamlSourceFreeEdit { - return validateYamlContent(yamlData.ValuesYAML) - } else if yamlData.YamlSource == setting.ValuesYamlSourceGitRepo { - if yamlData.GitRepoConfig == nil { - return errors.New("invalid repo config") - } - var ( - allValues []byte - fileContentMap sync.Map - wg sync.WaitGroup - err error - ) - - for index, filePath := range yamlData.GitRepoConfig.ValuesPaths { - wg.Add(1) - go func(index int, path string) { - defer wg.Done() - fileContent, err1 := fsservice.DownloadFileFromSource( - &fsservice.DownloadFromSourceArgs{ - CodehostID: yamlData.GitRepoConfig.CodehostID, - Owner: yamlData.GitRepoConfig.Owner, - Repo: yamlData.GitRepoConfig.Repo, - Path: path, - Branch: yamlData.GitRepoConfig.Branch, - }) - if err1 != nil { - err = errors.Errorf("fail to download file from git, err: %s, path: %s, repo: %v", err1.Error(), path, *yamlData.GitRepoConfig) - return - } - fileContentMap.Store(index, fileContent) - }(index, filePath) - } - wg.Wait() - - if err != nil { - return err - } - - allValueYamls := make([][]byte, len(yamlData.GitRepoConfig.ValuesPaths), len(yamlData.GitRepoConfig.ValuesPaths)) - for i := 0; i < len(yamlData.GitRepoConfig.ValuesPaths); i++ { - contentObj, _ := fileContentMap.Load(i) - allValueYamls[i] = contentObj.([]byte) - } - allValues, err = yamlutil.Merge(allValueYamls) - if err != nil { - return errors.Errorf("failed to merge yaml files, repo: %v", *yamlData.GitRepoConfig) - } - yamlData.ValuesYAML = string(allValues) - } - return nil -} - -func CreateOrUpdateRenderset(productName, envName string, args *commonservice.RendersetArg, userName, requestID string, log *zap.SugaredLogger) error { - serviceName := args.ServiceName - - serviceOpt := &commonrepo.ServiceFindOption{ - ProductName: productName, - ServiceName: serviceName, - Type: setting.HelmDeployType, - } - serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) - } - if serviceObj == nil { - return e.ErrCreateRenderSet.AddDesc("service not found") - } - - if serviceObj.HelmChart == nil { - return e.ErrCreateRenderSet.AddDesc("missing helm chart info") - } - - if args.DefaultValues != nil && args.DefaultValues.YamlSource != "" { - err := generateValuesYaml(args.DefaultValues, log) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) - } - } - - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - - opt := &commonrepo.RenderSetFindOption{Name: renderSetName} - curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) - } - - if !found { - curRenderset = &commonmodels.RenderSet{ - Name: renderSetName, - EnvName: envName, - ProductTmpl: productName, - UpdateBy: userName, - IsDefault: false, - } - } - - //update or insert service values.yaml - if args.RenderChartArg != nil { - var targetChartInfo *template.RenderChart - for _, singleChart := range curRenderset.ChartInfos { - if singleChart.ServiceName != serviceName { - continue - } - args.RenderChartArg.FillRenderChartModel(singleChart, singleChart.ChartVersion) - targetChartInfo = singleChart - break - } - if targetChartInfo == nil { - targetChartInfo = new(template.RenderChart) - targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml - args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) - curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) - } - } - - //update environment's defaults.yaml - if args.DefaultValues != nil { - curRenderset.DefaultValues = args.DefaultValues.ValuesYAML - } else { - curRenderset.DefaultValues = "" - } - - //create new renderset with increased revision - err = commonservice.CreateHelmRenderSet( - curRenderset, - log, - ) - - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) - } - return err -} - -func CreateOrUpdateChartValues(productName, envName string, args *commonservice.RenderChartArg, userName, requestID string, log *zap.SugaredLogger) error { - - serviceName := args.ServiceName - - serviceOpt := &commonrepo.ServiceFindOption{ - ProductName: productName, - ServiceName: serviceName, - Type: setting.HelmDeployType, - } - serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) - } - if serviceObj == nil { - return e.ErrCreateRenderSet.AddDesc("service not found") - } - - if serviceObj.HelmChart == nil { - return e.ErrCreateRenderSet.AddDesc("missing helm chart info") - } - - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - - opt := &commonrepo.RenderSetFindOption{Name: renderSetName} - curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) - } - - if !found { - curRenderset = &commonmodels.RenderSet{ - Name: renderSetName, - EnvName: envName, - ProductTmpl: productName, - UpdateBy: userName, - IsDefault: false, - } - } - - //update or insert service values.yaml - var targetChartInfo *template.RenderChart - for _, singleChart := range curRenderset.ChartInfos { - if singleChart.ServiceName != serviceName { - continue - } - args.FillRenderChartModel(singleChart, singleChart.ChartVersion) - targetChartInfo = singleChart - break - } - if targetChartInfo == nil { - targetChartInfo = new(template.RenderChart) - targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml - args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) - curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) - } - - //create new renderset with increased revision - err = commonservice.CreateHelmRenderSet( - curRenderset, - log, - ) - - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) - } - return err -} +//func validateYamlContent(yamlContent string) error { +// tMap := map[string]interface{}{} +// if err := yaml.Unmarshal([]byte(yamlContent), &tMap); err != nil { +// if err != nil { +// return errors.New("yaml content illegal") +// } +// } +// return nil +//} + +//func generateValuesYaml(yamlData *commonservice.YamlData, log *zap.SugaredLogger) error { +// if yamlData == nil { +// return nil +// } +// if yamlData.YamlSource == setting.ValuesYamlSourceFreeEdit { +// return validateYamlContent(yamlData.ValuesYAML) +// } else if yamlData.YamlSource == setting.ValuesYamlSourceGitRepo { +// if yamlData.GitRepoConfig == nil { +// return errors.New("invalid repo config") +// } +// var ( +// allValues []byte +// fileContentMap sync.Map +// wg sync.WaitGroup +// err error +// ) +// +// for index, filePath := range yamlData.GitRepoConfig.ValuesPaths { +// wg.Add(1) +// go func(index int, path string) { +// defer wg.Done() +// fileContent, err1 := fsservice.DownloadFileFromSource( +// &fsservice.DownloadFromSourceArgs{ +// CodehostID: yamlData.GitRepoConfig.CodehostID, +// Owner: yamlData.GitRepoConfig.Owner, +// Repo: yamlData.GitRepoConfig.Repo, +// Path: path, +// Branch: yamlData.GitRepoConfig.Branch, +// }) +// if err1 != nil { +// err = errors.Errorf("fail to download file from git, err: %s, path: %s, repo: %v", err1.Error(), path, *yamlData.GitRepoConfig) +// return +// } +// fileContentMap.Store(index, fileContent) +// }(index, filePath) +// } +// wg.Wait() +// +// if err != nil { +// return err +// } +// +// allValueYamls := make([][]byte, len(yamlData.GitRepoConfig.ValuesPaths), len(yamlData.GitRepoConfig.ValuesPaths)) +// for i := 0; i < len(yamlData.GitRepoConfig.ValuesPaths); i++ { +// contentObj, _ := fileContentMap.Load(i) +// allValueYamls[i] = contentObj.([]byte) +// } +// allValues, err = yamlutil.Merge(allValueYamls) +// if err != nil { +// return errors.Errorf("failed to merge yaml files, repo: %v", *yamlData.GitRepoConfig) +// } +// yamlData.ValuesYAML = string(allValues) +// } +// return nil +//} + +//func CreateOrUpdateRenderset(productName, envName string, args *commonservice.RendersetArg, userName, requestID string, log *zap.SugaredLogger) error { +// serviceName := args.ServiceName +// +// serviceOpt := &commonrepo.ServiceFindOption{ +// ProductName: productName, +// ServiceName: serviceName, +// Type: setting.HelmDeployType, +// } +// serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) +// } +// if serviceObj == nil { +// return e.ErrCreateRenderSet.AddDesc("service not found") +// } +// +// if serviceObj.HelmChart == nil { +// return e.ErrCreateRenderSet.AddDesc("missing helm chart info") +// } +// +// if args.DefaultValues != nil && args.DefaultValues.YamlSource != "" { +// err := generateValuesYaml(args.DefaultValues, log) +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(err.Error()) +// } +// } +// +// renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") +// +// opt := &commonrepo.RenderSetFindOption{Name: renderSetName} +// curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) +// } +// +// if !found { +// curRenderset = &commonmodels.RenderSet{ +// Name: renderSetName, +// EnvName: envName, +// ProductTmpl: productName, +// UpdateBy: userName, +// IsDefault: false, +// } +// } +// +// //update or insert service values.yaml +// if args.RenderChartArg != nil { +// var targetChartInfo *template.RenderChart +// for _, singleChart := range curRenderset.ChartInfos { +// if singleChart.ServiceName != serviceName { +// continue +// } +// args.RenderChartArg.FillRenderChartModel(singleChart, singleChart.ChartVersion) +// targetChartInfo = singleChart +// break +// } +// if targetChartInfo == nil { +// targetChartInfo = new(template.RenderChart) +// targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml +// args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) +// curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) +// } +// } +// +// //update environment's defaults.yaml +// if args.DefaultValues != nil { +// curRenderset.DefaultValues = args.DefaultValues.ValuesYAML +// } else { +// curRenderset.DefaultValues = "" +// } +// +// //create new renderset with increased revision +// err = commonservice.CreateHelmRenderSet( +// curRenderset, +// log, +// ) +// +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(err.Error()) +// } +// return err +//} + +//func CreateOrUpdateChartValues(productName, envName string, args *commonservice.RenderChartArg, userName, requestID string, log *zap.SugaredLogger) error { +// +// serviceName := args.ServiceName +// +// serviceOpt := &commonrepo.ServiceFindOption{ +// ProductName: productName, +// ServiceName: serviceName, +// Type: setting.HelmDeployType, +// } +// serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) +// } +// if serviceObj == nil { +// return e.ErrCreateRenderSet.AddDesc("service not found") +// } +// +// if serviceObj.HelmChart == nil { +// return e.ErrCreateRenderSet.AddDesc("missing helm chart info") +// } +// +// renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") +// +// opt := &commonrepo.RenderSetFindOption{Name: renderSetName} +// curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) +// } +// +// if !found { +// curRenderset = &commonmodels.RenderSet{ +// Name: renderSetName, +// EnvName: envName, +// ProductTmpl: productName, +// UpdateBy: userName, +// IsDefault: false, +// } +// } +// +// //update or insert service values.yaml +// var targetChartInfo *template.RenderChart +// for _, singleChart := range curRenderset.ChartInfos { +// if singleChart.ServiceName != serviceName { +// continue +// } +// args.FillRenderChartModel(singleChart, singleChart.ChartVersion) +// targetChartInfo = singleChart +// break +// } +// if targetChartInfo == nil { +// targetChartInfo = new(template.RenderChart) +// targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml +// args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) +// curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) +// } +// +// //create new renderset with increased revision +// err = commonservice.CreateHelmRenderSet( +// curRenderset, +// log, +// ) +// +// if err != nil { +// return e.ErrCreateRenderSet.AddDesc(err.Error()) +// } +// return err +//} func ensureHelmRenderSetArgs(args *commonmodels.RenderSet) error { if args == nil { -- Gitee From d280c24002b6c65f1c9c4b82906e24d60ad93d5b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 14:03:12 +0800 Subject: [PATCH 0147/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index aaa2eb5a..be9cb746 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -21,6 +21,7 @@ import ( "strings" "sync" + "github.com/koderover/zadig/pkg/tool/log" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" @@ -127,6 +128,8 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) return nil, err } + log.Infof("defaultURL:%s", defaultURL) + for _, serviceName := range serviceNames { newServiceName := serviceName wg.Start(func() { -- Gitee From 050e7402ed4c39b10adb3ce7e5107afc80e73fb2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 14:05:01 +0800 Subject: [PATCH 0148/2647] edit role Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 919686ea..ca33ac64 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -58,6 +58,8 @@ func UpdateRole(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") return } + name := c.Param("name") + args.Name = name ctx.Err = service.UpdateRole(projectName, args, ctx.Logger) } @@ -71,7 +73,8 @@ func UpdatePublicRole(c *gin.Context) { ctx.Err = err return } - + name := c.Param("name") + args.Name = name ctx.Err = service.UpdatePublicRole(args, ctx.Logger) } -- Gitee From 0c5713915dadca82421ff77295a3ca10ca98fc10 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 14:11:42 +0800 Subject: [PATCH 0149/2647] modify artifact interface Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 2 +- .../aslan/core/workflow/service/workflow/nsq_handlers.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index be9cb746..5fac6723 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -137,7 +137,7 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) Name: newServiceName, Type: kind, Source: string(config.WorkflowType), - PackageStorageURI: defaultURL, + PackageStorageURI: id, }) if err != nil { logger.Errorf("ListTars err:%s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 029dd499..19d378c3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -244,9 +244,11 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifact.ImageTag = buildInfo.JobCtx.FileArchiveCtx.FileName deliveryArtifact.Type = string(config.File) deliveryArtifact.PackageFileLocation = buildInfo.JobCtx.FileArchiveCtx.FileLocation - deliveryArtifact.PackageStorageURI = pt.StorageURI - deliveryArtifactArray = append(deliveryArtifactArray, deliveryArtifact) + if storageInfo, err := s3.NewS3StorageFromEncryptedURI(pt.StorageURI); err == nil { + deliveryArtifact.PackageStorageURI = storageInfo.ID.Hex() + } + deliveryArtifactArray = append(deliveryArtifactArray, deliveryArtifact) } if buildInfo.ServiceType != setting.PMDeployType { // image deliveryArtifact := new(commonmodels.DeliveryArtifact) -- Gitee From 6fe1ad4d1fd36d9b9c6cdca68998fc2ec9860a1b Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 14:18:40 +0800 Subject: [PATCH 0150/2647] edit role Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 2 +- .../policy/core/handler/router.go | 4 ++-- pkg/microservice/policy/core/service/role.go | 20 +++---------------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index ca33ac64..86bcb70b 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -75,7 +75,7 @@ func UpdatePublicRole(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdatePublicRole(args, ctx.Logger) + ctx.Err = service.UpdateRole("", args, ctx.Logger) } func ListRoles(c *gin.Context) { diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 4a585e12..15ec1289 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,7 +26,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) - roles.PUT("", UpdateRole) + roles.PUT(":/name", UpdateRole) roles.GET("", ListRoles) roles.DELETE("/:name", DeleteRole) } @@ -35,7 +35,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { publicRoles.POST("", CreatePublicRole) publicRoles.GET("", ListPublicRoles) - publicRoles.PUT("", UpdatePublicRole) + publicRoles.PUT("/:name", UpdatePublicRole) publicRoles.DELETE("/:name", DeletePublicRole) } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 461110dc..aed0722b 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -64,24 +64,10 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { Resources: r.Resources, }) } - - return mongodb.NewRoleColl().UpdateProjectRole(obj) -} - -func UpdatePublicRole(role *Role, _ *zap.SugaredLogger) error { - obj := &models.Role{ - Name: role.Name, - Kind: role.Kind, - } - - for _, r := range role.Rules { - obj.Rules = append(obj.Rules, &models.Rule{ - Verbs: r.Verbs, - Resources: r.Resources, - }) + if ns == "" { + return mongodb.NewRoleColl().UpdatePublicRole(obj) } - - return mongodb.NewRoleColl().UpdatePublicRole(obj) + return mongodb.NewRoleColl().UpdateProjectRole(obj) } func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { -- Gitee From 5ee9d2749bd6edc804ea063873513973c33bc542 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 14:24:58 +0800 Subject: [PATCH 0151/2647] modify artifact interface Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 5 +---- .../aslan/core/workflow/service/workflow/nsq_handlers.go | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 5fac6723..076b0fd6 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -21,7 +21,6 @@ import ( "strings" "sync" - "github.com/koderover/zadig/pkg/tool/log" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" @@ -128,8 +127,6 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) return nil, err } - log.Infof("defaultURL:%s", defaultURL) - for _, serviceName := range serviceNames { newServiceName := serviceName wg.Start(func() { @@ -137,7 +134,7 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) Name: newServiceName, Type: kind, Source: string(config.WorkflowType), - PackageStorageURI: id, + PackageStorageURI: store.Endpoint + "/" + store.Bucket, }) if err != nil { logger.Errorf("ListTars err:%s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 19d378c3..2b72a867 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -245,10 +245,11 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifact.Type = string(config.File) deliveryArtifact.PackageFileLocation = buildInfo.JobCtx.FileArchiveCtx.FileLocation if storageInfo, err := s3.NewS3StorageFromEncryptedURI(pt.StorageURI); err == nil { - deliveryArtifact.PackageStorageURI = storageInfo.ID.Hex() + deliveryArtifact.PackageStorageURI = storageInfo.Endpoint + "/" + storageInfo.Bucket } deliveryArtifactArray = append(deliveryArtifactArray, deliveryArtifact) + } if buildInfo.ServiceType != setting.PMDeployType { // image deliveryArtifact := new(commonmodels.DeliveryArtifact) -- Gitee From 33a7c2f70e65a3ea4804b2753e409e7e8e05f1c9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 14:25:26 +0800 Subject: [PATCH 0152/2647] edit role Signed-off-by: mouuii --- .../policy/core/repository/mongodb/role.go | 16 +--------------- pkg/microservice/policy/core/service/role.go | 5 +---- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index f67e3106..5ecff450 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -129,7 +129,7 @@ func (c *RoleColl) Delete(name string, projectName string) error { return err } -func (c *RoleColl) UpdateProjectRole(obj *models.Role) error { +func (c *RoleColl) UpdateRole(obj *models.Role) error { // avoid panic issue if obj == nil { return errors.New("nil Role") @@ -142,17 +142,3 @@ func (c *RoleColl) UpdateProjectRole(obj *models.Role) error { _, err := c.UpdateOne(context.TODO(), query, change) return err } - -func (c *RoleColl) UpdatePublicRole(obj *models.Role) error { - // avoid panic issue - if obj == nil { - return errors.New("nil Role") - } - - query := bson.M{"name": obj.Name, "namespace": ""} - change := bson.M{"$set": bson.M{ - "rules": obj.Rules, - }} - _, err := c.UpdateOne(context.TODO(), query, change) - return err -} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index aed0722b..c5574247 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -64,10 +64,7 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { Resources: r.Resources, }) } - if ns == "" { - return mongodb.NewRoleColl().UpdatePublicRole(obj) - } - return mongodb.NewRoleColl().UpdateProjectRole(obj) + return mongodb.NewRoleColl().UpdateRole(obj) } func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { -- Gitee From aed52b0cc027e7dd4d504c995cd8f5e7ad232598 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 22 Oct 2021 15:31:44 +0800 Subject: [PATCH 0153/2647] add config service Signed-off-by: lou --- Makefile | 2 +- cmd/systemconfig/main.go | 38 ++++++ docker/service/config.Dockerfile | 11 ++ go.mod | 6 + go.sum | 18 ++- pkg/microservice/policy/server/rest/router.go | 6 +- .../systemconfig/config/config.go | 23 ++++ .../systemconfig/config/consts.go | 8 ++ .../systemconfig/core/handler/connector.go | 72 +++++++++++ .../systemconfig/core/handler/router.go | 34 +++++ .../core/repository/models/connector.go | 37 ++++++ .../core/repository/orm/connector.go | 82 ++++++++++++ pkg/microservice/systemconfig/core/service.go | 39 ++++++ .../systemconfig/core/service/connector.go | 117 ++++++++++++++++++ .../systemconfig/core/service/types.go | 61 +++++++++ .../systemconfig/server/rest/router.go | 35 ++++++ .../systemconfig/server/rest/server.go | 76 ++++++++++++ .../systemconfig/server/server.go | 60 +++++++++ pkg/tool/gorm/client.go | 72 +++++++++++ 19 files changed, 792 insertions(+), 5 deletions(-) create mode 100644 cmd/systemconfig/main.go create mode 100644 docker/service/config.Dockerfile create mode 100644 pkg/microservice/systemconfig/config/config.go create mode 100644 pkg/microservice/systemconfig/config/consts.go create mode 100644 pkg/microservice/systemconfig/core/handler/connector.go create mode 100644 pkg/microservice/systemconfig/core/handler/router.go create mode 100644 pkg/microservice/systemconfig/core/repository/models/connector.go create mode 100644 pkg/microservice/systemconfig/core/repository/orm/connector.go create mode 100644 pkg/microservice/systemconfig/core/service.go create mode 100644 pkg/microservice/systemconfig/core/service/connector.go create mode 100644 pkg/microservice/systemconfig/core/service/types.go create mode 100644 pkg/microservice/systemconfig/server/rest/router.go create mode 100644 pkg/microservice/systemconfig/server/rest/server.go create mode 100644 pkg/microservice/systemconfig/server/server.go create mode 100644 pkg/tool/gorm/client.go diff --git a/Makefile b/Makefile index d46ca9fb..24bf0826 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc VERSION ?= $(shell date +'%Y%m%d%H%M%S') -TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy picket +TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy picket config REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) diff --git a/cmd/systemconfig/main.go b/cmd/systemconfig/main.go new file mode 100644 index 00000000..90b61cc3 --- /dev/null +++ b/cmd/systemconfig/main.go @@ -0,0 +1,38 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "log" + "os/signal" + "syscall" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/server" +) + +func main() { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + go func() { + <-ctx.Done() + stop() + }() + + if err := server.Serve(ctx); err != nil { + log.Fatal(err) + } +} diff --git a/docker/service/config.Dockerfile b/docker/service/config.Dockerfile new file mode 100644 index 00000000..83a3f2ff --- /dev/null +++ b/docker/service/config.Dockerfile @@ -0,0 +1,11 @@ +#golang.Dockerfile + +RUN go build -v -o /config ./cmd/systemconfig/main.go + +#alpine.Dockerfile + +WORKDIR /app + +COPY --from=build /config . + +ENTRYPOINT ["/app/config"] diff --git a/go.mod b/go.mod index 76bc8f27..ad4bc1c2 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 github.com/coocood/freecache v1.1.0 + github.com/dexidp/dex v2.13.0+incompatible github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible github.com/docker/go-connections v0.4.0 @@ -38,6 +39,7 @@ require ( github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/mittwald/go-helm-client v0.8.0 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect @@ -68,9 +70,13 @@ require ( golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/tools v0.1.5 // indirect + gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect + gopkg.in/ldap.v2 v2.5.1 // indirect gopkg.in/mholt/archiver.v3 v3.1.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + gorm.io/driver/mysql v1.1.2 + gorm.io/gorm v1.21.16 helm.sh/helm/v3 v3.6.2 k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 diff --git a/go.sum b/go.sum index bc09eeb7..e8ae4d70 100644 --- a/go.sum +++ b/go.sum @@ -193,6 +193,8 @@ github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1 github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dexidp/dex v2.13.0+incompatible h1:EQPpzCi51omkwBe0KYpRGaV3rk6CVvjcqeMGCe3Q00w= +github.com/dexidp/dex v2.13.0+incompatible/go.mod h1:cRGkPWqKhDD1FMCICe2JbYDdVR2xGLa38F6iuH/jNAs= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -343,8 +345,9 @@ github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKY github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -544,6 +547,8 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -595,6 +600,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -1343,6 +1350,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1360,6 +1369,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= +gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= gopkg.in/mholt/archiver.v3 v3.1.1 h1:sLb5X5fPPc9oU6YatdC+vbY46irElT3kPR0wOS7bjR4= gopkg.in/mholt/archiver.v3 v3.1.1/go.mod h1:EwJV1tr6GQdNZ5IFFVakh0aYAjGLVjqvFVL/b8ZVdMw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= @@ -1383,6 +1394,11 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= +gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs= +gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/pkg/microservice/policy/server/rest/router.go b/pkg/microservice/policy/server/rest/router.go index 2115e654..d9fb530c 100644 --- a/pkg/microservice/policy/server/rest/router.go +++ b/pkg/microservice/policy/server/rest/router.go @@ -23,10 +23,10 @@ import ( ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { - for name, r := range map[string]injector{ - "/api/v1": new(handler.Router), + for _, r := range []injector{ + new(handler.Router), } { - r.Inject(router.Group(name)) + r.Inject(router.Group("/api/v1")) } } diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go new file mode 100644 index 00000000..821936f8 --- /dev/null +++ b/pkg/microservice/systemconfig/config/config.go @@ -0,0 +1,23 @@ +package config + +import ( + "github.com/spf13/viper" + + _ "github.com/koderover/zadig/pkg/config" +) + +func DexMysqlDB() string { + return viper.GetString(ENVDexMysqlDB) +} + +func DexMysqlHost() string { + return viper.GetString(ENVDexMysqlHost) +} + +func DexMysqlUser() string { + return viper.GetString(ENVDexMysqlUser) +} + +func DexMysqlPassword() string { + return viper.GetString(ENVDexMysqlPassword) +} diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go new file mode 100644 index 00000000..23453c57 --- /dev/null +++ b/pkg/microservice/systemconfig/config/consts.go @@ -0,0 +1,8 @@ +package config + +const ( + ENVDexMysqlDB = "DEX_MYSQL_DB" + ENVDexMysqlHost = "DEX_MYSQL_HOST" + ENVDexMysqlUser = "DEX_MYSQL_USER" + ENVDexMysqlPassword = "DEX_MYSQL_PASSWORD" +) diff --git a/pkg/microservice/systemconfig/core/handler/connector.go b/pkg/microservice/systemconfig/core/handler/connector.go new file mode 100644 index 00000000..95e475db --- /dev/null +++ b/pkg/microservice/systemconfig/core/handler/connector.go @@ -0,0 +1,72 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateConnector(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Connector{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.CreateConnector(args, ctx.Logger) +} + +func ListConnectors(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListConnectors(ctx.Logger) +} + +func DeleteConnector(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = service.DeleteConnector(c.Param("id"), ctx.Logger) +} + +func GetConnector(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetConnector(c.Param("id"), ctx.Logger) +} + +func UpdateConnector(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Connector{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + args.ID = c.Param("id") + + ctx.Err = service.UpdateConnector(args, ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go new file mode 100644 index 00000000..b59de382 --- /dev/null +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -0,0 +1,34 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + roles := router.Group("connectors") + { + roles.POST("", CreateConnector) + roles.GET("", ListConnectors) + roles.GET("/:id", GetConnector) + roles.PUT("/:id", UpdateConnector) + roles.DELETE("/:id", DeleteConnector) + } +} diff --git a/pkg/microservice/systemconfig/core/repository/models/connector.go b/pkg/microservice/systemconfig/core/repository/models/connector.go new file mode 100644 index 00000000..42706039 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/models/connector.go @@ -0,0 +1,37 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "strconv" + +type Connector struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + ResourceVersion string `json:"resource_version"` + Config string `json:"config"` +} + +func (m *Connector) IncreaseResourceVersion() { + rv, _ := strconv.Atoi(m.ResourceVersion) + m.ResourceVersion = strconv.Itoa(rv + 1) +} + +// TableName sets the insert table name for this struct type +func (Connector) TableName() string { + return "connector" +} diff --git a/pkg/microservice/systemconfig/core/repository/orm/connector.go b/pkg/microservice/systemconfig/core/repository/orm/connector.go new file mode 100644 index 00000000..67de3de4 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/orm/connector.go @@ -0,0 +1,82 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package orm + +import ( + "gorm.io/gorm" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + gormtool "github.com/koderover/zadig/pkg/tool/gorm" +) + +type ConnectorColl struct { + *gorm.DB + + coll string +} + +func NewConnectorColl() *ConnectorColl { + name := models.Connector{}.TableName() + return &ConnectorColl{ + DB: gormtool.DB(), + coll: name, + } +} + +func (c *ConnectorColl) GetCollectionName() string { + return c.coll +} + +func (c *ConnectorColl) List() ([]*models.Connector, error) { + var res []*models.Connector + result := c.Find(&res) + + return res, result.Error +} + +func (c *ConnectorColl) Get(id string) (*models.Connector, error) { + res := &models.Connector{} + result := c.First(&res, id) + + return res, result.Error +} + +func (c *ConnectorColl) Create(obj *models.Connector) error { + obj.ResourceVersion = "1" + result := c.DB.Create(obj) + + return result.Error +} + +func (c *ConnectorColl) Update(obj *models.Connector) error { + current, err := c.Get(obj.ID) + if err != nil { + return err + } + + obj.ResourceVersion = current.ResourceVersion + obj.IncreaseResourceVersion() + result := c.DB.Save(obj) + + return result.Error +} + +func (c *ConnectorColl) Delete(id string) error { + result := c.DB.Delete(&models.Connector{}, id) + + return result.Error +} diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go new file mode 100644 index 00000000..fae2df93 --- /dev/null +++ b/pkg/microservice/systemconfig/core/service.go @@ -0,0 +1,39 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "context" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" + gormtool "github.com/koderover/zadig/pkg/tool/gorm" + "github.com/koderover/zadig/pkg/tool/log" +) + +func Start(_ context.Context) { + log.Init(&log.Config{ + Level: config.LogLevel(), + Filename: config.LogFile(), + SendToFile: config.SendLogToFile(), + Development: config.Mode() != setting.ReleaseMode, + }) +} + +func Stop(_ context.Context) { + gormtool.Close() +} diff --git a/pkg/microservice/systemconfig/core/service/connector.go b/pkg/microservice/systemconfig/core/service/connector.go new file mode 100644 index 00000000..03e3be1e --- /dev/null +++ b/pkg/microservice/systemconfig/core/service/connector.go @@ -0,0 +1,117 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/orm" +) + +func ListConnectors(logger *zap.SugaredLogger) ([]*Connector, error) { + cs, err := orm.NewConnectorColl().List() + if err != nil { + logger.Errorf("Failed to list connectors, err: %s", err) + return nil, err + } + + var res []*Connector + for _, c := range cs { + cf := make(map[string]interface{}) + err = json.Unmarshal([]byte(c.Config), cf) + if err != nil { + logger.Warnf("Failed to unmarshal config, err: %s", err) + continue + } + res = append(res, &Connector{ + ConnectorBase: ConnectorBase{ + Type: ConnectorType(c.Type), + }, + ID: c.ID, + Name: c.Name, + Config: cf, + }) + } + + return res, nil +} + +func GetConnector(id string, logger *zap.SugaredLogger) (*Connector, error) { + c, err := orm.NewConnectorColl().Get(id) + if err != nil { + logger.Errorf("Failed to get connector %s, err: %s", id, err) + return nil, err + } + + cf := make(map[string]interface{}) + err = json.Unmarshal([]byte(c.Config), cf) + if err != nil { + logger.Warnf("Failed to unmarshal config, err: %s", err) + + } + + return &Connector{ + ConnectorBase: ConnectorBase{ + Type: ConnectorType(c.Type), + }, + ID: c.ID, + Name: c.Name, + Config: cf, + }, nil + +} + +func DeleteConnector(id string, _ *zap.SugaredLogger) error { + return orm.NewConnectorColl().Delete(id) +} + +func CreateConnector(ct *Connector, logger *zap.SugaredLogger) error { + cf, err := json.Marshal(ct.Config) + if err != nil { + logger.Errorf("Failed to marshal config, err: %s", err) + return err + } + + obj := &models.Connector{ + ID: ct.ID, + Name: ct.Name, + Type: string(ct.Type), + Config: string(cf), + } + + return orm.NewConnectorColl().Create(obj) +} + +func UpdateConnector(ct *Connector, logger *zap.SugaredLogger) error { + cf, err := json.Marshal(ct.Config) + if err != nil { + logger.Errorf("Failed to marshal config, err: %s", err) + return err + } + + obj := &models.Connector{ + ID: ct.ID, + Name: ct.Name, + Type: string(ct.Type), + Config: string(cf), + } + + return orm.NewConnectorColl().Update(obj) +} diff --git a/pkg/microservice/systemconfig/core/service/types.go b/pkg/microservice/systemconfig/core/service/types.go new file mode 100644 index 00000000..dd87a95f --- /dev/null +++ b/pkg/microservice/systemconfig/core/service/types.go @@ -0,0 +1,61 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + + "github.com/dexidp/dex/connector/github" + "github.com/dexidp/dex/connector/ldap" +) + +type ConnectorType string + +const ( + TypeLDAP ConnectorType = "ldap" + TypeGitHub ConnectorType = "github" +) + +type Connector struct { + ConnectorBase + + ID string `json:"id"` + Name string `json:"name"` + Config interface{} `json:"config"` +} + +type ConnectorBase struct { + Type ConnectorType `json:"type"` +} + +func (c *Connector) UnmarshalJSON(data []byte) error { + cb := &ConnectorBase{} + if err := json.Unmarshal(data, cb); err != nil { + return err + } + + switch cb.Type { + case TypeLDAP: + c.Config = &ldap.Config{} + case TypeGitHub: + c.Config = &github.Config{} + } + + type tmp Connector + + return json.Unmarshal(data, (*tmp)(c)) +} diff --git a/pkg/microservice/systemconfig/server/rest/router.go b/pkg/microservice/systemconfig/server/rest/router.go new file mode 100644 index 00000000..c043ab8e --- /dev/null +++ b/pkg/microservice/systemconfig/server/rest/router.go @@ -0,0 +1,35 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/handler" +) + +func (s *engine) injectRouterGroup(router *gin.RouterGroup) { + for _, r := range []injector{ + new(handler.Router), + } { + r.Inject(router.Group("/api/v1")) + } +} + +type injector interface { + Inject(router *gin.RouterGroup) +} diff --git a/pkg/microservice/systemconfig/server/rest/server.go b/pkg/microservice/systemconfig/server/rest/server.go new file mode 100644 index 00000000..18843b0a --- /dev/null +++ b/pkg/microservice/systemconfig/server/rest/server.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/config" + ginmiddleware "github.com/koderover/zadig/pkg/middleware/gin" + "github.com/koderover/zadig/pkg/tool/log" +) + +type engine struct { + *gin.Engine + + mode string +} + +func NewEngine() *engine { + s := &engine{mode: config.Mode()} + + gin.SetMode(s.mode) + + s.injectMiddlewares() + s.injectRouters() + + return s +} + +func (s *engine) injectMiddlewares() { + g := gin.New() + defer func() { + s.Engine = g + }() + + if s.mode == gin.TestMode { + return + } + g.Use(ginmiddleware.Response()) + g.Use(ginmiddleware.RequestID()) + g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) + g.Use(gin.Recovery()) +} + +func (s *engine) injectRouters() { + g := s.Engine + + g.NoRoute(func(c *gin.Context) { + c.String(http.StatusNotFound, "Invalid path: %s", c.Request.URL.Path) + }) + g.HandleMethodNotAllowed = true + g.NoMethod(func(c *gin.Context) { + c.String(http.StatusMethodNotAllowed, "Method not allowed: %s %s", c.Request.Method, c.Request.URL.Path) + }) + + apiRouters := g.Group("") + s.injectRouterGroup(apiRouters) + + s.Engine = g +} diff --git a/pkg/microservice/systemconfig/server/server.go b/pkg/microservice/systemconfig/server/server.go new file mode 100644 index 00000000..cd8efc7a --- /dev/null +++ b/pkg/microservice/systemconfig/server/server.go @@ -0,0 +1,60 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package server + +import ( + "context" + "net/http" + "time" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core" + "github.com/koderover/zadig/pkg/microservice/systemconfig/server/rest" + "github.com/koderover/zadig/pkg/tool/log" +) + +func Serve(ctx context.Context) error { + core.Start(ctx) + defer core.Stop(ctx) + + log.Info("Start system config service") + + engine := rest.NewEngine() + server := &http.Server{Addr: ":80", Handler: engine} + + stopChan := make(chan struct{}) + go func() { + defer close(stopChan) + + <-ctx.Done() + + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Errorf("Failed to stop server, error: %s", err) + } + }() + + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Errorf("Failed to start http server, error: %s", err) + return err + } + + <-stopChan + + return nil +} diff --git a/pkg/tool/gorm/client.go b/pkg/tool/gorm/client.go new file mode 100644 index 00000000..f9ca8c03 --- /dev/null +++ b/pkg/tool/gorm/client.go @@ -0,0 +1,72 @@ +package gorm + +import ( + "fmt" + "sync" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/tool/log" +) + +type Database int + +const ( + Dex Database = iota + 1 +) + +var connections = make(map[Database]*gorm.DB, 1) +var once sync.Once + +func SetExternalConnection(conn *gorm.DB) { + once.Do(func() { + connections[Dex] = conn + }) +} + +func getConnection(db Database) *gorm.DB { + once.Do(func() { + connections[Dex] = initDex() + }) + + return connections[db] +} + +func initDex() *gorm.DB { + return openDB( + config.DexMysqlUser(), + config.DexMysqlPassword(), + config.DexMysqlHost(), + config.DexMysqlDB(), + ) +} + +func DB(db ...Database) *gorm.DB { + if len(db) == 0 { + return getConnection(Dex) + } + return getConnection(db[0]) +} + +func Close() { + //for _, conn := range connections { + // conn.Close() + //} +} + +func openDB(username, password, host, db string) *gorm.DB { + + // refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details + dsn := fmt.Sprintf( + "%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", + username, password, host, db, + ) + conn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + log.Panicf("Can not open db %s, err: %s", db, err) + } + + return conn +} -- Gitee From 9a8a5b5f764f200d47f8661beaf5c783ae536c28 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 22 Oct 2021 16:55:12 +0800 Subject: [PATCH 0154/2647] improve db connection Signed-off-by: lou --- .../core/repository/orm/connector.go | 3 +- pkg/microservice/systemconfig/core/service.go | 24 +++++++-- pkg/tool/gorm/client.go | 54 +++++-------------- 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/orm/connector.go b/pkg/microservice/systemconfig/core/repository/orm/connector.go index 67de3de4..a51b858d 100644 --- a/pkg/microservice/systemconfig/core/repository/orm/connector.go +++ b/pkg/microservice/systemconfig/core/repository/orm/connector.go @@ -19,6 +19,7 @@ package orm import ( "gorm.io/gorm" + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" gormtool "github.com/koderover/zadig/pkg/tool/gorm" ) @@ -32,7 +33,7 @@ type ConnectorColl struct { func NewConnectorColl() *ConnectorColl { name := models.Connector{}.TableName() return &ConnectorColl{ - DB: gormtool.DB(), + DB: gormtool.DB(config.DexMysqlDB()), coll: name, } } diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index fae2df93..a2e7b91a 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -19,7 +19,8 @@ package core import ( "context" - "github.com/koderover/zadig/pkg/config" + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" "github.com/koderover/zadig/pkg/tool/log" @@ -27,11 +28,24 @@ import ( func Start(_ context.Context) { log.Init(&log.Config{ - Level: config.LogLevel(), - Filename: config.LogFile(), - SendToFile: config.SendLogToFile(), - Development: config.Mode() != setting.ReleaseMode, + Level: configbase.LogLevel(), + Filename: configbase.LogFile(), + SendToFile: configbase.SendLogToFile(), + Development: configbase.Mode() != setting.ReleaseMode, }) + + initDatabase() +} + +func initDatabase() { + err := gormtool.Open(config.DexMysqlUser(), + config.DexMysqlPassword(), + config.DexMysqlHost(), + config.DexMysqlDB(), + ) + if err != nil { + log.Panicf("Failed to open database %s", config.DexMysqlDB()) + } } func Stop(_ context.Context) { diff --git a/pkg/tool/gorm/client.go b/pkg/tool/gorm/client.go index f9ca8c03..aea8c5b7 100644 --- a/pkg/tool/gorm/client.go +++ b/pkg/tool/gorm/client.go @@ -2,52 +2,30 @@ package gorm import ( "fmt" - "sync" "gorm.io/driver/mysql" "gorm.io/gorm" - - "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/tool/log" -) - -type Database int - -const ( - Dex Database = iota + 1 ) -var connections = make(map[Database]*gorm.DB, 1) -var once sync.Once +var connections = make(map[string]*gorm.DB) -func SetExternalConnection(conn *gorm.DB) { - once.Do(func() { - connections[Dex] = conn - }) +func getConnection(db string) *gorm.DB { + return connections[db] } -func getConnection(db Database) *gorm.DB { - once.Do(func() { - connections[Dex] = initDex() - }) +func Open(username, password, host, db string) error { + conn, err := openDB(username, password, host, db) + if err != nil { + return err + } - return connections[db] -} + connections[db] = conn -func initDex() *gorm.DB { - return openDB( - config.DexMysqlUser(), - config.DexMysqlPassword(), - config.DexMysqlHost(), - config.DexMysqlDB(), - ) + return nil } -func DB(db ...Database) *gorm.DB { - if len(db) == 0 { - return getConnection(Dex) - } - return getConnection(db[0]) +func DB(db string) *gorm.DB { + return getConnection(db) } func Close() { @@ -56,17 +34,13 @@ func Close() { //} } -func openDB(username, password, host, db string) *gorm.DB { +func openDB(username, password, host, db string) (*gorm.DB, error) { // refer https://github.com/go-sql-driver/mysql#dsn-data-source-name for details dsn := fmt.Sprintf( "%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, db, ) - conn, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) - if err != nil { - log.Panicf("Can not open db %s, err: %s", db, err) - } - return conn + return gorm.Open(mysql.Open(dsn), &gorm.Config{}) } -- Gitee From a9709561ea39366e2c94f60384c6b88d693a64e6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 17:33:07 +0800 Subject: [PATCH 0155/2647] add batch insert cvm host interface Signed-off-by: liu deyi --- .../core/common/repository/models/service.go | 1 + .../aslan/core/system/handler/private_key.go | 32 +++++++++++++++++++ .../aslan/core/system/handler/router.go | 2 ++ .../aslan/core/system/service/private_key.go | 22 +++++++++++++ 4 files changed, 57 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 3c1e8368..836873db 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -200,6 +200,7 @@ type EnvStatus struct { type EnvConfig struct { EnvName string `bson:"env_name,omitempty" json:"env_name"` HostIDs []string `bson:"host_ids,omitempty" json:"host_ids"` + Labels []string `bson:"labels,omitempty" json:"labels"` } type PmHealthCheck struct { diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index f2b32a18..b3db33ec 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -103,3 +103,35 @@ func DeletePrivateKey(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.Username, "", "删除", "资源管理-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.Logger) } + +func ListLabels(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListLabels(ctx.Logger) +} + +func BatchCreatePrivateKey(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.PrivateKey) + data, err := c.GetRawData() + if err != nil { + log.Errorf("batchCreatePrivateKey c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("batchCreatePrivateKey json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.Username, "", "批量新增", "资源管理-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid PrivateKey args") + return + } + args.UpdateBy = ctx.Username + + ctx.Err = service.BatchCreatePrivateKey(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index f85abb7d..155ffee3 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -153,7 +153,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { privateKey.GET("", ListPrivateKeys) privateKey.GET("/:id", GetPrivateKey) + privateKey.GET("/labels", ListLabels) privateKey.POST("", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, CreatePrivateKey) + privateKey.POST("/batch", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, BatchCreatePrivateKey) privateKey.PUT("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, UpdatePrivateKey) privateKey.DELETE("/:id", gin2.RequireSuperAdminAuth, gin2.UpdateOperationLogStatus, DeletePrivateKey) } diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index d34ca4a0..37e4fd92 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -87,3 +87,25 @@ func DeletePrivateKey(id string, log *zap.SugaredLogger) error { } return nil } + +func ListLabels(log *zap.SugaredLogger) ([]string, error) { + + return []string{}, nil +} + +func BatchCreatePrivateKey(args *commonmodels.PrivateKey, log *zap.SugaredLogger) error { + //if !config.CVMNameRegex.MatchString(args.Name) { + // return e.ErrCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + //} + // + //if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: args.Name}); len(privateKeys) > 0 { + // return e.ErrCreatePrivateKey.AddDesc("Name already exists") + //} + // + //err := commonrepo.NewPrivateKeyColl().Create(args) + //if err != nil { + // log.Errorf("PrivateKey.Create error: %v", err) + // return e.ErrCreatePrivateKey + //} + return nil +} -- Gitee From 9145fa27ec3e29f34479e85cb197cc7f460b6693 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 17:37:27 +0800 Subject: [PATCH 0156/2647] add kind Signed-off-by: mouuii --- pkg/microservice/policy/core/repository/models/role.go | 3 ++- .../policy/core/service/bundle/opa_bundle.go | 9 ++++----- pkg/microservice/policy/core/service/role.go | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 60279b11..2813821b 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -27,7 +27,7 @@ type Role struct { Name string `bson:"name" json:"name"` Namespace string `bson:"namespace" json:"namespace"` Rules []*Rule `bson:"rules" json:"rules"` - Kind string `bson:"kind" json:"kind"` + //Kind string `bson:"kind" json:"kind"` } // Rule holds information that describes a policy rule, but does not contain information @@ -39,6 +39,7 @@ type Rule struct { // Resources is a list of resources this rule applies to. '*' represents all resources. Resources []string `bson:"resources" json:"resources"` + Kind string `bson:"verbs" json:"verbs"` } func (Role) TableName() string { diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 3089d1c6..bf032b8e 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -192,14 +192,12 @@ func generateOPARoles(roles []*models.Role, policies []*models.Policy) *opaRoles for _, ro := range roles { opaRole := &role{Name: ro.Name, Namespace: ro.Namespace} - if ro.Kind == models.KindResource { - for _, r := range ro.Rules { + for _, r := range ro.Rules { + if r.Kind == models.KindResource { for _, res := range r.Resources { opaRole.Rules = append(opaRole.Rules, resourceMappings.GetRules(res, r.Verbs)...) } - } - } else { - for _, r := range ro.Rules { + } else { if len(r.Verbs) == 1 && r.Verbs[0] == models.MethodAll { r.Verbs = AllMethods } @@ -209,6 +207,7 @@ func generateOPARoles(roles []*models.Role, policies []*models.Policy) *opaRoles } } } + } sort.Sort(opaRole.Rules) diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index c5574247..861dcc8f 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -38,7 +38,6 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, Namespace: ns, - Kind: role.Kind, } for _, r := range role.Rules { @@ -55,7 +54,6 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, Namespace: ns, - Kind: role.Kind, } for _, r := range role.Rules { -- Gitee From fe1b461306d45cd4fb47b20a8aa4b5e7527f6810 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 17:54:11 +0800 Subject: [PATCH 0157/2647] add kind Signed-off-by: mouuii --- pkg/microservice/policy/core/repository/models/role.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 2813821b..30910828 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -27,7 +27,6 @@ type Role struct { Name string `bson:"name" json:"name"` Namespace string `bson:"namespace" json:"namespace"` Rules []*Rule `bson:"rules" json:"rules"` - //Kind string `bson:"kind" json:"kind"` } // Rule holds information that describes a policy rule, but does not contain information @@ -35,11 +34,11 @@ type Role struct { // If Kind is "resource", verbs are resource actions, while resources are resource names type Rule struct { // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. - Verbs []string `bson:"verbs" json:"verbs"` + Verbs []string `bson:"verbs" json:"verbs"` // Resources is a list of resources this rule applies to. '*' represents all resources. Resources []string `bson:"resources" json:"resources"` - Kind string `bson:"verbs" json:"verbs"` + Kind string `bson:"verbs" json:"verbs"` } func (Role) TableName() string { -- Gitee From f25cf216fdb0eb66d7443a9e7ca53e2e2697337e Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 22 Oct 2021 18:02:01 +0800 Subject: [PATCH 0158/2647] fix nil pointer Signed-off-by: lou --- pkg/microservice/aslan/core/common/service/gitlab/webhook.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go index 677f225e..cf6f7673 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go @@ -30,8 +30,11 @@ func (c *Client) CreateWebHook(owner, repo string) (string, error) { Secret: gitservice.GetHookSecret(), Events: []string{git.PushEvent, git.PullRequestEvent, git.BranchOrTagCreateEvent}, }) + if err != nil { + return "", err + } - return strconv.Itoa(projectHook.ID), err + return strconv.Itoa(projectHook.ID), nil } func (c *Client) DeleteWebHook(owner, repo, hookID string) error { -- Gitee From 876bdfdc52ddd1e6b19aca3e2a874ae0566eec44 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 22 Oct 2021 18:26:52 +0800 Subject: [PATCH 0159/2647] fix bug and change value merge orders Signed-off-by: allenshen --- .../core/environment/handler/environment.go | 2 +- .../core/environment/service/environment.go | 54 ++++++++++++------- .../aslan/core/environment/service/image.go | 2 +- .../core/service/taskplugin/deploy.go | 2 +- pkg/tool/helmclient/helmclient.go | 4 +- 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 3e8ae217..eabafab3 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -263,7 +263,7 @@ func EstimatedValues(c *gin.Context) { } arg := new(service.EstimateValuesArg) - if err := c.ShouldBindQuery(arg); err != nil { + if err := c.ShouldBind(arg); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 19abba06..f1ad87ff 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -166,9 +166,9 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar return nil } -func GetProductStatus(envType string, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, error) { //项目下所有公开环境 - publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true}) + publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true, Name: productName}) if err != nil { log.Errorf("Collection.Product.List List product error: %v", err) return nil, e.ErrListProducts.AddDesc(err.Error()) @@ -1154,21 +1154,18 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } -func GeneEstimatedValues(productName, envName, serviceName, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { - var opt *commonrepo.RenderSetFindOption - if len(envName) > 0 { - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - opt = &commonrepo.RenderSetFindOption{Name: renderSetName} - } else { - opt = &commonrepo.RenderSetFindOption{Name: productName} - } +func findTargetChartInfo(rendersetName, serviceName string) (*templatemodels.RenderChart, error) { + opt := &commonrepo.RenderSetFindOption{Name: rendersetName} productRenderset, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { - log.Errorf("failed to get renderset, productName %s evnName %s", productName, envName) + log.Errorf("failed to get renderset, name %s", rendersetName) + return nil, err } if !existed { - return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find renderset, envName: %s", envName)) + //return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find renderset, envName: %s", envName)) + return nil, nil } + var targetChart *templatemodels.RenderChart for _, chartInfo := range productRenderset.ChartInfos { if chartInfo.ServiceName == serviceName { @@ -1176,11 +1173,34 @@ func GeneEstimatedValues(productName, envName, serviceName, format string, arg * break } } + return targetChart, nil +} + +func GeneEstimatedValues(productName, envName, serviceName, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { + var targetChart *templatemodels.RenderChart + var err error + if len(envName) > 0 { + renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") + targetChart, err = findTargetChartInfo(renderSetName, serviceName) + if err != nil { + return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to query chartInfo, serviceName: %s", serviceName)) + } + } + + // if chart info not exist in env.renderset, find form default renderset + if targetChart == nil { + targetChart, err = findTargetChartInfo(productName, serviceName) + if err != nil { + return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to query chartInfo, serviceName: %s", serviceName)) + } + } + if targetChart == nil { return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find chart info, serviceName: %s", serviceName)) } + tempArg := &commonservice.RenderChartArg{OverrideValues: arg.OverrideValues} - mergeValues, err := helmtool.MergeOverrideValues(arg.DefaultValues, targetChart.ValuesYaml, arg.OverrideYaml, tempArg.ToOverrideValueString()) + mergeValues, err := helmtool.MergeOverrideValues(targetChart.ValuesYaml, arg.DefaultValues, arg.OverrideYaml, tempArg.ToOverrideValueString()) if err != nil { return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to merge values, err %s", err)) } @@ -2546,7 +2566,7 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( } func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { - mergedValuesYaml, err := helmtool.MergeOverrideValues(defaultValues, renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err @@ -2739,10 +2759,8 @@ func updateProductGroup(username, productName, envName, updateType string, produ } } - for _, serviceGroup := range productResp.Services { - for _, service := range serviceGroup { - productTemplServiceMap[service.ServiceName] = service - } + for _, service := range productResp.GetServiceMap() { + productTemplServiceMap[service.ServiceName] = service } // 找到环境里面还存在但是服务编排里面已经删除的服务卸载掉 diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index ad371b1d..f44cc312 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -275,7 +275,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri targetChart.ValuesYaml = replacedValuesYaml // merge override values and kvs into service's yaml - mergedValuesYaml, err = helmtool.MergeOverrideValues(renderSet.DefaultValues, replacedValuesYaml, targetChart.GetOverrideYaml(), targetChart.OverrideValues) + mergedValuesYaml, err = helmtool.MergeOverrideValues(replacedValuesYaml, renderSet.DefaultValues, targetChart.GetOverrideYaml(), targetChart.OverrideValues) if err != nil { return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 30bcadb0..ae5b3c28 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -449,7 +449,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } // merge override values and kvs into service's yaml - mergedValuesYaml, err = helmtool.MergeOverrideValues(renderInfo.DefaultValues, serviceValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 6d482000..cca8e2fa 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -49,11 +49,11 @@ type KV struct { // MergeOverrideValues merge override yaml and override kvs // override yaml used for -f option // override kvs used for --set option -func MergeOverrideValues(defaultValues, valuesYaml, overrideYaml, overrideValues string) (string, error) { +func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { // merge files for helm -f option // precedence from low to high: defaultValues valuesYaml overrideYaml - valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(defaultValues), []byte(valuesYaml), []byte(overrideYaml)}) + valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(defaultValues), []byte(overrideYaml)}) if err != nil { return "", err } -- Gitee From 735c994e54aec0211f70c01c31a87132f7e32913 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 22 Oct 2021 18:54:40 +0800 Subject: [PATCH 0160/2647] add kind Signed-off-by: mouuii --- pkg/microservice/policy/core/service/role.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 861dcc8f..bf238143 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -32,6 +32,7 @@ type Role struct { type Rule struct { Verbs []string `json:"verbs"` Resources []string `json:"resources"` + Kind string `json:"kind"` } func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { @@ -43,6 +44,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { for _, r := range role.Rules { obj.Rules = append(obj.Rules, &models.Rule{ Verbs: r.Verbs, + Kind: r.Kind, Resources: r.Resources, }) } @@ -59,6 +61,7 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { for _, r := range role.Rules { obj.Rules = append(obj.Rules, &models.Rule{ Verbs: r.Verbs, + Kind: r.Kind, Resources: r.Resources, }) } -- Gitee From 0ae04bccd95f00ef420b9e601575f93dd406765d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 19:19:18 +0800 Subject: [PATCH 0161/2647] validate bulk add privatekey function Signed-off-by: liu deyi --- .../aslan/core/system/handler/private_key.go | 7 ++-- .../aslan/core/system/service/private_key.go | 33 +++++++++++-------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index b3db33ec..be9c0239 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -115,7 +115,7 @@ func BatchCreatePrivateKey(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(commonmodels.PrivateKey) + args := make([]*commonmodels.PrivateKey, 0) data, err := c.GetRawData() if err != nil { log.Errorf("batchCreatePrivateKey c.GetRawData() err : %v", err) @@ -123,7 +123,7 @@ func BatchCreatePrivateKey(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("batchCreatePrivateKey json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.Username, "", "批量新增", "资源管理-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.Username, "", "批量新增", "资源管理-主机管理", "", string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -131,7 +131,6 @@ func BatchCreatePrivateKey(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid PrivateKey args") return } - args.UpdateBy = ctx.Username - ctx.Err = service.BatchCreatePrivateKey(args, ctx.Logger) + ctx.Err = service.BatchCreatePrivateKey(args, ctx.Username, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 37e4fd92..f7d2e6b1 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -93,19 +93,24 @@ func ListLabels(log *zap.SugaredLogger) ([]string, error) { return []string{}, nil } -func BatchCreatePrivateKey(args *commonmodels.PrivateKey, log *zap.SugaredLogger) error { - //if !config.CVMNameRegex.MatchString(args.Name) { - // return e.ErrCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") - //} - // - //if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: args.Name}); len(privateKeys) > 0 { - // return e.ErrCreatePrivateKey.AddDesc("Name already exists") - //} - // - //err := commonrepo.NewPrivateKeyColl().Create(args) - //if err != nil { - // log.Errorf("PrivateKey.Create error: %v", err) - // return e.ErrCreatePrivateKey - //} +func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, username string, log *zap.SugaredLogger) error { + for _, currentPrivateKey := range args { + if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + return e.ErrCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + } + + if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { + if err := commonrepo.NewPrivateKeyColl().Update(privateKeys[0].ID.Hex(), currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrCreatePrivateKey + } + continue + } + + if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrCreatePrivateKey + } + } return nil } -- Gitee From 26f5a713d1597f24cb39b82e873d8c1861de3430 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 22 Oct 2021 19:57:53 +0800 Subject: [PATCH 0162/2647] add labels interface Signed-off-by: liu deyi --- .../common/repository/mongodb/private_key.go | 16 ++++++++++++++++ .../aslan/core/system/handler/private_key.go | 2 +- .../aslan/core/system/service/private_key.go | 11 +++++------ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index 639b7b73..91db6796 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -189,3 +189,19 @@ func (c *PrivateKeyColl) ListHostIPByIDs(ids []string) ([]*models.PrivateKey, er return resp, err } + +// DistinctLabels returns distinct label +func (c *PrivateKeyColl) DistinctLabels() ([]string, error) { + var resp []string + query := bson.M{} + ctx := context.Background() + labels, err := c.Collection.Distinct(ctx, "label", query) + + for _, labelInter := range labels { + if label, ok := labelInter.(string); ok { + resp = append(resp, label) + } + } + + return resp, err +} diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index be9c0239..67758ff7 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -108,7 +108,7 @@ func ListLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListLabels(ctx.Logger) + ctx.Resp, ctx.Err = service.ListLabels() } func BatchCreatePrivateKey(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index f7d2e6b1..8b6ca690 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -88,9 +88,8 @@ func DeletePrivateKey(id string, log *zap.SugaredLogger) error { return nil } -func ListLabels(log *zap.SugaredLogger) ([]string, error) { - - return []string{}, nil +func ListLabels() ([]string, error) { + return commonrepo.NewPrivateKeyColl().DistinctLabels() } func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, username string, log *zap.SugaredLogger) error { @@ -98,18 +97,18 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, username string, log if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { return e.ErrCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") } - + currentPrivateKey.UpdateBy = username if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { if err := commonrepo.NewPrivateKeyColl().Update(privateKeys[0].ID.Hex(), currentPrivateKey); err != nil { log.Errorf("PrivateKey.Create error: %s", err) - return e.ErrCreatePrivateKey + return e.ErrCreatePrivateKey.AddDesc("update failed") } continue } if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { log.Errorf("PrivateKey.Create error: %s", err) - return e.ErrCreatePrivateKey + return e.ErrCreatePrivateKey.AddDesc("add failed") } } return nil -- Gitee From 5c8bcae7907628b095d57a3b9996992e99b85e14 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 11:08:33 +0800 Subject: [PATCH 0163/2647] router bugfix Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 15ec1289..145d956b 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,7 +26,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) - roles.PUT(":/name", UpdateRole) + roles.PUT("/:name", UpdateRole) roles.GET("", ListRoles) roles.DELETE("/:name", DeleteRole) } -- Gitee From 86b2794a7c06dc800683d964dbbfd858c22734aa Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 25 Oct 2021 11:09:59 +0800 Subject: [PATCH 0164/2647] modify bulk add priviateKey interface Signed-off-by: liu deyi --- .../common/repository/models/private_key.go | 10 --- .../common/repository/mongodb/private_key.go | 5 ++ .../aslan/core/system/handler/private_key.go | 9 ++- .../aslan/core/system/service/private_key.go | 62 +++++++++++++++---- pkg/tool/errors/http_errors.go | 1 + 5 files changed, 63 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/private_key.go b/pkg/microservice/aslan/core/common/repository/models/private_key.go index f8e711bc..c54b9c26 100644 --- a/pkg/microservice/aslan/core/common/repository/models/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/models/private_key.go @@ -37,13 +37,3 @@ type PrivateKey struct { func (PrivateKey) TableName() string { return "private_key" } - -//type SSH struct { -// ID string `json:"id"` -// Name string `json:"name"` -// UserName string `json:"user_name"` -// IP string `json:"ip"` -// IsProd bool `json:"is_prod"` -// Label string `json:"label"` -// PrivateKey string `json:"private_key"` -//} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index 91db6796..e77e5f39 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -158,6 +158,11 @@ func (c *PrivateKeyColl) Delete(id string) error { return err } +func (c *PrivateKeyColl) DeleteAll() error { + _, err := c.DeleteMany(context.TODO(), bson.M{}) + return err +} + func (c *PrivateKeyColl) ListHostIPByIDs(ids []string) ([]*models.PrivateKey, error) { query := bson.M{} resp := make([]*models.PrivateKey, 0) diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 67758ff7..aef54436 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -111,11 +111,16 @@ func ListLabels(c *gin.Context) { ctx.Resp, ctx.Err = service.ListLabels() } +type privateKeyArgs struct { + Option string `json:"option"` + Data []*commonmodels.PrivateKey `json:"data"` +} + func BatchCreatePrivateKey(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := make([]*commonmodels.PrivateKey, 0) + args := new(privateKeyArgs) data, err := c.GetRawData() if err != nil { log.Errorf("batchCreatePrivateKey c.GetRawData() err : %v", err) @@ -132,5 +137,5 @@ func BatchCreatePrivateKey(c *gin.Context) { return } - ctx.Err = service.BatchCreatePrivateKey(args, ctx.Username, ctx.Logger) + ctx.Err = service.BatchCreatePrivateKey(args.Data, args.Option, ctx.Username, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 8b6ca690..34d48850 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -92,24 +92,62 @@ func ListLabels() ([]string, error) { return commonrepo.NewPrivateKeyColl().DistinctLabels() } -func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, username string, log *zap.SugaredLogger) error { - for _, currentPrivateKey := range args { - if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { - return e.ErrCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") +// override: Full coverage +// increment: Incremental coverage +// patch: Overwrite existing +func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username string, log *zap.SugaredLogger) error { + switch option { + case "override": + if err := commonrepo.NewPrivateKeyColl().DeleteAll(); err != nil { + return e.ErrBulkCreatePrivateKey.AddDesc("delete all privateKeys failed") } - currentPrivateKey.UpdateBy = username - if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { - if err := commonrepo.NewPrivateKeyColl().Update(privateKeys[0].ID.Hex(), currentPrivateKey); err != nil { + for _, currentPrivateKey := range args { + if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + } + currentPrivateKey.UpdateBy = username + if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") + } + } + case "increment": + for _, currentPrivateKey := range args { + if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + } + + if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { + continue + } + + currentPrivateKey.UpdateBy = username + if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { log.Errorf("PrivateKey.Create error: %s", err) - return e.ErrCreatePrivateKey.AddDesc("update failed") + return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") } - continue } - if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - log.Errorf("PrivateKey.Create error: %s", err) - return e.ErrCreatePrivateKey.AddDesc("add failed") + case "patch": + for _, currentPrivateKey := range args { + if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + } + currentPrivateKey.UpdateBy = username + if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { + if err := commonrepo.NewPrivateKeyColl().Update(privateKeys[0].ID.Hex(), currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrBulkCreatePrivateKey.AddDesc("bulk update privateKey failed") + } + continue + } + + if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") + } } } + return nil } diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 38c3a8ac..740a9708 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -583,6 +583,7 @@ var ( ErrListPrivateKeys = NewHTTPError(6684, "列出私钥失败") ErrDeletePrivateKey = NewHTTPError(6685, "删除私钥失败") ErrDeleteUsedPrivateKey = NewHTTPError(6686, "删除私钥失败,此私钥已经被引用,请确认") + ErrBulkCreatePrivateKey = NewHTTPError(6687, "批量创建私钥失败") //----------------------------------------------------------------------------------------------- // signature APIs Range: 6690 - 6699 -- Gitee From 69a200cd6fee4a200700157793de6a8c0c2b3cdd Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 11:15:44 +0800 Subject: [PATCH 0165/2647] bson bug Signed-off-by: mouuii --- pkg/microservice/policy/core/repository/models/role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 30910828..63c51034 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -38,7 +38,7 @@ type Rule struct { // Resources is a list of resources this rule applies to. '*' represents all resources. Resources []string `bson:"resources" json:"resources"` - Kind string `bson:"verbs" json:"verbs"` + Kind string `bson:"kind" json:"kind"` } func (Role) TableName() string { -- Gitee From 06fd7af04bcfd985769164382e71a8fc93d9953f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 11:55:03 +0800 Subject: [PATCH 0166/2647] remove kind Signed-off-by: mouuii --- pkg/microservice/policy/core/service/role.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index bf238143..8c40f0ae 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -25,7 +25,6 @@ import ( type Role struct { Name string `json:"name"` - Kind string `json:"name"` Rules []*Rule `json:"rules,omitempty"` } -- Gitee From 40cc85c576082d9dc74f3af01593a07133f2e0e4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 14:10:33 +0800 Subject: [PATCH 0167/2647] remove poetry client Signed-off-by: mouuii --- .../aslan/core/common/service/git/hook.go | 18 ++++---- pkg/shared/poetry/org.go | 46 ------------------- 2 files changed, 10 insertions(+), 54 deletions(-) delete mode 100644 pkg/shared/poetry/org.go diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index 6450ef31..a40b49d8 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -19,9 +19,7 @@ package git import ( "sync" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/shared/poetry" - "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/tool/crypto" ) var once sync.Once @@ -29,13 +27,17 @@ var secret string func GetHookSecret() string { once.Do(func() { - poetryClient := poetry.New(config.PoetryAPIServer()) - org, err := poetryClient.GetOrganization(poetry.DefaultOrganization) + //poetryClient := poetry.New(config.PoetryAPIServer()) + //org, err := poetryClient.GetOrganization(poetry.DefaultOrganization) + //if err != nil { + // log.Errorf("failed to find default organization: %v", err) + // secret = "--impossible-token--" + //} + token, err := crypto.AesEncrypt("hook") if err != nil { - log.Errorf("failed to find default organization: %v", err) - secret = "--impossible-token--" + panic("Failed to get token") } - secret = org.Token + secret = token }) return secret diff --git a/pkg/shared/poetry/org.go b/pkg/shared/poetry/org.go deleted file mode 100644 index 463a7c07..00000000 --- a/pkg/shared/poetry/org.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "fmt" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -const ( - DefaultOrganization = iota + 1 -) - -type Organization struct { - ID int `json:"id"` - Name string `json:"name"` - Token string `json:"orgToken"` - Website string `json:"website"` -} - -func (c *Client) GetOrganization(orgID int) (*Organization, error) { - url := fmt.Sprintf("/directory/organization/%d", orgID) - - org := &Organization{} - _, err := c.Get(url, httpclient.SetResult(org)) - if err != nil { - return nil, err - } - - return org, nil -} -- Gitee From 69d568a91d780821da1162d776fe38a9f2d6cca3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 14:12:05 +0800 Subject: [PATCH 0168/2647] remove poetry client Signed-off-by: mouuii --- pkg/microservice/aslan/core/common/service/git/hook.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index a40b49d8..f716d9a6 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -27,12 +27,6 @@ var secret string func GetHookSecret() string { once.Do(func() { - //poetryClient := poetry.New(config.PoetryAPIServer()) - //org, err := poetryClient.GetOrganization(poetry.DefaultOrganization) - //if err != nil { - // log.Errorf("failed to find default organization: %v", err) - // secret = "--impossible-token--" - //} token, err := crypto.AesEncrypt("hook") if err != nil { panic("Failed to get token") -- Gitee From d9f97a16615deeb3d150aee5c71ccf9e3527f821 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 25 Oct 2021 14:26:23 +0800 Subject: [PATCH 0169/2647] add host label by label Signed-off-by: liu deyi --- .../common/repository/mongodb/private_key.go | 26 +++++++++++++------ .../service/workflow/workflow_task.go | 22 +++++++++++++--- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index e77e5f39..20ab535a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -163,20 +163,30 @@ func (c *PrivateKeyColl) DeleteAll() error { return err } -func (c *PrivateKeyColl) ListHostIPByIDs(ids []string) ([]*models.PrivateKey, error) { +type ListHostIPArgs struct { + IDs []string `json:"ids"` + Labels []string `json:"labels"` +} + +func (c *PrivateKeyColl) ListHostIPByArgs(args *ListHostIPArgs) ([]*models.PrivateKey, error) { query := bson.M{} resp := make([]*models.PrivateKey, 0) ctx := context.Background() - var oids []primitive.ObjectID - for _, id := range ids { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return nil, err + if len(args.IDs) > 0 { + var oids []primitive.ObjectID + for _, id := range args.IDs { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + oids = append(oids, oid) } - oids = append(oids, oid) + query["_id"] = bson.M{"$in": oids} + } else if len(args.Labels) > 0 { + query["label"] = bson.M{"$in": args.Labels} } - query["_id"] = bson.M{"$in": oids} + opt := options.Find() selector := bson.D{ {"ip", 1}, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4fe6f552..4d3836c5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1615,14 +1615,20 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) for _, envConfig := range serviceTmpl.EnvConfigs { - privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByIDs(envConfig.HostIDs) + privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) if err != nil { - log.Errorf("ListNameByIDs err:%s", err) + log.Errorf("ListNameByArgs ids err:%s", err) continue } - for _, privateKey := range privateKeys { - envHost[envConfig.EnvName] = append(envHost[envConfig.EnvName], privateKey.IP) + ips := sets.NewString() + getHostIPs(privateKeys, ips) + privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + continue } + getHostIPs(privateKeys, ips) + envHost[envConfig.EnvName] = ips.List() } build.EnvHostInfo = envHost } @@ -1721,6 +1727,14 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, return subTasks, nil } +func getHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) { + for _, privateKey := range privateKeys { + if !ips.Has(privateKey.IP) { + ips.Insert(privateKey.IP) + } + } +} + func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) error { var ( buildEnvs []*commonmodels.KeyVal -- Gitee From b2dfae52ecdfd1cd4541f224cbc07a98cc935051 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 25 Oct 2021 14:51:46 +0800 Subject: [PATCH 0170/2647] add more supported types Signed-off-by: lou --- go.mod | 17 +- go.sum | 268 ++++++++++++++++-- .../core/service/bundle/exemption_urls.go | 24 ++ .../systemconfig/core/service/types.go | 28 +- 4 files changed, 297 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index ad4bc1c2..e92c2ab0 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 github.com/coocood/freecache v1.1.0 - github.com/dexidp/dex v2.13.0+incompatible + github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible github.com/docker/go-connections v0.4.0 @@ -29,8 +29,7 @@ require ( github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 - github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/mux v1.7.3 + github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f @@ -39,7 +38,6 @@ require ( github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mittwald/go-helm-client v0.8.0 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect @@ -52,8 +50,8 @@ require ( github.com/rfyiamcool/cronlib v1.0.0 github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.1.3 - github.com/spf13/viper v1.7.0 + github.com/spf13/cobra v1.2.1 + github.com/spf13/viper v1.8.1 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect github.com/stretchr/testify v1.7.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 @@ -66,12 +64,9 @@ require ( github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect go.mongodb.org/mongo-driver v1.5.0 go.uber.org/zap v1.19.0 - golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 + golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/tools v0.1.5 // indirect - gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect - gopkg.in/ldap.v2 v2.5.1 // indirect gopkg.in/mholt/archiver.v3 v3.1.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b diff --git a/go.sum b/go.sum index e8ae4d70..cd347990 100644 --- a/go.sum +++ b/go.sum @@ -10,21 +10,42 @@ cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6T cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.88.0 h1:MZ2cf9Elnv1wqccq8ooKO2MqHQLc+ChCp/+QWObCpxg= +cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc= github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= +github.com/AppsFlyer/go-sundheit v0.4.0/go.mod h1:iZ8zWMS7idcvmqewf5mEymWWgoOiG/0WD4+aeh+heX4= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -36,6 +57,8 @@ github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSY github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -46,6 +69,8 @@ github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= @@ -106,6 +131,7 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -115,6 +141,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -147,7 +174,9 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk= @@ -169,12 +198,16 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= +github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -193,8 +226,9 @@ github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1 github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dexidp/dex v2.13.0+incompatible h1:EQPpzCi51omkwBe0KYpRGaV3rk6CVvjcqeMGCe3Q00w= -github.com/dexidp/dex v2.13.0+incompatible/go.mod h1:cRGkPWqKhDD1FMCICe2JbYDdVR2xGLa38F6iuH/jNAs= +github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b h1:ovHbNjGAQsGEs67tYU6C6ex2D2shDkeZ7pPprx58f2k= +github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b/go.mod h1:g64CEwk9b4oLTREOu8mFkjTkDUvyxzWGqhnPwQlfrq8= +github.com/dexidp/dex/api/v2 v2.0.0/go.mod h1:k5arBJT1QYvpsEY3sEd0NXJp3hKWKuUUfzJ3BlcqPdM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -231,8 +265,10 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -244,9 +280,11 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZM github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -268,6 +306,9 @@ github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -277,6 +318,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -294,6 +337,7 @@ github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= @@ -346,6 +390,7 @@ github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.5.1-0.20200311113236-681ffa848bae/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= @@ -387,6 +432,7 @@ github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY9 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= @@ -411,12 +457,16 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -426,11 +476,13 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= @@ -439,8 +491,11 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= @@ -455,11 +510,22 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -469,6 +535,7 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -478,8 +545,9 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -537,6 +605,7 @@ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 h1:nQ1QIkLxAoFsYyjvpQH3gVIy github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50/go.mod h1:Pp3sd2tx3j9qC7Ij6jGh5phZwTrI+/HUBK90f3Cn2CI= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= @@ -547,6 +616,7 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= @@ -561,6 +631,7 @@ github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXL github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -609,8 +680,9 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -618,8 +690,9 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -629,6 +702,7 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -640,12 +714,14 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237 h1:acuCHBjzG7MFTugvx3buC4m5rLDLaKC9J8C9jtlraRc= github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnLAUmVG5paym8pD3C4B9BQylUDC2vXFJJpT7JrlEA= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -664,8 +740,9 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -710,11 +787,13 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -760,8 +839,9 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -827,6 +907,7 @@ github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cS github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -858,8 +939,9 @@ github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= @@ -868,10 +950,12 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -880,8 +964,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -947,7 +1032,9 @@ github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6Ut github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= @@ -963,6 +1050,10 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -973,8 +1064,11 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= @@ -1022,11 +1116,13 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1049,6 +1145,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= @@ -1060,6 +1157,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1083,6 +1182,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1094,22 +1194,43 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1117,6 +1238,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= @@ -1171,21 +1294,37 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1257,14 +1396,31 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1284,7 +1440,24 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.52.0 h1:m5FLEd6dp5CU1F0tMWyqDi2XjchviIz8ntzOSz7w8As= +google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7SrU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1292,6 +1465,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1312,12 +1486,38 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f h1:YORWxaStkWBnWgELOHTmDrqNlFXuVGEbhwbB5iK94bQ= +google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1331,11 +1531,23 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1347,11 +1559,10 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1367,16 +1578,18 @@ gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= -gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mholt/archiver.v3 v3.1.1 h1:sLb5X5fPPc9oU6YatdC+vbY46irElT3kPR0wOS7bjR4= gopkg.in/mholt/archiver.v3 v3.1.1/go.mod h1:EwJV1tr6GQdNZ5IFFVakh0aYAjGLVjqvFVL/b8ZVdMw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -1413,6 +1626,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 11ee4884..3ff426bf 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -55,6 +55,22 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/podexec/health"}, }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"login"}, + }, + { + Methods: []string{"*"}, + Endpoints: []string{"api/v1/callback"}, + }, + { + Methods: []string{"*"}, + Endpoints: []string{"dex/**"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{""}, + }, } // actions which are allowed for admins only. @@ -187,4 +203,12 @@ var adminURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/aslan/system/proxy/config"}, }, + { + Methods: []string{"*"}, + Endpoints: []string{"api/v1/users"}, + }, + { + Methods: []string{"*"}, + Endpoints: []string{"api/v1/users/?*"}, + }, } diff --git a/pkg/microservice/systemconfig/core/service/types.go b/pkg/microservice/systemconfig/core/service/types.go index dd87a95f..90c22df8 100644 --- a/pkg/microservice/systemconfig/core/service/types.go +++ b/pkg/microservice/systemconfig/core/service/types.go @@ -19,15 +19,27 @@ package service import ( "encoding/json" + "github.com/dexidp/dex/connector/gitea" "github.com/dexidp/dex/connector/github" + "github.com/dexidp/dex/connector/gitlab" + "github.com/dexidp/dex/connector/google" "github.com/dexidp/dex/connector/ldap" + "github.com/dexidp/dex/connector/linkedin" + "github.com/dexidp/dex/connector/microsoft" + "github.com/dexidp/dex/connector/oidc" ) type ConnectorType string const ( - TypeLDAP ConnectorType = "ldap" - TypeGitHub ConnectorType = "github" + TypeLDAP ConnectorType = "ldap" + TypeGitHub ConnectorType = "github" + TypeGitlab ConnectorType = "gitlab" + TypeOIDC ConnectorType = "oidc" + TypeGitea ConnectorType = "gitea" + TypeGoogle ConnectorType = "google" + TypeLinkedIn ConnectorType = "linkedin" + TypeMicrosoft ConnectorType = "microsoft" ) type Connector struct { @@ -53,6 +65,18 @@ func (c *Connector) UnmarshalJSON(data []byte) error { c.Config = &ldap.Config{} case TypeGitHub: c.Config = &github.Config{} + case TypeGitlab: + c.Config = &gitlab.Config{} + case TypeOIDC: + c.Config = &oidc.Config{} + case TypeGitea: + c.Config = &gitea.Config{} + case TypeGoogle: + c.Config = &google.Config{} + case TypeLinkedIn: + c.Config = &linkedin.Config{} + case TypeMicrosoft: + c.Config = µsoft.Config{} } type tmp Connector -- Gitee From d9e6c4c781bed9287332f45a51b139a39d94a46a Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 25 Oct 2021 15:04:08 +0800 Subject: [PATCH 0171/2647] update workflow logic Signed-off-by: allenshen --- .../core/environment/handler/environment.go | 2 +- .../core/environment/service/environment.go | 9 +- .../aslan/core/workflow/handler/workflow.go | 2 +- .../workflow/service/workflow/workflow.go | 92 +++++++++++++++---- 4 files changed, 81 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index eabafab3..043d7f25 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -80,7 +80,7 @@ func GetProductStatus(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetProductStatus(c.Query("productName"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetProductStatus(c.Param("productName"), ctx.Logger) } func AutoCreateProduct(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f1ad87ff..33d99843 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -167,14 +167,17 @@ func UpdateProductPublic(productName string, args *ProductParams, log *zap.Sugar } func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, error) { - //项目下所有公开环境 - publicProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{IsPublic: true, Name: productName}) + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) if err != nil { log.Errorf("Collection.Product.List List product error: %v", err) return nil, e.ErrListProducts.AddDesc(err.Error()) } envStatusSlice := make([]*EnvStatus, 0) - for _, publicProduct := range publicProducts { + for _, publicProduct := range products { + //log.Infof("######### the product name is %s %s", publicProduct.ProductName, productName) + if publicProduct.ProductName != productName { + continue + } envStatus := &EnvStatus{ EnvName: publicProduct.EnvName, Status: publicProduct.Status, diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index 729fcf3f..83de0263 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -118,7 +118,7 @@ func ListWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), ctx.User.ID, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), c.Query("productName"), ctx.User.ID, ctx.Logger) } func ListAllWorkflows(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index ec44581e..b37be000 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -47,6 +47,41 @@ type EnvStatus struct { ErrMessage string `json:"err_message"` } +type workflowCreateArg struct { + name string + envName string + buildStageEnabled bool + ArtifactStageEnabled bool +} + +type workflowCreateArgs struct { + productName string + argsMap map[string]*workflowCreateArg +} + +func (args *workflowCreateArgs) addWorkflowArg(envName string, buildStageEnabled, artifactStageEnabled bool) { + wName := fmt.Sprintf("%s-workflow-%s", args.productName, envName) + if artifactStageEnabled { + wName = fmt.Sprintf("%s-%s-workflow", args.productName, "ops") + } + args.argsMap[wName] = &workflowCreateArg{ + name: wName, + envName: envName, + buildStageEnabled: buildStageEnabled, + ArtifactStageEnabled: artifactStageEnabled, + } +} + +func (args *workflowCreateArgs) initDefaultWorkflows() { + args.addWorkflowArg("dev", true, false) + args.addWorkflowArg("qa", true, false) + args.addWorkflowArg("", false, true) +} + +func (args *workflowCreateArgs) clear() { + args.argsMap = make(map[string]*workflowCreateArg) +} + func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { productTmpl, err := template.NewProductColl().Find(productName) if err != nil { @@ -60,22 +95,46 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { mut.Unlock() }() - workflowNames := []string{productName + "-workflow-dev", productName + "-workflow-qa", productName + "-workflow-ops"} + createArgs := &workflowCreateArgs{ + productName: productName, + argsMap: make(map[string]*workflowCreateArg), + } + createArgs.initDefaultWorkflows() + + // helm project may have customized products, use the real created products + if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.DeployType == setting.HelmDeployType { + productList, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + }) + if err != nil { + log.Errorf("fialed to list products, projectName %s, err %s", productName, err) + } + createArgs.clear() + for _, product := range productList { + createArgs.addWorkflowArg(product.EnvName, true, false) + } + createArgs.addWorkflowArg("", false, true) + } + // 云主机场景不创建ops工作流 if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.BasicFacility == "cloud_host" { - workflowNames = []string{productName + "-workflow-dev", productName + "-workflow-qa"} + createArgs.clear() + createArgs.addWorkflowArg("dev", true, false) + createArgs.addWorkflowArg("qa", true, false) } else if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { - workflowNames = []string{productName + "-workflow-dev"} + createArgs.clear() + createArgs.addWorkflowArg("dev", true, false) } workflowSlice := sets.NewString() - for _, workflowName := range workflowNames { + for workflowName, _ := range createArgs.argsMap { _, err := FindWorkflow(workflowName, log) if err == nil { workflowSlice.Insert(workflowName) } } - if len(workflowSlice) < len(workflowNames) { + + if len(workflowSlice) < len(createArgs.argsMap) { preSetResps, err := PreSetWorkflow(productName, log) if err != nil { errList = multierror.Append(errList, err) @@ -95,7 +154,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { artifactModules = append(artifactModules, artifactModule) } - for _, workflowName := range workflowNames { + for workflowName, workflowArg := range createArgs.argsMap { if workflowSlice.Has(workflowName) { continue } @@ -108,24 +167,19 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { workflow.Name = workflowName workflow.CreateBy = setting.SystemUser workflow.UpdateBy = setting.SystemUser - workflow.EnvName = "dev" + workflow.EnvName = workflowArg.envName workflow.BuildStage = &commonmodels.BuildStage{ - Enabled: true, + Enabled: workflowArg.buildStageEnabled, Modules: buildModules, } - if strings.Contains(workflowName, "qa") { - workflow.EnvName = "qa" - } - - if strings.Contains(workflowName, "ops") { - //如果是开启artifactStage,则关闭buildStage - workflow.BuildStage.Enabled = false + //如果是开启artifactStage,则关闭buildStage + if workflowArg.ArtifactStageEnabled { workflow.ArtifactStage = &commonmodels.ArtifactStage{ Enabled: true, Modules: artifactModules, } - workflow.EnvName = "ops" + workflow.EnvName = "ops" //TODO necessary to set a fake env name? } workflow.Schedules = &commonmodels.ScheduleCtrl{ @@ -162,7 +216,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { return &EnvStatus{Status: setting.ProductStatusFailed, ErrMessage: err.Error()} } return &EnvStatus{Status: setting.ProductStatusCreating} - } else if len(workflowSlice) == len(workflowNames) { + } else if len(workflowSlice) == len(createArgs.argsMap) { return &EnvStatus{Status: setting.ProductStatusSuccess} } return nil @@ -424,8 +478,8 @@ func validateWorkflowHookNames(w *commonmodels.Workflow) error { return validateHookNames(names) } -func ListWorkflows(queryType string, userID int, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { - workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) +func ListWorkflows(queryType string, productName string, userID int, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { + workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{ProductName: productName}) if err != nil { log.Errorf("Workflow.List error: %v", err) return workflows, e.ErrListWorkflow.AddDesc(err.Error()) -- Gitee From abf5959d52ca9c5ad7de363ce602d6bcfef62558 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 25 Oct 2021 15:31:26 +0800 Subject: [PATCH 0172/2647] add get_role api Signed-off-by: lou --- pkg/microservice/policy/core/handler/role.go | 22 ++++++++++++++- .../policy/core/handler/router.go | 2 ++ pkg/microservice/policy/core/service/role.go | 27 ++++++++++++++++++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 86bcb70b..c55ea6b9 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -87,8 +87,21 @@ func ListRoles(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") return } + ctx.Resp, ctx.Err = service.ListRoles(projectName, ctx.Logger) - return +} + +func GetRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + ctx.Resp, ctx.Err = service.GetRole(projectName, c.Param("name"), ctx.Logger) } func CreatePublicRole(c *gin.Context) { @@ -112,6 +125,13 @@ func ListPublicRoles(c *gin.Context) { return } +func GetPublicRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetRole("", c.Param("name"), ctx.Logger) +} + func DeleteRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 145d956b..dc0019f8 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -28,6 +28,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roles.POST("", CreateRole) roles.PUT("/:name", UpdateRole) roles.GET("", ListRoles) + roles.GET("/:name", GetRole) roles.DELETE("/:name", DeleteRole) } @@ -35,6 +36,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { publicRoles.POST("", CreatePublicRole) publicRoles.GET("", ListPublicRoles) + publicRoles.GET("/:name", GetPublicRole) publicRoles.PUT("/:name", UpdatePublicRole) publicRoles.DELETE("/:name", DeletePublicRole) } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 8c40f0ae..63b21e30 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -17,6 +17,8 @@ limitations under the License. package service import ( + "fmt" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" @@ -67,7 +69,8 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().UpdateRole(obj) } -func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err error) { +func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { + var roles []*Role projectRoles, err := mongodb.NewRoleColl().ListBy(projectName) if err != nil { return nil, err @@ -80,6 +83,28 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) (roles []*Role, err err return roles, nil } +func GetRole(ns, name string, _ *zap.SugaredLogger) (*Role, error) { + r, found, err := mongodb.NewRoleColl().Get(ns, name) + if err != nil { + return nil, err + } else if !found { + return nil, fmt.Errorf("role %s not found", name) + } + + res := &Role{ + Name: r.Name, + } + for _, ru := range r.Rules { + res.Rules = append(res.Rules, &Rule{ + Verbs: ru.Verbs, + Kind: ru.Kind, + Resources: ru.Resources, + }) + } + + return res, nil +} + func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) (err error) { return mongodb.NewRoleColl().Delete(name, projectName) } -- Gitee From 5d0af5ff56ee999c29ed0342fb87b9b9e957ee3b Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 15:43:16 +0800 Subject: [PATCH 0173/2647] remove jira organization Signed-off-by: mouuii --- .../aslan/core/common/service/git/hook.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index f716d9a6..0499752f 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -26,13 +26,11 @@ var once sync.Once var secret string func GetHookSecret() string { - once.Do(func() { - token, err := crypto.AesEncrypt("hook") - if err != nil { - panic("Failed to get token") - } - secret = token - }) + token, err := crypto.AesEncrypt("hook") + if err != nil { + panic("Failed to get token") + } + secret = token return secret } -- Gitee From 3f04b73a2ab2324cefd54fb71f4c0d9d3ba58e73 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 15:47:01 +0800 Subject: [PATCH 0174/2647] remove jira organization Signed-off-by: mouuii --- .../aslan/core/common/service/git/hook.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index 0499752f..0c7699d7 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -17,20 +17,15 @@ limitations under the License. package git import ( - "sync" - "github.com/koderover/zadig/pkg/tool/crypto" + "github.com/koderover/zadig/pkg/tool/log" ) -var once sync.Once -var secret string - func GetHookSecret() string { token, err := crypto.AesEncrypt("hook") if err != nil { - panic("Failed to get token") + log.DPanic(err) } - secret = token - return secret + return token } -- Gitee From e1bcd59ea3160898d8fee4803a1a75ef248e2a92 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 15:51:03 +0800 Subject: [PATCH 0175/2647] remove jira organization Signed-off-by: mouuii --- pkg/microservice/aslan/core/common/service/git/hook.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index 0c7699d7..a1d99219 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -21,8 +21,10 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +const plainSecret = "webhook secret" + func GetHookSecret() string { - token, err := crypto.AesEncrypt("hook") + token, err := crypto.AesEncrypt(plainSecret) if err != nil { log.DPanic(err) } -- Gitee From 61e1395a35b932db9ed0bbece74a255c4f274ced Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Oct 2021 17:02:18 +0800 Subject: [PATCH 0176/2647] added dockerfile template api & basic implementations Signed-off-by: Min Min --- .../core/templatestore/handler/dockerfile.go | 92 +++++++++++++++ .../core/templatestore/handler/router.go | 10 ++ .../repository/models/dockerfile.go | 13 +++ .../repository/mongodb/dockerfile.go | 109 ++++++++++++++++++ .../core/templatestore/service/dockerfile.go | 79 +++++++++++++ .../aslan/core/templatestore/service/types.go | 22 ++++ 6 files changed, 325 insertions(+) create mode 100644 pkg/microservice/aslan/core/templatestore/handler/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/service/dockerfile.go diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go new file mode 100644 index 00000000..aed5fd99 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -0,0 +1,92 @@ +package handler + +import ( + "errors" + "strconv" + + "github.com/gin-gonic/gin" + + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.DockerfileTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.CreateDockerfileTemplate(req, ctx.Logger) +} + +func UpdateDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.DockerfileTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.UpdateDockerfileTemplate(c.Param("id"), req, ctx.Logger) +} + +type ListDockefileResp struct { + DockerfileTemplates []*templateservice.DockerfileListObject `json:"dockerfile_template"` + Total int `json:"total"` +} + +func ListDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Query Verification + pageSizeString := c.DefaultQuery("page_size", "100") + pageSize, err := strconv.Atoi(pageSizeString) + if err != nil { + ctx.Err = errors.New("invalid page_size query") + return + } + pageNumString := c.DefaultQuery("page_num", "1") + pageNum, err := strconv.Atoi(pageNumString) + if err != nil { + ctx.Err = errors.New("invalid page_num query") + return + } + + dockerfileTemplateList, total, err := templateservice.ListDockerfileTemplate(pageNum, pageSize, ctx.Logger) + resp := ListDockefileResp{ + DockerfileTemplates: dockerfileTemplateList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err +} + +func GetDockerfileTemplateDetail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateDetail(c.Query("id"), ctx.Logger) +} + +func DeleteDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = templateservice.DeleteDockerfileTemplate(c.Query("id"), ctx.Logger) +} + +func GetDockerfileTemplateReference(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateReference(c.Query("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 120a55ff..9f8f188a 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -40,4 +40,14 @@ func (*Router) Inject(router *gin.RouterGroup) { install.PUT("/:name", UpdateChartTemplate) install.DELETE("/:name", RemoveChartTemplate) } + + dockerfile := router.Group("dockerfile") + { + dockerfile.POST("", CreateDockerfileTemplate) + dockerfile.PUT("/:id", UpdateDockerfileTemplate) + dockerfile.GET("", ListDockerfileTemplate) + dockerfile.GET("/:id", GetDockerfileTemplateDetail) + dockerfile.DELETE("/:id", DeleteDockerfileTemplate) + dockerfile.GET("/:id/reference", GetDockerfileTemplateReference) + } } diff --git a/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go new file mode 100644 index 00000000..19ab53db --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go @@ -0,0 +1,13 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type DockerfileTemplate struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Content string `bson:"content" json:"content"` +} + +func (DockerfileTemplate) TableName() string { + return "dockerfile_template" +} diff --git a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go new file mode 100644 index 00000000..d35ba227 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go @@ -0,0 +1,109 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type DockerfileTemplateColl struct { + *mongo.Collection + + coll string +} + +func NewDockerfileTemplateColl() *DockerfileTemplateColl { + name := models.DockerfileTemplate{}.TableName() + return &DockerfileTemplateColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *DockerfileTemplateColl) GetCollectionName() string { + return c.coll +} + +func (c *DockerfileTemplateColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *DockerfileTemplateColl) Create(obj *models.DockerfileTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + return err +} + +func (c *DockerfileTemplateColl) Update(idString string, obj *models.DockerfileTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *DockerfileTemplateColl) List(pageNum, pageSize int) ([]*models.DockerfileTemplate, int, error) { + resp := make([]*models.DockerfileTemplate, 0) + query := bson.M{} + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + opt := options.Find(). + SetSkip(int64((pageNum - 1) * pageSize)). + SetLimit(int64(pageSize)) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, int(count), nil +} + +func (c *DockerfileTemplateColl) GetById(idstring string) (*models.DockerfileTemplate, error) { + resp := new(models.DockerfileTemplate) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *DockerfileTemplateColl) DeleteByID(idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go new file mode 100644 index 00000000..94f1dd2e --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -0,0 +1,79 @@ +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" +) + +func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredLogger) error { + err := mongodb.NewDockerfileTemplateColl().Create(&models.DockerfileTemplate{ + Name: template.Name, + Content: template.Content, + }) + if err != nil { + logger.Errorf("create dockerfile template error: %+v", err) + } + return err +} + +func UpdateDockerfileTemplate(id string, template *DockerfileTemplate, logger *zap.SugaredLogger) error { + err := mongodb.NewDockerfileTemplateColl().Update( + id, + &models.DockerfileTemplate{ + Name: template.Name, + Content: template.Content, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %+v", err) + } + return err +} + +func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*DockerfileListObject, int, error) { + resp := make([]*DockerfileListObject, 0) + templateList, total, err := mongodb.NewDockerfileTemplateColl().List(pageNum, pageSize) + if err != nil { + logger.Errorf("list dockerfile template error: %+v", err) + } + for _, obj := range templateList { + resp = append(resp, &DockerfileListObject{ + ID: obj.ID.String(), + Name: obj.Name, + }) + } + return resp, total, err +} + +func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*DockerfileDetail, error) { + resp := new(DockerfileDetail) + dockerfileTemplate, err := mongodb.NewDockerfileTemplateColl().GetById(id) + if err != nil { + logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %+v", id, err) + return nil, err + } + variables := getVariables(dockerfileTemplate.Content) + resp.ID = dockerfileTemplate.ID.String() + resp.Name = dockerfileTemplate.Name + resp.Content = dockerfileTemplate.Content + resp.Variables = variables + return resp, nil +} + +func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { + err := mongodb.NewDockerfileTemplateColl().DeleteByID(id) + if err != nil { + logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %+v", id, err) + } + return err +} + +func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*DockerfileDetail, error) { + return []*DockerfileDetail{}, nil +} + +func getVariables(s string) []*Variable { + return []*Variable{} +} diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index da8cdeb7..51d35326 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -28,3 +28,25 @@ type Chart struct { Files []*fs.FileInfo `json:"files,omitempty"` } + +type DockerfileTemplate struct { + Name string `json:"name"` + Content string `json:"content"` +} + +type DockerfileListObject struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type DockerfileDetail struct { + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*Variable `json:"variable"` +} + +type Variable struct { + Key string `json:"key"` + Value string `json:"value"` +} -- Gitee From e7d940989aeb151ff50c759e12b2527fcb6d9dd4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Oct 2021 17:20:35 +0800 Subject: [PATCH 0177/2647] debug Signed-off-by: Min Min --- pkg/microservice/aslan/core/service.go | 4 ++++ .../aslan/core/templatestore/service/dockerfile.go | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index b6c26bf7..9a7c9164 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -33,6 +33,7 @@ import ( environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" @@ -169,6 +170,9 @@ func initDatabase() { commonrepo.NewWorkflowStatColl(), commonrepo.NewWorkLoadsStatColl(), + templaterepo.NewChartColl(), + templaterepo.NewDockerfileTemplateColl(), + systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), } { diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index 94f1dd2e..d7ca4a80 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -40,7 +40,7 @@ func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([ } for _, obj := range templateList { resp = append(resp, &DockerfileListObject{ - ID: obj.ID.String(), + ID: obj.ID.Hex(), Name: obj.Name, }) } @@ -55,7 +55,7 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf return nil, err } variables := getVariables(dockerfileTemplate.Content) - resp.ID = dockerfileTemplate.ID.String() + resp.ID = dockerfileTemplate.ID.Hex() resp.Name = dockerfileTemplate.Name resp.Content = dockerfileTemplate.Content resp.Variables = variables -- Gitee From 752ffa8706c3137c1c002cf16b55b80d710691a5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Oct 2021 17:33:55 +0800 Subject: [PATCH 0178/2647] minor bugfixes Signed-off-by: Min Min --- .../aslan/core/templatestore/handler/dockerfile.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index aed5fd99..d1f019aa 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -74,19 +74,19 @@ func GetDockerfileTemplateDetail(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateDetail(c.Query("id"), ctx.Logger) + ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateDetail(c.Param("id"), ctx.Logger) } func DeleteDockerfileTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = templateservice.DeleteDockerfileTemplate(c.Query("id"), ctx.Logger) + ctx.Err = templateservice.DeleteDockerfileTemplate(c.Param("id"), ctx.Logger) } func GetDockerfileTemplateReference(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateReference(c.Query("id"), ctx.Logger) + ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateReference(c.Param("id"), ctx.Logger) } -- Gitee From 654e6366deb8a6694f518a0737f30a9cacc015e3 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Oct 2021 18:34:22 +0800 Subject: [PATCH 0179/2647] added dockerfile parser validation Signed-off-by: Min Min --- go.mod | 1 + go.sum | 2 ++ .../core/templatestore/handler/dockerfile.go | 18 +++++++++++++++++ .../core/templatestore/service/dockerfile.go | 20 +++++++++++++++++-- 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 28512627..4df633e9 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/onsi/gomega v1.15.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/selinux v1.6.0 // indirect + github.com/openshift/imagebuilder v1.2.1 github.com/otiai10/copy v1.6.0 github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 diff --git a/go.sum b/go.sum index 08ed6a88..6b0e8940 100644 --- a/go.sum +++ b/go.sum @@ -753,6 +753,8 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/openshift/imagebuilder v1.2.1 h1:RuusdjNhFRSHXisuEkzdNmn3s+qJby+xfQ+KLtASuTw= +github.com/openshift/imagebuilder v1.2.1/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index d1f019aa..fd42303c 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -3,8 +3,10 @@ package handler import ( "errors" "strconv" + "strings" "github.com/gin-gonic/gin" + dockerfileparser "github.com/openshift/imagebuilder/dockerfile/parser" templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -21,6 +23,14 @@ func CreateDockerfileTemplate(c *gin.Context) { return } + // some dockerfile validation stuff + reader := strings.NewReader(req.Content) + _, err := dockerfileparser.Parse(reader) + if err != nil { + ctx.Err = errors.New("invalid dockerfile, please check") + return + } + ctx.Err = templateservice.CreateDockerfileTemplate(req, ctx.Logger) } @@ -35,6 +45,14 @@ func UpdateDockerfileTemplate(c *gin.Context) { return } + // some dockerfile validation stuff + reader := strings.NewReader(req.Content) + _, err := dockerfileparser.Parse(reader) + if err != nil { + ctx.Err = errors.New("invalid dockerfile, please check") + return + } + ctx.Err = templateservice.UpdateDockerfileTemplate(c.Param("id"), req, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index d7ca4a80..e91803a8 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -1,6 +1,8 @@ package service import ( + "strings" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" @@ -54,7 +56,7 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %+v", id, err) return nil, err } - variables := getVariables(dockerfileTemplate.Content) + variables := getVariables(dockerfileTemplate.Content, logger) resp.ID = dockerfileTemplate.ID.Hex() resp.Name = dockerfileTemplate.Name resp.Content = dockerfileTemplate.Content @@ -74,6 +76,20 @@ func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*Do return []*DockerfileDetail{}, nil } -func getVariables(s string) []*Variable { +func getVariables(s string, logger *zap.SugaredLogger) []*Variable { + lines := strings.Split(s, "\n") + for i, line := range lines { + lineWithoutSpace := strings.TrimSpace(line) + logger.Infof(">>>>>>>>>> line [%d] is: %s <<<<<<<<<<<<<", i, lineWithoutSpace) + if strings.HasPrefix(lineWithoutSpace, "ARG") { + logger.Infof("!!!!!!!!!!! line [%d] has prefix ARG !!!!!!!!", i) + logger.Infof("Proceeding to find variables") + } + } + //reader := strings.NewReader(s) + //result, err := dockerfileparser.Parse(reader) + //if err != nil { + // return nil + //} return []*Variable{} } -- Gitee From 0624d39bc87f45d91c5b7abd489be2ac0dd71b78 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Oct 2021 19:04:21 +0800 Subject: [PATCH 0180/2647] validation stuff Signed-off-by: Min Min --- go.mod | 9 +- go.sum | 825 +++++++++++++++++- .../core/templatestore/handler/dockerfile.go | 17 +- 3 files changed, 816 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index 4df633e9..8704428c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/cenkalti/backoff/v3 v3.0.0 github.com/coocood/freecache v1.1.0 github.com/docker/distribution v2.7.1+incompatible - github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible + github.com/docker/docker v20.10.7+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.1 // indirect @@ -30,9 +30,8 @@ require ( github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/mux v1.7.3 + github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 - github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 @@ -40,13 +39,12 @@ require ( github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mittwald/go-helm-client v0.8.0 + github.com/moby/buildkit v0.9.1 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/selinux v1.6.0 // indirect - github.com/openshift/imagebuilder v1.2.1 github.com/otiai10/copy v1.6.0 github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 @@ -60,7 +58,6 @@ require ( github.com/swaggo/gin-swagger v1.3.0 github.com/swaggo/swag v1.5.1 github.com/xanzy/go-gitlab v0.50.0 - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect github.com/yvasiyarov/gorelic v0.0.7 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect diff --git a/go.sum b/go.sum index 6b0e8940..9d0cd65a 100644 --- a/go.sum +++ b/go.sum @@ -1,51 +1,115 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= +github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= +github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= +github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= @@ -53,20 +117,42 @@ github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFP github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3HV0OhsddkmM= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.18 h1:cYnKADiM1869gvBpos3YCteeT6sZLB48lB5dmMMs8Tg= +github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RyanCarrier/dijkstra v1.1.0 h1:/NDihjfJA3CxFaZz8EdzTwdFKFZDvvB881OVLdakRcI= @@ -75,23 +161,34 @@ github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345 h1:fgSpoKVi github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72 h1:uGeGZl8PxSq8VZGG4QK5njJTFA4/G/x5CYORvQVXtAE= github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72/go.mod h1:o+JdB7VetTHjLhU0N57x18B9voDBQe0paApdEAEoEfw= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 h1:2Qp3BQZ7r1z+oMUnFhD6FQ86Hh1PO+59yoUNNJ5Dn0M= github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= +github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs= +github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= +github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= +github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -102,10 +199,20 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -115,6 +222,9 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -122,63 +232,168 @@ github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2y github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= +github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= +github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= +github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I= github.com/bugsnag/bugsnag-go v2.1.0+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/panicwrap v1.3.1 h1:pmuhHlhbUV4OOrGDvoiMjHSZzwRcL+I9cIzYKiW4lII= github.com/bugsnag/panicwrap v1.3.1/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.3 h1:mfKOepNDIJ3EiBTEyHFpEqB6YSOSkGcjPDIu7cD+YzY= +github.com/containerd/containerd v1.5.3/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8dskcM7wQ28gONkukbQkM4qpj4RnYbpFzrI= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fuse-overlayfs-snapshotter v1.0.2/go.mod h1:nRZceC8a7dRm3Ao6cJAwuJWPFiBPaibHiFntRUnzhwU= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter v0.0.0-20201027054423-3a04e4c2c116/go.mod h1:o59b3PCKVAf9jjiKtCc/9hLAd+5p/rfhBfm6aBcTEr4= +github.com/containerd/stargz-snapshotter v0.6.4/go.mod h1:1t0SF1gAHJhCSftWKDLVitvfF3c2qhL5hymG7C50wto= +github.com/containerd/stargz-snapshotter/estargz v0.6.4/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coocood/freecache v1.1.0 h1:ENiHOsWdj1BrrlPwblhbn4GdAsMymK3pZORJ+bJGAjA= github.com/coocood/freecache v1.1.0/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -189,6 +404,11 @@ github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -196,11 +416,20 @@ github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1 github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/cli v20.10.5+incompatible h1:bjflayQbWg+xOkF2WPEAOi4Y7zWhR7ptoPhV/VqLVDE= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= +github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible h1:357nGVUC8gSpeSc2Axup8HfrfTLLUfWfCsCUhiQSKIg= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81 h1:VnJAjP/7FbY46wddaiKakrc1ogXW+3MlP7cg+S4j4Og= @@ -209,9 +438,13 @@ github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGl github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 h1:Z8LR4yJgA3vaXpBiUWcWdQZpB9h+dzj4NdLjP9Sfdb0= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libnetwork v0.8.0-dev.2.0.20200917202933-d0951081b35f/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -236,6 +469,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= @@ -243,19 +477,27 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -270,17 +512,22 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -290,6 +537,7 @@ github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -298,11 +546,13 @@ github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2 github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= @@ -317,6 +567,7 @@ github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCs github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= @@ -329,6 +580,7 @@ github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pL github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= @@ -356,6 +608,21 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= @@ -391,15 +658,26 @@ github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kE github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -411,17 +689,21 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -434,59 +716,109 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4-0.20210608040537-544b4180ac70 h1:yxuuMouxXYv9V1HprM9jTODJPGrTrC0FYVtPSnyIXxs= +github.com/golang/snappy v0.0.4-0.20210608040537-544b4180ac70/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.23.7/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= +github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= +github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho= github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904= +github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w= +github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= @@ -496,6 +828,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -505,14 +839,24 @@ github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6t github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= +github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY= +github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= @@ -520,10 +864,14 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -534,14 +882,16 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -550,28 +900,47 @@ github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50/go.mod h1:Pp3sd2tx3j9qC7Ij6 github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= +github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -595,9 +964,14 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -606,11 +980,15 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -618,6 +996,8 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -626,26 +1006,39 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9 github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= @@ -653,44 +1046,74 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237 h1:acuCHBjzG7MFTugvx3buC4m5rLDLaKC9J8C9jtlraRc= github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnLAUmVG5paym8pD3C4B9BQylUDC2vXFJJpT7JrlEA= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= +github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mittwald/go-helm-client v0.8.0 h1:k8yMvoWE7C+jJgw6iodJ0U5nY4hVpLU20VIpR+FfyJk= github.com/mittwald/go-helm-client v0.8.0/go.mod h1:+Ks5OpzB0hJw91W8o/8G+FmjUtjrIXxPuJjE87XG/G0= +github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= +github.com/moby/buildkit v0.9.1 h1:6noq8jvkaRs3OQGTIDf5U5Etkgq6zsPmQHGWi5yhh88= +github.com/moby/buildkit v0.9.1/go.mod h1:oVZKk3TMm0MlDx7XxnlF0wKmcpyrzOs9GEp0VXKWFPk= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= +github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= +github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -698,12 +1121,17 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -711,6 +1139,7 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsqio/go-nsq v1.0.7 h1:O0pIZJYTf+x7cZBA0UMY8WxFG79lYTURmWzAAh48ljY= @@ -727,39 +1156,65 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE= +github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY= +github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/openshift/imagebuilder v1.2.1 h1:RuusdjNhFRSHXisuEkzdNmn3s+qJby+xfQ+KLtASuTw= -github.com/openshift/imagebuilder v1.2.1/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= +github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= @@ -774,30 +1229,39 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc= +github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= @@ -809,53 +1273,82 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= +github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -864,12 +1357,17 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -885,8 +1383,9 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -895,17 +1394,22 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -921,10 +1425,34 @@ github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZt github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0= github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= +github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= +github.com/tonistiigi/fsutil v0.0.0-20210609172227-d72af97c0eaf/go.mod h1:lJAxK//iyZ3yGbQswdrPTxugZIDM7sd4bEsD0x3XMHk= +github.com/tonistiigi/go-actions-cache v0.0.0-20211002214948-4d48f2ff622a/go.mod h1:YiIBjH5gP7mao3t0dBrNNBGuKYdeJmcAJjYLXr43k6A= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= +github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.13 h1:nB3O5kBSQGjEQAcfe1aLUYuxmXdFKmYgBZhY32rQb6Q= @@ -934,14 +1462,34 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4= github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= -github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= @@ -952,6 +1500,7 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= @@ -962,7 +1511,9 @@ github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6Ut github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= @@ -983,6 +1534,11 @@ go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.5.0 h1:REddm85e1Nl0JPXGGhgZkgJdG/yOe6xvpXUcYK5WLt0= go.mongodb.org/mongo-driver v1.5.0/go.mod h1:boiGPFqyBs5R0R5qf2ErokGRekMfwn+MqKaUyHs7wy0= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= +go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -991,16 +1547,31 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib v0.21.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.21.0/go.mod h1:Vm5u/mtkj1OMhtao0v+BGo2LUoLCgHYXvRmj0jWITlE= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0/go.mod h1:a9cocRplhIBkUAJmak+BPDx+LVL7cTmqUPB0uBcTA4k= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0/go.mod h1:JQAtechjxLEL81EjmbRwxBq/XEzGaHcsPuDHAx54hg4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= +go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1/go.mod h1:FXJnjGCoTQL6nQ8OpFJ0JI1DrdOvMoVx49ic0Hg4+D4= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.0-RC1/go.mod h1:FliQjImlo7emZVjixV8nbDMAa4iAkcWTE9zzSEOiEPw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.0-RC1/go.mod h1:cDwRc2Jrh5Gku1peGK8p9rRuX/Uq2OtVmLicjlw2WYU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.0.0-RC1/go.mod h1:OYKzEoxgXFvehW7X12WYT4/a2BlASJK9l7RtG4A91fg= +go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1020,13 +1591,21 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= +golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1034,16 +1613,24 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= @@ -1054,7 +1641,9 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -1077,11 +1666,17 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1097,11 +1692,15 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1109,22 +1708,33 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1132,20 +1742,26 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1156,25 +1772,35 @@ golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1182,22 +1808,42 @@ golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1208,6 +1854,7 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1221,25 +1868,38 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -1250,8 +1910,12 @@ golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1259,23 +1923,38 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -1290,8 +1969,19 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= +google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1300,7 +1990,11 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1308,14 +2002,21 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1324,16 +2025,25 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -1343,13 +2053,20 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1365,6 +2082,7 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1374,7 +2092,9 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= @@ -1382,16 +2102,20 @@ gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mholt/archiver.v3 v3.1.1 h1:sLb5X5fPPc9oU6YatdC+vbY46irElT3kPR0wOS7bjR4= gopkg.in/mholt/archiver.v3 v3.1.1/go.mod h1:EwJV1tr6GQdNZ5IFFVakh0aYAjGLVjqvFVL/b8ZVdMw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1412,53 +2136,96 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= helm.sh/helm/v3 v3.6.2 h1:7YbEhLC6AUgmcDoh5BGFUGNtR/o43pr0AIelSNjhuFU= helm.sh/helm/v3 v3.6.2/go.mod h1:mIIus8EOqj+obtycw3sidsR4ORr2aFDmXMSI3k+oeVY= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.21.0 h1:1hWMfsz+cXxB77k6/y0XxWxwl6l9OF26PC9QneUVn1Q= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= +k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= +k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= +k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= +pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= @@ -1468,10 +2235,16 @@ sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI= sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index fd42303c..5d814485 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -6,7 +6,8 @@ import ( "strings" "github.com/gin-gonic/gin" - dockerfileparser "github.com/openshift/imagebuilder/dockerfile/parser" + dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" + dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -25,7 +26,12 @@ func CreateDockerfileTemplate(c *gin.Context) { // some dockerfile validation stuff reader := strings.NewReader(req.Content) - _, err := dockerfileparser.Parse(reader) + result, err := dockerfileparser.Parse(reader) + if err != nil { + ctx.Err = errors.New("invalid dockerfile, please check") + return + } + _, _, err = dockerfileinstructions.Parse(result.AST) if err != nil { ctx.Err = errors.New("invalid dockerfile, please check") return @@ -47,7 +53,12 @@ func UpdateDockerfileTemplate(c *gin.Context) { // some dockerfile validation stuff reader := strings.NewReader(req.Content) - _, err := dockerfileparser.Parse(reader) + result, err := dockerfileparser.Parse(reader) + if err != nil { + ctx.Err = errors.New("invalid dockerfile, please check") + return + } + _, _, err = dockerfileinstructions.Parse(result.AST) if err != nil { ctx.Err = errors.New("invalid dockerfile, please check") return -- Gitee From 8f5b2019e4e9a3f2f9a825fa0d603fcb8d8d3fac Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Oct 2021 19:42:31 +0800 Subject: [PATCH 0181/2647] fix broken dependency Signed-off-by: Min Min --- go.mod | 16 ++- go.sum | 384 +++++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 257 insertions(+), 143 deletions(-) diff --git a/go.mod b/go.mod index 8704428c..12510309 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/bndr/gojenkins v1.1.0 github.com/bradleyfalzon/ghinstallation v1.1.1 - github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 @@ -29,7 +28,6 @@ require ( github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 - github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f @@ -38,7 +36,7 @@ require ( github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/mittwald/go-helm-client v0.8.0 + github.com/mittwald/go-helm-client v0.8.2 github.com/moby/buildkit v0.9.1 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect @@ -50,8 +48,8 @@ require ( github.com/rfyiamcool/cronlib v1.0.0 github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.8.1 - github.com/spf13/cobra v1.1.3 - github.com/spf13/viper v1.7.0 + github.com/spf13/cobra v1.2.1 + github.com/spf13/viper v1.8.1 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect github.com/stretchr/testify v1.7.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 @@ -63,18 +61,18 @@ require ( github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect go.mongodb.org/mongo-driver v1.5.0 go.uber.org/zap v1.19.0 - golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 + golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/tools v0.1.5 // indirect gopkg.in/mholt/archiver.v3 v3.1.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b - helm.sh/helm/v3 v3.6.2 + helm.sh/helm/v3 v3.7.1 k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 - k8s.io/kubectl v0.21.0 + k8s.io/kubectl v0.22.1 k8s.io/utils v0.0.0-20210802155522-efc7438f0176 sigs.k8s.io/controller-runtime v0.10.0 sigs.k8s.io/yaml v1.2.0 diff --git a/go.sum b/go.sum index 9d0cd65a..1227c29a 100644 --- a/go.sum +++ b/go.sum @@ -18,11 +18,19 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -34,6 +42,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= @@ -49,6 +58,7 @@ github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1W github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -61,6 +71,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -101,9 +112,9 @@ github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5H github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= @@ -112,6 +123,8 @@ github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0 github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= @@ -136,8 +149,9 @@ github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3H github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.18 h1:cYnKADiM1869gvBpos3YCteeT6sZLB48lB5dmMMs8Tg= github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= @@ -159,11 +173,11 @@ github.com/RyanCarrier/dijkstra v1.1.0 h1:/NDihjfJA3CxFaZz8EdzTwdFKFZDvvB881OVLd github.com/RyanCarrier/dijkstra v1.1.0/go.mod h1:5agGUBNEtUAGIANmbw09fuO3a2htPEkc1jNH01qxCWA= github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345 h1:fgSpoKViTSqRb4hjDNj10ig5wUvO0CayCzFdLf6fuRM= github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -183,7 +197,6 @@ github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1/go.mod h1:0 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs= github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= @@ -193,12 +206,10 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -206,12 +217,11 @@ github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -221,7 +231,11 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -229,6 +243,7 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= @@ -244,27 +259,30 @@ github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414 github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I= github.com/bugsnag/bugsnag-go v2.1.0+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v1.3.1 h1:pmuhHlhbUV4OOrGDvoiMjHSZzwRcL+I9cIzYKiW4lII= github.com/bugsnag/panicwrap v1.3.1/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -272,13 +290,16 @@ github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmE github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= @@ -310,15 +331,16 @@ github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.3 h1:mfKOepNDIJ3EiBTEyHFpEqB6YSOSkGcjPDIu7cD+YzY= github.com/containerd/containerd v1.5.3/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= +github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -413,20 +435,21 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= -github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= -github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3 h1:rEK0juuU5idazw//KzUcL3yYwUU3DIe2OnfJwjDBqno= +github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= @@ -439,12 +462,15 @@ github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avu github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 h1:Z8LR4yJgA3vaXpBiUWcWdQZpB9h+dzj4NdLjP9Sfdb0= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libnetwork v0.8.0-dev.2.0.20200917202933-d0951081b35f/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -456,15 +482,14 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -485,10 +510,9 @@ github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -497,6 +521,7 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= @@ -525,7 +550,6 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= @@ -550,15 +574,17 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+ github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -584,8 +610,9 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= @@ -628,8 +655,6 @@ github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQ github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= -github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= @@ -643,18 +668,18 @@ github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSC github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= -github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= -github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= -github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= +github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= @@ -663,13 +688,12 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= @@ -687,8 +711,9 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -696,6 +721,8 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -713,6 +740,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -740,11 +768,14 @@ github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunE github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -754,6 +785,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= @@ -778,12 +810,19 @@ github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -802,8 +841,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -840,6 +881,7 @@ github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= @@ -853,9 +895,7 @@ github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFi github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY= github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -893,12 +933,13 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 h1:nQ1QIkLxAoFsYyjvpQH3gVIy3mHfI0WDYEWdmwwxSG0= github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50/go.mod h1:Pp3sd2tx3j9qC7Ij6jGh5phZwTrI+/HUBK90f3Cn2CI= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -908,7 +949,6 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= @@ -934,9 +974,11 @@ github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xl github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -959,6 +1001,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= +github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= @@ -976,10 +1020,12 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -1003,25 +1049,30 @@ github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1041,16 +1092,15 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= @@ -1067,6 +1117,7 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= @@ -1081,13 +1132,15 @@ github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1D github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mittwald/go-helm-client v0.8.0 h1:k8yMvoWE7C+jJgw6iodJ0U5nY4hVpLU20VIpR+FfyJk= -github.com/mittwald/go-helm-client v0.8.0/go.mod h1:+Ks5OpzB0hJw91W8o/8G+FmjUtjrIXxPuJjE87XG/G0= +github.com/mittwald/go-helm-client v0.8.2 h1:tWPe+e4Bx2bAC0lY55nxuOxOvNEP4aBfCdizvGpM24U= +github.com/mittwald/go-helm-client v0.8.2/go.mod h1:DVtyXr7KDPIT44X320DkxF39nfbZn8KdjvibnN7iHnQ= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.9.1 h1:6noq8jvkaRs3OQGTIDf5U5Etkgq6zsPmQHGWi5yhh88= github.com/moby/buildkit v0.9.1/go.mod h1:oVZKk3TMm0MlDx7XxnlF0wKmcpyrzOs9GEp0VXKWFPk= @@ -1132,15 +1185,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsqio/go-nsq v1.0.7 h1:O0pIZJYTf+x7cZBA0UMY8WxFG79lYTURmWzAAh48ljY= github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito= @@ -1149,13 +1195,10 @@ github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWk github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -1194,30 +1237,27 @@ github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2r github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE= -github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -1225,22 +1265,20 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc= github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1248,7 +1286,6 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -1262,7 +1299,6 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -1270,7 +1306,6 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -1279,7 +1314,6 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= @@ -1311,13 +1345,10 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4= +github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1326,7 +1357,6 @@ github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0f github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -1365,24 +1395,27 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1395,14 +1428,13 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= -github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1444,6 +1476,7 @@ github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPf github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= @@ -1486,7 +1519,6 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17 github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= @@ -1516,10 +1548,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/gorelic v0.0.7 h1:4DTF1WOM2ZZS/xMOkTFBOcb6XiHu/PKn3rVo6dbewQE= github.com/yvasiyarov/gorelic v0.0.7/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 h1:AsFN8kXcCVkUFHyuzp1FtYbzp1nCO/H6+1uPSGEyPzM= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= @@ -1527,8 +1562,16 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -1539,15 +1582,17 @@ go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib v0.21.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.21.0/go.mod h1:Vm5u/mtkj1OMhtao0v+BGo2LUoLCgHYXvRmj0jWITlE= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0/go.mod h1:a9cocRplhIBkUAJmak+BPDx+LVL7cTmqUPB0uBcTA4k= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= @@ -1576,18 +1621,14 @@ go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee33 go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= @@ -1610,23 +1651,25 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1653,6 +1696,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= @@ -1664,6 +1708,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1710,19 +1756,27 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1732,8 +1786,15 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 h1:0Ja1LBD+yisY6RWM/BH7TJVXWsSjs2VwBSmvSX4HdBc= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1773,7 +1834,6 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1801,7 +1861,6 @@ golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1818,17 +1877,21 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1836,15 +1899,21 @@ golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1916,10 +1985,7 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1931,7 +1997,6 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1942,6 +2007,7 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= @@ -1953,9 +2019,18 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= @@ -1972,12 +2047,12 @@ gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCY gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= @@ -1995,6 +2070,15 @@ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2005,6 +2089,7 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -2034,23 +2119,39 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c h1:wtujag7C+4D6KMoulW9YauvK2lgdvCMS260jsqqBXr0= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= @@ -2061,10 +2162,14 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= @@ -2085,15 +2190,16 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= @@ -2103,8 +2209,9 @@ gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mholt/archiver.v3 v3.1.1 h1:sLb5X5fPPc9oU6YatdC+vbY46irElT3kPR0wOS7bjR4= gopkg.in/mholt/archiver.v3 v3.1.1/go.mod h1:EwJV1tr6GQdNZ5IFFVakh0aYAjGLVjqvFVL/b8ZVdMw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= @@ -2116,7 +2223,6 @@ gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -2129,6 +2235,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= @@ -2137,8 +2244,9 @@ gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= -helm.sh/helm/v3 v3.6.2 h1:7YbEhLC6AUgmcDoh5BGFUGNtR/o43pr0AIelSNjhuFU= -helm.sh/helm/v3 v3.6.2/go.mod h1:mIIus8EOqj+obtycw3sidsR4ORr2aFDmXMSI3k+oeVY= +helm.sh/helm/v3 v3.7.0/go.mod h1:DajHtQTe8KrjNmvy5gxWkosFKaADrS3uRS5EkDtsmI4= +helm.sh/helm/v3 v3.7.1 h1:kED/HWx09QHHSJhYaJY6ttj/BhmzBmT1oupKslncibY= +helm.sh/helm/v3 v3.7.1/go.mod h1:3eOeBD3Z+O/ELiuu19zynZSN8jP1ErXLuyP21SZeMq8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -2147,6 +2255,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= @@ -2158,8 +2267,9 @@ k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.21.0 h1:1hWMfsz+cXxB77k6/y0XxWxwl6l9OF26PC9QneUVn1Q= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= +k8s.io/apiserver v0.22.1 h1:Ul9Iv8OMB2s45h2tl5XWPpAZo1VPIJ/6N+MESeed7L8= +k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= @@ -2197,8 +2307,9 @@ k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= @@ -2219,6 +2330,8 @@ mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIa mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= +oras.land/oras-go v0.4.0 h1:u6+7D+raZDYHwlz/uOwNANiRmyYDSSMW7A9E1xXycUQ= +oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg= pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= @@ -2227,24 +2340,27 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= -sigs.k8s.io/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ= sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= +sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8= +sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= -sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI= sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/kustomize/kyaml v0.11.0 h1:9KhiCPKaVyuPcgOLJXkvytOvjMJLoxpjodiycb4gHsA= +sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= -- Gitee From b8858a536a4246e924c66049cf511328430675d5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Oct 2021 14:41:49 +0800 Subject: [PATCH 0182/2647] added dockerfile template to build Signed-off-by: Min Min --- .../core/common/repository/models/build.go | 4 + .../core/common/repository/mongodb/build.go | 15 ++++ .../core/templatestore/service/dockerfile.go | 76 ++++++++++++++----- .../aslan/core/templatestore/service/types.go | 5 ++ pkg/setting/consts.go | 5 ++ 5 files changed, 88 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index e808b529..518366f8 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -96,6 +96,10 @@ type DockerBuild struct { DockerFile string `bson:"docker_file" json:"docker_file"` // BuildArgs docker build args BuildArgs string `bson:"build_args,omitempty" json:"build_args"` + // Source whether dockerfile comes from template or existing file + Source string `bson:"source" json:"source"` + // TemplateId is the id of the template dockerfile + TemplateId string `bson:"template_id" json:"template_id"` } type JenkinsBuild struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/build.go b/pkg/microservice/aslan/core/common/repository/mongodb/build.go index a1402eaa..cee41677 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/build.go @@ -266,3 +266,18 @@ func (c *BuildColl) DistinctTargets(excludeModule []string, productName string) return resp, err } + +func (c *BuildColl) GetDockerfileTemplateReference(templateID string) ([]*models.Build, error) { + ret := make([]*models.Build, 0) + query := bson.M{"post_build.docker_build.template_id": templateID} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.Decode(&ret) + if err != nil { + return nil, err + } + return ret, nil +} diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index e91803a8..fd5ba506 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -1,12 +1,19 @@ package service import ( + "errors" + "fmt" "strings" "go.uber.org/zap" + dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" + dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" + + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" ) func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredLogger) error { @@ -56,7 +63,10 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %+v", id, err) return nil, err } - variables := getVariables(dockerfileTemplate.Content, logger) + variables, err := getVariables(dockerfileTemplate.Content, logger) + if err != nil { + return nil, errors.New("failed to get variables from dockerfile") + } resp.ID = dockerfileTemplate.ID.Hex() resp.Name = dockerfileTemplate.Name resp.Content = dockerfileTemplate.Content @@ -72,24 +82,56 @@ func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { return err } -func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*DockerfileDetail, error) { - return []*DockerfileDetail{}, nil +func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*BuildReference, error) { + ret := make([]*BuildReference, 0) + referenceList, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %+v", id, err) + return ret, err + } + for _, reference := range referenceList { + ret = append(ret, &BuildReference{ + BuildName: reference.Name, + ProjectName: reference.ProductName, + }) + } + return ret, nil } -func getVariables(s string, logger *zap.SugaredLogger) []*Variable { - lines := strings.Split(s, "\n") - for i, line := range lines { - lineWithoutSpace := strings.TrimSpace(line) - logger.Infof(">>>>>>>>>> line [%d] is: %s <<<<<<<<<<<<<", i, lineWithoutSpace) - if strings.HasPrefix(lineWithoutSpace, "ARG") { - logger.Infof("!!!!!!!!!!! line [%d] has prefix ARG !!!!!!!!", i) - logger.Infof("Proceeding to find variables") +func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { + ret := make([]*Variable, 0) + reader := strings.NewReader(s) + result, err := dockerfileparser.Parse(reader) + if err != nil { + logger.Errorf("Failed to parse the dockerfile from source, the error is: %+v", err) + return []*Variable{}, err + } + stages, _, err := dockerfileinstructions.Parse(result.AST) + if err != nil { + logger.Errorf("Failed to parse stages from generated dockerfile AST, the error is: %+v", err) + return []*Variable{}, err + } + keyMap := make(map[string]int) + for _, stage := range stages { + for _, command := range stage.Commands { + if command.Name() == setting.DockerfileCmdArg { + fullCommand := fmt.Sprintf("%s", command) + commandContent := strings.Split(fullCommand, " ")[1] + kv := strings.Split(commandContent, "=") + key := kv[0] + value := "" + if len(kv) > 1 { + value = kv[1] + } + // if key has not been added yet + if keyMap[key] == 0 { + ret = append(ret, &Variable{ + Key: key, + Value: value, + }) + } + } } } - //reader := strings.NewReader(s) - //result, err := dockerfileparser.Parse(reader) - //if err != nil { - // return nil - //} - return []*Variable{} + return ret, nil } diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index 51d35326..3437aa05 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -50,3 +50,8 @@ type Variable struct { Key string `json:"key"` Value string `json:"value"` } + +type BuildReference struct { + BuildName string `json:"build_name"` + ProjectName string `json:"project_name"` +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 95ca280b..cf8ce814 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -474,6 +474,11 @@ const ( AliyunHost = ".aliyuncs.com" ) +// Dockerfile parsing consts +const ( + DockerfileCmdArg = "ARG" +) + const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" -- Gitee From 056eeb30b3912e189548b76ec42b449a8dc1fa71 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Oct 2021 15:29:39 +0800 Subject: [PATCH 0183/2647] add validation API for dockerfile template Signed-off-by: Min Min --- .../core/templatestore/handler/dockerfile.go | 17 +++++++++++++++++ .../aslan/core/templatestore/handler/router.go | 1 + .../core/templatestore/service/dockerfile.go | 14 ++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 5d814485..5c226bf2 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -119,3 +119,20 @@ func GetDockerfileTemplateReference(c *gin.Context) { ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateReference(c.Param("id"), ctx.Logger) } + +type validateDockerfileTemplateReq struct { + Content string `json:"content"` +} + +func ValidateDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &validateDockerfileTemplateReq{} + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 9f8f188a..b26efc3f 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -49,5 +49,6 @@ func (*Router) Inject(router *gin.RouterGroup) { dockerfile.GET("/:id", GetDockerfileTemplateDetail) dockerfile.DELETE("/:id", DeleteDockerfileTemplate) dockerfile.GET("/:id/reference", GetDockerfileTemplateReference) + dockerfile.POST("/validation", ValidateDockerfileTemplate) } } diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index fd5ba506..42fc47b7 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -98,6 +98,20 @@ func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*Bu return ret, nil } +func ValidateDockerfileTemplate(template string, logger *zap.SugaredLogger) error { + // some dockerfile validation stuff + reader := strings.NewReader(template) + result, err := dockerfileparser.Parse(reader) + if err != nil { + return err + } + _, _, err = dockerfileinstructions.Parse(result.AST) + if err != nil { + return err + } + return nil +} + func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { ret := make([]*Variable, 0) reader := strings.NewReader(s) -- Gitee From 3013ea3c8417ec5b1bec41aee0fe7c9159a96d18 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Oct 2021 15:55:24 +0800 Subject: [PATCH 0184/2647] changed validation api response Signed-off-by: Min Min --- .../aslan/core/templatestore/handler/dockerfile.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 5c226bf2..0c2c6b9c 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -124,6 +124,10 @@ type validateDockerfileTemplateReq struct { Content string `json:"content"` } +type validateDockerfileTemplateResp struct { + Error string `json:"error"` +} + func ValidateDockerfileTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -134,5 +138,9 @@ func ValidateDockerfileTemplate(c *gin.Context) { return } - ctx.Err = templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) + err := templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) + ctx.Resp = &validateDockerfileTemplateResp{Error: ""} + if err != nil { + ctx.Resp = &validateDockerfileTemplateResp{Error: err.Error()} + } } -- Gitee From 58585d8b9234c2aab14622f942b80c5904cbeb8a Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Oct 2021 18:09:29 +0800 Subject: [PATCH 0185/2647] get reference api fixed Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/repository/models/build.go | 2 ++ .../aslan/core/common/repository/models/task/docker_build.go | 2 ++ pkg/microservice/aslan/core/common/repository/mongodb/build.go | 2 +- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 ++ pkg/microservice/warpdrive/core/service/types/task/build.go | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 518366f8..8c8ab739 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -100,6 +100,8 @@ type DockerBuild struct { Source string `bson:"source" json:"source"` // TemplateId is the id of the template dockerfile TemplateId string `bson:"template_id" json:"template_id"` + // TemplateName is the name of the template dockerfile + TemplateName string `bson:"template_name" json:"template_name"` } type JenkinsBuild struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go index fe616273..8d334a4f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go @@ -54,6 +54,8 @@ func (db *DockerBuild) ToSubTask() (map[string]interface{}, error) { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/release-candidates/image:tag type DockerBuildCtx struct { + Source string `yaml:"source" bson:"source" json:"source"` + TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/build.go b/pkg/microservice/aslan/core/common/repository/mongodb/build.go index cee41677..8dc74f0c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/build.go @@ -275,7 +275,7 @@ func (c *BuildColl) GetDockerfileTemplateReference(templateID string) ([]*models if err != nil { return nil, err } - err = cursor.Decode(&ret) + err = cursor.All(context.TODO(), &ret) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index a9f52013..cdef3ed3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1728,6 +1728,8 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ + Source: module.PostBuild.DockerBuild.Source, + TemplateID: module.PostBuild.DockerBuild.TemplateId, WorkDir: module.PostBuild.DockerBuild.WorkDir, DockerFile: module.PostBuild.DockerBuild.DockerFile, BuildArgs: module.PostBuild.DockerBuild.BuildArgs, diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index bb8c4b69..5c296f7e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -205,6 +205,8 @@ type DockerBuildCtx struct { ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` + Source string `yaml:"source" bson:"source" json:"source"` + TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` } type FileArchiveCtx struct { -- Gitee From f1520042665b2d81b751c027fe302f74b3ceff61 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 00:41:29 +0800 Subject: [PATCH 0186/2647] added dockerfile template function to workflow Signed-off-by: Min Min --- .../reaper/core/service/client/client.go | 24 ++++++++++ .../reaper/core/service/client/dockerfile.go | 44 +++++++++++++++++++ .../reaper/core/service/meta/types.go | 13 ++++-- .../reaper/core/service/reaper/reaper.go | 26 +++++++++++ .../core/service/taskplugin/docker_build.go | 2 + .../warpdrive/core/service/taskplugin/job.go | 2 + .../core/service/types/task/docker_build.go | 2 + pkg/setting/consts.go | 8 ++++ 8 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 pkg/microservice/reaper/core/service/client/client.go create mode 100644 pkg/microservice/reaper/core/service/client/dockerfile.go diff --git a/pkg/microservice/reaper/core/service/client/client.go b/pkg/microservice/reaper/core/service/client/client.go new file mode 100644 index 00000000..ab19330e --- /dev/null +++ b/pkg/microservice/reaper/core/service/client/client.go @@ -0,0 +1,24 @@ +package client + +import ( + "net/http" + "net/http/cookiejar" +) + +type Client struct { + APIBase string + Token string + Conn *http.Client +} + +// NewAslanClient is to get aslan client func +func NewAslanClient(host string) *Client { + jar, _ := cookiejar.New(nil) + + c := &Client{ + APIBase: host, + Conn: &http.Client{Transport: http.DefaultTransport, Jar: jar}, + } + + return c +} diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go new file mode 100644 index 00000000..ec4da5f6 --- /dev/null +++ b/pkg/microservice/reaper/core/service/client/dockerfile.go @@ -0,0 +1,44 @@ +package client + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" + + "github.com/koderover/zadig/pkg/tool/log" +) + +type Dockerfile struct { + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` +} + +func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { + resp := new(Dockerfile) + var err error + + url := fmt.Sprintf("%s/template/dockerfile/%s", c.APIBase, id) + request, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Errorf("new http request error: %v", err) + return nil, err + } + // No auth required + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + var body []byte + body, err = ioutil.ReadAll(ret.Body) + if err == nil { + if err = json.Unmarshal(body, &resp); err == nil { + return resp, nil + } + } + } + + return resp, errors.WithMessage(err, "failed to list pipeline") +} diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 3151b477..431be9a8 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -24,6 +24,7 @@ import ( "strings" "github.com/koderover/zadig/pkg/microservice/reaper/config" + "github.com/koderover/zadig/pkg/setting" ) // Context ... @@ -127,14 +128,20 @@ type ArtifactInfo struct { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/spock-release-candidates/image:tag type DockerBuildCtx struct { - WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` + Source string `yaml:"source" bson:"source" json:"source"` + TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` - ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` - BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` + ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` + BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` } func (c *DockerBuildCtx) GetDockerFile() string { + // if the source of the dockerfile is from template, we write our own dockerfile + if c.Source == "template" { + return fmt.Sprintf("/%s", setting.ZadigDockerfilePath) + } if c.DockerFile == "" { return "Dockerfile" } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index fdf015bc..f0ba8ad0 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -19,6 +19,7 @@ package reaper import ( "bytes" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -29,11 +30,14 @@ import ( "gopkg.in/yaml.v3" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/reaper/config" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/archive" + "github.com/koderover/zadig/pkg/microservice/reaper/core/service/client" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/meta" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util/fs" ) const ( @@ -301,6 +305,10 @@ func (r *Reaper) dockerCommands() []*exec.Cmd { func (r *Reaper) runDockerBuild() error { if r.Ctx.DockerBuildCtx != nil { + err := r.prepareDockerfile() + if err != nil { + return err + } if r.Ctx.Proxy != nil { r.setProxy(r.Ctx.DockerBuildCtx, r.Ctx.Proxy) } @@ -320,6 +328,24 @@ func (r *Reaper) runDockerBuild() error { return nil } +func (r *Reaper) prepareDockerfile() error { + if r.Ctx.DockerBuildCtx.Source == setting.DockerfileSourceTemplate { + aslanClient := client.NewAslanClient(configbase.AslanServiceAddress()) + dockerfile, err := aslanClient.GetDockerfile(r.Ctx.DockerBuildCtx.TemplateID) + if err != nil { + return err + } + reader := strings.NewReader(dockerfile.Content) + readcloser := io.NopCloser(reader) + path := fmt.Sprintf("/%s", setting.ZadigDockerfilePath) + err = fs.SaveFile(readcloser, path) + if err != nil { + return err + } + } + return nil +} + // Exec ... func (r *Reaper) Exec() error { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index b963e393..cc02d9cb 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -134,6 +134,8 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi JobType: setting.BuildImageJob, //Docker build context DockerBuildCtx: &task.DockerBuildCtx{ + Source: p.Task.Source, + TemplateID: p.Task.TemplateID, WorkDir: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.WorkDir), DockerFile: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.DockerFile), ImageName: p.Task.Image, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 8c566d90..e4eee4ec 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -283,6 +283,8 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.DockerBuildCtx != nil { ctx.DockerBuildCtx = &task.DockerBuildCtx{ + Source: b.JobCtx.DockerBuildCtx.Source, + TemplateID: b.JobCtx.DockerBuildCtx.TemplateID, WorkDir: b.JobCtx.DockerBuildCtx.WorkDir, DockerFile: b.JobCtx.DockerBuildCtx.DockerFile, ImageName: b.JobCtx.DockerBuildCtx.ImageName, diff --git a/pkg/microservice/warpdrive/core/service/types/task/docker_build.go b/pkg/microservice/warpdrive/core/service/types/task/docker_build.go index 88eb7dcb..f1e97a6e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/docker_build.go @@ -23,6 +23,8 @@ import ( ) type DockerBuild struct { + Source string `bson:"source" json:"source"` // Dockerfile source + TemplateID string `bson:"template_id" json:"template_id"` TaskType config.TaskType `bson:"type" json:"type"` Enabled bool `bson:"enabled" json:"enabled"` TaskStatus config.Status `bson:"status" json:"status"` diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index cf8ce814..8d2a801d 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -479,6 +479,14 @@ const ( DockerfileCmdArg = "ARG" ) +// Dockerfile template constant +const ( + DockerfileSourceLocal = "local" + DockerfileSourceTemplate = "template" + + ZadigDockerfilePath = "zadig-dockerfile" +) + const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" -- Gitee From 8a9fcf6f93a465234fc478f5efd1b677609a6146 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 11:39:04 +0800 Subject: [PATCH 0187/2647] added auth token for reaper Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/repository/models/queue.go | 2 +- pkg/microservice/aslan/core/common/service/config_payload.go | 1 + pkg/microservice/reaper/core/service/client/client.go | 3 ++- pkg/microservice/reaper/core/service/client/dockerfile.go | 2 ++ pkg/microservice/reaper/core/service/meta/types.go | 2 ++ pkg/microservice/warpdrive/core/service/taskplugin/job.go | 1 + pkg/microservice/warpdrive/core/service/types/reaper.go | 2 ++ .../warpdrive/core/service/types/task/config_payload.go | 1 + 8 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index d6279a8b..a39e2223 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -111,7 +111,7 @@ type ServiceTaskArgs struct { } type ConfigPayload struct { - Aslan AslanConfig `json:"aslan"` + APIToken string `json:"api_token"` Proxy Proxy `json:"proxy"` S3Storage S3Config `json:"s3_storage"` Github GithubConfig `json:"github"` diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 7c15bd52..ec01b0c7 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -26,6 +26,7 @@ import ( func GetConfigPayload(codeHostID int) *models.ConfigPayload { payload := &models.ConfigPayload{ + APIToken: config.PoetryAPIRootKey(), S3Storage: models.S3Config{ Ak: config.S3StorageAK(), Sk: config.S3StorageSK(), diff --git a/pkg/microservice/reaper/core/service/client/client.go b/pkg/microservice/reaper/core/service/client/client.go index ab19330e..bd90314d 100644 --- a/pkg/microservice/reaper/core/service/client/client.go +++ b/pkg/microservice/reaper/core/service/client/client.go @@ -12,10 +12,11 @@ type Client struct { } // NewAslanClient is to get aslan client func -func NewAslanClient(host string) *Client { +func NewAslanClient(host, token string) *Client { jar, _ := cookiejar.New(nil) c := &Client{ + Token: token, APIBase: host, Conn: &http.Client{Transport: http.DefaultTransport, Jar: jar}, } diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go index ec4da5f6..3f3b5066 100644 --- a/pkg/microservice/reaper/core/service/client/dockerfile.go +++ b/pkg/microservice/reaper/core/service/client/dockerfile.go @@ -8,6 +8,7 @@ import ( "github.com/pkg/errors" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -27,6 +28,7 @@ func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { log.Errorf("new http request error: %v", err) return nil, err } + request.Header.Set(setting.AuthorizationHeader, fmt.Sprintf("%s %s", setting.RootAPIKey, c.Token)) // No auth required var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 431be9a8..bf3e5be3 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -29,6 +29,8 @@ import ( // Context ... type Context struct { + // API token 服务访问使用的api token + APIToken string `yaml:"api_token"` // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index e4eee4ec..9add54d1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -174,6 +174,7 @@ func replaceWrapLine(script string) string { func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName string) *types.Context { ctx := &types.Context{ + APIToken: pipelineTask.ConfigPayload.APIToken, Workspace: b.PipelineCtx.Workspace, CleanWorkspace: b.JobCtx.CleanWorkspace, IgnoreCache: pipelineTask.ConfigPayload.IgnoreCache, diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index df689bea..9ed4b2fd 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -27,6 +27,8 @@ import ( // Context ... type Context struct { + // API token 服务访问使用的api token + APIToken string `yaml:"api_token"` // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 2692dbc3..0dfdbcda 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -21,6 +21,7 @@ import ( ) type ConfigPayload struct { + APIToken string `json:"api_token"` Proxy Proxy `json:"proxy"` S3Storage S3Config `json:"s3_storage"` Github GithubConfig `json:"github"` -- Gitee From 6c10f9d602cf96a6d1e3aecc64f908a984b3be49 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 11:45:22 +0800 Subject: [PATCH 0188/2647] reaper bugfix Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index f0ba8ad0..e1ee4dfe 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -330,7 +330,7 @@ func (r *Reaper) runDockerBuild() error { func (r *Reaper) prepareDockerfile() error { if r.Ctx.DockerBuildCtx.Source == setting.DockerfileSourceTemplate { - aslanClient := client.NewAslanClient(configbase.AslanServiceAddress()) + aslanClient := client.NewAslanClient(configbase.AslanServiceAddress(), r.Ctx.APIToken) dockerfile, err := aslanClient.GetDockerfile(r.Ctx.DockerBuildCtx.TemplateID) if err != nil { return err -- Gitee From d83c65d737a2957794e35d42fe8c92a13624ccbd Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 14:01:30 +0800 Subject: [PATCH 0189/2647] added some debug information Signed-off-by: Min Min --- .../aslan/core/common/repository/models/queue.go | 10 +++++----- .../warpdrive/core/service/taskplugin/job.go | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index a39e2223..cf8fc7a0 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -164,15 +164,15 @@ type S3Config struct { type GithubConfig struct { // github API access token - AccessToken string + AccessToken string `json:"access_token"` // github ssh key with base64 encoded - SSHKey string + SSHKey string `json:"ssh_key"` // github knownhost - KnownHost string + KnownHost string `json:"known_host"` // github app private key - AppKey string + AppKey string `json:"app_key"` // gihhub app id - AppID int + AppID int `json:"app_id"` } type GitlabConfig struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 9add54d1..72a4fac3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -210,7 +210,9 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, } - + fmt.Println("github ssh key is:", pipelineTask.ConfigPayload.Github.SSHKey) + fmt.Println("gitlab ssh key is:", pipelineTask.ConfigPayload.Gitlab.SSHKey) + fmt.Println("git known host is:", pipelineTask.ConfigPayload.GetGitKnownHost()) for _, install := range b.Installs { inst := &types.Install{ // TODO: 之后可以适配 install.Scripts 为[]string -- Gitee From 478bbac43260d1327929c717a1c16289f96c6e8c Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 14:24:18 +0800 Subject: [PATCH 0190/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/client/dockerfile.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go index 3f3b5066..b6660970 100644 --- a/pkg/microservice/reaper/core/service/client/dockerfile.go +++ b/pkg/microservice/reaper/core/service/client/dockerfile.go @@ -23,6 +23,7 @@ func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { var err error url := fmt.Sprintf("%s/template/dockerfile/%s", c.APIBase, id) + fmt.Println("url is:", url) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("new http request error: %v", err) @@ -35,6 +36,7 @@ func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { defer func() { _ = ret.Body.Close() }() var body []byte body, err = ioutil.ReadAll(ret.Body) + fmt.Println("response body is:", string(body)) if err == nil { if err = json.Unmarshal(body, &resp); err == nil { return resp, nil @@ -42,5 +44,5 @@ func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { } } - return resp, errors.WithMessage(err, "failed to list pipeline") + return resp, errors.WithMessage(err, "failed to get dockerfile template") } -- Gitee From c43733baa779a1835527a46e16b02f94cd15ef7e Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 15:01:30 +0800 Subject: [PATCH 0191/2647] removed debug logs & bugfix Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/client/dockerfile.go | 4 +--- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go index b6660970..299d6b87 100644 --- a/pkg/microservice/reaper/core/service/client/dockerfile.go +++ b/pkg/microservice/reaper/core/service/client/dockerfile.go @@ -22,8 +22,7 @@ func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { resp := new(Dockerfile) var err error - url := fmt.Sprintf("%s/template/dockerfile/%s", c.APIBase, id) - fmt.Println("url is:", url) + url := fmt.Sprintf("%s/api/template/dockerfile/%s", c.APIBase, id) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("new http request error: %v", err) @@ -36,7 +35,6 @@ func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { defer func() { _ = ret.Body.Close() }() var body []byte body, err = ioutil.ReadAll(ret.Body) - fmt.Println("response body is:", string(body)) if err == nil { if err = json.Unmarshal(body, &resp); err == nil { return resp, nil diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 72a4fac3..1d13553e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -210,9 +210,6 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, } - fmt.Println("github ssh key is:", pipelineTask.ConfigPayload.Github.SSHKey) - fmt.Println("gitlab ssh key is:", pipelineTask.ConfigPayload.Gitlab.SSHKey) - fmt.Println("git known host is:", pipelineTask.ConfigPayload.GetGitKnownHost()) for _, install := range b.Installs { inst := &types.Install{ // TODO: 之后可以适配 install.Scripts 为[]string -- Gitee From 7f1390b4ccd6b1ce37088f32b4d8a4525021a529 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 15:13:27 +0800 Subject: [PATCH 0192/2647] add debug logs to prevent nil Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1d13553e..72a4fac3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -210,6 +210,9 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, } + fmt.Println("github ssh key is:", pipelineTask.ConfigPayload.Github.SSHKey) + fmt.Println("gitlab ssh key is:", pipelineTask.ConfigPayload.Gitlab.SSHKey) + fmt.Println("git known host is:", pipelineTask.ConfigPayload.GetGitKnownHost()) for _, install := range b.Installs { inst := &types.Install{ // TODO: 之后可以适配 install.Scripts 为[]string -- Gitee From 49d62555a95785438bb9238cad14b17c0d1238e7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 16:00:02 +0800 Subject: [PATCH 0193/2647] removed logs & finalize Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 72a4fac3..1d13553e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -210,9 +210,6 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, } - fmt.Println("github ssh key is:", pipelineTask.ConfigPayload.Github.SSHKey) - fmt.Println("gitlab ssh key is:", pipelineTask.ConfigPayload.Gitlab.SSHKey) - fmt.Println("git known host is:", pipelineTask.ConfigPayload.GetGitKnownHost()) for _, install := range b.Installs { inst := &types.Install{ // TODO: 之后可以适配 install.Scripts 为[]string -- Gitee From deb4f043c7eb6c19f091104136efb69ea3c6fdb9 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 16:09:47 +0800 Subject: [PATCH 0194/2647] set name of dockerfile template as unique Signed-off-by: Min Min --- .../core/templatestore/repository/mongodb/dockerfile.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go index d35ba227..a904efc5 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go @@ -33,7 +33,14 @@ func (c *DockerfileTemplateColl) GetCollectionName() string { } func (c *DockerfileTemplateColl) EnsureIndex(ctx context.Context) error { - return nil + mod := mongo.IndexModel{ + Keys: bson.M{"name": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err } func (c *DockerfileTemplateColl) Create(obj *models.DockerfileTemplate) error { -- Gitee From 5cd055aa4ff0c7c08eb89f4790fe1a6fabe7a3e3 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Oct 2021 17:49:35 +0800 Subject: [PATCH 0195/2647] code review changes Signed-off-by: Min Min --- .../core/common/repository/models/build.go | 2 +- .../core/templatestore/handler/dockerfile.go | 21 ++++++++----------- .../core/templatestore/service/dockerfile.go | 6 +++--- .../service/workflow/workflow_task.go | 2 +- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 8c8ab739..c5288acc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -99,7 +99,7 @@ type DockerBuild struct { // Source whether dockerfile comes from template or existing file Source string `bson:"source" json:"source"` // TemplateId is the id of the template dockerfile - TemplateId string `bson:"template_id" json:"template_id"` + TemplateID string `bson:"template_id" json:"template_id"` // TemplateName is the name of the template dockerfile TemplateName string `bson:"template_name" json:"template_name"` } diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 0c2c6b9c..c0434488 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -2,7 +2,6 @@ package handler import ( "errors" - "strconv" "strings" "github.com/gin-gonic/gin" @@ -67,6 +66,11 @@ func UpdateDockerfileTemplate(c *gin.Context) { ctx.Err = templateservice.UpdateDockerfileTemplate(c.Param("id"), req, ctx.Logger) } +type listDockerfileQuery struct { + PageSize int `json:"page_size" form:"page_size,default=100"` + PageNum int `json:"page_num" form:"page_num,default=1"` +} + type ListDockefileResp struct { DockerfileTemplates []*templateservice.DockerfileListObject `json:"dockerfile_template"` Total int `json:"total"` @@ -77,20 +81,13 @@ func ListDockerfileTemplate(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() // Query Verification - pageSizeString := c.DefaultQuery("page_size", "100") - pageSize, err := strconv.Atoi(pageSizeString) - if err != nil { - ctx.Err = errors.New("invalid page_size query") - return - } - pageNumString := c.DefaultQuery("page_num", "1") - pageNum, err := strconv.Atoi(pageNumString) - if err != nil { - ctx.Err = errors.New("invalid page_num query") + args := listDockerfileQuery{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err return } - dockerfileTemplateList, total, err := templateservice.ListDockerfileTemplate(pageNum, pageSize, ctx.Logger) + dockerfileTemplateList, total, err := templateservice.ListDockerfileTemplate(args.PageNum, args.PageSize, ctx.Logger) resp := ListDockefileResp{ DockerfileTemplates: dockerfileTemplateList, Total: total, diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index 42fc47b7..90b63a87 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -22,7 +22,7 @@ func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredL Content: template.Content, }) if err != nil { - logger.Errorf("create dockerfile template error: %+v", err) + logger.Errorf("create dockerfile template error: %s", err) } return err } @@ -36,7 +36,7 @@ func UpdateDockerfileTemplate(id string, template *DockerfileTemplate, logger *z }, ) if err != nil { - logger.Errorf("update dockerfile template error: %+v", err) + logger.Errorf("update dockerfile template error: %s", err) } return err } @@ -45,7 +45,7 @@ func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([ resp := make([]*DockerfileListObject, 0) templateList, total, err := mongodb.NewDockerfileTemplateColl().List(pageNum, pageSize) if err != nil { - logger.Errorf("list dockerfile template error: %+v", err) + logger.Errorf("list dockerfile template error: %s", err) } for _, obj := range templateList { resp = append(resp, &DockerfileListObject{ diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index cdef3ed3..d3457a1e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1729,7 +1729,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ Source: module.PostBuild.DockerBuild.Source, - TemplateID: module.PostBuild.DockerBuild.TemplateId, + TemplateID: module.PostBuild.DockerBuild.TemplateID, WorkDir: module.PostBuild.DockerBuild.WorkDir, DockerFile: module.PostBuild.DockerBuild.DockerFile, BuildArgs: module.PostBuild.DockerBuild.BuildArgs, -- Gitee From f562ba02c57bcc0009f610190c268fcf7d1a1c05 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 21 Oct 2021 15:35:04 +0800 Subject: [PATCH 0196/2647] code review changes Signed-off-by: Min Min --- .../core/templatestore/handler/dockerfile.go | 19 ++----------------- .../core/templatestore/service/dockerfile.go | 14 +++++++------- .../reaper/core/service/meta/types.go | 2 +- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index c0434488..37eac492 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -2,11 +2,8 @@ package handler import ( "errors" - "strings" "github.com/gin-gonic/gin" - dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" - dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -24,13 +21,7 @@ func CreateDockerfileTemplate(c *gin.Context) { } // some dockerfile validation stuff - reader := strings.NewReader(req.Content) - result, err := dockerfileparser.Parse(reader) - if err != nil { - ctx.Err = errors.New("invalid dockerfile, please check") - return - } - _, _, err = dockerfileinstructions.Parse(result.AST) + err := templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) if err != nil { ctx.Err = errors.New("invalid dockerfile, please check") return @@ -51,13 +42,7 @@ func UpdateDockerfileTemplate(c *gin.Context) { } // some dockerfile validation stuff - reader := strings.NewReader(req.Content) - result, err := dockerfileparser.Parse(reader) - if err != nil { - ctx.Err = errors.New("invalid dockerfile, please check") - return - } - _, _, err = dockerfileinstructions.Parse(result.AST) + err := templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) if err != nil { ctx.Err = errors.New("invalid dockerfile, please check") return diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index 90b63a87..18b098a5 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -5,10 +5,9 @@ import ( "fmt" "strings" - "go.uber.org/zap" - dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" + "go.uber.org/zap" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" @@ -46,6 +45,7 @@ func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([ templateList, total, err := mongodb.NewDockerfileTemplateColl().List(pageNum, pageSize) if err != nil { logger.Errorf("list dockerfile template error: %s", err) + return resp, 0, err } for _, obj := range templateList { resp = append(resp, &DockerfileListObject{ @@ -60,7 +60,7 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf resp := new(DockerfileDetail) dockerfileTemplate, err := mongodb.NewDockerfileTemplateColl().GetById(id) if err != nil { - logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %+v", id, err) + logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) return nil, err } variables, err := getVariables(dockerfileTemplate.Content, logger) @@ -77,7 +77,7 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { err := mongodb.NewDockerfileTemplateColl().DeleteByID(id) if err != nil { - logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %+v", id, err) + logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) } return err } @@ -86,7 +86,7 @@ func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*Bu ret := make([]*BuildReference, 0) referenceList, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) if err != nil { - logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %+v", id, err) + logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) return ret, err } for _, reference := range referenceList { @@ -117,12 +117,12 @@ func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { reader := strings.NewReader(s) result, err := dockerfileparser.Parse(reader) if err != nil { - logger.Errorf("Failed to parse the dockerfile from source, the error is: %+v", err) + logger.Errorf("Failed to parse the dockerfile from source, the error is: %s", err) return []*Variable{}, err } stages, _, err := dockerfileinstructions.Parse(result.AST) if err != nil { - logger.Errorf("Failed to parse stages from generated dockerfile AST, the error is: %+v", err) + logger.Errorf("Failed to parse stages from generated dockerfile AST, the error is: %s", err) return []*Variable{}, err } keyMap := make(map[string]int) diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index bf3e5be3..1720cc78 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -141,7 +141,7 @@ type DockerBuildCtx struct { func (c *DockerBuildCtx) GetDockerFile() string { // if the source of the dockerfile is from template, we write our own dockerfile - if c.Source == "template" { + if c.Source == setting.DockerfileSourceTemplate { return fmt.Sprintf("/%s", setting.ZadigDockerfilePath) } if c.DockerFile == "" { -- Gitee From 2cb733c73d7ef322575732b0dcf662a78518dd9b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 13:39:14 +0800 Subject: [PATCH 0197/2647] debug Signed-off-by: Min Min --- pkg/microservice/aslan/core/templatestore/handler/dockerfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 37eac492..b01ee1b8 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -67,7 +67,7 @@ func ListDockerfileTemplate(c *gin.Context) { // Query Verification args := listDockerfileQuery{} - if err := c.ShouldBindQuery(args); err != nil { + if err := c.ShouldBindQuery(&args); err != nil { ctx.Err = err return } -- Gitee From ee4e2603fe65f73fae980d3f8fd2be8b11684ab4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 16:10:40 +0800 Subject: [PATCH 0198/2647] debug on missing arg Signed-off-by: Min Min --- .../aslan/core/templatestore/service/dockerfile.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index 18b098a5..116b0656 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -120,12 +120,23 @@ func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { logger.Errorf("Failed to parse the dockerfile from source, the error is: %s", err) return []*Variable{}, err } - stages, _, err := dockerfileinstructions.Parse(result.AST) + stages, metaArgs, err := dockerfileinstructions.Parse(result.AST) if err != nil { logger.Errorf("Failed to parse stages from generated dockerfile AST, the error is: %s", err) return []*Variable{}, err } keyMap := make(map[string]int) + for _, metaArg := range metaArgs { + for _, arg := range metaArg.Args { + if keyMap[arg.Key] == 0 { + ret = append(ret, &Variable{ + Key: arg.Key, + Value: arg.ValueString(), + }) + keyMap[arg.Key] = 1 + } + } + } for _, stage := range stages { for _, command := range stage.Commands { if command.Name() == setting.DockerfileCmdArg { @@ -143,6 +154,7 @@ func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { Key: key, Value: value, }) + keyMap[key] = 1 } } } -- Gitee From 7c455ae25ce99570a8180af4a2de726567c0377b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 16:46:00 +0800 Subject: [PATCH 0199/2647] dockerfile template Signed-off-by: Min Min --- .../aslan/core/templatestore/service/dockerfile.go | 10 +++++++++- pkg/microservice/reaper/core/service/reaper/reaper.go | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index 116b0656..2fa44246 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -75,7 +75,15 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf } func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { - err := mongodb.NewDockerfileTemplateColl().DeleteByID(id) + ref, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for template id: %s, the error is: %s", id, err) + return err + } + if len(ref) > 0 { + return errors.New("this template is in use") + } + err = mongodb.NewDockerfileTemplateColl().DeleteByID(id) if err != nil { logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index e1ee4dfe..85037b58 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -259,7 +259,7 @@ func (r *Reaper) BeforeExec() error { } func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bool) *exec.Cmd { - args := []string{"build", "--rm=true"} + args := []string{"-c", dockerExe, "build", "--rm=true"} if ignoreCache { args = append(args, "--no-cache") } @@ -273,7 +273,7 @@ func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bo } args = append(args, []string{"-t", fullImage, "-f", dockerfile, ctx}...) - return exec.Command(dockerExe, args...) + return exec.Command("sh", args...) } func (r *Reaper) setProxy(ctx *meta.DockerBuildCtx, cfg *meta.Proxy) { -- Gitee From a42f37c44017dc72d3ab4c7153fe8e7b61fcb8fb Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 17:11:45 +0800 Subject: [PATCH 0200/2647] debug Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 85037b58..d6df4ea0 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -259,7 +259,7 @@ func (r *Reaper) BeforeExec() error { } func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bool) *exec.Cmd { - args := []string{"-c", dockerExe, "build", "--rm=true"} + args := []string{"-c", "docker", "build", "--rm=true"} if ignoreCache { args = append(args, "--no-cache") } -- Gitee From 469511c9d01528a13d8b246fddc3b97ea39a0237 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 25 Oct 2021 17:17:39 +0800 Subject: [PATCH 0201/2647] modify list host ip interface Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/private_key.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index 20ab535a..faecde4c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -173,6 +173,10 @@ func (c *PrivateKeyColl) ListHostIPByArgs(args *ListHostIPArgs) ([]*models.Priva resp := make([]*models.PrivateKey, 0) ctx := context.Background() + if len(args.IDs) == 0 && len(args.Labels) == 0 { + return resp, nil + } + if len(args.IDs) > 0 { var oids []primitive.ObjectID for _, id := range args.IDs { -- Gitee From abd9e02a9735edf58c4b07135c847c71f9ac2551 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 17:25:49 +0800 Subject: [PATCH 0202/2647] change docker build command generate Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/reaper.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index d6df4ea0..f283bbe9 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -259,20 +259,22 @@ func (r *Reaper) BeforeExec() error { } func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bool) *exec.Cmd { - args := []string{"-c", "docker", "build", "--rm=true"} + args := []string{"-c"} + dockerCommand := "docker build --rm=true" if ignoreCache { - args = append(args, "--no-cache") + dockerCommand += " --no-cache" } if buildArgs != "" { for _, val := range strings.Fields(buildArgs) { if val != "" { - args = append(args, val) + dockerCommand = dockerCommand + " " + val } } } - args = append(args, []string{"-t", fullImage, "-f", dockerfile, ctx}...) + dockerCommand = dockerCommand + " -t " + fullImage + " -f " + dockerfile + ctx + args = append(args, dockerCommand) return exec.Command("sh", args...) } -- Gitee From 5fed84b407c1291de03ecdf2de377ab0fe350c41 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 17:53:21 +0800 Subject: [PATCH 0203/2647] debug Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index f283bbe9..a7a14136 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -273,7 +273,7 @@ func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bo } } - dockerCommand = dockerCommand + " -t " + fullImage + " -f " + dockerfile + ctx + dockerCommand = dockerCommand + " -t " + fullImage + " -f " + dockerfile + " " + ctx args = append(args, dockerCommand) return exec.Command("sh", args...) } -- Gitee From ab02b3e02ff1deb2e310569436837421b6d2e2f0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 25 Oct 2021 19:02:40 +0800 Subject: [PATCH 0204/2647] update the provider of privatekey Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/private_key.go | 1 + pkg/microservice/aslan/core/system/service/private_key.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index faecde4c..d05493e2 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -138,6 +138,7 @@ func (c *PrivateKeyColl) Update(id string, args *models.PrivateKey) error { "label": args.Label, "is_prod": args.IsProd, "private_key": args.PrivateKey, + "provider": args.Provider, "update_by": args.UpdateBy, "update_time": time.Now().Unix(), }} diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 34d48850..6d918e78 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -136,7 +136,7 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str currentPrivateKey.UpdateBy = username if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { if err := commonrepo.NewPrivateKeyColl().Update(privateKeys[0].ID.Hex(), currentPrivateKey); err != nil { - log.Errorf("PrivateKey.Create error: %s", err) + log.Errorf("PrivateKey.update error: %s", err) return e.ErrBulkCreatePrivateKey.AddDesc("bulk update privateKey failed") } continue -- Gitee From 3f008b0ab325e67739da1d9ee002374fd1a25cf7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 19:07:41 +0800 Subject: [PATCH 0205/2647] Added _COMMIT_ID as workflow default parameter Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/build.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 4da6b071..19f50df9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -162,6 +162,15 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) } + + if len(repo.CommitID) > 0 { + commitVar := &task.KeyVal{ + Key: fmt.Sprintf("%s_COMMIT_ID", repoName), + Value: repo.CommitID, + IsCredential: false, + } + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, commitVar) + } } jobCtx := JobCtxBuilder{ -- Gitee From 5bb52bbd195f9234010445f6e746246be2403515 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 25 Oct 2021 19:14:32 +0800 Subject: [PATCH 0206/2647] update env config Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 836873db..4952c0da 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -198,9 +198,9 @@ type EnvStatus struct { } type EnvConfig struct { - EnvName string `bson:"env_name,omitempty" json:"env_name"` - HostIDs []string `bson:"host_ids,omitempty" json:"host_ids"` - Labels []string `bson:"labels,omitempty" json:"labels"` + EnvName string `bson:"env_name" json:"env_name"` + HostIDs []string `bson:"host_ids" json:"host_ids"` + Labels []string `bson:"labels" json:"labels"` } type PmHealthCheck struct { -- Gitee From 21dfdf6bea1a58c03de59952bcb6a7cace38216a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 25 Oct 2021 19:25:16 +0800 Subject: [PATCH 0207/2647] update env config Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 4952c0da..836873db 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -198,9 +198,9 @@ type EnvStatus struct { } type EnvConfig struct { - EnvName string `bson:"env_name" json:"env_name"` - HostIDs []string `bson:"host_ids" json:"host_ids"` - Labels []string `bson:"labels" json:"labels"` + EnvName string `bson:"env_name,omitempty" json:"env_name"` + HostIDs []string `bson:"host_ids,omitempty" json:"host_ids"` + Labels []string `bson:"labels,omitempty" json:"labels"` } type PmHealthCheck struct { -- Gitee From 574e599b159a817586e5fdd8e191033a9024b89d Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 25 Oct 2021 19:39:11 +0800 Subject: [PATCH 0208/2647] add user binding api Signed-off-by: lou --- .../policy/core/handler/role_binding.go | 21 +++++++++++++++++-- .../policy/core/handler/router.go | 5 +++++ .../core/repository/models/role_binding.go | 4 ++-- .../core/repository/mongodb/role_binding.go | 6 +++--- .../policy/core/service/bundle/opa_bundle.go | 12 +++++------ .../core/service/bundle/opa_bundle_test.go | 12 +++++------ .../policy/core/service/role_binding.go | 16 +++++++------- 7 files changed, 50 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 87443742..5b102686 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -53,7 +53,7 @@ func ListRoleBindings(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.ListRoleBindings(projectName, c.Query("user"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoleBindings(projectName, "", ctx.Logger) } func DeleteRoleBinding(c *gin.Context) { @@ -97,5 +97,22 @@ func ListSystemRoleBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListRoleBindings(service.SystemScope, c.Query("user"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoleBindings(service.SystemScope, "", ctx.Logger) +} + +func ListUserBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + uid := c.Query("uid") + if uid == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("uid is empty") + return + } + projectName := c.Query("projectName") + if projectName == "" { + projectName = service.SystemScope + } + + ctx.Resp, ctx.Err = service.ListRoleBindings(c.Query("projectName"), uid, ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index dc0019f8..e53d01d0 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -62,6 +62,11 @@ func (*Router) Inject(router *gin.RouterGroup) { systemRoleBindings.DELETE("/:name", DeleteSystemRoleBinding) } + userBindings := router.Group("userbindings") + { + userBindings.GET("", ListUserBindings) + } + bundles := router.Group("bundles") { bundles.GET("/:name", DownloadBundle) diff --git a/pkg/microservice/policy/core/repository/models/role_binding.go b/pkg/microservice/policy/core/repository/models/role_binding.go index 956b1b1b..64d24dca 100644 --- a/pkg/microservice/policy/core/repository/models/role_binding.go +++ b/pkg/microservice/policy/core/repository/models/role_binding.go @@ -41,8 +41,8 @@ type RoleBinding struct { type Subject struct { // Kind of object being referenced. allowed values are "User", "Group". Kind SubjectKind `bson:"kind" json:"kind"` - // Name of the object being referenced. - Name string `bson:"name" json:"name"` + // unique identifier of the object being referenced. + UID string `bson:"uid" json:"uid"` } // RoleRef contains information that points to the role being used diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 722050fe..28e02ed0 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -78,13 +78,13 @@ func (c *RoleBindingColl) List() ([]*models.RoleBinding, error) { return res, nil } -func (c *RoleBindingColl) ListBy(projectName, user string) ([]*models.RoleBinding, error) { +func (c *RoleBindingColl) ListBy(projectName, uid string) ([]*models.RoleBinding, error) { var res []*models.RoleBinding ctx := context.Background() query := bson.M{"namespace": projectName} - if user != "" { - query["subjects.name"] = user + if uid != "" { + query["subjects.uid"] = uid query["subjects.kind"] = models.UserKind } diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index bf032b8e..855b2c03 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -77,7 +77,7 @@ type rule struct { } type roleBinding struct { - User string `json:"user"` + UID string `json:"uid"` Bindings bindings `json:"bindings"` } @@ -183,7 +183,7 @@ type roleBindings []*roleBinding func (o roleBindings) Len() int { return len(o) } func (o roleBindings) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o roleBindings) Less(i, j int) bool { - return o[i].User < o[j].User + return o[i].UID < o[j].UID } func generateOPARoles(roles []*models.Role, policies []*models.Policy) *opaRoles { @@ -227,10 +227,10 @@ func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { for _, rb := range rbs { for _, s := range rb.Subjects { if s.Kind == models.UserKind { - if _, ok := userRoleMap[s.Name]; !ok { - userRoleMap[s.Name] = make(map[string][]*roleRef) + if _, ok := userRoleMap[s.UID]; !ok { + userRoleMap[s.UID] = make(map[string][]*roleRef) } - userRoleMap[s.Name][rb.Namespace] = append(userRoleMap[s.Name][rb.Namespace], &roleRef{Name: rb.RoleRef.Name, Namespace: rb.RoleRef.Namespace}) + userRoleMap[s.UID][rb.Namespace] = append(userRoleMap[s.UID][rb.Namespace], &roleRef{Name: rb.RoleRef.Name, Namespace: rb.RoleRef.Namespace}) } } } @@ -242,7 +242,7 @@ func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { bindingsData = append(bindingsData, &binding{Namespace: n, RoleRefs: b}) } sort.Sort(bindings(bindingsData)) - data.RoleBindings = append(data.RoleBindings, &roleBinding{User: u, Bindings: bindingsData}) + data.RoleBindings = append(data.RoleBindings, &roleBinding{UID: u, Bindings: bindingsData}) } sort.Sort(data.RoleBindings) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle_test.go b/pkg/microservice/policy/core/service/bundle/opa_bundle_test.go index 62a5e86f..80c131c6 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle_test.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle_test.go @@ -59,11 +59,11 @@ var testBinding1 = ` "subjects": [ { "kind": "user", - "name": "alice" + "uid": "alice" }, { "kind": "user", - "name": "bob" + "uid": "bob" } ], "roleRef": { @@ -80,7 +80,7 @@ var testBinding2 = ` "subjects": [ { "kind": "user", - "name": "alice" + "uid": "alice" } ], "roleRef": { @@ -149,7 +149,7 @@ var expectOPARoleBindings = ` { "role_bindings": [ { - "user": "alice", + "uid": "alice", "role_refs": [ { "name": "author", @@ -162,7 +162,7 @@ var expectOPARoleBindings = ` ] }, { - "user": "bob", + "uid": "bob", "role_refs": [ { "name": "superuser", @@ -193,7 +193,7 @@ var _ = Describe("Testing generate OPA roles and bindings", func() { }) It("should work as expected", func() { - data := generateOPARoles(testRoles) + data := generateOPARoles(testRoles, nil) actual, err := json.MarshalIndent(data, "", " ") Expect(err).ShouldNot(HaveOccurred()) Expect(string(actual)).To(Equal(strings.TrimSpace(expectOPARoles))) diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index c89adb9a..4aeb6ed4 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -27,7 +27,7 @@ import ( type RoleBinding struct { Name string `json:"name"` - User string `json:"user"` + UID string `json:"uid"` Role string `json:"role"` Global bool `json:"global"` } @@ -51,7 +51,7 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er obj := &models.RoleBinding{ Name: rb.Name, Namespace: ns, - Subjects: []*models.Subject{{Kind: models.UserKind, Name: rb.User}}, + Subjects: []*models.Subject{{Kind: models.UserKind, UID: rb.UID}}, RoleRef: &models.RoleRef{ Name: role.Name, Namespace: role.Namespace, @@ -61,8 +61,9 @@ func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Create(obj) } -func ListRoleBindings(ns, user string, _ *zap.SugaredLogger) (roleBindings []*RoleBinding, err error) { - modelRoleBindings, err := mongodb.NewRoleBindingColl().ListBy(ns, user) +func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, error) { + var roleBindings []*RoleBinding + modelRoleBindings, err := mongodb.NewRoleBindingColl().ListBy(ns, uid) if err != nil { return nil, err } @@ -71,13 +72,14 @@ func ListRoleBindings(ns, user string, _ *zap.SugaredLogger) (roleBindings []*Ro roleBindings = append(roleBindings, &RoleBinding{ Name: v.Name, Role: v.RoleRef.Name, - User: v.Subjects[0].Name, + UID: v.Subjects[0].UID, Global: v.Namespace == "", }) } - return + + return roleBindings, nil } -func DeleteRoleBinding(name string, projectName string, logger *zap.SugaredLogger) error { +func DeleteRoleBinding(name string, projectName string, _ *zap.SugaredLogger) error { return mongodb.NewRoleBindingColl().Delete(name, projectName) } -- Gitee From 7fb7831f9f54c3589a565074482e6db231defb86 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 25 Oct 2021 19:54:15 +0800 Subject: [PATCH 0209/2647] user name to uid Signed-off-by: lou --- .../policy/core/service/bundle/rego/authz.rego | 12 ++++++------ .../service/bundle/rego/test_data/bindings/data.json | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 79686a41..d3bb99c2 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -22,7 +22,7 @@ allow { # Allow all valid users to visit exempted urls. allow { url_is_exempted - claims.name != "" + claims.uid != "" } # Allow admins to do anything. @@ -92,14 +92,14 @@ project_name := pn { # get all projects which are visible by current user user_projects[project] { some i - data.bindings.role_bindings[i].user == claims.name + data.bindings.role_bindings[i].uid == claims.uid project := data.bindings.role_bindings[i].bindings[_].namespace } # get all projects which are visible by all users (the user name is "*") user_projects[project] { some i - data.bindings.role_bindings[i].user == "*" + data.bindings.role_bindings[i].uid == "*" project := data.bindings.role_bindings[i].bindings[_].namespace } @@ -133,14 +133,14 @@ user_visible_projects[project] { all_roles[role_ref] { some i - data.bindings.role_bindings[i].user == claims.name + data.bindings.role_bindings[i].uid == claims.uid role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } # only roles under the given project are allowed allowed_roles[role_ref] { some i - data.bindings.role_bindings[i].user == claims.name + data.bindings.role_bindings[i].uid == claims.uid data.bindings.role_bindings[i].bindings[j].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } @@ -148,7 +148,7 @@ allowed_roles[role_ref] { # if the proejct is visible by all users (the user name is "*"), the bound roles are also allowed allowed_roles[role_ref] { some i - data.bindings.role_bindings[i].user == "*" + data.bindings.role_bindings[i].uid == "*" project := data.bindings.role_bindings[i].bindings[_].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json index fb99ca38..c13fae78 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json @@ -1,7 +1,7 @@ { "role_bindings": [ { - "user": "alice", + "uid": "alice", "bindings": [ { "namespace": "", @@ -41,7 +41,7 @@ ] }, { - "user": "*", + "uid": "*", "bindings": [ { "namespace": "project3", @@ -64,7 +64,7 @@ ] }, { - "user": "bob", + "uid": "bob", "bindings": [ { "namespace": "project4", -- Gitee From 7cdecba8a28ec623ce9c3489eaa4474f94f50a8d Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 20:03:10 +0800 Subject: [PATCH 0210/2647] remove feature Signed-off-by: mouuii --- .../core/common/repository/models/queue.go | 5 +- .../common/repository/models/task/model.go | 5 +- .../core/common/service/template_product.go | 59 +------------------ .../aslan/core/project/service/product.go | 13 ---- .../core/workflow/service/workflow/convert.go | 2 - .../cron/core/service/scheduler/scheduler.go | 18 ------ .../core/service/taskplugin/build.go | 7 --- .../core/service/types/task/model.go | 5 +- pkg/shared/poetry/check.go | 37 ------------ 9 files changed, 9 insertions(+), 142 deletions(-) delete mode 100644 pkg/shared/poetry/check.go diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index d6279a8b..4e1d2952 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -81,9 +81,8 @@ type Queue struct { TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index b71dabdb..04f9b8a3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -84,9 +84,8 @@ type Task struct { TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index bb4b4671..a303bf30 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -18,20 +18,16 @@ package service import ( "fmt" - "strings" "sync" "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" - "github.com/koderover/zadig/pkg/microservice/aslan/internal/cache" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -40,9 +36,9 @@ type PipelineResource struct { Kind string `json:"kind"` } -type Features struct { - Features []string `json:"features"` -} +//type Features struct { +// Features []string `json:"features"` +//} func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.Product, error) { resp, err := templaterepo.NewProductColl().Find(productName) @@ -52,26 +48,6 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P } totalFreeStyles := make([]*collie.CiPipelineResource, 0) - features, err := GetFeatures(log) - if err != nil { - log.Errorf("GetProductTemplate GetFeatures err : %v", err) - } - - if strings.Contains(features, string(config.FreestyleType)) { - // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 - collieAPIAddress := config.CollieAPIAddress() - cl := collie.New(collieAPIAddress) - if resp.ProductFeature != nil && resp.ProductFeature.DevelopHabit == "yaml" && resp.OnboardingStatus == setting.OnboardingStatusSecond && collieAPIAddress != "" { - ciPipelines, err := cl.ListCIPipelines(productName, log) - if err != nil { - log.Errorf("GetProductTemplate error: %v", err) - return nil, e.ErrGetProduct.AddDesc(err.Error()) - } - if len(ciPipelines) != 0 { - resp.CiPipelineID = ciPipelines[0].Metadata.ID - } - } - } err = FillProductTemplateVars([]*template.Product{resp}, log) if err != nil { @@ -116,15 +92,6 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Pipeline.List err : %v", err) } - if strings.Contains(features, string(config.FreestyleType)) { - collieAPIAddress := config.CollieAPIAddress() - cl := collie.New(collieAPIAddress) - totalFreeStyles, err = cl.ListCIPipelines(productName, log) - if err != nil { - log.Errorf("GetProductTemplate freestyle.List err : %v", err) - } - } - totalEnvTemplateServiceNum := 0 for _, services := range resp.Services { totalEnvTemplateServiceNum += len(services) @@ -165,26 +132,6 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, nil } -func GetFeatures(log *zap.SugaredLogger) (string, error) { - featuresByteKey := []byte("features") - featuresByteValue, err := cache.Get(featuresByteKey) - if err != nil { - poetryCtl := poetry.New(config.PoetryAPIServer()) - fs, err := poetryCtl.ListFeatures() - if err != nil { - return "", err - } - cacheValue := strings.Join(fs, ",") - // 一天过期 - if err = cache.Set(featuresByteKey, []byte(cacheValue), 86400); err != nil { - log.Errorf("getFeatures set cache err:%v", err) - } - return cacheValue, nil - } - - return string(featuresByteValue), nil -} - func FillProductTemplateVars(productTemplates []*template.Product, log *zap.SugaredLogger) error { var ( wg sync.WaitGroup diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index ee01dfe0..f0948b0d 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -35,7 +35,6 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" @@ -339,18 +338,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return err } - //删除自由编排工作流 - features, err := commonservice.GetFeatures(log) - if err != nil { - log.Errorf("DeleteProductTemplate productName %s getFeatures err: %v", productName, err) - } - if strings.Contains(features, string(config.FreestyleType)) { - collieClient := collie.New(config.CollieAPIAddress()) - if err = collieClient.DeleteCIPipelines(productName, log); err != nil { - log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) - } - } - err = templaterepo.NewProductColl().Delete(productName) if err != nil { log.Errorf("ProductTmpl.Delete error: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 81162742..8b7ab9b8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -69,7 +69,6 @@ func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { RwLock: queueTask.RwLock, ResetImage: queueTask.ResetImage, TriggerBy: queueTask.TriggerBy, - Features: queueTask.Features, IsRestart: queueTask.IsRestart, StorageEndpoint: queueTask.StorageEndpoint, } @@ -115,7 +114,6 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { RwLock: task.RwLock, ResetImage: task.ResetImage, TriggerBy: task.TriggerBy, - Features: task.Features, IsRestart: task.IsRestart, StorageEndpoint: task.StorageEndpoint, } diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 5f45ae44..ec390f33 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -20,7 +20,6 @@ import ( "fmt" stdlog "log" "os" - "strings" "time" "github.com/jasonlvhit/gocron" @@ -33,7 +32,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" "github.com/koderover/zadig/pkg/tool/log" ) @@ -141,12 +139,6 @@ func (c *CronClient) Init() { // 测试管理的定时任务触发 c.InitTestScheduler() - // 自由编排工作流定时任务触发 - features, _ := getFeatures() - if strings.Contains(features, freestyleType) { - c.InitColliePipelineScheduler() - } - // 定时清理环境 c.InitCleanProductScheduler() // 定时初始化构建数据 @@ -159,16 +151,6 @@ func (c *CronClient) Init() { c.InitHealthCheckScheduler() } -func getFeatures() (string, error) { - cl := poetry.New(configbase.PoetryServiceAddress()) - fs, err := cl.ListFeatures() - if err != nil { - return "", err - } - - return strings.Join(fs, ","), nil -} - // InitCleanJobScheduler ... func (c *CronClient) InitCleanJobScheduler() { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 1867fd45..630004f8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -29,11 +29,9 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -162,11 +160,6 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe Installs: p.Task.InstallCtx, } - poetryClient := poetry.New(configbase.PoetryServiceAddress()) - if fs, err := poetryClient.ListFeatures(); err == nil { - pipelineTask.Features = fs - } - if p.Task.BuildStatus == nil { p.Task.BuildStatus = &task.BuildStatus{} } diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 8fc7400a..6b9af3a2 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -81,9 +81,8 @@ type Task struct { TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type RenderInfo struct { diff --git a/pkg/shared/poetry/check.go b/pkg/shared/poetry/check.go deleted file mode 100644 index d205c512..00000000 --- a/pkg/shared/poetry/check.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type features struct { - Features []string `json:"features"` -} - -func (c *Client) ListFeatures() ([]string, error) { - url := "/directory/check" - - fs := &features{} - _, err := c.Get(url, httpclient.SetResult(fs)) - if err != nil { - return nil, err - } - - return fs.Features, nil -} -- Gitee From 9604ec0a9b00162f2226e3ef10876822bd6f3cac Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 20:04:19 +0800 Subject: [PATCH 0211/2647] remove feature Signed-off-by: mouuii --- .../aslan/core/common/service/template_product.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index a303bf30..916d490d 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -36,10 +36,6 @@ type PipelineResource struct { Kind string `json:"kind"` } -//type Features struct { -// Features []string `json:"features"` -//} - func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.Product, error) { resp, err := templaterepo.NewProductColl().Find(productName) if err != nil { -- Gitee From 96ac7757b99997eb31f725af503037eee2a0440d Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 25 Oct 2021 20:08:20 +0800 Subject: [PATCH 0212/2647] remove feature Signed-off-by: mouuii --- .../cron/core/service/scheduler/scheduler.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index ec390f33..11997133 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -191,16 +191,6 @@ func (c *CronClient) InitTestScheduler() { c.Schedulers[UpsertTestScheduler].Start() } -// InitJobScheduler ... -func (c *CronClient) InitColliePipelineScheduler() { - - c.Schedulers[UpsertColliePipelineScheduler] = gocron.NewScheduler() - - c.Schedulers[UpsertColliePipelineScheduler].Every(1).Minutes().Do(c.UpsertColliePipelineScheduler, c.log) - - c.Schedulers[UpsertColliePipelineScheduler].Start() -} - // InitBuildStatScheduler ... func (c *CronClient) InitBuildStatScheduler() { -- Gitee From 5265b3f6fec512a711b6973da80b3714be46e8cb Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 21 Oct 2021 20:27:47 +0800 Subject: [PATCH 0213/2647] CRUD for yaml template done Signed-off-by: Min Min --- .../common/repository/models/yaml_template.go | 19 +++ .../repository/mongodb/yaml_template.go | 116 ++++++++++++++ .../core/templatestore/handler/router.go | 10 ++ .../aslan/core/templatestore/handler/yaml.go | 87 ++++++++++ .../aslan/core/templatestore/service/types.go | 28 +++- .../aslan/core/templatestore/service/yaml.go | 150 ++++++++++++++++++ pkg/setting/consts.go | 5 + 7 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/yaml_template.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go create mode 100644 pkg/microservice/aslan/core/templatestore/handler/yaml.go create mode 100644 pkg/microservice/aslan/core/templatestore/service/yaml.go diff --git a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go new file mode 100644 index 00000000..34af08db --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go @@ -0,0 +1,19 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type YamlTemplate struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Content string `bson:"content" json:"content"` + Variables []*Variable `bson:"variables" json:"variables"` +} + +type Variable struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` +} + +func (YamlTemplate) TableName() string { + return "yaml_template" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go b/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go new file mode 100644 index 00000000..9d65e08b --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go @@ -0,0 +1,116 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type YamlTemplateColl struct { + *mongo.Collection + + coll string +} + +func NewYamlTemplateColl() *YamlTemplateColl { + name := models.YamlTemplate{}.TableName() + return &YamlTemplateColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *YamlTemplateColl) GetCollectionName() string { + return c.coll +} + +func (c *YamlTemplateColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"name": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *YamlTemplateColl) Create(obj *models.YamlTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + return err +} + +func (c *YamlTemplateColl) Update(idString string, obj *models.YamlTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *YamlTemplateColl) List(pageNum, pageSize int) ([]*models.YamlTemplate, int, error) { + resp := make([]*models.YamlTemplate, 0) + query := bson.M{} + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + opt := options.Find(). + SetSkip(int64((pageNum - 1) * pageSize)). + SetLimit(int64(pageSize)) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, int(count), nil +} + +func (c *YamlTemplateColl) GetById(idstring string) (*models.YamlTemplate, error) { + resp := new(models.YamlTemplate) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *YamlTemplateColl) DeleteByID(idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index b26efc3f..bd925d85 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -51,4 +51,14 @@ func (*Router) Inject(router *gin.RouterGroup) { dockerfile.GET("/:id/reference", GetDockerfileTemplateReference) dockerfile.POST("/validation", ValidateDockerfileTemplate) } + + yaml := router.Group("yaml") + { + yaml.POST("", CreateYamlTemplate) + yaml.PUT("/:id", UpdateYamlTemplate) + yaml.GET("", ListYamlTemplate) + yaml.GET("/:id", GetYamlTemplateDetail) + yaml.DELETE("/:id", DeleteYamlTemplate) + yaml.GET("/:id/reference", GetYamlTemplateReference) + } } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go new file mode 100644 index 00000000..6d3c28a9 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -0,0 +1,87 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.YamlTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.CreateYamlTemplate(req, ctx.Logger) +} + +func UpdateYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.YamlTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.UpdateYamlTemplate(c.Param("id"), req, ctx.Logger) +} + +type listYamlQuery struct { + PageSize int `json:"page_size" form:"page_size,default=100"` + PageNum int `json:"page_num" form:"page_num,default=1"` +} + +type ListYamlResp struct { + YamlTemplates []*templateservice.YamlListObject `json:"yaml_template"` + Total int `json:"total"` +} + +func ListYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Query Verification + args := listYamlQuery{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + YamlTemplateList, total, err := templateservice.ListYamlTemplate(args.PageNum, args.PageSize, ctx.Logger) + resp := ListYamlResp{ + YamlTemplates: YamlTemplateList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err +} + +func GetYamlTemplateDetail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetYamlTemplateDetail(c.Param("id"), ctx.Logger) +} + +func DeleteYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = templateservice.DeleteYamlTemplate(c.Param("id"), ctx.Logger) +} + +func GetYamlTemplateReference(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetYamlTemplateReference(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index 3437aa05..24229bda 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -47,11 +47,35 @@ type DockerfileDetail struct { } type Variable struct { - Key string `json:"key"` - Value string `json:"value"` + Key string `json:"key"` + Value string `json:"value,omitempty"` + Description string `json:"description,omitempty"` } type BuildReference struct { BuildName string `json:"build_name"` ProjectName string `json:"project_name"` } + +type YamlTemplate struct { + Name string `json:"name"` + Content string `json:"content"` +} + +type YamlListObject struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type YamlDetail struct { + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*Variable `json:"variable"` + SystemVariables []*Variable `json:"system_variable"` +} + +type ServiceReference struct { + ProjectName string `json:"project_name"` + ServiceName string `json:"service_name"` +} diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go new file mode 100644 index 00000000..1e2cda59 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -0,0 +1,150 @@ +package service + +import ( + "regexp" + "strings" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +var DefaultSystemVariable = map[string]string{ + "$Product$": "项目名称", + "$Service$": "服务名称", +} + +func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { + variableList, err := getYamlVariables(template.Content, logger) + if err != nil { + return err + } + variables := make([]*models.Variable, 0) + for _, v := range variableList { + variables = append(variables, &models.Variable{ + Key: v.Key, + Value: v.Value, + }) + } + err = mongodb.NewYamlTemplateColl().Create(&models.YamlTemplate{ + Name: template.Name, + Content: template.Content, + Variables: variables, + }) + if err != nil { + logger.Errorf("create dockerfile template error: %s", err) + } + return err +} + +func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { + variableList, err := getYamlVariables(template.Content, logger) + if err != nil { + return err + } + variables := make([]*models.Variable, 0) + for _, v := range variableList { + variables = append(variables, &models.Variable{ + Key: v.Key, + Value: v.Value, + }) + } + err = mongodb.NewYamlTemplateColl().Update( + id, + &models.YamlTemplate{ + Name: template.Name, + Content: template.Content, + Variables: variables, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %s", err) + } + return err +} + +func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*YamlListObject, int, error) { + resp := make([]*YamlListObject, 0) + templateList, total, err := mongodb.NewYamlTemplateColl().List(pageNum, pageSize) + if err != nil { + logger.Errorf("list dockerfile template error: %s", err) + return resp, 0, err + } + for _, obj := range templateList { + resp = append(resp, &YamlListObject{ + ID: obj.ID.Hex(), + Name: obj.Name, + }) + } + return resp, total, err +} + +func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, error) { + resp := new(YamlDetail) + yamlTemplate, err := mongodb.NewYamlTemplateColl().GetById(id) + if err != nil { + logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) + return nil, err + } + variables := make([]*Variable, 0) + for _, v := range yamlTemplate.Variables { + variables = append(variables, &Variable{ + Key: v.Key, + Value: v.Value, + }) + } + systemVariables := getSystemDefaultVariables() + resp.ID = yamlTemplate.ID.Hex() + resp.Name = yamlTemplate.Name + resp.Content = yamlTemplate.Content + resp.Variables = variables + resp.SystemVariables = systemVariables + return resp, nil +} + +func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { + err := mongodb.NewYamlTemplateColl().DeleteByID(id) + if err != nil { + logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) + } + return err +} + +func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceReference, error) { + return []*ServiceReference{}, nil +} + +func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { + resp := make([]*Variable, 0) + regex, err := regexp.Compile(setting.RegExpParameter) + if err != nil { + logger.Errorf("Cannot get regexp from the expression: %s, the error is: %s", setting.RegExpParameter, err) + return []*Variable{}, err + } + params := regex.FindAllString(s, -1) + for _, param := range params { + resp = append(resp, &Variable{ + Key: getParameterKey(param), + }) + } + return resp, nil +} + +func getSystemDefaultVariables() []*Variable { + resp := make([]*Variable, 0) + for key, description := range DefaultSystemVariable { + resp = append(resp, &Variable{ + Key: key, + Description: description, + }) + } + return resp +} + +// getParameter +func getParameterKey(parameter string) string { + a := strings.TrimPrefix(parameter, "{{.") + return strings.TrimSuffix(a, "}}") +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 8d2a801d..d713062f 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -487,6 +487,11 @@ const ( ZadigDockerfilePath = "zadig-dockerfile" ) +// Yaml template constant +const ( + RegExpParameter = `{{.(\w)+}}` +) + const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" -- Gitee From d5bd2acdd948bd3f590f102ea1b4b4c15ababe27 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 22 Oct 2021 16:19:54 +0800 Subject: [PATCH 0214/2647] added create service from yaml template and reload service from yaml template Signed-off-by: Min Min --- .../core/common/repository/models/service.go | 1 + .../aslan/core/service/handler/router.go | 6 ++ .../core/service/handler/yaml_template.go | 42 ++++++++++ .../aslan/core/service/service/service.go | 5 +- .../core/service/service/yaml_template.go | 84 +++++++++++++++++++ .../aslan/core/templatestore/service/yaml.go | 11 ++- pkg/setting/consts.go | 2 + 7 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 pkg/microservice/aslan/core/service/handler/yaml_template.go create mode 100644 pkg/microservice/aslan/core/service/service/yaml_template.go diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 3c1e8368..9f0591a4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -60,6 +60,7 @@ type Service struct { HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` } type GUIConfig struct { diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 461d2e0b..d90183a3 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -81,4 +81,10 @@ func (*Router) Inject(router *gin.RouterGroup) { pm.POST("/:productName", gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, CreatePMService) pm.PUT("/:productName", gin2.IsHavePermission([]string{permission.ServiceTemplateManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) } + + template := router.Group("template") + { + template.POST("/load", LoadServiceFromYamlTemplate) + template.POST("/reload", ReloadServiceFromYamlTemplate) + } } diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go new file mode 100644 index 00000000..310fd1c3 --- /dev/null +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -0,0 +1,42 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + svcservice "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +type LoadServiceFromYamlTemplateReq struct { + ServiceName string `json:"service_name"` + ProjectName string `json:"project_name"` + TemplateID string `json:"template_id"` + Variables []*svcservice.Variable `json:"variables"` +} + +func LoadServiceFromYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(LoadServiceFromYamlTemplateReq) + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.Username, req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, ctx.Logger) +} + +func ReloadServiceFromYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(LoadServiceFromYamlTemplateReq) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.Username, req.ProjectName, req.ServiceName, req.Variables, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c3b23dfa..24989135 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -69,8 +69,9 @@ type ServiceModule struct { } type Variable struct { - Key string `json:"key"` - Value string `json:"value"` + Key string `json:"key"` + Value string `json:"value"` + Description string `json:"description,omitempty"` } type YamlPreviewForPorts struct { diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go new file mode 100644 index 00000000..182d4b81 --- /dev/null +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -0,0 +1,84 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +func LoadServiceFromYamlTemplate(username, projectName, serviceName, templateID string, variables []*Variable, logger *zap.SugaredLogger) error { + template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) + if err != nil { + logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) + return err + } + renderedYaml := renderYamlFromTemplate(template.Content, variables) + service := &commonmodels.Service{ + ServiceName: serviceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: templateID, + } + _, err = CreateServiceTemplate(username, service, logger) + if err != nil { + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) + } + return err +} + +func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, variables []*Variable, logger *zap.SugaredLogger) error { + service, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: projectName, + }) + if err != nil { + logger.Errorf("Cannot find service of name [%s] from project [%s], the error is: %s", serviceName, projectName, err) + return err + } + if service.Source != setting.ServiceSourceTemplate { + return errors.New("service is not created from template") + } + if service.TemplateID == "" { + return errors.New("failed to find template id for service:" + serviceName) + } + template, err := commonrepo.NewYamlTemplateColl().GetById(service.TemplateID) + if err != nil { + logger.Errorf("Failed to find template of ID: %s, the error is: %s", service.TemplateID, err) + return err + } + renderedYaml := renderYamlFromTemplate(template.Content, variables) + svc := &commonmodels.Service{ + ServiceName: serviceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: service.TemplateID, + } + _, err = CreateServiceTemplate(username, svc, logger) + if err != nil { + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", service.TemplateID, err) + } + return err +} + +func renderYamlFromTemplate(yaml string, variables []*Variable) string { + for _, variable := range variables { + strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) + } + return yaml +} + +func buildVariable(key string) string { + return fmt.Sprintf("{{.%s}}", key) +} diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 1e2cda59..5c475c4d 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -124,10 +124,15 @@ func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) return []*Variable{}, err } params := regex.FindAllString(s, -1) + keyMap := make(map[string]int) for _, param := range params { - resp = append(resp, &Variable{ - Key: getParameterKey(param), - }) + key := getParameterKey(param) + if keyMap[key] == 0 { + resp = append(resp, &Variable{ + Key: key, + }) + keyMap[key] = 1 + } } return resp, nil } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index d713062f..0e4118bb 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -345,6 +345,8 @@ const ( GerritDefaultOwner = "dafault" // YamlFileSeperator ... YamlFileSeperator = "\n---\n" + + ServiceSourceTemplate = "template" ) const MaskValue = "********" -- Gitee From 068fba795a250d6d155725fd03a4fc6854f1db2f Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 22 Oct 2021 16:54:31 +0800 Subject: [PATCH 0215/2647] rebasing dockerfile_template Signed-off-by: Min Min --- pkg/microservice/aslan/core/templatestore/handler/yaml.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 6d3c28a9..b62cd100 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -50,7 +50,7 @@ func ListYamlTemplate(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() // Query Verification - args := listYamlQuery{} + args := &listYamlQuery{} if err := c.ShouldBindQuery(args); err != nil { ctx.Err = err return -- Gitee From 285ab37d5671134d0e999d9ddfebaa79085350b1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 22 Oct 2021 17:36:54 +0800 Subject: [PATCH 0216/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/aslan/core/service/service/yaml_template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go index 182d4b81..15ebada7 100644 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -74,6 +74,7 @@ func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, va func renderYamlFromTemplate(yaml string, variables []*Variable) string { for _, variable := range variables { + fmt.Printf("Replacing %s with %s", buildVariable(variable.Key), buildVariable(variable.Value)) strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) } return yaml -- Gitee From 9a88ceb0ef641dbd0def2adeec0991f1e1c29fe4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 22 Oct 2021 17:53:01 +0800 Subject: [PATCH 0217/2647] debug replace yaml and webhook process logic Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/service.go | 2 +- pkg/microservice/aslan/core/service/service/yaml_template.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 0e5f5289..0e57d15c 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -482,7 +482,7 @@ func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName s var action string var updatedHooks, currentHooks []*webhook.WebHook if updated != nil { - if updated.Source == setting.SourceFromZadig || updated.Source == setting.SourceFromGerrit || updated.Source == "" || updated.Source == setting.SourceFromExternal { + if updated.Source == setting.ServiceSourceTemplate || updated.Source == setting.SourceFromZadig || updated.Source == setting.SourceFromGerrit || updated.Source == "" || updated.Source == setting.SourceFromExternal { return } action = "add" diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go index 15ebada7..891df7d6 100644 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -74,8 +74,7 @@ func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, va func renderYamlFromTemplate(yaml string, variables []*Variable) string { for _, variable := range variables { - fmt.Printf("Replacing %s with %s", buildVariable(variable.Key), buildVariable(variable.Value)) - strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) + yaml = strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) } return yaml } -- Gitee From e01fca49203e8ed726662ce92a234e5af6e9b212 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 10:24:23 +0800 Subject: [PATCH 0218/2647] added get reference API Signed-off-by: Min Min --- .../core/common/repository/mongodb/service.go | 15 +++++++++++++++ .../aslan/core/templatestore/service/yaml.go | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 18bc6ddf..ed355a92 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -456,6 +456,21 @@ func (c *ServiceColl) Count(productName string) (int, error) { return cs[0].Count, nil } +func (c *ServiceColl) GetTemplateReference(templateID string) ([]*models.Service, error) { + ret := make([]*models.Service, 0) + query := bson.M{"template_id": templateID} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &ret) + if err != nil { + return nil, err + } + return ret, nil +} + func (c *ServiceColl) listMaxRevisions(preMatch, postMatch bson.M) ([]*models.Service, error) { var pipeResp []*grouped pipeline := []bson.M{ diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 5c475c4d..78f39f01 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -8,6 +8,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" ) @@ -113,7 +114,19 @@ func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { } func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceReference, error) { - return []*ServiceReference{}, nil + ret := make([]*ServiceReference, 0) + referenceList, err := commonrepo.NewServiceColl().GetTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) + return ret, err + } + for _, reference := range referenceList { + ret = append(ret, &ServiceReference{ + ServiceName: reference.ServiceName, + ProjectName: reference.ProductName, + }) + } + return ret, nil } func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { -- Gitee From 6b55d03e6765f9cc52d360a4b5927892d02fff7f Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 11:28:21 +0800 Subject: [PATCH 0219/2647] debug Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/service.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index ed355a92..c3db633d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -457,18 +457,9 @@ func (c *ServiceColl) Count(productName string) (int, error) { } func (c *ServiceColl) GetTemplateReference(templateID string) ([]*models.Service, error) { - ret := make([]*models.Service, 0) query := bson.M{"template_id": templateID} - cursor, err := c.Collection.Find(context.TODO(), query) - if err != nil { - return nil, err - } - err = cursor.All(context.TODO(), &ret) - if err != nil { - return nil, err - } - return ret, nil + return c.listMaxRevisions(query, nil) } func (c *ServiceColl) listMaxRevisions(preMatch, postMatch bson.M) ([]*models.Service, error) { -- Gitee From c9b65f4e07647e9153a8c0ea5899c83f6b9c37e1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 25 Oct 2021 18:50:20 +0800 Subject: [PATCH 0220/2647] Make template that is in-use not able to be deleted Signed-off-by: Min Min --- .../aslan/core/templatestore/service/yaml.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 78f39f01..73305db7 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -1,6 +1,7 @@ package service import ( + "errors" "regexp" "strings" @@ -106,7 +107,15 @@ func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, e } func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { - err := mongodb.NewYamlTemplateColl().DeleteByID(id) + ref, err := commonrepo.NewServiceColl().GetTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get service reference for template id: %s, the error is: %s", id, err) + return err + } + if len(ref) > 0 { + return errors.New("this template is in use") + } + err = mongodb.NewYamlTemplateColl().DeleteByID(id) if err != nil { logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) } -- Gitee From f2325fd58247ddb6b774001ba6bcce884f0e3164 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 26 Oct 2021 09:40:23 +0800 Subject: [PATCH 0221/2647] add privileged urls Signed-off-by: lou --- .../core/service/bundle/exemption_urls.go | 107 ++++++++++++++---- .../policy/core/service/bundle/opa_bundle.go | 26 +---- .../core/service/bundle/rego/authz.rego | 22 ++-- .../bundle/rego/test_data/bindings/data.json | 11 +- .../core/service/bundle/rego/test_input.json | 2 +- 5 files changed, 104 insertions(+), 64 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 3ff426bf..7d161d00 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -5,9 +5,8 @@ package bundle // how to make a decision without being aware of the detailed rules. type exemptionURLs struct { - Global rules `json:"global"` // global urls are only controlled by AuthN, and it is visible for all users - Namespaced rules `json:"namespaced"` // global urls are only controlled by AuthN, and it is visible for users under certain projects Public rules `json:"public"` // public urls are not controlled by AuthN and AuthZ + Privileged rules `json:"privileged"` // privileged urls can only be visited by system admins Registered rules `json:"registered"` // registered urls are the entire list of urls which are controlled by AuthZ, which means that if an url is not in this list, it is not controlled by AuthZ } @@ -16,20 +15,6 @@ type policyRule struct { Endpoints []string `json:"endpoints"` } -var globalURLs = []*policyRule{ - { - Methods: []string{"*"}, - Endpoints: []string{"api/v1/version"}, - }, -} - -var namespacedURLs = []*policyRule{ - { - Methods: []string{"*"}, - Endpoints: []string{"api/v1/version1"}, - }, -} - var publicURLs = []*policyRule{ { Methods: []string{"GET"}, @@ -71,12 +56,16 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{""}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/directory/check"}, + }, } -// actions which are allowed for admins only. -var adminURLs = []*policyRule{ +// actions which are allowed for system admins only. +var systemAdminURLs = []*policyRule{ { - Methods: []string{"POST", "PUT"}, + Methods: []string{"POST"}, Endpoints: []string{"api/aslan/project/products"}, }, { @@ -204,11 +193,85 @@ var adminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/proxy/config"}, }, { - Methods: []string{"*"}, + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/users/?*"}, + }, + { + Methods: []string{"POST"}, Endpoints: []string{"api/v1/users"}, }, { - Methods: []string{"*"}, - Endpoints: []string{"api/v1/users/?*"}, + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/public-roles"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/v1/public-roles/?*"}, + }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/v1/system-roles"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/system-roles/?*"}, + }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/v1/system-rolebindings"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/system-rolebindings/?*"}, }, } + +// actions which are allowed for system admins only. +var projectAdminURLs = []*policyRule{ + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/aslan/project/products"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/aslan/project/products/?*"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/users"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/roles"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/roles/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/roles"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/v1/roles/?*"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/public-roles"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/public-roles/?*"}, + }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/v1/rolebindings"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/rolebindings/?*"}, + }, +} + +var adminURLs = append(systemAdminURLs, projectAdminURLs...) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 855b2c03..84cdf4db 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -253,43 +253,29 @@ func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { func generateOPAExemptionURLs(policies []*models.Policy) *exemptionURLs { data := &exemptionURLs{} - for _, r := range globalURLs { - if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { - r.Methods = AllMethods - } - for _, method := range r.Methods { - for _, endpoint := range r.Endpoints { - data.Global = append(data.Global, &rule{Method: method, Endpoint: endpoint}) - } - } - } - sort.Sort(data.Global) - - for _, r := range namespacedURLs { + for _, r := range publicURLs { if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { r.Methods = AllMethods } for _, method := range r.Methods { for _, endpoint := range r.Endpoints { - data.Namespaced = append(data.Namespaced, &rule{Method: method, Endpoint: endpoint}) + data.Public = append(data.Public, &rule{Method: method, Endpoint: endpoint}) } } } + sort.Sort(data.Public) - sort.Sort(data.Namespaced) - - for _, r := range publicURLs { + for _, r := range systemAdminURLs { if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { r.Methods = AllMethods } for _, method := range r.Methods { for _, endpoint := range r.Endpoints { - data.Public = append(data.Public, &rule{Method: method, Endpoint: endpoint}) + data.Privileged = append(data.Privileged, &rule{Method: method, Endpoint: endpoint}) } } } - - sort.Sort(data.Public) + sort.Sort(data.Privileged) resourceMappings := getResourceActionMappings(policies) for _, resourceMappings := range resourceMappings { diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index d3bb99c2..da981352 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -32,11 +32,14 @@ allow { # Allow project admins to do anything under the given project. allow { + not url_is_privileged user_is_project_admin } # Allow the action if the user is granted permission to perform the action. allow { + not url_is_privileged + some grant user_is_granted[grant] @@ -60,22 +63,13 @@ user_is_project_admin { role.namespace == project_name } +# public urls are visible for all users url_is_public { data.exemptions.public[_].method == http_request.method glob.match(trim(data.exemptions.public[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) } -url_is_exempted { - data.exemptions.global[_].method == http_request.method - glob.match(trim(data.exemptions.global[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) -} - -url_is_exempted { - data.exemptions.namespaced[_].method == http_request.method - glob.match(trim(data.exemptions.namespaced[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) - user_projects[_] == project_name -} - +# exempted urls are visible for all authenticated users url_is_exempted { not url_is_registered } @@ -85,6 +79,12 @@ url_is_registered { glob.match(trim(data.exemptions.registered[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) } +# privileged urls are visible for system admins only +url_is_privileged { + data.exemptions.privileged[_].method == http_request.method + glob.match(trim(data.exemptions.privileged[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) +} + project_name := pn { pn := input.parsed_query.projectName[0] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json index c13fae78..9344d9cb 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json @@ -1,17 +1,8 @@ { "role_bindings": [ { - "uid": "alice", + "uid": "2e6c5200-358f-11ec-981f-de2269351a1e", "bindings": [ - { - "namespace": "", - "role_refs": [ - { - "name": "admin", - "namespace": "" - } - ] - }, { "namespace": "project1", "role_refs": [ diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_input.json b/pkg/microservice/policy/core/service/bundle/rego/test_input.json index d6aad2a2..9647fc86 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_input.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_input.json @@ -19,7 +19,7 @@ "accept": "*/*", "accept-encoding": "gzip, deflate", "accept-language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", - "authorization": "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImFmMGNlZTQxOGY0NjJkNWMzMjYyNGRiY2FlN2NjM2E2MjFmMDlkMjMifQ.eyJpc3MiOiJodHRwOi8vZGV4LnRlc3QuOHNsYW4uY29tL2RleCIsInN1YiI6IkNpVXhNak0wTlRZM09EQXRaR0k0T0MwMFlqY3pMVGt3WVRrdE0yTmtNVFkyTVdZMU5EWTJFZ1ZzYjJOaGJBIiwiYXVkIjoiZXhhbXBsZS1hcHAiLCJleHAiOjE2MzM3ODQ2NTgsImlhdCI6MTYzMzY5ODI1OCwiYXRfaGFzaCI6IkhzV0trWFhucDAzZFh1aGxOc00ycUEiLCJjX2hhc2giOiJISjllRUhhMnNBTHY5elBVaERERDd3IiwiZW1haWwiOiJsb3V0ZXN0QGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsIm5hbWUiOiJhbGljZSJ9.cAphZs4fv_ZxTNZpIziPi4btm50eYpSvXJGrmNP7TQilGKqkaIF6oroCHsdxuBm3pwIOI8dE4nws7Eogxdtx1AH6EQ3vWv8OoIW1TpCDQit4cX5MsLnEyzuS3r30QQXxi1x2veabsfQungudPivFr4_1xQcjho9G1Y3AGxEDU9zA0-r3U7g9M3lrcF4domaF1xzR1FT2TP0V2QVPCxOocPUl65P-Oxu7oRxpnv44d5ORvAuTuU5IrulZMs5NyaY145qzpBZSTU_x3xQAl4BE35XYh_w1wBqqSEpNbAum9VlpX9kaNrHc8B-jHw2cBRuEIyJU6CPC4g1wcYlxoCrCDg", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4xIiwiZW1haWwiOiJhZG1pbjFAa29kZXJvdmVyLmNvbSIsInVpZCI6IjJlNmM1MjAwLTM1OGYtMTFlYy05ODFmLWRlMjI2OTM1MWExZSIsImZlZGVyYXRlZF9jbGFpbXMiOnsiY29ubmVjdG9yX2lkIjoiIiwidXNlcl9pZCI6IiJ9LCJhdWQiOiJ6YWRpZyIsImV4cCI6MTYzNTI1MTQxOH0.7GAndkOICyk7QucBaUiGmd7ZJstWJfoNDw5JKZjz4TY", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:92.0) Gecko/20100101 Firefox/92.0", "x-forwarded-proto": "http", "x-request-id": "14404686-7a28-48a6-9be6-bdd6e119f81a" -- Gitee From 1acf586cd05964e9f653eaaad102d2c9b7231f1d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 26 Oct 2021 09:59:33 +0800 Subject: [PATCH 0222/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/system/service/private_key.go | 30 +++++++++---------- .../service/workflow/workflow_task.go | 4 +-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 6d918e78..bc9d8aec 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -92,25 +92,25 @@ func ListLabels() ([]string, error) { return commonrepo.NewPrivateKeyColl().DistinctLabels() } -// override: Full coverage +// override: Full coverage (temporarily reserved) // increment: Incremental coverage // patch: Overwrite existing func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username string, log *zap.SugaredLogger) error { switch option { - case "override": - if err := commonrepo.NewPrivateKeyColl().DeleteAll(); err != nil { - return e.ErrBulkCreatePrivateKey.AddDesc("delete all privateKeys failed") - } - for _, currentPrivateKey := range args { - if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { - return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") - } - currentPrivateKey.UpdateBy = username - if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - log.Errorf("PrivateKey.Create error: %s", err) - return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") - } - } + //case "override": + // if err := commonrepo.NewPrivateKeyColl().DeleteAll(); err != nil { + // return e.ErrBulkCreatePrivateKey.AddDesc("delete all privateKeys failed") + // } + // for _, currentPrivateKey := range args { + // if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + // return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + // } + // currentPrivateKey.UpdateBy = username + // if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + // log.Errorf("PrivateKey.Create error: %s", err) + // return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") + // } + // } case "increment": for _, currentPrivateKey := range args { if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4d3836c5..199a0616 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1729,9 +1729,7 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, func getHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) { for _, privateKey := range privateKeys { - if !ips.Has(privateKey.IP) { - ips.Insert(privateKey.IP) - } + ips.Insert(privateKey.IP) } } -- Gitee From b8ded56807ff2d0b90993657f96a7352891c3ba0 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 26 Oct 2021 10:01:39 +0800 Subject: [PATCH 0223/2647] Revert "remove poetry feature" --- .../core/common/repository/models/queue.go | 5 +- .../common/repository/models/task/model.go | 5 +- .../core/common/service/template_product.go | 57 +++++++++++++++++++ .../aslan/core/project/service/product.go | 13 +++++ .../core/workflow/service/workflow/convert.go | 2 + .../cron/core/service/scheduler/scheduler.go | 28 +++++++++ .../core/service/taskplugin/build.go | 7 +++ .../core/service/types/task/model.go | 5 +- pkg/shared/poetry/check.go | 37 ++++++++++++ 9 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 pkg/shared/poetry/check.go diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 4e1d2952..d6279a8b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -81,8 +81,9 @@ type Queue struct { TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 04f9b8a3..b71dabdb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -84,8 +84,9 @@ type Task struct { TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 916d490d..bb4b4671 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -18,16 +18,20 @@ package service import ( "fmt" + "strings" "sync" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" + "github.com/koderover/zadig/pkg/microservice/aslan/internal/cache" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -36,6 +40,10 @@ type PipelineResource struct { Kind string `json:"kind"` } +type Features struct { + Features []string `json:"features"` +} + func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.Product, error) { resp, err := templaterepo.NewProductColl().Find(productName) if err != nil { @@ -44,6 +52,26 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P } totalFreeStyles := make([]*collie.CiPipelineResource, 0) + features, err := GetFeatures(log) + if err != nil { + log.Errorf("GetProductTemplate GetFeatures err : %v", err) + } + + if strings.Contains(features, string(config.FreestyleType)) { + // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 + collieAPIAddress := config.CollieAPIAddress() + cl := collie.New(collieAPIAddress) + if resp.ProductFeature != nil && resp.ProductFeature.DevelopHabit == "yaml" && resp.OnboardingStatus == setting.OnboardingStatusSecond && collieAPIAddress != "" { + ciPipelines, err := cl.ListCIPipelines(productName, log) + if err != nil { + log.Errorf("GetProductTemplate error: %v", err) + return nil, e.ErrGetProduct.AddDesc(err.Error()) + } + if len(ciPipelines) != 0 { + resp.CiPipelineID = ciPipelines[0].Metadata.ID + } + } + } err = FillProductTemplateVars([]*template.Product{resp}, log) if err != nil { @@ -88,6 +116,15 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Pipeline.List err : %v", err) } + if strings.Contains(features, string(config.FreestyleType)) { + collieAPIAddress := config.CollieAPIAddress() + cl := collie.New(collieAPIAddress) + totalFreeStyles, err = cl.ListCIPipelines(productName, log) + if err != nil { + log.Errorf("GetProductTemplate freestyle.List err : %v", err) + } + } + totalEnvTemplateServiceNum := 0 for _, services := range resp.Services { totalEnvTemplateServiceNum += len(services) @@ -128,6 +165,26 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, nil } +func GetFeatures(log *zap.SugaredLogger) (string, error) { + featuresByteKey := []byte("features") + featuresByteValue, err := cache.Get(featuresByteKey) + if err != nil { + poetryCtl := poetry.New(config.PoetryAPIServer()) + fs, err := poetryCtl.ListFeatures() + if err != nil { + return "", err + } + cacheValue := strings.Join(fs, ",") + // 一天过期 + if err = cache.Set(featuresByteKey, []byte(cacheValue), 86400); err != nil { + log.Errorf("getFeatures set cache err:%v", err) + } + return cacheValue, nil + } + + return string(featuresByteValue), nil +} + func FillProductTemplateVars(productTemplates []*template.Product, log *zap.SugaredLogger) error { var ( wg sync.WaitGroup diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index f0948b0d..ee01dfe0 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -35,6 +35,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" @@ -338,6 +339,18 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return err } + //删除自由编排工作流 + features, err := commonservice.GetFeatures(log) + if err != nil { + log.Errorf("DeleteProductTemplate productName %s getFeatures err: %v", productName, err) + } + if strings.Contains(features, string(config.FreestyleType)) { + collieClient := collie.New(config.CollieAPIAddress()) + if err = collieClient.DeleteCIPipelines(productName, log); err != nil { + log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) + } + } + err = templaterepo.NewProductColl().Delete(productName) if err != nil { log.Errorf("ProductTmpl.Delete error: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 8b7ab9b8..81162742 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -69,6 +69,7 @@ func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { RwLock: queueTask.RwLock, ResetImage: queueTask.ResetImage, TriggerBy: queueTask.TriggerBy, + Features: queueTask.Features, IsRestart: queueTask.IsRestart, StorageEndpoint: queueTask.StorageEndpoint, } @@ -114,6 +115,7 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { RwLock: task.RwLock, ResetImage: task.ResetImage, TriggerBy: task.TriggerBy, + Features: task.Features, IsRestart: task.IsRestart, StorageEndpoint: task.StorageEndpoint, } diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 11997133..5f45ae44 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -20,6 +20,7 @@ import ( "fmt" stdlog "log" "os" + "strings" "time" "github.com/jasonlvhit/gocron" @@ -32,6 +33,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/poetry" "github.com/koderover/zadig/pkg/tool/log" ) @@ -139,6 +141,12 @@ func (c *CronClient) Init() { // 测试管理的定时任务触发 c.InitTestScheduler() + // 自由编排工作流定时任务触发 + features, _ := getFeatures() + if strings.Contains(features, freestyleType) { + c.InitColliePipelineScheduler() + } + // 定时清理环境 c.InitCleanProductScheduler() // 定时初始化构建数据 @@ -151,6 +159,16 @@ func (c *CronClient) Init() { c.InitHealthCheckScheduler() } +func getFeatures() (string, error) { + cl := poetry.New(configbase.PoetryServiceAddress()) + fs, err := cl.ListFeatures() + if err != nil { + return "", err + } + + return strings.Join(fs, ","), nil +} + // InitCleanJobScheduler ... func (c *CronClient) InitCleanJobScheduler() { @@ -191,6 +209,16 @@ func (c *CronClient) InitTestScheduler() { c.Schedulers[UpsertTestScheduler].Start() } +// InitJobScheduler ... +func (c *CronClient) InitColliePipelineScheduler() { + + c.Schedulers[UpsertColliePipelineScheduler] = gocron.NewScheduler() + + c.Schedulers[UpsertColliePipelineScheduler].Every(1).Minutes().Do(c.UpsertColliePipelineScheduler, c.log) + + c.Schedulers[UpsertColliePipelineScheduler].Start() +} + // InitBuildStatScheduler ... func (c *CronClient) InitBuildStatScheduler() { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 630004f8..1867fd45 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -29,9 +29,11 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/poetry" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -160,6 +162,11 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe Installs: p.Task.InstallCtx, } + poetryClient := poetry.New(configbase.PoetryServiceAddress()) + if fs, err := poetryClient.ListFeatures(); err == nil { + pipelineTask.Features = fs + } + if p.Task.BuildStatus == nil { p.Task.BuildStatus = &task.BuildStatus{} } diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 6b9af3a2..8fc7400a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -81,8 +81,9 @@ type Task struct { TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type RenderInfo struct { diff --git a/pkg/shared/poetry/check.go b/pkg/shared/poetry/check.go new file mode 100644 index 00000000..d205c512 --- /dev/null +++ b/pkg/shared/poetry/check.go @@ -0,0 +1,37 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package poetry + +import ( + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type features struct { + Features []string `json:"features"` +} + +func (c *Client) ListFeatures() ([]string, error) { + url := "/directory/check" + + fs := &features{} + _, err := c.Get(url, httpclient.SetResult(fs)) + if err != nil { + return nil, err + } + + return fs.Features, nil +} -- Gitee From fb2bab2a4da776bcd75ce3893efcfaac383cf265 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 10:13:15 +0800 Subject: [PATCH 0224/2647] changed update logic Signed-off-by: Min Min --- .../aslan/core/templatestore/service/types.go | 5 +++-- .../aslan/core/templatestore/service/yaml.go | 18 ++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index 24229bda..a21390fe 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -58,8 +58,9 @@ type BuildReference struct { } type YamlTemplate struct { - Name string `json:"name"` - Content string `json:"content"` + Name string `json:"name"` + Content string `json:"content"` + Variable []*Variable `json:"variable"` } type YamlListObject struct { diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 73305db7..14125c10 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -42,18 +42,16 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error } func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { - variableList, err := getYamlVariables(template.Content, logger) - if err != nil { - return err - } variables := make([]*models.Variable, 0) - for _, v := range variableList { - variables = append(variables, &models.Variable{ - Key: v.Key, - Value: v.Value, - }) + if template.Variable != nil { + for _, v := range template.Variable { + variables = append(variables, &models.Variable{ + Key: v.Key, + Value: v.Value, + }) + } } - err = mongodb.NewYamlTemplateColl().Update( + err := mongodb.NewYamlTemplateColl().Update( id, &models.YamlTemplate{ Name: template.Name, -- Gitee From 36d01bca06509259445a2b015d12e959a38b7355 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 26 Oct 2021 11:01:50 +0800 Subject: [PATCH 0225/2647] filter the case where the label is empty Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/private_key.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index d05493e2..c2f97eb7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -219,6 +219,9 @@ func (c *PrivateKeyColl) DistinctLabels() ([]string, error) { for _, labelInter := range labels { if label, ok := labelInter.(string); ok { + if label == "" { + continue + } resp = append(resp, label) } } -- Gitee From e1f9771317f291b27bab9b17bf7e4ebe586467fe Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 11:10:29 +0800 Subject: [PATCH 0226/2647] initconfig Signed-off-by: mouuii --- cmd/initconfig/main.go | 25 ++++++ pkg/cli/initconfig/cmd/init.go | 129 +++++++++++++++++++++++++++++ pkg/cli/initconfig/cmd/root.go | 86 +++++++++++++++++++ pkg/setting/consts.go | 1 + pkg/shared/client/policy/policy.go | 13 +++ 5 files changed, 254 insertions(+) create mode 100644 cmd/initconfig/main.go create mode 100644 pkg/cli/initconfig/cmd/init.go create mode 100644 pkg/cli/initconfig/cmd/root.go diff --git a/cmd/initconfig/main.go b/cmd/initconfig/main.go new file mode 100644 index 00000000..6d10e2d0 --- /dev/null +++ b/cmd/initconfig/main.go @@ -0,0 +1,25 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/koderover/zadig/pkg/cli/initconfig/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go new file mode 100644 index 00000000..f191ceff --- /dev/null +++ b/pkg/cli/initconfig/cmd/init.go @@ -0,0 +1,129 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/koderover/zadig/pkg/microservice/policy/core/service" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" +) + +func init() { + rootCmd.AddCommand(migrateCmd) + +} + +var migrateCmd = &cobra.Command{ + Use: "init", + Short: "init preset role", + Long: `init preset role.`, + Run: func(cmd *cobra.Command, args []string) { + if err := run(); err != nil { + log.Fatal(err) + } + }, +} + +func run() error { + return presetRole() +} + +func presetRole() error { + if err := policy.New().CreateSystemRole(&service.Role{ + Name: "admin", + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"*"}, + Resources: []string{"*"}, + }}, + }); err != nil { + return err + } + if err := policy.New().CreatePublicRole(&service.Role{ + Name: string(setting.Contributor), + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"get_workflow", "run_workflow"}, + Kind: "resource", + Resources: []string{"Workflow"}, + }, &service.Rule{ + Verbs: []string{"get_environment", "config_environment", "manage_environment", "delete_environment"}, + Kind: "resource", + Resources: []string{"Environment"}, + }, &service.Rule{ + Verbs: []string{"get_build", "get_service"}, + Kind: "resource", + Resources: []string{"Service"}, + }, &service.Rule{ + Verbs: []string{"get_test"}, + Kind: "resource", + Resources: []string{"Test"}, + }}, + }); err != nil { + return err + } + if err := policy.New().CreatePublicRole(&service.Role{ + Name: string(setting.Contributor), + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"get_workflow", "run_workflow"}, + Kind: "resource", + Resources: []string{"Workflow"}, + }, &service.Rule{ + Verbs: []string{"get_environment", "config_environment", "manage_environment", "delete_environment"}, + Kind: "resource", + Resources: []string{"Environment"}, + }, &service.Rule{ + Verbs: []string{"get_build", "get_service"}, + Kind: "resource", + Resources: []string{"Service"}, + }, &service.Rule{ + Verbs: []string{"get_test"}, + Kind: "resource", + Resources: []string{"Test"}, + }}, + }); err != nil { + return err + } + if err := policy.New().CreatePublicRole(&service.Role{ + Name: string(setting.ReadOnly), + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"get_workflow"}, + Kind: "resource", + Resources: []string{"Workflow"}, + }, &service.Rule{ + Verbs: []string{"get_environment"}, + Kind: "resource", + Resources: []string{"Environment"}, + }, &service.Rule{ + Verbs: []string{"get_build", "get_service"}, + Kind: "resource", + Resources: []string{"Service"}, + }, &service.Rule{ + Verbs: []string{"get_test"}, + Kind: "resource", + Resources: []string{"Test"}, + }, &service.Rule{ + Verbs: []string{"get_delivery"}, + Kind: "resource", + Resources: []string{"Delivery"}, + }}, + }); err != nil { + return err + } + return nil +} diff --git a/pkg/cli/initconfig/cmd/root.go b/pkg/cli/initconfig/cmd/root.go new file mode 100644 index 00000000..70038044 --- /dev/null +++ b/pkg/cli/initconfig/cmd/root.go @@ -0,0 +1,86 @@ +/* +Copyright © 2021 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/spf13/viper" +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "newApp", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + cobra.CheckErr(rootCmd.Execute()) +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.newApp.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".newApp" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".newApp") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index bc1457a1..31c61d31 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -484,5 +484,6 @@ type RoleType string const ( Contributor RoleType = "Contributor" + ReadOnly RoleType = "ReadOnly" ContributorRoleBindingFmt string = "contributor-project:%s-user:%s" ) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 10f01e74..3e63c087 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -3,6 +3,7 @@ package policy import ( "fmt" + "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -52,3 +53,15 @@ func (c *Client) DeleteRoleBinding(name string, projectName string) error { _, err := c.Delete(url) return err } + +func (c *Client) CreateSystemRole(role *service.Role) error { + url := fmt.Sprintf("/system-roles", role) + _, err := c.Post(url, httpclient.SetBody(role)) + return err +} + +func (c *Client) CreatePublicRole(role *service.Role) error { + url := fmt.Sprintf("/public-roles", role) + _, err := c.Post(url, httpclient.SetBody(role)) + return err +} -- Gitee From edcd2689c7f0f7547505152ade80c5d2a8152edf Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 26 Oct 2021 11:42:27 +0800 Subject: [PATCH 0227/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 199a0616..6b8cbed4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1621,13 +1621,13 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, continue } ips := sets.NewString() - getHostIPs(privateKeys, ips) + ips = extractHostIPs(privateKeys, ips) privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) if err != nil { log.Errorf("ListNameByArgs labels err:%s", err) continue } - getHostIPs(privateKeys, ips) + ips = extractHostIPs(privateKeys, ips) envHost[envConfig.EnvName] = ips.List() } build.EnvHostInfo = envHost @@ -1727,10 +1727,11 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, return subTasks, nil } -func getHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) { +func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { for _, privateKey := range privateKeys { ips.Insert(privateKey.IP) } + return ips } func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) error { -- Gitee From d237ea6cfd987c7104beac9705ddcd92a86e52b1 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 26 Oct 2021 14:33:22 +0800 Subject: [PATCH 0228/2647] init user --- Makefile | 2 +- cmd/user/main.go | 38 ++++ docker/service/user.Dockerfile | 11 ++ go.mod | 8 +- go.sum | 37 ++-- pkg/config/config.go | 4 + pkg/microservice/user/config/config.go | 85 +++++++++ pkg/microservice/user/config/consts.go | 23 +++ .../user/core/handler/login/internal.go | 18 ++ .../user/core/handler/login/third_part.go | 134 +++++++++++++ pkg/microservice/user/core/handler/router.go | 44 +++++ .../user/core/handler/user/user.go | 47 +++++ .../user/core/repository/models/user.go | 12 ++ .../user/core/repository/models/user_login.go | 10 + .../user/core/repository/mysql/user.go | 77 ++++++++ .../user/core/repository/mysql/user_login.go | 45 +++++ pkg/microservice/user/core/service.go | 98 ++++++++++ .../user/core/service/login/internal.go | 80 ++++++++ .../user/core/service/login/third_part.go | 75 ++++++++ .../user/core/service/user/user.go | 176 ++++++++++++++++++ pkg/microservice/user/server/rest/router.go | 35 ++++ pkg/microservice/user/server/rest/server.go | 76 ++++++++ pkg/microservice/user/server/server.go | 60 ++++++ pkg/setting/consts.go | 18 ++ pkg/tool/errors/http_errors.go | 3 +- pkg/util/sign.go | 24 +++ 26 files changed, 1215 insertions(+), 25 deletions(-) create mode 100644 cmd/user/main.go create mode 100644 docker/service/user.Dockerfile create mode 100644 pkg/microservice/user/config/config.go create mode 100644 pkg/microservice/user/config/consts.go create mode 100644 pkg/microservice/user/core/handler/login/internal.go create mode 100644 pkg/microservice/user/core/handler/login/third_part.go create mode 100644 pkg/microservice/user/core/handler/router.go create mode 100644 pkg/microservice/user/core/handler/user/user.go create mode 100644 pkg/microservice/user/core/repository/models/user.go create mode 100644 pkg/microservice/user/core/repository/models/user_login.go create mode 100644 pkg/microservice/user/core/repository/mysql/user.go create mode 100644 pkg/microservice/user/core/repository/mysql/user_login.go create mode 100644 pkg/microservice/user/core/service.go create mode 100644 pkg/microservice/user/core/service/login/internal.go create mode 100644 pkg/microservice/user/core/service/login/third_part.go create mode 100644 pkg/microservice/user/core/service/user/user.go create mode 100644 pkg/microservice/user/server/rest/router.go create mode 100644 pkg/microservice/user/server/rest/server.go create mode 100644 pkg/microservice/user/server/server.go diff --git a/Makefile b/Makefile index d46ca9fb..7921f0f3 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc VERSION ?= $(shell date +'%Y%m%d%H%M%S') -TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy picket +TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy user picket REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) diff --git a/cmd/user/main.go b/cmd/user/main.go new file mode 100644 index 00000000..2f8f7c69 --- /dev/null +++ b/cmd/user/main.go @@ -0,0 +1,38 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "log" + "os/signal" + "syscall" + + "github.com/koderover/zadig/pkg/microservice/user/server" +) + +func main() { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + go func() { + <-ctx.Done() + stop() + }() + + if err := server.Serve(ctx); err != nil { + log.Fatal(err) + } +} diff --git a/docker/service/user.Dockerfile b/docker/service/user.Dockerfile new file mode 100644 index 00000000..2ff14203 --- /dev/null +++ b/docker/service/user.Dockerfile @@ -0,0 +1,11 @@ +#golang.Dockerfile + +RUN go build -v -o /user ./cmd/user/main.go + +#alpine.Dockerfile + +WORKDIR /app + +COPY --from=build /user . + +ENTRYPOINT ["/app/user"] diff --git a/go.mod b/go.mod index 28512627..d4d5bada 100644 --- a/go.mod +++ b/go.mod @@ -16,13 +16,16 @@ require ( github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 github.com/coocood/freecache v1.1.0 + github.com/coreos/go-oidc v2.1.0+incompatible + github.com/coreos/go-oidc/v3 v3.0.0 + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.1 // indirect + github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/garyburd/redigo v1.6.2 // indirect - github.com/gin-contrib/sessions v0.0.3 github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 github.com/go-resty/resty/v2 v2.6.0 @@ -37,6 +40,8 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 + github.com/jinzhu/gorm v0.0.0-20180213101209-6e1387b44c64 + github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d // indirect github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mittwald/go-helm-client v0.8.0 @@ -65,6 +70,7 @@ require ( github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect go.mongodb.org/mongo-driver v1.5.0 go.uber.org/zap v1.19.0 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20210220032951-036812b2e83c diff --git a/go.sum b/go.sum index 08ed6a88..dd2bf2ed 100644 --- a/go.sum +++ b/go.sum @@ -121,11 +121,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= -github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= -github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= -github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I= @@ -172,7 +169,10 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= +github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -195,6 +195,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 h1:epsH3lb7KVbXHYk7LYGN5EiE0MxcevHU85CKITJ0wUY= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -235,6 +236,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -259,15 +262,12 @@ github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= -github.com/gin-contrib/sessions v0.0.3 h1:PoBXki+44XdJdlgDqDrY5nDVe3Wk7wDV/UCOuLP6fBI= -github.com/gin-contrib/sessions v0.0.3/go.mod h1:8C/J6cad3Il1mWYYgtw0w+hqasmpvy25mPkXdOgeB9I= github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -339,10 +339,8 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= @@ -403,6 +401,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -437,7 +436,6 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= -github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -480,18 +478,12 @@ github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyyc github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= -github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -559,6 +551,10 @@ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/gorm v0.0.0-20180213101209-6e1387b44c64 h1:8I4kQ5M5OjZKNsgRUs20soTdIoo1GbiGApV31kJ9e6Y= +github.com/jinzhu/gorm v0.0.0-20180213101209-6e1387b44c64/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= +github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc= +github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= @@ -590,7 +586,6 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -615,7 +610,6 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -645,7 +639,6 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= @@ -662,7 +655,6 @@ github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnL github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -791,6 +783,7 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -826,7 +819,6 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= @@ -1107,6 +1099,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -1161,7 +1154,6 @@ golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1375,7 +1367,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -1388,6 +1379,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= diff --git a/pkg/config/config.go b/pkg/config/config.go index 36aed966..1936fed8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -85,6 +85,10 @@ func AslanServiceInfo() *setting.ServiceInfo { return GetServiceByCode(setting.Aslan) } +func SecretKey() string { + return viper.GetString(setting.SecretKey) +} + func AslanServiceAddress() string { s := AslanServiceInfo() return GetServiceAddress(s.Name, s.Port) diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go new file mode 100644 index 00000000..ccffd53d --- /dev/null +++ b/pkg/microservice/user/config/config.go @@ -0,0 +1,85 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "context" + "fmt" + "github.com/coreos/go-oidc" + _ "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" + "github.com/spf13/viper" + "net/http" + "strings" +) + +func IssuerURL() string { + return viper.GetString(setting.IssuerURL) +} + +func Debug() string { + return viper.GetString(setting.Debug) +} + +func ClientID() string { + return viper.GetString(setting.ClientID) +} + +func ClientSecret() string { + return viper.GetString(setting.ClientSecret) +} + +func RedirectURI() string { + return viper.GetString(setting.RedirectURI) +} + +func Scopes() []string { + return strings.Split(viper.GetString(setting.Scopes), ",") +} + +func Client() *http.Client { + return http.DefaultClient +} + +func Provider() *oidc.Provider { + ctx := oidc.ClientContext(context.Background(), Client()) + provider, err := oidc.NewProvider(ctx, IssuerURL()) + if err != nil { + panic(fmt.Sprintf("init provider error:%s", err.Error())) + } + return provider +} + +func Verifier() *oidc.IDTokenVerifier { + return Provider().Verifier(&oidc.Config{ClientID: ClientID()}) +} + +func User() string { + return viper.GetString(setting.MysqlUser) +} + +func Password() string { + return viper.GetString(setting.Password) +} + +func Host() string { + return viper.GetString(setting.Host) +} + +func Name() string { + return viper.GetString(setting.Name) +} diff --git a/pkg/microservice/user/config/consts.go b/pkg/microservice/user/config/consts.go new file mode 100644 index 00000000..14144b2d --- /dev/null +++ b/pkg/microservice/user/config/consts.go @@ -0,0 +1,23 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +const ( + AppState = "zadig user" + Audience = "zadig" + SystemIdentityType = "system" +) diff --git a/pkg/microservice/user/core/handler/login/internal.go b/pkg/microservice/user/core/handler/login/internal.go new file mode 100644 index 00000000..74c1a6ee --- /dev/null +++ b/pkg/microservice/user/core/handler/login/internal.go @@ -0,0 +1,18 @@ +package login + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/core/service/login" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func InternalLogin(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &login.LoginArgs{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = login.InternalLogin(args, ctx.Logger) +} diff --git a/pkg/microservice/user/core/handler/login/third_part.go b/pkg/microservice/user/core/handler/login/third_part.go new file mode 100644 index 00000000..c274cf6d --- /dev/null +++ b/pkg/microservice/user/core/handler/login/third_part.go @@ -0,0 +1,134 @@ +package login + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/config" + "github.com/koderover/zadig/pkg/microservice/user/core/service/login" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/util" + "golang.org/x/oauth2" + "net/http" + "net/url" + "time" +) + +func Login(c *gin.Context) { + oauth2Config := &oauth2.Config{ + ClientID: config.ClientID(), + ClientSecret: config.ClientSecret(), + Endpoint: config.Provider().Endpoint(), + Scopes: config.Scopes(), + RedirectURL: config.RedirectURI(), + } + authCodeURL := oauth2Config.AuthCodeURL(config.AppState, oauth2.AccessTypeOffline) + c.Redirect(http.StatusSeeOther, authCodeURL) +} + +func Callback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + oidcCtx := oidc.ClientContext(c.Request.Context(), config.Client()) + oauth2Config := &oauth2.Config{ + ClientID: config.ClientID(), + ClientSecret: config.ClientSecret(), + Endpoint: config.Provider().Endpoint(), + Scopes: nil, + RedirectURL: config.RedirectURI(), + } + var err error + var token *oauth2.Token + switch c.Request.Method { + case http.MethodGet: + // Authorization redirect callback from OAuth2 auth flow. + if errMsg := c.Query("error"); errMsg != "" { + ctx.Err = e.ErrCallBackUser.AddDesc(errMsg) + return + } + code := c.Query("code") + if code == "" { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("no code in request: %q", c.Request.Form)) + return + } + if state := c.Query("state"); state != config.AppState { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("expected state %q got %q", config.AppState, state)) + return + } + token, err = oauth2Config.Exchange(oidcCtx, code) + case http.MethodPost: + // Form request from frontend to refresh a token. + refresh := c.Query("refresh_token") + if refresh == "" { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("no refresh_token in request: %q", c.Request.Form)) + return + } + t := &oauth2.Token{ + RefreshToken: refresh, + Expiry: time.Now().Add(-time.Hour), + } + token, err = oauth2Config.TokenSource(oidcCtx, t).Token() + default: + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("method not implemented: %s", c.Request.Method)) + return + } + + if err != nil { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("failed to get token: %v", err)) + return + } + + rawIDToken, ok := token.Extra("id_token").(string) + if !ok { + ctx.Err = e.ErrCallBackUser.AddDesc("no id_token in token response") + return + } + + idToken, err := config.Verifier().Verify(c.Request.Context(), rawIDToken) + if err != nil { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("failed to verify ID token: %v", err)) + return + } + + var claimsRaw json.RawMessage + if err := idToken.Claims(&claimsRaw); err != nil { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("error decoding ID token claims: %v", err)) + return + } + buff := new(bytes.Buffer) + if err := json.Indent(buff, claimsRaw, "", " "); err != nil { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("error indenting ID token claims: %v", err)) + return + } + + var claims util.Claims + err = json.Unmarshal(claimsRaw, &claims) + if err != nil { + ctx.Err = err + return + } + user, err := login.SyncUser(&login.SyncUserInfo{ + Email: claims.Email, + Name: claims.Name, + IdentityType: claims.FederatedClaims.ConnectorId, + }, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + claims.Uid = user.Uid + claims.StandardClaims.ExpiresAt = time.Now().Add(24 * time.Hour).Unix() + userToken, err := util.CreateToken(&claims) + if err != nil { + ctx.Err = err + return + } + v := url.Values{} + v.Add("token", userToken) + redirectUrl := "../..?" + v.Encode() + c.Redirect(http.StatusSeeOther, redirectUrl) +} diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go new file mode 100644 index 00000000..eaab5cbe --- /dev/null +++ b/pkg/microservice/user/core/handler/router.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/core/handler/login" + "github.com/koderover/zadig/pkg/microservice/user/core/handler/user" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + users := router.Group("/") + { + users.GET("login", login.Login) + + users.GET("api/v1/callback", login.Callback) + + users.POST("login", login.InternalLogin) + + users.POST("api/v1/users", user.CreateUser) + + users.PUT("api/v1/users/:uid/password", user.UpdatePassword) + + users.GET("api/v1/users/:uid", user.GetUser) + + users.GET("api/v1/users", user.GetUsers) + } +} diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go new file mode 100644 index 00000000..d89ee9ff --- /dev/null +++ b/pkg/microservice/user/core/handler/user/user.go @@ -0,0 +1,47 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/core/service/user" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "strconv" +) + +func GetUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = user.GetUser(c.Param("uid"), ctx.Logger) +} + +func GetUsers(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.Users{} + args.Name = c.Query("name") + args.Page, _ = strconv.Atoi(c.Query("page")) + args.PerPage, _ = strconv.Atoi(c.Query("per_page")) + ctx.Resp, ctx.Err = user.GetUsers(args, ctx.Logger) +} + +func CreateUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.User{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + ctx.Err = user.CreateUser(args, ctx.Logger) +} + +func UpdatePassword(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.Password{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + args.Uid = c.Param("uid") + ctx.Err = user.UpdatePassword(args, ctx.Logger) +} diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go new file mode 100644 index 00000000..53351e5f --- /dev/null +++ b/pkg/microservice/user/core/repository/models/user.go @@ -0,0 +1,12 @@ +package models + +import "github.com/koderover/zadig/pkg/microservice/user/core" + +type User struct { + core.Model + Uid string `json:"uid"` + Name string `json:"name"` + IdentityType string `gorm:"default:'unknown'" json:"identity_type"` + Email string `json:"email"` + Phone string `json:"phone"` +} diff --git a/pkg/microservice/user/core/repository/models/user_login.go b/pkg/microservice/user/core/repository/models/user_login.go new file mode 100644 index 00000000..b4bc0a1d --- /dev/null +++ b/pkg/microservice/user/core/repository/models/user_login.go @@ -0,0 +1,10 @@ +package models + +import "github.com/koderover/zadig/pkg/microservice/user/core" + +type UserLogin struct { + core.Model + Uid string `json:"uid"` + Password string `json:"password"` + LastLoginTime int64 `json:"last_login_time"` +} diff --git a/pkg/microservice/user/core/repository/mysql/user.go b/pkg/microservice/user/core/repository/mysql/user.go new file mode 100644 index 00000000..406914d8 --- /dev/null +++ b/pkg/microservice/user/core/repository/mysql/user.go @@ -0,0 +1,77 @@ +package mysql + +import ( + "github.com/jinzhu/gorm" + "github.com/koderover/zadig/pkg/microservice/user/core" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" +) + +// CreateUser create a user +func CreateUser(user *models.User, db *gorm.DB) error { + if err := db.Create(&user).Error; err != nil { + return err + } + return nil +} + +// GetUser Get a user based on email and identityType +func GetUser(email string, identityType string, db *gorm.DB) (*models.User, error) { + var user models.User + err := db.Where("email = ? and identity_type = ?", email, identityType).First(&user).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return &user, nil +} + +// GetUserByUid Get a user based on uid +func GetUserByUid(uid string, db *gorm.DB) (*models.User, error) { + var user models.User + err := db.Where("uid = ?", uid).First(&user).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return &user, nil +} + +// GetUsers gets a list of users based on paging constraints +func GetUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { + var ( + users []models.User + err error + ) + if page > 0 && perPage > 0 { + err = db.Where("name LIKE ?", "%"+name+"%").Find(&users).Offset((page - 1) * perPage).Limit(perPage).Error + } else { + err = db.Where("name LIKE ?", "%"+name+"%").Find(&users).Error + } + + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + + return users, nil +} + +// GetUsersCount gets user count +func GetUsersCount(name string) (int, error) { + var ( + users []models.User + err error + count int + ) + + err = core.DB.Where("name LIKE ?", "%"+name+"%").Find(&users).Count(&count).Error + + if err != nil { + return 0, err + } + + return count, nil +} diff --git a/pkg/microservice/user/core/repository/mysql/user_login.go b/pkg/microservice/user/core/repository/mysql/user_login.go new file mode 100644 index 00000000..8bbd2cd8 --- /dev/null +++ b/pkg/microservice/user/core/repository/mysql/user_login.go @@ -0,0 +1,45 @@ +package mysql + +import ( + "github.com/jinzhu/gorm" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" +) + +// CreateUserLogin add a userLogin record +func CreateUserLogin(userLogin *models.UserLogin, db *gorm.DB) error { + if err := db.Create(&userLogin).Error; err != nil { + return err + } + return nil +} + +// GetUserLogin Get a userLogin based on uid +func GetUserLogin(uid string, db *gorm.DB) (*models.UserLogin, error) { + var userLogin models.UserLogin + err := db.Where("uid = ?", uid).First(&userLogin).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + if err == gorm.ErrRecordNotFound { + return nil, nil + } + return &userLogin, nil +} + +// GetUserLogins Get a userLogin based on uid list +func GetUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { + var userLogins []models.UserLogin + err := db.Where("uid IN (?)", uids).Find(&userLogins).Error + if err != nil { + return nil, err + } + return &userLogins, nil +} + +// UpdateUserLogin update login info +func UpdateUserLogin(uid string, userLogin *models.UserLogin, db *gorm.DB) error { + if err := db.Model(&models.UserLogin{}).Where("uid = ?", uid).Updates(userLogin).Error; err != nil { + return err + } + return nil +} diff --git a/pkg/microservice/user/core/service.go b/pkg/microservice/user/core/service.go new file mode 100644 index 00000000..f4c09094 --- /dev/null +++ b/pkg/microservice/user/core/service.go @@ -0,0 +1,98 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package core + +import ( + "context" + "fmt" + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/mysql" + "github.com/koderover/zadig/pkg/config" + config2 "github.com/koderover/zadig/pkg/microservice/user/config" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" + "time" +) + +func Start(_ context.Context) { + log.Init(&log.Config{ + Level: config.LogLevel(), + Filename: config.LogFile(), + SendToFile: config.SendLogToFile(), + Development: config.Mode() != setting.ReleaseMode, + }) + Setup() +} + +var DB *gorm.DB + +type Model struct { + CreatedAt int `json:"created_at"` + UpdatedAt int `json:"updated_at"` +} + +// Setup initializes the database instance +func Setup() { + var err error + DB, err = gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", + config2.User(), + config2.Password(), + config2.Host(), + config2.Name())) + if err != nil { + log.Fatalf("models.Setup err: %v", err) + } + + gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string { + return defaultTableName + } + + DB.SingularTable(true) + DB.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback) + DB.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback) + DB.DB().SetMaxIdleConns(10) + DB.DB().SetMaxOpenConns(100) +} + +// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating +func updateTimeStampForCreateCallback(scope *gorm.Scope) { + if !scope.HasError() { + nowTime := time.Now().Unix() + if createTimeField, ok := scope.FieldByName("CreatedAt"); ok { + if createTimeField.IsBlank { + createTimeField.Set(nowTime) + } + } + + if modifyTimeField, ok := scope.FieldByName("UpdatedAt"); ok { + if modifyTimeField.IsBlank { + modifyTimeField.Set(nowTime) + } + } + } +} + +// updateTimeStampForUpdateCallback will set `ModifiedOn` when updating +func updateTimeStampForUpdateCallback(scope *gorm.Scope) { + if _, ok := scope.Get("gorm:update_column"); !ok { + scope.SetColumn("ModifiedOn", time.Now().Unix()) + } +} + +func Stop(_ context.Context) { + DB.Close() +} diff --git a/pkg/microservice/user/core/service/login/internal.go b/pkg/microservice/user/core/service/login/internal.go new file mode 100644 index 00000000..4badd824 --- /dev/null +++ b/pkg/microservice/user/core/service/login/internal.go @@ -0,0 +1,80 @@ +package login + +import ( + "fmt" + "github.com/dgrijalva/jwt-go" + "github.com/koderover/zadig/pkg/microservice/user/config" + "github.com/koderover/zadig/pkg/microservice/user/core" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/mysql" + "github.com/koderover/zadig/pkg/util" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" + "time" +) + +type LoginArgs struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type User struct { + Uid string `json:"uid"` + Token string `json:"token"` + Email string `json:"email"` + Phone string `json:"phone"` + Name string `json:"name"` + IdentityType string `json:"identityType"` +} + +func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { + user, err := mysql.GetUser(args.Email, config.SystemIdentityType, core.DB) + if err != nil { + logger.Errorf("InternalLogin get user email:%s error", args.Email) + return nil, err + } + if user == nil { + return nil, fmt.Errorf("user not exist") + } + userLogin, err := mysql.GetUserLogin(user.Uid, core.DB) + if err != nil { + logger.Errorf("InternalLogin get user:%s user login not exist, error msg:%s", args.Email, err.Error()) + return nil, err + } + if userLogin == nil { + logger.Errorf("InternalLogin user:%s user login not exist", args.Email) + return nil, fmt.Errorf("user login not exist") + } + password := []byte(args.Password) + err = bcrypt.CompareHashAndPassword([]byte(userLogin.Password), password) + if err != nil { + logger.Errorf("InternalLogin user:%s check password error, error msg:%s", args.Email, err.Error()) + return nil, fmt.Errorf("check password error, error msg:%s", err.Error()) + } + userLogin.LastLoginTime = time.Now().Unix() + err = mysql.UpdateUserLogin(userLogin.Uid, userLogin, core.DB) + if err != nil { + logger.Errorf("InternalLogin user:%s update user login password error, error msg:%s", args.Email, err.Error()) + return nil, err + } + token, err := util.CreateToken(&util.Claims{ + Name: user.Name, + Email: user.Email, + Uid: user.Uid, + StandardClaims: jwt.StandardClaims{ + Audience: config.Audience, + ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), + }, + }) + if err != nil { + logger.Errorf("InternalLogin user:%s create token error, error msg:%s", args.Email, err.Error()) + return nil, err + } + return &User{ + Uid: user.Uid, + Token: token, + Email: user.Email, + Phone: user.Phone, + Name: user.Name, + IdentityType: user.IdentityType, + }, nil +} diff --git a/pkg/microservice/user/core/service/login/third_part.go b/pkg/microservice/user/core/service/login/third_part.go new file mode 100644 index 00000000..81bc5338 --- /dev/null +++ b/pkg/microservice/user/core/service/login/third_part.go @@ -0,0 +1,75 @@ +package login + +import ( + "github.com/google/uuid" + "github.com/koderover/zadig/pkg/microservice/user/core" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/mysql" + "go.uber.org/zap" + "time" +) + +type SyncUserInfo struct { + Email string `json:"email"` + IdentityType string `json:"identityType"` + Name string `json:"name"` +} + +func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.User, error) { + user, err := mysql.GetUser(syncUserInfo.Email, syncUserInfo.IdentityType, core.DB) + if err != nil { + logger.Error("SyncUser get user:%s error, error msg:%s", syncUserInfo.Email, err.Error()) + return nil, err + } + tx := core.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if user == nil { + uid, _ := uuid.NewUUID() + user = &models.User{ + Uid: uid.String(), + Name: syncUserInfo.Name, + Email: syncUserInfo.Email, + IdentityType: syncUserInfo.IdentityType, + } + err = mysql.CreateUser(user, tx) + if err != nil { + tx.Rollback() + logger.Error("SyncUser create user:%s error, error msg:%s", syncUserInfo.Email, err.Error()) + return nil, err + } + } + userLogin, err := mysql.GetUserLogin(user.Uid, tx) + if err != nil { + tx.Rollback() + logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.Uid, err.Error()) + return nil, err + } + if userLogin != nil { + userLogin.LastLoginTime = time.Now().Unix() + err = mysql.UpdateUserLogin(user.Uid, userLogin, tx) + if err != nil { + tx.Rollback() + logger.Error("UpdateLoginInfo update user:%s login error, error msg:%s", user.Uid, err.Error()) + return nil, err + } + } else { + err = mysql.CreateUserLogin(&models.UserLogin{ + Uid: user.Uid, + LastLoginTime: time.Now().Unix(), + }, tx) + if err != nil { + tx.Rollback() + logger.Error("UpdateLoginInfo create user:%s login error, error msg:%s", user.Uid, err.Error()) + return nil, err + } + } + err = tx.Commit().Error + if err != nil { + return nil, err + } + return user, nil +} diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go new file mode 100644 index 00000000..0f368eb5 --- /dev/null +++ b/pkg/microservice/user/core/service/user/user.go @@ -0,0 +1,176 @@ +package user + +import ( + "fmt" + "github.com/google/uuid" + "github.com/koderover/zadig/pkg/microservice/user/config" + "github.com/koderover/zadig/pkg/microservice/user/core" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/mysql" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" +) + +type User struct { + Name string `json:"name"` + Password string `json:"password"` + Email string `json:"email"` + Phone string `json:"phone,omitempty"` +} + +type Users struct { + Name string `json:"name"` + PerPage int `json:"per_page"` + Page int `json:"page"` +} + +type UserInfo struct { + LastLoginTime int64 `json:"lastLoginTime"` + Uid string `json:"uid"` + Name string `json:"name"` + IdentityType string `gorm:"default:'unknown'" json:"identity_type"` + Email string `json:"email"` + Phone string `json:"phone"` +} + +type Password struct { + Uid string `json:"uid"` + OldPassword string `json:"oldPassword"` + NewPassword string `json:"newPassword"` +} + +type GetUsersResp struct { + Users []UserInfo `json:"users"` + TotalCount int `json:"totalCount"` +} + +func GetUser(uid string, logger *zap.SugaredLogger) (*models.User, error) { + user, err := mysql.GetUserByUid(uid, core.DB) + if err != nil { + logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) + return nil, err + } + return user, nil +} + +func GetUsers(args *Users, logger *zap.SugaredLogger) (*GetUsersResp, error) { + count, err := mysql.GetUsersCount(args.Name) + if err != nil { + logger.Errorf("GetUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) + return nil, err + } + users, err := mysql.GetUsers(args.Page, args.PerPage, args.Name, core.DB) + if err != nil { + logger.Errorf("GetUsers GetUsers By name:%s error, error msg:%s", args.Name, err.Error()) + return nil, err + } + var uids []string + for _, user := range users { + uids = append(uids, user.Uid) + } + userLogins, err := mysql.GetUserLogins(uids, core.DB) + if err != nil { + logger.Errorf("GetUsers GetUserLogins By uids:%s error, error msg:%s", uids, err.Error()) + return nil, err + } + userLoginMap := make(map[string]models.UserLogin) + for _, userLogin := range *userLogins { + userLoginMap[userLogin.Uid] = userLogin + } + var usersInfo []UserInfo + for _, user := range users { + if userLogin, ok := userLoginMap[user.Uid]; ok { + usersInfo = append(usersInfo, UserInfo{ + LastLoginTime: userLogin.LastLoginTime, + Uid: user.Uid, + Phone: user.Phone, + Name: user.Name, + Email: user.Email, + IdentityType: user.IdentityType, + }) + } else { + logger.Error("user:%s login info not exist") + } + } + return &GetUsersResp{ + Users: usersInfo, + TotalCount: count, + }, nil +} + +func CreateUser(args *User, logger *zap.SugaredLogger) error { + uid, _ := uuid.NewUUID() + user := &models.User{ + Name: args.Name, + Email: args.Email, + IdentityType: config.SystemIdentityType, + Phone: args.Phone, + Uid: uid.String(), + } + tx := core.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + err := mysql.CreateUser(user, tx) + if err != nil { + tx.Rollback() + logger.Errorf("CreateUser CreateUser :%v error, error msg:%s", user, err.Error()) + return err + } + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.Password), bcrypt.DefaultCost) + userLogin := &models.UserLogin{ + Uid: user.Uid, + Password: string(hashedPassword), + LastLoginTime: 0, + } + err = mysql.CreateUserLogin(userLogin, tx) + if err != nil { + tx.Rollback() + logger.Errorf("CreateUser CreateUserLogin:%v error, error msg:%s", user, err.Error()) + return err + } + return tx.Commit().Error +} + +func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { + user, err := mysql.GetUserByUid(args.Uid, core.DB) + if err != nil { + logger.Errorf("UpdatePassword GetUserByUid:%s error, error msg:%s", args.Uid, err.Error()) + return err + } + if user == nil { + return fmt.Errorf("user not exist") + } + userLogin, err := mysql.GetUserLogin(user.Uid, core.DB) + if err != nil { + logger.Errorf("UpdatePassword GetUserLogin:%s error, error msg:%s", args.Uid, err.Error()) + return err + } + if userLogin == nil { + logger.Errorf("UpdatePassword GetUserLogin:%s not exist", args.Uid) + return fmt.Errorf("userLogin not exist") + } + password := []byte(args.OldPassword) + err = bcrypt.CompareHashAndPassword([]byte(userLogin.Password), password) + if err == bcrypt.ErrMismatchedHashAndPassword { + return fmt.Errorf("password is wrong") + } + if err != nil { + logger.Errorf("UpdatePassword CompareHashAndPassword userLogin password:%s, password:%s error,"+ + " error msg:%s", userLogin.Password, password, err.Error()) + return err + } + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.NewPassword), bcrypt.DefaultCost) + userLogin = &models.UserLogin{ + Uid: user.Uid, + Password: string(hashedPassword), + } + err = mysql.UpdateUserLogin(user.Uid, userLogin, core.DB) + if err != nil { + logger.Errorf("UpdatePassword UpdateUserLogin:%v error, error msg:%s", userLogin, err.Error()) + return err + } + return nil +} diff --git a/pkg/microservice/user/server/rest/router.go b/pkg/microservice/user/server/rest/router.go new file mode 100644 index 00000000..101ce1e5 --- /dev/null +++ b/pkg/microservice/user/server/rest/router.go @@ -0,0 +1,35 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/user/core/handler" +) + +func (s *engine) injectRouterGroup(router *gin.RouterGroup) { + for name, r := range map[string]injector{ + "/": new(handler.Router), + } { + r.Inject(router.Group(name)) + } +} + +type injector interface { + Inject(router *gin.RouterGroup) +} diff --git a/pkg/microservice/user/server/rest/server.go b/pkg/microservice/user/server/rest/server.go new file mode 100644 index 00000000..18843b0a --- /dev/null +++ b/pkg/microservice/user/server/rest/server.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/config" + ginmiddleware "github.com/koderover/zadig/pkg/middleware/gin" + "github.com/koderover/zadig/pkg/tool/log" +) + +type engine struct { + *gin.Engine + + mode string +} + +func NewEngine() *engine { + s := &engine{mode: config.Mode()} + + gin.SetMode(s.mode) + + s.injectMiddlewares() + s.injectRouters() + + return s +} + +func (s *engine) injectMiddlewares() { + g := gin.New() + defer func() { + s.Engine = g + }() + + if s.mode == gin.TestMode { + return + } + g.Use(ginmiddleware.Response()) + g.Use(ginmiddleware.RequestID()) + g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) + g.Use(gin.Recovery()) +} + +func (s *engine) injectRouters() { + g := s.Engine + + g.NoRoute(func(c *gin.Context) { + c.String(http.StatusNotFound, "Invalid path: %s", c.Request.URL.Path) + }) + g.HandleMethodNotAllowed = true + g.NoMethod(func(c *gin.Context) { + c.String(http.StatusMethodNotAllowed, "Method not allowed: %s %s", c.Request.Method, c.Request.URL.Path) + }) + + apiRouters := g.Group("") + s.injectRouterGroup(apiRouters) + + s.Engine = g +} diff --git a/pkg/microservice/user/server/server.go b/pkg/microservice/user/server/server.go new file mode 100644 index 00000000..a6c1e616 --- /dev/null +++ b/pkg/microservice/user/server/server.go @@ -0,0 +1,60 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package server + +import ( + "context" + "net/http" + "time" + + "github.com/koderover/zadig/pkg/microservice/user/core" + "github.com/koderover/zadig/pkg/microservice/user/server/rest" + "github.com/koderover/zadig/pkg/tool/log" +) + +func Serve(ctx context.Context) error { + core.Start(ctx) + defer core.Stop(ctx) + + log.Info("Start policy service") + + engine := rest.NewEngine() + server := &http.Server{Addr: ":5555", Handler: engine} + + stopChan := make(chan struct{}) + go func() { + defer close(stopChan) + + <-ctx.Done() + + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Errorf("Failed to stop server, error: %s", err) + } + }() + + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Errorf("Failed to start http server, error: %s", err) + return err + } + + <-stopChan + + return nil +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0dfa6e85..1567f423 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -99,6 +99,24 @@ const ( DebugMode = "debug" ReleaseMode = "release" TestMode = "test" + + // user + IssuerURL = "ISSUER_URL" + Listen = "LISTEN" + TlsCert = "TLSCERT" + TlsKey = "TLSKEY" + RootCAs = "ROOT_CAS" + Debug = "DEBUG" + ClientID = "CLIENT_ID" + ClientSecret = "CLIENT_SECRET" + RedirectURI = "REDIRECT_URI" + SecretKey = "SECRET_KEY" + OfflineAsScope = "OFFLINE_AS_SCOPE" + MysqlUser = "MYSQL_USER" + Password = "PASSWORD" + Host = "HOST" + Name = "NAME" + Scopes = "SCOPES" ) // k8s concepts diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 2c920d95..5c6a6094 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -44,7 +44,8 @@ var ( ErrListUsers = NewHTTPError(6002, "列出用户信息失败") // ErrFindUser ... ErrFindUser = NewHTTPError(6002, "获取用户信息失败") - + // ErrCallBackUser ... + ErrCallBackUser = NewHTTPError(6003, "dex回调用户失败") //----------------------------------------------------------------------------------------------- // Team APIs Range: 6020 - 6039 //----------------------------------------------------------------------------------------------- diff --git a/pkg/util/sign.go b/pkg/util/sign.go index 2e8cabca..b982d836 100644 --- a/pkg/util/sign.go +++ b/pkg/util/sign.go @@ -20,6 +20,8 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" + "github.com/dgrijalva/jwt-go" + "github.com/koderover/zadig/pkg/config" ) // ComputeHmacSha256 According to ak/sk generate secret key @@ -31,3 +33,25 @@ func ComputeHmacSha256(ak string, sk string) string { hex.EncodeToString(h.Sum(nil)) return sha } + +type Claims struct { + Name string `json:"name"` + Email string `json:"email"` + Uid string `json:"uid"` + FederatedClaims FederatedClaims `json:"federated_claims"` + jwt.StandardClaims +} + +type FederatedClaims struct { + ConnectorId string `json:"connector_id"` + UserId string `json:"user_id"` +} + +func CreateToken(claims *Claims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(config.SecretKey())) + if err != nil { + return "", err + } + return tokenString, nil +} -- Gitee From 8e475387b5a7af4072acfd19284019540fa9190f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 15:01:41 +0800 Subject: [PATCH 0229/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 145 +++++++++++++---------------- pkg/cli/initconfig/cmd/root.go | 6 ++ pkg/shared/client/policy/client.go | 14 +++ pkg/shared/client/policy/policy.go | 4 +- 4 files changed, 89 insertions(+), 80 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index f191ceff..9c1b5642 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -17,7 +17,11 @@ limitations under the License. package cmd import ( + "fmt" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/sync/errgroup" "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/setting" @@ -27,7 +31,6 @@ import ( func init() { rootCmd.AddCommand(migrateCmd) - } var migrateCmd = &cobra.Command{ @@ -46,83 +49,69 @@ func run() error { } func presetRole() error { - if err := policy.New().CreateSystemRole(&service.Role{ - Name: "admin", - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"*"}, - Resources: []string{"*"}, - }}, - }); err != nil { - return err - } - if err := policy.New().CreatePublicRole(&service.Role{ - Name: string(setting.Contributor), - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"get_workflow", "run_workflow"}, - Kind: "resource", - Resources: []string{"Workflow"}, - }, &service.Rule{ - Verbs: []string{"get_environment", "config_environment", "manage_environment", "delete_environment"}, - Kind: "resource", - Resources: []string{"Environment"}, - }, &service.Rule{ - Verbs: []string{"get_build", "get_service"}, - Kind: "resource", - Resources: []string{"Service"}, - }, &service.Rule{ - Verbs: []string{"get_test"}, - Kind: "resource", - Resources: []string{"Test"}, - }}, - }); err != nil { - return err - } - if err := policy.New().CreatePublicRole(&service.Role{ - Name: string(setting.Contributor), - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"get_workflow", "run_workflow"}, - Kind: "resource", - Resources: []string{"Workflow"}, - }, &service.Rule{ - Verbs: []string{"get_environment", "config_environment", "manage_environment", "delete_environment"}, - Kind: "resource", - Resources: []string{"Environment"}, - }, &service.Rule{ - Verbs: []string{"get_build", "get_service"}, - Kind: "resource", - Resources: []string{"Service"}, - }, &service.Rule{ - Verbs: []string{"get_test"}, - Kind: "resource", - Resources: []string{"Test"}, - }}, - }); err != nil { - return err - } - if err := policy.New().CreatePublicRole(&service.Role{ - Name: string(setting.ReadOnly), - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"get_workflow"}, - Kind: "resource", - Resources: []string{"Workflow"}, - }, &service.Rule{ - Verbs: []string{"get_environment"}, - Kind: "resource", - Resources: []string{"Environment"}, - }, &service.Rule{ - Verbs: []string{"get_build", "get_service"}, - Kind: "resource", - Resources: []string{"Service"}, - }, &service.Rule{ - Verbs: []string{"get_test"}, - Kind: "resource", - Resources: []string{"Test"}, - }, &service.Rule{ - Verbs: []string{"get_delivery"}, - Kind: "resource", - Resources: []string{"Delivery"}, - }}, - }); err != nil { + ss := viper.Get("yaml") + fmt.Println(ss) + return nil + g := new(errgroup.Group) + g.Go(func() error { + return policy.NewDefault().CreateSystemRole(&service.Role{ + Name: "admin", + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"*"}, + Resources: []string{"*"}, + }}, + }) + }) + + g.Go(func() error { + return policy.NewDefault().CreatePublicRole(&service.Role{ + Name: string(setting.Contributor), + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"get_workflow", "run_workflow"}, + Kind: "resource", + Resources: []string{"Workflow"}, + }, &service.Rule{ + Verbs: []string{"get_environment", "config_environment", "manage_environment", "delete_environment"}, + Kind: "resource", + Resources: []string{"Environment"}, + }, &service.Rule{ + Verbs: []string{"get_build", "get_service"}, + Kind: "resource", + Resources: []string{"Service"}, + }, &service.Rule{ + Verbs: []string{"get_test"}, + Kind: "resource", + Resources: []string{"Test"}, + }}, + }) + }) + g.Go(func() error { + return policy.NewDefault().CreatePublicRole(&service.Role{ + Name: string(setting.ReadOnly), + Rules: []*service.Rule{&service.Rule{ + Verbs: []string{"get_workflow"}, + Kind: "resource", + Resources: []string{"Workflow"}, + }, &service.Rule{ + Verbs: []string{"get_environment"}, + Kind: "resource", + Resources: []string{"Environment"}, + }, &service.Rule{ + Verbs: []string{"get_build", "get_service"}, + Kind: "resource", + Resources: []string{"Service"}, + }, &service.Rule{ + Verbs: []string{"get_test"}, + Kind: "resource", + Resources: []string{"Test"}, + }, &service.Rule{ + Verbs: []string{"get_delivery"}, + Kind: "resource", + Resources: []string{"Delivery"}, + }}, + }) + }) + if err := g.Wait(); err != nil { return err } return nil diff --git a/pkg/cli/initconfig/cmd/root.go b/pkg/cli/initconfig/cmd/root.go index 70038044..de595ea1 100644 --- a/pkg/cli/initconfig/cmd/root.go +++ b/pkg/cli/initconfig/cmd/root.go @@ -21,6 +21,8 @@ import ( "github.com/spf13/cobra" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/spf13/viper" ) @@ -78,6 +80,10 @@ func initConfig() { } viper.AutomaticEnv() // read in environment variables that match + log.Init(&log.Config{ + Level: "debug", + NoCaller: true, + }) // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { diff --git a/pkg/shared/client/policy/client.go b/pkg/shared/client/policy/client.go index 7406675b..251a6b52 100644 --- a/pkg/shared/client/policy/client.go +++ b/pkg/shared/client/policy/client.go @@ -45,3 +45,17 @@ func New() *Client { host: host, } } + +func NewDefault() *Client { + //host := config.PolicyServiceAddress() + host := "http://localhost:8080" + + c := httpclient.New( + httpclient.SetHostURL(host + "/api/v1"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 3e63c087..14e05ede 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -55,13 +55,13 @@ func (c *Client) DeleteRoleBinding(name string, projectName string) error { } func (c *Client) CreateSystemRole(role *service.Role) error { - url := fmt.Sprintf("/system-roles", role) + url := "/system-roles" _, err := c.Post(url, httpclient.SetBody(role)) return err } func (c *Client) CreatePublicRole(role *service.Role) error { - url := fmt.Sprintf("/public-roles", role) + url := "/public-roles" _, err := c.Post(url, httpclient.SetBody(role)) return err } -- Gitee From d5b9e7dc06e63ff23845cf8cd25263bf956a7732 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 26 Oct 2021 15:10:15 +0800 Subject: [PATCH 0230/2647] update env yaml replace logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 33d99843..a0dee6d1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2925,10 +2925,11 @@ func diffRenderSet(username, productName, envName, updateType string, productRes } } + serviceMap := productResp.GetServiceMap() for serviceName, latestChartInfo := range tmpLatestChartInfoMap { if currentChartInfo, ok := tmpCurrentChartInfoMap[serviceName]; ok { //拿当前环境values.yaml的key的value去替换服务里面的values.yaml的相同的key的value - newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml)) + newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml), serviceMap[serviceName]) if err != nil { log.Errorf("Failed to override values for service %s, err: %s", serviceName, err) } else { @@ -2970,7 +2971,9 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return renderSet, nil } -func overrideValues(currentValuesYaml, latestValuesYaml []byte) ([]byte, error) { +// for keys exist in both yaml, current values will override latest values +// only for images +func overrideValues(currentValuesYaml, latestValuesYaml []byte, templateService *commonmodels.ProductService) ([]byte, error) { currentValuesMap := map[string]interface{}{} if err := yaml.Unmarshal(currentValuesYaml, ¤tValuesMap); err != nil { return nil, err @@ -2991,8 +2994,20 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte) ([]byte, error) return nil, err } + imageRelatedKey := sets.NewString() + if templateService != nil { + for _, container := range templateService.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } + } + } + replaceMap := make(map[string]interface{}) for key := range latestValuesFlatMap { + if !imageRelatedKey.Has(key) { + continue + } if currentValue, ok := currentValuesFlatMap[key]; ok { replaceMap[key] = currentValue } -- Gitee From f0bb50494767e4c284a8ff2ad1ec187d7023ceb7 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 26 Oct 2021 15:27:17 +0800 Subject: [PATCH 0231/2647] enable secret verify Signed-off-by: lou --- .../policy/core/handler/role_binding.go | 2 +- .../policy/core/service/bundle/rego/authz.rego | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 5b102686..0b9cbcdf 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -114,5 +114,5 @@ func ListUserBindings(c *gin.Context) { projectName = service.SystemScope } - ctx.Resp, ctx.Err = service.ListRoleBindings(c.Query("projectName"), uid, ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoleBindings(projectName, uid, ctx.Logger) } diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index da981352..cec268d7 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -164,11 +164,11 @@ user_is_granted[grant] { } claims := payload { - # TODO: Verify the signature on the Bearer token. The certificate can be + # Verify the signature on the Bearer token. The certificate can be # hardcoded into the policy, and it could also be loaded via data or # an environment variable. Environment variables can be accessed using # the `opa.runtime()` built-in function. - # io.jwt.verify_rs256(bearer_token, certificate) + io.jwt.verify_hs256(bearer_token, secret) # This statement invokes the built-in function `io.jwt.decode` passing the # parsed bearer_token as a parameter. The `io.jwt.decode` function returns an @@ -179,6 +179,12 @@ claims := payload { # In Rego, you can pattern match values using the `=` and `:=` operators. This # example pattern matches on the result to obtain the JWT payload. [_, payload, _] := io.jwt.decode(bearer_token) + + # it is not working, don't know why + # [valid, _, payload] := io.jwt.decode_verify(bearer_token, { + # "secret": secret, + # "alg": "alg", + # }) } bearer_token := t { @@ -189,3 +195,11 @@ bearer_token := t { startswith(v, "Bearer ") t := substring(v, count("Bearer "), -1) } + +envs := env { + env := opa.runtime()["env"] +} + +secret := s { + s := envs["SECRET_KEY"] +} -- Gitee From 159fe7abeff5f3f806b6d52632190af1d6d83f31 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 15:30:47 +0800 Subject: [PATCH 0232/2647] added update yaml template varible values API Signed-off-by: Min Min --- .../core/templatestore/handler/router.go | 1 + .../aslan/core/templatestore/handler/yaml.go | 17 ++++++ .../aslan/core/templatestore/service/yaml.go | 55 ++++++++++++++++--- 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index bd925d85..f2d37b01 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -60,5 +60,6 @@ func (*Router) Inject(router *gin.RouterGroup) { yaml.GET("/:id", GetYamlTemplateDetail) yaml.DELETE("/:id", DeleteYamlTemplate) yaml.GET("/:id/reference", GetYamlTemplateReference) + yaml.PUT("/:id/variables", UpdateYamlTemplateVariables) } } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index b62cd100..5a7a1de5 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -85,3 +85,20 @@ func GetYamlTemplateReference(c *gin.Context) { ctx.Resp, ctx.Err = templateservice.GetYamlTemplateReference(c.Param("id"), ctx.Logger) } + +type updateYamlTemplateVariablesReq struct { + Variables []*templateservice.Variable `json:"variables"` +} + +func UpdateYamlTemplateVariables(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &updateYamlTemplateVariablesReq{} + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.UpdateYamlTemplateVariables(c.Param("id"), req.Variables, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 14125c10..ed0b5f9c 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -2,6 +2,7 @@ package service import ( "errors" + "fmt" "regexp" "strings" @@ -42,16 +43,18 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error } func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { + variableList, err := getYamlVariables(template.Content, logger) + if err != nil { + return err + } variables := make([]*models.Variable, 0) - if template.Variable != nil { - for _, v := range template.Variable { - variables = append(variables, &models.Variable{ - Key: v.Key, - Value: v.Value, - }) - } + for _, v := range variableList { + variables = append(variables, &models.Variable{ + Key: v.Key, + Value: v.Value, + }) } - err := mongodb.NewYamlTemplateColl().Update( + err = mongodb.NewYamlTemplateColl().Update( id, &models.YamlTemplate{ Name: template.Name, @@ -136,6 +139,42 @@ func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceR return ret, nil } +func UpdateYamlTemplateVariables(id string, variables []*Variable, logger *zap.SugaredLogger) error { + templateDetail, err := mongodb.NewYamlTemplateColl().GetById(id) + if err != nil { + logger.Errorf("Failed to find yaml template of id: %s, the error is: %s", id, err) + return err + } + keyMap := make(map[string]int) + // the keys in database will not collide + for _, v := range templateDetail.Variables { + keyMap[v.Key] = 1 + } + newVars := make([]*models.Variable, 0) + for _, vars := range variables { + if keyMap[vars.Key] == 0 { + errorMsg := fmt.Sprintf("Given key [%s] does not exist in yaml template [%s]", vars.Key, id) + return errors.New(errorMsg) + } + newVars = append(newVars, &models.Variable{ + Key: vars.Key, + Value: vars.Value, + }) + } + err = mongodb.NewYamlTemplateColl().Update( + id, + &models.YamlTemplate{ + Name: templateDetail.Name, + Content: templateDetail.Content, + Variables: newVars, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %s", err) + } + return err +} + func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { resp := make([]*Variable, 0) regex, err := regexp.Compile(setting.RegExpParameter) -- Gitee From 98173fb86da997f787d1d8295cef5d6a850fc5d1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 15:36:25 +0800 Subject: [PATCH 0233/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/admin.yaml | 6 ++ pkg/cli/initconfig/cmd/contributor.yaml | 27 ++++++++ pkg/cli/initconfig/cmd/init.go | 83 ++++++++----------------- pkg/cli/initconfig/cmd/read-only.yaml | 29 +++++++++ 4 files changed, 88 insertions(+), 57 deletions(-) create mode 100644 pkg/cli/initconfig/cmd/admin.yaml create mode 100644 pkg/cli/initconfig/cmd/contributor.yaml create mode 100644 pkg/cli/initconfig/cmd/read-only.yaml diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml new file mode 100644 index 00000000..b64adb5c --- /dev/null +++ b/pkg/cli/initconfig/cmd/admin.yaml @@ -0,0 +1,6 @@ +name: admin +rules: + - verbs: + - "*" + resources: + - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml new file mode 100644 index 00000000..ecaf596a --- /dev/null +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -0,0 +1,27 @@ +name: Contributor +rules: + - verbs: + - get_workflow + - run_workflow + resources: + - Workflow + kind: resource + - verbs: + - get_environment + - config_environment + - manage_environment + - delete_environment + resources: + - Environment + kind: resource + - verbs: + - get_build + - get_service + resources: + - Service + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 9c1b5642..24451999 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -17,14 +17,13 @@ limitations under the License. package cmd import ( - "fmt" + _ "embed" "github.com/spf13/cobra" - "github.com/spf13/viper" "golang.org/x/sync/errgroup" + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/policy/core/service" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" ) @@ -33,6 +32,15 @@ func init() { rootCmd.AddCommand(migrateCmd) } +//go:embed contributor.yaml +var contributor []byte + +//go:embed read-only.yaml +var readOnly []byte + +//go:embed admin.yaml +var admin []byte + var migrateCmd = &cobra.Command{ Use: "init", Short: "init preset role", @@ -49,67 +57,28 @@ func run() error { } func presetRole() error { - ss := viper.Get("yaml") - fmt.Println(ss) - return nil g := new(errgroup.Group) g.Go(func() error { - return policy.NewDefault().CreateSystemRole(&service.Role{ - Name: "admin", - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"*"}, - Resources: []string{"*"}, - }}, - }) + adminRole := &service.Role{} + if err := yaml.Unmarshal(admin, adminRole); err != nil { + log.DPanic(err) + } + return policy.NewDefault().CreateSystemRole(adminRole) }) g.Go(func() error { - return policy.NewDefault().CreatePublicRole(&service.Role{ - Name: string(setting.Contributor), - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"get_workflow", "run_workflow"}, - Kind: "resource", - Resources: []string{"Workflow"}, - }, &service.Rule{ - Verbs: []string{"get_environment", "config_environment", "manage_environment", "delete_environment"}, - Kind: "resource", - Resources: []string{"Environment"}, - }, &service.Rule{ - Verbs: []string{"get_build", "get_service"}, - Kind: "resource", - Resources: []string{"Service"}, - }, &service.Rule{ - Verbs: []string{"get_test"}, - Kind: "resource", - Resources: []string{"Test"}, - }}, - }) + contributorRole := &service.Role{} + if err := yaml.Unmarshal(contributor, contributorRole); err != nil { + log.DPanic(err) + } + return policy.NewDefault().CreatePublicRole(contributorRole) }) g.Go(func() error { - return policy.NewDefault().CreatePublicRole(&service.Role{ - Name: string(setting.ReadOnly), - Rules: []*service.Rule{&service.Rule{ - Verbs: []string{"get_workflow"}, - Kind: "resource", - Resources: []string{"Workflow"}, - }, &service.Rule{ - Verbs: []string{"get_environment"}, - Kind: "resource", - Resources: []string{"Environment"}, - }, &service.Rule{ - Verbs: []string{"get_build", "get_service"}, - Kind: "resource", - Resources: []string{"Service"}, - }, &service.Rule{ - Verbs: []string{"get_test"}, - Kind: "resource", - Resources: []string{"Test"}, - }, &service.Rule{ - Verbs: []string{"get_delivery"}, - Kind: "resource", - Resources: []string{"Delivery"}, - }}, - }) + readOnlyRole := &service.Role{} + if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { + log.DPanic(err) + } + return policy.NewDefault().CreatePublicRole(readOnlyRole) }) if err := g.Wait(); err != nil { return err diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml new file mode 100644 index 00000000..b79574da --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -0,0 +1,29 @@ +# 如果觉得好用,请收藏或则分享本站 +name: ReadOnly +rules: + - verbs: + - get_workflow + resources: + - Workflow + kind: resource + - verbs: + - get_environment + resources: + - Environment + kind: resource + - verbs: + - get_build + - get_service + resources: + - Service + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource + - verbs: + - get_delivery + resources: + - Delivery + kind: resource \ No newline at end of file -- Gitee From 5502fe70ddc03d48e72926418df6998f5222e006 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 15:48:28 +0800 Subject: [PATCH 0234/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/read-only.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index b79574da..6b27e122 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -1,4 +1,3 @@ -# 如果觉得好用,请收藏或则分享本站 name: ReadOnly rules: - verbs: -- Gitee From 59469fc91980009d6ad37d0ea852528649db48d6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 16:13:21 +0800 Subject: [PATCH 0235/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/contributor.yaml | 2 +- pkg/cli/initconfig/cmd/init.go | 39 ++++++++++++++----------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml index ecaf596a..72513e43 100644 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -13,7 +13,7 @@ rules: - delete_environment resources: - Environment - kind: resource + kind: resourcepkg/cli/initconfig/cmd/init.go - verbs: - get_build - get_service diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 24451999..2ae0bf7f 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -29,7 +29,7 @@ import ( ) func init() { - rootCmd.AddCommand(migrateCmd) + rootCmd.AddCommand(initRoleCmd) } //go:embed contributor.yaml @@ -41,7 +41,7 @@ var readOnly []byte //go:embed admin.yaml var admin []byte -var migrateCmd = &cobra.Command{ +var initRoleCmd = &cobra.Command{ Use: "init", Short: "init preset role", Long: `init preset role.`, @@ -53,9 +53,12 @@ var migrateCmd = &cobra.Command{ } func run() error { - return presetRole() + return initSystemConfig() } +func initSystemConfig() { + presetRole() +} func presetRole() error { g := new(errgroup.Group) g.Go(func() error { @@ -66,20 +69,22 @@ func presetRole() error { return policy.NewDefault().CreateSystemRole(adminRole) }) - g.Go(func() error { - contributorRole := &service.Role{} - if err := yaml.Unmarshal(contributor, contributorRole); err != nil { - log.DPanic(err) - } - return policy.NewDefault().CreatePublicRole(contributorRole) - }) - g.Go(func() error { - readOnlyRole := &service.Role{} - if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { - log.DPanic(err) - } - return policy.NewDefault().CreatePublicRole(readOnlyRole) - }) + var publicRoles []*service.Role + contributorRole := &service.Role{} + if err := yaml.Unmarshal(contributor, contributorRole); err != nil { + log.DPanic(err) + } + readOnlyRole := &service.Role{} + if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { + log.DPanic(err) + } + publicRoles = append(publicRoles, contributorRole, readOnlyRole) + + for _, v := range publicRoles { + g.Go(func() error { + return policy.NewDefault().CreatePublicRole(v) + }) + } if err := g.Wait(); err != nil { return err } -- Gitee From 26654558bfa45b1a7ee0c328c1a955631816a544 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 16:13:44 +0800 Subject: [PATCH 0236/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 2ae0bf7f..009c2c12 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -56,8 +56,8 @@ func run() error { return initSystemConfig() } -func initSystemConfig() { - presetRole() +func initSystemConfig() error { + return presetRole() } func presetRole() error { g := new(errgroup.Group) -- Gitee From bb5a2cdb0c2e1e7931a990573d87ad11e544b7bb Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 17:02:35 +0800 Subject: [PATCH 0237/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/root.go | 64 ++++++---------------------------- 1 file changed, 10 insertions(+), 54 deletions(-) diff --git a/pkg/cli/initconfig/cmd/root.go b/pkg/cli/initconfig/cmd/root.go index de595ea1..082fdcd4 100644 --- a/pkg/cli/initconfig/cmd/root.go +++ b/pkg/cli/initconfig/cmd/root.go @@ -1,5 +1,5 @@ /* -Copyright © 2021 NAME HERE +Copyright 2021 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,80 +13,36 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package cmd import ( - "fmt" - "os" - "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/koderover/zadig/pkg/tool/log" - - "github.com/spf13/viper" ) -var cfgFile string - -// rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "newApp", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Use: "config", + Short: "init config for zadig", + Long: `init system config for zadig`, } -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - cobra.CheckErr(rootCmd.Execute()) +// Execute executes the root command. +func Execute() error { + return rootCmd.Execute() } func init() { cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.newApp.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } -// initConfig reads in config file and ENV variables if set. func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".newApp" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".newApp") - } + viper.AutomaticEnv() - viper.AutomaticEnv() // read in environment variables that match log.Init(&log.Config{ Level: "debug", NoCaller: true, }) - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } } -- Gitee From ee4bf1a36adf1f7f9903d509229f9f5b4923c1f8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 17:05:38 +0800 Subject: [PATCH 0238/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 19 ++----------------- pkg/shared/client/policy/policy.go | 9 ++++----- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 009c2c12..d37aa48b 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -21,9 +21,7 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" - "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" ) @@ -62,23 +60,10 @@ func initSystemConfig() error { func presetRole() error { g := new(errgroup.Group) g.Go(func() error { - adminRole := &service.Role{} - if err := yaml.Unmarshal(admin, adminRole); err != nil { - log.DPanic(err) - } - return policy.NewDefault().CreateSystemRole(adminRole) + return policy.NewDefault().CreateSystemRole(admin) }) - var publicRoles []*service.Role - contributorRole := &service.Role{} - if err := yaml.Unmarshal(contributor, contributorRole); err != nil { - log.DPanic(err) - } - readOnlyRole := &service.Role{} - if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { - log.DPanic(err) - } - publicRoles = append(publicRoles, contributorRole, readOnlyRole) + publicRoles := [][]byte{contributor, readOnly} for _, v := range publicRoles { g.Go(func() error { diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 14e05ede..62b9e098 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -3,7 +3,6 @@ package policy import ( "fmt" - "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -54,14 +53,14 @@ func (c *Client) DeleteRoleBinding(name string, projectName string) error { return err } -func (c *Client) CreateSystemRole(role *service.Role) error { +func (c *Client) CreateSystemRole(body []byte) error { url := "/system-roles" - _, err := c.Post(url, httpclient.SetBody(role)) + _, err := c.Post(url, httpclient.SetBody(body)) return err } -func (c *Client) CreatePublicRole(role *service.Role) error { +func (c *Client) CreatePublicRole(body []byte) error { url := "/public-roles" - _, err := c.Post(url, httpclient.SetBody(role)) + _, err := c.Post(url, httpclient.SetBody(body)) return err } -- Gitee From 732e964d6d0631b18fe59ea36cd7efb079072e04 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 17:27:34 +0800 Subject: [PATCH 0239/2647] debugs Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 0e57d15c..265418e6 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -493,7 +493,7 @@ func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName s updatedHooks = append(updatedHooks, &webhook.WebHook{Owner: updated.RepoOwner, Repo: updated.RepoName, Address: address, Name: "trigger", CodeHostID: updated.CodehostID}) } if current != nil { - if current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { + if updated.Source == setting.ServiceSourceTemplate || current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { return } action = "remove" -- Gitee From 18232b639ffc9533ed98fcb924a00b21a01be524 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 17:28:08 +0800 Subject: [PATCH 0240/2647] debugs Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 265418e6..d8f039ce 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -493,7 +493,7 @@ func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName s updatedHooks = append(updatedHooks, &webhook.WebHook{Owner: updated.RepoOwner, Repo: updated.RepoName, Address: address, Name: "trigger", CodeHostID: updated.CodehostID}) } if current != nil { - if updated.Source == setting.ServiceSourceTemplate || current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { + if current.Source == setting.ServiceSourceTemplate || current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { return } action = "remove" -- Gitee From 7c79c702dc0219879a163f42da1ceb196d2ac818 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 17:51:45 +0800 Subject: [PATCH 0241/2647] changed query reference logic Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/service.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index c3db633d..26bd34d2 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -457,7 +457,10 @@ func (c *ServiceColl) Count(productName string) (int, error) { } func (c *ServiceColl) GetTemplateReference(templateID string) ([]*models.Service, error) { - query := bson.M{"template_id": templateID} + query := bson.M{ + "template_id": templateID, + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + } return c.listMaxRevisions(query, nil) } -- Gitee From d35a81e2897c337e1f7a333df8253391cf40d459 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 18:07:59 +0800 Subject: [PATCH 0242/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index d37aa48b..2653c8f6 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -41,8 +41,8 @@ var admin []byte var initRoleCmd = &cobra.Command{ Use: "init", - Short: "init preset role", - Long: `init preset role.`, + Short: "init cmd", + Long: `init cmd.`, Run: func(cmd *cobra.Command, args []string) { if err := run(); err != nil { log.Fatal(err) -- Gitee From 842486beedc580490375130d425f6dedcd42f3d9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 18:09:44 +0800 Subject: [PATCH 0243/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 2653c8f6..86a98864 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -27,7 +27,7 @@ import ( ) func init() { - rootCmd.AddCommand(initRoleCmd) + rootCmd.AddCommand(initCmd) } //go:embed contributor.yaml @@ -39,10 +39,10 @@ var readOnly []byte //go:embed admin.yaml var admin []byte -var initRoleCmd = &cobra.Command{ +var initCmd = &cobra.Command{ Use: "init", - Short: "init cmd", - Long: `init cmd.`, + Short: "init system config", + Long: `init system config.`, Run: func(cmd *cobra.Command, args []string) { if err := run(); err != nil { log.Fatal(err) -- Gitee From 41214369b34a4ce2aae3ca4b3caefd5fb94dcb73 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 26 Oct 2021 18:21:06 +0800 Subject: [PATCH 0244/2647] update preview values.yaml logic Signed-off-by: allenshen --- .../core/environment/handler/environment.go | 2 +- .../core/environment/service/environment.go | 108 +++++++++++------- .../core/environment/service/renderset.go | 19 ++- 3 files changed, 84 insertions(+), 45 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 043d7f25..a939b1a2 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -268,7 +268,7 @@ func EstimatedValues(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GeneEstimatedValues(productName, c.Query("envName"), serviceName, c.Query("format"), arg, ctx.Logger) + ctx.Resp, ctx.Err = service.GeneEstimatedValues(productName, c.Query("envName"), serviceName, c.Query("scene"), c.Query("format"), arg, ctx.Logger) } func UpdateHelmProductRenderset(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index a0dee6d1..a2667ba0 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -48,6 +48,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -1157,53 +1158,79 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } -func findTargetChartInfo(rendersetName, serviceName string) (*templatemodels.RenderChart, error) { - opt := &commonrepo.RenderSetFindOption{Name: rendersetName} - productRenderset, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) +func prepareEstimatedData(productName, envName, serviceName, scene, defaultValues string, log *zap.SugaredLogger) (string, string, error) { + var err error + templateService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + }) if err != nil { - log.Errorf("failed to get renderset, name %s", rendersetName) - return nil, err + log.Errorf("failed to query service, name %s, err %s", serviceName, err) + return "", "", fmt.Errorf("faild to query service, name %s", serviceName) } - if !existed { - //return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find renderset, envName: %s", envName)) - return nil, nil + + if scene == "createEnv" { + return templateService.HelmChart.ValuesYaml, defaultValues, nil } - var targetChart *templatemodels.RenderChart - for _, chartInfo := range productRenderset.ChartInfos { - if chartInfo.ServiceName == serviceName { - targetChart = chartInfo - break - } + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return "", "", fmt.Errorf("faild to query product info, name %s", envName) + } + + // find chart info from cur render set + opt := &mongodb.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} + renderSet, err := mongodb.NewRenderSetColl().Find(opt) + if err != nil { + log.Errorf("renderset Find error, productName:%s, envName:%s, err:%s", productInfo.ProductName, productInfo.EnvName, err) + return "", "", fmt.Errorf("faild to query renderset info, name %s", productInfo.Render.Name) } - return targetChart, nil -} -func GeneEstimatedValues(productName, envName, serviceName, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { var targetChart *templatemodels.RenderChart - var err error - if len(envName) > 0 { - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - targetChart, err = findTargetChartInfo(renderSetName, serviceName) - if err != nil { - return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to query chartInfo, serviceName: %s", serviceName)) + for _, chart := range renderSet.ChartInfos { + if chart.ServiceName == serviceName { + targetChart = chart + break } } - // if chart info not exist in env.renderset, find form default renderset if targetChart == nil { - targetChart, err = findTargetChartInfo(productName, serviceName) + return "", "", fmt.Errorf("faild to find chart info, name: %s", serviceName) + } + + if scene == "updateEnv" { + imageRelatedKey := sets.NewString() + if templateService != nil { + for _, container := range templateService.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } + } + } + // merge environment values + mergedBs, err := overrideValues([]byte(targetChart.ValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) if err != nil { - return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to query chartInfo, serviceName: %s", serviceName)) + return "", "", errors.Wrapf(err, "faild to override values") } + return string(mergedBs), renderSet.DefaultValues, nil + } else if scene == "updateRenderSet" { + return targetChart.ValuesYaml, renderSet.DefaultValues, nil } + return "", "", fmt.Errorf("unrecognized scene:%s", scene) +} - if targetChart == nil { - return nil, e.ErrGetRenderSet.AddDesc(fmt.Sprintf("failed to find chart info, serviceName: %s", serviceName)) +func GeneEstimatedValues(productName, envName, serviceName, scene, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { + chartValues, defaultValues, err := prepareEstimatedData(productName, envName, serviceName, scene, arg.DefaultValues, log) + if err != nil { + return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to prepare data, err %s", err)) } tempArg := &commonservice.RenderChartArg{OverrideValues: arg.OverrideValues} - mergeValues, err := helmtool.MergeOverrideValues(targetChart.ValuesYaml, arg.DefaultValues, arg.OverrideYaml, tempArg.ToOverrideValueString()) + mergeValues, err := helmtool.MergeOverrideValues(chartValues, defaultValues, arg.OverrideYaml, tempArg.ToOverrideValueString()) if err != nil { return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to merge values, err %s", err)) } @@ -2928,8 +2955,18 @@ func diffRenderSet(username, productName, envName, updateType string, productRes serviceMap := productResp.GetServiceMap() for serviceName, latestChartInfo := range tmpLatestChartInfoMap { if currentChartInfo, ok := tmpCurrentChartInfoMap[serviceName]; ok { + serviceInfo := serviceMap[serviceName] + imageRelatedKey := sets.NewString() + if serviceInfo != nil { + for _, container := range serviceInfo.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } + } + } + //拿当前环境values.yaml的key的value去替换服务里面的values.yaml的相同的key的value - newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml), serviceMap[serviceName]) + newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml), imageRelatedKey) if err != nil { log.Errorf("Failed to override values for service %s, err: %s", serviceName, err) } else { @@ -2973,7 +3010,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes // for keys exist in both yaml, current values will override latest values // only for images -func overrideValues(currentValuesYaml, latestValuesYaml []byte, templateService *commonmodels.ProductService) ([]byte, error) { +func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey sets.String) ([]byte, error) { currentValuesMap := map[string]interface{}{} if err := yaml.Unmarshal(currentValuesYaml, ¤tValuesMap); err != nil { return nil, err @@ -2994,15 +3031,6 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte, templateService return nil, err } - imageRelatedKey := sets.NewString() - if templateService != nil { - for _, container := range templateService.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) - } - } - } - replaceMap := make(map[string]interface{}) for key := range latestValuesFlatMap { if !imageRelatedKey.Has(key) { diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 8bedec2c..8ab64c64 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -20,6 +20,8 @@ import ( "fmt" "strings" + "go.mongodb.org/mongo-driver/mongo" + "github.com/pkg/errors" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" @@ -76,18 +78,27 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL } func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*DefaultValuesResp, error) { - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - ret := &DefaultValuesResp{} + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err == mongo.ErrNoDocuments { + return ret, nil + } + if err != nil { + return nil, fmt.Errorf("faild to query product info, productName %s envName %s", productName, envName) + } + opt := &commonrepo.RenderSetFindOption{ - Name: renderSetName, + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, } rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { return nil, err } - if !existed { return ret, nil } -- Gitee From 764692238ba8e28918f0603d4145ea9d97aca395 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 18:39:40 +0800 Subject: [PATCH 0245/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 5 ++++- pkg/cli/initconfig/cmd/project-admin.yaml | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 pkg/cli/initconfig/cmd/project-admin.yaml diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 86a98864..35480051 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -39,6 +39,9 @@ var readOnly []byte //go:embed admin.yaml var admin []byte +//go:embed project-admin.yaml +var projectAdmin []byte + var initCmd = &cobra.Command{ Use: "init", Short: "init system config", @@ -63,7 +66,7 @@ func presetRole() error { return policy.NewDefault().CreateSystemRole(admin) }) - publicRoles := [][]byte{contributor, readOnly} + publicRoles := [][]byte{contributor, readOnly, projectAdmin} for _, v := range publicRoles { g.Go(func() error { diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml new file mode 100644 index 00000000..68938e3f --- /dev/null +++ b/pkg/cli/initconfig/cmd/project-admin.yaml @@ -0,0 +1,6 @@ +name: project-admin +rules: + - verbs: + - "*" + resources: + - "*" \ No newline at end of file -- Gitee From ac13a89080a33d031b39c043253af12756746f57 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 19:24:51 +0800 Subject: [PATCH 0246/2647] changed logic for loading service from template and list yaml templates api response Signed-off-by: Min Min --- .../aslan/core/service/service/yaml_template.go | 8 +++++--- .../aslan/core/templatestore/handler/yaml.go | 11 +++++++---- .../aslan/core/templatestore/service/types.go | 9 ++++----- .../aslan/core/templatestore/service/yaml.go | 8 +++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go index 891df7d6..11b1c70e 100644 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -18,7 +18,7 @@ func LoadServiceFromYamlTemplate(username, projectName, serviceName, templateID logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) return err } - renderedYaml := renderYamlFromTemplate(template.Content, variables) + renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) service := &commonmodels.Service{ ServiceName: serviceName, Type: setting.K8SDeployType, @@ -55,7 +55,7 @@ func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, va logger.Errorf("Failed to find template of ID: %s, the error is: %s", service.TemplateID, err) return err } - renderedYaml := renderYamlFromTemplate(template.Content, variables) + renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) svc := &commonmodels.Service{ ServiceName: serviceName, Type: setting.K8SDeployType, @@ -72,10 +72,12 @@ func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, va return err } -func renderYamlFromTemplate(yaml string, variables []*Variable) string { +func renderYamlFromTemplate(yaml, productName, serviceName string, variables []*Variable) string { for _, variable := range variables { yaml = strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) } + yaml = strings.Replace(yaml, "$T-Product$", productName, -1) + yaml = strings.Replace(yaml, "$T-Service$", serviceName, -1) return yaml } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 5a7a1de5..ab5c9909 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -41,8 +41,9 @@ type listYamlQuery struct { } type ListYamlResp struct { - YamlTemplates []*templateservice.YamlListObject `json:"yaml_template"` - Total int `json:"total"` + SystemVariables []*templateservice.Variable `json:"system_variables"` + YamlTemplates []*templateservice.YamlListObject `json:"yaml_template"` + Total int `json:"total"` } func ListYamlTemplate(c *gin.Context) { @@ -56,10 +57,12 @@ func ListYamlTemplate(c *gin.Context) { return } + systemVariables := templateservice.GetSystemDefaultVariables() YamlTemplateList, total, err := templateservice.ListYamlTemplate(args.PageNum, args.PageSize, ctx.Logger) resp := ListYamlResp{ - YamlTemplates: YamlTemplateList, - Total: total, + SystemVariables: systemVariables, + YamlTemplates: YamlTemplateList, + Total: total, } ctx.Resp = resp ctx.Err = err diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index a21390fe..92beafe2 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -69,11 +69,10 @@ type YamlListObject struct { } type YamlDetail struct { - ID string `json:"id"` - Name string `json:"name"` - Content string `json:"content"` - Variables []*Variable `json:"variable"` - SystemVariables []*Variable `json:"system_variable"` + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*Variable `json:"variable"` } type ServiceReference struct { diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index ed0b5f9c..df9c3638 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -15,8 +15,8 @@ import ( ) var DefaultSystemVariable = map[string]string{ - "$Product$": "项目名称", - "$Service$": "服务名称", + "$T-Product$": "项目名称", + "$T-Service$": "服务名称", } func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { @@ -98,12 +98,10 @@ func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, e Value: v.Value, }) } - systemVariables := getSystemDefaultVariables() resp.ID = yamlTemplate.ID.Hex() resp.Name = yamlTemplate.Name resp.Content = yamlTemplate.Content resp.Variables = variables - resp.SystemVariables = systemVariables return resp, nil } @@ -196,7 +194,7 @@ func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) return resp, nil } -func getSystemDefaultVariables() []*Variable { +func GetSystemDefaultVariables() []*Variable { resp := make([]*Variable, 0) for key, description := range DefaultSystemVariable { resp = append(resp, &Variable{ -- Gitee From 1f718d3cf407eef04e28a937e26248e974cb5b69 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 26 Oct 2021 19:44:56 +0800 Subject: [PATCH 0247/2647] fix panic error Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 6a7bc9fd..551f439d 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -480,7 +480,7 @@ func DeleteServiceWebhookByName(serviceName, productName string, logger *zap.Sug func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName string, logger *zap.SugaredLogger) { // helm service doesn't support webhook - if current.Type == setting.HelmDeployType { + if current != nil && current.Type == setting.HelmDeployType { return } var action string -- Gitee From 6c5ef13168e53fac5d23cfe62841c794d3c761df Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 19:50:45 +0800 Subject: [PATCH 0248/2647] service module updates Signed-off-by: Min Min --- pkg/microservice/aslan/core/service/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 24989135..56373af0 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1051,7 +1051,7 @@ func ensureServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sug args.Containers = make([]*commonmodels.Container, 0) } // Only the gerrit/spock/external type needs to be processed by yaml - if args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromZadig || args.Source == setting.SourceFromExternal { + if args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromZadig || args.Source == setting.SourceFromExternal || args.Source == setting.ServiceSourceTemplate { // 拆分 all-in-one yaml文件 // 替换分隔符 args.Yaml = util.ReplaceWrapLine(args.Yaml) -- Gitee From 58a5e047288b28e597e6098eb9aa124691bde343 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 20:14:12 +0800 Subject: [PATCH 0249/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 23 +++++++++--- pkg/microservice/policy/core/handler/role.go | 35 +++++++++++++++++++ .../policy/core/handler/router.go | 6 ++-- .../policy/core/repository/mongodb/role.go | 12 +++++++ pkg/microservice/policy/core/service/role.go | 16 +++++++++ pkg/shared/client/policy/policy.go | 13 +++---- 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 35480051..2e885e6c 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -22,6 +22,9 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" ) @@ -63,14 +66,26 @@ func initSystemConfig() error { func presetRole() error { g := new(errgroup.Group) g.Go(func() error { - return policy.NewDefault().CreateSystemRole(admin) + systemRole := &service.Role{} + if err := yaml.Unmarshal(admin, systemRole); err != nil { + log.DPanic(err) + } + return policy.NewDefault().CreateSystemRole(systemRole.Name, systemRole) }) - publicRoles := [][]byte{contributor, readOnly, projectAdmin} - + publicRoles := []*service.Role{} + var readOnlyRole *service.Role + var contributorRole *service.Role + if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { + log.DPanic(err) + } + if err := yaml.Unmarshal(contributor, contributorRole); err != nil { + log.DPanic(err) + } + publicRoles = append(publicRoles, readOnlyRole, contributorRole) for _, v := range publicRoles { g.Go(func() error { - return policy.NewDefault().CreatePublicRole(v) + return policy.NewDefault().CreatePublicRole(v.Name, v) }) } if err := g.Wait(); err != nil { diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index c55ea6b9..64c82e38 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -64,6 +64,27 @@ func UpdateRole(c *gin.Context) { ctx.Err = service.UpdateRole(projectName, args, ctx.Logger) } +func UpdateOrCreateRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Role{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + name := c.Param("name") + args.Name = name + + ctx.Err = service.UpdateOrCreateRole(projectName, args, ctx.Logger) +} + func UpdatePublicRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -78,6 +99,20 @@ func UpdatePublicRole(c *gin.Context) { ctx.Err = service.UpdateRole("", args, ctx.Logger) } +func UpdateOrCreatePublicRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Role{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + name := c.Param("name") + args.Name = name + ctx.Err = service.UpdateOrCreateRole("", args, ctx.Logger) +} + func ListRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index e53d01d0..3d86d812 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,7 +26,8 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) - roles.PUT("/:name", UpdateRole) + roles.PATCH("/:name", UpdateRole) + roles.PUT("/:name", UpdateOrCreateRole) roles.GET("", ListRoles) roles.GET("/:name", GetRole) roles.DELETE("/:name", DeleteRole) @@ -37,7 +38,8 @@ func (*Router) Inject(router *gin.RouterGroup) { publicRoles.POST("", CreatePublicRole) publicRoles.GET("", ListPublicRoles) publicRoles.GET("/:name", GetPublicRole) - publicRoles.PUT("/:name", UpdatePublicRole) + publicRoles.PATCH("/:name", UpdatePublicRole) + publicRoles.PUT("/:name", UpdateOrCreatePublicRole) publicRoles.DELETE("/:name", DeletePublicRole) } diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 5ecff450..6ac74026 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -142,3 +142,15 @@ func (c *RoleColl) UpdateRole(obj *models.Role) error { _, err := c.UpdateOne(context.TODO(), query, change) return err } + +func (c *RoleColl) UpdateOrCreate(obj *models.Role) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + query := bson.M{"name": obj.Name, "namespace": obj.Namespace} + opts := options.Replace().SetUpsert(true) + _, err := c.ReplaceOne(context.TODO(), query, obj, opts) + + return err +} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 63b21e30..d7379ff9 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -69,6 +69,22 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().UpdateRole(obj) } +func UpdateOrCreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { + obj := &models.Role{ + Name: role.Name, + Namespace: ns, + } + + for _, r := range role.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + }) + } + return mongodb.NewRoleColl().UpdateOrCreate(obj) +} + func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { var roles []*Role projectRoles, err := mongodb.NewRoleColl().ListBy(projectName) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 62b9e098..c1c0d393 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -3,6 +3,7 @@ package policy import ( "fmt" + "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -53,14 +54,14 @@ func (c *Client) DeleteRoleBinding(name string, projectName string) error { return err } -func (c *Client) CreateSystemRole(body []byte) error { - url := "/system-roles" - _, err := c.Post(url, httpclient.SetBody(body)) +func (c *Client) CreateSystemRole(name string, role *service.Role) error { + url := fmt.Sprintf("/system-roles/%s", name) + _, err := c.Put(url, httpclient.SetBody(role)) return err } -func (c *Client) CreatePublicRole(body []byte) error { - url := "/public-roles" - _, err := c.Post(url, httpclient.SetBody(body)) +func (c *Client) CreatePublicRole(name string, role *service.Role) error { + url := fmt.Sprintf("/public-roles/%s", name) + _, err := c.Put(url, httpclient.SetBody(role)) return err } -- Gitee From b746834864a2f94284c1992a7372fa729e5d9be8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 20:14:42 +0800 Subject: [PATCH 0250/2647] initconfig Signed-off-by: mouuii --- pkg/shared/client/policy/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/shared/client/policy/client.go b/pkg/shared/client/policy/client.go index 251a6b52..b81bce82 100644 --- a/pkg/shared/client/policy/client.go +++ b/pkg/shared/client/policy/client.go @@ -47,8 +47,7 @@ func New() *Client { } func NewDefault() *Client { - //host := config.PolicyServiceAddress() - host := "http://localhost:8080" + host := config.PolicyServiceAddress() c := httpclient.New( httpclient.SetHostURL(host + "/api/v1"), -- Gitee From c7fd7d0f1750deda5169d28e8a92e28418c9eca3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 20:17:21 +0800 Subject: [PATCH 0251/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 2e885e6c..64df3c06 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -76,13 +76,17 @@ func presetRole() error { publicRoles := []*service.Role{} var readOnlyRole *service.Role var contributorRole *service.Role + var projectAdminRole *service.Role if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { log.DPanic(err) } if err := yaml.Unmarshal(contributor, contributorRole); err != nil { log.DPanic(err) } - publicRoles = append(publicRoles, readOnlyRole, contributorRole) + if err := yaml.Unmarshal(projectAdmin, projectAdminRole); err != nil { + log.DPanic(err) + } + publicRoles = append(publicRoles, readOnlyRole, contributorRole, projectAdminRole) for _, v := range publicRoles { g.Go(func() error { return policy.NewDefault().CreatePublicRole(v.Name, v) -- Gitee From bf307435f5bd2a25646aa31bc8cf4d07f2a9873f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 20:18:48 +0800 Subject: [PATCH 0252/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 64df3c06..a6cb42be 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -88,8 +88,9 @@ func presetRole() error { } publicRoles = append(publicRoles, readOnlyRole, contributorRole, projectAdminRole) for _, v := range publicRoles { + tmp := v g.Go(func() error { - return policy.NewDefault().CreatePublicRole(v.Name, v) + return policy.NewDefault().CreatePublicRole(tmp.Name, tmp) }) } if err := g.Wait(); err != nil { -- Gitee From 1a4b16f62bfab7c72c38c2f2c8a40f0ac54c54c2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 20:27:38 +0800 Subject: [PATCH 0253/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index a6cb42be..d2d739b7 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -74,9 +74,9 @@ func presetRole() error { }) publicRoles := []*service.Role{} - var readOnlyRole *service.Role - var contributorRole *service.Role - var projectAdminRole *service.Role + readOnlyRole := &service.Role{} + contributorRole := &service.Role{} + projectAdminRole := &service.Role{} if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { log.DPanic(err) } -- Gitee From 385fb703828ab622c73f68da7a4acaca4b900e23 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 20:46:16 +0800 Subject: [PATCH 0254/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/contributor.yaml | 2 +- pkg/cli/initconfig/cmd/read-only.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml index 72513e43..e69c4524 100644 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -1,4 +1,4 @@ -name: Contributor +name: contributor rules: - verbs: - get_workflow diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index 6b27e122..c781fb94 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -1,4 +1,4 @@ -name: ReadOnly +name: readonly rules: - verbs: - get_workflow -- Gitee From 426c2c655fe96e42d1930249d4690a38e6f2cc87 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 26 Oct 2021 20:49:48 +0800 Subject: [PATCH 0255/2647] add ingress display to service list Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 3844e98b..251580c5 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -19,11 +19,13 @@ package service import ( "encoding/json" "fmt" + "sort" "strings" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -192,7 +194,7 @@ type Workload struct { ProductName string `json:"product_name"` Spec corev1.ServiceSpec `json:"-"` Images []string `json:"-"` - Ready bool + Ready bool `json:"ready"` } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -268,6 +270,17 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p productRespInfo.Status = setting.PodUnstable productRespInfo.Ready = setting.PodNotReady } + selector := labels.SelectorFromValidatedSet(workload.Spec.Selector) + if ingresses, err := getter.ListIngresses(namespace, selector, kubeClient); err == nil { + ingressInfo := new(IngressInfo) + hostInfos := make([]resource.HostInfo, 0) + for _, ingress := range ingresses { + hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) + } + ingressInfo.HostInfo = hostInfos + productRespInfo.Ingress = ingressInfo + } + resp = append(resp, productRespInfo) } -- Gitee From 6445a15c87e263a2c096a83fb7468f39ae19539a Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 21:01:00 +0800 Subject: [PATCH 0256/2647] initconfig Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/contributor.yaml | 2 +- pkg/cli/initconfig/cmd/init.go | 29 ++++++++--------------- pkg/cli/initconfig/cmd/project-admin.yaml | 2 +- pkg/shared/client/policy/policy.go | 14 ++++++++--- 4 files changed, 23 insertions(+), 24 deletions(-) diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml index e69c4524..34a8ebd2 100644 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -13,7 +13,7 @@ rules: - delete_environment resources: - Environment - kind: resourcepkg/cli/initconfig/cmd/init.go + kind: resource - verbs: - get_build - get_service diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index d2d739b7..60300e39 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -24,7 +24,6 @@ import ( "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" ) @@ -63,34 +62,26 @@ func run() error { func initSystemConfig() error { return presetRole() } + func presetRole() error { g := new(errgroup.Group) g.Go(func() error { - systemRole := &service.Role{} + systemRole := &policy.Role{} if err := yaml.Unmarshal(admin, systemRole); err != nil { log.DPanic(err) } return policy.NewDefault().CreateSystemRole(systemRole.Name, systemRole) }) - publicRoles := []*service.Role{} - readOnlyRole := &service.Role{} - contributorRole := &service.Role{} - projectAdminRole := &service.Role{} - if err := yaml.Unmarshal(readOnly, readOnlyRole); err != nil { - log.DPanic(err) - } - if err := yaml.Unmarshal(contributor, contributorRole); err != nil { - log.DPanic(err) - } - if err := yaml.Unmarshal(projectAdmin, projectAdminRole); err != nil { - log.DPanic(err) - } - publicRoles = append(publicRoles, readOnlyRole, contributorRole, projectAdminRole) - for _, v := range publicRoles { - tmp := v + rolesArray := [][]byte{readOnly, contributor, projectAdmin} + + for _, v := range rolesArray { + role := &policy.Role{} + if err := yaml.Unmarshal(v, role); err != nil { + log.DPanic(err) + } g.Go(func() error { - return policy.NewDefault().CreatePublicRole(tmp.Name, tmp) + return policy.NewDefault().CreatePublicRole(role.Name, role) }) } if err := g.Wait(); err != nil { diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml index 68938e3f..b64adb5c 100644 --- a/pkg/cli/initconfig/cmd/project-admin.yaml +++ b/pkg/cli/initconfig/cmd/project-admin.yaml @@ -1,4 +1,4 @@ -name: project-admin +name: admin rules: - verbs: - "*" diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index c1c0d393..4ec6b283 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -3,7 +3,6 @@ package policy import ( "fmt" - "github.com/koderover/zadig/pkg/microservice/policy/core/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -54,14 +53,23 @@ func (c *Client) DeleteRoleBinding(name string, projectName string) error { return err } -func (c *Client) CreateSystemRole(name string, role *service.Role) error { +func (c *Client) CreateSystemRole(name string, role *Role) error { url := fmt.Sprintf("/system-roles/%s", name) _, err := c.Put(url, httpclient.SetBody(role)) return err } -func (c *Client) CreatePublicRole(name string, role *service.Role) error { +func (c *Client) CreatePublicRole(name string, role *Role) error { url := fmt.Sprintf("/public-roles/%s", name) _, err := c.Put(url, httpclient.SetBody(role)) return err } + +type Role struct { + Name string `json:"name"` + Rules []*struct { + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + } `json:"rules"` +} -- Gitee From c3fbbba78d3f08586d632c810cb1f3a98ccfd305 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 26 Oct 2021 21:02:04 +0800 Subject: [PATCH 0257/2647] initconfig Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 64c82e38..76456979 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -79,8 +79,7 @@ func UpdateOrCreateRole(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") return } - name := c.Param("name") - args.Name = name + args.Name = c.Param("name") ctx.Err = service.UpdateOrCreateRole(projectName, args, ctx.Logger) } -- Gitee From 865faae8f4e7b82fd4eb05fe7337a69021525dd7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 26 Oct 2021 21:11:17 +0800 Subject: [PATCH 0258/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 251580c5..39f020ca 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -19,7 +19,6 @@ package service import ( "encoding/json" "fmt" - "sort" "strings" -- Gitee From 908daabf2503a1bfc92ff437926fd319d2eb64a2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 21:12:08 +0800 Subject: [PATCH 0259/2647] naming stuff Signed-off-by: Min Min --- .../aslan/core/service/service/yaml_template.go | 4 ++-- pkg/microservice/aslan/core/templatestore/service/yaml.go | 4 ++-- pkg/setting/consts.go | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go index 11b1c70e..a434fdf0 100644 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -76,8 +76,8 @@ func renderYamlFromTemplate(yaml, productName, serviceName string, variables []* for _, variable := range variables { yaml = strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) } - yaml = strings.Replace(yaml, "$T-Product$", productName, -1) - yaml = strings.Replace(yaml, "$T-Service$", serviceName, -1) + yaml = strings.Replace(yaml, setting.TemplateVariableProduct, productName, -1) + yaml = strings.Replace(yaml, setting.TemplateVariableService, serviceName, -1) return yaml } diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index df9c3638..fc382dff 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -15,8 +15,8 @@ import ( ) var DefaultSystemVariable = map[string]string{ - "$T-Product$": "项目名称", - "$T-Service$": "服务名称", + setting.TemplateVariableProduct: setting.TemplateVariableProductDescription, + setting.TemplateVariableService: setting.TemplateVariableServiceDescription, } func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0e4118bb..4fc71e8b 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -494,6 +494,14 @@ const ( RegExpParameter = `{{.(\w)+}}` ) +// template common constant +const ( + TemplateVariableProduct = "$T-Project$" + TemplateVariableProductDescription = "项目名称" + TemplateVariableService = "$T-Service$" + TemplateVariableServiceDescription = "服务名称" +) + const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" -- Gitee From e3a330d6caf22252e2d12a9e5d0cfed074f270a8 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 26 Oct 2021 21:13:01 +0800 Subject: [PATCH 0260/2647] changed location Signed-off-by: Min Min --- pkg/setting/consts.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 4fc71e8b..869b6e24 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -219,6 +219,8 @@ const ( SourceFromHelm = "helm" //SourceFromExternal SourceFromExternal = "external" + // service from yaml template + ServiceSourceTemplate = "template" ProdENV = "prod" TestENV = "test" @@ -345,8 +347,6 @@ const ( GerritDefaultOwner = "dafault" // YamlFileSeperator ... YamlFileSeperator = "\n---\n" - - ServiceSourceTemplate = "template" ) const MaskValue = "********" -- Gitee From 227b4e0507c90efc7ee3e1e5ca29538410496058 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 26 Oct 2021 22:18:54 +0800 Subject: [PATCH 0261/2647] optimize code Signed-off-by: allenshen --- .../repository/models/template/product.go | 3 - .../aslan/core/common/service/render.go | 11 - .../core/environment/handler/environment.go | 32 --- .../core/environment/handler/renderset.go | 67 +---- .../aslan/core/environment/handler/router.go | 8 - .../core/environment/service/environment.go | 36 --- .../core/environment/service/renderset.go | 239 ------------------ .../aslan/core/service/service/helm.go | 22 +- .../aslan/core/service/service/types.go | 7 +- pkg/tool/helmclient/helmclient.go | 4 +- 10 files changed, 7 insertions(+), 422 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 80b8d53f..69bc08d9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -105,10 +105,7 @@ type GitRepoConfig struct { } type CustomYaml struct { - //YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - //GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` - //ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` } // RenderChart ... diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index e318374e..a695162c 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -48,12 +48,6 @@ type KVPair struct { Value string `json:"value"` } -//type YamlData struct { -//YamlSource string `json:"yamlSource,omitempty"` -//GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` -//ValuesYAML string `json:"valuesYAML,omitempty"` -//} - type RenderChartArg struct { EnvName string `json:"envName,omitempty"` ServiceName string `json:"serviceName,omitempty"` @@ -62,11 +56,6 @@ type RenderChartArg struct { OverrideYaml string `json:"overrideYaml,omitempty"` } -//type RendersetArg struct { -// DefaultValues *YamlData `json:"defaultValues"` -// *RenderChartArg -//} - func (args *RenderChartArg) ToOverrideValueString() string { if len(args.OverrideValues) == 0 { return "" diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index a939b1a2..766830a8 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -303,38 +303,6 @@ func UpdateHelmProductRenderset(c *gin.Context) { } } -//func UpdateHelmProductRenderCharts(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// -// productName := c.Param("productName") -// if productName == "" { -// ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") -// return -// } -// -// envName := c.Query("envName") -// if envName == "" { -// ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") -// return -// } -// -// arg := new(service.EnvRenderChartArg) -// data, err := c.GetRawData() -// if err != nil { -// log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) -// } -// if err = json.Unmarshal(data, arg); err != nil { -// log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) -// } -// internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新", "helm集成环境变量", "", string(data), ctx.Logger) -// -// ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.Username, ctx.RequestID, arg, ctx.Logger) -// if ctx.Err != nil { -// ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) -// } -//} - func UpdateMultiHelmEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 14361ce0..bf9442ee 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -22,16 +22,14 @@ import ( "strings" "sync" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" + "github.com/gin-gonic/gin" "github.com/pkg/errors" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - - "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) func GetServiceRenderCharts(c *gin.Context) { @@ -68,65 +66,6 @@ func GetProductDefaultValues(c *gin.Context) { ctx.Resp, ctx.Err = service.GetDefaultValues(c.Query("productName"), c.Query("envName"), ctx.Logger) } -//func CreateOrUpdateRenderChart(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// -// if c.Query("productName") == "" { -// ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") -// return -// } -// -// if c.Query("envName") == "" { -// ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") -// return -// } -// -// data, err := c.GetRawData() -// if err != nil { -// log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) -// } -// -// args := new(commonservice.RenderChartArg) -// if err = json.Unmarshal(data, args); err != nil { -// log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) -// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) -// } -// internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) -// -// ctx.Err = service.CreateOrUpdateChartValues(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) -//} - -//func CreateOrUpdateRenderset(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// -// if c.Query("productName") == "" { -// ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") -// return -// } -// -// if c.Query("envName") == "" { -// ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") -// return -// } -// -// data, err := c.GetRawData() -// if err != nil { -// log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) -// } -// -// args := new(commonservice.RendersetArg) -// if err = json.Unmarshal(data, args); err != nil { -// log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) -// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) -// return -// } -// internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) -// -// ctx.Err = service.CreateOrUpdateRenderset(c.Query("productName"), c.Query("envName"), args, ctx.Username, ctx.RequestID, ctx.Logger) -//} - func GetYamlContent(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -180,7 +119,7 @@ func GetYamlContent(c *gin.Context) { RepoLink: repoLink, }) if err1 != nil { - err = errors.Errorf("fail to download file from git, err: %s, path: %s", err1.Error(), path) + err = errors.Errorf("fail to download file from git, err: %s, path: %s", err1, path) return } fileContentMap.Store(index, fileContent) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 031143e5..3ebe7044 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -133,9 +133,6 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/services/:serviceName/containers/:container/namespaces/:namespace", GetServiceContainer) environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) - - //TODO need to be deprecated - //environments.PUT("/:productName/renderchart", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) } // --------------------------------------------------------------------------------------- @@ -146,11 +143,6 @@ func (*Router) Inject(router *gin.RouterGroup) { rendersets.GET("/renderchart", GetServiceRenderCharts) rendersets.GET("/default-values", GetProductDefaultValues) rendersets.GET("/yamlContent", GetYamlContent) - - //TODO need to deprecated - //rendersets.PUT("/renderset", CreateOrUpdateRenderset) - //TODO need to deprecated - //rendersets.PUT("/renderchart", CreateOrUpdateRenderChart) // create or update renderchart when onBoarding } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ab104a17..02b77fdc 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -175,7 +175,6 @@ func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, } envStatusSlice := make([]*EnvStatus, 0) for _, publicProduct := range products { - //log.Infof("######### the product name is %s %s", publicProduct.ProductName, productName) if publicProduct.ProductName != productName { continue } @@ -1305,41 +1304,6 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcList, productRenderset, log) } -// UpdateHelmProductRenderCharts TODO need to be deprecated -//func UpdateHelmProductRenderCharts(productName, envName, userName, requestID string, args *EnvRenderChartArg, log *zap.SugaredLogger) error { -// -// renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") -// -// opt := &commonrepo.RenderSetFindOption{Name: renderSetName} -// productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) -// if err != nil || productRenderset == nil { -// if err != nil { -// log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) -// } -// return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) -// } -// -// // render charts need to be updated -// updatedRcs := make([]*template.RenderChart, 0) -// -// for _, requestRenderChart := range args.ChartValues { -// // update renderset info -// for _, curRenderChart := range productRenderset.ChartInfos { -// if curRenderChart.ServiceName != requestRenderChart.ServiceName { -// continue -// } -// if !checkOverrideValuesChange(curRenderChart, requestRenderChart) { -// continue -// } -// requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) -// updatedRcs = append(updatedRcs, curRenderChart) -// break -// } -// } -// -// return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcs, productRenderset, log) -//} - func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 8ab64c64..7fcf3b2d 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -21,12 +21,9 @@ import ( "strings" "go.mongodb.org/mongo-driver/mongo" - - "github.com/pkg/errors" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -105,239 +102,3 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def ret.DefaultValues = rendersetObj.DefaultValues return ret, nil } - -// validate yaml content -//func validateYamlContent(yamlContent string) error { -// tMap := map[string]interface{}{} -// if err := yaml.Unmarshal([]byte(yamlContent), &tMap); err != nil { -// if err != nil { -// return errors.New("yaml content illegal") -// } -// } -// return nil -//} - -//func generateValuesYaml(yamlData *commonservice.YamlData, log *zap.SugaredLogger) error { -// if yamlData == nil { -// return nil -// } -// if yamlData.YamlSource == setting.ValuesYamlSourceFreeEdit { -// return validateYamlContent(yamlData.ValuesYAML) -// } else if yamlData.YamlSource == setting.ValuesYamlSourceGitRepo { -// if yamlData.GitRepoConfig == nil { -// return errors.New("invalid repo config") -// } -// var ( -// allValues []byte -// fileContentMap sync.Map -// wg sync.WaitGroup -// err error -// ) -// -// for index, filePath := range yamlData.GitRepoConfig.ValuesPaths { -// wg.Add(1) -// go func(index int, path string) { -// defer wg.Done() -// fileContent, err1 := fsservice.DownloadFileFromSource( -// &fsservice.DownloadFromSourceArgs{ -// CodehostID: yamlData.GitRepoConfig.CodehostID, -// Owner: yamlData.GitRepoConfig.Owner, -// Repo: yamlData.GitRepoConfig.Repo, -// Path: path, -// Branch: yamlData.GitRepoConfig.Branch, -// }) -// if err1 != nil { -// err = errors.Errorf("fail to download file from git, err: %s, path: %s, repo: %v", err1.Error(), path, *yamlData.GitRepoConfig) -// return -// } -// fileContentMap.Store(index, fileContent) -// }(index, filePath) -// } -// wg.Wait() -// -// if err != nil { -// return err -// } -// -// allValueYamls := make([][]byte, len(yamlData.GitRepoConfig.ValuesPaths), len(yamlData.GitRepoConfig.ValuesPaths)) -// for i := 0; i < len(yamlData.GitRepoConfig.ValuesPaths); i++ { -// contentObj, _ := fileContentMap.Load(i) -// allValueYamls[i] = contentObj.([]byte) -// } -// allValues, err = yamlutil.Merge(allValueYamls) -// if err != nil { -// return errors.Errorf("failed to merge yaml files, repo: %v", *yamlData.GitRepoConfig) -// } -// yamlData.ValuesYAML = string(allValues) -// } -// return nil -//} - -//func CreateOrUpdateRenderset(productName, envName string, args *commonservice.RendersetArg, userName, requestID string, log *zap.SugaredLogger) error { -// serviceName := args.ServiceName -// -// serviceOpt := &commonrepo.ServiceFindOption{ -// ProductName: productName, -// ServiceName: serviceName, -// Type: setting.HelmDeployType, -// } -// serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) -// } -// if serviceObj == nil { -// return e.ErrCreateRenderSet.AddDesc("service not found") -// } -// -// if serviceObj.HelmChart == nil { -// return e.ErrCreateRenderSet.AddDesc("missing helm chart info") -// } -// -// if args.DefaultValues != nil && args.DefaultValues.YamlSource != "" { -// err := generateValuesYaml(args.DefaultValues, log) -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(err.Error()) -// } -// } -// -// renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") -// -// opt := &commonrepo.RenderSetFindOption{Name: renderSetName} -// curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) -// } -// -// if !found { -// curRenderset = &commonmodels.RenderSet{ -// Name: renderSetName, -// EnvName: envName, -// ProductTmpl: productName, -// UpdateBy: userName, -// IsDefault: false, -// } -// } -// -// //update or insert service values.yaml -// if args.RenderChartArg != nil { -// var targetChartInfo *template.RenderChart -// for _, singleChart := range curRenderset.ChartInfos { -// if singleChart.ServiceName != serviceName { -// continue -// } -// args.RenderChartArg.FillRenderChartModel(singleChart, singleChart.ChartVersion) -// targetChartInfo = singleChart -// break -// } -// if targetChartInfo == nil { -// targetChartInfo = new(template.RenderChart) -// targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml -// args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) -// curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) -// } -// } -// -// //update environment's defaults.yaml -// if args.DefaultValues != nil { -// curRenderset.DefaultValues = args.DefaultValues.ValuesYAML -// } else { -// curRenderset.DefaultValues = "" -// } -// -// //create new renderset with increased revision -// err = commonservice.CreateHelmRenderSet( -// curRenderset, -// log, -// ) -// -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(err.Error()) -// } -// return err -//} - -//func CreateOrUpdateChartValues(productName, envName string, args *commonservice.RenderChartArg, userName, requestID string, log *zap.SugaredLogger) error { -// -// serviceName := args.ServiceName -// -// serviceOpt := &commonrepo.ServiceFindOption{ -// ProductName: productName, -// ServiceName: serviceName, -// Type: setting.HelmDeployType, -// } -// serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) -// } -// if serviceObj == nil { -// return e.ErrCreateRenderSet.AddDesc("service not found") -// } -// -// if serviceObj.HelmChart == nil { -// return e.ErrCreateRenderSet.AddDesc("missing helm chart info") -// } -// -// renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") -// -// opt := &commonrepo.RenderSetFindOption{Name: renderSetName} -// curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) -// } -// -// if !found { -// curRenderset = &commonmodels.RenderSet{ -// Name: renderSetName, -// EnvName: envName, -// ProductTmpl: productName, -// UpdateBy: userName, -// IsDefault: false, -// } -// } -// -// //update or insert service values.yaml -// var targetChartInfo *template.RenderChart -// for _, singleChart := range curRenderset.ChartInfos { -// if singleChart.ServiceName != serviceName { -// continue -// } -// args.FillRenderChartModel(singleChart, singleChart.ChartVersion) -// targetChartInfo = singleChart -// break -// } -// if targetChartInfo == nil { -// targetChartInfo = new(template.RenderChart) -// targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml -// args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) -// curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) -// } -// -// //create new renderset with increased revision -// err = commonservice.CreateHelmRenderSet( -// curRenderset, -// log, -// ) -// -// if err != nil { -// return e.ErrCreateRenderSet.AddDesc(err.Error()) -// } -// return err -//} - -func ensureHelmRenderSetArgs(args *commonmodels.RenderSet) error { - if args == nil { - return errors.New("nil RenderSet") - } - - if len(args.Name) == 0 { - return errors.New("empty render set name") - } - // 设置新的版本号 - rev, err := commonrepo.NewCounterColl().GetNextSeq("renderset:" + args.Name) - if err != nil { - return fmt.Errorf("get next render set revision error: %v", err) - } - - args.Revision = rev - return nil -} diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 5dd8fc4d..1d0b3f4c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -670,27 +670,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { LoadPath: args.FilePath, } case setting.SourceFromChartTemplate: - var yamlData *template.CustomYaml - //if args.CodehostID > 0 { - // yamlData = &template.CustomYaml{ - // YamlSource: setting.ValuesYamlSourceGitRepo, - // YamlContent: "", - // GitRepoConfig: &templatemodels.GitRepoConfig{ - // CodehostID: args.CodehostID, - // Owner: args.Owner, - // Repo: args.Repo, - // Branch: args.Branch, - // }, - // ValuesPaths: args.ValuePaths, - // } - //} else if len(args.ValuesYaml) > 0 { - // yamlData = &template.CustomYaml{ - // YamlSource: setting.ValuesYamlSourceFreeEdit, - // YamlContent: args.ValuesYaml, - // } - //} - - yamlData = &template.CustomYaml{ + yamlData := &template.CustomYaml{ YamlContent: args.ValuesYaml, } diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index a5b1f5d3..a09a4477 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -79,13 +79,8 @@ type CreateFromPublicRepo struct { } type CreateFromChartTemplate struct { - //CodehostID int `json:"codehostID"` - //Owner string `json:"owner"` - //Repo string `json:"repo"` - //Branch string `json:"branch"` TemplateName string `json:"templateName"` - //ValuesPaths []string `json:"valuesPaths"` - ValuesYAML string `json:"valuesYAML"` + ValuesYAML string `json:"valuesYAML"` } func PublicRepoToPrivateRepoArgs(args *CreateFromPublicRepo) (*CreateFromRepo, error) { diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index cca8e2fa..6a149b24 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -47,8 +47,8 @@ type KV struct { } // MergeOverrideValues merge override yaml and override kvs -// override yaml used for -f option -// override kvs used for --set option +// defaultValues overrideYaml used for -f option +// overrideValues used for --set option func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { // merge files for helm -f option -- Gitee From 4c14922093caf5c0ac6e59f1f45a4ffc221b5eb9 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 27 Oct 2021 10:27:25 +0800 Subject: [PATCH 0262/2647] add bulk create role bindings api Signed-off-by: lou --- .../policy/core/handler/role_binding.go | 23 ++++--- .../core/repository/mongodb/role_binding.go | 15 +++++ .../core/service/bundle/exemption_urls.go | 2 +- .../policy/core/service/role_binding.go | 60 +++++++++++-------- 4 files changed, 68 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 0b9cbcdf..fe95f5cf 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -34,13 +34,22 @@ func CreateRoleBinding(c *gin.Context) { return } - args := &service.RoleBinding{} - if err := c.ShouldBindJSON(args); err != nil { - ctx.Err = err - return + args := make([]*service.RoleBinding, 0) + if c.Query("bulk") == "true" { + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = err + return + } + } else { + rb := &service.RoleBinding{} + if err := c.ShouldBindJSON(rb); err != nil { + ctx.Err = err + return + } + args = append(args, rb) } - ctx.Err = service.CreateRoleBinding(projectName, args, ctx.Logger) + ctx.Err = service.CreateRoleBindings(projectName, args, ctx.Logger) } func ListRoleBindings(c *gin.Context) { @@ -88,9 +97,9 @@ func CreateSystemRoleBinding(c *gin.Context) { return } - args.Global = false + args.Public = false - ctx.Err = service.CreateRoleBinding(service.SystemScope, args, ctx.Logger) + ctx.Err = service.CreateRoleBindings(service.SystemScope, []*service.RoleBinding{args}, ctx.Logger) } func ListSystemRoleBindings(c *gin.Context) { diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 28e02ed0..fa3ff2c3 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -116,3 +116,18 @@ func (c *RoleBindingColl) Create(obj *models.RoleBinding) error { return err } + +func (c *RoleBindingColl) BulkCreate(objs []*models.RoleBinding) error { + if len(objs) == 0 { + return nil + } + + var ois []interface{} + for _, obj := range objs { + ois = append(ois, obj) + } + + _, err := c.InsertMany(context.TODO(), ois) + + return err +} diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 7d161d00..7b4b451e 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -54,7 +54,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{""}, + Endpoints: []string{"", "signin", "setup", "loading", "static/**"}, }, { Methods: []string{"GET"}, diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 4aeb6ed4..51ce9dc2 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -29,36 +29,23 @@ type RoleBinding struct { Name string `json:"name"` UID string `json:"uid"` Role string `json:"role"` - Global bool `json:"global"` + Public bool `json:"public"` } const SystemScope = "*" -func CreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { - nsRole := ns - if rb.Global { - nsRole = "" - } - role, found, err := mongodb.NewRoleColl().Get(nsRole, rb.Role) - if err != nil { - logger.Errorf("Failed to get role %s in namespace %s, err: %s", rb.Role, nsRole, err) - return err - } else if !found { - logger.Errorf("Role %s is not found in namespace %s", rb.Role, nsRole) - return fmt.Errorf("role %s not found", rb.Role) - } +func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger) error { + var objs []*models.RoleBinding + for _, rb := range rbs { + obj, err := createRoleBindingObject(ns, rb, logger) + if err != nil { + return err + } - obj := &models.RoleBinding{ - Name: rb.Name, - Namespace: ns, - Subjects: []*models.Subject{{Kind: models.UserKind, UID: rb.UID}}, - RoleRef: &models.RoleRef{ - Name: role.Name, - Namespace: role.Namespace, - }, + objs = append(objs, obj) } - return mongodb.NewRoleBindingColl().Create(obj) + return mongodb.NewRoleBindingColl().BulkCreate(objs) } func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, error) { @@ -73,7 +60,7 @@ func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, err Name: v.Name, Role: v.RoleRef.Name, UID: v.Subjects[0].UID, - Global: v.Namespace == "", + Public: v.Namespace == "", }) } @@ -83,3 +70,28 @@ func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, err func DeleteRoleBinding(name string, projectName string, _ *zap.SugaredLogger) error { return mongodb.NewRoleBindingColl().Delete(name, projectName) } + +func createRoleBindingObject(ns string, rb *RoleBinding, logger *zap.SugaredLogger) (*models.RoleBinding, error) { + nsRole := ns + if rb.Public { + nsRole = "" + } + role, found, err := mongodb.NewRoleColl().Get(nsRole, rb.Role) + if err != nil { + logger.Errorf("Failed to get role %s in namespace %s, err: %s", rb.Role, nsRole, err) + return nil, err + } else if !found { + logger.Errorf("Role %s is not found in namespace %s", rb.Role, nsRole) + return nil, fmt.Errorf("role %s not found", rb.Role) + } + + return &models.RoleBinding{ + Name: rb.Name, + Namespace: ns, + Subjects: []*models.Subject{{Kind: models.UserKind, UID: rb.UID}}, + RoleRef: &models.RoleRef{ + Name: role.Name, + Namespace: role.Namespace, + }, + }, nil +} -- Gitee From b4b5aa7a993bcbd06931bcb9930ae8b8df6291f2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 11:28:27 +0800 Subject: [PATCH 0263/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 64 ++++++++++++++----- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 39f020ca..291ab3e1 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -187,13 +187,13 @@ func (f *workloadFilter) Match(workload *Workload) bool { } type Workload struct { - EnvName string `json:"env_name"` - Name string `json:"name"` - Type string `json:"type"` - ProductName string `json:"product_name"` - Spec corev1.ServiceSpec `json:"-"` - Images []string `json:"-"` - Ready bool `json:"ready"` + EnvName string `json:"env_name"` + Name string `json:"name"` + Type string `json:"type"` + ProductName string `json:"product_name"` + Spec corev1.ReplicationControllerSpec `json:"-"` + Images []string `json:"-"` + Ready bool `json:"ready"` } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -213,7 +213,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range listDeployments { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ServiceSpec{Selector: v.Spec.Selector.MatchLabels}, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Selector: v.Spec.Selector.MatchLabels, Template: &v.Spec.Template}, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) } statefulSets, err := getter.ListStatefulSets(namespace, nil, kubeClient) if err != nil { @@ -221,7 +221,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range statefulSets { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ServiceSpec{Selector: v.Spec.Selector.MatchLabels}, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Selector: v.Spec.Selector.MatchLabels, Template: &v.Spec.Template}, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) } log.Debugf("Found %d workloads in total", len(workLoads)) @@ -250,6 +250,13 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } } + ingressM := make(map[string][]resource.HostInfo) + if ingresses, err := getter.ListIngresses(namespace, nil, kubeClient); err == nil { + for _, ingress := range ingresses { + ingressM[ingress.Name] = wrapper.Ingress(ingress).HostInfo() + } + } + for _, workload := range workLoads { tmpProductName := workload.ProductName if tmpProductName == "" && productName != "" { @@ -269,15 +276,12 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p productRespInfo.Status = setting.PodUnstable productRespInfo.Ready = setting.PodNotReady } - selector := labels.SelectorFromValidatedSet(workload.Spec.Selector) - if ingresses, err := getter.ListIngresses(namespace, selector, kubeClient); err == nil { - ingressInfo := new(IngressInfo) - hostInfos := make([]resource.HostInfo, 0) - for _, ingress := range ingresses { - hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) + + selector := labels.SelectorFromValidatedSet(workload.Spec.Template.Labels) + if services, err := getter.ListServices(namespace, selector, kubeClient); err == nil { + productRespInfo.Ingress = &IngressInfo{ + HostInfo: findServiceFromIngress(ingressM, services), } - ingressInfo.HostInfo = hostInfos - productRespInfo.Ingress = ingressInfo } resp = append(resp, productRespInfo) @@ -287,3 +291,29 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return count, resp, nil } + +func findServiceFromIngress(ingressM map[string][]resource.HostInfo, services []*corev1.Service) []resource.HostInfo { + if len(services) == 0 { + return []resource.HostInfo{} + } + serviceNames := sets.NewString() + for _, service := range services { + serviceNames.Insert(service.Name) + } + hostInfos := make([]resource.HostInfo, 0) + for _, hostInfos := range ingressM { + for _, hostInfo := range hostInfos { + contain := false + for _, backend := range hostInfo.Backends { + if serviceNames.Has(backend.ServiceName) { + contain = true + break + } + } + if contain { + hostInfos = append(hostInfos, hostInfo) + } + } + } + return hostInfos +} -- Gitee From 56605793d129480002303775ed21bcf41ab91b76 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 27 Oct 2021 11:39:04 +0800 Subject: [PATCH 0264/2647] optimize code Signed-off-by: allenshen --- .../service/environment_creator.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index a77500ab..f8c5a8d6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -153,9 +153,9 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P } // renderset may exist before product created, by setting values.yaml content - var renderset *models.RenderSet + var renderSet *models.RenderSet if args.Render == nil || args.Render.Revision == 0 { - renderset, _, err = commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ + renderSet, _, err = commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ Name: args.Namespace, }) @@ -164,15 +164,15 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } // if env renderset is predefined, set render info - if renderset != nil { + if renderSet != nil { args.Render = &models.RenderInfo{ ProductTmpl: args.ProductName, - Name: renderset.Name, - Revision: renderset.Revision, + Name: renderSet.Name, + Revision: renderSet.Revision, } // user renderchart from renderset chartInfoMap := make(map[string]*template.RenderChart) - for _, renderChart := range renderset.ChartInfos { + for _, renderChart := range renderSet.ChartInfos { chartInfoMap[renderChart.ServiceName] = renderChart } @@ -191,8 +191,8 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } - if renderset == nil { - renderset, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) + if renderSet == nil { + renderSet, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) if err != nil { log.Errorf("[%s][P:%s] find product renderset error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddDesc(err.Error()) @@ -200,7 +200,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P } // 设置产品render revision - args.Render.Revision = renderset.Revision + args.Render.Revision = renderSet.Revision // 记录服务当前对应render版本 setServiceRender(args) @@ -217,7 +217,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P eventStart := time.Now().Unix() - go installProductHelmCharts(user, args.EnvName, requestID, args, renderset, eventStart, helmClient, log) + go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, log) return nil } -- Gitee From 5f7ff9aaf292f6163adc9198f4779e996e45e950 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 11:40:24 +0800 Subject: [PATCH 0265/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 291ab3e1..6b8fec05 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -22,6 +22,8 @@ import ( "sort" "strings" + "github.com/koderover/zadig/pkg/tool/log" + "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" @@ -250,6 +252,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } } + // get all ingresses ingressM := make(map[string][]resource.HostInfo) if ingresses, err := getter.ListIngresses(namespace, nil, kubeClient); err == nil { for _, ingress := range ingresses { @@ -278,6 +281,8 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } selector := labels.SelectorFromValidatedSet(workload.Spec.Template.Labels) + log.Infof("workload.Spec.Template.Labels:%+v", workload.Spec.Template.Labels) + log.Infof("workload.Spec.Selector:%+v", workload.Spec.Selector) if services, err := getter.ListServices(namespace, selector, kubeClient); err == nil { productRespInfo.Ingress = &IngressInfo{ HostInfo: findServiceFromIngress(ingressM, services), @@ -298,6 +303,7 @@ func findServiceFromIngress(ingressM map[string][]resource.HostInfo, services [] } serviceNames := sets.NewString() for _, service := range services { + log.Infof("service.name:%s", service.Name) serviceNames.Insert(service.Name) } hostInfos := make([]resource.HostInfo, 0) -- Gitee From c5440148fe4545f8a22af54cbd9b919fdc629b04 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 11:49:17 +0800 Subject: [PATCH 0266/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 6b8fec05..801df233 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -22,8 +22,6 @@ import ( "sort" "strings" - "github.com/koderover/zadig/pkg/tool/log" - "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" @@ -281,8 +279,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } selector := labels.SelectorFromValidatedSet(workload.Spec.Template.Labels) - log.Infof("workload.Spec.Template.Labels:%+v", workload.Spec.Template.Labels) - log.Infof("workload.Spec.Selector:%+v", workload.Spec.Selector) if services, err := getter.ListServices(namespace, selector, kubeClient); err == nil { productRespInfo.Ingress = &IngressInfo{ HostInfo: findServiceFromIngress(ingressM, services), @@ -303,10 +299,9 @@ func findServiceFromIngress(ingressM map[string][]resource.HostInfo, services [] } serviceNames := sets.NewString() for _, service := range services { - log.Infof("service.name:%s", service.Name) serviceNames.Insert(service.Name) } - hostInfos := make([]resource.HostInfo, 0) + resp := make([]resource.HostInfo, 0) for _, hostInfos := range ingressM { for _, hostInfo := range hostInfos { contain := false @@ -317,9 +312,9 @@ func findServiceFromIngress(ingressM map[string][]resource.HostInfo, services [] } } if contain { - hostInfos = append(hostInfos, hostInfo) + resp = append(resp, hostInfo) } } } - return hostInfos + return resp } -- Gitee From 751de8e120223a378af1c8f55bf53fb267fdffef Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 11:53:21 +0800 Subject: [PATCH 0267/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 801df233..84dfd70c 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -213,7 +213,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range listDeployments { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Selector: v.Spec.Selector.MatchLabels, Template: &v.Spec.Template}, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Template: &v.Spec.Template}, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) } statefulSets, err := getter.ListStatefulSets(namespace, nil, kubeClient) if err != nil { @@ -221,7 +221,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range statefulSets { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Selector: v.Spec.Selector.MatchLabels, Template: &v.Spec.Template}, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Template: &v.Spec.Template}, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) } log.Debugf("Found %d workloads in total", len(workLoads)) -- Gitee From 8dff54cb73ab0f7fc0956c78422532d86fbfb777 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 11:57:10 +0800 Subject: [PATCH 0268/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 84dfd70c..7b8f5a62 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -187,13 +187,13 @@ func (f *workloadFilter) Match(workload *Workload) bool { } type Workload struct { - EnvName string `json:"env_name"` - Name string `json:"name"` - Type string `json:"type"` - ProductName string `json:"product_name"` - Spec corev1.ReplicationControllerSpec `json:"-"` - Images []string `json:"-"` - Ready bool `json:"ready"` + EnvName string `json:"env_name"` + Name string `json:"name"` + Type string `json:"type"` + ProductName string `json:"product_name"` + Spec corev1.PodTemplateSpec `json:"-"` + Images []string `json:"-"` + Ready bool `json:"ready"` } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -213,7 +213,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range listDeployments { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Template: &v.Spec.Template}, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) } statefulSets, err := getter.ListStatefulSets(namespace, nil, kubeClient) if err != nil { @@ -221,7 +221,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range statefulSets { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ReplicationControllerSpec{Template: &v.Spec.Template}, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) } log.Debugf("Found %d workloads in total", len(workLoads)) @@ -278,7 +278,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p productRespInfo.Ready = setting.PodNotReady } - selector := labels.SelectorFromValidatedSet(workload.Spec.Template.Labels) + selector := labels.SelectorFromValidatedSet(workload.Spec.Labels) if services, err := getter.ListServices(namespace, selector, kubeClient); err == nil { productRespInfo.Ingress = &IngressInfo{ HostInfo: findServiceFromIngress(ingressM, services), -- Gitee From cc2379df29d43fc66e4e6232380e682fe6ff42db Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 27 Oct 2021 11:57:26 +0800 Subject: [PATCH 0269/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/environment/handler/renderset.go | 10 +++++----- .../aslan/core/environment/service/renderset.go | 6 ++++++ pkg/microservice/aslan/core/service/service/helm.go | 3 +-- pkg/tool/helmclient/helmclient.go | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index bf9442ee..a52a38fa 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -105,11 +105,11 @@ func GetYamlContent(c *gin.Context) { repoLink = c.Query("repoLink") ) - for index, filePath := range pathArr { + for i, filePath := range pathArr { wg.Add(1) go func(index int, path string) { defer wg.Done() - fileContent, err1 := fsservice.DownloadFileFromSource( + fileContent, errDownload := fsservice.DownloadFileFromSource( &fsservice.DownloadFromSourceArgs{ CodehostID: codehostID, Owner: owner, @@ -118,12 +118,12 @@ func GetYamlContent(c *gin.Context) { Branch: branch, RepoLink: repoLink, }) - if err1 != nil { - err = errors.Errorf("fail to download file from git, err: %s, path: %s", err1, path) + if errDownload != nil { + err = errors.Wrapf(errDownload, fmt.Sprintf("fail to download file from git, path %s", path)) return } fileContentMap.Store(index, fileContent) - }(index, filePath) + }(i, filePath) } wg.Wait() diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 7fcf3b2d..0069d37a 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -85,15 +85,21 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def return ret, nil } if err != nil { + log.Errorf("faild to query product info, productName %s envName %s err %s", productName, envName, err) return nil, fmt.Errorf("faild to query product info, productName %s envName %s", productName, envName) } + if productInfo.Render == nil { + return nil, fmt.Errorf("invalid product, nil render data") + } + opt := &commonrepo.RenderSetFindOption{ Name: productInfo.Render.Name, Revision: productInfo.Render.Revision, } rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { + log.Errorf("faild to query renderset info, envName %s err %s", productInfo.Render.Name, err) return nil, err } if !existed { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 1d0b3f4c..478e20b2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -311,8 +311,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe CreateBy: args.CreatedBy, Source: setting.SourceFromChartTemplate, HelmTemplateName: templateArgs.TemplateName, - //ValuePaths: templateArgs.ValuesPaths, - ValuesYaml: templateArgs.ValuesYAML, + ValuesYaml: templateArgs.ValuesYAML, }, logger, ) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 6a149b24..93a2a0fd 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -52,7 +52,7 @@ type KV struct { func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { // merge files for helm -f option - // precedence from low to high: defaultValues valuesYaml overrideYaml + // precedence from low to high: valuesYaml defaultValues overrideYaml valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(defaultValues), []byte(overrideYaml)}) if err != nil { return "", err -- Gitee From 20cc52a53c48fb5949c690d0552ac07344a2f4bd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 13:39:36 +0800 Subject: [PATCH 0270/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 7b8f5a62..e9f5efdd 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -294,7 +294,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } func findServiceFromIngress(ingressM map[string][]resource.HostInfo, services []*corev1.Service) []resource.HostInfo { - if len(services) == 0 { + if len(services) == 0 || len(ingressM) == 0 { return []resource.HostInfo{} } serviceNames := sets.NewString() -- Gitee From 61b128d22647417b48e1553aa6d6f373740b6880 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 13:52:31 +0800 Subject: [PATCH 0271/2647] features Signed-off-by: mouuii --- cmd/systemconfig/main.go | 11 +++++ .../systemconfig/config/config.go | 4 ++ .../systemconfig/config/consts.go | 4 ++ .../systemconfig/core/handler/features.go | 42 +++++++++++++++++++ .../systemconfig/core/handler/router.go | 4 ++ 5 files changed, 65 insertions(+) create mode 100644 pkg/microservice/systemconfig/core/handler/features.go diff --git a/cmd/systemconfig/main.go b/cmd/systemconfig/main.go index 90b61cc3..760713e9 100644 --- a/cmd/systemconfig/main.go +++ b/cmd/systemconfig/main.go @@ -18,14 +18,25 @@ package main import ( "context" + "flag" "log" "os/signal" "syscall" + "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/server" ) func main() { + + flag.String(config.FeatureFlag, "", "help message for flagname") + + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) go func() { <-ctx.Done() diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index 821936f8..8300198c 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -21,3 +21,7 @@ func DexMysqlUser() string { func DexMysqlPassword() string { return viper.GetString(ENVDexMysqlPassword) } + +func Features()string { + return viper.GetString(FeatureFlag) +} \ No newline at end of file diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go index 23453c57..f830d417 100644 --- a/pkg/microservice/systemconfig/config/consts.go +++ b/pkg/microservice/systemconfig/config/consts.go @@ -6,3 +6,7 @@ const ( ENVDexMysqlUser = "DEX_MYSQL_USER" ENVDexMysqlPassword = "DEX_MYSQL_PASSWORD" ) + +const ( + FeatureFlag = "features" +) diff --git a/pkg/microservice/systemconfig/core/handler/features.go b/pkg/microservice/systemconfig/core/handler/features.go new file mode 100644 index 00000000..6a69d930 --- /dev/null +++ b/pkg/microservice/systemconfig/core/handler/features.go @@ -0,0 +1,42 @@ +package handler + +import ( + "strings" + "sync" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +var featureM map[string]string = map[string]string{ + "xx": "true", +} + +var FeatureFlag string +var once sync.Once + +func GetFeatures(c *gin.Context) { + featureName := c.Query("feature") + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + once.Do(func(){ + features := config.Features() + featureList := strings.Split(features,",") + for _,v := range featureList{ + feature := strings.Split(v,"=") + if len(feature) != 2 { + continue + } + if _ , ok := featureM[feature[0]];ok { + featureM[feature[0]] = feature[1] + } + } + }) + if v,ok := featureM[featureName];ok{ + ctx.Resp = v + } + ctx.Resp = "notfound" + +} diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index b59de382..e681b846 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -31,4 +31,8 @@ func (*Router) Inject(router *gin.RouterGroup) { roles.PUT("/:id", UpdateConnector) roles.DELETE("/:id", DeleteConnector) } + features := router.Group("features") + { + features.GET("", GetFeatures) + } } -- Gitee From c94a5b4df552d7dd4e1bf9bf8a32f055a5d58323 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 27 Oct 2021 14:09:37 +0800 Subject: [PATCH 0272/2647] optimize code Signed-off-by: allenshen --- .../core/environment/handler/renderset.go | 61 ++----------------- .../core/environment/service/renderset.go | 49 +++++++++++++++ 2 files changed, 54 insertions(+), 56 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index a52a38fa..60b6e094 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -17,19 +17,13 @@ limitations under the License. package handler import ( - "fmt" "strconv" "strings" - "sync" "github.com/gin-gonic/gin" - "github.com/pkg/errors" - - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) func GetServiceRenderCharts(c *gin.Context) { @@ -93,56 +87,11 @@ func GetYamlContent(c *gin.Context) { } pathArr := strings.Split(c.Query("valuesPaths"), ",") - - contentArr := make([][]byte, 0) - var ( - fileContentMap sync.Map - wg sync.WaitGroup - owner = c.Query("owner") - repo = c.Query("repo") - branch = c.Query("branch") - repoLink = c.Query("repoLink") + owner = c.Query("owner") + repo = c.Query("repo") + branch = c.Query("branch") + repoLink = c.Query("repoLink") ) - - for i, filePath := range pathArr { - wg.Add(1) - go func(index int, path string) { - defer wg.Done() - fileContent, errDownload := fsservice.DownloadFileFromSource( - &fsservice.DownloadFromSourceArgs{ - CodehostID: codehostID, - Owner: owner, - Repo: repo, - Path: path, - Branch: branch, - RepoLink: repoLink, - }) - if errDownload != nil { - err = errors.Wrapf(errDownload, fmt.Sprintf("fail to download file from git, path %s", path)) - return - } - fileContentMap.Store(index, fileContent) - }(i, filePath) - } - wg.Wait() - - if err != nil { - ctx.Err = err - return - } - - allValueYamls := make([][]byte, len(pathArr), len(pathArr)) - for i := 0; i < len(pathArr); i++ { - contentObj, _ := fileContentMap.Load(i) - allValueYamls[i] = contentObj.([]byte) - contentArr = append(contentArr, contentObj.([]byte)) - } - ret, err := yamlutil.Merge(contentArr) - if err != nil { - ctx.Err = fmt.Errorf("failed to merge files, err %s", err) - return - } - - ctx.Resp = string(ret) + ctx.Resp, ctx.Err = service.GetMergedYamlContent(codehostID, owner, repo, branch, repoLink, pathArr) } diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 0069d37a..cadbf78a 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -19,6 +19,9 @@ package service import ( "fmt" "strings" + "sync" + + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" @@ -27,6 +30,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" + "github.com/pkg/errors" ) type DefaultValuesResp struct { @@ -108,3 +113,47 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def ret.DefaultValues = rendersetObj.DefaultValues return ret, nil } + +func GetMergedYamlContent(codehostID int, owner, repo, branch, repoLink string, paths []string) (string, error) { + var ( + fileContentMap sync.Map + wg sync.WaitGroup + err error + ) + for i, filePath := range paths { + wg.Add(1) + go func(index int, path string) { + defer wg.Done() + fileContent, errDownload := fsservice.DownloadFileFromSource( + &fsservice.DownloadFromSourceArgs{ + CodehostID: codehostID, + Owner: owner, + Repo: repo, + Path: path, + Branch: branch, + RepoLink: repoLink, + }) + if errDownload != nil { + err = errors.Wrapf(errDownload, fmt.Sprintf("fail to download file from git, path %s", path)) + return + } + fileContentMap.Store(index, fileContent) + }(i, filePath) + } + wg.Wait() + + if err != nil { + return "", err + } + + contentArr := make([][]byte, 0, len(paths)) + for i := 0; i < len(paths); i++ { + contentObj, _ := fileContentMap.Load(i) + contentArr = append(contentArr, contentObj.([]byte)) + } + ret, err := yamlutil.Merge(contentArr) + if err != nil { + return "", errors.Wrapf(err, "failed to merge files") + } + return string(ret), nil +} -- Gitee From 6f57f55d1c6372981c65dbada691a0eea2a5c2b0 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 27 Oct 2021 14:26:28 +0800 Subject: [PATCH 0273/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 6 +- pkg/config/config.go | 14 +- .../systemconfig/config/config.go | 16 +-- .../systemconfig/config/consts.go | 5 +- .../core/repository/orm/connector.go | 2 +- pkg/microservice/systemconfig/core/service.go | 10 +- .../systemconfig/core/service/types.go | 2 +- pkg/microservice/user/config/config.go | 51 ++----- pkg/microservice/user/config/consts.go | 1 - .../handler/login/{internal.go => local.go} | 3 +- .../login/{third_part.go => third_party.go} | 125 ++++++++---------- pkg/microservice/user/core/handler/router.go | 17 +-- .../user/core/handler/user/user.go | 15 ++- .../user/core/repository/models/user.go | 7 +- .../user/core/repository/models/user_login.go | 9 +- .../core/repository/{mysql => orm}/user.go | 13 +- .../repository/{mysql => orm}/user_login.go | 9 +- pkg/microservice/user/core/service.go | 77 +++-------- .../service/login/{internal.go => local.go} | 28 ++-- .../login/{third_part.go => third_party.go} | 28 ++-- .../user/core/service/login/token.go | 29 ++++ .../user/core/service/user/user.go | 59 +++++---- pkg/microservice/user/server/rest/router.go | 7 +- pkg/microservice/user/server/server.go | 2 +- pkg/setting/consts.go | 28 ++-- pkg/util/sign.go | 24 ---- 26 files changed, 272 insertions(+), 315 deletions(-) rename pkg/microservice/user/core/handler/login/{internal.go => local.go} (92%) rename pkg/microservice/user/core/handler/login/{third_part.go => third_party.go} (47%) rename pkg/microservice/user/core/repository/{mysql => orm}/user.go (86%) rename pkg/microservice/user/core/repository/{mysql => orm}/user_login.go (86%) rename pkg/microservice/user/core/service/login/{internal.go => local.go} (84%) rename pkg/microservice/user/core/service/login/{third_part.go => third_party.go} (78%) create mode 100644 pkg/microservice/user/core/service/login/token.go diff --git a/go.mod b/go.mod index 4603a21b..3dc239e8 100644 --- a/go.mod +++ b/go.mod @@ -16,21 +16,19 @@ require ( github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 github.com/coocood/freecache v1.1.0 - github.com/coreos/go-oidc v2.1.0+incompatible github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b - github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.1 // indirect - github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect github.com/garyburd/redigo v1.6.2 // indirect github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 @@ -40,7 +38,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 - github.com/jinzhu/gorm v0.0.0-20180213101209-6e1387b44c64 + github.com/jinzhu/gorm v1.9.16 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mittwald/go-helm-client v0.8.0 diff --git a/pkg/config/config.go b/pkg/config/config.go index 1936fed8..477198b4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -86,7 +86,7 @@ func AslanServiceInfo() *setting.ServiceInfo { } func SecretKey() string { - return viper.GetString(setting.SecretKey) + return viper.GetString(setting.ENVSecretKey) } func AslanServiceAddress() string { @@ -228,3 +228,15 @@ func MongoURI() string { func MongoDatabase() string { return viper.GetString(setting.ENVAslanDBName) } + +func MysqlUser() string { + return viper.GetString(setting.ENVMysqlUser) +} + +func MysqlPassword() string { + return viper.GetString(setting.ENVMysqlPassword) +} + +func MysqlHost() string { + return viper.GetString(setting.ENVMysqlHost) +} diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index 821936f8..5d0b8882 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -6,18 +6,6 @@ import ( _ "github.com/koderover/zadig/pkg/config" ) -func DexMysqlDB() string { - return viper.GetString(ENVDexMysqlDB) -} - -func DexMysqlHost() string { - return viper.GetString(ENVDexMysqlHost) -} - -func DexMysqlUser() string { - return viper.GetString(ENVDexMysqlUser) -} - -func DexMysqlPassword() string { - return viper.GetString(ENVDexMysqlPassword) +func MysqlDexDB() string { + return viper.GetString(ENVMysqlDexDB) } diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go index 23453c57..552789aa 100644 --- a/pkg/microservice/systemconfig/config/consts.go +++ b/pkg/microservice/systemconfig/config/consts.go @@ -1,8 +1,5 @@ package config const ( - ENVDexMysqlDB = "DEX_MYSQL_DB" - ENVDexMysqlHost = "DEX_MYSQL_HOST" - ENVDexMysqlUser = "DEX_MYSQL_USER" - ENVDexMysqlPassword = "DEX_MYSQL_PASSWORD" + ENVMysqlDexDB = "MYSQL_DEX_DB" ) diff --git a/pkg/microservice/systemconfig/core/repository/orm/connector.go b/pkg/microservice/systemconfig/core/repository/orm/connector.go index a51b858d..9ac1e501 100644 --- a/pkg/microservice/systemconfig/core/repository/orm/connector.go +++ b/pkg/microservice/systemconfig/core/repository/orm/connector.go @@ -33,7 +33,7 @@ type ConnectorColl struct { func NewConnectorColl() *ConnectorColl { name := models.Connector{}.TableName() return &ConnectorColl{ - DB: gormtool.DB(config.DexMysqlDB()), + DB: gormtool.DB(config.MysqlDexDB()), coll: name, } } diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index a2e7b91a..4030bd2f 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -38,13 +38,13 @@ func Start(_ context.Context) { } func initDatabase() { - err := gormtool.Open(config.DexMysqlUser(), - config.DexMysqlPassword(), - config.DexMysqlHost(), - config.DexMysqlDB(), + err := gormtool.Open(configbase.MysqlUser(), + configbase.MysqlPassword(), + configbase.MysqlHost(), + config.MysqlDexDB(), ) if err != nil { - log.Panicf("Failed to open database %s", config.DexMysqlDB()) + log.Panicf("Failed to open database %s", config.MysqlDexDB()) } } diff --git a/pkg/microservice/systemconfig/core/service/types.go b/pkg/microservice/systemconfig/core/service/types.go index 90c22df8..f9f45d74 100644 --- a/pkg/microservice/systemconfig/core/service/types.go +++ b/pkg/microservice/systemconfig/core/service/types.go @@ -19,6 +19,7 @@ package service import ( "encoding/json" + "github.com/coreos/go-oidc/v3/oidc" "github.com/dexidp/dex/connector/gitea" "github.com/dexidp/dex/connector/github" "github.com/dexidp/dex/connector/gitlab" @@ -26,7 +27,6 @@ import ( "github.com/dexidp/dex/connector/ldap" "github.com/dexidp/dex/connector/linkedin" "github.com/dexidp/dex/connector/microsoft" - "github.com/dexidp/dex/connector/oidc" ) type ConnectorType string diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go index 2b5b0933..160295d1 100644 --- a/pkg/microservice/user/config/config.go +++ b/pkg/microservice/user/config/config.go @@ -17,71 +17,48 @@ limitations under the License. package config import ( - "context" - "fmt" - "net/http" + "strconv" "strings" - "github.com/coreos/go-oidc" "github.com/spf13/viper" _ "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" ) func IssuerURL() string { - return viper.GetString(setting.IssuerURL) + return viper.GetString(setting.ENVIssuerURL) } func Debug() string { - return viper.GetString(setting.Debug) + return viper.GetString(setting.ENVDebug) } func ClientID() string { - return viper.GetString(setting.ClientID) + return viper.GetString(setting.ENVClientID) } func ClientSecret() string { - return viper.GetString(setting.ClientSecret) + return viper.GetString(setting.ENVClientSecret) } func RedirectURI() string { - return viper.GetString(setting.RedirectURI) + return viper.GetString(setting.ENVRedirectURI) } func Scopes() []string { - return strings.Split(viper.GetString(setting.Scopes), ",") + return strings.Split(viper.GetString(setting.ENVScopes), ",") } -func Client() *http.Client { - return http.DefaultClient +func MysqlUserDB() string { + return viper.GetString(setting.ENVMysqlUserDB) } -func Provider() *oidc.Provider { - ctx := oidc.ClientContext(context.Background(), Client()) - provider, err := oidc.NewProvider(ctx, IssuerURL()) +func TokenExpiresAt() int { + expiresAt, err := strconv.Atoi(viper.GetString(setting.ENVTokenExpiresAt)) if err != nil { - panic(fmt.Sprintf("init provider error:%s", err.Error())) + log.Panic(err) } - return provider -} - -func Verifier() *oidc.IDTokenVerifier { - return Provider().Verifier(&oidc.Config{ClientID: ClientID()}) -} - -func User() string { - return viper.GetString(setting.MysqlUser) -} - -func Password() string { - return viper.GetString(setting.Password) -} - -func Host() string { - return viper.GetString(setting.Host) -} - -func Name() string { - return viper.GetString(setting.Name) + return expiresAt } diff --git a/pkg/microservice/user/config/consts.go b/pkg/microservice/user/config/consts.go index 14144b2d..e7c7dca3 100644 --- a/pkg/microservice/user/config/consts.go +++ b/pkg/microservice/user/config/consts.go @@ -18,6 +18,5 @@ package config const ( AppState = "zadig user" - Audience = "zadig" SystemIdentityType = "system" ) diff --git a/pkg/microservice/user/core/handler/login/internal.go b/pkg/microservice/user/core/handler/login/local.go similarity index 92% rename from pkg/microservice/user/core/handler/login/internal.go rename to pkg/microservice/user/core/handler/login/local.go index 74c1a6ee..8f6a5338 100644 --- a/pkg/microservice/user/core/handler/login/internal.go +++ b/pkg/microservice/user/core/handler/login/local.go @@ -2,11 +2,12 @@ package login import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/core/service/login" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -func InternalLogin(c *gin.Context) { +func LocalLogin(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() args := &login.LoginArgs{} diff --git a/pkg/microservice/user/core/handler/login/third_part.go b/pkg/microservice/user/core/handler/login/third_party.go similarity index 47% rename from pkg/microservice/user/core/handler/login/third_part.go rename to pkg/microservice/user/core/handler/login/third_party.go index c274cf6d..30ad43b0 100644 --- a/pkg/microservice/user/core/handler/login/third_part.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -2,26 +2,38 @@ package login import ( "bytes" + "context" "encoding/json" "fmt" + "net/http" + "net/url" + "time" + "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" + "golang.org/x/oauth2" + "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/login" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/util" - "golang.org/x/oauth2" - "net/http" - "net/url" - "time" + "github.com/koderover/zadig/pkg/tool/log" ) +func provider() *oidc.Provider { + ctx := oidc.ClientContext(context.Background(), http.DefaultClient) + provider, err := oidc.NewProvider(ctx, config.IssuerURL()) + if err != nil { + log.Panic(fmt.Sprintf("init provider error:%s", err)) + } + return provider +} + func Login(c *gin.Context) { oauth2Config := &oauth2.Config{ ClientID: config.ClientID(), ClientSecret: config.ClientSecret(), - Endpoint: config.Provider().Endpoint(), + Endpoint: provider().Endpoint(), Scopes: config.Scopes(), RedirectURL: config.RedirectURI(), } @@ -29,84 +41,63 @@ func Login(c *gin.Context) { c.Redirect(http.StatusSeeOther, authCodeURL) } -func Callback(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - oidcCtx := oidc.ClientContext(c.Request.Context(), config.Client()) +func verifyAndDecode(ctx context.Context, code string) (*login.Claims, error) { + oidcCtx := oidc.ClientContext(ctx, http.DefaultClient) oauth2Config := &oauth2.Config{ ClientID: config.ClientID(), ClientSecret: config.ClientSecret(), - Endpoint: config.Provider().Endpoint(), + Endpoint: provider().Endpoint(), Scopes: nil, RedirectURL: config.RedirectURI(), } - var err error var token *oauth2.Token - switch c.Request.Method { - case http.MethodGet: - // Authorization redirect callback from OAuth2 auth flow. - if errMsg := c.Query("error"); errMsg != "" { - ctx.Err = e.ErrCallBackUser.AddDesc(errMsg) - return - } - code := c.Query("code") - if code == "" { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("no code in request: %q", c.Request.Form)) - return - } - if state := c.Query("state"); state != config.AppState { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("expected state %q got %q", config.AppState, state)) - return - } - token, err = oauth2Config.Exchange(oidcCtx, code) - case http.MethodPost: - // Form request from frontend to refresh a token. - refresh := c.Query("refresh_token") - if refresh == "" { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("no refresh_token in request: %q", c.Request.Form)) - return - } - t := &oauth2.Token{ - RefreshToken: refresh, - Expiry: time.Now().Add(-time.Hour), - } - token, err = oauth2Config.TokenSource(oidcCtx, t).Token() - default: - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("method not implemented: %s", c.Request.Method)) - return - } - + token, err := oauth2Config.Exchange(oidcCtx, code) if err != nil { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("failed to get token: %v", err)) - return + return nil, e.ErrCallBackUser.AddDesc(fmt.Sprintf("failed to get token: %v", err)) } - rawIDToken, ok := token.Extra("id_token").(string) if !ok { - ctx.Err = e.ErrCallBackUser.AddDesc("no id_token in token response") - return + return nil, e.ErrCallBackUser.AddDesc("no id_token in token response") } - - idToken, err := config.Verifier().Verify(c.Request.Context(), rawIDToken) + idToken, err := provider().Verifier(&oidc.Config{ClientID: config.ClientID()}).Verify(ctx, rawIDToken) if err != nil { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("failed to verify ID token: %v", err)) - return + return nil, e.ErrCallBackUser.AddDesc(fmt.Sprintf("failed to verify ID token: %v", err)) } - var claimsRaw json.RawMessage if err := idToken.Claims(&claimsRaw); err != nil { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("error decoding ID token claims: %v", err)) - return + return nil, e.ErrCallBackUser.AddDesc(fmt.Sprintf("error decoding ID token claims: %v", err)) } buff := new(bytes.Buffer) if err := json.Indent(buff, claimsRaw, "", " "); err != nil { - ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("error indenting ID token claims: %v", err)) - return + return nil, e.ErrCallBackUser.AddDesc(fmt.Sprintf("error indenting ID token claims: %v", err)) } - - var claims util.Claims + var claims login.Claims err = json.Unmarshal(claimsRaw, &claims) + if err != nil { + return nil, err + } + return &claims, nil +} + +func Callback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Authorization redirect callback from OAuth2 auth flow. + if errMsg := c.Query("error"); errMsg != "" { + ctx.Err = e.ErrCallBackUser.AddDesc(errMsg) + return + } + code := c.Query("code") + if code == "" { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("no code in request: %q", c.Request.Form)) + return + } + if state := c.Query("state"); state != config.AppState { + ctx.Err = e.ErrCallBackUser.AddDesc(fmt.Sprintf("expected state %q got %q", config.AppState, state)) + return + } + claims, err := verifyAndDecode(c.Request.Context(), code) if err != nil { ctx.Err = err return @@ -120,15 +111,15 @@ func Callback(c *gin.Context) { ctx.Err = err return } - claims.Uid = user.Uid - claims.StandardClaims.ExpiresAt = time.Now().Add(24 * time.Hour).Unix() - userToken, err := util.CreateToken(&claims) + claims.Uid = user.UID + claims.StandardClaims.ExpiresAt = time.Now().Add(time.Duration(config.TokenExpiresAt()) * time.Minute).Unix() + userToken, err := login.CreateToken(claims) if err != nil { ctx.Err = err return } v := url.Values{} v.Add("token", userToken) - redirectUrl := "../..?" + v.Encode() + redirectUrl := "/?" + v.Encode() c.Redirect(http.StatusSeeOther, redirectUrl) } diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index eaab5cbe..2964f347 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -18,6 +18,7 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/core/handler/login" "github.com/koderover/zadig/pkg/microservice/user/core/handler/user" ) @@ -25,20 +26,16 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - users := router.Group("/") + users := router.Group("") { - users.GET("login", login.Login) - - users.GET("api/v1/callback", login.Callback) - - users.POST("login", login.InternalLogin) + users.GET("/callback", login.Callback) - users.POST("api/v1/users", user.CreateUser) + users.POST("/users", user.CreateUser) - users.PUT("api/v1/users/:uid/password", user.UpdatePassword) + users.PUT("/users/:uid/password", user.UpdatePassword) - users.GET("api/v1/users/:uid", user.GetUser) + users.GET("/users/:uid", user.GetUser) - users.GET("api/v1/users", user.GetUsers) + users.GET("/users", user.ListUsers) } } diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index d89ee9ff..a16e107e 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -2,9 +2,9 @@ package user import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/core/service/user" internalhandler "github.com/koderover/zadig/pkg/shared/handler" - "strconv" ) func GetUser(c *gin.Context) { @@ -13,14 +13,15 @@ func GetUser(c *gin.Context) { ctx.Resp, ctx.Err = user.GetUser(c.Param("uid"), ctx.Logger) } -func GetUsers(c *gin.Context) { +func ListUsers(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := &user.Users{} - args.Name = c.Query("name") - args.Page, _ = strconv.Atoi(c.Query("page")) - args.PerPage, _ = strconv.Atoi(c.Query("per_page")) - ctx.Resp, ctx.Err = user.GetUsers(args, ctx.Logger) + args := &user.QueryArgs{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = user.SeachUsers(args, ctx.Logger) } func CreateUser(c *gin.Context) { diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go index 53351e5f..98ce949f 100644 --- a/pkg/microservice/user/core/repository/models/user.go +++ b/pkg/microservice/user/core/repository/models/user.go @@ -4,9 +4,14 @@ import "github.com/koderover/zadig/pkg/microservice/user/core" type User struct { core.Model - Uid string `json:"uid"` + UID string `json:"uid"` Name string `json:"name"` IdentityType string `gorm:"default:'unknown'" json:"identity_type"` Email string `json:"email"` Phone string `json:"phone"` } + +// TableName sets the insert table name for this struct type +func (User) TableName() string { + return "user" +} diff --git a/pkg/microservice/user/core/repository/models/user_login.go b/pkg/microservice/user/core/repository/models/user_login.go index b4bc0a1d..86d2b301 100644 --- a/pkg/microservice/user/core/repository/models/user_login.go +++ b/pkg/microservice/user/core/repository/models/user_login.go @@ -1,6 +1,8 @@ package models -import "github.com/koderover/zadig/pkg/microservice/user/core" +import ( + "github.com/koderover/zadig/pkg/microservice/user/core" +) type UserLogin struct { core.Model @@ -8,3 +10,8 @@ type UserLogin struct { Password string `json:"password"` LastLoginTime int64 `json:"last_login_time"` } + +// TableName sets the insert table name for this struct type +func (UserLogin) TableName() string { + return "user_login" +} diff --git a/pkg/microservice/user/core/repository/mysql/user.go b/pkg/microservice/user/core/repository/orm/user.go similarity index 86% rename from pkg/microservice/user/core/repository/mysql/user.go rename to pkg/microservice/user/core/repository/orm/user.go index 406914d8..9c34f210 100644 --- a/pkg/microservice/user/core/repository/mysql/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -1,7 +1,8 @@ -package mysql +package orm import ( - "github.com/jinzhu/gorm" + "gorm.io/gorm" + "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" ) @@ -40,8 +41,8 @@ func GetUserByUid(uid string, db *gorm.DB) (*models.User, error) { return &user, nil } -// GetUsers gets a list of users based on paging constraints -func GetUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { +// ListUsers gets a list of users based on paging constraints +func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { var ( users []models.User err error @@ -60,11 +61,11 @@ func GetUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, e } // GetUsersCount gets user count -func GetUsersCount(name string) (int, error) { +func GetUsersCount(name string) (int64, error) { var ( users []models.User err error - count int + count int64 ) err = core.DB.Where("name LIKE ?", "%"+name+"%").Find(&users).Count(&count).Error diff --git a/pkg/microservice/user/core/repository/mysql/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go similarity index 86% rename from pkg/microservice/user/core/repository/mysql/user_login.go rename to pkg/microservice/user/core/repository/orm/user_login.go index 8bbd2cd8..a7bc6c42 100644 --- a/pkg/microservice/user/core/repository/mysql/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -1,7 +1,8 @@ -package mysql +package orm import ( - "github.com/jinzhu/gorm" + "gorm.io/gorm" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" ) @@ -26,8 +27,8 @@ func GetUserLogin(uid string, db *gorm.DB) (*models.UserLogin, error) { return &userLogin, nil } -// GetUserLogins Get a userLogin based on uid list -func GetUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { +// ListUserLogins Get a userLogin based on uid list +func ListUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { var userLogins []models.UserLogin err := db.Where("uid IN (?)", uids).Find(&userLogins).Error if err != nil { diff --git a/pkg/microservice/user/core/service.go b/pkg/microservice/user/core/service.go index f4c09094..4d50f873 100644 --- a/pkg/microservice/user/core/service.go +++ b/pkg/microservice/user/core/service.go @@ -18,16 +18,23 @@ package core import ( "context" - "fmt" - "github.com/jinzhu/gorm" - _ "github.com/jinzhu/gorm/dialects/mysql" + + "gorm.io/gorm" + "github.com/koderover/zadig/pkg/config" config2 "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/setting" + gormtool "github.com/koderover/zadig/pkg/tool/gorm" "github.com/koderover/zadig/pkg/tool/log" - "time" ) +var DB *gorm.DB + +type Model struct { + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + func Start(_ context.Context) { log.Init(&log.Config{ Level: config.LogLevel(), @@ -35,64 +42,22 @@ func Start(_ context.Context) { SendToFile: config.SendLogToFile(), Development: config.Mode() != setting.ReleaseMode, }) - Setup() -} -var DB *gorm.DB - -type Model struct { - CreatedAt int `json:"created_at"` - UpdatedAt int `json:"updated_at"` + initDatabase() + DB = gormtool.DB(config2.MysqlUserDB()) } -// Setup initializes the database instance -func Setup() { - var err error - DB, err = gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", - config2.User(), - config2.Password(), - config2.Host(), - config2.Name())) +func initDatabase() { + err := gormtool.Open(config.MysqlUser(), + config.MysqlPassword(), + config.MysqlHost(), + config2.MysqlUserDB(), + ) if err != nil { - log.Fatalf("models.Setup err: %v", err) - } - - gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string { - return defaultTableName - } - - DB.SingularTable(true) - DB.Callback().Create().Replace("gorm:update_time_stamp", updateTimeStampForCreateCallback) - DB.Callback().Update().Replace("gorm:update_time_stamp", updateTimeStampForUpdateCallback) - DB.DB().SetMaxIdleConns(10) - DB.DB().SetMaxOpenConns(100) -} - -// updateTimeStampForCreateCallback will set `CreatedOn`, `ModifiedOn` when creating -func updateTimeStampForCreateCallback(scope *gorm.Scope) { - if !scope.HasError() { - nowTime := time.Now().Unix() - if createTimeField, ok := scope.FieldByName("CreatedAt"); ok { - if createTimeField.IsBlank { - createTimeField.Set(nowTime) - } - } - - if modifyTimeField, ok := scope.FieldByName("UpdatedAt"); ok { - if modifyTimeField.IsBlank { - modifyTimeField.Set(nowTime) - } - } - } -} - -// updateTimeStampForUpdateCallback will set `ModifiedOn` when updating -func updateTimeStampForUpdateCallback(scope *gorm.Scope) { - if _, ok := scope.Get("gorm:update_column"); !ok { - scope.SetColumn("ModifiedOn", time.Now().Unix()) + log.Panicf("Failed to open database %s", config2.MysqlUserDB()) } } func Stop(_ context.Context) { - DB.Close() + gormtool.Close() } diff --git a/pkg/microservice/user/core/service/login/internal.go b/pkg/microservice/user/core/service/login/local.go similarity index 84% rename from pkg/microservice/user/core/service/login/internal.go rename to pkg/microservice/user/core/service/login/local.go index 4badd824..b1577223 100644 --- a/pkg/microservice/user/core/service/login/internal.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -2,14 +2,16 @@ package login import ( "fmt" - "github.com/dgrijalva/jwt-go" - "github.com/koderover/zadig/pkg/microservice/user/config" - "github.com/koderover/zadig/pkg/microservice/user/core" - "github.com/koderover/zadig/pkg/microservice/user/core/repository/mysql" - "github.com/koderover/zadig/pkg/util" + "time" + + "github.com/golang-jwt/jwt" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" - "time" + + "github.com/koderover/zadig/pkg/microservice/user/config" + "github.com/koderover/zadig/pkg/microservice/user/core" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" + "github.com/koderover/zadig/pkg/setting" ) type LoginArgs struct { @@ -27,7 +29,7 @@ type User struct { } func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { - user, err := mysql.GetUser(args.Email, config.SystemIdentityType, core.DB) + user, err := orm.GetUser(args.Email, config.SystemIdentityType, core.DB) if err != nil { logger.Errorf("InternalLogin get user email:%s error", args.Email) return nil, err @@ -35,7 +37,7 @@ func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { if user == nil { return nil, fmt.Errorf("user not exist") } - userLogin, err := mysql.GetUserLogin(user.Uid, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, core.DB) if err != nil { logger.Errorf("InternalLogin get user:%s user login not exist, error msg:%s", args.Email, err.Error()) return nil, err @@ -51,17 +53,17 @@ func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { return nil, fmt.Errorf("check password error, error msg:%s", err.Error()) } userLogin.LastLoginTime = time.Now().Unix() - err = mysql.UpdateUserLogin(userLogin.Uid, userLogin, core.DB) + err = orm.UpdateUserLogin(userLogin.Uid, userLogin, core.DB) if err != nil { logger.Errorf("InternalLogin user:%s update user login password error, error msg:%s", args.Email, err.Error()) return nil, err } - token, err := util.CreateToken(&util.Claims{ + token, err := CreateToken(&Claims{ Name: user.Name, Email: user.Email, - Uid: user.Uid, + Uid: user.UID, StandardClaims: jwt.StandardClaims{ - Audience: config.Audience, + Audience: setting.ProductName, ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), }, }) @@ -70,7 +72,7 @@ func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { return nil, err } return &User{ - Uid: user.Uid, + Uid: user.UID, Token: token, Email: user.Email, Phone: user.Phone, diff --git a/pkg/microservice/user/core/service/login/third_part.go b/pkg/microservice/user/core/service/login/third_party.go similarity index 78% rename from pkg/microservice/user/core/service/login/third_part.go rename to pkg/microservice/user/core/service/login/third_party.go index 81bc5338..f51c021b 100644 --- a/pkg/microservice/user/core/service/login/third_part.go +++ b/pkg/microservice/user/core/service/login/third_party.go @@ -1,12 +1,14 @@ package login import ( + "time" + "github.com/google/uuid" + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/user/core/repository/mysql" - "go.uber.org/zap" - "time" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" ) type SyncUserInfo struct { @@ -16,7 +18,7 @@ type SyncUserInfo struct { } func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.User, error) { - user, err := mysql.GetUser(syncUserInfo.Email, syncUserInfo.IdentityType, core.DB) + user, err := orm.GetUser(syncUserInfo.Email, syncUserInfo.IdentityType, core.DB) if err != nil { logger.Error("SyncUser get user:%s error, error msg:%s", syncUserInfo.Email, err.Error()) return nil, err @@ -30,40 +32,40 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us if user == nil { uid, _ := uuid.NewUUID() user = &models.User{ - Uid: uid.String(), + UID: uid.String(), Name: syncUserInfo.Name, Email: syncUserInfo.Email, IdentityType: syncUserInfo.IdentityType, } - err = mysql.CreateUser(user, tx) + err = orm.CreateUser(user, tx) if err != nil { tx.Rollback() logger.Error("SyncUser create user:%s error, error msg:%s", syncUserInfo.Email, err.Error()) return nil, err } } - userLogin, err := mysql.GetUserLogin(user.Uid, tx) + userLogin, err := orm.GetUserLogin(user.UID, tx) if err != nil { tx.Rollback() - logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.Uid, err.Error()) + logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) return nil, err } if userLogin != nil { userLogin.LastLoginTime = time.Now().Unix() - err = mysql.UpdateUserLogin(user.Uid, userLogin, tx) + err = orm.UpdateUserLogin(user.UID, userLogin, tx) if err != nil { tx.Rollback() - logger.Error("UpdateLoginInfo update user:%s login error, error msg:%s", user.Uid, err.Error()) + logger.Error("UpdateLoginInfo update user:%s login error, error msg:%s", user.UID, err.Error()) return nil, err } } else { - err = mysql.CreateUserLogin(&models.UserLogin{ - Uid: user.Uid, + err = orm.CreateUserLogin(&models.UserLogin{ + Uid: user.UID, LastLoginTime: time.Now().Unix(), }, tx) if err != nil { tx.Rollback() - logger.Error("UpdateLoginInfo create user:%s login error, error msg:%s", user.Uid, err.Error()) + logger.Error("UpdateLoginInfo create user:%s login error, error msg:%s", user.UID, err.Error()) return nil, err } } diff --git a/pkg/microservice/user/core/service/login/token.go b/pkg/microservice/user/core/service/login/token.go new file mode 100644 index 00000000..76ccfcdc --- /dev/null +++ b/pkg/microservice/user/core/service/login/token.go @@ -0,0 +1,29 @@ +package login + +import ( + "github.com/golang-jwt/jwt" + + "github.com/koderover/zadig/pkg/config" +) + +type Claims struct { + Name string `json:"name"` + Email string `json:"email"` + Uid string `json:"uid"` + FederatedClaims FederatedClaims `json:"federated_claims"` + jwt.StandardClaims +} + +type FederatedClaims struct { + ConnectorId string `json:"connector_id"` + UserId string `json:"user_id"` +} + +func CreateToken(claims *Claims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString([]byte(config.SecretKey())) + if err != nil { + return "", err + } + return tokenString, nil +} diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 0f368eb5..4b280723 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -2,13 +2,15 @@ package user import ( "fmt" + "github.com/google/uuid" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" + "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/user/core/repository/mysql" - "go.uber.org/zap" - "golang.org/x/crypto/bcrypt" + "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" ) type User struct { @@ -18,7 +20,7 @@ type User struct { Phone string `json:"phone,omitempty"` } -type Users struct { +type QueryArgs struct { Name string `json:"name"` PerPage int `json:"per_page"` Page int `json:"page"` @@ -39,13 +41,13 @@ type Password struct { NewPassword string `json:"newPassword"` } -type GetUsersResp struct { +type UsersResp struct { Users []UserInfo `json:"users"` - TotalCount int `json:"totalCount"` + TotalCount int64 `json:"totalCount"` } func GetUser(uid string, logger *zap.SugaredLogger) (*models.User, error) { - user, err := mysql.GetUserByUid(uid, core.DB) + user, err := orm.GetUserByUid(uid, core.DB) if err != nil { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err @@ -53,24 +55,29 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*models.User, error) { return user, nil } -func GetUsers(args *Users, logger *zap.SugaredLogger) (*GetUsersResp, error) { - count, err := mysql.GetUsersCount(args.Name) +func SeachUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { + count, err := orm.GetUsersCount(args.Name) if err != nil { - logger.Errorf("GetUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) + logger.Errorf("SeachUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) return nil, err } - users, err := mysql.GetUsers(args.Page, args.PerPage, args.Name, core.DB) + if count == 0 { + return &UsersResp{ + TotalCount: 0, + }, nil + } + users, err := orm.ListUsers(args.Page, args.PerPage, args.Name, core.DB) if err != nil { - logger.Errorf("GetUsers GetUsers By name:%s error, error msg:%s", args.Name, err.Error()) + logger.Errorf("SeachUsers SeachUsers By name:%s error, error msg:%s", args.Name, err.Error()) return nil, err } var uids []string for _, user := range users { - uids = append(uids, user.Uid) + uids = append(uids, user.UID) } - userLogins, err := mysql.GetUserLogins(uids, core.DB) + userLogins, err := orm.ListUserLogins(uids, core.DB) if err != nil { - logger.Errorf("GetUsers GetUserLogins By uids:%s error, error msg:%s", uids, err.Error()) + logger.Errorf("SeachUsers ListUserLogins By uids:%s error, error msg:%s", uids, err.Error()) return nil, err } userLoginMap := make(map[string]models.UserLogin) @@ -79,10 +86,10 @@ func GetUsers(args *Users, logger *zap.SugaredLogger) (*GetUsersResp, error) { } var usersInfo []UserInfo for _, user := range users { - if userLogin, ok := userLoginMap[user.Uid]; ok { + if userLogin, ok := userLoginMap[user.UID]; ok { usersInfo = append(usersInfo, UserInfo{ LastLoginTime: userLogin.LastLoginTime, - Uid: user.Uid, + Uid: user.UID, Phone: user.Phone, Name: user.Name, Email: user.Email, @@ -92,7 +99,7 @@ func GetUsers(args *Users, logger *zap.SugaredLogger) (*GetUsersResp, error) { logger.Error("user:%s login info not exist") } } - return &GetUsersResp{ + return &UsersResp{ Users: usersInfo, TotalCount: count, }, nil @@ -105,7 +112,7 @@ func CreateUser(args *User, logger *zap.SugaredLogger) error { Email: args.Email, IdentityType: config.SystemIdentityType, Phone: args.Phone, - Uid: uid.String(), + UID: uid.String(), } tx := core.DB.Begin() defer func() { @@ -113,7 +120,7 @@ func CreateUser(args *User, logger *zap.SugaredLogger) error { tx.Rollback() } }() - err := mysql.CreateUser(user, tx) + err := orm.CreateUser(user, tx) if err != nil { tx.Rollback() logger.Errorf("CreateUser CreateUser :%v error, error msg:%s", user, err.Error()) @@ -121,11 +128,11 @@ func CreateUser(args *User, logger *zap.SugaredLogger) error { } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.Password), bcrypt.DefaultCost) userLogin := &models.UserLogin{ - Uid: user.Uid, + Uid: user.UID, Password: string(hashedPassword), LastLoginTime: 0, } - err = mysql.CreateUserLogin(userLogin, tx) + err = orm.CreateUserLogin(userLogin, tx) if err != nil { tx.Rollback() logger.Errorf("CreateUser CreateUserLogin:%v error, error msg:%s", user, err.Error()) @@ -135,7 +142,7 @@ func CreateUser(args *User, logger *zap.SugaredLogger) error { } func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { - user, err := mysql.GetUserByUid(args.Uid, core.DB) + user, err := orm.GetUserByUid(args.Uid, core.DB) if err != nil { logger.Errorf("UpdatePassword GetUserByUid:%s error, error msg:%s", args.Uid, err.Error()) return err @@ -143,7 +150,7 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { if user == nil { return fmt.Errorf("user not exist") } - userLogin, err := mysql.GetUserLogin(user.Uid, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, core.DB) if err != nil { logger.Errorf("UpdatePassword GetUserLogin:%s error, error msg:%s", args.Uid, err.Error()) return err @@ -164,10 +171,10 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.NewPassword), bcrypt.DefaultCost) userLogin = &models.UserLogin{ - Uid: user.Uid, + Uid: user.UID, Password: string(hashedPassword), } - err = mysql.UpdateUserLogin(user.Uid, userLogin, core.DB) + err = orm.UpdateUserLogin(user.UID, userLogin, core.DB) if err != nil { logger.Errorf("UpdatePassword UpdateUserLogin:%v error, error msg:%s", userLogin, err.Error()) return err diff --git a/pkg/microservice/user/server/rest/router.go b/pkg/microservice/user/server/rest/router.go index 101ce1e5..27be0a98 100644 --- a/pkg/microservice/user/server/rest/router.go +++ b/pkg/microservice/user/server/rest/router.go @@ -20,11 +20,16 @@ import ( "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/user/core/handler" + "github.com/koderover/zadig/pkg/microservice/user/core/handler/login" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { + { + router.GET("login", login.Login) + router.POST("login", login.LocalLogin) + } for name, r := range map[string]injector{ - "/": new(handler.Router), + "/api/v1": new(handler.Router), } { r.Inject(router.Group(name)) } diff --git a/pkg/microservice/user/server/server.go b/pkg/microservice/user/server/server.go index a6c1e616..b892ae27 100644 --- a/pkg/microservice/user/server/server.go +++ b/pkg/microservice/user/server/server.go @@ -33,7 +33,7 @@ func Serve(ctx context.Context) error { log.Info("Start policy service") engine := rest.NewEngine() - server := &http.Server{Addr: ":5555", Handler: engine} + server := &http.Server{Addr: ":80", Handler: engine} stopChan := make(chan struct{}) go func() { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 761f919a..96feb129 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -29,6 +29,9 @@ const ( ENVAslanDBName = "ASLAN_DB" ENVHubAgentImage = "HUB_AGENT_IMAGE" ENVPoetryAPIRootKey = "POETRY_API_ROOT_KEY" + ENVMysqlUser = "MYSQL_USER" + ENVMysqlPassword = "MYSQL_PASSWORD" + ENVMysqlHost = "MYSQL_HOST" // Aslan ENVPodName = "BE_POD_NAME" @@ -101,22 +104,15 @@ const ( TestMode = "test" // user - IssuerURL = "ISSUER_URL" - Listen = "LISTEN" - TlsCert = "TLSCERT" - TlsKey = "TLSKEY" - RootCAs = "ROOT_CAS" - Debug = "DEBUG" - ClientID = "CLIENT_ID" - ClientSecret = "CLIENT_SECRET" - RedirectURI = "REDIRECT_URI" - SecretKey = "SECRET_KEY" - OfflineAsScope = "OFFLINE_AS_SCOPE" - MysqlUser = "MYSQL_USER" - Password = "PASSWORD" - Host = "HOST" - Name = "NAME" - Scopes = "SCOPES" + ENVIssuerURL = "ISSUER_URL" + ENVDebug = "DEBUG" + ENVClientID = "CLIENT_ID" + ENVClientSecret = "CLIENT_SECRET" + ENVRedirectURI = "REDIRECT_URI" + ENVSecretKey = "SECRET_KEY" + ENVMysqlUserDB = "MYSQL_USER_DB" + ENVScopes = "SCOPES" + ENVTokenExpiresAt = "TOKEN_EXPIRES_AT" ) // k8s concepts diff --git a/pkg/util/sign.go b/pkg/util/sign.go index b982d836..2e8cabca 100644 --- a/pkg/util/sign.go +++ b/pkg/util/sign.go @@ -20,8 +20,6 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "github.com/dgrijalva/jwt-go" - "github.com/koderover/zadig/pkg/config" ) // ComputeHmacSha256 According to ak/sk generate secret key @@ -33,25 +31,3 @@ func ComputeHmacSha256(ak string, sk string) string { hex.EncodeToString(h.Sum(nil)) return sha } - -type Claims struct { - Name string `json:"name"` - Email string `json:"email"` - Uid string `json:"uid"` - FederatedClaims FederatedClaims `json:"federated_claims"` - jwt.StandardClaims -} - -type FederatedClaims struct { - ConnectorId string `json:"connector_id"` - UserId string `json:"user_id"` -} - -func CreateToken(claims *Claims) (string, error) { - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString([]byte(config.SecretKey())) - if err != nil { - return "", err - } - return tokenString, nil -} -- Gitee From ed19a53fe38ac23bd16f7e0272626f71dad7c0e9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 15:09:17 +0800 Subject: [PATCH 0274/2647] Modify the way to get the service Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 71 +++++++++++++------ 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index e9f5efdd..c647d953 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -194,6 +194,7 @@ type Workload struct { Spec corev1.PodTemplateSpec `json:"-"` Images []string `json:"-"` Ready bool `json:"ready"` + ServiceName string `json:"service_name"` } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -250,11 +251,32 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } } + hostInfos := make([]resource.HostInfo, 0) // get all ingresses - ingressM := make(map[string][]resource.HostInfo) if ingresses, err := getter.ListIngresses(namespace, nil, kubeClient); err == nil { for _, ingress := range ingresses { - ingressM[ingress.Name] = wrapper.Ingress(ingress).HostInfo() + hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) + } + } + + // get all services + deployWorkloads := make([]*Workload, 0) + stsWorkloads := make([]*Workload, 0) + if services, err := getter.ListServices(namespace, nil, kubeClient); err == nil { + for _, service := range services { + selector := labels.SelectorFromValidatedSet(service.Spec.Selector) + if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { + for _, deploy := range listDeployments { + deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: service.Name}) + } + continue + } + + if listStatefulsets, _ := getter.ListStatefulSets(namespace, selector, kubeClient); len(listStatefulsets) > 0 { + for _, sts := range listStatefulsets { + stsWorkloads = append(stsWorkloads, &Workload{Name: sts.Name, ServiceName: service.Name}) + } + } } } @@ -278,11 +300,8 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p productRespInfo.Ready = setting.PodNotReady } - selector := labels.SelectorFromValidatedSet(workload.Spec.Labels) - if services, err := getter.ListServices(namespace, selector, kubeClient); err == nil { - productRespInfo.Ingress = &IngressInfo{ - HostInfo: findServiceFromIngress(ingressM, services), - } + productRespInfo.Ingress = &IngressInfo{ + HostInfo: findServiceFromIngress(hostInfos, workload, deployWorkloads, stsWorkloads), } resp = append(resp, productRespInfo) @@ -293,26 +312,34 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return count, resp, nil } -func findServiceFromIngress(ingressM map[string][]resource.HostInfo, services []*corev1.Service) []resource.HostInfo { - if len(services) == 0 || len(ingressM) == 0 { +func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, deployWorkloads []*Workload, stsWorkloads []*Workload) []resource.HostInfo { + if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { return []resource.HostInfo{} } - serviceNames := sets.NewString() - for _, service := range services { - serviceNames.Insert(service.Name) + var serviceName string + switch currentWorkload.Type { + case setting.Deployment: + for _, deployWorkload := range deployWorkloads { + if deployWorkload.Name == currentWorkload.Name { + serviceName = deployWorkload.ServiceName + break + } + } + case setting.StatefulSet: + for _, stsWorkload := range stsWorkloads { + if stsWorkload.Name == currentWorkload.Name { + serviceName = stsWorkload.ServiceName + break + } + } } + resp := make([]resource.HostInfo, 0) - for _, hostInfos := range ingressM { - for _, hostInfo := range hostInfos { - contain := false - for _, backend := range hostInfo.Backends { - if serviceNames.Has(backend.ServiceName) { - contain = true - break - } - } - if contain { + for _, hostInfo := range hostInfos { + for _, backend := range hostInfo.Backends { + if backend.ServiceName == serviceName { resp = append(resp, hostInfo) + break } } } -- Gitee From f326085639306ba9097b7a740a26f92a72bb6a0f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 15:19:18 +0800 Subject: [PATCH 0275/2647] debug coe Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index c647d953..f523a9d2 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -265,6 +265,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p if services, err := getter.ListServices(namespace, nil, kubeClient); err == nil { for _, service := range services { selector := labels.SelectorFromValidatedSet(service.Spec.Selector) + log.Infof("service.Spec.Selector:%+v", service.Spec.Selector) if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { for _, deploy := range listDeployments { deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: service.Name}) @@ -301,7 +302,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } productRespInfo.Ingress = &IngressInfo{ - HostInfo: findServiceFromIngress(hostInfos, workload, deployWorkloads, stsWorkloads), + HostInfo: findServiceFromIngress(hostInfos, workload, deployWorkloads, stsWorkloads, log), } resp = append(resp, productRespInfo) @@ -312,7 +313,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return count, resp, nil } -func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, deployWorkloads []*Workload, stsWorkloads []*Workload) []resource.HostInfo { +func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, deployWorkloads []*Workload, stsWorkloads []*Workload, log *zap.SugaredLogger) []resource.HostInfo { if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { return []resource.HostInfo{} } @@ -320,6 +321,7 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work switch currentWorkload.Type { case setting.Deployment: for _, deployWorkload := range deployWorkloads { + log.Infof("deployWorkload:%+v", deployWorkload) if deployWorkload.Name == currentWorkload.Name { serviceName = deployWorkload.ServiceName break @@ -327,12 +329,17 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work } case setting.StatefulSet: for _, stsWorkload := range stsWorkloads { + log.Infof("stsWorkload:%+v", stsWorkload) if stsWorkload.Name == currentWorkload.Name { serviceName = stsWorkload.ServiceName break } } } + log.Infof("serviceName:%s", serviceName) + if serviceName == "" { + return []resource.HostInfo{} + } resp := make([]resource.HostInfo, 0) for _, hostInfo := range hostInfos { -- Gitee From 64242d339149b61e6dfd3603f625b1a3871a7e2e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 15:19:28 +0800 Subject: [PATCH 0276/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index f523a9d2..13f4137c 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -336,6 +336,7 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work } } } + log.Infof("serviceName:%s", serviceName) if serviceName == "" { return []resource.HostInfo{} -- Gitee From 9c1a11b89a60fe24f11af98795e2c6df69498177 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 15:29:59 +0800 Subject: [PATCH 0277/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 13f4137c..4d88dd52 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -307,8 +307,8 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p resp = append(resp, productRespInfo) } - log.Infof("Finish to list workloads in namespace %s", namespace) + fmt.Println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$") return count, resp, nil } @@ -317,6 +317,7 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { return []resource.HostInfo{} } + log.Infof("currentWorkload:%+v", currentWorkload) var serviceName string switch currentWorkload.Type { case setting.Deployment: -- Gitee From 8c287731a233f89d1ac9aa85829f9ebc62c74a46 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 15:54:27 +0800 Subject: [PATCH 0278/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 4d88dd52..83c13137 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -265,7 +265,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p if services, err := getter.ListServices(namespace, nil, kubeClient); err == nil { for _, service := range services { selector := labels.SelectorFromValidatedSet(service.Spec.Selector) - log.Infof("service.Spec.Selector:%+v", service.Spec.Selector) if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { for _, deploy := range listDeployments { deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: service.Name}) @@ -308,7 +307,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p resp = append(resp, productRespInfo) } log.Infof("Finish to list workloads in namespace %s", namespace) - fmt.Println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$") return count, resp, nil } @@ -317,12 +315,10 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { return []resource.HostInfo{} } - log.Infof("currentWorkload:%+v", currentWorkload) - var serviceName string + serviceName := "" switch currentWorkload.Type { case setting.Deployment: for _, deployWorkload := range deployWorkloads { - log.Infof("deployWorkload:%+v", deployWorkload) if deployWorkload.Name == currentWorkload.Name { serviceName = deployWorkload.ServiceName break @@ -330,7 +326,6 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work } case setting.StatefulSet: for _, stsWorkload := range stsWorkloads { - log.Infof("stsWorkload:%+v", stsWorkload) if stsWorkload.Name == currentWorkload.Name { serviceName = stsWorkload.ServiceName break -- Gitee From 13536ad06ed3fde27e718f8af56caf7ae02bbf33 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 15:57:09 +0800 Subject: [PATCH 0279/2647] features Signed-off-by: mouuii --- .../systemconfig/core/handler/features.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/systemconfig/core/handler/features.go b/pkg/microservice/systemconfig/core/handler/features.go index 6a69d930..f6c35335 100644 --- a/pkg/microservice/systemconfig/core/handler/features.go +++ b/pkg/microservice/systemconfig/core/handler/features.go @@ -11,7 +11,8 @@ import ( ) var featureM map[string]string = map[string]string{ - "xx": "true", + "ModernWorkflow": "false", + "CommunityProjectRepository":"false", } var FeatureFlag string @@ -21,6 +22,15 @@ func GetFeatures(c *gin.Context) { featureName := c.Query("feature") ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + initOnce() + if v,ok := featureM[featureName];ok{ + ctx.Resp = v + return + } + ctx.Resp = "notfound" +} + +func initOnce(){ once.Do(func(){ features := config.Features() featureList := strings.Split(features,",") @@ -34,9 +44,4 @@ func GetFeatures(c *gin.Context) { } } }) - if v,ok := featureM[featureName];ok{ - ctx.Resp = v - } - ctx.Resp = "notfound" - } -- Gitee From 95fe80e0760264fa05681b024532aa3f7a232be0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 16:05:41 +0800 Subject: [PATCH 0280/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 83c13137..47893c4b 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -307,6 +307,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p resp = append(resp, productRespInfo) } log.Infof("Finish to list workloads in namespace %s", namespace) + fmt.Println("--------------------------------------------------------------") return count, resp, nil } @@ -315,10 +316,12 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { return []resource.HostInfo{} } + log.Infof("currentWorkload.workloadName:%s", currentWorkload.Name) serviceName := "" switch currentWorkload.Type { case setting.Deployment: for _, deployWorkload := range deployWorkloads { + log.Infof("workloadName:%s,serviceName:%s", deployWorkload.Name, deployWorkload.ServiceName) if deployWorkload.Name == currentWorkload.Name { serviceName = deployWorkload.ServiceName break -- Gitee From 6e1e5c9d0cbe6441073feec725b3390cca64cdf2 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 27 Oct 2021 16:13:01 +0800 Subject: [PATCH 0281/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 1 - go.sum | 1685 +++++++++++++++++ .../systemconfig/core/service/types.go | 2 +- pkg/microservice/user/config/config.go | 12 +- pkg/microservice/user/config/consts.go | 6 +- .../user/core/handler/login/local.go | 2 +- .../user/core/handler/login/third_party.go | 2 +- .../user/core/repository/models/base.go | 6 + .../user/core/repository/models/user.go | 4 +- .../user/core/repository/models/user_login.go | 8 +- pkg/microservice/user/core/service.go | 5 - .../user/core/service/login/local.go | 4 +- .../user/core/service/login/third_party.go | 2 +- .../user/core/service/user/user.go | 6 +- pkg/microservice/user/server/server.go | 2 +- pkg/setting/consts.go | 1 - 16 files changed, 1710 insertions(+), 38 deletions(-) create mode 100644 go.sum create mode 100644 pkg/microservice/user/core/repository/models/base.go diff --git a/go.mod b/go.mod index 3dc239e8..d8f77493 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,6 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 - github.com/jinzhu/gorm v1.9.16 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mittwald/go-helm-client v0.8.0 diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..b34685d1 --- /dev/null +++ b/go.sum @@ -0,0 +1,1685 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.88.0 h1:MZ2cf9Elnv1wqccq8ooKO2MqHQLc+ChCp/+QWObCpxg= +cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc= +github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= +github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= +github.com/AppsFlyer/go-sundheit v0.4.0/go.mod h1:iZ8zWMS7idcvmqewf5mEymWWgoOiG/0WD4+aeh+heX4= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= +github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RyanCarrier/dijkstra v1.1.0 h1:/NDihjfJA3CxFaZz8EdzTwdFKFZDvvB881OVLdakRcI= +github.com/RyanCarrier/dijkstra v1.1.0/go.mod h1:5agGUBNEtUAGIANmbw09fuO3a2htPEkc1jNH01qxCWA= +github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345 h1:fgSpoKViTSqRb4hjDNj10ig5wUvO0CayCzFdLf6fuRM= +github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72 h1:uGeGZl8PxSq8VZGG4QK5njJTFA4/G/x5CYORvQVXtAE= +github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72/go.mod h1:o+JdB7VetTHjLhU0N57x18B9voDBQe0paApdEAEoEfw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 h1:2Qp3BQZ7r1z+oMUnFhD6FQ86Hh1PO+59yoUNNJ5Dn0M= +github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= +github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= +github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= +github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= +github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I= +github.com/bugsnag/bugsnag-go v2.1.0+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/panicwrap v1.3.1 h1:pmuhHlhbUV4OOrGDvoiMjHSZzwRcL+I9cIzYKiW4lII= +github.com/bugsnag/panicwrap v1.3.1/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= +github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= +github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8dskcM7wQ28gONkukbQkM4qpj4RnYbpFzrI= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/coocood/freecache v1.1.0 h1:ENiHOsWdj1BrrlPwblhbn4GdAsMymK3pZORJ+bJGAjA= +github.com/coocood/freecache v1.1.0/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= +github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= +github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b h1:ovHbNjGAQsGEs67tYU6C6ex2D2shDkeZ7pPprx58f2k= +github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b/go.mod h1:g64CEwk9b4oLTREOu8mFkjTkDUvyxzWGqhnPwQlfrq8= +github.com/dexidp/dex/api/v2 v2.0.0/go.mod h1:k5arBJT1QYvpsEY3sEd0NXJp3hKWKuUUfzJ3BlcqPdM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/cli v20.10.5+incompatible h1:bjflayQbWg+xOkF2WPEAOi4Y7zWhR7ptoPhV/VqLVDE= +github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible h1:357nGVUC8gSpeSc2Axup8HfrfTLLUfWfCsCUhiQSKIg= +github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81 h1:VnJAjP/7FbY46wddaiKakrc1ogXW+3MlP7cg+S4j4Og= +github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= +github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 h1:Z8LR4yJgA3vaXpBiUWcWdQZpB9h+dzj4NdLjP9Sfdb0= +github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= +github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= +github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= +github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= +github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= +github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.5 h1:Xm0Ao53uqnk9QE/LlYV5DEU09UAgpliA85QoT9LzqPw= +github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= +github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.5.1-0.20200311113236-681ffa848bae/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= +github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= +github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho= +github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= +github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= +github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 h1:nQ1QIkLxAoFsYyjvpQH3gVIy3mHfI0WDYEWdmwwxSG0= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50/go.mod h1:Pp3sd2tx3j9qC7Ij6jGh5phZwTrI+/HUBK90f3Cn2CI= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= +github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= +github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237 h1:acuCHBjzG7MFTugvx3buC4m5rLDLaKC9J8C9jtlraRc= +github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnLAUmVG5paym8pD3C4B9BQylUDC2vXFJJpT7JrlEA= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= +github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mittwald/go-helm-client v0.8.0 h1:k8yMvoWE7C+jJgw6iodJ0U5nY4hVpLU20VIpR+FfyJk= +github.com/mittwald/go-helm-client v0.8.0/go.mod h1:+Ks5OpzB0hJw91W8o/8G+FmjUtjrIXxPuJjE87XG/G0= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nsqio/go-nsq v1.0.7 h1:O0pIZJYTf+x7cZBA0UMY8WxFG79lYTURmWzAAh48ljY= +github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito= +github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= +github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= +github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= +github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= +github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= +github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= +github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= +github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= +github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= +github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZtPsI= +github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0= +github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= +github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go v1.1.13 h1:nB3O5kBSQGjEQAcfe1aLUYuxmXdFKmYgBZhY32rQb6Q= +github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc= +github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4= +github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= +github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= +github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= +github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= +github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.7 h1:4DTF1WOM2ZZS/xMOkTFBOcb6XiHu/PKn3rVo6dbewQE= +github.com/yvasiyarov/gorelic v0.0.7/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 h1:AsFN8kXcCVkUFHyuzp1FtYbzp1nCO/H6+1uPSGEyPzM= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= +github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.5.0 h1:REddm85e1Nl0JPXGGhgZkgJdG/yOe6xvpXUcYK5WLt0= +go.mongodb.org/mongo-driver v1.5.0/go.mod h1:boiGPFqyBs5R0R5qf2ErokGRekMfwn+MqKaUyHs7wy0= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= +go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.52.0 h1:m5FLEd6dp5CU1F0tMWyqDi2XjchviIz8ntzOSz7w8As= +google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7SrU= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f h1:YORWxaStkWBnWgELOHTmDrqNlFXuVGEbhwbB5iK94bQ= +google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0 h1:Klz8I9kdtkIN6EpHHUOMLCYhTn/2WAe5a0s1hcBkdTI= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mholt/archiver.v3 v3.1.1 h1:sLb5X5fPPc9oU6YatdC+vbY46irElT3kPR0wOS7bjR4= +gopkg.in/mholt/archiver.v3 v3.1.1/go.mod h1:EwJV1tr6GQdNZ5IFFVakh0aYAjGLVjqvFVL/b8ZVdMw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= +gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= +gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.21.16 h1:YBIQLtP5PLfZQz59qfrq7xbrK7KWQ+JsXXCH/THlMqs= +gorm.io/gorm v1.21.16/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +helm.sh/helm/v3 v3.6.2 h1:7YbEhLC6AUgmcDoh5BGFUGNtR/o43pr0AIelSNjhuFU= +helm.sh/helm/v3 v3.6.2/go.mod h1:mIIus8EOqj+obtycw3sidsR4ORr2aFDmXMSI3k+oeVY= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= +k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= +k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= +k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= +k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= +k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= +k8s.io/apiserver v0.21.0 h1:1hWMfsz+cXxB77k6/y0XxWxwl6l9OF26PC9QneUVn1Q= +k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= +k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= +k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= +k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= +k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= +k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= +k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= +k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= +k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= +k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= +k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= +k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= +rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= +sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= +sigs.k8s.io/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ= +sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= +sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= +sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= +sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI= +sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= +sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/pkg/microservice/systemconfig/core/service/types.go b/pkg/microservice/systemconfig/core/service/types.go index f9f45d74..90c22df8 100644 --- a/pkg/microservice/systemconfig/core/service/types.go +++ b/pkg/microservice/systemconfig/core/service/types.go @@ -19,7 +19,6 @@ package service import ( "encoding/json" - "github.com/coreos/go-oidc/v3/oidc" "github.com/dexidp/dex/connector/gitea" "github.com/dexidp/dex/connector/github" "github.com/dexidp/dex/connector/gitlab" @@ -27,6 +26,7 @@ import ( "github.com/dexidp/dex/connector/ldap" "github.com/dexidp/dex/connector/linkedin" "github.com/dexidp/dex/connector/microsoft" + "github.com/dexidp/dex/connector/oidc" ) type ConnectorType string diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go index 160295d1..5999c817 100644 --- a/pkg/microservice/user/config/config.go +++ b/pkg/microservice/user/config/config.go @@ -17,24 +17,18 @@ limitations under the License. package config import ( - "strconv" "strings" "github.com/spf13/viper" _ "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/tool/log" ) func IssuerURL() string { return viper.GetString(setting.ENVIssuerURL) } -func Debug() string { - return viper.GetString(setting.ENVDebug) -} - func ClientID() string { return viper.GetString(setting.ENVClientID) } @@ -56,9 +50,5 @@ func MysqlUserDB() string { } func TokenExpiresAt() int { - expiresAt, err := strconv.Atoi(viper.GetString(setting.ENVTokenExpiresAt)) - if err != nil { - log.Panic(err) - } - return expiresAt + return viper.GetInt(setting.ENVTokenExpiresAt) } diff --git a/pkg/microservice/user/config/consts.go b/pkg/microservice/user/config/consts.go index e7c7dca3..11887903 100644 --- a/pkg/microservice/user/config/consts.go +++ b/pkg/microservice/user/config/consts.go @@ -16,7 +16,11 @@ limitations under the License. package config +import ( + "github.com/koderover/zadig/pkg/setting" +) + const ( - AppState = "zadig user" + AppState = setting.ProductName + "user" SystemIdentityType = "system" ) diff --git a/pkg/microservice/user/core/handler/login/local.go b/pkg/microservice/user/core/handler/login/local.go index 8f6a5338..458b6051 100644 --- a/pkg/microservice/user/core/handler/login/local.go +++ b/pkg/microservice/user/core/handler/login/local.go @@ -15,5 +15,5 @@ func LocalLogin(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = login.InternalLogin(args, ctx.Logger) + ctx.Resp, ctx.Err = login.LocalLogin(args, ctx.Logger) } diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index 30ad43b0..b84412db 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -24,7 +24,7 @@ func provider() *oidc.Provider { ctx := oidc.ClientContext(context.Background(), http.DefaultClient) provider, err := oidc.NewProvider(ctx, config.IssuerURL()) if err != nil { - log.Panic(fmt.Sprintf("init provider error:%s", err)) + log.Panicf(fmt.Sprintf("init provider error:%s", err)) } return provider } diff --git a/pkg/microservice/user/core/repository/models/base.go b/pkg/microservice/user/core/repository/models/base.go new file mode 100644 index 00000000..cf42820a --- /dev/null +++ b/pkg/microservice/user/core/repository/models/base.go @@ -0,0 +1,6 @@ +package models + +type Model struct { + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go index 98ce949f..402d08a7 100644 --- a/pkg/microservice/user/core/repository/models/user.go +++ b/pkg/microservice/user/core/repository/models/user.go @@ -1,9 +1,7 @@ package models -import "github.com/koderover/zadig/pkg/microservice/user/core" - type User struct { - core.Model + Model UID string `json:"uid"` Name string `json:"name"` IdentityType string `gorm:"default:'unknown'" json:"identity_type"` diff --git a/pkg/microservice/user/core/repository/models/user_login.go b/pkg/microservice/user/core/repository/models/user_login.go index 86d2b301..34e5bbed 100644 --- a/pkg/microservice/user/core/repository/models/user_login.go +++ b/pkg/microservice/user/core/repository/models/user_login.go @@ -1,12 +1,8 @@ package models -import ( - "github.com/koderover/zadig/pkg/microservice/user/core" -) - type UserLogin struct { - core.Model - Uid string `json:"uid"` + Model + UID string `json:"uid"` Password string `json:"password"` LastLoginTime int64 `json:"last_login_time"` } diff --git a/pkg/microservice/user/core/service.go b/pkg/microservice/user/core/service.go index 4d50f873..8a91a7f2 100644 --- a/pkg/microservice/user/core/service.go +++ b/pkg/microservice/user/core/service.go @@ -30,11 +30,6 @@ import ( var DB *gorm.DB -type Model struct { - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` -} - func Start(_ context.Context) { log.Init(&log.Config{ Level: config.LogLevel(), diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index b1577223..01d16375 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -28,7 +28,7 @@ type User struct { IdentityType string `json:"identityType"` } -func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { +func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { user, err := orm.GetUser(args.Email, config.SystemIdentityType, core.DB) if err != nil { logger.Errorf("InternalLogin get user email:%s error", args.Email) @@ -53,7 +53,7 @@ func InternalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { return nil, fmt.Errorf("check password error, error msg:%s", err.Error()) } userLogin.LastLoginTime = time.Now().Unix() - err = orm.UpdateUserLogin(userLogin.Uid, userLogin, core.DB) + err = orm.UpdateUserLogin(userLogin.UID, userLogin, core.DB) if err != nil { logger.Errorf("InternalLogin user:%s update user login password error, error msg:%s", args.Email, err.Error()) return nil, err diff --git a/pkg/microservice/user/core/service/login/third_party.go b/pkg/microservice/user/core/service/login/third_party.go index f51c021b..30db1eb0 100644 --- a/pkg/microservice/user/core/service/login/third_party.go +++ b/pkg/microservice/user/core/service/login/third_party.go @@ -60,7 +60,7 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us } } else { err = orm.CreateUserLogin(&models.UserLogin{ - Uid: user.UID, + UID: user.UID, LastLoginTime: time.Now().Unix(), }, tx) if err != nil { diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 4b280723..44cf4c5e 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -82,7 +82,7 @@ func SeachUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) } userLoginMap := make(map[string]models.UserLogin) for _, userLogin := range *userLogins { - userLoginMap[userLogin.Uid] = userLogin + userLoginMap[userLogin.UID] = userLogin } var usersInfo []UserInfo for _, user := range users { @@ -128,7 +128,7 @@ func CreateUser(args *User, logger *zap.SugaredLogger) error { } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.Password), bcrypt.DefaultCost) userLogin := &models.UserLogin{ - Uid: user.UID, + UID: user.UID, Password: string(hashedPassword), LastLoginTime: 0, } @@ -171,7 +171,7 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.NewPassword), bcrypt.DefaultCost) userLogin = &models.UserLogin{ - Uid: user.UID, + UID: user.UID, Password: string(hashedPassword), } err = orm.UpdateUserLogin(user.UID, userLogin, core.DB) diff --git a/pkg/microservice/user/server/server.go b/pkg/microservice/user/server/server.go index b892ae27..aa0f55e5 100644 --- a/pkg/microservice/user/server/server.go +++ b/pkg/microservice/user/server/server.go @@ -30,7 +30,7 @@ func Serve(ctx context.Context) error { core.Start(ctx) defer core.Stop(ctx) - log.Info("Start policy service") + log.Info("Start user system service") engine := rest.NewEngine() server := &http.Server{Addr: ":80", Handler: engine} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 96feb129..84c8cb95 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -105,7 +105,6 @@ const ( // user ENVIssuerURL = "ISSUER_URL" - ENVDebug = "DEBUG" ENVClientID = "CLIENT_ID" ENVClientSecret = "CLIENT_SECRET" ENVRedirectURI = "REDIRECT_URI" -- Gitee From c5e6a3c2d95ed1c5b61b82419f36b38fd0344b5e Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 27 Oct 2021 16:19:38 +0800 Subject: [PATCH 0282/2647] add basic api logic Signed-off-by: allenshen --- .../aslan/core/templatestore/handler/chart.go | 26 +++++ .../core/templatestore/handler/router.go | 2 + .../templatestore/repository/models/chart.go | 32 ++++-- .../aslan/core/templatestore/service/chart.go | 108 +++++++++++++++++- .../aslan/core/templatestore/service/types.go | 13 ++- 5 files changed, 164 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index fd17107d..a1ac9c33 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -18,9 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -30,6 +32,10 @@ type addChartArgs struct { Name string `json:"name"` } +type updateChartTemplateVariablesReq struct { + Variables []*templateservice.Variable `json:"variables"` +} + func GetChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -45,6 +51,13 @@ func ListFiles(c *gin.Context) { ctx.Resp, ctx.Err = service.GetFileContent(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) } +func GetChartTemplateReferences(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetFileContent(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) +} + func ListChartTemplates(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -78,6 +91,19 @@ func UpdateChartTemplate(c *gin.Context) { ctx.Err = service.UpdateChartTemplate(c.Param("name"), args.DownloadFromSourceArgs, ctx.Logger) } +func UpdateChartTemplateVariables(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := make([]*templateservice.Variable, 0) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = errors.ErrInvalidParam.AddErr(err) + return + } + + ctx.Err = service.UpdateChartTemplateVariables(c.Param("name"), args, ctx.Logger) +} + func RemoveChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index f2d37b01..48da92ff 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -36,8 +36,10 @@ func (*Router) Inject(router *gin.RouterGroup) { install.GET("", ListChartTemplates) install.GET("/:name", GetChartTemplate) install.GET("/:name/files", ListFiles) + install.GET("/:name/references", GetChartTemplateReferences) install.POST("", AddChartTemplate) install.PUT("/:name", UpdateChartTemplate) + install.PUT("/:name/variables", UpdateChartTemplateVariables) install.DELETE("/:name", RemoveChartTemplate) } diff --git a/pkg/microservice/aslan/core/templatestore/repository/models/chart.go b/pkg/microservice/aslan/core/templatestore/repository/models/chart.go index daf105ba..48c02f04 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/models/chart.go +++ b/pkg/microservice/aslan/core/templatestore/repository/models/chart.go @@ -16,16 +16,30 @@ limitations under the License. package models +import commonmodes "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + type Chart struct { - Name string `bson:"name" json:"name"` - Source string `bson:"source" json:"source"` - Owner string `bson:"owner" json:"owner"` - Repo string `bson:"repo" json:"repo"` - Path string `bson:"path" json:"path"` - Branch string `bson:"branch" json:"branch"` - CodeHostID int `bson:"codehost_id" json:"codeHostID"` - Revision int64 `bson:"revision" json:"revision"` - Sha1 string `bson:"sha1" json:"sha1"` + Name string `bson:"name" json:"name"` + Source string `bson:"source" json:"source"` + Owner string `bson:"owner" json:"owner"` + Repo string `bson:"repo" json:"repo"` + Path string `bson:"path" json:"path"` + Branch string `bson:"branch" json:"branch"` + CodeHostID int `bson:"codehost_id" json:"codeHostID"` + Revision int64 `bson:"revision" json:"revision"` + Variables []*commonmodes.Variable `bson:"variables" json:"variables"` + Sha1 string `bson:"sha1" json:"sha1"` +} + +func (chart *Chart) GetVariableMap() map[string]*commonmodes.Variable { + if len(chart.Variables) == 0 { + return nil + } + ret := make(map[string]*commonmodes.Variable) + for _, v := range chart.Variables { + ret[v.Key] = v + } + return ret } func (Chart) TableName() string { diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 5c34db73..c4605315 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -20,18 +20,32 @@ import ( "fmt" "os" "path/filepath" + "regexp" "github.com/27149chen/afero" + "github.com/pkg/errors" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" configbase "github.com/koderover/zadig/pkg/config" + commonmodes "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" fsutil "github.com/koderover/zadig/pkg/util/fs" ) +var ( + variableExtractRegexp = regexp.MustCompile("\\$(.*?)\\$") +) + +type ChartTemplateListResp struct { + SystemVariables []*Variable `json:"systemVariables"` + ChartTemplates []*Chart `json:"chartTemplates"` +} + func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { chart, err := mongodb.NewChartColl().Get(name) if err != nil { @@ -53,6 +67,14 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { return nil, err } + variables := make([]*Variable, 0) + for _, v := range chart.Variables { + variables = append(variables, &Variable{ + Key: v.Key, + Value: v.Value, + }) + } + return &Chart{ Name: name, CodehostID: chart.CodeHostID, @@ -61,10 +83,11 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { Path: chart.Path, Branch: chart.Branch, Files: fis, + Variables: variables, }, nil } -func ListChartTemplates(logger *zap.SugaredLogger) ([]*Chart, error) { +func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, error) { cs, err := mongodb.NewChartColl().List() if err != nil { logger.Errorf("Failed to list chart templates, err: %s", err) @@ -83,7 +106,12 @@ func ListChartTemplates(logger *zap.SugaredLogger) ([]*Chart, error) { }) } - return res, nil + ret := &ChartTemplateListResp{ + SystemVariables: GetSystemDefaultVariables(), + ChartTemplates: res, + } + + return ret, nil } func GetFileContent(name, filePath, fileName string, logger *zap.SugaredLogger) ([]byte, error) { @@ -110,6 +138,22 @@ func GetFileContent(name, filePath, fileName string, logger *zap.SugaredLogger) return fileContent, nil } +func parseTemplateVariables(name string, logger *zap.SugaredLogger) ([]string, error) { + valueYamlContent, err := GetFileContent(name, "", setting.ValuesYaml, logger) + if err != nil { + return nil, errors.Wrap(err, "failed to read values.yaml") + } + strSet := sets.NewString() + allMatches := variableExtractRegexp.FindAllStringSubmatch(string(valueYamlContent), -1) + for _, match := range allMatches { + if len(match) < 1 { + continue + } + strSet.Insert(match[1:]...) + } + return strSet.List(), nil +} + func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap.SugaredLogger) error { if mongodb.NewChartColl().Exist(name) { return fmt.Errorf("a chart template with name %s is already existing", name) @@ -121,6 +165,18 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. return err } + variablesNames, err := parseTemplateVariables(name, logger) + if err != nil { + return errors.Wrapf(err, "faild to prase variables") + } + + variables := make([]*commonmodes.Variable, 0, len(variablesNames)) + for _, v := range variablesNames { + variables = append(variables, &commonmodes.Variable{ + Key: v, + }) + } + return mongodb.NewChartColl().Create(&models.Chart{ Name: name, Owner: args.Owner, @@ -129,6 +185,7 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. Branch: args.Branch, CodeHostID: args.CodehostID, Sha1: sha1, + Variables: variables, }) } @@ -150,6 +207,24 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z return nil } + variablesNames, err := parseTemplateVariables(name, logger) + if err != nil { + return errors.Wrapf(err, "faild to prase variables") + } + + variables := make([]*commonmodes.Variable, 0, len(variablesNames)) + curVariableMap := chart.GetVariableMap() + + for _, vName := range variablesNames { + variable := &commonmodes.Variable{ + Key: vName, + } + if v, ok := curVariableMap[vName]; ok { + variable.Value = v.Value + } + variables = append(variables, variable) + } + return mongodb.NewChartColl().Update(&models.Chart{ Name: name, Owner: args.Owner, @@ -158,6 +233,35 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z Branch: args.Branch, CodeHostID: args.CodehostID, Sha1: sha1, + Variables: variables, + }) +} + +func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.SugaredLogger) error { + chart, err := mongodb.NewChartColl().Get(name) + if err != nil { + logger.Errorf("Failed to get chart template %s, err: %s", name, err) + return err + } + + //TODO need validate keys + variables := make([]*commonmodes.Variable, 0) + for _, variable := range variables { + variables = append(variables, &commonmodes.Variable{ + Key: variable.Key, + Value: variable.Value, + }) + } + + return mongodb.NewChartColl().Update(&models.Chart{ + Name: name, + Owner: chart.Owner, + Repo: chart.Repo, + Path: chart.Path, + Branch: chart.Branch, + CodeHostID: chart.CodeHostID, + Sha1: chart.Sha1, + Variables: variables, }) } diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index 92beafe2..94845e7e 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -19,12 +19,13 @@ package service import "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" type Chart struct { - Name string `json:"name"` - CodehostID int `json:"codehostID"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Branch string `json:"branch"` - Path string `json:"path"` + Name string `json:"name"` + CodehostID int `json:"codehostID"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Branch string `json:"branch"` + Path string `json:"path"` + Variables []*Variable `json:"variable"` Files []*fs.FileInfo `json:"files,omitempty"` } -- Gitee From 6da0d661b94ce02341890783db0c0fdd87c006d2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 16:24:00 +0800 Subject: [PATCH 0283/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 47893c4b..fbee39f8 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -264,17 +264,18 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p stsWorkloads := make([]*Workload, 0) if services, err := getter.ListServices(namespace, nil, kubeClient); err == nil { for _, service := range services { + serviceName := service.Name selector := labels.SelectorFromValidatedSet(service.Spec.Selector) if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { for _, deploy := range listDeployments { - deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: service.Name}) + deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: serviceName}) } continue } if listStatefulsets, _ := getter.ListStatefulSets(namespace, selector, kubeClient); len(listStatefulsets) > 0 { for _, sts := range listStatefulsets { - stsWorkloads = append(stsWorkloads, &Workload{Name: sts.Name, ServiceName: service.Name}) + stsWorkloads = append(stsWorkloads, &Workload{Name: sts.Name, ServiceName: serviceName}) } } } -- Gitee From 6749ce80da80e5001a6a5cb4d9e7aaf8b71c3de2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 16:29:57 +0800 Subject: [PATCH 0284/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index fbee39f8..ec41b092 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -268,6 +268,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p selector := labels.SelectorFromValidatedSet(service.Spec.Selector) if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { for _, deploy := range listDeployments { + log.Infof("k8s serviceName:%s", serviceName) deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: serviceName}) } continue -- Gitee From a210226138c1e5cb4f872552c0244c5f4137879a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 16:46:16 +0800 Subject: [PATCH 0285/2647] remove debug code Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index ec41b092..539354bf 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -264,19 +264,20 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p stsWorkloads := make([]*Workload, 0) if services, err := getter.ListServices(namespace, nil, kubeClient); err == nil { for _, service := range services { - serviceName := service.Name + if len(service.Spec.Selector) == 0 { + continue + } selector := labels.SelectorFromValidatedSet(service.Spec.Selector) if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { for _, deploy := range listDeployments { - log.Infof("k8s serviceName:%s", serviceName) - deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: serviceName}) + deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: service.Name}) } continue } if listStatefulsets, _ := getter.ListStatefulSets(namespace, selector, kubeClient); len(listStatefulsets) > 0 { for _, sts := range listStatefulsets { - stsWorkloads = append(stsWorkloads, &Workload{Name: sts.Name, ServiceName: serviceName}) + stsWorkloads = append(stsWorkloads, &Workload{Name: sts.Name, ServiceName: service.Name}) } } } @@ -309,7 +310,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p resp = append(resp, productRespInfo) } log.Infof("Finish to list workloads in namespace %s", namespace) - fmt.Println("--------------------------------------------------------------") return count, resp, nil } @@ -318,12 +318,10 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { return []resource.HostInfo{} } - log.Infof("currentWorkload.workloadName:%s", currentWorkload.Name) serviceName := "" switch currentWorkload.Type { case setting.Deployment: for _, deployWorkload := range deployWorkloads { - log.Infof("workloadName:%s,serviceName:%s", deployWorkload.Name, deployWorkload.ServiceName) if deployWorkload.Name == currentWorkload.Name { serviceName = deployWorkload.ServiceName break @@ -338,7 +336,6 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work } } - log.Infof("serviceName:%s", serviceName) if serviceName == "" { return []resource.HostInfo{} } -- Gitee From 0eb536b64abac4ad62e4d00a973692d00f77662c Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 27 Oct 2021 16:54:43 +0800 Subject: [PATCH 0286/2647] improve feature gates Signed-off-by: lou --- cmd/systemconfig/main.go | 3 +- .../systemconfig/config/config.go | 4 +- .../systemconfig/config/consts.go | 5 +- .../systemconfig/core/handler/features.go | 45 ++++----------- .../systemconfig/core/handler/router.go | 2 +- pkg/microservice/systemconfig/core/service.go | 3 + .../core/service/featuregates/feature.go | 55 +++++++++++++++++++ 7 files changed, 76 insertions(+), 41 deletions(-) create mode 100644 pkg/microservice/systemconfig/core/service/featuregates/feature.go diff --git a/cmd/systemconfig/main.go b/cmd/systemconfig/main.go index 760713e9..3df10041 100644 --- a/cmd/systemconfig/main.go +++ b/cmd/systemconfig/main.go @@ -32,11 +32,12 @@ import ( func main() { - flag.String(config.FeatureFlag, "", "help message for flagname") + flag.String(config.FeatureFlag, "", "turn a set of features on or off") pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() viper.BindPFlags(pflag.CommandLine) + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) go func() { <-ctx.Done() diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index f8f90f55..1dbf7ef9 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -10,6 +10,6 @@ func MysqlDexDB() string { return viper.GetString(ENVMysqlDexDB) } -func Features()string { +func Features() string { return viper.GetString(FeatureFlag) -} \ No newline at end of file +} diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go index 89131328..27f698e5 100644 --- a/pkg/microservice/systemconfig/config/consts.go +++ b/pkg/microservice/systemconfig/config/consts.go @@ -2,8 +2,5 @@ package config const ( ENVMysqlDexDB = "MYSQL_DEX_DB" -) - -const ( - FeatureFlag = "features" + FeatureFlag = "feature-gates" ) diff --git a/pkg/microservice/systemconfig/core/handler/features.go b/pkg/microservice/systemconfig/core/handler/features.go index f6c35335..18952e06 100644 --- a/pkg/microservice/systemconfig/core/handler/features.go +++ b/pkg/microservice/systemconfig/core/handler/features.go @@ -1,47 +1,26 @@ package handler import ( - "strings" - "sync" - "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -var featureM map[string]string = map[string]string{ - "ModernWorkflow": "false", - "CommunityProjectRepository":"false", +type feature struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` } -var FeatureFlag string -var once sync.Once - -func GetFeatures(c *gin.Context) { - featureName := c.Query("feature") +func GetFeature(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - initOnce() - if v,ok := featureM[featureName];ok{ - ctx.Resp = v - return - } - ctx.Resp = "notfound" -} -func initOnce(){ - once.Do(func(){ - features := config.Features() - featureList := strings.Split(features,",") - for _,v := range featureList{ - feature := strings.Split(v,"=") - if len(feature) != 2 { - continue - } - if _ , ok := featureM[feature[0]];ok { - featureM[feature[0]] = feature[1] - } - } - }) + name := c.Param("name") + enabled := featuregates.Features.FeatureEnabled(featuregates.Feature(name)) + + ctx.Resp = &feature{ + Name: c.Param("name"), + Enabled: enabled, + } } diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index e681b846..282f4cb1 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -33,6 +33,6 @@ func (*Router) Inject(router *gin.RouterGroup) { } features := router.Group("features") { - features.GET("", GetFeatures) + features.GET("/:name", GetFeature) } } diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index 4030bd2f..e77858f8 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -21,6 +21,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" "github.com/koderover/zadig/pkg/tool/log" @@ -35,6 +36,8 @@ func Start(_ context.Context) { }) initDatabase() + + featuregates.Features.MergeFeatureGates(featuregates.ToFeatureGates(config.Features())) } func initDatabase() { diff --git a/pkg/microservice/systemconfig/core/service/featuregates/feature.go b/pkg/microservice/systemconfig/core/service/featuregates/feature.go new file mode 100644 index 00000000..13b5c7d9 --- /dev/null +++ b/pkg/microservice/systemconfig/core/service/featuregates/feature.go @@ -0,0 +1,55 @@ +package featuregates + +import "strings" + +type Feature string + +const ( + ModernWorkflow Feature = "ModernWorkflow" + CommunityProjectRepository Feature = "CommunityProjectRepository" +) + +var Features FeatureGates = map[Feature]bool{ + ModernWorkflow: false, + CommunityProjectRepository: false, +} + +type FeatureGates map[Feature]bool + +func (fg FeatureGates) EnabledFeatures() []Feature { + var res []Feature + + for k, v := range fg { + if v { + res = append(res, k) + } + } + + return res +} + +func (fg FeatureGates) FeatureEnabled(f Feature) bool { + return fg[f] +} + +func (fg FeatureGates) MergeFeatureGates(fs FeatureGates) { + for k, v := range fs { + fg[k] = v + } +} + +func ToFeatureGates(s string) FeatureGates { + res := make(FeatureGates) + + fs := strings.Split(s, ",") + for _, f := range fs { + kv := strings.Split(f, "=") + if len(kv) != 2 { + continue + } + + res[Feature(kv[0])] = kv[1] == "true" + } + + return res +} -- Gitee From 3f72d5519705312747a2f0e14b83ae98362e6988 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 27 Oct 2021 17:37:20 +0800 Subject: [PATCH 0287/2647] add api logic Signed-off-by: allenshen --- .../core/common/repository/models/service.go | 1 + .../aslan/core/service/service/helm.go | 11 ++++++++++- .../aslan/core/service/service/types.go | 5 +++-- .../aslan/core/templatestore/handler/chart.go | 9 ++++++++- .../core/templatestore/handler/router.go | 1 + .../aslan/core/templatestore/service/chart.go | 19 ++++++++++++++++++- .../aslan/core/templatestore/service/types.go | 2 +- 7 files changed, 42 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 8e2ae751..68bbdb5f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -82,6 +82,7 @@ type CreateFromChartTemplate struct { YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` TemplateName string `bson:"template_name" json:"template_name"` ServiceName string `bson:"service_name" json:"service_name"` + Variables []*Variable `bson:"variables" json:"variables"` } type GUIConfig struct { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 478e20b2..85654d26 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -110,6 +110,7 @@ type helmServiceCreationArgs struct { HelmTemplateName string ValuePaths []string ValuesYaml string + Variables []*Variable } type ChartTemplateData struct { @@ -312,6 +313,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe Source: setting.SourceFromChartTemplate, HelmTemplateName: templateArgs.TemplateName, ValuesYaml: templateArgs.ValuesYAML, + Variables: templateArgs.Variables, }, logger, ) @@ -672,11 +674,18 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { yamlData := &template.CustomYaml{ YamlContent: args.ValuesYaml, } - + variables := make([]*models.Variable, 0, len(args.Variables)) + for _, variable := range args.Variables { + variables = append(variables, &models.Variable{ + Key: variable.Key, + Value: variable.Value, + }) + } return &models.CreateFromChartTemplate{ YamlData: yamlData, TemplateName: args.HelmTemplateName, ServiceName: args.ServiceName, + Variables: variables, } } return nil diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index a09a4477..430c4783 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -79,8 +79,9 @@ type CreateFromPublicRepo struct { } type CreateFromChartTemplate struct { - TemplateName string `json:"templateName"` - ValuesYAML string `json:"valuesYAML"` + TemplateName string `json:"templateName"` + ValuesYAML string `json:"valuesYAML"` + Variables []*Variable `json:"variables"` } func PublicRepoToPrivateRepoArgs(args *CreateFromPublicRepo) (*CreateFromRepo, error) { diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index a1ac9c33..84243479 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -43,6 +43,13 @@ func GetChartTemplate(c *gin.Context) { ctx.Resp, ctx.Err = service.GetChartTemplate(c.Param("name"), ctx.Logger) } +func GetTemplateVariables(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetChartTemplateVariables(c.Param("name"), ctx.Logger) +} + func ListFiles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -96,7 +103,7 @@ func UpdateChartTemplateVariables(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := make([]*templateservice.Variable, 0) - if err := c.ShouldBindJSON(args); err != nil { + if err := c.ShouldBindJSON(&args); err != nil { ctx.Err = errors.ErrInvalidParam.AddErr(err) return } diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 48da92ff..46347227 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -36,6 +36,7 @@ func (*Router) Inject(router *gin.RouterGroup) { install.GET("", ListChartTemplates) install.GET("/:name", GetChartTemplate) install.GET("/:name/files", ListFiles) + install.GET("/:name/variables", GetTemplateVariables) install.GET("/:name/references", GetChartTemplateReferences) install.POST("", AddChartTemplate) install.PUT("/:name", UpdateChartTemplate) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index c4605315..f84d369f 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -87,6 +87,23 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { }, nil } +func GetChartTemplateVariables(name string, logger *zap.SugaredLogger) ([]*Variable, error) { + chart, err := mongodb.NewChartColl().Get(name) + if err != nil { + logger.Errorf("Failed to get chart template %s, err: %s", name, err) + return nil, err + } + + variables := make([]*Variable, 0) + for _, v := range chart.Variables { + variables = append(variables, &Variable{ + Key: v.Key, + Value: v.Value, + }) + } + return variables, nil +} + func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, error) { cs, err := mongodb.NewChartColl().List() if err != nil { @@ -246,7 +263,7 @@ func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.Sug //TODO need validate keys variables := make([]*commonmodes.Variable, 0) - for _, variable := range variables { + for _, variable := range args { variables = append(variables, &commonmodes.Variable{ Key: variable.Key, Value: variable.Value, diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index 94845e7e..613fb6b1 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -25,7 +25,7 @@ type Chart struct { Repo string `json:"repo"` Branch string `json:"branch"` Path string `json:"path"` - Variables []*Variable `json:"variable"` + Variables []*Variable `json:"variables,omitempty"` Files []*fs.FileInfo `json:"files,omitempty"` } -- Gitee From 0aadb1178ec979693bfeb45a57a5dc25ff2985ba Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 18:42:26 +0800 Subject: [PATCH 0288/2647] features Signed-off-by: mouuii --- pkg/config/config.go | 9 +++++++ .../core/common/service/template_product.go | 12 ++++----- .../aslan/core/project/service/product.go | 9 +++---- .../cron/core/service/scheduler/scheduler.go | 18 +++---------- .../core/service/taskplugin/build.go | 10 +++----- pkg/setting/consts.go | 4 +++ pkg/setting/types.go | 5 ++++ pkg/shared/config/client.go | 22 ++++++++++++++++ pkg/shared/config/features.go | 25 +++++++++++++++++++ 9 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 pkg/shared/config/client.go create mode 100644 pkg/shared/config/features.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 477198b4..35ee51b9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -150,11 +150,20 @@ func PoetryServiceInfo() *setting.ServiceInfo { return GetServiceByCode(setting.Poetry) } +func ConfigServiceInfo() *setting.ServiceInfo { + return GetServiceByCode(setting.Poetry) +} + func PoetryServiceAddress() string { s := PoetryServiceInfo() return GetServiceAddress(s.Name, s.Port) } +func ConfigServiceAddress() string { + s := ConfigServiceInfo() + return GetServiceAddress(s.Name, s.Port) +} + func WarpDriveServiceInfo() *setting.ServiceInfo { return GetServiceByCode(setting.WarpDrive) } diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index bb4b4671..e85dc1ff 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -23,6 +23,7 @@ import ( "go.uber.org/zap" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -31,6 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" "github.com/koderover/zadig/pkg/microservice/aslan/internal/cache" "github.com/koderover/zadig/pkg/setting" + configCli "github.com/koderover/zadig/pkg/shared/config" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -52,12 +54,8 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P } totalFreeStyles := make([]*collie.CiPipelineResource, 0) - features, err := GetFeatures(log) - if err != nil { - log.Errorf("GetProductTemplate GetFeatures err : %v", err) - } - - if strings.Contains(features, string(config.FreestyleType)) { + cl := configCli.New(configbase.ConfigServiceAddress()) + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 collieAPIAddress := config.CollieAPIAddress() cl := collie.New(collieAPIAddress) @@ -116,7 +114,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Pipeline.List err : %v", err) } - if strings.Contains(features, string(config.FreestyleType)) { + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ collieAPIAddress := config.CollieAPIAddress() cl := collie.New(collieAPIAddress) totalFreeStyles, err = cl.ListCIPipelines(productName, log) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index ee01dfe0..c66de01e 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -40,6 +41,7 @@ import ( workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" + configCli "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) @@ -340,11 +342,8 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } //删除自由编排工作流 - features, err := commonservice.GetFeatures(log) - if err != nil { - log.Errorf("DeleteProductTemplate productName %s getFeatures err: %v", productName, err) - } - if strings.Contains(features, string(config.FreestyleType)) { + cl := configCli.New(configbase.ConfigServiceAddress()) + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ collieClient := collie.New(config.CollieAPIAddress()) if err = collieClient.DeleteCIPipelines(productName, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 5f45ae44..d50874b5 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -20,7 +20,6 @@ import ( "fmt" stdlog "log" "os" - "strings" "time" "github.com/jasonlvhit/gocron" @@ -33,7 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" + configCli "github.com/koderover/zadig/pkg/shared/config" "github.com/koderover/zadig/pkg/tool/log" ) @@ -74,8 +73,6 @@ const ( //InitHealthCheckScheduler InitHealthCheckScheduler = "InitHealthCheckScheduler" - // FreestyleType 自由编排工作流 - freestyleType = "freestyle" ) // NewCronClient ... @@ -142,8 +139,8 @@ func (c *CronClient) Init() { c.InitTestScheduler() // 自由编排工作流定时任务触发 - features, _ := getFeatures() - if strings.Contains(features, freestyleType) { + cl := configCli.New(configbase.ConfigServiceAddress()) + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ c.InitColliePipelineScheduler() } @@ -159,15 +156,6 @@ func (c *CronClient) Init() { c.InitHealthCheckScheduler() } -func getFeatures() (string, error) { - cl := poetry.New(configbase.PoetryServiceAddress()) - fs, err := cl.ListFeatures() - if err != nil { - return "", err - } - - return strings.Join(fs, ","), nil -} // InitCleanJobScheduler ... func (c *CronClient) InitCleanJobScheduler() { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 1867fd45..6b972350 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -29,11 +29,9 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -162,10 +160,10 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe Installs: p.Task.InstallCtx, } - poetryClient := poetry.New(configbase.PoetryServiceAddress()) - if fs, err := poetryClient.ListFeatures(); err == nil { - pipelineTask.Features = fs - } + //poetryClient := poetry.New(configbase.PoetryServiceAddress()) + //if fs, err := poetryClient.ListFeatures(); err == nil { + // pipelineTask.Features = fs + //} if p.Task.BuildStatus == nil { p.Task.BuildStatus = &task.BuildStatus{} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 81932569..61af7f76 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -500,3 +500,7 @@ const ( ReadOnly RoleType = "ReadOnly" ContributorRoleBindingFmt string = "contributor-project:%s-user:%s" ) + + +// ModernWorkflowType 自由编排工作流 +const ModernWorkflowType = "ModernWorkflow" \ No newline at end of file diff --git a/pkg/setting/types.go b/pkg/setting/types.go index dd1f1315..a8293f08 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -48,6 +48,7 @@ const ( Minio // 11 OPA // 12 Policy // 13 + Config // 14 ) type ServiceInfo struct { @@ -108,4 +109,8 @@ var Services = map[int]*ServiceInfo{ Name: "policy", Port: 80, }, + Config:{ + Name:"config", + Port:80, + }, } diff --git a/pkg/shared/config/client.go b/pkg/shared/config/client.go new file mode 100644 index 00000000..07eeb4b2 --- /dev/null +++ b/pkg/shared/config/client.go @@ -0,0 +1,22 @@ +package config + +import ( + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New(host string) *Client { + c := httpclient.New( + httpclient.SetHostURL(host), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/config/features.go b/pkg/shared/config/features.go new file mode 100644 index 00000000..82ef1f21 --- /dev/null +++ b/pkg/shared/config/features.go @@ -0,0 +1,25 @@ +package config + + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type feature struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` +} + +func (c *Client) CheckFeature(featureName string) (bool, error) { + url := fmt.Sprintf("/api/v1/features/%s",featureName) + + fs := &feature{} + _, err := c.Get(url, httpclient.SetResult(fs)) + if err != nil { + return false, err + } + + return fs.Enabled, nil +} \ No newline at end of file -- Gitee From 49634684e453abb04d540bab8bbfd408e71dce60 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 19:03:15 +0800 Subject: [PATCH 0289/2647] features Signed-off-by: mouuii --- .../aslan/core/common/service/template_product.go | 4 ++-- pkg/microservice/aslan/core/project/service/product.go | 2 +- pkg/microservice/cron/core/service/scheduler/scheduler.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index e85dc1ff..b3263bdb 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -55,7 +55,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P totalFreeStyles := make([]*collie.CiPipelineResource, 0) cl := configCli.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 collieAPIAddress := config.CollieAPIAddress() cl := collie.New(collieAPIAddress) @@ -114,7 +114,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Pipeline.List err : %v", err) } - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ collieAPIAddress := config.CollieAPIAddress() cl := collie.New(collieAPIAddress) totalFreeStyles, err = cl.ListCIPipelines(productName, log) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index c66de01e..86201f7d 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -343,7 +343,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug //删除自由编排工作流 cl := configCli.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ collieClient := collie.New(config.CollieAPIAddress()) if err = collieClient.DeleteCIPipelines(productName, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index d50874b5..d466e549 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -140,7 +140,7 @@ func (c *CronClient) Init() { // 自由编排工作流定时任务触发 cl := configCli.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err !=nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ c.InitColliePipelineScheduler() } -- Gitee From d739e2b8ccce1133215f06298ad5c52b5d5ea38e Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 19:07:27 +0800 Subject: [PATCH 0290/2647] features Signed-off-by: mouuii --- .../aslan/core/common/service/template_product.go | 4 ++-- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- pkg/microservice/cron/core/service/scheduler/scheduler.go | 4 ++-- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 5 ----- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index b3263bdb..d77e64b3 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -32,7 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" "github.com/koderover/zadig/pkg/microservice/aslan/internal/cache" "github.com/koderover/zadig/pkg/setting" - configCli "github.com/koderover/zadig/pkg/shared/config" + configcli "github.com/koderover/zadig/pkg/shared/config" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -54,7 +54,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P } totalFreeStyles := make([]*collie.CiPipelineResource, 0) - cl := configCli.New(configbase.ConfigServiceAddress()) + cl := configcli.New(configbase.ConfigServiceAddress()) if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 collieAPIAddress := config.CollieAPIAddress() diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 86201f7d..c98e96a8 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -41,7 +41,7 @@ import ( workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" - configCli "github.com/koderover/zadig/pkg/shared/config" + configclient "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) @@ -342,7 +342,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } //删除自由编排工作流 - cl := configCli.New(configbase.ConfigServiceAddress()) + cl := configclient.New(configbase.ConfigServiceAddress()) if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ collieClient := collie.New(config.CollieAPIAddress()) if err = collieClient.DeleteCIPipelines(productName, log); err != nil { diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index d466e549..5bcb91f3 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -32,7 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" - configCli "github.com/koderover/zadig/pkg/shared/config" + configclient "github.com/koderover/zadig/pkg/shared/config" "github.com/koderover/zadig/pkg/tool/log" ) @@ -139,7 +139,7 @@ func (c *CronClient) Init() { c.InitTestScheduler() // 自由编排工作流定时任务触发 - cl := configCli.New(configbase.ConfigServiceAddress()) + cl := configclient.New(configbase.ConfigServiceAddress()) if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ c.InitColliePipelineScheduler() } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 6b972350..630004f8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -160,11 +160,6 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe Installs: p.Task.InstallCtx, } - //poetryClient := poetry.New(configbase.PoetryServiceAddress()) - //if fs, err := poetryClient.ListFeatures(); err == nil { - // pipelineTask.Features = fs - //} - if p.Task.BuildStatus == nil { p.Task.BuildStatus = &task.BuildStatus{} } -- Gitee From f51ed2cd507af791bdac97aaac73e15060b90ec5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 19:08:44 +0800 Subject: [PATCH 0291/2647] features Signed-off-by: mouuii --- .../aslan/core/common/service/template_product.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index d77e64b3..1f39e415 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -32,7 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" "github.com/koderover/zadig/pkg/microservice/aslan/internal/cache" "github.com/koderover/zadig/pkg/setting" - configcli "github.com/koderover/zadig/pkg/shared/config" + configclient "github.com/koderover/zadig/pkg/shared/config" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -54,7 +54,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P } totalFreeStyles := make([]*collie.CiPipelineResource, 0) - cl := configcli.New(configbase.ConfigServiceAddress()) + cl := configclient.New(configbase.ConfigServiceAddress()) if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 collieAPIAddress := config.CollieAPIAddress() -- Gitee From 7d65d6e793a540f4105feba9bd9a4cba9aad475e Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 27 Oct 2021 20:13:17 +0800 Subject: [PATCH 0292/2647] fix read file content bug Signed-off-by: allenshen --- .../aslan/core/templatestore/handler/chart.go | 5 ++--- .../aslan/core/templatestore/service/chart.go | 18 +++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 84243479..9d4b5b3d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -55,14 +55,13 @@ func ListFiles(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() // TODO: support to return multiple files in a bulk - ctx.Resp, ctx.Err = service.GetFileContent(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetFileContentForTemplate(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) } func GetChartTemplateReferences(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.GetFileContent(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) + return } func ListChartTemplates(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index f84d369f..edf83de8 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -131,20 +131,24 @@ func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, erro return ret, nil } -func GetFileContent(name, filePath, fileName string, logger *zap.SugaredLogger) ([]byte, error) { +func GetFileContentForTemplate(name, filePath, fileName string, logger *zap.SugaredLogger) ([]byte, error) { chart, err := mongodb.NewChartColl().Get(name) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", name, err) return nil, err } + return getFileContent(name, chart.Path, filePath, fileName, logger) +} + +func getFileContent(name, path, filePath, fileName string, logger *zap.SugaredLogger) ([]byte, error) { localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) - if err = fs.PreloadFiles(name, localBase, s3Base, logger); err != nil { + if err := fs.PreloadFiles(name, localBase, s3Base, logger); err != nil { return nil, err } - base := filepath.Base(chart.Path) + base := filepath.Base(path) file := filepath.Join(localBase, base, filePath, fileName) fileContent, err := os.ReadFile(file) if err != nil { @@ -155,8 +159,8 @@ func GetFileContent(name, filePath, fileName string, logger *zap.SugaredLogger) return fileContent, nil } -func parseTemplateVariables(name string, logger *zap.SugaredLogger) ([]string, error) { - valueYamlContent, err := GetFileContent(name, "", setting.ValuesYaml, logger) +func parseTemplateVariables(name, path string, logger *zap.SugaredLogger) ([]string, error) { + valueYamlContent, err := getFileContent(name, path, "", setting.ValuesYaml, logger) if err != nil { return nil, errors.Wrap(err, "failed to read values.yaml") } @@ -182,7 +186,7 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. return err } - variablesNames, err := parseTemplateVariables(name, logger) + variablesNames, err := parseTemplateVariables(name, args.Path, logger) if err != nil { return errors.Wrapf(err, "faild to prase variables") } @@ -224,7 +228,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z return nil } - variablesNames, err := parseTemplateVariables(name, logger) + variablesNames, err := parseTemplateVariables(name, chart.Path, logger) if err != nil { return errors.Wrapf(err, "faild to prase variables") } -- Gitee From 2155a3e2159e90ddd77e39e10d57eae4b26a9795 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 20:28:39 +0800 Subject: [PATCH 0293/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 49 +++++-------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 539354bf..7244cb8d 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -260,27 +260,9 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } // get all services - deployWorkloads := make([]*Workload, 0) - stsWorkloads := make([]*Workload, 0) - if services, err := getter.ListServices(namespace, nil, kubeClient); err == nil { - for _, service := range services { - if len(service.Spec.Selector) == 0 { - continue - } - selector := labels.SelectorFromValidatedSet(service.Spec.Selector) - if listDeployments, _ := getter.ListDeployments(namespace, selector, kubeClient); len(listDeployments) > 0 { - for _, deploy := range listDeployments { - deployWorkloads = append(deployWorkloads, &Workload{Name: deploy.Name, ServiceName: service.Name}) - } - continue - } - - if listStatefulsets, _ := getter.ListStatefulSets(namespace, selector, kubeClient); len(listStatefulsets) > 0 { - for _, sts := range listStatefulsets { - stsWorkloads = append(stsWorkloads, &Workload{Name: sts.Name, ServiceName: service.Name}) - } - } - } + allServices, err := getter.ListServices(namespace, nil, kubeClient) + if err != nil { + log.Errorf("[%s][%s] list service error: %s", envName, namespace, err) } for _, workload := range workLoads { @@ -304,7 +286,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } productRespInfo.Ingress = &IngressInfo{ - HostInfo: findServiceFromIngress(hostInfos, workload, deployWorkloads, stsWorkloads, log), + HostInfo: findServiceFromIngress(hostInfos, workload, allServices, log), } resp = append(resp, productRespInfo) @@ -314,25 +296,16 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return count, resp, nil } -func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, deployWorkloads []*Workload, stsWorkloads []*Workload, log *zap.SugaredLogger) []resource.HostInfo { - if (len(deployWorkloads) == 0 && len(stsWorkloads) == 0) || len(hostInfos) == 0 { +func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, allServices []*corev1.Service, log *zap.SugaredLogger) []resource.HostInfo { + if len(allServices) == 0 || len(hostInfos) == 0 { return []resource.HostInfo{} } serviceName := "" - switch currentWorkload.Type { - case setting.Deployment: - for _, deployWorkload := range deployWorkloads { - if deployWorkload.Name == currentWorkload.Name { - serviceName = deployWorkload.ServiceName - break - } - } - case setting.StatefulSet: - for _, stsWorkload := range stsWorkloads { - if stsWorkload.Name == currentWorkload.Name { - serviceName = stsWorkload.ServiceName - break - } + podLabels := labels.Set(currentWorkload.Spec.Labels) + for _, svc := range allServices { + if labels.SelectorFromValidatedSet(svc.Spec.Selector).Matches(podLabels) { + serviceName = svc.Name + break } } -- Gitee From 22388e8d76c855946a2f90bed507e98ff08dcfa8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 20:31:40 +0800 Subject: [PATCH 0294/2647] add put Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role.go | 14 ++++++++++++++ pkg/microservice/policy/core/handler/router.go | 1 + 2 files changed, 15 insertions(+) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 76456979..8f5d06e4 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -196,6 +196,20 @@ func CreateSystemRole(c *gin.Context) { ctx.Err = service.CreateRole("*", args, ctx.Logger) } +func UpdateOrCreateSystemRole(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Role{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + name := c.Param("name") + args.Name = name + ctx.Err = service.UpdateOrCreateRole("*", args, ctx.Logger) +} + func ListSystemRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 3d86d812..41a67e24 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -46,6 +46,7 @@ func (*Router) Inject(router *gin.RouterGroup) { systemRoles := router.Group("system-roles") { systemRoles.POST("", CreateSystemRole) + systemRoles.PUT("/:name",UpdateOrCreateSystemRole) systemRoles.GET("", ListSystemRoles) systemRoles.DELETE("/:name", DeleteSystemRole) } -- Gitee From 86f189404a6b63573adbac5884365a73e8d7d0c0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 20:36:57 +0800 Subject: [PATCH 0295/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/common/service/environment.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 7244cb8d..911bf4f4 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -286,7 +286,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } productRespInfo.Ingress = &IngressInfo{ - HostInfo: findServiceFromIngress(hostInfos, workload, allServices, log), + HostInfo: findServiceFromIngress(hostInfos, workload, allServices), } resp = append(resp, productRespInfo) @@ -296,19 +296,24 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return count, resp, nil } -func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, allServices []*corev1.Service, log *zap.SugaredLogger) []resource.HostInfo { +func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, allServices []*corev1.Service) []resource.HostInfo { if len(allServices) == 0 || len(hostInfos) == 0 { return []resource.HostInfo{} } serviceName := "" podLabels := labels.Set(currentWorkload.Spec.Labels) + fmt.Println(fmt.Sprintf("podLabels:%+v", podLabels)) for _, svc := range allServices { + if len(svc.Spec.Selector) == 0 { + continue + } + fmt.Println(fmt.Sprintf("svc.Spec.Selector:%+v", svc.Spec.Selector)) if labels.SelectorFromValidatedSet(svc.Spec.Selector).Matches(podLabels) { serviceName = svc.Name break } } - + fmt.Println(fmt.Sprintf("serviceName:%+v", serviceName)) if serviceName == "" { return []resource.HostInfo{} } -- Gitee From 349903f0b23c0cf4fca341cca48614f614d7436d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 27 Oct 2021 20:41:42 +0800 Subject: [PATCH 0296/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 911bf4f4..bb0e0d96 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -302,18 +302,15 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work } serviceName := "" podLabels := labels.Set(currentWorkload.Spec.Labels) - fmt.Println(fmt.Sprintf("podLabels:%+v", podLabels)) for _, svc := range allServices { if len(svc.Spec.Selector) == 0 { continue } - fmt.Println(fmt.Sprintf("svc.Spec.Selector:%+v", svc.Spec.Selector)) if labels.SelectorFromValidatedSet(svc.Spec.Selector).Matches(podLabels) { serviceName = svc.Name break } } - fmt.Println(fmt.Sprintf("serviceName:%+v", serviceName)) if serviceName == "" { return []resource.HostInfo{} } -- Gitee From ae9fb2097b6b65ad550e1df7524877d91d484ed6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 27 Oct 2021 20:51:47 +0800 Subject: [PATCH 0297/2647] change uids to string Signed-off-by: mouuii --- .../aslan/core/common/repository/models/template/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 549c9919..509a72a4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -41,7 +41,7 @@ type Product struct { Vars []*RenderKV `bson:"vars" json:"vars"` EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` - UserIDs []int `bson:"user_ids" json:"user_ids"` + UserIDs []string `bson:"user_ids" json:"user_ids"` TeamID int `bson:"team_id" json:"team_id"` Description string `bson:"description,omitempty" json:"desc,omitempty"` ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` -- Gitee From fe14ceb00f8639673a8c2922fa3137c3974a2ff1 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 27 Oct 2021 21:01:05 +0800 Subject: [PATCH 0298/2647] return empty projects Signed-off-by: lou --- pkg/microservice/picket/core/filter/service/project.go | 4 ++++ pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 134a6294..9b0a2b51 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -23,6 +23,10 @@ func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) return nil, err } + if len(names) == 0 { + return []byte("[]"), nil + } + for _, name := range names { qs.Add("names", name) } diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 7b4b451e..693b04a5 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -54,7 +54,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{"", "signin", "setup", "loading", "static/**"}, + Endpoints: []string{"", "signin", "setup", "loading", "static/**", "v1/**", "mobile/**", "productpipelines/**"}, }, { Methods: []string{"GET"}, -- Gitee From 91c0411cac48c979a2a8cf29797ed74118d7a716 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Wed, 27 Oct 2021 22:33:10 +0800 Subject: [PATCH 0299/2647] add Bootcamps info to readme Signed-off-by: landylee007 --- README-zh-CN.md | 35 ++++++++++++++++++++++------------- README.md | 36 +++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/README-zh-CN.md b/README-zh-CN.md index ecfe7fd7..beba21f0 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -44,25 +44,30 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery 产品特性介绍: -- **高并发的工作流** +
+ 高并发的工作流 + 基于云原生设计,经过简单配置,系统自动生成工作流,实现多服务高并发执行构建部署测试任务,以解决微服务架构下带来的多服务构建部署效率低下问题。 +
-基于云原生设计,经过简单配置,系统自动生成工作流,实现多服务高并发执行构建部署测试任务,以解决微服务架构下带来的多服务构建部署效率低下问题。 +
+ 以服务为核心的集成环境 + 一套服务配置,分钟级创建多套数据隔离的测试环境。为开发者进行日常调试、为测试人员做集成测试、为产品经理对外 Demo 提供强力支撑。 -- **以服务为核心的集成环境** + 对于现有的环境无需担心迁移成本,一键托管,轻松浏览、调试环境中的所有服务。 +
-一套服务配置,分钟级创建多套数据隔离的测试环境。为开发者进行日常调试、为测试人员做集成测试、为产品经理对外 Demo 提供强力支撑。 +
+ 无侵入的自动化测试 + 便捷且无侵入的对接已有自动化测试框架,通过 GitHub/GitLab Webhook 自动构建、部署及测试。 -对于现有的环境无需担心迁移成本,一键托管,轻松浏览、调试环境中的所有服务。 + 通过办公通讯机器人为开发者提供第一时间质量反馈,精准高效。有效落地“测试左移”工程实践,让测试价值得到体现。 +
-- **无侵入的自动化测试** +
+ 开发本地联调 CLI/IDE Plugin 插件 + 开发本地编辑完代码,一键进行本地代码构建,部署到联调环境,无需再陷入复杂且繁琐的工作流程。解放工程师双手,去创造更多产品价值。 +
-便捷且无侵入的对接已有自动化测试框架,通过 GitHub/GitLab Webhook 自动构建、部署及测试。 - -通过办公通讯机器人为开发者提供第一时间质量反馈,精准高效。有效落地“测试左移”工程实践,让测试价值得到体现。 - -- **开发本地联调 CLI** - -开发本地编辑完代码,一键进行本地代码构建,部署到联调环境,无需再陷入复杂且繁琐的工作流程,让本地联调不再成为难事。解放工程师双手,去创造更多产品价值。 ## 快速上手 @@ -70,6 +75,10 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery 请参阅 [快速入门](https://docs.koderover.com/zadig/quick-start/try-out-install) +### 训练营 + +Zadig [训练营](https://github.com/koderover/zadig-bootcamp)主要是为开发者提供实践小技巧、最佳实践案例的搭建、典型应用场景的演示等,以便快速获得持续交付最佳解决方案。可以直接进入 [教程](https://www.koderover.com/tutorials) 一步步实践和尝试。 + ### 快速开发 请阅读完整的 [Zadig 贡献指南](CONTRIBUTING-zh-CN.md),该包含参与贡献的方式、流程、格式、如何部署、哪里可以获取帮助等。 diff --git a/README.md b/README.md index 92c3a999..feae7ab1 100644 --- a/README.md +++ b/README.md @@ -47,25 +47,30 @@ The architecture is as follows: The Highlighted Features: -- **High Concurrency** +
+ High Concurrency + Based on cloud-native design, through simple configuration, the system automatically generates workflows to achieve high concurrent execution for continuous delivery relevant tasks such as building, testing and deployment, across multiple services. It significantly improves the efficiency of multi-services deployment in microservice architecture. +
-Based on cloud-native design, through simple configuration, the system automatically generates workflows to achieve high concurrent execution for continuous delivery relevant tasks such as building, testing and deployment, across multiple services. It significantly improves the efficiency of multi-services deployment in microservice architecture. +
+ Service-oriented Environment + With just one set of service configuration, multiple encapsulated environments will be provided automatically within minutes, empowering independent environments for developers, QAs and product managers. -- **Service-oriented Environment** + Minimum to none migration cost of existing environments -- just hosting with one click, the system allows browsing and adjusting all the services at your fingertips. +
-With just one set of service configuration, multiple encapsulated environments will be provided automatically within minutes, empowering independent environments for developers, QAs and product managers. +
+ Non-intrusive Testing Automation + Zadig can easily and non-intrusively embed existing testing automation frameworks, and achieve continuous building, testing and deployment via GitHub/GitLab Webhook. -Minimum to none migration cost of existing environments -- just hosting with one click, the system allows browsing and adjusting all the services at your fingertips. + It also integrates with productivity bots to provide instant quality report, which effectively applies shift-left testing best practices. +
-- **Non-intrusive Testing Automation** +
+ Convenient Development CLI/IDE Plugin + Zadig also provides a convenient toolkit with development commandline interface which allows compiling, building and deploying the changes to dev environment with one command. It enables collaborated debugging and testing with minimum manual toil, reduces cognitive load and allows teams to focus more on business. +
-Zadig can easily and non-intrusively embed existing testing automation frameworks, and achieve continuous building, testing and deployment via GitHub/GitLab Webhook. - -It also integrates with productivity bots to provide instant quality report, which effectively applies shift-left testing best practices. - -- **Convenient Development CLI** - -Zadig also provides a convenient toolkit with development commandline interface which allows compiling, building and deploying the changes to dev environment with one command. It enables collaborated debugging and testing with minimum manual toil, reduces cognitive load and allows teams to focus more on business. ## Quick start @@ -73,6 +78,11 @@ Zadig also provides a convenient toolkit with development commandline interface Please follow [Quick Start](https://docs.koderover.com/zadig/quick-start/try-out-install) +### Bootcamps + +Zadig [bootcamp](https://github.com/koderover/zadig-bootcamp) are designed to expose developers to both the simplicity and depth of the continuous delivery solution. With many Hands-on tips, best practice case study, and demonstrations of typical application scenario,and much more. +Just follow [Tutorials](https://www.koderover.com/tutorials) to try it step by step. + ### How to make contribution? Please check out [our contributing guideline](CONTRIBUTING.md). -- Gitee From 4a305944ba0f745659f743561cb6e78645cf230d Mon Sep 17 00:00:00 2001 From: landylee007 Date: Wed, 27 Oct 2021 22:38:41 +0800 Subject: [PATCH 0300/2647] add Bootcamps info to readme Signed-off-by: landylee007 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index feae7ab1..772a3249 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,8 @@ Please follow [Quick Start](https://docs.koderover.com/zadig/quick-start/try-out ### Bootcamps -Zadig [bootcamp](https://github.com/koderover/zadig-bootcamp) are designed to expose developers to both the simplicity and depth of the continuous delivery solution. With many Hands-on tips, best practice case study, and demonstrations of typical application scenario,and much more. +With many hands-on tips, best practices, case studies and demos of typical application scenarios, Zadig [bootcamp](https://github.com/koderover/zadig-bootcamp) is designed to expose developers to the simplicity and the depth of the Zadig continuous delivery solution. + Just follow [Tutorials](https://www.koderover.com/tutorials) to try it step by step. ### How to make contribution? -- Gitee From 88f7e473409d646f5aeda2bafe36f69517e20561 Mon Sep 17 00:00:00 2001 From: Landy Date: Wed, 27 Oct 2021 22:39:42 +0800 Subject: [PATCH 0301/2647] Update server.go --- pkg/microservice/aslan/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/server/server.go b/pkg/microservice/aslan/server/server.go index 399aec1e..6a16dfc1 100644 --- a/pkg/microservice/aslan/server/server.go +++ b/pkg/microservice/aslan/server/server.go @@ -53,7 +53,7 @@ func Serve(ctx context.Context) error { defer cancel() if err := server.Shutdown(ctx); err != nil { - log.Errorf("Failed to stop server, error : %s", err) + log.Errorf("Failed to stop server, error: %s", err) } }() -- Gitee From 40587ebe208f6dbe4f484a4cb089eba751f567a9 Mon Sep 17 00:00:00 2001 From: Landy Date: Wed, 27 Oct 2021 22:40:10 +0800 Subject: [PATCH 0302/2647] Update types.go --- pkg/microservice/cron/core/service/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index b37b9ed4..96c0c907 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -119,7 +119,7 @@ func (schedule *Schedule) Validate() error { return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) } if schedule.Number <= 0 { - return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0 ", e.InvalidFormatErrMsg) + return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0", e.InvalidFormatErrMsg) } //if schedule.Frequency == FrequencyMinutes && schedule.Number < 1 { // log.Info("minimum schedule minutes must >= 30") -- Gitee From 08b78d2f06b2c0858723f0c0685c74fbdd6d7830 Mon Sep 17 00:00:00 2001 From: Landy Date: Wed, 27 Oct 2021 22:40:38 +0800 Subject: [PATCH 0303/2647] Update action.yml --- action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/action.yml b/action.yml index d1f62cc5..16e71a8d 100644 --- a/action.yml +++ b/action.yml @@ -16,4 +16,3 @@ inputs: runs: using: 'node12' main: 'dist/index.js' - -- Gitee From 85ae6d5965f0f548bc522d3cf4fb8d5411d21d81 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Wed, 27 Oct 2021 22:44:31 +0800 Subject: [PATCH 0304/2647] add Bootcamps info to readme Signed-off-by: landylee007 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 772a3249..374fa483 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Please follow [Quick Start](https://docs.koderover.com/zadig/quick-start/try-out ### Bootcamps -With many hands-on tips, best practices, case studies and demos of typical application scenarios, Zadig [bootcamp](https://github.com/koderover/zadig-bootcamp) is designed to expose developers to the simplicity and the depth of the Zadig continuous delivery solution. +With many hands-on tips, case studies and demos of different application types, Zadig [bootcamp](https://github.com/koderover/zadig-bootcamp) gives developers easy to follow best practices in a cloud native CI/CD solution Just follow [Tutorials](https://www.koderover.com/tutorials) to try it step by step. -- Gitee From 8e14dedb51fecf9aa97c74e536c2f32a48f140bc Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 10:46:03 +0800 Subject: [PATCH 0305/2647] modify the number of pr list Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/service/merge_request.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 527bb06b..fd5ca554 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -19,6 +19,7 @@ package service import ( "context" + "github.com/google/go-github/v35/github" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -69,7 +70,9 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo } else { // github gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) - pullRequests, err := gh.ListPullRequests(context.TODO(), namespace, projectName, nil) + pullRequests, err := gh.ListPullRequests(context.TODO(), namespace, projectName, &github.PullRequestListOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + }) if err != nil { return nil, err } -- Gitee From 71a6d9205d0db6c2979204ff88b6d551685e10f2 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 28 Oct 2021 10:51:59 +0800 Subject: [PATCH 0306/2647] update policy Signed-off-by: lou --- pkg/microservice/aslan/core/project/handler/policy.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index 8e4a722a..dbaf18ad 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -14,8 +14,6 @@ rules: endpoint: "/api/aslan/project/products/?*/services" - method: GET endpoint: "/api/aslan/service/services/?*" - - method: GET - endpoint: "/api/aslan/service/name" - method: GET endpoint: "/api/aslan/project/products/?*/searching-rules" - method: GET -- Gitee From 12470880a2d81fd816e3f05f627a1abf78ab42ce Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Oct 2021 10:56:26 +0800 Subject: [PATCH 0307/2647] add variable merge logic Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 48 ++++++++++++------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 85654d26..33bad19e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -203,16 +203,17 @@ func GetFileContent(serviceName, productName, filePath, fileName string, log *za } func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (*ChartTemplateData, error) { - // get chart template from local disk templateChart, err := mongodb.NewChartColl().Get(templateName) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", templateName, err) - return nil, err + return nil, fmt.Errorf("failed to get chart template: %s", templateName) } + // get chart template from local disk localBase := configbase.LocalChartTemplatePath(templateName) s3Base := configbase.ObjectStorageChartTemplatePath(templateName) if err = fsservice.PreloadFiles(templateName, localBase, s3Base, logger); err != nil { + logger.Errorf("Failed to download template %s, err: %s", templateName, err) return nil, err } @@ -223,6 +224,7 @@ func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (* chartFilePath := filepath.Join(localBase, base, setting.ChartYaml) chartFileContent, err := os.ReadFile(chartFilePath) if err != nil { + logger.Errorf("Failed to read chartfile template %s, err: %s", templateName, err) return nil, err } chart := new(Chart) @@ -322,14 +324,12 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return err } - if svc.HelmChart != nil { - compareHelmVariable([]*templatemodels.RenderChart{ - {ServiceName: args.Name, - ChartVersion: svc.HelmChart.Version, - ValuesYaml: svc.HelmChart.ValuesYaml, - }, - }, projectName, args.CreatedBy, logger) - } + compareHelmVariable([]*templatemodels.RenderChart{ + {ServiceName: args.Name, + ChartVersion: svc.HelmChart.Version, + ValuesYaml: svc.HelmChart.ValuesYaml, + }, + }, projectName, args.CreatedBy, logger) return nil } @@ -448,13 +448,11 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } - if svc.HelmChart != nil { - helmRenderCharts = append(helmRenderCharts, &templatemodels.RenderChart{ - ServiceName: serviceName, - ChartVersion: svc.HelmChart.Version, - ValuesYaml: svc.HelmChart.ValuesYaml, - }) - } + helmRenderCharts = append(helmRenderCharts, &templatemodels.RenderChart{ + ServiceName: serviceName, + ChartVersion: svc.HelmChart.Version, + ValuesYaml: svc.HelmChart.ValuesYaml, + }) }) } @@ -691,6 +689,18 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { return nil } +func renderVariablesToYaml(valuesYaml string, productName, serviceName string, variables []*Variable) string { + valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableProduct, productName, -1) + valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableService, serviceName, -1) + variableFormatter := func(rawName string) string { + return fmt.Sprintf("$%s$", rawName) + } + for _, variable := range variables { + valuesYaml = strings.Replace(valuesYaml, variableFormatter(variable.Key), variable.Value, -1) + } + return valuesYaml +} + func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*models.Service, error) { chartName, chartVersion, err := readChartYAML(fsTree, args.ServiceName, logger) if err != nil { @@ -705,6 +715,9 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg } valuesYaml := string(values) + //render variables + valuesYaml = renderVariablesToYaml(valuesYaml, args.ProductName, args.ServiceName, args.Variables) + serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) if err != nil { @@ -827,6 +840,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { } // TODO:use yaml compare instead of just comparing the characters + // TODO service variables if helmServiceInfo.FileName == setting.ValuesYaml && preServiceTmpl.HelmChart.ValuesYaml != helmServiceInfo.FileContent { var valuesMap map[string]interface{} if err = yaml.Unmarshal([]byte(helmServiceInfo.FileContent), &valuesMap); err != nil { -- Gitee From c3ade7a9e04684d5a7c982884b001b917c22be65 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Oct 2021 11:03:28 +0800 Subject: [PATCH 0308/2647] optimize create service logic Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 478e20b2..98b718cc 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -96,7 +96,7 @@ type Chart struct { type helmServiceCreationArgs struct { ChartName string ChartVersion string - ValuesYAML string + MergedValues string ServiceName string FilePath string ProductName string @@ -304,7 +304,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe &helmServiceCreationArgs{ ChartName: templateChartInfo.ChartName, ChartVersion: templateChartInfo.ChartVersion, - ValuesYAML: string(merged), + MergedValues: string(merged), ServiceName: args.Name, FilePath: to, ProductName: projectName, @@ -403,7 +403,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC if err != nil { return serviceName, err } - valuesYAML, _, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + valuesYAML, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) return serviceName, err }) if err != nil { @@ -426,7 +426,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC &helmServiceCreationArgs{ ChartName: serviceName, ChartVersion: chartVersion, - ValuesYAML: string(valuesYAML), + MergedValues: string(valuesYAML), ServiceName: serviceName, FilePath: filePath, ProductName: projectName, @@ -591,7 +591,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi &helmServiceCreationArgs{ ChartName: templateChartData.ChartName, ChartVersion: templateChartData.ChartVersion, - ValuesYAML: string(mergedValues), + MergedValues: string(mergedValues), ServiceName: serviceName, FilePath: to, ProductName: projectName, @@ -631,20 +631,13 @@ func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (str return chart.Name, chart.Version, nil } -func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, map[string]interface{}, error) { +func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, error) { content, err := fs.ReadFile(chartTree, filepath.Join(base, setting.ValuesYaml)) if err != nil { logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) - return nil, nil, err - } - - valuesMap := make(map[string]interface{}) - if err = yaml.Unmarshal(content, &valuesMap); err != nil { - logger.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ValuesYaml, err) - return nil, nil, err + return nil, err } - - return content, valuesMap, nil + return content, nil } func geneCreationDetail(args *helmServiceCreationArgs) interface{} { @@ -689,12 +682,13 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - values, valuesMap, err := readValuesYAML(fsTree, args.ServiceName, logger) + valuesYaml := args.ValuesYaml + valuesMap := make(map[string]interface{}) + err = yaml.Unmarshal([]byte(valuesYaml), &valuesMap) if err != nil { - logger.Errorf("Failed to read values.yaml, err %s", err) + logger.Errorf("Failed to unmarshall yaml, err %s", err) return nil, err } - valuesYaml := string(values) serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) -- Gitee From 1561fb245ca59b9ed720719b6049cea44056cfe6 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Oct 2021 14:19:28 +0800 Subject: [PATCH 0309/2647] update regrex logic Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 42 +++++++++++++------ .../aslan/core/templatestore/service/chart.go | 5 ++- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e787b0c0..0d60a5e1 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -17,7 +17,9 @@ limitations under the License. package service import ( + "bytes" "fmt" + "html/template" "io/fs" "os" "path/filepath" @@ -25,6 +27,8 @@ import ( "sync" "time" + //"text/template" + "github.com/27149chen/afero" "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" @@ -36,7 +40,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + templatedata "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -266,7 +270,12 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe var values [][]byte if len(templateChartInfo.DefaultValuesYAML) > 0 { - values = append(values, templateChartInfo.DefaultValuesYAML) + //render variables + renderedYaml, err := renderVariablesToYaml(string(templateChartInfo.DefaultValuesYAML), projectName, args.Name, templateArgs.Variables) + if err != nil { + return err + } + values = append(values, []byte(renderedYaml)) } if len(templateArgs.ValuesYAML) > 0 { @@ -662,7 +671,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { LoadPath: args.FilePath, } case setting.SourceFromChartTemplate: - yamlData := &template.CustomYaml{ + yamlData := &templatedata.CustomYaml{ YamlContent: args.ValuesYaml, } variables := make([]*models.Variable, 0, len(args.Variables)) @@ -682,16 +691,26 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { return nil } -func renderVariablesToYaml(valuesYaml string, productName, serviceName string, variables []*Variable) string { +func renderVariablesToYaml(valuesYaml string, productName, serviceName string, variables []*Variable) (string, error) { valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableProduct, productName, -1) valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableService, serviceName, -1) - variableFormatter := func(rawName string) string { - return fmt.Sprintf("$%s$", rawName) - } + + // build replace data + valuesMap := make(map[string]interface{}) for _, variable := range variables { - valuesYaml = strings.Replace(valuesYaml, variableFormatter(variable.Key), variable.Value, -1) + valuesMap[variable.Key] = variable.Value + } + + tmpl := template.Must(template.New("values").Parse(valuesYaml)) + buf := bytes.NewBufferString("") + + err := tmpl.Execute(buf, valuesMap) + if err != nil { + log.Errorf("failed to render values content, err %s", err) + return "", fmt.Errorf("failed to render variables") } - return valuesYaml + valuesYaml = buf.String() + return valuesYaml, nil } func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*models.Service, error) { @@ -701,7 +720,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - valuesYaml := args.ValuesYaml + valuesYaml := args.MergedValues valuesMap := make(map[string]interface{}) err = yaml.Unmarshal([]byte(valuesYaml), &valuesMap) if err != nil { @@ -709,9 +728,6 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - //render variables - valuesYaml = renderVariablesToYaml(valuesYaml, args.ProductName, args.ServiceName, args.Variables) - serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) if err != nil { diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index edf83de8..739c9fa7 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -38,7 +38,8 @@ import ( ) var ( - variableExtractRegexp = regexp.MustCompile("\\$(.*?)\\$") + //variableExtractRegexp = regexp.MustCompile("\\$(.*?)\\$") + variableExtractRegexp = regexp.MustCompile("{{.(\\w*)}}") ) type ChartTemplateListResp struct { @@ -167,7 +168,7 @@ func parseTemplateVariables(name, path string, logger *zap.SugaredLogger) ([]str strSet := sets.NewString() allMatches := variableExtractRegexp.FindAllStringSubmatch(string(valueYamlContent), -1) for _, match := range allMatches { - if len(match) < 1 { + if len(match) < 2 { continue } strSet.Insert(match[1:]...) -- Gitee From 005295c08d3e4d789bcd96ff9270343929dddcb9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Oct 2021 14:58:06 +0800 Subject: [PATCH 0310/2647] optmize code Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 2 +- .../aslan/core/environment/service/environment.go | 10 +++++----- .../aslan/core/environment/service/renderset.go | 9 ++++----- .../warpdrive/core/service/taskplugin/deploy.go | 2 +- .../warpdrive/core/service/types/renderset.go | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index 4d23841a..a8ea77f0 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -151,5 +151,5 @@ func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[ } return nil } - return fmt.Errorf("faild to find image for contianer %s", container.Image) + return fmt.Errorf("failed to find image for contianer %s", container.Image) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 02b77fdc..cacdc66d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1166,7 +1166,7 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue }) if err != nil { log.Errorf("failed to query service, name %s, err %s", serviceName, err) - return "", "", fmt.Errorf("faild to query service, name %s", serviceName) + return "", "", fmt.Errorf("failed to query service, name %s", serviceName) } if scene == "createEnv" { @@ -1178,7 +1178,7 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue EnvName: envName, }) if err != nil { - return "", "", fmt.Errorf("faild to query product info, name %s", envName) + return "", "", fmt.Errorf("failed to query product info, name %s", envName) } // find chart info from cur render set @@ -1186,7 +1186,7 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue renderSet, err := mongodb.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("renderset Find error, productName:%s, envName:%s, err:%s", productInfo.ProductName, productInfo.EnvName, err) - return "", "", fmt.Errorf("faild to query renderset info, name %s", productInfo.Render.Name) + return "", "", fmt.Errorf("failed to query renderset info, name %s", productInfo.Render.Name) } var targetChart *templatemodels.RenderChart @@ -1198,7 +1198,7 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue } if targetChart == nil { - return "", "", fmt.Errorf("faild to find chart info, name: %s", serviceName) + return "", "", fmt.Errorf("failed to find chart info, name: %s", serviceName) } if scene == "updateEnv" { @@ -1213,7 +1213,7 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue // merge environment values mergedBs, err := overrideValues([]byte(targetChart.ValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) if err != nil { - return "", "", errors.Wrapf(err, "faild to override values") + return "", "", errors.Wrapf(err, "failed to override values") } return string(mergedBs), renderSet.DefaultValues, nil } else if scene == "updateRenderSet" { diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index cadbf78a..3310982b 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -21,8 +21,6 @@ import ( "strings" "sync" - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" @@ -30,6 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" "github.com/pkg/errors" ) @@ -90,8 +89,8 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def return ret, nil } if err != nil { - log.Errorf("faild to query product info, productName %s envName %s err %s", productName, envName, err) - return nil, fmt.Errorf("faild to query product info, productName %s envName %s", productName, envName) + log.Errorf("failed to query product info, productName %s envName %s err %s", productName, envName, err) + return nil, fmt.Errorf("failed to query product info, productName %s envName %s", productName, envName) } if productInfo.Render == nil { @@ -104,7 +103,7 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def } rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { - log.Errorf("faild to query renderset info, envName %s err %s", productInfo.Render.Name, err) + log.Errorf("failed to query renderset info, name %s err %s", productInfo.Render.Name, err) return nil, err } if !existed { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 7822a5f2..36c186a9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -406,7 +406,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if targetContainer.ImagePath == nil { - err = errors.Errorf("failded to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) return } diff --git a/pkg/microservice/warpdrive/core/service/types/renderset.go b/pkg/microservice/warpdrive/core/service/types/renderset.go index dba87417..3d469a78 100644 --- a/pkg/microservice/warpdrive/core/service/types/renderset.go +++ b/pkg/microservice/warpdrive/core/service/types/renderset.go @@ -28,7 +28,7 @@ type RenderSet struct { UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 ›yaml content KVs []*RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` ChartInfos []*RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` Description string `bson:"description,omitempty" json:"description,omitempty"` -- Gitee From 30c408c0176eb2150d5e0075184a1674d25b858f Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 16:28:16 +0800 Subject: [PATCH 0311/2647] product create Signed-off-by: mouuii --- .../repository/mongodb/template/product.go | 1 - .../core/environment/service/environment.go | 1 - .../aslan/core/environment/service/product.go | 2 -- .../aslan/core/project/service/product.go | 5 ++--- .../picket/client/aslan/project.go | 11 ++++++++++ .../picket/core/filter/handler/project.go | 21 +++++++++++++++++++ .../picket/core/filter/handler/router.go | 5 +++-- .../picket/core/filter/service/project.go | 21 +++++++++++++++++++ pkg/setting/consts.go | 3 ++- pkg/shared/client/policy/policy.go | 2 +- 10 files changed, 61 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index a4312f18..c32ca2bc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -247,7 +247,6 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { "teams": args.Teams, "enabled": args.Enabled, "description": args.Description, - "visibility": args.Visibility, "user_ids": args.UserIDs, "team_id": args.TeamID, "timeout": args.Timeout, diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 31e4deb8..0c904e67 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -633,7 +633,6 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l productObj := &commonmodels.Product{ ProductName: args.ProductName, Revision: 1, - Enabled: false, EnvName: args.EnvName, UpdateBy: userName, IsPublic: true, diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index c04f3cdf..eef10e4b 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -107,11 +107,9 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode //返回中的ProductName即产品模板的名称 ret.ProductName = prodTmpl.ProductName ret.Revision = prodTmpl.Revision - ret.Enabled = prodTmpl.Enabled ret.Services = [][]*commonmodels.ProductService{} ret.UpdateBy = prodTmpl.UpdateBy ret.CreateTime = prodTmpl.CreateTime - ret.Visibility = prodTmpl.Visibility ret.Render = &commonmodels.RenderInfo{Name: "", Description: ""} ret.Vars = prodTmpl.Vars ret.ChartInfos = prodTmpl.ChartInfos diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index c98e96a8..efc66517 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -510,12 +510,11 @@ func ForkProduct(username, requestID string, args *template.ForkProject, log *za CreateBy: username, UpdateBy: username, } - policyClient := policy.New() - err = policyClient.CreateRoleBinding(args.ProductName, &policy.RoleBinding{ + err = policy.NewDefault().CreateRoleBinding(args.ProductName, &policy.RoleBinding{ Name: fmt.Sprintf(setting.ContributorRoleBindingFmt, args.ProductName, username), User: username, Role: setting.Contributor, - Global: true, + Public: true, }) if err != nil { log.Error("rolebinding error") diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 80e67757..cbad86b7 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -1,6 +1,7 @@ package aslan import ( + "fmt" "net/http" "net/url" @@ -17,3 +18,13 @@ func (c *Client) ListProjects(header http.Header, qs url.Values) ([]byte, error) return res.Body(), nil } + +func (c *Client) CreateProject(header http.Header,body []byte,projectName string)([]byte,error){ + url := fmt.Sprintf("/project/projects/%s",projectName) + res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header),httpclient.SetBody(body)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index a4f38ab7..e4e1f1bc 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -1,10 +1,13 @@ package handler import ( + "io/ioutil" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func ListProjects(c *gin.Context) { @@ -13,3 +16,21 @@ func ListProjects(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProjects(c.Request.Header, c.Request.URL.Query(), ctx.Logger) } + +type CreateProjectReq struct { + Public bool `json:"public"` + ProjectName string `json:"project_name"` +} + +func CreateProject(c *gin.Context){ + ctx := internalhandler.NewContext(c) + defer func() {internalhandler.JSONResponse(c,ctx)}() + + args := new(CreateProjectReq) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid CreateProjectReq") + return + } + body ,_ := ioutil.ReadAll(c.Request.Body) + ctx.Resp,ctx.Err = service.CreateProject(c.Request.Header,body,args.ProjectName,args.Public,ctx.Logger) +} \ No newline at end of file diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 9e8aea85..52575eed 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -23,9 +23,10 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - roles := router.Group("projects") + projects := router.Group("projects") { - roles.GET("", ListProjects) + projects.GET("", ListProjects) + projects.POST("",CreateProject) } workflows := router.Group("workflows") { diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 9b0a2b51..946300a8 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -1,6 +1,7 @@ package service import ( + "fmt" "net/http" "net/url" "strings" @@ -10,12 +11,32 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" "github.com/koderover/zadig/pkg/microservice/picket/client/opa" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/policy" ) type allowedProjectsData struct { Result []string `json:"result"` } +func CreateProject(header http.Header,body []byte,projectName string,public bool, logger *zap.SugaredLogger)([]byte, error){ + // role binding + if public{ + err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ + Name: fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly), + User: "*", + Role: setting.ReadOnly, + Public: true, + }) + logger.Error(err) + } + + res, err := aslan.New().CreateProject(header,body,projectName) + if err !=nil { + policy.NewDefault().DeleteRoleBinding(fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly),projectName) + } + return res,err +} + func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { names, err := getVisibleProjects(header, logger) if err != nil { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 61af7f76..dca6919a 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -497,8 +497,9 @@ type RoleType string const ( Contributor RoleType = "Contributor" - ReadOnly RoleType = "ReadOnly" + ReadOnly RoleType = "readonly" ContributorRoleBindingFmt string = "contributor-project:%s-user:%s" + ReadOnlyRoleBindingFmt string = "readonly-project:%s-user:%s" ) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 4ec6b283..be7a8cb0 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -38,7 +38,7 @@ type RoleBinding struct { Name string User string Role setting.RoleType - Global bool + Public bool } func (c *Client) CreateRoleBinding(projectName string, roleBinding *RoleBinding) error { -- Gitee From 8a89805a514105c2862b4bb5936817808ba6f929 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 16:38:23 +0800 Subject: [PATCH 0312/2647] product create Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/handler/project.go | 4 ++-- pkg/microservice/picket/core/filter/handler/router.go | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index e4e1f1bc..79edfa3f 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -19,7 +19,7 @@ func ListProjects(c *gin.Context) { type CreateProjectReq struct { Public bool `json:"public"` - ProjectName string `json:"project_name"` + ProductName string `json:"product_name"` } func CreateProject(c *gin.Context){ @@ -32,5 +32,5 @@ func CreateProject(c *gin.Context){ return } body ,_ := ioutil.ReadAll(c.Request.Body) - ctx.Resp,ctx.Err = service.CreateProject(c.Request.Header,body,args.ProjectName,args.Public,ctx.Logger) + ctx.Resp,ctx.Err = service.CreateProject(c.Request.Header,body,args.ProductName,args.Public,ctx.Logger) } \ No newline at end of file diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 52575eed..4070548a 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -26,7 +26,10 @@ func (*Router) Inject(router *gin.RouterGroup) { projects := router.Group("projects") { projects.GET("", ListProjects) - projects.POST("",CreateProject) + } + products := router.Group("products") + { + products.POST("",CreateProject) } workflows := router.Group("workflows") { -- Gitee From c98bebdf4e0e3d255447937b45b0eeb107028459 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 16:41:42 +0800 Subject: [PATCH 0313/2647] product create Signed-off-by: mouuii --- .../picket/core/filter/service/project.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 946300a8..2a8ba670 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -18,9 +18,9 @@ type allowedProjectsData struct { Result []string `json:"result"` } -func CreateProject(header http.Header,body []byte,projectName string,public bool, logger *zap.SugaredLogger)([]byte, error){ +func CreateProject(header http.Header, body []byte, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { // role binding - if public{ + if public { err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ Name: fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly), User: "*", @@ -30,11 +30,11 @@ func CreateProject(header http.Header,body []byte,projectName string,public bool logger.Error(err) } - res, err := aslan.New().CreateProject(header,body,projectName) - if err !=nil { - policy.NewDefault().DeleteRoleBinding(fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly),projectName) + res, err := aslan.New().CreateProject(header, body, projectName) + if err != nil { + policy.NewDefault().DeleteRoleBinding(fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly), projectName) } - return res,err + return res, err } func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { -- Gitee From 850382ae0107102168ccd03f297c90bc92c9def4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 17:01:22 +0800 Subject: [PATCH 0314/2647] product create Signed-off-by: mouuii --- .../aslan/core/project/handler/product.go | 2 +- .../aslan/core/project/service/product.go | 10 +++++----- .../picket/core/filter/handler/project.go | 17 ++++++++--------- .../picket/core/filter/service/project.go | 9 ++++++--- pkg/setting/consts.go | 10 ++++------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index dee7e863..0d96803f 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -182,7 +182,7 @@ func ForkProduct(c *gin.Context) { return } args.ProductName = c.Param("productName") - ctx.Err = projectservice.ForkProduct(ctx.UserName, ctx.RequestID, args, ctx.Logger) + ctx.Err = projectservice.ForkProduct(ctx.UserName, ctx.UserID, ctx.RequestID, args, ctx.Logger) } func UnForkProduct(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index efc66517..94ba3b8c 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -343,7 +343,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug //删除自由编排工作流 cl := configclient.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType); err == nil && enable { collieClient := collie.New(config.CollieAPIAddress()) if err = collieClient.DeleteCIPipelines(productName, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) @@ -397,7 +397,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return nil } -func ForkProduct(username, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { +func ForkProduct(username, userid, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { @@ -511,8 +511,8 @@ func ForkProduct(username, requestID string, args *template.ForkProject, log *za UpdateBy: username, } err = policy.NewDefault().CreateRoleBinding(args.ProductName, &policy.RoleBinding{ - Name: fmt.Sprintf(setting.ContributorRoleBindingFmt, args.ProductName, username), - User: username, + Name: fmt.Sprintf(setting.RoleBindFmt, args.ProductName, userid), + User: userid, Role: setting.Contributor, Public: true, }) @@ -534,7 +534,7 @@ func UnForkProduct(userID string, username, productName, workflowName, envName, } policyClient := policy.New() - err := policyClient.DeleteRoleBinding(fmt.Sprintf(setting.ContributorRoleBindingFmt, productName, username), productName) + err := policyClient.DeleteRoleBinding(fmt.Sprintf(setting.RoleBindFmt, userID, setting.Contributor), productName) if err != nil { log.Error("rolebinding delete error") return e.ErrForkProduct diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 79edfa3f..6d5dff48 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -1,8 +1,6 @@ package handler import ( - "io/ioutil" - "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" @@ -18,19 +16,20 @@ func ListProjects(c *gin.Context) { } type CreateProjectReq struct { - Public bool `json:"public"` - ProductName string `json:"product_name"` + Public bool `json:"public"` + ProductName string `json:"product_name"` } -func CreateProject(c *gin.Context){ +func CreateProject(c *gin.Context) { ctx := internalhandler.NewContext(c) - defer func() {internalhandler.JSONResponse(c,ctx)}() + defer func() { internalhandler.JSONResponse(c, ctx) }() args := new(CreateProjectReq) if err := c.ShouldBindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid CreateProjectReq") return } - body ,_ := ioutil.ReadAll(c.Request.Body) - ctx.Resp,ctx.Err = service.CreateProject(c.Request.Header,body,args.ProductName,args.Public,ctx.Logger) -} \ No newline at end of file + body, _ := c.GetRawData() + + ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 2a8ba670..03469e36 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -20,19 +20,22 @@ type allowedProjectsData struct { func CreateProject(header http.Header, body []byte, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { // role binding + roleBindingName := fmt.Sprintf(setting.RoleBindFmt, "*", setting.ReadOnly) if public { + err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ - Name: fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly), + Name: roleBindingName, User: "*", Role: setting.ReadOnly, Public: true, }) - logger.Error(err) + logger.Errorf("create rolebinding: %s err: %s", roleBindingName, err) } res, err := aslan.New().CreateProject(header, body, projectName) if err != nil { - policy.NewDefault().DeleteRoleBinding(fmt.Sprintf(setting.ReadOnlyRoleBindingFmt, projectName, setting.ReadOnly), projectName) + policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName) + logger.Errorf("delete rolebinding: %s err: %s", roleBindingName, err) } return res, err } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index dca6919a..a6f44661 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -496,12 +496,10 @@ const ChartTemplatesPath = "charts" type RoleType string const ( - Contributor RoleType = "Contributor" - ReadOnly RoleType = "readonly" - ContributorRoleBindingFmt string = "contributor-project:%s-user:%s" - ReadOnlyRoleBindingFmt string = "readonly-project:%s-user:%s" + Contributor RoleType = "contributor" + ReadOnly RoleType = "readonly" + RoleBindFmt string = "userid:%s-role:%s" ) - // ModernWorkflowType 自由编排工作流 -const ModernWorkflowType = "ModernWorkflow" \ No newline at end of file +const ModernWorkflowType = "ModernWorkflow" -- Gitee From 8d5cd5d89d4d14bd39c3d8468e41a747995ef273 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 17:03:19 +0800 Subject: [PATCH 0315/2647] product create Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/project.go | 6 +++--- pkg/microservice/picket/core/filter/handler/router.go | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index cbad86b7..8eaed21b 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -19,9 +19,9 @@ func (c *Client) ListProjects(header http.Header, qs url.Values) ([]byte, error) return res.Body(), nil } -func (c *Client) CreateProject(header http.Header,body []byte,projectName string)([]byte,error){ - url := fmt.Sprintf("/project/projects/%s",projectName) - res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header),httpclient.SetBody(body)) +func (c *Client) CreateProject(header http.Header, body []byte, projectName string) ([]byte, error) { + url := fmt.Sprintf("/project/products/%s", projectName) + res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetBody(body)) if err != nil { return nil, err } diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 4070548a..f1a010d3 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -26,11 +26,9 @@ func (*Router) Inject(router *gin.RouterGroup) { projects := router.Group("projects") { projects.GET("", ListProjects) + projects.POST("", CreateProject) } - products := router.Group("products") - { - products.POST("",CreateProject) - } + workflows := router.Group("workflows") { workflows.GET("testName/:testName", ListTestWorkflows) -- Gitee From 03007ffb1848bbe26600d9e6f452c205bc5c20c9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Oct 2021 17:08:42 +0800 Subject: [PATCH 0316/2647] optimize code Signed-off-by: allenshen --- .../core/environment/service/environment.go | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index cacdc66d..829be05b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2776,8 +2776,8 @@ func updateProductGroup(username, productName, envName, updateType string, produ ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), Namespace: namespace, Wait: true, - Force: true, - Timeout: Timeout * time.Second * 10, + //Force: true, + //Timeout: Timeout * time.Second * 10, }); err != nil { log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) } @@ -2804,6 +2804,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ } errList := new(multierror.Error) + sIndex := 0 for groupIndex, services := range productResp.Services { var wg sync.WaitGroup for _, svc := range services { @@ -2817,6 +2818,8 @@ func updateProductGroup(username, productName, envName, updateType string, produ continue } + index := sIndex + wg.Add(1) go func(service *commonmodels.ProductService) { defer wg.Done() @@ -2835,12 +2838,17 @@ func updateProductGroup(username, productName, envName, updateType string, produ return } - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) + //TODO for little optimize + time.Sleep(time.Millisecond * 1500 * time.Duration(index)) + + //err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) if err != nil { errList = multierror.Append(errList, err) return } }(svc) + sIndex++ } wg.Wait() @@ -3048,6 +3056,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } errList := new(multierror.Error) + sIndex := 0 for groupIndex, services := range productResp.Services { var wg sync.WaitGroup groupServices := make([]*commonmodels.ProductService, 0) @@ -3066,14 +3075,21 @@ func updateProductVariable(productName, envName string, productResp *commonmodel continue } wg.Add(1) + index := sIndex go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service) { defer wg.Done() - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, Timeout*time.Second*10, helmClient) + //err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, Timeout*time.Second*10, helmClient) + + //TODO for little optimize + time.Sleep(time.Millisecond * 1500 * time.Duration(index)) + + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, 0, helmClient) if err != nil { errList = multierror.Append(errList, err) return } }(renderChart, serviceObj) + sIndex++ } groupServices = append(groupServices, service) } -- Gitee From ff0d40330342a748a3d7185d1415e4db26993d83 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 28 Oct 2021 17:14:24 +0800 Subject: [PATCH 0317/2647] bulk delete roles and bindings Signed-off-by: lou --- pkg/microservice/policy/core/handler/role.go | 30 ++++++++++++++++++- .../policy/core/handler/role_binding.go | 23 ++++++++++++++ .../policy/core/handler/router.go | 4 ++- .../policy/core/repository/mongodb/role.go | 9 ++++++ .../core/repository/mongodb/role_binding.go | 9 ++++++ pkg/microservice/policy/core/service/role.go | 14 ++++++++- .../policy/core/service/role_binding.go | 12 ++++++++ 7 files changed, 98 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 8f5d06e4..e9db1338 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -24,6 +24,10 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) +type deleteRolesArgs struct { + Names []string `json:"names"` +} + func CreateRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -169,10 +173,34 @@ func GetPublicRole(c *gin.Context) { func DeleteRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + name := c.Param("name") projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + ctx.Err = service.DeleteRole(name, projectName, ctx.Logger) - return +} + +func DeleteRoles(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + args := &deleteRolesArgs{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.DeleteRoles(args.Names, projectName, ctx.Logger) } func DeletePublicRole(c *gin.Context) { diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index fe95f5cf..95780198 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -24,6 +24,10 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) +type deleteRoleBindingsArgs struct { + Names []string `json:"names"` +} + func CreateRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -79,6 +83,25 @@ func DeleteRoleBinding(c *gin.Context) { ctx.Err = service.DeleteRoleBinding(name, projectName, ctx.Logger) } +func DeleteRoleBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + args := &deleteRoleBindingsArgs{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.DeleteRoleBindings(args.Names, projectName, ctx.Logger) +} + func DeleteSystemRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 41a67e24..a59a4243 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -26,6 +26,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roles := router.Group("roles") { roles.POST("", CreateRole) + roles.POST("/bulk-delete", DeleteRoles) roles.PATCH("/:name", UpdateRole) roles.PUT("/:name", UpdateOrCreateRole) roles.GET("", ListRoles) @@ -46,7 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { systemRoles := router.Group("system-roles") { systemRoles.POST("", CreateSystemRole) - systemRoles.PUT("/:name",UpdateOrCreateSystemRole) + systemRoles.PUT("/:name", UpdateOrCreateSystemRole) systemRoles.GET("", ListSystemRoles) systemRoles.DELETE("/:name", DeleteSystemRole) } @@ -56,6 +57,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings.POST("", CreateRoleBinding) roleBindings.GET("", ListRoleBindings) roleBindings.DELETE("/:name", DeleteRoleBinding) + roleBindings.POST("/bulk-delete", DeleteRoleBindings) } systemRoleBindings := router.Group("system-rolebindings") diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index 6ac74026..ff13c67f 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -129,6 +129,15 @@ func (c *RoleColl) Delete(name string, projectName string) error { return err } +func (c *RoleColl) DeleteMany(names []string, projectName string) error { + query := bson.M{"namespace": projectName} + if len(names) > 0 { + query["name"] = bson.M{"$in": names} + } + _, err := c.Collection.DeleteMany(context.TODO(), query) + return err +} + func (c *RoleColl) UpdateRole(obj *models.Role) error { // avoid panic issue if obj == nil { diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index fa3ff2c3..831a5abb 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -107,6 +107,15 @@ func (c *RoleBindingColl) Delete(name string, projectName string) error { return err } +func (c *RoleBindingColl) DeleteMany(names []string, projectName string) error { + query := bson.M{"namespace": projectName} + if len(names) > 0 { + query["name"] = bson.M{"$in": names} + } + _, err := c.Collection.DeleteMany(context.TODO(), query) + return err +} + func (c *RoleBindingColl) Create(obj *models.RoleBinding) error { if obj == nil { return fmt.Errorf("nil object") diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index d7379ff9..bb91ecc4 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -121,6 +121,18 @@ func GetRole(ns, name string, _ *zap.SugaredLogger) (*Role, error) { return res, nil } -func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) (err error) { +func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) error { return mongodb.NewRoleColl().Delete(name, projectName) } + +func DeleteRoles(names []string, projectName string, _ *zap.SugaredLogger) error { + if len(names) == 0 { + return nil + } + + if names[0] == "*" { + names = []string{} + } + + return mongodb.NewRoleColl().DeleteMany(names, projectName) +} diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 51ce9dc2..9fbdc5c6 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -71,6 +71,18 @@ func DeleteRoleBinding(name string, projectName string, _ *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Delete(name, projectName) } +func DeleteRoleBindings(names []string, projectName string, _ *zap.SugaredLogger) error { + if len(names) == 0 { + return nil + } + + if names[0] == "*" { + names = []string{} + } + + return mongodb.NewRoleBindingColl().DeleteMany(names, projectName) +} + func createRoleBindingObject(ns string, rb *RoleBinding, logger *zap.SugaredLogger) (*models.RoleBinding, error) { nsRole := ns if rb.Public { -- Gitee From 56146717e49b48b52ee31c19574e2aae3d0184e3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 17:43:01 +0800 Subject: [PATCH 0318/2647] rolebinding fmt Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- pkg/microservice/picket/client/aslan/project.go | 5 ++--- pkg/microservice/picket/core/filter/service/project.go | 5 ++--- pkg/setting/consts.go | 3 ++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 94ba3b8c..fbe696a4 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -511,7 +511,7 @@ func ForkProduct(username, userid, requestID string, args *template.ForkProject, UpdateBy: username, } err = policy.NewDefault().CreateRoleBinding(args.ProductName, &policy.RoleBinding{ - Name: fmt.Sprintf(setting.RoleBindFmt, args.ProductName, userid), + Name: fmt.Sprintf(setting.RoleBindingNameFmt, args.ProductName, userid, args.ProductName), User: userid, Role: setting.Contributor, Public: true, @@ -534,7 +534,7 @@ func UnForkProduct(userID string, username, productName, workflowName, envName, } policyClient := policy.New() - err := policyClient.DeleteRoleBinding(fmt.Sprintf(setting.RoleBindFmt, userID, setting.Contributor), productName) + err := policyClient.DeleteRoleBinding(fmt.Sprintf(setting.RoleBindingNameFmt, userID, setting.Contributor, productName), productName) if err != nil { log.Error("rolebinding delete error") return e.ErrForkProduct diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 8eaed21b..023b3fb3 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -1,7 +1,6 @@ package aslan import ( - "fmt" "net/http" "net/url" @@ -19,8 +18,8 @@ func (c *Client) ListProjects(header http.Header, qs url.Values) ([]byte, error) return res.Body(), nil } -func (c *Client) CreateProject(header http.Header, body []byte, projectName string) ([]byte, error) { - url := fmt.Sprintf("/project/products/%s", projectName) +func (c *Client) CreateProject(header http.Header, body []byte) ([]byte, error) { + url := "/project/products" res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetBody(body)) if err != nil { return nil, err diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 03469e36..a0eab378 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -20,9 +20,8 @@ type allowedProjectsData struct { func CreateProject(header http.Header, body []byte, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { // role binding - roleBindingName := fmt.Sprintf(setting.RoleBindFmt, "*", setting.ReadOnly) + roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) if public { - err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ Name: roleBindingName, User: "*", @@ -32,7 +31,7 @@ func CreateProject(header http.Header, body []byte, projectName string, public b logger.Errorf("create rolebinding: %s err: %s", roleBindingName, err) } - res, err := aslan.New().CreateProject(header, body, projectName) + res, err := aslan.New().CreateProject(header, body) if err != nil { policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName) logger.Errorf("delete rolebinding: %s err: %s", roleBindingName, err) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a6f44661..a739ce5a 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -498,7 +498,8 @@ type RoleType string const ( Contributor RoleType = "contributor" ReadOnly RoleType = "readonly" - RoleBindFmt string = "userid:%s-role:%s" + //scope is in {namespaced, public, system} + RoleBindingNameFmt string = "user:%s,role:%s,project:%s" ) // ModernWorkflowType 自由编排工作流 -- Gitee From 0cb36d9d250ef34408c1811a04f411b04bb1304a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 17:47:08 +0800 Subject: [PATCH 0319/2647] modify error message Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 56373af0..454de030 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -265,7 +265,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product // 检查环境是否存在,envName和productName唯一 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} if _, err := commonrepo.NewProductColl().Find(opt); err == nil { - log.Errorf("[%s][P:%s] duplicate envName in the same product", envName, productName) + log.Errorf("[%s][P:%s] duplicate envName in the same project", envName, productName) return e.ErrCreateEnv.AddDesc(e.DuplicateEnvErrMsg) } -- Gitee From b9f829c2a76b3e9be953f35167794aa133cf3b67 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 28 Oct 2021 17:47:19 +0800 Subject: [PATCH 0320/2647] rolebinding fmt Signed-off-by: mouuii --- pkg/setting/consts.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a739ce5a..88efd0d6 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -496,10 +496,9 @@ const ChartTemplatesPath = "charts" type RoleType string const ( - Contributor RoleType = "contributor" - ReadOnly RoleType = "readonly" - //scope is in {namespaced, public, system} - RoleBindingNameFmt string = "user:%s,role:%s,project:%s" + Contributor RoleType = "contributor" + ReadOnly RoleType = "readonly" + RoleBindingNameFmt string = "user:%s,role:%s,project:%s" ) // ModernWorkflowType 自由编排工作流 -- Gitee From e5251d2b17054e4d6bc0a91d2ecf69fb298cc53e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 17:52:01 +0800 Subject: [PATCH 0321/2647] fix file download failure problem Signed-off-by: liu deyi --- .../service/workflow/pipeline_task.go | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 7a1fdf80..15fa883d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -926,20 +926,12 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog } func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) - tempdir, _ := ioutil.TempDir("", "") - sourcePath := path.Join(tempdir, "artifact") + s3Storage, client, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) + tempDir, _ := ioutil.TempDir("", "") + sourcePath := path.Join(tempDir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { _ = os.MkdirAll(sourcePath, 0777) } - forcedPathStyle := true - if s3Storage.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err != nil { - return nil, fmt.Errorf("failed to create s3 client , error: %v", err) - } for _, artifactFile := range artifactFiles { artifactFileArr := strings.Split(artifactFile, "/") @@ -951,7 +943,6 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL } defer func() { file.Close() - os.Remove(path.Join(sourcePath, artifactFileName)) }() objectKey := s3Storage.GetObjectPath(artifactFile) @@ -964,26 +955,26 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL //将该目录压缩 goCacheManager := new(GoCacheManager) artifactTarFileName := path.Join(sourcePath, "artifact.tar.gz") - err = goCacheManager.Archive(sourcePath, artifactTarFileName) + err := goCacheManager.Archive(sourcePath, artifactTarFileName) if err != nil { return nil, fmt.Errorf("failed to Archive %s %v", sourcePath, err) } defer func() { _ = os.Remove(artifactTarFileName) - _ = os.Remove(tempdir) + _ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) return fileBytes, err } -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, []string, error) { +func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, fis, nil + return nil, nil, fis, nil } if storage.Subfolder != "" { @@ -998,13 +989,13 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) - return nil, fis, nil + return nil, nil, fis, nil } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, fis, nil + return nil, nil, fis, nil } - return storage, files, nil + return storage, client, files, nil } -- Gitee From 5ca45028bf75bfb6b18106f68d0a1d86222148c0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 18:01:55 +0800 Subject: [PATCH 0322/2647] fix file download failure problem Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 15fa883d..c23b0c9c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -945,8 +945,8 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL file.Close() }() - objectKey := s3Storage.GetObjectPath(artifactFile) - err = client.Download(s3Storage.Bucket, objectKey, file.Name()) + //objectKey := s3Storage.GetObjectPath(artifactFile) + err = client.Download(s3Storage.Bucket, artifactFile, file.Name()) if err != nil { return nil, fmt.Errorf("failed to download %s %v", artifactFile, err) } @@ -960,8 +960,8 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL return nil, fmt.Errorf("failed to Archive %s %v", sourcePath, err) } defer func() { - _ = os.Remove(artifactTarFileName) - _ = os.Remove(tempDir) + //_ = os.Remove(artifactTarFileName) + //_ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) -- Gitee From dd7b791d49778bb1aaefacf13d6a58986cd7e454 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 18:09:03 +0800 Subject: [PATCH 0323/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index c23b0c9c..91e1b91c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -933,7 +933,9 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL _ = os.MkdirAll(sourcePath, 0777) } + log.Infof("len(artifactFiles):%d", len(artifactFiles)) for _, artifactFile := range artifactFiles { + log.Infof("artifactFile:%s", artifactFile) artifactFileArr := strings.Split(artifactFile, "/") if len(artifactFileArr) > 1 { artifactFileName := artifactFileArr[len(artifactFileArr)-1] -- Gitee From 6619be59b66fe48534ac58530cec795898f7b3c9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 18:33:27 +0800 Subject: [PATCH 0324/2647] debug code Signed-off-by: liu deyi --- pkg/tool/s3/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index de4076b6..a4958845 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -211,6 +211,7 @@ func (c *Client) ListFiles(bucketName, prefix string, recursive bool) ([]string, for _, item := range output.Contents { itemKey := *item.Key + fmt.Println(fmt.Sprintf("itemKey:%+v", itemKey)) _, fileName := path.Split(itemKey) ret = append(ret, fileName) } -- Gitee From 0dfde048354668f7f5fc9c82655b24b2c8845708 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 18:39:28 +0800 Subject: [PATCH 0325/2647] debug code Signed-off-by: liu deyi --- pkg/tool/s3/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index a4958845..1980c070 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -211,9 +211,9 @@ func (c *Client) ListFiles(bucketName, prefix string, recursive bool) ([]string, for _, item := range output.Contents { itemKey := *item.Key - fmt.Println(fmt.Sprintf("itemKey:%+v", itemKey)) _, fileName := path.Split(itemKey) - ret = append(ret, fileName) + fmt.Println(fmt.Sprintf("itemKey:%+v,fileName:%s", itemKey, fileName)) + ret = append(ret, itemKey) } return ret, nil -- Gitee From f16cad0174920d68bae04ef3ef86909fde1e19b7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 18:55:14 +0800 Subject: [PATCH 0326/2647] remove code Signed-off-by: liu deyi --- .../service/workflow/pipeline_task.go | 33 +++++++++++-------- pkg/tool/s3/client.go | 3 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 91e1b91c..b9b1ce03 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -926,16 +926,22 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog } func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, client, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) + s3Storage, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) tempDir, _ := ioutil.TempDir("", "") sourcePath := path.Join(tempDir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { _ = os.MkdirAll(sourcePath, 0777) } + forcedPathStyle := true + if s3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err != nil { + return nil, fmt.Errorf("failed to create s3 client , error: %v", err) + } - log.Infof("len(artifactFiles):%d", len(artifactFiles)) for _, artifactFile := range artifactFiles { - log.Infof("artifactFile:%s", artifactFile) artifactFileArr := strings.Split(artifactFile, "/") if len(artifactFileArr) > 1 { artifactFileName := artifactFileArr[len(artifactFileArr)-1] @@ -945,10 +951,11 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL } defer func() { file.Close() + os.Remove(path.Join(sourcePath, artifactFileName)) }() - //objectKey := s3Storage.GetObjectPath(artifactFile) - err = client.Download(s3Storage.Bucket, artifactFile, file.Name()) + objectKey := s3Storage.GetObjectPath(artifactFile) + err = client.Download(s3Storage.Bucket, objectKey, file.Name()) if err != nil { return nil, fmt.Errorf("failed to download %s %v", artifactFile, err) } @@ -957,26 +964,26 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL //将该目录压缩 goCacheManager := new(GoCacheManager) artifactTarFileName := path.Join(sourcePath, "artifact.tar.gz") - err := goCacheManager.Archive(sourcePath, artifactTarFileName) + err = goCacheManager.Archive(sourcePath, artifactTarFileName) if err != nil { return nil, fmt.Errorf("failed to Archive %s %v", sourcePath, err) } defer func() { - //_ = os.Remove(artifactTarFileName) - //_ = os.Remove(tempDir) + _ = os.Remove(artifactTarFileName) + _ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) return fileBytes, err } -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { +func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, []string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, nil, fis, nil + return nil, fis, nil } if storage.Subfolder != "" { @@ -991,13 +998,13 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) - return nil, nil, fis, nil + return nil, fis, nil } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, nil, fis, nil + return nil, fis, nil } - return storage, client, files, nil + return storage, files, nil } diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index 1980c070..de4076b6 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -212,8 +212,7 @@ func (c *Client) ListFiles(bucketName, prefix string, recursive bool) ([]string, for _, item := range output.Contents { itemKey := *item.Key _, fileName := path.Split(itemKey) - fmt.Println(fmt.Sprintf("itemKey:%+v,fileName:%s", itemKey, fileName)) - ret = append(ret, itemKey) + ret = append(ret, fileName) } return ret, nil -- Gitee From 1fe39b99b40657565558decd1f0cc40fbb207b96 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 18:56:05 +0800 Subject: [PATCH 0327/2647] remove code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index b9b1ce03..7a1fdf80 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -927,8 +927,8 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { s3Storage, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) - tempDir, _ := ioutil.TempDir("", "") - sourcePath := path.Join(tempDir, "artifact") + tempdir, _ := ioutil.TempDir("", "") + sourcePath := path.Join(tempdir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { _ = os.MkdirAll(sourcePath, 0777) } @@ -970,7 +970,7 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL } defer func() { _ = os.Remove(artifactTarFileName) - _ = os.Remove(tempDir) + _ = os.Remove(tempdir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) -- Gitee From 257f594c6932d46ab268582d131cbc1405ecf8fc Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 28 Oct 2021 19:01:15 +0800 Subject: [PATCH 0328/2647] remove orgId Signed-off-by: lou --- .../repository/models/delivery_version.go | 1 - .../repository/mongodb/delivery_version.go | 13 +++++---- .../repository/mongodb/registry_namespace.go | 22 --------------- .../aslan/core/common/service/delivery.go | 6 ++--- .../aslan/core/common/service/workflow.go | 2 +- .../aslan/core/delivery/handler/product.go | 12 ++------- .../aslan/core/delivery/handler/version.go | 27 +++---------------- .../aslan/core/delivery/service/product.go | 4 +-- .../aslan/core/delivery/service/version.go | 3 +-- .../workflow/service/workflow/nsq_handlers.go | 2 +- 10 files changed, 18 insertions(+), 74 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 785bc638..459d7f96 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -22,7 +22,6 @@ import ( type DeliveryVersion struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - OrgID int `bson:"org_id" json:"orgId"` Version string `bson:"version" json:"version"` ProductName string `bson:"product_name" json:"productName"` WorkflowName string `bson:"workflow_name" json:"workflowName"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index 69b56e10..bae5fc66 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -33,7 +33,6 @@ import ( type DeliveryVersionArgs struct { ID string `json:"id"` - OrgID int `json:"orgId"` ProductName string `json:"productName"` WorkflowName string `json:"workflowName"` TaskID int `json:"taskId"` @@ -91,7 +90,7 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver } var resp []*models.DeliveryVersion - query := bson.M{"org_id": args.OrgID, "deleted_at": 0} + query := bson.M{"deleted_at": 0} if args.ProductName != "" { query["product_name"] = args.ProductName } @@ -137,9 +136,9 @@ func (c *DeliveryVersionColl) Delete(id string) error { return err } -func (c *DeliveryVersionColl) ListDeliveryVersions(productName string, orgID int) ([]*models.DeliveryVersion, error) { +func (c *DeliveryVersionColl) ListDeliveryVersions(productName string) ([]*models.DeliveryVersion, error) { var resp []*models.DeliveryVersion - query := bson.M{"org_id": orgID, "deleted_at": 0} + query := bson.M{"deleted_at": 0} if productName != "" { query["product_name"] = productName } @@ -167,7 +166,7 @@ func (c *DeliveryVersionColl) Get(args *DeliveryVersionArgs) (*models.DeliveryVe if args.ID != "" { query = bson.M{"_id": args.ID, "deleted_at": 0} } else { - query = bson.M{"org_id": args.OrgID, "product_name": args.ProductName, "workflow_name": args.WorkflowName, "task_id": args.TaskID, "deleted_at": 0} + query = bson.M{"product_name": args.ProductName, "workflow_name": args.WorkflowName, "task_id": args.TaskID, "deleted_at": 0} } err := c.FindOne(context.TODO(), query).Decode(&resp) @@ -205,9 +204,9 @@ func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { return err } -func (c *DeliveryVersionColl) FindProducts(orgID int) ([]string, error) { +func (c *DeliveryVersionColl) FindProducts() ([]string, error) { resp := make([]string, 0) - query := bson.M{"org_id": orgID, "deleted_at": 0} + query := bson.M{"deleted_at": 0} ret, err := c.Distinct(context.TODO(), "product_name", query) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go index 27c67248..539da8c3 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go @@ -148,28 +148,6 @@ func (r *RegistryNamespaceColl) FindAll(opt *FindRegOps) ([]*models.RegistryName return resp, err } -func (r *RegistryNamespaceColl) List(orgID int, regType string) ([]*models.RegistryNamespace, error) { - query := bson.M{"org_id": orgID} - if regType != "" { - query["reg_type"] = regType - } - - ctx := context.Background() - opts := options.Find() - resp := make([]*models.RegistryNamespace, 0) - cursor, err := r.Collection.Find(ctx, query, opts) - if err != nil { - return nil, err - } - - err = cursor.All(ctx, &resp) - if err != nil { - return nil, err - } - - return resp, err -} - func (r *RegistryNamespaceColl) Update(id string, args *models.RegistryNamespace) error { if args == nil { return errors.New("nil Install") diff --git a/pkg/microservice/aslan/core/common/service/delivery.go b/pkg/microservice/aslan/core/common/service/delivery.go index 91d40737..74baaf6f 100644 --- a/pkg/microservice/aslan/core/common/service/delivery.go +++ b/pkg/microservice/aslan/core/common/service/delivery.go @@ -36,7 +36,7 @@ import ( ) func DeleteDeliveryInfos(productName string, log *zap.SugaredLogger) error { - deliveryVersions, err := mongodb.NewDeliveryVersionColl().ListDeliveryVersions(productName, 1) + deliveryVersions, err := mongodb.NewDeliveryVersionColl().ListDeliveryVersions(productName) if err != nil { log.Errorf("delete DeleteDeliveryInfo error: %v", err) return e.ErrDeleteDeliveryVersion @@ -71,9 +71,8 @@ func DeleteDeliveryInfos(productName string, log *zap.SugaredLogger) error { return nil } -func AddDeliveryVersion(orgID, taskID int, productName, workflowName string, pipelineTask *taskmodels.Task, logger *zap.SugaredLogger) error { +func AddDeliveryVersion(taskID int, productName, workflowName string, pipelineTask *taskmodels.Task, logger *zap.SugaredLogger) error { deliveryVersionArgs := &mongodb.DeliveryVersionArgs{ - OrgID: orgID, ProductName: productName, WorkflowName: workflowName, TaskID: taskID, @@ -87,7 +86,6 @@ func AddDeliveryVersion(orgID, taskID int, productName, workflowName string, pip } deliveryVersion := new(commonmodels.DeliveryVersion) - deliveryVersion.OrgID = orgID deliveryVersion.WorkflowName = workflowName deliveryVersion.TaskID = taskID deliveryVersion.ProductName = productName diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 325a3f3d..beb0c351 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -112,7 +112,7 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log.Errorf("PipelineTaskV2.DeleteByPipelineName error: %v", err) } - if deliveryVersions, err := mongodb.NewDeliveryVersionColl().Find(&mongodb.DeliveryVersionArgs{OrgID: 1, WorkflowName: workflowName}); err == nil { + if deliveryVersions, err := mongodb.NewDeliveryVersionColl().Find(&mongodb.DeliveryVersionArgs{WorkflowName: workflowName}); err == nil { for _, deliveryVersion := range deliveryVersions { if err := mongodb.NewDeliveryVersionColl().Delete(deliveryVersion.ID.Hex()); err != nil { log.Errorf("DeleteWorkflow.DeliveryVersion.Delete error: %v", err) diff --git a/pkg/microservice/aslan/core/delivery/handler/product.go b/pkg/microservice/aslan/core/delivery/handler/product.go index ccdf392a..ad252633 100644 --- a/pkg/microservice/aslan/core/delivery/handler/product.go +++ b/pkg/microservice/aslan/core/delivery/handler/product.go @@ -17,8 +17,6 @@ limitations under the License. package handler import ( - "strconv" - "github.com/gin-gonic/gin" deliveryservice "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" @@ -29,14 +27,8 @@ import ( func ListDeliveryProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - //params validate - orgIDStr := c.Query("orgId") - orgID, err := strconv.Atoi(orgIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("orgId can't be empty!") - return - } - ctx.Resp, ctx.Err = deliveryservice.FindDeliveryProduct(orgID, ctx.Logger) + + ctx.Resp, ctx.Err = deliveryservice.FindDeliveryProduct(ctx.Logger) } func GetProductByDeliveryInfo(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 920e2e52..44ea5e99 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -96,16 +96,10 @@ type DeliverySecurityStatsInfo struct { func ListDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - //params validate - orgIDStr := c.Query("orgId") - orgID, err := strconv.Atoi(orgIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("orgId can't be empty!") - return - } taskIDStr := c.Query("taskId") var taskID = 0 + var err error if taskIDStr != "" { taskID, err = strconv.Atoi(taskIDStr) if err != nil { @@ -143,7 +137,6 @@ func ListDeliveryVersion(c *gin.Context) { serviceName := c.Query("serviceName") version := new(commonrepo.DeliveryVersionArgs) - version.OrgID = orgID version.ProductName = c.Query("projectName") version.WorkflowName = c.Query("workflowName") version.TaskID = taskID @@ -286,15 +279,8 @@ type DeliveryFileInfo struct { func ListPackagesVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - //params validate - orgIDStr := c.Query("orgId") - orgID, err := strconv.Atoi(orgIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("orgId can't be empty!") - return - } + version := &commonrepo.DeliveryVersionArgs{ - OrgID: orgID, ProductName: c.Query("projectName"), } deliveryVersions, err := deliveryservice.FindDeliveryVersion(version, ctx.Logger) @@ -383,12 +369,5 @@ func ListDeliveryServiceNames(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() productName := c.Query("projectName") - orgIDStr := c.Query("orgId") - orgID, err := strconv.Atoi(orgIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("orgId can't be empty!") - return - } - - ctx.Resp, ctx.Err = deliveryservice.ListDeliveryServiceNames(orgID, productName, ctx.Logger) + ctx.Resp, ctx.Err = deliveryservice.ListDeliveryServiceNames(productName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/delivery/service/product.go b/pkg/microservice/aslan/core/delivery/service/product.go index 9b2fdf8b..d9c4e561 100644 --- a/pkg/microservice/aslan/core/delivery/service/product.go +++ b/pkg/microservice/aslan/core/delivery/service/product.go @@ -24,8 +24,8 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func FindDeliveryProduct(orgID int, log *zap.SugaredLogger) ([]string, error) { - productNames, err := commonrepo.NewDeliveryVersionColl().FindProducts(orgID) +func FindDeliveryProduct(log *zap.SugaredLogger) ([]string, error) { + productNames, err := commonrepo.NewDeliveryVersionColl().FindProducts() if err != nil { log.Errorf("find FindDeliveryProduct error: %v", err) return []string{}, e.ErrFindDeliveryProducts diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index d9498b64..1c013f68 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -52,11 +52,10 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } -func ListDeliveryServiceNames(orgID int, productName string, log *zap.SugaredLogger) ([]string, error) { +func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]string, error) { serviceNames := sets.String{} version := new(commonrepo.DeliveryVersionArgs) - version.OrgID = orgID version.ProductName = productName deliveryVersions, err := FindDeliveryVersion(version, log) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index fbfa0e5c..0b2692b2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -691,7 +691,7 @@ func (h *TaskAckHandler) createVersion(pt *task.Task) error { } if isDeploy { //版本交付 - return commonservice.AddDeliveryVersion(1, int(pt.TaskID), pt.ProductName, pt.PipelineName, pt, log.SugaredLogger()) + return commonservice.AddDeliveryVersion(int(pt.TaskID), pt.ProductName, pt.PipelineName, pt, log.SugaredLogger()) } } } -- Gitee From e35bd49c32a1b7ed69ea099181dedcd72169cddf Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 19:11:11 +0800 Subject: [PATCH 0329/2647] fix artifact download bug Signed-off-by: liu deyi --- .../service/workflow/pipeline_task.go | 33 +++++++------------ .../workflow/testing/service/workspace.go | 17 ++++++---- pkg/tool/s3/client.go | 4 +-- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 7a1fdf80..0cd9cd2c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -926,20 +926,12 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog } func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) - tempdir, _ := ioutil.TempDir("", "") - sourcePath := path.Join(tempdir, "artifact") + s3Storage, client, artifactFiles, _ := GetS3AndClient(pipelineName, "", taskID, log) + tempDir, _ := ioutil.TempDir("", "") + sourcePath := path.Join(tempDir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { _ = os.MkdirAll(sourcePath, 0777) } - forcedPathStyle := true - if s3Storage.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err != nil { - return nil, fmt.Errorf("failed to create s3 client , error: %v", err) - } for _, artifactFile := range artifactFiles { artifactFileArr := strings.Split(artifactFile, "/") @@ -949,10 +941,7 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL if err != nil { return nil, fmt.Errorf("failed to create file %s %v", artifactFileName, err) } - defer func() { - file.Close() - os.Remove(path.Join(sourcePath, artifactFileName)) - }() + defer file.Close() objectKey := s3Storage.GetObjectPath(artifactFile) err = client.Download(s3Storage.Bucket, objectKey, file.Name()) @@ -964,26 +953,26 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL //将该目录压缩 goCacheManager := new(GoCacheManager) artifactTarFileName := path.Join(sourcePath, "artifact.tar.gz") - err = goCacheManager.Archive(sourcePath, artifactTarFileName) + err := goCacheManager.Archive(sourcePath, artifactTarFileName) if err != nil { return nil, fmt.Errorf("failed to Archive %s %v", sourcePath, err) } defer func() { _ = os.Remove(artifactTarFileName) - _ = os.Remove(tempdir) + _ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) return fileBytes, err } -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, []string, error) { +func GetS3AndClient(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, fis, nil + return nil, nil, fis, nil } if storage.Subfolder != "" { @@ -998,13 +987,13 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) - return nil, fis, nil + return nil, nil, fis, nil } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, fis, nil + return nil, nil, fis, nil } - return storage, files, nil + return storage, client, files, nil } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index 7f791612..a8a3bfa0 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -18,22 +18,22 @@ package service import ( "fmt" + "path" "go.uber.org/zap" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" ) -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*commonmodels.S3Storage, []string, error) { +func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) ([]string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, fis, nil + return fis, nil } if storage.Subfolder != "" { storage.Subfolder = fmt.Sprintf("%s/%s/%d/%s", storage.Subfolder, pipelineName, taskID, "artifact") @@ -47,13 +47,18 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo create s3 client err:%v", err) - return nil, fis, nil + return fis, nil } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, fis, nil + return fis, nil } - return storage.S3Storage, files, nil + resp := make([]string, len(files)) + for _, file := range files { + _, fileName := path.Split(file) + resp = append(resp, fileName) + } + return resp, nil } diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index de4076b6..cdabe6c6 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -19,7 +19,6 @@ package s3 import ( "fmt" "os" - "path" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -211,8 +210,7 @@ func (c *Client) ListFiles(bucketName, prefix string, recursive bool) ([]string, for _, item := range output.Contents { itemKey := *item.Key - _, fileName := path.Split(itemKey) - ret = append(ret, fileName) + ret = append(ret, itemKey) } return ret, nil -- Gitee From e4a41175961f84bd15826f4fc6777a366dce755d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 19:13:30 +0800 Subject: [PATCH 0330/2647] fix artifact download bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 454de030..56373af0 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -265,7 +265,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product // 检查环境是否存在,envName和productName唯一 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} if _, err := commonrepo.NewProductColl().Find(opt); err == nil { - log.Errorf("[%s][P:%s] duplicate envName in the same project", envName, productName) + log.Errorf("[%s][P:%s] duplicate envName in the same product", envName, productName) return e.ErrCreateEnv.AddDesc(e.DuplicateEnvErrMsg) } -- Gitee From 0941d1773bbb9770308fe433592f79b4c7b77ba7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 19:16:55 +0800 Subject: [PATCH 0331/2647] fix artifact download bug Signed-off-by: liu deyi --- .../aslan/core/workflow/testing/handler/workspace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go b/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go index 3c8ef86f..69171697 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go @@ -38,5 +38,5 @@ func GetTestArtifactInfo(c *gin.Context) { dir := c.Query("dir") - _, ctx.Resp, ctx.Err = service.GetTestArtifactInfo(c.Param("pipelineName"), dir, taskID, ctx.Logger) + ctx.Resp, ctx.Err = service.GetTestArtifactInfo(c.Param("pipelineName"), dir, taskID, ctx.Logger) } -- Gitee From 1f3edb5a18c8cdb8a86b74895e6a5c55eb22764e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 19:23:54 +0800 Subject: [PATCH 0332/2647] fix artifact download bug Signed-off-by: liu deyi --- .../aslan/core/workflow/testing/service/workspace.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index a8a3bfa0..d9dfb5ef 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -55,7 +55,7 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) return fis, nil } - resp := make([]string, len(files)) + resp := make([]string, 0) for _, file := range files { _, fileName := path.Split(file) resp = append(resp, fileName) -- Gitee From ab81bdf8283407f06ee1d43c88a8d82c70adc1aa Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 19:45:19 +0800 Subject: [PATCH 0333/2647] fix artifact download bug Signed-off-by: liu deyi --- .../service/workflow/pipeline_task.go | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 0cd9cd2c..d888045c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -926,7 +926,10 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog } func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, client, artifactFiles, _ := GetS3AndClient(pipelineName, "", taskID, log) + s3Storage, client, artifactFiles, err := GetS3AndClient(pipelineName, "", taskID, log) + if err != nil { + return nil, fmt.Errorf("download artifact err: %s", err) + } tempDir, _ := ioutil.TempDir("", "") sourcePath := path.Join(tempDir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { @@ -941,7 +944,9 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL if err != nil { return nil, fmt.Errorf("failed to create file %s %v", artifactFileName, err) } - defer file.Close() + defer func() { + _ = file.Close() + }() objectKey := s3Storage.GetObjectPath(artifactFile) err = client.Download(s3Storage.Bucket, objectKey, file.Name()) @@ -953,13 +958,13 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL //将该目录压缩 goCacheManager := new(GoCacheManager) artifactTarFileName := path.Join(sourcePath, "artifact.tar.gz") - err := goCacheManager.Archive(sourcePath, artifactTarFileName) + err = goCacheManager.Archive(sourcePath, artifactTarFileName) if err != nil { return nil, fmt.Errorf("failed to Archive %s %v", sourcePath, err) } defer func() { - _ = os.Remove(artifactTarFileName) - _ = os.Remove(tempDir) + //_ = os.Remove(artifactTarFileName) + //_ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) @@ -972,7 +977,7 @@ func GetS3AndClient(pipelineName, dir string, taskID int64, log *zap.SugaredLogg storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, nil, fis, nil + return nil, nil, fis, err } if storage.Subfolder != "" { @@ -987,13 +992,13 @@ func GetS3AndClient(pipelineName, dir string, taskID int64, log *zap.SugaredLogg client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) - return nil, nil, fis, nil + return nil, nil, fis, err } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, nil, fis, nil + return nil, nil, fis, err } return storage, client, files, nil } -- Gitee From 5b5a26a8d3c49ee05c722f60037ea8711a0b9d7c Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 28 Oct 2021 19:57:29 +0800 Subject: [PATCH 0334/2647] delete rolebindings when role is deleted Signed-off-by: lou --- .../policy/core/repository/mongodb/role_binding.go | 11 +++++++++++ pkg/microservice/policy/core/service/role.go | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 831a5abb..18789044 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -113,6 +113,17 @@ func (c *RoleBindingColl) DeleteMany(names []string, projectName string) error { query["name"] = bson.M{"$in": names} } _, err := c.Collection.DeleteMany(context.TODO(), query) + + return err +} +func (c *RoleBindingColl) DeleteByRole(roleName string, projectName string) error { + query := bson.M{"role_ref.name": roleName, "role_ref.namespace": projectName} + // if projectName == "", delete all rolebindings in all namespaces + if projectName != "" { + query["namespace"] = projectName + } + _, err := c.Collection.DeleteMany(context.TODO(), query) + return err } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index bb91ecc4..8a0f3b11 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -121,8 +121,13 @@ func GetRole(ns, name string, _ *zap.SugaredLogger) (*Role, error) { return res, nil } -func DeleteRole(name string, projectName string, _ *zap.SugaredLogger) error { - return mongodb.NewRoleColl().Delete(name, projectName) +func DeleteRole(name string, projectName string, logger *zap.SugaredLogger) error { + err := mongodb.NewRoleColl().Delete(name, projectName) + if err != nil { + logger.Errorf("Failed to delete role %s in project %s, err: %s", name, projectName, err) + } + + return mongodb.NewRoleBindingColl().DeleteByRole(name, projectName) } func DeleteRoles(names []string, projectName string, _ *zap.SugaredLogger) error { -- Gitee From 60b0d0da28e47f09cdb3e6c433b7570b4273ceaf Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 20:13:01 +0800 Subject: [PATCH 0335/2647] fix artifact download bug Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index d888045c..e5615c90 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -937,6 +937,7 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL } for _, artifactFile := range artifactFiles { + log.Infof("artifactFile:%+v", artifactFile) artifactFileArr := strings.Split(artifactFile, "/") if len(artifactFileArr) > 1 { artifactFileName := artifactFileArr[len(artifactFileArr)-1] -- Gitee From cbcfe436f87c7c1aca60fa5790c4ea2ed4b7e76e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 20:18:57 +0800 Subject: [PATCH 0336/2647] fix artifact download bug Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index e5615c90..c0d479d9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -950,7 +950,8 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL }() objectKey := s3Storage.GetObjectPath(artifactFile) - err = client.Download(s3Storage.Bucket, objectKey, file.Name()) + log.Infof("objectKey:%s", objectKey) + err = client.Download(s3Storage.Bucket, artifactFile, file.Name()) if err != nil { return nil, fmt.Errorf("failed to download %s %v", artifactFile, err) } -- Gitee From 933c1d0ed7744bde9680a543c7e1d8f286189a67 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 20:26:02 +0800 Subject: [PATCH 0337/2647] remove debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index c0d479d9..27c51a41 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -937,7 +937,6 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL } for _, artifactFile := range artifactFiles { - log.Infof("artifactFile:%+v", artifactFile) artifactFileArr := strings.Split(artifactFile, "/") if len(artifactFileArr) > 1 { artifactFileName := artifactFileArr[len(artifactFileArr)-1] @@ -949,8 +948,6 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL _ = file.Close() }() - objectKey := s3Storage.GetObjectPath(artifactFile) - log.Infof("objectKey:%s", objectKey) err = client.Download(s3Storage.Bucket, artifactFile, file.Name()) if err != nil { return nil, fmt.Errorf("failed to download %s %v", artifactFile, err) @@ -965,8 +962,8 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL return nil, fmt.Errorf("failed to Archive %s %v", sourcePath, err) } defer func() { - //_ = os.Remove(artifactTarFileName) - //_ = os.Remove(tempDir) + _ = os.Remove(artifactTarFileName) + _ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) -- Gitee From 39f47578c3e35abf37a8945a9e3792e80865c9b1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 20:59:34 +0800 Subject: [PATCH 0338/2647] rename function name Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 27c51a41..f8d594e5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -926,7 +926,7 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog } func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, client, artifactFiles, err := GetS3AndClient(pipelineName, "", taskID, log) + s3Storage, client, artifactFiles, err := GetArtifactAndS3Info(pipelineName, "", taskID, log) if err != nil { return nil, fmt.Errorf("download artifact err: %s", err) } @@ -970,7 +970,7 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL return fileBytes, err } -func GetS3AndClient(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { +func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() -- Gitee From fa6aa7829902e896a6cefb2788a41b8d8c0a24e6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 28 Oct 2021 21:26:38 +0800 Subject: [PATCH 0339/2647] return err Signed-off-by: liu deyi --- .../aslan/core/workflow/testing/service/workspace.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index d9dfb5ef..da56a35e 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -33,7 +33,7 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return fis, nil + return fis, err } if storage.Subfolder != "" { storage.Subfolder = fmt.Sprintf("%s/%s/%d/%s", storage.Subfolder, pipelineName, taskID, "artifact") @@ -47,13 +47,13 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo create s3 client err:%v", err) - return fis, nil + return fis, err } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return fis, nil + return fis, err } resp := make([]string, 0) for _, file := range files { -- Gitee From b65fb5afff01811f9bd9f0dd5df748ba4b977762 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 10:27:21 +0800 Subject: [PATCH 0340/2647] modify log download logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/log.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 7d8c5081..c4e9ce00 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -106,8 +106,7 @@ func getContainerLogFromS3(pipelineName, filenamePrefix string, taskID int64, lo if len(fileList) == 0 { return "", nil } - objectKey := storage.GetObjectPath(fileList[0]) - err = client.Download(storage.Bucket, objectKey, tempFile) + err = client.Download(storage.Bucket, fileList[0], tempFile) if err != nil { log.Errorf("GetContainerLogFromS3 Download err:%v", err) return "", err -- Gitee From b0dc33697eed13a094262d0ddf439c5e97395eb1 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 29 Oct 2021 11:15:37 +0800 Subject: [PATCH 0341/2647] list rolebindings in picket --- pkg/config/config.go | 9 ++++ .../aslan/core/project/service/product.go | 8 ++-- .../picket/client/policy/client.go | 25 ++++++++++ .../picket/client/policy/rolebinding.go | 27 +++++++++++ .../picket/core/filter/handler/rolebinding.go | 22 +++++++++ .../picket/core/filter/handler/router.go | 5 ++ .../picket/core/filter/service/project.go | 23 +++++---- .../picket/core/filter/service/rolebinding.go | 47 +++++++++++++++++++ .../core/repository/mongodb/role_binding.go | 15 ++++++ .../core/service/bundle/exemption_urls.go | 22 ++++++++- pkg/microservice/policy/core/service/role.go | 14 +++++- pkg/setting/types.go | 11 +++-- pkg/shared/client/policy/policy.go | 20 +++++--- pkg/shared/client/user/client.go | 41 ++++++++++++++++ pkg/shared/client/user/user.go | 32 +++++++++++++ 15 files changed, 297 insertions(+), 24 deletions(-) create mode 100644 pkg/microservice/picket/client/policy/client.go create mode 100644 pkg/microservice/picket/client/policy/rolebinding.go create mode 100644 pkg/microservice/picket/core/filter/handler/rolebinding.go create mode 100644 pkg/microservice/picket/core/filter/service/rolebinding.go create mode 100644 pkg/shared/client/user/client.go create mode 100644 pkg/shared/client/user/user.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 35ee51b9..88dd3561 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -190,6 +190,15 @@ func PolicyServiceAddress() string { return GetServiceAddress(s.Name, s.Port) } +func UserServiceInfo() *setting.ServiceInfo { + return GetServiceByCode(setting.User) +} + +func UserServiceAddress() string { + s := UserServiceInfo() + return GetServiceAddress(s.Name, s.Port) +} + func GetServiceAddress(name string, port int32) string { return fmt.Sprintf("http://%s:%d", name, port) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index fbe696a4..78a64f51 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -397,7 +397,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return nil } -func ForkProduct(username, userid, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { +func ForkProduct(username, uid, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { @@ -511,9 +511,9 @@ func ForkProduct(username, userid, requestID string, args *template.ForkProject, UpdateBy: username, } err = policy.NewDefault().CreateRoleBinding(args.ProductName, &policy.RoleBinding{ - Name: fmt.Sprintf(setting.RoleBindingNameFmt, args.ProductName, userid, args.ProductName), - User: userid, - Role: setting.Contributor, + Name: fmt.Sprintf(setting.RoleBindingNameFmt, args.ProductName, uid, args.ProductName), + UID: uid, + Role: string(setting.Contributor), Public: true, }) if err != nil { diff --git a/pkg/microservice/picket/client/policy/client.go b/pkg/microservice/picket/client/policy/client.go new file mode 100644 index 00000000..18b72463 --- /dev/null +++ b/pkg/microservice/picket/client/policy/client.go @@ -0,0 +1,25 @@ +package policy + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.PolicyServiceAddress() + + c := httpclient.New( + httpclient.SetHostURL(host + "/api/v1"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go new file mode 100644 index 00000000..faa2e2a7 --- /dev/null +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -0,0 +1,27 @@ +package policy + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type RoleBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Public bool `json:"public"` +} + +func (c *Client) ListRoleBindings(header http.Header, qs url.Values) ([]*RoleBinding, error) { + url := "/rolebindings" + + res := make([]*RoleBinding, 0) + _, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/pkg/microservice/picket/core/filter/handler/rolebinding.go b/pkg/microservice/picket/core/filter/handler/rolebinding.go new file mode 100644 index 00000000..9a69939c --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/rolebinding.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func ListRoleBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Resp, ctx.Err = service.ListRoleBindings(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index f1a010d3..2f4bcc95 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -33,4 +33,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflows.GET("testName/:testName", ListTestWorkflows) } + + rolebindings := router.Group("rolebindings") + { + rolebindings.GET("", ListRoleBindings) + } } diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index a0eab378..dbeb42d9 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -22,21 +22,28 @@ func CreateProject(header http.Header, body []byte, projectName string, public b // role binding roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) if public { - err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ + if err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ Name: roleBindingName, - User: "*", - Role: setting.ReadOnly, + UID: "*", + Role: string(setting.ReadOnly), Public: true, - }) - logger.Errorf("create rolebinding: %s err: %s", roleBindingName, err) + }); err != nil { + logger.Errorf("Failed to create rolebinding %s, err: %s", roleBindingName, err) + return nil, err + } } res, err := aslan.New().CreateProject(header, body) if err != nil { - policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName) - logger.Errorf("delete rolebinding: %s err: %s", roleBindingName, err) + logger.Errorf("Failed to create project %s, err: %s", projectName, err) + if err1 := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err1 != nil { + logger.Warnf("Failed to delete role binding, err: %s", err1) + } + + return nil, err } - return res, err + + return res, nil } func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go new file mode 100644 index 00000000..9cf53c12 --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -0,0 +1,47 @@ +package service + +import ( + "net/http" + "net/url" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/policy" + "github.com/koderover/zadig/pkg/shared/client/user" +) + +type roleBinding struct { + *policy.RoleBinding + Username string `json:"username"` +} + +func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*roleBinding, error) { + rbs, err := policy.New().ListRoleBindings(header, qs) + if err != nil { + logger.Errorf("Failed to list rolebindings, err: %s", err) + return nil, err + } + + var uids []string + uidToRoleBinding := make(map[string]*roleBinding) + for _, rb := range rbs { + uids = append(uids, rb.UID) + uidToRoleBinding[rb.UID] = &roleBinding{RoleBinding: rb} + } + + users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uids}) + if err != nil { + logger.Errorf("Failed to list users, err: %s", err) + return nil, err + } + + var res []*roleBinding + for _, u := range users { + if rb, ok := uidToRoleBinding[u.UID]; ok { + rb.Username = u.Name + res = append(res, rb) + } + } + + return res, nil +} diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 18789044..0bd9ee6f 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -116,6 +116,7 @@ func (c *RoleBindingColl) DeleteMany(names []string, projectName string) error { return err } + func (c *RoleBindingColl) DeleteByRole(roleName string, projectName string) error { query := bson.M{"role_ref.name": roleName, "role_ref.namespace": projectName} // if projectName == "", delete all rolebindings in all namespaces @@ -127,6 +128,20 @@ func (c *RoleBindingColl) DeleteByRole(roleName string, projectName string) erro return err } +func (c *RoleBindingColl) DeleteByRoles(roleNames []string, projectName string) error { + if projectName == "" { + return fmt.Errorf("projectName is empty") + } + if len(roleNames) == 0 { + return nil + } + + query := bson.M{"role_ref.name": bson.M{"$in": roleNames}, "role_ref.namespace": projectName, "namespace": projectName} + _, err := c.Collection.DeleteMany(context.TODO(), query) + + return err +} + func (c *RoleBindingColl) Create(obj *models.RoleBinding) error { if obj == nil { return fmt.Errorf("nil object") diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 693b04a5..809df627 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -68,6 +68,10 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/aslan/project/products"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/picket/projects"}, + }, { Methods: []string{"DELETE"}, Endpoints: []string{"api/aslan/project/products/?*"}, @@ -226,7 +230,7 @@ var systemAdminURLs = []*policyRule{ }, } -// actions which are allowed for system admins only. +// actions which are allowed for project admins. var projectAdminURLs = []*policyRule{ { Methods: []string{"PUT"}, @@ -240,6 +244,10 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/v1/users"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/users/search"}, + }, { Methods: []string{"GET"}, Endpoints: []string{"api/v1/roles"}, @@ -256,6 +264,10 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"PUT", "DELETE"}, Endpoints: []string{"api/v1/roles/?*"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/roles/bulk-delete"}, + }, { Methods: []string{"GET"}, Endpoints: []string{"api/v1/public-roles"}, @@ -268,10 +280,18 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"GET", "POST"}, Endpoints: []string{"api/v1/rolebindings"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/picket/rolebindings"}, + }, { Methods: []string{"DELETE"}, Endpoints: []string{"api/v1/rolebindings/?*"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/rolebindings/bulk-delete"}, + }, } var adminURLs = append(systemAdminURLs, projectAdminURLs...) diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 8a0f3b11..1735b7f2 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -125,19 +125,29 @@ func DeleteRole(name string, projectName string, logger *zap.SugaredLogger) erro err := mongodb.NewRoleColl().Delete(name, projectName) if err != nil { logger.Errorf("Failed to delete role %s in project %s, err: %s", name, projectName, err) + return err } return mongodb.NewRoleBindingColl().DeleteByRole(name, projectName) } -func DeleteRoles(names []string, projectName string, _ *zap.SugaredLogger) error { +func DeleteRoles(names []string, projectName string, logger *zap.SugaredLogger) error { if len(names) == 0 { return nil } + if projectName == "" { + return fmt.Errorf("projectName is empty") + } if names[0] == "*" { names = []string{} } - return mongodb.NewRoleColl().DeleteMany(names, projectName) + err := mongodb.NewRoleColl().DeleteMany(names, projectName) + if err != nil { + logger.Errorf("Failed to delete roles %s in project %s, err: %s", names, projectName, err) + return err + } + + return mongodb.NewRoleBindingColl().DeleteByRoles(names, projectName) } diff --git a/pkg/setting/types.go b/pkg/setting/types.go index a8293f08..746c65d1 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -49,6 +49,7 @@ const ( OPA // 12 Policy // 13 Config // 14 + User // 15 ) type ServiceInfo struct { @@ -109,8 +110,12 @@ var Services = map[int]*ServiceInfo{ Name: "policy", Port: 80, }, - Config:{ - Name:"config", - Port:80, + Config: { + Name: "config", + Port: 80, + }, + User: { + Name: "user", + Port: 80, }, } diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index be7a8cb0..8401f878 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -3,7 +3,6 @@ package policy import ( "fmt" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -26,6 +25,13 @@ type ActionRule struct { Endpoint string `json:"endpoint"` } +type RoleBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Public bool `json:"public"` +} + func (c *Client) CreateOrUpdatePolicy(p *Policy) error { url := fmt.Sprintf("/policies/%s", p.Resource) @@ -34,11 +40,13 @@ func (c *Client) CreateOrUpdatePolicy(p *Policy) error { return err } -type RoleBinding struct { - Name string - User string - Role setting.RoleType - Public bool +func (c *Client) ListRoleBindings(projectName string) ([]*RoleBinding, error) { + url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) + + res := make([]*RoleBinding, 0) + _, err := c.Get(url, httpclient.SetResult(&res)) + + return res, err } func (c *Client) CreateRoleBinding(projectName string, roleBinding *RoleBinding) error { diff --git a/pkg/shared/client/user/client.go b/pkg/shared/client/user/client.go new file mode 100644 index 00000000..5e6d8ac1 --- /dev/null +++ b/pkg/shared/client/user/client.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package user + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.UserServiceAddress() + + c := httpclient.New( + httpclient.SetHostURL(host + "/api/v1"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/user/user.go b/pkg/shared/client/user/user.go new file mode 100644 index 00000000..bcec5568 --- /dev/null +++ b/pkg/shared/client/user/user.go @@ -0,0 +1,32 @@ +package user + +import ( + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type User struct { + UID string `json:"uid"` + Name string `json:"name"` + Email string `json:"email"` + Phone string `json:"phone"` +} + +type usersResp struct { + Users []*User `json:"users"` +} + +type SearchArgs struct { + UIDs []string `json:"uids"` +} + +func (c *Client) ListUsers(args *SearchArgs) ([]*User, error) { + url := "/users/search" + + res := &usersResp{} + _, err := c.Post(url, httpclient.SetResult(res), httpclient.SetBody(args)) + if err != nil { + return nil, err + } + + return res.Users, err +} -- Gitee From 5edc396d463a935f4f2adb6b119c61650f8ce845 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 15:14:33 +0800 Subject: [PATCH 0342/2647] fix body read twice bug Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/handler/project.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 6d5dff48..4b2427ee 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -2,6 +2,7 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -25,11 +26,16 @@ func CreateProject(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := new(CreateProjectReq) - if err := c.ShouldBindJSON(args); err != nil { + if err := c.ShouldBindBodyWith(args, binding.JSON); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid CreateProjectReq") return } - body, _ := c.GetRawData() + var body []byte + if cb, ok := c.Get(gin.BodyBytesKey); ok { + if cbb, ok := cb.([]byte); ok { + body = cbb + } + } ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) } -- Gitee From c8bf661312a9cf10a06a36ad73fe81f5c6a1b3a2 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 29 Oct 2021 15:31:55 +0800 Subject: [PATCH 0343/2647] fix connector query error Signed-off-by: panxunying <641770806@qq.com> --- .../systemconfig/core/repository/orm/connector.go | 2 +- pkg/microservice/systemconfig/core/service/connector.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/orm/connector.go b/pkg/microservice/systemconfig/core/repository/orm/connector.go index 9ac1e501..bab55de8 100644 --- a/pkg/microservice/systemconfig/core/repository/orm/connector.go +++ b/pkg/microservice/systemconfig/core/repository/orm/connector.go @@ -51,7 +51,7 @@ func (c *ConnectorColl) List() ([]*models.Connector, error) { func (c *ConnectorColl) Get(id string) (*models.Connector, error) { res := &models.Connector{} - result := c.First(&res, id) + result := c.First(&res, "id=?", id) return res, result.Error } diff --git a/pkg/microservice/systemconfig/core/service/connector.go b/pkg/microservice/systemconfig/core/service/connector.go index 03e3be1e..a60d39e0 100644 --- a/pkg/microservice/systemconfig/core/service/connector.go +++ b/pkg/microservice/systemconfig/core/service/connector.go @@ -35,7 +35,7 @@ func ListConnectors(logger *zap.SugaredLogger) ([]*Connector, error) { var res []*Connector for _, c := range cs { cf := make(map[string]interface{}) - err = json.Unmarshal([]byte(c.Config), cf) + err = json.Unmarshal([]byte(c.Config), &cf) if err != nil { logger.Warnf("Failed to unmarshal config, err: %s", err) continue @@ -61,7 +61,7 @@ func GetConnector(id string, logger *zap.SugaredLogger) (*Connector, error) { } cf := make(map[string]interface{}) - err = json.Unmarshal([]byte(c.Config), cf) + err = json.Unmarshal([]byte(c.Config), &cf) if err != nil { logger.Warnf("Failed to unmarshal config, err: %s", err) -- Gitee From 8b5093b8fdbd34e026406e9d4ef1e4a8964ba263 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 15:33:01 +0800 Subject: [PATCH 0344/2647] update project Signed-off-by: mouuii --- .../picket/client/aslan/project.go | 10 ++++++++ .../picket/core/filter/handler/project.go | 23 +++++++++++++++++++ .../picket/core/filter/handler/router.go | 1 + .../picket/core/filter/service/project.go | 18 +++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 023b3fb3..9c62cd29 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -27,3 +27,13 @@ func (c *Client) CreateProject(header http.Header, body []byte) ([]byte, error) return res.Body(), nil } + +func (c *Client) UpdateProject(header http.Header, qs url.Values, body []byte) ([]byte, error) { + url := "/project/products" + res, err := c.Put(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetBody(body)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 4b2427ee..1aa08a7b 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -39,3 +39,26 @@ func CreateProject(c *gin.Context) { ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) } + +type UpdateProjectReq struct { + Public bool `json:"public"` + ProductName string `json:"product_name"` +} + +func UpdateProject(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(UpdateProjectReq) + if err := c.ShouldBindBodyWith(args, binding.JSON); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid UpdateProject") + return + } + var body []byte + if cb, ok := c.Get(gin.BodyBytesKey); ok { + if cbb, ok := cb.([]byte); ok { + body = cbb + } + } + ctx.Resp, ctx.Err = service.UpdateProject(c.Request.Header, body, c.Request.URL.Query(), args.ProductName, args.Public, ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 2f4bcc95..72a927c0 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -27,6 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { projects.GET("", ListProjects) projects.POST("", CreateProject) + projects.PUT("", UpdateProject) } workflows := router.Group("workflows") diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index dbeb42d9..dd47f5fd 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -46,6 +46,24 @@ func CreateProject(header http.Header, body []byte, projectName string, public b return res, nil } +func UpdateProject(header http.Header, body []byte, qs url.Values, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { + // role binding + roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) + if !public { + if err := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err != nil { + logger.Warnf("Failed to delete role binding, err: %s", err) + } + } + + res, err := aslan.New().UpdateProject(header, qs, body) + if err != nil { + logger.Errorf("Failed to create project %s, err: %s", projectName, err) + return nil, err + } + + return res, nil +} + func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { names, err := getVisibleProjects(header, logger) if err != nil { -- Gitee From 22045a1139caa1e15a1f9be1253d14c65bcaedf9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 15:42:38 +0800 Subject: [PATCH 0345/2647] update project Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 9c62cd29..c0350b5c 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -30,7 +30,7 @@ func (c *Client) CreateProject(header http.Header, body []byte) ([]byte, error) func (c *Client) UpdateProject(header http.Header, qs url.Values, body []byte) ([]byte, error) { url := "/project/products" - res, err := c.Put(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetBody(body)) + res, err := c.Put(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody(body)) if err != nil { return nil, err } -- Gitee From 4bd8e1bbbcef4b7d05375c4977e08fdfd96c6558 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 15:46:52 +0800 Subject: [PATCH 0346/2647] update project Signed-off-by: mouuii --- .../picket/core/filter/handler/project.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 1aa08a7b..5e952bc0 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -30,14 +30,18 @@ func CreateProject(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid CreateProjectReq") return } - var body []byte + body := getReqBody(c) + + ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) +} + +func getReqBody(c *gin.Context) (body []byte) { if cb, ok := c.Get(gin.BodyBytesKey); ok { if cbb, ok := cb.([]byte); ok { body = cbb } } - - ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) + return nil } type UpdateProjectReq struct { @@ -54,11 +58,6 @@ func UpdateProject(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid UpdateProject") return } - var body []byte - if cb, ok := c.Get(gin.BodyBytesKey); ok { - if cbb, ok := cb.([]byte); ok { - body = cbb - } - } + body := getReqBody(c) ctx.Resp, ctx.Err = service.UpdateProject(c.Request.Header, body, c.Request.URL.Query(), args.ProductName, args.Public, ctx.Logger) } -- Gitee From 744c7e68680c2ec637135e8fb4885704cdf0a6f7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 15:59:15 +0800 Subject: [PATCH 0347/2647] support the same service import Signed-off-by: liu deyi --- .../models/services_in_external_env.go | 13 ++ .../mongodb/services_in_external_env.go | 138 ++++++++++++++++++ .../aslan/core/common/service/environment.go | 13 +- .../aslan/core/common/service/product.go | 7 + .../core/environment/handler/environment.go | 14 ++ pkg/microservice/aslan/core/service.go | 1 + .../aslan/core/service/service/service.go | 24 ++- 7 files changed, 200 insertions(+), 10 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go diff --git a/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go new file mode 100644 index 00000000..42e2c66c --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go @@ -0,0 +1,13 @@ +package models + +type ServicesInExternalEnv struct { + ProductName string `bson:"product_name" json:"product_name"` + ServiceName string `bson:"service_name" json:"service_name"` + EnvName string `bson:"env_name" json:"env_name"` + Namespace string `bson:"namespace" json:"namespace"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` +} + +func (ServicesInExternalEnv) TableName() string { + return "services_in_external_env" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go new file mode 100644 index 00000000..7054eae0 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -0,0 +1,138 @@ +package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ServicesInExternalEnvColl struct { + *mongo.Collection + + coll string +} + +func NewServicesInExternalEnvColl() *ServicesInExternalEnvColl { + name := models.ServicesInExternalEnv{}.TableName() + return &ServicesInExternalEnvColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ServicesInExternalEnvColl) GetCollectionName() string { + return c.coll +} + +func (c *ServicesInExternalEnvColl) EnsureIndex(ctx context.Context) error { + mods := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "service_name", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + { + Keys: bson.D{ + bson.E{Key: "namespace", Value: 1}, + bson.E{Key: "cluster_id", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mods) + + return err +} + +// Create ... +func (c *ServicesInExternalEnvColl) Create(args *models.ServicesInExternalEnv) error { + // avoid panic issue + if args == nil { + return errors.New("nil ServicesInExternalEnv") + } + + _, err := c.InsertOne(context.TODO(), args) + + return err +} + +type ServicesInExternalEnvArgs struct { + ProductName string + ServiceName string + EnvName string + Namespace string + ClusterID string +} + +func (c *ServicesInExternalEnvColl) List(args *ServicesInExternalEnvArgs) ([]*models.ServicesInExternalEnv, error) { + if args == nil { + return nil, errors.New("nil ServicesInExternalEnvArgs") + } + query := bson.M{} + if args.ProductName != "" { + query["product_name"] = args.ProductName + } + + if args.EnvName != "" { + query["env_name"] = args.EnvName + } + + if args.ServiceName != "" { + query["service_name"] = args.ServiceName + } + + if args.Namespace != "" { + query["namespace"] = args.Namespace + } + + if args.ClusterID != "" { + query["cluster_id"] = args.ClusterID + } + + resp := make([]*models.ServicesInExternalEnv, 0) + ctx := context.Background() + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, err +} + +func (c *ServicesInExternalEnvColl) Delete(args *ServicesInExternalEnvArgs) error { + if args == nil { + return errors.New("nil ServicesInExternalEnvArgs") + } + query := bson.M{} + if args.ProductName != "" { + query["product_name"] = args.ProductName + } + + if args.EnvName != "" { + query["env_name"] = args.EnvName + } + + if args.ServiceName != "" { + query["service_name"] = args.ServiceName + } + + _, err := c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index bb0e0d96..6212da27 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -18,7 +18,6 @@ package service import ( "encoding/json" - "fmt" "sort" "strings" @@ -131,12 +130,20 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, } productServiceNames := sets.NewString() for _, productService := range productServices { - productServiceNames.Insert(fmt.Sprintf("%s/%s", productService.WorkloadType, productService.ServiceName)) + productServiceNames.Insert(productService.ServiceName) + } + // add services in external env data + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + for _, serviceInExternalEnv := range servicesInExternalEnv { + productServiceNames.Insert(serviceInExternalEnv.ServiceName) } var res []*Workload for _, workload := range workloads { - if productServiceNames.Has(fmt.Sprintf("%s/%s", workload.Type, workload.Name)) { + if productServiceNames.Has(workload.Name) { res = append(res, workload) } } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index c80b5637..df891817 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -159,6 +159,13 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if err != nil { log.Errorf("UpdateStatus external services error:%s", err) } + // delete services_in_external_env data + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }); err != nil { + log.Errorf("remove services in external env error:%s", err) + } } default: diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 18a493e9..bd3123c1 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -532,6 +532,20 @@ func ListWorkloads(c *gin.Context) { for _, workload := range workloadStat.Workloads { workloadM[workload.Name] = workload } + + // add services in external env data + servicesInExternalEnv, _ := mongodb.NewServicesInExternalEnvColl().List(&mongodb.ServicesInExternalEnvArgs{ + Namespace: args.Namespace, + ClusterID: args.ClusterID, + }) + + for _, serviceInExternalEnv := range servicesInExternalEnv { + workloadM[serviceInExternalEnv.ServiceName] = commonmodels.Workload{ + EnvName: serviceInExternalEnv.EnvName, + ProductName: serviceInExternalEnv.ProductName, + } + } + for index, currentWorkload := range workloads { if existWorkload, ok := workloadM[currentWorkload.Name]; ok { workloads[index].EnvName = existWorkload.EnvName diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 9a7c9164..eeccff7d 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -169,6 +169,7 @@ func initDatabase() { commonrepo.NewWorkflowColl(), commonrepo.NewWorkflowStatColl(), commonrepo.NewWorkLoadsStatColl(), + commonrepo.NewServicesInExternalEnvColl(), templaterepo.NewChartColl(), templaterepo.NewDockerfileTemplateColl(), diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 56373af0..b5e52328 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -265,7 +265,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product // 检查环境是否存在,envName和productName唯一 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} if _, err := commonrepo.NewProductColl().Find(opt); err == nil { - log.Errorf("[%s][P:%s] duplicate envName in the same product", envName, productName) + log.Errorf("[%s][P:%s] duplicate envName in the same project", envName, productName) return e.ErrCreateEnv.AddDesc(e.DuplicateEnvErrMsg) } @@ -275,22 +275,31 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product mu sync.Mutex ) - // pre judge workLoads same name serviceString := sets.NewString() services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") for _, v := range services { serviceString.Insert(v.ServiceName) } - for _, workload := range workLoads { - if serviceString.Has(workload.Name) { - return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", workload.Name)) - } - } + //for _, workload := range workLoads { + // if serviceString.Has(workload.Name) { + // return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", workload.Name)) + // } + //} g := new(errgroup.Group) for _, workload := range workLoads { tempWorkload := workload g.Go(func() error { + if serviceString.Has(tempWorkload.Name) { + return commonrepo.NewServicesInExternalEnvColl().Create(&commonmodels.ServicesInExternalEnv{ + ProductName: productName, + ServiceName: tempWorkload.Name, + EnvName: envName, + Namespace: namespace, + ClusterID: clusterID, + }) + } + var bs []byte switch tempWorkload.Type { case setting.Deployment: @@ -341,6 +350,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product ClusterID: clusterID, EnvName: envName, Namespace: namespace, + UpdateBy: username, }, log); err != nil { return e.ErrCreateProduct.AddDesc("create product Error for unknown reason") } -- Gitee From a66d6ef0aeeb207001eb604c3b6dc1798c110d69 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 16:02:41 +0800 Subject: [PATCH 0348/2647] optimize db index Signed-off-by: liu deyi --- .../repository/mongodb/services_in_external_env.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go index 7054eae0..50d1f14c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -37,7 +37,6 @@ func (c *ServicesInExternalEnvColl) EnsureIndex(ctx context.Context) error { Keys: bson.D{ bson.E{Key: "product_name", Value: 1}, bson.E{Key: "env_name", Value: 1}, - bson.E{Key: "service_name", Value: 1}, }, Options: options.Index().SetUnique(false), }, @@ -88,10 +87,6 @@ func (c *ServicesInExternalEnvColl) List(args *ServicesInExternalEnvArgs) ([]*mo query["env_name"] = args.EnvName } - if args.ServiceName != "" { - query["service_name"] = args.ServiceName - } - if args.Namespace != "" { query["namespace"] = args.Namespace } @@ -129,10 +124,6 @@ func (c *ServicesInExternalEnvColl) Delete(args *ServicesInExternalEnvArgs) erro query["env_name"] = args.EnvName } - if args.ServiceName != "" { - query["service_name"] = args.ServiceName - } - _, err := c.DeleteOne(context.TODO(), query) return err } -- Gitee From 759a5ec914ba3c49549f7e9bff5d3e2db42d34a5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 16:05:10 +0800 Subject: [PATCH 0349/2647] update project Signed-off-by: mouuii --- .../aslan/core/common/repository/models/template/product.go | 3 ++- .../aslan/core/common/repository/mongodb/template/product.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 509a72a4..7e2154c9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -41,7 +41,7 @@ type Product struct { Vars []*RenderKV `bson:"vars" json:"vars"` EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` - UserIDs []string `bson:"user_ids" json:"user_ids"` + UserIDs []string `bson:"user_ids" json:"user_ids"` TeamID int `bson:"team_id" json:"team_id"` Description string `bson:"description,omitempty" json:"desc,omitempty"` ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` @@ -72,6 +72,7 @@ type Product struct { IsOpensource bool `bson:"is_opensource" json:"is_opensource"` CustomImageRule *CustomRule `bson:"custom_image_rule,omitempty" json:"custom_image_rule,omitempty"` CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` + Public bool `bson:"public" json:"public"` } type ServiceInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index c32ca2bc..98cd277b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -254,6 +254,7 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { "image_searching_rules": args.ImageSearchingRules, "custom_tar_rule": args.CustomTarRule, "custom_image_rule": args.CustomImageRule, + "public": args.Public, }} _, err := c.UpdateOne(context.TODO(), query, change) -- Gitee From 46d7d677e560eda6596457b424e994ac1859f1f8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 16:13:23 +0800 Subject: [PATCH 0350/2647] update project Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/handler/project.go | 2 +- pkg/microservice/picket/core/filter/service/project.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 5e952bc0..2960abb0 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -41,7 +41,7 @@ func getReqBody(c *gin.Context) (body []byte) { body = cbb } } - return nil + return body } type UpdateProjectReq struct { diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index dd47f5fd..d1d953be 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -53,6 +53,13 @@ func UpdateProject(header http.Header, body []byte, qs url.Values, projectName s if err := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err != nil { logger.Warnf("Failed to delete role binding, err: %s", err) } + } else { + policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ + Name: roleBindingName, + UID: "*", + Role: string(setting.ReadOnly), + Public: true, + }) } res, err := aslan.New().UpdateProject(header, qs, body) -- Gitee From 4751304df82ed1ba50814acb417df759a8765658 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 29 Oct 2021 16:20:13 +0800 Subject: [PATCH 0351/2647] add more public rules Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 809df627..eac910da 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -60,6 +60,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/directory/check"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/directory/codehostss/?*/auth", "api/directory/codehosts/callback"}, + }, } // actions which are allowed for system admins only. -- Gitee From 9531e1a577181bb283b6b046e6b1dc7d84ec6be4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 16:21:33 +0800 Subject: [PATCH 0352/2647] update project Signed-off-by: mouuii --- .../aslan/core/common/repository/models/template/product.go | 2 +- pkg/microservice/picket/client/aslan/project.go | 4 ++-- pkg/microservice/picket/core/filter/handler/project.go | 4 ++-- pkg/microservice/picket/core/filter/service/project.go | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 7e2154c9..1554cd90 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -72,7 +72,7 @@ type Product struct { IsOpensource bool `bson:"is_opensource" json:"is_opensource"` CustomImageRule *CustomRule `bson:"custom_image_rule,omitempty" json:"custom_image_rule,omitempty"` CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` - Public bool `bson:"public" json:"public"` + Public bool `bson:"public,omitempty" json:"public"` } type ServiceInfo struct { diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index c0350b5c..0c8047e2 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -18,9 +18,9 @@ func (c *Client) ListProjects(header http.Header, qs url.Values) ([]byte, error) return res.Body(), nil } -func (c *Client) CreateProject(header http.Header, body []byte) ([]byte, error) { +func (c *Client) CreateProject(header http.Header, qs url.Values, body []byte) ([]byte, error) { url := "/project/products" - res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetBody(body)) + res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody(body)) if err != nil { return nil, err } diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 2960abb0..26e6bf49 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -32,7 +32,7 @@ func CreateProject(c *gin.Context) { } body := getReqBody(c) - ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, c.Request.URL.Query(), args.ProductName, args.Public, ctx.Logger) } func getReqBody(c *gin.Context) (body []byte) { @@ -59,5 +59,5 @@ func UpdateProject(c *gin.Context) { return } body := getReqBody(c) - ctx.Resp, ctx.Err = service.UpdateProject(c.Request.Header, body, c.Request.URL.Query(), args.ProductName, args.Public, ctx.Logger) + ctx.Resp, ctx.Err = service.UpdateProject(c.Request.Header, c.Request.URL.Query(), body, args.ProductName, args.Public, ctx.Logger) } diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index d1d953be..c9d4ba63 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -18,7 +18,7 @@ type allowedProjectsData struct { Result []string `json:"result"` } -func CreateProject(header http.Header, body []byte, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { +func CreateProject(header http.Header, body []byte, qs url.Values, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { // role binding roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) if public { @@ -33,7 +33,7 @@ func CreateProject(header http.Header, body []byte, projectName string, public b } } - res, err := aslan.New().CreateProject(header, body) + res, err := aslan.New().CreateProject(header, qs, body) if err != nil { logger.Errorf("Failed to create project %s, err: %s", projectName, err) if err1 := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err1 != nil { @@ -46,7 +46,7 @@ func CreateProject(header http.Header, body []byte, projectName string, public b return res, nil } -func UpdateProject(header http.Header, body []byte, qs url.Values, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { +func UpdateProject(header http.Header, qs url.Values, body []byte, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { // role binding roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) if !public { -- Gitee From 3d524b9485fd13701ac92a9daf0b3e5d104f850c Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 16:25:06 +0800 Subject: [PATCH 0353/2647] update project Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/project.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index c9d4ba63..02feff77 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -54,12 +54,14 @@ func UpdateProject(header http.Header, qs url.Values, body []byte, projectName s logger.Warnf("Failed to delete role binding, err: %s", err) } } else { - policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ + if err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ Name: roleBindingName, UID: "*", Role: string(setting.ReadOnly), Public: true, - }) + }); err != nil { + logger.Warnf("Failed to create role binding, err: %s", err) + } } res, err := aslan.New().UpdateProject(header, qs, body) -- Gitee From df220366323be8cbfd4bc1b0ea8b7dd259c029c4 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 29 Oct 2021 16:39:30 +0800 Subject: [PATCH 0354/2647] fix delete connector Signed-off-by: lou --- pkg/microservice/systemconfig/core/repository/orm/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/repository/orm/connector.go b/pkg/microservice/systemconfig/core/repository/orm/connector.go index bab55de8..a13db568 100644 --- a/pkg/microservice/systemconfig/core/repository/orm/connector.go +++ b/pkg/microservice/systemconfig/core/repository/orm/connector.go @@ -77,7 +77,7 @@ func (c *ConnectorColl) Update(obj *models.Connector) error { } func (c *ConnectorColl) Delete(id string) error { - result := c.DB.Delete(&models.Connector{}, id) + result := c.DB.Delete(&models.Connector{ID: id}) return result.Error } -- Gitee From 8e17ac0b3426fc0f2467225bc3f54f2ab87e3212 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 17:14:08 +0800 Subject: [PATCH 0355/2647] update project Signed-off-by: mouuii --- .../picket/core/filter/service/project.go | 2 +- .../policy/core/handler/role_binding.go | 15 +++++++++++++++ pkg/microservice/policy/core/handler/router.go | 1 + .../core/repository/mongodb/role_binding.go | 12 ++++++++++++ .../policy/core/service/role_binding.go | 8 ++++++++ pkg/shared/client/policy/policy.go | 4 ++-- 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 02feff77..6d80f75e 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -54,7 +54,7 @@ func UpdateProject(header http.Header, qs url.Values, body []byte, projectName s logger.Warnf("Failed to delete role binding, err: %s", err) } } else { - if err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ + if err := policy.NewDefault().CreateOrUpdateRoleBinding(projectName, &policy.RoleBinding{ Name: roleBindingName, UID: "*", Role: string(setting.ReadOnly), diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 95780198..0485cefd 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -28,6 +28,21 @@ type deleteRoleBindingsArgs struct { Names []string `json:"names"` } +func UpdateRoleBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + args := &service.RoleBinding{} + + ctx.Err = service.UpdateOrCreateRoleBinding(projectName, args, ctx.Logger) +} + func CreateRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index a59a4243..22f1018f 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -55,6 +55,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings := router.Group("rolebindings") { roleBindings.POST("", CreateRoleBinding) + roleBindings.PUT("", UpdateRoleBinding) roleBindings.GET("", ListRoleBindings) roleBindings.DELETE("/:name", DeleteRoleBinding) roleBindings.POST("/bulk-delete", DeleteRoleBindings) diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 0bd9ee6f..9174fdeb 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -166,3 +166,15 @@ func (c *RoleBindingColl) BulkCreate(objs []*models.RoleBinding) error { return err } + +func (c *RoleBindingColl) UpdateOrCreate(obj *models.RoleBinding) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + query := bson.M{"name": obj.Name, "namespace": obj.Namespace} + opts := options.Replace().SetUpsert(true) + _, err := c.ReplaceOne(context.TODO(), query, obj, opts) + + return err +} diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 9fbdc5c6..c08412c0 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -48,6 +48,14 @@ func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger return mongodb.NewRoleBindingColl().BulkCreate(objs) } +func UpdateOrCreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { + obj, err := createRoleBindingObject(ns, rb, logger) + if err != nil { + return err + } + return mongodb.NewRoleBindingColl().UpdateOrCreate(obj) +} + func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, error) { var roleBindings []*RoleBinding modelRoleBindings, err := mongodb.NewRoleBindingColl().ListBy(ns, uid) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 8401f878..8e9d4e8e 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -49,9 +49,9 @@ func (c *Client) ListRoleBindings(projectName string) ([]*RoleBinding, error) { return res, err } -func (c *Client) CreateRoleBinding(projectName string, roleBinding *RoleBinding) error { +func (c *Client) CreateOrUpdateRoleBinding(projectName string, roleBinding *RoleBinding) error { url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) - _, err := c.Post(url, httpclient.SetBody(roleBinding)) + _, err := c.Put(url, httpclient.SetBody(roleBinding)) return err } -- Gitee From a77386ac051fc829c264312010f40f848bb73bfb Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 17:17:06 +0800 Subject: [PATCH 0356/2647] update project Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 6d80f75e..c6cf2797 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -22,7 +22,7 @@ func CreateProject(header http.Header, body []byte, qs url.Values, projectName s // role binding roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) if public { - if err := policy.NewDefault().CreateRoleBinding(projectName, &policy.RoleBinding{ + if err := policy.NewDefault().CreateOrUpdateRoleBinding(projectName, &policy.RoleBinding{ Name: roleBindingName, UID: "*", Role: string(setting.ReadOnly), -- Gitee From 2d6e3a4b5ea7dbf902e85f95899e534932a151df Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 17:19:44 +0800 Subject: [PATCH 0357/2647] optimize host env logic Signed-off-by: liu deyi --- .../core/common/repository/mongodb/service.go | 72 ++++++++++++------- .../mongodb/services_in_external_env.go | 2 +- .../aslan/core/common/service/product.go | 30 +++++++- 3 files changed, 74 insertions(+), 30 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 26bd34d2..c4a08a92 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -160,32 +160,6 @@ func (c *ServiceColl) ListMaxRevisionsForServices(services []*templatemodels.Ser return c.listMaxRevisions(pre, post) } -// TODO refactor mouuii -// ListExternalServicesBy list service only for external services ,other service type not use before refactor -func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string) ([]*models.Service, error) { - services := make([]*models.Service, 0) - query := bson.M{ - "status": bson.M{"$ne": setting.ProductStatusDeleting}, - } - if productName != "" { - query["product_name"] = productName - } - if envName != "" { - query["env_name"] = envName - } - ctx := context.Background() - cursor, err := c.Collection.Find(ctx, query) - if err != nil { - return nil, err - } - - err = cursor.All(ctx, &services) - if err != nil { - return nil, err - } - return services, nil -} - func (c *ServiceColl) ListMaxRevisionsByProduct(productName string) ([]*models.Service, error) { m := bson.M{ "product_name": productName, @@ -304,6 +278,52 @@ func (c *ServiceColl) Update(args *models.Service) error { return err } +// ListExternalServicesBy list service only for external services ,other service type not use before refactor +func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string) ([]*models.Service, error) { + services := make([]*models.Service, 0) + query := bson.M{ + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + } + if productName != "" { + query["product_name"] = productName + } + if envName != "" { + query["env_name"] = envName + } + ctx := context.Background() + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &services) + if err != nil { + return nil, err + } + return services, nil +} + +func (c *ServiceColl) BatchUpdateExternalServicesStatus(productName, envName, status string, serviceNames []string) error { + if envName == "" { + return fmt.Errorf("envName is empty") + } + if productName == "" { + return fmt.Errorf("productName is empty") + } + + if len(serviceNames) == 0 { + return fmt.Errorf("servicenNames is empty") + } + + query := bson.M{"product_name": productName, "env_name": envName, "service_name": bson.M{"$in": serviceNames}} + change := bson.M{"$set": bson.M{ + "status": status, + }} + + _, err := c.UpdateMany(context.TODO(), query, change) + return err +} + // UpdateExternalServicesStatus only used by external services func (c *ServiceColl) UpdateExternalServicesStatus(serviceName, productName, status, envName string) error { if serviceName == "" && envName == "" { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go index 50d1f14c..466decd6 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -124,6 +124,6 @@ func (c *ServicesInExternalEnvColl) Delete(args *ServicesInExternalEnvArgs) erro query["env_name"] = args.EnvName } - _, err := c.DeleteOne(context.TODO(), query) + _, err := c.DeleteMany(context.TODO(), query) return err } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index df891817..2ad2afcd 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -24,6 +24,7 @@ import ( helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -154,10 +155,33 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("update workloads fail error:%s", err) } } - // 删除所有external的服务 - err = commonrepo.NewServiceColl().UpdateExternalServicesStatus("", productName, setting.ProductStatusDeleting, envName) + // 获取所有external的服务 + currentEnvServices, err := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, envName) if err != nil { - log.Errorf("UpdateStatus external services error:%s", err) + log.Errorf("failed to list external workload, error:%s", err) + } + + externnalEnvSerivces, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + }) + if err != nil { + log.Errorf("failed to list external service, error:%s", err) + } + + externnalEnvSerivceM := make(map[string]bool) + for _, externnalEnvSerivce := range externnalEnvSerivces { + externnalEnvSerivceM[externnalEnvSerivce.ServiceName] = true + } + + deleteServices := sets.NewString() + for _, currentEnvService := range currentEnvServices { + if _, isExist := externnalEnvSerivceM[currentEnvService.ServiceName]; !isExist { + deleteServices.Insert(currentEnvService.ServiceName) + } + } + err = commonrepo.NewServiceColl().BatchUpdateExternalServicesStatus(productName, envName, setting.ProductStatusDeleting, deleteServices.List()) + if err != nil { + log.Errorf("UpdateStatus external services error:%s", err) } // delete services_in_external_env data if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ -- Gitee From e918127fe309c2b45a3b6c2fa2335b8eb773d976 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 17:43:27 +0800 Subject: [PATCH 0358/2647] delete project Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/project.go | 12 ++++++++++++ .../picket/core/filter/handler/project.go | 7 +++++++ .../picket/core/filter/handler/router.go | 1 + .../picket/core/filter/service/project.go | 4 ++++ 4 files changed, 24 insertions(+) diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 023b3fb3..3c45c531 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -1,12 +1,24 @@ package aslan import ( + "fmt" "net/http" "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" ) +func (c *Client) DeleteProject(header http.Header, qs url.Values, productName string) ([]byte, error) { + url := fmt.Sprintf("/project/prodects/%s", productName) + + res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} + func (c *Client) ListProjects(header http.Header, qs url.Values) ([]byte, error) { url := "/project/projects" diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 4b2427ee..beab31b6 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -39,3 +39,10 @@ func CreateProject(c *gin.Context) { ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, args.ProductName, args.Public, ctx.Logger) } + +func DeleteProject(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.DeleteProject(c.Request.Header, c.Request.URL.Query(), c.Param("project"), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 2f4bcc95..47e34d14 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -25,6 +25,7 @@ type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { projects := router.Group("projects") { + projects.DELETE("/:project", DeleteProject) projects.GET("", ListProjects) projects.POST("", CreateProject) } diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index dbeb42d9..8a63b2ee 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -66,6 +66,10 @@ func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) return aslanClient.ListProjects(header, qs) } +func DeleteProject(header http.Header, qs url.Values, productName string, logger *zap.SugaredLogger) ([]byte, error) { + return aslan.New().DeleteProject(header, qs, productName) +} + func getVisibleProjects(headers http.Header, logger *zap.SugaredLogger) ([]string, error) { res := &allowedProjectsData{} opaClient := opa.NewDefault() -- Gitee From d6efde5c3309bc35aca755225ca4c9fd51ca2ac4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 17:46:55 +0800 Subject: [PATCH 0359/2647] optimize list service logic Signed-off-by: liu deyi --- .../core/common/repository/mongodb/service.go | 6 +++++- .../aslan/core/common/service/service.go | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index c4a08a92..b6bb4287 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -279,7 +279,7 @@ func (c *ServiceColl) Update(args *models.Service) error { } // ListExternalServicesBy list service only for external services ,other service type not use before refactor -func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string) ([]*models.Service, error) { +func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string, serviceNames ...string) ([]*models.Service, error) { services := make([]*models.Service, 0) query := bson.M{ "status": bson.M{"$ne": setting.ProductStatusDeleting}, @@ -290,6 +290,10 @@ func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string) ([]*m if envName != "" { query["env_name"] = envName } + + if len(serviceNames) > 0 { + query["service_name"] = bson.M{"$in": serviceNames} + } ctx := context.Background() cursor, err := c.Collection.Find(ctx, query) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 6421572d..08f00898 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -221,6 +221,21 @@ func ListWorkloadTemplate(productName, envName string, log *zap.SugaredLogger) ( return resp, e.ErrListTemplate.AddDesc(err.Error()) } + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + + serviceNames := sets.NewString() + for _, serviceInExternalEnv := range servicesInExternalEnv { + serviceNames.Insert(serviceInExternalEnv.ServiceName) + } + + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", serviceNames.List()...) + for _, service := range newServices { + services = append(services, service) + } + for _, serviceObject := range services { spmap := &ServiceProductMap{ Service: serviceObject.ServiceName, -- Gitee From 18732563fa266afe1c75ebb048a37c96d0736cd5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 17:59:17 +0800 Subject: [PATCH 0360/2647] optimize list service logic Signed-off-by: liu deyi --- .../aslan/core/common/service/service.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 08f00898..c3c91adc 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -221,17 +221,24 @@ func ListWorkloadTemplate(productName, envName string, log *zap.SugaredLogger) ( return resp, e.ErrListTemplate.AddDesc(err.Error()) } + currentServiceNames := sets.NewString() + for _, service := range services { + currentServiceNames.Insert(service.ServiceName) + } + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ ProductName: productName, EnvName: envName, }) - serviceNames := sets.NewString() + externalServiceNames := sets.NewString() for _, serviceInExternalEnv := range servicesInExternalEnv { - serviceNames.Insert(serviceInExternalEnv.ServiceName) + if !currentServiceNames.Has(serviceInExternalEnv.ServiceName) { + externalServiceNames.Insert(serviceInExternalEnv.ServiceName) + } } - newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", serviceNames.List()...) + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", externalServiceNames.List()...) for _, service := range newServices { services = append(services, service) } -- Gitee From 15ed0755ce93704be9fde31c011e0eb7e276c4a8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 18:08:04 +0800 Subject: [PATCH 0361/2647] optimize list service logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/service.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index c3c91adc..26c7a85f 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -238,9 +238,11 @@ func ListWorkloadTemplate(productName, envName string, log *zap.SugaredLogger) ( } } - newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", externalServiceNames.List()...) - for _, service := range newServices { - services = append(services, service) + if len(externalServiceNames) > 0 { + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", externalServiceNames.List()...) + for _, service := range newServices { + services = append(services, service) + } } for _, serviceObject := range services { -- Gitee From 568acfe15c4b0de2870a9d7fd5699ad578ca930e Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 18:17:19 +0800 Subject: [PATCH 0362/2647] delete project Signed-off-by: mouuii --- .../picket/core/filter/service/project.go | 3 +++ pkg/shared/client/policy/policy.go | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 8a63b2ee..53ffd559 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -67,6 +67,9 @@ func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) } func DeleteProject(header http.Header, qs url.Values, productName string, logger *zap.SugaredLogger) ([]byte, error) { + // delete roles and rolebindings + policy.NewDefault().DeleteRoleBindings([]string{"*"}, productName) + policy.NewDefault().DeleteRoles([]string{"*"}, productName) return aslan.New().DeleteProject(header, qs, productName) } diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 8401f878..ac0cc2cf 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -61,6 +61,30 @@ func (c *Client) DeleteRoleBinding(name string, projectName string) error { return err } +type NameArgs struct { + Names []string `json:"names"` +} + +func (c *Client) DeleteRoleBindings(names []string, projectName string) error { + url := fmt.Sprintf("/rolebindings/bulk-delete?projectName=%s", projectName) + nameArgs := NameArgs{} + for _, v := range names { + nameArgs.Names = append(nameArgs.Names, v) + } + _, err := c.Post(url, httpclient.SetBody(nameArgs)) + return err +} + +func (c *Client) DeleteRoles(names []string, projectName string) error { + url := fmt.Sprintf("/roles/bulk-delete?projectName=%s", projectName) + nameArgs := NameArgs{} + for _, v := range names { + nameArgs.Names = append(nameArgs.Names, v) + } + _, err := c.Post(url, httpclient.SetBody(nameArgs)) + return err +} + func (c *Client) CreateSystemRole(name string, role *Role) error { url := fmt.Sprintf("/system-roles/%s", name) _, err := c.Put(url, httpclient.SetBody(role)) -- Gitee From d7da9b8e9f042849d55a50800c2b2860b14b0d2f Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 18:19:23 +0800 Subject: [PATCH 0363/2647] delete project Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/project.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index c6cf2797..4708d5f6 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -37,7 +37,7 @@ func CreateProject(header http.Header, body []byte, qs url.Values, projectName s if err != nil { logger.Errorf("Failed to create project %s, err: %s", projectName, err) if err1 := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err1 != nil { - logger.Warnf("Failed to delete role binding, err: %s", err1) + logger.Errorf("Failed to delete role binding, err: %s", err1) } return nil, err @@ -51,7 +51,8 @@ func UpdateProject(header http.Header, qs url.Values, body []byte, projectName s roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) if !public { if err := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err != nil { - logger.Warnf("Failed to delete role binding, err: %s", err) + logger.Errorf("Failed to delete role binding, err: %s", err) + return nil, err } } else { if err := policy.NewDefault().CreateOrUpdateRoleBinding(projectName, &policy.RoleBinding{ @@ -61,6 +62,7 @@ func UpdateProject(header http.Header, qs url.Values, body []byte, projectName s Public: true, }); err != nil { logger.Warnf("Failed to create role binding, err: %s", err) + return nil, err } } -- Gitee From 40b32e1011a4092f7262e923e69a6633f49d37ba Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 29 Oct 2021 18:21:49 +0800 Subject: [PATCH 0364/2647] change from login by email to account and add GetPersonalUser Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 1 + pkg/microservice/user/config/consts.go | 6 + .../user/core/handler/login/third_party.go | 6 +- pkg/microservice/user/core/handler/router.go | 4 +- .../user/core/handler/user/user.go | 24 ++- .../user/core/repository/models/user.go | 1 + .../user/core/repository/models/user_login.go | 2 + .../user/core/repository/orm/user.go | 20 ++- .../user/core/repository/orm/user_login.go | 7 +- .../user/core/service/login/local.go | 26 ++-- .../user/core/service/login/third_party.go | 77 ---------- .../user/core/service/login/token.go | 2 +- .../user/core/service/user/user.go | 145 ++++++++++++++++-- 13 files changed, 204 insertions(+), 117 deletions(-) delete mode 100644 pkg/microservice/user/core/service/login/third_party.go diff --git a/go.mod b/go.mod index d8f77493..c281428b 100644 --- a/go.mod +++ b/go.mod @@ -53,6 +53,7 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 + github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect github.com/stretchr/testify v1.7.0 diff --git a/pkg/microservice/user/config/consts.go b/pkg/microservice/user/config/consts.go index 11887903..6f13e8e9 100644 --- a/pkg/microservice/user/config/consts.go +++ b/pkg/microservice/user/config/consts.go @@ -24,3 +24,9 @@ const ( AppState = setting.ProductName + "user" SystemIdentityType = "system" ) + +type LoginType int + +const ( + AccountLocinType LoginType = 0 +) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index b84412db..df86d6ea 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -15,6 +15,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/login" + "github.com/koderover/zadig/pkg/microservice/user/core/service/user" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -102,9 +103,8 @@ func Callback(c *gin.Context) { ctx.Err = err return } - user, err := login.SyncUser(&login.SyncUserInfo{ - Email: claims.Email, - Name: claims.Name, + user, err := user.SyncUser(&user.SyncUserInfo{ + Account: claims.Name, IdentityType: claims.FederatedClaims.ConnectorId, }, ctx.Logger) if err != nil { diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index 2964f347..beb9e0e3 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -36,6 +36,8 @@ func (*Router) Inject(router *gin.RouterGroup) { users.GET("/users/:uid", user.GetUser) - users.GET("/users", user.ListUsers) + users.GET("/users/:uid/personal", user.GetPersonalUser) + + users.POST("/users/search", user.ListUsers) } } diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index a16e107e..697dba0c 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -1,6 +1,8 @@ package user import ( + "fmt" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" @@ -13,15 +15,31 @@ func GetUser(c *gin.Context) { ctx.Resp, ctx.Err = user.GetUser(c.Param("uid"), ctx.Logger) } +func GetPersonalUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + uid := c.Param("uid") + if ctx.UserID != uid { + ctx.Err = fmt.Errorf("token uid doesn't match uid") + } + ctx.Resp, ctx.Err = user.GetUser(uid, ctx.Logger) +} + func ListUsers(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() args := &user.QueryArgs{} - if err := c.ShouldBindQuery(args); err != nil { + if err := c.ShouldBindJSON(args); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = user.SeachUsers(args, ctx.Logger) + if len(args.UIDs) > 0 { + ctx.Resp, ctx.Err = user.SearchUsersByUIDs(args.UIDs, ctx.Logger) + return + } else { + ctx.Resp, ctx.Err = user.SearchUsers(args, ctx.Logger) + return + } } func CreateUser(c *gin.Context) { @@ -32,7 +50,7 @@ func CreateUser(c *gin.Context) { ctx.Err = err return } - ctx.Err = user.CreateUser(args, ctx.Logger) + ctx.Resp, ctx.Err = user.CreateUser(args, ctx.Logger) } func UpdatePassword(c *gin.Context) { diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go index 402d08a7..30862a89 100644 --- a/pkg/microservice/user/core/repository/models/user.go +++ b/pkg/microservice/user/core/repository/models/user.go @@ -7,6 +7,7 @@ type User struct { IdentityType string `gorm:"default:'unknown'" json:"identity_type"` Email string `json:"email"` Phone string `json:"phone"` + Account string `json:"account"` } // TableName sets the insert table name for this struct type diff --git a/pkg/microservice/user/core/repository/models/user_login.go b/pkg/microservice/user/core/repository/models/user_login.go index 34e5bbed..f2b84804 100644 --- a/pkg/microservice/user/core/repository/models/user_login.go +++ b/pkg/microservice/user/core/repository/models/user_login.go @@ -5,6 +5,8 @@ type UserLogin struct { UID string `json:"uid"` Password string `json:"password"` LastLoginTime int64 `json:"last_login_time"` + LoginId string `json:"login_id"` + LoginType int `json:"login_type"` } // TableName sets the insert table name for this struct type diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index 9c34f210..faeda529 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -16,9 +16,9 @@ func CreateUser(user *models.User, db *gorm.DB) error { } // GetUser Get a user based on email and identityType -func GetUser(email string, identityType string, db *gorm.DB) (*models.User, error) { +func GetUser(account string, identityType string, db *gorm.DB) (*models.User, error) { var user models.User - err := db.Where("email = ? and identity_type = ?", email, identityType).First(&user).Error + err := db.Where("account = ? and identity_type = ?", account, identityType).First(&user).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } @@ -60,6 +60,22 @@ func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, return users, nil } +// ListUsersByUIDs gets a list of users based on paging constraints +func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { + var ( + users []models.User + err error + ) + + err = db.Find(&users, "uid in ?", uids).Error + + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + + return users, nil +} + // GetUsersCount gets user count func GetUsersCount(name string) (int64, error) { var ( diff --git a/pkg/microservice/user/core/repository/orm/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go index a7bc6c42..153a538a 100644 --- a/pkg/microservice/user/core/repository/orm/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -3,6 +3,7 @@ package orm import ( "gorm.io/gorm" + "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" ) @@ -15,9 +16,9 @@ func CreateUserLogin(userLogin *models.UserLogin, db *gorm.DB) error { } // GetUserLogin Get a userLogin based on uid -func GetUserLogin(uid string, db *gorm.DB) (*models.UserLogin, error) { +func GetUserLogin(uid string, account string, loginType config.LoginType, db *gorm.DB) (*models.UserLogin, error) { var userLogin models.UserLogin - err := db.Where("uid = ?", uid).First(&userLogin).Error + err := db.Where("uid = ? and login_id = ? and login_type = ?", uid, account, loginType).First(&userLogin).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } @@ -30,7 +31,7 @@ func GetUserLogin(uid string, db *gorm.DB) (*models.UserLogin, error) { // ListUserLogins Get a userLogin based on uid list func ListUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { var userLogins []models.UserLogin - err := db.Where("uid IN (?)", uids).Find(&userLogins).Error + err := db.Find(&userLogins, "uid in ?", uids).Error if err != nil { return nil, err } diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index 01d16375..e0c300cf 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -15,7 +15,7 @@ import ( ) type LoginArgs struct { - Email string `json:"email"` + Account string `json:"account"` Password string `json:"password"` } @@ -25,50 +25,51 @@ type User struct { Email string `json:"email"` Phone string `json:"phone"` Name string `json:"name"` + Account string `json:"account"` IdentityType string `json:"identityType"` } func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { - user, err := orm.GetUser(args.Email, config.SystemIdentityType, core.DB) + user, err := orm.GetUser(args.Account, config.SystemIdentityType, core.DB) if err != nil { - logger.Errorf("InternalLogin get user email:%s error", args.Email) + logger.Errorf("InternalLogin get user account:%s error", args.Account) return nil, err } if user == nil { return nil, fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLocinType, core.DB) if err != nil { - logger.Errorf("InternalLogin get user:%s user login not exist, error msg:%s", args.Email, err.Error()) + logger.Errorf("LocalLogin get user:%s user login not exist, error msg:%s", args.Account, err.Error()) return nil, err } if userLogin == nil { - logger.Errorf("InternalLogin user:%s user login not exist", args.Email) + logger.Errorf("InternalLogin user:%s user login not exist", args.Account) return nil, fmt.Errorf("user login not exist") } password := []byte(args.Password) err = bcrypt.CompareHashAndPassword([]byte(userLogin.Password), password) if err != nil { - logger.Errorf("InternalLogin user:%s check password error, error msg:%s", args.Email, err.Error()) + logger.Errorf("LocalLogin user:%s check password error, error msg:%s", args.Account, err.Error()) return nil, fmt.Errorf("check password error, error msg:%s", err.Error()) } userLogin.LastLoginTime = time.Now().Unix() err = orm.UpdateUserLogin(userLogin.UID, userLogin, core.DB) if err != nil { - logger.Errorf("InternalLogin user:%s update user login password error, error msg:%s", args.Email, err.Error()) + logger.Errorf("LocalLogin user:%s update user login password error, error msg:%s", args.Account, err.Error()) return nil, err } token, err := CreateToken(&Claims{ - Name: user.Name, - Email: user.Email, - Uid: user.UID, + Name: user.Name, + Account: user.Account, + Uid: user.UID, StandardClaims: jwt.StandardClaims{ Audience: setting.ProductName, ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), }, }) if err != nil { - logger.Errorf("InternalLogin user:%s create token error, error msg:%s", args.Email, err.Error()) + logger.Errorf("LocalLogin user:%s create token error, error msg:%s", args.Account, err.Error()) return nil, err } return &User{ @@ -77,6 +78,7 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { Email: user.Email, Phone: user.Phone, Name: user.Name, + Account: user.Account, IdentityType: user.IdentityType, }, nil } diff --git a/pkg/microservice/user/core/service/login/third_party.go b/pkg/microservice/user/core/service/login/third_party.go deleted file mode 100644 index 30db1eb0..00000000 --- a/pkg/microservice/user/core/service/login/third_party.go +++ /dev/null @@ -1,77 +0,0 @@ -package login - -import ( - "time" - - "github.com/google/uuid" - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/user/core" - "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" -) - -type SyncUserInfo struct { - Email string `json:"email"` - IdentityType string `json:"identityType"` - Name string `json:"name"` -} - -func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.User, error) { - user, err := orm.GetUser(syncUserInfo.Email, syncUserInfo.IdentityType, core.DB) - if err != nil { - logger.Error("SyncUser get user:%s error, error msg:%s", syncUserInfo.Email, err.Error()) - return nil, err - } - tx := core.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - if user == nil { - uid, _ := uuid.NewUUID() - user = &models.User{ - UID: uid.String(), - Name: syncUserInfo.Name, - Email: syncUserInfo.Email, - IdentityType: syncUserInfo.IdentityType, - } - err = orm.CreateUser(user, tx) - if err != nil { - tx.Rollback() - logger.Error("SyncUser create user:%s error, error msg:%s", syncUserInfo.Email, err.Error()) - return nil, err - } - } - userLogin, err := orm.GetUserLogin(user.UID, tx) - if err != nil { - tx.Rollback() - logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) - return nil, err - } - if userLogin != nil { - userLogin.LastLoginTime = time.Now().Unix() - err = orm.UpdateUserLogin(user.UID, userLogin, tx) - if err != nil { - tx.Rollback() - logger.Error("UpdateLoginInfo update user:%s login error, error msg:%s", user.UID, err.Error()) - return nil, err - } - } else { - err = orm.CreateUserLogin(&models.UserLogin{ - UID: user.UID, - LastLoginTime: time.Now().Unix(), - }, tx) - if err != nil { - tx.Rollback() - logger.Error("UpdateLoginInfo create user:%s login error, error msg:%s", user.UID, err.Error()) - return nil, err - } - } - err = tx.Commit().Error - if err != nil { - return nil, err - } - return user, nil -} diff --git a/pkg/microservice/user/core/service/login/token.go b/pkg/microservice/user/core/service/login/token.go index 76ccfcdc..734e19b5 100644 --- a/pkg/microservice/user/core/service/login/token.go +++ b/pkg/microservice/user/core/service/login/token.go @@ -8,7 +8,7 @@ import ( type Claims struct { Name string `json:"name"` - Email string `json:"email"` + Account string `json:"account"` Uid string `json:"uid"` FederatedClaims FederatedClaims `json:"federated_claims"` jwt.StandardClaims diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 44cf4c5e..6f93566e 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -2,6 +2,7 @@ package user import ( "fmt" + "time" "github.com/google/uuid" "go.uber.org/zap" @@ -14,16 +15,18 @@ import ( ) type User struct { - Name string `json:"name"` + Name string `json:"name,omitempty"` Password string `json:"password"` - Email string `json:"email"` + Email string `json:"email,omitempty"` + Account string `json:"account"` Phone string `json:"phone,omitempty"` } type QueryArgs struct { - Name string `json:"name"` - PerPage int `json:"per_page"` - Page int `json:"page"` + Name string `json:"name,omitempty"` + UIDs []string `json:"uids,omitempty"` + PerPage int `json:"per_page,omitempty"` + Page int `json:"page,omitempty"` } type UserInfo struct { @@ -33,6 +36,7 @@ type UserInfo struct { IdentityType string `gorm:"default:'unknown'" json:"identity_type"` Email string `json:"email"` Phone string `json:"phone"` + Account string `json:"account"` } type Password struct { @@ -46,16 +50,28 @@ type UsersResp struct { TotalCount int64 `json:"totalCount"` } -func GetUser(uid string, logger *zap.SugaredLogger) (*models.User, error) { +type SyncUserInfo struct { + Account string `json:"account"` + IdentityType string `json:"identityType"` + Name string `json:"name"` +} + +func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { user, err := orm.GetUserByUid(uid, core.DB) if err != nil { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err } - return user, nil + userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLocinType, core.DB) + if err != nil { + logger.Errorf("GetUser GetUserLogin:%s error, error msg:%s", uid, err.Error()) + return nil, err + } + userInfo := mergeUserLogin([]models.User{*user}, []models.UserLogin{*userLogin}, logger) + return &userInfo[0], nil } -func SeachUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { +func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { count, err := orm.GetUsersCount(args.Name) if err != nil { logger.Errorf("SeachUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) @@ -66,6 +82,7 @@ func SeachUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) TotalCount: 0, }, nil } + users, err := orm.ListUsers(args.Page, args.PerPage, args.Name, core.DB) if err != nil { logger.Errorf("SeachUsers SeachUsers By name:%s error, error msg:%s", args.Name, err.Error()) @@ -80,8 +97,16 @@ func SeachUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) logger.Errorf("SeachUsers ListUserLogins By uids:%s error, error msg:%s", uids, err.Error()) return nil, err } + usersInfo := mergeUserLogin(users, *userLogins, logger) + return &UsersResp{ + Users: usersInfo, + TotalCount: count, + }, nil +} + +func mergeUserLogin(users []models.User, userLogins []models.UserLogin, logger *zap.SugaredLogger) []UserInfo { userLoginMap := make(map[string]models.UserLogin) - for _, userLogin := range *userLogins { + for _, userLogin := range userLogins { userLoginMap[userLogin.UID] = userLogin } var usersInfo []UserInfo @@ -94,24 +119,51 @@ func SeachUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) Name: user.Name, Email: user.Email, IdentityType: user.IdentityType, + Account: user.Account, }) } else { logger.Error("user:%s login info not exist") } } + return usersInfo +} + +func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*UsersResp, error) { + users, err := orm.ListUsersByUIDs(uids, core.DB) + if err != nil { + logger.Errorf("SearchUsersByUIDs SeachUsers By uids:%s error, error msg:%s", uids, err.Error()) + return nil, err + } + userLogins, err := orm.ListUserLogins(uids, core.DB) + if err != nil { + logger.Errorf("SearchUsersByUIDs ListUserLogins By uids:%s error, error msg:%s", uids, err.Error()) + return nil, err + } + usersInfo := mergeUserLogin(users, *userLogins, logger) return &UsersResp{ Users: usersInfo, - TotalCount: count, + TotalCount: int64(len(usersInfo)), }, nil } -func CreateUser(args *User, logger *zap.SugaredLogger) error { +func getLoginId(user *models.User, loginType config.LoginType) string { + switch loginType { + case config.AccountLocinType: + return user.Account + default: + return user.Account + } + +} + +func CreateUser(args *User, logger *zap.SugaredLogger) (*models.User, error) { uid, _ := uuid.NewUUID() user := &models.User{ Name: args.Name, Email: args.Email, IdentityType: config.SystemIdentityType, Phone: args.Phone, + Account: args.Account, UID: uid.String(), } tx := core.DB.Begin() @@ -124,21 +176,23 @@ func CreateUser(args *User, logger *zap.SugaredLogger) error { if err != nil { tx.Rollback() logger.Errorf("CreateUser CreateUser :%v error, error msg:%s", user, err.Error()) - return err + return nil, err } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.Password), bcrypt.DefaultCost) userLogin := &models.UserLogin{ UID: user.UID, Password: string(hashedPassword), LastLoginTime: 0, + LoginId: getLoginId(user, config.AccountLocinType), + LoginType: int(config.AccountLocinType), } err = orm.CreateUserLogin(userLogin, tx) if err != nil { tx.Rollback() logger.Errorf("CreateUser CreateUserLogin:%v error, error msg:%s", user, err.Error()) - return err + return nil, err } - return tx.Commit().Error + return user, tx.Commit().Error } func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { @@ -150,7 +204,7 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { if user == nil { return fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, core.DB) if err != nil { logger.Errorf("UpdatePassword GetUserLogin:%s error, error msg:%s", args.Uid, err.Error()) return err @@ -181,3 +235,64 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { } return nil } + +func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.User, error) { + user, err := orm.GetUser(syncUserInfo.Account, syncUserInfo.IdentityType, core.DB) + if err != nil { + logger.Error("SyncUser get user:%s error, error msg:%s", syncUserInfo.Account, err.Error()) + return nil, err + } + tx := core.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + if user == nil { + uid, _ := uuid.NewUUID() + user = &models.User{ + UID: uid.String(), + Name: syncUserInfo.Name, + Account: syncUserInfo.Account, + IdentityType: syncUserInfo.IdentityType, + } + err = orm.CreateUser(user, tx) + if err != nil { + tx.Rollback() + logger.Error("SyncUser create user:%s error, error msg:%s", syncUserInfo.Account, err.Error()) + return nil, err + } + } + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, tx) + if err != nil { + tx.Rollback() + logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) + return nil, err + } + if userLogin != nil { + userLogin.LastLoginTime = time.Now().Unix() + err = orm.UpdateUserLogin(user.UID, userLogin, tx) + if err != nil { + tx.Rollback() + logger.Error("UpdateLoginInfo update user:%s login error, error msg:%s", user.UID, err.Error()) + return nil, err + } + } else { + err = orm.CreateUserLogin(&models.UserLogin{ + UID: user.UID, + LastLoginTime: time.Now().Unix(), + LoginId: getLoginId(user, config.AccountLocinType), + LoginType: int(config.AccountLocinType), + }, tx) + if err != nil { + tx.Rollback() + logger.Error("UpdateLoginInfo create user:%s login error, error msg:%s", user.UID, err.Error()) + return nil, err + } + } + err = tx.Commit().Error + if err != nil { + return nil, err + } + return user, nil +} -- Gitee From a0a994af2068663f5d27526614ae892b14245f5e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 29 Oct 2021 18:26:22 +0800 Subject: [PATCH 0365/2647] optimize workflow task logic Signed-off-by: liu deyi --- .../service/workflow/workflow_task.go | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f1b648cf..9e5d49cf 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -127,6 +127,31 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D resp := make(map[string][]commonmodels.DeployEnv) if prod.Source == setting.SourceFromExternal { services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) + + currentServiceNames := sets.NewString() + for _, service := range services { + currentServiceNames.Insert(service.ServiceName) + } + + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: prod.ProductName, + EnvName: prod.EnvName, + }) + + externalServiceNames := sets.NewString() + for _, serviceInExternalEnv := range servicesInExternalEnv { + if !currentServiceNames.Has(serviceInExternalEnv.ServiceName) { + externalServiceNames.Insert(serviceInExternalEnv.ServiceName) + } + } + + if len(externalServiceNames) > 0 { + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, "", externalServiceNames.List()...) + for _, service := range newServices { + services = append(services, service) + } + } + for _, service := range services { for _, container := range service.Containers { env := service.ServiceName + "/" + container.Name -- Gitee From daa30b0d57fc1ae6a97d51b647e8fb8f81e0753c Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 18:26:25 +0800 Subject: [PATCH 0366/2647] delete project Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/router.go | 2 +- pkg/shared/client/policy/policy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 22f1018f..1ddaebbb 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -55,7 +55,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings := router.Group("rolebindings") { roleBindings.POST("", CreateRoleBinding) - roleBindings.PUT("", UpdateRoleBinding) + roleBindings.PUT("/:name", UpdateRoleBinding) roleBindings.GET("", ListRoleBindings) roleBindings.DELETE("/:name", DeleteRoleBinding) roleBindings.POST("/bulk-delete", DeleteRoleBindings) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 8e9d4e8e..74fb132d 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -50,7 +50,7 @@ func (c *Client) ListRoleBindings(projectName string) ([]*RoleBinding, error) { } func (c *Client) CreateOrUpdateRoleBinding(projectName string, roleBinding *RoleBinding) error { - url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) + url := fmt.Sprintf("/rolebindings/%s?projectName=%s", roleBinding.Name, projectName) _, err := c.Put(url, httpclient.SetBody(roleBinding)) return err } -- Gitee From fca87305e20e557cb86a23f86463545f552e5453 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 18:32:22 +0800 Subject: [PATCH 0367/2647] put name project Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/handler/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 72a927c0..fcb5fd1f 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -27,7 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { projects.GET("", ListProjects) projects.POST("", CreateProject) - projects.PUT("", UpdateProject) + projects.PUT("/:name", UpdateProject) } workflows := router.Group("workflows") -- Gitee From d3d7c6f7cb24a2e24f233028d8862291cfcbfcb2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 18:35:50 +0800 Subject: [PATCH 0368/2647] marsh project Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/role_binding.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 0485cefd..c14d37ff 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -37,9 +37,10 @@ func UpdateRoleBinding(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") return } - args := &service.RoleBinding{} - + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("bind json fail %s") + } ctx.Err = service.UpdateOrCreateRoleBinding(projectName, args, ctx.Logger) } -- Gitee From f2af2573b61769d16624373ae9c155b0f34f8f1a Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 18:41:01 +0800 Subject: [PATCH 0369/2647] log err Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 4708d5f6..3dcf64f8 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -61,7 +61,7 @@ func UpdateProject(header http.Header, qs url.Values, body []byte, projectName s Role: string(setting.ReadOnly), Public: true, }); err != nil { - logger.Warnf("Failed to create role binding, err: %s", err) + logger.Errorf("Failed to create role binding, err: %s", err) return nil, err } } -- Gitee From c751e944d8654eab5116b087de1762614256a2e9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 19:00:03 +0800 Subject: [PATCH 0370/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 2 +- pkg/microservice/picket/core/filter/handler/project.go | 2 +- pkg/microservice/picket/core/filter/handler/router.go | 2 +- pkg/microservice/picket/core/filter/service/project.go | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 78a64f51..cabd3fc5 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -510,7 +510,7 @@ func ForkProduct(username, uid, requestID string, args *template.ForkProject, lo CreateBy: username, UpdateBy: username, } - err = policy.NewDefault().CreateRoleBinding(args.ProductName, &policy.RoleBinding{ + err = policy.NewDefault().CreateOrUpdateRoleBinding(args.ProductName, &policy.RoleBinding{ Name: fmt.Sprintf(setting.RoleBindingNameFmt, args.ProductName, uid, args.ProductName), UID: uid, Role: string(setting.Contributor), diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index e3bc33fa..5e8097f0 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -66,5 +66,5 @@ func DeleteProject(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.DeleteProject(c.Request.Header, c.Request.URL.Query(), c.Param("project"), ctx.Logger) + ctx.Resp, ctx.Err = service.DeleteProject(c.Request.Header, c.Request.URL.Query(), c.Param("name"), ctx.Logger) } diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 2d41dfd0..5ec37540 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -25,7 +25,7 @@ type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { projects := router.Group("projects") { - projects.DELETE("/:project", DeleteProject) + projects.DELETE("/:name", DeleteProject) projects.GET("", ListProjects) projects.POST("", CreateProject) projects.PUT("/:name", UpdateProject) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index e7a1deee..b76650b7 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -12,6 +12,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/client/opa" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/tool/log" ) type allowedProjectsData struct { @@ -97,8 +98,12 @@ func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) func DeleteProject(header http.Header, qs url.Values, productName string, logger *zap.SugaredLogger) ([]byte, error) { // delete roles and rolebindings - policy.NewDefault().DeleteRoleBindings([]string{"*"}, productName) - policy.NewDefault().DeleteRoles([]string{"*"}, productName) + if err := policy.NewDefault().DeleteRoleBindings([]string{"*"}, productName); err != nil { + log.Errorf("delete rolebindings err:%s", err) + } + if err := policy.NewDefault().DeleteRoles([]string{"*"}, productName); err != nil { + log.Errorf("delete roles err:%s", err) + } return aslan.New().DeleteProject(header, qs, productName) } -- Gitee From 87dec6457750cd8f9a79ae977f5f78c69ebda505 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 29 Oct 2021 19:32:14 +0800 Subject: [PATCH 0371/2647] refactor Signed-off-by: mouuii --- pkg/shared/client/policy/policy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 50233364..23cdbe5f 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -67,7 +67,7 @@ type NameArgs struct { func (c *Client) DeleteRoleBindings(names []string, projectName string) error { url := fmt.Sprintf("/rolebindings/bulk-delete?projectName=%s", projectName) - nameArgs := NameArgs{} + nameArgs := &NameArgs{} for _, v := range names { nameArgs.Names = append(nameArgs.Names, v) } @@ -77,7 +77,7 @@ func (c *Client) DeleteRoleBindings(names []string, projectName string) error { func (c *Client) DeleteRoles(names []string, projectName string) error { url := fmt.Sprintf("/roles/bulk-delete?projectName=%s", projectName) - nameArgs := NameArgs{} + nameArgs := &NameArgs{} for _, v := range names { nameArgs.Names = append(nameArgs.Names, v) } -- Gitee From 35335d4959e0ffe6248ee629dad4361205fe378e Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 29 Oct 2021 19:51:47 +0800 Subject: [PATCH 0372/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/config/consts.go | 2 +- pkg/microservice/user/core/handler/user/user.go | 6 +++--- pkg/microservice/user/core/service/user/user.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/user/config/consts.go b/pkg/microservice/user/config/consts.go index 6f13e8e9..4935cbe9 100644 --- a/pkg/microservice/user/config/consts.go +++ b/pkg/microservice/user/config/consts.go @@ -28,5 +28,5 @@ const ( type LoginType int const ( - AccountLocinType LoginType = 0 + AccountLoginType LoginType = 0 ) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 697dba0c..b103d84f 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -1,12 +1,12 @@ package user import ( - "fmt" - "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + + e "github.com/koderover/zadig/pkg/tool/errors" ) func GetUser(c *gin.Context) { @@ -20,7 +20,7 @@ func GetPersonalUser(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() uid := c.Param("uid") if ctx.UserID != uid { - ctx.Err = fmt.Errorf("token uid doesn't match uid") + ctx.Err = e.ErrForbidden } ctx.Resp, ctx.Err = user.GetUser(uid, ctx.Logger) } diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 6f93566e..016357fa 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -183,8 +183,8 @@ func CreateUser(args *User, logger *zap.SugaredLogger) (*models.User, error) { UID: user.UID, Password: string(hashedPassword), LastLoginTime: 0, - LoginId: getLoginId(user, config.AccountLocinType), - LoginType: int(config.AccountLocinType), + LoginId: getLoginId(user, config.AccountLoginType), + LoginType: int(config.AccountLoginType), } err = orm.CreateUserLogin(userLogin, tx) if err != nil { -- Gitee From a4945ab9b8071c000b0e017093da1e987385b5e2 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 29 Oct 2021 19:57:21 +0800 Subject: [PATCH 0373/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/user/user.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index b103d84f..530b434d 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -21,6 +21,7 @@ func GetPersonalUser(c *gin.Context) { uid := c.Param("uid") if ctx.UserID != uid { ctx.Err = e.ErrForbidden + return } ctx.Resp, ctx.Err = user.GetUser(uid, ctx.Logger) } -- Gitee From 7b01caa3e488e598696f4ecb74f2816441f2d0f2 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 29 Oct 2021 20:06:53 +0800 Subject: [PATCH 0374/2647] return 401 if not authenticated Signed-off-by: lou --- .../core/service/bundle/rego/authz.rego | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index cec268d7..5e726706 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -5,11 +5,31 @@ import input.attributes.request.http as http_request # Policy rule definitions in rbac style which is consumed by OPA server. # you can use it to: # 1. decide if a request is allowed by querying: rbac.allow -# 2. get all visible projects for an authenticated user by querying: rbac.user_visible_projects -# 3. get all allowed projects for a certain action(method+endpoint) for an authenticated user by querying: rbac.user_allowed_projects -# 4. check if a user is system admin by querying: rbac.user_is_admin -# 5. check if a user is project admin by querying: rbac.user_is_project_admin +# 2. decide if a request is allowed and get the status code by querying: rbac.response +# 3. get all visible projects for an authenticated user by querying: rbac.user_visible_projects +# 4. get all allowed projects for a certain action(method+endpoint) for an authenticated user by querying: rbac.user_allowed_projects +# 5. check if a user is system admin by querying: rbac.user_is_admin +# 6. check if a user is project admin by querying: rbac.user_is_project_admin +default response = { + "allowed": false, + "http_status": 403 +} + +response = r { + not is_authenticated + r := { + "allowed": false, + "http_status": 401 + } +} + +response = r { + allow + r := { + "allowed": true, + } +} # By default, deny requests. default allow = false @@ -21,8 +41,8 @@ allow { # Allow all valid users to visit exempted urls. allow { + is_authenticated url_is_exempted - claims.uid != "" } # Allow admins to do anything. @@ -196,6 +216,11 @@ bearer_token := t { t := substring(v, count("Bearer "), -1) } +is_authenticated { + claims + claims.uid != "" +} + envs := env { env := opa.runtime()["env"] } -- Gitee From 7d0b5c984c567d9fbc8d6b93b2da43b162ba6fb8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 29 Oct 2021 20:16:00 +0800 Subject: [PATCH 0375/2647] fix bug Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 11 ++++++----- pkg/microservice/aslan/core/service/service/helm.go | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 829be05b..2e9ae5f9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2834,17 +2834,18 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceObj, err := commonrepo.NewServiceColl().Find(opt) if err != nil { log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) - errList = multierror.Append(errList, err) + errList = multierror.Append(errList, errors.Wrapf(err, "failed to find template servce %s", service.ServiceName)) return } //TODO for little optimize - time.Sleep(time.Millisecond * 1500 * time.Duration(index)) + time.Sleep(time.Millisecond * 2500 * time.Duration(index)) //err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) + //TODO service update may fail, should modify data err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) if err != nil { - errList = multierror.Append(errList, err) + errList = multierror.Append(errList, errors.Wrapf(err, "failed to install of upgrade service %s", service.ServiceName)) return } }(svc) @@ -3081,11 +3082,11 @@ func updateProductVariable(productName, envName string, productResp *commonmodel //err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, Timeout*time.Second*10, helmClient) //TODO for little optimize - time.Sleep(time.Millisecond * 1500 * time.Duration(index)) + time.Sleep(time.Millisecond * 2500 * time.Duration(index)) err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, 0, helmClient) if err != nil { - errList = multierror.Append(errList, err) + errList = multierror.Append(errList, errors.Wrapf(err, "failed to upgrade service %s", currentService.ServiceName)) return } }(renderChart, serviceObj) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 98b718cc..82fb5047 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -600,7 +600,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi Source: setting.SourceFromChartTemplate, HelmTemplateName: templateChartData.TemplateName, ValuePaths: []string{path}, - ValuesYaml: string(templateChartData.DefaultValuesYAML), + ValuesYaml: string(valuesYAML), }, logger, ) @@ -682,7 +682,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - valuesYaml := args.ValuesYaml + valuesYaml := args.MergedValues valuesMap := make(map[string]interface{}) err = yaml.Unmarshal([]byte(valuesYaml), &valuesMap) if err != nil { -- Gitee From 0bdabbc3aeadb381a34a6c747059b13f1cb4a86d Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 29 Oct 2021 21:03:49 +0800 Subject: [PATCH 0376/2647] fix issue Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 5e726706..f35dd344 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -18,6 +18,7 @@ default response = { response = r { not is_authenticated + not url_is_public r := { "allowed": false, "http_status": 401 -- Gitee From 2ea6f74ed74ba9e3ec51537e22c622725fa87df3 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 29 Oct 2021 21:22:55 +0800 Subject: [PATCH 0377/2647] add user-sync Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 1 + pkg/config/config.go | 8 +-- pkg/microservice/user/core/handler/router.go | 2 + .../user/core/handler/user/user.go | 70 +++++++++++++++++++ .../user/core/repository/orm/user.go | 13 ++-- .../user/core/service/user/user.go | 10 +-- pkg/shared/client/systemconfig/client.go | 40 +++++++++++ .../client/systemconfig/systemconfig.go | 18 +++++ 8 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 pkg/shared/client/systemconfig/client.go create mode 100644 pkg/shared/client/systemconfig/systemconfig.go diff --git a/go.mod b/go.mod index c281428b..140b7fbf 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/garyburd/redigo v1.6.2 // indirect github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 + github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible diff --git a/pkg/config/config.go b/pkg/config/config.go index 88dd3561..7dfa9703 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -150,15 +150,15 @@ func PoetryServiceInfo() *setting.ServiceInfo { return GetServiceByCode(setting.Poetry) } -func ConfigServiceInfo() *setting.ServiceInfo { - return GetServiceByCode(setting.Poetry) -} - func PoetryServiceAddress() string { s := PoetryServiceInfo() return GetServiceAddress(s.Name, s.Port) } +func ConfigServiceInfo() *setting.ServiceInfo { + return GetServiceByCode(setting.Config) +} + func ConfigServiceAddress() string { s := ConfigServiceInfo() return GetServiceAddress(s.Name, s.Port) diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index beb9e0e3..5b191c85 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -39,5 +39,7 @@ func (*Router) Inject(router *gin.RouterGroup) { users.GET("/users/:uid/personal", user.GetPersonalUser) users.POST("/users/search", user.ListUsers) + + users.POST("/users/ldap/:ldapId", user.SyncLdapUser) } } diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 697dba0c..642fcc9d 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -3,12 +3,82 @@ package user import ( "fmt" + "github.com/dexidp/dex/connector/ldap" "github.com/gin-gonic/gin" + "go.uber.org/zap" + ldapv3 "github.com/go-ldap/ldap/v3" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) +func searchAndSyncUser(si *service.Connector, logger *zap.SugaredLogger) error { + if si == nil || si.Config == nil { + logger.Error("can't find connector") + return fmt.Errorf("can't find connector") + } + config, _ := si.Config.(*ldap.Config) + l, err := ldapv3.Dial("tcp", config.Host) + if err != nil { + logger.Error(err) + return err + } + defer l.Close() + + err = l.Bind(config.BindDN, config.BindPW) + if err != nil { + logger.Error(err) + return err + } + + searchRequest := ldapv3.NewSearchRequest( + config.GroupSearch.BaseDN, + ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, + config.GroupSearch.Filter, // The filter to apply + []string{config.UserSearch.NameAttr, config.UserSearch.EmailAttr}, // A list attributes to retrieve + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + logger.Error(err) + return err + } + + for _, entry := range sr.Entries { + _, err := user.SyncUser(&user.SyncUserInfo{ + Account: entry.GetAttributeValue(config.UserSearch.NameAttr), + IdentityType: si.ID, + }, logger) + if err != nil { + return err + } + } + return nil +} + +func SyncLdapUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Logger.Info("SyncLdapUser start") + ldapId := c.Param("ldapId") + systemConfigClient := systemconfig.New() + si, err := systemConfigClient.GetConnector(ldapId) + if err != nil { + ctx.Logger.Error(err) + ctx.Err = err + return + } + if err != nil { + ctx.Err = err + return + } + ctx.Err = searchAndSyncUser(si, ctx.Logger) +} + func GetUser(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index faeda529..b70ffa9e 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -42,16 +42,13 @@ func GetUserByUid(uid string, db *gorm.DB) (*models.User, error) { } // ListUsers gets a list of users based on paging constraints -func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { +func ListUsers(page int, perPage int, account string, db *gorm.DB) ([]models.User, error) { var ( users []models.User err error ) - if page > 0 && perPage > 0 { - err = db.Where("name LIKE ?", "%"+name+"%").Find(&users).Offset((page - 1) * perPage).Limit(perPage).Error - } else { - err = db.Where("name LIKE ?", "%"+name+"%").Find(&users).Error - } + + err = db.Where("account LIKE ?", "%"+account+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err @@ -77,14 +74,14 @@ func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { } // GetUsersCount gets user count -func GetUsersCount(name string) (int64, error) { +func GetUsersCount(account string) (int64, error) { var ( users []models.User err error count int64 ) - err = core.DB.Where("name LIKE ?", "%"+name+"%").Find(&users).Count(&count).Error + err = core.DB.Where("account LIKE ?", "%"+account+"%").Find(&users).Count(&count).Error if err != nil { return 0, err diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 6f93566e..dd8423b0 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -23,7 +23,7 @@ type User struct { } type QueryArgs struct { - Name string `json:"name,omitempty"` + Account string `json:"account,omitempty"` UIDs []string `json:"uids,omitempty"` PerPage int `json:"per_page,omitempty"` Page int `json:"page,omitempty"` @@ -72,9 +72,9 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { } func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { - count, err := orm.GetUsersCount(args.Name) + count, err := orm.GetUsersCount(args.Account) if err != nil { - logger.Errorf("SeachUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) + logger.Errorf("SeachUsers GetUsersCount By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err } if count == 0 { @@ -83,9 +83,9 @@ func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) }, nil } - users, err := orm.ListUsers(args.Page, args.PerPage, args.Name, core.DB) + users, err := orm.ListUsers(args.Page, args.PerPage, args.Account, core.DB) if err != nil { - logger.Errorf("SeachUsers SeachUsers By name:%s error, error msg:%s", args.Name, err.Error()) + logger.Errorf("SeachUsers SeachUsers By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err } var uids []string diff --git a/pkg/shared/client/systemconfig/client.go b/pkg/shared/client/systemconfig/client.go new file mode 100644 index 00000000..cc9052c5 --- /dev/null +++ b/pkg/shared/client/systemconfig/client.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package systemconfig + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.ConfigServiceAddress() + c := httpclient.New( + httpclient.SetHostURL(host + "/api/v1"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go new file mode 100644 index 00000000..f75eb830 --- /dev/null +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -0,0 +1,18 @@ +package systemconfig + +import ( + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) GetConnector(id string) (*service.Connector, error) { + url := "/connectors/" + id + + res := &service.Connector{} + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, err +} -- Gitee From bc6b179fd2da04e04703bd5ff4ceca49896a27ad Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 29 Oct 2021 21:35:31 +0800 Subject: [PATCH 0378/2647] remove org_id Signed-off-by: lou --- .../aslan/core/common/repository/models/pipeline.go | 2 -- .../aslan/core/common/repository/models/queue.go | 2 -- .../core/common/repository/models/registry_namespace.go | 4 ---- .../aslan/core/common/repository/models/task/model.go | 2 -- .../aslan/core/common/repository/models/template/product.go | 1 - .../core/common/repository/mongodb/registry_namespace.go | 5 ----- .../aslan/core/workflow/service/workflow/convert.go | 2 -- .../aslan/core/workflow/service/workflow/pipeline_task.go | 1 - pkg/microservice/cron/core/service/types.go | 2 -- pkg/microservice/warpdrive/core/service/types/task/build.go | 1 - pkg/microservice/warpdrive/core/service/types/task/model.go | 2 -- pkg/shared/codehost/codehost.go | 1 - pkg/shared/poetry/codehost.go | 3 +-- pkg/shared/poetry/jira.go | 2 +- 14 files changed, 2 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/pipeline.go b/pkg/microservice/aslan/core/common/repository/models/pipeline.go index 40cfe156..8be7b23f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/pipeline.go +++ b/pkg/microservice/aslan/core/common/repository/models/pipeline.go @@ -32,8 +32,6 @@ type Pipeline struct { Enabled bool `bson:"enabled" json:"enabled"` TeamName string `bson:"team" json:"team"` ProductName string `bson:"product_name" json:"product_name"` - // OrgID 单租户ID - OrgID int `bson:"org_id" json:"org_id"` // target 服务名称, k8s为容器名称, 物理机为服务名 Target string `bson:"target" json:"target"` // 使用预定义编译管理模块中的内容生成SubTasks, diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index d6279a8b..20b45845 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -67,8 +67,6 @@ type Queue struct { ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` - // OrgID 单租户ID - OrgID int `bson:"org_id,omitempty" json:"org_id,omitempty"` Services [][]*ProductService `bson:"services" json:"services"` Render *RenderInfo `bson:"render" json:"render"` StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index 3c45ae98..707493e3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -24,7 +24,6 @@ import ( type RegistryNamespace struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - OrgID int `bson:"org_id" json:"org_id"` RegAddr string `bson:"reg_addr" json:"reg_addr"` RegType string `bson:"reg_type" json:"reg_type"` RegProvider string `bson:"reg_provider" json:"reg_provider"` @@ -38,9 +37,6 @@ type RegistryNamespace struct { } func (ns *RegistryNamespace) Validate() error { - if ns.OrgID == 0 { - return errors.New("empty org_id") - } if ns.RegAddr == "" { return errors.New("empty reg_addr") diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index b71dabdb..c503d9c6 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -70,8 +70,6 @@ type Task struct { // ConfigPayload 系统配置信息 ConfigPayload *models.ConfigPayload `json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` - // OrgID 单租户ID - OrgID int `bson:"org_id,omitempty" json:"org_id,omitempty"` Services [][]*models.ProductService `bson:"services" json:"services"` Render *models.RenderInfo `bson:"render" json:"render"` StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 1554cd90..73d4cc95 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -68,7 +68,6 @@ type Product struct { LatestWorkflowUpdateTime int64 `bson:"-" json:"latest_workflow_update_time"` LatestWorkflowUpdateBy string `bson:"-" json:"latest_workflow_update_by"` TotalEnvTemplateServiceNum int `bson:"-" json:"total_env_template_service_num"` - ShowProject bool `bson:"-" json:"show_project"` IsOpensource bool `bson:"is_opensource" json:"is_opensource"` CustomImageRule *CustomRule `bson:"custom_image_rule,omitempty" json:"custom_image_rule,omitempty"` CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go index 539da8c3..d3df395c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/registry_namespace.go @@ -33,7 +33,6 @@ import ( type FindRegOps struct { ID string `json:"id"` - OrgID int `json:"org_id"` RegAddr string `json:"reg_addr"` RegType string `json:"reg_type"` RegProvider string `json:"reg_provider"` @@ -97,10 +96,6 @@ func (opt FindRegOps) getQuery() bson.M { query["is_default"] = true } - if opt.OrgID != 0 { - query["org_id"] = opt.OrgID - } - if opt.RegType != "" { query["reg_type"] = opt.RegType } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 81162742..7d98f8c9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -61,7 +61,6 @@ func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { ServiceTaskArgs: queueTask.ServiceTaskArgs, ConfigPayload: queueTask.ConfigPayload, Error: queueTask.Error, - OrgID: queueTask.OrgID, Services: queueTask.Services, Render: queueTask.Render, StorageURI: queueTask.StorageURI, @@ -107,7 +106,6 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { ServiceTaskArgs: task.ServiceTaskArgs, ConfigPayload: task.ConfigPayload, Error: task.Error, - OrgID: task.OrgID, Services: task.Services, Render: task.Render, StorageURI: task.StorageURI, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 906a7fbe..556a23a8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -196,7 +196,6 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C MultiRun: pipeline.MultiRun, BuildModuleVer: pipeline.BuildModuleVer, Target: pipeline.Target, - OrgID: pipeline.OrgID, StorageURI: defaultStorageURI, } diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 2fdddf7c..ecce072b 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -439,8 +439,6 @@ type Pipeline struct { Enabled bool `bson:"enabled" json:"enabled"` TeamName string `bson:"team" json:"team"` ProductName string `bson:"product_name" json:"product_name"` - // OrgID 单租户ID - OrgID int `bson:"org_id" json:"org_id"` // target 服务名称, k8s为容器名称, 物理机为服务名 Target string `bson:"target" json:"target"` // 使用预定义编译管理模块中的内容生成SubTasks, diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index eed15c97..e43541c7 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -76,7 +76,6 @@ type Install struct { } type RegistryNamespace struct { - OrgID int `bson:"org_id" json:"org_id"` RegAddr string `bson:"reg_addr" json:"reg_addr"` RegType string `bson:"reg_type" json:"reg_type"` RegProvider string `bson:"reg_provider" json:"reg_provider"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 8fc7400a..d21c91cc 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -67,8 +67,6 @@ type Task struct { // ConfigPayload 系统配置信息 ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` - // OrgID 单租户ID - OrgID int `bson:"org_id,omitempty" json:"org_id,omitempty"` Services [][]*ProductService `bson:"services" json:"services"` Render *RenderInfo `bson:"render" json:"render"` StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` diff --git a/pkg/shared/codehost/codehost.go b/pkg/shared/codehost/codehost.go index a266fd4a..f86b0803 100644 --- a/pkg/shared/codehost/codehost.go +++ b/pkg/shared/codehost/codehost.go @@ -34,7 +34,6 @@ const ( type CodeHost struct { ID int `json:"id"` - OrgID int `json:"orgId"` Address string `json:"address"` Type string `json:"type"` AccessToken string `json:"accessToken"` diff --git a/pkg/shared/poetry/codehost.go b/pkg/shared/poetry/codehost.go index add22aa3..f095f9c6 100644 --- a/pkg/shared/poetry/codehost.go +++ b/pkg/shared/poetry/codehost.go @@ -22,7 +22,6 @@ import ( type CodeHost struct { ID int `json:"id"` - OrgID int `json:"orgId"` Address string `json:"address"` Type string `json:"type"` AccessToken string `json:"accessToken"` @@ -38,7 +37,7 @@ func (c *Client) ListCodeHosts() ([]*CodeHost, error) { url := "/directory/codehostss/search" codeHosts := make([]*CodeHost, 0) - _, err := c.Get(url, httpclient.SetResult(&codeHosts), httpclient.SetQueryParam("orgId", "1")) + _, err := c.Get(url, httpclient.SetResult(&codeHosts)) if err != nil { return nil, err } diff --git a/pkg/shared/poetry/jira.go b/pkg/shared/poetry/jira.go index 8cb13177..08827995 100644 --- a/pkg/shared/poetry/jira.go +++ b/pkg/shared/poetry/jira.go @@ -34,7 +34,7 @@ func (c *Client) GetJiraInfo() (*JiraInfo, error) { url := "/directory/jira" jira := &JiraInfo{} - _, err := c.Get(url, httpclient.SetResult(jira), httpclient.SetQueryParam("orgId", "1")) + _, err := c.Get(url, httpclient.SetResult(jira)) if err != nil { return nil, err } -- Gitee From 2879462fd23582b0990fac4ad360d47dac292707 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 11:06:52 +0800 Subject: [PATCH 0379/2647] optimize update project logic Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/service.go | 9 +++++---- pkg/microservice/aslan/core/common/service/product.go | 2 +- pkg/microservice/aslan/core/project/service/product.go | 7 ++++++- pkg/microservice/aslan/core/service/service/service.go | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index b6bb4287..572ef8ac 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -308,9 +308,6 @@ func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string, servi } func (c *ServiceColl) BatchUpdateExternalServicesStatus(productName, envName, status string, serviceNames []string) error { - if envName == "" { - return fmt.Errorf("envName is empty") - } if productName == "" { return fmt.Errorf("productName is empty") } @@ -319,7 +316,11 @@ func (c *ServiceColl) BatchUpdateExternalServicesStatus(productName, envName, st return fmt.Errorf("servicenNames is empty") } - query := bson.M{"product_name": productName, "env_name": envName, "service_name": bson.M{"$in": serviceNames}} + query := bson.M{"product_name": productName, "service_name": bson.M{"$in": serviceNames}} + if envName != "" { + query["env_name"] = envName + } + change := bson.M{"$set": bson.M{ "status": status, }} diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 2ad2afcd..988de3ae 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -179,7 +179,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su deleteServices.Insert(currentEnvService.ServiceName) } } - err = commonrepo.NewServiceColl().BatchUpdateExternalServicesStatus(productName, envName, setting.ProductStatusDeleting, deleteServices.List()) + err = commonrepo.NewServiceColl().BatchUpdateExternalServicesStatus(productName, "", setting.ProductStatusDeleting, deleteServices.List()) if err != nil { log.Errorf("UpdateStatus external services error:%s", err) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 98ad31dc..0045b5f9 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -605,7 +605,12 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(v) } }() - + // delete servicesInExternalEnv data + go func() { + _ = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + }) + }() return nil } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index b5e52328..ecac78c6 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -555,7 +555,7 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za //获取项目里面的所有服务 if len(productTempl.Services) > 0 && !sets.NewString(productTempl.Services[0]...).Has(args.ServiceName) { productTempl.Services[0] = append(productTempl.Services[0], args.ServiceName) - } else { + } else if len(productTempl.Services) == 0 { productTempl.Services = [][]string{{args.ServiceName}} } //更新项目模板 -- Gitee From a200f8e566872e7cfb5fe3526de4bef01b4b81b1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 1 Nov 2021 11:54:40 +0800 Subject: [PATCH 0380/2647] optmize temp code Signed-off-by: allenshen --- .../core/environment/service/environment.go | 130 ++++++++++-------- 1 file changed, 76 insertions(+), 54 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 2e9ae5f9..0cb9d4b9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -25,6 +25,7 @@ import ( "sort" "strings" "sync" + "sync/atomic" "time" "github.com/hashicorp/go-multierror" @@ -2741,6 +2742,35 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } +func intervalExecutor(interval time.Duration, serviceList []interface{}, handler func(data interface{}, log *zap.SugaredLogger) error, log *zap.SugaredLogger) *multierror.Error { + if len(serviceList) == 0 { + return nil + } + wg := sync.WaitGroup{} + errList := new(multierror.Error) + var executeIndex int32 + wg.Add(len(serviceList)) + ticker := time.NewTicker(interval) + defer ticker.Stop() + go func() { + for ; true; <-ticker.C { + go func() { + defer wg.Done() + err := handler(serviceList[int(atomic.LoadInt32(&executeIndex))], log) + if err != nil { + errList = multierror.Append(errList, err) + } + }() + atomic.AddInt32(&executeIndex, 1) + if int(atomic.LoadInt32(&executeIndex)) >= len(serviceList) { + break + } + } + }() + wg.Wait() + return errList +} + func updateProductGroup(username, productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { var ( renderChartMap = make(map[string]*template.RenderChart) @@ -2803,56 +2833,48 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } + handler := func(data interface{}, log *zap.SugaredLogger) error { + service := data.(*commonmodels.ProductService) + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: service.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) + return errors.Wrapf(err, "failed to find template servce %s", service.ServiceName) + } + renderChart, _ := renderChartMap[service.ServiceName] + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) + if err != nil { + return errors.Wrapf(err, "failed to install of upgrade service %s", service.ServiceName) + } + return nil + } + errList := new(multierror.Error) - sIndex := 0 for groupIndex, services := range productResp.Services { - var wg sync.WaitGroup + serviceList := make([]interface{}, 0) for _, svc := range services { - renderChart, ok := renderChartMap[svc.ServiceName] + _, ok := renderChartMap[svc.ServiceName] if !ok { continue } - // service is not in update list if !svcNameSet.Has(svc.ServiceName) { continue } + serviceList = append(serviceList, svc) + } - index := sIndex - - wg.Add(1) - go func(service *commonmodels.ProductService) { - defer wg.Done() - - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: service.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) - errList = multierror.Append(errList, errors.Wrapf(err, "failed to find template servce %s", service.ServiceName)) - return - } - - //TODO for little optimize - time.Sleep(time.Millisecond * 2500 * time.Duration(index)) - - //err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) - //TODO service update may fail, should modify data - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) - if err != nil { - errList = multierror.Append(errList, errors.Wrapf(err, "failed to install of upgrade service %s", service.ServiceName)) - return - } - }(svc) - sIndex++ + serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if serviceGroupErr != nil { + errList = multierror.Append(errList, serviceGroupErr.Errors...) } - wg.Wait() if err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, services); err != nil { log.Errorf("Failed to update service group %d, err: %s", groupIndex, err) errList = multierror.Append(errList, err) @@ -3056,13 +3078,23 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } + handler := func(data interface{}, log *zap.SugaredLogger) error { + service := data.(*commonmodels.Service) + renderChart, _ := renderChartMap[service.ServiceName] + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, helmClient) + if err != nil { + return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) + } + return err + } + errList := new(multierror.Error) - sIndex := 0 for groupIndex, services := range productResp.Services { + serviceList := make([]interface{}, 0) var wg sync.WaitGroup groupServices := make([]*commonmodels.ProductService, 0) for _, service := range services { - if renderChart, isExist := renderChartMap[service.ServiceName]; isExist { + if _, isExist := renderChartMap[service.ServiceName]; isExist { opt := &commonrepo.ServiceFindOption{ ServiceName: service.ServiceName, Type: service.Type, @@ -3075,22 +3107,12 @@ func updateProductVariable(productName, envName string, productResp *commonmodel log.Errorf("failed to find service %s, err %s", service.ServiceName, err.Error()) continue } - wg.Add(1) - index := sIndex - go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service) { - defer wg.Done() - //err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, Timeout*time.Second*10, helmClient) - - //TODO for little optimize - time.Sleep(time.Millisecond * 2500 * time.Duration(index)) + serviceList = append(serviceList, serviceObj) - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, currentService, 0, helmClient) - if err != nil { - errList = multierror.Append(errList, errors.Wrapf(err, "failed to upgrade service %s", currentService.ServiceName)) - return - } - }(renderChart, serviceObj) - sIndex++ + groupServiceErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if groupServiceErr != nil { + errList = multierror.Append(errList, groupServiceErr.Errors...) + } } groupServices = append(groupServices, service) } -- Gitee From e430cbff4e7def3cc0e69320d01468489ee46aa3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 13:52:41 +0800 Subject: [PATCH 0381/2647] optimize update service logic Signed-off-by: liu deyi --- .../mongodb/services_in_external_env.go | 15 ++++-- .../aslan/core/common/service/product.go | 13 ++--- .../aslan/core/service/service/service.go | 49 +++++++++++++++---- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go index 466decd6..955da3e0 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -67,11 +67,12 @@ func (c *ServicesInExternalEnvColl) Create(args *models.ServicesInExternalEnv) e } type ServicesInExternalEnvArgs struct { - ProductName string - ServiceName string - EnvName string - Namespace string - ClusterID string + ProductName string + ServiceName string + EnvName string + Namespace string + ClusterID string + ExcludeEnvName string } func (c *ServicesInExternalEnvColl) List(args *ServicesInExternalEnvArgs) ([]*models.ServicesInExternalEnv, error) { @@ -87,6 +88,10 @@ func (c *ServicesInExternalEnvColl) List(args *ServicesInExternalEnvArgs) ([]*mo query["env_name"] = args.EnvName } + if args.ExcludeEnvName != "" { + query["env_name"] = bson.M{"$ne": args.ExcludeEnvName} + } + if args.Namespace != "" { query["namespace"] = args.Namespace } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 988de3ae..d7f2ef47 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -161,21 +161,22 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("failed to list external workload, error:%s", err) } - externnalEnvSerivces, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, + externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + ExcludeEnvName: envName, }) if err != nil { log.Errorf("failed to list external service, error:%s", err) } - externnalEnvSerivceM := make(map[string]bool) - for _, externnalEnvSerivce := range externnalEnvSerivces { - externnalEnvSerivceM[externnalEnvSerivce.ServiceName] = true + externalEnvServiceM := make(map[string]bool) + for _, externalEnvService := range externalEnvServices { + externalEnvServiceM[externalEnvService.ServiceName] = true } deleteServices := sets.NewString() for _, currentEnvService := range currentEnvServices { - if _, isExist := externnalEnvSerivceM[currentEnvService.ServiceName]; !isExist { + if _, isExist := externalEnvServiceM[currentEnvService.ServiceName]; !isExist { deleteServices.Insert(currentEnvService.ServiceName) } } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index ecac78c6..5b110f38 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -424,12 +424,12 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } // pre judge workLoads same name - services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") - for _, v := range services { - if addString.Has(v.ServiceName) { - return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", v.ServiceName)) - } - } + //services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") + //for _, v := range services { + // if addString.Has(v.ServiceName) { + // return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", v.ServiceName)) + // } + //} for _, v := range args.WorkLoads { if addString.Has(v.Name) { @@ -443,13 +443,28 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } + externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + ExcludeEnvName: envName, + }) + if err != nil { + log.Errorf("failed to list external service, error:%s", err) + } + + externalEnvServiceM := make(map[string]bool) + for _, externalEnvService := range externalEnvServices { + externalEnvServiceM[externalEnvService.ServiceName] = true + } + for _, v := range diff { switch v.Operation { // 删除workload的引用 case "delete": - err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) - if err != nil { - log.Errorf("UpdateStatus external services error:%s", err) + if _, isExist := externalEnvServiceM[v.Name]; !isExist { + err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) + if err != nil { + log.Errorf("UpdateStatus external services error:%s", err) + } } // 添加workload的引用 case "add": @@ -566,7 +581,21 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za } } } else { - return e.ErrCreateTemplate.AddDesc("do not support import same service name") + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return err + } + return commonrepo.NewServicesInExternalEnvColl().Create(&commonmodels.ServicesInExternalEnv{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + EnvName: args.EnvName, + Namespace: product.Namespace, + ClusterID: product.ClusterID, + }) + //return e.ErrCreateTemplate.AddDesc("do not support import same service name") } if err := commonrepo.NewServiceColl().Delete(args.ServiceName, args.Type, args.ProductName, setting.ProductStatusDeleting, 0); err != nil { -- Gitee From 7be756b2a6c29e7fb02689ffc5b4468efe3391ba Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 1 Nov 2021 13:55:49 +0800 Subject: [PATCH 0382/2647] optimize code Signed-off-by: allenshen --- .../core/environment/handler/renderset.go | 30 +++++++------------ .../core/environment/service/renderset.go | 21 +++++++++---- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 60b6e094..4defcca2 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -17,7 +17,6 @@ limitations under the License. package handler import ( - "strconv" "strings" "github.com/gin-gonic/gin" @@ -65,33 +64,24 @@ func GetYamlContent(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() var err error - codehostID := 0 - - codehostIDStr := c.Query("codehostID") - if len(codehostIDStr) > 0 { - codehostID, err = strconv.Atoi(codehostIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("cannot convert codehost id to int") - return - } + arg := &service.YamlContentRequestArg{} + + err = c.ShouldBindQuery(arg) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } - if codehostID == 0 && len(c.Query("repoLink")) == 0 { + if arg.CodehostID == 0 && len(arg.RepoLink) == 0 { ctx.Err = e.ErrInvalidParam.AddDesc("neither codehost nor repo link is specified") return } - if len(c.Query("valuesPaths")) == 0 { + if len(arg.ValuesPaths) == 0 { ctx.Err = e.ErrInvalidParam.AddDesc("paths can't be empty") return } - pathArr := strings.Split(c.Query("valuesPaths"), ",") - var ( - owner = c.Query("owner") - repo = c.Query("repo") - branch = c.Query("branch") - repoLink = c.Query("repoLink") - ) - ctx.Resp, ctx.Err = service.GetMergedYamlContent(codehostID, owner, repo, branch, repoLink, pathArr) + pathArr := strings.Split(arg.ValuesPaths, ",") + ctx.Resp, ctx.Err = service.GetMergedYamlContent(arg, pathArr) } diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 3310982b..442affa4 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -37,6 +37,15 @@ type DefaultValuesResp struct { DefaultValues string `json:"defaultValues"` } +type YamlContentRequestArg struct { + CodehostID int `json:"codehostID"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Branch string `json:"branch"` + RepoLink string `json:"repoLink"` + ValuesPaths string `json:"valuesPaths"` +} + func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*commonservice.RenderChartArg, error) { renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") @@ -113,7 +122,7 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def return ret, nil } -func GetMergedYamlContent(codehostID int, owner, repo, branch, repoLink string, paths []string) (string, error) { +func GetMergedYamlContent(arg *YamlContentRequestArg, paths []string) (string, error) { var ( fileContentMap sync.Map wg sync.WaitGroup @@ -125,12 +134,12 @@ func GetMergedYamlContent(codehostID int, owner, repo, branch, repoLink string, defer wg.Done() fileContent, errDownload := fsservice.DownloadFileFromSource( &fsservice.DownloadFromSourceArgs{ - CodehostID: codehostID, - Owner: owner, - Repo: repo, + CodehostID: arg.CodehostID, + Owner: arg.Owner, + Repo: arg.Repo, Path: path, - Branch: branch, - RepoLink: repoLink, + Branch: arg.Branch, + RepoLink: arg.RepoLink, }) if errDownload != nil { err = errors.Wrapf(errDownload, fmt.Sprintf("fail to download file from git, path %s", path)) -- Gitee From 7de989360f2c41e62d69a3e80cf9cdfad412e5c5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 1 Nov 2021 13:57:27 +0800 Subject: [PATCH 0383/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/renderset.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 4defcca2..d98f2671 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -63,11 +63,9 @@ func GetYamlContent(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - var err error arg := &service.YamlContentRequestArg{} - err = c.ShouldBindQuery(arg) - if err != nil { + if err := c.ShouldBindQuery(arg); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return } -- Gitee From 452b48cefa955a2ebb5710700df9e2fa05bb6628 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 14:08:29 +0800 Subject: [PATCH 0384/2647] optimize update service logic Signed-off-by: liu deyi --- .../common/repository/mongodb/services_in_external_env.go | 5 +++++ pkg/microservice/aslan/core/service/service/service.go | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go index 955da3e0..66865af8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -37,6 +37,7 @@ func (c *ServicesInExternalEnvColl) EnsureIndex(ctx context.Context) error { Keys: bson.D{ bson.E{Key: "product_name", Value: 1}, bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "service_name", Value: 1}, }, Options: options.Index().SetUnique(false), }, @@ -129,6 +130,10 @@ func (c *ServicesInExternalEnvColl) Delete(args *ServicesInExternalEnvArgs) erro query["env_name"] = args.EnvName } + if args.ServiceName != "" { + query["service_name"] = args.ServiceName + } + _, err := c.DeleteMany(context.TODO(), query) return err } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 5b110f38..47432771 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -465,6 +465,14 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN if err != nil { log.Errorf("UpdateStatus external services error:%s", err) } + } else { + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + ServiceName: v.Name, + }); err != nil { + log.Errorf("delete services in external env error:%s", err) + } } // 添加workload的引用 case "add": -- Gitee From 3cf4e28fb0e8cd9f526e4d334b0539386062ef1b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 14:20:24 +0800 Subject: [PATCH 0385/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 47432771..c14e39ba 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -455,11 +455,12 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN for _, externalEnvService := range externalEnvServices { externalEnvServiceM[externalEnvService.ServiceName] = true } - + log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) for _, v := range diff { switch v.Operation { // 删除workload的引用 case "delete": + log.Infof("delete ServiceName:%s", v.Name) if _, isExist := externalEnvServiceM[v.Name]; !isExist { err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) if err != nil { -- Gitee From 9b80d21be3f881dc7eea149b7682ade00f040be2 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Mon, 1 Nov 2021 14:27:14 +0800 Subject: [PATCH 0386/2647] merge main to nga (#503) * test labeler Signed-off-by: landylee007 * test labeler Signed-off-by: landylee007 * test labeler Signed-off-by: landylee007 * test labeler Signed-off-by: landylee007 * test labeler Signed-off-by: landylee007 * test labeler Signed-off-by: landylee007 * Feature/upgrade assistant (#343) * update upgrade logic Signed-off-by: allenshen * remove test code Signed-off-by: allenshen * Hotfix/environment update (#356) * Feature/upgrade assistant (#343) * update upgrade logic Signed-off-by: allenshen * remove test code Signed-off-by: allenshen * add filter serivce logic when updat Signed-off-by: allenshen * add helm service create data Signed-off-by: allenshen * add repo_link json tag Signed-off-by: allenshen * add service name response Signed-off-by: allenshen * use helm upgrade to update container image Signed-off-by: allenshen * add load bulk service from chart template Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * add list tar interface Signed-off-by: liu deyi * optimize list tar interface Signed-off-by: liu deyi * artifact args add url field Signed-off-by: liu deyi * download artifact file Signed-off-by: liu deyi * modify workspace Signed-off-by: liu deyi * optimize create artifact logic Signed-off-by: liu deyi * optimize create artifact logic Signed-off-by: liu deyi * add artifact sort Signed-off-by: liu deyi * modify artifact deploy logic Signed-off-by: liu deyi * modify subtask logic Signed-off-by: liu deyi * add artifact deploy stage Signed-off-by: liu deyi * modify artifact deploy stage Signed-off-by: liu deyi * optimize artifact deploy stage Signed-off-by: liu deyi * modify field Signed-off-by: liu deyi * add pipeline name field Signed-off-by: liu deyi * add product name field Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * update replace image logic Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * fix compile error Signed-off-by: allenshen * remove useless code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optize err print format Signed-off-by: allenshen * optimize code Signed-off-by: liu deyi * build add productName field Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * optmize code Signed-off-by: allenshen * workflow add storage_id field Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * opmize code Signed-off-by: allenshen * fix share service bug Signed-off-by: liu deyi * fix create env bug Signed-off-by: liu deyi * fix create env bug Signed-off-by: liu deyi * fix create env bug Signed-off-by: liu deyi * fix service deploy bug Signed-off-by: liu deyi * fix build bug Signed-off-by: liu deyi * fix tar file rule bug Signed-off-by: liu deyi * modify reaper Signed-off-by: liu deyi * modify reaper Signed-off-by: liu deyi * modify reaper Signed-off-by: liu deyi * fix loop bug Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * modify list tar interface Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * modify artifact interface Signed-off-by: liu deyi * modify artifact interface Signed-off-by: liu deyi * add batch insert cvm host interface Signed-off-by: liu deyi * fix nil pointer Signed-off-by: lou * optimize code Signed-off-by: allenshen * validate bulk add privatekey function Signed-off-by: liu deyi * add labels interface Signed-off-by: liu deyi * modify bulk add priviateKey interface Signed-off-by: liu deyi * add host label by label Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * added dockerfile template api & basic implementations Signed-off-by: Min Min * debug Signed-off-by: Min Min * minor bugfixes Signed-off-by: Min Min * added dockerfile parser validation Signed-off-by: Min Min * validation stuff Signed-off-by: Min Min * fix broken dependency Signed-off-by: Min Min * added dockerfile template to build Signed-off-by: Min Min * add validation API for dockerfile template Signed-off-by: Min Min * changed validation api response Signed-off-by: Min Min * get reference api fixed Signed-off-by: Min Min * added dockerfile template function to workflow Signed-off-by: Min Min * added auth token for reaper Signed-off-by: Min Min * reaper bugfix Signed-off-by: Min Min * added some debug information Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * removed debug logs & bugfix Signed-off-by: Min Min * add debug logs to prevent nil Signed-off-by: Min Min * removed logs & finalize Signed-off-by: Min Min * set name of dockerfile template as unique Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * debug Signed-off-by: Min Min * debug on missing arg Signed-off-by: Min Min * dockerfile template Signed-off-by: Min Min * debug Signed-off-by: Min Min * modify list host ip interface Signed-off-by: liu deyi * change docker build command generate Signed-off-by: Min Min * debug Signed-off-by: Min Min * update the provider of privatekey Signed-off-by: liu deyi * Added _COMMIT_ID as workflow default parameter Signed-off-by: Min Min * update env config Signed-off-by: liu deyi * update env config Signed-off-by: liu deyi * CRUD for yaml template done Signed-off-by: Min Min * added create service from yaml template and reload service from yaml template Signed-off-by: Min Min * rebasing dockerfile_template Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * debug replace yaml and webhook process logic Signed-off-by: Min Min * added get reference API Signed-off-by: Min Min * debug Signed-off-by: Min Min * Make template that is in-use not able to be deleted Signed-off-by: Min Min * optimize code Signed-off-by: liu deyi * changed update logic Signed-off-by: Min Min * filter the case where the label is empty Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * added update yaml template varible values API Signed-off-by: Min Min * debugs Signed-off-by: Min Min * debugs Signed-off-by: Min Min * changed query reference logic Signed-off-by: Min Min * changed logic for loading service from template and list yaml templates api response Signed-off-by: Min Min * fix panic error Signed-off-by: allenshen * service module updates Signed-off-by: Min Min * add ingress display to service list Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * naming stuff Signed-off-by: Min Min * changed location Signed-off-by: Min Min * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * Modify the way to get the service Signed-off-by: liu deyi * debug coe Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * add Bootcamps info to readme Signed-off-by: landylee007 * add Bootcamps info to readme Signed-off-by: landylee007 * Update server.go * Update types.go * Update action.yml * add Bootcamps info to readme Signed-off-by: landylee007 * modify the number of pr list Signed-off-by: liu deyi * modify error message Signed-off-by: liu deyi * fix file download failure problem Signed-off-by: liu deyi * fix file download failure problem Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove code Signed-off-by: liu deyi * remove code Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * rename function name Signed-off-by: liu deyi * return err Signed-off-by: liu deyi * modify log download logic Signed-off-by: liu deyi * fix conflict Signed-off-by: lou Co-authored-by: landylee007 Co-authored-by: Landy Co-authored-by: shendongdong Co-authored-by: lilianzhu Co-authored-by: liu deyi Co-authored-by: Min Min --- README-zh-CN.md | 35 +- README.md | 37 +- go.mod | 17 +- go.sum | 1003 ++++++++++++++--- pkg/microservice/aslan/config/consts.go | 1 + .../aslan/core/code/service/merge_request.go | 5 +- .../core/common/repository/models/build.go | 6 + .../common/repository/models/private_key.go | 10 - .../core/common/repository/models/queue.go | 11 +- .../aslan/core/common/repository/models/s3.go | 8 + .../core/common/repository/models/service.go | 23 + .../common/repository/models/task/build.go | 17 +- .../repository/models/task/docker_build.go | 2 + .../repository/models/template/product.go | 14 +- .../core/common/repository/models/workflow.go | 51 +- .../common/repository/models/yaml_template.go | 19 + .../core/common/repository/mongodb/build.go | 15 + .../repository/mongodb/delivery_artifact.go | 64 +- .../common/repository/mongodb/private_key.go | 55 +- .../core/common/repository/mongodb/service.go | 18 +- .../repository/mongodb/yaml_template.go | 116 ++ .../aslan/core/common/service/environment.go | 71 +- .../core/common/service/gitlab/webhook.go | 5 +- .../aslan/core/common/service/render.go | 8 +- .../aslan/core/common/service/service.go | 8 +- .../core/environment/service/environment.go | 222 ++-- .../service/environment_creator.go | 2 +- .../aslan/core/environment/service/image.go | 356 +++++- .../aslan/core/log/service/log.go | 3 +- pkg/microservice/aslan/core/service.go | 4 + .../aslan/core/service/handler/helm.go | 14 + .../aslan/core/service/handler/router.go | 7 + .../core/service/handler/yaml_template.go | 42 + .../aslan/core/service/service/helm.go | 385 ++++++- .../aslan/core/service/service/pm.go | 2 +- .../aslan/core/service/service/service.go | 7 +- .../aslan/core/service/service/types.go | 51 +- .../core/service/service/yaml_template.go | 86 ++ .../aslan/core/system/handler/private_key.go | 36 + .../aslan/core/system/handler/router.go | 3 + .../aslan/core/system/handler/s3.go | 17 + .../aslan/core/system/service/private_key.go | 64 ++ .../aslan/core/system/service/s3.go | 75 ++ .../core/templatestore/handler/dockerfile.go | 128 +++ .../core/templatestore/handler/router.go | 22 + .../aslan/core/templatestore/handler/yaml.go | 107 ++ .../repository/models/dockerfile.go | 13 + .../repository/mongodb/dockerfile.go | 116 ++ .../core/templatestore/service/dockerfile.go | 171 +++ .../aslan/core/templatestore/service/types.go | 51 + .../aslan/core/templatestore/service/yaml.go | 212 ++++ .../aslan/core/workflow/handler/build.go | 7 +- .../core/workflow/handler/workflow_task.go | 12 +- .../service/webhook/github_workflow_task.go | 2 +- .../workflow/service/workflow/nsq_handlers.go | 6 +- .../service/workflow/pipeline_task.go | 39 +- .../service/workflow/pipeline_validation.go | 6 +- .../workflow/service/workflow/service_task.go | 7 +- .../workflow/service/workflow/workflow.go | 4 +- .../service/workflow/workflow_task.go | 154 ++- .../workflow/testing/handler/workspace.go | 2 +- .../workflow/testing/service/workspace.go | 17 +- pkg/microservice/cron/core/service/types.go | 1 + .../reaper/core/service/client/client.go | 25 + .../reaper/core/service/client/dockerfile.go | 44 + .../reaper/core/service/meta/types.go | 33 +- .../reaper/core/service/reaper/reaper.go | 65 +- .../reaper/core/service/reaper/script.go | 37 + pkg/microservice/reaper/executor/executor.go | 8 +- pkg/microservice/warpdrive/config/const.go | 1 + .../service/taskcontroller/task_helper.go | 1 + .../service/taskplugin/artifact_deploy.go | 369 ++++++ .../core/service/taskplugin/build.go | 19 + .../core/service/taskplugin/deploy.go | 50 +- .../core/service/taskplugin/docker_build.go | 2 + .../warpdrive/core/service/taskplugin/job.go | 12 +- .../core/service/types/environment.go | 2 +- .../warpdrive/core/service/types/reaper.go | 20 +- .../core/service/types/task/build.go | 19 +- .../core/service/types/task/config_payload.go | 1 + .../core/service/types/task/docker_build.go | 2 + .../core/service/types/task/model.go | 42 +- pkg/setting/consts.go | 36 + pkg/tool/errors/http_errors.go | 1 + pkg/tool/s3/client.go | 4 +- 85 files changed, 4190 insertions(+), 675 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/yaml_template.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go create mode 100644 pkg/microservice/aslan/core/service/handler/yaml_template.go create mode 100644 pkg/microservice/aslan/core/service/service/yaml_template.go create mode 100644 pkg/microservice/aslan/core/templatestore/handler/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/handler/yaml.go create mode 100644 pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/service/dockerfile.go create mode 100644 pkg/microservice/aslan/core/templatestore/service/yaml.go create mode 100644 pkg/microservice/reaper/core/service/client/client.go create mode 100644 pkg/microservice/reaper/core/service/client/dockerfile.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go diff --git a/README-zh-CN.md b/README-zh-CN.md index ecfe7fd7..beba21f0 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -44,25 +44,30 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery 产品特性介绍: -- **高并发的工作流** +
+ 高并发的工作流 + 基于云原生设计,经过简单配置,系统自动生成工作流,实现多服务高并发执行构建部署测试任务,以解决微服务架构下带来的多服务构建部署效率低下问题。 +
-基于云原生设计,经过简单配置,系统自动生成工作流,实现多服务高并发执行构建部署测试任务,以解决微服务架构下带来的多服务构建部署效率低下问题。 +
+ 以服务为核心的集成环境 + 一套服务配置,分钟级创建多套数据隔离的测试环境。为开发者进行日常调试、为测试人员做集成测试、为产品经理对外 Demo 提供强力支撑。 -- **以服务为核心的集成环境** + 对于现有的环境无需担心迁移成本,一键托管,轻松浏览、调试环境中的所有服务。 +
-一套服务配置,分钟级创建多套数据隔离的测试环境。为开发者进行日常调试、为测试人员做集成测试、为产品经理对外 Demo 提供强力支撑。 +
+ 无侵入的自动化测试 + 便捷且无侵入的对接已有自动化测试框架,通过 GitHub/GitLab Webhook 自动构建、部署及测试。 -对于现有的环境无需担心迁移成本,一键托管,轻松浏览、调试环境中的所有服务。 + 通过办公通讯机器人为开发者提供第一时间质量反馈,精准高效。有效落地“测试左移”工程实践,让测试价值得到体现。 +
-- **无侵入的自动化测试** +
+ 开发本地联调 CLI/IDE Plugin 插件 + 开发本地编辑完代码,一键进行本地代码构建,部署到联调环境,无需再陷入复杂且繁琐的工作流程。解放工程师双手,去创造更多产品价值。 +
-便捷且无侵入的对接已有自动化测试框架,通过 GitHub/GitLab Webhook 自动构建、部署及测试。 - -通过办公通讯机器人为开发者提供第一时间质量反馈,精准高效。有效落地“测试左移”工程实践,让测试价值得到体现。 - -- **开发本地联调 CLI** - -开发本地编辑完代码,一键进行本地代码构建,部署到联调环境,无需再陷入复杂且繁琐的工作流程,让本地联调不再成为难事。解放工程师双手,去创造更多产品价值。 ## 快速上手 @@ -70,6 +75,10 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery 请参阅 [快速入门](https://docs.koderover.com/zadig/quick-start/try-out-install) +### 训练营 + +Zadig [训练营](https://github.com/koderover/zadig-bootcamp)主要是为开发者提供实践小技巧、最佳实践案例的搭建、典型应用场景的演示等,以便快速获得持续交付最佳解决方案。可以直接进入 [教程](https://www.koderover.com/tutorials) 一步步实践和尝试。 + ### 快速开发 请阅读完整的 [Zadig 贡献指南](CONTRIBUTING-zh-CN.md),该包含参与贡献的方式、流程、格式、如何部署、哪里可以获取帮助等。 diff --git a/README.md b/README.md index 92c3a999..374fa483 100644 --- a/README.md +++ b/README.md @@ -47,25 +47,30 @@ The architecture is as follows: The Highlighted Features: -- **High Concurrency** +
+ High Concurrency + Based on cloud-native design, through simple configuration, the system automatically generates workflows to achieve high concurrent execution for continuous delivery relevant tasks such as building, testing and deployment, across multiple services. It significantly improves the efficiency of multi-services deployment in microservice architecture. +
-Based on cloud-native design, through simple configuration, the system automatically generates workflows to achieve high concurrent execution for continuous delivery relevant tasks such as building, testing and deployment, across multiple services. It significantly improves the efficiency of multi-services deployment in microservice architecture. +
+ Service-oriented Environment + With just one set of service configuration, multiple encapsulated environments will be provided automatically within minutes, empowering independent environments for developers, QAs and product managers. -- **Service-oriented Environment** + Minimum to none migration cost of existing environments -- just hosting with one click, the system allows browsing and adjusting all the services at your fingertips. +
-With just one set of service configuration, multiple encapsulated environments will be provided automatically within minutes, empowering independent environments for developers, QAs and product managers. +
+ Non-intrusive Testing Automation + Zadig can easily and non-intrusively embed existing testing automation frameworks, and achieve continuous building, testing and deployment via GitHub/GitLab Webhook. -Minimum to none migration cost of existing environments -- just hosting with one click, the system allows browsing and adjusting all the services at your fingertips. + It also integrates with productivity bots to provide instant quality report, which effectively applies shift-left testing best practices. +
-- **Non-intrusive Testing Automation** +
+ Convenient Development CLI/IDE Plugin + Zadig also provides a convenient toolkit with development commandline interface which allows compiling, building and deploying the changes to dev environment with one command. It enables collaborated debugging and testing with minimum manual toil, reduces cognitive load and allows teams to focus more on business. +
-Zadig can easily and non-intrusively embed existing testing automation frameworks, and achieve continuous building, testing and deployment via GitHub/GitLab Webhook. - -It also integrates with productivity bots to provide instant quality report, which effectively applies shift-left testing best practices. - -- **Convenient Development CLI** - -Zadig also provides a convenient toolkit with development commandline interface which allows compiling, building and deploying the changes to dev environment with one command. It enables collaborated debugging and testing with minimum manual toil, reduces cognitive load and allows teams to focus more on business. ## Quick start @@ -73,6 +78,12 @@ Zadig also provides a convenient toolkit with development commandline interface Please follow [Quick Start](https://docs.koderover.com/zadig/quick-start/try-out-install) +### Bootcamps + +With many hands-on tips, case studies and demos of different application types, Zadig [bootcamp](https://github.com/koderover/zadig-bootcamp) gives developers easy to follow best practices in a cloud native CI/CD solution + +Just follow [Tutorials](https://www.koderover.com/tutorials) to try it step by step. + ### How to make contribution? Please check out [our contributing guideline](CONTRIBUTING.md). diff --git a/go.mod b/go.mod index c281428b..61ad1a85 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/blang/semver/v4 v4.0.0 github.com/bndr/gojenkins v1.1.0 github.com/bradleyfalzon/ghinstallation v1.1.1 - github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 @@ -19,7 +18,7 @@ require ( github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b github.com/docker/distribution v2.7.1+incompatible - github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible + github.com/docker/docker v20.10.7+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.1 // indirect @@ -33,20 +32,19 @@ require ( github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.4.2 - github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/mittwald/go-helm-client v0.8.0 + github.com/mittwald/go-helm-client v0.8.2 + github.com/moby/buildkit v0.9.1 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/selinux v1.6.0 // indirect github.com/otiai10/copy v1.6.0 github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 @@ -61,14 +59,13 @@ require ( github.com/swaggo/gin-swagger v1.3.0 github.com/swaggo/swag v1.5.1 github.com/xanzy/go-gitlab v0.50.0 - github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect github.com/yvasiyarov/gorelic v0.0.7 // indirect github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect go.mongodb.org/mongo-driver v1.5.0 go.uber.org/zap v1.19.0 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 - golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a + golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/mholt/archiver.v3 v3.1.1 @@ -76,11 +73,11 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gorm.io/driver/mysql v1.1.2 gorm.io/gorm v1.21.16 - helm.sh/helm/v3 v3.6.2 + helm.sh/helm/v3 v3.7.1 k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 - k8s.io/kubectl v0.21.0 + k8s.io/kubectl v0.22.1 k8s.io/utils v0.0.0-20210802155522-efc7438f0176 sigs.k8s.io/controller-runtime v0.10.0 sigs.k8s.io/yaml v1.2.0 diff --git a/go.sum b/go.sum index b34685d1..e9ff3cff 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,15 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.2/go.mod h1:H8IAquKe2L30IxoupDgqTaQvKSwF/c8prYHynGIWQbA= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -41,21 +47,67 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= +contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0= +contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= +contrib.go.opencensus.io/integrations/ocsql v0.1.4/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= +contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc= +git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= +github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= github.com/AppsFlyer/go-sundheit v0.4.0/go.mod h1:iZ8zWMS7idcvmqewf5mEymWWgoOiG/0WD4+aeh+heX4= +github.com/Azure/azure-amqp-common-go/v2 v2.1.0/go.mod h1:R8rea+gJRuJR6QxTir/XuEd+YuKoUiazDC/N96FiDEU= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v19.1.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v30.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v42.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-service-bus-go v0.9.1/go.mod h1:yzBx6/BUGfjfeqbRZny9AQIbIe3AcV9WZbAdpkoXOa0= +github.com/Azure/azure-storage-blob-go v0.8.0/go.mod h1:lPI3aLPpuLTeUwh1sViKXFxwl2B6teiRqI0deQUvsw0= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v10.15.5+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v12.0.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.1.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= +github.com/Azure/go-autorest/autorest v0.10.2/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.8.3/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= +github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= +github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= +github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= @@ -64,73 +116,121 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/Djarvur/go-err113 v0.1.0/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= +github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/hcsshim v0.8.14 h1:lbPVK25c1cu5xTLITwpUcxoA9vKrKErASPYygvouJns= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17 h1:iT12IBVClFevaf8PuVyi3UmZOVh4OqnaLxDTW2O6j3w= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.10/go.mod h1:g5uw8EV2mAlzqe94tfNBNdr89fnbD/n3HV0OhsddkmM= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/RyanCarrier/dijkstra v1.1.0 h1:/NDihjfJA3CxFaZz8EdzTwdFKFZDvvB881OVLdakRcI= github.com/RyanCarrier/dijkstra v1.1.0/go.mod h1:5agGUBNEtUAGIANmbw09fuO3a2htPEkc1jNH01qxCWA= github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345 h1:fgSpoKViTSqRb4hjDNj10ig5wUvO0CayCzFdLf6fuRM= github.com/RyanCarrier/dijkstra-1 v0.0.0-20170512020943-0e5801a26345/go.mod h1:OK4EvWJ441LQqGzed5NGB6vKBAE34n3z7iayPcEwr30= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72 h1:uGeGZl8PxSq8VZGG4QK5njJTFA4/G/x5CYORvQVXtAE= github.com/albertorestifo/dijkstra v0.0.0-20160910063646-aba76f725f72/go.mod h1:o+JdB7VetTHjLhU0N57x18B9voDBQe0paApdEAEoEfw= +github.com/alecthomas/kingpin v2.2.6+incompatible/go.mod h1:59OFYbFVLKQKq+mqrL6Rw5bR0c3ACQaawgXx0QYndlE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 h1:2Qp3BQZ7r1z+oMUnFhD6FQ86Hh1PO+59yoUNNJ5Dn0M= github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= +github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs= +github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo= +github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE= +github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aws/aws-sdk-go v1.15.90/go.mod h1:es1KtYUFs7le0xQ3rOihkuoVD90z7D0fR2Qm4S00/gU= +github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -140,75 +240,192 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bndr/gojenkins v1.1.0 h1:TWyJI6ST1qDAfH33DQb3G4mD8KkrBfyfSUoZBHQAvPI= github.com/bndr/gojenkins v1.1.0/go.mod h1:QeskxN9F/Csz0XV/01IC8y37CapKKWvOHa0UHLLX1fM= +github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= +github.com/bombsimon/wsl/v2 v2.2.0/go.mod h1:Azh8c3XGEJl9LyX0/sFC+CKMc7Ssgua0g+6abzXN4Pg= +github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bradleyfalzon/ghinstallation v1.1.1 h1:pmBXkxgM1WeF8QYvDLT5kuQiHMcmf+X015GI0KM/E3I= github.com/bradleyfalzon/ghinstallation v1.1.1/go.mod h1:vyCmHTciHx/uuyN82Zc3rXN3X2KTK8nUTCrTMwAhcug= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I= github.com/bugsnag/bugsnag-go v2.1.0+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v1.3.1 h1:pmuhHlhbUV4OOrGDvoiMjHSZzwRcL+I9cIzYKiW4lII= github.com/bugsnag/panicwrap v1.3.1/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= +github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59 h1:qWj4qVYZ95vLWwqyNJCQg7rDsG5wPdze0UaPolH7DUk= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.0/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= -github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.3/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= +github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7 h1:6ejg6Lkk8dskcM7wQ28gONkukbQkM4qpj4RnYbpFzrI= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fuse-overlayfs-snapshotter v1.0.2/go.mod h1:nRZceC8a7dRm3Ao6cJAwuJWPFiBPaibHiFntRUnzhwU= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/stargz-snapshotter v0.0.0-20201027054423-3a04e4c2c116/go.mod h1:o59b3PCKVAf9jjiKtCc/9hLAd+5p/rfhBfm6aBcTEr4= +github.com/containerd/stargz-snapshotter v0.6.4/go.mod h1:1t0SF1gAHJhCSftWKDLVitvfF3c2qhL5hymG7C50wto= +github.com/containerd/stargz-snapshotter/estargz v0.6.4/go.mod h1:83VWDqHnurTKliEB0YvWMiCfLDwv4Cjj1X9Vk98GJZw= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/coocood/freecache v1.1.0 h1:ENiHOsWdj1BrrlPwblhbn4GdAsMymK3pZORJ+bJGAjA= github.com/coocood/freecache v1.1.0/go.mod h1:ePwxCDzOYvARfHdr1pByNct1at3CoKnsipOHwKlNbzI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -219,21 +436,36 @@ github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/deislabs/oras v0.11.1 h1:oo2J/3vXdcti8cjFi8ghMOkx0OacONxHC8dhJ17NdJ0= -github.com/deislabs/oras v0.11.1/go.mod h1:39lCtf8Q6WDC7ul9cnyWXONNzKvabEKk+AX+L0ImnQk= -github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b h1:ovHbNjGAQsGEs67tYU6C6ex2D2shDkeZ7pPprx58f2k= github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b/go.mod h1:g64CEwk9b4oLTREOu8mFkjTkDUvyxzWGqhnPwQlfrq8= github.com/dexidp/dex/api/v2 v2.0.0/go.mod h1:k5arBJT1QYvpsEY3sEd0NXJp3hKWKuUUfzJ3BlcqPdM= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/cli v20.10.5+incompatible h1:bjflayQbWg+xOkF2WPEAOi4Y7zWhR7ptoPhV/VqLVDE= -github.com/docker/cli v20.10.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3 h1:rEK0juuU5idazw//KzUcL3yYwUU3DIe2OnfJwjDBqno= +github.com/distribution/distribution/v3 v3.0.0-20210804104954-38ab4c606ee3/go.mod h1:gt38b7cvVKazi5XkHvINNytZXgTEntyhtyM3HQz46Nk= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= +github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible h1:357nGVUC8gSpeSc2Axup8HfrfTLLUfWfCsCUhiQSKIg= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81 h1:VnJAjP/7FbY46wddaiKakrc1ogXW+3MlP7cg+S4j4Og= @@ -242,9 +474,16 @@ github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGl github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 h1:Z8LR4yJgA3vaXpBiUWcWdQZpB9h+dzj4NdLjP9Sfdb0= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libnetwork v0.8.0-dev.2.0.20200917202933-d0951081b35f/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= @@ -256,12 +495,10 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -271,6 +508,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= @@ -278,21 +516,27 @@ github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQL github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM= github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -304,22 +548,26 @@ github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6 github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c= +github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ= github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -329,6 +577,7 @@ github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -338,17 +587,21 @@ github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQH github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= @@ -357,6 +610,7 @@ github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCs github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= @@ -369,11 +623,13 @@ github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pL github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= @@ -396,13 +652,26 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= -github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= @@ -416,31 +685,40 @@ github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSC github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= -github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= +github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= -github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= -github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= +github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= +github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -452,8 +730,10 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -463,6 +743,7 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -483,12 +764,37 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4-0.20210608040537-544b4180ac70 h1:yxuuMouxXYv9V1HprM9jTODJPGrTrC0FYVtPSnyIXxs= +github.com/golang/snappy v0.0.4-0.20210608040537-544b4180ac70/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.23.7/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= +github.com/golangci/golangci-lint v1.27.0/go.mod h1:+eZALfxIuthdrHPtfM7w/R3POJLjHDfJJw8XZl9xOng= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= +github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= +github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= +github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -502,16 +808,26 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= +github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-github/v29 v29.0.2 h1:opYN6Wc7DOz7Ku3Oh4l7prmkOMwEcQxpFtxdU8N8Pts= github.com/google/go-github/v29 v29.0.2/go.mod h1:CHKiKKPHJ0REzfwc14QMklvtHwCveD0PxlMjLlzAM5E= github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho= github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= +github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= @@ -529,20 +845,36 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/rpmpack v0.0.0-20191226140753-aa36bfddb3a0/go.mod h1:RaTPr0KUf2K7fnZYLNDrr8rxAamWs3iNywJLtQ2AzBg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s= +github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= +github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/goreleaser/goreleaser v0.136.0/go.mod h1:wiKrPUeSNh6Wu8nUHxZydSOVQ/OZvOaO7DTtFqie904= +github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhjYcR6G9w= +github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= @@ -554,6 +886,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= @@ -563,14 +897,23 @@ github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6t github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= +github.com/hanwen/go-fuse/v2 v2.0.3/go.mod h1:0EQM6aH2ctVpvZ6a+onrQ/vaykxh2GH7hy3e13vzTUY= +github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= @@ -578,10 +921,14 @@ github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs= github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -592,49 +939,71 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c/go.mod h1:fHzc09UnyJyqyW+bFuq864eh+wC7dj65aXmXLRe5to0= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 h1:nQ1QIkLxAoFsYyjvpQH3gVIy3mHfI0WDYEWdmwwxSG0= github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50/go.mod h1:Pp3sd2tx3j9qC7Ij6jGh5phZwTrI+/HUBK90f3Cn2CI= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/ishidawataru/sctp v0.0.0-20210226210310-f2269e66cdee/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3YhEW2BmA1wgGpPYI3HZy/5gD705PXKUVSg= +github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= +github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -653,26 +1022,39 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= +github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= @@ -681,83 +1063,132 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= +github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= +github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237 h1:acuCHBjzG7MFTugvx3buC4m5rLDLaKC9J8C9jtlraRc= github.com/mattomatic/dijkstra v0.0.0-20130617153013-6f6d134eb237/go.mod h1:UOnLAUmVG5paym8pD3C4B9BQylUDC2vXFJJpT7JrlEA= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mittwald/go-helm-client v0.8.0 h1:k8yMvoWE7C+jJgw6iodJ0U5nY4hVpLU20VIpR+FfyJk= -github.com/mittwald/go-helm-client v0.8.0/go.mod h1:+Ks5OpzB0hJw91W8o/8G+FmjUtjrIXxPuJjE87XG/G0= +github.com/mittwald/go-helm-client v0.8.2 h1:tWPe+e4Bx2bAC0lY55nxuOxOvNEP4aBfCdizvGpM24U= +github.com/mittwald/go-helm-client v0.8.2/go.mod h1:DVtyXr7KDPIT44X320DkxF39nfbZn8KdjvibnN7iHnQ= +github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= +github.com/moby/buildkit v0.9.1 h1:6noq8jvkaRs3OQGTIDf5U5Etkgq6zsPmQHGWi5yhh88= +github.com/moby/buildkit v0.9.1/go.mod h1:oVZKk3TMm0MlDx7XxnlF0wKmcpyrzOs9GEp0VXKWFPk= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mount v0.1.0/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mount v0.1.1/go.mod h1:FVQFLDRWwyBjDTBNQXDlWnSFREqOo3OKX9aqhmeoo74= +github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= +github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.1.3/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ= +github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 h1:yH0SvLzcbZxcJXho2yh7CqdENGMQe73Cw3woZBpPli0= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -765,20 +1196,19 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nsqio/go-nsq v1.0.7 h1:O0pIZJYTf+x7cZBA0UMY8WxFG79lYTURmWzAAh48ljY= github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito= @@ -787,49 +1217,70 @@ github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWk github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc92/go.mod h1:X1zlU4p7wOlX4+WRCz+hvlRv8phdL7UqbYD+vQwNMmE= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.6.0 h1:+bIAS/Za3q5FTwWym4fTB0vObnfCf3G/NC7K6Jx62mY= +github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2 h1:c4ca10UMgRcvZ6h0K4HtS15UaVSBEaE+iln2LVpAuGc= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ= github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -837,36 +1288,40 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E= github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -874,56 +1329,79 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.1.2-0.20200318202121-b00d7a75d3d8/go.mod h1:CGFX09Ci3pq9QZdj86B+VGIdNj4VyCo2iPOGS9esB/k= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= -github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= -github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc h1:BD7uZqkN8CpjJtN/tScAKiccBikU4dlqe/gNrkRaPY4= +github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc/go.mod h1:HFLT6i9iR4QBOF5rdCyjddC9t59ArqWJV2xx+jwcCMo= +github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:am+Fp8Bt506lA3Rk3QCmSqmYmLMnPDhdDUcosQCAx+I= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A= +github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -932,12 +1410,17 @@ github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrf github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= +github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -950,6 +1433,7 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= @@ -966,18 +1450,21 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI= github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -993,10 +1480,35 @@ github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZt github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0= github.com/swaggo/swag v1.5.1 h1:2Agm8I4K5qb00620mHq0VJ05/KT4FtmALPIcQR9lEZM= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= +github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= +github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= +github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/tonistiigi/fsutil v0.0.0-20201103201449-0834f99b7b85/go.mod h1:a7cilN64dG941IOXfhJhlH0qB92hxJ9A1ewrdUmJ6xo= +github.com/tonistiigi/fsutil v0.0.0-20210609172227-d72af97c0eaf/go.mod h1:lJAxK//iyZ3yGbQswdrPTxugZIDM7sd4bEsD0x3XMHk= +github.com/tonistiigi/go-actions-cache v0.0.0-20211002214948-4d48f2ff622a/go.mod h1:YiIBjH5gP7mao3t0dBrNNBGuKYdeJmcAJjYLXr43k6A= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= +github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.13 h1:nB3O5kBSQGjEQAcfe1aLUYuxmXdFKmYgBZhY32rQb6Q= @@ -1006,14 +1518,33 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.13 h1:013LbFhocBoIqgHeIHKlV4JWYhqogATYWZhIcH0WHn4= github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU= -github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243 h1:R43TdZy32XXSXjJn7M/HhALJ9imq6ztLnChfYJpVDnM= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= @@ -1024,6 +1555,7 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= @@ -1039,10 +1571,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/gorelic v0.0.7 h1:4DTF1WOM2ZZS/xMOkTFBOcb6XiHu/PKn3rVo6dbewQE= github.com/yvasiyarov/gorelic v0.0.7/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 h1:AsFN8kXcCVkUFHyuzp1FtYbzp1nCO/H6+1uPSGEyPzM= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= @@ -1050,19 +1585,26 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.5.0 h1:REddm85e1Nl0JPXGGhgZkgJdG/yOe6xvpXUcYK5WLt0= go.mongodb.org/mongo-driver v1.5.0/go.mod h1:boiGPFqyBs5R0R5qf2ErokGRekMfwn+MqKaUyHs7wy0= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= +go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= +go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= +go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -1072,61 +1614,90 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib v0.21.0/go.mod h1:EH4yDYeNoaTqn/8yCWQmfNB78VHfGX2Jt2bvnvzBlGM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.21.0/go.mod h1:Vm5u/mtkj1OMhtao0v+BGo2LUoLCgHYXvRmj0jWITlE= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.21.0/go.mod h1:a9cocRplhIBkUAJmak+BPDx+LVL7cTmqUPB0uBcTA4k= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.21.0/go.mod h1:JQAtechjxLEL81EjmbRwxBq/XEzGaHcsPuDHAx54hg4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.0.0-RC1/go.mod h1:x9tRa9HK4hSSq7jf2TKbqFbtt58/TGk0f9XiEYISI1I= +go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC1/go.mod h1:FXJnjGCoTQL6nQ8OpFJ0JI1DrdOvMoVx49ic0Hg4+D4= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.0-RC1/go.mod h1:FliQjImlo7emZVjixV8nbDMAa4iAkcWTE9zzSEOiEPw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.0-RC1/go.mod h1:cDwRc2Jrh5Gku1peGK8p9rRuX/Uq2OtVmLicjlw2WYU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.0.0-RC1/go.mod h1:OYKzEoxgXFvehW7X12WYT4/a2BlASJK9l7RtG4A91fg= +go.opentelemetry.io/otel/internal/metric v0.21.0/go.mod h1:iOfAaY2YycsXfYD4kaRSbLx2LKmfpKObWBEv9QK5zFo= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/metric v0.21.0/go.mod h1:JWCt1bjivC4iCrz/aCrM1GSw+ZcvY44KCbaeeRhzHnc= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/oteltest v1.0.0-RC1/go.mod h1:+eoIG0gdEOaPNftuy1YScLr1Gb4mL/9lpDkZ0JjMRq4= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.0.0-RC1/go.mod h1:kj6yPn7Pgt5ByRuwesbaWcRLA+V7BSDg3Hf8xRvsvf8= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.0.0-RC1/go.mod h1:86UHmyHWFEtWjfWPSbu0+d0Pf9Q6e1U+3ViBOc+NXAg= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.9.0/go.mod h1:1vKfU9rv61e9EVGthD1zNvUbiwPcimSsOPU9brfSHJg= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +gocloud.dev v0.19.0/go.mod h1:SmKwiR8YwIMMJvQBKLsC3fHNyMwXLw3PMDO+VVteJMI= +golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= @@ -1137,7 +1708,9 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -1163,11 +1736,17 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1183,12 +1762,15 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1205,21 +1787,29 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1233,6 +1823,7 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1246,16 +1837,20 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1266,33 +1861,43 @@ golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190620070143-6f217b454f45/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1302,13 +1907,25 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1316,11 +1933,14 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210313202042-bd2e13477e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1333,6 +1953,7 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1346,25 +1967,38 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -1375,34 +2009,44 @@ golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331202046-9d5940d49312/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200502202811-ed308ab3e770/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1432,8 +2076,19 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1N golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= +google.golang.org/api v0.3.0/go.mod h1:IuvZyQh8jgscv8qWfQ4ABd8m7hEudgBFM/EdhA3BnXw= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1446,6 +2101,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= @@ -1470,12 +2126,19 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1484,6 +2147,7 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1492,16 +2156,19 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -1520,15 +2187,17 @@ google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+n google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f h1:YORWxaStkWBnWgELOHTmDrqNlFXuVGEbhwbB5iK94bQ= google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1564,16 +2233,20 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= @@ -1581,6 +2254,7 @@ gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mholt/archiver.v3 v3.1.1 h1:sLb5X5fPPc9oU6YatdC+vbY46irElT3kPR0wOS7bjR4= @@ -1589,12 +2263,13 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1607,6 +2282,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M= @@ -1619,9 +2295,12 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -helm.sh/helm/v3 v3.6.2 h1:7YbEhLC6AUgmcDoh5BGFUGNtR/o43pr0AIelSNjhuFU= -helm.sh/helm/v3 v3.6.2/go.mod h1:mIIus8EOqj+obtycw3sidsR4ORr2aFDmXMSI3k+oeVY= +grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= +helm.sh/helm/v3 v3.7.0/go.mod h1:DajHtQTe8KrjNmvy5gxWkosFKaADrS3uRS5EkDtsmI4= +helm.sh/helm/v3 v3.7.1 h1:kED/HWx09QHHSJhYaJY6ttj/BhmzBmT1oupKslncibY= +helm.sh/helm/v3 v3.7.1/go.mod h1:3eOeBD3Z+O/ELiuu19zynZSN8jP1ErXLuyP21SZeMq8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1629,57 +2308,111 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= k8s.io/apiextensions-apiserver v0.21.0/go.mod h1:gsQGNtGkc/YoDG9loKI0V+oLZM4ljRPjc/sql5tmvzc= k8s.io/apimachinery v0.21.0 h1:3Fx+41if+IRavNcKOz09FwEXDBG6ORh6iMsTSelhkMA= k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= -k8s.io/apiserver v0.21.0 h1:1hWMfsz+cXxB77k6/y0XxWxwl6l9OF26PC9QneUVn1Q= +k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= +k8s.io/apiserver v0.22.1 h1:Ul9Iv8OMB2s45h2tl5XWPpAZo1VPIJ/6N+MESeed7L8= +k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= +k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U= +k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.21.0/go.mod h1:hUlps5+9QaTrKx+jiM4rmq7YmH8wPOIko64uZCHDh6Q= +k8s.io/component-base v0.17.4/go.mod h1:5BRqHMbbQPm2kKu35v3G+CpVq4K0RJKC7TRioF0I9lE= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/csi-translation-lib v0.17.4/go.mod h1:CsxmjwxEI0tTNMzffIAcgR9lX4wOh6AKHdxQrT7L0oo= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0= +k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= +k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= +k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= +modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= +modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= +modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= +modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= +mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc= +oras.land/oras-go v0.4.0 h1:u6+7D+raZDYHwlz/uOwNANiRmyYDSSMW7A9E1xXycUQ= +oras.land/oras-go v0.4.0/go.mod h1:VJcU+VE4rkclUbum5C0O7deEZbBYnsnpbGSACwTjOcg= +pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/letsencrypt v0.0.3 h1:H7xDfhkaFFSYEJlKeq38RwX2jYcnTeHuDQyT+mMNMwM= rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= -sigs.k8s.io/kustomize/api v0.8.5 h1:bfCXGXDAbFbb/Jv5AhMj2BB8a5VAJuuQ5/KU69WtDjQ= sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= +sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8= +sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= sigs.k8s.io/kustomize/cmd/config v0.9.7/go.mod h1:MvXCpHs77cfyxRmCNUQjIqCmZyYsbn5PyQpWiq44nW0= sigs.k8s.io/kustomize/kustomize/v4 v4.0.5/go.mod h1:C7rYla7sI8EnxHE/xEhRBSHMNfcL91fx0uKmUlUhrBk= -sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI= sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg= +sigs.k8s.io/kustomize/kyaml v0.11.0 h1:9KhiCPKaVyuPcgOLJXkvytOvjMJLoxpjodiycb4gHsA= +sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= +sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.0 h1:C4r9BgJ98vrKnnVCjwCSXcWjWe0NKcUQkmzDXZXGwH8= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index f6a1b9df..36376093 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -119,6 +119,7 @@ const ( TaskBuild TaskType = "buildv2" TaskJenkinsBuild TaskType = "jenkins_build" TaskArtifact TaskType = "artifact" + TaskArtifactDeploy TaskType = "artifact_deploy" TaskDeploy TaskType = "deploy" TaskTestingV2 TaskType = "testingv2" TaskDistributeToS3 TaskType = "distribute2kodo" diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 527bb06b..fd5ca554 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -19,6 +19,7 @@ package service import ( "context" + "github.com/google/go-github/v35/github" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -69,7 +70,9 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo } else { // github gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) - pullRequests, err := gh.ListPullRequests(context.TODO(), namespace, projectName, nil) + pullRequests, err := gh.ListPullRequests(context.TODO(), namespace, projectName, &github.PullRequestListOptions{ + ListOptions: github.ListOptions{PerPage: 100}, + }) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index e808b529..c5288acc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -96,6 +96,12 @@ type DockerBuild struct { DockerFile string `bson:"docker_file" json:"docker_file"` // BuildArgs docker build args BuildArgs string `bson:"build_args,omitempty" json:"build_args"` + // Source whether dockerfile comes from template or existing file + Source string `bson:"source" json:"source"` + // TemplateId is the id of the template dockerfile + TemplateID string `bson:"template_id" json:"template_id"` + // TemplateName is the name of the template dockerfile + TemplateName string `bson:"template_name" json:"template_name"` } type JenkinsBuild struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/private_key.go b/pkg/microservice/aslan/core/common/repository/models/private_key.go index f8e711bc..c54b9c26 100644 --- a/pkg/microservice/aslan/core/common/repository/models/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/models/private_key.go @@ -37,13 +37,3 @@ type PrivateKey struct { func (PrivateKey) TableName() string { return "private_key" } - -//type SSH struct { -// ID string `json:"id"` -// Name string `json:"name"` -// UserName string `json:"user_name"` -// IP string `json:"ip"` -// IsProd bool `json:"is_prod"` -// Label string `json:"label"` -// PrivateKey string `json:"private_key"` -//} diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 20b45845..20922d12 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -109,7 +109,6 @@ type ServiceTaskArgs struct { } type ConfigPayload struct { - Aslan AslanConfig `json:"aslan"` Proxy Proxy `json:"proxy"` S3Storage S3Config `json:"s3_storage"` Github GithubConfig `json:"github"` @@ -162,15 +161,15 @@ type S3Config struct { type GithubConfig struct { // github API access token - AccessToken string + AccessToken string `json:"access_token"` // github ssh key with base64 encoded - SSHKey string + SSHKey string `json:"ssh_key"` // github knownhost - KnownHost string + KnownHost string `json:"known_host"` // github app private key - AppKey string + AppKey string `json:"app_key"` // gihhub app id - AppID int + AppID int `json:"app_id"` } type GitlabConfig struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/s3.go b/pkg/microservice/aslan/core/common/repository/models/s3.go index 23a2be10..c0381b27 100644 --- a/pkg/microservice/aslan/core/common/repository/models/s3.go +++ b/pkg/microservice/aslan/core/common/repository/models/s3.go @@ -35,6 +35,14 @@ type S3Storage struct { Provider int8 `bson:"provider" json:"provider"` } +type TarInfo struct { + URL string `bson:"url" json:"url"` + Name string `bson:"name" json:"name"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + FileName string `bson:"file_name" json:"file_name"` +} + func (s S3Storage) TableName() string { return "s3storage" } diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 3c1e8368..8e2ae751 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -16,6 +16,10 @@ limitations under the License. package models +import ( + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" +) + // TODO: move Revision out of Service. // Service : service template struct @@ -57,9 +61,27 @@ type Service struct { BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` LoadFromDir bool `bson:"is_dir,omitempty" json:"is_dir,omitempty"` + CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` +} + +type CreateFromRepo struct { + GitRepoConfig *templatemodels.GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` + LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` +} + +type CreateFromPublicRepo struct { + RepoLink string `bson:"repo_link" json:"repo_link"` + LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` +} + +type CreateFromChartTemplate struct { + YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` + TemplateName string `bson:"template_name" json:"template_name"` + ServiceName string `bson:"service_name" json:"service_name"` } type GUIConfig struct { @@ -200,6 +222,7 @@ type EnvStatus struct { type EnvConfig struct { EnvName string `bson:"env_name,omitempty" json:"env_name"` HostIDs []string `bson:"host_ids,omitempty" json:"host_ids"` + Labels []string `bson:"labels,omitempty" json:"labels"` } type PmHealthCheck struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 47f18074..c8211297 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -26,9 +26,10 @@ import ( ) type Build struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ProductName string `bson:"product_name" json:"product_name"` // 新增一个service表示服务名称 Service string `bson:"service" json:"service"` // 该名称实际为服务组件名称 @@ -56,7 +57,15 @@ type Build struct { BuildStatus *BuildStatus `bson:"build_status,omitempty" json:"build_status,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` // Get the host bound to the environment of the cloud host service configuration - EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` +} + +type ArtifactInfo struct { + URL string `bson:"url" json:"url"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + FileName string `bson:"file_name" json:"file_name"` } //type Item struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go index fe616273..8d334a4f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go @@ -54,6 +54,8 @@ func (db *DockerBuild) ToSubTask() (map[string]interface{}, error) { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/release-candidates/image:tag type DockerBuildCtx struct { + Source string `yaml:"source" bson:"source" json:"source"` + TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 73d4cc95..f6357012 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -104,20 +104,20 @@ type GitRepoConfig struct { Branch string `bson:"branch,omitempty"` } -type OverrideYaml struct { +type CustomYaml struct { YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"gitRepoConfig,omitempty"` + GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` } // RenderChart ... type RenderChart struct { - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` - ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` - OverrideYaml *OverrideYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` - OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` + ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` + OverrideYaml *CustomYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` + OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` } type ProductFeature struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 0eaa8dc8..86c7a161 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -140,18 +140,19 @@ type WorkflowTaskArgs struct { ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` Description string `bson:"description,omitempty" json:"description,omitempty"` //为了兼容老数据,namespace可能会存多个环境名称,用逗号隔开 - Namespace string `bson:"namespace" json:"namespace"` - BaseNamespace string `bson:"base_namespace,omitempty" json:"base_namespace,omitempty"` - EnvRecyclePolicy string `bson:"env_recycle_policy,omitempty" json:"env_recycle_policy,omitempty"` - EnvUpdatePolicy string `bson:"env_update_policy,omitempty" json:"env_update_policy,omitempty"` - Target []*TargetArgs `bson:"targets" json:"targets"` - Artifact []*ArtifactArgs `bson:"artifact_args" json:"artifact_args"` - Tests []*TestArgs `bson:"tests" json:"tests"` - VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` - ReqID string `bson:"req_id" json:"req_id"` - RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` - DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` - WorklowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` + Namespace string `bson:"namespace" json:"namespace"` + BaseNamespace string `bson:"base_namespace,omitempty" json:"base_namespace,omitempty"` + EnvRecyclePolicy string `bson:"env_recycle_policy,omitempty" json:"env_recycle_policy,omitempty"` + EnvUpdatePolicy string `bson:"env_update_policy,omitempty" json:"env_update_policy,omitempty"` + Target []*TargetArgs `bson:"targets" json:"targets"` + Artifact []*ArtifactArgs `bson:"artifact_args" json:"artifact_args"` + Tests []*TestArgs `bson:"tests" json:"tests"` + VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` + ReqID string `bson:"req_id" json:"req_id"` + RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` + DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` + WorkflowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache IgnoreCache bool `json:"ignore_cache" bson:"ignore_cache"` // Ignore workspace cache and reset volume @@ -339,10 +340,14 @@ type DeployEnv struct { } type ArtifactArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` + Name string `bson:"name" json:"name"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` + TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` + FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` + URL string `bson:"url,omitempty" json:"url,omitempty"` } type VersionArgs struct { @@ -352,6 +357,20 @@ type VersionArgs struct { Labels []string `bson:"labels" json:"labels"` } +type BuildModuleArgs struct { + BuildName string + Target string + ServiceName string + ProductName string + Variables []*KeyVal + Env *Product + WorkflowName string + TaskID int64 + FileName string + URL string + TaskType string +} + func (Workflow) TableName() string { return "workflow" } diff --git a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go new file mode 100644 index 00000000..34af08db --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go @@ -0,0 +1,19 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type YamlTemplate struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Content string `bson:"content" json:"content"` + Variables []*Variable `bson:"variables" json:"variables"` +} + +type Variable struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` +} + +func (YamlTemplate) TableName() string { + return "yaml_template" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/build.go b/pkg/microservice/aslan/core/common/repository/mongodb/build.go index a1402eaa..8dc74f0c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/build.go @@ -266,3 +266,18 @@ func (c *BuildColl) DistinctTargets(excludeModule []string, productName string) return resp, err } + +func (c *BuildColl) GetDockerfileTemplateReference(templateID string) ([]*models.Build, error) { + ret := make([]*models.Build, 0) + query := bson.M{"post_build.docker_build.template_id": templateID} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &ret) + if err != nil { + return nil, err + } + return ret, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index 85cfa6f6..e0e0cc75 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -32,19 +32,20 @@ import ( ) type DeliveryArtifactArgs struct { - ID string `json:"id"` - Image string `json:"image"` - Name string `json:"name"` - Type string `json:"type"` - RepoName string `json:"repo_name"` - Branch string `json:"branch"` - Source string `json:"source"` - ImageHash string `json:"image_hash"` - ImageTag string `json:"image_tag"` - ImageDigest string `json:"image_digest"` - PerPage int `json:"per_page"` - Page int `json:"page"` - IsFuzzyQuery bool `json:"is_fuzzy_query"` + ID string `json:"id"` + Image string `json:"image"` + Name string `json:"name"` + Type string `json:"type"` + RepoName string `json:"repo_name"` + Branch string `json:"branch"` + Source string `json:"source"` + ImageHash string `json:"image_hash"` + ImageTag string `json:"image_tag"` + ImageDigest string `json:"image_digest"` + PerPage int `json:"per_page"` + Page int `json:"page"` + IsFuzzyQuery bool `json:"is_fuzzy_query"` + PackageStorageURI string `json:"package_storage_uri"` } type DeliveryArtifactColl struct { @@ -187,3 +188,40 @@ func (c *DeliveryArtifactColl) Update(args *DeliveryArtifactArgs) error { _, err := c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) return err } + +func (c *DeliveryArtifactColl) ListTars(args *DeliveryArtifactArgs) ([]*models.DeliveryArtifact, error) { + if args == nil { + return nil, errors.New("nil delivery_artifact args") + } + + resp := make([]*models.DeliveryArtifact, 0) + query := bson.M{} + if args.Name != "" { + query["name"] = args.Name + } + + if args.Type != "" { + query["type"] = args.Type + } + + if args.Source != "" { + query["source"] = args.Source + } + + if args.PackageStorageURI != "" { + query["package_storage_uri"] = args.PackageStorageURI + } + + opt := options.Find(). + SetSort(bson.D{{"created_time", -1}}) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index 639b7b73..c2f97eb7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -138,6 +138,7 @@ func (c *PrivateKeyColl) Update(id string, args *models.PrivateKey) error { "label": args.Label, "is_prod": args.IsProd, "private_key": args.PrivateKey, + "provider": args.Provider, "update_by": args.UpdateBy, "update_time": time.Now().Unix(), }} @@ -158,20 +159,39 @@ func (c *PrivateKeyColl) Delete(id string) error { return err } -func (c *PrivateKeyColl) ListHostIPByIDs(ids []string) ([]*models.PrivateKey, error) { +func (c *PrivateKeyColl) DeleteAll() error { + _, err := c.DeleteMany(context.TODO(), bson.M{}) + return err +} + +type ListHostIPArgs struct { + IDs []string `json:"ids"` + Labels []string `json:"labels"` +} + +func (c *PrivateKeyColl) ListHostIPByArgs(args *ListHostIPArgs) ([]*models.PrivateKey, error) { query := bson.M{} resp := make([]*models.PrivateKey, 0) ctx := context.Background() - var oids []primitive.ObjectID - for _, id := range ids { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return nil, err + if len(args.IDs) == 0 && len(args.Labels) == 0 { + return resp, nil + } + + if len(args.IDs) > 0 { + var oids []primitive.ObjectID + for _, id := range args.IDs { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + oids = append(oids, oid) } - oids = append(oids, oid) + query["_id"] = bson.M{"$in": oids} + } else if len(args.Labels) > 0 { + query["label"] = bson.M{"$in": args.Labels} } - query["_id"] = bson.M{"$in": oids} + opt := options.Find() selector := bson.D{ {"ip", 1}, @@ -189,3 +209,22 @@ func (c *PrivateKeyColl) ListHostIPByIDs(ids []string) ([]*models.PrivateKey, er return resp, err } + +// DistinctLabels returns distinct label +func (c *PrivateKeyColl) DistinctLabels() ([]string, error) { + var resp []string + query := bson.M{} + ctx := context.Background() + labels, err := c.Collection.Distinct(ctx, "label", query) + + for _, labelInter := range labels { + if label, ok := labelInter.(string); ok { + if label == "" { + continue + } + resp = append(resp, label) + } + } + + return resp, err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 93dcba90..26bd34d2 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -204,15 +204,14 @@ func (c *ServiceColl) Find(opt *ServiceFindOption) (*models.Service, error) { if opt.ServiceName == "" { return nil, fmt.Errorf("ServiceName is empty") } - if opt.ProductName == "" { - return nil, fmt.Errorf("ProductName is empty") - } query := bson.M{} query["service_name"] = opt.ServiceName - query["product_name"] = opt.ProductName - service := new(models.Service) + if opt.ProductName != "" { + query["product_name"] = opt.ProductName + } + service := new(models.Service) if opt.Type != "" { query["type"] = opt.Type } @@ -457,6 +456,15 @@ func (c *ServiceColl) Count(productName string) (int, error) { return cs[0].Count, nil } +func (c *ServiceColl) GetTemplateReference(templateID string) ([]*models.Service, error) { + query := bson.M{ + "template_id": templateID, + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + } + + return c.listMaxRevisions(query, nil) +} + func (c *ServiceColl) listMaxRevisions(preMatch, postMatch bson.M) ([]*models.Service, error) { var pipeResp []*grouped pipeline := []bson.M{ diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go b/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go new file mode 100644 index 00000000..9d65e08b --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go @@ -0,0 +1,116 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type YamlTemplateColl struct { + *mongo.Collection + + coll string +} + +func NewYamlTemplateColl() *YamlTemplateColl { + name := models.YamlTemplate{}.TableName() + return &YamlTemplateColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *YamlTemplateColl) GetCollectionName() string { + return c.coll +} + +func (c *YamlTemplateColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"name": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *YamlTemplateColl) Create(obj *models.YamlTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + return err +} + +func (c *YamlTemplateColl) Update(idString string, obj *models.YamlTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *YamlTemplateColl) List(pageNum, pageSize int) ([]*models.YamlTemplate, int, error) { + resp := make([]*models.YamlTemplate, 0) + query := bson.M{} + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + opt := options.Find(). + SetSkip(int64((pageNum - 1) * pageSize)). + SetLimit(int64(pageSize)) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, int(count), nil +} + +func (c *YamlTemplateColl) GetById(idstring string) (*models.YamlTemplate, error) { + resp := new(models.YamlTemplate) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *YamlTemplateColl) DeleteByID(idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 3844e98b..bb0e0d96 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -24,6 +24,7 @@ import ( "go.uber.org/zap" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -186,13 +187,14 @@ func (f *workloadFilter) Match(workload *Workload) bool { } type Workload struct { - EnvName string `json:"env_name"` - Name string `json:"name"` - Type string `json:"type"` - ProductName string `json:"product_name"` - Spec corev1.ServiceSpec `json:"-"` - Images []string `json:"-"` - Ready bool + EnvName string `json:"env_name"` + Name string `json:"name"` + Type string `json:"type"` + ProductName string `json:"product_name"` + Spec corev1.PodTemplateSpec `json:"-"` + Images []string `json:"-"` + Ready bool `json:"ready"` + ServiceName string `json:"service_name"` } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -212,7 +214,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range listDeployments { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ServiceSpec{Selector: v.Spec.Selector.MatchLabels}, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) } statefulSets, err := getter.ListStatefulSets(namespace, nil, kubeClient) if err != nil { @@ -220,7 +222,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range statefulSets { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: corev1.ServiceSpec{Selector: v.Spec.Selector.MatchLabels}, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) + workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) } log.Debugf("Found %d workloads in total", len(workLoads)) @@ -249,6 +251,20 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } } + hostInfos := make([]resource.HostInfo, 0) + // get all ingresses + if ingresses, err := getter.ListIngresses(namespace, nil, kubeClient); err == nil { + for _, ingress := range ingresses { + hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) + } + } + + // get all services + allServices, err := getter.ListServices(namespace, nil, kubeClient) + if err != nil { + log.Errorf("[%s][%s] list service error: %s", envName, namespace, err) + } + for _, workload := range workLoads { tmpProductName := workload.ProductName if tmpProductName == "" && productName != "" { @@ -268,10 +284,45 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p productRespInfo.Status = setting.PodUnstable productRespInfo.Ready = setting.PodNotReady } + + productRespInfo.Ingress = &IngressInfo{ + HostInfo: findServiceFromIngress(hostInfos, workload, allServices), + } + resp = append(resp, productRespInfo) } - log.Infof("Finish to list workloads in namespace %s", namespace) return count, resp, nil } + +func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Workload, allServices []*corev1.Service) []resource.HostInfo { + if len(allServices) == 0 || len(hostInfos) == 0 { + return []resource.HostInfo{} + } + serviceName := "" + podLabels := labels.Set(currentWorkload.Spec.Labels) + for _, svc := range allServices { + if len(svc.Spec.Selector) == 0 { + continue + } + if labels.SelectorFromValidatedSet(svc.Spec.Selector).Matches(podLabels) { + serviceName = svc.Name + break + } + } + if serviceName == "" { + return []resource.HostInfo{} + } + + resp := make([]resource.HostInfo, 0) + for _, hostInfo := range hostInfos { + for _, backend := range hostInfo.Backends { + if backend.ServiceName == serviceName { + resp = append(resp, hostInfo) + break + } + } + } + return resp +} diff --git a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go index 677f225e..cf6f7673 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go @@ -30,8 +30,11 @@ func (c *Client) CreateWebHook(owner, repo string) (string, error) { Secret: gitservice.GetHookSecret(), Events: []string{git.PushEvent, git.PullRequestEvent, git.BranchOrTagCreateEvent}, }) + if err != nil { + return "", err + } - return strconv.Itoa(projectHook.ID), err + return strconv.Itoa(projectHook.ID), nil } func (c *Client) DeleteWebHook(owner, repo, hookID string) error { diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 110d4249..3552b1e1 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -83,15 +83,15 @@ func (args *RenderChartArg) fromOverrideValueString(valueStr string) { } } -func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.OverrideYaml { +func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.CustomYaml { switch args.YamlSource { case setting.ValuesYamlSourceFreeEdit: - return &templatemodels.OverrideYaml{ + return &templatemodels.CustomYaml{ YamlSource: args.YamlSource, YamlContent: args.ValuesYAML, } case setting.ValuesYamlSourceGitRepo: - return &templatemodels.OverrideYaml{ + return &templatemodels.CustomYaml{ YamlSource: args.YamlSource, YamlContent: args.ValuesYAML, ValuesPaths: args.GitRepoConfig.ValuesPaths, @@ -106,7 +106,7 @@ func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.OverrideYaml { return nil } -func (args *RenderChartArg) fromCustomValueYaml(customValuesYaml *templatemodels.OverrideYaml) { +func (args *RenderChartArg) fromCustomValueYaml(customValuesYaml *templatemodels.CustomYaml) { if customValuesYaml == nil { return } diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 0e5f5289..6421572d 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -479,10 +479,14 @@ func DeleteServiceWebhookByName(serviceName, productName string, logger *zap.Sug } func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName string, logger *zap.SugaredLogger) { + // helm service doesn't support webhook + if current != nil && current.Type == setting.HelmDeployType { + return + } var action string var updatedHooks, currentHooks []*webhook.WebHook if updated != nil { - if updated.Source == setting.SourceFromZadig || updated.Source == setting.SourceFromGerrit || updated.Source == "" || updated.Source == setting.SourceFromExternal { + if updated.Source == setting.ServiceSourceTemplate || updated.Source == setting.SourceFromZadig || updated.Source == setting.SourceFromGerrit || updated.Source == "" || updated.Source == setting.SourceFromExternal { return } action = "add" @@ -493,7 +497,7 @@ func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName s updatedHooks = append(updatedHooks, &webhook.WebHook{Owner: updated.RepoOwner, Repo: updated.RepoName, Address: address, Name: "trigger", CodeHostID: updated.CodehostID}) } if current != nil { - if current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { + if current.Source == setting.ServiceSourceTemplate || current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { return } action = "remove" diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0c904e67..5f542f97 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -57,6 +57,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" @@ -1484,26 +1485,36 @@ func upsertService(isUpdate bool, env *commonmodels.Product, u.SetAPIVersion(setting.APIVersionAppsV1) u.SetLabels(kube.MergeLabels(labels, u.GetLabels())) - podLabels, _, _ := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") - err := unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") + podLabels, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") if err != nil { - // should not have happened - panic(err) + podLabels = nil + } + err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") + if err != nil { + log.Errorf("merge label failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, podLabels), "spec", "template", "metadata", "labels") } - podAnnotations, _, _ := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "annotations") + podAnnotations, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "annotations") + if err != nil { + podAnnotations = nil + } err = unstructured.SetNestedStringMap(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") if err != nil { - // should not have happened - panic(err) + log.Errorf("merge annotation failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") } // Inject selector: s-product and s-service - selector, _, _ := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") + selector, _, err := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") + if err != nil { + selector = nil + } + err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") if err != nil { - // should not have happened - panic(err) + log.Errorf("merge selector failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") } jsonData, err := u.MarshalJSON() @@ -2019,7 +2030,49 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpdateHelmChart(user, envName, requestID string, args *commonmodels.Product, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { +func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + if err != nil { + err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) + return err + } + return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, helmClient) +} + +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { + base := config.LocalServicePath(serviceObj.ProductName, serviceObj.ServiceName) + if err := commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { + log.Errorf("Failed to load manifest for service %s in project %s, err: %s", serviceObj.ServiceName, serviceObj.ProductName, err) + return err + } + + chartFullPath := filepath.Join(base, serviceObj.ServiceName) + chartPath, err := fs.RelativeToCurrentPath(chartFullPath) + if err != nil { + log.Errorf("Failed to get relative path %s, err: %s", chartFullPath, err) + return err + } + + chartSpec := &helmclient.ChartSpec{ + ReleaseName: util.GeneHelmReleaseName(namespace, serviceObj.ServiceName), + ChartName: chartPath, + Namespace: namespace, + Version: renderChart.ChartVersion, + ValuesYaml: valuesYaml, + UpgradeCRDs: true, + } + if timeout > 0 { + chartSpec.Wait = true + chartSpec.Timeout = timeout + } + + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { + return err + } + return nil +} + +func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { var ( err error wg sync.WaitGroup @@ -2077,45 +2130,8 @@ func installOrUpdateHelmChart(user, envName, requestID string, args *commonmodel return } - base := config.LocalServicePath(service.ProductName, service.ServiceName) - if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { - log.Errorf("Failed to load service menifests for service %s in project %s, err: %s", service.ServiceName, service.ProductName, err) - errList = multierror.Append(errList, err) - return - } - - chartFullPath := filepath.Join(base, service.ServiceName) - chartPath, err := fs.RelativeToCurrentPath(chartFullPath) + err = installOrUpgradeHelmChart(args.Namespace, renderChart, serviceObj, Timeout*time.Second*10, helmClient) if err != nil { - log.Errorf("Failed to get relative path %s, err: %s", chartFullPath, err) - errList = multierror.Append(errList, err) - return - } - - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) - if err != nil { - err = errors.WithMessagef( - err, - "failed to merge override yaml %s and values %s", - renderChart.GetOverrideYaml(), - renderChart.OverrideValues, - ) - errList = multierror.Append(errList, err) - return - } - - chartSpec := &helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(args.Namespace, service.ServiceName), - ChartName: chartPath, - Namespace: args.Namespace, - Wait: true, - Version: renderChart.ChartVersion, - ValuesYaml: mergedValuesYaml, - UpgradeCRDs: true, - Timeout: Timeout * time.Second * 10, - } - - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { errList = multierror.Append(errList, err) return } @@ -2238,6 +2254,14 @@ func updateProductGroup(productName, envName, updateType string, productResp *co return e.ErrUpdateEnv.AddDesc("对比环境中的value.yaml和系统默认的value.yaml失败") } + svcNameSet := sets.NewString() + for _, singleChart := range overrideCharts { + if singleChart.EnvName != envName { + continue + } + svcNameSet.Insert(singleChart.ServiceName) + } + for _, renderChart := range renderSet.ChartInfos { renderChartMap[renderChart.ServiceName] = renderChart } @@ -2251,6 +2275,11 @@ func updateProductGroup(productName, envName, updateType string, productResp *co continue } + // service is not in update list + if !svcNameSet.Has(svc.ServiceName) { + continue + } + wg.Add(1) go func(service *commonmodels.ProductService) { defer wg.Done() @@ -2269,48 +2298,11 @@ func updateProductGroup(productName, envName, updateType string, productResp *co return } - base := config.LocalServicePath(service.ProductName, service.ServiceName) - if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { - log.Errorf("Failed to load service menifests for service %s in project %s, err: %s", service.ServiceName, service.ProductName, err) - errList = multierror.Append(errList, err) - return - } - - chartFullPath := filepath.Join(base, service.ServiceName) - chartPath, err := fs.RelativeToCurrentPath(chartFullPath) - if err != nil { - log.Errorf("Failed to get relative path %s, err: %s", chartFullPath, err) - errList = multierror.Append(errList, err) - return - } - - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, serviceObj, Timeout*time.Second*10, helmClient) if err != nil { - err = errors.WithMessagef( - err, - "failed to merge override yaml %s and values %s", - renderChart.GetOverrideYaml(), - renderChart.OverrideValues, - ) errList = multierror.Append(errList, err) return } - - chartSpec := helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(productResp.Namespace, service.ServiceName), - ChartName: chartPath, - Namespace: productResp.Namespace, - Wait: true, - Version: renderChart.ChartVersion, - ValuesYaml: mergedValuesYaml, - UpgradeCRDs: true, - Timeout: Timeout * time.Second * 10, - } - - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { - log.Errorf("install helm chart %s error :%+v", chartSpec.ReleaseName, err) - errList = multierror.Append(errList, err) - } }(svc) } @@ -2522,48 +2514,11 @@ func updateProductVariable(productName, envName string, productResp *commonmodel go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service) { defer wg.Done() - base := config.LocalServicePath(currentService.ProductName, currentService.ServiceName) - if err = commonservice.PreLoadServiceManifests(base, currentService); err != nil { - log.Errorf("Failed to load service menifests for service %s in project %s, err: %s", currentService.ServiceName, currentService.ProductName, err) - errList = multierror.Append(errList, err) - return - } - - chartFullPath := filepath.Join(base, currentService.ServiceName) - chartPath, err := fs.RelativeToCurrentPath(chartFullPath) - if err != nil { - log.Errorf("Failed to get relative path %s, err: %s", chartFullPath, err) - errList = multierror.Append(errList, err) - return - } - - mergedValuesYaml, err := helmtool.MergeOverrideValues(tmpRenderChart.ValuesYaml, tmpRenderChart.GetOverrideYaml(), tmpRenderChart.OverrideValues) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, currentService, Timeout*time.Second*10, helmClient) if err != nil { - err = errors.WithMessagef( - err, - "failed to merge override yaml %s and values %s", - tmpRenderChart.GetOverrideYaml(), - tmpRenderChart.OverrideValues, - ) errList = multierror.Append(errList, err) return } - - chartSpec := helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(productResp.Namespace, tmpRenderChart.ServiceName), - ChartName: chartPath, - Namespace: productResp.Namespace, - Wait: true, - Version: tmpRenderChart.ChartVersion, - ValuesYaml: mergedValuesYaml, - UpgradeCRDs: true, - Atomic: true, - Timeout: Timeout * time.Second * 10, - } - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { - errList = multierror.Append(errList, err) - log.Errorf("install helm chart error :%+v", err) - } }(renderChart, serviceObj) } groupServices = append(groupServices, service) @@ -2580,3 +2535,20 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } return errList.ErrorOrNil() } + +func setFieldValueIsNotExist(obj map[string]interface{}, value interface{}, fields ...string) map[string]interface{} { + m := obj + for _, field := range fields[:len(fields)-1] { + if val, ok := m[field]; ok { + if valMap, ok := val.(map[string]interface{}); ok { + m = valMap + } else { + newVal := make(map[string]interface{}) + m[field] = newVal + m = newVal + } + } + } + m[fields[len(fields)-1]] = value + return obj +} diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index cbf8d2ce..45a58c96 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -217,7 +217,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P eventStart := time.Now().Unix() - go installOrUpdateHelmChart(user, args.EnvName, requestID, args, eventStart, helmClient, log) + go installProductHelmCharts(user, args.EnvName, requestID, args, eventStart, helmClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 4f8705da..34589fe2 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -19,20 +19,33 @@ package service import ( "bytes" "fmt" + "regexp" "strings" "time" + helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "helm.sh/helm/v3/pkg/strvals" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type UpdateContainerImageArgs struct { @@ -45,6 +58,262 @@ type UpdateContainerImageArgs struct { Image string `json:"image"` } +const ( + imageUrlParseRegexString = `(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?` +) + +var ( + imageParseRegex = regexp.MustCompile(imageUrlParseRegexString) +) + +func getHelmServiceName(namespace, resType, resName string, kubeClient client.Client) (string, error) { + res := &unstructured.Unstructured{} + res.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: resType, + }) + found, err := getter.GetResourceInCache(namespace, resName, res, kubeClient) + if err != nil { + return "", fmt.Errorf("failed to find resource %s, type %s, err %s", resName, resType, err.Error()) + } + if !found { + return "", fmt.Errorf("failed to find resource %s, type %s", resName, resType) + } + annotation := res.GetAnnotations() + if len(annotation) > 0 { + if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { + return util.ExtraServiceName(chartRelease, namespace), nil + } + } + return "", fmt.Errorf("failed to get annotation from resource %s, type %s", resName, resType) +} + +func getValidMatchData(spec *models.ImagePathSpec) map[string]string { + ret := make(map[string]string) + if spec.Repo != "" { + ret[setting.PathSearchComponentRepo] = spec.Repo + } + if spec.Image != "" { + ret[setting.PathSearchComponentImage] = spec.Image + } + if spec.Tag != "" { + ret[setting.PathSearchComponentTag] = spec.Tag + } + return ret +} + +// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx +func resolveImageUrl(imageUrl string) map[string]string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + result := make(map[string]string) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + result[exNames[i]] = matchedStr + } + } + return result +} + +// replace image defines in yaml by new version +func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { + nestedMap, err := converter.Expand(imageValuesMap) + if err != nil { + return "", err + } + bs, err := yaml.Marshal(nestedMap) + if err != nil { + return "", err + } + mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) + if err != nil { + return "", err + } + return string(mergedBs), nil +} + +// AssignImageData assign image url data into match data +// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path +// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value +func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { + ret := make(map[string]interface{}) + // total image url assigned into one single value + if len(matchData) == 1 { + for _, v := range matchData { + ret[v] = imageUrl + } + return ret, nil + } + + resolvedImageUrl := resolveImageUrl(imageUrl) + + // image url assigned into repo/image+tag + if len(matchData) == 3 { + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] + ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] + return ret, nil + } + + if len(matchData) == 2 { + // image url assigned into repo/image + tag + if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { + ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] + for k, imagePath := range matchData { + if k == setting.PathSearchComponentTag { + continue + } + ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) + break + } + return ret, nil + } + // image url assigned into repo + image(tag) + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) + return ret, nil + } + + return nil, fmt.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) +} + +// prepare necessary data from db +func prepareData(namespace, serviceName string, containerName string, product *models.Product) (targetContainer *models.Container, + targetChart *templatemodels.RenderChart, renderSet *models.RenderSet, serviceObj *models.Service, err error) { + + var targetProductService *models.ProductService + for _, productService := range product.GetServiceMap() { + if productService.ServiceName != serviceName { + continue + } + targetProductService = productService + for _, container := range productService.Containers { + if container.Name != containerName { + continue + } + targetContainer = container + break + } + break + } + if targetContainer == nil { + err = fmt.Errorf("failed to find container %s", containerName) + return + } + + renderSet, err = commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: namespace, Revision: product.Render.Revision}) + if err != nil { + log.Errorf("[RenderSet.find] update product %s error: %s", product.ProductName, err.Error()) + err = fmt.Errorf("failed to find redset name %s revision %d", namespace, product.Render.Revision) + return + } + + for _, chartInfo := range renderSet.ChartInfos { + if chartInfo.ServiceName == serviceName { + targetChart = chartInfo + break + } + } + if targetChart == nil { + err = fmt.Errorf("failed to find chart info %s", serviceName) + return + } + + // 获取服务详情 + opt := &commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Revision: targetProductService.Revision, + ProductName: product.ProductName, + } + + serviceObj, err = commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("failed to find template service, opt %+v, err :%s", *opt, err.Error()) + err = fmt.Errorf("failed to find template service, opt %+v", *opt) + return + } + return +} + +func updateContainerForHelmChart(serviceName, resType, image, containerName string, product *models.Product) error { + var ( + replaceValuesMap map[string]interface{} + replacedValuesYaml string + mergedValuesYaml string + replacedMergedValuesYaml string + restConfig *rest.Config + helmClient helmclient.Client + namespace string = product.Namespace + ) + + targetContainer, targetChart, renderSet, serviceObj, err := prepareData(namespace, serviceName, containerName, product) + if err != nil { + return err + } + + // update image info in product.services.container + targetContainer.Image = image + + // prepare image replace info + replaceValuesMap, err = assignImageData(image, getValidMatchData(targetContainer.ImagePath)) + if err != nil { + return fmt.Errorf("failed to pase image uri %s/%s, err %s", namespace, serviceName, err.Error()) + } + + // replace image into service's values.yaml + replacedValuesYaml, err = replaceImage(targetChart.ValuesYaml, replaceValuesMap) + if err != nil { + return fmt.Errorf("failed to replace image uri %s/%s, err %s", namespace, serviceName, err.Error()) + + } + if replacedValuesYaml == "" { + return fmt.Errorf("failed to set new image uri into service's values.yaml %s/%s", namespace, serviceName) + } + + // update values.yaml content in chart + targetChart.ValuesYaml = replacedValuesYaml + + // merge override values and kvs into service's yaml + mergedValuesYaml, err = helmtool.MergeOverrideValues(replacedValuesYaml, targetChart.GetOverrideYaml(), targetChart.OverrideValues) + if err != nil { + return err + } + + // replace image into final merged values.yaml + replacedMergedValuesYaml, err = replaceImage(mergedValuesYaml, replaceValuesMap) + if err != nil { + return err + } + + restConfig, err = kube.GetRESTConfig(product.ClusterID) + if err != nil { + return err + } + helmClient, err = helmtool.NewClientFromRestConf(restConfig, namespace) + if err != nil { + return err + } + + // when replace image, should not wait + err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, 0, helmClient) + if err != nil { + return err + } + + if err = commonrepo.NewRenderSetColl().Update(renderSet); err != nil { + log.Errorf("[RenderSet.update] product %s error: %s", product.ProductName, err.Error()) + return fmt.Errorf("failed to update render set, productName %s", product.ProductName) + } + + if err = commonrepo.NewProductColl().Update(product); err != nil { + log.Errorf("[%s] update product %s error: %s", namespace, product.ProductName, err.Error()) + return fmt.Errorf("failed to update product info, name %s", product.ProductName) + } + + return nil +} + func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{EnvName: args.EnvName, Name: args.ProductName}) if err != nil { @@ -63,78 +332,49 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log commonservice.LogProductStats(namespace, setting.UpdateContainerImageEvent, args.ProductName, requestID, eventStart, log) }() - switch args.Type { - case setting.Deployment: - if err := updater.UpdateDeploymentImage(namespace, args.Name, args.ContainerName, args.Image, kubeClient); err != nil { - log.Errorf("[%s] UpdateDeploymentImageByName error: %v", namespace, err) - return e.ErrUpdateConainterImage.AddDesc("更新 Deployment 容器镜像失败") + // update service in helm way + if product.Source == setting.HelmDeployType { + serviceName, err := getHelmServiceName(namespace, args.Type, args.Name, kubeClient) + if err != nil { + return e.ErrUpdateConainterImage.AddErr(err) } - case setting.StatefulSet: - if err := updater.UpdateStatefulSetImage(namespace, args.Name, args.ContainerName, args.Image, kubeClient); err != nil { - log.Errorf("[%s] UpdateStatefulsetImageByName error: %v", namespace, err) - return e.ErrUpdateConainterImage.AddDesc("更新 StatefulSet 容器镜像失败") + err = updateContainerForHelmChart(serviceName, args.Type, args.Image, args.ContainerName, product) + if err != nil { + return e.ErrUpdateConainterImage.AddErr(err) } - default: - return nil - } - - oldImageName := "" - for _, group := range product.Services { - for _, service := range group { - //如果为helm,serviceName可能不匹配 - if product.Source != setting.HelmDeployType { - if service.ServiceName == args.ServiceName { - for _, container := range service.Containers { - if container.Name == args.ContainerName { - container.Image = args.Image - break - } - } - } + } else { + switch args.Type { + case setting.Deployment: + if err := updater.UpdateDeploymentImage(namespace, args.Name, args.ContainerName, args.Image, kubeClient); err != nil { + log.Errorf("[%s] UpdateDeploymentImageByName error: %s", namespace, err.Error()) + return e.ErrUpdateConainterImage.AddDesc("更新 Deployment 容器镜像失败") + } + case setting.StatefulSet: + if err := updater.UpdateStatefulSetImage(namespace, args.Name, args.ContainerName, args.Image, kubeClient); err != nil { + log.Errorf("[%s] UpdateStatefulsetImageByName error: %s", namespace, err.Error()) + return e.ErrUpdateConainterImage.AddDesc("更新 StatefulSet 容器镜像失败") + } + default: + return e.ErrUpdateConainterImage.AddDesc(fmt.Sprintf("不支持的资源类型: %s", args.Type)) + } + // update image info in product.services.container + for _, service := range product.GetServiceMap() { + if service.ServiceName != args.ServiceName { continue } for _, container := range service.Containers { if container.Name == args.ContainerName { - oldImageName = container.Image container.Image = args.Image break } } + break } - } - - //如果环境是helm环境也要更新renderSet - if product.Source == setting.HelmDeployType && oldImageName != "" { - renderSetOpt := &commonrepo.RenderSetFindOption{Name: namespace, Revision: product.Render.Revision} - renderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) - if err != nil { - log.Errorf("[RenderSet.find] update product %s error: %v", args.ProductName, err) - return e.ErrUpdateConainterImage.AddDesc("更新环境信息失败") - } - oldImageRepo := strings.Split(oldImageName, ":")[0] - newImageTag := strings.Split(args.Image, ":")[1] - - for _, chartInfo := range renderSet.ChartInfos { - newValues, err := updateImageTagInValues([]byte(chartInfo.ValuesYaml), oldImageRepo, newImageTag) - if err != nil { - log.Errorf("Failed to update image tag, err: %v", err) - continue - } else if newValues == nil { - continue - } - chartInfo.ValuesYaml = string(newValues) - } - - if err := commonrepo.NewRenderSetColl().Update(renderSet); err != nil { - log.Errorf("[RenderSet.update] product %s error: %v", args.ProductName, err) + if err := commonrepo.NewProductColl().Update(product); err != nil { + log.Errorf("[%s] update product %s error: %s", namespace, args.ProductName, err.Error()) return e.ErrUpdateConainterImage.AddDesc("更新环境信息失败") } } - - if err := commonrepo.NewProductColl().Update(product); err != nil { - log.Errorf("[%s] update product %s error: %v", namespace, args.ProductName, err) - return e.ErrUpdateConainterImage.AddDesc("更新环境信息失败") - } return nil } diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 7d8c5081..c4e9ce00 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -106,8 +106,7 @@ func getContainerLogFromS3(pipelineName, filenamePrefix string, taskID int64, lo if len(fileList) == 0 { return "", nil } - objectKey := storage.GetObjectPath(fileList[0]) - err = client.Download(storage.Bucket, objectKey, tempFile) + err = client.Download(storage.Bucket, fileList[0], tempFile) if err != nil { log.Errorf("GetContainerLogFromS3 Download err:%v", err) return "", err diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 5ef918f4..90555c12 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -36,6 +36,7 @@ import ( projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" @@ -199,6 +200,9 @@ func initDatabase() { commonrepo.NewWorkflowStatColl(), commonrepo.NewWorkLoadsStatColl(), + templaterepo.NewChartColl(), + templaterepo.NewDockerfileTemplateColl(), + systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), } { diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 0ce5f006..8f19493f 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -69,6 +69,20 @@ func CreateOrUpdateHelmService(c *gin.Context) { ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("projectName"), args, ctx.Logger) } +func CreateOrUpdateBulkHelmServices(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(svcservice.BulkHelmServiceCreationArgs) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") + return + } + args.CreatedBy = ctx.UserName + + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(c.Query("productName"), args, ctx.Logger) +} + func UpdateHelmService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 3fabc8e8..e83c9403 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -40,6 +40,7 @@ func (*Router) Inject(router *gin.RouterGroup) { helm.GET("/:productName/:serviceName/filePath", GetFilePath) helm.GET("/:productName/:serviceName/fileContent", GetFileContent) helm.POST("/services", CreateOrUpdateHelmService) + helm.POST("/services/bulk", CreateOrUpdateBulkHelmServices) helm.PUT("/:productName", UpdateHelmService) } @@ -79,4 +80,10 @@ func (*Router) Inject(router *gin.RouterGroup) { pm.POST("/:productName", gin2.UpdateOperationLogStatus, CreatePMService) pm.PUT("/:productName", gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) } + + template := router.Group("template") + { + template.POST("/load", LoadServiceFromYamlTemplate) + template.POST("/reload", ReloadServiceFromYamlTemplate) + } } diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go new file mode 100644 index 00000000..99fd797c --- /dev/null +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -0,0 +1,42 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + svcservice "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +type LoadServiceFromYamlTemplateReq struct { + ServiceName string `json:"service_name"` + ProjectName string `json:"project_name"` + TemplateID string `json:"template_id"` + Variables []*svcservice.Variable `json:"variables"` +} + +func LoadServiceFromYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(LoadServiceFromYamlTemplateReq) + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.UserName, req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, ctx.Logger) +} + +func ReloadServiceFromYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(LoadServiceFromYamlTemplateReq) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.UserName, req.ProjectName, req.ServiceName, req.Variables, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e549c782..5cf4e4dd 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -36,13 +36,16 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + templatestore "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/codehost" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -91,15 +94,30 @@ type Chart struct { } type helmServiceCreationArgs struct { - ServiceName string - FilePath string - ProductName string - CreateBy string - CodehostID int - Owner string - Repo string - Branch string - RepoLink string + ChartName string + ChartVersion string + ValuesYAML string + ServiceName string + FilePath string + ProductName string + CreateBy string + CodehostID int + Owner string + Repo string + Branch string + RepoLink string + Source string + HelmTemplateName string + ValuePaths []string + ValuesYaml string +} + +type ChartTemplateData struct { + TemplateName string + TemplateData *templatestore.Chart + ChartName string + ChartVersion string + DefaultValuesYAML []byte // content of values.yaml in template } func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { @@ -132,6 +150,7 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, } func GetHelmServiceModule(serviceName, productName string, revision int64, log *zap.SugaredLogger) (*HelmServiceModule, error) { + serviceTemplate, err := commonservice.GetServiceTemplate(serviceName, setting.HelmDeployType, productName, setting.ProductStatusDeleting, revision, log) if err != nil { return nil, err @@ -182,6 +201,44 @@ func GetFileContent(serviceName, productName, filePath, fileName string, log *za return string(fileContent), nil } +func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (*ChartTemplateData, error) { + // get chart template from local disk + templateChart, err := mongodb.NewChartColl().Get(templateName) + if err != nil { + logger.Errorf("Failed to get chart template %s, err: %s", templateName, err) + return nil, err + } + + localBase := configbase.LocalChartTemplatePath(templateName) + s3Base := configbase.ObjectStorageChartTemplatePath(templateName) + if err = fsservice.PreloadFiles(templateName, localBase, s3Base, logger); err != nil { + return nil, err + } + + base := filepath.Base(templateChart.Path) + defaultValuesFile := filepath.Join(localBase, base, setting.ValuesYaml) + defaultValues, _ := os.ReadFile(defaultValuesFile) + + chartFilePath := filepath.Join(localBase, base, setting.ChartYaml) + chartFileContent, err := os.ReadFile(chartFilePath) + if err != nil { + return nil, err + } + chart := new(Chart) + if err = yaml.Unmarshal(chartFileContent, chart); err != nil { + logger.Errorf("Failed to unmarshal chart yaml %s, err: %s", setting.ChartYaml, err) + return nil, err + } + + return &ChartTemplateData{ + TemplateName: templateName, + TemplateData: templateChart, + ChartName: chart.Name, + ChartVersion: chart.Version, + DefaultValuesYAML: defaultValues, + }, nil +} + func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: @@ -199,28 +256,16 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return fmt.Errorf("invalid argument") } - // get chart template from local disk - chart, err := mongodb.NewChartColl().Get(templateArgs.TemplateName) + templateChartInfo, err := prepareChartTemplateData(templateArgs.TemplateName, logger) if err != nil { - logger.Errorf("Failed to get chart template %s, err: %s", templateArgs.TemplateName, err) return err } - localBase := configbase.LocalChartTemplatePath(templateArgs.TemplateName) - s3Base := configbase.ObjectStorageChartTemplatePath(templateArgs.TemplateName) - if err = fsservice.PreloadFiles(templateArgs.TemplateName, localBase, s3Base, logger); err != nil { - return err - } - - // deal with values, values will come from template, git repo and user defined one, - // if one key exists in more than one values source, the latter one will override the former one. var values [][]byte - base := filepath.Base(chart.Path) - defaultValuesFile := filepath.Join(localBase, base, setting.ValuesYaml) - defaultValues, _ := os.ReadFile(defaultValuesFile) - if len(defaultValues) > 0 { - values = append(values, defaultValues) + if len(templateChartInfo.DefaultValuesYAML) > 0 { + values = append(values, templateChartInfo.DefaultValuesYAML) } + for _, path := range templateArgs.ValuesPaths { v, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: templateArgs.CodehostID, @@ -240,6 +285,9 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe values = append(values, []byte(templateArgs.ValuesYAML)) } + localBase := configbase.LocalChartTemplatePath(templateArgs.TemplateName) + base := filepath.Base(templateChartInfo.TemplateData.Path) + // copy template to service path and update the values.yaml from := filepath.Join(localBase, base) to := filepath.Join(config.LocalServicePath(projectName, args.Name), args.Name) @@ -269,14 +317,21 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ - ServiceName: args.Name, - FilePath: to, - ProductName: projectName, - CreateBy: args.CreatedBy, - CodehostID: templateArgs.CodehostID, - Owner: templateArgs.Owner, - Repo: templateArgs.Repo, - Branch: templateArgs.Branch, + ChartName: templateChartInfo.ChartName, + ChartVersion: templateChartInfo.ChartVersion, + ValuesYAML: string(merged), + ServiceName: args.Name, + FilePath: to, + ProductName: projectName, + CreateBy: args.CreatedBy, + CodehostID: templateArgs.CodehostID, + Owner: templateArgs.Owner, + Repo: templateArgs.Repo, + Branch: templateArgs.Branch, + Source: setting.SourceFromChartTemplate, + HelmTemplateName: templateArgs.TemplateName, + ValuePaths: templateArgs.ValuesPaths, + ValuesYaml: templateArgs.ValuesYAML, }, logger, ) @@ -297,6 +352,18 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil } +func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, error) { + if repoLink != "" { + return setting.SourceFromPublicRepo, nil + } + ch, err := codehost.GetCodeHostInfoByID(repoArgs.CodehostID) + if err != nil { + log.Errorf("Failed to get codeHost by id %d, err: %s", repoArgs.CodehostID, err.Error()) + return "", err + } + return ch.Type, nil +} + func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) error { var err error var repoLink string @@ -315,6 +382,13 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC repoLink = publicArgs.RepoLink } + + source, err := getCodehostType(repoArgs, repoLink) + if err != nil { + log.Errorf("Failed to get source form repo data %+v, err: %s", *repoArgs, err.Error()) + return err + } + helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(repoArgs.Paths)) var errs *multierror.Error @@ -335,13 +409,22 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC log.Infof("Loading chart under path %s", filePath) - var serviceName string + var ( + serviceName string + chartVersion string + valuesYAML []byte + ) + fsTree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, func(chartTree afero.Fs) (string, error) { - chartName, _, err := readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) - serviceName = chartName - return chartName, err + var err error + serviceName, chartVersion, err = readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + if err != nil { + return serviceName, err + } + valuesYAML, _, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + return serviceName, err }) if err != nil { log.Errorf("Failed to download files from source, err %s", err) @@ -361,15 +444,19 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ - ServiceName: serviceName, - FilePath: filePath, - ProductName: projectName, - CreateBy: args.CreatedBy, - CodehostID: repoArgs.CodehostID, - Owner: repoArgs.Owner, - Repo: repoArgs.Repo, - Branch: repoArgs.Branch, - RepoLink: repoLink, + ChartName: serviceName, + ChartVersion: chartVersion, + ValuesYAML: string(valuesYAML), + ServiceName: serviceName, + FilePath: filePath, + ProductName: projectName, + CreateBy: args.CreatedBy, + CodehostID: repoArgs.CodehostID, + Owner: repoArgs.Owner, + Repo: repoArgs.Repo, + Branch: repoArgs.Branch, + RepoLink: repoLink, + Source: source, }, log, ) @@ -396,6 +483,159 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return errs.ErrorOrNil() } +func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { + switch args.Source { + case LoadFromChartTemplate: + return CreateOrUpdateBulkHelmServiceFromTemplate(projectName, args, logger) + default: + return nil, fmt.Errorf("invalid source") + } +} + +func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { + templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) + if !ok { + return nil, fmt.Errorf("invalid argument") + } + + if args.ValuesData == nil || args.ValuesData.GitRepoConfig == nil || len(args.ValuesData.GitRepoConfig.ValuesPaths) == 0 { + return nil, fmt.Errorf("invalid argument, missing values") + } + + templateChartData, err := prepareChartTemplateData(templateArgs.TemplateName, logger) + if err != nil { + return nil, err + } + + localBase := configbase.LocalChartTemplatePath(templateArgs.TemplateName) + base := filepath.Base(templateChartData.TemplateData.Path) + // copy template to service path and update the values.yaml + from := filepath.Join(localBase, base) + + //record errors for every service + failedServiceMap := &sync.Map{} + renderChartMap := &sync.Map{} + + wg := sync.WaitGroup{} + // run goroutines to speed up + for _, singlePath := range args.ValuesData.GitRepoConfig.ValuesPaths { + wg.Add(1) + go func(repoConfig *commonservice.RepoConfig, path string) { + defer wg.Done() + renderChart, err := handleSingleService(projectName, repoConfig, path, from, args.CreatedBy, templateChartData, logger) + if err != nil { + failedServiceMap.Store(path, err.Error()) + } else { + renderChartMap.Store(renderChart.ServiceName, renderChart) + } + }(args.ValuesData.GitRepoConfig, singlePath) + } + + wg.Wait() + + resp := &BulkHelmServiceCreationResponse{ + SuccessServices: make([]string, 0), + FailedServices: make([]*FailedService, 0), + } + + renderChars := make([]*templatemodels.RenderChart, 0) + + renderChartMap.Range(func(key, value interface{}) bool { + resp.SuccessServices = append(resp.SuccessServices, key.(string)) + renderChars = append(renderChars, value.(*templatemodels.RenderChart)) + return true + }) + + failedServiceMap.Range(func(key, value interface{}) bool { + resp.FailedServices = append(resp.FailedServices, &FailedService{ + Path: key.(string), + Error: value.(string), + }) + return true + }) + + compareHelmVariable(renderChars, projectName, args.CreatedBy, logger) + + return resp, nil +} + +func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, path, fromPath, createBy string, + templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { + + valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ + CodehostID: repoConfig.CodehostID, + Owner: repoConfig.Owner, + Repo: repoConfig.Repo, + Path: path, + Branch: repoConfig.Branch, + }) + if err != nil { + return nil, err + } + + if len(valuesYAML) == 0 { + return nil, fmt.Errorf("values.yaml is empty") + } + + values := [][]byte{templateChartData.DefaultValuesYAML, valuesYAML} + mergedValues, err := yamlutil.Merge(values) + if err != nil { + logger.Errorf("Failed to merge values, err: %s", err) + return nil, err + } + + serviceName := filepath.Base(path) + serviceName = strings.TrimSuffix(serviceName, filepath.Ext(serviceName)) + + to := filepath.Join(config.LocalServicePath(projectName, serviceName), serviceName) + if err = copy.Copy(fromPath, to); err != nil { + logger.Errorf("Failed to copy file from %s to %s, err: %s", fromPath, to, err) + return nil, err + } + + // write values.yaml file + if err = os.WriteFile(filepath.Join(to, setting.ValuesYaml), mergedValues, 0644); err != nil { + logger.Errorf("Failed to write values, err: %s", err) + return nil, err + } + + fsTree := os.DirFS(config.LocalServicePath(projectName, serviceName)) + ServiceS3Base := config.ObjectStorageServicePath(projectName, serviceName) + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, serviceName, ServiceS3Base, logger); err != nil { + logger.Errorf("Failed to upload files for service %s in project %s, err: %s", serviceName, projectName, err) + return nil, err + } + + _, err = createOrUpdateHelmService( + fsTree, + &helmServiceCreationArgs{ + ChartName: templateChartData.ChartName, + ChartVersion: templateChartData.ChartVersion, + ValuesYAML: string(mergedValues), + ServiceName: serviceName, + FilePath: to, + ProductName: projectName, + CreateBy: createBy, + CodehostID: repoConfig.CodehostID, + Source: setting.SourceFromChartTemplate, + HelmTemplateName: templateChartData.TemplateName, + ValuePaths: []string{path}, + ValuesYaml: string(templateChartData.DefaultValuesYAML), + }, + logger, + ) + if err != nil { + logger.Errorf("Failed to create service %s in project %s, error: %s", serviceName, projectName, err) + return nil, err + } + + return &templatemodels.RenderChart{ + ServiceName: serviceName, + ChartVersion: templateChartData.ChartVersion, + ValuesYaml: string(mergedValues), + }, nil +} + func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (string, string, error) { chartFile, err := fs.ReadFile(chartTree, filepath.Join(base, setting.ChartYaml)) if err != nil { @@ -427,6 +667,57 @@ func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([] return content, valuesMap, nil } +func geneCreationDetail(args *helmServiceCreationArgs) interface{} { + switch args.Source { + case setting.SourceFromGitlab, + setting.SourceFromGithub, + setting.SourceFromGerrit, + setting.SourceFromCodeHub, + setting.SourceFromIlyshin: + return &models.CreateFromRepo{ + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: args.CodehostID, + Owner: args.Owner, + Repo: args.Repo, + Branch: args.Branch, + }, + LoadPath: args.FilePath, + } + case setting.SourceFromPublicRepo: + return &models.CreateFromPublicRepo{ + RepoLink: args.RepoLink, + LoadPath: args.FilePath, + } + case setting.SourceFromChartTemplate: + var yamlData *template.CustomYaml + if args.CodehostID > 0 { + yamlData = &template.CustomYaml{ + YamlSource: setting.ValuesYamlSourceGitRepo, + YamlContent: "", + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: args.CodehostID, + Owner: args.Owner, + Repo: args.Repo, + Branch: args.Branch, + }, + ValuesPaths: args.ValuePaths, + } + } else if len(args.ValuesYaml) > 0 { + yamlData = &template.CustomYaml{ + YamlSource: setting.ValuesYamlSourceFreeEdit, + YamlContent: args.ValuesYaml, + } + } + + return &models.CreateFromChartTemplate{ + YamlData: yamlData, + TemplateName: args.HelmTemplateName, + ServiceName: args.ServiceName, + } + } + return nil +} + func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*models.Service, error) { chartName, chartVersion, err := readChartYAML(fsTree, args.ServiceName, logger) if err != nil { @@ -471,6 +762,8 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg BranchName: args.Branch, LoadPath: args.FilePath, SrcPath: args.RepoLink, + CreateFrom: geneCreationDetail(args), + Source: args.Source, HelmChart: &models.HelmChart{ Name: chartName, Version: chartVersion, diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index a41b7146..0365592f 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -90,7 +90,7 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug } // Confirm whether the build exists - build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: args.Build.Name}) + build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: args.Build.Name, ProductName: args.Build.ProductName}) if err != nil { if err := commonservice.CreateBuild(username, args.Build, log); err != nil { log.Errorf("pmService.Create build %s error: %v", args.Build.Name, err) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c3b23dfa..56373af0 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -69,8 +69,9 @@ type ServiceModule struct { } type Variable struct { - Key string `json:"key"` - Value string `json:"value"` + Key string `json:"key"` + Value string `json:"value"` + Description string `json:"description,omitempty"` } type YamlPreviewForPorts struct { @@ -1050,7 +1051,7 @@ func ensureServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sug args.Containers = make([]*commonmodels.Container, 0) } // Only the gerrit/spock/external type needs to be processed by yaml - if args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromZadig || args.Source == setting.SourceFromExternal { + if args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromZadig || args.Source == setting.SourceFromExternal || args.Source == setting.ServiceSourceTemplate { // 拆分 all-in-one yaml文件 // 替换分隔符 args.Yaml = util.ReplaceWrapLine(args.Yaml) diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index bdfdcd5b..0edc04a4 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" ) @@ -37,12 +38,33 @@ type HelmLoadSource struct { type HelmServiceCreationArgs struct { HelmLoadSource - Name string `json:"name"` CreatedBy string `json:"createdBy"` CreateFrom interface{} `json:"createFrom"` } +type ValuesDataArgs struct { + YamlSource string `json:"yamlSource,omitempty"` + GitRepoConfig *service.RepoConfig `json:"gitRepoConfig,omitempty"` +} + +type BulkHelmServiceCreationArgs struct { + HelmLoadSource + CreateFrom interface{} `json:"createFrom"` + CreatedBy string `json:"createdBy"` + ValuesData *ValuesDataArgs `json:"valuesData"` +} + +type FailedService struct { + Path string `json:"path"` + Error string `json:"error"` +} + +type BulkHelmServiceCreationResponse struct { + SuccessServices []string `json:"successServices"` + FailedServices []*FailedService `json:"failedServices"` +} + type CreateFromRepo struct { CodehostID int `json:"codehostID"` Owner string `json:"owner"` @@ -57,13 +79,13 @@ type CreateFromPublicRepo struct { } type CreateFromChartTemplate struct { - CodehostID int `json:"codehostID"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Branch string `json:"branch"` + CodehostID int `json:"codehostID,omitempty"` + Owner string `json:"owner,omitempty"` + Repo string `json:"repo,omitempty"` + Branch string `json:"branch,omitempty"` TemplateName string `json:"templateName"` - ValuesPaths []string `json:"valuesPaths"` - ValuesYAML string `json:"valuesYAML"` + ValuesPaths []string `json:"valuesPaths,omitempty"` + ValuesYAML string `json:"valuesYAML,omitempty"` } func PublicRepoToPrivateRepoArgs(args *CreateFromPublicRepo) (*CreateFromRepo, error) { @@ -101,3 +123,18 @@ func (a *HelmServiceCreationArgs) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, (*tmp)(a)) } + +func (a *BulkHelmServiceCreationArgs) UnmarshalJSON(data []byte) error { + s := &HelmLoadSource{} + if err := json.Unmarshal(data, s); err != nil { + return err + } + + if s.Source == LoadFromChartTemplate { + a.CreateFrom = &CreateFromChartTemplate{} + } + + type tmp BulkHelmServiceCreationArgs + + return json.Unmarshal(data, (*tmp)(a)) +} diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go new file mode 100644 index 00000000..a434fdf0 --- /dev/null +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -0,0 +1,86 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +func LoadServiceFromYamlTemplate(username, projectName, serviceName, templateID string, variables []*Variable, logger *zap.SugaredLogger) error { + template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) + if err != nil { + logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) + return err + } + renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) + service := &commonmodels.Service{ + ServiceName: serviceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: templateID, + } + _, err = CreateServiceTemplate(username, service, logger) + if err != nil { + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) + } + return err +} + +func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, variables []*Variable, logger *zap.SugaredLogger) error { + service, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: projectName, + }) + if err != nil { + logger.Errorf("Cannot find service of name [%s] from project [%s], the error is: %s", serviceName, projectName, err) + return err + } + if service.Source != setting.ServiceSourceTemplate { + return errors.New("service is not created from template") + } + if service.TemplateID == "" { + return errors.New("failed to find template id for service:" + serviceName) + } + template, err := commonrepo.NewYamlTemplateColl().GetById(service.TemplateID) + if err != nil { + logger.Errorf("Failed to find template of ID: %s, the error is: %s", service.TemplateID, err) + return err + } + renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) + svc := &commonmodels.Service{ + ServiceName: serviceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: service.TemplateID, + } + _, err = CreateServiceTemplate(username, svc, logger) + if err != nil { + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", service.TemplateID, err) + } + return err +} + +func renderYamlFromTemplate(yaml, productName, serviceName string, variables []*Variable) string { + for _, variable := range variables { + yaml = strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) + } + yaml = strings.Replace(yaml, setting.TemplateVariableProduct, productName, -1) + yaml = strings.Replace(yaml, setting.TemplateVariableService, serviceName, -1) + return yaml +} + +func buildVariable(key string) string { + return fmt.Sprintf("{{.%s}}", key) +} diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 93377b12..b4756e25 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -103,3 +103,39 @@ func DeletePrivateKey(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "资源管理-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.Logger) } + +func ListLabels(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListLabels() +} + +type privateKeyArgs struct { + Option string `json:"option"` + Data []*commonmodels.PrivateKey `json:"data"` +} + +func BatchCreatePrivateKey(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(privateKeyArgs) + data, err := c.GetRawData() + if err != nil { + log.Errorf("batchCreatePrivateKey c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("batchCreatePrivateKey json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "批量新增", "资源管理-主机管理", "", string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid PrivateKey args") + return + } + + ctx.Err = service.BatchCreatePrivateKey(args.Data, args.Option, ctx.UserName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index f0175bbc..c72f6e13 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -78,6 +78,7 @@ func (*Router) Inject(router *gin.RouterGroup) { s3storage.GET("/:id", GetS3Storage) s3storage.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateS3Storage) s3storage.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteS3Storage) + s3storage.POST("/:id/releases/search", ListTars) } //系统清理缓存 @@ -151,7 +152,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { privateKey.GET("", ListPrivateKeys) privateKey.GET("/:id", GetPrivateKey) + privateKey.GET("/labels", ListLabels) privateKey.POST("", gin2.UpdateOperationLogStatus, CreatePrivateKey) + privateKey.POST("/batch", gin2.UpdateOperationLogStatus, BatchCreatePrivateKey) privateKey.PUT("/:id", gin2.UpdateOperationLogStatus, UpdatePrivateKey) privateKey.DELETE("/:id", gin2.UpdateOperationLogStatus, DeletePrivateKey) } diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index f7f0aa2d..f8e534ad 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -117,3 +117,20 @@ func DeleteS3Storage(c *gin.Context) { ctx.Err = service.DeleteS3Storage(ctx.UserName, c.Param("id"), ctx.Logger) } + +type ListTarsOption struct { + Names []string `json:"names"` +} + +func ListTars(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(ListTarsOption) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.ListTars(c.Param("id"), c.Query("kind"), args.Names, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index d34ca4a0..bc9d8aec 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -87,3 +87,67 @@ func DeletePrivateKey(id string, log *zap.SugaredLogger) error { } return nil } + +func ListLabels() ([]string, error) { + return commonrepo.NewPrivateKeyColl().DistinctLabels() +} + +// override: Full coverage (temporarily reserved) +// increment: Incremental coverage +// patch: Overwrite existing +func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username string, log *zap.SugaredLogger) error { + switch option { + //case "override": + // if err := commonrepo.NewPrivateKeyColl().DeleteAll(); err != nil { + // return e.ErrBulkCreatePrivateKey.AddDesc("delete all privateKeys failed") + // } + // for _, currentPrivateKey := range args { + // if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + // return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + // } + // currentPrivateKey.UpdateBy = username + // if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + // log.Errorf("PrivateKey.Create error: %s", err) + // return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") + // } + // } + case "increment": + for _, currentPrivateKey := range args { + if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + } + + if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { + continue + } + + currentPrivateKey.UpdateBy = username + if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") + } + } + + case "patch": + for _, currentPrivateKey := range args { + if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { + return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") + } + currentPrivateKey.UpdateBy = username + if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: currentPrivateKey.Name}); len(privateKeys) > 0 { + if err := commonrepo.NewPrivateKeyColl().Update(privateKeys[0].ID.Hex(), currentPrivateKey); err != nil { + log.Errorf("PrivateKey.update error: %s", err) + return e.ErrBulkCreatePrivateKey.AddDesc("bulk update privateKey failed") + } + continue + } + + if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + log.Errorf("PrivateKey.Create error: %s", err) + return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") + } + } + } + + return nil +} diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 89f26377..076b0fd6 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -17,8 +17,14 @@ limitations under the License. package service import ( + "strconv" + "strings" + "sync" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/wait" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" @@ -95,3 +101,72 @@ func GetS3Storage(id string, logger *zap.SugaredLogger) (*commonmodels.S3Storage return store, nil } + +func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) ([]*commonmodels.TarInfo, error) { + var ( + wg wait.Group + mutex sync.RWMutex + tarInfos = make([]*commonmodels.TarInfo, 0) + store *commonmodels.S3Storage + defaultS3 s3.S3 + defaultURL string + err error + ) + + store, err = commonrepo.NewS3StorageColl().Find(id) + if err != nil { + logger.Errorf("can't find store by id:%s err:%s", id, err) + return nil, err + } + defaultS3 = s3.S3{ + S3Storage: store, + } + defaultURL, err = defaultS3.GetEncryptedURL() + if err != nil { + logger.Errorf("defaultS3 GetEncryptedURL err:%s", err) + return nil, err + } + + for _, serviceName := range serviceNames { + newServiceName := serviceName + wg.Start(func() { + deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ + Name: newServiceName, + Type: kind, + Source: string(config.WorkflowType), + PackageStorageURI: store.Endpoint + "/" + store.Bucket, + }) + if err != nil { + logger.Errorf("ListTars err:%s", err) + return + } + for _, deliveryArtifact := range deliveryArtifacts { + activities, _, err := commonrepo.NewDeliveryActivityColl().List(&commonrepo.DeliveryActivityArgs{ArtifactID: deliveryArtifact.ID.Hex()}) + if err != nil { + logger.Errorf("deliveryActivity.list err:%s", err) + return + } + urlArr := strings.Split(activities[0].URL, "/") + workflowName := urlArr[len(urlArr)-2] + taskIDStr := urlArr[len(urlArr)-1] + taskID, err := strconv.Atoi(taskIDStr) + if err != nil { + logger.Errorf("string convert to int err:%s", err) + return + } + + mutex.Lock() + tarInfos = append(tarInfos, &commonmodels.TarInfo{ + URL: defaultURL, + Name: newServiceName, + FileName: deliveryArtifact.Image, + WorkflowName: workflowName, + TaskID: int64(taskID), + }) + mutex.Unlock() + } + }) + } + wg.Wait() + return tarInfos, nil +} diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go new file mode 100644 index 00000000..b01ee1b8 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -0,0 +1,128 @@ +package handler + +import ( + "errors" + + "github.com/gin-gonic/gin" + + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.DockerfileTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + // some dockerfile validation stuff + err := templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) + if err != nil { + ctx.Err = errors.New("invalid dockerfile, please check") + return + } + + ctx.Err = templateservice.CreateDockerfileTemplate(req, ctx.Logger) +} + +func UpdateDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.DockerfileTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + // some dockerfile validation stuff + err := templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) + if err != nil { + ctx.Err = errors.New("invalid dockerfile, please check") + return + } + + ctx.Err = templateservice.UpdateDockerfileTemplate(c.Param("id"), req, ctx.Logger) +} + +type listDockerfileQuery struct { + PageSize int `json:"page_size" form:"page_size,default=100"` + PageNum int `json:"page_num" form:"page_num,default=1"` +} + +type ListDockefileResp struct { + DockerfileTemplates []*templateservice.DockerfileListObject `json:"dockerfile_template"` + Total int `json:"total"` +} + +func ListDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Query Verification + args := listDockerfileQuery{} + if err := c.ShouldBindQuery(&args); err != nil { + ctx.Err = err + return + } + + dockerfileTemplateList, total, err := templateservice.ListDockerfileTemplate(args.PageNum, args.PageSize, ctx.Logger) + resp := ListDockefileResp{ + DockerfileTemplates: dockerfileTemplateList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err +} + +func GetDockerfileTemplateDetail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateDetail(c.Param("id"), ctx.Logger) +} + +func DeleteDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = templateservice.DeleteDockerfileTemplate(c.Param("id"), ctx.Logger) +} + +func GetDockerfileTemplateReference(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateReference(c.Param("id"), ctx.Logger) +} + +type validateDockerfileTemplateReq struct { + Content string `json:"content"` +} + +type validateDockerfileTemplateResp struct { + Error string `json:"error"` +} + +func ValidateDockerfileTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &validateDockerfileTemplateReq{} + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + err := templateservice.ValidateDockerfileTemplate(req.Content, ctx.Logger) + ctx.Resp = &validateDockerfileTemplateResp{Error: ""} + if err != nil { + ctx.Resp = &validateDockerfileTemplateResp{Error: err.Error()} + } +} diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 3dea48db..78bc1300 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -35,4 +35,26 @@ func (*Router) Inject(router *gin.RouterGroup) { install.PUT("/:name", UpdateChartTemplate) install.DELETE("/:name", RemoveChartTemplate) } + + dockerfile := router.Group("dockerfile") + { + dockerfile.POST("", CreateDockerfileTemplate) + dockerfile.PUT("/:id", UpdateDockerfileTemplate) + dockerfile.GET("", ListDockerfileTemplate) + dockerfile.GET("/:id", GetDockerfileTemplateDetail) + dockerfile.DELETE("/:id", DeleteDockerfileTemplate) + dockerfile.GET("/:id/reference", GetDockerfileTemplateReference) + dockerfile.POST("/validation", ValidateDockerfileTemplate) + } + + yaml := router.Group("yaml") + { + yaml.POST("", CreateYamlTemplate) + yaml.PUT("/:id", UpdateYamlTemplate) + yaml.GET("", ListYamlTemplate) + yaml.GET("/:id", GetYamlTemplateDetail) + yaml.DELETE("/:id", DeleteYamlTemplate) + yaml.GET("/:id/reference", GetYamlTemplateReference) + yaml.PUT("/:id/variables", UpdateYamlTemplateVariables) + } } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go new file mode 100644 index 00000000..ab5c9909 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -0,0 +1,107 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.YamlTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.CreateYamlTemplate(req, ctx.Logger) +} + +func UpdateYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &templateservice.YamlTemplate{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.UpdateYamlTemplate(c.Param("id"), req, ctx.Logger) +} + +type listYamlQuery struct { + PageSize int `json:"page_size" form:"page_size,default=100"` + PageNum int `json:"page_num" form:"page_num,default=1"` +} + +type ListYamlResp struct { + SystemVariables []*templateservice.Variable `json:"system_variables"` + YamlTemplates []*templateservice.YamlListObject `json:"yaml_template"` + Total int `json:"total"` +} + +func ListYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Query Verification + args := &listYamlQuery{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + systemVariables := templateservice.GetSystemDefaultVariables() + YamlTemplateList, total, err := templateservice.ListYamlTemplate(args.PageNum, args.PageSize, ctx.Logger) + resp := ListYamlResp{ + SystemVariables: systemVariables, + YamlTemplates: YamlTemplateList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err +} + +func GetYamlTemplateDetail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetYamlTemplateDetail(c.Param("id"), ctx.Logger) +} + +func DeleteYamlTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = templateservice.DeleteYamlTemplate(c.Param("id"), ctx.Logger) +} + +func GetYamlTemplateReference(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetYamlTemplateReference(c.Param("id"), ctx.Logger) +} + +type updateYamlTemplateVariablesReq struct { + Variables []*templateservice.Variable `json:"variables"` +} + +func UpdateYamlTemplateVariables(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &updateYamlTemplateVariablesReq{} + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + + ctx.Err = templateservice.UpdateYamlTemplateVariables(c.Param("id"), req.Variables, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go new file mode 100644 index 00000000..19ab53db --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go @@ -0,0 +1,13 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type DockerfileTemplate struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Content string `bson:"content" json:"content"` +} + +func (DockerfileTemplate) TableName() string { + return "dockerfile_template" +} diff --git a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go new file mode 100644 index 00000000..a904efc5 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go @@ -0,0 +1,116 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type DockerfileTemplateColl struct { + *mongo.Collection + + coll string +} + +func NewDockerfileTemplateColl() *DockerfileTemplateColl { + name := models.DockerfileTemplate{}.TableName() + return &DockerfileTemplateColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *DockerfileTemplateColl) GetCollectionName() string { + return c.coll +} + +func (c *DockerfileTemplateColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"name": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *DockerfileTemplateColl) Create(obj *models.DockerfileTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + return err +} + +func (c *DockerfileTemplateColl) Update(idString string, obj *models.DockerfileTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *DockerfileTemplateColl) List(pageNum, pageSize int) ([]*models.DockerfileTemplate, int, error) { + resp := make([]*models.DockerfileTemplate, 0) + query := bson.M{} + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + opt := options.Find(). + SetSkip(int64((pageNum - 1) * pageSize)). + SetLimit(int64(pageSize)) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, int(count), nil +} + +func (c *DockerfileTemplateColl) GetById(idstring string) (*models.DockerfileTemplate, error) { + resp := new(models.DockerfileTemplate) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *DockerfileTemplateColl) DeleteByID(idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go new file mode 100644 index 00000000..2fa44246 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -0,0 +1,171 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" + dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" + "go.uber.org/zap" + + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredLogger) error { + err := mongodb.NewDockerfileTemplateColl().Create(&models.DockerfileTemplate{ + Name: template.Name, + Content: template.Content, + }) + if err != nil { + logger.Errorf("create dockerfile template error: %s", err) + } + return err +} + +func UpdateDockerfileTemplate(id string, template *DockerfileTemplate, logger *zap.SugaredLogger) error { + err := mongodb.NewDockerfileTemplateColl().Update( + id, + &models.DockerfileTemplate{ + Name: template.Name, + Content: template.Content, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %s", err) + } + return err +} + +func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*DockerfileListObject, int, error) { + resp := make([]*DockerfileListObject, 0) + templateList, total, err := mongodb.NewDockerfileTemplateColl().List(pageNum, pageSize) + if err != nil { + logger.Errorf("list dockerfile template error: %s", err) + return resp, 0, err + } + for _, obj := range templateList { + resp = append(resp, &DockerfileListObject{ + ID: obj.ID.Hex(), + Name: obj.Name, + }) + } + return resp, total, err +} + +func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*DockerfileDetail, error) { + resp := new(DockerfileDetail) + dockerfileTemplate, err := mongodb.NewDockerfileTemplateColl().GetById(id) + if err != nil { + logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) + return nil, err + } + variables, err := getVariables(dockerfileTemplate.Content, logger) + if err != nil { + return nil, errors.New("failed to get variables from dockerfile") + } + resp.ID = dockerfileTemplate.ID.Hex() + resp.Name = dockerfileTemplate.Name + resp.Content = dockerfileTemplate.Content + resp.Variables = variables + return resp, nil +} + +func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { + ref, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for template id: %s, the error is: %s", id, err) + return err + } + if len(ref) > 0 { + return errors.New("this template is in use") + } + err = mongodb.NewDockerfileTemplateColl().DeleteByID(id) + if err != nil { + logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) + } + return err +} + +func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*BuildReference, error) { + ret := make([]*BuildReference, 0) + referenceList, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) + return ret, err + } + for _, reference := range referenceList { + ret = append(ret, &BuildReference{ + BuildName: reference.Name, + ProjectName: reference.ProductName, + }) + } + return ret, nil +} + +func ValidateDockerfileTemplate(template string, logger *zap.SugaredLogger) error { + // some dockerfile validation stuff + reader := strings.NewReader(template) + result, err := dockerfileparser.Parse(reader) + if err != nil { + return err + } + _, _, err = dockerfileinstructions.Parse(result.AST) + if err != nil { + return err + } + return nil +} + +func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { + ret := make([]*Variable, 0) + reader := strings.NewReader(s) + result, err := dockerfileparser.Parse(reader) + if err != nil { + logger.Errorf("Failed to parse the dockerfile from source, the error is: %s", err) + return []*Variable{}, err + } + stages, metaArgs, err := dockerfileinstructions.Parse(result.AST) + if err != nil { + logger.Errorf("Failed to parse stages from generated dockerfile AST, the error is: %s", err) + return []*Variable{}, err + } + keyMap := make(map[string]int) + for _, metaArg := range metaArgs { + for _, arg := range metaArg.Args { + if keyMap[arg.Key] == 0 { + ret = append(ret, &Variable{ + Key: arg.Key, + Value: arg.ValueString(), + }) + keyMap[arg.Key] = 1 + } + } + } + for _, stage := range stages { + for _, command := range stage.Commands { + if command.Name() == setting.DockerfileCmdArg { + fullCommand := fmt.Sprintf("%s", command) + commandContent := strings.Split(fullCommand, " ")[1] + kv := strings.Split(commandContent, "=") + key := kv[0] + value := "" + if len(kv) > 1 { + value = kv[1] + } + // if key has not been added yet + if keyMap[key] == 0 { + ret = append(ret, &Variable{ + Key: key, + Value: value, + }) + keyMap[key] = 1 + } + } + } + } + return ret, nil +} diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/templatestore/service/types.go index da8cdeb7..92beafe2 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/templatestore/service/types.go @@ -28,3 +28,54 @@ type Chart struct { Files []*fs.FileInfo `json:"files,omitempty"` } + +type DockerfileTemplate struct { + Name string `json:"name"` + Content string `json:"content"` +} + +type DockerfileListObject struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type DockerfileDetail struct { + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*Variable `json:"variable"` +} + +type Variable struct { + Key string `json:"key"` + Value string `json:"value,omitempty"` + Description string `json:"description,omitempty"` +} + +type BuildReference struct { + BuildName string `json:"build_name"` + ProjectName string `json:"project_name"` +} + +type YamlTemplate struct { + Name string `json:"name"` + Content string `json:"content"` + Variable []*Variable `json:"variable"` +} + +type YamlListObject struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type YamlDetail struct { + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*Variable `json:"variable"` +} + +type ServiceReference struct { + ProjectName string `json:"project_name"` + ServiceName string `json:"service_name"` +} diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go new file mode 100644 index 00000000..fc382dff --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -0,0 +1,212 @@ +package service + +import ( + "errors" + "fmt" + "regexp" + "strings" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +var DefaultSystemVariable = map[string]string{ + setting.TemplateVariableProduct: setting.TemplateVariableProductDescription, + setting.TemplateVariableService: setting.TemplateVariableServiceDescription, +} + +func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { + variableList, err := getYamlVariables(template.Content, logger) + if err != nil { + return err + } + variables := make([]*models.Variable, 0) + for _, v := range variableList { + variables = append(variables, &models.Variable{ + Key: v.Key, + Value: v.Value, + }) + } + err = mongodb.NewYamlTemplateColl().Create(&models.YamlTemplate{ + Name: template.Name, + Content: template.Content, + Variables: variables, + }) + if err != nil { + logger.Errorf("create dockerfile template error: %s", err) + } + return err +} + +func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { + variableList, err := getYamlVariables(template.Content, logger) + if err != nil { + return err + } + variables := make([]*models.Variable, 0) + for _, v := range variableList { + variables = append(variables, &models.Variable{ + Key: v.Key, + Value: v.Value, + }) + } + err = mongodb.NewYamlTemplateColl().Update( + id, + &models.YamlTemplate{ + Name: template.Name, + Content: template.Content, + Variables: variables, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %s", err) + } + return err +} + +func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*YamlListObject, int, error) { + resp := make([]*YamlListObject, 0) + templateList, total, err := mongodb.NewYamlTemplateColl().List(pageNum, pageSize) + if err != nil { + logger.Errorf("list dockerfile template error: %s", err) + return resp, 0, err + } + for _, obj := range templateList { + resp = append(resp, &YamlListObject{ + ID: obj.ID.Hex(), + Name: obj.Name, + }) + } + return resp, total, err +} + +func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, error) { + resp := new(YamlDetail) + yamlTemplate, err := mongodb.NewYamlTemplateColl().GetById(id) + if err != nil { + logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) + return nil, err + } + variables := make([]*Variable, 0) + for _, v := range yamlTemplate.Variables { + variables = append(variables, &Variable{ + Key: v.Key, + Value: v.Value, + }) + } + resp.ID = yamlTemplate.ID.Hex() + resp.Name = yamlTemplate.Name + resp.Content = yamlTemplate.Content + resp.Variables = variables + return resp, nil +} + +func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { + ref, err := commonrepo.NewServiceColl().GetTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get service reference for template id: %s, the error is: %s", id, err) + return err + } + if len(ref) > 0 { + return errors.New("this template is in use") + } + err = mongodb.NewYamlTemplateColl().DeleteByID(id) + if err != nil { + logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) + } + return err +} + +func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceReference, error) { + ret := make([]*ServiceReference, 0) + referenceList, err := commonrepo.NewServiceColl().GetTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) + return ret, err + } + for _, reference := range referenceList { + ret = append(ret, &ServiceReference{ + ServiceName: reference.ServiceName, + ProjectName: reference.ProductName, + }) + } + return ret, nil +} + +func UpdateYamlTemplateVariables(id string, variables []*Variable, logger *zap.SugaredLogger) error { + templateDetail, err := mongodb.NewYamlTemplateColl().GetById(id) + if err != nil { + logger.Errorf("Failed to find yaml template of id: %s, the error is: %s", id, err) + return err + } + keyMap := make(map[string]int) + // the keys in database will not collide + for _, v := range templateDetail.Variables { + keyMap[v.Key] = 1 + } + newVars := make([]*models.Variable, 0) + for _, vars := range variables { + if keyMap[vars.Key] == 0 { + errorMsg := fmt.Sprintf("Given key [%s] does not exist in yaml template [%s]", vars.Key, id) + return errors.New(errorMsg) + } + newVars = append(newVars, &models.Variable{ + Key: vars.Key, + Value: vars.Value, + }) + } + err = mongodb.NewYamlTemplateColl().Update( + id, + &models.YamlTemplate{ + Name: templateDetail.Name, + Content: templateDetail.Content, + Variables: newVars, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %s", err) + } + return err +} + +func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { + resp := make([]*Variable, 0) + regex, err := regexp.Compile(setting.RegExpParameter) + if err != nil { + logger.Errorf("Cannot get regexp from the expression: %s, the error is: %s", setting.RegExpParameter, err) + return []*Variable{}, err + } + params := regex.FindAllString(s, -1) + keyMap := make(map[string]int) + for _, param := range params { + key := getParameterKey(param) + if keyMap[key] == 0 { + resp = append(resp, &Variable{ + Key: key, + }) + keyMap[key] = 1 + } + } + return resp, nil +} + +func GetSystemDefaultVariables() []*Variable { + resp := make([]*Variable, 0) + for key, description := range DefaultSystemVariable { + resp = append(resp, &Variable{ + Key: key, + Description: description, + }) + } + return resp +} + +// getParameter +func getParameterKey(parameter string) string { + a := strings.TrimPrefix(parameter, "{{.") + return strings.TrimSuffix(a, "}}") +} diff --git a/pkg/microservice/aslan/core/workflow/handler/build.go b/pkg/microservice/aslan/core/workflow/handler/build.go index 2fed933d..aeec1312 100644 --- a/pkg/microservice/aslan/core/workflow/handler/build.go +++ b/pkg/microservice/aslan/core/workflow/handler/build.go @@ -19,6 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -27,7 +28,11 @@ func BuildModuleToSubTasks(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - resp, err := workflow.BuildModuleToSubTasks(c.Param("name"), "", "", c.Query("projectName"), nil, nil, ctx.Logger) + args := &models.BuildModuleArgs{ + BuildName: c.Param("name"), + ProductName: c.Query("productName"), + } + resp, err := workflow.BuildModuleToSubTasks(args, ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index bb3bb851..81a250a3 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -105,11 +105,11 @@ func CreateWorkflowTask(c *gin.Context) { return } - if args.WorklowTaskCreator != setting.CronTaskCreator && args.WorklowTaskCreator != setting.WebhookTaskCreator { - args.WorklowTaskCreator = ctx.UserName + if args.WorkflowTaskCreator != setting.CronTaskCreator && args.WorkflowTaskCreator != setting.WebhookTaskCreator { + args.WorkflowTaskCreator = ctx.UserName } - ctx.Resp, ctx.Err = workflow.CreateWorkflowTask(args, args.WorklowTaskCreator, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateWorkflowTask(args, args.WorkflowTaskCreator, ctx.Logger) // 发送通知 if ctx.Err != nil { @@ -138,11 +138,11 @@ func CreateArtifactWorkflowTask(c *gin.Context) { return } - if args.WorklowTaskCreator != setting.CronTaskCreator && args.WorklowTaskCreator != setting.WebhookTaskCreator { - args.WorklowTaskCreator = ctx.UserName + if args.WorkflowTaskCreator != setting.CronTaskCreator && args.WorkflowTaskCreator != setting.WebhookTaskCreator { + args.WorkflowTaskCreator = ctx.UserName } - ctx.Resp, ctx.Err = workflow.CreateArtifactWorkflowTask(args, args.WorklowTaskCreator, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateArtifactWorkflowTask(args, args.WorkflowTaskCreator, ctx.Logger) } // ListWorkflowTasksResult workflowtask分页信息 diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index dcefdc6c..175bc6bb 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -149,7 +149,7 @@ type workflowArgsFactory struct { func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, repo *types.Repository) *commonmodels.WorkflowTaskArgs { workflow := waf.workflow args.WorkflowName = workflow.Name - args.WorklowTaskCreator = setting.WebhookTaskCreator + args.WorkflowTaskCreator = setting.WebhookTaskCreator args.ProductTmplName = workflow.ProductTmplName args.ReqID = waf.reqID var targetMap map[string][]commonmodels.DeployEnv diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 0b2692b2..01f804ac 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -244,8 +244,10 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifact.ImageTag = buildInfo.JobCtx.FileArchiveCtx.FileName deliveryArtifact.Type = string(config.File) deliveryArtifact.PackageFileLocation = buildInfo.JobCtx.FileArchiveCtx.FileLocation - storageInfo, _ := s3.NewS3StorageFromEncryptedURI(pt.StorageURI) - deliveryArtifact.PackageStorageURI = storageInfo.Endpoint + if storageInfo, err := s3.NewS3StorageFromEncryptedURI(pt.StorageURI); err == nil { + deliveryArtifact.PackageStorageURI = storageInfo.Endpoint + "/" + storageInfo.Bucket + } + deliveryArtifactArray = append(deliveryArtifactArray, deliveryArtifact) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 556a23a8..c59d90c1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -68,7 +68,9 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C // 如果用户使用预定义编译配置, 则从编译模块配置中生成SubTasks if pipeline.BuildModuleVer != "" { - subTasks, err := BuildModuleToSubTasks("", pipeline.Target, "", "", nil, nil, log) + subTasks, err := BuildModuleToSubTasks(&commonmodels.BuildModuleArgs{ + Target: pipeline.Target, + }, log) if err != nil { return nil, e.ErrCreateTask.AddErr(err) } @@ -923,20 +925,15 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog } func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, artifactFiles, _ := GetTestArtifactInfo(pipelineName, "", taskID, log) - tempdir, _ := ioutil.TempDir("", "") - sourcePath := path.Join(tempdir, "artifact") + s3Storage, client, artifactFiles, err := GetArtifactAndS3Info(pipelineName, "", taskID, log) + if err != nil { + return nil, fmt.Errorf("download artifact err: %s", err) + } + tempDir, _ := ioutil.TempDir("", "") + sourcePath := path.Join(tempDir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { _ = os.MkdirAll(sourcePath, 0777) } - forcedPathStyle := true - if s3Storage.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err != nil { - return nil, fmt.Errorf("failed to create s3 client , error: %v", err) - } for _, artifactFile := range artifactFiles { artifactFileArr := strings.Split(artifactFile, "/") @@ -947,12 +944,10 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL return nil, fmt.Errorf("failed to create file %s %v", artifactFileName, err) } defer func() { - file.Close() - os.Remove(path.Join(sourcePath, artifactFileName)) + _ = file.Close() }() - objectKey := s3Storage.GetObjectPath(artifactFile) - err = client.Download(s3Storage.Bucket, objectKey, file.Name()) + err = client.Download(s3Storage.Bucket, artifactFile, file.Name()) if err != nil { return nil, fmt.Errorf("failed to download %s %v", artifactFile, err) } @@ -967,20 +962,20 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL } defer func() { _ = os.Remove(artifactTarFileName) - _ = os.Remove(tempdir) + _ = os.Remove(tempDir) }() fileBytes, err := ioutil.ReadFile(path.Join(sourcePath, "artifact.tar.gz")) return fileBytes, err } -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, []string, error) { +func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, fis, nil + return nil, nil, fis, err } if storage.Subfolder != "" { @@ -995,13 +990,13 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) - return nil, fis, nil + return nil, nil, fis, err } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, fis, nil + return nil, nil, fis, err } - return storage, files, nil + return storage, client, files, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index f233e98f..797cbd47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -533,7 +533,11 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, deliver } switch deliveryType { case config.TarResourceType: - return replaceVariable(customTarRule, candidate) + newTarRule := replaceVariable(customTarRule, candidate) + if strings.Contains(newTarRule, ":") { + return strings.Replace(newTarRule, ":", "-", -1) + } + return newTarRule default: return replaceVariable(customImageRule, candidate) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index a6aa7d81..b15a1f18 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -215,7 +215,12 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge } stages := make([]*commonmodels.Stage, 0) - subTasks, err := BuildModuleToSubTasks("", args.ServiceName, args.ServiceName, args.ProductName, nil, nil, log) + buildModuleArgs := &commonmodels.BuildModuleArgs{ + Target: args.ServiceName, + ServiceName: args.ServiceName, + ProductName: args.ProductName, + } + subTasks, err := BuildModuleToSubTasks(buildModuleArgs, log) if err != nil { return nil, e.ErrCreateTask.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index bf65a5bb..fc4fdc88 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -60,9 +60,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { workflowNames := []string{productName + "-workflow-dev", productName + "-workflow-qa", productName + "-workflow-ops"} // 云主机场景不创建ops工作流 - if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.BasicFacility == "cloud_host" { - workflowNames = []string{productName + "-workflow-dev", productName + "-workflow-qa"} - } else if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { + if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { workflowNames = []string{productName + "-workflow-dev"} } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f94144fb..4184d9aa 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -28,6 +28,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -497,7 +498,14 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, var subTasks []map[string]interface{} var err error if target.JenkinsBuildArgs == nil { - subTasks, err = BuildModuleToSubTasks("", target.Name, target.ServiceName, target.ProductName, target.Envs, env, log) + buildModuleArgs := &commonmodels.BuildModuleArgs{ + Target: target.Name, + ServiceName: target.ServiceName, + ProductName: target.ProductName, + Variables: target.Envs, + Env: env, + } + subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) } else { subTasks, err = JenkinsBuildModuleToSubTasks(&JenkinsBuildOption{ Target: target.Name, @@ -1095,7 +1103,7 @@ func addSecurityToSubTasks() (map[string]interface{}, error) { } func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTaskArgs) *commonmodels.TaskArgs { - resp := &commonmodels.TaskArgs{PipelineName: workflowArgs.WorkflowName, TaskCreator: workflowArgs.WorklowTaskCreator} + resp := &commonmodels.TaskArgs{PipelineName: workflowArgs.WorkflowName, TaskCreator: workflowArgs.WorkflowTaskCreator} for _, build := range workflowArgs.Target { if build.Name == target { if build.Build != nil { @@ -1119,7 +1127,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } testArgs := args.Tests - testCreator := args.WorklowTaskCreator + testCreator := args.WorkflowTaskCreator registries, err := commonservice.ListRegistryNamespaces(log) if err != nil { @@ -1271,34 +1279,54 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator stages := make([]*commonmodels.Stage, 0) for _, artifact := range args.Artifact { subTasks := make([]map[string]interface{}, 0) - artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) - if err != nil { - log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) - return nil, e.ErrCreateTask.AddErr(err) - } - subTasks = append(subTasks, artifactSubtask) - if env != nil { - // 生成部署的subtask - for _, deployEnv := range artifact.Deploy { - deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) - if err != nil { - log.Errorf("deploy env to subtask error: %v", err) - return nil, err - } - - if workflow.ResetImage { - resetImageTask, err := resetImageTaskToSubTask(deployEnv, env) + // image artifact deploy + if artifact.Image != "" { + artifactSubtask, err := artifactToSubTasks(artifact.Name, artifact.Image) + if err != nil { + log.Errorf("artifactToSubTasks artifact.Name:[%s] err:%v", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) + } + subTasks = append(subTasks, artifactSubtask) + if env != nil { + // 生成部署的subtask + for _, deployEnv := range artifact.Deploy { + deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) if err != nil { - log.Errorf("resetImageTaskToSubTask deploy env:[%s] err:%v ", deployEnv.Env, err) + log.Errorf("deploy env to subtask error: %v", err) return nil, err } - if resetImageTask != nil { - subTasks = append(subTasks, resetImageTask) - } - } - subTasks = append(subTasks, deployTask) + if workflow.ResetImage { + resetImageTask, err := resetImageTaskToSubTask(deployEnv, env) + if err != nil { + log.Errorf("resetImageTaskToSubTask deploy env:[%s] err:%v ", deployEnv.Env, err) + return nil, err + } + if resetImageTask != nil { + subTasks = append(subTasks, resetImageTask) + } + } + subTasks = append(subTasks, deployTask) + } + } + } else if artifact.FileName != "" { + buildModuleArgs := &commonmodels.BuildModuleArgs{ + Target: artifact.Name, + ServiceName: artifact.ServiceName, + ProductName: args.ProductTmplName, + Env: env, + URL: artifact.URL, + WorkflowName: artifact.WorkflowName, + TaskID: artifact.TaskID, + FileName: artifact.FileName, + TaskType: string(config.TaskArtifactDeploy), + } + buildSubtasks, err := BuildModuleToSubTasks(buildModuleArgs, log) + if err != nil { + log.Errorf("buildModuleToSubTasks target:[%s] err:%s", artifact.Name, err) + return nil, e.ErrCreateTask.AddErr(err) } + subTasks = append(subTasks, buildSubtasks...) } // 生成分发的subtask @@ -1347,12 +1375,15 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator // 填充subtask之间关联内容 task := &task.Task{ + ProductName: args.ProductTmplName, + PipelineName: args.WorkflowName, TaskID: nextTaskID, TaskCreator: taskCreator, ReqID: args.ReqID, SubTasks: subTasks, ServiceName: artifact.Name, ConfigPayload: configPayload, + TaskArgs: workFlowArgsToTaskArgs(artifact.Name, args), WorkflowArgs: args, } sort.Sort(ByTaskKind(task.SubTasks)) @@ -1456,25 +1487,25 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator return resp, nil } -func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, envs []*commonmodels.KeyVal, pro *commonmodels.Product, log *zap.SugaredLogger) ([]map[string]interface{}, error) { +func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredLogger) ([]map[string]interface{}, error) { var ( subTasks = make([]map[string]interface{}, 0) serviceTmpl *commonmodels.Service ) opt := &commonrepo.BuildListOption{ - Name: moduleName, - ServiceName: serviceName, - ProductName: productName, + Name: args.BuildName, + ServiceName: args.ServiceName, + ProductName: args.ProductName, } - if len(target) > 0 { - opt.Targets = []string{target} + if len(args.Target) > 0 { + opt.Targets = []string{args.Target} } - if pro != nil { + if args.Env != nil { serviceTmpl, _ = commonservice.GetServiceTemplate( - target, setting.PMDeployType, productName, setting.ProductStatusDeleting, 0, log, + args.Target, setting.PMDeployType, args.ProductName, setting.ProductStatusDeleting, 0, log, ) } @@ -1493,8 +1524,8 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, - ServiceName: target, - Service: serviceName, + ServiceName: args.Target, + Service: args.ServiceName, JobCtx: task.JobCtx{}, ImageID: module.PreBuild.ImageID, BuildOS: module.PreBuild.BuildOS, @@ -1502,6 +1533,11 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, ResReq: module.PreBuild.ResReq, Timeout: module.Timeout, Registries: registries, + ProductName: args.ProductName, + } + + if args.TaskType != "" { + build.TaskType = config.TaskArtifactDeploy } // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 @@ -1519,24 +1555,30 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, } if serviceTmpl != nil { - build.Namespace = pro.Namespace + build.Namespace = args.Env.Namespace build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) for _, envConfig := range serviceTmpl.EnvConfigs { - privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByIDs(envConfig.HostIDs) + privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) if err != nil { - log.Errorf("ListNameByIDs err:%s", err) + log.Errorf("ListNameByArgs ids err:%s", err) continue } - for _, privateKey := range privateKeys { - envHost[envConfig.EnvName] = append(envHost[envConfig.EnvName], privateKey.IP) + ips := sets.NewString() + ips = extractHostIPs(privateKeys, ips) + privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + continue } + ips = extractHostIPs(privateKeys, ips) + envHost[envConfig.EnvName] = ips.List() } build.EnvHostInfo = envHost } - if pro != nil { - build.EnvName = pro.EnvName + if args.Env != nil { + build.EnvName = args.Env.EnvName } if build.InstallItems == nil { @@ -1583,9 +1625,9 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, build.JobCtx.EnvVars = make([]*commonmodels.KeyVal, 0) } - if len(envs) > 0 { + if len(args.Variables) > 0 { for _, envVar := range build.JobCtx.EnvVars { - for _, overwrite := range envs { + for _, overwrite := range args.Variables { if overwrite.Key == envVar.Key && overwrite.Value != setting.MaskValue { envVar.Value = overwrite.Value envVar.IsCredential = overwrite.IsCredential @@ -1601,6 +1643,8 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ + Source: module.PostBuild.DockerBuild.Source, + TemplateID: module.PostBuild.DockerBuild.TemplateID, WorkDir: module.PostBuild.DockerBuild.WorkDir, DockerFile: module.PostBuild.DockerBuild.DockerFile, BuildArgs: module.PostBuild.DockerBuild.BuildArgs, @@ -1619,6 +1663,15 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, build.JobCtx.Caches = module.Caches + if args.FileName != "" { + build.ArtifactInfo = &task.ArtifactInfo{ + URL: args.URL, + WorkflowName: args.WorkflowName, + TaskID: args.TaskID, + FileName: args.FileName, + } + } + bst, err := build.ToSubTask() if err != nil { return subTasks, e.ErrConvertSubTasks.AddErr(err) @@ -1629,6 +1682,13 @@ func BuildModuleToSubTasks(moduleName, target, serviceName, productName string, return subTasks, nil } +func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { + for _, privateKey := range privateKeys { + ips.Insert(privateKey.IP) + } + return ips +} + func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) error { var ( buildEnvs []*commonmodels.KeyVal @@ -1651,7 +1711,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e switch pre.TaskType { - case config.TaskBuild: + case config.TaskBuild, config.TaskArtifactDeploy: t, err := base.ToBuildTask(subTask) fmtBuildsTask(t, log) if err != nil { @@ -1798,7 +1858,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { if pt.TaskArgs == nil { - pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorklowTaskCreator} + pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorkflowTaskCreator} } registry := pt.ConfigPayload.RepoConfigs[pt.WorkflowArgs.RegistryID] t.Image = fmt.Sprintf("%s/%s/%s", util.GetURLHostName(registry.RegAddr), registry.Namespace, t.Image) diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go b/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go index 3c8ef86f..69171697 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/workspace.go @@ -38,5 +38,5 @@ func GetTestArtifactInfo(c *gin.Context) { dir := c.Query("dir") - _, ctx.Resp, ctx.Err = service.GetTestArtifactInfo(c.Param("pipelineName"), dir, taskID, ctx.Logger) + ctx.Resp, ctx.Err = service.GetTestArtifactInfo(c.Param("pipelineName"), dir, taskID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index 7f791612..da56a35e 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -18,22 +18,22 @@ package service import ( "fmt" + "path" "go.uber.org/zap" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" ) -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*commonmodels.S3Storage, []string, error) { +func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) ([]string, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, fis, nil + return fis, err } if storage.Subfolder != "" { storage.Subfolder = fmt.Sprintf("%s/%s/%d/%s", storage.Subfolder, pipelineName, taskID, "artifact") @@ -47,13 +47,18 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo create s3 client err:%v", err) - return nil, fis, nil + return fis, err } prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, fis, nil + return fis, err } - return storage.S3Storage, files, nil + resp := make([]string, 0) + for _, file := range files { + _, fileName := path.Split(file) + resp = append(resp, fileName) + } + return resp, nil } diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index ecce072b..f1c5c917 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -260,6 +260,7 @@ type WorkflowTaskArgs struct { VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` ReqID string `bson:"req_id" json:"req_id"` RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` WorklowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache diff --git a/pkg/microservice/reaper/core/service/client/client.go b/pkg/microservice/reaper/core/service/client/client.go new file mode 100644 index 00000000..bd90314d --- /dev/null +++ b/pkg/microservice/reaper/core/service/client/client.go @@ -0,0 +1,25 @@ +package client + +import ( + "net/http" + "net/http/cookiejar" +) + +type Client struct { + APIBase string + Token string + Conn *http.Client +} + +// NewAslanClient is to get aslan client func +func NewAslanClient(host, token string) *Client { + jar, _ := cookiejar.New(nil) + + c := &Client{ + Token: token, + APIBase: host, + Conn: &http.Client{Transport: http.DefaultTransport, Jar: jar}, + } + + return c +} diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go new file mode 100644 index 00000000..211f5edb --- /dev/null +++ b/pkg/microservice/reaper/core/service/client/dockerfile.go @@ -0,0 +1,44 @@ +package client + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" + + "github.com/koderover/zadig/pkg/tool/log" +) + +type Dockerfile struct { + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` +} + +func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { + resp := new(Dockerfile) + var err error + + url := fmt.Sprintf("%s/api/template/dockerfile/%s", c.APIBase, id) + request, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Errorf("new http request error: %v", err) + return nil, err + } + // No auth required + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + var body []byte + body, err = ioutil.ReadAll(ret.Body) + if err == nil { + if err = json.Unmarshal(body, &resp); err == nil { + return resp, nil + } + } + } + + return resp, errors.WithMessage(err, "failed to get dockerfile template") +} diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 7dfd4337..1720cc78 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -24,10 +24,13 @@ import ( "strings" "github.com/koderover/zadig/pkg/microservice/reaper/config" + "github.com/koderover/zadig/pkg/setting" ) // Context ... type Context struct { + // API token 服务访问使用的api token + APIToken string `yaml:"api_token"` // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` @@ -106,11 +109,19 @@ type Context struct { // IgnoreCache ignore docker build cache [runtime] IgnoreCache bool `yaml:"ignore_cache"` - StorageEndpoint string `yaml:"storage_endpoint"` - StorageAK string `yaml:"storage_ak"` - StorageSK string `yaml:"storage_sk"` - StorageBucket string `yaml:"storage_bucket"` - StorageProvider int `yaml:"storage_provider"` + StorageEndpoint string `yaml:"storage_endpoint"` + StorageAK string `yaml:"storage_ak"` + StorageSK string `yaml:"storage_sk"` + StorageBucket string `yaml:"storage_bucket"` + StorageProvider int `yaml:"storage_provider"` + ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` +} + +type ArtifactInfo struct { + URL string `yaml:"url"` + WorkflowName string `yaml:"workflow_name"` + TaskID int64 `yaml:"task_id"` + FileName string `yaml:"file_name"` } // DockerBuildCtx ... @@ -119,14 +130,20 @@ type Context struct { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/spock-release-candidates/image:tag type DockerBuildCtx struct { - WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` + Source string `yaml:"source" bson:"source" json:"source"` + TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` - ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` - BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` + ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` + BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` } func (c *DockerBuildCtx) GetDockerFile() string { + // if the source of the dockerfile is from template, we write our own dockerfile + if c.Source == setting.DockerfileSourceTemplate { + return fmt.Sprintf("/%s", setting.ZadigDockerfilePath) + } if c.DockerFile == "" { return "Dockerfile" } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index e720fb2c..a7a14136 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -19,6 +19,7 @@ package reaper import ( "bytes" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -29,11 +30,14 @@ import ( "gopkg.in/yaml.v3" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/reaper/config" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/archive" + "github.com/koderover/zadig/pkg/microservice/reaper/core/service/client" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/meta" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util/fs" ) const ( @@ -255,21 +259,23 @@ func (r *Reaper) BeforeExec() error { } func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bool) *exec.Cmd { - args := []string{"build", "--rm=true"} + args := []string{"-c"} + dockerCommand := "docker build --rm=true" if ignoreCache { - args = append(args, "--no-cache") + dockerCommand += " --no-cache" } if buildArgs != "" { for _, val := range strings.Fields(buildArgs) { if val != "" { - args = append(args, val) + dockerCommand = dockerCommand + " " + val } } } - args = append(args, []string{"-t", fullImage, "-f", dockerfile, ctx}...) - return exec.Command(dockerExe, args...) + dockerCommand = dockerCommand + " -t " + fullImage + " -f " + dockerfile + " " + ctx + args = append(args, dockerCommand) + return exec.Command("sh", args...) } func (r *Reaper) setProxy(ctx *meta.DockerBuildCtx, cfg *meta.Proxy) { @@ -301,6 +307,10 @@ func (r *Reaper) dockerCommands() []*exec.Cmd { func (r *Reaper) runDockerBuild() error { if r.Ctx.DockerBuildCtx != nil { + err := r.prepareDockerfile() + if err != nil { + return err + } if r.Ctx.Proxy != nil { r.setProxy(r.Ctx.DockerBuildCtx, r.Ctx.Proxy) } @@ -320,6 +330,24 @@ func (r *Reaper) runDockerBuild() error { return nil } +func (r *Reaper) prepareDockerfile() error { + if r.Ctx.DockerBuildCtx.Source == setting.DockerfileSourceTemplate { + aslanClient := client.NewAslanClient(configbase.AslanServiceAddress(), r.Ctx.APIToken) + dockerfile, err := aslanClient.GetDockerfile(r.Ctx.DockerBuildCtx.TemplateID) + if err != nil { + return err + } + reader := strings.NewReader(dockerfile.Content) + readcloser := io.NopCloser(reader) + path := fmt.Sprintf("/%s", setting.ZadigDockerfilePath) + err = fs.SaveFile(readcloser, path) + if err != nil { + return err + } + } + return nil +} + // Exec ... func (r *Reaper) Exec() error { @@ -390,12 +418,12 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } // 将上面生成的统计结果文件上传到S3 if err = r.archiveTestFiles(); err != nil { - log.Errorf("archiveFiles err %v", err) + log.Errorf("archiveTestFiles err %v", err) return err } // 将HTML测试报告上传到S3 if err = r.archiveHTMLTestReportFile(); err != nil { - log.Errorf("archiveFiles err %v", err) + log.Errorf("archiveHTMLTestReportFile err %v", err) return err } @@ -403,14 +431,21 @@ func (r *Reaper) AfterExec(upStreamErr error) error { // should archive file first, since compress cache will clean the workspace if upStreamErr == nil { - if err = r.archiveS3Files(); err != nil { - log.Errorf("archiveFiles err %v", err) - return err - } - // 运行构建后置脚本 - if err = r.RunPostScripts(); err != nil { - log.Errorf("RunPostScripts err %v", err) - return err + if r.Ctx.ArtifactInfo == nil { + if err = r.archiveS3Files(); err != nil { + log.Errorf("archiveFiles err %v", err) + return err + } + // 运行构建后置脚本 + if err = r.RunPostScripts(); err != nil { + log.Errorf("RunPostScripts err %v", err) + return err + } + } else { + if err = r.downloadArtifactFile(); err != nil { + log.Errorf("download archiveFiles err %v", err) + return err + } } // 运行物理机部署脚本 diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index bbe55ba3..9a175f43 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -373,3 +373,40 @@ func (r *Reaper) RunPMDeployScripts() error { return cmd.Run() } + +func (r *Reaper) downloadArtifactFile() error { + var err error + var store *s3.S3 + if store, err = s3.NewS3StorageFromEncryptedURI(r.Ctx.ArtifactInfo.URL); err != nil { + log.Errorf("Archive failed to create s3 storage %s", r.Ctx.ArtifactInfo.URL) + return err + } + if store.Subfolder != "" { + store.Subfolder = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, r.Ctx.ArtifactInfo.WorkflowName, r.Ctx.ArtifactInfo.TaskID, "file") + } else { + store.Subfolder = fmt.Sprintf("%s/%d/%s", r.Ctx.ArtifactInfo.WorkflowName, r.Ctx.ArtifactInfo.TaskID, "file") + } + + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + log.Errorf("s3 create client err:%s", err) + return err + } + files, err := s3client.ListFiles(store.Bucket, store.GetObjectPath(r.Ctx.ArtifactInfo.FileName), false) + if err != nil { + log.Errorf("s3 list file err:%s", err) + return err + } + if len(files) > 0 { + objectKey := store.GetObjectPath(files[0]) + if err = s3client.Download(store.Bucket, objectKey, fmt.Sprintf("%s/%s", r.ActiveWorkspace, r.Ctx.ArtifactInfo.FileName)); err != nil { + log.Errorf("s3 download file err:%s", err) + return err + } + } + return nil +} diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 15af0387..76ca7a0c 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -54,9 +54,13 @@ func Execute() error { if err = r.BeforeExec(); err != nil { log.Fatal(err) } - if err = r.Exec(); err != nil { - errs = multierror.Append(errs, err) + + if r.Ctx.ArtifactInfo == nil { + if err = r.Exec(); err != nil { + errs = multierror.Append(errs, err) + } } + if err = r.AfterExec(err); err != nil { errs = multierror.Append(errs, err) } diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 79acdadf..6e4fcaca 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -21,6 +21,7 @@ type TaskType string const ( TaskPipeline TaskType = "pipeline" TaskBuild TaskType = "buildv2" + TaskArtifactDeploy TaskType = "artifact_deploy" TaskJenkinsBuild TaskType = "jenkins_build" TaskArtifact TaskType = "artifact" TaskDeploy TaskType = "deploy" diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 867b82e3..b0a13595 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -540,6 +540,7 @@ func initTaskPlugins(execHandler *ExecHandler) { pluginConf := map[config.TaskType]plugins.Initiator{ config.TaskJira: plugins.InitializeJiraTaskPlugin, config.TaskBuild: plugins.InitializeBuildTaskPlugin, + config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, config.TaskDeploy: plugins.InitializeDeployTaskPlugin, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go new file mode 100644 index 00000000..c3a06aaf --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -0,0 +1,369 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +const ( + // ArtifactDeployTaskV2Timeout ... + ArtifactDeployTaskV2Timeout = 60 * 60 * 1 // 60 minutes +) + +// InitializeArtifactTaskPlugin to initialize build task plugin, and return reference +func InitializeArtifactTaskPlugin(taskType config.TaskType) TaskPlugin { + return &ArtifactDeployTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +// BuildTaskPlugin is Plugin, name should be compatible with task type +type ArtifactDeployTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.Build + Log *zap.SugaredLogger + + ack func() +} + +func (p *ArtifactDeployTaskPlugin) SetAckFunc(ack func()) { + p.ack = ack +} + +// Init ... +func (p *ArtifactDeployTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl + p.FileName = filename +} + +func (p *ArtifactDeployTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *ArtifactDeployTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *ArtifactDeployTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +// TaskTimeout ... +func (p *ArtifactDeployTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = ArtifactDeployTaskV2Timeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *ArtifactDeployTaskPlugin) SetBuildStatusCompleted(status config.Status) { + p.Task.BuildStatus.Status = status + p.Task.BuildStatus.EndTime = time.Now().Unix() +} + +func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + envName := pipelineTask.WorkflowArgs.Namespace + envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameVar) + + taskIDVar := &task.KeyVal{Key: "TASK_ID", Value: strconv.FormatInt(pipelineTask.TaskID, 10), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) + + privateKeys := sets.String{} + for _, privateKey := range pipelineTask.ConfigPayload.PrivateKeys { + privateKeys.Insert(privateKey.Name) + } + + privateKeysVar := &task.KeyVal{Key: "AGENTS", Value: strings.Join(privateKeys.List(), ","), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, privateKeysVar) + + // env host ips + for envName, HostIPs := range p.Task.EnvHostInfo { + envHostKeysVar := &task.KeyVal{Key: envName + "_HOST_IPs", Value: strings.Join(HostIPs, ","), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) + } + + // ARTIFACT + if p.Task.ArtifactInfo != nil { + var workspace = "/workspace" + if pipelineTask.ConfigPayload.ClassicBuild { + workspace = pipelineCtx.Workspace + } + pipelineTask.ArtifactInfo = p.Task.ArtifactInfo + artifactKeysVar := &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s", workspace, p.Task.ArtifactInfo.FileName), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) + } + + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + for _, repo := range p.Task.JobCtx.Builds { + repoName := strings.Replace(repo.RepoName, "-", "_", -1) + if len(repo.Branch) > 0 { + branchVar := &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, branchVar) + } + + if len(repo.Tag) > 0 { + tagVar := &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, tagVar) + } + + if repo.PR > 0 { + prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) + } + } + + jobCtx := JobCtxBuilder{ + JobName: p.JobName, + PipelineCtx: pipelineCtx, + ArchiveFile: p.Task.JobCtx.PackageFile, + JobCtx: p.Task.JobCtx, + Installs: p.Task.InstallCtx, + } + + if p.Task.BuildStatus == nil { + p.Task.BuildStatus = &task.BuildStatus{} + } + + p.Task.BuildStatus.Status = config.StatusRunning + p.Task.BuildStatus.StartTime = time.Now().Unix() + p.ack() + + jobCtxBytes, err := yaml.Marshal(jobCtx.BuildReaperContext(pipelineTask, serviceName)) + if err != nil { + msg := fmt.Sprintf("cannot reaper.Context data: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + if err := createJobConfigMap( + p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient); err != nil { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.Log.Infof("succeed to create cm for build job %s", p.JobName) + + jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) + if p.Task.ImageFrom == setting.ImageFromCustom { + jobImage = p.Task.BuildOS + } + + //Resource request default value is LOW + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + if err != nil { + msg := fmt.Sprintf("create build job context error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + job.Namespace = p.KubeNamespace + + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("delete build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + msg := fmt.Sprintf("create secret error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + if err := updater.CreateJob(job, p.kubeClient); err != nil { + msg := fmt.Sprintf("create build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.Log.Infof("succeed to create build job %s", p.JobName) +} + +// Wait ... +func (p *ArtifactDeployTaskPlugin) Wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + p.SetBuildStatusCompleted(status) + + if status == config.StatusPassed { + if p.Task.DockerBuildStatus == nil { + p.Task.DockerBuildStatus = &task.DockerBuildStatus{} + } + + p.Task.DockerBuildStatus.StartTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusRunning + p.ack() + + select { + case <-ctx.Done(): + p.Task.DockerBuildStatus.EndTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusCancelled + p.Task.TaskStatus = config.StatusCancelled + p.ack() + return + case <-time.After(time.Duration(rand.Int()%2) * time.Second): + p.Task.DockerBuildStatus.EndTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusPassed + } + } + + p.SetStatus(status) +} + +// Complete ... +func (p *ArtifactDeployTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + // 清理用户取消和超时的任务 + defer func() { + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return + } + }() + + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + if err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + return + } + + p.Task.LogFile = p.FileName +} + +// SetTask ... +func (p *ArtifactDeployTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToBuildTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *ArtifactDeployTaskPlugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *ArtifactDeployTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *ArtifactDeployTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *ArtifactDeployTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *ArtifactDeployTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *ArtifactDeployTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *ArtifactDeployTaskPlugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 630004f8..db5c7f07 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -133,6 +133,16 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) } + // ARTIFACT + if p.Task.JobCtx.FileArchiveCtx != nil { + var workspace = "/workspace" + if pipelineTask.ConfigPayload.ClassicBuild { + workspace = pipelineCtx.Workspace + } + artifactKeysVar := &task.KeyVal{Key: "ARTIFACT", Value: fmt.Sprintf("%s/%s/%s", workspace, p.Task.JobCtx.FileArchiveCtx.FileLocation, p.Task.JobCtx.FileArchiveCtx.FileName), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) + } + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace for _, repo := range p.Task.JobCtx.Builds { repoName := strings.Replace(repo.RepoName, "-", "_", -1) @@ -150,6 +160,15 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) } + + if len(repo.CommitID) > 0 { + commitVar := &task.KeyVal{ + Key: fmt.Sprintf("%s_COMMIT_ID", repoName), + Value: repo.CommitID, + IsCredential: false, + } + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, commitVar) + } } jobCtx := JobCtxBuilder{ diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index d3dce9b0..663a3e53 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -193,7 +193,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ) serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, p.Task.ProductName) if err != nil { - return + // Maybe it is a share service, the entity is not under the project + serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, "") + if err != nil { + return + } } if serviceInfo.WorkloadType == "" { selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() @@ -415,8 +419,9 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * serviceValuesYaml := renderChart.ValuesYaml // prepare image replace info - getValidMatchData := getValidMatchData(targetContainer.ImagePath) - replaceValuesMap, err = assignImageData(p.Task.Image, getValidMatchData) + validMatchData := getValidMatchData(targetContainer.ImagePath) + + replaceValuesMap, err = assignImageData(p.Task.Image, validMatchData) if err != nil { err = errors.WithMessagef( err, @@ -622,18 +627,29 @@ func getValidMatchData(spec *types.ImagePathSpec) map[string]string { return ret } +// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx +func resolveImageUrl(imageUrl string) map[string]string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + result := make(map[string]string) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + result[exNames[i]] = matchedStr + } + } + return result +} + // replace image defines in yaml by new version func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { nestedMap, err := converter.Expand(imageValuesMap) if err != nil { return "", err } - bs, err := yaml.Marshal(nestedMap) if err != nil { return "", err } - mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) if err != nil { return "", err @@ -641,7 +657,7 @@ func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (str return string(mergedBs), nil } -// assign image url data into match data +// assignImageData assign image url data into match data // matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path // return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { @@ -676,30 +692,16 @@ func assignImageData(imageUrl string, matchData map[string]string) (map[string]i break } return ret, nil - } else { - // image url assigned into repo + image(tag) - ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") - ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) - return ret, nil } + // image url assigned into repo + image(tag) + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) + return ret, nil } return nil, errors.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) } -// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx -func resolveImageUrl(imageUrl string) map[string]string { - subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) - result := make(map[string]string) - exNames := imageParseRegex.SubexpNames() - for i, matchedStr := range subMatchAll { - if i != 0 && matchedStr != "" && matchedStr != ":" { - result[exNames[i]] = matchedStr - } - } - return result -} - // Wait ... func (p *DeployTaskPlugin) Wait(ctx context.Context) { // skip waiting for reset image task diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index b963e393..cc02d9cb 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -134,6 +134,8 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi JobType: setting.BuildImageJob, //Docker build context DockerBuildCtx: &task.DockerBuildCtx{ + Source: p.Task.Source, + TemplateID: p.Task.TemplateID, WorkDir: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.WorkDir), DockerFile: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.DockerFile), ImageName: p.Task.Image, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index d0ddc2ad..1d13553e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -174,6 +174,7 @@ func replaceWrapLine(script string) string { func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName string) *types.Context { ctx := &types.Context{ + APIToken: pipelineTask.ConfigPayload.APIToken, Workspace: b.PipelineCtx.Workspace, CleanWorkspace: b.JobCtx.CleanWorkspace, IgnoreCache: pipelineTask.ConfigPayload.IgnoreCache, @@ -209,7 +210,6 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, } - for _, install := range b.Installs { inst := &types.Install{ // TODO: 之后可以适配 install.Scripts 为[]string @@ -283,6 +283,8 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.DockerBuildCtx != nil { ctx.DockerBuildCtx = &task.DockerBuildCtx{ + Source: b.JobCtx.DockerBuildCtx.Source, + TemplateID: b.JobCtx.DockerBuildCtx.TemplateID, WorkDir: b.JobCtx.DockerBuildCtx.WorkDir, DockerFile: b.JobCtx.DockerBuildCtx.DockerFile, ImageName: b.JobCtx.DockerBuildCtx.ImageName, @@ -309,6 +311,14 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ctx.StorageSK = pipelineTask.ConfigPayload.S3Storage.Sk ctx.StorageBucket = pipelineTask.ConfigPayload.S3Storage.Bucket ctx.StorageProvider = pipelineTask.ConfigPayload.S3Storage.Provider + if pipelineTask.ArtifactInfo != nil { + ctx.ArtifactInfo = &types.ArtifactInfo{ + URL: pipelineTask.ArtifactInfo.URL, + WorkflowName: pipelineTask.ArtifactInfo.WorkflowName, + TaskID: pipelineTask.ArtifactInfo.TaskID, + FileName: pipelineTask.ArtifactInfo.FileName, + } + } return ctx } diff --git a/pkg/microservice/warpdrive/core/service/types/environment.go b/pkg/microservice/warpdrive/core/service/types/environment.go index dcb576db..b8ac9e7e 100644 --- a/pkg/microservice/warpdrive/core/service/types/environment.go +++ b/pkg/microservice/warpdrive/core/service/types/environment.go @@ -70,7 +70,7 @@ type GitRepoConfig struct { type OverrideYaml struct { YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"gitRepoConfig,omitempty"` + GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 8ac5c01c..9ed4b2fd 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -27,6 +27,8 @@ import ( // Context ... type Context struct { + // API token 服务访问使用的api token + APIToken string `yaml:"api_token"` // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` @@ -105,11 +107,19 @@ type Context struct { // IgnoreCache ignore docker build cache [runtime] IgnoreCache bool `yaml:"ignore_cache"` - StorageEndpoint string `yaml:"storage_endpoint"` - StorageAK string `yaml:"storage_ak"` - StorageSK string `yaml:"storage_sk"` - StorageBucket string `yaml:"storage_bucket"` - StorageProvider int8 `yaml:"storage_provider"` + StorageEndpoint string `yaml:"storage_endpoint"` + StorageAK string `yaml:"storage_ak"` + StorageSK string `yaml:"storage_sk"` + StorageBucket string `yaml:"storage_bucket"` + StorageProvider int8 `yaml:"storage_provider"` + ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` +} + +type ArtifactInfo struct { + URL string `yaml:"url"` + WorkflowName string `yaml:"workflow_name"` + TaskID int64 `yaml:"task_id"` + FileName string `yaml:"file_name"` } // Proxy 翻墙配置信息 diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index e43541c7..4d6e55c3 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -24,9 +24,10 @@ import ( ) type Build struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ProductName string `bson:"product_name" json:"product_name"` // 新增一个service表示服务名称 Service string `bson:"service" json:"service"` // 该名称实际为服务组件名称 @@ -54,7 +55,15 @@ type Build struct { BuildStatus *BuildStatus `bson:"build_status,omitempty" json:"build_status,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` // Get the host bound to the environment of the cloud host service configuration - EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` +} + +type ArtifactInfo struct { + URL string `bson:"url" json:"url"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + FileName string `bson:"file_name" json:"file_name"` } type Item struct { @@ -195,6 +204,8 @@ type DockerBuildCtx struct { ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` + Source string `yaml:"source" bson:"source" json:"source"` + TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` } type FileArchiveCtx struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 2692dbc3..0dfdbcda 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -21,6 +21,7 @@ import ( ) type ConfigPayload struct { + APIToken string `json:"api_token"` Proxy Proxy `json:"proxy"` S3Storage S3Config `json:"s3_storage"` Github GithubConfig `json:"github"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/docker_build.go b/pkg/microservice/warpdrive/core/service/types/task/docker_build.go index 88eb7dcb..f1e97a6e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/docker_build.go @@ -23,6 +23,8 @@ import ( ) type DockerBuild struct { + Source string `bson:"source" json:"source"` // Dockerfile source + TemplateID string `bson:"template_id" json:"template_id"` TaskType config.TaskType `bson:"type" json:"type"` Enabled bool `bson:"enabled" json:"enabled"` TaskStatus config.Status `bson:"status" json:"status"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index d21c91cc..d6a0d975 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -65,23 +65,20 @@ type Task struct { // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ConfigPayload 系统配置信息 - ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - - RwLock sync.Mutex `bson:"-" json:"-"` - - ResetImage bool `json:"resetImage" bson:"resetImage"` - - TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `json:"resetImage" bson:"resetImage"` + TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info" json:"artifact_info"` } type RenderInfo struct { @@ -135,6 +132,7 @@ type WorkflowTaskArgs struct { VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` ReqID string `bson:"req_id" json:"req_id"` RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` WorklowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` // Ignore docker build cache @@ -168,10 +166,14 @@ type TestArgs struct { } type ArtifactArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` + Name string `bson:"name" json:"name"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image" json:"image"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` + TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` + FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` + URL string `bson:"url,omitempty" json:"url,omitempty"` } type VersionArgs struct { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 88efd0d6..3836cd53 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -179,6 +179,9 @@ const ( ClusterNotFound = "NotFound" ClusterDisconnected = "Disconnected" + // annotations + HelmReleaseNameAnnotation = "meta.helm.sh/release-name" + EnvCreatedBy = "createdBy" EnvCreator = "koderover" PodReady = "ready" @@ -226,12 +229,19 @@ const ( SourceFromCodeHub = "codehub" // SourceFromIlyshin 配置来源为ilyshin SourceFromIlyshin = "ilyshin" + // SourceFromChartTemplate 配置来源为helmTemplate + SourceFromChartTemplate = "chartTemplate" + // SourceFromPublicRepo 配置来源为publicRepo + SourceFromPublicRepo = "publicRepo" + // SourceFromGUI 配置来源为gui SourceFromGUI = "gui" //SourceFromHelm SourceFromHelm = "helm" //SourceFromExternal SourceFromExternal = "external" + // service from yaml template + ServiceSourceTemplate = "template" ProdENV = "prod" TestENV = "test" @@ -482,6 +492,32 @@ const ( AliyunHost = ".aliyuncs.com" ) +// Dockerfile parsing consts +const ( + DockerfileCmdArg = "ARG" +) + +// Dockerfile template constant +const ( + DockerfileSourceLocal = "local" + DockerfileSourceTemplate = "template" + + ZadigDockerfilePath = "zadig-dockerfile" +) + +// Yaml template constant +const ( + RegExpParameter = `{{.(\w)+}}` +) + +// template common constant +const ( + TemplateVariableProduct = "$T-Project$" + TemplateVariableProductDescription = "项目名称" + TemplateVariableService = "$T-Service$" + TemplateVariableServiceDescription = "服务名称" +) + const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index e235a1b6..a1a2f870 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -584,6 +584,7 @@ var ( ErrListPrivateKeys = NewHTTPError(6684, "列出私钥失败") ErrDeletePrivateKey = NewHTTPError(6685, "删除私钥失败") ErrDeleteUsedPrivateKey = NewHTTPError(6686, "删除私钥失败,此私钥已经被引用,请确认") + ErrBulkCreatePrivateKey = NewHTTPError(6687, "批量创建私钥失败") //----------------------------------------------------------------------------------------------- // signature APIs Range: 6690 - 6699 diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index de4076b6..cdabe6c6 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -19,7 +19,6 @@ package s3 import ( "fmt" "os" - "path" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -211,8 +210,7 @@ func (c *Client) ListFiles(bucketName, prefix string, recursive bool) ([]string, for _, item := range output.Contents { itemKey := *item.Key - _, fileName := path.Split(itemKey) - ret = append(ret, fileName) + ret = append(ret, itemKey) } return ret, nil -- Gitee From 3b1dec7882928532e9b64ee41a430230f75713f1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 14:28:40 +0800 Subject: [PATCH 0387/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c14e39ba..1fc38076 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -444,8 +444,7 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, - ExcludeEnvName: envName, + ProductName: productName, }) if err != nil { log.Errorf("failed to list external service, error:%s", err) @@ -457,6 +456,7 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) for _, v := range diff { + log.Infof("v.Operation:%s", v.Operation) switch v.Operation { // 删除workload的引用 case "delete": -- Gitee From f3ab1fbb6cf5252d415984cfca865ca58bc3e74c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 14:38:48 +0800 Subject: [PATCH 0388/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/service/service/service.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 1fc38076..06c40fce 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -403,6 +403,15 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN originSet.Insert(v.Name) } } + + externalEnvServices, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + for _, externalEnvService := range externalEnvServices { + originSet.Insert(externalEnvService.ServiceName) + } + for _, v := range args.WorkLoads { uploadSet.Insert(v.Name) } @@ -443,15 +452,16 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } - externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, + otherExternalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + ExcludeEnvName: envName, }) if err != nil { log.Errorf("failed to list external service, error:%s", err) } externalEnvServiceM := make(map[string]bool) - for _, externalEnvService := range externalEnvServices { + for _, externalEnvService := range otherExternalEnvServices { externalEnvServiceM[externalEnvService.ServiceName] = true } log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) -- Gitee From a06220af05e7d1b8fad219c10f91506be907bff7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 14:52:09 +0800 Subject: [PATCH 0389/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/service/service/service.go | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 06c40fce..7631f094 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -395,6 +395,19 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN log.Errorf("[%s][%s]NewWorkLoadsStatColl().Find %s", args.ClusterID, args.Namespace, err) return err } + externalEnvServices, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + + for _, externalEnvService := range externalEnvServices { + workloadStat.Workloads = append(workloadStat.Workloads, commonmodels.Workload{ + ProductName: externalEnvService.ProductName, + EnvName: externalEnvService.EnvName, + Name: externalEnvService.ServiceName, + }) + } + diff := map[string]*ServiceWorkloadsUpdateAction{} originSet := sets.NewString() uploadSet := sets.NewString() @@ -404,14 +417,6 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } - externalEnvServices, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, - EnvName: envName, - }) - for _, externalEnvService := range externalEnvServices { - originSet.Insert(externalEnvService.ServiceName) - } - for _, v := range args.WorkLoads { uploadSet.Insert(v.Name) } -- Gitee From 2fcae4f73944405814be0235d24caa667609fd3f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 15:07:16 +0800 Subject: [PATCH 0390/2647] remove debug code Signed-off-by: liu deyi --- .../aslan/core/service/service/service.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 7631f094..6357d481 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -471,24 +471,21 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) for _, v := range diff { - log.Infof("v.Operation:%s", v.Operation) switch v.Operation { // 删除workload的引用 case "delete": - log.Infof("delete ServiceName:%s", v.Name) if _, isExist := externalEnvServiceM[v.Name]; !isExist { err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) if err != nil { log.Errorf("UpdateStatus external services error:%s", err) } - } else { - if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, - EnvName: envName, - ServiceName: v.Name, - }); err != nil { - log.Errorf("delete services in external env error:%s", err) - } + } + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + ServiceName: v.Name, + }); err != nil { + log.Errorf("delete services in external env error:%s", err) } // 添加workload的引用 case "add": -- Gitee From bd89301d99f1aa27455ff40e786352b34ac9bcc7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 15:26:23 +0800 Subject: [PATCH 0391/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 6357d481..be0eb0ae 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -469,7 +469,6 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN for _, externalEnvService := range otherExternalEnvServices { externalEnvServiceM[externalEnvService.ServiceName] = true } - log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) for _, v := range diff { switch v.Operation { // 删除workload的引用 -- Gitee From 7c8df4d32c06b6d0fec7300910aa09bf627e5aec Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 15:45:52 +0800 Subject: [PATCH 0392/2647] add debug log Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index be0eb0ae..22bd8a20 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -469,7 +469,11 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN for _, externalEnvService := range otherExternalEnvServices { externalEnvServiceM[externalEnvService.ServiceName] = true } + log.Infof("deleteString:%s", deleteString.List()) + log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) for _, v := range diff { + log.Infof("v.Operation:%s", v.Operation) + log.Infof("serviceName:%s", v.Name) switch v.Operation { // 删除workload的引用 case "delete": -- Gitee From 30bc066ad5c8f619b673feb2a5e81a8146e970c8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 15:56:55 +0800 Subject: [PATCH 0393/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 22bd8a20..be0eb0ae 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -469,11 +469,7 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN for _, externalEnvService := range otherExternalEnvServices { externalEnvServiceM[externalEnvService.ServiceName] = true } - log.Infof("deleteString:%s", deleteString.List()) - log.Infof("externalEnvServiceM:%+v", externalEnvServiceM) for _, v := range diff { - log.Infof("v.Operation:%s", v.Operation) - log.Infof("serviceName:%s", v.Name) switch v.Operation { // 删除workload的引用 case "delete": -- Gitee From 8a7a5a93a5c9f2edfb00104e91f5a8fbf9079026 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 16:16:47 +0800 Subject: [PATCH 0394/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/handler/user/user.go | 60 ++--------------- .../user/core/service/login/local.go | 2 +- .../user/core/service/user/user.go | 64 +++++++++++++++++-- .../client/systemconfig/systemconfig.go | 8 +++ 4 files changed, 73 insertions(+), 61 deletions(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 45268f6f..d1f109dc 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -1,15 +1,8 @@ package user import ( - "fmt" - - "github.com/dexidp/dex/connector/ldap" "github.com/gin-gonic/gin" - "go.uber.org/zap" - - ldapv3 "github.com/go-ldap/ldap/v3" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" "github.com/koderover/zadig/pkg/shared/client/systemconfig" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -17,55 +10,9 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func searchAndSyncUser(si *service.Connector, logger *zap.SugaredLogger) error { - if si == nil || si.Config == nil { - logger.Error("can't find connector") - return fmt.Errorf("can't find connector") - } - config, _ := si.Config.(*ldap.Config) - l, err := ldapv3.Dial("tcp", config.Host) - if err != nil { - logger.Error(err) - return err - } - defer l.Close() - - err = l.Bind(config.BindDN, config.BindPW) - if err != nil { - logger.Error(err) - return err - } - - searchRequest := ldapv3.NewSearchRequest( - config.GroupSearch.BaseDN, - ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, - config.GroupSearch.Filter, // The filter to apply - []string{config.UserSearch.NameAttr, config.UserSearch.EmailAttr}, // A list attributes to retrieve - nil, - ) - - sr, err := l.Search(searchRequest) - if err != nil { - logger.Error(err) - return err - } - - for _, entry := range sr.Entries { - _, err := user.SyncUser(&user.SyncUserInfo{ - Account: entry.GetAttributeValue(config.UserSearch.NameAttr), - IdentityType: si.ID, - }, logger) - if err != nil { - return err - } - } - return nil -} - func SyncLdapUser(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Logger.Info("SyncLdapUser start") ldapId := c.Param("ldapId") systemConfigClient := systemconfig.New() si, err := systemConfigClient.GetConnector(ldapId) @@ -78,7 +25,12 @@ func SyncLdapUser(c *gin.Context) { ctx.Err = err return } - ctx.Err = searchAndSyncUser(si, ctx.Logger) + ctx.Err = user.SearchAndSyncUser(&systemconfig.Connector{ + ConnectorBase: si.ConnectorBase, + ID: si.ID, + Name: si.Name, + Config: si.Config, + }, ctx.Logger) } func GetUser(c *gin.Context) { diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index e0c300cf..ccb54d62 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -38,7 +38,7 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { if user == nil { return nil, fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("LocalLogin get user:%s user login not exist, error msg:%s", args.Account, err.Error()) return nil, err diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index ec6c52df..6e5d1b3c 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + "github.com/dexidp/dex/connector/ldap" + ldapv3 "github.com/go-ldap/ldap/v3" "github.com/google/uuid" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" @@ -12,6 +14,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) type User struct { @@ -56,13 +59,62 @@ type SyncUserInfo struct { Name string `json:"name"` } +func SearchAndSyncUser(si *systemconfig.Connector, logger *zap.SugaredLogger) error { + if si == nil || si.Config == nil { + logger.Error("can't find connector") + return fmt.Errorf("can't find connector") + } + config, ok := si.Config.(*ldap.Config) + if !ok { + return fmt.Errorf("connector config error") + } + l, err := ldapv3.Dial("tcp", config.Host) + if err != nil { + logger.Errorf("ldap dial host:%s error, error msg:%s", config.Host, err) + return err + } + defer l.Close() + + err = l.Bind(config.BindDN, config.BindPW) + if err != nil { + logger.Errorf("ldap bind host:%s error, error msg:%s", config.Host, err) + return err + } + + searchRequest := ldapv3.NewSearchRequest( + config.GroupSearch.BaseDN, + ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, + config.GroupSearch.Filter, // The filter to apply + []string{config.UserSearch.NameAttr}, // A list attributes to retrieve + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + logger.Errorf("ldap search host:%s error, error msg:%s", config.Host, err) + return err + } + + for _, entry := range sr.Entries { + _, err := SyncUser(&SyncUserInfo{ + Account: entry.GetAttributeValue(config.UserSearch.NameAttr), + IdentityType: si.ID, + }, logger) + if err != nil { + logger.Errorf("ldap host:%s sync user error, error msg:%s", config.Host, err) + return err + } + } + return nil +} + func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { user, err := orm.GetUserByUid(uid, core.DB) if err != nil { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err } - userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("GetUser GetUserLogin:%s error, error msg:%s", uid, err.Error()) return nil, err @@ -148,7 +200,7 @@ func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*UsersResp, er func getLoginId(user *models.User, loginType config.LoginType) string { switch loginType { - case config.AccountLocinType: + case config.AccountLoginType: return user.Account default: return user.Account @@ -204,7 +256,7 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { if user == nil { return fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("UpdatePassword GetUserLogin:%s error, error msg:%s", args.Uid, err.Error()) return err @@ -263,7 +315,7 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us return nil, err } } - userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, tx) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, tx) if err != nil { tx.Rollback() logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) @@ -281,8 +333,8 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us err = orm.CreateUserLogin(&models.UserLogin{ UID: user.UID, LastLoginTime: time.Now().Unix(), - LoginId: getLoginId(user, config.AccountLocinType), - LoginType: int(config.AccountLocinType), + LoginId: getLoginId(user, config.AccountLoginType), + LoginType: int(config.AccountLoginType), }, tx) if err != nil { tx.Rollback() diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go index f75eb830..acc8f4ce 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -5,6 +5,14 @@ import ( "github.com/koderover/zadig/pkg/tool/httpclient" ) +type Connector struct { + service.ConnectorBase + + ID string `json:"id"` + Name string `json:"name"` + Config interface{} `json:"config"` +} + func (c *Client) GetConnector(id string) (*service.Connector, error) { url := "/connectors/" + id -- Gitee From 5b83ce1666d0497661ed96b5949e22586b222694 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 17:26:24 +0800 Subject: [PATCH 0395/2647] only the currently existing services are returned in the project list Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/service/product.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 0045b5f9..833c541d 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -70,6 +70,16 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te return nil, fmt.Errorf("FillProductTemplateVars err : %v", err) } + if resp.ProductFeature != nil && resp.ProductFeature.CreateEnvType == setting.SourceFromExternal { + services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(resp.ProductName, "") + serviceNamesSet := sets.NewString() + for _, service := range services { + serviceNamesSet.Insert(service.ServiceName) + } + resp.Services = make([][]string, 0) + resp.Services[0] = serviceNamesSet.List() + } + if resp.Services == nil { resp.Services = make([][]string, 0) } -- Gitee From fb5c4add4ac096f6b6599d4044eead8a565a0262 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 17:40:15 +0800 Subject: [PATCH 0396/2647] only the currently existing services are returned in the project list Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/service/product.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 833c541d..5d538d9f 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -76,7 +76,6 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te for _, service := range services { serviceNamesSet.Insert(service.ServiceName) } - resp.Services = make([][]string, 0) resp.Services[0] = serviceNamesSet.List() } -- Gitee From d29ac450af62ef1cc3d669e83ccb6756fcb366bd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 17:48:18 +0800 Subject: [PATCH 0397/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/project/service/product.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 5d538d9f..7da80b99 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -70,18 +70,21 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te return nil, fmt.Errorf("FillProductTemplateVars err : %v", err) } + if resp.Services == nil { + resp.Services = make([][]string, 0) + } + + log.Infof("resp1:%+v", resp) if resp.ProductFeature != nil && resp.ProductFeature.CreateEnvType == setting.SourceFromExternal { services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(resp.ProductName, "") serviceNamesSet := sets.NewString() for _, service := range services { serviceNamesSet.Insert(service.ServiceName) } + log.Infof("serviceNamesSet:%+v", serviceNamesSet.List()) resp.Services[0] = serviceNamesSet.List() } - - if resp.Services == nil { - resp.Services = make([][]string, 0) - } + log.Infof("resp2:%+v", resp) return resp, nil } -- Gitee From 283898895333d82656a7821c7776f0c21254c1e4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 17:58:27 +0800 Subject: [PATCH 0398/2647] only the currently existing services are returned in the project list Signed-off-by: liu deyi --- .../aslan/core/common/service/template_product.go | 6 ++++++ .../aslan/core/project/service/product.go | 11 ----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 94dd0dc5..219a7068 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -22,6 +22,7 @@ import ( "sync" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -84,6 +85,11 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P if err != nil { return resp, fmt.Errorf("ListExternalWorkloadsBy err : %s", err) } + serviceNamesSet := sets.NewString() + for _, service := range totalServices { + serviceNamesSet.Insert(service.ServiceName) + } + resp.Services[0] = serviceNamesSet.List() } else { totalServices, err = commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) if err != nil { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 7da80b99..17d1ba7d 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -74,17 +74,6 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te resp.Services = make([][]string, 0) } - log.Infof("resp1:%+v", resp) - if resp.ProductFeature != nil && resp.ProductFeature.CreateEnvType == setting.SourceFromExternal { - services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(resp.ProductName, "") - serviceNamesSet := sets.NewString() - for _, service := range services { - serviceNamesSet.Insert(service.ServiceName) - } - log.Infof("serviceNamesSet:%+v", serviceNamesSet.List()) - resp.Services[0] = serviceNamesSet.List() - } - log.Infof("resp2:%+v", resp) return resp, nil } -- Gitee From a4714096782eeceed162774892ee3a724db45dac Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 1 Nov 2021 18:32:38 +0800 Subject: [PATCH 0399/2647] fix bind query error Signed-off-by: allenshen --- .../aslan/core/environment/handler/renderset.go | 3 +++ .../aslan/core/environment/service/renderset.go | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index d98f2671..a2d6b542 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -19,6 +19,8 @@ package handler import ( "strings" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -69,6 +71,7 @@ func GetYamlContent(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } + log.Info("######### the arg info is %+v", *arg) if arg.CodehostID == 0 && len(arg.RepoLink) == 0 { ctx.Err = e.ErrInvalidParam.AddDesc("neither codehost nor repo link is specified") diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 442affa4..a9907ef7 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -38,12 +38,12 @@ type DefaultValuesResp struct { } type YamlContentRequestArg struct { - CodehostID int `json:"codehostID"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Branch string `json:"branch"` - RepoLink string `json:"repoLink"` - ValuesPaths string `json:"valuesPaths"` + CodehostID int `json:"codehostID" form:"codehostID"` + Owner string `json:"owner" form:"owner"` + Repo string `json:"repo" form:"repo"` + Branch string `json:"branch" form:"branch"` + RepoLink string `json:"repoLink" form:"repoLink"` + ValuesPaths string `json:"valuesPaths" form:"valuesPaths"` } func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*commonservice.RenderChartArg, error) { -- Gitee From f126301a57d9585614f0f45f52295f738017810b Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 18:54:06 +0800 Subject: [PATCH 0400/2647] update list user Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/handler/user/user.go | 2 + .../user/core/service/login/local.go | 2 +- .../user/core/service/user/user.go | 40 ++++++++++++++----- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 530b434d..d78edcb0 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -37,6 +37,8 @@ func ListUsers(c *gin.Context) { if len(args.UIDs) > 0 { ctx.Resp, ctx.Err = user.SearchUsersByUIDs(args.UIDs, ctx.Logger) return + } else if len(args.Account) > 0 { + ctx.Resp, ctx.Err = user.SearchUserByAccount(args, ctx.Logger) } else { ctx.Resp, ctx.Err = user.SearchUsers(args, ctx.Logger) return diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index e0c300cf..ccb54d62 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -38,7 +38,7 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { if user == nil { return nil, fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("LocalLogin get user:%s user login not exist, error msg:%s", args.Account, err.Error()) return nil, err diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 016357fa..5933f5a8 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -23,10 +23,12 @@ type User struct { } type QueryArgs struct { - Name string `json:"name,omitempty"` - UIDs []string `json:"uids,omitempty"` - PerPage int `json:"per_page,omitempty"` - Page int `json:"page,omitempty"` + Name string `json:"name,omitempty"` + Account string `json:"account,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + UIDs []string `json:"uids,omitempty"` + PerPage int `json:"per_page,omitempty"` + Page int `json:"page,omitempty"` } type UserInfo struct { @@ -62,7 +64,7 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err } - userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("GetUser GetUserLogin:%s error, error msg:%s", uid, err.Error()) return nil, err @@ -71,6 +73,24 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { return &userInfo[0], nil } +func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { + user, err := orm.GetUser(args.Account, args.IdentityType, core.DB) + if err != nil { + logger.Errorf("SearchUserByAccount GetUser By account:%s error, error msg:%s", args.Account, err.Error()) + return nil, err + } + userLogins, err := orm.ListUserLogins([]string{user.UID}, core.DB) + if err != nil { + logger.Errorf("SearchUserByAccount ListUserLogins By uid:%s error, error msg:%s", user.UID, err.Error()) + return nil, err + } + usersInfo := mergeUserLogin([]models.User{*user}, *userLogins, logger) + return &UsersResp{ + Users: usersInfo, + TotalCount: int64(len(usersInfo)), + }, nil +} + func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { count, err := orm.GetUsersCount(args.Name) if err != nil { @@ -148,7 +168,7 @@ func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*UsersResp, er func getLoginId(user *models.User, loginType config.LoginType) string { switch loginType { - case config.AccountLocinType: + case config.AccountLoginType: return user.Account default: return user.Account @@ -204,7 +224,7 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { if user == nil { return fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("UpdatePassword GetUserLogin:%s error, error msg:%s", args.Uid, err.Error()) return err @@ -263,7 +283,7 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us return nil, err } } - userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, tx) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, tx) if err != nil { tx.Rollback() logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) @@ -281,8 +301,8 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us err = orm.CreateUserLogin(&models.UserLogin{ UID: user.UID, LastLoginTime: time.Now().Unix(), - LoginId: getLoginId(user, config.AccountLocinType), - LoginType: int(config.AccountLocinType), + LoginId: getLoginId(user, config.AccountLoginType), + LoginType: int(config.AccountLoginType), }, tx) if err != nil { tx.Rollback() -- Gitee From dcc609b627eb3b10d9fe2ed16e2af43810f2b98a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 18:58:55 +0800 Subject: [PATCH 0401/2647] remove blank Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/service/product.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 17d1ba7d..0045b5f9 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -73,7 +73,6 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te if resp.Services == nil { resp.Services = make([][]string, 0) } - return resp, nil } -- Gitee From 0217767b6e890bb3912592e730143328d7725679 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 19:03:45 +0800 Subject: [PATCH 0402/2647] Modify the primary key of the database Signed-off-by: liu deyi --- .../core/common/repository/mongodb/services_in_external_env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go index 66865af8..56631326 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -39,7 +39,7 @@ func (c *ServicesInExternalEnvColl) EnsureIndex(ctx context.Context) error { bson.E{Key: "env_name", Value: 1}, bson.E{Key: "service_name", Value: 1}, }, - Options: options.Index().SetUnique(false), + Options: options.Index().SetUnique(true), }, { Keys: bson.D{ -- Gitee From 4066e26f4c8d0c2485dcde4726a88a87ba3dc4ec Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 19:16:28 +0800 Subject: [PATCH 0403/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/handler/user/user.go | 22 +++---------------- .../user/core/service/user/user.go | 8 ++++++- .../client/systemconfig/systemconfig.go | 8 +++---- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index d1f109dc..03c26748 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -4,7 +4,6 @@ import ( "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -13,24 +12,9 @@ import ( func SyncLdapUser(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ldapId := c.Param("ldapId") - systemConfigClient := systemconfig.New() - si, err := systemConfigClient.GetConnector(ldapId) - if err != nil { - ctx.Logger.Error(err) - ctx.Err = err - return - } - if err != nil { - ctx.Err = err - return - } - ctx.Err = user.SearchAndSyncUser(&systemconfig.Connector{ - ConnectorBase: si.ConnectorBase, - ID: si.ID, - Name: si.Name, - Config: si.Config, - }, ctx.Logger) + ldapID := c.Param("ldapId") + + ctx.Err = user.SearchAndSyncUser(ldapID, ctx.Logger) } func GetUser(c *gin.Context) { diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 6e5d1b3c..ab03313d 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -59,7 +59,13 @@ type SyncUserInfo struct { Name string `json:"name"` } -func SearchAndSyncUser(si *systemconfig.Connector, logger *zap.SugaredLogger) error { +func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { + systemConfigClient := systemconfig.New() + si, err := systemConfigClient.GetConnector(ldapId) + if err != nil { + logger.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) + return fmt.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) + } if si == nil || si.Config == nil { logger.Error("can't find connector") return fmt.Errorf("can't find connector") diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go index acc8f4ce..6d98e516 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -1,22 +1,20 @@ package systemconfig import ( - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" "github.com/koderover/zadig/pkg/tool/httpclient" ) type Connector struct { - service.ConnectorBase - + Type string `json:"type"` ID string `json:"id"` Name string `json:"name"` Config interface{} `json:"config"` } -func (c *Client) GetConnector(id string) (*service.Connector, error) { +func (c *Client) GetConnector(id string) (*Connector, error) { url := "/connectors/" + id - res := &service.Connector{} + res := &Connector{} _, err := c.Get(url, httpclient.SetResult(res)) if err != nil { return nil, err -- Gitee From f4f3aa83d17b3200cdbf03ff905828586fccf84b Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 1 Nov 2021 19:20:19 +0800 Subject: [PATCH 0404/2647] fix service module bug Signed-off-by: allenshen --- .../aslan/core/common/service/utils.go | 2 +- .../core/environment/handler/renderset.go | 4 --- .../core/environment/service/environment.go | 26 +++++++---------- .../aslan/core/service/service/helm.go | 28 ++++++++----------- 4 files changed, 23 insertions(+), 37 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/utils.go b/pkg/microservice/aslan/core/common/service/utils.go index 742826e8..5270cc00 100644 --- a/pkg/microservice/aslan/core/common/service/utils.go +++ b/pkg/microservice/aslan/core/common/service/utils.go @@ -102,7 +102,7 @@ func GetGitlabAddress(URL string) (string, error) { // GetOwnerRepoBranchPath 获取gitlab路径中的owner、repo、branch和path func GetOwnerRepoBranchPath(URL string) (string, string, string, string, string, string, error) { if !strings.Contains(URL, "https") && !strings.Contains(URL, "http") { - return "", "", "", "", "", "", fmt.Errorf("url is illegal") + return "", "", "", "", "", "", fmt.Errorf("url is illegal:%s", URL) } //适配公网的gitlab if strings.Contains(URL, "-") { diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index a2d6b542..534188c5 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -19,8 +19,6 @@ package handler import ( "strings" - "github.com/koderover/zadig/pkg/tool/log" - "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -71,8 +69,6 @@ func GetYamlContent(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - log.Info("######### the arg info is %+v", *arg) - if arg.CodehostID == 0 && len(arg.RepoLink) == 0 { ctx.Err = e.ErrInvalidParam.AddDesc("neither codehost nor repo link is specified") return diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0cb9d4b9..2ddbd298 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -25,7 +25,6 @@ import ( "sort" "strings" "sync" - "sync/atomic" "time" "github.com/hashicorp/go-multierror" @@ -2748,25 +2747,20 @@ func intervalExecutor(interval time.Duration, serviceList []interface{}, handler } wg := sync.WaitGroup{} errList := new(multierror.Error) - var executeIndex int32 wg.Add(len(serviceList)) ticker := time.NewTicker(interval) defer ticker.Stop() - go func() { - for ; true; <-ticker.C { - go func() { - defer wg.Done() - err := handler(serviceList[int(atomic.LoadInt32(&executeIndex))], log) - if err != nil { - errList = multierror.Append(errList, err) - } - }() - atomic.AddInt32(&executeIndex, 1) - if int(atomic.LoadInt32(&executeIndex)) >= len(serviceList) { - break + + for _, data := range serviceList { + go func() { + defer wg.Done() + err := handler(data, log) + if err != nil { + errList = multierror.Append(errList, err) } - } - }() + }() + <-ticker.C + } wg.Wait() return errList } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 82fb5047..ed988c04 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -46,6 +46,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -57,17 +58,6 @@ type HelmService struct { FileInfos []*types.FileInfo `json:"file_infos"` } -type HelmServiceReq struct { - ProductName string `json:"product_name"` - CreateBy string `json:"create_by"` - CodehostID int `json:"codehost_id"` - RepoOwner string `json:"repo_owner"` - RepoName string `json:"repo_name"` - BranchName string `json:"branch_name"` - FilePaths []string `json:"file_paths"` - SrcPath string `json:"src_path"` -} - type HelmServiceArgs struct { ProductName string `json:"product_name"` CreateBy string `json:"create_by"` @@ -332,16 +322,16 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil } -func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, error) { +func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry.CodeHost, error) { if repoLink != "" { - return setting.SourceFromPublicRepo, nil + return setting.SourceFromPublicRepo, nil, nil } ch, err := codehost.GetCodeHostInfoByID(repoArgs.CodehostID) if err != nil { log.Errorf("Failed to get codeHost by id %d, err: %s", repoArgs.CodehostID, err.Error()) - return "", err + return "", ch, err } - return ch.Type, nil + return ch.Type, ch, nil } func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) error { @@ -361,9 +351,11 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC } repoLink = publicArgs.RepoLink + } else { + } - source, err := getCodehostType(repoArgs, repoLink) + source, codehostInfo, err := getCodehostType(repoArgs, repoLink) if err != nil { log.Errorf("Failed to get source form repo data %+v, err: %s", *repoArgs, err.Error()) return err @@ -421,6 +413,10 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } + if source != setting.SourceFromPublicRepo && codehostInfo != nil { + repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) + } + svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ -- Gitee From a2dd5905302b7e0ac1fa543ac470348649dccbb1 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 19:22:12 +0800 Subject: [PATCH 0405/2647] Ldap sync user (#508) * add user-sync Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 1 + pkg/config/config.go | 8 +- pkg/microservice/user/core/handler/router.go | 2 + .../user/core/handler/user/user.go | 8 ++ .../user/core/repository/orm/user.go | 13 ++- .../user/core/service/login/local.go | 2 +- .../user/core/service/user/user.go | 80 ++++++++++++++++--- pkg/shared/client/systemconfig/client.go | 40 ++++++++++ .../client/systemconfig/systemconfig.go | 24 ++++++ 9 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 pkg/shared/client/systemconfig/client.go create mode 100644 pkg/shared/client/systemconfig/systemconfig.go diff --git a/go.mod b/go.mod index 61ad1a85..d17cc71d 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/garyburd/redigo v1.6.2 // indirect github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 + github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible diff --git a/pkg/config/config.go b/pkg/config/config.go index 88dd3561..7dfa9703 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -150,15 +150,15 @@ func PoetryServiceInfo() *setting.ServiceInfo { return GetServiceByCode(setting.Poetry) } -func ConfigServiceInfo() *setting.ServiceInfo { - return GetServiceByCode(setting.Poetry) -} - func PoetryServiceAddress() string { s := PoetryServiceInfo() return GetServiceAddress(s.Name, s.Port) } +func ConfigServiceInfo() *setting.ServiceInfo { + return GetServiceByCode(setting.Config) +} + func ConfigServiceAddress() string { s := ConfigServiceInfo() return GetServiceAddress(s.Name, s.Port) diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index beb9e0e3..5b191c85 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -39,5 +39,7 @@ func (*Router) Inject(router *gin.RouterGroup) { users.GET("/users/:uid/personal", user.GetPersonalUser) users.POST("/users/search", user.ListUsers) + + users.POST("/users/ldap/:ldapId", user.SyncLdapUser) } } diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 530b434d..03c26748 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -9,6 +9,14 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) +func SyncLdapUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ldapID := c.Param("ldapId") + + ctx.Err = user.SearchAndSyncUser(ldapID, ctx.Logger) +} + func GetUser(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index faeda529..b70ffa9e 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -42,16 +42,13 @@ func GetUserByUid(uid string, db *gorm.DB) (*models.User, error) { } // ListUsers gets a list of users based on paging constraints -func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { +func ListUsers(page int, perPage int, account string, db *gorm.DB) ([]models.User, error) { var ( users []models.User err error ) - if page > 0 && perPage > 0 { - err = db.Where("name LIKE ?", "%"+name+"%").Find(&users).Offset((page - 1) * perPage).Limit(perPage).Error - } else { - err = db.Where("name LIKE ?", "%"+name+"%").Find(&users).Error - } + + err = db.Where("account LIKE ?", "%"+account+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err @@ -77,14 +74,14 @@ func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { } // GetUsersCount gets user count -func GetUsersCount(name string) (int64, error) { +func GetUsersCount(account string) (int64, error) { var ( users []models.User err error count int64 ) - err = core.DB.Where("name LIKE ?", "%"+name+"%").Find(&users).Count(&count).Error + err = core.DB.Where("account LIKE ?", "%"+account+"%").Find(&users).Count(&count).Error if err != nil { return 0, err diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index e0c300cf..ccb54d62 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -38,7 +38,7 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { if user == nil { return nil, fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, args.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("LocalLogin get user:%s user login not exist, error msg:%s", args.Account, err.Error()) return nil, err diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 016357fa..ab03313d 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -4,6 +4,8 @@ import ( "fmt" "time" + "github.com/dexidp/dex/connector/ldap" + ldapv3 "github.com/go-ldap/ldap/v3" "github.com/google/uuid" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" @@ -12,6 +14,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) type User struct { @@ -23,7 +26,7 @@ type User struct { } type QueryArgs struct { - Name string `json:"name,omitempty"` + Account string `json:"account,omitempty"` UIDs []string `json:"uids,omitempty"` PerPage int `json:"per_page,omitempty"` Page int `json:"page,omitempty"` @@ -56,13 +59,68 @@ type SyncUserInfo struct { Name string `json:"name"` } +func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { + systemConfigClient := systemconfig.New() + si, err := systemConfigClient.GetConnector(ldapId) + if err != nil { + logger.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) + return fmt.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) + } + if si == nil || si.Config == nil { + logger.Error("can't find connector") + return fmt.Errorf("can't find connector") + } + config, ok := si.Config.(*ldap.Config) + if !ok { + return fmt.Errorf("connector config error") + } + l, err := ldapv3.Dial("tcp", config.Host) + if err != nil { + logger.Errorf("ldap dial host:%s error, error msg:%s", config.Host, err) + return err + } + defer l.Close() + + err = l.Bind(config.BindDN, config.BindPW) + if err != nil { + logger.Errorf("ldap bind host:%s error, error msg:%s", config.Host, err) + return err + } + + searchRequest := ldapv3.NewSearchRequest( + config.GroupSearch.BaseDN, + ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, + config.GroupSearch.Filter, // The filter to apply + []string{config.UserSearch.NameAttr}, // A list attributes to retrieve + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + logger.Errorf("ldap search host:%s error, error msg:%s", config.Host, err) + return err + } + + for _, entry := range sr.Entries { + _, err := SyncUser(&SyncUserInfo{ + Account: entry.GetAttributeValue(config.UserSearch.NameAttr), + IdentityType: si.ID, + }, logger) + if err != nil { + logger.Errorf("ldap host:%s sync user error, error msg:%s", config.Host, err) + return err + } + } + return nil +} + func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { user, err := orm.GetUserByUid(uid, core.DB) if err != nil { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err } - userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("GetUser GetUserLogin:%s error, error msg:%s", uid, err.Error()) return nil, err @@ -72,9 +130,9 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { } func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { - count, err := orm.GetUsersCount(args.Name) + count, err := orm.GetUsersCount(args.Account) if err != nil { - logger.Errorf("SeachUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) + logger.Errorf("SeachUsers GetUsersCount By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err } if count == 0 { @@ -83,9 +141,9 @@ func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) }, nil } - users, err := orm.ListUsers(args.Page, args.PerPage, args.Name, core.DB) + users, err := orm.ListUsers(args.Page, args.PerPage, args.Account, core.DB) if err != nil { - logger.Errorf("SeachUsers SeachUsers By name:%s error, error msg:%s", args.Name, err.Error()) + logger.Errorf("SeachUsers SeachUsers By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err } var uids []string @@ -148,7 +206,7 @@ func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*UsersResp, er func getLoginId(user *models.User, loginType config.LoginType) string { switch loginType { - case config.AccountLocinType: + case config.AccountLoginType: return user.Account default: return user.Account @@ -204,7 +262,7 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { if user == nil { return fmt.Errorf("user not exist") } - userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, core.DB) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("UpdatePassword GetUserLogin:%s error, error msg:%s", args.Uid, err.Error()) return err @@ -263,7 +321,7 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us return nil, err } } - userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLocinType, tx) + userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, tx) if err != nil { tx.Rollback() logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) @@ -281,8 +339,8 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us err = orm.CreateUserLogin(&models.UserLogin{ UID: user.UID, LastLoginTime: time.Now().Unix(), - LoginId: getLoginId(user, config.AccountLocinType), - LoginType: int(config.AccountLocinType), + LoginId: getLoginId(user, config.AccountLoginType), + LoginType: int(config.AccountLoginType), }, tx) if err != nil { tx.Rollback() diff --git a/pkg/shared/client/systemconfig/client.go b/pkg/shared/client/systemconfig/client.go new file mode 100644 index 00000000..cc9052c5 --- /dev/null +++ b/pkg/shared/client/systemconfig/client.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package systemconfig + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.ConfigServiceAddress() + c := httpclient.New( + httpclient.SetHostURL(host + "/api/v1"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go new file mode 100644 index 00000000..6d98e516 --- /dev/null +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -0,0 +1,24 @@ +package systemconfig + +import ( + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Connector struct { + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + Config interface{} `json:"config"` +} + +func (c *Client) GetConnector(id string) (*Connector, error) { + url := "/connectors/" + id + + res := &Connector{} + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, err +} -- Gitee From f65b41ebdcca00e947e99ed97f0cc94ceea6f4d8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 19:26:45 +0800 Subject: [PATCH 0406/2647] add function annotation Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index be0eb0ae..f57c8c4a 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -290,6 +290,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product for _, workload := range workLoads { tempWorkload := workload g.Go(func() error { + // If the service is already included in the database service template, add it to the new association table if serviceString.Has(tempWorkload.Name) { return commonrepo.NewServicesInExternalEnvColl().Create(&commonmodels.ServicesInExternalEnv{ ProductName: productName, -- Gitee From 3805d30dd1899ddca24da9dc853f8c73b9f3fdb6 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 19:56:27 +0800 Subject: [PATCH 0407/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/user/user.go | 6 ++++-- pkg/microservice/user/core/service/user/user.go | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index cbc3f255..22a69727 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -44,13 +44,15 @@ func ListUsers(c *gin.Context) { } if len(args.UIDs) > 0 { ctx.Resp, ctx.Err = user.SearchUsersByUIDs(args.UIDs, ctx.Logger) - return } else if len(args.Account) > 0 { + if len(args.IdentityType) == 0 { + args.IdentityType = "system" + } ctx.Resp, ctx.Err = user.SearchUserByAccount(args, ctx.Logger) } else { ctx.Resp, ctx.Err = user.SearchUsers(args, ctx.Logger) - return } + return } func CreateUser(c *gin.Context) { diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index b15e9d1d..692284f0 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -137,6 +137,12 @@ func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp logger.Errorf("SearchUserByAccount GetUser By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err } + if user == nil { + return &UsersResp{ + Users: nil, + TotalCount: 0, + }, nil + } userLogins, err := orm.ListUserLogins([]string{user.UID}, core.DB) if err != nil { logger.Errorf("SearchUserByAccount ListUserLogins By uid:%s error, error msg:%s", user.UID, err.Error()) -- Gitee From 42f037c189f1c7c4cd342bb8b4b486bff3225033 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 1 Nov 2021 19:59:24 +0800 Subject: [PATCH 0408/2647] test workflows Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/workflow.go | 9 ++++++--- pkg/microservice/picket/core/filter/service/workflow.go | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index c7bbc880..edd7015c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -175,9 +175,12 @@ func (c *WorkflowColl) ListWithScheduleEnabled() ([]*models.Workflow, error) { func (c *WorkflowColl) ListWorkflowsByProjects(projects []string) ([]*models.Workflow, error) { resp := make([]*models.Workflow, 0) - query := bson.M{"product_tmpl_name": bson.M{ - "$in": projects, - }} + query := bson.M{} + if len(projects) != 0 { + query = bson.M{"product_tmpl_name": bson.M{ + "$in": projects, + }} + } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index c05c6260..273acab1 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -21,6 +21,9 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err } + if len(names) == 0 { + return nil, nil + } for _, name := range names { qs.Add("projects", name) -- Gitee From 0e6fdf35a7d42534c1c7988dc74996bcd634b871 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 20:12:43 +0800 Subject: [PATCH 0409/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/service.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 26c7a85f..c4d6a038 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -240,9 +240,7 @@ func ListWorkloadTemplate(productName, envName string, log *zap.SugaredLogger) ( if len(externalServiceNames) > 0 { newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", externalServiceNames.List()...) - for _, service := range newServices { - services = append(services, service) - } + services = append(services, newServices...) } for _, serviceObject := range services { -- Gitee From 1c5eb4c2674dde35b1d150a6ae6557893b23ecfe Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 1 Nov 2021 20:16:29 +0800 Subject: [PATCH 0410/2647] fix index out of range bug Signed-off-by: liu deyi --- .../aslan/core/common/service/template_product.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 219a7068..91a3f36d 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -89,7 +89,9 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P for _, service := range totalServices { serviceNamesSet.Insert(service.ServiceName) } - resp.Services[0] = serviceNamesSet.List() + if len(resp.Services) > 0 { + resp.Services[0] = serviceNamesSet.List() + } } else { totalServices, err = commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) if err != nil { -- Gitee From a893df9a8a2a700ac9a015246d724aa3bfd0b617 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 20:16:31 +0800 Subject: [PATCH 0411/2647] fix get user nil pointer Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/service/user/user.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 692284f0..35200379 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -122,6 +122,9 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err } + if user == nil { + return nil, nil + } userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("GetUser GetUserLogin:%s error, error msg:%s", uid, err.Error()) -- Gitee From af72fb3213baa0f29d9205cc34c8242649ac2a43 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Mon, 1 Nov 2021 20:34:01 +0800 Subject: [PATCH 0412/2647] token expiry check (#510) Signed-off-by: lou --- .../policy/core/service/bundle/exemption_urls.go | 2 +- .../policy/core/service/bundle/rego/authz.rego | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index eac910da..6f68587d 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -74,7 +74,7 @@ var systemAdminURLs = []*policyRule{ }, { Methods: []string{"POST"}, - Endpoints: []string{"api/picket/projects"}, + Endpoints: []string{"api/v1/picket/projects"}, }, { Methods: []string{"DELETE"}, diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index f35dd344..6cb643b9 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -2,7 +2,7 @@ package rbac import input.attributes.request.http as http_request -# Policy rule definitions in rbac style which is consumed by OPA server. +# Policy rule definitions in rbac style, which is consumed by OPA server. # you can use it to: # 1. decide if a request is allowed by querying: rbac.allow # 2. decide if a request is allowed and get the status code by querying: rbac.response @@ -40,25 +40,30 @@ allow { url_is_public } -# Allow all valid users to visit exempted urls. allow { is_authenticated + access_is_granted + +} + +# Allow all valid users to visit exempted urls. +access_is_granted { url_is_exempted } # Allow admins to do anything. -allow { +access_is_granted { user_is_admin } # Allow project admins to do anything under the given project. -allow { +access_is_granted { not url_is_privileged user_is_project_admin } # Allow the action if the user is granted permission to perform the action. -allow { +access_is_granted { not url_is_privileged some grant @@ -220,6 +225,7 @@ bearer_token := t { is_authenticated { claims claims.uid != "" + claims.exp > time.now_ns()/1000000000 } envs := env { -- Gitee From 1a698daaab41f5c05bb5e99aef2fbbda38d1a8b1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 1 Nov 2021 20:40:09 +0800 Subject: [PATCH 0413/2647] test workflows Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/workflow.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index 273acab1..d088fad7 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -24,14 +24,15 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge if len(names) == 0 { return nil, nil } + if len(names) == 1 && names[0] == "*" { + return aslan.New().ListTestWorkflows(testName, header, qs) + } for _, name := range names { qs.Add("projects", name) } - aslanClient := aslan.New() - - return aslanClient.ListTestWorkflows(testName, header, qs) + return aslan.New().ListTestWorkflows(testName, header, qs) } type rule struct { -- Gitee From 1ae855936d54169aa4a966991a40f7396abc307a Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 1 Nov 2021 20:44:49 +0800 Subject: [PATCH 0414/2647] test workflows Signed-off-by: mouuii --- .../picket/core/filter/service/workflow.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index d088fad7..d2550025 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -24,14 +24,11 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge if len(names) == 0 { return nil, nil } - if len(names) == 1 && names[0] == "*" { - return aslan.New().ListTestWorkflows(testName, header, qs) - } - - for _, name := range names { - qs.Add("projects", name) + if !(len(names) == 1 && names[0] == "*") { + for _, name := range names { + qs.Add("projects", name) + } } - return aslan.New().ListTestWorkflows(testName, header, qs) } -- Gitee From 2726eec03945409c8900dec205d2ca63a1095db4 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 1 Nov 2021 21:37:43 +0800 Subject: [PATCH 0415/2647] add sync deleted user Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/repository/orm/user.go | 26 +++++++++++++ .../user/core/repository/orm/user_login.go | 10 +++++ .../user/core/service/user/user.go | 39 ++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index b70ffa9e..a90a5041 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -73,6 +73,32 @@ func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { return users, nil } +// ListUsersByIdentityType gets a list of users based on identityType +func ListUsersByIdentityType(identityType string, db *gorm.DB) ([]models.User, error) { + var ( + users []models.User + err error + ) + + err = db.Find(&users, "identity_type = ?", identityType).Error + + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + + return users, nil +} + +// DeleteUserByUids Delete users based on uids +func DeleteUserByUids(uids []string, db *gorm.DB) error { + var user models.User + err := db.Where("uid in ?", uids).Delete(&user).Error + if err != nil { + return err + } + return nil +} + // GetUsersCount gets user count func GetUsersCount(account string) (int64, error) { var ( diff --git a/pkg/microservice/user/core/repository/orm/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go index 153a538a..a46c517d 100644 --- a/pkg/microservice/user/core/repository/orm/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -28,6 +28,16 @@ func GetUserLogin(uid string, account string, loginType config.LoginType, db *go return &userLogin, nil } +// DeleteUserLoginByUids Delete userLogin based on uids +func DeleteUserLoginByUids(uids []string, db *gorm.DB) error { + var userLogin models.UserLogin + err := db.Where("uid in ?", uids).Delete(&userLogin).Error + if err != nil { + return err + } + return nil +} + // ListUserLogins Get a userLogin based on uid list func ListUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { var userLogins []models.UserLogin diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index ab03313d..6dcd5945 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -100,16 +100,51 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { logger.Errorf("ldap search host:%s error, error msg:%s", config.Host, err) return err } - + syncUserMap := make(map[string]bool) for _, entry := range sr.Entries { + account := config.UserSearch.NameAttr _, err := SyncUser(&SyncUserInfo{ - Account: entry.GetAttributeValue(config.UserSearch.NameAttr), + Account: entry.GetAttributeValue(account), IdentityType: si.ID, }, logger) if err != nil { logger.Errorf("ldap host:%s sync user error, error msg:%s", config.Host, err) return err } + syncUserMap[account] = true + } + users, err := orm.ListUsersByIdentityType(si.ID, core.DB) + if err != nil { + logger.Errorf("ListUsers by identityType:%s error, error msg:%s", si.ID, err) + return err + } + var deletedUsers []string + for _, user := range users { + if ok := syncUserMap[user.Account]; !ok { + deletedUsers = append(deletedUsers, user.UID) + } + } + tx := core.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + err = orm.DeleteUserByUids(deletedUsers, tx) + if err != nil { + tx.Rollback() + logger.Errorf("SearchAndSyncUser delete user by uids:%s error, error msg:%s", deletedUsers, err) + return fmt.Errorf("SearchAndSyncUser delete user by uids:%s error, error msg:%s", deletedUsers, err) + } + err = orm.DeleteUserLoginByUids(deletedUsers, tx) + if err != nil { + tx.Rollback() + logger.Errorf("SearchAndSyncUser delete userlogin by uids:%s error, error msg:%s", deletedUsers, err) + return fmt.Errorf("SearchAndSyncUser delete userlogin by uids:%s error, error msg:%s", deletedUsers, err) + } + err = tx.Commit().Error + if err != nil { + return err } return nil } -- Gitee From fec5d38afedb7b240a4830460a3f9f24533e13fc Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 2 Nov 2021 10:53:35 +0800 Subject: [PATCH 0416/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/user/user.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 22a69727..a3f5d143 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -3,6 +3,7 @@ package user import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -46,7 +47,7 @@ func ListUsers(c *gin.Context) { ctx.Resp, ctx.Err = user.SearchUsersByUIDs(args.UIDs, ctx.Logger) } else if len(args.Account) > 0 { if len(args.IdentityType) == 0 { - args.IdentityType = "system" + args.IdentityType = config.SystemIdentityType } ctx.Resp, ctx.Err = user.SearchUserByAccount(args, ctx.Logger) } else { -- Gitee From 7a732f01804b6800860ef10843fd2a69930010f0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 11:06:34 +0800 Subject: [PATCH 0417/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/environment/handler/router.go | 6 +- .../core/environment/service/environment.go | 89 ++++++++++--------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 3ebe7044..770b2f4e 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -103,14 +103,14 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.PUT("/:productName/autoUpdate", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, AutoUpdateProduct) environments.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProduct) - environments.POST("/:productName/helm", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateHelmProduct) - environments.PUT("/:productName/multiHelmEnv", gin2.IsHavePermission([]string{permission.TestEnvCreateUUID, permission.ProdEnvCreateUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) + environments.POST("/:productName/helm", gin2.UpdateOperationLogStatus, CreateHelmProduct) + environments.PUT("/:productName/multiHelmEnv", gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) environments.POST("/:productName", gin2.IsHavePermission([]string{permission.TestEnvManageUUID, permission.ProdEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProduct) environments.PUT("/:productName/envRecycle", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) environments.POST("/:productName/estimated-values", EstimatedValues) - environments.PUT("/:productName/renderset", gin2.IsHavePermission([]string{permission.TestEnvManageUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) + environments.PUT("/:productName/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) environments.PUT("/:productName", gin2.IsHavePermission([]string{permission.TestEnvShareUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, EnvShare) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 2ddbd298..7fc46af5 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -74,6 +74,14 @@ const ( UpdateTypeEnv = "envVar" ) +type usageScenario string + +const ( + usageScenarioCreateEnv = "createEnv" + usageScenarioUpdateEnv = "updateEnv" + usageScenarioUpdateRenderSet = "updateRenderSet" +) + type EnvStatus struct { EnvName string `json:"env_name,omitempty"` Status string `json:"status"` @@ -1157,7 +1165,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } -func prepareEstimatedData(productName, envName, serviceName, scene, defaultValues string, log *zap.SugaredLogger) (string, string, error) { +func prepareEstimatedData(productName, envName, serviceName, usageScenario, defaultValues string, log *zap.SugaredLogger) (string, string, error) { var err error templateService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, @@ -1169,7 +1177,7 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue return "", "", fmt.Errorf("failed to query service, name %s", serviceName) } - if scene == "createEnv" { + if usageScenario == usageScenarioCreateEnv { return templateService.HelmChart.ValuesYaml, defaultValues, nil } @@ -1201,7 +1209,8 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue return "", "", fmt.Errorf("failed to find chart info, name: %s", serviceName) } - if scene == "updateEnv" { + switch usageScenario { + case usageScenarioUpdateEnv: imageRelatedKey := sets.NewString() if templateService != nil { for _, container := range templateService.Containers { @@ -1216,10 +1225,11 @@ func prepareEstimatedData(productName, envName, serviceName, scene, defaultValue return "", "", errors.Wrapf(err, "failed to override values") } return string(mergedBs), renderSet.DefaultValues, nil - } else if scene == "updateRenderSet" { + case usageScenarioUpdateRenderSet: return targetChart.ValuesYaml, renderSet.DefaultValues, nil + default: + return "", "", fmt.Errorf("unrecognized usageScenario:%s", usageScenario) } - return "", "", fmt.Errorf("unrecognized scene:%s", scene) } func GeneEstimatedValues(productName, envName, serviceName, scene, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { @@ -1234,10 +1244,6 @@ func GeneEstimatedValues(productName, envName, serviceName, scene, format string return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to merge values, err %s", err)) } - type YamlCountResp struct { - YamlContent string `json:"yamlContent"` - } - switch format { case "flatMap": mapData, err := converter.YamlToFlatMap([]byte(mergeValues)) @@ -1246,7 +1252,7 @@ func GeneEstimatedValues(productName, envName, serviceName, scene, format string } return mapData, nil default: - return &YamlCountResp{YamlContent: mergeValues}, nil + return &RawYamlResp{YamlContent: mergeValues}, nil } } @@ -2741,13 +2747,13 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutor(interval time.Duration, serviceList []interface{}, handler func(data interface{}, log *zap.SugaredLogger) error, log *zap.SugaredLogger) *multierror.Error { +func intervalExecutor(interval time.Duration, serviceList []interface{}, handler func(data interface{}, log *zap.SugaredLogger) error, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } wg := sync.WaitGroup{} - errList := new(multierror.Error) wg.Add(len(serviceList)) + errList := make([]error, 0) ticker := time.NewTicker(interval) defer ticker.Stop() @@ -2756,7 +2762,7 @@ func intervalExecutor(interval time.Duration, serviceList []interface{}, handler defer wg.Done() err := handler(data, log) if err != nil { - errList = multierror.Append(errList, err) + errList = append(errList, err) } }() <-ticker.C @@ -2827,33 +2833,11 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(data interface{}, log *zap.SugaredLogger) error { - service := data.(*commonmodels.ProductService) - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: service.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) - return errors.Wrapf(err, "failed to find template servce %s", service.ServiceName) - } - renderChart, _ := renderChartMap[service.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) - if err != nil { - return errors.Wrapf(err, "failed to install of upgrade service %s", service.ServiceName) - } - return nil - } - errList := new(multierror.Error) for groupIndex, services := range productResp.Services { - serviceList := make([]interface{}, 0) + var wg sync.WaitGroup for _, svc := range services { - _, ok := renderChartMap[svc.ServiceName] + renderChart, ok := renderChartMap[svc.ServiceName] if !ok { continue } @@ -2861,14 +2845,33 @@ func updateProductGroup(username, productName, envName, updateType string, produ if !svcNameSet.Has(svc.ServiceName) { continue } - serviceList = append(serviceList, svc) - } + wg.Add(1) + go func(service *commonmodels.ProductService) { + defer wg.Done() - serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) - if serviceGroupErr != nil { - errList = multierror.Append(errList, serviceGroupErr.Errors...) + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: service.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) + errList = multierror.Append(errList, err) + return + } + + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) + if err != nil { + errList = multierror.Append(errList, err) + return + } + }(svc) } + wg.Wait() if err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, services); err != nil { log.Errorf("Failed to update service group %d, err: %s", groupIndex, err) errList = multierror.Append(errList, err) @@ -3105,7 +3108,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel groupServiceErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) if groupServiceErr != nil { - errList = multierror.Append(errList, groupServiceErr.Errors...) + errList = multierror.Append(errList, groupServiceErr...) } } groupServices = append(groupServices, service) -- Gitee From 292055bf100dba98f44d72171532823a427c7b84 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 11:16:10 +0800 Subject: [PATCH 0418/2647] Support to modify the order of helm services Signed-off-by: liu deyi --- .../aslan/core/project/handler/router.go | 1 + .../aslan/core/service/service/helm.go | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 8a503162..82fff0a8 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -48,6 +48,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) + product.PUT("/:name/serviceOrder", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 5cf4e4dd..d24601e5 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -53,8 +53,9 @@ import ( ) type HelmService struct { - Services []*models.Service `json:"services"` - FileInfos []*types.FileInfo `json:"file_infos"` + ServiceInfos []*models.Service `json:"service_infos"` + FileInfos []*types.FileInfo `json:"file_infos"` + Services [][]string `json:"services"` } type HelmServiceReq struct { @@ -122,8 +123,9 @@ type ChartTemplateData struct { func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { helmService := &HelmService{ - Services: []*models.Service{}, - FileInfos: []*types.FileInfo{}, + ServiceInfos: []*models.Service{}, + FileInfos: []*types.FileInfo{}, + Services: [][]string{}, } opt := &commonrepo.ServiceListOption{ @@ -136,7 +138,7 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, log.Errorf("[helmService.list] err:%v", err) return nil, e.ErrListTemplate.AddErr(err) } - helmService.Services = services + helmService.ServiceInfos = services if len(services) > 0 { fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, "") @@ -146,6 +148,13 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, } helmService.FileInfos = fis } + project, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + log.Errorf("Failed to find project info, err: %s", err) + return nil, e.ErrListTemplate.AddErr(err) + } + helmService.Services = project.Services + return helmService, nil } -- Gitee From 85e2b6f7568a51779f0df9236a401bf56c7fd0b8 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 2 Nov 2021 11:29:34 +0800 Subject: [PATCH 0419/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/repository/orm/user.go | 8 ++++---- pkg/microservice/user/core/service/user/user.go | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index b70ffa9e..84e658bc 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -42,13 +42,13 @@ func GetUserByUid(uid string, db *gorm.DB) (*models.User, error) { } // ListUsers gets a list of users based on paging constraints -func ListUsers(page int, perPage int, account string, db *gorm.DB) ([]models.User, error) { +func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { var ( users []models.User err error ) - err = db.Where("account LIKE ?", "%"+account+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error + err = db.Where("name LIKE ?", "%"+name+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err @@ -74,14 +74,14 @@ func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { } // GetUsersCount gets user count -func GetUsersCount(account string) (int64, error) { +func GetUsersCount(name string) (int64, error) { var ( users []models.User err error count int64 ) - err = core.DB.Where("account LIKE ?", "%"+account+"%").Find(&users).Count(&count).Error + err = core.DB.Where("name LIKE ?", "%"+name+"%").Find(&users).Count(&count).Error if err != nil { return 0, err diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 35200379..3ab8627e 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -159,7 +159,7 @@ func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp } func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { - count, err := orm.GetUsersCount(args.Account) + count, err := orm.GetUsersCount(args.Name) if err != nil { logger.Errorf("SeachUsers GetUsersCount By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err @@ -170,9 +170,9 @@ func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) }, nil } - users, err := orm.ListUsers(args.Page, args.PerPage, args.Account, core.DB) + users, err := orm.ListUsers(args.Page, args.PerPage, args.Name, core.DB) if err != nil { - logger.Errorf("SeachUsers SeachUsers By account:%s error, error msg:%s", args.Account, err.Error()) + logger.Errorf("SeachUsers SeachUsers By name:%s error, error msg:%s", args.Name, err.Error()) return nil, err } var uids []string -- Gitee From c13640fcf9037526cd58ab1bbbf2fb70ad3fb6fb Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 11:32:37 +0800 Subject: [PATCH 0420/2647] fix spell error Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/renderset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index a9907ef7..f87764d2 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -142,7 +142,7 @@ func GetMergedYamlContent(arg *YamlContentRequestArg, paths []string) (string, e RepoLink: arg.RepoLink, }) if errDownload != nil { - err = errors.Wrapf(errDownload, fmt.Sprintf("fail to download file from git, path %s", path)) + err = errors.Wrapf(errDownload, fmt.Sprintf("failed to download file from git, path %s", path)) return } fileContentMap.Store(index, fileContent) -- Gitee From f795ec3c5454d0a5d3e5aca1f334ab995dab75be Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 2 Nov 2021 11:39:48 +0800 Subject: [PATCH 0421/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/user/user.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index a3f5d143..7694a6e9 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -53,7 +53,6 @@ func ListUsers(c *gin.Context) { } else { ctx.Resp, ctx.Err = user.SearchUsers(args, ctx.Logger) } - return } func CreateUser(c *gin.Context) { -- Gitee From 2724976e7b81b99ac8db83b77aba9f24485be2ba Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Tue, 2 Nov 2021 11:54:36 +0800 Subject: [PATCH 0422/2647] add more user info (#511) Signed-off-by: lou --- pkg/microservice/picket/core/filter/service/rolebinding.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 9cf53c12..b46719be 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -13,6 +13,8 @@ import ( type roleBinding struct { *policy.RoleBinding Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` } func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*roleBinding, error) { @@ -39,6 +41,8 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg for _, u := range users { if rb, ok := uidToRoleBinding[u.UID]; ok { rb.Username = u.Name + rb.Email = u.Email + rb.Phone = u.Phone res = append(res, rb) } } -- Gitee From c953f1870e1d674680bec5043a539065d9c38511 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 2 Nov 2021 11:54:49 +0800 Subject: [PATCH 0423/2647] List user (#512) * update list user Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> * fix get user nil pointer Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/handler/user/user.go | 8 +++- .../user/core/repository/orm/user.go | 8 ++-- .../user/core/service/user/user.go | 43 ++++++++++++++++--- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 03c26748..7694a6e9 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -3,6 +3,7 @@ package user import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -44,10 +45,13 @@ func ListUsers(c *gin.Context) { } if len(args.UIDs) > 0 { ctx.Resp, ctx.Err = user.SearchUsersByUIDs(args.UIDs, ctx.Logger) - return + } else if len(args.Account) > 0 { + if len(args.IdentityType) == 0 { + args.IdentityType = config.SystemIdentityType + } + ctx.Resp, ctx.Err = user.SearchUserByAccount(args, ctx.Logger) } else { ctx.Resp, ctx.Err = user.SearchUsers(args, ctx.Logger) - return } } diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index b70ffa9e..84e658bc 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -42,13 +42,13 @@ func GetUserByUid(uid string, db *gorm.DB) (*models.User, error) { } // ListUsers gets a list of users based on paging constraints -func ListUsers(page int, perPage int, account string, db *gorm.DB) ([]models.User, error) { +func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, error) { var ( users []models.User err error ) - err = db.Where("account LIKE ?", "%"+account+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error + err = db.Where("name LIKE ?", "%"+name+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err @@ -74,14 +74,14 @@ func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { } // GetUsersCount gets user count -func GetUsersCount(account string) (int64, error) { +func GetUsersCount(name string) (int64, error) { var ( users []models.User err error count int64 ) - err = core.DB.Where("account LIKE ?", "%"+account+"%").Find(&users).Count(&count).Error + err = core.DB.Where("name LIKE ?", "%"+name+"%").Find(&users).Count(&count).Error if err != nil { return 0, err diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index ab03313d..3ab8627e 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -26,10 +26,12 @@ type User struct { } type QueryArgs struct { - Account string `json:"account,omitempty"` - UIDs []string `json:"uids,omitempty"` - PerPage int `json:"per_page,omitempty"` - Page int `json:"page,omitempty"` + Name string `json:"name,omitempty"` + Account string `json:"account,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + UIDs []string `json:"uids,omitempty"` + PerPage int `json:"per_page,omitempty"` + Page int `json:"page,omitempty"` } type UserInfo struct { @@ -120,6 +122,9 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) return nil, err } + if user == nil { + return nil, nil + } userLogin, err := orm.GetUserLogin(uid, user.Account, config.AccountLoginType, core.DB) if err != nil { logger.Errorf("GetUser GetUserLogin:%s error, error msg:%s", uid, err.Error()) @@ -129,8 +134,32 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { return &userInfo[0], nil } +func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { + user, err := orm.GetUser(args.Account, args.IdentityType, core.DB) + if err != nil { + logger.Errorf("SearchUserByAccount GetUser By account:%s error, error msg:%s", args.Account, err.Error()) + return nil, err + } + if user == nil { + return &UsersResp{ + Users: nil, + TotalCount: 0, + }, nil + } + userLogins, err := orm.ListUserLogins([]string{user.UID}, core.DB) + if err != nil { + logger.Errorf("SearchUserByAccount ListUserLogins By uid:%s error, error msg:%s", user.UID, err.Error()) + return nil, err + } + usersInfo := mergeUserLogin([]models.User{*user}, *userLogins, logger) + return &UsersResp{ + Users: usersInfo, + TotalCount: int64(len(usersInfo)), + }, nil +} + func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { - count, err := orm.GetUsersCount(args.Account) + count, err := orm.GetUsersCount(args.Name) if err != nil { logger.Errorf("SeachUsers GetUsersCount By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err @@ -141,9 +170,9 @@ func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) }, nil } - users, err := orm.ListUsers(args.Page, args.PerPage, args.Account, core.DB) + users, err := orm.ListUsers(args.Page, args.PerPage, args.Name, core.DB) if err != nil { - logger.Errorf("SeachUsers SeachUsers By account:%s error, error msg:%s", args.Account, err.Error()) + logger.Errorf("SeachUsers SeachUsers By name:%s error, error msg:%s", args.Name, err.Error()) return nil, err } var uids []string -- Gitee From d3a50f91f9fa1c0f3468608446bb2a8380dbcb31 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 13:48:25 +0800 Subject: [PATCH 0424/2647] modify the helm service arrangement Signed-off-by: liu deyi --- .../repository/models/template/product.go | 5 +++++ .../repository/mongodb/template/product.go | 18 ++++++++++++++++++ .../aslan/core/project/handler/product.go | 17 +++++++++++++++++ .../aslan/core/project/handler/router.go | 2 +- .../aslan/core/project/service/product.go | 12 ++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 97f772e1..60451adf 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -74,6 +74,11 @@ type Product struct { CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` } +type ProductArgs struct { + ProductName string `json:"product_name"` + Services [][]string `json:"services"` + UpdateBy string `json:"update_by"` +} type ServiceInfo struct { Name string `bson:"name" json:"name"` Owner string `bson:"owner" json:"owner"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 430da125..b1d42a47 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -215,6 +215,24 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { return err } +// UpdateServiceOrder existing ProductTmpl +func (c *ProductColl) UpdateServiceOrder(args *template.ProductArgs) error { + // avoid panic issue + if args == nil { + return errors.New("nil product args") + } + + query := bson.M{"product_name": args.ProductName} + change := bson.M{"$set": bson.M{ + "services": args.Services, + "update_time": time.Now().Unix(), + "update_by": args.UpdateBy, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + // AddService adds a service to services[0] if it is not there. func (c *ProductColl) AddService(productName, serviceName string) error { diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index b119ffb7..82c17e49 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -118,6 +118,23 @@ func UpdateProductTmplStatus(c *gin.Context) { ctx.Err = projectservice.UpdateProductTmplStatus(productName, onboardingStatus, ctx.Logger) } +type updateServiceOrderArgs struct { + Services [][]string `json:"services"` +} + +func UpdateServiceOrder(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(updateServiceOrderArgs) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid updateServiceOrder args") + return + } + + ctx.Err = projectservice.UpdateServiceOrder(ctx.User.Name, c.Param("name"), args.Services, ctx.Logger) +} + func UpdateProject(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 82fff0a8..40b6b3bd 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -48,7 +48,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) - product.PUT("/:name/serviceOrder", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) + product.PUT("/:name/serviceOrder", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateServiceOrder) product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 0045b5f9..23cc9743 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -401,6 +401,18 @@ func UpdateProductTmplStatus(productName, onboardingStatus string, log *zap.Suga return nil } +func UpdateServiceOrder(username, name string, services [][]string, log *zap.SugaredLogger) error { + if err := templaterepo.NewProductColl().UpdateServiceOrder(&template.ProductArgs{ + ProductName: name, + Services: services, + UpdateBy: username, + }); err != nil { + log.Errorf("failed to update service order,err:%s", err) + return err + } + return nil +} + // UpdateProject 更新项目 func UpdateProject(name string, args *template.Product, log *zap.SugaredLogger) (err error) { err = validateRule(args.CustomImageRule, args.CustomTarRule) -- Gitee From 2c369124c8833394c969f86d9b1ccd51ecbf49c5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 14:01:25 +0800 Subject: [PATCH 0425/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 12 ++++++---- .../aslan/core/templatestore/handler/chart.go | 8 +------ .../core/templatestore/handler/router.go | 1 - .../aslan/core/templatestore/service/chart.go | 23 +++++++++++++++---- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index fd592e77..d0a3241b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -27,8 +27,6 @@ import ( "sync" "time" - //"text/template" - "github.com/27149chen/afero" "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" @@ -697,10 +695,14 @@ func renderVariablesToYaml(valuesYaml string, productName, serviceName string, v valuesMap[variable.Key] = variable.Value } - tmpl := template.Must(template.New("values").Parse(valuesYaml)) - buf := bytes.NewBufferString("") + tmpl, err := template.New("values").Parse(valuesYaml) + if err != nil { + log.Errorf("failed to parse template, err %s valuesYaml %s", err, valuesYaml) + return "", errors.Wrapf(err, "failed to parse template, err %s", err) + } - err := tmpl.Execute(buf, valuesMap) + buf := bytes.NewBufferString("") + err = tmpl.Execute(buf, valuesMap) if err != nil { log.Errorf("failed to render values content, err %s", err) return "", fmt.Errorf("failed to render variables") diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 9d4b5b3d..17cf7eda 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -18,12 +18,12 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/errors" ) type addChartArgs struct { @@ -58,12 +58,6 @@ func ListFiles(c *gin.Context) { ctx.Resp, ctx.Err = service.GetFileContentForTemplate(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) } -func GetChartTemplateReferences(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - return -} - func ListChartTemplates(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 46347227..0d3c84f3 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -37,7 +37,6 @@ func (*Router) Inject(router *gin.RouterGroup) { install.GET("/:name", GetChartTemplate) install.GET("/:name/files", ListFiles) install.GET("/:name/variables", GetTemplateVariables) - install.GET("/:name/references", GetChartTemplateReferences) install.POST("", AddChartTemplate) install.PUT("/:name", UpdateChartTemplate) install.PUT("/:name/variables", UpdateChartTemplateVariables) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 739c9fa7..5f755192 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -38,7 +38,6 @@ import ( ) var ( - //variableExtractRegexp = regexp.MustCompile("\\$(.*?)\\$") variableExtractRegexp = regexp.MustCompile("{{.(\\w*)}}") ) @@ -68,7 +67,7 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { return nil, err } - variables := make([]*Variable, 0) + variables := make([]*Variable, 0, len(chart.Variables)) for _, v := range chart.Variables { variables = append(variables, &Variable{ Key: v.Key, @@ -266,16 +265,26 @@ func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.Sug return err } - //TODO need validate keys + requestKeys := sets.NewString() variables := make([]*commonmodes.Variable, 0) for _, variable := range args { variables = append(variables, &commonmodes.Variable{ Key: variable.Key, Value: variable.Value, }) + requestKeys.Insert(variable.Key) } - return mongodb.NewChartColl().Update(&models.Chart{ + curKeys := sets.NewString() + for _, variable := range chart.Variables { + curKeys.Insert(variable.Key) + } + + if !requestKeys.Equal(curKeys) { + return fmt.Errorf("request variable keys are different from keys in file") + } + + err = mongodb.NewChartColl().Update(&models.Chart{ Name: name, Owner: chart.Owner, Repo: chart.Repo, @@ -285,6 +294,12 @@ func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.Sug Sha1: chart.Sha1, Variables: variables, }) + + if err != nil { + logger.Errorf("failed to save variables err %s", err) + return fmt.Errorf("failed to save variables") + } + return nil } func RemoveChartTemplate(name string, logger *zap.SugaredLogger) error { -- Gitee From b6687071df55cdf3fee7bb2d2a5c44bd72866826 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 14:23:29 +0800 Subject: [PATCH 0426/2647] add error msg return Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/service/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 23cc9743..1b31d94e 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -408,7 +408,7 @@ func UpdateServiceOrder(username, name string, services [][]string, log *zap.Sug UpdateBy: username, }); err != nil { log.Errorf("failed to update service order,err:%s", err) - return err + return e.ErrUpdateProduct.AddDesc("failed to update service order") } return nil } -- Gitee From 14aece1a8bc923ecf34369b8520f0fefd6d75fdf Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Tue, 2 Nov 2021 14:44:49 +0800 Subject: [PATCH 0427/2647] fix permission issue (#514) Signed-off-by: lou --- .../policy/core/service/bundle/rego/authz.rego | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 6cb643b9..e5ad3d10 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -91,8 +91,9 @@ user_is_project_admin { # public urls are visible for all users url_is_public { - data.exemptions.public[_].method == http_request.method - glob.match(trim(data.exemptions.public[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) + some i + data.exemptions.public[i].method == http_request.method + glob.match(trim(data.exemptions.public[i].endpoint, "/"), ["/"], concat("/", input.parsed_path)) } # exempted urls are visible for all authenticated users @@ -101,14 +102,16 @@ url_is_exempted { } url_is_registered { - data.exemptions.registered[_].method == http_request.method - glob.match(trim(data.exemptions.registered[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) + some i + data.exemptions.registered[i].method == http_request.method + glob.match(trim(data.exemptions.registered[i].endpoint, "/"), ["/"], concat("/", input.parsed_path)) } # privileged urls are visible for system admins only url_is_privileged { - data.exemptions.privileged[_].method == http_request.method - glob.match(trim(data.exemptions.privileged[_].endpoint, "/"), ["/"], concat("/", input.parsed_path)) + some i + data.exemptions.privileged[i].method == http_request.method + glob.match(trim(data.exemptions.privileged[i].endpoint, "/"), ["/"], concat("/", input.parsed_path)) } project_name := pn { -- Gitee From e53a4b571ed1616b8bd40b49444e513647eb08d5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 14:45:17 +0800 Subject: [PATCH 0428/2647] fix template update bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/templatestore/service/chart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 5f755192..526f10ca 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -228,7 +228,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z return nil } - variablesNames, err := parseTemplateVariables(name, chart.Path, logger) + variablesNames, err := parseTemplateVariables(name, args.Path, logger) if err != nil { return errors.Wrapf(err, "faild to prase variables") } -- Gitee From 8200faebda1101db82cdaea37aff197681f15753 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 14:52:59 +0800 Subject: [PATCH 0429/2647] remove ilyshin code Signed-off-by: liu deyi --- .../aslan/core/code/service/branch.go | 8 - .../aslan/core/code/service/merge_request.go | 9 - .../aslan/core/code/service/namespace.go | 9 - .../aslan/core/code/service/project.go | 9 - .../aslan/core/code/service/tag.go | 8 - .../aslan/core/code/service/types.go | 51 ----- pkg/tool/ilyshin/branch.go | 57 ------ pkg/tool/ilyshin/client.go | 43 ----- pkg/tool/ilyshin/commit.go | 61 ------ pkg/tool/ilyshin/event_parsing.go | 179 ------------------ pkg/tool/ilyshin/group_project.go | 45 ----- pkg/tool/ilyshin/merge_request.go | 115 ----------- pkg/tool/ilyshin/project.go | 80 -------- pkg/tool/ilyshin/repo.go | 122 ------------ pkg/tool/ilyshin/tag.go | 53 ------ pkg/tool/ilyshin/webhook.go | 100 ---------- 16 files changed, 949 deletions(-) delete mode 100644 pkg/tool/ilyshin/branch.go delete mode 100644 pkg/tool/ilyshin/client.go delete mode 100644 pkg/tool/ilyshin/commit.go delete mode 100644 pkg/tool/ilyshin/event_parsing.go delete mode 100644 pkg/tool/ilyshin/group_project.go delete mode 100644 pkg/tool/ilyshin/merge_request.go delete mode 100644 pkg/tool/ilyshin/project.go delete mode 100644 pkg/tool/ilyshin/repo.go delete mode 100644 pkg/tool/ilyshin/tag.go delete mode 100644 pkg/tool/ilyshin/webhook.go diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index b11b3344..3945aa41 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListBranches(codeHostID int, projectName, namespace string, log *zap.SugaredLogger) ([]*Branch, error) { @@ -52,13 +51,6 @@ func CodeHostListBranches(codeHostID int, projectName, namespace string, log *za return nil, err } return ToBranches(brList), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - brList, err := client.ListBranches(namespace, projectName, log) - if err != nil { - return nil, err - } - return ToBranches(brList), nil } else if ch.Type == gerrit.CodehostTypeGerrit { cli := gerrit.NewClient(ch.Address, ch.AccessToken) branches, err := cli.ListBranches(projectName) diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index fd5ca554..4f0d78e0 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, log *zap.SugaredLogger) ([]*PullRequest, error) { @@ -55,14 +54,6 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo return ToPullRequests(prs), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - prs, err := client.ListOpenedProjectMergeRequests(namespace, projectName, targetBr, log) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) - } - return ToPullRequests(prs), nil } else if ch.Type == gerrit.CodehostTypeGerrit { return nil, nil } else if ch.Type == CodeHostCodeHub { diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 68eb68ec..5b5923de 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) const ( @@ -39,7 +38,6 @@ const ( page = 1 perPage = 100 CodeHostCodeHub = "codehub" - CodeHostIlyshin = "ilyshin" ) func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*Namespace, error) { @@ -63,13 +61,6 @@ func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogg return nil, err } return ToNamespaces(nsList), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - nsList, err := client.ListNamespaces(keyword, log) - if err != nil { - return nil, err - } - return ToNamespaces(nsList), nil } else if ch.Type == gerrit.CodehostTypeGerrit { return []*Namespace{{ Name: gerrit.DefaultNamespace, diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 1c724afb..38103faf 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword string, log *zap.SugaredLogger) ([]*Project, error) { @@ -64,14 +63,6 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword stri } return ToProjects(projects), nil - } else if ch.Type == CodeHostIlyshin { - cli := ilyshin.NewClient(ch.Address, ch.AccessToken) - projects, err := cli.ListGroupProjects(namespace, keyword, log) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - return ToProjects(projects), nil } else if ch.Type == gerrit.CodehostTypeGerrit { cli := gerrit.NewClient(ch.Address, ch.AccessToken) projects, err := cli.ListProjectsByKey(keyword) diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 8c3edcaa..90dd3cc3 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListTags(codeHostID int, projectName string, namespace string, log *zap.SugaredLogger) ([]*Tag, error) { @@ -53,13 +52,6 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log return nil, err } return ToTags(tags), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - tags, err := client.ListTags(namespace, projectName, log) - if err != nil { - return nil, err - } - return ToTags(tags), nil } else if ch.Type == gerrit.CodehostTypeGerrit { client := gerrit.NewClient(ch.Address, ch.AccessToken) tags, err := client.ListTags(projectName) diff --git a/pkg/microservice/aslan/core/code/service/types.go b/pkg/microservice/aslan/core/code/service/types.go index 722c415a..81528aaa 100644 --- a/pkg/microservice/aslan/core/code/service/types.go +++ b/pkg/microservice/aslan/core/code/service/types.go @@ -17,11 +17,8 @@ limitations under the License. package service import ( - "strings" - "github.com/andygrunwald/go-gerrit" "github.com/google/go-github/v35/github" - "github.com/koderover/zadig/pkg/tool/ilyshin" "github.com/xanzy/go-gitlab" "github.com/koderover/zadig/pkg/tool/codehub" @@ -106,14 +103,6 @@ func ToBranches(obj interface{}) []*Branch { Merged: o.Merged, }) } - case []*ilyshin.Branch: - for _, o := range os { - res = append(res, &Branch{ - Name: o.Name, - Protected: o.Protected, - Merged: o.Merged, - }) - } } return res @@ -152,20 +141,6 @@ func ToPullRequests(obj interface{}) []*PullRequest { AuthorUsername: o.Author.Username, }) } - case []*ilyshin.MergeRequest: - for _, o := range os { - res = append(res, &PullRequest{ - ID: o.IID, - TargetBranch: o.TargetBranch, - SourceBranch: o.SourceBranch, - ProjectID: o.ProjectID, - Title: o.Title, - State: o.State, - CreatedAt: o.CreatedAt.Unix(), - UpdatedAt: o.UpdatedAt.Unix(), - AuthorUsername: o.Author.Username, - }) - } } return res @@ -214,14 +189,6 @@ func ToNamespaces(obj interface{}) []*Namespace { ProjectUUID: o.ProjectUUID, }) } - case []*ilyshin.Project: - for _, o := range os { - res = append(res, &Namespace{ - Name: o.Namespace.Name, - Path: o.Namespace.Path, - Kind: GroupKind, - }) - } } return res @@ -271,17 +238,6 @@ func ToProjects(obj interface{}) []*Project { RepoID: project.RepoID, }) } - case []*ilyshin.Project: - for _, o := range os { - namespaces := strings.Split(o.PathWithNamespace, "/") - res = append(res, &Project{ - ID: o.ID, - Name: o.Path, - Namespace: namespaces[0], - Description: o.Description, - DefaultBranch: "master", - }) - } } return res @@ -319,13 +275,6 @@ func ToTags(obj interface{}) []*Tag { Name: o.Name, }) } - case []*ilyshin.Tag: - for _, o := range os { - res = append(res, &Tag{ - Name: o.Name, - Message: o.Message, - }) - } } return res diff --git a/pkg/tool/ilyshin/branch.go b/pkg/tool/ilyshin/branch.go deleted file mode 100644 index aa168d3e..00000000 --- a/pkg/tool/ilyshin/branch.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "net/url" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Branch struct { - Name string `json:"name"` - Protected bool `json:"protected"` - Merged bool `json:"merged"` - Default bool `json:"default"` - CanPush bool `json:"can_push"` - DevelopersCanPush bool `json:"developers_can_push"` - DevelopersCanMerge bool `json:"developers_can_merge"` - Commit *Commit `json:"commit"` -} - -func (c *Client) ListBranches(owner, repo string, log *zap.SugaredLogger) ([]*Branch, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/branches", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "100", - } - - var err error - var branches []*Branch - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&branches)); err != nil { - log.Errorf("Failed to list project branches, error: %s", err) - return branches, err - } - - return branches, nil -} - -func generateProjectName(owner, repo string) string { - return url.PathEscape(fmt.Sprintf("%s/%s", owner, repo)) -} diff --git a/pkg/tool/ilyshin/client.go b/pkg/tool/ilyshin/client.go deleted file mode 100644 index 03a9d3a4..00000000 --- a/pkg/tool/ilyshin/client.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "crypto/tls" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Client struct { - *httpclient.Client - Address string - AccessToken string -} - -func NewClient(address, accessToken string) *Client { - c := httpclient.New( - httpclient.SetAuthScheme("Bearer"), - httpclient.SetAuthToken(accessToken), - httpclient.SetHostURL(address), - httpclient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}), - ) - return &Client{ - Client: c, - Address: address, - AccessToken: accessToken, - } -} diff --git a/pkg/tool/ilyshin/commit.go b/pkg/tool/ilyshin/commit.go deleted file mode 100644 index e90f7a76..00000000 --- a/pkg/tool/ilyshin/commit.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Commit struct { - ID string `json:"id"` - ShortID string `json:"short_id"` - Title string `json:"title"` - Message string `json:"message"` - AuthorName string `json:"author_name"` - AuthorEmail string `json:"author_email"` - AuthoredDate *time.Time `json:"authored_date"` - CommitterName string `json:"committer_name"` - CommitterEmail string `json:"committer_email"` - CommittedDate *time.Time `json:"committed_date"` - CreatedAt *time.Time `json:"created_at"` -} - -func (c *Client) GetLatestCommit(owner, repo, branch, path string, log *zap.SugaredLogger) (*Commit, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/commits", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "1", - "ref_name": branch, - "path": path, - } - - var err error - var commits []*Commit - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&commits)); err != nil { - log.Errorf("Failed to list project commits, error: %s", err) - return nil, err - } - if len(commits) == 0 { - return nil, fmt.Errorf("not found commits") - } - - return commits[0], nil -} diff --git a/pkg/tool/ilyshin/event_parsing.go b/pkg/tool/ilyshin/event_parsing.go deleted file mode 100644 index ff06dc34..00000000 --- a/pkg/tool/ilyshin/event_parsing.go +++ /dev/null @@ -1,179 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "encoding/json" - "fmt" - "net/http" - "time" -) - -type ProjectEvent struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - AvatarURL string `json:"avatar_url"` - GitSSHURL string `json:"git_ssh_url"` - GitHTTPURL string `json:"git_http_url"` - Namespace string `json:"namespace"` - PathWithNamespace string `json:"path_with_namespace"` - DefaultBranch string `json:"default_branch"` - Homepage string `json:"homepage"` - URL string `json:"url"` - SSHURL string `json:"ssh_url"` - HTTPURL string `json:"http_url"` - WebURL string `json:"web_url"` -} - -type Repository struct { - Name string `json:"name"` - Description string `json:"description"` - WebURL string `json:"web_url"` - AvatarURL string `json:"avatar_url"` - GitSSHURL string `json:"git_ssh_url"` - GitHTTPURL string `json:"git_http_url"` - Namespace string `json:"namespace"` - PathWithNamespace string `json:"path_with_namespace"` - DefaultBranch string `json:"default_branch"` - Homepage string `json:"homepage"` - URL string `json:"url"` - SSHURL string `json:"ssh_url"` - HTTPURL string `json:"http_url"` -} - -type ObjectAttributes struct { - ID int `json:"id"` - TargetBranch string `json:"target_branch"` - SourceBranch string `json:"source_branch"` - SourceProjectID int `json:"source_project_id"` - AuthorID int `json:"author_id"` - AssigneeID int `json:"assignee_id"` - AssigneeIDs []int `json:"assignee_ids"` - Title string `json:"title"` - StCommits []*Commit `json:"st_commits"` - MilestoneID int `json:"milestone_id"` - State string `json:"state"` - MergeStatus string `json:"merge_status"` - TargetProjectID int `json:"target_project_id"` - IID int `json:"iid"` - Description string `json:"description"` - Position int `json:"position"` - LockedAt string `json:"locked_at"` - UpdatedByID int `json:"updated_by_id"` - MergeError string `json:"merge_error"` - MergeWhenBuildSucceeds bool `json:"merge_when_build_succeeds"` - MergeUserID int `json:"merge_user_id"` - MergeCommitSHA string `json:"merge_commit_sha"` - DeletedAt string `json:"deleted_at"` - ApprovalsBeforeMerge string `json:"approvals_before_merge"` - RebaseCommitSHA string `json:"rebase_commit_sha"` - InProgressMergeCommitSHA string `json:"in_progress_merge_commit_sha"` - LockVersion int `json:"lock_version"` - TimeEstimate int `json:"time_estimate"` - Target *Repository `json:"target"` - LastCommit LastCommit `json:"last_commit"` - WorkInProgress bool `json:"work_in_progress"` - URL string `json:"url"` - Action string `json:"action"` - OldRev string `json:"oldrev"` -} - -type LastCommit struct { - ID string `json:"id"` - Message string `json:"message"` - Timestamp *time.Time `json:"timestamp"` - URL string `json:"url"` - Author Author `json:"author"` -} - -type MergeEvent struct { - ObjectKind string `json:"object_kind"` - Project ProjectEvent `json:"project"` - ObjectAttributes ObjectAttributes `json:"object_attributes"` -} - -type PushEvent struct { - ObjectKind string `json:"object_kind"` - Before string `json:"before"` - After string `json:"after"` - Ref string `json:"ref"` - CheckoutSHA string `json:"checkout_sha"` - UserID int `json:"user_id"` - UserName string `json:"user_name"` - UserUsername string `json:"user_username"` - UserEmail string `json:"user_email"` - UserAvatar string `json:"user_avatar"` - ProjectID int `json:"project_id"` - Project ProjectEvent `json:"project"` - Commits []Commits `json:"commits"` - TotalCommitsCount int `json:"total_commits_count"` -} - -type Commits struct { - ID string `json:"id"` - Message string `json:"message"` - Timestamp *time.Time `json:"timestamp"` - URL string `json:"url"` - Author Author `json:"author"` - Added []string `json:"added"` - Modified []string `json:"modified"` - Removed []string `json:"removed"` -} - -type Author struct { - Name string `json:"name"` - Email string `json:"email"` -} - -// EventType represents a ilyshin event type. -type EventType string - -// List of available event types. -const ( - EventTypeMergeRequest EventType = "Merge Request Hook" - EventTypePush EventType = "Push Hook" - EventTypeTagPush EventType = "Tag Push Hook" -) - -const eventTypeHeader = "X-CodeHub-Event" - -// HookEventType returns the event type for the given request. -func HookEventType(r *http.Request) EventType { - return EventType(r.Header.Get(eventTypeHeader)) -} - -func ParseHook(eventType EventType, payload []byte) (event interface{}, err error) { - return parseWebhook(eventType, payload) -} - -func parseWebhook(eventType EventType, payload []byte) (event interface{}, err error) { - switch eventType { - case EventTypeMergeRequest: - event = &MergeEvent{} - case EventTypePush: - event = &PushEvent{} - default: - return nil, fmt.Errorf("unexpected event type: %s", eventType) - } - - if err := json.Unmarshal(payload, event); err != nil { - return nil, err - } - - return event, nil -} diff --git a/pkg/tool/ilyshin/group_project.go b/pkg/tool/ilyshin/group_project.go deleted file mode 100644 index ffcb7ca3..00000000 --- a/pkg/tool/ilyshin/group_project.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -func (c *Client) ListGroupProjects(namespace, keyword string, log *zap.SugaredLogger) ([]*Project, error) { - url := fmt.Sprintf("/api/v4/groups/%s/projects", namespace) - qs := map[string]string{ - "order_by": "name", - "sort": "asc", - "per_page": "100", - } - if keyword != "" && len(keyword) > 2 { - qs["search"] = keyword - } - var err error - var gps []*Project - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&gps)); err != nil { - log.Errorf("Failed to list group projects, error: %s", err) - return gps, err - } - - return gps, nil -} diff --git a/pkg/tool/ilyshin/merge_request.go b/pkg/tool/ilyshin/merge_request.go deleted file mode 100644 index 0b166866..00000000 --- a/pkg/tool/ilyshin/merge_request.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" -) - -type MergeRequest struct { - ID int `json:"id"` - IID int `json:"iid"` - ProjectID int `json:"project_id"` - Title string `json:"title"` - State string `json:"state"` - CreatedAt *time.Time `json:"created_at"` - UpdatedAt *time.Time `json:"updated_at"` - SourceBranch string `json:"source_branch"` - TargetBranch string `json:"target_branch"` - SourceProjectID int `json:"source_project_id"` - TargetProjectID int `json:"target_project_id"` - Description string `json:"description"` - Author *BasicUser `json:"author"` - Changes []Changes `json:"changes"` -} - -type Changes struct { - OldPath string `json:"old_path"` - NewPath string `json:"new_path"` - AMode string `json:"a_mode"` - BMode string `json:"b_mode"` - Diff string `json:"diff"` - NewFile bool `json:"new_file"` - RenamedFile bool `json:"renamed_file"` - DeletedFile bool `json:"deleted_file"` -} - -type BasicUser struct { - ID int `json:"id"` - Username string `json:"username"` - Name string `json:"name"` - State string `json:"state"` - CreatedAt *time.Time `json:"created_at"` - AvatarURL string `json:"avatar_url"` - WebURL string `json:"web_url"` -} - -func (c *Client) ListOpenedProjectMergeRequests(owner, repo, targetBranch string, log *zap.SugaredLogger) ([]*MergeRequest, error) { - url := fmt.Sprintf("/api/v4/projects/%s/isource/merge_requests", generateProjectName(owner, repo)) - qs := map[string]string{ - "state": "opened", - "per_page": "100", - } - if targetBranch != "" { - qs["target_branch"] = targetBranch - } - - var err error - var mergeRequests []*MergeRequest - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&mergeRequests)); err != nil { - log.Errorf("Failed to list project mergeRequests, error: %s", err) - return mergeRequests, err - } - - return mergeRequests, nil -} - -func (c *Client) ListChangedFiles(event *MergeEvent) ([]string, error) { - files := make([]string, 0) - - url := fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/changes", event.ObjectAttributes.TargetProjectID, event.ObjectAttributes.IID) - var err error - var mergeRequest *MergeRequest - if _, err = c.Get(url, httpclient.SetResult(&mergeRequest)); err != nil { - log.Errorf("Failed to get project mergeRequest, error: %s", err) - return files, err - } - - for _, change := range mergeRequest.Changes { - files = append(files, change.NewPath) - files = append(files, change.OldPath) - } - - return files, nil -} - -func (c *Client) GetLatestPRCommitList(projectID string, pr int, log *zap.SugaredLogger) (*Commit, error) { - url := fmt.Sprintf("/api/v4/projects/%s/merge_requests/%d/commits", projectID, pr) - - var commit *Commit - if _, err := c.Get(url, httpclient.SetResult(&commit)); err != nil { - log.Errorf("Failed to get project mergeRequest commits, error: %s", err) - return commit, err - } - return commit, nil -} diff --git a/pkg/tool/ilyshin/project.go b/pkg/tool/ilyshin/project.go deleted file mode 100644 index d348a2da..00000000 --- a/pkg/tool/ilyshin/project.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Project struct { - ID int `json:"id"` - Description string `json:"description"` - Name string `json:"name"` - NameWithNamespace string `json:"name_with_namespace"` - Path string `json:"path"` - PathWithNamespace string `json:"path_with_namespace"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Archived bool `json:"archived"` - DefaultBranch string `json:"default_branch"` - Namespace *ProjectNamespace `json:"namespace"` -} - -type ProjectNamespace struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - Kind string `json:"kind"` - FullPath string `json:"full_path"` -} - -func (c *Client) ListNamespaces(keyword string, log *zap.SugaredLogger) ([]*Project, error) { - url := fmt.Sprintf("/api/v4/projects") - qs := map[string]string{ - "order_by": "name", - "sort": "asc", - "project_type": "group", // group or project - "per_page": "100", - "simple": "true", - } - if keyword != "" && len(keyword) > 2 { - qs["search"] = keyword - } - var err error - var gps []*Project - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&gps)); err != nil { - log.Errorf("Failed to list projects, error: %s", err) - return gps, err - } - - var resp []*Project - projectNames := sets.NewString() - for _, gp := range gps { - if projectNames.Has(gp.Namespace.Name) || gp.Namespace.Kind == "user" { - continue - } - - projectNames.Insert(gp.Namespace.Name) - resp = append(resp, gp) - } - return resp, nil -} diff --git a/pkg/tool/ilyshin/repo.go b/pkg/tool/ilyshin/repo.go deleted file mode 100644 index 1bd95db4..00000000 --- a/pkg/tool/ilyshin/repo.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - - "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" -) - -type TreeNode struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Path string `json:"path"` - Mode string `json:"mode"` -} - -type File struct { - FileName string `json:"file_name"` - FilePath string `json:"file_path"` - Size int `json:"size"` - Encoding string `json:"encoding"` - Content string `json:"content"` - Ref string `json:"ref"` - BlobID string `json:"blob_id"` - CommitID string `json:"commit_id"` - SHA256 string `json:"content_sha256"` - LastCommitID string `json:"last_commit_id"` -} - -type Diff struct { - Diff string `json:"diff"` - NewPath string `json:"new_path"` - OldPath string `json:"old_path"` - AMode string `json:"a_mode"` - BMode string `json:"b_mode"` - NewFile bool `json:"new_file"` - RenamedFile bool `json:"renamed_file"` - DeletedFile bool `json:"deleted_file"` -} - -func (c *Client) ListTree(owner, repo string, ref string, path string) ([]*TreeNode, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/tree", generateProjectName(owner, repo)) - qs := map[string]string{ - "ref": ref, - "path": path, - "per_page": "100", - } - - var err error - var treeNodes []*TreeNode - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&treeNodes)); err != nil { - log.Errorf("Failed to list project tree nodes, error: %s", err) - return treeNodes, err - } - - return treeNodes, nil -} - -func (c *Client) GetRawFile(owner, repo string, sha string, fileName string) ([]byte, error) { - File, err := c.GetFile(owner, repo, sha, fileName) - if err != nil { - return nil, err - } - var resp []byte - url := fmt.Sprintf("/api/v4/projects/%s/repository/blobs/%s/raw", generateProjectName(owner, repo), File.BlobID) - if _, err = c.Get(url, httpclient.SetResult(&resp)); err != nil { - log.Errorf("Failed to get project blob raw, error: %s", err) - return resp, err - } - - return resp, nil -} - -func (c *Client) GetFile(owner, repo string, ref, path string) (*File, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/files/%s", generateProjectName(owner, repo), path) - qs := map[string]string{ - "ref": ref, - } - - var err error - var file *File - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&file)); err != nil { - log.Errorf("Failed to get project file content, error: %s", err) - return file, err - } - - return file, nil -} - -func (c *Client) Compare(projectID int, from, to string) ([]*Diff, error) { - url := fmt.Sprintf("/api/v4/projects/%d/repository/compare", projectID) - qs := map[string]string{ - "from": from, - "to": to, - } - - var err error - var diffs []*Diff - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&diffs)); err != nil { - log.Errorf("Failed to compare content, error: %s", err) - return diffs, err - } - - return diffs, nil -} diff --git a/pkg/tool/ilyshin/tag.go b/pkg/tool/ilyshin/tag.go deleted file mode 100644 index cfbb81ef..00000000 --- a/pkg/tool/ilyshin/tag.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Tag struct { - Name string `json:"name"` - Message string `json:"message"` - Release *ReleaseNote `json:"release"` - Commit *Commit `json:"commit"` -} - -type ReleaseNote struct { - TagName string `json:"tag_name"` - Description string `json:"description"` -} - -func (c *Client) ListTags(owner, repo string, log *zap.SugaredLogger) ([]*Tag, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/tags", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "100", - } - - var err error - var tags []*Tag - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&tags)); err != nil { - log.Errorf("Failed to list project tags, error: %s", err) - return tags, err - } - - return tags, nil -} diff --git a/pkg/tool/ilyshin/webhook.go b/pkg/tool/ilyshin/webhook.go deleted file mode 100644 index 2d38c4d2..00000000 --- a/pkg/tool/ilyshin/webhook.go +++ /dev/null @@ -1,100 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" -) - -type ProjectHook struct { - ID int `json:"id"` - URL string `json:"url"` - ConfidentialNoteEvents bool `json:"confidential_note_events"` - ProjectID int `json:"project_id"` - PushEvents bool `json:"push_events"` - PushEventsBranchFilter string `json:"push_events_branch_filter"` - IssuesEvents bool `json:"issues_events"` - ConfidentialIssuesEvents bool `json:"confidential_issues_events"` - MergeRequestsEvents bool `json:"merge_requests_events"` - TagPushEvents bool `json:"tag_push_events"` - NoteEvents bool `json:"note_events"` - JobEvents bool `json:"job_events"` - PipelineEvents bool `json:"pipeline_events"` - WikiPageEvents bool `json:"wiki_page_events"` - DeploymentEvents bool `json:"deployment_events"` - ReleasesEvents bool `json:"releases_events"` - EnableSSLVerification bool `json:"enable_ssl_verification"` - CreatedAt *time.Time `json:"created_at"` -} - -type Hook struct { - URL string `url:"url,omitempty" json:"url,omitempty"` - Name string `url:"name,omitempty" json:"name,omitempty"` - Token string `url:"token,omitempty" json:"token,omitempty"` - PushEvents bool `url:"push_events,omitempty" json:"push_events,omitempty"` - MergeRequestsEvents bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` - TagPushEvents bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"` - EnableSSLVerification bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"` -} - -func (c *Client) ListProjectHooks(owner, repo string, log *zap.SugaredLogger) ([]*ProjectHook, error) { - url := fmt.Sprintf("/api/v4/projects/%s/hooks", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "100", - } - - var err error - var projectHooks []*ProjectHook - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&projectHooks)); err != nil { - log.Errorf("Failed to list project hooks, error: %s", err) - return projectHooks, err - } - - return projectHooks, nil -} - -func (c *Client) AddProjectHook(owner, repo, hookURL, token string) (*ProjectHook, error) { - url := fmt.Sprintf("/api/v4/projects/%s/hooks", generateProjectName(owner, repo)) - hook := &Hook{ - URL: hookURL, - Token: token, - MergeRequestsEvents: true, - PushEvents: true, - TagPushEvents: true, - } - var projectHook *ProjectHook - if _, err := c.Post(url, httpclient.SetBody(hook), httpclient.SetResult(&projectHook)); err != nil { - log.Errorf("Failed to create project hook, error: %s", err) - return projectHook, err - } - return projectHook, nil -} - -func (c *Client) DeleteProjectHook(owner, repo string, id int) error { - url := fmt.Sprintf("/api/v4/projects/%s/hooks/%d", generateProjectName(owner, repo), id) - if _, err := c.Delete(url); err != nil { - log.Errorf("Failed to delete project webhook, error: %s", err) - return err - } - return nil -} -- Gitee From 534829bf9740781bfc00770a2b575a2bb73afe3a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 15:01:25 +0800 Subject: [PATCH 0430/2647] remove permission code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/handler/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 8f03e42f..c81d1461 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -48,7 +48,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) - product.PUT("/:name/serviceOrder", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateServiceOrder) + product.PUT("/:name/serviceOrder", gin2.UpdateOperationLogStatus, UpdateServiceOrder) product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) } -- Gitee From c5bd2295be6d1bc7f360bd58a2186caee6be8381 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 15:11:00 +0800 Subject: [PATCH 0431/2647] optimize service update logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7fc46af5..756cb8d7 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2833,11 +2833,33 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } + handler := func(data interface{}, log *zap.SugaredLogger) error { + service := data.(*commonmodels.ProductService) + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: service.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) + return errors.Wrapf(err, "failed to find template servce %s", service.ServiceName) + } + renderChart, _ := renderChartMap[service.ServiceName] + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) + if err != nil { + return errors.Wrapf(err, "failed to install of upgrade service %s", service.ServiceName) + } + return nil + } + errList := new(multierror.Error) for groupIndex, services := range productResp.Services { - var wg sync.WaitGroup + serviceList := make([]interface{}, 0) for _, svc := range services { - renderChart, ok := renderChartMap[svc.ServiceName] + _, ok := renderChartMap[svc.ServiceName] if !ok { continue } @@ -2845,33 +2867,14 @@ func updateProductGroup(username, productName, envName, updateType string, produ if !svcNameSet.Has(svc.ServiceName) { continue } - wg.Add(1) - go func(service *commonmodels.ProductService) { - defer wg.Done() - - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: service.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) - errList = multierror.Append(errList, err) - return - } + serviceList = append(serviceList, svc) + } - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) - if err != nil { - errList = multierror.Append(errList, err) - return - } - }(svc) + serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if serviceGroupErr != nil { + errList = multierror.Append(errList, serviceGroupErr...) } - wg.Wait() if err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, services); err != nil { log.Errorf("Failed to update service group %d, err: %s", groupIndex, err) errList = multierror.Append(errList, err) -- Gitee From 3e7491fd1592eaa17932d92cb5497656d7b30a0e Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Tue, 2 Nov 2021 15:48:00 +0800 Subject: [PATCH 0432/2647] fix restart workflow panic (#515) Signed-off-by: lou --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index c59d90c1..0a47a946 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -386,7 +386,7 @@ func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, t subBuildTaskMap := subStage.SubTasks for serviceModule, subTask := range subBuildTaskMap { if buildInfo, err := base.ToBuildTask(subTask); err == nil { - if newModules, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{Targets: []string{serviceModule}, ServiceName: buildInfo.Service, ProductName: t.ProductName}); err == nil { + if newModules, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{Targets: []string{serviceModule}, ServiceName: buildInfo.Service, ProductName: t.ProductName}); err == nil && len(newModules) > 0 { newBuildInfo := newModules[0] buildInfo.JobCtx.BuildSteps = []*task.BuildStep{} if newBuildInfo.Scripts != "" { -- Gitee From dc0f3fbcfac4a6538172471322fc6a6290d39810 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 16:26:22 +0800 Subject: [PATCH 0433/2647] remove ilyshin code Signed-off-by: liu deyi --- .../core/common/service/ilyshin/client.go | 32 -- .../core/common/service/ilyshin/webhook.go | 38 --- .../core/common/service/webhook/controller.go | 5 - .../aslan/core/common/service/workflow.go | 4 +- .../aslan/core/service/service/loader.go | 256 --------------- .../aslan/core/service/service/service.go | 5 +- .../aslan/core/workflow/handler/webhook.go | 3 - .../core/workflow/service/webhook/ilyshin.go | 291 ------------------ .../service/webhook/ilyshin_workflow_task.go | 152 --------- .../core/workflow/service/webhook/utils.go | 131 -------- .../core/workflow/service/workflow/gitlab.go | 47 --- .../service/workflow/pipeline_validation.go | 2 +- .../reaper/core/service/meta/const.go | 2 - .../reaper/core/service/meta/types.go | 2 +- .../reaper/core/service/reaper/git.go | 4 +- pkg/setting/consts.go | 2 - pkg/shared/codehost/codehost.go | 1 - 17 files changed, 7 insertions(+), 970 deletions(-) delete mode 100644 pkg/microservice/aslan/core/common/service/ilyshin/client.go delete mode 100644 pkg/microservice/aslan/core/common/service/ilyshin/webhook.go delete mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go delete mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go diff --git a/pkg/microservice/aslan/core/common/service/ilyshin/client.go b/pkg/microservice/aslan/core/common/service/ilyshin/client.go deleted file mode 100644 index d2fc7736..00000000 --- a/pkg/microservice/aslan/core/common/service/ilyshin/client.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "github.com/koderover/zadig/pkg/tool/ilyshin" -) - -type Client struct { - *ilyshin.Client -} - -func NewClient(address, accessToken string) *Client { - c := ilyshin.NewClient(address, accessToken) - return &Client{ - Client: c, - } -} diff --git a/pkg/microservice/aslan/core/common/service/ilyshin/webhook.go b/pkg/microservice/aslan/core/common/service/ilyshin/webhook.go deleted file mode 100644 index 4a8dd644..00000000 --- a/pkg/microservice/aslan/core/common/service/ilyshin/webhook.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "strconv" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" -) - -func (c *Client) CreateWebHook(owner, repo string) (string, error) { - projectHook, err := c.AddProjectHook(owner, repo, config.WebHookURL(), gitservice.GetHookSecret()) - - return strconv.Itoa(projectHook.ID), err -} - -func (c *Client) DeleteWebHook(owner, repo, hookID string) error { - hookIDInt, err := strconv.Atoi(hookID) - if err != nil { - return err - } - return c.DeleteProjectHook(owner, repo, hookIDInt) -} diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index c9e80961..0297b64c 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -29,7 +29,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/ilyshin" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -132,8 +131,6 @@ func removeWebhook(t *task, logger *zap.Logger) { } case setting.SourceFromCodeHub: cl = codehub.NewClient(t.ak, t.sk, t.region) - case setting.SourceFromIlyshin: - cl = ilyshin.NewClient(t.address, t.token) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} @@ -195,8 +192,6 @@ func addWebhook(t *task, logger *zap.Logger) { case setting.SourceFromCodeHub: cl = codehub.NewClient(t.ak, t.sk, t.region) - case setting.SourceFromIlyshin: - cl = ilyshin.NewClient(t.address, t.token) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 325a3f3d..4b762119 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -175,7 +175,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger } switch ch.Type { - case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromIlyshin: + case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: err = webhook.NewClient().RemoveWebHook(&webhook.TaskOption{ Name: wh.name, Owner: wh.owner, @@ -209,7 +209,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger } switch ch.Type { - case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromIlyshin: + case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: err = webhook.NewClient().AddWebHook(&webhook.TaskOption{ Name: wh.name, Owner: wh.owner, diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 6fc46294..4c9434af 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -23,7 +23,6 @@ import ( "io/ioutil" "os" "path" - "path/filepath" "strings" "github.com/google/go-github/v35/github" @@ -40,7 +39,6 @@ import ( "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/ilyshin" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) @@ -67,8 +65,6 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b ret, err = preloadGerritService(ch, repoName, branchName, remoteName, path, isDir) case setting.SourceFromCodeHub: ret, err = preloadCodehubService(ch, repoName, repoUUID, branchName, path, isDir) - case setting.SourceFromIlyshin: - ret, err = preloadIlyshinService(ch, repoOwner, repoName, branchName, path, isDir) default: return nil, e.ErrPreloadServiceTemplate.AddDesc("Not supported code source") } @@ -90,8 +86,6 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoNam return loadGerritService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) case setting.SourceFromCodeHub: return loadCodehubService(username, ch, repoOwner, repoName, repoUUID, branchName, args, log) - case setting.SourceFromIlyshin: - return loadIlyshinService(username, ch, repoOwner, repoName, branchName, args, log) default: return e.ErrLoadServiceTemplate.AddDesc("unsupported code source") } @@ -113,8 +107,6 @@ func ValidateServiceUpdate(codehostID int, serviceName, repoOwner, repoName, rep return validateServiceUpdateGerrit(detail, serviceName, repoName, branchName, remoteName, path, isDir) case setting.SourceFromCodeHub: return validateServiceUpdateCodehub(detail, serviceName, repoName, repoUUID, branchName, path, isDir) - case setting.SourceFromIlyshin: - return validateServiceUpdateIlyshin(detail, serviceName, repoOwner, repoName, branchName, path, isDir) default: return e.ErrValidateServiceUpdate.AddDesc("Not supported code source") } @@ -246,64 +238,6 @@ func preloadCodehubService(detail *poetry.CodeHost, repoName, repoUUID, branchNa return ret, nil } -// 根据 repo 信息获取 ilyshin 可以加载的服务列表 -func preloadIlyshinService(detail *poetry.CodeHost, repoOwner, repoName, branchName, path string, isDir bool) ([]string, error) { - ret := make([]string, 0) - - ilyshinClient := ilyshin.NewClient(detail.Address, detail.AccessToken) - // 非文件夹情况下直接获取文件信息 - if !isDir { - if !isYaml(path) { - return ret, e.ErrPreloadServiceTemplate.AddDesc("File is not of type yaml or yml, select again") - } - fileInfo, err := ilyshinClient.GetFile(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get file info from ilyshin with path: %s, the error is %+v", path, err) - return ret, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) - } - extension := filepath.Ext(fileInfo.FileName) - fileName := fileInfo.FileName[0 : len(fileInfo.FileName)-len(extension)] - ret = append(ret, fileName) - return ret, nil - } - - treeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is: %+v", path, err) - return ret, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(treeInfo) { - svcName := path - if path == "" { - svcName = repoName - } - pathList := strings.Split(svcName, "/") - folderName := pathList[len(pathList)-1] - ret = append(ret, folderName) - return ret, nil - } - isGrandparent := false - for _, entry := range treeInfo { - if entry.Type == "tree" { - subtreeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is: %+v", path, err) - return ret, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(subtreeInfo) { - isGrandparent = true - ret = append(ret, entry.Name) - } - } - } - if !isGrandparent { - log.Errorf("invalid folder selected since no yaml is presented in path: %s", path) - return ret, e.ErrPreloadServiceTemplate.AddDesc("所选路径下没有yaml,请重新选择") - } - - return ret, nil -} - // 根据repo信息从gerrit加载服务 func loadGerritService(username string, ch *poetry.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { base := path.Join(config.S3StoragePath(), repoName) @@ -569,124 +503,6 @@ func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeN return err } -// 根据 repo 信息从 Ilyshin 加载服务 -func loadIlyshinService(username string, ch *poetry.CodeHost, repoOwner, repoName, branchName string, args *LoadServiceReq, log *zap.SugaredLogger) error { - ilyshinClient := ilyshin.NewClient(ch.Address, ch.AccessToken) - repoInfo := fmt.Sprintf("%s/%s", repoOwner, repoName) - if !args.LoadFromDir { - file, err := ilyshinClient.GetFile(repoOwner, repoName, branchName, args.LoadPath) - if err != nil { - log.Errorf("Failed to get file info for path: %s from ilyshin, the error is: %+v", args.LoadPath, err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - decodedContent, err := base64.StdEncoding.DecodeString(file.Content) - if err != nil { - log.Errorf("Failed to decode file, the error is: %+v", err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - srcPath := fmt.Sprintf("%s/%s/blob/%s/%s", ch.Address, repoInfo, branchName, args.LoadPath) - splittedYaml := SplitYaml(string(decodedContent)) - createSvcArgs := &models.Service{ - CodehostID: ch.ID, - RepoOwner: repoOwner, - RepoName: repoName, - BranchName: branchName, - LoadPath: args.LoadPath, - LoadFromDir: args.LoadFromDir, - KubeYamls: splittedYaml, - SrcPath: srcPath, - CreateBy: username, - ServiceName: getFileName(file.FileName), - Type: args.Type, - ProductName: args.ProductName, - Source: setting.SourceFromIlyshin, - Yaml: string(decodedContent), - Commit: &models.Commit{SHA: file.CommitID}, - Visibility: args.Visibility, - } - - _, err = CreateServiceTemplate(username, createSvcArgs, log) - if err != nil { - _, messageMap := e.ErrorMessage(err) - if description, ok := messageMap["description"]; ok { - return e.ErrLoadServiceTemplate.AddDesc(description.(string)) - } - return e.ErrLoadServiceTemplate.AddDesc("Load Service Error for unknown reason") - } - return nil - } - - treeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, args.LoadPath) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is: %+v", args.LoadPath, err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(treeInfo) { - return loadServiceFromIlyshin(ilyshinClient, treeInfo, ch, username, repoOwner, repoName, branchName, args.LoadPath, args, log) - } - for _, treeNode := range treeInfo { - if treeNode.Type == "tree" { - subtree, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, args.LoadPath) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is %+v", treeNode.Path, err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(subtree) { - if err := loadServiceFromIlyshin(ilyshinClient, subtree, ch, username, repoOwner, repoName, branchName, treeNode.Path, args, log); err != nil { - return err - } - } - } - } - return nil -} - -func loadServiceFromIlyshin(client *ilyshin.Client, tree []*ilyshin.TreeNode, ch *poetry.CodeHost, username, repoOwner, repoName, branchName, path string, args *LoadServiceReq, log *zap.SugaredLogger) error { - pathList := strings.Split(path, "/") - splittedYaml := []string{} - serviceName := pathList[len(pathList)-1] - repoInfo := fmt.Sprintf("%s/%s", repoOwner, repoName) - yamlList, sha, err := extractIlyshinYamls(client, tree, repoOwner, repoName, branchName) - if err != nil { - log.Errorf("Failed to extract yamls from ilyshin, the error is: %+v", err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - for _, yamlEntry := range yamlList { - splittedYaml = append(splittedYaml, SplitYaml(yamlEntry)...) - } - yml := joinYamls(yamlList) - srcPath := fmt.Sprintf("%s/%s/tree/%s/%s", ch.Address, repoInfo, branchName, path) - createSvcArgs := &models.Service{ - CodehostID: ch.ID, - RepoOwner: repoOwner, - RepoName: repoName, - BranchName: branchName, - LoadPath: path, - LoadFromDir: args.LoadFromDir, - KubeYamls: splittedYaml, - SrcPath: srcPath, - CreateBy: username, - ServiceName: serviceName, - Type: args.Type, - ProductName: args.ProductName, - Source: setting.SourceFromIlyshin, - Yaml: yml, - Commit: &models.Commit{SHA: sha}, - Visibility: args.Visibility, - } - - _, err = CreateServiceTemplate(username, createSvcArgs, log) - if err != nil { - _, messageMap := e.ErrorMessage(err) - if description, ok := messageMap["description"]; ok { - err = e.ErrLoadServiceTemplate.AddDesc(description.(string)) - } else { - err = e.ErrLoadServiceTemplate.AddDesc("Load Service Error for unknown reason") - } - } - return err -} - // 根据github repo决定服务是否可以更新这个repo地址 func validateServiceUpdateGithub(detail *poetry.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { ctx := context.Background() @@ -879,47 +695,6 @@ func validateServiceUpdateCodehub(detail *poetry.CodeHost, serviceName, repoName return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") } -// 根据 ilyshin repo 决定服务是否可以更新这个 repo 地址 -func validateServiceUpdateIlyshin(detail *poetry.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { - ilyshinClient := ilyshin.NewClient(detail.Address, detail.AccessToken) - // 非文件夹情况下直接获取文件信息 - if !isDir { - if !isYaml(path) { - return e.ErrValidateServiceUpdate.AddDesc("File is not of type yaml or yml, select again") - } - fileInfo, err := ilyshinClient.GetFile(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get file info from ilyshin with path: %s, the error is %+v", path, err) - return e.ErrValidateServiceUpdate.AddDesc(err.Error()) - } - if getFileName(fileInfo.FileName) != serviceName { - log.Errorf("The loaded file name [%s] is the same as the service to be updated: [%s]", fileInfo.FileName, serviceName) - return e.ErrValidateServiceUpdate.AddDesc("文件名称和服务名称不一致") - } - return nil - } - - treeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get dir content from gitlab with path: %s, the error is: %+v", path, err) - return e.ErrValidateServiceUpdate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(treeInfo) { - svcName := path - if path == "" { - svcName = repoName - } - pathList := strings.Split(svcName, "/") - folderName := pathList[len(pathList)-1] - if folderName != serviceName { - log.Errorf("The loaded file name [%s] is the same as the service to be updated: [%s]", folderName, serviceName) - return e.ErrValidateServiceUpdate.AddDesc("文件夹名称和服务名称不一致") - } - return nil - } - return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") -} - func isValidGithubServiceDir(child []*github.RepositoryContent) bool { for _, entry := range child { if entry.GetType() == "file" && isYaml(entry.GetName()) { @@ -938,15 +713,6 @@ func isValidGitlabServiceDir(child []*gitlab.TreeNode) bool { return false } -func isValidIlyshinServiceDir(child []*ilyshin.TreeNode) bool { - for _, entry := range child { - if entry.Type == "blob" && isYaml(entry.Name) { - return true - } - } - return false -} - func isValidGerritServiceDir(child []os.FileInfo) bool { for _, file := range child { if !file.IsDir() && isYaml(file.Name()) { @@ -999,25 +765,3 @@ func extractGerritYamls(basePath string, tree []os.FileInfo) ([]string, error) { } return ret, nil } - -func extractIlyshinYamls(client *ilyshin.Client, tree []*ilyshin.TreeNode, repoOwner, repoName, branchName string) ([]string, string, error) { - ret := []string{} - var sha string - for _, entry := range tree { - if isYaml(entry.Name) { - fileInfo, err := client.GetFile(repoOwner, repoName, branchName, entry.Path) - if err != nil { - log.Errorf("Failed to download ilyshin file: %s, the error is: %+v", entry.Path, err) - return nil, "", err - } - decodedFile, err := base64.StdEncoding.DecodeString(fileInfo.Content) - if err != nil { - log.Errorf("Failed to decode content from the given file of path: %s, the error is: %s", entry.Path, err) - return nil, "", err - } - ret = append(ret, string(decodedFile)) - sha = fileInfo.CommitID - } - } - return ret, sha, nil -} diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index f57c8c4a..17cf5c53 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -249,8 +249,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv } if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || - args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub || - args.Source == setting.SourceFromIlyshin { + args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub { serviceOption.Yaml = args.Yaml } return serviceOption, nil @@ -659,7 +658,7 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap } } // 配置来源为Gitlab,对比配置的ChangeLog是否变化 - if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromIlyshin { + if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub { if args.Commit != nil && serviceTmpl.Commit != nil && args.Commit.SHA == serviceTmpl.Commit.SHA { log.Infof("%s change log remains the same, quit creation", args.Source) return GetServiceOption(serviceTmpl, log) diff --git a/pkg/microservice/aslan/core/workflow/handler/webhook.go b/pkg/microservice/aslan/core/workflow/handler/webhook.go index 9179049c..fb9b8a50 100644 --- a/pkg/microservice/aslan/core/workflow/handler/webhook.go +++ b/pkg/microservice/aslan/core/workflow/handler/webhook.go @@ -28,7 +28,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/webhook" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/codehub" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) // @Router /workflow/webhook [POST] @@ -51,8 +50,6 @@ func ProcessWebHook(c *gin.Context) { ctx.Err = webhook.ProcessGitlabHook(payload, c.Request, ctx.RequestID, ctx.Logger) } else if codehub.HookEventType(c.Request) != "" { ctx.Err = webhook.ProcessCodehubHook(payload, c.Request, ctx.RequestID, ctx.Logger) - } else if ilyshin.HookEventType(c.Request) != "" { - ctx.Err = webhook.ProcessIlyshinHook(payload, c.Request, ctx.RequestID, ctx.Logger) } else { ctx.Err = webhook.ProcessGerritHook(payload, c.Request, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go b/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go deleted file mode 100644 index 491d1514..00000000 --- a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go +++ /dev/null @@ -1,291 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package webhook - -import ( - "errors" - "fmt" - "net/http" - "strconv" - "strings" - "time" - - "github.com/hashicorp/go-multierror" - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" - workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/ilyshin" - "github.com/koderover/zadig/pkg/types/permission" -) - -func ProcessIlyshinHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { - token := req.Header.Get("X-CodeHub-Token") - secret := gitservice.GetHookSecret() - - if secret != "" && token != secret { - return errors.New("token is illegal") - } - - eventType := ilyshin.HookEventType(req) - event, err := ilyshin.ParseHook(eventType, payload) - if err != nil { - log.Warnf("unexpected event type: %s", eventType) - return nil - } - - forwardedProto := req.Header.Get("X-Forwarded-Proto") - forwardedHost := req.Header.Get("X-Forwarded-Host") - baseURI := fmt.Sprintf("%s://%s", forwardedProto, forwardedHost) - - var pushEvent *ilyshin.PushEvent - var errorList = &multierror.Error{} - switch event := event.(type) { - case *ilyshin.PushEvent: - pushEvent = event - if len(pushEvent.Commits) > 0 { - webhookUser := &commonmodels.WebHookUser{ - Domain: req.Header.Get("X-Forwarded-Host"), - UserName: pushEvent.Commits[0].Author.Name, - Email: pushEvent.Commits[0].Author.Email, - Source: setting.SourceFromIlyshin, - CreatedAt: time.Now().Unix(), - } - commonrepo.NewWebHookUserColl().Upsert(webhookUser) - } - - if err = updateServiceTemplateByIlyshinPushEvent(pushEvent, log); err != nil { - errorList = multierror.Append(errorList, err) - } - } - - //产品工作流webhook - if err = TriggerWorkflowByIlyshinEvent(event, baseURI, requestID, log); err != nil { - errorList = multierror.Append(errorList, err) - } - - return errorList.ErrorOrNil() -} - -func updateServiceTemplateByIlyshinPushEvent(event *ilyshin.PushEvent, log *zap.SugaredLogger) error { - log.Infof("EVENT: Ilyshin WEBHOOK UPDATING SERVICE TEMPLATE") - - address, err := GetAddress(event.Project.WebURL) - if err != nil { - log.Errorf("GetAddress failed, error: %s", err) - return err - } - - client, err := getIlyshinClientByAddress(address) - if err != nil { - return err - } - - diffs, err := client.Compare(event.ProjectID, event.Before, event.After) - if err != nil { - log.Errorf("Failed to get push event diffs, error: %s", err) - return err - } - serviceTmpls, err := GetIlyshinServiceTemplates() - if err != nil { - log.Errorf("Failed to get ilyshin service templates, error: %s", err) - return err - } - - errs := &multierror.Error{} - for _, service := range serviceTmpls { - srcPath := service.SrcPath - _, _, _, _, path, _, err := GetOwnerRepoBranchPath(srcPath) - if err != nil { - errs = multierror.Append(errs, err) - } - // 判断PushEvent的Diffs中是否包含该服务模板的src_path - affected := false - for _, diff := range diffs { - if strings.Contains(diff.OldPath, path) || strings.Contains(diff.NewPath, path) { - affected = true - break - } - } - if affected { - log.Infof("Started to sync service template %s from ilyshin %s", service.ServiceName, service.SrcPath) - service.CreateBy = "system" - err := SyncServiceTemplateFromIlyshin(service, log) - if err != nil { - log.Errorf("SyncServiceTemplateFromIlyshin failed, error: %s", err) - errs = multierror.Append(errs, err) - } - } else { - log.Infof("Service template %s from ilyshin %s is not affected, no sync", service.ServiceName, service.SrcPath) - } - - } - return errs.ErrorOrNil() -} - -func GetIlyshinServiceTemplates() ([]*commonmodels.Service, error) { - opt := &commonrepo.ServiceListOption{ - Type: setting.K8SDeployType, - Source: setting.SourceFromIlyshin, - } - return commonrepo.NewServiceColl().ListMaxRevisions(opt) -} - -// SyncServiceTemplateFromIlyshin Force to sync Service Template to latest commit and content, -// Notes: if remains the same, quit sync; if updates, revision +1 -func SyncServiceTemplateFromIlyshin(service *commonmodels.Service, log *zap.SugaredLogger) error { - // 判断一下 Source 字段,如果 Source 字段不是 ilyshin,直接返回 - if service.Source != setting.SourceFromIlyshin { - return fmt.Errorf("service template is not from ilyshin") - } - // 获取当前Commit的SHA - var before string - if service.Commit != nil { - before = service.Commit.SHA - } - // Sync最新的Commit的SHA - var after string - err := syncIlyshinLatestCommit(service, log) - if err != nil { - return err - } - after = service.Commit.SHA - // 判断一下是否需要 Sync 内容 - if before == after { - log.Infof("Before and after SHA: %s remains the same, no need to sync", before) - // 无需更新 - return nil - } - // 在 Ensure 过程中会检查 source,如果 source 为 ilyshin,则同步 ilyshin 内容到 service 中 - if err := fillServiceTmpl(setting.WebhookTaskCreator, service, log); err != nil { - log.Errorf("ensureServiceTmpl error: %+v", err) - return e.ErrValidateTemplate.AddDesc(err.Error()) - } - // 更新到数据库,revision+1 - if err := commonrepo.NewServiceColl().Create(service); err != nil { - log.Errorf("Failed to sync service %s from ilyshin path %s error: %v", service.ServiceName, service.SrcPath, err) - return e.ErrCreateTemplate.AddDesc(err.Error()) - } - log.Infof("End of sync service template %s from ilyshin path %s", service.ServiceName, service.SrcPath) - return nil -} - -func TriggerWorkflowByIlyshinEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { - // 1. find configured workflow - workflowList, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) - if err != nil { - log.Errorf("failed to list workflow %v", err) - return err - } - - mErr := &multierror.Error{} - diffSrv := func(mergeEvent *ilyshin.MergeEvent, codehostId int) ([]string, error) { - return findIlyshinChangedFilesOfMergeRequest(mergeEvent, codehostId) - } - - for _, workflow := range workflowList { - if workflow.HookCtl == nil || !workflow.HookCtl.Enabled { - continue - } - - log.Debugf("find %d hooks in workflow %s", len(workflow.HookCtl.Items), workflow.Name) - for _, item := range workflow.HookCtl.Items { - if item.WorkflowArgs == nil { - continue - } - - // 2. match webhook - matcher := createIlyshinEventMatcher(event, diffSrv, workflow, log) - if matcher == nil { - continue - } - - matches, err := matcher.Match(item.MainRepo) - if err != nil { - mErr = multierror.Append(mErr, err) - continue - } - - if !matches { - log.Debugf("event not matches %v", item.MainRepo) - continue - } - - log.Infof("event match hook %v of %s", item.MainRepo, workflow.Name) - namespace := strings.Split(item.WorkflowArgs.Namespace, ",")[0] - opt := &commonrepo.ProductFindOptions{Name: workflow.ProductTmplName, EnvName: namespace} - var prod *commonmodels.Product - if prod, err = commonrepo.NewProductColl().Find(opt); err != nil { - log.Warnf("can't find environment %s-%s", item.WorkflowArgs.Namespace, workflow.ProductTmplName) - continue - } - - var mergeRequestID, commitID string - if ev, isPr := event.(*ilyshin.MergeEvent); isPr { - // 如果是merge request,且该webhook触发器配置了自动取消, - // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 - mergeRequestID = strconv.Itoa(ev.ObjectAttributes.IID) - commitID = ev.ObjectAttributes.LastCommit.ID - autoCancelOpt := &AutoCancelOpt{ - MergeRequestID: mergeRequestID, - CommitID: commitID, - TaskType: config.WorkflowType, - MainRepo: item.MainRepo, - WorkflowArgs: item.WorkflowArgs, - } - err := AutoCancelTask(autoCancelOpt, log) - if err != nil { - log.Errorf("failed to auto cancel workflow task when receive event %v due to %v ", event, err) - mErr = multierror.Append(mErr, err) - } - } - - args := matcher.UpdateTaskArgs(prod, item.WorkflowArgs, item.MainRepo, requestID) - args.MergeRequestID = mergeRequestID - args.CommitID = commitID - args.Source = setting.SourceFromIlyshin - args.CodehostID = item.MainRepo.CodehostID - args.RepoOwner = item.MainRepo.RepoOwner - args.RepoName = item.MainRepo.RepoName - // 3. create task with args - if _, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, permission.AnonymousUserID, false, log); err != nil { - log.Errorf("failed to create workflow task when receive push event %v due to %s ", event, err) - mErr = multierror.Append(mErr, err) - } else { - log.Infof("succeed to create task") - } - } - } - - return mErr.ErrorOrNil() -} - -func findIlyshinChangedFilesOfMergeRequest(event *ilyshin.MergeEvent, codehostID int) ([]string, error) { - detail, err := codehost.GetCodehostDetail(codehostID) - if err != nil { - return nil, fmt.Errorf("failed to find codehost %d: %s", codehostID, err) - } - - client := ilyshin.NewClient(detail.Address, detail.OauthToken) - return client.ListChangedFiles(event) -} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go deleted file mode 100644 index bea8048f..00000000 --- a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package webhook - -import ( - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/shared/codehost" - "github.com/koderover/zadig/pkg/tool/ilyshin" - "github.com/koderover/zadig/pkg/types" -) - -type ilyshinMergeRequestDiffFunc func(event *ilyshin.MergeEvent, id int) ([]string, error) - -type ilyshinMergeEventMatcher struct { - diffFunc ilyshinMergeRequestDiffFunc - log *zap.SugaredLogger - workflow *commonmodels.Workflow - event *ilyshin.MergeEvent -} - -func (gmem *ilyshinMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { - ev := gmem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { - if ev.ObjectAttributes.State == "opened" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v, err:%v", ev, err) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) - - return MatchChanges(hookRepo, changedFiles), nil - } - } - } - return false, nil -} - -func (gmem *ilyshinMergeEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, -) *commonmodels.WorkflowTaskArgs { - factory := &workflowArgsFactory{ - workflow: gmem.workflow, - reqID: requestID, - } - - args = factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - PR: gmem.event.ObjectAttributes.IID, - }) - - return args -} - -func createIlyshinEventMatcher( - event interface{}, diffSrv ilyshinMergeRequestDiffFunc, workflow *commonmodels.Workflow, log *zap.SugaredLogger, -) gitEventMatcher { - switch evt := event.(type) { - case *ilyshin.PushEvent: - return &ilyshinPushEventMatcher{ - workflow: workflow, - log: log, - event: evt, - } - case *ilyshin.MergeEvent: - return &ilyshinMergeEventMatcher{ - diffFunc: diffSrv, - log: log, - event: evt, - workflow: workflow, - } - } - - return nil -} - -type ilyshinPushEventMatcher struct { - log *zap.SugaredLogger - workflow *commonmodels.Workflow - event *ilyshin.PushEvent -} - -func (gpem *ilyshinPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { - ev := gpem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { - var changedFiles []string - - detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) - if err != nil { - gpem.log.Errorf("GetCodehostDetail error: %v", err) - return false, err - } - - client := ilyshin.NewClient(detail.Address, detail.OauthToken) - // compare接口获取两个commit之间的最终的改动 - diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) - if err != nil { - gpem.log.Errorf("Failed to get push event diffs, error: %v", err) - return false, err - } - for _, diff := range diffs { - changedFiles = append(changedFiles, diff.NewPath) - changedFiles = append(changedFiles, diff.OldPath) - } - - return MatchChanges(hookRepo, changedFiles), nil - } - } - - return false, nil -} - -func (gpem *ilyshinPushEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, -) *commonmodels.WorkflowTaskArgs { - factory := &workflowArgsFactory{ - workflow: gpem.workflow, - reqID: requestID, - } - - factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - }) - - return args -} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 98fba154..1434a232 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -18,7 +18,6 @@ package webhook import ( "context" - "encoding/base64" "errors" "fmt" "path" @@ -41,7 +40,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" githubtool "github.com/koderover/zadig/pkg/tool/git/github" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -108,18 +106,6 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar log.Errorf("Sync content from gitlab failed, error: %v", err) return err } - } else if args.Source == setting.SourceFromIlyshin { - // Set args.Commit - if err := syncIlyshinLatestCommit(args, log); err != nil { - log.Errorf("Sync change log from ilyshin failed, error: %v", err) - return err - } - // 从 ilyshin 同步 args 指定的 Commit 下,指定目录中对应的 Yaml 文件 - // Set args.Yaml & args.KubeYamls - if err := syncContentFromIlyshin(userName, args); err != nil { - log.Errorf("Sync content from ilyshin failed, error: %v", err) - return err - } } else if args.Source == setting.SourceFromGithub { err := syncContentFromGithub(args, log) if err != nil { @@ -186,32 +172,6 @@ func syncLatestCommit(service *commonmodels.Service) error { return nil } -func syncIlyshinLatestCommit(service *commonmodels.Service, log *zap.SugaredLogger) error { - if service.SrcPath == "" { - return fmt.Errorf("url不能是空的") - } - - address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(service.SrcPath) - if err != nil { - return fmt.Errorf("url 必须包含 owner/repo/tree/branch/path,具体请参考 Placeholder 提示") - } - - client, err := getIlyshinClientByAddress(address) - if err != nil { - return err - } - - commit, err := client.GetLatestCommit(owner, repo, branch, path, log) - if err != nil { - return err - } - service.Commit = &commonmodels.Commit{ - SHA: commit.ID, - Message: commit.Message, - } - return nil -} - func syncCodehubLatestCommit(service *commonmodels.Service) error { if service.SrcPath == "" { return fmt.Errorf("url不能是空的") @@ -253,21 +213,6 @@ func getCodehubClientByAddress(address string) (*codehub.Client, error) { return client, nil } -func getIlyshinClientByAddress(address string) (*ilyshin.Client, error) { - opt := &codehost.Option{ - Address: address, - CodeHostType: codehost.IlyshinProvider, - } - codehost, err := codehost.GetCodeHostInfo(opt) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") - } - client := ilyshin.NewClient(codehost.Address, codehost.AccessToken) - - return client, nil -} - func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { opt := &codehost.Option{ Address: address, @@ -384,82 +329,6 @@ func joinYamls(files []string) string { return strings.Join(files, setting.YamlFileSeperator) } -// IlyshinGetRawFiles ... -func IlyshinGetRawFiles(client *ilyshin.Client, owner, repo, ref, path, pathType string) (files []string, err error) { - files = make([]string, 0) - var errs *multierror.Error - if pathType == "tree" { - nodes, err := client.ListTree(owner, repo, ref, path) - if err != nil { - return files, err - } - for _, node := range nodes { - // if node type is "tree", it is a directory, skip it for now - if node.Type == "tree" { - continue - } - fileName := strings.ToLower(node.Name) - if !strings.HasSuffix(fileName, ".yaml") && !strings.HasSuffix(fileName, ".yml") { - continue - } - // if node type is "blob", it is a file - // Path is filepath of a node - content, err := client.GetRawFile(owner, repo, ref, node.Path) - if err != nil { - errs = multierror.Append(errs, err) - } - contentStr := string(content) - contentStr = util.ReplaceWrapLine(contentStr) - files = append(files, contentStr) - } - return files, errs.ErrorOrNil() - } - fileInfo, err := client.GetFile(owner, repo, ref, path) - if err != nil { - return files, err - } - decodedFile, err := base64.StdEncoding.DecodeString(fileInfo.Content) - if err != nil { - return files, err - } - files = append(files, string(decodedFile)) - return files, errs.ErrorOrNil() -} - -// syncContentFromIlyshin ... -// sync content with commit, args.Commit should not be nil -func syncContentFromIlyshin(userName string, args *commonmodels.Service) error { - if args.Commit == nil { - return nil - } - - address, owner, repo, branch, path, pathType, err := GetOwnerRepoBranchPath(args.SrcPath) - if err != nil { - return fmt.Errorf("url format failed") - } - - client, err := getIlyshinClientByAddress(address) - if err != nil { - return err - } - - files, err := IlyshinGetRawFiles(client, owner, repo, branch, path, pathType) - if err != nil { - return err - } - if userName != setting.WebhookTaskCreator { - if len(files) == 0 { - return fmt.Errorf("没有检索到yml,yaml类型文件,请检查目录是否正确") - } - } - // KubeYamls field is dynamicly synced. - // 根据gitlab sync的内容来设置args.KubeYamls - args.KubeYamls = files - // 拼装并设置args.Yaml - args.Yaml = joinYamls(files) - return nil -} - func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) error { // 根据pipeline中的filepath获取文件内容 address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(args.SrcPath) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go index 58fc471b..1d15a7d6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go @@ -28,7 +28,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) // RepoCommit : Repository commit struct @@ -85,23 +84,6 @@ func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) ( CreatedAt: &commitDate, Message: commit.Message, }, nil - } else if ch.Type == setting.SourceFromIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - branches, err := client.ListBranches(owner, name, log) - if err != nil { - return nil, err - } - for _, br := range branches { - if br.Name == branch { - return &RepoCommit{ - ID: br.Commit.ID, - Title: br.Commit.Title, - AuthorName: br.Commit.AuthorName, - CreatedAt: br.Commit.CreatedAt, - Message: br.Commit.Message, - }, nil - } - } } return nil, errors.New(ch.Type + "is not supported yet") @@ -152,23 +134,6 @@ func QueryByTag(id int, owner, name, tag string, log *zap.SugaredLogger) (*RepoC CreatedAt: &commitDate, Message: commit.Message, }, nil - } else if ch.Type == setting.SourceFromIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - tags, err := client.ListTags(owner, name, log) - if err != nil { - return nil, err - } - for _, tagInfo := range tags { - if tagInfo.Name == tag { - return &RepoCommit{ - ID: tagInfo.Commit.ID, - Title: tagInfo.Commit.Title, - AuthorName: tagInfo.Commit.AuthorName, - CreatedAt: tagInfo.Commit.CreatedAt, - Message: tagInfo.Commit.Message, - }, nil - } - } } return nil, errors.New(ch.Type + "is not supported yet") @@ -219,18 +184,6 @@ func GetLatestPrCommit(codehostID, pr int, namespace, projectName string, log *z CreatedAt: &tm, CheckoutRef: change.Revisions[change.CurrentRevision].Ref, }, nil - } else if ch.Type == setting.SourceFromIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - commit, err := client.GetLatestPRCommitList(projectID, pr, log) - if err != nil { - return nil, err - } - return &PRCommit{ - ID: commit.ID, - Title: commit.Title, - AuthorName: commit.AuthorName, - CreatedAt: commit.CreatedAt, - }, nil } return GetLatestPRCommitList(cli, projectID, pr) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 797cbd47..8eb8f220 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -313,7 +313,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { log.Errorf("failed to get codehost detail %d %v", build.CodehostID, err) return } - if codeHostInfo.Type == codehost.GitLabProvider || codeHostInfo.Type == codehost.GerritProvider || codeHostInfo.Type == codehost.IlyshinProvider { + if codeHostInfo.Type == codehost.GitLabProvider || codeHostInfo.Type == codehost.GerritProvider { if build.CommitID == "" { var commit *RepoCommit var pr *PRCommit diff --git a/pkg/microservice/reaper/core/service/meta/const.go b/pkg/microservice/reaper/core/service/meta/const.go index d7d8fb66..d0385d72 100644 --- a/pkg/microservice/reaper/core/service/meta/const.go +++ b/pkg/microservice/reaper/core/service/meta/const.go @@ -22,8 +22,6 @@ const ( // ProviderGitlab ... ProviderGitlab = "gitlab" - //ProviderIlyshin - ProviderIlyshin = "ilyshin" // ProviderGerrit ProviderGerrit = "gerrit" diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 1720cc78..4a587d48 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -396,7 +396,7 @@ func (g *Git) HTTPSCloneURL(source, token, owner, name string) string { // e.g. //https://oauth2:ACCESS_TOKEN@somegitlab.com/owner/name.git func (g *Git) OAuthCloneURL(source, token, address, owner, name, scheme string) string { - if strings.ToLower(source) == ProviderGitlab || strings.ToLower(source) == ProviderIlyshin { + if strings.ToLower(source) == ProviderGitlab { // address 需要传过来 return fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", scheme, OauthTokenPrefix, token, address, owner, name) } diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 4dbc6c0b..01fdeed9 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -100,8 +100,6 @@ func (r *Reaper) runGitCmds() error { } } else if repo.Source == meta.ProviderCodehub { tokens = append(tokens, repo.Password) - } else if repo.Source == meta.ProviderIlyshin { - cmds = append(cmds, &c.Command{Cmd: c.SetConfig("http.sslVerify", "false"), DisableTrace: true}) } tokens = append(tokens, repo.OauthToken) cmds = append(cmds, r.buildGitCommands(repo)...) @@ -172,7 +170,7 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { cmds = append(cmds, &c.Command{Cmd: c.RemoteRemove(repo.RemoteName), DisableTrace: true, IgnoreError: true}) } - if repo.Source == meta.ProviderGitlab || repo.Source == meta.ProviderIlyshin { + if repo.Source == meta.ProviderGitlab { u, _ := url.Parse(repo.Address) cmds = append(cmds, &c.Command{ Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, repo.Owner, repo.Name, u.Scheme)), diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index f1dd65c7..6b987381 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -214,8 +214,6 @@ const ( SourceFromGerrit = "gerrit" // SourceFromCodeHub 配置来源为codehub SourceFromCodeHub = "codehub" - // SourceFromIlyshin 配置来源为ilyshin - SourceFromIlyshin = "ilyshin" // SourceFromChartTemplate 配置来源为helmTemplate SourceFromChartTemplate = "chartTemplate" // SourceFromPublicRepo 配置来源为publicRepo diff --git a/pkg/shared/codehost/codehost.go b/pkg/shared/codehost/codehost.go index 67b8d5f7..a0876711 100644 --- a/pkg/shared/codehost/codehost.go +++ b/pkg/shared/codehost/codehost.go @@ -29,7 +29,6 @@ const ( GitHubProvider = "github" GerritProvider = "gerrit" CodeHubProvider = "codehub" - IlyshinProvider = "ilyshin" ) type CodeHost struct { -- Gitee From 85a804731e43fcc1311b05c86a74d74df90b90f2 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:39:27 +0800 Subject: [PATCH 0434/2647] init admin user (#502) * init admin user Signed-off-by: mouuii * init admin user Signed-off-by: mouuii * admin Signed-off-by: mouuii * admin Signed-off-by: mouuii * admin Signed-off-by: mouuii * xxx Signed-off-by: mouuii * test workflows Signed-off-by: mouuii * init admin user Signed-off-by: mouuii * init admin user Signed-off-by: mouuii * init admin user Signed-off-by: mouuii * init admin user Signed-off-by: mouuii * flag Signed-off-by: mouuii * flag Signed-off-by: mouuii * flag Signed-off-by: mouuii * flag Signed-off-by: mouuii * flag Signed-off-by: mouuii Co-authored-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 55 ++++++++++++++++++- pkg/config/config.go | 8 +++ .../policy/core/handler/role_binding.go | 15 +++++ .../policy/core/handler/router.go | 1 + .../policy/core/service/role_binding.go | 9 +++ pkg/setting/consts.go | 5 ++ pkg/shared/client/policy/policy.go | 6 ++ pkg/shared/client/user/user.go | 37 +++++++++++++ 8 files changed, 135 insertions(+), 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 60300e39..1ab3924c 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -18,13 +18,17 @@ package cmd import ( _ "embed" + "fmt" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/shared/client/user" "github.com/koderover/zadig/pkg/tool/log" ) @@ -60,7 +64,56 @@ func run() error { } func initSystemConfig() error { - return presetRole() + email := config.AdminEmail() + password := config.AdminPassword() + + uid, err := presetSystemAdmin(email, password) + if err != nil { + log.Errorf("presetSystemAdmin err:%s", err) + return err + } + if err := presetRole(); err != nil { + log.Errorf("presetRole err:%s", err) + return err + } + + if err := presetRoleBinding(uid); err != nil { + log.Errorf("presetRoleBinding :%s", err) + return err + } + return nil +} + +func presetSystemAdmin(email string, password string) (string, error) { + r, err := user.New().SearchUser(&user.SearchUserArgs{Account: setting.PresetAccount}) + if err != nil { + log.Errorf("SearchUser err:%s", err) + return "", err + } + if len(r.Users) > 0 { + log.Infof("User admin exists, skip it.") + return r.Users[0].UID, nil + } + user, err := user.New().CreateUser(&user.CreateUserArgs{ + Name: setting.PresetAccount, + Password: password, + Account: setting.PresetAccount, + Email: email, + }) + if err != nil { + log.Infof("created admin err:%s", err) + return "", err + } + return user.Uid, nil +} + +func presetRoleBinding(uid string) error { + return policy.NewDefault().CreateOrUpdateSystemRoleBinding(&policy.RoleBinding{ + Name: fmt.Sprintf(setting.RoleBindingNameFmt, setting.RoleAdmin, setting.PresetAccount, ""), + UID: uid, + Role: setting.RoleAdmin, + }) + } func presetRole() error { diff --git a/pkg/config/config.go b/pkg/config/config.go index 7dfa9703..42de9dce 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -258,3 +258,11 @@ func MysqlPassword() string { func MysqlHost() string { return viper.GetString(setting.ENVMysqlHost) } + +func AdminEmail() string { + return viper.GetString(setting.ENVAdminEmail) +} + +func AdminPassword() string { + return viper.GetString(setting.ENVAdminPassword) +} diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index c14d37ff..d33d7b8f 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -141,6 +141,21 @@ func CreateSystemRoleBinding(c *gin.Context) { ctx.Err = service.CreateRoleBindings(service.SystemScope, []*service.RoleBinding{args}, ctx.Logger) } +func CreateOrUpdateSystemRoleBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.RoleBinding{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + args.Public = false + + ctx.Err = service.CreateOrUpdateSystemRoleBinding(service.SystemScope, args, ctx.Logger) +} + func ListSystemRoleBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 1ddaebbb..69da8b61 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -66,6 +66,7 @@ func (*Router) Inject(router *gin.RouterGroup) { systemRoleBindings.POST("", CreateSystemRoleBinding) systemRoleBindings.GET("", ListSystemRoleBindings) systemRoleBindings.DELETE("/:name", DeleteSystemRoleBinding) + systemRoleBindings.PUT("/:name", CreateOrUpdateSystemRoleBinding) } userBindings := router.Group("userbindings") diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index c08412c0..c3509eb4 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -48,6 +48,15 @@ func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger return mongodb.NewRoleBindingColl().BulkCreate(objs) } +func CreateOrUpdateSystemRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { + + obj, err := createRoleBindingObject(ns, rb, logger) + if err != nil { + return err + } + return mongodb.NewRoleBindingColl().UpdateOrCreate(obj) +} + func UpdateOrCreateRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { obj, err := createRoleBindingObject(ns, rb, logger) if err != nil { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 3836cd53..19815828 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -112,6 +112,11 @@ const ( ENVMysqlUserDB = "MYSQL_USER_DB" ENVScopes = "SCOPES" ENVTokenExpiresAt = "TOKEN_EXPIRES_AT" + + // initconfig + ENVAdminEmail = "ADMIN_EMAIL" + ENVAdminPassword = "ADMIN_PASSWORD" + PresetAccount = "admin" ) // k8s concepts diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 23cdbe5f..ae27d619 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -55,6 +55,12 @@ func (c *Client) CreateOrUpdateRoleBinding(projectName string, roleBinding *Role return err } +func (c *Client) CreateOrUpdateSystemRoleBinding(roleBinding *RoleBinding) error { + url := fmt.Sprintf("/system-rolebindings/%s", roleBinding.Name) + _, err := c.Put(url, httpclient.SetBody(roleBinding)) + return err +} + func (c *Client) DeleteRoleBinding(name string, projectName string) error { url := fmt.Sprintf("/rolebindings/%s?projectName=%s", name, projectName) _, err := c.Delete(url) diff --git a/pkg/shared/client/user/user.go b/pkg/shared/client/user/user.go index bcec5568..c080f6d2 100644 --- a/pkg/shared/client/user/user.go +++ b/pkg/shared/client/user/user.go @@ -30,3 +30,40 @@ func (c *Client) ListUsers(args *SearchArgs) ([]*User, error) { return res.Users, err } + +type CreateUserArgs struct { + Name string `json:"name"` + Password string `json:"password"` + Email string `json:"email"` + Phone string `json:"phone"` + Account string `json:"account"` +} + +type CreateUserResp struct { + Name string `json:"name"` + Account string `json:"account"` + Uid string `json:"uid"` +} + +func (c *Client) CreateUser(args *CreateUserArgs) (*CreateUserResp, error) { + url := "/users" + resp := &CreateUserResp{} + _, err := c.Post(url, httpclient.SetBody(args), httpclient.SetResult(resp)) + return resp, err +} + +type SearchUserArgs struct { + Account string `json:"account"` +} + +type SearchUserResp struct { + TotalCount int `json:"totalCount"` + Users []*User `json:"users"` +} + +func (c *Client) SearchUser(args *SearchUserArgs) (*SearchUserResp, error) { + url := "/users/search" + resp := &SearchUserResp{} + _, err := c.Post(url, httpclient.SetBody(args), httpclient.SetResult(resp)) + return resp, err +} -- Gitee From 8954251bb7dc640acfc27cf2171d5a07b1f15b55 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 2 Nov 2021 16:39:37 +0800 Subject: [PATCH 0435/2647] public Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/template/product.go | 1 + pkg/microservice/aslan/core/project/service/project.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 98cd277b..85513537 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -38,6 +38,7 @@ type ProjectInfo struct { UpdatedAt int64 `bson:"update_time"` UpdatedBy string `bson:"update_by"` OnboardStatus int `bson:"onboarding_status"` + Public bool `bson:"public"` } type ProductColl struct { diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 046e45e2..31f29039 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -47,6 +47,7 @@ type ProjectDetailedRepresentation struct { UpdatedAt int64 `json:"updatedAt"` UpdatedBy string `json:"updatedBy"` Onboard bool `json:"onboard"` + Public bool `json:"public"` } type ProjectBriefRepresentation struct { @@ -111,6 +112,7 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge UpdatedAt: info.UpdatedAt, UpdatedBy: info.UpdatedBy, Onboard: info.OnboardStatus != 0, + Public: info.Public, }) } -- Gitee From 0dbb87033fa8cfd502c8eb4920da73b0d0f8a8fb Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 2 Nov 2021 16:54:00 +0800 Subject: [PATCH 0436/2647] public Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/template/product.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 85513537..ec911ff7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -105,6 +105,7 @@ func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error {"update_time", 1}, {"update_by", 1}, {"onboarding_status", 1}, + {"public", 1}, }) } -- Gitee From df7ebf814d0568ee1d9f1fe71d84d5b938d751e6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 18:24:18 +0800 Subject: [PATCH 0437/2647] remove code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index ed988c04..b7775823 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -642,7 +642,6 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { setting.SourceFromGithub, setting.SourceFromGerrit, setting.SourceFromCodeHub, - setting.SourceFromIlyshin: return &models.CreateFromRepo{ GitRepoConfig: &templatemodels.GitRepoConfig{ CodehostID: args.CodehostID, -- Gitee From aefa981a1363ed26f9ca69580ae435e67bc2b758 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 18:58:23 +0800 Subject: [PATCH 0438/2647] remove ilyshin code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index b7775823..a1bb800f 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -641,7 +641,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { case setting.SourceFromGitlab, setting.SourceFromGithub, setting.SourceFromGerrit, - setting.SourceFromCodeHub, + setting.SourceFromCodeHub: return &models.CreateFromRepo{ GitRepoConfig: &templatemodels.GitRepoConfig{ CodehostID: args.CodehostID, -- Gitee From 107ebfe40eb4f6b792d34376f94f5de9a9d30235 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 19:25:27 +0800 Subject: [PATCH 0439/2647] fix spell errors Signed-off-by: allenshen --- .../core/environment/service/environment.go | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 756cb8d7..d03c99c9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2620,7 +2620,6 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { var ( err error - wg sync.WaitGroup errList = &multierror.Error{} ) @@ -2647,43 +2646,51 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel return } }() + chartInfoMap := make(map[string]*template.RenderChart) for _, renderChart := range args.ChartInfos { chartInfoMap[renderChart.ServiceName] = renderChart } + + serviceList := make([]interface{}, 0) + handler := func(data interface{}, logger *zap.SugaredLogger) error { + service := data.(*commonmodels.ProductService) + // 获取服务详情 + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: args.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + return err + } + + renderChart := chartInfoMap[service.ServiceName] + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, helmClient) + if err != nil { + return err + } + return nil + } + for _, serviceGroups := range args.Services { for _, svc := range serviceGroups { - renderChart, ok := chartInfoMap[svc.ServiceName] + _, ok := chartInfoMap[svc.ServiceName] if !ok { continue } - - wg.Add(1) - go func(service *commonmodels.ProductService) { - defer wg.Done() - - // 获取服务详情 - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: args.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - return - } - - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, Timeout*time.Second*10, helmClient) - if err != nil { - errList = multierror.Append(errList, err) - return - } - }(svc) + serviceList = append(serviceList, svc) } } - wg.Wait() + + serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if serviceGroupErr != nil { + errList = multierror.Append(errList, serviceGroupErr...) + } + err = errList.ErrorOrNil() } @@ -2844,13 +2851,13 @@ func updateProductGroup(username, productName, envName, updateType string, produ } serviceObj, err := commonrepo.NewServiceColl().Find(opt) if err != nil { - log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) + log.Errorf("failed to find service with opt %+v, err: %s", opt, err) return errors.Wrapf(err, "failed to find template servce %s", service.ServiceName) } - renderChart, _ := renderChartMap[service.ServiceName] + renderChart := renderChartMap[service.ServiceName] err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) if err != nil { - return errors.Wrapf(err, "failed to install of upgrade service %s", service.ServiceName) + return errors.Wrapf(err, "failed to install or upgrade service %s", service.ServiceName) } return nil } @@ -3080,7 +3087,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel handler := func(data interface{}, log *zap.SugaredLogger) error { service := data.(*commonmodels.Service) - renderChart, _ := renderChartMap[service.ServiceName] + renderChart := renderChartMap[service.ServiceName] err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, helmClient) if err != nil { return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) -- Gitee From a90b75293a4db3835aa8e32374079706733c799f Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 20:04:25 +0800 Subject: [PATCH 0440/2647] update import url Signed-off-by: allenshen --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d0a3241b..4b6df8ce 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -19,12 +19,12 @@ package service import ( "bytes" "fmt" - "html/template" "io/fs" "os" "path/filepath" "strings" "sync" + "text/template" "time" "github.com/27149chen/afero" -- Gitee From 857d2adc491bda1de2573ffd4c23f0b6e795ffa1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 20:53:04 +0800 Subject: [PATCH 0441/2647] remove targer dir bofore copy Signed-off-by: allenshen --- pkg/microservice/aslan/core/service/service/helm.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index ed988c04..3f23d199 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -266,6 +266,11 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe // copy template to service path and update the values.yaml from := filepath.Join(localBase, base) to := filepath.Join(config.LocalServicePath(projectName, args.Name), args.Name) + // remove old files + if err = os.RemoveAll(to); err != nil { + logger.Errorf("Failed to remove dir to %s, err: %s", to, err) + return err + } if err = copy.Copy(from, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", from, to, err) return err @@ -564,6 +569,11 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi serviceName = strings.TrimSuffix(serviceName, filepath.Ext(serviceName)) to := filepath.Join(config.LocalServicePath(projectName, serviceName), serviceName) + // remove old files + if err = os.RemoveAll(to); err != nil { + logger.Errorf("Failed to remove dir to %s, err: %s", to, err) + return nil, err + } if err = copy.Copy(fromPath, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", fromPath, to, err) return nil, err -- Gitee From 584129ab6b3c574030d9150c0140fc845cfaaf6f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 2 Nov 2021 20:57:37 +0800 Subject: [PATCH 0442/2647] optimize code Signed-off-by: liu deyi --- .../core/common/repository/models/template/product.go | 5 ----- .../core/common/repository/mongodb/template/product.go | 8 +++++++- pkg/microservice/aslan/core/project/handler/product.go | 4 ++-- pkg/microservice/aslan/core/project/handler/router.go | 2 +- pkg/microservice/aslan/core/project/service/product.go | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 101e1c63..69bc08d9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -74,11 +74,6 @@ type Product struct { CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` } -type ProductArgs struct { - ProductName string `json:"product_name"` - Services [][]string `json:"services"` - UpdateBy string `json:"update_by"` -} type ServiceInfo struct { Name string `bson:"name" json:"name"` Owner string `bson:"owner" json:"owner"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index b1d42a47..5f0dbd15 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -215,8 +215,14 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { return err } +type ProductArgs struct { + ProductName string `json:"product_name"` + Services [][]string `json:"services"` + UpdateBy string `json:"update_by"` +} + // UpdateServiceOrder existing ProductTmpl -func (c *ProductColl) UpdateServiceOrder(args *template.ProductArgs) error { +func (c *ProductColl) UpdateServiceOrder(args *ProductArgs) error { // avoid panic issue if args == nil { return errors.New("nil product args") diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 82c17e49..a84cc30d 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -122,7 +122,7 @@ type updateServiceOrderArgs struct { Services [][]string `json:"services"` } -func UpdateServiceOrder(c *gin.Context) { +func UpdateServiceOrchestration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -132,7 +132,7 @@ func UpdateServiceOrder(c *gin.Context) { return } - ctx.Err = projectservice.UpdateServiceOrder(ctx.User.Name, c.Param("name"), args.Services, ctx.Logger) + ctx.Err = projectservice.UpdateServiceOrchestration(ctx.User.Name, c.Param("name"), args.Services, ctx.Logger) } func UpdateProject(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index c81d1461..1d5c8ea7 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -48,7 +48,7 @@ func (*Router) Inject(router *gin.RouterGroup) { product.POST("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.IsHavePermission([]string{permission.ServiceTemplateEditUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) - product.PUT("/:name/serviceOrder", gin2.UpdateOperationLogStatus, UpdateServiceOrder) + product.PATCH("/:name", gin2.UpdateOperationLogStatus, UpdateServiceOrchestration) product.PUT("", gin2.StoreProductName, gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ContextKeyType), gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.IsHavePermission([]string{permission.SuperUserUUID}, permission.ParamType), gin2.UpdateOperationLogStatus, DeleteProductTemplate) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 1b31d94e..6c605bdb 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -401,7 +401,7 @@ func UpdateProductTmplStatus(productName, onboardingStatus string, log *zap.Suga return nil } -func UpdateServiceOrder(username, name string, services [][]string, log *zap.SugaredLogger) error { +func UpdateServiceOrchestration(username, name string, services [][]string, log *zap.SugaredLogger) error { if err := templaterepo.NewProductColl().UpdateServiceOrder(&template.ProductArgs{ ProductName: name, Services: services, -- Gitee From 38596adb99bcdb603af78b5fa5d6121f41d5871a Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 2 Nov 2021 21:03:05 +0800 Subject: [PATCH 0443/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/service/service/helm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 3f23d199..e528464e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -268,7 +268,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe to := filepath.Join(config.LocalServicePath(projectName, args.Name), args.Name) // remove old files if err = os.RemoveAll(to); err != nil { - logger.Errorf("Failed to remove dir to %s, err: %s", to, err) + logger.Errorf("Failed to remove dir %s, err: %s", to, err) return err } if err = copy.Copy(from, to); err != nil { @@ -571,7 +571,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi to := filepath.Join(config.LocalServicePath(projectName, serviceName), serviceName) // remove old files if err = os.RemoveAll(to); err != nil { - logger.Errorf("Failed to remove dir to %s, err: %s", to, err) + logger.Errorf("Failed to remove dir %s, err: %s", to, err) return nil, err } if err = copy.Copy(fromPath, to); err != nil { -- Gitee From 263fa058db4522ba3a61d1ea98cb9ede29818e9e Mon Sep 17 00:00:00 2001 From: dyliu Date: Wed, 3 Nov 2021 10:38:18 +0800 Subject: [PATCH 0444/2647] Modify code reference (#521) Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/service/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 6c605bdb..5e00738e 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -402,7 +402,7 @@ func UpdateProductTmplStatus(productName, onboardingStatus string, log *zap.Suga } func UpdateServiceOrchestration(username, name string, services [][]string, log *zap.SugaredLogger) error { - if err := templaterepo.NewProductColl().UpdateServiceOrder(&template.ProductArgs{ + if err := templaterepo.NewProductColl().UpdateServiceOrder(&templaterepo.ProductArgs{ ProductName: name, Services: services, UpdateBy: username, -- Gitee From 48230ae873381e77625cc0347d3f2026b0882df1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 3 Nov 2021 10:57:19 +0800 Subject: [PATCH 0445/2647] update variable replace logic Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 4 +-- .../aslan/core/templatestore/service/chart.go | 29 +++++++++++++++++-- pkg/setting/consts.go | 2 ++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 4b6df8ce..6204232b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -686,14 +686,14 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { } func renderVariablesToYaml(valuesYaml string, productName, serviceName string, variables []*Variable) (string, error) { - valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableProduct, productName, -1) - valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableService, serviceName, -1) // build replace data valuesMap := make(map[string]interface{}) for _, variable := range variables { valuesMap[variable.Key] = variable.Value } + valuesMap[setting.PresetTemplateVariableProduct] = productName + valuesMap[setting.PresetTemplateVariableService] = serviceName tmpl, err := template.New("values").Parse(valuesYaml) if err != nil { diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 526f10ca..6b14cdd4 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -41,6 +41,11 @@ var ( variableExtractRegexp = regexp.MustCompile("{{.(\\w*)}}") ) +var ChartTemplateDefaultSystemVariable = map[string]string{ + setting.PresetTemplateVariableProduct: setting.TemplateVariableProductDescription, + setting.PresetTemplateVariableService: setting.TemplateVariableServiceDescription, +} + type ChartTemplateListResp struct { SystemVariables []*Variable `json:"systemVariables"` ChartTemplates []*Chart `json:"chartTemplates"` @@ -104,6 +109,22 @@ func GetChartTemplateVariables(name string, logger *zap.SugaredLogger) ([]*Varia return variables, nil } +func getChartTemplateDefaultVariables() []*Variable { + resp := make([]*Variable, 0) + for key, description := range ChartTemplateDefaultSystemVariable { + resp = append(resp, &Variable{ + Key: key, + Description: description, + }) + } + return resp +} + +func isPresetVariable(key string) bool { + _, ok := ChartTemplateDefaultSystemVariable[key] + return ok +} + func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, error) { cs, err := mongodb.NewChartColl().List() if err != nil { @@ -124,7 +145,7 @@ func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, erro } ret := &ChartTemplateListResp{ - SystemVariables: GetSystemDefaultVariables(), + SystemVariables: getChartTemplateDefaultVariables(), ChartTemplates: res, } @@ -170,7 +191,11 @@ func parseTemplateVariables(name, path string, logger *zap.SugaredLogger) ([]str if len(match) < 2 { continue } - strSet.Insert(match[1:]...) + for _, key := range match[1:] { + if !isPresetVariable(key) { + strSet.Insert(key) + } + } } return strSet.List(), nil } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index f1dd65c7..3e60c4d5 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -505,8 +505,10 @@ const ( // template common constant const ( TemplateVariableProduct = "$T-Project$" + PresetTemplateVariableProduct = "PROJECTNAME" TemplateVariableProductDescription = "项目名称" TemplateVariableService = "$T-Service$" + PresetTemplateVariableService = "SERVICENAME" TemplateVariableServiceDescription = "服务名称" ) -- Gitee From 4bcc4fe664501c4dd2b2f9de9276b504869132d1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 3 Nov 2021 11:06:03 +0800 Subject: [PATCH 0446/2647] update variable replace logic Signed-off-by: allenshen --- .../aslan/core/templatestore/service/chart.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 6b14cdd4..ae475dbe 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -38,14 +38,13 @@ import ( ) var ( - variableExtractRegexp = regexp.MustCompile("{{.(\\w*)}}") + variableExtractRegexp = regexp.MustCompile("{{.(\\w*)}}") + ChartTemplateDefaultSystemVariable = map[string]string{ + setting.PresetTemplateVariableProduct: setting.TemplateVariableProductDescription, + setting.PresetTemplateVariableService: setting.TemplateVariableServiceDescription, + } ) -var ChartTemplateDefaultSystemVariable = map[string]string{ - setting.PresetTemplateVariableProduct: setting.TemplateVariableProductDescription, - setting.PresetTemplateVariableService: setting.TemplateVariableServiceDescription, -} - type ChartTemplateListResp struct { SystemVariables []*Variable `json:"systemVariables"` ChartTemplates []*Chart `json:"chartTemplates"` -- Gitee From f79b9cea290d62ca7bfe011458e3b3759b082a5b Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 3 Nov 2021 11:14:14 +0800 Subject: [PATCH 0447/2647] merge main to nga (#520) * add product name field Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * update replace image logic Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * fix compile error Signed-off-by: allenshen * remove useless code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optimze code Signed-off-by: allenshen * optize err print format Signed-off-by: allenshen * optimize code Signed-off-by: liu deyi * build add productName field Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * optmize code Signed-off-by: allenshen * workflow add storage_id field Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * opmize code Signed-off-by: allenshen * fix share service bug Signed-off-by: liu deyi * fix compile error Signed-off-by: allenshen * fix create env bug Signed-off-by: liu deyi * fix create env bug Signed-off-by: liu deyi * fix create env bug Signed-off-by: liu deyi * fix service deploy bug Signed-off-by: liu deyi * fix build bug Signed-off-by: liu deyi * fix tar file rule bug Signed-off-by: liu deyi * modify reaper Signed-off-by: liu deyi * modify reaper Signed-off-by: liu deyi * modify reaper Signed-off-by: liu deyi * fix loop bug Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * add environment variable logic Signed-off-by: allenshen * modify list tar interface Signed-off-by: liu deyi * annotating useless code Signed-off-by: allenshen * debug code Signed-off-by: liu deyi * modify artifact interface Signed-off-by: liu deyi * modify artifact interface Signed-off-by: liu deyi * add batch insert cvm host interface Signed-off-by: liu deyi * fix nil pointer Signed-off-by: lou * fix bug and change value merge orders Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * validate bulk add privatekey function Signed-off-by: liu deyi * add labels interface Signed-off-by: liu deyi * modify bulk add priviateKey interface Signed-off-by: liu deyi * add host label by label Signed-off-by: liu deyi * update workflow logic Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * added dockerfile template api & basic implementations Signed-off-by: Min Min * debug Signed-off-by: Min Min * minor bugfixes Signed-off-by: Min Min * added dockerfile parser validation Signed-off-by: Min Min * validation stuff Signed-off-by: Min Min * fix broken dependency Signed-off-by: Min Min * added dockerfile template to build Signed-off-by: Min Min * add validation API for dockerfile template Signed-off-by: Min Min * changed validation api response Signed-off-by: Min Min * get reference api fixed Signed-off-by: Min Min * added dockerfile template function to workflow Signed-off-by: Min Min * added auth token for reaper Signed-off-by: Min Min * reaper bugfix Signed-off-by: Min Min * added some debug information Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * removed debug logs & bugfix Signed-off-by: Min Min * add debug logs to prevent nil Signed-off-by: Min Min * removed logs & finalize Signed-off-by: Min Min * set name of dockerfile template as unique Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * debug Signed-off-by: Min Min * debug on missing arg Signed-off-by: Min Min * dockerfile template Signed-off-by: Min Min * debug Signed-off-by: Min Min * modify list host ip interface Signed-off-by: liu deyi * change docker build command generate Signed-off-by: Min Min * debug Signed-off-by: Min Min * update the provider of privatekey Signed-off-by: liu deyi * Added _COMMIT_ID as workflow default parameter Signed-off-by: Min Min * update env config Signed-off-by: liu deyi * update env config Signed-off-by: liu deyi * CRUD for yaml template done Signed-off-by: Min Min * added create service from yaml template and reload service from yaml template Signed-off-by: Min Min * rebasing dockerfile_template Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * debug replace yaml and webhook process logic Signed-off-by: Min Min * added get reference API Signed-off-by: Min Min * debug Signed-off-by: Min Min * Make template that is in-use not able to be deleted Signed-off-by: Min Min * optimize code Signed-off-by: liu deyi * changed update logic Signed-off-by: Min Min * filter the case where the label is empty Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * update env yaml replace logic Signed-off-by: allenshen * added update yaml template varible values API Signed-off-by: Min Min * debugs Signed-off-by: Min Min * debugs Signed-off-by: Min Min * changed query reference logic Signed-off-by: Min Min * update preview values.yaml logic Signed-off-by: allenshen * changed logic for loading service from template and list yaml templates api response Signed-off-by: Min Min * fix panic error Signed-off-by: allenshen * service module updates Signed-off-by: Min Min * add ingress display to service list Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * naming stuff Signed-off-by: Min Min * changed location Signed-off-by: Min Min * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * Modify the way to get the service Signed-off-by: liu deyi * debug coe Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * add Bootcamps info to readme Signed-off-by: landylee007 * add Bootcamps info to readme Signed-off-by: landylee007 * Update server.go * Update types.go * Update action.yml * add Bootcamps info to readme Signed-off-by: landylee007 * modify the number of pr list Signed-off-by: liu deyi * optimize create service logic Signed-off-by: allenshen * optmize code Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * modify error message Signed-off-by: liu deyi * fix file download failure problem Signed-off-by: liu deyi * fix file download failure problem Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove code Signed-off-by: liu deyi * remove code Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * fix artifact download bug Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * rename function name Signed-off-by: liu deyi * return err Signed-off-by: liu deyi * modify log download logic Signed-off-by: liu deyi * support the same service import Signed-off-by: liu deyi * optimize db index Signed-off-by: liu deyi * optimize host env logic Signed-off-by: liu deyi * optimize list service logic Signed-off-by: liu deyi * optimize list service logic Signed-off-by: liu deyi * optimize list service logic Signed-off-by: liu deyi * optimize workflow task logic Signed-off-by: liu deyi * fix bug Signed-off-by: allenshen * optimize update project logic Signed-off-by: liu deyi * optmize temp code Signed-off-by: allenshen * optimize update service logic Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * optimize update service logic Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * add debug log Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * only the currently existing services are returned in the project list Signed-off-by: liu deyi * only the currently existing services are returned in the project list Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * only the currently existing services are returned in the project list Signed-off-by: liu deyi * fix bind query error Signed-off-by: allenshen * remove blank Signed-off-by: liu deyi * Modify the primary key of the database Signed-off-by: liu deyi * fix service module bug Signed-off-by: allenshen * add function annotation Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * fix index out of range bug Signed-off-by: liu deyi * resolve comments Signed-off-by: allenshen * Support to modify the order of helm services Signed-off-by: liu deyi * fix spell error Signed-off-by: allenshen * modify the helm service arrangement Signed-off-by: liu deyi * add error msg return Signed-off-by: liu deyi * remove ilyshin code Signed-off-by: liu deyi * remove permission code Signed-off-by: liu deyi * optimize service update logic Signed-off-by: allenshen * remove ilyshin code Signed-off-by: liu deyi * remove code Signed-off-by: liu deyi * remove ilyshin code Signed-off-by: liu deyi * fix spell errors Signed-off-by: allenshen * remove targer dir bofore copy Signed-off-by: allenshen * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: allenshen * fix conflict Signed-off-by: lou * fix conflict Signed-off-by: lou Co-authored-by: liu deyi Co-authored-by: allenshen Co-authored-by: lilianzhu Co-authored-by: Min Min Co-authored-by: Landy Co-authored-by: Landy --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 2 +- .../aslan/core/code/service/branch.go | 8 - .../aslan/core/code/service/merge_request.go | 9 - .../aslan/core/code/service/namespace.go | 9 - .../aslan/core/code/service/project.go | 9 - .../aslan/core/code/service/tag.go | 8 - .../aslan/core/code/service/types.go | 51 -- .../common/repository/models/render_set.go | 19 +- .../models/services_in_external_env.go | 13 + .../repository/models/template/product.go | 21 +- .../core/common/repository/mongodb/service.go | 77 ++- .../mongodb/services_in_external_env.go | 139 ++++ .../repository/mongodb/template/product.go | 24 + .../aslan/core/common/service/environment.go | 13 +- .../core/common/service/ilyshin/client.go | 32 - .../core/common/service/ilyshin/webhook.go | 38 -- .../aslan/core/common/service/product.go | 38 +- .../aslan/core/common/service/render.go | 60 +- .../aslan/core/common/service/service.go | 22 + .../core/common/service/template_product.go | 8 + .../aslan/core/common/service/utils.go | 2 +- .../core/common/service/webhook/controller.go | 5 - .../aslan/core/common/service/workflow.go | 4 +- .../core/environment/handler/environment.go | 138 ++-- .../core/environment/handler/renderset.go | 37 +- .../aslan/core/environment/handler/router.go | 14 +- .../core/environment/service/environment.go | 606 +++++++++++------- .../service/environment_creator.go | 2 +- .../aslan/core/environment/service/image.go | 2 +- .../core/environment/service/renderset.go | 211 +++--- .../aslan/core/project/handler/router.go | 2 +- .../aslan/core/project/service/product.go | 7 +- pkg/microservice/aslan/core/service.go | 4 + .../aslan/core/service/service/helm.go | 130 ++-- .../aslan/core/service/service/loader.go | 256 -------- .../aslan/core/service/service/service.go | 101 ++- .../aslan/core/service/service/types.go | 9 +- .../aslan/core/workflow/handler/webhook.go | 3 - .../aslan/core/workflow/handler/workflow.go | 2 +- .../core/workflow/service/webhook/ilyshin.go | 290 --------- .../service/webhook/ilyshin_workflow_task.go | 152 ----- .../core/workflow/service/webhook/utils.go | 131 ---- .../core/workflow/service/workflow/gitlab.go | 47 -- .../service/workflow/pipeline_validation.go | 2 +- .../workflow/service/workflow/workflow.go | 88 ++- .../service/workflow/workflow_task.go | 25 + .../reaper/core/service/meta/const.go | 2 - .../reaper/core/service/meta/types.go | 2 +- .../reaper/core/service/reaper/git.go | 4 +- .../core/service/taskplugin/deploy.go | 14 +- .../core/service/types/environment.go | 23 +- .../warpdrive/core/service/types/renderset.go | 19 +- pkg/setting/consts.go | 2 - pkg/shared/codehost/codehost.go | 1 - pkg/tool/helmclient/helmclient.go | 20 +- pkg/tool/ilyshin/branch.go | 57 -- pkg/tool/ilyshin/client.go | 43 -- pkg/tool/ilyshin/commit.go | 61 -- pkg/tool/ilyshin/event_parsing.go | 179 ------ pkg/tool/ilyshin/group_project.go | 45 -- pkg/tool/ilyshin/merge_request.go | 115 ---- pkg/tool/ilyshin/project.go | 80 --- pkg/tool/ilyshin/repo.go | 122 ---- pkg/tool/ilyshin/tag.go | 53 -- pkg/tool/ilyshin/webhook.go | 100 --- 65 files changed, 1141 insertions(+), 2671 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go delete mode 100644 pkg/microservice/aslan/core/common/service/ilyshin/client.go delete mode 100644 pkg/microservice/aslan/core/common/service/ilyshin/webhook.go delete mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go delete mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go delete mode 100644 pkg/tool/ilyshin/branch.go delete mode 100644 pkg/tool/ilyshin/client.go delete mode 100644 pkg/tool/ilyshin/commit.go delete mode 100644 pkg/tool/ilyshin/event_parsing.go delete mode 100644 pkg/tool/ilyshin/group_project.go delete mode 100644 pkg/tool/ilyshin/merge_request.go delete mode 100644 pkg/tool/ilyshin/project.go delete mode 100644 pkg/tool/ilyshin/repo.go delete mode 100644 pkg/tool/ilyshin/tag.go delete mode 100644 pkg/tool/ilyshin/webhook.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index 4d23841a..a8ea77f0 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -151,5 +151,5 @@ func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[ } return nil } - return fmt.Errorf("faild to find image for contianer %s", container.Image) + return fmt.Errorf("failed to find image for contianer %s", container.Image) } diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index b11b3344..3945aa41 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListBranches(codeHostID int, projectName, namespace string, log *zap.SugaredLogger) ([]*Branch, error) { @@ -52,13 +51,6 @@ func CodeHostListBranches(codeHostID int, projectName, namespace string, log *za return nil, err } return ToBranches(brList), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - brList, err := client.ListBranches(namespace, projectName, log) - if err != nil { - return nil, err - } - return ToBranches(brList), nil } else if ch.Type == gerrit.CodehostTypeGerrit { cli := gerrit.NewClient(ch.Address, ch.AccessToken) branches, err := cli.ListBranches(projectName) diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index fd5ca554..4f0d78e0 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, log *zap.SugaredLogger) ([]*PullRequest, error) { @@ -55,14 +54,6 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo return ToPullRequests(prs), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - prs, err := client.ListOpenedProjectMergeRequests(namespace, projectName, targetBr, log) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) - } - return ToPullRequests(prs), nil } else if ch.Type == gerrit.CodehostTypeGerrit { return nil, nil } else if ch.Type == CodeHostCodeHub { diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 68eb68ec..5b5923de 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) const ( @@ -39,7 +38,6 @@ const ( page = 1 perPage = 100 CodeHostCodeHub = "codehub" - CodeHostIlyshin = "ilyshin" ) func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*Namespace, error) { @@ -63,13 +61,6 @@ func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogg return nil, err } return ToNamespaces(nsList), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - nsList, err := client.ListNamespaces(keyword, log) - if err != nil { - return nil, err - } - return ToNamespaces(nsList), nil } else if ch.Type == gerrit.CodehostTypeGerrit { return []*Namespace{{ Name: gerrit.DefaultNamespace, diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 1c724afb..38103faf 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword string, log *zap.SugaredLogger) ([]*Project, error) { @@ -64,14 +63,6 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword stri } return ToProjects(projects), nil - } else if ch.Type == CodeHostIlyshin { - cli := ilyshin.NewClient(ch.Address, ch.AccessToken) - projects, err := cli.ListGroupProjects(namespace, keyword, log) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - return ToProjects(projects), nil } else if ch.Type == gerrit.CodehostTypeGerrit { cli := gerrit.NewClient(ch.Address, ch.AccessToken) projects, err := cli.ListProjectsByKey(keyword) diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 8c3edcaa..90dd3cc3 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -28,7 +28,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) func CodeHostListTags(codeHostID int, projectName string, namespace string, log *zap.SugaredLogger) ([]*Tag, error) { @@ -53,13 +52,6 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log return nil, err } return ToTags(tags), nil - } else if ch.Type == CodeHostIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - tags, err := client.ListTags(namespace, projectName, log) - if err != nil { - return nil, err - } - return ToTags(tags), nil } else if ch.Type == gerrit.CodehostTypeGerrit { client := gerrit.NewClient(ch.Address, ch.AccessToken) tags, err := client.ListTags(projectName) diff --git a/pkg/microservice/aslan/core/code/service/types.go b/pkg/microservice/aslan/core/code/service/types.go index 722c415a..81528aaa 100644 --- a/pkg/microservice/aslan/core/code/service/types.go +++ b/pkg/microservice/aslan/core/code/service/types.go @@ -17,11 +17,8 @@ limitations under the License. package service import ( - "strings" - "github.com/andygrunwald/go-gerrit" "github.com/google/go-github/v35/github" - "github.com/koderover/zadig/pkg/tool/ilyshin" "github.com/xanzy/go-gitlab" "github.com/koderover/zadig/pkg/tool/codehub" @@ -106,14 +103,6 @@ func ToBranches(obj interface{}) []*Branch { Merged: o.Merged, }) } - case []*ilyshin.Branch: - for _, o := range os { - res = append(res, &Branch{ - Name: o.Name, - Protected: o.Protected, - Merged: o.Merged, - }) - } } return res @@ -152,20 +141,6 @@ func ToPullRequests(obj interface{}) []*PullRequest { AuthorUsername: o.Author.Username, }) } - case []*ilyshin.MergeRequest: - for _, o := range os { - res = append(res, &PullRequest{ - ID: o.IID, - TargetBranch: o.TargetBranch, - SourceBranch: o.SourceBranch, - ProjectID: o.ProjectID, - Title: o.Title, - State: o.State, - CreatedAt: o.CreatedAt.Unix(), - UpdatedAt: o.UpdatedAt.Unix(), - AuthorUsername: o.Author.Username, - }) - } } return res @@ -214,14 +189,6 @@ func ToNamespaces(obj interface{}) []*Namespace { ProjectUUID: o.ProjectUUID, }) } - case []*ilyshin.Project: - for _, o := range os { - res = append(res, &Namespace{ - Name: o.Namespace.Name, - Path: o.Namespace.Path, - Kind: GroupKind, - }) - } } return res @@ -271,17 +238,6 @@ func ToProjects(obj interface{}) []*Project { RepoID: project.RepoID, }) } - case []*ilyshin.Project: - for _, o := range os { - namespaces := strings.Split(o.PathWithNamespace, "/") - res = append(res, &Project{ - ID: o.ID, - Name: o.Path, - Namespace: namespaces[0], - Description: o.Description, - DefaultBranch: "master", - }) - } } return res @@ -319,13 +275,6 @@ func ToTags(obj interface{}) []*Tag { Name: o.Name, }) } - case []*ilyshin.Tag: - for _, o := range os { - res = append(res, &Tag{ - Name: o.Name, - Message: o.Message, - }) - } } return res diff --git a/pkg/microservice/aslan/core/common/repository/models/render_set.go b/pkg/microservice/aslan/core/common/repository/models/render_set.go index 5899120f..f114671b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/models/render_set.go @@ -28,15 +28,16 @@ type RenderSet struct { Name string `bson:"name" json:"name"` Revision int64 `bson:"revision" json:"revision"` // 可以为空,空时为产品模板默认的渲染集,非空时为环境的渲染集 - EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` - ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - KVs []*templatemodels.RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` - ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + KVs []*templatemodels.RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` + ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` } func (RenderSet) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go new file mode 100644 index 00000000..42e2c66c --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go @@ -0,0 +1,13 @@ +package models + +type ServicesInExternalEnv struct { + ProductName string `bson:"product_name" json:"product_name"` + ServiceName string `bson:"service_name" json:"service_name"` + EnvName string `bson:"env_name" json:"env_name"` + Namespace string `bson:"namespace" json:"namespace"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` +} + +func (ServicesInExternalEnv) TableName() string { + return "services_in_external_env" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index f6357012..eadcc5b9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -105,10 +105,7 @@ type GitRepoConfig struct { } type CustomYaml struct { - YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` - YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` - ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` + YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` } // RenderChart ... @@ -231,22 +228,6 @@ func (r *RenderKV) RemoveDupServices() { r.Services = result } -func (rc *RenderChart) DiffOverrideYaml(other *RenderChart) bool { - if rc.OverrideYaml == nil { - if other.OverrideYaml != nil { - return true - } - return false - } - if other.OverrideYaml == nil { - return true - } - if rc.OverrideYaml.YamlSource != other.OverrideYaml.YamlSource || rc.OverrideYaml.YamlContent != other.OverrideYaml.YamlContent { - return true - } - return false -} - func (rc *RenderChart) GetOverrideYaml() string { if rc.OverrideYaml == nil { return "" diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 26bd34d2..572ef8ac 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -160,32 +160,6 @@ func (c *ServiceColl) ListMaxRevisionsForServices(services []*templatemodels.Ser return c.listMaxRevisions(pre, post) } -// TODO refactor mouuii -// ListExternalServicesBy list service only for external services ,other service type not use before refactor -func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string) ([]*models.Service, error) { - services := make([]*models.Service, 0) - query := bson.M{ - "status": bson.M{"$ne": setting.ProductStatusDeleting}, - } - if productName != "" { - query["product_name"] = productName - } - if envName != "" { - query["env_name"] = envName - } - ctx := context.Background() - cursor, err := c.Collection.Find(ctx, query) - if err != nil { - return nil, err - } - - err = cursor.All(ctx, &services) - if err != nil { - return nil, err - } - return services, nil -} - func (c *ServiceColl) ListMaxRevisionsByProduct(productName string) ([]*models.Service, error) { m := bson.M{ "product_name": productName, @@ -304,6 +278,57 @@ func (c *ServiceColl) Update(args *models.Service) error { return err } +// ListExternalServicesBy list service only for external services ,other service type not use before refactor +func (c *ServiceColl) ListExternalWorkloadsBy(productName, envName string, serviceNames ...string) ([]*models.Service, error) { + services := make([]*models.Service, 0) + query := bson.M{ + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + } + if productName != "" { + query["product_name"] = productName + } + if envName != "" { + query["env_name"] = envName + } + + if len(serviceNames) > 0 { + query["service_name"] = bson.M{"$in": serviceNames} + } + ctx := context.Background() + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &services) + if err != nil { + return nil, err + } + return services, nil +} + +func (c *ServiceColl) BatchUpdateExternalServicesStatus(productName, envName, status string, serviceNames []string) error { + if productName == "" { + return fmt.Errorf("productName is empty") + } + + if len(serviceNames) == 0 { + return fmt.Errorf("servicenNames is empty") + } + + query := bson.M{"product_name": productName, "service_name": bson.M{"$in": serviceNames}} + if envName != "" { + query["env_name"] = envName + } + + change := bson.M{"$set": bson.M{ + "status": status, + }} + + _, err := c.UpdateMany(context.TODO(), query, change) + return err +} + // UpdateExternalServicesStatus only used by external services func (c *ServiceColl) UpdateExternalServicesStatus(serviceName, productName, status, envName string) error { if serviceName == "" && envName == "" { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go new file mode 100644 index 00000000..56631326 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -0,0 +1,139 @@ +package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ServicesInExternalEnvColl struct { + *mongo.Collection + + coll string +} + +func NewServicesInExternalEnvColl() *ServicesInExternalEnvColl { + name := models.ServicesInExternalEnv{}.TableName() + return &ServicesInExternalEnvColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ServicesInExternalEnvColl) GetCollectionName() string { + return c.coll +} + +func (c *ServicesInExternalEnvColl) EnsureIndex(ctx context.Context) error { + mods := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "service_name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }, + { + Keys: bson.D{ + bson.E{Key: "namespace", Value: 1}, + bson.E{Key: "cluster_id", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mods) + + return err +} + +// Create ... +func (c *ServicesInExternalEnvColl) Create(args *models.ServicesInExternalEnv) error { + // avoid panic issue + if args == nil { + return errors.New("nil ServicesInExternalEnv") + } + + _, err := c.InsertOne(context.TODO(), args) + + return err +} + +type ServicesInExternalEnvArgs struct { + ProductName string + ServiceName string + EnvName string + Namespace string + ClusterID string + ExcludeEnvName string +} + +func (c *ServicesInExternalEnvColl) List(args *ServicesInExternalEnvArgs) ([]*models.ServicesInExternalEnv, error) { + if args == nil { + return nil, errors.New("nil ServicesInExternalEnvArgs") + } + query := bson.M{} + if args.ProductName != "" { + query["product_name"] = args.ProductName + } + + if args.EnvName != "" { + query["env_name"] = args.EnvName + } + + if args.ExcludeEnvName != "" { + query["env_name"] = bson.M{"$ne": args.ExcludeEnvName} + } + + if args.Namespace != "" { + query["namespace"] = args.Namespace + } + + if args.ClusterID != "" { + query["cluster_id"] = args.ClusterID + } + + resp := make([]*models.ServicesInExternalEnv, 0) + ctx := context.Background() + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, err +} + +func (c *ServicesInExternalEnvColl) Delete(args *ServicesInExternalEnvArgs) error { + if args == nil { + return errors.New("nil ServicesInExternalEnvArgs") + } + query := bson.M{} + if args.ProductName != "" { + query["product_name"] = args.ProductName + } + + if args.EnvName != "" { + query["env_name"] = args.EnvName + } + + if args.ServiceName != "" { + query["service_name"] = args.ServiceName + } + + _, err := c.DeleteMany(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index ec911ff7..8f76189c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -263,6 +263,30 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { return err } +type ProductArgs struct { + ProductName string `json:"product_name"` + Services [][]string `json:"services"` + UpdateBy string `json:"update_by"` +} + +// UpdateServiceOrder existing ProductTmpl +func (c *ProductColl) UpdateServiceOrder(args *ProductArgs) error { + // avoid panic issue + if args == nil { + return errors.New("nil product args") + } + + query := bson.M{"product_name": args.ProductName} + change := bson.M{"$set": bson.M{ + "services": args.Services, + "update_time": time.Now().Unix(), + "update_by": args.UpdateBy, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + // AddService adds a service to services[0] if it is not there. func (c *ProductColl) AddService(productName, serviceName string) error { diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index bb0e0d96..6212da27 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -18,7 +18,6 @@ package service import ( "encoding/json" - "fmt" "sort" "strings" @@ -131,12 +130,20 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, } productServiceNames := sets.NewString() for _, productService := range productServices { - productServiceNames.Insert(fmt.Sprintf("%s/%s", productService.WorkloadType, productService.ServiceName)) + productServiceNames.Insert(productService.ServiceName) + } + // add services in external env data + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + for _, serviceInExternalEnv := range servicesInExternalEnv { + productServiceNames.Insert(serviceInExternalEnv.ServiceName) } var res []*Workload for _, workload := range workloads { - if productServiceNames.Has(fmt.Sprintf("%s/%s", workload.Type, workload.Name)) { + if productServiceNames.Has(workload.Name) { res = append(res, workload) } } diff --git a/pkg/microservice/aslan/core/common/service/ilyshin/client.go b/pkg/microservice/aslan/core/common/service/ilyshin/client.go deleted file mode 100644 index d2fc7736..00000000 --- a/pkg/microservice/aslan/core/common/service/ilyshin/client.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "github.com/koderover/zadig/pkg/tool/ilyshin" -) - -type Client struct { - *ilyshin.Client -} - -func NewClient(address, accessToken string) *Client { - c := ilyshin.NewClient(address, accessToken) - return &Client{ - Client: c, - } -} diff --git a/pkg/microservice/aslan/core/common/service/ilyshin/webhook.go b/pkg/microservice/aslan/core/common/service/ilyshin/webhook.go deleted file mode 100644 index 4a8dd644..00000000 --- a/pkg/microservice/aslan/core/common/service/ilyshin/webhook.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "strconv" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" -) - -func (c *Client) CreateWebHook(owner, repo string) (string, error) { - projectHook, err := c.AddProjectHook(owner, repo, config.WebHookURL(), gitservice.GetHookSecret()) - - return strconv.Itoa(projectHook.ID), err -} - -func (c *Client) DeleteWebHook(owner, repo, hookID string) error { - hookIDInt, err := strconv.Atoi(hookID) - if err != nil { - return err - } - return c.DeleteProjectHook(owner, repo, hookIDInt) -} diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index a62713d3..9986f357 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -24,6 +24,7 @@ import ( helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -140,10 +141,41 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("update workloads fail error:%s", err) } } - // 删除所有external的服务 - err = commonrepo.NewServiceColl().UpdateExternalServicesStatus("", productName, setting.ProductStatusDeleting, envName) + // 获取所有external的服务 + currentEnvServices, err := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, envName) if err != nil { - log.Errorf("UpdateStatus external services error:%s", err) + log.Errorf("failed to list external workload, error:%s", err) + } + + externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + ExcludeEnvName: envName, + }) + if err != nil { + log.Errorf("failed to list external service, error:%s", err) + } + + externalEnvServiceM := make(map[string]bool) + for _, externalEnvService := range externalEnvServices { + externalEnvServiceM[externalEnvService.ServiceName] = true + } + + deleteServices := sets.NewString() + for _, currentEnvService := range currentEnvServices { + if _, isExist := externalEnvServiceM[currentEnvService.ServiceName]; !isExist { + deleteServices.Insert(currentEnvService.ServiceName) + } + } + err = commonrepo.NewServiceColl().BatchUpdateExternalServicesStatus(productName, "", setting.ProductStatusDeleting, deleteServices.List()) + if err != nil { + log.Errorf("UpdateStatus external services error:%s", err) + } + // delete services_in_external_env data + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }); err != nil { + log.Errorf("remove services in external env error:%s", err) } } diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 3552b1e1..a695162c 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -49,16 +49,14 @@ type KVPair struct { } type RenderChartArg struct { - EnvName string `json:"envName,omitempty"` - ServiceName string `json:"serviceName,omitempty"` - ChartVersion string `json:"chartVersion,omitempty"` - YamlSource string `json:"yamlSource,omitempty"` - GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` - OverrideValues []*KVPair `json:"overrideValues,omitempty"` - ValuesYAML string `json:"valuesYAML,omitempty"` + EnvName string `json:"envName,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + ChartVersion string `json:"chartVersion,omitempty"` + OverrideValues []*KVPair `json:"overrideValues,omitempty"` + OverrideYaml string `json:"overrideYaml,omitempty"` } -func (args *RenderChartArg) toOverrideValueString() string { +func (args *RenderChartArg) ToOverrideValueString() string { if len(args.OverrideValues) == 0 { return "" } @@ -84,23 +82,9 @@ func (args *RenderChartArg) fromOverrideValueString(valueStr string) { } func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.CustomYaml { - switch args.YamlSource { - case setting.ValuesYamlSourceFreeEdit: + if len(args.OverrideYaml) > 0 { return &templatemodels.CustomYaml{ - YamlSource: args.YamlSource, - YamlContent: args.ValuesYAML, - } - case setting.ValuesYamlSourceGitRepo: - return &templatemodels.CustomYaml{ - YamlSource: args.YamlSource, - YamlContent: args.ValuesYAML, - ValuesPaths: args.GitRepoConfig.ValuesPaths, - GitRepoConfig: &templatemodels.GitRepoConfig{ - CodehostID: args.GitRepoConfig.CodehostID, - Owner: args.GitRepoConfig.Owner, - Repo: args.GitRepoConfig.Repo, - Branch: args.GitRepoConfig.Branch, - }, + YamlContent: args.OverrideYaml, } } return nil @@ -110,31 +94,19 @@ func (args *RenderChartArg) fromCustomValueYaml(customValuesYaml *templatemodels if customValuesYaml == nil { return } - args.YamlSource = customValuesYaml.YamlSource - switch customValuesYaml.YamlSource { - case setting.ValuesYamlSourceFreeEdit: - args.ValuesYAML = customValuesYaml.YamlContent - case setting.ValuesYamlSourceGitRepo: - args.ValuesYAML = "" - if customValuesYaml.GitRepoConfig != nil { - args.GitRepoConfig = &RepoConfig{ - CodehostID: customValuesYaml.GitRepoConfig.CodehostID, - Owner: customValuesYaml.GitRepoConfig.Owner, - Repo: customValuesYaml.GitRepoConfig.Repo, - Branch: customValuesYaml.GitRepoConfig.Branch, - ValuesPaths: customValuesYaml.ValuesPaths, - } - } - - } + args.OverrideYaml = customValuesYaml.YamlContent } // FillRenderChartModel fill render chart model func (args *RenderChartArg) FillRenderChartModel(chart *templatemodels.RenderChart, version string) { chart.ServiceName = args.ServiceName chart.ChartVersion = version - chart.OverrideValues = args.toOverrideValueString() - chart.OverrideYaml = args.toCustomValuesYaml() + chart.OverrideValues = args.ToOverrideValueString() + if len(args.OverrideYaml) > 0 { + chart.OverrideYaml = args.toCustomValuesYaml() + } else { + chart.OverrideYaml = nil + } } // LoadFromRenderChartModel load from render chart model @@ -256,7 +228,7 @@ func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) e if rs != nil && err == nil { // 已经存在渲染配置集 // 判断是否有修改 - if rs.HelmRenderDiff(args) { + if rs.DefaultValues != args.DefaultValues || rs.HelmRenderDiff(args) { args.IsDefault = rs.IsDefault } else { return nil diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 6421572d..c4d6a038 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -221,6 +221,28 @@ func ListWorkloadTemplate(productName, envName string, log *zap.SugaredLogger) ( return resp, e.ErrListTemplate.AddDesc(err.Error()) } + currentServiceNames := sets.NewString() + for _, service := range services { + currentServiceNames.Insert(service.ServiceName) + } + + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + + externalServiceNames := sets.NewString() + for _, serviceInExternalEnv := range servicesInExternalEnv { + if !currentServiceNames.Has(serviceInExternalEnv.ServiceName) { + externalServiceNames.Insert(serviceInExternalEnv.ServiceName) + } + } + + if len(externalServiceNames) > 0 { + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "", externalServiceNames.List()...) + services = append(services, newServices...) + } + for _, serviceObject := range services { spmap := &ServiceProductMap{ Service: serviceObject.ServiceName, diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 1f39e415..df136cbe 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -22,6 +22,7 @@ import ( "sync" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -82,6 +83,13 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P if err != nil { return resp, fmt.Errorf("ListExternalWorkloadsBy err : %s", err) } + serviceNamesSet := sets.NewString() + for _, service := range totalServices { + serviceNamesSet.Insert(service.ServiceName) + } + if len(resp.Services) > 0 { + resp.Services[0] = serviceNamesSet.List() + } } else { totalServices, err = commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/utils.go b/pkg/microservice/aslan/core/common/service/utils.go index 010bdc95..3b811390 100644 --- a/pkg/microservice/aslan/core/common/service/utils.go +++ b/pkg/microservice/aslan/core/common/service/utils.go @@ -95,7 +95,7 @@ func GetGitlabAddress(URL string) (string, error) { // GetOwnerRepoBranchPath 获取gitlab路径中的owner、repo、branch和path func GetOwnerRepoBranchPath(URL string) (string, string, string, string, string, string, error) { if !strings.Contains(URL, "https") && !strings.Contains(URL, "http") { - return "", "", "", "", "", "", fmt.Errorf("url is illegal") + return "", "", "", "", "", "", fmt.Errorf("url is illegal:%s", URL) } //适配公网的gitlab if strings.Contains(URL, "-") { diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index c9e80961..0297b64c 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -29,7 +29,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/ilyshin" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -132,8 +131,6 @@ func removeWebhook(t *task, logger *zap.Logger) { } case setting.SourceFromCodeHub: cl = codehub.NewClient(t.ak, t.sk, t.region) - case setting.SourceFromIlyshin: - cl = ilyshin.NewClient(t.address, t.token) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} @@ -195,8 +192,6 @@ func addWebhook(t *task, logger *zap.Logger) { case setting.SourceFromCodeHub: cl = codehub.NewClient(t.ak, t.sk, t.region) - case setting.SourceFromIlyshin: - cl = ilyshin.NewClient(t.address, t.token) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index beb0c351..36e82f91 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -175,7 +175,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger } switch ch.Type { - case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromIlyshin: + case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: err = webhook.NewClient().RemoveWebHook(&webhook.TaskOption{ Name: wh.name, Owner: wh.owner, @@ -209,7 +209,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger } switch ch.Type { - case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromIlyshin: + case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: err = webhook.NewClient().AddWebHook(&webhook.TaskOption{ Name: wh.name, Owner: wh.owner, diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 69657515..645b85d8 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -63,6 +63,14 @@ func ListProducts(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProducts(c.Query("projectName"), ctx.UserName, ctx.Logger) } +// GetProductStatus List product status +func GetProductStatus(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetProductStatus(c.Param("productName"), ctx.Logger) +} + func AutoCreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -104,25 +112,34 @@ func CreateHelmProduct(c *gin.Context) { return } - args := new(service.CreateHelmProductArg) + productName := c.Param("productName") + + createArgs := make([]*service.CreateHelmProductArg, 0) data, err := c.GetRawData() if err != nil { - log.Errorf("CreateProduct c.GetRawData() err : %v", err) + log.Errorf("CreateHelmProduct c.GetRawData() err : %v", err) + } else if err = json.Unmarshal(data, &createArgs); err != nil { + log.Errorf("CreateHelmProduct json.Unmarshal err : %v", err) } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateProduct json.Unmarshal err : %v", err) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } - args.ProductName = c.Param("productName") - if args.EnvName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can not be null!") - return + envNameList := make([]string, 0) + for _, arg := range createArgs { + if arg.EnvName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName is empty") + return + } + arg.ProductName = productName + envNameList = append(envNameList, arg.EnvName) } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( - ctx.UserName, ctx.RequestID, args, ctx.Logger, + productName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, ) } @@ -171,6 +188,8 @@ func UpdateProduct(c *gin.Context) { } if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProduct json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -215,21 +234,32 @@ func UpdateProductRecycleDay(c *gin.Context) { ctx.Err = service.UpdateProductRecycleDay(envName, productName, recycleDay) } -func UpdateHelmProduct(c *gin.Context) { +func EstimatedValues(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - updateType := c.Query("updateType") productName := c.Param("productName") + if productName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + return + } - ctx.Err = service.UpdateHelmProduct(productName, envName, updateType, ctx.UserName, ctx.RequestID, nil, ctx.Logger) - if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product %s %s: %v", envName, productName, ctx.Err) + serviceName := c.Query("serviceName") + if serviceName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("serviceName can't be empty!") + return + } + + arg := new(service.EstimateValuesArg) + if err := c.ShouldBind(arg); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } + + ctx.Resp, ctx.Err = service.GeneEstimatedValues(productName, c.Query("envName"), serviceName, c.Query("scene"), c.Query("format"), arg, ctx.Logger) } -func UpdateHelmProductRenderCharts(c *gin.Context) { +func UpdateHelmProductRenderset(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -245,7 +275,7 @@ func UpdateHelmProductRenderCharts(c *gin.Context) { return } - arg := new(service.EnvRenderChartArg) + arg := new(service.EnvRendersetArg) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) @@ -255,37 +285,7 @@ func UpdateHelmProductRenderCharts(c *gin.Context) { } internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "更新环境变量", "", string(data), ctx.Logger) - ctx.Err = service.UpdateHelmProductRenderCharts(productName, envName, ctx.UserName, ctx.RequestID, arg.ChartValues, ctx.Logger) - if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) - } -} - -func UpdateHelmProductVariable(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - envName := c.Query("envName") - productName := c.Param("productName") - - args := new(ChartInfoArgs) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) - } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "helm集成环境变量", "", string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.BindJSON(args); err != nil { - ctx.Logger.Error(err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - - ctx.Err = service.UpdateHelmProductVariable(productName, envName, ctx.UserName, ctx.RequestID, args.ChartInfos, args.ChartInfos, ctx.Logger) + ctx.Err = service.UpdateHelmProductRenderset(productName, envName, ctx.UserName, ctx.RequestID, arg, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) } @@ -317,30 +317,6 @@ func UpdateMultiHelmEnv(c *gin.Context) { ) } -func UpdateMultiHelmProduct(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(UpdateEnvs) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateMultiHelmProduct c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateMultiHelmProduct json.Unmarshal err : %v", err) - } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新helm环境", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.BindJSON(args); err != nil { - ctx.Logger.Error(err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - - ctx.Resp = service.UpdateMultiHelmProduct(args.EnvNames, args.UpdateType, c.Param("productName"), ctx.RequestID, ctx.Logger) -} - func GetProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -402,11 +378,13 @@ func GetEstimatedRenderCharts(c *gin.Context) { productName := c.Query("projectName") if productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + return } envName := c.Query("envName") if envName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") + return } ctx.Resp, ctx.Err = service.GetEstimatedRenderCharts(productName, envName, c.Query("serviceName"), ctx.Logger) @@ -520,6 +498,20 @@ func ListWorkloads(c *gin.Context) { for _, workload := range workloadStat.Workloads { workloadM[workload.Name] = workload } + + // add services in external env data + servicesInExternalEnv, _ := mongodb.NewServicesInExternalEnvColl().List(&mongodb.ServicesInExternalEnvArgs{ + Namespace: args.Namespace, + ClusterID: args.ClusterID, + }) + + for _, serviceInExternalEnv := range servicesInExternalEnv { + workloadM[serviceInExternalEnv.ServiceName] = commonmodels.Workload{ + EnvName: serviceInExternalEnv.EnvName, + ProductName: serviceInExternalEnv.ProductName, + } + } + for index, currentWorkload := range workloads { if existWorkload, ok := workloadM[currentWorkload.Name]; ok { workloads[index].EnvName = existWorkload.EnvName diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 851f0332..85fa5854 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -17,15 +17,12 @@ limitations under the License. package handler import ( - "encoding/json" + "strings" "github.com/gin-gonic/gin" - - commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/log" ) func GetServiceRenderCharts(c *gin.Context) { @@ -45,7 +42,7 @@ func GetServiceRenderCharts(c *gin.Context) { ctx.Resp, ctx.Err = service.GetRenderCharts(c.Query("projectName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) } -func CreateOrUpdateRenderChart(c *gin.Context) { +func GetProductDefaultValues(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -59,17 +56,29 @@ func CreateOrUpdateRenderChart(c *gin.Context) { return } - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateOrUpdateRenderChart c.GetRawData() err : %v", err) + ctx.Resp, ctx.Err = service.GetDefaultValues(c.Query("productName"), c.Query("envName"), ctx.Logger) +} + +func GetYamlContent(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + arg := &service.YamlContentRequestArg{} + + if err := c.ShouldBindQuery(arg); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + if arg.CodehostID == 0 && len(arg.RepoLink) == 0 { + ctx.Err = e.ErrInvalidParam.AddDesc("neither codehost nor repo link is specified") + return } - args := new(commonservice.RenderChartArg) - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateOrUpdateRenderChart json.Unmarshal err : %v", err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + if len(arg.ValuesPaths) == 0 { + ctx.Err = e.ErrInvalidParam.AddDesc("paths can't be empty") + return } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "新增", "环境变量", c.Query("envName"), string(data), ctx.Logger) - ctx.Err = service.CreateOrUpdateChartValues(c.Query("projectName"), c.Query("envName"), args, ctx.UserName, ctx.RequestID, ctx.Logger) + pathArr := strings.Split(arg.ValuesPaths, ",") + ctx.Resp, ctx.Err = service.GetMergedYamlContent(arg, pathArr) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 5005916b..bc13c84c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -94,7 +94,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments := router.Group("environments") { environments.GET("", ListProducts) + environments.GET("/:productName/status", GetProductStatus) environments.POST("/:productName/auto", AutoCreateProduct) + environments.PUT("/:productName/autoUpdate", gin2.UpdateOperationLogStatus, AutoUpdateProduct) environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) @@ -103,7 +105,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.POST("/:productName", gin2.UpdateOperationLogStatus, UpdateProduct) environments.PUT("/:productName/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) - environments.PUT("/:productName/renderchart", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderCharts) + + environments.POST("/:productName/estimated-values", EstimatedValues) + environments.PUT("/:productName/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) //environments.PUT("/:productName", gin2.UpdateOperationLogStatus, EnvShare) @@ -126,11 +130,6 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/services/:serviceName/containers/:container/namespaces/:namespace", GetServiceContainer) environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) - - // need to be deprecated - environments.PUT("/:productName/helmEnv", gin2.UpdateOperationLogStatus, UpdateHelmProduct) - environments.PUT("/:productName/helmEnvVariable", gin2.UpdateOperationLogStatus, UpdateHelmProductVariable) - environments.PUT("/:productName/updateMultiEnv", gin2.UpdateOperationLogStatus, UpdateMultiHelmProduct) } // --------------------------------------------------------------------------------------- @@ -139,7 +138,8 @@ func (*Router) Inject(router *gin.RouterGroup) { rendersets := router.Group("rendersets") { rendersets.GET("/renderchart", GetServiceRenderCharts) - rendersets.PUT("/renderchart", CreateOrUpdateRenderChart) // create or update renderchart when onBoarding + rendersets.GET("/default-values", GetProductDefaultValues) + rendersets.GET("/yamlContent", GetYamlContent) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5f542f97..6e635f9b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,6 +46,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -69,6 +71,14 @@ const ( UpdateTypeEnv = "envVar" ) +type usageScenario string + +const ( + usageScenarioCreateEnv = "createEnv" + usageScenarioUpdateEnv = "updateEnv" + usageScenarioUpdateRenderSet = "updateRenderSet" +) + type EnvStatus struct { EnvName string `json:"env_name,omitempty"` Status string `json:"status"` @@ -101,16 +111,28 @@ type ProductParams struct { PermissionUUIDs []string `json:"permissionUUIDs"` } +type EstimateValuesArg struct { + DefaultValues string `json:"defaultValues"` + OverrideYaml string `json:"overrideYaml"` + OverrideValues []*commonservice.KVPair `json:"overrideValues,omitempty"` +} + type EnvRenderChartArg struct { ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } +type EnvRendersetArg struct { + DefaultValues string `json:"defaultValues"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` +} + type CreateHelmProductArg struct { - ProductName string `json:"productName"` - EnvName string `json:"envName"` - Namespace string `json:"namespace"` - ClusterID string `json:"clusterID"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + ProductName string `json:"productName"` + EnvName string `json:"envName"` + Namespace string `json:"namespace"` + ClusterID string `json:"clusterID"` + DefaultValues string `json:"defaultValues"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } type UpdateMultiHelmProductArg struct { @@ -120,31 +142,32 @@ type UpdateMultiHelmProductArg struct { ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented } -//func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { -// err := commonrepo.NewProductColl().UpdateIsPublic(args.EnvName, productName, args.IsPublic) -// if err != nil { -// log.Errorf("UpdateProductPublic error: %v", err) -// return fmt.Errorf("UpdateProductPublic error: %v", err) -// } -// -// poetryCtl := poetry.New(config.PoetryAPIServer()) -// if !args.IsPublic { //把公开设置成不公开 -// _, err := poetryCtl.AddEnvRolePermission(productName, args.EnvName, args.PermissionUUIDs, args.RoleID, log) -// if err != nil { -// log.Errorf("UpdateProductPublic AddEnvRole error: %v", err) -// return fmt.Errorf("UpdateProductPublic AddEnvRole error: %v", err) -// } -// return nil -// } -// //把不公开设成公开 删除原来环境绑定的角色 -// _, err = poetryCtl.DeleteEnvRolePermission(productName, args.EnvName, log) -// if err != nil { -// log.Errorf("UpdateProductPublic DeleteEnvRole error: %v", err) -// return fmt.Errorf("UpdateProductPublic DeleteEnvRole error: %v", err) -// } -// -// return nil -//} +type RawYamlResp struct { + YamlContent string `json:"yamlContent"` +} + +func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, error) { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) + if err != nil { + log.Errorf("Collection.Product.List List product error: %v", err) + return nil, e.ErrListProducts.AddDesc(err.Error()) + } + envStatusSlice := make([]*EnvStatus, 0) + for _, publicProduct := range products { + if publicProduct.ProductName != productName { + continue + } + envStatus := &EnvStatus{ + EnvName: publicProduct.EnvName, + Status: publicProduct.Status, + } + if len(publicProduct.Error) > 0 { + envStatus.ErrMessage = publicProduct.Error + } + envStatusSlice = append(envStatusSlice, envStatus) + } + return envStatusSlice, err +} func ListProducts(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam, IsSortByProductName: true}) @@ -617,13 +640,13 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return nil } -func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, log *zap.SugaredLogger) error { - templateProduct, err := templaterepo.NewProductColl().Find(args.ProductName) +func CreateHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { + templateProduct, err := templaterepo.NewProductColl().Find(productName) if err != nil || templateProduct == nil { if err != nil { - log.Errorf("failed to query product %s, err %s ", args.ProductName, err.Error()) + log.Errorf("failed to query product %s, err %s ", productName, err.Error()) } - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", args.ProductName)) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", productName)) } err = commonservice.FillProductTemplateValuesYamls(templateProduct, log) @@ -631,22 +654,9 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l return e.ErrCreateEnv.AddDesc(err.Error()) } - productObj := &commonmodels.Product{ - ProductName: args.ProductName, - Revision: 1, - EnvName: args.EnvName, - UpdateBy: userName, - IsPublic: true, - ClusterID: args.ClusterID, - Namespace: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName, args.Namespace), - Source: setting.SourceFromHelm, - IsOpenSource: templateProduct.IsOpensource, - ChartInfos: templateProduct.ChartInfos, - IsForkedProduct: false, - } - + // generate service group data allServiceInfoMap := templateProduct.AllServiceInfoMap() - + var serviceGroup [][]*commonmodels.ProductService for _, names := range templateProduct.Services { servicesResp := make([]*commonmodels.ProductService, 0) for _, serviceName := range names { @@ -682,50 +692,64 @@ func CreateHelmProduct(userName, requestID string, args *CreateHelmProductArg, l } servicesResp = append(servicesResp, serviceResp) } - productObj.Services = append(productObj.Services, servicesResp) + serviceGroup = append(serviceGroup, servicesResp) } - // extract values.yaml from request and save into db - serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(args.ProductName) - if err != nil { - log.Infof("query services from product: %s fail, error %s", args.ProductName, err.Error()) - return e.ErrCreateEnv.AddDesc("failed to query services") + errList := new(multierror.Error) + for _, arg := range args { + err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, arg, log) + if err != nil { + errList = multierror.Append(errList, err) + } } + return errList.ErrorOrNil() +} - serviceMap := make(map[string]*commonmodels.Service) - for _, singleService := range serviceList { - serviceMap[singleService.ServiceName] = singleService +func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][]*commonmodels.ProductService, requestID, userName string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { + productObj := &commonmodels.Product{ + ProductName: templateProduct.ProductName, + Revision: 1, + Enabled: false, + EnvName: arg.EnvName, + UpdateBy: userName, + Services: serviceGroup, + IsPublic: true, + ClusterID: arg.ClusterID, + Namespace: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + Source: setting.SourceFromHelm, + IsOpenSource: templateProduct.IsOpensource, + ChartInfos: templateProduct.ChartInfos, + IsForkedProduct: false, } customChartValueMap := make(map[string]*commonservice.RenderChartArg) - for _, singleCV := range args.ChartValues { + for _, singleCV := range arg.ChartValues { customChartValueMap[singleCV.ServiceName] = singleCV } for _, latestChart := range productObj.ChartInfos { if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { - yamlContent, err := generateValuesYaml(singleCV, log) - if err != nil { - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", singleCV.ServiceName, err.Error())) - } - singleCV.ValuesYAML = yamlContent singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) } } + // default values + defaultValuesYaml := arg.DefaultValues + // insert renderset info into db if len(productObj.ChartInfos) > 0 { - err = commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ - Name: commonservice.GetProductEnvNamespace(args.EnvName, args.ProductName, args.Namespace), - EnvName: args.EnvName, - ProductTmpl: args.ProductName, - UpdateBy: userName, - IsDefault: false, - ChartInfos: productObj.ChartInfos, + err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ + Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + EnvName: arg.EnvName, + ProductTmpl: arg.ProductName, + UpdateBy: userName, + IsDefault: false, + DefaultValues: defaultValuesYaml, + ChartInfos: productObj.ChartInfos, }, log) if err != nil { - log.Errorf("rennderset create fail when creating helm product, productName: %s", args.ProductName) - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", args.ProductName)) + log.Errorf("rennderset create fail when creating helm product, productName: %s", arg.ProductName) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", arg.ProductName)) } } @@ -766,7 +790,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str } //对比当前环境中的环境变量和默认的环境变量 go func() { - err := updateProductGroup(productName, envName, updateType, productResp, currentProductService, overrideCharts, log) + err := updateProductGroup(username, productName, envName, updateType, productResp, currentProductService, overrideCharts, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -802,18 +826,106 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } +func prepareEstimatedData(productName, envName, serviceName, usageScenario, defaultValues string, log *zap.SugaredLogger) (string, string, error) { + var err error + templateService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + }) + if err != nil { + log.Errorf("failed to query service, name %s, err %s", serviceName, err) + return "", "", fmt.Errorf("failed to query service, name %s", serviceName) + } + + if usageScenario == usageScenarioCreateEnv { + return templateService.HelmChart.ValuesYaml, defaultValues, nil + } + + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return "", "", fmt.Errorf("failed to query product info, name %s", envName) + } + + // find chart info from cur render set + opt := &mongodb.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} + renderSet, err := mongodb.NewRenderSetColl().Find(opt) + if err != nil { + log.Errorf("renderset Find error, productName:%s, envName:%s, err:%s", productInfo.ProductName, productInfo.EnvName, err) + return "", "", fmt.Errorf("failed to query renderset info, name %s", productInfo.Render.Name) + } + + var targetChart *templatemodels.RenderChart + for _, chart := range renderSet.ChartInfos { + if chart.ServiceName == serviceName { + targetChart = chart + break + } + } + + if targetChart == nil { + return "", "", fmt.Errorf("failed to find chart info, name: %s", serviceName) + } + + switch usageScenario { + case usageScenarioUpdateEnv: + imageRelatedKey := sets.NewString() + if templateService != nil { + for _, container := range templateService.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } + } + } + // merge environment values + mergedBs, err := overrideValues([]byte(targetChart.ValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) + if err != nil { + return "", "", errors.Wrapf(err, "failed to override values") + } + return string(mergedBs), renderSet.DefaultValues, nil + case usageScenarioUpdateRenderSet: + return targetChart.ValuesYaml, renderSet.DefaultValues, nil + default: + return "", "", fmt.Errorf("unrecognized usageScenario:%s", usageScenario) + } +} + +func GeneEstimatedValues(productName, envName, serviceName, scene, format string, arg *EstimateValuesArg, log *zap.SugaredLogger) (interface{}, error) { + chartValues, defaultValues, err := prepareEstimatedData(productName, envName, serviceName, scene, arg.DefaultValues, log) + if err != nil { + return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to prepare data, err %s", err)) + } + + tempArg := &commonservice.RenderChartArg{OverrideValues: arg.OverrideValues} + mergeValues, err := helmtool.MergeOverrideValues(chartValues, defaultValues, arg.OverrideYaml, tempArg.ToOverrideValueString()) + if err != nil { + return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to merge values, err %s", err)) + } + + switch format { + case "flatMap": + mapData, err := converter.YamlToFlatMap([]byte(mergeValues)) + if err != nil { + return nil, e.ErrUpdateRenderSet.AddDesc(fmt.Sprintf("failed to generate flat map , err %s", err)) + } + return mapData, nil + default: + return &RawYamlResp{YamlContent: mergeValues}, nil + } +} + // check if override values or yaml content changes func checkOverrideValuesChange(source *template.RenderChart, args *commonservice.RenderChartArg) bool { - tmpRenderCharts := &template.RenderChart{} - args.FillRenderChartModel(tmpRenderCharts, "") - if source.OverrideValues != tmpRenderCharts.OverrideValues || source.DiffOverrideYaml(tmpRenderCharts) { + if source.OverrideValues != args.ToOverrideValueString() || source.GetOverrideYaml() != args.OverrideYaml { return true } return false } -func UpdateHelmProductRenderCharts(productName, envName, userName, requestID string, renderCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { - +func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") opt := &commonrepo.RenderSetFindOption{Name: renderSetName} @@ -826,15 +938,18 @@ func UpdateHelmProductRenderCharts(productName, envName, userName, requestID str } // render charts need to be updated - updatedRcs := make([]*template.RenderChart, 0) + updatedRcList := make([]*template.RenderChart, 0) + updatedRCMap := make(map[string]*template.RenderChart) - for _, requestRenderChart := range renderCharts { - yamlContent, err := generateValuesYaml(requestRenderChart, log) - if err != nil { - return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) + // default values change + if args.DefaultValues != productRenderset.DefaultValues { + for _, curRenderChart := range productRenderset.ChartInfos { + updatedRCMap[curRenderChart.ServiceName] = curRenderChart } - requestRenderChart.ValuesYAML = yamlContent + productRenderset.DefaultValues = args.DefaultValues + } + for _, requestRenderChart := range args.ChartValues { // update renderset info for _, curRenderChart := range productRenderset.ChartInfos { if curRenderChart.ServiceName != requestRenderChart.ServiceName { @@ -844,15 +959,19 @@ func UpdateHelmProductRenderCharts(productName, envName, userName, requestID str continue } requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) - updatedRcs = append(updatedRcs, curRenderChart) + updatedRCMap[curRenderChart.ServiceName] = curRenderChart break } } - return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcs, productRenderset.ChartInfos, log) + for _, updatedRc := range updatedRCMap { + updatedRcList = append(updatedRcList, updatedRc) + } + + return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcList, productRenderset, log) } -func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs, allRcs []*template.RenderChart, log *zap.SugaredLogger) error { +func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -867,11 +986,12 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, if err = commonservice.CreateHelmRenderSet( &commonmodels.RenderSet{ - Name: productResp.Namespace, - EnvName: envName, - ProductTmpl: productName, - UpdateBy: username, - ChartInfos: allRcs, + Name: productResp.Namespace, + EnvName: envName, + ProductTmpl: productName, + UpdateBy: username, + DefaultValues: renderset.DefaultValues, + ChartInfos: renderset.ChartInfos, }, log, ); err != nil { @@ -890,10 +1010,10 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, } productResp.Render.Revision = renderSet.Revision - return updateHelmProductVariable(productResp, oldRenderVersion, username, requestID, log) + return updateHelmProductVariable(productResp, renderSet, oldRenderVersion, username, requestID, log) } -func updateHelmProductVariable(productResp *commonmodels.Product, oldRenderVersion int64, userName, requestID string, log *zap.SugaredLogger) error { +func updateHelmProductVariable(productResp *commonmodels.Product, renderset *commonmodels.RenderSet, oldRenderVersion int64, userName, requestID string, log *zap.SugaredLogger) error { envName, productName := productResp.EnvName, productResp.ProductName @@ -904,7 +1024,7 @@ func updateHelmProductVariable(productResp *commonmodels.Product, oldRenderVersi } go func() { - err := updateProductVariable(productName, envName, productResp, log) + err := updateProductVariable(productName, envName, productResp, renderset, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -992,14 +1112,6 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo serviceMap[singleService.ServiceName] = singleService } - for _, requestRenderChart := range args.ChartValues { - yamlContent, err := generateValuesYaml(requestRenderChart, log) - if err != nil { - return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to get yaml content for service: %s, err %v", requestRenderChart.ServiceName, err.Error())) - } - requestRenderChart.ValuesYAML = yamlContent - } - // extract values.yaml and update renderset for envName, _ := range productMap { renderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ @@ -1038,55 +1150,6 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo return envStatuses, nil } -// UpdateMultiHelmProduct TODO need to be deprecated -func UpdateMultiHelmProduct(envNames []string, updateType, productName string, requestID string, log *zap.SugaredLogger) []*EnvStatus { - mutexUpdateMultiHelm.Lock() - defer func() { - mutexUpdateMultiHelm.Unlock() - }() - - envStatuses := make([]*EnvStatus, 0) - productsRevison, err := ListProductsRevision(productName, "", log) - if err != nil { - log.Errorf("UpdateMultiHelmProduct ListProductsRevision err:%v", err) - return envStatuses - } - productMap := make(map[string]*ProductRevision) - for _, productRevison := range productsRevison { - if productRevison.ProductName == productName && sets.NewString(envNames...).Has(productRevison.EnvName) && productRevison.Updatable { - productMap[productRevison.EnvName] = productRevison - if len(productMap) == len(envNames) { - break - } - } - } - - for envName := range productMap { - err = UpdateHelmProduct(productName, envName, updateType, setting.SystemUser, requestID, nil, log) - if err != nil { - log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) - return envStatuses - } - } - - productResps := make([]*ProductResp, 0) - for _, envName := range envNames { - productResp, err := GetProduct(setting.SystemUser, envName, productName, log) - if err == nil && productResp != nil { - productResps = append(productResps, productResp) - } - } - - for _, productResp := range productResps { - if productResp.Error != "" { - envStatuses = append(envStatuses, &EnvStatus{EnvName: productResp.EnvName, Status: setting.ProductStatusFailed, ErrMessage: productResp.Error}) - continue - } - envStatuses = append(envStatuses, &EnvStatus{EnvName: productResp.EnvName, Status: productResp.Status}) - } - return envStatuses -} - func GetProductInfo(username, envName, productName string, log *zap.SugaredLogger) (*commonmodels.Product, error) { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) @@ -2030,8 +2093,8 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) +func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err @@ -2072,10 +2135,9 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha return nil } -func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { +func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { var ( err error - wg sync.WaitGroup errList = &multierror.Error{} ) @@ -2102,43 +2164,51 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel return } }() + chartInfoMap := make(map[string]*template.RenderChart) for _, renderChart := range args.ChartInfos { chartInfoMap[renderChart.ServiceName] = renderChart } + + serviceList := make([]interface{}, 0) + handler := func(data interface{}, logger *zap.SugaredLogger) error { + service := data.(*commonmodels.ProductService) + // 获取服务详情 + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: args.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + return err + } + + renderChart := chartInfoMap[service.ServiceName] + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, helmClient) + if err != nil { + return err + } + return nil + } + for _, serviceGroups := range args.Services { for _, svc := range serviceGroups { - renderChart, ok := chartInfoMap[svc.ServiceName] + _, ok := chartInfoMap[svc.ServiceName] if !ok { continue } - - wg.Add(1) - go func(service *commonmodels.ProductService) { - defer wg.Done() - - // 获取服务详情 - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: args.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - return - } - - err = installOrUpgradeHelmChart(args.Namespace, renderChart, serviceObj, Timeout*time.Second*10, helmClient) - if err != nil { - errList = multierror.Append(errList, err) - return - } - }(svc) + serviceList = append(serviceList, svc) } } - wg.Wait() + + serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if serviceGroupErr != nil { + errList = multierror.Append(errList, serviceGroupErr...) + } + err = errList.ErrorOrNil() } @@ -2202,7 +2272,31 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func updateProductGroup(productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func intervalExecutor(interval time.Duration, serviceList []interface{}, handler func(data interface{}, log *zap.SugaredLogger) error, log *zap.SugaredLogger) []error { + if len(serviceList) == 0 { + return nil + } + wg := sync.WaitGroup{} + wg.Add(len(serviceList)) + errList := make([]error, 0) + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for _, data := range serviceList { + go func() { + defer wg.Done() + err := handler(data, log) + if err != nil { + errList = append(errList, err) + } + }() + <-ticker.C + } + wg.Wait() + return errList +} + +func updateProductGroup(username, productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { var ( renderChartMap = make(map[string]*template.RenderChart) productServiceMap = make(map[string]*commonmodels.ProductService) @@ -2224,10 +2318,8 @@ func updateProductGroup(productName, envName, updateType string, productResp *co } } - for _, serviceGroup := range productResp.Services { - for _, service := range serviceGroup { - productTemplServiceMap[service.ServiceName] = service - } + for _, service := range productResp.GetServiceMap() { + productTemplServiceMap[service.ServiceName] = service } // 找到环境里面还存在但是服务编排里面已经删除的服务卸载掉 @@ -2239,8 +2331,8 @@ func updateProductGroup(productName, envName, updateType string, productResp *co ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), Namespace: namespace, Wait: true, - Force: true, - Timeout: Timeout * time.Second * 10, + //Force: true, + //Timeout: Timeout * time.Second * 10, }); err != nil { log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) } @@ -2249,7 +2341,7 @@ func updateProductGroup(productName, envName, updateType string, productResp *co } //比较当前环境中的变量和系统默认的最新变量 - renderSet, err := diffRenderSet(productName, envName, updateType, productResp, overrideCharts, log) + renderSet, err := diffRenderSet(username, productName, envName, updateType, productResp, overrideCharts, log) if err != nil { return e.ErrUpdateEnv.AddDesc("对比环境中的value.yaml和系统默认的value.yaml失败") } @@ -2266,47 +2358,48 @@ func updateProductGroup(productName, envName, updateType string, productResp *co renderChartMap[renderChart.ServiceName] = renderChart } + handler := func(data interface{}, log *zap.SugaredLogger) error { + service := data.(*commonmodels.ProductService) + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: service.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("failed to find service with opt %+v, err: %s", opt, err) + return errors.Wrapf(err, "failed to find template servce %s", service.ServiceName) + } + renderChart := renderChartMap[service.ServiceName] + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) + if err != nil { + return errors.Wrapf(err, "failed to install or upgrade service %s", service.ServiceName) + } + return nil + } + errList := new(multierror.Error) for groupIndex, services := range productResp.Services { - var wg sync.WaitGroup + serviceList := make([]interface{}, 0) for _, svc := range services { - renderChart, ok := renderChartMap[svc.ServiceName] + _, ok := renderChartMap[svc.ServiceName] if !ok { continue } - // service is not in update list if !svcNameSet.Has(svc.ServiceName) { continue } + serviceList = append(serviceList, svc) + } - wg.Add(1) - go func(service *commonmodels.ProductService) { - defer wg.Done() - - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: service.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("Failed to find service with opt %+v, err: %s", opt, err) - errList = multierror.Append(errList, err) - return - } - - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, serviceObj, Timeout*time.Second*10, helmClient) - if err != nil { - errList = multierror.Append(errList, err) - return - } - }(svc) + serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if serviceGroupErr != nil { + errList = multierror.Append(errList, serviceGroupErr...) } - wg.Wait() if err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, services); err != nil { log.Errorf("Failed to update service group %d, err: %s", groupIndex, err) errList = multierror.Append(errList, err) @@ -2323,7 +2416,7 @@ func updateProductGroup(productName, envName, updateType string, productResp *co } // diffRenderSet 对比环境中的renderSet的值和服务的最新的renderSet的值 -func diffRenderSet(productName, envName, updateType string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { +func diffRenderSet(username, productName, envName, updateType string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { productTemp, err := templaterepo.NewProductColl().Find(productName) if err != nil { log.Errorf("[ProductTmpl.find] err: %v", err) @@ -2350,7 +2443,7 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm } newChartInfos := make([]*template.RenderChart, 0) - renderSetName := productResp.Namespace + defaultValues := "" switch updateType { case UpdateTypeSystem: for _, serviceNameGroup := range productTemp.Services { @@ -2361,12 +2454,13 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm } } case UpdateTypeEnv: - renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: productResp.Render.Revision} + renderSetOpt := &commonrepo.RenderSetFindOption{Name: productResp.Render.Name, Revision: productResp.Render.Revision} currentEnvRenderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err } + defaultValues = currentEnvRenderSet.DefaultValues // 环境里面的变量 currentEnvRenderSetMap := make(map[string]*template.RenderChart) @@ -2388,13 +2482,23 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm } } + serviceMap := productResp.GetServiceMap() for serviceName, latestChartInfo := range tmpLatestChartInfoMap { if currentChartInfo, ok := tmpCurrentChartInfoMap[serviceName]; ok { + serviceInfo := serviceMap[serviceName] + imageRelatedKey := sets.NewString() + if serviceInfo != nil { + for _, container := range serviceInfo.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } + } + } + //拿当前环境values.yaml的key的value去替换服务里面的values.yaml的相同的key的value - //TODO values.yaml should be the same in both currentChartInfo and latestChartInfo, since values.yaml can't be edited directly - newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml)) + newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml), imageRelatedKey) if err != nil { - log.Errorf("Failed to override values, err: %s", err) + log.Errorf("Failed to override values for service %s, err: %s", serviceName, err) } else { latestChartInfo.ValuesYaml = string(newValuesYaml) } @@ -2413,10 +2517,12 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm if err = commonservice.CreateHelmRenderSet( &commonmodels.RenderSet{ - Name: renderSetName, - EnvName: envName, - ProductTmpl: productName, - ChartInfos: newChartInfos, + Name: productResp.Render.Name, + EnvName: envName, + ProductTmpl: productName, + ChartInfos: newChartInfos, + DefaultValues: defaultValues, + UpdateBy: username, }, log, ); err != nil { @@ -2424,7 +2530,7 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm return nil, err } - renderSet, err := FindHelmRenderSet(productName, renderSetName, log) + renderSet, err := FindHelmRenderSet(productName, productResp.Render.Name, log) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err @@ -2432,7 +2538,9 @@ func diffRenderSet(productName, envName, updateType string, productResp *commonm return renderSet, nil } -func overrideValues(currentValuesYaml, latestValuesYaml []byte) ([]byte, error) { +// for keys exist in both yaml, current values will override latest values +// only for images +func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey sets.String) ([]byte, error) { currentValuesMap := map[string]interface{}{} if err := yaml.Unmarshal(currentValuesYaml, ¤tValuesMap); err != nil { return nil, err @@ -2455,6 +2563,9 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte) ([]byte, error) replaceMap := make(map[string]interface{}) for key := range latestValuesFlatMap { + if !imageRelatedKey.Has(key) { + continue + } if currentValue, ok := currentValuesFlatMap[key]; ok { replaceMap[key] = currentValue } @@ -2476,7 +2587,7 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte) ([]byte, error) return yaml.Marshal(latestValuesMap) } -func updateProductVariable(productName, envName string, productResp *commonmodels.Product, log *zap.SugaredLogger) error { +func updateProductVariable(productName, envName string, productResp *commonmodels.Product, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { restConfig, err := kube.GetRESTConfig(productResp.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) @@ -2492,12 +2603,23 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } + handler := func(data interface{}, log *zap.SugaredLogger) error { + service := data.(*commonmodels.Service) + renderChart := renderChartMap[service.ServiceName] + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, helmClient) + if err != nil { + return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) + } + return err + } + errList := new(multierror.Error) for groupIndex, services := range productResp.Services { + serviceList := make([]interface{}, 0) var wg sync.WaitGroup groupServices := make([]*commonmodels.ProductService, 0) for _, service := range services { - if renderChart, isExist := renderChartMap[service.ServiceName]; isExist { + if _, isExist := renderChartMap[service.ServiceName]; isExist { opt := &commonrepo.ServiceFindOption{ ServiceName: service.ServiceName, Type: service.Type, @@ -2507,19 +2629,15 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } serviceObj, err := commonrepo.NewServiceColl().Find(opt) if err != nil { - log.Errorf("finded to find service %s, err %s", service.ServiceName, err.Error()) + log.Errorf("failed to find service %s, err %s", service.ServiceName, err.Error()) continue } - wg.Add(1) - go func(tmpRenderChart *template.RenderChart, currentService *commonmodels.Service) { - defer wg.Done() - - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, currentService, Timeout*time.Second*10, helmClient) - if err != nil { - errList = multierror.Append(errList, err) - return - } - }(renderChart, serviceObj) + serviceList = append(serviceList, serviceObj) + + groupServiceErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if groupServiceErr != nil { + errList = multierror.Append(errList, groupServiceErr...) + } } groupServices = append(groupServices, service) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 45a58c96..f8c5a8d6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -217,7 +217,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P eventStart := time.Now().Unix() - go installProductHelmCharts(user, args.EnvName, requestID, args, eventStart, helmClient, log) + go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 34589fe2..f44cc312 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -275,7 +275,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri targetChart.ValuesYaml = replacedValuesYaml // merge override values and kvs into service's yaml - mergedValuesYaml, err = helmtool.MergeOverrideValues(replacedValuesYaml, targetChart.GetOverrideYaml(), targetChart.OverrideValues) + mergedValuesYaml, err = helmtool.MergeOverrideValues(replacedValuesYaml, renderSet.DefaultValues, targetChart.GetOverrideYaml(), targetChart.OverrideValues) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 5bd11d00..f87764d2 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -21,21 +21,31 @@ import ( "strings" "sync" - "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" - "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "github.com/koderover/zadig/pkg/setting" - e "github.com/koderover/zadig/pkg/tool/errors" yamlutil "github.com/koderover/zadig/pkg/util/yaml" + "github.com/pkg/errors" ) +type DefaultValuesResp struct { + DefaultValues string `json:"defaultValues"` +} + +type YamlContentRequestArg struct { + CodehostID int `json:"codehostID" form:"codehostID"` + Owner string `json:"owner" form:"owner"` + Repo string `json:"repo" form:"repo"` + Branch string `json:"branch" form:"branch"` + RepoLink string `json:"repoLink" form:"repoLink"` + ValuesPaths string `json:"valuesPaths" form:"valuesPaths"` +} + func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*commonservice.RenderChartArg, error) { renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") @@ -77,158 +87,81 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL return ret, nil } -// validate yaml content -func validateYamlContent(yamlContent string) error { - tMap := map[string]interface{}{} - if err := yaml.Unmarshal([]byte(yamlContent), &tMap); err != nil { - if err != nil { - return errors.New("yaml content illegal") - } - } - return nil -} - -func generateValuesYaml(args *commonservice.RenderChartArg, log *zap.SugaredLogger) (string, error) { - if args.YamlSource == setting.ValuesYamlSourceFreeEdit { - return args.ValuesYAML, validateYamlContent(args.ValuesYAML) - } else if args.YamlSource == setting.ValuesYamlSourceGitRepo { - if args.GitRepoConfig == nil { - return "", errors.New("invalid repo config") - } - var ( - allValues []byte - fileContentMap sync.Map - wg sync.WaitGroup - err error - ) - - for index, filePath := range args.GitRepoConfig.ValuesPaths { - wg.Add(1) - go func(index int, path string) { - defer wg.Done() - fileContent, err1 := fsservice.DownloadFileFromSource( - &fsservice.DownloadFromSourceArgs{ - CodehostID: args.GitRepoConfig.CodehostID, - Owner: args.GitRepoConfig.Owner, - Repo: args.GitRepoConfig.Repo, - Path: path, - Branch: args.GitRepoConfig.Branch, - }) - if err1 != nil { - err = errors.Errorf("fail to download file from git, err: %s, path: %s, repo: %v", err1.Error(), path, *args.GitRepoConfig) - return - } - fileContentMap.Store(index, fileContent) - }(index, filePath) - } - wg.Wait() - - if err != nil { - return "", err - } - - allValueYamls := make([][]byte, len(args.GitRepoConfig.ValuesPaths), len(args.GitRepoConfig.ValuesPaths)) - for i := 0; i < len(args.GitRepoConfig.ValuesPaths); i++ { - contentObj, _ := fileContentMap.Load(i) - allValueYamls[i] = contentObj.([]byte) - } - allValues, err = yamlutil.Merge(allValueYamls) - if err != nil { - return "", errors.Errorf("failed to merge yaml files, repo: %v", *args.GitRepoConfig) - } - return string(allValues), nil - } - return "", nil -} - -func CreateOrUpdateChartValues(productName, envName string, args *commonservice.RenderChartArg, userName, requestID string, log *zap.SugaredLogger) error { +func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*DefaultValuesResp, error) { + ret := &DefaultValuesResp{} - serviceName := args.ServiceName - - serviceOpt := &commonrepo.ServiceFindOption{ - ProductName: productName, - ServiceName: serviceName, - Type: setting.HelmDeployType, + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err == mongo.ErrNoDocuments { + return ret, nil } - serviceObj, err := commonrepo.NewServiceColl().Find(serviceOpt) if err != nil { - return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get service %s", err.Error())) - } - if serviceObj == nil { - return e.ErrCreateRenderSet.AddDesc("service not found") + log.Errorf("failed to query product info, productName %s envName %s err %s", productName, envName, err) + return nil, fmt.Errorf("failed to query product info, productName %s envName %s", productName, envName) } - if serviceObj.HelmChart == nil { - return e.ErrCreateRenderSet.AddDesc("missing helm chart info") + if productInfo.Render == nil { + return nil, fmt.Errorf("invalid product, nil render data") } - yamlContent, err := generateValuesYaml(args, log) - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) + opt := &commonrepo.RenderSetFindOption{ + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, } - args.ValuesYAML = yamlContent - - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - - opt := &commonrepo.RenderSetFindOption{Name: renderSetName} - curRenderset, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { - return e.ErrCreateRenderSet.AddDesc(fmt.Sprintf("failed to get renderset %s", err.Error())) - } - - if !found { - curRenderset = &commonmodels.RenderSet{ - Name: renderSetName, - EnvName: envName, - ProductTmpl: productName, - UpdateBy: userName, - IsDefault: false, - } - } - - //update or insert service values.yaml - var targetChartInfo *template.RenderChart - for _, singleChart := range curRenderset.ChartInfos { - if singleChart.ServiceName != serviceName { - continue - } - args.FillRenderChartModel(singleChart, singleChart.ChartVersion) - targetChartInfo = singleChart - break + log.Errorf("failed to query renderset info, name %s err %s", productInfo.Render.Name, err) + return nil, err } - if targetChartInfo == nil { - targetChartInfo = new(template.RenderChart) - targetChartInfo.ValuesYaml = serviceObj.HelmChart.ValuesYaml - args.FillRenderChartModel(targetChartInfo, serviceObj.HelmChart.Version) - curRenderset.ChartInfos = append(curRenderset.ChartInfos, targetChartInfo) + if !existed { + return ret, nil } + ret.DefaultValues = rendersetObj.DefaultValues + return ret, nil +} - //create new renderset with increased revision - err = commonservice.CreateHelmRenderSet( - curRenderset, - log, +func GetMergedYamlContent(arg *YamlContentRequestArg, paths []string) (string, error) { + var ( + fileContentMap sync.Map + wg sync.WaitGroup + err error ) - - if err != nil { - return e.ErrCreateRenderSet.AddDesc(err.Error()) + for i, filePath := range paths { + wg.Add(1) + go func(index int, path string) { + defer wg.Done() + fileContent, errDownload := fsservice.DownloadFileFromSource( + &fsservice.DownloadFromSourceArgs{ + CodehostID: arg.CodehostID, + Owner: arg.Owner, + Repo: arg.Repo, + Path: path, + Branch: arg.Branch, + RepoLink: arg.RepoLink, + }) + if errDownload != nil { + err = errors.Wrapf(errDownload, fmt.Sprintf("failed to download file from git, path %s", path)) + return + } + fileContentMap.Store(index, fileContent) + }(i, filePath) } - return err -} + wg.Wait() -func ensureHelmRenderSetArgs(args *commonmodels.RenderSet) error { - if args == nil { - return errors.New("nil RenderSet") + if err != nil { + return "", err } - if len(args.Name) == 0 { - return errors.New("empty render set name") + contentArr := make([][]byte, 0, len(paths)) + for i := 0; i < len(paths); i++ { + contentObj, _ := fileContentMap.Load(i) + contentArr = append(contentArr, contentObj.([]byte)) } - // 设置新的版本号 - rev, err := commonrepo.NewCounterColl().GetNextSeq("renderset:" + args.Name) + ret, err := yamlutil.Merge(contentArr) if err != nil { - return fmt.Errorf("get next render set revision error: %v", err) + return "", errors.Wrapf(err, "failed to merge files") } - - args.Revision = rev - return nil + return string(ret), nil } diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index d0131340..d8a5c4d1 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -44,9 +44,9 @@ func (*Router) Inject(router *gin.RouterGroup) { product.POST("", gin2.UpdateOperationLogStatus, CreateProductTemplate) product.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProductTemplate) product.PUT("/:name/:status", gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) + product.PATCH("/:name", gin2.UpdateOperationLogStatus, UpdateServiceOrchestration) product.PUT("", gin2.UpdateOperationLogStatus, UpdateProject) product.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProductTemplate) - product.PATCH("/:name", UpdateServiceOrchestration) } openSource := router.Group("opensource") diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index cabd3fc5..29010f33 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -393,7 +393,12 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(v) } }() - + // delete servicesInExternalEnv data + go func() { + _ = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + }) + }() return nil } diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 90555c12..b0b15a8c 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -199,6 +199,10 @@ func initDatabase() { commonrepo.NewWorkflowColl(), commonrepo.NewWorkflowStatColl(), commonrepo.NewWorkLoadsStatColl(), + commonrepo.NewServicesInExternalEnvColl(), + + templaterepo.NewChartColl(), + templaterepo.NewDockerfileTemplateColl(), templaterepo.NewChartColl(), templaterepo.NewDockerfileTemplateColl(), diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 5cf4e4dd..d0f1fa95 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -46,6 +46,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -53,19 +54,9 @@ import ( ) type HelmService struct { - Services []*models.Service `json:"services"` - FileInfos []*types.FileInfo `json:"file_infos"` -} - -type HelmServiceReq struct { - ProductName string `json:"product_name"` - CreateBy string `json:"create_by"` - CodehostID int `json:"codehost_id"` - RepoOwner string `json:"repo_owner"` - RepoName string `json:"repo_name"` - BranchName string `json:"branch_name"` - FilePaths []string `json:"file_paths"` - SrcPath string `json:"src_path"` + ServiceInfos []*models.Service `json:"service_infos"` + FileInfos []*types.FileInfo `json:"file_infos"` + Services [][]string `json:"services"` } type HelmServiceArgs struct { @@ -96,7 +87,7 @@ type Chart struct { type helmServiceCreationArgs struct { ChartName string ChartVersion string - ValuesYAML string + MergedValues string ServiceName string FilePath string ProductName string @@ -122,8 +113,9 @@ type ChartTemplateData struct { func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { helmService := &HelmService{ - Services: []*models.Service{}, - FileInfos: []*types.FileInfo{}, + ServiceInfos: []*models.Service{}, + FileInfos: []*types.FileInfo{}, + Services: [][]string{}, } opt := &commonrepo.ServiceListOption{ @@ -136,7 +128,7 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, log.Errorf("[helmService.list] err:%v", err) return nil, e.ErrListTemplate.AddErr(err) } - helmService.Services = services + helmService.ServiceInfos = services if len(services) > 0 { fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, "") @@ -146,6 +138,13 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, } helmService.FileInfos = fis } + project, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + log.Errorf("Failed to find project info, err: %s", err) + return nil, e.ErrListTemplate.AddErr(err) + } + helmService.Services = project.Services + return helmService, nil } @@ -266,21 +265,6 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe values = append(values, templateChartInfo.DefaultValuesYAML) } - for _, path := range templateArgs.ValuesPaths { - v, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ - CodehostID: templateArgs.CodehostID, - Owner: templateArgs.Owner, - Repo: templateArgs.Repo, - Path: path, - Branch: templateArgs.Branch, - }) - if err != nil { - return err - } - - values = append(values, v) - } - if len(templateArgs.ValuesYAML) > 0 { values = append(values, []byte(templateArgs.ValuesYAML)) } @@ -291,6 +275,11 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe // copy template to service path and update the values.yaml from := filepath.Join(localBase, base) to := filepath.Join(config.LocalServicePath(projectName, args.Name), args.Name) + // remove old files + if err = os.RemoveAll(to); err != nil { + logger.Errorf("Failed to remove dir %s, err: %s", to, err) + return err + } if err = copy.Copy(from, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", from, to, err) return err @@ -319,18 +308,13 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe &helmServiceCreationArgs{ ChartName: templateChartInfo.ChartName, ChartVersion: templateChartInfo.ChartVersion, - ValuesYAML: string(merged), + MergedValues: string(merged), ServiceName: args.Name, FilePath: to, ProductName: projectName, CreateBy: args.CreatedBy, - CodehostID: templateArgs.CodehostID, - Owner: templateArgs.Owner, - Repo: templateArgs.Repo, - Branch: templateArgs.Branch, Source: setting.SourceFromChartTemplate, HelmTemplateName: templateArgs.TemplateName, - ValuePaths: templateArgs.ValuesPaths, ValuesYaml: templateArgs.ValuesYAML, }, logger, @@ -352,16 +336,16 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil } -func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, error) { +func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry.CodeHost, error) { if repoLink != "" { - return setting.SourceFromPublicRepo, nil + return setting.SourceFromPublicRepo, nil, nil } ch, err := codehost.GetCodeHostInfoByID(repoArgs.CodehostID) if err != nil { log.Errorf("Failed to get codeHost by id %d, err: %s", repoArgs.CodehostID, err.Error()) - return "", err + return "", ch, err } - return ch.Type, nil + return ch.Type, ch, nil } func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) error { @@ -383,7 +367,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC repoLink = publicArgs.RepoLink } - source, err := getCodehostType(repoArgs, repoLink) + source, codehostInfo, err := getCodehostType(repoArgs, repoLink) if err != nil { log.Errorf("Failed to get source form repo data %+v, err: %s", *repoArgs, err.Error()) return err @@ -423,7 +407,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC if err != nil { return serviceName, err } - valuesYAML, _, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + valuesYAML, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) return serviceName, err }) if err != nil { @@ -441,12 +425,16 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } + if source != setting.SourceFromPublicRepo && codehostInfo != nil { + repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) + } + svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ ChartName: serviceName, ChartVersion: chartVersion, - ValuesYAML: string(valuesYAML), + MergedValues: string(valuesYAML), ServiceName: serviceName, FilePath: filePath, ProductName: projectName, @@ -588,6 +576,11 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi serviceName = strings.TrimSuffix(serviceName, filepath.Ext(serviceName)) to := filepath.Join(config.LocalServicePath(projectName, serviceName), serviceName) + // remove old files + if err = os.RemoveAll(to); err != nil { + logger.Errorf("Failed to remove dir %s, err: %s", to, err) + return nil, err + } if err = copy.Copy(fromPath, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", fromPath, to, err) return nil, err @@ -611,7 +604,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi &helmServiceCreationArgs{ ChartName: templateChartData.ChartName, ChartVersion: templateChartData.ChartVersion, - ValuesYAML: string(mergedValues), + MergedValues: string(mergedValues), ServiceName: serviceName, FilePath: to, ProductName: projectName, @@ -620,7 +613,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi Source: setting.SourceFromChartTemplate, HelmTemplateName: templateChartData.TemplateName, ValuePaths: []string{path}, - ValuesYaml: string(templateChartData.DefaultValuesYAML), + ValuesYaml: string(valuesYAML), }, logger, ) @@ -651,20 +644,13 @@ func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (str return chart.Name, chart.Version, nil } -func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, map[string]interface{}, error) { +func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, error) { content, err := fs.ReadFile(chartTree, filepath.Join(base, setting.ValuesYaml)) if err != nil { logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) - return nil, nil, err - } - - valuesMap := make(map[string]interface{}) - if err = yaml.Unmarshal(content, &valuesMap); err != nil { - logger.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ValuesYaml, err) - return nil, nil, err + return nil, err } - - return content, valuesMap, nil + return content, nil } func geneCreationDetail(args *helmServiceCreationArgs) interface{} { @@ -672,8 +658,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { case setting.SourceFromGitlab, setting.SourceFromGithub, setting.SourceFromGerrit, - setting.SourceFromCodeHub, - setting.SourceFromIlyshin: + setting.SourceFromCodeHub: return &models.CreateFromRepo{ GitRepoConfig: &templatemodels.GitRepoConfig{ CodehostID: args.CodehostID, @@ -689,24 +674,8 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { LoadPath: args.FilePath, } case setting.SourceFromChartTemplate: - var yamlData *template.CustomYaml - if args.CodehostID > 0 { - yamlData = &template.CustomYaml{ - YamlSource: setting.ValuesYamlSourceGitRepo, - YamlContent: "", - GitRepoConfig: &templatemodels.GitRepoConfig{ - CodehostID: args.CodehostID, - Owner: args.Owner, - Repo: args.Repo, - Branch: args.Branch, - }, - ValuesPaths: args.ValuePaths, - } - } else if len(args.ValuesYaml) > 0 { - yamlData = &template.CustomYaml{ - YamlSource: setting.ValuesYamlSourceFreeEdit, - YamlContent: args.ValuesYaml, - } + yamlData := &template.CustomYaml{ + YamlContent: args.ValuesYaml, } return &models.CreateFromChartTemplate{ @@ -725,12 +694,13 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - values, valuesMap, err := readValuesYAML(fsTree, args.ServiceName, logger) + valuesYaml := args.MergedValues + valuesMap := make(map[string]interface{}) + err = yaml.Unmarshal([]byte(valuesYaml), &valuesMap) if err != nil { - logger.Errorf("Failed to read values.yaml, err %s", err) + logger.Errorf("Failed to unmarshall yaml, err %s", err) return nil, err } - valuesYaml := string(values) serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 6fc46294..4c9434af 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -23,7 +23,6 @@ import ( "io/ioutil" "os" "path" - "path/filepath" "strings" "github.com/google/go-github/v35/github" @@ -40,7 +39,6 @@ import ( "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/ilyshin" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) @@ -67,8 +65,6 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b ret, err = preloadGerritService(ch, repoName, branchName, remoteName, path, isDir) case setting.SourceFromCodeHub: ret, err = preloadCodehubService(ch, repoName, repoUUID, branchName, path, isDir) - case setting.SourceFromIlyshin: - ret, err = preloadIlyshinService(ch, repoOwner, repoName, branchName, path, isDir) default: return nil, e.ErrPreloadServiceTemplate.AddDesc("Not supported code source") } @@ -90,8 +86,6 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoNam return loadGerritService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) case setting.SourceFromCodeHub: return loadCodehubService(username, ch, repoOwner, repoName, repoUUID, branchName, args, log) - case setting.SourceFromIlyshin: - return loadIlyshinService(username, ch, repoOwner, repoName, branchName, args, log) default: return e.ErrLoadServiceTemplate.AddDesc("unsupported code source") } @@ -113,8 +107,6 @@ func ValidateServiceUpdate(codehostID int, serviceName, repoOwner, repoName, rep return validateServiceUpdateGerrit(detail, serviceName, repoName, branchName, remoteName, path, isDir) case setting.SourceFromCodeHub: return validateServiceUpdateCodehub(detail, serviceName, repoName, repoUUID, branchName, path, isDir) - case setting.SourceFromIlyshin: - return validateServiceUpdateIlyshin(detail, serviceName, repoOwner, repoName, branchName, path, isDir) default: return e.ErrValidateServiceUpdate.AddDesc("Not supported code source") } @@ -246,64 +238,6 @@ func preloadCodehubService(detail *poetry.CodeHost, repoName, repoUUID, branchNa return ret, nil } -// 根据 repo 信息获取 ilyshin 可以加载的服务列表 -func preloadIlyshinService(detail *poetry.CodeHost, repoOwner, repoName, branchName, path string, isDir bool) ([]string, error) { - ret := make([]string, 0) - - ilyshinClient := ilyshin.NewClient(detail.Address, detail.AccessToken) - // 非文件夹情况下直接获取文件信息 - if !isDir { - if !isYaml(path) { - return ret, e.ErrPreloadServiceTemplate.AddDesc("File is not of type yaml or yml, select again") - } - fileInfo, err := ilyshinClient.GetFile(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get file info from ilyshin with path: %s, the error is %+v", path, err) - return ret, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) - } - extension := filepath.Ext(fileInfo.FileName) - fileName := fileInfo.FileName[0 : len(fileInfo.FileName)-len(extension)] - ret = append(ret, fileName) - return ret, nil - } - - treeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is: %+v", path, err) - return ret, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(treeInfo) { - svcName := path - if path == "" { - svcName = repoName - } - pathList := strings.Split(svcName, "/") - folderName := pathList[len(pathList)-1] - ret = append(ret, folderName) - return ret, nil - } - isGrandparent := false - for _, entry := range treeInfo { - if entry.Type == "tree" { - subtreeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is: %+v", path, err) - return ret, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(subtreeInfo) { - isGrandparent = true - ret = append(ret, entry.Name) - } - } - } - if !isGrandparent { - log.Errorf("invalid folder selected since no yaml is presented in path: %s", path) - return ret, e.ErrPreloadServiceTemplate.AddDesc("所选路径下没有yaml,请重新选择") - } - - return ret, nil -} - // 根据repo信息从gerrit加载服务 func loadGerritService(username string, ch *poetry.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { base := path.Join(config.S3StoragePath(), repoName) @@ -569,124 +503,6 @@ func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeN return err } -// 根据 repo 信息从 Ilyshin 加载服务 -func loadIlyshinService(username string, ch *poetry.CodeHost, repoOwner, repoName, branchName string, args *LoadServiceReq, log *zap.SugaredLogger) error { - ilyshinClient := ilyshin.NewClient(ch.Address, ch.AccessToken) - repoInfo := fmt.Sprintf("%s/%s", repoOwner, repoName) - if !args.LoadFromDir { - file, err := ilyshinClient.GetFile(repoOwner, repoName, branchName, args.LoadPath) - if err != nil { - log.Errorf("Failed to get file info for path: %s from ilyshin, the error is: %+v", args.LoadPath, err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - decodedContent, err := base64.StdEncoding.DecodeString(file.Content) - if err != nil { - log.Errorf("Failed to decode file, the error is: %+v", err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - srcPath := fmt.Sprintf("%s/%s/blob/%s/%s", ch.Address, repoInfo, branchName, args.LoadPath) - splittedYaml := SplitYaml(string(decodedContent)) - createSvcArgs := &models.Service{ - CodehostID: ch.ID, - RepoOwner: repoOwner, - RepoName: repoName, - BranchName: branchName, - LoadPath: args.LoadPath, - LoadFromDir: args.LoadFromDir, - KubeYamls: splittedYaml, - SrcPath: srcPath, - CreateBy: username, - ServiceName: getFileName(file.FileName), - Type: args.Type, - ProductName: args.ProductName, - Source: setting.SourceFromIlyshin, - Yaml: string(decodedContent), - Commit: &models.Commit{SHA: file.CommitID}, - Visibility: args.Visibility, - } - - _, err = CreateServiceTemplate(username, createSvcArgs, log) - if err != nil { - _, messageMap := e.ErrorMessage(err) - if description, ok := messageMap["description"]; ok { - return e.ErrLoadServiceTemplate.AddDesc(description.(string)) - } - return e.ErrLoadServiceTemplate.AddDesc("Load Service Error for unknown reason") - } - return nil - } - - treeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, args.LoadPath) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is: %+v", args.LoadPath, err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(treeInfo) { - return loadServiceFromIlyshin(ilyshinClient, treeInfo, ch, username, repoOwner, repoName, branchName, args.LoadPath, args, log) - } - for _, treeNode := range treeInfo { - if treeNode.Type == "tree" { - subtree, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, args.LoadPath) - if err != nil { - log.Errorf("Failed to get dir content from ilyshin with path: %s, the error is %+v", treeNode.Path, err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(subtree) { - if err := loadServiceFromIlyshin(ilyshinClient, subtree, ch, username, repoOwner, repoName, branchName, treeNode.Path, args, log); err != nil { - return err - } - } - } - } - return nil -} - -func loadServiceFromIlyshin(client *ilyshin.Client, tree []*ilyshin.TreeNode, ch *poetry.CodeHost, username, repoOwner, repoName, branchName, path string, args *LoadServiceReq, log *zap.SugaredLogger) error { - pathList := strings.Split(path, "/") - splittedYaml := []string{} - serviceName := pathList[len(pathList)-1] - repoInfo := fmt.Sprintf("%s/%s", repoOwner, repoName) - yamlList, sha, err := extractIlyshinYamls(client, tree, repoOwner, repoName, branchName) - if err != nil { - log.Errorf("Failed to extract yamls from ilyshin, the error is: %+v", err) - return e.ErrLoadServiceTemplate.AddDesc(err.Error()) - } - for _, yamlEntry := range yamlList { - splittedYaml = append(splittedYaml, SplitYaml(yamlEntry)...) - } - yml := joinYamls(yamlList) - srcPath := fmt.Sprintf("%s/%s/tree/%s/%s", ch.Address, repoInfo, branchName, path) - createSvcArgs := &models.Service{ - CodehostID: ch.ID, - RepoOwner: repoOwner, - RepoName: repoName, - BranchName: branchName, - LoadPath: path, - LoadFromDir: args.LoadFromDir, - KubeYamls: splittedYaml, - SrcPath: srcPath, - CreateBy: username, - ServiceName: serviceName, - Type: args.Type, - ProductName: args.ProductName, - Source: setting.SourceFromIlyshin, - Yaml: yml, - Commit: &models.Commit{SHA: sha}, - Visibility: args.Visibility, - } - - _, err = CreateServiceTemplate(username, createSvcArgs, log) - if err != nil { - _, messageMap := e.ErrorMessage(err) - if description, ok := messageMap["description"]; ok { - err = e.ErrLoadServiceTemplate.AddDesc(description.(string)) - } else { - err = e.ErrLoadServiceTemplate.AddDesc("Load Service Error for unknown reason") - } - } - return err -} - // 根据github repo决定服务是否可以更新这个repo地址 func validateServiceUpdateGithub(detail *poetry.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { ctx := context.Background() @@ -879,47 +695,6 @@ func validateServiceUpdateCodehub(detail *poetry.CodeHost, serviceName, repoName return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") } -// 根据 ilyshin repo 决定服务是否可以更新这个 repo 地址 -func validateServiceUpdateIlyshin(detail *poetry.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { - ilyshinClient := ilyshin.NewClient(detail.Address, detail.AccessToken) - // 非文件夹情况下直接获取文件信息 - if !isDir { - if !isYaml(path) { - return e.ErrValidateServiceUpdate.AddDesc("File is not of type yaml or yml, select again") - } - fileInfo, err := ilyshinClient.GetFile(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get file info from ilyshin with path: %s, the error is %+v", path, err) - return e.ErrValidateServiceUpdate.AddDesc(err.Error()) - } - if getFileName(fileInfo.FileName) != serviceName { - log.Errorf("The loaded file name [%s] is the same as the service to be updated: [%s]", fileInfo.FileName, serviceName) - return e.ErrValidateServiceUpdate.AddDesc("文件名称和服务名称不一致") - } - return nil - } - - treeInfo, err := ilyshinClient.ListTree(repoOwner, repoName, branchName, path) - if err != nil { - log.Errorf("Failed to get dir content from gitlab with path: %s, the error is: %+v", path, err) - return e.ErrValidateServiceUpdate.AddDesc(err.Error()) - } - if isValidIlyshinServiceDir(treeInfo) { - svcName := path - if path == "" { - svcName = repoName - } - pathList := strings.Split(svcName, "/") - folderName := pathList[len(pathList)-1] - if folderName != serviceName { - log.Errorf("The loaded file name [%s] is the same as the service to be updated: [%s]", folderName, serviceName) - return e.ErrValidateServiceUpdate.AddDesc("文件夹名称和服务名称不一致") - } - return nil - } - return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") -} - func isValidGithubServiceDir(child []*github.RepositoryContent) bool { for _, entry := range child { if entry.GetType() == "file" && isYaml(entry.GetName()) { @@ -938,15 +713,6 @@ func isValidGitlabServiceDir(child []*gitlab.TreeNode) bool { return false } -func isValidIlyshinServiceDir(child []*ilyshin.TreeNode) bool { - for _, entry := range child { - if entry.Type == "blob" && isYaml(entry.Name) { - return true - } - } - return false -} - func isValidGerritServiceDir(child []os.FileInfo) bool { for _, file := range child { if !file.IsDir() && isYaml(file.Name()) { @@ -999,25 +765,3 @@ func extractGerritYamls(basePath string, tree []os.FileInfo) ([]string, error) { } return ret, nil } - -func extractIlyshinYamls(client *ilyshin.Client, tree []*ilyshin.TreeNode, repoOwner, repoName, branchName string) ([]string, string, error) { - ret := []string{} - var sha string - for _, entry := range tree { - if isYaml(entry.Name) { - fileInfo, err := client.GetFile(repoOwner, repoName, branchName, entry.Path) - if err != nil { - log.Errorf("Failed to download ilyshin file: %s, the error is: %+v", entry.Path, err) - return nil, "", err - } - decodedFile, err := base64.StdEncoding.DecodeString(fileInfo.Content) - if err != nil { - log.Errorf("Failed to decode content from the given file of path: %s, the error is: %s", entry.Path, err) - return nil, "", err - } - ret = append(ret, string(decodedFile)) - sha = fileInfo.CommitID - } - } - return ret, sha, nil -} diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 56373af0..17cf5c53 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -249,8 +249,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv } if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || - args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub || - args.Source == setting.SourceFromIlyshin { + args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub { serviceOption.Yaml = args.Yaml } return serviceOption, nil @@ -265,7 +264,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product // 检查环境是否存在,envName和productName唯一 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} if _, err := commonrepo.NewProductColl().Find(opt); err == nil { - log.Errorf("[%s][P:%s] duplicate envName in the same product", envName, productName) + log.Errorf("[%s][P:%s] duplicate envName in the same project", envName, productName) return e.ErrCreateEnv.AddDesc(e.DuplicateEnvErrMsg) } @@ -275,22 +274,32 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product mu sync.Mutex ) - // pre judge workLoads same name serviceString := sets.NewString() services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") for _, v := range services { serviceString.Insert(v.ServiceName) } - for _, workload := range workLoads { - if serviceString.Has(workload.Name) { - return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", workload.Name)) - } - } + //for _, workload := range workLoads { + // if serviceString.Has(workload.Name) { + // return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", workload.Name)) + // } + //} g := new(errgroup.Group) for _, workload := range workLoads { tempWorkload := workload g.Go(func() error { + // If the service is already included in the database service template, add it to the new association table + if serviceString.Has(tempWorkload.Name) { + return commonrepo.NewServicesInExternalEnvColl().Create(&commonmodels.ServicesInExternalEnv{ + ProductName: productName, + ServiceName: tempWorkload.Name, + EnvName: envName, + Namespace: namespace, + ClusterID: clusterID, + }) + } + var bs []byte switch tempWorkload.Type { case setting.Deployment: @@ -341,6 +350,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product ClusterID: clusterID, EnvName: envName, Namespace: namespace, + UpdateBy: username, }, log); err != nil { return e.ErrCreateProduct.AddDesc("create product Error for unknown reason") } @@ -385,6 +395,19 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN log.Errorf("[%s][%s]NewWorkLoadsStatColl().Find %s", args.ClusterID, args.Namespace, err) return err } + externalEnvServices, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }) + + for _, externalEnvService := range externalEnvServices { + workloadStat.Workloads = append(workloadStat.Workloads, commonmodels.Workload{ + ProductName: externalEnvService.ProductName, + EnvName: externalEnvService.EnvName, + Name: externalEnvService.ServiceName, + }) + } + diff := map[string]*ServiceWorkloadsUpdateAction{} originSet := sets.NewString() uploadSet := sets.NewString() @@ -393,6 +416,7 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN originSet.Insert(v.Name) } } + for _, v := range args.WorkLoads { uploadSet.Insert(v.Name) } @@ -414,12 +438,12 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } // pre judge workLoads same name - services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") - for _, v := range services { - if addString.Has(v.ServiceName) { - return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", v.ServiceName)) - } - } + //services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") + //for _, v := range services { + // if addString.Has(v.ServiceName) { + // return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", v.ServiceName)) + // } + //} for _, v := range args.WorkLoads { if addString.Has(v.Name) { @@ -433,13 +457,34 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } + otherExternalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + ExcludeEnvName: envName, + }) + if err != nil { + log.Errorf("failed to list external service, error:%s", err) + } + + externalEnvServiceM := make(map[string]bool) + for _, externalEnvService := range otherExternalEnvServices { + externalEnvServiceM[externalEnvService.ServiceName] = true + } for _, v := range diff { switch v.Operation { // 删除workload的引用 case "delete": - err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) - if err != nil { - log.Errorf("UpdateStatus external services error:%s", err) + if _, isExist := externalEnvServiceM[v.Name]; !isExist { + err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) + if err != nil { + log.Errorf("UpdateStatus external services error:%s", err) + } + } + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + ServiceName: v.Name, + }); err != nil { + log.Errorf("delete services in external env error:%s", err) } // 添加workload的引用 case "add": @@ -545,7 +590,7 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za //获取项目里面的所有服务 if len(productTempl.Services) > 0 && !sets.NewString(productTempl.Services[0]...).Has(args.ServiceName) { productTempl.Services[0] = append(productTempl.Services[0], args.ServiceName) - } else { + } else if len(productTempl.Services) == 0 { productTempl.Services = [][]string{{args.ServiceName}} } //更新项目模板 @@ -556,7 +601,21 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za } } } else { - return e.ErrCreateTemplate.AddDesc("do not support import same service name") + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return err + } + return commonrepo.NewServicesInExternalEnvColl().Create(&commonmodels.ServicesInExternalEnv{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + EnvName: args.EnvName, + Namespace: product.Namespace, + ClusterID: product.ClusterID, + }) + //return e.ErrCreateTemplate.AddDesc("do not support import same service name") } if err := commonrepo.NewServiceColl().Delete(args.ServiceName, args.Type, args.ProductName, setting.ProductStatusDeleting, 0); err != nil { @@ -599,7 +658,7 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap } } // 配置来源为Gitlab,对比配置的ChangeLog是否变化 - if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromIlyshin { + if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub { if args.Commit != nil && serviceTmpl.Commit != nil && args.Commit.SHA == serviceTmpl.Commit.SHA { log.Infof("%s change log remains the same, quit creation", args.Source) return GetServiceOption(serviceTmpl, log) diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index 0edc04a4..a09a4477 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -79,13 +79,8 @@ type CreateFromPublicRepo struct { } type CreateFromChartTemplate struct { - CodehostID int `json:"codehostID,omitempty"` - Owner string `json:"owner,omitempty"` - Repo string `json:"repo,omitempty"` - Branch string `json:"branch,omitempty"` - TemplateName string `json:"templateName"` - ValuesPaths []string `json:"valuesPaths,omitempty"` - ValuesYAML string `json:"valuesYAML,omitempty"` + TemplateName string `json:"templateName"` + ValuesYAML string `json:"valuesYAML"` } func PublicRepoToPrivateRepoArgs(args *CreateFromPublicRepo) (*CreateFromRepo, error) { diff --git a/pkg/microservice/aslan/core/workflow/handler/webhook.go b/pkg/microservice/aslan/core/workflow/handler/webhook.go index 9179049c..fb9b8a50 100644 --- a/pkg/microservice/aslan/core/workflow/handler/webhook.go +++ b/pkg/microservice/aslan/core/workflow/handler/webhook.go @@ -28,7 +28,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/webhook" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/codehub" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) // @Router /workflow/webhook [POST] @@ -51,8 +50,6 @@ func ProcessWebHook(c *gin.Context) { ctx.Err = webhook.ProcessGitlabHook(payload, c.Request, ctx.RequestID, ctx.Logger) } else if codehub.HookEventType(c.Request) != "" { ctx.Err = webhook.ProcessCodehubHook(payload, c.Request, ctx.RequestID, ctx.Logger) - } else if ilyshin.HookEventType(c.Request) != "" { - ctx.Err = webhook.ProcessIlyshinHook(payload, c.Request, ctx.RequestID, ctx.Logger) } else { ctx.Err = webhook.ProcessGerritHook(payload, c.Request, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index 724265f2..e399babf 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -118,7 +118,7 @@ func ListWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), ctx.UserID, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), c.Query("productName"), ctx.UserID, ctx.Logger) } func ListTestWorkflows(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go b/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go deleted file mode 100644 index 22fde091..00000000 --- a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin.go +++ /dev/null @@ -1,290 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package webhook - -import ( - "errors" - "fmt" - "net/http" - "strconv" - "strings" - "time" - - "github.com/hashicorp/go-multierror" - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" - workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/ilyshin" -) - -func ProcessIlyshinHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { - token := req.Header.Get("X-CodeHub-Token") - secret := gitservice.GetHookSecret() - - if secret != "" && token != secret { - return errors.New("token is illegal") - } - - eventType := ilyshin.HookEventType(req) - event, err := ilyshin.ParseHook(eventType, payload) - if err != nil { - log.Warnf("unexpected event type: %s", eventType) - return nil - } - - forwardedProto := req.Header.Get("X-Forwarded-Proto") - forwardedHost := req.Header.Get("X-Forwarded-Host") - baseURI := fmt.Sprintf("%s://%s", forwardedProto, forwardedHost) - - var pushEvent *ilyshin.PushEvent - var errorList = &multierror.Error{} - switch event := event.(type) { - case *ilyshin.PushEvent: - pushEvent = event - if len(pushEvent.Commits) > 0 { - webhookUser := &commonmodels.WebHookUser{ - Domain: req.Header.Get("X-Forwarded-Host"), - UserName: pushEvent.Commits[0].Author.Name, - Email: pushEvent.Commits[0].Author.Email, - Source: setting.SourceFromIlyshin, - CreatedAt: time.Now().Unix(), - } - commonrepo.NewWebHookUserColl().Upsert(webhookUser) - } - - if err = updateServiceTemplateByIlyshinPushEvent(pushEvent, log); err != nil { - errorList = multierror.Append(errorList, err) - } - } - - //产品工作流webhook - if err = TriggerWorkflowByIlyshinEvent(event, baseURI, requestID, log); err != nil { - errorList = multierror.Append(errorList, err) - } - - return errorList.ErrorOrNil() -} - -func updateServiceTemplateByIlyshinPushEvent(event *ilyshin.PushEvent, log *zap.SugaredLogger) error { - log.Infof("EVENT: Ilyshin WEBHOOK UPDATING SERVICE TEMPLATE") - - address, err := GetAddress(event.Project.WebURL) - if err != nil { - log.Errorf("GetAddress failed, error: %s", err) - return err - } - - client, err := getIlyshinClientByAddress(address) - if err != nil { - return err - } - - diffs, err := client.Compare(event.ProjectID, event.Before, event.After) - if err != nil { - log.Errorf("Failed to get push event diffs, error: %s", err) - return err - } - serviceTmpls, err := GetIlyshinServiceTemplates() - if err != nil { - log.Errorf("Failed to get ilyshin service templates, error: %s", err) - return err - } - - errs := &multierror.Error{} - for _, service := range serviceTmpls { - srcPath := service.SrcPath - _, _, _, _, path, _, err := GetOwnerRepoBranchPath(srcPath) - if err != nil { - errs = multierror.Append(errs, err) - } - // 判断PushEvent的Diffs中是否包含该服务模板的src_path - affected := false - for _, diff := range diffs { - if strings.Contains(diff.OldPath, path) || strings.Contains(diff.NewPath, path) { - affected = true - break - } - } - if affected { - log.Infof("Started to sync service template %s from ilyshin %s", service.ServiceName, service.SrcPath) - service.CreateBy = "system" - err := SyncServiceTemplateFromIlyshin(service, log) - if err != nil { - log.Errorf("SyncServiceTemplateFromIlyshin failed, error: %s", err) - errs = multierror.Append(errs, err) - } - } else { - log.Infof("Service template %s from ilyshin %s is not affected, no sync", service.ServiceName, service.SrcPath) - } - - } - return errs.ErrorOrNil() -} - -func GetIlyshinServiceTemplates() ([]*commonmodels.Service, error) { - opt := &commonrepo.ServiceListOption{ - Type: setting.K8SDeployType, - Source: setting.SourceFromIlyshin, - } - return commonrepo.NewServiceColl().ListMaxRevisions(opt) -} - -// SyncServiceTemplateFromIlyshin Force to sync Service Template to latest commit and content, -// Notes: if remains the same, quit sync; if updates, revision +1 -func SyncServiceTemplateFromIlyshin(service *commonmodels.Service, log *zap.SugaredLogger) error { - // 判断一下 Source 字段,如果 Source 字段不是 ilyshin,直接返回 - if service.Source != setting.SourceFromIlyshin { - return fmt.Errorf("service template is not from ilyshin") - } - // 获取当前Commit的SHA - var before string - if service.Commit != nil { - before = service.Commit.SHA - } - // Sync最新的Commit的SHA - var after string - err := syncIlyshinLatestCommit(service, log) - if err != nil { - return err - } - after = service.Commit.SHA - // 判断一下是否需要 Sync 内容 - if before == after { - log.Infof("Before and after SHA: %s remains the same, no need to sync", before) - // 无需更新 - return nil - } - // 在 Ensure 过程中会检查 source,如果 source 为 ilyshin,则同步 ilyshin 内容到 service 中 - if err := fillServiceTmpl(setting.WebhookTaskCreator, service, log); err != nil { - log.Errorf("ensureServiceTmpl error: %+v", err) - return e.ErrValidateTemplate.AddDesc(err.Error()) - } - // 更新到数据库,revision+1 - if err := commonrepo.NewServiceColl().Create(service); err != nil { - log.Errorf("Failed to sync service %s from ilyshin path %s error: %v", service.ServiceName, service.SrcPath, err) - return e.ErrCreateTemplate.AddDesc(err.Error()) - } - log.Infof("End of sync service template %s from ilyshin path %s", service.ServiceName, service.SrcPath) - return nil -} - -func TriggerWorkflowByIlyshinEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { - // 1. find configured workflow - workflowList, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) - if err != nil { - log.Errorf("failed to list workflow %v", err) - return err - } - - mErr := &multierror.Error{} - diffSrv := func(mergeEvent *ilyshin.MergeEvent, codehostId int) ([]string, error) { - return findIlyshinChangedFilesOfMergeRequest(mergeEvent, codehostId) - } - - for _, workflow := range workflowList { - if workflow.HookCtl == nil || !workflow.HookCtl.Enabled { - continue - } - - log.Debugf("find %d hooks in workflow %s", len(workflow.HookCtl.Items), workflow.Name) - for _, item := range workflow.HookCtl.Items { - if item.WorkflowArgs == nil { - continue - } - - // 2. match webhook - matcher := createIlyshinEventMatcher(event, diffSrv, workflow, log) - if matcher == nil { - continue - } - - matches, err := matcher.Match(item.MainRepo) - if err != nil { - mErr = multierror.Append(mErr, err) - continue - } - - if !matches { - log.Debugf("event not matches %v", item.MainRepo) - continue - } - - log.Infof("event match hook %v of %s", item.MainRepo, workflow.Name) - namespace := strings.Split(item.WorkflowArgs.Namespace, ",")[0] - opt := &commonrepo.ProductFindOptions{Name: workflow.ProductTmplName, EnvName: namespace} - var prod *commonmodels.Product - if prod, err = commonrepo.NewProductColl().Find(opt); err != nil { - log.Warnf("can't find environment %s-%s", item.WorkflowArgs.Namespace, workflow.ProductTmplName) - continue - } - - var mergeRequestID, commitID string - if ev, isPr := event.(*ilyshin.MergeEvent); isPr { - // 如果是merge request,且该webhook触发器配置了自动取消, - // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 - mergeRequestID = strconv.Itoa(ev.ObjectAttributes.IID) - commitID = ev.ObjectAttributes.LastCommit.ID - autoCancelOpt := &AutoCancelOpt{ - MergeRequestID: mergeRequestID, - CommitID: commitID, - TaskType: config.WorkflowType, - MainRepo: item.MainRepo, - WorkflowArgs: item.WorkflowArgs, - } - err := AutoCancelTask(autoCancelOpt, log) - if err != nil { - log.Errorf("failed to auto cancel workflow task when receive event %v due to %v ", event, err) - mErr = multierror.Append(mErr, err) - } - } - - args := matcher.UpdateTaskArgs(prod, item.WorkflowArgs, item.MainRepo, requestID) - args.MergeRequestID = mergeRequestID - args.CommitID = commitID - args.Source = setting.SourceFromIlyshin - args.CodehostID = item.MainRepo.CodehostID - args.RepoOwner = item.MainRepo.RepoOwner - args.RepoName = item.MainRepo.RepoName - // 3. create task with args - if _, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { - log.Errorf("failed to create workflow task when receive push event %v due to %s ", event, err) - mErr = multierror.Append(mErr, err) - } else { - log.Infof("succeed to create task") - } - } - } - - return mErr.ErrorOrNil() -} - -func findIlyshinChangedFilesOfMergeRequest(event *ilyshin.MergeEvent, codehostID int) ([]string, error) { - detail, err := codehost.GetCodehostDetail(codehostID) - if err != nil { - return nil, fmt.Errorf("failed to find codehost %d: %s", codehostID, err) - } - - client := ilyshin.NewClient(detail.Address, detail.OauthToken) - return client.ListChangedFiles(event) -} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go deleted file mode 100644 index bea8048f..00000000 --- a/pkg/microservice/aslan/core/workflow/service/webhook/ilyshin_workflow_task.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package webhook - -import ( - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/shared/codehost" - "github.com/koderover/zadig/pkg/tool/ilyshin" - "github.com/koderover/zadig/pkg/types" -) - -type ilyshinMergeRequestDiffFunc func(event *ilyshin.MergeEvent, id int) ([]string, error) - -type ilyshinMergeEventMatcher struct { - diffFunc ilyshinMergeRequestDiffFunc - log *zap.SugaredLogger - workflow *commonmodels.Workflow - event *ilyshin.MergeEvent -} - -func (gmem *ilyshinMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { - ev := gmem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { - if ev.ObjectAttributes.State == "opened" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v, err:%v", ev, err) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) - - return MatchChanges(hookRepo, changedFiles), nil - } - } - } - return false, nil -} - -func (gmem *ilyshinMergeEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, -) *commonmodels.WorkflowTaskArgs { - factory := &workflowArgsFactory{ - workflow: gmem.workflow, - reqID: requestID, - } - - args = factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - PR: gmem.event.ObjectAttributes.IID, - }) - - return args -} - -func createIlyshinEventMatcher( - event interface{}, diffSrv ilyshinMergeRequestDiffFunc, workflow *commonmodels.Workflow, log *zap.SugaredLogger, -) gitEventMatcher { - switch evt := event.(type) { - case *ilyshin.PushEvent: - return &ilyshinPushEventMatcher{ - workflow: workflow, - log: log, - event: evt, - } - case *ilyshin.MergeEvent: - return &ilyshinMergeEventMatcher{ - diffFunc: diffSrv, - log: log, - event: evt, - workflow: workflow, - } - } - - return nil -} - -type ilyshinPushEventMatcher struct { - log *zap.SugaredLogger - workflow *commonmodels.Workflow - event *ilyshin.PushEvent -} - -func (gpem *ilyshinPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { - ev := gpem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { - var changedFiles []string - - detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) - if err != nil { - gpem.log.Errorf("GetCodehostDetail error: %v", err) - return false, err - } - - client := ilyshin.NewClient(detail.Address, detail.OauthToken) - // compare接口获取两个commit之间的最终的改动 - diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) - if err != nil { - gpem.log.Errorf("Failed to get push event diffs, error: %v", err) - return false, err - } - for _, diff := range diffs { - changedFiles = append(changedFiles, diff.NewPath) - changedFiles = append(changedFiles, diff.OldPath) - } - - return MatchChanges(hookRepo, changedFiles), nil - } - } - - return false, nil -} - -func (gpem *ilyshinPushEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, -) *commonmodels.WorkflowTaskArgs { - factory := &workflowArgsFactory{ - workflow: gpem.workflow, - reqID: requestID, - } - - factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - }) - - return args -} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 98fba154..1434a232 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -18,7 +18,6 @@ package webhook import ( "context" - "encoding/base64" "errors" "fmt" "path" @@ -41,7 +40,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" githubtool "github.com/koderover/zadig/pkg/tool/git/github" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" - "github.com/koderover/zadig/pkg/tool/ilyshin" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -108,18 +106,6 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar log.Errorf("Sync content from gitlab failed, error: %v", err) return err } - } else if args.Source == setting.SourceFromIlyshin { - // Set args.Commit - if err := syncIlyshinLatestCommit(args, log); err != nil { - log.Errorf("Sync change log from ilyshin failed, error: %v", err) - return err - } - // 从 ilyshin 同步 args 指定的 Commit 下,指定目录中对应的 Yaml 文件 - // Set args.Yaml & args.KubeYamls - if err := syncContentFromIlyshin(userName, args); err != nil { - log.Errorf("Sync content from ilyshin failed, error: %v", err) - return err - } } else if args.Source == setting.SourceFromGithub { err := syncContentFromGithub(args, log) if err != nil { @@ -186,32 +172,6 @@ func syncLatestCommit(service *commonmodels.Service) error { return nil } -func syncIlyshinLatestCommit(service *commonmodels.Service, log *zap.SugaredLogger) error { - if service.SrcPath == "" { - return fmt.Errorf("url不能是空的") - } - - address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(service.SrcPath) - if err != nil { - return fmt.Errorf("url 必须包含 owner/repo/tree/branch/path,具体请参考 Placeholder 提示") - } - - client, err := getIlyshinClientByAddress(address) - if err != nil { - return err - } - - commit, err := client.GetLatestCommit(owner, repo, branch, path, log) - if err != nil { - return err - } - service.Commit = &commonmodels.Commit{ - SHA: commit.ID, - Message: commit.Message, - } - return nil -} - func syncCodehubLatestCommit(service *commonmodels.Service) error { if service.SrcPath == "" { return fmt.Errorf("url不能是空的") @@ -253,21 +213,6 @@ func getCodehubClientByAddress(address string) (*codehub.Client, error) { return client, nil } -func getIlyshinClientByAddress(address string) (*ilyshin.Client, error) { - opt := &codehost.Option{ - Address: address, - CodeHostType: codehost.IlyshinProvider, - } - codehost, err := codehost.GetCodeHostInfo(opt) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") - } - client := ilyshin.NewClient(codehost.Address, codehost.AccessToken) - - return client, nil -} - func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { opt := &codehost.Option{ Address: address, @@ -384,82 +329,6 @@ func joinYamls(files []string) string { return strings.Join(files, setting.YamlFileSeperator) } -// IlyshinGetRawFiles ... -func IlyshinGetRawFiles(client *ilyshin.Client, owner, repo, ref, path, pathType string) (files []string, err error) { - files = make([]string, 0) - var errs *multierror.Error - if pathType == "tree" { - nodes, err := client.ListTree(owner, repo, ref, path) - if err != nil { - return files, err - } - for _, node := range nodes { - // if node type is "tree", it is a directory, skip it for now - if node.Type == "tree" { - continue - } - fileName := strings.ToLower(node.Name) - if !strings.HasSuffix(fileName, ".yaml") && !strings.HasSuffix(fileName, ".yml") { - continue - } - // if node type is "blob", it is a file - // Path is filepath of a node - content, err := client.GetRawFile(owner, repo, ref, node.Path) - if err != nil { - errs = multierror.Append(errs, err) - } - contentStr := string(content) - contentStr = util.ReplaceWrapLine(contentStr) - files = append(files, contentStr) - } - return files, errs.ErrorOrNil() - } - fileInfo, err := client.GetFile(owner, repo, ref, path) - if err != nil { - return files, err - } - decodedFile, err := base64.StdEncoding.DecodeString(fileInfo.Content) - if err != nil { - return files, err - } - files = append(files, string(decodedFile)) - return files, errs.ErrorOrNil() -} - -// syncContentFromIlyshin ... -// sync content with commit, args.Commit should not be nil -func syncContentFromIlyshin(userName string, args *commonmodels.Service) error { - if args.Commit == nil { - return nil - } - - address, owner, repo, branch, path, pathType, err := GetOwnerRepoBranchPath(args.SrcPath) - if err != nil { - return fmt.Errorf("url format failed") - } - - client, err := getIlyshinClientByAddress(address) - if err != nil { - return err - } - - files, err := IlyshinGetRawFiles(client, owner, repo, branch, path, pathType) - if err != nil { - return err - } - if userName != setting.WebhookTaskCreator { - if len(files) == 0 { - return fmt.Errorf("没有检索到yml,yaml类型文件,请检查目录是否正确") - } - } - // KubeYamls field is dynamicly synced. - // 根据gitlab sync的内容来设置args.KubeYamls - args.KubeYamls = files - // 拼装并设置args.Yaml - args.Yaml = joinYamls(files) - return nil -} - func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) error { // 根据pipeline中的filepath获取文件内容 address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(args.SrcPath) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go index 58fc471b..1d15a7d6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go @@ -28,7 +28,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/ilyshin" ) // RepoCommit : Repository commit struct @@ -85,23 +84,6 @@ func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) ( CreatedAt: &commitDate, Message: commit.Message, }, nil - } else if ch.Type == setting.SourceFromIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - branches, err := client.ListBranches(owner, name, log) - if err != nil { - return nil, err - } - for _, br := range branches { - if br.Name == branch { - return &RepoCommit{ - ID: br.Commit.ID, - Title: br.Commit.Title, - AuthorName: br.Commit.AuthorName, - CreatedAt: br.Commit.CreatedAt, - Message: br.Commit.Message, - }, nil - } - } } return nil, errors.New(ch.Type + "is not supported yet") @@ -152,23 +134,6 @@ func QueryByTag(id int, owner, name, tag string, log *zap.SugaredLogger) (*RepoC CreatedAt: &commitDate, Message: commit.Message, }, nil - } else if ch.Type == setting.SourceFromIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - tags, err := client.ListTags(owner, name, log) - if err != nil { - return nil, err - } - for _, tagInfo := range tags { - if tagInfo.Name == tag { - return &RepoCommit{ - ID: tagInfo.Commit.ID, - Title: tagInfo.Commit.Title, - AuthorName: tagInfo.Commit.AuthorName, - CreatedAt: tagInfo.Commit.CreatedAt, - Message: tagInfo.Commit.Message, - }, nil - } - } } return nil, errors.New(ch.Type + "is not supported yet") @@ -219,18 +184,6 @@ func GetLatestPrCommit(codehostID, pr int, namespace, projectName string, log *z CreatedAt: &tm, CheckoutRef: change.Revisions[change.CurrentRevision].Ref, }, nil - } else if ch.Type == setting.SourceFromIlyshin { - client := ilyshin.NewClient(ch.Address, ch.AccessToken) - commit, err := client.GetLatestPRCommitList(projectID, pr, log) - if err != nil { - return nil, err - } - return &PRCommit{ - ID: commit.ID, - Title: commit.Title, - AuthorName: commit.AuthorName, - CreatedAt: commit.CreatedAt, - }, nil } return GetLatestPRCommitList(cli, projectID, pr) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 797cbd47..8eb8f220 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -313,7 +313,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { log.Errorf("failed to get codehost detail %d %v", build.CodehostID, err) return } - if codeHostInfo.Type == codehost.GitLabProvider || codeHostInfo.Type == codehost.GerritProvider || codeHostInfo.Type == codehost.IlyshinProvider { + if codeHostInfo.Type == codehost.GitLabProvider || codeHostInfo.Type == codehost.GerritProvider { if build.CommitID == "" { var commit *RepoCommit var pr *PRCommit diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index fc4fdc88..3d535127 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -45,6 +45,41 @@ type EnvStatus struct { ErrMessage string `json:"err_message"` } +type workflowCreateArg struct { + name string + envName string + buildStageEnabled bool + ArtifactStageEnabled bool +} + +type workflowCreateArgs struct { + productName string + argsMap map[string]*workflowCreateArg +} + +func (args *workflowCreateArgs) addWorkflowArg(envName string, buildStageEnabled, artifactStageEnabled bool) { + wName := fmt.Sprintf("%s-workflow-%s", args.productName, envName) + if artifactStageEnabled { + wName = fmt.Sprintf("%s-%s-workflow", args.productName, "ops") + } + args.argsMap[wName] = &workflowCreateArg{ + name: wName, + envName: envName, + buildStageEnabled: buildStageEnabled, + ArtifactStageEnabled: artifactStageEnabled, + } +} + +func (args *workflowCreateArgs) initDefaultWorkflows() { + args.addWorkflowArg("dev", true, false) + args.addWorkflowArg("qa", true, false) + args.addWorkflowArg("", false, true) +} + +func (args *workflowCreateArgs) clear() { + args.argsMap = make(map[string]*workflowCreateArg) +} + func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { productTmpl, err := template.NewProductColl().Find(productName) if err != nil { @@ -58,20 +93,42 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { mut.Unlock() }() - workflowNames := []string{productName + "-workflow-dev", productName + "-workflow-qa", productName + "-workflow-ops"} + createArgs := &workflowCreateArgs{ + productName: productName, + argsMap: make(map[string]*workflowCreateArg), + } + createArgs.initDefaultWorkflows() + + // helm project may have customized products, use the real created products + if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.DeployType == setting.HelmDeployType { + productList, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + }) + if err != nil { + log.Errorf("fialed to list products, projectName %s, err %s", productName, err) + } + createArgs.clear() + for _, product := range productList { + createArgs.addWorkflowArg(product.EnvName, true, false) + } + createArgs.addWorkflowArg("", false, true) + } + // 云主机场景不创建ops工作流 if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { - workflowNames = []string{productName + "-workflow-dev"} + createArgs.clear() + createArgs.addWorkflowArg("dev", true, false) } workflowSlice := sets.NewString() - for _, workflowName := range workflowNames { + for workflowName, _ := range createArgs.argsMap { _, err := FindWorkflow(workflowName, log) if err == nil { workflowSlice.Insert(workflowName) } } - if len(workflowSlice) < len(workflowNames) { + + if len(workflowSlice) < len(createArgs.argsMap) { preSetResps, err := PreSetWorkflow(productName, log) if err != nil { errList = multierror.Append(errList, err) @@ -91,7 +148,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { artifactModules = append(artifactModules, artifactModule) } - for _, workflowName := range workflowNames { + for workflowName, workflowArg := range createArgs.argsMap { if workflowSlice.Has(workflowName) { continue } @@ -104,24 +161,19 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { workflow.Name = workflowName workflow.CreateBy = setting.SystemUser workflow.UpdateBy = setting.SystemUser - workflow.EnvName = "dev" + workflow.EnvName = workflowArg.envName workflow.BuildStage = &commonmodels.BuildStage{ - Enabled: true, + Enabled: workflowArg.buildStageEnabled, Modules: buildModules, } - if strings.Contains(workflowName, "qa") { - workflow.EnvName = "qa" - } - - if strings.Contains(workflowName, "ops") { - //如果是开启artifactStage,则关闭buildStage - workflow.BuildStage.Enabled = false + //如果是开启artifactStage,则关闭buildStage + if workflowArg.ArtifactStageEnabled { workflow.ArtifactStage = &commonmodels.ArtifactStage{ Enabled: true, Modules: artifactModules, } - workflow.EnvName = "ops" + workflow.EnvName = "ops" //TODO necessary to set a fake env name? } workflow.Schedules = &commonmodels.ScheduleCtrl{ @@ -158,7 +210,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { return &EnvStatus{Status: setting.ProductStatusFailed, ErrMessage: err.Error()} } return &EnvStatus{Status: setting.ProductStatusCreating} - } else if len(workflowSlice) == len(workflowNames) { + } else if len(workflowSlice) == len(createArgs.argsMap) { return &EnvStatus{Status: setting.ProductStatusSuccess} } return nil @@ -420,8 +472,8 @@ func validateWorkflowHookNames(w *commonmodels.Workflow) error { return validateHookNames(names) } -func ListWorkflows(queryType, userID string, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { - workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) +func ListWorkflows(queryType string, productName string, userID string, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { + workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{ProductName: productName}) if err != nil { log.Errorf("Workflow.List error: %v", err) return workflows, e.ErrListWorkflow.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4184d9aa..b15e4f54 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -126,6 +126,31 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D resp := make(map[string][]commonmodels.DeployEnv) if prod.Source == setting.SourceFromExternal { services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) + + currentServiceNames := sets.NewString() + for _, service := range services { + currentServiceNames.Insert(service.ServiceName) + } + + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: prod.ProductName, + EnvName: prod.EnvName, + }) + + externalServiceNames := sets.NewString() + for _, serviceInExternalEnv := range servicesInExternalEnv { + if !currentServiceNames.Has(serviceInExternalEnv.ServiceName) { + externalServiceNames.Insert(serviceInExternalEnv.ServiceName) + } + } + + if len(externalServiceNames) > 0 { + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, "", externalServiceNames.List()...) + for _, service := range newServices { + services = append(services, service) + } + } + for _, service := range services { for _, container := range service.Containers { env := service.ServiceName + "/" + container.Name diff --git a/pkg/microservice/reaper/core/service/meta/const.go b/pkg/microservice/reaper/core/service/meta/const.go index d7d8fb66..d0385d72 100644 --- a/pkg/microservice/reaper/core/service/meta/const.go +++ b/pkg/microservice/reaper/core/service/meta/const.go @@ -22,8 +22,6 @@ const ( // ProviderGitlab ... ProviderGitlab = "gitlab" - //ProviderIlyshin - ProviderIlyshin = "ilyshin" // ProviderGerrit ProviderGerrit = "gerrit" diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 1720cc78..4a587d48 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -396,7 +396,7 @@ func (g *Git) HTTPSCloneURL(source, token, owner, name string) string { // e.g. //https://oauth2:ACCESS_TOKEN@somegitlab.com/owner/name.git func (g *Git) OAuthCloneURL(source, token, address, owner, name, scheme string) string { - if strings.ToLower(source) == ProviderGitlab || strings.ToLower(source) == ProviderIlyshin { + if strings.ToLower(source) == ProviderGitlab { // address 需要传过来 return fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", scheme, OauthTokenPrefix, token, address, owner, name) } diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 4dbc6c0b..01fdeed9 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -100,8 +100,6 @@ func (r *Reaper) runGitCmds() error { } } else if repo.Source == meta.ProviderCodehub { tokens = append(tokens, repo.Password) - } else if repo.Source == meta.ProviderIlyshin { - cmds = append(cmds, &c.Command{Cmd: c.SetConfig("http.sslVerify", "false"), DisableTrace: true}) } tokens = append(tokens, repo.OauthToken) cmds = append(cmds, r.buildGitCommands(repo)...) @@ -172,7 +170,7 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { cmds = append(cmds, &c.Command{Cmd: c.RemoteRemove(repo.RemoteName), DisableTrace: true, IgnoreError: true}) } - if repo.Source == meta.ProviderGitlab || repo.Source == meta.ProviderIlyshin { + if repo.Source == meta.ProviderGitlab { u, _ := url.Parse(repo.Address) cmds = append(cmds, &c.Command{ Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, repo.Owner, repo.Name, u.Scheme)), diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 663a3e53..d82c9e31 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -403,6 +403,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } + if targetContainer.ImagePath == nil { + err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + return + } + for _, chartInfo := range renderInfo.ChartInfos { if chartInfo.ServiceName == p.Task.ServiceName { renderChart = chartInfo @@ -446,7 +451,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } // merge override values and kvs into service's yaml - mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef( err, @@ -530,9 +535,10 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * // TODO too dangerous to override entire renderset! err = p.updateRenderSet(ctx, &types.RenderSet{ - Name: renderInfo.Name, - Revision: renderInfo.Revision, - ChartInfos: renderInfo.ChartInfos, + Name: renderInfo.Name, + Revision: renderInfo.Revision, + DefaultValues: renderInfo.DefaultValues, + ChartInfos: renderInfo.ChartInfos, }) if err != nil { err = errors.WithMessagef( diff --git a/pkg/microservice/warpdrive/core/service/types/environment.go b/pkg/microservice/warpdrive/core/service/types/environment.go index b8ac9e7e..5de5d860 100644 --- a/pkg/microservice/warpdrive/core/service/types/environment.go +++ b/pkg/microservice/warpdrive/core/service/types/environment.go @@ -67,7 +67,7 @@ type GitRepoConfig struct { Branch string `bson:"branch,omitempty"` } -type OverrideYaml struct { +type CustomYaml struct { YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` @@ -75,11 +75,11 @@ type OverrideYaml struct { } type RenderChart struct { - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` - ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` - OverrideYaml *OverrideYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` - OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` + ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` + OverrideYaml *CustomYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` + OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` } type ProductAuth struct { @@ -98,17 +98,6 @@ type Service struct { EnvConfigs []*EnvConfig `bson:"-" json:"env_configs,omitempty"` } -//type EnvConfig struct { -// EnvName string `json:"env_name"` -// HostIDs []string `json:"host_ids"` -//} -// -//// Container ... -//type Container struct { -// Name string `bson:"name" json:"name"` -// Image string `bson:"image" json:"image"` -//} - // Config ... type Config struct { ConfigName string `bson:"config_name" json:"config_name"` diff --git a/pkg/microservice/warpdrive/core/service/types/renderset.go b/pkg/microservice/warpdrive/core/service/types/renderset.go index 0552cad5..3d469a78 100644 --- a/pkg/microservice/warpdrive/core/service/types/renderset.go +++ b/pkg/microservice/warpdrive/core/service/types/renderset.go @@ -22,15 +22,16 @@ type RenderSet struct { Name string `bson:"name" json:"name"` Revision int64 `bson:"revision" json:"revision"` // 可以为空,空时为产品模板默认的渲染集,非空时为环境的渲染集 - EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` - ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - KVs []*RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` - ChartInfos []*RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 ›yaml content + KVs []*RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` + ChartInfos []*RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` } //// RenderChart ... diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 19815828..3f28e61c 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -232,8 +232,6 @@ const ( SourceFromGerrit = "gerrit" // SourceFromCodeHub 配置来源为codehub SourceFromCodeHub = "codehub" - // SourceFromIlyshin 配置来源为ilyshin - SourceFromIlyshin = "ilyshin" // SourceFromChartTemplate 配置来源为helmTemplate SourceFromChartTemplate = "chartTemplate" // SourceFromPublicRepo 配置来源为publicRepo diff --git a/pkg/shared/codehost/codehost.go b/pkg/shared/codehost/codehost.go index f86b0803..170a3ca0 100644 --- a/pkg/shared/codehost/codehost.go +++ b/pkg/shared/codehost/codehost.go @@ -29,7 +29,6 @@ const ( GitHubProvider = "github" GerritProvider = "gerrit" CodeHubProvider = "codehub" - IlyshinProvider = "ilyshin" ) type CodeHost struct { diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index f786d63c..93a2a0fd 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -46,21 +46,19 @@ type KV struct { Value string `json:"value"` } -// MergeOverrideValues merge values.yaml and override values -// overrideYaml used for -f option +// MergeOverrideValues merge override yaml and override kvs +// defaultValues overrideYaml used for -f option // overrideValues used for --set option -func MergeOverrideValues(valuesYaml, overrideYaml, overrideValues string) (string, error) { +func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { - if overrideYaml == "" && overrideValues == "" { - return valuesYaml, nil - } - - // merge files for -f option - values, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(overrideYaml)}) + // merge files for helm -f option + // precedence from low to high: valuesYaml defaultValues overrideYaml + valuesMap, err := yamlutil.MergeAndUnmarshal([][]byte{[]byte(valuesYaml), []byte(defaultValues), []byte(overrideYaml)}) if err != nil { return "", err } + // merge kv values for helm --set option if overrideValues != "" { kvList := make([]*KV, 0) err = json.Unmarshal([]byte(overrideValues), &kvList) @@ -72,13 +70,13 @@ func MergeOverrideValues(valuesYaml, overrideYaml, overrideValues string) (strin kvStr = append(kvStr, fmt.Sprintf("%s=%s", kv.Key, kv.Value)) } // override values for --set option - err = strvals.ParseInto(strings.Join(kvStr, ","), values) + err = strvals.ParseInto(strings.Join(kvStr, ","), valuesMap) if err != nil { return "", err } } - bs, err := yaml.Marshal(values) + bs, err := yaml.Marshal(valuesMap) if err != nil { return "", err } diff --git a/pkg/tool/ilyshin/branch.go b/pkg/tool/ilyshin/branch.go deleted file mode 100644 index aa168d3e..00000000 --- a/pkg/tool/ilyshin/branch.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "net/url" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Branch struct { - Name string `json:"name"` - Protected bool `json:"protected"` - Merged bool `json:"merged"` - Default bool `json:"default"` - CanPush bool `json:"can_push"` - DevelopersCanPush bool `json:"developers_can_push"` - DevelopersCanMerge bool `json:"developers_can_merge"` - Commit *Commit `json:"commit"` -} - -func (c *Client) ListBranches(owner, repo string, log *zap.SugaredLogger) ([]*Branch, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/branches", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "100", - } - - var err error - var branches []*Branch - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&branches)); err != nil { - log.Errorf("Failed to list project branches, error: %s", err) - return branches, err - } - - return branches, nil -} - -func generateProjectName(owner, repo string) string { - return url.PathEscape(fmt.Sprintf("%s/%s", owner, repo)) -} diff --git a/pkg/tool/ilyshin/client.go b/pkg/tool/ilyshin/client.go deleted file mode 100644 index 03a9d3a4..00000000 --- a/pkg/tool/ilyshin/client.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "crypto/tls" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Client struct { - *httpclient.Client - Address string - AccessToken string -} - -func NewClient(address, accessToken string) *Client { - c := httpclient.New( - httpclient.SetAuthScheme("Bearer"), - httpclient.SetAuthToken(accessToken), - httpclient.SetHostURL(address), - httpclient.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}), - ) - return &Client{ - Client: c, - Address: address, - AccessToken: accessToken, - } -} diff --git a/pkg/tool/ilyshin/commit.go b/pkg/tool/ilyshin/commit.go deleted file mode 100644 index e90f7a76..00000000 --- a/pkg/tool/ilyshin/commit.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Commit struct { - ID string `json:"id"` - ShortID string `json:"short_id"` - Title string `json:"title"` - Message string `json:"message"` - AuthorName string `json:"author_name"` - AuthorEmail string `json:"author_email"` - AuthoredDate *time.Time `json:"authored_date"` - CommitterName string `json:"committer_name"` - CommitterEmail string `json:"committer_email"` - CommittedDate *time.Time `json:"committed_date"` - CreatedAt *time.Time `json:"created_at"` -} - -func (c *Client) GetLatestCommit(owner, repo, branch, path string, log *zap.SugaredLogger) (*Commit, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/commits", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "1", - "ref_name": branch, - "path": path, - } - - var err error - var commits []*Commit - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&commits)); err != nil { - log.Errorf("Failed to list project commits, error: %s", err) - return nil, err - } - if len(commits) == 0 { - return nil, fmt.Errorf("not found commits") - } - - return commits[0], nil -} diff --git a/pkg/tool/ilyshin/event_parsing.go b/pkg/tool/ilyshin/event_parsing.go deleted file mode 100644 index ff06dc34..00000000 --- a/pkg/tool/ilyshin/event_parsing.go +++ /dev/null @@ -1,179 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "encoding/json" - "fmt" - "net/http" - "time" -) - -type ProjectEvent struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - AvatarURL string `json:"avatar_url"` - GitSSHURL string `json:"git_ssh_url"` - GitHTTPURL string `json:"git_http_url"` - Namespace string `json:"namespace"` - PathWithNamespace string `json:"path_with_namespace"` - DefaultBranch string `json:"default_branch"` - Homepage string `json:"homepage"` - URL string `json:"url"` - SSHURL string `json:"ssh_url"` - HTTPURL string `json:"http_url"` - WebURL string `json:"web_url"` -} - -type Repository struct { - Name string `json:"name"` - Description string `json:"description"` - WebURL string `json:"web_url"` - AvatarURL string `json:"avatar_url"` - GitSSHURL string `json:"git_ssh_url"` - GitHTTPURL string `json:"git_http_url"` - Namespace string `json:"namespace"` - PathWithNamespace string `json:"path_with_namespace"` - DefaultBranch string `json:"default_branch"` - Homepage string `json:"homepage"` - URL string `json:"url"` - SSHURL string `json:"ssh_url"` - HTTPURL string `json:"http_url"` -} - -type ObjectAttributes struct { - ID int `json:"id"` - TargetBranch string `json:"target_branch"` - SourceBranch string `json:"source_branch"` - SourceProjectID int `json:"source_project_id"` - AuthorID int `json:"author_id"` - AssigneeID int `json:"assignee_id"` - AssigneeIDs []int `json:"assignee_ids"` - Title string `json:"title"` - StCommits []*Commit `json:"st_commits"` - MilestoneID int `json:"milestone_id"` - State string `json:"state"` - MergeStatus string `json:"merge_status"` - TargetProjectID int `json:"target_project_id"` - IID int `json:"iid"` - Description string `json:"description"` - Position int `json:"position"` - LockedAt string `json:"locked_at"` - UpdatedByID int `json:"updated_by_id"` - MergeError string `json:"merge_error"` - MergeWhenBuildSucceeds bool `json:"merge_when_build_succeeds"` - MergeUserID int `json:"merge_user_id"` - MergeCommitSHA string `json:"merge_commit_sha"` - DeletedAt string `json:"deleted_at"` - ApprovalsBeforeMerge string `json:"approvals_before_merge"` - RebaseCommitSHA string `json:"rebase_commit_sha"` - InProgressMergeCommitSHA string `json:"in_progress_merge_commit_sha"` - LockVersion int `json:"lock_version"` - TimeEstimate int `json:"time_estimate"` - Target *Repository `json:"target"` - LastCommit LastCommit `json:"last_commit"` - WorkInProgress bool `json:"work_in_progress"` - URL string `json:"url"` - Action string `json:"action"` - OldRev string `json:"oldrev"` -} - -type LastCommit struct { - ID string `json:"id"` - Message string `json:"message"` - Timestamp *time.Time `json:"timestamp"` - URL string `json:"url"` - Author Author `json:"author"` -} - -type MergeEvent struct { - ObjectKind string `json:"object_kind"` - Project ProjectEvent `json:"project"` - ObjectAttributes ObjectAttributes `json:"object_attributes"` -} - -type PushEvent struct { - ObjectKind string `json:"object_kind"` - Before string `json:"before"` - After string `json:"after"` - Ref string `json:"ref"` - CheckoutSHA string `json:"checkout_sha"` - UserID int `json:"user_id"` - UserName string `json:"user_name"` - UserUsername string `json:"user_username"` - UserEmail string `json:"user_email"` - UserAvatar string `json:"user_avatar"` - ProjectID int `json:"project_id"` - Project ProjectEvent `json:"project"` - Commits []Commits `json:"commits"` - TotalCommitsCount int `json:"total_commits_count"` -} - -type Commits struct { - ID string `json:"id"` - Message string `json:"message"` - Timestamp *time.Time `json:"timestamp"` - URL string `json:"url"` - Author Author `json:"author"` - Added []string `json:"added"` - Modified []string `json:"modified"` - Removed []string `json:"removed"` -} - -type Author struct { - Name string `json:"name"` - Email string `json:"email"` -} - -// EventType represents a ilyshin event type. -type EventType string - -// List of available event types. -const ( - EventTypeMergeRequest EventType = "Merge Request Hook" - EventTypePush EventType = "Push Hook" - EventTypeTagPush EventType = "Tag Push Hook" -) - -const eventTypeHeader = "X-CodeHub-Event" - -// HookEventType returns the event type for the given request. -func HookEventType(r *http.Request) EventType { - return EventType(r.Header.Get(eventTypeHeader)) -} - -func ParseHook(eventType EventType, payload []byte) (event interface{}, err error) { - return parseWebhook(eventType, payload) -} - -func parseWebhook(eventType EventType, payload []byte) (event interface{}, err error) { - switch eventType { - case EventTypeMergeRequest: - event = &MergeEvent{} - case EventTypePush: - event = &PushEvent{} - default: - return nil, fmt.Errorf("unexpected event type: %s", eventType) - } - - if err := json.Unmarshal(payload, event); err != nil { - return nil, err - } - - return event, nil -} diff --git a/pkg/tool/ilyshin/group_project.go b/pkg/tool/ilyshin/group_project.go deleted file mode 100644 index ffcb7ca3..00000000 --- a/pkg/tool/ilyshin/group_project.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -func (c *Client) ListGroupProjects(namespace, keyword string, log *zap.SugaredLogger) ([]*Project, error) { - url := fmt.Sprintf("/api/v4/groups/%s/projects", namespace) - qs := map[string]string{ - "order_by": "name", - "sort": "asc", - "per_page": "100", - } - if keyword != "" && len(keyword) > 2 { - qs["search"] = keyword - } - var err error - var gps []*Project - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&gps)); err != nil { - log.Errorf("Failed to list group projects, error: %s", err) - return gps, err - } - - return gps, nil -} diff --git a/pkg/tool/ilyshin/merge_request.go b/pkg/tool/ilyshin/merge_request.go deleted file mode 100644 index 0b166866..00000000 --- a/pkg/tool/ilyshin/merge_request.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" -) - -type MergeRequest struct { - ID int `json:"id"` - IID int `json:"iid"` - ProjectID int `json:"project_id"` - Title string `json:"title"` - State string `json:"state"` - CreatedAt *time.Time `json:"created_at"` - UpdatedAt *time.Time `json:"updated_at"` - SourceBranch string `json:"source_branch"` - TargetBranch string `json:"target_branch"` - SourceProjectID int `json:"source_project_id"` - TargetProjectID int `json:"target_project_id"` - Description string `json:"description"` - Author *BasicUser `json:"author"` - Changes []Changes `json:"changes"` -} - -type Changes struct { - OldPath string `json:"old_path"` - NewPath string `json:"new_path"` - AMode string `json:"a_mode"` - BMode string `json:"b_mode"` - Diff string `json:"diff"` - NewFile bool `json:"new_file"` - RenamedFile bool `json:"renamed_file"` - DeletedFile bool `json:"deleted_file"` -} - -type BasicUser struct { - ID int `json:"id"` - Username string `json:"username"` - Name string `json:"name"` - State string `json:"state"` - CreatedAt *time.Time `json:"created_at"` - AvatarURL string `json:"avatar_url"` - WebURL string `json:"web_url"` -} - -func (c *Client) ListOpenedProjectMergeRequests(owner, repo, targetBranch string, log *zap.SugaredLogger) ([]*MergeRequest, error) { - url := fmt.Sprintf("/api/v4/projects/%s/isource/merge_requests", generateProjectName(owner, repo)) - qs := map[string]string{ - "state": "opened", - "per_page": "100", - } - if targetBranch != "" { - qs["target_branch"] = targetBranch - } - - var err error - var mergeRequests []*MergeRequest - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&mergeRequests)); err != nil { - log.Errorf("Failed to list project mergeRequests, error: %s", err) - return mergeRequests, err - } - - return mergeRequests, nil -} - -func (c *Client) ListChangedFiles(event *MergeEvent) ([]string, error) { - files := make([]string, 0) - - url := fmt.Sprintf("/api/v4/projects/%d/merge_requests/%d/changes", event.ObjectAttributes.TargetProjectID, event.ObjectAttributes.IID) - var err error - var mergeRequest *MergeRequest - if _, err = c.Get(url, httpclient.SetResult(&mergeRequest)); err != nil { - log.Errorf("Failed to get project mergeRequest, error: %s", err) - return files, err - } - - for _, change := range mergeRequest.Changes { - files = append(files, change.NewPath) - files = append(files, change.OldPath) - } - - return files, nil -} - -func (c *Client) GetLatestPRCommitList(projectID string, pr int, log *zap.SugaredLogger) (*Commit, error) { - url := fmt.Sprintf("/api/v4/projects/%s/merge_requests/%d/commits", projectID, pr) - - var commit *Commit - if _, err := c.Get(url, httpclient.SetResult(&commit)); err != nil { - log.Errorf("Failed to get project mergeRequest commits, error: %s", err) - return commit, err - } - return commit, nil -} diff --git a/pkg/tool/ilyshin/project.go b/pkg/tool/ilyshin/project.go deleted file mode 100644 index d348a2da..00000000 --- a/pkg/tool/ilyshin/project.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Project struct { - ID int `json:"id"` - Description string `json:"description"` - Name string `json:"name"` - NameWithNamespace string `json:"name_with_namespace"` - Path string `json:"path"` - PathWithNamespace string `json:"path_with_namespace"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Archived bool `json:"archived"` - DefaultBranch string `json:"default_branch"` - Namespace *ProjectNamespace `json:"namespace"` -} - -type ProjectNamespace struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - Kind string `json:"kind"` - FullPath string `json:"full_path"` -} - -func (c *Client) ListNamespaces(keyword string, log *zap.SugaredLogger) ([]*Project, error) { - url := fmt.Sprintf("/api/v4/projects") - qs := map[string]string{ - "order_by": "name", - "sort": "asc", - "project_type": "group", // group or project - "per_page": "100", - "simple": "true", - } - if keyword != "" && len(keyword) > 2 { - qs["search"] = keyword - } - var err error - var gps []*Project - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&gps)); err != nil { - log.Errorf("Failed to list projects, error: %s", err) - return gps, err - } - - var resp []*Project - projectNames := sets.NewString() - for _, gp := range gps { - if projectNames.Has(gp.Namespace.Name) || gp.Namespace.Kind == "user" { - continue - } - - projectNames.Insert(gp.Namespace.Name) - resp = append(resp, gp) - } - return resp, nil -} diff --git a/pkg/tool/ilyshin/repo.go b/pkg/tool/ilyshin/repo.go deleted file mode 100644 index 1bd95db4..00000000 --- a/pkg/tool/ilyshin/repo.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - - "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" -) - -type TreeNode struct { - ID string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Path string `json:"path"` - Mode string `json:"mode"` -} - -type File struct { - FileName string `json:"file_name"` - FilePath string `json:"file_path"` - Size int `json:"size"` - Encoding string `json:"encoding"` - Content string `json:"content"` - Ref string `json:"ref"` - BlobID string `json:"blob_id"` - CommitID string `json:"commit_id"` - SHA256 string `json:"content_sha256"` - LastCommitID string `json:"last_commit_id"` -} - -type Diff struct { - Diff string `json:"diff"` - NewPath string `json:"new_path"` - OldPath string `json:"old_path"` - AMode string `json:"a_mode"` - BMode string `json:"b_mode"` - NewFile bool `json:"new_file"` - RenamedFile bool `json:"renamed_file"` - DeletedFile bool `json:"deleted_file"` -} - -func (c *Client) ListTree(owner, repo string, ref string, path string) ([]*TreeNode, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/tree", generateProjectName(owner, repo)) - qs := map[string]string{ - "ref": ref, - "path": path, - "per_page": "100", - } - - var err error - var treeNodes []*TreeNode - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&treeNodes)); err != nil { - log.Errorf("Failed to list project tree nodes, error: %s", err) - return treeNodes, err - } - - return treeNodes, nil -} - -func (c *Client) GetRawFile(owner, repo string, sha string, fileName string) ([]byte, error) { - File, err := c.GetFile(owner, repo, sha, fileName) - if err != nil { - return nil, err - } - var resp []byte - url := fmt.Sprintf("/api/v4/projects/%s/repository/blobs/%s/raw", generateProjectName(owner, repo), File.BlobID) - if _, err = c.Get(url, httpclient.SetResult(&resp)); err != nil { - log.Errorf("Failed to get project blob raw, error: %s", err) - return resp, err - } - - return resp, nil -} - -func (c *Client) GetFile(owner, repo string, ref, path string) (*File, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/files/%s", generateProjectName(owner, repo), path) - qs := map[string]string{ - "ref": ref, - } - - var err error - var file *File - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&file)); err != nil { - log.Errorf("Failed to get project file content, error: %s", err) - return file, err - } - - return file, nil -} - -func (c *Client) Compare(projectID int, from, to string) ([]*Diff, error) { - url := fmt.Sprintf("/api/v4/projects/%d/repository/compare", projectID) - qs := map[string]string{ - "from": from, - "to": to, - } - - var err error - var diffs []*Diff - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&diffs)); err != nil { - log.Errorf("Failed to compare content, error: %s", err) - return diffs, err - } - - return diffs, nil -} diff --git a/pkg/tool/ilyshin/tag.go b/pkg/tool/ilyshin/tag.go deleted file mode 100644 index cfbb81ef..00000000 --- a/pkg/tool/ilyshin/tag.go +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type Tag struct { - Name string `json:"name"` - Message string `json:"message"` - Release *ReleaseNote `json:"release"` - Commit *Commit `json:"commit"` -} - -type ReleaseNote struct { - TagName string `json:"tag_name"` - Description string `json:"description"` -} - -func (c *Client) ListTags(owner, repo string, log *zap.SugaredLogger) ([]*Tag, error) { - url := fmt.Sprintf("/api/v4/projects/%s/repository/tags", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "100", - } - - var err error - var tags []*Tag - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&tags)); err != nil { - log.Errorf("Failed to list project tags, error: %s", err) - return tags, err - } - - return tags, nil -} diff --git a/pkg/tool/ilyshin/webhook.go b/pkg/tool/ilyshin/webhook.go deleted file mode 100644 index 2d38c4d2..00000000 --- a/pkg/tool/ilyshin/webhook.go +++ /dev/null @@ -1,100 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package ilyshin - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" -) - -type ProjectHook struct { - ID int `json:"id"` - URL string `json:"url"` - ConfidentialNoteEvents bool `json:"confidential_note_events"` - ProjectID int `json:"project_id"` - PushEvents bool `json:"push_events"` - PushEventsBranchFilter string `json:"push_events_branch_filter"` - IssuesEvents bool `json:"issues_events"` - ConfidentialIssuesEvents bool `json:"confidential_issues_events"` - MergeRequestsEvents bool `json:"merge_requests_events"` - TagPushEvents bool `json:"tag_push_events"` - NoteEvents bool `json:"note_events"` - JobEvents bool `json:"job_events"` - PipelineEvents bool `json:"pipeline_events"` - WikiPageEvents bool `json:"wiki_page_events"` - DeploymentEvents bool `json:"deployment_events"` - ReleasesEvents bool `json:"releases_events"` - EnableSSLVerification bool `json:"enable_ssl_verification"` - CreatedAt *time.Time `json:"created_at"` -} - -type Hook struct { - URL string `url:"url,omitempty" json:"url,omitempty"` - Name string `url:"name,omitempty" json:"name,omitempty"` - Token string `url:"token,omitempty" json:"token,omitempty"` - PushEvents bool `url:"push_events,omitempty" json:"push_events,omitempty"` - MergeRequestsEvents bool `url:"merge_requests_events,omitempty" json:"merge_requests_events,omitempty"` - TagPushEvents bool `url:"tag_push_events,omitempty" json:"tag_push_events,omitempty"` - EnableSSLVerification bool `url:"enable_ssl_verification,omitempty" json:"enable_ssl_verification,omitempty"` -} - -func (c *Client) ListProjectHooks(owner, repo string, log *zap.SugaredLogger) ([]*ProjectHook, error) { - url := fmt.Sprintf("/api/v4/projects/%s/hooks", generateProjectName(owner, repo)) - qs := map[string]string{ - "per_page": "100", - } - - var err error - var projectHooks []*ProjectHook - if _, err = c.Get(url, httpclient.SetQueryParams(qs), httpclient.SetResult(&projectHooks)); err != nil { - log.Errorf("Failed to list project hooks, error: %s", err) - return projectHooks, err - } - - return projectHooks, nil -} - -func (c *Client) AddProjectHook(owner, repo, hookURL, token string) (*ProjectHook, error) { - url := fmt.Sprintf("/api/v4/projects/%s/hooks", generateProjectName(owner, repo)) - hook := &Hook{ - URL: hookURL, - Token: token, - MergeRequestsEvents: true, - PushEvents: true, - TagPushEvents: true, - } - var projectHook *ProjectHook - if _, err := c.Post(url, httpclient.SetBody(hook), httpclient.SetResult(&projectHook)); err != nil { - log.Errorf("Failed to create project hook, error: %s", err) - return projectHook, err - } - return projectHook, nil -} - -func (c *Client) DeleteProjectHook(owner, repo string, id int) error { - url := fmt.Sprintf("/api/v4/projects/%s/hooks/%d", generateProjectName(owner, repo), id) - if _, err := c.Delete(url); err != nil { - log.Errorf("Failed to delete project webhook, error: %s", err) - return err - } - return nil -} -- Gitee From 541d475a9b50d932da0121cb3f3ce8fa228bff73 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 3 Nov 2021 11:47:37 +0800 Subject: [PATCH 0448/2647] fix config convert error Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/service/user/user.go | 25 ++++++++----------- .../client/systemconfig/systemconfig.go | 10 +++++--- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 019e5957..e2a3704f 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -4,7 +4,6 @@ import ( "fmt" "time" - "github.com/dexidp/dex/connector/ldap" ldapv3 "github.com/go-ldap/ldap/v3" "github.com/google/uuid" "go.uber.org/zap" @@ -72,45 +71,41 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { logger.Error("can't find connector") return fmt.Errorf("can't find connector") } - config, ok := si.Config.(*ldap.Config) - if !ok { - return fmt.Errorf("connector config error") - } - l, err := ldapv3.Dial("tcp", config.Host) + l, err := ldapv3.Dial("tcp", si.Config.Host) if err != nil { - logger.Errorf("ldap dial host:%s error, error msg:%s", config.Host, err) + logger.Errorf("ldap dial host:%s error, error msg:%s", si.Config.Host, err) return err } defer l.Close() - err = l.Bind(config.BindDN, config.BindPW) + err = l.Bind(si.Config.BindDN, si.Config.BindPW) if err != nil { - logger.Errorf("ldap bind host:%s error, error msg:%s", config.Host, err) + logger.Errorf("ldap bind host:%s error, error msg:%s", si.Config.Host, err) return err } searchRequest := ldapv3.NewSearchRequest( - config.GroupSearch.BaseDN, + si.Config.GroupSearch.BaseDN, ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, - config.GroupSearch.Filter, // The filter to apply - []string{config.UserSearch.NameAttr}, // A list attributes to retrieve + si.Config.GroupSearch.Filter, // The filter to apply + []string{si.Config.UserSearch.NameAttr}, // A list attributes to retrieve nil, ) sr, err := l.Search(searchRequest) if err != nil { - logger.Errorf("ldap search host:%s error, error msg:%s", config.Host, err) + logger.Errorf("ldap search host:%s error, error msg:%s", si.Config.Host, err) return err } syncUserMap := make(map[string]bool) for _, entry := range sr.Entries { - account := config.UserSearch.NameAttr + account := si.Config.UserSearch.NameAttr _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), IdentityType: si.ID, }, logger) if err != nil { - logger.Errorf("ldap host:%s sync user error, error msg:%s", config.Host, err) + logger.Errorf("ldap host:%s sync user error, error msg:%s", si.Config.Host, err) return err } syncUserMap[account] = true diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go index 6d98e516..c13fae54 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -1,14 +1,16 @@ package systemconfig import ( + "github.com/dexidp/dex/connector/ldap" + "github.com/koderover/zadig/pkg/tool/httpclient" ) type Connector struct { - Type string `json:"type"` - ID string `json:"id"` - Name string `json:"name"` - Config interface{} `json:"config"` + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + Config *ldap.Config `json:"config"` } func (c *Client) GetConnector(id string) (*Connector, error) { -- Gitee From 279880f5bb32b12ab96ed2f1bd62b4eabd5505c9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 3 Nov 2021 13:47:26 +0800 Subject: [PATCH 0449/2647] fix environment variable update bug Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d03c99c9..646f320e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -3098,7 +3098,6 @@ func updateProductVariable(productName, envName string, productResp *commonmodel errList := new(multierror.Error) for groupIndex, services := range productResp.Services { serviceList := make([]interface{}, 0) - var wg sync.WaitGroup groupServices := make([]*commonmodels.ProductService, 0) for _, service := range services { if _, isExist := renderChartMap[service.ServiceName]; isExist { @@ -3115,15 +3114,13 @@ func updateProductVariable(productName, envName string, productResp *commonmodel continue } serviceList = append(serviceList, serviceObj) - - groupServiceErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) - if groupServiceErr != nil { - errList = multierror.Append(errList, groupServiceErr...) - } } groupServices = append(groupServices, service) } - wg.Wait() + groupServiceErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + if groupServiceErr != nil { + errList = multierror.Append(errList, groupServiceErr...) + } err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, groupServices) if err != nil { log.Errorf("Failed to update collection - service group %d. Error: %v", groupIndex, err) -- Gitee From b9562b3b2500c1226f738f24d44bd794629a59ca Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 3 Nov 2021 13:57:37 +0800 Subject: [PATCH 0450/2647] fix spell errors Signed-off-by: allenshen --- pkg/microservice/aslan/core/templatestore/service/chart.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index ae475dbe..51c54d88 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -212,7 +212,7 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. variablesNames, err := parseTemplateVariables(name, args.Path, logger) if err != nil { - return errors.Wrapf(err, "faild to prase variables") + return errors.Wrapf(err, "failed to prase variables") } variables := make([]*commonmodes.Variable, 0, len(variablesNames)) @@ -254,7 +254,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z variablesNames, err := parseTemplateVariables(name, args.Path, logger) if err != nil { - return errors.Wrapf(err, "faild to prase variables") + return errors.Wrapf(err, "failed to prase variables") } variables := make([]*commonmodes.Variable, 0, len(variablesNames)) -- Gitee From ecf2a64a8e146c94858e01e0bed995f2af3a6472 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 3 Nov 2021 14:17:20 +0800 Subject: [PATCH 0451/2647] remove sync delete user Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/service/user/user.go | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index e2a3704f..d1a45510 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -97,7 +97,6 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { logger.Errorf("ldap search host:%s error, error msg:%s", si.Config.Host, err) return err } - syncUserMap := make(map[string]bool) for _, entry := range sr.Entries { account := si.Config.UserSearch.NameAttr _, err := SyncUser(&SyncUserInfo{ @@ -108,40 +107,6 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { logger.Errorf("ldap host:%s sync user error, error msg:%s", si.Config.Host, err) return err } - syncUserMap[account] = true - } - users, err := orm.ListUsersByIdentityType(si.ID, core.DB) - if err != nil { - logger.Errorf("ListUsers by identityType:%s error, error msg:%s", si.ID, err) - return err - } - var deletedUsers []string - for _, user := range users { - if ok := syncUserMap[user.Account]; !ok { - deletedUsers = append(deletedUsers, user.UID) - } - } - tx := core.DB.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - err = orm.DeleteUserByUids(deletedUsers, tx) - if err != nil { - tx.Rollback() - logger.Errorf("SearchAndSyncUser delete user by uids:%s error, error msg:%s", deletedUsers, err) - return fmt.Errorf("SearchAndSyncUser delete user by uids:%s error, error msg:%s", deletedUsers, err) - } - err = orm.DeleteUserLoginByUids(deletedUsers, tx) - if err != nil { - tx.Rollback() - logger.Errorf("SearchAndSyncUser delete userlogin by uids:%s error, error msg:%s", deletedUsers, err) - return fmt.Errorf("SearchAndSyncUser delete userlogin by uids:%s error, error msg:%s", deletedUsers, err) - } - err = tx.Commit().Error - if err != nil { - return err } return nil } -- Gitee From ef0b6ff858a35ae6657d367aee675503e391dabb Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 3 Nov 2021 14:42:25 +0800 Subject: [PATCH 0452/2647] add account and identityType for rolebindings (#524) Signed-off-by: lou --- .../core/common/service/template_product.go | 27 +------------- .../picket/core/filter/service/rolebinding.go | 26 ++++++++----- pkg/shared/client/user/user.go | 10 +++-- pkg/shared/poetry/check.go | 37 ------------------- 4 files changed, 25 insertions(+), 75 deletions(-) delete mode 100644 pkg/shared/poetry/check.go diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index df136cbe..b55da743 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -18,7 +18,6 @@ package service import ( "fmt" - "strings" "sync" "go.uber.org/zap" @@ -31,10 +30,8 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" - "github.com/koderover/zadig/pkg/microservice/aslan/internal/cache" "github.com/koderover/zadig/pkg/setting" configclient "github.com/koderover/zadig/pkg/shared/config" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -56,7 +53,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P totalFreeStyles := make([]*collie.CiPipelineResource, 0) cl := configclient.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType); err == nil && enable { // CI场景onboarding流程处于第二步时,需要返回ci工作流id,用于前端跳转 collieAPIAddress := config.CollieAPIAddress() cl := collie.New(collieAPIAddress) @@ -122,7 +119,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Pipeline.List err : %v", err) } - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType); err == nil && enable { collieAPIAddress := config.CollieAPIAddress() cl := collie.New(collieAPIAddress) totalFreeStyles, err = cl.ListCIPipelines(productName, log) @@ -171,26 +168,6 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, nil } -func GetFeatures(log *zap.SugaredLogger) (string, error) { - featuresByteKey := []byte("features") - featuresByteValue, err := cache.Get(featuresByteKey) - if err != nil { - poetryCtl := poetry.New(config.PoetryAPIServer()) - fs, err := poetryCtl.ListFeatures() - if err != nil { - return "", err - } - cacheValue := strings.Join(fs, ",") - // 一天过期 - if err = cache.Set(featuresByteKey, []byte(cacheValue), 86400); err != nil { - log.Errorf("getFeatures set cache err:%v", err) - } - return cacheValue, nil - } - - return string(featuresByteValue), nil -} - func FillProductTemplateVars(productTemplates []*template.Product, log *zap.SugaredLogger) error { var ( wg sync.WaitGroup diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index b46719be..b8740c9e 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -12,9 +12,11 @@ import ( type roleBinding struct { *policy.RoleBinding - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` + Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` } func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*roleBinding, error) { @@ -25,10 +27,10 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg } var uids []string - uidToRoleBinding := make(map[string]*roleBinding) + uidToRoleBinding := make(map[string][]*roleBinding) for _, rb := range rbs { uids = append(uids, rb.UID) - uidToRoleBinding[rb.UID] = &roleBinding{RoleBinding: rb} + uidToRoleBinding[rb.UID] = append(uidToRoleBinding[rb.UID], &roleBinding{RoleBinding: rb}) } users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uids}) @@ -40,10 +42,16 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg var res []*roleBinding for _, u := range users { if rb, ok := uidToRoleBinding[u.UID]; ok { - rb.Username = u.Name - rb.Email = u.Email - rb.Phone = u.Phone - res = append(res, rb) + for _, r := range rb { + r.Username = u.Name + r.Email = u.Email + r.Phone = u.Phone + r.IdentityType = u.IdentityType + r.Account = u.Account + + res = append(res, r) + } + } } diff --git a/pkg/shared/client/user/user.go b/pkg/shared/client/user/user.go index c080f6d2..d51c371c 100644 --- a/pkg/shared/client/user/user.go +++ b/pkg/shared/client/user/user.go @@ -5,10 +5,12 @@ import ( ) type User struct { - UID string `json:"uid"` - Name string `json:"name"` - Email string `json:"email"` - Phone string `json:"phone"` + UID string `json:"uid"` + Name string `json:"name"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` } type usersResp struct { diff --git a/pkg/shared/poetry/check.go b/pkg/shared/poetry/check.go deleted file mode 100644 index d205c512..00000000 --- a/pkg/shared/poetry/check.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type features struct { - Features []string `json:"features"` -} - -func (c *Client) ListFeatures() ([]string, error) { - url := "/directory/check" - - fs := &features{} - _, err := c.Get(url, httpclient.SetResult(fs)) - if err != nil { - return nil, err - } - - return fs.Features, nil -} -- Gitee From fc51731686699267f5abb4e0aa2cbea052d68bcd Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 3 Nov 2021 15:07:32 +0800 Subject: [PATCH 0453/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/service/user/user.go | 8 ++++---- pkg/shared/client/systemconfig/systemconfig.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index d1a45510..648fa102 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -62,10 +62,10 @@ type SyncUserInfo struct { func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { systemConfigClient := systemconfig.New() - si, err := systemConfigClient.GetConnector(ldapId) + si, err := systemConfigClient.GetLDAPConnector(ldapId) if err != nil { - logger.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) - return fmt.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) + logger.Errorf("SearchAndSyncUser GetLDAPConnector error, error msg:%s", err) + return fmt.Errorf("SearchAndSyncUser GetLDAPConnector error, error msg:%s", err) } if si == nil || si.Config == nil { logger.Error("can't find connector") @@ -101,7 +101,7 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { account := si.Config.UserSearch.NameAttr _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), - IdentityType: si.ID, + IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { logger.Errorf("ldap host:%s sync user error, error msg:%s", si.Config.Host, err) diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go index c13fae54..caf958e3 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -13,7 +13,7 @@ type Connector struct { Config *ldap.Config `json:"config"` } -func (c *Client) GetConnector(id string) (*Connector, error) { +func (c *Client) GetLDAPConnector(id string) (*Connector, error) { url := "/connectors/" + id res := &Connector{} -- Gitee From 4e81b3cf0f9a3d397bb56c7f441f02c11ba2fbcc Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 3 Nov 2021 17:19:22 +0800 Subject: [PATCH 0454/2647] fix auth mismatch Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index e5ad3d10..bac66f0b 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -178,7 +178,7 @@ allowed_roles[role_ref] { allowed_roles[role_ref] { some i data.bindings.role_bindings[i].uid == "*" - project := data.bindings.role_bindings[i].bindings[_].namespace == project_name + data.bindings.role_bindings[i].bindings[_].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } -- Gitee From 531e41eeb119df6fa83a2a345579d0d64027384b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 3 Nov 2021 17:30:54 +0800 Subject: [PATCH 0455/2647] webhook branch supports regular expressions Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 1 + .../service/webhook/github_workflow_task.go | 57 ++++++++---- .../service/webhook/gitlab_workflow_task.go | 86 +++++++++++-------- 3 files changed, 92 insertions(+), 52 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 86c7a161..3bf21167 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -87,6 +87,7 @@ type MainHookRepo struct { Events []config.HookEventType `bson:"events" json:"events"` Label string `bson:"label" json:"label"` Revision string `bson:"revision" json:"revision"` + IsRegular bool `bson:"is_regular" json:"is_regular"` } func (m MainHookRepo) GetLabelValue() string { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 9292683b..074b3ff9 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -19,6 +19,7 @@ package webhook import ( "context" "fmt" + "regexp" "strconv" "strings" @@ -55,15 +56,25 @@ type githubPushEventMatcher struct { func (gpem *githubPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { - if hookRepo.Branch == getBranchFromRef(*ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { - var changedFiles []string - for _, commit := range ev.Commits { - changedFiles = append(changedFiles, commit.Added...) - changedFiles = append(changedFiles, commit.Removed...) - changedFiles = append(changedFiles, commit.Modified...) - } - return MatchChanges(hookRepo, changedFiles), nil + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(*ev.Ref)) { + return false, nil + } + + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) } + return MatchChanges(hookRepo, changedFiles), nil } return false, nil @@ -106,18 +117,28 @@ type githubMergeEventMatcher struct { func (gmem *githubMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { ev := gmem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { - if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == *ev.PullRequest.Base.Ref) { - if *ev.PullRequest.State == "open" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v", ev) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { + return false, nil + } + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(*ev.PullRequest.Base.Ref) { + return false, nil + } - return MatchChanges(hookRepo, changedFiles), nil + if *ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(hookRepo, changedFiles), nil } } return false, nil diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index a58c4f5c..b16ef1f8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -18,6 +18,7 @@ package webhook import ( "fmt" + "regexp" "strconv" "strings" "sync" @@ -57,18 +58,26 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( ev := gmem.event // TODO: match codehost if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { - if ev.ObjectAttributes.State == "opened" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v, err:%v", ev, err) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) - - return MatchChanges(hookRepo, changedFiles), nil + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { + return false, nil + } + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { + return false, nil + } + if ev.ObjectAttributes.State == "opened" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v, err:%v", ev, err) + return false, err } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(hookRepo, changedFiles), nil } } return false, nil @@ -124,34 +133,43 @@ type gitlabPushEventMatcher struct { func (gpem *gitlabPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { - var changedFiles []string + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } - detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) - if err != nil { - gpem.log.Errorf("GetCodehostDetail error: %v", err) - return false, err - } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(ev.Ref)) { + return false, nil + } - client, err := gitlabtool.NewClient(detail.Address, detail.OauthToken) - if err != nil { - gpem.log.Errorf("NewClient error: %v", err) - return false, err - } + var changedFiles []string + detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) + if err != nil { + gpem.log.Errorf("GetCodehostDetail error: %v", err) + return false, err + } - // compare接口获取两个commit之间的最终的改动 - diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) - if err != nil { - gpem.log.Errorf("Failed to get push event diffs, error: %v", err) - return false, err - } - for _, diff := range diffs { - changedFiles = append(changedFiles, diff.NewPath) - changedFiles = append(changedFiles, diff.OldPath) - } + client, err := gitlabtool.NewClient(detail.Address, detail.OauthToken) + if err != nil { + gpem.log.Errorf("NewClient error: %v", err) + return false, err + } - return MatchChanges(hookRepo, changedFiles), nil + // compare接口获取两个commit之间的最终的改动 + diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) + if err != nil { + gpem.log.Errorf("Failed to get push event diffs, error: %v", err) + return false, err } + for _, diff := range diffs { + changedFiles = append(changedFiles, diff.NewPath) + changedFiles = append(changedFiles, diff.OldPath) + } + + return MatchChanges(hookRepo, changedFiles), nil } return false, nil -- Gitee From 2b09586ab5b4615df8265b967c791586edea5810 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 3 Nov 2021 19:12:35 +0800 Subject: [PATCH 0456/2647] debug code Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gitlab_workflow_task.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index b16ef1f8..929b6231 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -62,9 +62,16 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( return false, nil } isRegular := hookRepo.IsRegular + fmt.Println(fmt.Sprintf("isRegular:%v", isRegular)) + if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { return false, nil } + + fmt.Println(fmt.Sprintf("hookRepo.Branch:%s", hookRepo.Branch)) + fmt.Println(fmt.Sprintf("ev.ObjectAttributes.TargetBranch:%s", ev.ObjectAttributes.TargetBranch)) + fmt.Println(fmt.Sprintf("match:%v", regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch))) + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { return false, nil } -- Gitee From 07dba5ed80c3a8332c7830b887b6fc6cd1ad38c2 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 3 Nov 2021 19:13:47 +0800 Subject: [PATCH 0457/2647] optimize intervalExecutor logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 646f320e..1bb7ec87 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2652,23 +2652,9 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - serviceList := make([]interface{}, 0) - handler := func(data interface{}, logger *zap.SugaredLogger) error { - service := data.(*commonmodels.ProductService) - // 获取服务详情 - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: args.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - return err - } - - renderChart := chartInfoMap[service.ServiceName] + serviceList := make([]*commonmodels.Service, 0) + handler := func(serviceObj *commonmodels.Service, logger *zap.SugaredLogger) error { + renderChart := chartInfoMap[serviceObj.ServiceName] err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, helmClient) if err != nil { return err @@ -2682,7 +2668,22 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel if !ok { continue } - serviceList = append(serviceList, svc) + + // 获取服务详情 + opt := &commonrepo.ServiceFindOption{ + ServiceName: svc.ServiceName, + Type: svc.Type, + Revision: svc.Revision, + ProductName: args.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + errList = multierror.Append(errList, errors.Wrapf(err, "failed to find template servce, serviceName %s", svc.ServiceName)) + continue + } + + serviceList = append(serviceList, serviceObj) } } @@ -2754,7 +2755,7 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutor(interval time.Duration, serviceList []interface{}, handler func(data interface{}, log *zap.SugaredLogger) error, log *zap.SugaredLogger) []error { +func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, handler func(data *commonmodels.Service, log *zap.SugaredLogger) error, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } @@ -2767,9 +2768,11 @@ func intervalExecutor(interval time.Duration, serviceList []interface{}, handler for _, data := range serviceList { go func() { defer wg.Done() + log.Infof("handling single serivce %s", data.ServiceName) err := handler(data, log) if err != nil { errList = append(errList, err) + log.Errorf("service:%s apply fail, err %s", data.ServiceName, err) } }() <-ticker.C @@ -2840,31 +2843,18 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(data interface{}, log *zap.SugaredLogger) error { - service := data.(*commonmodels.ProductService) - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: service.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("failed to find service with opt %+v, err: %s", opt, err) - return errors.Wrapf(err, "failed to find template servce %s", service.ServiceName) - } - renderChart := renderChartMap[service.ServiceName] + handler := func(serviceObj *commonmodels.Service, log *zap.SugaredLogger) error { + renderChart := renderChartMap[serviceObj.ServiceName] err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) if err != nil { - return errors.Wrapf(err, "failed to install or upgrade service %s", service.ServiceName) + return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) } return nil } errList := new(multierror.Error) for groupIndex, services := range productResp.Services { - serviceList := make([]interface{}, 0) + serviceList := make([]*commonmodels.Service, 0) for _, svc := range services { _, ok := renderChartMap[svc.ServiceName] if !ok { @@ -2874,7 +2864,22 @@ func updateProductGroup(username, productName, envName, updateType string, produ if !svcNameSet.Has(svc.ServiceName) { continue } - serviceList = append(serviceList, svc) + + opt := &commonrepo.ServiceFindOption{ + ServiceName: svc.ServiceName, + Type: svc.Type, + Revision: svc.Revision, + ProductName: svc.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("failed to find service with opt %+v, err: %s", opt, err) + errList = multierror.Append(errList, errors.Wrapf(err, "failed to find template servce, serviceName %s", svc.ServiceName)) + continue + } + + serviceList = append(serviceList, serviceObj) } serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) @@ -3085,8 +3090,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(data interface{}, log *zap.SugaredLogger) error { - service := data.(*commonmodels.Service) + handler := func(service *commonmodels.Service, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, helmClient) if err != nil { @@ -3097,7 +3101,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel errList := new(multierror.Error) for groupIndex, services := range productResp.Services { - serviceList := make([]interface{}, 0) + serviceList := make([]*commonmodels.Service, 0) groupServices := make([]*commonmodels.ProductService, 0) for _, service := range services { if _, isExist := renderChartMap[service.ServiceName]; isExist { -- Gitee From c1832fe9273acd9352da9aceb8c372d68fc6763a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 3 Nov 2021 19:49:12 +0800 Subject: [PATCH 0458/2647] debug code Signed-off-by: liu deyi --- .../core/common/repository/models/testing.go | 2 +- .../core/common/repository/models/workflow.go | 2 +- .../service/webhook/codehub_workflow_task.go | 8 ++++---- .../service/webhook/gerrit_workflow_task.go | 14 +++++++------- .../core/workflow/service/webhook/github.go | 2 +- .../service/webhook/github_workflow_task.go | 16 ++++++++-------- .../service/webhook/gitlab_pipeline_task.go | 8 ++++---- .../service/webhook/gitlab_testing_task.go | 8 ++++---- .../service/webhook/gitlab_workflow_task.go | 19 ++++++++++--------- .../core/workflow/service/webhook/utils.go | 4 ++-- 10 files changed, 42 insertions(+), 41 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index 7ea1b46e..c42a6deb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -60,7 +60,7 @@ type TestingHookCtrl struct { type TestingHook struct { AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` - MainRepo MainHookRepo `bson:"main_repo" json:"main_repo"` + MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` TestArgs *TestTaskArgs `bson:"test_args" json:"test_args"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3bf21167..a05cc853 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -71,7 +71,7 @@ type WorkflowHookCtrl struct { type WorkflowHook struct { AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` CheckPatchSetChange bool `bson:"check_patch_set_change" json:"check_patch_set_change"` - MainRepo MainHookRepo `bson:"main_repo" json:"main_repo"` + MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go index 9a0eda6d..1b452cbc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go @@ -39,7 +39,7 @@ type codehubMergeEventMatcher struct { event *codehub.MergeEvent } -func (cmem *codehubMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (cmem *codehubMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := cmem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { @@ -52,7 +52,7 @@ func (cmem *codehubMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) } func (cmem *codehubMergeEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: cmem.workflow, @@ -76,7 +76,7 @@ type codehubPushEventMatcher struct { event *codehub.PushEvent } -func (cpem *codehubPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (cpem *codehubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := cpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { @@ -88,7 +88,7 @@ func (cpem *codehubPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( } func (cpem *codehubPushEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: cpem.workflow, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index 7bbf9e29..0e941bd0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -157,8 +157,8 @@ type SubmitterInfo struct { } type gerritEventMatcher interface { - Match(commonmodels.MainHookRepo) (bool, error) - UpdateTaskArgs(*commonmodels.Product, *commonmodels.WorkflowTaskArgs, commonmodels.MainHookRepo, string) *commonmodels.WorkflowTaskArgs + Match(*commonmodels.MainHookRepo) (bool, error) + UpdateTaskArgs(*commonmodels.Product, *commonmodels.WorkflowTaskArgs, *commonmodels.MainHookRepo, string) *commonmodels.WorkflowTaskArgs } type gerritChangeMergedEventMatcher struct { @@ -168,7 +168,7 @@ type gerritChangeMergedEventMatcher struct { Event *changeMergedEvent } -func (gruem *gerritChangeMergedEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gruem *gerritChangeMergedEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { event := gruem.Event if event == nil { return false, fmt.Errorf("event doesn't match") @@ -187,7 +187,7 @@ func (gruem *gerritChangeMergedEventMatcher) Match(hookRepo commonmodels.MainHoo } func (gruem *gerritChangeMergedEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: gruem.Workflow, @@ -211,7 +211,7 @@ type gerritPatchsetCreatedEventMatcher struct { Event *patchsetCreatedEvent } -func (gpcem *gerritPatchsetCreatedEventMatcher) Match(commonmodels.MainHookRepo) (bool, error) { +func (gpcem *gerritPatchsetCreatedEventMatcher) Match(*commonmodels.MainHookRepo) (bool, error) { event := gpcem.Event if event == nil { return false, fmt.Errorf("event doesn't match") @@ -229,7 +229,7 @@ func (gpcem *gerritPatchsetCreatedEventMatcher) Match(commonmodels.MainHookRepo) return false, nil } -func (gpcem *gerritPatchsetCreatedEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { +func (gpcem *gerritPatchsetCreatedEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: gpcem.Workflow, reqID: requestID, @@ -366,7 +366,7 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base mainRepo.RepoOwner = "" mainRepo.Revision = m.Event.PatchSet.Revision notification, _ = scmnotify.NewService().SendInitWebhookComment( - &mainRepo, m.Event.Change.Number, baseURI, false, false, log, + mainRepo, m.Event.Change.Number, baseURI, false, false, log, ) } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index f963ff2e..2d6e23ce 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -460,7 +460,7 @@ type AutoCancelOpt struct { MergeRequestID string CommitID string TaskType config.PipelineType - MainRepo commonmodels.MainHookRepo + MainRepo *commonmodels.MainHookRepo WorkflowArgs *commonmodels.WorkflowTaskArgs TestArgs *commonmodels.TestTaskArgs } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 074b3ff9..284e7e11 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -43,8 +43,8 @@ const SplitSymbol = "&" type githubPullRequestDiffFunc func(event *github.PullRequestEvent, id int) ([]string, error) type gitEventMatcher interface { - Match(commonmodels.MainHookRepo) (bool, error) - UpdateTaskArgs(*commonmodels.Product, *commonmodels.WorkflowTaskArgs, commonmodels.MainHookRepo, string) *commonmodels.WorkflowTaskArgs + Match(*commonmodels.MainHookRepo) (bool, error) + UpdateTaskArgs(*commonmodels.Product, *commonmodels.WorkflowTaskArgs, *commonmodels.MainHookRepo, string) *commonmodels.WorkflowTaskArgs } type githubPushEventMatcher struct { @@ -53,7 +53,7 @@ type githubPushEventMatcher struct { event *github.PushEvent } -func (gpem *githubPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gpem *githubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { if !EventConfigured(hookRepo, config.HookEventPush) { @@ -67,7 +67,7 @@ func (gpem *githubPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (b if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(*ev.Ref)) { return false, nil } - + hookRepo.Branch = getBranchFromRef(*ev.Ref) var changedFiles []string for _, commit := range ev.Commits { changedFiles = append(changedFiles, commit.Added...) @@ -90,7 +90,7 @@ func getBranchFromRef(ref string) string { } func (gpem *githubPushEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: gpem.workflow, @@ -114,7 +114,7 @@ type githubMergeEventMatcher struct { event *github.PullRequestEvent } -func (gmem *githubMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gmem *githubMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { if !EventConfigured(hookRepo, config.HookEventPr) { @@ -128,7 +128,7 @@ func (gmem *githubMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(*ev.PullRequest.Base.Ref) { return false, nil } - + hookRepo.Branch = *ev.PullRequest.Base.Ref if *ev.PullRequest.State == "open" { var changedFiles []string changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) @@ -145,7 +145,7 @@ func (gmem *githubMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( } func (gmem *githubMergeEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: gmem.workflow, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go index 9370a064..ec5ec801 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go @@ -47,7 +47,7 @@ func (pgpem *pipelineGitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHo changedFiles = make([]string, 0) ) if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(*hookRepo, config.HookEventPush) { + if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { for _, commit := range ev.Commits { changedFiles = append(changedFiles, commit.Added...) changedFiles = append(changedFiles, commit.Removed...) @@ -55,7 +55,7 @@ func (pgpem *pipelineGitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHo } } } - if MatchChanges(*hookRepo, changedFiles) { + if MatchChanges(hookRepo, changedFiles) { var ( branch = getBranchFromRef(ev.Ref) ref = ev.Ref @@ -108,7 +108,7 @@ func (pgmem *pipelineGitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainH changedFiles = make([]string, 0) ) if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if EventConfigured(*hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { + if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { if ev.ObjectAttributes.State == "opened" { changedFiles, err = pgmem.diffFunc(ev, hookRepo.CodehostID) if err != nil { @@ -119,7 +119,7 @@ func (pgmem *pipelineGitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainH } } } - if MatchChanges(*hookRepo, changedFiles) { + if MatchChanges(hookRepo, changedFiles) { var ( branch = ev.ObjectAttributes.TargetBranch commitID = ev.ObjectAttributes.LastCommit.ID diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index 082dd8fc..858b0eb1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -32,7 +32,7 @@ import ( ) type gitEventMatcherForTesting interface { - Match(commonmodels.MainHookRepo) (bool, error) + Match(*commonmodels.MainHookRepo) (bool, error) UpdateTaskArgs(*commonmodels.TestTaskArgs, string) *commonmodels.TestTaskArgs } @@ -56,7 +56,7 @@ type gitlabPushEventMatcherForTesting struct { event *gitlab.PushEvent } -func (gpem *gitlabPushEventMatcherForTesting) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gpem *gitlabPushEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { @@ -139,7 +139,7 @@ func TriggerTestByGitlabEvent(event interface{}, baseURI, requestID string, log // 发送本次commit的通知 if notification == nil { notification, _ = scmnotify.NewService().SendInitWebhookComment( - &item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, true, log, + item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, true, log, ) } } @@ -202,7 +202,7 @@ type gitlabMergeEventMatcherForTesting struct { event *gitlab.MergeEvent } -func (gmem *gitlabMergeEventMatcherForTesting) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gmem *gitlabMergeEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event // TODO: match codehost if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 929b6231..e7d38da6 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -54,7 +54,7 @@ type gitlabMergeEventMatcher struct { event *gitlab.MergeEvent } -func (gmem *gitlabMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event // TODO: match codehost if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { @@ -62,8 +62,6 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( return false, nil } isRegular := hookRepo.IsRegular - fmt.Println(fmt.Sprintf("isRegular:%v", isRegular)) - if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { return false, nil } @@ -75,6 +73,7 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { return false, nil } + hookRepo.Branch = ev.ObjectAttributes.TargetBranch if ev.ObjectAttributes.State == "opened" { var changedFiles []string changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) @@ -91,7 +90,7 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo commonmodels.MainHookRepo) ( } func (gmem *gitlabMergeEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: gmem.workflow, @@ -137,7 +136,7 @@ type gitlabPushEventMatcher struct { event *gitlab.PushEvent } -func (gpem *gitlabPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (bool, error) { +func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { if !EventConfigured(hookRepo, config.HookEventPush) { @@ -152,6 +151,8 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (b return false, nil } + hookRepo.Branch = getBranchFromRef(ev.Ref) + var changedFiles []string detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) if err != nil { @@ -183,7 +184,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo commonmodels.MainHookRepo) (b } func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( - product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo commonmodels.MainHookRepo, requestID string, + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { factory := &workflowArgsFactory{ workflow: gpem.workflow, @@ -279,7 +280,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, if notification == nil { notification, _ = scmnotify.NewService().SendInitWebhookComment( - &item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, log, + item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, log, ) // 初始化 gitlab diff_note @@ -305,7 +306,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, mErr = multierror.Append(mErr, err) // 单独创建一条通知,展示任务创建失败的错误信息 _, err2 := scmnotify.NewService().SendErrWebhookComment( - &item.MainRepo, workflow, err, prID, baseURI, false, false, log, + item.MainRepo, workflow, err, prID, baseURI, false, false, log, ) if err2 != nil { log.Errorf("SendErrWebhookComment failed, product:%s, workflow:%s, err:%v", workflow.ProductTmplName, workflow.Name, err2) @@ -340,7 +341,7 @@ func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([ } // InitDiffNote 调用gitlab接口初始化DiffNote,并保存到数据库 -func InitDiffNote(ev *gitlab.MergeEvent, mainRepo commonmodels.MainHookRepo, log *zap.SugaredLogger) error { +func InitDiffNote(ev *gitlab.MergeEvent, mainRepo *commonmodels.MainHookRepo, log *zap.SugaredLogger) error { commitID := ev.ObjectAttributes.LastCommit.ID body := "KodeRover CI 检查中..." diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 1434a232..2b5531c6 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -492,7 +492,7 @@ func (m MatchFolders) ContainsFile(file string) bool { return false } -func MatchChanges(m commonmodels.MainHookRepo, files []string) bool { +func MatchChanges(m *commonmodels.MainHookRepo, files []string) bool { mf := MatchFolders(m.MatchFolders) for _, file := range files { if matches := mf.ContainsFile(file); matches { @@ -502,7 +502,7 @@ func MatchChanges(m commonmodels.MainHookRepo, files []string) bool { return false } -func EventConfigured(m commonmodels.MainHookRepo, event config.HookEventType) bool { +func EventConfigured(m *commonmodels.MainHookRepo, event config.HookEventType) bool { for _, ev := range m.Events { if ev == event { return true -- Gitee From ed60b7900b9c50b7bb3d9e816aa85c4b8a09ebe9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 3 Nov 2021 20:10:36 +0800 Subject: [PATCH 0459/2647] add test webhook regular expressions Signed-off-by: liu deyi --- .../service/webhook/gitlab_testing_task.go | 60 +++++++++++++------ .../service/webhook/gitlab_workflow_task.go | 4 -- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index 858b0eb1..221f013b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -17,6 +17,7 @@ limitations under the License. package webhook import ( + "regexp" "strconv" multierror "github.com/hashicorp/go-multierror" @@ -59,16 +60,26 @@ type gitlabPushEventMatcherForTesting struct { func (gpem *gitlabPushEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { - var changedFiles []string - for _, commit := range ev.Commits { - changedFiles = append(changedFiles, commit.Added...) - changedFiles = append(changedFiles, commit.Removed...) - changedFiles = append(changedFiles, commit.Modified...) - } + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } - return MatchChanges(hookRepo, changedFiles), nil + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(ev.Ref)) { + return false, nil } + hookRepo.Branch = getBranchFromRef(ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + + return MatchChanges(hookRepo, changedFiles), nil } return false, nil @@ -206,19 +217,32 @@ func (gmem *gitlabMergeEventMatcherForTesting) Match(hookRepo *commonmodels.Main ev := gmem.event // TODO: match codehost if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { - if ev.ObjectAttributes.State == "opened" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v", ev) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } - return MatchChanges(hookRepo, changedFiles), nil + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { + return false, nil + } + + if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { + return false, nil + } + hookRepo.Branch = ev.ObjectAttributes.TargetBranch + + if ev.ObjectAttributes.State == "opened" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(hookRepo, changedFiles), nil } + } return false, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index e7d38da6..4f738e60 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -66,10 +66,6 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) return false, nil } - fmt.Println(fmt.Sprintf("hookRepo.Branch:%s", hookRepo.Branch)) - fmt.Println(fmt.Sprintf("ev.ObjectAttributes.TargetBranch:%s", ev.ObjectAttributes.TargetBranch)) - fmt.Println(fmt.Sprintf("match:%v", regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch))) - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { return false, nil } -- Gitee From e20bf31aaf7e1f464871d7e14ac447da789fd4a8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 3 Nov 2021 20:45:29 +0800 Subject: [PATCH 0460/2647] add service apply backoff logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 1bb7ec87..d6725cdd 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -27,6 +27,7 @@ import ( "sync" "time" + "github.com/cenkalti/backoff/v3" "github.com/hashicorp/go-multierror" helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" @@ -149,6 +150,8 @@ type RawYamlResp struct { YamlContent string `json:"yamlContent"` } +type intervalExecutorHandler func(data *commonmodels.Service, log *zap.SugaredLogger) error + func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { err := commonrepo.NewProductColl().UpdateIsPublic(args.EnvName, productName, args.IsPublic) if err != nil { @@ -2687,7 +2690,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel } } - serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2755,7 +2758,26 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, handler func(data *commonmodels.Service, log *zap.SugaredLogger) error, log *zap.SugaredLogger) []error { +func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { + bo := backoff.NewExponentialBackOff() + bo.InitialInterval = 3 * time.Second + bo1 := backoff.WithMaxRetries(bo, retryCount) + errList := make([]error, 0) + + _ = backoff.Retry(func() error { + failedServices := make([]*commonmodels.Service, 0) + errList = intervalExecutor(interval, serviceList, &failedServices, handler, log) + if len(errList) == 0 { + return nil + } + log.Infof("############################# %d services waiting to retry", len(failedServices)) + serviceList = failedServices + return fmt.Errorf("%d services apply fail", len(errList)) + }, bo1) + return errList +} + +func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } @@ -2772,6 +2794,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic err := handler(data, log) if err != nil { errList = append(errList, err) + *failedServices = append(*failedServices, data) log.Errorf("service:%s apply fail, err %s", data.ServiceName, err) } }() @@ -2882,7 +2905,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -3121,7 +3144,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := intervalExecutor(time.Millisecond*2500, serviceList, handler, log) + groupServiceErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } -- Gitee From 79b67051b384a1454b061f2ea9978f35a170abc1 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 3 Nov 2021 21:23:45 +0800 Subject: [PATCH 0461/2647] fix get-hook-secert (#527) * fix get-hook-secert Signed-off-by: lou * fix hook secret Signed-off-by: lou --- .../aslan/core/common/service/git/hook.go | 15 ++++++--------- .../aslan/core/workflow/service/webhook/gitlab.go | 4 ++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/git/hook.go b/pkg/microservice/aslan/core/common/service/git/hook.go index a1d99219..08770cc5 100644 --- a/pkg/microservice/aslan/core/common/service/git/hook.go +++ b/pkg/microservice/aslan/core/common/service/git/hook.go @@ -17,17 +17,14 @@ limitations under the License. package git import ( - "github.com/koderover/zadig/pkg/tool/crypto" - "github.com/koderover/zadig/pkg/tool/log" -) + "crypto/sha256" + "encoding/hex" -const plainSecret = "webhook secret" + "github.com/koderover/zadig/pkg/config" +) func GetHookSecret() string { - token, err := crypto.AesEncrypt(plainSecret) - if err != nil { - log.DPanic(err) - } + hash := sha256.Sum256([]byte(config.SecretKey())) - return token + return hex.EncodeToString(hash[:]) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 39013998..7fafcdec 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -312,8 +312,8 @@ func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) func GetGitlabServiceTemplates() ([]*commonmodels.Service, error) { opt := &commonrepo.ServiceListOption{ - Type: setting.K8SDeployType, - Source: setting.SourceFromGitlab, + Type: setting.K8SDeployType, + Source: setting.SourceFromGitlab, } return commonrepo.NewServiceColl().ListMaxRevisions(opt) } -- Gitee From 7c1875bdf65430f7f65389d32305b02008d425d0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 10:03:15 +0800 Subject: [PATCH 0462/2647] poetry Signed-off-by: mouuii --- go.mod | 1 + go.sum | 1 + .../systemconfig/config/config.go | 9 + .../systemconfig/core/handler/emails.go | 79 +++++++++ .../systemconfig/core/handler/router.go | 12 ++ .../core/repository/models/email.go | 38 ++++ .../core/repository/mongo/email.go | 163 ++++++++++++++++++ pkg/microservice/systemconfig/core/service.go | 34 ++++ .../systemconfig/core/service/email.go | 40 +++++ 9 files changed, 377 insertions(+) create mode 100644 pkg/microservice/systemconfig/core/handler/emails.go create mode 100644 pkg/microservice/systemconfig/core/repository/models/email.go create mode 100644 pkg/microservice/systemconfig/core/repository/mongo/email.go create mode 100644 pkg/microservice/systemconfig/core/service/email.go diff --git a/go.mod b/go.mod index d17cc71d..bd573294 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/garyburd/redigo v1.6.2 // indirect github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 + github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index e9ff3cff..05dce6a4 100644 --- a/go.sum +++ b/go.sum @@ -550,6 +550,7 @@ github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index 1dbf7ef9..2430e9b9 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -4,6 +4,7 @@ import ( "github.com/spf13/viper" _ "github.com/koderover/zadig/pkg/config" + configbase "github.com/koderover/zadig/pkg/config" ) func MysqlDexDB() string { @@ -13,3 +14,11 @@ func MysqlDexDB() string { func Features() string { return viper.GetString(FeatureFlag) } + +func MongoURI() string { + return configbase.MongoURI() +} + +func MongoDatabase() string { + return configbase.MongoDatabase() +} diff --git a/pkg/microservice/systemconfig/core/handler/emails.go b/pkg/microservice/systemconfig/core/handler/emails.go new file mode 100644 index 00000000..f1a33ee0 --- /dev/null +++ b/pkg/microservice/systemconfig/core/handler/emails.go @@ -0,0 +1,79 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetEmailHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) +} + +func CreateEmailHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + req := new(models.EmailHost) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.CreateEmailHost(req, ctx.Logger) +} + +func UpdateEmailHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + req := new(models.EmailHost) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.UpdateEmailHost(req, ctx.Logger) +} + +func DeleteEmailHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = service.DeleteEmailHost(ctx.Logger) +} + +//// + +func GetEmailService(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) +} + +func CreateEmailService(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + req := new(models.EmailHost) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.CreateEmailHost(req, ctx.Logger) +} + +func UpdateEmailService(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + req := new(models.EmailHost) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.UpdateEmailHost(req, ctx.Logger) +} + +func DeleteEmailService(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = service.DeleteEmailHost(ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index 282f4cb1..f61b00ec 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -35,4 +35,16 @@ func (*Router) Inject(router *gin.RouterGroup) { { features.GET("/:name", GetFeature) } + emails := router.Group("emails") + { + emails.GET("/host", GetEmailHost) + emails.POST("/host", CreateEmailHost) + emails.PATCH("/host", UpdateEmailHost) + emails.DELETE("/host", DeleteEmailHost) + + emails.GET("/service", GetEmailService) + emails.POST("/service", CreateEmailService) + emails.PATCH("/service", UpdateEmailService) + emails.DELETE("/service", DeleteEmailService) + } } diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go new file mode 100644 index 00000000..8209f4e3 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -0,0 +1,38 @@ +package models + +import ( + "github.com/globalsign/mgo/bson" +) + +type EmailHost struct { + ID int `json:"id"` + Name string `json:"name"` + Port int `json:"port"` + Username string `json:"username"` + Password string `json:"password"` + IsTLS bool `json:"isTLS"` + OrganizationID int `json:"orgId"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +func (EmailHost) TableName() string { + return "email_host" +} + +type EmailService struct { + ObjectID bson.ObjectId `bson:"_id,omitempty"` + ID int `bson:"id"` + Name string `bson:"name"` + Address string `bson:"address"` + DisplayName string `bson:"display_name"` + Theme string `bson:"theme"` + OrganizationID int `bson:"organization_id"` + CreatedAt int64 `bson:"created_at"` + UpdatedAt int64 `bson:"updated_at"` + DeletedAt int64 `bson:"deleted_at"` +} + +func (EmailService) TableName() string { + return "email_service" +} diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go new file mode 100644 index 00000000..ca02e977 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -0,0 +1,163 @@ +package mongo + +import ( + "context" + "time" + + "github.com/globalsign/mgo/bson" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type EmailHostColl struct { + *mongo.Collection + + coll string +} + +type EmailServiceColl struct { + *mongo.Collection + + coll string +} + +func NewEmailServiceColl() *EmailServiceColl { + name := models.EmailHost{}.TableName() + coll := &EmailServiceColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func NewEmailHostColl() *EmailHostColl { + name := models.EmailHost{}.TableName() + coll := &EmailHostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *EmailServiceColl) GetCollectionName() string { + return c.coll +} +func (c *EmailServiceColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *EmailHostColl) GetCollectionName() string { + return c.coll +} +func (c *EmailHostColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *EmailHostColl) Find() (*models.EmailHost, error) { + emailHost := new(models.EmailHost) + query := bson.M{"deleted_at": 0} + + ctx := context.Background() + + err := c.Collection.FindOne(ctx, query).Decode(emailHost) + if err != nil { + log.Errorf("repository GetEmailHost err : %v", err) + return nil, err + } + return emailHost, nil +} + +func (c *EmailHostColl) Update(emailHost *models.EmailHost) (*models.EmailHost, error) { + query := bson.M{"organization_id": emailHost.OrganizationID, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "name": emailHost.Name, + "port": emailHost.Port, + "username": emailHost.Username, + "password": emailHost.Password, + "is_tls": emailHost.IsTLS, + "updated_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository Update EmailHostColl err : %v", err) + return nil, err + } + return emailHost, nil +} + +func (c *EmailHostColl) Delete() error { + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository Delete EmailHostColl err : %v", err) + return err + } + return nil +} + +func (c *EmailHostColl) Add(emailHost *models.EmailHost) (*models.EmailHost, error) { + + _, err := c.Collection.InsertOne(context.TODO(), emailHost) + if err != nil { + log.Error("repository AddEmailHost err : %v", err) + return nil, err + } + return emailHost, nil +} + +func (c *EmailServiceColl) AddEmailService(iEmailService *models.EmailService) (*models.EmailService, error) { + + _, err := c.Collection.InsertOne(context.TODO(), iEmailService) + if err != nil { + log.Error("repository AddEmailService err : %v", err) + return nil, err + } + return iEmailService, nil +} + +func (c *EmailServiceColl) GetEmailService() (*models.EmailService, error) { + query := bson.M{"deleted_at": 0} + iEmailService := &models.EmailService{} + err := c.Collection.FindOne(context.TODO(), query).Decode(iEmailService) + if err != nil { + log.Error("repository GetEmailService err : %v", err) + return nil, err + } + return iEmailService, nil +} + +func (c *EmailServiceColl) UpdateEmailService(iEmailService *models.EmailService) (*models.EmailService, error) { + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "name": iEmailService.Name, + "address": iEmailService.Address, + "display_name": iEmailService.DisplayName, + "theme": iEmailService.Theme, + "updated_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository UpdateEmailService err : %v", err) + return nil, err + } + return iEmailService, nil +} + +func (c *EmailServiceColl) DeleteEmailService() error { + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository DeleteEmailServiceByOrgID err : %v", err) + return err + } + return nil +} diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index e77858f8..e46d9786 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -18,13 +18,18 @@ package core import ( "context" + "fmt" + "sync" + "time" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) func Start(_ context.Context) { @@ -49,6 +54,35 @@ func initDatabase() { if err != nil { log.Panicf("Failed to open database %s", config.MysqlDexDB()) } + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + mongotool.Init(ctx, config.MongoURI()) + if err := mongotool.Ping(ctx); err != nil { + panic(fmt.Errorf("failed to connect to mongo, error: %s", err)) + } + idxCtx, idxCancel := context.WithTimeout(ctx, 10*time.Minute) + defer idxCancel() + var wg sync.WaitGroup + for _, r := range []indexer{ + mongo.NewEmailHostColl(), + } { + + wg.Add(1) + go func(r indexer) { + defer wg.Done() + if err := r.EnsureIndex(idxCtx); err != nil { + panic(fmt.Errorf("failed to create index for %s, error: %s", r.GetCollectionName(), err)) + } + }(r) + } + wg.Wait() +} + +type indexer interface { + EnsureIndex(ctx context.Context) error + GetCollectionName() string } func Stop(_ context.Context) { diff --git a/pkg/microservice/systemconfig/core/service/email.go b/pkg/microservice/systemconfig/core/service/email.go new file mode 100644 index 00000000..e4f9ea5b --- /dev/null +++ b/pkg/microservice/systemconfig/core/service/email.go @@ -0,0 +1,40 @@ +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" +) + +func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { + return mongo.NewEmailHostColl().Find() +} + +func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { + return mongo.NewEmailHostColl().Add(emailHost) +} + +func UpdateEmailHost(host *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { + return mongo.NewEmailHostColl().Update(host) +} + +func DeleteEmailHost(_ *zap.SugaredLogger) error { + return mongo.NewEmailHostColl().Delete() +} + +func GetEmailService(_ *zap.SugaredLogger) (*models.EmailService, error) { + return mongo.NewEmailServiceColl().GetEmailService() +} + +func CreateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { + return mongo.NewEmailServiceColl().AddEmailService(service) +} + +func UpdateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { + return mongo.NewEmailServiceColl().UpdateEmailService(service) +} + +func DeleteEmailService(_ *zap.SugaredLogger) error { + return mongo.NewEmailServiceColl().DeleteEmailService() +} -- Gitee From ab68e62046ba676cf29a31e331a54108ddb76210 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 4 Nov 2021 10:06:36 +0800 Subject: [PATCH 0463/2647] optmize code Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d6725cdd..83787844 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2761,19 +2761,18 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { bo := backoff.NewExponentialBackOff() bo.InitialInterval = 3 * time.Second - bo1 := backoff.WithMaxRetries(bo, retryCount) + retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) - _ = backoff.Retry(func() error { failedServices := make([]*commonmodels.Service, 0) errList = intervalExecutor(interval, serviceList, &failedServices, handler, log) if len(errList) == 0 { return nil } - log.Infof("############################# %d services waiting to retry", len(failedServices)) + log.Infof("%d services waiting to retry", len(failedServices)) serviceList = failedServices return fmt.Errorf("%d services apply fail", len(errList)) - }, bo1) + }, retryBo) return errList } -- Gitee From 4d9e7f1310f2e1d184448636756a66f9bdabbd5c Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 10:35:01 +0800 Subject: [PATCH 0464/2647] poetry Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/handler/emails.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/systemconfig/core/handler/emails.go b/pkg/microservice/systemconfig/core/handler/emails.go index f1a33ee0..a1fcbea1 100644 --- a/pkg/microservice/systemconfig/core/handler/emails.go +++ b/pkg/microservice/systemconfig/core/handler/emails.go @@ -47,33 +47,33 @@ func DeleteEmailHost(c *gin.Context) { func GetEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) + ctx.Resp, ctx.Err = service.GetEmailService(ctx.Logger) } func CreateEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models.EmailHost) + req := new(models.EmailService) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service.CreateEmailHost(req, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateEmailService(req, ctx.Logger) } func UpdateEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models.EmailHost) + req := new(models.EmailService) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service.UpdateEmailHost(req, ctx.Logger) + ctx.Resp, ctx.Err = service.UpdateEmailService(req, ctx.Logger) } func DeleteEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DeleteEmailHost(ctx.Logger) + ctx.Err = service.DeleteEmailService(ctx.Logger) } -- Gitee From 63d345f5871af9cee181ac4e4b36cdc74c7e5803 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 4 Nov 2021 10:35:13 +0800 Subject: [PATCH 0465/2647] optimize regular expressions Signed-off-by: liu deyi --- .../service/webhook/github_workflow_task.go | 13 +++++++++---- .../service/webhook/gitlab_testing_task.go | 12 ++++++++---- .../service/webhook/gitlab_workflow_task.go | 14 ++++++++------ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 284e7e11..7f1a379a 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -64,8 +64,11 @@ func (gpem *githubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { return false, nil } - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(*ev.Ref)) { - return false, nil + if isRegular { + // Do not use regexp.MustCompile to avoid panic + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + return false, nil + } } hookRepo.Branch = getBranchFromRef(*ev.Ref) var changedFiles []string @@ -125,8 +128,10 @@ func (gmem *githubMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { return false, nil } - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(*ev.PullRequest.Base.Ref) { - return false, nil + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.PullRequest.Base.Ref); !matched { + return false, nil + } } hookRepo.Branch = *ev.PullRequest.Base.Ref if *ev.PullRequest.State == "open" { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index 221f013b..3c23fe42 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -68,8 +68,10 @@ func (gpem *gitlabPushEventMatcherForTesting) Match(hookRepo *commonmodels.MainH return false, nil } - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(ev.Ref)) { - return false, nil + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil + } } hookRepo.Branch = getBranchFromRef(ev.Ref) var changedFiles []string @@ -226,8 +228,10 @@ func (gmem *gitlabMergeEventMatcherForTesting) Match(hookRepo *commonmodels.Main return false, nil } - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { - return false, nil + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { + return false, nil + } } hookRepo.Branch = ev.ObjectAttributes.TargetBranch diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 4f738e60..f4461656 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -65,9 +65,10 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { return false, nil } - - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(ev.ObjectAttributes.TargetBranch) { - return false, nil + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { + return false, nil + } } hookRepo.Branch = ev.ObjectAttributes.TargetBranch if ev.ObjectAttributes.State == "opened" { @@ -143,10 +144,11 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { return false, nil } - if isRegular && !regexp.MustCompile(hookRepo.Branch).MatchString(getBranchFromRef(ev.Ref)) { - return false, nil + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil + } } - hookRepo.Branch = getBranchFromRef(ev.Ref) var changedFiles []string -- Gitee From 3bf8a4e177a029940331f75df9c2eba2e8f0911e Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 4 Nov 2021 10:42:15 +0800 Subject: [PATCH 0466/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 83787844..322df798 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2771,7 +2771,7 @@ func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, servic } log.Infof("%d services waiting to retry", len(failedServices)) serviceList = failedServices - return fmt.Errorf("%d services apply fail", len(errList)) + return fmt.Errorf("%d services apply failed", len(errList)) }, retryBo) return errList } -- Gitee From 72f90998b7135240b050fd8969c8874ea6583fb7 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 4 Nov 2021 10:42:42 +0800 Subject: [PATCH 0467/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 322df798..8d25cd2d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2794,7 +2794,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic if err != nil { errList = append(errList, err) *failedServices = append(*failedServices, data) - log.Errorf("service:%s apply fail, err %s", data.ServiceName, err) + log.Errorf("service:%s apply failed, err %s", data.ServiceName, err) } }() <-ticker.C -- Gitee From 59f2e5f16a1c3b7dfce6849fad5d28e30c041af2 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 4 Nov 2021 11:00:32 +0800 Subject: [PATCH 0468/2647] =?UTF-8?q?add=20signup=E3=80=81retrieve?= =?UTF-8?q?=E3=80=81delete=E3=80=81reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 2 + go.sum | 4 + pkg/microservice/user/config/config.go | 16 ++ pkg/microservice/user/config/consts.go | 1 + pkg/microservice/user/core/handler/router.go | 2 + .../user/core/handler/user/user.go | 35 ++++ .../user/core/repository/orm/user.go | 10 ++ .../user/core/repository/orm/user_login.go | 10 ++ .../user/core/service/user/user.go | 155 ++++++++++++++++++ pkg/microservice/user/server/rest/router.go | 4 + pkg/microservice/user/server/server.go | 4 +- pkg/setting/consts.go | 19 ++- pkg/util/mail/mail.go | 50 ++++++ 13 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 pkg/util/mail/mail.go diff --git a/go.mod b/go.mod index d17cc71d..70860f4e 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,8 @@ require ( golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/mholt/archiver.v3 v3.1.1 gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b diff --git a/go.sum b/go.sum index e9ff3cff..15c88469 100644 --- a/go.sum +++ b/go.sum @@ -2235,6 +2235,8 @@ google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+Rur google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -2249,6 +2251,8 @@ gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go index 5999c817..a633c8b0 100644 --- a/pkg/microservice/user/config/config.go +++ b/pkg/microservice/user/config/config.go @@ -52,3 +52,19 @@ func MysqlUserDB() string { func TokenExpiresAt() int { return viper.GetInt(setting.ENVTokenExpiresAt) } + +func NoReplyEmailAddress() string { + return viper.GetString(setting.ENVNoReplyEmailAddress) +} + +func NoReplyEmailPassword() string { + return viper.GetString(setting.ENVNoReplyEmailPassword) +} + +func UserPort() string { + port := viper.GetString(setting.ENVUserPort) + if len(port) > 0 { + return port + } + return "80" +} diff --git a/pkg/microservice/user/config/consts.go b/pkg/microservice/user/config/consts.go index 4935cbe9..96e4e9ca 100644 --- a/pkg/microservice/user/config/consts.go +++ b/pkg/microservice/user/config/consts.go @@ -23,6 +23,7 @@ import ( const ( AppState = setting.ProductName + "user" SystemIdentityType = "system" + FeiShuEmailHost = "smtp.feishu.cn" ) type LoginType int diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index 5b191c85..fdf7fbf8 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -36,6 +36,8 @@ func (*Router) Inject(router *gin.RouterGroup) { users.GET("/users/:uid", user.GetUser) + users.DELETE("/users/:uid", user.DeleteUser) + users.GET("/users/:uid/personal", user.GetPersonalUser) users.POST("/users/search", user.ListUsers) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 7694a6e9..f3a01fe7 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -24,6 +24,12 @@ func GetUser(c *gin.Context) { ctx.Resp, ctx.Err = user.GetUser(c.Param("uid"), ctx.Logger) } +func DeleteUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = user.DeleteUserByUID(c.Param("uid"), ctx.Logger) +} + func GetPersonalUser(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -66,6 +72,23 @@ func CreateUser(c *gin.Context) { ctx.Resp, ctx.Err = user.CreateUser(args, ctx.Logger) } +func SignUp(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.User{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = user.CreateUser(args, ctx.Logger) +} + +func Retrieve(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = user.Retrieve(c.Query("account"), ctx.Logger) +} + func UpdatePassword(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -77,3 +100,15 @@ func UpdatePassword(c *gin.Context) { args.Uid = c.Param("uid") ctx.Err = user.UpdatePassword(args, ctx.Logger) } + +func Reset(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.ResetParams{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + args.Uid = ctx.UserID + ctx.Err = user.Reset(args, ctx.Logger) +} diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index ba025eac..27f4e55c 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -99,6 +99,16 @@ func DeleteUserByUids(uids []string, db *gorm.DB) error { return nil } +// DeleteUserByUid Delete users based on uids +func DeleteUserByUid(uid string, db *gorm.DB) error { + var user models.User + err := db.Where("uid = ?", uid).Delete(&user).Error + if err != nil { + return err + } + return nil +} + // GetUsersCount gets user count func GetUsersCount(name string) (int64, error) { var ( diff --git a/pkg/microservice/user/core/repository/orm/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go index a46c517d..b44af382 100644 --- a/pkg/microservice/user/core/repository/orm/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -38,6 +38,16 @@ func DeleteUserLoginByUids(uids []string, db *gorm.DB) error { return nil } +// DeleteUserLoginByUid Delete userLogin based on uids +func DeleteUserLoginByUid(uid string, db *gorm.DB) error { + var userLogin models.UserLogin + err := db.Where("uid = ?", uid).Delete(&userLogin).Error + if err != nil { + return err + } + return nil +} + // ListUserLogins Get a userLogin based on uid list func ListUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { var userLogins []models.UserLogin diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 648fa102..07d9ed92 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -2,18 +2,24 @@ package user import ( "fmt" + "net/url" "time" ldapv3 "github.com/go-ldap/ldap/v3" + "github.com/golang-jwt/jwt" "github.com/google/uuid" "go.uber.org/zap" "golang.org/x/crypto/bcrypt" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" + "github.com/koderover/zadig/pkg/microservice/user/core/service/login" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/util/mail" ) type User struct { @@ -49,6 +55,11 @@ type Password struct { NewPassword string `json:"newPassword"` } +type ResetParams struct { + Uid string `json:"uid"` + Password string `json:"password"` +} + type UsersResp struct { Users []UserInfo `json:"users"` TotalCount int64 `json:"totalCount"` @@ -60,6 +71,10 @@ type SyncUserInfo struct { Name string `json:"name"` } +type RetrieveResp struct { + Email string `json:"email"` +} + func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { systemConfigClient := systemconfig.New() si, err := systemConfigClient.GetLDAPConnector(ldapId) @@ -101,6 +116,7 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { account := si.Config.UserSearch.NameAttr _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), + Name: entry.GetAttributeValue(account), IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { @@ -238,6 +254,120 @@ func getLoginId(user *models.User, loginType config.LoginType) string { } +func DeleteUserByUID(uid string, logger *zap.SugaredLogger) error { + tx := core.DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + err := orm.DeleteUserByUid(uid, tx) + if err != nil { + tx.Rollback() + logger.Errorf("DeleteUserByUID DeleteUserByUid :%s error, error msg:%s", uid, err.Error()) + return err + } + err = orm.DeleteUserLoginByUid(uid, tx) + if err != nil { + tx.Rollback() + logger.Errorf("DeleteUserByUID DeleteUserLoginByUid:%s error, error msg:%s", uid, err.Error()) + return err + } + return tx.Commit().Error +} + +func getRetrieveHtmlTemplate() string { + return ` +
+
+ + + + + + +
hi,您好
+
+
+ +
+
+

非常感谢使用 Zadig,点击下面的链接完成重置密码。

+
+
+ + + + + + + + + +
{{.Url}}
如果您不想继续完成密码重置,请忽略该邮件,链接将在5分钟之后过期!
+
+
+ +
+

本邮件由 Zadig 系统自动发出,请勿直接回复。

+

Made By Zadig Team ♥ Happy Coding.

+
` +} + +func Retrieve(account string, logger *zap.SugaredLogger) (*RetrieveResp, error) { + user, err := orm.GetUser(account, config.SystemIdentityType, core.DB) + if err != nil { + logger.Errorf("Retrieve GetUser:%s error, error msg:%s ", account, err) + return nil, fmt.Errorf("Retrieve GetUser:%s error, error msg:%s ", account, err) + } + if user == nil { + return nil, fmt.Errorf("user not exist") + } + if len(user.Email) == 0 { + logger.Errorf("the account:%s has not email", account) + return nil, fmt.Errorf("the account has not email") + } + + token, err := login.CreateToken(&login.Claims{ + Name: user.Name, + Account: user.Account, + Uid: user.UID, + StandardClaims: jwt.StandardClaims{ + Audience: setting.ProductName, + ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), + }, + }) + if err != nil { + logger.Errorf("Retrieve user:%s create token error, error msg:%s", user.Account, err) + return nil, err + } + v := url.Values{} + v.Add("idtoken", token) + retrieveURL := configbase.SystemAddress() + "/signin?" + v.Encode() + body, err := mail.RenderEmailTemplate(retrieveURL, getRetrieveHtmlTemplate()) + if err != nil { + logger.Errorf("Retrieve renderEmailTemplate error, error msg:%s ", err) + return nil, fmt.Errorf("Retrieve renderEmailTemplate error, error msg:%s ", err) + } + err = mail.SendEmail(&mail.EmailParams{ + From: config.NoReplyEmailAddress(), + To: user.Email, + Subject: "重置密码", + Host: config.FeiShuEmailHost, + UserName: config.NoReplyEmailAddress(), + Password: config.NoReplyEmailPassword(), + Port: 465, + Body: body, + }) + if err != nil { + logger.Errorf("Retrieve SendEmail error, error msg:%s ", err) + return nil, err + } + return &RetrieveResp{ + Email: user.Email, + }, nil +} + func CreateUser(args *User, logger *zap.SugaredLogger) (*models.User, error) { uid, _ := uuid.NewUUID() user := &models.User{ @@ -318,6 +448,30 @@ func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { return nil } +func Reset(args *ResetParams, logger *zap.SugaredLogger) error { + user, err := orm.GetUserByUid(args.Uid, core.DB) + if err != nil { + logger.Errorf("Reset GetUserByUid:%s error, error msg:%s", args.Uid, err) + return err + } + if user == nil { + logger.Error("user not exist") + return fmt.Errorf("user not exist") + } + + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.Password), bcrypt.DefaultCost) + userLogin := &models.UserLogin{ + UID: user.UID, + Password: string(hashedPassword), + } + err = orm.UpdateUserLogin(user.UID, userLogin, core.DB) + if err != nil { + logger.Errorf("UpdatePassword UpdateUserLogin:%v error, error msg:%s", userLogin, err.Error()) + return err + } + return nil +} + func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.User, error) { user, err := orm.GetUser(syncUserInfo.Account, syncUserInfo.IdentityType, core.DB) if err != nil { @@ -374,6 +528,7 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us } err = tx.Commit().Error if err != nil { + logger.Errorf("SyncUser tx commit error, error msg:%s ", err) return nil, err } return user, nil diff --git a/pkg/microservice/user/server/rest/router.go b/pkg/microservice/user/server/rest/router.go index 27be0a98..1ec76974 100644 --- a/pkg/microservice/user/server/rest/router.go +++ b/pkg/microservice/user/server/rest/router.go @@ -21,12 +21,16 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core/handler" "github.com/koderover/zadig/pkg/microservice/user/core/handler/login" + "github.com/koderover/zadig/pkg/microservice/user/core/handler/user" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { { router.GET("login", login.Login) router.POST("login", login.LocalLogin) + router.POST("signup", user.SignUp) + router.GET("retrieve", user.Retrieve) + router.POST("reset", user.Reset) } for name, r := range map[string]injector{ "/api/v1": new(handler.Router), diff --git a/pkg/microservice/user/server/server.go b/pkg/microservice/user/server/server.go index aa0f55e5..648b1196 100644 --- a/pkg/microservice/user/server/server.go +++ b/pkg/microservice/user/server/server.go @@ -21,6 +21,7 @@ import ( "net/http" "time" + "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/server/rest" "github.com/koderover/zadig/pkg/tool/log" @@ -33,8 +34,7 @@ func Serve(ctx context.Context) error { log.Info("Start user system service") engine := rest.NewEngine() - server := &http.Server{Addr: ":80", Handler: engine} - + server := &http.Server{Addr: ":" + config.UserPort(), Handler: engine} stopChan := make(chan struct{}) go func() { defer close(stopChan) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 3f28e61c..8603a63b 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -104,14 +104,17 @@ const ( TestMode = "test" // user - ENVIssuerURL = "ISSUER_URL" - ENVClientID = "CLIENT_ID" - ENVClientSecret = "CLIENT_SECRET" - ENVRedirectURI = "REDIRECT_URI" - ENVSecretKey = "SECRET_KEY" - ENVMysqlUserDB = "MYSQL_USER_DB" - ENVScopes = "SCOPES" - ENVTokenExpiresAt = "TOKEN_EXPIRES_AT" + ENVIssuerURL = "ISSUER_URL" + ENVClientID = "CLIENT_ID" + ENVClientSecret = "CLIENT_SECRET" + ENVRedirectURI = "REDIRECT_URI" + ENVSecretKey = "SECRET_KEY" + ENVMysqlUserDB = "MYSQL_USER_DB" + ENVScopes = "SCOPES" + ENVTokenExpiresAt = "TOKEN_EXPIRES_AT" + ENVNoReplyEmailAddress = "NOREPLY_EMAIL_ADDRESS" + ENVNoReplyEmailPassword = "NOREPLY_EMAIL_PASSWORD" + ENVUserPort = "USER_PORT" // initconfig ENVAdminEmail = "ADMIN_EMAIL" diff --git a/pkg/util/mail/mail.go b/pkg/util/mail/mail.go new file mode 100644 index 00000000..ca8acdb1 --- /dev/null +++ b/pkg/util/mail/mail.go @@ -0,0 +1,50 @@ +package mail + +import ( + "bytes" + "html/template" + + "gopkg.in/gomail.v2" +) + +type EmailParams struct { + From string + To string + Subject string + Body string + Host string + Port int + UserName string + Password string +} + +func SendEmail(param *EmailParams) error { + m := gomail.NewMessage() + m.SetHeader("From", param.From) + m.SetHeader("To", param.To) + m.SetHeader("Subject", param.Subject) + m.SetBody("text/html", param.Body) + + d := gomail.NewDialer(param.Host, param.Port, param.UserName, param.Password) + + if err := d.DialAndSend(m); err != nil { + return err + } + return nil +} + +type GenerateUrl struct { + Url string +} + +func RenderEmailTemplate(url string, html string) (content string, err error) { + buf := new(bytes.Buffer) + t := template.Must(template.New("email").Funcs(template.FuncMap{}).Parse(html)) + + if err = t.Execute(buf, &GenerateUrl{ + Url: url, + }); err != nil { + return "", err + } + return buf.String(), nil +} -- Gitee From ad71fe65614e4df020ea372dadffccedd1bac2e4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 4 Nov 2021 11:16:26 +0800 Subject: [PATCH 0469/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/gitlab_workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index f4461656..85e9eb20 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -75,7 +75,7 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) var changedFiles []string changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) if err != nil { - gmem.log.Warnf("failed to get changes of event %v, err:%v", ev, err) + gmem.log.Warnf("failed to get changes of event %v, err:%s", ev, err) return false, err } gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) -- Gitee From 2defac8a431bbdede1cb64368d140b9787c5bf59 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 4 Nov 2021 11:22:07 +0800 Subject: [PATCH 0470/2647] optimize code Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gitlab_workflow_task.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 85e9eb20..73eeb855 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -154,20 +154,20 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( var changedFiles []string detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) if err != nil { - gpem.log.Errorf("GetCodehostDetail error: %v", err) + gpem.log.Errorf("GetCodehostDetail error: %s", err) return false, err } client, err := gitlabtool.NewClient(detail.Address, detail.OauthToken) if err != nil { - gpem.log.Errorf("NewClient error: %v", err) + gpem.log.Errorf("NewClient error: %s", err) return false, err } // compare接口获取两个commit之间的最终的改动 diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) if err != nil { - gpem.log.Errorf("Failed to get push event diffs, error: %v", err) + gpem.log.Errorf("Failed to get push event diffs, error: %s", err) return false, err } for _, diff := range diffs { -- Gitee From 9521fd32fad6a87378c0c398a16b0aa4886a4ac5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 13:57:21 +0800 Subject: [PATCH 0471/2647] poetry Signed-off-by: mouuii --- .../systemconfig/core/handler/jira.go | 43 +++++++++ .../systemconfig/core/handler/router.go | 8 ++ .../core/repository/models/jira.go | 21 +++++ .../core/repository/mongo/jira.go | 89 +++++++++++++++++++ .../systemconfig/core/service/jira.go | 24 +++++ 5 files changed, 185 insertions(+) create mode 100644 pkg/microservice/systemconfig/core/handler/jira.go create mode 100644 pkg/microservice/systemconfig/core/repository/models/jira.go create mode 100644 pkg/microservice/systemconfig/core/repository/mongo/jira.go create mode 100644 pkg/microservice/systemconfig/core/service/jira.go diff --git a/pkg/microservice/systemconfig/core/handler/jira.go b/pkg/microservice/systemconfig/core/handler/jira.go new file mode 100644 index 00000000..7354e4f4 --- /dev/null +++ b/pkg/microservice/systemconfig/core/handler/jira.go @@ -0,0 +1,43 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func DeleteJira(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = service.DeleteEmailService(ctx.Logger) +} + +func GetJira(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GeJira(ctx.Logger) +} + +func CreateJira(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + req := new(models.Jira) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.CreateJira(req, ctx.Logger) +} + +func UpdateJira(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + req := new(models.Jira) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.UpdateJira(req, ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index f61b00ec..359c4e94 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -47,4 +47,12 @@ func (*Router) Inject(router *gin.RouterGroup) { emails.PATCH("/service", UpdateEmailService) emails.DELETE("/service", DeleteEmailService) } + + jira := router.Group("jira") + { + jira.GET("", GetJira) + jira.POST("", CreateJira) + jira.PATCH("", UpdateJira) + jira.DELETE("", DeleteJira) + } } diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go new file mode 100644 index 00000000..fd22eb18 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -0,0 +1,21 @@ +package models + +import ( + "github.com/globalsign/mgo/bson" +) + +type Jira struct { + ObjectID bson.ObjectId `bson:"_id,omitempty"` + ID int64 `bson:"id"` + Host string `bson:"host"` + User string `bson:"user"` + AccessToken string `bson:"access_token"` + OrganizationID int `bson:"organization_id"` + CreatedAt int64 `bson:"created_at"` + UpdatedAt int64 `bson:"updated_at"` + DeletedAt int64 `bson:"deleted_at"` +} + +func (Jira) TableName() string { + return "jira" +} diff --git a/pkg/microservice/systemconfig/core/repository/mongo/jira.go b/pkg/microservice/systemconfig/core/repository/mongo/jira.go new file mode 100644 index 00000000..cf25ccfa --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/mongo/jira.go @@ -0,0 +1,89 @@ +package mongo + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/mongo" + + "github.com/globalsign/mgo/bson" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type JiraColl struct { + *mongo.Collection + + coll string +} + +func NewJiraColl() *JiraColl { + name := models.Jira{}.TableName() + coll := &JiraColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *JiraColl) GetCollectionName() string { + return c.coll +} +func (c *JiraColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *JiraColl) AddJira(iJira *models.Jira) (*models.Jira, error) { + _, err := c.Collection.InsertOne(context.TODO(), iJira) + if err != nil { + log.Error("repository AddJira err : %v", err) + return nil, err + } + return iJira, nil +} + +func (c *JiraColl) UpdateJira(iJira *models.Jira) (*models.Jira, error) { + + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "host": iJira.Host, + "user": iJira.User, + "access_token": iJira.AccessToken, + "updated_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository UpdateJira err : %v", err) + return nil, err + } + return iJira, nil +} + +func (c *JiraColl) DeleteJira() error { + + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository DeleteJira err : %v", err) + return err + } + return nil +} + +func (c *JiraColl) GetJira() (*models.Jira, error) { + jira := &models.Jira{} + query := bson.M{"deleted_at": 0} + + err := c.Collection.FindOne(context.TODO(), query).Decode(jira) + if err != nil { + log.Error("repository GetJira err : %v", err) + return nil, err + } + return jira, nil +} diff --git a/pkg/microservice/systemconfig/core/service/jira.go b/pkg/microservice/systemconfig/core/service/jira.go new file mode 100644 index 00000000..b84a9aa8 --- /dev/null +++ b/pkg/microservice/systemconfig/core/service/jira.go @@ -0,0 +1,24 @@ +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" +) + +func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { + return mongo.NewJiraColl().GetJira() +} + +func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { + return mongo.NewJiraColl().AddJira(jira) +} + +func UpdateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { + return mongo.NewJiraColl().UpdateJira(jira) +} + +func DeleteJira(_ *zap.SugaredLogger) error { + return mongo.NewEmailServiceColl().DeleteEmailService() +} -- Gitee From 4930da46965377837faebeb9700d72bda73d610d Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 4 Nov 2021 14:22:53 +0800 Subject: [PATCH 0472/2647] update create logic Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 8d25cd2d..a50ed29f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2655,7 +2655,6 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - serviceList := make([]*commonmodels.Service, 0) handler := func(serviceObj *commonmodels.Service, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, helmClient) @@ -2666,6 +2665,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel } for _, serviceGroups := range args.Services { + serviceList := make([]*commonmodels.Service, 0) for _, svc := range serviceGroups { _, ok := chartInfoMap[svc.ServiceName] if !ok { @@ -2688,13 +2688,11 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + if serviceGroupErr != nil { + errList = multierror.Append(errList, serviceGroupErr...) + } } - - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) - if serviceGroupErr != nil { - errList = multierror.Append(errList, serviceGroupErr...) - } - err = errList.ErrorOrNil() } -- Gitee From 96caa49ef0b1c85422978c953b0e59a280ef9bfc Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 4 Nov 2021 14:36:50 +0800 Subject: [PATCH 0473/2647] optimize and add sort list user Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/config/config.go | 10 ++-- .../user/core/repository/orm/user.go | 2 +- .../user/core/service/user/retrieve.html | 34 ++++++++++++ .../user/core/service/user/user.go | 52 ++++--------------- pkg/{util => tool}/mail/mail.go | 0 5 files changed, 51 insertions(+), 47 deletions(-) create mode 100644 pkg/microservice/user/core/service/user/retrieve.html rename pkg/{util => tool}/mail/mail.go (100%) diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go index a633c8b0..1981dfdc 100644 --- a/pkg/microservice/user/config/config.go +++ b/pkg/microservice/user/config/config.go @@ -25,6 +25,10 @@ import ( "github.com/koderover/zadig/pkg/setting" ) +func init() { + viper.SetDefault(setting.ENVUserPort, "80") +} + func IssuerURL() string { return viper.GetString(setting.ENVIssuerURL) } @@ -62,9 +66,5 @@ func NoReplyEmailPassword() string { } func UserPort() string { - port := viper.GetString(setting.ENVUserPort) - if len(port) > 0 { - return port - } - return "80" + return viper.GetString(setting.ENVUserPort) } diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index 27f4e55c..994a89d9 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -48,7 +48,7 @@ func ListUsers(page int, perPage int, name string, db *gorm.DB) ([]models.User, err error ) - err = db.Where("name LIKE ?", "%"+name+"%").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error + err = db.Where("name LIKE ?", "%"+name+"%").Order("account ASC").Offset((page - 1) * perPage).Limit(perPage).Find(&users).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err diff --git a/pkg/microservice/user/core/service/user/retrieve.html b/pkg/microservice/user/core/service/user/retrieve.html new file mode 100644 index 00000000..87f024a4 --- /dev/null +++ b/pkg/microservice/user/core/service/user/retrieve.html @@ -0,0 +1,34 @@ +
+
+ + + + + + +
hi,您好
+
+
+ +
+
+

非常感谢使用 Zadig,点击下面的链接完成重置密码。

+
+
+ + + + + + + + + +
{{.Url}}
如果您不想继续完成密码重置,请忽略该邮件,链接将在5分钟之后过期!
+
+
+ +
+

本邮件由 Zadig 系统自动发出,请勿直接回复。

+

Made By Zadig Team ♥ Happy Coding.

+
\ No newline at end of file diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 07d9ed92..8bab6047 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -1,6 +1,7 @@ package user import ( + _ "embed" "fmt" "net/url" "time" @@ -19,7 +20,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core/service/login" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/util/mail" + "github.com/koderover/zadig/pkg/tool/mail" ) type User struct { @@ -113,10 +114,14 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { return err } for _, entry := range sr.Entries { - account := si.Config.UserSearch.NameAttr + account := si.Config.UserSearch.Username + name := account + if len(si.Config.UserSearch.NameAttr) != 0 { + name = si.Config.UserSearch.NameAttr + } _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), - Name: entry.GetAttributeValue(account), + Name: name, IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { @@ -276,43 +281,8 @@ func DeleteUserByUID(uid string, logger *zap.SugaredLogger) error { return tx.Commit().Error } -func getRetrieveHtmlTemplate() string { - return ` -
-
- - - - - - -
hi,您好
-
-
- -
-
-

非常感谢使用 Zadig,点击下面的链接完成重置密码。

-
-
- - - - - - - - - -
{{.Url}}
如果您不想继续完成密码重置,请忽略该邮件,链接将在5分钟之后过期!
-
-
- -
-

本邮件由 Zadig 系统自动发出,请勿直接回复。

-

Made By Zadig Team ♥ Happy Coding.

-
` -} +//go:embed retrieve.html +var retrieveHemlTemplate []byte func Retrieve(account string, logger *zap.SugaredLogger) (*RetrieveResp, error) { user, err := orm.GetUser(account, config.SystemIdentityType, core.DB) @@ -344,7 +314,7 @@ func Retrieve(account string, logger *zap.SugaredLogger) (*RetrieveResp, error) v := url.Values{} v.Add("idtoken", token) retrieveURL := configbase.SystemAddress() + "/signin?" + v.Encode() - body, err := mail.RenderEmailTemplate(retrieveURL, getRetrieveHtmlTemplate()) + body, err := mail.RenderEmailTemplate(retrieveURL, string(retrieveHemlTemplate)) if err != nil { logger.Errorf("Retrieve renderEmailTemplate error, error msg:%s ", err) return nil, fmt.Errorf("Retrieve renderEmailTemplate error, error msg:%s ", err) diff --git a/pkg/util/mail/mail.go b/pkg/tool/mail/mail.go similarity index 100% rename from pkg/util/mail/mail.go rename to pkg/tool/mail/mail.go -- Gitee From d224c745a4d6907ed4606f18c0c7e53739ea0991 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 4 Nov 2021 14:56:38 +0800 Subject: [PATCH 0474/2647] update retry logic and update backoff library version Signed-off-by: allenshen --- go.mod | 2 +- go.sum | 3 +-- .../aslan/core/environment/service/environment.go | 11 +++++------ pkg/microservice/hubagent/core/service/client.go | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 12510309..908353cf 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/bradleyfalzon/ghinstallation v1.1.1 github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 + github.com/cenkalti/backoff/v4 v4.1.1 github.com/coocood/freecache v1.1.0 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v20.10.7+incompatible diff --git a/go.sum b/go.sum index 1227c29a..8a052bb3 100644 --- a/go.sum +++ b/go.sum @@ -269,8 +269,7 @@ github.com/bugsnag/panicwrap v1.3.1/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywR github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= +github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index a50ed29f..3b5a11e1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -27,7 +27,7 @@ import ( "sync" "time" - "github.com/cenkalti/backoff/v3" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/go-multierror" helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" @@ -2688,7 +2688,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2757,8 +2757,7 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis } func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { - bo := backoff.NewExponentialBackOff() - bo.InitialInterval = 3 * time.Second + bo := backoff.NewConstantBackOff(time.Second * 3) retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) _ = backoff.Retry(func() error { @@ -2902,7 +2901,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -3141,7 +3140,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + groupServiceErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } diff --git a/pkg/microservice/hubagent/core/service/client.go b/pkg/microservice/hubagent/core/service/client.go index e1b53b71..410f8930 100644 --- a/pkg/microservice/hubagent/core/service/client.go +++ b/pkg/microservice/hubagent/core/service/client.go @@ -25,7 +25,7 @@ import ( "strings" "time" - "github.com/cenkalti/backoff/v3" + "github.com/cenkalti/backoff/v4" "github.com/gorilla/websocket" "github.com/pkg/errors" "go.uber.org/zap" -- Gitee From 907369258dfbfc5cc0e0e18c6ff154acea8b9c96 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 4 Nov 2021 15:03:54 +0800 Subject: [PATCH 0475/2647] remove useless log Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 3b5a11e1..08c33d7b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2786,7 +2786,6 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic for _, data := range serviceList { go func() { defer wg.Done() - log.Infof("handling single serivce %s", data.ServiceName) err := handler(data, log) if err != nil { errList = append(errList, err) -- Gitee From a53a1e4d431b36a02c03ccd717f8e3d3e9e756f1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 4 Nov 2021 15:20:31 +0800 Subject: [PATCH 0476/2647] regular check Signed-off-by: liu deyi --- .../aslan/core/system/handler/operation.go | 7 +++++++ .../aslan/core/system/handler/router.go | 4 ++++ .../aslan/core/system/service/operation.go | 16 ++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/pkg/microservice/aslan/core/system/handler/operation.go b/pkg/microservice/aslan/core/system/handler/operation.go index d4d97b77..92bfb25e 100644 --- a/pkg/microservice/aslan/core/system/handler/operation.go +++ b/pkg/microservice/aslan/core/system/handler/operation.go @@ -101,3 +101,10 @@ func UpdateOperationLog(c *gin.Context) { } ctx.Err = service.UpdateOperation(c.Param("id"), args.Status, ctx.Logger) } + +func CheckRegularValid(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.CheckRegularValid(c.Query("regular")) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index bf5a2040..b4f260b8 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -188,4 +188,8 @@ func (*Router) Inject(router *gin.RouterGroup) { operation.PUT("/:id", gin2.RequireSuperAdminAuth, UpdateOperationLog) } + regular := router.Group("regular") + { + regular.GET("/check", CheckRegularValid) + } } diff --git a/pkg/microservice/aslan/core/system/service/operation.go b/pkg/microservice/aslan/core/system/service/operation.go index 9f668b7e..2f88bebd 100644 --- a/pkg/microservice/aslan/core/system/service/operation.go +++ b/pkg/microservice/aslan/core/system/service/operation.go @@ -17,6 +17,8 @@ limitations under the License. package service import ( + "regexp" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/models" @@ -73,3 +75,17 @@ func UpdateOperation(id string, status int, log *zap.SugaredLogger) error { } return nil } + +type CheckRegularValidResp struct { + Valid bool `json:"valid"` +} + +func CheckRegularValid(regular string) (*CheckRegularValidResp, error) { + valid := false + if _, err := regexp.Compile(regular); err == nil { + valid = true + } + return &CheckRegularValidResp{ + Valid: valid, + }, nil +} -- Gitee From 0d4154ffb8490ad0cf654bbb7c757d988861464d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 4 Nov 2021 15:55:12 +0800 Subject: [PATCH 0477/2647] regular check Signed-off-by: liu deyi --- .../aslan/core/code/handler/codehost.go | 18 ++++++++++++++++++ .../aslan/core/code/handler/router.go | 2 ++ .../aslan/core/code/service/repo.go | 11 +++++++++++ .../aslan/core/system/handler/operation.go | 7 ------- .../aslan/core/system/handler/router.go | 5 ----- .../aslan/core/system/service/operation.go | 16 ---------------- 6 files changed, 31 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 93b257bc..85a7198e 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -192,3 +192,21 @@ func ListRepoInfos(c *gin.Context) { param := c.Query("param") ctx.Resp, ctx.Err = service.ListRepoInfos(args.Infos, param, ctx.Logger) } + +type BranchesRequest struct { + Regular string `json:"regular"` + Branches []string `json:"branches"` +} + +func MatchBranchesList(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(BranchesRequest) + err := c.ShouldBindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid branches args") + return + } + ctx.Resp = service.MatchBranchesList(args.Regular, args.Branches) +} diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 6b0a22f8..74244b59 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -37,6 +37,7 @@ func (*Router) Inject(router *gin.RouterGroup) { codehost.GET("/:codehostId/namespaces/:namespace/projects/:projectName/tags", CodeHostGetTagList) codehost.GET("/:codehostId/namespaces/:namespace/projects/:projectName/prs", CodeHostGetPRList) codehost.PUT("/infos", ListRepoInfos) + codehost.POST("/branches/regular/check", MatchBranchesList) } // --------------------------------------------------------------------------------------- @@ -51,4 +52,5 @@ func (*Router) Inject(router *gin.RouterGroup) { workspace.GET("/tree", GetRepoTree) workspace.GET("/codehub/:codehostId/:repoUUID/:branchName", GetCodehubRepoInfo) } + } diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 5fe2392f..1bc4114a 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "regexp" "strings" "sync" @@ -114,3 +115,13 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( } return infos, nil } + +func MatchBranchesList(regular string, branches []string) []string { + matchBranches := make([]string, 0) + for _, branch := range branches { + if matched, _ := regexp.MatchString(regular, branch); matched { + matchBranches = append(matchBranches, branch) + } + } + return matchBranches +} diff --git a/pkg/microservice/aslan/core/system/handler/operation.go b/pkg/microservice/aslan/core/system/handler/operation.go index 92bfb25e..d4d97b77 100644 --- a/pkg/microservice/aslan/core/system/handler/operation.go +++ b/pkg/microservice/aslan/core/system/handler/operation.go @@ -101,10 +101,3 @@ func UpdateOperationLog(c *gin.Context) { } ctx.Err = service.UpdateOperation(c.Param("id"), args.Status, ctx.Logger) } - -func CheckRegularValid(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.CheckRegularValid(c.Query("regular")) -} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index b4f260b8..b0e5df7e 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -187,9 +187,4 @@ func (*Router) Inject(router *gin.RouterGroup) { operation.POST("", gin2.RequireSuperAdminAuth, AddSystemOperationLog) operation.PUT("/:id", gin2.RequireSuperAdminAuth, UpdateOperationLog) } - - regular := router.Group("regular") - { - regular.GET("/check", CheckRegularValid) - } } diff --git a/pkg/microservice/aslan/core/system/service/operation.go b/pkg/microservice/aslan/core/system/service/operation.go index 2f88bebd..9f668b7e 100644 --- a/pkg/microservice/aslan/core/system/service/operation.go +++ b/pkg/microservice/aslan/core/system/service/operation.go @@ -17,8 +17,6 @@ limitations under the License. package service import ( - "regexp" - "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/models" @@ -75,17 +73,3 @@ func UpdateOperation(id string, status int, log *zap.SugaredLogger) error { } return nil } - -type CheckRegularValidResp struct { - Valid bool `json:"valid"` -} - -func CheckRegularValid(regular string) (*CheckRegularValidResp, error) { - valid := false - if _, err := regexp.Compile(regular); err == nil { - valid = true - } - return &CheckRegularValidResp{ - Valid: valid, - }, nil -} -- Gitee From 890f250ffcc73c359b2cd28074e1376b0e0577f3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 4 Nov 2021 15:55:57 +0800 Subject: [PATCH 0478/2647] add blank Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/handler/router.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index b0e5df7e..bf5a2040 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -187,4 +187,5 @@ func (*Router) Inject(router *gin.RouterGroup) { operation.POST("", gin2.RequireSuperAdminAuth, AddSystemOperationLog) operation.PUT("/:id", gin2.RequireSuperAdminAuth, UpdateOperationLog) } + } -- Gitee From 7017ad86c62ec912f1532c19afa702dce1f17356 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 16:20:21 +0800 Subject: [PATCH 0479/2647] json Signed-off-by: mouuii --- .../core/repository/models/email.go | 35 +++++++++---------- .../core/repository/models/jira.go | 16 ++++----- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go index 8209f4e3..15070556 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -5,15 +5,14 @@ import ( ) type EmailHost struct { - ID int `json:"id"` - Name string `json:"name"` - Port int `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - IsTLS bool `json:"isTLS"` - OrganizationID int `json:"orgId"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + ID int `json:"id" bson:"id"` + Name string `json:"name" bson:"name"` + Port int `json:"port" bson:"port"` + Username string `json:"username" bson:"username"` + Password string `json:"password" bson:"password"` + IsTLS bool `json:"isTLS" bson:"is_tls"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` } func (EmailHost) TableName() string { @@ -22,15 +21,15 @@ func (EmailHost) TableName() string { type EmailService struct { ObjectID bson.ObjectId `bson:"_id,omitempty"` - ID int `bson:"id"` - Name string `bson:"name"` - Address string `bson:"address"` - DisplayName string `bson:"display_name"` - Theme string `bson:"theme"` - OrganizationID int `bson:"organization_id"` - CreatedAt int64 `bson:"created_at"` - UpdatedAt int64 `bson:"updated_at"` - DeletedAt int64 `bson:"deleted_at"` + ID int `json:"id" bson:"id"` + Name string `json:"name" bson:"name"` + Address string `json:"address" bson:"address"` + DisplayName string `json:"display_name" bson:"display_name"` + Theme string `json:"theme" bson:"theme"` + OrganizationID int `json:"organization_id" bson:"organization_id"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` } func (EmailService) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go index fd22eb18..2d29b668 100644 --- a/pkg/microservice/systemconfig/core/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -6,14 +6,14 @@ import ( type Jira struct { ObjectID bson.ObjectId `bson:"_id,omitempty"` - ID int64 `bson:"id"` - Host string `bson:"host"` - User string `bson:"user"` - AccessToken string `bson:"access_token"` - OrganizationID int `bson:"organization_id"` - CreatedAt int64 `bson:"created_at"` - UpdatedAt int64 `bson:"updated_at"` - DeletedAt int64 `bson:"deleted_at"` + ID int64 `json:"id" bson:"id"` + Host string `json:"host" bson:"host"` + User string `json:"user" bson:"user"` + AccessToken string `json:"access_token" bson:"access_token"` + OrganizationID int `json:"organization_id" bson:"organization_id"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` } func (Jira) TableName() string { -- Gitee From f513b9a47c46e0222b88f255609596bd81cf01fd Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 4 Nov 2021 16:37:23 +0800 Subject: [PATCH 0480/2647] remove user config Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/config/config.go | 4 ---- pkg/microservice/user/server/server.go | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go index 1981dfdc..dcc74c8d 100644 --- a/pkg/microservice/user/config/config.go +++ b/pkg/microservice/user/config/config.go @@ -64,7 +64,3 @@ func NoReplyEmailAddress() string { func NoReplyEmailPassword() string { return viper.GetString(setting.ENVNoReplyEmailPassword) } - -func UserPort() string { - return viper.GetString(setting.ENVUserPort) -} diff --git a/pkg/microservice/user/server/server.go b/pkg/microservice/user/server/server.go index 648b1196..aa0f55e5 100644 --- a/pkg/microservice/user/server/server.go +++ b/pkg/microservice/user/server/server.go @@ -21,7 +21,6 @@ import ( "net/http" "time" - "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/server/rest" "github.com/koderover/zadig/pkg/tool/log" @@ -34,7 +33,8 @@ func Serve(ctx context.Context) error { log.Info("Start user system service") engine := rest.NewEngine() - server := &http.Server{Addr: ":" + config.UserPort(), Handler: engine} + server := &http.Server{Addr: ":80", Handler: engine} + stopChan := make(chan struct{}) go func() { defer close(stopChan) -- Gitee From 9ec4f22220bd54828604ef57620cce4e02aad587 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 4 Nov 2021 16:45:13 +0800 Subject: [PATCH 0481/2647] Ldap sync user (#522) * add user-sync Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> * add sync deleted user Signed-off-by: panxunying <641770806@qq.com> * fix config convert error Signed-off-by: panxunying <641770806@qq.com> * remove sync delete user Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/repository/orm/user.go | 26 +++++++++++++ .../user/core/repository/orm/user_login.go | 10 +++++ .../user/core/service/user/user.go | 37 ++++++++----------- .../client/systemconfig/systemconfig.go | 12 +++--- 4 files changed, 59 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index 84e658bc..ba025eac 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -73,6 +73,32 @@ func ListUsersByUIDs(uids []string, db *gorm.DB) ([]models.User, error) { return users, nil } +// ListUsersByIdentityType gets a list of users based on identityType +func ListUsersByIdentityType(identityType string, db *gorm.DB) ([]models.User, error) { + var ( + users []models.User + err error + ) + + err = db.Find(&users, "identity_type = ?", identityType).Error + + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + + return users, nil +} + +// DeleteUserByUids Delete users based on uids +func DeleteUserByUids(uids []string, db *gorm.DB) error { + var user models.User + err := db.Where("uid in ?", uids).Delete(&user).Error + if err != nil { + return err + } + return nil +} + // GetUsersCount gets user count func GetUsersCount(name string) (int64, error) { var ( diff --git a/pkg/microservice/user/core/repository/orm/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go index 153a538a..a46c517d 100644 --- a/pkg/microservice/user/core/repository/orm/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -28,6 +28,16 @@ func GetUserLogin(uid string, account string, loginType config.LoginType, db *go return &userLogin, nil } +// DeleteUserLoginByUids Delete userLogin based on uids +func DeleteUserLoginByUids(uids []string, db *gorm.DB) error { + var userLogin models.UserLogin + err := db.Where("uid in ?", uids).Delete(&userLogin).Error + if err != nil { + return err + } + return nil +} + // ListUserLogins Get a userLogin based on uid list func ListUserLogins(uids []string, db *gorm.DB) (*[]models.UserLogin, error) { var userLogins []models.UserLogin diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 3ab8627e..648fa102 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -4,7 +4,6 @@ import ( "fmt" "time" - "github.com/dexidp/dex/connector/ldap" ldapv3 "github.com/go-ldap/ldap/v3" "github.com/google/uuid" "go.uber.org/zap" @@ -63,53 +62,49 @@ type SyncUserInfo struct { func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { systemConfigClient := systemconfig.New() - si, err := systemConfigClient.GetConnector(ldapId) + si, err := systemConfigClient.GetLDAPConnector(ldapId) if err != nil { - logger.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) - return fmt.Errorf("SearchAndSyncUser GetConnector error, error msg:%s", err) + logger.Errorf("SearchAndSyncUser GetLDAPConnector error, error msg:%s", err) + return fmt.Errorf("SearchAndSyncUser GetLDAPConnector error, error msg:%s", err) } if si == nil || si.Config == nil { logger.Error("can't find connector") return fmt.Errorf("can't find connector") } - config, ok := si.Config.(*ldap.Config) - if !ok { - return fmt.Errorf("connector config error") - } - l, err := ldapv3.Dial("tcp", config.Host) + l, err := ldapv3.Dial("tcp", si.Config.Host) if err != nil { - logger.Errorf("ldap dial host:%s error, error msg:%s", config.Host, err) + logger.Errorf("ldap dial host:%s error, error msg:%s", si.Config.Host, err) return err } defer l.Close() - err = l.Bind(config.BindDN, config.BindPW) + err = l.Bind(si.Config.BindDN, si.Config.BindPW) if err != nil { - logger.Errorf("ldap bind host:%s error, error msg:%s", config.Host, err) + logger.Errorf("ldap bind host:%s error, error msg:%s", si.Config.Host, err) return err } searchRequest := ldapv3.NewSearchRequest( - config.GroupSearch.BaseDN, + si.Config.GroupSearch.BaseDN, ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, - config.GroupSearch.Filter, // The filter to apply - []string{config.UserSearch.NameAttr}, // A list attributes to retrieve + si.Config.GroupSearch.Filter, // The filter to apply + []string{si.Config.UserSearch.NameAttr}, // A list attributes to retrieve nil, ) sr, err := l.Search(searchRequest) if err != nil { - logger.Errorf("ldap search host:%s error, error msg:%s", config.Host, err) + logger.Errorf("ldap search host:%s error, error msg:%s", si.Config.Host, err) return err } - for _, entry := range sr.Entries { + account := si.Config.UserSearch.NameAttr _, err := SyncUser(&SyncUserInfo{ - Account: entry.GetAttributeValue(config.UserSearch.NameAttr), - IdentityType: si.ID, + Account: entry.GetAttributeValue(account), + IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { - logger.Errorf("ldap host:%s sync user error, error msg:%s", config.Host, err) + logger.Errorf("ldap host:%s sync user error, error msg:%s", si.Config.Host, err) return err } } @@ -161,7 +156,7 @@ func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { count, err := orm.GetUsersCount(args.Name) if err != nil { - logger.Errorf("SeachUsers GetUsersCount By account:%s error, error msg:%s", args.Account, err.Error()) + logger.Errorf("SeachUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) return nil, err } if count == 0 { diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go index 6d98e516..caf958e3 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -1,17 +1,19 @@ package systemconfig import ( + "github.com/dexidp/dex/connector/ldap" + "github.com/koderover/zadig/pkg/tool/httpclient" ) type Connector struct { - Type string `json:"type"` - ID string `json:"id"` - Name string `json:"name"` - Config interface{} `json:"config"` + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + Config *ldap.Config `json:"config"` } -func (c *Client) GetConnector(id string) (*Connector, error) { +func (c *Client) GetLDAPConnector(id string) (*Connector, error) { url := "/connectors/" + id res := &Connector{} -- Gitee From e88aad0d484a07b9fda3c0435d0627d186194691 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 16:55:36 +0800 Subject: [PATCH 0482/2647] json Signed-off-by: mouuii --- .../systemconfig/core/handler/codehost.go | 20 +++++ .../systemconfig/core/handler/router.go | 4 + .../core/repository/models/codehost.go | 24 +++++ .../core/repository/models/email.go | 2 +- .../core/repository/mongo/codehost.go | 87 +++++++++++++++++++ .../core/repository/mongo/email.go | 2 +- .../systemconfig/core/service/codehost.go | 12 +++ 7 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 pkg/microservice/systemconfig/core/handler/codehost.go create mode 100644 pkg/microservice/systemconfig/core/repository/models/codehost.go create mode 100644 pkg/microservice/systemconfig/core/repository/mongo/codehost.go create mode 100644 pkg/microservice/systemconfig/core/service/codehost.go diff --git a/pkg/microservice/systemconfig/core/handler/codehost.go b/pkg/microservice/systemconfig/core/handler/codehost.go new file mode 100644 index 00000000..564f97e0 --- /dev/null +++ b/pkg/microservice/systemconfig/core/handler/codehost.go @@ -0,0 +1,20 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateCodehost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + rep := new(models.CodeHost) + if err := c.ShouldBindJSON(rep); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.CreateCodehost(rep, ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index 359c4e94..33f3273c 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -55,4 +55,8 @@ func (*Router) Inject(router *gin.RouterGroup) { jira.PATCH("", UpdateJira) jira.DELETE("", DeleteJira) } + codehost := router.Group("codehost") + { + codehost.POST("", CreateCodehost) + } } diff --git a/pkg/microservice/systemconfig/core/repository/models/codehost.go b/pkg/microservice/systemconfig/core/repository/models/codehost.go new file mode 100644 index 00000000..fd4e10c5 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/models/codehost.go @@ -0,0 +1,24 @@ +package models + +type CodeHost struct { + ID int `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Address string `json:"address"` + IsReady bool `json:"ready"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + Namespace string `json:"namespace"` + OrganizationID int `json:"orgId"` + ApplicationId string `json:"applicationId"` + Region string `json:"region,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + ClientSecret string `json:"clientSecret"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +func (CodeHost) TableName() string { + return "code_host" +} diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go index 15070556..7682584a 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -10,7 +10,7 @@ type EmailHost struct { Port int `json:"port" bson:"port"` Username string `json:"username" bson:"username"` Password string `json:"password" bson:"password"` - IsTLS bool `json:"isTLS" bson:"is_tls"` + IsTLS bool `json:"isTLS" bson:"is_tls"` CreatedAt int64 `json:"created_at" bson:"created_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"` } diff --git a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go new file mode 100644 index 00000000..d6d71962 --- /dev/null +++ b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go @@ -0,0 +1,87 @@ +package mongo + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/mongo" + + "github.com/globalsign/mgo/bson" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type CodehostColl struct { + *mongo.Collection + + coll string +} + +func NewCodehostColl() *CodehostColl { + name := models.CodeHost{}.TableName() + coll := &CodehostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *CodehostColl) GetCollectionName() string { + return c.coll +} +func (c *CodehostColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *CodehostColl) AddCodeHost(iCodeHost *models.CodeHost) (*models.CodeHost, error) { + + _, err := c.Collection.InsertOne(context.TODO(), iCodeHost) + if err != nil { + log.Error("repository AddCodeHost err : %v", err) + return nil, err + } + return iCodeHost, nil +} + +func (c *CodehostColl) DeleteCodeHostByID(ID int) error { + query := bson.M{"id": ID, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository DeleteCodeHostByID err : %v", err) + return err + } + return nil +} + +func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { + + codehost := new(models.CodeHost) + query := bson.M{"id": ID, "deleted_at": 0} + err := c.Collection.FindOne(context.TODO(), query).Decode(codehost) + if err != nil { + log.Error("repository GetCodeHostByID err : %v", err) + return nil, err + } + + return codehost, nil +} + +func (c *CodehostColl) FindCodeHosts() ([]*models.CodeHost, error) { + codeHosts := make([]*models.CodeHost, 0) + query := bson.M{"deleted_at": 0} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &codeHosts) + if err != nil { + return nil, err + } + return codeHosts, nil +} diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index ca02e977..5b3b4703 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -68,7 +68,7 @@ func (c *EmailHostColl) Find() (*models.EmailHost, error) { } func (c *EmailHostColl) Update(emailHost *models.EmailHost) (*models.EmailHost, error) { - query := bson.M{"organization_id": emailHost.OrganizationID, "deleted_at": 0} + query := bson.M{"deleted_at": 0} change := bson.M{"$set": bson.M{ "name": emailHost.Name, "port": emailHost.Port, diff --git a/pkg/microservice/systemconfig/core/service/codehost.go b/pkg/microservice/systemconfig/core/service/codehost.go new file mode 100644 index 00000000..e0b800fe --- /dev/null +++ b/pkg/microservice/systemconfig/core/service/codehost.go @@ -0,0 +1,12 @@ +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" +) + +func CreateCodehost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { + return mongo.NewCodehostColl().AddCodeHost(codehost) +} -- Gitee From 98044faad0d4f6aa6175ff12bbee399221538c03 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 4 Nov 2021 16:57:50 +0800 Subject: [PATCH 0483/2647] added yaml template variable API & delete save variable api Signed-off-by: Min Min --- .../core/templatestore/handler/router.go | 2 +- .../aslan/core/templatestore/handler/yaml.go | 10 +-- .../aslan/core/templatestore/service/yaml.go | 75 ++++--------------- 3 files changed, 21 insertions(+), 66 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 0d3c84f3..7490a239 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -62,6 +62,6 @@ func (*Router) Inject(router *gin.RouterGroup) { yaml.GET("/:id", GetYamlTemplateDetail) yaml.DELETE("/:id", DeleteYamlTemplate) yaml.GET("/:id/reference", GetYamlTemplateReference) - yaml.PUT("/:id/variables", UpdateYamlTemplateVariables) + yaml.GET("/variables", GetTemplateVariables) } } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index ab5c9909..0acbff32 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -89,19 +89,19 @@ func GetYamlTemplateReference(c *gin.Context) { ctx.Resp, ctx.Err = templateservice.GetYamlTemplateReference(c.Param("id"), ctx.Logger) } -type updateYamlTemplateVariablesReq struct { - Variables []*templateservice.Variable `json:"variables"` +type getYamlTemplateVariablesReq struct { + Content string `json:"content"` } -func UpdateYamlTemplateVariables(c *gin.Context) { +func GetYamlVariables(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &updateYamlTemplateVariablesReq{} + req := &getYamlTemplateVariablesReq{} if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Err = templateservice.UpdateYamlTemplateVariables(c.Param("id"), req.Variables, ctx.Logger) + ctx.Resp, ctx.Err = templateservice.GetYamlVariables(req.Content, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index fc382dff..2fce6f55 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -2,7 +2,6 @@ package service import ( "errors" - "fmt" "regexp" "strings" @@ -20,21 +19,17 @@ var DefaultSystemVariable = map[string]string{ } func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { - variableList, err := getYamlVariables(template.Content, logger) - if err != nil { - return err - } - variables := make([]*models.Variable, 0) - for _, v := range variableList { - variables = append(variables, &models.Variable{ - Key: v.Key, - Value: v.Value, + vars := make([]*models.Variable, 0) + for _, variable := range template.Variable { + vars = append(vars, &models.Variable{ + Key: variable.Key, + Value: variable.Value, }) } - err = mongodb.NewYamlTemplateColl().Create(&models.YamlTemplate{ + err := mongodb.NewYamlTemplateColl().Create(&models.YamlTemplate{ Name: template.Name, Content: template.Content, - Variables: variables, + Variables: vars, }) if err != nil { logger.Errorf("create dockerfile template error: %s", err) @@ -43,23 +38,19 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error } func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { - variableList, err := getYamlVariables(template.Content, logger) - if err != nil { - return err - } - variables := make([]*models.Variable, 0) - for _, v := range variableList { - variables = append(variables, &models.Variable{ - Key: v.Key, - Value: v.Value, + vars := make([]*models.Variable, 0) + for _, variable := range template.Variable { + vars = append(vars, &models.Variable{ + Key: variable.Key, + Value: variable.Value, }) } - err = mongodb.NewYamlTemplateColl().Update( + err := mongodb.NewYamlTemplateColl().Update( id, &models.YamlTemplate{ Name: template.Name, Content: template.Content, - Variables: variables, + Variables: vars, }, ) if err != nil { @@ -137,43 +128,7 @@ func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceR return ret, nil } -func UpdateYamlTemplateVariables(id string, variables []*Variable, logger *zap.SugaredLogger) error { - templateDetail, err := mongodb.NewYamlTemplateColl().GetById(id) - if err != nil { - logger.Errorf("Failed to find yaml template of id: %s, the error is: %s", id, err) - return err - } - keyMap := make(map[string]int) - // the keys in database will not collide - for _, v := range templateDetail.Variables { - keyMap[v.Key] = 1 - } - newVars := make([]*models.Variable, 0) - for _, vars := range variables { - if keyMap[vars.Key] == 0 { - errorMsg := fmt.Sprintf("Given key [%s] does not exist in yaml template [%s]", vars.Key, id) - return errors.New(errorMsg) - } - newVars = append(newVars, &models.Variable{ - Key: vars.Key, - Value: vars.Value, - }) - } - err = mongodb.NewYamlTemplateColl().Update( - id, - &models.YamlTemplate{ - Name: templateDetail.Name, - Content: templateDetail.Content, - Variables: newVars, - }, - ) - if err != nil { - logger.Errorf("update dockerfile template error: %s", err) - } - return err -} - -func getYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { +func GetYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { resp := make([]*Variable, 0) regex, err := regexp.Compile(setting.RegExpParameter) if err != nil { -- Gitee From c6803225a71bd698c4f5db847ef70ac66cbefe73 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 4 Nov 2021 17:00:46 +0800 Subject: [PATCH 0484/2647] minor api naming changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/templatestore/handler/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 7490a239..f28543bd 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -62,6 +62,6 @@ func (*Router) Inject(router *gin.RouterGroup) { yaml.GET("/:id", GetYamlTemplateDetail) yaml.DELETE("/:id", DeleteYamlTemplate) yaml.GET("/:id/reference", GetYamlTemplateReference) - yaml.GET("/variables", GetTemplateVariables) + yaml.POST("/getVariables", GetTemplateVariables) } } -- Gitee From a098b7786768a0683dfe84c6f5a6998db5193070 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 17:04:01 +0800 Subject: [PATCH 0485/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/repository/models/jira.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go index 2d29b668..9f7a2c08 100644 --- a/pkg/microservice/systemconfig/core/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -5,7 +5,7 @@ import ( ) type Jira struct { - ObjectID bson.ObjectId `bson:"_id,omitempty"` + ObjectID bson.ObjectId `json:"-" bson:"_id,omitempty"` ID int64 `json:"id" bson:"id"` Host string `json:"host" bson:"host"` User string `json:"user" bson:"user"` -- Gitee From 5fb880b48e8dc053f59925930486e79e7142caf0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 17:06:33 +0800 Subject: [PATCH 0486/2647] json Signed-off-by: mouuii --- .../systemconfig/core/repository/mongo/codehost.go | 4 +--- pkg/microservice/systemconfig/core/repository/mongo/email.go | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go index d6d71962..b6e7721d 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go @@ -64,10 +64,8 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { query := bson.M{"id": ID, "deleted_at": 0} err := c.Collection.FindOne(context.TODO(), query).Decode(codehost) if err != nil { - log.Error("repository GetCodeHostByID err : %v", err) - return nil, err + return nil, nil } - return codehost, nil } diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index 5b3b4703..0eeb7aea 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -61,8 +61,7 @@ func (c *EmailHostColl) Find() (*models.EmailHost, error) { err := c.Collection.FindOne(ctx, query).Decode(emailHost) if err != nil { - log.Errorf("repository GetEmailHost err : %v", err) - return nil, err + return nil, nil } return emailHost, nil } -- Gitee From 218bf26d7b04e0d9c50883cb66414b391da184d7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 4 Nov 2021 17:10:37 +0800 Subject: [PATCH 0487/2647] bugfix Signed-off-by: Min Min --- pkg/microservice/aslan/core/templatestore/handler/router.go | 2 +- pkg/microservice/aslan/core/templatestore/handler/yaml.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index f28543bd..15efec5d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -62,6 +62,6 @@ func (*Router) Inject(router *gin.RouterGroup) { yaml.GET("/:id", GetYamlTemplateDetail) yaml.DELETE("/:id", DeleteYamlTemplate) yaml.GET("/:id/reference", GetYamlTemplateReference) - yaml.POST("/getVariables", GetTemplateVariables) + yaml.POST("/getVariables", GetYamlTemplateVariables) } } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 0acbff32..0d71936d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -93,7 +93,7 @@ type getYamlTemplateVariablesReq struct { Content string `json:"content"` } -func GetYamlVariables(c *gin.Context) { +func GetYamlTemplateVariables(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() -- Gitee From 0004e42287edf0de4beca027ab2f7aeb696bff99 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 17:13:37 +0800 Subject: [PATCH 0488/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/repository/mongo/email.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index 0eeb7aea..a2de89a7 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -124,8 +124,7 @@ func (c *EmailServiceColl) GetEmailService() (*models.EmailService, error) { iEmailService := &models.EmailService{} err := c.Collection.FindOne(context.TODO(), query).Decode(iEmailService) if err != nil { - log.Error("repository GetEmailService err : %v", err) - return nil, err + return nil, nil } return iEmailService, nil } -- Gitee From 9e586be77acefbeb6dfe4a6605f45defcaea2995 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 17:44:30 +0800 Subject: [PATCH 0489/2647] json Signed-off-by: mouuii --- .../systemconfig/core/handler/jira.go | 2 +- .../core/repository/models/codehost.go | 32 +++++++++---------- .../core/repository/mongo/codehost.go | 4 +-- .../systemconfig/core/service/jira.go | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/systemconfig/core/handler/jira.go b/pkg/microservice/systemconfig/core/handler/jira.go index 7354e4f4..e249fa83 100644 --- a/pkg/microservice/systemconfig/core/handler/jira.go +++ b/pkg/microservice/systemconfig/core/handler/jira.go @@ -11,7 +11,7 @@ import ( func DeleteJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DeleteEmailService(ctx.Logger) + ctx.Err = service.DeleteJira(ctx.Logger) } func GetJira(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/repository/models/codehost.go b/pkg/microservice/systemconfig/core/repository/models/codehost.go index fd4e10c5..2de3e823 100644 --- a/pkg/microservice/systemconfig/core/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/repository/models/codehost.go @@ -1,22 +1,22 @@ package models type CodeHost struct { - ID int `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Address string `json:"address"` - IsReady bool `json:"ready"` - AccessToken string `json:"accessToken"` - RefreshToken string `json:"refreshToken"` - Namespace string `json:"namespace"` - OrganizationID int `json:"orgId"` - ApplicationId string `json:"applicationId"` - Region string `json:"region,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - ClientSecret string `json:"clientSecret"` - CreatedAt int64 `json:"created_at"` - UpdatedAt int64 `json:"updated_at"` + ID int `bson:"id"json:"id"` + Name string `bson:"name"json:"name"` + Type string `bson:"type"json:"type"` + Address string `bson:"address"json:"address"` + IsReady bool `bson:"is_ready"json:"ready"` + AccessToken string `bson:"access_token"json:"accessToken"` + RefreshToken string `bson:"refresh_token"json:"refreshToken"` + Namespace string `bson:"namespace"json:"namespace"` + OrganizationID int `bson:"organization_id"json:"orgId"` + ApplicationId string `bson:"application_id"json:"applicationId"` + Region string `bson:"region"json:"region,omitempty"` + Username string `bson:"username"json:"username,omitempty"` + Password string `bson:"password"json:"password,omitempty"` + ClientSecret string `bson:"client_secret"json:"clientSecret"` + CreatedAt int64 `bson:"created_at"json:"created_at"` + UpdatedAt int64 `bson:"updated_at"json:"updated_at"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go index b6e7721d..a493b32c 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go @@ -44,8 +44,8 @@ func (c *CodehostColl) AddCodeHost(iCodeHost *models.CodeHost) (*models.CodeHost return iCodeHost, nil } -func (c *CodehostColl) DeleteCodeHostByID(ID int) error { - query := bson.M{"id": ID, "deleted_at": 0} +func (c *CodehostColl) DeleteCodeHost() error { + query := bson.M{"deleted_at": 0} change := bson.M{"$set": bson.M{ "deleted_at": time.Now().Unix(), }} diff --git a/pkg/microservice/systemconfig/core/service/jira.go b/pkg/microservice/systemconfig/core/service/jira.go index b84a9aa8..9d3e2e11 100644 --- a/pkg/microservice/systemconfig/core/service/jira.go +++ b/pkg/microservice/systemconfig/core/service/jira.go @@ -20,5 +20,5 @@ func UpdateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { } func DeleteJira(_ *zap.SugaredLogger) error { - return mongo.NewEmailServiceColl().DeleteEmailService() + return mongo.NewJiraColl().DeleteJira() } -- Gitee From 07da8483c04bbbbdd7dd6ad14abcaab2086e89f4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 17:48:00 +0800 Subject: [PATCH 0490/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/repository/mongo/jira.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/mongo/jira.go b/pkg/microservice/systemconfig/core/repository/mongo/jira.go index cf25ccfa..034d1e37 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/jira.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/jira.go @@ -82,8 +82,7 @@ func (c *JiraColl) GetJira() (*models.Jira, error) { err := c.Collection.FindOne(context.TODO(), query).Decode(jira) if err != nil { - log.Error("repository GetJira err : %v", err) - return nil, err + return nil, nil } return jira, nil } -- Gitee From 1679dd413f5bc43fb9989c5799f953286eb80d84 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 4 Nov 2021 17:51:28 +0800 Subject: [PATCH 0491/2647] change defines of preset chart template variables (#533) * change defines of preset chart template variables Signed-off-by: allenshen * update string find logic Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 4 ++-- .../aslan/core/templatestore/service/chart.go | 17 ++++------------- pkg/setting/consts.go | 2 -- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e6fa646c..543dbdf5 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -704,14 +704,14 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { } func renderVariablesToYaml(valuesYaml string, productName, serviceName string, variables []*Variable) (string, error) { + valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableProduct, productName, -1) + valuesYaml = strings.Replace(valuesYaml, setting.TemplateVariableService, serviceName, -1) // build replace data valuesMap := make(map[string]interface{}) for _, variable := range variables { valuesMap[variable.Key] = variable.Value } - valuesMap[setting.PresetTemplateVariableProduct] = productName - valuesMap[setting.PresetTemplateVariableService] = serviceName tmpl, err := template.New("values").Parse(valuesYaml) if err != nil { diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 51c54d88..f3ca87bf 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -40,8 +40,8 @@ import ( var ( variableExtractRegexp = regexp.MustCompile("{{.(\\w*)}}") ChartTemplateDefaultSystemVariable = map[string]string{ - setting.PresetTemplateVariableProduct: setting.TemplateVariableProductDescription, - setting.PresetTemplateVariableService: setting.TemplateVariableServiceDescription, + setting.TemplateVariableProduct: setting.TemplateVariableProductDescription, + setting.TemplateVariableService: setting.TemplateVariableServiceDescription, } ) @@ -119,11 +119,6 @@ func getChartTemplateDefaultVariables() []*Variable { return resp } -func isPresetVariable(key string) bool { - _, ok := ChartTemplateDefaultSystemVariable[key] - return ok -} - func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, error) { cs, err := mongodb.NewChartColl().List() if err != nil { @@ -187,14 +182,10 @@ func parseTemplateVariables(name, path string, logger *zap.SugaredLogger) ([]str strSet := sets.NewString() allMatches := variableExtractRegexp.FindAllStringSubmatch(string(valueYamlContent), -1) for _, match := range allMatches { - if len(match) < 2 { + if len(match) != 2 { continue } - for _, key := range match[1:] { - if !isPresetVariable(key) { - strSet.Insert(key) - } - } + strSet.Insert(match[1]) } return strSet.List(), nil } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 77edc6c0..6b987381 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -503,10 +503,8 @@ const ( // template common constant const ( TemplateVariableProduct = "$T-Project$" - PresetTemplateVariableProduct = "PROJECTNAME" TemplateVariableProductDescription = "项目名称" TemplateVariableService = "$T-Service$" - PresetTemplateVariableService = "SERVICENAME" TemplateVariableServiceDescription = "服务名称" ) -- Gitee From a26e9add48944bc8d26537212be432661c1206b9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 17:51:31 +0800 Subject: [PATCH 0492/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/handler/codehost.go | 6 ++++++ pkg/microservice/systemconfig/core/service/codehost.go | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/pkg/microservice/systemconfig/core/handler/codehost.go b/pkg/microservice/systemconfig/core/handler/codehost.go index 564f97e0..0bd2bf5b 100644 --- a/pkg/microservice/systemconfig/core/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/handler/codehost.go @@ -18,3 +18,9 @@ func CreateCodehost(c *gin.Context) { } ctx.Resp, ctx.Err = service.CreateCodehost(rep, ctx.Logger) } + +func FindCodehost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.FindCodehost(ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/service/codehost.go b/pkg/microservice/systemconfig/core/service/codehost.go index e0b800fe..b5f2febe 100644 --- a/pkg/microservice/systemconfig/core/service/codehost.go +++ b/pkg/microservice/systemconfig/core/service/codehost.go @@ -10,3 +10,7 @@ import ( func CreateCodehost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongo.NewCodehostColl().AddCodeHost(codehost) } + +func FindCodehost(_ *zap.SugaredLogger) ([]*models.CodeHost, error) { + return mongo.NewCodehostColl().FindCodeHosts() +} -- Gitee From bf60c3216849c7cb6d944b3ad80ee6ab0e2c3dc7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 18:32:14 +0800 Subject: [PATCH 0493/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/handler/codehost.go | 2 +- pkg/microservice/systemconfig/core/handler/router.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/handler/codehost.go b/pkg/microservice/systemconfig/core/handler/codehost.go index 0bd2bf5b..e6b2dd1d 100644 --- a/pkg/microservice/systemconfig/core/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/handler/codehost.go @@ -19,7 +19,7 @@ func CreateCodehost(c *gin.Context) { ctx.Resp, ctx.Err = service.CreateCodehost(rep, ctx.Logger) } -func FindCodehost(c *gin.Context) { +func ListCodehost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() ctx.Resp, ctx.Err = service.FindCodehost(ctx.Logger) diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index 33f3273c..c32b1eaa 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -57,6 +57,6 @@ func (*Router) Inject(router *gin.RouterGroup) { } codehost := router.Group("codehost") { - codehost.POST("", CreateCodehost) + codehost.POST("", ListCodehost) } } -- Gitee From 56bad1e7ea01819c336ccae99551e28f95e03e17 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 18:50:35 +0800 Subject: [PATCH 0494/2647] json Signed-off-by: mouuii --- .../core/repository/models/codehost.go | 2 +- .../core/repository/models/email.go | 16 ++++++++-------- .../core/repository/models/jira.go | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/models/codehost.go b/pkg/microservice/systemconfig/core/repository/models/codehost.go index 2de3e823..b0571283 100644 --- a/pkg/microservice/systemconfig/core/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/repository/models/codehost.go @@ -1,7 +1,7 @@ package models type CodeHost struct { - ID int `bson:"id"json:"id"` + ID int `bson:"id"` Name string `bson:"name"json:"name"` Type string `bson:"type"json:"type"` Address string `bson:"address"json:"address"` diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go index 7682584a..78a643fe 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -5,14 +5,14 @@ import ( ) type EmailHost struct { - ID int `json:"id" bson:"id"` - Name string `json:"name" bson:"name"` - Port int `json:"port" bson:"port"` - Username string `json:"username" bson:"username"` - Password string `json:"password" bson:"password"` - IsTLS bool `json:"isTLS" bson:"is_tls"` - CreatedAt int64 `json:"created_at" bson:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + ID int `bson:"id" json:"id"` + Name string `bson:"name" json:"name"` + Port int `bson:"port" json:"port"` + Username string `bson:"username" json:"username"` + Password string `bson:"password" json:"password"` + IsTLS bool `bson:"is_tls" json:"isTLS"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } func (EmailHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go index 9f7a2c08..20a1ee69 100644 --- a/pkg/microservice/systemconfig/core/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -5,15 +5,15 @@ import ( ) type Jira struct { - ObjectID bson.ObjectId `json:"-" bson:"_id,omitempty"` - ID int64 `json:"id" bson:"id"` - Host string `json:"host" bson:"host"` - User string `json:"user" bson:"user"` - AccessToken string `json:"access_token" bson:"access_token"` - OrganizationID int `json:"organization_id" bson:"organization_id"` - CreatedAt int64 `json:"created_at" bson:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at"` - DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` + ObjectID bson.ObjectId `bson:"_id,omitempty"json:"-" ` + ID int64 `bson:"id"json:"id" ` + Host string `bson:"host"json:"host" ` + User string `bson:"user"json:"user" ` + AccessToken string `bson:"access_token" json:"access_token"` + OrganizationID int `bson:"organization_id"json:"organization_id"` + CreatedAt int64 `bson:"created_at"json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at"json:"deleted_at"` } func (Jira) TableName() string { -- Gitee From 4608a8c21867369a8f82af8a98ef27fa5cf5db11 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 18:51:36 +0800 Subject: [PATCH 0495/2647] json Signed-off-by: mouuii --- .../core/repository/models/email.go | 19 +++++++++---------- .../core/repository/models/jira.go | 17 ++++++++--------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go index 78a643fe..93887686 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -20,16 +20,15 @@ func (EmailHost) TableName() string { } type EmailService struct { - ObjectID bson.ObjectId `bson:"_id,omitempty"` - ID int `json:"id" bson:"id"` - Name string `json:"name" bson:"name"` - Address string `json:"address" bson:"address"` - DisplayName string `json:"display_name" bson:"display_name"` - Theme string `json:"theme" bson:"theme"` - OrganizationID int `json:"organization_id" bson:"organization_id"` - CreatedAt int64 `json:"created_at" bson:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at"` - DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` + ObjectID bson.ObjectId `bson:"_id,omitempty"` + ID int `json:"id" bson:"id"` + Name string `json:"name" bson:"name"` + Address string `json:"address" bson:"address"` + DisplayName string `json:"display_name" bson:"display_name"` + Theme string `json:"theme" bson:"theme"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` } func (EmailService) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go index 20a1ee69..5c4b7c35 100644 --- a/pkg/microservice/systemconfig/core/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -5,15 +5,14 @@ import ( ) type Jira struct { - ObjectID bson.ObjectId `bson:"_id,omitempty"json:"-" ` - ID int64 `bson:"id"json:"id" ` - Host string `bson:"host"json:"host" ` - User string `bson:"user"json:"user" ` - AccessToken string `bson:"access_token" json:"access_token"` - OrganizationID int `bson:"organization_id"json:"organization_id"` - CreatedAt int64 `bson:"created_at"json:"created_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` - DeletedAt int64 `bson:"deleted_at"json:"deleted_at"` + ObjectID bson.ObjectId `bson:"_id,omitempty"json:"-" ` + ID int64 `bson:"id"json:"id" ` + Host string `bson:"host"json:"host" ` + User string `bson:"user"json:"user" ` + AccessToken string `bson:"access_token" json:"access_token"` + CreatedAt int64 `bson:"created_at"json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at"json:"deleted_at"` } func (Jira) TableName() string { -- Gitee From 3100ef43d1c758c6c4ea550a7725f6bb7437fefa Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 4 Nov 2021 18:55:14 +0800 Subject: [PATCH 0496/2647] Init db (#535) * add initDBAndTables Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> --- go.mod | 1 + pkg/microservice/user/core/service.go | 27 ++++++++++++++++++ .../user/core/service/init/mysql.sql | 28 +++++++++++++++++++ pkg/tool/gorm/client.go | 1 - 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 pkg/microservice/user/core/service/init/mysql.sql diff --git a/go.mod b/go.mod index 70860f4e..2752cdd3 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/gin-gonic/gin v1.7.2 github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 + github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github/v35 v35.3.0 diff --git a/pkg/microservice/user/core/service.go b/pkg/microservice/user/core/service.go index 8a91a7f2..29d1af0c 100644 --- a/pkg/microservice/user/core/service.go +++ b/pkg/microservice/user/core/service.go @@ -18,7 +18,11 @@ package core import ( "context" + "database/sql" + _ "embed" + "fmt" + _ "github.com/go-sql-driver/mysql" "gorm.io/gorm" "github.com/koderover/zadig/pkg/config" @@ -30,6 +34,28 @@ import ( var DB *gorm.DB +//go:embed service/init/mysql.sql +var mysql []byte + +func initDBAndTables() { + if len(mysql) == 0 { + return + } + db, err := sql.Open("mysql", fmt.Sprintf( + "%s:%s@tcp(%s)/?charset=utf8&multiStatements=true", + config.MysqlUser(), config.MysqlPassword(), config.MysqlHost(), + )) + if err != nil { + log.Panic(err) + } + defer db.Close() + _, err = db.Exec(string(mysql)) + + if err != nil { + log.Panic(err) + } +} + func Start(_ context.Context) { log.Init(&log.Config{ Level: config.LogLevel(), @@ -43,6 +69,7 @@ func Start(_ context.Context) { } func initDatabase() { + initDBAndTables() err := gormtool.Open(config.MysqlUser(), config.MysqlPassword(), config.MysqlHost(), diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql new file mode 100644 index 00000000..bb77d2f9 --- /dev/null +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -0,0 +1,28 @@ +CREATE DATABASE IF NOT EXISTS user; +use user; +CREATE TABLE IF NOT EXISTS `user_login`( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `uid` varchar(64) NOT NULL DEFAULT '0' COMMENT '用户id', + `login_id` varchar(64) NOT NULL DEFAULT '0' COMMENT '用户登录id,如账号名', + `login_type` int(4) unsigned NOT NULL DEFAULT '0' COMMENT '登录类型,0.账号名', + `password` varchar(64) DEFAULT '' COMMENT '密码', + `last_login_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '最后登陆时间', + `created_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', + `updated_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', + UNIQUE KEY `login` (`uid`,`login_id`,`login_type`), + PRIMARY KEY (`id`), + KEY `idx_uid` (`uid`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 59 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '账号登陆表' ROW_FORMAT = Compact; + +CREATE TABLE IF NOT EXISTS `user`( + `uid` varchar(64) NOT NULL COMMENT '用户ID', + `account` varchar(32) NOT NULL DEFAULT '' COMMENT '用户账号', + `name` varchar(32) NOT NULL DEFAULT '' COMMENT '用户名', + `identity_type` varchar(32) NOT NULL DEFAULT 'unknown' COMMENT '用户来源', + `phone` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号码', + `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱', + `created_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `updated_at` int(11) unsigned NOT NULL COMMENT '修改时间', + UNIQUE KEY `account` (`account`,`identity_type`), + PRIMARY KEY (`uid`) +) ENGINE = InnoDB AUTO_INCREMENT = 59 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户信息表' ROW_FORMAT = Compact; \ No newline at end of file diff --git a/pkg/tool/gorm/client.go b/pkg/tool/gorm/client.go index aea8c5b7..c5e65fb1 100644 --- a/pkg/tool/gorm/client.go +++ b/pkg/tool/gorm/client.go @@ -41,6 +41,5 @@ func openDB(username, password, host, db string) (*gorm.DB, error) { "%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, host, db, ) - return gorm.Open(mysql.Open(dsn), &gorm.Config{}) } -- Gitee From 4b691a9f500b6e5fa6415059574eed7b23d32389 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 19:03:36 +0800 Subject: [PATCH 0497/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/repository/models/email.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go index 93887686..8712bd0a 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -12,6 +12,7 @@ type EmailHost struct { Password string `bson:"password" json:"password"` IsTLS bool `bson:"is_tls" json:"isTLS"` CreatedAt int64 `bson:"created_at" json:"created_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } -- Gitee From 06151050735daa2a9567b4095988a38008aae7fb Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 19:06:10 +0800 Subject: [PATCH 0498/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/repository/mongo/email.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index a2de89a7..02dbab5d 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -63,6 +63,7 @@ func (c *EmailHostColl) Find() (*models.EmailHost, error) { if err != nil { return nil, nil } + emailHost.Password = "***" return emailHost, nil } -- Gitee From 086828414ebf90ac628662eaa9e8ba984916011f Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 19:29:46 +0800 Subject: [PATCH 0499/2647] json Signed-off-by: mouuii --- .../core/{ => email}/handler/emails.go | 18 ++++++------ .../core/{ => email}/service/email.go | 0 .../systemconfig/core/handler/router.go | 29 ++++++++++--------- .../core/{ => jira}/handler/jira.go | 10 +++---- .../core/{ => jira}/service/jira.go | 0 .../core/repository/models/email.go | 21 +++++--------- .../core/repository/models/jira.go | 19 +++++------- .../core/repository/mongo/codehost.go | 3 +- .../core/repository/mongo/email.go | 2 +- .../core/repository/mongo/jira.go | 3 +- 10 files changed, 48 insertions(+), 57 deletions(-) rename pkg/microservice/systemconfig/core/{ => email}/handler/emails.go (75%) rename pkg/microservice/systemconfig/core/{ => email}/service/email.go (100%) rename pkg/microservice/systemconfig/core/{ => jira}/handler/jira.go (75%) rename pkg/microservice/systemconfig/core/{ => jira}/service/jira.go (100%) diff --git a/pkg/microservice/systemconfig/core/handler/emails.go b/pkg/microservice/systemconfig/core/email/handler/emails.go similarity index 75% rename from pkg/microservice/systemconfig/core/handler/emails.go rename to pkg/microservice/systemconfig/core/email/handler/emails.go index a1fcbea1..6aa77c95 100644 --- a/pkg/microservice/systemconfig/core/handler/emails.go +++ b/pkg/microservice/systemconfig/core/email/handler/emails.go @@ -3,15 +3,15 @@ package handler import ( "github.com/gin-gonic/gin" + service2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/service" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) func GetEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) + ctx.Resp, ctx.Err = service2.GetEmailHost(ctx.Logger) } func CreateEmailHost(c *gin.Context) { @@ -22,7 +22,7 @@ func CreateEmailHost(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.CreateEmailHost(req, ctx.Logger) + ctx.Resp, ctx.Err = service2.CreateEmailHost(req, ctx.Logger) } func UpdateEmailHost(c *gin.Context) { @@ -33,13 +33,13 @@ func UpdateEmailHost(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.UpdateEmailHost(req, ctx.Logger) + ctx.Resp, ctx.Err = service2.UpdateEmailHost(req, ctx.Logger) } func DeleteEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DeleteEmailHost(ctx.Logger) + ctx.Err = service2.DeleteEmailHost(ctx.Logger) } //// @@ -47,7 +47,7 @@ func DeleteEmailHost(c *gin.Context) { func GetEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetEmailService(ctx.Logger) + ctx.Resp, ctx.Err = service2.GetEmailService(ctx.Logger) } func CreateEmailService(c *gin.Context) { @@ -58,7 +58,7 @@ func CreateEmailService(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.CreateEmailService(req, ctx.Logger) + ctx.Resp, ctx.Err = service2.CreateEmailService(req, ctx.Logger) } func UpdateEmailService(c *gin.Context) { @@ -69,11 +69,11 @@ func UpdateEmailService(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.UpdateEmailService(req, ctx.Logger) + ctx.Resp, ctx.Err = service2.UpdateEmailService(req, ctx.Logger) } func DeleteEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DeleteEmailService(ctx.Logger) + ctx.Err = service2.DeleteEmailService(ctx.Logger) } diff --git a/pkg/microservice/systemconfig/core/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go similarity index 100% rename from pkg/microservice/systemconfig/core/service/email.go rename to pkg/microservice/systemconfig/core/email/service/email.go diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index c32b1eaa..b0caf1fe 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -18,6 +18,9 @@ package handler import ( "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/handler" + handler2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/handler" ) type Router struct{} @@ -37,23 +40,23 @@ func (*Router) Inject(router *gin.RouterGroup) { } emails := router.Group("emails") { - emails.GET("/host", GetEmailHost) - emails.POST("/host", CreateEmailHost) - emails.PATCH("/host", UpdateEmailHost) - emails.DELETE("/host", DeleteEmailHost) - - emails.GET("/service", GetEmailService) - emails.POST("/service", CreateEmailService) - emails.PATCH("/service", UpdateEmailService) - emails.DELETE("/service", DeleteEmailService) + emails.GET("/host", handler.GetEmailHost) + emails.POST("/host", handler.CreateEmailHost) + emails.PATCH("/host", handler.UpdateEmailHost) + emails.DELETE("/host", handler.DeleteEmailHost) + + emails.GET("/service", handler.GetEmailService) + emails.POST("/service", handler.CreateEmailService) + emails.PATCH("/service", handler.UpdateEmailService) + emails.DELETE("/service", handler.DeleteEmailService) } jira := router.Group("jira") { - jira.GET("", GetJira) - jira.POST("", CreateJira) - jira.PATCH("", UpdateJira) - jira.DELETE("", DeleteJira) + jira.GET("", handler2.GetJira) + jira.POST("", handler2.CreateJira) + jira.PATCH("", handler2.UpdateJira) + jira.DELETE("", handler2.DeleteJira) } codehost := router.Group("codehost") { diff --git a/pkg/microservice/systemconfig/core/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go similarity index 75% rename from pkg/microservice/systemconfig/core/handler/jira.go rename to pkg/microservice/systemconfig/core/jira/handler/jira.go index e249fa83..7a0074d4 100644 --- a/pkg/microservice/systemconfig/core/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -3,21 +3,21 @@ package handler import ( "github.com/gin-gonic/gin" + service2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/service" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) func DeleteJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DeleteJira(ctx.Logger) + ctx.Err = service2.DeleteJira(ctx.Logger) } func GetJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GeJira(ctx.Logger) + ctx.Resp, ctx.Err = service2.GeJira(ctx.Logger) } func CreateJira(c *gin.Context) { @@ -28,7 +28,7 @@ func CreateJira(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.CreateJira(req, ctx.Logger) + ctx.Resp, ctx.Err = service2.CreateJira(req, ctx.Logger) } func UpdateJira(c *gin.Context) { @@ -39,5 +39,5 @@ func UpdateJira(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.UpdateJira(req, ctx.Logger) + ctx.Resp, ctx.Err = service2.UpdateJira(req, ctx.Logger) } diff --git a/pkg/microservice/systemconfig/core/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go similarity index 100% rename from pkg/microservice/systemconfig/core/service/jira.go rename to pkg/microservice/systemconfig/core/jira/service/jira.go diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/repository/models/email.go index 8712bd0a..69f7a93a 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/repository/models/email.go @@ -1,9 +1,5 @@ package models -import ( - "github.com/globalsign/mgo/bson" -) - type EmailHost struct { ID int `bson:"id" json:"id"` Name string `bson:"name" json:"name"` @@ -21,15 +17,14 @@ func (EmailHost) TableName() string { } type EmailService struct { - ObjectID bson.ObjectId `bson:"_id,omitempty"` - ID int `json:"id" bson:"id"` - Name string `json:"name" bson:"name"` - Address string `json:"address" bson:"address"` - DisplayName string `json:"display_name" bson:"display_name"` - Theme string `json:"theme" bson:"theme"` - CreatedAt int64 `json:"created_at" bson:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at"` - DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` + ID int `json:"id" bson:"id"` + Name string `json:"name" bson:"name"` + Address string `json:"address" bson:"address"` + DisplayName string `json:"display_name" bson:"display_name"` + Theme string `json:"theme" bson:"theme"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` } func (EmailService) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go index 5c4b7c35..02499db5 100644 --- a/pkg/microservice/systemconfig/core/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -1,18 +1,13 @@ package models -import ( - "github.com/globalsign/mgo/bson" -) - type Jira struct { - ObjectID bson.ObjectId `bson:"_id,omitempty"json:"-" ` - ID int64 `bson:"id"json:"id" ` - Host string `bson:"host"json:"host" ` - User string `bson:"user"json:"user" ` - AccessToken string `bson:"access_token" json:"access_token"` - CreatedAt int64 `bson:"created_at"json:"created_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` - DeletedAt int64 `bson:"deleted_at"json:"deleted_at"` + ID int64 `bson:"id"json:"id"` + Host string `bson:"host"json:"host"` + User string `bson:"user"json:"user"` + AccessToken string `bson:"access_token" json:"access_token"` + CreatedAt int64 `bson:"created_at"json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at"json:"deleted_at"` } func (Jira) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go index a493b32c..f589f747 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/codehost.go @@ -4,10 +4,9 @@ import ( "context" "time" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "github.com/globalsign/mgo/bson" - "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" "github.com/koderover/zadig/pkg/tool/log" diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index 02dbab5d..e9a57c4d 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/globalsign/mgo/bson" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" diff --git a/pkg/microservice/systemconfig/core/repository/mongo/jira.go b/pkg/microservice/systemconfig/core/repository/mongo/jira.go index 034d1e37..ef1ccf4d 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/jira.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/jira.go @@ -4,10 +4,9 @@ import ( "context" "time" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "github.com/globalsign/mgo/bson" - "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" "github.com/koderover/zadig/pkg/tool/log" -- Gitee From 287cca7f45d2d6f178fad5e02a92ad99cd290b8c Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 19:43:39 +0800 Subject: [PATCH 0500/2647] json Signed-off-by: mouuii --- go.mod | 1 - go.sum | 1 - 2 files changed, 2 deletions(-) diff --git a/go.mod b/go.mod index bd573294..d17cc71d 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/garyburd/redigo v1.6.2 // indirect github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 - github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect diff --git a/go.sum b/go.sum index 05dce6a4..e9ff3cff 100644 --- a/go.sum +++ b/go.sum @@ -550,7 +550,6 @@ github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -- Gitee From 74a8165d3188761d620f6940f342fcbfb08046da Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 4 Nov 2021 20:18:44 +0800 Subject: [PATCH 0501/2647] update remote dialer (#536) * update hubserver verison Signed-off-by: allenshen * remove useless code Signed-off-by: allenshen * remove old version remote dialer Signed-off-by: allenshen * organise code Signed-off-by: allenshen * remove useless code Signed-off-by: allenshen --- .../hubagent/core/service/client.go | 2 + .../hubserver/core/service/service.go | 38 +++--- pkg/tool/remotedialer/client.go | 61 ++++++--- pkg/tool/remotedialer/client_dialer.go | 14 +- pkg/tool/remotedialer/connection.go | 97 ++------------ pkg/tool/remotedialer/dialer.go | 24 ++-- pkg/tool/remotedialer/message.go | 28 ++-- pkg/tool/remotedialer/peer.go | 9 +- .../{session_windows.go => readbuffer.go} | 125 ++++++++++++------ pkg/tool/remotedialer/server.go | 64 +++++++-- pkg/tool/remotedialer/session.go | 93 +++++++++---- pkg/tool/remotedialer/session_manager.go | 16 +-- pkg/tool/remotedialer/types.go | 11 +- pkg/tool/remotedialer/wsconn.go | 45 +++++-- 14 files changed, 376 insertions(+), 251 deletions(-) rename pkg/tool/remotedialer/{session_windows.go => readbuffer.go} (31%) diff --git a/pkg/microservice/hubagent/core/service/client.go b/pkg/microservice/hubagent/core/service/client.go index 410f8930..5aa65b4c 100644 --- a/pkg/microservice/hubagent/core/service/client.go +++ b/pkg/microservice/hubagent/core/service/client.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -173,6 +174,7 @@ func (c *Client) Start() error { tm := time.Now() remotedialer.ClientConnect( + context.Background(), connectURL, headers, &websocket.Dialer{ diff --git a/pkg/microservice/hubserver/core/service/service.go b/pkg/microservice/hubserver/core/service/service.go index 87aba25f..2acae6b2 100644 --- a/pkg/microservice/hubserver/core/service/service.go +++ b/pkg/microservice/hubserver/core/service/service.go @@ -17,8 +17,6 @@ limitations under the License. package service import ( - "crypto/tls" - "crypto/x509" "encoding/base64" "encoding/json" "fmt" @@ -29,6 +27,7 @@ import ( "github.com/gorilla/mux" "k8s.io/apimachinery/pkg/util/proxy" + "k8s.io/apimachinery/pkg/util/wait" "github.com/koderover/zadig/pkg/microservice/hubserver/config" "github.com/koderover/zadig/pkg/microservice/hubserver/core/repository/models" @@ -171,12 +170,24 @@ func Forward(server *remotedialer.Server, w http.ResponseWriter, r *http.Request } }() - _, err = mongodb.NewK8sClusterColl().Get(clientKey) - if err != nil { - return - } + //_, err = mongodb.NewK8sClusterColl().Get(clientKey) + //if err != nil { + // return + //} clusterInfo, exists := clusters.Load(clientKey) + if !server.HasSession(clientKey) || !exists { + for i := 0; i < 4; i++ { + log.Infof("stuck waiting for connection index:%d", i) + if server.HasSession(clientKey) && exists { + log.Infof("succeeded waiting for connection index:%d", i) + break + } + time.Sleep(wait.Jitter(3*time.Second, 2)) + clusterInfo, exists = clusters.Load(clientKey) + } + } + if !server.HasSession(clientKey) || !exists { errHandled = true logger.Infof("waiting for cluster %s to connect", clientKey) @@ -204,23 +215,12 @@ func Forward(server *remotedialer.Server, w http.ResponseWriter, r *http.Request r.URL.Host = r.Host r.Header.Set("authorization", "Bearer "+cluster.Token) - var certBytes []byte - certBytes, err = base64.StdEncoding.DecodeString(cluster.CACert) + transport, err := server.GetTransport(cluster.CACert, clientKey) if err != nil { + log.Errorf(fmt.Sprintf("failed to get transport, err %s", err)) return } - certs := x509.NewCertPool() - certs.AppendCertsFromPEM(certBytes) - - transport := &http.Transport{} - - //noinspection GoDeprecation - transport.Dial = server.Dialer(clientKey, 15*time.Second) - transport.TLSClientConfig = &tls.Config{ - RootCAs: certs, - } - httpProxy := proxy.NewUpgradeAwareHandler(endpoint, transport, true, false, er) httpProxy.ServeHTTP(w, r) } diff --git a/pkg/tool/remotedialer/client.go b/pkg/tool/remotedialer/client.go index fdfe85c6..32981a7c 100644 --- a/pkg/tool/remotedialer/client.go +++ b/pkg/tool/remotedialer/client.go @@ -18,6 +18,7 @@ package remotedialer import ( "context" + "io/ioutil" "net/http" "time" @@ -25,39 +26,69 @@ import ( "github.com/sirupsen/logrus" ) +// ConnectAuthorizer custom for authorization type ConnectAuthorizer func(proto, address string) bool -func ClientConnect(wsURL string, headers http.Header, dialer *websocket.Dialer, auth ConnectAuthorizer, onConnect func(context.Context) error) { - if err := connectToProxy(wsURL, headers, auth, dialer, onConnect); err != nil { - logrus.WithError(err).Error("Failed to connect to proxy") +// ClientConnect connect to WS and wait 5 seconds when error +func ClientConnect(ctx context.Context, wsURL string, headers http.Header, dialer *websocket.Dialer, + auth ConnectAuthorizer, onConnect func(context.Context, *Session) error) error { + if err := ConnectToProxy(ctx, wsURL, headers, auth, dialer, onConnect); err != nil { + logrus.WithError(err).Error("Remotedialer proxy error") time.Sleep(time.Duration(5) * time.Second) + return err } + return nil } -func connectToProxy(proxyURL string, headers http.Header, auth ConnectAuthorizer, dialer *websocket.Dialer, onConnect func(context.Context) error) error { +// ConnectToProxy connect to websocket server +func ConnectToProxy(rootCtx context.Context, proxyURL string, headers http.Header, auth ConnectAuthorizer, dialer *websocket.Dialer, onConnect func(context.Context, *Session) error) error { logrus.WithField("url", proxyURL).Info("Connecting to proxy") if dialer == nil { - dialer = &websocket.Dialer{HandshakeTimeout: 10 * time.Second} + dialer = &websocket.Dialer{Proxy: http.ProxyFromEnvironment, HandshakeTimeout: HandshakeTimeOut} } - ws, _, err := dialer.Dial(proxyURL, headers) + ws, resp, err := dialer.DialContext(rootCtx, proxyURL, headers) if err != nil { - logrus.WithError(err).Error("Failed to connect to proxy") + if resp == nil { + logrus.WithError(err).Errorf("Failed to connect to proxy. Empty dialer response") + } else { + rb, err2 := ioutil.ReadAll(resp.Body) + if err2 != nil { + logrus.WithError(err).Errorf("Failed to connect to proxy. Response status: %v - %v. Couldn't read response body (err: %v)", resp.StatusCode, resp.Status, err2) + } else { + logrus.WithError(err).Errorf("Failed to connect to proxy. Response status: %v - %v. Response body: %s", resp.StatusCode, resp.Status, rb) + } + } return err } defer ws.Close() - ctx, cancel := context.WithCancel(context.Background()) + result := make(chan error, 2) + + ctx, cancel := context.WithCancel(rootCtx) defer cancel() + session := NewClientSession(auth, ws) + defer session.Close() + if onConnect != nil { - if err := onConnect(ctx); err != nil { - return err - } + go func() { + if err := onConnect(ctx, session); err != nil { + result <- err + } + }() } - session := NewClientSession(auth, ws) - _, err = session.Serve() - session.Close() - return err + go func() { + _, err = session.Serve(ctx) + result <- err + }() + + select { + case <-ctx.Done(): + logrus.WithField("url", proxyURL).WithField("err", ctx.Err()).Info("Proxy done") + return nil + case err := <-result: + return err + } } diff --git a/pkg/tool/remotedialer/client_dialer.go b/pkg/tool/remotedialer/client_dialer.go index 2bb5401e..5fd49b4f 100644 --- a/pkg/tool/remotedialer/client_dialer.go +++ b/pkg/tool/remotedialer/client_dialer.go @@ -17,13 +17,14 @@ limitations under the License. package remotedialer import ( + "context" "io" "net" "sync" "time" ) -func clientDial(dialer Dialer, conn *connection, message *message) { +func clientDial(ctx context.Context, dialer Dialer, conn *connection, message *message) { defer conn.Close() var ( @@ -31,15 +32,14 @@ func clientDial(dialer Dialer, conn *connection, message *message) { err error ) + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Minute)) if dialer == nil { - netDialer := &net.Dialer{ - Timeout: time.Duration(message.deadline) * time.Millisecond, - KeepAlive: 30 * time.Second, - } - netConn, err = netDialer.Dial(message.proto, message.address) + d := net.Dialer{} + netConn, err = d.DialContext(ctx, message.proto, message.address) } else { - netConn, err = dialer(message.proto, message.address) + netConn, err = dialer(ctx, message.proto, message.address) } + cancel() if err != nil { conn.tunnelClose(err) diff --git a/pkg/tool/remotedialer/connection.go b/pkg/tool/remotedialer/connection.go index 6e8870aa..3538fa0b 100644 --- a/pkg/tool/remotedialer/connection.go +++ b/pkg/tool/remotedialer/connection.go @@ -17,20 +17,15 @@ limitations under the License. package remotedialer import ( - "errors" "io" "net" - "sync" "time" ) type connection struct { - sync.Mutex - err error writeDeadline time.Time - buf chan []byte - readBuf []byte + buffer *readBuffer addr addr session *Session connID int64 @@ -38,13 +33,13 @@ type connection struct { func newConnection(connID int64, session *Session, proto, address string) *connection { c := &connection{ + buffer: newReadBuffer(), addr: addr{ proto: proto, address: address, }, connID: connID, session: session, - buf: make(chan []byte, 1024), } return c } @@ -55,9 +50,6 @@ func (c *connection) tunnelClose(err error) { } func (c *connection) doTunnelClose(err error) { - c.Lock() - defer c.Unlock() - if c.err != nil { return } @@ -67,11 +59,11 @@ func (c *connection) doTunnelClose(err error) { c.err = io.ErrClosedPipe } - close(c.buf) + c.buffer.Close(c.err) } -func (c *connection) tunnelWriter() io.Writer { - return chanWriter{conn: c, C: c.buf} +func (c *connection) OnData(m *message) error { + return c.buffer.Offer(m.body) } func (c *connection) Close() error { @@ -79,56 +71,23 @@ func (c *connection) Close() error { return nil } -func (c *connection) copyData(b []byte) int { - n := copy(b, c.readBuf) - c.readBuf = c.readBuf[n:] - return n -} - func (c *connection) Read(b []byte) (int, error) { - if len(b) == 0 { - return 0, nil - } - - n := c.copyData(b) - if n > 0 { - return n, nil - } - - next, ok := <-c.buf - if !ok { - err := io.EOF - c.Lock() - if c.err != nil { - err = c.err - } - c.Unlock() - return 0, err - } - - c.readBuf = next - n = c.copyData(b) - return n, nil + n, err := c.buffer.Read(b) + return n, err } func (c *connection) Write(b []byte) (int, error) { - c.Lock() if c.err != nil { - defer c.Unlock() - return 0, c.err + return 0, io.ErrClosedPipe } - c.Unlock() - - deadline := int64(0) - if !c.writeDeadline.IsZero() { - deadline = time.Until(c.writeDeadline).Nanoseconds() / 1000000 - } - return c.session.writeMessage(newMessage(c.connID, deadline, b)) + msg := newMessage(c.connID, b) + return c.session.writeMessage(c.writeDeadline, msg) } func (c *connection) writeErr(err error) { if err != nil { - c.session.writeMessage(newErrorMessage(c.connID, err)) + msg := newErrorMessage(c.connID, err) + c.session.writeMessage(c.writeDeadline, msg) } } @@ -148,6 +107,7 @@ func (c *connection) SetDeadline(t time.Time) error { } func (c *connection) SetReadDeadline(t time.Time) error { + c.buffer.deadline = t return nil } @@ -168,34 +128,3 @@ func (a addr) Network() string { func (a addr) String() string { return a.address } - -type chanWriter struct { - conn *connection - C chan []byte -} - -func (c chanWriter) Write(buf []byte) (int, error) { - c.conn.Lock() - defer c.conn.Unlock() - - if c.conn.err != nil { - return 0, c.conn.err - } - - newBuf := make([]byte, len(buf)) - copy(newBuf, buf) - buf = newBuf - - select { - // must copy the buffer - case c.C <- buf: - return len(buf), nil - default: - select { - case c.C <- buf: - return len(buf), nil - case <-time.After(15 * time.Second): - return 0, errors.New("backed up reader") - } - } -} diff --git a/pkg/tool/remotedialer/dialer.go b/pkg/tool/remotedialer/dialer.go index 5772fa08..3e669083 100644 --- a/pkg/tool/remotedialer/dialer.go +++ b/pkg/tool/remotedialer/dialer.go @@ -17,28 +17,24 @@ limitations under the License. package remotedialer import ( + "context" "net" - "time" ) -type Dialer func(network, address string) (net.Conn, error) +type Dialer func(ctx context.Context, network, address string) (net.Conn, error) func (s *Server) HasSession(clientKey string) bool { - _, err := s.sessions.getDialer(clientKey, 0) + _, err := s.sessions.getDialer(clientKey) return err == nil } -func (s *Server) Dial(clientKey string, deadline time.Duration, proto, address string) (net.Conn, error) { - d, err := s.sessions.getDialer(clientKey, deadline) - if err != nil { - return nil, err - } - - return d(proto, address) -} +func (s *Server) Dialer(clientKey string) Dialer { + return func(ctx context.Context, network, address string) (net.Conn, error) { + d, err := s.sessions.getDialer(clientKey) + if err != nil { + return nil, err + } -func (s *Server) Dialer(clientKey string, deadline time.Duration) Dialer { - return func(proto, address string) (net.Conn, error) { - return s.Dial(clientKey, deadline, proto, address) + return d(ctx, network, address) } } diff --git a/pkg/tool/remotedialer/message.go b/pkg/tool/remotedialer/message.go index 90e21e55..04c073da 100644 --- a/pkg/tool/remotedialer/message.go +++ b/pkg/tool/remotedialer/message.go @@ -40,7 +40,8 @@ const ( ) var ( - idCounter int64 + idCounter int64 + legacyDeadline = (15 * time.Second).Milliseconds() ) func init() { @@ -54,7 +55,6 @@ type message struct { id int64 err error connID int64 - deadline int64 messageType messageType bytes []byte body io.Reader @@ -66,21 +66,19 @@ func nextid() int64 { return atomic.AddInt64(&idCounter, 1) } -func newMessage(connID int64, deadline int64, bytes []byte) *message { +func newMessage(connID int64, bytes []byte) *message { return &message{ id: nextid(), connID: connID, - deadline: deadline, messageType: Data, bytes: bytes, } } -func newConnect(connID int64, deadline time.Duration, proto, address string) *message { +func newConnect(connID int64, proto, address string) *message { return &message{ id: nextid(), connID: connID, - deadline: deadline.Nanoseconds() / 1000000, messageType: Connect, bytes: []byte(fmt.Sprintf("%s/%s", proto, address)), proto: proto, @@ -142,11 +140,11 @@ func newServerMessage(reader io.Reader) (*message, error) { } if m.messageType == Data || m.messageType == Connect { - deadline, err := binary.ReadVarint(buf) + // no longer used, this is the deadline field + _, err := binary.ReadVarint(buf) if err != nil { return nil, err } - m.deadline = deadline } if m.messageType == Connect { @@ -192,17 +190,17 @@ func (m *message) Err() error { } func (m *message) Bytes() []byte { - return append(m.header(), m.bytes...) + return append(m.header(len(m.bytes)), m.bytes...) } -func (m *message) header() []byte { - buf := make([]byte, 24) +func (m *message) header(space int) []byte { + buf := make([]byte, 24+space) offset := 0 offset += binary.PutVarint(buf[offset:], m.id) offset += binary.PutVarint(buf[offset:], m.connID) offset += binary.PutVarint(buf[offset:], int64(m.messageType)) if m.messageType == Data || m.messageType == Connect { - offset += binary.PutVarint(buf[offset:], m.deadline) + offset += binary.PutVarint(buf[offset:], legacyDeadline) } return buf[:offset] } @@ -211,8 +209,8 @@ func (m *message) Read(p []byte) (int, error) { return m.body.Read(p) } -func (m *message) WriteTo(wsConn *wsConn) (int, error) { - err := wsConn.WriteMessage(websocket.BinaryMessage, m.Bytes()) +func (m *message) WriteTo(deadline time.Time, wsConn *wsConn) (int, error) { + err := wsConn.WriteMessage(websocket.BinaryMessage, deadline, m.Bytes()) return len(m.bytes), err } @@ -226,7 +224,7 @@ func (m *message) String() string { case Error: return fmt.Sprintf("%d ERROR [%d]: %s", m.id, m.connID, m.Err()) case Connect: - return fmt.Sprintf("%d CONNECT [%d]: %s/%s deadline %d", m.id, m.connID, m.proto, m.address, m.deadline) + return fmt.Sprintf("%d CONNECT [%d]: %s/%s", m.id, m.connID, m.proto, m.address) case AddClient: return fmt.Sprintf("%d ADDCLIENT [%s]", m.id, m.address) case RemoveClient: diff --git a/pkg/tool/remotedialer/peer.go b/pkg/tool/remotedialer/peer.go index ba6e9eff..f280bf29 100644 --- a/pkg/tool/remotedialer/peer.go +++ b/pkg/tool/remotedialer/peer.go @@ -95,7 +95,7 @@ func (p *peer) start(ctx context.Context, s *Server) { TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, - HandshakeTimeout: 10 * time.Second, + HandshakeTimeout: HandshakeTimeOut, } outer: @@ -114,16 +114,17 @@ outer: } session := NewClientSession(func(string, string) bool { return true }, ws) - session.dialer = func(network, address string) (net.Conn, error) { + session.dialer = func(ctx context.Context, network, address string) (net.Conn, error) { parts := strings.SplitN(network, "::", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid clientKey/proto: %s", network) } - return s.Dial(parts[0], 15*time.Second, parts[1], address) + d := s.Dialer(parts[0]) + return d(ctx, parts[1], address) } s.sessions.addListener(session) - _, err = session.Serve() + _, err = session.Serve(ctx) s.sessions.removeListener(session) session.Close() diff --git a/pkg/tool/remotedialer/session_windows.go b/pkg/tool/remotedialer/readbuffer.go similarity index 31% rename from pkg/tool/remotedialer/session_windows.go rename to pkg/tool/remotedialer/readbuffer.go index d43885bf..3b2f5498 100644 --- a/pkg/tool/remotedialer/session_windows.go +++ b/pkg/tool/remotedialer/readbuffer.go @@ -17,57 +17,106 @@ limitations under the License. package remotedialer import ( - "context" + "bytes" + "errors" + "io" + "sync" "time" +) - "github.com/gorilla/websocket" - "github.com/sirupsen/logrus" +const ( + MaxBuffer = 1 << 20 ) -func (s *Session) startPingsWhileWindows(rootCtx context.Context) { - ctx, cancel := context.WithCancel(rootCtx) - s.pingCancel = cancel - s.pingWait.Add(1) - - go func() { - defer s.pingWait.Done() - - t := time.NewTicker(PingWriteInterval) - defer t.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-t.C: - s.conn.Lock() - if err := s.conn.conn.WriteControl(websocket.PingMessage, []byte(""), time.Now().Add(time.Second)); err != nil { - logrus.WithError(err).Error("Error writing ping") - } - logrus.Debug("Wrote ping") - s.conn.Unlock() - } - } - }() +type readBuffer struct { + cond sync.Cond + deadline time.Time + buf bytes.Buffer + err error } -func (s *Session) ServeWhileWindows(ctx context.Context) (int, error) { - if s.client { - s.startPingsWhileWindows(ctx) +func newReadBuffer() *readBuffer { + return &readBuffer{ + cond: sync.Cond{ + L: &sync.Mutex{}, + }, + } +} + +func (r *readBuffer) Offer(reader io.Reader) error { + r.cond.L.Lock() + defer r.cond.L.Unlock() + + for { + if r.err != nil { + return r.err + } + + if n, err := io.Copy(&r.buf, reader); err != nil { + return err + } else if n > 0 { + r.cond.Broadcast() + } + + if r.buf.Len() < MaxBuffer { + return nil + } + + r.cond.Wait() } +} + +func (r *readBuffer) Read(b []byte) (int, error) { + r.cond.L.Lock() + defer r.cond.L.Unlock() for { - msType, reader, err := s.conn.NextReader() - if err != nil { - return 400, err + var ( + n int + err error + ) + + if r.buf.Len() > 0 { + n, err = r.buf.Read(b) + r.cond.Broadcast() + if err != io.EOF { + return n, err + } + // buffer remains to be read + if r.err == io.EOF && r.buf.Len() > 0 { + return n, nil + } + return n, r.err } - if msType != websocket.BinaryMessage { - return 400, errWrongMessageType + if r.err != nil { + return n, r.err } - if err := s.serveMessage(reader); err != nil { - return 500, err + now := time.Now() + if !r.deadline.IsZero() { + if now.After(r.deadline) { + return 0, errors.New("deadline exceeded") + } } + + var t *time.Timer + if !r.deadline.IsZero() { + t = time.AfterFunc(r.deadline.Sub(now), func() { r.cond.Broadcast() }) + } + r.cond.Wait() + if t != nil { + t.Stop() + } + } +} + +func (r *readBuffer) Close(err error) error { + r.cond.L.Lock() + defer r.cond.L.Unlock() + if r.err == nil { + r.err = err } + r.cond.Broadcast() + return nil } diff --git a/pkg/tool/remotedialer/server.go b/pkg/tool/remotedialer/server.go index d5401567..38cbf4a9 100644 --- a/pkg/tool/remotedialer/server.go +++ b/pkg/tool/remotedialer/server.go @@ -17,6 +17,9 @@ limitations under the License. package remotedialer import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" "net/http" "sync" "time" @@ -39,14 +42,22 @@ func DefaultErrorWriter(rw http.ResponseWriter, req *http.Request, code int, err rw.Write([]byte(err.Error())) } +type transportGetter func() (http.RoundTripper, error) + type Server struct { - PeerID string - PeerToken string - authorizer Authorizer - errorWriter ErrorWriter - sessions *sessionManager - peers map[string]peer - peerLock sync.Mutex + PeerID string + PeerToken string + ClientConnectAuthorizer ConnectAuthorizer + authorizer Authorizer + errorWriter ErrorWriter + sessions *sessionManager + peers map[string]peer + peerLock sync.Mutex + + caCert string + httpTransport *http.Transport + + sync.Mutex } func New(auth Authorizer, errorWriter ErrorWriter) *Server { @@ -84,10 +95,10 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } session := s.sessions.add(clientKey, wsConn, peer) + session.auth = s.ClientConnectAuthorizer defer s.sessions.remove(session) - // Don't need to associate req.Context() to the Session, it will cancel otherwise - code, err := session.Serve() + code, err := session.Serve(req.Context()) if err != nil { // Hijacked so we can't write to the client logrus.Infof("error in remotedialer server [%d]: %v", code, err) @@ -112,6 +123,41 @@ func (s *Server) auth(req *http.Request) (clientKey string, authed, peer bool, e return id, authed, false, err } +func (r *Server) GetTransport(clusterCaCert string, clientKey string) (http.RoundTripper, error) { + + r.Lock() + defer r.Unlock() + + if r.httpTransport != nil && r.caCert == clusterCaCert { + return r.httpTransport, nil + } + + transport := &http.Transport{} + if clusterCaCert != "" { + certBytes, err := base64.StdEncoding.DecodeString(clusterCaCert) + if err != nil { + return nil, err + } + certs := x509.NewCertPool() + certs.AppendCertsFromPEM(certBytes) + transport.TLSClientConfig = &tls.Config{ + RootCAs: certs, + } + } + + d := r.Dialer(clientKey) + transport.DialContext = d + transport.Proxy = http.ProxyFromEnvironment + + r.caCert = clusterCaCert + if r.httpTransport != nil { + r.httpTransport.CloseIdleConnections() + } + r.httpTransport = transport + + return transport, nil +} + func (s *Server) Disconnect(clientKey string) { for _, session := range s.sessions.clients[clientKey] { session.CloseImmediately() diff --git a/pkg/tool/remotedialer/session.go b/pkg/tool/remotedialer/session.go index 82d017a5..0baaec17 100644 --- a/pkg/tool/remotedialer/session.go +++ b/pkg/tool/remotedialer/session.go @@ -59,12 +59,17 @@ func init() { } func NewClientSession(auth ConnectAuthorizer, conn *websocket.Conn) *Session { + return NewClientSessionWithDialer(auth, conn, nil) +} + +func NewClientSessionWithDialer(auth ConnectAuthorizer, conn *websocket.Conn, dialer Dialer) *Session { return &Session{ clientKey: "client", conn: newWSConn(conn), conns: map[int64]*connection{}, auth: auth, client: true, + dialer: dialer, } } @@ -79,8 +84,8 @@ func newSession(sessionKey int64, clientKey string, conn *websocket.Conn) *Sessi } } -func (s *Session) startPings() { - ctx, cancel := context.WithCancel(context.Background()) +func (s *Session) startPings(rootCtx context.Context) { + ctx, cancel := context.WithCancel(rootCtx) s.pingCancel = cancel s.pingWait.Add(1) @@ -96,7 +101,7 @@ func (s *Session) startPings() { return case <-t.C: s.conn.Lock() - if err := s.conn.conn.WriteControl(websocket.PingMessage, []byte(""), time.Now().Add(time.Second)); err != nil { + if err := s.conn.conn.WriteControl(websocket.PingMessage, []byte(""), time.Now().Add(PingWaitDuration)); err != nil { logrus.WithError(err).Error("Error writing ping") } logrus.Debug("Wrote ping") @@ -115,9 +120,9 @@ func (s *Session) stopPings() { s.pingWait.Wait() } -func (s *Session) Serve() (int, error) { +func (s *Session) Serve(ctx context.Context) (int, error) { if s.client { - s.startPings() + s.startPings(ctx) } for { @@ -130,13 +135,13 @@ func (s *Session) Serve() (int, error) { return 400, errWrongMessageType } - if err := s.serveMessage(reader); err != nil { + if err := s.serveMessage(ctx, reader); err != nil { return 500, err } } } -func (s *Session) serveMessage(reader io.Reader) error { +func (s *Session) serveMessage(ctx context.Context, reader io.Reader) error { message, err := newServerMessage(reader) if err != nil { return err @@ -150,7 +155,7 @@ func (s *Session) serveMessage(reader io.Reader) error { if s.auth == nil || !s.auth(message.proto, message.address) { return errors.New("connect not allowed") } - s.clientConnect(message) + s.clientConnect(ctx, message) return nil } @@ -170,14 +175,14 @@ func (s *Session) serveMessage(reader io.Reader) error { if conn == nil { if message.messageType == Data { err := fmt.Errorf("connection not found %s/%d/%d", s.clientKey, s.sessionKey, message.connID) - newErrorMessage(message.connID, err).WriteTo(s.conn) + newErrorMessage(message.connID, err).WriteTo(defaultDeadline(), s.conn) } return nil } switch message.messageType { case Data: - if _, err := io.Copy(conn.tunnelWriter(), message); err != nil { + if err := conn.OnData(message); err != nil { s.closeConnection(message.connID, err) } case Error: @@ -187,6 +192,10 @@ func (s *Session) serveMessage(reader io.Reader) error { return nil } +func defaultDeadline() time.Time { + return time.Now().Add(time.Minute) +} + func parseAddress(address string) (string, int, error) { parts := strings.SplitN(address, "/", 2) if len(parts) != 2 { @@ -207,7 +216,7 @@ func (s *Session) addRemoteClient(address string) error { keys = map[int]bool{} s.remoteClientKeys[clientKey] = keys } - keys[int(sessionKey)] = true + keys[sessionKey] = true if PrintTunnelData { logrus.Debugf("ADD REMOTE CLIENT %s, SESSION %d", address, s.sessionKey) @@ -249,7 +258,7 @@ func (s *Session) closeConnection(connID int64, err error) { } } -func (s *Session) clientConnect(message *message) { +func (s *Session) clientConnect(ctx context.Context, message *message) { conn := newConnection(message.connID, s, message.proto, message.address) s.Lock() @@ -259,10 +268,46 @@ func (s *Session) clientConnect(message *message) { } s.Unlock() - go clientDial(s.dialer, conn, message) + go clientDial(ctx, s.dialer, conn, message) +} + +type connResult struct { + conn net.Conn + err error +} + +func (s *Session) Dial(ctx context.Context, proto, address string) (net.Conn, error) { + return s.serverConnectContext(ctx, proto, address) } -func (s *Session) serverConnect(deadline time.Duration, proto, address string) (net.Conn, error) { +func (s *Session) serverConnectContext(ctx context.Context, proto, address string) (net.Conn, error) { + deadline, ok := ctx.Deadline() + if ok { + return s.serverConnect(deadline, proto, address) + } + + result := make(chan connResult, 1) + go func() { + c, err := s.serverConnect(defaultDeadline(), proto, address) + result <- connResult{conn: c, err: err} + }() + + select { + case <-ctx.Done(): + // We don't want to orphan an open connection so we wait for the result and immediately close it + go func() { + r := <-result + if r.err == nil { + r.conn.Close() + } + }() + return nil, ctx.Err() + case r := <-result: + return r.conn, r.err + } +} + +func (s *Session) serverConnect(deadline time.Time, proto, address string) (net.Conn, error) { connID := atomic.AddInt64(&s.nextConnID, 1) conn := newConnection(connID, s, proto, address) @@ -273,7 +318,7 @@ func (s *Session) serverConnect(deadline time.Duration, proto, address string) ( } s.Unlock() - _, err := s.writeMessage(newConnect(connID, deadline, proto, address)) + _, err := s.writeMessage(deadline, newConnect(connID, proto, address)) if err != nil { s.closeConnection(connID, err) return nil, err @@ -282,11 +327,11 @@ func (s *Session) serverConnect(deadline time.Duration, proto, address string) ( return conn, err } -func (s *Session) writeMessage(message *message) (int, error) { +func (s *Session) writeMessage(deadline time.Time, message *message) (int, error) { if PrintTunnelData { logrus.Debug("WRITE ", message) } - return message.WriteTo(s.conn) + return message.WriteTo(deadline, s.conn) } func (s *Session) Close() { @@ -302,14 +347,9 @@ func (s *Session) Close() { s.conns = map[int64]*connection{} } -func (s *Session) CloseImmediately() { - s.Close() - _ = s.conn.conn.Close() -} - func (s *Session) sessionAdded(clientKey string, sessionKey int64) { client := fmt.Sprintf("%s/%d", clientKey, sessionKey) - _, err := s.writeMessage(newAddClient(client)) + _, err := s.writeMessage(time.Time{}, newAddClient(client)) if err != nil { s.conn.conn.Close() } @@ -317,8 +357,13 @@ func (s *Session) sessionAdded(clientKey string, sessionKey int64) { func (s *Session) sessionRemoved(clientKey string, sessionKey int64) { client := fmt.Sprintf("%s/%d", clientKey, sessionKey) - _, err := s.writeMessage(newRemoveClient(client)) + _, err := s.writeMessage(time.Time{}, newRemoveClient(client)) if err != nil { s.conn.conn.Close() } } + +func (s *Session) CloseImmediately() { + s.Close() + _ = s.conn.conn.Close() +} diff --git a/pkg/tool/remotedialer/session_manager.go b/pkg/tool/remotedialer/session_manager.go index d92062ab..2fab0bd7 100644 --- a/pkg/tool/remotedialer/session_manager.go +++ b/pkg/tool/remotedialer/session_manager.go @@ -17,11 +17,11 @@ limitations under the License. package remotedialer import ( + "context" "fmt" "math/rand" "net" "sync" - "time" "github.com/gorilla/websocket" ) @@ -46,12 +46,12 @@ func newSessionManager() *sessionManager { } } -func toDialer(s *Session, prefix string, deadline time.Duration) Dialer { - return func(proto, address string) (net.Conn, error) { +func toDialer(s *Session, prefix string) Dialer { + return func(ctx context.Context, proto, address string) (net.Conn, error) { if prefix == "" { - return s.serverConnect(deadline, proto, address) + return s.serverConnectContext(ctx, proto, address) } - return s.serverConnect(deadline, prefix+"::"+proto, address) + return s.serverConnectContext(ctx, prefix+"::"+proto, address) } } @@ -81,13 +81,13 @@ func (sm *sessionManager) addListener(listener sessionListener) { } } -func (sm *sessionManager) getDialer(clientKey string, deadline time.Duration) (Dialer, error) { +func (sm *sessionManager) getDialer(clientKey string) (Dialer, error) { sm.Lock() defer sm.Unlock() sessions := sm.clients[clientKey] if len(sessions) > 0 { - return toDialer(sessions[0], "", deadline), nil + return toDialer(sessions[0], ""), nil } for _, sessions := range sm.peers { @@ -96,7 +96,7 @@ func (sm *sessionManager) getDialer(clientKey string, deadline time.Duration) (D keys := session.remoteClientKeys[clientKey] session.Unlock() if len(keys) > 0 { - return toDialer(session, clientKey, deadline), nil + return toDialer(session, clientKey), nil } } } diff --git a/pkg/tool/remotedialer/types.go b/pkg/tool/remotedialer/types.go index 061408d3..98ed9dfd 100644 --- a/pkg/tool/remotedialer/types.go +++ b/pkg/tool/remotedialer/types.go @@ -16,12 +16,11 @@ limitations under the License. package remotedialer -import ( - "time" -) +import "time" -var ( - PingWaitDuration = time.Duration(10 * time.Second) - PingWriteInterval = time.Duration(5 * time.Second) +const ( + PingWaitDuration = 60 * time.Second + PingWriteInterval = 5 * time.Second MaxRead = 8192 + HandshakeTimeOut = 10 * time.Second ) diff --git a/pkg/tool/remotedialer/wsconn.go b/pkg/tool/remotedialer/wsconn.go index 51256698..9a53ddc8 100644 --- a/pkg/tool/remotedialer/wsconn.go +++ b/pkg/tool/remotedialer/wsconn.go @@ -17,6 +17,8 @@ limitations under the License. package remotedialer import ( + "context" + "fmt" "io" "sync" "time" @@ -37,11 +39,29 @@ func newWSConn(conn *websocket.Conn) *wsConn { return w } -func (w *wsConn) WriteMessage(messageType int, data []byte) error { - w.Lock() - defer w.Unlock() - w.conn.SetWriteDeadline(time.Now().Add(PingWaitDuration)) - return w.conn.WriteMessage(messageType, data) +func (w *wsConn) WriteMessage(messageType int, deadline time.Time, data []byte) error { + if deadline.IsZero() { + w.Lock() + defer w.Unlock() + return w.conn.WriteMessage(messageType, data) + } + + ctx, cancel := context.WithDeadline(context.Background(), deadline) + defer cancel() + + done := make(chan error, 1) + go func() { + w.Lock() + defer w.Unlock() + done <- w.conn.WriteMessage(messageType, data) + }() + + select { + case <-ctx.Done(): + return fmt.Errorf("i/o timeout") + case err := <-done: + return err + } } func (w *wsConn) NextReader() (int, io.Reader, error) { @@ -52,12 +72,21 @@ func (w *wsConn) setupDeadline() { w.conn.SetReadDeadline(time.Now().Add(PingWaitDuration)) w.conn.SetPingHandler(func(string) error { w.Lock() - w.conn.WriteControl(websocket.PongMessage, []byte(""), time.Now().Add(time.Second)) + err := w.conn.WriteControl(websocket.PongMessage, []byte(""), time.Now().Add(PingWaitDuration)) w.Unlock() - return w.conn.SetReadDeadline(time.Now().Add(PingWaitDuration)) + if err != nil { + return err + } + if err := w.conn.SetReadDeadline(time.Now().Add(PingWaitDuration)); err != nil { + return err + } + return w.conn.SetWriteDeadline(time.Now().Add(PingWaitDuration)) }) w.conn.SetPongHandler(func(string) error { - return w.conn.SetReadDeadline(time.Now().Add(PingWaitDuration)) + if err := w.conn.SetReadDeadline(time.Now().Add(PingWaitDuration)); err != nil { + return err + } + return w.conn.SetWriteDeadline(time.Now().Add(PingWaitDuration)) }) } -- Gitee From f2f197a8a6ff7f7b254050a05b0d9c2b57048448 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:37:25 +0800 Subject: [PATCH 0502/2647] add init dockerfile (#537) Signed-off-by: lou --- Makefile | 2 +- docker/service/init.Dockerfile | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 docker/service/init.Dockerfile diff --git a/Makefile b/Makefile index 0a9a6ec6..08428072 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc VERSION ?= $(shell date +'%Y%m%d%H%M%S') -TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy user picket config +TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy user picket config init REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) diff --git a/docker/service/init.Dockerfile b/docker/service/init.Dockerfile new file mode 100644 index 00000000..d8acda07 --- /dev/null +++ b/docker/service/init.Dockerfile @@ -0,0 +1,10 @@ +#golang.Dockerfile + +RUN go build -o /init ./cmd/initconfig/main.go + +#alpine.Dockerfile + +WORKDIR /app +COPY --from=build /init /app/init + +ENTRYPOINT ["/app/init"] -- Gitee From 19d99fdd8369f421dcbe5a489c4f33bcbdece230 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 20:40:46 +0800 Subject: [PATCH 0503/2647] json Signed-off-by: mouuii --- .../core/{ => codehost}/handler/codehost.go | 0 .../core/{ => codehost}/service/codehost.go | 0 .../email/handler/{emails.go => email.go} | 10 +++---- .../core/email/models/emailhost.go | 1 + .../email.go => email/models/emailservice.go} | 16 ---------- .../core/repository/models/codehost.go | 30 +++++++++---------- .../core/repository/models/jira.go | 12 ++++---- 7 files changed, 27 insertions(+), 42 deletions(-) rename pkg/microservice/systemconfig/core/{ => codehost}/handler/codehost.go (100%) rename pkg/microservice/systemconfig/core/{ => codehost}/service/codehost.go (100%) rename pkg/microservice/systemconfig/core/email/handler/{emails.go => email.go} (90%) create mode 100644 pkg/microservice/systemconfig/core/email/models/emailhost.go rename pkg/microservice/systemconfig/core/{repository/models/email.go => email/models/emailservice.go} (50%) diff --git a/pkg/microservice/systemconfig/core/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go similarity index 100% rename from pkg/microservice/systemconfig/core/handler/codehost.go rename to pkg/microservice/systemconfig/core/codehost/handler/codehost.go diff --git a/pkg/microservice/systemconfig/core/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go similarity index 100% rename from pkg/microservice/systemconfig/core/service/codehost.go rename to pkg/microservice/systemconfig/core/codehost/service/codehost.go diff --git a/pkg/microservice/systemconfig/core/email/handler/emails.go b/pkg/microservice/systemconfig/core/email/handler/email.go similarity index 90% rename from pkg/microservice/systemconfig/core/email/handler/emails.go rename to pkg/microservice/systemconfig/core/email/handler/email.go index 6aa77c95..0cf74ad9 100644 --- a/pkg/microservice/systemconfig/core/email/handler/emails.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -3,8 +3,8 @@ package handler import ( "github.com/gin-gonic/gin" + models2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" service2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/service" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -17,7 +17,7 @@ func GetEmailHost(c *gin.Context) { func CreateEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models.EmailHost) + req := new(models2.EmailHost) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return @@ -28,7 +28,7 @@ func CreateEmailHost(c *gin.Context) { func UpdateEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models.EmailHost) + req := new(models2.EmailHost) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return @@ -53,7 +53,7 @@ func GetEmailService(c *gin.Context) { func CreateEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models.EmailService) + req := new(models2.EmailService) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return @@ -64,7 +64,7 @@ func CreateEmailService(c *gin.Context) { func UpdateEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models.EmailService) + req := new(models2.EmailService) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return diff --git a/pkg/microservice/systemconfig/core/email/models/emailhost.go b/pkg/microservice/systemconfig/core/email/models/emailhost.go new file mode 100644 index 00000000..2640e7f9 --- /dev/null +++ b/pkg/microservice/systemconfig/core/email/models/emailhost.go @@ -0,0 +1 @@ +package models diff --git a/pkg/microservice/systemconfig/core/repository/models/email.go b/pkg/microservice/systemconfig/core/email/models/emailservice.go similarity index 50% rename from pkg/microservice/systemconfig/core/repository/models/email.go rename to pkg/microservice/systemconfig/core/email/models/emailservice.go index 69f7a93a..44e61a39 100644 --- a/pkg/microservice/systemconfig/core/repository/models/email.go +++ b/pkg/microservice/systemconfig/core/email/models/emailservice.go @@ -1,21 +1,5 @@ package models -type EmailHost struct { - ID int `bson:"id" json:"id"` - Name string `bson:"name" json:"name"` - Port int `bson:"port" json:"port"` - Username string `bson:"username" json:"username"` - Password string `bson:"password" json:"password"` - IsTLS bool `bson:"is_tls" json:"isTLS"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` -} - -func (EmailHost) TableName() string { - return "email_host" -} - type EmailService struct { ID int `json:"id" bson:"id"` Name string `json:"name" bson:"name"` diff --git a/pkg/microservice/systemconfig/core/repository/models/codehost.go b/pkg/microservice/systemconfig/core/repository/models/codehost.go index b0571283..ba7d6269 100644 --- a/pkg/microservice/systemconfig/core/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/repository/models/codehost.go @@ -2,21 +2,21 @@ package models type CodeHost struct { ID int `bson:"id"` - Name string `bson:"name"json:"name"` - Type string `bson:"type"json:"type"` - Address string `bson:"address"json:"address"` - IsReady bool `bson:"is_ready"json:"ready"` - AccessToken string `bson:"access_token"json:"accessToken"` - RefreshToken string `bson:"refresh_token"json:"refreshToken"` - Namespace string `bson:"namespace"json:"namespace"` - OrganizationID int `bson:"organization_id"json:"orgId"` - ApplicationId string `bson:"application_id"json:"applicationId"` - Region string `bson:"region"json:"region,omitempty"` - Username string `bson:"username"json:"username,omitempty"` - Password string `bson:"password"json:"password,omitempty"` - ClientSecret string `bson:"client_secret"json:"clientSecret"` - CreatedAt int64 `bson:"created_at"json:"created_at"` - UpdatedAt int64 `bson:"updated_at"json:"updated_at"` + Name string `bson:"name" json:"name"` + Type string `bson:"type" json:"type"` + Address string `bson:"address" json:"address"` + IsReady bool `bson:"is_ready" json:"ready"` + AccessToken string `bson:"access_token" json:"accessToken"` + RefreshToken string `bson:"refresh_token" json:"refreshToken"` + Namespace string `bson:"namespace" json:"namespace"` + OrganizationID int `bson:"organization_id" json:"orgId"` + ApplicationId string `bson:"application_id" json:"applicationId"` + Region string `bson:"region" json:"region,omitempty"` + Username string `bson:"username" json:"username,omitempty"` + Password string `bson:"password" json:"password,omitempty"` + ClientSecret string `bson:"client_secret" json:"clientSecret"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/repository/models/jira.go index 02499db5..6038bf7b 100644 --- a/pkg/microservice/systemconfig/core/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/repository/models/jira.go @@ -1,13 +1,13 @@ package models type Jira struct { - ID int64 `bson:"id"json:"id"` - Host string `bson:"host"json:"host"` - User string `bson:"user"json:"user"` + ID int64 `bson:"id" json:"id"` + Host string `bson:"host" json:"host"` + User string `bson:"user" json:"user"` AccessToken string `bson:"access_token" json:"access_token"` - CreatedAt int64 `bson:"created_at"json:"created_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` - DeletedAt int64 `bson:"deleted_at"json:"deleted_at"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } func (Jira) TableName() string { -- Gitee From adee81a68aca4dde097f5c1d54dbf1f1e5a889ef Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 20:53:47 +0800 Subject: [PATCH 0504/2647] json Signed-off-by: mouuii --- .../core/codehost/handler/router.go | 22 +++++++++++++++++++ .../systemconfig/core/email/handler/router.go | 1 + .../systemconfig/core/jira/handler/router.go | 1 + 3 files changed, 24 insertions(+) create mode 100644 pkg/microservice/systemconfig/core/codehost/handler/router.go create mode 100644 pkg/microservice/systemconfig/core/email/handler/router.go create mode 100644 pkg/microservice/systemconfig/core/jira/handler/router.go diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go new file mode 100644 index 00000000..9f844c7a --- /dev/null +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + emails := router.Group("emails") + { + emails.GET("/host", GetEmailHost) + emails.POST("/host", CreateEmailHost) + emails.PATCH("/host", UpdateEmailHost) + emails.DELETE("/host", DeleteEmailHost) + + emails.GET("/service", GetEmailService) + emails.POST("/service", CreateEmailService) + emails.PATCH("/service", UpdateEmailService) + emails.DELETE("/service", DeleteEmailService) + } +} diff --git a/pkg/microservice/systemconfig/core/email/handler/router.go b/pkg/microservice/systemconfig/core/email/handler/router.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/systemconfig/core/email/handler/router.go @@ -0,0 +1 @@ +package handler diff --git a/pkg/microservice/systemconfig/core/jira/handler/router.go b/pkg/microservice/systemconfig/core/jira/handler/router.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/systemconfig/core/jira/handler/router.go @@ -0,0 +1 @@ +package handler -- Gitee From a57280c36016d12484b77ae8296e997aec5252f2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 21:08:25 +0800 Subject: [PATCH 0505/2647] json Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 2 +- .../core/codehost/handler/router.go | 12 ++------ .../systemconfig/core/email/handler/email.go | 28 +++++++++---------- .../systemconfig/core/email/handler/router.go | 21 ++++++++++++++ .../core/email/models/emailhost.go | 16 +++++++++++ .../core/email/models/emailservice.go | 14 +++++----- .../systemconfig/core/email/service/email.go | 2 +- .../systemconfig/core/handler/router.go | 27 ------------------ .../systemconfig/core/jira/handler/jira.go | 10 +++---- .../systemconfig/core/jira/handler/router.go | 17 +++++++++++ .../core/repository/mongo/email.go | 2 +- .../systemconfig/server/rest/router.go | 4 +++ 12 files changed, 89 insertions(+), 66 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index e6b2dd1d..96f34b9d 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -3,8 +3,8 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index 9f844c7a..5db3c015 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -7,16 +7,8 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - emails := router.Group("emails") + codehost := router.Group("codehost") { - emails.GET("/host", GetEmailHost) - emails.POST("/host", CreateEmailHost) - emails.PATCH("/host", UpdateEmailHost) - emails.DELETE("/host", DeleteEmailHost) - - emails.GET("/service", GetEmailService) - emails.POST("/service", CreateEmailService) - emails.PATCH("/service", UpdateEmailService) - emails.DELETE("/service", DeleteEmailService) + codehost.POST("", ListCodehost) } } diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index 0cf74ad9..1aec123f 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -3,43 +3,43 @@ package handler import ( "github.com/gin-gonic/gin" - models2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" - service2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/service" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) func GetEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service2.GetEmailHost(ctx.Logger) + ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) } func CreateEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models2.EmailHost) + req := new(models.EmailHost) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service2.CreateEmailHost(req, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateEmailHost(req, ctx.Logger) } func UpdateEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models2.EmailHost) + req := new(models.EmailHost) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service2.UpdateEmailHost(req, ctx.Logger) + ctx.Resp, ctx.Err = service.UpdateEmailHost(req, ctx.Logger) } func DeleteEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service2.DeleteEmailHost(ctx.Logger) + ctx.Err = service.DeleteEmailHost(ctx.Logger) } //// @@ -47,33 +47,33 @@ func DeleteEmailHost(c *gin.Context) { func GetEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service2.GetEmailService(ctx.Logger) + ctx.Resp, ctx.Err = service.GetEmailService(ctx.Logger) } func CreateEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models2.EmailService) + req := new(models.EmailService) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service2.CreateEmailService(req, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateEmailService(req, ctx.Logger) } func UpdateEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(models2.EmailService) + req := new(models.EmailService) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service2.UpdateEmailService(req, ctx.Logger) + ctx.Resp, ctx.Err = service.UpdateEmailService(req, ctx.Logger) } func DeleteEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service2.DeleteEmailService(ctx.Logger) + ctx.Err = service.DeleteEmailService(ctx.Logger) } diff --git a/pkg/microservice/systemconfig/core/email/handler/router.go b/pkg/microservice/systemconfig/core/email/handler/router.go index abeebd16..9f844c7a 100644 --- a/pkg/microservice/systemconfig/core/email/handler/router.go +++ b/pkg/microservice/systemconfig/core/email/handler/router.go @@ -1 +1,22 @@ package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + emails := router.Group("emails") + { + emails.GET("/host", GetEmailHost) + emails.POST("/host", CreateEmailHost) + emails.PATCH("/host", UpdateEmailHost) + emails.DELETE("/host", DeleteEmailHost) + + emails.GET("/service", GetEmailService) + emails.POST("/service", CreateEmailService) + emails.PATCH("/service", UpdateEmailService) + emails.DELETE("/service", DeleteEmailService) + } +} diff --git a/pkg/microservice/systemconfig/core/email/models/emailhost.go b/pkg/microservice/systemconfig/core/email/models/emailhost.go index 2640e7f9..4274ef57 100644 --- a/pkg/microservice/systemconfig/core/email/models/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/models/emailhost.go @@ -1 +1,17 @@ package models + +type EmailHost struct { + ID int `bson:"id" json:"id"` + Name string `bson:"name" json:"name"` + Port int `bson:"port" json:"port"` + Username string `bson:"username" json:"username"` + Password string `bson:"password" json:"password"` + IsTLS bool `bson:"is_tls" json:"isTLS"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` +} + +func (EmailHost) TableName() string { + return "email_host" +} diff --git a/pkg/microservice/systemconfig/core/email/models/emailservice.go b/pkg/microservice/systemconfig/core/email/models/emailservice.go index 44e61a39..f8199cb8 100644 --- a/pkg/microservice/systemconfig/core/email/models/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/models/emailservice.go @@ -1,14 +1,14 @@ package models type EmailService struct { - ID int `json:"id" bson:"id"` - Name string `json:"name" bson:"name"` - Address string `json:"address" bson:"address"` + ID int `json:"id" bson:"id"` + Name string `json:"name" bson:"name"` + Address string `json:"address" bson:"address"` DisplayName string `json:"display_name" bson:"display_name"` - Theme string `json:"theme" bson:"theme"` - CreatedAt int64 `json:"created_at" bson:"created_at"` - UpdatedAt int64 `json:"updated_at" bson:"updated_at"` - DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` + Theme string `json:"theme" bson:"theme"` + CreatedAt int64 `json:"created_at" bson:"created_at"` + UpdatedAt int64 `json:"updated_at" bson:"updated_at"` + DeletedAt int64 `json:"deleted_at" bson:"deleted_at"` } func (EmailService) TableName() string { diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index e4f9ea5b..3e4b4c40 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -3,7 +3,7 @@ package service import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" ) diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go index b0caf1fe..282f4cb1 100644 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ b/pkg/microservice/systemconfig/core/handler/router.go @@ -18,9 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/handler" - handler2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/handler" ) type Router struct{} @@ -38,28 +35,4 @@ func (*Router) Inject(router *gin.RouterGroup) { { features.GET("/:name", GetFeature) } - emails := router.Group("emails") - { - emails.GET("/host", handler.GetEmailHost) - emails.POST("/host", handler.CreateEmailHost) - emails.PATCH("/host", handler.UpdateEmailHost) - emails.DELETE("/host", handler.DeleteEmailHost) - - emails.GET("/service", handler.GetEmailService) - emails.POST("/service", handler.CreateEmailService) - emails.PATCH("/service", handler.UpdateEmailService) - emails.DELETE("/service", handler.DeleteEmailService) - } - - jira := router.Group("jira") - { - jira.GET("", handler2.GetJira) - jira.POST("", handler2.CreateJira) - jira.PATCH("", handler2.UpdateJira) - jira.DELETE("", handler2.DeleteJira) - } - codehost := router.Group("codehost") - { - codehost.POST("", ListCodehost) - } } diff --git a/pkg/microservice/systemconfig/core/jira/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go index 7a0074d4..f084c7a4 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -3,7 +3,7 @@ package handler import ( "github.com/gin-gonic/gin" - service2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/service" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/service" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -11,13 +11,13 @@ import ( func DeleteJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service2.DeleteJira(ctx.Logger) + ctx.Err = service.DeleteJira(ctx.Logger) } func GetJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service2.GeJira(ctx.Logger) + ctx.Resp, ctx.Err = service.GeJira(ctx.Logger) } func CreateJira(c *gin.Context) { @@ -28,7 +28,7 @@ func CreateJira(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service2.CreateJira(req, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateJira(req, ctx.Logger) } func UpdateJira(c *gin.Context) { @@ -39,5 +39,5 @@ func UpdateJira(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service2.UpdateJira(req, ctx.Logger) + ctx.Resp, ctx.Err = service.UpdateJira(req, ctx.Logger) } diff --git a/pkg/microservice/systemconfig/core/jira/handler/router.go b/pkg/microservice/systemconfig/core/jira/handler/router.go index abeebd16..b302096f 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/router.go +++ b/pkg/microservice/systemconfig/core/jira/handler/router.go @@ -1 +1,18 @@ package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + jira := router.Group("jira") + { + jira.GET("", GetJira) + jira.POST("", CreateJira) + jira.PATCH("", UpdateJira) + jira.DELETE("", DeleteJira) + } + +} diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index e9a57c4d..f969cbef 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -8,7 +8,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) diff --git a/pkg/microservice/systemconfig/server/rest/router.go b/pkg/microservice/systemconfig/server/rest/router.go index c043ab8e..4cd31ed5 100644 --- a/pkg/microservice/systemconfig/server/rest/router.go +++ b/pkg/microservice/systemconfig/server/rest/router.go @@ -19,12 +19,16 @@ package rest import ( "github.com/gin-gonic/gin" + emailHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/handler" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/handler" + jiraHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/handler" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { for _, r := range []injector{ new(handler.Router), + new(emailHandler.Router), + new(jiraHandler.Router), } { r.Inject(router.Group("/api/v1")) } -- Gitee From d97428d6976d67fc1480d92d885e95bc6b8dcf9e Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 21:20:46 +0800 Subject: [PATCH 0506/2647] json Signed-off-by: mouuii --- pkg/microservice/systemconfig/server/rest/router.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/systemconfig/server/rest/router.go b/pkg/microservice/systemconfig/server/rest/router.go index 4cd31ed5..ca654736 100644 --- a/pkg/microservice/systemconfig/server/rest/router.go +++ b/pkg/microservice/systemconfig/server/rest/router.go @@ -19,6 +19,7 @@ package rest import ( "github.com/gin-gonic/gin" + codehosthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/code/handler" emailHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/handler" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/handler" jiraHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/handler" @@ -29,6 +30,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { new(handler.Router), new(emailHandler.Router), new(jiraHandler.Router), + new(codehosthandler.Router), } { r.Inject(router.Group("/api/v1")) } -- Gitee From 183bc8cb3e3093f23ac3fc7abcbdb333e4bcd1a0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 21:48:46 +0800 Subject: [PATCH 0507/2647] json Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 2 +- .../core/{ => codehost}/repository/models/codehost.go | 0 .../core/{ => codehost}/repository/mongo/codehost.go | 2 +- .../systemconfig/core/codehost/service/codehost.go | 4 ++-- pkg/microservice/systemconfig/core/email/handler/email.go | 2 +- .../core/email/{ => repository}/models/emailhost.go | 0 .../core/email/{ => repository}/models/emailservice.go | 0 .../systemconfig/core/{ => email}/repository/mongo/jira.go | 2 +- pkg/microservice/systemconfig/core/email/service/email.go | 2 +- pkg/microservice/systemconfig/core/jira/handler/jira.go | 2 +- .../systemconfig/core/{ => jira}/repository/models/jira.go | 0 pkg/microservice/systemconfig/core/jira/service/jira.go | 4 ++-- pkg/microservice/systemconfig/core/repository/mongo/email.go | 2 +- 13 files changed, 11 insertions(+), 11 deletions(-) rename pkg/microservice/systemconfig/core/{ => codehost}/repository/models/codehost.go (100%) rename pkg/microservice/systemconfig/core/{ => codehost}/repository/mongo/codehost.go (98%) rename pkg/microservice/systemconfig/core/email/{ => repository}/models/emailhost.go (100%) rename pkg/microservice/systemconfig/core/email/{ => repository}/models/emailservice.go (100%) rename pkg/microservice/systemconfig/core/{ => email}/repository/mongo/jira.go (98%) rename pkg/microservice/systemconfig/core/{ => jira}/repository/models/jira.go (100%) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 96f34b9d..8cdc2ad4 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -3,8 +3,8 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/systemconfig/core/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go similarity index 100% rename from pkg/microservice/systemconfig/core/repository/models/codehost.go rename to pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go diff --git a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongo/codehost.go similarity index 98% rename from pkg/microservice/systemconfig/core/repository/mongo/codehost.go rename to pkg/microservice/systemconfig/core/codehost/repository/mongo/codehost.go index f589f747..15d62942 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongo/codehost.go @@ -8,7 +8,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index b5f2febe..5b0b3e28 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -3,8 +3,8 @@ package service import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongo" ) func CreateCodehost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index 1aec123f..2dea6904 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -3,7 +3,7 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/systemconfig/core/email/models/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go similarity index 100% rename from pkg/microservice/systemconfig/core/email/models/emailhost.go rename to pkg/microservice/systemconfig/core/email/repository/models/emailhost.go diff --git a/pkg/microservice/systemconfig/core/email/models/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go similarity index 100% rename from pkg/microservice/systemconfig/core/email/models/emailservice.go rename to pkg/microservice/systemconfig/core/email/repository/models/emailservice.go diff --git a/pkg/microservice/systemconfig/core/repository/mongo/jira.go b/pkg/microservice/systemconfig/core/email/repository/mongo/jira.go similarity index 98% rename from pkg/microservice/systemconfig/core/repository/mongo/jira.go rename to pkg/microservice/systemconfig/core/email/repository/mongo/jira.go index ef1ccf4d..e49a5fbd 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/jira.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongo/jira.go @@ -8,7 +8,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 3e4b4c40..9395fa3e 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -3,7 +3,7 @@ package service import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" ) diff --git a/pkg/microservice/systemconfig/core/jira/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go index f084c7a4..4e8b6493 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -3,8 +3,8 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/service" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/systemconfig/core/repository/models/jira.go b/pkg/microservice/systemconfig/core/jira/repository/models/jira.go similarity index 100% rename from pkg/microservice/systemconfig/core/repository/models/jira.go rename to pkg/microservice/systemconfig/core/jira/repository/models/jira.go diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 9d3e2e11..72cd90eb 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -3,8 +3,8 @@ package service import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" ) func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/repository/mongo/email.go index f969cbef..77b17a91 100644 --- a/pkg/microservice/systemconfig/core/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/repository/mongo/email.go @@ -8,7 +8,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -- Gitee From cf372453482123dd4116cf6f70c35ccbbfce67e9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 21:52:16 +0800 Subject: [PATCH 0508/2647] refactor Signed-off-by: mouuii --- .../core/{ => email}/repository/mongo/email.go | 0 .../systemconfig/core/email/service/email.go | 18 +++++++++--------- .../{email => jira}/repository/mongo/jira.go | 0 .../systemconfig/core/jira/service/jira.go | 10 +++++----- pkg/microservice/systemconfig/core/service.go | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) rename pkg/microservice/systemconfig/core/{ => email}/repository/mongo/email.go (100%) rename pkg/microservice/systemconfig/core/{email => jira}/repository/mongo/jira.go (100%) diff --git a/pkg/microservice/systemconfig/core/repository/mongo/email.go b/pkg/microservice/systemconfig/core/email/repository/mongo/email.go similarity index 100% rename from pkg/microservice/systemconfig/core/repository/mongo/email.go rename to pkg/microservice/systemconfig/core/email/repository/mongo/email.go diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 9395fa3e..1321afad 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -4,37 +4,37 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" + mongo2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" ) func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo.NewEmailHostColl().Find() + return mongo2.NewEmailHostColl().Find() } func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo.NewEmailHostColl().Add(emailHost) + return mongo2.NewEmailHostColl().Add(emailHost) } func UpdateEmailHost(host *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo.NewEmailHostColl().Update(host) + return mongo2.NewEmailHostColl().Update(host) } func DeleteEmailHost(_ *zap.SugaredLogger) error { - return mongo.NewEmailHostColl().Delete() + return mongo2.NewEmailHostColl().Delete() } func GetEmailService(_ *zap.SugaredLogger) (*models.EmailService, error) { - return mongo.NewEmailServiceColl().GetEmailService() + return mongo2.NewEmailServiceColl().GetEmailService() } func CreateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { - return mongo.NewEmailServiceColl().AddEmailService(service) + return mongo2.NewEmailServiceColl().AddEmailService(service) } func UpdateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { - return mongo.NewEmailServiceColl().UpdateEmailService(service) + return mongo2.NewEmailServiceColl().UpdateEmailService(service) } func DeleteEmailService(_ *zap.SugaredLogger) error { - return mongo.NewEmailServiceColl().DeleteEmailService() + return mongo2.NewEmailServiceColl().DeleteEmailService() } diff --git a/pkg/microservice/systemconfig/core/email/repository/mongo/jira.go b/pkg/microservice/systemconfig/core/jira/repository/mongo/jira.go similarity index 100% rename from pkg/microservice/systemconfig/core/email/repository/mongo/jira.go rename to pkg/microservice/systemconfig/core/jira/repository/mongo/jira.go diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 72cd90eb..41a2c240 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -3,22 +3,22 @@ package service import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" + mongo2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/mongo" ) func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { - return mongo.NewJiraColl().GetJira() + return mongo2.NewJiraColl().GetJira() } func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { - return mongo.NewJiraColl().AddJira(jira) + return mongo2.NewJiraColl().AddJira(jira) } func UpdateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { - return mongo.NewJiraColl().UpdateJira(jira) + return mongo2.NewJiraColl().UpdateJira(jira) } func DeleteJira(_ *zap.SugaredLogger) error { - return mongo.NewJiraColl().DeleteJira() + return mongo2.NewJiraColl().DeleteJira() } diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index e46d9786..13425178 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -24,7 +24,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/mongo" + mongo2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" @@ -66,7 +66,7 @@ func initDatabase() { defer idxCancel() var wg sync.WaitGroup for _, r := range []indexer{ - mongo.NewEmailHostColl(), + mongo2.NewEmailHostColl(), } { wg.Add(1) -- Gitee From c9800f6e42574974bf2a998b389e7c30e5b324e5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 21:58:50 +0800 Subject: [PATCH 0509/2647] refactor Signed-off-by: mouuii --- .../core/email/repository/mongo/emailhost.go | 91 +++++++++++++++++++ .../mongo/{email.go => emailservice.go} | 77 ---------------- 2 files changed, 91 insertions(+), 77 deletions(-) create mode 100644 pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go rename pkg/microservice/systemconfig/core/email/repository/mongo/{email.go => emailservice.go} (55%) diff --git a/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go new file mode 100644 index 00000000..d2ca4f3b --- /dev/null +++ b/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go @@ -0,0 +1,91 @@ +package mongo + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type EmailHostColl struct { + *mongo.Collection + + coll string +} + +func NewEmailHostColl() *EmailHostColl { + name := models.EmailHost{}.TableName() + coll := &EmailHostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *EmailHostColl) GetCollectionName() string { + return c.coll +} +func (c *EmailHostColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *EmailHostColl) Find() (*models.EmailHost, error) { + emailHost := new(models.EmailHost) + query := bson.M{"deleted_at": 0} + + ctx := context.Background() + + err := c.Collection.FindOne(ctx, query).Decode(emailHost) + if err != nil { + return nil, nil + } + emailHost.Password = "***" + return emailHost, nil +} + +func (c *EmailHostColl) Update(emailHost *models.EmailHost) (*models.EmailHost, error) { + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "name": emailHost.Name, + "port": emailHost.Port, + "username": emailHost.Username, + "password": emailHost.Password, + "is_tls": emailHost.IsTLS, + "updated_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository Update EmailHostColl err : %v", err) + return nil, err + } + return emailHost, nil +} + +func (c *EmailHostColl) Delete() error { + query := bson.M{"deleted_at": 0} + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository Delete EmailHostColl err : %v", err) + return err + } + return nil +} + +func (c *EmailHostColl) Add(emailHost *models.EmailHost) (*models.EmailHost, error) { + + _, err := c.Collection.InsertOne(context.TODO(), emailHost) + if err != nil { + log.Error("repository AddEmailHost err : %v", err) + return nil, err + } + return emailHost, nil +} diff --git a/pkg/microservice/systemconfig/core/email/repository/mongo/email.go b/pkg/microservice/systemconfig/core/email/repository/mongo/emailservice.go similarity index 55% rename from pkg/microservice/systemconfig/core/email/repository/mongo/email.go rename to pkg/microservice/systemconfig/core/email/repository/mongo/emailservice.go index 77b17a91..b8f366a7 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongo/email.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongo/emailservice.go @@ -13,12 +13,6 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -type EmailHostColl struct { - *mongo.Collection - - coll string -} - type EmailServiceColl struct { *mongo.Collection @@ -32,13 +26,6 @@ func NewEmailServiceColl() *EmailServiceColl { return coll } -func NewEmailHostColl() *EmailHostColl { - name := models.EmailHost{}.TableName() - coll := &EmailHostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} - - return coll -} - func (c *EmailServiceColl) GetCollectionName() string { return c.coll } @@ -46,70 +33,6 @@ func (c *EmailServiceColl) EnsureIndex(ctx context.Context) error { return nil } -func (c *EmailHostColl) GetCollectionName() string { - return c.coll -} -func (c *EmailHostColl) EnsureIndex(ctx context.Context) error { - return nil -} - -func (c *EmailHostColl) Find() (*models.EmailHost, error) { - emailHost := new(models.EmailHost) - query := bson.M{"deleted_at": 0} - - ctx := context.Background() - - err := c.Collection.FindOne(ctx, query).Decode(emailHost) - if err != nil { - return nil, nil - } - emailHost.Password = "***" - return emailHost, nil -} - -func (c *EmailHostColl) Update(emailHost *models.EmailHost) (*models.EmailHost, error) { - query := bson.M{"deleted_at": 0} - change := bson.M{"$set": bson.M{ - "name": emailHost.Name, - "port": emailHost.Port, - "username": emailHost.Username, - "password": emailHost.Password, - "is_tls": emailHost.IsTLS, - "updated_at": time.Now().Unix(), - }} - - _, err := c.Collection.UpdateOne(context.TODO(), query, change) - if err != nil { - log.Error("repository Update EmailHostColl err : %v", err) - return nil, err - } - return emailHost, nil -} - -func (c *EmailHostColl) Delete() error { - query := bson.M{"deleted_at": 0} - change := bson.M{"$set": bson.M{ - "deleted_at": time.Now().Unix(), - }} - - _, err := c.Collection.UpdateOne(context.TODO(), query, change) - if err != nil { - log.Error("repository Delete EmailHostColl err : %v", err) - return err - } - return nil -} - -func (c *EmailHostColl) Add(emailHost *models.EmailHost) (*models.EmailHost, error) { - - _, err := c.Collection.InsertOne(context.TODO(), emailHost) - if err != nil { - log.Error("repository AddEmailHost err : %v", err) - return nil, err - } - return emailHost, nil -} - func (c *EmailServiceColl) AddEmailService(iEmailService *models.EmailService) (*models.EmailService, error) { _, err := c.Collection.InsertOne(context.TODO(), iEmailService) -- Gitee From df84d2722257a4d861363186e5c5dd645699977e Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 4 Nov 2021 22:10:10 +0800 Subject: [PATCH 0510/2647] refactor Signed-off-by: mouuii --- .../systemconfig/core/jira/service/jira.go | 10 +++++----- pkg/microservice/systemconfig/core/service.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 41a2c240..59e75090 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -4,21 +4,21 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" - mongo2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/mongo" ) func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { - return mongo2.NewJiraColl().GetJira() + return mongo.NewJiraColl().GetJira() } func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { - return mongo2.NewJiraColl().AddJira(jira) + return mongo.NewJiraColl().AddJira(jira) } func UpdateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { - return mongo2.NewJiraColl().UpdateJira(jira) + return mongo.NewJiraColl().UpdateJira(jira) } func DeleteJira(_ *zap.SugaredLogger) error { - return mongo2.NewJiraColl().DeleteJira() + return mongo.NewJiraColl().DeleteJira() } diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index 13425178..94aeadd7 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -24,7 +24,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - mongo2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" @@ -66,7 +66,7 @@ func initDatabase() { defer idxCancel() var wg sync.WaitGroup for _, r := range []indexer{ - mongo2.NewEmailHostColl(), + mongo.NewEmailHostColl(), } { wg.Add(1) -- Gitee From 9cc5f78a3979089da4695345099f28136833c758 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 5 Nov 2021 10:49:20 +0800 Subject: [PATCH 0511/2647] refactor Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/email/handler/email.go | 6 ++++++ .../systemconfig/core/email/handler/router.go | 2 ++ .../systemconfig/core/email/repository/mongo/emailhost.go | 1 - pkg/microservice/systemconfig/core/email/service/email.go | 8 ++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index 2dea6904..e7c911e7 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -14,6 +14,12 @@ func GetEmailHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) } +func InternalGetEmailHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.InternalGetEmailHost(ctx.Logger) +} + func CreateEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/email/handler/router.go b/pkg/microservice/systemconfig/core/email/handler/router.go index 9f844c7a..f8e2a8ec 100644 --- a/pkg/microservice/systemconfig/core/email/handler/router.go +++ b/pkg/microservice/systemconfig/core/email/handler/router.go @@ -14,6 +14,8 @@ func (*Router) Inject(router *gin.RouterGroup) { emails.PATCH("/host", UpdateEmailHost) emails.DELETE("/host", DeleteEmailHost) + emails.GET("/internal/host", InternalGetEmailHost) + emails.GET("/service", GetEmailService) emails.POST("/service", CreateEmailService) emails.PATCH("/service", UpdateEmailService) diff --git a/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go index d2ca4f3b..e5caea07 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go @@ -43,7 +43,6 @@ func (c *EmailHostColl) Find() (*models.EmailHost, error) { if err != nil { return nil, nil } - emailHost.Password = "***" return emailHost, nil } diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 1321afad..333f7af0 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -11,6 +11,14 @@ func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { return mongo2.NewEmailHostColl().Find() } +func InternalGetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { + host, err := mongo2.NewEmailHostColl().Find() + if err != nil { + host.Password = "***" + } + return host, err +} + func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { return mongo2.NewEmailHostColl().Add(emailHost) } -- Gitee From 3371a7a5bc4b04b4f45ad3dc95d1a439aed7b4fc Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 5 Nov 2021 11:03:42 +0800 Subject: [PATCH 0512/2647] refactor Signed-off-by: mouuii --- .../systemconfig/core/email/service/email.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 333f7af0..89e1c997 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -8,17 +8,17 @@ import ( ) func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo2.NewEmailHostColl().Find() -} - -func InternalGetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { host, err := mongo2.NewEmailHostColl().Find() - if err != nil { + if host != nil { host.Password = "***" } return host, err } +func InternalGetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { + return mongo2.NewEmailHostColl().Find() +} + func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { return mongo2.NewEmailHostColl().Add(emailHost) } -- Gitee From 1914b916bd232a287f3505a156f6f6785e3611b5 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Fri, 5 Nov 2021 11:19:50 +0800 Subject: [PATCH 0513/2647] create project with admins (#539) Signed-off-by: lou --- .../repository/models/template/product.go | 4 -- .../repository/mongodb/template/product.go | 3 -- .../picket/core/filter/handler/project.go | 9 +--- .../picket/core/filter/service/project.go | 43 ++++++++++++++----- .../core/repository/mongodb/role_binding.go | 17 +++++++- .../policy/core/service/role_binding.go | 26 ++++++++++- pkg/setting/consts.go | 1 + 7 files changed, 76 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index eadcc5b9..64940bca 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -29,8 +29,6 @@ type Product struct { ProductName string `bson:"product_name" json:"product_name"` Revision int64 `bson:"revision" json:"revision"` CreateTime int64 `bson:"create_time" json:"create_time"` - Teams []*Team `bson:"teams" json:"teams"` - Team string `bson:"team" json:"team"` UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` Enabled bool `bson:"enabled" json:"enabled"` @@ -41,8 +39,6 @@ type Product struct { Vars []*RenderKV `bson:"vars" json:"vars"` EnvVars []*EnvRenderKV `bson:"-" json:"env_vars,omitempty"` ChartInfos []*RenderChart `bson:"-" json:"chart_infos,omitempty"` - UserIDs []string `bson:"user_ids" json:"user_ids"` - TeamID int `bson:"team_id" json:"team_id"` Description string `bson:"description,omitempty" json:"desc,omitempty"` ProductFeature *ProductFeature `bson:"product_feature,omitempty" json:"product_feature,omitempty"` ImageSearchingRules []*ImageSearchingRule `bson:"image_searching_rules,omitempty" json:"image_searching_rules,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 8f76189c..ca235b6b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -246,11 +246,8 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { "services": args.Services, "update_time": time.Now().Unix(), "update_by": args.UpdateBy, - "teams": args.Teams, "enabled": args.Enabled, "description": args.Description, - "user_ids": args.UserIDs, - "team_id": args.TeamID, "timeout": args.Timeout, "shared_services": args.SharedServices, "image_searching_rules": args.ImageSearchingRules, diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index 5e8097f0..c5260054 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -16,23 +16,18 @@ func ListProjects(c *gin.Context) { ctx.Resp, ctx.Err = service.ListProjects(c.Request.Header, c.Request.URL.Query(), ctx.Logger) } -type CreateProjectReq struct { - Public bool `json:"public"` - ProductName string `json:"product_name"` -} - func CreateProject(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(CreateProjectReq) + args := &service.CreateProjectArgs{} if err := c.ShouldBindBodyWith(args, binding.JSON); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err).AddDesc("invalid CreateProjectReq") return } body := getReqBody(c) - ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, c.Request.URL.Query(), args.ProductName, args.Public, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateProject(c.Request.Header, body, c.Request.URL.Query(), args, ctx.Logger) } func getReqBody(c *gin.Context) (body []byte) { diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index b76650b7..1b0d45c6 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -15,30 +15,53 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +type CreateProjectArgs struct { + Public bool `json:"public"` + ProductName string `json:"product_name"` + Admins []string `json:"admins"` +} + type allowedProjectsData struct { Result []string `json:"result"` } -func CreateProject(header http.Header, body []byte, qs url.Values, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { - // role binding - roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) - if public { - if err := policy.NewDefault().CreateOrUpdateRoleBinding(projectName, &policy.RoleBinding{ +func CreateProject(header http.Header, body []byte, qs url.Values, args *CreateProjectArgs, logger *zap.SugaredLogger) ([]byte, error) { + var rbs []*policy.RoleBinding + for _, uid := range args.Admins { + roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, uid, setting.Admin, args.ProductName) + rbs = append(rbs, &policy.RoleBinding{ + Name: roleBindingName, + UID: uid, + Role: string(setting.Admin), + Public: true, + }) + } + + if args.Public { + roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, args.ProductName) + rbs = append(rbs, &policy.RoleBinding{ Name: roleBindingName, UID: "*", Role: string(setting.ReadOnly), Public: true, - }); err != nil { - logger.Errorf("Failed to create rolebinding %s, err: %s", roleBindingName, err) + }) + } + + policyClient := policy.NewDefault() + for _, rb := range rbs { + if err := policyClient.CreateOrUpdateRoleBinding(args.ProductName, rb); err != nil { + logger.Errorf("Failed to create rolebinding %s, err: %s", rb.Name, err) return nil, err } } res, err := aslan.New().CreateProject(header, qs, body) if err != nil { - logger.Errorf("Failed to create project %s, err: %s", projectName, err) - if err1 := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err1 != nil { - logger.Errorf("Failed to delete role binding, err: %s", err1) + logger.Errorf("Failed to create project %s, err: %s", args.ProductName, err) + for _, rb := range rbs { + if err1 := policyClient.DeleteRoleBinding(rb.Name, args.ProductName); err1 != nil { + logger.Errorf("Failed to create rolebinding %s, err: %s", rb.Name, err1) + } } return nil, err diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 9174fdeb..b3fae0bb 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -29,6 +29,10 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) +type ListOptions struct { + RoleName, RoleNamespace string +} + type RoleBindingColl struct { *mongo.Collection @@ -61,11 +65,20 @@ func (c *RoleBindingColl) EnsureIndex(ctx context.Context) error { return err } -func (c *RoleBindingColl) List() ([]*models.RoleBinding, error) { +func (c *RoleBindingColl) List(opts ...*ListOptions) ([]*models.RoleBinding, error) { var res []*models.RoleBinding ctx := context.Background() - cursor, err := c.Collection.Find(ctx, bson.M{}) + query := bson.M{} + if len(opts) > 0 { + opt := opts[0] + if opt.RoleName != "" { + query["role_ref.name"] = opt.RoleName + query["role_ref.namespace"] = opt.RoleNamespace + } + } + + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index c3509eb4..44606ea1 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -77,7 +77,31 @@ func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, err Name: v.Name, Role: v.RoleRef.Name, UID: v.Subjects[0].UID, - Public: v.Namespace == "", + Public: v.RoleRef.Namespace == "", + }) + } + + return roleBindings, nil +} + +func ListRoleBindingsByRole(ns, roleName string, publicRole bool, _ *zap.SugaredLogger) ([]*RoleBinding, error) { + var roleBindings []*RoleBinding + + roleNamespace := ns + if publicRole { + roleNamespace = "" + } + modelRoleBindings, err := mongodb.NewRoleBindingColl().List(&mongodb.ListOptions{RoleName: roleName, RoleNamespace: roleNamespace}) + if err != nil { + return nil, err + } + + for _, v := range modelRoleBindings { + roleBindings = append(roleBindings, &RoleBinding{ + Name: v.Name, + Role: v.RoleRef.Name, + UID: v.Subjects[0].UID, + Public: v.RoleRef.Namespace == "", }) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 8603a63b..0382b09a 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -540,6 +540,7 @@ type RoleType string const ( Contributor RoleType = "contributor" ReadOnly RoleType = "readonly" + Admin RoleType = "admin" RoleBindingNameFmt string = "user:%s,role:%s,project:%s" ) -- Gitee From 2ee51fb15673b544c9e20ca5431bd67f0b13ad6c Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Fri, 5 Nov 2021 12:46:19 +0800 Subject: [PATCH 0514/2647] refactor (#540) Signed-off-by: mouuii Co-authored-by: mouuii --- .../repository/{mongo => mongodb}/codehost.go | 2 +- .../core/codehost/service/codehost.go | 6 +++--- .../{mongo => mongodb}/emailhost.go | 2 +- .../{mongo => mongodb}/emailservice.go | 2 +- .../systemconfig/core/email/service/email.go | 20 +++++++++---------- .../repository/{mongo => mongodb}/jira.go | 2 +- .../systemconfig/core/jira/service/jira.go | 10 +++++----- pkg/microservice/systemconfig/core/service.go | 4 ++-- 8 files changed, 24 insertions(+), 24 deletions(-) rename pkg/microservice/systemconfig/core/codehost/repository/{mongo => mongodb}/codehost.go (99%) rename pkg/microservice/systemconfig/core/email/repository/{mongo => mongodb}/emailhost.go (99%) rename pkg/microservice/systemconfig/core/email/repository/{mongo => mongodb}/emailservice.go (99%) rename pkg/microservice/systemconfig/core/jira/repository/{mongo => mongodb}/jira.go (99%) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongo/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go similarity index 99% rename from pkg/microservice/systemconfig/core/codehost/repository/mongo/codehost.go rename to pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 15d62942..cd592593 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongo/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -1,4 +1,4 @@ -package mongo +package mongodb import ( "context" diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 5b0b3e28..16f740c0 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -4,13 +4,13 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" ) func CreateCodehost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { - return mongo.NewCodehostColl().AddCodeHost(codehost) + return mongodb.NewCodehostColl().AddCodeHost(codehost) } func FindCodehost(_ *zap.SugaredLogger) ([]*models.CodeHost, error) { - return mongo.NewCodehostColl().FindCodeHosts() + return mongodb.NewCodehostColl().FindCodeHosts() } diff --git a/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go similarity index 99% rename from pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go rename to pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go index e5caea07..666b00e0 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongo/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go @@ -1,4 +1,4 @@ -package mongo +package mongodb import ( "context" diff --git a/pkg/microservice/systemconfig/core/email/repository/mongo/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go similarity index 99% rename from pkg/microservice/systemconfig/core/email/repository/mongo/emailservice.go rename to pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go index b8f366a7..14395a20 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongo/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go @@ -1,4 +1,4 @@ -package mongo +package mongodb import ( "context" diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 89e1c997..75d6be87 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -4,11 +4,11 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" - mongo2 "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongodb" ) func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { - host, err := mongo2.NewEmailHostColl().Find() + host, err := mongodb.NewEmailHostColl().Find() if host != nil { host.Password = "***" } @@ -16,33 +16,33 @@ func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { } func InternalGetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo2.NewEmailHostColl().Find() + return mongodb.NewEmailHostColl().Find() } func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo2.NewEmailHostColl().Add(emailHost) + return mongodb.NewEmailHostColl().Add(emailHost) } func UpdateEmailHost(host *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongo2.NewEmailHostColl().Update(host) + return mongodb.NewEmailHostColl().Update(host) } func DeleteEmailHost(_ *zap.SugaredLogger) error { - return mongo2.NewEmailHostColl().Delete() + return mongodb.NewEmailHostColl().Delete() } func GetEmailService(_ *zap.SugaredLogger) (*models.EmailService, error) { - return mongo2.NewEmailServiceColl().GetEmailService() + return mongodb.NewEmailServiceColl().GetEmailService() } func CreateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { - return mongo2.NewEmailServiceColl().AddEmailService(service) + return mongodb.NewEmailServiceColl().AddEmailService(service) } func UpdateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { - return mongo2.NewEmailServiceColl().UpdateEmailService(service) + return mongodb.NewEmailServiceColl().UpdateEmailService(service) } func DeleteEmailService(_ *zap.SugaredLogger) error { - return mongo2.NewEmailServiceColl().DeleteEmailService() + return mongodb.NewEmailServiceColl().DeleteEmailService() } diff --git a/pkg/microservice/systemconfig/core/jira/repository/mongo/jira.go b/pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go similarity index 99% rename from pkg/microservice/systemconfig/core/jira/repository/mongo/jira.go rename to pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go index e49a5fbd..acefdbd4 100644 --- a/pkg/microservice/systemconfig/core/jira/repository/mongo/jira.go +++ b/pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go @@ -1,4 +1,4 @@ -package mongo +package mongodb import ( "context" diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 59e75090..5428837e 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -4,21 +4,21 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/mongodb" ) func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { - return mongo.NewJiraColl().GetJira() + return mongodb.NewJiraColl().GetJira() } func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { - return mongo.NewJiraColl().AddJira(jira) + return mongodb.NewJiraColl().AddJira(jira) } func UpdateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { - return mongo.NewJiraColl().UpdateJira(jira) + return mongodb.NewJiraColl().UpdateJira(jira) } func DeleteJira(_ *zap.SugaredLogger) error { - return mongo.NewJiraColl().DeleteJira() + return mongodb.NewJiraColl().DeleteJira() } diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index 94aeadd7..c0aae5ba 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -24,7 +24,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongo" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" @@ -66,7 +66,7 @@ func initDatabase() { defer idxCancel() var wg sync.WaitGroup for _, r := range []indexer{ - mongo.NewEmailHostColl(), + mongodb.NewEmailHostColl(), } { wg.Add(1) -- Gitee From e823d32cfc3d6db09e81d958b4d631411d648819 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Fri, 5 Nov 2021 13:46:44 +0800 Subject: [PATCH 0515/2647] add new public and admin apis (#541) Signed-off-by: lou --- .../policy/core/service/bundle/exemption_urls.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 6f68587d..cf86b6ab 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -42,7 +42,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET", "POST"}, - Endpoints: []string{"login"}, + Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve"}, }, { Methods: []string{"*"}, @@ -56,10 +56,6 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"", "signin", "setup", "loading", "static/**", "v1/**", "mobile/**", "productpipelines/**"}, }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/directory/check"}, - }, { Methods: []string{"GET"}, Endpoints: []string{"api/directory/codehostss/?*/auth", "api/directory/codehosts/callback"}, @@ -201,7 +197,7 @@ var systemAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/proxy/config"}, }, { - Methods: []string{"GET"}, + Methods: []string{"GET", "DELETE"}, Endpoints: []string{"api/v1/users/?*"}, }, { -- Gitee From 618064e15fcc81faf7031f237fc34811cf771d95 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 5 Nov 2021 13:47:54 +0800 Subject: [PATCH 0516/2647] update version config (#542) * update version config Signed-off-by: allenshen * fix config error Signed-off-by: allenshen --- pkg/cli/upgradeassistant/internal/upgradepath/versions.go | 2 ++ version/version.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index b57300a7..562dfd39 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -28,6 +28,7 @@ const ( V131 V140 V150 + V160 ) var versionMap versions = map[string]int{ @@ -35,6 +36,7 @@ var versionMap versions = map[string]int{ "1.3.1": V131, "1.4.0": V140, "1.5.0": V150, + "1.6.0": V160, } type versions map[string]int diff --git a/version/version.go b/version/version.go index c9539103..9d703572 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.5.0" + Version = "1.6.0" BuildNumber = "0" GitCommit = "" ) -- Gitee From ee26041ec138a4917a6aba48dede4e6f8eb75ca6 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 5 Nov 2021 14:15:24 +0800 Subject: [PATCH 0517/2647] User email (#543) * change get email from systemconfig and update route api Signed-off-by: panxunying <641770806@qq.com> * optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/config/config.go | 8 -------- pkg/microservice/user/core/handler/router.go | 10 ++++++++++ .../user/core/service/user/user.go | 16 ++++++++++----- pkg/microservice/user/server/rest/router.go | 9 --------- pkg/setting/consts.go | 20 +++++++++---------- .../client/systemconfig/systemconfig.go | 19 ++++++++++++++++++ 6 files changed, 49 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/user/config/config.go b/pkg/microservice/user/config/config.go index dcc74c8d..3ca77a21 100644 --- a/pkg/microservice/user/config/config.go +++ b/pkg/microservice/user/config/config.go @@ -56,11 +56,3 @@ func MysqlUserDB() string { func TokenExpiresAt() int { return viper.GetInt(setting.ENVTokenExpiresAt) } - -func NoReplyEmailAddress() string { - return viper.GetString(setting.ENVNoReplyEmailAddress) -} - -func NoReplyEmailPassword() string { - return viper.GetString(setting.ENVNoReplyEmailPassword) -} diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index fdf7fbf8..1b35af75 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -43,5 +43,15 @@ func (*Router) Inject(router *gin.RouterGroup) { users.POST("/users/search", user.ListUsers) users.POST("/users/ldap/:ldapId", user.SyncLdapUser) + + router.GET("login", login.Login) + + router.POST("login", login.LocalLogin) + + router.POST("signup", user.SignUp) + + router.GET("retrieve", user.Retrieve) + + router.POST("reset", user.Reset) } } diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 8bab6047..b9aab916 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -319,14 +319,20 @@ func Retrieve(account string, logger *zap.SugaredLogger) (*RetrieveResp, error) logger.Errorf("Retrieve renderEmailTemplate error, error msg:%s ", err) return nil, fmt.Errorf("Retrieve renderEmailTemplate error, error msg:%s ", err) } + systemConfigClient := systemconfig.New() + email, err := systemConfigClient.GetEmailHost() + if err != nil { + logger.Errorf("Retrieve GetEmailHost error, error msg:%s", err) + return nil, fmt.Errorf("Retrieve GetEmailHost error, error msg:%s ", err) + } err = mail.SendEmail(&mail.EmailParams{ - From: config.NoReplyEmailAddress(), + From: email.UserName, To: user.Email, Subject: "重置密码", - Host: config.FeiShuEmailHost, - UserName: config.NoReplyEmailAddress(), - Password: config.NoReplyEmailPassword(), - Port: 465, + Host: email.Name, + UserName: email.UserName, + Password: email.Password, + Port: email.Port, Body: body, }) if err != nil { diff --git a/pkg/microservice/user/server/rest/router.go b/pkg/microservice/user/server/rest/router.go index 1ec76974..d811f5ba 100644 --- a/pkg/microservice/user/server/rest/router.go +++ b/pkg/microservice/user/server/rest/router.go @@ -20,18 +20,9 @@ import ( "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/user/core/handler" - "github.com/koderover/zadig/pkg/microservice/user/core/handler/login" - "github.com/koderover/zadig/pkg/microservice/user/core/handler/user" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { - { - router.GET("login", login.Login) - router.POST("login", login.LocalLogin) - router.POST("signup", user.SignUp) - router.GET("retrieve", user.Retrieve) - router.POST("reset", user.Reset) - } for name, r := range map[string]injector{ "/api/v1": new(handler.Router), } { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0382b09a..26352978 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -104,17 +104,15 @@ const ( TestMode = "test" // user - ENVIssuerURL = "ISSUER_URL" - ENVClientID = "CLIENT_ID" - ENVClientSecret = "CLIENT_SECRET" - ENVRedirectURI = "REDIRECT_URI" - ENVSecretKey = "SECRET_KEY" - ENVMysqlUserDB = "MYSQL_USER_DB" - ENVScopes = "SCOPES" - ENVTokenExpiresAt = "TOKEN_EXPIRES_AT" - ENVNoReplyEmailAddress = "NOREPLY_EMAIL_ADDRESS" - ENVNoReplyEmailPassword = "NOREPLY_EMAIL_PASSWORD" - ENVUserPort = "USER_PORT" + ENVIssuerURL = "ISSUER_URL" + ENVClientID = "CLIENT_ID" + ENVClientSecret = "CLIENT_SECRET" + ENVRedirectURI = "REDIRECT_URI" + ENVSecretKey = "SECRET_KEY" + ENVMysqlUserDB = "MYSQL_USER_DB" + ENVScopes = "SCOPES" + ENVTokenExpiresAt = "TOKEN_EXPIRES_AT" + ENVUserPort = "USER_PORT" // initconfig ENVAdminEmail = "ADMIN_EMAIL" diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/systemconfig.go index caf958e3..818a0382 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/systemconfig.go @@ -13,6 +13,13 @@ type Connector struct { Config *ldap.Config `json:"config"` } +type Email struct { + Name string `json:"name"` + Port int `json:"port"` + UserName string `json:"username"` + Password string `json:"password"` +} + func (c *Client) GetLDAPConnector(id string) (*Connector, error) { url := "/connectors/" + id @@ -24,3 +31,15 @@ func (c *Client) GetLDAPConnector(id string) (*Connector, error) { return res, err } + +func (c *Client) GetEmailHost() (*Email, error) { + url := "/emails/internal/host/" + + res := &Email{} + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, err +} -- Gitee From 7b9a455e09fb70483252a4289516321caed26e8f Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 5 Nov 2021 14:43:59 +0800 Subject: [PATCH 0518/2647] add codehost Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 22 ++++++++++--- .../core/codehost/handler/router.go | 4 ++- .../codehost/repository/mongodb/codehost.go | 31 +++++++++++++++++++ .../core/codehost/service/codehost.go | 12 +++++-- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 8cdc2ad4..90f93654 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -1,6 +1,8 @@ package handler import ( + "strconv" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" @@ -8,7 +10,7 @@ import ( internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -func CreateCodehost(c *gin.Context) { +func CreateCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() rep := new(models.CodeHost) @@ -16,11 +18,23 @@ func CreateCodehost(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.CreateCodehost(rep, ctx.Logger) + ctx.Resp, ctx.Err = service.CreateCodeHost(rep, ctx.Logger) +} + +func ListCodeHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.FindCodeHost(ctx.Logger) } -func ListCodehost(c *gin.Context) { +func DeleteCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.FindCodehost(ctx.Logger) + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + ctx.Err = err + return + } + ctx.Err = service.DeleteCodeHost(id, ctx.Logger) } diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index 5db3c015..74487957 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -9,6 +9,8 @@ type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { codehost := router.Group("codehost") { - codehost.POST("", ListCodehost) + codehost.GET("", ListCodeHost) + codehost.DELETE("/:id", DeleteCodeHost) + codehost.POST("", CreateCodeHost) } } diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index cd592593..855df2c7 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -82,3 +82,34 @@ func (c *CodehostColl) FindCodeHosts() ([]*models.CodeHost, error) { } return codeHosts, nil } + +func (c *CodehostColl) DeleteCodeHostByID(ID int) error { + query := bson.M{"id": ID, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository update fail") + return err + } + return nil +} + +func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, error) { + query := bson.M{"id": host.ID, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "name": host.Name, + "type": host.Type, + "address": host.Address, + "namespace": host.Namespace, + "application_id": host.ApplicationId, + "client_secret": host.ClientSecret, + "region": host.Region, + "username": host.Username, + "password": host.Password, + "updated_at": time.Now().Unix(), + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + return host, err +} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 16f740c0..293e90bf 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -7,10 +7,18 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" ) -func CreateCodehost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { +func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().AddCodeHost(codehost) } -func FindCodehost(_ *zap.SugaredLogger) ([]*models.CodeHost, error) { +func FindCodeHost(_ *zap.SugaredLogger) ([]*models.CodeHost, error) { return mongodb.NewCodehostColl().FindCodeHosts() } + +func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { + return mongodb.NewCodehostColl().DeleteCodeHostByID(id) +} + +func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { + return mongodb.NewCodehostColl().UpdateCodeHost(host) +} -- Gitee From 9676de9fc58c1f8b04de501c0c830f5abfc7cbae Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 5 Nov 2021 15:08:44 +0800 Subject: [PATCH 0519/2647] add codehost Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 30 +++++++++++++++++++ .../core/codehost/handler/router.go | 2 ++ .../core/codehost/service/codehost.go | 4 +++ 3 files changed, 36 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 90f93654..b98565cc 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -38,3 +38,33 @@ func DeleteCodeHost(c *gin.Context) { } ctx.Err = service.DeleteCodeHost(id, ctx.Logger) } + +func GetCodeHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.GetCodeHost(id, ctx.Logger) +} + +func UpdateCodeHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + idParam := c.Param("id") + id, err := strconv.Atoi(idParam) + if err != nil { + ctx.Err = err + return + } + req := &models.CodeHost{} + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + req.ID = id + ctx.Resp, ctx.Err = service.UpdateCodeHost(req, ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index 74487957..e29fa97d 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -12,5 +12,7 @@ func (*Router) Inject(router *gin.RouterGroup) { codehost.GET("", ListCodeHost) codehost.DELETE("/:id", DeleteCodeHost) codehost.POST("", CreateCodeHost) + codehost.PATCH("/:id", UpdateCodeHost) + codehost.GET("/:id", GetCodeHost) } } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 293e90bf..0cc0c7c5 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -22,3 +22,7 @@ func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().UpdateCodeHost(host) } + +func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { + return mongodb.NewCodehostColl().GetCodeHostByID(id) +} -- Gitee From f4e57b4ece7ca6ffe74b6413221b06fb592560fe Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 5 Nov 2021 16:04:08 +0800 Subject: [PATCH 0520/2647] ingore error for old incomplete service data Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/service.go | 2 +- .../aslan/core/common/service/template_product.go | 7 ++++--- pkg/shared/codehost/codehost.go | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index c4d6a038..31d13b28 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -356,7 +356,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str } detail, err := codehost.GetCodeHostInfo( - &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner, CodeHostID: resp.CodehostID}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 91a3f36d..f1768bd6 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -142,10 +142,11 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P if len(totalServices) > 0 { serviceObj, err := GetServiceTemplate(totalServices[0].ServiceName, totalServices[0].Type, productName, setting.ProductStatusDeleting, totalServices[0].Revision, log) if err != nil { - return resp, fmt.Errorf("GetServiceTemplate err : %v", err) + log.Errorf("GetServiceTemplate err : %v", err) + } else { + resp.LatestServiceUpdateTime = serviceObj.CreateTime + resp.LatestServiceUpdateBy = serviceObj.CreateBy } - resp.LatestServiceUpdateTime = serviceObj.CreateTime - resp.LatestServiceUpdateBy = serviceObj.CreateBy } resp.TotalBuildNum = len(totalBuilds) if len(totalBuilds) > 0 { diff --git a/pkg/shared/codehost/codehost.go b/pkg/shared/codehost/codehost.go index a0876711..c3d0cfa0 100644 --- a/pkg/shared/codehost/codehost.go +++ b/pkg/shared/codehost/codehost.go @@ -79,7 +79,7 @@ func GetCodeHostInfo(option *Option) (*poetry.CodeHost, error) { switch option.CodeHostType { case GitHubProvider: ns := strings.ToLower(codeHost.Namespace) - if strings.Contains(option.Address, codeHost.Address) && option.Namespace == ns { + if strings.Contains(option.Address, codeHost.Address) && strings.ToLower(option.Namespace) == ns { return codeHost, nil } default: -- Gitee From 6bea84041befc7dcfaabd8800a1830bde6d665af Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 5 Nov 2021 16:22:38 +0800 Subject: [PATCH 0521/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/template_product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index f1768bd6..0d40911f 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -142,7 +142,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P if len(totalServices) > 0 { serviceObj, err := GetServiceTemplate(totalServices[0].ServiceName, totalServices[0].Type, productName, setting.ProductStatusDeleting, totalServices[0].Revision, log) if err != nil { - log.Errorf("GetServiceTemplate err : %v", err) + log.Errorf("GetServiceTemplate err : %s", err) } else { resp.LatestServiceUpdateTime = serviceObj.CreateTime resp.LatestServiceUpdateBy = serviceObj.CreateBy -- Gitee From 21247173031e6faed5a47f68abdb69e04eded738 Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 5 Nov 2021 16:26:55 +0800 Subject: [PATCH 0522/2647] fix s3 bug (#546) Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/cachemanager.go | 3 +-- pkg/microservice/reaper/core/service/reaper/script.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/cachemanager.go b/pkg/microservice/reaper/core/service/reaper/cachemanager.go index cac90509..ae4eb537 100644 --- a/pkg/microservice/reaper/core/service/reaper/cachemanager.go +++ b/pkg/microservice/reaper/core/service/reaper/cachemanager.go @@ -153,8 +153,7 @@ func (gcm *TarCacheManager) Unarchive(source, dest string) error { defer func() { _ = os.Remove(sourceFilename) }() - objectKey := store.GetObjectPath(files[0]) - err = s3client.Download(store.Bucket, objectKey, sourceFilename) + err = s3client.Download(store.Bucket, files[0], sourceFilename) if err != nil { return err } diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 9a175f43..47cbf240 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -402,8 +402,7 @@ func (r *Reaper) downloadArtifactFile() error { return err } if len(files) > 0 { - objectKey := store.GetObjectPath(files[0]) - if err = s3client.Download(store.Bucket, objectKey, fmt.Sprintf("%s/%s", r.ActiveWorkspace, r.Ctx.ArtifactInfo.FileName)); err != nil { + if err = s3client.Download(store.Bucket, files[0], fmt.Sprintf("%s/%s", r.ActiveWorkspace, r.Ctx.ArtifactInfo.FileName)); err != nil { log.Errorf("s3 download file err:%s", err) return err } -- Gitee From fc5160eec4203b9e3d912e509f996f558ba917b3 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Fri, 5 Nov 2021 16:45:18 +0800 Subject: [PATCH 0523/2647] add check (#547) * add check Signed-off-by: mouuii * add check Signed-off-by: mouuii Co-authored-by: mouuii --- .../systemconfig/core/email/handler/email.go | 2 -- .../core/email/repository/mongodb/emailhost.go | 17 ++++++++++++++++- .../email/repository/mongodb/emailservice.go | 17 ++++++++++++++++- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index e7c911e7..fbb3432c 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -48,8 +48,6 @@ func DeleteEmailHost(c *gin.Context) { ctx.Err = service.DeleteEmailHost(ctx.Logger) } -//// - func GetEmailService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go index 666b00e0..9d16d343 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go @@ -2,6 +2,7 @@ package mongodb import ( "context" + "errors" "time" "go.mongodb.org/mongo-driver/bson" @@ -80,8 +81,22 @@ func (c *EmailHostColl) Delete() error { } func (c *EmailHostColl) Add(emailHost *models.EmailHost) (*models.EmailHost, error) { + // TODO: tmp solution to avoid bug + var res []*models.EmailHost + query := bson.M{"deleted_at": 0} + cur, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cur.All(context.TODO(), &res) + if err != nil { + return nil, err + } + if len(res) >= 1 { + return nil, errors.New("cant add more than one emailhost") + } - _, err := c.Collection.InsertOne(context.TODO(), emailHost) + _, err = c.Collection.InsertOne(context.TODO(), emailHost) if err != nil { log.Error("repository AddEmailHost err : %v", err) return nil, err diff --git a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go index 14395a20..6f8c0b44 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go @@ -2,6 +2,7 @@ package mongodb import ( "context" + "errors" "time" "go.mongodb.org/mongo-driver/bson" @@ -34,8 +35,22 @@ func (c *EmailServiceColl) EnsureIndex(ctx context.Context) error { } func (c *EmailServiceColl) AddEmailService(iEmailService *models.EmailService) (*models.EmailService, error) { + // TODO: tmp solution to avoid bug + var res []*models.EmailService + query := bson.M{"deleted_at": 0} + cur, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cur.All(context.TODO(), &res) + if err != nil { + return nil, err + } + if len(res) >= 1 { + return nil, errors.New("cant add more than one emailservice") + } - _, err := c.Collection.InsertOne(context.TODO(), iEmailService) + _, err = c.Collection.InsertOne(context.TODO(), iEmailService) if err != nil { log.Error("repository AddEmailService err : %v", err) return nil, err -- Gitee From 8c1ee6fc27b729d33a03b1439e211e8355f0c5f0 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Fri, 5 Nov 2021 18:15:48 +0800 Subject: [PATCH 0524/2647] support token in qs (#548) Signed-off-by: lou --- .../aslan/core/environment/handler/policy.yaml | 2 +- .../policy/core/service/bundle/rego/authz.rego | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 1fe633c7..5ff750e6 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -78,7 +78,7 @@ rules: endpoint: "/api/aslan/environment/environments/?*/services/?*/scale/?*" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew/?*" - - method: PUT + - method: GET endpoint: " /api/podexec/?*/?*/?*/?*/podExec" - action: delete_environment alias: "删除集成环境" diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index bac66f0b..48a8cdcd 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -216,7 +216,7 @@ claims := payload { # }) } -bearer_token := t { +bearer_token_in_header := t { # Bearer tokens are contained inside of the HTTP Authorization header. This rule # parses the header and extracts the Bearer token value. If no Bearer token is # provided, the `bearer_token` value is undefined. @@ -225,6 +225,15 @@ bearer_token := t { t := substring(v, count("Bearer "), -1) } +bearer_token = t { + t := bearer_token_in_header +} + +bearer_token = t { + not bearer_token_in_header + t := input.parsed_query.token[0] +} + is_authenticated { claims claims.uid != "" -- Gitee From 8a2fa59767468f60b11618b727251b61e2f73ce4 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 5 Nov 2021 19:16:07 +0800 Subject: [PATCH 0525/2647] fix conflict Signed-off-by: lou --- .../repository/mongodb/template/product.go | 42 ---------------- .../core/common/service/config_payload.go | 1 - .../core/environment/handler/environment.go | 8 --- .../core/environment/service/environment.go | 49 ------------------- .../aslan/core/project/handler/product.go | 13 ----- .../aslan/core/project/service/product.go | 12 ----- .../aslan/core/service/handler/helm.go | 14 ------ .../aslan/core/service/handler/router.go | 6 --- .../aslan/core/service/service/helm.go | 1 - .../aslan/core/system/handler/s3.go | 17 ------- .../aslan/core/templatestore/service/yaml.go | 1 - 11 files changed, 164 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 45444f45..c5d23227 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -266,48 +266,6 @@ type ProductArgs struct { UpdateBy string `json:"update_by"` } -// UpdateServiceOrder existing ProductTmpl -func (c *ProductColl) UpdateServiceOrder(args *ProductArgs) error { - // avoid panic issue - if args == nil { - return errors.New("nil product args") - } - - query := bson.M{"product_name": args.ProductName} - change := bson.M{"$set": bson.M{ - "services": args.Services, - "update_time": time.Now().Unix(), - "update_by": args.UpdateBy, - }} - - _, err := c.UpdateOne(context.TODO(), query, change) - return err -} - -type ProductArgs struct { - ProductName string `json:"product_name"` - Services [][]string `json:"services"` - UpdateBy string `json:"update_by"` -} - -// UpdateServiceOrder existing ProductTmpl -func (c *ProductColl) UpdateServiceOrder(args *ProductArgs) error { - // avoid panic issue - if args == nil { - return errors.New("nil product args") - } - - query := bson.M{"product_name": args.ProductName} - change := bson.M{"$set": bson.M{ - "services": args.Services, - "update_time": time.Now().Unix(), - "update_by": args.UpdateBy, - }} - - _, err := c.UpdateOne(context.TODO(), query, change) - return err -} - // AddService adds a service to services[0] if it is not there. func (c *ProductColl) AddService(productName, serviceName string) error { diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index ec01b0c7..7c15bd52 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -26,7 +26,6 @@ import ( func GetConfigPayload(codeHostID int) *models.ConfigPayload { payload := &models.ConfigPayload{ - APIToken: config.PoetryAPIRootKey(), S3Storage: models.S3Config{ Ak: config.S3StorageAK(), Sk: config.S3StorageSK(), diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index f7de917f..7e8edcba 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -71,14 +71,6 @@ func GetProductStatus(c *gin.Context) { ctx.Resp, ctx.Err = service.GetProductStatus(c.Param("productName"), ctx.Logger) } -// GetProductStatus List product status -func GetProductStatus(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.GetProductStatus(c.Param("productName"), ctx.Logger) -} - func AutoCreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 8fcfa53a..e8f7c584 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -55,7 +55,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/wrapper" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -150,13 +149,6 @@ type RawYamlResp struct { type intervalExecutorHandler func(data *commonmodels.Service, log *zap.SugaredLogger) error -func UpdateProductPublic(productName string, args *ProductParams, log *zap.SugaredLogger) error { - err := commonrepo.NewProductColl().UpdateIsPublic(args.EnvName, productName, args.IsPublic) - if err != nil { - log.Errorf("UpdateProductPublic error: %v", err) - return fmt.Errorf("UpdateProductPublic error: %v", err) - } - func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) if err != nil { @@ -164,15 +156,6 @@ func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, return nil, e.ErrListProducts.AddDesc(err.Error()) } - return nil -} - -func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, error) { - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) - if err != nil { - log.Errorf("Collection.Product.List List product error: %v", err) - return nil, e.ErrListProducts.AddDesc(err.Error()) - } envStatusSlice := make([]*EnvStatus, 0) for _, publicProduct := range products { if publicProduct.ProductName != productName { @@ -190,38 +173,6 @@ func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, return envStatusSlice, err } -func ListProducts(productNameParam, envType string, userName string, userID int, superUser bool, log *zap.SugaredLogger) ([]*ProductResp, error) { - var ( - err error - testResp []*ProductResp - prodResp []*ProductResp - products = make([]*commonmodels.Product, 0) - productNameMap map[string][]int64 - productNamespaces = sets.NewString() - ) - resp := make([]*ProductResp, 0) - - poetryCtl := poetry.New(config.PoetryAPIServer(), config.PoetryAPIRootKey()) - - // 获取所有产品 - if superUser { - products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam}) - if err != nil { - log.Errorf("[%s] Collections.Product.List error: %v", userName, err) - return resp, e.ErrListEnvs.AddDesc(err.Error()) - } - envStatus := &EnvStatus{ - EnvName: publicProduct.EnvName, - Status: publicProduct.Status, - } - if len(publicProduct.Error) > 0 { - envStatus.ErrMessage = publicProduct.Error - } - envStatusSlice = append(envStatusSlice, envStatus) - } - return envStatusSlice, err -} - func ListProducts(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam, IsSortByProductName: true}) if err != nil { diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 9a0973fd..0729c3e2 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -109,19 +109,6 @@ type updateServiceOrderArgs struct { Services [][]string `json:"services"` } -func UpdateServiceOrchestration(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(updateServiceOrderArgs) - if err := c.ShouldBindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid updateServiceOrder args") - return - } - - ctx.Err = projectservice.UpdateServiceOrchestration(ctx.User.Name, c.Param("name"), args.Services, ctx.Logger) -} - func UpdateProject(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 6cefd8ae..29010f33 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -207,18 +207,6 @@ func UpdateProductTmplStatus(productName, onboardingStatus string, log *zap.Suga return nil } -func UpdateServiceOrchestration(username, name string, services [][]string, log *zap.SugaredLogger) error { - if err := templaterepo.NewProductColl().UpdateServiceOrder(&templaterepo.ProductArgs{ - ProductName: name, - Services: services, - UpdateBy: username, - }); err != nil { - log.Errorf("failed to update service order,err:%s", err) - return e.ErrUpdateProduct.AddDesc("failed to update service order") - } - return nil -} - // UpdateProject 更新项目 func UpdateProject(name string, args *template.Product, log *zap.SugaredLogger) (err error) { err = validateRule(args.CustomImageRule, args.CustomTarRule) diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index a4546052..8f19493f 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -83,20 +83,6 @@ func CreateOrUpdateBulkHelmServices(c *gin.Context) { ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(c.Query("productName"), args, ctx.Logger) } -func CreateOrUpdateBulkHelmServices(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(svcservice.BulkHelmServiceCreationArgs) - if err := c.BindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") - return - } - args.CreatedBy = ctx.Username - - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(c.Query("productName"), args, ctx.Logger) -} - func UpdateHelmService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 17bfbfa9..e83c9403 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -86,10 +86,4 @@ func (*Router) Inject(router *gin.RouterGroup) { template.POST("/load", LoadServiceFromYamlTemplate) template.POST("/reload", ReloadServiceFromYamlTemplate) } - - template := router.Group("template") - { - template.POST("/load", LoadServiceFromYamlTemplate) - template.POST("/reload", ReloadServiceFromYamlTemplate) - } } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 6f7eb8d1..543dbdf5 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -38,7 +38,6 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatedata "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index 0e8f1c90..f8e534ad 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -134,20 +134,3 @@ func ListTars(c *gin.Context) { ctx.Resp, ctx.Err = service.ListTars(c.Param("id"), c.Query("kind"), args.Names, ctx.Logger) } - -type ListTarsOption struct { - Names []string `json:"names"` -} - -func ListTars(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(ListTarsOption) - if err := c.ShouldBindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return - } - - ctx.Resp, ctx.Err = service.ListTars(c.Param("id"), c.Query("kind"), args.Names, ctx.Logger) -} diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index e565bd10..2fce6f55 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -2,7 +2,6 @@ package service import ( "errors" - "fmt" "regexp" "strings" -- Gitee From 893cae329727411798d76f9ed4a85717bc1a5701 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 5 Nov 2021 19:37:59 +0800 Subject: [PATCH 0526/2647] return service name to client after chart imported Signed-off-by: allenshen --- .../aslan/core/service/handler/helm.go | 2 +- .../aslan/core/service/service/helm.go | 67 ++++++++++--------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index e2db5031..13393399 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -66,7 +66,7 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy = ctx.Username - ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("productName"), args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("productName"), args, ctx.Logger) } func CreateOrUpdateBulkHelmServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 543dbdf5..50aa6e69 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -28,7 +28,6 @@ import ( "time" "github.com/27149chen/afero" - "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "github.com/pkg/errors" "go.uber.org/zap" @@ -243,26 +242,26 @@ func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (* }, nil } -func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { +func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) case LoadFromChartTemplate: return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) default: - return fmt.Errorf("invalid source") + return nil, fmt.Errorf("invalid source") } } -func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { +func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { - return fmt.Errorf("invalid argument") + return nil, fmt.Errorf("invalid argument") } templateChartInfo, err := prepareChartTemplateData(templateArgs.TemplateName, logger) if err != nil { - return err + return nil, err } var values [][]byte @@ -270,7 +269,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe //render variables renderedYaml, err := renderVariablesToYaml(string(templateChartInfo.DefaultValuesYAML), projectName, args.Name, templateArgs.Variables) if err != nil { - return err + return nil, err } values = append(values, []byte(renderedYaml)) } @@ -288,29 +287,29 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe // remove old files if err = os.RemoveAll(to); err != nil { logger.Errorf("Failed to remove dir %s, err: %s", to, err) - return err + return nil, err } if err = copy.Copy(from, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", from, to, err) - return err + return nil, err } merged, err := yamlutil.Merge(values) if err != nil { logger.Errorf("Failed to merge values, err: %s", err) - return err + return nil, err } if err = os.WriteFile(filepath.Join(to, setting.ValuesYaml), merged, 0644); err != nil { logger.Errorf("Failed to write values, err: %s", err) - return err + return nil, err } fsTree := os.DirFS(config.LocalServicePath(projectName, args.Name)) ServiceS3Base := config.ObjectStorageServicePath(projectName, args.Name) if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, args.Name, ServiceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", args.Name, projectName, err) - return err + return nil, err } svc, err := createOrUpdateHelmService( @@ -332,7 +331,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe ) if err != nil { logger.Errorf("Failed to create service %s in project %s, error: %s", args.Name, projectName, err) - return err + return nil, err } compareHelmVariable([]*templatemodels.RenderChart{ @@ -342,7 +341,9 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe }, }, projectName, args.CreatedBy, logger) - return nil + return &BulkHelmServiceCreationResponse{ + SuccessServices: []string{args.Name}, + }, nil } func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry.CodeHost, error) { @@ -357,20 +358,20 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry return ch.Type, ch, nil } -func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) error { +func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { var err error var repoLink string repoArgs, ok := args.CreateFrom.(*CreateFromRepo) if !ok { publicArgs, ok := args.CreateFrom.(*CreateFromPublicRepo) if !ok { - return fmt.Errorf("invalid argument") + return nil, fmt.Errorf("invalid argument") } repoArgs, err = PublicRepoToPrivateRepoArgs(publicArgs) if err != nil { log.Errorf("Failed to parse repo args %+v, err: %s", publicArgs, err) - return err + return nil, err } repoLink = publicArgs.RepoLink @@ -378,38 +379,41 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC } + response := &BulkHelmServiceCreationResponse{} source, codehostInfo, err := getCodehostType(repoArgs, repoLink) if err != nil { log.Errorf("Failed to get source form repo data %+v, err: %s", *repoArgs, err.Error()) - return err + return nil, err } helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(repoArgs.Paths)) - var errs *multierror.Error var wg wait.Group var mux sync.RWMutex for _, p := range repoArgs.Paths { filePath := strings.TrimLeft(p, "/") wg.Start(func() { - var finalErr error - + var ( + serviceName string + chartVersion string + valuesYAML []byte + finalErr error + ) defer func() { + mux.Lock() if finalErr != nil { - mux.Lock() - errs = multierror.Append(errs, finalErr) - mux.Unlock() + response.FailedServices = append(response.FailedServices, &FailedService{ + Path: filePath, + Error: finalErr.Error(), + }) + } else { + response.SuccessServices = append(response.SuccessServices, serviceName) } + mux.Unlock() }() log.Infof("Loading chart under path %s", filePath) - var ( - serviceName string - chartVersion string - valuesYAML []byte - ) - fsTree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, func(chartTree afero.Fs) (string, error) { @@ -476,8 +480,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC wg.Wait() compareHelmVariable(helmRenderCharts, projectName, args.CreatedBy, log) - - return errs.ErrorOrNil() + return response, nil } func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { -- Gitee From 7c3b7388a8cb83d6fa9ee042d0af7c9a75841873 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 5 Nov 2021 19:38:39 +0800 Subject: [PATCH 0527/2647] update after review Signed-off-by: lou --- pkg/microservice/aslan/core/service/service/helm.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 543dbdf5..7cc0ede3 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -374,8 +374,6 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC } repoLink = publicArgs.RepoLink - } else { - } source, codehostInfo, err := getCodehostType(repoArgs, repoLink) -- Gitee From 29889885ee22a81ffa7529c7855369d75f1e4c1c Mon Sep 17 00:00:00 2001 From: Landy Date: Fri, 5 Nov 2021 19:54:49 +0800 Subject: [PATCH 0528/2647] Revert "Release 1.6.0" --- .../aslan/core/service/handler/helm.go | 2 +- .../aslan/core/service/service/helm.go | 67 +++++++++---------- 2 files changed, 33 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 13393399..e2db5031 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -66,7 +66,7 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy = ctx.Username - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("productName"), args, ctx.Logger) + ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("productName"), args, ctx.Logger) } func CreateOrUpdateBulkHelmServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 50aa6e69..543dbdf5 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -28,6 +28,7 @@ import ( "time" "github.com/27149chen/afero" + "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "github.com/pkg/errors" "go.uber.org/zap" @@ -242,26 +243,26 @@ func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (* }, nil } -func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) case LoadFromChartTemplate: return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) default: - return nil, fmt.Errorf("invalid source") + return fmt.Errorf("invalid source") } } -func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { - return nil, fmt.Errorf("invalid argument") + return fmt.Errorf("invalid argument") } templateChartInfo, err := prepareChartTemplateData(templateArgs.TemplateName, logger) if err != nil { - return nil, err + return err } var values [][]byte @@ -269,7 +270,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe //render variables renderedYaml, err := renderVariablesToYaml(string(templateChartInfo.DefaultValuesYAML), projectName, args.Name, templateArgs.Variables) if err != nil { - return nil, err + return err } values = append(values, []byte(renderedYaml)) } @@ -287,29 +288,29 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe // remove old files if err = os.RemoveAll(to); err != nil { logger.Errorf("Failed to remove dir %s, err: %s", to, err) - return nil, err + return err } if err = copy.Copy(from, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", from, to, err) - return nil, err + return err } merged, err := yamlutil.Merge(values) if err != nil { logger.Errorf("Failed to merge values, err: %s", err) - return nil, err + return err } if err = os.WriteFile(filepath.Join(to, setting.ValuesYaml), merged, 0644); err != nil { logger.Errorf("Failed to write values, err: %s", err) - return nil, err + return err } fsTree := os.DirFS(config.LocalServicePath(projectName, args.Name)) ServiceS3Base := config.ObjectStorageServicePath(projectName, args.Name) if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, args.Name, ServiceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", args.Name, projectName, err) - return nil, err + return err } svc, err := createOrUpdateHelmService( @@ -331,7 +332,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe ) if err != nil { logger.Errorf("Failed to create service %s in project %s, error: %s", args.Name, projectName, err) - return nil, err + return err } compareHelmVariable([]*templatemodels.RenderChart{ @@ -341,9 +342,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe }, }, projectName, args.CreatedBy, logger) - return &BulkHelmServiceCreationResponse{ - SuccessServices: []string{args.Name}, - }, nil + return nil } func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry.CodeHost, error) { @@ -358,20 +357,20 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry return ch.Type, ch, nil } -func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) error { var err error var repoLink string repoArgs, ok := args.CreateFrom.(*CreateFromRepo) if !ok { publicArgs, ok := args.CreateFrom.(*CreateFromPublicRepo) if !ok { - return nil, fmt.Errorf("invalid argument") + return fmt.Errorf("invalid argument") } repoArgs, err = PublicRepoToPrivateRepoArgs(publicArgs) if err != nil { log.Errorf("Failed to parse repo args %+v, err: %s", publicArgs, err) - return nil, err + return err } repoLink = publicArgs.RepoLink @@ -379,41 +378,38 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC } - response := &BulkHelmServiceCreationResponse{} source, codehostInfo, err := getCodehostType(repoArgs, repoLink) if err != nil { log.Errorf("Failed to get source form repo data %+v, err: %s", *repoArgs, err.Error()) - return nil, err + return err } helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(repoArgs.Paths)) + var errs *multierror.Error var wg wait.Group var mux sync.RWMutex for _, p := range repoArgs.Paths { filePath := strings.TrimLeft(p, "/") wg.Start(func() { - var ( - serviceName string - chartVersion string - valuesYAML []byte - finalErr error - ) + var finalErr error + defer func() { - mux.Lock() if finalErr != nil { - response.FailedServices = append(response.FailedServices, &FailedService{ - Path: filePath, - Error: finalErr.Error(), - }) - } else { - response.SuccessServices = append(response.SuccessServices, serviceName) + mux.Lock() + errs = multierror.Append(errs, finalErr) + mux.Unlock() } - mux.Unlock() }() log.Infof("Loading chart under path %s", filePath) + var ( + serviceName string + chartVersion string + valuesYAML []byte + ) + fsTree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, func(chartTree afero.Fs) (string, error) { @@ -480,7 +476,8 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC wg.Wait() compareHelmVariable(helmRenderCharts, projectName, args.CreatedBy, log) - return response, nil + + return errs.ErrorOrNil() } func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { -- Gitee From 22324c21602ad08650513d7d93e3ec2ab97f878a Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 5 Nov 2021 21:39:05 +0800 Subject: [PATCH 0529/2647] fix ua upgrad bug Signed-off-by: allenshen --- pkg/cli/upgradeassistant/internal/upgradepath/versions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index 562dfd39..476dca70 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -91,7 +91,7 @@ func init() { sort.Strings(versionList) - for i := 0; i < len(versionList)-2; i++ { + for i := 0; i < len(versionList)-1; i++ { lowVersion := versionList[i] highVersion := versionList[i+1] AddHandler(versionMap[lowVersion], versionMap[highVersion], defaultUpgradeHandler) -- Gitee From 47bf2b689ed4553550e4600c8d4306366ef7ba7b Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 8 Nov 2021 11:25:13 +0800 Subject: [PATCH 0530/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/service/login/local.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index ccb54d62..60906169 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -49,9 +49,12 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { } password := []byte(args.Password) err = bcrypt.CompareHashAndPassword([]byte(userLogin.Password), password) + if err == bcrypt.ErrMismatchedHashAndPassword { + return nil, fmt.Errorf("password is wrong") + } if err != nil { - logger.Errorf("LocalLogin user:%s check password error, error msg:%s", args.Account, err.Error()) - return nil, fmt.Errorf("check password error, error msg:%s", err.Error()) + logger.Errorf("LocalLogin user:%s check password error, error msg:%s", args.Account, err) + return nil, fmt.Errorf("check password error, error msg:%s", err) } userLogin.LastLoginTime = time.Now().Unix() err = orm.UpdateUserLogin(userLogin.UID, userLogin, core.DB) -- Gitee From 636a882b00b04eaf839839054ced4ca3a67e50b2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 13:38:39 +0800 Subject: [PATCH 0531/2647] bugfix Signed-off-by: mouuii --- .../systemconfig/core/codehost/repository/models/codehost.go | 2 +- .../systemconfig/core/email/repository/mongodb/emailservice.go | 2 +- pkg/microservice/systemconfig/server/rest/router.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index ba7d6269..8ff1b9d9 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -5,7 +5,7 @@ type CodeHost struct { Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` - IsReady bool `bson:"is_ready" json:"ready"` + IsReady string `bson:"is_ready" json:"ready"` AccessToken string `bson:"access_token" json:"accessToken"` RefreshToken string `bson:"refresh_token" json:"refreshToken"` Namespace string `bson:"namespace" json:"namespace"` diff --git a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go index 6f8c0b44..7ded08a2 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go @@ -21,7 +21,7 @@ type EmailServiceColl struct { } func NewEmailServiceColl() *EmailServiceColl { - name := models.EmailHost{}.TableName() + name := models.EmailService{}.TableName() coll := &EmailServiceColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} return coll diff --git a/pkg/microservice/systemconfig/server/rest/router.go b/pkg/microservice/systemconfig/server/rest/router.go index ca654736..015f0c56 100644 --- a/pkg/microservice/systemconfig/server/rest/router.go +++ b/pkg/microservice/systemconfig/server/rest/router.go @@ -19,7 +19,7 @@ package rest import ( "github.com/gin-gonic/gin" - codehosthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/code/handler" + codehosthandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/handler" emailHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/handler" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/handler" jiraHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/handler" -- Gitee From d2517d39cdb7b6e13363d977c8e9107e69e721b1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 8 Nov 2021 14:30:42 +0800 Subject: [PATCH 0532/2647] fix modify hosting env services bug Signed-off-by: liu deyi --- .../core/common/repository/mongodb/service.go | 18 +++++++++++ .../aslan/core/service/service/service.go | 31 +++++++++++-------- .../workflow/service/workflow/workflow.go | 8 +++-- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 572ef8ac..30342b4c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -329,6 +329,24 @@ func (c *ServiceColl) BatchUpdateExternalServicesStatus(productName, envName, st return err } +// UpdateExternalServiceEnvName only used by external services +func (c *ServiceColl) UpdateExternalServiceEnvName(serviceName, productName, envName string) error { + if serviceName == "" { + return fmt.Errorf("serviceName is empty") + } + if productName == "" { + return fmt.Errorf("productName is empty") + } + + query := bson.M{"service_name": serviceName, "product_name": productName} + change := bson.M{"$set": bson.M{ + "env_name": envName, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + // UpdateExternalServicesStatus only used by external services func (c *ServiceColl) UpdateExternalServicesStatus(serviceName, productName, status, envName string) error { if serviceName == "" && envName == "" { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 17cf5c53..16034ecc 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -437,13 +437,6 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN } } } - // pre judge workLoads same name - //services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") - //for _, v := range services { - // if addString.Has(v.ServiceName) { - // return e.ErrCreateTemplate.AddDesc(fmt.Sprintf("do not support import same service name: %s", v.ServiceName)) - // } - //} for _, v := range args.WorkLoads { if addString.Has(v.Name) { @@ -465,26 +458,38 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN log.Errorf("failed to list external service, error:%s", err) } - externalEnvServiceM := make(map[string]bool) + externalEnvServiceM := make(map[string]*commonmodels.ServicesInExternalEnv) for _, externalEnvService := range otherExternalEnvServices { - externalEnvServiceM[externalEnvService.ServiceName] = true + externalEnvServiceM[externalEnvService.ServiceName] = externalEnvService } for _, v := range diff { switch v.Operation { // 删除workload的引用 case "delete": - if _, isExist := externalEnvServiceM[v.Name]; !isExist { - err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName) - if err != nil { + if externalService, isExist := externalEnvServiceM[v.Name]; !isExist { + if err = commonrepo.NewServiceColl().UpdateExternalServicesStatus(v.Name, productName, setting.ProductStatusDeleting, envName); err != nil { log.Errorf("UpdateStatus external services error:%s", err) } + } else { + // Update the env name in the service + if err = commonrepo.NewServiceColl().UpdateExternalServiceEnvName(v.Name, productName, externalService.EnvName); err != nil { + log.Errorf("UpdateEnvName external services error:%s", err) + } + // Delete the reference in the original service + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: externalService.ProductName, + EnvName: externalService.EnvName, + ServiceName: externalService.ServiceName, + }); err != nil { + log.Errorf("delete service in external env envName:%s error:%s", externalService.EnvName, err) + } } if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ ProductName: productName, EnvName: envName, ServiceName: v.Name, }); err != nil { - log.Errorf("delete services in external env error:%s", err) + log.Errorf("delete service in external env envName:%s error:%s", envName, err) } // 添加workload的引用 case "add": diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index f2c8a357..647cbf2d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -64,6 +64,10 @@ func (args *workflowCreateArgs) addWorkflowArg(envName string, buildStageEnabled if artifactStageEnabled { wName = fmt.Sprintf("%s-%s-workflow", args.productName, "ops") } + // The hosting env workflow name is not bound to the environment + if envName == "" { + wName = fmt.Sprintf("%s-workflow", args.productName) + } args.argsMap[wName] = &workflowCreateArg{ name: wName, envName: envName, @@ -116,10 +120,10 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { createArgs.addWorkflowArg("", false, true) } - // 云主机场景不创建ops工作流 + // Only one workflow is created in the hosting environment if productTmpl.ProductFeature != nil && productTmpl.ProductFeature.CreateEnvType == setting.SourceFromExternal { createArgs.clear() - createArgs.addWorkflowArg("dev", true, false) + createArgs.addWorkflowArg("", true, false) } workflowSlice := sets.NewString() -- Gitee From 79af5e2e7013dff213de6abbd8d510fb76a393fc Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 8 Nov 2021 14:47:06 +0800 Subject: [PATCH 0533/2647] optimize sync third party user Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/login/third_party.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index df86d6ea..633b0d8c 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -103,8 +103,10 @@ func Callback(c *gin.Context) { ctx.Err = err return } + user, err := user.SyncUser(&user.SyncUserInfo{ Account: claims.Name, + Name: claims.Name, IdentityType: claims.FederatedClaims.ConnectorId, }, ctx.Logger) if err != nil { @@ -112,6 +114,7 @@ func Callback(c *gin.Context) { return } claims.Uid = user.UID + claims.Account = claims.Name claims.StandardClaims.ExpiresAt = time.Now().Add(time.Duration(config.TokenExpiresAt()) * time.Minute).Unix() userToken, err := login.CreateToken(claims) if err != nil { -- Gitee From 91fa6e28b280bba55ecc6d11ecfffb886d083ae2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 14:49:26 +0800 Subject: [PATCH 0534/2647] bugfix Signed-off-by: mouuii --- .../systemconfig/core/codehost/repository/models/codehost.go | 2 +- .../systemconfig/core/codehost/service/codehost.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index 8ff1b9d9..ba7d6269 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -5,7 +5,7 @@ type CodeHost struct { Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` - IsReady string `bson:"is_ready" json:"ready"` + IsReady bool `bson:"is_ready" json:"ready"` AccessToken string `bson:"access_token" json:"accessToken"` RefreshToken string `bson:"refresh_token" json:"refreshToken"` Namespace string `bson:"namespace" json:"namespace"` diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 0cc0c7c5..193f1dc6 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -8,6 +8,9 @@ import ( ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { + if codehost.Type == "codehub" { + codehost.IsReady = true + } return mongodb.NewCodehostColl().AddCodeHost(codehost) } -- Gitee From 4131061ebf8851de6f51012539524e6ac1c41992 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 14:50:39 +0800 Subject: [PATCH 0535/2647] bugfix Signed-off-by: mouuii --- .../systemconfig/core/codehost/repository/models/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index ba7d6269..567431c7 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -5,7 +5,7 @@ type CodeHost struct { Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` - IsReady bool `bson:"is_ready" json:"ready"` + IsReadyV2 bool `bson:"is_ready_v2" json:"is_ready_v2"` AccessToken string `bson:"access_token" json:"accessToken"` RefreshToken string `bson:"refresh_token" json:"refreshToken"` Namespace string `bson:"namespace" json:"namespace"` -- Gitee From 469128182f73dd54dfd60eeec04c9bb7b9fc2e85 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 14:50:57 +0800 Subject: [PATCH 0536/2647] bugfix Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/service/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 193f1dc6..bab0bb0c 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -9,7 +9,7 @@ import ( func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { if codehost.Type == "codehub" { - codehost.IsReady = true + codehost.IsReadyV2 = true } return mongodb.NewCodehostColl().AddCodeHost(codehost) } -- Gitee From cdc98906fc6c46e064a50d3ffdb7cac90f4fb1d2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 15:09:27 +0800 Subject: [PATCH 0537/2647] bugfix Signed-off-by: mouuii --- pkg/microservice/systemconfig/config/consts.go | 7 +++++++ .../core/codehost/repository/models/codehost.go | 2 +- .../core/codehost/repository/mongodb/codehost.go | 9 +++++++++ .../systemconfig/core/codehost/service/codehost.go | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go index 27f698e5..8acf347c 100644 --- a/pkg/microservice/systemconfig/config/consts.go +++ b/pkg/microservice/systemconfig/config/consts.go @@ -4,3 +4,10 @@ const ( ENVMysqlDexDB = "MYSQL_DEX_DB" FeatureFlag = "feature-gates" ) + +var CodeHostMap = map[string]string{ + "1": "gitlab", + "2": "github", + "3": "gerrit", + "4": "codehub", +} diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index 567431c7..8ff1b9d9 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -5,7 +5,7 @@ type CodeHost struct { Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` - IsReadyV2 bool `bson:"is_ready_v2" json:"is_ready_v2"` + IsReady string `bson:"is_ready" json:"ready"` AccessToken string `bson:"access_token" json:"accessToken"` RefreshToken string `bson:"refresh_token" json:"refreshToken"` Namespace string `bson:"namespace" json:"namespace"` diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 855df2c7..60e61623 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -65,6 +65,9 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { if err != nil { return nil, nil } + if v, ok := config.CodeHostMap[codehost.Type]; ok { + codehost.Type = v + } return codehost, nil } @@ -80,6 +83,12 @@ func (c *CodehostColl) FindCodeHosts() ([]*models.CodeHost, error) { if err != nil { return nil, err } + // NOTE: to adapt old data + for i, v := range codeHosts { + if v, ok := config.CodeHostMap[v.Type]; ok { + codeHosts[i].Type = v + } + } return codeHosts, nil } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index bab0bb0c..596a5a47 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -9,7 +9,7 @@ import ( func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { if codehost.Type == "codehub" { - codehost.IsReadyV2 = true + codehost.IsReady = "2" } return mongodb.NewCodehostColl().AddCodeHost(codehost) } -- Gitee From 613987aae3f1a80a8dafa2c3f4ba237a32552d21 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 15:30:57 +0800 Subject: [PATCH 0538/2647] bugfix Signed-off-by: mouuii --- .../core/codehost/repository/mongodb/codehost.go | 14 ++++++++++++++ .../systemconfig/core/codehost/service/codehost.go | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 60e61623..561a60f7 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -92,6 +92,20 @@ func (c *CodehostColl) FindCodeHosts() ([]*models.CodeHost, error) { return codeHosts, nil } +func (c *CodehostColl) CodeHostList() ([]*models.CodeHost, error) { + codeHosts := make([]*models.CodeHost, 0) + + cursor, err := c.Collection.Find(context.TODO(), bson.M{}) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &codeHosts) + if err != nil { + return nil, err + } + return codeHosts, nil +} + func (c *CodehostColl) DeleteCodeHostByID(ID int) error { query := bson.M{"id": ID, "deleted_at": 0} change := bson.M{"$set": bson.M{ diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 596a5a47..fbf137ef 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -11,6 +11,11 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co if codehost.Type == "codehub" { codehost.IsReady = "2" } + list, err := mongodb.NewCodehostColl().CodeHostList() + if err != nil { + return nil, err + } + codehost.ID = len(list) + 1 return mongodb.NewCodehostColl().AddCodeHost(codehost) } -- Gitee From 41b439f9f4427c8b4ea4fd4e71c6440802cb9e51 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 16:04:21 +0800 Subject: [PATCH 0539/2647] codehost auth Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 37 +++++++++++++++++++ .../core/codehost/handler/router.go | 1 + 2 files changed, 38 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index b98565cc..50fd5c4c 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -1,9 +1,13 @@ package handler import ( + "fmt" + "net/http" + "net/url" "strconv" "github.com/gin-gonic/gin" + "golang.org/x/oauth2" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" @@ -51,6 +55,39 @@ func GetCodeHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetCodeHost(id, ctx.Logger) } +func AuthCodeHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + provider := c.Query("provider") + redirect := c.Query("redirect") + redirectHost, err := url.Parse(redirect) + if err != nil { + ctx.Err = err + return + } + id := c.Param("id") + idInt, err := strconv.Atoi(id) + if err != nil { + ctx.Err = err + return + } + authStateString := fmt.Sprintf("%s%s%d%s%s", redirect, "&codeHostId=", id, "&provider=", provider) + codeHost, err := service.GetCodeHost(idInt, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/directory/codehosts/callback") + authConfig := &oauth2.Config{ + ClientID: codeHost.ApplicationId, + ClientSecret: codeHost.ClientSecret, + RedirectURL: callBackUrl, + //RedirectURL: "http://localhost:34001/directory/codehosts/callback", + } + redirectURL := authConfig.AuthCodeURL(authStateString) + http.Redirect(c.Writer, c.Request, redirectURL, http.StatusFound) +} + func UpdateCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index e29fa97d..b1734528 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -14,5 +14,6 @@ func (*Router) Inject(router *gin.RouterGroup) { codehost.POST("", CreateCodeHost) codehost.PATCH("/:id", UpdateCodeHost) codehost.GET("/:id", GetCodeHost) + codehost.GET("/:id/auth", AuthCodeHost) } } -- Gitee From 461e5b15fb141db5d0b6ca524dc315b8a91b5787 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 16:26:17 +0800 Subject: [PATCH 0540/2647] codehost auth Signed-off-by: mouuii --- pkg/shared/config/features.go | 120 +++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 3 deletions(-) diff --git a/pkg/shared/config/features.go b/pkg/shared/config/features.go index 82ef1f21..bedd55b0 100644 --- a/pkg/shared/config/features.go +++ b/pkg/shared/config/features.go @@ -1,9 +1,12 @@ package config - import ( + "errors" "fmt" + "strings" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" + "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -13,7 +16,7 @@ type feature struct { } func (c *Client) CheckFeature(featureName string) (bool, error) { - url := fmt.Sprintf("/api/v1/features/%s",featureName) + url := fmt.Sprintf("/api/v1/features/%s", featureName) fs := &feature{} _, err := c.Get(url, httpclient.SetResult(fs)) @@ -22,4 +25,115 @@ func (c *Client) CheckFeature(featureName string) (bool, error) { } return fs.Enabled, nil -} \ No newline at end of file +} + +func (c *Client) GetCodeHostList() ([]*models.CodeHost, error) { + url := fmt.Sprintf("/api/v1/codehost") + var list []*models.CodeHost + _, err := c.Post(url, httpclient.SetResult(&list)) + if err != nil { + return nil, err + } + return list, nil +} + +type Option struct { + CodeHostType string + Address string + Namespace string + CodeHostID int +} + +func (c *Client) GetCodeHostInfo(option *Option) (*models.CodeHost, error) { + codeHosts, err := c.GetCodeHostList() + if err != nil { + return nil, err + } + + for _, codeHost := range codeHosts { + if option.CodeHostID != 0 && codeHost.ID == option.CodeHostID { + return codeHost, nil + } else if option.CodeHostID == 0 && option.CodeHostType != "" { + switch option.CodeHostType { + case codehost.GitHubProvider: + ns := strings.ToLower(codeHost.Namespace) + if strings.Contains(option.Address, codeHost.Address) && strings.ToLower(option.Namespace) == ns { + return codeHost, nil + } + default: + if strings.Contains(option.Address, codeHost.Address) { + return codeHost, nil + } + } + } + } + + return nil, errors.New("not find codeHost") +} + +func (c *Client) GetCodeHostInfoByID(id int) (*models.CodeHost, error) { + return c.GetCodeHostInfo(&Option{CodeHostID: id}) +} + +type Detail struct { + ID int `json:"id"` + Name string `json:"name"` + Address string `json:"address"` + Owner string `json:"repoowner"` + Source string `json:"source"` + OauthToken string `json:"oauth_token"` + Region string `json:"region"` + Username string `json:"username"` + Password string `json:"password"` + AccessKey string `json:"applicationId"` + SecretKey string `json:"clientSecret"` +} + +func (c *Client) GetCodehostDetail(codehostID int) (*Detail, error) { + codehost, err := c.GetCodeHostInfo(&Option{CodeHostID: codehostID}) + if err != nil { + return nil, err + } + detail := &Detail{ + codehostID, + "", + codehost.Address, + codehost.Namespace, + codehost.Type, + codehost.AccessToken, + codehost.Region, + codehost.Username, + codehost.Password, + codehost.ApplicationId, + codehost.ClientSecret, + } + + return detail, nil +} + +func (c *Client) ListCodehostDetial() ([]*Detail, error) { + codehosts, err := c.GetCodeHostList() + if err != nil { + return nil, err + } + + var details []*Detail + + for _, codehost := range codehosts { + details = append(details, &Detail{ + codehost.ID, + "", + codehost.Address, + codehost.Namespace, + codehost.Type, + codehost.AccessToken, + codehost.Region, + codehost.Username, + codehost.Password, + codehost.ApplicationId, + codehost.ClientSecret, + }) + } + + return details, nil +} -- Gitee From fb004c60de1d87e9cb153e4e777f19c642f51b55 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 16:37:11 +0800 Subject: [PATCH 0541/2647] codehost client Signed-off-by: mouuii --- pkg/shared/client/systemconfig/codehost.go | 121 +++++++++++++++++++++ pkg/shared/config/features.go | 115 -------------------- 2 files changed, 121 insertions(+), 115 deletions(-) create mode 100644 pkg/shared/client/systemconfig/codehost.go diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go new file mode 100644 index 00000000..e2d88df9 --- /dev/null +++ b/pkg/shared/client/systemconfig/codehost.go @@ -0,0 +1,121 @@ +package systemconfig + +import ( + "errors" + "fmt" + "strings" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" + "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func GetCodeHostList() ([]*models.CodeHost, error) { + var list []*models.CodeHost + _, err := New().Post(fmt.Sprintf("/api/v1/codehost"), httpclient.SetResult(&list)) + if err != nil { + return nil, err + } + return list, nil +} + +type Option struct { + CodeHostType string + Address string + Namespace string + CodeHostID int +} + +func GetCodeHostInfo(option *Option) (*models.CodeHost, error) { + codeHosts, err := GetCodeHostList() + if err != nil { + return nil, err + } + + for _, codeHost := range codeHosts { + if option.CodeHostID != 0 && codeHost.ID == option.CodeHostID { + return codeHost, nil + } else if option.CodeHostID == 0 && option.CodeHostType != "" { + switch option.CodeHostType { + case codehost.GitHubProvider: + ns := strings.ToLower(codeHost.Namespace) + if strings.Contains(option.Address, codeHost.Address) && strings.ToLower(option.Namespace) == ns { + return codeHost, nil + } + default: + if strings.Contains(option.Address, codeHost.Address) { + return codeHost, nil + } + } + } + } + + return nil, errors.New("not find codeHost") +} + +func (c *Client) GetCodeHostInfoByID(id int) (*models.CodeHost, error) { + return GetCodeHostInfo(&Option{CodeHostID: id}) +} + +type Detail struct { + ID int `json:"id"` + Name string `json:"name"` + Address string `json:"address"` + Owner string `json:"repoowner"` + Source string `json:"source"` + OauthToken string `json:"oauth_token"` + Region string `json:"region"` + Username string `json:"username"` + Password string `json:"password"` + AccessKey string `json:"applicationId"` + SecretKey string `json:"clientSecret"` +} + +func (c *Client) GetCodehostDetail(codehostID int) (*Detail, error) { + codehost, err := GetCodeHostInfo(&Option{CodeHostID: codehostID}) + if err != nil { + return nil, err + } + detail := &Detail{ + codehostID, + "", + codehost.Address, + codehost.Namespace, + codehost.Type, + codehost.AccessToken, + codehost.Region, + codehost.Username, + codehost.Password, + codehost.ApplicationId, + codehost.ClientSecret, + } + + return detail, nil +} + +func (c *Client) ListCodehostDetial() ([]*Detail, error) { + codehosts, err := GetCodeHostList() + if err != nil { + return nil, err + } + + var details []*Detail + + for _, codehost := range codehosts { + details = append(details, &Detail{ + codehost.ID, + "", + codehost.Address, + codehost.Namespace, + codehost.Type, + codehost.AccessToken, + codehost.Region, + codehost.Username, + codehost.Password, + codehost.ApplicationId, + codehost.ClientSecret, + }) + } + + return details, nil +} diff --git a/pkg/shared/config/features.go b/pkg/shared/config/features.go index bedd55b0..9f542e10 100644 --- a/pkg/shared/config/features.go +++ b/pkg/shared/config/features.go @@ -1,12 +1,8 @@ package config import ( - "errors" "fmt" - "strings" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -26,114 +22,3 @@ func (c *Client) CheckFeature(featureName string) (bool, error) { return fs.Enabled, nil } - -func (c *Client) GetCodeHostList() ([]*models.CodeHost, error) { - url := fmt.Sprintf("/api/v1/codehost") - var list []*models.CodeHost - _, err := c.Post(url, httpclient.SetResult(&list)) - if err != nil { - return nil, err - } - return list, nil -} - -type Option struct { - CodeHostType string - Address string - Namespace string - CodeHostID int -} - -func (c *Client) GetCodeHostInfo(option *Option) (*models.CodeHost, error) { - codeHosts, err := c.GetCodeHostList() - if err != nil { - return nil, err - } - - for _, codeHost := range codeHosts { - if option.CodeHostID != 0 && codeHost.ID == option.CodeHostID { - return codeHost, nil - } else if option.CodeHostID == 0 && option.CodeHostType != "" { - switch option.CodeHostType { - case codehost.GitHubProvider: - ns := strings.ToLower(codeHost.Namespace) - if strings.Contains(option.Address, codeHost.Address) && strings.ToLower(option.Namespace) == ns { - return codeHost, nil - } - default: - if strings.Contains(option.Address, codeHost.Address) { - return codeHost, nil - } - } - } - } - - return nil, errors.New("not find codeHost") -} - -func (c *Client) GetCodeHostInfoByID(id int) (*models.CodeHost, error) { - return c.GetCodeHostInfo(&Option{CodeHostID: id}) -} - -type Detail struct { - ID int `json:"id"` - Name string `json:"name"` - Address string `json:"address"` - Owner string `json:"repoowner"` - Source string `json:"source"` - OauthToken string `json:"oauth_token"` - Region string `json:"region"` - Username string `json:"username"` - Password string `json:"password"` - AccessKey string `json:"applicationId"` - SecretKey string `json:"clientSecret"` -} - -func (c *Client) GetCodehostDetail(codehostID int) (*Detail, error) { - codehost, err := c.GetCodeHostInfo(&Option{CodeHostID: codehostID}) - if err != nil { - return nil, err - } - detail := &Detail{ - codehostID, - "", - codehost.Address, - codehost.Namespace, - codehost.Type, - codehost.AccessToken, - codehost.Region, - codehost.Username, - codehost.Password, - codehost.ApplicationId, - codehost.ClientSecret, - } - - return detail, nil -} - -func (c *Client) ListCodehostDetial() ([]*Detail, error) { - codehosts, err := c.GetCodeHostList() - if err != nil { - return nil, err - } - - var details []*Detail - - for _, codehost := range codehosts { - details = append(details, &Detail{ - codehost.ID, - "", - codehost.Address, - codehost.Namespace, - codehost.Type, - codehost.AccessToken, - codehost.Region, - codehost.Username, - codehost.Password, - codehost.ApplicationId, - codehost.ClientSecret, - }) - } - - return details, nil -} -- Gitee From 6e5913650a45dd900c4b54d305e7aad7f83159c9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 16:59:50 +0800 Subject: [PATCH 0542/2647] codehost client Signed-off-by: mouuii --- pkg/shared/client/systemconfig/codehost.go | 30 +++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index e2d88df9..81e0515a 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -5,13 +5,25 @@ import ( "fmt" "strings" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/httpclient" ) -func GetCodeHostList() ([]*models.CodeHost, error) { - var list []*models.CodeHost +type CodeHost struct { + ID int `json:"id"` + Address string `json:"address"` + Type string `json:"type"` + AccessToken string `json:"accessToken"` + Namespace string `json:"namespace"` + Region string `json:"region"` + AccessKey string `json:"applicationId"` + SecretKey string `json:"clientSecret"` + Username string `json:"username"` + Password string `json:"password"` +} + +func GetCodeHostList() ([]*CodeHost, error) { + var list []*CodeHost _, err := New().Post(fmt.Sprintf("/api/v1/codehost"), httpclient.SetResult(&list)) if err != nil { return nil, err @@ -26,7 +38,7 @@ type Option struct { CodeHostID int } -func GetCodeHostInfo(option *Option) (*models.CodeHost, error) { +func GetCodeHostInfo(option *Option) (*CodeHost, error) { codeHosts, err := GetCodeHostList() if err != nil { return nil, err @@ -53,7 +65,7 @@ func GetCodeHostInfo(option *Option) (*models.CodeHost, error) { return nil, errors.New("not find codeHost") } -func (c *Client) GetCodeHostInfoByID(id int) (*models.CodeHost, error) { +func (c *Client) GetCodeHostInfoByID(id int) (*CodeHost, error) { return GetCodeHostInfo(&Option{CodeHostID: id}) } @@ -86,8 +98,8 @@ func (c *Client) GetCodehostDetail(codehostID int) (*Detail, error) { codehost.Region, codehost.Username, codehost.Password, - codehost.ApplicationId, - codehost.ClientSecret, + codehost.AccessKey, + codehost.SecretKey, } return detail, nil @@ -112,8 +124,8 @@ func (c *Client) ListCodehostDetial() ([]*Detail, error) { codehost.Region, codehost.Username, codehost.Password, - codehost.ApplicationId, - codehost.ClientSecret, + codehost.AccessKey, + codehost.SecretKey, }) } -- Gitee From 4de0b59e6127f9ce4dfac2fdfdc4aa60fd61b3ba Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 17:03:37 +0800 Subject: [PATCH 0543/2647] add id json Signed-off-by: mouuii --- .../systemconfig/core/codehost/repository/models/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index 8ff1b9d9..522440d0 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -1,7 +1,7 @@ package models type CodeHost struct { - ID int `bson:"id"` + ID int `bson:"id" json:"id"` Name string `bson:"name" json:"name"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` -- Gitee From b63fa374d28c8fda205bd3d960026cca554f9df0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 17:16:40 +0800 Subject: [PATCH 0544/2647] replace client Signed-off-by: mouuii --- .../aslan/core/code/handler/codehost.go | 7 +++---- .../aslan/core/code/service/branch.go | 6 +++--- .../aslan/core/code/service/merge_request.go | 6 +++--- .../aslan/core/code/service/namespace.go | 6 +++--- .../aslan/core/code/service/project.go | 6 +++--- .../aslan/core/code/service/tag.go | 6 +++--- .../aslan/core/code/service/workspace.go | 5 ++--- .../core/common/service/command/git_cmd.go | 4 ++-- .../core/common/service/config_payload.go | 4 ++-- .../aslan/core/common/service/fs/git.go | 3 +-- .../aslan/core/common/service/gerrit/gerrit.go | 3 +-- .../core/common/service/scmnotify/client.go | 3 +-- .../aslan/core/common/service/service.go | 14 +++++++------- .../aslan/core/common/service/workflow.go | 5 ++--- .../aslan/core/service/service/helm.go | 3 +-- .../aslan/core/service/service/loader.go | 6 +++--- .../aslan/core/service/service/service.go | 16 ++++++++-------- .../core/workflow/service/webhook/gerrit.go | 8 ++++---- .../service/webhook/gerrit_workflow_task.go | 6 +++--- .../core/workflow/service/webhook/github.go | 10 +++++----- .../service/webhook/github_workflow_task.go | 3 +-- .../service/webhook/gitlab_workflow_task.go | 7 +++---- .../core/workflow/service/webhook/utils.go | 13 +++++++------ .../core/workflow/service/workflow/gerrit.go | 18 +++++++++--------- .../core/workflow/service/workflow/github.go | 3 +-- .../core/workflow/service/workflow/gitlab.go | 14 +++++++------- .../service/workflow/pipeline_validation.go | 9 +++++---- pkg/shared/client/systemconfig/codehost.go | 6 +++--- 28 files changed, 96 insertions(+), 104 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 85a7198e..211114b3 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -24,9 +24,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/service" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" internalhandler "github.com/koderover/zadig/pkg/shared/handler" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -34,8 +33,8 @@ func GetCodeHostList(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - codeHostSlice := make([]*poetry.CodeHost, 0) - codeHosts, err := codehost.GetCodeHostList() + codeHostSlice := make([]*systemconfig.CodeHost, 0) + codeHosts, err := systemconfig.GetCodeHostList() ctx.Err = err for _, codeHost := range codeHosts { codeHost.AccessToken = setting.MaskValue diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 3945aa41..fecb306a 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -23,7 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" @@ -31,10 +31,10 @@ import ( ) func CodeHostListBranches(codeHostID int, projectName, namespace string, log *zap.SugaredLogger) ([]*Branch, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: codeHostID, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") } diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 4f0d78e0..6d063356 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -24,17 +24,17 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/git/gitlab" ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, log *zap.SugaredLogger) ([]*PullRequest, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: codeHostID, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return nil, e.ErrCodehostListPrs.AddDesc("git client is nil") } diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 5b5923de..b8e67930 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -23,7 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" @@ -41,10 +41,10 @@ const ( ) func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*Namespace, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: codeHostID, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return nil, e.ErrCodehostListNamespaces.AddDesc("git client is nil") } diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 38103faf..316327e6 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -23,7 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" @@ -31,10 +31,10 @@ import ( ) func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword string, log *zap.SugaredLogger) ([]*Project, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: codeHostID, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 90dd3cc3..dc77ed54 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -23,7 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" @@ -31,10 +31,10 @@ import ( ) func CodeHostListTags(codeHostID int, projectName string, namespace string, log *zap.SugaredLogger) ([]*Tag, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: codeHostID, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { log.Error(err) return nil, e.ErrCodehostListTags.AddDesc("git client is nil") diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index 751e008a..6eac61dc 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -28,7 +28,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -145,7 +144,7 @@ func GetGitRepoInfo(codehostID int, repoOwner, repoName, branchName, remoteName, if err := os.RemoveAll(base); err != nil { log.Errorf("dir remove err:%v", err) } - detail, err := codehost.GetCodehostDetail(codehostID) + detail, err := systemconfig.GetCodehostDetail(codehostID) if err != nil { log.Errorf("GetGitRepoInfo GetCodehostDetail err:%v", err) return fis, e.ErrListRepoDir.AddDesc(err.Error()) @@ -189,7 +188,7 @@ type CodehostFileInfo struct { func GetCodehubRepoInfo(codehostID int, repoUUID, branchName, path string, log *zap.SugaredLogger) ([]*CodehostFileInfo, error) { fileInfos := make([]*CodehostFileInfo, 0) - detail, err := codehost.GetCodehostDetail(codehostID) + detail, err := systemconfig.GetCodehostDetail(codehostID) if err != nil { log.Errorf("GetCodehubRepoInfo GetCodehostDetail err:%s", err) return fileInfos, e.ErrListWorkspace.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/service/command/git_cmd.go b/pkg/microservice/aslan/core/common/service/command/git_cmd.go index d47f3f8c..f60a2170 100644 --- a/pkg/microservice/aslan/core/common/service/command/git_cmd.go +++ b/pkg/microservice/aslan/core/common/service/command/git_cmd.go @@ -30,7 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/log" ) @@ -65,7 +65,7 @@ type Command struct { IgnoreError bool } -func RunGitCmds(codehostDetail *codehost.Detail, repoOwner, repoName, branchName, remoteName string) error { +func RunGitCmds(codehostDetail *systemconfig.Detail, repoOwner, repoName, branchName, remoteName string) error { var ( tokens []string repo *Repo diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 7c15bd52..8adf52d1 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -21,7 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) func GetConfigPayload(codeHostID int) *models.ConfigPayload { @@ -73,7 +73,7 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { } if codeHostID > 0 { - ch, _ := codehost.GetCodeHostInfoByID(codeHostID) + ch, _ := systemconfig.GetCodeHostInfoByID(codeHostID) if ch != nil && ch.Type == setting.SourceFromGithub { payload.Github.AccessToken = ch.AccessToken } diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index 12b5c900..a7dce1a0 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -27,7 +27,6 @@ import ( githubservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" gitlabservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/log" ) @@ -101,7 +100,7 @@ func GetPublicTreeGetter(repoLink string) (TreeGetter, error) { } func GetTreeGetter(codeHostID int) (TreeGetter, error) { - ch, err := codehost.GetCodeHostInfoByID(codeHostID) + ch, err := systemconfig.GetCodeHostInfoByID(codeHostID) if err != nil { log.Errorf("Failed to get codeHost by id %d, err: %s", codeHostID, err) return nil, err diff --git a/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go b/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go index 57068177..dc01a6a2 100644 --- a/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go @@ -26,7 +26,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/gerrit" ) @@ -37,7 +36,7 @@ func DeleteGerritWebhook(workflow *models.Workflow, log *zap.SugaredLogger) erro continue } - detail, err := codehost.GetCodeHostInfoByID(workflowWebhook.MainRepo.CodehostID) + detail, err := systemconfig.GetCodeHostInfoByID(workflowWebhook.MainRepo.CodehostID) if err != nil { log.Errorf("DeleteGerritWebhook GetCodehostDetail err:%v", err) continue diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index f7299ce3..7fcbc14a 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -27,7 +27,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/gerrit" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" @@ -55,7 +54,7 @@ func (c *Client) Comment(notify *models.Notification) error { } } - codeHostDetail, err := codehost.GetCodeHostInfoByID(notify.CodehostID) + codeHostDetail, err := systemconfig.GetCodeHostInfoByID(notify.CodehostID) if err != nil { return errors.Wrapf(err, "codehost %d not found to comment", notify.CodehostID) } diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 31d13b28..6bfed47c 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -37,7 +37,7 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -131,7 +131,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm return nil, e.ErrListTemplate.AddDesc(err.Error()) } - details, err := codehost.ListCodehostDetial() + details, err := systemconfig.ListCodehostDetial() if err != nil { log.Errorf("无法从原有数据中恢复加载信息, listCodehostDetail failed err: %+v", err) return nil, e.ErrListTemplate.AddDesc(err.Error()) @@ -162,8 +162,8 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm return nil, err } - detail, err := codehost.GetCodeHostInfo( - &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + detail, err := systemconfig.GetCodeHostInfo( + &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err @@ -318,7 +318,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { if gitlabAddress, err := GetGitlabAddress(resp.SrcPath); err == nil { - if details, err := codehost.ListCodehostDetial(); err == nil { + if details, err := systemconfig.ListCodehostDetial(); err == nil { for _, detail := range details { if strings.Contains(detail.Address, gitlabAddress) { resp.GerritCodeHostID = detail.ID @@ -355,8 +355,8 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str return nil, err } - detail, err := codehost.GetCodeHostInfo( - &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner, CodeHostID: resp.CodehostID}) + detail, err := systemconfig.GetCodeHostInfo( + &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner, CodeHostID: resp.CodehostID}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 36e82f91..373e4097 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -30,7 +30,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -167,7 +166,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger wg.Add(1) go func(wh hookItem) { defer wg.Done() - ch, err := codehost.GetCodeHostInfoByID(wh.codeHostID) + ch, err := systemconfig.GetCodeHostInfoByID(wh.codeHostID) if err != nil { logger.Errorf("Failed to get codeHost by id %d, err: %s", wh.codeHostID, err) errs = multierror.Append(errs, err) @@ -201,7 +200,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger wg.Add(1) go func(wh hookItem) { defer wg.Done() - ch, err := codehost.GetCodeHostInfoByID(wh.codeHostID) + ch, err := systemconfig.GetCodeHostInfoByID(wh.codeHostID) if err != nil { logger.Errorf("Failed to get codeHost by id %d, err: %s", wh.codeHostID, err) errs = multierror.Append(errs, err) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 7cc0ede3..d7d108cf 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -47,7 +47,6 @@ import ( templatestore "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -349,7 +348,7 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry if repoLink != "" { return setting.SourceFromPublicRepo, nil, nil } - ch, err := codehost.GetCodeHostInfoByID(repoArgs.CodehostID) + ch, err := systemconfig.GetCodeHostInfoByID(repoArgs.CodehostID) if err != nil { log.Errorf("Failed to get codeHost by id %d, err: %s", repoArgs.CodehostID, err.Error()) return "", ch, err diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 4c9434af..28393c1b 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -53,7 +53,7 @@ type LoadServiceReq struct { func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, branchName, remoteName, path string, isDir bool, log *zap.SugaredLogger) ([]string, error) { var ret []string - ch, err := codehost.GetCodeHostInfoByID(codehostID) + ch, err := systemconfig.GetCodeHostInfoByID(codehostID) if err != nil { log.Errorf("Failed to load codehost for preload service list, the error is: %+v", err) return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) @@ -74,7 +74,7 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b // LoadServiceFromCodeHost 根据提供的codehost信息加载服务 func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoName, repoUUID, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { - ch, err := codehost.GetCodeHostInfoByID(codehostID) + ch, err := systemconfig.GetCodeHostInfoByID(codehostID) if err != nil { log.Errorf("Failed to load codehost for preload service list, the error is: %+v", err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -93,7 +93,7 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoNam // ValidateServiceUpdate 根据服务名和提供的加载信息确认是否可以更新服务加载地址 func ValidateServiceUpdate(codehostID int, serviceName, repoOwner, repoName, repoUUID, branchName, remoteName, path string, isDir bool, log *zap.SugaredLogger) error { - detail, err := codehost.GetCodeHostInfoByID(codehostID) + detail, err := systemconfig.GetCodeHostInfoByID(codehostID) if err != nil { log.Errorf("Failed to load codehost for validate service update, the error is: %+v", err) return e.ErrValidateServiceUpdate.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 17cf5c53..76165275 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -46,7 +46,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/httpclient" @@ -1279,10 +1279,10 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { } func updateGerritWebhookByService(lastService, currentService *commonmodels.Service) error { - var codehostDetail *codehost.Detail + var codehostDetail *systemconfig.Detail var err error if lastService != nil && lastService.Source == setting.SourceFromGerrit { - codehostDetail, err = codehost.GetCodehostDetail(lastService.GerritCodeHostID) + codehostDetail, err = systemconfig.GetCodehostDetail(lastService.GerritCodeHostID) if err != nil { log.Errorf("updateGerritWebhookByService GetCodehostDetail err:%v", err) return err @@ -1295,11 +1295,11 @@ func updateGerritWebhookByService(lastService, currentService *commonmodels.Serv log.Errorf("updateGerritWebhookByService deleteGerritWebhook err:%v", err) } } - var detail *codehost.Detail + var detail *systemconfig.Detail if lastService.GerritCodeHostID == currentService.GerritCodeHostID && codehostDetail != nil { detail = codehostDetail } else { - detail, _ = codehost.GetCodehostDetail(currentService.GerritCodeHostID) + detail, _ = systemconfig.GetCodehostDetail(currentService.GerritCodeHostID) } cl := gerrit.NewHTTPClient(detail.Address, detail.OauthToken) @@ -1322,7 +1322,7 @@ func updateGerritWebhookByService(lastService, currentService *commonmodels.Serv } func createGerritWebhookByService(codehostID int, serviceName, repoName, branchName string) error { - detail, err := codehost.GetCodehostDetail(codehostID) + detail, err := systemconfig.GetCodehostDetail(codehostID) if err != nil { log.Errorf("createGerritWebhookByService GetCodehostDetail err:%v", err) return err @@ -1347,7 +1347,7 @@ func createGerritWebhookByService(codehostID int, serviceName, repoName, branchN return nil } -func syncGerritLatestCommit(service *commonmodels.Service) (*codehost.Detail, error) { +func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.Detail, error) { if service.GerritCodeHostID == 0 { return nil, fmt.Errorf("codehostId不能是空的") } @@ -1357,7 +1357,7 @@ func syncGerritLatestCommit(service *commonmodels.Service) (*codehost.Detail, er if service.GerritBranchName == "" { return nil, fmt.Errorf("branchName不能是空的") } - ch, _ := codehost.GetCodehostDetail(service.GerritCodeHostID) + ch, _ := systemconfig.GetCodehostDetail(service.GerritCodeHostID) gerritCli := gerrit.NewClient(ch.Address, ch.OauthToken) commit, err := gerritCli.GetCommitByBranch(service.GerritRepoName, service.GerritBranchName) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index b3df30c1..3569bcbe 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -36,7 +36,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" gerritservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/util" @@ -94,7 +94,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro log.Errorf("updateServiceTemplateByGerritEvent GetServiceTemplate err:%v", err) errs = multierror.Append(errs, err) } - detail, err := codehost.GetCodehostDetail(service.GerritCodeHostID) + detail, err := systemconfig.GetCodehostDetail(service.GerritCodeHostID) if err != nil { log.Errorf("updateServiceTemplateByGerritEvent GetCodehostDetail err:%v", err) errs = multierror.Append(errs, err) @@ -228,7 +228,7 @@ func SyncServiceTemplateFromGerrit(service *commonmodels.Service, log *zap.Sugar return nil } -func syncGerritLatestCommit(service *commonmodels.Service) (*codehost.Detail, error) { +func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.Detail, error) { if service.GerritCodeHostID == 0 { return nil, fmt.Errorf("codehostId不能是空的") } @@ -238,7 +238,7 @@ func syncGerritLatestCommit(service *commonmodels.Service) (*codehost.Detail, er if service.GerritBranchName == "" { return nil, fmt.Errorf("branchName不能是空的") } - detail, err := codehost.GetCodehostDetail(service.GerritCodeHostID) + detail, err := systemconfig.GetCodehostDetail(service.GerritCodeHostID) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index 10843579..86425582 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -34,7 +34,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/types" ) @@ -305,7 +305,7 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base if item.WorkflowArgs == nil { continue } - detail, err := codehost.GetCodehostDetail(item.MainRepo.CodehostID) + detail, err := systemconfig.GetCodehostDetail(item.MainRepo.CodehostID) if err != nil { log.Errorf("TriggerWorkflowByGerritEvent GetCodehostDetail err:%v", err) return err @@ -410,7 +410,7 @@ func addWebHookUser(match gerritEventMatcher, domain string) { } } -func checkLatestTaskStaus(pipelineName, mergeRequestID, commitID string, detail *codehost.Detail, log *zap.SugaredLogger) bool { +func checkLatestTaskStaus(pipelineName, mergeRequestID, commitID string, detail *systemconfig.Detail, log *zap.SugaredLogger) bool { opt := &commonrepo.ListTaskOption{ PipelineName: pipelineName, Type: config.WorkflowType, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 2d6e23ce..5e0c4ef3 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -37,7 +37,7 @@ import ( gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" githubtool "github.com/koderover/zadig/pkg/tool/git/github" @@ -188,8 +188,8 @@ func prEventToPipelineTasks(event *github.PullRequestEvent, requestID string, lo log.Errorf("GetAddress failed, url: %s, err: %s", event.Repo.GetURL(), err) return nil, err } - ch, err := codehost.GetCodeHostInfo( - &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + ch, err := systemconfig.GetCodeHostInfo( + &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("GetCodeHostInfo failed, err: %v", err) return nil, err @@ -327,8 +327,8 @@ func pushEventToPipelineTasks(event *github.PushEvent, requestID string, log *za log.Errorf("GetAddress failed, url: %s, err: %s", event.Repo.GetURL(), err) return nil, err } - ch, err := codehost.GetCodeHostInfo( - &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + ch, err := systemconfig.GetCodeHostInfo( + &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("GetCodeHostInfo failed, err: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 55741d4b..b2dab64d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -33,7 +33,6 @@ import ( git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/types" ) @@ -340,7 +339,7 @@ func TriggerWorkflowByGithubEvent(event interface{}, baseURI, deliveryID, reques } func findChangedFilesOfPullRequest(event *github.PullRequestEvent, codehostID int) ([]string, error) { - detail, err := codehost.GetCodehostDetail(codehostID) + detail, err := systemconfig.GetCodehostDetail(codehostID) if err != nil { return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index a0ff10bb..103d67d0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -36,7 +36,6 @@ import ( environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" e "github.com/koderover/zadig/pkg/tool/errors" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" @@ -151,7 +150,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( hookRepo.Branch = getBranchFromRef(ev.Ref) var changedFiles []string - detail, err := codehost.GetCodehostDetail(hookRepo.CodehostID) + detail, err := systemconfig.GetCodehostDetail(hookRepo.CodehostID) if err != nil { gpem.log.Errorf("GetCodehostDetail error: %s", err) return false, err @@ -323,7 +322,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([]string, error) { - detail, err := codehost.GetCodehostDetail(codehostID) + detail, err := systemconfig.GetCodehostDetail(codehostID) if err != nil { return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } @@ -343,7 +342,7 @@ func InitDiffNote(ev *gitlab.MergeEvent, mainRepo *commonmodels.MainHookRepo, lo body := "KodeRover CI 检查中..." // 调用gitlab api获取相关数据 - detail, err := codehost.GetCodehostDetail(mainRepo.CodehostID) + detail, err := systemconfig.GetCodehostDetail(mainRepo.CodehostID) if err != nil { log.Errorf("GetCodehostDetail failed, codehost:%d, err:%v", mainRepo.CodehostID, err) return fmt.Errorf("failed to find codehost %d: %v", mainRepo.CodehostID, err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 2b5531c6..a65bb34a 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -35,6 +35,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" @@ -199,11 +200,11 @@ func syncCodehubLatestCommit(service *commonmodels.Service) error { } func getCodehubClientByAddress(address string) (*codehub.Client, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ Address: address, CodeHostType: codehost.CodeHubProvider, } - codehost, err := codehost.GetCodeHostInfo(opt) + codehost, err := systemconfig.GetCodeHostInfo(opt) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") @@ -214,11 +215,11 @@ func getCodehubClientByAddress(address string) (*codehub.Client, error) { } func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ Address: address, CodeHostType: codehost.GitLabProvider, } - codehost, err := codehost.GetCodeHostInfo(opt) + codehost, err := systemconfig.GetCodeHostInfo(opt) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") @@ -337,8 +338,8 @@ func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) e return errors.New("invalid url " + args.SrcPath) } - ch, err := codehost.GetCodeHostInfo( - &codehost.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + ch, err := systemconfig.GetCodeHostInfo( + &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("GetCodeHostInfo failed, srcPath:%s, err:%v", args.SrcPath, err) return err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go b/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go index b5be0c5e..54d85549 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go @@ -24,7 +24,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -36,10 +36,10 @@ func CreateGerritWebhook(workflow *commonmodels.Workflow, log *zap.SugaredLogger continue } - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: workflowWebhook.MainRepo.CodehostID, } - detail, err := codehost.GetCodeHostInfo(opt) + detail, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return err } @@ -88,10 +88,10 @@ func UpdateGerritWebhook(currentWorkflow *commonmodels.Workflow, log *zap.Sugare continue } - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: oldWorkflowWebhook.MainRepo.CodehostID, } - detail, err := codehost.GetCodeHostInfo(opt) + detail, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return err } @@ -113,10 +113,10 @@ func UpdateGerritWebhook(currentWorkflow *commonmodels.Workflow, log *zap.Sugare continue } - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: workflowWebhook.MainRepo.CodehostID, } - detail, err := codehost.GetCodeHostInfo(opt) + detail, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return err } @@ -155,10 +155,10 @@ func DeleteGerritWebhook(workflow *commonmodels.Workflow, log *zap.SugaredLogger continue } - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: workflowWebhook.MainRepo.CodehostID, } - detail, err := codehost.GetCodeHostInfo(opt) + detail, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/github.go b/pkg/microservice/aslan/core/workflow/service/workflow/github.go index 4a938211..fe6b4349 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/github.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/github.go @@ -27,7 +27,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -85,7 +84,7 @@ func createGitCheck(pt *task.Task, log *zap.SugaredLogger) error { } log.Infof("Init GitHub status") - ch, err := codehost.GetCodeHostInfoByID(pt.TriggerBy.CodehostID) + ch, err := systemconfig.GetCodeHostInfoByID(pt.TriggerBy.CodehostID) if err != nil { log.Errorf("GetCodeHostInfoByID failed, err:%v", err) return e.ErrGithubUpdateStatus.AddErr(err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go index 1d15a7d6..7b27cde0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go @@ -26,7 +26,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/gerrit" ) @@ -40,10 +40,10 @@ type RepoCommit struct { } func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) (*RepoCommit, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: id, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return nil, err } @@ -90,10 +90,10 @@ func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) ( } func QueryByTag(id int, owner, name, tag string, log *zap.SugaredLogger) (*RepoCommit, error) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: id, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return nil, err } @@ -150,10 +150,10 @@ type PRCommit struct { func GetLatestPrCommit(codehostID, pr int, namespace, projectName string, log *zap.SugaredLogger) (*PRCommit, error) { projectID := fmt.Sprintf("%s/%s", namespace, projectName) - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: codehostID, } - ch, err := codehost.GetCodeHostInfo(opt) + ch, err := systemconfig.GetCodeHostInfo(opt) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 8eb8f220..2479d0b6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -45,6 +45,7 @@ import ( git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" @@ -257,10 +258,10 @@ func FmtBuilds(builds []*types.Repository, log *zap.SugaredLogger) { log.Error("codehostID can't be empty") return } - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: cID, } - detail, err := codehost.GetCodeHostInfo(opt) + detail, err := systemconfig.GetCodeHostInfo(opt) if err != nil { log.Error(err) return @@ -305,10 +306,10 @@ func SetTriggerBuilds(builds []*types.Repository, buildArgs []*types.Repository, } func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { - opt := &codehost.Option{ + opt := &systemconfig.Option{ CodeHostID: build.CodehostID, } - codeHostInfo, err := codehost.GetCodeHostInfo(opt) + codeHostInfo, err := systemconfig.GetCodeHostInfo(opt) if err != nil { log.Errorf("failed to get codehost detail %d %v", build.CodehostID, err) return diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 81e0515a..f84e6eaf 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -65,7 +65,7 @@ func GetCodeHostInfo(option *Option) (*CodeHost, error) { return nil, errors.New("not find codeHost") } -func (c *Client) GetCodeHostInfoByID(id int) (*CodeHost, error) { +func GetCodeHostInfoByID(id int) (*CodeHost, error) { return GetCodeHostInfo(&Option{CodeHostID: id}) } @@ -83,7 +83,7 @@ type Detail struct { SecretKey string `json:"clientSecret"` } -func (c *Client) GetCodehostDetail(codehostID int) (*Detail, error) { +func GetCodehostDetail(codehostID int) (*Detail, error) { codehost, err := GetCodeHostInfo(&Option{CodeHostID: codehostID}) if err != nil { return nil, err @@ -105,7 +105,7 @@ func (c *Client) GetCodehostDetail(codehostID int) (*Detail, error) { return detail, nil } -func (c *Client) ListCodehostDetial() ([]*Detail, error) { +func ListCodehostDetial() ([]*Detail, error) { codehosts, err := GetCodeHostList() if err != nil { return nil, err -- Gitee From b6d0d21846782575f2256a72b2237fb08a86646f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 17:19:36 +0800 Subject: [PATCH 0545/2647] add public Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index cf86b6ab..9719c9a8 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -44,6 +44,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET", "POST"}, Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/codehost/?*/auth"}, + }, { Methods: []string{"*"}, Endpoints: []string{"api/v1/callback"}, -- Gitee From 64309b668f991bd328e997fcd5344ef8147c5a15 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 17:59:50 +0800 Subject: [PATCH 0546/2647] callback Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 94 +++++++++++++++++++ .../core/codehost/handler/router.go | 3 +- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 50fd5c4c..688ef846 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "github.com/gin-gonic/gin" "golang.org/x/oauth2" @@ -88,6 +89,99 @@ func AuthCodeHost(c *gin.Context) { http.Redirect(c.Writer, c.Request, redirectURL, http.StatusFound) } +func Callback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + //增加数据验证 + state := c.Query("state") + urlArray := strings.Split(state, "&codeHostId=") + frontEndUrl := urlArray[0] + if strings.Contains(frontEndUrl, "errCode") { + frontEndUrl = strings.Split(frontEndUrl, "?errCode")[0] + } + + gitlabError := c.Query("error") + if gitlabError != "" { + ctx.Logger.Warn("view code_host_get_call_back_gitlab user denied") + url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 10007, "&errMessage=access_denied") + http.Redirect(c.Writer, c.Request, url, http.StatusFound) + return + } + stateURL, err := url.Parse(state) + if err != nil { + url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=failed_to_parse_redirect_url") + http.Redirect(c.Writer, c.Request, url, http.StatusFound) + return + } + codeHostArray := strings.Split(urlArray[1], "&provider=") + codeHostID, err := strconv.Atoi(codeHostArray[0]) + if err != nil { + ctx.Logger.Error("view code_host_get_call_back_gitlab codeHostID convert err : %v", err) + url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=codeHostID convert failed") + http.Redirect(c.Writer, c.Request, url, http.StatusFound) + return + } + code := c.Query("code") + iCodehost, err := service.GetCodeHost(codeHostID, ctx.Logger) + if err != nil { + ctx.Logger.Error("view code_host_get_call_back_gitlab GetCodeHostByID err : %v", err) + url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=get codehost failed") + http.Redirect(c.Writer, c.Request, url, http.StatusFound) + return + } + + codehost := new(models.CodeHost) + + callBackUrl := fmt.Sprintf("%s://%s%s", stateURL.Scheme, stateURL.Host, "/api/directory/codehosts/callback") + authConfig := &oauth2.Config{ + ClientID: iCodehost.ApplicationId, + ClientSecret: iCodehost.ClientSecret, + RedirectURL: callBackUrl, + //RedirectURL: "http://localhost:34001/directory/codehosts/callback", + } + if codeHostArray[1] == "gitlab" { + authConfig.Scopes = []string{"api", "read_user"} + authConfig.Endpoint = oauth2.Endpoint{ + AuthURL: fmt.Sprintf("%s%s", iCodehost.Address, "/oauth/authorize"), + TokenURL: fmt.Sprintf("%s%s", iCodehost.Address, "/oauth/token"), + } + } else if codeHostArray[1] == "github" { + authConfig.Scopes = []string{"repo", "user"} + authConfig.Endpoint = oauth2.Endpoint{ + AuthURL: fmt.Sprintf("%s%s", iCodehost.Address, "/login/oauth/authorize"), + TokenURL: fmt.Sprintf("%s%s", iCodehost.Address, "/login/oauth/access_token"), + } + } + + token, err := authConfig.Exchange(oauth2.NoContext, code) + if err != nil { + ctx.Logger.Error("view code_host_get_call_back_gitlab Exchange err : %v", err) + url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 10007, "&errMessage=exchange failed") + http.Redirect(c.Writer, c.Request, url, http.StatusFound) + return + } + + codehost.AccessToken = token.AccessToken + codehost.RefreshToken = token.RefreshToken + _, err = service.UpdateCodeHost(codehost, ctx.Logger) + if err != nil { + ctx.Logger.Error("view UpdateCodeHostByToken err : %v", err) + url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=update codehost failed") + http.Redirect(c.Writer, c.Request, url, http.StatusFound) + return + } + + //跳转回前端 + redirectURL := "" + if strings.Contains(frontEndUrl, "?succeed") { + redirectURL = fmt.Sprintf("%s%s", strings.Split(frontEndUrl, "?succeed")[0], "?succeed=true") + } else { + redirectURL = fmt.Sprintf("%s%s", frontEndUrl, "?succeed=true") + } + http.Redirect(c.Writer, c.Request, redirectURL, http.StatusFound) +} + func UpdateCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index b1734528..ddfff52a 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -7,8 +7,9 @@ import ( type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { - codehost := router.Group("codehost") + codehost := router.Group("codehosts") { + codehost.GET("/callback", Callback) codehost.GET("", ListCodeHost) codehost.DELETE("/:id", DeleteCodeHost) codehost.POST("", CreateCodeHost) -- Gitee From a61ad6335465d16b11b565ddfe676316746ebc46 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 18:00:19 +0800 Subject: [PATCH 0547/2647] callback Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/handler/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 688ef846..0a7ced85 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -78,7 +78,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/directory/codehosts/callback") + callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/v1/codehosts/callback") authConfig := &oauth2.Config{ ClientID: codeHost.ApplicationId, ClientSecret: codeHost.ClientSecret, -- Gitee From 803385b2b9c855525a9350bf0dba2a17a377b0fb Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 8 Nov 2021 18:01:40 +0800 Subject: [PATCH 0548/2647] callback Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 9719c9a8..7c4fff0d 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -46,7 +46,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{"api/v1/codehost/?*/auth"}, + Endpoints: []string{"api/v1/codehosts/?*/auth", "api/v1/codehosts/callback"}, }, { Methods: []string{"*"}, -- Gitee From 460c098aa52dcbad0e51201bc2a62c1c23f10c27 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 8 Nov 2021 18:32:23 +0800 Subject: [PATCH 0549/2647] add external link interface Signed-off-by: liu deyi --- .../common/repository/models/external_link.go | 16 +++ .../repository/mongodb/external_link.go | 109 ++++++++++++++++++ pkg/microservice/aslan/core/service.go | 1 + .../core/system/handler/external_link.go | 81 +++++++++++++ .../aslan/core/system/handler/router.go | 10 ++ .../core/system/service/external_link.go | 45 ++++++++ pkg/tool/errors/http_errors.go | 8 ++ 7 files changed, 270 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/repository/models/external_link.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/external_link.go create mode 100644 pkg/microservice/aslan/core/system/handler/external_link.go create mode 100644 pkg/microservice/aslan/core/system/service/external_link.go diff --git a/pkg/microservice/aslan/core/common/repository/models/external_link.go b/pkg/microservice/aslan/core/common/repository/models/external_link.go new file mode 100644 index 00000000..9afc405b --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/external_link.go @@ -0,0 +1,16 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type ExternalLink struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + URL string `bson:"url" json:"url"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` +} + +func (ExternalLink) TableName() string { + return "external_link" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go new file mode 100644 index 00000000..af87e978 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go @@ -0,0 +1,109 @@ +package mongodb + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ExternalLinkColl struct { + *mongo.Collection + + coll string +} + +func NewExternalLinkColl() *ExternalLinkColl { + name := models.ExternalLink{}.TableName() + return &ExternalLinkColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ExternalLinkColl) GetCollectionName() string { + return c.coll +} + +func (c *ExternalLinkColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"name": 1, "url": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + return err +} + +func (c *ExternalLinkColl) List() ([]*models.ExternalLink, error) { + query := bson.M{} + resp := make([]*models.ExternalLink, 0) + ctx := context.Background() + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, err +} + +func (c *ExternalLinkColl) Create(args *models.ExternalLink) error { + if args == nil { + return errors.New("nil externalLink info") + } + + args.CreateTime = time.Now().Unix() + args.UpdateTime = time.Now().Unix() + + _, err := c.InsertOne(context.TODO(), args) + + return err +} + +func (c *ExternalLinkColl) Update(id string, args *models.ExternalLink) error { + if args == nil { + return errors.New("nil externalLink info") + } + + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + change := bson.M{"$set": bson.M{ + "name": args.Name, + "url": args.URL, + "update_by": args.UpdateBy, + "update_time": time.Now().Unix(), + }} + + _, err = c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *ExternalLinkColl) Delete(id string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index b0b15a8c..35d9c1f1 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -200,6 +200,7 @@ func initDatabase() { commonrepo.NewWorkflowStatColl(), commonrepo.NewWorkLoadsStatColl(), commonrepo.NewServicesInExternalEnvColl(), + commonrepo.NewExternalLinkColl(), templaterepo.NewChartColl(), templaterepo.NewDockerfileTemplateColl(), diff --git a/pkg/microservice/aslan/core/system/handler/external_link.go b/pkg/microservice/aslan/core/system/handler/external_link.go new file mode 100644 index 00000000..5df1edd5 --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/external_link.go @@ -0,0 +1,81 @@ +package handler + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +func ListExternalLinks(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListExternalLinks(ctx.Logger) +} + +func CreateExternalLink(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.ExternalLink) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateExternalLink c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateExternalLink json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统配置-快捷链接", fmt.Sprintf("name:%s url:%s", args.Name, args.URL), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") + return + } + args.UpdateBy = ctx.UserName + + ctx.Err = service.CreateExternalLink(args, ctx.Logger) +} + +func UpdateExternalLink(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.ExternalLink) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateExternal c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("UpdateExternal json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统配置-快捷链接", fmt.Sprintf("name:%s url:%s", args.Name, args.URL), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") + return + } + args.UpdateBy = ctx.UserName + + ctx.Err = service.UpdateExternalLink(c.Param("id"), args, ctx.Logger) +} + +func DeleteExternalLink(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "系统配置-快捷链接", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + ctx.Err = service.DeleteExternalLink(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index c72f6e13..9de790a6 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -186,4 +186,14 @@ func (*Router) Inject(router *gin.RouterGroup) { operation.PUT("/:id", UpdateOperationLog) } + // --------------------------------------------------------------------------------------- + // system external link + // --------------------------------------------------------------------------------------- + externalLink := router.Group("externalLink") + { + externalLink.GET("", ListExternalLinks) + externalLink.POST("", gin2.UpdateOperationLogStatus, CreateExternalLink) + externalLink.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateExternalLink) + externalLink.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteExternalLink) + } } diff --git a/pkg/microservice/aslan/core/system/service/external_link.go b/pkg/microservice/aslan/core/system/service/external_link.go new file mode 100644 index 00000000..b468b201 --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/external_link.go @@ -0,0 +1,45 @@ +package service + +import ( + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func ListExternalLinks(log *zap.SugaredLogger) ([]*commonmodels.ExternalLink, error) { + resp, err := commonrepo.NewExternalLinkColl().List() + if err != nil { + log.Errorf("ExternalLink.List error: %s", err) + return resp, e.ErrListExternalLink.AddErr(err) + } + return resp, nil +} + +func CreateExternalLink(args *commonmodels.ExternalLink, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalLinkColl().Create(args) + if err != nil { + log.Errorf("ExternalLink.Create error: %s", err) + return e.ErrCreateExternalLink.AddErr(err) + } + return nil +} + +func UpdateExternalLink(id string, args *commonmodels.ExternalLink, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalLinkColl().Update(id, args) + if err != nil { + log.Errorf("ExternalLink.Update %s error: %s", id, err) + return e.ErrUpdateExternalLink.AddErr(err) + } + return nil +} + +func DeleteExternalLink(id string, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalLinkColl().Delete(id) + if err != nil { + log.Errorf("ExternalLink.Delete %s error: %s", id, err) + return e.ErrDeleteExternalLink.AddErr(err) + } + return nil +} diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index a1a2f870..45554ea7 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -672,4 +672,12 @@ var ( ErrTestJenkinsConnection = NewHTTPError(6835, "用户名或者密码不正确") ErrListJobNames = NewHTTPError(6836, "获取job名称列表失败") ErrListJobBuildArgs = NewHTTPError(6837, "获取job构建参数列表失败") + + //----------------------------------------------------------------------------------------------- + // external link Error Range: 6840 - 6849 + //----------------------------------------------------------------------------------------------- + ErrCreateExternalLink = NewHTTPError(6841, "创建链接失败") + ErrUpdateExternalLink = NewHTTPError(6842, "更新链接失败") + ErrDeleteExternalLink = NewHTTPError(6843, "删除链接失败") + ErrListExternalLink = NewHTTPError(6844, "获取链接列表失败") ) -- Gitee From a258b67da6c6f0b2f449d491367fb54949f8e9ae Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 8 Nov 2021 18:40:59 +0800 Subject: [PATCH 0550/2647] add refresh webhook secret api --- .../core/common/service/github/webhook.go | 19 ++++++++-- .../core/common/service/gitlab/webhook.go | 12 +++++++ pkg/tool/git/github/repositories.go | 23 ++++++++++++ pkg/tool/git/gitlab/project.go | 36 +++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/github/webhook.go b/pkg/microservice/aslan/core/common/service/github/webhook.go index ebd17228..7850b805 100644 --- a/pkg/microservice/aslan/core/common/service/github/webhook.go +++ b/pkg/microservice/aslan/core/common/service/github/webhook.go @@ -31,14 +31,29 @@ func (c *Client) CreateWebHook(owner, repo string) (string, error) { Secret: gitservice.GetHookSecret(), Events: []string{git.PushEvent, git.PullRequestEvent, git.BranchOrTagCreateEvent, git.CheckRunEvent}, }) + if err != nil { + return "", err + } return strconv.Itoa(int(hook.GetID())), err } func (c *Client) DeleteWebHook(owner, repo, hookID string) error { - hookIDInt, err := strconv.Atoi(hookID) + hookIDInt, err := strconv.ParseInt(hookID, 10, 64) + if err != nil { + return err + } + return c.DeleteHook(context.TODO(), owner, repo, hookIDInt) +} + +func (c *Client) RefreshWebHookSecret(owner, repo, hookID string) error { + hookIDInt, err := strconv.ParseInt(hookID, 10, 64) if err != nil { return err } - return c.DeleteHook(context.TODO(), owner, repo, int64(hookIDInt)) + _, err = c.UpdateHook(context.TODO(), owner, repo, hookIDInt, &git.Hook{ + Secret: gitservice.GetHookSecret(), + }) + + return err } diff --git a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go index cf6f7673..b2ecc88b 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go @@ -44,3 +44,15 @@ func (c *Client) DeleteWebHook(owner, repo, hookID string) error { } return c.DeleteProjectHook(owner, repo, hookIDInt) } + +func (c *Client) RefreshWebHookSecret(owner, repo, hookID string) error { + hookIDInt, err := strconv.Atoi(hookID) + if err != nil { + return err + } + _, err = c.UpdateProjectHook(owner, repo, hookIDInt, &git.Hook{ + Secret: gitservice.GetHookSecret(), + }) + + return err +} diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 77f4e7ce..03f56b29 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -192,6 +192,29 @@ func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.H return nil, err } +func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, hook *git.Hook) (*github.Hook, error) { + h := &github.Hook{ + Config: map[string]interface{}{ + "secret": hook.Secret, + }, + } + if hook.URL != "" { + h.Config["url"] = hook.URL + } + if len(hook.Events) > 0 { + h.Events = hook.Events + } + if hook.Active != nil { + h.Active = hook.Active + } + updated, err := wrap(c.Repositories.EditHook(ctx, owner, repo, id, h)) + if h, ok := updated.(*github.Hook); ok { + return h, err + } + + return nil, err +} + func (c *Client) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, error) { created, err := wrap(c.Repositories.CreateStatus(ctx, owner, repo, ref, status)) if s, ok := created.(*github.RepoStatus); ok { diff --git a/pkg/tool/git/gitlab/project.go b/pkg/tool/git/gitlab/project.go index bea1ea80..edfee8bd 100644 --- a/pkg/tool/git/gitlab/project.go +++ b/pkg/tool/git/gitlab/project.go @@ -74,6 +74,27 @@ func (c *Client) AddProjectHook(owner, repo string, hook *git.Hook) (*gitlab.Pro return nil, err } +func (c *Client) UpdateProjectHook(owner, repo string, id int, hook *git.Hook) (*gitlab.ProjectHook, error) { + opts := &gitlab.EditProjectHookOptions{} + if hook.URL != "" { + opts.URL = gitlab.String(hook.URL) + } + if hook.Secret != "" { + opts.Token = gitlab.String(hook.Secret) + } + opts = addEventsToProjectHookEditOptions(hook.Events, opts) + updated, err := wrap(c.Projects.EditProjectHook(generateProjectName(owner, repo), id, opts)) + if err != nil { + return nil, err + } + + if h, ok := updated.(*gitlab.ProjectHook); ok { + return h, nil + } + + return nil, err +} + func (c *Client) DeleteProjectHook(owner, repo string, id int) error { err := wrapError(c.Projects.DeleteProjectHook(generateProjectName(owner, repo), id)) if httpclient.IsNotFound(err) { @@ -140,3 +161,18 @@ func addEventsToProjectHookOptions(events []string, opts *gitlab.AddProjectHookO return opts } + +func addEventsToProjectHookEditOptions(events []string, opts *gitlab.EditProjectHookOptions) *gitlab.EditProjectHookOptions { + for _, evt := range events { + switch evt { + case git.PushEvent: + opts.PushEvents = boolptr.True() + case git.PullRequestEvent: + opts.MergeRequestsEvents = boolptr.True() + case git.BranchOrTagCreateEvent: + opts.TagPushEvents = boolptr.True() + } + } + + return opts +} -- Gitee From 27b61b61d1fd0efb441cc9c8fe0791c2b121edd9 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 8 Nov 2021 20:23:34 +0800 Subject: [PATCH 0551/2647] update after review Signed-off-by: lou --- pkg/tool/git/github/repositories.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 03f56b29..bee1413b 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -185,6 +185,10 @@ func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.H Active: hook.Active, } created, err := wrap(c.Repositories.CreateHook(ctx, owner, repo, h)) + if err != nil { + return nil, err + } + if h, ok := created.(*github.Hook); ok { return h, err } @@ -208,6 +212,10 @@ func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, h h.Active = hook.Active } updated, err := wrap(c.Repositories.EditHook(ctx, owner, repo, id, h)) + if err != nil { + return nil, err + } + if h, ok := updated.(*github.Hook); ok { return h, err } -- Gitee From 239af662a9ba86a1999c78909f21b43cd6e8e37c Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 8 Nov 2021 20:57:37 +0800 Subject: [PATCH 0552/2647] update after review Signed-off-by: lou --- .../aslan/core/common/service/github/webhook.go | 2 +- pkg/tool/git/github/repositories.go | 16 ++++++++-------- pkg/tool/git/gitlab/project.go | 13 +++++++------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/github/webhook.go b/pkg/microservice/aslan/core/common/service/github/webhook.go index 7850b805..433d8f57 100644 --- a/pkg/microservice/aslan/core/common/service/github/webhook.go +++ b/pkg/microservice/aslan/core/common/service/github/webhook.go @@ -35,7 +35,7 @@ func (c *Client) CreateWebHook(owner, repo string) (string, error) { return "", err } - return strconv.Itoa(int(hook.GetID())), err + return strconv.Itoa(int(hook.GetID())), nil } func (c *Client) DeleteWebHook(owner, repo, hookID string) error { diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index bee1413b..e28316b5 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -189,11 +189,11 @@ func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.H return nil, err } - if h, ok := created.(*github.Hook); ok { - return h, err + if h, ok := created.(*github.Hook); !ok { + return nil, fmt.Errorf("object is not a github Hook") + } else { + return h, nil } - - return nil, err } func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, hook *git.Hook) (*github.Hook, error) { @@ -216,11 +216,11 @@ func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, h return nil, err } - if h, ok := updated.(*github.Hook); ok { - return h, err + if h, ok := updated.(*github.Hook); !ok { + return nil, fmt.Errorf("object is not a github Hook") + } else { + return h, nil } - - return nil, err } func (c *Client) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, error) { diff --git a/pkg/tool/git/gitlab/project.go b/pkg/tool/git/gitlab/project.go index edfee8bd..2c583749 100644 --- a/pkg/tool/git/gitlab/project.go +++ b/pkg/tool/git/gitlab/project.go @@ -17,6 +17,7 @@ limitations under the License. package gitlab import ( + "fmt" "strings" "github.com/xanzy/go-gitlab" @@ -67,11 +68,11 @@ func (c *Client) AddProjectHook(owner, repo string, hook *git.Hook) (*gitlab.Pro return nil, err } - if h, ok := created.(*gitlab.ProjectHook); ok { + if h, ok := created.(*gitlab.ProjectHook); !ok { + return nil, fmt.Errorf("object is not a gitlab Hook") + } else { return h, nil } - - return nil, err } func (c *Client) UpdateProjectHook(owner, repo string, id int, hook *git.Hook) (*gitlab.ProjectHook, error) { @@ -88,11 +89,11 @@ func (c *Client) UpdateProjectHook(owner, repo string, id int, hook *git.Hook) ( return nil, err } - if h, ok := updated.(*gitlab.ProjectHook); ok { + if h, ok := updated.(*gitlab.ProjectHook); !ok { + return nil, fmt.Errorf("object is not a gitlab Hook") + } else { return h, nil } - - return nil, err } func (c *Client) DeleteProjectHook(owner, repo string, id int) error { -- Gitee From 3f696e110a5a15685d234283830aab16f029cc9c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 8 Nov 2021 21:12:51 +0800 Subject: [PATCH 0553/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/system/handler/external_link.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/external_link.go b/pkg/microservice/aslan/core/system/handler/external_link.go index 5df1edd5..cbe7c44c 100644 --- a/pkg/microservice/aslan/core/system/handler/external_link.go +++ b/pkg/microservice/aslan/core/system/handler/external_link.go @@ -29,10 +29,10 @@ func CreateExternalLink(c *gin.Context) { args := new(commonmodels.ExternalLink) data, err := c.GetRawData() if err != nil { - log.Errorf("CreateExternalLink c.GetRawData() err : %v", err) + log.Errorf("CreateExternalLink c.GetRawData() err : %s", err) } if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateExternalLink json.Unmarshal err : %v", err) + log.Errorf("CreateExternalLink json.Unmarshal err : %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统配置-快捷链接", fmt.Sprintf("name:%s url:%s", args.Name, args.URL), string(data), ctx.Logger) @@ -54,10 +54,10 @@ func UpdateExternalLink(c *gin.Context) { args := new(commonmodels.ExternalLink) data, err := c.GetRawData() if err != nil { - log.Errorf("UpdateExternal c.GetRawData() err : %v", err) + log.Errorf("UpdateExternal c.GetRawData() err : %s", err) } if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateExternal json.Unmarshal err : %v", err) + log.Errorf("UpdateExternal json.Unmarshal err : %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统配置-快捷链接", fmt.Sprintf("name:%s url:%s", args.Name, args.URL), string(data), ctx.Logger) -- Gitee From 9d23190b8c5460d187fd419c1a453e60559549e2 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 8 Nov 2021 21:15:10 +0800 Subject: [PATCH 0554/2647] update after review --- pkg/tool/git/github/repositories.go | 14 ++++++++------ pkg/tool/git/gitlab/project.go | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index e28316b5..4d38997c 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -189,11 +189,12 @@ func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.H return nil, err } - if h, ok := created.(*github.Hook); !ok { + res, ok := created.(*github.Hook) + if !ok { return nil, fmt.Errorf("object is not a github Hook") - } else { - return h, nil } + + return res, nil } func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, hook *git.Hook) (*github.Hook, error) { @@ -216,11 +217,12 @@ func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, h return nil, err } - if h, ok := updated.(*github.Hook); !ok { + res, ok := updated.(*github.Hook) + if !ok { return nil, fmt.Errorf("object is not a github Hook") - } else { - return h, nil } + + return res, nil } func (c *Client) CreateStatus(ctx context.Context, owner, repo, ref string, status *github.RepoStatus) (*github.RepoStatus, error) { diff --git a/pkg/tool/git/gitlab/project.go b/pkg/tool/git/gitlab/project.go index 2c583749..36f29e0d 100644 --- a/pkg/tool/git/gitlab/project.go +++ b/pkg/tool/git/gitlab/project.go @@ -68,11 +68,12 @@ func (c *Client) AddProjectHook(owner, repo string, hook *git.Hook) (*gitlab.Pro return nil, err } - if h, ok := created.(*gitlab.ProjectHook); !ok { + res, ok := created.(*gitlab.ProjectHook) + if !ok { return nil, fmt.Errorf("object is not a gitlab Hook") - } else { - return h, nil } + + return res, nil } func (c *Client) UpdateProjectHook(owner, repo string, id int, hook *git.Hook) (*gitlab.ProjectHook, error) { @@ -89,11 +90,12 @@ func (c *Client) UpdateProjectHook(owner, repo string, id int, hook *git.Hook) ( return nil, err } - if h, ok := updated.(*gitlab.ProjectHook); !ok { + res, ok := updated.(*gitlab.ProjectHook) + if !ok { return nil, fmt.Errorf("object is not a gitlab Hook") - } else { - return h, nil } + + return res, nil } func (c *Client) DeleteProjectHook(owner, repo string, id int) error { -- Gitee From befc1095ee476f944de6bf5fc48b5ec09415d469 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 8 Nov 2021 21:17:55 +0800 Subject: [PATCH 0555/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/external_link.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go index af87e978..c4473b8e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go @@ -35,7 +35,10 @@ func (c *ExternalLinkColl) GetCollectionName() string { func (c *ExternalLinkColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ - Keys: bson.M{"name": 1, "url": 1}, + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "url", Value: 1}, + }, Options: options.Index().SetUnique(true), } -- Gitee From d7bec2ad126fd769b3f4ae4f4583282bba3a4d49 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 16:15:45 +0800 Subject: [PATCH 0556/2647] codehost auth Signed-off-by: mouuii --- pkg/config/config.go | 4 + .../systemconfig/config/config.go | 4 + .../core/codehost/handler/codehost.go | 85 +++++++++++++------ .../codehost/repository/models/codehost.go | 1 + .../codehost/repository/mongodb/codehost.go | 12 +++ .../core/codehost/service/codehost.go | 4 + pkg/setting/consts.go | 1 + 7 files changed, 84 insertions(+), 27 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 42de9dce..d6edc02e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -266,3 +266,7 @@ func AdminEmail() string { func AdminPassword() string { return viper.GetString(setting.ENVAdminPassword) } + +func HttpProxy() string { + return viper.GetString(setting.ENVHttpProxy) +} diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index 2430e9b9..23e2427d 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -22,3 +22,7 @@ func MongoURI() string { func MongoDatabase() string { return configbase.MongoDatabase() } + +func HttpPorxy() string { + return configbase.HttpProxy() +} diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 0a7ced85..11b82230 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -1,6 +1,7 @@ package handler import ( + "context" "fmt" "net/http" "net/url" @@ -10,6 +11,7 @@ import ( "github.com/gin-gonic/gin" "golang.org/x/oauth2" + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -72,7 +74,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - authStateString := fmt.Sprintf("%s%s%d%s%s", redirect, "&codeHostId=", id, "&provider=", provider) + authStateString := fmt.Sprintf("%s%s%s%s%s", redirect, "&codeHostId=", id, "&provider=", provider) codeHost, err := service.GetCodeHost(idInt, ctx.Logger) if err != nil { ctx.Err = err @@ -85,8 +87,21 @@ func AuthCodeHost(c *gin.Context) { RedirectURL: callBackUrl, //RedirectURL: "http://localhost:34001/directory/codehosts/callback", } + if provider == "gitlab" { + authConfig.Scopes = []string{"api", "read_user"} + authConfig.Endpoint = oauth2.Endpoint{ + AuthURL: fmt.Sprintf("%s%s", codeHost.Address, "/oauth/authorize"), + TokenURL: fmt.Sprintf("%s%s", codeHost.Address, "/oauth/token"), + } + } else if provider == "github" { + authConfig.Scopes = []string{"repo", "user"} + authConfig.Endpoint = oauth2.Endpoint{ + AuthURL: fmt.Sprintf("%s%s", codeHost.Address, "/login/oauth/authorize"), + TokenURL: fmt.Sprintf("%s%s", codeHost.Address, "/login/oauth/access_token"), + } + } redirectURL := authConfig.AuthCodeURL(authStateString) - http.Redirect(c.Writer, c.Request, redirectURL, http.StatusFound) + c.Redirect(http.StatusFound, redirectURL) } func Callback(c *gin.Context) { @@ -103,37 +118,35 @@ func Callback(c *gin.Context) { gitlabError := c.Query("error") if gitlabError != "" { - ctx.Logger.Warn("view code_host_get_call_back_gitlab user denied") - url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 10007, "&errMessage=access_denied") - http.Redirect(c.Writer, c.Request, url, http.StatusFound) + ctx.Logger.Warn("code_host_get_call_back_gitlab user denied") + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=access_denied") + c.Redirect(http.StatusFound, url) return } stateURL, err := url.Parse(state) if err != nil { - url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=failed_to_parse_redirect_url") + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=failed_to_parse_redirect_url") http.Redirect(c.Writer, c.Request, url, http.StatusFound) return } codeHostArray := strings.Split(urlArray[1], "&provider=") codeHostID, err := strconv.Atoi(codeHostArray[0]) if err != nil { - ctx.Logger.Error("view code_host_get_call_back_gitlab codeHostID convert err : %v", err) - url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=codeHostID convert failed") - http.Redirect(c.Writer, c.Request, url, http.StatusFound) + ctx.Logger.Error("code_host_get_call_back_gitlab codeHostID convert err : %v", err) + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=codeHostID convert failed") + c.Redirect(http.StatusFound, url) return } code := c.Query("code") iCodehost, err := service.GetCodeHost(codeHostID, ctx.Logger) if err != nil { - ctx.Logger.Error("view code_host_get_call_back_gitlab GetCodeHostByID err : %v", err) - url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=get codehost failed") - http.Redirect(c.Writer, c.Request, url, http.StatusFound) + ctx.Logger.Errorf("code_host_get_call_back_gitlab GetCodeHostByID err: %v", err) + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=get codehost failed") + c.Redirect(http.StatusFound, url) return } - codehost := new(models.CodeHost) - - callBackUrl := fmt.Sprintf("%s://%s%s", stateURL.Scheme, stateURL.Host, "/api/directory/codehosts/callback") + callBackUrl := fmt.Sprintf("%s://%s%s", stateURL.Scheme, stateURL.Host, "/api/v1/codehosts/callback") authConfig := &oauth2.Config{ ClientID: iCodehost.ApplicationId, ClientSecret: iCodehost.ClientSecret, @@ -153,22 +166,39 @@ func Callback(c *gin.Context) { TokenURL: fmt.Sprintf("%s%s", iCodehost.Address, "/login/oauth/access_token"), } } - - token, err := authConfig.Exchange(oauth2.NoContext, code) + dc := http.DefaultClient + // if http proxy env is set + if config.HttpPorxy() != "" { + p, err := url.Parse("http://proxy.proxy-env-dev:7890") + if err != nil { + ctx.Logger.Errorf("parse proxy err: %v", err) + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=parse proxy err") + c.Redirect(http.StatusFound, url) + return + } + proxy := http.ProxyURL(p) + trans := &http.Transport{ + Proxy: proxy, + } + dc = &http.Client{Transport: trans} + } + ctxx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) + token, err := authConfig.Exchange(ctxx, code) if err != nil { - ctx.Logger.Error("view code_host_get_call_back_gitlab Exchange err : %v", err) - url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 10007, "&errMessage=exchange failed") - http.Redirect(c.Writer, c.Request, url, http.StatusFound) + ctx.Logger.Errorf("code_host_get_call_back_gitlab Exchange err: %v", err) + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=exchange failed") + c.Redirect(http.StatusFound, url) return } - codehost.AccessToken = token.AccessToken - codehost.RefreshToken = token.RefreshToken - _, err = service.UpdateCodeHost(codehost, ctx.Logger) + iCodehost.AccessToken = token.AccessToken + iCodehost.RefreshToken = token.RefreshToken + ctx.Logger.Infof("%+v", iCodehost) + _, err = service.UpdateCodeHostByToken(iCodehost, ctx.Logger) if err != nil { - ctx.Logger.Error("view UpdateCodeHostByToken err : %v", err) - url := fmt.Sprintf("%s%s%d%s", frontEndUrl, "?errCode=", 404, "&errMessage=update codehost failed") - http.Redirect(c.Writer, c.Request, url, http.StatusFound) + ctx.Logger.Errorf("view UpdateCodeHostByToken err: %v", err) + url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=update codehost failed") + c.Redirect(http.StatusFound, url) return } @@ -179,7 +209,8 @@ func Callback(c *gin.Context) { } else { redirectURL = fmt.Sprintf("%s%s", frontEndUrl, "?succeed=true") } - http.Redirect(c.Writer, c.Request, redirectURL, http.StatusFound) + c.Redirect(http.StatusFound, redirectURL) + } func UpdateCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index 522440d0..845be7a2 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -17,6 +17,7 @@ type CodeHost struct { ClientSecret string `bson:"client_secret" json:"clientSecret"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 561a60f7..95c81f05 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -136,3 +136,15 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, _, err := c.Collection.UpdateOne(context.TODO(), query, change) return host, err } + +func (c *CodehostColl) UpdateCodeHostByToken(host *models.CodeHost) (*models.CodeHost, error) { + query := bson.M{"id": host.ID, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "is_ready": "2", + "access_token": host.AccessToken, + "updated_at": time.Now().Unix(), + "refresh_token": host.RefreshToken, + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + return host, err +} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index fbf137ef..ea879355 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -31,6 +31,10 @@ func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHo return mongodb.NewCodehostColl().UpdateCodeHost(host) } +func UpdateCodeHostByToken(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { + return mongodb.NewCodehostColl().UpdateCodeHostByToken(host) +} + func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().GetCodeHostByID(id) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 26352978..a5a1a3e4 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -118,6 +118,7 @@ const ( ENVAdminEmail = "ADMIN_EMAIL" ENVAdminPassword = "ADMIN_PASSWORD" PresetAccount = "admin" + ENVHttpProxy = "HTTP_PROXY" ) // k8s concepts -- Gitee From ff984f0dca9b78c2dbfab7b842c70d384e9445ee Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 16:28:56 +0800 Subject: [PATCH 0557/2647] auth Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 11b82230..fc18d63c 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -11,7 +11,6 @@ import ( "github.com/gin-gonic/gin" "golang.org/x/oauth2" - "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -168,20 +167,20 @@ func Callback(c *gin.Context) { } dc := http.DefaultClient // if http proxy env is set - if config.HttpPorxy() != "" { - p, err := url.Parse("http://proxy.proxy-env-dev:7890") - if err != nil { - ctx.Logger.Errorf("parse proxy err: %v", err) - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=parse proxy err") - c.Redirect(http.StatusFound, url) - return - } - proxy := http.ProxyURL(p) - trans := &http.Transport{ - Proxy: proxy, - } - dc = &http.Client{Transport: trans} - } + //if config.HttpPorxy() != "" { + // p, err := url.Parse("***") + // if err != nil { + // ctx.Logger.Errorf("parse proxy err: %v", err) + // url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=parse proxy err") + // c.Redirect(http.StatusFound, url) + // return + // } + // proxy := http.ProxyURL(p) + // trans := &http.Transport{ + // Proxy: proxy, + // } + // dc = &http.Client{Transport: trans} + //} ctxx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) token, err := authConfig.Exchange(ctxx, code) if err != nil { @@ -196,7 +195,7 @@ func Callback(c *gin.Context) { ctx.Logger.Infof("%+v", iCodehost) _, err = service.UpdateCodeHostByToken(iCodehost, ctx.Logger) if err != nil { - ctx.Logger.Errorf("view UpdateCodeHostByToken err: %v", err) + ctx.Logger.Errorf("UpdateCodeHostByToken err: %v", err) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=update codehost failed") c.Redirect(http.StatusFound, url) return -- Gitee From 94b6d5a660afdb0587d8866d50aa345444ef6d07 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 16:43:07 +0800 Subject: [PATCH 0558/2647] remove code Signed-off-by: mouuii --- pkg/config/config.go | 4 ---- pkg/microservice/systemconfig/config/config.go | 4 ---- .../core/codehost/handler/codehost.go | 15 --------------- pkg/setting/consts.go | 1 - 4 files changed, 24 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index d6edc02e..42de9dce 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -266,7 +266,3 @@ func AdminEmail() string { func AdminPassword() string { return viper.GetString(setting.ENVAdminPassword) } - -func HttpProxy() string { - return viper.GetString(setting.ENVHttpProxy) -} diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index 23e2427d..2430e9b9 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -22,7 +22,3 @@ func MongoURI() string { func MongoDatabase() string { return configbase.MongoDatabase() } - -func HttpPorxy() string { - return configbase.HttpProxy() -} diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index fc18d63c..e43af225 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -166,21 +166,6 @@ func Callback(c *gin.Context) { } } dc := http.DefaultClient - // if http proxy env is set - //if config.HttpPorxy() != "" { - // p, err := url.Parse("***") - // if err != nil { - // ctx.Logger.Errorf("parse proxy err: %v", err) - // url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=parse proxy err") - // c.Redirect(http.StatusFound, url) - // return - // } - // proxy := http.ProxyURL(p) - // trans := &http.Transport{ - // Proxy: proxy, - // } - // dc = &http.Client{Transport: trans} - //} ctxx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) token, err := authConfig.Exchange(ctxx, code) if err != nil { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a5a1a3e4..26352978 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -118,7 +118,6 @@ const ( ENVAdminEmail = "ADMIN_EMAIL" ENVAdminPassword = "ADMIN_PASSWORD" PresetAccount = "admin" - ENVHttpProxy = "HTTP_PROXY" ) // k8s concepts -- Gitee From 4eb5be01e64885d793fa559e5ec1b503d4d5d2d2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 16:50:01 +0800 Subject: [PATCH 0559/2647] log Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/handler/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index e43af225..84ee1d60 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -131,7 +131,7 @@ func Callback(c *gin.Context) { codeHostArray := strings.Split(urlArray[1], "&provider=") codeHostID, err := strconv.Atoi(codeHostArray[0]) if err != nil { - ctx.Logger.Error("code_host_get_call_back_gitlab codeHostID convert err : %v", err) + ctx.Logger.Errorf("code_host_get_call_back_gitlab codeHostID convert err : %v", err) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=codeHostID convert failed") c.Redirect(http.StatusFound, url) return -- Gitee From 02c0cf847f7ebe68e953797bc2105de6970a8af0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 16:54:06 +0800 Subject: [PATCH 0560/2647] log Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/handler/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 84ee1d60..263924e9 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -180,7 +180,7 @@ func Callback(c *gin.Context) { ctx.Logger.Infof("%+v", iCodehost) _, err = service.UpdateCodeHostByToken(iCodehost, ctx.Logger) if err != nil { - ctx.Logger.Errorf("UpdateCodeHostByToken err: %v", err) + ctx.Logger.Errorf("UpdateCodeHostByToken err: %s", err) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=update codehost failed") c.Redirect(http.StatusFound, url) return -- Gitee From aa7d7962e6e7eb6aa772380edd90a7f8d7363920 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 17:16:50 +0800 Subject: [PATCH 0561/2647] refactor Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 263924e9..1f8f0354 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -86,19 +86,23 @@ func AuthCodeHost(c *gin.Context) { RedirectURL: callBackUrl, //RedirectURL: "http://localhost:34001/directory/codehosts/callback", } + var authUrl string + var tokenUrl string + var scopes []string if provider == "gitlab" { - authConfig.Scopes = []string{"api", "read_user"} - authConfig.Endpoint = oauth2.Endpoint{ - AuthURL: fmt.Sprintf("%s%s", codeHost.Address, "/oauth/authorize"), - TokenURL: fmt.Sprintf("%s%s", codeHost.Address, "/oauth/token"), - } + scopes = []string{"api", "read_user"} + authUrl = codeHost.Address + "/oauth/authorize" + tokenUrl = codeHost.Address + "/oauth/token" } else if provider == "github" { - authConfig.Scopes = []string{"repo", "user"} - authConfig.Endpoint = oauth2.Endpoint{ - AuthURL: fmt.Sprintf("%s%s", codeHost.Address, "/login/oauth/authorize"), - TokenURL: fmt.Sprintf("%s%s", codeHost.Address, "/login/oauth/access_token"), - } + scopes = []string{"repo", "user"} + authUrl = codeHost.Address + "/login/oauth/authorize" + tokenUrl = codeHost.Address + "/login/oauth/access_token" + } + authConfig.Endpoint = oauth2.Endpoint{ + AuthURL: authUrl, + TokenURL: tokenUrl, } + authConfig.Scopes = scopes redirectURL := authConfig.AuthCodeURL(authStateString) c.Redirect(http.StatusFound, redirectURL) } @@ -107,7 +111,6 @@ func Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - //增加数据验证 state := c.Query("state") urlArray := strings.Split(state, "&codeHostId=") frontEndUrl := urlArray[0] @@ -117,7 +120,7 @@ func Callback(c *gin.Context) { gitlabError := c.Query("error") if gitlabError != "" { - ctx.Logger.Warn("code_host_get_call_back_gitlab user denied") + ctx.Logger.Warnf("code_host_get_call_back_gitlab user denied: %s", gitlabError) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=access_denied") c.Redirect(http.StatusFound, url) return @@ -131,7 +134,7 @@ func Callback(c *gin.Context) { codeHostArray := strings.Split(urlArray[1], "&provider=") codeHostID, err := strconv.Atoi(codeHostArray[0]) if err != nil { - ctx.Logger.Errorf("code_host_get_call_back_gitlab codeHostID convert err : %v", err) + ctx.Logger.Errorf("code_host_get_call_back_gitlab codeHostID convert err : %s", err) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=codeHostID convert failed") c.Redirect(http.StatusFound, url) return @@ -139,7 +142,7 @@ func Callback(c *gin.Context) { code := c.Query("code") iCodehost, err := service.GetCodeHost(codeHostID, ctx.Logger) if err != nil { - ctx.Logger.Errorf("code_host_get_call_back_gitlab GetCodeHostByID err: %v", err) + ctx.Logger.Errorf("code_host_get_call_back_gitlab GetCodeHostByID err: %s", err) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=get codehost failed") c.Redirect(http.StatusFound, url) return @@ -169,7 +172,7 @@ func Callback(c *gin.Context) { ctxx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) token, err := authConfig.Exchange(ctxx, code) if err != nil { - ctx.Logger.Errorf("code_host_get_call_back_gitlab Exchange err: %v", err) + ctx.Logger.Errorf("code_host_get_call_back_gitlab Exchange err: %s", err) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=exchange failed") c.Redirect(http.StatusFound, url) return @@ -186,7 +189,7 @@ func Callback(c *gin.Context) { return } - //跳转回前端 + //redirect to front end redirectURL := "" if strings.Contains(frontEndUrl, "?succeed") { redirectURL = fmt.Sprintf("%s%s", strings.Split(frontEndUrl, "?succeed")[0], "?succeed=true") -- Gitee From 976d876c2afb1da6667549f41147073a24a8184b Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 17:18:07 +0800 Subject: [PATCH 0562/2647] refactor Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 1f8f0354..ecb5834a 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -80,12 +80,7 @@ func AuthCodeHost(c *gin.Context) { return } callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/v1/codehosts/callback") - authConfig := &oauth2.Config{ - ClientID: codeHost.ApplicationId, - ClientSecret: codeHost.ClientSecret, - RedirectURL: callBackUrl, - //RedirectURL: "http://localhost:34001/directory/codehosts/callback", - } + var authUrl string var tokenUrl string var scopes []string @@ -98,11 +93,16 @@ func AuthCodeHost(c *gin.Context) { authUrl = codeHost.Address + "/login/oauth/authorize" tokenUrl = codeHost.Address + "/login/oauth/access_token" } - authConfig.Endpoint = oauth2.Endpoint{ - AuthURL: authUrl, - TokenURL: tokenUrl, + authConfig := &oauth2.Config{ + ClientID: codeHost.ApplicationId, + ClientSecret: codeHost.ClientSecret, + RedirectURL: callBackUrl, + Endpoint: oauth2.Endpoint{ + AuthURL: authUrl, + TokenURL: tokenUrl, + }, + Scopes: scopes, } - authConfig.Scopes = scopes redirectURL := authConfig.AuthCodeURL(authStateString) c.Redirect(http.StatusFound, redirectURL) } -- Gitee From 71eb33b9b9153b2cde199abfc5718df816be0f82 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 17:23:51 +0800 Subject: [PATCH 0563/2647] refactor Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/handler/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index ecb5834a..775455b8 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -120,7 +120,7 @@ func Callback(c *gin.Context) { gitlabError := c.Query("error") if gitlabError != "" { - ctx.Logger.Warnf("code_host_get_call_back_gitlab user denied: %s", gitlabError) + ctx.Logger.Warnf("code_host_get_call_back_gitlab user denied err: %s", gitlabError) url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=access_denied") c.Redirect(http.StatusFound, url) return -- Gitee From 6222a3eeffe6850bcbdf3f1f79be11a01657b883 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 9 Nov 2021 17:27:33 +0800 Subject: [PATCH 0564/2647] add update user Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/router.go | 4 +++ .../user/core/handler/user/user.go | 28 +++++++++++++++++++ .../user/core/repository/orm/user.go | 8 ++++++ .../user/core/service/user/user.go | 22 +++++++++++++-- 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index 1b35af75..7f09d9c5 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -34,6 +34,10 @@ func (*Router) Inject(router *gin.RouterGroup) { users.PUT("/users/:uid/password", user.UpdatePassword) + users.PUT("/users/:uid", user.UpdateUser) + + users.PUT("/users/:uid/personal", user.UpdatePersonalUser) + users.GET("/users/:uid", user.GetUser) users.DELETE("/users/:uid", user.DeleteUser) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index f3a01fe7..b94787bc 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -72,6 +72,34 @@ func CreateUser(c *gin.Context) { ctx.Resp, ctx.Err = user.CreateUser(args, ctx.Logger) } +func UpdateUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.UpdateUserInfo{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + uid := c.Param("uid") + ctx.Err = user.UpdateUser(uid, args, ctx.Logger) +} + +func UpdatePersonalUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &user.UpdateUserInfo{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + uid := c.Param("uid") + if ctx.UserID != uid { + ctx.Err = e.ErrForbidden + return + } + ctx.Err = user.UpdateUser(uid, args, ctx.Logger) +} + func SignUp(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index 994a89d9..73aeb283 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -125,3 +125,11 @@ func GetUsersCount(name string) (int64, error) { return count, nil } + +// UpdateUser update user info +func UpdateUser(uid string, user *models.User, db *gorm.DB) error { + if err := db.Model(&models.User{}).Where("uid = ?", uid).Updates(user).Error; err != nil { + return err + } + return nil +} diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index b9aab916..3d17ef6a 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -24,13 +24,20 @@ import ( ) type User struct { - Name string `json:"name,omitempty"` + Name string `json:"name"` Password string `json:"password"` - Email string `json:"email,omitempty"` + Email string `json:"email"` Account string `json:"account"` Phone string `json:"phone,omitempty"` } +type UpdateUserInfo struct { + Name string `json:"name"` + Email string `json:"email"` + Phone string `json:"phone,omitempty"` + IdentityType string `json:"identity_type"` +} + type QueryArgs struct { Name string `json:"name,omitempty"` Account string `json:"account,omitempty"` @@ -383,6 +390,17 @@ func CreateUser(args *User, logger *zap.SugaredLogger) (*models.User, error) { return user, tx.Commit().Error } +func UpdateUser(uid string, args *UpdateUserInfo, _ *zap.SugaredLogger) error { + user := &models.User{ + Name: args.Name, + Email: args.Email, + IdentityType: args.IdentityType, + Phone: args.Phone, + } + return orm.UpdateUser(uid, user, core.DB) + +} + func UpdatePassword(args *Password, logger *zap.SugaredLogger) error { user, err := orm.GetUserByUid(args.Uid, core.DB) if err != nil { -- Gitee From ae9d7e08ff20ed964afafbfb3ac00893a723e3ca Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 9 Nov 2021 17:33:28 +0800 Subject: [PATCH 0565/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/service/user/user.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 3d17ef6a..2f2b22e7 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -32,8 +32,8 @@ type User struct { } type UpdateUserInfo struct { - Name string `json:"name"` - Email string `json:"email"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` Phone string `json:"phone,omitempty"` IdentityType string `json:"identity_type"` } -- Gitee From 62173267ca2c336dc7ccc1e734b7adf727c34775 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 17:35:14 +0800 Subject: [PATCH 0566/2647] build fail Signed-off-by: mouuii --- .../aslan/core/code/service/workspace.go | 1 + .../aslan/core/common/service/fs/git.go | 1 + .../core/common/service/gerrit/gerrit.go | 1 + .../core/common/service/scmnotify/client.go | 1 + .../aslan/core/common/service/workflow.go | 1 + .../aslan/core/service/service/helm.go | 5 ++-- .../aslan/core/service/service/loader.go | 27 +++++++++---------- .../aslan/core/service/service/new_loader.go | 8 +++--- .../service/webhook/github_workflow_task.go | 1 + .../service/webhook/gitlab_workflow_task.go | 1 + .../core/workflow/service/workflow/github.go | 1 + 11 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index 6eac61dc..434a1828 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -28,6 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" ) diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index a7dce1a0..0d76a793 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -27,6 +27,7 @@ import ( githubservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" gitlabservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/log" ) diff --git a/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go b/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go index dc01a6a2..6f4fa71a 100644 --- a/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go @@ -26,6 +26,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/gerrit" ) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index 7fcbc14a..c3c8b51f 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -27,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/gerrit" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 373e4097..eb174190 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -30,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" ) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d7d108cf..a053a45c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -19,6 +19,7 @@ package service import ( "bytes" "fmt" + "io/fs" "os" "path/filepath" @@ -47,7 +48,7 @@ import ( templatestore "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -344,7 +345,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil } -func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *poetry.CodeHost, error) { +func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *systemconfig.CodeHost, error) { if repoLink != "" { return setting.SourceFromPublicRepo, nil, nil } diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 28393c1b..4043e2c9 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -34,8 +34,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/codehost" - "github.com/koderover/zadig/pkg/shared/poetry" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" @@ -113,12 +112,12 @@ func ValidateServiceUpdate(codehostID int, serviceName, repoOwner, repoName, rep } // 根据repo信息获取gerrit可以加载的服务列表 -func preloadGerritService(detail *poetry.CodeHost, repoName, branchName, remoteName, loadPath string, isDir bool) ([]string, error) { +func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, remoteName, loadPath string, isDir bool) ([]string, error) { ret := make([]string, 0) base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - chDetail := &codehost.Detail{ + chDetail := &systemconfig.Detail{ ID: detail.ID, Name: "", Address: detail.Address, @@ -182,7 +181,7 @@ func preloadGerritService(detail *poetry.CodeHost, repoName, branchName, remoteN } // 根据 repo 信息获取 codehub 可以加载的服务列表 -func preloadCodehubService(detail *poetry.CodeHost, repoName, repoUUID, branchName, path string, isDir bool) ([]string, error) { +func preloadCodehubService(detail *systemconfig.CodeHost, repoName, repoUUID, branchName, path string, isDir bool) ([]string, error) { var ret []string codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region) @@ -239,10 +238,10 @@ func preloadCodehubService(detail *poetry.CodeHost, repoName, repoUUID, branchNa } // 根据repo信息从gerrit加载服务 -func loadGerritService(username string, ch *poetry.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(&codehost.Detail{Source: ch.Type, Address: ch.Address, OauthToken: ch.AccessToken}, repoOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(&systemconfig.Detail{Source: ch.Type, Address: ch.Address, OauthToken: ch.AccessToken}, repoOwner, repoName, branchName, remoteName) if err != nil { return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } @@ -378,7 +377,7 @@ func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loa } // load codehub service -func loadCodehubService(username string, ch *poetry.CodeHost, repoOwner, repoName, repoUUID, branchName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, repoUUID, branchName string, args *LoadServiceReq, log *zap.SugaredLogger) error { codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region) if !args.LoadFromDir { @@ -449,7 +448,7 @@ func loadCodehubService(username string, ch *poetry.CodeHost, repoOwner, repoNam return nil } -func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeNode, ch *poetry.CodeHost, username, repoOwner, repoName, repoUUID, branchName, path string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeNode, ch *systemconfig.CodeHost, username, repoOwner, repoName, repoUUID, branchName, path string, args *LoadServiceReq, log *zap.SugaredLogger) error { pathList := strings.Split(path, "/") var splittedYaml []string serviceName := pathList[len(pathList)-1] @@ -504,7 +503,7 @@ func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeN } // 根据github repo决定服务是否可以更新这个repo地址 -func validateServiceUpdateGithub(detail *poetry.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { +func validateServiceUpdateGithub(detail *systemconfig.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { ctx := context.Background() tokenSource := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: detail.AccessToken}, @@ -551,7 +550,7 @@ func validateServiceUpdateGithub(detail *poetry.CodeHost, serviceName, repoOwner } // 根据gitlab repo决定服务是否可以更新这个repo地址 -func validateServiceUpdateGitlab(detail *poetry.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { +func validateServiceUpdateGitlab(detail *systemconfig.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { repoInfo := fmt.Sprintf("%s/%s", repoOwner, repoName) gitlabClient, err := gitlab.NewOAuthClient(detail.AccessToken, gitlab.WithBaseURL(detail.Address)) @@ -603,10 +602,10 @@ func validateServiceUpdateGitlab(detail *poetry.CodeHost, serviceName, repoOwner } // 根据gerrit repo决定服务是否可以更新这个repo地址 -func validateServiceUpdateGerrit(detail *poetry.CodeHost, serviceName, repoName, branchName, remoteName, loadPath string, isDir bool) error { +func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, repoName, branchName, remoteName, loadPath string, isDir bool) error { base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - chDetail := &codehost.Detail{ + chDetail := &systemconfig.Detail{ ID: detail.ID, Name: "", Address: detail.Address, @@ -655,7 +654,7 @@ func validateServiceUpdateGerrit(detail *poetry.CodeHost, serviceName, repoName, return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") } -func validateServiceUpdateCodehub(detail *poetry.CodeHost, serviceName, repoName, repoUUID, branchName, loadPath string, isDir bool) error { +func validateServiceUpdateCodehub(detail *systemconfig.CodeHost, serviceName, repoName, repoUUID, branchName, loadPath string, isDir bool) error { codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region) // 非文件夹情况下直接获取文件信息 if !isDir { diff --git a/pkg/microservice/aslan/core/service/service/new_loader.go b/pkg/microservice/aslan/core/service/service/new_loader.go index 3c2a0576..181c18e2 100644 --- a/pkg/microservice/aslan/core/service/service/new_loader.go +++ b/pkg/microservice/aslan/core/service/service/new_loader.go @@ -30,13 +30,13 @@ import ( githubservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" gitlabservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) -func preloadService(ch *poetry.CodeHost, owner, repo, branch, path string, isDir bool, logger *zap.SugaredLogger) ([]string, error) { +func preloadService(ch *systemconfig.CodeHost, owner, repo, branch, path string, isDir bool, logger *zap.SugaredLogger) ([]string, error) { logger.Infof("Preloading service from %s with owner %s, repo %s, branch %s and path %s", ch.Type, owner, repo, branch, path) loader, err := getLoader(ch) @@ -95,7 +95,7 @@ type serviceInfo struct { yamls []string } -func loadService(username string, ch *poetry.CodeHost, owner, repo, branch string, args *LoadServiceReq, logger *zap.SugaredLogger) error { +func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch string, args *LoadServiceReq, logger *zap.SugaredLogger) error { logger.Infof("Loading service from %s with owner %s, repo %s, branch %s and path %s", ch.Type, owner, repo, branch, args.LoadPath) project, err := templaterepo.NewProductColl().Find(args.ProductName) @@ -236,7 +236,7 @@ type yamlLoader interface { GetTree(owner, repo, path, branch string) ([]*git.TreeNode, error) } -func getLoader(ch *poetry.CodeHost) (yamlLoader, error) { +func getLoader(ch *systemconfig.CodeHost) (yamlLoader, error) { switch ch.Type { case setting.SourceFromGithub: return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()), nil diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index b2dab64d..4d882d9c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -33,6 +33,7 @@ import ( git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/types" ) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 103d67d0..e83735d9 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -36,6 +36,7 @@ import ( environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/github.go b/pkg/microservice/aslan/core/workflow/service/workflow/github.go index fe6b4349..0a347bc2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/github.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/github.go @@ -27,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" ) -- Gitee From d67b237c4e5e372c418a963a7b8df3a1800fcea5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 9 Nov 2021 18:29:14 +0800 Subject: [PATCH 0567/2647] optimize db index Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/external_link.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go index a92a20a7..5c675613 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go @@ -35,10 +35,12 @@ func (c *ExternalLinkColl) GetCollectionName() string { func (c *ExternalLinkColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ - Keys: bson.M{"name": 1, "url": 1}, - Options: options.Index().SetUnique(false), + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "url", Value: 1}, + }, + Options: options.Index().SetUnique(true), } - _, err := c.Indexes().CreateOne(ctx, mod) return err } -- Gitee From 9cacbabaf67260f100c4dd156646f39eb8e5f0b3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 9 Nov 2021 18:57:53 +0800 Subject: [PATCH 0568/2647] fix compile error Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/service.go | 2 +- pkg/microservice/aslan/core/service/service/helm.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index e414718c..11076eae 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -80,7 +80,7 @@ func UpdateService(c *gin.Context) { func UpdateServiceRevision(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.Username, c.Param("productName"), "更新Revision", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), fmt.Sprintf("%s,%s", permission.TestEnvManageUUID, permission.ProdEnvManageUUID), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新Revision", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) revision, err := strconv.ParseInt(c.DefaultQuery("revision", "0"), 10, 64) if err != nil || revision == 0 { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index fede41d5..e3e9c083 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -28,8 +28,6 @@ import ( "text/template" "time" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/27149chen/afero" "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" -- Gitee From 528be9c5d2962e18208d67a9edba10c5098fd26c Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 9 Nov 2021 19:06:39 +0800 Subject: [PATCH 0569/2647] add update path --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 143 ++++++++++++++++++ .../internal/upgradepath/versions.go | 2 + .../core/common/repository/mongodb/webhook.go | 14 ++ .../core/common/service/github/webhook.go | 1 + .../core/common/service/gitlab/webhook.go | 1 + .../core/service/bundle/exemption_urls.go | 2 +- .../core/codehost/handler/codehost.go | 2 +- .../codehost/repository/models/codehost.go | 32 ++-- .../codehost/repository/mongodb/codehost.go | 14 +- .../core/codehost/service/codehost.go | 7 +- pkg/shared/client/systemconfig/codehost.go | 37 ++++- 11 files changed, 226 insertions(+), 29 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/170.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go new file mode 100644 index 00000000..c0bbc2e2 --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -0,0 +1,143 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrate + +import ( + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" +) + +func init() { + upgradepath.AddHandler(upgradepath.V160, upgradepath.V170, V160ToV170) + upgradepath.AddHandler(upgradepath.V170, upgradepath.V160, V170ToV160) +} + +// V160ToV170 refreshes the secret of all webhooks +func V160ToV170() error { + log.Info("Migrating data from 1.6.0 to 1.7.0") + + err := refreshWebHookSecret() + if err != nil { + log.Errorf("Failed to refresh webhook secret, err: %s", err) + return err + } + + return nil +} + +func V170ToV160() error { + log.Info("Rollback data from 1.7.0 to 1.6.0") + return nil +} + +type hookRefresher interface { + RefreshWebHookSecret(owner, repo, hookID string) error +} + +func refreshWebHookSecret() error { + hooks, err := mongodb.NewWebHookColl().List() + if err != nil { + log.Errorf("Failed to list webhooks, err: %s", err) + return err + } + + codeHosts, err := systemconfig.New().ListCodeHosts() + if err != nil { + log.Errorf("Failed to list codehosts, err: %s", err) + return err + } + + cs := toCodeHostSet(codeHosts) + for _, hook := range hooks { + var cl hookRefresher + ch, ok := cs[codeHostUniqueID{address: hook.Address, owner: hook.Owner}] + if !ok { + log.Warnf("Failed to get codehost for %s/%s", hook.Address, hook.Owner) + continue + } + + switch ch.Type { + case setting.SourceFromGithub: + cl = github.NewClient(ch.accessToken, config.ProxyHTTPSAddr()) + case setting.SourceFromGitlab: + cl, err = gitlab.NewClient(ch.address, ch.accessToken) + if err != nil { + log.Warnf("Failed to create gitlab client, err: %s", err) + continue + } + default: + log.Warnf("Invalid type: %s", ch.Type) + continue + } + + if err = cl.RefreshWebHookSecret(hook.Owner, hook.Repo, hook.HookID); err != nil { + log.Warnf("Failed to refresh webhook secret for hook %d in %s/%s, err: %s", hook.HookID, hook.Owner, hook.Repo, err) + continue + } + } + + return nil +} + +type codeHostUniqueID struct { + address, owner string +} + +type codeHostItem struct { + codeHostUniqueID + accessToken string + Type string +} + +type codeHostSet map[codeHostUniqueID]codeHostItem + +// NewCodeHostSet creates a HookSet from a list of values. +func NewCodeHostSet(items ...codeHostItem) codeHostSet { + ss := codeHostSet{} + ss.Insert(items...) + return ss +} + +// Insert adds items to the set. +func (s codeHostSet) Insert(items ...codeHostItem) codeHostSet { + for _, item := range items { + s[item.codeHostUniqueID] = item + } + return s +} + +func toCodeHostSet(codeHosts []*systemconfig.CodeHost) codeHostSet { + res := NewCodeHostSet() + for _, h := range codeHosts { + res.Insert(codeHostItem{ + codeHostUniqueID: codeHostUniqueID{ + address: h.Address, + owner: h.Namespace, + }, + accessToken: h.AccessToken, + Type: h.Type, + }) + } + + return res +} diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index 476dca70..0304cee0 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -29,6 +29,7 @@ const ( V140 V150 V160 + V170 ) var versionMap versions = map[string]int{ @@ -37,6 +38,7 @@ var versionMap versions = map[string]int{ "1.4.0": V140, "1.5.0": V150, "1.6.0": V160, + "1.7.0": V170, } type versions map[string]int diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go b/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go index 39f153ba..2f68b259 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go @@ -119,6 +119,20 @@ func (c *WebHookColl) Delete(owner, repo, address string) error { return nil } +func (c *WebHookColl) List() ([]*models.WebHook, error) { + res := make([]*models.WebHook, 0) + cursor, err := c.Collection.Find(context.TODO(), bson.M{}) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &res) + if err != nil { + return nil, err + } + + return res, err +} + // Find find webhook func (c *WebHookColl) Find(owner, repo, address string) (*models.WebHook, error) { query := bson.M{"owner": owner, "repo": repo, "address": address} diff --git a/pkg/microservice/aslan/core/common/service/github/webhook.go b/pkg/microservice/aslan/core/common/service/github/webhook.go index 433d8f57..4544ea73 100644 --- a/pkg/microservice/aslan/core/common/service/github/webhook.go +++ b/pkg/microservice/aslan/core/common/service/github/webhook.go @@ -52,6 +52,7 @@ func (c *Client) RefreshWebHookSecret(owner, repo, hookID string) error { return err } _, err = c.UpdateHook(context.TODO(), owner, repo, hookIDInt, &git.Hook{ + URL: config.WebHookURL(), Secret: gitservice.GetHookSecret(), }) diff --git a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go index b2ecc88b..7251fea9 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go @@ -51,6 +51,7 @@ func (c *Client) RefreshWebHookSecret(owner, repo, hookID string) error { return err } _, err = c.UpdateProjectHook(owner, repo, hookIDInt, &git.Hook{ + URL: config.WebHookURL(), Secret: gitservice.GetHookSecret(), }) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 7c4fff0d..b84f5631 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -201,7 +201,7 @@ var systemAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/proxy/config"}, }, { - Methods: []string{"GET", "DELETE"}, + Methods: []string{"*"}, Endpoints: []string{"api/v1/users/?*"}, }, { diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 775455b8..05478b07 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -30,7 +30,7 @@ func CreateCodeHost(c *gin.Context) { func ListCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.FindCodeHost(ctx.Logger) + ctx.Resp, ctx.Err = service.List(c.Query("address"), c.Query("owner"), ctx.Logger) } func DeleteCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index 845be7a2..bbbed219 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -1,23 +1,21 @@ package models type CodeHost struct { - ID int `bson:"id" json:"id"` - Name string `bson:"name" json:"name"` - Type string `bson:"type" json:"type"` - Address string `bson:"address" json:"address"` - IsReady string `bson:"is_ready" json:"ready"` - AccessToken string `bson:"access_token" json:"accessToken"` - RefreshToken string `bson:"refresh_token" json:"refreshToken"` - Namespace string `bson:"namespace" json:"namespace"` - OrganizationID int `bson:"organization_id" json:"orgId"` - ApplicationId string `bson:"application_id" json:"applicationId"` - Region string `bson:"region" json:"region,omitempty"` - Username string `bson:"username" json:"username,omitempty"` - Password string `bson:"password" json:"password,omitempty"` - ClientSecret string `bson:"client_secret" json:"clientSecret"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` + Address string `bson:"address" json:"address"` + IsReady string `bson:"is_ready" json:"ready"` + AccessToken string `bson:"access_token" json:"accessToken"` + RefreshToken string `bson:"refresh_token" json:"refreshToken"` + Namespace string `bson:"namespace" json:"namespace"` + ApplicationId string `bson:"application_id" json:"applicationId"` + Region string `bson:"region" json:"region,omitempty"` + Username string `bson:"username" json:"username,omitempty"` + Password string `bson:"password" json:"password,omitempty"` + ClientSecret string `bson:"client_secret" json:"clientSecret"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 95c81f05..0bc5836e 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -19,6 +19,11 @@ type CodehostColl struct { coll string } +type ListArgs struct { + Owner string `json:"owner"` + Address string `json:"address"` +} + func NewCodehostColl() *CodehostColl { name := models.CodeHost{}.TableName() coll := &CodehostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} @@ -71,9 +76,15 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { return codehost, nil } -func (c *CodehostColl) FindCodeHosts() ([]*models.CodeHost, error) { +func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { codeHosts := make([]*models.CodeHost, 0) query := bson.M{"deleted_at": 0} + if args.Address != "" { + query["address"] = args.Address + } + if args.Owner != "" { + query["namespace"] = args.Owner + } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { @@ -122,7 +133,6 @@ func (c *CodehostColl) DeleteCodeHostByID(ID int) error { func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, error) { query := bson.M{"id": host.ID, "deleted_at": 0} change := bson.M{"$set": bson.M{ - "name": host.Name, "type": host.Type, "address": host.Address, "namespace": host.Namespace, diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index ea879355..e2335d67 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -19,8 +19,11 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co return mongodb.NewCodehostColl().AddCodeHost(codehost) } -func FindCodeHost(_ *zap.SugaredLogger) ([]*models.CodeHost, error) { - return mongodb.NewCodehostColl().FindCodeHosts() +func List(address, owner string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { + return mongodb.NewCodehostColl().List(&mongodb.ListArgs{ + Address: address, + Owner: owner, + }) } func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index f84e6eaf..ed3344f2 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -23,12 +23,7 @@ type CodeHost struct { } func GetCodeHostList() ([]*CodeHost, error) { - var list []*CodeHost - _, err := New().Post(fmt.Sprintf("/api/v1/codehost"), httpclient.SetResult(&list)) - if err != nil { - return nil, err - } - return list, nil + return New().ListCodeHosts() } type Option struct { @@ -131,3 +126,33 @@ func ListCodehostDetial() ([]*Detail, error) { return details, nil } + +func (c *Client) ListCodeHosts() ([]*CodeHost, error) { + url := "/codehosts" + + res := make([]*CodeHost, 0) + _, err := c.Get(url, httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *Client) GetCodeHostByAddressAndOwner(address, owner string) (*CodeHost, error) { + url := "/codehosts" + + res := make([]*CodeHost, 0) + _, err := c.Get(url, httpclient.SetQueryParam("address", address), httpclient.SetQueryParam("owner", owner), httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + if len(res) == 0 { + return nil, fmt.Errorf("no codehost found") + } else if len(res) > 1 { + return nil, fmt.Errorf("more than one codehosts found") + } + + return res[0], nil +} -- Gitee From 43c898207f884dbb882112377674cbd30ff9342a Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 19:35:53 +0800 Subject: [PATCH 0570/2647] callback Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 775455b8..09a7e70e 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -79,7 +79,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/v1/codehosts/callback") + callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/directory/codehosts/callback") var authUrl string var tokenUrl string @@ -148,7 +148,7 @@ func Callback(c *gin.Context) { return } - callBackUrl := fmt.Sprintf("%s://%s%s", stateURL.Scheme, stateURL.Host, "/api/v1/codehosts/callback") + callBackUrl := fmt.Sprintf("%s://%s%s", stateURL.Scheme, stateURL.Host, "/api/directory/codehosts/callback") authConfig := &oauth2.Config{ ClientID: iCodehost.ApplicationId, ClientSecret: iCodehost.ClientSecret, -- Gitee From 83613eaf4758d15926edfded8bd0df767d8812b7 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 9 Nov 2021 20:04:36 +0800 Subject: [PATCH 0571/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/service/user/user.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 2f2b22e7..06c2aeed 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -32,10 +32,9 @@ type User struct { } type UpdateUserInfo struct { - Name string `json:"name,omitempty"` - Email string `json:"email,omitempty"` - Phone string `json:"phone,omitempty"` - IdentityType string `json:"identity_type"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + Phone string `json:"phone,omitempty"` } type QueryArgs struct { @@ -392,10 +391,9 @@ func CreateUser(args *User, logger *zap.SugaredLogger) (*models.User, error) { func UpdateUser(uid string, args *UpdateUserInfo, _ *zap.SugaredLogger) error { user := &models.User{ - Name: args.Name, - Email: args.Email, - IdentityType: args.IdentityType, - Phone: args.Phone, + Name: args.Name, + Email: args.Email, + Phone: args.Phone, } return orm.UpdateUser(uid, user, core.DB) -- Gitee From 953d55f1e7d45fe0b88025904fa1ba4a582a14b9 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 9 Nov 2021 20:16:56 +0800 Subject: [PATCH 0572/2647] update after review Signed-off-by: lou --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 9d703572..90ad6975 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.6.0" + Version = "1.7.0" BuildNumber = "0" GitCommit = "" ) -- Gitee From 2c2a2eeb827622d66e150ce51aba8a1daed449f3 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 9 Nov 2021 21:11:06 +0800 Subject: [PATCH 0573/2647] improve codehost client Signed-off-by: lou --- .../aslan/core/code/handler/codehost.go | 2 +- .../aslan/core/code/service/branch.go | 5 +- .../aslan/core/code/service/merge_request.go | 5 +- .../aslan/core/code/service/namespace.go | 5 +- .../aslan/core/code/service/project.go | 5 +- .../aslan/core/code/service/tag.go | 5 +- .../aslan/core/code/service/workspace.go | 4 +- .../core/common/service/command/git_cmd.go | 6 +- .../core/common/service/config_payload.go | 2 +- .../aslan/core/common/service/fs/git.go | 2 +- .../core/common/service/gerrit/gerrit.go | 2 +- .../core/common/service/scmnotify/client.go | 2 +- .../aslan/core/common/service/service.go | 4 +- .../aslan/core/common/service/workflow.go | 4 +- .../aslan/core/service/service/helm.go | 3 +- .../aslan/core/service/service/loader.go | 28 ++----- .../aslan/core/service/service/service.go | 22 ++--- .../core/workflow/service/webhook/gerrit.go | 8 +- .../service/webhook/gerrit_workflow_task.go | 8 +- .../service/webhook/github_workflow_task.go | 4 +- .../service/webhook/gitlab_workflow_task.go | 14 ++-- .../core/workflow/service/workflow/gerrit.go | 20 +---- .../core/workflow/service/workflow/github.go | 4 +- .../core/workflow/service/workflow/gitlab.go | 15 +--- .../service/workflow/pipeline_validation.go | 10 +-- pkg/shared/client/systemconfig/codehost.go | 82 ++++--------------- 26 files changed, 80 insertions(+), 191 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 211114b3..5c2ac180 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -34,7 +34,7 @@ func GetCodeHostList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codeHostSlice := make([]*systemconfig.CodeHost, 0) - codeHosts, err := systemconfig.GetCodeHostList() + codeHosts, err := systemconfig.New().ListCodeHosts() ctx.Err = err for _, codeHost := range codeHosts { codeHost.AccessToken = setting.MaskValue diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index fecb306a..91c2fcf8 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -31,10 +31,7 @@ import ( ) func CodeHostListBranches(codeHostID int, projectName, namespace string, log *zap.SugaredLogger) ([]*Branch, error) { - opt := &systemconfig.Option{ - CodeHostID: codeHostID, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") } diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 6d063356..bbf8c40e 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -31,10 +31,7 @@ import ( ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, log *zap.SugaredLogger) ([]*PullRequest, error) { - opt := &systemconfig.Option{ - CodeHostID: codeHostID, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { return nil, e.ErrCodehostListPrs.AddDesc("git client is nil") } diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index b8e67930..4633e85b 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -41,10 +41,7 @@ const ( ) func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*Namespace, error) { - opt := &systemconfig.Option{ - CodeHostID: codeHostID, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { return nil, e.ErrCodehostListNamespaces.AddDesc("git client is nil") } diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 316327e6..3a210a1c 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -31,10 +31,7 @@ import ( ) func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword string, log *zap.SugaredLogger) ([]*Project, error) { - opt := &systemconfig.Option{ - CodeHostID: codeHostID, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index dc77ed54..4b9af5a0 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -31,10 +31,7 @@ import ( ) func CodeHostListTags(codeHostID int, projectName string, namespace string, log *zap.SugaredLogger) ([]*Tag, error) { - opt := &systemconfig.Option{ - CodeHostID: codeHostID, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { log.Error(err) return nil, e.ErrCodehostListTags.AddDesc("git client is nil") diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index 434a1828..a3b30f5f 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -145,7 +145,7 @@ func GetGitRepoInfo(codehostID int, repoOwner, repoName, branchName, remoteName, if err := os.RemoveAll(base); err != nil { log.Errorf("dir remove err:%v", err) } - detail, err := systemconfig.GetCodehostDetail(codehostID) + detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("GetGitRepoInfo GetCodehostDetail err:%v", err) return fis, e.ErrListRepoDir.AddDesc(err.Error()) @@ -189,7 +189,7 @@ type CodehostFileInfo struct { func GetCodehubRepoInfo(codehostID int, repoUUID, branchName, path string, log *zap.SugaredLogger) ([]*CodehostFileInfo, error) { fileInfos := make([]*CodehostFileInfo, 0) - detail, err := systemconfig.GetCodehostDetail(codehostID) + detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("GetCodehubRepoInfo GetCodehostDetail err:%s", err) return fileInfos, e.ErrListWorkspace.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/service/command/git_cmd.go b/pkg/microservice/aslan/core/common/service/command/git_cmd.go index f60a2170..d28708bb 100644 --- a/pkg/microservice/aslan/core/common/service/command/git_cmd.go +++ b/pkg/microservice/aslan/core/common/service/command/git_cmd.go @@ -65,7 +65,7 @@ type Command struct { IgnoreError bool } -func RunGitCmds(codehostDetail *systemconfig.Detail, repoOwner, repoName, branchName, remoteName string) error { +func RunGitCmds(codehostDetail *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string) error { var ( tokens []string repo *Repo @@ -73,11 +73,11 @@ func RunGitCmds(codehostDetail *systemconfig.Detail, repoOwner, repoName, branch cmds = make([]*Command, 0) ) repo = &Repo{ - Source: codehostDetail.Source, + Source: codehostDetail.Type, Address: codehostDetail.Address, Name: repoName, Branch: branchName, - OauthToken: codehostDetail.OauthToken, + OauthToken: codehostDetail.AccessToken, RemoteName: remoteName, Owner: repoOwner, } diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 8adf52d1..ac244fe3 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -73,7 +73,7 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { } if codeHostID > 0 { - ch, _ := systemconfig.GetCodeHostInfoByID(codeHostID) + ch, _ := systemconfig.New().GetCodeHost(codeHostID) if ch != nil && ch.Type == setting.SourceFromGithub { payload.Github.AccessToken = ch.AccessToken } diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index 0d76a793..4ab9082a 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -101,7 +101,7 @@ func GetPublicTreeGetter(repoLink string) (TreeGetter, error) { } func GetTreeGetter(codeHostID int) (TreeGetter, error) { - ch, err := systemconfig.GetCodeHostInfoByID(codeHostID) + ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { log.Errorf("Failed to get codeHost by id %d, err: %s", codeHostID, err) return nil, err diff --git a/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go b/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go index 6f4fa71a..516d4e17 100644 --- a/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/common/service/gerrit/gerrit.go @@ -37,7 +37,7 @@ func DeleteGerritWebhook(workflow *models.Workflow, log *zap.SugaredLogger) erro continue } - detail, err := systemconfig.GetCodeHostInfoByID(workflowWebhook.MainRepo.CodehostID) + detail, err := systemconfig.New().GetCodeHost(workflowWebhook.MainRepo.CodehostID) if err != nil { log.Errorf("DeleteGerritWebhook GetCodehostDetail err:%v", err) continue diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index c3c8b51f..39964c6b 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -55,7 +55,7 @@ func (c *Client) Comment(notify *models.Notification) error { } } - codeHostDetail, err := systemconfig.GetCodeHostInfoByID(notify.CodehostID) + codeHostDetail, err := systemconfig.New().GetCodeHost(notify.CodehostID) if err != nil { return errors.Wrapf(err, "codehost %d not found to comment", notify.CodehostID) } diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 6bfed47c..bf031f69 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -131,7 +131,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm return nil, e.ErrListTemplate.AddDesc(err.Error()) } - details, err := systemconfig.ListCodehostDetial() + details, err := systemconfig.New().ListCodeHosts() if err != nil { log.Errorf("无法从原有数据中恢复加载信息, listCodehostDetail failed err: %+v", err) return nil, e.ErrListTemplate.AddDesc(err.Error()) @@ -318,7 +318,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { if gitlabAddress, err := GetGitlabAddress(resp.SrcPath); err == nil { - if details, err := systemconfig.ListCodehostDetial(); err == nil { + if details, err := systemconfig.New().ListCodeHosts(); err == nil { for _, detail := range details { if strings.Contains(detail.Address, gitlabAddress) { resp.GerritCodeHostID = detail.ID diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index eb174190..023d7c98 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -167,7 +167,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger wg.Add(1) go func(wh hookItem) { defer wg.Done() - ch, err := systemconfig.GetCodeHostInfoByID(wh.codeHostID) + ch, err := systemconfig.New().GetCodeHost(wh.codeHostID) if err != nil { logger.Errorf("Failed to get codeHost by id %d, err: %s", wh.codeHostID, err) errs = multierror.Append(errs, err) @@ -201,7 +201,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger wg.Add(1) go func(wh hookItem) { defer wg.Done() - ch, err := systemconfig.GetCodeHostInfoByID(wh.codeHostID) + ch, err := systemconfig.New().GetCodeHost(wh.codeHostID) if err != nil { logger.Errorf("Failed to get codeHost by id %d, err: %s", wh.codeHostID, err) errs = multierror.Append(errs, err) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index a053a45c..2185e6de 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -19,7 +19,6 @@ package service import ( "bytes" "fmt" - "io/fs" "os" "path/filepath" @@ -349,7 +348,7 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *system if repoLink != "" { return setting.SourceFromPublicRepo, nil, nil } - ch, err := systemconfig.GetCodeHostInfoByID(repoArgs.CodehostID) + ch, err := systemconfig.New().GetCodeHost(repoArgs.CodehostID) if err != nil { log.Errorf("Failed to get codeHost by id %d, err: %s", repoArgs.CodehostID, err.Error()) return "", ch, err diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 4043e2c9..af9a2bf4 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -52,7 +52,7 @@ type LoadServiceReq struct { func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, branchName, remoteName, path string, isDir bool, log *zap.SugaredLogger) ([]string, error) { var ret []string - ch, err := systemconfig.GetCodeHostInfoByID(codehostID) + ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("Failed to load codehost for preload service list, the error is: %+v", err) return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) @@ -73,7 +73,7 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b // LoadServiceFromCodeHost 根据提供的codehost信息加载服务 func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoName, repoUUID, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { - ch, err := systemconfig.GetCodeHostInfoByID(codehostID) + ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("Failed to load codehost for preload service list, the error is: %+v", err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -92,7 +92,7 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoNam // ValidateServiceUpdate 根据服务名和提供的加载信息确认是否可以更新服务加载地址 func ValidateServiceUpdate(codehostID int, serviceName, repoOwner, repoName, repoUUID, branchName, remoteName, path string, isDir bool, log *zap.SugaredLogger) error { - detail, err := systemconfig.GetCodeHostInfoByID(codehostID) + detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("Failed to load codehost for validate service update, the error is: %+v", err) return e.ErrValidateServiceUpdate.AddDesc(err.Error()) @@ -117,15 +117,7 @@ func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, r base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - chDetail := &systemconfig.Detail{ - ID: detail.ID, - Name: "", - Address: detail.Address, - Owner: detail.Namespace, - Source: detail.Type, - OauthToken: detail.AccessToken, - } - err = command.RunGitCmds(chDetail, setting.GerritDefaultOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(detail, setting.GerritDefaultOwner, repoName, branchName, remoteName) if err != nil { return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) } @@ -241,7 +233,7 @@ func preloadCodehubService(detail *systemconfig.CodeHost, repoName, repoUUID, br func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(&systemconfig.Detail{Source: ch.Type, Address: ch.Address, OauthToken: ch.AccessToken}, repoOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(ch, repoOwner, repoName, branchName, remoteName) if err != nil { return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } @@ -605,15 +597,7 @@ func validateServiceUpdateGitlab(detail *systemconfig.CodeHost, serviceName, rep func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, repoName, branchName, remoteName, loadPath string, isDir bool) error { base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - chDetail := &systemconfig.Detail{ - ID: detail.ID, - Name: "", - Address: detail.Address, - Owner: detail.Namespace, - Source: detail.Type, - OauthToken: detail.AccessToken, - } - err = command.RunGitCmds(chDetail, setting.GerritDefaultOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(detail, setting.GerritDefaultOwner, repoName, branchName, remoteName) if err != nil { return e.ErrValidateServiceUpdate.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 711eb1ed..73c3733f 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1284,15 +1284,15 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { } func updateGerritWebhookByService(lastService, currentService *commonmodels.Service) error { - var codehostDetail *systemconfig.Detail + var codehostDetail *systemconfig.CodeHost var err error if lastService != nil && lastService.Source == setting.SourceFromGerrit { - codehostDetail, err = systemconfig.GetCodehostDetail(lastService.GerritCodeHostID) + codehostDetail, err = systemconfig.New().GetCodeHost(lastService.GerritCodeHostID) if err != nil { log.Errorf("updateGerritWebhookByService GetCodehostDetail err:%v", err) return err } - cl := gerrit.NewHTTPClient(codehostDetail.Address, codehostDetail.OauthToken) + cl := gerrit.NewHTTPClient(codehostDetail.Address, codehostDetail.AccessToken) webhookURLPrefix := fmt.Sprintf("/%s/%s/%s", "a/config/server/webhooks~projects", gerrit.Escape(lastService.GerritRepoName), "remotes") _, _ = cl.Delete(fmt.Sprintf("%s/%s", webhookURLPrefix, gerrit.RemoteName)) _, err = cl.Delete(fmt.Sprintf("%s/%s", webhookURLPrefix, lastService.ServiceName)) @@ -1300,14 +1300,14 @@ func updateGerritWebhookByService(lastService, currentService *commonmodels.Serv log.Errorf("updateGerritWebhookByService deleteGerritWebhook err:%v", err) } } - var detail *systemconfig.Detail + var detail *systemconfig.CodeHost if lastService.GerritCodeHostID == currentService.GerritCodeHostID && codehostDetail != nil { detail = codehostDetail } else { - detail, _ = systemconfig.GetCodehostDetail(currentService.GerritCodeHostID) + detail, _ = systemconfig.New().GetCodeHost(currentService.GerritCodeHostID) } - cl := gerrit.NewHTTPClient(detail.Address, detail.OauthToken) + cl := gerrit.NewHTTPClient(detail.Address, detail.AccessToken) webhookURL := fmt.Sprintf("/%s/%s/%s/%s", "a/config/server/webhooks~projects", gerrit.Escape(currentService.GerritRepoName), "remotes", currentService.ServiceName) if _, err := cl.Get(webhookURL); err != nil { log.Errorf("updateGerritWebhookByService getGerritWebhook err:%v", err) @@ -1327,13 +1327,13 @@ func updateGerritWebhookByService(lastService, currentService *commonmodels.Serv } func createGerritWebhookByService(codehostID int, serviceName, repoName, branchName string) error { - detail, err := systemconfig.GetCodehostDetail(codehostID) + detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("createGerritWebhookByService GetCodehostDetail err:%v", err) return err } - cl := gerrit.NewHTTPClient(detail.Address, detail.OauthToken) + cl := gerrit.NewHTTPClient(detail.Address, detail.AccessToken) webhookURL := fmt.Sprintf("/%s/%s/%s/%s", "a/config/server/webhooks~projects", gerrit.Escape(repoName), "remotes", serviceName) if _, err := cl.Get(webhookURL); err != nil { log.Errorf("createGerritWebhookByService getGerritWebhook err:%v", err) @@ -1352,7 +1352,7 @@ func createGerritWebhookByService(codehostID int, serviceName, repoName, branchN return nil } -func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.Detail, error) { +func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.CodeHost, error) { if service.GerritCodeHostID == 0 { return nil, fmt.Errorf("codehostId不能是空的") } @@ -1362,9 +1362,9 @@ func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.Detail if service.GerritBranchName == "" { return nil, fmt.Errorf("branchName不能是空的") } - ch, _ := systemconfig.GetCodehostDetail(service.GerritCodeHostID) + ch, _ := systemconfig.New().GetCodeHost(service.GerritCodeHostID) - gerritCli := gerrit.NewClient(ch.Address, ch.OauthToken) + gerritCli := gerrit.NewClient(ch.Address, ch.AccessToken) commit, err := gerritCli.GetCommitByBranch(service.GerritRepoName, service.GerritBranchName) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index 3569bcbe..34e733b1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -94,7 +94,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro log.Errorf("updateServiceTemplateByGerritEvent GetServiceTemplate err:%v", err) errs = multierror.Append(errs, err) } - detail, err := systemconfig.GetCodehostDetail(service.GerritCodeHostID) + detail, err := systemconfig.New().GetCodeHost(service.GerritCodeHostID) if err != nil { log.Errorf("updateServiceTemplateByGerritEvent GetCodehostDetail err:%v", err) errs = multierror.Append(errs, err) @@ -228,7 +228,7 @@ func SyncServiceTemplateFromGerrit(service *commonmodels.Service, log *zap.Sugar return nil } -func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.Detail, error) { +func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.CodeHost, error) { if service.GerritCodeHostID == 0 { return nil, fmt.Errorf("codehostId不能是空的") } @@ -238,12 +238,12 @@ func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.Detail if service.GerritBranchName == "" { return nil, fmt.Errorf("branchName不能是空的") } - detail, err := systemconfig.GetCodehostDetail(service.GerritCodeHostID) + detail, err := systemconfig.New().GetCodeHost(service.GerritCodeHostID) if err != nil { return nil, err } - gerritCli := gerrit.NewClient(detail.Address, detail.OauthToken) + gerritCli := gerrit.NewClient(detail.Address, detail.AccessToken) commit, err := gerritCli.GetCommitByBranch(service.GerritRepoName, service.GerritBranchName) if err != nil { return detail, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index 86425582..6ed9cd1a 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -305,12 +305,12 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base if item.WorkflowArgs == nil { continue } - detail, err := systemconfig.GetCodehostDetail(item.MainRepo.CodehostID) + detail, err := systemconfig.New().GetCodeHost(item.MainRepo.CodehostID) if err != nil { log.Errorf("TriggerWorkflowByGerritEvent GetCodehostDetail err:%v", err) return err } - if detail.Source == gerrit.CodehostTypeGerrit { + if detail.Type == gerrit.CodehostTypeGerrit { log.Debugf("TriggerWorkflowByGerritEvent find gerrit hook in workflow %s", workflow.Name) matcher := createGerritEventMatcher(event, body, item, workflow, log) if matcher == nil { @@ -410,7 +410,7 @@ func addWebHookUser(match gerritEventMatcher, domain string) { } } -func checkLatestTaskStaus(pipelineName, mergeRequestID, commitID string, detail *systemconfig.Detail, log *zap.SugaredLogger) bool { +func checkLatestTaskStaus(pipelineName, mergeRequestID, commitID string, detail *systemconfig.CodeHost, log *zap.SugaredLogger) bool { opt := &commonrepo.ListTaskOption{ PipelineName: pipelineName, Type: config.WorkflowType, @@ -434,7 +434,7 @@ func checkLatestTaskStaus(pipelineName, mergeRequestID, commitID string, detail } // 比较本次patchset 和 上一个触发任务的patchset 的change file是否相同 - cli := gerrit.NewClient(detail.Address, detail.OauthToken) + cli := gerrit.NewClient(detail.Address, detail.AccessToken) isDiff, err := cli.CompareTwoPatchset(mergeRequestID, commitID, tasks[0].TriggerBy.CommitID) if err != nil { log.Errorf("CompareTwoPatchset failed, mergeRequestID:%s, patchsetID:%s, oldPatchsetID:%s, err:%v", mergeRequestID, commitID, tasks[0].TriggerBy.CommitID, err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 4d882d9c..3cd1f374 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -340,12 +340,12 @@ func TriggerWorkflowByGithubEvent(event interface{}, baseURI, deliveryID, reques } func findChangedFilesOfPullRequest(event *github.PullRequestEvent, codehostID int) ([]string, error) { - detail, err := systemconfig.GetCodehostDetail(codehostID) + detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } //pullrequest文件修改 - githubCli := git.NewClient(detail.OauthToken, config.ProxyHTTPSAddr()) + githubCli := git.NewClient(detail.AccessToken, config.ProxyHTTPSAddr()) commitComparison, _, err := githubCli.Repositories.CompareCommits(context.Background(), *event.PullRequest.Base.Repo.Owner.Login, *event.PullRequest.Base.Repo.Name, *event.PullRequest.Base.SHA, *event.PullRequest.Head.SHA) if err != nil { return nil, fmt.Errorf("failed to get changes from github, err: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index e83735d9..d8fdc3a4 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -151,13 +151,13 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( hookRepo.Branch = getBranchFromRef(ev.Ref) var changedFiles []string - detail, err := systemconfig.GetCodehostDetail(hookRepo.CodehostID) + detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) if err != nil { gpem.log.Errorf("GetCodehostDetail error: %s", err) return false, err } - client, err := gitlabtool.NewClient(detail.Address, detail.OauthToken) + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken) if err != nil { gpem.log.Errorf("NewClient error: %s", err) return false, err @@ -323,12 +323,12 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([]string, error) { - detail, err := systemconfig.GetCodehostDetail(codehostID) + detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } - client, err := gitlabtool.NewClient(detail.Address, detail.OauthToken) + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) @@ -343,12 +343,12 @@ func InitDiffNote(ev *gitlab.MergeEvent, mainRepo *commonmodels.MainHookRepo, lo body := "KodeRover CI 检查中..." // 调用gitlab api获取相关数据 - detail, err := systemconfig.GetCodehostDetail(mainRepo.CodehostID) + detail, err := systemconfig.New().GetCodeHost(mainRepo.CodehostID) if err != nil { log.Errorf("GetCodehostDetail failed, codehost:%d, err:%v", mainRepo.CodehostID, err) return fmt.Errorf("failed to find codehost %d: %v", mainRepo.CodehostID, err) } - cli, _ := gitlab.NewOAuthClient(detail.OauthToken, gitlab.WithBaseURL(detail.Address)) + cli, _ := gitlab.NewOAuthClient(detail.AccessToken, gitlab.WithBaseURL(detail.Address)) opt := &commonrepo.DiffNoteFindOpt{ CodehostID: mainRepo.CodehostID, @@ -402,7 +402,7 @@ func InitDiffNote(ev *gitlab.MergeEvent, mainRepo *commonmodels.MainHookRepo, lo Source: "gitlab", ProjectID: mainRepo.RepoOwner + "/" + mainRepo.RepoName, Address: detail.Address, - OauthToken: detail.OauthToken, + OauthToken: detail.AccessToken, }, MergeRequestID: ev.ObjectAttributes.IID, CommitID: commitID, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go b/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go index 54d85549..dae225e4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gerrit.go @@ -36,10 +36,7 @@ func CreateGerritWebhook(workflow *commonmodels.Workflow, log *zap.SugaredLogger continue } - opt := &systemconfig.Option{ - CodeHostID: workflowWebhook.MainRepo.CodehostID, - } - detail, err := systemconfig.GetCodeHostInfo(opt) + detail, err := systemconfig.New().GetCodeHost(workflowWebhook.MainRepo.CodehostID) if err != nil { return err } @@ -88,10 +85,7 @@ func UpdateGerritWebhook(currentWorkflow *commonmodels.Workflow, log *zap.Sugare continue } - opt := &systemconfig.Option{ - CodeHostID: oldWorkflowWebhook.MainRepo.CodehostID, - } - detail, err := systemconfig.GetCodeHostInfo(opt) + detail, err := systemconfig.New().GetCodeHost(oldWorkflowWebhook.MainRepo.CodehostID) if err != nil { return err } @@ -113,10 +107,7 @@ func UpdateGerritWebhook(currentWorkflow *commonmodels.Workflow, log *zap.Sugare continue } - opt := &systemconfig.Option{ - CodeHostID: workflowWebhook.MainRepo.CodehostID, - } - detail, err := systemconfig.GetCodeHostInfo(opt) + detail, err := systemconfig.New().GetCodeHost(workflowWebhook.MainRepo.CodehostID) if err != nil { return err } @@ -155,10 +146,7 @@ func DeleteGerritWebhook(workflow *commonmodels.Workflow, log *zap.SugaredLogger continue } - opt := &systemconfig.Option{ - CodeHostID: workflowWebhook.MainRepo.CodehostID, - } - detail, err := systemconfig.GetCodeHostInfo(opt) + detail, err := systemconfig.New().GetCodeHost(workflowWebhook.MainRepo.CodehostID) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/github.go b/pkg/microservice/aslan/core/workflow/service/workflow/github.go index 0a347bc2..57155407 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/github.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/github.go @@ -85,9 +85,9 @@ func createGitCheck(pt *task.Task, log *zap.SugaredLogger) error { } log.Infof("Init GitHub status") - ch, err := systemconfig.GetCodeHostInfoByID(pt.TriggerBy.CodehostID) + ch, err := systemconfig.New().GetCodeHost(pt.TriggerBy.CodehostID) if err != nil { - log.Errorf("GetCodeHostInfoByID failed, err:%v", err) + log.Errorf("Failed to get codeHost, err:%v", err) return e.ErrGithubUpdateStatus.AddErr(err) } gc := github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go index 7b27cde0..21549bde 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go @@ -40,10 +40,7 @@ type RepoCommit struct { } func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) (*RepoCommit, error) { - opt := &systemconfig.Option{ - CodeHostID: id, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(id) if err != nil { return nil, err } @@ -90,10 +87,7 @@ func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) ( } func QueryByTag(id int, owner, name, tag string, log *zap.SugaredLogger) (*RepoCommit, error) { - opt := &systemconfig.Option{ - CodeHostID: id, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(id) if err != nil { return nil, err } @@ -150,10 +144,7 @@ type PRCommit struct { func GetLatestPrCommit(codehostID, pr int, namespace, projectName string, log *zap.SugaredLogger) (*PRCommit, error) { projectID := fmt.Sprintf("%s/%s", namespace, projectName) - opt := &systemconfig.Option{ - CodeHostID: codehostID, - } - ch, err := systemconfig.GetCodeHostInfo(opt) + ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 2479d0b6..689acf36 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -258,10 +258,7 @@ func FmtBuilds(builds []*types.Repository, log *zap.SugaredLogger) { log.Error("codehostID can't be empty") return } - opt := &systemconfig.Option{ - CodeHostID: cID, - } - detail, err := systemconfig.GetCodeHostInfo(opt) + detail, err := systemconfig.New().GetCodeHost(cID) if err != nil { log.Error(err) return @@ -306,10 +303,7 @@ func SetTriggerBuilds(builds []*types.Repository, buildArgs []*types.Repository, } func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { - opt := &systemconfig.Option{ - CodeHostID: build.CodehostID, - } - codeHostInfo, err := systemconfig.GetCodeHostInfo(opt) + codeHostInfo, err := systemconfig.New().GetCodeHost(build.CodehostID) if err != nil { log.Errorf("failed to get codehost detail %d %v", build.CodehostID, err) return diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index ed3344f2..2d754bb8 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -22,10 +22,6 @@ type CodeHost struct { Password string `json:"password"` } -func GetCodeHostList() ([]*CodeHost, error) { - return New().ListCodeHosts() -} - type Option struct { CodeHostType string Address string @@ -34,7 +30,7 @@ type Option struct { } func GetCodeHostInfo(option *Option) (*CodeHost, error) { - codeHosts, err := GetCodeHostList() + codeHosts, err := New().ListCodeHosts() if err != nil { return nil, err } @@ -60,71 +56,16 @@ func GetCodeHostInfo(option *Option) (*CodeHost, error) { return nil, errors.New("not find codeHost") } -func GetCodeHostInfoByID(id int) (*CodeHost, error) { - return GetCodeHostInfo(&Option{CodeHostID: id}) -} - -type Detail struct { - ID int `json:"id"` - Name string `json:"name"` - Address string `json:"address"` - Owner string `json:"repoowner"` - Source string `json:"source"` - OauthToken string `json:"oauth_token"` - Region string `json:"region"` - Username string `json:"username"` - Password string `json:"password"` - AccessKey string `json:"applicationId"` - SecretKey string `json:"clientSecret"` -} - -func GetCodehostDetail(codehostID int) (*Detail, error) { - codehost, err := GetCodeHostInfo(&Option{CodeHostID: codehostID}) - if err != nil { - return nil, err - } - detail := &Detail{ - codehostID, - "", - codehost.Address, - codehost.Namespace, - codehost.Type, - codehost.AccessToken, - codehost.Region, - codehost.Username, - codehost.Password, - codehost.AccessKey, - codehost.SecretKey, - } - - return detail, nil -} +func (c *Client) GetCodeHost(id int) (*CodeHost, error) { + url := fmt.Sprintf("/codehosts/%d", id) -func ListCodehostDetial() ([]*Detail, error) { - codehosts, err := GetCodeHostList() + res := &CodeHost{} + _, err := c.Get(url, httpclient.SetResult(res)) if err != nil { return nil, err } - var details []*Detail - - for _, codehost := range codehosts { - details = append(details, &Detail{ - codehost.ID, - "", - codehost.Address, - codehost.Namespace, - codehost.Type, - codehost.AccessToken, - codehost.Region, - codehost.Username, - codehost.Password, - codehost.AccessKey, - codehost.SecretKey, - }) - } - - return details, nil + return res, nil } func (c *Client) ListCodeHosts() ([]*CodeHost, error) { @@ -139,11 +80,18 @@ func (c *Client) ListCodeHosts() ([]*CodeHost, error) { return res, nil } -func (c *Client) GetCodeHostByAddressAndOwner(address, owner string) (*CodeHost, error) { +func (c *Client) GetCodeHostByAddressAndOwner(address, owner, source string) (*CodeHost, error) { url := "/codehosts" res := make([]*CodeHost, 0) - _, err := c.Get(url, httpclient.SetQueryParam("address", address), httpclient.SetQueryParam("owner", owner), httpclient.SetResult(&res)) + + req := map[string]string{ + "address": address, + "owner": owner, + "source": source, + } + + _, err := c.Get(url, httpclient.SetQueryParams(req), httpclient.SetResult(&res)) if err != nil { return nil, err } -- Gitee From cd336f5046b1bd4b1c60aca7f48ea4abe22af277 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 9 Nov 2021 21:13:54 +0800 Subject: [PATCH 0574/2647] hide secret Signed-off-by: mouuii --- .../picket/core/filter/handler/codehost.go | 15 ++++++++++++++ .../picket/core/filter/handler/router.go | 4 ++++ .../picket/core/filter/service/codehost.go | 20 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 pkg/microservice/picket/core/filter/handler/codehost.go create mode 100644 pkg/microservice/picket/core/filter/service/codehost.go diff --git a/pkg/microservice/picket/core/filter/handler/codehost.go b/pkg/microservice/picket/core/filter/handler/codehost.go new file mode 100644 index 00000000..a7a2fec8 --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/codehost.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListCodeHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListCodeHost(ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 5ec37540..421fdc1a 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -40,4 +40,8 @@ func (*Router) Inject(router *gin.RouterGroup) { { rolebindings.GET("", ListRoleBindings) } + codehosts := router.Group("codehosts") + { + codehosts.GET("", ListCodeHost) + } } diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go new file mode 100644 index 00000000..5a3a732b --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/codehost.go @@ -0,0 +1,20 @@ +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/shared/client/systemconfig" +) + +func ListCodeHost(_ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { + list, err := systemconfig.GetCodeHostList() + if err != nil { + return nil, err + } + for k, _ := range list { + list[k].AccessKey = "***" + list[k].SecretKey = "***" + list[k].AccessToken = "***" + } + return list, nil +} -- Gitee From afb1794e86cbc3f59711c75e5d0ef63867a07b05 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 9 Nov 2021 22:12:53 +0800 Subject: [PATCH 0575/2647] poetry cleanup Signed-off-by: lou --- pkg/config/config.go | 13 -- pkg/microservice/aslan/config/config.go | 4 - .../core/common/repository/models/queue.go | 16 +- .../aslan/core/common/service/service.go | 5 +- .../aslan/core/common/service/utils.go | 7 - .../aslan/core/environment/service/product.go | 6 - .../aslan/core/system/service/capacity.go | 14 -- .../core/workflow/service/webhook/github.go | 5 +- .../core/workflow/service/webhook/utils.go | 8 +- .../workflow/service/workflow/nsq_handlers.go | 5 +- .../service/workflow/pipeline_controller.go | 2 +- .../service/workflow/pipeline_task.go | 4 +- .../service/workflow/pipeline_validation.go | 5 +- .../service/workflow/workflow_task.go | 4 +- pkg/microservice/aslan/server/rest/router.go | 2 +- pkg/microservice/podexec/config/config.go | 7 - .../core/codehost/handler/codehost.go | 2 +- .../codehost/repository/mongodb/codehost.go | 8 +- .../core/codehost/service/codehost.go | 3 +- pkg/microservice/warpdrive/config/config.go | 4 - .../warpdrive/core/service/taskplugin/jira.go | 6 +- .../core/service/types/task/config_payload.go | 4 +- pkg/setting/consts.go | 1 - pkg/setting/types.go | 5 - pkg/shared/client/systemconfig/codehost.go | 37 ++--- .../{poetry => client/systemconfig}/jira.go | 9 +- pkg/shared/codehost/codehost.go | 146 ------------------ pkg/shared/poetry/client.go | 44 ------ pkg/shared/poetry/codehost.go | 46 ------ 29 files changed, 48 insertions(+), 374 deletions(-) rename pkg/shared/{poetry => client/systemconfig}/jira.go (89%) delete mode 100644 pkg/shared/codehost/codehost.go delete mode 100644 pkg/shared/poetry/client.go delete mode 100644 pkg/shared/poetry/codehost.go diff --git a/pkg/config/config.go b/pkg/config/config.go index 42de9dce..92c0d23f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -73,10 +73,6 @@ func RequestLogFile() string { return LogPath() + RequestLogName() } -func PoetryAPIRootKey() string { - return viper.GetString(setting.ENVPoetryAPIRootKey) -} - func GetServiceByCode(code int) *setting.ServiceInfo { return setting.Services[code] } @@ -146,15 +142,6 @@ func CollieServiceAddress() string { return GetServiceAddress(s.Name, s.Port) } -func PoetryServiceInfo() *setting.ServiceInfo { - return GetServiceByCode(setting.Poetry) -} - -func PoetryServiceAddress() string { - s := PoetryServiceInfo() - return GetServiceAddress(s.Name, s.Port) -} - func ConfigServiceInfo() *setting.ServiceInfo { return GetServiceByCode(setting.Config) } diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index d9243f8c..81838113 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -74,10 +74,6 @@ func LogLevel() int { return viper.GetInt(setting.ENVLogLevel) } -func PoetryAPIServer() string { - return configbase.PoetryServiceAddress() -} - func CollieAPIAddress() string { return configbase.CollieServiceAddress() } diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 20922d12..7801b2c4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -64,12 +64,12 @@ type Queue struct { // TestArgs 测试任务参数 TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` - ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` @@ -200,13 +200,13 @@ type RegistryConfig struct { type ReleaseConfig struct { // ReaperImage sets build job image - // e.g. xxx.com/poetry-resources/reaper-plugin:1.0.0 + // e.g. xxx.com/resources/reaper-plugin:1.0.0 ReaperImage string // ReaperBinaryFile sets download url of reaper binary file in build job // e.g. http://resource.koderover.com/reaper-20201014203000 ReaperBinaryFile string // PredatorImage sets docker build image - // e.g. xxx.com/poetry-resources/predator-plugin:v0.1.0 + // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string } diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index bf031f69..78040931 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -38,7 +38,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util/converter" @@ -163,7 +162,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm } detail, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err @@ -356,7 +355,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str } detail, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner, CodeHostID: resp.CodehostID}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err diff --git a/pkg/microservice/aslan/core/common/service/utils.go b/pkg/microservice/aslan/core/common/service/utils.go index 3b811390..a8940492 100644 --- a/pkg/microservice/aslan/core/common/service/utils.go +++ b/pkg/microservice/aslan/core/common/service/utils.go @@ -67,13 +67,6 @@ func SendFailedTaskMessage(username, productName, name, requestID string, workfl SendMessage(username, title, content, requestID, log) return } - - //// 如果是timer创建的任务,通知需要发送给该项目下有编辑工作流权限的用户 - //poetryClient := poetry.New(config.PoetryAPIServer()) - //users, _ := poetryClient.ListProductPermissionUsers(productName, perm, log) - //for _, user := range users { - // SendMessage(user, title, content, requestID, log) - //} } func SendErrorMessage(sender, title, requestID string, err error, log *zap.SugaredLogger) { diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index eef10e4b..c91818ea 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -73,12 +73,6 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { } } - //poetryClient := poetry.New(config.PoetryAPIServer()) - //users, _ := poetryClient.ListProductPermissionUsers("", "", log) - //for _, user := range users { - // commonservice.SendMessage(user, title, content, requestID, log) - //} - log.Warnf("[%s] product %s deleted", product.EnvName, product.ProductName) } } diff --git a/pkg/microservice/aslan/core/system/service/capacity.go b/pkg/microservice/aslan/core/system/service/capacity.go index 37858978..31419d11 100644 --- a/pkg/microservice/aslan/core/system/service/capacity.go +++ b/pkg/microservice/aslan/core/system/service/capacity.go @@ -170,20 +170,6 @@ func sendSyscapNotify(handleErr error, totalCleanTasks *int) { if handleErr != nil { content.Content = fmt.Sprintf("清理时间: %s, 状态: 失败, 内容: %v", now, handleErr) } - - //notifyInfo := &commonmodels.Notify{ - // Type: config.Message, - // Content: content, - // CreateTime: time.Now().Unix(), - // IsRead: false, - //} - - //poetryClient := poetry.New(config.PoetryAPIServer()) - //users, _ := poetryClient.ListProductPermissionUsers("", "", log.SugaredLogger()) - //for _, user := range users { - // notifyInfo.Receiver = user - // notify.NewNotifyClient().CreateNotify(user, notifyInfo) - //} } func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, dryRun bool) error { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 5e0c4ef3..26cc03b8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -38,7 +38,6 @@ import ( workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" githubtool "github.com/koderover/zadig/pkg/tool/git/github" "github.com/koderover/zadig/pkg/types" @@ -189,7 +188,7 @@ func prEventToPipelineTasks(event *github.PullRequestEvent, requestID string, lo return nil, err } ch, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("GetCodeHostInfo failed, err: %v", err) return nil, err @@ -328,7 +327,7 @@ func pushEventToPipelineTasks(event *github.PushEvent, requestID string, log *za return nil, err } ch, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("GetCodeHostInfo failed, err: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index a65bb34a..57951755 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -36,8 +36,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/shared/codehost" - "github.com/koderover/zadig/pkg/shared/poetry" e "github.com/koderover/zadig/pkg/tool/errors" githubtool "github.com/koderover/zadig/pkg/tool/git/github" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" @@ -202,7 +200,7 @@ func syncCodehubLatestCommit(service *commonmodels.Service) error { func getCodehubClientByAddress(address string) (*codehub.Client, error) { opt := &systemconfig.Option{ Address: address, - CodeHostType: codehost.CodeHubProvider, + CodeHostType: systemconfig.CodeHubProvider, } codehost, err := systemconfig.GetCodeHostInfo(opt) if err != nil { @@ -217,7 +215,7 @@ func getCodehubClientByAddress(address string) (*codehub.Client, error) { func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { opt := &systemconfig.Option{ Address: address, - CodeHostType: codehost.GitLabProvider, + CodeHostType: systemconfig.GitLabProvider, } codehost, err := systemconfig.GetCodeHostInfo(opt) if err != nil { @@ -339,7 +337,7 @@ func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) e } ch, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: poetry.GitHubProvider, Address: address, Namespace: owner}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) if err != nil { log.Errorf("GetCodeHostInfo failed, srcPath:%s, err:%v", args.SrcPath, err) return err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 01f804ac..b71b9e88 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -46,7 +46,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" @@ -64,12 +63,11 @@ type TaskAckHandler struct { deliveryArtifactColl *commonrepo.DeliveryArtifactColl deliveryActivityColl *commonrepo.DeliveryActivityColl TestTaskStatColl *commonrepo.TestTaskStatColl - PoetryClient *poetry.Client messages chan *nsq.Message log *zap.SugaredLogger } -func NewTaskAckHandler(poetryServer string, maxInFlight int, log *zap.SugaredLogger) *TaskAckHandler { +func NewTaskAckHandler(maxInFlight int, log *zap.SugaredLogger) *TaskAckHandler { return &TaskAckHandler{ queue: NewPipelineQueue(log), ptColl: commonrepo.NewTaskColl(), @@ -79,7 +77,6 @@ func NewTaskAckHandler(poetryServer string, maxInFlight int, log *zap.SugaredLog deliveryArtifactColl: commonrepo.NewDeliveryArtifactColl(), deliveryActivityColl: commonrepo.NewDeliveryActivityColl(), TestTaskStatColl: commonrepo.NewTestTaskStatColl(), - PoetryClient: poetry.New(poetryServer), messages: make(chan *nsq.Message, maxInFlight*10), log: log, } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go index f4c4cb96..d368d0b5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go @@ -43,7 +43,7 @@ func SubScribeNSQ() error { // init ack consumer ackConfig := nsqservice.Config() ackConfig.MaxInFlight = 50 - ackHandler := NewTaskAckHandler(config.PoetryAPIServer(), ackConfig.MaxInFlight, logger) + ackHandler := NewTaskAckHandler(ackConfig.MaxInFlight, logger) err := nsqservice.SubScribe(setting.TopicAck, "ack", 1, ackConfig, ackHandler) if err != nil { logger.Errorf("ack subscription failed, the error is: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 0a47a946..21140296 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -40,7 +40,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -165,7 +165,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } } - jiraInfo, _ := poetry.GetJiraInfo(config.PoetryAPIServer()) + jiraInfo, _ := systemconfig.New().GetJiraInfo() if jiraInfo != nil { jiraTask, err := AddPipelineJiraSubTask(pipeline, log) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 689acf36..58099249 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -46,7 +46,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -308,7 +307,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { log.Errorf("failed to get codehost detail %d %v", build.CodehostID, err) return } - if codeHostInfo.Type == codehost.GitLabProvider || codeHostInfo.Type == codehost.GerritProvider { + if codeHostInfo.Type == systemconfig.GitLabProvider || codeHostInfo.Type == systemconfig.GerritProvider { if build.CommitID == "" { var commit *RepoCommit var pr *PRCommit @@ -343,7 +342,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { build.CommitMessage = commit.Message build.AuthorName = commit.AuthorName } - } else if codeHostInfo.Type == codehost.CodeHubProvider { + } else if codeHostInfo.Type == systemconfig.CodeHubProvider { codeHubClient := codehub.NewClient(codeHostInfo.AccessKey, codeHostInfo.SecretKey, codeHostInfo.Region) if build.CommitID == "" && build.Branch != "" { branchList, _ := codeHubClient.BranchList(build.RepoUUID) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index b15e4f54..368a4352 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -41,7 +41,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -602,7 +602,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, subTasks = append(subTasks, distributeTasks...) } - jiraInfo, _ := poetry.GetJiraInfo(config.PoetryAPIServer()) + jiraInfo, _ := systemconfig.New().GetJiraInfo() if jiraInfo != nil { jiraTask, err := AddJiraSubTask("", target.Name, target.ServiceName, args.ProductTmplName, log) if err != nil { diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 989cfd36..960dd790 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -63,7 +63,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { public.GET("/health", commonhandler.Health) } - // no auth required, should not be exposed via poetry-api-proxy or will fail + // no auth required router.GET("/api/hub/connect", multiclusterhandler.ClusterConnectFromAgent) router.GET("/api/kodespace/downloadUrl", commonhandler.GetToolDownloadURL) diff --git a/pkg/microservice/podexec/config/config.go b/pkg/microservice/podexec/config/config.go index c75b077e..782d8b45 100644 --- a/pkg/microservice/podexec/config/config.go +++ b/pkg/microservice/podexec/config/config.go @@ -17,16 +17,9 @@ limitations under the License. package config import ( - "github.com/spf13/viper" - configbase "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/setting" ) -func PoetryAPIRootKey() string { - return viper.GetString(setting.ENVPoetryAPIRootKey) -} - func HubServerAddr() string { return configbase.HubServerServiceAddress() } diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index b4b242f0..2364b5a0 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -30,7 +30,7 @@ func CreateCodeHost(c *gin.Context) { func ListCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.List(c.Query("address"), c.Query("owner"), ctx.Logger) + ctx.Resp, ctx.Err = service.List(c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } func DeleteCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 0bc5836e..2c9ea41a 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -20,8 +20,9 @@ type CodehostColl struct { } type ListArgs struct { - Owner string `json:"owner"` - Address string `json:"address"` + Owner string + Address string + Source string } func NewCodehostColl() *CodehostColl { @@ -85,6 +86,9 @@ func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { if args.Owner != "" { query["namespace"] = args.Owner } + if args.Source != "" { + query["type"] = args.Source + } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index e2335d67..54a165c0 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -19,10 +19,11 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co return mongodb.NewCodehostColl().AddCodeHost(codehost) } -func List(address, owner string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { +func List(address, owner, source string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { return mongodb.NewCodehostColl().List(&mongodb.ListArgs{ Address: address, Owner: owner, + Source: source, }) } diff --git a/pkg/microservice/warpdrive/config/config.go b/pkg/microservice/warpdrive/config/config.go index 2795735f..c4f31d1c 100644 --- a/pkg/microservice/warpdrive/config/config.go +++ b/pkg/microservice/warpdrive/config/config.go @@ -34,10 +34,6 @@ func NSQLookupAddrs() []string { return strings.Split(viper.GetString(setting.ENVNsqLookupAddrs), ",") } -func PoetryAPIRootKey() string { - return viper.GetString(setting.ENVPoetryAPIRootKey) -} - func ReleaseImageTimeout() string { return viper.GetString(setting.ReleaseImageTimeout) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go index 892100fd..d14c33c5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go @@ -26,12 +26,11 @@ import ( "github.com/xanzy/go-gitlab" "go.uber.org/zap" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" wdgithub "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/github" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/poetry" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/jira" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -340,8 +339,7 @@ func (p *JiraPlugin) SetEndTime() { func (p *JiraPlugin) getJiraIssue(pipelineTask *task.Task, key string) (*task.JiraIssue, error) { jiraIssue := new(task.JiraIssue) - poetryClient := poetry.New(configbase.PoetryServiceAddress()) - jiraInfo, err := poetryClient.GetJiraInfo() + jiraInfo, err := systemconfig.New().GetJiraInfo() if err != nil { return nil, fmt.Errorf("getJiraInfo [%s] error: %v", key, err) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 0dfdbcda..3b3e6e0a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -121,13 +121,13 @@ type RegistryConfig struct { type ReleaseConfig struct { // ReaperImage sets build job image - // e.g. xxx.com/poetry-resources/reaper-plugin:1.0.0 + // e.g. xxx.com/resources/reaper-plugin:1.0.0 ReaperImage string // ReaperBinaryFile sets download url of reaper binary file in build job // e.g. http://resource.koderover.com/reaper-20201014203000 ReaperBinaryFile string // PredatorImage sets docker build image - // e.g. xxx.com/poetry-resources/predator-plugin:v0.1.0 + // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 26352978..e81dafee 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -28,7 +28,6 @@ const ( ENVMongoDBConnectionString = "MONGODB_CONNECTION_STRING" ENVAslanDBName = "ASLAN_DB" ENVHubAgentImage = "HUB_AGENT_IMAGE" - ENVPoetryAPIRootKey = "POETRY_API_ROOT_KEY" ENVMysqlUser = "MYSQL_USER" ENVMysqlPassword = "MYSQL_PASSWORD" ENVMysqlHost = "MYSQL_HOST" diff --git a/pkg/setting/types.go b/pkg/setting/types.go index 746c65d1..dfec91fa 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -42,7 +42,6 @@ const ( Cron // 5 HubServer // 6 PodExec // 7 - Poetry // 8 SonarQube // 9 WarpDrive // 10 Minio // 11 @@ -86,10 +85,6 @@ var Services = map[int]*ServiceInfo{ Name: "podexec", Port: 27000, }, - Poetry: { - Name: "poetry", - Port: 34001, - }, SonarQube: { Name: "sonarqube", Port: 80, diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 2d754bb8..733fcf0b 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -1,14 +1,18 @@ package systemconfig import ( - "errors" "fmt" - "strings" - "github.com/koderover/zadig/pkg/shared/codehost" "github.com/koderover/zadig/pkg/tool/httpclient" ) +const ( + GitLabProvider = "gitlab" + GitHubProvider = "github" + GerritProvider = "gerrit" + CodeHubProvider = "codehub" +) + type CodeHost struct { ID int `json:"id"` Address string `json:"address"` @@ -29,31 +33,8 @@ type Option struct { CodeHostID int } -func GetCodeHostInfo(option *Option) (*CodeHost, error) { - codeHosts, err := New().ListCodeHosts() - if err != nil { - return nil, err - } - - for _, codeHost := range codeHosts { - if option.CodeHostID != 0 && codeHost.ID == option.CodeHostID { - return codeHost, nil - } else if option.CodeHostID == 0 && option.CodeHostType != "" { - switch option.CodeHostType { - case codehost.GitHubProvider: - ns := strings.ToLower(codeHost.Namespace) - if strings.Contains(option.Address, codeHost.Address) && strings.ToLower(option.Namespace) == ns { - return codeHost, nil - } - default: - if strings.Contains(option.Address, codeHost.Address) { - return codeHost, nil - } - } - } - } - - return nil, errors.New("not find codeHost") +func GetCodeHostInfo(opt *Option) (*CodeHost, error) { + return New().GetCodeHostByAddressAndOwner(opt.Address, opt.Namespace, opt.CodeHostType) } func (c *Client) GetCodeHost(id int) (*CodeHost, error) { diff --git a/pkg/shared/poetry/jira.go b/pkg/shared/client/systemconfig/jira.go similarity index 89% rename from pkg/shared/poetry/jira.go rename to pkg/shared/client/systemconfig/jira.go index 08827995..1b975b59 100644 --- a/pkg/shared/poetry/jira.go +++ b/pkg/shared/client/systemconfig/jira.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package poetry +package systemconfig import ( "github.com/koderover/zadig/pkg/tool/httpclient" @@ -31,7 +31,7 @@ type JiraInfo struct { } func (c *Client) GetJiraInfo() (*JiraInfo, error) { - url := "/directory/jira" + url := "/jira" jira := &JiraInfo{} _, err := c.Get(url, httpclient.SetResult(jira)) @@ -41,8 +41,3 @@ func (c *Client) GetJiraInfo() (*JiraInfo, error) { return jira, nil } - -func GetJiraInfo(host string) (*JiraInfo, error) { - c := New(host) - return c.GetJiraInfo() -} diff --git a/pkg/shared/codehost/codehost.go b/pkg/shared/codehost/codehost.go deleted file mode 100644 index 263a05fd..00000000 --- a/pkg/shared/codehost/codehost.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package codehost - -import ( - "errors" - "strings" - - "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/shared/poetry" -) - -const ( - GitLabProvider = "gitlab" - GitHubProvider = "github" - GerritProvider = "gerrit" - CodeHubProvider = "codehub" -) - -type CodeHost struct { - ID int `json:"id"` - Address string `json:"address"` - Type string `json:"type"` - AccessToken string `json:"accessToken"` - Namespace string `json:"namespace"` -} - -type Option struct { - CodeHostType string - Address string - Namespace string - CodeHostID int -} - -type Detail struct { - ID int `json:"id"` - Name string `json:"name"` - Address string `json:"address"` - Owner string `json:"repoowner"` - Source string `json:"source"` - OauthToken string `json:"oauth_token"` - Region string `json:"region"` - Username string `json:"username"` - Password string `json:"password"` - AccessKey string `json:"applicationId"` - SecretKey string `json:"clientSecret"` -} - -func GetCodeHostList() ([]*poetry.CodeHost, error) { - poetryClient := poetry.New(config.PoetryServiceAddress()) - return poetryClient.ListCodeHosts() -} - -func GetCodeHostInfo(option *Option) (*poetry.CodeHost, error) { - codeHosts, err := GetCodeHostList() - if err != nil { - return nil, err - } - - for _, codeHost := range codeHosts { - if option.CodeHostID != 0 && codeHost.ID == option.CodeHostID { - return codeHost, nil - } else if option.CodeHostID == 0 && option.CodeHostType != "" { - switch option.CodeHostType { - case GitHubProvider: - ns := strings.ToLower(codeHost.Namespace) - if strings.Contains(option.Address, codeHost.Address) && strings.ToLower(option.Namespace) == ns { - return codeHost, nil - } - default: - if strings.Contains(option.Address, codeHost.Address) { - return codeHost, nil - } - } - } - } - - return nil, errors.New("not find codeHost") -} - -func GetCodeHostInfoByID(id int) (*poetry.CodeHost, error) { - return GetCodeHostInfo(&Option{CodeHostID: id}) -} - -func GetCodehostDetail(codehostID int) (*Detail, error) { - codehost, err := GetCodeHostInfo(&Option{CodeHostID: codehostID}) - if err != nil { - return nil, err - } - detail := &Detail{ - codehostID, - "", - codehost.Address, - codehost.Namespace, - codehost.Type, - codehost.AccessToken, - codehost.Region, - codehost.Username, - codehost.Password, - codehost.AccessKey, - codehost.SecretKey, - } - - return detail, nil -} - -func ListCodehostDetial() ([]*Detail, error) { - codehosts, err := GetCodeHostList() - if err != nil { - return nil, err - } - - var details []*Detail - - for _, codehost := range codehosts { - details = append(details, &Detail{ - codehost.ID, - "", - codehost.Address, - codehost.Namespace, - codehost.Type, - codehost.AccessToken, - codehost.Region, - codehost.Username, - codehost.Password, - codehost.AccessKey, - codehost.SecretKey, - }) - } - - return details, nil -} diff --git a/pkg/shared/poetry/client.go b/pkg/shared/poetry/client.go deleted file mode 100644 index 47f93d43..00000000 --- a/pkg/shared/poetry/client.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -const ( - GitLabProvider = "gitlab" - GitHubProvider = "github" - GerritProvider = "gerrit" -) - -type Client struct { - *httpclient.Client - - host string -} - -func New(host string) *Client { - c := httpclient.New( - httpclient.SetHostURL(host), - ) - - return &Client{ - Client: c, - host: host, - } -} diff --git a/pkg/shared/poetry/codehost.go b/pkg/shared/poetry/codehost.go deleted file mode 100644 index f095f9c6..00000000 --- a/pkg/shared/poetry/codehost.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package poetry - -import ( - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type CodeHost struct { - ID int `json:"id"` - Address string `json:"address"` - Type string `json:"type"` - AccessToken string `json:"accessToken"` - Namespace string `json:"namespace"` - Region string `json:"region"` - AccessKey string `json:"applicationId"` - SecretKey string `json:"clientSecret"` - Username string `json:"username"` - Password string `json:"password"` -} - -func (c *Client) ListCodeHosts() ([]*CodeHost, error) { - url := "/directory/codehostss/search" - - codeHosts := make([]*CodeHost, 0) - _, err := c.Get(url, httpclient.SetResult(&codeHosts)) - if err != nil { - return nil, err - } - - return codeHosts, nil -} -- Gitee From 3dd59baa64df937569f1fabdfd1a6a8e64467d34 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 10 Nov 2021 10:22:46 +0800 Subject: [PATCH 0576/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/system/handler/external_link.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/external_link.go b/pkg/microservice/aslan/core/system/handler/external_link.go index cbe7c44c..ac32a51a 100644 --- a/pkg/microservice/aslan/core/system/handler/external_link.go +++ b/pkg/microservice/aslan/core/system/handler/external_link.go @@ -11,7 +11,6 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" - e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) @@ -38,10 +37,10 @@ func CreateExternalLink(c *gin.Context) { c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") - return - } + //if err := c.ShouldBindJSON(&args); err != nil { + // ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") + // return + //} args.UpdateBy = ctx.UserName ctx.Err = service.CreateExternalLink(args, ctx.Logger) @@ -63,10 +62,10 @@ func UpdateExternalLink(c *gin.Context) { c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") - return - } + //if err := c.ShouldBindJSON(&args); err != nil { + // ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") + // return + //} args.UpdateBy = ctx.UserName ctx.Err = service.UpdateExternalLink(c.Param("id"), args, ctx.Logger) -- Gitee From d6441d479ab39a237658b4b5fa59ed6727f06aee Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 10 Nov 2021 10:35:05 +0800 Subject: [PATCH 0577/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/system/handler/external_link.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/external_link.go b/pkg/microservice/aslan/core/system/handler/external_link.go index ac32a51a..37c68a21 100644 --- a/pkg/microservice/aslan/core/system/handler/external_link.go +++ b/pkg/microservice/aslan/core/system/handler/external_link.go @@ -6,6 +6,8 @@ import ( "fmt" "io/ioutil" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -37,10 +39,10 @@ func CreateExternalLink(c *gin.Context) { c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - //if err := c.ShouldBindJSON(&args); err != nil { - // ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") - // return - //} + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") + return + } args.UpdateBy = ctx.UserName ctx.Err = service.CreateExternalLink(args, ctx.Logger) @@ -62,10 +64,10 @@ func UpdateExternalLink(c *gin.Context) { c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - //if err := c.ShouldBindJSON(&args); err != nil { - // ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") - // return - //} + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid externalLink args") + return + } args.UpdateBy = ctx.UserName ctx.Err = service.UpdateExternalLink(c.Param("id"), args, ctx.Logger) -- Gitee From f9ee1167f7ae50bbdc73a36827f23d5bbb6fb09b Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 10:49:13 +0800 Subject: [PATCH 0578/2647] build fail Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go index 5a3a732b..7ea6998f 100644 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ b/pkg/microservice/picket/core/filter/service/codehost.go @@ -7,7 +7,7 @@ import ( ) func ListCodeHost(_ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { - list, err := systemconfig.GetCodeHostList() + list, err := systemconfig.New().ListCodeHosts() if err != nil { return nil, err } -- Gitee From 53d8de2ce069eb34307447c751dd1ee16cd825af Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 11:46:08 +0800 Subject: [PATCH 0579/2647] move api Signed-off-by: mouuii --- .../picket/client/aslan/workflow.go | 22 ++++++ .../picket/core/filter/handler/dev.go | 68 +++++++++++++++++++ .../picket/core/filter/handler/router.go | 5 ++ .../picket/core/filter/service/dev.go | 18 +++++ 4 files changed, 113 insertions(+) create mode 100644 pkg/microservice/picket/core/filter/handler/dev.go create mode 100644 pkg/microservice/picket/core/filter/service/dev.go diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index f4ebc8c1..b5298ff3 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -18,3 +18,25 @@ func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.V return res.Body(), nil } + +func (c *Client) CreateWorkflowTask(header http.Header, qs url.Values, body []byte) ([]byte, error) { + url := "/workflow/workflowtask" + + res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody(body)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} + +func (c *Client) CancelWorkflowTask(header http.Header, qs url.Values, id string, name string) (statusCode int, err error) { + url := fmt.Sprintf("/workflow/workflowtask/id/%s/pipelines/%s", id, name) + + res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return http.StatusInternalServerError, err + } + + return res.StatusCode(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/dev.go b/pkg/microservice/picket/core/filter/handler/dev.go new file mode 100644 index 00000000..e87b94d8 --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/dev.go @@ -0,0 +1,68 @@ +package handler + +import ( + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CreateWorkflowTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + body, _ := ioutil.ReadAll(c.Request.Body) + res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + // to avoid customer feel confused ,return workflow_name instead of pipline_name + var resp *CreateWorkflowTaskResp + err = json.Unmarshal(res, &resp) + if err != nil { + ctx.Err = err + return + } + resp.WorkflowName = resp.PipelineName + resp.PipelineName = "" + ctx.Resp = resp +} + +type CreateWorkflowTaskResp struct { + PipelineName string `json:"pipeline_name,omitempty"` + WorkflowName string `json:"workflow_name"` + TaskID int64 `json:"task_id"` +} + +type EndpointResponse struct { + ResultCode int `json:"resultCode"` + ErrorMsg string `json:"errorMsg"` +} + +func CancelWorkflowTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + id := c.Param("id") + name := c.Param("name") + statusCode, _ := service.CancelWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) + var code int + var errorMsg string + if statusCode == http.StatusOK { + code = 0 + errorMsg = "success" + } else if statusCode == http.StatusForbidden { + code = statusCode + errorMsg = "forbidden" + } else { + code = statusCode + errorMsg = "fail" + } + ctx.Resp = EndpointResponse{ + ResultCode: code, + ErrorMsg: errorMsg, + } +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 421fdc1a..a0113a4c 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -44,4 +44,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehosts.GET("", ListCodeHost) } + dev := router.Group("dev") + { + dev.POST("/workflowTask", CreateWorkflowTask) + dev.DELETE("/workflowTask", CancelWorkflowTask) + } } diff --git a/pkg/microservice/picket/core/filter/service/dev.go b/pkg/microservice/picket/core/filter/service/dev.go new file mode 100644 index 00000000..59fe5af7 --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/dev.go @@ -0,0 +1,18 @@ +package service + +import ( + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + + "net/http" + "net/url" + + "go.uber.org/zap" +) + +func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.SugaredLogger) ([]byte, error) { + return aslan.New().CreateWorkflowTask(header, qs, body) +} + +func CancelWorkflowTask(header http.Header, qs url.Values, id string, name string, _ *zap.SugaredLogger) (int, error) { + return aslan.New().CancelWorkflowTask(header, qs, id, name) +} -- Gitee From 874fcc2d5c1863f57cc3764d68b83aa57aec6ed3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 13:45:41 +0800 Subject: [PATCH 0580/2647] move api Signed-off-by: mouuii --- .../picket/client/aslan/workflow.go | 21 +++++++++++++ .../picket/core/filter/handler/dev.go | 31 +++++++++++++++++++ .../picket/core/filter/handler/router.go | 2 ++ .../picket/core/filter/service/dev.go | 8 +++++ 4 files changed, 62 insertions(+) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index b5298ff3..f2031f88 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -40,3 +40,24 @@ func (c *Client) CancelWorkflowTask(header http.Header, qs url.Values, id string return res.StatusCode(), nil } + +func (c *Client) RestartWorkflowTask(header http.Header, qs url.Values, id string, name string) (statusCode int, err error) { + url := fmt.Sprintf("/workflow/workflowtask/id/%s/pipelines/%s/restart", id, name) + + res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return http.StatusInternalServerError, err + } + + return res.StatusCode(), nil +} +func (c *Client) ListWorkflowTask(header http.Header, qs url.Values, commitId string) ([]byte, error) { + url := fmt.Sprintf("/workflow/v2/status/task/info?commitId=%s", commitId) + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/dev.go b/pkg/microservice/picket/core/filter/handler/dev.go index e87b94d8..5d3b1e8e 100644 --- a/pkg/microservice/picket/core/filter/handler/dev.go +++ b/pkg/microservice/picket/core/filter/handler/dev.go @@ -66,3 +66,34 @@ func CancelWorkflowTask(c *gin.Context) { ErrorMsg: errorMsg, } } + +func RestartWorkflowTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + id := c.Param("id") + name := c.Param("name") + statusCode, _ := service.RestartWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) + var code int + var errorMsg string + if statusCode == http.StatusOK { + code = 0 + errorMsg = "success" + } else if statusCode == http.StatusForbidden { + code = statusCode + errorMsg = "forbidden" + } else { + code = statusCode + errorMsg = "fail" + } + ctx.Resp = EndpointResponse{ + ResultCode: code, + ErrorMsg: errorMsg, + } +} + +func ListWorkflowTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + commitId := c.Param("commitId") + ctx.Resp, ctx.Err = service.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index a0113a4c..655575eb 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -48,5 +48,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { dev.POST("/workflowTask", CreateWorkflowTask) dev.DELETE("/workflowTask", CancelWorkflowTask) + dev.POST("/workflowTask/restart", RestartWorkflowTask) + dev.GET("/workflowTask", ListWorkflowTask) } } diff --git a/pkg/microservice/picket/core/filter/service/dev.go b/pkg/microservice/picket/core/filter/service/dev.go index 59fe5af7..a9398123 100644 --- a/pkg/microservice/picket/core/filter/service/dev.go +++ b/pkg/microservice/picket/core/filter/service/dev.go @@ -16,3 +16,11 @@ func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.S func CancelWorkflowTask(header http.Header, qs url.Values, id string, name string, _ *zap.SugaredLogger) (int, error) { return aslan.New().CancelWorkflowTask(header, qs, id, name) } + +func RestartWorkflowTask(header http.Header, qs url.Values, id string, name string, _ *zap.SugaredLogger) (int, error) { + return aslan.New().RestartWorkflowTask(header, qs, id, name) +} + +func ListWorkflowTask(header http.Header, qs url.Values, commitId string, _ *zap.SugaredLogger) ([]byte, error) { + return aslan.New().ListWorkflowTask(header, qs, commitId) +} -- Gitee From 9308e67a913ae2e9c9a2bc3d58324512f9c15a18 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 10 Nov 2021 13:47:40 +0800 Subject: [PATCH 0581/2647] fix log file acquire bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/log/service/log.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index c4e9ce00..8d40b76e 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -97,6 +97,7 @@ func getContainerLogFromS3(pipelineName, filenamePrefix string, taskID int64, lo log.Errorf("Failed to create s3 client, the error is: %+v", err) return "", err } + fileName += ".log" objectPrefix := storage.GetObjectPath(fileName) fileList, err := client.ListFiles(storage.Bucket, objectPrefix, false) if err != nil { -- Gitee From 8555ab8a6f718133d00dff921776b14e252da8cd Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 13:55:14 +0800 Subject: [PATCH 0582/2647] move api Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/workflow.go | 11 +++++++++++ pkg/microservice/picket/core/filter/handler/dev.go | 11 +++++++++++ pkg/microservice/picket/core/filter/handler/router.go | 1 + pkg/microservice/picket/core/filter/service/dev.go | 4 ++++ 4 files changed, 27 insertions(+) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index f2031f88..333eb5b0 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -61,3 +61,14 @@ func (c *Client) ListWorkflowTask(header http.Header, qs url.Values, commitId st return res.Body(), nil } + +func (c *Client) ListDelivery(header http.Header, qs url.Values, productName, workflowName, taskId, perPage, page string) ([]byte, error) { + url := fmt.Sprintf("/delivery/releases?orgId=1&productName=%s&workflowName=%s&taskId=%s&per_page=%s&page=%s", productName, workflowName, taskId, perPage, page) + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/dev.go b/pkg/microservice/picket/core/filter/handler/dev.go index 5d3b1e8e..a8bc5773 100644 --- a/pkg/microservice/picket/core/filter/handler/dev.go +++ b/pkg/microservice/picket/core/filter/handler/dev.go @@ -97,3 +97,14 @@ func ListWorkflowTask(c *gin.Context) { commitId := c.Param("commitId") ctx.Resp, ctx.Err = service.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) } + +func ListDelivery(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + productName := c.Query("productName") + workflowName := c.Query("workflowName") + taskIDStr := c.Query("taskId") + perPageStr := c.Query("perPage") + pageStr := c.Query("page") + ctx.Resp, ctx.Err = service.ListDelivery(c.Request.Header, c.Request.URL.Query(), productName, workflowName, taskIDStr, perPageStr, pageStr, ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 655575eb..c4fcfdcb 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -50,5 +50,6 @@ func (*Router) Inject(router *gin.RouterGroup) { dev.DELETE("/workflowTask", CancelWorkflowTask) dev.POST("/workflowTask/restart", RestartWorkflowTask) dev.GET("/workflowTask", ListWorkflowTask) + dev.GET("/delivery", ListDelivery) } } diff --git a/pkg/microservice/picket/core/filter/service/dev.go b/pkg/microservice/picket/core/filter/service/dev.go index a9398123..4b36d919 100644 --- a/pkg/microservice/picket/core/filter/service/dev.go +++ b/pkg/microservice/picket/core/filter/service/dev.go @@ -24,3 +24,7 @@ func RestartWorkflowTask(header http.Header, qs url.Values, id string, name stri func ListWorkflowTask(header http.Header, qs url.Values, commitId string, _ *zap.SugaredLogger) ([]byte, error) { return aslan.New().ListWorkflowTask(header, qs, commitId) } + +func ListDelivery(header http.Header, qs url.Values, productName string, workflowName string, taskId string, perPage string, page string, _ *zap.SugaredLogger) ([]byte, error) { + return aslan.New().ListDelivery(header, qs, productName, workflowName, taskId, perPage, page) +} -- Gitee From 2d845523a8764602e3c2b59cf13b5fb22cf723df Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 15:12:40 +0800 Subject: [PATCH 0583/2647] upload Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 34 +++++++++++++++++-- pkg/cli/initconfig/cmd/rsa.go | 62 ++++++++++++++++++++++++++++++++++ pkg/config/config.go | 4 +++ pkg/setting/consts.go | 1 + 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/initconfig/cmd/rsa.go diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 1ab3924c..797f4ebe 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -18,7 +18,12 @@ package cmd import ( _ "embed" + "encoding/base64" + "encoding/json" "fmt" + "net/http" + "strings" + "time" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" @@ -66,8 +71,9 @@ func run() error { func initSystemConfig() error { email := config.AdminEmail() password := config.AdminPassword() + domain := config.AdminDomain() - uid, err := presetSystemAdmin(email, password) + uid, err := presetSystemAdmin(email, password, domain) if err != nil { log.Errorf("presetSystemAdmin err:%s", err) return err @@ -84,7 +90,7 @@ func initSystemConfig() error { return nil } -func presetSystemAdmin(email string, password string) (string, error) { +func presetSystemAdmin(email string, password, domain string) (string, error) { r, err := user.New().SearchUser(&user.SearchUserArgs{Account: setting.PresetAccount}) if err != nil { log.Errorf("SearchUser err:%s", err) @@ -104,9 +110,33 @@ func presetSystemAdmin(email string, password string) (string, error) { log.Infof("created admin err:%s", err) return "", err } + // report register + go reportRegister(domain, email) return user.Uid, nil } +type Operation struct { + Data string `json:"data"` +} + +func reportRegister(domain, email string) { + uploadStrFmt := ` + { + "domain":"%s", + "username":"admin", + "email":"%s", + "created_at":%d + }` + + encrypt := RSA_Encrypt([]byte(fmt.Sprintf(uploadStrFmt, domain, email, time.Now().Unix()))) + encodeString := base64.StdEncoding.EncodeToString(encrypt) + resp := Operation{Data: encodeString} + bs, _ := json.Marshal(resp) + http.Post("https://api.koderover.com/api/operation/admin/user", + "application/json", + strings.NewReader(string(bs))) +} + func presetRoleBinding(uid string) error { return policy.NewDefault().CreateOrUpdateSystemRoleBinding(&policy.RoleBinding{ Name: fmt.Sprintf(setting.RoleBindingNameFmt, setting.RoleAdmin, setting.PresetAccount, ""), diff --git a/pkg/cli/initconfig/cmd/rsa.go b/pkg/cli/initconfig/cmd/rsa.go new file mode 100644 index 00000000..9c48cdd8 --- /dev/null +++ b/pkg/cli/initconfig/cmd/rsa.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "io/ioutil" +) + +var pub *rsa.PublicKey + +func RSA_Encrypt(plainText []byte) []byte { + if err := LoadPubKey(""); err != nil { + return nil + } + //对明文进行加密 + cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, pub, plainText) + if err != nil { + panic(err) + } + //返回密文 + return cipherText +} + +// LoadPubKey ... +func LoadPubKey(filename string) (err error) { + var block *pem.Block + if filename == "" { + block, _ = pem.Decode(defaultPublicKey) + } else { + b, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + block, _ = pem.Decode(b) + } + if block == nil { + return errors.New("public key error") + } + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return + } + pub = pubInterface.(*rsa.PublicKey) + return +} + +var defaultPublicKey = []byte(` +-----BEGIN RSA PUBLIC KEY----- +MIIBpTANBgkqhkiG9w0BAQEFAAOCAZIAMIIBjQKCAYQAz5IqagSbovHGXmUf7wTB +XrR+DZ0u3p5jsgJW08ISJl83t0rCCGMEtcsRXJU8bE2dIIfndNwvmBiqh13/WnJd ++jgyIm6i1ZfNmf/R8pEqVXpOAOuyoD3VLT9tfWvz9nPQbjVI+PsUHH7nVR0Jwxem +NsH/7MC2O15t+2DVC1533UlhjT/pKFDdTri0mgDrLZHp6gPF5d7/yQ7cPbzv6/0p +0UgIdStT7IhkDfsJDRmLAz09znv5tQQtHfJIMdAKxwHw9mExcL2gE40sOezrgj7m +srOnJd65N8anoMGxQqNv+ycAHB9aI1Yrtgue2KKzpI/Fneghd/ZavGVFWKDYoFP3 +531Ga/CiCwtKfM0vQezfLZKAo3qpb0Edy2BcDHhLwidmaFwh8ZlXuaHbNaF/FiVR +h7uu0/9B/gn81o2f+c8GSplWB5bALhQH8tJZnvmWZGI9OnrIlWmQZsuUBooTul9Q +ZJ/w3sE1Zoxa+Or1/eWijqtIfhukOJBNyGaj+esFg6uEeBgHAgMBAAE= +-----END RSA PUBLIC KEY----- +`) diff --git a/pkg/config/config.go b/pkg/config/config.go index 92c0d23f..88104d3b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -253,3 +253,7 @@ func AdminEmail() string { func AdminPassword() string { return viper.GetString(setting.ENVAdminPassword) } + +func AdminDomain() string { + return viper.GetString(setting.ENVAdminDomain) +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index e81dafee..58a81785 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -116,6 +116,7 @@ const ( // initconfig ENVAdminEmail = "ADMIN_EMAIL" ENVAdminPassword = "ADMIN_PASSWORD" + ENVAdminDomain = "ADMIN_DOMAIN" PresetAccount = "admin" ) -- Gitee From 027492185564bd0d964c1c60bf524fbc7a68a63c Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 10 Nov 2021 15:45:27 +0800 Subject: [PATCH 0584/2647] update chart version logic Signed-off-by: allenshen --- .../aslan/core/common/service/fs/file.go | 16 +- .../aslan/core/common/service/fs/s3.go | 12 +- .../aslan/core/common/service/helm.go | 18 +-- .../core/environment/service/environment.go | 12 +- .../aslan/core/service/service/helm.go | 151 +++++++++++------- .../aslan/core/templatestore/service/chart.go | 2 +- .../core/service/taskplugin/deploy.go | 29 ++-- 7 files changed, 155 insertions(+), 85 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index c5317ae9..cb01720d 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -26,6 +26,18 @@ import ( fsutil "github.com/koderover/zadig/pkg/util/fs" ) +type fileHook interface { + Execute() error +} + +type LocalFileCopy struct { + ExtraFiles []string +} + +func (hook *LocalFileCopy) Execute() error { + return nil +} + // PreloadFiles downloads a tarball from object storage and extracts it to a local path for further usage. // It happens only if files do not exist in local disk. func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) error { @@ -47,7 +59,7 @@ func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) err } // SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. -func SaveAndUploadFiles(fileTree fs.FS, name, localBase, s3Base string, logger *zap.SugaredLogger) error { +func SaveAndUploadFiles(fileTree fs.FS, name, localBase string, s3Base string, s3Copies []string, logger *zap.SugaredLogger) error { var wg wait.Group var err error @@ -59,7 +71,7 @@ func SaveAndUploadFiles(fileTree fs.FS, name, localBase, s3Base string, logger * } }) wg.Start(func() { - err2 := ArchiveAndUploadFilesToS3(fileTree, name, s3Base, logger) + err2 := ArchiveAndUploadFilesToS3(fileTree, name, s3Base, s3Copies, logger) if err2 != nil { logger.Errorf("Failed to upload files to s3, err: %s", err2) err = err2 diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index a2e0eb7a..0680a4c2 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -30,7 +30,7 @@ import ( fsutil "github.com/koderover/zadig/pkg/util/fs" ) -func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, logger *zap.SugaredLogger) error { +func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, copies []string, logger *zap.SugaredLogger) error { tmpDir, err := os.MkdirTemp("", "") if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) @@ -64,6 +64,16 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, logger *zap. return err } + //copy file to avoid duplicated file transfer + for _, copyName := range copies { + targetPath := filepath.Join(s3Storage.Subfolder, s3Base, fmt.Sprintf("%s.tar.gz", copyName)) + err = client.CopyObject(s3Storage.Bucket, s3Path, targetPath) + if err != nil { + logger.Errorf("Failed to copy object from %s to %s", s3Path, targetPath) + return err + } + } + return nil } diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index f5c502d5..347b238c 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -80,19 +80,19 @@ func DownloadServiceManifests(base, projectName, serviceName string) error { return fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) } -func SaveAndUploadService(projectName, serviceName string, fileTree fs.FS) error { +func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS) error { localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) - return fsservice.SaveAndUploadFiles(fileTree, serviceName, localBase, s3Base, log.SugaredLogger()) + return fsservice.SaveAndUploadFiles(fileTree, serviceName, localBase, s3Base, copies, log.SugaredLogger()) } // SaveAndUploadServiceWithRevision save file to local and upload to s3 -func SaveAndUploadServiceWithRevision(projectName, serviceName string, fileTree fs.FS, revision int64) error { - serviceNameWithRevision := config.ServiceNameWithRevision(serviceName, revision) - localBase := config.LocalServicePathWithRevision(projectName, serviceName, revision) - s3Base := config.ObjectStorageServicePath(projectName, serviceName) - return fsservice.SaveAndUploadFiles(fileTree, serviceNameWithRevision, localBase, s3Base, log.SugaredLogger()) -} +//func SaveAndUploadServiceWithRevision(projectName, serviceName string, fileTree fs.FS, revision int64) error { +// serviceNameWithRevision := config.ServiceNameWithRevision(serviceName, revision) +// localBase := config.LocalServicePathWithRevision(projectName, serviceName, revision) +// s3Base := config.ObjectStorageServicePath(projectName, serviceName) +// return fsservice.SaveAndUploadFiles(fileTree, serviceNameWithRevision, localBase, s3Base, log.SugaredLogger()) +//} func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { tree, err := fsservice.DownloadFilesFromSource( @@ -105,7 +105,7 @@ func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { } // save files to disk and upload them to s3 - if err = SaveAndUploadService(svc.ProductName, svc.ServiceName, tree); err != nil { + if err = SaveAndUploadService(svc.ProductName, svc.ServiceName, nil, tree); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", svc.ServiceName, svc.ProductName, err) return err } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 8c403b0c..7e2969a3 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2631,17 +2631,18 @@ func updateProductVariable(productName, envName string, productResp *commonmodel productServiceMap := productResp.GetServiceMap() handler := func(service *commonmodels.Service, log *zap.SugaredLogger) error { + targetRevision := service.Revision //preload chart info with specific revision base := config.LocalServicePathWithRevision(service.ProductName, service.ServiceName, service.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, service); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + service.Revision, service.ServiceName) // use the latest version when it fails to download the specific version base = config.LocalServicePath(service.ProductName, service.ServiceName) if err = commonservice.PreLoadServiceManifests(base, service); err != nil { log.Errorf("failed to load chart info for service %v", service.ServiceName) return fmt.Errorf("failed to load chart info for service %s", service.ServiceName) } - log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - service.Revision, service.ServiceName) findOpt := &commonrepo.ServiceFindOption{ ServiceName: service.ServiceName, @@ -2654,9 +2655,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel log.Errorf("failed to get service:%v with latest version", service.ServiceName) return fmt.Errorf("failed to get service:%v with latest version", service.ServiceName) } - if productService, ok := productServiceMap[service.ServiceName]; ok { - productService.Revision = latestService.Revision - } + targetRevision = latestService.Revision } renderChart := renderChartMap[service.ServiceName] @@ -2664,6 +2663,9 @@ func updateProductVariable(productName, envName string, productResp *commonmodel if err != nil { return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) } + if productService, ok := productServiceMap[service.ServiceName]; ok { + productService.Revision = targetRevision + } return err } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e3e9c083..2bc6d619 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -19,7 +19,6 @@ package service import ( "bytes" "fmt" - "io/fs" "os" "path/filepath" @@ -89,6 +88,7 @@ type Chart struct { type helmServiceCreationArgs struct { ChartName string ChartVersion string + ServiceRevision int64 MergedValues string ServiceName string FilePath string @@ -243,6 +243,38 @@ func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (* }, nil } +func getNextServiceRevision(productName, serviceName string) (int64, error) { + serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, serviceName, productName) + rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) + if err != nil { + log.Errorf("Failed to get next revision for service %s, err: %s", serviceName, err) + return 0, err + } + if err = commonrepo.NewServiceColl().Delete(serviceName, setting.HelmDeployType, serviceName, setting.ProductStatusDeleting, rev); err != nil { + log.Warnf("Failed to delete stale service %s with revision %d, err: %s", serviceName, rev, err) + } + return rev, err +} + +//make local chart info copy with revision +func copyChartRevision(projectName, serviceName string, revision int64) error { + sourceChartPath := config.LocalServicePath(projectName, serviceName) + revisionChartLocalPath := config.LocalServicePathWithRevision(projectName, serviceName, revision) + + err := os.RemoveAll(revisionChartLocalPath) + if err != nil { + log.Errorf("failed to remove old chart revision data, projectName %s serviceName %s revision %d, err %s", projectName, serviceName, revision, err) + return err + } + + err = copy.Copy(sourceChartPath, revisionChartLocalPath) + if err != nil { + log.Errorf("failed to copy chart info, projectName %s serviceName %s revision %d, err %s", projectName, serviceName, revision, err) + return err + } + return nil +} + func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: @@ -306,9 +338,21 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return err } + rev, err := getNextServiceRevision(projectName, args.Name) + if err != nil { + log.Errorf("Failed to get next revision for service %s, err: %s", args.Name, err) + return errors.Wrapf(err, "Failed to get service next revision, service %s", args.Name) + } + + err = copyChartRevision(projectName, args.Name, rev) + if err != nil { + log.Errorf("Failed to copy file %s, err: %s", args.Name, err) + return errors.Wrapf(err, "Failed to copy chart info, service %s", args.Name) + } + fsTree := os.DirFS(config.LocalServicePath(projectName, args.Name)) ServiceS3Base := config.ObjectStorageServicePath(projectName, args.Name) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, args.Name, ServiceS3Base, logger); err != nil { + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, args.Name, ServiceS3Base, []string{fmt.Sprintf("%s-%d", args.Name, rev)}, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", args.Name, projectName, err) return err } @@ -318,6 +362,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe &helmServiceCreationArgs{ ChartName: templateChartInfo.ChartName, ChartVersion: templateChartInfo.ChartVersion, + ServiceRevision: rev, MergedValues: string(merged), ServiceName: args.Name, FilePath: to, @@ -330,6 +375,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe }, logger, ) + if err != nil { logger.Errorf("Failed to create service %s in project %s, error: %s", args.Name, projectName, err) return err @@ -427,6 +473,20 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC log.Info("Found valid chart, Starting to save and upload files") + rev, err := getNextServiceRevision(projectName, serviceName) + if err != nil { + log.Errorf("Failed to get next revision for service %s, err: %s", serviceName, err) + finalErr = e.ErrCreateTemplate.AddErr(err) + return + } + + // save files to disk and upload them to s3 + if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, fsTree); err != nil { + log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) + finalErr = e.ErrCreateTemplate.AddErr(err) + return + } + if source != setting.SourceFromPublicRepo && codehostInfo != nil { repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) } @@ -434,19 +494,20 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ - ChartName: serviceName, - ChartVersion: chartVersion, - MergedValues: string(valuesYAML), - ServiceName: serviceName, - FilePath: filePath, - ProductName: projectName, - CreateBy: args.CreatedBy, - CodehostID: repoArgs.CodehostID, - Owner: repoArgs.Owner, - Repo: repoArgs.Repo, - Branch: repoArgs.Branch, - RepoLink: repoLink, - Source: source, + ChartName: serviceName, + ChartVersion: chartVersion, + ServiceRevision: rev, + MergedValues: string(valuesYAML), + ServiceName: serviceName, + FilePath: filePath, + ProductName: projectName, + CreateBy: args.CreatedBy, + CodehostID: repoArgs.CodehostID, + Owner: repoArgs.Owner, + Repo: repoArgs.Repo, + Branch: repoArgs.Branch, + RepoLink: repoLink, + Source: source, }, log, ) @@ -456,21 +517,6 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } - // save files to disk and upload them to s3 - if err = commonservice.SaveAndUploadService(projectName, serviceName, fsTree); err != nil { - log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) - finalErr = e.ErrCreateTemplate.AddErr(err) - return - } - - // save chart files with revision to disk and upload to s3 - if err = commonservice.SaveAndUploadServiceWithRevision(projectName, serviceName, fsTree, svc.Revision); err != nil { - log.Errorf("Failed to save or upload files for service %s with revision %v in project %s, error: %s", - serviceName, svc.Revision, projectName, err) - err = e.ErrCreateTemplate.AddDesc(err.Error()) - return - } - helmRenderCharts = append(helmRenderCharts, &templatemodels.RenderChart{ ServiceName: serviceName, ChartVersion: svc.HelmChart.Version, @@ -606,9 +652,21 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi return nil, err } + rev, err := getNextServiceRevision(projectName, serviceName) + if err != nil { + log.Errorf("Failed to get next revision for service %s, err: %s", serviceName, err) + return nil, errors.Wrapf(err, "Failed to get service next revision, service %s", serviceName) + } + + err = copyChartRevision(projectName, serviceName, rev) + if err != nil { + log.Errorf("Failed to copy file %s, err: %s", serviceName, err) + return nil, errors.Wrapf(err, "Failed to copy chart info, service %s", serviceName) + } + fsTree := os.DirFS(config.LocalServicePath(projectName, serviceName)) ServiceS3Base := config.ObjectStorageServicePath(projectName, serviceName) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, serviceName, ServiceS3Base, logger); err != nil { + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, serviceName, ServiceS3Base, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", serviceName, projectName, err) return nil, err } @@ -618,6 +676,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi &helmServiceCreationArgs{ ChartName: templateChartData.ChartName, ChartVersion: templateChartData.ChartVersion, + ServiceRevision: rev, MergedValues: string(mergedValues), ServiceName: serviceName, FilePath: to, @@ -749,25 +808,15 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) - rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) - if err != nil { - logger.Errorf("Failed to get next revision for service %s, err: %s", args.ServiceName, err) - return nil, err - } - if err = commonrepo.NewServiceColl().Delete(args.ServiceName, setting.HelmDeployType, args.ProductName, setting.ProductStatusDeleting, rev); err != nil { - logger.Warnf("Failed to delete stale service %s with revision %d, err: %s", args.ServiceName, rev, err) - } - containerList, err := commonservice.ParseImagesForProductService(valuesMap, args.ServiceName, args.ProductName) if err != nil { - return nil, errors.Wrapf(err, "failed to parse service from yaml") + return nil, errors.Wrapf(err, "Failed to parse service from yaml") } serviceObj := &models.Service{ ServiceName: args.ServiceName, Type: setting.HelmDeployType, - Revision: rev, + Revision: args.ServiceRevision, ProductName: args.ProductName, Visibility: setting.PrivateVisibility, CreateTime: time.Now().Unix(), @@ -788,7 +837,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg }, } - log.Infof("Starting to create service %s with revision %d", args.ServiceName, rev) + log.Infof("Starting to create service %s with revision %d", args.ServiceName, args.ServiceRevision) if err = commonrepo.NewServiceColl().Create(serviceObj); err != nil { log.Errorf("Failed to create service %s error: %s", args.ServiceName, err) @@ -845,9 +894,8 @@ func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileIn // UpdateHelmService TODO need to be deprecated func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { - var serviceNames []string + var serviceMap map[string]int64 for _, helmServiceInfo := range args.HelmServiceInfos { - serviceNames = append(serviceNames, helmServiceInfo.ServiceName) opt := &commonrepo.ServiceFindOption{ ProductName: args.ProductName, @@ -930,12 +978,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { return fmt.Errorf("get next helm service revision error: %v", err) } - //store chart with revision info to local and upload to s3 - if err := commonservice.SaveAndUploadServiceWithRevision(args.ProductName, helmServiceInfo.ServiceName, - os.DirFS(config.LocalServicePath(args.ProductName, helmServiceInfo.ServiceName)), rev); err != nil { - return e.ErrUpdateTemplate.AddDesc(err.Error()) - } - + serviceMap[helmServiceInfo.ServiceName] = rev preServiceTmpl.Revision = rev if err := commonrepo.NewServiceColl().Delete(helmServiceInfo.ServiceName, setting.HelmDeployType, args.ProductName, setting.ProductStatusDeleting, preServiceTmpl.Revision); err != nil { log.Errorf("helmService.update delete %s error: %v", helmServiceInfo.ServiceName, err) @@ -948,9 +991,9 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { } - for _, serviceName := range serviceNames { + for serviceName, rev := range serviceMap { s3Base := config.ObjectStorageServicePath(args.ProductName, serviceName) - if err := fsservice.ArchiveAndUploadFilesToS3(os.DirFS(config.LocalServicePath(args.ProductName, serviceName)), serviceName, s3Base, log); err != nil { + if err := fsservice.ArchiveAndUploadFilesToS3(os.DirFS(config.LocalServicePath(args.ProductName, serviceName)), serviceName, s3Base, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, log); err != nil { return e.ErrUpdateTemplate.AddDesc(err.Error()) } } diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index f3ca87bf..c0d86270 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -352,7 +352,7 @@ func processChartFromSource(name string, args *fs.DownloadFromSourceArgs, logger localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) - err1 := fs.SaveAndUploadFiles(tree, name, localBase, s3Base, logger) + err1 := fs.SaveAndUploadFiles(tree, name, localBase, s3Base, nil, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) err = err1 diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index cbf4b0ff..bc578f1f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -432,10 +432,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } //task执行时候 product.service.revision 可能已经更新,需要使用当前环境中的service.revision - targetRevision := p.Task.ServiceRevision - if curRevisionInProduct > 0 && curRevisionInProduct != p.Task.ServiceRevision { - targetRevision = curRevisionInProduct - } + targetRevision := curRevisionInProduct path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, targetRevision) @@ -609,14 +606,13 @@ func (p *DeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs) (* return prod, nil } -func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, - productName string, revision int64) (*types.ServiceTmpl, error) { +func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, productName string, revision int64) (*types.ServiceTmpl, error) { url := fmt.Sprintf("/api/service/services/%s/%s", name, serviceType) s := &types.ServiceTmpl{} _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ "productName": productName, - "revision": fmt.Sprintf("%v", revision), + "revision": fmt.Sprintf("%d", revision), })) if err != nil { return nil, err @@ -631,7 +627,7 @@ func (p *DeployTaskPlugin) updateServiceRevision(ctx context.Context, name, prod _, err := p.httpClient.Put(url, httpclient.SetQueryParams(map[string]string{ "productName": productName, "envName": envName, - "revision": fmt.Sprintf("%v", revision), + "revision": fmt.Sprintf("%d", revision), })) return err } @@ -653,7 +649,11 @@ func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI localBase := configbase.LocalServicePath(productName, serviceName) tarFilePath := filepath.Join(localBase, tarball) - if exists, _ := fsutil.FileExists(tarFilePath); exists { + exists, err := fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if exists { return tarFilePath, nil } @@ -662,21 +662,24 @@ func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI if s3Storage.Provider == setting.ProviderSourceAli { forcedPathStyle = false } - client, err1 := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + s3Client, err1 := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) if err1 != nil { p.Log.Errorf("failed to create s3 client, err: %+v", err1) return "", err1 } - if err = client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { + if err = s3Client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) _ = os.Remove(tarFilePath) return "", err } - if exist, _ := fsutil.FileExists(tarFilePath); !exist { + exists, err = fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if !exists { return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) } - return tarFilePath, nil } -- Gitee From a80d5c7ff0fcb165a16a2faeb22dde16db7f5238 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 15:47:09 +0800 Subject: [PATCH 0585/2647] domain Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 15 +++++++++++---- pkg/cli/initconfig/cmd/init_test.go | 9 +++++++++ pkg/cli/initconfig/cmd/rsa.go | 8 ++++---- 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 pkg/cli/initconfig/cmd/init_test.go diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 797f4ebe..eb181b5b 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -111,7 +111,10 @@ func presetSystemAdmin(email string, password, domain string) (string, error) { return "", err } // report register - go reportRegister(domain, email) + err = reportRegister(domain, email) + if err != nil { + log.Infof("reportRegister err: %s", err) + } return user.Uid, nil } @@ -119,7 +122,7 @@ type Operation struct { Data string `json:"data"` } -func reportRegister(domain, email string) { +func reportRegister(domain, email string) error { uploadStrFmt := ` { "domain":"%s", @@ -128,13 +131,17 @@ func reportRegister(domain, email string) { "created_at":%d }` - encrypt := RSA_Encrypt([]byte(fmt.Sprintf(uploadStrFmt, domain, email, time.Now().Unix()))) + encrypt, err := RSA_Encrypt([]byte(fmt.Sprintf(uploadStrFmt, domain, email, time.Now().Unix()))) + if err != nil { + return err + } encodeString := base64.StdEncoding.EncodeToString(encrypt) resp := Operation{Data: encodeString} bs, _ := json.Marshal(resp) - http.Post("https://api.koderover.com/api/operation/admin/user", + _, err = http.Post("https://api.koderover.com/api/operation/admin/user", "application/json", strings.NewReader(string(bs))) + return err } func presetRoleBinding(uid string) error { diff --git a/pkg/cli/initconfig/cmd/init_test.go b/pkg/cli/initconfig/cmd/init_test.go new file mode 100644 index 00000000..a7902aec --- /dev/null +++ b/pkg/cli/initconfig/cmd/init_test.go @@ -0,0 +1,9 @@ +package cmd + +import ( + "testing" +) + +func TestRegister(t *testing.T) { + reportRegister("test@register.com", "test") +} diff --git a/pkg/cli/initconfig/cmd/rsa.go b/pkg/cli/initconfig/cmd/rsa.go index 9c48cdd8..4b25a849 100644 --- a/pkg/cli/initconfig/cmd/rsa.go +++ b/pkg/cli/initconfig/cmd/rsa.go @@ -11,17 +11,17 @@ import ( var pub *rsa.PublicKey -func RSA_Encrypt(plainText []byte) []byte { +func RSA_Encrypt(plainText []byte) ([]byte, error) { if err := LoadPubKey(""); err != nil { - return nil + return nil, err } //对明文进行加密 cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, pub, plainText) if err != nil { - panic(err) + return nil, err } //返回密文 - return cipherText + return cipherText, nil } // LoadPubKey ... -- Gitee From 82f47a2c0a89af44b91ca97d4360126159bb95af Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 10 Nov 2021 15:52:45 +0800 Subject: [PATCH 0586/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/handler/external_link.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/external_link.go b/pkg/microservice/aslan/core/system/handler/external_link.go index 37c68a21..cbe7c44c 100644 --- a/pkg/microservice/aslan/core/system/handler/external_link.go +++ b/pkg/microservice/aslan/core/system/handler/external_link.go @@ -6,13 +6,12 @@ import ( "fmt" "io/ioutil" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) -- Gitee From d25fb01290fb69bc3e1928e3edc0441a5e7f0a84 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 10 Nov 2021 15:54:50 +0800 Subject: [PATCH 0587/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/log/service/log.go | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 8d40b76e..290b6ba1 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -25,6 +25,8 @@ import ( "go.uber.org/zap" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/s3" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -72,6 +74,7 @@ func GetWorkflowTestJobContainerLogs(pipelineName, serviceName, pipelineType str func getContainerLogFromS3(pipelineName, filenamePrefix string, taskID int64, log *zap.SugaredLogger) (string, error) { fileName := strings.Replace(strings.ToLower(filenamePrefix), "_", "-", -1) + fileName += ".log" tempFile, _ := util.GenerateTmpFile() defer func() { _ = os.Remove(tempFile) @@ -97,18 +100,13 @@ func getContainerLogFromS3(pipelineName, filenamePrefix string, taskID int64, lo log.Errorf("Failed to create s3 client, the error is: %+v", err) return "", err } - fileName += ".log" - objectPrefix := storage.GetObjectPath(fileName) - fileList, err := client.ListFiles(storage.Bucket, objectPrefix, false) - if err != nil { - log.Errorf("GetContainerLogFromS3 ListFiles err:%v", err) - return "", err - } - if len(fileList) == 0 { - return "", nil - } - err = client.Download(storage.Bucket, fileList[0], tempFile) + fullPath := storage.GetObjectPath(fileName) + err = client.Download(storage.Bucket, fullPath, tempFile) if err != nil { + //ignore error if log not exist + if e, ok := err.(awserr.Error); ok && e.Code() == s3.ErrCodeNoSuchKey { + return "", nil + } log.Errorf("GetContainerLogFromS3 Download err:%v", err) return "", err } -- Gitee From c26985343f5c89ad5fcc9f461e1b67cfc0846f72 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 15:55:47 +0800 Subject: [PATCH 0588/2647] domain Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index eb181b5b..be542ae3 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -71,7 +71,7 @@ func run() error { func initSystemConfig() error { email := config.AdminEmail() password := config.AdminPassword() - domain := config.AdminDomain() + domain := config.Domain() uid, err := presetSystemAdmin(email, password, domain) if err != nil { -- Gitee From 06ee185fa74806745836b2f5510dbfd5913474e4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 15:57:56 +0800 Subject: [PATCH 0589/2647] domain Signed-off-by: mouuii --- pkg/config/config.go | 4 ++-- pkg/setting/consts.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 88104d3b..ca1e0604 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -254,6 +254,6 @@ func AdminPassword() string { return viper.GetString(setting.ENVAdminPassword) } -func AdminDomain() string { - return viper.GetString(setting.ENVAdminDomain) +func Domain() string { + return viper.GetString(setting.ENVDomain) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 58a81785..0545b556 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -116,7 +116,7 @@ const ( // initconfig ENVAdminEmail = "ADMIN_EMAIL" ENVAdminPassword = "ADMIN_PASSWORD" - ENVAdminDomain = "ADMIN_DOMAIN" + ENVDomain = "ADDRESS" PresetAccount = "admin" ) -- Gitee From dc0a780f832e6db10a24df94abdb57e9ce751381 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 16:18:12 +0800 Subject: [PATCH 0590/2647] refactor Signed-off-by: mouuii --- .../picket/client/aslan/workflow.go | 2 +- .../picket/core/filter/handler/router.go | 8 -------- .../dev.go => public/handler/public.go} | 17 ++++++++--------- .../picket/core/public/handler/router.go | 19 +++++++++++++++++++ .../dev.go => public/service/public.go} | 4 ++-- pkg/microservice/picket/server/rest/router.go | 8 ++++++++ 6 files changed, 38 insertions(+), 20 deletions(-) rename pkg/microservice/picket/core/{filter/handler/dev.go => public/handler/public.go} (77%) create mode 100644 pkg/microservice/picket/core/public/handler/router.go rename pkg/microservice/picket/core/{filter/service/dev.go => public/service/public.go} (100%) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 333eb5b0..28e56d8b 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -46,7 +46,7 @@ func (c *Client) RestartWorkflowTask(header http.Header, qs url.Values, id strin res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { - return http.StatusInternalServerError, err + return nil, err } return res.StatusCode(), nil diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index c4fcfdcb..421fdc1a 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -44,12 +44,4 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehosts.GET("", ListCodeHost) } - dev := router.Group("dev") - { - dev.POST("/workflowTask", CreateWorkflowTask) - dev.DELETE("/workflowTask", CancelWorkflowTask) - dev.POST("/workflowTask/restart", RestartWorkflowTask) - dev.GET("/workflowTask", ListWorkflowTask) - dev.GET("/delivery", ListDelivery) - } } diff --git a/pkg/microservice/picket/core/filter/handler/dev.go b/pkg/microservice/picket/core/public/handler/public.go similarity index 77% rename from pkg/microservice/picket/core/filter/handler/dev.go rename to pkg/microservice/picket/core/public/handler/public.go index a8bc5773..bf76c92d 100644 --- a/pkg/microservice/picket/core/filter/handler/dev.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -2,20 +2,19 @@ package handler import ( "encoding/json" - "io/ioutil" "net/http" "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + service2 "github.com/koderover/zadig/pkg/microservice/picket/core/public/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) func CreateWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - body, _ := ioutil.ReadAll(c.Request.Body) - res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) + body, _ := c.GetRawData() + res, err := service2.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) if err != nil { ctx.Err = err return @@ -48,7 +47,7 @@ func CancelWorkflowTask(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") name := c.Param("name") - statusCode, _ := service.CancelWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) + statusCode, _ := service2.CancelWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) var code int var errorMsg string if statusCode == http.StatusOK { @@ -72,7 +71,7 @@ func RestartWorkflowTask(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") name := c.Param("name") - statusCode, _ := service.RestartWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) + statusCode, _ := service2.RestartWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) var code int var errorMsg string if statusCode == http.StatusOK { @@ -94,8 +93,8 @@ func RestartWorkflowTask(c *gin.Context) { func ListWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - commitId := c.Param("commitId") - ctx.Resp, ctx.Err = service.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) + commitId := c.Query("commitId") + ctx.Resp, ctx.Err = service2.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) } func ListDelivery(c *gin.Context) { @@ -106,5 +105,5 @@ func ListDelivery(c *gin.Context) { taskIDStr := c.Query("taskId") perPageStr := c.Query("perPage") pageStr := c.Query("page") - ctx.Resp, ctx.Err = service.ListDelivery(c.Request.Header, c.Request.URL.Query(), productName, workflowName, taskIDStr, perPageStr, pageStr, ctx.Logger) + ctx.Resp, ctx.Err = service2.ListDelivery(c.Request.Header, c.Request.URL.Query(), productName, workflowName, taskIDStr, perPageStr, pageStr, ctx.Logger) } diff --git a/pkg/microservice/picket/core/public/handler/router.go b/pkg/microservice/picket/core/public/handler/router.go new file mode 100644 index 00000000..7ae9cf8a --- /dev/null +++ b/pkg/microservice/picket/core/public/handler/router.go @@ -0,0 +1,19 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + + dev := router.Group("") + { + dev.POST("/workflowTask/create", CreateWorkflowTask) + dev.DELETE("workflowTask/id/:id/pipelines/:name/cancel", CancelWorkflowTask) + dev.POST("/workflowTask/id/:id/pipelines/:name/restart", RestartWorkflowTask) + dev.GET("/workflowTask", ListWorkflowTask) + dev.GET("/dc/releases", ListDelivery) + } +} diff --git a/pkg/microservice/picket/core/filter/service/dev.go b/pkg/microservice/picket/core/public/service/public.go similarity index 100% rename from pkg/microservice/picket/core/filter/service/dev.go rename to pkg/microservice/picket/core/public/service/public.go index 4b36d919..787ac43b 100644 --- a/pkg/microservice/picket/core/filter/service/dev.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -1,12 +1,12 @@ package service import ( - "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" - "net/http" "net/url" "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" ) func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.SugaredLogger) ([]byte, error) { diff --git a/pkg/microservice/picket/server/rest/router.go b/pkg/microservice/picket/server/rest/router.go index 3253c5d4..ba02b80e 100644 --- a/pkg/microservice/picket/server/rest/router.go +++ b/pkg/microservice/picket/server/rest/router.go @@ -21,6 +21,7 @@ import ( evaluationhandler "github.com/koderover/zadig/pkg/microservice/picket/core/evaluation/handler" filterhandler "github.com/koderover/zadig/pkg/microservice/picket/core/filter/handler" + publichandler "github.com/koderover/zadig/pkg/microservice/picket/core/public/handler" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { @@ -30,6 +31,13 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { } { r.Inject(router.Group("/api/v1")) } + + for _, r := range []injector{ + new(publichandler.Router), + } { + r.Inject(router.Group("/public-api/v1")) + } + } type injector interface { -- Gitee From 9efe14a22edec2d779d6a6b70a25b946e247de49 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 16:18:54 +0800 Subject: [PATCH 0591/2647] refactor Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/workflow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 28e56d8b..333eb5b0 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -46,7 +46,7 @@ func (c *Client) RestartWorkflowTask(header http.Header, qs url.Values, id strin res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { - return nil, err + return http.StatusInternalServerError, err } return res.StatusCode(), nil -- Gitee From 0eb434c73437bf91645c71f7526d619e1d8f83f3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 16:29:55 +0800 Subject: [PATCH 0592/2647] refactor Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 24 +++++++++++++++--------- pkg/cli/initconfig/cmd/rsa.go | 12 ++++++------ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index be542ae3..8fdca9ac 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -19,10 +19,7 @@ package cmd import ( _ "embed" "encoding/base64" - "encoding/json" "fmt" - "net/http" - "strings" "time" "github.com/spf13/cobra" @@ -34,11 +31,18 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/shared/client/user" + "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" ) func init() { rootCmd.AddCommand(initCmd) + log.Init(&log.Config{ + Level: config.LogLevel(), + Filename: config.LogFile(), + SendToFile: config.SendLogToFile(), + Development: config.Mode() != setting.ReleaseMode, + }) } //go:embed contributor.yaml @@ -113,7 +117,7 @@ func presetSystemAdmin(email string, password, domain string) (string, error) { // report register err = reportRegister(domain, email) if err != nil { - log.Infof("reportRegister err: %s", err) + log.Errorf("reportRegister err: %s", err) } return user.Uid, nil } @@ -131,16 +135,18 @@ func reportRegister(domain, email string) error { "created_at":%d }` - encrypt, err := RSA_Encrypt([]byte(fmt.Sprintf(uploadStrFmt, domain, email, time.Now().Unix()))) + encrypt, err := RSAEncrypt([]byte(fmt.Sprintf(uploadStrFmt, domain, email, time.Now().Unix()))) if err != nil { return err } encodeString := base64.StdEncoding.EncodeToString(encrypt) resp := Operation{Data: encodeString} - bs, _ := json.Marshal(resp) - _, err = http.Post("https://api.koderover.com/api/operation/admin/user", - "application/json", - strings.NewReader(string(bs))) + //bs, _ := json.Marshal(resp) + //_, err = http.Post("https://api.koderover.com/api/operation/admin/user", + // "application/json", + // strings.NewReader(string(bs))) + + _, err = httpclient.Post("https://api.koderover.com/api/operation/admin/user", httpclient.SetBody(resp), httpclient.ForceContentType("application/json")) return err } diff --git a/pkg/cli/initconfig/cmd/rsa.go b/pkg/cli/initconfig/cmd/rsa.go index 4b25a849..0d299d76 100644 --- a/pkg/cli/initconfig/cmd/rsa.go +++ b/pkg/cli/initconfig/cmd/rsa.go @@ -6,12 +6,12 @@ import ( "crypto/x509" "encoding/pem" "errors" - "io/ioutil" + "os" ) var pub *rsa.PublicKey -func RSA_Encrypt(plainText []byte) ([]byte, error) { +func RSAEncrypt(plainText []byte) ([]byte, error) { if err := LoadPubKey(""); err != nil { return nil, err } @@ -28,9 +28,9 @@ func RSA_Encrypt(plainText []byte) ([]byte, error) { func LoadPubKey(filename string) (err error) { var block *pem.Block if filename == "" { - block, _ = pem.Decode(defaultPublicKey) + block, _ = pem.Decode([]byte(defaultPublicKey)) } else { - b, err := ioutil.ReadFile(filename) + b, err := os.ReadFile(filename) if err != nil { return err } @@ -47,7 +47,7 @@ func LoadPubKey(filename string) (err error) { return } -var defaultPublicKey = []byte(` +var defaultPublicKey = ` -----BEGIN RSA PUBLIC KEY----- MIIBpTANBgkqhkiG9w0BAQEFAAOCAZIAMIIBjQKCAYQAz5IqagSbovHGXmUf7wTB XrR+DZ0u3p5jsgJW08ISJl83t0rCCGMEtcsRXJU8bE2dIIfndNwvmBiqh13/WnJd @@ -59,4 +59,4 @@ srOnJd65N8anoMGxQqNv+ycAHB9aI1Yrtgue2KKzpI/Fneghd/ZavGVFWKDYoFP3 h7uu0/9B/gn81o2f+c8GSplWB5bALhQH8tJZnvmWZGI9OnrIlWmQZsuUBooTul9Q ZJ/w3sE1Zoxa+Or1/eWijqtIfhukOJBNyGaj+esFg6uEeBgHAgMBAAE= -----END RSA PUBLIC KEY----- -`) +` -- Gitee From b8507b59467b9d447a35cfe2e5e5c955a46b92a2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 16:30:17 +0800 Subject: [PATCH 0593/2647] refactor Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 11 +++-------- pkg/config/config.go | 4 ---- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 8fdca9ac..f3c98445 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -75,7 +75,7 @@ func run() error { func initSystemConfig() error { email := config.AdminEmail() password := config.AdminPassword() - domain := config.Domain() + domain := config.SystemAddress() uid, err := presetSystemAdmin(email, password, domain) if err != nil { @@ -140,13 +140,8 @@ func reportRegister(domain, email string) error { return err } encodeString := base64.StdEncoding.EncodeToString(encrypt) - resp := Operation{Data: encodeString} - //bs, _ := json.Marshal(resp) - //_, err = http.Post("https://api.koderover.com/api/operation/admin/user", - // "application/json", - // strings.NewReader(string(bs))) - - _, err = httpclient.Post("https://api.koderover.com/api/operation/admin/user", httpclient.SetBody(resp), httpclient.ForceContentType("application/json")) + reqBody := Operation{Data: encodeString} + _, err = httpclient.Post("https://api.koderover.com/api/operation/admin/user", httpclient.SetBody(reqBody), httpclient.ForceContentType("application/json")) return err } diff --git a/pkg/config/config.go b/pkg/config/config.go index ca1e0604..92c0d23f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -253,7 +253,3 @@ func AdminEmail() string { func AdminPassword() string { return viper.GetString(setting.ENVAdminPassword) } - -func Domain() string { - return viper.GetString(setting.ENVDomain) -} -- Gitee From 87454ffc17bc6c5be421ae4544ffa0adb9c1a890 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 16:31:04 +0800 Subject: [PATCH 0594/2647] refactor Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init_test.go | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 pkg/cli/initconfig/cmd/init_test.go diff --git a/pkg/cli/initconfig/cmd/init_test.go b/pkg/cli/initconfig/cmd/init_test.go deleted file mode 100644 index a7902aec..00000000 --- a/pkg/cli/initconfig/cmd/init_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package cmd - -import ( - "testing" -) - -func TestRegister(t *testing.T) { - reportRegister("test@register.com", "test") -} -- Gitee From 02e0aa8e6ededfc863f011d3f406104a9d90e95d Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 16:31:35 +0800 Subject: [PATCH 0595/2647] refactor Signed-off-by: mouuii --- pkg/setting/consts.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0545b556..e81dafee 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -116,7 +116,6 @@ const ( // initconfig ENVAdminEmail = "ADMIN_EMAIL" ENVAdminPassword = "ADMIN_PASSWORD" - ENVDomain = "ADDRESS" PresetAccount = "admin" ) -- Gitee From 9e4d8dda7b730ee9aae3ca6cf513e3082123dfaa Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 10 Nov 2021 16:36:51 +0800 Subject: [PATCH 0596/2647] update code Signed-off-by: allenshen --- .../aslan/core/log/service/log.go | 11 ++++------ pkg/tool/s3/client.go | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 290b6ba1..73778bc9 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -25,8 +25,6 @@ import ( "go.uber.org/zap" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/s3" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -101,12 +99,11 @@ func getContainerLogFromS3(pipelineName, filenamePrefix string, taskID int64, lo return "", err } fullPath := storage.GetObjectPath(fileName) - err = client.Download(storage.Bucket, fullPath, tempFile) + err = client.DownloadWithOption(storage.Bucket, fullPath, tempFile, &s3tool.DownloadOption{ + IgnoreNotExistError: true, + RetryNum: 3, + }) if err != nil { - //ignore error if log not exist - if e, ok := err.(awserr.Error); ok && e.Code() == s3.ErrCodeNoSuchKey { - return "", nil - } log.Errorf("GetContainerLogFromS3 Download err:%v", err) return "", err } diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index cdabe6c6..0128520c 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -38,6 +38,15 @@ type Client struct { *s3.S3 } +type DownloadOption struct { + IgnoreNotExistError bool + RetryNum int +} + +var defaultDownloadOption = &DownloadOption{ + RetryNum: 3, +} + func NewClient(endpoint, ak, sk string, insecure, forcedPathStyle bool) (*Client, error) { creds := credentials.NewStaticCredentials(ak, sk, "") config := &aws.Config{ @@ -70,8 +79,17 @@ func (c *Client) ValidateBucket(bucketName string) error { return fmt.Errorf("validate s3 error: given bucket does not exist") } +func (c *Client) DownloadWithOption(bucketName, objectKey, dest string, option *DownloadOption) error { + return c.download(bucketName, objectKey, dest, option) +} + // Download the file to object storage func (c *Client) Download(bucketName, objectKey, dest string) error { + return c.download(bucketName, objectKey, dest, defaultDownloadOption) +} + +func (c *Client) download(bucketName, objectKey, dest string, option *DownloadOption) error { + retry := 0 var err error @@ -83,6 +101,9 @@ func (c *Client) Download(bucketName, objectKey, dest string) error { obj, err1 := c.GetObject(opt) if err1 != nil { if e, ok := err1.(awserr.Error); ok && e.Code() == s3.ErrCodeNoSuchKey { + if option.IgnoreNotExistError { + return nil + } return err1 } -- Gitee From 5de58c5c310a52e71a820b8ed956b678cdb00990 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 10 Nov 2021 16:42:55 +0800 Subject: [PATCH 0597/2647] update code Signed-off-by: allenshen --- pkg/tool/s3/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index 0128520c..9f8cd450 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -93,7 +93,7 @@ func (c *Client) download(bucketName, objectKey, dest string, option *DownloadOp retry := 0 var err error - for retry < 3 { + for retry < option.RetryNum { opt := &s3.GetObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), -- Gitee From 3c97bbc94396c2d9f94aecb896a332e5bc25b352 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 10 Nov 2021 16:50:51 +0800 Subject: [PATCH 0598/2647] add api for checking if 3rd-party login is enabled Signed-off-by: lou --- .../user/core/handler/login/third_party.go | 7 +++ pkg/microservice/user/core/handler/router.go | 2 + .../user/core/service/login/third_party.go | 14 +++++ .../user/core/service/user/user.go | 27 +++++----- pkg/microservice/user/server/rest/router.go | 6 +-- pkg/shared/client/systemconfig/connector.go | 52 +++++++++++++++++++ .../{systemconfig.go => email.go} | 21 -------- 7 files changed, 93 insertions(+), 36 deletions(-) create mode 100644 pkg/microservice/user/core/service/login/third_party.go create mode 100644 pkg/shared/client/systemconfig/connector.go rename pkg/shared/client/systemconfig/{systemconfig.go => email.go} (50%) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index 633b0d8c..6b9dc2f6 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -42,6 +42,13 @@ func Login(c *gin.Context) { c.Redirect(http.StatusSeeOther, authCodeURL) } +func ThirdPartyLoginEnabled(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp = login.ThirdPartyLoginEnabled() +} + func verifyAndDecode(ctx context.Context, code string) (*login.Claims, error) { oidcCtx := oidc.ClientContext(ctx, http.DefaultClient) oauth2Config := &oauth2.Config{ diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index 7f09d9c5..7e12cc50 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -50,6 +50,8 @@ func (*Router) Inject(router *gin.RouterGroup) { router.GET("login", login.Login) + router.GET("login-enabled", login.ThirdPartyLoginEnabled) + router.POST("login", login.LocalLogin) router.POST("signup", user.SignUp) diff --git a/pkg/microservice/user/core/service/login/third_party.go b/pkg/microservice/user/core/service/login/third_party.go new file mode 100644 index 00000000..10450db4 --- /dev/null +++ b/pkg/microservice/user/core/service/login/third_party.go @@ -0,0 +1,14 @@ +package login + +import "github.com/koderover/zadig/pkg/shared/client/systemconfig" + +type enabledStatus struct { + Enabled bool `json:"enabled"` +} + +func ThirdPartyLoginEnabled() *enabledStatus { + connectors, _ := systemconfig.New().ListConnectors() + return &enabledStatus{ + Enabled: len(connectors) > 0, + } +} diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 06c2aeed..d6b0aabe 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -6,6 +6,7 @@ import ( "net/url" "time" + "github.com/dexidp/dex/connector/ldap" ldapv3 "github.com/go-ldap/ldap/v3" "github.com/golang-jwt/jwt" "github.com/google/uuid" @@ -93,37 +94,39 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { logger.Error("can't find connector") return fmt.Errorf("can't find connector") } - l, err := ldapv3.Dial("tcp", si.Config.Host) + + config := si.Config.(*ldap.Config) + l, err := ldapv3.Dial("tcp", config.Host) if err != nil { - logger.Errorf("ldap dial host:%s error, error msg:%s", si.Config.Host, err) + logger.Errorf("ldap dial host:%s error, error msg:%s", config.Host, err) return err } defer l.Close() - err = l.Bind(si.Config.BindDN, si.Config.BindPW) + err = l.Bind(config.BindDN, config.BindPW) if err != nil { - logger.Errorf("ldap bind host:%s error, error msg:%s", si.Config.Host, err) + logger.Errorf("ldap bind host:%s error, error msg:%s", config.Host, err) return err } searchRequest := ldapv3.NewSearchRequest( - si.Config.GroupSearch.BaseDN, + config.GroupSearch.BaseDN, ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, - si.Config.GroupSearch.Filter, // The filter to apply - []string{si.Config.UserSearch.NameAttr}, // A list attributes to retrieve + config.GroupSearch.Filter, // The filter to apply + []string{config.UserSearch.NameAttr}, // A list attributes to retrieve nil, ) sr, err := l.Search(searchRequest) if err != nil { - logger.Errorf("ldap search host:%s error, error msg:%s", si.Config.Host, err) + logger.Errorf("ldap search host:%s error, error msg:%s", config.Host, err) return err } for _, entry := range sr.Entries { - account := si.Config.UserSearch.Username + account := config.UserSearch.Username name := account - if len(si.Config.UserSearch.NameAttr) != 0 { - name = si.Config.UserSearch.NameAttr + if len(config.UserSearch.NameAttr) != 0 { + name = config.UserSearch.NameAttr } _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), @@ -131,7 +134,7 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { - logger.Errorf("ldap host:%s sync user error, error msg:%s", si.Config.Host, err) + logger.Errorf("ldap host:%s sync user error, error msg:%s", config.Host, err) return err } } diff --git a/pkg/microservice/user/server/rest/router.go b/pkg/microservice/user/server/rest/router.go index d811f5ba..51139f3a 100644 --- a/pkg/microservice/user/server/rest/router.go +++ b/pkg/microservice/user/server/rest/router.go @@ -23,10 +23,10 @@ import ( ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { - for name, r := range map[string]injector{ - "/api/v1": new(handler.Router), + for _, r := range []injector{ + new(handler.Router), } { - r.Inject(router.Group(name)) + r.Inject(router.Group("/api/v1")) } } diff --git a/pkg/shared/client/systemconfig/connector.go b/pkg/shared/client/systemconfig/connector.go new file mode 100644 index 00000000..ad1359ff --- /dev/null +++ b/pkg/shared/client/systemconfig/connector.go @@ -0,0 +1,52 @@ +package systemconfig + +import ( + "encoding/json" + + "github.com/dexidp/dex/connector/ldap" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Connector struct { + Type string `json:"type"` + ID string `json:"id"` + Name string `json:"name"` + Config interface{} `json:"config"` +} + +func (c *Client) GetLDAPConnector(id string) (*Connector, error) { + url := "/connectors/" + id + + res := &Connector{} + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + configData, err := json.Marshal(res.Config) + if err != nil { + return nil, err + } + + ldapConfig := &ldap.Config{} + if err = json.Unmarshal(configData, ldapConfig); err != nil { + return nil, err + } + + res.Config = ldapConfig + + return res, err +} + +func (c *Client) ListConnectors() ([]*Connector, error) { + url := "/connectors" + + res := make([]*Connector, 0) + _, err := c.Get(url, httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, err +} diff --git a/pkg/shared/client/systemconfig/systemconfig.go b/pkg/shared/client/systemconfig/email.go similarity index 50% rename from pkg/shared/client/systemconfig/systemconfig.go rename to pkg/shared/client/systemconfig/email.go index 818a0382..72189414 100644 --- a/pkg/shared/client/systemconfig/systemconfig.go +++ b/pkg/shared/client/systemconfig/email.go @@ -1,18 +1,9 @@ package systemconfig import ( - "github.com/dexidp/dex/connector/ldap" - "github.com/koderover/zadig/pkg/tool/httpclient" ) -type Connector struct { - Type string `json:"type"` - ID string `json:"id"` - Name string `json:"name"` - Config *ldap.Config `json:"config"` -} - type Email struct { Name string `json:"name"` Port int `json:"port"` @@ -20,18 +11,6 @@ type Email struct { Password string `json:"password"` } -func (c *Client) GetLDAPConnector(id string) (*Connector, error) { - url := "/connectors/" + id - - res := &Connector{} - _, err := c.Get(url, httpclient.SetResult(res)) - if err != nil { - return nil, err - } - - return res, err -} - func (c *Client) GetEmailHost() (*Email, error) { url := "/emails/internal/host/" -- Gitee From eb2ceb60ee342f45047bb3965fd75cf47e33d50a Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 17:00:55 +0800 Subject: [PATCH 0599/2647] refactor Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index f3c98445..24a15d12 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -38,10 +38,7 @@ import ( func init() { rootCmd.AddCommand(initCmd) log.Init(&log.Config{ - Level: config.LogLevel(), - Filename: config.LogFile(), - SendToFile: config.SendLogToFile(), - Development: config.Mode() != setting.ReleaseMode, + Level: config.LogLevel(), }) } @@ -111,7 +108,7 @@ func presetSystemAdmin(email string, password, domain string) (string, error) { Email: email, }) if err != nil { - log.Infof("created admin err:%s", err) + log.Errorf("created admin err:%s", err) return "", err } // report register @@ -141,7 +138,7 @@ func reportRegister(domain, email string) error { } encodeString := base64.StdEncoding.EncodeToString(encrypt) reqBody := Operation{Data: encodeString} - _, err = httpclient.Post("https://api.koderover.com/api/operation/admin/user", httpclient.SetBody(reqBody), httpclient.ForceContentType("application/json")) + _, err = httpclient.Post("https://api.koderover.com/api/operation/admin/user", httpclient.SetBody(reqBody)) return err } -- Gitee From 0b96bb4165af9c7881ae59f9d90ee5aba2b5d4e9 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 10 Nov 2021 17:03:28 +0800 Subject: [PATCH 0600/2647] update after review Signed-off-by: lou --- .../user/core/service/login/third_party.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/user/core/service/login/third_party.go b/pkg/microservice/user/core/service/login/third_party.go index 10450db4..ba50713e 100644 --- a/pkg/microservice/user/core/service/login/third_party.go +++ b/pkg/microservice/user/core/service/login/third_party.go @@ -1,13 +1,19 @@ package login -import "github.com/koderover/zadig/pkg/shared/client/systemconfig" +import ( + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" +) type enabledStatus struct { Enabled bool `json:"enabled"` } func ThirdPartyLoginEnabled() *enabledStatus { - connectors, _ := systemconfig.New().ListConnectors() + connectors, err := systemconfig.New().ListConnectors() + if err != nil { + log.Warnf("Failed to list connectors, err: %s", err) + } return &enabledStatus{ Enabled: len(connectors) > 0, } -- Gitee From 19ff0d327457e3bf0388891e7096732eec640753 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 17:08:48 +0800 Subject: [PATCH 0601/2647] refactor Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 24a15d12..d807c165 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -19,6 +19,7 @@ package cmd import ( _ "embed" "encoding/base64" + "encoding/json" "fmt" "time" @@ -122,17 +123,22 @@ func presetSystemAdmin(email string, password, domain string) (string, error) { type Operation struct { Data string `json:"data"` } +type Register struct { + Domain string `json:"domain"` + Username string `json:"username"` + Email string `json:"email"` + CreatedAt int64 `json:"created_at"` +} func reportRegister(domain, email string) error { - uploadStrFmt := ` - { - "domain":"%s", - "username":"admin", - "email":"%s", - "created_at":%d - }` - - encrypt, err := RSAEncrypt([]byte(fmt.Sprintf(uploadStrFmt, domain, email, time.Now().Unix()))) + register := Register{ + Domain: domain, + Username: "admin", + Email: email, + CreatedAt: time.Now().Unix(), + } + registerByte, _ := json.Marshal(register) + encrypt, err := RSAEncrypt([]byte(registerByte)) if err != nil { return err } -- Gitee From ea87e84b6a1ba11b51c7380258f491aef54816a5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 17:09:58 +0800 Subject: [PATCH 0602/2647] refactor Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index d807c165..1d0d6793 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -140,6 +140,7 @@ func reportRegister(domain, email string) error { registerByte, _ := json.Marshal(register) encrypt, err := RSAEncrypt([]byte(registerByte)) if err != nil { + log.Errorf("RSAEncrypt err: %s", err) return err } encodeString := base64.StdEncoding.EncodeToString(encrypt) -- Gitee From 61c56f2a7c72e7263335d3924317f86b98eadbba Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 17:57:20 +0800 Subject: [PATCH 0603/2647] refactor Signed-off-by: mouuii --- .../picket/core/public/handler/public.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index bf76c92d..800c0703 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - service2 "github.com/koderover/zadig/pkg/microservice/picket/core/public/service" + "github.com/koderover/zadig/pkg/microservice/picket/core/public/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -14,7 +14,7 @@ func CreateWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() body, _ := c.GetRawData() - res, err := service2.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) + res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) if err != nil { ctx.Err = err return @@ -47,7 +47,7 @@ func CancelWorkflowTask(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") name := c.Param("name") - statusCode, _ := service2.CancelWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) + statusCode, _ := service.CancelWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) var code int var errorMsg string if statusCode == http.StatusOK { @@ -71,7 +71,7 @@ func RestartWorkflowTask(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") name := c.Param("name") - statusCode, _ := service2.RestartWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) + statusCode, _ := service.RestartWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) var code int var errorMsg string if statusCode == http.StatusOK { @@ -94,7 +94,7 @@ func ListWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() commitId := c.Query("commitId") - ctx.Resp, ctx.Err = service2.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) + ctx.Resp, ctx.Err = service.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) } func ListDelivery(c *gin.Context) { @@ -105,5 +105,5 @@ func ListDelivery(c *gin.Context) { taskIDStr := c.Query("taskId") perPageStr := c.Query("perPage") pageStr := c.Query("page") - ctx.Resp, ctx.Err = service2.ListDelivery(c.Request.Header, c.Request.URL.Query(), productName, workflowName, taskIDStr, perPageStr, pageStr, ctx.Logger) + ctx.Resp, ctx.Err = service.ListDelivery(c.Request.Header, c.Request.URL.Query(), productName, workflowName, taskIDStr, perPageStr, pageStr, ctx.Logger) } -- Gitee From 6e6e9870f6c73da8c8bf2e54e32e380371ca6705 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 18:02:02 +0800 Subject: [PATCH 0604/2647] refactor Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/workflow.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 333eb5b0..c02e7cd4 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -6,6 +6,7 @@ import ( "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" ) func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.Values) ([]byte, error) { @@ -46,6 +47,7 @@ func (c *Client) RestartWorkflowTask(header http.Header, qs url.Values, id strin res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { + log.Errorf("RestartWorkflowTask err: %s,res: %s", err, string(res.Body())) return http.StatusInternalServerError, err } -- Gitee From 8ee96b7af0517977aecfcc0079175390c257aa54 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 18:06:03 +0800 Subject: [PATCH 0605/2647] refactor Signed-off-by: mouuii --- pkg/microservice/picket/client/aslan/workflow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index c02e7cd4..5bd8014c 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -45,7 +45,7 @@ func (c *Client) CancelWorkflowTask(header http.Header, qs url.Values, id string func (c *Client) RestartWorkflowTask(header http.Header, qs url.Values, id string, name string) (statusCode int, err error) { url := fmt.Sprintf("/workflow/workflowtask/id/%s/pipelines/%s/restart", id, name) - res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { log.Errorf("RestartWorkflowTask err: %s,res: %s", err, string(res.Body())) return http.StatusInternalServerError, err -- Gitee From 2ff7de4b61b8818cf4a69151ad39c9565afd2e87 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 18:34:50 +0800 Subject: [PATCH 0606/2647] add policy Signed-off-by: mouuii --- .../aslan/core/delivery/handler/policy.yaml | 2 ++ .../aslan/core/workflow/handler/policy.yaml | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml index 38084a4f..fe2625db 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -8,6 +8,8 @@ rules: rules: - method: GET endpoint: "/api/aslan/delivery/releases" + - method: GET + endpoint: "/api/directory/dc/releases" - action: delete_delivery alias: "删除版本" description: "" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 6a488c63..8afcb973 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -28,6 +28,8 @@ rules: endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" - method: GET endpoint: "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*" + - method: GET + endpoint: "/api/directory/workflowTask" - action: edit_workflow alias: "编辑工作流" description: "" @@ -42,6 +44,8 @@ rules: endpoint: "api/aslan/workflow/workflow" - method: PUT endpoint: "/api/aslan/workflow/workflow/old/?*/new/?*" + - method: POST + endpoint: "/api/directory/workflowTask/create" - action: delete_workflow alias: "删除工作流" description: "" @@ -59,4 +63,8 @@ rules: - method: POST endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" - method: DELETE - endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" \ No newline at end of file + endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" + - method: POST + endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" + - method: POST + endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" \ No newline at end of file -- Gitee From 083b53f2c1c86e1ffc70170699d16a8c5ab7e8d9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 10 Nov 2021 18:35:36 +0800 Subject: [PATCH 0607/2647] add policy Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 8afcb973..d3563eb3 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -67,4 +67,4 @@ rules: - method: POST endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" - method: POST - endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" \ No newline at end of file + endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" -- Gitee From 460d040c7556e598030148fa94651dbd51a035b1 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 10 Nov 2021 19:35:44 +0800 Subject: [PATCH 0608/2647] add public urls Signed-off-by: lou --- .../aslan/internal/cache/cache.go | 51 ------------------- .../core/service/bundle/exemption_urls.go | 2 +- 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 pkg/microservice/aslan/internal/cache/cache.go diff --git a/pkg/microservice/aslan/internal/cache/cache.go b/pkg/microservice/aslan/internal/cache/cache.go deleted file mode 100644 index b6266355..00000000 --- a/pkg/microservice/aslan/internal/cache/cache.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package cache - -import ( - "sync" - - "github.com/coocood/freecache" -) - -const defaultSize = 1024 * 1024 * 10 // 10 Mi - -var once sync.Once -var cache *freecache.Cache - -// Set sets a key, value and expiration for a cache entry and stores it in the cache. -// If the key is larger than 65535 or value is larger than 1/1024 of the cache size, -// the entry will not be written to the cache. expireSeconds <= 0 means no expire, -// but it can be evicted when cache is full. -func Set(key, value []byte, expireSeconds int) error { - once.Do(func() { - cache = initCache(0) - }) - return cache.Set(key, value, expireSeconds) -} - -// Get returns the value or not found error. -func Get(key []byte) ([]byte, error) { - once.Do(func() { - cache = initCache(defaultSize) - }) - return cache.Get(key) -} - -func initCache(size int) *freecache.Cache { - return freecache.NewCache(size) -} diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index b84f5631..cb74c3c8 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -42,7 +42,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve"}, + Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve", "api/v1/login-enabled"}, }, { Methods: []string{"GET"}, -- Gitee From fee04c0c3725855e7928e017971474ae75a6be2a Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 11 Nov 2021 11:58:17 +0800 Subject: [PATCH 0609/2647] fix helm deploy timeout bug Signed-off-by: allenshen --- .../core/service/taskplugin/deploy.go | 113 +++++++++++------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index d82c9e31..68b22aa8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -28,6 +28,7 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -139,22 +140,68 @@ type SelectorBuilder struct { EnvName string } -const ( - // ProductLabel ... - ProductLabel = "s-product" - // GroupLabel ... - GroupLabel = "s-group" - // ServiceLabel ... - ServiceLabel = "s-service" - // ConfigBackupLabel ... - ConfigBackupLabel = "config-backup" - // NamespaceLabel - EnvNameLabel = "s-env" - // EnvName ... - UpdateBy = "update-by" - UpdateByID = "update-by-id" - UpdateTime = "update-time" -) +type ResourceComponentSet struct { + Name string + Kind string + Containers []v1.Container + Annotations map[string]string +} + +func RcsListFromDeployments(source []*appsv1.Deployment) []*ResourceComponentSet { + rcsList := make([]*ResourceComponentSet, 0) + for _, deploy := range source { + rcsList = append(rcsList, &ResourceComponentSet{ + Name: deploy.Name, + Kind: setting.Deployment, + Containers: deploy.Spec.Template.Spec.Containers, + Annotations: deploy.Annotations, + }) + } + return rcsList +} + +func RcsListFromStatefulSets(source []*appsv1.StatefulSet) []*ResourceComponentSet { + rcsList := make([]*ResourceComponentSet, 0) + for _, sfs := range source { + rcsList = append(rcsList, &ResourceComponentSet{ + Name: sfs.Name, + Kind: setting.StatefulSet, + Containers: sfs.Spec.Template.Spec.Containers, + Annotations: sfs.Annotations, + }) + } + return rcsList +} + +// find affected resources(deployment+statefulSet) for helm install or upgrade +// resource type: deployment statefulSet +func (p *DeployTaskPlugin) findHelmAffectedResources(namespace, serviceName string, resList []*ResourceComponentSet) { + for _, res := range resList { + annotation := res.Annotations + if len(annotation) == 0 { + continue + } + // filter by services + if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { + extractedServiceName := util.ExtraServiceName(chartRelease, namespace) + if extractedServiceName != serviceName { + continue + } + } + for _, container := range res.Containers { + resolvedImageUrl := resolveImageUrl(container.Image) + if resolvedImageUrl[setting.PathSearchComponentImage] == p.Task.ContainerName { + p.Log.Infof("%s find match container.name:%s container.image:%s", res.Kind, container.Name, container.Image) + p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ + Kind: res.Kind, + Container: container.Name, + Origin: container.Image, + Name: res.Name, + }) + } + } + } +} func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ *task.PipelineCtx, _ string) { var ( @@ -335,34 +382,18 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * helmClient helmclient.Client ) - deployments, _ := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) - for _, deploy := range deployments { - for _, container := range deploy.Spec.Template.Spec.Containers { - if strings.Contains(container.Image, p.Task.ContainerName) { - p.Log.Infof("deployments find match container.name:%s", container.Name) - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: setting.Deployment, - Container: container.Name, - Origin: container.Image, - Name: deploy.Name, - }) - } - } + deployments, err := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) + if err != nil { + p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) + } else { + p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, RcsListFromDeployments(deployments)) } statefulSets, _ := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) - for _, sts := range statefulSets { - for _, container := range sts.Spec.Template.Spec.Containers { - if strings.Contains(container.Image, p.Task.ContainerName) { - p.Log.Infof("statefulSets find match container.name:%s", container.Name) - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: setting.StatefulSet, - Container: container.Name, - Origin: container.Image, - Name: sts.Name, - }) - } - } + if err != nil { + p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) + } else { + p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, RcsListFromStatefulSets(statefulSets)) } p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, -- Gitee From c2446d9a2065746871ddbf11084dfeccffa6c389 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 11 Nov 2021 15:10:15 +0800 Subject: [PATCH 0610/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/handler/login/third_party.go | 6 +++--- pkg/microservice/user/core/service/login/local.go | 10 +++++++--- pkg/microservice/user/core/service/login/token.go | 4 ++-- pkg/microservice/user/core/service/user/user.go | 12 +++++++++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index 6b9dc2f6..afb951f6 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -112,16 +112,16 @@ func Callback(c *gin.Context) { } user, err := user.SyncUser(&user.SyncUserInfo{ - Account: claims.Name, + Account: claims.FederatedClaims.UserId, Name: claims.Name, + Email: claims.Email, IdentityType: claims.FederatedClaims.ConnectorId, }, ctx.Logger) if err != nil { ctx.Err = err return } - claims.Uid = user.UID - claims.Account = claims.Name + claims.UID = user.UID claims.StandardClaims.ExpiresAt = time.Now().Add(time.Duration(config.TokenExpiresAt()) * time.Minute).Unix() userToken, err := login.CreateToken(claims) if err != nil { diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index 60906169..6b0c1f00 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -63,13 +63,17 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { return nil, err } token, err := CreateToken(&Claims{ - Name: user.Name, - Account: user.Account, - Uid: user.UID, + Name: user.Name, + UID: user.UID, + Email: user.Email, StandardClaims: jwt.StandardClaims{ Audience: setting.ProductName, ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), }, + FederatedClaims: FederatedClaims{ + ConnectorId: user.IdentityType, + UserId: user.Account, + }, }) if err != nil { logger.Errorf("LocalLogin user:%s create token error, error msg:%s", args.Account, err.Error()) diff --git a/pkg/microservice/user/core/service/login/token.go b/pkg/microservice/user/core/service/login/token.go index 734e19b5..dfa3d4fb 100644 --- a/pkg/microservice/user/core/service/login/token.go +++ b/pkg/microservice/user/core/service/login/token.go @@ -8,8 +8,8 @@ import ( type Claims struct { Name string `json:"name"` - Account string `json:"account"` - Uid string `json:"uid"` + Email string `json:"email"` + UID string `json:"uid"` FederatedClaims FederatedClaims `json:"federated_claims"` jwt.StandardClaims } diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index d6b0aabe..3048d56c 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -77,6 +77,7 @@ type SyncUserInfo struct { Account string `json:"account"` IdentityType string `json:"identityType"` Name string `json:"name"` + Email string `json:"email"` } type RetrieveResp struct { @@ -131,6 +132,7 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), Name: name, + Email: config.UserSearch.EmailAttr, IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { @@ -308,13 +310,17 @@ func Retrieve(account string, logger *zap.SugaredLogger) (*RetrieveResp, error) } token, err := login.CreateToken(&login.Claims{ - Name: user.Name, - Account: user.Account, - Uid: user.UID, + Name: user.Name, + UID: user.UID, + Email: user.Email, StandardClaims: jwt.StandardClaims{ Audience: setting.ProductName, ExpiresAt: time.Now().Add(5 * time.Minute).Unix(), }, + FederatedClaims: login.FederatedClaims{ + UserId: user.Account, + ConnectorId: user.IdentityType, + }, }) if err != nil { logger.Errorf("Retrieve user:%s create token error, error msg:%s", user.Account, err) -- Gitee From 3055c0a503a5d13717d96395ac812d45fb59e3ae Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 11 Nov 2021 16:28:08 +0800 Subject: [PATCH 0611/2647] * Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/project.go | 2 +- pkg/microservice/picket/core/filter/service/project.go | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 31f29039..2b35cd78 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -60,7 +60,7 @@ type ProjectMinimalRepresentation struct { } func (o *ProjectListOptions) InNames() []string { - if len(o.Names) == 0 || (len(o.Names) == 1 && o.Names[0] == allProjects) { + if len(o.Names) == 0 { return []string{} } diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 1b0d45c6..fded8d7b 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -110,8 +110,10 @@ func ListProjects(header http.Header, qs url.Values, logger *zap.SugaredLogger) return []byte("[]"), nil } - for _, name := range names { - qs.Add("names", name) + if !(len(names) == 1 && names[0] == "*") { + for _, name := range names { + qs.Add("names", name) + } } aslanClient := aslan.New() -- Gitee From 22f33c33cf909835c9f3327990bd410f5d9e5413 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 11 Nov 2021 16:30:19 +0800 Subject: [PATCH 0612/2647] * Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/project.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 2b35cd78..aafa3898 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -30,8 +30,6 @@ const ( VerbosityDetailed QueryVerbosity = "detailed" // all information VerbosityBrief QueryVerbosity = "brief" // short information or a summary VerbosityMinimal QueryVerbosity = "minimal" // very little information, usually only a resource identifier - - allProjects = "*" ) type ProjectListOptions struct { -- Gitee From 8da4abe2e24f152f5543b94ed2fe5a8ecbeae684 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 11 Nov 2021 17:47:19 +0800 Subject: [PATCH 0613/2647] * Signed-off-by: mouuii --- .../aslan/core/project/service/project.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index aafa3898..314d9ef6 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -57,14 +57,6 @@ type ProjectMinimalRepresentation struct { Name string `json:"name"` } -func (o *ProjectListOptions) InNames() []string { - if len(o.Names) == 0 { - return []string{} - } - - return o.Names -} - func ListProjects(opts *ProjectListOptions, logger *zap.SugaredLogger) (interface{}, error) { switch opts.Verbosity { case VerbosityDetailed: @@ -150,7 +142,7 @@ func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { var res []*ProjectMinimalRepresentation - names, err := templaterepo.NewProductColl().ListNames(opts.InNames()) + names, err := templaterepo.NewProductColl().ListNames(opts.Names) if err != nil { logger.Errorf("Failed to list project names, err: %s", err) return nil, err @@ -187,7 +179,7 @@ func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger } func getProjectsWithEnvs(opts *ProjectListOptions) (sets.String, map[string][]string, error) { - nameWithEnvs, err := mongodb.NewProductColl().ListProjectsInNames(opts.InNames()) + nameWithEnvs, err := mongodb.NewProductColl().ListProjectsInNames(opts.Names) if err != nil { return nil, nil, err } @@ -203,7 +195,7 @@ func getProjectsWithEnvs(opts *ProjectListOptions) (sets.String, map[string][]st } func getProjects(opts *ProjectListOptions) (sets.String, map[string]*templaterepo.ProjectInfo, error) { - res, err := templaterepo.NewProductColl().ListProjectBriefs(opts.InNames()) + res, err := templaterepo.NewProductColl().ListProjectBriefs(opts.Names) if err != nil { return nil, nil, err } -- Gitee From 3bb1142b34258149abeba6d90f435a2d14127064 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 11 Nov 2021 20:16:57 +0800 Subject: [PATCH 0614/2647] optimize Signed-off-by: panxunying <641770806@qq.com> --- .../user/core/handler/login/third_party.go | 2 +- .../user/core/service/login/local.go | 7 ++++--- .../user/core/service/login/token.go | 9 +++++---- pkg/microservice/user/core/service/user/user.go | 16 ++++++++++++++-- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index afb951f6..b9cd4a89 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -112,7 +112,7 @@ func Callback(c *gin.Context) { } user, err := user.SyncUser(&user.SyncUserInfo{ - Account: claims.FederatedClaims.UserId, + Account: claims.PreferredUsername, Name: claims.Name, Email: claims.Email, IdentityType: claims.FederatedClaims.ConnectorId, diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index 6b0c1f00..83a8ecb5 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -63,9 +63,10 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { return nil, err } token, err := CreateToken(&Claims{ - Name: user.Name, - UID: user.UID, - Email: user.Email, + Name: user.Name, + UID: user.UID, + Email: user.Email, + PreferredUsername: user.Account, StandardClaims: jwt.StandardClaims{ Audience: setting.ProductName, ExpiresAt: time.Now().Add(24 * time.Hour).Unix(), diff --git a/pkg/microservice/user/core/service/login/token.go b/pkg/microservice/user/core/service/login/token.go index dfa3d4fb..bc24988e 100644 --- a/pkg/microservice/user/core/service/login/token.go +++ b/pkg/microservice/user/core/service/login/token.go @@ -7,10 +7,11 @@ import ( ) type Claims struct { - Name string `json:"name"` - Email string `json:"email"` - UID string `json:"uid"` - FederatedClaims FederatedClaims `json:"federated_claims"` + Name string `json:"name"` + Email string `json:"email"` + UID string `json:"uid"` + PreferredUsername string `json:"preferred_username"` + FederatedClaims FederatedClaims `json:"federated_claims"` jwt.StandardClaims } diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 3048d56c..5bebb468 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -131,8 +131,8 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { } _, err := SyncUser(&SyncUserInfo{ Account: entry.GetAttributeValue(account), - Name: name, - Email: config.UserSearch.EmailAttr, + Name: entry.GetAttributeValue(name), + Email: entry.GetAttributeValue(config.UserSearch.EmailAttr), IdentityType: si.ID, // ldap may have not only one instance, so use id as identityType }, logger) if err != nil { @@ -491,6 +491,7 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us UID: uid.String(), Name: syncUserInfo.Name, Account: syncUserInfo.Account, + Email: syncUserInfo.Email, IdentityType: syncUserInfo.IdentityType, } err = orm.CreateUser(user, tx) @@ -499,6 +500,17 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us logger.Error("SyncUser create user:%s error, error msg:%s", syncUserInfo.Account, err.Error()) return nil, err } + } else { + err = orm.UpdateUser(user.UID, &models.User{ + Name: syncUserInfo.Name, + Account: syncUserInfo.Account, + Email: syncUserInfo.Email, + }, tx) + if err != nil { + tx.Rollback() + logger.Error("SyncUser update user:%s error, error msg:%s", syncUserInfo.Account, err.Error()) + return nil, err + } } userLogin, err := orm.GetUserLogin(user.UID, user.Account, config.AccountLoginType, tx) if err != nil { -- Gitee From a57afd38d800b0b13e350723e17e127894ea3967 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 11 Nov 2021 21:37:16 +0800 Subject: [PATCH 0615/2647] fix github webhook Signed-off-by: lou --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 61 ++++++------------- .../core/workflow/service/webhook/github.go | 43 ++++++------- pkg/tool/git/github/repositories.go | 7 +-- 3 files changed, 37 insertions(+), 74 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index c0bbc2e2..71f721a0 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -67,20 +67,19 @@ func refreshWebHookSecret() error { return err } - cs := toCodeHostSet(codeHosts) for _, hook := range hooks { var cl hookRefresher - ch, ok := cs[codeHostUniqueID{address: hook.Address, owner: hook.Owner}] - if !ok { + ch := getCodeHostByAddressAndOwner(hook.Address, hook.Owner, codeHosts) + if ch == nil { log.Warnf("Failed to get codehost for %s/%s", hook.Address, hook.Owner) continue } switch ch.Type { case setting.SourceFromGithub: - cl = github.NewClient(ch.accessToken, config.ProxyHTTPSAddr()) + cl = github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(ch.address, ch.accessToken) + cl, err = gitlab.NewClient(ch.Address, ch.AccessToken) if err != nil { log.Warnf("Failed to create gitlab client, err: %s", err) continue @@ -99,45 +98,19 @@ func refreshWebHookSecret() error { return nil } -type codeHostUniqueID struct { - address, owner string -} - -type codeHostItem struct { - codeHostUniqueID - accessToken string - Type string -} - -type codeHostSet map[codeHostUniqueID]codeHostItem - -// NewCodeHostSet creates a HookSet from a list of values. -func NewCodeHostSet(items ...codeHostItem) codeHostSet { - ss := codeHostSet{} - ss.Insert(items...) - return ss -} - -// Insert adds items to the set. -func (s codeHostSet) Insert(items ...codeHostItem) codeHostSet { - for _, item := range items { - s[item.codeHostUniqueID] = item - } - return s -} - -func toCodeHostSet(codeHosts []*systemconfig.CodeHost) codeHostSet { - res := NewCodeHostSet() - for _, h := range codeHosts { - res.Insert(codeHostItem{ - codeHostUniqueID: codeHostUniqueID{ - address: h.Address, - owner: h.Namespace, - }, - accessToken: h.AccessToken, - Type: h.Type, - }) +func getCodeHostByAddressAndOwner(address, owner string, all []*systemconfig.CodeHost) *systemconfig.CodeHost { + for _, one := range all { + if one.Address != address { + continue + } + // it is a limitation, we support only one gitlab account before + if one.Type == "gitlab" { + return one + } + if one.Namespace == owner { + return one + } } - return res + return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 26cc03b8..07af078c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -49,32 +49,31 @@ const ( payloadFormParam = "payload" ) -func ProcessGithubHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) (output string, err error) { +func ProcessGithubHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) (string, error) { hookType := github.WebHookType(req) if hookType == "integration_installation" || hookType == "installation" || hookType == "ping" { - output = fmt.Sprintf("event %s received", hookType) - return + return fmt.Sprintf("event %s received", hookType), nil } hookSecret := gitservice.GetHookSecret() - if hookSecret == "" { var headers []string for header := range req.Header { headers = append(headers, fmt.Sprintf("%s: %s", header, req.Header.Get(header))) } - log.Infof("[Webhook] hook headers: \n %s", strings.Join(headers, "\n ")) } - err = validateSecret(payload, []byte(hookSecret), req) + err := validateSecret(payload, []byte(hookSecret), req) if err != nil { - return + log.Errorf("Failed to validate secret, err: %s", err) + return "", err } event, err := github.ParseWebHook(github.WebHookType(req), payload) if err != nil { - return + log.Errorf("Failed to parse webhook, err: %s", err) + return "", err } deliveryID := github.DeliveryID(req) @@ -85,15 +84,13 @@ func ProcessGithubHook(payload []byte, req *http.Request, requestID string, log switch et := event.(type) { case *github.PullRequestEvent: if *et.Action != "opened" && *et.Action != "synchronize" && *et.Action != "reopened" { - output = fmt.Sprintf("action %s is skipped", *et.Action) - return + return fmt.Sprintf("action %s is skipped", *et.Action), nil } tasks, err = prEventToPipelineTasks(et, requestID, log) if err != nil { log.Errorf("prEventToPipelineTasks error: %v", err) - err = e.ErrGithubWebHook.AddErr(err) - return + return "", e.ErrGithubWebHook.AddErr(err) } case *github.PushEvent: @@ -111,40 +108,34 @@ func ProcessGithubHook(payload []byte, req *http.Request, requestID string, log tasks, err = pushEventToPipelineTasks(et, requestID, log) if err != nil { - log.Infof("pushEventToPipelineTasks error: %v", err) - err = e.ErrGithubWebHook.AddErr(err) - return + log.Errorf("pushEventToPipelineTasks error: %v", err) + return "", e.ErrGithubWebHook.AddErr(err) } case *github.CheckRunEvent: // The action performed. Can be "created", "updated", "rerequested" or "requested_action". if *et.Action != "rerequested" { - output = fmt.Sprintf("action %s is skipped", *et.Action) - return + return fmt.Sprintf("action %s is skipped", *et.Action), nil } id := et.CheckRun.GetExternalID() items := strings.Split(id, "/") if len(items) != 2 { - err = fmt.Errorf("invalid CheckRun ExternalID %s", id) - return + return "", fmt.Errorf("invalid CheckRun ExternalID %s", id) } pipeName := items[0] var taskID int64 taskID, err = strconv.ParseInt(items[1], 10, 64) if err != nil { - err = fmt.Errorf("invalid taskID in CheckRun ExternalID %s", id) - return + return "", fmt.Errorf("invalid taskID in CheckRun ExternalID %s", id) } if err = workflowservice.RestartPipelineTaskV2("CheckRun", taskID, pipeName, config.SingleType, log); err != nil { - err = e.ErrGithubWebHook.AddErr(err) - return + return "", e.ErrGithubWebHook.AddErr(err) } default: - output = fmt.Sprintf("event %s not support", hookType) - return + return fmt.Sprintf("event %s not support", hookType), nil } for _, task := range tasks { @@ -158,7 +149,7 @@ func ProcessGithubHook(payload []byte, req *http.Request, requestID string, log log.Infof("[Webhook] %s triggered task %s:%d", deliveryID, task.PipelineName, resp.TaskID) } - return + return "", nil } func validateSecret(payload, secretKey []byte, r *http.Request) error { diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 4d38997c..5bba3419 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -200,12 +200,11 @@ func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.H func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, hook *git.Hook) (*github.Hook, error) { h := &github.Hook{ Config: map[string]interface{}{ - "secret": hook.Secret, + "url": hook.URL, + "content_type": "json", + "secret": hook.Secret, }, } - if hook.URL != "" { - h.Config["url"] = hook.URL - } if len(hook.Events) > 0 { h.Events = hook.Events } -- Gitee From 1b213dfc2c7c80cbf6a85b9580e4c2654b2e4c11 Mon Sep 17 00:00:00 2001 From: lilianzhu Date: Fri, 12 Nov 2021 09:37:28 +0800 Subject: [PATCH 0616/2647] update examples/multi-service-demo Signed-off-by: lilianzhu --- examples/multi-service-demo/Dockerfile | 4 +- examples/multi-service-demo/Makefile | 10 ++- .../charts/service1/.helmignore | 23 ------- .../charts/service1/Chart.yaml | 24 ------- .../charts/service1/templates/secret.yaml | 7 --- .../charts/service2/.helmignore | 23 ------- .../charts/service2/templates/_helpers.tpl | 63 ------------------- .../charts/service2/templates/deployment.yaml | 38 ----------- .../charts/service2/templates/secret.yaml | 7 --- .../service2 => general-chart}/Chart.yaml | 2 +- .../templates/_helpers.tpl | 0 .../templates/deployment.yaml | 9 +-- .../general-chart/templates/secret.yaml | 10 +++ .../templates/service.yaml | 0 .../general-chart/values.yaml | 19 ++++++ .../k8s-yaml/service1/deployment.yaml | 2 +- .../k8s-yaml/service1/service.yaml | 4 +- .../k8s-yaml/service2/deployment.yaml | 2 +- .../k8s-yaml/service2/service.yaml | 4 +- .../k8s-yaml/service3/deployment.yaml | 29 +++++++++ .../service3}/service.yaml | 11 ++-- .../multi-service-demo/k8s-yaml/template.yaml | 42 +++++++++++++ .../{ => src}/service1/service1.go | 4 +- .../{ => src}/service2/service2.go | 0 .../src/service3/service3.go | 44 +++++++++++++ .../values.yaml => values/service1.yaml} | 17 +++-- .../values.yaml => values/service2.yaml} | 17 +++-- .../multi-service-demo/values/service3.yaml | 20 ++++++ 28 files changed, 204 insertions(+), 231 deletions(-) delete mode 100644 examples/multi-service-demo/charts/service1/.helmignore delete mode 100644 examples/multi-service-demo/charts/service1/Chart.yaml delete mode 100644 examples/multi-service-demo/charts/service1/templates/secret.yaml delete mode 100644 examples/multi-service-demo/charts/service2/.helmignore delete mode 100644 examples/multi-service-demo/charts/service2/templates/_helpers.tpl delete mode 100644 examples/multi-service-demo/charts/service2/templates/deployment.yaml delete mode 100644 examples/multi-service-demo/charts/service2/templates/secret.yaml rename examples/multi-service-demo/{charts/service2 => general-chart}/Chart.yaml (98%) rename examples/multi-service-demo/{charts/service1 => general-chart}/templates/_helpers.tpl (100%) rename examples/multi-service-demo/{charts/service1 => general-chart}/templates/deployment.yaml (82%) create mode 100644 examples/multi-service-demo/general-chart/templates/secret.yaml rename examples/multi-service-demo/{charts/service1 => general-chart}/templates/service.yaml (100%) create mode 100644 examples/multi-service-demo/general-chart/values.yaml create mode 100644 examples/multi-service-demo/k8s-yaml/service3/deployment.yaml rename examples/multi-service-demo/{charts/service2/templates => k8s-yaml/service3}/service.yaml (32%) create mode 100644 examples/multi-service-demo/k8s-yaml/template.yaml rename examples/multi-service-demo/{ => src}/service1/service1.go (91%) rename examples/multi-service-demo/{ => src}/service2/service2.go (100%) create mode 100644 examples/multi-service-demo/src/service3/service3.go rename examples/multi-service-demo/{charts/service1/values.yaml => values/service1.yaml} (63%) rename examples/multi-service-demo/{charts/service2/values.yaml => values/service2.yaml} (67%) create mode 100644 examples/multi-service-demo/values/service3.yaml diff --git a/examples/multi-service-demo/Dockerfile b/examples/multi-service-demo/Dockerfile index bb035846..bdab9f3a 100644 --- a/examples/multi-service-demo/Dockerfile +++ b/examples/multi-service-demo/Dockerfile @@ -4,6 +4,4 @@ ARG service WORKDIR /workspace -COPY ${service}/${service} /workspace/${service} - -CMD [ "./${service}" ] \ No newline at end of file +COPY src/${service}/${service} /workspace/${service} \ No newline at end of file diff --git a/examples/multi-service-demo/Makefile b/examples/multi-service-demo/Makefile index e2f9cd6a..54a96417 100644 --- a/examples/multi-service-demo/Makefile +++ b/examples/multi-service-demo/Makefile @@ -1,6 +1,10 @@ - build-service1: - cd service1 && go clean && GOOS=linux GOARCH=amd64 go build -o service1 service1.go + cd src/service1 && go clean && GOOS=linux GOARCH=amd64 go build -o service1 service1.go + build-service2: - cd service2 && go clean && GOOS=linux GOARCH=amd64 go build -o service2 service2.go \ No newline at end of file + cd src/service2 && go clean && GOOS=linux GOARCH=amd64 go build -o service2 service2.go + + +build-service3: + cd src/service3 && go clean && GOOS=linux GOARCH=amd64 go build -o service3 service3.go \ No newline at end of file diff --git a/examples/multi-service-demo/charts/service1/.helmignore b/examples/multi-service-demo/charts/service1/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/examples/multi-service-demo/charts/service1/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/examples/multi-service-demo/charts/service1/Chart.yaml b/examples/multi-service-demo/charts/service1/Chart.yaml deleted file mode 100644 index ae9c80a0..00000000 --- a/examples/multi-service-demo/charts/service1/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v2 -name: service1 -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/examples/multi-service-demo/charts/service1/templates/secret.yaml b/examples/multi-service-demo/charts/service1/templates/secret.yaml deleted file mode 100644 index cd16a681..00000000 --- a/examples/multi-service-demo/charts/service1/templates/secret.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - .dockercfg: {{ .Values.registrySecret }} -kind: Secret -metadata: - name: {{ .Values.imagePullSecrets }} -type: kubernetes.io/dockercfg \ No newline at end of file diff --git a/examples/multi-service-demo/charts/service2/.helmignore b/examples/multi-service-demo/charts/service2/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/examples/multi-service-demo/charts/service2/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/examples/multi-service-demo/charts/service2/templates/_helpers.tpl b/examples/multi-service-demo/charts/service2/templates/_helpers.tpl deleted file mode 100644 index 2668feb5..00000000 --- a/examples/multi-service-demo/charts/service2/templates/_helpers.tpl +++ /dev/null @@ -1,63 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "chart.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "chart.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "chart.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "chart.labels" -}} -helm.sh/chart: {{ include "chart.chart" . }} -{{ include "chart.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "chart.selectorLabels" -}} -app.kubernetes.io/name: {{ include "chart.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Create the name of the service account to use -*/}} -{{- define "chart.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} diff --git a/examples/multi-service-demo/charts/service2/templates/deployment.yaml b/examples/multi-service-demo/charts/service2/templates/deployment.yaml deleted file mode 100644 index 2ecbd191..00000000 --- a/examples/multi-service-demo/charts/service2/templates/deployment.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Values.fullnameOverride }} - labels: - app.kubernetes.io/name: demo - app.kubernetes.io/instance: {{ .Values.fullnameOverride }} -spec: - selector: - matchLabels: - app.kubernetes.io/name: demo - app.kubernetes.io/instance: {{ .Values.fullnameOverride }} - replicas: {{ .Values.replicaCount }} - template: - metadata: - labels: - app.kubernetes.io/name: demo - app.kubernetes.io/instance: {{ .Values.fullnameOverride }} - spec: - containers: - - name: {{ .Values.fullnameOverride }} - image: {{ .Values.image.repository }}:{{ .Values.image.tag }} - imagePullPolicy: Always - command: - - /workspace/{{ .Values.fullnameOverride }} - ports: - - protocol: TCP - containerPort: {{ .Values.port }} - resources: - limits: - cpu: {{ .Values.resources.limits.cpu }} - memory: {{ .Values.resources.limits.memory }} - requests: - cpu: {{ .Values.resources.requests.cpu }} - memory: {{ .Values.resources.requests.memory }} - imagePullSecrets: - - name: {{ .Values.imagePullSecrets }} - diff --git a/examples/multi-service-demo/charts/service2/templates/secret.yaml b/examples/multi-service-demo/charts/service2/templates/secret.yaml deleted file mode 100644 index cd16a681..00000000 --- a/examples/multi-service-demo/charts/service2/templates/secret.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - .dockercfg: {{ .Values.registrySecret }} -kind: Secret -metadata: - name: {{ .Values.imagePullSecrets }} -type: kubernetes.io/dockercfg \ No newline at end of file diff --git a/examples/multi-service-demo/charts/service2/Chart.yaml b/examples/multi-service-demo/general-chart/Chart.yaml similarity index 98% rename from examples/multi-service-demo/charts/service2/Chart.yaml rename to examples/multi-service-demo/general-chart/Chart.yaml index d7ba887a..08e787e5 100644 --- a/examples/multi-service-demo/charts/service2/Chart.yaml +++ b/examples/multi-service-demo/general-chart/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -name: service2 +name: general-chart description: A Helm chart for Kubernetes # A chart can be either an 'application' or a 'library' chart. diff --git a/examples/multi-service-demo/charts/service1/templates/_helpers.tpl b/examples/multi-service-demo/general-chart/templates/_helpers.tpl similarity index 100% rename from examples/multi-service-demo/charts/service1/templates/_helpers.tpl rename to examples/multi-service-demo/general-chart/templates/_helpers.tpl diff --git a/examples/multi-service-demo/charts/service1/templates/deployment.yaml b/examples/multi-service-demo/general-chart/templates/deployment.yaml similarity index 82% rename from examples/multi-service-demo/charts/service1/templates/deployment.yaml rename to examples/multi-service-demo/general-chart/templates/deployment.yaml index 2ecbd191..7f2e70d3 100644 --- a/examples/multi-service-demo/charts/service1/templates/deployment.yaml +++ b/examples/multi-service-demo/general-chart/templates/deployment.yaml @@ -29,10 +29,11 @@ spec: resources: limits: cpu: {{ .Values.resources.limits.cpu }} - memory: {{ .Values.resources.limits.memory }} + memory: {{ .Values.resources.limits.mem }} requests: cpu: {{ .Values.resources.requests.cpu }} - memory: {{ .Values.resources.requests.memory }} + memory: {{ .Values.resources.requests.mem }} + {{- if .Values.registryConf }} imagePullSecrets: - - name: {{ .Values.imagePullSecrets }} - + - name: {{ .Values.imagePullSecretsName }} + {{- end }} \ No newline at end of file diff --git a/examples/multi-service-demo/general-chart/templates/secret.yaml b/examples/multi-service-demo/general-chart/templates/secret.yaml new file mode 100644 index 00000000..60417294 --- /dev/null +++ b/examples/multi-service-demo/general-chart/templates/secret.yaml @@ -0,0 +1,10 @@ +{{- if .Values.registryConf -}} +apiVersion: v1 +data: + .dockercfg: {{ .Values.registryConf }} +kind: Secret +metadata: + name: {{ .Values.imagePullSecretsName }} +type: kubernetes.io/dockercfg + +{{- end -}} \ No newline at end of file diff --git a/examples/multi-service-demo/charts/service1/templates/service.yaml b/examples/multi-service-demo/general-chart/templates/service.yaml similarity index 100% rename from examples/multi-service-demo/charts/service1/templates/service.yaml rename to examples/multi-service-demo/general-chart/templates/service.yaml diff --git a/examples/multi-service-demo/general-chart/values.yaml b/examples/multi-service-demo/general-chart/values.yaml new file mode 100644 index 00000000..74d04023 --- /dev/null +++ b/examples/multi-service-demo/general-chart/values.yaml @@ -0,0 +1,19 @@ +fullnameOverride: $T-Service$ +replicaCount: 1 +port: {{.port}} + +registryConf: "" +imagePullSecretsName: "$T-Service$-registry-secret" + +image: + repository: "ccr.ccs.tencentyun.com/koderover-public/$T-Service$" + tag: "latest" + +resources: + requests: + cpu: 10m + mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi diff --git a/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml index 7eb132f7..8765d0d8 100644 --- a/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml @@ -25,5 +25,5 @@ spec: - /workspace/service1 ports: - protocol: TCP - containerPort: 20220 + containerPort: 20221 \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/service1/service.yaml b/examples/multi-service-demo/k8s-yaml/service1/service.yaml index e543d7f6..a49ac621 100644 --- a/examples/multi-service-demo/k8s-yaml/service1/service.yaml +++ b/examples/multi-service-demo/k8s-yaml/service1/service.yaml @@ -9,5 +9,5 @@ spec: type: NodePort ports: - protocol: TCP - port: 20220 - targetPort: 20220 \ No newline at end of file + port: 20221 + targetPort: 20221 \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml index b42dfb24..8e643058 100644 --- a/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml @@ -25,5 +25,5 @@ spec: - /workspace/service2 ports: - protocol: TCP - containerPort: 20220 + containerPort: 20222 \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/service2/service.yaml b/examples/multi-service-demo/k8s-yaml/service2/service.yaml index 78b5422b..8ca11162 100644 --- a/examples/multi-service-demo/k8s-yaml/service2/service.yaml +++ b/examples/multi-service-demo/k8s-yaml/service2/service.yaml @@ -9,5 +9,5 @@ spec: type: NodePort ports: - protocol: TCP - port: 20221 - targetPort: 20221 \ No newline at end of file + port: 20222 + targetPort: 20222 \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml new file mode 100644 index 00000000..83466d5b --- /dev/null +++ b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: service3 + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: service3 +spec: + selector: + matchLabels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: service3 + replicas: 1 + template: + metadata: + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: service3 + spec: + containers: + - name: service3 + image: ccr.ccs.tencentyun.com/koderover-public/service3:latest + imagePullPolicy: Always + command: + - /workspace/service3 + ports: + - protocol: TCP + containerPort: 20223 + \ No newline at end of file diff --git a/examples/multi-service-demo/charts/service2/templates/service.yaml b/examples/multi-service-demo/k8s-yaml/service3/service.yaml similarity index 32% rename from examples/multi-service-demo/charts/service2/templates/service.yaml rename to examples/multi-service-demo/k8s-yaml/service3/service.yaml index d21ce064..c36bac62 100644 --- a/examples/multi-service-demo/charts/service2/templates/service.yaml +++ b/examples/multi-service-demo/k8s-yaml/service3/service.yaml @@ -1,16 +1,13 @@ apiVersion: v1 kind: Service metadata: - name: {{ .Values.fullnameOverride }} + name: service3 labels: app.kubernetes.io/name: demo - app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + app.kubernetes.io/instance: service3 spec: - selector: - app.kubernetes.io/name: demo - app.kubernetes.io/instance: {{ .Values.fullnameOverride }} type: NodePort ports: - protocol: TCP - port: {{ .Values.port }} - targetPort: {{ .Values.port }} \ No newline at end of file + port: 20223 + targetPort: 20223 \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/template.yaml b/examples/multi-service-demo/k8s-yaml/template.yaml new file mode 100644 index 00000000..b33b5163 --- /dev/null +++ b/examples/multi-service-demo/k8s-yaml/template.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: $T-Service$ + labels: + app.kubernetes.io/name: $T-Project$ + app.kubernetes.io/instance: $T-Service$ +spec: + selector: + matchLabels: + app.kubernetes.io/name: $T-Project$ + app.kubernetes.io/instance: $T-Service$ + replicas: 2 + template: + metadata: + labels: + app.kubernetes.io/name: $T-Project$ + app.kubernetes.io/instance: $T-Service$ + spec: + containers: + - name: $T-Service$ + image: ccr.ccs.tencentyun.com/koderover-public/$T-Service$:latest + imagePullPolicy: Always + command: + - /workspace/$T-Service$ + ports: + - protocol: TCP + containerPort: {{.port}} +--- +apiVersion: v1 +kind: Service +metadata: + name: $T-Service$ + labels: + app.kubernetes.io/name: $T-Project$ + app.kubernetes.io/instance: $T-Service$ +spec: + type: NodePort + ports: + - protocol: TCP + port: {{.port}} + targetPort: {{.port}} \ No newline at end of file diff --git a/examples/multi-service-demo/service1/service1.go b/examples/multi-service-demo/src/service1/service1.go similarity index 91% rename from examples/multi-service-demo/service1/service1.go rename to examples/multi-service-demo/src/service1/service1.go index d8e0540e..2d3fdcc1 100644 --- a/examples/multi-service-demo/service1/service1.go +++ b/examples/multi-service-demo/src/service1/service1.go @@ -23,7 +23,7 @@ import ( func hello(w http.ResponseWriter, req *http.Request) { - fmt.Fprintf(w, "hello, my name is service1~~\n") + fmt.Fprintf(w, "hello, my name is service1~\n") } func headers(w http.ResponseWriter, req *http.Request) { @@ -40,5 +40,5 @@ func main() { http.HandleFunc("/", hello) http.HandleFunc("/headers", headers) - http.ListenAndServe(":20220", nil) + http.ListenAndServe(":20222", nil) } diff --git a/examples/multi-service-demo/service2/service2.go b/examples/multi-service-demo/src/service2/service2.go similarity index 100% rename from examples/multi-service-demo/service2/service2.go rename to examples/multi-service-demo/src/service2/service2.go diff --git a/examples/multi-service-demo/src/service3/service3.go b/examples/multi-service-demo/src/service3/service3.go new file mode 100644 index 00000000..f88096cf --- /dev/null +++ b/examples/multi-service-demo/src/service3/service3.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "net/http" +) + +func hello(w http.ResponseWriter, req *http.Request) { + + fmt.Fprintf(w, "hello, my name is service3~~\n") +} + +func headers(w http.ResponseWriter, req *http.Request) { + + for name, headers := range req.Header { + for _, h := range headers { + fmt.Fprintf(w, "%v: %v\n", name, h) + } + } +} + +func main() { + + http.HandleFunc("/", hello) + http.HandleFunc("/headers", headers) + + http.ListenAndServe(":20223", nil) +} diff --git a/examples/multi-service-demo/charts/service1/values.yaml b/examples/multi-service-demo/values/service1.yaml similarity index 63% rename from examples/multi-service-demo/charts/service1/values.yaml rename to examples/multi-service-demo/values/service1.yaml index 5d7aa51e..cad204c4 100644 --- a/examples/multi-service-demo/charts/service1/values.yaml +++ b/examples/multi-service-demo/values/service1.yaml @@ -1,22 +1,19 @@ - - fullnameOverride: "service1" replicaCount: 1 -port: 20220 +port: 20221 -registrySecret: "" -imagePullSecrets: "service1-secret" +imagePullSecretsName: "secret-service1" +registryConf: "" image: repository: "ccr.ccs.tencentyun.com/koderover-public/service1" tag: "latest" - resources: requests: - cpu: 1 - mem: 512Mi - - limit: cpu: 10m mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi diff --git a/examples/multi-service-demo/charts/service2/values.yaml b/examples/multi-service-demo/values/service2.yaml similarity index 67% rename from examples/multi-service-demo/charts/service2/values.yaml rename to examples/multi-service-demo/values/service2.yaml index 0a4f4cac..651c75dd 100644 --- a/examples/multi-service-demo/charts/service2/values.yaml +++ b/examples/multi-service-demo/values/service2.yaml @@ -1,22 +1,19 @@ - - fullnameOverride: "service2" replicaCount: 1 -port: 20221 +port: 20222 -registrySecret: "" -imagePullSecrets: "service2-secret" +imagePullSecretsName: "secret-service2" +registryConf: "" image: repository: "ccr.ccs.tencentyun.com/koderover-public/service2" tag: "latest" - resources: requests: - cpu: 1 - mem: 512Mi - - limits: cpu: 10m mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi diff --git a/examples/multi-service-demo/values/service3.yaml b/examples/multi-service-demo/values/service3.yaml new file mode 100644 index 00000000..13de1091 --- /dev/null +++ b/examples/multi-service-demo/values/service3.yaml @@ -0,0 +1,20 @@ +fullnameOverride: "service3" +replicaCount: 1 +port: 20223 + +imagePullSecretsName: "secret-service3" +registryConf: "" + +image: + repository: "ccr.ccs.tencentyun.com/koderover-public/service3" + tag: "latest" + +resources: + requests: + cpu: 10m + mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi + -- Gitee From 6e3a8ff967bbabb44f8069f8ae75726d457c8410 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 11:41:42 +0800 Subject: [PATCH 0617/2647] fix dockerfile panic error Signed-off-by: allenshen --- .../core/common/service/template_store.go | 27 +++++++ .../workflow/service/workflow/nsq_handlers.go | 74 +++++++++++-------- 2 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/template_store.go diff --git a/pkg/microservice/aslan/core/common/service/template_store.go b/pkg/microservice/aslan/core/common/service/template_store.go new file mode 100644 index 00000000..b97b14a9 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/template_store.go @@ -0,0 +1,27 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" + +func GetDockerfileTemplateContent(id string) (string, error) { + dockerfileTemplate, err := mongodb.NewDockerfileTemplateColl().GetById(id) + if err != nil { + return "", err + } + return dockerfileTemplate.Content, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index b71b9e88..ec167e96 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -49,6 +49,7 @@ import ( "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" ) @@ -212,6 +213,49 @@ func (h *TaskAckHandler) handle(message *nsq.Message) error { return nil } +func (h *TaskAckHandler) getDockerfileContent(build *types.Repository, ctx *task.DockerBuildCtx) string { + switch ctx.Source { + case setting.ServiceSourceTemplate: + content, err := commonservice.GetDockerfileTemplateContent(ctx.TemplateID) + if err != nil { + h.log.Errorf("Failed to get dockerfile template content, err: %v", err) + return "" + } + return content + default: + path := ctx.DockerFile + pathArray := strings.Split(path, "/") + dockerfilePath := path[len(pathArray[0])+1:] + if strings.Contains(build.Address, "gitlab") { + cli, err := gitlab.NewClient(build.Address, build.OauthToken) + if err != nil { + h.log.Errorf("Failed to get gitlab client, err: %v", err) + return "" + } + content, err := cli.GetRawFile(build.RepoOwner, build.RepoName, build.Branch, dockerfilePath) + if err != nil { + h.log.Errorf("uploadTaskData gitlab GetRawFile err:%v", err) + return "" + } + return string(content) + } else { + gitClient := git.NewClient(build.OauthToken, config.ProxyHTTPSAddr()) + fileContent, _, _, _ := gitClient.Repositories.GetContents(context.Background(), build.RepoOwner, build.RepoName, dockerfilePath, nil) + if fileContent != nil { + dockerfileContent := *fileContent.Content + dockerfileContent = dockerfileContent[:len(dockerfileContent)-2] + content, err := base64.StdEncoding.DecodeString(dockerfileContent) + if err != nil { + h.log.Errorf("uploadTaskData github GetRawFile err:%v", err) + return "" + } + return string(content) + } + } + return "" + } +} + func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifacts := make([]*commonmodels.DeliveryArtifact, 0) if pt.Type == config.WorkflowType { @@ -289,35 +333,7 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { if buildInfo.JobCtx.DockerBuildCtx != nil { for _, build := range buildInfo.JobCtx.Builds { - path := buildInfo.JobCtx.DockerBuildCtx.DockerFile - pathArray := strings.Split(path, "/") - dockerfilePath := path[len(pathArray[0])+1:] - if strings.Contains(build.Address, "gitlab") { - cli, err := gitlab.NewClient(build.Address, build.OauthToken) - if err != nil { - h.log.Errorf("Failed to get gitlab client, err: %v", err) - continue - } - content, err := cli.GetRawFile(build.RepoOwner, build.RepoName, build.Branch, dockerfilePath) - if err != nil { - h.log.Errorf("uploadTaskData gitlab GetRawFile err:%v", err) - continue - } - deliveryArtifact.DockerFile = string(content) - } else { - gitClient := git.NewClient(build.OauthToken, config.ProxyHTTPSAddr()) - fileContent, _, _, _ := gitClient.Repositories.GetContents(context.Background(), build.RepoOwner, build.RepoName, dockerfilePath, nil) - if fileContent != nil { - dockerfileContent := *fileContent.Content - dockerfileContent = dockerfileContent[:len(dockerfileContent)-2] - content, err := base64.StdEncoding.DecodeString(dockerfileContent) - if err != nil { - h.log.Errorf("uploadTaskData github GetRawFile err:%v", err) - continue - } - deliveryArtifact.DockerFile = string(content) - } - } + deliveryArtifact.DockerFile = h.getDockerfileContent(build, buildInfo.JobCtx.DockerBuildCtx) } } deliveryArtifactArray = append(deliveryArtifactArray, deliveryArtifact) -- Gitee From e824773d89f048156b2594b0becca08e1d09fe21 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 13:47:26 +0800 Subject: [PATCH 0618/2647] fix podexec permission --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 5ff750e6..45b7ef41 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -79,7 +79,7 @@ rules: - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew/?*" - method: GET - endpoint: " /api/podexec/?*/?*/?*/?*/podExec" + endpoint: "/api/podexec/?*/?*/?*/?*/podExec" - action: delete_environment alias: "删除集成环境" description: "" -- Gitee From 8f363429ded1401ed85499bf01300de78c8d085e Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 12 Nov 2021 14:46:27 +0800 Subject: [PATCH 0619/2647] bug Signed-off-by: mouuii --- .../core/email/repository/models/emailhost.go | 1 - .../email/repository/models/emailservice.go | 1 - .../systemconfig/core/email/service/email.go | 20 +++++++++++++------ .../core/jira/repository/models/jira.go | 1 - .../systemconfig/core/jira/service/jira.go | 5 +++++ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go index 4274ef57..9dff9ad7 100644 --- a/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go @@ -1,7 +1,6 @@ package models type EmailHost struct { - ID int `bson:"id" json:"id"` Name string `bson:"name" json:"name"` Port int `bson:"port" json:"port"` Username string `bson:"username" json:"username"` diff --git a/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go index f8199cb8..148d7af1 100644 --- a/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go @@ -1,7 +1,6 @@ package models type EmailService struct { - ID int `json:"id" bson:"id"` Name string `json:"name" bson:"name"` Address string `json:"address" bson:"address"` DisplayName string `json:"display_name" bson:"display_name"` diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 75d6be87..4de7edc8 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -1,6 +1,8 @@ package service import ( + "time" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" @@ -20,11 +22,14 @@ func InternalGetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { } func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { + emailHost.CreatedAt = time.Now().Unix() + emailHost.UpdatedAt = time.Now().Unix() return mongodb.NewEmailHostColl().Add(emailHost) } -func UpdateEmailHost(host *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongodb.NewEmailHostColl().Update(host) +func UpdateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { + emailHost.UpdatedAt = time.Now().Unix() + return mongodb.NewEmailHostColl().Update(emailHost) } func DeleteEmailHost(_ *zap.SugaredLogger) error { @@ -35,12 +40,15 @@ func GetEmailService(_ *zap.SugaredLogger) (*models.EmailService, error) { return mongodb.NewEmailServiceColl().GetEmailService() } -func CreateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { - return mongodb.NewEmailServiceColl().AddEmailService(service) +func CreateEmailService(emailService *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { + emailService.CreatedAt = time.Now().Unix() + emailService.UpdatedAt = time.Now().Unix() + return mongodb.NewEmailServiceColl().AddEmailService(emailService) } -func UpdateEmailService(service *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { - return mongodb.NewEmailServiceColl().UpdateEmailService(service) +func UpdateEmailService(emailService *models.EmailService, _ *zap.SugaredLogger) (*models.EmailService, error) { + emailService.UpdatedAt = time.Now().Unix() + return mongodb.NewEmailServiceColl().UpdateEmailService(emailService) } func DeleteEmailService(_ *zap.SugaredLogger) error { diff --git a/pkg/microservice/systemconfig/core/jira/repository/models/jira.go b/pkg/microservice/systemconfig/core/jira/repository/models/jira.go index 6038bf7b..b4325669 100644 --- a/pkg/microservice/systemconfig/core/jira/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/jira/repository/models/jira.go @@ -1,7 +1,6 @@ package models type Jira struct { - ID int64 `bson:"id" json:"id"` Host string `bson:"host" json:"host"` User string `bson:"user" json:"user"` AccessToken string `bson:"access_token" json:"access_token"` diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 5428837e..0368497d 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -1,6 +1,8 @@ package service import ( + "time" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" @@ -12,10 +14,13 @@ func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { } func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { + jira.CreatedAt = time.Now().Unix() + jira.UpdatedAt = time.Now().Unix() return mongodb.NewJiraColl().AddJira(jira) } func UpdateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { + jira.UpdatedAt = time.Now().Unix() return mongodb.NewJiraColl().UpdateJira(jira) } -- Gitee From 449755e3c11f3b644af3f820b66187537be6b2fa Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 12 Nov 2021 14:59:41 +0800 Subject: [PATCH 0620/2647] bug Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/service/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 54a165c0..8a324121 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -8,7 +8,7 @@ import ( ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { - if codehost.Type == "codehub" { + if codehost.Type == "codehub" || codehost.Type == "gerrit" { codehost.IsReady = "2" } list, err := mongodb.NewCodehostColl().CodeHostList() -- Gitee From bb391e431e754e314967e842c819971a7c37ba03 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 12 Nov 2021 15:15:23 +0800 Subject: [PATCH 0621/2647] bug Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 8a324121..b27ff5b4 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -1,6 +1,9 @@ package service import ( + "encoding/base64" + "fmt" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" @@ -8,9 +11,14 @@ import ( ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { - if codehost.Type == "codehub" || codehost.Type == "gerrit" { + if codehost.Type == "codehub" { + codehost.IsReady = "2" + } + if codehost.Type == "gerrit" { codehost.IsReady = "2" + codehost.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", codehost.Username, codehost.Password))) } + list, err := mongodb.NewCodehostColl().CodeHostList() if err != nil { return nil, err -- Gitee From 113a2bbaeabe40628f58c50a8f8ebe7f1502ebb5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 15:19:46 +0800 Subject: [PATCH 0622/2647] optimize code Signed-off-by: allenshen --- .../core/service/taskplugin/deploy.go | 61 +++++++------------ pkg/shared/kube/wrapper/deployment.go | 12 ++++ pkg/shared/kube/wrapper/statefulset.go | 12 ++++ 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 68b22aa8..ba9cb3bc 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -28,7 +28,6 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -40,6 +39,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/httpclient" @@ -132,52 +132,34 @@ type EnvArgs struct { ProductName string `json:"product_name"` } -type SelectorBuilder struct { - ProductName string - GroupName string - ServiceName string - ConfigBackup string - EnvName string +type ResourceComponentSet interface { + GetName() string + GetAnnotations() map[string]string + Containers() []*resource.ContainerImage + GetKind() string } -type ResourceComponentSet struct { - Name string - Kind string - Containers []v1.Container - Annotations map[string]string -} - -func RcsListFromDeployments(source []*appsv1.Deployment) []*ResourceComponentSet { - rcsList := make([]*ResourceComponentSet, 0) +func RcsListFromDeployments(source []*appsv1.Deployment) []ResourceComponentSet { + rcsList := make([]ResourceComponentSet, 0, len(source)) for _, deploy := range source { - rcsList = append(rcsList, &ResourceComponentSet{ - Name: deploy.Name, - Kind: setting.Deployment, - Containers: deploy.Spec.Template.Spec.Containers, - Annotations: deploy.Annotations, - }) + rcsList = append(rcsList, wrapper.Deployment(deploy)) } return rcsList } -func RcsListFromStatefulSets(source []*appsv1.StatefulSet) []*ResourceComponentSet { - rcsList := make([]*ResourceComponentSet, 0) +func RcsListFromStatefulSets(source []*appsv1.StatefulSet) []ResourceComponentSet { + rcsList := make([]ResourceComponentSet, 0, len(source)) for _, sfs := range source { - rcsList = append(rcsList, &ResourceComponentSet{ - Name: sfs.Name, - Kind: setting.StatefulSet, - Containers: sfs.Spec.Template.Spec.Containers, - Annotations: sfs.Annotations, - }) + rcsList = append(rcsList, wrapper.StatefulSet(sfs)) } return rcsList } // find affected resources(deployment+statefulSet) for helm install or upgrade // resource type: deployment statefulSet -func (p *DeployTaskPlugin) findHelmAffectedResources(namespace, serviceName string, resList []*ResourceComponentSet) { +func (p *DeployTaskPlugin) findHelmAffectedResources(namespace, serviceName string, resList []ResourceComponentSet) { for _, res := range resList { - annotation := res.Annotations + annotation := res.GetAnnotations() if len(annotation) == 0 { continue } @@ -188,15 +170,15 @@ func (p *DeployTaskPlugin) findHelmAffectedResources(namespace, serviceName stri continue } } - for _, container := range res.Containers { + for _, container := range res.Containers() { resolvedImageUrl := resolveImageUrl(container.Image) if resolvedImageUrl[setting.PathSearchComponentImage] == p.Task.ContainerName { - p.Log.Infof("%s find match container.name:%s container.image:%s", res.Kind, container.Name, container.Image) + p.Log.Infof("%s find match container.name:%s container.image:%s", res.GetKind(), container.Name, container.Image) p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: res.Kind, + Kind: res.GetKind(), Container: container.Name, Origin: container.Image, - Name: res.Name, + Name: res.GetName(), }) } } @@ -382,19 +364,20 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * helmClient helmclient.Client ) + rcsList := make([]ResourceComponentSet, 0) deployments, err := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) if err != nil { p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) } else { - p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, RcsListFromDeployments(deployments)) + rcsList = append(rcsList, RcsListFromDeployments(deployments)...) } - statefulSets, _ := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) if err != nil { p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) } else { - p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, RcsListFromStatefulSets(statefulSets)) + rcsList = append(rcsList, RcsListFromStatefulSets(statefulSets)...) } + p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, rcsList) p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, p.Task.ServiceName, p.Task.ContainerName, p.Task.Namespace) diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 5cd417f7..24e62473 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -80,3 +80,15 @@ func (w *deployment) ImageInfos() (images []string) { } return } + +func (w *deployment) GetKind() string { + return w.Kind +} + +func (w *deployment) Containers() []*resource.ContainerImage { + containers := make([]*resource.ContainerImage, 0) + for _, c := range w.Spec.Template.Spec.Containers { + containers = append(containers, &resource.ContainerImage{Name: c.Name, Image: c.Image}) + } + return containers +} diff --git a/pkg/shared/kube/wrapper/statefulset.go b/pkg/shared/kube/wrapper/statefulset.go index 0ae9c333..60e75bbf 100644 --- a/pkg/shared/kube/wrapper/statefulset.go +++ b/pkg/shared/kube/wrapper/statefulset.go @@ -80,3 +80,15 @@ func (w *statefulSet) ImageInfos() (images []string) { } return } + +func (w *statefulSet) GetKind() string { + return w.Kind +} + +func (w *statefulSet) Containers() []*resource.ContainerImage { + containers := make([]*resource.ContainerImage, 0) + for _, c := range w.Spec.Template.Spec.Containers { + containers = append(containers, &resource.ContainerImage{Name: c.Name, Image: c.Image}) + } + return containers +} -- Gitee From f28343853d016992482ad58e893f11e0c066819c Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 15:22:14 +0800 Subject: [PATCH 0623/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 4 ++-- pkg/shared/kube/wrapper/deployment.go | 2 +- pkg/shared/kube/wrapper/statefulset.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index ba9cb3bc..1748ab51 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -135,7 +135,7 @@ type EnvArgs struct { type ResourceComponentSet interface { GetName() string GetAnnotations() map[string]string - Containers() []*resource.ContainerImage + GetContainers() []*resource.ContainerImage GetKind() string } @@ -170,7 +170,7 @@ func (p *DeployTaskPlugin) findHelmAffectedResources(namespace, serviceName stri continue } } - for _, container := range res.Containers() { + for _, container := range res.GetContainers() { resolvedImageUrl := resolveImageUrl(container.Image) if resolvedImageUrl[setting.PathSearchComponentImage] == p.Task.ContainerName { p.Log.Infof("%s find match container.name:%s container.image:%s", res.GetKind(), container.Name, container.Image) diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 24e62473..7bae2eb4 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -85,7 +85,7 @@ func (w *deployment) GetKind() string { return w.Kind } -func (w *deployment) Containers() []*resource.ContainerImage { +func (w *deployment) GetContainers() []*resource.ContainerImage { containers := make([]*resource.ContainerImage, 0) for _, c := range w.Spec.Template.Spec.Containers { containers = append(containers, &resource.ContainerImage{Name: c.Name, Image: c.Image}) diff --git a/pkg/shared/kube/wrapper/statefulset.go b/pkg/shared/kube/wrapper/statefulset.go index 60e75bbf..4a42be38 100644 --- a/pkg/shared/kube/wrapper/statefulset.go +++ b/pkg/shared/kube/wrapper/statefulset.go @@ -85,7 +85,7 @@ func (w *statefulSet) GetKind() string { return w.Kind } -func (w *statefulSet) Containers() []*resource.ContainerImage { +func (w *statefulSet) GetContainers() []*resource.ContainerImage { containers := make([]*resource.ContainerImage, 0) for _, c := range w.Spec.Template.Spec.Containers { containers = append(containers, &resource.ContainerImage{Name: c.Name, Image: c.Image}) -- Gitee From f39515dd6f7f89ffea01101216c2f801f086a252 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 15:23:42 +0800 Subject: [PATCH 0624/2647] optimize code Signed-off-by: allenshen --- pkg/shared/kube/wrapper/deployment.go | 2 +- pkg/shared/kube/wrapper/statefulset.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 7bae2eb4..6d0c5e79 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -86,7 +86,7 @@ func (w *deployment) GetKind() string { } func (w *deployment) GetContainers() []*resource.ContainerImage { - containers := make([]*resource.ContainerImage, 0) + containers := make([]*resource.ContainerImage, 0, len(w.Spec.Template.Spec.Containers)) for _, c := range w.Spec.Template.Spec.Containers { containers = append(containers, &resource.ContainerImage{Name: c.Name, Image: c.Image}) } diff --git a/pkg/shared/kube/wrapper/statefulset.go b/pkg/shared/kube/wrapper/statefulset.go index 4a42be38..7f4bb459 100644 --- a/pkg/shared/kube/wrapper/statefulset.go +++ b/pkg/shared/kube/wrapper/statefulset.go @@ -86,7 +86,7 @@ func (w *statefulSet) GetKind() string { } func (w *statefulSet) GetContainers() []*resource.ContainerImage { - containers := make([]*resource.ContainerImage, 0) + containers := make([]*resource.ContainerImage, 0, len(w.Spec.Template.Spec.Containers)) for _, c := range w.Spec.Template.Spec.Containers { containers = append(containers, &resource.ContainerImage{Name: c.Name, Image: c.Image}) } -- Gitee From 05d46f4ebb755f959dc021f41b401b3fc4b1f7d6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 12 Nov 2021 15:24:55 +0800 Subject: [PATCH 0625/2647] bug Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index b27ff5b4..864ddda1 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -3,6 +3,7 @@ package service import ( "encoding/base64" "fmt" + "time" "go.uber.org/zap" @@ -18,6 +19,8 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co codehost.IsReady = "2" codehost.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", codehost.Username, codehost.Password))) } + codehost.CreatedAt = time.Now().Unix() + codehost.UpdatedAt = time.Now().Unix() list, err := mongodb.NewCodehostColl().CodeHostList() if err != nil { -- Gitee From c1e5bba053e164bf542995cd6fdd95d4e62a3b79 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 15:56:04 +0800 Subject: [PATCH 0626/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/nsq_handlers.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index ec167e96..a33ee974 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -222,9 +222,13 @@ func (h *TaskAckHandler) getDockerfileContent(build *types.Repository, ctx *task return "" } return content - default: + default: // TODO need rewrite path := ctx.DockerFile pathArray := strings.Split(path, "/") + if len(pathArray[0])+1 >= len(path) { + h.log.Errorf("Failed to get dockerfile content, build context: %+v", *ctx) + return "" + } dockerfilePath := path[len(pathArray[0])+1:] if strings.Contains(build.Address, "gitlab") { cli, err := gitlab.NewClient(build.Address, build.OauthToken) -- Gitee From 9f705550c28f8b9d26028b92ebccb00003681670 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 15:56:38 +0800 Subject: [PATCH 0627/2647] fix permissions Signed-off-by: lou --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 4 ++++ pkg/microservice/aslan/core/project/handler/policy.yaml | 6 ++++++ .../aslan/core/workflow/testing/handler/policy.yaml | 6 ++++++ .../policy/core/service/bundle/exemption_urls.go | 6 +++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 45b7ef41..cef0b3b1 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -42,6 +42,8 @@ rules: endpoint: "/api/aslan/environment/environments/?*/envRecycle" - method: PUT endpoint: "/api/aslan/environment/environments/?*/renderchart" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/renderset" - method: PUT endpoint: "/api/aslan/environment/environments/?*/multiHelmEnv" - method: PUT @@ -66,6 +68,8 @@ rules: endpoint: "/api/aslan/environment/export/service" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" + - method: GET + endpoint: "/api/aslan/environment/kube/events" - method: GET endpoint: "/api/aslan/environment/export/service" - method: GET diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index dbaf18ad..810be08e 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -50,6 +50,10 @@ rules: endpoint: "/api/aslan/service/pm/?*" - method: POST endpoint: "/api/aslan/service/services" + - method: GET + endpoint: "/api/aslan/service/loader/preload/?*/?*" + - method: POST + endpoint: "/api/aslan/service/loader/load/?*/?*" - action: delete_service alias: "删除服务" description: "" @@ -82,6 +86,8 @@ rules: endpoint: "/api/aslan/build/build" - method: POST endpoint: "/api/aslan/build/targets" + - method: POST + endpoint: "/api/aslan/build/build/targets" - action: delete_build alias: "删除构建配置" description: "" diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml index 320fcded..f4f13084 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -32,3 +32,9 @@ rules: rules: - method: POST endpoint: "/api/aslan/testing/test" + - action: run_test + alias: "执行测试" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/testing/testtask" diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index cb74c3c8..12938788 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -58,7 +58,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{"", "signin", "setup", "loading", "static/**", "v1/**", "mobile/**", "productpipelines/**"}, + Endpoints: []string{"", "signin", "setup", "static/**", "v1/**", "mobile/**", "productpipelines/**", "favicon.ico"}, }, { Methods: []string{"GET"}, @@ -240,6 +240,10 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"PUT"}, Endpoints: []string{"api/aslan/project/products"}, }, + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/v1/picket/projects/?*"}, + }, { Methods: []string{"DELETE"}, Endpoints: []string{"api/aslan/project/products/?*"}, -- Gitee From 7498a9408855c5baa5f485df953d03265cdc10ca Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 16:45:57 +0800 Subject: [PATCH 0628/2647] fix project admin Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 48a8cdcd..81ce157b 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -86,7 +86,7 @@ user_is_project_admin { allowed_roles[role] role.name == "admin" - role.namespace == project_name + #role.namespace == project_name } # public urls are visible for all users -- Gitee From f9e86de58d238ca32436ee612ac3b3cc94828080 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 16:53:39 +0800 Subject: [PATCH 0629/2647] add copy logic Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/router.go | 2 +- pkg/microservice/aslan/core/service/service/helm.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 0cba666d..86b06e08 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -122,8 +122,8 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/workloads", ListWorkloadsInEnv) environments.GET("/:productName/services/:serviceName", GetService) + environments.PUT("/:productName/services/:serviceName/revision", gin2.UpdateOperationLogStatus, UpdateServiceRevision) environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.UpdateOperationLogStatus, UpdateService) - environments.PUT("/:productName/services/:serviceName/:updateRevision", gin2.UpdateOperationLogStatus, UpdateServiceRevision) environments.POST("/:productName/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) environments.POST("/:productName/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) environments.POST("/:productName/services/:serviceName/scale/:number", gin2.UpdateOperationLogStatus, ScaleService) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 7aacac8b..dc2fe48f 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -480,6 +480,13 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } + err = copyChartRevision(projectName, serviceName, rev) + if err != nil { + log.Errorf("Failed to copy file %s, err: %s", args.Name, err) + finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", args.Name) + return + } + // save files to disk and upload them to s3 if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, fsTree); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) -- Gitee From b5dcfd43e3d5ad90abb31a333916bddd79234726 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 12 Nov 2021 17:17:05 +0800 Subject: [PATCH 0630/2647] change productName to projectName Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/renderset.go | 2 +- pkg/microservice/aslan/core/service/handler/helm.go | 2 +- pkg/microservice/aslan/core/workflow/handler/build.go | 2 +- pkg/microservice/aslan/core/workflow/handler/workflow.go | 2 +- pkg/microservice/picket/core/public/handler/public.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 85fa5854..50aff572 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -56,7 +56,7 @@ func GetProductDefaultValues(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GetDefaultValues(c.Query("productName"), c.Query("envName"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetDefaultValues(c.Query("projectName"), c.Query("envName"), ctx.Logger) } func GetYamlContent(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 8f19493f..5b7974c7 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -80,7 +80,7 @@ func CreateOrUpdateBulkHelmServices(c *gin.Context) { } args.CreatedBy = ctx.UserName - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(c.Query("productName"), args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(c.Query("projectName"), args, ctx.Logger) } func UpdateHelmService(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/handler/build.go b/pkg/microservice/aslan/core/workflow/handler/build.go index aeec1312..37166728 100644 --- a/pkg/microservice/aslan/core/workflow/handler/build.go +++ b/pkg/microservice/aslan/core/workflow/handler/build.go @@ -30,7 +30,7 @@ func BuildModuleToSubTasks(c *gin.Context) { args := &models.BuildModuleArgs{ BuildName: c.Param("name"), - ProductName: c.Query("productName"), + ProductName: c.Query("projectName"), } resp, err := workflow.BuildModuleToSubTasks(args, ctx.Logger) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index e399babf..9f69a055 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -118,7 +118,7 @@ func ListWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), c.Query("productName"), ctx.UserID, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), c.Query("projectName"), ctx.UserID, ctx.Logger) } func ListTestWorkflows(c *gin.Context) { diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 800c0703..5a868e42 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -100,7 +100,7 @@ func ListWorkflowTask(c *gin.Context) { func ListDelivery(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Query("productName") + productName := c.Query("projectName") workflowName := c.Query("workflowName") taskIDStr := c.Query("taskId") perPageStr := c.Query("perPage") -- Gitee From 73fe99a562e3dd389428aacff7964c20a115d6e8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 12 Nov 2021 18:33:01 +0800 Subject: [PATCH 0631/2647] migrate Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 26 +++++++- .../upgradeassistant/internal/mongo/mongo.go | 59 +++++++++++++++++++ .../systemconfig/config/consts.go | 7 --- .../codehost/repository/mongodb/codehost.go | 9 --- 4 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/mongo/mongo.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 71f721a0..a02c57e5 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -17,6 +17,7 @@ limitations under the License. package migrate import ( + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/mongo" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -36,7 +37,13 @@ func init() { func V160ToV170() error { log.Info("Migrating data from 1.6.0 to 1.7.0") - err := refreshWebHookSecret() + err := changeCodehostType() + if err != nil { + log.Errorf("Failed to change codehost type, err: %s", err) + return err + } + + err = refreshWebHookSecret() if err != nil { log.Errorf("Failed to refresh webhook secret, err: %s", err) return err @@ -114,3 +121,20 @@ func getCodeHostByAddressAndOwner(address, owner string, all []*systemconfig.Cod return nil } + +func changeCodehostType() error { + // get all codehosts + codeHosts, err := systemconfig.New().ListCodeHosts() + if err != nil { + log.Errorf("fail to list codehosts, err: %s", err) + return err + } + // change type to readable string + for _, v := range codeHosts { + if err := mongo.NewCodehostColl().ChangeType(v.ID, v.Type); err != nil { + log.Errorf("fail to change id:%d type:%s , err: %s", v.ID, v.Type, err) + return err + } + } + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/mongo/mongo.go b/pkg/cli/upgradeassistant/internal/mongo/mongo.go new file mode 100644 index 00000000..f9cde38c --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/mongo/mongo.go @@ -0,0 +1,59 @@ +package mongo + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +func (c *CodehostColl) ChangeType(ID int, sourceType string) error { + query := bson.M{"id": ID} + + if sourceType == "1" { + sourceType = "gitlab" + } else if sourceType == "2" { + sourceType = "github" + } else if sourceType == "3" { + sourceType = "gerrit" + } else if sourceType == "4" { + sourceType = "codehub" + } else if sourceType == "5" { + sourceType = "ilyshin" + } + + change := bson.M{"$set": bson.M{ + "type": sourceType, + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository update fail") + return err + } + log.Infof("success to change id:%d to type:%s", ID, sourceType) + return nil +} + +type CodehostColl struct { + *mongo.Collection + + coll string +} + +type ListArgs struct { + Owner string + Address string + Source string +} + +func NewCodehostColl() *CodehostColl { + name := models.CodeHost{}.TableName() + coll := &CodehostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go index 8acf347c..27f698e5 100644 --- a/pkg/microservice/systemconfig/config/consts.go +++ b/pkg/microservice/systemconfig/config/consts.go @@ -4,10 +4,3 @@ const ( ENVMysqlDexDB = "MYSQL_DEX_DB" FeatureFlag = "feature-gates" ) - -var CodeHostMap = map[string]string{ - "1": "gitlab", - "2": "github", - "3": "gerrit", - "4": "codehub", -} diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 2c9ea41a..65bfa37a 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -71,9 +71,6 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { if err != nil { return nil, nil } - if v, ok := config.CodeHostMap[codehost.Type]; ok { - codehost.Type = v - } return codehost, nil } @@ -98,12 +95,6 @@ func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { if err != nil { return nil, err } - // NOTE: to adapt old data - for i, v := range codeHosts { - if v, ok := config.CodeHostMap[v.Type]; ok { - codeHosts[i].Type = v - } - } return codeHosts, nil } -- Gitee From 6613d88c225cd85ec43f4a949e06f3ca92608c25 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 12 Nov 2021 18:37:53 +0800 Subject: [PATCH 0632/2647] rollback Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 22 +++++++++++++++ .../upgradeassistant/internal/mongo/mongo.go | 27 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index a02c57e5..3856fd4f 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -54,6 +54,11 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") + err := RollbackchangeCodehostType() + if err != nil { + log.Errorf("Failed to rollback codehost type, err: %s", err) + return err + } return nil } @@ -138,3 +143,20 @@ func changeCodehostType() error { } return nil } + +func RollbackchangeCodehostType() error { + // get all codehosts + codeHosts, err := systemconfig.New().ListCodeHosts() + if err != nil { + log.Errorf("fail to list codehosts, err: %s", err) + return err + } + // rollback change type to readable string + for _, v := range codeHosts { + if err := mongo.NewCodehostColl().ChangeType(v.ID, v.Type); err != nil { + log.Errorf("fail to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) + return err + } + } + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/mongo/mongo.go b/pkg/cli/upgradeassistant/internal/mongo/mongo.go index f9cde38c..90180fd0 100644 --- a/pkg/cli/upgradeassistant/internal/mongo/mongo.go +++ b/pkg/cli/upgradeassistant/internal/mongo/mongo.go @@ -39,6 +39,33 @@ func (c *CodehostColl) ChangeType(ID int, sourceType string) error { return nil } +func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { + query := bson.M{"id": ID} + + if sourceType == "gitlab" { + sourceType = "1" + } else if sourceType == "github" { + sourceType = "2" + } else if sourceType == "gerrit" { + sourceType = "3" + } else if sourceType == "codehub" { + sourceType = "4" + } else if sourceType == "ilyshin" { + sourceType = "5" + } + + change := bson.M{"$set": bson.M{ + "type": sourceType, + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Error("repository update fail") + return err + } + log.Infof("success to rollback id:%d to type:%s", ID, sourceType) + return nil +} + type CodehostColl struct { *mongo.Collection -- Gitee From 988d42cd7550f9e4ca0a7d1b9dcb3fe38d0dca77 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 5 Nov 2021 19:37:59 +0800 Subject: [PATCH 0633/2647] return service name to client after chart imported Signed-off-by: allenshen --- .../aslan/core/service/handler/helm.go | 2 +- .../aslan/core/service/service/helm.go | 67 ++++++++++--------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 5b7974c7..0573b098 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -66,7 +66,7 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy = ctx.UserName - ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("projectName"), args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("projectName"), args, ctx.Logger) } func CreateOrUpdateBulkHelmServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 2185e6de..662fe9cb 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -28,7 +28,6 @@ import ( "time" "github.com/27149chen/afero" - "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "github.com/pkg/errors" "go.uber.org/zap" @@ -242,26 +241,26 @@ func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (* }, nil } -func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { +func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) case LoadFromChartTemplate: return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) default: - return fmt.Errorf("invalid source") + return nil, fmt.Errorf("invalid source") } } -func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) error { +func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { - return fmt.Errorf("invalid argument") + return nil, fmt.Errorf("invalid argument") } templateChartInfo, err := prepareChartTemplateData(templateArgs.TemplateName, logger) if err != nil { - return err + return nil, err } var values [][]byte @@ -269,7 +268,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe //render variables renderedYaml, err := renderVariablesToYaml(string(templateChartInfo.DefaultValuesYAML), projectName, args.Name, templateArgs.Variables) if err != nil { - return err + return nil, err } values = append(values, []byte(renderedYaml)) } @@ -287,29 +286,29 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe // remove old files if err = os.RemoveAll(to); err != nil { logger.Errorf("Failed to remove dir %s, err: %s", to, err) - return err + return nil, err } if err = copy.Copy(from, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", from, to, err) - return err + return nil, err } merged, err := yamlutil.Merge(values) if err != nil { logger.Errorf("Failed to merge values, err: %s", err) - return err + return nil, err } if err = os.WriteFile(filepath.Join(to, setting.ValuesYaml), merged, 0644); err != nil { logger.Errorf("Failed to write values, err: %s", err) - return err + return nil, err } fsTree := os.DirFS(config.LocalServicePath(projectName, args.Name)) ServiceS3Base := config.ObjectStorageServicePath(projectName, args.Name) if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, args.Name, ServiceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", args.Name, projectName, err) - return err + return nil, err } svc, err := createOrUpdateHelmService( @@ -331,7 +330,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe ) if err != nil { logger.Errorf("Failed to create service %s in project %s, error: %s", args.Name, projectName, err) - return err + return nil, err } compareHelmVariable([]*templatemodels.RenderChart{ @@ -341,7 +340,9 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe }, }, projectName, args.CreatedBy, logger) - return nil + return &BulkHelmServiceCreationResponse{ + SuccessServices: []string{args.Name}, + }, nil } func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *systemconfig.CodeHost, error) { @@ -356,57 +357,60 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *system return ch.Type, ch, nil } -func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) error { +func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { var err error var repoLink string repoArgs, ok := args.CreateFrom.(*CreateFromRepo) if !ok { publicArgs, ok := args.CreateFrom.(*CreateFromPublicRepo) if !ok { - return fmt.Errorf("invalid argument") + return nil, fmt.Errorf("invalid argument") } repoArgs, err = PublicRepoToPrivateRepoArgs(publicArgs) if err != nil { log.Errorf("Failed to parse repo args %+v, err: %s", publicArgs, err) - return err + return nil, err } repoLink = publicArgs.RepoLink } + response := &BulkHelmServiceCreationResponse{} source, codehostInfo, err := getCodehostType(repoArgs, repoLink) if err != nil { log.Errorf("Failed to get source form repo data %+v, err: %s", *repoArgs, err.Error()) - return err + return nil, err } helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(repoArgs.Paths)) - var errs *multierror.Error var wg wait.Group var mux sync.RWMutex for _, p := range repoArgs.Paths { filePath := strings.TrimLeft(p, "/") wg.Start(func() { - var finalErr error - + var ( + serviceName string + chartVersion string + valuesYAML []byte + finalErr error + ) defer func() { + mux.Lock() if finalErr != nil { - mux.Lock() - errs = multierror.Append(errs, finalErr) - mux.Unlock() + response.FailedServices = append(response.FailedServices, &FailedService{ + Path: filePath, + Error: finalErr.Error(), + }) + } else { + response.SuccessServices = append(response.SuccessServices, serviceName) } + mux.Unlock() }() log.Infof("Loading chart under path %s", filePath) - var ( - serviceName string - chartVersion string - valuesYAML []byte - ) - fsTree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, func(chartTree afero.Fs) (string, error) { @@ -473,8 +477,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC wg.Wait() compareHelmVariable(helmRenderCharts, projectName, args.CreatedBy, log) - - return errs.ErrorOrNil() + return response, nil } func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { -- Gitee From 230e1c3fa35ccd65778d5322e9934156fc94c5a2 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 21:06:38 +0800 Subject: [PATCH 0634/2647] fix permissions Signed-off-by: lou --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- pkg/microservice/aslan/core/project/handler/policy.yaml | 4 ++-- .../aslan/core/workflow/testing/handler/policy.yaml | 2 ++ .../policy/core/service/bundle/exemption_urls.go | 6 +----- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index cef0b3b1..0cb2d279 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -59,7 +59,7 @@ rules: - method: DELETE endpoint: "/api/aslan/environment/kube/pods/?*" - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" + endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" - method: PUT endpoint: "/api/aslan/environment/configmaps" - method: POST diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index 810be08e..c4e18781 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -26,8 +26,6 @@ rules: alias: "编辑服务" description: "" rules: - - method: POST - endpoint: "/api/aslan/service/helm/services" - method: POST endpoint: "/api/aslan/service/helm/?*" - method: PUT @@ -54,6 +52,8 @@ rules: endpoint: "/api/aslan/service/loader/preload/?*/?*" - method: POST endpoint: "/api/aslan/service/loader/load/?*/?*" + - method: POST + endpoint: "/api/aslan/service/helm/services" - action: delete_service alias: "删除服务" description: "" diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml index f4f13084..56b2c62a 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -14,6 +14,8 @@ rules: endpoint: "/api/aslan/testing/testdetail" - method: GET endpoint: "/api/aslan/workflow/workflow/testName/?*" + - method: GET + endpoint: "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*" - action: edit_test alias: "编辑测试" description: "" diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 12938788..60cf5a6b 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -76,10 +76,6 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/v1/picket/projects"}, }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/aslan/project/products/?*"}, - }, { Methods: []string{"POST"}, Endpoints: []string{"api/aslan/cluster/clusters"}, @@ -241,7 +237,7 @@ var projectAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/project/products"}, }, { - Methods: []string{"PUT"}, + Methods: []string{"PUT", "DELETE"}, Endpoints: []string{"api/v1/picket/projects/?*"}, }, { -- Gitee From d1a6cc3e53b8199f93de0beead48c9abc4f5490d Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 21:10:11 +0800 Subject: [PATCH 0635/2647] typo Signed-off-by: lou --- pkg/microservice/picket/client/aslan/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 0fd20556..d76f26d6 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -9,7 +9,7 @@ import ( ) func (c *Client) DeleteProject(header http.Header, qs url.Values, productName string) ([]byte, error) { - url := fmt.Sprintf("/project/prodects/%s", productName) + url := fmt.Sprintf("/project/projects/%s", productName) res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { -- Gitee From a016ac256866c49f715b62c52caf38506ca8d171 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 21:14:20 +0800 Subject: [PATCH 0636/2647] typo Signed-off-by: lou --- pkg/microservice/picket/client/aslan/project.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index d76f26d6..7aaf053e 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -9,7 +9,7 @@ import ( ) func (c *Client) DeleteProject(header http.Header, qs url.Values, productName string) ([]byte, error) { - url := fmt.Sprintf("/project/projects/%s", productName) + url := fmt.Sprintf("/project/products/%s", productName) res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) if err != nil { -- Gitee From 5d195d6009df82f007adf47ad06580fda9c7b8b7 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 22:00:46 +0800 Subject: [PATCH 0637/2647] add new filter for projects Signed-off-by: lou --- .../aslan/core/project/handler/project.go | 14 +++++-- .../aslan/core/project/service/project.go | 40 +++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/project.go b/pkg/microservice/aslan/core/project/handler/project.go index f7836262..c2747c35 100644 --- a/pkg/microservice/aslan/core/project/handler/project.go +++ b/pkg/microservice/aslan/core/project/handler/project.go @@ -25,9 +25,10 @@ import ( ) type projectListArgs struct { - IgnoreNoEnvs bool `json:"ignoreNoEnvs" form:"ignoreNoEnvs"` - Verbosity string `json:"verbosity" form:"verbosity"` - Names []string `json:"names" form:"names"` + IgnoreNoEnvs bool `json:"ignoreNoEnvs" form:"ignoreNoEnvs"` + IgnoreNoVersions bool `json:"ignoreNoVersions" form:"ignoreNoVersions"` + Verbosity string `json:"verbosity" form:"verbosity"` + Names []string `json:"names" form:"names"` } func ListProjects(c *gin.Context) { @@ -47,7 +48,12 @@ func ListProjects(c *gin.Context) { } ctx.Resp, ctx.Err = projectservice.ListProjects( - &projectservice.ProjectListOptions{IgnoreNoEnvs: args.IgnoreNoEnvs, Verbosity: projectservice.QueryVerbosity(args.Verbosity), Names: args.Names}, + &projectservice.ProjectListOptions{ + IgnoreNoEnvs: args.IgnoreNoEnvs, + IgnoreNoVersions: args.IgnoreNoVersions, + Verbosity: projectservice.QueryVerbosity(args.Verbosity), + Names: args.Names, + }, ctx.Logger, ) } diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 314d9ef6..05358d00 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -33,9 +33,10 @@ const ( ) type ProjectListOptions struct { - IgnoreNoEnvs bool - Verbosity QueryVerbosity - Names []string + IgnoreNoEnvs bool + IgnoreNoVersions bool + Verbosity QueryVerbosity + Names []string } type ProjectDetailedRepresentation struct { @@ -139,7 +140,40 @@ func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) return res, nil } +func listMinimalProjectInfoForDelivery(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { + var res []*ProjectMinimalRepresentation + names, err := templaterepo.NewProductColl().ListNames(opts.Names) + if err != nil { + logger.Errorf("Failed to list project names, err: %s", err) + return nil, err + } + + nameSet := sets.NewString() + for _, name := range names { + nameSet.Insert(name) + } + namesWithDelivery, err := mongodb.NewDeliveryVersionColl().FindProducts() + if err != nil { + logger.Errorf("Failed to list projects by delivery, err: %s", err) + return nil, err + } + + for _, name := range namesWithDelivery { + // namesWithDelivery may contain projects which are already deleted. + if !nameSet.Has(name) { + continue + } + res = append(res, &ProjectMinimalRepresentation{Name: name}) + } + + return res, nil +} + func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { + if opts.IgnoreNoVersions { + return listMinimalProjectInfoForDelivery(opts, logger) + } + var res []*ProjectMinimalRepresentation names, err := templaterepo.NewProductColl().ListNames(opts.Names) -- Gitee From 87a1b86a395da9de93c73200914933e4081f9c30 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 12 Nov 2021 22:03:56 +0800 Subject: [PATCH 0638/2647] remove unused api Signed-off-by: lou --- .../aslan/core/delivery/handler/product.go | 7 ------- pkg/microservice/aslan/core/delivery/handler/router.go | 1 - .../aslan/core/delivery/service/product.go | 10 ---------- 3 files changed, 18 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/product.go b/pkg/microservice/aslan/core/delivery/handler/product.go index ad252633..43bf3bfd 100644 --- a/pkg/microservice/aslan/core/delivery/handler/product.go +++ b/pkg/microservice/aslan/core/delivery/handler/product.go @@ -24,13 +24,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListDeliveryProduct(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = deliveryservice.FindDeliveryProduct(ctx.Logger) -} - func GetProductByDeliveryInfo(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 362ce68c..889b7e7a 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -37,7 +37,6 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryProduct := router.Group("products") { - deliveryProduct.GET("", ListDeliveryProduct) deliveryProduct.GET("/:releaseId", GetProductByDeliveryInfo) } diff --git a/pkg/microservice/aslan/core/delivery/service/product.go b/pkg/microservice/aslan/core/delivery/service/product.go index d9c4e561..ece5c4ad 100644 --- a/pkg/microservice/aslan/core/delivery/service/product.go +++ b/pkg/microservice/aslan/core/delivery/service/product.go @@ -24,16 +24,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func FindDeliveryProduct(log *zap.SugaredLogger) ([]string, error) { - productNames, err := commonrepo.NewDeliveryVersionColl().FindProducts() - if err != nil { - log.Errorf("find FindDeliveryProduct error: %v", err) - return []string{}, e.ErrFindDeliveryProducts - } - - return productNames, err -} - func GetProductByDeliveryInfo(username, releaseID string, log *zap.SugaredLogger) (*commonmodels.Product, error) { version := new(commonrepo.DeliveryVersionArgs) version.ID = releaseID -- Gitee From 77f3c3352f6f9075ea9094dbfdc19f16e5c4684f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 10:56:58 +0800 Subject: [PATCH 0639/2647] refactor show Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/project-admin.yaml | 2 +- pkg/cli/initconfig/cmd/read-only.yaml | 2 +- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 2 +- pkg/microservice/policy/core/service/role.go | 4 ++++ pkg/setting/consts.go | 4 ++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml index b64adb5c..68938e3f 100644 --- a/pkg/cli/initconfig/cmd/project-admin.yaml +++ b/pkg/cli/initconfig/cmd/project-admin.yaml @@ -1,4 +1,4 @@ -name: admin +name: project-admin rules: - verbs: - "*" diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index c781fb94..d6bd5d64 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -1,4 +1,4 @@ -name: readonly +name: read-only rules: - verbs: - get_workflow diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 81ce157b..45fd16e0 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -85,7 +85,7 @@ user_is_project_admin { some role allowed_roles[role] - role.name == "admin" + role.name == "project-admin" #role.namespace == project_name } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 1735b7f2..d281d687 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -23,6 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" ) type Role struct { @@ -92,6 +93,9 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { return nil, err } for _, v := range projectRoles { + if v.Name == string(setting.Contributor) { + continue + } roles = append(roles, &Role{ Name: v.Name, }) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index e81dafee..c8c77099 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -536,8 +536,8 @@ type RoleType string const ( Contributor RoleType = "contributor" - ReadOnly RoleType = "readonly" - Admin RoleType = "admin" + ReadOnly RoleType = "read-only" + Admin RoleType = "project-admin" RoleBindingNameFmt string = "user:%s,role:%s,project:%s" ) -- Gitee From d4c28b687b75162a7b35c10a0d7bb8b98a9528ea Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 11:24:41 +0800 Subject: [PATCH 0640/2647] improve code Signed-off-by: lou --- .../aslan/core/project/service/project.go | 31 ++++++------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 05358d00..42bdef1f 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -140,18 +140,8 @@ func listBriefProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) return res, nil } -func listMinimalProjectInfoForDelivery(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { +func listMinimalProjectInfoForDelivery(_ *ProjectListOptions, nameSet sets.String, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { var res []*ProjectMinimalRepresentation - names, err := templaterepo.NewProductColl().ListNames(opts.Names) - if err != nil { - logger.Errorf("Failed to list project names, err: %s", err) - return nil, err - } - - nameSet := sets.NewString() - for _, name := range names { - nameSet.Insert(name) - } namesWithDelivery, err := mongodb.NewDeliveryVersionColl().FindProducts() if err != nil { logger.Errorf("Failed to list projects by delivery, err: %s", err) @@ -170,18 +160,22 @@ func listMinimalProjectInfoForDelivery(opts *ProjectListOptions, logger *zap.Sug } func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger) ([]*ProjectMinimalRepresentation, error) { - if opts.IgnoreNoVersions { - return listMinimalProjectInfoForDelivery(opts, logger) - } - var res []*ProjectMinimalRepresentation - names, err := templaterepo.NewProductColl().ListNames(opts.Names) if err != nil { logger.Errorf("Failed to list project names, err: %s", err) return nil, err } + nameSet := sets.NewString() + for _, name := range names { + nameSet.Insert(name) + } + + if opts.IgnoreNoVersions { + return listMinimalProjectInfoForDelivery(opts, nameSet, logger) + } + if !opts.IgnoreNoEnvs { for _, name := range names { res = append(res, &ProjectMinimalRepresentation{Name: name}) @@ -190,11 +184,6 @@ func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger return res, nil } - nameSet := sets.NewString() - for _, name := range names { - nameSet.Insert(name) - } - nameWithEnvSet, _, err := getProjectsWithEnvs(opts) if err != nil { logger.Errorf("Failed to list projects, err: %s", err) -- Gitee From e2a7d336d380b6ea13b93a4678c0dd31b1c7edb5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 15 Nov 2021 11:34:46 +0800 Subject: [PATCH 0641/2647] remove set service revision logic Signed-off-by: allenshen --- .../aslan/core/environment/handler/router.go | 1 - .../aslan/core/environment/handler/service.go | 14 ---- .../core/environment/service/environment.go | 48 +++---------- .../aslan/core/environment/service/service.go | 34 --------- .../core/service/taskplugin/deploy.go | 69 ++++--------------- .../core/service/types/environment.go | 10 +++ 6 files changed, 34 insertions(+), 142 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 86b06e08..bc13c84c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -122,7 +122,6 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/workloads", ListWorkloadsInEnv) environments.GET("/:productName/services/:serviceName", GetService) - environments.PUT("/:productName/services/:serviceName/revision", gin2.UpdateOperationLogStatus, UpdateServiceRevision) environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.UpdateOperationLogStatus, UpdateService) environments.POST("/:productName/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) environments.POST("/:productName/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index 11076eae..3405c7c0 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -76,20 +76,6 @@ func UpdateService(c *gin.Context) { ctx.Err = service.UpdateService(args, ctx.Logger) } -// update revison of a service in product -func UpdateServiceRevision(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新Revision", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) - - revision, err := strconv.ParseInt(c.DefaultQuery("revision", "0"), 10, 64) - if err != nil || revision == 0 { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") - return - } - ctx.Err = service.UpdateServiceRevision(c.Query("envName"), c.Param("productName"), c.Param("serviceName"), revision, ctx.Logger) -} - func RestartNewService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7e2969a3..ad3eba38 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2107,10 +2107,16 @@ func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderCha } func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { - base := config.LocalServicePath(serviceObj.ProductName, serviceObj.ServiceName) - if err := commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { - log.Errorf("Failed to load manifest for service %s in project %s, err: %s", serviceObj.ServiceName, serviceObj.ProductName, err) - return err + base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) + if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + serviceObj.Revision, serviceObj.ServiceName) + // use the latest version when it fails to download the specific version + base = config.LocalServicePath(serviceObj.ProductName, serviceObj.ServiceName) + if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { + log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) + return fmt.Errorf("failed to load chart info for service %s", serviceObj.ServiceName) + } } chartFullPath := filepath.Join(base, serviceObj.ServiceName) @@ -2628,45 +2634,13 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } - productServiceMap := productResp.GetServiceMap() - handler := func(service *commonmodels.Service, log *zap.SugaredLogger) error { - targetRevision := service.Revision - //preload chart info with specific revision - base := config.LocalServicePathWithRevision(service.ProductName, service.ServiceName, service.Revision) - if err := commonservice.PreloadServiceManifestsByRevision(base, service); err != nil { - log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - service.Revision, service.ServiceName) - // use the latest version when it fails to download the specific version - base = config.LocalServicePath(service.ProductName, service.ServiceName) - if err = commonservice.PreLoadServiceManifests(base, service); err != nil { - log.Errorf("failed to load chart info for service %v", service.ServiceName) - return fmt.Errorf("failed to load chart info for service %s", service.ServiceName) - } - - findOpt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - ProductName: productName, - ExcludeStatus: setting.ProductStatusDeleting, - } - latestService, err := commonrepo.NewServiceColl().Find(findOpt) - if err != nil { - log.Errorf("failed to get service:%v with latest version", service.ServiceName) - return fmt.Errorf("failed to get service:%v with latest version", service.ServiceName) - } - targetRevision = latestService.Revision - } - renderChart := renderChartMap[service.ServiceName] err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, helmClient) if err != nil { return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) } - if productService, ok := productServiceMap[service.ServiceName]; ok { - productService.Revision = targetRevision - } - return err + return nil } errList := new(multierror.Error) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 1846e9e0..630d3764 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -402,40 +402,6 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return nil } -func UpdateServiceRevision(envName, productName, serviceName string, targetRevision int64, log *zap.SugaredLogger) (err error) { - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - productInfo, err := commonrepo.NewProductColl().Find(opt) - if err != nil { - return e.ErrUpdateEnv.AddDesc(err.Error()) - } - - groupIndex := -1 - var targetGroup []*commonmodels.ProductService - for gIndex, serviceGroup := range productInfo.Services { - for _, service := range serviceGroup { - if service.ServiceName == serviceName { - groupIndex = gIndex - targetGroup = serviceGroup - service.Revision = targetRevision - break - } - } - if groupIndex >= 0 { - break - } - } - - if groupIndex < 0 { - return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("servce: %v not found", serviceName)) - } - - err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, targetGroup) - if err != nil { - return e.ErrUpdateEnv.AddDesc(err.Error()) - } - return nil -} - func queryPodsStatus(namespace, productName, serviceName string, kubeClient client.Client, log *zap.SugaredLogger) (string, string, []string) { ls := labels.Set{} if productName != "" { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index a1290996..87de2acc 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -402,29 +402,18 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } - curRevisionInProduct := int64(0) - for _, serviceGroup := range productInfo.Services { - for _, service := range serviceGroup { - if service.ServiceName == p.Task.ServiceName { - curRevisionInProduct = service.Revision - break - } - } - if curRevisionInProduct > 0 { - break - } - } - + serviceRevisionInProduct := int64(0) var targetContainer *types.Container - for _, serviceGroup := range productInfo.Services { - for _, service := range serviceGroup { - if service.ServiceName == p.Task.ServiceName { - for _, container := range service.Containers { - if container.Name == p.Task.ContainerName { - targetContainer = container - } + for _, service := range productInfo.GetServiceMap() { + if service.ServiceName == p.Task.ServiceName { + serviceRevisionInProduct = service.Revision + for _, container := range service.Containers { + if container.Name == p.Task.ContainerName { + targetContainer = container + break } } + break } } @@ -446,11 +435,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } //task执行时候 product.service.revision 可能已经更新,需要使用当前环境中的service.revision - targetRevision := curRevisionInProduct - path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, - pipelineTask.StorageURI, targetRevision) + pipelineTask.StorageURI, serviceRevisionInProduct) if errDownload != nil { + p.Log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + serviceRevisionInProduct, p.Task.ServiceName) path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, 0) if errDownload != nil { @@ -461,21 +450,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * _ = os.Remove(path) return } - - //获取当前service的最新revision - latestService, errGetService := p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, - pipelineTask.ProductName, 0) - if errGetService != nil { - err = errors.WithMessagef( - errDownload, - "failed to get latest service %s/%s", - p.Task.Namespace, p.Task.ServiceName) - _ = os.Remove(path) - } - - //当前实际的revision - targetRevision = latestService.Revision } + chartPath, err = fs.RelativeToCurrentPath(path) if err != nil { err = errors.WithMessagef( @@ -497,13 +473,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * // prepare image replace info validMatchData := getValidMatchData(targetContainer.ImagePath) - // update product.service.revision when use the latest revision - if targetRevision > 0 && targetRevision != p.Task.ServiceRevision { - err = p.updateServiceRevision(ctx, p.Task.ServiceName, pipelineTask.ProductName, p.Task.EnvName, targetRevision) - if err != nil { - p.Log.Errorf("update service version fail [env:%v][productName:%v][serviceName:%v], err %v", p.Task.EnvName, p.Task.ProductName, p.Task.ServiceName, err) - } - } replaceValuesMap, err = assignImageData(p.Task.Image, validMatchData) if err != nil { err = errors.WithMessagef( @@ -634,18 +603,6 @@ func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, pr return s, nil } -func (p *DeployTaskPlugin) updateServiceRevision(ctx context.Context, name, productName, envName string, - revision int64) error { - url := fmt.Sprintf("/api/environment/environments/%s/services/%s/updateRevision", productName, name) - - _, err := p.httpClient.Put(url, httpclient.SetQueryParams(map[string]string{ - "productName": productName, - "envName": envName, - "revision": fmt.Sprintf("%d", revision), - })) - return err -} - // download chart info of specific version, use the latest version if fails func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { logger := p.Log diff --git a/pkg/microservice/warpdrive/core/service/types/environment.go b/pkg/microservice/warpdrive/core/service/types/environment.go index 5de5d860..be83e18c 100644 --- a/pkg/microservice/warpdrive/core/service/types/environment.go +++ b/pkg/microservice/warpdrive/core/service/types/environment.go @@ -110,3 +110,13 @@ func (rc *RenderChart) GetOverrideYaml() string { } return rc.OverrideYaml.YamlContent } + +func (p *Product) GetServiceMap() map[string]*Service { + ret := make(map[string]*Service) + for _, group := range p.Services { + for _, svc := range group { + ret[svc.ServiceName] = svc + } + } + return ret +} -- Gitee From 0ec3b5710b0c9484ea645afac1e7ae0dea9588e0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 13:52:53 +0800 Subject: [PATCH 0642/2647] refactor show Signed-off-by: mouuii --- pkg/microservice/policy/core/service/role.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index d281d687..d21a6821 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -93,6 +93,7 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { return nil, err } for _, v := range projectRoles { + // front end doesn't need to see contributor role if v.Name == string(setting.Contributor) { continue } -- Gitee From 1ff0b42f4fd515165e15f34b654b27468ac8d4b3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 13:53:39 +0800 Subject: [PATCH 0643/2647] refactor show Signed-off-by: mouuii --- pkg/microservice/policy/core/service/role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index d21a6821..c8179e62 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -93,7 +93,7 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { return nil, err } for _, v := range projectRoles { - // front end doesn't need to see contributor role + // frontend doesn't need to see contributor role if v.Name == string(setting.Contributor) { continue } -- Gitee From daa45337b549bcd3c3b7222d0ee479d20598c4e9 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 17:06:45 +0800 Subject: [PATCH 0644/2647] update after review Signed-off-by: lou --- pkg/microservice/aslan/core/project/service/project.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 42bdef1f..4c9e1fb9 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -167,11 +167,7 @@ func listMinimalProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogger return nil, err } - nameSet := sets.NewString() - for _, name := range names { - nameSet.Insert(name) - } - + nameSet := sets.NewString(names...) if opts.IgnoreNoVersions { return listMinimalProjectInfoForDelivery(opts, nameSet, logger) } -- Gitee From 00ce9eae09b34d4e2274fa02573fee9ac71a612b Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 17:08:58 +0800 Subject: [PATCH 0645/2647] fix users/search permission Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 60cf5a6b..9c56543b 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -197,7 +197,7 @@ var systemAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/proxy/config"}, }, { - Methods: []string{"*"}, + Methods: []string{"GET", "PUT", "PATCH", "DELETE"}, Endpoints: []string{"api/v1/users/?*"}, }, { -- Gitee From ec8b10e6d372300e2b294d4da135abc4498e3e74 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 17:14:40 +0800 Subject: [PATCH 0646/2647] refactor show Signed-off-by: mouuii --- pkg/cli/upgradeassistant/internal/mongo/mongo.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/mongo/mongo.go b/pkg/cli/upgradeassistant/internal/mongo/mongo.go index 90180fd0..015b24e0 100644 --- a/pkg/cli/upgradeassistant/internal/mongo/mongo.go +++ b/pkg/cli/upgradeassistant/internal/mongo/mongo.go @@ -15,6 +15,7 @@ import ( func (c *CodehostColl) ChangeType(ID int, sourceType string) error { query := bson.M{"id": ID} + preType := sourceType if sourceType == "1" { sourceType = "gitlab" } else if sourceType == "2" { @@ -23,8 +24,6 @@ func (c *CodehostColl) ChangeType(ID int, sourceType string) error { sourceType = "gerrit" } else if sourceType == "4" { sourceType = "codehub" - } else if sourceType == "5" { - sourceType = "ilyshin" } change := bson.M{"$set": bson.M{ @@ -35,7 +34,7 @@ func (c *CodehostColl) ChangeType(ID int, sourceType string) error { log.Error("repository update fail") return err } - log.Infof("success to change id:%d to type:%s", ID, sourceType) + log.Infof("success to change id:%d type:%s to type:%s", ID, preType, sourceType) return nil } -- Gitee From ea571df39c4ddfa52e42fa87ff8531d4b1597eb3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 17:17:08 +0800 Subject: [PATCH 0647/2647] refactor show Signed-off-by: mouuii --- pkg/cli/upgradeassistant/internal/mongo/mongo.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/mongo/mongo.go b/pkg/cli/upgradeassistant/internal/mongo/mongo.go index 015b24e0..0ebfce20 100644 --- a/pkg/cli/upgradeassistant/internal/mongo/mongo.go +++ b/pkg/cli/upgradeassistant/internal/mongo/mongo.go @@ -49,8 +49,6 @@ func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { sourceType = "3" } else if sourceType == "codehub" { sourceType = "4" - } else if sourceType == "ilyshin" { - sourceType = "5" } change := bson.M{"$set": bson.M{ -- Gitee From 56e5f2747289d054d2d6ac9354571b43065752d6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 15 Nov 2021 17:45:59 +0800 Subject: [PATCH 0648/2647] fix auto create workflow name bug Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 3bcaff9d..98387c82 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -63,7 +63,7 @@ func (args *workflowCreateArgs) addWorkflowArg(envName string, buildStageEnabled wName = fmt.Sprintf("%s-%s-workflow", args.productName, "ops") } // The hosting env workflow name is not bound to the environment - if envName == "" { + if !artifactStageEnabled && envName == "" { wName = fmt.Sprintf("%s-workflow", args.productName) } args.argsMap[wName] = &workflowCreateArg{ -- Gitee From 72c44183d595ceb6f405f0e316f44af2ab1484ba Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 18:49:25 +0800 Subject: [PATCH 0649/2647] webhook rollback Signed-off-by: lou --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 33 +++++++++++-- .../repository/models/organization.go | 10 ++++ .../repository/mongodb/organization.go | 48 +++++++++++++++++++ .../core/common/service/github/webhook.go | 4 +- .../core/common/service/gitlab/webhook.go | 4 +- .../core/workflow/service/webhook/github.go | 2 +- 6 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/organization.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 71f721a0..629ad1d2 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -17,9 +17,11 @@ limitations under the License. package migrate import ( + internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" "github.com/koderover/zadig/pkg/setting" @@ -36,7 +38,7 @@ func init() { func V160ToV170() error { log.Info("Migrating data from 1.6.0 to 1.7.0") - err := refreshWebHookSecret() + err := refreshWebHookSecret(gitservice.GetHookSecret()) if err != nil { log.Errorf("Failed to refresh webhook secret, err: %s", err) return err @@ -47,14 +49,27 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") + + token := getWebHookTokenFromOrganization() + if token == "" { + return nil + } + + log.Info("Start to rollback all webhook secrets") + err := refreshWebHookSecret(token) + if err != nil { + log.Errorf("Failed to refresh webhook secret, err: %s", err) + return err + } + return nil } type hookRefresher interface { - RefreshWebHookSecret(owner, repo, hookID string) error + RefreshWebHookSecret(secret, owner, repo, hookID string) error } -func refreshWebHookSecret() error { +func refreshWebHookSecret(secret string) error { hooks, err := mongodb.NewWebHookColl().List() if err != nil { log.Errorf("Failed to list webhooks, err: %s", err) @@ -89,7 +104,7 @@ func refreshWebHookSecret() error { continue } - if err = cl.RefreshWebHookSecret(hook.Owner, hook.Repo, hook.HookID); err != nil { + if err = cl.RefreshWebHookSecret(secret, hook.Owner, hook.Repo, hook.HookID); err != nil { log.Warnf("Failed to refresh webhook secret for hook %d in %s/%s, err: %s", hook.HookID, hook.Owner, hook.Repo, err) continue } @@ -114,3 +129,13 @@ func getCodeHostByAddressAndOwner(address, owner string, all []*systemconfig.Cod return nil } + +func getWebHookTokenFromOrganization() string { + org, found, err := internalmongodb.NewOrganizationColl().Get(1) + if err != nil || !found { + log.Warnf("Failed to get organization, err: %s", err) + return "" + } + + return org.Token +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/organization.go b/pkg/cli/upgradeassistant/internal/repository/models/organization.go new file mode 100644 index 00000000..33b2ccd9 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/organization.go @@ -0,0 +1,10 @@ +package models + +type Organization struct { + ID int `bson:"id" json:"id"` + Token string `bson:"token,omitempty" json:"token,omitempty"` +} + +func (Organization) TableName() string { + return "organization" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go new file mode 100644 index 00000000..c5740f19 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go @@ -0,0 +1,48 @@ +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type OrganizationColl struct { + *mongo.Collection + + coll string +} + +func NewOrganizationColl() *OrganizationColl { + name := models.Organization{}.TableName() + return &OrganizationColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *OrganizationColl) GetCollectionName() string { + return c.coll +} + +func (c *OrganizationColl) EnsureIndex(_ context.Context) error { + return nil +} + +func (c *OrganizationColl) Get(id int) (*models.Organization, bool, error) { + org := &models.Organization{} + query := bson.M{"id": id} + + err := c.FindOne(context.TODO(), query).Decode(org) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, false, nil + } + return nil, false, err + } + return org, true, nil +} diff --git a/pkg/microservice/aslan/core/common/service/github/webhook.go b/pkg/microservice/aslan/core/common/service/github/webhook.go index 4544ea73..645bf05d 100644 --- a/pkg/microservice/aslan/core/common/service/github/webhook.go +++ b/pkg/microservice/aslan/core/common/service/github/webhook.go @@ -46,14 +46,14 @@ func (c *Client) DeleteWebHook(owner, repo, hookID string) error { return c.DeleteHook(context.TODO(), owner, repo, hookIDInt) } -func (c *Client) RefreshWebHookSecret(owner, repo, hookID string) error { +func (c *Client) RefreshWebHookSecret(secret, owner, repo, hookID string) error { hookIDInt, err := strconv.ParseInt(hookID, 10, 64) if err != nil { return err } _, err = c.UpdateHook(context.TODO(), owner, repo, hookIDInt, &git.Hook{ URL: config.WebHookURL(), - Secret: gitservice.GetHookSecret(), + Secret: secret, }) return err diff --git a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go index 7251fea9..f1a1e772 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/webhook.go @@ -45,14 +45,14 @@ func (c *Client) DeleteWebHook(owner, repo, hookID string) error { return c.DeleteProjectHook(owner, repo, hookIDInt) } -func (c *Client) RefreshWebHookSecret(owner, repo, hookID string) error { +func (c *Client) RefreshWebHookSecret(secret, owner, repo, hookID string) error { hookIDInt, err := strconv.Atoi(hookID) if err != nil { return err } _, err = c.UpdateProjectHook(owner, repo, hookIDInt, &git.Hook{ URL: config.WebHookURL(), - Secret: gitservice.GetHookSecret(), + Secret: secret, }) return err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 07af078c..19553661 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -173,7 +173,7 @@ func prEventToPipelineTasks(event *github.PullRequestEvent, requestID string, lo commitMessage = *event.PullRequest.Title ) - address, err := util.GetAddress(event.Repo.GetURL()) + address, err := util.GetAddress(event.Repo.GetHTMLURL()) if err != nil { log.Errorf("GetAddress failed, url: %s, err: %s", event.Repo.GetURL(), err) return nil, err -- Gitee From 98e848e9520c281c49943f786cf852d8cb40461b Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 19:06:32 +0800 Subject: [PATCH 0650/2647] fix all users role Signed-off-by: lou --- .../picket/core/filter/service/rolebinding.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index b8740c9e..9ec4293d 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -10,6 +10,8 @@ import ( "github.com/koderover/zadig/pkg/shared/client/user" ) +const allUsers = "*" + type roleBinding struct { *policy.RoleBinding Username string `json:"username"` @@ -29,7 +31,9 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg var uids []string uidToRoleBinding := make(map[string][]*roleBinding) for _, rb := range rbs { - uids = append(uids, rb.UID) + if rb.UID != allUsers { + uids = append(uids, rb.UID) + } uidToRoleBinding[rb.UID] = append(uidToRoleBinding[rb.UID], &roleBinding{RoleBinding: rb}) } @@ -55,5 +59,8 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg } } + // add all 'allUsers' roles + res = append(res, uidToRoleBinding[allUsers]...) + return res, nil } -- Gitee From 4614a720729e1d8342ff42950a14cc4ecfbf8d05 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 15 Nov 2021 19:09:10 +0800 Subject: [PATCH 0651/2647] update third party params Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/login/third_party.go | 3 +++ pkg/microservice/user/core/service/user/user.go | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index b9cd4a89..c4b555a7 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -84,6 +84,9 @@ func verifyAndDecode(ctx context.Context, code string) (*login.Claims, error) { if err != nil { return nil, err } + if len(claims.Name) == 0 { + claims.Name = claims.PreferredUsername + } return &claims, nil } diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 5bebb468..f43ac7ec 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -113,8 +113,9 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { searchRequest := ldapv3.NewSearchRequest( config.GroupSearch.BaseDN, ldapv3.ScopeWholeSubtree, ldapv3.NeverDerefAliases, 0, 0, false, - config.GroupSearch.Filter, // The filter to apply - []string{config.UserSearch.NameAttr}, // A list attributes to retrieve + config.GroupSearch.Filter, // The filter to apply + []string{config.GroupSearch.NameAttr, config.UserSearch.NameAttr, config.UserSearch.PreferredUsernameAttrAttr, + config.UserSearch.EmailAttr}, // A list attributes to retrieve nil, ) @@ -124,7 +125,7 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { return err } for _, entry := range sr.Entries { - account := config.UserSearch.Username + account := config.UserSearch.PreferredUsernameAttrAttr name := account if len(config.UserSearch.NameAttr) != 0 { name = config.UserSearch.NameAttr -- Gitee From bee8e7030d0c1fce328a6b5d9fe3702ce37ac653 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 19:13:09 +0800 Subject: [PATCH 0652/2647] update after review Signed-off-by: lou --- .../internal/repository/mongodb/organization.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go index c5740f19..41452b31 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go @@ -25,14 +25,6 @@ func NewOrganizationColl() *OrganizationColl { } } -func (c *OrganizationColl) GetCollectionName() string { - return c.coll -} - -func (c *OrganizationColl) EnsureIndex(_ context.Context) error { - return nil -} - func (c *OrganizationColl) Get(id int) (*models.Organization, bool, error) { org := &models.Organization{} query := bson.M{"id": id} -- Gitee From caec6baa3060c8f7c3bbc706a5584b47f1b8fa24 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 19:35:45 +0800 Subject: [PATCH 0653/2647] refactor show Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 6 ++- .../upgradeassistant/internal/mongo/mongo.go | 38 ++++++++++++++++++- .../codehost/repository/mongodb/codehost.go | 2 +- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 3856fd4f..f97d2991 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -127,9 +127,10 @@ func getCodeHostByAddressAndOwner(address, owner string, all []*systemconfig.Cod return nil } +// change type "1,2,3,4" to "github,gitlab..." func changeCodehostType() error { // get all codehosts - codeHosts, err := systemconfig.New().ListCodeHosts() + codeHosts, err := mongo.NewCodehostColl().ListCodeHosts() if err != nil { log.Errorf("fail to list codehosts, err: %s", err) return err @@ -144,9 +145,10 @@ func changeCodehostType() error { return nil } +// rollback type "github,gitlab..." to "1,2..." func RollbackchangeCodehostType() error { // get all codehosts - codeHosts, err := systemconfig.New().ListCodeHosts() + codeHosts, err := mongo.NewCodehostColl().ListCodeHosts() if err != nil { log.Errorf("fail to list codehosts, err: %s", err) return err diff --git a/pkg/cli/upgradeassistant/internal/mongo/mongo.go b/pkg/cli/upgradeassistant/internal/mongo/mongo.go index 0ebfce20..bbc77e87 100644 --- a/pkg/cli/upgradeassistant/internal/mongo/mongo.go +++ b/pkg/cli/upgradeassistant/internal/mongo/mongo.go @@ -24,6 +24,8 @@ func (c *CodehostColl) ChangeType(ID int, sourceType string) error { sourceType = "gerrit" } else if sourceType == "4" { sourceType = "codehub" + } else { + return nil } change := bson.M{"$set": bson.M{ @@ -31,7 +33,7 @@ func (c *CodehostColl) ChangeType(ID int, sourceType string) error { }} _, err := c.Collection.UpdateOne(context.TODO(), query, change) if err != nil { - log.Error("repository update fail") + log.Errorf("repository update fail,err:%s", err) return err } log.Infof("success to change id:%d type:%s to type:%s", ID, preType, sourceType) @@ -56,13 +58,45 @@ func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { }} _, err := c.Collection.UpdateOne(context.TODO(), query, change) if err != nil { - log.Error("repository update fail") + log.Errorf("repository update fail,err:%s", err) return err } log.Infof("success to rollback id:%d to type:%s", ID, sourceType) return nil } +type CodeHost struct { + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` + Address string `bson:"address" json:"address"` + IsReady string `bson:"is_ready" json:"ready"` + AccessToken string `bson:"access_token" json:"accessToken"` + RefreshToken string `bson:"refresh_token" json:"refreshToken"` + Namespace string `bson:"namespace" json:"namespace"` + ApplicationId string `bson:"application_id" json:"applicationId"` + Region string `bson:"region" json:"region,omitempty"` + Username string `bson:"username" json:"username,omitempty"` + Password string `bson:"password" json:"password,omitempty"` + ClientSecret string `bson:"client_secret" json:"clientSecret"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` +} + +func (c *CodehostColl) ListCodeHosts() ([]*CodeHost, error) { + codeHosts := make([]*CodeHost, 0) + + cursor, err := c.Collection.Find(context.TODO(), bson.M{}) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &codeHosts) + if err != nil { + return nil, err + } + return codeHosts, nil +} + type CodehostColl struct { *mongo.Collection diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 65bfa37a..5b7170bb 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -119,7 +119,7 @@ func (c *CodehostColl) DeleteCodeHostByID(ID int) error { }} _, err := c.Collection.UpdateOne(context.TODO(), query, change) if err != nil { - log.Error("repository update fail") + log.Errorf("repository update fail,err:%s", err) return err } return nil -- Gitee From 45816ed9960869cf762d3846705d74102e0e9ee4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 19:37:06 +0800 Subject: [PATCH 0654/2647] refactor show Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 10 +++++----- pkg/cli/upgradeassistant/internal/mongo/mongo.go | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index f97d2991..39e79538 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -138,8 +138,8 @@ func changeCodehostType() error { // change type to readable string for _, v := range codeHosts { if err := mongo.NewCodehostColl().ChangeType(v.ID, v.Type); err != nil { - log.Errorf("fail to change id:%d type:%s , err: %s", v.ID, v.Type, err) - return err + log.Warnf("fail to change id:%d type:%s , err: %s", v.ID, v.Type, err) + continue } } return nil @@ -155,9 +155,9 @@ func RollbackchangeCodehostType() error { } // rollback change type to readable string for _, v := range codeHosts { - if err := mongo.NewCodehostColl().ChangeType(v.ID, v.Type); err != nil { - log.Errorf("fail to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) - return err + if err := mongo.NewCodehostColl().RollbackChangeType(v.ID, v.Type); err != nil { + log.Warnf("fail to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) + continue } } return nil diff --git a/pkg/cli/upgradeassistant/internal/mongo/mongo.go b/pkg/cli/upgradeassistant/internal/mongo/mongo.go index bbc77e87..5082a723 100644 --- a/pkg/cli/upgradeassistant/internal/mongo/mongo.go +++ b/pkg/cli/upgradeassistant/internal/mongo/mongo.go @@ -51,6 +51,8 @@ func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { sourceType = "3" } else if sourceType == "codehub" { sourceType = "4" + } else { + return nil } change := bson.M{"$set": bson.M{ -- Gitee From 2185ef3a51e57fc84225efae99d888aca881955c Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 19:51:46 +0800 Subject: [PATCH 0655/2647] add service policy yaml url Signed-off-by: mouuii --- .../aslan/core/project/handler/policy.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index c4e18781..6aeb3361 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -40,6 +40,8 @@ rules: endpoint: "/api/aslan/project/products/?*" - method: PUT endpoint: "/api/aslan/project/products/?*/searching-rules" + - method: POST + endpoint: "/api/aslan/service/template/reload" - action: create_service alias: "新建服务" description: "" @@ -54,6 +56,16 @@ rules: endpoint: "/api/aslan/service/loader/load/?*/?*" - method: POST endpoint: "/api/aslan/service/helm/services" + - method: GET + endpoint: "/api/aslan/template/yaml" + - method: GET + endpoint: "/api/aslan/template/yaml/?*" + - method: POST + endpoint: "/api/aslan/service/template/load" + - method: GET + endpoint: "/api/aslan/template/charts/service-demo/variables" + - method: POST + endpoint: "/api/aslan/service/helm/services/bulk" - action: delete_service alias: "删除服务" description: "" -- Gitee From b4c82e1560ff91fcc91b621e211bd444474cced7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:02:07 +0800 Subject: [PATCH 0656/2647] add service policy yaml url Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 18 ++++++++---------- .../aslan/core/project/handler/policy.yaml | 2 -- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 0cb2d279..d4a8ac99 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -20,6 +20,14 @@ rules: endpoint: "/api/aslan/environment/kube/workloads" - method: GET endpoint: "/api/aslan/service/workloads" + - method: GET + endpoint: "/api/aslan/environment/export/service" + - method: GET + endpoint: "/api/aslan/environment/kube/pods/?*/events" + - method: GET + endpoint: "/api/aslan/environment/kube/events" + - method: GET + endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" - action: create_environment alias: "新建集成环境" description: "" @@ -64,16 +72,6 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: POST endpoint: "/api/aslan/environment/configmaps" - - method: GET - endpoint: "/api/aslan/environment/export/service" - - method: GET - endpoint: "/api/aslan/environment/kube/pods/?*/events" - - method: GET - endpoint: "/api/aslan/environment/kube/events" - - method: GET - endpoint: "/api/aslan/environment/export/service" - - method: GET - endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" - method: PUT endpoint: "/api/aslan/environment/environments/?*/services/?*/?*" - method: POST diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index 6aeb3361..01292e60 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -30,8 +30,6 @@ rules: endpoint: "/api/aslan/service/helm/?*" - method: PUT endpoint: "/api/aslan/service/services" - - method: PUT - endpoint: " /api/aslan/service/services/yaml/validator" - method: PUT endpoint: "/api/aslan/service/pm/?*" - method: PUT -- Gitee From c1823d3acb93801f51f883771ad0115e75242861 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:02:50 +0800 Subject: [PATCH 0657/2647] add service policy yaml url Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/handler/policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index 01292e60..2ceecbcc 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -61,7 +61,7 @@ rules: - method: POST endpoint: "/api/aslan/service/template/load" - method: GET - endpoint: "/api/aslan/template/charts/service-demo/variables" + endpoint: "/api/aslan/template/charts/?*/variables" - method: POST endpoint: "/api/aslan/service/helm/services/bulk" - action: delete_service -- Gitee From 2b5dc0db24c1d634501d1ef08de83ffed90a16d4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:11:06 +0800 Subject: [PATCH 0658/2647] config Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index d4a8ac99..b09e4011 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -22,6 +22,8 @@ rules: endpoint: "/api/aslan/service/workloads" - method: GET endpoint: "/api/aslan/environment/export/service" + - method: GET + endpoint: "/api/aslan/environment/configmaps" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" - method: GET -- Gitee From 33e43f6d8a63bfbaf2d46d0c5f34d63b74976168 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:25:10 +0800 Subject: [PATCH 0659/2647] jira Signed-off-by: mouuii --- pkg/shared/client/systemconfig/jira.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/client/systemconfig/jira.go b/pkg/shared/client/systemconfig/jira.go index 1b975b59..fb2698b8 100644 --- a/pkg/shared/client/systemconfig/jira.go +++ b/pkg/shared/client/systemconfig/jira.go @@ -24,7 +24,7 @@ type JiraInfo struct { ID int64 `json:"id"` Host string `json:"host"` User string `json:"user"` - AccessToken string `json:"accessToken"` + AccessToken string `json:"access_token"` OrganizationID int `json:"organizationId"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` -- Gitee From 47ca13d937658702711de61963b9670d4a4f6e64 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:37:09 +0800 Subject: [PATCH 0660/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 4 ++-- .../internal/repository/mongodb/codehost.go | 17 ++--------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 9671b613..3dba7ae6 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -55,7 +55,7 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") - err := RollbackchangeCodehostType() + err := rollbackChangeCodehostType() if err != nil { log.Errorf("Failed to rollback codehost type, err: %s", err) return err @@ -159,7 +159,7 @@ func changeCodehostType() error { } // rollback type "github,gitlab..." to "1,2..." -func RollbackchangeCodehostType() error { +func rollbackChangeCodehostType() error { // get all codehosts codeHosts, err := internalmongodb.NewCodehostColl().ListCodeHosts() if err != nil { diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go index 213e647d..0e7571a0 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go @@ -68,21 +68,8 @@ func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { } type CodeHost struct { - ID int `bson:"id" json:"id"` - Type string `bson:"type" json:"type"` - Address string `bson:"address" json:"address"` - IsReady string `bson:"is_ready" json:"ready"` - AccessToken string `bson:"access_token" json:"accessToken"` - RefreshToken string `bson:"refresh_token" json:"refreshToken"` - Namespace string `bson:"namespace" json:"namespace"` - ApplicationId string `bson:"application_id" json:"applicationId"` - Region string `bson:"region" json:"region,omitempty"` - Username string `bson:"username" json:"username,omitempty"` - Password string `bson:"password" json:"password,omitempty"` - ClientSecret string `bson:"client_secret" json:"clientSecret"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` } func (c *CodehostColl) ListCodeHosts() ([]*CodeHost, error) { -- Gitee From 3baae3dc7bc8c4609c8c7cceda6e2199e66a8cee Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:41:46 +0800 Subject: [PATCH 0661/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 2 +- .../internal/repository/models/codehost.go | 10 ++++++++++ .../internal/repository/mongodb/codehost.go | 18 ++++++------------ 3 files changed, 17 insertions(+), 13 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/codehost.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 3dba7ae6..ad2e8af0 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -168,7 +168,7 @@ func rollbackChangeCodehostType() error { } // rollback change type to readable string for _, v := range codeHosts { - if err := internalmongodb.NewCodehostColl().RollbackChangeType(v.ID, v.Type); err != nil { + if err := internalmongodb.NewCodehostColl().RollbackType(v.ID, v.Type); err != nil { log.Warnf("fail to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) continue } diff --git a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go new file mode 100644 index 00000000..57945fa6 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go @@ -0,0 +1,10 @@ +package models + +type CodeHost struct { + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` +} + +func (CodeHost) TableName() string { + return "code_host" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go index 0e7571a0..b308b11d 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go @@ -3,13 +3,12 @@ package mongodb import ( "context" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" ) func (c *CodehostColl) ChangeType(ID int, sourceType string) error { @@ -40,7 +39,7 @@ func (c *CodehostColl) ChangeType(ID int, sourceType string) error { return nil } -func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { +func (c *CodehostColl) RollbackType(ID int, sourceType string) error { query := bson.M{"id": ID} if sourceType == "gitlab" { @@ -67,13 +66,8 @@ func (c *CodehostColl) RollbackChangeType(ID int, sourceType string) error { return nil } -type CodeHost struct { - ID int `bson:"id" json:"id"` - Type string `bson:"type" json:"type"` -} - -func (c *CodehostColl) ListCodeHosts() ([]*CodeHost, error) { - codeHosts := make([]*CodeHost, 0) +func (c *CodehostColl) ListCodeHosts() ([]*models.CodeHost, error) { + codeHosts := make([]*models.CodeHost, 0) cursor, err := c.Collection.Find(context.TODO(), bson.M{}) if err != nil { -- Gitee From 5e78dd3e22eab23b03e39e1e2c7a00c84a946feb Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:42:50 +0800 Subject: [PATCH 0662/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index ad2e8af0..e5c3848f 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -55,7 +55,7 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") - err := rollbackChangeCodehostType() + err := rollbackCodehostType() if err != nil { log.Errorf("Failed to rollback codehost type, err: %s", err) return err @@ -159,7 +159,7 @@ func changeCodehostType() error { } // rollback type "github,gitlab..." to "1,2..." -func rollbackChangeCodehostType() error { +func rollbackCodehostType() error { // get all codehosts codeHosts, err := internalmongodb.NewCodehostColl().ListCodeHosts() if err != nil { -- Gitee From 75548fffaac91681b9d794c2d8e59d333fcbb6dd Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 15 Nov 2021 20:48:32 +0800 Subject: [PATCH 0663/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index e5c3848f..677d5c1b 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -148,14 +148,15 @@ func changeCodehostType() error { log.Errorf("fail to list codehosts, err: %s", err) return err } + var finalErr error // change type to readable string for _, v := range codeHosts { if err := internalmongodb.NewCodehostColl().ChangeType(v.ID, v.Type); err != nil { log.Warnf("fail to change id:%d type:%s , err: %s", v.ID, v.Type, err) - continue + finalErr = err } } - return nil + return finalErr } // rollback type "github,gitlab..." to "1,2..." @@ -166,14 +167,16 @@ func rollbackCodehostType() error { log.Errorf("fail to list codehosts, err: %s", err) return err } + var finalErr error // rollback change type to readable string for _, v := range codeHosts { if err := internalmongodb.NewCodehostColl().RollbackType(v.ID, v.Type); err != nil { log.Warnf("fail to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) + finalErr = err continue } } - return nil + return finalErr } func getWebHookTokenFromOrganization() string { -- Gitee From 0de88b8a7f76fc13c990e45e5e3e927211bf3132 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 20:51:27 +0800 Subject: [PATCH 0664/2647] temporarily skip get workflow Signed-off-by: lou --- .../policy/core/service/bundle/rego/authz.rego | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 45fd16e0..b3c0997d 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -73,6 +73,13 @@ access_is_granted { glob.match(trim(grant.endpoint, "/"), ["/"], concat("/", input.parsed_path)) } +# Temporarily skip this endpoint, it will be fixed in 1.7.1 +access_is_granted { + concat("/", input.parsed_path) == "api/aslan/workflow/workflow" + http_request.method == "GET" + project_name == "" +} + user_is_admin { some role all_roles[role] -- Gitee From f62d17b7753e6fc4b90aa2e0ca7bfeddd56e509f Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 15 Nov 2021 21:08:57 +0800 Subject: [PATCH 0665/2647] update code Signed-off-by: allenshen --- .../workflow/service/workflow/nsq_handlers.go | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index a33ee974..5837a6b9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -18,10 +18,8 @@ package workflow import ( "context" - "encoding/base64" "encoding/json" "encoding/xml" - "errors" "fmt" "io/ioutil" "net/http" @@ -33,6 +31,7 @@ import ( dockerCli "github.com/docker/docker/client" "github.com/docker/go-connections/sockets" "github.com/nsqio/go-nsq" + "github.com/pkg/errors" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -46,6 +45,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" @@ -213,16 +213,38 @@ func (h *TaskAckHandler) handle(message *nsq.Message) error { return nil } +// get docker file content from codehost +// TODO need to support codehub and gerrit +func getRawFileContent(codehostID int, repo, owner, branch, filePath string) ([]byte, error) { + ch, err := systemconfig.New().GetCodeHost(codehostID) + if err != nil { + return nil, errors.Wrapf(err, "Failed to get codeHost info %d", codehostID) + } + switch ch.Type { + case setting.SourceFromGitlab: + cli, err := gitlab.NewClient(ch.Address, ch.AccessToken) + if err != nil { + return nil, errors.Wrapf(err, "Failed to get gitlab client") + } + return cli.GetRawFile(repo, owner, branch, filePath) + case setting.SourceFromGithub: + gitClient := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + return gitClient.GetFileContent(owner, repo, filePath, branch) + default: + return nil, fmt.Errorf("Failed to create client for codehostID: %d", codehostID) + } +} + func (h *TaskAckHandler) getDockerfileContent(build *types.Repository, ctx *task.DockerBuildCtx) string { switch ctx.Source { - case setting.ServiceSourceTemplate: + case setting.DockerfileSourceTemplate: content, err := commonservice.GetDockerfileTemplateContent(ctx.TemplateID) if err != nil { h.log.Errorf("Failed to get dockerfile template content, err: %v", err) return "" } return content - default: // TODO need rewrite + case setting.DockerfileSourceLocal: path := ctx.DockerFile pathArray := strings.Split(path, "/") if len(pathArray[0])+1 >= len(path) { @@ -230,32 +252,14 @@ func (h *TaskAckHandler) getDockerfileContent(build *types.Repository, ctx *task return "" } dockerfilePath := path[len(pathArray[0])+1:] - if strings.Contains(build.Address, "gitlab") { - cli, err := gitlab.NewClient(build.Address, build.OauthToken) - if err != nil { - h.log.Errorf("Failed to get gitlab client, err: %v", err) - return "" - } - content, err := cli.GetRawFile(build.RepoOwner, build.RepoName, build.Branch, dockerfilePath) - if err != nil { - h.log.Errorf("uploadTaskData gitlab GetRawFile err:%v", err) - return "" - } - return string(content) - } else { - gitClient := git.NewClient(build.OauthToken, config.ProxyHTTPSAddr()) - fileContent, _, _, _ := gitClient.Repositories.GetContents(context.Background(), build.RepoOwner, build.RepoName, dockerfilePath, nil) - if fileContent != nil { - dockerfileContent := *fileContent.Content - dockerfileContent = dockerfileContent[:len(dockerfileContent)-2] - content, err := base64.StdEncoding.DecodeString(dockerfileContent) - if err != nil { - h.log.Errorf("uploadTaskData github GetRawFile err:%v", err) - return "" - } - return string(content) - } + content, err := getRawFileContent(build.CodehostID, build.RepoName, build.RepoOwner, build.Branch, dockerfilePath) + if err != nil { + h.log.Errorf("Failed to get dockerfile content, err %s", err) + return "" } + return string(content) + default: // from local + h.log.Errorf("Failed to get dockerfile content, illegal source %s", ctx.Source) return "" } } -- Gitee From 645dea32cf38cd901647096dc95113e7cc58be4e Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 15 Nov 2021 22:02:36 +0800 Subject: [PATCH 0666/2647] add policy rules Signed-off-by: lou --- .../aslan/core/environment/handler/policy.yaml | 8 ++++++++ .../aslan/core/workflow/handler/policy.yaml | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index b09e4011..12e3cde0 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -30,6 +30,8 @@ rules: endpoint: "/api/aslan/environment/kube/events" - method: GET endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" + - method: GET + endpoint: "/api/aslan/project/products/?*/services" - action: create_environment alias: "新建集成环境" description: "" @@ -40,6 +42,10 @@ rules: endpoint: "/api/aslan/environment/environments/?*/helm" - method: POST endpoint: "/api/aslan/service/workloads" + - method: GET + endpoint: "/api/aslan/project/products/?*/services" + - method: GET + endpoint: "/api/aslan/delivery/releases" - action: config_environment alias: "配置集成环境" description: "" @@ -58,6 +64,8 @@ rules: endpoint: "/api/aslan/environment/environments/?*/multiHelmEnv" - method: PUT endpoint: "/api/aslan/service/workloads" + - method: GET + endpoint: "/api/aslan/project/products/?*/services" - action: manage_environment alias: "管理服务实例" description: "" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index d3563eb3..ae49e38f 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -36,6 +36,12 @@ rules: rules: - method: PUT endpoint: "api/aslan/workflow/workflow" + - method: GET + endpoint: "/api/aslan/environment/environments" + - method: GET + endpoint: "/api/aslan/workflow/workflow/preset/?*" + - method: GET + endpoint: "/api/aslan/testing/testdetail" - action: create_workflow alias: "新建工作流" description: "" @@ -46,6 +52,10 @@ rules: endpoint: "/api/aslan/workflow/workflow/old/?*/new/?*" - method: POST endpoint: "/api/directory/workflowTask/create" + - method: GET + endpoint: "/api/aslan/environment/environments" + - method: GET + endpoint: "/api/aslan/testing/testdetail" - action: delete_workflow alias: "删除工作流" description: "" -- Gitee From 3322f2c82caacaaa8376f99f3e025bc1786411fd Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 15 Nov 2021 22:11:38 +0800 Subject: [PATCH 0667/2647] remove unused code & fix errcheck lint Signed-off-by: zhangyifei --- .../core/environment/service/environment.go | 2 - .../aslan/core/environment/service/image.go | 38 ------------------- .../aslan/core/project/handler/product.go | 4 -- .../aslan/core/templatestore/handler/chart.go | 4 -- pkg/tool/remotedialer/server.go | 7 ++-- 5 files changed, 4 insertions(+), 51 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index e8f7c584..57de424a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -72,8 +72,6 @@ const ( UpdateTypeEnv = "envVar" ) -type usageScenario string - const ( usageScenarioCreateEnv = "createEnv" usageScenarioUpdateEnv = "updateEnv" diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index f44cc312..4fd4bbf7 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -17,7 +17,6 @@ limitations under the License. package service import ( - "bytes" "fmt" "regexp" "strings" @@ -25,7 +24,6 @@ import ( helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" - "helm.sh/helm/v3/pkg/strvals" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" @@ -377,39 +375,3 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log } return nil } - -func updateImageTagInValues(valuesYaml []byte, repo, newTag string) ([]byte, error) { - if !bytes.Contains(valuesYaml, []byte(repo)) { - return nil, nil - } - - valuesMap := map[string]interface{}{} - if err := yaml.Unmarshal(valuesYaml, &valuesMap); err != nil { - return nil, err - } - - valuesFlatMap, err := converter.Flatten(valuesMap) - if err != nil { - return nil, err - } - - var matchingKey string - for k, v := range valuesFlatMap { - if val, ok := v.(string); ok && repo == val { - matchingKey = k - } - } - if matchingKey == "" { - return nil, fmt.Errorf("key is not found for value %s in map %v", repo, valuesFlatMap) - } - - newKey := strings.Replace(matchingKey, ".repository", ".tag", 1) - if newKey == matchingKey { - return nil, fmt.Errorf("field repository is not found in key %s", matchingKey) - } - if err := strvals.ParseInto(fmt.Sprintf("%s=%s", newKey, newTag), valuesMap); err != nil { - return nil, err - } - - return yaml.Marshal(valuesMap) -} diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 0729c3e2..0d96803f 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -105,10 +105,6 @@ func UpdateProductTmplStatus(c *gin.Context) { ctx.Err = projectservice.UpdateProductTmplStatus(productName, onboardingStatus, ctx.Logger) } -type updateServiceOrderArgs struct { - Services [][]string `json:"services"` -} - func UpdateProject(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 17cf7eda..54276fb3 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -32,10 +32,6 @@ type addChartArgs struct { Name string `json:"name"` } -type updateChartTemplateVariablesReq struct { - Variables []*templateservice.Variable `json:"variables"` -} - func GetChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/tool/remotedialer/server.go b/pkg/tool/remotedialer/server.go index 38cbf4a9..6473704c 100644 --- a/pkg/tool/remotedialer/server.go +++ b/pkg/tool/remotedialer/server.go @@ -39,11 +39,12 @@ type ErrorWriter func(rw http.ResponseWriter, req *http.Request, code int, err e func DefaultErrorWriter(rw http.ResponseWriter, req *http.Request, code int, err error) { rw.WriteHeader(code) - rw.Write([]byte(err.Error())) + _, errWrite := rw.Write([]byte(err.Error())) + if errWrite != nil { + logrus.Warnf("Failed to write error: %s", errWrite) + } } -type transportGetter func() (http.RoundTripper, error) - type Server struct { PeerID string PeerToken string -- Gitee From fbbd86bd6fd4c1088eb48be63b1ea91b515a90fb Mon Sep 17 00:00:00 2001 From: fansi Date: Mon, 15 Nov 2021 22:32:31 +0800 Subject: [PATCH 0668/2647] add missing policy rule Signed-off-by: fansi --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index ae49e38f..1dc34f8c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -78,3 +78,5 @@ rules: endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" - method: POST endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" + - method: GET + endpoint: "/api/aslan/delivery/releases" -- Gitee From dede8884c8f47b811531ab687d04225061dfb4c0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 16 Nov 2021 09:46:26 +0800 Subject: [PATCH 0669/2647] custom cache dir support custom variable Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 5 ++++- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index fc915ce0..6e252a97 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -41,9 +41,10 @@ type WorkspaceAchiever struct { StorageURI string PipelineName string ServiceName string + Envs []string } -func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, caches []string, gitFolders []string) *WorkspaceAchiever { +func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, caches, gitFolders, envs []string) *WorkspaceAchiever { return &WorkspaceAchiever{ paths: caches, wd: wd, @@ -51,6 +52,7 @@ func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, cach StorageURI: storageURI, PipelineName: pipelineName, ServiceName: serviceName, + Envs: envs, } } @@ -119,6 +121,7 @@ func (c *WorkspaceAchiever) process(match string) bool { func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { for _, path := range paths { + fmt.Println("path:", path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) if err != nil { log.Warningf("%s: %v", path, err) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index a7a14136..ff470607 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -85,7 +85,7 @@ func (r *Reaper) GetCacheFile() string { } func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) error { - fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}) + fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}, r.getUserEnvs()) // list files matches caches return fileAchiever.Achieve(dest) -- Gitee From 15aabec7c729a17861d53fa81afa64ab847a5692 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 16 Nov 2021 11:56:35 +0800 Subject: [PATCH 0670/2647] fix chart vresion copy error Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/helm.go | 8 -------- .../aslan/core/service/service/helm.go | 14 +++++++------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 347b238c..bc1dfcf0 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -86,14 +86,6 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file return fsservice.SaveAndUploadFiles(fileTree, serviceName, localBase, s3Base, copies, log.SugaredLogger()) } -// SaveAndUploadServiceWithRevision save file to local and upload to s3 -//func SaveAndUploadServiceWithRevision(projectName, serviceName string, fileTree fs.FS, revision int64) error { -// serviceNameWithRevision := config.ServiceNameWithRevision(serviceName, revision) -// localBase := config.LocalServicePathWithRevision(projectName, serviceName, revision) -// s3Base := config.ObjectStorageServicePath(projectName, serviceName) -// return fsservice.SaveAndUploadFiles(fileTree, serviceNameWithRevision, localBase, s3Base, log.SugaredLogger()) -//} - func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { tree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName, RepoLink: svc.SrcPath}, diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 0608ee0d..cc91441b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -484,13 +484,6 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } - err = copyChartRevision(projectName, serviceName, rev) - if err != nil { - log.Errorf("Failed to copy file %s, err: %s", args.Name, err) - finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", args.Name) - return - } - // save files to disk and upload them to s3 if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, fsTree); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) @@ -498,6 +491,13 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } + err = copyChartRevision(projectName, serviceName, rev) + if err != nil { + log.Errorf("Failed to copy file %s, err: %s", args.Name, err) + finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", args.Name) + return + } + if source != setting.SourceFromPublicRepo && codehostInfo != nil { repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) } -- Gitee From 54ceb27670d990b2ed635ef20b8185d0e8389394 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 16 Nov 2021 11:56:39 +0800 Subject: [PATCH 0671/2647] debug code Signed-off-by: liu deyi --- .../reaper/core/service/archive/file_archive.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 6e252a97..f2ae197a 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -121,6 +121,11 @@ func (c *WorkspaceAchiever) process(match string) bool { func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { for _, path := range paths { + //replace path + path, err := c.renderPathVariable(path) + if err != nil { + fmt.Println("err:", err) + } fmt.Println("path:", path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) if err != nil { @@ -234,3 +239,14 @@ func (c *WorkspaceAchiever) Achieve(target string) error { return nil } + +func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { + envs := c.Envs + cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s %s", "echo", path)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = envs + data, err := cmd.Output() + + return string(data), err +} -- Gitee From 49f06573d93ac34970f48c862859a3478fcd7f4d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 16 Nov 2021 14:01:54 +0800 Subject: [PATCH 0672/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index f2ae197a..4a3aade5 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -243,7 +243,6 @@ func (c *WorkspaceAchiever) Achieve(target string) error { func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { envs := c.Envs cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s %s", "echo", path)) - cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = envs data, err := cmd.Output() -- Gitee From c25c3c9834dc07f887b6ba3ae2af407f5f0a94d1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 16 Nov 2021 15:47:33 +0800 Subject: [PATCH 0673/2647] fixed conflict resolve problems Signed-off-by: Min Min --- pkg/microservice/aslan/core/templatestore/handler/yaml.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 21717aa0..0d71936d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -98,6 +98,10 @@ func GetYamlTemplateVariables(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() req := &getYamlTemplateVariablesReq{} + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } ctx.Resp, ctx.Err = templateservice.GetYamlVariables(req.Content, ctx.Logger) } -- Gitee From 07510b24d1f25dca4e11682b74e3cca24bbcdc80 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 16 Nov 2021 19:52:19 +0800 Subject: [PATCH 0674/2647] return err Signed-off-by: mouuii --- .../core/codehost/repository/mongodb/codehost.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 5b7170bb..ac7b2349 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -68,10 +68,7 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { codehost := new(models.CodeHost) query := bson.M{"id": ID, "deleted_at": 0} err := c.Collection.FindOne(context.TODO(), query).Decode(codehost) - if err != nil { - return nil, nil - } - return codehost, nil + return codehost, err } func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { -- Gitee From 33b4e04174d8fb7a940ce1ec627460b96942b064 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 16 Nov 2021 20:02:01 +0800 Subject: [PATCH 0675/2647] return err Signed-off-by: mouuii --- .../core/codehost/repository/mongodb/codehost.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index ac7b2349..5287ecf6 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -67,8 +67,10 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { codehost := new(models.CodeHost) query := bson.M{"id": ID, "deleted_at": 0} - err := c.Collection.FindOne(context.TODO(), query).Decode(codehost) - return codehost, err + if err := c.Collection.FindOne(context.TODO(), query).Decode(codehost); err != nil { + return nil, err + } + return codehost, nil } func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { -- Gitee From ebb48431533e7e05967ff8c615d22badb5eba5c8 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 20:21:44 +0800 Subject: [PATCH 0676/2647] add biz labeler for v1.7.0 --- .github/labeler.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index fa949ecb..c31f7da4 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -2,6 +2,18 @@ service/aslan: - pkg/microservice/aslan/**/* +service/picket: +- pkg/microservice/picket/**/* + +service/user: +- pkg/microservice/user/**/* + +service/policy: +- pkg/microservice/policy/**/* + +service/config: +- pkg/microservice/systemconfig/**/* + service/cron: - pkg/microservice/cron/**/* -- Gitee From 55e539ff303b533516d26da0d53f0ded7caba9a3 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 20:41:40 +0800 Subject: [PATCH 0677/2647] add Zadig System ArchitectureOverview Signed-off-by: landylee007 --- README-zh-CN.md | 2 ++ README.md | 4 ++- System-Architecture-Overview-zh-CN.md | 39 ++++++++++++++++++++++++++ System-Architecture-Overview.md | 40 +++++++++++++++++++++++++++ Zadig-System-Architecture.svg | 1 + 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 System-Architecture-Overview-zh-CN.md create mode 100644 System-Architecture-Overview.md create mode 100644 Zadig-System-Architecture.svg diff --git a/README-zh-CN.md b/README-zh-CN.md index beba21f0..3bd629ba 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -42,6 +42,8 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery ![业务架构图](./Zadig-Business-Architecture-zh.jpg) +想了解更多系统架构信息,参考 [系统架构简介](System-Architecture-Overview-zh-CN.md). + 产品特性介绍:
diff --git a/README.md b/README.md index 374fa483..7ba402d4 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,12 @@ Zadig is non-invasive, it does not exclude any of your existing development proc > Our vision is: Developer + Zadig = Business success -The architecture is as follows: +The business architecture is as follows: ![Business Architecture](./Zadig-Business-Architecture.jpg) +For more details about system architecture, see [Zadig System Architecture Overview](System-Architecture-Overview.md). + The Highlighted Features:
diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md new file mode 100644 index 00000000..2584f4bf --- /dev/null +++ b/System-Architecture-Overview-zh-CN.md @@ -0,0 +1,39 @@ +# Zadig 系统架构简介 + +## 系统架构图 + +![Architecture_diagram](./Zadig-System-Architecture.svg) + +## 核心组件介绍 + +用户入口: +- zadig-portal:zadig 前端组件 +- kodespace:zadig 开发者命令行工具 +- Zadig Toolkit:vscode 开发者插件 + +API 网关: +- Gloo Edge:Zadig 的 API 网关组件 +- OPA:认证和授权组件 +- User:用户管理,Token 生成 +- Dex:Zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD,LDAP,OAuth2,GitHub,... + +Zadig 核心业务: +- Picket:数据聚合服务 +- Policy:OPA 数据源,策略注册中心 +- Aslan:项目,环境,服务,工作流,构建配置,系统配置等系统功能 +- Config: 系统配置 +- Workflow Runner: + - warpdrive:工作流引擎,负责 reaper、predator 实例的创建销毁等管理操作 + - reaper: 负责执行单个工作流作业中的构建、测试等任务 + - predator:负责执行单个工作流作业中的镜像分发任务 + - plugins:工作流插件 + - Jenkins-plugin: 用于触发 Jenkins job,显示状态和结果等 +- cron:定时任务,包括环境的回收,K8s 资源的清理等 +- nsq:消息队列(第三方组件) + +数据平面: +- mongodb:业务数据数据库 +- mysql:存储 dex 配置、用户信息的数据库 + +K8s 集群: +- Zadig 业务运行在各种云厂商的标准 K8s 集群 \ No newline at end of file diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md new file mode 100644 index 00000000..b21bee19 --- /dev/null +++ b/System-Architecture-Overview.md @@ -0,0 +1,40 @@ +# Zadig System Architecture Overview + +## System Architecture + +![Architecture_diagram](./Zadig-System-Architecture.svg) + +## Main Components + +User Interface: +- zadig-portal:zadig web component +- kodespace:zadig command line tools +- Zadig Toolkit:vscode plugin + +API Gateway: +- Gloo Edge:Zadig API gateway +- OPA:Authentication and authorization +- User:User management, token generation +- Dex:Identity service for Zadig, acts as a portal to other identity providers like AD, LDAP, OAuth2, GitHub, ... + +Zadig Core: +- Picket:backend for frontend service. +- Policy: data source of OPA and policy registration center. +- Aslan:main service for all business logic. Project, environment, service, workflow, build, system management are all in this service. +- Config: system configuration center. + +- Workflow Runner: + - warpdrive:workflow engine, manages reaper and predator + - reaper: workflow runner. Used for building, testing tasks. + - predator:workflow runner. Used for distribute tasks. + - plugins:workflow plugins + - Jenkins-plugin: workflow runner. Used as connector to trigger Jenkins job and retrieve job information. +- cron:cronjob runner +- nsq:message queue + +Data Plane: +- mongodb:database for business data. +- mysql:dex configuration,database for user. + +Kubernetes Cluster: +- Zadig business runs on standard K8s clusters from cloud vendors diff --git a/Zadig-System-Architecture.svg b/Zadig-System-Architecture.svg new file mode 100644 index 00000000..edafbd98 --- /dev/null +++ b/Zadig-System-Architecture.svg @@ -0,0 +1 @@ +
User
Interface
User...
API
Gateway
API...
OAuth 2.0
OAuth 2.0
LDAP/AD
LDAP/AD
GitHub
GitHub
……
……
OpenID Provider
OpenID Provider
Zadig Core
Zadig...
Data Plane
Data Plane
MongoDB
MongoDB
MySQL
MySQL
Engineer (development, testing, SRE etc.)
Engineer (development, testing, SRE etc.)
Workflow Runner
Workflow Runner
warpdrive
warpdrive
Dind Replicas
Dind Replicas
dind
dind
dind
dind
NSQ
NSQ
Cron
Cron
workflow controller
workflow controller
project
project
service
service
environment
environment
cronjob controller
cronjob controller
Zadig Portal
Zadig Portal
Zadig Toolkit
Zadig Toolkit
Kodespace
Kodespace
reaper
reaper
predator
predat...
plugins
plugins
warpdrive
warpdr...
warpdrive
warpdr...
Picket
Picket
Aslan
Aslan
Dex
Dex
User
User
Gloo Edge
Gloo Edge
Opa
Opa
Kubernetes
Cluster
Kubern...
Ali ACK 
Ali ACK 
AWS EKS
AWS EKS
Tencent TKE
Tencent TKE
Huawei CCE
Huawei CCE
......
......
Config
Config
Policy
Policy
Viewer does not support full SVG 1.1
\ No newline at end of file -- Gitee From c94f211f0be8f8fc53f17c1cf27b8a32a0ca952e Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 20:44:04 +0800 Subject: [PATCH 0678/2647] add Zadig System ArchitectureOverview Signed-off-by: landylee007 --- System-Architecture-Overview-zh-CN.md | 6 +++--- System-Architecture-Overview.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md index 2584f4bf..f43d9a59 100644 --- a/System-Architecture-Overview-zh-CN.md +++ b/System-Architecture-Overview-zh-CN.md @@ -12,10 +12,10 @@ - Zadig Toolkit:vscode 开发者插件 API 网关: -- Gloo Edge:Zadig 的 API 网关组件 +- Gloo Edge:zadig 的 API 网关组件 - OPA:认证和授权组件 - User:用户管理,Token 生成 -- Dex:Zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD,LDAP,OAuth2,GitHub,... +- Dex:zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD,LDAP,OAuth2,GitHub,... Zadig 核心业务: - Picket:数据聚合服务 @@ -36,4 +36,4 @@ Zadig 核心业务: - mysql:存储 dex 配置、用户信息的数据库 K8s 集群: -- Zadig 业务运行在各种云厂商的标准 K8s 集群 \ No newline at end of file +- zadig 业务运行在各种云厂商的标准 K8s 集群 \ No newline at end of file diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index b21bee19..9acb35ed 100644 --- a/System-Architecture-Overview.md +++ b/System-Architecture-Overview.md @@ -13,7 +13,7 @@ User Interface: API Gateway: - Gloo Edge:Zadig API gateway -- OPA:Authentication and authorization +- OPA:Authentication and authorization - User:User management, token generation - Dex:Identity service for Zadig, acts as a portal to other identity providers like AD, LDAP, OAuth2, GitHub, ... @@ -37,4 +37,4 @@ Data Plane: - mysql:dex configuration,database for user. Kubernetes Cluster: -- Zadig business runs on standard K8s clusters from cloud vendors +- zadig business runs on standard K8s clusters from cloud vendors -- Gitee From 203cd877c551b16b3d615bc6d3f3444248f55382 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 21:02:36 +0800 Subject: [PATCH 0679/2647] fix spelling Signed-off-by: landylee007 --- System-Architecture-Overview-zh-CN.md | 12 ++++++------ System-Architecture-Overview.md | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md index f43d9a59..d502216f 100644 --- a/System-Architecture-Overview-zh-CN.md +++ b/System-Architecture-Overview-zh-CN.md @@ -15,12 +15,12 @@ API 网关: - Gloo Edge:zadig 的 API 网关组件 - OPA:认证和授权组件 - User:用户管理,Token 生成 -- Dex:zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD,LDAP,OAuth2,GitHub,... +- Dex:zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD / LDAP / OAuth2 / GitHub / .. Zadig 核心业务: - Picket:数据聚合服务 - Policy:OPA 数据源,策略注册中心 -- Aslan:项目,环境,服务,工作流,构建配置,系统配置等系统功能 +- Aslan:项目 / 环境 / 服务 / 工作流 / 构建配置 / 系统配置等系统功能 - Config: 系统配置 - Workflow Runner: - warpdrive:工作流引擎,负责 reaper、predator 实例的创建销毁等管理操作 @@ -28,12 +28,12 @@ Zadig 核心业务: - predator:负责执行单个工作流作业中的镜像分发任务 - plugins:工作流插件 - Jenkins-plugin: 用于触发 Jenkins job,显示状态和结果等 -- cron:定时任务,包括环境的回收,K8s 资源的清理等 -- nsq:消息队列(第三方组件) +- Cron:定时任务,包括环境的回收,K8s 资源的清理等 +- NSQ:消息队列(第三方组件) 数据平面: -- mongodb:业务数据数据库 -- mysql:存储 dex 配置、用户信息的数据库 +- MongoDB:业务数据数据库 +- MySQL:存储 dex 配置、用户信息的数据库 K8s 集群: - zadig 业务运行在各种云厂商的标准 K8s 集群 \ No newline at end of file diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index 9acb35ed..d272ba32 100644 --- a/System-Architecture-Overview.md +++ b/System-Architecture-Overview.md @@ -15,26 +15,25 @@ API Gateway: - Gloo Edge:Zadig API gateway - OPA:Authentication and authorization - User:User management, token generation -- Dex:Identity service for Zadig, acts as a portal to other identity providers like AD, LDAP, OAuth2, GitHub, ... +- Dex:Identity service for Zadig, acts as a portal to other identity providers like AD / LDAP / OAuth2 / GitHub / .. Zadig Core: - Picket:backend for frontend service. - Policy: data source of OPA and policy registration center. - Aslan:main service for all business logic. Project, environment, service, workflow, build, system management are all in this service. - Config: system configuration center. - - Workflow Runner: - warpdrive:workflow engine, manages reaper and predator - reaper: workflow runner. Used for building, testing tasks. - predator:workflow runner. Used for distribute tasks. - plugins:workflow plugins - Jenkins-plugin: workflow runner. Used as connector to trigger Jenkins job and retrieve job information. -- cron:cronjob runner -- nsq:message queue +- Cron:cronjob runner +- NSQ:message queue Data Plane: -- mongodb:database for business data. -- mysql:dex configuration,database for user. +- MongoDB:database for business data. +- MySQL:dex configuration,database for user. Kubernetes Cluster: - zadig business runs on standard K8s clusters from cloud vendors -- Gitee From 1097d0635d284b8575f10702b1d73d55f2e69ab1 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 21:26:25 +0800 Subject: [PATCH 0680/2647] add Zadig System ArchitectureOverview Signed-off-by: landylee007 --- Zadig-System-Architecture.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zadig-System-Architecture.svg b/Zadig-System-Architecture.svg index edafbd98..c53a8eac 100644 --- a/Zadig-System-Architecture.svg +++ b/Zadig-System-Architecture.svg @@ -1 +1 @@ -
User
Interface
User...
API
Gateway
API...
OAuth 2.0
OAuth 2.0
LDAP/AD
LDAP/AD
GitHub
GitHub
……
……
OpenID Provider
OpenID Provider
Zadig Core
Zadig...
Data Plane
Data Plane
MongoDB
MongoDB
MySQL
MySQL
Engineer (development, testing, SRE etc.)
Engineer (development, testing, SRE etc.)
Workflow Runner
Workflow Runner
warpdrive
warpdrive
Dind Replicas
Dind Replicas
dind
dind
dind
dind
NSQ
NSQ
Cron
Cron
workflow controller
workflow controller
project
project
service
service
environment
environment
cronjob controller
cronjob controller
Zadig Portal
Zadig Portal
Zadig Toolkit
Zadig Toolkit
Kodespace
Kodespace
reaper
reaper
predator
predat...
plugins
plugins
warpdrive
warpdr...
warpdrive
warpdr...
Picket
Picket
Aslan
Aslan
Dex
Dex
User
User
Gloo Edge
Gloo Edge
Opa
Opa
Kubernetes
Cluster
Kubern...
Ali ACK 
Ali ACK 
AWS EKS
AWS EKS
Tencent TKE
Tencent TKE
Huawei CCE
Huawei CCE
......
......
Config
Config
Policy
Policy
Viewer does not support full SVG 1.1
\ No newline at end of file +
User
Interface
User...
API
Gateway
API...
OAuth 2.0
OAuth 2.0
LDAP/AD
LDAP/AD
GitHub
GitHub
……
……
OpenID Provider
OpenID Provider
Zadig Core
Zadig...
Data Plane
Data Plane
MongoDB
MongoDB
MySQL
MySQL
Engineer (development, testing, SRE etc.)
Engineer (development, testing, SRE etc.)
Workflow Runner
Workflow Runner
warpdrive
warpdrive
Dind Replicas
Dind Replicas
dind
dind
dind
dind
NSQ
NSQ
Cron
Cron
workflow controller
workflow controller
project
project
service
service
environment
environment
cronjob controller
cronjob controller
Zadig Portal
Zadig Portal
Zadig Toolkit
Zadig Toolkit
Kodespace
Kodespace
reaper
reaper
predator
predat...
plugins
plugins
warpdrive
warpdr...
warpdrive
warpdr...
Picket
Picket
Aslan
Aslan
Dex
Dex
User
User
Gloo Edge
Gloo Edge
Opa
Opa
Kubernetes
Cluster
Kubern...
Ali ACK 
Ali ACK 
AWS EKS
AWS EKS
Tencent TKE
Tencent TKE
Huawei CCE
Huawei CCE
......
......
Config
Config
Policy
Policy
Viewer does not support full SVG 1.1
\ No newline at end of file -- Gitee From 036ee99513c541ea020a91f87cecad6d1102b555 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 21:29:09 +0800 Subject: [PATCH 0681/2647] add Zadig System ArchitectureOverview Signed-off-by: landylee007 --- System-Architecture-Overview-zh-CN.md | 2 +- System-Architecture-Overview.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md index d502216f..91d8059a 100644 --- a/System-Architecture-Overview-zh-CN.md +++ b/System-Architecture-Overview-zh-CN.md @@ -19,8 +19,8 @@ API 网关: Zadig 核心业务: - Picket:数据聚合服务 -- Policy:OPA 数据源,策略注册中心 - Aslan:项目 / 环境 / 服务 / 工作流 / 构建配置 / 系统配置等系统功能 +- Policy:OPA 数据源,策略注册中心 - Config: 系统配置 - Workflow Runner: - warpdrive:工作流引擎,负责 reaper、predator 实例的创建销毁等管理操作 diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index d272ba32..efff3056 100644 --- a/System-Architecture-Overview.md +++ b/System-Architecture-Overview.md @@ -19,8 +19,8 @@ API Gateway: Zadig Core: - Picket:backend for frontend service. -- Policy: data source of OPA and policy registration center. - Aslan:main service for all business logic. Project, environment, service, workflow, build, system management are all in this service. +- Policy: data source of OPA and policy registration center. - Config: system configuration center. - Workflow Runner: - warpdrive:workflow engine, manages reaper and predator -- Gitee From 4ecf5aa5acfe92da164ab5fecc5ee0cae18203e4 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 21:32:31 +0800 Subject: [PATCH 0682/2647] add Zadig System ArchitectureOverview Signed-off-by: landylee007 --- System-Architecture-Overview-zh-CN.md | 6 +++--- System-Architecture-Overview.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md index 91d8059a..ed04b632 100644 --- a/System-Architecture-Overview-zh-CN.md +++ b/System-Architecture-Overview-zh-CN.md @@ -21,13 +21,13 @@ Zadig 核心业务: - Picket:数据聚合服务 - Aslan:项目 / 环境 / 服务 / 工作流 / 构建配置 / 系统配置等系统功能 - Policy:OPA 数据源,策略注册中心 -- Config: 系统配置 +- Config:系统配置 - Workflow Runner: - warpdrive:工作流引擎,负责 reaper、predator 实例的创建销毁等管理操作 - - reaper: 负责执行单个工作流作业中的构建、测试等任务 + - reaper:负责执行单个工作流作业中的构建、测试等任务 - predator:负责执行单个工作流作业中的镜像分发任务 - plugins:工作流插件 - - Jenkins-plugin: 用于触发 Jenkins job,显示状态和结果等 + - Jenkins-plugin:用于触发 Jenkins job,显示状态和结果等 - Cron:定时任务,包括环境的回收,K8s 资源的清理等 - NSQ:消息队列(第三方组件) diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index efff3056..4180f597 100644 --- a/System-Architecture-Overview.md +++ b/System-Architecture-Overview.md @@ -20,14 +20,14 @@ API Gateway: Zadig Core: - Picket:backend for frontend service. - Aslan:main service for all business logic. Project, environment, service, workflow, build, system management are all in this service. -- Policy: data source of OPA and policy registration center. -- Config: system configuration center. +- Policy:data source of OPA and policy registration center. +- Config:system configuration center. - Workflow Runner: - warpdrive:workflow engine, manages reaper and predator - - reaper: workflow runner. Used for building, testing tasks. + - reaper:workflow runner. Used for building, testing tasks. - predator:workflow runner. Used for distribute tasks. - plugins:workflow plugins - - Jenkins-plugin: workflow runner. Used as connector to trigger Jenkins job and retrieve job information. + - Jenkins-plugin:workflow runner. Used as connector to trigger Jenkins job and retrieve job information. - Cron:cronjob runner - NSQ:message queue -- Gitee From 32b5cd904d5f7417db5a68c93e02a13597e17a04 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Tue, 16 Nov 2021 21:47:37 +0800 Subject: [PATCH 0683/2647] add Zadig System ArchitectureOverview Signed-off-by: landylee007 --- System-Architecture-Overview-zh-CN.md | 10 +++++----- System-Architecture-Overview.md | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md index ed04b632..4f10c078 100644 --- a/System-Architecture-Overview-zh-CN.md +++ b/System-Architecture-Overview-zh-CN.md @@ -7,15 +7,15 @@ ## 核心组件介绍 用户入口: -- zadig-portal:zadig 前端组件 -- kodespace:zadig 开发者命令行工具 +- zadig-portal:Zadig 前端组件 +- kodespace:Zadig 开发者命令行工具 - Zadig Toolkit:vscode 开发者插件 API 网关: -- Gloo Edge:zadig 的 API 网关组件 +- Gloo Edge:Zadig 的 API 网关组件 - OPA:认证和授权组件 - User:用户管理,Token 生成 -- Dex:zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD / LDAP / OAuth2 / GitHub / .. +- Dex:Zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD / LDAP / OAuth2 / GitHub / .. Zadig 核心业务: - Picket:数据聚合服务 @@ -36,4 +36,4 @@ Zadig 核心业务: - MySQL:存储 dex 配置、用户信息的数据库 K8s 集群: -- zadig 业务运行在各种云厂商的标准 K8s 集群 \ No newline at end of file +- Zadig 业务运行在各种云厂商的标准 K8s 集群 \ No newline at end of file diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index 4180f597..61e05977 100644 --- a/System-Architecture-Overview.md +++ b/System-Architecture-Overview.md @@ -7,8 +7,8 @@ ## Main Components User Interface: -- zadig-portal:zadig web component -- kodespace:zadig command line tools +- zadig-portal:Zadig web component +- kodespace:Zadig command line tools - Zadig Toolkit:vscode plugin API Gateway: @@ -36,4 +36,4 @@ Data Plane: - MySQL:dex configuration,database for user. Kubernetes Cluster: -- zadig business runs on standard K8s clusters from cloud vendors +- Zadig business runs on standard K8s clusters from cloud vendors -- Gitee From 2bdc927e3e9dfd25cb3cff2d1bccdf875283db30 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 16 Nov 2021 22:24:04 +0800 Subject: [PATCH 0684/2647] update get chart version api logic Signed-off-by: allenshen --- pkg/microservice/aslan/config/config.go | 4 + .../repository/models/delivery_version.go | 1 + .../core/environment/handler/helm_release.go | 40 +++ .../aslan/core/environment/handler/router.go | 3 + .../aslan/core/environment/service/helm.go | 263 ++++++++++++++++++ .../aslan/core/service/handler/helm.go | 18 +- .../aslan/core/service/service/helm.go | 66 ++++- pkg/tool/errors/http_errors.go | 6 + 8 files changed, 387 insertions(+), 14 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/handler/helm_release.go create mode 100644 pkg/microservice/aslan/core/environment/service/helm.go diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 02da935d..22fd8bfb 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -230,6 +230,10 @@ func LocalServicePathWithRevision(project, service string, revision int64) strin return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("%d", revision)) } +func LocalDeliveryChartPathWithRevision(project, service string, revision int64) string { + return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("delivery/%d", revision)) +} + func ServiceNameWithRevision(serviceName string, revision int64) string { return fmt.Sprintf("%s-%d", serviceName, revision) } diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 459d7f96..7efeda7d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -25,6 +25,7 @@ type DeliveryVersion struct { Version string `bson:"version" json:"version"` ProductName string `bson:"product_name" json:"productName"` WorkflowName string `bson:"workflow_name" json:"workflowName"` + Type string `bson:"type" json:"type"` TaskID int `bson:"task_id" json:"taskId"` Desc string `bson:"desc" json:"desc"` Labels []string `bson:"labels" json:"labels"` diff --git a/pkg/microservice/aslan/core/environment/handler/helm_release.go b/pkg/microservice/aslan/core/environment/handler/helm_release.go new file mode 100644 index 00000000..d93e7a5e --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/helm_release.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListReleases(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Query("envName") + productName := c.Param("productName") + ctx.Resp, ctx.Err = service.ListReleases(productName, envName, ctx.Logger) +} + +func GetChartInfos(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Query("envName") + servicesName := c.Query("serviceName") + productName := c.Param("productName") + ctx.Resp, ctx.Err = service.GetChartInfos(productName, envName, servicesName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index bc13c84c..4b7b8529 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -121,6 +121,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:productName/groups", ListGroups) environments.GET("/:productName/workloads", ListWorkloadsInEnv) + environments.GET("/:productName/helm/releases", ListReleases) + environments.GET("/:productName/helm/charts", GetChartInfos) + environments.GET("/:productName/services/:serviceName", GetService) environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.UpdateOperationLogStatus, UpdateService) environments.POST("/:productName/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go new file mode 100644 index 00000000..64fe4fb2 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -0,0 +1,263 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/otiai10/copy" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + + "github.com/koderover/zadig/pkg/tool/log" + + "github.com/koderover/zadig/pkg/types" + + "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/util" + + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" + e "github.com/koderover/zadig/pkg/tool/errors" + "go.uber.org/zap" +) + +type HelmReleaseResp struct { + ReleaseName string `json:"releaseName"` + ServiceName string `json:"serviceName"` + Revision int `json:"revision"` + Chart string `json:"chart"` + AppVersion string `json:"appVersion"` +} + +type ChartInfo struct { + ServiceName string `json:"serviceName"` + Revision int64 `json:"revision"` +} + +type HelmChartsResp struct { + ChartInfos []*ChartInfo `json:"chartInfos"` + FileInfos []*types.FileInfo `json:"fileInfos"` +} + +func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmReleaseResp, error) { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, e.ErrListHelmReleases.AddDesc(err.Error()) + } + + restConfig, err := kube.GetRESTConfig(prod.ClusterID) + if err != nil { + log.Errorf("GetRESTConfig error: %v", err) + return nil, e.ErrListHelmReleases.AddDesc(err.Error()) + } + helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + if err != nil { + log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) + return nil, e.ErrListHelmReleases.AddErr(err) + } + + releases, err := helmClient.ListDeployedReleases() + if err != nil { + return nil, e.ErrListHelmReleases.AddErr(err) + } + + ret := make([]*HelmReleaseResp, 0, len(releases)) + for _, release := range releases { + + for _, singleFile := range release.Chart.Files { + log.Infof("###### single file name is %s", singleFile.Name) + log.Infof("###### single file data is %s", string(singleFile.Data)) + } + + for _, singleFile := range release.Chart.Templates { + log.Infof("###### single file name is %s", singleFile.Name) + log.Infof("###### single file data is %s", string(singleFile.Data)) + } + + ret = append(ret, &HelmReleaseResp{ + ReleaseName: release.Name, + ServiceName: util.ExtraServiceName(release.Name, prod.Namespace), + Revision: release.Version, + Chart: release.Chart.Name(), + AppVersion: release.Chart.AppVersion(), + }) + } + return ret, nil +} + +func loadChartFilesInfo(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { + base := config.LocalServicePathWithRevision(productName, serviceName, revision) + + var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) + if err != nil { + log.Warnf("failed to read chart info for service %s with revision %d", serviceName, revision) + base = config.LocalServicePath(productName, serviceName) + files, err = os.ReadDir(filepath.Join(base, serviceName, dir)) + if err != nil { + return nil, err + } + } + + for _, file := range files { + info, _ := file.Info() + if info == nil { + continue + } + fi := &types.FileInfo{ + Parent: dir, + Name: file.Name(), + Size: info.Size(), + Mode: file.Type(), + ModTime: info.ModTime().Unix(), + IsDir: file.IsDir(), + } + + fis = append(fis, fi) + } + return fis, nil +} + +//prepare chart version data +func prepareChartVersionData(productName string, serviceObj *models.Service) error { + serviceName, revision := serviceObj.ServiceName, serviceObj.Revision + base := config.LocalServicePathWithRevision(productName, serviceName, revision) + if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) + // use the latest version when it fails to download the specific version + base = config.LocalServicePath(productName, serviceName) + if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { + log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) + return err + } + } + + fullPath := filepath.Join(base, serviceObj.ServiceName) + deliveryChartPath := filepath.Join(config.LocalDeliveryChartPathWithRevision(productName, serviceObj.ServiceName, serviceObj.Revision), serviceObj.ServiceName) + err := copy.Copy(fullPath, deliveryChartPath) + if err != nil { + return nil + } + + // write values.yaml + if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte("this is fake data"), 0644); err != nil { + return err + } + + return nil +} + +func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLogger) (*HelmChartsResp, error) { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, e.ErrGetHelmCharts.AddErr(err) + } + + //restConfig, err := kube.GetRESTConfig(prod.ClusterID) + //if err != nil { + // log.Errorf("GetRESTConfig error: %v", err) + // return nil, e.ErrGetHelmCharts.AddDesc(err.Error()) + //} + //helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + //if err != nil { + // log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) + // return nil, e.ErrGetHelmCharts.AddErr(err) + //} + + allServiceMap := prod.GetServiceMap() + serviceMap := make(map[string]*models.ProductService) + if len(serviceName) > 0 { + serviceList := strings.Split(serviceName, ",") + // validate service exists in environment + for _, singleService := range serviceList { + if service, ok := allServiceMap[singleService]; ok { + serviceMap[service.ServiceName] = service + } else { + return nil, e.ErrGetHelmCharts.AddDesc(fmt.Sprintf("failed to find service %s in target namespace", singleService)) + } + } + } else { + serviceMap = allServiceMap + } + + if len(serviceMap) == 0 { + return nil, nil + } + + ret := &HelmChartsResp{ + ChartInfos: make([]*ChartInfo, 0), + FileInfos: make([]*types.FileInfo, 0), + } + + errList := new(multierror.Error) + wg := sync.WaitGroup{} + + for _, service := range serviceMap { + ret.ChartInfos = append(ret.ChartInfos, &ChartInfo{ + ServiceName: service.ServiceName, + Revision: service.Revision, + }) + wg.Add(1) + // download chart info with particular version + go func(serviceName string, revision int64) { + defer wg.Done() + serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Revision: revision, + Type: setting.HelmDeployType, + }) + if err != nil { + log.Errorf("failed to query services name: %s, revision: %d, error: %s", serviceName, revision, err) + errList = multierror.Append(errList, fmt.Errorf("failed to query service, serviceName: %s, revision: %d", serviceName, revision)) + return + } + err = prepareChartVersionData(productName, serviceObj) + if err != nil { + errList = multierror.Append(errList, fmt.Errorf("failed to prepare chart info for service %s", serviceObj.ServiceName)) + return + } + }(service.ServiceName, service.Revision) + } + wg.Wait() + + if errList.ErrorOrNil() != nil { + return nil, errList.ErrorOrNil() + } + + // expand file info for first service + serviceToExpand := ret.ChartInfos[0].ServiceName + fis, err := loadChartFilesInfo(productName, serviceToExpand, serviceMap[serviceToExpand].Revision, "") + if err != nil { + log.Errorf("Failed to load service file info, err: %s", err) + return nil, e.ErrListTemplate.AddErr(err) + } + ret.FileInfos = fis + + return ret, nil +} diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 0573b098..b0fb475b 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -46,13 +46,27 @@ func GetHelmServiceModule(c *gin.Context) { func GetFilePath(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = svcservice.GetFilePath(c.Param("serviceName"), c.Param("productName"), c.Query("dir"), ctx.Logger) + c.Query("revision") + revision, err := strconv.ParseInt(c.DefaultQuery("revision", "0"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") + return + } + ctx.Resp, ctx.Err = svcservice.GetFilePath(c.Param("serviceName"), c.Param("productName"), revision, c.Query("dir"), ctx.Logger) } func GetFileContent(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = svcservice.GetFileContent(c.Param("serviceName"), c.Param("productName"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) + + param := new(svcservice.GetFileContentParam) + err := c.ShouldBindQuery(param) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = svcservice.GetFileContent(c.Param("serviceName"), c.Param("productName"), param, ctx.Logger) } func CreateOrUpdateHelmService(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index cc91441b..a915bf29 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -113,6 +113,13 @@ type ChartTemplateData struct { DefaultValuesYAML []byte // content of values.yaml in template } +type GetFileContentParam struct { + FilePath string `json:"filePath" form:"filePath"` + FileName string `json:"fileName" form:"fileName"` + Revision int64 `json:"revision" form:"revision"` + DeliveryVersion bool `json:"deliveryVersion" form:"deliveryVersion"` +} + func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { helmService := &HelmService{ ServiceInfos: []*models.Service{}, @@ -133,7 +140,7 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, helmService.ServiceInfos = services if len(services) > 0 { - fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, "") + fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, 0, "") if err != nil { log.Errorf("Failed to load service file info, err: %s", err) return nil, e.ErrListTemplate.AddErr(err) @@ -172,24 +179,45 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * return helmServiceModule, err } -func GetFilePath(serviceName, productName, dir string, _ *zap.SugaredLogger) ([]*types.FileInfo, error) { - return loadServiceFileInfos(productName, serviceName, dir) +func GetFilePath(serviceName, productName string, revision int64, dir string, _ *zap.SugaredLogger) ([]*types.FileInfo, error) { + return loadServiceFileInfos(productName, serviceName, revision, dir) } -func GetFileContent(serviceName, productName, filePath, fileName string, log *zap.SugaredLogger) (string, error) { - base := config.LocalServicePath(productName, serviceName) - +func GetFileContent(serviceName, productName string, param *GetFileContentParam, log *zap.SugaredLogger) (string, error) { + filePath, fileName, revision, forDelivery := param.FilePath, param.FileName, param.Revision, param.DeliveryVersion svc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, + Revision: revision, }) if err != nil { return "", e.ErrFileContent.AddDesc(err.Error()) } - err = commonservice.PreLoadServiceManifests(base, svc) - if err != nil { - return "", e.ErrFileContent.AddDesc(err.Error()) + base := config.LocalServicePath(productName, serviceName) + if revision > 0 { + base = config.LocalServicePathWithRevision(productName, serviceName, revision) + if err = commonservice.PreloadServiceManifestsByRevision(base, svc); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + svc.Revision, svc.ServiceName) + } + } + if err != nil || revision == 0 { + base = config.LocalServicePath(productName, serviceName) + err = commonservice.PreLoadServiceManifests(base, svc) + if err != nil { + return "", e.ErrFileContent.AddDesc(err.Error()) + } + } + + if forDelivery { + fullPath := filepath.Join(base, serviceName) + base = config.LocalDeliveryChartPathWithRevision(productName, serviceName, revision) + deliveryChartPath := filepath.Join(base, serviceName) + err := copy.Copy(fullPath, deliveryChartPath) + if err != nil { + return "", e.ErrFileContent.AddErr(err) + } } file := filepath.Join(base, serviceName, filePath, fileName) @@ -863,9 +891,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return serviceObj, nil } -func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileInfo, error) { - base := config.LocalServicePath(productName, serviceName) - +func loadServiceFileInfos(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { svc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, @@ -874,6 +900,22 @@ func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileIn return nil, e.ErrFilePath.AddDesc(err.Error()) } + base := config.LocalServicePath(productName, serviceName) + if revision > 0 { + base = config.LocalServicePathWithRevision(productName, serviceName, revision) + if err = commonservice.PreloadServiceManifestsByRevision(base, svc); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + svc.Revision, svc.ServiceName) + } + } + if err != nil || revision == 0 { + base = config.LocalServicePath(productName, serviceName) + err = commonservice.PreLoadServiceManifests(base, svc) + if err != nil { + return nil, e.ErrFilePath.AddDesc(err.Error()) + } + } + err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 45554ea7..e0ffc2ee 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -680,4 +680,10 @@ var ( ErrUpdateExternalLink = NewHTTPError(6842, "更新链接失败") ErrDeleteExternalLink = NewHTTPError(6843, "删除链接失败") ErrListExternalLink = NewHTTPError(6844, "获取链接列表失败") + + //----------------------------------------------------------------------------------------------- + // helm releated Error Range: 6850 - 6869 + //----------------------------------------------------------------------------------------------- + ErrListHelmReleases = NewHTTPError(6850, "获取release失败") + ErrGetHelmCharts = NewHTTPError(6851, "获取chart信息失败") ) -- Gitee From a666e75e90079cad6edb1d5351b0b9a8df2f2b3f Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 16 Nov 2021 22:37:09 +0800 Subject: [PATCH 0685/2647] add filter for workflow list Signed-off-by: lou --- .../core/common/repository/models/workflow.go | 7 - .../core/common/repository/mongodb/task.go | 58 +++++ .../common/repository/mongodb/workflow.go | 9 +- .../repository/mongodb/workflow_stat.go | 11 +- .../core/common/service/template_product.go | 2 +- .../aslan/core/common/service/workflow.go | 2 +- .../aslan/core/workflow/handler/workflow.go | 7 +- .../workflow/service/workflow/nsq_handlers.go | 2 +- .../workflow/service/workflow/service_task.go | 2 +- .../workflow/service/workflow/workflow.go | 140 ++++++++---- pkg/microservice/cron/core/service/testing.go | 1 - pkg/microservice/cron/core/service/types.go | 212 +----------------- .../picket/client/aslan/workflow.go | 11 + .../picket/core/filter/handler/router.go | 1 + .../picket/core/filter/handler/workflow.go | 8 + .../picket/core/filter/service/workflow.go | 45 +++- 16 files changed, 238 insertions(+), 280 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index a05cc853..3cd5e836 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -49,13 +49,6 @@ type Workflow struct { DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` - IsFavorite bool `bson:"-" json:"is_favorite"` - LastestTask *TaskInfo `bson:"-" json:"lastest_task"` - LastSucessTask *TaskInfo `bson:"-" json:"last_task_success"` - LastFailureTask *TaskInfo `bson:"-" json:"last_task_failure"` - TotalDuration int64 `bson:"-" json:"total_duration"` - TotalNum int `bson:"-" json:"total_num"` - TotalSuccess int `bson:"-" json:"total_success"` // ResetImage indicate whether reset image to original version after completion ResetImage bool `json:"reset_image" bson:"reset_image"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index 2640d1cc..6a845632 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -34,6 +34,7 @@ import ( type ListTaskOption struct { PipelineName string + PipelineNames []string Status config.Status Team string TeamID int @@ -191,6 +192,63 @@ func (c *TaskColl) FindLatestTask(args *FindTaskOption) (*task.Task, error) { return res, err } +type TaskInfo struct { + ID taskGrouped `bson:"_id"` + TaskID int64 `bson:"task_id"` + PipelineName string `bson:"pipeline_name"` + Status string `bson:"status"` +} + +type taskGrouped struct { + PipelineName string `bson:"pipeline_name"` + Type config.PipelineType `bson:"type"` +} + +func (c *TaskColl) ListRecentTasks(args *ListTaskOption) ([]*TaskInfo, error) { + query := bson.M{"is_deleted": false} + if args.Type != "" { + query["type"] = args.Type + } + if args.Status != "" { + query["status"] = args.Status + } + if len(args.PipelineNames) > 0 { + query["pipeline_name"] = bson.M{"$in": args.PipelineNames} + } + + pipeline := []bson.M{ + { + "$match": query, + }, + { + "$sort": bson.M{"task_id": -1}, + }, + { + "$group": bson.M{ + "_id": bson.D{ + {"pipeline_name", "$pipeline_name"}, + {"type", "$type"}, + }, + "pipeline_name": bson.M{"$first": "$pipeline_name"}, + "task_id": bson.M{"$first": "$task_id"}, + "status": bson.M{"$first": "$status"}, + }, + }, + } + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + + res := make([]*TaskInfo, 0) + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + + return res, err +} + func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) { ret = make([]*TaskPreview, 0) if option == nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index edd7015c..f874a6fa 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -31,9 +31,8 @@ import ( ) type ListWorkflowOption struct { - ProductName string - IsSort bool - TestName string + IsSort bool + Projects []string } type WorkflowColl struct { @@ -64,8 +63,8 @@ func (c *WorkflowColl) EnsureIndex(ctx context.Context) error { func (c *WorkflowColl) List(opt *ListWorkflowOption) ([]*models.Workflow, error) { query := bson.M{} - if opt.ProductName != "" { - query["product_tmpl_name"] = opt.ProductName + if len(opt.Projects) > 0 { + query["product_tmpl_name"] = bson.M{"$in": opt.Projects} } resp := make([]*models.Workflow, 0) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_stat.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_stat.go index d0d65ee8..29133d35 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_stat.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_stat.go @@ -93,8 +93,8 @@ func (c *WorkflowStatColl) Delete(name, workflowType string) error { } type WorkflowStatArgs struct { - Name string `json:"name"` - Type string `json:"type"` + Names []string + Type string } func (c *WorkflowStatColl) FindWorkflowStat(args *WorkflowStatArgs) ([]*models.WorkflowStat, error) { @@ -104,13 +104,12 @@ func (c *WorkflowStatColl) FindWorkflowStat(args *WorkflowStatArgs) ([]*models.W } query := bson.M{} - if args.Name != "" { - query["name"] = args.Name - } - if args.Type != "" { query["type"] = args.Type } + if len(args.Names) > 0 { + query["name"] = bson.M{"$in": args.Names} + } ctx := context.Background() diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index 5ae1cd62..df2446df 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -109,7 +109,7 @@ func GetProductTemplate(productName string, log *zap.SugaredLogger) (*template.P return resp, fmt.Errorf("Product.List err : %v", err) } - totalWorkflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{ProductName: productName, IsSort: true}) + totalWorkflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{Projects: []string{productName}, IsSort: true}) if err != nil { return resp, fmt.Errorf("Workflow.List err : %v", err) } diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 023d7c98..6592413e 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -35,7 +35,7 @@ import ( ) func DeleteWorkflows(productName, requestID string, log *zap.SugaredLogger) error { - workflows, err := mongodb.NewWorkflowColl().List(&mongodb.ListWorkflowOption{ProductName: productName}) + workflows, err := mongodb.NewWorkflowColl().List(&mongodb.ListWorkflowOption{Projects: []string{productName}}) if err != nil { log.Errorf("Workflow.List error: %v", err) return fmt.Errorf("DeleteWorkflows productName %s Workflow.List error: %v", productName, err) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index 9f69a055..664d0270 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -118,7 +118,12 @@ func ListWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.ListWorkflows(c.Query("type"), c.Query("projectName"), ctx.UserID, ctx.Logger) + projects := c.QueryArray("projects") + if c.Query("projectName") != "" { + projects = []string{c.Query("projectName")} + } + + ctx.Resp, ctx.Err = workflow.ListWorkflows(projects, ctx.UserID, ctx.Logger) } func ListTestWorkflows(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 5837a6b9..f81f6318 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -894,7 +894,7 @@ func (h *TaskNotificationHandler) HandleMessage(message *nsq.Message) error { } func upsertWorkflowStat(args *commonmodels.WorkflowStat, log *zap.SugaredLogger) error { - if workflowStats, _ := commonrepo.NewWorkflowStatColl().FindWorkflowStat(&commonrepo.WorkflowStatArgs{Name: args.Name, Type: args.Type}); len(workflowStats) > 0 { + if workflowStats, _ := commonrepo.NewWorkflowStatColl().FindWorkflowStat(&commonrepo.WorkflowStatArgs{Names: []string{args.Name}, Type: args.Type}); len(workflowStats) > 0 { currentWorkflowStat := workflowStats[0] currentWorkflowStat.TotalDuration += args.TotalDuration currentWorkflowStat.TotalSuccess += args.TotalSuccess diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index b15a1f18..d55f83d7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -90,7 +90,7 @@ func ListServiceWorkflows(productName, envName, serviceName, serviceType string, } // 获取支持升级此服务的产品工作流 - workflowOpt := &commonrepo.ListWorkflowOption{ProductName: productName} + workflowOpt := &commonrepo.ListWorkflowOption{Projects: []string{productName}} workflows, err := commonrepo.NewWorkflowColl().List(workflowOpt) if err != nil { log.Errorf("Workflow.List failed, productName:%s, serviceName:%s, serviceType:%s, err:%v", productName, serviceName, serviceType, err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 98387c82..db825951 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -45,6 +45,28 @@ type EnvStatus struct { ErrMessage string `json:"err_message"` } +type Workflow struct { + Name string `json:"name"` + ProjectName string `json:"projectName"` + UpdateTime int64 `json:"updateTime"` + CreateTime int64 `json:"createTime"` + Schedules *commonmodels.ScheduleCtrl `json:"schedules,omitempty"` + SchedulerEnabled bool `json:"schedulerEnabled"` + EnabledStages []string `json:"enabledStages"` + IsFavorite bool `json:"isFavorite"` + RecentTask *TaskInfo `json:"recentTask"` + RecentSuccessfulTask *TaskInfo `json:"recentSuccessfulTask"` + RecentFailedTask *TaskInfo `json:"recentFailedTask"` + AverageExecutionTime float64 `json:"averageExecutionTime"` + SuccessRate float64 `json:"successRate"` +} + +type TaskInfo struct { + TaskID int64 `json:"taskID"` + PipelineName string `json:"pipelineName"` + Status string `json:"status"` +} + type workflowCreateArg struct { name string envName string @@ -476,70 +498,110 @@ func validateWorkflowHookNames(w *commonmodels.Workflow) error { return validateHookNames(names) } -func ListWorkflows(queryType string, productName string, userID string, log *zap.SugaredLogger) ([]*commonmodels.Workflow, error) { - workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{ProductName: productName}) +func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([]*Workflow, error) { + workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{Projects: projects}) if err != nil { - log.Errorf("Workflow.List error: %v", err) - return workflows, e.ErrListWorkflow.AddDesc(err.Error()) + log.Errorf("Failed to list workflows, err: %s", err) + return nil, e.ErrListWorkflow.AddDesc(err.Error()) + } + + var workflowNames []string + var res []*Workflow + for _, w := range workflows { + stages := make([]string, 0, 4) + if w.BuildStage != nil && w.BuildStage.Enabled { + stages = append(stages, "build") + } + if w.ArtifactStage != nil && w.ArtifactStage.Enabled { + stages = append(stages, "artifact") + } + if w.TestStage != nil && w.TestStage.Enabled { + stages = append(stages, "test") + } + if w.DistributeStage != nil && w.DistributeStage.Enabled { + stages = append(stages, "distribute") + } + + res = append(res, &Workflow{ + Name: w.Name, + ProjectName: w.ProductTmplName, + UpdateTime: w.UpdateTime, + CreateTime: w.CreateTime, + SchedulerEnabled: w.ScheduleEnabled, + Schedules: w.Schedules, + EnabledStages: stages, + }) + + workflowNames = append(workflowNames, w.Name) } favorites, err := commonrepo.NewFavoriteColl().List(&commonrepo.FavoriteArgs{UserID: userID, Type: string(config.WorkflowType)}) if err != nil { - log.Errorf("list favorite error: %v", err) - return workflows, e.ErrListFavorite + log.Warnf("Failed to list favorites, err: %s", err) + } + favoriteSet := sets.NewString() + for _, f := range favorites { + favoriteSet.Insert(f.Name) } - workflowStats, err := commonrepo.NewWorkflowStatColl().FindWorkflowStat(&commonrepo.WorkflowStatArgs{Type: string(config.WorkflowType)}) + workflowStats, err := commonrepo.NewWorkflowStatColl().FindWorkflowStat(&commonrepo.WorkflowStatArgs{Names: workflowNames, Type: string(config.WorkflowType)}) if err != nil { - log.Errorf("list workflow stat error: %v", err) - return workflows, fmt.Errorf("列出工作流统计失败") + log.Warnf("Failed to list workflow stats, err: %s", err) + } + workflowStatMap := make(map[string]*commonmodels.WorkflowStat) + for _, s := range workflowStats { + workflowStatMap[s.Name] = s } - for _, workflow := range workflows { - if queryType == "artifact" { - if workflow.ArtifactStage == nil || !workflow.ArtifactStage.Enabled { - continue - } + recentTaskMap := getTaskMap(workflowNames, "", log) + recentSuccessfulTaskMap := getTaskMap(workflowNames, config.StatusPassed, log) + recentFailedTaskMap := getTaskMap(workflowNames, config.StatusFailed, log) + for _, r := range res { + if t, ok := recentTaskMap[r.Name]; ok { + r.RecentTask = t } - latestTask, _ := commonrepo.NewTaskColl().FindLatestTask(&commonrepo.FindTaskOption{PipelineName: workflow.Name, Type: config.WorkflowType}) - if latestTask != nil { - workflow.LastestTask = &commonmodels.TaskInfo{TaskID: latestTask.TaskID, PipelineName: latestTask.PipelineName, Status: latestTask.Status} + if t, ok := recentSuccessfulTaskMap[r.Name]; ok { + r.RecentSuccessfulTask = t } - - latestPassedTask, _ := commonrepo.NewTaskColl().FindLatestTask(&commonrepo.FindTaskOption{PipelineName: workflow.Name, Type: config.WorkflowType, Status: config.StatusPassed}) - if latestPassedTask != nil { - workflow.LastSucessTask = &commonmodels.TaskInfo{TaskID: latestPassedTask.TaskID, PipelineName: latestPassedTask.PipelineName} + if t, ok := recentFailedTaskMap[r.Name]; ok { + r.RecentFailedTask = t } - latestFailedTask, _ := commonrepo.NewTaskColl().FindLatestTask(&commonrepo.FindTaskOption{PipelineName: workflow.Name, Type: config.WorkflowType, Status: config.StatusFailed}) - if latestFailedTask != nil { - workflow.LastFailureTask = &commonmodels.TaskInfo{TaskID: latestFailedTask.TaskID, PipelineName: latestFailedTask.PipelineName} + if favoriteSet.Has(r.Name) { + r.IsFavorite = true } - workflow.IsFavorite = IsFavoriteWorkflow(workflow, favorites) + if s, ok := workflowStatMap[r.Name]; ok { + total := float64(s.TotalSuccess + s.TotalFailure) + successful := float64(s.TotalSuccess) + totalDuration := float64(s.TotalDuration) - workflow.TotalDuration, workflow.TotalNum, workflow.TotalSuccess = findWorkflowStat(workflow, workflowStats) + r.AverageExecutionTime = totalDuration / total + r.SuccessRate = successful / total + } } - return workflows, nil + return res, nil } -func IsFavoriteWorkflow(workflow *commonmodels.Workflow, favorites []*commonmodels.Favorite) bool { - for _, favorite := range favorites { - if workflow.Name == favorite.Name && workflow.ProductTmplName == favorite.ProductName { - return true - } +func getTaskMap(names []string, status config.Status, logger *zap.SugaredLogger) map[string]*TaskInfo { + res := make(map[string]*TaskInfo) + + tasks, err := commonrepo.NewTaskColl().ListRecentTasks(&commonrepo.ListTaskOption{PipelineNames: names, Type: config.WorkflowType, Status: status}) + if err != nil { + logger.Warnf("Failed to list tasks, err: %s", err) + return res } - return false -} -func findWorkflowStat(workflow *commonmodels.Workflow, workflowStats []*commonmodels.WorkflowStat) (int64, int, int) { - for _, workflowStat := range workflowStats { - if workflow.Name == workflowStat.Name { - return workflowStat.TotalDuration, workflowStat.TotalSuccess + workflowStat.TotalFailure, workflowStat.TotalSuccess + for _, t := range tasks { + res[t.PipelineName] = &TaskInfo{ + TaskID: t.TaskID, + PipelineName: t.PipelineName, + Status: t.Status, } } - return 0, 0, 0 + + return res } func ListTestWorkflows(testName string, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { diff --git a/pkg/microservice/cron/core/service/testing.go b/pkg/microservice/cron/core/service/testing.go index 9dc549fa..58fa1193 100644 --- a/pkg/microservice/cron/core/service/testing.go +++ b/pkg/microservice/cron/core/service/testing.go @@ -26,6 +26,5 @@ type TestingOpt struct { ExecuteNum int `json:"execute_num,omitempty"` PassRate float64 `json:"pass_rate,omitempty"` AvgDuration float64 `json:"avg_duration,omitempty"` - Workflows []*Workflow `json:"workflows,omitempty"` Schedules *ScheduleCtrl `json:"schedules,omitempty"` } diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index f1c5c917..33079be5 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -423,23 +423,12 @@ type RenderKV struct { // KeyState can be new, deleted, normal type KeyState string -const ( - // KeyStateNew ... - KeyStateNew = KeyState("new") - // KeyStateUnused ... - KeyStateUnused = KeyState("unused") - // KeyStatePresent ... - KeyStatePresent = KeyState("present") -) - type Pipeline struct { - // ID is auto generated by mgo, ready only - ID primitive.ObjectID `bson:"_id" json:"id"` - Name string `bson:"name" json:"name"` - Type PipelineType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TeamName string `bson:"team" json:"team"` - ProductName string `bson:"product_name" json:"product_name"` + Name string `bson:"name" json:"name"` + Type PipelineType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TeamName string `bson:"team" json:"team"` + ProductName string `bson:"product_name" json:"product_name"` // target 服务名称, k8s为容器名称, 物理机为服务名 Target string `bson:"target" json:"target"` // 使用预定义编译管理模块中的内容生成SubTasks, @@ -452,17 +441,11 @@ type Pipeline struct { CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` UpdateTime int64 `bson:"update_time" json:"update_time,omitempty"` Schedules ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` - Hook *Hook `bson:"hook,omitempty" json:"hook,omitempty"` Notifiers []string `bson:"notifiers,omitempty" json:"notifiers,omitempty"` RunCount int `bson:"run_count,omitempty" json:"run_count,omitempty"` DailyRunCount float32 `bson:"daily_run_count,omitempty" json:"daily_run_count,omitempty"` PassRate float32 `bson:"pass_rate,omitempty" json:"pass_rate,omitempty"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - Slack *Slack `bson:"slack" json:"slack"` - NotifyCtl *NotifyCtl `bson:"notify_ctl" json:"notify_ctl"` - IsFavorite bool `bson:"-" json:"is_favorite"` - // 是否允许同时运行多次 - MultiRun bool `bson:"multi_run" json:"multi_run"` } // ScheduleCtrl ... @@ -487,188 +470,7 @@ const ( ServiceType = PipelineType("service") ) -type Hook struct { - Enabled bool `bson:"enabled" json:"enabled"` - GitHooks []GitHook `bson:"git_hooks" json:"git_hooks,omitempty"` -} - -// GitHook Events: push, pull_request -// MatchFolders: 包含目录或者文件后缀 -// 以!开头的目录或者后缀名为不运行pipeline的过滤条件 -type GitHook struct { - Owner string `bson:"repo_owner" json:"repo_owner"` - Repo string `bson:"repo" json:"repo"` - Branch string `bson:"branch" json:"branch"` - Events []string `bson:"events" json:"events"` - MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` - AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` -} - -type Slack struct { - Channel string `bson:"channel" json:"channel"` - Enabled bool `bson:"enabled" json:"enabled"` - Notifiers []string `bson:"notifiers" json:"notifiers"` - NotifyType SlackNotifyType `bson:"notify_type" json:"notify_type"` -} - -// SlackNotifyType ... -type SlackNotifyType string - -const ( - // SlackAll ... - SlackAll = SlackNotifyType("all") - // SlackOnChange ... - SlackOnChange = SlackNotifyType("onchange") - // SlackOnfailure ... - SlackOnfailure = SlackNotifyType("onfailure") -) - -type NotifyCtl struct { - Enabled bool `bson:"enabled" json:"enabled"` - WebHookType string `bson:"webhook_type" json:"webhook_type"` - WeChatWebHook string `bson:"weChat_webHook,omitempty" json:"weChat_webHook,omitempty"` - DingDingWebHook string `bson:"dingding_webhook,omitempty" json:"dingding_webhook,omitempty"` - FeiShuWebHook string `bson:"feishu_webhook,omitempty" json:"feishu_webhook,omitempty"` - AtMobiles []string `bson:"at_mobiles,omitempty" json:"at_mobiles,omitempty"` - IsAtAll bool `bson:"is_at_all,omitempty" json:"is_at_all,omitempty"` - NotifyTypes []string `bson:"notify_type" json:"notify_type"` -} - type Workflow struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Enabled bool `bson:"enabled" json:"enabled"` - ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - EnvName string `bson:"env_name" json:"env_name,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - UpdateBy string `bson:"update_by" json:"update_by,omitempty"` - CreateBy string `bson:"create_by" json:"create_by,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - CreateTime int64 `bson:"create_time" json:"create_time"` - Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` - ScheduleEnabled bool `bson:"schedule_enabled" json:"schedule_enabled"` - Slack *Slack `bson:"slack,omitempty" json:"slack,omitempty"` - BuildStage *BuildStage `bson:"build_stage" json:"build_stage"` - ArtifactStage *ArtifactStage `bson:"artifact_stage,omitempty" json:"artifact_stage,omitempty"` - TestStage *TestStage `bson:"test_stage" json:"test_stage"` - SecurityStage *SecurityStage `bson:"security_stage" json:"security_stage"` - DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` - NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` - HookCtl *HookCtrl `bson:"hook_ctl" json:"hook_ctl"` - IsFavorite bool `bson:"-" json:"is_favorite"` - LastestTask *TaskInfo `bson:"-" json:"lastest_task"` - LastSucessTask *TaskInfo `bson:"-" json:"last_task_success"` - LastFailureTask *TaskInfo `bson:"-" json:"last_task_failure"` - TotalDuration int64 `bson:"-" json:"total_duration"` - TotalNum int `bson:"-" json:"total_num"` - TotalSuccess int `bson:"-" json:"total_success"` - - // ResetImage indicate whether reset image to original version after completion - ResetImage bool `json:"reset_image" bson:"reset_image"` - // IsParallel 控制单一工作流的任务是否支持并行处理 - IsParallel bool `json:"is_parallel" bson:"is_parallel"` -} - -// TaskInfo ... -type TaskInfo struct { - TaskID int64 `bson:"task_id" json:"task_id"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Status Status `bson:"status" json:"status"` -} - -type Status string - -type BuildStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - Modules []*BuildModule `bson:"modules" json:"modules"` -} - -type BuildModule struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` - BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` -} - -type ServiceModuleTarget struct { - ProductName string `bson:"product_name" json:"product_name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` -} - -type ArtifactStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - Modules []*ArtifactModule `bson:"modules" json:"modules"` -} - -type ArtifactModule struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` + Name string `json:"name"` + Schedules *ScheduleCtrl `json:"schedules,omitempty"` } - -type TestStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - TestNames []string `bson:"test_names,omitempty" json:"test_names,omitempty"` - Tests []*TestExecArgs `bson:"tests,omitempty" json:"tests,omitempty"` -} - -type TestExecArgs struct { - Name string `bson:"test_name" json:"test_name"` - Envs []*KeyVal `bson:"envs" json:"envs"` -} - -type SecurityStage struct { - Enabled bool `bson:"enabled" json:"enabled"` -} - -type DistributeStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - S3StorageID string `bson:"s3_storage_id" json:"s3_storage_id"` - ImageRepo string `bson:"image_repo" json:"image_repo"` - JumpBoxHost string `bson:"jump_box_host" json:"jump_box_host"` - Distributes []*ProductDistribute `bson:"distributes" json:"distributes"` - - // repos to release images - Releases []RepoImage `bson:"releases" json:"releases"` -} - -type RepoImage struct { - RepoID string `json:"repo_id" bson:"repo_id"` - Name string `json:"name" bson:"name" yaml:"name"` - Username string `json:"-" yaml:"username"` - Password string `json:"-" yaml:"password"` - Host string `json:"host" yaml:"host"` - Namespace string `json:"namespace" yaml:"namespace"` -} - -type ProductDistribute struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` - ImageDistribute bool `bson:"image_distribute" json:"image_distribute"` - JumpBoxDistribute bool `bson:"jump_box_distribute" json:"jump_box_distribute"` - QstackDistribute bool `bson:"qstack_distribute" json:"qstack_distribute"` -} - -type HookCtrl struct { - Enabled bool `bson:"enabled" json:"enabled"` - Items []*WorkflowHook `bson:"items" json:"items"` -} - -type WorkflowHook struct { - AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` - CheckPatchSetChange bool `bson:"check_patch_set_change" json:"check_patch_set_change"` - MainRepo MainHookRepo `bson:"main_repo" json:"main_repo"` - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` -} - -type MainHookRepo struct { - Source string `bson:"source,omitempty" json:"source,omitempty"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoName string `bson:"repo_name" json:"repo_name"` - Branch string `bson:"branch" json:"branch"` - MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` - Events []HookEventType `bson:"events" json:"events"` - Label string `bson:"label" json:"label"` - Revision string `bson:"revision" json:"revision"` -} - -type HookEventType string diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 5bd8014c..110cefd9 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -9,6 +9,17 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +func (c *Client) ListWorkflows(header http.Header, qs url.Values) ([]byte, error) { + url := "/workflow/workflow" + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} + func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.Values) ([]byte, error) { url := fmt.Sprintf("/workflow/workflow/testName/%s", testName) diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 421fdc1a..29063a25 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -34,6 +34,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflows := router.Group("workflows") { workflows.GET("testName/:testName", ListTestWorkflows) + workflows.GET("", ListWorkflows) } rolebindings := router.Group("rolebindings") diff --git a/pkg/microservice/picket/core/filter/handler/workflow.go b/pkg/microservice/picket/core/filter/handler/workflow.go index 2a93f977..3f769137 100644 --- a/pkg/microservice/picket/core/filter/handler/workflow.go +++ b/pkg/microservice/picket/core/filter/handler/workflow.go @@ -10,6 +10,14 @@ import ( func ListTestWorkflows(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + testName := c.Param("testName") ctx.Resp, ctx.Err = service.ListTestWorkflows(testName, c.Request.Header, c.Request.URL.Query(), ctx.Logger) } + +func ListWorkflows(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListWorkflows(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index d2550025..14a2dbcf 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -11,12 +11,38 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/client/opa" ) +type rule struct { + method string + endpoint string +} + +func ListWorkflows(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { + rules := []*rule{{ + method: "/api/aslan/workflow/workflow", + endpoint: "GET", + }} + names, err := getAllowedProjects(header, rules, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + if len(names) == 0 { + return nil, nil + } + if !(len(names) == 1 && names[0] == "*") { + for _, name := range names { + qs.Add("projects", name) + } + } + return aslan.New().ListWorkflows(header, qs) +} + func ListTestWorkflows(testName string, header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { rules := []*rule{{ method: "/api/aslan/workflow/workflow", endpoint: "PUT", }} - names, err := getAllowedProjectsWithUpdateWorkflowsPermission(header, rules, logger) + names, err := getAllowedProjects(header, rules, logger) if err != nil { logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err @@ -32,26 +58,21 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge return aslan.New().ListTestWorkflows(testName, header, qs) } -type rule struct { - method string - endpoint string -} - -func getAllowedProjectsWithUpdateWorkflowsPermission(headers http.Header, rules []*rule, logger *zap.SugaredLogger) (projects []string, err error) { - resps := [][]string{} +func getAllowedProjects(headers http.Header, rules []*rule, logger *zap.SugaredLogger) (projects []string, err error) { + var res [][]string for _, v := range rules { - res := &allowedProjectsData{} + allowedProjects := &allowedProjectsData{} opaClient := opa.NewDefault() - err := opaClient.Evaluate("rbac.user_allowed_projects", res, func() (*opa.Input, error) { + err := opaClient.Evaluate("rbac.user_allowed_projects", allowedProjects, func() (*opa.Input, error) { return generateOPAInput(headers, v.method, v.endpoint), nil }) if err != nil { logger.Errorf("opa evaluation failed, err: %s", err) return nil, err } - resps = append(resps, res.Result) + res = append(res, allowedProjects.Result) } - return intersect(resps), nil + return intersect(res), nil } func intersect(s [][]string) []string { -- Gitee From a1d1cdaaebdf120d8011f5be6270b49e30a80741 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 17 Nov 2021 10:11:59 +0800 Subject: [PATCH 0686/2647] fix rollback issue Signed-off-by: lou --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 10 +++++----- .../internal/repository/models/codehost.go | 7 +++++-- .../internal/repository/mongodb/codehost.go | 12 +++++++----- .../core/codehost/repository/mongodb/codehost.go | 4 ++++ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 677d5c1b..efe7e3c5 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -17,6 +17,7 @@ limitations under the License. package migrate import ( + internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -25,7 +26,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/log" ) @@ -86,7 +86,7 @@ func refreshWebHookSecret(secret string) error { return err } - codeHosts, err := systemconfig.New().ListCodeHosts() + codeHosts, err := internalmongodb.NewCodehostColl().List() if err != nil { log.Errorf("Failed to list codehosts, err: %s", err) return err @@ -123,7 +123,7 @@ func refreshWebHookSecret(secret string) error { return nil } -func getCodeHostByAddressAndOwner(address, owner string, all []*systemconfig.CodeHost) *systemconfig.CodeHost { +func getCodeHostByAddressAndOwner(address, owner string, all []*internalmodels.CodeHost) *internalmodels.CodeHost { for _, one := range all { if one.Address != address { continue @@ -143,7 +143,7 @@ func getCodeHostByAddressAndOwner(address, owner string, all []*systemconfig.Cod // change type "1,2,3,4" to "github,gitlab..." func changeCodehostType() error { // get all codehosts - codeHosts, err := internalmongodb.NewCodehostColl().ListCodeHosts() + codeHosts, err := internalmongodb.NewCodehostColl().List() if err != nil { log.Errorf("fail to list codehosts, err: %s", err) return err @@ -162,7 +162,7 @@ func changeCodehostType() error { // rollback type "github,gitlab..." to "1,2..." func rollbackCodehostType() error { // get all codehosts - codeHosts, err := internalmongodb.NewCodehostColl().ListCodeHosts() + codeHosts, err := internalmongodb.NewCodehostColl().List() if err != nil { log.Errorf("fail to list codehosts, err: %s", err) return err diff --git a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go index 57945fa6..8afdbed1 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go @@ -1,8 +1,11 @@ package models type CodeHost struct { - ID int `bson:"id" json:"id"` - Type string `bson:"type" json:"type"` + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` + Address string `bson:"address" json:"address"` + Namespace string `bson:"namespace" json:"namespace"` + AccessToken string `bson:"access_token" json:"accessToken"` } func (CodeHost) TableName() string { diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go index b308b11d..757b5924 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go @@ -3,12 +3,13 @@ package mongodb import ( "context" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" ) func (c *CodehostColl) ChangeType(ID int, sourceType string) error { @@ -66,10 +67,10 @@ func (c *CodehostColl) RollbackType(ID int, sourceType string) error { return nil } -func (c *CodehostColl) ListCodeHosts() ([]*models.CodeHost, error) { +func (c *CodehostColl) List() ([]*models.CodeHost, error) { codeHosts := make([]*models.CodeHost, 0) - - cursor, err := c.Collection.Find(context.TODO(), bson.M{}) + query := bson.M{"deleted_at": 0} + cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { return nil, err } @@ -77,6 +78,7 @@ func (c *CodehostColl) ListCodeHosts() ([]*models.CodeHost, error) { if err != nil { return nil, err } + return codeHosts, nil } diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 5287ecf6..411f685b 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -76,6 +76,10 @@ func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { codeHosts := make([]*models.CodeHost, 0) query := bson.M{"deleted_at": 0} + + if args == nil { + args = &ListArgs{} + } if args.Address != "" { query["address"] = args.Address } -- Gitee From a4288abadd54059778a481363bca493bc98412c6 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 17 Nov 2021 10:27:33 +0800 Subject: [PATCH 0687/2647] clean code Signed-off-by: lou --- .../policy/core/service/bundle/rego/authz.rego | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index b3c0997d..45fd16e0 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -73,13 +73,6 @@ access_is_granted { glob.match(trim(grant.endpoint, "/"), ["/"], concat("/", input.parsed_path)) } -# Temporarily skip this endpoint, it will be fixed in 1.7.1 -access_is_granted { - concat("/", input.parsed_path) == "api/aslan/workflow/workflow" - http_request.method == "GET" - project_name == "" -} - user_is_admin { some role all_roles[role] -- Gitee From f5b15bb0675b6f8018310a2c51ba14d83246dba2 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 17 Nov 2021 20:15:53 +0800 Subject: [PATCH 0688/2647] improve rollback order and add more logs Signed-off-by: lou --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index efe7e3c5..c8329c78 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -38,12 +38,14 @@ func init() { func V160ToV170() error { log.Info("Migrating data from 1.6.0 to 1.7.0") + log.Info("Start to change codeHost type") err := changeCodehostType() if err != nil { log.Errorf("Failed to change codehost type, err: %s", err) return err } + log.Info("Start to refresh all webhook secrets") err = refreshWebHookSecret(gitservice.GetHookSecret()) if err != nil { log.Errorf("Failed to refresh webhook secret, err: %s", err) @@ -55,23 +57,26 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") - err := rollbackCodehostType() - if err != nil { - log.Errorf("Failed to rollback codehost type, err: %s", err) - return err - } + token := getWebHookTokenFromOrganization() if token == "" { return nil } log.Info("Start to rollback all webhook secrets") - err = refreshWebHookSecret(token) + err := refreshWebHookSecret(token) if err != nil { log.Errorf("Failed to refresh webhook secret, err: %s", err) return err } + log.Info("Start to rollback codeHost type") + err = rollbackCodehostType() + if err != nil { + log.Errorf("Failed to rollback codehost type, err: %s", err) + return err + } + return nil } -- Gitee From 0d1ac78fae7deb3b240e6418fbb9bdc2582410c3 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 17 Nov 2021 20:25:32 +0800 Subject: [PATCH 0689/2647] add updateBy Signed-off-by: lou --- .../aslan/core/workflow/service/workflow/workflow.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index db825951..a7e2af10 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -50,6 +50,7 @@ type Workflow struct { ProjectName string `json:"projectName"` UpdateTime int64 `json:"updateTime"` CreateTime int64 `json:"createTime"` + UpdateBy string `json:"updateBy,omitempty"` Schedules *commonmodels.ScheduleCtrl `json:"schedules,omitempty"` SchedulerEnabled bool `json:"schedulerEnabled"` EnabledStages []string `json:"enabledStages"` @@ -527,6 +528,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] ProjectName: w.ProductTmplName, UpdateTime: w.UpdateTime, CreateTime: w.CreateTime, + UpdateBy: w.UpdateBy, SchedulerEnabled: w.ScheduleEnabled, Schedules: w.Schedules, EnabledStages: stages, -- Gitee From 04d6664b78abd34a9c376926b566a8ce355ee5a1 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 17 Nov 2021 20:34:01 +0800 Subject: [PATCH 0690/2647] exclude invalid projects Signed-off-by: lou --- .../aslan/core/workflow/service/workflow/workflow.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index a7e2af10..d87e15cf 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -500,7 +500,13 @@ func validateWorkflowHookNames(w *commonmodels.Workflow) error { } func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([]*Workflow, error) { - workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{Projects: projects}) + existingProjects, err := template.NewProductColl().ListNames(projects) + if err != nil { + log.Errorf("Failed to list projects, err: %s", err) + return nil, e.ErrListWorkflow.AddDesc(err.Error()) + } + + workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{Projects: existingProjects}) if err != nil { log.Errorf("Failed to list workflows, err: %s", err) return nil, e.ErrListWorkflow.AddDesc(err.Error()) -- Gitee From a885dcb8815fd149308d5197dac59eef915204cc Mon Sep 17 00:00:00 2001 From: landylee007 Date: Wed, 17 Nov 2021 21:08:39 +0800 Subject: [PATCH 0691/2647] Contributors list update for v1.7.0 Signed-off-by: landylee007 --- GOVERNANCE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 368ccfb3..0b4feee4 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -60,4 +60,5 @@ The council will make a decision for the nomination. The candidate has to have d - [liuflylove666](https://github.com/liuflylove666) - [jiangpeipei327](https://github.com/jiangpeipei327) - [lyzhang1999](https://github.com/lyzhang1999) -- [avtion](https://github.com/avtion) \ No newline at end of file +- [avtion](https://github.com/avtion) +- [wbjxxzx](https://github.com/wbjxxzx) \ No newline at end of file -- Gitee From 36fb53e238983a33078044d57266f0a8ce676d00 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 17 Nov 2021 22:52:23 +0800 Subject: [PATCH 0692/2647] added external system integration function Signed-off-by: Min Min --- .../repository/models/external_system.go | 14 +++ .../repository/mongodb/external_system.go | 115 ++++++++++++++++++ .../core/system/handler/external_system.go | 111 +++++++++++++++++ .../aslan/core/system/handler/router.go | 12 ++ .../core/system/service/external_system.go | 68 +++++++++++ .../aslan/core/system/service/types.go | 8 ++ 6 files changed, 328 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/repository/models/external_system.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/external_system.go create mode 100644 pkg/microservice/aslan/core/system/handler/external_system.go create mode 100644 pkg/microservice/aslan/core/system/service/external_system.go create mode 100644 pkg/microservice/aslan/core/system/service/types.go diff --git a/pkg/microservice/aslan/core/common/repository/models/external_system.go b/pkg/microservice/aslan/core/common/repository/models/external_system.go new file mode 100644 index 00000000..264615e6 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/external_system.go @@ -0,0 +1,14 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type ExternalSystem struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + Name string `bson:"name"` + Server string `bson:"server"` + APIToken string `bson:"api_token"` +} + +func (ExternalSystem) TableName() string { + return "external_system" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go new file mode 100644 index 00000000..b23f5931 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go @@ -0,0 +1,115 @@ +package mongodb + +import ( + "context" + "errors" + "fmt" + + "github.com/globalsign/mgo/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ExternalSystemColl struct { + *mongo.Collection + + coll string +} + +func NewExternalSystemColl() *ExternalSystemColl { + name := models.ExternalSystem{}.TableName() + return &ExternalSystemColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ExternalSystemColl) GetCollectionName() string { + return c.coll +} + +func (c *ExternalSystemColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *ExternalSystemColl) Create(args *models.ExternalSystem) error { + if args == nil { + return errors.New("external system is nil") + } + + _, err := c.InsertOne(context.TODO(), args) + + return err +} + +func (c *ExternalSystemColl) List(pageSize, pageNum int64) ([]*models.ExternalSystem, int64, error) { + query := bson.M{} + resp := make([]*models.ExternalSystem, 0) + ctx := context.Background() + + opt := options.Find(). + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + + cursor, err := c.Collection.Find(ctx, query, opt) + if err != nil { + return nil, 0, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, 0, err + } + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return nil, 0, err + } + + return resp, count, err +} + +func (c *ExternalSystemColl) GetByID(idstring string) (*models.ExternalSystem, error) { + resp := new(models.ExternalSystem) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *ExternalSystemColl) Update(idString string, obj *models.ExternalSystem) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *ExternalSystemColl) DeleteByID(idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/system/handler/external_system.go b/pkg/microservice/aslan/core/system/handler/external_system.go new file mode 100644 index 00000000..1dd4ca27 --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/external_system.go @@ -0,0 +1,111 @@ +package handler + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/log" +) + +func CreateExternalSystem(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.ExternalSystem) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateExternalSystem GetRawData err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateExternalSystem Unmarshal err : %s", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统配置-外部系统", fmt.Sprintf("name:%s server:%s", args.Name, args.Server), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if args.Name == "" || args.Server == "" { + ctx.Err = errors.New("name and server must be provided") + return + } + + ctx.Err = service.CreateExternalSystem(args, ctx.Logger) +} + +type listQuery struct { + PageSize int64 `json:"page_size" form:"page_size,default=100"` + PageNum int64 `json:"page_num" form:"page_num,default=1"` +} + +type listExternalResp struct { + SystemList []*service.ExternalSystemDetail `json:"external_system"` + Total int64 `json:"total"` +} + +func ListExternalSystem(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Query Verification + args := &listQuery{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + systemList, length, err := service.ListExternalSystem(args.PageNum, args.PageSize, ctx.Logger) + if err == nil { + ctx.Resp = &listExternalResp{ + SystemList: systemList, + Total: length, + } + } + ctx.Err = err +} + +func GetExternalSystemDetail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetExternalSystemDetail(c.Param("id"), ctx.Logger) +} + +func UpdateExternalSystem(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(commonmodels.ExternalSystem) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateExternalSystem GetRawData err : %s", err) + } + if err = json.Unmarshal(data, req); err != nil { + log.Errorf("UpdateExternalSystem Unmarshal err : %s", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统配置-外部系统", fmt.Sprintf("name:%s server:%s", req.Name, req.Server), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if req.Name == "" || req.Server == "" { + ctx.Err = errors.New("name and server must be provided") + return + } + + ctx.Err = service.UpdateExternalSystem(c.Param("id"), req, ctx.Logger) +} + +func DeleteExternalSystem(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "系统配置-外部系统", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + + ctx.Err = service.DeleteExternalSystem(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 9de790a6..093babab 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -196,4 +196,16 @@ func (*Router) Inject(router *gin.RouterGroup) { externalLink.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateExternalLink) externalLink.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteExternalLink) } + + // --------------------------------------------------------------------------------------- + // external system API + // --------------------------------------------------------------------------------------- + externalSystem := router.Group("external") + { + externalSystem.POST("", gin2.UpdateOperationLogStatus, CreateExternalSystem) + externalSystem.GET("", ListExternalSystem) + externalSystem.GET("/:id", GetExternalSystemDetail) + externalSystem.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateExternalSystem) + externalSystem.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteExternalSystem) + } } diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go new file mode 100644 index 00000000..a61112a8 --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -0,0 +1,68 @@ +package service + +import ( + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func CreateExternalSystem(args *commonmodels.ExternalSystem, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalSystemColl().Create(args) + if err != nil { + log.Errorf("ExternalLink.Create error: %s", err) + return e.ErrCreateExternalLink.AddErr(err) + } + return nil +} + +func ListExternalSystem(pageNum, pageSize int64, log *zap.SugaredLogger) ([]*ExternalSystemDetail, int64, error) { + resp, length, err := commonrepo.NewExternalSystemColl().List(pageNum, pageSize) + if err != nil { + log.Errorf("Failed to list external system from db, the error is: %s", err) + return nil, 0, err + } + systemList := make([]*ExternalSystemDetail, 0) + for _, item := range resp { + systemList = append(systemList, &ExternalSystemDetail{ + ID: item.ID.Hex(), + Name: item.Name, + Server: item.Server, + }) + } + return systemList, length, nil +} + +func GetExternalSystemDetail(id string, log *zap.SugaredLogger) (*ExternalSystemDetail, error) { + resp := new(ExternalSystemDetail) + externalSystem, err := commonrepo.NewExternalSystemColl().GetByID(id) + if err != nil { + log.Errorf("Failed to get external system detail from id %s, the error is: %s", id, err) + return nil, err + } + resp.ID = externalSystem.ID.Hex() + resp.Name = externalSystem.Name + resp.Server = externalSystem.Server + resp.APIToken = externalSystem.APIToken + return resp, nil +} + +func UpdateExternalSystem(id string, system *commonmodels.ExternalSystem, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalSystemColl().Update( + id, + system, + ) + if err != nil { + log.Errorf("update external system error: %s", err) + } + return err +} + +func DeleteExternalSystem(id string, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalSystemColl().DeleteByID(id) + if err != nil { + log.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) + } + return err +} diff --git a/pkg/microservice/aslan/core/system/service/types.go b/pkg/microservice/aslan/core/system/service/types.go new file mode 100644 index 00000000..db9dea3f --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/types.go @@ -0,0 +1,8 @@ +package service + +type ExternalSystemDetail struct { + ID string `json:"id"` + Name string `json:"name"` + Server string `json:"server"` + APIToken string `json:"api_token,omitempty"` +} -- Gitee From 5848520d1cdc0ed417eaeb7017758352d2788d31 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 03:25:20 +0800 Subject: [PATCH 0693/2647] fix wrong package import Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/external_system.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go index b23f5931..d8a66798 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/globalsign/mgo/bson" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" -- Gitee From 9c82cb2a77203603838fe0cd230b0df9886a2d46 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 14:10:06 +0800 Subject: [PATCH 0694/2647] debug Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/external_system.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go index d8a66798..d839609b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go @@ -47,7 +47,7 @@ func (c *ExternalSystemColl) Create(args *models.ExternalSystem) error { return err } -func (c *ExternalSystemColl) List(pageSize, pageNum int64) ([]*models.ExternalSystem, int64, error) { +func (c *ExternalSystemColl) List(pageNum, pageSize int64) ([]*models.ExternalSystem, int64, error) { query := bson.M{} resp := make([]*models.ExternalSystem, 0) ctx := context.Background() -- Gitee From 5147ce7511976eb6be0ebffabb7c5832ef80721d Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 14:24:25 +0800 Subject: [PATCH 0695/2647] debug Signed-off-by: Min Min --- .../aslan/core/system/handler/external_system.go | 2 +- .../aslan/core/system/service/external_system.go | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/external_system.go b/pkg/microservice/aslan/core/system/handler/external_system.go index 1dd4ca27..e35ff99b 100644 --- a/pkg/microservice/aslan/core/system/handler/external_system.go +++ b/pkg/microservice/aslan/core/system/handler/external_system.go @@ -19,7 +19,7 @@ func CreateExternalSystem(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(commonmodels.ExternalSystem) + args := new(service.ExternalSystemDetail) data, err := c.GetRawData() if err != nil { log.Errorf("CreateExternalSystem GetRawData err : %s", err) diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go index a61112a8..67d40663 100644 --- a/pkg/microservice/aslan/core/system/service/external_system.go +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -8,8 +8,12 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func CreateExternalSystem(args *commonmodels.ExternalSystem, log *zap.SugaredLogger) error { - err := commonrepo.NewExternalSystemColl().Create(args) +func CreateExternalSystem(args *ExternalSystemDetail, log *zap.SugaredLogger) error { + err := commonrepo.NewExternalSystemColl().Create(&commonmodels.ExternalSystem{ + Name: args.Name, + Server: args.Server, + APIToken: args.APIToken, + }) if err != nil { log.Errorf("ExternalLink.Create error: %s", err) return e.ErrCreateExternalLink.AddErr(err) -- Gitee From 56c2e3c08d9f5da5eabc350709ce378b2e62fc34 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 14:30:37 +0800 Subject: [PATCH 0696/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index ff470607..34aa24a9 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -98,7 +98,7 @@ func (r *Reaper) CompressCache(storageURI string) error { return err } if len(r.Ctx.Caches) > 0 { - log.Infof("custom caches will be cached: %v", r.Ctx.Caches) + log.Infof("custom caches will be cached") if err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches); err != nil { return err } -- Gitee From e10bd39e13763bfa55114c9f3352d2c61ed2fae4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 15:22:50 +0800 Subject: [PATCH 0697/2647] code review changes Signed-off-by: Min Min --- .../common/repository/mongodb/external_system.go | 2 +- .../aslan/core/system/handler/external_system.go | 4 ++-- .../aslan/core/system/service/external_system.go | 12 ++++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go index d839609b..9e651d88 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go @@ -70,7 +70,7 @@ func (c *ExternalSystemColl) List(pageNum, pageSize int64) ([]*models.ExternalSy return nil, 0, err } - return resp, count, err + return resp, count, nil } func (c *ExternalSystemColl) GetByID(idstring string) (*models.ExternalSystem, error) { diff --git a/pkg/microservice/aslan/core/system/handler/external_system.go b/pkg/microservice/aslan/core/system/handler/external_system.go index e35ff99b..41998fc9 100644 --- a/pkg/microservice/aslan/core/system/handler/external_system.go +++ b/pkg/microservice/aslan/core/system/handler/external_system.go @@ -9,7 +9,6 @@ import ( "github.com/gin-gonic/gin" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/log" @@ -66,6 +65,7 @@ func ListExternalSystem(c *gin.Context) { SystemList: systemList, Total: length, } + return } ctx.Err = err } @@ -81,7 +81,7 @@ func UpdateExternalSystem(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(commonmodels.ExternalSystem) + req := new(service.ExternalSystemDetail) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateExternalSystem GetRawData err : %s", err) diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go index 67d40663..ed72a681 100644 --- a/pkg/microservice/aslan/core/system/service/external_system.go +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -15,7 +15,7 @@ func CreateExternalSystem(args *ExternalSystemDetail, log *zap.SugaredLogger) er APIToken: args.APIToken, }) if err != nil { - log.Errorf("ExternalLink.Create error: %s", err) + log.Errorf("Create external system error: %s", err) return e.ErrCreateExternalLink.AddErr(err) } return nil @@ -52,10 +52,14 @@ func GetExternalSystemDetail(id string, log *zap.SugaredLogger) (*ExternalSystem return resp, nil } -func UpdateExternalSystem(id string, system *commonmodels.ExternalSystem, log *zap.SugaredLogger) error { +func UpdateExternalSystem(id string, system *ExternalSystemDetail, log *zap.SugaredLogger) error { err := commonrepo.NewExternalSystemColl().Update( id, - system, + &commonmodels.ExternalSystem{ + Name: system.Name, + Server: system.Server, + APIToken: system.APIToken, + }, ) if err != nil { log.Errorf("update external system error: %s", err) @@ -66,7 +70,7 @@ func UpdateExternalSystem(id string, system *commonmodels.ExternalSystem, log *z func DeleteExternalSystem(id string, log *zap.SugaredLogger) error { err := commonrepo.NewExternalSystemColl().DeleteByID(id) if err != nil { - log.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) + log.Errorf("Failed to delete external system of id: %s, the error is: %s", id, err) } return err } -- Gitee From 36f725f019a1e7b3b81a4feec1f079d12982d587 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 05:50:24 +0800 Subject: [PATCH 0698/2647] workflow V3 data implementation Signed-off-by: Min Min --- .../common/repository/models/workflow_v3.go | 64 +++++++++ .../common/repository/mongodb/workflow_v3.go | 123 ++++++++++++++++++ .../aslan/core/workflow/handler/router.go | 12 ++ .../core/workflow/handler/workflow_v3.go | 103 +++++++++++++++ .../core/workflow/service/workflow/types.go | 60 +++++++++ .../workflow/service/workflow/workflow_v3.go | 99 ++++++++++++++ 6 files changed, 461 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/repository/models/workflow_v3.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go create mode 100644 pkg/microservice/aslan/core/workflow/handler/workflow_v3.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/types.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go new file mode 100644 index 00000000..9e946142 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -0,0 +1,64 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type WorkflowV3 struct { + ID primitive.ObjectID `bson:"_id"` + Name string `bson:"name"` + ProjectName string `bson:"project_name"` + Description string `bson:"description"` + Parameters []*ParameterSetting `bson:"parameters"` + Subtasks []map[string]interface{} `bson:"subtasks"` + CreatedBy string `bson:"created_by"` + CreateTime int64 `bson:"create_time"` + UpdatedBy string `bson:"updated_by"` + UpdateTime int64 `bson:"update_time"` +} + +type ParameterSetting struct { + // External type parameter will NOT use this key. + Key string `bson:"key"` + // Type list: + // string + // choice + // external + Type string `bson:"type"` + //DefaultValue is the + DefaultValue string `bson:"default_value"` + // choiceOption 是枚举的所有选项 + ChoiceOption []string `bson:"choice_option"` + // ExternalSetting 是外部系统获取变量的配置 + ExternalSetting *ExternalSetting `bson:"external_setting"` +} + +type ExternalSetting struct { + // 外部系统ID + SystemID string `bson:"system_id"` + // Endpoint路径 + Endpoint string `bson:"endpoint"` + // 请求方法 + Method string `bson:"method"` + // 请求头 + Headers []*KV `bson:"headers"` + // 请求体 + Body string `bson:"body"` + // 外部变量配置 + Params []*ExternalParamMapping `bson:"params"` +} + +type KV struct { + Key string `bson:"key"` + Value string `bson:"value"` +} + +type ExternalParamMapping struct { + // zadig变量名称 + ParamKey string `bson:"param_key"` + // 返回中的key的位置 + ResponseKey string `bson:"response_key"` + Display bool `bson:"display"` +} + +func (WorkflowV3) TableName() string { + return "workflow_v3" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go new file mode 100644 index 00000000..4b6bf71b --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go @@ -0,0 +1,123 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type WorkflowV3Coll struct { + *mongo.Collection + + coll string +} + +type ListWorkflowV3Option struct { + ProjectName string +} + +func NewWorkflowV3Coll() *WorkflowV3Coll { + name := models.WorkflowV3{}.TableName() + return &WorkflowV3Coll{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *WorkflowV3Coll) GetCollectionName() string { + return c.coll +} + +func (c *WorkflowV3Coll) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"project_name": 1}, + Options: options.Index().SetUnique(false), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *WorkflowV3Coll) Create(obj *models.WorkflowV3) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + return err +} + +func (c *WorkflowV3Coll) List(opt *ListWorkflowV3Option, pageNum, pageSize int64) ([]*models.WorkflowV3, int64, error) { + resp := make([]*models.WorkflowV3, 0) + query := bson.M{} + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + findOption := options.Find(). + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + + cursor, err := c.Collection.Find(context.TODO(), query, findOption) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, count, nil +} + +func (c *WorkflowV3Coll) GetByID(idstring string) (*models.WorkflowV3, error) { + resp := new(models.WorkflowV3) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *WorkflowV3Coll) Update(idString string, obj *models.WorkflowV3) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *WorkflowV3Coll) DeleteByID(idString string) error { + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 091767d4..c36a0d85 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -133,4 +133,16 @@ func (*Router) Inject(router *gin.RouterGroup) { { serviceTask.GET("/workflows/:productName/:envName/:serviceName/:serviceType", ListServiceWorkflows) } + + // --------------------------------------------------------------------------------------- + // 新版本 通用工作流(暂命名) 接口 + // --------------------------------------------------------------------------------------- + workflowV3 := router.Group("v3") + { + workflowV3.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowV3) + workflowV3.GET("", ListWorkflowsV3) + workflowV3.GET("/:id", GetWorkflowV3Detail) + workflowV3.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateWorkflowV3) + workflowV3.DELETE("/:id", DeleteWorkflowV3) + } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go new file mode 100644 index 00000000..dc25e726 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -0,0 +1,103 @@ +package handler + +import ( + "encoding/json" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +func CreateWorkflowV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &workflowservice.WorkflowV3{} + + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowV3 err: %v", err) + } + if err = json.Unmarshal(data, req); err != nil { + log.Errorf("CreateWorkflow unmarshal json err: %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "新增", "工作流V3", req.Name, string(data), ctx.Logger) + + if err := c.ShouldBindWith(&req, binding.JSON); err != nil { + ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) + return + } + ctx.Err = workflowservice.CreateWorkflowV3(ctx.UserName, req, ctx.Logger) +} + +type listWorkflowV3Query struct { + PageSize int64 `json:"page_size" form:"page_size,default=100"` + PageNum int64 `json:"page_num" form:"page_num,default=1"` + ProjectName string `json:"project_name" form:"project_name"` +} + +type listWorkflowV3Resp struct { + WorkflowList []*workflowservice.WorkflowV3Brief `json:"workflow_list"` + Total int64 `json:"total"` +} + +func ListWorkflowsV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + // Query Verification + args := &listWorkflowV3Query{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + workflowList, total, err := workflowservice.ListWorkflowsV3(args.ProjectName, args.PageNum, args.PageSize, ctx.Logger) + resp := listWorkflowV3Resp{ + WorkflowList: workflowList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err +} + +func GetWorkflowV3Detail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = workflowservice.GetWorkflowV3Detail(c.Param("id"), ctx.Logger) +} + +func UpdateWorkflowV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &workflowservice.WorkflowV3{} + + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowV3 err: %v", err) + } + if err = json.Unmarshal(data, req); err != nil { + log.Errorf("CreateWorkflow unmarshal json err: %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "修改", "工作流V3", req.Name, string(data), ctx.Logger) + + if err := c.ShouldBindWith(&req, binding.JSON); err != nil { + ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = workflowservice.UpdateWorkflowV3(c.Param("id"), ctx.UserName, req, ctx.Logger) +} + +func DeleteWorkflowV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = workflowservice.DeleteWorkflowV3(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go new file mode 100644 index 00000000..9c254f6d --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -0,0 +1,60 @@ +package workflow + +type WorkflowV3 struct { + ID string `json:"id"` + Name string `json:"name"` + ProjectName string `json:"project_name"` + Description string `json:"description"` + Parameters []*ParameterSetting `json:"parameters"` + Subtasks []map[string]interface{} `json:"subtasks"` +} + +type WorkflowV3Brief struct { + ID string `json:"id"` + Name string `json:"name"` + ProjectName string `json:"project_name"` +} + +type ParameterSetting struct { + // External type parameter will NOT use this key. + Key string `json:"key"` + // Type list: + // string + // choice + // external + Type string `json:"type"` + //DefaultValue is the + DefaultValue string `json:"default_value"` + // choiceOption 是枚举的所有选项 + ChoiceOption []string `json:"choice_option"` + // ExternalSetting 是外部系统获取变量的配置 + ExternalSetting *ExternalSetting `json:"external_setting"` +} + +type ExternalSetting struct { + // 外部系统ID + SystemID string `json:"system_id"` + // Endpoint路径 + Endpoint string `json:"endpoint"` + // 请求方法 + Method string `json:"method"` + // 请求头 + Headers []*KV `json:"headers"` + // 请求体 + Body string `json:"body"` + // 外部变量配置 + Params []*ExternalParamMapping `json:"params"` +} + +type KV struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type ExternalParamMapping struct { + // zadig变量名称 + ParamKey string `json:"param_key"` + // 返回中的key的位置 + ResponseKey string `json:"response_key"` + Display bool `json:"display"` +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go new file mode 100644 index 00000000..de6889d9 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -0,0 +1,99 @@ +package workflow + +import ( + "encoding/json" + "time" + + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" +) + +func CreateWorkflowV3(user string, workflow *WorkflowV3, logger *zap.SugaredLogger) error { + workflowModel := new(commonmodels.WorkflowV3) + out, err := json.Marshal(workflow) + if err != nil { + logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) + return err + } + err = json.Unmarshal(out, &workflowModel) + if err != nil { + logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) + return err + } + workflowModel.CreatedBy = user + workflowModel.CreateTime = time.Now().Unix() + return commonrepo.NewWorkflowV3Coll().Create(workflowModel) +} + +func ListWorkflowsV3(projectName string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*WorkflowV3Brief, int64, error) { + resp := make([]*WorkflowV3Brief, 0) + workflowV3List, total, err := commonrepo.NewWorkflowV3Coll().List(&commonrepo.ListWorkflowV3Option{ + ProjectName: projectName, + }, pageNum, pageSize) + if err != nil { + logger.Errorf("Failed to list workflow v3, the error is: %s", err) + return nil, 0, err + } + for _, workflow := range workflowV3List { + resp = append(resp, &WorkflowV3Brief{ + ID: workflow.ID.Hex(), + Name: workflow.Name, + ProjectName: workflow.ProjectName, + }) + } + return resp, total, nil +} + +func GetWorkflowV3Detail(id string, logger *zap.SugaredLogger) (*WorkflowV3, error) { + resp := new(WorkflowV3) + workflow, err := commonrepo.NewWorkflowV3Coll().GetByID(id) + if err != nil { + logger.Errorf("Failed to get workflowV3 detail from id: %s, the error is: %s", id, err) + return nil, err + } + out, err := json.Marshal(workflow) + if err != nil { + logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) + return nil, err + } + err = json.Unmarshal(out, &resp) + if err != nil { + logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) + return nil, err + } + return resp, nil +} + +func UpdateWorkflowV3(id, user string, workflow *WorkflowV3, logger *zap.SugaredLogger) error { + workflowModel := new(commonmodels.WorkflowV3) + out, err := json.Marshal(workflow) + if err != nil { + logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) + return err + } + err = json.Unmarshal(out, &workflowModel) + if err != nil { + logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) + return err + } + workflowModel.UpdatedBy = user + workflowModel.UpdateTime = time.Now().Unix() + err = commonrepo.NewWorkflowV3Coll().Update( + id, + workflowModel, + ) + if err != nil { + logger.Errorf("update workflowV3 error: %s", err) + } + return err +} + +func DeleteWorkflowV3(id string, logger *zap.SugaredLogger) error { + err := commonrepo.NewWorkflowV3Coll().DeleteByID(id) + if err != nil { + logger.Errorf("Failed to WorkflowV3 of id: %s, the error is: %s", id, err) + } + return err +} -- Gitee From 0cf9412aed61926ef4111cf3f0a8af18e8c00dbf Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 16:17:08 +0800 Subject: [PATCH 0699/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/workflow/handler/workflow_v3.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index dc25e726..cdb6db19 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -4,7 +4,6 @@ import ( "encoding/json" "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/binding" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -20,14 +19,14 @@ func CreateWorkflowV3(c *gin.Context) { data, err := c.GetRawData() if err != nil { - log.Errorf("CreateWorkflowV3 err: %v", err) + log.Errorf("CreateWorkflowV3 err: %s", err) } if err = json.Unmarshal(data, req); err != nil { - log.Errorf("CreateWorkflow unmarshal json err: %v", err) + log.Errorf("CreateWorkflow unmarshal json err: %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "新增", "工作流V3", req.Name, string(data), ctx.Logger) - if err := c.ShouldBindWith(&req, binding.JSON); err != nil { + if err := c.ShouldBindJSON(&req); err != nil { ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) return } @@ -80,14 +79,14 @@ func UpdateWorkflowV3(c *gin.Context) { data, err := c.GetRawData() if err != nil { - log.Errorf("CreateWorkflowV3 err: %v", err) + log.Errorf("CreateWorkflowV3 err: %s", err) } if err = json.Unmarshal(data, req); err != nil { - log.Errorf("CreateWorkflow unmarshal json err: %v", err) + log.Errorf("CreateWorkflow unmarshal json err: %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "修改", "工作流V3", req.Name, string(data), ctx.Logger) - if err := c.ShouldBindWith(&req, binding.JSON); err != nil { + if err := c.ShouldBindJSON(&req); err != nil { ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) return } -- Gitee From 4c01d861e6bb9828fe53e7720e371b95a8f26a97 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 18 Nov 2021 16:19:22 +0800 Subject: [PATCH 0700/2647] code review changes2 Signed-off-by: Min Min --- pkg/microservice/aslan/core/workflow/handler/workflow_v3.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index cdb6db19..d46bc8da 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -79,10 +79,10 @@ func UpdateWorkflowV3(c *gin.Context) { data, err := c.GetRawData() if err != nil { - log.Errorf("CreateWorkflowV3 err: %s", err) + log.Errorf("UpdateWorkflowV3 err: %s", err) } if err = json.Unmarshal(data, req); err != nil { - log.Errorf("CreateWorkflow unmarshal json err: %s", err) + log.Errorf("UpdateWorkflowV3 unmarshal json err: %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "修改", "工作流V3", req.Name, string(data), ctx.Logger) -- Gitee From 9d708c5d246d6049463732cc44810d8cb518bd00 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 16:26:08 +0800 Subject: [PATCH 0701/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/config/consts.go | 3 + .../aslan/core/workflow/handler/router.go | 18 +- .../core/workflow/handler/workflow_task.go | 56 +++---- .../service/workflow/workflow_task_v3.go | 155 ++++++++++++++++++ pkg/setting/consts.go | 9 +- 5 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 36376093..ccaa7cfe 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -81,6 +81,8 @@ const ( TestType PipelineType = "test" // ServiceType 服务 ServiceType PipelineType = "service" + // WorkflowTypeV3 + WorkflowTypeV3 PipelineType = "workflow_v3" ) type Status string @@ -117,6 +119,7 @@ type TaskType string const ( TaskPipeline TaskType = "pipeline" TaskBuild TaskType = "buildv2" + TaskBuildV3 TaskType = "buildv3" TaskJenkinsBuild TaskType = "jenkins_build" TaskArtifact TaskType = "artifact" TaskArtifactDeploy TaskType = "artifact_deploy" diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 091767d4..a51ff074 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -121,12 +121,22 @@ func (*Router) Inject(router *gin.RouterGroup) { //todo 修改权限的uuid workflowtask.GET("/targets/:productName/:namespace", GetWorkflowArgs) workflowtask.GET("/preset/:namespace/:workflowName", PresetWorkflowArgs) - workflowtask.POST("", GetWorkflowTaskProductName, gin2.UpdateOperationLogStatus, CreateWorkflowTask) - workflowtask.PUT("", GetWorkflowTaskProductName, gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) + workflowtask.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTask) + workflowtask.PUT("", gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) workflowtask.GET("/max/:max/start/:start/pipelines/:name", ListWorkflowTasksResult) workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) - workflowtask.POST("/id/:id/pipelines/:name/restart", GetWorkflowTaskProductNameByTask, gin2.UpdateOperationLogStatus, RestartWorkflowTask) - workflowtask.DELETE("/id/:id/pipelines/:name", GetWorkflowTaskProductNameByTask, gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) + workflowtask.POST("/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTask) + workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) + } + + // --------------------------------------------------------------------------------------- + // workflow v3 任务接口 + // --------------------------------------------------------------------------------------- + taskV3 := router.Group("v3/workflowtask") + { + taskV3.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTaskV3) + taskV3.POST("/id/:id/name/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTaskV3) + taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) } serviceTask := router.Group("servicetask") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 81a250a3..434c6ce1 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -35,39 +35,6 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) -func GetWorkflowTaskProductName(c *gin.Context) { - args := new(commonmodels.WorkflowTaskArgs) - data, err := c.GetRawData() - if err != nil { - log.Errorf("c.GetRawData() err : %v", err) - return - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("json.Unmarshal err : %v", err) - return - } - c.Set("productName", args.ProductTmplName) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - c.Next() -} - -func GetWorkflowTaskProductNameByTask(c *gin.Context) { - ctx := internalhandler.NewContext(c) - taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") - return - } - - pipelineTask, err := workflow.GetPipelineTaskV2(taskID, c.Param("name"), config.WorkflowType, ctx.Logger) - if err != nil { - log.Errorf("GetPipelineTaskV2 err : %v", err) - return - } - c.Set("productName", pipelineTask.WorkflowArgs.ProductTmplName) - c.Next() -} - // GetWorkflowArgs find workflow args func GetWorkflowArgs(c *gin.Context) { ctx := internalhandler.NewContext(c) @@ -211,3 +178,26 @@ func CancelWorkflowTaskV2(c *gin.Context) { } ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.WorkflowType, ctx.RequestID, ctx.Logger) } + +func CreateWorkflowTaskV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.WorkflowV3Args) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowTaskV3 c.GetRawData() err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateWorkflowTaskV3 json.Unmarshal err : %s", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "工作流v3-task", args.Name, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Resp, ctx.Err = workflow.CreateWorkflowTaskV3(args, ctx.UserName, ctx.RequestID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go new file mode 100644 index 00000000..548e5347 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -0,0 +1,155 @@ +package workflow + +//func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID string, log *zap.SugaredLogger) (*CreateTaskResp, error) { +// pipeline, err := commonrepo.NewPipelineColl().Find(&commonrepo.PipelineFindOption{Name: args.PipelineName}) +// if err != nil { +// log.Errorf("PipelineV2.Find %s error: %v", args.PipelineName, err) +// return nil, e.ErrCreateTask.AddDesc(e.FindPipelineErrMsg) +// } +// +// nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.PipelineTaskFmt, args.Name)) +// if err != nil { +// log.Errorf("Counter.GetNextSeq error: %v", err) +// return nil, e.ErrGetCounter.AddDesc(err.Error()) +// } +// +// // 更新单服务工作的subtask的build_os +// // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 +// for i, subTask := range pipeline.SubTasks { +// pre, err := base.ToPreview(subTask) +// if err != nil { +// log.Errorf("subTask.ToPreview error: %v", err) +// continue +// } +// switch pre.TaskType { +// case config.TaskBuildV3: +// build, err := base.ToBuildTask(subTask) +// if err != nil || build == nil { +// log.Errorf("subTask.ToBuildTask error: %v", err) +// continue +// } +// if build.ImageID == "" { +// continue +// } +// basicImage, err := commonrepo.NewBasicImageColl().Find(build.ImageID) +// if err != nil { +// log.Errorf("BasicImage.Find failed, id:%s, err:%v", build.ImageID, err) +// continue +// } +// build.BuildOS = basicImage.Value +// +// // 创建任务时可以临时编辑环境变量,需要将pipeline中的环境变量更新成新的值。 +// if args.BuildArgs != nil { +// build.JobCtx.EnvVars = args.BuildArgs +// } +// +// pipeline.SubTasks[i], err = build.ToSubTask() +// if err != nil { +// log.Errorf("build.ToSubTask error: %v", err) +// continue +// } +// } +// } +// +// var defaultStorageURI string +// if defaultS3, err := s3.FindDefaultS3(); err == nil { +// defaultStorageURI, err = defaultS3.GetEncryptedURL() +// if err != nil { +// return nil, e.ErrS3Storage.AddErr(err) +// } +// } +// +// pt := &task.Task{ +// TaskID: nextTaskID, +// ProductName: pipeline.ProductName, +// PipelineName: args.Name, +// Type: config.WorkflowTypeV3, +// TaskCreator: username, +// ReqID: reqID, +// Status: config.StatusCreated, +// SubTasks: pipeline.SubTasks, +// TeamName: pipeline.TeamName, +// ConfigPayload: commonservice.GetConfigPayload(0), +// MultiRun: pipeline.MultiRun, +// StorageURI: defaultStorageURI, +// } +// +// sort.Sort(ByTaskKind(pt.SubTasks)) +// +// for i, t := range pt.SubTasks { +// preview, err := base.ToPreview(t) +// if err != nil { +// continue +// } +// if preview.TaskType != config.TaskDeploy { +// continue +// } +// +// t, err := base.ToDeployTask(t) +// if err == nil && t.Enabled { +// env, err := commonrepo.NewProductColl().FindEnv(&commonrepo.ProductEnvFindOptions{ +// Namespace: pt.TaskArgs.Deploy.Namespace, +// Name: pt.ProductName, +// }) +// +// if err != nil { +// return nil, e.ErrCreateTask.AddDesc( +// e.EnvNotFoundErrMsg + ": " + pt.TaskArgs.Deploy.Namespace, +// ) +// } +// +// t.EnvName = env.EnvName +// t.ProductName = pt.ProductName +// pt.ConfigPayload.DeployClusterID = env.ClusterID +// pt.SubTasks[i], _ = t.ToSubTask() +// } +// } +// +// repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) +// if err != nil { +// return nil, e.ErrCreateTask.AddErr(err) +// } +// +// pt.ConfigPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) +// for _, repo := range repos { +// pt.ConfigPayload.RepoConfigs[repo.ID.Hex()] = repo +// } +// +// if err := ensurePipelineTask(pt, pt.TaskArgs.Deploy.Namespace, log); err != nil { +// log.Errorf("Service.ensurePipelineTask failed %v %v", args, err) +// if err, ok := err.(*ContainerNotFound); ok { +// return nil, e.NewWithExtras( +// e.ErrCreateTaskFailed, +// "container doesn't exists", map[string]interface{}{ +// "productName": err.ProductName, +// "envName": err.EnvName, +// "serviceName": err.ServiceName, +// "containerName": err.Container, +// }) +// } +// +// return nil, e.ErrCreateTask.AddDesc(err.Error()) +// } +// +// if len(pt.SubTasks) <= 0 { +// return nil, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) +// } +// +// if config.EnableGitCheck() { +// if err := createGitCheck(pt, log); err != nil { +// log.Error(err) +// } +// } +// +// // send to queue to execute task +// if err := CreateTask(pt); err != nil { +// log.Error(err) +// return nil, e.ErrCreateTask +// } +// +// resp := &CreateTaskResp{ +// PipelineName: args.Name, +// TaskID: nextTaskID, +// } +// return resp, nil +//} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index c8c77099..212a6876 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -305,10 +305,11 @@ const ( // counter prefix const ( - PipelineTaskFmt = "PipelineTask:%s" - WorkflowTaskFmt = "WorkflowTask:%s" - TestTaskFmt = "TestTask:%s" - ServiceTaskFmt = "ServiceTask:%s" + PipelineTaskFmt = "PipelineTask:%s" + WorkflowTaskFmt = "WorkflowTask:%s" + WorkflowTaskV3Fmt = "WorkflowTaskV3:%s" + TestTaskFmt = "TestTask:%s" + ServiceTaskFmt = "ServiceTask:%s" ) // Product Status -- Gitee From 2c19cb1199feb5789ff8db925a9c8100d4c1b908 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 21:16:29 +0800 Subject: [PATCH 0702/2647] add create workflow v3 task interface Signed-off-by: liu deyi --- pkg/microservice/aslan/config/consts.go | 1 + .../common/repository/models/task/trigger.go | 30 ++ .../common/repository/models/workflow_v3.go | 16 +- .../aslan/core/common/service/base/convert.go | 8 + .../aslan/core/common/service/task.go | 8 + .../aslan/core/log/handler/log.go | 13 + .../aslan/core/log/handler/router.go | 2 + .../aslan/core/log/handler/sse.go | 35 ++ .../aslan/core/log/service/log.go | 10 + .../aslan/core/workflow/handler/router.go | 22 +- .../core/workflow/handler/workflow_task.go | 23 -- .../core/workflow/handler/workflow_v3.go | 3 + .../core/workflow/handler/workflow_v3_task.go | 100 +++++ .../core/workflow/service/workflow/sort.go | 23 +- .../service/workflow/workflow_task.go | 2 +- .../service/workflow/workflow_task_v3.go | 338 +++++++++-------- pkg/microservice/warpdrive/config/const.go | 4 + .../service/taskcontroller/task_handler.go | 3 + .../service/taskcontroller/task_helper.go | 2 + .../core/service/taskplugin/build_v3.go | 349 ++++++++++++++++++ .../core/service/taskplugin/trigger.go | 165 +++++++++ .../core/service/taskplugin/utils.go | 8 + .../core/service/types/task/trigger.go | 30 ++ 23 files changed, 996 insertions(+), 199 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/task/trigger.go create mode 100644 pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/trigger.go create mode 100644 pkg/microservice/warpdrive/core/service/types/task/trigger.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index ccaa7cfe..d05e4269 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -132,6 +132,7 @@ const ( TaskSecurity TaskType = "security" TaskResetImage TaskType = "reset_image" TaskDistribute TaskType = "distribute" + TaskTrigger TaskType = "trigger" ) type DistributeType string diff --git a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go new file mode 100644 index 00000000..afb7beae --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go @@ -0,0 +1,30 @@ +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" +) + +type Trigger struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url,omitempty" json:"url,omitempty"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +// ToSubTask ... +func (t *Trigger) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert trigger to interface error: %s", err) + } + return task, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go index 9e946142..bfb78d8d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -1,6 +1,9 @@ package models -import "go.mongodb.org/mongo-driver/bson/primitive" +import ( + "github.com/koderover/zadig/pkg/types" + "go.mongodb.org/mongo-driver/bson/primitive" +) type WorkflowV3 struct { ID primitive.ObjectID `bson:"_id"` @@ -8,7 +11,7 @@ type WorkflowV3 struct { ProjectName string `bson:"project_name"` Description string `bson:"description"` Parameters []*ParameterSetting `bson:"parameters"` - Subtasks []map[string]interface{} `bson:"subtasks"` + SubTasks []map[string]interface{} `bson:"sub_tasks"` CreatedBy string `bson:"created_by"` CreateTime int64 `bson:"create_time"` UpdatedBy string `bson:"updated_by"` @@ -59,6 +62,15 @@ type ExternalParamMapping struct { Display bool `bson:"display"` } +// WorkflowV3Args 工作流v3任务参数 +type WorkflowV3Args struct { + ProjectName string `bson:"project_name" json:"project_name"` + ID string `bson:"id" json:"id"` + Name string `bson:"name" json:"name"` + Builds []*types.Repository `bson:"builds" json:"builds"` + BuildArgs []*KeyVal `bson:"build_args" json:"build_args"` +} + func (WorkflowV3) TableName() string { return "workflow_v3" } diff --git a/pkg/microservice/aslan/core/common/service/base/convert.go b/pkg/microservice/aslan/core/common/service/base/convert.go index b6913548..5a1da24d 100644 --- a/pkg/microservice/aslan/core/common/service/base/convert.go +++ b/pkg/microservice/aslan/core/common/service/base/convert.go @@ -115,3 +115,11 @@ func ToJenkinsBuildTask(sb map[string]interface{}) (*task.JenkinsBuild, error) { } return jenkinsBuild, nil } + +func ToTriggerTask(sb map[string]interface{}) (*task.Trigger, error) { + var trigger *task.Trigger + if err := task.IToi(sb, &trigger); err != nil { + return nil, fmt.Errorf("convert interface to triggerTask error: %s", err) + } + return trigger, nil +} diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index 17bd6216..010a2a52 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -40,6 +40,14 @@ type cancelMessage struct { ReqID string `json:"req_id"` } +func CancelWorkflowTaskV3(userName, pipelineName string, taskID int64, typeString config.PipelineType, requestID string, log *zap.SugaredLogger) error { + if err := CancelTask(userName, pipelineName, taskID, typeString, requestID, log); err != nil { + log.Errorf("[%s] cancel pipeline [%s:%d] error: %s", userName, pipelineName, taskID, err) + return e.ErrCancelTask.AddDesc(err.Error()) + } + return nil +} + func CancelTaskV2(userName, pipelineName string, taskID int64, typeString config.PipelineType, requestID string, log *zap.SugaredLogger) error { if err := CancelTask(userName, pipelineName, taskID, typeString, requestID, log); err != nil { diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 648d8588..1243a524 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -115,3 +115,16 @@ func GetContainerLogs(c *gin.Context) { logservice.ContainerLogStream(ctx1, streamChan, envName, productName, podName, containerName, follow, tailLines, ctx.Logger) }, ctx.Logger) } + +func GetWorkflowBuildV3JobContainerLogs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("taskId"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + // job名称使用全小写,避免出现subdomain错误 + ctx.Resp, ctx.Err = logservice.GetWorkflowBuildV3JobContainerLogs(strings.ToLower(c.Param("workflowName")), c.Query("type"), taskID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/log/handler/router.go b/pkg/microservice/aslan/core/log/handler/router.go index 2fcfcb08..11034f28 100644 --- a/pkg/microservice/aslan/core/log/handler/router.go +++ b/pkg/microservice/aslan/core/log/handler/router.go @@ -33,6 +33,7 @@ func (*Router) Inject(router *gin.RouterGroup) { log.GET("/workflow/:pipelineName/tasks/:taskId/service/:serviceName", GetWorkflowBuildJobContainerLogs) log.GET("/pipelines/:pipelineName/tasks/:taskId/tests/:testName", GetTestJobContainerLogs) log.GET("/workflow/:pipelineName/tasks/:taskId/tests/:testName/service/:serviceName", GetWorkflowTestJobContainerLogs) + log.GET("/v3/workflow/:workflowName/tasks/:taskId", GetWorkflowBuildV3JobContainerLogs) } sse := router.Group("sse") @@ -43,5 +44,6 @@ func (*Router) Inject(router *gin.RouterGroup) { sse.GET("/test/:pipelineName/:taskId/:testName/:lines", GetTestJobContainerLogsSSE) sse.GET("/workflow/test/:pipelineName/:taskId/:testName/:lines/:serviceName", GetWorkflowTestJobContainerLogsSSE) sse.GET("/service/build/:serviceName/:envName/:productName", GetServiceJobContainerLogsSSE) + sse.GET("/v3/workflow/build/:workflowName/:taskId/:lines", GetWorkflowBuildV3JobContainerLogsSSE) } } diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index 2ac7c832..f2d28bbf 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -215,3 +215,38 @@ func GetServiceJobContainerLogsSSE(c *gin.Context) { ctx.Logger) }, ctx.Logger) } + +func GetWorkflowBuildV3JobContainerLogsSSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + + taskID, err := strconv.ParseInt(c.Param("taskId"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + internalhandler.JSONResponse(c, ctx) + return + } + + tails, err := strconv.ParseInt(c.Param("lines"), 10, 64) + if err != nil { + tails = int64(10) + } + + subTask := c.Query("subTask") + options := &logservice.GetContainerOptions{ + Namespace: config.Namespace(), + PipelineName: c.Param("workflowName"), + SubTask: subTask, + TailLines: tails, + TaskID: taskID, + PipelineType: string(config.WorkflowTypeV3), + EnvName: c.Query("envName"), + ProductName: c.Query("projectName"), + } + + internalhandler.Stream(c, func(ctx1 context.Context, streamChan chan interface{}) { + logservice.TaskContainerLogStream( + ctx1, streamChan, + options, + ctx.Logger) + }, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 73778bc9..21a770b0 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -137,3 +137,13 @@ func GetCurrentContainerLogs(podName, containerName, envName, productName string return buf.String(), nil } + +func GetWorkflowBuildV3JobContainerLogs(workflowName, buildType string, taskID int64, log *zap.SugaredLogger) (string, error) { + buildJobNamePrefix := fmt.Sprintf("%s-%s-%d-%s", config.WorkflowTypeV3, workflowName, taskID, buildType) + buildLog, err := getContainerLogFromS3(workflowName, buildJobNamePrefix, taskID, log) + if err != nil { + return "", err + } + + return buildLog, nil +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index c5db1cd1..d890f09c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -129,16 +129,6 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) } - // --------------------------------------------------------------------------------------- - // workflow v3 任务接口 - // --------------------------------------------------------------------------------------- - taskV3 := router.Group("v3/workflowtask") - { - taskV3.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTaskV3) - taskV3.POST("/id/:id/name/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTaskV3) - taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) - } - serviceTask := router.Group("servicetask") { serviceTask.GET("/workflows/:productName/:envName/:serviceName/:serviceType", ListServiceWorkflows) @@ -155,4 +145,16 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowV3.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateWorkflowV3) workflowV3.DELETE("/:id", DeleteWorkflowV3) } + + // --------------------------------------------------------------------------------------- + // workflow v3 任务接口 + // --------------------------------------------------------------------------------------- + taskV3 := router.Group("v3/workflowtask") + { + taskV3.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTaskV3) + taskV3.POST("/id/:id/name/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTaskV3) + taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) + taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) + taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) + } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 434c6ce1..6440e978 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -178,26 +178,3 @@ func CancelWorkflowTaskV2(c *gin.Context) { } ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.WorkflowType, ctx.RequestID, ctx.Logger) } - -func CreateWorkflowTaskV3(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(commonmodels.WorkflowV3Args) - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateWorkflowTaskV3 c.GetRawData() err : %s", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateWorkflowTaskV3 json.Unmarshal err : %s", err) - } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "工作流v3-task", args.Name, string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - - ctx.Resp, ctx.Err = workflow.CreateWorkflowTaskV3(args, ctx.UserName, ctx.RequestID, ctx.Logger) -} diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index d46bc8da..965eb2f2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -1,7 +1,9 @@ package handler import ( + "bytes" "encoding/json" + "io/ioutil" "github.com/gin-gonic/gin" @@ -25,6 +27,7 @@ func CreateWorkflowV3(c *gin.Context) { log.Errorf("CreateWorkflow unmarshal json err: %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "新增", "工作流V3", req.Name, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindJSON(&req); err != nil { ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go new file mode 100644 index 00000000..dca21680 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -0,0 +1,100 @@ +package handler + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "strconv" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +func CreateWorkflowTaskV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.WorkflowV3Args) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowTaskV3 c.GetRawData() err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateWorkflowTaskV3 json.Unmarshal err : %s", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "创建", "工作流v3-task", args.Name, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Resp, ctx.Err = workflow.CreateWorkflowTaskV3(args, ctx.UserName, ctx.RequestID, ctx.Logger) +} + +func RestartWorkflowTaskV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "重启", "工作流taskV3", c.Param("name"), "", ctx.Logger) + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Err = workflow.RestartWorkflowTaskV3(ctx.UserName, taskID, c.Param("name"), config.WorkflowTypeV3, ctx.Logger) +} + +func CancelWorkflowTaskV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "取消", "工作流taskV3", c.Param("name"), "", ctx.Logger) + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + ctx.Err = commonservice.CancelWorkflowTaskV3(ctx.UserName, c.Param("name"), taskID, config.WorkflowTypeV3, ctx.RequestID, ctx.Logger) +} + +// ListWorkflowV3TasksResult workflowtask分页信息 +func ListWorkflowV3TasksResult(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + maxResult, err := strconv.Atoi(c.Param("max")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid max result number") + return + } + startAt, err := strconv.Atoi(c.Param("start")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid start at number") + return + } + + ctx.Resp, ctx.Err = workflow.ListWorkflowTasksV3Result(c.Param("name"), config.WorkflowTypeV3, maxResult, startAt, ctx.Logger) +} + +func GetWorkflowTaskV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV3(taskID, c.Param("name"), config.WorkflowTypeV3, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go index 1d6313ac..15ba83f4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go @@ -36,16 +36,19 @@ var SubtaskOrder = map[config.TaskType]int{ config.TaskType("jira"): 1, config.TaskType("pipeline"): 2, config.TaskType("buildv2"): 3, - config.TaskType("jenkins_build"): 4, - config.TaskType("docker_build"): 5, - config.TaskType("archive"): 6, - config.TaskType("artifact"): 7, - config.TaskType("deploy"): 8, - config.TaskType("testingv2"): 9, - config.TaskType("security"): 10, - config.TaskType("distribute2kodo"): 11, - config.TaskType("release_image"): 12, - config.TaskType("reset_image"): 13, + config.TaskType("buildv3"): 4, + config.TaskType("jenkins_build"): 5, + config.TaskType("docker_build"): 6, + config.TaskType("archive"): 7, + config.TaskType("artifact"): 8, + config.TaskType("artifact_deploy"): 9, + config.TaskType("deploy"): 10, + config.TaskType("testingv2"): 11, + config.TaskType("security"): 12, + config.TaskType("distribute2kodo"): 13, + config.TaskType("release_image"): 14, + config.TaskType("reset_image"): 15, + config.TaskType("trigger"): 16, } type ByStageKind []*commonmodels.Stage diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 368a4352..6c5d4bf8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1736,7 +1736,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e switch pre.TaskType { - case config.TaskBuild, config.TaskArtifactDeploy: + case config.TaskBuild, config.TaskArtifactDeploy, config.TaskBuildV3: t, err := base.ToBuildTask(subTask) fmtBuildsTask(t, log) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 548e5347..7c190e9b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -1,155 +1,187 @@ package workflow -//func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID string, log *zap.SugaredLogger) (*CreateTaskResp, error) { -// pipeline, err := commonrepo.NewPipelineColl().Find(&commonrepo.PipelineFindOption{Name: args.PipelineName}) -// if err != nil { -// log.Errorf("PipelineV2.Find %s error: %v", args.PipelineName, err) -// return nil, e.ErrCreateTask.AddDesc(e.FindPipelineErrMsg) -// } -// -// nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.PipelineTaskFmt, args.Name)) -// if err != nil { -// log.Errorf("Counter.GetNextSeq error: %v", err) -// return nil, e.ErrGetCounter.AddDesc(err.Error()) -// } -// -// // 更新单服务工作的subtask的build_os -// // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 -// for i, subTask := range pipeline.SubTasks { -// pre, err := base.ToPreview(subTask) -// if err != nil { -// log.Errorf("subTask.ToPreview error: %v", err) -// continue -// } -// switch pre.TaskType { -// case config.TaskBuildV3: -// build, err := base.ToBuildTask(subTask) -// if err != nil || build == nil { -// log.Errorf("subTask.ToBuildTask error: %v", err) -// continue -// } -// if build.ImageID == "" { -// continue -// } -// basicImage, err := commonrepo.NewBasicImageColl().Find(build.ImageID) -// if err != nil { -// log.Errorf("BasicImage.Find failed, id:%s, err:%v", build.ImageID, err) -// continue -// } -// build.BuildOS = basicImage.Value -// -// // 创建任务时可以临时编辑环境变量,需要将pipeline中的环境变量更新成新的值。 -// if args.BuildArgs != nil { -// build.JobCtx.EnvVars = args.BuildArgs -// } -// -// pipeline.SubTasks[i], err = build.ToSubTask() -// if err != nil { -// log.Errorf("build.ToSubTask error: %v", err) -// continue -// } -// } -// } -// -// var defaultStorageURI string -// if defaultS3, err := s3.FindDefaultS3(); err == nil { -// defaultStorageURI, err = defaultS3.GetEncryptedURL() -// if err != nil { -// return nil, e.ErrS3Storage.AddErr(err) -// } -// } -// -// pt := &task.Task{ -// TaskID: nextTaskID, -// ProductName: pipeline.ProductName, -// PipelineName: args.Name, -// Type: config.WorkflowTypeV3, -// TaskCreator: username, -// ReqID: reqID, -// Status: config.StatusCreated, -// SubTasks: pipeline.SubTasks, -// TeamName: pipeline.TeamName, -// ConfigPayload: commonservice.GetConfigPayload(0), -// MultiRun: pipeline.MultiRun, -// StorageURI: defaultStorageURI, -// } -// -// sort.Sort(ByTaskKind(pt.SubTasks)) -// -// for i, t := range pt.SubTasks { -// preview, err := base.ToPreview(t) -// if err != nil { -// continue -// } -// if preview.TaskType != config.TaskDeploy { -// continue -// } -// -// t, err := base.ToDeployTask(t) -// if err == nil && t.Enabled { -// env, err := commonrepo.NewProductColl().FindEnv(&commonrepo.ProductEnvFindOptions{ -// Namespace: pt.TaskArgs.Deploy.Namespace, -// Name: pt.ProductName, -// }) -// -// if err != nil { -// return nil, e.ErrCreateTask.AddDesc( -// e.EnvNotFoundErrMsg + ": " + pt.TaskArgs.Deploy.Namespace, -// ) -// } -// -// t.EnvName = env.EnvName -// t.ProductName = pt.ProductName -// pt.ConfigPayload.DeployClusterID = env.ClusterID -// pt.SubTasks[i], _ = t.ToSubTask() -// } -// } -// -// repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) -// if err != nil { -// return nil, e.ErrCreateTask.AddErr(err) -// } -// -// pt.ConfigPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) -// for _, repo := range repos { -// pt.ConfigPayload.RepoConfigs[repo.ID.Hex()] = repo -// } -// -// if err := ensurePipelineTask(pt, pt.TaskArgs.Deploy.Namespace, log); err != nil { -// log.Errorf("Service.ensurePipelineTask failed %v %v", args, err) -// if err, ok := err.(*ContainerNotFound); ok { -// return nil, e.NewWithExtras( -// e.ErrCreateTaskFailed, -// "container doesn't exists", map[string]interface{}{ -// "productName": err.ProductName, -// "envName": err.EnvName, -// "serviceName": err.ServiceName, -// "containerName": err.Container, -// }) -// } -// -// return nil, e.ErrCreateTask.AddDesc(err.Error()) -// } -// -// if len(pt.SubTasks) <= 0 { -// return nil, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) -// } -// -// if config.EnableGitCheck() { -// if err := createGitCheck(pt, log); err != nil { -// log.Error(err) -// } -// } -// -// // send to queue to execute task -// if err := CreateTask(pt); err != nil { -// log.Error(err) -// return nil, e.ErrCreateTask -// } -// -// resp := &CreateTaskResp{ -// PipelineName: args.Name, -// TaskID: nextTaskID, -// } -// return resp, nil -//} +import ( + "fmt" + "sort" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID string, log *zap.SugaredLogger) (*CreateTaskResp, error) { + workflowV3, err := commonrepo.NewWorkflowV3Coll().GetByID(args.ID) + if err != nil { + log.Errorf("workflowV3.Find %s error: %s", args.Name, err) + return nil, e.ErrCreateTask.AddDesc(e.FindPipelineErrMsg) + } + + nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.WorkflowTaskV3Fmt, args.Name)) + if err != nil { + log.Errorf("Counter.GetNextSeq error: %s", err) + return nil, e.ErrGetCounter.AddDesc(err.Error()) + } + + // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 + for i, subTask := range workflowV3.SubTasks { + pre, err := base.ToPreview(subTask) + if err != nil { + log.Errorf("subTask.ToPreview error: %s", err) + continue + } + switch pre.TaskType { + case config.TaskBuildV3: + build, err := base.ToBuildTask(subTask) + if err != nil || build == nil { + log.Errorf("subTask.ToBuildTask error: %s", err) + continue + } + if build.ImageID == "" { + continue + } + basicImage, err := commonrepo.NewBasicImageColl().Find(build.ImageID) + if err != nil { + log.Errorf("BasicImage.Find failed, id:%s, err:%s", build.ImageID, err) + continue + } + build.BuildOS = basicImage.Value + + // 创建任务时可以临时编辑环境变量,需要将pipeline中的环境变量更新成新的值。 + if args.BuildArgs != nil { + build.JobCtx.EnvVars = args.BuildArgs + } + + workflowV3.SubTasks[i], err = build.ToSubTask() + if err != nil { + log.Errorf("build.ToSubTask error: %s", err) + continue + } + case config.TaskTrigger: + if !pre.Enabled { + continue + } + trigger, err := base.ToTriggerTask(subTask) + if err != nil || trigger == nil { + log.Errorf("subTask.ToTriggerTask error: %s", err) + continue + } + workflowV3.SubTasks[i], err = trigger.ToSubTask() + } + } + + var defaultStorageURI string + if defaultS3, err := s3.FindDefaultS3(); err == nil { + defaultStorageURI, err = defaultS3.GetEncryptedURL() + if err != nil { + return nil, e.ErrS3Storage.AddErr(err) + } + } + + pt := &task.Task{ + TaskID: nextTaskID, + ProductName: args.ProjectName, + PipelineName: args.Name, + Type: config.WorkflowTypeV3, + TaskCreator: username, + ReqID: reqID, + Status: config.StatusCreated, + SubTasks: workflowV3.SubTasks, + ConfigPayload: commonservice.GetConfigPayload(0), + StorageURI: defaultStorageURI, + } + + sort.Sort(ByTaskKind(pt.SubTasks)) + + if err := ensurePipelineTask(pt, "", log); err != nil { + log.Errorf("Service.ensurePipelineTask failed %v %s", args, err) + if err, ok := err.(*ContainerNotFound); ok { + return nil, e.NewWithExtras( + e.ErrCreateTaskFailed, + "container doesn't exists", map[string]interface{}{ + "productName": err.ProductName, + "envName": err.EnvName, + "serviceName": err.ServiceName, + "containerName": err.Container, + }) + } + + return nil, e.ErrCreateTask.AddDesc(err.Error()) + } + + if len(pt.SubTasks) <= 0 { + return nil, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + } + + // send to queue to execute task + if err := CreateTask(pt); err != nil { + log.Error(err) + return nil, e.ErrCreateTask + } + + resp := &CreateTaskResp{ + PipelineName: args.Name, + TaskID: nextTaskID, + } + return resp, nil +} + +func RestartWorkflowTaskV3(userName string, taskID int64, workflowName string, typeString config.PipelineType, log *zap.SugaredLogger) error { + t, err := commonrepo.NewTaskColl().Find(taskID, workflowName, typeString) + if err != nil { + log.Errorf("[%d:%s] find workflow task v3 error: %s", taskID, workflowName, err) + return e.ErrRestartTask.AddDesc(e.FindPipelineTaskErrMsg) + } + + // 不重试已经成功的workflow task + if t.Status == config.StatusRunning || t.Status == config.StatusPassed { + log.Errorf("cannot restart running or passed task. Status: %v", t.Status) + return e.ErrRestartTask.AddDesc(e.RestartPassedTaskErrMsg) + } + + t.IsRestart = true + t.Status = config.StatusCreated + t.TaskCreator = userName + if err := UpdateTask(t); err != nil { + log.Errorf("update workflow task v3 error: %s", err) + return e.ErrRestartTask.AddDesc(e.UpdatePipelineTaskErrMsg) + } + return nil +} + +// ListWorkflowTasksV3Result 工作流任务分页信息 +func ListWorkflowTasksV3Result(name string, typeString config.PipelineType, maxResult, startAt int, log *zap.SugaredLogger) (*TaskResult, error) { + ret := &TaskResult{MaxResult: maxResult, StartAt: startAt} + var err error + ret.Data, err = commonrepo.NewTaskColl().List(&commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Detail: true, Type: typeString}) + if err != nil { + log.Errorf("PipelineTaskV2.List error: %s", err) + return ret, e.ErrListTasks + } + + pipelineList := []string{name} + ret.Total, err = commonrepo.NewTaskColl().Count(&commonrepo.CountTaskOption{PipelineNames: pipelineList, Type: typeString}) + if err != nil { + log.Errorf("workflowTaskV3.List error: %s", err) + return ret, e.ErrCountTasks + } + return ret, nil +} + +func GetWorkflowTaskV3(taskID int64, pipelineName string, typeString config.PipelineType, log *zap.SugaredLogger) (*task.Task, error) { + resp, err := commonrepo.NewTaskColl().Find(taskID, pipelineName, typeString) + if err != nil { + log.Errorf("[%d:%s] PipelineTaskV2.Find error: %s", taskID, pipelineName, err) + return resp, e.ErrGetTask + } + + Clean(resp) + return resp, nil +} diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 6e4fcaca..fe4f528f 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -21,6 +21,7 @@ type TaskType string const ( TaskPipeline TaskType = "pipeline" TaskBuild TaskType = "buildv2" + TaskBuildV3 TaskType = "buildv3" TaskArtifactDeploy TaskType = "artifact_deploy" TaskJenkinsBuild TaskType = "jenkins_build" TaskArtifact TaskType = "artifact" @@ -33,6 +34,7 @@ const ( TaskSecurity TaskType = "security" TaskResetImage TaskType = "reset_image" TaskDistribute TaskType = "distribute" + TaskTrigger TaskType = "trigger" ) type Status string @@ -65,6 +67,8 @@ const ( TestType PipelineType = "test" // ServiceType 服务 ServiceType PipelineType = "service" + // workflowTypeV3 服务 + WorkflowTypeV3 PipelineType = "workflow_v3" ) type NotifyType int diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 301be7bf..07a6c107 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -411,6 +411,9 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } else if pipelineTask.Type == config.ServiceType { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.ServiceType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), servicename)), "_", "-", -1) + } else if pipelineTask.Type == config.WorkflowTypeV3 { + fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.WorkflowTypeV3, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), + "_", "-", -1) } plugin.Init(jobName, fileName, xl) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index b0a13595..5561d10b 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -540,6 +540,7 @@ func initTaskPlugins(execHandler *ExecHandler) { pluginConf := map[config.TaskType]plugins.Initiator{ config.TaskJira: plugins.InitializeJiraTaskPlugin, config.TaskBuild: plugins.InitializeBuildTaskPlugin, + config.TaskBuildV3: plugins.InitializeBuildTaskV3Plugin, config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, @@ -549,6 +550,7 @@ func initTaskPlugins(execHandler *ExecHandler) { config.TaskReleaseImage: plugins.InitializeReleaseImagePlugin, config.TaskDistributeToS3: plugins.InitializeDistribute2S3TaskPlugin, config.TaskResetImage: plugins.InitializeDeployTaskPlugin, + config.TaskTrigger: plugins.InitializeTriggerTaskPlugin, } for name, pluginInitiator := range pluginConf { registerTaskPlugin(execHandler, name, pluginInitiator) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go new file mode 100644 index 00000000..e41ee5a0 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -0,0 +1,349 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "math/rand" + "strconv" + "strings" + "time" + + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +const ( + // BuildTaskV3Timeout ... + BuildTaskV3Timeout = 60 * 60 * 1 // 60 minutes +) + +// InitializeBuildTaskV3Plugin to initialize build task plugin, and return reference +func InitializeBuildTaskV3Plugin(taskType config.TaskType) TaskPlugin { + return &BuildTaskV3Plugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +// BuildTaskV3Plugin is Plugin, name should be compatible with task type +type BuildTaskV3Plugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.Build + Log *zap.SugaredLogger + + ack func() +} + +func (p *BuildTaskV3Plugin) SetAckFunc(ack func()) { + p.ack = ack +} + +// Init ... +func (p *BuildTaskV3Plugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl + p.FileName = filename +} + +func (p *BuildTaskV3Plugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *BuildTaskV3Plugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *BuildTaskV3Plugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +// TaskTimeout ... +func (p *BuildTaskV3Plugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = BuildTaskV3Timeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *BuildTaskV3Plugin) SetBuildStatusCompleted(status config.Status) { + p.Task.BuildStatus.Status = status + p.Task.BuildStatus.EndTime = time.Now().Unix() +} + +//TODO: Binded Archive File logic +func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + taskIDVar := &task.KeyVal{Key: "TASK_ID", Value: strconv.FormatInt(pipelineTask.TaskID, 10), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) + + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + for _, repo := range p.Task.JobCtx.Builds { + repoName := strings.Replace(repo.RepoName, "-", "_", -1) + if len(repo.Branch) > 0 { + branchVar := &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, branchVar) + } + + if len(repo.Tag) > 0 { + tagVar := &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, tagVar) + } + + if repo.PR > 0 { + prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) + } + + if len(repo.CommitID) > 0 { + commitVar := &task.KeyVal{ + Key: fmt.Sprintf("%s_COMMIT_ID", repoName), + Value: repo.CommitID, + IsCredential: false, + } + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, commitVar) + } + } + + jobCtx := JobCtxBuilder{ + JobName: p.JobName, + PipelineCtx: pipelineCtx, + ArchiveFile: p.Task.JobCtx.PackageFile, + JobCtx: p.Task.JobCtx, + Installs: p.Task.InstallCtx, + } + + if p.Task.BuildStatus == nil { + p.Task.BuildStatus = &task.BuildStatus{} + } + + p.Task.BuildStatus.Status = config.StatusRunning + p.Task.BuildStatus.StartTime = time.Now().Unix() + p.ack() + + jobCtxBytes, err := yaml.Marshal(jobCtx.BuildReaperContext(pipelineTask, serviceName)) + if err != nil { + msg := fmt.Sprintf("cannot reaper.Context data: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + if err := createJobConfigMap( + p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient); err != nil { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.Log.Infof("succeed to create cm for build job %s", p.JobName) + + jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) + if p.Task.ImageFrom == setting.ImageFromCustom { + jobImage = p.Task.BuildOS + } + + //Resource request default value is LOW + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + if err != nil { + msg := fmt.Sprintf("create build job context error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + job.Namespace = p.KubeNamespace + + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("delete build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + msg := fmt.Sprintf("create secret error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + if err := updater.CreateJob(job, p.kubeClient); err != nil { + msg := fmt.Sprintf("create build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.Log.Infof("succeed to create build job %s", p.JobName) +} + +// Wait ... +func (p *BuildTaskV3Plugin) Wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + p.SetBuildStatusCompleted(status) + + if status == config.StatusPassed { + if p.Task.DockerBuildStatus == nil { + p.Task.DockerBuildStatus = &task.DockerBuildStatus{} + } + + p.Task.DockerBuildStatus.StartTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusRunning + p.ack() + + select { + case <-ctx.Done(): + p.Task.DockerBuildStatus.EndTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusCancelled + p.Task.TaskStatus = config.StatusCancelled + p.ack() + return + case <-time.After(time.Duration(rand.Int()%2) * time.Second): + p.Task.DockerBuildStatus.EndTime = time.Now().Unix() + p.Task.DockerBuildStatus.Status = config.StatusPassed + } + } + + p.SetStatus(status) +} + +// Complete ... +func (p *BuildTaskV3Plugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + // 清理用户取消和超时的任务 + defer func() { + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return + } + }() + + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + if err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + return + } + + p.Task.LogFile = p.FileName +} + +// SetTask ... +func (p *BuildTaskV3Plugin) SetTask(t map[string]interface{}) error { + task, err := ToBuildTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *BuildTaskV3Plugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *BuildTaskV3Plugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *BuildTaskV3Plugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *BuildTaskV3Plugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *BuildTaskV3Plugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *BuildTaskV3Plugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *BuildTaskV3Plugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go new file mode 100644 index 00000000..ebd27422 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -0,0 +1,165 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "time" + + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" +) + +const ( + // TriggerTaskTimeout ... + TriggerTaskTimeout = 60 * 60 * 1 // 60 minutes +) + +// InitializeTriggerTaskPlugin to initialize build task plugin, and return reference +func InitializeTriggerTaskPlugin(taskType config.TaskType) TaskPlugin { + return &TriggerTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +// TriggerTaskPlugin is Plugin, name should be compatible with task type +type TriggerTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.Trigger + Log *zap.SugaredLogger + + ack func() +} + +func (p *TriggerTaskPlugin) SetAckFunc(ack func()) { + p.ack = ack +} + +// Init ... +func (p *TriggerTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl + p.FileName = filename +} + +func (p *TriggerTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *TriggerTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *TriggerTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +// TaskTimeout ... +func (p *TriggerTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = TriggerTaskTimeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *TriggerTaskPlugin) SetTriggerStatusCompleted(status config.Status) { + p.Task.TaskStatus = status + p.Task.EndTime = time.Now().Unix() +} + +func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + + p.Log.Infof("succeed to create trigger task %s", p.JobName) +} + +// Wait ... +func (p *TriggerTaskPlugin) Wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + p.SetTriggerStatusCompleted(status) + + p.SetStatus(status) +} + +// Complete ... +func (p *TriggerTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { +} + +// SetTask ... +func (p *TriggerTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToTriggerTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *TriggerTaskPlugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *TriggerTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *TriggerTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *TriggerTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *TriggerTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *TriggerTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *TriggerTaskPlugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 6d411101..d16e6bbe 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -195,3 +195,11 @@ func IToi(before interface{}, after interface{}) error { return nil } + +func ToTriggerTask(sb map[string]interface{}) (*task.Trigger, error) { + var trigger *task.Trigger + if err := task.IToi(sb, &trigger); err != nil { + return nil, fmt.Errorf("convert interface to triggerTask error: %s", err) + } + return trigger, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go new file mode 100644 index 00000000..8bb32ce0 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -0,0 +1,30 @@ +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +) + +type Trigger struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url,omitempty" json:"url,omitempty"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +// ToSubTask ... +func (t *Trigger) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert trigger to interface error: %s", err) + } + return task, nil +} -- Gitee From 3f636bdd077524f03e501107ed20e5883af661fa Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 18 Nov 2021 21:45:28 +0800 Subject: [PATCH 0703/2647] update after review Signed-off-by: lou --- .../aslan/core/common/repository/mongodb/task.go | 2 +- pkg/microservice/aslan/core/workflow/handler/workflow.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index 6a845632..333bb5fb 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -246,7 +246,7 @@ func (c *TaskColl) ListRecentTasks(args *ListTaskOption) ([]*TaskInfo, error) { return nil, err } - return res, err + return res, nil } func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index 664d0270..c73934a4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -119,7 +119,12 @@ func ListWorkflows(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projects := c.QueryArray("projects") - if c.Query("projectName") != "" { + projectName := c.Query("projectName") + if projectName != "" && len(projects) > 0 { + ctx.Err = e.ErrInvalidParam.AddDesc("projects and projectName can not be set together") + return + } + if projectName != "" { projects = []string{c.Query("projectName")} } -- Gitee From eee56fb81f748eb2ff75f2daee0db7c3a8ccb10f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 22:13:23 +0800 Subject: [PATCH 0704/2647] optimize create workflow logic Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/workflow_v3.go | 2 +- pkg/microservice/aslan/core/workflow/service/workflow/types.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go index bfb78d8d..dc059064 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -6,7 +6,7 @@ import ( ) type WorkflowV3 struct { - ID primitive.ObjectID `bson:"_id"` + ID primitive.ObjectID `bson:"_id,omitempty"` Name string `bson:"name"` ProjectName string `bson:"project_name"` Description string `bson:"description"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index 9c254f6d..69f0817e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -6,7 +6,7 @@ type WorkflowV3 struct { ProjectName string `json:"project_name"` Description string `json:"description"` Parameters []*ParameterSetting `json:"parameters"` - Subtasks []map[string]interface{} `json:"subtasks"` + SubTasks []map[string]interface{} `bson:"sub_tasks"` } type WorkflowV3Brief struct { -- Gitee From 36263ea53a07e6ea133f1bf9e435ca43a158c41e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 22:21:42 +0800 Subject: [PATCH 0705/2647] optimize create workflow logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/service/workflow/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index 69f0817e..ff28f80a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -6,7 +6,7 @@ type WorkflowV3 struct { ProjectName string `json:"project_name"` Description string `json:"description"` Parameters []*ParameterSetting `json:"parameters"` - SubTasks []map[string]interface{} `bson:"sub_tasks"` + SubTasks []map[string]interface{} `json:"sub_tasks"` } type WorkflowV3Brief struct { -- Gitee From d8492f4d2ed3120ef56371951421e6e33294236b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 22:34:23 +0800 Subject: [PATCH 0706/2647] optimize create workflow logic Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index de6889d9..8964d59e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -17,13 +17,17 @@ func CreateWorkflowV3(user string, workflow *WorkflowV3, logger *zap.SugaredLogg logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) return err } - err = json.Unmarshal(out, &workflowModel) + logger.Infof("out:%s", string(out)) + err = json.Unmarshal(out, workflowModel) if err != nil { logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) return err } + logger.Infof("workflowModel:%+v", workflowModel) workflowModel.CreatedBy = user + workflowModel.UpdatedBy = user workflowModel.CreateTime = time.Now().Unix() + workflowModel.UpdateTime = time.Now().Unix() return commonrepo.NewWorkflowV3Coll().Create(workflowModel) } -- Gitee From 339a58d98977c8d8c9efa8932a14af389dfe4f60 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 22:47:00 +0800 Subject: [PATCH 0707/2647] optimize create workflow logic Signed-off-by: liu deyi --- .../core/workflow/handler/workflow_v3.go | 3 +- .../workflow/service/workflow/workflow_v3.go | 45 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index 965eb2f2..e98259a6 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -7,6 +7,7 @@ import ( "github.com/gin-gonic/gin" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/errors" @@ -17,7 +18,7 @@ func CreateWorkflowV3(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &workflowservice.WorkflowV3{} + req := &commonmodels.WorkflowV3{} data, err := c.GetRawData() if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index 8964d59e..e04d9887 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -10,20 +10,37 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" ) -func CreateWorkflowV3(user string, workflow *WorkflowV3, logger *zap.SugaredLogger) error { - workflowModel := new(commonmodels.WorkflowV3) - out, err := json.Marshal(workflow) - if err != nil { - logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) - return err - } - logger.Infof("out:%s", string(out)) - err = json.Unmarshal(out, workflowModel) - if err != nil { - logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) - return err - } - logger.Infof("workflowModel:%+v", workflowModel) +func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { + //workflowModel := &commonmodels.WorkflowV3{ + // Name: workflow.Name, + // ProjectName: workflow.ProjectName, + // Description: workflow.Description, + // Parameters: workflow.Parameters, + // SubTasks: workflow.SubTasks, + //} + //out, err := json.Marshal(workflow) + //if err != nil { + // logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) + // return err + //}out, err := json.Marshal(workflow) + // //if err != nil { + // // logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) + // // return err + // //} + // //logger.Infof("out:%s", string(out)) + // //err = json.Unmarshal(out, workflowModel) + // //if err != nil { + // // logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) + // // return err + // //} + // //logger.Infof("workflowModel:%+v", workflowModel) + //logger.Infof("out:%s", string(out)) + //err = json.Unmarshal(out, workflowModel) + //if err != nil { + // logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) + // return err + //} + //logger.Infof("workflowModel:%+v", workflowModel) workflowModel.CreatedBy = user workflowModel.UpdatedBy = user workflowModel.CreateTime = time.Now().Unix() -- Gitee From 57432a8f8c01f7f051f60c1bed38c740ce33759b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 23:04:29 +0800 Subject: [PATCH 0708/2647] optimize create workflow logic Signed-off-by: liu deyi --- .../common/repository/models/workflow_v3.go | 22 ++++++------- .../core/workflow/handler/workflow_v3.go | 1 - .../workflow/service/workflow/workflow_v3.go | 31 +------------------ 3 files changed, 12 insertions(+), 42 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go index dc059064..cccfabe5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -6,16 +6,16 @@ import ( ) type WorkflowV3 struct { - ID primitive.ObjectID `bson:"_id,omitempty"` - Name string `bson:"name"` - ProjectName string `bson:"project_name"` - Description string `bson:"description"` - Parameters []*ParameterSetting `bson:"parameters"` - SubTasks []map[string]interface{} `bson:"sub_tasks"` - CreatedBy string `bson:"created_by"` - CreateTime int64 `bson:"create_time"` - UpdatedBy string `bson:"updated_by"` - UpdateTime int64 `bson:"update_time"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + ProjectName string `bson:"project_name" json:"project_name"` + Description string `bson:"description" json:"description"` + Parameters []*ParameterSetting `bson:"parameters" json:"parameters"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + CreatedBy string `bson:"created_by" json:"created_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdatedBy string `bson:"updated_by" json:"updated_by"` + UpdateTime int64 `bson:"update_time" json:"update_time"` } type ParameterSetting struct { @@ -64,8 +64,8 @@ type ExternalParamMapping struct { // WorkflowV3Args 工作流v3任务参数 type WorkflowV3Args struct { - ProjectName string `bson:"project_name" json:"project_name"` ID string `bson:"id" json:"id"` + ProjectName string `bson:"project_name" json:"project_name"` Name string `bson:"name" json:"name"` Builds []*types.Repository `bson:"builds" json:"builds"` BuildArgs []*KeyVal `bson:"build_args" json:"build_args"` diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index e98259a6..fbfedd98 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -19,7 +19,6 @@ func CreateWorkflowV3(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() req := &commonmodels.WorkflowV3{} - data, err := c.GetRawData() if err != nil { log.Errorf("CreateWorkflowV3 err: %s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index e04d9887..9515c9d5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -11,36 +11,7 @@ import ( ) func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { - //workflowModel := &commonmodels.WorkflowV3{ - // Name: workflow.Name, - // ProjectName: workflow.ProjectName, - // Description: workflow.Description, - // Parameters: workflow.Parameters, - // SubTasks: workflow.SubTasks, - //} - //out, err := json.Marshal(workflow) - //if err != nil { - // logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) - // return err - //}out, err := json.Marshal(workflow) - // //if err != nil { - // // logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) - // // return err - // //} - // //logger.Infof("out:%s", string(out)) - // //err = json.Unmarshal(out, workflowModel) - // //if err != nil { - // // logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) - // // return err - // //} - // //logger.Infof("workflowModel:%+v", workflowModel) - //logger.Infof("out:%s", string(out)) - //err = json.Unmarshal(out, workflowModel) - //if err != nil { - // logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) - // return err - //} - //logger.Infof("workflowModel:%+v", workflowModel) + logger.Infof("workflowModel:%+v", workflowModel) workflowModel.CreatedBy = user workflowModel.UpdatedBy = user workflowModel.CreateTime = time.Now().Unix() -- Gitee From 7a5ed67a7f21f468d23de6b43dcfcc125a4ac4aa Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 09:54:51 +0800 Subject: [PATCH 0709/2647] optimize create workflow logic Signed-off-by: liu deyi --- .../common/repository/models/workflow_v3.go | 32 +++++++-------- .../core/workflow/handler/workflow_v3.go | 2 +- .../workflow/service/workflow/workflow_v3.go | 41 +++++++++++-------- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go index cccfabe5..4567087f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -20,46 +20,46 @@ type WorkflowV3 struct { type ParameterSetting struct { // External type parameter will NOT use this key. - Key string `bson:"key"` + Key string `bson:"key" json:"key"` // Type list: // string // choice // external - Type string `bson:"type"` + Type string `bson:"type" json:"type"` //DefaultValue is the - DefaultValue string `bson:"default_value"` + DefaultValue string `bson:"default_value" json:"default_value"` // choiceOption 是枚举的所有选项 - ChoiceOption []string `bson:"choice_option"` + ChoiceOption []string `bson:"choice_option" json:"choice_option"` // ExternalSetting 是外部系统获取变量的配置 - ExternalSetting *ExternalSetting `bson:"external_setting"` + ExternalSetting *ExternalSetting `bson:"external_setting" json:"external_setting"` } type ExternalSetting struct { // 外部系统ID - SystemID string `bson:"system_id"` + SystemID string `bson:"system_id" json:"system_id"` // Endpoint路径 - Endpoint string `bson:"endpoint"` + Endpoint string `bson:"endpoint" json:"endpoint"` // 请求方法 - Method string `bson:"method"` + Method string `bson:"method" json:"method"` // 请求头 - Headers []*KV `bson:"headers"` + Headers []*KV `bson:"headers" json:"headers"` // 请求体 - Body string `bson:"body"` + Body string `bson:"body" json:"body"` // 外部变量配置 - Params []*ExternalParamMapping `bson:"params"` + Params []*ExternalParamMapping `bson:"params" json:"params"` } type KV struct { - Key string `bson:"key"` - Value string `bson:"value"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` } type ExternalParamMapping struct { // zadig变量名称 - ParamKey string `bson:"param_key"` + ParamKey string `bson:"param_key" json:"param_key"` // 返回中的key的位置 - ResponseKey string `bson:"response_key"` - Display bool `bson:"display"` + ResponseKey string `bson:"response_key" json:"response_key"` + Display bool `bson:"display" json:"display"` } // WorkflowV3Args 工作流v3任务参数 diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index fbfedd98..ad6cd072 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -78,7 +78,7 @@ func UpdateWorkflowV3(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &workflowservice.WorkflowV3{} + req := &commonmodels.WorkflowV3{} data, err := c.GetRawData() if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index 9515c9d5..7af1a31a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -8,15 +8,20 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" ) func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { - logger.Infof("workflowModel:%+v", workflowModel) workflowModel.CreatedBy = user workflowModel.UpdatedBy = user workflowModel.CreateTime = time.Now().Unix() workflowModel.UpdateTime = time.Now().Unix() - return commonrepo.NewWorkflowV3Coll().Create(workflowModel) + + if err := commonrepo.NewWorkflowV3Coll().Create(workflowModel); err != nil { + logger.Errorf("Failed to create workflow v3, the error is: %s", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + return nil } func ListWorkflowsV3(projectName string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*WorkflowV3Brief, int64, error) { @@ -58,34 +63,36 @@ func GetWorkflowV3Detail(id string, logger *zap.SugaredLogger) (*WorkflowV3, err return resp, nil } -func UpdateWorkflowV3(id, user string, workflow *WorkflowV3, logger *zap.SugaredLogger) error { - workflowModel := new(commonmodels.WorkflowV3) - out, err := json.Marshal(workflow) - if err != nil { - logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) - return err - } - err = json.Unmarshal(out, &workflowModel) - if err != nil { - logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) - return err - } +func UpdateWorkflowV3(id, user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { + //workflowModel := new(commonmodels.WorkflowV3) + //out, err := json.Marshal(workflow) + //if err != nil { + // logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) + // return err + //} + //err = json.Unmarshal(out, &workflowModel) + //if err != nil { + // logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) + // return err + //} workflowModel.UpdatedBy = user workflowModel.UpdateTime = time.Now().Unix() - err = commonrepo.NewWorkflowV3Coll().Update( + err := commonrepo.NewWorkflowV3Coll().Update( id, workflowModel, ) if err != nil { logger.Errorf("update workflowV3 error: %s", err) + return e.ErrUpsertWorkflow.AddErr(err) } - return err + return nil } func DeleteWorkflowV3(id string, logger *zap.SugaredLogger) error { err := commonrepo.NewWorkflowV3Coll().DeleteByID(id) if err != nil { logger.Errorf("Failed to WorkflowV3 of id: %s, the error is: %s", id, err) + return e.ErrDeleteWorkflow.AddErr(err) } - return err + return nil } -- Gitee From 052add16368f8c2e5b4959396697b67a18ef2fdb Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 10:31:01 +0800 Subject: [PATCH 0710/2647] optimize update workflow logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/handler/workflow_v3.go | 1 + pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index ad6cd072..a06f6c92 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -88,6 +88,7 @@ func UpdateWorkflowV3(c *gin.Context) { log.Errorf("UpdateWorkflowV3 unmarshal json err: %s", err) } internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "修改", "工作流V3", req.Name, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.ShouldBindJSON(&req); err != nil { ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go index e41ee5a0..208e5441 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -103,7 +103,6 @@ func (p *BuildTaskV3Plugin) SetBuildStatusCompleted(status config.Status) { p.Task.BuildStatus.EndTime = time.Now().Unix() } -//TODO: Binded Archive File logic func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { taskIDVar := &task.KeyVal{Key: "TASK_ID", Value: strconv.FormatInt(pipelineTask.TaskID, 10), IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) -- Gitee From 528c02773cd322f6f3abd8b13e462544336f127c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 11:26:50 +0800 Subject: [PATCH 0711/2647] optimize create workflow task logic Signed-off-by: liu deyi --- .../core/common/repository/models/task/build.go | 1 + pkg/microservice/reaper/core/service/meta/types.go | 1 + .../reaper/core/service/reaper/artifact.go | 3 +-- .../reaper/core/service/reaper/reaper.go | 14 +++++++++++--- .../warpdrive/core/service/taskplugin/job.go | 3 +++ .../warpdrive/core/service/types/reaper.go | 1 + .../warpdrive/core/service/types/task/build.go | 1 + 7 files changed, 19 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index c8211297..36907d2e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -148,6 +148,7 @@ type JobCtx struct { TestType string `bson:"test_type" json:"test_type"` // Caches Caches []string `bson:"caches" json:"caches"` + ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` IsHasArtifact bool `bson:"is_has_artifact" json:"is_has_artifact"` // StorageUri is used for qbox release-candidates diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 4a587d48..065e46da 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -115,6 +115,7 @@ type Context struct { StorageBucket string `yaml:"storage_bucket"` StorageProvider int `yaml:"storage_provider"` ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` + ArtifactPath string `yaml:"artifact_path"` } type ArtifactInfo struct { diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index ff93b4ff..49227243 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -31,7 +31,7 @@ import ( s3tool "github.com/koderover/zadig/pkg/tool/s3" ) -func artifactsUpload(ctx *meta.Context, activeWorkspace string) error { +func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths []string) error { var ( err error store *s3.S3 @@ -49,7 +49,6 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string) error { } } - artifactPaths := ctx.GinkgoTest.ArtifactPaths for _, artifactPath := range artifactPaths { if len(artifactPath) == 0 { continue diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index a7a14136..22516327 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -409,13 +409,21 @@ func (r *Reaper) AfterExec(upStreamErr error) error { return err } } - // 将测试文件导出地址的文件上传到S3 + // 将归档文件上传到S3 if len(r.Ctx.GinkgoTest.ArtifactPaths) > 0 { - if err = artifactsUpload(r.Ctx, r.ActiveWorkspace); err != nil { - log.Errorf("artifactsUpload err %v", err) + if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, r.Ctx.GinkgoTest.ArtifactPaths); err != nil { + log.Errorf("artifactsUpload err %s", err) return err } } + + if r.Ctx.ArtifactPath != "" { + if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}); err != nil { + log.Errorf("artifactsUpload err %s", err) + return err + } + } + // 将上面生成的统计结果文件上传到S3 if err = r.archiveTestFiles(); err != nil { log.Errorf("archiveTestFiles err %v", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1d13553e..3b531df5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -319,6 +319,9 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName FileName: pipelineTask.ArtifactInfo.FileName, } } + if len(b.JobCtx.ArtifactPaths) > 0 { + ctx.ArtifactPaths = b.JobCtx.ArtifactPaths + } return ctx } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 9ed4b2fd..2ca8cca9 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -113,6 +113,7 @@ type Context struct { StorageBucket string `yaml:"storage_bucket"` StorageProvider int8 `yaml:"storage_provider"` ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` + ArtifactPath string `yaml:"artifact_path"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 4d6e55c3..5712a916 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -172,6 +172,7 @@ type JobCtx struct { TestType string `bson:"test_type" json:"test_type"` // Caches Caches []string `bson:"caches" json:"caches"` + ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` IsHasArtifact bool `bson:"is_has_artifact" json:"is_has_artifact"` // StorageUri is used for qbox release-candidates -- Gitee From f3e3075ca4b3ab117cae005c17483c79365ae8e4 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 19 Nov 2021 11:29:56 +0800 Subject: [PATCH 0712/2647] fix thirdparty network error Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/login/third_party.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index c4b555a7..cedec6a7 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -7,12 +7,14 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" "golang.org/x/oauth2" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/login" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" @@ -39,6 +41,7 @@ func Login(c *gin.Context) { RedirectURL: config.RedirectURI(), } authCodeURL := oauth2Config.AuthCodeURL(config.AppState, oauth2.AccessTypeOffline) + authCodeURL = strings.Replace(authCodeURL, config.IssuerURL(), configbase.SystemAddress()+"/dex", -1) c.Redirect(http.StatusSeeOther, authCodeURL) } -- Gitee From 0c6f09595d1f3f94d741c9718303f0c368767c3b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 11:33:28 +0800 Subject: [PATCH 0713/2647] optimize create workflow task logic Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 4 ++-- pkg/microservice/warpdrive/core/service/types/task/build.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 3b531df5..441a1f5a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -319,8 +319,8 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName FileName: pipelineTask.ArtifactInfo.FileName, } } - if len(b.JobCtx.ArtifactPaths) > 0 { - ctx.ArtifactPaths = b.JobCtx.ArtifactPaths + if b.JobCtx.ArtifactPath != "" { + ctx.ArtifactPath = b.JobCtx.ArtifactPath } return ctx diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 5712a916..bdbbf725 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -171,7 +171,8 @@ type JobCtx struct { // TestType TestType string `bson:"test_type" json:"test_type"` // Caches - Caches []string `bson:"caches" json:"caches"` + Caches []string `bson:"caches" json:"caches"` + // buildV3 ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` IsHasArtifact bool `bson:"is_has_artifact" json:"is_has_artifact"` -- Gitee From 96ab55829b11a1be68ba4b2514e70b58dcf8afc1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 19 Nov 2021 11:42:46 +0800 Subject: [PATCH 0714/2647] make basic progress work Signed-off-by: allenshen --- go.mod | 1 + go.sum | 5 + pkg/config/config.go | 4 + .../repository/models/delivery_version.go | 10 + .../repository/mongodb/delivery_version.go | 28 ++ .../common/repository/mongodb/helm_repo.go | 32 +- .../aslan/core/delivery/handler/router.go | 2 + .../aslan/core/delivery/handler/version.go | 14 + .../aslan/core/delivery/service/version.go | 347 ++++++++++++++++++ .../aslan/core/environment/service/helm.go | 68 ++-- .../aslan/core/service/handler/helm.go | 7 +- .../aslan/core/system/handler/helm.go | 1 + pkg/setting/consts.go | 11 + 13 files changed, 496 insertions(+), 34 deletions(-) diff --git a/go.mod b/go.mod index 3c65dad5..ddc70b3d 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v4 v4.1.1 + github.com/chartmuseum/helm-push v0.10.1 github.com/coocood/freecache v1.1.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b diff --git a/go.sum b/go.sum index 7fa362e8..e7218b75 100644 --- a/go.sum +++ b/go.sum @@ -286,6 +286,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chartmuseum/helm-push v0.10.1 h1:NqStAmarEy0GnrDCk0zubGKeRmkhOm/rRi5au8h76BA= +github.com/chartmuseum/helm-push v0.10.1/go.mod h1:s6xTICU31jKdLkOXS+GgaR61E+oU4h8TWb1yZcHq8OE= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -536,6 +538,7 @@ github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -2349,6 +2352,8 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao= +k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= diff --git a/pkg/config/config.go b/pkg/config/config.go index 78ff4533..4f324676 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -210,6 +210,10 @@ func ObjectStorageTemplatePath(name, kind string) string { return filepath.Join("templates", kind, name) } +func ObjectStorageDeliveryVersionPath(project string) string { + return filepath.Join(project, "delivery-versions") +} + func ObjectStorageChartTemplatePath(name string) string { return ObjectStorageTemplatePath(name, setting.ChartTemplatesPath) } diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 7efeda7d..8fcc93f8 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -20,6 +20,12 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) +type DeliveryChart struct { + Name string `bson:"name" json:"name"` + Version string `bson:"version" json:"version"` + Repo string `bson:"repo" json:"repo"` +} + type DeliveryVersion struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` Version string `bson:"version" json:"version"` @@ -30,6 +36,10 @@ type DeliveryVersion struct { Desc string `bson:"desc" json:"desc"` Labels []string `bson:"labels" json:"labels"` ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` + Charts []*DeliveryChart `bson:"charts" json:"charts"` + Status string `bson:"status" json:"status"` + Error string `bson:"log" json:"log"` + CreateArgument interface{} `bson:"createArgument" json:"-"` CreatedBy string `bson:"created_by" json:"createdBy"` CreatedAt int64 `bson:"created_at" json:"created_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index bae5fc66..35547768 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -190,6 +190,34 @@ func (c *DeliveryVersionColl) Insert(args *models.DeliveryVersion) error { return nil } +func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr string) error { + query := bson.M{"version": versionName, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "status": status, + "error": errorStr, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *DeliveryVersionColl) AddDeliveryChart(versionName string, chartInfo *models.DeliveryChart) error { + query := bson.M{ + "version": versionName, + "deleted_at": 0, + "charts.name": bson.M{"$ne": chartInfo.Name}, + } + change := bson.M{ + "$push": bson.M{ + "charts": bson.M{ + "name": chartInfo.Name, + "version": chartInfo.Version, + "repo": chartInfo.Repo, + }, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { if args == nil { return errors.New("nil delivery_version args") diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go b/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go index 6d773167..86fff875 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go @@ -37,6 +37,11 @@ type HelmRepoColl struct { coll string } +type HelmRepoFindOption struct { + Id string + RepoName string +} + func NewHelmRepoColl() *HelmRepoColl { name := models.HelmRepo{}.TableName() coll := &HelmRepoColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} @@ -49,7 +54,12 @@ func (c *HelmRepoColl) GetCollectionName() string { } func (c *HelmRepoColl) EnsureIndex(ctx context.Context) error { - return nil + mod := mongo.IndexModel{ + Keys: bson.M{"repo_name": 1}, + Options: options.Index().SetUnique(false), + } + _, err := c.Indexes().CreateOne(ctx, mod) + return err } func (c *HelmRepoColl) Create(args *models.HelmRepo) error { @@ -64,6 +74,26 @@ func (c *HelmRepoColl) Create(args *models.HelmRepo) error { return err } +func (c *HelmRepoColl) Find(opt *HelmRepoFindOption) (*models.HelmRepo, error) { + query := bson.M{} + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return nil, err + } + query["_id"] = oid + } + if len(opt.RepoName) > 0 { + query["repo_name"] = opt.RepoName + } + ret := new(models.HelmRepo) + err := c.FindOne(context.TODO(), query).Decode(ret) + if err != nil { + return nil, err + } + return ret, nil +} + func (c *HelmRepoColl) Update(id string, args *models.HelmRepo) error { oid, err := primitive.ObjectIDFromHex(id) if err != nil { diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 889b7e7a..7b532a81 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -45,6 +45,8 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.GET("/:id", GetDeliveryVersion) deliveryRelease.GET("", ListDeliveryVersion) deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) + + deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) } deliveryPackage := router.Group("packages") diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 44ea5e99..4ac0900d 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -326,6 +326,20 @@ func ListPackagesVersion(c *gin.Context) { ctx.Resp = fileInfoList } +func CreateHelmDeliveryVersion(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.CreateHelmDeliveryVersionArgs) + err := c.ShouldBindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + args.CreateBy = ctx.UserName + ctx.Err = deliveryservice.CreateHelmDeliveryVersion(args, ctx.Logger) +} + func DeleteDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 1c013f68..de24a038 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -17,14 +17,77 @@ limitations under the License. package service import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "sync" + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" + + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + + fsutil "github.com/koderover/zadig/pkg/util/fs" + + "github.com/hashicorp/go-multierror" + + cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" + "github.com/chartmuseum/helm-push/pkg/helm" + "github.com/otiai10/copy" + "github.com/pkg/errors" "go.uber.org/zap" + chartloader "helm.sh/helm/v3/pkg/chart/loader" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/yaml" + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" ) +type CreateHelmDeliveryVersionOption struct { + EnableOfflineDist bool `json:"enableOfflineDist"` + S3StorageID string `json:"s3StorageID"` +} + +type CreateHelmDeliveryVersionChartData struct { + ServiceName string `json:"serviceName"` + Version string `json:"version"` + ValuesYamlContent string `json:"valuesYamlContent"` +} + +type CreateHelmDeliveryVersionArgs struct { + CreateBy string `json:"-"` + Version string `json:"version"` + Desc string `json:"desc"` + ProductName string `json:"productName"` + EnvName string `json:"envName"` + Labels []string `json:"labels"` + //ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` + ChartRepoName string `json:"chartRepoName"` + //Options *CreateHelmDeliveryVersionOption `json:"options"` + *DeliveryVersionChartData +} + +type DeliveryVersionChartData struct { + ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` + Options *CreateHelmDeliveryVersionOption `json:"options"` +} + +type ChartDeliveryData struct { + ChartData *CreateHelmDeliveryVersionChartData + ProductService *commonmodels.ProductService + ServiceObj *commonmodels.Service +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { resp, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { @@ -52,6 +115,290 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } +func getProductEnvInfo(productName, envName string) (*commonmodels.Product, error) { + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + log.Errorf("failed to query product info, productName: %s envName: %s err: %s", productName, envName, err) + return nil, fmt.Errorf("failed to query product info, productName: %s envName: %s", productName, envName) + } + return productInfo, nil +} + +func getChartRepoData(repoName string) (*commonmodels.HelmRepo, error) { + return commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: repoName}) +} + +func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { + client, err := cm.NewClient( + cm.URL(repo.URL), + cm.Username(repo.Username), + cm.Password(repo.Password), + // need support more auth types + ) + if err != nil { + return nil, errors.Wrapf(err, "failed to create chart repo client") + } + return client, nil +} + +func handleSingleChart(chartData *ChartDeliveryData, chartRepo *commonmodels.HelmRepo, dir string) error { + serviceObj := chartData.ServiceObj + serviceName, revision := serviceObj.ServiceName, serviceObj.Revision + base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) + if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) + // use the latest version when it fails to download the specific version + base = config.LocalServicePath(serviceObj.ProductName, serviceName) + if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { + log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) + return err + } + } + + fullPath := filepath.Join(base, serviceObj.ServiceName) + revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) + deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) + err := copy.Copy(fullPath, deliveryChartPath) + if err != nil { + return nil + } + + //load chart info from local storage + chartRequested, err := chartloader.Load(deliveryChartPath) + if err != nil { + return errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) + } + + //set version and values.yaml content + chartRequested.Metadata.Name = chartData.ChartData.ServiceName + chartRequested.Metadata.Version = chartData.ChartData.Version + chartRequested.Metadata.AppVersion = chartData.ChartData.Version + if len(chartData.ChartData.ValuesYamlContent) > 0 { + valuesInfo := make(map[string]interface{}) + if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { + log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) + return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) + } + chartRequested.Values = valuesInfo + } + + chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) + if err != nil { + return err + } + + client, err := createChartRepoClient(chartRepo) + if err != nil { + return err + } + + log.Infof("pushing %s to %s...\n", filepath.Base(chartPackagePath), chartRepo.URL) + resp, err := client.UploadChartPackage(chartPackagePath, false) + if err != nil { + return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + } + err = handlePushResponse(resp) + if err != nil { + return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) + } + return nil +} + +func handlePushResponse(resp *http.Response) error { + if resp.StatusCode != 201 && resp.StatusCode != 202 { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return getChartmuseumError(b, resp.StatusCode) + } + log.Infof("push chart to chart repo done") + return nil +} + +func getChartmuseumError(b []byte, code int) error { + var er struct { + Error string `json:"error"` + } + err := json.Unmarshal(b, &er) + if err != nil || er.Error == "" { + return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) + } + return errors.Errorf("%d: %s", code, er.Error) +} + +func mkChartTGZFileDir(versionName string) (string, error) { + tmpDir := os.TempDir() + path := filepath.Join(tmpDir, "chart-tgz", versionName) + if err := os.RemoveAll(path); err != nil { + if !os.IsExist(err) { + return "", errors.Wrapf(err, "failed to claer dir for chart tgz files") + } + } + err := os.MkdirAll(path, 0777) + if err != nil { + return "", errors.Wrapf(err, "failed to create chart tgz dir for version: %s", versionName) + } + return path, nil +} + +func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { + // need appoint chart info + if len(args.ChartDatas) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("no chart info appointed") + } + + // prepare data + productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) + if err != nil { + log.Infof("failed to query product info, procutName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) + } + repoInfo, err := getChartRepoData(args.ChartRepoName) + if err != nil { + log.Infof("failed to query chart-repo info, procutName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query chart-repo info, procutName: %s envName %s", args.ProductName, args.EnvName)) + } + + dir, err := mkChartTGZFileDir(args.Version) + if err != nil { + return e.ErrCreateDeliveryVersion.AddErr(err) + } + + // validate chartInfo, make sure service is in environment + // prepare data set for chart delivery + chartDataMap := make(map[string]*ChartDeliveryData) + serviceMap := productInfo.GetServiceMap() + for _, chartDta := range args.ChartDatas { + if productService, ok := serviceMap[chartDta.ServiceName]; ok { + serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: chartDta.ServiceName, + Revision: productService.Revision, + Type: setting.HelmDeployType, + ProductName: args.ProductName, + }) + if err != nil { + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query service: %s", chartDta.ServiceName)) + } + chartDataMap[chartDta.ServiceName] = &ChartDeliveryData{ + ChartData: chartDta, + ProductService: productService, + ServiceObj: serviceObj, + } + } else { + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("service %s not found in environment", chartDta.ServiceName)) + } + } + + productInfo.ID, _ = primitive.ObjectIDFromHex("") + + versionObj := &commonmodels.DeliveryVersion{ + Version: args.Version, + ProductName: args.ProductName, + Type: setting.DeliveryVersionTypeChart, + Desc: args.Desc, + Labels: args.Labels, + ProductEnvInfo: productInfo, + Status: setting.DeliveryVersionStatusCreating, + CreateArgument: args.DeliveryVersionChartData, + CreatedBy: args.CreateBy, + CreatedAt: time.Now().Unix(), + DeletedAt: 0, + } + + err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) + if err != nil { + logger.Errorf("failed to insert version data, err: %s", err) + return fmt.Errorf("failed to insert delivery version: %s", versionObj.Version) + } + + go func() { + var err error + defer func() { + if err != nil { + versionObj.Status = setting.DeliveryVersionStatusFailed + versionObj.Error = err.Error() + } else { + versionObj.Status = setting.DeliveryVersionStatusSuccess + versionObj.Error = "" + } + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionObj.Version, versionObj.Status, versionObj.Error) + if err != nil { + logger.Errorf("failed to update delivery version data, name: %s error: %s", versionObj.Version, err) + } + }() + + var errLock sync.Mutex + errorList := &multierror.Error{} + + appendError := func(err error) { + errLock.Lock() + defer errLock.Unlock() + errorList = multierror.Append(errorList, err) + } + + // push charts to repo + wg := sync.WaitGroup{} + for _, chartData := range chartDataMap { + wg.Add(1) + go func(cData *ChartDeliveryData) { + defer wg.Done() + err := handleSingleChart(cData, repoInfo, dir) + if err != nil { + logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + appendError(err) + } else { + err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(versionObj.Version, &commonmodels.DeliveryChart{ + Name: cData.ServiceObj.ServiceName, + Version: cData.ChartData.Version, + Repo: args.ChartRepoName, + }) + if err != nil { + appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) + } + } + }(chartData) + } + wg.Wait() + + if errorList.ErrorOrNil() != nil { + err = errorList.ErrorOrNil() + return + } + + tmpDir, err := os.MkdirTemp("", "delivery-") + if err != nil { + logger.Errorf("failed to create temp dir, err: %s", err) + return + } + defer os.RemoveAll(tmpDir) + + //tar all chart files and send to s3 store + fsTree := os.DirFS(dir) + tarball := fmt.Sprintf("%s.tar.gz", versionObj.Version) + localPath := filepath.Join(tmpDir, tarball) + if err = fsutil.Tar(fsTree, localPath); err != nil { + logger.Errorf("failed to archive tarball %s, err: %s", localPath, err) + versionObj.Status = setting.DeliveryVersionStatusFailed + err = errors.Wrapf(err, "failed to archive chart files, path %s", localPath) + return + } + + ServiceS3Base := configbase.ObjectStorageDeliveryVersionPath(args.ProductName) + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, versionObj.Version, ServiceS3Base, nil, logger); err != nil { + logger.Errorf("failed to upload chart package files for project %s, err: %s", args.ProductName, err) + err = errors.Wrapf(err, "failed to upload package file") + return + } + + }() + + return nil +} + func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]string, error) { serviceNames := sets.String{} diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 64fe4fb2..b4087f30 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -23,26 +23,23 @@ import ( "strings" "sync" - "github.com/otiai10/copy" - - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - - "github.com/koderover/zadig/pkg/tool/log" - - "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/hashicorp/go-multierror" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/util" - - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/otiai10/copy" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" - "go.uber.org/zap" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" ) type HelmReleaseResp struct { @@ -67,23 +64,23 @@ func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmR opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) if err != nil { - return nil, e.ErrListHelmReleases.AddDesc(err.Error()) + return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) } restConfig, err := kube.GetRESTConfig(prod.ClusterID) if err != nil { log.Errorf("GetRESTConfig error: %v", err) - return nil, e.ErrListHelmReleases.AddDesc(err.Error()) + return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) } helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) if err != nil { log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) - return nil, e.ErrListHelmReleases.AddErr(err) + return nil, e.ErrCreateDeliveryVersion.AddErr(err) } releases, err := helmClient.ListDeployedReleases() if err != nil { - return nil, e.ErrListHelmReleases.AddErr(err) + return nil, e.ErrCreateDeliveryVersion.AddErr(err) } ret := make([]*HelmReleaseResp, 0, len(releases)) @@ -144,7 +141,7 @@ func loadChartFilesInfo(productName, serviceName string, revision int64, dir str } //prepare chart version data -func prepareChartVersionData(productName string, serviceObj *models.Service) error { +func prepareChartVersionData(productName string, serviceObj *models.Service, renderChart *template.RenderChart, renderset *models.RenderSet) error { serviceName, revision := serviceObj.ServiceName, serviceObj.Revision base := config.LocalServicePathWithRevision(productName, serviceName, revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { @@ -161,11 +158,16 @@ func prepareChartVersionData(productName string, serviceObj *models.Service) err deliveryChartPath := filepath.Join(config.LocalDeliveryChartPathWithRevision(productName, serviceObj.ServiceName, serviceObj.Revision), serviceObj.ServiceName) err := copy.Copy(fullPath, deliveryChartPath) if err != nil { - return nil + return err + } + + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderset.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + if err != nil { + return err } // write values.yaml - if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte("this is fake data"), 0644); err != nil { + if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644); err != nil { return err } @@ -178,23 +180,22 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog if err != nil { return nil, e.ErrGetHelmCharts.AddErr(err) } + renderSet, err := FindHelmRenderSet(productName, envName, log) + if err != nil { + log.Errorf("[%s][P:%s] find product renderset error: %v", envName, productName, err) + return nil, e.ErrGetHelmCharts.AddErr(err) + } - //restConfig, err := kube.GetRESTConfig(prod.ClusterID) - //if err != nil { - // log.Errorf("GetRESTConfig error: %v", err) - // return nil, e.ErrGetHelmCharts.AddDesc(err.Error()) - //} - //helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) - //if err != nil { - // log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) - // return nil, e.ErrGetHelmCharts.AddErr(err) - //} + chartMap := make(map[string]*template.RenderChart) + for _, chart := range renderSet.ChartInfos { + chartMap[chart.ServiceName] = chart + } allServiceMap := prod.GetServiceMap() serviceMap := make(map[string]*models.ProductService) + //validate data, make sure service and chart info exists if len(serviceName) > 0 { serviceList := strings.Split(serviceName, ",") - // validate service exists in environment for _, singleService := range serviceList { if service, ok := allServiceMap[singleService]; ok { serviceMap[service.ServiceName] = service @@ -237,7 +238,12 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog errList = multierror.Append(errList, fmt.Errorf("failed to query service, serviceName: %s, revision: %d", serviceName, revision)) return } - err = prepareChartVersionData(productName, serviceObj) + renderChart, ok := chartMap[serviceName] + if !ok { + errList = multierror.Append(errList, fmt.Errorf("failed to find render chart for service %s in target namespace", serviceName)) + return + } + err = prepareChartVersionData(productName, serviceObj, renderChart, renderSet) if err != nil { errList = multierror.Append(errList, fmt.Errorf("failed to prepare chart info for service %s", serviceObj.ServiceName)) return diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index b0fb475b..d1462e36 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -46,8 +46,11 @@ func GetHelmServiceModule(c *gin.Context) { func GetFilePath(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - c.Query("revision") - revision, err := strconv.ParseInt(c.DefaultQuery("revision", "0"), 10, 64) + revision := int64(0) + var err error + if len(c.Query("revision")) > 0 { + revision, err = strconv.ParseInt(c.Query("revision"), 10, 64) + } if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") return diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 31a84a53..7f92809e 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -49,6 +49,7 @@ func CreateHelmRepo(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid url") return } + ctx.Err = service.CreateHelmRepo(args, ctx.Logger) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index c8c77099..dd37e701 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -271,6 +271,10 @@ const ( FunctionTestType = "function" ) +const ( + DeliveryVersionTypeChart = "HelmChart" +) + const ( AuthorizationHeader = "Authorization" ) @@ -322,6 +326,13 @@ const ( ProductStatusUnstable = "Unstable" ) +// DeliveryVersion status +const ( + DeliveryVersionStatusSuccess = "success" + DeliveryVersionStatusFailed = "failed" + DeliveryVersionStatusCreating = "creating" +) + const ( NormalModeProduct = "normal" ) -- Gitee From f69961004d3035f5d06e26e9f6c4afc763e5c768 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 11:46:31 +0800 Subject: [PATCH 0715/2647] optimize create workflow task logic Signed-off-by: liu deyi --- .../workflow/service/workflow/workflow_task.go | 15 ++++++++++++++- .../workflow/service/workflow/workflow_task_v3.go | 3 --- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 6c5d4bf8..14487f9e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2146,7 +2146,20 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } } case config.TaskDistribute: - // 为了兼容历史数据类型,目前什么都不用做,避免出错 + // 为了兼容历史数据类型,目前什么都不用做,避免出错 + case config.TaskTrigger: + t, err := base.ToTriggerTask(subTask) + if err != nil { + log.Error(err) + return err + } + + if t.Enabled { + pt.SubTasks[i], err = t.ToSubTask() + if err != nil { + return err + } + } default: return e.NewErrInvalidTaskType(string(pre.TaskType)) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 7c190e9b..ebce88e6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -65,9 +65,6 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str continue } case config.TaskTrigger: - if !pre.Enabled { - continue - } trigger, err := base.ToTriggerTask(subTask) if err != nil || trigger == nil { log.Errorf("subTask.ToTriggerTask error: %s", err) -- Gitee From 34a4a4f2cece545182805fe67d87bd9c99d6e67e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 13:59:36 +0800 Subject: [PATCH 0716/2647] add sse interface Signed-off-by: liu deyi --- .../aslan/core/workflow/handler/router.go | 1 + .../aslan/core/workflow/handler/sse.go | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index d890f09c..25143aaa 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -47,6 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { sse.GET("/tasks/running", RunningPipelineTasksSSE) sse.GET("/tasks/pending", PendingPipelineTasksSSE) sse.GET("/tasks/id/:id/pipelines/:name", GetPipelineTaskSSE) + sse.GET("/workflowTask/v3/id/:id/name/:name", GetWorkflowTaskV3SSE) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/workflow/handler/sse.go b/pkg/microservice/aslan/core/workflow/handler/sse.go index 3ba90e70..1212adf1 100644 --- a/pkg/microservice/aslan/core/workflow/handler/sse.go +++ b/pkg/microservice/aslan/core/workflow/handler/sse.go @@ -134,3 +134,37 @@ func GetWorkflowTaskSSE(c *gin.Context) { } }, ctx.Logger) } + +func GetWorkflowTaskV3SSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("Invalid id Args") + internalhandler.JSONResponse(c, ctx) + return + } + + internalhandler.Stream(c, func(ctx1 context.Context, msgChan chan interface{}) { + startTime := time.Now() + err := wait.PollImmediateUntil(time.Second, func() (bool, error) { + res, err := workflow.GetWorkflowTaskV3(taskID, c.Param("name"), config.WorkflowTypeV3, ctx.Logger) + if err != nil { + ctx.Logger.Errorf("[%s] GetWorkflowTaskV3SSE error: %s", ctx.UserName, err) + return false, err + } + + msgChan <- res + + if time.Since(startTime).Minutes() == float64(60) { + ctx.Logger.Warnf("[%s] Query GetWorkflowTaskV3SSE API over 60 minutes", ctx.UserName) + } + + return false, nil + }, ctx1.Done()) + + if err != nil && err != wait.ErrWaitTimeout { + ctx.Logger.Error(err) + } + }, ctx.Logger) +} -- Gitee From 992339a1507993b6270563717f9436865e4e6bc2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 14:01:23 +0800 Subject: [PATCH 0717/2647] add sse interface Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/handler/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 25143aaa..aaa1317a 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -47,7 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { sse.GET("/tasks/running", RunningPipelineTasksSSE) sse.GET("/tasks/pending", PendingPipelineTasksSSE) sse.GET("/tasks/id/:id/pipelines/:name", GetPipelineTaskSSE) - sse.GET("/workflowTask/v3/id/:id/name/:name", GetWorkflowTaskV3SSE) + sse.GET("/workflowtask/v3/id/:id/name/:name", GetWorkflowTaskV3SSE) } // --------------------------------------------------------------------------------------- -- Gitee From 63d725943e80dc471d5504e24ff71664f4ef012f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 14:45:28 +0800 Subject: [PATCH 0718/2647] optimize create workflow v3 task code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task_v3.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index ebce88e6..d0968d43 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -89,6 +89,7 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str Type: config.WorkflowTypeV3, TaskCreator: username, ReqID: reqID, + TaskArgs: &commonmodels.TaskArgs{Builds: args.Builds, BuildArgs: args.BuildArgs}, Status: config.StatusCreated, SubTasks: workflowV3.SubTasks, ConfigPayload: commonservice.GetConfigPayload(0), -- Gitee From e85e3f0e37a4516a20107a731355e2c12a062c13 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 15:13:46 +0800 Subject: [PATCH 0719/2647] optimize create workflow v3 task code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskcontroller/task_handler.go | 4 ++-- .../warpdrive/core/service/taskcontroller/task_helper.go | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 07a6c107..60cfb6da 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -337,7 +337,7 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, xl *zap.SugaredLogger) { xl.Info("start pipeline task executor...") // 如果是pipeline 1.0, 先将subtasks进行transform,转化为stages结构 - if pipelineTask.Type == config.SingleType || pipelineTask.Type == "" { + if pipelineTask.Type == config.SingleType || pipelineTask.Type == "" || pipelineTask.Type == config.WorkflowTypeV3 { err := transformToStages(pipelineTask, xl) // 初始化出错时,直接返回pipeline状态错误 if err != nil { @@ -457,7 +457,7 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl xl.Info("start to call plugin.Run") // 如果是并行跑,用servicename来区分不同的workspace runCtx := *pipelineCtx - if pipelineTask.Type == config.WorkflowType { + if pipelineTask.Type == config.WorkflowType || pipelineTask.Type == config.WorkflowTypeV3 { runCtx.Workspace = fmt.Sprintf("%s/%s", pipelineCtx.Workspace, servicename) } // 运行 SubTask, 如果需要异步,请在方法内实现 diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 5561d10b..45faeaf2 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -136,6 +136,9 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv } else if pipelineTask.Type == config.ServiceType { xl.Info("pipeline type is service type: pipeline 3.0") pipelineTask.Stages[pos].SubTasks[servicename] = subTask + } else if pipelineTask.Type == config.WorkflowTypeV3 { + xl.Info("pipeline type is workflow type: pipeline 3.0") + pipelineTask.Stages[pos].SubTasks[servicename] = subTask } } -- Gitee From 7a9c5553a32b57c8a909dd176880cbb904a693af Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 15:48:39 +0800 Subject: [PATCH 0720/2647] optimize create workflow v3 task code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/handler/sse.go | 2 ++ pkg/microservice/aslan/core/log/service/log.go | 2 +- .../warpdrive/core/service/taskcontroller/task_handler.go | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index f2d28bbf..cd1dc7ea 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -18,6 +18,7 @@ package handler import ( "context" + "fmt" "strconv" "github.com/gin-contrib/sse" @@ -241,6 +242,7 @@ func GetWorkflowBuildV3JobContainerLogsSSE(c *gin.Context) { PipelineType: string(config.WorkflowTypeV3), EnvName: c.Query("envName"), ProductName: c.Query("projectName"), + ServiceName: fmt.Sprintf("%s-job", c.Param("workflowName")), } internalhandler.Stream(c, func(ctx1 context.Context, streamChan chan interface{}) { diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 21a770b0..7b2c94f3 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -139,7 +139,7 @@ func GetCurrentContainerLogs(podName, containerName, envName, productName string } func GetWorkflowBuildV3JobContainerLogs(workflowName, buildType string, taskID int64, log *zap.SugaredLogger) (string, error) { - buildJobNamePrefix := fmt.Sprintf("%s-%s-%d-%s", config.WorkflowTypeV3, workflowName, taskID, buildType) + buildJobNamePrefix := fmt.Sprintf("%s-%s-%d-%s-%s", config.WorkflowTypeV3, workflowName, taskID, buildType, fmt.Sprintf("%s-job", workflowName)) buildLog, err := getContainerLogFromS3(workflowName, buildJobNamePrefix, taskID, log) if err != nil { return "", err diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 60cfb6da..50cf2377 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -412,8 +412,11 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.ServiceType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), servicename)), "_", "-", -1) } else if pipelineTask.Type == config.WorkflowTypeV3 { - fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.WorkflowTypeV3, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), + fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.WorkflowTypeV3, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), fmt.Sprintf("%s-job", pipelineTask.PipelineName))), "_", "-", -1) + if servicename == "" { + servicename = fmt.Sprintf("%s-job", pipelineTask.PipelineName) + } } plugin.Init(jobName, fileName, xl) -- Gitee From 8d4b3f080493077a197ea28dfb08b681bfe67444 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 16:57:05 +0800 Subject: [PATCH 0721/2647] Delete the project and delete the workflow Signed-off-by: liu deyi --- .../common/repository/mongodb/workflow_v3.go | 22 ++++- .../aslan/core/common/service/workflow_v3.go | 61 ++++++++++++ .../aslan/core/project/service/product.go | 5 + .../core/workflow/handler/workflow_v3.go | 2 +- .../workflow/service/workflow/workflow_v3.go | 96 ++++++++++++++++--- 5 files changed, 171 insertions(+), 15 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/workflow_v3.go diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go index 4b6bf71b..be22b7dc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go @@ -66,9 +66,14 @@ func (c *WorkflowV3Coll) List(opt *ListWorkflowV3Option, pageNum, pageSize int64 if err != nil { return nil, 0, err } - findOption := options.Find(). - SetSkip((pageNum - 1) * pageSize). - SetLimit(pageSize) + var findOption *options.FindOptions + if pageNum == 0 && pageSize == 0 { + findOption = options.Find() + } else { + findOption = options.Find(). + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + } cursor, err := c.Collection.Find(context.TODO(), query, findOption) if err != nil { @@ -81,6 +86,17 @@ func (c *WorkflowV3Coll) List(opt *ListWorkflowV3Option, pageNum, pageSize int64 return resp, count, nil } +func (c *WorkflowV3Coll) Find(name string) (*models.WorkflowV3, error) { + resp := new(models.WorkflowV3) + query := bson.M{"name": name} + + err := c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + func (c *WorkflowV3Coll) GetByID(idstring string) (*models.WorkflowV3, error) { resp := new(models.WorkflowV3) id, err := primitive.ObjectIDFromHex(idstring) diff --git a/pkg/microservice/aslan/core/common/service/workflow_v3.go b/pkg/microservice/aslan/core/common/service/workflow_v3.go new file mode 100644 index 00000000..580fcaaf --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/workflow_v3.go @@ -0,0 +1,61 @@ +package service + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func DeleteWorkflowV3s(productName, requestID string, log *zap.SugaredLogger) error { + workflowV3s, _, err := mongodb.NewWorkflowV3Coll().List(&mongodb.ListWorkflowV3Option{ProjectName: productName}, 0, 0) + if err != nil { + log.Errorf("WorkflowV3.List error: %s", err) + return fmt.Errorf("DeleteWorkflowV3s productName %s WorkflowV3.List error: %s", productName, err) + } + errList := new(multierror.Error) + for _, workflowV3 := range workflowV3s { + if err = DeleteWorkflowV3(workflowV3.ID.Hex(), workflowV3.Name, requestID, log); err != nil { + errList = multierror.Append(errList, fmt.Errorf("productName %s workflowV3 delete %s error: %s", productName, workflowV3.Name, err)) + } + } + if err := errList.ErrorOrNil(); err != nil { + log.Error(err) + return err + } + return nil +} + +func DeleteWorkflowV3(workflowID, workflowName, requestID string, log *zap.SugaredLogger) error { + taskQueue, err := mongodb.NewQueueColl().List(&mongodb.ListQueueOption{}) + if err != nil { + log.Errorf("List queued task error: %s", err) + return e.ErrDeletePipeline.AddErr(err) + } + // 当task还在运行时,先取消任务 + for _, task := range taskQueue { + if task.PipelineName == workflowName && task.Type == config.WorkflowTypeV3 { + if err = CancelWorkflowTaskV3("system", task.PipelineName, task.TaskID, config.WorkflowTypeV3, requestID, log); err != nil { + log.Errorf("task still running, cancel pipelineV3 %s task %d", task.PipelineName, task.TaskID) + } + } + } + + if err := mongodb.NewWorkflowV3Coll().DeleteByID(workflowID); err != nil { + log.Errorf("WorkflowV3.delete error: %s", err) + return e.ErrDeleteWorkflow.AddDesc(err.Error()) + } + + if err := mongodb.NewTaskColl().DeleteByPipelineNameAndType(workflowName, config.WorkflowTypeV3); err != nil { + log.Errorf("PipelineTaskV2.DeleteByPipelineName error: %v", err) + } + + if err := mongodb.NewCounterColl().Delete("WorkflowTask:" + workflowName); err != nil { + log.Errorf("Counter.Delete error: %v", err) + } + return nil +} diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 29010f33..321b13d8 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -336,6 +336,11 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return err } + if err = commonservice.DeleteWorkflowV3s(productName, requestID, log); err != nil { + log.Errorf("DeleteProductTemplate Delete productName %s workflowV3 err: %s", productName, err) + return err + } + if err = commonservice.DeletePipelines(productName, requestID, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s pipeline err: %v", productName, err) return err diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index a06f6c92..c0a1b07e 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -37,7 +37,7 @@ func CreateWorkflowV3(c *gin.Context) { } type listWorkflowV3Query struct { - PageSize int64 `json:"page_size" form:"page_size,default=100"` + PageSize int64 `json:"page_size" form:"page_size,default=20"` PageNum int64 `json:"page_num" form:"page_num,default=1"` ProjectName string `json:"project_name" form:"project_name"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index 7af1a31a..b6ed5c5e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -2,16 +2,30 @@ package workflow import ( "encoding/json" + "errors" + "fmt" "time" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" ) func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { + if !checkWorkflowSubModules(workflowModel) { + errStr := "工作流没有子模块,请先设置子模块" + return e.ErrUpsertWorkflow.AddDesc(errStr) + } + if err := ensureWorkflowV3(workflowModel, logger); err != nil { + return e.ErrUpsertWorkflow.AddDesc(err.Error()) + } + workflowModel.CreatedBy = user workflowModel.UpdatedBy = user workflowModel.CreateTime = time.Now().Unix() @@ -24,6 +38,73 @@ func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logge return nil } +func ensureWorkflowV3(args *commonmodels.WorkflowV3, log *zap.SugaredLogger) error { + if !defaultNameRegex.MatchString(args.Name) { + log.Errorf("workflow name must match %s", defaultNameRegexString) + return fmt.Errorf("%s %s", e.InvalidFormatErrMsg, defaultNameRegexString) + } + + if err := validateV3SubTaskSetting(args.Name, args.SubTasks); err != nil { + log.Errorf("validateV3SubTaskSetting: %+v", err) + return err + } + + if workflowV3, err := commonrepo.NewWorkflowV3Coll().Find(args.Name); err == nil { + errStr := fmt.Sprintf("workflow [%s] 在项目 [%s] 中已经存在!", workflowV3.Name, workflowV3.ProjectName) + return e.ErrCreatePipeline.AddDesc(errStr) + } + + if workflow, err := commonrepo.NewWorkflowColl().Find(args.Name); err == nil { + errStr := fmt.Sprintf("workflow [%s] 在项目 [%s] 中已经存在!", workflow.Name, workflow.ProductTmplName) + return e.ErrCreatePipeline.AddDesc(errStr) + } + return nil +} + +// validateV3SubTaskSetting Validating subtasks +func validateV3SubTaskSetting(workflowName string, subtasks []map[string]interface{}) error { + for i, subTask := range subtasks { + pre, err := base.ToPreview(subTask) + if err != nil { + return errors.New(e.InterfaceToTaskErrMsg) + } + + if !pre.Enabled { + continue + } + + switch pre.TaskType { + case config.TaskBuildV3: + t, err := base.ToBuildTask(subTask) + if err != nil { + log.Error(err) + return err + } + // 设置默认 build OS 为 Ubuntu 16.04 + if t.BuildOS == "" { + t.BuildOS = setting.UbuntuXenial + t.ImageFrom = commonmodels.ImageFromKoderover + } + + ensureTaskSecretEnvs(workflowName, config.TaskBuildV3, t.JobCtx.EnvVars) + + subtasks[i], err = t.ToSubTask() + if err != nil { + return err + } + } + } + return nil +} + +func checkWorkflowSubModules(args *commonmodels.WorkflowV3) bool { + if args.SubTasks != nil && len(args.SubTasks) > 0 { + return true + } + + return false +} + func ListWorkflowsV3(projectName string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*WorkflowV3Brief, int64, error) { resp := make([]*WorkflowV3Brief, 0) workflowV3List, total, err := commonrepo.NewWorkflowV3Coll().List(&commonrepo.ListWorkflowV3Option{ @@ -64,17 +145,10 @@ func GetWorkflowV3Detail(id string, logger *zap.SugaredLogger) (*WorkflowV3, err } func UpdateWorkflowV3(id, user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { - //workflowModel := new(commonmodels.WorkflowV3) - //out, err := json.Marshal(workflow) - //if err != nil { - // logger.Errorf("Failed to unmarshal given workflow, the error is: %s", err) - // return err - //} - //err = json.Unmarshal(out, &workflowModel) - //if err != nil { - // logger.Errorf("Cannot convert workflow into database model, the error is: %s", err) - // return err - //} + if !checkWorkflowSubModules(workflowModel) { + errStr := "工作流没有子模块,请先设置子模块" + return e.ErrCreatePipeline.AddDesc(errStr) + } workflowModel.UpdatedBy = user workflowModel.UpdateTime = time.Now().Unix() err := commonrepo.NewWorkflowV3Coll().Update( -- Gitee From dd3e6e1d00fb8ae45918407b2ec5fb832bacb839 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 19 Nov 2021 17:33:23 +0800 Subject: [PATCH 0722/2647] Delete the project and delete the workflow Signed-off-by: liu deyi --- .../service/workflow/workflow_task_v3.go | 48 ++++++++++++++++++- .../service/taskcontroller/task_handler.go | 3 -- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index d0968d43..7bfddfb6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -59,6 +59,7 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str build.JobCtx.EnvVars = args.BuildArgs } + build.ServiceName = fmt.Sprintf("%s-job", args.Name) workflowV3.SubTasks[i], err = build.ToSubTask() if err != nil { log.Errorf("build.ToSubTask error: %s", err) @@ -180,6 +181,51 @@ func GetWorkflowTaskV3(taskID int64, pipelineName string, typeString config.Pipe return resp, e.ErrGetTask } - Clean(resp) + CleanWorkflow3(resp) return resp, nil } + +func CleanWorkflow3(task *task.Task) { + task.ConfigPayload = nil + task.TaskArgs = nil + + EnsureSubTasksV3Resp(task.SubTasks) + for _, stage := range task.Stages { + ensureSubStageV3Resp(stage) + } +} + +// EnsureSubTasksResp 确保SubTask中敏感信息和其他不必要信息不返回给前端 +func EnsureSubTasksV3Resp(subTasks []map[string]interface{}) { + for i, subTask := range subTasks { + pre, err := base.ToPreview(subTask) + if err != nil { + continue + } + + switch pre.TaskType { + case config.TaskBuildV3: + if newTask, err := ensureBuildTask(subTask); err == nil { + subTasks[i] = newTask + } + } + + } +} + +func ensureSubStageV3Resp(stage *commonmodels.Stage) { + subTasks := stage.SubTasks + for key, subTask := range subTasks { + pre, err := base.ToPreview(subTask) + if err != nil { + continue + } + + switch pre.TaskType { + case config.TaskBuildV3: + if newTask, err := ensureBuildTask(subTask); err == nil { + subTasks[key] = newTask + } + } + } +} diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 50cf2377..d292399a 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -414,9 +414,6 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } else if pipelineTask.Type == config.WorkflowTypeV3 { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.WorkflowTypeV3, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), fmt.Sprintf("%s-job", pipelineTask.PipelineName))), "_", "-", -1) - if servicename == "" { - servicename = fmt.Sprintf("%s-job", pipelineTask.PipelineName) - } } plugin.Init(jobName, fileName, xl) -- Gitee From 4edb589d26cf7a8bf7c4bc27447fc23d844d012c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 21 Nov 2021 10:39:07 +0800 Subject: [PATCH 0723/2647] optimize delete workflow3 Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/workflow_v3.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow_v3.go b/pkg/microservice/aslan/core/common/service/workflow_v3.go index 580fcaaf..02e1c143 100644 --- a/pkg/microservice/aslan/core/common/service/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/service/workflow_v3.go @@ -51,11 +51,11 @@ func DeleteWorkflowV3(workflowID, workflowName, requestID string, log *zap.Sugar } if err := mongodb.NewTaskColl().DeleteByPipelineNameAndType(workflowName, config.WorkflowTypeV3); err != nil { - log.Errorf("PipelineTaskV2.DeleteByPipelineName error: %v", err) + log.Errorf("PipelineTaskV2.DeleteByPipelineName error: %s", err) } - if err := mongodb.NewCounterColl().Delete("WorkflowTask:" + workflowName); err != nil { - log.Errorf("Counter.Delete error: %v", err) + if err := mongodb.NewCounterColl().Delete("WorkflowTaskV3:" + workflowName); err != nil { + log.Errorf("Counter.Delete error: %s", err) } return nil } -- Gitee From bdaa159215aa4b51c6fb530bbd9040c6ad18f40c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 21 Nov 2021 13:16:55 +0800 Subject: [PATCH 0724/2647] optimize trigger stage Signed-off-by: liu deyi --- .../reaper/core/service/reaper/artifact.go | 67 ++++++++++--------- .../reaper/core/service/reaper/reaper.go | 2 +- .../core/service/taskplugin/trigger.go | 24 +++++-- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 49227243..f51426e8 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -31,7 +31,7 @@ import ( s3tool "github.com/koderover/zadig/pkg/tool/s3" ) -func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths []string) error { +func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths []string, pluginType ...string) error { var ( err error store *s3.S3 @@ -49,49 +49,52 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } } - for _, artifactPath := range artifactPaths { - if len(artifactPath) == 0 { - continue - } - - artifactPath = strings.TrimPrefix(artifactPath, "/") - - artifactPath = filepath.Join(activeWorkspace, artifactPath) - - artifactFiles, err := ioutil.ReadDir(artifactPath) - if err != nil || len(artifactFiles) == 0 { - continue - } - - for _, artifactFile := range artifactFiles { - if artifactFile.IsDir() { + if len(pluginType) == 0 { + for _, artifactPath := range artifactPaths { + if len(artifactPath) == 0 { continue } - filePath := path.Join(artifactPath, artifactFile.Name()) - if _, err := os.Stat(filePath); os.IsNotExist(err) { + artifactPath = strings.TrimPrefix(artifactPath, "/") + + artifactPath = filepath.Join(activeWorkspace, artifactPath) + + artifactFiles, err := ioutil.ReadDir(artifactPath) + if err != nil || len(artifactFiles) == 0 { continue } - if store != nil { - forcedPathStyle := true - if store.Provider == setting.ProviderSourceAli { - forcedPathStyle = false + for _, artifactFile := range artifactFiles { + if artifactFile.IsDir() { + continue } - s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) - if err != nil { - log.Errorf("failed to create s3 client, error is: %+v", err) - return err + filePath := path.Join(artifactPath, artifactFile.Name()) + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + continue } - objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s", artifactPath, artifactFile.Name())) - err = s3client.Upload(store.Bucket, filePath, objectKey) - if err != nil { - log.Errorf("artifactsUpload failed to upload package %s, err:%v", filePath, err) - return err + if store != nil { + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + log.Errorf("failed to create s3 client, error is: %+v", err) + return err + } + objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s", artifactPath, artifactFile.Name())) + err = s3client.Upload(store.Bucket, filePath, objectKey) + + if err != nil { + log.Errorf("artifactsUpload failed to upload package %s, err:%v", filePath, err) + return err + } } } } } + return nil } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 22516327..c1c81cc4 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -418,7 +418,7 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } if r.Ctx.ArtifactPath != "" { - if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}); err != nil { + if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { log.Errorf("artifactsUpload err %s", err) return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index ebd27422..1f1933c9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -97,16 +97,32 @@ func (p *TriggerTaskPlugin) SetTriggerStatusCompleted(status config.Status) { } func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { - p.Log.Infof("succeed to create trigger task %s", p.JobName) + } // Wait ... func (p *TriggerTaskPlugin) Wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) - p.SetTriggerStatusCompleted(status) + timeout := time.After(time.Duration(p.TaskTimeout())) + + for { + select { + case <-ctx.Done(): + p.Task.TaskStatus = config.StatusCancelled + return + + case <-timeout: + p.Task.TaskStatus = config.StatusTimeout + return - p.SetStatus(status) + default: + time.Sleep(time.Second * 1) + + if p.IsTaskDone() { + return + } + } + } } // Complete ... -- Gitee From 9d0fea08e340b59b545dbf41e56a9e750777146b Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 21 Nov 2021 16:44:21 +0800 Subject: [PATCH 0725/2647] add get chart data api Signed-off-by: allenshen --- .../repository/models/delivery_version.go | 2 +- .../repository/mongodb/delivery_version.go | 9 +- .../aslan/core/delivery/handler/router.go | 4 + .../aslan/core/delivery/handler/version.go | 27 +++ .../aslan/core/delivery/service/version.go | 210 +++++++++++++++--- 5 files changed, 221 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 8fcc93f8..956070a3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -38,7 +38,7 @@ type DeliveryVersion struct { ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` Charts []*DeliveryChart `bson:"charts" json:"charts"` Status string `bson:"status" json:"status"` - Error string `bson:"log" json:"log"` + Error string `bson:"error" json:"error"` CreateArgument interface{} `bson:"createArgument" json:"-"` CreatedBy string `bson:"created_by" json:"createdBy"` CreatedAt int64 `bson:"created_at" json:"created_at"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index 35547768..ada39606 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -34,6 +34,7 @@ import ( type DeliveryVersionArgs struct { ID string `json:"id"` ProductName string `json:"productName"` + Version string `json:"version"` WorkflowName string `json:"workflowName"` TaskID int `json:"taskId"` PerPage int `json:"perPage"` @@ -164,7 +165,13 @@ func (c *DeliveryVersionColl) Get(args *DeliveryVersionArgs) (*models.DeliveryVe resp := new(models.DeliveryVersion) var query map[string]interface{} if args.ID != "" { - query = bson.M{"_id": args.ID, "deleted_at": 0} + oid, err := primitive.ObjectIDFromHex(args.ID) + if err != nil { + return nil, err + } + query = bson.M{"_id": oid, "deleted_at": 0} + } else if len(args.Version) > 0 { + query = bson.M{"product_name": args.ProductName, "version": args.Version, "deleted_at": 0} } else { query = bson.M{"product_name": args.ProductName, "workflow_name": args.WorkflowName, "task_id": args.TaskID, "deleted_at": 0} } diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 7b532a81..18ecc0de 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -46,7 +46,11 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.GET("", ListDeliveryVersion) deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) + deliveryRelease.GET("/charts", DownloadDeliveryChart) + deliveryRelease.GET("/charts/preview", PreviewGetDeliveryChart) + deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) + } deliveryPackage := router.Group("packages") diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 4ac0900d..f91c4ec0 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -385,3 +385,30 @@ func ListDeliveryServiceNames(c *gin.Context) { productName := c.Query("projectName") ctx.Resp, ctx.Err = deliveryservice.ListDeliveryServiceNames(productName, ctx.Logger) } + +func DownloadDeliveryChart(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + versionName := c.Query("version") + chartName := c.Query("chartName") + projectName := c.Query("projectName") + + filePath, err := deliveryservice.DownloadDeliveryChart(projectName, versionName, chartName, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + c.File(filePath) +} + +func PreviewGetDeliveryChart(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + versionName := c.Query("version") + chartName := c.Query("chartName") + projectName := c.Query("projectName") + + ctx.Resp, ctx.Err = deliveryservice.PreviewDeliveryChart(projectName, versionName, chartName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index de24a038..9973e81d 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -23,23 +23,19 @@ import ( "net/http" "os" "path/filepath" + "strings" "sync" "time" - "go.mongodb.org/mongo-driver/bson/primitive" - - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - - fsutil "github.com/koderover/zadig/pkg/util/fs" - - "github.com/hashicorp/go-multierror" - cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" + "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" chartloader "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" @@ -48,9 +44,12 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + fsutil "github.com/koderover/zadig/pkg/util/fs" ) type CreateHelmDeliveryVersionOption struct { @@ -65,15 +64,13 @@ type CreateHelmDeliveryVersionChartData struct { } type CreateHelmDeliveryVersionArgs struct { - CreateBy string `json:"-"` - Version string `json:"version"` - Desc string `json:"desc"` - ProductName string `json:"productName"` - EnvName string `json:"envName"` - Labels []string `json:"labels"` - //ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` - ChartRepoName string `json:"chartRepoName"` - //Options *CreateHelmDeliveryVersionOption `json:"options"` + CreateBy string `json:"-"` + Version string `json:"version"` + Desc string `json:"desc"` + ProductName string `json:"productName"` + EnvName string `json:"envName"` + Labels []string `json:"labels"` + ChartRepoName string `json:"chartRepoName"` *DeliveryVersionChartData } @@ -82,12 +79,16 @@ type DeliveryVersionChartData struct { Options *CreateHelmDeliveryVersionOption `json:"options"` } -type ChartDeliveryData struct { +type DeliveryChartData struct { ChartData *CreateHelmDeliveryVersionChartData ProductService *commonmodels.ProductService ServiceObj *commonmodels.Service } +type DeliveryChartResp struct { + FileInfos []*types.FileInfo `json:"fileInfos"` +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { resp, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { @@ -115,6 +116,16 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } +func getChartTGZDir(productName, versionName string) string { + tmpDir := os.TempDir() + return filepath.Join(tmpDir, "chart-tgz", productName, versionName) +} + +func getChartExpandDir(productName, versionName string) string { + tmpDir := os.TempDir() + return filepath.Join(tmpDir, "chart", productName, versionName) +} + func getProductEnvInfo(productName, envName string) (*commonmodels.Product, error) { productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, @@ -139,12 +150,12 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { // need support more auth types ) if err != nil { - return nil, errors.Wrapf(err, "failed to create chart repo client") + return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", repo.RepoName) } return client, nil } -func handleSingleChart(chartData *ChartDeliveryData, chartRepo *commonmodels.HelmRepo, dir string) error { +func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string) error { serviceObj := chartData.ServiceObj serviceName, revision := serviceObj.ServiceName, serviceObj.Revision base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) @@ -230,9 +241,8 @@ func getChartmuseumError(b []byte, code int) error { return errors.Errorf("%d: %s", code, er.Error) } -func mkChartTGZFileDir(versionName string) (string, error) { - tmpDir := os.TempDir() - path := filepath.Join(tmpDir, "chart-tgz", versionName) +func mkChartTGZFileDir(productName, versionName string) (string, error) { + path := getChartTGZDir(productName, versionName) if err := os.RemoveAll(path); err != nil { if !os.IsExist(err) { return "", errors.Wrapf(err, "failed to claer dir for chart tgz files") @@ -260,17 +270,17 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. repoInfo, err := getChartRepoData(args.ChartRepoName) if err != nil { log.Infof("failed to query chart-repo info, procutName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query chart-repo info, procutName: %s envName %s", args.ProductName, args.EnvName)) + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query chart-repo info, procutName: %s, envName %s, repoName", args.ProductName, args.EnvName, args.ChartRepoName)) } - dir, err := mkChartTGZFileDir(args.Version) + dir, err := mkChartTGZFileDir(args.ProductName, args.Version) if err != nil { return e.ErrCreateDeliveryVersion.AddErr(err) } // validate chartInfo, make sure service is in environment // prepare data set for chart delivery - chartDataMap := make(map[string]*ChartDeliveryData) + chartDataMap := make(map[string]*DeliveryChartData) serviceMap := productInfo.GetServiceMap() for _, chartDta := range args.ChartDatas { if productService, ok := serviceMap[chartDta.ServiceName]; ok { @@ -283,7 +293,7 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. if err != nil { return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query service: %s", chartDta.ServiceName)) } - chartDataMap[chartDta.ServiceName] = &ChartDeliveryData{ + chartDataMap[chartDta.ServiceName] = &DeliveryChartData{ ChartData: chartDta, ProductService: productService, ServiceObj: serviceObj, @@ -344,7 +354,7 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. wg := sync.WaitGroup{} for _, chartData := range chartDataMap { wg.Add(1) - go func(cData *ChartDeliveryData) { + go func(cData *DeliveryChartData) { defer wg.Done() err := handleSingleChart(cData, repoInfo, dir) if err != nil { @@ -374,7 +384,9 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. logger.Errorf("failed to create temp dir, err: %s", err) return } - defer os.RemoveAll(tmpDir) + defer func(path string) { + _ = os.RemoveAll(path) + }(tmpDir) //tar all chart files and send to s3 store fsTree := os.DirFS(dir) @@ -425,3 +437,143 @@ func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]str return serviceNames.UnsortedList(), nil } + +func downloadChart(version *commonmodels.DeliveryVersion, chartInfo *commonmodels.DeliveryChart) (string, error) { + chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.Name, chartInfo.Version) + chartTGZFileParent := getChartTGZDir(version.ProductName, version.Version) + chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) + if _, err := os.Stat(chartTGZFilePath); err == nil { + // local cache exists + log.Infof("local cache exists, path %s", chartTGZFilePath) + return chartTGZFilePath, nil + } + + chartRepo, err := getChartRepoData(chartInfo.Repo) + if err != nil { + return "", fmt.Errorf("failed to query chart-repo info, repoName %s", chartInfo.Repo) + } + + client, err := createChartRepoClient(chartRepo) + if err != nil { + return "", err + } + + if err = os.MkdirAll(chartTGZFileParent, 0644); err != nil { + return "", errors.Wrapf(err, "failed to craete tgz parent dir") + } + + out, err := os.Create(chartTGZFilePath) + if err != nil { + os.RemoveAll(chartTGZFilePath) + return "", errors.Wrapf(err, "failed to create chart tgz file") + } + + response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) + if err != nil { + return "", errors.Wrapf(err, "failed to download file") + } + + //helm.CreateChartPackage() + + if response.StatusCode != 200 { + return "", errors.Wrapf(err, "download file failed %s", chartTGZName) + } + + b, err := ioutil.ReadAll(response.Body) + defer response.Body.Close() + if err != nil { + return "", errors.Wrapf(err, "failed to read response data") + } + + defer func(out *os.File) { + _ = out.Close() + }(out) + + err = ioutil.WriteFile(chartTGZFilePath, b, 0644) + if err != nil { + return "", err + } + return chartTGZFilePath, nil +} + +func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (string, error) { + deliveryVersion, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: version, + }, log) + if err != nil { + return "", err + } + + var chartInfo *commonmodels.DeliveryChart + for _, singleChart := range deliveryVersion.Charts { + if singleChart.Name == chartName { + chartInfo = singleChart + } + } + + if chartInfo == nil { + return "", fmt.Errorf("can't find target chart: %s", chartName) + } + + // prepare chart data + filePath, err := downloadChart(deliveryVersion, chartInfo) + if err != nil { + return "", err + } + + return filePath, err +} + +func PreviewDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (*DeliveryChartResp, error) { + filePath, err := DownloadDeliveryChart(projectName, version, chartName, log) + if err != nil { + return nil, err + } + fileName := filepath.Base(filePath) + dstDir := getChartExpandDir(projectName, version) + + fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName)) + dstDir = filepath.Join(dstDir, fileName) + + file, err := os.Open(filePath) + if err != nil { + return nil, errors.Wrap(err, "unable to open tarball") + } + defer file.Close() + + err = chartutil.Expand(dstDir, file) + if err != nil { + log.Errorf("failed to uncompress file: %s", filePath) + return nil, errors.Wrapf(err, "failed to uncompress file") + } + + ret := &DeliveryChartResp{ + FileInfos: make([]*types.FileInfo, 0), + } + + var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(dstDir, chartName)) + if err != nil { + return nil, err + } + + for _, file := range files { + info, _ := file.Info() + if info == nil { + continue + } + fi := &types.FileInfo{ + Parent: "", + Name: file.Name(), + Size: info.Size(), + Mode: file.Type(), + ModTime: info.ModTime().Unix(), + IsDir: file.IsDir(), + } + + fis = append(fis, fi) + } + ret.FileInfos = fis + return ret, nil +} -- Gitee From 90cf410302e927b05bea90feba59fff6a896103c Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 21 Nov 2021 16:51:00 +0800 Subject: [PATCH 0726/2647] fix error be overrided bug Signed-off-by: allenshen --- pkg/util/fs/file.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/pkg/util/fs/file.go b/pkg/util/fs/file.go index d0edf1f5..ea6041b0 100644 --- a/pkg/util/fs/file.go +++ b/pkg/util/fs/file.go @@ -123,25 +123,37 @@ func Tar(src fs.FS, dst string) error { // Untar extracts the src tarball and saves to disk with path dst. func Untar(src, dst string) (err error) { - fr, err := os.Open(src) + var ( + fr *os.File + gr *gzip.Reader + hdr *tar.Header + file *os.File + ) + fr, err = os.Open(src) if err != nil { return err } defer func() { - err = fr.Close() + err1 := fr.Close() + if err == nil { + err = err1 + } }() - gr, err := gzip.NewReader(fr) + gr, err = gzip.NewReader(fr) if err != nil { return err } defer func() { - err = gr.Close() + err1 := gr.Close() + if err == nil { + err = err1 + } }() tr := tar.NewReader(gr) for { - hdr, err := tr.Next() + hdr, err = tr.Next() if err != nil { if err == io.EOF { return nil @@ -160,7 +172,7 @@ func Untar(src, dst string) (err error) { return err } case tar.TypeReg: - file, err := os.OpenFile(dirOrFile, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) + file, err = os.OpenFile(dirOrFile, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) if err != nil { return err } -- Gitee From 630dd40569a645c4332f6952a437ae80a1bb4980 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 02:01:20 +0800 Subject: [PATCH 0727/2647] added get env api Signed-off-by: Min Min --- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_v3.go | 7 ++ .../core/workflow/service/workflow/types.go | 9 ++ .../workflow/service/workflow/workflow_v3.go | 91 +++++++++++++++++++ 4 files changed, 108 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index aaa1317a..42484285 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -145,6 +145,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowV3.GET("/:id", GetWorkflowV3Detail) workflowV3.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateWorkflowV3) workflowV3.DELETE("/:id", DeleteWorkflowV3) + workflowV3.GET("/:id/args", GetWorkflowV3Args) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index c0a1b07e..b509005f 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -104,3 +104,10 @@ func DeleteWorkflowV3(c *gin.Context) { ctx.Err = workflowservice.DeleteWorkflowV3(c.Param("id"), ctx.Logger) } + +func GetWorkflowV3Args(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = workflowservice.GetWorkflowV3Args(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index ff28f80a..fd0ba0e6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -58,3 +58,12 @@ type ExternalParamMapping struct { ResponseKey string `json:"response_key"` Display bool `json:"display"` } + +type WorkflowV3TaskArgs struct { + Type string `json:"type"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Choice []string `json:"choice,omitempty"` + DisplayKey string `json:"display_key,omitempty"` + Options []map[string]interface{} `json:"options,omitempty"` +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index b6ed5c5e..f1edcdec 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "fmt" + "net/http" + "strings" "time" "go.uber.org/zap" @@ -170,3 +172,92 @@ func DeleteWorkflowV3(id string, logger *zap.SugaredLogger) error { } return nil } + +func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskArgs, error) { + workflow, err := commonrepo.NewWorkflowV3Coll().GetByID(id) + if err != nil { + logger.Errorf("Failed to get workflowV3 detail from id: %s, the error is: %s", id, err) + return nil, err + } + resp := make([]*WorkflowV3TaskArgs, 0) + for _, param := range workflow.Parameters { + switch param.Type { + case "string": + resp = append(resp, &WorkflowV3TaskArgs{ + Type: param.Type, + Key: param.Key, + Value: param.DefaultValue, + }) + case "choice": + resp = append(resp, &WorkflowV3TaskArgs{ + Type: param.Type, + Key: param.Key, + Value: param.DefaultValue, + Choice: param.ChoiceOption, + }) + case "external": + externalEnv := &WorkflowV3TaskArgs{ + Type: param.Type, + } + for _, kv := range param.ExternalSetting.Params { + if kv.Display { + externalEnv.DisplayKey = kv.ParamKey + break + } + } + if externalEnv.DisplayKey == "" { + errorMsg := fmt.Sprintf("error getting external key, cannot find the display key") + logger.Error(errorMsg) + return nil, errors.New(errorMsg) + } + options, err := getEnvsFromExternalSystem(param.ExternalSetting) + if err != nil { + logger.Errorf("Failed to get response from external system, the error is: %s", err) + return nil, err + } + externalEnv.Options = options + resp = append(resp, externalEnv) + default: + return nil, err + } + } + return resp, nil +} + +func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[string]interface{}, error) { + externalSystem, err := commonrepo.NewExternalSystemColl().GetByID(setting.SystemID) + if err != nil { + return nil, err + } + server := fmt.Sprintf("%s/%s", externalSystem.Server, setting.Endpoint) + client := http.Client{} + req, err := http.NewRequest(setting.Method, server, strings.NewReader(setting.Body)) + if err != nil { + return nil, err + } + if setting.Headers != nil { + for _, header := range setting.Headers { + req.Header.Set(header.Key, header.Value) + } + } + resp, err := client.Do(req) + if err != nil || resp.StatusCode != http.StatusOK { + return nil, errors.New("failed to get response from external system") + } + decoder := json.NewDecoder(resp.Body) + respList := make([]map[string]interface{}, 0) + err = decoder.Decode(&respList) + if err != nil { + return nil, err + } + envList := make([]map[string]interface{}, 0) + // find every single key required + for _, respItem := range respList { + item := map[string]interface{}{} + for _, kv := range setting.Params { + item[kv.ParamKey] = respItem[kv.ResponseKey] + } + envList = append(envList, item) + } + return envList, nil +} -- Gitee From 8cc6c7f0544723cc0b588910192664cb45f03213 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 18 Nov 2021 14:08:43 +0800 Subject: [PATCH 0728/2647] dir refactor Signed-off-by: mouuii features Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii service Signed-off-by: mouuii policy Signed-off-by: mouuii code review Signed-off-by: mouuii add features Signed-off-by: mouuii add features Signed-off-by: mouuii add features err Signed-off-by: mouuii --- .../core/service/bundle/exemption_urls.go | 10 ++- .../core/{ => connector}/handler/connector.go | 2 +- .../core/connector/handler/router.go | 18 +++++ .../core/{ => connector}/service/connector.go | 0 .../core/{ => connector}/service/types.go | 0 .../core/{ => features}/handler/features.go | 16 +++- .../core/features/handler/router.go | 15 ++++ .../features/repository/models/feature.go | 10 +++ .../features/repository/mongodb/feature.go | 49 +++++++++++++ .../service}/feature.go | 73 ++++++++++++++++--- .../systemconfig/core/handler/router.go | 38 ---------- pkg/microservice/systemconfig/core/service.go | 13 +++- .../systemconfig/server/rest/router.go | 6 +- 13 files changed, 192 insertions(+), 58 deletions(-) rename pkg/microservice/systemconfig/core/{ => connector}/handler/connector.go (99%) create mode 100644 pkg/microservice/systemconfig/core/connector/handler/router.go rename pkg/microservice/systemconfig/core/{ => connector}/service/connector.go (100%) rename pkg/microservice/systemconfig/core/{ => connector}/service/types.go (100%) rename pkg/microservice/systemconfig/core/{ => features}/handler/features.go (56%) create mode 100644 pkg/microservice/systemconfig/core/features/handler/router.go create mode 100644 pkg/microservice/systemconfig/core/features/repository/models/feature.go create mode 100644 pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go rename pkg/microservice/systemconfig/core/{service/featuregates => features/service}/feature.go (30%) delete mode 100644 pkg/microservice/systemconfig/core/handler/router.go diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 9c56543b..1193189c 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -62,12 +62,20 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{"api/directory/codehostss/?*/auth", "api/directory/codehosts/callback"}, + Endpoints: []string{"api/directory/codehosts/callback"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/features/?*"}, }, } // actions which are allowed for system admins only. var systemAdminURLs = []*policyRule{ + { + Methods: []string{"PUT"}, + Endpoints: []string{"api/v1/features/?*"}, + }, { Methods: []string{"POST"}, Endpoints: []string{"api/aslan/project/products"}, diff --git a/pkg/microservice/systemconfig/core/handler/connector.go b/pkg/microservice/systemconfig/core/connector/handler/connector.go similarity index 99% rename from pkg/microservice/systemconfig/core/handler/connector.go rename to pkg/microservice/systemconfig/core/connector/handler/connector.go index 95e475db..288e95c4 100644 --- a/pkg/microservice/systemconfig/core/handler/connector.go +++ b/pkg/microservice/systemconfig/core/connector/handler/connector.go @@ -19,7 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/connector/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/systemconfig/core/connector/handler/router.go b/pkg/microservice/systemconfig/core/connector/handler/router.go new file mode 100644 index 00000000..be2f7509 --- /dev/null +++ b/pkg/microservice/systemconfig/core/connector/handler/router.go @@ -0,0 +1,18 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + connector := router.Group("connectors") + { + connector.POST("", CreateConnector) + connector.GET("", ListConnectors) + connector.GET("/:id", GetConnector) + connector.PUT("/:id", UpdateConnector) + connector.DELETE("/:id", DeleteConnector) + } +} diff --git a/pkg/microservice/systemconfig/core/service/connector.go b/pkg/microservice/systemconfig/core/connector/service/connector.go similarity index 100% rename from pkg/microservice/systemconfig/core/service/connector.go rename to pkg/microservice/systemconfig/core/connector/service/connector.go diff --git a/pkg/microservice/systemconfig/core/service/types.go b/pkg/microservice/systemconfig/core/connector/service/types.go similarity index 100% rename from pkg/microservice/systemconfig/core/service/types.go rename to pkg/microservice/systemconfig/core/connector/service/types.go diff --git a/pkg/microservice/systemconfig/core/handler/features.go b/pkg/microservice/systemconfig/core/features/handler/features.go similarity index 56% rename from pkg/microservice/systemconfig/core/handler/features.go rename to pkg/microservice/systemconfig/core/features/handler/features.go index 18952e06..52b09ee9 100644 --- a/pkg/microservice/systemconfig/core/handler/features.go +++ b/pkg/microservice/systemconfig/core/features/handler/features.go @@ -3,7 +3,7 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/features/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -17,10 +17,22 @@ func GetFeature(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - enabled := featuregates.Features.FeatureEnabled(featuregates.Feature(name)) + enabled := service.Features.FeatureEnabled(service.Feature(name)) ctx.Resp = &feature{ Name: c.Param("name"), Enabled: enabled, } } + +func UpdateOrCreateFeature(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(service.FeatureReq) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + return + } + ctx.Err = service.UpdateOrCreateFeature(req) +} diff --git a/pkg/microservice/systemconfig/core/features/handler/router.go b/pkg/microservice/systemconfig/core/features/handler/router.go new file mode 100644 index 00000000..44b35ebe --- /dev/null +++ b/pkg/microservice/systemconfig/core/features/handler/router.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + features := router.Group("features") + { + features.PUT("/:name", UpdateOrCreateFeature) + features.GET("/:name", GetFeature) + } +} diff --git a/pkg/microservice/systemconfig/core/features/repository/models/feature.go b/pkg/microservice/systemconfig/core/features/repository/models/feature.go new file mode 100644 index 00000000..35c9936a --- /dev/null +++ b/pkg/microservice/systemconfig/core/features/repository/models/feature.go @@ -0,0 +1,10 @@ +package models + +type Feature struct { + Name string `bson:"name" json:"name"` + Enabled bool `bson:"enabled" json:"enabled"` +} + +func (f *Feature) TableName() string { + return "feature" +} diff --git a/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go b/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go new file mode 100644 index 00000000..d5b2fe24 --- /dev/null +++ b/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go @@ -0,0 +1,49 @@ +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/config" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/features/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type FeatureColl struct { + *mongo.Collection + + coll string +} + +func NewFeatureColl() *FeatureColl { + name := models.Feature{}.TableName() + coll := &FeatureColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *FeatureColl) GetCollectionName() string { + return c.coll +} + +func (c *FeatureColl) UpdateOrCreateFeature(ctx context.Context, obj *models.Feature) error { + q := bson.M{"name": obj.Name} + opts := options.Replace().SetUpsert(true) + _, err := c.ReplaceOne(ctx, q, obj, opts) + return err +} + +func (c *FeatureColl) ListFeatures() ([]*models.Feature, error) { + var fs []*models.Feature + cursor, err := c.Find(context.TODO(), bson.M{}) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &fs); err != nil { + return nil, err + } + return fs, nil +} diff --git a/pkg/microservice/systemconfig/core/service/featuregates/feature.go b/pkg/microservice/systemconfig/core/features/service/feature.go similarity index 30% rename from pkg/microservice/systemconfig/core/service/featuregates/feature.go rename to pkg/microservice/systemconfig/core/features/service/feature.go index 13b5c7d9..90431ba8 100644 --- a/pkg/microservice/systemconfig/core/service/featuregates/feature.go +++ b/pkg/microservice/systemconfig/core/features/service/feature.go @@ -1,21 +1,31 @@ -package featuregates +package service -import "strings" +import ( + "context" + "strconv" + "strings" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/features/repository/models" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/features/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/log" +) type Feature string const ( ModernWorkflow Feature = "ModernWorkflow" CommunityProjectRepository Feature = "CommunityProjectRepository" + UserRegistration Feature = "UserRegistration" ) +type FeatureGates map[Feature]bool + var Features FeatureGates = map[Feature]bool{ ModernWorkflow: false, CommunityProjectRepository: false, + UserRegistration: true, } -type FeatureGates map[Feature]bool - func (fg FeatureGates) EnabledFeatures() []Feature { var res []Feature @@ -32,13 +42,31 @@ func (fg FeatureGates) FeatureEnabled(f Feature) bool { return fg[f] } -func (fg FeatureGates) MergeFeatureGates(fs FeatureGates) { - for k, v := range fs { - fg[k] = v +// MergeFeatureGates merge feature config from different source +// latter feature configs will be overridden by former ones +func (fg FeatureGates) MergeFeatureGates(fs ...FeatureGates) { + for _, v := range fs { + for k, vv := range v { + fg[k] = vv + } } } -func ToFeatureGates(s string) FeatureGates { +func DBToFeatureGates() (FeatureGates, error) { + fg := make(FeatureGates) + fs, err := mongodb.NewFeatureColl().ListFeatures() + if err != nil { + log.Errorf("list features err:%s", err) + return nil, err + } + + for _, v := range fs { + fg[Feature(v.Name)] = v.Enabled + } + return fg, nil +} + +func FlagToFeatureGates(s string) (FeatureGates, error) { res := make(FeatureGates) fs := strings.Split(s, ",") @@ -47,9 +75,32 @@ func ToFeatureGates(s string) FeatureGates { if len(kv) != 2 { continue } - - res[Feature(kv[0])] = kv[1] == "true" + boolValue, err := strconv.ParseBool(kv[1]) + if err != nil { + log.Errorf("invalid value of %s=%s, err: %v", kv[0], kv[1], err) + return nil, err + } + res[Feature(kv[0])] = boolValue } - return res + return res, nil +} + +type FeatureReq struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` +} + +func UpdateOrCreateFeature(req *FeatureReq) error { + if err := mongodb.NewFeatureColl().UpdateOrCreateFeature(context.TODO(), &models.Feature{ + Name: req.Name, + Enabled: req.Enabled, + }); err != nil { + return err + } + fg := FeatureGates{ + Feature(req.Name): req.Enabled, + } + Features.MergeFeatureGates(fg) + return nil } diff --git a/pkg/microservice/systemconfig/core/handler/router.go b/pkg/microservice/systemconfig/core/handler/router.go deleted file mode 100644 index 282f4cb1..00000000 --- a/pkg/microservice/systemconfig/core/handler/router.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "github.com/gin-gonic/gin" -) - -type Router struct{} - -func (*Router) Inject(router *gin.RouterGroup) { - roles := router.Group("connectors") - { - roles.POST("", CreateConnector) - roles.GET("", ListConnectors) - roles.GET("/:id", GetConnector) - roles.PUT("/:id", UpdateConnector) - roles.DELETE("/:id", DeleteConnector) - } - features := router.Group("features") - { - features.GET("/:name", GetFeature) - } -} diff --git a/pkg/microservice/systemconfig/core/service.go b/pkg/microservice/systemconfig/core/service.go index c0aae5ba..c5c485c7 100644 --- a/pkg/microservice/systemconfig/core/service.go +++ b/pkg/microservice/systemconfig/core/service.go @@ -25,7 +25,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/service/featuregates" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/features/service" "github.com/koderover/zadig/pkg/setting" gormtool "github.com/koderover/zadig/pkg/tool/gorm" "github.com/koderover/zadig/pkg/tool/log" @@ -41,8 +41,15 @@ func Start(_ context.Context) { }) initDatabase() - - featuregates.Features.MergeFeatureGates(featuregates.ToFeatureGates(config.Features())) + flagFG, err := service.FlagToFeatureGates(config.Features()) + if err != nil { + log.Errorf("FlagToFeatureGates err:%s", err) + } + dbFG, err := service.DBToFeatureGates() + if err != nil { + log.Errorf("DBToFeatureGates err:%s", err) + } + service.Features.MergeFeatureGates(flagFG, dbFG) } func initDatabase() { diff --git a/pkg/microservice/systemconfig/server/rest/router.go b/pkg/microservice/systemconfig/server/rest/router.go index 015f0c56..affff629 100644 --- a/pkg/microservice/systemconfig/server/rest/router.go +++ b/pkg/microservice/systemconfig/server/rest/router.go @@ -20,17 +20,19 @@ import ( "github.com/gin-gonic/gin" codehosthandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/handler" + connectorHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/connector/handler" emailHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/handler" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/handler" + featuresHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/features/handler" jiraHandler "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/handler" ) func (s *engine) injectRouterGroup(router *gin.RouterGroup) { for _, r := range []injector{ - new(handler.Router), + new(connectorHandler.Router), new(emailHandler.Router), new(jiraHandler.Router), new(codehosthandler.Router), + new(featuresHandler.Router), } { r.Inject(router.Group("/api/v1")) } -- Gitee From 0123ca4eebb555a04436b8f453ea57e54d3c6d75 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 10:50:57 +0800 Subject: [PATCH 0729/2647] optimize trigger stage Signed-off-by: liu deyi --- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_v3_task.go | 35 +++++++++++ .../reaper/core/service/reaper/artifact.go | 36 ++++++++++++ .../core/service/taskplugin/trigger.go | 58 +++++++++++++++++++ .../core/service/types/task/trigger.go | 14 +++++ 5 files changed, 144 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index aaa1317a..dd6033eb 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -157,5 +157,6 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) + taskV3.POST("/workflow/webhookPayload", OutputWebhookPayload) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index dca21680..eae0ab7c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -3,6 +3,7 @@ package handler import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "strconv" @@ -98,3 +99,37 @@ func GetWorkflowTaskV3(c *gin.Context) { ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV3(taskID, c.Param("name"), config.WorkflowTypeV3, ctx.Logger) } + +type WebhookPayload struct { + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output"` + TaskEnvs []*KeyVal `json:"task_envs"` +} + +type TaskOutput struct { + Type string `json:"type"` + Value string `json:"value"` +} + +type KeyVal struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + IsCredential bool `bson:"is_credential" json:"is_credential"` +} + +func OutputWebhookPayload(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(WebhookPayload) + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + fmt.Println(fmt.Sprintf("args:%+v", args)) + fmt.Println(fmt.Sprintf("header:%+v", c.Request.Header)) + return +} diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index f51426e8..fc0a385d 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path" "path/filepath" "strings" @@ -94,6 +95,41 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } } } + return nil + } + + artifactPath := artifactPaths[0] + temp, err := ioutil.TempFile("", "*artifact.tar.gz") + if err != nil { + log.Errorf("failed to create temp file %s", err) + return err + } + + _ = temp.Close() + cmd := exec.Command("tar", "czf", temp.Name(), "-C", artifactPath, ".") + cmd.Dir = artifactPath + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err = cmd.Run(); err != nil { + log.Errorf("failed to compress artifact %s", err) + return err + } + if store != nil { + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + log.Errorf("failed to create s3 client, error is: %s", err) + return err + } + + objectKey := store.GetObjectPath("artifact.tar.gz") + if err = s3client.Upload(store.Bucket, temp.Name(), objectKey); err != nil { + log.Errorf("Archive s3 upload err:%s", err) + return err + } } return nil diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 1f1933c9..79f734e8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -18,14 +18,19 @@ package taskplugin import ( "context" + "encoding/json" + "fmt" "time" "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/log" ) const ( @@ -97,8 +102,61 @@ func (p *TriggerTaskPlugin) SetTriggerStatusCompleted(status config.Status) { } func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + var ( + err error + body []byte + artifactPath string + ) + defer func() { + if err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + return + } + }() p.Log.Infof("succeed to create trigger task %s", p.JobName) + httpClient := httpclient.New( + httpclient.SetHostURL(p.Task.URL), + ) + url := p.Task.Path + artifactPath, err = p.getS3Storage(pipelineTask) + if err != nil { + return + } + taskOutput := &task.TaskOutput{ + Type: "object_storage", + Value: artifactPath, + } + webhookPayload := &task.WebhookPayload{ + EventName: "workflow", + ProjectName: pipelineTask.ProductName, + TaskName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + TaskOutput: []*task.TaskOutput{taskOutput}, + TaskEnvs: pipelineTask.TaskArgs.BuildArgs, + } + body, err = json.Marshal(webhookPayload) + _, err = httpClient.Post(url, httpclient.SetHeader("X-Zadig-Event", "Workflow"), httpclient.SetBody(body)) + if err != nil { + return + } +} +func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error) { + var err error + var store *s3.S3 + if store, err = s3.NewS3StorageFromEncryptedURI(pipelineTask.StorageURI); err != nil { + log.Errorf("Archive failed to create s3 storage %s", pipelineTask.StorageURI) + return "", err + } + subPath := "" + if store.Subfolder != "" { + subPath = fmt.Sprintf("%s/%s/%s/%s", store.Subfolder, pipelineTask.PipelineName, pipelineTask.ServiceName, "artifact") + } else { + subPath = fmt.Sprintf("%s/%s/%s", pipelineTask.PipelineName, pipelineTask.ServiceName, "artifact") + } + return fmt.Sprintf("%s/%s/artifact.tar.gz", store.Endpoint, subPath), nil } // Wait ... diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 8bb32ce0..fc2d8ac4 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -28,3 +28,17 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } return task, nil } + +type WebhookPayload struct { + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output"` + TaskEnvs []*KeyVal `json:"task_envs"` +} + +type TaskOutput struct { + Type string `json:"type"` + Value string `json:"value"` +} -- Gitee From aa050f0959fc2f8545cb432cc2d97eeff9d186f5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 11:16:41 +0800 Subject: [PATCH 0730/2647] debug logs Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index f1edcdec..afe515a8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -230,6 +230,7 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str return nil, err } server := fmt.Sprintf("%s/%s", externalSystem.Server, setting.Endpoint) + fmt.Println(">>>>>>>>>>>>> server:", server, "<<<<<<<<<<<<<<<") client := http.Client{} req, err := http.NewRequest(setting.Method, server, strings.NewReader(setting.Body)) if err != nil { -- Gitee From a33d960cef36175c772d4286da95b487b0f23c76 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 11:36:03 +0800 Subject: [PATCH 0731/2647] modify workflowv3 task resp Signed-off-by: liu deyi --- .../workflow/service/workflow/workflow_task_v3.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 7bfddfb6..d530f4dc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -17,7 +17,12 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID string, log *zap.SugaredLogger) (*CreateTaskResp, error) { +type TaskResp struct { + Name string `json:"name"` + TaskID int64 `json:"task_id"` +} + +func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID string, log *zap.SugaredLogger) (*TaskResp, error) { workflowV3, err := commonrepo.NewWorkflowV3Coll().GetByID(args.ID) if err != nil { log.Errorf("workflowV3.Find %s error: %s", args.Name, err) @@ -125,9 +130,9 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str return nil, e.ErrCreateTask } - resp := &CreateTaskResp{ - PipelineName: args.Name, - TaskID: nextTaskID, + resp := &TaskResp{ + Name: args.Name, + TaskID: nextTaskID, } return resp, nil } -- Gitee From 34d01917f4e09b946b65e83ac6d6e01677c8981a Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 11:36:14 +0800 Subject: [PATCH 0732/2647] debug and add id to create api Signed-off-by: Min Min --- .../common/repository/mongodb/workflow_v3.go | 16 +++++++++---- .../core/workflow/handler/workflow_v3.go | 9 +++++++- .../workflow/service/workflow/workflow_v3.go | 23 +++++++++++-------- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go index be22b7dc..07a5f160 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go @@ -2,6 +2,7 @@ package mongodb import ( "context" + "errors" "fmt" "go.mongodb.org/mongo-driver/bson" @@ -47,13 +48,20 @@ func (c *WorkflowV3Coll) EnsureIndex(ctx context.Context) error { return err } -func (c *WorkflowV3Coll) Create(obj *models.WorkflowV3) error { +func (c *WorkflowV3Coll) Create(obj *models.WorkflowV3) (string, error) { if obj == nil { - return fmt.Errorf("nil object") + return "", fmt.Errorf("nil object") } - _, err := c.InsertOne(context.TODO(), obj) - return err + res, err := c.InsertOne(context.TODO(), obj) + if err != nil { + return "", err + } + ID, ok := res.InsertedID.(primitive.ObjectID) + if !ok { + return "", errors.New("failed to get object id from create") + } + return ID.Hex(), err } func (c *WorkflowV3Coll) List(opt *ListWorkflowV3Option, pageNum, pageSize int64) ([]*models.WorkflowV3, int64, error) { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index b509005f..fa221bb4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -14,6 +14,10 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +type createWorkflowResp struct { + ID string `json:"id"` +} + func CreateWorkflowV3(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -33,7 +37,10 @@ func CreateWorkflowV3(c *gin.Context) { ctx.Err = errors.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Err = workflowservice.CreateWorkflowV3(ctx.UserName, req, ctx.Logger) + workflowID, err := workflowservice.CreateWorkflowV3(ctx.UserName, req, ctx.Logger) + resp := createWorkflowResp{ID: workflowID} + ctx.Resp = resp + ctx.Err = err } type listWorkflowV3Query struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index afe515a8..84a81480 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strings" "time" @@ -19,13 +20,13 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) -func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { +func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) (string, error) { if !checkWorkflowSubModules(workflowModel) { errStr := "工作流没有子模块,请先设置子模块" - return e.ErrUpsertWorkflow.AddDesc(errStr) + return "", e.ErrUpsertWorkflow.AddDesc(errStr) } if err := ensureWorkflowV3(workflowModel, logger); err != nil { - return e.ErrUpsertWorkflow.AddDesc(err.Error()) + return "", e.ErrUpsertWorkflow.AddDesc(err.Error()) } workflowModel.CreatedBy = user @@ -33,11 +34,12 @@ func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logge workflowModel.CreateTime = time.Now().Unix() workflowModel.UpdateTime = time.Now().Unix() - if err := commonrepo.NewWorkflowV3Coll().Create(workflowModel); err != nil { + workflowID, err := commonrepo.NewWorkflowV3Coll().Create(workflowModel) + if err != nil { logger.Errorf("Failed to create workflow v3, the error is: %s", err) - return e.ErrUpsertWorkflow.AddErr(err) + return "", e.ErrUpsertWorkflow.AddErr(err) } - return nil + return workflowID, nil } func ensureWorkflowV3(args *commonmodels.WorkflowV3, log *zap.SugaredLogger) error { @@ -229,10 +231,13 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str if err != nil { return nil, err } - server := fmt.Sprintf("%s/%s", externalSystem.Server, setting.Endpoint) - fmt.Println(">>>>>>>>>>>>> server:", server, "<<<<<<<<<<<<<<<") + serverURL, err := url.Parse(externalSystem.Server) + if err != nil { + return nil, err + } client := http.Client{} - req, err := http.NewRequest(setting.Method, server, strings.NewReader(setting.Body)) + requestPath := fmt.Sprintf("%s/%s/%s", serverURL.Host, serverURL.Path, setting.Endpoint) + req, err := http.NewRequest(setting.Method, requestPath, strings.NewReader(setting.Body)) if err != nil { return nil, err } -- Gitee From c81830f6f565db25341960e35a0a07fdf4075be5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 11:57:33 +0800 Subject: [PATCH 0733/2647] debug logs Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index 84a81480..dfc8a683 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -237,6 +237,7 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str } client := http.Client{} requestPath := fmt.Sprintf("%s/%s/%s", serverURL.Host, serverURL.Path, setting.Endpoint) + fmt.Println("!!!!!!!!! request path is:", requestPath, "!!!!!!!!!!!!!!!!!") req, err := http.NewRequest(setting.Method, requestPath, strings.NewReader(setting.Body)) if err != nil { return nil, err @@ -248,6 +249,8 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str } resp, err := client.Do(req) if err != nil || resp.StatusCode != http.StatusOK { + fmt.Println(">>>>>>>>>>>>>>>>>>>> error is:", err, "<<<<<<<<<<<<<<<<<<<<") + fmt.Println(">>>>>>>>>>>>>>>>>>>> status code is:", resp.StatusCode, "<<<<<<<<<<<<<<<<<") return nil, errors.New("failed to get response from external system") } decoder := json.NewDecoder(resp.Body) -- Gitee From d02f28f2f27112171334245ea2376932087fd457 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 12:04:01 +0800 Subject: [PATCH 0734/2647] optimize artifact file upload Signed-off-by: liu deyi --- .../reaper/core/service/reaper/artifact.go | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index fc0a385d..66c506c7 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -76,20 +76,8 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } if store != nil { - forcedPathStyle := true - if store.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) - if err != nil { - log.Errorf("failed to create s3 client, error is: %+v", err) - return err - } objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s", artifactPath, artifactFile.Name())) - err = s3client.Upload(store.Bucket, filePath, objectKey) - - if err != nil { - log.Errorf("artifactsUpload failed to upload package %s, err:%v", filePath, err) + if err = s3FileUpload(store, filePath, objectKey); err != nil { return err } } @@ -115,22 +103,29 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] return err } if store != nil { - forcedPathStyle := true - if store.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) - if err != nil { - log.Errorf("failed to create s3 client, error is: %s", err) - return err - } - objectKey := store.GetObjectPath("artifact.tar.gz") - if err = s3client.Upload(store.Bucket, temp.Name(), objectKey); err != nil { - log.Errorf("Archive s3 upload err:%s", err) + if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { return err } } return nil } + +func s3FileUpload(store *s3.S3, sourceFile, objectKey string) error { + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + log.Errorf("failed to create s3 client, error is: %s", err) + return err + } + + if err = s3client.Upload(store.Bucket, sourceFile, objectKey); err != nil { + log.Errorf("Archive s3 upload err:%s", err) + return err + } + return nil +} -- Gitee From 3613ec518dc4ec5236a15ea8d39ff84ea0b22ce4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 14:13:46 +0800 Subject: [PATCH 0735/2647] stuff Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index dfc8a683..e0b1bc30 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -181,6 +181,8 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA logger.Errorf("Failed to get workflowV3 detail from id: %s, the error is: %s", id, err) return nil, err } + fmt.Println("ID is:", workflow.ID) + fmt.Println("Name is:", workflow.Name) resp := make([]*WorkflowV3TaskArgs, 0) for _, param := range workflow.Parameters { switch param.Type { @@ -212,6 +214,7 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA logger.Error(errorMsg) return nil, errors.New(errorMsg) } + fmt.Println("stuff is:", param.ExternalSetting.Endpoint) options, err := getEnvsFromExternalSystem(param.ExternalSetting) if err != nil { logger.Errorf("Failed to get response from external system, the error is: %s", err) @@ -236,7 +239,7 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str return nil, err } client := http.Client{} - requestPath := fmt.Sprintf("%s/%s/%s", serverURL.Host, serverURL.Path, setting.Endpoint) + requestPath := fmt.Sprintf("%s%s/%s", serverURL.Host, serverURL.Path, setting.Endpoint) fmt.Println("!!!!!!!!! request path is:", requestPath, "!!!!!!!!!!!!!!!!!") req, err := http.NewRequest(setting.Method, requestPath, strings.NewReader(setting.Body)) if err != nil { -- Gitee From 6ca83bcf7a7a25bade61172f609678bf5e727b68 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 14:46:28 +0800 Subject: [PATCH 0736/2647] =?UTF-8?q?debugs=E3=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index e0b1bc30..c5ce320e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "net/url" "strings" "time" @@ -234,12 +233,8 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str if err != nil { return nil, err } - serverURL, err := url.Parse(externalSystem.Server) - if err != nil { - return nil, err - } client := http.Client{} - requestPath := fmt.Sprintf("%s%s/%s", serverURL.Host, serverURL.Path, setting.Endpoint) + requestPath := fmt.Sprintf("%s/%s", externalSystem.Server, setting.Endpoint) fmt.Println("!!!!!!!!! request path is:", requestPath, "!!!!!!!!!!!!!!!!!") req, err := http.NewRequest(setting.Method, requestPath, strings.NewReader(setting.Body)) if err != nil { -- Gitee From 127b927fe6e010c39d2d516ee2bfde7f19b8f438 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 14:58:00 +0800 Subject: [PATCH 0737/2647] final update on get env api Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/types.go | 11 +++++------ .../core/workflow/service/workflow/workflow_v3.go | 10 ++-------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index fd0ba0e6..e42985ca 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -60,10 +60,9 @@ type ExternalParamMapping struct { } type WorkflowV3TaskArgs struct { - Type string `json:"type"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` - Choice []string `json:"choice,omitempty"` - DisplayKey string `json:"display_key,omitempty"` - Options []map[string]interface{} `json:"options,omitempty"` + Type string `json:"type"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Choice []string `json:"choice,omitempty"` + Options []map[string]interface{} `json:"options,omitempty"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index c5ce320e..e07c89a5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -180,8 +180,6 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA logger.Errorf("Failed to get workflowV3 detail from id: %s, the error is: %s", id, err) return nil, err } - fmt.Println("ID is:", workflow.ID) - fmt.Println("Name is:", workflow.Name) resp := make([]*WorkflowV3TaskArgs, 0) for _, param := range workflow.Parameters { switch param.Type { @@ -204,16 +202,15 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA } for _, kv := range param.ExternalSetting.Params { if kv.Display { - externalEnv.DisplayKey = kv.ParamKey + externalEnv.Key = kv.ParamKey break } } - if externalEnv.DisplayKey == "" { + if externalEnv.Key == "" { errorMsg := fmt.Sprintf("error getting external key, cannot find the display key") logger.Error(errorMsg) return nil, errors.New(errorMsg) } - fmt.Println("stuff is:", param.ExternalSetting.Endpoint) options, err := getEnvsFromExternalSystem(param.ExternalSetting) if err != nil { logger.Errorf("Failed to get response from external system, the error is: %s", err) @@ -235,7 +232,6 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str } client := http.Client{} requestPath := fmt.Sprintf("%s/%s", externalSystem.Server, setting.Endpoint) - fmt.Println("!!!!!!!!! request path is:", requestPath, "!!!!!!!!!!!!!!!!!") req, err := http.NewRequest(setting.Method, requestPath, strings.NewReader(setting.Body)) if err != nil { return nil, err @@ -247,8 +243,6 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str } resp, err := client.Do(req) if err != nil || resp.StatusCode != http.StatusOK { - fmt.Println(">>>>>>>>>>>>>>>>>>>> error is:", err, "<<<<<<<<<<<<<<<<<<<<") - fmt.Println(">>>>>>>>>>>>>>>>>>>> status code is:", resp.StatusCode, "<<<<<<<<<<<<<<<<<") return nil, errors.New("failed to get response from external system") } decoder := json.NewDecoder(resp.Body) -- Gitee From 6fa86c5b2de34d0e44d3e6962a45111c60ca4ea3 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Mon, 22 Nov 2021 15:09:45 +0800 Subject: [PATCH 0738/2647] Update bug report template. Signed-off-by: leozhang2018 --- .github/ISSUE_TEMPLATE/bug_report.md | 64 +++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7df2129a..35305139 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,12 +7,66 @@ assignees: jamsman94 --- -**What steps did you take and what happened:** -[A clear and concise description of what the bug is.] + -**What did you expect to happen:** +**What happened?** + + +**What did you expect to happen?** + + +**How To Reproduce it(as minimally and precisely as possible)** + + +**Install Methods** + +- [ ] Helm +- [ ] Script base on K8s +- [ ] All in One +- [ ] Offline + +**Versions Used** +zadig: + +kubernetes: + + +**Environment** + +Cloud Provider: + + +Resources: + + +OS: + + +**Services Status** +```console +kubectl version +kubectl get po -n `zadig-installed-namespace` +# paste output here +``` + +If there is abnormal service, please provide service log + +```console +kubectl describe pods `abnormal-pod` +kubectl logs --tail=500 `abnormal-pod` +# paste output here +``` -**Anything else you would like to add:** -[Miscellaneous information that will assist in solving the issue.] -- Gitee From 473caa84af3e0f493da1dacd88375bd44bec2add Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 15:22:28 +0800 Subject: [PATCH 0739/2647] changed type from interface{} to string Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/types.go | 10 +++++----- .../core/workflow/service/workflow/workflow_v3.go | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index e42985ca..166908db 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -60,9 +60,9 @@ type ExternalParamMapping struct { } type WorkflowV3TaskArgs struct { - Type string `json:"type"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` - Choice []string `json:"choice,omitempty"` - Options []map[string]interface{} `json:"options,omitempty"` + Type string `json:"type"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Choice []string `json:"choice,omitempty"` + Options []map[string]string `json:"options,omitempty"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index e07c89a5..e70c4b6b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -225,7 +225,7 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA return resp, nil } -func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[string]interface{}, error) { +func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[string]string, error) { externalSystem, err := commonrepo.NewExternalSystemColl().GetByID(setting.SystemID) if err != nil { return nil, err @@ -246,15 +246,15 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str return nil, errors.New("failed to get response from external system") } decoder := json.NewDecoder(resp.Body) - respList := make([]map[string]interface{}, 0) + respList := make([]map[string]string, 0) err = decoder.Decode(&respList) if err != nil { return nil, err } - envList := make([]map[string]interface{}, 0) + envList := make([]map[string]string, 0) // find every single key required for _, respItem := range respList { - item := map[string]interface{}{} + item := map[string]string{} for _, kv := range setting.Params { item[kv.ParamKey] = respItem[kv.ResponseKey] } -- Gitee From b34d9d2bf1873c5d52c1e5484079ad123836b07b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 15:28:03 +0800 Subject: [PATCH 0740/2647] rollback Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/types.go | 10 +++++----- .../core/workflow/service/workflow/workflow_v3.go | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index 166908db..e42985ca 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -60,9 +60,9 @@ type ExternalParamMapping struct { } type WorkflowV3TaskArgs struct { - Type string `json:"type"` - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` - Choice []string `json:"choice,omitempty"` - Options []map[string]string `json:"options,omitempty"` + Type string `json:"type"` + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` + Choice []string `json:"choice,omitempty"` + Options []map[string]interface{} `json:"options,omitempty"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index e70c4b6b..e07c89a5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -225,7 +225,7 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA return resp, nil } -func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[string]string, error) { +func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[string]interface{}, error) { externalSystem, err := commonrepo.NewExternalSystemColl().GetByID(setting.SystemID) if err != nil { return nil, err @@ -246,15 +246,15 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str return nil, errors.New("failed to get response from external system") } decoder := json.NewDecoder(resp.Body) - respList := make([]map[string]string, 0) + respList := make([]map[string]interface{}, 0) err = decoder.Decode(&respList) if err != nil { return nil, err } - envList := make([]map[string]string, 0) + envList := make([]map[string]interface{}, 0) // find every single key required for _, respItem := range respList { - item := map[string]string{} + item := map[string]interface{}{} for _, kv := range setting.Params { item[kv.ParamKey] = respItem[kv.ResponseKey] } -- Gitee From 0045ed30ad517a868209bb4a59e7955e1ded6cda Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 15:37:28 +0800 Subject: [PATCH 0741/2647] stuff Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index e07c89a5..501efc6f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -211,7 +211,7 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA logger.Error(errorMsg) return nil, errors.New(errorMsg) } - options, err := getEnvsFromExternalSystem(param.ExternalSetting) + options, err := getEnvsFromExternalSystem(param.ExternalSetting, logger) if err != nil { logger.Errorf("Failed to get response from external system, the error is: %s", err) return nil, err @@ -225,7 +225,7 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA return resp, nil } -func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[string]interface{}, error) { +func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting, logger *zap.SugaredLogger) ([]map[string]interface{}, error) { externalSystem, err := commonrepo.NewExternalSystemColl().GetByID(setting.SystemID) if err != nil { return nil, err @@ -243,6 +243,7 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting) ([]map[str } resp, err := client.Do(req) if err != nil || resp.StatusCode != http.StatusOK { + logger.Errorf("failed to get response from external system, the error is: %s", err) return nil, errors.New("failed to get response from external system") } decoder := json.NewDecoder(resp.Body) -- Gitee From 35f1297a647cf914d6cf3fd62f055fee0707bf4b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 15:48:06 +0800 Subject: [PATCH 0742/2647] stuff Signed-off-by: Min Min --- .../core/workflow/service/workflow/workflow_v3.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index 501efc6f..9439597b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -242,9 +242,14 @@ func getEnvsFromExternalSystem(setting *commonmodels.ExternalSetting, logger *za } } resp, err := client.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { - logger.Errorf("failed to get response from external system, the error is: %s", err) - return nil, errors.New("failed to get response from external system") + if err != nil { + logger.Errorf("failed to get env from external system, the error is: %s", err) + return nil, err + } + if resp.StatusCode != 200 { + logger.Errorf("failed to get env from external system, the status code is: %d", resp.StatusCode) + errMsg := fmt.Sprintf("got response code %d from external system", resp.StatusCode) + return nil, errors.New(errMsg) } decoder := json.NewDecoder(resp.Body) respList := make([]map[string]interface{}, 0) -- Gitee From 72971858c5090b7e809fb685a31c3aff8240dc6c Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 17:01:41 +0800 Subject: [PATCH 0743/2647] error message Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index 9439597b..b708b0ef 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -219,7 +219,7 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA externalEnv.Options = options resp = append(resp, externalEnv) default: - return nil, err + return nil, fmt.Errorf("parameter of type %s is not supported", param.Type) } } return resp, nil -- Gitee From a7c6b3ffdadd064bfac0170df6ea3d9d511f8972 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 19:04:29 +0800 Subject: [PATCH 0744/2647] debug code Signed-off-by: liu deyi --- pkg/tool/httpclient/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index be6499fd..92bbb14b 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -18,6 +18,7 @@ package httpclient import ( "bytes" + "fmt" "io" "os" "time" @@ -148,7 +149,7 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons for _, rf := range rfs { rf(r) } - + fmt.Println(fmt.Sprintf("url:%s", url)) return c.wrapError(r.Execute(method, url)) } -- Gitee From 60e8ac5618744717e096b3e790ff85939296adac Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 19:22:53 +0800 Subject: [PATCH 0745/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 1 + pkg/tool/httpclient/client.go | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 79f734e8..ccad3bc8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -161,6 +161,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error // Wait ... func (p *TriggerTaskPlugin) Wait(ctx context.Context) { + p.Log.Infof("p.TaskTimeout():%d", p.TaskTimeout()) timeout := time.After(time.Duration(p.TaskTimeout())) for { diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index 92bbb14b..a9698ce3 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -18,7 +18,6 @@ package httpclient import ( "bytes" - "fmt" "io" "os" "time" @@ -149,7 +148,6 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons for _, rf := range rfs { rf(r) } - fmt.Println(fmt.Sprintf("url:%s", url)) return c.wrapError(r.Execute(method, url)) } -- Gitee From 4532d8f360c6bfb7e3b2d5bd88bfee3989910d15 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 19:49:00 +0800 Subject: [PATCH 0746/2647] debug code Signed-off-by: liu deyi --- .../core/workflow/handler/workflow_v3_task.go | 2 ++ .../warpdrive/core/service/taskplugin/trigger.go | 15 ++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index eae0ab7c..02814d53 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -130,6 +130,8 @@ func OutputWebhookPayload(c *gin.Context) { return } fmt.Println(fmt.Sprintf("args:%+v", args)) + fmt.Println(fmt.Sprintf("args.TaskEnvs:%+v", args.TaskEnvs)) + fmt.Println(fmt.Sprintf("args.TaskOutput:%+v", args.TaskOutput)) fmt.Println(fmt.Sprintf("header:%+v", c.Request.Header)) return } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index ccad3bc8..b22731b9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -55,8 +55,8 @@ type TriggerTaskPlugin struct { kubeClient client.Client Task *task.Trigger Log *zap.SugaredLogger - - ack func() + cancel context.CancelFunc + ack func() } func (p *TriggerTaskPlugin) SetAckFunc(ack func()) { @@ -116,6 +116,7 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi } }() p.Log.Infof("succeed to create trigger task %s", p.JobName) + ctx, p.cancel = context.WithCancel(context.Background()) httpClient := httpclient.New( httpclient.SetHostURL(p.Task.URL), ) @@ -161,22 +162,22 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error // Wait ... func (p *TriggerTaskPlugin) Wait(ctx context.Context) { - p.Log.Infof("p.TaskTimeout():%d", p.TaskTimeout()) timeout := time.After(time.Duration(p.TaskTimeout())) - + defer p.cancel() for { select { case <-ctx.Done(): p.Task.TaskStatus = config.StatusCancelled + p.Log.Infof("11111111111111") return - case <-timeout: p.Task.TaskStatus = config.StatusTimeout + p.Task.Error = "timeout" + p.Log.Infof("11111111222222") return - default: time.Sleep(time.Second * 1) - + p.Log.Infof("111111113333333") if p.IsTaskDone() { return } -- Gitee From add7fa7db7344b057cba3902f19ee9e18fd45a5c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 20:00:52 +0800 Subject: [PATCH 0747/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/handler/router.go | 2 +- .../core/workflow/handler/workflow_v3_task.go | 27 ++++++++----------- .../core/service/taskplugin/trigger.go | 2 +- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 7938e040..07569d3b 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -158,6 +158,6 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) - taskV3.POST("/workflow/webhookPayload", OutputWebhookPayload) + //taskV3.POST("/workflow/webhookPayload", OutputWebhookPayload) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index 02814d53..cacd4203 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -3,7 +3,6 @@ package handler import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "strconv" @@ -120,18 +119,14 @@ type KeyVal struct { IsCredential bool `bson:"is_credential" json:"is_credential"` } -func OutputWebhookPayload(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(WebhookPayload) - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - fmt.Println(fmt.Sprintf("args:%+v", args)) - fmt.Println(fmt.Sprintf("args.TaskEnvs:%+v", args.TaskEnvs)) - fmt.Println(fmt.Sprintf("args.TaskOutput:%+v", args.TaskOutput)) - fmt.Println(fmt.Sprintf("header:%+v", c.Request.Header)) - return -} +//func OutputWebhookPayload(c *gin.Context) { +// ctx := internalhandler.NewContext(c) +// defer func() { internalhandler.JSONResponse(c, ctx) }() +// +// args := new(WebhookPayload) +// if err := c.ShouldBindJSON(&args); err != nil { +// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) +// return +// } +// return +//} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index b22731b9..2c3752d1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -162,7 +162,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error // Wait ... func (p *TriggerTaskPlugin) Wait(ctx context.Context) { - timeout := time.After(time.Duration(p.TaskTimeout())) + timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) defer p.cancel() for { select { -- Gitee From 1ffe93665a23e831ffe4fc5053a7463543cfb96e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 20:31:20 +0800 Subject: [PATCH 0748/2647] debug code Signed-off-by: liu deyi --- .../reaper/core/service/reaper/reaper.go | 14 +++++++------- .../warpdrive/core/service/taskplugin/trigger.go | 3 --- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index c1c81cc4..4866648a 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -417,13 +417,6 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } } - if r.Ctx.ArtifactPath != "" { - if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { - log.Errorf("artifactsUpload err %s", err) - return err - } - } - // 将上面生成的统计结果文件上传到S3 if err = r.archiveTestFiles(); err != nil { log.Errorf("archiveTestFiles err %v", err) @@ -456,6 +449,13 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } } + if r.Ctx.ArtifactPath != "" { + if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { + log.Errorf("artifactsUpload err %s", err) + return err + } + } + // 运行物理机部署脚本 if err = r.RunPMDeployScripts(); err != nil { log.Errorf("RunPMDeployScripts err %v", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 2c3752d1..bbf32551 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -168,16 +168,13 @@ func (p *TriggerTaskPlugin) Wait(ctx context.Context) { select { case <-ctx.Done(): p.Task.TaskStatus = config.StatusCancelled - p.Log.Infof("11111111111111") return case <-timeout: p.Task.TaskStatus = config.StatusTimeout p.Task.Error = "timeout" - p.Log.Infof("11111111222222") return default: time.Sleep(time.Second * 1) - p.Log.Infof("111111113333333") if p.IsTaskDone() { return } -- Gitee From b866bba0bdf930f2ca111ba6ab7fec9609f2e185 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 20:34:42 +0800 Subject: [PATCH 0749/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/artifact.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 66c506c7..ffadd1c1 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -86,7 +86,7 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] return nil } - artifactPath := artifactPaths[0] + artifactPath := filepath.Join(activeWorkspace, artifactPaths[0]) temp, err := ioutil.TempFile("", "*artifact.tar.gz") if err != nil { log.Errorf("failed to create temp file %s", err) -- Gitee From 0c8f83ea9e1db4c4758496067ad9fdcaf385c5e5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 20:43:47 +0800 Subject: [PATCH 0750/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index bbf32551..d87e416e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -125,6 +125,7 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi if err != nil { return } + p.Log.Infof("artifactPath:%s", artifactPath) taskOutput := &task.TaskOutput{ Type: "object_storage", Value: artifactPath, -- Gitee From 21d6658bb2705d8d0f9ff50748eccf7909a36fb7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 21:03:43 +0800 Subject: [PATCH 0751/2647] optimize trigger stage Signed-off-by: liu deyi --- .../common/repository/models/task/trigger.go | 28 +++++++++++-------- .../core/service/taskplugin/trigger.go | 3 ++ .../core/service/types/task/trigger.go | 28 +++++++++++-------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go index afb7beae..8528a103 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go @@ -7,17 +7,23 @@ import ( ) type Trigger struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url,omitempty" json:"url,omitempty"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url,omitempty" json:"url,omitempty"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` + CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` + CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type CallbackPayload struct { + QRCodeURL string `bson:"QR_code_URL,omitempty" json:"QR_code_URL,omitempty"` } // ToSubTask ... diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index d87e416e..6853e3d5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -143,6 +143,9 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi if err != nil { return } + if !p.Task.IsCallback { + p.SetTriggerStatusCompleted(config.StatusPassed) + } } func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error) { diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index fc2d8ac4..19f36363 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -7,17 +7,23 @@ import ( ) type Trigger struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url,omitempty" json:"url,omitempty"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url,omitempty" json:"url,omitempty"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` + CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` + CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type CallbackPayload struct { + QRCodeURL string `bson:"QR_code_URL,omitempty" json:"QR_code_URL,omitempty"` } // ToSubTask ... -- Gitee From 945421b9a47e7671481fb3f0b242bacf5c3bdca2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 21:37:13 +0800 Subject: [PATCH 0752/2647] callback API Signed-off-by: Min Min --- .../aslan/core/common/handler/callback.go | 28 ++++++++++ .../repository/models/callback_request.go | 18 +++++++ .../repository/mongodb/callback_request.go | 54 +++++++++++++++++++ .../aslan/core/common/service/callback.go | 10 ++++ pkg/microservice/aslan/server/rest/router.go | 1 + 5 files changed, 111 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/handler/callback.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/callback_request.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go create mode 100644 pkg/microservice/aslan/core/common/service/callback.go diff --git a/pkg/microservice/aslan/core/common/handler/callback.go b/pkg/microservice/aslan/core/common/handler/callback.go new file mode 100644 index 00000000..0a41b5a5 --- /dev/null +++ b/pkg/microservice/aslan/core/common/handler/callback.go @@ -0,0 +1,28 @@ +package handler + +import ( + "fmt" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/log" +) + +func HandleCallback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &commonmodels.CallbackRequest{} + err := c.ShouldBindJSON(req) + if err != nil { + log.Errorf("failed to validate callback request, the error is: %s", err) + ctx.Err = fmt.Errorf("failed to validate callback request, the error is: %s", err) + return + } + + ctx.Err = commonservice.HandleCallback(req) + +} diff --git a/pkg/microservice/aslan/core/common/repository/models/callback_request.go b/pkg/microservice/aslan/core/common/repository/models/callback_request.go new file mode 100644 index 00000000..ec4cdd17 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/callback_request.go @@ -0,0 +1,18 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type CallbackRequest struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + WorkflowName string `bson:"task_name" json:"task_name"` + ProjectName string `bson:"project_name" json:"project_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + Status string `bson:"status" json:"status"` + StatusMessage string `bson:"status_message" json:"status_message"` + Payload map[string]interface{} `bson:"payload" json:"payload"` +} + +func (CallbackRequest) TableName() string { + return "callback_request" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go new file mode 100644 index 00000000..a915e447 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go @@ -0,0 +1,54 @@ +package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type CallbackRequestColl struct { + *mongo.Collection + + coll string +} + +func NewCallbackRequestColl() *CallbackRequestColl { + name := models.CallbackRequest{}.TableName() + return &CallbackRequestColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *CallbackRequestColl) GetCollectionName() string { + return c.coll +} + +func (c *CallbackRequestColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "task_name", Value: 1}, + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "task_id", Value: 1}, + }, + Options: options.Index().SetUnique(false), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *CallbackRequestColl) Create(req *models.CallbackRequest) error { + if req == nil { + return errors.New("nil Module args") + } + + _, err := c.Collection.InsertOne(context.TODO(), req) + + return err +} diff --git a/pkg/microservice/aslan/core/common/service/callback.go b/pkg/microservice/aslan/core/common/service/callback.go new file mode 100644 index 00000000..3edfa219 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/callback.go @@ -0,0 +1,10 @@ +package service + +import ( + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" +) + +func HandleCallback(req *commonmodels.CallbackRequest) error { + return commonrepo.NewCallbackRequestColl().Create(req) +} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 960dd790..77d68e65 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -61,6 +61,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { s.HandleContext(c) }) public.GET("/health", commonhandler.Health) + public.POST("/callback", commonhandler.HandleCallback) } // no auth required -- Gitee From 46bbe6359a0770d4f063f4a7fe85e4ea25075367 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 21:41:33 +0800 Subject: [PATCH 0753/2647] register for create index Signed-off-by: Min Min --- pkg/microservice/aslan/core/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 35d9c1f1..7a3b3542 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -159,6 +159,7 @@ func initDatabase() { template.NewProductColl(), commonrepo.NewBasicImageColl(), commonrepo.NewBuildColl(), + commonrepo.NewCallbackRequestColl(), commonrepo.NewCounterColl(), commonrepo.NewCronjobColl(), commonrepo.NewDeliveryActivityColl(), -- Gitee From 2b92ca0f8dd4aee8de53a8e8e157a2ed83924d11 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 22 Nov 2021 21:47:51 +0800 Subject: [PATCH 0754/2647] optimize trigger stage Signed-off-by: liu deyi --- .../core/service/taskplugin/trigger.go | 34 ++++++++++++++++--- .../core/service/types/task/trigger.go | 14 ++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 6853e3d5..c98e550c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -20,11 +20,13 @@ import ( "context" "encoding/json" "fmt" + "strconv" "time" "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" @@ -178,14 +180,38 @@ func (p *TriggerTaskPlugin) Wait(ctx context.Context) { p.Task.Error = "timeout" return default: - time.Sleep(time.Second * 1) - if p.IsTaskDone() { - return - } + time.Sleep(time.Second * 3) + p.Task.CallbackType = "wechat_callback" + p.Task.CallbackPayload = &task.CallbackPayload{QRCodeURL: "nihao"} + p.Task.TaskStatus = config.StatusPassed + return + //if p.IsTaskDone() { + // return + //} } } } +func (p *TriggerTaskPlugin) getCallbackObj(pipelineTask *task.Task) (*task.CallbackPayloadObj, error) { + url := "/api/" + httpClient := httpclient.New( + httpclient.SetHostURL(configbase.AslanServiceAddress()), + ) + + qs := map[string]string{ + "name": pipelineTask.PipelineName, + "taskId": strconv.Itoa(int(pipelineTask.TaskID)), + "projectName": pipelineTask.ProductName, + } + + CallbackPayloadObj := new(task.CallbackPayloadObj) + _, err := httpClient.Get(url, httpclient.SetResult(&CallbackPayloadObj), httpclient.SetQueryParams(qs)) + if err != nil { + return nil, err + } + return CallbackPayloadObj, nil +} + // Complete ... func (p *TriggerTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { } diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 19f36363..e5ce24e1 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -48,3 +48,17 @@ type TaskOutput struct { Type string `json:"type"` Value string `json:"value"` } + +type CallbackPayloadObj struct { + TaskName string `json:"task_name"` + ProjectName string `json:"project_name"` + TaskID int `json:"task_id"` + Type string `json:"type"` + Status string `json:"status"` + StatusMessage string `json:"status_message"` + Payload *Payload `json:"payload"` +} + +type Payload struct { + QRCodeURL string `json:"QR_code_URL"` +} -- Gitee From 39c40357151e9f2b7e1ca4e8123461f1bf729eb2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 22 Nov 2021 22:22:55 +0800 Subject: [PATCH 0755/2647] added callback query API for workflow Signed-off-by: Min Min --- .../repository/models/callback_request.go | 7 +++-- .../repository/mongodb/callback_request.go | 30 +++++++++++++++++++ .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_v3_task.go | 13 ++++++++ .../service/workflow/workflow_task_v3.go | 8 +++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/callback_request.go b/pkg/microservice/aslan/core/common/repository/models/callback_request.go index ec4cdd17..9aced44f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/callback_request.go +++ b/pkg/microservice/aslan/core/common/repository/models/callback_request.go @@ -3,9 +3,10 @@ package models import "go.mongodb.org/mongo-driver/bson/primitive" type CallbackRequest struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type string `bson:"type" json:"type"` - WorkflowName string `bson:"task_name" json:"task_name"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + // this is called pipeline name because it is called so all around this system + PipelineName string `bson:"task_name" json:"task_name"` ProjectName string `bson:"project_name" json:"project_name"` TaskID int64 `bson:"task_id" json:"task_id"` Status string `bson:"status" json:"status"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go index a915e447..c4baca95 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go @@ -52,3 +52,33 @@ func (c *CallbackRequestColl) Create(req *models.CallbackRequest) error { return err } + +type CallbackFindOption struct { + TaskID int64 + PipelineName string + ProjectName string +} + +func (c *CallbackRequestColl) Find(req *CallbackFindOption) (*models.CallbackRequest, error) { + if req == nil { + return nil, errors.New("nil FindOption") + } + + query := bson.M{} + if req.TaskID != 0 { + query["task_id"] = req.TaskID + } + if req.PipelineName != "" { + query["task_name"] = req.PipelineName + } + if req.ProjectName != "" { + query["project_name"] = req.ProjectName + } + + resp := new(models.CallbackRequest) + err := c.Collection.FindOne(context.TODO(), query).Decode(resp) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 42484285..02aee1b6 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -158,5 +158,6 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) + taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index dca21680..e91d619c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -98,3 +98,16 @@ func GetWorkflowTaskV3(c *gin.Context) { ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV3(taskID, c.Param("name"), config.WorkflowTypeV3, ctx.Logger) } + +func GetWorkflowTaskV3Callback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV3Callback(taskID, c.Param("name"), c.Query("project_name"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index d530f4dc..92c66a1d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -190,6 +190,14 @@ func GetWorkflowTaskV3(taskID int64, pipelineName string, typeString config.Pipe return resp, nil } +func GetWorkflowTaskV3Callback(taskID int64, pipelineName, projectName string, logger *zap.SugaredLogger) (*commonmodels.CallbackRequest, error) { + return commonrepo.NewCallbackRequestColl().Find(&commonrepo.CallbackFindOption{ + TaskID: taskID, + PipelineName: pipelineName, + ProjectName: projectName, + }) +} + func CleanWorkflow3(task *task.Task) { task.ConfigPayload = nil task.TaskArgs = nil -- Gitee From 961aaea5eb480c85e8dacecaf15be0d04b028fae Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 23 Nov 2021 10:37:55 +0800 Subject: [PATCH 0756/2647] stuff Signed-off-by: Min Min --- .../aslan/core/workflow/handler/workflow_v3_task.go | 2 +- .../aslan/core/workflow/service/workflow/workflow_task_v3.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index e91d619c..9b01e739 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -109,5 +109,5 @@ func GetWorkflowTaskV3Callback(c *gin.Context) { return } - ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV3Callback(taskID, c.Param("name"), c.Query("project_name"), ctx.Logger) + ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV3Callback(taskID, c.Param("name"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 92c66a1d..b9e9ecee 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -190,11 +190,10 @@ func GetWorkflowTaskV3(taskID int64, pipelineName string, typeString config.Pipe return resp, nil } -func GetWorkflowTaskV3Callback(taskID int64, pipelineName, projectName string, logger *zap.SugaredLogger) (*commonmodels.CallbackRequest, error) { +func GetWorkflowTaskV3Callback(taskID int64, pipelineName string, logger *zap.SugaredLogger) (*commonmodels.CallbackRequest, error) { return commonrepo.NewCallbackRequestColl().Find(&commonrepo.CallbackFindOption{ TaskID: taskID, PipelineName: pipelineName, - ProjectName: projectName, }) } -- Gitee From 77c75b6b30366dbad62b72085661443422d85dfd Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 16 Nov 2021 17:01:24 +0800 Subject: [PATCH 0757/2647] xx Signed-off-by: mouuii service Signed-off-by: mouuii add comment Signed-off-by: mouuii --- .../core/codehost/repository/models/codehost.go | 10 +++++----- pkg/shared/client/systemconfig/codehost.go | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index bbbed219..730ff415 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -4,15 +4,15 @@ type CodeHost struct { ID int `bson:"id" json:"id"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` - IsReady string `bson:"is_ready" json:"ready"` - AccessToken string `bson:"access_token" json:"accessToken"` - RefreshToken string `bson:"refresh_token" json:"refreshToken"` + IsReady string `bson:"is_ready" json:"is_ready"` + AccessToken string `bson:"access_token" json:"access_token"` + RefreshToken string `bson:"refresh_token" json:"refresh_token"` Namespace string `bson:"namespace" json:"namespace"` - ApplicationId string `bson:"application_id" json:"applicationId"` + ApplicationId string `bson:"application_id" json:"application_id"` Region string `bson:"region" json:"region,omitempty"` Username string `bson:"username" json:"username,omitempty"` Password string `bson:"password" json:"password,omitempty"` - ClientSecret string `bson:"client_secret" json:"clientSecret"` + ClientSecret string `bson:"client_secret" json:"client_secret"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 733fcf0b..39104606 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -17,13 +17,14 @@ type CodeHost struct { ID int `json:"id"` Address string `json:"address"` Type string `json:"type"` - AccessToken string `json:"accessToken"` + AccessToken string `json:"access_token"` Namespace string `json:"namespace"` Region string `json:"region"` - AccessKey string `json:"applicationId"` - SecretKey string `json:"clientSecret"` - Username string `json:"username"` - Password string `json:"password"` + // the field and tag not consistent because of db field + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + Username string `json:"username"` + Password string `json:"password"` } type Option struct { -- Gitee From 8fba8d9492e259a3dd0b5f10012f7aabb93e9b9f Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 23 Nov 2021 10:43:18 +0800 Subject: [PATCH 0758/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/callback_request.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go index c4baca95..c1eb40f1 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go @@ -32,8 +32,8 @@ func (c *CallbackRequestColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ Keys: bson.D{ bson.E{Key: "task_name", Value: 1}, - bson.E{Key: "project_name", Value: 1}, bson.E{Key: "task_id", Value: 1}, + bson.E{Key: "project_name", Value: 1}, }, Options: options.Index().SetUnique(false), } @@ -45,7 +45,7 @@ func (c *CallbackRequestColl) EnsureIndex(ctx context.Context) error { func (c *CallbackRequestColl) Create(req *models.CallbackRequest) error { if req == nil { - return errors.New("nil Module args") + return errors.New("nil callbackRequest args") } _, err := c.Collection.InsertOne(context.TODO(), req) -- Gitee From a47b1a9ba540556611e6c864777dc2df45a9c276 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 23 Nov 2021 11:16:53 +0800 Subject: [PATCH 0759/2647] optimize trigger stage Signed-off-by: liu deyi --- .../aslan/core/workflow/handler/router.go | 2 +- .../core/workflow/handler/workflow_v3_task.go | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 7f90915c..6ad02de2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -158,7 +158,7 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) - //taskV3.POST("/workflow/webhookPayload", OutputWebhookPayload) + taskV3.POST("/workflow/webhookPayload", OutputWebhookPayload) taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index fc589cca..712e177c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -119,17 +119,17 @@ type KeyVal struct { IsCredential bool `bson:"is_credential" json:"is_credential"` } -//func OutputWebhookPayload(c *gin.Context) { -// ctx := internalhandler.NewContext(c) -// defer func() { internalhandler.JSONResponse(c, ctx) }() -// -// args := new(WebhookPayload) -// if err := c.ShouldBindJSON(&args); err != nil { -// ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) -// return -// } -// return -//} +func OutputWebhookPayload(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(WebhookPayload) + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + return +} func GetWorkflowTaskV3Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) -- Gitee From 5c59314ad59da183a49d89147a6eeafd69c9028e Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 23 Nov 2021 11:22:26 +0800 Subject: [PATCH 0760/2647] make callback api public Signed-off-by: Min Min --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 9c56543b..f0f15b3a 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -64,6 +64,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/directory/codehostss/?*/auth", "api/directory/codehosts/callback"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/callback"}, + }, } // actions which are allowed for system admins only. -- Gitee From 796abd84d4686d031446a8c69f7e3828849bd44b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 23 Nov 2021 11:43:16 +0800 Subject: [PATCH 0761/2647] optimize trigger stage Signed-off-by: liu deyi --- .../core/service/taskplugin/trigger.go | 19 ++++++++++--------- .../core/service/types/task/trigger.go | 18 +++++++----------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 54e64e37..84f880df 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -185,18 +185,19 @@ func (p *TriggerTaskPlugin) Wait(ctx context.Context) { return default: time.Sleep(time.Second * 3) - p.Task.CallbackType = "wechat_callback" - p.Task.CallbackPayload = &task.CallbackPayload{QRCodeURL: "nihao"} - p.Task.TaskStatus = config.StatusPassed - - callbackPayloadObj, err := p.getCallbackObj(p.taskId, p.pipelineName) - if err != nil { - p.Log.Error(err) + callbackPayloadObj, _ := p.getCallbackObj(p.taskId, p.pipelineName) + if callbackPayloadObj != nil { + p.Task.CallbackType = callbackPayloadObj.Type + p.Task.CallbackPayload = callbackPayloadObj.Payload + if callbackPayloadObj.Status == "success" { + p.Task.TaskStatus = config.StatusPassed + return + } p.Task.TaskStatus = config.StatusFailed - p.Task.Error = err.Error() + p.Task.Error = callbackPayloadObj.StatusMessage return } - p.Log.Infof("callbackPayloadObj:%+v", callbackPayloadObj) + if p.IsTaskDone() { return } diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index e5ce24e1..18461fd6 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -50,15 +50,11 @@ type TaskOutput struct { } type CallbackPayloadObj struct { - TaskName string `json:"task_name"` - ProjectName string `json:"project_name"` - TaskID int `json:"task_id"` - Type string `json:"type"` - Status string `json:"status"` - StatusMessage string `json:"status_message"` - Payload *Payload `json:"payload"` -} - -type Payload struct { - QRCodeURL string `json:"QR_code_URL"` + TaskName string `json:"task_name"` + ProjectName string `json:"project_name"` + TaskID int `json:"task_id"` + Type string `json:"type"` + Status string `json:"status"` + StatusMessage string `json:"status_message"` + Payload *CallbackPayload `json:"payload"` } -- Gitee From 7598070a35d288c8969e7145fe48ab95f4fbec1f Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 23 Nov 2021 12:21:43 +0800 Subject: [PATCH 0762/2647] add retry api Signed-off-by: allenshen --- .../repository/models/delivery_version.go | 40 +- .../repository/mongodb/template/product.go | 37 +- .../aslan/core/common/service/fs/s3.go | 25 +- .../aslan/core/common/service/s3/s3.go | 19 + .../aslan/core/delivery/handler/router.go | 2 + .../aslan/core/delivery/handler/version.go | 29 + .../aslan/core/delivery/service/version.go | 547 +++++++++++++----- .../aslan/core/environment/service/helm.go | 3 +- .../aslan/core/project/service/project.go | 26 +- pkg/setting/consts.go | 8 + 10 files changed, 553 insertions(+), 183 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 956070a3..c221d1af 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -26,23 +26,31 @@ type DeliveryChart struct { Repo string `bson:"repo" json:"repo"` } +type DeliveryVersionProgress struct { + SuccessChartCount int `json:"successChartCount"` + TotalChartCount int `json:"totalChartCount"` + PackageUploadStatus string `json:"packageStatus"` + Error string `json:"error"` +} + type DeliveryVersion struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Version string `bson:"version" json:"version"` - ProductName string `bson:"product_name" json:"productName"` - WorkflowName string `bson:"workflow_name" json:"workflowName"` - Type string `bson:"type" json:"type"` - TaskID int `bson:"task_id" json:"taskId"` - Desc string `bson:"desc" json:"desc"` - Labels []string `bson:"labels" json:"labels"` - ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` - Charts []*DeliveryChart `bson:"charts" json:"charts"` - Status string `bson:"status" json:"status"` - Error string `bson:"error" json:"error"` - CreateArgument interface{} `bson:"createArgument" json:"-"` - CreatedBy string `bson:"created_by" json:"createdBy"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Version string `bson:"version" json:"version"` + ProductName string `bson:"product_name" json:"productName"` + WorkflowName string `bson:"workflow_name" json:"workflowName"` + Type string `bson:"type" json:"type"` + TaskID int `bson:"task_id" json:"taskId"` + Desc string `bson:"desc" json:"desc"` + Labels []string `bson:"labels" json:"labels"` + ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` + Charts []*DeliveryChart `bson:"charts" json:"charts"` + Status string `bson:"status" json:"status"` + Error string `bson:"error" json:"error"` + Progress *DeliveryVersionProgress `bson:"-" json:"progress"` + CreateArgument interface{} `bson:"createArgument" json:"-"` + CreatedBy string `bson:"created_by" json:"createdBy"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } func (DeliveryVersion) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index c5d23227..88f03844 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -39,6 +39,7 @@ type ProjectInfo struct { UpdatedBy string `bson:"update_by"` OnboardStatus int `bson:"onboarding_status"` Public bool `bson:"public"` + DeployType string `bson:"deploy_type"` } type ProductColl struct { @@ -82,8 +83,8 @@ func (c *ProductColl) FindProjectName(project string) (*template.Product, error) } func (c *ProductColl) ListNames(inNames []string) ([]string, error) { - res, err := c.listProjects(inNames, bson.D{ - {"product_name", 1}, + res, err := c.listProjects(inNames, bson.M{ + "product_name": "$product_name", }) if err != nil { return nil, err @@ -98,29 +99,34 @@ func (c *ProductColl) ListNames(inNames []string) ([]string, error) { } func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error) { - return c.listProjects(inNames, bson.D{ - {"product_name", 1}, - {"project_name", 1}, - {"description", 1}, - {"update_time", 1}, - {"update_by", 1}, - {"onboarding_status", 1}, - {"public", 1}, + return c.listProjects(inNames, bson.M{ + "product_name": "$product_name", + "project_name": "$project_name", + "description": "$description", + "update_time": "$update_time", + "update_by": "$update_by", + "onboarding_status": "$onboarding_status", + "public": "$public", + "deploy_type": "$product_feature.deploy_type", }) } -func (c *ProductColl) listProjects(inNames []string, projection bson.D) ([]*ProjectInfo, error) { - opts := options.Find() +func (c *ProductColl) listProjects(inNames []string, projection bson.M) ([]*ProjectInfo, error) { filter := bson.M{} if len(inNames) > 0 { filter["product_name"] = bson.M{"$in": inNames} } - if len(projection) > 0 { - opts.SetProjection(projection) + pipeline := []bson.M{ + { + "$match": filter, + }, + { + "$project": projection, + }, } - cursor, err := c.Collection.Find(context.TODO(), filter, opts) + cursor, err := c.Collection.Aggregate(context.TODO(), pipeline) if err != nil { return nil, err } @@ -130,7 +136,6 @@ func (c *ProductColl) listProjects(inNames []string, projection bson.D) ([]*Proj if err != nil { return nil, err } - return res, nil } diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 0680a4c2..e792f0f2 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -24,13 +24,32 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" fsutil "github.com/koderover/zadig/pkg/util/fs" ) +func ArchiveAndUploadFilesToSpecifiedS3(fileTree fs.FS, name, s3Base string, copies []string, s3Id string, logger *zap.SugaredLogger) error { + s3Storage, err := s3service.FindS3ById(s3Id) + if err != nil { + logger.Errorf("Failed to find default s3, err:%v", err) + return err + } + return archiveAndUploadFiles(fileTree, name, s3Base, copies, s3Storage, logger) +} + func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, copies []string, logger *zap.SugaredLogger) error { + s3Storage, err := s3service.FindDefaultS3() + if err != nil { + logger.Errorf("Failed to find default s3, err:%v", err) + return err + } + return archiveAndUploadFiles(fileTree, name, s3Base, copies, s3Storage, logger) +} + +func archiveAndUploadFiles(fileTree fs.FS, name, s3Base string, copies []string, s3Storage *s3.S3, logger *zap.SugaredLogger) error { tmpDir, err := os.MkdirTemp("", "") if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) @@ -44,11 +63,7 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, copies []str logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) return err } - s3Storage, err := s3service.FindDefaultS3() - if err != nil { - logger.Errorf("Failed to find default s3, err:%v", err) - return err - } + forcedPathStyle := true if s3Storage.Provider == setting.ProviderSourceAli { forcedPathStyle = false diff --git a/pkg/microservice/aslan/core/common/service/s3/s3.go b/pkg/microservice/aslan/core/common/service/s3/s3.go index 24655148..7a7f31a7 100644 --- a/pkg/microservice/aslan/core/common/service/s3/s3.go +++ b/pkg/microservice/aslan/core/common/service/s3/s3.go @@ -148,6 +148,25 @@ func FindDefaultS3() (*S3, error) { return &S3{S3Storage: storage}, nil } +func FindS3ById(id string) (*S3, error) { + storage, err := commonrepo.NewS3StorageColl().Find(id) + if err != nil { + log.Warnf("Failed to find default s3 in db, err: %s", err) + return &S3{ + S3Storage: &models.S3Storage{ + Ak: config.S3StorageAK(), + Sk: config.S3StorageSK(), + Endpoint: config.S3StorageEndpoint(), + Bucket: config.S3StorageBucket(), + Insecure: config.S3StorageProtocol() == "http", + Provider: setting.ProviderSourceSystemDefault, + }, + }, nil + } + + return &S3{S3Storage: storage}, nil +} + // 获取内置的s3 func FindInternalS3() *S3 { storage := &models.S3Storage{ diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 18ecc0de..69b26bb4 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -48,6 +48,8 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.GET("/charts", DownloadDeliveryChart) deliveryRelease.GET("/charts/preview", PreviewGetDeliveryChart) + deliveryRelease.GET("/charts/filePath", GetDeliveryChartFilePath) + deliveryRelease.GET("/charts/fileContent", GetDeliveryChartFileContent) deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index f91c4ec0..b750ab62 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -152,6 +152,7 @@ func ListDeliveryVersion(c *gin.Context) { for _, deliveryVersion := range deliveryVersions { releaseInfo := new(ReleaseInfo) //versionInfo + deliveryVersion.Progress = deliveryservice.FillDeliveryProgressInfo(deliveryVersion) releaseInfo.VersionInfo = deliveryVersion //deployInfo @@ -412,3 +413,31 @@ func PreviewGetDeliveryChart(c *gin.Context) { ctx.Resp, ctx.Err = deliveryservice.PreviewDeliveryChart(projectName, versionName, chartName, ctx.Logger) } + +func GetDeliveryChartFilePath(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.DeliveryChartFilePathArgs) + err := c.BindQuery(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFilePath(args, ctx.Logger) +} + +func GetDeliveryChartFileContent(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.DeliveryChartFileContentArgs) + err := c.BindQuery(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFileContent(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 9973e81d..88eca22f 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -23,7 +23,6 @@ import ( "net/http" "os" "path/filepath" - "strings" "sync" "time" @@ -49,7 +48,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" - fsutil "github.com/koderover/zadig/pkg/util/fs" ) type CreateHelmDeliveryVersionOption struct { @@ -65,30 +63,47 @@ type CreateHelmDeliveryVersionChartData struct { type CreateHelmDeliveryVersionArgs struct { CreateBy string `json:"-"` + ProductName string `json:"productName"` + Retry bool `json:"retry"` Version string `json:"version"` Desc string `json:"desc"` - ProductName string `json:"productName"` EnvName string `json:"envName"` Labels []string `json:"labels"` - ChartRepoName string `json:"chartRepoName"` + ImageRepoName string `json:"imageRepoName"` *DeliveryVersionChartData } type DeliveryVersionChartData struct { - ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` - Options *CreateHelmDeliveryVersionOption `json:"options"` + ChartRepoName string `json:"chartRepoName"` + ImageRegistryID string `json:"imageRegistryID"` + ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` + Options *CreateHelmDeliveryVersionOption `json:"options"` } type DeliveryChartData struct { - ChartData *CreateHelmDeliveryVersionChartData - ProductService *commonmodels.ProductService - ServiceObj *commonmodels.Service + ChartData *CreateHelmDeliveryVersionChartData + ServiceObj *commonmodels.Service } type DeliveryChartResp struct { FileInfos []*types.FileInfo `json:"fileInfos"` } +type DeliveryChartFilePathArgs struct { + Dir string `json:"dir"` + ProjectName string `json:"projectName"` + ChartName string `json:"chartName"` + Version string `json:"version"` +} + +type DeliveryChartFileContentArgs struct { + FilePath string `json:"filePath"` + FileName string `json:"fileName"` + ProjectName string `json:"projectName"` + ChartName string `json:"chartName"` + Version string `json:"version"` +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { resp, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { @@ -116,6 +131,53 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } +func FillDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion) *commonmodels.DeliveryVersionProgress { + if deliveryVersion.Type != setting.DeliveryVersionTypeChart { + return nil + } + successfulChartCount := len(deliveryVersion.Charts) + + progress := &commonmodels.DeliveryVersionProgress{ + SuccessChartCount: successfulChartCount, + TotalChartCount: 0, + PackageUploadStatus: "", + Error: "", + } + if deliveryVersion.Status == setting.DeliveryVersionStatusSuccess { + progress.TotalChartCount = successfulChartCount + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusSuccess + return progress + } + + argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) + if err != nil { + log.Errorf("failed to marshal arguments, versionName: %s err %s", deliveryVersion.Version, err) + return progress + } + createArgs := new(DeliveryVersionChartData) + err = json.Unmarshal(argsBytes, createArgs) + if err != nil { + log.Errorf("failed to unMarshal arguments, versionName: %s err %s", deliveryVersion.Version, err) + return progress + } + + progress.TotalChartCount = len(createArgs.ChartDatas) + + if deliveryVersion.Status == setting.DeliveryVersionStatusFailed { + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusFailed + return progress + } + + if len(createArgs.ChartDatas) > successfulChartCount { + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusWaiting + return progress + } + + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusUploading + return progress + +} + func getChartTGZDir(productName, versionName string) string { tmpDir := os.TempDir() return filepath.Join(tmpDir, "chart-tgz", productName, versionName) @@ -169,6 +231,9 @@ func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.Hel } } + // push 镜像 + //commonrepo.NewRegistryNamespaceColl().Find(&commonrepo.FindRegOps{}) + fullPath := filepath.Join(base, serviceObj.ServiceName) revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) @@ -241,7 +306,7 @@ func getChartmuseumError(b []byte, code int) error { return errors.Errorf("%d: %s", code, er.Error) } -func mkChartTGZFileDir(productName, versionName string) (string, error) { +func makeChartTGZFileDir(productName, versionName string) (string, error) { path := getChartTGZDir(productName, versionName) if err := os.RemoveAll(path); err != nil { if !os.IsExist(err) { @@ -256,51 +321,136 @@ func mkChartTGZFileDir(productName, versionName string) (string, error) { } func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { - // need appoint chart info - if len(args.ChartDatas) == 0 { - return e.ErrCreateDeliveryVersion.AddDesc("no chart info appointed") - } - - // prepare data - productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) - if err != nil { - log.Infof("failed to query product info, procutName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) - } - repoInfo, err := getChartRepoData(args.ChartRepoName) - if err != nil { - log.Infof("failed to query chart-repo info, procutName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query chart-repo info, procutName: %s, envName %s, repoName", args.ProductName, args.EnvName, args.ChartRepoName)) - } - - dir, err := mkChartTGZFileDir(args.ProductName, args.Version) - if err != nil { - return e.ErrCreateDeliveryVersion.AddErr(err) + if args.Retry { + return RetryCreateHelmDeliveryVersion(args.ProductName, args.Version, logger) + } else { + return CreateNewHelmDeliveryVersion(args, logger) } +} - // validate chartInfo, make sure service is in environment - // prepare data set for chart delivery +// validate chartInfo, make sure service is in environment +// prepare data set for chart delivery +func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productInfo *commonmodels.Product) (map[string]*DeliveryChartData, error) { chartDataMap := make(map[string]*DeliveryChartData) serviceMap := productInfo.GetServiceMap() - for _, chartDta := range args.ChartDatas { + for _, chartDta := range chartDatas { if productService, ok := serviceMap[chartDta.ServiceName]; ok { serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: chartDta.ServiceName, Revision: productService.Revision, Type: setting.HelmDeployType, - ProductName: args.ProductName, + ProductName: productInfo.ProductName, }) if err != nil { - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query service: %s", chartDta.ServiceName)) + return nil, fmt.Errorf("failed to query service: %s", chartDta.ServiceName) } chartDataMap[chartDta.ServiceName] = &DeliveryChartData{ - ChartData: chartDta, - ProductService: productService, - ServiceObj: serviceObj, + ChartData: chartDta, + //ProductService: productService, + ServiceObj: serviceObj, } } else { - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("service %s not found in environment", chartDta.ServiceName)) + return nil, fmt.Errorf("service %s not found in environment", chartDta.ServiceName) + } + } + return chartDataMap, nil +} + +func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData, logger *zap.SugaredLogger) (err error) { + defer func() { + if err != nil { + deliveryVersion.Status = setting.DeliveryVersionStatusFailed + deliveryVersion.Error = err.Error() + } else { + deliveryVersion.Status = setting.DeliveryVersionStatusSuccess + deliveryVersion.Error = "" + } + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) + if err != nil { + logger.Errorf("failed to update delivery version data, name: %s error: %s", deliveryVersion.Version, err) } + }() + + var errLock sync.Mutex + errorList := &multierror.Error{} + + appendError := func(err error) { + errLock.Lock() + defer errLock.Unlock() + errorList = multierror.Append(errorList, err) + } + + dir, err := makeChartTGZFileDir(deliveryVersion.ProductName, deliveryVersion.Version) + if err != nil { + return err + } + repoInfo, err := getChartRepoData(args.ChartRepoName) + if err != nil { + log.Infof("failed to query chart-repo info, productName: %s, err: %s", deliveryVersion.ProductName, err) + return fmt.Errorf("failed to query chart-repo info, productName: %s, repoName: %s", deliveryVersion.ProductName, args.ChartRepoName) + } + + // push charts to repo + wg := sync.WaitGroup{} + for _, chartData := range chartDataMap { + wg.Add(1) + go func(cData *DeliveryChartData) { + defer wg.Done() + err := handleSingleChart(cData, repoInfo, dir) + if err != nil { + logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + appendError(err) + } else { + err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(deliveryVersion.Version, &commonmodels.DeliveryChart{ + Name: cData.ChartData.ServiceName, + Version: cData.ChartData.Version, + Repo: args.ChartRepoName, + }) + if err != nil { + appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) + } + } + }(chartData) + } + wg.Wait() + + if errorList.ErrorOrNil() != nil { + err = errorList.ErrorOrNil() + return + } + + // no need to upload chart packages + if args.Options == nil || !args.Options.EnableOfflineDist { + return + } + + //tar all chart files and send to s3 store + fsTree := os.DirFS(dir) + ServiceS3Base := configbase.ObjectStorageDeliveryVersionPath(deliveryVersion.ProductName) + if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, deliveryVersion.Version, ServiceS3Base, nil, args.Options.S3StorageID, logger); err != nil { + logger.Errorf("failed to upload chart package files for project %s, err: %s", deliveryVersion.ProductName, err) + err = errors.Wrapf(err, "failed to upload package file") + return + } + return +} + +func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { + // need appoint chart info + if len(args.ChartDatas) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("no chart info appointed") + } + + // prepare data + productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) + if err != nil { + log.Infof("failed to query product info, productName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) + } + + chartDataMap, err := prepareChartData(args.ChartDatas, productInfo) + if err != nil { + return e.ErrCreateDeliveryVersion.AddErr(err) } productInfo.ID, _ = primitive.ObjectIDFromHex("") @@ -322,91 +472,149 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) if err != nil { logger.Errorf("failed to insert version data, err: %s", err) - return fmt.Errorf("failed to insert delivery version: %s", versionObj.Version) + return e.ErrCreateDeliveryVersion.AddErr(fmt.Errorf("failed to insert delivery version: %s", versionObj.Version)) } - go func() { - var err error - defer func() { - if err != nil { - versionObj.Status = setting.DeliveryVersionStatusFailed - versionObj.Error = err.Error() - } else { - versionObj.Status = setting.DeliveryVersionStatusSuccess - versionObj.Error = "" - } - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionObj.Version, versionObj.Status, versionObj.Error) - if err != nil { - logger.Errorf("failed to update delivery version data, name: %s error: %s", versionObj.Version, err) - } - }() + err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) + if err != nil { + return err + } - var errLock sync.Mutex - errorList := &multierror.Error{} + //go func() { + // var err error + // defer func() { + // if err != nil { + // versionObj.Status = setting.DeliveryVersionStatusFailed + // versionObj.Error = err.Error() + // } else { + // versionObj.Status = setting.DeliveryVersionStatusSuccess + // versionObj.Error = "" + // } + // err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionObj.Version, versionObj.Status, versionObj.Error) + // if err != nil { + // logger.Errorf("failed to update delivery version data, name: %s error: %s", versionObj.Version, err) + // } + // }() + // + // var errLock sync.Mutex + // errorList := &multierror.Error{} + // + // appendError := func(err error) { + // errLock.Lock() + // defer errLock.Unlock() + // errorList = multierror.Append(errorList, err) + // } + // + // // push charts to repo + // wg := sync.WaitGroup{} + // for _, chartData := range chartDataMap { + // wg.Add(1) + // go func(cData *DeliveryChartData) { + // defer wg.Done() + // err := handleSingleChart(cData, repoInfo, dir) + // if err != nil { + // logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + // appendError(err) + // } else { + // err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(versionObj.Version, &commonmodels.DeliveryChart{ + // Name: cData.ChartData.ServiceName, + // Version: cData.ChartData.Version, + // Repo: args.ChartRepoName, + // }) + // if err != nil { + // appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) + // } + // } + // }(chartData) + // } + // wg.Wait() + // + // if errorList.ErrorOrNil() != nil { + // err = errorList.ErrorOrNil() + // return + // } + // + // // no need to upload chart packages + // if args.Options == nil || !args.Options.EnableOfflineDist { + // return + // } + // + // //tar all chart files and send to s3 store + // fsTree := os.DirFS(dir) + // ServiceS3Base := configbase.ObjectStorageDeliveryVersionPath(args.ProductName) + // if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, versionObj.Version, ServiceS3Base, nil, args.Options.S3StorageID, logger); err != nil { + // logger.Errorf("failed to upload chart package files for project %s, err: %s", args.ProductName, err) + // err = errors.Wrapf(err, "failed to upload package file") + // return + // } + //}() - appendError := func(err error) { - errLock.Lock() - defer errLock.Unlock() - errorList = multierror.Append(errorList, err) - } + return nil +} - // push charts to repo - wg := sync.WaitGroup{} - for _, chartData := range chartDataMap { - wg.Add(1) - go func(cData *DeliveryChartData) { - defer wg.Done() - err := handleSingleChart(cData, repoInfo, dir) - if err != nil { - logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) - appendError(err) - } else { - err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(versionObj.Version, &commonmodels.DeliveryChart{ - Name: cData.ServiceObj.ServiceName, - Version: cData.ChartData.Version, - Repo: args.ChartRepoName, - }) - if err != nil { - appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) - } - } - }(chartData) - } - wg.Wait() +func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap.SugaredLogger) error { + deliveryVersion, err := commonrepo.NewDeliveryVersionColl().Get(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }) + if err != nil { + logger.Errorf("failed to query delivery version data, verisonName: %s, error: %s", versionName, err) + return fmt.Errorf("failed to query delivery version data, verisonName: %s", versionName) + } - if errorList.ErrorOrNil() != nil { - err = errorList.ErrorOrNil() - return - } + if deliveryVersion.Status != setting.DeliveryVersionStatusFailed { + return fmt.Errorf("can't reCreate version with status:%s", deliveryVersion.Status) + } + + argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) + if err != nil { + return errors.Wrapf(err, "failed to marshal arguments, versionName: %s err %s", deliveryVersion.Version) + } + createArgs := new(DeliveryVersionChartData) + err = json.Unmarshal(argsBytes, createArgs) + if err != nil { + return errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err %s", deliveryVersion.Version) + } + + productInfoSnap := deliveryVersion.ProductEnvInfo - tmpDir, err := os.MkdirTemp("", "delivery-") + // for charts has been successfully handled, download charts directly + successCharts := sets.NewString() + for _, singleChart := range deliveryVersion.Charts { + // prepare chart data + _, err := downloadChart(projectName, versionName, singleChart) if err != nil { - logger.Errorf("failed to create temp dir, err: %s", err) - return - } - defer func(path string) { - _ = os.RemoveAll(path) - }(tmpDir) - - //tar all chart files and send to s3 store - fsTree := os.DirFS(dir) - tarball := fmt.Sprintf("%s.tar.gz", versionObj.Version) - localPath := filepath.Join(tmpDir, tarball) - if err = fsutil.Tar(fsTree, localPath); err != nil { - logger.Errorf("failed to archive tarball %s, err: %s", localPath, err) - versionObj.Status = setting.DeliveryVersionStatusFailed - err = errors.Wrapf(err, "failed to archive chart files, path %s", localPath) - return + log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", singleChart.Name, err) + continue } + successCharts.Insert(singleChart.Name) + } - ServiceS3Base := configbase.ObjectStorageDeliveryVersionPath(args.ProductName) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, versionObj.Version, ServiceS3Base, nil, logger); err != nil { - logger.Errorf("failed to upload chart package files for project %s, err: %s", args.ProductName, err) - err = errors.Wrapf(err, "failed to upload package file") - return + chartsToBeHandled := make([]*CreateHelmDeliveryVersionChartData, 0) + for _, chartConfig := range createArgs.ChartDatas { + if successCharts.Has(chartConfig.ServiceName) { + continue } + chartsToBeHandled = append(chartsToBeHandled, chartConfig) + } - }() + chartDataMap, err := prepareChartData(chartsToBeHandled, productInfoSnap) + if err != nil { + return e.ErrCreateDeliveryVersion.AddErr(err) + } + + err = buildDeliveryCharts(chartDataMap, deliveryVersion, createArgs, logger) + if err != nil { + return err + } + + // update status + deliveryVersion.Status = setting.DeliveryVersionStatusRetrying + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, "") + if err != nil { + logger.Errorf("failed to update delivery status, name: %s, err: %s", deliveryVersion.Version, err) + return fmt.Errorf("failed to update delivery status, name: %s", deliveryVersion.Version) + } return nil } @@ -438,9 +646,9 @@ func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]str return serviceNames.UnsortedList(), nil } -func downloadChart(version *commonmodels.DeliveryVersion, chartInfo *commonmodels.DeliveryChart) (string, error) { +func downloadChart(productName, versionName string, chartInfo *commonmodels.DeliveryChart) (string, error) { chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.Name, chartInfo.Version) - chartTGZFileParent := getChartTGZDir(version.ProductName, version.Version) + chartTGZFileParent := getChartTGZDir(productName, versionName) chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) if _, err := os.Stat(chartTGZFilePath); err == nil { // local cache exists @@ -464,7 +672,7 @@ func downloadChart(version *commonmodels.DeliveryVersion, chartInfo *commonmodel out, err := os.Create(chartTGZFilePath) if err != nil { - os.RemoveAll(chartTGZFilePath) + _ = os.RemoveAll(chartTGZFilePath) return "", errors.Wrapf(err, "failed to create chart tgz file") } @@ -473,14 +681,12 @@ func downloadChart(version *commonmodels.DeliveryVersion, chartInfo *commonmodel return "", errors.Wrapf(err, "failed to download file") } - //helm.CreateChartPackage() - if response.StatusCode != 200 { return "", errors.Wrapf(err, "download file failed %s", chartTGZName) } + defer func() { _ = response.Body.Close() }() b, err := ioutil.ReadAll(response.Body) - defer response.Body.Close() if err != nil { return "", errors.Wrapf(err, "failed to read response data") } @@ -496,13 +702,13 @@ func downloadChart(version *commonmodels.DeliveryVersion, chartInfo *commonmodel return chartTGZFilePath, nil } -func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (string, error) { +func getChartConfigInfo(projectName, version string, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryChart, error) { deliveryVersion, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ ProductName: projectName, Version: version, }, log) if err != nil { - return "", err + return nil, err } var chartInfo *commonmodels.DeliveryChart @@ -513,39 +719,57 @@ func DownloadDeliveryChart(projectName, version string, chartName string, log *z } if chartInfo == nil { - return "", fmt.Errorf("can't find target chart: %s", chartName) + return nil, fmt.Errorf("can't find target chart: %s", chartName) } + return chartInfo, nil +} +func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (string, error) { + chartInfo, err := getChartConfigInfo(projectName, version, chartName, log) + if err != nil { + return "", err + } // prepare chart data - filePath, err := downloadChart(deliveryVersion, chartInfo) + filePath, err := downloadChart(projectName, version, chartInfo) if err != nil { return "", err } - return filePath, err } -func PreviewDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (*DeliveryChartResp, error) { - filePath, err := DownloadDeliveryChart(projectName, version, chartName, log) +func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { + + chartInfo, err := getChartConfigInfo(projectName, versionName, chartName, log) if err != nil { - return nil, err + return "", err } - fileName := filepath.Base(filePath) - dstDir := getChartExpandDir(projectName, version) + dstDir := getChartExpandDir(projectName, versionName) + dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartInfo.Name, chartInfo.Version)) - fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName)) - dstDir = filepath.Join(dstDir, fileName) + filePath, err := DownloadDeliveryChart(projectName, versionName, chartName, log) + if err != nil { + return "", err + } file, err := os.Open(filePath) if err != nil { - return nil, errors.Wrap(err, "unable to open tarball") + return "", errors.Wrap(err, "unable to open tarball") } - defer file.Close() + defer func() { _ = file.Close() }() err = chartutil.Expand(dstDir, file) if err != nil { log.Errorf("failed to uncompress file: %s", filePath) - return nil, errors.Wrapf(err, "failed to uncompress file") + return "", errors.Wrapf(err, "failed to uncompress file") + } + return dstDir, nil +} + +func PreviewDeliveryChart(projectName, version, chartName string, log *zap.SugaredLogger) (*DeliveryChartResp, error) { + + dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) + if err != nil { + return nil, err } ret := &DeliveryChartResp{ @@ -577,3 +801,60 @@ func PreviewDeliveryChart(projectName, version string, chartName string, log *za ret.FileInfos = fis return ret, nil } + +// load chart file infos +func loadChartFileInfos(fileDir, chartName string, dir string) ([]*types.FileInfo, error) { + var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(fileDir, chartName, dir)) + if err != nil { + return nil, e.ErrFilePath.AddDesc(err.Error()) + } + + for _, file := range files { + info, _ := file.Info() + if info == nil { + continue + } + fi := &types.FileInfo{ + Parent: dir, + Name: file.Name(), + Size: info.Size(), + Mode: file.Type(), + ModTime: info.ModTime().Unix(), + IsDir: file.IsDir(), + } + fis = append(fis, fi) + } + return fis, nil +} + +func GetDeliveryChartFilePath(args *DeliveryChartFilePathArgs, log *zap.SugaredLogger) ([]*types.FileInfo, error) { + projectName, version, chartName := args.ProjectName, args.Version, args.ChartName + dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) + if err != nil { + return nil, nil + } + + fileInfos, err := loadChartFileInfos(dstDir, chartName, args.Dir) + if err != nil { + return nil, err + } + return fileInfos, nil +} + +func GetDeliveryChartFileContent(args *DeliveryChartFileContentArgs, log *zap.SugaredLogger) (string, error) { + projectName, version, chartName := args.ProjectName, args.Version, args.ChartName + dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) + if err != nil { + return "", nil + } + + file := filepath.Join(dstDir, chartName, args.FilePath, args.FileName) + fileContent, err := os.ReadFile(file) + if err != nil { + log.Errorf("Failed to read file %s, err: %s", file, err) + return "", e.ErrFileContent.AddDesc(err.Error()) + } + + return string(fileContent), nil +} diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index b4087f30..98503c44 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -180,7 +180,7 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog if err != nil { return nil, e.ErrGetHelmCharts.AddErr(err) } - renderSet, err := FindHelmRenderSet(productName, envName, log) + renderSet, err := FindHelmRenderSet(productName, prod.Render.Name, log) if err != nil { log.Errorf("[%s][P:%s] find product renderset error: %v", envName, productName, err) return nil, e.ErrGetHelmCharts.AddErr(err) @@ -193,6 +193,7 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog allServiceMap := prod.GetServiceMap() serviceMap := make(map[string]*models.ProductService) + //validate data, make sure service and chart info exists if len(serviceName) > 0 { serviceList := strings.Split(serviceName, ",") diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 4c9e1fb9..196d63db 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -41,12 +41,13 @@ type ProjectListOptions struct { type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation - Alias string `json:"alias"` - Desc string `json:"desc"` - UpdatedAt int64 `json:"updatedAt"` - UpdatedBy string `json:"updatedBy"` - Onboard bool `json:"onboard"` - Public bool `json:"public"` + Alias string `json:"alias"` + Desc string `json:"desc"` + UpdatedAt int64 `json:"updatedAt"` + UpdatedBy string `json:"updatedBy"` + Onboard bool `json:"onboard"` + Public bool `json:"public"` + DeployType string `json:"deployType"` } type ProjectBriefRepresentation struct { @@ -98,12 +99,13 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, Envs: nameWithEnvMap[name], }, - Alias: info.Alias, - Desc: info.Desc, - UpdatedAt: info.UpdatedAt, - UpdatedBy: info.UpdatedBy, - Onboard: info.OnboardStatus != 0, - Public: info.Public, + Alias: info.Alias, + Desc: info.Desc, + UpdatedAt: info.UpdatedAt, + UpdatedBy: info.UpdatedBy, + Onboard: info.OnboardStatus != 0, + Public: info.Public, + DeployType: info.DeployType, }) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index dd37e701..af9b8b8c 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -331,6 +331,14 @@ const ( DeliveryVersionStatusSuccess = "success" DeliveryVersionStatusFailed = "failed" DeliveryVersionStatusCreating = "creating" + DeliveryVersionStatusRetrying = "retrying" +) + +const ( + DeliveryVersionPackageStatusSuccess = "success" + DeliveryVersionPackageStatusFailed = "failed" + DeliveryVersionPackageStatusWaiting = "waiting" + DeliveryVersionPackageStatusUploading = "uploading" ) const ( -- Gitee From e707526e7e44fbbe0d9111993ec82ec450aecb3d Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 23 Nov 2021 12:24:38 +0800 Subject: [PATCH 0763/2647] remove useless code Signed-off-by: allenshen --- .../aslan/core/common/service/fs/file.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index cb01720d..bb24417f 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -26,18 +26,6 @@ import ( fsutil "github.com/koderover/zadig/pkg/util/fs" ) -type fileHook interface { - Execute() error -} - -type LocalFileCopy struct { - ExtraFiles []string -} - -func (hook *LocalFileCopy) Execute() error { - return nil -} - // PreloadFiles downloads a tarball from object storage and extracts it to a local path for further usage. // It happens only if files do not exist in local disk. func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) error { -- Gitee From 3307321befc555f28cf63e4384758ceb419b96ad Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 23 Nov 2021 13:45:44 +0800 Subject: [PATCH 0764/2647] remove test interface Signed-off-by: liu deyi --- .../aslan/core/workflow/handler/router.go | 1 - .../aslan/core/workflow/handler/workflow_v3_task.go | 12 ------------ 2 files changed, 13 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 6ad02de2..02aee1b6 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -158,7 +158,6 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) - taskV3.POST("/workflow/webhookPayload", OutputWebhookPayload) taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) } } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index 712e177c..323626bd 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -119,18 +119,6 @@ type KeyVal struct { IsCredential bool `bson:"is_credential" json:"is_credential"` } -func OutputWebhookPayload(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(WebhookPayload) - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - return -} - func GetWorkflowTaskV3Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() -- Gitee From a2949290884f1845bae3c7ca22c0b9d4083b5cf3 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 19 Nov 2021 11:29:56 +0800 Subject: [PATCH 0765/2647] fix thirdparty network error Signed-off-by: panxunying <641770806@qq.com> --- pkg/microservice/user/core/handler/login/third_party.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index c4b555a7..cedec6a7 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -7,12 +7,14 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/coreos/go-oidc/v3/oidc" "github.com/gin-gonic/gin" "golang.org/x/oauth2" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/login" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" @@ -39,6 +41,7 @@ func Login(c *gin.Context) { RedirectURL: config.RedirectURI(), } authCodeURL := oauth2Config.AuthCodeURL(config.AppState, oauth2.AccessTypeOffline) + authCodeURL = strings.Replace(authCodeURL, config.IssuerURL(), configbase.SystemAddress()+"/dex", -1) c.Redirect(http.StatusSeeOther, authCodeURL) } -- Gitee From 5ba2d40597e0361c3f29174642efe6c1c6cecc48 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 23 Nov 2021 15:12:44 +0800 Subject: [PATCH 0766/2647] add attribute check in rego Signed-off-by: lou --- .../core/service/bundle/rego/authz.rego | 69 +++++++++-- .../bundle/rego/test_data/bindings/data.json | 29 +---- .../rego/test_data/exemptions/data.json | 14 +-- .../bundle/rego/test_data/resources/data.json | 9 ++ .../bundle/rego/test_data/roles/data.json | 110 +++--------------- .../core/service/bundle/rego/test_input.json | 9 +- 6 files changed, 96 insertions(+), 144 deletions(-) create mode 100644 pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 45fd16e0..8072fa2a 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -66,11 +66,51 @@ access_is_granted { access_is_granted { not url_is_privileged - some grant - user_is_granted[grant] + some rule - grant.method == http_request.method - glob.match(trim(grant.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + allowed_plain_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) +} + +access_is_granted { + some rule + + allowed_attributive_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + all_attributes_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) +} + +all_attributes_match(attributes, resourceType, resourceID) { + res := data.resources[_] + res.resourceType == resourceType + res.resourceID == resourceID + res.projectName + res.projectName == project_name + + # a && b <=> !(!a || !b) + not attributes_mismatch(attributes, res) +} + +attributes_mismatch(attributes, res) { + attribute := attributes[_] + attribute_mismatch(attribute, res) +} + +attribute_mismatch(attribute, res) { + res[attribute.key] != attribute.value +} + +attribute_mismatch(attribute, res) { + not res[attribute.key] +} + +get_resource_id(idRegex) = id { + output := regex.find_all_string_submatch_n(trim(idRegex, "/"), concat("/", input.parsed_path), -1) + count(output) == 1 + count(output[0]) == 2 + id := output[0][1] } user_is_admin { @@ -159,7 +199,6 @@ user_visible_projects[project] { user_is_admin } - all_roles[role_ref] { some i data.bindings.role_bindings[i].uid == claims.uid @@ -182,14 +221,30 @@ allowed_roles[role_ref] { role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } -user_is_granted[grant] { +allowed_rules[rule] { some role_ref allowed_roles[role_ref] some i data.roles.roles[i].name == role_ref.name data.roles.roles[i].namespace == role_ref.namespace - grant := data.roles.roles[i].rules[_] + rule := data.roles.roles[i].rules[_] +} + +allowed_plain_rules[rule] { + rule := allowed_rules[_] + not rule.matchAttributes + not rule.matchExpressions +} + +allowed_attributive_rules[rule] { + rule := allowed_rules[_] + rule.matchAttributes +} + +allowed_attributive_rules[rule] { + rule := allowed_rules[_] + rule.matchExpressions } claims := payload { diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json index 9344d9cb..52782d07 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/bindings/data.json @@ -4,11 +4,11 @@ "uid": "2e6c5200-358f-11ec-981f-de2269351a1e", "bindings": [ { - "namespace": "project1", + "namespace": "test", "role_refs": [ { - "name": "reader1", - "namespace": "project1" + "name": "test", + "namespace": "test" }, { "name": "reader2", @@ -53,29 +53,6 @@ ] } ] - }, - { - "uid": "bob", - "bindings": [ - { - "namespace": "project4", - "role_refs": [ - { - "name": "view", - "namespace": "" - } - ] - }, - { - "namespace": "project5", - "role_refs": [ - { - "name": "view", - "namespace": "" - } - ] - } - ] } ] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json index 271edede..a0904254 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json @@ -1,16 +1,4 @@ { - "global": [ - { - "method": "GET", - "endpoint": "/version" - } - ], - "namespaced": [ - { - "method": "GET", - "endpoint": "/version1" - } - ], "public": [ { "method": "POST", @@ -20,7 +8,7 @@ "registered": [ { "method": "GET", - "endpoint": "/api/articles" + "endpoint": "/api/aslan/environment/environments/?*" } ] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json new file mode 100644 index 00000000..c7a3dbe9 --- /dev/null +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json @@ -0,0 +1,9 @@ +[ + { + "resourceType": "Environment", + "projectName": "test", + "resourceID": "test", + "production": "true", + "production1": "true1" + } +] diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json index d86936b5..82c4ffe0 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json @@ -1,104 +1,24 @@ { "roles": [ { - "name": "author", - "namespace": "project2", + "name": "test", + "namespace": "test", "rules": [ { "method": "GET", - "endpoint": "/api/authors" - }, - { - "method": "POST", - "endpoint": "/api/authors" - }, - { - "method": "PUT", - "endpoint": "/api/authors" - }, - { - "method": "PATCH", - "endpoint": "/api/authors" - }, - { - "method": "DELETE", - "endpoint": "/api/authors" - } - ] - }, - { - "name": "reader", - "namespace": "project2", - "rules": [ - { - "method": "GET", - "endpoint": "/api/authors" - }, - { - "method": "GET", - "endpoint": "/api/articles" - }, - { - "method": "POST", - "endpoint": "/api/authors" - }, - { - "method": "POST", - "endpoint": "/api/articles" - } - ] - }, - { - "name": "reader1", - "namespace": "project1", - "rules": [ - { - "method": "GET", - "endpoint": "/api/authors" - }, - { - "method": "GET", - "endpoint": "/api/articles" - }, - { - "method": "POST", - "endpoint": "/api/authors" - }, - { - "method": "POST", - "endpoint": "/api/articles" - } - ] - }, - { - "name": "view", - "namespace": "project3", - "rules": [ - { - "method": "GET", - "endpoint": "/api/articles" - } - ] - }, - { - "name": "reader2", - "namespace": "project1", - "rules": [ - { - "method": "GET", - "endpoint": "/api/authors" - }, - { - "method": "GET", - "endpoint": "/api/articles" - }, - { - "method": "POST", - "endpoint": "/api/authors" - }, - { - "method": "POST", - "endpoint": "/api/articles" + "endpoint": "/api/aslan/environment/environments/?*", + "resourceType": "Environment", + "idRegex": "/api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "production", + "value": "true" + }, + { + "key": "production1", + "value": "true1" + } + ] } ] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_input.json b/pkg/microservice/policy/core/service/bundle/rego/test_input.json index 9647fc86..058dc6f2 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_input.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_input.json @@ -19,7 +19,7 @@ "accept": "*/*", "accept-encoding": "gzip, deflate", "accept-language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", - "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW4xIiwiZW1haWwiOiJhZG1pbjFAa29kZXJvdmVyLmNvbSIsInVpZCI6IjJlNmM1MjAwLTM1OGYtMTFlYy05ODFmLWRlMjI2OTM1MWExZSIsImZlZGVyYXRlZF9jbGFpbXMiOnsiY29ubmVjdG9yX2lkIjoiIiwidXNlcl9pZCI6IiJ9LCJhdWQiOiJ6YWRpZyIsImV4cCI6MTYzNTI1MTQxOH0.7GAndkOICyk7QucBaUiGmd7ZJstWJfoNDw5JKZjz4TY", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibG91dGVzdCIsImVtYWlsIjoibG91dGVzdEBleGFtcGxlLmNvbSIsInVpZCI6IjJlNmM1MjAwLTM1OGYtMTFlYy05ODFmLWRlMjI2OTM1MWExZSIsInByZWZlcnJlZF91c2VybmFtZSI6ImxvdXRlc3QiLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6InN5c3RlbSIsInVzZXJfaWQiOiJsb3V0ZXN0In0sImF1ZCI6InphZGlnIiwiZXhwIjoxNzI0MDQ3NTMyfQ.H05PvCXmvPX4gpgMjfHDNUlS1s_EpYrqcFG61SYUJB8", "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:92.0) Gecko/20100101 Firefox/92.0", "x-forwarded-proto": "http", "x-request-id": "14404686-7a28-48a6-9be6-bdd6e119f81a" @@ -45,11 +45,14 @@ "parsed_body": null, "parsed_path": [ "api", - "articles" + "aslan", + "environment", + "environments", + "test" ], "parsed_query": { "projectName": [ - "project1" + "test" ] }, "truncated_body": false, -- Gitee From 57aacfc685a47c2e745556c5f0ab7cf03553bcde Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 16 Nov 2021 17:01:24 +0800 Subject: [PATCH 0767/2647] xx Signed-off-by: mouuii service Signed-off-by: mouuii add comment Signed-off-by: mouuii --- .../core/codehost/repository/models/codehost.go | 10 +++++----- pkg/shared/client/systemconfig/codehost.go | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index bbbed219..730ff415 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -4,15 +4,15 @@ type CodeHost struct { ID int `bson:"id" json:"id"` Type string `bson:"type" json:"type"` Address string `bson:"address" json:"address"` - IsReady string `bson:"is_ready" json:"ready"` - AccessToken string `bson:"access_token" json:"accessToken"` - RefreshToken string `bson:"refresh_token" json:"refreshToken"` + IsReady string `bson:"is_ready" json:"is_ready"` + AccessToken string `bson:"access_token" json:"access_token"` + RefreshToken string `bson:"refresh_token" json:"refresh_token"` Namespace string `bson:"namespace" json:"namespace"` - ApplicationId string `bson:"application_id" json:"applicationId"` + ApplicationId string `bson:"application_id" json:"application_id"` Region string `bson:"region" json:"region,omitempty"` Username string `bson:"username" json:"username,omitempty"` Password string `bson:"password" json:"password,omitempty"` - ClientSecret string `bson:"client_secret" json:"clientSecret"` + ClientSecret string `bson:"client_secret" json:"client_secret"` CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 733fcf0b..39104606 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -17,13 +17,14 @@ type CodeHost struct { ID int `json:"id"` Address string `json:"address"` Type string `json:"type"` - AccessToken string `json:"accessToken"` + AccessToken string `json:"access_token"` Namespace string `json:"namespace"` Region string `json:"region"` - AccessKey string `json:"applicationId"` - SecretKey string `json:"clientSecret"` - Username string `json:"username"` - Password string `json:"password"` + // the field and tag not consistent because of db field + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + Username string `json:"username"` + Password string `json:"password"` } type Option struct { -- Gitee From 7e5d3c95db95b0b8da299b8ddc8769bbb2146bd4 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 23 Nov 2021 16:15:48 +0800 Subject: [PATCH 0768/2647] add apply global variable api Signed-off-by: allenshen --- .../aslan/core/delivery/handler/router.go | 11 +- .../aslan/core/delivery/handler/version.go | 14 ++- .../aslan/core/delivery/service/version.go | 111 ++++++------------ 3 files changed, 56 insertions(+), 80 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 69b26bb4..7e9b1f48 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -46,13 +46,12 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.GET("", ListDeliveryVersion) deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) - deliveryRelease.GET("/charts", DownloadDeliveryChart) - deliveryRelease.GET("/charts/preview", PreviewGetDeliveryChart) - deliveryRelease.GET("/charts/filePath", GetDeliveryChartFilePath) - deliveryRelease.GET("/charts/fileContent", GetDeliveryChartFileContent) - deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) - + deliveryRelease.POST("/helm/global-variables", ApplyDeliveryGlobalVariables) + deliveryRelease.GET("/helm/charts", DownloadDeliveryChart) + deliveryRelease.GET("/helm/charts/preview", PreviewGetDeliveryChart) + deliveryRelease.GET("/helm/charts/filePath", GetDeliveryChartFilePath) + deliveryRelease.GET("/helm/charts/fileContent", GetDeliveryChartFileContent) } deliveryPackage := router.Group("packages") diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index b750ab62..1d11d635 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -438,6 +438,18 @@ func GetDeliveryChartFileContent(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFileContent(args, ctx.Logger) } + +func ApplyDeliveryGlobalVariables(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.DeliveryVariablesApplyArgs) + err := c.BindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = deliveryservice.ApplyDeliveryGlobalVariables(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 88eca22f..c764572b 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -26,6 +26,8 @@ import ( "sync" "time" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" + cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" "github.com/hashicorp/go-multierror" @@ -57,7 +59,7 @@ type CreateHelmDeliveryVersionOption struct { type CreateHelmDeliveryVersionChartData struct { ServiceName string `json:"serviceName"` - Version string `json:"version"` + Version string `json:"version,omitempty"` ValuesYamlContent string `json:"valuesYamlContent"` } @@ -74,6 +76,7 @@ type CreateHelmDeliveryVersionArgs struct { } type DeliveryVersionChartData struct { + GlobalVariables string `json:"globalVariables"` ChartRepoName string `json:"chartRepoName"` ImageRegistryID string `json:"imageRegistryID"` ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` @@ -104,6 +107,11 @@ type DeliveryChartFileContentArgs struct { Version string `json:"version"` } +type DeliveryVariablesApplyArgs struct { + GlobalVariables string `json:"globalVariables,omitempty"` + ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { resp, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { @@ -217,7 +225,7 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { return client, nil } -func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string) error { +func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string) error { serviceObj := chartData.ServiceObj serviceName, revision := serviceObj.ServiceName, serviceObj.Revision base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) @@ -252,13 +260,23 @@ func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.Hel chartRequested.Metadata.Name = chartData.ChartData.ServiceName chartRequested.Metadata.Version = chartData.ChartData.Version chartRequested.Metadata.AppVersion = chartData.ChartData.Version - if len(chartData.ChartData.ValuesYamlContent) > 0 { + + if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly valuesInfo := make(map[string]interface{}) if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) } chartRequested.Values = valuesInfo + } else if len(globalVariables) > 0 { // merge global variables + curValuesStr, err := yaml.Marshal(chartRequested.Values) + if err != nil { + return errors.Wrapf(err, "failed to marshal values.yaml for service: %s", serviceObj.ServiceName) + } + chartRequested.Values, err = yamlutil.MergeAndUnmarshal([][]byte{curValuesStr, []byte(globalVariables)}) + if err != nil { + return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) + } } chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) @@ -286,6 +304,7 @@ func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.Hel func handlePushResponse(resp *http.Response) error { if resp.StatusCode != 201 && resp.StatusCode != 202 { b, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() if err != nil { return err } @@ -396,7 +415,7 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - err := handleSingleChart(cData, repoInfo, dir) + err := handleSingleChart(cData, repoInfo, dir, args.GlobalVariables) if err != nil { logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) @@ -480,75 +499,6 @@ func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *z return err } - //go func() { - // var err error - // defer func() { - // if err != nil { - // versionObj.Status = setting.DeliveryVersionStatusFailed - // versionObj.Error = err.Error() - // } else { - // versionObj.Status = setting.DeliveryVersionStatusSuccess - // versionObj.Error = "" - // } - // err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionObj.Version, versionObj.Status, versionObj.Error) - // if err != nil { - // logger.Errorf("failed to update delivery version data, name: %s error: %s", versionObj.Version, err) - // } - // }() - // - // var errLock sync.Mutex - // errorList := &multierror.Error{} - // - // appendError := func(err error) { - // errLock.Lock() - // defer errLock.Unlock() - // errorList = multierror.Append(errorList, err) - // } - // - // // push charts to repo - // wg := sync.WaitGroup{} - // for _, chartData := range chartDataMap { - // wg.Add(1) - // go func(cData *DeliveryChartData) { - // defer wg.Done() - // err := handleSingleChart(cData, repoInfo, dir) - // if err != nil { - // logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) - // appendError(err) - // } else { - // err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(versionObj.Version, &commonmodels.DeliveryChart{ - // Name: cData.ChartData.ServiceName, - // Version: cData.ChartData.Version, - // Repo: args.ChartRepoName, - // }) - // if err != nil { - // appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) - // } - // } - // }(chartData) - // } - // wg.Wait() - // - // if errorList.ErrorOrNil() != nil { - // err = errorList.ErrorOrNil() - // return - // } - // - // // no need to upload chart packages - // if args.Options == nil || !args.Options.EnableOfflineDist { - // return - // } - // - // //tar all chart files and send to s3 store - // fsTree := os.DirFS(dir) - // ServiceS3Base := configbase.ObjectStorageDeliveryVersionPath(args.ProductName) - // if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, versionObj.Version, ServiceS3Base, nil, args.Options.S3StorageID, logger); err != nil { - // logger.Errorf("failed to upload chart package files for project %s, err: %s", args.ProductName, err) - // err = errors.Wrapf(err, "failed to upload package file") - // return - // } - //}() - return nil } @@ -858,3 +808,18 @@ func GetDeliveryChartFileContent(args *DeliveryChartFileContentArgs, log *zap.Su return string(fileContent), nil } + +func ApplyDeliveryGlobalVariables(args *DeliveryVariablesApplyArgs, log *zap.SugaredLogger) (interface{}, error) { + ret := new(DeliveryVariablesApplyArgs) + for _, chartData := range args.ChartDatas { + mergedYaml, err := yamlutil.Merge([][]byte{[]byte(chartData.ValuesYamlContent), []byte(args.GlobalVariables)}) + if err != nil { + return nil, errors.Wrapf(err, "failed to merge global variable for service: %s", chartData.ServiceName) + } + ret.ChartDatas = append(ret.ChartDatas, &CreateHelmDeliveryVersionChartData{ + ServiceName: chartData.ServiceName, + ValuesYamlContent: string(mergedYaml), + }) + } + return ret, nil +} -- Gitee From fe42c750d83bc7980e7a0cbdea5d87011a2e1f1c Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 23 Nov 2021 16:25:47 +0800 Subject: [PATCH 0769/2647] bug Signed-off-by: mouuii --- .../systemconfig/core/features/repository/models/feature.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/features/repository/models/feature.go b/pkg/microservice/systemconfig/core/features/repository/models/feature.go index 35c9936a..a0530ad5 100644 --- a/pkg/microservice/systemconfig/core/features/repository/models/feature.go +++ b/pkg/microservice/systemconfig/core/features/repository/models/feature.go @@ -5,6 +5,6 @@ type Feature struct { Enabled bool `bson:"enabled" json:"enabled"` } -func (f *Feature) TableName() string { +func (Feature) TableName() string { return "feature" } -- Gitee From b0c81c0b6fe4d5e7a1cae78784294e7f43139083 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 23 Nov 2021 18:22:17 +0800 Subject: [PATCH 0770/2647] resolve comment Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/fs/file.go | 4 ++-- pkg/microservice/aslan/core/common/service/fs/s3.go | 11 ++++++++++- pkg/microservice/aslan/core/common/service/helm.go | 3 ++- pkg/microservice/aslan/core/service/service/helm.go | 6 +++--- .../aslan/core/templatestore/service/chart.go | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index bb24417f..e6aaac52 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -47,7 +47,7 @@ func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) err } // SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. -func SaveAndUploadFiles(fileTree fs.FS, name, localBase string, s3Base string, s3Copies []string, logger *zap.SugaredLogger) error { +func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase string, s3Base string, logger *zap.SugaredLogger) error { var wg wait.Group var err error @@ -59,7 +59,7 @@ func SaveAndUploadFiles(fileTree fs.FS, name, localBase string, s3Base string, s } }) wg.Start(func() { - err2 := ArchiveAndUploadFilesToS3(fileTree, name, s3Base, s3Copies, logger) + err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) if err2 != nil { logger.Errorf("Failed to upload files to s3, err: %s", err2) err = err2 diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 0680a4c2..8a06750a 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -30,7 +30,13 @@ import ( fsutil "github.com/koderover/zadig/pkg/util/fs" ) -func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, copies []string, logger *zap.SugaredLogger) error { +// ArchiveAndUploadFilesToS3 archive local files and upload to default s3 storage +// if multiple names appointed, s3storage.copy will be used to handle extra names +func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, logger *zap.SugaredLogger) error { + if len(names) == 0 { + return fmt.Errorf("names not appointed") + } + tmpDir, err := os.MkdirTemp("", "") if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) @@ -38,6 +44,9 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, name, s3Base string, copies []str } defer os.RemoveAll(tmpDir) + name := names[0] + copies := names[1:] + tarball := fmt.Sprintf("%s.tar.gz", name) localPath := filepath.Join(tmpDir, tarball) if err = fsutil.Tar(fileTree, localPath); err != nil { diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index bc1dfcf0..f8ddc6c8 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -83,7 +83,8 @@ func DownloadServiceManifests(base, projectName, serviceName string) error { func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS) error { localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) - return fsservice.SaveAndUploadFiles(fileTree, serviceName, localBase, s3Base, copies, log.SugaredLogger()) + names := append([]string{serviceName}, copies...) + return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index cc91441b..00eb7422 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -351,7 +351,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe fsTree := os.DirFS(config.LocalServicePath(projectName, args.Name)) ServiceS3Base := config.ObjectStorageServicePath(projectName, args.Name) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, args.Name, ServiceS3Base, []string{fmt.Sprintf("%s-%d", args.Name, rev)}, logger); err != nil { + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{args.Name, fmt.Sprintf("%s-%d", args.Name, rev)}, ServiceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", args.Name, projectName, err) return nil, err } @@ -677,7 +677,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi fsTree := os.DirFS(config.LocalServicePath(projectName, serviceName)) ServiceS3Base := config.ObjectStorageServicePath(projectName, serviceName) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, serviceName, ServiceS3Base, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, logger); err != nil { + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{serviceName, fmt.Sprintf("%s-%d", serviceName, rev)}, ServiceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", serviceName, projectName, err) return nil, err } @@ -1004,7 +1004,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { for serviceName, rev := range serviceMap { s3Base := config.ObjectStorageServicePath(args.ProductName, serviceName) - if err := fsservice.ArchiveAndUploadFilesToS3(os.DirFS(config.LocalServicePath(args.ProductName, serviceName)), serviceName, s3Base, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, log); err != nil { + if err := fsservice.ArchiveAndUploadFilesToS3(os.DirFS(config.LocalServicePath(args.ProductName, serviceName)), []string{serviceName, fmt.Sprintf("%s-%d", serviceName, rev)}, s3Base, log); err != nil { return e.ErrUpdateTemplate.AddDesc(err.Error()) } } diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index c0d86270..4ea91382 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -352,7 +352,7 @@ func processChartFromSource(name string, args *fs.DownloadFromSourceArgs, logger localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) - err1 := fs.SaveAndUploadFiles(tree, name, localBase, s3Base, nil, logger) + err1 := fs.SaveAndUploadFiles(tree, []string{name}, localBase, s3Base, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) err = err1 -- Gitee From 2b4e9d53b47e7ab104c12e68f9bcf94c208a41d4 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 23 Nov 2021 21:02:59 +0800 Subject: [PATCH 0771/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/common/service/fs/file.go | 2 +- .../aslan/core/common/service/fs/s3.go | 15 ++-- .../aslan/core/service/service/helm.go | 68 ++++++++++++++++--- .../aslan/core/service/service/service.go | 2 +- .../aslan/core/templatestore/service/chart.go | 2 +- .../core/service/taskplugin/deploy.go | 39 ++++++----- 6 files changed, 90 insertions(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index e6aaac52..2f3c711a 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -47,7 +47,7 @@ func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) err } // SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. -func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase string, s3Base string, logger *zap.SugaredLogger) error { +func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string, logger *zap.SugaredLogger) error { var wg wait.Group var err error diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 8a06750a..67c5db65 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -73,7 +73,7 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, lo return err } - //copy file to avoid duplicated file transfer + // copy file to avoid duplicated file transfer for _, copyName := range copies { targetPath := filepath.Join(s3Storage.Subfolder, s3Base, fmt.Sprintf("%s.tar.gz", copyName)) err = client.CopyObject(s3Storage.Bucket, s3Path, targetPath) @@ -124,15 +124,20 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S return nil } -func DeleteArchivedFileFromS3(name, s3Base string, logger *zap.SugaredLogger) error { +func DeleteArchivedFileFromS3(names []string, s3Base string, logger *zap.SugaredLogger) error { s3, err := s3service.FindDefaultS3() if err != nil { logger.Errorf("Failed to find default s3, err: %s", err) return err } - tarball := fmt.Sprintf("%s.tar.gz", name) - s3Path := filepath.Join(s3.Subfolder, s3Base, tarball) + s3PathList := make([]string, 0, len(names)) + for _, name := range s3PathList { + tarball := fmt.Sprintf("%s.tar.gz", name) + s3Path := filepath.Join(s3.Subfolder, s3Base, tarball) + s3PathList = append(s3PathList, s3Path) + } + forcedPathStyle := true if s3.Provider == setting.ProviderSourceAli { forcedPathStyle = false @@ -143,5 +148,5 @@ func DeleteArchivedFileFromS3(name, s3Base string, logger *zap.SugaredLogger) er return err } - return client.DeleteObjects(s3.Bucket, []string{s3Path}) + return client.DeleteObjects(s3.Bucket, s3PathList) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 00eb7422..562b3852 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -255,7 +255,7 @@ func getNextServiceRevision(productName, serviceName string) (int64, error) { return rev, err } -//make local chart info copy with revision +// make local chart info copy with revision func copyChartRevision(projectName, serviceName string, revision int64) error { sourceChartPath := config.LocalServicePath(projectName, serviceName) revisionChartLocalPath := config.LocalServicePathWithRevision(projectName, serviceName, revision) @@ -274,6 +274,32 @@ func copyChartRevision(projectName, serviceName string, revision int64) error { return nil } +func clearChartFiles(projectName, serviceName string, revision int64, logger *zap.SugaredLogger) { + clearChartFilesInS3Storage(projectName, serviceName, revision, logger) + clearLocalChartFiles(projectName, serviceName, revision, logger) +} + +// clear chart files in s3 storage +func clearChartFilesInS3Storage(projectName, serviceName string, revision int64, logger *zap.SugaredLogger) { + s3FileNames := []string{serviceName, fmt.Sprintf("%s-%d", serviceName, revision)} + errRemoveFile := fsservice.DeleteArchivedFileFromS3(s3FileNames, config.ObjectStorageServicePath(projectName, serviceName), logger) + if errRemoveFile != nil { + logger.Errorf("Failed to remove files: %v from s3 strorage, err: %s", s3FileNames, errRemoveFile) + } +} + +// clear local chart infos +func clearLocalChartFiles(projectName, serviceName string, revision int64, logger *zap.SugaredLogger) { + latestChartPath := config.LocalServicePath(projectName, serviceName) + revisionChartLocalPath := config.LocalServicePathWithRevision(projectName, serviceName, revision) + for _, path := range []string{latestChartPath, revisionChartLocalPath} { + err := os.RemoveAll(path) + if err != nil { + logger.Errorf("failed to remove local chart data, path: %s, err: %s", path, err) + } + } +} + func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: @@ -339,24 +365,31 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe rev, err := getNextServiceRevision(projectName, args.Name) if err != nil { - log.Errorf("Failed to get next revision for service %s, err: %s", args.Name, err) + logger.Errorf("Failed to get next revision for service %s, err: %s", args.Name, err) return nil, errors.Wrapf(err, "Failed to get service next revision, service %s", args.Name) } err = copyChartRevision(projectName, args.Name, rev) if err != nil { - log.Errorf("Failed to copy file %s, err: %s", args.Name, err) + logger.Errorf("Failed to copy file %s, err: %s", args.Name, err) return nil, errors.Wrapf(err, "Failed to copy chart info, service %s", args.Name) } + // clear files from both s3 and local when error occurred in next stages + defer func() { + if err != nil { + clearChartFiles(projectName, args.Name, rev, logger) + } + }() + fsTree := os.DirFS(config.LocalServicePath(projectName, args.Name)) - ServiceS3Base := config.ObjectStorageServicePath(projectName, args.Name) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{args.Name, fmt.Sprintf("%s-%d", args.Name, rev)}, ServiceS3Base, logger); err != nil { + serviceS3Base := config.ObjectStorageServicePath(projectName, args.Name) + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{args.Name, fmt.Sprintf("%s-%d", args.Name, rev)}, serviceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", args.Name, projectName, err) return nil, err } - svc, err := createOrUpdateHelmService( + svc, errCreate := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ ChartName: templateChartInfo.ChartName, @@ -375,7 +408,8 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe logger, ) - if err != nil { + if errCreate != nil { + err = errCreate logger.Errorf("Failed to create service %s in project %s, error: %s", args.Name, projectName, err) return nil, err } @@ -484,6 +518,13 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return } + // clear files from both s3 and local when error occurred in next stages + defer func() { + if finalErr != nil { + clearChartFiles(projectName, serviceName, rev, log) + } + }() + // save files to disk and upload them to s3 if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, fsTree); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) @@ -676,8 +717,16 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi } fsTree := os.DirFS(config.LocalServicePath(projectName, serviceName)) - ServiceS3Base := config.ObjectStorageServicePath(projectName, serviceName) - if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{serviceName, fmt.Sprintf("%s-%d", serviceName, rev)}, ServiceS3Base, logger); err != nil { + serviceS3Base := config.ObjectStorageServicePath(projectName, serviceName) + + // clear files from both s3 and local when error occurred in next stages + defer func() { + if err != nil { + clearChartFiles(projectName, serviceName, rev, logger) + } + }() + + if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{serviceName, fmt.Sprintf("%s-%d", serviceName, rev)}, serviceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", serviceName, projectName, err) return nil, err } @@ -999,7 +1048,6 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { log.Errorf("helmService.update serviceName:%s error:%v", helmServiceInfo.ServiceName, err) return e.ErrUpdateTemplate.AddDesc(err.Error()) } - } for serviceName, rev := range serviceMap { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 73c3733f..b664967d 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -916,7 +916,7 @@ func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, } // 把该服务相关的s3的数据从仓库删除 - if err = fs.DeleteArchivedFileFromS3(serviceName, configbase.ObjectStorageServicePath(productName, serviceName), log); err != nil { + if err = fs.DeleteArchivedFileFromS3([]string{serviceName}, configbase.ObjectStorageServicePath(productName, serviceName), log); err != nil { log.Warnf("Failed to delete file %s, err: %s", serviceName, err) } //onBoarding流程时,需要删除预定的renderset中的已经存在的renderchart信息 diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 4ea91382..c8f26471 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -324,7 +324,7 @@ func RemoveChartTemplate(name string, logger *zap.SugaredLogger) error { return err } - if err = fs.DeleteArchivedFileFromS3(name, configbase.ObjectStorageChartTemplatePath(name), logger); err != nil { + if err = fs.DeleteArchivedFileFromS3([]string{name}, configbase.ObjectStorageChartTemplatePath(name), logger); err != nil { logger.Warnf("Failed to delete file %s, err: %s", name, err) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 87de2acc..0799f94c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -19,7 +19,6 @@ package taskplugin import ( "context" "fmt" - "os" "path/filepath" "regexp" "strings" @@ -434,7 +433,13 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } } - //task执行时候 product.service.revision 可能已经更新,需要使用当前环境中的service.revision + if renderChart == nil { + err = errors.Errorf("failed to update container image in %s/%s,chart not found", + p.Task.Namespace, p.Task.ServiceName) + return + } + + // use revision of service currently applied in environment instead of the latest revision path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, serviceRevisionInProduct) if errDownload != nil { @@ -447,7 +452,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * errDownload, "failed to download service %s/%s", p.Task.Namespace, p.Task.ServiceName) - _ = os.Remove(path) return } } @@ -462,12 +466,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } - if renderChart == nil { - err = errors.Errorf("failed to update container image in %s/%s,not find", - p.Task.Namespace, p.Task.ServiceName) - return - } - serviceValuesYaml := renderChart.ValuesYaml // prepare image replace info @@ -607,11 +605,6 @@ func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, pr func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { logger := p.Log - s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) - if err != nil { - return "", err - } - fileName := serviceName if revision > 0 { fileName = fmt.Sprintf("%s-%d", serviceName, revision) @@ -628,19 +621,23 @@ func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI return tarFilePath, nil } + s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) + if err != nil { + return "", err + } + s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, configbase.ObjectStorageServicePath(productName, serviceName)) forcedPathStyle := true if s3Storage.Provider == setting.ProviderSourceAli { forcedPathStyle = false } - s3Client, err1 := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err1 != nil { - p.Log.Errorf("failed to create s3 client, err: %+v", err1) - return "", err1 + s3Client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err != nil { + p.Log.Errorf("failed to create s3 client, err: %s", err) + return "", err } if err = s3Client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { - logger.Errorf("Failed to download file from s3, err: %s", err) - _ = os.Remove(tarFilePath) + logger.Errorf("failed to download file from s3, err: %s", err) return "", err } @@ -651,6 +648,7 @@ func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI if !exists { return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) } + return tarFilePath, nil } @@ -662,6 +660,7 @@ func (p *DeployTaskPlugin) getRenderSet(ctx context.Context, name string, revisi if err != nil { return nil, err } + return rs, nil } -- Gitee From 9d71bfa92aa614f893f9346c1fbb5b53b38c2622 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 15:00:57 +0800 Subject: [PATCH 0772/2647] optimize code Signed-off-by: liu deyi --- .../core/service/archive/file_archive.go | 34 +++++++++++-------- .../reaper/core/service/reaper/reaper.go | 7 ++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 4a3aade5..a1af23b6 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -119,14 +119,14 @@ func (c *WorkspaceAchiever) process(match string) bool { return false } -func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { +func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { + newPath := make([]string, 0) for _, path := range paths { - //replace path path, err := c.renderPathVariable(path) if err != nil { - fmt.Println("err:", err) + return fmt.Errorf("Failed to render variables") } - fmt.Println("path:", path) + newPath = append(newPath, path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) if err != nil { log.Warningf("%s: %v", path, err) @@ -155,12 +155,16 @@ func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { } } } + c.paths = newPath + return nil } func (c *WorkspaceAchiever) enumerate() error { c.files = make(map[string]os.FileInfo) - c.processPaths(c.paths, true) + if err := c.processPaths(c.paths, true); err != nil { + return err + } for _, folder := range c.gitFolders { c.processPaths([]string{filepath.Join(folder, ".git")}, false) @@ -169,14 +173,14 @@ func (c *WorkspaceAchiever) enumerate() error { return nil } -func (c *WorkspaceAchiever) Achieve(target string) error { +func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { if err := c.enumerate(); err != nil { - return err + return []string{}, err } f, err := ioutil.TempFile("", "*cached_files.txt") if err != nil { - return err + return []string{}, err } defer func() { @@ -187,18 +191,18 @@ func (c *WorkspaceAchiever) Achieve(target string) error { for _, path := range c.sortedFiles() { _, err = f.WriteString(path + "\n") if err != nil { - return fmt.Errorf("failed to create cache file list: %s", err) + return []string{}, fmt.Errorf("failed to create cache file list: %s", err) } } if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil { - return err + return []string{}, err } temp, err := ioutil.TempFile("", "*reaper.tar.gz") if err != nil { log.Errorf("failed to create temporary file: %v", err) - return err + return []string{}, err } _ = temp.Close() @@ -211,7 +215,7 @@ func (c *WorkspaceAchiever) Achieve(target string) error { if err := cmd.Run(); err != nil { log.Errorf("failed to compress %v", err) - return err + return []string{}, err } //if err := helper.Move(temp.Name(), target); err != nil { @@ -227,16 +231,16 @@ func (c *WorkspaceAchiever) Achieve(target string) error { s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) if err != nil { log.Errorf("Archive s3 create s3 client error: %+v", err) - return err + return []string{}, err } objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s/%s/%s", c.PipelineName, c.ServiceName, "cache", meta.FileName)) if err = s3client.Upload(store.Bucket, temp.Name(), objectKey); err != nil { log.Errorf("Archive s3 upload err:%v", err) - return err + return []string{}, err } } - return nil + return c.paths, nil } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 34aa24a9..16e190aa 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -84,7 +84,7 @@ func (r *Reaper) GetCacheFile() string { return filepath.Join(r.Ctx.Workspace, "reaper.tar.gz") } -func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) error { +func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) ([]string, error) { fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}, r.getUserEnvs()) // list files matches caches @@ -99,10 +99,11 @@ func (r *Reaper) CompressCache(storageURI string) error { } if len(r.Ctx.Caches) > 0 { log.Infof("custom caches will be cached") - if err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches); err != nil { + caches, err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches) + if err != nil { return err } - log.Infof("succeed to cache %s", r.Ctx.Caches) + log.Infof("succeed to cache %s", caches) } else { log.Infof("workspace will be cached in background") if err := r.cm.Archive(r.ActiveWorkspace, r.GetCacheFile()); err != nil { -- Gitee From 25c241d81fd9a7416a4a3a9e8f334d2b08577143 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 15:13:49 +0800 Subject: [PATCH 0773/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 16e190aa..136d0d39 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -103,7 +103,7 @@ func (r *Reaper) CompressCache(storageURI string) error { if err != nil { return err } - log.Infof("succeed to cache %s", caches) + log.Infof("succeed to cache [%s]", strings.Join(caches, ",")) } else { log.Infof("workspace will be cached in background") if err := r.cm.Archive(r.ActiveWorkspace, r.GetCacheFile()); err != nil { -- Gitee From fd815bf65f31078813bcce6ab77b2fdc2b088269 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 15:27:49 +0800 Subject: [PATCH 0774/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index a1af23b6..fd95a0ef 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -251,5 +251,5 @@ func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { cmd.Env = envs data, err := cmd.Output() - return string(data), err + return strings.Replace(string(data), "\n", "", -1), err } -- Gitee From 5d0b27c3dfec1d463553ae0bc42aec6afb20bfff Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 24 Nov 2021 16:58:21 +0800 Subject: [PATCH 0775/2647] add resource filter fules Signed-off-by: lou --- .../core/service/bundle/rego/authz.rego | 71 +++++++++++++------ .../rego/test_data/exemptions/data.json | 4 ++ .../bundle/rego/test_data/resources/data.json | 31 +++++--- .../bundle/rego/test_data/roles/data.json | 11 +++ .../core/service/bundle/rego/test_input.json | 3 +- 5 files changed, 89 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 8072fa2a..290ea6e2 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -4,12 +4,11 @@ import input.attributes.request.http as http_request # Policy rule definitions in rbac style, which is consumed by OPA server. # you can use it to: -# 1. decide if a request is allowed by querying: rbac.allow -# 2. decide if a request is allowed and get the status code by querying: rbac.response -# 3. get all visible projects for an authenticated user by querying: rbac.user_visible_projects -# 4. get all allowed projects for a certain action(method+endpoint) for an authenticated user by querying: rbac.user_allowed_projects -# 5. check if a user is system admin by querying: rbac.user_is_admin -# 6. check if a user is project admin by querying: rbac.user_is_project_admin +# 1. decide if a request is allowed and get status code and additional headers(if any) by querying: rbac.response +# 2. get all visible projects for an authenticated user by querying: rbac.user_visible_projects +# 3. get all allowed projects for a certain action(method+endpoint) for an authenticated user by querying: rbac.user_allowed_projects +# 4. check if a user is system admin by querying: rbac.user_is_admin +# 5. check if a user is project admin by querying: rbac.user_is_project_admin default response = { "allowed": false, @@ -17,19 +16,28 @@ default response = { } response = r { - not is_authenticated - not url_is_public - r := { - "allowed": false, - "http_status": 401 - } + not is_authenticated + not url_is_public + r := { + "allowed": false, + "http_status": 401 + } } response = r { - allow - r := { - "allowed": true, - } + allow + r := { + "allowed": true + } +} + +# response for resource filtering, all allowed resources IDs will be returned in headers +response = r { + rule_is_matched_for_filtering + r := { + "allowed": true, + "headers": {"Resources": concat(",", user_allowed_resources)} + } } # By default, deny requests. @@ -79,17 +87,40 @@ access_is_granted { allowed_attributive_rules[rule] rule.method == http_request.method glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + all_attributes_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) } +rule_is_matched_for_filtering { + count(user_matched_rule_for_filtering) > 0 +} + +# get all resources which matches the attributes +user_allowed_resources[resourceID] { + some rule + + user_matched_rule_for_filtering[rule] + res := data.resources[rule.resourceType][_] + res.projectName == project_name + not attributes_mismatch(rule.matchAttributes, res) + resourceID := res.resourceID +} + +user_matched_rule_for_filtering[rule] { + some rule + + allowed_attributive_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + not rule.idRegex +} + all_attributes_match(attributes, resourceType, resourceID) { - res := data.resources[_] - res.resourceType == resourceType + res := data.resources[resourceType][_] res.resourceID == resourceID - res.projectName res.projectName == project_name - # a && b <=> !(!a || !b) + # a && b <=> !(!a || !b), De Morgan’s laws, see details in https://www.fugue.co/blog/5-tips-for-using-the-rego-language-for-open-policy-agent-opa not attributes_mismatch(attributes, res) } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json index a0904254..ee052a02 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/exemptions/data.json @@ -9,6 +9,10 @@ { "method": "GET", "endpoint": "/api/aslan/environment/environments/?*" + }, + { + "method": "GET", + "endpoint": "/api/aslan/environment/environments" } ] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json index c7a3dbe9..0b331663 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/resources/data.json @@ -1,9 +1,22 @@ -[ - { - "resourceType": "Environment", - "projectName": "test", - "resourceID": "test", - "production": "true", - "production1": "true1" - } -] +{ + "Environment": [ + { + "projectName": "test", + "resourceID": "test", + "production": "true", + "production1": "true1" + }, + { + "projectName": "test", + "resourceID": "test1", + "production": "true", + "production1": "true1" + }, + { + "projectName": "test", + "resourceID": "test2", + "production": "false", + "production1": "true1" + } + ] +} diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json b/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json index 82c4ffe0..05f449ff 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_data/roles/data.json @@ -19,6 +19,17 @@ "value": "true1" } ] + }, + { + "method": "GET", + "endpoint": "/api/aslan/environment/environments", + "resourceType": "Environment", + "matchAttributes": [ + { + "key": "production", + "value": "true" + } + ] } ] } diff --git a/pkg/microservice/policy/core/service/bundle/rego/test_input.json b/pkg/microservice/policy/core/service/bundle/rego/test_input.json index 058dc6f2..5cd29351 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/test_input.json +++ b/pkg/microservice/policy/core/service/bundle/rego/test_input.json @@ -47,8 +47,7 @@ "api", "aslan", "environment", - "environments", - "test" + "environments" ], "parsed_query": { "projectName": [ -- Gitee From e7cc84cb19738713fc4d1772821af11ee5fd7f68 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:16:08 +0800 Subject: [PATCH 0776/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index fd95a0ef..7a12de87 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -120,7 +120,7 @@ func (c *WorkspaceAchiever) process(match string) bool { } func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { - newPath := make([]string, 0) + newPath := make([]string, 0, len(paths)) for _, path := range paths { path, err := c.renderPathVariable(path) if err != nil { -- Gitee From ec021d96f0ffd74b37079eb6b63889352f8d75fc Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:23:40 +0800 Subject: [PATCH 0777/2647] add log Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 7a12de87..64c27780 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -124,6 +124,7 @@ func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { for _, path := range paths { path, err := c.renderPathVariable(path) if err != nil { + log.Errorf("Failed to render variables, error :%s", err) return fmt.Errorf("Failed to render variables") } newPath = append(newPath, path) -- Gitee From 2e71b4122998cd291048d0b116337d2e169705b1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:32:13 +0800 Subject: [PATCH 0778/2647] optimize code Signed-off-by: liu deyi --- .../reaper/core/service/archive/file_archive.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 64c27780..415434aa 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -176,12 +176,12 @@ func (c *WorkspaceAchiever) enumerate() error { func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { if err := c.enumerate(); err != nil { - return []string{}, err + return nil, err } f, err := ioutil.TempFile("", "*cached_files.txt") if err != nil { - return []string{}, err + return nil, err } defer func() { @@ -192,18 +192,18 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { for _, path := range c.sortedFiles() { _, err = f.WriteString(path + "\n") if err != nil { - return []string{}, fmt.Errorf("failed to create cache file list: %s", err) + return nil, fmt.Errorf("failed to create cache file list: %s", err) } } if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil { - return []string{}, err + return nil, err } temp, err := ioutil.TempFile("", "*reaper.tar.gz") if err != nil { log.Errorf("failed to create temporary file: %v", err) - return []string{}, err + return nil, err } _ = temp.Close() @@ -216,7 +216,7 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { if err := cmd.Run(); err != nil { log.Errorf("failed to compress %v", err) - return []string{}, err + return nil, err } //if err := helper.Move(temp.Name(), target); err != nil { @@ -232,12 +232,12 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) if err != nil { log.Errorf("Archive s3 create s3 client error: %+v", err) - return []string{}, err + return nil, err } objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s/%s/%s", c.PipelineName, c.ServiceName, "cache", meta.FileName)) if err = s3client.Upload(store.Bucket, temp.Name(), objectKey); err != nil { log.Errorf("Archive s3 upload err:%v", err) - return []string{}, err + return nil, err } } -- Gitee From 48ea4c26a62d13322fa7186e28488b4e4a5f5d6b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:52:07 +0800 Subject: [PATCH 0779/2647] optimize code Signed-off-by: liu deyi --- .../reaper/core/service/archive/file_archive.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 415434aa..96ee051c 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -124,8 +124,8 @@ func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { for _, path := range paths { path, err := c.renderPathVariable(path) if err != nil { - log.Errorf("Failed to render variables, error :%s", err) - return fmt.Errorf("Failed to render variables") + log.Errorf("The variable of the custom cache directory is invalid, failed to render variables, error :%s", err) + return err } newPath = append(newPath, path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) @@ -245,6 +245,7 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { } +// The path contains shell variables, use linux's own ability to render func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { envs := c.Envs cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s %s", "echo", path)) @@ -252,5 +253,5 @@ func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { cmd.Env = envs data, err := cmd.Output() - return strings.Replace(string(data), "\n", "", -1), err + return strings.TrimSuffix(string(data), "\n"), err } -- Gitee From e74616092307214d380c58075a26312b176bc749 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 16 Nov 2021 09:46:26 +0800 Subject: [PATCH 0780/2647] custom cache dir support custom variable Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 5 ++++- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index fc915ce0..6e252a97 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -41,9 +41,10 @@ type WorkspaceAchiever struct { StorageURI string PipelineName string ServiceName string + Envs []string } -func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, caches []string, gitFolders []string) *WorkspaceAchiever { +func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, caches, gitFolders, envs []string) *WorkspaceAchiever { return &WorkspaceAchiever{ paths: caches, wd: wd, @@ -51,6 +52,7 @@ func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, cach StorageURI: storageURI, PipelineName: pipelineName, ServiceName: serviceName, + Envs: envs, } } @@ -119,6 +121,7 @@ func (c *WorkspaceAchiever) process(match string) bool { func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { for _, path := range paths { + fmt.Println("path:", path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) if err != nil { log.Warningf("%s: %v", path, err) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index a7a14136..ff470607 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -85,7 +85,7 @@ func (r *Reaper) GetCacheFile() string { } func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) error { - fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}) + fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}, r.getUserEnvs()) // list files matches caches return fileAchiever.Achieve(dest) -- Gitee From 53844dbe0bf4a72dd211303776dba1ee303f392e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 16 Nov 2021 11:56:39 +0800 Subject: [PATCH 0781/2647] debug code Signed-off-by: liu deyi --- .../reaper/core/service/archive/file_archive.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 6e252a97..f2ae197a 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -121,6 +121,11 @@ func (c *WorkspaceAchiever) process(match string) bool { func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { for _, path := range paths { + //replace path + path, err := c.renderPathVariable(path) + if err != nil { + fmt.Println("err:", err) + } fmt.Println("path:", path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) if err != nil { @@ -234,3 +239,14 @@ func (c *WorkspaceAchiever) Achieve(target string) error { return nil } + +func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { + envs := c.Envs + cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s %s", "echo", path)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = envs + data, err := cmd.Output() + + return string(data), err +} -- Gitee From efa6ee905be0f749d8b202dcd27af28f9effa400 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 16 Nov 2021 14:01:54 +0800 Subject: [PATCH 0782/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index f2ae197a..4a3aade5 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -243,7 +243,6 @@ func (c *WorkspaceAchiever) Achieve(target string) error { func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { envs := c.Envs cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s %s", "echo", path)) - cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Env = envs data, err := cmd.Output() -- Gitee From 757d7fb93df4de4ec15d40e83d20686ee7e3799d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 18 Nov 2021 14:30:37 +0800 Subject: [PATCH 0783/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index ff470607..34aa24a9 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -98,7 +98,7 @@ func (r *Reaper) CompressCache(storageURI string) error { return err } if len(r.Ctx.Caches) > 0 { - log.Infof("custom caches will be cached: %v", r.Ctx.Caches) + log.Infof("custom caches will be cached") if err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches); err != nil { return err } -- Gitee From 9761f59219a6d2126c3a4b6ace087e02b8ec1c84 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 15:00:57 +0800 Subject: [PATCH 0784/2647] optimize code Signed-off-by: liu deyi --- .../core/service/archive/file_archive.go | 34 +++++++++++-------- .../reaper/core/service/reaper/reaper.go | 7 ++-- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 4a3aade5..a1af23b6 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -119,14 +119,14 @@ func (c *WorkspaceAchiever) process(match string) bool { return false } -func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { +func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { + newPath := make([]string, 0) for _, path := range paths { - //replace path path, err := c.renderPathVariable(path) if err != nil { - fmt.Println("err:", err) + return fmt.Errorf("Failed to render variables") } - fmt.Println("path:", path) + newPath = append(newPath, path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) if err != nil { log.Warningf("%s: %v", path, err) @@ -155,12 +155,16 @@ func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) { } } } + c.paths = newPath + return nil } func (c *WorkspaceAchiever) enumerate() error { c.files = make(map[string]os.FileInfo) - c.processPaths(c.paths, true) + if err := c.processPaths(c.paths, true); err != nil { + return err + } for _, folder := range c.gitFolders { c.processPaths([]string{filepath.Join(folder, ".git")}, false) @@ -169,14 +173,14 @@ func (c *WorkspaceAchiever) enumerate() error { return nil } -func (c *WorkspaceAchiever) Achieve(target string) error { +func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { if err := c.enumerate(); err != nil { - return err + return []string{}, err } f, err := ioutil.TempFile("", "*cached_files.txt") if err != nil { - return err + return []string{}, err } defer func() { @@ -187,18 +191,18 @@ func (c *WorkspaceAchiever) Achieve(target string) error { for _, path := range c.sortedFiles() { _, err = f.WriteString(path + "\n") if err != nil { - return fmt.Errorf("failed to create cache file list: %s", err) + return []string{}, fmt.Errorf("failed to create cache file list: %s", err) } } if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil { - return err + return []string{}, err } temp, err := ioutil.TempFile("", "*reaper.tar.gz") if err != nil { log.Errorf("failed to create temporary file: %v", err) - return err + return []string{}, err } _ = temp.Close() @@ -211,7 +215,7 @@ func (c *WorkspaceAchiever) Achieve(target string) error { if err := cmd.Run(); err != nil { log.Errorf("failed to compress %v", err) - return err + return []string{}, err } //if err := helper.Move(temp.Name(), target); err != nil { @@ -227,16 +231,16 @@ func (c *WorkspaceAchiever) Achieve(target string) error { s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) if err != nil { log.Errorf("Archive s3 create s3 client error: %+v", err) - return err + return []string{}, err } objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s/%s/%s", c.PipelineName, c.ServiceName, "cache", meta.FileName)) if err = s3client.Upload(store.Bucket, temp.Name(), objectKey); err != nil { log.Errorf("Archive s3 upload err:%v", err) - return err + return []string{}, err } } - return nil + return c.paths, nil } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 34aa24a9..16e190aa 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -84,7 +84,7 @@ func (r *Reaper) GetCacheFile() string { return filepath.Join(r.Ctx.Workspace, "reaper.tar.gz") } -func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) error { +func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) ([]string, error) { fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}, r.getUserEnvs()) // list files matches caches @@ -99,10 +99,11 @@ func (r *Reaper) CompressCache(storageURI string) error { } if len(r.Ctx.Caches) > 0 { log.Infof("custom caches will be cached") - if err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches); err != nil { + caches, err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches) + if err != nil { return err } - log.Infof("succeed to cache %s", r.Ctx.Caches) + log.Infof("succeed to cache %s", caches) } else { log.Infof("workspace will be cached in background") if err := r.cm.Archive(r.ActiveWorkspace, r.GetCacheFile()); err != nil { -- Gitee From ac283f1c8800aadd0c2c8e1c39dc9b685bbde569 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 15:13:49 +0800 Subject: [PATCH 0785/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 16e190aa..136d0d39 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -103,7 +103,7 @@ func (r *Reaper) CompressCache(storageURI string) error { if err != nil { return err } - log.Infof("succeed to cache %s", caches) + log.Infof("succeed to cache [%s]", strings.Join(caches, ",")) } else { log.Infof("workspace will be cached in background") if err := r.cm.Archive(r.ActiveWorkspace, r.GetCacheFile()); err != nil { -- Gitee From 50e67f9f3104b785e2b2d1f99007ffdb322b0e24 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 15:27:49 +0800 Subject: [PATCH 0786/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index a1af23b6..fd95a0ef 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -251,5 +251,5 @@ func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { cmd.Env = envs data, err := cmd.Output() - return string(data), err + return strings.Replace(string(data), "\n", "", -1), err } -- Gitee From 83de7fd20203ff66f309c026e01ed4f87a4a1c8e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:16:08 +0800 Subject: [PATCH 0787/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index fd95a0ef..7a12de87 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -120,7 +120,7 @@ func (c *WorkspaceAchiever) process(match string) bool { } func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { - newPath := make([]string, 0) + newPath := make([]string, 0, len(paths)) for _, path := range paths { path, err := c.renderPathVariable(path) if err != nil { -- Gitee From 1da699a284c4ea8a8ebabc76000e9af7175d47e1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:23:40 +0800 Subject: [PATCH 0788/2647] add log Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 7a12de87..64c27780 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -124,6 +124,7 @@ func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { for _, path := range paths { path, err := c.renderPathVariable(path) if err != nil { + log.Errorf("Failed to render variables, error :%s", err) return fmt.Errorf("Failed to render variables") } newPath = append(newPath, path) -- Gitee From c502b3fe5db3726f8e7e0ad8d5bcbc4e1deac94b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:32:13 +0800 Subject: [PATCH 0789/2647] optimize code Signed-off-by: liu deyi --- .../reaper/core/service/archive/file_archive.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 64c27780..415434aa 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -176,12 +176,12 @@ func (c *WorkspaceAchiever) enumerate() error { func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { if err := c.enumerate(); err != nil { - return []string{}, err + return nil, err } f, err := ioutil.TempFile("", "*cached_files.txt") if err != nil { - return []string{}, err + return nil, err } defer func() { @@ -192,18 +192,18 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { for _, path := range c.sortedFiles() { _, err = f.WriteString(path + "\n") if err != nil { - return []string{}, fmt.Errorf("failed to create cache file list: %s", err) + return nil, fmt.Errorf("failed to create cache file list: %s", err) } } if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil { - return []string{}, err + return nil, err } temp, err := ioutil.TempFile("", "*reaper.tar.gz") if err != nil { log.Errorf("failed to create temporary file: %v", err) - return []string{}, err + return nil, err } _ = temp.Close() @@ -216,7 +216,7 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { if err := cmd.Run(); err != nil { log.Errorf("failed to compress %v", err) - return []string{}, err + return nil, err } //if err := helper.Move(temp.Name(), target); err != nil { @@ -232,12 +232,12 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) if err != nil { log.Errorf("Archive s3 create s3 client error: %+v", err) - return []string{}, err + return nil, err } objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s/%s/%s", c.PipelineName, c.ServiceName, "cache", meta.FileName)) if err = s3client.Upload(store.Bucket, temp.Name(), objectKey); err != nil { log.Errorf("Archive s3 upload err:%v", err) - return []string{}, err + return nil, err } } -- Gitee From c6fa6abc5382a9930b3e06667f50c1eae754b9e0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 24 Nov 2021 17:52:07 +0800 Subject: [PATCH 0790/2647] optimize code Signed-off-by: liu deyi --- .../reaper/core/service/archive/file_archive.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 415434aa..96ee051c 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -124,8 +124,8 @@ func (c *WorkspaceAchiever) processPaths(paths []string, verbose bool) error { for _, path := range paths { path, err := c.renderPathVariable(path) if err != nil { - log.Errorf("Failed to render variables, error :%s", err) - return fmt.Errorf("Failed to render variables") + log.Errorf("The variable of the custom cache directory is invalid, failed to render variables, error :%s", err) + return err } newPath = append(newPath, path) matches, err := filepath.Glob(filepath.Join(c.wd, path)) @@ -245,6 +245,7 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { } +// The path contains shell variables, use linux's own ability to render func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { envs := c.Envs cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s %s", "echo", path)) @@ -252,5 +253,5 @@ func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { cmd.Env = envs data, err := cmd.Output() - return strings.Replace(string(data), "\n", "", -1), err + return strings.TrimSuffix(string(data), "\n"), err } -- Gitee From 63c3f7276deab60a707edf3d0ef05983b9dc5820 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 25 Nov 2021 10:22:20 +0800 Subject: [PATCH 0791/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 96ee051c..5e7b6c97 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -253,5 +253,6 @@ func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { cmd.Env = envs data, err := cmd.Output() - return strings.TrimSuffix(string(data), "\n"), err + //return strings.TrimSuffix(string(data), "\n"), err + return strings.TrimSpace(string(data)), err } -- Gitee From 37fd9e94800ef7ce25efed96848b12160b93ba46 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 25 Nov 2021 10:39:10 +0800 Subject: [PATCH 0792/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/archive/file_archive.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 2e00fa8f..96ee051c 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -253,6 +253,5 @@ func (c *WorkspaceAchiever) renderPathVariable(path string) (string, error) { cmd.Env = envs data, err := cmd.Output() - return strings.TrimSpace(string(data)), err - //return strings.TrimSuffix(string(data), "\n"), err + return strings.TrimSuffix(string(data), "\n"), err } -- Gitee From 711066e2ac73bd47d4783a59eacb2d3166217e72 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 25 Nov 2021 11:18:38 +0800 Subject: [PATCH 0793/2647] fix compile error Signed-off-by: allenshen --- pkg/microservice/aslan/config/consts.go | 1 + .../repository/models/delivery_distribute.go | 2 + .../repository/models/delivery_version.go | 32 +- .../repository/mongodb/delivery_version.go | 32 +- .../aslan/core/common/service/service.go | 36 ++ .../aslan/core/delivery/handler/version.go | 190 +------ .../aslan/core/delivery/service/docker.go | 57 ++ .../aslan/core/delivery/service/version.go | 489 ++++++++++++++++-- .../aslan/core/environment/service/helm.go | 11 - pkg/setting/consts.go | 8 +- 10 files changed, 580 insertions(+), 278 deletions(-) create mode 100644 pkg/microservice/aslan/core/delivery/service/docker.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 36376093..718b2bb4 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -136,6 +136,7 @@ type DistributeType string const ( File DistributeType = "file" Image DistributeType = "image" + Chart DistributeType = "chart" ) type K8SClusterStatus string diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go index db24294d..24b87745 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go @@ -28,6 +28,8 @@ type DeliveryDistribute struct { ServiceName string `bson:"service_name" json:"serviceName"` DistributeType config.DistributeType `bson:"distribute_type" json:"distributeType"` RegistryName string `bson:"registry_name" json:"registryName"` + Version string `bson:"version" json:"version"` + ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName"` Namespace string `bson:"namespace" json:"namespace"` PackageFile string `bson:"package_file" json:"packageFile"` RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey"` diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index c221d1af..8720ac8b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -20,11 +20,11 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -type DeliveryChart struct { - Name string `bson:"name" json:"name"` - Version string `bson:"version" json:"version"` - Repo string `bson:"repo" json:"repo"` -} +//type DeliveryChart struct { +// Name string `bson:"name" json:"name"` +// Version string `bson:"version" json:"version"` +// Repo string `bson:"repo" json:"repo"` +//} type DeliveryVersionProgress struct { SuccessChartCount int `json:"successChartCount"` @@ -34,18 +34,18 @@ type DeliveryVersionProgress struct { } type DeliveryVersion struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Version string `bson:"version" json:"version"` - ProductName string `bson:"product_name" json:"productName"` - WorkflowName string `bson:"workflow_name" json:"workflowName"` - Type string `bson:"type" json:"type"` - TaskID int `bson:"task_id" json:"taskId"` - Desc string `bson:"desc" json:"desc"` - Labels []string `bson:"labels" json:"labels"` - ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` - Charts []*DeliveryChart `bson:"charts" json:"charts"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Version string `bson:"version" json:"version"` + ProductName string `bson:"product_name" json:"productName"` + WorkflowName string `bson:"workflow_name" json:"workflowName"` + Type string `bson:"type" json:"type"` + TaskID int `bson:"task_id" json:"taskId"` + Desc string `bson:"desc" json:"desc"` + Labels []string `bson:"labels" json:"labels"` + ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` + //Charts []*DeliveryChart `bson:"charts" json:"charts"` Status string `bson:"status" json:"status"` - Error string `bson:"error" json:"error"` + Error string `bson:"error" json:"-"` Progress *DeliveryVersionProgress `bson:"-" json:"progress"` CreateArgument interface{} `bson:"createArgument" json:"-"` CreatedBy string `bson:"created_by" json:"createdBy"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index ada39606..05d7cb8b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -207,22 +207,22 @@ func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr s return err } -func (c *DeliveryVersionColl) AddDeliveryChart(versionName string, chartInfo *models.DeliveryChart) error { - query := bson.M{ - "version": versionName, - "deleted_at": 0, - "charts.name": bson.M{"$ne": chartInfo.Name}, - } - change := bson.M{ - "$push": bson.M{ - "charts": bson.M{ - "name": chartInfo.Name, - "version": chartInfo.Version, - "repo": chartInfo.Repo, - }, - }} - _, err := c.UpdateOne(context.TODO(), query, change) - return err +func (c *DeliveryVersionColl) AddDeliveryChart(versionName string, chartInfo *models.DeliveryDistribute) error { + //query := bson.M{ + // "version": versionName, + // "deleted_at": 0, + // "charts.name": bson.M{"$ne": chartInfo.Name}, + //} + //change := bson.M{ + // "$push": bson.M{ + // "charts": bson.M{ + // "name": chartInfo.Name, + // "version": chartInfo.Version, + // "repo": chartInfo.Repo, + // }, + // }} + //_, err := c.UpdateOne(context.TODO(), query, change) + return nil } func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 78040931..b5bb8a6b 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "regexp" "strings" templ "text/template" @@ -614,6 +615,41 @@ func ExtractImageName(imageURI string) string { return "" } +// ExtractImageRegistry extract registry url from total image uri +func ExtractImageRegistry(imageURI string) (string, error) { + subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + if exNames[i] == "repo" { + u, err := url.Parse(matchedStr) + if err != nil { + return "", err + } + if len(u.Scheme) > 0 { + matchedStr = strings.TrimPrefix(matchedStr, fmt.Sprintf("%s://", u.Scheme)) + } + return matchedStr, nil + } + } + } + return "", fmt.Errorf("failed to extract registry url") +} + +// ExtractImageTag extract image tag from total image uri +func ExtractImageTag(imageURI string) string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + if exNames[i] == "tag" { + return matchedStr + } + } + } + return "" +} + func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 1d11d635..10397ed4 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -17,19 +17,14 @@ limitations under the License. package handler import ( - "encoding/json" "fmt" - "strconv" "strings" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" deliveryservice "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" - workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -65,190 +60,31 @@ func GetDeliveryVersion(c *gin.Context) { } version := new(commonrepo.DeliveryVersionArgs) version.ID = ID - ctx.Resp, ctx.Err = deliveryservice.GetDeliveryVersion(version, ctx.Logger) -} - -type ReleaseInfo struct { - VersionInfo *commonmodels.DeliveryVersion `json:"versionInfo"` - BuildInfo []*commonmodels.DeliveryBuild `json:"buildInfo"` - DeployInfo []*commonmodels.DeliveryDeploy `json:"deployInfo"` - TestInfo []*commonmodels.DeliveryTest `json:"testInfo"` - DistributeInfo []*commonmodels.DeliveryDistribute `json:"distributeInfo"` - SecurityInfo []*DeliverySecurityStats `json:"securityStatsInfo"` -} - -type DeliverySecurityStats struct { - ImageName string `json:"imageName"` - ImageID string `json:"imageId"` - DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` -} - -type DeliverySecurityStatsInfo struct { - Total int `json:"total"` - Unknown int `json:"unkown"` - Negligible int `json:"negligible"` - Low int `json:"low"` - Medium int `json:"medium"` - High int `json:"high"` - Critical int `json:"critical"` + ctx.Resp, ctx.Err = deliveryservice.GetDetailReleaseData(version, ctx.Logger) } func ListDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - taskIDStr := c.Query("taskId") - var taskID = 0 - var err error - if taskIDStr != "" { - taskID, err = strconv.Atoi(taskIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } + args := new(deliveryservice.ListDeliveryVersionArgs) + err := c.BindQuery(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } - perPageStr := c.Query("per_page") - pageStr := c.Query("page") - var ( - perPage int - page int - ) - if perPageStr == "" { - perPage = 20 - } else { - perPage, err = strconv.Atoi(perPageStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("perPage args err :%s", err)) - return - } + if args.Page <= 0 { + args.Page = 1 } - - if pageStr == "" { - page = 1 - } else { - page, err = strconv.Atoi(pageStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("page args err :%s", err)) - return - } + if args.PerPage <= 0 { + args.Page = 20 } - - serviceName := c.Query("serviceName") - - version := new(commonrepo.DeliveryVersionArgs) - version.ProductName = c.Query("projectName") - version.WorkflowName = c.Query("workflowName") - version.TaskID = taskID - version.PerPage = perPage - version.Page = page - deliveryVersions, err := deliveryservice.FindDeliveryVersion(version, ctx.Logger) - - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return + if len(args.Verbosity) == 0 { + args.Verbosity = deliveryservice.VerbosityDetailed } - releaseInfos := make([]*ReleaseInfo, 0) - for _, deliveryVersion := range deliveryVersions { - releaseInfo := new(ReleaseInfo) - //versionInfo - deliveryVersion.Progress = deliveryservice.FillDeliveryProgressInfo(deliveryVersion) - releaseInfo.VersionInfo = deliveryVersion - - //deployInfo - deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) - deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDeploys, err := deliveryservice.FindDeliveryDeploy(deliveryDeployArgs, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - // 查询条件serviceName - if serviceName != "" { - match := false - for _, deliveryDeploy := range deliveryDeploys { - if deliveryDeploy.ServiceName == serviceName { - match = true - break - } - } - if !match { - continue - } - } - // 将serviceName替换为服务名/服务组件的形式,用于前端展示 - for _, deliveryDeploy := range deliveryDeploys { - if deliveryDeploy.ContainerName != "" { - deliveryDeploy.ServiceName = deliveryDeploy.ServiceName + "/" + deliveryDeploy.ContainerName - } - } - releaseInfo.DeployInfo = deliveryDeploys - - //buildInfo - deliveryBuildArgs := new(commonrepo.DeliveryBuildArgs) - deliveryBuildArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryBuilds, err := deliveryservice.FindDeliveryBuild(deliveryBuildArgs, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - releaseInfo.BuildInfo = deliveryBuilds - - //testInfo - deliveryTestArgs := new(commonrepo.DeliveryTestArgs) - deliveryTestArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryTests, err := deliveryservice.FindDeliveryTest(deliveryTestArgs, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - releaseInfo.TestInfo = deliveryTests - - //securityStatsInfo - deliverySecurityStatss := make([]*DeliverySecurityStats, 0) - if pipelineTask, err := workflowservice.GetPipelineTaskV2(int64(deliveryVersion.TaskID), deliveryVersion.WorkflowName, config.WorkflowType, ctx.Logger); err == nil { - for _, subStage := range pipelineTask.Stages { - if subStage.TaskType == config.TaskSecurity { - subSecurityTaskMap := subStage.SubTasks - for _, subTask := range subSecurityTaskMap { - securityInfo, _ := base.ToSecurityTask(subTask) - - deliverySecurityStats := new(DeliverySecurityStats) - deliverySecurityStats.ImageName = securityInfo.ImageName - deliverySecurityStats.ImageID = securityInfo.ImageID - deliverySecurityStatsMap, err := deliveryservice.FindDeliverySecurityStatistics(securityInfo.ImageID, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - var transErr error - b, err := json.Marshal(deliverySecurityStatsMap) - if err != nil { - transErr = fmt.Errorf("marshal task error: %v", err) - } - - if err := json.Unmarshal(b, &deliverySecurityStats.DeliverySecurityStatsInfo); err != nil { - transErr = fmt.Errorf("unmarshal task error: %v", err) - } - if transErr != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - - deliverySecurityStatss = append(deliverySecurityStatss, deliverySecurityStats) - } - break - } - } - releaseInfo.SecurityInfo = deliverySecurityStatss - } - - //distributeInfo - deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) - deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDistributes, _ := deliveryservice.FindDeliveryDistribute(deliveryDistributeArgs, ctx.Logger) - - releaseInfo.DistributeInfo = deliveryDistributes - releaseInfos = append(releaseInfos, releaseInfo) - } - ctx.Err = err - ctx.Resp = releaseInfos + ctx.Resp, ctx.Err = deliveryservice.ListDeliveryVersion(args, ctx.Logger) } func getFileName(fileName string) string { diff --git a/pkg/microservice/aslan/core/delivery/service/docker.go b/pkg/microservice/aslan/core/delivery/service/docker.go new file mode 100644 index 00000000..65a35941 --- /dev/null +++ b/pkg/microservice/aslan/core/delivery/service/docker.go @@ -0,0 +1,57 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import "os/exec" + +const dockerExe = "/usr/local/bin/docker" + +func dockerLogin(user, password, registry string) *exec.Cmd { + return exec.Command( + dockerExe, + "login", + "-u", user, + "-p", password, + registry, + ) +} + +func dockerInfo() *exec.Cmd { + return exec.Command(dockerExe, "info") +} + +func dockerPush(fullImage string) *exec.Cmd { + args := []string{ + "push", + fullImage, + } + return exec.Command(dockerExe, args...) +} + +func dockerPull(image string) *exec.Cmd { + args := []string{"pull", image} + return exec.Command(dockerExe, args...) +} + +func dockerTag(sourceFullImage, targetFullImage string) *exec.Cmd { + args := []string{ + "tag", + sourceFullImage, + targetFullImage, + } + return exec.Command(dockerExe, args...) +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index c764572b..5524f87d 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -17,16 +17,21 @@ limitations under the License. package service import ( + "bytes" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" + "net/url" "os" + "os/exec" "path/filepath" + "strings" "sync" "time" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" @@ -43,15 +48,29 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util/converter" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) +const ( + VerbosityBrief string = "brief" // brief delivery data + VerbosityDetailed string = "detailed" // detailed delivery version with total data +) + +type DeliveryVersionFilter struct { + ServiceName string +} + type CreateHelmDeliveryVersionOption struct { EnableOfflineDist bool `json:"enableOfflineDist"` S3StorageID string `json:"s3StorageID"` @@ -112,13 +131,57 @@ type DeliveryVariablesApplyArgs struct { ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` } +type ListDeliveryVersionArgs struct { + Page int `form:"page"` + PerPage int `form:"per_page"` + TaskId int `form:"taskId"` + ServiceName string `form:"serviceName"` + Verbosity string `form:"verbosity"` + ProjectName string `form:"projectName"` + WorkflowName string `form:"workflowName"` +} + +type ReleaseInfo struct { + VersionInfo *commonmodels.DeliveryVersion `json:"versionInfo"` + BuildInfo []*commonmodels.DeliveryBuild `json:"buildInfo,omitempty"` + DeployInfo []*commonmodels.DeliveryDeploy `json:"deployInfo,omitempty"` + TestInfo []*commonmodels.DeliveryTest `json:"testInfo,omitempty"` + DistributeInfo []*commonmodels.DeliveryDistribute `json:"distributeInfo,omitempty"` + SecurityInfo []*DeliverySecurityStats `json:"securityStatsInfo,omitempty"` +} + +type DeliverySecurityStatsInfo struct { + Total int `json:"total"` + Unknown int `json:"unkown"` + Negligible int `json:"negligible"` + Low int `json:"low"` + Medium int `json:"medium"` + High int `json:"high"` + Critical int `json:"critical"` +} + +type DeliverySecurityStats struct { + ImageName string `json:"imageName"` + ImageID string `json:"imageId"` + DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { - resp, err := commonrepo.NewDeliveryVersionColl().Get(args) + versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { log.Errorf("get deliveryVersion error: %v", err) return nil, e.ErrGetDeliveryVersion } - return resp, err + return versionData, err +} + +func GetDetailReleaseData(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*ReleaseInfo, error) { + versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) + if err != nil { + log.Errorf("get deliveryVersion error: %v", err) + return nil, e.ErrGetDeliveryVersion + } + return buildReleaseRespData(VerbosityDetailed, versionData, nil, log) } func FindDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) ([]*commonmodels.DeliveryVersion, error) { @@ -139,11 +202,176 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } -func FillDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion) *commonmodels.DeliveryVersionProgress { +func filterReleases(filter *DeliveryVersionFilter, deliveryVersion *commonmodels.DeliveryVersion, logger *zap.SugaredLogger) bool { + if filter == nil { + return true + } + if filter.ServiceName != "" { + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, logger) + if err != nil { + return true + } + match := false + for _, deliveryDeploy := range deliveryDeploys { + if deliveryDeploy.ServiceName == filter.ServiceName { + match = true + break + } + } + if !match { + return false + } + } + return true +} + +func buildBriefRelease(deliveryVersion *commonmodels.DeliveryVersion, _ *zap.SugaredLogger) (*ReleaseInfo, error) { + return &ReleaseInfo{ + VersionInfo: deliveryVersion, + }, nil +} + +func buildDetailedRelease(deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { + releaseInfo := new(ReleaseInfo) + //versionInfo + releaseInfo.VersionInfo = deliveryVersion + + //deployInfo + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, logger) + if err != nil { + return nil, err + } + if filterOpt != nil { + if !filterReleases(filterOpt, deliveryVersion, logger) { + return nil, nil + } + } + // 将serviceName替换为服务名/服务组件的形式,用于前端展示 + for _, deliveryDeploy := range deliveryDeploys { + if deliveryDeploy.ContainerName != "" { + deliveryDeploy.ServiceName = deliveryDeploy.ServiceName + "/" + deliveryDeploy.ContainerName + } + } + releaseInfo.DeployInfo = deliveryDeploys + + //buildInfo + deliveryBuildArgs := new(commonrepo.DeliveryBuildArgs) + deliveryBuildArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryBuilds, err := FindDeliveryBuild(deliveryBuildArgs, logger) + if err != nil { + return nil, err + } + releaseInfo.BuildInfo = deliveryBuilds + + //testInfo + deliveryTestArgs := new(commonrepo.DeliveryTestArgs) + deliveryTestArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryTests, err := FindDeliveryTest(deliveryTestArgs, logger) + if err != nil { + return nil, err + } + releaseInfo.TestInfo = deliveryTests + + //securityStatsInfo + deliverySecurityStatss := make([]*DeliverySecurityStats, 0) + if pipelineTask, err := workflowservice.GetPipelineTaskV2(int64(deliveryVersion.TaskID), deliveryVersion.WorkflowName, config.WorkflowType, logger); err == nil { + for _, subStage := range pipelineTask.Stages { + if subStage.TaskType == config.TaskSecurity { + subSecurityTaskMap := subStage.SubTasks + for _, subTask := range subSecurityTaskMap { + securityInfo, _ := base.ToSecurityTask(subTask) + + deliverySecurityStats := new(DeliverySecurityStats) + deliverySecurityStats.ImageName = securityInfo.ImageName + deliverySecurityStats.ImageID = securityInfo.ImageID + deliverySecurityStatsMap, err := FindDeliverySecurityStatistics(securityInfo.ImageID, logger) + if err != nil { + return nil, err + } + var transErr error + b, err := json.Marshal(deliverySecurityStatsMap) + if err != nil { + transErr = fmt.Errorf("marshal task error: %v", err) + } + if err := json.Unmarshal(b, &deliverySecurityStats.DeliverySecurityStatsInfo); err != nil { + transErr = fmt.Errorf("unmarshal task error: %v", err) + } + if transErr != nil { + return nil, transErr + } + + deliverySecurityStatss = append(deliverySecurityStatss, deliverySecurityStats) + } + break + } + } + releaseInfo.SecurityInfo = deliverySecurityStatss + } + + //distributeInfo + deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) + deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDistributes, _ := FindDeliveryDistribute(deliveryDistributeArgs, logger) + + deliveryVersion.Progress = FillDeliveryProgressInfo(deliveryVersion, deliveryDistributes) + + releaseInfo.DistributeInfo = deliveryDistributes + return releaseInfo, nil +} + +func buildReleaseRespData(verbosity string, deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { + switch verbosity { + case VerbosityBrief: + return buildBriefRelease(deliveryVersion, logger) + case VerbosityDetailed: + return buildDetailedRelease(deliveryVersion, filterOpt, logger) + default: + return buildDetailedRelease(deliveryVersion, filterOpt, logger) + } +} + +func ListDeliveryVersion(args *ListDeliveryVersionArgs, logger *zap.SugaredLogger) ([]*ReleaseInfo, error) { + version := new(commonrepo.DeliveryVersionArgs) + version.ProductName = args.ProjectName + version.WorkflowName = args.WorkflowName + version.TaskID = args.TaskId + version.PerPage = args.PerPage + version.Page = args.Page + deliveryVersions, err := FindDeliveryVersion(version, logger) + if err != nil { + return nil, err + } + + log.Infof("###### the verbosity is %s, projectName is %s", args.Verbosity, args.ProjectName) + releaseInfos := make([]*ReleaseInfo, 0) + for _, deliveryVersion := range deliveryVersions { + releaseInfo, err := buildReleaseRespData(args.Verbosity, deliveryVersion, &DeliveryVersionFilter{ServiceName: args.ServiceName}, logger) + if err != nil { + return nil, err + } + if releaseInfo == nil { + continue + } + releaseInfos = append(releaseInfos, releaseInfo) + } + return releaseInfos, nil +} + +func FillDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion, deliveryDistribute []*commonmodels.DeliveryDistribute) *commonmodels.DeliveryVersionProgress { if deliveryVersion.Type != setting.DeliveryVersionTypeChart { return nil } - successfulChartCount := len(deliveryVersion.Charts) + chartDeploys := make([]*commonmodels.DeliveryDistribute, 0) + for _, deliveryDistribute := range deliveryDistribute { + if deliveryDistribute.DistributeType == config.Chart { + chartDeploys = append(chartDeploys, deliveryDistribute) + } + } + successfulChartCount := len(chartDeploys) progress := &commonmodels.DeliveryVersionProgress{ SuccessChartCount: successfulChartCount, @@ -173,6 +401,7 @@ func FillDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion) *co if deliveryVersion.Status == setting.DeliveryVersionStatusFailed { progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusFailed + progress.Error = deliveryVersion.Error return progress } @@ -225,24 +454,109 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { return client, nil } -func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string) error { +func runDockerCommand(cmd *exec.Cmd) error { + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +// pull image from currently using registry and push to specified repo +func pushImages(serviceObj *commonmodels.Service, valuesMap map[string]interface{}, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { + + log.Infof("######## start pushing image #########") + + flatMap, err := converter.Flatten(valuesMap) + if err != nil { + return err + } + + imagePathSpecs := make([]map[string]string, 0) + for _, container := range serviceObj.Containers { + imageSearchRule := &template.ImageSearchingRule{ + Repo: container.ImagePath.Repo, + Image: container.ImagePath.Image, + Tag: container.ImagePath.Tag, + } + pattern := imageSearchRule.GetSearchingPattern() + imagePathSpecs = append(imagePathSpecs, pattern) + } + + // find full image uris from values.yaml + imageUris := make([]string, 0) + for _, spec := range imagePathSpecs { + imageUri, err := commonservice.GeneImageURI(spec, flatMap) + if err != nil { + return err + } + imageUris = append(imageUris, imageUri) + } + + // login to the target repo + err = runDockerCommand(dockerLogin(targetRegistry.AccessKey, targetRegistry.SecretKey, targetRegistry.RegAddr)) + if err != nil { + return errors.Wrapf(err, fmt.Sprintf("failed to login docker registry, url: %s", targetRegistry.RegAddr)) + } + + for _, imageUri := range imageUris { + log.Infof("##### handle single image uri %s ######", imageUri) + registryUrl, err := commonservice.ExtractImageRegistry(imageUri) + if err != nil { + return errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUri) + } + registryUrl = strings.TrimSuffix(registryUrl, "/") + log.Infof("#### extract registry url %s ####", registryUrl) + if registry, ok := registryMap[registryUrl]; ok { + if registry.ID == targetRegistry.ID { + log.Infof("######## same registry, no need to push again ########") + continue + } + //TODO need optimize to avoid login to same repo multiple times + err := runDockerCommand(dockerLogin(registry.AccessKey, registry.SecretKey, registry.RegAddr)) + if err != nil { + return errors.Wrapf(err, fmt.Sprintf("failed to login docker registry, url: %s", registry.RegAddr)) + } + } + err = runDockerCommand(dockerPull(imageUri)) + if err != nil { + return errors.Wrapf(err, fmt.Sprintf("failed to pull docker image, source url: %s", imageUri)) + } + + imageName := commonservice.ExtractImageName(imageUri) + imageTag := commonservice.ExtractImageTag(imageUri) + + targetFullImageUri := fmt.Sprintf("%s/%s/%s:%s", targetRegistry.RegAddr, targetRegistry.Namespace, imageName, imageTag) + err = runDockerCommand(dockerTag(imageUri, targetFullImageUri)) + if err != nil { + return errors.Wrapf(err, "failed to tag image from: %s to : %s", imageUri, targetFullImageUri) + } + + err = runDockerCommand(dockerPush(targetFullImageUri)) + if err != nil { + return errors.Wrapf(err, "failed to push image: %s", targetFullImageUri) + } + } + return nil +} + +func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { serviceObj := chartData.ServiceObj serviceName, revision := serviceObj.ServiceName, serviceObj.Revision - base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) - if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { + basePath := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) + if err := commonservice.PreloadServiceManifestsByRevision(basePath, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) // use the latest version when it fails to download the specific version - base = config.LocalServicePath(serviceObj.ProductName, serviceName) - if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { + basePath = config.LocalServicePath(serviceObj.ProductName, serviceName) + if err = commonservice.PreLoadServiceManifests(basePath, serviceObj); err != nil { log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) return err } } - // push 镜像 - //commonrepo.NewRegistryNamespaceColl().Find(&commonrepo.FindRegOps{}) - - fullPath := filepath.Join(base, serviceObj.ServiceName) + fullPath := filepath.Join(basePath, serviceObj.ServiceName) revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) err := copy.Copy(fullPath, deliveryChartPath) @@ -284,9 +598,15 @@ func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.Hel return err } + // push docker image + err = pushImages(serviceObj, chartRequested.Values, targetRegistry, registryMap) + if err != nil { + return errors.Wrapf(err, "failed to handle image") + } + client, err := createChartRepoClient(chartRepo) if err != nil { - return err + return errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) } log.Infof("pushing %s to %s...\n", filepath.Base(chartPackagePath), chartRepo.URL) @@ -304,7 +624,9 @@ func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.Hel func handlePushResponse(resp *http.Response) error { if resp.StatusCode != 201 && resp.StatusCode != 202 { b, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) if err != nil { return err } @@ -375,6 +697,24 @@ func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productI return chartDataMap, nil } +func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { + registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) + if err != nil { + return nil, fmt.Errorf("failed to query registries") + } + ret := make(map[string]*commonmodels.RegistryNamespace) + for _, singleRegistry := range registries { + fullUrl := fmt.Sprintf("%s/%s", singleRegistry.RegAddr, singleRegistry.Namespace) + fullUrl = strings.TrimSuffix(fullUrl, "/") + u, _ := url.Parse(fullUrl) + if len(u.Scheme) > 0 { + fullUrl = strings.TrimPrefix(fullUrl, fmt.Sprintf("%s://", u.Scheme)) + } + ret[fullUrl] = singleRegistry + } + return ret, nil +} + func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData, logger *zap.SugaredLogger) (err error) { defer func() { if err != nil { @@ -405,29 +745,47 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer } repoInfo, err := getChartRepoData(args.ChartRepoName) if err != nil { - log.Infof("failed to query chart-repo info, productName: %s, err: %s", deliveryVersion.ProductName, err) + log.Errorf("failed to query chart-repo info, productName: %s, err: %s", deliveryVersion.ProductName, err) return fmt.Errorf("failed to query chart-repo info, productName: %s, repoName: %s", deliveryVersion.ProductName, args.ChartRepoName) } + targetRegistry, err := commonrepo.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{ + ID: args.ImageRegistryID, + }) + if err != nil { + log.Errorf("failed to query target image registry with ID: %s, err %s", args.ImageRegistryID, err) + return fmt.Errorf("faild to query target registry namespace") + } + + registryMap, err := buildRegistryMap() + if err != nil { + return fmt.Errorf("failed to build registry map") + } + + err = runDockerCommand(dockerInfo()) + if err != nil { + return fmt.Errorf("failed to run docker deamon") + } + // push charts to repo wg := sync.WaitGroup{} for _, chartData := range chartDataMap { wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - err := handleSingleChart(cData, repoInfo, dir, args.GlobalVariables) + err := handleSingleChart(cData, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) if err != nil { logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) } else { - err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(deliveryVersion.Version, &commonmodels.DeliveryChart{ - Name: cData.ChartData.ServiceName, - Version: cData.ChartData.Version, - Repo: args.ChartRepoName, - }) - if err != nil { - appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) - } + //err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(deliveryVersion.Version, &commonmodels.DeliveryChart{ + // Name: cData.ChartData.ServiceName, + // Version: cData.ChartData.Version, + // Repo: args.ChartRepoName, + //}) + //if err != nil { + // appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) + //} } }(chartData) } @@ -467,6 +825,14 @@ func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *z return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) } + // validate necessary params + if len(args.ChartRepoName) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("chart repo not appointed") + } + if len(args.ImageRegistryID) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("image registry not appointed") + } + chartDataMap, err := prepareChartData(args.ChartDatas, productInfo) if err != nil { return e.ErrCreateDeliveryVersion.AddErr(err) @@ -518,27 +884,28 @@ func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) if err != nil { - return errors.Wrapf(err, "failed to marshal arguments, versionName: %s err %s", deliveryVersion.Version) + return errors.Wrapf(err, "failed to marshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) } createArgs := new(DeliveryVersionChartData) err = json.Unmarshal(argsBytes, createArgs) if err != nil { - return errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err %s", deliveryVersion.Version) + return errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) } productInfoSnap := deliveryVersion.ProductEnvInfo + //deploys := make + // for charts has been successfully handled, download charts directly successCharts := sets.NewString() - for _, singleChart := range deliveryVersion.Charts { - // prepare chart data - _, err := downloadChart(projectName, versionName, singleChart) - if err != nil { - log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", singleChart.Name, err) - continue - } - successCharts.Insert(singleChart.Name) - } + //for _, singleChart := range deliveryVersion.Charts { + // _, err := downloadChart(projectName, versionName, singleChart) + // if err != nil { + // log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", singleChart.Name, err) + // continue + // } + // successCharts.Insert(singleChart.Name) + //} chartsToBeHandled := make([]*CreateHelmDeliveryVersionChartData, 0) for _, chartConfig := range createArgs.ChartDatas { @@ -596,8 +963,8 @@ func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]str return serviceNames.UnsortedList(), nil } -func downloadChart(productName, versionName string, chartInfo *commonmodels.DeliveryChart) (string, error) { - chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.Name, chartInfo.Version) +func downloadChart(productName, versionName string, chartInfo *commonmodels.DeliveryDistribute) (string, error) { + chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.ServiceName, chartInfo.Version) chartTGZFileParent := getChartTGZDir(productName, versionName) chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) if _, err := os.Stat(chartTGZFilePath); err == nil { @@ -606,9 +973,9 @@ func downloadChart(productName, versionName string, chartInfo *commonmodels.Deli return chartTGZFilePath, nil } - chartRepo, err := getChartRepoData(chartInfo.Repo) + chartRepo, err := getChartRepoData(chartInfo.ChartRepoName) if err != nil { - return "", fmt.Errorf("failed to query chart-repo info, repoName %s", chartInfo.Repo) + return "", fmt.Errorf("failed to query chart-repo info, repoName %s", chartInfo.ChartRepoName) } client, err := createChartRepoClient(chartRepo) @@ -652,19 +1019,26 @@ func downloadChart(productName, versionName string, chartInfo *commonmodels.Deli return chartTGZFilePath, nil } -func getChartConfigInfo(projectName, version string, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryChart, error) { - deliveryVersion, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ - ProductName: projectName, - Version: version, +func getChartDistributeInfo(projectName, version string, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { + //deliveryVersion, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ + // ProductName: projectName, + // Version: version, + //}, log) + + //if err != nil { + // return nil, err + //} + + distributes, _ := FindDeliveryDistribute(&commonrepo.DeliveryDistributeArgs{ + ReleaseID: "", + ServiceName: chartName, + DistributeType: config.Chart, }, log) - if err != nil { - return nil, err - } - var chartInfo *commonmodels.DeliveryChart - for _, singleChart := range deliveryVersion.Charts { - if singleChart.Name == chartName { - chartInfo = singleChart + var chartInfo *commonmodels.DeliveryDistribute + for _, distribute := range distributes { + if distribute.ServiceName == chartName { + chartInfo = distribute } } @@ -675,7 +1049,7 @@ func getChartConfigInfo(projectName, version string, chartName string, log *zap. } func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (string, error) { - chartInfo, err := getChartConfigInfo(projectName, version, chartName, log) + chartInfo, err := getChartDistributeInfo(projectName, version, chartName, log) if err != nil { return "", err } @@ -689,12 +1063,12 @@ func DownloadDeliveryChart(projectName, version string, chartName string, log *z func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { - chartInfo, err := getChartConfigInfo(projectName, versionName, chartName, log) + chartDistribute, err := getChartDistributeInfo(projectName, versionName, chartName, log) if err != nil { return "", err } dstDir := getChartExpandDir(projectName, versionName) - dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartInfo.Name, chartInfo.Version)) + dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartDistribute.ServiceName, chartDistribute.Version)) filePath, err := DownloadDeliveryChart(projectName, versionName, chartName, log) if err != nil { @@ -809,12 +1183,13 @@ func GetDeliveryChartFileContent(args *DeliveryChartFileContentArgs, log *zap.Su return string(fileContent), nil } -func ApplyDeliveryGlobalVariables(args *DeliveryVariablesApplyArgs, log *zap.SugaredLogger) (interface{}, error) { +func ApplyDeliveryGlobalVariables(args *DeliveryVariablesApplyArgs, logger *zap.SugaredLogger) (interface{}, error) { ret := new(DeliveryVariablesApplyArgs) for _, chartData := range args.ChartDatas { mergedYaml, err := yamlutil.Merge([][]byte{[]byte(chartData.ValuesYamlContent), []byte(args.GlobalVariables)}) if err != nil { - return nil, errors.Wrapf(err, "failed to merge global variable for service: %s", chartData.ServiceName) + logger.Errorf("failed to merge gobal variables for service: %s", chartData.ServiceName) + return nil, errors.Wrapf(err, "failed to merge global variables for service: %s", chartData.ServiceName) } ret.ChartDatas = append(ret.ChartDatas, &CreateHelmDeliveryVersionChartData{ ServiceName: chartData.ServiceName, diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 98503c44..20f88738 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -85,17 +85,6 @@ func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmR ret := make([]*HelmReleaseResp, 0, len(releases)) for _, release := range releases { - - for _, singleFile := range release.Chart.Files { - log.Infof("###### single file name is %s", singleFile.Name) - log.Infof("###### single file data is %s", string(singleFile.Data)) - } - - for _, singleFile := range release.Chart.Templates { - log.Infof("###### single file name is %s", singleFile.Name) - log.Infof("###### single file data is %s", string(singleFile.Data)) - } - ret = append(ret, &HelmReleaseResp{ ReleaseName: release.Name, ServiceName: util.ExtraServiceName(release.Name, prod.Namespace), diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index af9b8b8c..9a6b0032 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -272,7 +272,13 @@ const ( ) const ( - DeliveryVersionTypeChart = "HelmChart" + DeliveryVersionTypeChart = "HelmChart" + DeliveryVersionTypeK8SWorkflow = "K8SWorkflow" +) + +const ( + DeliveryDeployTypeImage = "image" + DeliveryDeployTypeChart = "chart" ) const ( -- Gitee From 80f53227092ada2928909f62a3a87ac0a3650e02 Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 25 Nov 2021 16:39:24 +0800 Subject: [PATCH 0794/2647] resource bundle api Signed-off-by: lou --- .../core/common/repository/mongodb/product.go | 3 + .../aslan/core/environment/handler/bundle.go | 47 ++++++ .../aslan/core/environment/service/bundle.go | 137 ++++++++++++++++ pkg/microservice/aslan/server/rest/router.go | 5 + .../policy/core/service/bundle/opa_bundle.go | 137 ++++++---------- pkg/tool/opa/bundle.go | 147 ++++++++++++++++++ 6 files changed, 389 insertions(+), 87 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/handler/bundle.go create mode 100644 pkg/microservice/aslan/core/environment/service/bundle.go create mode 100644 pkg/tool/opa/bundle.go diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 3611104d..9ea70ca7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -138,6 +138,9 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { var ret []*models.Product query := bson.M{} + if opt == nil { + opt = &ProductListOptions{} + } if opt.EnvName != "" { query["env_name"] = opt.EnvName } diff --git a/pkg/microservice/aslan/core/environment/handler/bundle.go b/pkg/microservice/aslan/core/environment/handler/bundle.go new file mode 100644 index 00000000..dfa3aac0 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/bundle.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "net/http" + "path/filepath" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" +) + +func DownloadBundle(c *gin.Context) { + if err := service.GenerateOPABundle(); err != nil { + c.String(http.StatusInternalServerError, "bundle generation failure, err: %s", err) + return + } + + revision := service.GetRevision() + matching := c.GetHeader("If-None-Match") + if revision != "" && revision == matching { + c.Status(http.StatusNotModified) + return + } + + c.Header("Content-Type", "application/gzip") + if revision != "" { + c.Header("Etag", revision) + } + c.File(filepath.Join(config.DataPath(), c.Param("name"))) +} diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go new file mode 100644 index 00000000..ed469da8 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -0,0 +1,137 @@ +package service + +import ( + "sort" + "strconv" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/tool/opa" +) + +const ( + resourcesPath = "resources/data.json" + + resourcesRoot = "resources" + + EnvironmentType = "Environment" +) + +var revision string + +type opaBundleMeta struct { + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` +} + +type opaMeta interface { + Meta() *opaBundleMeta +} + +type opaResources map[string]resources + +type resources []opaMeta + +func (o resources) Len() int { return len(o) } +func (o resources) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o resources) Less(i, j int) bool { + metaI := o[i].Meta() + metaJ := o[j].Meta() + if metaI.ProjectName == metaJ.ProjectName { + return metaI.ResourceID < metaJ.ResourceID + } + return metaI.ProjectName < metaJ.ProjectName +} + +func AppendOPAResources(res opaResources, resourceType string, objs []opaMeta) opaResources { + if res == nil { + res = make(map[string]resources) + } + + sort.Sort(resources(objs)) + res[resourceType] = objs + + return res +} + +func GenerateOPABundle() error { + log.Info("Generating OPA bundle") + defer log.Info("OPA bundle is generated") + + objs, err := GenerateEnvironmentBundle() + if err != nil { + log.Errorf("Failed to generate environment bundle, err: %s", err) + } + + res := AppendOPAResources(nil, EnvironmentType, objs) + + bundle := &opa.Bundle{ + Data: []*opa.DataSpec{ + {Data: res, Path: resourcesPath}, + }, + Roots: []string{resourcesRoot}, + } + + hash, err := bundle.Rehash() + if err != nil { + log.Errorf("Failed to calculate bundle hash, err: %s", err) + return err + } + revision = hash + + return bundle.Save(config.DataPath()) +} + +func GetRevision() string { + return revision +} + +type EnvironmentBundle struct { + *opaBundleMeta + Production string `json:"production"` +} + +func (b *EnvironmentBundle) Meta() *opaBundleMeta { + return b.opaBundleMeta +} + +func GenerateEnvironmentBundle() ([]opaMeta, error) { + var res []opaMeta + + envs, err := mongodb.NewProductColl().List(nil) + if err != nil { + log.Errorf("Failed to list envs, err: %s", err) + return nil, err + } + + clusterMap := make(map[string]*models.K8SCluster) + for _, env := range envs { + clusterID := env.ClusterID + production := false + if clusterID != "" { + cluster, ok := clusterMap[clusterID] + if !ok { + cluster, err = mongodb.NewK8SClusterColl().Get(clusterID) + if err != nil { + log.Warnf("Failed to get cluster %s in db, can not determine if env %s is a production env or not, err: %s", clusterID, env.EnvName, err) + continue + } + clusterMap[clusterID] = cluster + } + + production = cluster.Production + } + + res = append(res, &EnvironmentBundle{ + opaBundleMeta: &opaBundleMeta{ + ResourceID: env.EnvName, + ProjectName: env.ProductName, + }, + Production: strconv.FormatBool(production), + }) + } + + return res, nil +} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 960dd790..edb0648b 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -68,6 +68,11 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { router.GET("/api/kodespace/downloadUrl", commonhandler.GetToolDownloadURL) + bundles := router.Group("/api/bundles") + { + bundles.GET("/:name", environmenthandler.DownloadBundle) + } + for name, r := range map[string]injector{ "/api/project": new(projecthandler.Router), "/api/code": new(codehosthandler.Router), diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 84cdf4db..122a95c5 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -17,19 +17,14 @@ limitations under the License. package bundle import ( - "encoding/json" "net/http" - "path/filepath" "sort" - "github.com/27149chen/afero" - "github.com/google/uuid" - "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" "github.com/koderover/zadig/pkg/tool/log" - fsutil "github.com/koderover/zadig/pkg/util/fs" + "github.com/koderover/zadig/pkg/tool/opa" ) const ( @@ -37,7 +32,21 @@ const ( policyPath = "authz.rego" rolesPath = "roles/data.json" rolebindingsPath = "bindings/data.json" - exemptionPath = "exemptions/data.json" + exemptionsPath = "exemptions/data.json" + + policyRoot = "rbac" + rolesRoot = "roles" + rolebindingsRoot = "bindings" + exemptionsRoot = "exemptions" +) + +type expressionOperator string + +const ( + OperatorIn expressionOperator = "In" + OperatorNotIn expressionOperator = "NotIn" + OperatorExists expressionOperator = "Exists" + OperatorDoesNotExist expressionOperator = "DoesNotExist" ) const ( @@ -50,7 +59,7 @@ const ( var AllMethods = []string{MethodGet, MethodPost, MethodPut, MethodPatch, MethodDelete} -var cacheFS afero.Fs +var revision string type opaRoles struct { Roles roles `json:"roles"` @@ -60,11 +69,6 @@ type opaRoleBindings struct { RoleBindings roleBindings `json:"role_bindings"` } -type opaManifest struct { - Revision string `json:"revision"` - Roots []string `json:"roots"` -} - type role struct { Name string `json:"name"` Namespace string `json:"namespace"` @@ -72,8 +76,23 @@ type role struct { } type rule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` + Method string `json:"method"` + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType,omitempty"` + IDRegex string `json:"idRegex,omitempty"` + MatchAttributes []attribute `json:"matchAttributes,omitempty"` + MatchExpressions []expression `json:"matchExpressions,omitempty"` +} + +type attribute struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type expression struct { + Key string `json:"key"` + Values []string `json:"values"` + Operator expressionOperator `json:"operator"` } type roleBinding struct { @@ -91,52 +110,6 @@ type roleRef struct { Namespace string `json:"namespace"` } -type opaDataSpec struct { - data interface{} - path string -} - -type opaData []*opaDataSpec - -func (o *opaData) save() error { - var err error - - cacheFS = afero.NewMemMapFs() - for _, file := range *o { - var content []byte - switch c := file.data.(type) { - case []byte: - content = c - default: - content, err = json.MarshalIndent(c, "", " ") - if err != nil { - log.Errorf("Failed to marshal file %s, err: %s", file.path, err) - return err - } - } - - err = cacheFS.MkdirAll(filepath.Dir(file.path), 0755) - if err != nil { - log.Errorf("Failed to create path %s, err: %s", filepath.Dir(file.path), err) - return err - } - err = afero.WriteFile(cacheFS, file.path, content, 0644) - if err != nil { - log.Errorf("Failed to write file %s, err: %s", file.path, err) - return err - } - } - - tarball := "bundle.tar.gz" - path := filepath.Join(config.DataPath(), tarball) - if err = fsutil.Tar(afero.NewIOFS(cacheFS), path); err != nil { - log.Errorf("Failed to archive tarball %s, err: %s", path, err) - return err - } - - return nil -} - type rules []*rule func (o rules) Len() int { return len(o) } @@ -301,13 +274,6 @@ func generateOPAExemptionURLs(policies []*models.Policy) *exemptionURLs { return data } -func generateOPAManifest() *opaManifest { - return &opaManifest{ - Revision: uuid.New().String(), - Roots: []string{""}, - } -} - func generateOPAPolicy() []byte { return authz } @@ -329,29 +295,26 @@ func GenerateOPABundle() error { log.Errorf("Failed to list policies, err: %s", err) } - data := &opaData{ - {data: generateOPAManifest(), path: manifestPath}, - {data: generateOPAPolicy(), path: policyPath}, - {data: generateOPARoles(rs, ps), path: rolesPath}, - {data: generateOPARoleBindings(bs), path: rolebindingsPath}, - {data: generateOPAExemptionURLs(ps), path: exemptionPath}, + bundle := &opa.Bundle{ + Data: []*opa.DataSpec{ + {Data: generateOPAPolicy(), Path: policyPath}, + {Data: generateOPARoles(rs, ps), Path: rolesPath}, + {Data: generateOPARoleBindings(bs), Path: rolebindingsPath}, + {Data: generateOPAExemptionURLs(ps), Path: exemptionsPath}, + }, + Roots: []string{policyRoot, rolesRoot, rolebindingsRoot, exemptionsRoot}, } - return data.save() -} - -func GetRevision() string { - data, err := afero.ReadFile(cacheFS, manifestPath) + hash, err := bundle.Rehash() if err != nil { - log.Errorf("Failed to read manifest, err: %s", err) - return "" + log.Errorf("Failed to calculate bundle hash, err: %s", err) + return err } + revision = hash - mf := &opaManifest{} - if err = json.Unmarshal(data, mf); err != nil { - log.Errorf("Failed to Unmarshal manifest, err: %s", err) - return "" - } + return bundle.Save(config.DataPath()) +} - return mf.Revision +func GetRevision() string { + return revision } diff --git a/pkg/tool/opa/bundle.go b/pkg/tool/opa/bundle.go new file mode 100644 index 00000000..13cc8f41 --- /dev/null +++ b/pkg/tool/opa/bundle.go @@ -0,0 +1,147 @@ +package opa + +import ( + "crypto/sha256" + "encoding/json" + "fmt" + "path/filepath" + + "github.com/27149chen/afero" + + "github.com/koderover/zadig/pkg/tool/log" + fsutil "github.com/koderover/zadig/pkg/util/fs" +) + +const ( + manifestPath = ".manifest" +) + +type Manifest struct { + Revision string `json:"revision"` + Roots []string `json:"roots"` +} + +type DataSpec struct { + Data interface{} + Path string +} + +type serializedDataSpec struct { + Content []byte + Path string +} + +type Bundle struct { + Data []*DataSpec + Roots []string + hash string + serialized []*serializedDataSpec +} + +func (b *Bundle) Rehash() (string, error) { + if !b.validate() { + return "", fmt.Errorf("validation failed, data or roots is missing") + } + + var allContents []byte + var err error + b.serialized = nil + + for _, file := range b.Data { + var content []byte + switch c := file.Data.(type) { + case []byte: + content = c + default: + content, err = json.MarshalIndent(c, "", " ") + if err != nil { + log.Errorf("Failed to marshal file %s, err: %s", file.Path, err) + return "", err + } + } + + b.serialized = append(b.serialized, &serializedDataSpec{ + Content: content, + Path: file.Path, + }) + allContents = append(allContents, content...) + } + + h := sha256.New() + h.Write(allContents) + b.hash = fmt.Sprintf("%x", h.Sum(nil)) + + return b.hash, nil +} + +func (b *Bundle) Hash() (string, error) { + if b.hash != "" { + return b.hash, nil + } + + return b.Rehash() +} + +func (b *Bundle) Save(parentPath string) error { + mf, err := b.serializeOPAManifest() + if err != nil { + return err + } + b.serialized = append(b.serialized, mf) + + cacheFS := afero.NewMemMapFs() + for _, file := range b.serialized { + err = cacheFS.MkdirAll(filepath.Dir(file.Path), 0755) + if err != nil { + log.Errorf("Failed to create path %s, err: %s", filepath.Dir(file.Path), err) + return err + } + err = afero.WriteFile(cacheFS, file.Path, file.Content, 0644) + if err != nil { + log.Errorf("Failed to write file %s, err: %s", file.Path, err) + return err + } + } + + tarball := "bundle.tar.gz" + path := filepath.Join(parentPath, tarball) + if err = fsutil.Tar(afero.NewIOFS(cacheFS), path); err != nil { + log.Errorf("Failed to archive tarball %s, err: %s", path, err) + return err + } + + return nil +} + +func (b *Bundle) generateOPAManifest() (*Manifest, error) { + revision, err := b.Hash() + if err != nil { + return nil, err + } + + return &Manifest{ + Revision: revision, + Roots: b.Roots, + }, nil +} + +func (b *Bundle) serializeOPAManifest() (*serializedDataSpec, error) { + mf, err := b.generateOPAManifest() + if err != nil { + return nil, err + } + + content, err := json.MarshalIndent(mf, "", " ") + if err != nil { + return nil, err + } + + return &serializedDataSpec{ + Content: content, + Path: manifestPath, + }, nil +} + +func (b *Bundle) validate() bool { + return len(b.Data) > 0 && len(b.Roots) > 0 +} -- Gitee From 4a7a35b4b84d5c618c107a77e1adcb59de484eee Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 25 Nov 2021 20:43:51 +0800 Subject: [PATCH 0795/2647] update after review Signed-off-by: lou --- pkg/microservice/aslan/core/environment/handler/bundle.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/bundle.go b/pkg/microservice/aslan/core/environment/handler/bundle.go index dfa3aac0..775a120f 100644 --- a/pkg/microservice/aslan/core/environment/handler/bundle.go +++ b/pkg/microservice/aslan/core/environment/handler/bundle.go @@ -24,10 +24,12 @@ import ( "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + "github.com/koderover/zadig/pkg/tool/log" ) func DownloadBundle(c *gin.Context) { if err := service.GenerateOPABundle(); err != nil { + log.Errorf("Failed to generate OPA bundle, err: %s", err) c.String(http.StatusInternalServerError, "bundle generation failure, err: %s", err) return } -- Gitee From f17add1c47de007fc103cb9b0126a575d2d5751a Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 25 Nov 2021 20:53:08 +0800 Subject: [PATCH 0796/2647] update after review Signed-off-by: lou --- pkg/tool/opa/bundle.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tool/opa/bundle.go b/pkg/tool/opa/bundle.go index 13cc8f41..1177301d 100644 --- a/pkg/tool/opa/bundle.go +++ b/pkg/tool/opa/bundle.go @@ -85,6 +85,7 @@ func (b *Bundle) Hash() (string, error) { func (b *Bundle) Save(parentPath string) error { mf, err := b.serializeOPAManifest() if err != nil { + log.Errorf("Failed to serialize OPA manifest, err: %s", err) return err } b.serialized = append(b.serialized, mf) @@ -116,6 +117,7 @@ func (b *Bundle) Save(parentPath string) error { func (b *Bundle) generateOPAManifest() (*Manifest, error) { revision, err := b.Hash() if err != nil { + log.Errorf("Failed to get hash, err: %s", err) return nil, err } -- Gitee From a4766f9cc198cc3fa6657001e17937f47f4538cb Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 26 Nov 2021 14:28:35 +0800 Subject: [PATCH 0797/2647] update after review Signed-off-by: lou --- .../aslan/core/environment/service/bundle.go | 17 +++++++++++++++++ pkg/tool/opa/bundle.go | 16 ++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index ed469da8..5bbb568a 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( @@ -63,6 +79,7 @@ func GenerateOPABundle() error { objs, err := GenerateEnvironmentBundle() if err != nil { log.Errorf("Failed to generate environment bundle, err: %s", err) + return err } res := AppendOPAResources(nil, EnvironmentType, objs) diff --git a/pkg/tool/opa/bundle.go b/pkg/tool/opa/bundle.go index 1177301d..b04c8c04 100644 --- a/pkg/tool/opa/bundle.go +++ b/pkg/tool/opa/bundle.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package opa import ( -- Gitee From e78fad54d6a9dc64429df21d18f68620f5f3eecd Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 26 Nov 2021 16:38:56 +0800 Subject: [PATCH 0798/2647] refine envs api Signed-off-by: lou --- .../core/common/repository/mongodb/product.go | 3 + .../core/environment/handler/environment.go | 235 ++++++++---------- .../aslan/core/environment/handler/router.go | 57 ++--- .../aslan/core/environment/handler/service.go | 59 ++--- .../core/environment/service/environment.go | 80 +++--- .../policy/core/repository/models/policy.go | 12 +- .../bundle/resource_endpoint_mapping.go | 28 +-- .../core/service/policy_registration.go | 25 +- pkg/setting/consts.go | 7 + pkg/shared/client/policy/policy.go | 12 +- pkg/shared/handler/base.go | 8 + 11 files changed, 277 insertions(+), 249 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 9ea70ca7..ca490b5b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -49,6 +49,7 @@ type ProductListOptions struct { ExcludeSource string Source string InProjects []string + InEnvs []string } type projectEnvs struct { @@ -143,6 +144,8 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { } if opt.EnvName != "" { query["env_name"] = opt.EnvName + } else if len(opt.InEnvs) > 0 { + query["env_name"] = bson.M{"$in": opt.InEnvs} } if opt.Name != "" { query["product_name"] = opt.Name diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 7e8edcba..5e631774 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -60,37 +60,48 @@ func ListProducts(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListProducts(c.Query("projectName"), ctx.UserName, ctx.Logger) -} -// GetProductStatus List product status -func GetProductStatus(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() + envNames, found := internalhandler.GetResourcesInHeader(c) + if found && len(envNames) == 0 { + ctx.Resp = []*service.ProductResp{} + return + } - ctx.Resp, ctx.Err = service.GetProductStatus(c.Param("productName"), ctx.Logger) + switch c.Query(setting.Subresource) { + case setting.StatusSubresource: + ctx.Resp, ctx.Err = service.GetProductStatus(projectName, ctx.Logger) + return + case setting.IngressSubresource: + ctx.Resp, ctx.Err = service.GetProductIngress(projectName, ctx.Logger) + return + } + + ctx.Resp, ctx.Err = service.ListProducts(projectName, envNames, ctx.Logger) } -func AutoCreateProduct(c *gin.Context) { +func UpdateMultiProducts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp = service.AutoCreateProduct(c.Param("productName"), c.Query("envType"), ctx.RequestID, ctx.Logger) -} + if c.Query("helm") == "true" { + updateMultiHelmEnv(c, ctx) + return + } -func AutoUpdateProduct(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() + if c.Query("auto") != "true" { + ctx.Err = e.ErrInvalidParam.AddDesc("auto=true is required") + return + } args := new(UpdateEnvs) data, err := c.GetRawData() if err != nil { - log.Errorf("AutoUpdateProduct c.GetRawData() err : %v", err) + log.Errorf("UpdateMultiProducts c.GetRawData() err : %v", err) } if err = json.Unmarshal(data, args); err != nil { - log.Errorf("AutoUpdateProduct json.Unmarshal err : %v", err) + log.Errorf("UpdateMultiProducts json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "自动更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -100,20 +111,16 @@ func AutoUpdateProduct(c *gin.Context) { } force, _ := strconv.ParseBool(c.Query("force")) - ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Param("productName"), ctx.RequestID, force, ctx.Logger) + ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Query("projectName"), ctx.RequestID, force, ctx.Logger) } -func CreateHelmProduct(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - if c.Param("productName") == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") +func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - productName := c.Param("productName") - createArgs := make([]*service.CreateHelmProductArg, 0) data, err := c.GetRawData() if err != nil { @@ -132,14 +139,14 @@ func CreateHelmProduct(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("envName is empty") return } - arg.ProductName = productName + arg.ProductName = projectName envNameList = append(envNameList, arg.EnvName) } - internalhandler.InsertOperationLog(c, ctx.UserName, productName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( - productName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, + projectName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, ) } @@ -147,6 +154,15 @@ func CreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + if c.Query("auto") == "true" { + ctx.Resp = service.AutoCreateProduct(c.Query("projectName"), c.Query("envType"), ctx.RequestID, ctx.Logger) + return + } + if c.Query("helm") == "true" { + createHelmProduct(c, ctx) + return + } + args := new(commonmodels.Product) data, err := c.GetRawData() if err != nil { @@ -178,8 +194,8 @@ func UpdateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") + envName := c.Param("name") + projectName := c.Query("projectName") args := new(commonmodels.Product) data, err := c.GetRawData() @@ -191,7 +207,7 @@ func UpdateProduct(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境", envName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -201,9 +217,9 @@ func UpdateProduct(c *gin.Context) { force, _ := strconv.ParseBool(c.Query("force")) // update product asynchronously - ctx.Err = service.UpdateProductV2(envName, productName, ctx.UserName, ctx.RequestID, force, args.Vars, ctx.Logger) + ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, force, args.Vars, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product %s %s: %v", envName, productName, ctx.Err) + ctx.Logger.Errorf("failed to update product %s %s: %v", envName, projectName, ctx.Err) } } @@ -211,11 +227,11 @@ func UpdateProductRecycleDay(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") + envName := c.Param("name") + projectName := c.Query("projectName") recycleDayStr := c.Query("recycleDay") - internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境-环境回收", envName, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境-环境回收", envName, "", ctx.Logger) var ( recycleDay int @@ -231,16 +247,17 @@ func UpdateProductRecycleDay(c *gin.Context) { return } - ctx.Err = service.UpdateProductRecycleDay(envName, productName, recycleDay) + ctx.Err = service.UpdateProductRecycleDay(envName, projectName, recycleDay) } func EstimatedValues(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Param("productName") - if productName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + envName := c.Param("name") + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be empty!") return } @@ -256,20 +273,20 @@ func EstimatedValues(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GeneEstimatedValues(productName, c.Query("envName"), serviceName, c.Query("scene"), c.Query("format"), arg, ctx.Logger) + ctx.Resp, ctx.Err = service.GeneEstimatedValues(projectName, envName, serviceName, c.Query("scene"), c.Query("format"), arg, ctx.Logger) } func UpdateHelmProductRenderset(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Param("productName") - if productName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be empty!") return } - envName := c.Query("envName") + envName := c.Param("name") if envName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") return @@ -284,20 +301,18 @@ func UpdateHelmProductRenderset(c *gin.Context) { log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "更新环境变量", "", string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "更新环境变量", "", string(data), ctx.Logger) - ctx.Err = service.UpdateHelmProductRenderset(productName, envName, ctx.UserName, ctx.RequestID, arg, ctx.Logger) + ctx.Err = service.UpdateHelmProductRenderset(projectName, envName, ctx.UserName, ctx.RequestID, arg, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, productName, ctx.Err) + ctx.Logger.Errorf("failed to update product Variable %s %s: %v", envName, projectName, ctx.Err) } } -func UpdateMultiHelmEnv(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - if c.Param("productName") == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can not be null!") +func updateMultiHelmEnv(c *gin.Context, ctx *internalhandler.Context) { + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } @@ -309,9 +324,9 @@ func UpdateMultiHelmEnv(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateProduct json.Unmarshal err : %v", err) } - args.ProductName = c.Param("productName") + args.ProductName = projectName - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) ctx.Resp, ctx.Err = service.UpdateMultipleHelmEnv( ctx.RequestID, args, ctx.Logger, @@ -322,40 +337,32 @@ func GetProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") + projectName := c.Query("projectName") + name := c.Param("name") - ctx.Resp, ctx.Err = service.GetProduct(ctx.UserName, envName, productName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetProduct(ctx.UserName, name, projectName, ctx.Logger) } func GetProductInfo(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") - - ctx.Resp, ctx.Err = service.GetProductInfo(ctx.UserName, envName, productName, ctx.Logger) -} - -func GetProductIngress(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Param("name") + projectName := c.Query("projectName") - productName := c.Param("productName") - ctx.Resp, ctx.Err = service.GetProductIngress(ctx.UserName, productName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetProductInfo(ctx.UserName, envName, projectName, ctx.Logger) } func ListRenderCharts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") + envName := c.Param("name") + projectName := c.Query("projectName") - ctx.Resp, ctx.Err = service.ListRenderCharts(productName, envName, ctx.Logger) + ctx.Resp, ctx.Err = service.ListRenderCharts(projectName, envName, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to listRenderCharts %s %s: %v", envName, productName, ctx.Err) + ctx.Logger.Errorf("failed to listRenderCharts %s %s: %v", envName, projectName, ctx.Err) } } @@ -363,12 +370,12 @@ func GetHelmChartVersions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") + envName := c.Param("Name") + projectName := c.Query("projectName") - ctx.Resp, ctx.Err = service.GetHelmChartVersions(productName, envName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetHelmChartVersions(projectName, envName, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to get helmVersions %s %s: %v", envName, productName, ctx.Err) + ctx.Logger.Errorf("failed to get helmVersions %s %s: %v", envName, projectName, ctx.Err) } } @@ -376,73 +383,40 @@ func GetEstimatedRenderCharts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Query("projectName") - if productName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be empty!") return } - envName := c.Query("envName") + envName := c.Param("name") if envName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") return } - ctx.Resp, ctx.Err = service.GetEstimatedRenderCharts(productName, envName, c.Query("serviceName"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetEstimatedRenderCharts(projectName, envName, c.Query("serviceName"), ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to get estimatedRenderCharts %s %s: %v", envName, productName, ctx.Err) + ctx.Logger.Errorf("failed to get estimatedRenderCharts %s %s: %v", envName, projectName, ctx.Err) } } func DeleteProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "删除", "集成环境", envName, "", ctx.Logger) - ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, c.Param("productName"), ctx.RequestID, ctx.Logger) -} - -func EnvShare(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Param("productName") - args := new(service.ProductParams) - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateProduct c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateProduct json.Unmarshal err : %v", err) - } - internalhandler.InsertOperationLog(c, ctx.UserName, productName, "更新", "集成环境-环境授权", args.EnvName, string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if productName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("productName can't be empty!") - return - } - - if err := c.BindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - - if args.EnvName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") - return - } - - //ctx.Err = service.UpdateProductPublic(productName, args, ctx.Logger) + envName := c.Param("name") + projectName := c.Query("projectName") + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "集成环境", envName, "", ctx.Logger) + ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) } func ListGroups(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - productName := c.Param("productName") - envName := c.Query("envName") + envName := c.Param("name") + projectName := c.Query("projectName") serviceName := c.Query("serviceName") perPageStr := c.Query("perPage") pageStr := c.Query("page") @@ -472,7 +446,7 @@ func ListGroups(c *gin.Context) { } } - ctx.Resp, count, ctx.Err = service.ListGroups(serviceName, envName, productName, perPage, page, ctx.Logger) + ctx.Resp, count, ctx.Err = service.ListGroups(serviceName, envName, projectName, perPage, page, ctx.Logger) c.Writer.Header().Set("X-Total", strconv.Itoa(count)) } @@ -538,25 +512,26 @@ func ListWorkloads(c *gin.Context) { c.Writer.Header().Set("X-Total", strconv.Itoa(count)) } -// TODO: envName must be a param, while productName can be a query type workloadQueryArgs struct { - Env string `json:"env" form:"env"` - PerPage int `json:"perPage" form:"perPage,default=10"` - Page int `json:"page" form:"page,default=1"` - Filter string `json:"filter" form:"filter"` + ProjectName string `json:"projectName" form:"projectName"` + PerPage int `json:"perPage" form:"perPage,default=10"` + Page int `json:"page" form:"page,default=1"` + Filter string `json:"filter" form:"filter"` } func ListWorkloadsInEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Param("name") + args := &workloadQueryArgs{} if err := c.ShouldBindQuery(args); err != nil { ctx.Err = err return } - count, services, err := commonservice.ListWorkloadsInEnv(args.Env, c.Param("productName"), args.Filter, args.PerPage, args.Page, ctx.Logger) + count, services, err := commonservice.ListWorkloadsInEnv(envName, args.ProjectName, args.Filter, args.PerPage, args.Page, ctx.Logger) ctx.Resp = &NamespaceResource{ Services: services, } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index bc13c84c..49239285 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -94,42 +94,29 @@ func (*Router) Inject(router *gin.RouterGroup) { environments := router.Group("environments") { environments.GET("", ListProducts) - environments.GET("/:productName/status", GetProductStatus) - environments.POST("/:productName/auto", AutoCreateProduct) - - environments.PUT("/:productName/autoUpdate", gin2.UpdateOperationLogStatus, AutoUpdateProduct) + environments.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProduct) + environments.PUT("", gin2.UpdateOperationLogStatus, UpdateMultiProducts) environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) - - environments.POST("/:productName/helm", gin2.UpdateOperationLogStatus, CreateHelmProduct) - environments.PUT("/:productName/multiHelmEnv", gin2.UpdateOperationLogStatus, UpdateMultiHelmEnv) - - environments.POST("/:productName", gin2.UpdateOperationLogStatus, UpdateProduct) - environments.PUT("/:productName/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) - - environments.POST("/:productName/estimated-values", EstimatedValues) - environments.PUT("/:productName/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) - - environments.GET("/:productName/helmChartVersions", GetHelmChartVersions) - //environments.PUT("/:productName", gin2.UpdateOperationLogStatus, EnvShare) - - environments.GET("/:productName", GetProduct) - environments.GET("/:productName/productInfo", GetProductInfo) - environments.GET("/:productName/ingressInfo", GetProductIngress) - environments.GET("/:productName/helmRenderCharts", ListRenderCharts) - environments.DELETE("/:productName", gin2.UpdateOperationLogStatus, DeleteProduct) - - environments.GET("/:productName/groups", ListGroups) - environments.GET("/:productName/workloads", ListWorkloadsInEnv) - - environments.GET("/:productName/services/:serviceName", GetService) - environments.PUT("/:productName/services/:serviceName/:serviceType", gin2.UpdateOperationLogStatus, UpdateService) - environments.POST("/:productName/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) - environments.POST("/:productName/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) - environments.POST("/:productName/services/:serviceName/scale/:number", gin2.UpdateOperationLogStatus, ScaleService) - environments.POST("/:productName/services/:serviceName/scaleNew/:number", gin2.UpdateOperationLogStatus, ScaleNewService) - environments.GET("/:productName/services/:serviceName/containers/:container/namespaces/:namespace", GetServiceContainer) - - environments.GET("/estimated-renderchart", GetEstimatedRenderCharts) + environments.GET("/:name", GetProduct) + environments.PUT("/:name/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) + environments.POST("/:name/estimated-values", EstimatedValues) + environments.PUT("/:name/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) + environments.GET("/:name/helmChartVersions", GetHelmChartVersions) + environments.GET("/:name/productInfo", GetProductInfo) + environments.GET("/:name/helmRenderCharts", ListRenderCharts) + environments.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProduct) + environments.GET("/:name/groups", ListGroups) + environments.GET("/:name/workloads", ListWorkloadsInEnv) + + environments.GET("/:name/services/:serviceName", GetService) + environments.PUT("/:name/services/:serviceName", gin2.UpdateOperationLogStatus, UpdateService) + environments.POST("/:name/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) + environments.POST("/:name/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) + environments.POST("/:name/services/:serviceName/scale", gin2.UpdateOperationLogStatus, ScaleService) + environments.POST("/:name/services/:serviceName/scaleNew", gin2.UpdateOperationLogStatus, ScaleNewService) + environments.GET("/:name/services/:serviceName/containers/:container", GetServiceContainer) + + environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index 3405c7c0..00ec97b0 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -31,12 +31,12 @@ import ( func GetService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") + envName := c.Param("name") + projectName := c.Query("projectName") serviceName := c.Param("serviceName") workLoadType := c.Query("workLoadType") - ctx.Resp, ctx.Err = service.GetService(envName, productName, serviceName, workLoadType, ctx.Logger) + ctx.Resp, ctx.Err = service.GetService(envName, projectName, serviceName, workLoadType, ctx.Logger) } func RestartService(c *gin.Context) { @@ -44,19 +44,22 @@ func RestartService(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := &service.SvcOptArgs{ - EnvName: c.Query("envName"), - ProductName: c.Param("productName"), + EnvName: c.Param("name"), + ProductName: c.Query("projectName"), ServiceName: c.Param("serviceName"), } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "重启", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Param("name"), c.Param("serviceName")), "", ctx.Logger) ctx.Err = service.RestartService(args.EnvName, args, ctx.Logger) } func UpdateService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "更新", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) + + envName := c.Param("name") + projectName := c.Query("projectName") + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", envName, c.Param("serviceName")), "", ctx.Logger) svcRev := new(service.SvcRevision) if err := c.BindJSON(svcRev); err != nil { @@ -65,10 +68,10 @@ func UpdateService(c *gin.Context) { } args := &service.SvcOptArgs{ - EnvName: c.Query("envName"), - ProductName: c.Param("productName"), + EnvName: envName, + ProductName: projectName, ServiceName: c.Param("serviceName"), - ServiceType: c.Param("serviceType"), + ServiceType: c.Query("serviceType"), ServiceRev: svcRev, UpdateBy: ctx.UserName, } @@ -81,8 +84,8 @@ func RestartNewService(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := &service.RestartScaleArgs{ - EnvName: c.Query("envName"), - ProductName: c.Param("productName"), + EnvName: c.Param("name"), + ProductName: c.Query("projectName"), ServiceName: c.Param("serviceName"), Type: c.Query("type"), Name: c.Query("name"), @@ -90,7 +93,7 @@ func RestartNewService(c *gin.Context) { internalhandler.InsertOperationLog( c, ctx.UserName, - c.Param("productName"), + c.Query("projectName"), "重启", "集成环境-服务", fmt.Sprintf( @@ -109,21 +112,21 @@ func ScaleNewService(c *gin.Context) { args := new(service.ScaleArgs) args.Type = setting.Deployment - productName := c.Param("productName") + projectName := c.Query("projectName") serviceName := c.Param("serviceName") - envName := c.Query("envName") + envName := c.Param("name") resourceType := c.Query("type") name := c.Query("name") internalhandler.InsertOperationLog( c, ctx.UserName, - c.Param("productName"), + projectName, "伸缩", "集成环境-服务", - fmt.Sprintf("环境名称:%s,%s:%s", args.EnvName, args.Type, args.Name), + fmt.Sprintf("环境名称:%s,%s:%s", envName, resourceType, name), "", ctx.Logger) - number, err := strconv.Atoi(c.Param("number")) + number, err := strconv.Atoi(c.Query("number")) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid number format") return @@ -131,7 +134,7 @@ func ScaleNewService(c *gin.Context) { ctx.Err = service.Scale(&service.ScaleArgs{ Type: resourceType, - ProductName: productName, + ProductName: projectName, EnvName: envName, ServiceName: serviceName, Name: name, @@ -142,21 +145,22 @@ func ScaleNewService(c *gin.Context) { func ScaleService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("productName"), "伸缩", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Query("envName"), c.Param("serviceName")), "", ctx.Logger) - number, err := strconv.Atoi(c.Param("number")) + envName := c.Param("name") + projectName := c.Query("projectName") + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "伸缩", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", envName, c.Param("serviceName")), "", ctx.Logger) + + number, err := strconv.Atoi(c.Query("number")) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid number format") return } - productName := c.Param("productName") serviceName := c.Param("serviceName") - envName := c.Query("envName") ctx.Err = service.ScaleService( envName, - productName, + projectName, serviceName, number, ctx.Logger, @@ -167,11 +171,10 @@ func GetServiceContainer(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - //namespace := c.Param("namespace") - productName := c.Param("productName") + envName := c.Param("name") + projectName := c.Query("projectName") serviceName := c.Param("serviceName") container := c.Param("container") - ctx.Err = service.GetServiceContainer(envName, productName, serviceName, container, ctx.Logger) + ctx.Err = service.GetServiceContainer(envName, projectName, serviceName, container, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 57de424a..0a24a157 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -171,41 +171,55 @@ func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, return envStatusSlice, err } -func ListProducts(productNameParam string, userName string, log *zap.SugaredLogger) (resp []*ProductResp, err error) { - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productNameParam, IsSortByProductName: true}) +func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*ProductResp, error) { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) if err != nil { - log.Errorf("[%s] Collections.Product.List error: %v", userName, err) - return resp, e.ErrListEnvs.AddDesc(err.Error()) + log.Errorf("Failed to list envs, err: %s", err) + return nil, e.ErrListEnvs.AddDesc(err.Error()) } - for _, prod := range products { - product := &ProductResp{ - ID: prod.ID.Hex(), - ProductName: prod.ProductName, - EnvName: prod.EnvName, - Namespace: prod.Namespace, - Vars: prod.Vars[:], - IsPublic: prod.IsPublic, - ClusterID: prod.ClusterID, - UpdateTime: prod.UpdateTime, - UpdateBy: prod.UpdateBy, - RecycleDay: prod.RecycleDay, - Render: prod.Render, - Source: prod.Source, - } - if product.ClusterID != "" { - cluster, _ := commonrepo.NewK8SClusterColl().Get(product.ClusterID) - if cluster != nil && cluster.Production { - product.IsProd = true - } - } - err = FillProductVars(products, log) - if err != nil { - return resp, err - } - resp = append(resp, product) + err = FillProductVars(envs, log) + if err != nil { + return nil, err } - return resp, nil + + var res []*ProductResp + clusterMap := make(map[string]*commonmodels.K8SCluster) + for _, env := range envs { + clusterID := env.ClusterID + production := false + if clusterID != "" { + cluster, ok := clusterMap[clusterID] + if !ok { + cluster, err = commonrepo.NewK8SClusterColl().Get(clusterID) + if err != nil { + log.Warnf("Failed to get cluster %s in db, can not determine if env %s is a production env or not, err: %s", clusterID, env.EnvName, err) + continue + } + clusterMap[clusterID] = cluster + } + + production = cluster.Production + } + + res = append(res, &ProductResp{ + ID: env.ID.Hex(), + ProductName: env.ProductName, + EnvName: env.EnvName, + Namespace: env.Namespace, + Vars: env.Vars[:], + IsPublic: env.IsPublic, + ClusterID: env.ClusterID, + UpdateTime: env.UpdateTime, + UpdateBy: env.UpdateBy, + RecycleDay: env.RecycleDay, + Render: env.Render, + Source: env.Source, + IsProd: production, + }) + } + + return res, nil } func FillProductVars(products []*commonmodels.Product, log *zap.SugaredLogger) error { @@ -1172,11 +1186,11 @@ func GetProductInfo(username, envName, productName string, log *zap.SugaredLogge return prod, nil } -func GetProductIngress(username, productName string, log *zap.SugaredLogger) ([]*ProductIngressInfo, error) { +func GetProductIngress(productName string, log *zap.SugaredLogger) ([]*ProductIngressInfo, error) { productIngressInfos := make([]*ProductIngressInfo, 0) products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) if err != nil { - log.Errorf("[%s] Collections.Product.List error: %v", username, err) + log.Errorf("Failed to list envs, err: %s", err) return productIngressInfos, e.ErrListEnvs.AddDesc(err.Error()) } for _, prod := range products { diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index 6797f8fe..8b333f60 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -31,8 +31,16 @@ type PolicyRule struct { } type ActionRule struct { - Method string `bson:"method" json:"method"` - Endpoint string `bson:"endpoint" json:"endpoint"` + Method string `bson:"method" json:"method"` + Endpoint string `bson:"endpoint" json:"endpoint"` + ResourceType string `bson:"resource_type,omitempty" json:"resourceType,omitempty"` + IDRegex string `bson:"id_regex,omitempty" json:"idRegex,omitempty"` + MatchAttributes []Attribute `bson:"match_attributes,omitempty" json:"matchAttributes,omitempty"` +} + +type Attribute struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` } func (Policy) TableName() string { diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index def928aa..fd1a9a0c 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -29,21 +29,6 @@ func (m resourceActionMappings) GetRules(resource string, actions []string) []*r return res } -//var resourceActionMappings = map[string]map[string][]*rule{ -// "Workflow": workflowMapping, -//} -// -//var workflowMapping = map[string][]*rule{ -// ActionCreate: { -// {Method: MethodPost, Endpoint: "/api/aslan/workflow/workflow"}, -// {Method: MethodPost, Endpoint: "/api/aslan/workflow/v2/pipelines"}, -// }, -// ActionDelete: { -// {Method: MethodPost, Endpoint: "/api/aslan/workflow/workflow/?*"}, -// {Method: MethodPost, Endpoint: "/api/aslan/workflow/v2/pipelines/?*"}, -// }, -//} - func getResourceActionMappings(policies []*models.Policy) resourceActionMappings { data := make(resourceActionMappings) for _, p := range policies { @@ -53,7 +38,18 @@ func getResourceActionMappings(policies []*models.Policy) resourceActionMappings for _, r := range p.Rules { for _, ar := range r.Rules { - data[p.Resource][r.Action] = append(data[p.Resource][r.Action], &rule{Method: ar.Method, Endpoint: ar.Endpoint}) + var as []attribute + for _, a := range ar.MatchAttributes { + as = append(as, attribute{Key: a.Key, Value: a.Value}) + } + + data[p.Resource][r.Action] = append(data[p.Resource][r.Action], &rule{ + Method: ar.Method, + Endpoint: ar.Endpoint, + ResourceType: ar.ResourceType, + IDRegex: ar.IDRegex, + MatchAttributes: as, + }) } } } diff --git a/pkg/microservice/policy/core/service/policy_registration.go b/pkg/microservice/policy/core/service/policy_registration.go index f940b115..64a0cb23 100644 --- a/pkg/microservice/policy/core/service/policy_registration.go +++ b/pkg/microservice/policy/core/service/policy_registration.go @@ -22,8 +22,16 @@ type PolicyRule struct { } type ActionRule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` + Method string `json:"method"` + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType,omitempty"` + IDRegex string `json:"idRegex,omitempty"` + MatchAttributes []attribute `json:"matchAttributes,omitempty"` +} + +type attribute struct { + Key string `json:"key"` + Value string `json:"value"` } type PolicyDefinition struct { @@ -53,7 +61,18 @@ func CreateOrUpdatePolicyRegistration(p *Policy, _ *zap.SugaredLogger) error { Description: r.Description, } for _, ar := range r.Rules { - rule.Rules = append(rule.Rules, &models.ActionRule{Method: ar.Method, Endpoint: ar.Endpoint}) + var as []models.Attribute + for _, a := range ar.MatchAttributes { + as = append(as, models.Attribute{Key: a.Key, Value: a.Value}) + } + + rule.Rules = append(rule.Rules, &models.ActionRule{ + Method: ar.Method, + Endpoint: ar.Endpoint, + ResourceType: ar.ResourceType, + IDRegex: ar.IDRegex, + MatchAttributes: as, + }) } obj.Rules = append(obj.Rules, rule) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index c8c77099..fd65a305 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -543,3 +543,10 @@ const ( // ModernWorkflowType 自由编排工作流 const ModernWorkflowType = "ModernWorkflow" + +const ( + Subresource = "subresource" + StatusSubresource = "status" + IngressSubresource = "ingress" + ResourcesHeader = "Resources" +) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index ae27d619..66a4736c 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -21,8 +21,16 @@ type Rule struct { } type ActionRule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` + Method string `json:"method"` + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType,omitempty"` + IDRegex string `json:"idRegex,omitempty"` + MatchAttributes []attribute `json:"matchAttributes,omitempty"` +} + +type attribute struct { + Key string `json:"key"` + Value string `json:"value"` } type RoleBinding struct { diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index 001400a6..e7617496 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -68,6 +68,14 @@ func NewContext(c *gin.Context) *Context { } } +func GetResourcesInHeader(c *gin.Context) ([]string, bool) { + _, ok := c.Request.Header[setting.ResourcesHeader] + if !ok { + return nil, false + } + return strings.Split(c.GetHeader(setting.ResourcesHeader), ","), true +} + func getUserFromJWT(token string) (jwtClaims, error) { cs := jwtClaims{} -- Gitee From aba139dbe27aebb2856b9daeab04a032104c9a07 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 26 Nov 2021 17:40:23 +0800 Subject: [PATCH 0799/2647] Workflow and test tasks support resource scheduling configuration Signed-off-by: liu deyi --- .../core/common/repository/models/build.go | 4 ++- .../core/common/repository/models/testing.go | 4 ++- .../aslan/core/environment/handler/kube.go | 7 ++++ .../aslan/core/environment/handler/router.go | 1 + .../aslan/core/environment/service/kube.go | 33 +++++++++++++++++++ pkg/shared/kube/resource/node.go | 6 ++++ 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 pkg/shared/kube/resource/node.go diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index c5288acc..123fc7d9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -65,7 +65,9 @@ type PreBuild struct { // Parameters Parameters []*Parameter `bson:"parameters,omitempty" json:"parameters"` // UploadPkg uploads package to s3 - UploadPkg bool `bson:"upload_pkg" json:"upload_pkg"` + UploadPkg bool `bson:"upload_pkg" json:"upload_pkg"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + Namespace string `bson:"namespace" json:"namespace"` } type BuildObj struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index c42a6deb..6b802cf3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -78,7 +78,9 @@ type PreTest struct { // Envs stores user defined env key val for build Envs []*KeyVal `bson:"envs,omitempty" json:"envs"` // EnableProxy - EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + Namespace string `bson:"namespace" json:"namespace"` } func (Testing) TableName() string { diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index 1e4c0c48..215d8237 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -97,3 +97,10 @@ func ListPodEvents(c *gin.Context) { ctx.Resp, ctx.Err = service.ListPodEvents(envName, productName, podName, ctx.Logger) } + +func ListNodes(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListAvailableNodes(c.Query("clusterId"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index bc13c84c..d2eecee1 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -86,6 +86,7 @@ func (*Router) Inject(router *gin.RouterGroup) { kube.DELETE("/pods/:podName", gin2.UpdateOperationLogStatus, DeletePod) kube.GET("/pods/:podName/events", ListPodEvents) kube.GET("/workloads", ListWorkloads) + kube.GET("/nodes", ListNodes) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index 7c8e0fa3..0e403db3 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -223,3 +223,36 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { LastUpdateTime: t, } } + +func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) ([]*resource.Node, error) { + resp := make([]*resource.Node, 0) + kubeClient, err := kube.GetKubeClient(clusterID) + if err != nil { + log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) + return resp, err + } + + nodes, err := getter.ListNodes(kubeClient) + if err != nil { + log.Errorf("ListNodes err:%s", err) + if apierrors.IsForbidden(err) { + return resp, nil + } + return resp, err + } + + for _, node := range nodes { + labelM := node.Labels + labels := make([]string, 0, len(labelM)) + for key, value := range labelM { + labels = append(labels, fmt.Sprintf("%s:%s", key, value)) + } + nodeResource := &resource.Node{ + Status: string(node.Status.Phase), + Labels: labels, + } + resp = append(resp, nodeResource) + } + + return resp, nil +} diff --git a/pkg/shared/kube/resource/node.go b/pkg/shared/kube/resource/node.go new file mode 100644 index 00000000..c54f3d36 --- /dev/null +++ b/pkg/shared/kube/resource/node.go @@ -0,0 +1,6 @@ +package resource + +type Node struct { + Labels []string `json:"node_labels"` + Status string `json:"node_status"` +} -- Gitee From 7d77438a3e325c4e0416b3e8fdf8c02bfd48c940 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 26 Nov 2021 18:46:11 +0800 Subject: [PATCH 0800/2647] optimize get node info Signed-off-by: liu deyi --- .../aslan/core/environment/service/kube.go | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index 0e403db3..684b534d 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -20,6 +20,8 @@ import ( "fmt" "time" + corev1 "k8s.io/api/core/v1" + "go.uber.org/zap" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -242,17 +244,32 @@ func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) ([]*resource.N } for _, node := range nodes { - labelM := node.Labels - labels := make([]string, 0, len(labelM)) - for key, value := range labelM { - labels = append(labels, fmt.Sprintf("%s:%s", key, value)) - } nodeResource := &resource.Node{ - Status: string(node.Status.Phase), - Labels: labels, + Status: nodeReady(node), + Labels: nodeLabel(node), } resp = append(resp, nodeResource) } return resp, nil } + +// Ready indicates that the node is ready for traffic. +func nodeReady(node *corev1.Node) string { + cs := node.Status.Conditions + for _, c := range cs { + if c.Type == corev1.NodeReady && c.Status == corev1.ConditionTrue { + return "ready" + } + } + return "not ready" +} + +func nodeLabel(node *corev1.Node) []string { + labels := make([]string, 0, len(node.Labels)) + labelM := node.Labels + for key, value := range labelM { + labels = append(labels, fmt.Sprintf("%s:%s", key, value)) + } + return labels +} -- Gitee From 025dcf257dfe2816f5f4acc953d78e4e7535acc9 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 26 Nov 2021 20:51:15 +0800 Subject: [PATCH 0801/2647] update after review Signed-off-by: lou --- .../aslan/core/environment/service/bundle.go | 23 +++++++++-------- .../core/environment/service/environment.go | 25 ++++++++++--------- .../core/service/bundle/rego/authz.rego | 2 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 5bbb568a..91b2a766 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -124,20 +124,21 @@ func GenerateEnvironmentBundle() ([]opaMeta, error) { } clusterMap := make(map[string]*models.K8SCluster) + clusters, err := mongodb.NewK8SClusterColl().Find("") + if err != nil { + log.Errorf("Failed to list clusters in db, err: %s", err) + return nil, err + } + + for _, cls := range clusters { + clusterMap[cls.ID.Hex()] = cls + } + for _, env := range envs { clusterID := env.ClusterID production := false - if clusterID != "" { - cluster, ok := clusterMap[clusterID] - if !ok { - cluster, err = mongodb.NewK8SClusterColl().Get(clusterID) - if err != nil { - log.Warnf("Failed to get cluster %s in db, can not determine if env %s is a production env or not, err: %s", clusterID, env.EnvName, err) - continue - } - clusterMap[clusterID] = cluster - } - + cluster, ok := clusterMap[clusterID] + if ok { production = cluster.Production } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0a24a157..54d0991b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -183,22 +183,23 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) return nil, err } - var res []*ProductResp clusterMap := make(map[string]*commonmodels.K8SCluster) + clusters, err := commonrepo.NewK8SClusterColl().Find("") + if err != nil { + log.Errorf("Failed to list clusters in db, err: %s", err) + return nil, err + } + + for _, cls := range clusters { + clusterMap[cls.ID.Hex()] = cls + } + + var res []*ProductResp for _, env := range envs { clusterID := env.ClusterID production := false - if clusterID != "" { - cluster, ok := clusterMap[clusterID] - if !ok { - cluster, err = commonrepo.NewK8SClusterColl().Get(clusterID) - if err != nil { - log.Warnf("Failed to get cluster %s in db, can not determine if env %s is a production env or not, err: %s", clusterID, env.EnvName, err) - continue - } - clusterMap[clusterID] = cluster - } - + cluster, ok := clusterMap[clusterID] + if ok { production = cluster.Production } diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 290ea6e2..787d2452 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -33,6 +33,7 @@ response = r { # response for resource filtering, all allowed resources IDs will be returned in headers response = r { + not allow rule_is_matched_for_filtering r := { "allowed": true, @@ -51,7 +52,6 @@ allow { allow { is_authenticated access_is_granted - } # Allow all valid users to visit exempted urls. -- Gitee From 0118ff8e9b4826d2750e1016e3a69fdfbac390f8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 28 Nov 2021 19:20:16 +0800 Subject: [PATCH 0802/2647] update api logic | revier dockerfile changes Signed-off-by: allenshen --- docker/service/aslan.Dockerfile | 2 +- pkg/config/config.go | 2 +- .../repository/models/delivery_distribute.go | 25 +- .../repository/models/delivery_version.go | 25 +- .../repository/mongodb/delivery_distribute.go | 2 +- .../repository/mongodb/delivery_version.go | 20 +- .../aslan/core/common/service/s3/s3.go | 13 +- .../aslan/core/common/service/service.go | 18 +- .../aslan/core/delivery/handler/version.go | 9 +- .../aslan/core/delivery/service/version.go | 280 +++++++++++------- 10 files changed, 224 insertions(+), 172 deletions(-) diff --git a/docker/service/aslan.Dockerfile b/docker/service/aslan.Dockerfile index 434810fa..4384bb45 100644 --- a/docker/service/aslan.Dockerfile +++ b/docker/service/aslan.Dockerfile @@ -2,7 +2,7 @@ RUN go build -v -o /aslan ./cmd/aslan/main.go -#alpine-git.Dockerfile +#alpine.Dockerfile WORKDIR /app diff --git a/pkg/config/config.go b/pkg/config/config.go index 4f324676..e88d6736 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -211,7 +211,7 @@ func ObjectStorageTemplatePath(name, kind string) string { } func ObjectStorageDeliveryVersionPath(project string) string { - return filepath.Join(project, "delivery-versions") + return filepath.Join("delivery-distributes", "files", project) } func ObjectStorageChartTemplatePath(name string) string { diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go index 24b87745..4365fa13 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go @@ -25,18 +25,23 @@ import ( type DeliveryDistribute struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ReleaseID primitive.ObjectID `bson:"release_id" json:"releaseId"` - ServiceName string `bson:"service_name" json:"serviceName"` + ServiceName string `bson:"service_name" json:"serviceName,omitempty"` DistributeType config.DistributeType `bson:"distribute_type" json:"distributeType"` RegistryName string `bson:"registry_name" json:"registryName"` - Version string `bson:"version" json:"version"` - ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName"` - Namespace string `bson:"namespace" json:"namespace"` - PackageFile string `bson:"package_file" json:"packageFile"` - RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey"` - DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl"` - SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl"` - StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + ChartVersion string `bson:"chart_version" json:"chartVersion,omitempty"` + ChartName string `bson:"chart_name" json:"chartName,omitempty"` + ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName,omitempty"` + SubDistributes []*DeliveryDistribute `bson:"-" json:"subDistributes,omitempty"` + Namespace string `bson:"namespace" json:"namespace,omitempty"` + PackageFile string `bson:"package_file" json:"packageFile,omitempty"` + RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey,omitempty"` + DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl,omitempty"` + S3StorageID string `bson:"s3_storage_id" json:"s3StorageID"` + StorageURL string `bson:"-" json:"storageUrl"` + StorageBucket string `bson:"-" json:"storageBucket"` + SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 8720ac8b..432d6615 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -20,12 +20,6 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -//type DeliveryChart struct { -// Name string `bson:"name" json:"name"` -// Version string `bson:"version" json:"version"` -// Repo string `bson:"repo" json:"repo"` -//} - type DeliveryVersionProgress struct { SuccessChartCount int `json:"successChartCount"` TotalChartCount int `json:"totalChartCount"` @@ -34,16 +28,15 @@ type DeliveryVersionProgress struct { } type DeliveryVersion struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Version string `bson:"version" json:"version"` - ProductName string `bson:"product_name" json:"productName"` - WorkflowName string `bson:"workflow_name" json:"workflowName"` - Type string `bson:"type" json:"type"` - TaskID int `bson:"task_id" json:"taskId"` - Desc string `bson:"desc" json:"desc"` - Labels []string `bson:"labels" json:"labels"` - ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` - //Charts []*DeliveryChart `bson:"charts" json:"charts"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Version string `bson:"version" json:"version"` + ProductName string `bson:"product_name" json:"productName"` + WorkflowName string `bson:"workflow_name" json:"workflowName"` + Type string `bson:"type" json:"type"` + TaskID int `bson:"task_id" json:"taskId"` + Desc string `bson:"desc" json:"desc"` + Labels []string `bson:"labels" json:"labels"` + ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` Status string `bson:"status" json:"status"` Error string `bson:"error" json:"-"` Progress *DeliveryVersionProgress `bson:"-" json:"progress"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go index 0b3ce01a..21ef1dd5 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go @@ -116,7 +116,7 @@ func (c *DeliveryDistributeColl) Find(args *DeliveryDistributeArgs) ([]*models.D query := bson.M{"release_id": releaseID, "deleted_at": 0} if args.DistributeType != "" { - query["distribute_type"] = config.File + query["distribute_type"] = string(args.DistributeType) } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index 05d7cb8b..e0adcfc8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -90,7 +90,6 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver return nil, errors.New("nil delivery_version args") } - var resp []*models.DeliveryVersion query := bson.M{"deleted_at": 0} if args.ProductName != "" { query["product_name"] = args.ProductName @@ -114,6 +113,7 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver return nil, err } + resp := make([]*models.DeliveryVersion, 0) err = cursor.All(ctx, &resp) if err != nil { return nil, err @@ -207,24 +207,6 @@ func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr s return err } -func (c *DeliveryVersionColl) AddDeliveryChart(versionName string, chartInfo *models.DeliveryDistribute) error { - //query := bson.M{ - // "version": versionName, - // "deleted_at": 0, - // "charts.name": bson.M{"$ne": chartInfo.Name}, - //} - //change := bson.M{ - // "$push": bson.M{ - // "charts": bson.M{ - // "name": chartInfo.Name, - // "version": chartInfo.Version, - // "repo": chartInfo.Repo, - // }, - // }} - //_, err := c.UpdateOne(context.TODO(), query, change) - return nil -} - func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { if args == nil { return errors.New("nil delivery_version args") diff --git a/pkg/microservice/aslan/core/common/service/s3/s3.go b/pkg/microservice/aslan/core/common/service/s3/s3.go index 7a7f31a7..fc363b61 100644 --- a/pkg/microservice/aslan/core/common/service/s3/s3.go +++ b/pkg/microservice/aslan/core/common/service/s3/s3.go @@ -151,17 +151,8 @@ func FindDefaultS3() (*S3, error) { func FindS3ById(id string) (*S3, error) { storage, err := commonrepo.NewS3StorageColl().Find(id) if err != nil { - log.Warnf("Failed to find default s3 in db, err: %s", err) - return &S3{ - S3Storage: &models.S3Storage{ - Ak: config.S3StorageAK(), - Sk: config.S3StorageSK(), - Endpoint: config.S3StorageEndpoint(), - Bucket: config.S3StorageBucket(), - Insecure: config.S3StorageProtocol() == "http", - Provider: setting.ProviderSourceSystemDefault, - }, - }, nil + log.Warnf("Failed to find s3 in db, err: %s", err) + return nil, err } return &S3{S3Storage: storage}, nil diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index b5bb8a6b..fbcf8086 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -91,8 +91,9 @@ type ServiceProductMap struct { } var ( - imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) - presetPatterns = []map[string]string{ + imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) + imageSpaceParseRegex = regexp.MustCompile(`\/(.*?)\/`) + presetPatterns = []map[string]string{ {setting.PathSearchComponentImage: "image.repository", setting.PathSearchComponentTag: "image.tag"}, {setting.PathSearchComponentImage: "image"}, } @@ -650,6 +651,19 @@ func ExtractImageTag(imageURI string) string { return "" } +// ExtractRegistryNamespace extract registry namespace form image uri +func ExtractRegistryNamespace(imageURI string) string { + imageURI = strings.TrimPrefix(imageURI, "http://") + imageURI = strings.TrimPrefix(imageURI, "https://") + + firstIndex := strings.Index(imageURI, "/") + lastIndex := strings.LastIndex(imageURI, "/") + if lastIndex == firstIndex { + return "" + } + return strings.TrimPrefix(imageURI[firstIndex:lastIndex], "/") +} + func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 10397ed4..c943e5d3 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -18,6 +18,7 @@ package handler import ( "fmt" + "net/http" "strings" "github.com/gin-gonic/gin" @@ -78,7 +79,7 @@ func ListDeliveryVersion(c *gin.Context) { args.Page = 1 } if args.PerPage <= 0 { - args.Page = 20 + args.PerPage = 20 } if len(args.Verbosity) == 0 { args.Verbosity = deliveryservice.VerbosityDetailed @@ -231,12 +232,14 @@ func DownloadDeliveryChart(c *gin.Context) { chartName := c.Query("chartName") projectName := c.Query("projectName") - filePath, err := deliveryservice.DownloadDeliveryChart(projectName, versionName, chartName, ctx.Logger) + fileBytes, fileName, err := deliveryservice.DownloadDeliveryChart(projectName, versionName, chartName, ctx.Logger) if err != nil { ctx.Err = err return } - c.File(filePath) + + c.Writer.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName)) + c.Data(http.StatusOK, "application/octet-stream", fileBytes) } func PreviewGetDeliveryChart(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 5524f87d..e839a7dd 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -17,7 +17,6 @@ limitations under the License. package service import ( - "bytes" "encoding/json" "fmt" "io" @@ -25,7 +24,6 @@ import ( "net/http" "net/url" "os" - "os/exec" "path/filepath" "strings" "sync" @@ -181,7 +179,7 @@ func GetDetailReleaseData(args *commonrepo.DeliveryVersionArgs, log *zap.Sugared log.Errorf("get deliveryVersion error: %v", err) return nil, e.ErrGetDeliveryVersion } - return buildReleaseRespData(VerbosityDetailed, versionData, nil, log) + return buildListReleaseResp(VerbosityDetailed, versionData, nil, log) } func FindDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) ([]*commonmodels.DeliveryVersion, error) { @@ -316,14 +314,15 @@ func buildDetailedRelease(deliveryVersion *commonmodels.DeliveryVersion, filterO deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() deliveryDistributes, _ := FindDeliveryDistribute(deliveryDistributeArgs, logger) + releaseInfo.DistributeInfo = deliveryDistributes - deliveryVersion.Progress = FillDeliveryProgressInfo(deliveryVersion, deliveryDistributes) + // fill some data for helm delivery releases + processReleaseRespData(releaseInfo) - releaseInfo.DistributeInfo = deliveryDistributes return releaseInfo, nil } -func buildReleaseRespData(verbosity string, deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { +func buildListReleaseResp(verbosity string, deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { switch verbosity { case VerbosityBrief: return buildBriefRelease(deliveryVersion, logger) @@ -335,21 +334,20 @@ func buildReleaseRespData(verbosity string, deliveryVersion *commonmodels.Delive } func ListDeliveryVersion(args *ListDeliveryVersionArgs, logger *zap.SugaredLogger) ([]*ReleaseInfo, error) { - version := new(commonrepo.DeliveryVersionArgs) - version.ProductName = args.ProjectName - version.WorkflowName = args.WorkflowName - version.TaskID = args.TaskId - version.PerPage = args.PerPage - version.Page = args.Page - deliveryVersions, err := FindDeliveryVersion(version, logger) + versionListArgs := new(commonrepo.DeliveryVersionArgs) + versionListArgs.ProductName = args.ProjectName + versionListArgs.WorkflowName = args.WorkflowName + versionListArgs.TaskID = args.TaskId + versionListArgs.PerPage = args.PerPage + versionListArgs.Page = args.Page + deliveryVersions, err := FindDeliveryVersion(versionListArgs, logger) if err != nil { return nil, err } - log.Infof("###### the verbosity is %s, projectName is %s", args.Verbosity, args.ProjectName) releaseInfos := make([]*ReleaseInfo, 0) for _, deliveryVersion := range deliveryVersions { - releaseInfo, err := buildReleaseRespData(args.Verbosity, deliveryVersion, &DeliveryVersionFilter{ServiceName: args.ServiceName}, logger) + releaseInfo, err := buildListReleaseResp(args.Verbosity, deliveryVersion, &DeliveryVersionFilter{ServiceName: args.ServiceName}, logger) if err != nil { return nil, err } @@ -361,17 +359,50 @@ func ListDeliveryVersion(args *ListDeliveryVersionArgs, logger *zap.SugaredLogge return releaseInfos, nil } -func FillDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion, deliveryDistribute []*commonmodels.DeliveryDistribute) *commonmodels.DeliveryVersionProgress { - if deliveryVersion.Type != setting.DeliveryVersionTypeChart { - return nil +// fill release +func processReleaseRespData(release *ReleaseInfo) { + if release.VersionInfo.Type != setting.DeliveryVersionTypeChart { + return } - chartDeploys := make([]*commonmodels.DeliveryDistribute, 0) - for _, deliveryDistribute := range deliveryDistribute { - if deliveryDistribute.DistributeType == config.Chart { - chartDeploys = append(chartDeploys, deliveryDistribute) + + distributeMap := make(map[string][]*commonmodels.DeliveryDistribute) + for _, distributeImage := range release.DistributeInfo { + if distributeImage.DistributeType != config.Image { + continue + } + distributeMap[distributeImage.ChartName] = append(distributeMap[distributeImage.ChartName], distributeImage) + } + + chartDistributeCount := 0 + distributes := make([]*commonmodels.DeliveryDistribute, 0) + for _, distribute := range release.DistributeInfo { + if distribute.DistributeType == config.Image { + continue } + switch distribute.DistributeType { + case config.Chart: + chartDistributeCount++ + distribute.SubDistributes = distributeMap[distribute.ChartName] + case config.File: + s3Storage, err := commonrepo.NewS3StorageColl().Find(distribute.S3StorageID) + if err != nil { + log.Errorf("failed to query s3 storageID: %s, err: %s", distribute.S3StorageID, err) + } else { + distribute.StorageURL = s3Storage.Endpoint + distribute.StorageBucket = s3Storage.Bucket + } + } + distributes = append(distributes, distribute) + } + release.DistributeInfo = distributes + + release.VersionInfo.Progress = buildDeliveryProgressInfo(release.VersionInfo, chartDistributeCount) +} + +func buildDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion, successfulChartCount int) *commonmodels.DeliveryVersionProgress { + if deliveryVersion.Type != setting.DeliveryVersionTypeChart { + return nil } - successfulChartCount := len(chartDeploys) progress := &commonmodels.DeliveryVersionProgress{ SuccessChartCount: successfulChartCount, @@ -454,18 +485,8 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { return client, nil } -func runDockerCommand(cmd *exec.Cmd) error { - var out bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &out - if err := cmd.Run(); err != nil { - return err - } - return nil -} - // pull image from currently using registry and push to specified repo -func pushImages(serviceObj *commonmodels.Service, valuesMap map[string]interface{}, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { +func pushImages(deliveryVersion *commonmodels.DeliveryVersion, serviceObj *commonmodels.Service, valuesMap map[string]interface{}, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { log.Infof("######## start pushing image #########") @@ -495,12 +516,6 @@ func pushImages(serviceObj *commonmodels.Service, valuesMap map[string]interface imageUris = append(imageUris, imageUri) } - // login to the target repo - err = runDockerCommand(dockerLogin(targetRegistry.AccessKey, targetRegistry.SecretKey, targetRegistry.RegAddr)) - if err != nil { - return errors.Wrapf(err, fmt.Sprintf("failed to login docker registry, url: %s", targetRegistry.RegAddr)) - } - for _, imageUri := range imageUris { log.Infof("##### handle single image uri %s ######", imageUri) registryUrl, err := commonservice.ExtractImageRegistry(imageUri) @@ -509,40 +524,37 @@ func pushImages(serviceObj *commonmodels.Service, valuesMap map[string]interface } registryUrl = strings.TrimSuffix(registryUrl, "/") log.Infof("#### extract registry url %s ####", registryUrl) - if registry, ok := registryMap[registryUrl]; ok { - if registry.ID == targetRegistry.ID { - log.Infof("######## same registry, no need to push again ########") - continue - } - //TODO need optimize to avoid login to same repo multiple times - err := runDockerCommand(dockerLogin(registry.AccessKey, registry.SecretKey, registry.RegAddr)) - if err != nil { - return errors.Wrapf(err, fmt.Sprintf("failed to login docker registry, url: %s", registry.RegAddr)) - } - } - err = runDockerCommand(dockerPull(imageUri)) - if err != nil { - return errors.Wrapf(err, fmt.Sprintf("failed to pull docker image, source url: %s", imageUri)) - } imageName := commonservice.ExtractImageName(imageUri) imageTag := commonservice.ExtractImageTag(imageUri) + imageNamespace := commonservice.ExtractRegistryNamespace(imageUri) + targetFullImageUri := imageUri - targetFullImageUri := fmt.Sprintf("%s/%s/%s:%s", targetRegistry.RegAddr, targetRegistry.Namespace, imageName, imageTag) - err = runDockerCommand(dockerTag(imageUri, targetFullImageUri)) - if err != nil { - return errors.Wrapf(err, "failed to tag image from: %s to : %s", imageUri, targetFullImageUri) + if registry, ok := registryMap[registryUrl]; ok { + if registry.ID != targetRegistry.ID { + targetFullImageUri = fmt.Sprintf("%s/%s/%s:%s", targetRegistry.RegAddr, targetRegistry.Namespace, imageName, imageTag) + } } - err = runDockerCommand(dockerPush(targetFullImageUri)) + // add image distribution data + err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + ServiceName: imageName, + ChartName: serviceObj.ServiceName, + DistributeType: config.Image, + RegistryName: targetFullImageUri, + Namespace: imageNamespace, + CreatedAt: time.Now().Unix(), + }) if err != nil { - return errors.Wrapf(err, "failed to push image: %s", targetFullImageUri) + log.Errorf("failed to insert image distribute data, version: %s, image: %s, err: %s", deliveryVersion.Version, targetFullImageUri, err) + return fmt.Errorf("failed to insert image distribute data") } } return nil } -func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { +func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { serviceObj := chartData.ServiceObj serviceName, revision := serviceObj.ServiceName, serviceObj.Revision basePath := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) @@ -599,7 +611,7 @@ func handleSingleChart(chartData *DeliveryChartData, chartRepo *commonmodels.Hel } // push docker image - err = pushImages(serviceObj, chartRequested.Values, targetRegistry, registryMap) + err = pushImages(deliveryVersion, serviceObj, chartRequested.Values, targetRegistry, registryMap) if err != nil { return errors.Wrapf(err, "failed to handle image") } @@ -762,10 +774,10 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return fmt.Errorf("failed to build registry map") } - err = runDockerCommand(dockerInfo()) - if err != nil { - return fmt.Errorf("failed to run docker deamon") - } + //err = runDockerCommand(dockerInfo()) + //if err != nil { + // return fmt.Errorf("failed to run docker deamon") + //} // push charts to repo wg := sync.WaitGroup{} @@ -773,19 +785,24 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - err := handleSingleChart(cData, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) + err := handleSingleChart(deliveryVersion, cData, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) if err != nil { logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) - } else { - //err = commonrepo.NewDeliveryVersionColl().AddDeliveryChart(deliveryVersion.Version, &commonmodels.DeliveryChart{ - // Name: cData.ChartData.ServiceName, - // Version: cData.ChartData.Version, - // Repo: args.ChartRepoName, - //}) - //if err != nil { - // appendError(errors.Wrapf(err, "failed to save delivery chart: %s", cData.ChartData.ServiceName)) - //} + return + } + err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + DistributeType: config.Chart, + ChartName: cData.ChartData.ServiceName, + ChartVersion: cData.ChartData.Version, + ChartRepoName: args.ChartRepoName, + SubDistributes: nil, + CreatedAt: time.Now().Unix(), + }) + if err != nil { + logger.Errorf("failed to insert delivery distribute chart, serviceName: %s, err: %s", cData.ChartData.ServiceName, err) + appendError(err) } }(chartData) } @@ -801,14 +818,28 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return } - //tar all chart files and send to s3 store + //tar all chart files and image offline packages, send to s3 store fsTree := os.DirFS(dir) - ServiceS3Base := configbase.ObjectStorageDeliveryVersionPath(deliveryVersion.ProductName) - if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, deliveryVersion.Version, ServiceS3Base, nil, args.Options.S3StorageID, logger); err != nil { + s3Base := configbase.ObjectStorageDeliveryVersionPath(deliveryVersion.ProductName) + if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, deliveryVersion.Version, s3Base, nil, args.Options.S3StorageID, logger); err != nil { logger.Errorf("failed to upload chart package files for project %s, err: %s", deliveryVersion.ProductName, err) err = errors.Wrapf(err, "failed to upload package file") return } + + err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + DistributeType: config.File, + PackageFile: fmt.Sprintf("%s.tar.gz", deliveryVersion.Version), + RemoteFileKey: filepath.Join(s3Base, fmt.Sprintf("%s.tar.gz", deliveryVersion.Version)), + S3StorageID: args.Options.S3StorageID, + CreatedAt: time.Now().Unix(), + }) + if err != nil { + logger.Errorf("failed to insert file distribute data, version: %s, err: %s", deliveryVersion.Version, err) + return fmt.Errorf("failed to insert file distribute data") + } + return } @@ -896,16 +927,26 @@ func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap //deploys := make + distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ + ReleaseID: deliveryVersion.ID.Hex(), + }) + if err != nil { + return errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) + } + // for charts has been successfully handled, download charts directly successCharts := sets.NewString() - //for _, singleChart := range deliveryVersion.Charts { - // _, err := downloadChart(projectName, versionName, singleChart) - // if err != nil { - // log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", singleChart.Name, err) - // continue - // } - // successCharts.Insert(singleChart.Name) - //} + for _, distribute := range distributes { + if distribute.DistributeType != config.Chart { + continue + } + _, err := downloadChart(deliveryVersion, distribute) + if err != nil { + log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", distribute.ChartName, err) + continue + } + successCharts.Insert(distribute.ChartName) + } chartsToBeHandled := make([]*CreateHelmDeliveryVersionChartData, 0) for _, chartConfig := range createArgs.ChartDatas { @@ -963,8 +1004,9 @@ func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]str return serviceNames.UnsortedList(), nil } -func downloadChart(productName, versionName string, chartInfo *commonmodels.DeliveryDistribute) (string, error) { - chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.ServiceName, chartInfo.Version) +func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *commonmodels.DeliveryDistribute) (string, error) { + productName, versionName := deliveryVersion.ProductName, deliveryVersion.Version + chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.ChartName, chartInfo.ChartVersion) chartTGZFileParent := getChartTGZDir(productName, versionName) chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) if _, err := os.Stat(chartTGZFilePath); err == nil { @@ -1019,25 +1061,16 @@ func downloadChart(productName, versionName string, chartInfo *commonmodels.Deli return chartTGZFilePath, nil } -func getChartDistributeInfo(projectName, version string, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { - //deliveryVersion, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ - // ProductName: projectName, - // Version: version, - //}, log) - - //if err != nil { - // return nil, err - //} - +func getChartDistributeInfo(releaseID, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { distributes, _ := FindDeliveryDistribute(&commonrepo.DeliveryDistributeArgs{ - ReleaseID: "", + ReleaseID: releaseID, ServiceName: chartName, DistributeType: config.Chart, }, log) var chartInfo *commonmodels.DeliveryDistribute for _, distribute := range distributes { - if distribute.ServiceName == chartName { + if distribute.ChartName == chartName { chartInfo = distribute } } @@ -1048,13 +1081,36 @@ func getChartDistributeInfo(projectName, version string, chartName string, log * return chartInfo, nil } -func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) (string, error) { - chartInfo, err := getChartDistributeInfo(projectName, version, chartName, log) +func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) ([]byte, string, error) { + + filePath, err := preDownloadChart(projectName, version, chartName, log) + if err != nil { + return nil, "", err + } + + fileBytes, err := os.ReadFile(filePath) + if err != nil { + return nil, "", err + } + + return fileBytes, filepath.Base(filePath), err +} + +func preDownloadChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { + deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }, log) + if err != nil { + return "", fmt.Errorf("failed to query delivery info") + } + + chartInfo, err := getChartDistributeInfo(deliveryInfo.ID.Hex(), chartName, log) if err != nil { return "", err } // prepare chart data - filePath, err := downloadChart(projectName, version, chartInfo) + filePath, err := downloadChart(deliveryInfo, chartInfo) if err != nil { return "", err } @@ -1063,14 +1119,22 @@ func DownloadDeliveryChart(projectName, version string, chartName string, log *z func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { - chartDistribute, err := getChartDistributeInfo(projectName, versionName, chartName, log) + deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }, log) + if err != nil { + return "", fmt.Errorf("failed to query delivery info") + } + + chartDistribute, err := getChartDistributeInfo(deliveryInfo.ID.Hex(), chartName, log) if err != nil { return "", err } dstDir := getChartExpandDir(projectName, versionName) - dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartDistribute.ServiceName, chartDistribute.Version)) + dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartDistribute.ChartName, chartDistribute.ChartVersion)) - filePath, err := DownloadDeliveryChart(projectName, versionName, chartName, log) + filePath, err := preDownloadChart(projectName, versionName, chartName, log) if err != nil { return "", err } -- Gitee From cb622dff86c5e89e074f1c9c7153a99011b59d25 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 29 Nov 2021 14:10:41 +0800 Subject: [PATCH 0803/2647] add production policy Signed-off-by: lou --- .../delivery/handler/policy_definitions.go | 4 +- .../core/environment/handler/environment.go | 23 ++++ .../core/environment/handler/policy.yaml | 104 ++++++++++++++---- .../environment/handler/policy_definitions.go | 26 ++++- .../project/handler/policy_definitions.go | 4 +- pkg/microservice/aslan/core/service.go | 10 +- .../aslan/core/workflow/handler/policy.yaml | 8 ++ .../workflow/handler/policy_definitions.go | 4 +- .../testing/handler/policy_definitions.go | 4 +- .../core/service/bundle/rego/authz.rego | 1 + pkg/shared/client/policy/policy.go | 12 +- 11 files changed, 160 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go index a434ba1d..02789a47 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go @@ -12,7 +12,7 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() *policy.Policy { +func (*Router) Policies() []*policy.Policy { res := &policy.Policy{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { @@ -20,5 +20,5 @@ func (*Router) Policies() *policy.Policy { log.DPanic(err) } - return res + return []*policy.Policy{res} } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 5e631774..e7bf68ef 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -21,10 +21,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/http" "strconv" "strings" "github.com/gin-gonic/gin" + "k8s.io/apimachinery/pkg/util/sets" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -101,6 +103,17 @@ func UpdateMultiProducts(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateMultiProducts json.Unmarshal err : %v", err) } + + allowedEnvs, found := internalhandler.GetResourcesInHeader(c) + if found { + allowedSet := sets.NewString(allowedEnvs...) + currentSet := sets.NewString(args.EnvNames...) + if !allowedSet.IsSuperset(currentSet) { + c.String(http.StatusForbidden, "not all input envs are allowed, allowed envs are %v", allowedEnvs) + return + } + } + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) @@ -326,6 +339,16 @@ func updateMultiHelmEnv(c *gin.Context, ctx *internalhandler.Context) { } args.ProductName = projectName + allowedEnvs, found := internalhandler.GetResourcesInHeader(c) + if found { + allowedSet := sets.NewString(allowedEnvs...) + currentSet := sets.NewString(args.EnvNames...) + if !allowedSet.IsSuperset(currentSet) { + c.String(http.StatusForbidden, "not all input envs are allowed, allowed envs are %v", allowedEnvs) + return + } + } + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) ctx.Resp, ctx.Err = service.UpdateMultipleHelmEnv( diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 12e3cde0..e62ca40b 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -8,14 +8,31 @@ rules: rules: - method: GET endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*" - - method: GET - endpoint: "/api/aslan/environment/revision/products" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/groups" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/groups$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/services/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/kube/workloads" - method: GET @@ -32,14 +49,14 @@ rules: endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" - method: GET endpoint: "/api/aslan/project/products/?*/services" + - method: GET + endpoint: "/api/aslan/environment/revision/products" - action: create_environment alias: "新建集成环境" description: "" rules: - method: POST endpoint: "/api/aslan/environment/environments" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/helm" - method: POST endpoint: "/api/aslan/service/workloads" - method: GET @@ -51,17 +68,32 @@ rules: description: "" rules: - method: PUT - endpoint: "/api/aslan/environment/environments/?*/autoUpdate" - - method: POST + endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" + - method: PUT endpoint: "/api/aslan/environment/environments/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments/?*/envRecycle" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/renderchart" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/envRecycle$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments/?*/renderset" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/multiHelmEnv" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/renderset$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/service/workloads" - method: GET @@ -70,26 +102,51 @@ rules: alias: "管理服务实例" description: "" rules: + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/scale" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" + - method: POST + endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/services/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/environment/image/deployment" - method: POST endpoint: "/api/aslan/environment/image/statefulset" - method: DELETE endpoint: "/api/aslan/environment/kube/pods/?*" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" - method: PUT endpoint: "/api/aslan/environment/configmaps" - method: POST endpoint: "/api/aslan/environment/configmaps" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/services/?*/?*" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/scale/?*" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew/?*" - method: GET endpoint: "/api/podexec/?*/?*/?*/?*/podExec" - action: delete_environment @@ -97,4 +154,9 @@ rules: description: "" rules: - method: DELETE - endpoint: "/api/aslan/environment/environments/?*" \ No newline at end of file + endpoint: "/api/aslan/environment/environments/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go index a434ba1d..17df5f9f 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go @@ -9,10 +9,17 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +const ( + productionPolicyResource = "ProductionEnvironment" + productionPolicyAlias = "集成环境(类生产/预发布)" + productionKey = "production" + productionValueTrue = "true" +) + //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() *policy.Policy { +func (*Router) Policies() []*policy.Policy { res := &policy.Policy{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { @@ -20,5 +27,20 @@ func (*Router) Policies() *policy.Policy { log.DPanic(err) } - return res + productionPolicy := &policy.Policy{} + _ = yaml.Unmarshal(policyDefinitions, productionPolicy) + productionPolicy.Resource = productionPolicyResource + productionPolicy.Alias = productionPolicyAlias + //productionPolicy.Description = "" + for _, ru := range productionPolicy.Rules { + for _, r := range ru.Rules { + for _, a := range r.MatchAttributes { + if a.Key == productionKey { + a.Value = productionValueTrue + } + } + } + } + + return []*policy.Policy{res, productionPolicy} } diff --git a/pkg/microservice/aslan/core/project/handler/policy_definitions.go b/pkg/microservice/aslan/core/project/handler/policy_definitions.go index a434ba1d..02789a47 100644 --- a/pkg/microservice/aslan/core/project/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/project/handler/policy_definitions.go @@ -12,7 +12,7 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() *policy.Policy { +func (*Router) Policies() []*policy.Policy { res := &policy.Policy{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { @@ -20,5 +20,5 @@ func (*Router) Policies() *policy.Policy { log.DPanic(err) } - return res + return []*policy.Policy{res} } diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 35d9c1f1..42d0268c 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -51,7 +51,7 @@ const ( ) type policyGetter interface { - Policies() *policy.Policy + Policies() []*policy.Policy } type Controller interface { @@ -80,7 +80,7 @@ func StartControllers(stopCh <-chan struct{}) { func registerPolicies() { policyClient := policy.New() - + var policies []*policy.Policy for _, r := range []policyGetter{ new(workflowhandler.Router), new(environmenthandler.Router), @@ -88,7 +88,11 @@ func registerPolicies() { new(testinghandler.Router), new(deliveryhandler.Router), } { - err := policyClient.CreateOrUpdatePolicy(r.Policies()) + policies = append(policies, r.Policies()...) + } + + for _, p := range policies { + err := policyClient.CreateOrUpdatePolicy(p) if err != nil { // should not have happened here log.DPanic(err) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 1dc34f8c..fb681fbc 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -38,6 +38,10 @@ rules: endpoint: "api/aslan/workflow/workflow" - method: GET endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" + matchAttributes: + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/workflow/workflow/preset/?*" - method: GET @@ -54,6 +58,10 @@ rules: endpoint: "/api/directory/workflowTask/create" - method: GET endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" + matchAttributes: + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/testing/testdetail" - action: delete_workflow diff --git a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go index a434ba1d..02789a47 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go @@ -12,7 +12,7 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() *policy.Policy { +func (*Router) Policies() []*policy.Policy { res := &policy.Policy{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { @@ -20,5 +20,5 @@ func (*Router) Policies() *policy.Policy { log.DPanic(err) } - return res + return []*policy.Policy{res} } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go index a434ba1d..02789a47 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go @@ -12,7 +12,7 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() *policy.Policy { +func (*Router) Policies() []*policy.Policy { res := &policy.Policy{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { @@ -20,5 +20,5 @@ func (*Router) Policies() *policy.Policy { log.DPanic(err) } - return res + return []*policy.Policy{res} } diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 787d2452..2179b7c8 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -33,6 +33,7 @@ response = r { # response for resource filtering, all allowed resources IDs will be returned in headers response = r { + is_authenticated not allow rule_is_matched_for_filtering r := { diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 66a4736c..278c2f06 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -21,14 +21,14 @@ type Rule struct { } type ActionRule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` - ResourceType string `json:"resourceType,omitempty"` - IDRegex string `json:"idRegex,omitempty"` - MatchAttributes []attribute `json:"matchAttributes,omitempty"` + Method string `json:"method"` + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType,omitempty"` + IDRegex string `json:"idRegex,omitempty"` + MatchAttributes []*Attribute `json:"matchAttributes,omitempty"` } -type attribute struct { +type Attribute struct { Key string `json:"key"` Value string `json:"value"` } -- Gitee From 9cc7c849cb101ce60fdb695e4dd6a6b3cce27721 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 29 Nov 2021 18:05:24 +0800 Subject: [PATCH 0804/2647] add cluster config Signed-off-by: liu deyi --- .../common/repository/models/k8s_cluster.go | 36 +++++++++++-------- .../core/common/repository/models/queue.go | 1 + .../common/repository/models/task/build.go | 1 + .../common/repository/models/task/testing.go | 2 ++ .../common/repository/mongodb/k8s_cluster.go | 17 +++++++++ .../core/common/service/config_payload.go | 4 +++ .../aslan/core/common/service/environment.go | 5 +-- .../aslan/core/common/service/kube/service.go | 4 --- .../aslan/core/common/service/product.go | 4 ++- .../core/environment/service/configmap.go | 8 +++-- .../core/environment/service/environment.go | 7 ++-- .../service/environment_creator.go | 5 +-- .../environment/service/environment_group.go | 4 ++- .../aslan/core/environment/service/export.go | 5 +-- .../aslan/core/environment/service/image.go | 4 ++- .../aslan/core/environment/service/k8s.go | 7 ++-- .../aslan/core/environment/service/kube.go | 13 +++---- .../aslan/core/environment/service/service.go | 14 ++++---- .../aslan/core/service/service/service.go | 6 ++-- .../aslan/core/system/service/registry.go | 7 ++-- .../service/workflow/pipeline_validation.go | 4 +-- .../service/workflow/workflow_task.go | 4 +++ .../core/service/taskplugin/build.go | 32 +++++++++++++---- .../core/service/taskplugin/testing.go | 14 ++++---- .../core/service/types/task/build.go | 1 + .../core/service/types/task/config_payload.go | 18 ++++++++++ .../core/service/types/task/testing.go | 2 ++ pkg/shared/kube/resource/namespace.go | 2 ++ pkg/shared/kube/wrapper/namespace.go | 10 +++--- 29 files changed, 166 insertions(+), 75 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index f3f60b21..4ddf9fd9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -29,21 +29,22 @@ import ( var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - Status config.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Provider int8 `json:"provider" bson:"provider"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` + Status config.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Provider int8 `json:"provider" bson:"provider"` } type K8SClusterInfo struct { @@ -53,6 +54,11 @@ type K8SClusterInfo struct { Memory string `json:"memory" bson:"memory"` } +type ClusterConfig struct { + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` +} + func (K8SCluster) TableName() string { return "k8s_cluster" } diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 7801b2c4..b60f7e0f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -136,6 +136,7 @@ type ConfigPayload struct { ResetCache bool `json:"reset_cache"` JenkinsBuildConfig JenkinsBuildConfig `json:"jenkins_build_config"` PrivateKeys []*PrivateKey `json:"private_keys"` + K8SClusters []*K8SCluster `json:"k8s_cluster"` } type AslanConfig struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index c8211297..bd75aa2b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -59,6 +59,7 @@ type Build struct { // Get the host bound to the environment of the cloud host service configuration EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` + ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` } type ArtifactInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/testing.go b/pkg/microservice/aslan/core/common/repository/models/task/testing.go index f6aaab8a..d2c2c80e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/testing.go @@ -45,6 +45,8 @@ type Testing struct { ReportReady bool `bson:"report_ready" json:"report_ready"` IsRestart bool `bson:"is_restart" json:"is_restart"` Registries []*models.RegistryNamespace `bson:"-" json:"registries"` + ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` + Namespace string `bson:"namespace" json:"namespace"` } func (t *Testing) ToSubTask() (map[string]interface{}, error) { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 31f5efc8..7e05ce65 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -201,3 +201,20 @@ func (c *K8SClusterColl) GetByToken(token string) (*models.K8SCluster, error) { return c.Get(id) } + +func (c *K8SClusterColl) List() ([]*models.K8SCluster, error) { + resp := make([]*models.K8SCluster, 0) + ctx := context.Background() + + cursor, err := c.Collection.Find(ctx, bson.M{}) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, err +} diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index ac244fe3..b3c2808f 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -89,5 +89,9 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { payload.PrivateKeys = privateKeys } + k8sClusters, _ := mongodb.NewK8SClusterColl().List() + if len(k8sClusters) != 0 { + payload.K8SClusters = k8sClusters + } return payload } diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 6212da27..61b2cddc 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -26,16 +26,17 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/util" jsonutil "github.com/koderover/zadig/pkg/util/json" ) @@ -208,7 +209,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Infof("Start to list workloads in namespace %s", namespace) var resp = make([]*ServiceResp, 0) - kubeClient, err := kube.GetKubeClient(clusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index ee405bfe..8f092f71 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -36,10 +36,6 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) -func GetKubeClient(clusterID string) (client.Client, error) { - return multicluster.GetKubeClient(config.HubServerAddress(), clusterID) -} - func GetKubeAPIReader(clusterID string) (client.Reader, error) { return multicluster.GetKubeAPIReader(config.HubServerAddress(), clusterID) } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 9986f357..2a024a06 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -36,6 +37,7 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -51,7 +53,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return e.ErrDeleteEnv.AddDesc("not found") } - kubeClient, err := kube.GetKubeClient(productInfo.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrDeleteEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index bbb634c5..038b0f07 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -35,6 +36,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/kube/util" ) @@ -98,7 +100,7 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*configM if err != nil { return nil, e.ErrListConfigMaps.AddErr(err) } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return nil, e.ErrListConfigMaps.AddErr(err) } @@ -153,7 +155,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } @@ -230,7 +232,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 57de424a..f708a2ff 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -58,6 +58,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -276,7 +277,7 @@ func AutoUpdateProduct(envNames []string, productName, requestID string, force b continue } - kubeClient, err := kube.GetKubeClient(p.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), p.ClusterID) if err != nil { log.Errorf("Failed to get kube client for %s, error: %v", productName, err) continue @@ -388,7 +389,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return } - kubeClient, err := kube.GetKubeClient(existedProd.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), existedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } @@ -532,7 +533,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return e.ErrUpdateEnv.AddDesc(e.EnvNotFoundErrMsg) } - kubeClient, err := kube.GetKubeClient(exitedProd.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index f8c5a8d6..b73b49e9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -33,6 +33,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) type CreateProductParam struct { @@ -120,7 +121,7 @@ func newHelmProductCreator() *HelmProductCreator { } func (creator *HelmProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := kube.GetKubeClient(args.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { log.Errorf("[%s][%s] GetKubeClient error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) @@ -277,7 +278,7 @@ func newDefaultProductCreator() *DefaultProductCreator { } func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := kube.GetKubeClient(args.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { return e.ErrCreateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index 70e8f689..cb438525 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -24,6 +24,7 @@ import ( "helm.sh/helm/v3/pkg/releaseutil" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -32,6 +33,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/util" ) @@ -65,7 +67,7 @@ func ListGroups(serviceName, envName, productName string, perPage, page int, log //将获取到的所有服务按照名称进行排序 sort.SliceStable(allServices, func(i, j int) bool { return allServices[i].ServiceName < allServices[j].ServiceName }) - kubeClient, err := kube.GetKubeClient(productInfo.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, productName, err) return resp, count, e.ErrListGroups.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/export.go b/pkg/microservice/aslan/core/environment/service/export.go index 035c30d5..775c62f6 100644 --- a/pkg/microservice/aslan/core/environment/service/export.go +++ b/pkg/microservice/aslan/core/environment/service/export.go @@ -21,10 +21,11 @@ import ( "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) // ExportYaml 查询使用到服务模板的服务组模板 @@ -39,7 +40,7 @@ func ExportYaml(envName, productName, serviceName string, log *zap.SugaredLogger } namespace := env.Namespace - kubeClient, err := kube.GetKubeClient(env.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), env.ClusterID) if err != nil { log.Errorf("cluster is not connected [%s][%s][%s]", env.EnvName, env.ProductName, env.ClusterID) return res diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 4fd4bbf7..b1556b5b 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -30,6 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -39,6 +40,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -319,7 +321,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log } namespace := product.Namespace - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 3f922b06..4606d4a2 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -25,18 +25,17 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" - "sigs.k8s.io/controller-runtime/pkg/client" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) type K8sService struct { @@ -84,7 +83,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return errors.New(e.UpsertServiceErrMsg) } - kubeClient, err := kube.GetKubeClient(exitedProd.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index 684b534d..c4ba3ff6 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -20,14 +20,14 @@ import ( "fmt" "time" - corev1 "k8s.io/api/core/v1" - "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/kube/util" ) @@ -111,7 +112,7 @@ func ListPodEvents(envName, productName, podName string, log *zap.SugaredLogger) // ListAvailableNamespaces lists available namespaces created by non-koderover func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { resp := make([]*resource.Namespace, 0) - kubeClient, err := kube.GetKubeClient(clusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListNamespaces clusterID:%s err:%v", clusterID, err) return resp, err @@ -148,7 +149,7 @@ func ListServicePods(productName, envName string, serviceName string, log *zap.S if err != nil { return res, e.ErrListServicePod.AddErr(err) } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return res, e.ErrListServicePod.AddErr(err) } @@ -175,7 +176,7 @@ func DeletePod(envName, productName, podName string, log *zap.SugaredLogger) err if err != nil { return e.ErrDeletePod.AddErr(err) } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrDeletePod.AddErr(err) } @@ -228,7 +229,7 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) ([]*resource.Node, error) { resp := make([]*resource.Node, 0) - kubeClient, err := kube.GetKubeClient(clusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) return resp, err diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 630d3764..d21d4e38 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -36,6 +37,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -59,7 +61,7 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap return e.ErrScaleService.AddErr(err) } - kubeClient, err := kube.GetKubeClient(prod.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return e.ErrScaleService.AddErr(err) } @@ -110,7 +112,7 @@ func Scale(args *ScaleArgs, logger *zap.SugaredLogger) error { return e.ErrScaleService.AddErr(err) } - kubeClient, err := kube.GetKubeClient(prod.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return e.ErrScaleService.AddErr(err) } @@ -140,7 +142,7 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { return err } - kubeClient, err := kube.GetKubeClient(prod.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return err } @@ -176,7 +178,7 @@ func GetService(envName, productName, serviceName string, workLoadType string, l return nil, e.ErrGetService.AddErr(err) } - kubeClient, err := kube.GetKubeClient(env.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), env.ClusterID) if err != nil { return nil, e.ErrGetService.AddErr(err) } @@ -322,7 +324,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e if err != nil { return err } - kubeClient, err := kube.GetKubeClient(productObj.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), productObj.ClusterID) if err != nil { return err } @@ -421,7 +423,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - kubeClient, err := kube.GetKubeClient(prod.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return "", err } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 73c3733f..ab213cbd 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -43,7 +43,6 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -51,6 +50,7 @@ import ( "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) @@ -256,7 +256,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv } func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []models.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { - kubeClient, err := kube.GetKubeClient(clusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s] error: %v", namespace, err) return err @@ -385,7 +385,7 @@ type UpdateWorkloadsArgs struct { } func UpdateWorkloads(ctx context.Context, requestID, username, productName, envName string, args UpdateWorkloadsArgs, log *zap.SugaredLogger) error { - kubeClient, err := kube.GetKubeClient(args.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { log.Errorf("[%s] error: %s", args.Namespace, err) return err diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 0aa8288e..8df8afe0 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -22,13 +22,14 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/util" ) @@ -86,7 +87,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa } for _, env := range envs { go func(prod *commonmodels.Product) { - kubeClient, err := kube.GetKubeClient(prod.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return @@ -161,7 +162,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam } for _, env := range envs { go func(prod *commonmodels.Product) { - kubeClient, err := kube.GetKubeClient(prod.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 58099249..af56f9a5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -43,12 +43,12 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" @@ -688,7 +688,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return "", err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 368a4352..9872000b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1212,6 +1212,8 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * testTask.ImageID = testModule.PreTest.ImageID testTask.BuildOS = testModule.PreTest.BuildOS testTask.ImageFrom = testModule.PreTest.ImageFrom + testTask.ClusterID = testModule.PreTest.ClusterID + testTask.Namespace = testModule.PreTest.Namespace // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 if testModule.PreTest.ImageID != "" { basicImage, err := commonrepo.NewBasicImageColl().Find(testModule.PreTest.ImageID) @@ -1559,6 +1561,8 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL Timeout: module.Timeout, Registries: registries, ProductName: args.ProductName, + Namespace: module.PreBuild.Namespace, + ClusterID: module.PreBuild.ClusterID, } if args.TaskType != "" { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index db5c7f07..95e334f3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -24,6 +24,8 @@ import ( "strings" "time" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" + "go.uber.org/zap" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" @@ -106,6 +108,20 @@ func (p *BuildTaskPlugin) SetBuildStatusCompleted(status config.Status) { //TODO: Binded Archive File logic func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + if p.Task.Namespace != "" { + p.KubeNamespace = p.Task.Namespace + kubeClient, err := multicluster.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get kube client: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.kubeClient = kubeClient + } if pipelineTask.Type == config.WorkflowType { envName := pipelineTask.WorkflowArgs.Namespace envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} @@ -143,7 +159,6 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) } - p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace for _, repo := range p.Task.JobCtx.Builds { repoName := strings.Replace(repo.RepoName, "-", "_", -1) if len(repo.Branch) > 0 { @@ -313,13 +328,16 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, // 清理用户取消和超时的任务 defer func() { - if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - return + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return }() err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index d61273c0..44348007 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -257,13 +257,15 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv // 日志保存失败与否都清理job defer func() { - if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - return + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() } + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return }() err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 4d6e55c3..daa76bbc 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -57,6 +57,7 @@ type Build struct { // Get the host bound to the environment of the cloud host service configuration EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` + ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 3b3e6e0a..d42d25ea 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -18,6 +18,9 @@ package task import ( "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "go.mongodb.org/mongo-driver/bson/primitive" ) type ConfigPayload struct { @@ -49,6 +52,7 @@ type ConfigPayload struct { // ResetCache means ignore workspace cache ResetCache bool `json:"reset_cache"` PrivateKeys []*PrivateKey `json:"private_keys"` + K8SClusters []*K8SCluster `json:"k8s_cluster"` } func (cp *ConfigPayload) GetGitKnownHost() string { @@ -148,3 +152,17 @@ type JenkinsBuildConfig struct { type PrivateKey struct { Name string `json:"name"` } + +type K8SCluster struct { + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Status config.K8SClusterStatus `json:"status" bson:"status"` + ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` + Production bool `json:"production" bson:"production"` + Disconnected bool `json:"-" bson:"disconnected"` +} + +type ClusterConfig struct { + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/testing.go b/pkg/microservice/warpdrive/core/service/types/task/testing.go index fcbb0a0c..dbef2b7c 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/testing.go +++ b/pkg/microservice/warpdrive/core/service/types/task/testing.go @@ -45,6 +45,8 @@ type Testing struct { ReportReady bool `bson:"report_ready" json:"report_ready"` IsRestart bool `bson:"is_restart" json:"is_restart"` Registries []*RegistryNamespace `bson:"-" json:"registries"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + Namespace string `bson:"namespace" json:"namespace"` } func (t *Testing) ToSubTask() (map[string]interface{}, error) { diff --git a/pkg/shared/kube/resource/namespace.go b/pkg/shared/kube/resource/namespace.go index 7fad232c..50fc6e1b 100644 --- a/pkg/shared/kube/resource/namespace.go +++ b/pkg/shared/kube/resource/namespace.go @@ -21,4 +21,6 @@ type Namespace struct { Status string `json:"status"` Age string `json:"age"` Labels map[string]string `json:"labels"` + // Whether it is the namespace currently installed by zadig + Current bool `json:"current"` } diff --git a/pkg/shared/kube/wrapper/namespace.go b/pkg/shared/kube/wrapper/namespace.go index 4faa5d72..0f7c8402 100644 --- a/pkg/shared/kube/wrapper/namespace.go +++ b/pkg/shared/kube/wrapper/namespace.go @@ -19,6 +19,7 @@ package wrapper import ( corev1 "k8s.io/api/core/v1" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/util" ) @@ -53,9 +54,10 @@ func (w *namespace) Terminating() bool { func (w *namespace) Resource() *resource.Namespace { return &resource.Namespace{ - Name: w.Name, - Status: string(w.Status.Phase), - Age: util.Age(w.CreationTimestamp.Unix()), - Labels: w.Labels, + Name: w.Name, + Status: string(w.Status.Phase), + Age: util.Age(w.CreationTimestamp.Unix()), + Labels: w.Labels, + Current: w.Name == config.Namespace(), } } -- Gitee From ca42fdc76e1d87505b5f6d14f59c15e033060f99 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 29 Nov 2021 18:07:48 +0800 Subject: [PATCH 0805/2647] fix list envs Signed-off-by: lou --- .../core/environment/handler/environment.go | 3 -- .../core/environment/service/environment.go | 32 ++++--------------- .../aslan/core/environment/service/product.go | 1 + 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index e7bf68ef..5751b09b 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -70,9 +70,6 @@ func ListProducts(c *gin.Context) { } switch c.Query(setting.Subresource) { - case setting.StatusSubresource: - ctx.Resp, ctx.Err = service.GetProductStatus(projectName, ctx.Logger) - return case setting.IngressSubresource: ctx.Resp, ctx.Err = service.GetProductIngress(projectName, ctx.Logger) return diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 54d0991b..c23d5c36 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -89,7 +89,7 @@ type ProductResp struct { ProductName string `json:"product_name"` Namespace string `json:"namespace"` Status string `json:"status"` - Error string `json:"error"` + Error string `json:"error,omitempty"` EnvName string `json:"env_name"` UpdateBy string `json:"update_by"` UpdateTime int64 `json:"update_time"` @@ -98,6 +98,7 @@ type ProductResp struct { Vars []*template.RenderKV `json:"vars"` IsPublic bool `json:"isPublic"` ClusterID string `json:"cluster_id,omitempty"` + ClusterName string `json:"cluster_name,omitempty"` RecycleDay int `json:"recycle_day"` IsProd bool `json:"is_prod"` Source string `json:"source"` @@ -147,30 +148,6 @@ type RawYamlResp struct { type intervalExecutorHandler func(data *commonmodels.Service, log *zap.SugaredLogger) error -func GetProductStatus(productName string, log *zap.SugaredLogger) ([]*EnvStatus, error) { - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) - if err != nil { - log.Errorf("Collection.Product.List List product error: %v", err) - return nil, e.ErrListProducts.AddDesc(err.Error()) - } - - envStatusSlice := make([]*EnvStatus, 0) - for _, publicProduct := range products { - if publicProduct.ProductName != productName { - continue - } - envStatus := &EnvStatus{ - EnvName: publicProduct.EnvName, - Status: publicProduct.Status, - } - if len(publicProduct.Error) > 0 { - envStatus.ErrMessage = publicProduct.Error - } - envStatusSlice = append(envStatusSlice, envStatus) - } - return envStatusSlice, err -} - func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*ProductResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) if err != nil { @@ -198,9 +175,11 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) for _, env := range envs { clusterID := env.ClusterID production := false + clusterName := "" cluster, ok := clusterMap[clusterID] if ok { production = cluster.Production + clusterName = cluster.Name } res = append(res, &ProductResp{ @@ -211,12 +190,15 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) Vars: env.Vars[:], IsPublic: env.IsPublic, ClusterID: env.ClusterID, + ClusterName: clusterName, UpdateTime: env.UpdateTime, UpdateBy: env.UpdateBy, RecycleDay: env.RecycleDay, Render: env.Render, Source: env.Source, IsProd: production, + Status: env.Status, + Error: env.Error, }) } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index c91818ea..88d38802 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -206,6 +206,7 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar return prodResp } prodResp.IsProd = cluster.Production + prodResp.ClusterName = cluster.Name if !clusterService.ClusterConnected(prod.ClusterID) { prodResp.Status = setting.ClusterDisconnected -- Gitee From e2ca01823baee1a692acbcfc6393a7673df50ca3 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 29 Nov 2021 18:55:58 +0800 Subject: [PATCH 0806/2647] update after review Signed-off-by: lou --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index c23d5c36..882f9379 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -89,7 +89,7 @@ type ProductResp struct { ProductName string `json:"product_name"` Namespace string `json:"namespace"` Status string `json:"status"` - Error string `json:"error,omitempty"` + Error string `json:"error"` EnvName string `json:"env_name"` UpdateBy string `json:"update_by"` UpdateTime int64 `json:"update_time"` -- Gitee From 3c091deb82da42be222fb66e6a5adab18aaa27fd Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 29 Nov 2021 19:43:06 +0800 Subject: [PATCH 0807/2647] fix env apis Signed-off-by: lou --- .../core/environment/handler/environment.go | 13 ----------- .../aslan/core/environment/handler/router.go | 1 - .../core/environment/service/environment.go | 23 ------------------- .../cron/core/service/client/env.go | 2 +- .../core/service/taskplugin/deploy.go | 4 ++-- pkg/shared/client/aslan/environment.go | 22 +++++++++--------- 6 files changed, 14 insertions(+), 51 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 5751b09b..4eda4f87 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -373,19 +373,6 @@ func GetProductInfo(c *gin.Context) { ctx.Resp, ctx.Err = service.GetProductInfo(ctx.UserName, envName, projectName, ctx.Logger) } -func ListRenderCharts(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - envName := c.Param("name") - projectName := c.Query("projectName") - - ctx.Resp, ctx.Err = service.ListRenderCharts(projectName, envName, ctx.Logger) - if ctx.Err != nil { - ctx.Logger.Errorf("failed to listRenderCharts %s %s: %v", envName, projectName, ctx.Err) - } -} - func GetHelmChartVersions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 49239285..625df8a2 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -103,7 +103,6 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.PUT("/:name/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) environments.GET("/:name/helmChartVersions", GetHelmChartVersions) environments.GET("/:name/productInfo", GetProductInfo) - environments.GET("/:name/helmRenderCharts", ListRenderCharts) environments.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProduct) environments.GET("/:name/groups", ListGroups) environments.GET("/:name/workloads", ListWorkloadsInEnv) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 882f9379..277070fd 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1197,29 +1197,6 @@ func GetProductIngress(productName string, log *zap.SugaredLogger) ([]*ProductIn return productIngressInfos, nil } -// ListRenderCharts 获取集成环境中的values.yaml的值 -func ListRenderCharts(productName, envName string, log *zap.SugaredLogger) ([]*template.RenderChart, error) { - renderSetOpt := &commonrepo.RenderSetFindOption{Name: productName} - if envName != "" { - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - productResp, err := commonrepo.NewProductColl().Find(opt) - if err != nil { - log.Errorf("GetProduct envName:%s, productName:%s, err:%+v", envName, productName, err) - return nil, e.ErrListRenderSets.AddDesc(err.Error()) - } - - renderSetName := productResp.Namespace - renderSetOpt = &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: productResp.Render.Revision} - } - - renderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) - if err != nil { - log.Errorf("find helm renderset[%s] error: %v", productName, err) - return nil, e.ErrListRenderSets.AddDesc(err.Error()) - } - return renderSet.ChartInfos, nil -} - func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ([]*commonmodels.HelmVersions, error) { var ( helmVersions = make([]*commonmodels.HelmVersions, 0) diff --git a/pkg/microservice/cron/core/service/client/env.go b/pkg/microservice/cron/core/service/client/env.go index b50274e0..43fdbc85 100644 --- a/pkg/microservice/cron/core/service/client/env.go +++ b/pkg/microservice/cron/core/service/client/env.go @@ -62,7 +62,7 @@ func (c *Client) GetEnvService(productName, envName string, log *zap.SugaredLogg err error envObj = new(service.ProductResp) ) - url := fmt.Sprintf("%s/environment/environments/%s?envName=%s", c.APIBase, productName, envName) + url := fmt.Sprintf("%s/environment/environments/%s?projectName=%s", c.APIBase, envName, productName) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("GetService new http request error: %v", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 1748ab51..6cc3f935 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -564,10 +564,10 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } func (p *DeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs) (*types.Product, error) { - url := fmt.Sprintf("/api/environment/environments/%s/productInfo", args.ProductName) + url := fmt.Sprintf("/api/environment/environments/%s/productInfo", args.EnvName) prod := &types.Product{} - _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("envName", args.EnvName)) + _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("projectName", args.ProductName)) if err != nil { return nil, err } diff --git a/pkg/shared/client/aslan/environment.go b/pkg/shared/client/aslan/environment.go index 1a82a5db..a1387054 100644 --- a/pkg/shared/client/aslan/environment.go +++ b/pkg/shared/client/aslan/environment.go @@ -27,7 +27,7 @@ func (c *Client) ListEnvironments(projectName string) ([]*Environment, error) { url := "/environment/environments" res := make([]*Environment, 0) - _, err := c.Get(url, httpclient.SetQueryParam("productName", projectName), httpclient.SetResult(&res)) + _, err := c.Get(url, httpclient.SetQueryParam("projectName", projectName), httpclient.SetResult(&res)) if err != nil { return nil, err } @@ -36,10 +36,10 @@ func (c *Client) ListEnvironments(projectName string) ([]*Environment, error) { } func (c *Client) GetEnvironment(envName, projectName string) (*Environment, error) { - url := fmt.Sprintf("/environment/environments/%s", projectName) + url := fmt.Sprintf("/environment/environments/%s", envName) res := &Environment{} - _, err := c.Get(url, httpclient.SetQueryParam("envName", envName), httpclient.SetResult(res)) + _, err := c.Get(url, httpclient.SetQueryParam("projectName", projectName), httpclient.SetResult(res)) if err != nil { return nil, err } @@ -48,10 +48,10 @@ func (c *Client) GetEnvironment(envName, projectName string) (*Environment, erro } func (c *Client) ListHelmServicesInEnvironment(envName, projectName string) ([]*Service, error) { - url := fmt.Sprintf("/environment/environments/%s/groups/helm", projectName) + url := fmt.Sprintf("/environment/environments/%s/groups", envName) res := &ServicesResp{} - _, err := c.Get(url, httpclient.SetQueryParam("envName", envName), httpclient.SetResult(res)) + _, err := c.Get(url, httpclient.SetQueryParam("projectName", projectName), httpclient.SetResult(res)) if err != nil { return nil, err } @@ -60,20 +60,20 @@ func (c *Client) ListHelmServicesInEnvironment(envName, projectName string) ([]* } func (c *Client) ListServices(envName, projectName string) ([]*Service, error) { - url := fmt.Sprintf("/environment/environments/%s/groups", projectName) + url := fmt.Sprintf("/environment/environments/%s/groups", envName) res := make([]*Service, 0) - _, err := c.Get(url, httpclient.SetQueryParam("envName", envName), httpclient.SetResult(&res)) + _, err := c.Get(url, httpclient.SetQueryParam("projectName", projectName), httpclient.SetResult(&res)) return res, err } func (c *Client) GetServiceDetail(projectName, serviceName, envName string) (*ServiceDetail, error) { - url := fmt.Sprintf("/environment/environments/%s/services/%s", projectName, serviceName) + url := fmt.Sprintf("/environment/environments/%s/services/%s", envName, serviceName) res := &ServiceDetail{} req := map[string]string{ - "envName": envName, + "projectName": projectName, } _, err := c.Get(url, httpclient.SetQueryParams(req), httpclient.SetResult(res)) if err != nil { @@ -100,7 +100,7 @@ func (c *Client) ListServicesStatusByEnvironment(envName, projectName string) ([ for _, s := range ss { res = append(res, &ServiceStatus{ ServiceName: s.ServiceName, - Status: s.Status, + Status: s.Status, }) } @@ -113,7 +113,7 @@ func (c *Client) ListServicesStatusByEnvironment(envName, projectName string) ([ for _, s := range ss { res = append(res, &ServiceStatus{ ServiceName: s.ServiceName, - Status: s.Status, + Status: s.Status, }) } } -- Gitee From c5280e6384faed526cd6fca78131c73aaa64b37a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 29 Nov 2021 19:54:32 +0800 Subject: [PATCH 0808/2647] add affinity Signed-off-by: liu deyi --- .../service/taskplugin/artifact_deploy.go | 2 +- .../core/service/taskplugin/build.go | 2 +- .../core/service/taskplugin/docker_build.go | 2 +- .../core/service/taskplugin/jenkins_plugin.go | 2 +- .../warpdrive/core/service/taskplugin/job.go | 61 +++++++++++++++++-- .../core/service/taskplugin/release_image.go | 2 +- .../core/service/taskplugin/testing.go | 2 +- 7 files changed, 63 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index c3a06aaf..875da7a6 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -215,7 +215,7 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, "", p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 95e334f3..75525a35 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -245,7 +245,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index cc02d9cb..53cfffa2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -196,7 +196,7 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi return } - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, "", setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 5e38031f..eef51210 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -158,7 +158,7 @@ func (j *JenkinsBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, p j.Log.Infof("succeed to create cm for jenkins build job %s", j.JobName) j.Log.Infof("JenkinsBuildConfig : %+v", pipelineTask.ConfigPayload.JenkinsBuildConfig) - job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, "", setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create jenkins build job context error: %v", err) j.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1d13553e..72f5009b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -57,9 +57,10 @@ const ( defaultSecretEmail = "bot@koderover.com" PredatorPlugin = "predator-plugin" JenkinsPlugin = "jenkins-plugin" -) + NormalSchedule = "normal" + RequiredSchedule = "required" + PreferredSchedule = "preferred" -const ( registrySecretSuffix = "-registry-secret" ) @@ -381,12 +382,13 @@ func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx st //"s-job": pipelinename-taskid-tasktype-servicename, //"s-task": pipelinename-taskid, //"s-type": tasktype, -func buildJob(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace) (*batchv1.Job, error) { +func buildJob(taskType config.TaskType, jobImage, jobName, serviceName, clusterID string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace) (*batchv1.Job, error) { return buildJobWithLinkedNs( taskType, jobImage, jobName, serviceName, + clusterID, resReq, ctx, pipelineTask, @@ -396,7 +398,7 @@ func buildJob(taskType config.TaskType, jobImage, jobName, serviceName string, r ) } -func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { +func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName, clusterID string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { var reaperBootingScript string if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { @@ -805,3 +807,54 @@ func checkDogFoodExistsInContainer(namespace string, pod string, container strin return success, err } + +func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.Affinity { + clusterConfig := findClusterConfig(clusterID, K8SClusters) + if clusterConfig == nil { + return nil + } + + switch clusterConfig.Strategy { + case NormalSchedule: + return nil + case RequiredSchedule: + if len(clusterConfig.NodeLabels) == 0 { + return nil + } + nodeSelectorTerms := make([]corev1.NodeSelectorTerm, 0) + for _, nodeLabel := range clusterConfig.NodeLabels { + if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { + continue + } + matchExpressions := make([]corev1.NodeSelectorRequirement, 0) + matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ + Key: strings.Split(nodeLabel, ":")[0], + Operator: "in", + Values: []string{strings.Split(nodeLabel, ":")[1]}, + }) + nodeSelectorTerms = append(nodeSelectorTerms, corev1.NodeSelectorTerm{ + MatchExpressions: matchExpressions, + }) + } + affinity := &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + NodeSelectorTerms: nodeSelectorTerms, + }, + }, + } + return affinity + case PreferredSchedule: + + } + return nil +} + +func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.ClusterConfig { + for _, K8SCluster := range K8SClusters { + if K8SCluster.ID.Hex() == clusterID { + return K8SCluster.ClusterConfig + } + } + return nil +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 08495bea..27dc3393 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -176,7 +176,7 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p } p.Log.Infof("succeed to create cm for image job %s", p.JobName) - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, "", setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create release image job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 44348007..4107b646 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -200,7 +200,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC // search namespace should also include desired namespace job, err := buildJobWithLinkedNs( - p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries, + p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries, p.KubeNamespace, linkedNamespace, ) -- Gitee From 8d09f3f809b8560c76f7dafe44a2ca05389ed5ec Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 29 Nov 2021 20:50:01 +0800 Subject: [PATCH 0809/2647] Increase the processing of file logic Signed-off-by: liu deyi --- .../reaper/core/service/reaper/artifact.go | 46 ++++++++++++------- pkg/util/fs/fs.go | 12 +++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index ffadd1c1..5120492b 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -30,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util/fs" ) func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths []string, pluginType ...string) error { @@ -87,26 +88,37 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } artifactPath := filepath.Join(activeWorkspace, artifactPaths[0]) - temp, err := ioutil.TempFile("", "*artifact.tar.gz") - if err != nil { - log.Errorf("failed to create temp file %s", err) - return err - } + isDir := fs.IsDir(artifactPath) + if isDir { + temp, err := ioutil.TempFile("", "*artifact.tar.gz") + if err != nil { + log.Errorf("failed to create temp file %s", err) + return err + } - _ = temp.Close() - cmd := exec.Command("tar", "czf", temp.Name(), "-C", artifactPath, ".") - cmd.Dir = artifactPath - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - if err = cmd.Run(); err != nil { - log.Errorf("failed to compress artifact %s", err) - return err - } - if store != nil { - objectKey := store.GetObjectPath("artifact.tar.gz") - if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { + _ = temp.Close() + cmd := exec.Command("tar", "czf", temp.Name(), "-C", artifactPath, ".") + cmd.Dir = artifactPath + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + if err = cmd.Run(); err != nil { + log.Errorf("failed to compress artifact err:%s", err) return err } + if store != nil { + objectKey := store.GetObjectPath("artifact.tar.gz") + if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { + return err + } + } + } else { + st, _ := os.Stat(artifactPath) + if store != nil { + objectKey := store.GetObjectPath(artifactPath) + if err = s3FileUpload(store, st.Name(), objectKey); err != nil { + return err + } + } } return nil diff --git a/pkg/util/fs/fs.go b/pkg/util/fs/fs.go index a6b2bcd0..003c85be 100644 --- a/pkg/util/fs/fs.go +++ b/pkg/util/fs/fs.go @@ -90,3 +90,15 @@ func SaveFile(src io.ReadCloser, dst string) error { _, err = io.Copy(file, src) return err } + +// Determine whether the given path is a folder +func IsDir(path string) bool { + s, err := os.Stat(path) + if err != nil { + if !os.IsNotExist(err) { + return false + } + return false + } + return s.IsDir() +} -- Gitee From b013c2514359772b58fb5b6123098efd2461dfd0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 29 Nov 2021 21:20:20 +0800 Subject: [PATCH 0810/2647] Increase the processing of file logic Signed-off-by: liu deyi --- .../core/service/taskplugin/trigger.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 84f880df..69be95e5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -29,9 +29,11 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/log" + s3tool "github.com/koderover/zadig/pkg/tool/s3" ) const ( @@ -130,7 +132,6 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi if err != nil { return } - p.Log.Infof("artifactPath:%s", artifactPath) taskOutput := &task.TaskOutput{ Type: "object_storage", Value: artifactPath, @@ -166,7 +167,20 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error } else { subPath = fmt.Sprintf("%s/%s/%s", pipelineTask.PipelineName, pipelineTask.ServiceName, "artifact") } - return fmt.Sprintf("%s/%s/artifact.tar.gz", store.Endpoint, subPath), nil + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + return "", err + } + prefix := store.GetObjectPath("") + files, err := s3client.ListFiles(store.Bucket, prefix, false) + if err != nil { + return "", err + } + return fmt.Sprintf("%s/%s/%s", store.Endpoint, subPath, files[0]), nil } // Wait ... -- Gitee From e19289785c2433c1ba374729e95b92df8cc43d11 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 29 Nov 2021 21:52:27 +0800 Subject: [PATCH 0811/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/artifact.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 5120492b..d2b75c5e 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -115,6 +115,8 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] st, _ := os.Stat(artifactPath) if store != nil { objectKey := store.GetObjectPath(artifactPath) + log.Infof("st.Name():%+v", st.Name()) + log.Infof("objectKey:%s", objectKey) if err = s3FileUpload(store, st.Name(), objectKey); err != nil { return err } -- Gitee From d8d7bd48a22ff92b0549d3657e7f407da448495e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 29 Nov 2021 21:56:44 +0800 Subject: [PATCH 0812/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/artifact.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index d2b75c5e..d4048cdb 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -107,6 +107,8 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } if store != nil { objectKey := store.GetObjectPath("artifact.tar.gz") + log.Infof("temp.Name():%+v", temp.Name()) + log.Infof("objectKey:%+v", objectKey) if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { return err } -- Gitee From 3eddb17153dfe09a338b4b828f743762c5f5e299 Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 29 Nov 2021 22:57:29 +0800 Subject: [PATCH 0813/2647] fix create-env permission Signed-off-by: lou --- .../common/repository/mongodb/k8s_cluster.go | 37 ++++++ .../aslan/core/environment/handler/bundle.go | 30 +---- .../core/environment/handler/environment.go | 10 ++ .../core/environment/handler/policy.yaml | 10 ++ .../aslan/core/environment/handler/router.go | 5 + .../aslan/core/environment/service/bundle.go | 108 ++---------------- .../core/environment/service/environment.go | 2 +- .../aslan/core/multicluster/handler/bundle.go | 31 +++++ .../core/multicluster/handler/clusters.go | 10 +- .../aslan/core/multicluster/handler/router.go | 5 + .../aslan/core/multicluster/service/bundle.go | 51 +++++++++ .../core/multicluster/service/clusters.go | 35 +++++- pkg/microservice/aslan/server/rest/router.go | 5 - .../policy/core/handler/bundle.go | 7 ++ pkg/microservice/policy/core/service.go | 2 +- .../policy/core/service/bundle/opa_bundle.go | 5 +- .../core/service/bundle/rego/authz.rego | 16 ++- .../core/service/bundle/resource_bundle.go | 100 ++++++++++++++++ pkg/microservice/policy/server/rest/server.go | 2 +- pkg/shared/handler/base.go | 8 +- 20 files changed, 339 insertions(+), 140 deletions(-) create mode 100644 pkg/microservice/aslan/core/multicluster/handler/bundle.go create mode 100644 pkg/microservice/aslan/core/multicluster/service/bundle.go create mode 100644 pkg/microservice/policy/core/service/bundle/resource_bundle.go diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 31f5efc8..80e34947 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -30,6 +30,10 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) +type ClusterListOpts struct { + IDs []string +} + type K8SClusterColl struct { *mongo.Collection @@ -108,6 +112,39 @@ func (c *K8SClusterColl) HasDuplicateName(id, name string) (bool, error) { return count > 0, nil } +func (c *K8SClusterColl) List(opts *ClusterListOpts) ([]*models.K8SCluster, error) { + var clusters []*models.K8SCluster + + if opts == nil { + opts = &ClusterListOpts{} + } + + query := bson.M{} + if len(opts.IDs) > 0 { + var ids []primitive.ObjectID + for _, id := range opts.IDs { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + ids = append(ids, oid) + } + + query["_id"] = bson.M{"$in": ids} + } + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &clusters) + if err != nil { + return nil, err + } + + return clusters, err +} + func (c *K8SClusterColl) Find(clusterType string) ([]*models.K8SCluster, error) { var clusters []*models.K8SCluster diff --git a/pkg/microservice/aslan/core/environment/handler/bundle.go b/pkg/microservice/aslan/core/environment/handler/bundle.go index 775a120f..aa8dac3b 100644 --- a/pkg/microservice/aslan/core/environment/handler/bundle.go +++ b/pkg/microservice/aslan/core/environment/handler/bundle.go @@ -17,33 +17,15 @@ limitations under the License. package handler import ( - "net/http" - "path/filepath" - "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" - "github.com/koderover/zadig/pkg/tool/log" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -func DownloadBundle(c *gin.Context) { - if err := service.GenerateOPABundle(); err != nil { - log.Errorf("Failed to generate OPA bundle, err: %s", err) - c.String(http.StatusInternalServerError, "bundle generation failure, err: %s", err) - return - } - - revision := service.GetRevision() - matching := c.GetHeader("If-None-Match") - if revision != "" && revision == matching { - c.Status(http.StatusNotModified) - return - } - - c.Header("Content-Type", "application/gzip") - if revision != "" { - c.Header("Etag", revision) - } - c.File(filepath.Join(config.DataPath(), c.Param("name"))) +func GetBundleResources(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetBundleResources() } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 4eda4f87..09efcf4d 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -181,6 +181,16 @@ func CreateProduct(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateProduct json.Unmarshal err : %v", err) } + + allowedClusters, found := internalhandler.GetResourcesInHeader(c) + if found { + allowedSet := sets.NewString(allowedClusters...) + if !allowedSet.Has(args.ClusterID) { + c.String(http.StatusForbidden, "permission denied for cluster %s", args.ClusterID) + return + } + } + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index e62ca40b..4447b0ff 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -57,12 +57,22 @@ rules: rules: - method: POST endpoint: "/api/aslan/environment/environments" + resourceType: "Cluster" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/service/workloads" - method: GET endpoint: "/api/aslan/project/products/?*/services" - method: GET endpoint: "/api/aslan/delivery/releases" + - method: GET + endpoint: "api/aslan/cluster/clusters" + resourceType: "Cluster" + matchAttributes: + - key: "production" + value: "false" - action: config_environment alias: "配置集成环境" description: "" diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 625df8a2..f5bb3dd9 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -135,4 +135,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { revision.GET("/products", ListProductsRevision) } + + bundles := router.Group("bundle-resources") + { + bundles.GET("", GetBundleResources) + } } diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 91b2a766..114b2f92 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -17,105 +17,21 @@ limitations under the License. package service import ( - "sort" "strconv" - "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/tool/opa" ) -const ( - resourcesPath = "resources/data.json" - - resourcesRoot = "resources" - - EnvironmentType = "Environment" -) - -var revision string - -type opaBundleMeta struct { - ResourceID string `json:"resourceID"` - ProjectName string `json:"projectName"` -} - -type opaMeta interface { - Meta() *opaBundleMeta -} - -type opaResources map[string]resources - -type resources []opaMeta - -func (o resources) Len() int { return len(o) } -func (o resources) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o resources) Less(i, j int) bool { - metaI := o[i].Meta() - metaJ := o[j].Meta() - if metaI.ProjectName == metaJ.ProjectName { - return metaI.ResourceID < metaJ.ResourceID - } - return metaI.ProjectName < metaJ.ProjectName -} - -func AppendOPAResources(res opaResources, resourceType string, objs []opaMeta) opaResources { - if res == nil { - res = make(map[string]resources) - } - - sort.Sort(resources(objs)) - res[resourceType] = objs - - return res -} - -func GenerateOPABundle() error { - log.Info("Generating OPA bundle") - defer log.Info("OPA bundle is generated") - - objs, err := GenerateEnvironmentBundle() - if err != nil { - log.Errorf("Failed to generate environment bundle, err: %s", err) - return err - } - - res := AppendOPAResources(nil, EnvironmentType, objs) - - bundle := &opa.Bundle{ - Data: []*opa.DataSpec{ - {Data: res, Path: resourcesPath}, - }, - Roots: []string{resourcesRoot}, - } - - hash, err := bundle.Rehash() - if err != nil { - log.Errorf("Failed to calculate bundle hash, err: %s", err) - return err - } - revision = hash - - return bundle.Save(config.DataPath()) -} - -func GetRevision() string { - return revision -} - -type EnvironmentBundle struct { - *opaBundleMeta - Production string `json:"production"` -} - -func (b *EnvironmentBundle) Meta() *opaBundleMeta { - return b.opaBundleMeta +type resourceSpec struct { + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec map[string]interface{} `json:"spec"` } -func GenerateEnvironmentBundle() ([]opaMeta, error) { - var res []opaMeta +func GetBundleResources() ([]*resourceSpec, error) { + var res []*resourceSpec envs, err := mongodb.NewProductColl().List(nil) if err != nil { @@ -124,7 +40,7 @@ func GenerateEnvironmentBundle() ([]opaMeta, error) { } clusterMap := make(map[string]*models.K8SCluster) - clusters, err := mongodb.NewK8SClusterColl().Find("") + clusters, err := mongodb.NewK8SClusterColl().List(nil) if err != nil { log.Errorf("Failed to list clusters in db, err: %s", err) return nil, err @@ -142,12 +58,12 @@ func GenerateEnvironmentBundle() ([]opaMeta, error) { production = cluster.Production } - res = append(res, &EnvironmentBundle{ - opaBundleMeta: &opaBundleMeta{ - ResourceID: env.EnvName, - ProjectName: env.ProductName, + res = append(res, &resourceSpec{ + ResourceID: env.EnvName, + ProjectName: env.ProductName, + Spec: map[string]interface{}{ + "production": strconv.FormatBool(production), }, - Production: strconv.FormatBool(production), }) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 277070fd..cc4db510 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -161,7 +161,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) } clusterMap := make(map[string]*commonmodels.K8SCluster) - clusters, err := commonrepo.NewK8SClusterColl().Find("") + clusters, err := commonrepo.NewK8SClusterColl().List(nil) if err != nil { log.Errorf("Failed to list clusters in db, err: %s", err) return nil, err diff --git a/pkg/microservice/aslan/core/multicluster/handler/bundle.go b/pkg/microservice/aslan/core/multicluster/handler/bundle.go new file mode 100644 index 00000000..1c67c3ef --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/handler/bundle.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetBundleResources(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetBundleResources() +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index d84ea096..42617c97 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -32,9 +32,13 @@ func ListClusters(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListClusters(c.Query("clusterType"), - ctx.Logger, - ) + clusters, found := internalhandler.GetResourcesInHeader(c) + if found && len(clusters) == 0 { + ctx.Resp = []*service.K8SCluster{} + return + } + + ctx.Resp, ctx.Err = service.ListClusters(clusters, ctx.Logger) } func GetCluster(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 7d03a0a4..22438e95 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -40,4 +40,9 @@ func (*Router) Inject(router *gin.RouterGroup) { Cluster.PUT("/:id/disconnect", DisconnectCluster) Cluster.PUT("/:id/reconnect", ReconnectCluster) } + + bundles := router.Group("bundle-resources") + { + bundles.GET("", GetBundleResources) + } } diff --git a/pkg/microservice/aslan/core/multicluster/service/bundle.go b/pkg/microservice/aslan/core/multicluster/service/bundle.go new file mode 100644 index 00000000..190943c1 --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/service/bundle.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "strconv" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/log" +) + +type resourceSpec struct { + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec map[string]interface{} `json:"spec"` +} + +func GetBundleResources() ([]*resourceSpec, error) { + var res []*resourceSpec + clusters, err := mongodb.NewK8SClusterColl().List(nil) + if err != nil { + log.Errorf("Failed to list clusters in db, err: %s", err) + return nil, err + } + + for _, cluster := range clusters { + res = append(res, &resourceSpec{ + ResourceID: cluster.ID.Hex(), + ProjectName: "", + Spec: map[string]interface{}{ + "production": strconv.FormatBool(cluster.Production), + }, + }) + } + + return res, nil +} diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index f5eb7b7c..cfef21af 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -30,10 +30,39 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListClusters(clusterType string, logger *zap.SugaredLogger) ([]*commonmodels.K8SCluster, error) { - s, _ := kube.NewService("") +type K8SCluster struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status config.K8SClusterStatus `json:"status"` + Production bool `json:"production"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Provider int8 `json:"provider"` +} + +func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { + cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: ids}) + if err != nil { + logger.Errorf("Failed to list clusters, err: %s", err) + return nil, err + } + + var res []*K8SCluster + for _, c := range cs { + res = append(res, &K8SCluster{ + ID: c.ID.Hex(), + Name: c.Name, + Description: c.Description, + Status: c.Status, + Production: c.Production, + CreatedBy: c.CreatedBy, + CreatedAt: c.CreatedAt, + Provider: c.Provider, + }) + } - return s.ListClusters(clusterType, logger) + return res, nil } func GetCluster(id string, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index edb0648b..960dd790 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -68,11 +68,6 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { router.GET("/api/kodespace/downloadUrl", commonhandler.GetToolDownloadURL) - bundles := router.Group("/api/bundles") - { - bundles.GET("/:name", environmenthandler.DownloadBundle) - } - for name, r := range map[string]injector{ "/api/project": new(projecthandler.Router), "/api/code": new(codehosthandler.Router), diff --git a/pkg/microservice/policy/core/handler/bundle.go b/pkg/microservice/policy/core/handler/bundle.go index 7f3e1b76..71bd6c7c 100644 --- a/pkg/microservice/policy/core/handler/bundle.go +++ b/pkg/microservice/policy/core/handler/bundle.go @@ -24,9 +24,16 @@ import ( "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/service/bundle" + "github.com/koderover/zadig/pkg/tool/log" ) func DownloadBundle(c *gin.Context) { + if err := bundle.GenerateOPABundle(); err != nil { + log.Errorf("Failed to generate OPA bundle, err: %s", err) + c.String(http.StatusInternalServerError, "bundle generation failure, err: %s", err) + return + } + revision := bundle.GetRevision() matching := c.GetHeader("If-None-Match") if revision != "" && revision == matching { diff --git a/pkg/microservice/policy/core/service.go b/pkg/microservice/policy/core/service.go index 3576ee08..b4661431 100644 --- a/pkg/microservice/policy/core/service.go +++ b/pkg/microservice/policy/core/service.go @@ -67,7 +67,7 @@ func Start(ctx context.Context) { go StartControllers(ctx.Done()) - bundle.RefreshOPABundle() + bundle.GenerateOPABundle() } func Stop(ctx context.Context) { diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 122a95c5..90936cf8 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -33,11 +33,13 @@ const ( rolesPath = "roles/data.json" rolebindingsPath = "bindings/data.json" exemptionsPath = "exemptions/data.json" + resourcesPath = "resources/data.json" policyRoot = "rbac" rolesRoot = "roles" rolebindingsRoot = "bindings" exemptionsRoot = "exemptions" + resourcesRoot = "resources" ) type expressionOperator string @@ -301,8 +303,9 @@ func GenerateOPABundle() error { {Data: generateOPARoles(rs, ps), Path: rolesPath}, {Data: generateOPARoleBindings(bs), Path: rolebindingsPath}, {Data: generateOPAExemptionURLs(ps), Path: exemptionsPath}, + {Data: generateResourceBundle(), Path: resourcesPath}, }, - Roots: []string{policyRoot, rolesRoot, rolebindingsRoot, exemptionsRoot}, + Roots: []string{policyRoot, rolesRoot, rolebindingsRoot, exemptionsRoot, resourcesRoot}, } hash, err := bundle.Rehash() diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 2179b7c8..150a8074 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -102,11 +102,19 @@ user_allowed_resources[resourceID] { user_matched_rule_for_filtering[rule] res := data.resources[rule.resourceType][_] - res.projectName == project_name + project_name_is_match(res) not attributes_mismatch(rule.matchAttributes, res) resourceID := res.resourceID } +project_name_is_match(res) { + res.projectName == project_name +} + +project_name_is_match(res) { + res.projectName == "" +} + user_matched_rule_for_filtering[rule] { some rule @@ -119,7 +127,7 @@ user_matched_rule_for_filtering[rule] { all_attributes_match(attributes, resourceType, resourceID) { res := data.resources[resourceType][_] res.resourceID == resourceID - res.projectName == project_name + project_name_is_match(res) # a && b <=> !(!a || !b), De Morgan’s laws, see details in https://www.fugue.co/blog/5-tips-for-using-the-rego-language-for-open-policy-agent-opa not attributes_mismatch(attributes, res) @@ -131,11 +139,11 @@ attributes_mismatch(attributes, res) { } attribute_mismatch(attribute, res) { - res[attribute.key] != attribute.value + res.spec[attribute.key] != attribute.value } attribute_mismatch(attribute, res) { - not res[attribute.key] + not res.spec[attribute.key] } get_resource_id(idRegex) = id { diff --git a/pkg/microservice/policy/core/service/bundle/resource_bundle.go b/pkg/microservice/policy/core/service/bundle/resource_bundle.go new file mode 100644 index 00000000..5bb3abef --- /dev/null +++ b/pkg/microservice/policy/core/service/bundle/resource_bundle.go @@ -0,0 +1,100 @@ +package bundle + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" +) + +const ( + EnvironmentType = "Environment" + ClusterType = "Cluster" +) + +type resourceBundleService struct { + endpoint, resourceType string +} + +var resourceBundleMap = map[string]resourceBundleService{ + EnvironmentType: {resourceType: EnvironmentType, endpoint: fmt.Sprintf("%s/api/environment/bundle-resources", config.AslanServiceAddress())}, + ClusterType: {resourceType: ClusterType, endpoint: fmt.Sprintf("%s/api/cluster/bundle-resources", config.AslanServiceAddress())}, +} + +type ResourceSpec struct { + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec map[string]interface{} `json:"spec"` +} + +type ResourceBundle map[string]resources + +type resources []*ResourceSpec + +func (o resources) Len() int { return len(o) } +func (o resources) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o resources) Less(i, j int) bool { + if o[i].ProjectName == o[j].ProjectName { + return o[i].ResourceID < o[j].ResourceID + } + return o[i].ProjectName < o[j].ProjectName +} + +func (r ResourceBundle) MarshalJSON() ([]byte, error) { + var keys []string + for k := range r { + keys = append(keys, k) + } + sort.Strings(keys) + + buf := &bytes.Buffer{} + buf.Write([]byte{'{'}) + for i, key := range keys { + value := r[key] + b, err := json.Marshal(&value) + if err != nil { + return nil, err + } + buf.WriteString(fmt.Sprintf("%q:", key)) + buf.Write(b) + if i < len(keys)-1 { + buf.Write([]byte{','}) + } + } + buf.Write([]byte{'}'}) + + return buf.Bytes(), nil +} + +func AppendOPAResources(res ResourceBundle, resourceType string, objs []*ResourceSpec) ResourceBundle { + if res == nil { + res = make(map[string]resources) + } + + sort.Sort(resources(objs)) + res[resourceType] = objs + + return res +} + +func generateResourceBundle() ResourceBundle { + var res ResourceBundle + + cl := httpclient.New() + for resourceType, bundleService := range resourceBundleMap { + objs := make([]*ResourceSpec, 0) + _, err := cl.Get(bundleService.endpoint, httpclient.SetResult(&objs)) + if err != nil { + log.Warnf("Failed to get %s bundle, err: %s", resourceType, err) + continue + } + + res = AppendOPAResources(res, resourceType, objs) + } + + return res +} diff --git a/pkg/microservice/policy/server/rest/server.go b/pkg/microservice/policy/server/rest/server.go index 345e1b43..2156f21b 100644 --- a/pkg/microservice/policy/server/rest/server.go +++ b/pkg/microservice/policy/server/rest/server.go @@ -55,7 +55,7 @@ func (s *engine) injectMiddlewares() { g.Use(ginmiddleware.Response()) g.Use(ginmiddleware.RequestID()) g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) - g.Use(RefreshOPABundle()) + //g.Use(RefreshOPABundle()) g.Use(gin.Recovery()) } diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index e7617496..ee84aded 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -73,7 +73,13 @@ func GetResourcesInHeader(c *gin.Context) ([]string, bool) { if !ok { return nil, false } - return strings.Split(c.GetHeader(setting.ResourcesHeader), ","), true + + res := c.GetHeader(setting.ResourcesHeader) + if res == "" { + return nil, true + } + + return strings.Split(res, ","), true } func getUserFromJWT(token string) (jwtClaims, error) { -- Gitee From 5e6bb84b1f37b66a90e177f52338e111a851f08c Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 30 Nov 2021 09:44:17 +0800 Subject: [PATCH 0814/2647] fix rego Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 150a8074..f8b10088 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -108,6 +108,7 @@ user_allowed_resources[resourceID] { } project_name_is_match(res) { + res.projectName != "" res.projectName == project_name } -- Gitee From 4c3accfa7c633b43ffd83122dcd0748d5d07f6a2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 09:48:37 +0800 Subject: [PATCH 0815/2647] upload file Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/artifact.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index d4048cdb..35b1747c 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -107,19 +107,14 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } if store != nil { objectKey := store.GetObjectPath("artifact.tar.gz") - log.Infof("temp.Name():%+v", temp.Name()) - log.Infof("objectKey:%+v", objectKey) if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { return err } } } else { - st, _ := os.Stat(artifactPath) if store != nil { objectKey := store.GetObjectPath(artifactPath) - log.Infof("st.Name():%+v", st.Name()) - log.Infof("objectKey:%s", objectKey) - if err = s3FileUpload(store, st.Name(), objectKey); err != nil { + if err = s3FileUpload(store, artifactPath, objectKey); err != nil { return err } } -- Gitee From 30d30058627841d159475ce08b973e12dcc99f41 Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 30 Nov 2021 13:53:08 +0800 Subject: [PATCH 0816/2647] update after review Signed-off-by: lou --- .../core/environment/service/environment.go | 40 ++++++++++--------- .../aslan/core/multicluster/service/bundle.go | 3 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index cc4db510..428d03e9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -84,6 +84,19 @@ type EnvStatus struct { ErrMessage string `json:"err_message"` } +type EnvResp struct { + ProjectName string `json:"projectName"` + Status string `json:"status"` + Error string `json:"error"` + Name string `json:"name"` + UpdateBy string `json:"updateBy"` + UpdateTime int64 `json:"updateTime"` + IsPublic bool `json:"isPublic"` + ClusterName string `json:"clusterName"` + Production bool `json:"production"` + Source string `json:"source"` +} + type ProductResp struct { ID string `json:"id"` ProductName string `json:"product_name"` @@ -148,18 +161,13 @@ type RawYamlResp struct { type intervalExecutorHandler func(data *commonmodels.Service, log *zap.SugaredLogger) error -func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*ProductResp, error) { +func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) if err != nil { log.Errorf("Failed to list envs, err: %s", err) return nil, e.ErrListEnvs.AddDesc(err.Error()) } - err = FillProductVars(envs, log) - if err != nil { - return nil, err - } - clusterMap := make(map[string]*commonmodels.K8SCluster) clusters, err := commonrepo.NewK8SClusterColl().List(nil) if err != nil { @@ -171,7 +179,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) clusterMap[cls.ID.Hex()] = cls } - var res []*ProductResp + var res []*EnvResp for _, env := range envs { clusterID := env.ClusterID production := false @@ -182,23 +190,17 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) clusterName = cluster.Name } - res = append(res, &ProductResp{ - ID: env.ID.Hex(), - ProductName: env.ProductName, - EnvName: env.EnvName, - Namespace: env.Namespace, - Vars: env.Vars[:], + res = append(res, &EnvResp{ + ProjectName: projectName, + Name: env.EnvName, IsPublic: env.IsPublic, - ClusterID: env.ClusterID, ClusterName: clusterName, - UpdateTime: env.UpdateTime, - UpdateBy: env.UpdateBy, - RecycleDay: env.RecycleDay, - Render: env.Render, Source: env.Source, - IsProd: production, + Production: production, Status: env.Status, Error: env.Error, + UpdateTime: env.UpdateTime, + UpdateBy: env.UpdateBy, }) } diff --git a/pkg/microservice/aslan/core/multicluster/service/bundle.go b/pkg/microservice/aslan/core/multicluster/service/bundle.go index 190943c1..2285a5b6 100644 --- a/pkg/microservice/aslan/core/multicluster/service/bundle.go +++ b/pkg/microservice/aslan/core/multicluster/service/bundle.go @@ -39,8 +39,7 @@ func GetBundleResources() ([]*resourceSpec, error) { for _, cluster := range clusters { res = append(res, &resourceSpec{ - ResourceID: cluster.ID.Hex(), - ProjectName: "", + ResourceID: cluster.ID.Hex(), Spec: map[string]interface{}{ "production": strconv.FormatBool(cluster.Production), }, -- Gitee From f34e1357e7793945ff034398f2c1c8c908562ff2 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 30 Nov 2021 14:32:02 +0800 Subject: [PATCH 0817/2647] generate kubeconfig (#664) * generate kubeconfig Signed-off-by: mouuii * generate kubeconfig Signed-off-by: mouuii * create rolebinding Signed-off-by: mouuii * create rolebinding Signed-off-by: mouuii * create rolebinding Signed-off-by: mouuii * create rolebinding Signed-off-by: mouuii * fix bug Signed-off-by: mouuii * remove code Signed-off-by: mouuii * filter product Signed-off-by: mouuii * create or patch serviceaccount Signed-off-by: mouuii * create or patch serviceaccount bug Signed-off-by: mouuii * create or patch serviceaccount bug Signed-off-by: mouuii * create or patch serviceaccount bug Signed-off-by: mouuii * remove v2 Signed-off-by: mouuii * code reviewer Signed-off-by: mouuii * code reviewer Signed-off-by: mouuii * code reviewer Signed-off-by: mouuii * code reviewer Signed-off-by: mouuii * code reviewer Signed-off-by: mouuii * get allowedprojects Signed-off-by: mouuii * union Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii --- pkg/microservice/aslan/config/config.go | 4 + pkg/microservice/aslan/config/consts.go | 7 + .../aslan/core/setting/handler/user.go | 4 +- .../aslan/core/setting/service/user.go | 361 +++++++++--------- .../picket/client/aslan/kubeconfig.go | 19 + pkg/microservice/picket/config/consts.go | 8 + .../picket/core/filter/handler/kubeconfig.go | 15 + .../picket/core/filter/handler/router.go | 5 + .../picket/core/filter/service/kubeconfig.go | 49 +++ .../picket/core/filter/service/workflow.go | 24 +- pkg/tool/kube/getter/role.go | 10 + pkg/tool/kube/updater/clusterrole.go | 4 + pkg/tool/kube/updater/rolebinding.go | 1 + 13 files changed, 331 insertions(+), 180 deletions(-) create mode 100644 pkg/microservice/picket/client/aslan/kubeconfig.go create mode 100644 pkg/microservice/picket/config/consts.go create mode 100644 pkg/microservice/picket/core/filter/handler/kubeconfig.go create mode 100644 pkg/microservice/picket/core/filter/service/kubeconfig.go diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 02da935d..c9c115c8 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -233,3 +233,7 @@ func LocalServicePathWithRevision(project, service string, revision int64) strin func ServiceNameWithRevision(serviceName string, revision int64) string { return fmt.Sprintf("%s-%d", serviceName, revision) } + +func ServiceAccountNameForUser(userID string) string { + return fmt.Sprintf("%s-sa", userID) +} diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 36376093..ca01f216 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -18,6 +18,8 @@ package config import ( "regexp" + + "github.com/koderover/zadig/pkg/setting" ) var ( @@ -220,3 +222,8 @@ const ( ImageResourceType = "image" TarResourceType = "tar" ) + +const ( + RoleBindingNameEdit = setting.ProductName+"-edit" + RoleBindingNameView = setting.ProductName+"-view" +) diff --git a/pkg/microservice/aslan/core/setting/handler/user.go b/pkg/microservice/aslan/core/setting/handler/user.go index 0dc66b84..b78904bb 100644 --- a/pkg/microservice/aslan/core/setting/handler/user.go +++ b/pkg/microservice/aslan/core/setting/handler/user.go @@ -27,5 +27,7 @@ func GetUserKubeConfig(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.UserName, ctx.Logger) + projectsEnvCanEdit := c.QueryArray("projectsEnvCanEdit") + projectsEnvCanView := c.QueryArray("projectsEnvCanView") + ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.UserID, projectsEnvCanEdit, projectsEnvCanView, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index 11eea83a..d4fc2c32 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -20,15 +20,9 @@ import ( "bytes" "encoding/base64" "errors" - "fmt" "html/template" - "io/ioutil" - "os" - "path/filepath" - "strings" - "sync" + "time" - "github.com/hashicorp/go-multierror" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" rbacv1beta1 "k8s.io/api/rbac/v1beta1" @@ -38,12 +32,13 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/wrapper" - e "github.com/koderover/zadig/pkg/tool/errors" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" ) type kubeCfgTmplArgs struct { @@ -56,91 +51,63 @@ type kubeCfgTmplArgs struct { ClientKeyBase64 string } -func GetUserKubeConfig(userName string, log *zap.SugaredLogger) (string, error) { - username := strings.ToLower(userName) - username = config.NameSpaceRegex.ReplaceAllString(username, "-") - var ( - err error - productEnvs = make([]*commonmodels.Product, 0) - ) - - productEnvs, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{}) - if err != nil { - log.Errorf("GetUserKubeConfig Collection.Product.List error: %v", err) - return "", e.ErrListProducts.AddDesc(err.Error()) - } - - // 只管理同集群的资源,且排除状态为Terminating的namespace - productEnvs = filterProductWithoutExternalCluster(productEnvs) - +func GetUserKubeConfig(userID string, projectsEnvCanEdit []string, projectsEnvCanView []string, log *zap.SugaredLogger) (string, error) { saNamespace := config.Namespace() - if err := ensureServiceAccount(saNamespace, username, log); err != nil { + if err := ensureClusterRole(log); err != nil { + log.Errorf("ensurClusterRole err: %s", err) return "", err } - - var ( - wg sync.WaitGroup - pool = make(chan int, 20) - errList = new(multierror.Error) - ) - for _, productEnv := range productEnvs { - namespace := productEnv.Namespace - - if _, found, err := getter.GetNamespace(namespace, krkubeclient.Client()); err != nil || !found { - log.Error(err) - continue - } - - wg.Add(1) - pool <- 1 - go func() { - defer func() { - wg.Done() - <-pool - }() - if err := ensureUserRole(namespace, username, log); err != nil { - log.Error(err) - errList = multierror.Append(errList, err) - } - - if err := ensureUserRoleBinding(saNamespace, namespace, username); err != nil { - log.Error(err) - errList = multierror.Append(errList, err) - } - }() - } - wg.Wait() - close(pool) - if err := errList.ErrorOrNil(); err != nil { - log.Error(err) + if err := ensureServiceAccountAndRolebinding(saNamespace, projectsEnvCanEdit, projectsEnvCanView, userID, log); err != nil { + log.Errorf("ensureServiceAccountAndRolebinding err: %s", err) return "", err } - - if err := createK8sSSLCert(saNamespace, username); err != nil { - log.Errorf("[%s] createSSLCert error: %v", username, err) - return "", err - } - - caCrtBase64, err := ioutil.ReadFile(filepath.Join(os.TempDir(), username, "ca.crt")) - if err != nil { - return "", fmt.Errorf("get client.crt error: %v", err) - } - - caKeyBase64, err := ioutil.ReadFile(filepath.Join(os.TempDir(), username, "ca.key")) + crt, token, err := getCrtAndToken(saNamespace, userID) if err != nil { - return "", fmt.Errorf("get client.key error: %v", err) + log.Errorf("getCrtAndToken err: %s", err) + return "", err } - args := &kubeCfgTmplArgs{ KubeServerAddr: config.KubeServerAddr(), - User: username, - CaCrtBase64: string(caCrtBase64), - CaKeyBase64: string(caKeyBase64), + CaCrtBase64: crt, + User: userID, + CaKeyBase64: token, } return renderCfgTmpl(args) } +func getCrtAndToken(namespace, userID string) (string, string, error) { + kubeClient := krkubeclient.Client() + var sa *corev1.ServiceAccount + for i := 0; i < 5; i++ { + tmpsa, found, err := getter.GetServiceAccount(namespace, config.ServiceAccountNameForUser(userID), kubeClient) + if err != nil { + log.Errorf("GetServiceAccount err:%s name:%s namespace:%s", err, config.ServiceAccountNameForUser(userID), namespace) + return "", "", err + } else if found || len(sa.Secrets) > 0 { + sa = tmpsa + break + } + log.Warnf("waiting for the secret created") + time.Sleep(time.Second) + + } + if sa == nil { + log.Errorf("can not get user:%s service account in namespace:%s", userID, namespace) + return "", "", errors.New("serviceAccount not found") + } + + secret, found, err := getter.GetSecret(namespace, sa.Secrets[0].Name, kubeClient) + if err != nil { + log.Errorf("GetSecret:%s err: %s", sa.Secrets[0].Name, err) + return "", "", err + } else if !found { + log.Error("secret:%s not found in namespace:%s", sa.Secrets[0].Name, namespace) + return "", "", errors.New("secret not found") + } + return base64.StdEncoding.EncodeToString(secret.Data["ca.crt"]), string(secret.Data["token"]), nil +} + func filterProductWithoutExternalCluster(products []*commonmodels.Product) []*commonmodels.Product { var ret []*commonmodels.Product kubeClient := krkubeclient.Client() @@ -167,132 +134,111 @@ func filterProductWithoutExternalCluster(products []*commonmodels.Product) []*co return ret } -func ensureServiceAccount(namespace, username string, log *zap.SugaredLogger) error { - serviceAccount := &corev1.ServiceAccount{ +var ( + clusterRoleEdit = &rbacv1beta1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ - Name: username + "-sa", - Namespace: namespace, + Name: config.RoleBindingNameEdit, }, - } - if err := updater.CreateServiceAccount(serviceAccount, krkubeclient.Client()); err != nil { - log.Errorf("CreateServiceAccount err: %+v", err) - return nil - } - return nil -} - -func ensureUserRole(namespace, username string, _ *zap.SugaredLogger) error { - roleName := fmt.Sprintf("%s-role", username) - verbs := []string{"*"} - role := &rbacv1beta1.Role{ + Rules: []rbacv1beta1.PolicyRule{rbacv1beta1.PolicyRule{ + Verbs: []string{"*"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }}} + clusterRoleView = &rbacv1beta1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ - Name: roleName, - Namespace: namespace, + Name: config.RoleBindingNameView, }, - Rules: []rbacv1beta1.PolicyRule{ - rbacv1beta1.PolicyRule{ - APIGroups: []string{"*"}, - Resources: []string{"*"}, - Verbs: verbs, - }, - rbacv1beta1.PolicyRule{ - APIGroups: []string{"*"}, - Resources: []string{ - "limitranges", - "resourcequotas", - }, - Verbs: []string{"get", "list", "watch"}, - }, - }, - } + Rules: []rbacv1beta1.PolicyRule{rbacv1beta1.PolicyRule{ + Verbs: []string{"get", "watch", "list"}, + APIGroups: []string{"*"}, + Resources: []string{"*"}, + }}} +) - old, found, err := getter.GetRole(namespace, roleName, krkubeclient.Client()) - if err == nil && found { - old.Rules = role.Rules - if err := updater.UpdateRole(old, krkubeclient.Client()); err != nil { +func ensureClusterRole(log *zap.SugaredLogger) error { + if _, found, err := getter.GetClusterRole(config.RoleBindingNameEdit, krkubeclient.Client()); err != nil { + log.Errorf("GetClusterRole:%s err: %s", config.RoleBindingNameEdit, err) + return err + } else if !found { + if err := updater.CreateClusterRole(clusterRoleEdit, krkubeclient.Client()); err != nil { + log.Errorf("CreateClusterRole:%s err: %s", clusterRoleEdit, err) return err } - } else { - if err := updater.CreateRole(role, krkubeclient.Client()); err != nil { + } + if _, found, err := getter.GetClusterRole(config.RoleBindingNameView, krkubeclient.Client()); err != nil { + log.Errorf("GetClusterRole:%s err: %s", config.RoleBindingNameView, err) + return err + } else if !found { + if err := updater.CreateClusterRole(clusterRoleView, krkubeclient.Client()); err != nil { + log.Errorf("CreateClusterRole:%s err: %s", clusterRoleView, err) return err } } - return nil } -func ensureUserRoleBinding(saNamespace, namespace, username string) error { - roleName := fmt.Sprintf("%s-role", username) - roleBindName := fmt.Sprintf("%s-role-bind", username) - rolebinding := &rbacv1beta1.RoleBinding{ +func ensureServiceAccountAndRolebinding(namespace string, projectsEnvCanEdit []string, projectsEnvCanView []string, userID string, log *zap.SugaredLogger) error { + serviceAccountName := config.ServiceAccountNameForUser(userID) + serviceAccount := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: roleBindName, + Name: serviceAccountName, Namespace: namespace, }, - RoleRef: rbacv1beta1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: roleName, - }, - Subjects: []rbacv1beta1.Subject{ - rbacv1beta1.Subject{ - //APIGroup: "rbac.authorization.k8s.io", - //Kind: "User", - //Name: namespace, - Kind: "ServiceAccount", - Name: username + "-sa", - Namespace: saNamespace, - }, - }, } - _, found, err := getter.GetRoleBinding(namespace, roleBindName, krkubeclient.Client()) - if err != nil || !found { - if err := updater.CreateRoleBinding(rolebinding, krkubeclient.Client()); err != nil { + _, found, err := getter.GetServiceAccount(namespace, serviceAccountName, krkubeclient.Client()) + if err != nil { + log.Errorf("GetServiceAccount name:%s err:%s", err) + return err + } + if !found { + if err := updater.CreateServiceAccount(serviceAccount, krkubeclient.Client()); err != nil { + log.Errorf("CreateServiceAccount name:%s err:%s", serviceAccountName, err) return err } - } else { - if err := updater.UpdateRoleBinding(rolebinding, krkubeclient.Client()); err != nil { + } + + // caller provide a list of projects for which the user has permission to edit env or read env + // while []string{*} means all projects + if len(projectsEnvCanEdit) == 1 && projectsEnvCanEdit[0] == "*" { + res, err := templaterepo.NewProductColl().ListNames(nil) + if err != nil { + log.Errorf("ListProjectBriefs err:%s", err) return err } + projectsEnvCanEdit = res } - return nil -} -func createK8sSSLCert(namespace, username string) error { - workDir := filepath.Join(os.TempDir(), username) - if err := os.MkdirAll(workDir, os.ModePerm); err != nil { - return err + if len(projectsEnvCanView) == 1 && projectsEnvCanView[0] == "*" { + res, err := templaterepo.NewProductColl().ListNames(nil) + if err != nil { + log.Errorf("ListProjectBriefs err:%s", err) + return err + } + projectsEnvCanView = res } - kubeClient := krkubeclient.Client() - - sa, found, err := getter.GetServiceAccount(namespace, username+"-sa", kubeClient) + canEditProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanEdit}) if err != nil { - return err - } else if !found { - return errors.New("sa not fonud") - } else if len(sa.Secrets) == 0 { - return errors.New("no secrets in sa") + log.Errorf("[%s] Collections.Product.List error: %s", projectsEnvCanEdit, err) } - secret, found, err := getter.GetSecret(namespace, sa.Secrets[0].Name, kubeClient) - if err != nil { - return err - } else if !found { - return errors.New("secret not found") + canEditProducts = filterProductWithoutExternalCluster(canEditProducts) + for _, vv := range canEditProducts { + if err := CreateRoleBinding(vv.Namespace, namespace, serviceAccountName, config.RoleBindingNameEdit); err != nil { + log.Errorf("CreateRoleBinding err: %s", err) + } } - keyPath := filepath.Join(workDir, "ca.key") - certPath := filepath.Join(workDir, "ca.crt") - err = ioutil.WriteFile(keyPath, secret.Data["token"], 0644) + canViewProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanView}) if err != nil { - return err + log.Errorf("[%s] Collections.Product.List error: %s", projectsEnvCanView, err) } - cert := make([]byte, base64.StdEncoding.EncodedLen(len(secret.Data["ca.crt"]))) - base64.StdEncoding.Encode(cert, secret.Data["ca.crt"]) - err = ioutil.WriteFile(certPath, cert, 0644) - if err != nil { - return err + canViewProducts = filterProductWithoutExternalCluster(canViewProducts) + for _, vv := range canViewProducts { + if err := CreateRoleBinding(vv.Namespace, namespace, serviceAccountName, config.RoleBindingNameView); err != nil { + log.Errorf("CreateRoleBinding err: %s", err) + } } + return nil } @@ -317,13 +263,76 @@ contexts: - context: cluster: koderover user: {{.User}} - name: {{.User}}@kubernetes -current-context: {{.User}}@kubernetes + name: koderover +current-context: koderover kind: Config preferences: {} users: - name: {{.User}} user: token: {{.CaKeyBase64}} - client-key-data: {{.CaCrtBase64}} ` + +func CreateRoleBinding(rbNamespace, saNamspace, serviceAccountName, roleBindName string) error { + rolebinding, found, err := getter.GetRoleBinding(rbNamespace, roleBindName, krkubeclient.Client()) + subs := []rbacv1beta1.Subject{{ + Kind: "ServiceAccount", + Name: serviceAccountName, + Namespace: saNamspace, + }} + if err != nil { + log.Errorf("GetRoleBinding name:%s err: %s", roleBindName, err) + return err + } + if !found{ + if err := updater.CreateRoleBinding(&rbacv1beta1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindName, + Namespace: rbNamespace, + }, + Subjects: subs, + RoleRef: rbacv1beta1.RoleRef{ + // APIGroup is the group for the resource being referenced + APIGroup: "rbac.authorization.k8s.io", + // Kind is the type of resource being referenced + Kind: "ClusterRole", + // Name is the name of resource being referenced + Name: roleBindName, + }, + }, krkubeclient.Client()); err != nil { + log.Errorf("create rolebinding:%s err: %s", roleBindName, err) + return err + } + }else{ + isExist := false + for _, v := range rolebinding.Subjects { + if v.Name == serviceAccountName { + isExist = true + } + } + if isExist { + return nil + } else { + subs = append(subs, rolebinding.Subjects...) + } + } + if err := updater.UpdateRoleBinding(&rbacv1beta1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleBindName, + Namespace: rbNamespace, + }, + Subjects: subs, + RoleRef: rbacv1beta1.RoleRef{ + // APIGroup is the group for the resource being referenced + APIGroup: "rbac.authorization.k8s.io", + // Kind is the type of resource being referenced + Kind: "ClusterRole", + // Name is the name of resource being referenced + Name: roleBindName, + }, + }, krkubeclient.Client()); err != nil { + log.Errorf("create rolebinding:%s err: %s", roleBindName, err) + return err + } + return nil +} diff --git a/pkg/microservice/picket/client/aslan/kubeconfig.go b/pkg/microservice/picket/client/aslan/kubeconfig.go new file mode 100644 index 00000000..b463271b --- /dev/null +++ b/pkg/microservice/picket/client/aslan/kubeconfig.go @@ -0,0 +1,19 @@ +package aslan + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) DownloadKubeConfig(header http.Header, qs url.Values) ([]byte, error) { + url := "/setting/user/kube/config" + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/config/consts.go b/pkg/microservice/picket/config/consts.go new file mode 100644 index 00000000..06e1924e --- /dev/null +++ b/pkg/microservice/picket/config/consts.go @@ -0,0 +1,8 @@ +package config + +type RulesLogicalOperator string + +const ( + AND RulesLogicalOperator = "and" + OR RulesLogicalOperator = "or" +) diff --git a/pkg/microservice/picket/core/filter/handler/kubeconfig.go b/pkg/microservice/picket/core/filter/handler/kubeconfig.go new file mode 100644 index 00000000..5cac861f --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/kubeconfig.go @@ -0,0 +1,15 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetKubeConfig(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetKubeConfig(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 29063a25..80429d11 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -45,4 +45,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehosts.GET("", ListCodeHost) } + downloads := router.Group("kubeconfig") + { + downloads.GET("", GetKubeConfig) + } + } diff --git a/pkg/microservice/picket/core/filter/service/kubeconfig.go b/pkg/microservice/picket/core/filter/service/kubeconfig.go new file mode 100644 index 00000000..b20b1fc8 --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/kubeconfig.go @@ -0,0 +1,49 @@ +package service + +import ( + "net/http" + "net/url" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + "github.com/koderover/zadig/pkg/microservice/picket/config" +) + +//DownloadKubeConfig user download kube config file which has permission to read or edit namespaces he has permission to +//query the opa service to get the project lists by pass through *rules parameter action +func GetKubeConfig(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { + readEnvRules := []*rule{{ + method: "GET", + endpoint: "/api/aslan/environment/environments", + }} + editEnvRules := []*rule{{ + method: "POST", + endpoint: "/api/aslan/environment/environments/?*", + }, { + method: "POST", + endpoint: "/api/aslan/environment/environments/?*/services/?*/restart", + }, + } + projectsEnvCanView, err := getAllowedProjects(header, readEnvRules, config.OR, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + + for _, name := range projectsEnvCanView { + qs.Add("projectsEnvCanView", name) + } + + projectsEnvCanEdit, err := getAllowedProjects(header, editEnvRules, config.OR, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + + for _, name := range projectsEnvCanEdit { + qs.Add("projectsEnvCanEdit", name) + } + + return aslan.New().DownloadKubeConfig(header, qs) +} diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index 14a2dbcf..a979aece 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -9,6 +9,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" "github.com/koderover/zadig/pkg/microservice/picket/client/opa" + "github.com/koderover/zadig/pkg/microservice/picket/config" ) type rule struct { @@ -21,7 +22,7 @@ func ListWorkflows(header http.Header, qs url.Values, logger *zap.SugaredLogger) method: "/api/aslan/workflow/workflow", endpoint: "GET", }} - names, err := getAllowedProjects(header, rules, logger) + names, err := getAllowedProjects(header, rules, config.AND, logger) if err != nil { logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err @@ -42,7 +43,7 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge method: "/api/aslan/workflow/workflow", endpoint: "PUT", }} - names, err := getAllowedProjects(header, rules, logger) + names, err := getAllowedProjects(header, rules, config.AND, logger) if err != nil { logger.Errorf("Failed to get allowed project names, err: %s", err) return nil, err @@ -58,7 +59,9 @@ func ListTestWorkflows(testName string, header http.Header, qs url.Values, logge return aslan.New().ListTestWorkflows(testName, header, qs) } -func getAllowedProjects(headers http.Header, rules []*rule, logger *zap.SugaredLogger) (projects []string, err error) { +//getAllowedProjects +//rulesLogicalOperator@ OR:satisfy any one of rules / AND:satisfy all rules +func getAllowedProjects(headers http.Header, rules []*rule, rulesLogicalOperator config.RulesLogicalOperator, logger *zap.SugaredLogger) (projects []string, err error) { var res [][]string for _, v := range rules { allowedProjects := &allowedProjectsData{} @@ -72,9 +75,24 @@ func getAllowedProjects(headers http.Header, rules []*rule, logger *zap.SugaredL } res = append(res, allowedProjects.Result) } + if rulesLogicalOperator == config.OR { + return union(res), nil + } return intersect(res), nil } +func union(s [][]string) []string { + if len(s) == 0 { + return nil + } + tmp := sets.NewString(s[0]...) + for _, v := range s[1:] { + t := sets.NewString(v...) + tmp = t.Union(tmp) + } + return tmp.List() +} + func intersect(s [][]string) []string { if len(s) == 0 { return nil diff --git a/pkg/tool/kube/getter/role.go b/pkg/tool/kube/getter/role.go index 56e53469..f1b10664 100644 --- a/pkg/tool/kube/getter/role.go +++ b/pkg/tool/kube/getter/role.go @@ -30,3 +30,13 @@ func GetRole(ns, name string, cl client.Client) (*rbacv1beta1.Role, bool, error) return g, found, err } + +func GetClusterRole(name string, cl client.Client) (*rbacv1beta1.ClusterRole, bool, error) { + g := &rbacv1beta1.ClusterRole{} + found, err := GetResourceInCache("", name, g, cl) + if err != nil || !found { + g = nil + } + + return g, found, err +} diff --git a/pkg/tool/kube/updater/clusterrole.go b/pkg/tool/kube/updater/clusterrole.go index 3ba0bcd5..e4d6ef29 100644 --- a/pkg/tool/kube/updater/clusterrole.go +++ b/pkg/tool/kube/updater/clusterrole.go @@ -25,3 +25,7 @@ import ( func DeleteClusterRoles(selector labels.Selector, cl client.Client) error { return deleteObjectsWithDefaultOptions("", selector, &rbacv1beta1.ClusterRole{}, cl) } + +func CreateClusterRole(role *rbacv1beta1.ClusterRole, cl client.Client) error { + return createObject(role, cl) +} diff --git a/pkg/tool/kube/updater/rolebinding.go b/pkg/tool/kube/updater/rolebinding.go index 293fa00c..827bfb3f 100644 --- a/pkg/tool/kube/updater/rolebinding.go +++ b/pkg/tool/kube/updater/rolebinding.go @@ -33,3 +33,4 @@ func CreateRoleBinding(rb *rbacv1beta1.RoleBinding, cl client.Client) error { func UpdateRoleBinding(rb *rbacv1beta1.RoleBinding, cl client.Client) error { return updateObject(rb, cl) } + -- Gitee From 556f89f1b958c116a80ab151551e365f6f597476 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Tue, 30 Nov 2021 14:52:27 +0800 Subject: [PATCH 0818/2647] add copyright header (#676) Signed-off-by: lou --- pkg/cli/initconfig/cmd/rsa.go | 16 ++++++++++++++++ .../internal/repository/models/codehost.go | 16 ++++++++++++++++ .../internal/repository/models/organization.go | 16 ++++++++++++++++ .../internal/repository/mongodb/codehost.go | 16 ++++++++++++++++ .../internal/repository/mongodb/organization.go | 16 ++++++++++++++++ .../common/repository/models/external_link.go | 16 ++++++++++++++++ .../models/services_in_external_env.go | 16 ++++++++++++++++ .../common/repository/models/yaml_template.go | 16 ++++++++++++++++ .../common/repository/mongodb/external_link.go | 16 ++++++++++++++++ .../mongodb/services_in_external_env.go | 16 ++++++++++++++++ .../common/repository/mongodb/yaml_template.go | 16 ++++++++++++++++ .../core/delivery/handler/policy_definitions.go | 16 ++++++++++++++++ .../environment/handler/policy_definitions.go | 16 ++++++++++++++++ .../core/project/handler/policy_definitions.go | 16 ++++++++++++++++ .../aslan/core/service/handler/yaml_template.go | 16 ++++++++++++++++ .../aslan/core/service/service/yaml_template.go | 16 ++++++++++++++++ .../aslan/core/system/handler/external_link.go | 16 ++++++++++++++++ .../aslan/core/system/service/external_link.go | 16 ++++++++++++++++ .../core/templatestore/handler/dockerfile.go | 16 ++++++++++++++++ .../aslan/core/templatestore/handler/yaml.go | 16 ++++++++++++++++ .../repository/models/dockerfile.go | 16 ++++++++++++++++ .../repository/mongodb/dockerfile.go | 16 ++++++++++++++++ .../core/templatestore/service/dockerfile.go | 16 ++++++++++++++++ .../aslan/core/templatestore/service/yaml.go | 16 ++++++++++++++++ .../core/workflow/handler/policy_definitions.go | 16 ++++++++++++++++ .../testing/handler/policy_definitions.go | 16 ++++++++++++++++ pkg/microservice/picket/client/aslan/project.go | 16 ++++++++++++++++ pkg/microservice/picket/client/aslan/workflow.go | 16 ++++++++++++++++ pkg/microservice/picket/client/opa/evaluate.go | 16 ++++++++++++++++ pkg/microservice/picket/client/opa/input.go | 16 ++++++++++++++++ pkg/microservice/picket/client/policy/client.go | 16 ++++++++++++++++ .../picket/client/policy/rolebinding.go | 16 ++++++++++++++++ .../picket/core/filter/handler/codehost.go | 16 ++++++++++++++++ .../picket/core/filter/handler/project.go | 16 ++++++++++++++++ .../picket/core/filter/handler/rolebinding.go | 16 ++++++++++++++++ .../picket/core/filter/handler/workflow.go | 16 ++++++++++++++++ .../picket/core/filter/service/codehost.go | 16 ++++++++++++++++ .../picket/core/filter/service/project.go | 16 ++++++++++++++++ .../picket/core/filter/service/rolebinding.go | 16 ++++++++++++++++ .../picket/core/filter/service/workflow.go | 16 ++++++++++++++++ .../picket/core/public/handler/public.go | 16 ++++++++++++++++ .../picket/core/public/handler/router.go | 16 ++++++++++++++++ .../picket/core/public/service/public.go | 16 ++++++++++++++++ .../policy/core/handler/policy_registration.go | 16 ++++++++++++++++ .../policy/core/service/bundle/exemption_urls.go | 16 ++++++++++++++++ .../core/service/bundle/resource_bundle.go | 16 ++++++++++++++++ .../service/bundle/resource_endpoint_mapping.go | 16 ++++++++++++++++ .../policy/core/service/policy_registration.go | 16 ++++++++++++++++ .../reaper/core/service/client/client.go | 16 ++++++++++++++++ .../reaper/core/service/client/dockerfile.go | 16 ++++++++++++++++ pkg/microservice/systemconfig/config/config.go | 16 ++++++++++++++++ pkg/microservice/systemconfig/config/consts.go | 16 ++++++++++++++++ .../core/codehost/handler/codehost.go | 16 ++++++++++++++++ .../systemconfig/core/codehost/handler/router.go | 16 ++++++++++++++++ .../core/codehost/repository/models/codehost.go | 16 ++++++++++++++++ .../core/codehost/repository/mongodb/codehost.go | 16 ++++++++++++++++ .../core/codehost/service/codehost.go | 16 ++++++++++++++++ .../core/connector/handler/router.go | 16 ++++++++++++++++ .../systemconfig/core/email/handler/email.go | 16 ++++++++++++++++ .../systemconfig/core/email/handler/router.go | 16 ++++++++++++++++ .../core/email/repository/models/emailhost.go | 16 ++++++++++++++++ .../core/email/repository/models/emailservice.go | 16 ++++++++++++++++ .../core/email/repository/mongodb/emailhost.go | 16 ++++++++++++++++ .../email/repository/mongodb/emailservice.go | 16 ++++++++++++++++ .../systemconfig/core/email/service/email.go | 16 ++++++++++++++++ .../core/features/handler/features.go | 16 ++++++++++++++++ .../systemconfig/core/features/handler/router.go | 16 ++++++++++++++++ .../core/features/repository/models/feature.go | 16 ++++++++++++++++ .../core/features/repository/mongodb/feature.go | 16 ++++++++++++++++ .../core/features/service/feature.go | 16 ++++++++++++++++ .../systemconfig/core/jira/handler/jira.go | 16 ++++++++++++++++ .../systemconfig/core/jira/handler/router.go | 16 ++++++++++++++++ .../core/jira/repository/models/jira.go | 16 ++++++++++++++++ .../core/jira/repository/mongodb/jira.go | 16 ++++++++++++++++ .../systemconfig/core/jira/service/jira.go | 16 ++++++++++++++++ .../user/core/handler/login/local.go | 16 ++++++++++++++++ .../user/core/handler/login/third_party.go | 16 ++++++++++++++++ pkg/microservice/user/core/handler/user/user.go | 16 ++++++++++++++++ .../user/core/repository/models/base.go | 16 ++++++++++++++++ .../user/core/repository/models/user.go | 16 ++++++++++++++++ .../user/core/repository/models/user_login.go | 16 ++++++++++++++++ .../user/core/repository/orm/user.go | 16 ++++++++++++++++ .../user/core/repository/orm/user_login.go | 16 ++++++++++++++++ .../user/core/service/login/local.go | 16 ++++++++++++++++ .../user/core/service/login/third_party.go | 16 ++++++++++++++++ .../user/core/service/login/token.go | 16 ++++++++++++++++ pkg/microservice/user/core/service/user/user.go | 16 ++++++++++++++++ .../service/taskplugin/taskplugin_suite_test.go | 16 ++++++++++++++++ pkg/shared/client/policy/policy.go | 16 ++++++++++++++++ pkg/shared/client/systemconfig/codehost.go | 16 ++++++++++++++++ pkg/shared/client/systemconfig/connector.go | 16 ++++++++++++++++ pkg/shared/client/systemconfig/email.go | 16 ++++++++++++++++ pkg/shared/client/user/user.go | 16 ++++++++++++++++ pkg/shared/config/client.go | 16 ++++++++++++++++ pkg/shared/config/features.go | 16 ++++++++++++++++ pkg/tool/gorm/client.go | 16 ++++++++++++++++ pkg/tool/mail/mail.go | 16 ++++++++++++++++ 97 files changed, 1552 insertions(+) diff --git a/pkg/cli/initconfig/cmd/rsa.go b/pkg/cli/initconfig/cmd/rsa.go index 0d299d76..3e8e6bb3 100644 --- a/pkg/cli/initconfig/cmd/rsa.go +++ b/pkg/cli/initconfig/cmd/rsa.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package cmd import ( diff --git a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go index 8afdbed1..c1bc5a24 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type CodeHost struct { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/organization.go b/pkg/cli/upgradeassistant/internal/repository/models/organization.go index 33b2ccd9..fe545832 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/organization.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/organization.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type Organization struct { diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go index 757b5924..2bf1fa57 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go index 41452b31..56a80822 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/organization.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/common/repository/models/external_link.go b/pkg/microservice/aslan/core/common/repository/models/external_link.go index 9afc405b..beb086a5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/external_link.go +++ b/pkg/microservice/aslan/core/common/repository/models/external_link.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go index 42e2c66c..5320e189 100644 --- a/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/models/services_in_external_env.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type ServicesInExternalEnv struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go index 34af08db..f3347106 100644 --- a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go +++ b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go index 5c675613..414ace19 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_link.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go index 56631326..182374ee 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/services_in_external_env.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go b/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go index 9d65e08b..7cfcf491 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/yaml_template.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go index 02789a47..377fb358 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go index 17df5f9f..46500ee8 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/project/handler/policy_definitions.go b/pkg/microservice/aslan/core/project/handler/policy_definitions.go index 02789a47..377fb358 100644 --- a/pkg/microservice/aslan/core/project/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/project/handler/policy_definitions.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go index 99fd797c..051653f4 100644 --- a/pkg/microservice/aslan/core/service/handler/yaml_template.go +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go index a434fdf0..81a40a00 100644 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/system/handler/external_link.go b/pkg/microservice/aslan/core/system/handler/external_link.go index cbe7c44c..bf20968e 100644 --- a/pkg/microservice/aslan/core/system/handler/external_link.go +++ b/pkg/microservice/aslan/core/system/handler/external_link.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/system/service/external_link.go b/pkg/microservice/aslan/core/system/service/external_link.go index b468b201..812e3a01 100644 --- a/pkg/microservice/aslan/core/system/service/external_link.go +++ b/pkg/microservice/aslan/core/system/service/external_link.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index b01ee1b8..76f16a8d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 0d71936d..9a0619a3 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go index 19ab53db..97af86c5 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go index a904efc5..36b131df 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go index 2fa44246..95f33151 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 2fce6f55..e8857f4f 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go index 02789a47..377fb358 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go index 02789a47..377fb358 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/client/aslan/project.go b/pkg/microservice/picket/client/aslan/project.go index 7aaf053e..6751e8b1 100644 --- a/pkg/microservice/picket/client/aslan/project.go +++ b/pkg/microservice/picket/client/aslan/project.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 110cefd9..b1a50012 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( diff --git a/pkg/microservice/picket/client/opa/evaluate.go b/pkg/microservice/picket/client/opa/evaluate.go index d0fb31f9..7d4ae908 100644 --- a/pkg/microservice/picket/client/opa/evaluate.go +++ b/pkg/microservice/picket/client/opa/evaluate.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package opa import ( diff --git a/pkg/microservice/picket/client/opa/input.go b/pkg/microservice/picket/client/opa/input.go index df28516d..fafb4a2f 100644 --- a/pkg/microservice/picket/client/opa/input.go +++ b/pkg/microservice/picket/client/opa/input.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package opa type Input struct { diff --git a/pkg/microservice/picket/client/policy/client.go b/pkg/microservice/picket/client/policy/client.go index 18b72463..9790686f 100644 --- a/pkg/microservice/picket/client/policy/client.go +++ b/pkg/microservice/picket/client/policy/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package policy import ( diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go index faa2e2a7..0b002fc0 100644 --- a/pkg/microservice/picket/client/policy/rolebinding.go +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package policy import ( diff --git a/pkg/microservice/picket/core/filter/handler/codehost.go b/pkg/microservice/picket/core/filter/handler/codehost.go index a7a2fec8..0cfa7d00 100644 --- a/pkg/microservice/picket/core/filter/handler/codehost.go +++ b/pkg/microservice/picket/core/filter/handler/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/filter/handler/project.go b/pkg/microservice/picket/core/filter/handler/project.go index c5260054..a0c55652 100644 --- a/pkg/microservice/picket/core/filter/handler/project.go +++ b/pkg/microservice/picket/core/filter/handler/project.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/filter/handler/rolebinding.go b/pkg/microservice/picket/core/filter/handler/rolebinding.go index 9a69939c..d4c11471 100644 --- a/pkg/microservice/picket/core/filter/handler/rolebinding.go +++ b/pkg/microservice/picket/core/filter/handler/rolebinding.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/filter/handler/workflow.go b/pkg/microservice/picket/core/filter/handler/workflow.go index 3f769137..dde21a05 100644 --- a/pkg/microservice/picket/core/filter/handler/workflow.go +++ b/pkg/microservice/picket/core/filter/handler/workflow.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go index 7ea6998f..f46a051f 100644 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ b/pkg/microservice/picket/core/filter/service/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index fded8d7b..11bce797 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 9ec4293d..4b58931f 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index a979aece..d1d32122 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 5a868e42..17c209e3 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/public/handler/router.go b/pkg/microservice/picket/core/public/handler/router.go index 7ae9cf8a..a4eeca51 100644 --- a/pkg/microservice/picket/core/public/handler/router.go +++ b/pkg/microservice/picket/core/public/handler/router.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/public/service/public.go b/pkg/microservice/picket/core/public/service/public.go index 787ac43b..f3d9ef9e 100644 --- a/pkg/microservice/picket/core/public/service/public.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/policy/core/handler/policy_registration.go b/pkg/microservice/policy/core/handler/policy_registration.go index c8983244..748bdfa8 100644 --- a/pkg/microservice/policy/core/handler/policy_registration.go +++ b/pkg/microservice/policy/core/handler/policy_registration.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 1193189c..d0770902 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package bundle // TODO: Policy Service should not care about the policy details of a certain service, instead, a service which wants diff --git a/pkg/microservice/policy/core/service/bundle/resource_bundle.go b/pkg/microservice/policy/core/service/bundle/resource_bundle.go index 5bb3abef..4deebcf1 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/resource_bundle.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package bundle import ( diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index fd1a9a0c..a1a63edf 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package bundle import ( diff --git a/pkg/microservice/policy/core/service/policy_registration.go b/pkg/microservice/policy/core/service/policy_registration.go index 64a0cb23..bee56d20 100644 --- a/pkg/microservice/policy/core/service/policy_registration.go +++ b/pkg/microservice/policy/core/service/policy_registration.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/reaper/core/service/client/client.go b/pkg/microservice/reaper/core/service/client/client.go index bd90314d..e72b2ec3 100644 --- a/pkg/microservice/reaper/core/service/client/client.go +++ b/pkg/microservice/reaper/core/service/client/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client import ( diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go index 211f5edb..32e2db86 100644 --- a/pkg/microservice/reaper/core/service/client/dockerfile.go +++ b/pkg/microservice/reaper/core/service/client/dockerfile.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client import ( diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index 2430e9b9..d93f30b0 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config import ( diff --git a/pkg/microservice/systemconfig/config/consts.go b/pkg/microservice/systemconfig/config/consts.go index 27f698e5..476a94cc 100644 --- a/pkg/microservice/systemconfig/config/consts.go +++ b/pkg/microservice/systemconfig/config/consts.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config const ( diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 2364b5a0..5e0a40c0 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index ddfff52a..857fae2d 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index 730ff415..cbbc49bd 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type CodeHost struct { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 411f685b..aad783d1 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 864ddda1..0e7b39be 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/systemconfig/core/connector/handler/router.go b/pkg/microservice/systemconfig/core/connector/handler/router.go index be2f7509..91626d3b 100644 --- a/pkg/microservice/systemconfig/core/connector/handler/router.go +++ b/pkg/microservice/systemconfig/core/connector/handler/router.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index fbb3432c..d90af53a 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/email/handler/router.go b/pkg/microservice/systemconfig/core/email/handler/router.go index f8e2a8ec..0a1cf8f8 100644 --- a/pkg/microservice/systemconfig/core/email/handler/router.go +++ b/pkg/microservice/systemconfig/core/email/handler/router.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go index 9dff9ad7..1b1b493e 100644 --- a/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/repository/models/emailhost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type EmailHost struct { diff --git a/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go index 148d7af1..37c60e33 100644 --- a/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/repository/models/emailservice.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type EmailService struct { diff --git a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go index 9d16d343..baa3d3f9 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailhost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go index 7ded08a2..4a49a426 100644 --- a/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go +++ b/pkg/microservice/systemconfig/core/email/repository/mongodb/emailservice.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 4de7edc8..1127081a 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/systemconfig/core/features/handler/features.go b/pkg/microservice/systemconfig/core/features/handler/features.go index 52b09ee9..389ecfb8 100644 --- a/pkg/microservice/systemconfig/core/features/handler/features.go +++ b/pkg/microservice/systemconfig/core/features/handler/features.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/features/handler/router.go b/pkg/microservice/systemconfig/core/features/handler/router.go index 44b35ebe..7e3a8e34 100644 --- a/pkg/microservice/systemconfig/core/features/handler/router.go +++ b/pkg/microservice/systemconfig/core/features/handler/router.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/features/repository/models/feature.go b/pkg/microservice/systemconfig/core/features/repository/models/feature.go index a0530ad5..63fe2f63 100644 --- a/pkg/microservice/systemconfig/core/features/repository/models/feature.go +++ b/pkg/microservice/systemconfig/core/features/repository/models/feature.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type Feature struct { diff --git a/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go b/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go index d5b2fe24..2b32687d 100644 --- a/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go +++ b/pkg/microservice/systemconfig/core/features/repository/mongodb/feature.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/systemconfig/core/features/service/feature.go b/pkg/microservice/systemconfig/core/features/service/feature.go index 90431ba8..4b91fd7a 100644 --- a/pkg/microservice/systemconfig/core/features/service/feature.go +++ b/pkg/microservice/systemconfig/core/features/service/feature.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/systemconfig/core/jira/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go index 4e8b6493..2f9cf62a 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/jira/handler/router.go b/pkg/microservice/systemconfig/core/jira/handler/router.go index b302096f..dc18be77 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/router.go +++ b/pkg/microservice/systemconfig/core/jira/handler/router.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/systemconfig/core/jira/repository/models/jira.go b/pkg/microservice/systemconfig/core/jira/repository/models/jira.go index b4325669..e2b6d483 100644 --- a/pkg/microservice/systemconfig/core/jira/repository/models/jira.go +++ b/pkg/microservice/systemconfig/core/jira/repository/models/jira.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type Jira struct { diff --git a/pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go b/pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go index acefdbd4..7b544bca 100644 --- a/pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go +++ b/pkg/microservice/systemconfig/core/jira/repository/mongodb/jira.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 0368497d..64851bd5 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/user/core/handler/login/local.go b/pkg/microservice/user/core/handler/login/local.go index 458b6051..4df87751 100644 --- a/pkg/microservice/user/core/handler/login/local.go +++ b/pkg/microservice/user/core/handler/login/local.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package login import ( diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index cedec6a7..40240bae 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package login import ( diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index b94787bc..76f9e4e2 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package user import ( diff --git a/pkg/microservice/user/core/repository/models/base.go b/pkg/microservice/user/core/repository/models/base.go index cf42820a..b2682a07 100644 --- a/pkg/microservice/user/core/repository/models/base.go +++ b/pkg/microservice/user/core/repository/models/base.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type Model struct { diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go index 30862a89..dae08d0f 100644 --- a/pkg/microservice/user/core/repository/models/user.go +++ b/pkg/microservice/user/core/repository/models/user.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type User struct { diff --git a/pkg/microservice/user/core/repository/models/user_login.go b/pkg/microservice/user/core/repository/models/user_login.go index f2b84804..88a32ecf 100644 --- a/pkg/microservice/user/core/repository/models/user_login.go +++ b/pkg/microservice/user/core/repository/models/user_login.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type UserLogin struct { diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index 73aeb283..ad48dcc2 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package orm import ( diff --git a/pkg/microservice/user/core/repository/orm/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go index b44af382..c63d54e8 100644 --- a/pkg/microservice/user/core/repository/orm/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package orm import ( diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index 83a8ecb5..4a39805c 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package login import ( diff --git a/pkg/microservice/user/core/service/login/third_party.go b/pkg/microservice/user/core/service/login/third_party.go index ba50713e..061fc0dc 100644 --- a/pkg/microservice/user/core/service/login/third_party.go +++ b/pkg/microservice/user/core/service/login/third_party.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package login import ( diff --git a/pkg/microservice/user/core/service/login/token.go b/pkg/microservice/user/core/service/login/token.go index bc24988e..d2e59c21 100644 --- a/pkg/microservice/user/core/service/login/token.go +++ b/pkg/microservice/user/core/service/login/token.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package login import ( diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index f43ac7ec..5741c31c 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package user import ( diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/taskplugin_suite_test.go b/pkg/microservice/warpdrive/core/service/taskplugin/taskplugin_suite_test.go index 6c966975..5d87240a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/taskplugin_suite_test.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/taskplugin_suite_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package taskplugin_test import ( diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 278c2f06..52036620 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package policy import ( diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 39104606..ef87d173 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package systemconfig import ( diff --git a/pkg/shared/client/systemconfig/connector.go b/pkg/shared/client/systemconfig/connector.go index ad1359ff..374102da 100644 --- a/pkg/shared/client/systemconfig/connector.go +++ b/pkg/shared/client/systemconfig/connector.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package systemconfig import ( diff --git a/pkg/shared/client/systemconfig/email.go b/pkg/shared/client/systemconfig/email.go index 72189414..0aecd6bc 100644 --- a/pkg/shared/client/systemconfig/email.go +++ b/pkg/shared/client/systemconfig/email.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package systemconfig import ( diff --git a/pkg/shared/client/user/user.go b/pkg/shared/client/user/user.go index d51c371c..436410ed 100644 --- a/pkg/shared/client/user/user.go +++ b/pkg/shared/client/user/user.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package user import ( diff --git a/pkg/shared/config/client.go b/pkg/shared/config/client.go index 07eeb4b2..cc072ffa 100644 --- a/pkg/shared/config/client.go +++ b/pkg/shared/config/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config import ( diff --git a/pkg/shared/config/features.go b/pkg/shared/config/features.go index 9f542e10..38b78319 100644 --- a/pkg/shared/config/features.go +++ b/pkg/shared/config/features.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config import ( diff --git a/pkg/tool/gorm/client.go b/pkg/tool/gorm/client.go index c5e65fb1..93b0f5de 100644 --- a/pkg/tool/gorm/client.go +++ b/pkg/tool/gorm/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gorm import ( diff --git a/pkg/tool/mail/mail.go b/pkg/tool/mail/mail.go index ca8acdb1..f1faf30d 100644 --- a/pkg/tool/mail/mail.go +++ b/pkg/tool/mail/mail.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mail import ( -- Gitee From 4a63524ce8aded9c6c664bf5e1e95e47d4f731ed Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 15:09:41 +0800 Subject: [PATCH 0819/2647] debug code Signed-off-by: liu deyi --- .../reaper/core/service/reaper/artifact.go | 7 +++++-- .../warpdrive/core/service/taskplugin/trigger.go | 1 + pkg/util/fs/fs.go | 10 +++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 35b1747c..685c33a1 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -88,9 +88,12 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } artifactPath := filepath.Join(activeWorkspace, artifactPaths[0]) - isDir := fs.IsDir(artifactPath) + isDir, err := fs.IsDir(artifactPath) + if err != nil { + return err + } if isDir { - temp, err := ioutil.TempFile("", "*artifact.tar.gz") + temp, err := os.CreateTemp("", "*artifact.tar.gz") if err != nil { log.Errorf("failed to create temp file %s", err) return err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 69be95e5..ac61c145 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -132,6 +132,7 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi if err != nil { return } + log.Infof("artifactPath:%s", artifactPath) taskOutput := &task.TaskOutput{ Type: "object_storage", Value: artifactPath, diff --git a/pkg/util/fs/fs.go b/pkg/util/fs/fs.go index 003c85be..17be952a 100644 --- a/pkg/util/fs/fs.go +++ b/pkg/util/fs/fs.go @@ -92,13 +92,13 @@ func SaveFile(src io.ReadCloser, dst string) error { } // Determine whether the given path is a folder -func IsDir(path string) bool { +func IsDir(path string) (bool, error) { s, err := os.Stat(path) if err != nil { - if !os.IsNotExist(err) { - return false + if os.IsNotExist(err) { + return false, nil } - return false + return false, err } - return s.IsDir() + return s.IsDir(), nil } -- Gitee From 698bc14eadbe78af53c02852b98c0f65a56c381f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 15:21:35 +0800 Subject: [PATCH 0820/2647] optimze code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/trigger.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index ac61c145..3c5fce17 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -177,11 +177,14 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error return "", err } prefix := store.GetObjectPath("") - files, err := s3client.ListFiles(store.Bucket, prefix, false) + files, err := s3client.ListFiles(store.Bucket, prefix, true) if err != nil { return "", err } - return fmt.Sprintf("%s/%s/%s", store.Endpoint, subPath, files[0]), nil + if len(files) > 0 { + return fmt.Sprintf("%s/%s/%s", store.Endpoint, subPath, files[0]), nil + } + return fmt.Sprintf("%s/%s/artifact.tar.gz", store.Endpoint, subPath), nil } // Wait ... -- Gitee From a9dd8dbbc2feefcd9a6ac017537b1ade9f257662 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 15:35:57 +0800 Subject: [PATCH 0821/2647] optimze code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/trigger.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 3c5fce17..07175335 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -164,9 +164,9 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error } subPath := "" if store.Subfolder != "" { - subPath = fmt.Sprintf("%s/%s/%s/%s", store.Subfolder, pipelineTask.PipelineName, pipelineTask.ServiceName, "artifact") + subPath = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") } else { - subPath = fmt.Sprintf("%s/%s/%s", pipelineTask.PipelineName, pipelineTask.ServiceName, "artifact") + subPath = fmt.Sprintf("%s/%d/%s", pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") } forcedPathStyle := true if store.Provider == setting.ProviderSourceAli { @@ -176,8 +176,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error if err != nil { return "", err } - prefix := store.GetObjectPath("") - files, err := s3client.ListFiles(store.Bucket, prefix, true) + files, err := s3client.ListFiles(store.Bucket, "/", true) if err != nil { return "", err } -- Gitee From a699d819d49feb04c5d886672e95df4a710ed9e4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 15:45:35 +0800 Subject: [PATCH 0822/2647] optimze code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 07175335..9d6a6157 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -176,7 +176,8 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error if err != nil { return "", err } - files, err := s3client.ListFiles(store.Bucket, "/", true) + prefix := store.GetObjectPath("") + files, err := s3client.ListFiles(store.Bucket, prefix, true) if err != nil { return "", err } -- Gitee From 90c130c5a154d43a499cb663665e57361ead9c21 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 16:00:37 +0800 Subject: [PATCH 0823/2647] optimze code Signed-off-by: liu deyi --- .../common/repository/models/task/trigger.go | 1 + .../service/workflow/workflow_task_v3.go | 4 +++- .../core/service/taskplugin/trigger.go | 19 ++----------------- .../core/service/types/task/trigger.go | 1 + 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go index 8528a103..7799d6c8 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go @@ -15,6 +15,7 @@ type Trigger struct { IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` + ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index b9e9ecee..e92da129 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -35,6 +35,7 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str return nil, e.ErrGetCounter.AddDesc(err.Error()) } + artifactPath := "" // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 for i, subTask := range workflowV3.SubTasks { pre, err := base.ToPreview(subTask) @@ -63,7 +64,7 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str if args.BuildArgs != nil { build.JobCtx.EnvVars = args.BuildArgs } - + artifactPath = build.JobCtx.ArtifactPath build.ServiceName = fmt.Sprintf("%s-job", args.Name) workflowV3.SubTasks[i], err = build.ToSubTask() if err != nil { @@ -76,6 +77,7 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str log.Errorf("subTask.ToTriggerTask error: %s", err) continue } + trigger.ArtifactPath = artifactPath workflowV3.SubTasks[i], err = trigger.ToSubTask() } } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 9d6a6157..c34db604 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -29,11 +29,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/log" - s3tool "github.com/koderover/zadig/pkg/tool/s3" ) const ( @@ -168,21 +166,8 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error } else { subPath = fmt.Sprintf("%s/%d/%s", pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") } - forcedPathStyle := true - if store.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) - if err != nil { - return "", err - } - prefix := store.GetObjectPath("") - files, err := s3client.ListFiles(store.Bucket, prefix, true) - if err != nil { - return "", err - } - if len(files) > 0 { - return fmt.Sprintf("%s/%s/%s", store.Endpoint, subPath, files[0]), nil + if p.Task.ArtifactPath != "" { + return fmt.Sprintf("%s/%s/workspace/%s", store.Endpoint, subPath, p.Task.ArtifactPath), nil } return fmt.Sprintf("%s/%s/artifact.tar.gz", store.Endpoint, subPath), nil } diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 18461fd6..de27140e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -15,6 +15,7 @@ type Trigger struct { IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` + ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` -- Gitee From 362f1bb035f8a2f871d645bbb1c44a858d44537d Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:28:46 +0800 Subject: [PATCH 0824/2647] generate stable bundle (#678) Signed-off-by: lou --- .../policy/core/service/bundle/opa_bundle.go | 36 +++++++++++++++++-- .../bundle/resource_endpoint_mapping.go | 4 +-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 90936cf8..32c66cbd 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -82,15 +82,44 @@ type rule struct { Endpoint string `json:"endpoint"` ResourceType string `json:"resourceType,omitempty"` IDRegex string `json:"idRegex,omitempty"` - MatchAttributes []attribute `json:"matchAttributes,omitempty"` + MatchAttributes Attributes `json:"matchAttributes,omitempty"` MatchExpressions []expression `json:"matchExpressions,omitempty"` } -type attribute struct { +type Attribute struct { Key string `json:"key"` Value string `json:"value"` } +type Attributes []*Attribute + +func (a Attributes) LessOrEqual(other Attributes) bool { + if len(a) == 0 { + return true + } + if len(other) == 0 { + return false + } + for i, attr := range a { + if i > len(other)-1 { + return false + } + ki := attr.Key + kj := other[i].Key + vi := attr.Value + vj := other[i].Value + if ki == kj { + if vi == vj { + continue + } + return vi < vj + } + return ki < kj + } + + return true +} + type expression struct { Key string `json:"key"` Values []string `json:"values"` @@ -118,6 +147,9 @@ func (o rules) Len() int { return len(o) } func (o rules) Swap(i, j int) { o[i], o[j] = o[j], o[i] } func (o rules) Less(i, j int) bool { if o[i].Endpoint == o[j].Endpoint { + if o[i].Method == o[j].Method { + return o[i].MatchAttributes.LessOrEqual(o[j].MatchAttributes) + } return o[i].Method < o[j].Method } return o[i].Endpoint < o[j].Endpoint diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index a1a63edf..92a02637 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -54,9 +54,9 @@ func getResourceActionMappings(policies []*models.Policy) resourceActionMappings for _, r := range p.Rules { for _, ar := range r.Rules { - var as []attribute + var as []*Attribute for _, a := range ar.MatchAttributes { - as = append(as, attribute{Key: a.Key, Value: a.Value}) + as = append(as, &Attribute{Key: a.Key, Value: a.Value}) } data[p.Resource][r.Action] = append(data[p.Resource][r.Action], &rule{ -- Gitee From e25ad19acafd651ebfa019f7cec8015bf34c7fbf Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 30 Nov 2021 16:37:09 +0800 Subject: [PATCH 0825/2647] clean rolebindings when delete user (#672) * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * user Signed-off-by: mouuii * user Signed-off-by: mouuii * user Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii --- .../picket/client/policy/rolebinding.go | 11 +++++ pkg/microservice/picket/client/user/client.go | 41 +++++++++++++++++++ pkg/microservice/picket/client/user/user.go | 35 ++++++++++++++++ .../picket/core/filter/handler/router.go | 4 ++ .../picket/core/filter/handler/users.go | 32 +++++++++++++++ .../picket/core/filter/service/users.go | 39 ++++++++++++++++++ .../policy/core/handler/role_binding.go | 7 +--- .../core/repository/mongodb/role_binding.go | 11 ++++- .../core/service/bundle/exemption_urls.go | 6 ++- .../policy/core/service/role_binding.go | 9 ++-- 10 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 pkg/microservice/picket/client/user/client.go create mode 100644 pkg/microservice/picket/client/user/user.go create mode 100644 pkg/microservice/picket/core/filter/handler/users.go create mode 100644 pkg/microservice/picket/core/filter/service/users.go diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go index 0b002fc0..b93fb933 100644 --- a/pkg/microservice/picket/client/policy/rolebinding.go +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -41,3 +41,14 @@ func (c *Client) ListRoleBindings(header http.Header, qs url.Values) ([]*RoleBin return res, nil } + +func (c *Client) DeleteRoleBindings(userID string, header http.Header, qs url.Values) ([]byte, error) { + url := "rolebindings/bulk-delete" + + qs.Add("userID", userID) + res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody([]byte("{}"))) + if err != nil { + return []byte{}, err + } + return res.Body(), err +} diff --git a/pkg/microservice/picket/client/user/client.go b/pkg/microservice/picket/client/user/client.go new file mode 100644 index 00000000..5e6d8ac1 --- /dev/null +++ b/pkg/microservice/picket/client/user/client.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package user + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.UserServiceAddress() + + c := httpclient.New( + httpclient.SetHostURL(host + "/api/v1"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/microservice/picket/client/user/user.go b/pkg/microservice/picket/client/user/user.go new file mode 100644 index 00000000..eb0d93a3 --- /dev/null +++ b/pkg/microservice/picket/client/user/user.go @@ -0,0 +1,35 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package user + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) DeleteUser(userId string, header http.Header, qs url.Values) ([]byte, error) { + url := "/users/" + userId + + res, err := c.Delete(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 80429d11..b50e5a32 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -45,6 +45,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehosts.GET("", ListCodeHost) } + users := router.Group("users") + { + users.DELETE("/:id", DeleteUser) + } downloads := router.Group("kubeconfig") { downloads.GET("", GetKubeConfig) diff --git a/pkg/microservice/picket/core/filter/handler/users.go b/pkg/microservice/picket/core/filter/handler/users.go new file mode 100644 index 00000000..e0ee375e --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/users.go @@ -0,0 +1,32 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func DeleteUser(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + uid := c.Param("id") + ctx.Resp, ctx.Err = service.DeleteUser(uid, c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/users.go b/pkg/microservice/picket/core/filter/service/users.go new file mode 100644 index 00000000..af86762f --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/users.go @@ -0,0 +1,39 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "net/http" + "net/url" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/policy" + "github.com/koderover/zadig/pkg/microservice/picket/client/user" +) + +type DeleteUserResp struct { + Message string `json:"message"` +} + +func DeleteUser(userID string, header http.Header, qs url.Values, _ *zap.SugaredLogger) ([]byte, error) { + _, err := user.New().DeleteUser(userID, header, qs) + if err != nil { + return []byte{}, err + } + return policy.New().DeleteRoleBindings(userID, header, qs) +} diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index d33d7b8f..5f3218f0 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -104,10 +104,7 @@ func DeleteRoleBindings(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } + userID := c.Query("userID") args := &deleteRoleBindingsArgs{} if err := c.ShouldBindJSON(args); err != nil { @@ -115,7 +112,7 @@ func DeleteRoleBindings(c *gin.Context) { return } - ctx.Err = service.DeleteRoleBindings(args.Names, projectName, ctx.Logger) + ctx.Err = service.DeleteRoleBindings(args.Names, projectName, userID, ctx.Logger) } func DeleteSystemRoleBinding(c *gin.Context) { diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index b3fae0bb..d4103326 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -120,11 +120,18 @@ func (c *RoleBindingColl) Delete(name string, projectName string) error { return err } -func (c *RoleBindingColl) DeleteMany(names []string, projectName string) error { - query := bson.M{"namespace": projectName} +func (c *RoleBindingColl) DeleteMany(names []string, projectName string, userID string) error { + query := bson.M{} + if projectName != "" { + query["namespace"] = projectName + } if len(names) > 0 { query["name"] = bson.M{"$in": names} } + + if userID != "" { + query = bson.M{"subjects.uid": userID} + } _, err := c.Collection.DeleteMany(context.TODO(), query) return err diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index d0770902..16ea310e 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -221,9 +221,13 @@ var systemAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/proxy/config"}, }, { - Methods: []string{"GET", "PUT", "PATCH", "DELETE"}, + Methods: []string{"GET", "PUT", "PATCH"}, Endpoints: []string{"api/v1/users/?*"}, }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/picket/users/?*"}, + }, { Methods: []string{"POST"}, Endpoints: []string{"api/v1/users"}, diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 44606ea1..eb4b4129 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -112,16 +112,13 @@ func DeleteRoleBinding(name string, projectName string, _ *zap.SugaredLogger) er return mongodb.NewRoleBindingColl().Delete(name, projectName) } -func DeleteRoleBindings(names []string, projectName string, _ *zap.SugaredLogger) error { - if len(names) == 0 { - return nil - } +func DeleteRoleBindings(names []string, projectName string, userID string, _ *zap.SugaredLogger) error { - if names[0] == "*" { + if len(names) == 1 && names[0] == "*" { names = []string{} } - return mongodb.NewRoleBindingColl().DeleteMany(names, projectName) + return mongodb.NewRoleBindingColl().DeleteMany(names, projectName, userID) } func createRoleBindingObject(ns string, rb *RoleBinding, logger *zap.SugaredLogger) (*models.RoleBinding, error) { -- Gitee From a14a9b18d611c53901ff734f87769e079d85a9c1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 18:23:22 +0800 Subject: [PATCH 0826/2647] optimize code Signed-off-by: liu deyi --- .../reaper/core/service/reaper/artifact.go | 6 +++-- .../core/service/taskplugin/trigger.go | 22 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 685c33a1..771795ba 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -116,8 +116,10 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } } else { if store != nil { - objectKey := store.GetObjectPath(artifactPath) - if err = s3FileUpload(store, artifactPath, objectKey); err != nil { + dir, fileName := filepath.Split(artifactPath) + log.Infof("dir:%s", dir) + log.Infof("fileName:%s", fileName) + if err = s3FileUpload(store, artifactPath, fileName); err != nil { return err } } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index c34db604..6c73a8b9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -29,9 +29,11 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/log" + s3tool "github.com/koderover/zadig/pkg/tool/s3" ) const ( @@ -166,10 +168,24 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error } else { subPath = fmt.Sprintf("%s/%d/%s", pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") } - if p.Task.ArtifactPath != "" { - return fmt.Sprintf("%s/%s/workspace/%s", store.Endpoint, subPath, p.Task.ArtifactPath), nil + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false } - return fmt.Sprintf("%s/%s/artifact.tar.gz", store.Endpoint, subPath), nil + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + return "", err + } + prefix := store.GetObjectPath("/") + files, err := s3client.ListFiles(store.Bucket, prefix, true) + if err != nil { + return "", err + } + fileName := "artifact.tar.gz" + if len(files) > 0 { + fileName = files[0] + } + return fmt.Sprintf("%s://%s/%s/%s", store.GetSchema(), store.Endpoint, subPath, fileName), nil } // Wait ... -- Gitee From 563fc193fac8423e7f6b8261001461d377308ea9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 18:30:38 +0800 Subject: [PATCH 0827/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 6c73a8b9..177c87e1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -177,7 +177,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error return "", err } prefix := store.GetObjectPath("/") - files, err := s3client.ListFiles(store.Bucket, prefix, true) + files, err := s3client.ListFiles(store.Bucket, prefix, false) if err != nil { return "", err } -- Gitee From 002f7e333e565351622e6b9af096f2cb42a1a3b9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 18:40:58 +0800 Subject: [PATCH 0828/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/artifact.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 771795ba..ca2009a1 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -119,7 +119,8 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] dir, fileName := filepath.Split(artifactPath) log.Infof("dir:%s", dir) log.Infof("fileName:%s", fileName) - if err = s3FileUpload(store, artifactPath, fileName); err != nil { + objectKey := store.GetObjectPath(fileName) + if err = s3FileUpload(store, artifactPath, objectKey); err != nil { return err } } -- Gitee From 2ab88238e2939c3e2c91d6048e88d77fa7bfb610 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 18:46:12 +0800 Subject: [PATCH 0829/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 177c87e1..97b628f2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "time" "go.uber.org/zap" @@ -185,6 +186,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error if len(files) > 0 { fileName = files[0] } + log.Infof("files:%s", strings.Join(files, ",")) return fmt.Sprintf("%s://%s/%s/%s", store.GetSchema(), store.Endpoint, subPath, fileName), nil } -- Gitee From 08f5960f923aa7ba81acd42f5dc25c533e825de1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 19:01:15 +0800 Subject: [PATCH 0830/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 97b628f2..7a3c3033 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -177,7 +177,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error if err != nil { return "", err } - prefix := store.GetObjectPath("/") + prefix := store.GetObjectPath("") files, err := s3client.ListFiles(store.Bucket, prefix, false) if err != nil { return "", err -- Gitee From c43f10c7f1a4a13b16ed70a26b8266d53b4c751b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 19:10:18 +0800 Subject: [PATCH 0831/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 7a3c3033..5375da5c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -178,7 +178,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error return "", err } prefix := store.GetObjectPath("") - files, err := s3client.ListFiles(store.Bucket, prefix, false) + files, err := s3client.ListFiles(store.Bucket, prefix, true) if err != nil { return "", err } -- Gitee From 9b7b2b0a9653a11988485b4c24baad8ab7ac6a0f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 19:23:08 +0800 Subject: [PATCH 0832/2647] optimize code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/trigger.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 5375da5c..baf37fe4 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -163,11 +163,10 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error log.Errorf("Archive failed to create s3 storage %s", pipelineTask.StorageURI) return "", err } - subPath := "" if store.Subfolder != "" { - subPath = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") + store.Subfolder = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") } else { - subPath = fmt.Sprintf("%s/%d/%s", pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") + store.Subfolder = fmt.Sprintf("%s/%d/%s", pipelineTask.PipelineName, pipelineTask.TaskID, "artifact") } forcedPathStyle := true if store.Provider == setting.ProviderSourceAli { @@ -178,7 +177,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error return "", err } prefix := store.GetObjectPath("") - files, err := s3client.ListFiles(store.Bucket, prefix, true) + files, err := s3client.ListFiles(store.Bucket, prefix, false) if err != nil { return "", err } @@ -187,7 +186,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error fileName = files[0] } log.Infof("files:%s", strings.Join(files, ",")) - return fmt.Sprintf("%s://%s/%s/%s", store.GetSchema(), store.Endpoint, subPath, fileName), nil + return fmt.Sprintf("%s://%s/%s/%s", store.GetSchema(), store.Endpoint, store.Subfolder, fileName), nil } // Wait ... -- Gitee From 77cb6107b0de2b6fd7532ea66ccaec32c8bb0448 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 19:57:39 +0800 Subject: [PATCH 0833/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/trigger.go | 1 - .../aslan/core/workflow/service/workflow/workflow_task_v3.go | 3 --- pkg/microservice/warpdrive/core/service/types/task/trigger.go | 1 - 3 files changed, 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go index 7799d6c8..8528a103 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go @@ -15,7 +15,6 @@ type Trigger struct { IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` - ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index e92da129..7f0edb2f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -35,7 +35,6 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str return nil, e.ErrGetCounter.AddDesc(err.Error()) } - artifactPath := "" // 自定义基础镜像的镜像名称可能会被更新,需要使用ID获取最新的镜像名称 for i, subTask := range workflowV3.SubTasks { pre, err := base.ToPreview(subTask) @@ -64,7 +63,6 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str if args.BuildArgs != nil { build.JobCtx.EnvVars = args.BuildArgs } - artifactPath = build.JobCtx.ArtifactPath build.ServiceName = fmt.Sprintf("%s-job", args.Name) workflowV3.SubTasks[i], err = build.ToSubTask() if err != nil { @@ -77,7 +75,6 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str log.Errorf("subTask.ToTriggerTask error: %s", err) continue } - trigger.ArtifactPath = artifactPath workflowV3.SubTasks[i], err = trigger.ToSubTask() } } diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index de27140e..18461fd6 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -15,7 +15,6 @@ type Trigger struct { IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` - ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` -- Gitee From 382fc2effcac8a5f936bff1fdfd36ec5944b8f26 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 20:14:23 +0800 Subject: [PATCH 0834/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index baf37fe4..1ae69f02 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -177,7 +177,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error return "", err } prefix := store.GetObjectPath("") - files, err := s3client.ListFiles(store.Bucket, prefix, false) + files, err := s3client.ListFiles(store.Bucket, prefix, true) if err != nil { return "", err } -- Gitee From 952f584d95ffbd2f64911348a337db64fa9b0d6b Mon Sep 17 00:00:00 2001 From: dehuaduan <30834299+ddh27@users.noreply.github.com> Date: Tue, 30 Nov 2021 20:16:35 +0800 Subject: [PATCH 0835/2647] Customize resource specifications (#666) * Customize resource specifications Signed-off-by: dehuaduan * Customize resource specifications fix Signed-off-by: dehuaduan * add test case Signed-off-by: ddh27 * fix Customize resource specifications bug Signed-off-by: ddh27 * remove unnecessary comments Signed-off-by: ddh27 * Unify the units of CPU and memory resources Signed-off-by: ddh27 * param bson omitempty Signed-off-by: ddh27 * format Signed-off-by: ddh27 Co-authored-by: dehuaduan --- pkg/microservice/aslan/config/consts.go | 14 --- .../aslan/core/build/service/build.go | 7 ++ .../core/common/repository/models/build.go | 3 +- .../common/repository/models/task/build.go | 1 + .../common/repository/models/task/jenkins.go | 1 + .../common/repository/models/task/testing.go | 4 +- .../core/common/repository/models/testing.go | 5 +- .../aslan/core/common/service/build.go | 7 ++ .../aslan/core/common/util/validate.go | 36 +++++++ .../aslan/core/service/service/pm.go | 4 + .../core/workflow/service/workflow/convert.go | 1 + .../service/workflow/pipeline_task.go | 3 + .../service/workflow/workflow_task.go | 2 + .../core/workflow/testing/service/testing.go | 9 +- .../service/taskplugin/artifact_deploy.go | 2 +- .../core/service/taskplugin/build.go | 2 +- .../core/service/taskplugin/docker_build.go | 2 +- .../core/service/taskplugin/jenkins_plugin.go | 2 +- .../warpdrive/core/service/taskplugin/job.go | 102 +++++++++--------- .../core/service/taskplugin/job_test.go | 77 +++++++++++-- .../core/service/taskplugin/release_image.go | 2 +- .../core/service/taskplugin/testing.go | 2 +- .../core/service/types/task/build.go | 1 + .../core/service/types/task/jenkins.go | 1 + .../core/service/types/task/testing.go | 1 + pkg/setting/types.go | 56 ++++++++++ 26 files changed, 264 insertions(+), 83 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/util/validate.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index ca01f216..a29153a6 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -160,20 +160,6 @@ const ( NameSpaceRegexString = "[^a-z0-9.-]" ) -// Request ... -type Request string - -const ( - // HighRequest 16 CPU 32 G - HighRequest = Request("high") - // MediumRequest 8 CPU 16 G - MediumRequest = Request("medium") - // LowRequest 4 CPU 8 G - LowRequest = Request("low") - // MinRequest 2 CPU 2 G - MinRequest = Request("min") -) - //ProductPermission ... type ProductPermission string diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 186ca028..848247e0 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -28,6 +28,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -112,6 +113,9 @@ func CreateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog if len(build.Name) == 0 { return e.ErrCreateBuildModule.AddDesc("empty name") } + if err := commonutil.CheckDefineResourceParam(build.PreBuild.ResReq, build.PreBuild.ResReqSpec); err != nil { + return e.ErrCreateBuildModule.AddDesc(err.Error()) + } build.UpdateBy = username correctFields(build) @@ -128,6 +132,9 @@ func UpdateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog if len(build.Name) == 0 { return e.ErrUpdateBuildModule.AddDesc("empty name") } + if err := commonutil.CheckDefineResourceParam(build.PreBuild.ResReq, build.PreBuild.ResReqSpec); err != nil { + return e.ErrUpdateBuildModule.AddDesc(err.Error()) + } existed, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.Name, ProductName: build.ProductName}) if err == nil && existed.PreBuild != nil && build.PreBuild != nil { diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index c5288acc..26550762 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -51,7 +51,8 @@ type Build struct { type PreBuild struct { CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` // ResReq defines job requested resources - ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` // BuildOS defines job image OS, it supports 12.04, 14.04, 16.04 BuildOS string `bson:"build_os" json:"build_os"` ImageFrom string `bson:"image_from" json:"image_from"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index c8211297..f4d41e2f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -48,6 +48,7 @@ type Build struct { ImageFrom string `bson:"image_from" json:"image_from,omitempty"` ImageID string `bson:"image_id" json:"image_id"` ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` LogFile string `bson:"log_file" json:"log_file"` InstallCtx []*models.Install `bson:"-" json:"install_ctx,omitempty"` Registries []*models.RegistryNamespace `bson:"-" json:"registries"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 43f8946b..8e3c5c59 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -50,6 +50,7 @@ type JenkinsBuild struct { Timeout int `bson:"timeout" json:"timeout,omitempty"` Error string `bson:"error" json:"error,omitempty"` ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/testing.go b/pkg/microservice/aslan/core/common/repository/models/task/testing.go index f6aaab8a..460a7ca0 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/testing.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" ) type Testing struct { @@ -39,7 +40,8 @@ type Testing struct { ImageFrom string `bson:"image_from" json:"image_from,omitempty"` ImageID string `bson:"image_id" json:"image_id"` // ResReq defines job requested resources - ResReq config.Request `bson:"res_req" json:"res_req"` + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` LogFile string `bson:"log_file" json:"log_file"` TestModuleName string `bson:"test_module_name" json:"test_module_name"` ReportReady bool `bson:"report_ready" json:"report_ready"` diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index c42a6deb..fa223496 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -19,7 +19,7 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" ) @@ -72,7 +72,8 @@ type PreTest struct { ImageFrom string `bson:"image_from" json:"image_from"` ImageID string `bson:"image_id" json:"image_id"` // ResReq defines job requested resources - ResReq config.Request `bson:"res_req" json:"res_req"` + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` // Installs defines apps to be installed for build Installs []*Item `bson:"installs,omitempty" json:"installs"` // Envs stores user defined env key val for build diff --git a/pkg/microservice/aslan/core/common/service/build.go b/pkg/microservice/aslan/core/common/service/build.go index db16a3dd..e5a46bd2 100644 --- a/pkg/microservice/aslan/core/common/service/build.go +++ b/pkg/microservice/aslan/core/common/service/build.go @@ -24,6 +24,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types" @@ -33,6 +34,9 @@ func CreateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog if len(build.Name) == 0 { return e.ErrCreateBuildModule.AddDesc("empty name") } + if err := commonutil.CheckDefineResourceParam(build.PreBuild.ResReq, build.PreBuild.ResReqSpec); err != nil { + return e.ErrCreateBuildModule.AddDesc(err.Error()) + } build.UpdateBy = username correctFields(build) @@ -49,6 +53,9 @@ func UpdateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog if len(build.Name) == 0 { return e.ErrUpdateBuildModule.AddDesc("empty name") } + if err := commonutil.CheckDefineResourceParam(build.PreBuild.ResReq, build.PreBuild.ResReqSpec); err != nil { + return e.ErrUpdateBuildModule.AddDesc(err.Error()) + } existed, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.Name, ProductName: build.ProductName}) if err == nil && existed.PreBuild != nil && build.PreBuild != nil { diff --git a/pkg/microservice/aslan/core/common/util/validate.go b/pkg/microservice/aslan/core/common/util/validate.go new file mode 100644 index 00000000..4a1f6258 --- /dev/null +++ b/pkg/microservice/aslan/core/common/util/validate.go @@ -0,0 +1,36 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/setting" +) + +func CheckDefineResourceParam(req setting.Request, reqSpec setting.RequestSpec) error { + if req != setting.DefineRequest { + return nil + } + if reqSpec.CpuLimit < 1 { + return fmt.Errorf("Parameter res_req_spc.cpu_limit must be greater than 1m") + } + if reqSpec.MemoryLimit < 1 { + return fmt.Errorf("Parameter res_req_spc.memory_limit must be greater than 1Mi") + } + return nil +} diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index 0365592f..8d7916d0 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -28,6 +28,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -44,6 +45,9 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug if !config.ServiceNameRegex.MatchString(args.ServiceTmplObject.ServiceName) { return e.ErrInvalidParam.AddDesc("服务名称格式错误,请检查") } + if err := commonutil.CheckDefineResourceParam(args.Build.PreBuild.ResReq, args.Build.PreBuild.ResReqSpec); err != nil { + return e.ErrInvalidParam.AddDesc(err.Error()) + } opt := &commonrepo.ServiceFindOption{ ServiceName: args.ServiceTmplObject.ServiceName, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 7d98f8c9..4003a1c6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -166,6 +166,7 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z ServiceName: jenkinsBuildOption.Target, Service: jenkinsBuildOption.ServiceName, ResReq: module.PreBuild.ResReq, + ResReqSpec: module.PreBuild.ResReqSpec, Timeout: module.Timeout, JenkinsBuildArgs: &task.JenkinsBuildArgs{ JobName: jenkinsBuildOption.JenkinsBuildArgs.JobName, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 21140296..31feb5d7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -413,6 +413,7 @@ func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, t buildInfo.BuildOS = newBuildInfo.PreBuild.BuildOS buildInfo.ImageFrom = newBuildInfo.PreBuild.ImageFrom buildInfo.ResReq = newBuildInfo.PreBuild.ResReq + buildInfo.ResReqSpec = newBuildInfo.PreBuild.ResReqSpec } if newBuildInfo.PostBuild != nil && newBuildInfo.PostBuild.DockerBuild != nil { @@ -477,6 +478,7 @@ func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, t testInfo.BuildOS = newTestInfo.PreTest.BuildOS testInfo.ImageFrom = newTestInfo.PreTest.ImageFrom testInfo.ResReq = newTestInfo.PreTest.ResReq + testInfo.ResReqSpec = newTestInfo.PreTest.ResReqSpec } // 设置 build 安装脚本 testInfo.InstallCtx, err = buildInstallCtx(testInfo.InstallItems) @@ -604,6 +606,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testTask.BuildOS = testModule.PreTest.BuildOS testTask.ImageFrom = testModule.PreTest.ImageFrom testTask.ResReq = testModule.PreTest.ResReq + testTask.ResReqSpec = testModule.PreTest.ResReqSpec } // 设置 build 安装脚本 testTask.InstallCtx, err = buildInstallCtx(testTask.InstallItems) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 368a4352..45f27398 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1222,6 +1222,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } } testTask.ResReq = testModule.PreTest.ResReq + testTask.ResReqSpec = testModule.PreTest.ResReqSpec } // 设置 build 安装脚本 testTask.InstallCtx, err = buildInstallCtx(testTask.InstallItems) @@ -1556,6 +1557,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL BuildOS: module.PreBuild.BuildOS, ImageFrom: module.PreBuild.ImageFrom, ResReq: module.PreBuild.ResReq, + ResReqSpec: module.PreBuild.ResReqSpec, Timeout: module.Timeout, Registries: registries, ProductName: args.ProductName, diff --git a/pkg/microservice/aslan/core/workflow/testing/service/testing.go b/pkg/microservice/aslan/core/workflow/testing/service/testing.go index 0ea62ff5..2647c29c 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/testing.go @@ -32,6 +32,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -43,7 +44,9 @@ func CreateTesting(username string, testing *commonmodels.Testing, log *zap.Suga if len(testing.Name) == 0 { return e.ErrCreateTestModule.AddDesc("empty Name") } - + if err := commonutil.CheckDefineResourceParam(testing.PreTest.ResReq, testing.PreTest.ResReqSpec); err != nil { + return e.ErrCreateTestModule.AddDesc(err.Error()) + } err := HandleCronjob(testing, log) if err != nil { return e.ErrCreateTestModule.AddErr(err) @@ -96,7 +99,9 @@ func UpdateTesting(username string, testing *commonmodels.Testing, log *zap.Suga if len(testing.Name) == 0 { return e.ErrUpdateTestModule.AddDesc("empty Name") } - + if err := commonutil.CheckDefineResourceParam(testing.PreTest.ResReq, testing.PreTest.ResReqSpec); err != nil { + return e.ErrUpdateTestModule.AddDesc(err.Error()) + } err := HandleCronjob(testing, log) if err != nil { return e.ErrUpdateTestModule.AddErr(err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index c3a06aaf..b37306e7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -215,7 +215,7 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index db5c7f07..8374fdb0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -230,7 +230,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index cc02d9cb..7824478d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -196,7 +196,7 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi return } - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 5e38031f..2d5c475c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -158,7 +158,7 @@ func (j *JenkinsBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, p j.Log.Infof("succeed to create cm for jenkins build job %s", j.JobName) j.Log.Infof("JenkinsBuildConfig : %+v", pipelineTask.ConfigPayload.JenkinsBuildConfig) - job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create jenkins build job context error: %v", err) j.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1d13553e..4835fa81 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -26,6 +26,7 @@ import ( "os" "path" "sort" + "strconv" "strings" "time" @@ -381,13 +382,14 @@ func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx st //"s-job": pipelinename-taskid-tasktype-servicename, //"s-task": pipelinename-taskid, //"s-type": tasktype, -func buildJob(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace) (*batchv1.Job, error) { +func buildJob(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace) (*batchv1.Job, error) { return buildJobWithLinkedNs( taskType, jobImage, jobName, serviceName, resReq, + resReqSpec, ctx, pipelineTask, registries, @@ -396,7 +398,7 @@ func buildJob(taskType config.TaskType, jobImage, jobName, serviceName string, r ) } -func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { +func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { var reaperBootingScript string if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { @@ -471,7 +473,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa }, }, VolumeMounts: getVolumeMounts(ctx), - Resources: getResourceRequirements(resReq), + Resources: getResourceRequirements(resReq, resReqSpec), }, }, Volumes: getVolumes(jobName), @@ -631,67 +633,69 @@ func getVolumes(jobName string) []corev1.Volume { // ResReqLow 4 CPU 8 G used by testing module // ResReqMin 2 CPU 2 G used by docker build, release image module // Fallback ResReq 1 CPU 1 G -func getResourceRequirements(resReq setting.Request) corev1.ResourceRequirements { +func getResourceRequirements(resReq setting.Request, resReqSpec setting.RequestSpec) corev1.ResourceRequirements { switch resReq { - case setting.HighRequest: - return corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("16"), - corev1.ResourceMemory: resource.MustParse("32Gi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("4"), - corev1.ResourceMemory: resource.MustParse("4Gi"), - }, - } + return generateResourceRequirements(setting.HighRequest, setting.HighRequestSpec) + case setting.MediumRequest: - return corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("8"), - corev1.ResourceMemory: resource.MustParse("16Gi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("2"), - corev1.ResourceMemory: resource.MustParse("2Gi"), - }, - } + return generateResourceRequirements(setting.MediumRequest, setting.MediumRequestSpec) + case setting.LowRequest: - return corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("4"), - corev1.ResourceMemory: resource.MustParse("8Gi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("1"), - corev1.ResourceMemory: resource.MustParse("1Gi"), - }, - } + return generateResourceRequirements(setting.LowRequest, setting.LowRequestSpec) case setting.MinRequest: - return corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("2"), - corev1.ResourceMemory: resource.MustParse("2Gi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("0.5"), - corev1.ResourceMemory: resource.MustParse("512Mi"), - }, - } + return generateResourceRequirements(setting.MinRequest, setting.MinRequestSpec) + + case setting.DefineRequest: + return generateResourceRequirements(resReq, resReqSpec) + default: + return generateResourceRequirements(setting.DefaultRequest, setting.DefaultRequestSpec) + } +} + +//generateResourceRequirements +//cpu Request:Limit=1:4 +//memory default Request:Limit=1:4 ; if memoryLimit>= 8Gi,Request:Limit=1:8 +func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSpec) corev1.ResourceRequirements { + + if req != setting.DefineRequest { return corev1.ResourceRequirements{ Limits: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("4"), - corev1.ResourceMemory: resource.MustParse("8Gi"), + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(reqSpec.CpuLimit) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(reqSpec.MemoryLimit) + setting.MemoryUintMi), }, Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("1"), - corev1.ResourceMemory: resource.MustParse("1Gi"), + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(reqSpec.CpuReq) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(reqSpec.MemoryReq) + setting.MemoryUintMi), }, } } + + cpuReqInt := reqSpec.CpuLimit / 4 + if cpuReqInt < 1 { + cpuReqInt = 1 + } + memoryReqInt := reqSpec.MemoryLimit / 4 + if memoryReqInt >= 2*1024 { + memoryReqInt = memoryReqInt / 2 + } + if memoryReqInt < 1 { + memoryReqInt = 1 + } + + return corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(reqSpec.CpuLimit) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(reqSpec.MemoryLimit) + setting.MemoryUintMi), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(cpuReqInt) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(memoryReqInt) + setting.MemoryUintMi), + }, + } } //waitJobEnd diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job_test.go b/pkg/microservice/warpdrive/core/service/taskplugin/job_test.go index c4b3439b..351cd91a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job_test.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job_test.go @@ -18,17 +18,18 @@ package taskplugin import ( "fmt" + "reflect" "strings" "testing" - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) //Test create configmap named jobname @@ -63,7 +64,7 @@ func TestCreateJobConfigMap(t *testing.T) { TaskType: fmt.Sprintf("%s", config.TaskBuild), } - createJobConfigMap(FakeKubeCli, namespace, jobname, jobLabel, jobCtx) + createJobConfigMap(namespace, jobname, jobLabel, jobCtx, FakeKubeCli) jobConfigmap, err := FakeKubeCli.GetConfigMap(namespace, jobname) assert.Nil(err) assert.Equal(jobname, jobConfigmap.Name) @@ -150,7 +151,7 @@ func TestBuildJob(t *testing.T) { ConfigPayload: &task.ConfigPayload{}, } - _, err := buildJob(config.TaskBuild, jobImage, "test-build-job", "test123", setting.LowRequest, pipelineCtx, pipelineTask, nil) + _, err := buildJob(config.TaskBuild, jobImage, "test-build-job", "test123", setting.LowRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, nil) log.Error(err) const ( @@ -158,7 +159,7 @@ func TestBuildJob(t *testing.T) { serviceName = "Test123" ) job, err := buildJob(config.TaskBuild, jobImage, jobName, serviceName, - setting.LowRequest, pipelineCtx, pipelineTask, make([]*task.RegistryNamespace, 0)) + setting.LowRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, make([]*task.RegistryNamespace, 0)) assert.Nil(err) assert.Equal(jobName, job.ObjectMeta.Name) @@ -194,7 +195,7 @@ func TestEnsureDeleteJob(t *testing.T) { ConfigPayload: &task.ConfigPayload{}, } - job, err := buildJob(config.TaskBuild, jobImage, jobname, servicename, setting.LowRequest, pipelineCtx, pipelineTask, nil) + job, err := buildJob(config.TaskBuild, jobImage, jobname, servicename, setting.LowRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, nil) assert.Nil(err) defer func() { @@ -230,3 +231,63 @@ func TestEnsureDeleteJob(t *testing.T) { assert.Nil(getJob) */ } + +func Test_getResourceRequirements(t *testing.T) { + type args struct { + resReq setting.Request + resReqSpec setting.RequestSpec + } + tests := []struct { + name string + args args + want corev1.ResourceRequirements + }{ + { + name: "high", + args: args{ + resReq: setting.HighRequest, + resReqSpec: setting.HighRequestSpec, + }, + want: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("16000m"), + corev1.ResourceMemory: resource.MustParse("32768Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("4000m"), + corev1.ResourceMemory: resource.MustParse("4096Mi"), + }, + }, + }, + { + name: "define", + args: args{ + resReq: setting.DefineRequest, + resReqSpec: setting.RequestSpec{ + CpuLimit: 4000, + MemoryLimit: 2048, + }, + }, + want: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("4000m"), + corev1.ResourceMemory: resource.MustParse("2048Mi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1000m"), + corev1.ResourceMemory: resource.MustParse("512Mi"), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getResourceRequirements(tt.args.resReq, tt.args.resReqSpec) + fmt.Println("Limit:", got.Limits.Cpu().String(), got.Limits.Memory().String()) + fmt.Println("Memory:", got.Requests.Cpu().String(), got.Requests.Memory().String()) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getResourceRequirements() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 08495bea..373e3068 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -176,7 +176,7 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p } p.Log.Infof("succeed to create cm for image job %s", p.JobName) - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create release image job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index d61273c0..07ab9732 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -200,7 +200,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC // search namespace should also include desired namespace job, err := buildJobWithLinkedNs( - p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries, + p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries, p.KubeNamespace, linkedNamespace, ) diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 4d6e55c3..42a36ee6 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -46,6 +46,7 @@ type Build struct { ImageFrom string `bson:"image_from" json:"image_from,omitempty"` ImageID string `bson:"image_id" json:"image_id"` ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` LogFile string `bson:"log_file" json:"log_file"` InstallCtx []*Install `bson:"-" json:"install_ctx,omitempty"` Registries []*RegistryNamespace `bson:"-" json:"registries"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go index 5f6bd942..4606af19 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go +++ b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go @@ -50,6 +50,7 @@ type JenkinsBuild struct { Timeout int `bson:"timeout" json:"timeout,omitempty"` Error string `bson:"error" json:"error,omitempty"` ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"jenkins_integration" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/testing.go b/pkg/microservice/warpdrive/core/service/types/task/testing.go index fcbb0a0c..74aa27c4 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/testing.go +++ b/pkg/microservice/warpdrive/core/service/types/task/testing.go @@ -40,6 +40,7 @@ type Testing struct { ImageID string `bson:"image_id" json:"image_id"` // ResReq defines job requested resources ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` LogFile string `bson:"log_file" json:"log_file"` TestModuleName string `bson:"test_module_name" json:"test_module_name"` ReportReady bool `bson:"report_ready" json:"report_ready"` diff --git a/pkg/setting/types.go b/pkg/setting/types.go index dfec91fa..46b08787 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -29,6 +29,62 @@ const ( LowRequest Request = "low" // MinRequest 2 CPU 2 G MinRequest Request = "min" + // DefaultRequest 4 CPU 8 G + DefaultRequest Request = "default" + // DefineRequest x CPU x G + DefineRequest Request = "define" +) + +type RequestSpec struct { + CpuLimit int `bson:"cpu_limit" json:"cpu_limit"` + MemoryLimit int `bson:"memory_limit" json:"memory_limit"` + CpuReq int `bson:"cpu_req,omitempty" json:"cpu_req,omitempty"` + MemoryReq int `bson:"memory_req,omitempty" json:"memory_req,omitempty"` +} + +var ( + // HighRequestSpec 16 CPU 32 G + HighRequestSpec = RequestSpec{ + CpuLimit: 16000, + MemoryLimit: 32768, + CpuReq: 4000, + MemoryReq: 4096, + } + // MediumRequestSpec 8 CPU 16 G + MediumRequestSpec = RequestSpec{ + CpuLimit: 8000, + MemoryLimit: 16384, + CpuReq: 2000, + MemoryReq: 2048, + } + // LowRequestSpec 4 CPU 8 G + LowRequestSpec = RequestSpec{ + CpuLimit: 4000, + MemoryLimit: 8192, + CpuReq: 1000, + MemoryReq: 1024, + } + // MinRequestSpec 2 CPU 2 G + MinRequestSpec = RequestSpec{ + CpuLimit: 2000, + MemoryLimit: 2048, + CpuReq: 500, + MemoryReq: 512, + } + // DefineRequest X CPU X G + DefineRequestSpec = RequestSpec{} + DefaultRequestSpec = RequestSpec{ + CpuLimit: 4000, + MemoryLimit: 8192, + CpuReq: 1000, + MemoryReq: 1024, + } +) + +const ( + CpuUintM string = "m" + MemoryUintMi string = "Mi" + MemoryUintGi string = "Gi" ) var ValidName = regexp.MustCompile(`^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$`) -- Gitee From 9fa3492c01274fa3a7766eaf1be1b4474b3e7faa Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 20:21:31 +0800 Subject: [PATCH 0836/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/artifact.go | 4 +--- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index ca2009a1..94a65033 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -116,9 +116,7 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } } else { if store != nil { - dir, fileName := filepath.Split(artifactPath) - log.Infof("dir:%s", dir) - log.Infof("fileName:%s", fileName) + _, fileName := filepath.Split(artifactPath) objectKey := store.GetObjectPath(fileName) if err = s3FileUpload(store, artifactPath, objectKey); err != nil { return err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 1ae69f02..083eec3e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -20,7 +20,6 @@ import ( "context" "encoding/json" "fmt" - "strings" "time" "go.uber.org/zap" @@ -185,8 +184,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error if len(files) > 0 { fileName = files[0] } - log.Infof("files:%s", strings.Join(files, ",")) - return fmt.Sprintf("%s://%s/%s/%s", store.GetSchema(), store.Endpoint, store.Subfolder, fileName), nil + return fmt.Sprintf("%s://%s/%s", store.GetSchema(), store.Endpoint, fileName), nil } // Wait ... -- Gitee From dd0fca9d912394aedb80526c5f00db527f3bdbb3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 20:38:06 +0800 Subject: [PATCH 0837/2647] optimize artifact download url Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 083eec3e..d3e5ab6f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -184,7 +184,7 @@ func (p *TriggerTaskPlugin) getS3Storage(pipelineTask *task.Task) (string, error if len(files) > 0 { fileName = files[0] } - return fmt.Sprintf("%s://%s/%s", store.GetSchema(), store.Endpoint, fileName), nil + return fmt.Sprintf("%s://%s.%s/%s", store.GetSchema(), store.Bucket, store.Endpoint, fileName), nil } // Wait ... -- Gitee From e17da83ed39bb0bdecaa26b81ef035b7a313f42b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 30 Nov 2021 20:42:43 +0800 Subject: [PATCH 0838/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index d3e5ab6f..caa8924d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -132,7 +132,6 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi if err != nil { return } - log.Infof("artifactPath:%s", artifactPath) taskOutput := &task.TaskOutput{ Type: "object_storage", Value: artifactPath, -- Gitee From 37923bad99f39d50e8fcc8801d489624973aff9f Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Tue, 30 Nov 2021 21:20:50 +0800 Subject: [PATCH 0839/2647] fix user_allowed_projects rule (#679) Signed-off-by: lou --- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index f8b10088..9fc4edcf 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -218,7 +218,7 @@ user_allowed_projects[project] { some project user_projects[project] not user_is_admin - allow with project_name as project + response.allowed with project_name as project } # if user is system admin, return all projects -- Gitee From 18aa5096b5d0f11f57048bd0c9ff4f744a420521 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 30 Nov 2021 22:38:51 +0800 Subject: [PATCH 0840/2647] K8s YAML template permission remove (#682) Signed-off-by: fansi Co-authored-by: fansi --- pkg/microservice/aslan/core/project/handler/policy.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index 2ceecbcc..25760b9e 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -54,10 +54,6 @@ rules: endpoint: "/api/aslan/service/loader/load/?*/?*" - method: POST endpoint: "/api/aslan/service/helm/services" - - method: GET - endpoint: "/api/aslan/template/yaml" - - method: GET - endpoint: "/api/aslan/template/yaml/?*" - method: POST endpoint: "/api/aslan/service/template/load" - method: GET -- Gitee From d9c3ae9605ea90d82cc36a71af3dd8a44c2665ad Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 10:56:37 +0800 Subject: [PATCH 0841/2647] handler conflict Signed-off-by: liu deyi --- .../common/repository/mongodb/k8s_cluster.go | 17 ----------------- .../aslan/core/common/service/config_payload.go | 2 +- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 2139cab5..80e34947 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -238,20 +238,3 @@ func (c *K8SClusterColl) GetByToken(token string) (*models.K8SCluster, error) { return c.Get(id) } - -func (c *K8SClusterColl) List() ([]*models.K8SCluster, error) { - resp := make([]*models.K8SCluster, 0) - ctx := context.Background() - - cursor, err := c.Collection.Find(ctx, bson.M{}) - if err != nil { - return nil, err - } - - err = cursor.All(ctx, &resp) - if err != nil { - return nil, err - } - - return resp, err -} diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index b3c2808f..1f3ed425 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -89,7 +89,7 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { payload.PrivateKeys = privateKeys } - k8sClusters, _ := mongodb.NewK8SClusterColl().List() + k8sClusters, _ := mongodb.NewK8SClusterColl().List(nil) if len(k8sClusters) != 0 { payload.K8SClusters = k8sClusters } -- Gitee From aa4e95efcd7041e1181b9aa894fd7574067caf6d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 11:01:23 +0800 Subject: [PATCH 0842/2647] modify buildv3 Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go index 208e5441..8e035957 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -194,7 +194,7 @@ func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pi } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) -- Gitee From 4d1bd4ffdd3dc079d30770cab1356cd1da5c3e16 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 1 Dec 2021 11:20:29 +0800 Subject: [PATCH 0843/2647] add missing policy (#683) Signed-off-by: lou --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index fb681fbc..8a8351d6 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -88,3 +88,11 @@ rules: endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" - method: GET endpoint: "/api/aslan/delivery/releases" + - method: GET + endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" + matchAttributes: + - key: "placeholder" + value: "placeholder" + - method: GET + endpoint: "/api/aslan/project/products/?*/services" -- Gitee From 324a4e93b82010b0947b07ba4a72e35a22ce6485 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 1 Dec 2021 11:22:40 +0800 Subject: [PATCH 0844/2647] store resource bundle config in configmap (#680) * store resource bundle config in configmap Signed-off-by: lou * update after review Signed-off-by: lou --- .../core/service/bundle/resource_bundle.go | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/resource_bundle.go b/pkg/microservice/policy/core/service/bundle/resource_bundle.go index 4deebcf1..542fadd5 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/resource_bundle.go @@ -20,25 +20,24 @@ import ( "bytes" "encoding/json" "fmt" + "os" "sort" - "github.com/koderover/zadig/pkg/config" + "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" ) -const ( - EnvironmentType = "Environment" - ClusterType = "Cluster" -) +const configPath = "/config/config.yaml" -type resourceBundleService struct { - endpoint, resourceType string +type ResourceBundleService struct { + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType"` } -var resourceBundleMap = map[string]resourceBundleService{ - EnvironmentType: {resourceType: EnvironmentType, endpoint: fmt.Sprintf("%s/api/environment/bundle-resources", config.AslanServiceAddress())}, - ClusterType: {resourceType: ClusterType, endpoint: fmt.Sprintf("%s/api/cluster/bundle-resources", config.AslanServiceAddress())}, +type Config struct { + Bundles []*ResourceBundleService `json:"bundles"` } type ResourceSpec struct { @@ -86,6 +85,22 @@ func (r ResourceBundle) MarshalJSON() ([]byte, error) { return buf.Bytes(), nil } +func loadConfig() (*Config, error) { + data, err := os.ReadFile(configPath) + if err != nil { + log.Errorf("Failed to load config, err: %s", err) + return nil, err + } + + conf := &Config{} + if err = yaml.Unmarshal(data, conf); err != nil { + log.Errorf("Failed to unmarshal yaml, err: %s", err) + return nil, err + } + + return conf, nil +} + func AppendOPAResources(res ResourceBundle, resourceType string, objs []*ResourceSpec) ResourceBundle { if res == nil { res = make(map[string]resources) @@ -100,16 +115,21 @@ func AppendOPAResources(res ResourceBundle, resourceType string, objs []*Resourc func generateResourceBundle() ResourceBundle { var res ResourceBundle + cfg, err := loadConfig() + if err != nil { + return nil + } + cl := httpclient.New() - for resourceType, bundleService := range resourceBundleMap { + for _, bundleService := range cfg.Bundles { objs := make([]*ResourceSpec, 0) - _, err := cl.Get(bundleService.endpoint, httpclient.SetResult(&objs)) + _, err := cl.Get(bundleService.Endpoint, httpclient.SetResult(&objs)) if err != nil { - log.Warnf("Failed to get %s bundle, err: %s", resourceType, err) + log.Warnf("Failed to get %s bundle, err: %s", bundleService.ResourceType, err) continue } - res = AppendOPAResources(res, resourceType, objs) + res = AppendOPAResources(res, bundleService.ResourceType, objs) } return res -- Gitee From 35e3fa845d71934205f17b5ff3e84943425ae634 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 11:25:10 +0800 Subject: [PATCH 0845/2647] modify node interface Signed-off-by: liu deyi --- .../aslan/core/environment/service/kube.go | 14 ++++++++++---- pkg/shared/kube/resource/node.go | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index c4ba3ff6..c64c4fea 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -26,6 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -227,8 +228,8 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { } } -func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) ([]*resource.Node, error) { - resp := make([]*resource.Node, 0) +func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.NodeResp, error) { + resp := new(resource.NodeResp) kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) @@ -244,14 +245,19 @@ func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) ([]*resource.N return resp, err } + nodeInfos := make([]*resource.Node, 0) + labels := sets.NewString() for _, node := range nodes { nodeResource := &resource.Node{ Status: nodeReady(node), Labels: nodeLabel(node), + IP: node.Name, } - resp = append(resp, nodeResource) + nodeInfos = append(nodeInfos, nodeResource) + labels.Insert(nodeResource.Labels...) } - + resp.Nodes = nodeInfos + resp.Labels = labels.List() return resp, nil } diff --git a/pkg/shared/kube/resource/node.go b/pkg/shared/kube/resource/node.go index c54f3d36..c76b5eb6 100644 --- a/pkg/shared/kube/resource/node.go +++ b/pkg/shared/kube/resource/node.go @@ -3,4 +3,10 @@ package resource type Node struct { Labels []string `json:"node_labels"` Status string `json:"node_status"` + IP string `json:"node_ip"` +} + +type NodeResp struct { + Nodes []*Node `json:"data"` + Labels []string `json:"labels"` } -- Gitee From 9e9b0a454aa0288f65ecf98e24cd1653f53edc7e Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 1 Dec 2021 11:31:12 +0800 Subject: [PATCH 0846/2647] add resource to prevent delete namespace (#677) * add resource to prevent delete namespace Signed-off-by: mouuii * add resource to prevent delete namespace Signed-off-by: mouuii --- .../aslan/core/setting/service/user.go | 30 +++++++++++-------- .../picket/core/filter/service/kubeconfig.go | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index d4fc2c32..9e2e2fda 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -142,7 +143,7 @@ var ( Rules: []rbacv1beta1.PolicyRule{rbacv1beta1.PolicyRule{ Verbs: []string{"*"}, APIGroups: []string{"*"}, - Resources: []string{"*"}, + Resources: []string{"pods", "deployments", "configmaps", "crobjobs", "daemonsets", "ingresses", "jobs", "secrets", "services", "statefulsets"}, }}} clusterRoleView = &rbacv1beta1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ @@ -217,25 +218,30 @@ func ensureServiceAccountAndRolebinding(namespace string, projectsEnvCanEdit []s projectsEnvCanView = res } - canEditProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanEdit}) - if err != nil { - log.Errorf("[%s] Collections.Product.List error: %s", projectsEnvCanEdit, err) + canEditProducts := []*models.Product{} + if len(projectsEnvCanEdit) != 0 { + canEditProducts, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanEdit}) + if err != nil { + log.Warnf("[%s] Collections.Product.List error: %s", projectsEnvCanEdit, err) + } } canEditProducts = filterProductWithoutExternalCluster(canEditProducts) for _, vv := range canEditProducts { if err := CreateRoleBinding(vv.Namespace, namespace, serviceAccountName, config.RoleBindingNameEdit); err != nil { - log.Errorf("CreateRoleBinding err: %s", err) + log.Warnf("CreateRoleBinding err: %s", err) } } - - canViewProducts, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanView}) - if err != nil { - log.Errorf("[%s] Collections.Product.List error: %s", projectsEnvCanView, err) + canViewProducts := []*models.Product{} + if len(projectsEnvCanView) != 0 { + canViewProducts, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanView}) + if err != nil { + log.Warnf("[%s] Collections.Product.List error: %s", projectsEnvCanView, err) + } } canViewProducts = filterProductWithoutExternalCluster(canViewProducts) for _, vv := range canViewProducts { if err := CreateRoleBinding(vv.Namespace, namespace, serviceAccountName, config.RoleBindingNameView); err != nil { - log.Errorf("CreateRoleBinding err: %s", err) + log.Warnf("CreateRoleBinding err: %s", err) } } @@ -284,7 +290,7 @@ func CreateRoleBinding(rbNamespace, saNamspace, serviceAccountName, roleBindName log.Errorf("GetRoleBinding name:%s err: %s", roleBindName, err) return err } - if !found{ + if !found { if err := updater.CreateRoleBinding(&rbacv1beta1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: roleBindName, @@ -303,7 +309,7 @@ func CreateRoleBinding(rbNamespace, saNamspace, serviceAccountName, roleBindName log.Errorf("create rolebinding:%s err: %s", roleBindName, err) return err } - }else{ + } else { isExist := false for _, v := range rolebinding.Subjects { if v.Name == serviceAccountName { diff --git a/pkg/microservice/picket/core/filter/service/kubeconfig.go b/pkg/microservice/picket/core/filter/service/kubeconfig.go index b20b1fc8..773fc8cb 100644 --- a/pkg/microservice/picket/core/filter/service/kubeconfig.go +++ b/pkg/microservice/picket/core/filter/service/kubeconfig.go @@ -18,7 +18,7 @@ func GetKubeConfig(header http.Header, qs url.Values, logger *zap.SugaredLogger) endpoint: "/api/aslan/environment/environments", }} editEnvRules := []*rule{{ - method: "POST", + method: "PUT", endpoint: "/api/aslan/environment/environments/?*", }, { method: "POST", -- Gitee From 8c7903fee082d285cdbf11244c8efc93290920e1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 1 Dec 2021 14:51:36 +0800 Subject: [PATCH 0847/2647] add basic logic to push images by running jobs Signed-off-by: allenshen --- Makefile | 2 +- cmd/packager/main.go | 29 ++ docker/service/packager-plugin.Dockerfile | 17 + pkg/microservice/aslan/config/config.go | 4 + pkg/microservice/aslan/config/consts.go | 31 +- .../core/common/repository/models/queue.go | 39 +- .../models/task/artifact_package.go | 50 +++ .../common/repository/models/task/model.go | 12 +- .../aslan/core/common/service/base/convert.go | 8 + .../core/common/service/config_payload.go | 1 + .../aslan/core/workflow/handler/router.go | 4 + .../aslan/core/workflow/handler/workflow.go | 22 + .../service/workflow/artifact_task.go | 140 ++++++ .../core/workflow/service/workflow/convert.go | 162 +++---- .../service/workflow/workflow_task.go | 2 + pkg/microservice/packager/config/config.go | 33 ++ .../packager/core/service/docker.go | 72 +++ .../packager/core/service/packager.go | 46 ++ .../packager/core/service/service.go | 215 +++++++++ .../packager/executor/executor.go | 51 +++ pkg/microservice/warpdrive/config/const.go | 31 +- .../service/taskcontroller/task_handler.go | 3 + .../service/taskcontroller/task_helper.go | 26 +- .../service/taskplugin/artifact_package.go | 421 ++++++++++++++++++ .../warpdrive/core/service/taskplugin/job.go | 15 +- .../core/service/taskplugin/utils.go | 38 +- .../warpdrive/core/service/types/packager.go | 27 ++ .../service/types/task/artifact_package.go | 50 +++ .../core/service/types/task/config_payload.go | 3 + .../core/service/types/task/model.go | 20 + pkg/setting/consts.go | 7 + 31 files changed, 1430 insertions(+), 151 deletions(-) create mode 100644 cmd/packager/main.go create mode 100644 docker/service/packager-plugin.Dockerfile create mode 100644 pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go create mode 100644 pkg/microservice/packager/config/config.go create mode 100644 pkg/microservice/packager/core/service/docker.go create mode 100644 pkg/microservice/packager/core/service/packager.go create mode 100644 pkg/microservice/packager/core/service/service.go create mode 100644 pkg/microservice/packager/executor/executor.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go create mode 100644 pkg/microservice/warpdrive/core/service/types/packager.go create mode 100644 pkg/microservice/warpdrive/core/service/types/task/artifact_package.go diff --git a/Makefile b/Makefile index 08428072..224828be 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc VERSION ?= $(shell date +'%Y%m%d%H%M%S') -TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy user picket config init +TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin packager-plugin resource-server ua warpdrive policy user picket config init REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) diff --git a/cmd/packager/main.go b/cmd/packager/main.go new file mode 100644 index 00000000..ba2938d9 --- /dev/null +++ b/cmd/packager/main.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "log" + + "github.com/koderover/zadig/pkg/microservice/packager/executor" +) + +func main() { + if err := executor.Execute(); err != nil { + log.Fatalf("Failed to run predator, the error is: %+v", err) + } +} diff --git a/docker/service/packager-plugin.Dockerfile b/docker/service/packager-plugin.Dockerfile new file mode 100644 index 00000000..d7074f6a --- /dev/null +++ b/docker/service/packager-plugin.Dockerfile @@ -0,0 +1,17 @@ +#golang.Dockerfile + +RUN go build -v -o /packager-plugin ./cmd/packager/main.go + +#ubuntu-xenial.Dockerfile + +# 安装 docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin &&\ + rm -rf docke* + +WORKDIR /app + +COPY --from=build /packager-plugin . + +ENTRYPOINT ["/app/packager-plugin"] diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 81838113..7b1c3e4b 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -190,6 +190,10 @@ func PredatorImage() string { return viper.GetString(setting.ENVPredatorImage) } +func PackagerImage() string { + return viper.GetString(setting.EnvPackagerImage) +} + func DockerHosts() []string { return strings.Split(viper.GetString(setting.ENVDockerHosts), ",") } diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 36376093..b810ddd3 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -81,6 +81,8 @@ const ( TestType PipelineType = "test" // ServiceType 服务 ServiceType PipelineType = "service" + // ArtifactPackageType package artifact + ArtifactType PipelineType = "artifact" ) type Status string @@ -115,20 +117,21 @@ const ( type TaskType string const ( - TaskPipeline TaskType = "pipeline" - TaskBuild TaskType = "buildv2" - TaskJenkinsBuild TaskType = "jenkins_build" - TaskArtifact TaskType = "artifact" - TaskArtifactDeploy TaskType = "artifact_deploy" - TaskDeploy TaskType = "deploy" - TaskTestingV2 TaskType = "testingv2" - TaskDistributeToS3 TaskType = "distribute2kodo" - TaskReleaseImage TaskType = "release_image" - TaskJira TaskType = "jira" - TaskDockerBuild TaskType = "docker_build" - TaskSecurity TaskType = "security" - TaskResetImage TaskType = "reset_image" - TaskDistribute TaskType = "distribute" + TaskPipeline TaskType = "pipeline" + TaskBuild TaskType = "buildv2" + TaskJenkinsBuild TaskType = "jenkins_build" + TaskArtifact TaskType = "artifact" + TaskArtifactDeploy TaskType = "artifact_deploy" + TaskDeploy TaskType = "deploy" + TaskTestingV2 TaskType = "testingv2" + TaskDistributeToS3 TaskType = "distribute2kodo" + TaskReleaseImage TaskType = "release_image" + TaskJira TaskType = "jira" + TaskDockerBuild TaskType = "docker_build" + TaskSecurity TaskType = "security" + TaskResetImage TaskType = "reset_image" + TaskDistribute TaskType = "distribute" + TaskArtifactPackage TaskType = "artifact_package" ) type DistributeType string diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 7801b2c4..9d81c752 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -19,9 +19,8 @@ package models import ( "sync" - "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/microservice/aslan/config" + "go.mongodb.org/mongo-driver/bson/primitive" ) type Queue struct { @@ -64,12 +63,14 @@ type Queue struct { // TestArgs 测试任务参数 TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` - ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + // ArtifactPackageTaskArgs arguments for artifact-package type tasks + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` @@ -108,6 +109,25 @@ type ServiceTaskArgs struct { Updatable bool `bson:"updatable" json:"updatable"` } +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` +} + +type ImagesByService struct { + ServiceName string `bson:"service_name" json:"service_name"` + Images []*ImageData `bson:"images" json:"images"` +} + +type ArtifactPackageTaskArgs struct { + ProjectName string `bson:"project_name" json:"project_name"` + EnvName string `bson:"env_name" json:"env_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `json:"source_registries" json:"source_registries"` + TargetRegistries []string `json:"target_registries" json:"target_registries"` +} + type ConfigPayload struct { Proxy Proxy `json:"proxy"` S3Storage S3Config `json:"s3_storage"` @@ -208,6 +228,9 @@ type ReleaseConfig struct { // PredatorImage sets docker build image // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string + // PackagerImage sets docker build image + // e.g. xxx.com/resources/predator-plugin:v0.1.0 + PackagerImage string } type ImageReleaseConfig struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go new file mode 100644 index 00000000..8f578b62 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type ArtifactPackage struct { + TaskType config.TaskType `bson:"type" json:"type"` + TaskStatus config.Status `bson:"status" json:"status"` + Enabled bool `bson:"enabled" json:"enabled"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + LogFile string `bson:"log_file" json:"log_file"` + + // source images + Images []*models.ImagesByService `bson:"images" json:"images"` + // source registries need auth to pull images + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + // target registries to push images + TargetRegistries []string `bson:"target_registries" json:"target_registries"` +} + +func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(ri, &task); err != nil { + return nil, fmt.Errorf("convert ReleaseImageTask to interface error: %v", err) + } + return task, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index c503d9c6..746534f3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -67,12 +67,14 @@ type Task struct { TestArgs *models.TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *models.ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + // ArtifactPackageTaskArgs arguments for artifact-package type tasks + ArtifactPackageTaskArgs *models.ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` // ConfigPayload 系统配置信息 - ConfigPayload *models.ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*models.ProductService `bson:"services" json:"services"` - Render *models.RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + ConfigPayload *models.ConfigPayload `json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*models.ProductService `bson:"services" json:"services"` + Render *models.RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` diff --git a/pkg/microservice/aslan/core/common/service/base/convert.go b/pkg/microservice/aslan/core/common/service/base/convert.go index b6913548..95ae1740 100644 --- a/pkg/microservice/aslan/core/common/service/base/convert.go +++ b/pkg/microservice/aslan/core/common/service/base/convert.go @@ -92,6 +92,14 @@ func ToReleaseImageTask(sb map[string]interface{}) (*task.ReleaseImage, error) { return t, nil } +func ToArtifactPackageImageTask(sb map[string]interface{}) (*task.ArtifactPackage, error) { + var t *task.ArtifactPackage + if err := task.IToi(sb, &t); err != nil { + return nil, fmt.Errorf("convert interface to ReleaseImageTask error: %v", err) + } + return t, nil +} + func ToJiraTask(sb map[string]interface{}) (*task.Jira, error) { var t *task.Jira if err := task.IToi(sb, &t); err != nil { diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index ac244fe3..dcbb24ae 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -54,6 +54,7 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { ReaperImage: config.ReaperImage(), ReaperBinaryFile: config.ReaperBinaryFile(), PredatorImage: config.PredatorImage(), + PackagerImage: config.PackagerImage(), }, Docker: models.DockerConfig{ HostList: config.DockerHosts(), diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 091767d4..b8fde441 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -103,6 +103,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflow.POST("/:productName/auto", AutoCreateWorkflow) workflow.POST("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateWorkflow) + + // test api need to delete + workflow.POST("airifactTask", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateArtifactTask) + workflow.PUT("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) workflow.GET("/testName/:testName", ListTestWorkflows) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index c73934a4..d33f1a58 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -90,6 +90,28 @@ func CreateWorkflow(c *gin.Context) { ctx.Err = workflow.CreateWorkflow(args, ctx.Logger) } +// TODO test code need to delete +func CreateArtifactTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.ArtifactPackageTaskArgs) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflow c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateWorkflow json.Unmarshal err : %v", err) + } + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + if err := c.ShouldBindWith(&args, binding.JSON); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = workflow.CreateArtifactPackageTask(args, ctx.Logger) +} + // UpdateWorkflow update a workflow func UpdateWorkflow(c *gin.Context) { ctx := internalhandler.NewContext(c) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go new file mode 100644 index 00000000..15c7b59f --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -0,0 +1,140 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workflow + +import ( + "fmt" + "sort" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/util" + "go.uber.org/zap" +) + +func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log *zap.SugaredLogger) error { + + // 获取全局configpayload + configPayload := commonservice.GetConfigPayload(0) + repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + + if err != nil { + log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) + return fmt.Errorf("failed to query registries") + } + + registriesInvolved := sets.NewString() + registriesInvolved.Insert(args.SourceRegistries...) + registriesInvolved.Insert(args.TargetRegistries...) + + configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) + for _, repo := range repos { + if !registriesInvolved.Has(repo.ID.Hex()) { + continue + } + // if the registry is SWR, we need to modify ak/sk according to the rule + if repo.RegProvider == config.SWRProvider { + ak := fmt.Sprintf("%s@%s", repo.Region, repo.AccessKey) + sk := util.ComputeHmacSha256(repo.AccessKey, repo.SecretKey) + repo.AccessKey = ak + repo.SecretKey = sk + } + configPayload.RepoConfigs[repo.ID.Hex()] = repo + } + + defaultS3, err := s3.FindDefaultS3() + if err != nil { + err = e.ErrFindDefaultS3Storage.AddDesc("default storage is required by distribute task") + return err + } + + defaultURL, err := defaultS3.GetEncryptedURL() + if err != nil { + err = e.ErrS3Storage.AddErr(err) + return err + } + + task := &task.Task{ + Type: config.ArtifactType, + ProductName: args.ProjectName, + Status: config.StatusCreated, + ArtifactPackageTaskArgs: args, + ConfigPayload: configPayload, + StorageURI: defaultURL, + } + + subTask, err := (&taskmodels.ArtifactPackage{ + TaskType: config.TaskArtifactPackage, + Enabled: true, + TaskStatus: "", + Timeout: 0, + StartTime: 0, + EndTime: 0, + LogFile: "", + Images: args.Images, + SourceRegistries: args.SourceRegistries, + TargetRegistries: args.TargetRegistries, + }).ToSubTask() + + if err != nil { + return err + } + + task.SubTasks = []map[string]interface{}{subTask} + + if err := ensurePipelineTask(task, "", log); err != nil { + log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) + return err + } + + stages := make([]*commonmodels.Stage, 0) + for _, subTask := range task.SubTasks { + AddSubtaskToStage(&stages, subTask, args.EnvName) + } + sort.Sort(ByStageKind(stages)) + task.Stages = stages + if len(task.Stages) == 0 { + return e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + } + + pipelineName := fmt.Sprintf("%s-%s-%s", args.ProjectName, args.EnvName, "artifact") + nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.ServiceTaskFmt, pipelineName)) + if err != nil { + log.Errorf("CreateServiceTask Counter.GetNextSeq error: %v", err) + return e.ErrGetCounter.AddDesc(err.Error()) + } + + task.SubTasks = []map[string]interface{}{} + task.TaskID = nextTaskID + task.PipelineName = pipelineName + + if err := CreateTask(task); err != nil { + log.Error(err) + return e.ErrCreateTask + } + + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 7d98f8c9..1bd2649d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -31,91 +31,93 @@ import ( func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { return &task.Task{ - TaskID: queueTask.TaskID, - ProductName: queueTask.ProductName, - PipelineName: queueTask.PipelineName, - Type: queueTask.Type, - Status: queueTask.Status, - Description: queueTask.Description, - TaskCreator: queueTask.TaskCreator, - TaskRevoker: queueTask.TaskRevoker, - CreateTime: queueTask.CreateTime, - StartTime: queueTask.StartTime, - EndTime: queueTask.EndTime, - SubTasks: queueTask.SubTasks, - Stages: queueTask.Stages, - ReqID: queueTask.ReqID, - AgentHost: queueTask.AgentHost, - DockerHost: queueTask.DockerHost, - TeamName: queueTask.TeamName, - IsDeleted: queueTask.IsDeleted, - IsArchived: queueTask.IsArchived, - AgentID: queueTask.AgentID, - MultiRun: queueTask.MultiRun, - Target: queueTask.Target, - BuildModuleVer: queueTask.BuildModuleVer, - ServiceName: queueTask.ServiceName, - TaskArgs: queueTask.TaskArgs, - WorkflowArgs: queueTask.WorkflowArgs, - TestArgs: queueTask.TestArgs, - ServiceTaskArgs: queueTask.ServiceTaskArgs, - ConfigPayload: queueTask.ConfigPayload, - Error: queueTask.Error, - Services: queueTask.Services, - Render: queueTask.Render, - StorageURI: queueTask.StorageURI, - TestReports: queueTask.TestReports, - RwLock: queueTask.RwLock, - ResetImage: queueTask.ResetImage, - TriggerBy: queueTask.TriggerBy, - Features: queueTask.Features, - IsRestart: queueTask.IsRestart, - StorageEndpoint: queueTask.StorageEndpoint, + TaskID: queueTask.TaskID, + ProductName: queueTask.ProductName, + PipelineName: queueTask.PipelineName, + Type: queueTask.Type, + Status: queueTask.Status, + Description: queueTask.Description, + TaskCreator: queueTask.TaskCreator, + TaskRevoker: queueTask.TaskRevoker, + CreateTime: queueTask.CreateTime, + StartTime: queueTask.StartTime, + EndTime: queueTask.EndTime, + SubTasks: queueTask.SubTasks, + Stages: queueTask.Stages, + ReqID: queueTask.ReqID, + AgentHost: queueTask.AgentHost, + DockerHost: queueTask.DockerHost, + TeamName: queueTask.TeamName, + IsDeleted: queueTask.IsDeleted, + IsArchived: queueTask.IsArchived, + AgentID: queueTask.AgentID, + MultiRun: queueTask.MultiRun, + Target: queueTask.Target, + BuildModuleVer: queueTask.BuildModuleVer, + ServiceName: queueTask.ServiceName, + TaskArgs: queueTask.TaskArgs, + WorkflowArgs: queueTask.WorkflowArgs, + TestArgs: queueTask.TestArgs, + ServiceTaskArgs: queueTask.ServiceTaskArgs, + ArtifactPackageTaskArgs: queueTask.ArtifactPackageTaskArgs, + ConfigPayload: queueTask.ConfigPayload, + Error: queueTask.Error, + Services: queueTask.Services, + Render: queueTask.Render, + StorageURI: queueTask.StorageURI, + TestReports: queueTask.TestReports, + RwLock: queueTask.RwLock, + ResetImage: queueTask.ResetImage, + TriggerBy: queueTask.TriggerBy, + Features: queueTask.Features, + IsRestart: queueTask.IsRestart, + StorageEndpoint: queueTask.StorageEndpoint, } } func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { return &commonmodels.Queue{ - TaskID: task.TaskID, - ProductName: task.ProductName, - PipelineName: task.PipelineName, - Type: task.Type, - Status: task.Status, - Description: task.Description, - TaskCreator: task.TaskCreator, - TaskRevoker: task.TaskRevoker, - CreateTime: task.CreateTime, - StartTime: task.StartTime, - EndTime: task.EndTime, - SubTasks: task.SubTasks, - Stages: task.Stages, - ReqID: task.ReqID, - AgentHost: task.AgentHost, - DockerHost: task.DockerHost, - TeamName: task.TeamName, - IsDeleted: task.IsDeleted, - IsArchived: task.IsArchived, - AgentID: task.AgentID, - MultiRun: task.MultiRun, - Target: task.Target, - BuildModuleVer: task.BuildModuleVer, - ServiceName: task.ServiceName, - TaskArgs: task.TaskArgs, - WorkflowArgs: task.WorkflowArgs, - TestArgs: task.TestArgs, - ServiceTaskArgs: task.ServiceTaskArgs, - ConfigPayload: task.ConfigPayload, - Error: task.Error, - Services: task.Services, - Render: task.Render, - StorageURI: task.StorageURI, - TestReports: task.TestReports, - RwLock: task.RwLock, - ResetImage: task.ResetImage, - TriggerBy: task.TriggerBy, - Features: task.Features, - IsRestart: task.IsRestart, - StorageEndpoint: task.StorageEndpoint, + TaskID: task.TaskID, + ProductName: task.ProductName, + PipelineName: task.PipelineName, + Type: task.Type, + Status: task.Status, + Description: task.Description, + TaskCreator: task.TaskCreator, + TaskRevoker: task.TaskRevoker, + CreateTime: task.CreateTime, + StartTime: task.StartTime, + EndTime: task.EndTime, + SubTasks: task.SubTasks, + Stages: task.Stages, + ReqID: task.ReqID, + AgentHost: task.AgentHost, + DockerHost: task.DockerHost, + TeamName: task.TeamName, + IsDeleted: task.IsDeleted, + IsArchived: task.IsArchived, + AgentID: task.AgentID, + MultiRun: task.MultiRun, + Target: task.Target, + BuildModuleVer: task.BuildModuleVer, + ServiceName: task.ServiceName, + TaskArgs: task.TaskArgs, + WorkflowArgs: task.WorkflowArgs, + TestArgs: task.TestArgs, + ServiceTaskArgs: task.ServiceTaskArgs, + ArtifactPackageTaskArgs: task.ArtifactPackageTaskArgs, + ConfigPayload: task.ConfigPayload, + Error: task.Error, + Services: task.Services, + Render: task.Render, + StorageURI: task.StorageURI, + TestReports: task.TestReports, + RwLock: task.RwLock, + ResetImage: task.ResetImage, + TriggerBy: task.TriggerBy, + Features: task.Features, + IsRestart: task.IsRestart, + StorageEndpoint: task.StorageEndpoint, } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 368a4352..e7cd5588 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2147,6 +2147,8 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } case config.TaskDistribute: // 为了兼容历史数据类型,目前什么都不用做,避免出错 + case config.TaskArtifactPackage: + // do nothing default: return e.NewErrInvalidTaskType(string(pre.TaskType)) } diff --git a/pkg/microservice/packager/config/config.go b/pkg/microservice/packager/config/config.go new file mode 100644 index 00000000..76ed9e11 --- /dev/null +++ b/pkg/microservice/packager/config/config.go @@ -0,0 +1,33 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "github.com/spf13/viper" + + // init the config first + _ "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" +) + +func JobConfigFile() string { + return viper.GetString(setting.JobConfigFile) +} + +func Home() string { + return viper.GetString(setting.Home) +} diff --git a/pkg/microservice/packager/core/service/docker.go b/pkg/microservice/packager/core/service/docker.go new file mode 100644 index 00000000..8cb0bce0 --- /dev/null +++ b/pkg/microservice/packager/core/service/docker.go @@ -0,0 +1,72 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "os/exec" + "strings" +) + +const dockerExe = "/usr/local/bin/docker" + +func dockerVersion() *exec.Cmd { + return exec.Command(dockerExe, "version") +} + +func dockerInfo() *exec.Cmd { + return exec.Command(dockerExe, "info") +} + +// Docker returns build command +// +// e.g. docker build --rm=true -t name:tag -f Dockerfile --build-arg APP_BIN=... . +func dockerBuild(dockerfile, fullImage, buildArgs string) *exec.Cmd { + + args := []string{"build", "--rm=true"} + if buildArgs != "" { + for _, val := range strings.Fields(buildArgs) { + if val != "" { + args = append(args, val) + } + } + + } + args = append(args, []string{"-t", fullImage, "-f", dockerfile, "."}...) + return exec.Command(dockerExe, args...) +} + +func dockerPull(image string) *exec.Cmd { + args := []string{"pull", image} + return exec.Command(dockerExe, args...) +} + +func dockerTag(sourceFullImage, targetFullImage string) *exec.Cmd { + args := []string{ + "tag", + sourceFullImage, + targetFullImage, + } + return exec.Command(dockerExe, args...) +} + +func dockerPush(fullImage string) *exec.Cmd { + args := []string{ + "push", + fullImage, + } + return exec.Command(dockerExe, args...) +} diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go new file mode 100644 index 00000000..771d2f20 --- /dev/null +++ b/pkg/microservice/packager/core/service/packager.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` +} + +// ImagesByService defines all images in a service +type ImagesByService struct { + ServiceName string `bson:"service_name" json:"service_name"` + Images []*ImageData `bson:"images" json:"images"` +} + +//DockerRegistry registry host/user/password +type DockerRegistry struct { + Host string `yaml:"host"` + UserName string `yaml:"username"` + Password string `yaml:"password"` + Namespace string `yaml:"namespace"` +} + +// Context ... +type Context struct { + JobType string `yaml:"job_type"` + ProgressFile string `yaml:"progress_file"` + Images []*ImagesByService `yaml:"images"` + SourceRegistries []*DockerRegistry `yaml:"source_registries"` + TargetRegistries []*DockerRegistry `yaml:"target_registries"` +} diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go new file mode 100644 index 00000000..1f85dc7e --- /dev/null +++ b/pkg/microservice/packager/core/service/service.go @@ -0,0 +1,215 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "time" + + "github.com/pkg/errors" + + "gopkg.in/yaml.v3" + + "github.com/koderover/zadig/pkg/microservice/packager/config" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" +) + +// Packager ... +type Packager struct { + Ctx *Context +} + +type ServicePackageResult struct { + ServiceName string `json:"service_name"` + Result string `json:"result"` + ErrorMsg string `json:"error_msg"` +} + +func NewPackager() (*Packager, error) { + context, err := ioutil.ReadFile(config.JobConfigFile()) + if err != nil { + return nil, err + } + + var ctx *Context + if err := yaml.Unmarshal(context, &ctx); err != nil { + return nil, err + } + + packager := &Packager{ + Ctx: ctx, + } + + return packager, nil +} + +// BeforeExec ... +func (p *Packager) BeforeExec() error { + log.Info("wait for docker daemon to start") + for i := 0; i < 120; i++ { + err := dockerInfo().Run() + if err == nil { + break + } + time.Sleep(time.Second * 1) + } + + if len(p.Ctx.ProgressFile) == 0 { + return nil + } + + // create log file + if err := os.MkdirAll(filepath.Dir(p.Ctx.ProgressFile), 0770); err != nil { + return errors.Wrapf(err, "failed to create progress file dir") + } + file, err := os.Create(p.Ctx.ProgressFile) + if err != nil { + return errors.Wrapf(err, "failed to create progress file") + } + err = file.Close() + return errors.Wrapf(err, "failed to close progress file") +} + +// Exec ... +func (p *Packager) Exec() error { + + // add docker registry auths for all registries + allRegistries := make([]*DockerRegistry, 0) + allRegistries = append(allRegistries, p.Ctx.SourceRegistries...) + allRegistries = append(allRegistries, p.Ctx.TargetRegistries...) + err := writeDockerConfig(allRegistries) + if err != nil { + return err + } + + realTimeProgress := make([]*ServicePackageResult, 0) + + for _, image := range p.Ctx.Images { + cmds := p.dockerCommands(image) + var err error + for _, cmd := range cmds { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + log.Info(strings.Join(cmd.Args, " ")) + if err = cmd.Run(); err != nil { + break + } + } + + result := &ServicePackageResult{ + ServiceName: image.ServiceName, + } + + if err != nil { + result.Result = "failed" + result.ErrorMsg = err.Error() + log.Infof("[result][fail][%s][%s]", image.ServiceName, err) + } else { + result.Result = "success" + log.Infof("[result][success][%s]", image.ServiceName) + } + + realTimeProgress = append(realTimeProgress, result) + + if len(p.Ctx.ProgressFile) > 0 { + bs, err := json.Marshal(realTimeProgress) + if err != nil { + log.Errorf("failed to marshal progress data %s", err) + continue + } + err = os.WriteFile(p.Ctx.ProgressFile, bs, 0644) + if err != nil { + log.Errorf("failed to write progress data %s", err) + continue + } + } + } + + time.Sleep(time.Second * 10) + return nil +} + +func writeDockerConfig(registries []*DockerRegistry) error { + + authMap := make(map[string]map[string]string) + //host string, username string, password string + for _, registry := range registries { + if registry.UserName == "" { + continue + } + authMap[registry.Host] = map[string]string{ + "auth": base64.StdEncoding.EncodeToString( + []byte(strings.Join([]string{registry.UserName, registry.Password}, ":")), + )} + } + + dir := path.Join(config.Home(), ".docker") + if err := os.MkdirAll(dir, 0600); err != nil { + return err + } + + cfg := map[string]map[string]map[string]string{ + "auths": authMap, + } + + data, err := json.Marshal(cfg) + + if err != nil { + return err + } + + return ioutil.WriteFile(path.Join(dir, "config.json"), data, 0600) +} + +func (p *Packager) dockerCommands(imageByService *ImagesByService) []*exec.Cmd { + cmds := make([]*exec.Cmd, 0) + //cmds = append(cmds, dockerVersion()) + + buildTargetImage := func(imageName, imageTag, host, nameSpace string) string { + ret := "" + if len(nameSpace) > 0 { + ret = fmt.Sprintf("%s/%s/%s:%s", host, nameSpace, imageName, imageTag) + } else { + ret = fmt.Sprintf("%s/%s:%s", host, imageName, imageTag) + } + ret = strings.TrimPrefix(ret, "http://") + ret = strings.TrimPrefix(ret, "https://") + return ret + } + + if p.Ctx.JobType == setting.BuildChartPackage { + for _, image := range imageByService.Images { + for _, registry := range p.Ctx.TargetRegistries { + cmds = append(cmds, dockerPull(image.ImageUrl)) + targetImage := buildTargetImage(image.ImageName, image.ImageTag, registry.Host, registry.Namespace) + cmds = append(cmds, dockerTag(image.ImageUrl, targetImage)) + cmds = append(cmds, dockerPush(targetImage)) + } + } + } + + return cmds +} diff --git a/pkg/microservice/packager/executor/executor.go b/pkg/microservice/packager/executor/executor.go new file mode 100644 index 00000000..13f75787 --- /dev/null +++ b/pkg/microservice/packager/executor/executor.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package executor + +import ( + commonconfig "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/packager/core/service" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" +) + +func Execute() error { + log.Init(&log.Config{ + Level: commonconfig.LogLevel(), + NoCaller: true, + NoLogLevel: true, + Development: commonconfig.Mode() != setting.ReleaseMode, + }) + + pred, err := service.NewPackager() + if err != nil { + log.Errorf("Failed to start packager, error: %s", err) + return err + } + + if err := pred.BeforeExec(); err != nil { + log.Errorf("Failed to run before exec step, error: %s", err) + return err + } + + if err := pred.Exec(); err != nil { + log.Errorf("Failed to run exec step, error: %s", err) + return err + } + + return nil +} diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 6e4fcaca..88c00a89 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -19,20 +19,21 @@ package config type TaskType string const ( - TaskPipeline TaskType = "pipeline" - TaskBuild TaskType = "buildv2" - TaskArtifactDeploy TaskType = "artifact_deploy" - TaskJenkinsBuild TaskType = "jenkins_build" - TaskArtifact TaskType = "artifact" - TaskDeploy TaskType = "deploy" - TaskTestingV2 TaskType = "testingv2" - TaskDistributeToS3 TaskType = "distribute2kodo" - TaskReleaseImage TaskType = "release_image" - TaskJira TaskType = "jira" - TaskDockerBuild TaskType = "docker_build" - TaskSecurity TaskType = "security" - TaskResetImage TaskType = "reset_image" - TaskDistribute TaskType = "distribute" + TaskPipeline TaskType = "pipeline" + TaskBuild TaskType = "buildv2" + TaskArtifactDeploy TaskType = "artifact_deploy" + TaskJenkinsBuild TaskType = "jenkins_build" + TaskArtifact TaskType = "artifact" + TaskDeploy TaskType = "deploy" + TaskTestingV2 TaskType = "testingv2" + TaskDistributeToS3 TaskType = "distribute2kodo" + TaskReleaseImage TaskType = "release_image" + TaskJira TaskType = "jira" + TaskDockerBuild TaskType = "docker_build" + TaskSecurity TaskType = "security" + TaskResetImage TaskType = "reset_image" + TaskDistribute TaskType = "distribute" + TaskArtifactPackage TaskType = "artifact_package" ) type Status string @@ -65,6 +66,8 @@ const ( TestType PipelineType = "test" // ServiceType 服务 ServiceType PipelineType = "service" + // ArtifactType 服务 + ArtifactType PipelineType = "artifact" ) type NotifyType int diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 301be7bf..db98e084 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -411,6 +411,9 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } else if pipelineTask.Type == config.ServiceType { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.ServiceType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), servicename)), "_", "-", -1) + } else if pipelineTask.Type == config.ArtifactType { + fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.ArtifactType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), + "_", "-", -1) } plugin.Init(jobName, fileName, xl) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index b0a13595..f8b7a57e 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -136,6 +136,9 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv } else if pipelineTask.Type == config.ServiceType { xl.Info("pipeline type is service type: pipeline 3.0") pipelineTask.Stages[pos].SubTasks[servicename] = subTask + } else if pipelineTask.Type == config.ArtifactType { + xl.Info("pipeline type is artifact-package type: pipeline 3.0") + pipelineTask.Stages[pos].SubTasks[servicename] = subTask } } @@ -538,17 +541,18 @@ func getSubTaskTypeAndIsRestart(subTask map[string]interface{}) bool { func initTaskPlugins(execHandler *ExecHandler) { pluginConf := map[config.TaskType]plugins.Initiator{ - config.TaskJira: plugins.InitializeJiraTaskPlugin, - config.TaskBuild: plugins.InitializeBuildTaskPlugin, - config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, - config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, - config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, - config.TaskDeploy: plugins.InitializeDeployTaskPlugin, - config.TaskTestingV2: plugins.InitializeTestTaskPlugin, - config.TaskSecurity: plugins.InitializeSecurityPlugin, - config.TaskReleaseImage: plugins.InitializeReleaseImagePlugin, - config.TaskDistributeToS3: plugins.InitializeDistribute2S3TaskPlugin, - config.TaskResetImage: plugins.InitializeDeployTaskPlugin, + config.TaskJira: plugins.InitializeJiraTaskPlugin, + config.TaskBuild: plugins.InitializeBuildTaskPlugin, + config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, + config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, + config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, + config.TaskDeploy: plugins.InitializeDeployTaskPlugin, + config.TaskTestingV2: plugins.InitializeTestTaskPlugin, + config.TaskSecurity: plugins.InitializeSecurityPlugin, + config.TaskReleaseImage: plugins.InitializeReleaseImagePlugin, + config.TaskDistributeToS3: plugins.InitializeDistribute2S3TaskPlugin, + config.TaskResetImage: plugins.InitializeDeployTaskPlugin, + config.TaskArtifactPackage: plugins.InitializeArtifactPackagePlugin, } for name, pluginInitiator := range pluginConf { registerTaskPlugin(execHandler, name, pluginInitiator) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go new file mode 100644 index 00000000..2436728d --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -0,0 +1,421 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "time" + + "github.com/koderover/zadig/pkg/shared/kube/wrapper" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/podexec" + "k8s.io/apimachinery/pkg/labels" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" + "github.com/koderover/zadig/pkg/setting" + "gopkg.in/yaml.v3" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// InitializeArtifactPackagePlugin to ini +func InitializeArtifactPackagePlugin(taskType config.TaskType) TaskPlugin { + return &ArtifactPackageTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +type ArtifactPackageTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.ArtifactPackage + AckFunc func() + Log *zap.SugaredLogger + + RealTimeProgress string +} + +const ( + // ArtifactPackageBuildImageTaskTimeout ... + ArtifactPackageBuildImageTaskTimeout = 60 * 5 // 5 minutes +) + +// Init ... +func (p *ArtifactPackageTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.FileName = filename + // SetLogger ... + p.Log = xl +} + +func (p *ArtifactPackageTaskPlugin) SetAckFunc(f func()) { + p.AckFunc = f +} + +// Type ... +func (p *ArtifactPackageTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *ArtifactPackageTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *ArtifactPackageTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +func (p *ArtifactPackageTaskPlugin) SetProgress(progress string) { + p.Task.Progress = progress +} + +// TaskTimeout ... +func (p *ArtifactPackageTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = ArtifactPackageBuildImageTaskTimeout + } + return p.Task.Timeout +} + +func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + + sourceRegistries := make([]*types.DockerRegistry, 0) + for _, registryID := range pipelineTask.ArtifactPackageTaskArgs.SourceRegistries { + if registry, ok := pipelineTask.ConfigPayload.RepoConfigs[registryID]; ok { + sourceRegistries = append(sourceRegistries, &types.DockerRegistry{ + Host: registry.RegAddr, + Namespace: registry.Namespace, + UserName: registry.AccessKey, + Password: registry.SecretKey, + }) + } + } + + targetRegistries := make([]*types.DockerRegistry, 0) + for _, registryID := range pipelineTask.ArtifactPackageTaskArgs.TargetRegistries { + if registry, ok := pipelineTask.ConfigPayload.RepoConfigs[registryID]; ok { + targetRegistries = append(targetRegistries, &types.DockerRegistry{ + Host: registry.RegAddr, + Namespace: registry.Namespace, + UserName: registry.AccessKey, + Password: registry.SecretKey, + }) + } + } + + jobCtx := &types.ArtifactPackagerContext{ + JobType: setting.BuildChartPackage, + ProgressFile: setting.ProgressFile, + Images: pipelineTask.ArtifactPackageTaskArgs.Images, + SourceRegistries: sourceRegistries, + TargetRegistries: targetRegistries, + } + + jobCtxBytes, err := yaml.Marshal(jobCtx) + if err != nil { + msg := fmt.Sprintf("cannot mashal predetor.Context data: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + p.Task.Error = "" + + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("ensureDeleteConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + if err := createJobConfigMap( + p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient); err != nil { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.Log.Infof("succeed to create cm for artifact package job %s", p.JobName) + + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + if err != nil { + msg := fmt.Sprintf("create release artifact package job context error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("delete release artifact package job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + job.Namespace = p.KubeNamespace + if err := updater.CreateJob(job, p.kubeClient); err != nil { + msg := fmt.Sprintf("create release image job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.Log.Infof("succeed to create artifact package job %s", p.JobName) +} + +// SetTask ... +func (p *ArtifactPackageTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToArtifactTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *ArtifactPackageTaskPlugin) GetTask() interface{} { + return p.Task +} + +// Wait ... +func (p *ArtifactPackageTaskPlugin) Wait(ctx context.Context) { + + status := p.waitJobStart() + if status != config.StatusRunning { + p.SetStatus(status) + return + } + + status = p.waitJobEnd(ctx, p.TaskTimeout()) + p.SetProgress(p.RealTimeProgress) + p.SetStatus(status) +} + +func (p *ArtifactPackageTaskPlugin) waitJobStart() config.Status { + namespace, jobName, kubeClient, xl := p.KubeNamespace, p.JobName, p.kubeClient, p.Log + xl.Infof("wait job to start: %s/%s", namespace, jobName) + podTimeout := time.After(120 * time.Second) + var started bool + for { + select { + case <-podTimeout: + return config.StatusTimeout + default: + job, _, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil { + xl.Errorf("get job failed, namespace:%s, jobName:%s, err:%v", namespace, jobName, err) + } + if job != nil { + started = job.Status.Active > 0 + } + } + if started { + break + } + + time.Sleep(time.Second) + } + return config.StatusRunning +} + +func (p *ArtifactPackageTaskPlugin) waitJobEnd(ctx context.Context, taskTimeout int) (status config.Status) { + namespace, jobName, kubeClient, xl := p.KubeNamespace, p.JobName, p.kubeClient, p.Log + xl.Infof("wait job to start: %s/%s", namespace, jobName) + timeout := time.After(time.Duration(taskTimeout) * time.Second) + + // 等待job 运行结束 + xl.Infof("wait job to end: %s %s", namespace, jobName) + for { + select { + case <-ctx.Done(): + return config.StatusCancelled + + case <-timeout: + return config.StatusTimeout + + default: + job, found, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil || !found { + xl.Errorf("failed to get pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + // pod is still running + if job.Status.Active != 0 { + + pods, err := getter.ListPods(namespace, labels.Set{"job-name": jobName}.AsSelector(), kubeClient) + if err != nil { + xl.Errorf("failed to find pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + + var done bool + for _, pod := range pods { + ipod := wrapper.Pod(pod) + if ipod.Pending() { + continue + } + if ipod.Failed() { + return config.StatusFailed + } + + if !ipod.Finished() { + progressInfo, err := getProgressInfo(namespace, ipod.Name, ipod.ContainerNames()[0], setting.ProgressFile) + if err != nil { + xl.Infof("failed to check dog food file %s %v", pods[0].Name, err) + break + } + p.RealTimeProgress = progressInfo + xl.Infof("find progress info %s", progressInfo) + p.SetProgress(progressInfo) + p.SetStatus(config.StatusRunning) + + if p.AckFunc != nil { + p.AckFunc() + } + } else { + done = true + } + } + + if done { + return config.StatusPassed + } + } else if job.Status.Succeeded != 0 { + return config.StatusPassed + } else { + return config.StatusFailed + } + } + + time.Sleep(time.Second * 5) + } + +} + +// Complete ... +func (p *ArtifactPackageTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + // 清理用户取消和超时的任务 + defer func() { + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return + } + }() + + // 保存实时日志到s3 + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + if err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + return + } + + p.Task.LogFile = p.JobName +} + +// IsTaskDone ... +func (p *ArtifactPackageTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *ArtifactPackageTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *ArtifactPackageTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *ArtifactPackageTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *ArtifactPackageTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *ArtifactPackageTaskPlugin) ResetError() { + p.Task.Error = "" +} + +func getProgressInfo(namespace string, pod string, container string, progressInfoFile string) (string, error) { + stdOut, stdErr, success, err := podexec.ExecWithOptions(podexec.ExecOptions{ + Command: []string{"cat", progressInfoFile}, + Namespace: namespace, + PodName: pod, + ContainerName: container, + }) + + if err != nil { + return "", err + } + if len(stdErr) != 0 { + return "", fmt.Errorf(stdErr) + } + if !success { + return "", fmt.Errorf("pod exec execute failed") + } + return stdOut, nil +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1d13553e..81c0dbcf 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -57,6 +57,7 @@ const ( defaultSecretEmail = "bot@koderover.com" PredatorPlugin = "predator-plugin" JenkinsPlugin = "jenkins-plugin" + PackagerPlugin = "packager-plugin" ) const ( @@ -106,6 +107,7 @@ func saveContainerLog(pipelineTask *task.Task, namespace, fileName string, jobLa if err = saveFile(buf, tempFileName); err == nil { var store *s3.S3 if store, err = s3.NewS3StorageFromEncryptedURI(pipelineTask.StorageURI); err != nil { + log.Errorf("failed to NewS3StorageFromEncryptedURI ") return err } if store.Subfolder != "" { @@ -351,12 +353,19 @@ const ( // getJobLabels get labels k-v map from JobLabel struct func getJobLabels(jobLabel *JobLabel) map[string]string { - return map[string]string{ + retMap := map[string]string{ jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.PipelineName), jobLabel.TaskID), jobLabelServiceKey: strings.ToLower(jobLabel.ServiceName), jobLabelSTypeKey: strings.Replace(jobLabel.TaskType, "_", "-", -1), jobLabelPTypeKey: jobLabel.PipelineType, } + // no need to add labels with empty value to a job + for k, v := range retMap { + if len(v) == 0 { + delete(retMap, k) + } + } + return retMap } func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx string, kubeClient client.Client) error { @@ -399,7 +408,7 @@ func buildJob(taskType config.TaskType, jobImage, jobName, serviceName string, r func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName string, resReq setting.Request, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { var reaperBootingScript string - if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { + if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", pipelineTask.ConfigPayload.Release.ReaperBinaryFile) if pipelineTask.ConfigPayload.Proxy.EnableApplicationProxy && pipelineTask.ConfigPayload.Proxy.Type == "http" { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL --proxy %s %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", @@ -480,7 +489,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa }, } - if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { + if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { job.Spec.Template.Spec.Containers[0].Command = []string{"/bin/sh", "-c"} job.Spec.Template.Spec.Containers[0].Args = []string{reaperBootingScript} } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 6d411101..35236b81 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -98,7 +98,7 @@ type Preview struct { func ToPreview(sb map[string]interface{}) (*Preview, error) { var pre *Preview if err := IToi(sb, &pre); err != nil { - return nil, fmt.Errorf("convert interface to SubTaskPreview error: %v", err) + return nil, fmt.Errorf("convert interface to SubTaskPreview error: %s", err) } return pre, nil } @@ -106,15 +106,15 @@ func ToPreview(sb map[string]interface{}) (*Preview, error) { func ToBuildTask(sb map[string]interface{}) (*task.Build, error) { var t *task.Build if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to BuildTaskV2 error: %v", err) + return nil, fmt.Errorf("convert interface to BuildTaskV2 error: %s", err) } return t, nil } -func ToArtifactTask(sb map[string]interface{}) (*task.Artifact, error) { - var t *task.Artifact +func ToArtifactTask(sb map[string]interface{}) (*task.ArtifactPackage, error) { + var t *task.ArtifactPackage if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to ArtifactTask error: %v", err) + return nil, fmt.Errorf("convert interface to ArtifactTask error: %s", err) } return t, nil } @@ -122,7 +122,7 @@ func ToArtifactTask(sb map[string]interface{}) (*task.Artifact, error) { func ToDockerBuildTask(sb map[string]interface{}) (*task.DockerBuild, error) { var t *task.DockerBuild if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to DockerBuildTask error: %v", err) + return nil, fmt.Errorf("convert interface to DockerBuildTask error: %s", err) } return t, nil } @@ -130,7 +130,7 @@ func ToDockerBuildTask(sb map[string]interface{}) (*task.DockerBuild, error) { func ToDeployTask(sb map[string]interface{}) (*task.Deploy, error) { var t *task.Deploy if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to DeployTask error: %v", err) + return nil, fmt.Errorf("convert interface to DeployTask error: %s", err) } return t, nil } @@ -138,7 +138,7 @@ func ToDeployTask(sb map[string]interface{}) (*task.Deploy, error) { func ToTestingTask(sb map[string]interface{}) (*task.Testing, error) { var t *task.Testing if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to Testing error: %v", err) + return nil, fmt.Errorf("convert interface to Testing error: %s", err) } return t, nil } @@ -146,7 +146,7 @@ func ToTestingTask(sb map[string]interface{}) (*task.Testing, error) { func ToDistributeToS3Task(sb map[string]interface{}) (*task.DistributeToS3, error) { var t *task.DistributeToS3 if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to DistributeToS3Task error: %v", err) + return nil, fmt.Errorf("convert interface to DistributeToS3Task error: %s", err) } return t, nil } @@ -154,15 +154,23 @@ func ToDistributeToS3Task(sb map[string]interface{}) (*task.DistributeToS3, erro func ToReleaseImageTask(sb map[string]interface{}) (*task.ReleaseImage, error) { var t *task.ReleaseImage if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to ReleaseImageTask error: %v", err) + return nil, fmt.Errorf("convert interface to ReleaseImageTask error: %s", err) } return t, nil } +func ToArtifactPackageTask(sb map[string]interface{}) (*task.ArtifactPackageTaskArgs, error) { + var ret *task.ArtifactPackageTaskArgs + if err := IToi(sb, &ret); err != nil { + return nil, fmt.Errorf("convert interface to ArtifactPackageTaskArgs error: %s", err) + } + return ret, nil +} + func ToJiraTask(sb map[string]interface{}) (*task.Jira, error) { var t *task.Jira if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to JiraTask error: %v", err) + return nil, fmt.Errorf("convert interface to JiraTask error: %s", err) } return t, nil } @@ -170,7 +178,7 @@ func ToJiraTask(sb map[string]interface{}) (*task.Jira, error) { func ToSecurityTask(sb map[string]interface{}) (*task.Security, error) { var t *task.Security if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to securityTask error: %v", err) + return nil, fmt.Errorf("convert interface to securityTask error: %s", err) } return t, nil } @@ -178,7 +186,7 @@ func ToSecurityTask(sb map[string]interface{}) (*task.Security, error) { func ToJenkinsBuildTask(sb map[string]interface{}) (*task.JenkinsBuild, error) { var task *task.JenkinsBuild if err := IToi(sb, &task); err != nil { - return nil, fmt.Errorf("convert interface to JenkinsBuildTask error: %v", err) + return nil, fmt.Errorf("convert interface to JenkinsBuildTask error: %s", err) } return task, nil } @@ -186,11 +194,11 @@ func ToJenkinsBuildTask(sb map[string]interface{}) (*task.JenkinsBuild, error) { func IToi(before interface{}, after interface{}) error { b, err := json.Marshal(before) if err != nil { - return fmt.Errorf("marshal task error: %v", err) + return fmt.Errorf("marshal task error: %s", err) } if err := json.Unmarshal(b, &after); err != nil { - return fmt.Errorf("unmarshal task error: %v", err) + return fmt.Errorf("unmarshal task error: %s", err) } return nil diff --git a/pkg/microservice/warpdrive/core/service/types/packager.go b/pkg/microservice/warpdrive/core/service/types/packager.go new file mode 100644 index 00000000..19aac50a --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/packager.go @@ -0,0 +1,27 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + +type ArtifactPackagerContext struct { + JobType string `yaml:"job_type"` + ProgressFile string `yaml:"progress_file"` + Images []*task.ImagesByService `yaml:"images"` + SourceRegistries []*DockerRegistry `yaml:"source_registries"` + TargetRegistries []*DockerRegistry `yaml:"target_registries"` +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go new file mode 100644 index 00000000..9ac39d95 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +) + +type ArtifactPackage struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + Progress string `bson:"progress" json:"progress"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + LogFile string `bson:"log_file" json:"log_file"` + + // source images + Images []*ImagesByService `bson:"images" json:"images"` + // target registries to push images + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + // target registries to push images + TargetRegistries []string `bson:"target_registries" json:"target_registries"` +} + +func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(ri, &task); err != nil { + return nil, fmt.Errorf("convert ReleaseImageTask to interface error: %v", err) + } + return task, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 3b3e6e0a..fb5c696e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -129,6 +129,9 @@ type ReleaseConfig struct { // PredatorImage sets docker build image // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string + // PackagerImage sets docker build image + // e.g. xxx.com/resources/predator-plugin:v0.1.0 + PackagerImage string } type ImageReleaseConfig struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index d6a0d975..aa4ed2f1 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -64,6 +64,8 @@ type Task struct { TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + // ArtifactPackageTaskArgs arguments for artifact-package type tasks + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` // ConfigPayload 系统配置信息 ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -243,6 +245,24 @@ type ServiceTaskArgs struct { Updatable bool `bson:"updatable" json:"updatable"` } +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` +} + +type ImagesByService struct { + ServiceName string `bson:"service_name" json:"service_name"` + Images []*ImageData `bson:"images" json:"images"` +} + +type ArtifactPackageTaskArgs struct { + ProductName string `bson:"product_name" json:"product_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + TargetRegistries []string `bson:"target_registries" json:"target_registries"` +} + type ProductService struct { ServiceName string `bson:"service_name" json:"service_name"` ProductName string `bson:"product_name" json:"product_name"` diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index c8c77099..6aa3107e 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -50,6 +50,7 @@ const ( ENVReaperImage = "REAPER_IMAGE" ENVReaperBinaryFile = "REAPER_BINARY_FILE" ENVPredatorImage = "PREDATOR_IMAGE" + EnvPackagerImage = "PACKAGER_IMAGE" ENVDockerHosts = "DOCKER_HOSTS" @@ -299,6 +300,10 @@ const ( ReleaseImageJob = "docker-release" ) +const ( + BuildChartPackage = "chart-package" +) + const ( JenkinsBuildJob = "jenkins-build" ) @@ -525,6 +530,8 @@ const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" +const ProgressFile = "/var/log/job-progress" + const ( ResponseError = "error" ResponseData = "response" -- Gitee From a90f34e532ad9c31eacc9cd7a1cf9e344b3711f9 Mon Sep 17 00:00:00 2001 From: Guang Jiong Lou <7991675+27149chen@users.noreply.github.com> Date: Wed, 1 Dec 2021 14:59:39 +0800 Subject: [PATCH 0848/2647] expose local cluster (#681) * expose local cluster Signed-off-by: lou * fix list local ns Signed-off-by: lou * fix local cluster id Signed-off-by: lou * fix local cluster id Signed-off-by: lou * update after review Signed-off-by: lou --- pkg/microservice/aslan/config/consts.go | 11 ++----- .../common/repository/models/k8s_cluster.go | 32 +++++++++---------- .../common/repository/mongodb/k8s_cluster.go | 4 +-- .../aslan/core/common/service/kube/service.go | 3 +- .../core/environment/handler/environment.go | 8 +++++ .../aslan/core/environment/handler/kube.go | 6 +++- .../aslan/core/multicluster/service/bundle.go | 10 +++++- .../core/multicluster/service/clusters.go | 32 +++++++++++++------ .../aslan/core/service/handler/service.go | 4 +++ pkg/setting/consts.go | 14 ++++++++ 10 files changed, 85 insertions(+), 39 deletions(-) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index a29153a6..a028737f 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -140,13 +140,6 @@ const ( Image DistributeType = "image" ) -type K8SClusterStatus string - -const ( - Disconnected K8SClusterStatus = "disconnected" - Pending K8SClusterStatus = "pending" -) - type NotifyType int var ( @@ -210,6 +203,6 @@ const ( ) const ( - RoleBindingNameEdit = setting.ProductName+"-edit" - RoleBindingNameView = setting.ProductName+"-view" + RoleBindingNameEdit = setting.ProductName + "-edit" + RoleBindingNameView = setting.ProductName + "-view" ) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index f3f60b21..777a2a46 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -23,27 +23,27 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" ) var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - Status config.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Provider int8 `json:"provider" bson:"provider"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Provider int8 `json:"provider" bson:"provider"` } type K8SClusterInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 80e34947..f49abb0c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -221,9 +221,9 @@ func (c *K8SClusterColl) UpdateConnectState(id string, disconnected bool) error newState := bson.M{"disconnected": disconnected} if disconnected { - newState["status"] = config.Disconnected + newState["status"] = string(setting.Disconnected) } else { - newState["status"] = config.Pending + newState["status"] = string(setting.Pending) } _, err = c.UpdateMany(context.TODO(), bson.M{"_id": oid}, bson.M{"$set": newState}) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index ee405bfe..65791f9f 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -31,6 +31,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/multicluster" @@ -104,7 +105,7 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, logger *zap.SugaredL return nil, e.ErrCreateCluster.AddDesc(e.DuplicateClusterNameFound) } - cluster.Status = config.Pending + cluster.Status = setting.Pending err = s.coll.Create(cluster) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 09efcf4d..ad200efd 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -205,6 +205,10 @@ func CreateProduct(c *gin.Context) { } args.UpdateBy = ctx.UserName + // don't save the local cluster id to db + if args.ClusterID == setting.LocalClusterID { + args.ClusterID = "" + } ctx.Err = service.CreateProduct( ctx.UserName, ctx.RequestID, args, ctx.Logger, ) @@ -484,6 +488,10 @@ func ListWorkloads(c *gin.Context) { return } + if args.ClusterID == setting.LocalClusterID { + args.ClusterID = "" + } + count, services, err := commonservice.ListWorkloads("", args.ClusterID, args.Namespace, "", args.PerPage, args.Page, ctx.Logger, func(workloads []*commonservice.Workload) []*commonservice.Workload { workloadStat, _ := mongodb.NewWorkLoadsStatColl().Find(args.ClusterID, args.Namespace) workloadM := map[string]commonmodels.Workload{} diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index 1e4c0c48..691c1220 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -54,7 +54,11 @@ func ListAvailableNamespaces(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListAvailableNamespaces(c.Query("clusterId"), ctx.Logger) + clusterID := c.Query("clusterId") + if clusterID == setting.LocalClusterID { + clusterID = "" + } + ctx.Resp, ctx.Err = service.ListAvailableNamespaces(clusterID, ctx.Logger) } func ListServicePods(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/multicluster/service/bundle.go b/pkg/microservice/aslan/core/multicluster/service/bundle.go index 2285a5b6..6a5d2084 100644 --- a/pkg/microservice/aslan/core/multicluster/service/bundle.go +++ b/pkg/microservice/aslan/core/multicluster/service/bundle.go @@ -20,6 +20,7 @@ import ( "strconv" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -30,13 +31,20 @@ type resourceSpec struct { } func GetBundleResources() ([]*resourceSpec, error) { - var res []*resourceSpec clusters, err := mongodb.NewK8SClusterColl().List(nil) if err != nil { log.Errorf("Failed to list clusters in db, err: %s", err) return nil, err } + res := []*resourceSpec{ + { + ResourceID: setting.LocalClusterID, + Spec: map[string]interface{}{ + "production": "false", + }, + }, + } for _, cluster := range clusters { res = append(res, &resourceSpec{ ResourceID: cluster.ID.Hex(), diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index cfef21af..ddc196cb 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -21,34 +21,48 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" ) type K8SCluster struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Status config.K8SClusterStatus `json:"status"` - Production bool `json:"production"` - CreatedAt int64 `json:"createdAt"` - CreatedBy string `json:"createdBy"` - Provider int8 `json:"provider"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status setting.K8SClusterStatus `json:"status"` + Production bool `json:"production"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Provider int8 `json:"provider"` } func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { - cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: ids}) + idSet := sets.NewString(ids...) + localClusterIncluded := idSet.Has(setting.LocalClusterID) + idSet = idSet.Delete(setting.LocalClusterID) + cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: idSet.UnsortedList()}) if err != nil { logger.Errorf("Failed to list clusters, err: %s", err) return nil, err } var res []*K8SCluster + if len(ids) == 0 || localClusterIncluded { + res = append(res, &K8SCluster{ + ID: setting.LocalClusterID, + Name: setting.LocalClusterName, + Production: false, + Status: setting.Normal, + }) + } + for _, c := range cs { res = append(res, &K8SCluster{ ID: c.ID.Hex(), diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index cf747551..bfa9de83 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -190,6 +190,10 @@ func CreateK8sWorkloads(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid K8sWorkloadsArgs args") return } + + if args.ClusterID == setting.LocalClusterID { + args.ClusterID = "" + } ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index fd65a305..582fbe97 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -550,3 +550,17 @@ const ( IngressSubresource = "ingress" ResourcesHeader = "Resources" ) + +const ( + LocalClusterID = "local" // special(fake) id of the local cluster + LocalClusterName = "local" // special(fake) name of the local cluster +) + +type K8SClusterStatus string + +const ( + Disconnected K8SClusterStatus = "disconnected" + Pending K8SClusterStatus = "pending" + Normal K8SClusterStatus = "normal" + Abnormal K8SClusterStatus = "abnormal" +) -- Gitee From 2ea93701372e040d1b7376b668777317183024df Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 16:47:08 +0800 Subject: [PATCH 0849/2647] add cluster init Signed-off-by: liu deyi --- pkg/cli/initconfig/cmd/init.go | 28 ++++++++++++- .../common/repository/models/k8s_cluster.go | 33 ++++++++------- .../core/multicluster/service/clusters.go | 12 +----- pkg/shared/client/aslan/multicluster.go | 42 +++++++++++++++++++ 4 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 pkg/shared/client/aslan/multicluster.go diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 1d0d6793..da0a70ff 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -25,11 +25,11 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" - "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/shared/client/user" "github.com/koderover/zadig/pkg/tool/httpclient" @@ -86,9 +86,15 @@ func initSystemConfig() error { } if err := presetRoleBinding(uid); err != nil { - log.Errorf("presetRoleBinding :%s", err) + log.Errorf("presetRoleBinding err:%s", err) + return err + } + + if err := createMultiCluster(); err != nil { + log.Errorf("createMultiCluster err:%s", err) return err } + return nil } @@ -184,3 +190,21 @@ func presetRole() error { } return nil } + +func createMultiCluster() error { + clusters, err := aslan.New(config.AslanServiceAddress()).ListMultiCluster() + if err != nil { + return err + } + var hasLocal bool + for _, cluster := range clusters { + if cluster.Local { + hasLocal = true + break + } + } + if !hasLocal { + return aslan.New(config.AslanServiceAddress()).AddMultiCluster() + } + return nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index b1768569..bbc32146 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -29,22 +29,23 @@ import ( var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - Status setting.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Provider int8 `json:"provider" bson:"provider"` - Local bool `json:"local" bson:"local"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Provider int8 `json:"provider" bson:"provider"` + Local bool `json:"local" bson:"local"` } type K8SClusterInfo struct { diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index ddc196cb..49386dd4 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -41,11 +41,11 @@ type K8SCluster struct { CreatedAt int64 `json:"createdAt"` CreatedBy string `json:"createdBy"` Provider int8 `json:"provider"` + Local bool `json:"local"` } func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { idSet := sets.NewString(ids...) - localClusterIncluded := idSet.Has(setting.LocalClusterID) idSet = idSet.Delete(setting.LocalClusterID) cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: idSet.UnsortedList()}) if err != nil { @@ -54,15 +54,6 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error } var res []*K8SCluster - if len(ids) == 0 || localClusterIncluded { - res = append(res, &K8SCluster{ - ID: setting.LocalClusterID, - Name: setting.LocalClusterName, - Production: false, - Status: setting.Normal, - }) - } - for _, c := range cs { res = append(res, &K8SCluster{ ID: c.ID.Hex(), @@ -73,6 +64,7 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error CreatedBy: c.CreatedBy, CreatedAt: c.CreatedAt, Provider: c.Provider, + Local: c.Local, }) } diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go new file mode 100644 index 00000000..e74e9606 --- /dev/null +++ b/pkg/shared/client/aslan/multicluster.go @@ -0,0 +1,42 @@ +package aslan + +import ( + "fmt" + "time" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type cluster struct { + Name string `json:"name"` + Production bool `json:"production"` + Local bool `json:"local"` +} + +func (c *Client) AddMultiCluster() error { + timeStamp := time.Now().Format("20060102150405") + url := "/cluster/clusters" + req := cluster{ + Name: fmt.Sprintf("%s-%s", "local", timeStamp), + Local: true, + } + + _, err := c.Post(url, httpclient.SetBody(req)) + if err != nil { + return fmt.Errorf("Failed to add multi cluster, error: %s", err) + } + + return nil +} + +func (c *Client) ListMultiCluster() ([]*cluster, error) { + url := "/cluster/clusters" + + clusters := make([]*cluster, 0) + _, err := c.Get(url, httpclient.SetResult(clusters)) + if err != nil { + return nil, fmt.Errorf("Failed to list multi cluster, error: %s", err) + } + + return clusters, nil +} -- Gitee From 48868cc784c0c96014ce6c927bc3a32deb7ae5f5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 17:19:38 +0800 Subject: [PATCH 0850/2647] add cluster init Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index e74e9606..532a6b43 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -33,7 +33,7 @@ func (c *Client) ListMultiCluster() ([]*cluster, error) { url := "/cluster/clusters" clusters := make([]*cluster, 0) - _, err := c.Get(url, httpclient.SetResult(clusters)) + _, err := c.Get(url, httpclient.SetResult(&clusters)) if err != nil { return nil, fmt.Errorf("Failed to list multi cluster, error: %s", err) } -- Gitee From 8112c9c0d3fbd4f5c6b6e2f0c86d9795e2c4fab1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 17:28:07 +0800 Subject: [PATCH 0851/2647] modify local cluster status Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 532a6b43..ba09c491 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -4,21 +4,24 @@ import ( "fmt" "time" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/httpclient" ) type cluster struct { - Name string `json:"name"` - Production bool `json:"production"` - Local bool `json:"local"` + Name string `json:"name"` + Status setting.K8SClusterStatus `json:"status"` + Local bool `json:"local"` } func (c *Client) AddMultiCluster() error { timeStamp := time.Now().Format("20060102150405") url := "/cluster/clusters" req := cluster{ - Name: fmt.Sprintf("%s-%s", "local", timeStamp), - Local: true, + Name: fmt.Sprintf("%s-%s", "local", timeStamp), + Local: true, + Status: setting.Normal, } _, err := c.Post(url, httpclient.SetBody(req)) -- Gitee From 93f06ead02f5d938b36e4c19747bd8a923c4ac29 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 17:45:59 +0800 Subject: [PATCH 0852/2647] add to determine if clusterid is a local cluster Signed-off-by: liu deyi --- .../aslan/core/common/service/kube/service.go | 4 +++- .../aslan/core/environment/service/kube.go | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 8919f145..e13a813a 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -101,7 +101,9 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, logger *zap.SugaredL return nil, e.ErrCreateCluster.AddDesc(e.DuplicateClusterNameFound) } - cluster.Status = setting.Pending + if cluster.Status == "" { + cluster.Status = setting.Pending + } err = s.coll.Create(cluster) diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index c64c4fea..74484533 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -113,6 +113,10 @@ func ListPodEvents(envName, productName, podName string, log *zap.SugaredLogger) // ListAvailableNamespaces lists available namespaces created by non-koderover func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { resp := make([]*resource.Namespace, 0) + isLocalCluster := isLocalCluster(clusterID, log) + if isLocalCluster { + clusterID = "" + } kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListNamespaces clusterID:%s err:%v", clusterID, err) @@ -230,6 +234,10 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.NodeResp, error) { resp := new(resource.NodeResp) + isLocalCluster := isLocalCluster(clusterID, log) + if isLocalCluster { + clusterID = "" + } kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) @@ -280,3 +288,17 @@ func nodeLabel(node *corev1.Node) []string { } return labels } + +func isLocalCluster(clusterID string, log *zap.SugaredLogger) bool { + clusters, err := commonrepo.NewK8SClusterColl().List(nil) + if err != nil { + log.Errorf("list k8s cluster err:%s", err) + return false + } + for _, cluster := range clusters { + if clusterID == cluster.ID.Hex() && cluster.Local { + return true + } + } + return false +} -- Gitee From 16119ee63978c876a39d3acbeaab49500a81dec9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 1 Dec 2021 18:06:25 +0800 Subject: [PATCH 0853/2647] modify cluster status Signed-off-by: liu deyi --- pkg/microservice/hubserver/core/repository/models/models.go | 1 + pkg/microservice/hubserver/core/service/service.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/hubserver/core/repository/models/models.go b/pkg/microservice/hubserver/core/repository/models/models.go index 4bfefe01..04c47b98 100644 --- a/pkg/microservice/hubserver/core/repository/models/models.go +++ b/pkg/microservice/hubserver/core/repository/models/models.go @@ -44,6 +44,7 @@ type K8SCluster struct { CreatedBy string `json:"createdBy" bson:"createdBy"` Disconnected bool `json:"-" bson:"disconnected"` Token string `json:"token" bson:"-"` + Local bool `json:"local" bson:"local"` } func (K8SCluster) TableName() string { diff --git a/pkg/microservice/hubserver/core/service/service.go b/pkg/microservice/hubserver/core/service/service.go index 2acae6b2..9b84f2bc 100644 --- a/pkg/microservice/hubserver/core/service/service.go +++ b/pkg/microservice/hubserver/core/service/service.go @@ -235,7 +235,7 @@ func Reset() { } for _, cluster := range clusters { - if cluster.Status == config.Normal { + if cluster.Status == config.Normal && !cluster.Local { cluster.Status = config.Abnormal err := mongodb.NewK8sClusterColl().UpdateStatus(cluster) if err != nil { @@ -271,7 +271,7 @@ func Sync(server *remotedialer.Server, stopCh <-chan struct{}) { statusChanged = true } } else { - if cluster.Status == config.Normal { + if cluster.Status == config.Normal && !cluster.Local { log.Infof( "cluster %s disconnected changed %s => %s", cluster.Name, cluster.Status, config.Abnormal, -- Gitee From c7a5012fcc6a615a0d7e0d7494dfdc32b7aa58e8 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 2 Dec 2021 10:19:16 +0800 Subject: [PATCH 0854/2647] update create product with new registry Signed-off-by: panxunying --- .../core/common/repository/models/product.go | 1 + .../aslan/core/common/service/registry.go | 18 ++++++++++++------ .../core/environment/service/environment.go | 11 ++++++----- .../aslan/core/system/service/registry.go | 4 ++-- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 5bfa65c9..2b290d88 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -50,6 +50,7 @@ type Product struct { RecycleDay int `bson:"recycle_day" json:"recycle_day"` Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` + RegistryId string `bson:"registry_id" json:"registry_id"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` } diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index ecf5a1c3..74ca6fc6 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -30,11 +30,13 @@ import ( "github.com/koderover/zadig/pkg/util" ) -func FindDefaultRegistry(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { +func FindRegistryById(registryId string, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { + return FindRegisty(&mongodb.FindRegOps{ID: registryId}, log) +} + +func FindRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { // TODO: 多租户适配 - resp, err := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{ - IsDefault: true, - }) + resp, err := mongodb.NewRegistryNamespaceColl().Find(regOps) if err != nil { log.Warnf("RegistryNamespace.Find error: %v", err) @@ -58,6 +60,10 @@ func FindDefaultRegistry(log *zap.SugaredLogger) (*models.RegistryNamespace, err return resp, nil } +func FindDefaultRegistry(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { + return FindRegisty(&mongodb.FindRegOps{IsDefault: true}, log) +} + func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{IsDefault: true}) if err != nil { @@ -76,8 +82,8 @@ func ListRegistryNamespaces(log *zap.SugaredLogger) ([]*models.RegistryNamespace return resp, nil } -func EnsureDefaultRegistrySecret(namespace string, kubeClient client.Client, log *zap.SugaredLogger) error { - reg, err := FindDefaultRegistry(log) +func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient client.Client, log *zap.SugaredLogger) error { + reg, err := FindRegistryById(registryId, log) if err != nil { log.Errorf( "service.EnsureRegistrySecret: failed to find default candidate registry: %s %v", diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 71e03bfd..93596723 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -547,7 +547,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k } } - err = ensureKubeEnv(exitedProd.Namespace, kubeClient, log) + err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryId, kubeClient, log) if err != nil { log.Errorf("[%s][P:%s] service.UpdateProductV2 create kubeEnv error: %v", envName, productName, err) @@ -1844,7 +1844,8 @@ func waitResourceRunning( }) } -func preCreateProduct(envName string, args *commonmodels.Product, kubeClient client.Client, log *zap.SugaredLogger) error { +func preCreateProduct(envName string, args *commonmodels.Product, kubeClient client.Client, + log *zap.SugaredLogger) error { var ( productTemplateName = args.ProductName renderSetName = commonservice.GetProductEnvNamespace(envName, args.ProductName, args.Namespace) @@ -1925,7 +1926,7 @@ func preCreateProduct(envName string, args *commonmodels.Product, kubeClient cli args.Render = tmpRenderInfo if preCreateNSAndSecret(productTmpl.ProductFeature) { - return ensureKubeEnv(args.Namespace, kubeClient, log) + return ensureKubeEnv(args.Namespace, args.ID.String(), kubeClient, log) } return nil } @@ -2037,7 +2038,7 @@ func applySystemImagePullSecrets(podSpec *corev1.PodSpec) { }) } -func ensureKubeEnv(namespace string, kubeClient client.Client, log *zap.SugaredLogger) error { +func ensureKubeEnv(namespace string, registryId string, kubeClient client.Client, log *zap.SugaredLogger) error { err := kube.CreateNamespace(namespace, kubeClient) if err != nil { log.Errorf("[%s] get or create namespace error: %v", namespace, err) @@ -2045,7 +2046,7 @@ func ensureKubeEnv(namespace string, kubeClient client.Client, log *zap.SugaredL } // 创建默认的镜像仓库secret - if err := commonservice.EnsureDefaultRegistrySecret(namespace, kubeClient, log); err != nil { + if err := commonservice.EnsureDefaultRegistrySecret(namespace, registryId, kubeClient, log); err != nil { log.Errorf("[%s] get or create namespace error: %v", namespace, err) return e.ErrCreateSecret.AddDesc(e.CreateDefaultRegistryErrMsg) } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 0aa8288e..794e4710 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -91,7 +91,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return } - err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, kubeClient, log) + err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, args.ID.String(), kubeClient, log) if err != nil { log.Errorf("[updateRegistry] Failed to update registry secret for namespace: %s, the error is: %+v", prod.Namespace, err) } @@ -166,7 +166,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return } - err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, kubeClient, log) + err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, id, kubeClient, log) if err != nil { log.Errorf("[updateRegistry] Failed to update registry secret for namespace: %s, the error is: %+v", prod.Namespace, err) } -- Gitee From e308d97ff4a4564171d757c45ae618f4152774de Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 11:07:18 +0800 Subject: [PATCH 0855/2647] optimize k8s schedule Signed-off-by: liu deyi --- .../core/environment/handler/environment.go | 8 ---- .../aslan/core/environment/handler/kube.go | 3 -- .../aslan/core/multicluster/service/bundle.go | 10 +---- .../aslan/core/service/handler/service.go | 3 -- .../warpdrive/core/service/taskplugin/job.go | 41 ++++++++++++++++--- pkg/setting/consts.go | 5 --- 6 files changed, 36 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index ad200efd..09efcf4d 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -205,10 +205,6 @@ func CreateProduct(c *gin.Context) { } args.UpdateBy = ctx.UserName - // don't save the local cluster id to db - if args.ClusterID == setting.LocalClusterID { - args.ClusterID = "" - } ctx.Err = service.CreateProduct( ctx.UserName, ctx.RequestID, args, ctx.Logger, ) @@ -488,10 +484,6 @@ func ListWorkloads(c *gin.Context) { return } - if args.ClusterID == setting.LocalClusterID { - args.ClusterID = "" - } - count, services, err := commonservice.ListWorkloads("", args.ClusterID, args.Namespace, "", args.PerPage, args.Page, ctx.Logger, func(workloads []*commonservice.Workload) []*commonservice.Workload { workloadStat, _ := mongodb.NewWorkLoadsStatColl().Find(args.ClusterID, args.Namespace) workloadM := map[string]commonmodels.Workload{} diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index 78a3e5db..869fae79 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -55,9 +55,6 @@ func ListAvailableNamespaces(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() clusterID := c.Query("clusterId") - if clusterID == setting.LocalClusterID { - clusterID = "" - } ctx.Resp, ctx.Err = service.ListAvailableNamespaces(clusterID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/multicluster/service/bundle.go b/pkg/microservice/aslan/core/multicluster/service/bundle.go index 6a5d2084..5eb49f16 100644 --- a/pkg/microservice/aslan/core/multicluster/service/bundle.go +++ b/pkg/microservice/aslan/core/multicluster/service/bundle.go @@ -20,7 +20,6 @@ import ( "strconv" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -37,14 +36,7 @@ func GetBundleResources() ([]*resourceSpec, error) { return nil, err } - res := []*resourceSpec{ - { - ResourceID: setting.LocalClusterID, - Spec: map[string]interface{}{ - "production": "false", - }, - }, - } + var res []*resourceSpec for _, cluster := range clusters { res = append(res, &resourceSpec{ ResourceID: cluster.ID.Hex(), diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index bfa9de83..495551d7 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -191,9 +191,6 @@ func CreateK8sWorkloads(c *gin.Context) { return } - if args.ClusterID == setting.LocalClusterID { - args.ClusterID = "" - } ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 3fa456e9..7f25dfa9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -489,6 +489,10 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa job.Spec.Template.Spec.Containers[0].Args = []string{reaperBootingScript} } + if affinity := addNodeAffinity(clusterID, pipelineTask.ConfigPayload.K8SClusters); affinity != nil { + job.Spec.Template.Spec.Affinity = affinity + } + if linkedNs != "" && execNs != "" && pipelineTask.ConfigPayload.CustomDNSSupported { job.Spec.Template.Spec.DNSConfig = &corev1.PodDNSConfig{ Searches: []string{ @@ -818,13 +822,12 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A return nil } - switch clusterConfig.Strategy { - case NormalSchedule: + if len(clusterConfig.NodeLabels) == 0 { return nil + } + + switch clusterConfig.Strategy { case RequiredSchedule: - if len(clusterConfig.NodeLabels) == 0 { - return nil - } nodeSelectorTerms := make([]corev1.NodeSelectorTerm, 0) for _, nodeLabel := range clusterConfig.NodeLabels { if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { @@ -849,9 +852,35 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A } return affinity case PreferredSchedule: + preferredScheduleTerms := make([]corev1.PreferredSchedulingTerm, 0) + for _, nodeLabel := range clusterConfig.NodeLabels { + if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { + continue + } + matchExpressions := make([]corev1.NodeSelectorRequirement, 0) + matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ + Key: strings.Split(nodeLabel, ":")[0], + Operator: "in", + Values: []string{strings.Split(nodeLabel, ":")[1]}, + }) + nodeSelectorTerm := corev1.NodeSelectorTerm{ + MatchExpressions: matchExpressions, + } + preferredScheduleTerms = append(preferredScheduleTerms, corev1.PreferredSchedulingTerm{ + Weight: 10, + Preference: nodeSelectorTerm, + }) + } + affinity := &corev1.Affinity{ + NodeAffinity: &corev1.NodeAffinity{ + PreferredDuringSchedulingIgnoredDuringExecution: preferredScheduleTerms, + }, + } + return affinity + default: + return nil } - return nil } func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.ClusterConfig { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 582fbe97..d49f52a3 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -551,11 +551,6 @@ const ( ResourcesHeader = "Resources" ) -const ( - LocalClusterID = "local" // special(fake) id of the local cluster - LocalClusterName = "local" // special(fake) name of the local cluster -) - type K8SClusterStatus string const ( -- Gitee From 34f97a7e928e2e38e01a4136e1ceac180bb784a0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 14:07:10 +0800 Subject: [PATCH 0856/2647] modify cluster init Signed-off-by: liu deyi --- .../aslan/core/multicluster/service/clusters.go | 1 - pkg/setting/consts.go | 2 ++ pkg/shared/client/aslan/multicluster.go | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 49386dd4..335eed3b 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -46,7 +46,6 @@ type K8SCluster struct { func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { idSet := sets.NewString(ids...) - idSet = idSet.Delete(setting.LocalClusterID) cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: idSet.UnsortedList()}) if err != nil { logger.Errorf("Failed to list clusters, err: %s", err) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index d49f52a3..707822cc 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -559,3 +559,5 @@ const ( Normal K8SClusterStatus = "normal" Abnormal K8SClusterStatus = "abnormal" ) + +const LocalClusterID = "0123456789ABCDEF12345678" diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index ba09c491..5db4d02f 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -4,22 +4,25 @@ import ( "fmt" "time" - "github.com/koderover/zadig/pkg/setting" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type cluster struct { + ID primitive.ObjectID `json:"id,omitempty"` Name string `json:"name"` Status setting.K8SClusterStatus `json:"status"` Local bool `json:"local"` } func (c *Client) AddMultiCluster() error { - timeStamp := time.Now().Format("20060102150405") + oid, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) url := "/cluster/clusters" req := cluster{ - Name: fmt.Sprintf("%s-%s", "local", timeStamp), + ID: oid, + Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), Local: true, Status: setting.Normal, } -- Gitee From 5a35014417267f3bbbfb39163539f56965c7e12c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 14:56:25 +0800 Subject: [PATCH 0857/2647] optimize local cluster Signed-off-by: liu deyi --- .../aslan/core/environment/service/kube.go | 22 ------------------- pkg/tool/kube/multicluster/service.go | 3 ++- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index 74484533..c64c4fea 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -113,10 +113,6 @@ func ListPodEvents(envName, productName, podName string, log *zap.SugaredLogger) // ListAvailableNamespaces lists available namespaces created by non-koderover func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { resp := make([]*resource.Namespace, 0) - isLocalCluster := isLocalCluster(clusterID, log) - if isLocalCluster { - clusterID = "" - } kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListNamespaces clusterID:%s err:%v", clusterID, err) @@ -234,10 +230,6 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.NodeResp, error) { resp := new(resource.NodeResp) - isLocalCluster := isLocalCluster(clusterID, log) - if isLocalCluster { - clusterID = "" - } kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) @@ -288,17 +280,3 @@ func nodeLabel(node *corev1.Node) []string { } return labels } - -func isLocalCluster(clusterID string, log *zap.SugaredLogger) bool { - clusters, err := commonrepo.NewK8SClusterColl().List(nil) - if err != nil { - log.Errorf("list k8s cluster err:%s", err) - return false - } - for _, cluster := range clusters { - if clusterID == cluster.ID.Hex() && cluster.Local { - return true - } - } - return false -} diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index 067c8740..3e0ad316 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -28,11 +28,12 @@ import ( "k8s.io/client-go/tools/clientcmd/api" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" ) func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { - if clusterID == "" { + if clusterID == "" || clusterID == setting.LocalClusterID { return krkubeclient.Client(), nil } -- Gitee From 49a217c28e19c6829cd1bcc42064d51e6d15eb70 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 15:21:12 +0800 Subject: [PATCH 0858/2647] env add local field Signed-off-by: liu deyi --- pkg/microservice/aslan/core/environment/service/environment.go | 1 + pkg/microservice/aslan/core/environment/service/product.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 37250bd8..f9e9fc33 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -115,6 +115,7 @@ type ProductResp struct { ClusterName string `json:"cluster_name,omitempty"` RecycleDay int `json:"recycle_day"` IsProd bool `json:"is_prod"` + IsLocal bool `json:"is_local"` Source string `json:"source"` } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 88d38802..e1c19f18 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -207,6 +207,7 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar } prodResp.IsProd = cluster.Production prodResp.ClusterName = cluster.Name + prodResp.IsLocal = cluster.Local if !clusterService.ClusterConnected(prod.ClusterID) { prodResp.Status = setting.ClusterDisconnected -- Gitee From f288b0beb517428a61128269a4de76df55f13c5b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 15:25:47 +0800 Subject: [PATCH 0859/2647] modify filed Signed-off-by: liu deyi --- .../core/service/types/task/config_payload.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index d42d25ea..6339fb94 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -19,7 +19,8 @@ package task import ( "fmt" - "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" + "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -154,12 +155,12 @@ type PrivateKey struct { } type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Status config.K8SClusterStatus `json:"status" bson:"status"` - ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` - Production bool `json:"production" bson:"production"` - Disconnected bool `json:"-" bson:"disconnected"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` + Production bool `json:"production" bson:"production"` + Disconnected bool `json:"-" bson:"disconnected"` } type ClusterConfig struct { -- Gitee From 98247929f1b214c0ab118c79f0e8ce4c1f9a13d1 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 2 Dec 2021 15:30:54 +0800 Subject: [PATCH 0860/2647] rolebinding (#686) Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/read-only.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index d6bd5d64..d30a5f3e 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -10,6 +10,11 @@ rules: resources: - Environment kind: resource + - verbs: + - get_environment + resources: + - ProductionEnvironment + kind: resource - verbs: - get_build - get_service -- Gitee From 23d12508c36ca21819b394fd543c2c0d84f518ff Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 15:37:27 +0800 Subject: [PATCH 0861/2647] modify const Signed-off-by: liu deyi --- pkg/setting/consts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 707822cc..02085f39 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -560,4 +560,4 @@ const ( Abnormal K8SClusterStatus = "abnormal" ) -const LocalClusterID = "0123456789ABCDEF12345678" +const LocalClusterID = "0123456789abcdef12345678" -- Gitee From bef79054bfa04b0b933dc35cf7de15c757bb396e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 16:13:38 +0800 Subject: [PATCH 0862/2647] do not clean up the job temporarily, it is convenient for testing Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 17 ++++++++++------- .../core/service/taskplugin/testing.go | 16 +++++++++------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 581d1835..039ecc9b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -328,15 +328,18 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, // 清理用户取消和超时的任务 defer func() { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } } + return }() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 4703b559..88efe451 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -257,13 +257,15 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv // 日志保存失败与否都清理job defer func() { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } } return }() -- Gitee From abe7432f34c1f082e85866a2a85430e90f22e445 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 16:22:31 +0800 Subject: [PATCH 0863/2647] store cluster config Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/k8s_cluster.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index f49abb0c..8daf229f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -185,6 +185,7 @@ func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster) error { "tags": cluster.Tags, "namespace": cluster.Namespace, "production": cluster.Production, + "config": cluster.ClusterConfig, }}, ) -- Gitee From 950468ddbca4a8895ab57823e7288447a1cb36e6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 16:42:31 +0800 Subject: [PATCH 0864/2647] optimize code Signed-off-by: liu deyi --- .../core/multicluster/service/clusters.go | 38 ++++++++++--------- .../warpdrive/core/service/taskplugin/job.go | 4 +- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 335eed3b..8508224c 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -33,15 +33,16 @@ import ( ) type K8SCluster struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Status setting.K8SClusterStatus `json:"status"` - Production bool `json:"production"` - CreatedAt int64 `json:"createdAt"` - CreatedBy string `json:"createdBy"` - Provider int8 `json:"provider"` - Local bool `json:"local"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status setting.K8SClusterStatus `json:"status"` + Production bool `json:"production"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Provider int8 `json:"provider"` + Local bool `json:"local"` + ClusterConfig *commonmodels.ClusterConfig `json:"config,omitempty"` } func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { @@ -55,15 +56,16 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error var res []*K8SCluster for _, c := range cs { res = append(res, &K8SCluster{ - ID: c.ID.Hex(), - Name: c.Name, - Description: c.Description, - Status: c.Status, - Production: c.Production, - CreatedBy: c.CreatedBy, - CreatedAt: c.CreatedAt, - Provider: c.Provider, - Local: c.Local, + ID: c.ID.Hex(), + Name: c.Name, + Description: c.Description, + Status: c.Status, + Production: c.Production, + CreatedBy: c.CreatedBy, + CreatedAt: c.CreatedAt, + Provider: c.Provider, + Local: c.Local, + ClusterConfig: c.ClusterConfig, }) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 7f25dfa9..b6060a9f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -836,7 +836,7 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A matchExpressions := make([]corev1.NodeSelectorRequirement, 0) matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ Key: strings.Split(nodeLabel, ":")[0], - Operator: "in", + Operator: corev1.NodeSelectorOpIn, Values: []string{strings.Split(nodeLabel, ":")[1]}, }) nodeSelectorTerms = append(nodeSelectorTerms, corev1.NodeSelectorTerm{ @@ -860,7 +860,7 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A matchExpressions := make([]corev1.NodeSelectorRequirement, 0) matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ Key: strings.Split(nodeLabel, ":")[0], - Operator: "in", + Operator: corev1.NodeSelectorOpIn, Values: []string{strings.Split(nodeLabel, ":")[1]}, }) nodeSelectorTerm := corev1.NodeSelectorTerm{ -- Gitee From dbe625fc078124c02cf64b74db7dad4bfa5e43bb Mon Sep 17 00:00:00 2001 From: dehuaduan <30834299+ddh27@users.noreply.github.com> Date: Thu, 2 Dec 2021 19:37:04 +0800 Subject: [PATCH 0865/2647] Init Job service performs multiple Error pods (#671) * Init Job service performs multiple Error pods Signed-off-by: ddh27 * fix ping Signed-off-by: ddh27 * init job err Signed-off-by: ddh27 * fix init job error Signed-off-by: ddh27 * fix init job error Signed-off-by: ddh27 * rm startupProbe Signed-off-by: ddh27 * fix bug Signed-off-by: ddh27 * init job err Signed-off-by: ddh27 Co-authored-by: ddh27 --- go.mod | 1 + go.sum | 1 + pkg/cli/initconfig/cmd/healthz.go | 42 +++++++++++++++++++ pkg/cli/initconfig/cmd/init.go | 15 ++++++- .../policy/core/handler/healthz.go | 30 +++++++++++++ .../policy/core/handler/router.go | 4 ++ pkg/microservice/policy/core/service.go | 4 ++ pkg/microservice/user/core/handler/healthz.go | 30 +++++++++++++ pkg/microservice/user/core/handler/router.go | 2 + pkg/microservice/user/core/service.go | 9 ++++ pkg/shared/client/policy/policy.go | 6 +++ pkg/shared/client/user/user.go | 6 +++ 12 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/initconfig/cmd/healthz.go create mode 100644 pkg/microservice/policy/core/handler/healthz.go create mode 100644 pkg/microservice/user/core/handler/healthz.go diff --git a/go.mod b/go.mod index 3c65dad5..c00c9190 100644 --- a/go.mod +++ b/go.mod @@ -80,6 +80,7 @@ require ( helm.sh/helm/v3 v3.7.1 k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 + k8s.io/apiserver v0.22.1 // indirect k8s.io/client-go v0.22.1 k8s.io/kubectl v0.22.1 k8s.io/utils v0.0.0-20210802155522-efc7438f0176 diff --git a/go.sum b/go.sum index 7fa362e8..79c316c2 100644 --- a/go.sum +++ b/go.sum @@ -497,6 +497,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/pkg/cli/initconfig/cmd/healthz.go b/pkg/cli/initconfig/cmd/healthz.go new file mode 100644 index 00000000..7e270b8c --- /dev/null +++ b/pkg/cli/initconfig/cmd/healthz.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/shared/client/user" +) + +func Healthz() error { + if err := checkUserServiceHealth(); err != nil { + return fmt.Errorf("checkUserServiceHealth error:%s", err) + } + if err := checkPolicyServiceHealth(); err != nil { + return fmt.Errorf("checkPolicyServiceHealth error:%s", err) + } + return nil +} + +func checkUserServiceHealth() error { + return user.New().Healthz() +} + +func checkPolicyServiceHealth() error { + return policy.NewDefault().Healthz() +} diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 1d0d6793..5e5ea0f2 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -25,7 +25,6 @@ import ( "github.com/spf13/cobra" "golang.org/x/sync/errgroup" - "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/config" @@ -67,7 +66,19 @@ var initCmd = &cobra.Command{ } func run() error { - return initSystemConfig() + for { + err := Healthz() + if err == nil { + break + } + log.Error(err) + time.Sleep(10 * time.Second) + } + err := initSystemConfig() + if err == nil { + log.Info("zadig init success") + } + return err } func initSystemConfig() error { diff --git a/pkg/microservice/policy/core/handler/healthz.go b/pkg/microservice/policy/core/handler/healthz.go new file mode 100644 index 00000000..860a2e17 --- /dev/null +++ b/pkg/microservice/policy/core/handler/healthz.go @@ -0,0 +1,30 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/policy/core" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func Healthz(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = core.Healthz(c.Request.Context()) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 69da8b61..67d2fbd2 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -89,4 +89,8 @@ func (*Router) Inject(router *gin.RouterGroup) { policyDefinitions.GET("", GetPolicyRegistrationDefinitions) } + policySvrHealthz := router.Group("healthz") + { + policySvrHealthz.GET("", Healthz) + } } diff --git a/pkg/microservice/policy/core/service.go b/pkg/microservice/policy/core/service.go index b4661431..5b20babe 100644 --- a/pkg/microservice/policy/core/service.go +++ b/pkg/microservice/policy/core/service.go @@ -108,3 +108,7 @@ type indexer interface { EnsureIndex(ctx context.Context) error GetCollectionName() string } + +func Healthz(ctx context.Context) error { + return mongotool.Ping(ctx) +} diff --git a/pkg/microservice/user/core/handler/healthz.go b/pkg/microservice/user/core/handler/healthz.go new file mode 100644 index 00000000..74be7210 --- /dev/null +++ b/pkg/microservice/user/core/handler/healthz.go @@ -0,0 +1,30 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/user/core" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func Healthz(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = core.Healthz() +} diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index 7e12cc50..a764ce41 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -59,5 +59,7 @@ func (*Router) Inject(router *gin.RouterGroup) { router.GET("retrieve", user.Retrieve) router.POST("reset", user.Reset) + + router.GET("/healthz", Healthz) } } diff --git a/pkg/microservice/user/core/service.go b/pkg/microservice/user/core/service.go index 29d1af0c..869f59fe 100644 --- a/pkg/microservice/user/core/service.go +++ b/pkg/microservice/user/core/service.go @@ -83,3 +83,12 @@ func initDatabase() { func Stop(_ context.Context) { gormtool.Close() } + +func Healthz() error { + sqlDB, err := DB.DB() + if err != nil { + log.Errorf("Healthz get db error:%s", err.Error()) + return err + } + return sqlDB.Ping() +} diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 52036620..a511f330 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -135,3 +135,9 @@ type Role struct { Kind string `json:"kind"` } `json:"rules"` } + +func (c *Client) Healthz() error { + url := "/healthz" + _, err := c.Get(url) + return err +} diff --git a/pkg/shared/client/user/user.go b/pkg/shared/client/user/user.go index 436410ed..d420163f 100644 --- a/pkg/shared/client/user/user.go +++ b/pkg/shared/client/user/user.go @@ -85,3 +85,9 @@ func (c *Client) SearchUser(args *SearchUserArgs) (*SearchUserResp, error) { _, err := c.Post(url, httpclient.SetBody(args), httpclient.SetResult(resp)) return resp, err } + +func (c *Client) Healthz() error { + url := "/healthz" + _, err := c.Get(url) + return err +} -- Gitee From 9178d9a8a1fbc447c760b11c873c5db202bdac38 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 2 Dec 2021 20:11:45 +0800 Subject: [PATCH 0866/2647] optimize code Signed-off-by: liu deyi --- .../core/common/repository/models/queue.go | 1 + .../core/common/service/config_payload.go | 2 ++ .../core/service/archive/file_archive.go | 6 ++-- .../reaper/core/service/meta/types.go | 1 + .../reaper/core/service/reaper/archive.go | 6 ++-- .../reaper/core/service/reaper/artifact.go | 2 +- .../core/service/reaper/cachemanager.go | 6 ++-- .../reaper/core/service/reaper/reaper.go | 4 +-- .../reaper/core/service/reaper/script.go | 2 +- pkg/microservice/reaper/internal/s3/s3.go | 4 +-- .../warpdrive/core/service/taskplugin/job.go | 35 ++++++++++--------- .../warpdrive/core/service/types/reaper.go | 1 + .../core/service/types/task/config_payload.go | 1 + pkg/tool/crypto/aes.go | 16 +++++++-- 14 files changed, 55 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index b60f7e0f..5edfa0b9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -126,6 +126,7 @@ type ConfigPayload struct { CustomDNSSupported bool `json:"custom_dns_supported"` HubServerAddr string DeployClusterID string + AesKey string RepoConfigs map[string]*RegistryNamespace diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 1f3ed425..fb38f9ed 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/crypto" ) func GetConfigPayload(codeHostID int) *models.ConfigPayload { @@ -64,6 +65,7 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { JenkinsBuildConfig: models.JenkinsBuildConfig{ JenkinsBuildImage: config.JenkinsImage(), }, + AesKey: crypto.GetAesKey(), } githubApps, _ := mongodb.NewGithubAppColl().Find() diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 96ee051c..5857925a 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -42,12 +42,14 @@ type WorkspaceAchiever struct { PipelineName string ServiceName string Envs []string + aesKey string } -func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd string, caches, gitFolders, envs []string) *WorkspaceAchiever { +func NewWorkspaceAchiever(storageURI, pipelineName, serviceName, wd, aesKey string, caches, gitFolders, envs []string) *WorkspaceAchiever { return &WorkspaceAchiever{ paths: caches, wd: wd, + aesKey: aesKey, gitFolders: gitFolders, StorageURI: storageURI, PipelineName: pipelineName, @@ -224,7 +226,7 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { // return err //} - if store, err := s3.NewS3StorageFromEncryptedURI(c.StorageURI); err == nil { + if store, err := s3.NewS3StorageFromEncryptedURI(c.StorageURI, c.aesKey); err == nil { forcedPathStyle := true if store.Provider == setting.ProviderSourceAli { forcedPathStyle = false diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 4a587d48..86d69329 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -115,6 +115,7 @@ type Context struct { StorageBucket string `yaml:"storage_bucket"` StorageProvider int `yaml:"storage_provider"` ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` + AesKey string `yaml:"aes_key"` } type ArtifactInfo struct { diff --git a/pkg/microservice/reaper/core/service/reaper/archive.go b/pkg/microservice/reaper/core/service/reaper/archive.go index b729ce66..c2c136bf 100644 --- a/pkg/microservice/reaper/core/service/reaper/archive.go +++ b/pkg/microservice/reaper/core/service/reaper/archive.go @@ -34,7 +34,7 @@ func (r *Reaper) archiveS3Files() (err error) { if r.Ctx.FileArchiveCtx != nil && r.Ctx.StorageURI != "" { var store *s3.S3 - if store, err = s3.NewS3StorageFromEncryptedURI(r.Ctx.StorageURI); err != nil { + if store, err = s3.NewS3StorageFromEncryptedURI(r.Ctx.StorageURI, r.Ctx.AesKey); err != nil { log.Errorf("failed to create s3 storage %s", r.Ctx.StorageURI) return } @@ -77,7 +77,7 @@ func (r *Reaper) archiveTestFiles() error { return nil } - store, err := s3.NewS3StorageFromEncryptedURI(r.Ctx.StorageURI) + store, err := s3.NewS3StorageFromEncryptedURI(r.Ctx.StorageURI, r.Ctx.AesKey) if err != nil { log.Errorf("failed to create s3 storage %s, err: %s", r.Ctx.StorageURI, err) return err @@ -130,7 +130,7 @@ func (r *Reaper) archiveHTMLTestReportFile() error { return nil } - store, err := s3.NewS3StorageFromEncryptedURI(r.Ctx.StorageURI) + store, err := s3.NewS3StorageFromEncryptedURI(r.Ctx.StorageURI, r.Ctx.AesKey) if err != nil { log.Errorf("failed to create s3 storage %s, err: %s", r.Ctx.StorageURI, err) return err diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index ff93b4ff..09af436c 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -38,7 +38,7 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string) error { ) if ctx.StorageURI != "" { - if store, err = s3.NewS3StorageFromEncryptedURI(ctx.StorageURI); err != nil { + if store, err = s3.NewS3StorageFromEncryptedURI(ctx.StorageURI, ctx.AesKey); err != nil { log.Errorf("artifactsUpload failed to create s3 storage err:%v", err) return err } diff --git a/pkg/microservice/reaper/core/service/reaper/cachemanager.go b/pkg/microservice/reaper/core/service/reaper/cachemanager.go index ae4eb537..a4eba4d9 100644 --- a/pkg/microservice/reaper/core/service/reaper/cachemanager.go +++ b/pkg/microservice/reaper/core/service/reaper/cachemanager.go @@ -80,13 +80,15 @@ type TarCacheManager struct { StorageURI string PipelineName string ServiceName string + aesKey string } -func NewTarCacheManager(storageURI, pipelineName, serviceName string) *TarCacheManager { +func NewTarCacheManager(storageURI, pipelineName, serviceName, aesKey string) *TarCacheManager { return &TarCacheManager{ StorageURI: storageURI, PipelineName: pipelineName, ServiceName: serviceName, + aesKey: aesKey, } } @@ -180,7 +182,7 @@ func (gcm *TarCacheManager) Unarchive(source, dest string) error { func (gcm *TarCacheManager) getS3Storage() (*s3.S3, error) { var err error var store *s3.S3 - if store, err = s3.NewS3StorageFromEncryptedURI(gcm.StorageURI); err != nil { + if store, err = s3.NewS3StorageFromEncryptedURI(gcm.StorageURI, gcm.aesKey); err != nil { log.Errorf("Archive failed to create s3 storage %s", gcm.StorageURI) return nil, err } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 136d0d39..d85d4a67 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -74,7 +74,7 @@ func NewReaper() (*Reaper, error) { reaper := &Reaper{ Ctx: ctx, - cm: NewTarCacheManager(ctx.StorageURI, ctx.PipelineName, ctx.ServiceName), + cm: NewTarCacheManager(ctx.StorageURI, ctx.PipelineName, ctx.ServiceName, ctx.AesKey), } return reaper, nil @@ -85,7 +85,7 @@ func (r *Reaper) GetCacheFile() string { } func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) ([]string, error) { - fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, caches, []string{}, r.getUserEnvs()) + fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, r.Ctx.AesKey, caches, []string{}, r.getUserEnvs()) // list files matches caches return fileAchiever.Achieve(dest) diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 47cbf240..3521273d 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -377,7 +377,7 @@ func (r *Reaper) RunPMDeployScripts() error { func (r *Reaper) downloadArtifactFile() error { var err error var store *s3.S3 - if store, err = s3.NewS3StorageFromEncryptedURI(r.Ctx.ArtifactInfo.URL); err != nil { + if store, err = s3.NewS3StorageFromEncryptedURI(r.Ctx.ArtifactInfo.URL, r.Ctx.AesKey); err != nil { log.Errorf("Archive failed to create s3 storage %s", r.Ctx.ArtifactInfo.URL) return err } diff --git a/pkg/microservice/reaper/internal/s3/s3.go b/pkg/microservice/reaper/internal/s3/s3.go index 3c15cc4b..ed87e9c7 100644 --- a/pkg/microservice/reaper/internal/s3/s3.go +++ b/pkg/microservice/reaper/internal/s3/s3.go @@ -75,8 +75,8 @@ func NewS3StorageFromURL(uri string) (*S3, error) { return ret, nil } -func NewS3StorageFromEncryptedURI(encryptedURI string) (*S3, error) { - uri, err := crypto.AesDecrypt(encryptedURI) +func NewS3StorageFromEncryptedURI(encryptedURI, aesKey string) (*S3, error) { + uri, err := crypto.AesDecrypt(encryptedURI, aesKey) if err != nil { return nil, err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index b6060a9f..cdf6f435 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -211,6 +211,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName TaskID: pipelineTask.TaskID, ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, + AesKey: pipelineTask.ConfigPayload.AesKey, } for _, install := range b.Installs { inst := &types.Install{ @@ -597,11 +598,11 @@ func getVolumeMounts(ctx *task.PipelineCtx) []corev1.VolumeMount { Name: "job-config", MountPath: ctx.ConfigMapMountDir, }) - resp = append(resp, corev1.VolumeMount{ - Name: "aes-key", - ReadOnly: true, - MountPath: "/etc/encryption", - }) + //resp = append(resp, corev1.VolumeMount{ + // Name: "aes-key", + // ReadOnly: true, + // MountPath: "/etc/encryption", + //}) return resp } @@ -618,18 +619,18 @@ func getVolumes(jobName string) []corev1.Volume { }, }, }) - resp = append(resp, corev1.Volume{ - Name: "aes-key", - VolumeSource: corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: "zadig-aes-key", - Items: []corev1.KeyToPath{{ - Key: "aesKey", - Path: "aes", - }}, - }, - }, - }) + //resp = append(resp, corev1.Volume{ + // Name: "aes-key", + // VolumeSource: corev1.VolumeSource{ + // Secret: &corev1.SecretVolumeSource{ + // SecretName: "zadig-aes-key", + // Items: []corev1.KeyToPath{{ + // Key: "aesKey", + // Path: "aes", + // }}, + // }, + // }, + //}) return resp } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 9ed4b2fd..c3fa2fa7 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -113,6 +113,7 @@ type Context struct { StorageBucket string `yaml:"storage_bucket"` StorageProvider int8 `yaml:"storage_provider"` ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` + AesKey string `yaml:"aes_key"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 6339fb94..313af2d7 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -44,6 +44,7 @@ type ConfigPayload struct { CustomDNSSupported bool `json:"custom_dns_supported"` HubServerAddr string DeployClusterID string + AesKey string RepoConfigs map[string]*RegistryNamespace diff --git a/pkg/tool/crypto/aes.go b/pkg/tool/crypto/aes.go index 65e69ce0..9889f609 100644 --- a/pkg/tool/crypto/aes.go +++ b/pkg/tool/crypto/aes.go @@ -51,6 +51,10 @@ func getAESKey() string { return aesKey } +func GetAesKey() string { + return getAESKey() +} + func AesEncrypt(src string) (string, error) { client, err := NewAes(getAESKey()) if err != nil { @@ -63,8 +67,16 @@ func AesEncrypt(src string) (string, error) { return dest, nil } -func AesDecrypt(src string) (string, error) { - client, err := NewAes(getAESKey()) +func AesDecrypt(src string, aesKey ...string) (string, error) { + var ( + err error + client *Aes + ) + if len(aesKey) > 0 { + client, err = NewAes(aesKey[0]) + } else { + client, err = NewAes(getAESKey()) + } if err != nil { return "", err } -- Gitee From 2cc37b99d22aa6866c65f61f4977045d46243fc0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 2 Dec 2021 20:22:10 +0800 Subject: [PATCH 0867/2647] fix workflow reset_image not working bug Signed-off-by: allenshen --- .../service/workflow/pipeline_validation.go | 10 ++++++++++ .../workflow/service/workflow/workflow_task.go | 16 ++++++++++++++++ .../warpdrive/core/service/taskplugin/deploy.go | 12 ++++++------ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 58099249..c158467c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -733,6 +733,16 @@ func validateServiceContainer2(namespace, envName, productName, serviceName, con } for _, p := range pods { + // filter by serviceName because same container name may exist in multi-services + if source == setting.SourceFromHelm { + annotation := p.Annotations + if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { + if util.ExtraServiceName(chartRelease, namespace) != serviceName { + continue + } + } + } + pod := wrapper.Pod(p).Resource() for _, c := range pod.ContainerStatuses { if c.Name == container || strings.Contains(c.Image, container) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 45f27398..06232049 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1049,6 +1049,22 @@ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.P deployTask.ServiceName = envList[0] deployTask.ContainerName = envList[1] return deployTask.ToSubTask() + case setting.HelmDeployType: + deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} + deployTask.Namespace = prodEnv.Namespace + deployTask.ProductName = prodEnv.ProductName + deployTask.SkipWaiting = true + deployTask.EnvName = prodEnv.EnvName + envList := strings.Split(env.Env, "/") + if len(envList) != 2 { + err := fmt.Errorf("[%s]split target env error", env.Env) + log.Error(err) + return nil, err + } + deployTask.ServiceName = envList[0] + deployTask.ContainerName = envList[1] + deployTask.ServiceType = setting.HelmDeployType + return deployTask.ToSubTask() default: return nil, nil } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 6632789b..34b72aca 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -366,15 +366,15 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ) rcsList := make([]ResourceComponentSet, 0) - deployments, err := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) - if err != nil { - p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) + deployments, errFindDeploy := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) + if errFindDeploy != nil { + p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, errFindDeploy) } else { rcsList = append(rcsList, RcsListFromDeployments(deployments)...) } - statefulSets, _ := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) - if err != nil { - p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) + statefulSets, errFindSts := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) + if errFindSts != nil { + p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, errFindSts) } else { rcsList = append(rcsList, RcsListFromStatefulSets(statefulSets)...) } -- Gitee From 6fdbe9b2f0f22e73a934197f76ac2c90931ba5c8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 2 Dec 2021 20:35:32 +0800 Subject: [PATCH 0868/2647] optimize code Signed-off-by: allenshen --- .../service/workflow/pipeline_validation.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index c158467c..1646b33c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -733,13 +733,16 @@ func validateServiceContainer2(namespace, envName, productName, serviceName, con } for _, p := range pods { - // filter by serviceName because same container name may exist in multi-services + // need to filter by serviceName because same container name may exist in multi services if source == setting.SourceFromHelm { annotation := p.Annotations - if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { - if util.ExtraServiceName(chartRelease, namespace) != serviceName { - continue - } + if len(annotation) == 0 { + continue + } + if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; !ok { + continue + } else if util.ExtraServiceName(chartRelease, namespace) != serviceName { + continue } } -- Gitee From cc85b5d34c7c264891ecec10529e952c68ac52d1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 2 Dec 2021 22:57:47 +0800 Subject: [PATCH 0869/2647] add more detail to progress data Signed-off-by: allenshen --- pkg/microservice/packager/core/service/service.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 1f85dc7e..043ac8c6 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -42,10 +42,11 @@ type Packager struct { Ctx *Context } -type ServicePackageResult struct { - ServiceName string `json:"service_name"` - Result string `json:"result"` - ErrorMsg string `json:"error_msg"` +type PackageResult struct { + ServiceName string `json:"service_name"` + Result string `json:"result"` + ErrorMsg string `json:"error_msg"` + ImageData []*ImageData `json:"image_data"` } func NewPackager() (*Packager, error) { @@ -105,7 +106,7 @@ func (p *Packager) Exec() error { return err } - realTimeProgress := make([]*ServicePackageResult, 0) + realTimeProgress := make([]*PackageResult, 0) for _, image := range p.Ctx.Images { cmds := p.dockerCommands(image) @@ -119,7 +120,7 @@ func (p *Packager) Exec() error { } } - result := &ServicePackageResult{ + result := &PackageResult{ ServiceName: image.ServiceName, } @@ -129,6 +130,7 @@ func (p *Packager) Exec() error { log.Infof("[result][fail][%s][%s]", image.ServiceName, err) } else { result.Result = "success" + result.ImageData = image.Images log.Infof("[result][success][%s]", image.ServiceName) } -- Gitee From 49699164b90d20f9574840b6e5019e47aaec72db Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 5 Dec 2021 13:29:00 +0800 Subject: [PATCH 0870/2647] make build delivery version work Signed-off-by: allenshen --- .../models/task/artifact_package.go | 26 +- .../repository/mongodb/delivery_distribute.go | 7 +- .../aslan/core/delivery/service/version.go | 431 +++++++++++++----- .../handler/{helm_release.go => helm.go} | 12 +- .../aslan/core/environment/handler/router.go | 3 + .../aslan/core/workflow/handler/workflow.go | 2 +- .../service/workflow/artifact_task.go | 21 +- .../service/workflow/workflow_task.go | 2 +- .../packager/core/service/service.go | 2 + .../service/taskplugin/artifact_package.go | 2 +- .../core/service/taskplugin/deploy.go | 12 +- 11 files changed, 386 insertions(+), 134 deletions(-) rename pkg/microservice/aslan/core/environment/handler/{helm_release.go => helm.go} (80%) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go index 8f578b62..0686b461 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go @@ -17,12 +17,27 @@ limitations under the License. package task import ( + "encoding/json" "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageNamespace string `bson:"image_namespace" json:"image_namespace"` + ImageTag string `bson:"image_tag" json:"image_tag"` +} + +type ServicePackageResult struct { + ServiceName string `json:"service_name"` + Result string `json:"result"` + ErrorMsg string `json:"error_msg"` + ImageData []*ImageData `json:"image_data"` +} + type ArtifactPackage struct { TaskType config.TaskType `bson:"type" json:"type"` TaskStatus config.Status `bson:"status" json:"status"` @@ -32,7 +47,7 @@ type ArtifactPackage struct { StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` - + Progress string `bson:"progress" json:"progress"` // source images Images []*models.ImagesByService `bson:"images" json:"images"` // source registries need auth to pull images @@ -48,3 +63,12 @@ func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { } return task, nil } + +func (ri *ArtifactPackage) GetProgress() ([]*ServicePackageResult, error) { + if len(ri.Progress) == 0 { + return nil, nil + } + ret := make([]*ServicePackageResult, 0) + err := json.Unmarshal([]byte(ri.Progress), &ret) + return ret, err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go index 21ef1dd5..98a9c39e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go @@ -35,6 +35,7 @@ type DeliveryDistributeArgs struct { ID string `json:"id"` ReleaseID string `json:"releaseId"` ServiceName string `json:"serviceName"` + ChartName string `json:"chartName"` DistributeType config.DistributeType `json:"distributeType"` } @@ -62,6 +63,7 @@ func (c *DeliveryDistributeColl) EnsureIndex(ctx context.Context) error { Keys: bson.D{ bson.E{Key: "release_id", Value: 1}, bson.E{Key: "service_name", Value: 1}, + bson.E{Key: "chart_name", Value: 1}, bson.E{Key: "deleted_at", Value: 1}, }, Options: options.Index().SetUnique(false), @@ -115,9 +117,12 @@ func (c *DeliveryDistributeColl) Find(args *DeliveryDistributeArgs) ([]*models.D } query := bson.M{"release_id": releaseID, "deleted_at": 0} - if args.DistributeType != "" { + if len(args.DistributeType) > 0 { query["distribute_type"] = string(args.DistributeType) } + if len(args.ChartName) > 0 { + query["chart_name"] = args.ChartName + } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 1dcdf020..90cd83e4 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -29,6 +29,8 @@ import ( "sync" "time" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" @@ -43,7 +45,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -51,7 +52,6 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -164,6 +164,19 @@ type DeliverySecurityStats struct { DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` } +type ImageUrlDetail struct { + ImageUrl string + Name string + Registry string + Tag string +} + +type ServiceImageDetails struct { + ServiceName string + Images []*ImageUrlDetail + Registries []string +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { @@ -486,13 +499,12 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { } // pull image from currently using registry and push to specified repo -func pushImages(deliveryVersion *commonmodels.DeliveryVersion, serviceObj *commonmodels.Service, valuesMap map[string]interface{}, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { - - log.Infof("######## start pushing image #########") +func extractImages(deliveryVersion *commonmodels.DeliveryVersion, serviceObj *commonmodels.Service, valuesMap map[string]interface{}, + targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { flatMap, err := converter.Flatten(valuesMap) if err != nil { - return err + return nil, err } imagePathSpecs := make([]map[string]string, 0) @@ -507,54 +519,77 @@ func pushImages(deliveryVersion *commonmodels.DeliveryVersion, serviceObj *commo } // find full image uris from values.yaml - imageUris := make([]string, 0) + imageUrls := make([]string, 0) for _, spec := range imagePathSpecs { imageUri, err := commonservice.GeneImageURI(spec, flatMap) if err != nil { - return err + return nil, err } - imageUris = append(imageUris, imageUri) + + imageUrls = append(imageUrls, imageUri) + } + + ret := &ServiceImageDetails{ + ServiceName: serviceObj.ServiceName, + Images: make([]*ImageUrlDetail, 0), } - for _, imageUri := range imageUris { - log.Infof("##### handle single image uri %s ######", imageUri) - registryUrl, err := commonservice.ExtractImageRegistry(imageUri) + registrySet := sets.NewString() + + for _, imageUrl := range imageUrls { + + registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) if err != nil { - return errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUri) + return nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) } registryUrl = strings.TrimSuffix(registryUrl, "/") - log.Infof("#### extract registry url %s ####", registryUrl) - imageName := commonservice.ExtractImageName(imageUri) - imageTag := commonservice.ExtractImageTag(imageUri) - imageNamespace := commonservice.ExtractRegistryNamespace(imageUri) - targetFullImageUri := imageUri + imageName := commonservice.ExtractImageName(imageUrl) + imageTag := commonservice.ExtractImageTag(imageUrl) + // used source registry if registry, ok := registryMap[registryUrl]; ok { - if registry.ID != targetRegistry.ID { - targetFullImageUri = fmt.Sprintf("%s/%s/%s:%s", targetRegistry.RegAddr, targetRegistry.Namespace, imageName, imageTag) - } + registrySet.Insert(registry.ID.Hex()) } - // add image distribution data - err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ - ReleaseID: deliveryVersion.ID, - ServiceName: imageName, - ChartName: serviceObj.ServiceName, - DistributeType: config.Image, - RegistryName: targetFullImageUri, - Namespace: imageNamespace, - CreatedAt: time.Now().Unix(), + ret.Images = append(ret.Images, &ImageUrlDetail{ + ImageUrl: imageUrl, + Name: imageName, + Tag: imageTag, }) - if err != nil { - log.Errorf("failed to insert image distribute data, version: %s, image: %s, err: %s", deliveryVersion.Version, targetFullImageUri, err) - return fmt.Errorf("failed to insert image distribute data") - } } - return nil + + ret.Registries = registrySet.List() + return ret, nil + + //for _, imageUrl := range imageUrls { + // registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) + // if err != nil { + // return nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) + // } + // registryUrl = strings.TrimSuffix(registryUrl, "/") + // + // imageName := commonservice.ExtractImageName(imageUrl) + // imageTag := commonservice.ExtractImageTag(imageUrl) + // imageNamespace := commonservice.ExtractRegistryNamespace(imageUrl) + // targetFullImageUri := imageUrl + // + // if registry, ok := registryMap[registryUrl]; ok { + // if registry.ID != targetRegistry.ID { + // targetFullImageUri = fmt.Sprintf("%s/%s/%s:%s", targetRegistry.RegAddr, targetRegistry.Namespace, imageName, imageTag) + // } + // } + // + // if err != nil { + // log.Errorf("failed to insert image distribute data, version: %s, image: %s, err: %s", deliveryVersion.Version, targetFullImageUri, err) + // return nil, fmt.Errorf("failed to insert image distribute data") + // } + //} + //return nil, nil } -func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) error { +func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, + targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { serviceObj := chartData.ServiceObj serviceName, revision := serviceObj.ServiceName, serviceObj.Revision basePath := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) @@ -564,7 +599,7 @@ func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData basePath = config.LocalServicePath(serviceObj.ProductName, serviceName) if err = commonservice.PreLoadServiceManifests(basePath, serviceObj); err != nil { log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) - return err + return nil, err } } @@ -573,13 +608,13 @@ func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) err := copy.Copy(fullPath, deliveryChartPath) if err != nil { - return nil + return nil, err } //load chart info from local storage chartRequested, err := chartloader.Load(deliveryChartPath) if err != nil { - return errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) + return nil, errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) } //set version and values.yaml content @@ -591,46 +626,46 @@ func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData valuesInfo := make(map[string]interface{}) if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) - return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) } chartRequested.Values = valuesInfo } else if len(globalVariables) > 0 { // merge global variables curValuesStr, err := yaml.Marshal(chartRequested.Values) if err != nil { - return errors.Wrapf(err, "failed to marshal values.yaml for service: %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "failed to marshal values.yaml for service: %s", serviceObj.ServiceName) } chartRequested.Values, err = yamlutil.MergeAndUnmarshal([][]byte{curValuesStr, []byte(globalVariables)}) if err != nil { - return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) } } chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) if err != nil { - return err + return nil, err } - // push docker image - err = pushImages(deliveryVersion, serviceObj, chartRequested.Values, targetRegistry, registryMap) + // parse docker image + imagesData, err := extractImages(deliveryVersion, serviceObj, chartRequested.Values, targetRegistry, registryMap) if err != nil { - return errors.Wrapf(err, "failed to handle image") + return nil, errors.Wrapf(err, "failed to handle image") } client, err := createChartRepoClient(chartRepo) if err != nil { - return errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) + return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) } log.Infof("pushing %s to %s...\n", filepath.Base(chartPackagePath), chartRepo.URL) resp, err := client.UploadChartPackage(chartPackagePath, false) if err != nil { - return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + return nil, errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) } err = handlePushResponse(resp) if err != nil { - return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) + return nil, errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) } - return nil + return imagesData, nil } func handlePushResponse(resp *http.Response) error { @@ -727,14 +762,75 @@ func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { return ret, nil } +func buildArtifactTaskArgs(projectName, envName string, imagesMap *sync.Map) *commonmodels.ArtifactPackageTaskArgs { + imageArgs := make([]*commonmodels.ImagesByService, 0) + sourceRegistry := sets.NewString() + imagesMap.Range(func(key, value interface{}) bool { + imageDetail := value.(*ServiceImageDetails) + imagesByService := &commonmodels.ImagesByService{ + ServiceName: imageDetail.ServiceName, + Images: make([]*commonmodels.ImageData, 0), + } + for _, image := range imageDetail.Images { + imagesByService.Images = append(imagesByService.Images, &commonmodels.ImageData{ + ImageUrl: image.ImageUrl, + ImageName: image.Name, + ImageTag: image.Tag, + }) + } + imageArgs = append(imageArgs, imagesByService) + sourceRegistry.Insert(imageDetail.Registries...) + return true + }) + + ret := &commonmodels.ArtifactPackageTaskArgs{ + ProjectName: projectName, + EnvName: envName, + Images: imageArgs, + SourceRegistries: sourceRegistry.List(), + } + return ret +} + +// insert delivery distribution data for single chart, include image and chart +func insertDeliveryDistributions(result *task.ServicePackageResult, chartVersion string, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData) error { + for _, image := range result.ImageData { + err := commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + ServiceName: image.ImageName, // image name + ChartName: result.ServiceName, + DistributeType: config.Image, + RegistryName: image.ImageUrl, + Namespace: commonservice.ExtractRegistryNamespace(image.ImageUrl), + CreatedAt: time.Now().Unix(), + }) + if err != nil { + log.Errorf("failed to insert image distribute data, chartName: %s, err: %s", result.ServiceName, err) + return fmt.Errorf("failed to insert image distribute data, chartName: %s", result.ServiceName) + } + } + + err := commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + DistributeType: config.Chart, + ChartName: result.ServiceName, + ChartVersion: chartVersion, + ChartRepoName: args.ChartRepoName, + SubDistributes: nil, + CreatedAt: time.Now().Unix(), + }) + if err != nil { + log.Errorf("failed to insert chart distribute data, chartName: %s, err: %s", result.ServiceName, err) + return fmt.Errorf("failed to insert chart distribute data, chartName: %s", result.ServiceName) + } + return nil +} + func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData, logger *zap.SugaredLogger) (err error) { defer func() { if err != nil { deliveryVersion.Status = setting.DeliveryVersionStatusFailed deliveryVersion.Error = err.Error() - } else { - deliveryVersion.Status = setting.DeliveryVersionStatusSuccess - deliveryVersion.Error = "" } err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) if err != nil { @@ -774,10 +870,7 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return fmt.Errorf("failed to build registry map") } - //err = runDockerCommand(dockerInfo()) - //if err != nil { - // return fmt.Errorf("failed to run docker deamon") - //} + imagesDataMap := &sync.Map{} // push charts to repo wg := sync.WaitGroup{} @@ -785,25 +878,14 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - err := handleSingleChart(deliveryVersion, cData, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) + imagesData, err := handleSingleChart(deliveryVersion, cData, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) if err != nil { logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) return } - err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ - ReleaseID: deliveryVersion.ID, - DistributeType: config.Chart, - ChartName: cData.ChartData.ServiceName, - ChartVersion: cData.ChartData.Version, - ChartRepoName: args.ChartRepoName, - SubDistributes: nil, - CreatedAt: time.Now().Unix(), - }) - if err != nil { - logger.Errorf("failed to insert delivery distribute chart, serviceName: %s, err: %s", cData.ChartData.ServiceName, err) - appendError(err) - } + imagesDataMap.Store(cData.ServiceObj.ServiceName, imagesData) + }(chartData) } wg.Wait() @@ -813,34 +895,174 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return } + // create task to deal with images + // offline docker images are not supported + taskArgs := buildArtifactTaskArgs(deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, imagesDataMap) + taskArgs.TargetRegistries = []string{args.ImageRegistryID} + taskID, err := workflowservice.CreateArtifactPackageTask(taskArgs, deliveryVersion.Version, logger) + if err != nil { + return err + } + deliveryVersion.TaskID = int(taskID) + + // start a new routine to check task results + go waitVersionDone(deliveryVersion) + // no need to upload chart packages - if args.Options == nil || !args.Options.EnableOfflineDist { - return + //if args.Options == nil || !args.Options.EnableOfflineDist { + // return + //} + // + ////tar all chart files and image offline packages, send to s3 store + //fsTree := os.DirFS(dir) + //s3Base := configbase.ObjectStorageDeliveryVersionPath(deliveryVersion.ProductName) + //if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, []string{deliveryVersion.Version}, s3Base, args.Options.S3StorageID, logger); err != nil { + // logger.Errorf("failed to upload chart package files for project %s, err: %s", deliveryVersion.ProductName, err) + // err = errors.Wrapf(err, "failed to upload package file") + // return + //} + + //err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + // ReleaseID: deliveryVersion.ID, + // DistributeType: config.File, + // PackageFile: fmt.Sprintf("%s.tar.gz", deliveryVersion.Version), + // //RemoteFileKey: filepath.Join(s3Base, fmt.Sprintf("%s.tar.gz", deliveryVersion.Version)), + // S3StorageID: args.Options.S3StorageID, + // CreatedAt: time.Now().Unix(), + //}) + //if err != nil { + // logger.Errorf("failed to insert file distribute data, version: %s, err: %s", deliveryVersion.Version, err) + // return fmt.Errorf("failed to insert file distribute data") + //} + + return +} + +func updateVersionStatus(versionName, status, errStr string) { + err := commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionName, status, errStr) + if err != nil { + log.Errorf("failed to update version status, name: %s, err: %s", versionName, err) } +} - //tar all chart files and image offline packages, send to s3 store - fsTree := os.DirFS(dir) - s3Base := configbase.ObjectStorageDeliveryVersionPath(deliveryVersion.ProductName) - if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, []string{deliveryVersion.Version}, s3Base, args.Options.S3StorageID, logger); err != nil { - logger.Errorf("failed to upload chart package files for project %s, err: %s", deliveryVersion.ProductName, err) - err = errors.Wrapf(err, "failed to upload package file") - return +func taskFinished(status config.Status) bool { + return status == config.StatusPassed || status == config.StatusFailed || status == config.StatusTimeout || status == config.StatusCancelled +} + +func waitVersionDone(deliveryVersion *commonmodels.DeliveryVersion) { + waitTimeout := time.After(10 * time.Minute) + for { + select { + case <-waitTimeout: + updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, "timeout") + return + default: + done, err := checkVersionStatus(deliveryVersion) + if err != nil { + updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, err.Error()) + return + } + if done { + return + } + } + time.Sleep(time.Second * 5) } +} - err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ - ReleaseID: deliveryVersion.ID, - DistributeType: config.File, - PackageFile: fmt.Sprintf("%s.tar.gz", deliveryVersion.Version), - RemoteFileKey: filepath.Join(s3Base, fmt.Sprintf("%s.tar.gz", deliveryVersion.Version)), - S3StorageID: args.Options.S3StorageID, - CreatedAt: time.Now().Unix(), +func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, error) { + if deliveryVersion.Status == setting.DeliveryVersionStatusSuccess || deliveryVersion.Status == setting.DeliveryVersionStatusFailed { + return true, nil + } + pipelineName := fmt.Sprintf("%s-%s-%s", deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, "artifact") + taskData, err := commonrepo.NewTaskColl().Find(int64(deliveryVersion.TaskID), pipelineName, config.ArtifactType) + if err != nil { + return false, fmt.Errorf("failed to query taskData, id: %d, pipelineName: %s", deliveryVersion.TaskID) + } + + if len(taskData.Stages) != 1 { + return false, fmt.Errorf("invalid task data, stage length not leagal") + } + + argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) + if err != nil { + return false, errors.Wrapf(err, "failed to marshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) + } + createArgs := new(DeliveryVersionChartData) + err = json.Unmarshal(argsBytes, createArgs) + if err != nil { + return false, errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) + } + + distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ + DistributeType: config.Chart, + ReleaseID: deliveryVersion.ID.Hex(), }) if err != nil { - logger.Errorf("failed to insert file distribute data, version: %s, err: %s", deliveryVersion.Version, err) - return fmt.Errorf("failed to insert file distribute data") + return false, errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) } - return + // for charts has been successfully handled, download charts directly + successCharts := sets.NewString() + for _, distribute := range distributes { + successCharts.Insert(distribute.ChartName) + } + + stage := taskData.Stages[0] + errorList := &multierror.Error{} + + allTaskDone := true + for _, taskData := range stage.SubTasks { + artifactPackageArgs, err := base.ToArtifactPackageImageTask(taskData) + if err != nil { + return false, errors.Wrapf(err, "failed to generate origin artifact task data") + } + + progressData, err := artifactPackageArgs.GetProgress() + if err != nil { + return false, errors.Wrapf(err, "failed to get progress data from data") + } + progressDataMap := make(map[string]*task.ServicePackageResult) + for _, singleResult := range progressData { + progressDataMap[singleResult.ServiceName] = singleResult + } + + for _, chartData := range createArgs.ChartDatas { + // service artifact has been marked as success + if successCharts.Has(chartData.ServiceName) { + continue + } + if singleResult, ok := progressDataMap[chartData.ServiceName]; ok { + if singleResult.Result != "success" { + errorList = multierror.Append(errorList, fmt.Errorf("failed to build image distribute for service:%s ", singleResult.ServiceName)) + continue + } + err = insertDeliveryDistributions(singleResult, chartData.Version, deliveryVersion, createArgs) + if err != nil { + errorList = multierror.Append(errorList, fmt.Errorf("failed to insert distribute data for service:%s ", singleResult.ServiceName)) + continue + } + successCharts.Insert(chartData.ServiceName) + } + } + + if !taskFinished(artifactPackageArgs.TaskStatus) { + allTaskDone = false + } + } + + if allTaskDone { + if successCharts.Len() == len(createArgs.ChartDatas) { + deliveryVersion.Status = setting.DeliveryVersionStatusSuccess + } else { + deliveryVersion.Status = setting.DeliveryVersionStatusFailed + } + } + if errorList.ErrorOrNil() != nil { + deliveryVersion.Error = errorList.Error() + } + updateVersionStatus(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) + return allTaskDone, nil } func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { @@ -885,15 +1107,15 @@ func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *z DeletedAt: 0, } - err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) + err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) if err != nil { - logger.Errorf("failed to insert version data, err: %s", err) - return e.ErrCreateDeliveryVersion.AddErr(fmt.Errorf("failed to insert delivery version: %s", versionObj.Version)) + return err } - err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) + err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) if err != nil { - return err + logger.Errorf("failed to insert version data, err: %s", err) + return e.ErrCreateDeliveryVersion.AddErr(fmt.Errorf("failed to insert delivery version: %s", versionObj.Version)) } return nil @@ -925,10 +1147,9 @@ func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap productInfoSnap := deliveryVersion.ProductEnvInfo - //deploys := make - distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ - ReleaseID: deliveryVersion.ID.Hex(), + DistributeType: config.Chart, + ReleaseID: deliveryVersion.ID.Hex(), }) if err != nil { return errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) @@ -1064,20 +1285,16 @@ func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *com func getChartDistributeInfo(releaseID, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { distributes, _ := FindDeliveryDistribute(&commonrepo.DeliveryDistributeArgs{ ReleaseID: releaseID, - ServiceName: chartName, + ChartName: chartName, DistributeType: config.Chart, }, log) - var chartInfo *commonmodels.DeliveryDistribute - for _, distribute := range distributes { - if distribute.ChartName == chartName { - chartInfo = distribute - } + if len(distributes) != 1 { + log.Warnf("find chart %s failed, expect count %d, found count %d, release_id: %s", chartName, 1, len(distributes), releaseID) + return nil, fmt.Errorf("can't find target charts") } - if chartInfo == nil { - return nil, fmt.Errorf("can't find target chart: %s", chartName) - } + chartInfo := distributes[0] return chartInfo, nil } diff --git a/pkg/microservice/aslan/core/environment/handler/helm_release.go b/pkg/microservice/aslan/core/environment/handler/helm.go similarity index 80% rename from pkg/microservice/aslan/core/environment/handler/helm_release.go rename to pkg/microservice/aslan/core/environment/handler/helm.go index d93e7a5e..9e097cd9 100644 --- a/pkg/microservice/aslan/core/environment/handler/helm_release.go +++ b/pkg/microservice/aslan/core/environment/handler/helm.go @@ -25,16 +25,16 @@ import ( func ListReleases(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") - productName := c.Param("productName") - ctx.Resp, ctx.Err = service.ListReleases(productName, envName, ctx.Logger) + envName := c.Param("name") + projectName := c.Query("projectName") + ctx.Resp, ctx.Err = service.ListReleases(projectName, envName, ctx.Logger) } func GetChartInfos(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") + envName := c.Param("name") servicesName := c.Query("serviceName") - productName := c.Param("productName") - ctx.Resp, ctx.Err = service.GetChartInfos(productName, envName, servicesName, ctx.Logger) + projectName := c.Query("projectName") + ctx.Resp, ctx.Err = service.GetChartInfos(projectName, envName, servicesName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 03d649bc..fcca8191 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -107,6 +107,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/groups", ListGroups) environments.GET("/:name/workloads", ListWorkloadsInEnv) + environments.GET("/:name/helm/releases", ListReleases) + environments.GET("/:name/helm/charts", GetChartInfos) + environments.GET("/:name/services/:serviceName", GetService) environments.PUT("/:name/services/:serviceName", gin2.UpdateOperationLogStatus, UpdateService) environments.POST("/:name/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index d33f1a58..3e4eca7b 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -109,7 +109,7 @@ func CreateArtifactTask(c *gin.Context) { return } - ctx.Err = workflow.CreateArtifactPackageTask(args, ctx.Logger) + _, ctx.Err = workflow.CreateArtifactPackageTask(args, ctx.UserName, ctx.Logger) } // UpdateWorkflow update a workflow diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index 15c7b59f..abc79582 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -35,7 +35,7 @@ import ( "go.uber.org/zap" ) -func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log *zap.SugaredLogger) error { +func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskCreator string, log *zap.SugaredLogger) (int64, error) { // 获取全局configpayload configPayload := commonservice.GetConfigPayload(0) @@ -43,7 +43,7 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * if err != nil { log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) - return fmt.Errorf("failed to query registries") + return 0, fmt.Errorf("failed to query registries") } registriesInvolved := sets.NewString() @@ -68,13 +68,13 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * defaultS3, err := s3.FindDefaultS3() if err != nil { err = e.ErrFindDefaultS3Storage.AddDesc("default storage is required by distribute task") - return err + return 0, err } defaultURL, err := defaultS3.GetEncryptedURL() if err != nil { err = e.ErrS3Storage.AddErr(err) - return err + return 0, err } task := &task.Task{ @@ -82,6 +82,7 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * ProductName: args.ProjectName, Status: config.StatusCreated, ArtifactPackageTaskArgs: args, + TaskCreator: taskCreator, ConfigPayload: configPayload, StorageURI: defaultURL, } @@ -100,14 +101,14 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * }).ToSubTask() if err != nil { - return err + return 0, err } task.SubTasks = []map[string]interface{}{subTask} if err := ensurePipelineTask(task, "", log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) - return err + return 0, err } stages := make([]*commonmodels.Stage, 0) @@ -117,14 +118,14 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * sort.Sort(ByStageKind(stages)) task.Stages = stages if len(task.Stages) == 0 { - return e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + return 0, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) } pipelineName := fmt.Sprintf("%s-%s-%s", args.ProjectName, args.EnvName, "artifact") nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.ServiceTaskFmt, pipelineName)) if err != nil { log.Errorf("CreateServiceTask Counter.GetNextSeq error: %v", err) - return e.ErrGetCounter.AddDesc(err.Error()) + return 0, e.ErrGetCounter.AddDesc(err.Error()) } task.SubTasks = []map[string]interface{}{} @@ -133,8 +134,8 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * if err := CreateTask(task); err != nil { log.Error(err) - return e.ErrCreateTask + return 0, e.ErrCreateTask } - return nil + return nextTaskID, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index deb423c1..896f3d0a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1034,7 +1034,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.Product) (map[string]interface{}, error) { switch env.Type { - case setting.K8SDeployType: + case setting.K8SDeployType, setting.HelmDeployType: deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} deployTask.Namespace = prodEnv.Namespace deployTask.ProductName = prodEnv.ProductName diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 043ac8c6..17d2ba3c 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -101,6 +101,7 @@ func (p *Packager) Exec() error { allRegistries := make([]*DockerRegistry, 0) allRegistries = append(allRegistries, p.Ctx.SourceRegistries...) allRegistries = append(allRegistries, p.Ctx.TargetRegistries...) + log.Infof("all registry count %v", len(allRegistries)) err := writeDockerConfig(allRegistries) if err != nil { return err @@ -209,6 +210,7 @@ func (p *Packager) dockerCommands(imageByService *ImagesByService) []*exec.Cmd { targetImage := buildTargetImage(image.ImageName, image.ImageTag, registry.Host, registry.Namespace) cmds = append(cmds, dockerTag(image.ImageUrl, targetImage)) cmds = append(cmds, dockerPush(targetImage)) + image.ImageUrl = targetImage } } } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go index 2436728d..693403be 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -174,7 +174,7 @@ func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task. } p.Log.Infof("succeed to create cm for artifact package job %s", p.JobName) - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create release artifact package job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 6632789b..3a69965e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -366,15 +366,15 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ) rcsList := make([]ResourceComponentSet, 0) - deployments, err := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) - if err != nil { - p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) + deployments, errListDeploy := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) + if errListDeploy != nil { + p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, errListDeploy) } else { rcsList = append(rcsList, RcsListFromDeployments(deployments)...) } - statefulSets, _ := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) - if err != nil { - p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, err) + statefulSets, errListSts := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) + if errListSts != nil { + p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, errListSts) } else { rcsList = append(rcsList, RcsListFromStatefulSets(statefulSets)...) } -- Gitee From af1f0ef55be7c6b9fe227008834bec6583fe46b2 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 6 Dec 2021 15:01:56 +0800 Subject: [PATCH 0871/2647] fix compile error Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index a2bd6c88..4ce8e4c4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1034,7 +1034,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.Product) (map[string]interface{}, error) { switch env.Type { - case setting.K8SDeployType, setting.HelmDeployType: + case setting.K8SDeployType: deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} deployTask.Namespace = prodEnv.Namespace deployTask.ProductName = prodEnv.ProductName -- Gitee From 5c08446f042dfbbfd68c24cc018d811a7d08c23a Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 6 Dec 2021 15:37:36 +0800 Subject: [PATCH 0872/2647] update create workflow task image with registryID Signed-off-by: panxunying --- .../core/workflow/service/workflow/workflow_task.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 45f27398..946d7954 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1778,13 +1778,20 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) } + opt := &commonrepo.ProductFindOptions{Namespace: envName} + exitedProd, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + log.Errorf("can't find product by namespace:%s error msg: %v", envName, err) + return e.ErrFindRegistry.AddDesc(err.Error()) + } + // 生成默认镜像tag后缀 //pt.TaskArgs.Deploy.Tag = releaseCandidate(t, pt.TaskID, pt.ProductName, pt.EnvName, "image") // 设置镜像名称 // 编译任务使用 t.JobCtx.Image // 注意: 其他任务从 pt.TaskArgs.Deploy.Image 获取, 必须要有编译任务 - reg, err := commonservice.FindDefaultRegistry(log) + reg, err := commonservice.FindRegistryById(exitedProd.RegistryId, log) if err != nil { log.Errorf("can't find default candidate registry: %v", err) return e.ErrFindRegistry.AddDesc(err.Error()) -- Gitee From 9c4ee32aeba7e052da20198e8efda7737f60fed2 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 6 Dec 2021 15:39:19 +0800 Subject: [PATCH 0873/2647] add GetRegistryNamespace and UpdateProductRegistry Signed-off-by: panxunying --- .../core/common/repository/mongodb/product.go | 18 +++++- .../aslan/core/common/service/product.go | 15 +++++ .../aslan/core/common/service/registry.go | 9 +++ .../core/environment/handler/environment.go | 60 +++++++++++++++---- .../aslan/core/environment/handler/router.go | 1 + .../core/environment/service/environment.go | 48 ++++++++++++--- .../service/environment_creator.go | 2 + .../aslan/core/system/handler/registry.go | 18 ++++++ .../aslan/core/system/handler/router.go | 1 + 9 files changed, 152 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index ca490b5b..023ba43f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -33,8 +33,9 @@ import ( ) type ProductFindOptions struct { - Name string - EnvName string + Name string + EnvName string + Namespace string } // ClusterId is a primitive.ObjectID{}.Hex() @@ -130,6 +131,9 @@ func (c *ProductColl) Find(opt *ProductFindOptions) (*models.Product, error) { if opt.EnvName != "" { query["env_name"] = opt.EnvName } + if opt.Namespace != "" { + query["namespace"] = opt.Namespace + } err := c.FindOne(context.TODO(), query).Decode(res) return res, err @@ -241,6 +245,16 @@ func (c *ProductColl) UpdateErrors(owner, productName, errorMsg string) error { return err } +func (c *ProductColl) UpdateRegistry(namespace, registryId string) error { + query := bson.M{"namespace": namespace} + change := bson.M{"$set": bson.M{ + "registry_id": registryId, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + + return err +} + func (c *ProductColl) UpdateRender(envName, productName string, render *models.RenderInfo) error { query := bson.M{"env_name": envName, "product_name": productName} change := bson.M{"$set": bson.M{ diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 9986f357..8c72170d 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -352,3 +352,18 @@ func GetProductEnvNamespace(envName, productName, namespace string) string { } return product.Namespace } + +func GetProductEnv(envName, productName, namespace string) *models.Product { + if namespace != "" { + return nil + } + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + product = &commonmodels.Product{EnvName: envName, ProductName: productName} + return product + } + return product +} diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 74ca6fc6..08a963f1 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -73,6 +73,15 @@ func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamesp return resp, nil } +func GetRegistryNamespace(registryID string, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { + resp, err := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{ID: registryID}) + if err != nil { + log.Errorf("get default registry error: %v", err) + return resp, fmt.Errorf("get default registry error: %v", err) + } + return resp, nil +} + func ListRegistryNamespaces(log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index ad200efd..6a1f5119 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -130,7 +130,11 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - + registryID := c.Query("registryID") + if registryID == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("registryID can not be empty") + return + } createArgs := make([]*service.CreateHelmProductArg, 0) data, err := c.GetRawData() if err != nil { @@ -156,7 +160,7 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( - projectName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, + projectName, ctx.UserName, ctx.RequestID, registryID, createArgs, ctx.Logger, ) } @@ -164,15 +168,6 @@ func CreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - if c.Query("auto") == "true" { - ctx.Resp = service.AutoCreateProduct(c.Query("projectName"), c.Query("envType"), ctx.RequestID, ctx.Logger) - return - } - if c.Query("helm") == "true" { - createHelmProduct(c, ctx) - return - } - args := new(commonmodels.Product) data, err := c.GetRawData() if err != nil { @@ -182,6 +177,15 @@ func CreateProduct(c *gin.Context) { log.Errorf("CreateProduct json.Unmarshal err : %v", err) } + if c.Query("auto") == "true" { + ctx.Resp = service.AutoCreateProduct(c.Query("projectName"), c.Query("envType"), ctx.RequestID, ctx.Logger) + return + } + if c.Query("helm") == "true" { + createHelmProduct(c, ctx) + return + } + allowedClusters, found := internalhandler.GetResourcesInHeader(c) if found { allowedSet := sets.NewString(allowedClusters...) @@ -204,6 +208,11 @@ func CreateProduct(c *gin.Context) { return } + if args.RegistryId == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("RegistryId can not be null!") + return + } + args.UpdateBy = ctx.UserName // don't save the local cluster id to db if args.ClusterID == setting.LocalClusterID { @@ -247,6 +256,35 @@ func UpdateProduct(c *gin.Context) { } } +func UpdateProductRegistry(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + namespace := c.Param("namespace") + projectName := c.Query("projectName") + args := new(commonmodels.Product) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateProduct c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("UpdateProduct json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", namespace, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + ctx.Err = service.UpdateProductRegistry(namespace, args.RegistryId, ctx.Logger) + if ctx.Err != nil { + ctx.Logger.Errorf("failed to update product %s %s: %v", namespace, args.RegistryId, ctx.Err) + } +} + func UpdateProductRecycleDay(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index f5bb3dd9..2dcb66cb 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -95,6 +95,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { environments.GET("", ListProducts) environments.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProduct) + environments.PUT("/:namespace/registry", gin2.UpdateOperationLogStatus, UpdateProductRegistry) environments.PUT("", gin2.UpdateOperationLogStatus, UpdateMultiProducts) environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) environments.GET("/:name", GetProduct) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 93596723..b5e4b8dd 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -522,6 +522,31 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return nil } +func UpdateProductRegistry(namespace, registryID string, log *zap.SugaredLogger) (err error) { + opt := &commonrepo.ProductFindOptions{Namespace: namespace} + exitedProd, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + log.Errorf("UpdateProductRegistry find product by namespace:%s,error: %v", namespace, err) + return e.ErrUpdateEnv.AddDesc(e.EnvNotFoundErrMsg) + } + err = commonrepo.NewProductColl().UpdateRegistry(namespace, registryID) + if err != nil { + log.Errorf("UpdateProductRegistry UpdateRegistry by namespace:%s registryID:%s error: %v", namespace, registryID, err) + return e.ErrUpdateEnv.AddErr(err) + } + kubeClient, err := kube.GetKubeClient(exitedProd.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryId, kubeClient, log) + + if err != nil { + log.Errorf("UpdateProductRegistry ensureKubeEnv by namespace:%s,error: %v", namespace, err) + return err + } + return nil +} + func UpdateProductV2(envName, productName, user, requestID string, force bool, kvs []*template.RenderKV, log *zap.SugaredLogger) (err error) { // 根据产品名称和产品创建者到数据库中查找已有产品记录 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} @@ -641,7 +666,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return nil } -func CreateHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { +func CreateHelmProduct(productName, userName, requestID, registryID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { templateProduct, err := templaterepo.NewProductColl().Find(productName) if err != nil || templateProduct == nil { if err != nil { @@ -698,7 +723,7 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe errList := new(multierror.Error) for _, arg := range args { - err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, arg, log) + err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, registryID, arg, log) if err != nil { errList = multierror.Append(errList, err) } @@ -706,7 +731,7 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe return errList.ErrorOrNil() } -func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][]*commonmodels.ProductService, requestID, userName string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { +func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][]*commonmodels.ProductService, requestID, userName, registryID string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { productObj := &commonmodels.Product{ ProductName: templateProduct.ProductName, Revision: 1, @@ -721,6 +746,7 @@ func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][ IsOpenSource: templateProduct.IsOpensource, ChartInfos: templateProduct.ChartInfos, IsForkedProduct: false, + RegistryId: registryID, } customChartValueMap := make(map[string]*commonservice.RenderChartArg) @@ -927,9 +953,8 @@ func checkOverrideValuesChange(source *template.RenderChart, args *commonservice } func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - - opt := &commonrepo.RenderSetFindOption{Name: renderSetName} + product := commonservice.GetProductEnv(envName, productName, "") + opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { @@ -969,7 +994,16 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string updatedRcList = append(updatedRcList, updatedRc) } - return UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcList, productRenderset, log) + err = UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcList, productRenderset, log) + if err != nil { + return err + } + kubeClient, err := kube.GetKubeClient(product.ClusterID) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) + return err + } + return ensureKubeEnv(product.Namespace, product.RegistryId, kubeClient, log) } func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index f8c5a8d6..0abe50d1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -41,6 +41,7 @@ type CreateProductParam struct { ProductName string EnvType string log *zap.SugaredLogger + RegistryID string } type AutoCreator struct { @@ -84,6 +85,7 @@ func (autoCreator *AutoCreator) Create(envName string) (string, error) { productObject.Namespace = commonservice.GetProductEnvNamespace(envName, productName, "") productObject.UpdateBy = autoCreator.Param.UserName productObject.EnvName = envName + productObject.RegistryId = autoCreator.Param.RegistryID if autoCreator.Param.EnvType == setting.HelmDeployType { productObject.Source = setting.SourceFromHelm } diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 7a419d0c..14c0cd37 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -60,6 +60,24 @@ func GetDefaultRegistryNamespace(c *gin.Context) { } } +func GetRegistryNamespace(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + reg, err := commonservice.GetRegistryNamespace(c.Param("id"), ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + ctx.Resp = &Registry{ + ID: reg.ID.Hex(), + RegAddr: reg.RegAddr, + IsDefault: reg.IsDefault, + Namespace: reg.Namespace, + } +} + func ListRegistryNamespaces(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 9de790a6..bf356fe7 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -61,6 +61,7 @@ func (*Router) Inject(router *gin.RouterGroup) { registry.GET("", ListRegistries) // 获取默认的镜像仓库配置,用于kodespace CLI调用 registry.GET("/namespaces/default", GetDefaultRegistryNamespace) + registry.GET("/namespaces/:id", GetRegistryNamespace) registry.GET("/namespaces", ListRegistryNamespaces) registry.POST("/namespaces", gin2.UpdateOperationLogStatus, CreateRegistryNamespace) registry.PUT("/namespaces/:id", gin2.UpdateOperationLogStatus, UpdateRegistryNamespace) -- Gitee From c2ea20d5bee2a556cd6a665d92b30bcfbb141477 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 15:40:22 +0800 Subject: [PATCH 0874/2647] trigger add headers Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/trigger.go | 2 ++ pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 3 +++ pkg/microservice/warpdrive/core/service/types/task/trigger.go | 2 ++ 3 files changed, 7 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go index 8528a103..8ee73797 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) type Trigger struct { @@ -15,6 +16,7 @@ type Trigger struct { IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index caa8924d..b423cf8e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -145,6 +145,9 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi TaskEnvs: pipelineTask.TaskArgs.BuildArgs, } body, err = json.Marshal(webhookPayload) + for _, header := range p.Task.Headers { + httpclient.SetHeader(header.Key, header.Value) + } _, err = httpClient.Post(url, httpclient.SetHeader("X-Zadig-Event", "Workflow"), httpclient.SetBody(body)) if err != nil { return diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 18461fd6..da67fada 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -3,6 +3,7 @@ package task import ( "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" ) @@ -15,6 +16,7 @@ type Trigger struct { IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` CallbackType string `bson:"callback_type,omitempty" json:"callback_type,omitempty"` CallbackPayload *CallbackPayload `bson:"callback_payload,omitempty" json:"callback_payload,omitempty"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` -- Gitee From f88556afa8e8400039b54a4ea245bb7b1cc94d01 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 16:52:02 +0800 Subject: [PATCH 0875/2647] optimize schdule code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/job.go | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index cdf6f435..b34a3ce2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -829,21 +829,28 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A switch clusterConfig.Strategy { case RequiredSchedule: - nodeSelectorTerms := make([]corev1.NodeSelectorTerm, 0) + nodeLabelM := make(map[string][]string, 0) for _, nodeLabel := range clusterConfig.NodeLabels { if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { continue } - matchExpressions := make([]corev1.NodeSelectorRequirement, 0) + key := strings.Split(nodeLabel, ":")[0] + value := strings.Split(nodeLabel, ":")[1] + nodeLabelM[key] = append(nodeLabelM[key], value) + } + nodeSelectorTerms := make([]corev1.NodeSelectorTerm, 0) + for key, value := range nodeLabelM { + var matchExpressions []corev1.NodeSelectorRequirement matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ - Key: strings.Split(nodeLabel, ":")[0], + Key: key, Operator: corev1.NodeSelectorOpIn, - Values: []string{strings.Split(nodeLabel, ":")[1]}, + Values: value, }) nodeSelectorTerms = append(nodeSelectorTerms, corev1.NodeSelectorTerm{ MatchExpressions: matchExpressions, }) } + affinity := &corev1.Affinity{ NodeAffinity: &corev1.NodeAffinity{ RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ @@ -853,16 +860,22 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A } return affinity case PreferredSchedule: - preferredScheduleTerms := make([]corev1.PreferredSchedulingTerm, 0) + nodeLabelM := make(map[string][]string, 0) for _, nodeLabel := range clusterConfig.NodeLabels { if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { continue } - matchExpressions := make([]corev1.NodeSelectorRequirement, 0) + key := strings.Split(nodeLabel, ":")[0] + value := strings.Split(nodeLabel, ":")[1] + nodeLabelM[key] = append(nodeLabelM[key], value) + } + preferredScheduleTerms := make([]corev1.PreferredSchedulingTerm, 0) + for key, value := range nodeLabelM { + var matchExpressions []corev1.NodeSelectorRequirement matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ - Key: strings.Split(nodeLabel, ":")[0], + Key: key, Operator: corev1.NodeSelectorOpIn, - Values: []string{strings.Split(nodeLabel, ":")[1]}, + Values: value, }) nodeSelectorTerm := corev1.NodeSelectorTerm{ MatchExpressions: matchExpressions, @@ -872,7 +885,6 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A Preference: nodeSelectorTerm, }) } - affinity := &corev1.Affinity{ NodeAffinity: &corev1.NodeAffinity{ PreferredDuringSchedulingIgnoredDuringExecution: preferredScheduleTerms, -- Gitee From fbf80b9f38f82a3eb158e7298c3e4eb805e73a5d Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 6 Dec 2021 18:33:22 +0800 Subject: [PATCH 0876/2647] fix service duplicate bug (#692) * fix service duplicate bug Signed-off-by: allenshen * fix import issue Signed-off-by: allenshen * change default service sequence list index Signed-off-by: allenshen * update service seq validate logic Signed-off-by: allenshen --- .../repository/mongodb/template/product.go | 11 +++++-- .../aslan/core/project/service/product.go | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index c5d23227..4cf141c2 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -270,10 +270,17 @@ type ProductArgs struct { func (c *ProductColl) AddService(productName, serviceName string) error { query := bson.M{"product_name": productName} + serviceUniqueFilter := bson.M{ + "$elemMatch": bson.M{ + "$elemMatch": bson.M{ + "$eq": serviceName, + }, + }, + } + query["services"] = bson.M{"$not": serviceUniqueFilter} change := bson.M{"$addToSet": bson.M{ - "services.0": serviceName, + "services.1": serviceName, }} - _, err := c.UpdateOne(context.TODO(), query, change) return err } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 29010f33..836b2975 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -130,6 +130,36 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err } func UpdateServiceOrchestration(name string, services [][]string, updateBy string, log *zap.SugaredLogger) (err error) { + templateProductInfo, err := templaterepo.NewProductColl().Find(name) + if err != nil { + log.Errorf("failed to query productInfo, projectName: %s, err: %s", name, err) + return fmt.Errorf("failed to query productInfo, projectName: %s", name) + } + + //validate services + validServices := sets.NewString() + usedServiceSet := sets.NewString() + for _, serviceList := range templateProductInfo.Services { + validServices.Insert(serviceList...) + } + + for _, serviceSeq := range services { + for _, service := range serviceSeq { + if usedServiceSet.Has(service) { + return fmt.Errorf("duplicated service:%s", service) + } + if !validServices.Has(service) { + return fmt.Errorf("service:%s not in valid service list", service) + } + usedServiceSet.Insert(service) + validServices.Delete(service) + } + } + + if validServices.Len() > 0 { + return fmt.Errorf("service: [%s] not found in params", strings.Join(validServices.List(), ",")) + } + if err = templaterepo.NewProductColl().UpdateServiceOrchestration(name, services, updateBy); err != nil { log.Errorf("UpdateChoreographyService error: %v", err) return e.ErrUpdateProduct.AddErr(err) -- Gitee From 7cdd11f384f293837b86854f88a8157f1dcd40ed Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 6 Dec 2021 18:55:55 +0800 Subject: [PATCH 0877/2647] registry data compatibility Signed-off-by: panxunying --- .../aslan/core/common/service/registry.go | 27 ++++++++++++++----- .../aslan/core/environment/handler/router.go | 2 +- .../service/workflow/workflow_task.go | 18 ++++++++++--- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 08a963f1..4c0323aa 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -92,13 +92,26 @@ func ListRegistryNamespaces(log *zap.SugaredLogger) ([]*models.RegistryNamespace } func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient client.Client, log *zap.SugaredLogger) error { - reg, err := FindRegistryById(registryId, log) - if err != nil { - log.Errorf( - "service.EnsureRegistrySecret: failed to find default candidate registry: %s %v", - namespace, err, - ) - return err + var reg *models.RegistryNamespace + var err error + if len(registryId) > 0 { + reg, err = FindRegistryById(registryId, log) + if err != nil { + log.Errorf( + "service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", + registryId, err, + ) + return err + } + } else { + reg, err = FindDefaultRegistry(log) + if err != nil { + log.Errorf( + "service.EnsureRegistrySecret: failed to find default candidate registry: %s %v", + namespace, err, + ) + return err + } } err = kube.CreateOrUpdateRegistrySecret(namespace, reg, kubeClient) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 2dcb66cb..3b0cd996 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -95,7 +95,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { environments.GET("", ListProducts) environments.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProduct) - environments.PUT("/:namespace/registry", gin2.UpdateOperationLogStatus, UpdateProductRegistry) + environments.PUT("/registry", gin2.UpdateOperationLogStatus, UpdateProductRegistry) environments.PUT("", gin2.UpdateOperationLogStatus, UpdateMultiProducts) environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) environments.GET("/:name", GetProduct) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 946d7954..28bd0f74 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1791,10 +1791,20 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // 设置镜像名称 // 编译任务使用 t.JobCtx.Image // 注意: 其他任务从 pt.TaskArgs.Deploy.Image 获取, 必须要有编译任务 - reg, err := commonservice.FindRegistryById(exitedProd.RegistryId, log) - if err != nil { - log.Errorf("can't find default candidate registry: %v", err) - return e.ErrFindRegistry.AddDesc(err.Error()) + var reg *commonmodels.RegistryNamespace + if len(exitedProd.RegistryId) > 0 { + reg, err = commonservice.FindRegistryById(exitedProd.RegistryId, log) + if err != nil { + log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", + exitedProd.RegistryId, err) + return e.ErrFindRegistry.AddDesc(err.Error()) + } + } else { + reg, err = commonservice.FindDefaultRegistry(log) + if err != nil { + log.Errorf("can't find default candidate registry: %v", err) + return e.ErrFindRegistry.AddDesc(err.Error()) + } } t.JobCtx.Image = GetImage(reg, releaseCandidate(t, pt.TaskID, pt.ProductName, envName, "image")) -- Gitee From 6d5871b7626baa8e01a18e36f04d3a04a42e45cd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 18:59:36 +0800 Subject: [PATCH 0878/2647] optimize sse interface Signed-off-by: liu deyi --- .../aslan/core/log/service/sse.go | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index eb7872dd..5811f743 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -32,9 +32,9 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/watcher" ) @@ -53,6 +53,7 @@ type GetContainerOptions struct { TestName string EnvName string ProductName string + ClusterID string } func ContainerLogStream(ctx context.Context, streamChan chan interface{}, envName, productName, podName, containerName string, follow bool, tailLines int64, log *zap.SugaredLogger) { @@ -127,6 +128,20 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op options.TaskID = taskObj.TaskID } } + } else if options.ProductName != "" { + build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ + ProductName: options.ProductName, + Targets: []string{options.ServiceName}, + }) + if err != nil { + // Maybe this service is a shared service + build, err = commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ + Targets: []string{options.ServiceName}, + }) + } + if build != nil && build.PreBuild != nil { + options.ClusterID = build.PreBuild.ClusterID + } } if options.SubTask == "" { @@ -137,9 +152,14 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op } func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, options *GetContainerOptions, log *zap.SugaredLogger) { - options.SubTask = string(config.TaskTestingV2) selector := getPipelineSelector(options) + // get cluster ID + testName := strings.Replace(options.ServiceName, "-job", "", 1) + testing, _ := commonrepo.NewTestingColl().Find(testName, "") + if testing != nil && testing.PreTest != nil { + options.ClusterID = testing.PreTest.ClusterID + } waitAndGetLog(ctx, streamChan, selector, options, log) } @@ -149,12 +169,22 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la defer cancel() log.Debugf("Waiting until pod is running before establishing the stream.") - err := watcher.WaitUntilPodRunning(PodCtx, options.Namespace, selector, krkubeclient.Clientset()) + clientSet, err := multicluster.GetClientset(config.HubServerAddress(), options.ClusterID) + if err != nil { + log.Errorf("GetContainerLogs, get client set error: %s", err) + return + } + err = watcher.WaitUntilPodRunning(PodCtx, options.Namespace, selector, clientSet) + if err != nil { + log.Errorf("GetContainerLogs, wait pod running error: %s", err) + return + } + kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), options.ClusterID) if err != nil { - log.Errorf("GetContainerLogs, wait pod running error: %+v", err) + log.Errorf("GetContainerLogs, get kube client error: %s", err) return } - pods, err := getter.ListPods(options.Namespace, selector, krkubeclient.Client()) + pods, err := getter.ListPods(options.Namespace, selector, kubeClient) if err != nil { log.Errorf("GetContainerLogs, get pod error: %+v", err) return @@ -169,7 +199,7 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la pods[0].Name, options.SubTask, true, options.TailLines, - krkubeclient.Clientset(), + clientSet, log, ) } -- Gitee From f0198bea7b1a8ff70d04e21f9634cc017577a419 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 19:11:40 +0800 Subject: [PATCH 0879/2647] optimize sse interface Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 5811f743..7ae80d0c 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -141,6 +141,7 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op } if build != nil && build.PreBuild != nil { options.ClusterID = build.PreBuild.ClusterID + options.Namespace = build.PreBuild.Namespace } } @@ -159,6 +160,7 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, testing, _ := commonrepo.NewTestingColl().Find(testName, "") if testing != nil && testing.PreTest != nil { options.ClusterID = testing.PreTest.ClusterID + options.Namespace = testing.PreTest.Namespace } waitAndGetLog(ctx, streamChan, selector, options, log) -- Gitee From e85161bda2bea5ec6e22d220be6bbbc12829d160 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 6 Dec 2021 19:48:16 +0800 Subject: [PATCH 0880/2647] get env response add registryID Signed-off-by: panxunying --- .../aslan/core/environment/service/environment.go | 1 + .../aslan/core/environment/service/product.go | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index b5e4b8dd..8cf70dea 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -115,6 +115,7 @@ type ProductResp struct { RecycleDay int `json:"recycle_day"` IsProd bool `json:"is_prod"` Source string `json:"source"` + RegisterID string `json:"register_id"` } type ProductParams struct { diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 88d38802..44f63776 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -169,6 +169,14 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( } } + if len(prod.RegistryId) == 0 { + reg, err := commonservice.FindDefaultRegistry(log) + if err != nil { + log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %v", username, envName, productName, err) + return nil, err + } + prod.RegistryId = reg.ID.String() + } resp := buildProductResp(prod.EnvName, prod, log) return resp, nil } @@ -190,6 +198,7 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar ClusterID: prod.ClusterID, RecycleDay: prod.RecycleDay, Source: prod.Source, + RegisterID: prod.RegistryId, } if prod.ClusterID != "" { -- Gitee From 7c06ff2623536f6d42e444289ec25603a8e9434f Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 6 Dec 2021 20:02:29 +0800 Subject: [PATCH 0881/2647] list env response add registryID Signed-off-by: panxunying --- .../aslan/core/environment/service/environment.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 8cf70dea..86db090c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -95,6 +95,7 @@ type EnvResp struct { ClusterName string `json:"clusterName"` Production bool `json:"production"` Source string `json:"source"` + RegistryID string `json:"registry_id"` } type ProductResp struct { @@ -181,11 +182,19 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) } var res []*EnvResp + reg, err := commonservice.FindDefaultRegistry(log) + if err != nil { + log.Errorf("FindDefaultRegistry error: %v", err) + return nil, err + } for _, env := range envs { clusterID := env.ClusterID production := false clusterName := "" cluster, ok := clusterMap[clusterID] + if len(env.RegistryId) == 0 { + env.RegistryId = reg.ID.String() + } if ok { production = cluster.Production clusterName = cluster.Name @@ -202,6 +211,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) Error: env.Error, UpdateTime: env.UpdateTime, UpdateBy: env.UpdateBy, + RegistryID: env.RegistryId, }) } -- Gitee From ed44a68aab95e8b0889cd26e424a76eee4248969 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 20:52:37 +0800 Subject: [PATCH 0882/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/workflow/workflow_task.go | 1 - .../core/service/taskplugin/artifact_deploy.go | 2 +- .../warpdrive/core/service/taskplugin/build.go | 2 +- .../core/service/taskplugin/docker_build.go | 2 +- .../core/service/taskplugin/jenkins_plugin.go | 2 +- .../warpdrive/core/service/taskplugin/job.go | 13 ++++++++++--- .../core/service/taskplugin/release_image.go | 2 +- .../warpdrive/core/service/taskplugin/testing.go | 15 ++++++++++++++- pkg/tool/kube/multicluster/service.go | 6 +++--- 9 files changed, 32 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f7fe5165..814f5681 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1778,7 +1778,6 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // } // } //} - // 设置Pipeline对应的服务名称 if t.ServiceName != "" { pt.ServiceName = t.ServiceName diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index 37059967..626e235c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -307,7 +307,7 @@ func (p *ArtifactDeployTaskPlugin) Complete(ctx context.Context, pipelineTask *t } }() - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, "", p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 039ecc9b..a5b68979 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -343,7 +343,7 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, return }() - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.Task.ClusterID, p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index d8ab9b86..bc358059 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -244,7 +244,7 @@ func (p *DockerBuildPlugin) Complete(ctx context.Context, pipelineTask *task.Tas }() // 保存实时日志到s3 - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, "", p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 279e8f52..fbf8f15f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -233,7 +233,7 @@ func (j *JenkinsBuildPlugin) Complete(ctx context.Context, pipelineTask *task.Ta }() // 保存实时日志到s3 - err := saveContainerLog(pipelineTask, j.KubeNamespace, j.FileName, jobLabel, j.kubeClient) + err := saveContainerLog(pipelineTask, j.KubeNamespace, "", j.FileName, jobLabel, j.kubeClient) if err != nil { j.Log.Error(err) j.Task.Error = err.Error() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index b34a3ce2..f5ef80a3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -44,9 +44,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/wrapper" - krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -77,7 +77,7 @@ func saveFile(src io.Reader, localFile string) error { return err } -func saveContainerLog(pipelineTask *task.Task, namespace, fileName string, jobLabel *JobLabel, kubeClient client.Client) error { +func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName string, jobLabel *JobLabel, kubeClient client.Client) error { selector := labels.Set(getJobLabels(jobLabel)).AsSelector() pods, err := getter.ListPods(namespace, selector, kubeClient) if err != nil { @@ -97,7 +97,14 @@ func saveContainerLog(pipelineTask *task.Task, namespace, fileName string, jobLa sort.SliceStable(pods, func(i, j int) bool { return pods[i].CreationTimestamp.Before(&pods[j].CreationTimestamp) }) - if err := containerlog.GetContainerLogs(namespace, pods[0].Name, pods[0].Spec.Containers[0].Name, false, int64(0), buf, krkubeclient.Clientset()); err != nil { + + clientSet, err := multicluster.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, clusterID) + if err != nil { + log.Errorf("saveContainerLog, get client set error: %s", err) + return err + } + + if err := containerlog.GetContainerLogs(namespace, pods[0].Name, pods[0].Spec.Containers[0].Name, false, int64(0), buf, clientSet); err != nil { return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index e8b76de5..8cb98b58 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -232,7 +232,7 @@ func (p *ReleaseImagePlugin) Complete(ctx context.Context, pipelineTask *task.Ta }() // 保存实时日志到s3 - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, "", p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 88efe451..bba2c0da 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" @@ -104,6 +105,18 @@ func (p *TestPlugin) TaskTimeout() int { func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace + if p.Task.Namespace != "" { + p.KubeNamespace = p.Task.Namespace + kubeClient, err := multicluster.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get kube client: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.kubeClient = kubeClient + } // 重置错误信息 p.Task.Error = "" // 获取测试相关的namespace @@ -270,7 +283,7 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv return }() - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.Task.ClusterID, p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index 3e0ad316..eb6e6aec 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -46,7 +46,7 @@ func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { } func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { - if clusterID == "" { + if clusterID == "" || clusterID == setting.LocalClusterID { return krkubeclient.APIReader(), nil } @@ -59,7 +59,7 @@ func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { } func GetRESTConfig(hubServerAddr, clusterID string) (*rest.Config, error) { - if clusterID == "" { + if clusterID == "" || clusterID == setting.LocalClusterID { return krkubeclient.RESTConfig(), nil } @@ -68,7 +68,7 @@ func GetRESTConfig(hubServerAddr, clusterID string) (*rest.Config, error) { // GetClientset returns a client to interact with APIServer which implements kubernetes.Interface func GetClientset(hubServerAddr, clusterID string) (kubernetes.Interface, error) { - if clusterID == "" { + if clusterID == "" || clusterID == setting.LocalClusterID { return krkubeclient.Clientset(), nil } -- Gitee From f8b958b8d6891e8b765158fbe254852bb72d410b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 21:10:54 +0800 Subject: [PATCH 0883/2647] clean job Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 16 +++++++--------- .../warpdrive/core/service/taskplugin/testing.go | 16 +++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index a5b68979..1f4e3886 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -328,16 +328,14 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, // 清理用户取消和超时的任务 defer func() { - if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() } return diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index bba2c0da..325f7168 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -270,15 +270,13 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv // 日志保存失败与否都清理job defer func() { - if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() } return }() -- Gitee From e17c125b9ff7826ac613e4f83d4cd0c6c07dcac2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 21:24:19 +0800 Subject: [PATCH 0884/2647] modify test task Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 31feb5d7..f60fea43 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -567,6 +567,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * TestName: "test", Timeout: testModule.Timeout, } + testTask.TestModuleName = testModule.Name testTask.JobCtx.TestType = testModule.TestType testTask.JobCtx.Builds = testModule.Repos @@ -588,6 +589,8 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testTask.InstallItems = testModule.PreTest.Installs testTask.JobCtx.CleanWorkspace = testModule.PreTest.CleanWorkspace testTask.JobCtx.EnableProxy = testModule.PreTest.EnableProxy + testTask.Namespace = testModule.PreTest.Namespace + testTask.ClusterID = testModule.PreTest.ClusterID envs := testModule.PreTest.Envs[:] -- Gitee From 4a998e66cdb8fed1ca3d48c84713856ba078e45e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 6 Dec 2021 21:31:50 +0800 Subject: [PATCH 0885/2647] modify test task Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 814f5681..71f5f63a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1211,6 +1211,8 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * testTask.InstallItems = testModule.PreTest.Installs testTask.JobCtx.CleanWorkspace = testModule.PreTest.CleanWorkspace testTask.JobCtx.EnableProxy = testModule.PreTest.EnableProxy + testTask.Namespace = testModule.PreTest.Namespace + testTask.ClusterID = testModule.PreTest.ClusterID envs := testModule.PreTest.Envs[:] -- Gitee From 9d2f1711abd64fc8e7256a13c0965e453024928d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 09:43:00 +0800 Subject: [PATCH 0886/2647] modify the import order of dependent packages Signed-off-by: liu deyi --- .../warpdrive/core/service/types/task/config_payload.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 313af2d7..a8827cfe 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -19,9 +19,9 @@ package task import ( "fmt" - "github.com/koderover/zadig/pkg/setting" - "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/setting" ) type ConfigPayload struct { -- Gitee From 2abdaa87c1ca7d9e65cb12e5c808a851565391d1 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 7 Dec 2021 10:24:33 +0800 Subject: [PATCH 0887/2647] optimize create helm product Signed-off-by: panxunying --- .../core/environment/handler/environment.go | 25 ++++++++----------- .../core/environment/service/environment.go | 5 ++-- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 6a1f5119..6998191c 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -130,11 +130,6 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - registryID := c.Query("registryID") - if registryID == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("registryID can not be empty") - return - } createArgs := make([]*service.CreateHelmProductArg, 0) data, err := c.GetRawData() if err != nil { @@ -160,7 +155,7 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( - projectName, ctx.UserName, ctx.RequestID, registryID, createArgs, ctx.Logger, + projectName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, ) } @@ -168,15 +163,6 @@ func CreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(commonmodels.Product) - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateProduct c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateProduct json.Unmarshal err : %v", err) - } - if c.Query("auto") == "true" { ctx.Resp = service.AutoCreateProduct(c.Query("projectName"), c.Query("envType"), ctx.RequestID, ctx.Logger) return @@ -186,6 +172,15 @@ func CreateProduct(c *gin.Context) { return } + args := new(commonmodels.Product) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateProduct c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateProduct json.Unmarshal err : %v", err) + } + allowedClusters, found := internalhandler.GetResourcesInHeader(c) if found { allowedSet := sets.NewString(allowedClusters...) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 86db090c..404fa393 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -147,6 +147,7 @@ type CreateHelmProductArg struct { Namespace string `json:"namespace"` ClusterID string `json:"clusterID"` DefaultValues string `json:"defaultValues"` + RegistryID string `json:"registry_id"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } @@ -677,7 +678,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return nil } -func CreateHelmProduct(productName, userName, requestID, registryID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { +func CreateHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { templateProduct, err := templaterepo.NewProductColl().Find(productName) if err != nil || templateProduct == nil { if err != nil { @@ -734,7 +735,7 @@ func CreateHelmProduct(productName, userName, requestID, registryID string, args errList := new(multierror.Error) for _, arg := range args { - err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, registryID, arg, log) + err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, arg.RegistryID, arg, log) if err != nil { errList = multierror.Append(errList, err) } -- Gitee From 7b41d9a9785a2cec3b888cf287bb3bb284369a52 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 13:47:23 +0800 Subject: [PATCH 0888/2647] modify the import order of dependent packages Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 1f4e3886..941be3e3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -24,8 +24,6 @@ import ( "strings" "time" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" - "go.uber.org/zap" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" @@ -35,6 +33,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" ) -- Gitee From 0d74a7eb5b2e63e0a700d93f4bc2e4b6298c5bf3 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 7 Dec 2021 14:01:28 +0800 Subject: [PATCH 0889/2647] fix worflow cant find registry Signed-off-by: panxunying --- .../aslan/core/workflow/service/workflow/workflow_task.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 28bd0f74..b0f58e47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1778,10 +1778,10 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) } - opt := &commonrepo.ProductFindOptions{Namespace: envName} + opt := &commonrepo.ProductFindOptions{Name: pt.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("can't find product by namespace:%s error msg: %v", envName, err) + log.Errorf("can't find product by name:%s error msg: %v", pt.ProductName, err) return e.ErrFindRegistry.AddDesc(err.Error()) } @@ -1794,6 +1794,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e var reg *commonmodels.RegistryNamespace if len(exitedProd.RegistryId) > 0 { reg, err = commonservice.FindRegistryById(exitedProd.RegistryId, log) + log.Infof("FindRegistryById %s", exitedProd.RegistryId) if err != nil { log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", exitedProd.RegistryId, err) -- Gitee From ded40ff6f238c6bb4c7bea99f50ef609bb19ab9d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 14:25:06 +0800 Subject: [PATCH 0890/2647] optimize the way to obtain reaper Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/job.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index f5ef80a3..0f10d1eb 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -409,14 +409,21 @@ func buildJob(taskType config.TaskType, jobImage, jobName, serviceName, clusterI } func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName, clusterID string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { - var reaperBootingScript string + var ( + reaperBootingScript string + reaperBinaryFile = pipelineTask.ConfigPayload.Release.ReaperBinaryFile + ) + // not local cluster + if clusterID != "" && clusterID != setting.LocalClusterID { + reaperBinaryFile = strings.Replace(reaperBinaryFile, "resource-server", "resource-server.koderover-agent", -1) + } if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { - reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", pipelineTask.ConfigPayload.Release.ReaperBinaryFile) + reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", reaperBinaryFile) if pipelineTask.ConfigPayload.Proxy.EnableApplicationProxy && pipelineTask.ConfigPayload.Proxy.Type == "http" { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL --proxy %s %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", pipelineTask.ConfigPayload.Proxy.GetProxyURL(), - pipelineTask.ConfigPayload.Release.ReaperBinaryFile, + reaperBinaryFile, ) } } -- Gitee From 969ddcd64cbb2653997d9aa12ff44eb35735b72d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 14:42:13 +0800 Subject: [PATCH 0891/2647] optimize code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/job.go | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 0f10d1eb..72c578d1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -841,17 +841,18 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A return nil } + nodeLabelM := make(map[string][]string, 0) + for _, nodeLabel := range clusterConfig.NodeLabels { + if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { + continue + } + key := strings.Split(nodeLabel, ":")[0] + value := strings.Split(nodeLabel, ":")[1] + nodeLabelM[key] = append(nodeLabelM[key], value) + } + switch clusterConfig.Strategy { case RequiredSchedule: - nodeLabelM := make(map[string][]string, 0) - for _, nodeLabel := range clusterConfig.NodeLabels { - if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { - continue - } - key := strings.Split(nodeLabel, ":")[0] - value := strings.Split(nodeLabel, ":")[1] - nodeLabelM[key] = append(nodeLabelM[key], value) - } nodeSelectorTerms := make([]corev1.NodeSelectorTerm, 0) for key, value := range nodeLabelM { var matchExpressions []corev1.NodeSelectorRequirement @@ -874,15 +875,6 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A } return affinity case PreferredSchedule: - nodeLabelM := make(map[string][]string, 0) - for _, nodeLabel := range clusterConfig.NodeLabels { - if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { - continue - } - key := strings.Split(nodeLabel, ":")[0] - value := strings.Split(nodeLabel, ":")[1] - nodeLabelM[key] = append(nodeLabelM[key], value) - } preferredScheduleTerms := make([]corev1.PreferredSchedulingTerm, 0) for key, value := range nodeLabelM { var matchExpressions []corev1.NodeSelectorRequirement -- Gitee From 768ef571601b2dfec8d9ca00949086f07da19079 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 7 Dec 2021 14:51:28 +0800 Subject: [PATCH 0892/2647] optimize Signed-off-by: panxunying --- .../aslan/core/environment/handler/environment.go | 9 +++++---- .../aslan/core/environment/service/environment.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 6998191c..ef9f41c3 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -255,28 +255,29 @@ func UpdateProductRegistry(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - namespace := c.Param("namespace") projectName := c.Query("projectName") args := new(commonmodels.Product) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateProduct c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateProduct json.Unmarshal err : %v", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", namespace, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", args.Namespace, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Err = service.UpdateProductRegistry(namespace, args.RegistryId, ctx.Logger) + ctx.Err = service.UpdateProductRegistry(args.Namespace, args.RegistryId, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product %s %s: %v", namespace, args.RegistryId, ctx.Err) + ctx.Logger.Errorf("failed to update product %s %s: %v", args.Namespace, args.RegistryId, ctx.Err) } } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 404fa393..597af6de 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -116,7 +116,7 @@ type ProductResp struct { RecycleDay int `json:"recycle_day"` IsProd bool `json:"is_prod"` Source string `json:"source"` - RegisterID string `json:"register_id"` + RegisterID string `json:"registry_id"` } type ProductParams struct { -- Gitee From 8eaf618222cc112106cc693d6bf5611815627a87 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 7 Dec 2021 16:27:51 +0800 Subject: [PATCH 0893/2647] fix pod filter logic (#697) * fix pod filter logic Signed-off-by: allenshen * update filter logic Signed-off-by: allenshen * update filter logic Signed-off-by: allenshen * remove useless code Signed-off-by: allenshen --- .../aslan/core/common/service/environment.go | 29 ++++++++++++++ .../aslan/core/environment/service/image.go | 30 +------------- .../service/workflow/pipeline_validation.go | 39 +++++++++++-------- 3 files changed, 52 insertions(+), 46 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 6212da27..97a1f15e 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -18,13 +18,17 @@ package service import ( "encoding/json" + "fmt" "sort" "strings" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -333,3 +337,28 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work } return resp } + +// GetHelmServiceName get service name from annotations of resources deployed by helm +// resType currently only support Deployment and StatefulSet +func GetHelmServiceName(namespace, resType, resName string, kubeClient client.Client) (string, error) { + res := &unstructured.Unstructured{} + res.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apps", + Version: "v1", + Kind: resType, + }) + found, err := getter.GetResourceInCache(namespace, resName, res, kubeClient) + if err != nil { + return "", fmt.Errorf("failed to find resource %s, type %s, err %s", resName, resType, err.Error()) + } + if !found { + return "", fmt.Errorf("failed to find resource %s, type %s", resName, resType) + } + annotation := res.GetAnnotations() + if len(annotation) > 0 { + if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { + return util.ExtraServiceName(chartRelease, namespace), nil + } + } + return "", fmt.Errorf("failed to get annotation from resource %s, type %s", resName, resType) +} diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 4fd4bbf7..b4312a5b 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -24,10 +24,7 @@ import ( helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -38,10 +35,8 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -64,29 +59,6 @@ var ( imageParseRegex = regexp.MustCompile(imageUrlParseRegexString) ) -func getHelmServiceName(namespace, resType, resName string, kubeClient client.Client) (string, error) { - res := &unstructured.Unstructured{} - res.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apps", - Version: "v1", - Kind: resType, - }) - found, err := getter.GetResourceInCache(namespace, resName, res, kubeClient) - if err != nil { - return "", fmt.Errorf("failed to find resource %s, type %s, err %s", resName, resType, err.Error()) - } - if !found { - return "", fmt.Errorf("failed to find resource %s, type %s", resName, resType) - } - annotation := res.GetAnnotations() - if len(annotation) > 0 { - if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { - return util.ExtraServiceName(chartRelease, namespace), nil - } - } - return "", fmt.Errorf("failed to get annotation from resource %s, type %s", resName, resType) -} - func getValidMatchData(spec *models.ImagePathSpec) map[string]string { ret := make(map[string]string) if spec.Repo != "" { @@ -332,7 +304,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log // update service in helm way if product.Source == setting.HelmDeployType { - serviceName, err := getHelmServiceName(namespace, args.Type, args.Name, kubeClient) + serviceName, err := commonservice.GetHelmServiceName(namespace, args.Type, args.Name, kubeClient) if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 1646b33c..1ab541b8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -694,7 +694,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin } return validateServiceContainer2( - product.Namespace, envName, productName, serviceName, container, product.Source, kubeClient, + product, serviceName, container, kubeClient, ) } @@ -716,10 +716,28 @@ func (c *ImageIllegal) Error() string { return "" } +// find currently using image for services deployed by helm +func findCurrentlyUsingImage(productInfo *commonmodels.Product, serviceName, containerName string) (string, error) { + for _, service := range productInfo.GetServiceMap() { + if service.ServiceName == serviceName { + for _, container := range service.Containers { + if container.Name == containerName { + return container.Image, nil + } + } + } + } + return "", fmt.Errorf("failed to find image url") +} + // validateServiceContainer2 validate container with raw namespace like dev-product -func validateServiceContainer2(namespace, envName, productName, serviceName, container, source string, kubeClient client.Client) (string, error) { - var selector labels.Selector +func validateServiceContainer2(productInfo *commonmodels.Product, serviceName, container string, kubeClient client.Client) (string, error) { + namespace, productName, envName, source := productInfo.Namespace, productInfo.ProductName, productInfo.EnvName, productInfo.Source + if source == setting.SourceFromHelm { + return findCurrentlyUsingImage(productInfo, serviceName, container) + } + var selector labels.Selector //helm和托管类型的服务查询所有标签的pod if source != setting.SourceFromHelm && source != setting.SourceFromExternal { selector = labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceName}.AsSelector() @@ -733,22 +751,9 @@ func validateServiceContainer2(namespace, envName, productName, serviceName, con } for _, p := range pods { - // need to filter by serviceName because same container name may exist in multi services - if source == setting.SourceFromHelm { - annotation := p.Annotations - if len(annotation) == 0 { - continue - } - if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; !ok { - continue - } else if util.ExtraServiceName(chartRelease, namespace) != serviceName { - continue - } - } - pod := wrapper.Pod(p).Resource() for _, c := range pod.ContainerStatuses { - if c.Name == container || strings.Contains(c.Image, container) { + if c.Name == container || commonservice.ExtractImageName(c.Image) == container { return c.Image, nil } } -- Gitee From f88b65c284f45f6cb84d236652667896651fed88 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 20:55:55 +0800 Subject: [PATCH 0894/2647] optimize Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 12 ++++++------ pkg/shared/kube/client/client.go | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 pkg/shared/kube/client/client.go diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 7ae80d0c..af913bd6 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -120,14 +120,14 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op return } log.Debugf("Start to get task container log.") - if options.EnvName != "" && options.ProductName != "" { + // Cloud host scenario reads real-time logs from the environment, so pipelineName is empty + if options.EnvName != "" && options.ProductName != "" && options.PipelineName == "" { //修改pipelineName,判断pipelineName是否为空,为空代表是来自环境里面请求,不为空代表是来自工作流任务的请求 - if options.PipelineName == "" { - options.PipelineName = fmt.Sprintf("%s-%s-%s", options.ServiceName, options.EnvName, "job") - if taskObj, err := commonrepo.NewTaskColl().FindTask(options.PipelineName, config.ServiceType); err == nil { - options.TaskID = taskObj.TaskID - } + options.PipelineName = fmt.Sprintf("%s-%s-%s", options.ServiceName, options.EnvName, "job") + if taskObj, err := commonrepo.NewTaskColl().FindTask(options.PipelineName, config.ServiceType); err == nil { + options.TaskID = taskObj.TaskID } + // Need to get build info based on the project name and service component name, then get clusterID and namespace } else if options.ProductName != "" { build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ ProductName: options.ProductName, diff --git a/pkg/shared/kube/client/client.go b/pkg/shared/kube/client/client.go new file mode 100644 index 00000000..f84e1e4e --- /dev/null +++ b/pkg/shared/kube/client/client.go @@ -0,0 +1 @@ +package kube -- Gitee From 831601955ebb641b4e595230fba4734e0b52ff41 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 20:59:03 +0800 Subject: [PATCH 0895/2647] optimize code Signed-off-by: liu deyi --- pkg/cli/initconfig/cmd/init.go | 24 +-- pkg/config/config.go | 4 + .../common/repository/models/k8s_cluster.go | 79 ++++----- .../core/common/repository/models/queue.go | 4 +- .../common/repository/mongodb/k8s_cluster.go | 2 +- .../core/common/service/config_payload.go | 10 +- .../aslan/core/common/service/environment.go | 4 +- .../aslan/core/common/service/kube/service.go | 7 +- .../aslan/core/common/service/product.go | 4 +- .../core/environment/service/configmap.go | 8 +- .../core/environment/service/environment.go | 8 +- .../service/environment_creator.go | 6 +- .../environment/service/environment_group.go | 4 +- .../aslan/core/environment/service/export.go | 4 +- .../aslan/core/environment/service/image.go | 4 +- .../aslan/core/environment/service/k8s.go | 4 +- .../aslan/core/environment/service/kube.go | 18 +-- .../aslan/core/environment/service/service.go | 14 +- .../aslan/core/log/service/sse.go | 14 +- .../core/multicluster/handler/clusters.go | 5 +- .../core/multicluster/service/clusters.go | 152 +++++++++++++++--- .../aslan/core/service/service/service.go | 6 +- .../aslan/core/system/service/registry.go | 6 +- .../service/workflow/pipeline_validation.go | 4 +- .../core/service/taskplugin/build.go | 4 +- .../core/service/taskplugin/deploy.go | 6 +- .../warpdrive/core/service/taskplugin/job.go | 36 ++--- .../core/service/taskplugin/testing.go | 4 +- .../core/service/types/task/config_payload.go | 27 ++-- pkg/shared/client/aslan/multicluster.go | 21 ++- pkg/shared/kube/client/client.go | 40 ++++- pkg/shared/kube/resource/node.go | 2 +- pkg/shared/kube/wrapper/namespace.go | 2 +- pkg/tool/kube/multicluster/service.go | 9 +- 34 files changed, 334 insertions(+), 212 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 8d85e22c..00add4ae 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -102,8 +102,8 @@ func initSystemConfig() error { return err } - if err := createMultiCluster(); err != nil { - log.Errorf("createMultiCluster err:%s", err) + if err := createLocalCluster(); err != nil { + log.Errorf("createLocalCluster err:%s", err) return err } @@ -203,20 +203,10 @@ func presetRole() error { return nil } -func createMultiCluster() error { - clusters, err := aslan.New(config.AslanServiceAddress()).ListMultiCluster() - if err != nil { - return err +func createLocalCluster() error { + cluster, _ := aslan.New(config.AslanServiceAddress()).GetCluster() + if cluster != nil { + return nil } - var hasLocal bool - for _, cluster := range clusters { - if cluster.Local { - hasLocal = true - break - } - } - if !hasLocal { - return aslan.New(config.AslanServiceAddress()).AddMultiCluster() - } - return nil + return aslan.New(config.AslanServiceAddress()).AddLocalCluster() } diff --git a/pkg/config/config.go b/pkg/config/config.go index 78ff4533..593eff9c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -257,3 +257,7 @@ func AdminEmail() string { func AdminPassword() string { return viper.GetString(setting.ENVAdminPassword) } + +func Namespace() string { + return viper.GetString(setting.ENVNamespace) +} diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index bbc32146..767d9449 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -17,35 +17,36 @@ limitations under the License. package models import ( - "fmt" - "regexp" - "strings" - "go.mongodb.org/mongo-driver/bson/primitive" + corev1 "k8s.io/api/core/v1" "github.com/koderover/zadig/pkg/setting" ) -var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) - type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` - Status setting.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Provider int8 `json:"provider" bson:"provider"` - Local bool `json:"local" bson:"local"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Provider int8 `json:"provider" bson:"provider"` + Local bool `json:"local" bson:"local"` +} + +type K8SClusterResp struct { + ID string `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` } type K8SClusterInfo struct { @@ -55,31 +56,17 @@ type K8SClusterInfo struct { Memory string `json:"memory" bson:"memory"` } -type ClusterConfig struct { - Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` - NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` +type AdvancedConfig struct { + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` } -func (K8SCluster) TableName() string { - return "k8s_cluster" +type NodeSelectorRequirement struct { + Key string `json:"key"` + Value []string `json:"value"` + Operator corev1.NodeSelectorOperator `json:"operator"` } -func (k *K8SCluster) Clean() error { - k.Name = strings.TrimSpace(k.Name) - tags := k.Tags - k.Tags = []string{} - for _, tag := range tags { - tag = strings.TrimSpace(tag) - if tag != "" { - k.Tags = append(k.Tags, tag) - } - } - - k.Description = strings.TrimSpace(k.Description) - - if !namePattern.MatchString(k.Name) { - return fmt.Errorf("集群名称不符合规则") - } - - return nil +func (K8SCluster) TableName() string { + return "k8s_cluster" } diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 5edfa0b9..973097c9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -126,7 +126,7 @@ type ConfigPayload struct { CustomDNSSupported bool `json:"custom_dns_supported"` HubServerAddr string DeployClusterID string - AesKey string + AesKey string `bson:"-" json:"aes_key"` RepoConfigs map[string]*RegistryNamespace @@ -137,7 +137,7 @@ type ConfigPayload struct { ResetCache bool `json:"reset_cache"` JenkinsBuildConfig JenkinsBuildConfig `json:"jenkins_build_config"` PrivateKeys []*PrivateKey `json:"private_keys"` - K8SClusters []*K8SCluster `json:"k8s_cluster"` + K8SClusters []*K8SClusterResp `bson:"-" json:"k8s_cluster"` } type AslanConfig struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 8daf229f..207d8c2a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -185,7 +185,7 @@ func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster) error { "tags": cluster.Tags, "namespace": cluster.Namespace, "production": cluster.Production, - "config": cluster.ClusterConfig, + "config": cluster.AdvancedConfig, }}, ) diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index fb38f9ed..c4981fd5 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -93,7 +93,15 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { k8sClusters, _ := mongodb.NewK8SClusterColl().List(nil) if len(k8sClusters) != 0 { - payload.K8SClusters = k8sClusters + var K8SClusterResp []*models.K8SClusterResp + for _, k8sCluster := range k8sClusters { + K8SClusterResp = append(K8SClusterResp, &models.K8SClusterResp{ + ID: k8sCluster.ID.Hex(), + Name: k8sCluster.Name, + AdvancedConfig: k8sCluster.AdvancedConfig, + }) + } + payload.K8SClusters = K8SClusterResp } return payload } diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 61b2cddc..f2b50cf2 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -32,11 +32,11 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/util" jsonutil "github.com/koderover/zadig/pkg/util/json" ) @@ -209,7 +209,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Infof("Start to list workloads in namespace %s", namespace) var resp = make([]*ServiceResp, 0) - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index e13a813a..1b650fa5 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -32,22 +32,23 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) func GetKubeAPIReader(clusterID string) (client.Reader, error) { - return multicluster.GetKubeAPIReader(config.HubServerAddress(), clusterID) + return client2.GetKubeAPIReader(config.HubServerAddress(), clusterID) } func GetRESTConfig(clusterID string) (*rest.Config, error) { - return multicluster.GetRESTConfig(config.HubServerAddress(), clusterID) + return client2.GetRESTConfig(config.HubServerAddress(), clusterID) } // GetClientset returns a client to interact with APIServer which implements kubernetes.Interface func GetClientset(clusterID string) (kubernetes.Interface, error) { - return multicluster.GetClientset(config.HubServerAddress(), clusterID) + return client2.GetClientset(config.HubServerAddress(), clusterID) } type Service struct { diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 2a024a06..e336b30d 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -21,6 +21,7 @@ import ( "time" "github.com/hashicorp/go-multierror" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "k8s.io/apimachinery/pkg/labels" @@ -37,7 +38,6 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -53,7 +53,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return e.ErrDeleteEnv.AddDesc("not found") } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrDeleteEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 038b0f07..a3d63401 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -33,10 +33,10 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/kube/util" ) @@ -100,7 +100,7 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*configM if err != nil { return nil, e.ErrListConfigMaps.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return nil, e.ErrListConfigMaps.AddErr(err) } @@ -155,7 +155,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } @@ -232,7 +232,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f9e9fc33..822e93fe 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -54,11 +54,11 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -277,7 +277,7 @@ func AutoUpdateProduct(envNames []string, productName, requestID string, force b continue } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), p.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), p.ClusterID) if err != nil { log.Errorf("Failed to get kube client for %s, error: %v", productName, err) continue @@ -389,7 +389,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), existedProd.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), existedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } @@ -533,7 +533,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return e.ErrUpdateEnv.AddDesc(e.EnvNotFoundErrMsg) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index b73b49e9..62d50807 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -30,10 +30,10 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) type CreateProductParam struct { @@ -121,7 +121,7 @@ func newHelmProductCreator() *HelmProductCreator { } func (creator *HelmProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), args.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { log.Errorf("[%s][%s] GetKubeClient error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) @@ -278,7 +278,7 @@ func newDefaultProductCreator() *DefaultProductCreator { } func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), args.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { return e.ErrCreateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index cb438525..ab8fa01c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -30,10 +30,10 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/util" ) @@ -67,7 +67,7 @@ func ListGroups(serviceName, envName, productName string, perPage, page int, log //将获取到的所有服务按照名称进行排序 sort.SliceStable(allServices, func(i, j int) bool { return allServices[i].ServiceName < allServices[j].ServiceName }) - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, productName, err) return resp, count, e.ErrListGroups.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/export.go b/pkg/microservice/aslan/core/environment/service/export.go index 775c62f6..29d6589b 100644 --- a/pkg/microservice/aslan/core/environment/service/export.go +++ b/pkg/microservice/aslan/core/environment/service/export.go @@ -24,8 +24,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) // ExportYaml 查询使用到服务模板的服务组模板 @@ -40,7 +40,7 @@ func ExportYaml(envName, productName, serviceName string, log *zap.SugaredLogger } namespace := env.Namespace - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), env.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), env.ClusterID) if err != nil { log.Errorf("cluster is not connected [%s][%s][%s]", env.EnvName, env.ProductName, env.ClusterID) return res diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index b1556b5b..df062a82 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -37,10 +37,10 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -321,7 +321,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log } namespace := product.Namespace - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 4606d4a2..5b6e998e 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -34,8 +34,8 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) type K8sService struct { @@ -83,7 +83,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return errors.New(e.UpsertServiceErrMsg) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index c64c4fea..bfdc4c13 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -32,11 +32,11 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/kube/util" ) @@ -113,7 +113,7 @@ func ListPodEvents(envName, productName, podName string, log *zap.SugaredLogger) // ListAvailableNamespaces lists available namespaces created by non-koderover func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { resp := make([]*resource.Namespace, 0) - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListNamespaces clusterID:%s err:%v", clusterID, err) return resp, err @@ -150,7 +150,7 @@ func ListServicePods(productName, envName string, serviceName string, log *zap.S if err != nil { return res, e.ErrListServicePod.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return res, e.ErrListServicePod.AddErr(err) } @@ -177,7 +177,7 @@ func DeletePod(envName, productName, podName string, log *zap.SugaredLogger) err if err != nil { return e.ErrDeletePod.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrDeletePod.AddErr(err) } @@ -230,7 +230,7 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.NodeResp, error) { resp := new(resource.NodeResp) - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) return resp, err @@ -249,7 +249,7 @@ func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.Nod labels := sets.NewString() for _, node := range nodes { nodeResource := &resource.Node{ - Status: nodeReady(node), + Ready: nodeReady(node), Labels: nodeLabel(node), IP: node.Name, } @@ -262,14 +262,14 @@ func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.Nod } // Ready indicates that the node is ready for traffic. -func nodeReady(node *corev1.Node) string { +func nodeReady(node *corev1.Node) bool { cs := node.Status.Conditions for _, c := range cs { if c.Type == corev1.NodeReady && c.Status == corev1.ConditionTrue { - return "ready" + return true } } - return "not ready" + return false } func nodeLabel(node *corev1.Node) []string { diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index d21d4e38..70593037 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -33,11 +33,11 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" internalresource "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -61,7 +61,7 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap return e.ErrScaleService.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return e.ErrScaleService.AddErr(err) } @@ -112,7 +112,7 @@ func Scale(args *ScaleArgs, logger *zap.SugaredLogger) error { return e.ErrScaleService.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return e.ErrScaleService.AddErr(err) } @@ -142,7 +142,7 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { return err } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return err } @@ -178,7 +178,7 @@ func GetService(envName, productName, serviceName string, workLoadType string, l return nil, e.ErrGetService.AddErr(err) } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), env.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), env.ClusterID) if err != nil { return nil, e.ErrGetService.AddErr(err) } @@ -324,7 +324,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e if err != nil { return err } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), productObj.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), productObj.ClusterID) if err != nil { return err } @@ -423,7 +423,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return "", err } diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index af913bd6..f66547d7 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -32,9 +32,9 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/watcher" ) @@ -156,8 +156,7 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, options.SubTask = string(config.TaskTestingV2) selector := getPipelineSelector(options) // get cluster ID - testName := strings.Replace(options.ServiceName, "-job", "", 1) - testing, _ := commonrepo.NewTestingColl().Find(testName, "") + testing, _ := commonrepo.NewTestingColl().Find(getTestName(options.ServiceName), "") if testing != nil && testing.PreTest != nil { options.ClusterID = testing.PreTest.ClusterID options.Namespace = testing.PreTest.Namespace @@ -166,12 +165,17 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, waitAndGetLog(ctx, streamChan, selector, options, log) } +func getTestName(serviceName string) string { + testName := strings.TrimRight(serviceName, "-job") + return testName +} + func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector labels.Selector, options *GetContainerOptions, log *zap.SugaredLogger) { PodCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() log.Debugf("Waiting until pod is running before establishing the stream.") - clientSet, err := multicluster.GetClientset(config.HubServerAddress(), options.ClusterID) + clientSet, err := client.GetClientset(config.HubServerAddress(), options.ClusterID) if err != nil { log.Errorf("GetContainerLogs, get client set error: %s", err) return @@ -181,7 +185,7 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la log.Errorf("GetContainerLogs, wait pod running error: %s", err) return } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), options.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), options.ClusterID) if err != nil { log.Errorf("GetContainerLogs, get kube client error: %s", err) return diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index 42617c97..ca1ed744 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -22,7 +22,6 @@ import ( "github.com/gin-gonic/gin" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -52,7 +51,7 @@ func CreateCluster(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(commonmodels.K8SCluster) + args := new(service.K8SCluster) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return @@ -73,7 +72,7 @@ func UpdateCluster(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(commonmodels.K8SCluster) + args := new(service.K8SCluster) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 8508224c..cb315872 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -17,10 +17,14 @@ limitations under the License. package service import ( + "fmt" "net/http" + "regexp" + "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" configbase "github.com/koderover/zadig/pkg/config" @@ -32,17 +36,51 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) +var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) + type K8SCluster struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Status setting.K8SClusterStatus `json:"status"` - Production bool `json:"production"` - CreatedAt int64 `json:"createdAt"` - CreatedBy string `json:"createdBy"` - Provider int8 `json:"provider"` - Local bool `json:"local"` - ClusterConfig *commonmodels.ClusterConfig `json:"config,omitempty"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + Tags []string `json:"tags"` + Description string `json:"description"` + Namespace string `json:"namespace"` + Info *commonmodels.K8SClusterInfo `json:"info,omitempty"` + AdvancedConfig *AdvancedConfig `json:"config,omitempty"` + Status setting.K8SClusterStatus `json:"status"` + Error string `json:"error"` + Yaml string `json:"yaml"` + Production bool `json:"production"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Disconnected bool `json:"-"` + Token string `json:"token"` + Provider int8 `json:"provider"` + Local bool `json:"local"` +} + +type AdvancedConfig struct { + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` +} + +func (k *K8SCluster) Clean() error { + k.Name = strings.TrimSpace(k.Name) + tags := k.Tags + k.Tags = []string{} + for _, tag := range tags { + tag = strings.TrimSpace(tag) + if tag != "" { + k.Tags = append(k.Tags, tag) + } + } + + k.Description = strings.TrimSpace(k.Description) + + if !namePattern.MatchString(k.Name) { + return fmt.Errorf("集群名称不符合规则") + } + + return nil } func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { @@ -55,17 +93,28 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error var res []*K8SCluster for _, c := range cs { + advancedConfig := new(AdvancedConfig) + if c.AdvancedConfig != nil { + advancedConfig.Strategy = c.AdvancedConfig.Strategy + var nodeLabels []string + for _, nodeSelectorRequirement := range c.AdvancedConfig.NodeLabels { + for _, labelValue := range nodeSelectorRequirement.Value { + nodeLabels = append(nodeLabels, fmt.Sprintf("%s:%s", nodeSelectorRequirement.Key, labelValue)) + } + } + advancedConfig.NodeLabels = nodeLabels + } res = append(res, &K8SCluster{ - ID: c.ID.Hex(), - Name: c.Name, - Description: c.Description, - Status: c.Status, - Production: c.Production, - CreatedBy: c.CreatedBy, - CreatedAt: c.CreatedAt, - Provider: c.Provider, - Local: c.Local, - ClusterConfig: c.ClusterConfig, + ID: c.ID.Hex(), + Name: c.Name, + Description: c.Description, + Status: c.Status, + Production: c.Production, + CreatedBy: c.CreatedBy, + CreatedAt: c.CreatedAt, + Provider: c.Provider, + Local: c.Local, + AdvancedConfig: advancedConfig, }) } @@ -78,15 +127,70 @@ func GetCluster(id string, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, return s.GetCluster(id, logger) } -func CreateCluster(cluster *commonmodels.K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { - s, _ := kube.NewService("") +func convertToNodeSelectorRequirements(args *K8SCluster) []*commonmodels.NodeSelectorRequirement { + var nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement + nodeLabelM := make(map[string][]string) + for _, nodeLabel := range args.AdvancedConfig.NodeLabels { + if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { + continue + } + key := strings.Split(nodeLabel, ":")[0] + value := strings.Split(nodeLabel, ":")[1] + nodeLabelM[key] = append(nodeLabelM[key], value) + } + for key, value := range nodeLabelM { + nodeSelectorRequirements = append(nodeSelectorRequirements, &commonmodels.NodeSelectorRequirement{ + Key: key, + Value: value, + Operator: corev1.NodeSelectorOpIn, + }) + } + return nodeSelectorRequirements +} +func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { + s, _ := kube.NewService("") + advancedConfig := new(commonmodels.AdvancedConfig) + if args.AdvancedConfig != nil { + advancedConfig.Strategy = args.AdvancedConfig.Strategy + advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args) + } + cluster := &commonmodels.K8SCluster{ + Name: args.Name, + Tags: args.Tags, + Description: args.Description, + Namespace: args.Namespace, + Info: args.Info, + AdvancedConfig: advancedConfig, + Status: args.Status, + Error: args.Error, + Yaml: args.Yaml, + Production: args.Production, + Disconnected: args.Disconnected, + Token: args.Token, + Provider: args.Provider, + Local: args.Local, + CreatedAt: args.CreatedAt, + CreatedBy: args.CreatedBy, + } return s.CreateCluster(cluster, logger) } -func UpdateCluster(id string, cluster *commonmodels.K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { +func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { s, _ := kube.NewService("") - + advancedConfig := new(commonmodels.AdvancedConfig) + if args.AdvancedConfig != nil { + advancedConfig.Strategy = args.AdvancedConfig.Strategy + advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args) + } + cluster := &commonmodels.K8SCluster{ + Name: args.Name, + Tags: args.Tags, + Description: args.Description, + Namespace: args.Namespace, + AdvancedConfig: advancedConfig, + Production: args.Production, + } return s.UpdateCluster(id, cluster, logger) } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index e5904d3c..39cadff5 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -46,11 +46,11 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) @@ -256,7 +256,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv } func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []models.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s] error: %v", namespace, err) return err @@ -385,7 +385,7 @@ type UpdateWorkloadsArgs struct { } func UpdateWorkloads(ctx context.Context, requestID, username, productName, envName string, args UpdateWorkloadsArgs, log *zap.SugaredLogger) error { - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), args.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { log.Errorf("[%s] error: %s", args.Namespace, err) return err diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 8df8afe0..3ab20ad3 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -28,8 +28,8 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/util" ) @@ -87,7 +87,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa } for _, env := range envs { go func(prod *commonmodels.Product) { - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return @@ -162,7 +162,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam } for _, env := range envs { go func(prod *commonmodels.Product) { - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := client.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 27210e24..a8e90a08 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -45,10 +45,10 @@ import ( git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" @@ -688,7 +688,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - kubeClient, err := multicluster.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return "", err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 941be3e3..687c471f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -32,8 +32,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -110,7 +110,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace - kubeClient, err := multicluster.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + kubeClient, err := client2.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 34b72aca..e65e5f06 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -39,13 +39,13 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" @@ -202,13 +202,13 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * }() if pipelineTask.ConfigPayload.DeployClusterID != "" { - p.restConfig, err = multicluster.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) + p.restConfig, err = client2.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) if err != nil { err = errors.WithMessage(err, "can't get k8s rest config") return } - p.kubeClient, err = multicluster.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) + p.kubeClient, err = client2.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) if err != nil { err = errors.WithMessage(err, "can't init k8s client") return diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 72c578d1..4fd3c6a9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -43,10 +43,10 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -98,7 +98,7 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st return pods[i].CreationTimestamp.Before(&pods[j].CreationTimestamp) }) - clientSet, err := multicluster.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, clusterID) + clientSet, err := client2.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, clusterID) if err != nil { log.Errorf("saveContainerLog, get client set error: %s", err) return err @@ -841,25 +841,15 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A return nil } - nodeLabelM := make(map[string][]string, 0) - for _, nodeLabel := range clusterConfig.NodeLabels { - if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { - continue - } - key := strings.Split(nodeLabel, ":")[0] - value := strings.Split(nodeLabel, ":")[1] - nodeLabelM[key] = append(nodeLabelM[key], value) - } - switch clusterConfig.Strategy { case RequiredSchedule: nodeSelectorTerms := make([]corev1.NodeSelectorTerm, 0) - for key, value := range nodeLabelM { + for _, nodeLabel := range clusterConfig.NodeLabels { var matchExpressions []corev1.NodeSelectorRequirement matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ - Key: key, - Operator: corev1.NodeSelectorOpIn, - Values: value, + Key: nodeLabel.Key, + Operator: nodeLabel.Operator, + Values: nodeLabel.Value, }) nodeSelectorTerms = append(nodeSelectorTerms, corev1.NodeSelectorTerm{ MatchExpressions: matchExpressions, @@ -876,12 +866,12 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A return affinity case PreferredSchedule: preferredScheduleTerms := make([]corev1.PreferredSchedulingTerm, 0) - for key, value := range nodeLabelM { + for _, nodeLabel := range clusterConfig.NodeLabels { var matchExpressions []corev1.NodeSelectorRequirement matchExpressions = append(matchExpressions, corev1.NodeSelectorRequirement{ - Key: key, - Operator: corev1.NodeSelectorOpIn, - Values: value, + Key: nodeLabel.Key, + Operator: nodeLabel.Operator, + Values: nodeLabel.Value, }) nodeSelectorTerm := corev1.NodeSelectorTerm{ MatchExpressions: matchExpressions, @@ -902,10 +892,10 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A } } -func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.ClusterConfig { +func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.AdvancedConfig { for _, K8SCluster := range K8SClusters { - if K8SCluster.ID.Hex() == clusterID { - return K8SCluster.ClusterConfig + if K8SCluster.ID == clusterID { + return K8SCluster.AdvancedConfig } } return nil diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 325f7168..76406a7d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -34,8 +34,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" - "github.com/koderover/zadig/pkg/tool/kube/multicluster" "github.com/koderover/zadig/pkg/tool/kube/updater" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" @@ -107,7 +107,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace - kubeClient, err := multicluster.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + kubeClient, err := client2.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index a8827cfe..10b76384 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -19,9 +19,7 @@ package task import ( "fmt" - "go.mongodb.org/mongo-driver/bson/primitive" - - "github.com/koderover/zadig/pkg/setting" + corev1 "k8s.io/api/core/v1" ) type ConfigPayload struct { @@ -44,7 +42,7 @@ type ConfigPayload struct { CustomDNSSupported bool `json:"custom_dns_supported"` HubServerAddr string DeployClusterID string - AesKey string + AesKey string `json:"aes_key"` RepoConfigs map[string]*RegistryNamespace @@ -156,15 +154,18 @@ type PrivateKey struct { } type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Status setting.K8SClusterStatus `json:"status" bson:"status"` - ClusterConfig *ClusterConfig `json:"config,omitempty" bson:"config,omitempty"` - Production bool `json:"production" bson:"production"` - Disconnected bool `json:"-" bson:"disconnected"` + ID string `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` +} + +type AdvancedConfig struct { + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` } -type ClusterConfig struct { - Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` - NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` +type NodeSelectorRequirement struct { + Key string `json:"key"` + Value []string `json:"value"` + Operator corev1.NodeSelectorOperator `json:"operator"` } diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 5db4d02f..e555ead6 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -4,24 +4,21 @@ import ( "fmt" "time" - "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type cluster struct { - ID primitive.ObjectID `json:"id,omitempty"` + ID string `json:"id"` Name string `json:"name"` Status setting.K8SClusterStatus `json:"status"` Local bool `json:"local"` } -func (c *Client) AddMultiCluster() error { - oid, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) +func (c *Client) AddLocalCluster() error { url := "/cluster/clusters" req := cluster{ - ID: oid, + ID: setting.LocalClusterID, Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), Local: true, Status: setting.Normal, @@ -35,14 +32,14 @@ func (c *Client) AddMultiCluster() error { return nil } -func (c *Client) ListMultiCluster() ([]*cluster, error) { - url := "/cluster/clusters" +func (c *Client) GetCluster() (*cluster, error) { + url := "/cluster/clusters/" + setting.LocalClusterID - clusters := make([]*cluster, 0) - _, err := c.Get(url, httpclient.SetResult(&clusters)) + var cluster *cluster + _, err := c.Get(url, httpclient.SetResult(&cluster)) if err != nil { - return nil, fmt.Errorf("Failed to list multi cluster, error: %s", err) + return nil, fmt.Errorf("Failed to get cluster, error: %s", err) } - return clusters, nil + return cluster, nil } diff --git a/pkg/shared/kube/client/client.go b/pkg/shared/kube/client/client.go index f84e1e4e..dfb984b1 100644 --- a/pkg/shared/kube/client/client.go +++ b/pkg/shared/kube/client/client.go @@ -1 +1,39 @@ -package kube +package client + +import ( + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/kube/multicluster" +) + +func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { + if clusterID == setting.LocalClusterID { + clusterID = "" + } + + return multicluster.GetKubeClient(hubServerAddr, clusterID) +} + +func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { + if clusterID == setting.LocalClusterID { + clusterID = "" + } + return multicluster.GetKubeAPIReader(hubServerAddr, clusterID) +} +func GetRESTConfig(hubServerAddr, clusterID string) (*rest.Config, error) { + if clusterID == setting.LocalClusterID { + clusterID = "" + } + return multicluster.GetRESTConfig(hubServerAddr, clusterID) +} + +func GetClientset(hubServerAddr, clusterID string) (kubernetes.Interface, error) { + if clusterID == setting.LocalClusterID { + clusterID = "" + } + + return multicluster.GetClientset(hubServerAddr, clusterID) +} diff --git a/pkg/shared/kube/resource/node.go b/pkg/shared/kube/resource/node.go index c76b5eb6..a78564c2 100644 --- a/pkg/shared/kube/resource/node.go +++ b/pkg/shared/kube/resource/node.go @@ -2,7 +2,7 @@ package resource type Node struct { Labels []string `json:"node_labels"` - Status string `json:"node_status"` + Ready bool `json:"ready"` IP string `json:"node_ip"` } diff --git a/pkg/shared/kube/wrapper/namespace.go b/pkg/shared/kube/wrapper/namespace.go index 0f7c8402..3fd58ef1 100644 --- a/pkg/shared/kube/wrapper/namespace.go +++ b/pkg/shared/kube/wrapper/namespace.go @@ -19,7 +19,7 @@ package wrapper import ( corev1 "k8s.io/api/core/v1" - "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/util" ) diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index eb6e6aec..067c8740 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -28,12 +28,11 @@ import ( "k8s.io/client-go/tools/clientcmd/api" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" ) func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { - if clusterID == "" || clusterID == setting.LocalClusterID { + if clusterID == "" { return krkubeclient.Client(), nil } @@ -46,7 +45,7 @@ func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { } func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { - if clusterID == "" || clusterID == setting.LocalClusterID { + if clusterID == "" { return krkubeclient.APIReader(), nil } @@ -59,7 +58,7 @@ func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { } func GetRESTConfig(hubServerAddr, clusterID string) (*rest.Config, error) { - if clusterID == "" || clusterID == setting.LocalClusterID { + if clusterID == "" { return krkubeclient.RESTConfig(), nil } @@ -68,7 +67,7 @@ func GetRESTConfig(hubServerAddr, clusterID string) (*rest.Config, error) { // GetClientset returns a client to interact with APIServer which implements kubernetes.Interface func GetClientset(hubServerAddr, clusterID string) (kubernetes.Interface, error) { - if clusterID == "" || clusterID == setting.LocalClusterID { + if clusterID == "" { return krkubeclient.Clientset(), nil } -- Gitee From 690aaff7963ec1373c9e07454ef21d6b988a33cb Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 21:12:34 +0800 Subject: [PATCH 0896/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/environment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 8729b55d..ae4c0198 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -36,7 +36,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/kube/client" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" @@ -213,7 +213,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Infof("Start to list workloads in namespace %s", namespace) var resp = make([]*ServiceResp, 0) - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) -- Gitee From ac80b52bf738cad8485f9bb2f57c51e1072de557 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 21:34:00 +0800 Subject: [PATCH 0897/2647] optimize code Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index e555ead6..5790f5b1 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -32,14 +32,20 @@ func (c *Client) AddLocalCluster() error { return nil } -func (c *Client) GetCluster() (*cluster, error) { +type clusterResp struct { + Name string `json:"name"` + Status setting.K8SClusterStatus `json:"status"` + Local bool `json:"local"` +} + +func (c *Client) GetCluster() (*clusterResp, error) { url := "/cluster/clusters/" + setting.LocalClusterID - var cluster *cluster - _, err := c.Get(url, httpclient.SetResult(&cluster)) + var clusterResp *clusterResp + _, err := c.Get(url, httpclient.SetResult(&clusterResp)) if err != nil { return nil, fmt.Errorf("Failed to get cluster, error: %s", err) } - return cluster, nil + return clusterResp, nil } -- Gitee From c18ddd99c26d797636fab3eb5f450c97faba9657 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 21:37:56 +0800 Subject: [PATCH 0898/2647] optimize code Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 5790f5b1..356a711f 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -39,11 +39,12 @@ type clusterResp struct { } func (c *Client) GetCluster() (*clusterResp, error) { - url := "/cluster/clusters/" + setting.LocalClusterID + url := fmt.Sprintf("/cluster/clusters/%s", setting.LocalClusterID) - var clusterResp *clusterResp + clusterResp := new(clusterResp) _, err := c.Get(url, httpclient.SetResult(&clusterResp)) if err != nil { + fmt.Println(fmt.Sprintf("err:%+v", err)) return nil, fmt.Errorf("Failed to get cluster, error: %s", err) } -- Gitee From c1636c2d4f3d6aaa568a50d0c43ca7ff08fed0e7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 21:59:56 +0800 Subject: [PATCH 0899/2647] optimize code Signed-off-by: liu deyi --- pkg/cli/initconfig/cmd/init.go | 5 ++++- pkg/shared/client/aslan/multicluster.go | 9 ++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 00add4ae..aa237763 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -204,7 +204,10 @@ func presetRole() error { } func createLocalCluster() error { - cluster, _ := aslan.New(config.AslanServiceAddress()).GetCluster() + cluster, err := aslan.New(config.AslanServiceAddress()).GetCluster() + if err != nil { + fmt.Println("err:", err) + } if cluster != nil { return nil } diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 356a711f..a29c709e 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -33,16 +33,15 @@ func (c *Client) AddLocalCluster() error { } type clusterResp struct { - Name string `json:"name"` - Status setting.K8SClusterStatus `json:"status"` - Local bool `json:"local"` + Name string `json:"name"` + Local bool `json:"local"` } func (c *Client) GetCluster() (*clusterResp, error) { url := fmt.Sprintf("/cluster/clusters/%s", setting.LocalClusterID) - clusterResp := new(clusterResp) - _, err := c.Get(url, httpclient.SetResult(&clusterResp)) + clusterResp := &clusterResp{} + _, err := c.Get(url, httpclient.SetResult(clusterResp)) if err != nil { fmt.Println(fmt.Sprintf("err:%+v", err)) return nil, fmt.Errorf("Failed to get cluster, error: %s", err) -- Gitee From d6ff5f40e5b641d2e271f7f86cc95ff797c32be5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 22:04:39 +0800 Subject: [PATCH 0900/2647] optimize code Signed-off-by: liu deyi --- pkg/cli/initconfig/cmd/init.go | 5 +---- pkg/shared/client/aslan/multicluster.go | 8 +++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index aa237763..00add4ae 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -204,10 +204,7 @@ func presetRole() error { } func createLocalCluster() error { - cluster, err := aslan.New(config.AslanServiceAddress()).GetCluster() - if err != nil { - fmt.Println("err:", err) - } + cluster, _ := aslan.New(config.AslanServiceAddress()).GetCluster() if cluster != nil { return nil } diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index a29c709e..fa18e673 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -4,21 +4,24 @@ import ( "fmt" "time" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type cluster struct { - ID string `json:"id"` + ID primitive.ObjectID `json:"id,omitempty"` Name string `json:"name"` Status setting.K8SClusterStatus `json:"status"` Local bool `json:"local"` } func (c *Client) AddLocalCluster() error { + oid, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) url := "/cluster/clusters" req := cluster{ - ID: setting.LocalClusterID, + ID: oid, Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), Local: true, Status: setting.Normal, @@ -43,7 +46,6 @@ func (c *Client) GetCluster() (*clusterResp, error) { clusterResp := &clusterResp{} _, err := c.Get(url, httpclient.SetResult(clusterResp)) if err != nil { - fmt.Println(fmt.Sprintf("err:%+v", err)) return nil, fmt.Errorf("Failed to get cluster, error: %s", err) } -- Gitee From 5ca61a8ad3d5e5b3297d38c3d4e991a9d23eef4c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 22:09:19 +0800 Subject: [PATCH 0901/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/multicluster/service/clusters.go | 5 +++++ pkg/shared/client/aslan/multicluster.go | 7 ++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index cb315872..928b7e61 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -22,6 +22,8 @@ import ( "regexp" "strings" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/gin-gonic/gin" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -173,6 +175,9 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K CreatedAt: args.CreatedAt, CreatedBy: args.CreatedBy, } + if args.ID != "" { + cluster.ID, _ = primitive.ObjectIDFromHex(args.ID) + } return s.CreateCluster(cluster, logger) } diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index fa18e673..9904bcfe 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -4,24 +4,21 @@ import ( "fmt" "time" - "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) type cluster struct { - ID primitive.ObjectID `json:"id,omitempty"` + ID string `json:"id,omitempty"` Name string `json:"name"` Status setting.K8SClusterStatus `json:"status"` Local bool `json:"local"` } func (c *Client) AddLocalCluster() error { - oid, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) url := "/cluster/clusters" req := cluster{ - ID: oid, + ID: setting.LocalClusterID, Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), Local: true, Status: setting.Normal, -- Gitee From ed1c347edac750fa473297aef19dd36fcc6e2cc1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 22:17:08 +0800 Subject: [PATCH 0902/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/repository/models/queue.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 973097c9..1ea52e5f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -126,7 +126,7 @@ type ConfigPayload struct { CustomDNSSupported bool `json:"custom_dns_supported"` HubServerAddr string DeployClusterID string - AesKey string `bson:"-" json:"aes_key"` + AesKey string `json:"aes_key"` RepoConfigs map[string]*RegistryNamespace @@ -137,7 +137,7 @@ type ConfigPayload struct { ResetCache bool `json:"reset_cache"` JenkinsBuildConfig JenkinsBuildConfig `json:"jenkins_build_config"` PrivateKeys []*PrivateKey `json:"private_keys"` - K8SClusters []*K8SClusterResp `bson:"-" json:"k8s_cluster"` + K8SClusters []*K8SClusterResp `json:"k8s_cluster"` } type AslanConfig struct { -- Gitee From 1b8ff0eb9b5a8345b625c60facf0cfdbb265107c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 22:34:04 +0800 Subject: [PATCH 0903/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/k8s_cluster.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 767d9449..59fc65c9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -62,9 +62,9 @@ type AdvancedConfig struct { } type NodeSelectorRequirement struct { - Key string `json:"key"` - Value []string `json:"value"` - Operator corev1.NodeSelectorOperator `json:"operator"` + Key string `json:"key" bson:"key"` + Value []string `json:"value" bson:"value"` + Operator corev1.NodeSelectorOperator `json:"operator" bson:"operator"` } func (K8SCluster) TableName() string { -- Gitee From 861f82db23c19343289d7f24b081e0640ee639ef Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 22:46:18 +0800 Subject: [PATCH 0904/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/multicluster/service/clusters.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 928b7e61..756e68cd 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -196,6 +196,7 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com AdvancedConfig: advancedConfig, Production: args.Production, } + logger.Infof("advancedConfig:%+v", cluster.AdvancedConfig) return s.UpdateCluster(id, cluster, logger) } -- Gitee From d8241665d1f9c955830db66ea5b6a15eca5c8ed8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 7 Dec 2021 22:52:35 +0800 Subject: [PATCH 0905/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/kube/service.go | 4 +++- pkg/microservice/aslan/core/multicluster/service/clusters.go | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 1b650fa5..97180eb0 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -23,6 +23,8 @@ import ( "strings" "text/template" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/zap" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -134,7 +136,7 @@ func (s *Service) UpdateCluster(id string, cluster *models.K8SCluster, logger *z return nil, e.ErrUpdateCluster.AddDesc(e.DuplicateClusterNameFound) } - + cluster.ID, _ = primitive.ObjectIDFromHex(id) err = s.coll.UpdateMutableFields(cluster) if err != nil { logger.Errorf("failed to update mutable fields %v", err) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 756e68cd..928b7e61 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -196,7 +196,6 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com AdvancedConfig: advancedConfig, Production: args.Production, } - logger.Infof("advancedConfig:%+v", cluster.AdvancedConfig) return s.UpdateCluster(id, cluster, logger) } -- Gitee From 24c8f2b0dd8a237d79b0449d2ef54ef928cf50b6 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 8 Dec 2021 10:33:30 +0800 Subject: [PATCH 0906/2647] add previous chart version api Signed-off-by: allenshen --- .../aslan/core/delivery/handler/router.go | 1 + .../aslan/core/delivery/handler/version.go | 10 + .../aslan/core/delivery/service/version.go | 319 +++++++++++------- .../aslan/core/environment/service/helm.go | 1 + .../aslan/core/service/service/helm.go | 6 - 5 files changed, 216 insertions(+), 121 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 7e9b1f48..7b58bcad 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -49,6 +49,7 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) deliveryRelease.POST("/helm/global-variables", ApplyDeliveryGlobalVariables) deliveryRelease.GET("/helm/charts", DownloadDeliveryChart) + deliveryRelease.GET("/helm/charts/version", GetChartVersionFromRepo) deliveryRelease.GET("/helm/charts/preview", PreviewGetDeliveryChart) deliveryRelease.GET("/helm/charts/filePath", GetDeliveryChartFilePath) deliveryRelease.GET("/helm/charts/fileContent", GetDeliveryChartFileContent) diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index c943e5d3..39a4e551 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -242,6 +242,16 @@ func DownloadDeliveryChart(c *gin.Context) { c.Data(http.StatusOK, "application/octet-stream", fileBytes) } +func GetChartVersionFromRepo(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + chartName := c.Query("chartName") + chartRepoName := c.Query("chartRepoName") + + ctx.Resp, ctx.Err = deliveryservice.GetChartVersions(chartName, chartRepoName) +} + func PreviewGetDeliveryChart(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 90cd83e4..7db6b6e7 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -29,10 +29,6 @@ import ( "sync" "time" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" - - workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" "github.com/hashicorp/go-multierror" @@ -47,16 +43,19 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/util/converter" + fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -101,8 +100,11 @@ type DeliveryVersionChartData struct { } type DeliveryChartData struct { - ChartData *CreateHelmDeliveryVersionChartData - ServiceObj *commonmodels.Service + ChartData *CreateHelmDeliveryVersionChartData + ServiceObj *commonmodels.Service + ProductService *commonmodels.ProductService + RenderChart *template.RenderChart + RenderSet *commonmodels.RenderSet } type DeliveryChartResp struct { @@ -177,6 +179,11 @@ type ServiceImageDetails struct { Registries []string } +type ChartVersionResp struct { + ChartName string `json:"chartName"` + ChartVersion string `json:"chartVersion"` +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { @@ -499,44 +506,22 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { } // pull image from currently using registry and push to specified repo -func extractImages(deliveryVersion *commonmodels.DeliveryVersion, serviceObj *commonmodels.Service, valuesMap map[string]interface{}, - targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { - - flatMap, err := converter.Flatten(valuesMap) - if err != nil { - return nil, err - } - - imagePathSpecs := make([]map[string]string, 0) - for _, container := range serviceObj.Containers { - imageSearchRule := &template.ImageSearchingRule{ - Repo: container.ImagePath.Repo, - Image: container.ImagePath.Image, - Tag: container.ImagePath.Tag, - } - pattern := imageSearchRule.GetSearchingPattern() - imagePathSpecs = append(imagePathSpecs, pattern) - } - - // find full image uris from values.yaml - imageUrls := make([]string, 0) - for _, spec := range imagePathSpecs { - imageUri, err := commonservice.GeneImageURI(spec, flatMap) - if err != nil { - return nil, err - } +func extractImages(productService *commonmodels.ProductService, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { - imageUrls = append(imageUrls, imageUri) + // find all images in one single chart + imageUrlsSet := sets.NewString() + for _, container := range productService.Containers { + imageUrlsSet.Insert(container.Image) } ret := &ServiceImageDetails{ - ServiceName: serviceObj.ServiceName, + ServiceName: productService.ServiceName, Images: make([]*ImageUrlDetail, 0), } registrySet := sets.NewString() - for _, imageUrl := range imageUrls { + for _, imageUrl := range imageUrlsSet.List() { registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) if err != nil { @@ -561,36 +546,17 @@ func extractImages(deliveryVersion *commonmodels.DeliveryVersion, serviceObj *co ret.Registries = registrySet.List() return ret, nil - - //for _, imageUrl := range imageUrls { - // registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) - // if err != nil { - // return nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) - // } - // registryUrl = strings.TrimSuffix(registryUrl, "/") - // - // imageName := commonservice.ExtractImageName(imageUrl) - // imageTag := commonservice.ExtractImageTag(imageUrl) - // imageNamespace := commonservice.ExtractRegistryNamespace(imageUrl) - // targetFullImageUri := imageUrl - // - // if registry, ok := registryMap[registryUrl]; ok { - // if registry.ID != targetRegistry.ID { - // targetFullImageUri = fmt.Sprintf("%s/%s/%s:%s", targetRegistry.RegAddr, targetRegistry.Namespace, imageName, imageTag) - // } - // } - // - // if err != nil { - // log.Errorf("failed to insert image distribute data, version: %s, image: %s, err: %s", deliveryVersion.Version, targetFullImageUri, err) - // return nil, fmt.Errorf("failed to insert image distribute data") - // } - //} - //return nil, nil } -func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, - targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { +// ensure chart files exist +func ensureChartFiles(chartData *DeliveryChartData) (string, error) { serviceObj := chartData.ServiceObj + revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) + deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) + if exists, _ := fsutil.DirExists(deliveryChartPath); exists { + return deliveryChartPath, nil + } + serviceName, revision := serviceObj.ServiceName, serviceObj.Revision basePath := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) if err := commonservice.PreloadServiceManifestsByRevision(basePath, serviceObj); err != nil { @@ -599,73 +565,97 @@ func handleSingleChart(deliveryVersion *commonmodels.DeliveryVersion, chartData basePath = config.LocalServicePath(serviceObj.ProductName, serviceName) if err = commonservice.PreLoadServiceManifests(basePath, serviceObj); err != nil { log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) - return nil, err + return "", err } } fullPath := filepath.Join(basePath, serviceObj.ServiceName) - revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) - deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) err := copy.Copy(fullPath, deliveryChartPath) if err != nil { - return nil, err + return "", err } - //load chart info from local storage - chartRequested, err := chartloader.Load(deliveryChartPath) + mergedValuesYaml, err := helmtool.MergeOverrideValues(chartData.RenderChart.ValuesYaml, chartData.RenderSet.DefaultValues, chartData.RenderChart.GetOverrideYaml(), chartData.RenderChart.OverrideValues) if err != nil { - return nil, errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) + return "", err + } + err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644) + if err != nil { + return "", errors.Wrapf(err, "failed to write values.yaml") } - //set version and values.yaml content - chartRequested.Metadata.Name = chartData.ChartData.ServiceName - chartRequested.Metadata.Version = chartData.ChartData.Version - chartRequested.Metadata.AppVersion = chartData.ChartData.Version + return deliveryChartPath, nil +} +func buildChartPackage(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, registryMap map[string]*commonmodels.RegistryNamespace) error { + serviceObj := chartData.ServiceObj + + deliveryChartPath, err := ensureChartFiles(chartData) + if err != nil { + return err + } + + valuesYamlData := make(map[string]interface{}) + valuesFilePath := filepath.Join(deliveryChartPath, setting.ValuesYaml) + valueYamlContent, err := os.ReadFile(valuesFilePath) + if err != nil { + return errors.Wrapf(err, "failed to read values.yaml for service %s", serviceObj.ServiceName) + } + err = yaml.Unmarshal(valueYamlContent, &valuesYamlData) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) + } + + // write values.yaml file before load if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly - valuesInfo := make(map[string]interface{}) if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) - return nil, errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) + return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) } - chartRequested.Values = valuesInfo + valueYamlContent = []byte(chartData.ChartData.ValuesYamlContent) } else if len(globalVariables) > 0 { // merge global variables - curValuesStr, err := yaml.Marshal(chartRequested.Values) - if err != nil { - return nil, errors.Wrapf(err, "failed to marshal values.yaml for service: %s", serviceObj.ServiceName) - } - chartRequested.Values, err = yamlutil.MergeAndUnmarshal([][]byte{curValuesStr, []byte(globalVariables)}) + valueYamlContent, err = yamlutil.Merge([][]byte{valueYamlContent, []byte(globalVariables)}) if err != nil { - return nil, errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) + return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) } } + err = os.WriteFile(valuesFilePath, valueYamlContent, 0644) + if err != nil { + return errors.Wrapf(err, "failed to write values.yaml file for service %s", serviceObj.ServiceName) + } - chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) + //load chart info from local storage + chartRequested, err := chartloader.Load(deliveryChartPath) if err != nil { - return nil, err + return errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) } - // parse docker image - imagesData, err := extractImages(deliveryVersion, serviceObj, chartRequested.Values, targetRegistry, registryMap) + //set metadata + chartRequested.Metadata.Name = chartData.ChartData.ServiceName + chartRequested.Metadata.Version = chartData.ChartData.Version + chartRequested.Metadata.AppVersion = chartData.ChartData.Version + + //create local chart package + chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) if err != nil { - return nil, errors.Wrapf(err, "failed to handle image") + return err } client, err := createChartRepoClient(chartRepo) if err != nil { - return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) + return errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) } - log.Infof("pushing %s to %s...\n", filepath.Base(chartPackagePath), chartRepo.URL) + log.Infof("pushing chart %s to %s...", filepath.Base(chartPackagePath), chartRepo.URL) resp, err := client.UploadChartPackage(chartPackagePath, false) if err != nil { - return nil, errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) } err = handlePushResponse(resp) if err != nil { - return nil, errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) + return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) } - return imagesData, nil + return nil } func handlePushResponse(resp *http.Response) error { @@ -719,26 +709,45 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. // validate chartInfo, make sure service is in environment // prepare data set for chart delivery func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productInfo *commonmodels.Product) (map[string]*DeliveryChartData, error) { + + renderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + Revision: productInfo.Render.Revision, + Name: productInfo.Render.Name, + }) + if err != nil { + return nil, fmt.Errorf("failed to find renderSet: %s, revision: %d", productInfo.Render.Name, productInfo.Render.Revision) + } + chartMap := make(map[string]*template.RenderChart) + for _, rChart := range renderSet.ChartInfos { + chartMap[rChart.ServiceName] = rChart + } + chartDataMap := make(map[string]*DeliveryChartData) serviceMap := productInfo.GetServiceMap() - for _, chartDta := range chartDatas { - if productService, ok := serviceMap[chartDta.ServiceName]; ok { + for _, chartData := range chartDatas { + if productService, ok := serviceMap[chartData.ServiceName]; ok { serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: chartDta.ServiceName, + ServiceName: chartData.ServiceName, Revision: productService.Revision, Type: setting.HelmDeployType, ProductName: productInfo.ProductName, }) if err != nil { - return nil, fmt.Errorf("failed to query service: %s", chartDta.ServiceName) + return nil, fmt.Errorf("failed to query service: %s", chartData.ServiceName) } - chartDataMap[chartDta.ServiceName] = &DeliveryChartData{ - ChartData: chartDta, - //ProductService: productService, - ServiceObj: serviceObj, + renderChart, ok := chartMap[chartData.ServiceName] + if !ok { + return nil, fmt.Errorf("can't find renderChart for service: %s", chartData.ServiceName) + } + chartDataMap[chartData.ServiceName] = &DeliveryChartData{ + ChartData: chartData, + RenderChart: renderChart, + ServiceObj: serviceObj, + ProductService: productService, + RenderSet: renderSet, } } else { - return nil, fmt.Errorf("service %s not found in environment", chartDta.ServiceName) + return nil, fmt.Errorf("service %s not found in environment", chartData.ServiceName) } } return chartDataMap, nil @@ -857,14 +866,6 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return fmt.Errorf("failed to query chart-repo info, productName: %s, repoName: %s", deliveryVersion.ProductName, args.ChartRepoName) } - targetRegistry, err := commonrepo.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{ - ID: args.ImageRegistryID, - }) - if err != nil { - log.Errorf("failed to query target image registry with ID: %s, err %s", args.ImageRegistryID, err) - return fmt.Errorf("faild to query target registry namespace") - } - registryMap, err := buildRegistryMap() if err != nil { return fmt.Errorf("failed to build registry map") @@ -878,14 +879,19 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - imagesData, err := handleSingleChart(deliveryVersion, cData, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) + err := buildChartPackage(cData, repoInfo, dir, args.GlobalVariables, registryMap) if err != nil { - logger.Errorf("failed to handle single chart data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + logger.Errorf("failed to build chart package, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) return } - imagesDataMap.Store(cData.ServiceObj.ServiceName, imagesData) - + imageData, err := extractImages(cData.ProductService, registryMap) + if err != nil { + logger.Errorf("failed to extract image data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + appendError(err) + return + } + imagesDataMap.Store(cData.ServiceObj.ServiceName, imageData) }(chartData) } wg.Wait() @@ -950,7 +956,7 @@ func taskFinished(status config.Status) bool { } func waitVersionDone(deliveryVersion *commonmodels.DeliveryVersion) { - waitTimeout := time.After(10 * time.Minute) + waitTimeout := time.After(60 * time.Minute * 2) for { select { case <-waitTimeout: @@ -1049,6 +1055,9 @@ func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, er if !taskFinished(artifactPackageArgs.TaskStatus) { allTaskDone = false } + if len(artifactPackageArgs.Error) > 0 { + errorList = multierror.Append(errorList, fmt.Errorf(artifactPackageArgs.Error)) + } } if allTaskDone { @@ -1334,6 +1343,86 @@ func preDownloadChart(projectName, versionName, chartName string, log *zap.Sugar return filePath, err } +func getIndexDownloader(client *cm.Client) helm.IndexDownloader { + return func() ([]byte, error) { + resp, err := client.DownloadFile("index.yaml") + if err != nil { + return nil, err + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, getChartmuseumError(b, resp.StatusCode) + } + return b, nil + } +} + +func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, error) { + + chartRepo, err := getChartRepoData(chartRepoName) + if err != nil { + return nil, fmt.Errorf("failed to query chart-repo info, repoName %s", chartRepoName) + } + + client, err := createChartRepoClient(chartRepo) + if err != nil { + return nil, errors.Wrapf(err, "failed to create chart repo client") + } + + resp, err := client.DownloadFile("index.yaml") + if err != nil { + return nil, errors.Wrapf(err, "failed to download index.yaml") + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") + } + + log.Infof("###### the raw data is %s", string(b)) + + index, err := helm.LoadIndex(b) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse index.yaml") + } + + chartNameList := strings.Split(chartName, ",") + chartNameSet := sets.NewString(chartNameList...) + + ret := make([]*ChartVersionResp, 0) + + for name, entry := range index.Entries { + if !chartNameSet.Has(name) { + continue + } + if len(entry) == 0 { + continue + } + latestEntry := entry[0] + ret = append(ret, &ChartVersionResp{ + ChartName: name, + ChartVersion: latestEntry.Version, + }) + } + + return ret, nil +} + func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 20f88738..8a2ea609 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -219,6 +219,7 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog go func(serviceName string, revision int64) { defer wg.Done() serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ProductName: productName, ServiceName: serviceName, Revision: revision, Type: setting.HelmDeployType, diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 7d0c214e..931bfa21 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -211,13 +211,7 @@ func GetFileContent(serviceName, productName string, param *GetFileContentParam, } if forDelivery { - fullPath := filepath.Join(base, serviceName) base = config.LocalDeliveryChartPathWithRevision(productName, serviceName, revision) - deliveryChartPath := filepath.Join(base, serviceName) - err := copy.Copy(fullPath, deliveryChartPath) - if err != nil { - return "", e.ErrFileContent.AddErr(err) - } } file := filepath.Join(base, serviceName, filePath, fileName) -- Gitee From 00efc07a79df64f9ba13ac33cab955d8914391ea Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 8 Dec 2021 10:49:53 +0800 Subject: [PATCH 0907/2647] optimize code Signed-off-by: allenshen --- .../core/common/repository/models/queue.go | 3 ++- .../aslan/core/workflow/handler/router.go | 4 ---- .../aslan/core/workflow/handler/workflow.go | 22 ------------------- .../service/workflow/artifact_task.go | 2 +- .../packager/core/service/service.go | 1 - .../service/taskplugin/artifact_package.go | 17 +++++++------- 6 files changed, 11 insertions(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 9d81c752..bda3be27 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -19,8 +19,9 @@ package models import ( "sync" - "github.com/koderover/zadig/pkg/microservice/aslan/config" "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" ) type Queue struct { diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index b8fde441..091767d4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -103,10 +103,6 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflow.POST("/:productName/auto", AutoCreateWorkflow) workflow.POST("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateWorkflow) - - // test api need to delete - workflow.POST("airifactTask", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateArtifactTask) - workflow.PUT("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) workflow.GET("/testName/:testName", ListTestWorkflows) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index d33f1a58..c73934a4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -90,28 +90,6 @@ func CreateWorkflow(c *gin.Context) { ctx.Err = workflow.CreateWorkflow(args, ctx.Logger) } -// TODO test code need to delete -func CreateArtifactTask(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(commonmodels.ArtifactPackageTaskArgs) - data, err := c.GetRawData() - if err != nil { - log.Errorf("CreateWorkflow c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateWorkflow json.Unmarshal err : %v", err) - } - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - if err := c.ShouldBindWith(&args, binding.JSON); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - - ctx.Err = workflow.CreateArtifactPackageTask(args, ctx.Logger) -} - // UpdateWorkflow update a workflow func UpdateWorkflow(c *gin.Context) { ctx := internalhandler.NewContext(c) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index 15c7b59f..00ca9b6c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -20,6 +20,7 @@ import ( "fmt" "sort" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -32,7 +33,6 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util" - "go.uber.org/zap" ) func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 043ac8c6..c35301fd 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -29,7 +29,6 @@ import ( "time" "github.com/pkg/errors" - "gopkg.in/yaml.v3" "github.com/koderover/zadig/pkg/microservice/packager/config" diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go index 2436728d..24fcb383 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -21,21 +21,20 @@ import ( "fmt" "time" - "github.com/koderover/zadig/pkg/shared/kube/wrapper" - "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/podexec" - "k8s.io/apimachinery/pkg/labels" - - "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" - "github.com/koderover/zadig/pkg/setting" + "go.uber.org/zap" "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/wrapper" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" - "go.uber.org/zap" - "sigs.k8s.io/controller-runtime/pkg/client" ) // InitializeArtifactPackagePlugin to ini -- Gitee From 8c79d67723e15114d9ed8b519b19510e7525306e Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:54:07 +0800 Subject: [PATCH 0908/2647] workflow (#693) Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index d87e15cf..a2f167ff 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -60,6 +60,7 @@ type Workflow struct { RecentFailedTask *TaskInfo `json:"recentFailedTask"` AverageExecutionTime float64 `json:"averageExecutionTime"` SuccessRate float64 `json:"successRate"` + Description string `json:"description,omitempty"` } type TaskInfo struct { @@ -538,6 +539,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] SchedulerEnabled: w.ScheduleEnabled, Schedules: w.Schedules, EnabledStages: stages, + Description: w.Description, }) workflowNames = append(workflowNames, w.Name) -- Gitee From 04def52a8f17cb1a1b8329876c11a70870c3e81b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 14:01:27 +0800 Subject: [PATCH 0909/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/kube/service.go | 1 - pkg/microservice/aslan/core/common/service/product.go | 2 +- pkg/microservice/aslan/core/multicluster/service/clusters.go | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 97180eb0..47aca935 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -24,7 +24,6 @@ import ( "text/template" "go.mongodb.org/mongo-driver/bson/primitive" - "go.uber.org/zap" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index e336b30d..b18548d7 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -21,7 +21,6 @@ import ( "time" "github.com/hashicorp/go-multierror" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "k8s.io/apimachinery/pkg/labels" @@ -36,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + client2 "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/updater" diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 928b7e61..6a9bed6d 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -22,9 +22,8 @@ import ( "regexp" "strings" - "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" -- Gitee From e3e70705146e2ae7c498d682d194af860af28812 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 14:05:02 +0800 Subject: [PATCH 0910/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/multicluster/service/clusters.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 6a9bed6d..a8380b37 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -94,16 +94,18 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error var res []*K8SCluster for _, c := range cs { - advancedConfig := new(AdvancedConfig) + var advancedConfig *AdvancedConfig if c.AdvancedConfig != nil { - advancedConfig.Strategy = c.AdvancedConfig.Strategy var nodeLabels []string for _, nodeSelectorRequirement := range c.AdvancedConfig.NodeLabels { for _, labelValue := range nodeSelectorRequirement.Value { nodeLabels = append(nodeLabels, fmt.Sprintf("%s:%s", nodeSelectorRequirement.Key, labelValue)) } } - advancedConfig.NodeLabels = nodeLabels + advancedConfig = &AdvancedConfig{ + Strategy: c.AdvancedConfig.Strategy, + NodeLabels: nodeLabels, + } } res = append(res, &K8SCluster{ ID: c.ID.Hex(), -- Gitee From 614ebc504bdd674fa7e7db3ac19929e770e0e0c8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 14:32:54 +0800 Subject: [PATCH 0911/2647] optimize code Signed-off-by: liu deyi --- .../core/multicluster/service/clusters.go | 80 +++++++------------ 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index a8380b37..1c615c6d 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -40,23 +40,16 @@ import ( var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) type K8SCluster struct { - ID string `json:"id,omitempty"` - Name string `json:"name"` - Tags []string `json:"tags"` - Description string `json:"description"` - Namespace string `json:"namespace"` - Info *commonmodels.K8SClusterInfo `json:"info,omitempty"` - AdvancedConfig *AdvancedConfig `json:"config,omitempty"` - Status setting.K8SClusterStatus `json:"status"` - Error string `json:"error"` - Yaml string `json:"yaml"` - Production bool `json:"production"` - CreatedAt int64 `json:"createdAt"` - CreatedBy string `json:"createdBy"` - Disconnected bool `json:"-"` - Token string `json:"token"` - Provider int8 `json:"provider"` - Local bool `json:"local"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + AdvancedConfig *AdvancedConfig `json:"config,omitempty"` + Status setting.K8SClusterStatus `json:"status"` + Production bool `json:"production"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Provider int8 `json:"provider"` + Local bool `json:"local"` } type AdvancedConfig struct { @@ -64,17 +57,8 @@ type AdvancedConfig struct { NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` } -func (k *K8SCluster) Clean() error { +func (k *K8SCluster) Validate() error { k.Name = strings.TrimSpace(k.Name) - tags := k.Tags - k.Tags = []string{} - for _, tag := range tags { - tag = strings.TrimSpace(tag) - if tag != "" { - k.Tags = append(k.Tags, tag) - } - } - k.Description = strings.TrimSpace(k.Description) if !namePattern.MatchString(k.Name) { @@ -96,16 +80,8 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error for _, c := range cs { var advancedConfig *AdvancedConfig if c.AdvancedConfig != nil { - var nodeLabels []string - for _, nodeSelectorRequirement := range c.AdvancedConfig.NodeLabels { - for _, labelValue := range nodeSelectorRequirement.Value { - nodeLabels = append(nodeLabels, fmt.Sprintf("%s:%s", nodeSelectorRequirement.Key, labelValue)) - } - } - advancedConfig = &AdvancedConfig{ - Strategy: c.AdvancedConfig.Strategy, - NodeLabels: nodeLabels, - } + advancedConfig = convertToAdvancedConfig(c.AdvancedConfig.NodeLabels) + advancedConfig.Strategy = c.AdvancedConfig.Strategy } res = append(res, &K8SCluster{ ID: c.ID.Hex(), @@ -130,10 +106,23 @@ func GetCluster(id string, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, return s.GetCluster(id, logger) } -func convertToNodeSelectorRequirements(args *K8SCluster) []*commonmodels.NodeSelectorRequirement { +func convertToAdvancedConfig(nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement) *AdvancedConfig { + var nodeLabels []string + for _, nodeSelectorRequirement := range nodeSelectorRequirements { + for _, labelValue := range nodeSelectorRequirement.Value { + nodeLabels = append(nodeLabels, fmt.Sprintf("%s:%s", nodeSelectorRequirement.Key, labelValue)) + } + } + advancedConfig := &AdvancedConfig{ + NodeLabels: nodeLabels, + } + return advancedConfig +} + +func convertToNodeSelectorRequirements(args *AdvancedConfig) []*commonmodels.NodeSelectorRequirement { var nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement nodeLabelM := make(map[string][]string) - for _, nodeLabel := range args.AdvancedConfig.NodeLabels { + for _, nodeLabel := range args.NodeLabels { if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { continue } @@ -156,21 +145,14 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K advancedConfig := new(commonmodels.AdvancedConfig) if args.AdvancedConfig != nil { advancedConfig.Strategy = args.AdvancedConfig.Strategy - advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args) + advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig) } cluster := &commonmodels.K8SCluster{ Name: args.Name, - Tags: args.Tags, Description: args.Description, - Namespace: args.Namespace, - Info: args.Info, AdvancedConfig: advancedConfig, Status: args.Status, - Error: args.Error, - Yaml: args.Yaml, Production: args.Production, - Disconnected: args.Disconnected, - Token: args.Token, Provider: args.Provider, Local: args.Local, CreatedAt: args.CreatedAt, @@ -187,13 +169,11 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com advancedConfig := new(commonmodels.AdvancedConfig) if args.AdvancedConfig != nil { advancedConfig.Strategy = args.AdvancedConfig.Strategy - advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args) + advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig) } cluster := &commonmodels.K8SCluster{ Name: args.Name, - Tags: args.Tags, Description: args.Description, - Namespace: args.Namespace, AdvancedConfig: advancedConfig, Production: args.Production, } -- Gitee From ca118a4c3e7a8877e3d266686912f55556d09e95 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Wed, 8 Dec 2021 14:35:22 +0800 Subject: [PATCH 0912/2647] add links for api gateway component gloo,opa,dex --- System-Architecture-Overview-zh-CN.md | 6 +++--- System-Architecture-Overview.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/System-Architecture-Overview-zh-CN.md b/System-Architecture-Overview-zh-CN.md index 4f10c078..dcef018d 100644 --- a/System-Architecture-Overview-zh-CN.md +++ b/System-Architecture-Overview-zh-CN.md @@ -12,10 +12,10 @@ - Zadig Toolkit:vscode 开发者插件 API 网关: -- Gloo Edge:Zadig 的 API 网关组件 -- OPA:认证和授权组件 +- [Gloo Edge](https://github.com/solo-io/gloo):Zadig 的 API 网关组件 +- [OPA](https://github.com/open-policy-agent/opa):认证和授权组件 +- [Dex](https://github.com/dexidp/dex):Zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD / LDAP / OAuth2 / GitHub / .. - User:用户管理,Token 生成 -- Dex:Zadig 的身份认证服务,用于连接其他第三方认证系统,比如 AD / LDAP / OAuth2 / GitHub / .. Zadig 核心业务: - Picket:数据聚合服务 diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index 61e05977..9c941499 100644 --- a/System-Architecture-Overview.md +++ b/System-Architecture-Overview.md @@ -12,10 +12,10 @@ User Interface: - Zadig Toolkit:vscode plugin API Gateway: -- Gloo Edge:Zadig API gateway -- OPA:Authentication and authorization +- [Gloo Edge](https://github.com/solo-io/gloo):Zadig API gateway +- [OPA](https://github.com/open-policy-agent/opa):Authentication and authorization +- [Dex](https://github.com/dexidp/dex):Identity service for Zadig, acts as a portal to other identity providers like AD / LDAP / OAuth2 / GitHub / .. - User:User management, token generation -- Dex:Identity service for Zadig, acts as a portal to other identity providers like AD / LDAP / OAuth2 / GitHub / .. Zadig Core: - Picket:backend for frontend service. -- Gitee From ef6fd727f85748d1e90ddcd952f27c256aeecea8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 14:47:29 +0800 Subject: [PATCH 0913/2647] optimize code Signed-off-by: liu deyi --- .../core/multicluster/service/clusters.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 1c615c6d..6e606700 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -57,7 +57,7 @@ type AdvancedConfig struct { NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` } -func (k *K8SCluster) Validate() error { +func (k *K8SCluster) Clean() error { k.Name = strings.TrimSpace(k.Name) k.Description = strings.TrimSpace(k.Description) @@ -80,8 +80,10 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error for _, c := range cs { var advancedConfig *AdvancedConfig if c.AdvancedConfig != nil { - advancedConfig = convertToAdvancedConfig(c.AdvancedConfig.NodeLabels) - advancedConfig.Strategy = c.AdvancedConfig.Strategy + advancedConfig = &AdvancedConfig{ + Strategy: c.AdvancedConfig.Strategy, + NodeLabels: convertToNodeLabels(c.AdvancedConfig.NodeLabels), + } } res = append(res, &K8SCluster{ ID: c.ID.Hex(), @@ -106,23 +108,21 @@ func GetCluster(id string, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, return s.GetCluster(id, logger) } -func convertToAdvancedConfig(nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement) *AdvancedConfig { +func convertToNodeLabels(nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement) []string { var nodeLabels []string for _, nodeSelectorRequirement := range nodeSelectorRequirements { for _, labelValue := range nodeSelectorRequirement.Value { nodeLabels = append(nodeLabels, fmt.Sprintf("%s:%s", nodeSelectorRequirement.Key, labelValue)) } } - advancedConfig := &AdvancedConfig{ - NodeLabels: nodeLabels, - } - return advancedConfig + + return nodeLabels } -func convertToNodeSelectorRequirements(args *AdvancedConfig) []*commonmodels.NodeSelectorRequirement { +func convertToNodeSelectorRequirements(nodeLabels []string) []*commonmodels.NodeSelectorRequirement { var nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement nodeLabelM := make(map[string][]string) - for _, nodeLabel := range args.NodeLabels { + for _, nodeLabel := range nodeLabels { if !strings.Contains(nodeLabel, ":") || len(strings.Split(nodeLabel, ":")) != 2 { continue } @@ -145,7 +145,7 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K advancedConfig := new(commonmodels.AdvancedConfig) if args.AdvancedConfig != nil { advancedConfig.Strategy = args.AdvancedConfig.Strategy - advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig) + advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels) } cluster := &commonmodels.K8SCluster{ Name: args.Name, @@ -169,7 +169,7 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com advancedConfig := new(commonmodels.AdvancedConfig) if args.AdvancedConfig != nil { advancedConfig.Strategy = args.AdvancedConfig.Strategy - advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig) + advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels) } cluster := &commonmodels.K8SCluster{ Name: args.Name, -- Gitee From 437297aecf6a0f0ed4f2e467b07e5051b0f51e3c Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 8 Dec 2021 15:54:42 +0800 Subject: [PATCH 0914/2647] add 1.7.1 version define (#701) Signed-off-by: allenshen --- pkg/cli/upgradeassistant/internal/upgradepath/versions.go | 2 ++ version/version.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index 0304cee0..cdc71233 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -30,6 +30,7 @@ const ( V150 V160 V170 + V171 ) var versionMap versions = map[string]int{ @@ -39,6 +40,7 @@ var versionMap versions = map[string]int{ "1.5.0": V150, "1.6.0": V160, "1.7.0": V170, + "1.7.1": V171, } type versions map[string]int diff --git a/version/version.go b/version/version.go index 90ad6975..1faa9cfc 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.7.0" + Version = "1.7.1" BuildNumber = "0" GitCommit = "" ) -- Gitee From 0d951dfcbfd323e13f0cc7753856143b16e09cb3 Mon Sep 17 00:00:00 2001 From: lou Date: Wed, 8 Dec 2021 16:22:55 +0800 Subject: [PATCH 0915/2647] do not panic for remote cluster Signed-off-by: lou --- pkg/tool/kube/client/cluster.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/tool/kube/client/cluster.go b/pkg/tool/kube/client/cluster.go index 927cd411..d4b70be0 100644 --- a/pkg/tool/kube/client/cluster.go +++ b/pkg/tool/kube/client/cluster.go @@ -38,7 +38,11 @@ var c cluster.Cluster // Cluster is a singleton, it will be initialized only once. func Cluster() cluster.Cluster { once.Do(func() { - c = initCluster(ctrl.GetConfigOrDie()) + var err error + c, err = initCluster(ctrl.GetConfigOrDie()) + if err != nil { + panic(err) + } }) return c @@ -77,7 +81,10 @@ func NewClientFromAPIConfig(cfg *api.Config) (client.Client, error) { return nil, err } - cls := initCluster(restConfig) + cls, err := initCluster(restConfig) + if err != nil { + return nil, err + } return newAPIClient(cls.GetClient(), cls.GetAPIReader()), nil } @@ -105,7 +112,7 @@ func (c *apiClient) List(ctx context.Context, list client.ObjectList, opts ...cl return c.apiReader.List(ctx, list, opts...) } -func initCluster(restConfig *rest.Config) cluster.Cluster { +func initCluster(restConfig *rest.Config) (cluster.Cluster, error) { scheme := runtime.NewScheme() // add all known types @@ -116,8 +123,8 @@ func initCluster(restConfig *rest.Config) cluster.Cluster { clusterOptions.Scheme = scheme }) if err != nil { - panic(errors.Wrap(err, "unable to init client")) + return nil, errors.Wrap(err, "unable to init client") } - return c + return c, nil } -- Gitee From 3a90e9af8ccff41a76847a8990080d9d0bad686b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 16:24:19 +0800 Subject: [PATCH 0916/2647] optimize code Signed-off-by: liu deyi --- pkg/cli/initconfig/cmd/init.go | 5 +++- .../common/repository/models/k8s_cluster.go | 2 +- .../common/repository/mongodb/k8s_cluster.go | 14 +++++++--- .../aslan/core/common/service/environment.go | 4 +-- .../aslan/core/common/service/kube/service.go | 27 +++++++++---------- .../aslan/core/common/service/product.go | 4 +-- .../core/environment/service/configmap.go | 8 +++--- .../core/environment/service/environment.go | 8 +++--- .../service/environment_creator.go | 6 ++--- .../environment/service/environment_group.go | 4 +-- .../aslan/core/environment/service/export.go | 4 +-- .../aslan/core/environment/service/image.go | 4 +-- .../aslan/core/environment/service/k8s.go | 4 +-- .../aslan/core/environment/service/kube.go | 14 +++++----- .../aslan/core/environment/service/service.go | 14 +++++----- .../aslan/core/environment/service/types.go | 5 ++++ .../aslan/core/log/service/sse.go | 6 ++--- .../core/multicluster/service/clusters.go | 8 ++---- .../aslan/core/service/service/service.go | 6 ++--- .../aslan/core/system/service/registry.go | 6 ++--- .../service/workflow/pipeline_validation.go | 4 +-- .../core/service/taskplugin/build.go | 4 +-- .../core/service/taskplugin/deploy.go | 6 ++--- .../warpdrive/core/service/taskplugin/job.go | 4 +-- .../core/service/taskplugin/testing.go | 4 +-- pkg/shared/client/aslan/multicluster.go | 11 ++++---- pkg/shared/kube/resource/node.go | 5 ---- 27 files changed, 98 insertions(+), 93 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 00add4ae..b27f0ed8 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -204,7 +204,10 @@ func presetRole() error { } func createLocalCluster() error { - cluster, _ := aslan.New(config.AslanServiceAddress()).GetCluster() + cluster, err := aslan.New(config.AslanServiceAddress()).GetLocalCluster() + if err != nil { + return err + } if cluster != nil { return nil } diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 59fc65c9..309ead9a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -44,7 +44,7 @@ type K8SCluster struct { } type K8SClusterResp struct { - ID string `json:"id,omitempty" bson:"_id,omitempty"` + ID string `json:"id" bson:"_id,omitempty"` Name string `json:"name" bson:"name"` AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 207d8c2a..8d6c5acd 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -67,7 +67,10 @@ func (c *K8SClusterColl) Delete(id string) error { return err } -func (c *K8SClusterColl) Create(cluster *models.K8SCluster) error { +func (c *K8SClusterColl) Create(cluster *models.K8SCluster, id string) error { + if id != "" { + cluster.ID, _ = primitive.ObjectIDFromHex(id) + } res, err := c.InsertOne(context.TODO(), cluster) if oid, ok := res.InsertedID.(primitive.ObjectID); ok { cluster.ID = oid @@ -177,8 +180,13 @@ func (c *K8SClusterColl) FindByName(name string) (*models.K8SCluster, error) { return res, err } -func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster) error { - _, err := c.UpdateOne(context.TODO(), +func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster, id string) error { + var err error + cluster.ID, err = primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + _, err = c.UpdateOne(context.TODO(), bson.M{"_id": cluster.ID}, bson.M{"$set": bson.M{ "name": cluster.Name, "description": cluster.Description, diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index ae4c0198..3d4a41ea 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -36,7 +36,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" @@ -213,7 +213,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Infof("Start to list workloads in namespace %s", namespace) var resp = make([]*ServiceResp, 0) - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 47aca935..d31ee877 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -23,7 +23,6 @@ import ( "strings" "text/template" - "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -33,23 +32,23 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/multicluster" ) func GetKubeAPIReader(clusterID string) (client.Reader, error) { - return client2.GetKubeAPIReader(config.HubServerAddress(), clusterID) + return kubeclient.GetKubeAPIReader(config.HubServerAddress(), clusterID) } func GetRESTConfig(clusterID string) (*rest.Config, error) { - return client2.GetRESTConfig(config.HubServerAddress(), clusterID) + return kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) } // GetClientset returns a client to interact with APIServer which implements kubernetes.Interface func GetClientset(clusterID string) (kubernetes.Interface, error) { - return client2.GetClientset(config.HubServerAddress(), clusterID) + return kubeclient.GetClientset(config.HubServerAddress(), clusterID) } type Service struct { @@ -95,20 +94,18 @@ func (s *Service) ListClusters(clusterType string, logger *zap.SugaredLogger) ([ return clusters, nil } -func (s *Service) CreateCluster(cluster *models.K8SCluster, logger *zap.SugaredLogger) (*models.K8SCluster, error) { +func (s *Service) CreateCluster(cluster *models.K8SCluster, id string, logger *zap.SugaredLogger) (*models.K8SCluster, error) { _, err := s.coll.FindByName(cluster.Name) - if err == nil { logger.Errorf("failed to create cluster %s %v", cluster.Name, err) return nil, e.ErrCreateCluster.AddDesc(e.DuplicateClusterNameFound) } - - if cluster.Status == "" { - cluster.Status = setting.Pending + cluster.Status = setting.Pending + if id == setting.LocalClusterID { + cluster.Status = setting.Normal + cluster.Local = true } - - err = s.coll.Create(cluster) - + err = s.coll.Create(cluster, id) if err != nil { return nil, e.ErrCreateCluster.AddErr(err) } @@ -135,8 +132,8 @@ func (s *Service) UpdateCluster(id string, cluster *models.K8SCluster, logger *z return nil, e.ErrUpdateCluster.AddDesc(e.DuplicateClusterNameFound) } - cluster.ID, _ = primitive.ObjectIDFromHex(id) - err = s.coll.UpdateMutableFields(cluster) + + err = s.coll.UpdateMutableFields(cluster, id) if err != nil { logger.Errorf("failed to update mutable fields %v", err) return nil, e.ErrUpdateCluster.AddErr(err) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index b18548d7..4e4249b9 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -35,7 +35,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/updater" @@ -53,7 +53,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return e.ErrDeleteEnv.AddDesc("not found") } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrDeleteEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index a3d63401..1982dea0 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -33,7 +33,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -100,7 +100,7 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*configM if err != nil { return nil, e.ErrListConfigMaps.AddErr(err) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return nil, e.ErrListConfigMaps.AddErr(err) } @@ -155,7 +155,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } @@ -232,7 +232,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 822e93fe..64e26f00 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -54,7 +54,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" @@ -277,7 +277,7 @@ func AutoUpdateProduct(envNames []string, productName, requestID string, force b continue } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), p.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), p.ClusterID) if err != nil { log.Errorf("Failed to get kube client for %s, error: %v", productName, err) continue @@ -389,7 +389,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), existedProd.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), existedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } @@ -533,7 +533,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k return e.ErrUpdateEnv.AddDesc(e.EnvNotFoundErrMsg) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 62d50807..15c6b155 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -30,7 +30,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -121,7 +121,7 @@ func newHelmProductCreator() *HelmProductCreator { } func (creator *HelmProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), args.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { log.Errorf("[%s][%s] GetKubeClient error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) @@ -278,7 +278,7 @@ func newDefaultProductCreator() *DefaultProductCreator { } func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), args.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { return e.ErrCreateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index ab8fa01c..ac0fabe6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -30,7 +30,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" @@ -67,7 +67,7 @@ func ListGroups(serviceName, envName, productName string, perPage, page int, log //将获取到的所有服务按照名称进行排序 sort.SliceStable(allServices, func(i, j int) bool { return allServices[i].ServiceName < allServices[j].ServiceName }) - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, productName, err) return resp, count, e.ErrListGroups.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/export.go b/pkg/microservice/aslan/core/environment/service/export.go index 29d6589b..acf9aa6b 100644 --- a/pkg/microservice/aslan/core/environment/service/export.go +++ b/pkg/microservice/aslan/core/environment/service/export.go @@ -24,7 +24,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" ) @@ -40,7 +40,7 @@ func ExportYaml(envName, productName, serviceName string, log *zap.SugaredLogger } namespace := env.Namespace - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), env.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), env.ClusterID) if err != nil { log.Errorf("cluster is not connected [%s][%s][%s]", env.EnvName, env.ProductName, env.ClusterID) return res diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 44f61a71..e6d344b0 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -34,7 +34,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/updater" @@ -293,7 +293,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log } namespace := product.Namespace - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 5b6e998e..24e4f5a1 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -34,7 +34,7 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -83,7 +83,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return errors.New(e.UpsertServiceErrMsg) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index bfdc4c13..bae1bc9d 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -32,7 +32,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" @@ -113,7 +113,7 @@ func ListPodEvents(envName, productName, podName string, log *zap.SugaredLogger) // ListAvailableNamespaces lists available namespaces created by non-koderover func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { resp := make([]*resource.Namespace, 0) - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListNamespaces clusterID:%s err:%v", clusterID, err) return resp, err @@ -150,7 +150,7 @@ func ListServicePods(productName, envName string, serviceName string, log *zap.S if err != nil { return res, e.ErrListServicePod.AddErr(err) } - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return res, e.ErrListServicePod.AddErr(err) } @@ -177,7 +177,7 @@ func DeletePod(envName, productName, podName string, log *zap.SugaredLogger) err if err != nil { return e.ErrDeletePod.AddErr(err) } - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return e.ErrDeletePod.AddErr(err) } @@ -228,9 +228,9 @@ func getModifiedServiceFromObjectMeta(om metav1.Object) *serviceInfo { } } -func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*resource.NodeResp, error) { - resp := new(resource.NodeResp) - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) +func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*NodeResp, error) { + resp := new(NodeResp) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("ListAvailableNodes clusterID:%s err:%s", clusterID, err) return resp, err diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 70593037..572d6d87 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -33,7 +33,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" internalresource "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" @@ -61,7 +61,7 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap return e.ErrScaleService.AddErr(err) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return e.ErrScaleService.AddErr(err) } @@ -112,7 +112,7 @@ func Scale(args *ScaleArgs, logger *zap.SugaredLogger) error { return e.ErrScaleService.AddErr(err) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return e.ErrScaleService.AddErr(err) } @@ -142,7 +142,7 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { return err } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return err } @@ -178,7 +178,7 @@ func GetService(envName, productName, serviceName string, workLoadType string, l return nil, e.ErrGetService.AddErr(err) } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), env.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), env.ClusterID) if err != nil { return nil, e.ErrGetService.AddErr(err) } @@ -324,7 +324,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e if err != nil { return err } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), productObj.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productObj.ClusterID) if err != nil { return err } @@ -423,7 +423,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { return "", err } diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index 60942bf8..f3c0af94 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -115,3 +115,8 @@ type ContainerNotFound struct { func (c *ContainerNotFound) Error() string { return fmt.Sprintf("serviceName:%s,container:%s", c.ServiceName, c.Container) } + +type NodeResp struct { + Nodes []*internalresource.Node `json:"data"` + Labels []string `json:"labels"` +} diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index f66547d7..384a27d5 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -32,7 +32,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/watcher" @@ -175,7 +175,7 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la defer cancel() log.Debugf("Waiting until pod is running before establishing the stream.") - clientSet, err := client.GetClientset(config.HubServerAddress(), options.ClusterID) + clientSet, err := kubeclient.GetClientset(config.HubServerAddress(), options.ClusterID) if err != nil { log.Errorf("GetContainerLogs, get client set error: %s", err) return @@ -185,7 +185,7 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la log.Errorf("GetContainerLogs, wait pod running error: %s", err) return } - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), options.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), options.ClusterID) if err != nil { log.Errorf("GetContainerLogs, get kube client error: %s", err) return diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 6e606700..f9815cf3 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -23,7 +23,6 @@ import ( "strings" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/sets" @@ -154,14 +153,11 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K Status: args.Status, Production: args.Production, Provider: args.Provider, - Local: args.Local, CreatedAt: args.CreatedAt, CreatedBy: args.CreatedBy, } - if args.ID != "" { - cluster.ID, _ = primitive.ObjectIDFromHex(args.ID) - } - return s.CreateCluster(cluster, logger) + + return s.CreateCluster(cluster, args.ID, logger) } func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 39cadff5..1bd83639 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -46,7 +46,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" "github.com/koderover/zadig/pkg/tool/httpclient" @@ -256,7 +256,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv } func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []models.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), clusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s] error: %v", namespace, err) return err @@ -385,7 +385,7 @@ type UpdateWorkloadsArgs struct { } func UpdateWorkloads(ctx context.Context, requestID, username, productName, envName string, args UpdateWorkloadsArgs, log *zap.SugaredLogger) error { - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), args.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { log.Errorf("[%s] error: %s", args.Namespace, err) return err diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 3ab20ad3..a4c22609 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -28,7 +28,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util" ) @@ -87,7 +87,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa } for _, env := range envs { go func(prod *commonmodels.Product) { - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return @@ -162,7 +162,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam } for _, env := range envs { go func(prod *commonmodels.Product) { - kubeClient, err := client.GetKubeClient(config.HubServerAddress(), prod.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) if err != nil { log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 183580ed..1c33ddcc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -45,7 +45,7 @@ import ( git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -688,7 +688,7 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - kubeClient, err := client2.GetKubeClient(config.HubServerAddress(), product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return "", err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 687c471f..0649e215 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -32,7 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -110,7 +110,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace - kubeClient, err := client2.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index e65e5f06..3c5d364d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -39,7 +39,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" @@ -202,13 +202,13 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * }() if pipelineTask.ConfigPayload.DeployClusterID != "" { - p.restConfig, err = client2.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) + p.restConfig, err = kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) if err != nil { err = errors.WithMessage(err, "can't get k8s rest config") return } - p.kubeClient, err = client2.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) + p.kubeClient, err = kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) if err != nil { err = errors.WithMessage(err, "can't init k8s client") return diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 4fd3c6a9..b41d87fb 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -43,7 +43,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -98,7 +98,7 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st return pods[i].CreationTimestamp.Before(&pods[j].CreationTimestamp) }) - clientSet, err := client2.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, clusterID) + clientSet, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, clusterID) if err != nil { log.Errorf("saveContainerLog, get client set error: %s", err) return err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 76406a7d..8edc8526 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -34,7 +34,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - client2 "github.com/koderover/zadig/pkg/shared/kube/client" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" s3tool "github.com/koderover/zadig/pkg/tool/s3" @@ -107,7 +107,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace - kubeClient, err := client2.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) p.Log.Error(msg) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 9904bcfe..89ab4871 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -18,10 +18,8 @@ type cluster struct { func (c *Client) AddLocalCluster() error { url := "/cluster/clusters" req := cluster{ - ID: setting.LocalClusterID, - Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), - Local: true, - Status: setting.Normal, + ID: setting.LocalClusterID, + Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), } _, err := c.Post(url, httpclient.SetBody(req)) @@ -37,12 +35,15 @@ type clusterResp struct { Local bool `json:"local"` } -func (c *Client) GetCluster() (*clusterResp, error) { +func (c *Client) GetLocalCluster() (*clusterResp, error) { url := fmt.Sprintf("/cluster/clusters/%s", setting.LocalClusterID) clusterResp := &clusterResp{} _, err := c.Get(url, httpclient.SetResult(clusterResp)) if err != nil { + if httpclient.IsNotFound(err) { + return nil, nil + } return nil, fmt.Errorf("Failed to get cluster, error: %s", err) } diff --git a/pkg/shared/kube/resource/node.go b/pkg/shared/kube/resource/node.go index a78564c2..a0fb402d 100644 --- a/pkg/shared/kube/resource/node.go +++ b/pkg/shared/kube/resource/node.go @@ -5,8 +5,3 @@ type Node struct { Ready bool `json:"ready"` IP string `json:"node_ip"` } - -type NodeResp struct { - Nodes []*Node `json:"data"` - Labels []string `json:"labels"` -} -- Gitee From 9e80ed62ffd924f4374768beaa8eadff5c7b912c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 16:50:10 +0800 Subject: [PATCH 0917/2647] optimize code Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 89ab4871..6b73fa98 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -2,6 +2,7 @@ package aslan import ( "fmt" + "strings" "time" "github.com/koderover/zadig/pkg/setting" @@ -41,7 +42,7 @@ func (c *Client) GetLocalCluster() (*clusterResp, error) { clusterResp := &clusterResp{} _, err := c.Get(url, httpclient.SetResult(clusterResp)) if err != nil { - if httpclient.IsNotFound(err) { + if strings.Contains(err.Error(), "\"code\":6643") { return nil, nil } return nil, fmt.Errorf("Failed to get cluster, error: %s", err) -- Gitee From f035d45a0c6bc00c6a12432e81138dcd296a0240 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 16:55:00 +0800 Subject: [PATCH 0918/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/multicluster/service/clusters.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index f9815cf3..047cb9dc 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -141,10 +141,12 @@ func convertToNodeSelectorRequirements(nodeLabels []string) []*commonmodels.Node func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { s, _ := kube.NewService("") - advancedConfig := new(commonmodels.AdvancedConfig) + var advancedConfig *commonmodels.AdvancedConfig if args.AdvancedConfig != nil { - advancedConfig.Strategy = args.AdvancedConfig.Strategy - advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels) + advancedConfig = &commonmodels.AdvancedConfig{ + Strategy: args.AdvancedConfig.Strategy, + NodeLabels: convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels), + } } cluster := &commonmodels.K8SCluster{ Name: args.Name, -- Gitee From eed7e5aa9b7b8eb89e281e665691e0de4bed4b1f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 17:26:54 +0800 Subject: [PATCH 0919/2647] debug code Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 6b73fa98..1acdb464 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -40,8 +40,9 @@ func (c *Client) GetLocalCluster() (*clusterResp, error) { url := fmt.Sprintf("/cluster/clusters/%s", setting.LocalClusterID) clusterResp := &clusterResp{} - _, err := c.Get(url, httpclient.SetResult(clusterResp)) + resp, err := c.Get(url, httpclient.SetResult(clusterResp)) if err != nil { + fmt.Println(fmt.Sprintf("resp.body:%s", string(resp.Body()))) if strings.Contains(err.Error(), "\"code\":6643") { return nil, nil } -- Gitee From 7563dfee59049f85de506806bef344ea7959cf92 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 8 Dec 2021 17:30:20 +0800 Subject: [PATCH 0920/2647] update wd registry Signed-off-by: panxunying --- .../aslan/core/common/repository/models/queue.go | 2 ++ .../aslan/core/environment/service/environment.go | 4 +++- .../aslan/core/environment/service/product.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 12 ++++++++++++ .../core/service/taskplugin/artifact_deploy.go | 2 +- .../warpdrive/core/service/taskplugin/build.go | 2 +- .../warpdrive/core/service/taskplugin/job.go | 11 ++--------- .../warpdrive/core/service/taskplugin/testing.go | 2 +- .../warpdrive/core/service/types/task/build.go | 1 + .../core/service/types/task/config_payload.go | 2 ++ 10 files changed, 26 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 7801b2c4..edd8a89a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -136,6 +136,8 @@ type ConfigPayload struct { ResetCache bool `json:"reset_cache"` JenkinsBuildConfig JenkinsBuildConfig `json:"jenkins_build_config"` PrivateKeys []*PrivateKey `json:"private_keys"` + + RegistryID string `json:"registry_id"` } type AslanConfig struct { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 597af6de..dcecd7cc 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -93,6 +93,7 @@ type EnvResp struct { UpdateTime int64 `json:"updateTime"` IsPublic bool `json:"isPublic"` ClusterName string `json:"clusterName"` + ClusterID string `json:"cluster_id"` Production bool `json:"production"` Source string `json:"source"` RegistryID string `json:"registry_id"` @@ -213,6 +214,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) UpdateTime: env.UpdateTime, UpdateBy: env.UpdateBy, RegistryID: env.RegistryId, + ClusterID: env.ClusterID, }) } @@ -550,7 +552,7 @@ func UpdateProductRegistry(namespace, registryID string, log *zap.SugaredLogger) if err != nil { return e.ErrUpdateEnv.AddErr(err) } - err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryId, kubeClient, log) + err = ensureKubeEnv(exitedProd.Namespace, registryID, kubeClient, log) if err != nil { log.Errorf("UpdateProductRegistry ensureKubeEnv by namespace:%s,error: %v", namespace, err) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 44f63776..1429229a 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -175,7 +175,7 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %v", username, envName, productName, err) return nil, err } - prod.RegistryId = reg.ID.String() + prod.RegistryId = reg.ID.Hex() } resp := buildProductResp(prod.EnvName, prod, log) return resp, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index b0f58e47..c0284a02 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -494,6 +494,18 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) + if len(env.RegistryId) == 0 { + op := &commonrepo.FindRegOps{ + IsDefault: true, + } + reg, err := commonrepo.NewRegistryNamespaceColl().Find(op) + if err != nil { + log.Errorf("get default registry error: %v", err) + return nil, e.ErrGetCounter.AddDesc(err.Error()) + } + env.RegistryId = reg.ID.String() + } + configPayload.RegistryID = env.RegistryId repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err == nil { configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index b37306e7..e2dda26b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -237,7 +237,7 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T } // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 - if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { msg := fmt.Sprintf("create secret error: %v", err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 8374fdb0..ce9b2048 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -252,7 +252,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 - if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { msg := fmt.Sprintf("create secret error: %v", err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 4835fa81..6ac361c3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -514,14 +514,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa return job, nil } -func createOrUpdateRegistrySecrets(namespace string, registries []*task.RegistryNamespace, kubeClient client.Client) error { - defaultRegistry := &task.RegistryNamespace{ - RegAddr: config.DefaultRegistryAddr(), - AccessKey: config.DefaultRegistryAK(), - SecretKey: config.DefaultRegistrySK(), - } - registries = append(registries, defaultRegistry) - +func createOrUpdateRegistrySecrets(namespace, registryID string, registries []*task.RegistryNamespace, kubeClient client.Client) error { for _, reg := range registries { if reg.AccessKey == "" { continue @@ -533,7 +526,7 @@ func createOrUpdateRegistrySecrets(namespace string, registries []*task.Registry if reg.RegType != "" { secretName = namespaceInRegistry + "-" + reg.RegType + registrySecretSuffix } - if reg.RegAddr == config.DefaultRegistryAddr() { + if reg.ID == registryID { secretName = setting.DefaultImagePullSecret } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 07ab9732..7736b08f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -224,7 +224,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 - if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { p.Log.Errorf("create secret error: %v", err) } if err := updater.CreateJob(job, p.kubeClient); err != nil { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 42a36ee6..d3242050 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -86,6 +86,7 @@ type Install struct { } type RegistryNamespace struct { + ID string `bson:"_id,omitempty" json:"id,omitempty"` RegAddr string `bson:"reg_addr" json:"reg_addr"` RegType string `bson:"reg_type" json:"reg_type"` RegProvider string `bson:"reg_provider" json:"reg_provider"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 3b3e6e0a..b310c6be 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -49,6 +49,8 @@ type ConfigPayload struct { // ResetCache means ignore workspace cache ResetCache bool `json:"reset_cache"` PrivateKeys []*PrivateKey `json:"private_keys"` + + RegistryID string `json:"registry_id"` } func (cp *ConfigPayload) GetGitKnownHost() string { -- Gitee From 33b6c4cae7dfbf7413cc401dcb97e2ae3ab6c1e6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 17:40:45 +0800 Subject: [PATCH 0921/2647] optimize code Signed-off-by: liu deyi --- pkg/shared/client/aslan/multicluster.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 1acdb464..4e2f24da 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -1,8 +1,8 @@ package aslan import ( + "encoding/json" "fmt" - "strings" "time" "github.com/koderover/zadig/pkg/setting" @@ -36,14 +36,23 @@ type clusterResp struct { Local bool `json:"local"` } +type ErrorMessage struct { + Type string `json:"type"` + Code int `json:"code"` +} + func (c *Client) GetLocalCluster() (*clusterResp, error) { url := fmt.Sprintf("/cluster/clusters/%s", setting.LocalClusterID) clusterResp := &clusterResp{} resp, err := c.Get(url, httpclient.SetResult(clusterResp)) if err != nil { - fmt.Println(fmt.Sprintf("resp.body:%s", string(resp.Body()))) - if strings.Contains(err.Error(), "\"code\":6643") { + errorMessage := new(ErrorMessage) + err := json.Unmarshal(resp.Body(), errorMessage) + if err != nil { + return nil, fmt.Errorf("Failed to get cluster, error: %s", err) + } + if errorMessage.Code == 6643 { return nil, nil } return nil, fmt.Errorf("Failed to get cluster, error: %s", err) -- Gitee From 08b9346c2f55cda39cacce8fa2876b89e678d8c7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 18:16:12 +0800 Subject: [PATCH 0922/2647] debug code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 18 +++++++++--------- .../core/service/taskplugin/testing.go | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 0649e215..8c5d48ca 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -327,15 +327,15 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, // 清理用户取消和超时的任务 defer func() { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + //if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + // p.Log.Error(err) + // p.Task.Error = err.Error() + //} + // + //if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + // p.Log.Error(err) + // p.Task.Error = err.Error() + //} return }() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 8edc8526..424b8a5c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -270,14 +270,14 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv // 日志保存失败与否都清理job defer func() { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + //if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + // p.Log.Error(err) + // p.Task.Error = err.Error() + //} + //if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + // p.Log.Error(err) + // p.Task.Error = err.Error() + //} return }() -- Gitee From 3c7cf7701aadf3ff1d7e622b46028f2402c0820c Mon Sep 17 00:00:00 2001 From: huangzexiong Date: Wed, 8 Dec 2021 18:46:56 +0800 Subject: [PATCH 0923/2647] test add $SERVICES params Signed-off-by: huangzexiong --- .../workflow/service/workflow/workflow_task.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 06232049..d849ead7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1158,6 +1158,8 @@ func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTa // TODO 和validation中转化testsubtask合并为一个方法 func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log *zap.SugaredLogger) ([]*task.Testing, error) { var resp []*task.Testing + var servicesArray []string + var services string // 创建任务的测试参数为脱敏数据,需要转换为实际数据 for _, test := range args.Tests { @@ -1167,6 +1169,15 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } } + // 获取本次任务执行的服务,按照逗号分隔,service1,service2... + if len(args.Target) > 0 { + for _, service := range args.Target { + servicesArray = append(servicesArray, service.ServiceName) + } + services = strings.Replace(strings.Trim(fmt.Sprint(servicesArray), "[]"), " ", ",", -1) + log.Info("获取本次任务执行的服务services:%v", services) + } + testArgs := args.Tests testCreator := args.WorkflowTaskCreator @@ -1224,6 +1235,9 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } } envs = append(envs, &commonmodels.KeyVal{Key: "TEST_URL", Value: GetLink(pt, configbase.SystemAddress(), config.WorkflowType)}) + envs = append(envs, &commonmodels.KeyVal{Key: "SERVICES", Value: services}) + log.Info("test envs:%v", envs) + testTask.JobCtx.EnvVars = envs testTask.ImageID = testModule.PreTest.ImageID testTask.BuildOS = testModule.PreTest.BuildOS -- Gitee From d35d2210491bd38ecdf5f55df6bee0e258e9715c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 18:57:08 +0800 Subject: [PATCH 0924/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/environment/service/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index e1c19f18..b43e62a9 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -209,7 +209,7 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar prodResp.ClusterName = cluster.Name prodResp.IsLocal = cluster.Local - if !clusterService.ClusterConnected(prod.ClusterID) { + if !prodResp.IsLocal && !clusterService.ClusterConnected(prod.ClusterID) { prodResp.Status = setting.ClusterDisconnected prodResp.Error = "集群未连接" return prodResp -- Gitee From 9cf0667075757fd30e53f4b3bf80abb02085eee6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 8 Dec 2021 19:14:38 +0800 Subject: [PATCH 0925/2647] remove debug code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 18 +++++++++--------- .../core/service/taskplugin/testing.go | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 8c5d48ca..0649e215 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -327,15 +327,15 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, // 清理用户取消和超时的任务 defer func() { - //if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - // p.Log.Error(err) - // p.Task.Error = err.Error() - //} - // - //if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - // p.Log.Error(err) - // p.Task.Error = err.Error() - //} + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } return }() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 424b8a5c..8edc8526 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -270,14 +270,14 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv // 日志保存失败与否都清理job defer func() { - //if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - // p.Log.Error(err) - // p.Task.Error = err.Error() - //} - //if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - // p.Log.Error(err) - // p.Task.Error = err.Error() - //} + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } return }() -- Gitee From 2b3c2a140c52d6092ae8da952acea3ba4283dfe3 Mon Sep 17 00:00:00 2001 From: huangzexiong Date: Wed, 8 Dec 2021 19:20:55 +0800 Subject: [PATCH 0926/2647] test add $SERVICES params Signed-off-by: huangzexiong --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index d849ead7..01c72c37 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1174,7 +1174,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * for _, service := range args.Target { servicesArray = append(servicesArray, service.ServiceName) } - services = strings.Replace(strings.Trim(fmt.Sprint(servicesArray), "[]"), " ", ",", -1) + services = strings.Join(servicesArray,",") log.Info("获取本次任务执行的服务services:%v", services) } -- Gitee From a4ee227286d5df36d7e9a607fbfa7335a2512d36 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 10:08:30 +0800 Subject: [PATCH 0927/2647] optimize code Signed-off-by: liu deyi --- .../common/repository/models/k8s_cluster.go | 34 +++++++++---------- .../core/common/repository/models/queue.go | 2 +- .../aslan/core/environment/service/kube.go | 4 +-- .../core/multicluster/service/clusters.go | 4 +-- .../picket/client/aslan/kubeconfig.go | 16 +++++++++ pkg/microservice/picket/config/consts.go | 16 +++++++++ .../picket/core/filter/handler/kubeconfig.go | 16 +++++++++ .../picket/core/filter/service/kubeconfig.go | 16 +++++++++ .../warpdrive/core/service/taskplugin/job.go | 17 ---------- .../core/service/types/task/config_payload.go | 4 +-- pkg/shared/client/aslan/multicluster.go | 16 +++++++++ pkg/shared/kube/client/client.go | 17 ++++++++++ pkg/shared/kube/resource/node.go | 20 +++++++++-- 13 files changed, 139 insertions(+), 43 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 309ead9a..81668968 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -24,23 +24,23 @@ import ( ) type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` - Status setting.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Provider int8 `json:"provider" bson:"provider"` - Local bool `json:"local" bson:"local"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty" bson:"advanced_config,omitempty"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Provider int8 `json:"provider" bson:"provider"` + Local bool `json:"local" bson:"local"` } type K8SClusterResp struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 1ea52e5f..2286fb79 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -137,7 +137,7 @@ type ConfigPayload struct { ResetCache bool `json:"reset_cache"` JenkinsBuildConfig JenkinsBuildConfig `json:"jenkins_build_config"` PrivateKeys []*PrivateKey `json:"private_keys"` - K8SClusters []*K8SClusterResp `json:"k8s_cluster"` + K8SClusters []*K8SClusterResp `json:"k8s_clusters"` } type AslanConfig struct { diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index bae1bc9d..103d1c43 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -122,7 +122,7 @@ func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resou if err != nil { log.Errorf("ListNamespaces err:%v", err) if apierrors.IsForbidden(err) { - return resp, nil + return resp, err } return resp, err } @@ -240,7 +240,7 @@ func ListAvailableNodes(clusterID string, log *zap.SugaredLogger) (*NodeResp, er if err != nil { log.Errorf("ListNodes err:%s", err) if apierrors.IsForbidden(err) { - return resp, nil + return resp, err } return resp, err } diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 047cb9dc..dc7f954a 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -42,7 +42,7 @@ type K8SCluster struct { ID string `json:"id,omitempty"` Name string `json:"name"` Description string `json:"description"` - AdvancedConfig *AdvancedConfig `json:"config,omitempty"` + AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty"` Status setting.K8SClusterStatus `json:"status"` Production bool `json:"production"` CreatedAt int64 `json:"createdAt"` @@ -61,7 +61,7 @@ func (k *K8SCluster) Clean() error { k.Description = strings.TrimSpace(k.Description) if !namePattern.MatchString(k.Name) { - return fmt.Errorf("集群名称不符合规则") + return fmt.Errorf("The cluster name does not meet the rules") } return nil diff --git a/pkg/microservice/picket/client/aslan/kubeconfig.go b/pkg/microservice/picket/client/aslan/kubeconfig.go index b463271b..6373970a 100644 --- a/pkg/microservice/picket/client/aslan/kubeconfig.go +++ b/pkg/microservice/picket/client/aslan/kubeconfig.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( diff --git a/pkg/microservice/picket/config/consts.go b/pkg/microservice/picket/config/consts.go index 06e1924e..94410301 100644 --- a/pkg/microservice/picket/config/consts.go +++ b/pkg/microservice/picket/config/consts.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config type RulesLogicalOperator string diff --git a/pkg/microservice/picket/core/filter/handler/kubeconfig.go b/pkg/microservice/picket/core/filter/handler/kubeconfig.go index 5cac861f..365e75e5 100644 --- a/pkg/microservice/picket/core/filter/handler/kubeconfig.go +++ b/pkg/microservice/picket/core/filter/handler/kubeconfig.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/picket/core/filter/service/kubeconfig.go b/pkg/microservice/picket/core/filter/service/kubeconfig.go index 773fc8cb..b020f970 100644 --- a/pkg/microservice/picket/core/filter/service/kubeconfig.go +++ b/pkg/microservice/picket/core/filter/service/kubeconfig.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index b41d87fb..318f4454 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -612,11 +612,6 @@ func getVolumeMounts(ctx *task.PipelineCtx) []corev1.VolumeMount { Name: "job-config", MountPath: ctx.ConfigMapMountDir, }) - //resp = append(resp, corev1.VolumeMount{ - // Name: "aes-key", - // ReadOnly: true, - // MountPath: "/etc/encryption", - //}) return resp } @@ -633,18 +628,6 @@ func getVolumes(jobName string) []corev1.Volume { }, }, }) - //resp = append(resp, corev1.Volume{ - // Name: "aes-key", - // VolumeSource: corev1.VolumeSource{ - // Secret: &corev1.SecretVolumeSource{ - // SecretName: "zadig-aes-key", - // Items: []corev1.KeyToPath{{ - // Key: "aesKey", - // Path: "aes", - // }}, - // }, - // }, - //}) return resp } diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 10b76384..cd6fb626 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -52,7 +52,7 @@ type ConfigPayload struct { // ResetCache means ignore workspace cache ResetCache bool `json:"reset_cache"` PrivateKeys []*PrivateKey `json:"private_keys"` - K8SClusters []*K8SCluster `json:"k8s_cluster"` + K8SClusters []*K8SCluster `json:"k8s_clusters"` } func (cp *ConfigPayload) GetGitKnownHost() string { @@ -156,7 +156,7 @@ type PrivateKey struct { type K8SCluster struct { ID string `json:"id,omitempty" bson:"_id,omitempty"` Name string `json:"name" bson:"name"` - AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` + AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty" bson:"advanced_config,omitempty"` } type AdvancedConfig struct { diff --git a/pkg/shared/client/aslan/multicluster.go b/pkg/shared/client/aslan/multicluster.go index 4e2f24da..0611bcd8 100644 --- a/pkg/shared/client/aslan/multicluster.go +++ b/pkg/shared/client/aslan/multicluster.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( diff --git a/pkg/shared/kube/client/client.go b/pkg/shared/kube/client/client.go index dfb984b1..8374adb5 100644 --- a/pkg/shared/kube/client/client.go +++ b/pkg/shared/kube/client/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client import ( @@ -23,6 +39,7 @@ func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { } return multicluster.GetKubeAPIReader(hubServerAddr, clusterID) } + func GetRESTConfig(hubServerAddr, clusterID string) (*rest.Config, error) { if clusterID == setting.LocalClusterID { clusterID = "" diff --git a/pkg/shared/kube/resource/node.go b/pkg/shared/kube/resource/node.go index a0fb402d..733ef420 100644 --- a/pkg/shared/kube/resource/node.go +++ b/pkg/shared/kube/resource/node.go @@ -1,7 +1,23 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package resource type Node struct { - Labels []string `json:"node_labels"` + Labels []string `json:"labels"` Ready bool `json:"ready"` - IP string `json:"node_ip"` + IP string `json:"ip"` } -- Gitee From ee82522296b079e25aef49693d30101e35c59b50 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 11:01:35 +0800 Subject: [PATCH 0928/2647] optimize code Signed-off-by: liu deyi --- .../core/common/repository/mongodb/k8s_cluster.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 8d6c5acd..a9464a01 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -188,12 +188,12 @@ func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster, id stri } _, err = c.UpdateOne(context.TODO(), bson.M{"_id": cluster.ID}, bson.M{"$set": bson.M{ - "name": cluster.Name, - "description": cluster.Description, - "tags": cluster.Tags, - "namespace": cluster.Namespace, - "production": cluster.Production, - "config": cluster.AdvancedConfig, + "name": cluster.Name, + "description": cluster.Description, + "tags": cluster.Tags, + "namespace": cluster.Namespace, + "production": cluster.Production, + "advanced_config": cluster.AdvancedConfig, }}, ) -- Gitee From ec592a79b66677b1388c2c7f8b141086160a39fb Mon Sep 17 00:00:00 2001 From: YuanPei Li Date: Thu, 9 Dec 2021 11:55:39 +0800 Subject: [PATCH 0929/2647] fix the quick start link Signed-off-by: Ryan Li --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ba402d4..b004e68e 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ The Highlighted Features: ### How to use? -Please follow [Quick Start](https://docs.koderover.com/zadig/quick-start/try-out-install) +Please follow [Quick Start](https://docs.koderover.com/zadig/quick-start/try-out-install/) ### Bootcamps -- Gitee From d4a5c17a3b04765e6d2606403a241457c3049526 Mon Sep 17 00:00:00 2001 From: YuanPei Li Date: Thu, 9 Dec 2021 12:01:06 +0800 Subject: [PATCH 0930/2647] fix quick start link in zh-cn doc Signed-off-by: Ryan Li --- README-zh-CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-zh-CN.md b/README-zh-CN.md index 3bd629ba..cbfb9005 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -75,7 +75,7 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery ### 快速使用 -请参阅 [快速入门](https://docs.koderover.com/zadig/quick-start/try-out-install) +请参阅 [快速入门](https://docs.koderover.com/zadig/quick-start/try-out-install/) ### 训练营 -- Gitee From 55513efc6ac52902288b1036f364be072c03471f Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 9 Dec 2021 14:46:36 +0800 Subject: [PATCH 0931/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/repository/models/queue.go | 1 + pkg/microservice/aslan/core/common/service/product.go | 9 +++------ pkg/microservice/aslan/core/common/service/registry.go | 9 --------- .../aslan/core/environment/handler/environment.go | 7 ++++++- .../aslan/core/environment/service/environment.go | 8 ++++++-- .../aslan/core/environment/service/product.go | 4 ++-- pkg/microservice/aslan/core/system/handler/registry.go | 2 +- pkg/microservice/aslan/core/system/service/registry.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 3 +-- 9 files changed, 21 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index edd8a89a..f0d33f1e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -137,6 +137,7 @@ type ConfigPayload struct { JenkinsBuildConfig JenkinsBuildConfig `json:"jenkins_build_config"` PrivateKeys []*PrivateKey `json:"private_keys"` + // RegistryID is the id of product registry RegistryID string `json:"registry_id"` } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 8c72170d..6eed899e 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -353,17 +353,14 @@ func GetProductEnvNamespace(envName, productName, namespace string) string { return product.Namespace } -func GetProductEnv(envName, productName, namespace string) *models.Product { - if namespace != "" { - return nil - } +func GetProductEnv(envName, productName string) (*models.Product, error) { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, EnvName: envName, }) if err != nil { product = &commonmodels.Product{EnvName: envName, ProductName: productName} - return product + return nil, err } - return product + return product, nil } diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 4c0323aa..0c8d7dec 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -73,15 +73,6 @@ func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamesp return resp, nil } -func GetRegistryNamespace(registryID string, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { - resp, err := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{ID: registryID}) - if err != nil { - log.Errorf("get default registry error: %v", err) - return resp, fmt.Errorf("get default registry error: %v", err) - } - return resp, nil -} - func ListRegistryNamespaces(log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index ef9f41c3..414b549f 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -54,6 +54,11 @@ type NamespaceResource struct { Ingresses []resource.Ingress `json:"ingresses"` } +type UpdateProductRegistryRequest struct { + RegistryId string `json:"registry_id"` + Namespace string `json:"namespace"` +} + func ListProducts(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -256,7 +261,7 @@ func UpdateProductRegistry(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - args := new(commonmodels.Product) + args := new(UpdateProductRegistryRequest) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateProduct c.GetRawData() err : %v", err) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index dcecd7cc..3030a6b3 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -967,7 +967,11 @@ func checkOverrideValuesChange(source *template.RenderChart, args *commonservice } func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { - product := commonservice.GetProductEnv(envName, productName, "") + product, err := commonservice.GetProductEnv(envName, productName) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) + return err + } opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { @@ -1974,7 +1978,7 @@ func preCreateProduct(envName string, args *commonmodels.Product, kubeClient cli args.Render = tmpRenderInfo if preCreateNSAndSecret(productTmpl.ProductFeature) { - return ensureKubeEnv(args.Namespace, args.ID.String(), kubeClient, log) + return ensureKubeEnv(args.Namespace, args.RegistryId, kubeClient, log) } return nil } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 1429229a..be1f9738 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -158,7 +158,7 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("[User:%s][EnvName:%s][Product:%s] Product.FindByOwner error: %v", username, envName, productName, err) + log.Errorf("[User:%s][EnvName:%s][Product:%s] Product.FindByOwner error: %s", username, envName, productName, err) return nil, e.ErrGetEnv } @@ -172,7 +172,7 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( if len(prod.RegistryId) == 0 { reg, err := commonservice.FindDefaultRegistry(log) if err != nil { - log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %v", username, envName, productName, err) + log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %s", username, envName, productName, err) return nil, err } prod.RegistryId = reg.ID.Hex() diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 14c0cd37..b9a77ed8 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -64,7 +64,7 @@ func GetRegistryNamespace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - reg, err := commonservice.GetRegistryNamespace(c.Param("id"), ctx.Logger) + reg, err := commonservice.FindRegistryById(c.Param("id"), ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 794e4710..42babe6b 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -91,7 +91,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) return } - err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, args.ID.String(), kubeClient, log) + err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, args.ID.Hex(), kubeClient, log) if err != nil { log.Errorf("[updateRegistry] Failed to update registry secret for namespace: %s, the error is: %+v", prod.Namespace, err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c0284a02..b1f9aa72 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -503,7 +503,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, log.Errorf("get default registry error: %v", err) return nil, e.ErrGetCounter.AddDesc(err.Error()) } - env.RegistryId = reg.ID.String() + env.RegistryId = reg.ID.Hex() } configPayload.RegistryID = env.RegistryId repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) @@ -1806,7 +1806,6 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e var reg *commonmodels.RegistryNamespace if len(exitedProd.RegistryId) > 0 { reg, err = commonservice.FindRegistryById(exitedProd.RegistryId, log) - log.Infof("FindRegistryById %s", exitedProd.RegistryId) if err != nil { log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", exitedProd.RegistryId, err) -- Gitee From edf055925d7a07eaa62e7ecabd94239ffcb699d1 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 9 Dec 2021 15:42:37 +0800 Subject: [PATCH 0932/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/service/product.go | 12 ------------ .../aslan/core/environment/service/environment.go | 5 ++++- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 6eed899e..9986f357 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -352,15 +352,3 @@ func GetProductEnvNamespace(envName, productName, namespace string) string { } return product.Namespace } - -func GetProductEnv(envName, productName string) (*models.Product, error) { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: productName, - EnvName: envName, - }) - if err != nil { - product = &commonmodels.Product{EnvName: envName, ProductName: productName} - return nil, err - } - return product, nil -} diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 3030a6b3..b4ab56ca 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -967,7 +967,10 @@ func checkOverrideValuesChange(source *template.RenderChart, args *commonservice } func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { - product, err := commonservice.GetProductEnv(envName, productName) + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) if err != nil { log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) return err -- Gitee From 78ee7daeda0c4e547b9ab850492475b0c4ce58b4 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:54:07 +0800 Subject: [PATCH 0933/2647] workflow (#693) Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index d87e15cf..a2f167ff 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -60,6 +60,7 @@ type Workflow struct { RecentFailedTask *TaskInfo `json:"recentFailedTask"` AverageExecutionTime float64 `json:"averageExecutionTime"` SuccessRate float64 `json:"successRate"` + Description string `json:"description,omitempty"` } type TaskInfo struct { @@ -538,6 +539,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] SchedulerEnabled: w.ScheduleEnabled, Schedules: w.Schedules, EnabledStages: stages, + Description: w.Description, }) workflowNames = append(workflowNames, w.Name) -- Gitee From 2cde4971a2b9c3ead360c47594240ac7dacd8cb1 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 8 Dec 2021 15:54:42 +0800 Subject: [PATCH 0934/2647] add 1.7.1 version define (#701) Signed-off-by: allenshen Signed-off-by: mouuii --- pkg/cli/upgradeassistant/internal/upgradepath/versions.go | 2 ++ version/version.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index 0304cee0..cdc71233 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -30,6 +30,7 @@ const ( V150 V160 V170 + V171 ) var versionMap versions = map[string]int{ @@ -39,6 +40,7 @@ var versionMap versions = map[string]int{ "1.5.0": V150, "1.6.0": V160, "1.7.0": V170, + "1.7.1": V171, } type versions map[string]int diff --git a/version/version.go b/version/version.go index 90ad6975..1faa9cfc 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.7.0" + Version = "1.7.1" BuildNumber = "0" GitCommit = "" ) -- Gitee From 8a9988a884bd78dd57705506efa3d7b993e382fc Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 9 Dec 2021 15:42:05 +0800 Subject: [PATCH 0935/2647] fix panic bug & parse url Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 5e0a40c0..f8500b4e 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -128,8 +128,21 @@ func Callback(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() state := c.Query("state") + state , err := url.PathUnescape(state) + if err != nil { + ctx.Logger.Errorf("codehost callback PathUnescape err:%s",err) + url := fmt.Sprintf("%s&errMessage=failed_to_parse_redirect_url") + c.Redirect(http.StatusFound,url) + return + } urlArray := strings.Split(state, "&codeHostId=") frontEndUrl := urlArray[0] + if len(urlArray) != 2 { + ctx.Logger.Errorf("codehost get callback len !=2 , state%s",state) + url := fmt.Sprintf("%s?&errMessage=failed_to_paarse_redirect_url",frontEndUrl) + c.Redirect(http.StatusFound,url) + return + } if strings.Contains(frontEndUrl, "errCode") { frontEndUrl = strings.Split(frontEndUrl, "?errCode")[0] } -- Gitee From 812526d29417dd541bb7aec18bbe21a56adfd708 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 9 Dec 2021 15:51:44 +0800 Subject: [PATCH 0936/2647] fmt Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index f8500b4e..37f2df69 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -130,16 +130,16 @@ func Callback(c *gin.Context) { state := c.Query("state") state , err := url.PathUnescape(state) if err != nil { - ctx.Logger.Errorf("codehost callback PathUnescape err:%s",err) - url := fmt.Sprintf("%s&errMessage=failed_to_parse_redirect_url") + ctx.Logger.Errorf("code_host_get_call_back PathUnescape state:%s err : %s", state, err) + url := fmt.Sprintf("%s%s", state, "&errMessage=failed_to_parse_redirect_url") c.Redirect(http.StatusFound,url) return } urlArray := strings.Split(state, "&codeHostId=") frontEndUrl := urlArray[0] if len(urlArray) != 2 { - ctx.Logger.Errorf("codehost get callback len !=2 , state%s",state) - url := fmt.Sprintf("%s?&errMessage=failed_to_paarse_redirect_url",frontEndUrl) + ctx.Logger.Errorf("code_host_get_call_back split &codeHostId fail ,state: %s", state) + url := fmt.Sprintf("%s?%s", frontEndUrl, "&errMessage=failed_to_parse_redirect_url") c.Redirect(http.StatusFound,url) return } -- Gitee From 4e51c86e8ab01dcdb2ca9b67fa3ff8b9164dac16 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 16:10:59 +0800 Subject: [PATCH 0937/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/environment/service/product.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index b43e62a9..db210857 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -214,6 +214,8 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar prodResp.Error = "集群未连接" return prodResp } + } else { + prodResp.IsLocal = true } if prod.Status == setting.ProductStatusCreating { -- Gitee From 1d6f5ba7c508c9ee188fce91e713932900f91494 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 9 Dec 2021 17:14:56 +0800 Subject: [PATCH 0938/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/repository/models/product.go | 2 +- .../aslan/core/common/service/registry.go | 6 +++--- .../aslan/core/environment/handler/environment.go | 8 ++++---- .../aslan/core/environment/handler/router.go | 2 +- .../aslan/core/environment/service/environment.go | 14 +++++++------- .../environment/service/environment_creator.go | 2 +- .../aslan/core/environment/service/product.go | 6 +++--- .../aslan/core/system/handler/router.go | 2 +- .../workflow/service/workflow/workflow_task.go | 12 ++++++------ 9 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 2b290d88..af7ecc6b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -50,7 +50,7 @@ type Product struct { RecycleDay int `bson:"recycle_day" json:"recycle_day"` Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` - RegistryId string `bson:"registry_id" json:"registry_id"` + RegistryID string `bson:"registry_id" json:"registry_id"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` } diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 0c8d7dec..301e0224 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -31,10 +31,10 @@ import ( ) func FindRegistryById(registryId string, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { - return FindRegisty(&mongodb.FindRegOps{ID: registryId}, log) + return findRegisty(&mongodb.FindRegOps{ID: registryId}, log) } -func FindRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { +func findRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { // TODO: 多租户适配 resp, err := mongodb.NewRegistryNamespaceColl().Find(regOps) @@ -61,7 +61,7 @@ func FindRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.Re } func FindDefaultRegistry(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { - return FindRegisty(&mongodb.FindRegOps{IsDefault: true}, log) + return findRegisty(&mongodb.FindRegOps{IsDefault: true}, log) } func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 414b549f..c3ad692f 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -55,7 +55,7 @@ type NamespaceResource struct { } type UpdateProductRegistryRequest struct { - RegistryId string `json:"registry_id"` + RegistryID string `json:"registry_id"` Namespace string `json:"namespace"` } @@ -208,7 +208,7 @@ func CreateProduct(c *gin.Context) { return } - if args.RegistryId == "" { + if args.RegistryID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("RegistryId can not be null!") return } @@ -280,9 +280,9 @@ func UpdateProductRegistry(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Err = service.UpdateProductRegistry(args.Namespace, args.RegistryId, ctx.Logger) + ctx.Err = service.UpdateProductRegistry(args.Namespace, args.RegistryID, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product %s %s: %v", args.Namespace, args.RegistryId, ctx.Err) + ctx.Logger.Errorf("failed to update product %s %s: %v", args.Namespace, args.RegistryID, ctx.Err) } } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 3b0cd996..bf404434 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -95,7 +95,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { environments.GET("", ListProducts) environments.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProduct) - environments.PUT("/registry", gin2.UpdateOperationLogStatus, UpdateProductRegistry) + environments.PUT("/:name/registry", gin2.UpdateOperationLogStatus, UpdateProductRegistry) environments.PUT("", gin2.UpdateOperationLogStatus, UpdateMultiProducts) environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) environments.GET("/:name", GetProduct) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index b4ab56ca..4e8caa24 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -194,8 +194,8 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) production := false clusterName := "" cluster, ok := clusterMap[clusterID] - if len(env.RegistryId) == 0 { - env.RegistryId = reg.ID.String() + if len(env.RegistryID) == 0 { + env.RegistryID = reg.ID.Hex() } if ok { production = cluster.Production @@ -213,7 +213,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) Error: env.Error, UpdateTime: env.UpdateTime, UpdateBy: env.UpdateBy, - RegistryID: env.RegistryId, + RegistryID: env.RegistryID, ClusterID: env.ClusterID, }) } @@ -586,7 +586,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k } } - err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryId, kubeClient, log) + err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, kubeClient, log) if err != nil { log.Errorf("[%s][P:%s] service.UpdateProductV2 create kubeEnv error: %v", envName, productName, err) @@ -760,7 +760,7 @@ func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][ IsOpenSource: templateProduct.IsOpensource, ChartInfos: templateProduct.ChartInfos, IsForkedProduct: false, - RegistryId: registryID, + RegistryID: registryID, } customChartValueMap := make(map[string]*commonservice.RenderChartArg) @@ -1024,7 +1024,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) return err } - return ensureKubeEnv(product.Namespace, product.RegistryId, kubeClient, log) + return ensureKubeEnv(product.Namespace, product.RegistryID, kubeClient, log) } func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { @@ -1981,7 +1981,7 @@ func preCreateProduct(envName string, args *commonmodels.Product, kubeClient cli args.Render = tmpRenderInfo if preCreateNSAndSecret(productTmpl.ProductFeature) { - return ensureKubeEnv(args.Namespace, args.RegistryId, kubeClient, log) + return ensureKubeEnv(args.Namespace, args.RegistryID, kubeClient, log) } return nil } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 0abe50d1..1d8896d1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -85,7 +85,7 @@ func (autoCreator *AutoCreator) Create(envName string) (string, error) { productObject.Namespace = commonservice.GetProductEnvNamespace(envName, productName, "") productObject.UpdateBy = autoCreator.Param.UserName productObject.EnvName = envName - productObject.RegistryId = autoCreator.Param.RegistryID + productObject.RegistryID = autoCreator.Param.RegistryID if autoCreator.Param.EnvType == setting.HelmDeployType { productObject.Source = setting.SourceFromHelm } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index be1f9738..b1a07790 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -169,13 +169,13 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( } } - if len(prod.RegistryId) == 0 { + if len(prod.RegistryID) == 0 { reg, err := commonservice.FindDefaultRegistry(log) if err != nil { log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %s", username, envName, productName, err) return nil, err } - prod.RegistryId = reg.ID.Hex() + prod.RegistryID = reg.ID.Hex() } resp := buildProductResp(prod.EnvName, prod, log) return resp, nil @@ -198,7 +198,7 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar ClusterID: prod.ClusterID, RecycleDay: prod.RecycleDay, Source: prod.Source, - RegisterID: prod.RegistryId, + RegisterID: prod.RegistryID, } if prod.ClusterID != "" { diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index bf356fe7..25372854 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -61,7 +61,7 @@ func (*Router) Inject(router *gin.RouterGroup) { registry.GET("", ListRegistries) // 获取默认的镜像仓库配置,用于kodespace CLI调用 registry.GET("/namespaces/default", GetDefaultRegistryNamespace) - registry.GET("/namespaces/:id", GetRegistryNamespace) + registry.GET("/namespaces/specific/:id", GetRegistryNamespace) registry.GET("/namespaces", ListRegistryNamespaces) registry.POST("/namespaces", gin2.UpdateOperationLogStatus, CreateRegistryNamespace) registry.PUT("/namespaces/:id", gin2.UpdateOperationLogStatus, UpdateRegistryNamespace) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index b1f9aa72..35eb6bea 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -494,7 +494,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) - if len(env.RegistryId) == 0 { + if len(env.RegistryID) == 0 { op := &commonrepo.FindRegOps{ IsDefault: true, } @@ -503,9 +503,9 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, log.Errorf("get default registry error: %v", err) return nil, e.ErrGetCounter.AddDesc(err.Error()) } - env.RegistryId = reg.ID.Hex() + env.RegistryID = reg.ID.Hex() } - configPayload.RegistryID = env.RegistryId + configPayload.RegistryID = env.RegistryID repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err == nil { configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) @@ -1804,11 +1804,11 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // 编译任务使用 t.JobCtx.Image // 注意: 其他任务从 pt.TaskArgs.Deploy.Image 获取, 必须要有编译任务 var reg *commonmodels.RegistryNamespace - if len(exitedProd.RegistryId) > 0 { - reg, err = commonservice.FindRegistryById(exitedProd.RegistryId, log) + if len(exitedProd.RegistryID) > 0 { + reg, err = commonservice.FindRegistryById(exitedProd.RegistryID, log) if err != nil { log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", - exitedProd.RegistryId, err) + exitedProd.RegistryID, err) return e.ErrFindRegistry.AddDesc(err.Error()) } } else { -- Gitee From d231cb5940e26841925320cb7b4f2193ac7919ff Mon Sep 17 00:00:00 2001 From: huangzexiong Date: Thu, 9 Dec 2021 17:38:06 +0800 Subject: [PATCH 0939/2647] fix code smell Signed-off-by: huangzexiong --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 01c72c37..f5bcbe4c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1174,8 +1174,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * for _, service := range args.Target { servicesArray = append(servicesArray, service.ServiceName) } - services = strings.Join(servicesArray,",") - log.Info("获取本次任务执行的服务services:%v", services) + services = strings.Join(servicesArray, ",") } testArgs := args.Tests @@ -1236,7 +1235,6 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } envs = append(envs, &commonmodels.KeyVal{Key: "TEST_URL", Value: GetLink(pt, configbase.SystemAddress(), config.WorkflowType)}) envs = append(envs, &commonmodels.KeyVal{Key: "SERVICES", Value: services}) - log.Info("test envs:%v", envs) testTask.JobCtx.EnvVars = envs testTask.ImageID = testModule.PreTest.ImageID -- Gitee From 9db9984fbf6655afb0b67c849300231ae95ca940 Mon Sep 17 00:00:00 2001 From: huangzexiong Date: Thu, 9 Dec 2021 18:53:02 +0800 Subject: [PATCH 0940/2647] test add $SERVICES params Signed-off-by: huangzexiong --- .../core/workflow/service/workflow/workflow_task.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f5bcbe4c..7b36bbfa 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1169,13 +1169,11 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } } - // 获取本次任务执行的服务,按照逗号分隔,service1,service2... - if len(args.Target) > 0 { - for _, service := range args.Target { - servicesArray = append(servicesArray, service.ServiceName) - } - services = strings.Join(servicesArray, ",") + // services env + for _, service := range args.Target { + servicesArray = append(servicesArray, service.ServiceName) } + services = strings.Join(servicesArray, ",") testArgs := args.Tests testCreator := args.WorkflowTaskCreator -- Gitee From d0898416245d9c0e12f85bbceebec2976eeab68d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 18:53:29 +0800 Subject: [PATCH 0941/2647] optimize code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/artifact_deploy.go | 2 +- .../warpdrive/core/service/taskplugin/build.go | 4 +++- .../warpdrive/core/service/taskplugin/docker_build.go | 2 +- .../warpdrive/core/service/taskplugin/jenkins_plugin.go | 2 +- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 7 +++++-- .../warpdrive/core/service/taskplugin/release_image.go | 2 +- .../warpdrive/core/service/taskplugin/testing.go | 4 +++- 7 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index 626e235c..c2c1e968 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -215,7 +215,7 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, "", p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 0649e215..0cee599e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -120,6 +120,8 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe return } p.kubeClient = kubeClient + // replace docker host + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), fmt.Sprintf(".%s", p.Task.Namespace), 1) } if pipelineTask.Type == config.WorkflowType { envName := pipelineTask.WorkflowArgs.Namespace @@ -244,7 +246,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index bc358059..d9abed53 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -196,7 +196,7 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi return } - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, "", setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index fbf8f15f..1879bb0a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -157,7 +157,7 @@ func (j *JenkinsBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, p } j.Log.Infof("succeed to create cm for jenkins build job %s", j.JobName) - job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, "", setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create jenkins build job context error: %v", err) j.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 318f4454..0b8b6b1c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -391,13 +391,14 @@ func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx st //"s-job": pipelinename-taskid-tasktype-servicename, //"s-task": pipelinename-taskid, //"s-type": tasktype, -func buildJob(taskType config.TaskType, jobImage, jobName, serviceName, clusterID string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace) (*batchv1.Job, error) { +func buildJob(taskType config.TaskType, jobImage, jobName, serviceName, clusterID, currentNamespace string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace) (*batchv1.Job, error) { return buildJobWithLinkedNs( taskType, jobImage, jobName, serviceName, clusterID, + currentNamespace, resReq, resReqSpec, ctx, @@ -408,7 +409,7 @@ func buildJob(taskType config.TaskType, jobImage, jobName, serviceName, clusterI ) } -func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName, clusterID string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { +func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceName, clusterID, currentNamespace string, resReq setting.Request, resReqSpec setting.RequestSpec, ctx *task.PipelineCtx, pipelineTask *task.Task, registries []*task.RegistryNamespace, execNs, linkedNs string) (*batchv1.Job, error) { var ( reaperBootingScript string reaperBinaryFile = pipelineTask.ConfigPayload.Release.ReaperBinaryFile @@ -416,6 +417,8 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa // not local cluster if clusterID != "" && clusterID != setting.LocalClusterID { reaperBinaryFile = strings.Replace(reaperBinaryFile, "resource-server", "resource-server.koderover-agent", -1) + } else { + reaperBinaryFile = strings.Replace(reaperBinaryFile, "resource-server", "resource-server."+currentNamespace, -1) } if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 8cb98b58..fd34f27b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -176,7 +176,7 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p } p.Log.Infof("succeed to create cm for image job %s", p.JobName) - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, "", setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PredatorImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create release image job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 8edc8526..17c44251 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -116,6 +116,8 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC return } p.kubeClient = kubeClient + // replace docker host + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), fmt.Sprintf(".%s", p.Task.Namespace), 1) } // 重置错误信息 p.Task.Error = "" @@ -213,7 +215,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC // search namespace should also include desired namespace job, err := buildJobWithLinkedNs( - p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries, + p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, pipelineTask.ConfigPayload.Test.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries, p.KubeNamespace, linkedNamespace, ) -- Gitee From dfc6dd9f11039eced695ae08b309e759833aacc0 Mon Sep 17 00:00:00 2001 From: huangzexiong Date: Thu, 9 Dec 2021 18:55:02 +0800 Subject: [PATCH 0942/2647] test add $SERVICES params Signed-off-by: huangzexiong --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 7b36bbfa..8d4df9b2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1169,7 +1169,6 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * } } - // services env for _, service := range args.Target { servicesArray = append(servicesArray, service.ServiceName) } -- Gitee From 13b9b52f64cfe6d7204b93bbd66db0d0d73d0889 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 19:01:49 +0800 Subject: [PATCH 0943/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 0cee599e..2808787d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -121,6 +121,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } p.kubeClient = kubeClient // replace docker host + p.Log.Infof("pipelineTask.DockerHost:%s", pipelineTask.DockerHost) pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), fmt.Sprintf(".%s", p.Task.Namespace), 1) } if pipelineTask.Type == config.WorkflowType { -- Gitee From 9f7052b91c879d4c4cf88077b8348325b83aabd0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 19:20:12 +0800 Subject: [PATCH 0944/2647] debug code Signed-off-by: liu deyi --- .../core/service/taskplugin/build.go | 20 ++++++++++++++++--- .../core/service/taskplugin/testing.go | 17 ++++++++++++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 2808787d..94e2e990 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -120,10 +120,24 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe return } p.kubeClient = kubeClient - // replace docker host - p.Log.Infof("pipelineTask.DockerHost:%s", pipelineTask.DockerHost) - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), fmt.Sprintf(".%s", p.Task.Namespace), 1) } + + // not local cluster + if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { + if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { + // replace namespace only + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), ".koderover-agent", 1) + } else { + // add namespace + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind.koderover-agent", 1) + } + } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { + if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { + // add namespace + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) + } + } + if pipelineTask.Type == config.WorkflowType { envName := pipelineTask.WorkflowArgs.Namespace envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 17c44251..c8beb03e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -116,8 +116,21 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC return } p.kubeClient = kubeClient - // replace docker host - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), fmt.Sprintf(".%s", p.Task.Namespace), 1) + } + // not local cluster + if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { + if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { + // replace namespace only + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Test.KubeNamespace), ".koderover-agent", 1) + } else { + // add namespace + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind.koderover-agent", 1) + } + } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { + if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { + // add namespace + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) + } } // 重置错误信息 p.Task.Error = "" -- Gitee From 043ed52a5c8362111e999e1baa110e6c61d465a5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 19:32:34 +0800 Subject: [PATCH 0945/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 1 + pkg/microservice/warpdrive/core/service/taskplugin/testing.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 94e2e990..2c9d0fed 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -137,6 +137,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } + pipelineCtx.DockerHost = pipelineTask.DockerHost if pipelineTask.Type == config.WorkflowType { envName := pipelineTask.WorkflowArgs.Namespace diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index c8beb03e..c97e5e52 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -132,6 +132,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) } } + pipelineCtx.DockerHost = pipelineTask.DockerHost // 重置错误信息 p.Task.Error = "" // 获取测试相关的namespace -- Gitee From e18dbe0066c29b798407061f356ebf5fb18c6742 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 20:29:25 +0800 Subject: [PATCH 0946/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 4 ++-- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 6 ++++-- .../warpdrive/core/service/taskplugin/testing.go | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 2c9d0fed..f7abcd8b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -129,12 +129,12 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), ".koderover-agent", 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind.koderover-agent", 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind.koderover-agent", 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } pipelineCtx.DockerHost = pipelineTask.DockerHost diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 0b8b6b1c..0eb8310a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -63,6 +63,8 @@ const ( PreferredSchedule = "preferred" registrySecretSuffix = "-registry-secret" + ResourceServer = "resource-server" + DindServer = "dind" ) func saveFile(src io.Reader, localFile string) error { @@ -416,9 +418,9 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa ) // not local cluster if clusterID != "" && clusterID != setting.LocalClusterID { - reaperBinaryFile = strings.Replace(reaperBinaryFile, "resource-server", "resource-server.koderover-agent", -1) + reaperBinaryFile = strings.Replace(reaperBinaryFile, ResourceServer, ResourceServer+".koderover-agent", -1) } else { - reaperBinaryFile = strings.Replace(reaperBinaryFile, "resource-server", "resource-server."+currentNamespace, -1) + reaperBinaryFile = strings.Replace(reaperBinaryFile, ResourceServer, ResourceServer+"."+currentNamespace, -1) } if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index c97e5e52..baea2f74 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -124,12 +124,12 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Test.KubeNamespace), ".koderover-agent", 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind.koderover-agent", 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind.koderover-agent", 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, ".dind", ".dind."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) } } pipelineCtx.DockerHost = pipelineTask.DockerHost -- Gitee From b5b3be97c4f29e5a37157bb5a336033459938e97 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 20:59:08 +0800 Subject: [PATCH 0947/2647] optimize code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 6 +++--- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 7 ++++--- .../warpdrive/core/service/taskplugin/testing.go | 6 +++--- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index f7abcd8b..002f3acf 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -126,15 +126,15 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // replace namespace only - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Build.KubeNamespace), ".koderover-agent", 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind.koderover-agent", 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+KoderoverAgentNamespace, 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } pipelineCtx.DockerHost = pipelineTask.DockerHost diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 0eb8310a..1d889e59 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -62,9 +62,10 @@ const ( RequiredSchedule = "required" PreferredSchedule = "preferred" - registrySecretSuffix = "-registry-secret" - ResourceServer = "resource-server" - DindServer = "dind" + registrySecretSuffix = "-registry-secret" + ResourceServer = "resource-server" + DindServer = "dind" + KoderoverAgentNamespace = "koderover-agent" ) func saveFile(src io.Reader, localFile string) error { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index baea2f74..fa3008e4 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -121,15 +121,15 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { // replace namespace only - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, fmt.Sprintf(".%s", pipelineTask.ConfigPayload.Test.KubeNamespace), ".koderover-agent", 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace, KoderoverAgentNamespace, 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind.koderover-agent", 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+KoderoverAgentNamespace, 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, "."+DindServer, ".dind."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) } } pipelineCtx.DockerHost = pipelineTask.DockerHost -- Gitee From ad3fd6b7d0b02e61d5838d157220ffe771b9d2a7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 9 Dec 2021 21:15:41 +0800 Subject: [PATCH 0948/2647] optimize code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 5 +++-- .../warpdrive/core/service/taskplugin/testing.go | 11 ++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 002f3acf..10ee1463 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -123,18 +123,19 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } // not local cluster + replaceDindServer := "." + DindServer if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // replace namespace only pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+KoderoverAgentNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } pipelineCtx.DockerHost = pipelineTask.DockerHost diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index fa3008e4..3a935cb9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -118,18 +118,19 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.kubeClient = kubeClient } // not local cluster + replaceDindServer := "." + DindServer if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { - if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { + if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // replace namespace only - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace, KoderoverAgentNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+KoderoverAgentNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { - if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Test.KubeNamespace) { + if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, DindServer, DindServer+"."+pipelineTask.ConfigPayload.Test.KubeNamespace, 1) + pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } pipelineCtx.DockerHost = pipelineTask.DockerHost -- Gitee From d08e90520a5f2af84a36ae53b6d804576668a75b Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 9 Dec 2021 21:19:31 +0800 Subject: [PATCH 0949/2647] fix comments Signed-off-by: allenshen --- cmd/packager/main.go | 2 +- docker/service/packager-plugin.Dockerfile | 8 +- .../core/common/repository/models/queue.go | 103 ++++---- .../common/repository/models/task/model.go | 33 ++- .../packager/core/service/docker.go | 72 ------ .../packager/core/service/packager.go | 22 +- .../packager/core/service/service.go | 230 +++++++++--------- .../packager/executor/executor.go | 9 +- .../service/taskplugin/artifact_package.go | 31 ++- .../warpdrive/core/service/types/reaper.go | 9 +- .../core/service/types/task/model.go | 11 +- 11 files changed, 232 insertions(+), 298 deletions(-) delete mode 100644 pkg/microservice/packager/core/service/docker.go diff --git a/cmd/packager/main.go b/cmd/packager/main.go index ba2938d9..bd2fa8bc 100644 --- a/cmd/packager/main.go +++ b/cmd/packager/main.go @@ -24,6 +24,6 @@ import ( func main() { if err := executor.Execute(); err != nil { - log.Fatalf("Failed to run predator, the error is: %+v", err) + log.Fatalf("Failed to run packager, the error is: %+v", err) } } diff --git a/docker/service/packager-plugin.Dockerfile b/docker/service/packager-plugin.Dockerfile index d7074f6a..2ae89592 100644 --- a/docker/service/packager-plugin.Dockerfile +++ b/docker/service/packager-plugin.Dockerfile @@ -2,10 +2,12 @@ RUN go build -v -o /packager-plugin ./cmd/packager/main.go -#ubuntu-xenial.Dockerfile +#alpine.Dockerfile -# 安装 docker client -RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ +RUN apk --no-cache add curl +# install docker client +RUN apk --no-cache add curl && \ + curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin &&\ rm -rf docke* diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index bda3be27..a122e6fa 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -25,65 +25,50 @@ import ( ) type Queue struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` - // 是否允许同时运行多次 - MultiRun bool `bson:"multi_run" json:"multi_run"` - // target 服务名称, k8s为容器名称, 物理机为服务名 - Target string `bson:"target,omitempty" json:"target"` - // 使用预定义编译管理模块中的内容生成SubTasks, - // 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer - // 如果为空,则使用pipeline自定义SubTasks - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - // TaskArgs 单服务工作流任务参数 - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` - // WorkflowArgs 多服务工作流任务参数 - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` - // TestArgs 测试任务参数 - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` - // ServiceTaskArgs 脚本部署工作流任务参数 - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` - // ArtifactPackageTaskArgs arguments for artifact-package type tasks - ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` - ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` - // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - - RwLock sync.Mutex `bson:"-" json:"-"` - - ResetImage bool `json:"resetImage" bson:"resetImage"` - - TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + Stages []*Stage `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + MultiRun bool `bson:"multi_run" json:"multi_run"` + Target string `bson:"target,omitempty" json:"target"` // target 服务名称, k8s为容器名称, 物理机为服务名 + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` // 使用预定义编译管理模块中的内容生成SubTasks, 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer 如果为空,则使用pipeline自定义SubTasks + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs 单服务工作流任务参数 + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs 测试任务参数 + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 746534f3..d1d23f20 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -28,7 +28,7 @@ import ( ) type Task struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` TaskID int64 `bson:"task_id" json:"task_id"` ProductName string `bson:"product_name" json:"product_name"` PipelineName string `bson:"pipeline_name" json:"pipeline_name"` @@ -48,7 +48,7 @@ type Task struct { TeamName string `bson:"team,omitempty" json:"team,omitempty"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` + AgentID string `bson:"agent_id" json:"agent_id"` // 是否允许同时运行多次 MultiRun bool `bson:"multi_run" json:"multi_run"` // target 服务名称, k8s为容器名称, 物理机为服务名 @@ -57,8 +57,7 @@ type Task struct { // 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer // 如果为空,则使用pipeline自定义SubTasks BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` // TaskArgs 单服务工作流任务参数 TaskArgs *models.TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 @@ -70,23 +69,19 @@ type Task struct { // ArtifactPackageTaskArgs arguments for artifact-package type tasks ArtifactPackageTaskArgs *models.ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` // ConfigPayload 系统配置信息 - ConfigPayload *models.ConfigPayload `json:"config_payload,omitempty"` + ConfigPayload *models.ConfigPayload `bson:"configpayload" json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*models.ProductService `bson:"services" json:"services"` - Render *models.RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + Services [][]*models.ProductService `bson:"services" json:"services"` + Render *models.RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - - RwLock sync.Mutex `bson:"-" json:"-"` - - ResetImage bool `json:"resetImage" bson:"resetImage"` - - TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `json:"resetImage" bson:"resetImage"` + TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { diff --git a/pkg/microservice/packager/core/service/docker.go b/pkg/microservice/packager/core/service/docker.go deleted file mode 100644 index 8cb0bce0..00000000 --- a/pkg/microservice/packager/core/service/docker.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "os/exec" - "strings" -) - -const dockerExe = "/usr/local/bin/docker" - -func dockerVersion() *exec.Cmd { - return exec.Command(dockerExe, "version") -} - -func dockerInfo() *exec.Cmd { - return exec.Command(dockerExe, "info") -} - -// Docker returns build command -// -// e.g. docker build --rm=true -t name:tag -f Dockerfile --build-arg APP_BIN=... . -func dockerBuild(dockerfile, fullImage, buildArgs string) *exec.Cmd { - - args := []string{"build", "--rm=true"} - if buildArgs != "" { - for _, val := range strings.Fields(buildArgs) { - if val != "" { - args = append(args, val) - } - } - - } - args = append(args, []string{"-t", fullImage, "-f", dockerfile, "."}...) - return exec.Command(dockerExe, args...) -} - -func dockerPull(image string) *exec.Cmd { - args := []string{"pull", image} - return exec.Command(dockerExe, args...) -} - -func dockerTag(sourceFullImage, targetFullImage string) *exec.Cmd { - args := []string{ - "tag", - sourceFullImage, - targetFullImage, - } - return exec.Command(dockerExe, args...) -} - -func dockerPush(fullImage string) *exec.Cmd { - args := []string{ - "push", - fullImage, - } - return exec.Command(dockerExe, args...) -} diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go index 771d2f20..d58b587b 100644 --- a/pkg/microservice/packager/core/service/packager.go +++ b/pkg/microservice/packager/core/service/packager.go @@ -17,26 +17,28 @@ limitations under the License. package service type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" json:"image_tag"` + ImageUrl string `yaml:"image_url"` + ImageName string `yaml:"image_name"` + ImageTag string `yaml:"image_tag"` + RegistryID string `yaml:"registry_id,omitempty"` } // ImagesByService defines all images in a service type ImagesByService struct { - ServiceName string `bson:"service_name" json:"service_name"` - Images []*ImageData `bson:"images" json:"images"` + ServiceName string `yaml:"service_name"` + Images []*ImageData `yaml:"images"` } //DockerRegistry registry host/user/password type DockerRegistry struct { - Host string `yaml:"host"` - UserName string `yaml:"username"` - Password string `yaml:"password"` - Namespace string `yaml:"namespace"` + RegistryID string `yaml:"registry_id"` + Host string `yaml:"host"` + UserName string `yaml:"username"` + Password string `yaml:"password"` + Namespace string `yaml:"namespace"` } -// Context ... +// Context parameters for job to run with type Context struct { JobType string `yaml:"job_type"` ProgressFile string `yaml:"progress_file"` diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index c35301fd..19219602 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -17,26 +17,25 @@ limitations under the License. package service import ( + "bytes" + "context" "encoding/base64" "encoding/json" "fmt" - "io/ioutil" "os" - "os/exec" - "path" "path/filepath" "strings" "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" "github.com/pkg/errors" "gopkg.in/yaml.v3" "github.com/koderover/zadig/pkg/microservice/packager/config" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) -// Packager ... type Packager struct { Ctx *Context } @@ -49,7 +48,7 @@ type PackageResult struct { } func NewPackager() (*Packager, error) { - context, err := ioutil.ReadFile(config.JobConfigFile()) + context, err := os.ReadFile(config.JobConfigFile()) if err != nil { return nil, err } @@ -66,71 +65,144 @@ func NewPackager() (*Packager, error) { return packager, nil } -// BeforeExec ... -func (p *Packager) BeforeExec() error { - log.Info("wait for docker daemon to start") - for i := 0; i < 120; i++ { - err := dockerInfo().Run() - if err == nil { - break - } - time.Sleep(time.Second * 1) +func buildRegistryMap(registries []*DockerRegistry) map[string]*DockerRegistry { + ret := make(map[string]*DockerRegistry) + for _, registry := range registries { + ret[registry.RegistryID] = registry } + return ret +} - if len(p.Ctx.ProgressFile) == 0 { - return nil +func base64EncodeAuth(auth *types.AuthConfig) (string, error) { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(auth); err != nil { + return "", err } + return base64.URLEncoding.EncodeToString(buf.Bytes()), nil +} - // create log file - if err := os.MkdirAll(filepath.Dir(p.Ctx.ProgressFile), 0770); err != nil { - return errors.Wrapf(err, "failed to create progress file dir") +func buildTargetImage(imageName, imageTag, host, nameSpace string) string { + ret := "" + if len(nameSpace) > 0 { + ret = fmt.Sprintf("%s/%s/%s:%s", host, nameSpace, imageName, imageTag) + } else { + ret = fmt.Sprintf("%s/%s:%s", host, imageName, imageTag) } - file, err := os.Create(p.Ctx.ProgressFile) - if err != nil { - return errors.Wrapf(err, "failed to create progress file") + ret = strings.TrimPrefix(ret, "http://") + ret = strings.TrimPrefix(ret, "https://") + return ret +} + +func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) error { + targetImageUrlByRepo := make(map[string][]string) + for _, singleImage := range imageByService.Images { + options := types.ImagePullOptions{} + // for images from public repo,registryID won't be appointed + if len(singleImage.RegistryID) > 0 { + registryInfo, ok := allRegistries[singleImage.RegistryID] + if !ok { + return fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) + } + encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ + Username: registryInfo.UserName, + Password: registryInfo.Password, + ServerAddress: registryInfo.Host, + }) + if err != nil { + return errors.Wrapf(err, "faied to create docker pull auth data") + } + options.RegistryAuth = encodedAuth + } + + // pull image + _, err := dockerClient.ImagePull(context.TODO(), singleImage.ImageUrl, options) + if err != nil { + return errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) + } + + // tag image + for _, registry := range targetRegistries { + targetImage := buildTargetImage(singleImage.ImageName, singleImage.ImageTag, registry.Host, registry.Namespace) + err = dockerClient.ImageTag(context.TODO(), singleImage.ImageUrl, targetImage) + if err != nil { + return errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) + } + targetImageUrlByRepo[registry.RegistryID] = append(targetImageUrlByRepo[registry.RegistryID], targetImage) + } + } + + // push image + for _, registry := range targetRegistries { + encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ + Username: registry.UserName, + Password: registry.Password, + ServerAddress: registry.Host, + }) + if err != nil { + return errors.Wrapf(err, "faied to create docker push auth data") + } + options := types.ImagePushOptions{ + RegistryAuth: encodedAuth, + } + for _, targetImageUrl := range targetImageUrlByRepo[registry.RegistryID] { + _, err = dockerClient.ImagePush(context.TODO(), targetImageUrl, options) + if err != nil { + return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + } + } } - err = file.Close() - return errors.Wrapf(err, "failed to close progress file") + return nil } -// Exec ... func (p *Packager) Exec() error { - // add docker registry auths for all registries - allRegistries := make([]*DockerRegistry, 0) - allRegistries = append(allRegistries, p.Ctx.SourceRegistries...) - allRegistries = append(allRegistries, p.Ctx.TargetRegistries...) - err := writeDockerConfig(allRegistries) + // init docker client + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { - return err + log.Errorf("failed to init docker client %s", err) } - realTimeProgress := make([]*PackageResult, 0) + // run docker info to ensure docker daemon connection + for i := 0; i < 120; i++ { + _, err := cli.Info(context.TODO()) + if err == nil { + break + } + log.Errorf("failed tor run docker info, try index: %d, err: %s", i, err) + time.Sleep(time.Second * 1) + } - for _, image := range p.Ctx.Images { - cmds := p.dockerCommands(image) - var err error - for _, cmd := range cmds { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - log.Info(strings.Join(cmd.Args, " ")) - if err = cmd.Run(); err != nil { - break - } + // create log file + if len(p.Ctx.ProgressFile) >= 0 { + if err := os.MkdirAll(filepath.Dir(p.Ctx.ProgressFile), 0770); err != nil { + return errors.Wrapf(err, "failed to create progress file dir") } + file, err := os.Create(p.Ctx.ProgressFile) + if err != nil { + return errors.Wrapf(err, "failed to create progress file") + } + if err = file.Close(); err != nil { + return errors.Wrapf(err, "failed to close progress file") + } + } + + allRegistries := append(p.Ctx.SourceRegistries, p.Ctx.TargetRegistries...) + realTimeProgress := make([]*PackageResult, 0) + for _, imageByService := range p.Ctx.Images { result := &PackageResult{ - ServiceName: image.ServiceName, + ServiceName: imageByService.ServiceName, } + err = handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) if err != nil { result.Result = "failed" result.ErrorMsg = err.Error() - log.Infof("[result][fail][%s][%s]", image.ServiceName, err) + log.Errorf("[result][fail][%s][%s]", imageByService.ServiceName, err) } else { result.Result = "success" - result.ImageData = image.Images - log.Infof("[result][success][%s]", image.ServiceName) + result.ImageData = imageByService.Images + log.Infof("[result][success][%s]", imageByService.ServiceName) } realTimeProgress = append(realTimeProgress, result) @@ -149,68 +221,8 @@ func (p *Packager) Exec() error { } } + // keep job alive for extra 10 seconds to make the runner be able to catch all progress info + // TODO need optimize time.Sleep(time.Second * 10) return nil } - -func writeDockerConfig(registries []*DockerRegistry) error { - - authMap := make(map[string]map[string]string) - //host string, username string, password string - for _, registry := range registries { - if registry.UserName == "" { - continue - } - authMap[registry.Host] = map[string]string{ - "auth": base64.StdEncoding.EncodeToString( - []byte(strings.Join([]string{registry.UserName, registry.Password}, ":")), - )} - } - - dir := path.Join(config.Home(), ".docker") - if err := os.MkdirAll(dir, 0600); err != nil { - return err - } - - cfg := map[string]map[string]map[string]string{ - "auths": authMap, - } - - data, err := json.Marshal(cfg) - - if err != nil { - return err - } - - return ioutil.WriteFile(path.Join(dir, "config.json"), data, 0600) -} - -func (p *Packager) dockerCommands(imageByService *ImagesByService) []*exec.Cmd { - cmds := make([]*exec.Cmd, 0) - //cmds = append(cmds, dockerVersion()) - - buildTargetImage := func(imageName, imageTag, host, nameSpace string) string { - ret := "" - if len(nameSpace) > 0 { - ret = fmt.Sprintf("%s/%s/%s:%s", host, nameSpace, imageName, imageTag) - } else { - ret = fmt.Sprintf("%s/%s:%s", host, imageName, imageTag) - } - ret = strings.TrimPrefix(ret, "http://") - ret = strings.TrimPrefix(ret, "https://") - return ret - } - - if p.Ctx.JobType == setting.BuildChartPackage { - for _, image := range imageByService.Images { - for _, registry := range p.Ctx.TargetRegistries { - cmds = append(cmds, dockerPull(image.ImageUrl)) - targetImage := buildTargetImage(image.ImageName, image.ImageTag, registry.Host, registry.Namespace) - cmds = append(cmds, dockerTag(image.ImageUrl, targetImage)) - cmds = append(cmds, dockerPush(targetImage)) - } - } - } - - return cmds -} diff --git a/pkg/microservice/packager/executor/executor.go b/pkg/microservice/packager/executor/executor.go index 13f75787..f4d1c0aa 100644 --- a/pkg/microservice/packager/executor/executor.go +++ b/pkg/microservice/packager/executor/executor.go @@ -31,18 +31,13 @@ func Execute() error { Development: commonconfig.Mode() != setting.ReleaseMode, }) - pred, err := service.NewPackager() + packager, err := service.NewPackager() if err != nil { log.Errorf("Failed to start packager, error: %s", err) return err } - if err := pred.BeforeExec(); err != nil { - log.Errorf("Failed to run before exec step, error: %s", err) - return err - } - - if err := pred.Exec(); err != nil { + if err := packager.Exec(); err != nil { log.Errorf("Failed to run exec step, error: %s", err) return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go index 24fcb383..f8e8f2de 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" ) // InitializeArtifactPackagePlugin to ini @@ -109,10 +110,11 @@ func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task. for _, registryID := range pipelineTask.ArtifactPackageTaskArgs.SourceRegistries { if registry, ok := pipelineTask.ConfigPayload.RepoConfigs[registryID]; ok { sourceRegistries = append(sourceRegistries, &types.DockerRegistry{ - Host: registry.RegAddr, - Namespace: registry.Namespace, - UserName: registry.AccessKey, - Password: registry.SecretKey, + RegistryID: registryID, + Host: registry.RegAddr, + Namespace: registry.Namespace, + UserName: registry.AccessKey, + Password: registry.SecretKey, }) } } @@ -121,14 +123,25 @@ func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task. for _, registryID := range pipelineTask.ArtifactPackageTaskArgs.TargetRegistries { if registry, ok := pipelineTask.ConfigPayload.RepoConfigs[registryID]; ok { targetRegistries = append(targetRegistries, &types.DockerRegistry{ - Host: registry.RegAddr, - Namespace: registry.Namespace, - UserName: registry.AccessKey, - Password: registry.SecretKey, + RegistryID: registryID, + Host: registry.RegAddr, + Namespace: registry.Namespace, + UserName: registry.AccessKey, + Password: registry.SecretKey, }) } } + log.Infof("######## source registry is %v", sourceRegistries) + log.Infof("######## targetRegistries registry is %+v", targetRegistries) + for _, singleImage := range pipelineTask.ArtifactPackageTaskArgs.Images { + log.Infof("##### the image service name is %s", singleImage.ServiceName) + log.Infof("##### the image service is %v", singleImage.Images) + for _, image := range singleImage.Images { + log.Infof("######## single image %+v", *image) + } + } + jobCtx := &types.ArtifactPackagerContext{ JobType: setting.BuildChartPackage, ProgressFile: setting.ProgressFile, @@ -173,7 +186,7 @@ func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task. } p.Log.Infof("succeed to create cm for artifact package job %s", p.JobName) - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, setting.MinRequest, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, setting.MinRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create release artifact package job context error: %v", err) p.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 9ed4b2fd..88147976 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -269,10 +269,11 @@ type GinkgoTest struct { // DockerRegistry 推送镜像到 docker registry 配置 type DockerRegistry struct { - Host string `yaml:"host"` - Namespace string `yaml:"namespace"` - UserName string `yaml:"username"` - Password string `yaml:"password"` + RegistryID string `yaml:"registry_id"` + Host string `yaml:"host"` + Namespace string `yaml:"namespace"` + UserName string `yaml:"username"` + Password string `yaml:"password"` } // Git ... diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index aa4ed2f1..a7bd4e9d 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -246,14 +246,15 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" json:"image_tag"` + ImageUrl string `bson:"image_url" yaml:"image_url" json:"image_url"` + ImageName string `bson:"image_name" yaml:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" yaml:"image_tag" json:"image_tag"` + RegistryID string `bson:"registry_id,omitempty" yaml:"registry_id,omitempty" json:"registry_id,omitempty"` } type ImagesByService struct { - ServiceName string `bson:"service_name" json:"service_name"` - Images []*ImageData `bson:"images" json:"images"` + ServiceName string `bson:"service_name" json:"service_name" yaml:"service_name"` + Images []*ImageData `bson:"images" json:"images" yaml:"images"` } type ArtifactPackageTaskArgs struct { -- Gitee From 58aef56b727f082f818640a583744a35f06d788c Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 9 Dec 2021 21:43:55 +0800 Subject: [PATCH 0950/2647] optimize code Signed-off-by: allenshen --- docker/service/aslan.Dockerfile | 2 +- .../aslan/core/delivery/service/version.go | 52 +------------------ .../aslan/core/environment/handler/router.go | 2 +- .../aslan/core/environment/service/helm.go | 3 +- .../core/service/types/task/config_payload.go | 2 +- 5 files changed, 5 insertions(+), 56 deletions(-) diff --git a/docker/service/aslan.Dockerfile b/docker/service/aslan.Dockerfile index 4384bb45..434810fa 100644 --- a/docker/service/aslan.Dockerfile +++ b/docker/service/aslan.Dockerfile @@ -2,7 +2,7 @@ RUN go build -v -o /aslan ./cmd/aslan/main.go -#alpine.Dockerfile +#alpine-git.Dockerfile WORKDIR /app diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7db6b6e7..03020925 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -505,10 +505,8 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { return client, nil } -// pull image from currently using registry and push to specified repo +// find all images in one single chart func extractImages(productService *commonmodels.ProductService, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { - - // find all images in one single chart imageUrlsSet := sets.NewString() for _, container := range productService.Containers { imageUrlsSet.Insert(container.Image) @@ -914,33 +912,6 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer // start a new routine to check task results go waitVersionDone(deliveryVersion) - // no need to upload chart packages - //if args.Options == nil || !args.Options.EnableOfflineDist { - // return - //} - // - ////tar all chart files and image offline packages, send to s3 store - //fsTree := os.DirFS(dir) - //s3Base := configbase.ObjectStorageDeliveryVersionPath(deliveryVersion.ProductName) - //if err = fsservice.ArchiveAndUploadFilesToSpecifiedS3(fsTree, []string{deliveryVersion.Version}, s3Base, args.Options.S3StorageID, logger); err != nil { - // logger.Errorf("failed to upload chart package files for project %s, err: %s", deliveryVersion.ProductName, err) - // err = errors.Wrapf(err, "failed to upload package file") - // return - //} - - //err = commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ - // ReleaseID: deliveryVersion.ID, - // DistributeType: config.File, - // PackageFile: fmt.Sprintf("%s.tar.gz", deliveryVersion.Version), - // //RemoteFileKey: filepath.Join(s3Base, fmt.Sprintf("%s.tar.gz", deliveryVersion.Version)), - // S3StorageID: args.Options.S3StorageID, - // CreatedAt: time.Now().Unix(), - //}) - //if err != nil { - // logger.Errorf("failed to insert file distribute data, version: %s, err: %s", deliveryVersion.Version, err) - // return fmt.Errorf("failed to insert file distribute data") - //} - return } @@ -1343,27 +1314,6 @@ func preDownloadChart(projectName, versionName, chartName string, log *zap.Sugar return filePath, err } -func getIndexDownloader(client *cm.Client) helm.IndexDownloader { - return func() ([]byte, error) { - resp, err := client.DownloadFile("index.yaml") - if err != nil { - return nil, err - } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - if resp.StatusCode != 200 { - return nil, getChartmuseumError(b, resp.StatusCode) - } - return b, nil - } -} - func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, error) { chartRepo, err := getChartRepoData(chartRepoName) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index fcca8191..741f7010 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -116,7 +116,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.POST("/:name/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) environments.POST("/:name/services/:serviceName/scale", gin2.UpdateOperationLogStatus, ScaleService) environments.POST("/:name/services/:serviceName/scaleNew", gin2.UpdateOperationLogStatus, ScaleNewService) - environments.GET("/:name/service/:serviceName/containers/:container", GetServiceContainer) + environments.GET("/:name/services/:serviceName/containers/:container", GetServiceContainer) environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) } diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 8a2ea609..255a2ba7 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -23,14 +23,13 @@ import ( "strings" "sync" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index fb5c696e..6892f10e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -130,7 +130,7 @@ type ReleaseConfig struct { // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string // PackagerImage sets docker build image - // e.g. xxx.com/resources/predator-plugin:v0.1.0 + // e.g. xxx.com/resources/packager-plugin:v0.1.0 PackagerImage string } -- Gitee From 0da4739d24c73b5e20c0bb54b3662e28a5f256f9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 9 Dec 2021 21:49:50 +0800 Subject: [PATCH 0951/2647] update tag align Signed-off-by: allenshen --- .../core/common/repository/models/task/model.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index d1d23f20..d4783d2e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -76,12 +76,12 @@ type Task struct { StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `json:"resetImage" bson:"resetImage"` - TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { -- Gitee From cd5330a3d9ed27ec676dcd5e2bca6215f83de06b Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 10 Dec 2021 08:40:35 +0800 Subject: [PATCH 0952/2647] fix get estimate renderset error bug (#705) Signed-off-by: allenshen --- .../core/environment/service/environment.go | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 71e03bfd..dfc388c6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -859,6 +859,7 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa return "", "", fmt.Errorf("failed to query renderset info, name %s", productInfo.Render.Name) } + // find target render chart from render set var targetChart *templatemodels.RenderChart for _, chart := range renderSet.ChartInfos { if chart.ServiceName == serviceName { @@ -867,27 +868,30 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa } } - if targetChart == nil { - return "", "", fmt.Errorf("failed to find chart info, name: %s", serviceName) - } - switch usageScenario { case usageScenarioUpdateEnv: imageRelatedKey := sets.NewString() - if templateService != nil { - for _, container := range templateService.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) - } + for _, container := range templateService.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) } } + + curValuesYaml := "" + if targetChart != nil { // service has been applied into environment, use current values.yaml + curValuesYaml = targetChart.ValuesYaml + } + // merge environment values - mergedBs, err := overrideValues([]byte(targetChart.ValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) + mergedBs, err := overrideValues([]byte(curValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) if err != nil { return "", "", errors.Wrapf(err, "failed to override values") } return string(mergedBs), renderSet.DefaultValues, nil case usageScenarioUpdateRenderSet: + if targetChart == nil { + return "", "", fmt.Errorf("failed to find chart info, name: %s", serviceName) + } return targetChart.ValuesYaml, renderSet.DefaultValues, nil default: return "", "", fmt.Errorf("unrecognized usageScenario:%s", usageScenario) @@ -2577,7 +2581,7 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey } if len(replaceMap) == 0 { - return nil, nil + return latestValuesYaml, nil } var replaceKV []string -- Gitee From d599b1858eeca39be3c82d5bfbcb869b2ea0b82b Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 09:54:23 +0800 Subject: [PATCH 0953/2647] add registry id Signed-off-by: allenshen --- .../aslan/core/common/repository/models/queue.go | 7 ++++--- .../aslan/core/delivery/service/version.go | 12 ++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index a122e6fa..2259f9dc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -96,9 +96,10 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" json:"image_tag"` + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` + RegistryID string `bson:"registry_id" json:"registry_id"` } type ImagesByService struct { diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 03020925..961cdcfb 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -530,15 +530,18 @@ func extractImages(productService *commonmodels.ProductService, registryMap map[ imageName := commonservice.ExtractImageName(imageUrl) imageTag := commonservice.ExtractImageTag(imageUrl) + registryID := "" // used source registry if registry, ok := registryMap[registryUrl]; ok { - registrySet.Insert(registry.ID.Hex()) + registryID = registry.ID.Hex() + registrySet.Insert(registryID) } ret.Images = append(ret.Images, &ImageUrlDetail{ ImageUrl: imageUrl, Name: imageName, Tag: imageTag, + Registry: registryID, }) } @@ -780,9 +783,10 @@ func buildArtifactTaskArgs(projectName, envName string, imagesMap *sync.Map) *co } for _, image := range imageDetail.Images { imagesByService.Images = append(imagesByService.Images, &commonmodels.ImageData{ - ImageUrl: image.ImageUrl, - ImageName: image.Name, - ImageTag: image.Tag, + ImageUrl: image.ImageUrl, + ImageName: image.Name, + ImageTag: image.Tag, + RegistryID: image.Registry, }) } imageArgs = append(imageArgs, imagesByService) -- Gitee From 9962d1167c34f2a7b3cfe21a774fc137386ed25e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 10 Dec 2021 10:21:29 +0800 Subject: [PATCH 0954/2647] Support for environment replication at helm deployment Signed-off-by: ddh27 --- .../core/environment/handler/environment.go | 40 ++++++++++ .../core/environment/service/environment.go | 73 +++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 09efcf4d..5bf5f5f5 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -160,6 +160,42 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { ) } +func copyHelmProduct(c *gin.Context, ctx *internalhandler.Context) { + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") + return + } + + createArgs := make([]*service.CreateHelmProductArg, 0) + data, err := c.GetRawData() + if err != nil { + log.Errorf("copyHelmProduct c.GetRawData() err : %v", err) + } else if err = json.Unmarshal(data, &createArgs); err != nil { + log.Errorf("copyHelmProduct json.Unmarshal err : %v", err) + } + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + envNameCopyList := make([]string, 0) + for _, arg := range createArgs { + if arg.EnvName == "" || arg.BaseEnvName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName or baseEnvName is empty") + return + } + arg.ProductName = projectName + envNameCopyList = append(envNameCopyList, arg.BaseEnvName+"-->"+arg.EnvName) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "复制", "集成环境", strings.Join(envNameCopyList, ","), string(data), ctx.Logger) + + ctx.Err = service.CopyHelmProduct( + projectName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, + ) +} + func CreateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -169,6 +205,10 @@ func CreateProduct(c *gin.Context) { return } if c.Query("helm") == "true" { + if c.Query("scene") == "copy" { + copyHelmProduct(c, ctx) + return + } createHelmProduct(c, ctx) return } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d99eb4b5..859aab42 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -148,6 +148,7 @@ type CreateHelmProductArg struct { ClusterID string `json:"clusterID"` DefaultValues string `json:"defaultValues"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + BaseEnvName string `json:"baseEnvName"` } type UpdateMultiHelmProductArg struct { @@ -766,6 +767,78 @@ func CreateProduct(user, requestID string, args *commonmodels.Product, log *zap. return creator.Create(user, requestID, args, log) } +func CopyHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { + + errList := new(multierror.Error) + for _, arg := range args { + err := copySingleHelmProduct(productName, requestID, userName, arg, log) + if err != nil { + errList = multierror.Append(errList, err) + } + } + return errList.ErrorOrNil() +} + +func copySingleHelmProduct(productName, requestID, userName string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { + + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: arg.BaseEnvName, + }) + if err != nil { + return fmt.Errorf("failed to query base product info, name %s", arg.BaseEnvName) + } + + productInfo.Revision = 1 + productInfo.EnvName = arg.EnvName + productInfo.UpdateBy = userName + productInfo.ClusterID = arg.ClusterID + productInfo.Namespace = commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace) + + opt := &commonrepo.RenderSetFindOption{ + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, + } + renderSetInfo, err := commonrepo.NewRenderSetColl().Find(opt) + if err != nil { + return err + } + productInfo.ChartInfos = renderSetInfo.ChartInfos + + customChartValueMap := make(map[string]*commonservice.RenderChartArg) + for _, singleCV := range arg.ChartValues { + customChartValueMap[singleCV.ServiceName] = singleCV + } + + for _, latestChart := range productInfo.ChartInfos { + if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { + singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) + } + } + + // default values + defaultValuesYaml := arg.DefaultValues + + // insert renderset info into db + if len(productInfo.ChartInfos) > 0 { + err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ + Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + EnvName: arg.EnvName, + ProductTmpl: arg.ProductName, + UpdateBy: userName, + IsDefault: false, + DefaultValues: defaultValuesYaml, + ChartInfos: productInfo.ChartInfos, + }, log) + if err != nil { + log.Errorf("rennderset create fail when copy creating helm product, productName: %s", arg.ProductName) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", arg.ProductName)) + } + } + + return CreateProduct(userName, requestID, productInfo, log) +} + func UpdateProductRecycleDay(envName, productName string, recycleDay int) error { return commonrepo.NewProductColl().UpdateProductRecycleDay(envName, productName, recycleDay) } -- Gitee From 86c08af2a666a88a918ed8d795c931d97336a52e Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 10:36:08 +0800 Subject: [PATCH 0955/2647] fix comments Signed-off-by: allenshen --- .../repository/models/delivery_version.go | 8 +++--- .../core/common/repository/models/queue.go | 26 +++++++++---------- .../aslan/core/service/service/helm.go | 6 ++--- .../service/workflow/artifact_task.go | 2 +- .../service/types/task/artifact_package.go | 2 +- .../core/service/types/task/model.go | 14 +++++----- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 432d6615..ed851379 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -32,14 +32,14 @@ type DeliveryVersion struct { Version string `bson:"version" json:"version"` ProductName string `bson:"product_name" json:"productName"` WorkflowName string `bson:"workflow_name" json:"workflowName"` - Type string `bson:"type" json:"type"` + Type string `bson:"type" json:"type"` TaskID int `bson:"task_id" json:"taskId"` Desc string `bson:"desc" json:"desc"` Labels []string `bson:"labels" json:"labels"` ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` - Status string `bson:"status" json:"status"` - Error string `bson:"error" json:"-"` - Progress *DeliveryVersionProgress `bson:"-" json:"progress"` + Status string `bson:"status" json:"status"` + Error string `bson:"error" json:"-"` + Progress *DeliveryVersionProgress `bson:"-" json:"progress"` CreateArgument interface{} `bson:"createArgument" json:"-"` CreatedBy string `bson:"created_by" json:"createdBy"` CreatedAt int64 `bson:"created_at" json:"created_at"` diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 2259f9dc..f87c728d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -49,13 +49,13 @@ type Queue struct { IsArchived bool `bson:"is_archived" json:"is_archived"` AgentID string `bson:"agent_id" json:"agent_id"` MultiRun bool `bson:"multi_run" json:"multi_run"` - Target string `bson:"target,omitempty" json:"target"` // target 服务名称, k8s为容器名称, 物理机为服务名 - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` // 使用预定义编译管理模块中的内容生成SubTasks, 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer 如果为空,则使用pipeline自定义SubTasks + Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs 单服务工作流任务参数 - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs 测试任务参数 - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -96,9 +96,9 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" json:"image_tag"` + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` RegistryID string `bson:"registry_id" json:"registry_id"` } @@ -109,10 +109,10 @@ type ImagesByService struct { type ArtifactPackageTaskArgs struct { ProjectName string `bson:"project_name" json:"project_name"` - EnvName string `bson:"env_name" json:"env_name"` - Images []*ImagesByService `bson:"images" json:"images"` - SourceRegistries []string `json:"source_registries" json:"source_registries"` - TargetRegistries []string `json:"target_registries" json:"target_registries"` + EnvName string `bson:"env_name" json:"env_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `json:"source_registries" json:"source_registries"` + TargetRegistries []string `json:"target_registries" json:"target_registries"` } type ConfigPayload struct { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 931bfa21..a9d2dec2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -114,9 +114,9 @@ type ChartTemplateData struct { } type GetFileContentParam struct { - FilePath string `json:"filePath" form:"filePath"` - FileName string `json:"fileName" form:"fileName"` - Revision int64 `json:"revision" form:"revision"` + FilePath string `json:"filePath" form:"filePath"` + FileName string `json:"fileName" form:"fileName"` + Revision int64 `json:"revision" form:"revision"` DeliveryVersion bool `json:"deliveryVersion" form:"deliveryVersion"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index 512cdc59..d69d7c4b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -37,7 +37,7 @@ import ( func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskCreator string, log *zap.SugaredLogger) (int64, error) { - // 获取全局configpayload + // get global config payload configPayload := commonservice.GetConfigPayload(0) repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) diff --git a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go index 9ac39d95..5ebe24fb 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go @@ -26,7 +26,7 @@ type ArtifactPackage struct { TaskType config.TaskType `bson:"type" json:"type"` Enabled bool `bson:"enabled" json:"enabled"` TaskStatus config.Status `bson:"status" json:"status"` - Progress string `bson:"progress" json:"progress"` + Progress string `bson:"progress" json:"progress"` Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index a7bd4e9d..d8588c9d 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -246,20 +246,20 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" yaml:"image_url" json:"image_url"` - ImageName string `bson:"image_name" yaml:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" yaml:"image_tag" json:"image_tag"` - RegistryID string `bson:"registry_id,omitempty" yaml:"registry_id,omitempty" json:"registry_id,omitempty"` + ImageUrl string `bson:"image_url" json:"image_url" yaml:"image_url"` + ImageName string `bson:"image_name" json:"image_name" yaml:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag" yaml:"image_tag"` + RegistryID string `bson:"registry_id" json:"registry_id" yaml:"registry_id"` } type ImagesByService struct { ServiceName string `bson:"service_name" json:"service_name" yaml:"service_name"` - Images []*ImageData `bson:"images" json:"images" yaml:"images"` + Images []*ImageData `bson:"images" json:"images" yaml:"images"` } type ArtifactPackageTaskArgs struct { - ProductName string `bson:"product_name" json:"product_name"` - Images []*ImagesByService `bson:"images" json:"images"` + ProductName string `bson:"product_name" json:"product_name"` + Images []*ImagesByService `bson:"images" json:"images"` SourceRegistries []string `bson:"source_registries" json:"source_registries"` TargetRegistries []string `bson:"target_registries" json:"target_registries"` } -- Gitee From ef95d2ce9456f412617aa7d5f66f1da4f329876d Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 11:16:45 +0800 Subject: [PATCH 0956/2647] resolve comments Signed-off-by: allenshen --- docker/service/packager-plugin.Dockerfile | 8 -------- pkg/microservice/packager/core/service/service.go | 8 ++++++++ pkg/microservice/packager/executor/executor.go | 2 +- pkg/microservice/warpdrive/config/const.go | 12 ++++++------ .../core/service/taskplugin/artifact_package.go | 11 ----------- .../warpdrive/core/service/types/task/model.go | 14 +++++++------- 6 files changed, 22 insertions(+), 33 deletions(-) diff --git a/docker/service/packager-plugin.Dockerfile b/docker/service/packager-plugin.Dockerfile index 2ae89592..5ef3c5d5 100644 --- a/docker/service/packager-plugin.Dockerfile +++ b/docker/service/packager-plugin.Dockerfile @@ -4,14 +4,6 @@ RUN go build -v -o /packager-plugin ./cmd/packager/main.go #alpine.Dockerfile -RUN apk --no-cache add curl -# install docker client -RUN apk --no-cache add curl && \ - curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ - tar -xvzf docker.tgz &&\ - mv docker/* /usr/local/bin &&\ - rm -rf docke* - WORKDIR /app COPY --from=build /packager-plugin . diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 19219602..339ab5de 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -160,8 +160,16 @@ func (p *Packager) Exec() error { cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { log.Errorf("failed to init docker client %s", err) + return err } + defer func() { + err = cli.Close() + if err != nil { + log.Errorf("failed to close client: %s", err) + } + }() + // run docker info to ensure docker daemon connection for i := 0; i < 120; i++ { _, err := cli.Info(context.TODO()) diff --git a/pkg/microservice/packager/executor/executor.go b/pkg/microservice/packager/executor/executor.go index f4d1c0aa..840ca013 100644 --- a/pkg/microservice/packager/executor/executor.go +++ b/pkg/microservice/packager/executor/executor.go @@ -38,7 +38,7 @@ func Execute() error { } if err := packager.Exec(); err != nil { - log.Errorf("Failed to run exec step, error: %s", err) + log.Errorf("Failed to run packager, error: %s", err) return err } diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 88c00a89..380b211f 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -56,17 +56,17 @@ const ( type PipelineType string const ( - // SingleType 单服务工作流 + // SingleType single-service workflow SingleType PipelineType = "single" - // WorkflowType 多服务工作流 + // WorkflowType multi-service workflow WorkflowType PipelineType = "workflow" - // FreestyleType 自由编排工作流 + // FreestyleType freeStyle workflow FreestyleType PipelineType = "freestyle" - // TestType 测试 + // TestType testing TestType PipelineType = "test" - // ServiceType 服务 + // ServiceType pipeline ServiceType PipelineType = "service" - // ArtifactType 服务 + // ArtifactType artifact build ArtifactType PipelineType = "artifact" ) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go index f8e8f2de..a546856e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -35,7 +35,6 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/tool/log" ) // InitializeArtifactPackagePlugin to ini @@ -132,16 +131,6 @@ func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task. } } - log.Infof("######## source registry is %v", sourceRegistries) - log.Infof("######## targetRegistries registry is %+v", targetRegistries) - for _, singleImage := range pipelineTask.ArtifactPackageTaskArgs.Images { - log.Infof("##### the image service name is %s", singleImage.ServiceName) - log.Infof("##### the image service is %v", singleImage.Images) - for _, image := range singleImage.Images { - log.Infof("######## single image %+v", *image) - } - } - jobCtx := &types.ArtifactPackagerContext{ JobType: setting.BuildChartPackage, ProgressFile: setting.ProgressFile, diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index a7bd4e9d..e3f77606 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -246,20 +246,20 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" yaml:"image_url" json:"image_url"` - ImageName string `bson:"image_name" yaml:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" yaml:"image_tag" json:"image_tag"` - RegistryID string `bson:"registry_id,omitempty" yaml:"registry_id,omitempty" json:"registry_id,omitempty"` + ImageUrl string `bson:"image_url" json:"image_url" yaml:"image_url"` + ImageName string `bson:"image_name" json:"image_name" yaml:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag" yaml:"image_tag"` + RegistryID string `bson:"registry_id" json:"registry_id" yaml:"registry_id"` } type ImagesByService struct { ServiceName string `bson:"service_name" json:"service_name" yaml:"service_name"` - Images []*ImageData `bson:"images" json:"images" yaml:"images"` + Images []*ImageData `bson:"images" json:"images" yaml:"images"` } type ArtifactPackageTaskArgs struct { - ProductName string `bson:"product_name" json:"product_name"` - Images []*ImagesByService `bson:"images" json:"images"` + ProductName string `bson:"product_name" json:"product_name"` + Images []*ImagesByService `bson:"images" json:"images"` SourceRegistries []string `bson:"source_registries" json:"source_registries"` TargetRegistries []string `bson:"target_registries" json:"target_registries"` } -- Gitee From 8ce5eee7b386bee294e4663457463f76c55a17d9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 11:41:31 +0800 Subject: [PATCH 0957/2647] fix conflicts Signed-off-by: allenshen --- .../warpdrive/core/service/taskplugin/artifact_package.go | 4 ++-- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go index a546856e..a2eec1c0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -175,7 +175,7 @@ func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task. } p.Log.Infof("succeed to create cm for artifact package job %s", p.JobName) - job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, setting.MinRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) if err != nil { msg := fmt.Sprintf("create release artifact package job context error: %v", err) p.Log.Error(msg) @@ -355,7 +355,7 @@ func (p *ArtifactPackageTaskPlugin) Complete(ctx context.Context, pipelineTask * }() // 保存实时日志到s3 - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, "", p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index a542babe..bfcce997 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -433,13 +433,8 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa reaperBinaryFile = strings.Replace(reaperBinaryFile, ResourceServer, ResourceServer+"."+currentNamespace, -1) } -<<<<<<< HEAD if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { - reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", pipelineTask.ConfigPayload.Release.ReaperBinaryFile) -======= - if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", reaperBinaryFile) ->>>>>>> upstream/main if pipelineTask.ConfigPayload.Proxy.EnableApplicationProxy && pipelineTask.ConfigPayload.Proxy.Type == "http" { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL --proxy %s %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", pipelineTask.ConfigPayload.Proxy.GetProxyURL(), -- Gitee From 367454034e8c14faccdd66d423dd38824c8810ce Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 10 Dec 2021 11:43:21 +0800 Subject: [PATCH 0958/2647] Add init to aslan dependent health check Signed-off-by: ddh27 --- pkg/cli/initconfig/cmd/healthz.go | 9 +++++++++ pkg/shared/client/aslan/healthz.go | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 pkg/shared/client/aslan/healthz.go diff --git a/pkg/cli/initconfig/cmd/healthz.go b/pkg/cli/initconfig/cmd/healthz.go index 7e270b8c..bdc4d036 100644 --- a/pkg/cli/initconfig/cmd/healthz.go +++ b/pkg/cli/initconfig/cmd/healthz.go @@ -19,6 +19,8 @@ package cmd import ( "fmt" + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/shared/client/user" ) @@ -30,6 +32,9 @@ func Healthz() error { if err := checkPolicyServiceHealth(); err != nil { return fmt.Errorf("checkPolicyServiceHealth error:%s", err) } + if err := checkAslanServiceHealth(); err != nil { + return fmt.Errorf("checkPolicyServiceHealth error:%s", err) + } return nil } @@ -40,3 +45,7 @@ func checkUserServiceHealth() error { func checkPolicyServiceHealth() error { return policy.NewDefault().Healthz() } + +func checkAslanServiceHealth() error { + return aslan.New(config.AslanServiceAddress()).Healthz() +} diff --git a/pkg/shared/client/aslan/healthz.go b/pkg/shared/client/aslan/healthz.go new file mode 100644 index 00000000..d47bcee3 --- /dev/null +++ b/pkg/shared/client/aslan/healthz.go @@ -0,0 +1,24 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aslan + +// Healthz api/health +func (c *Client) Healthz() error { + url := "/health" + _, err := c.Get(url) + return err +} -- Gitee From ec50023d7bfd9f9eb05d71e5aab0d27844133854 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 10 Dec 2021 11:43:26 +0800 Subject: [PATCH 0959/2647] helm chart packager (#698) * add basic logic to push images by running jobs Signed-off-by: allenshen * add more detail to progress data Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * fix comments Signed-off-by: allenshen * resolve comments Signed-off-by: allenshen * fix conflicts Signed-off-by: allenshen --- Makefile | 2 +- cmd/packager/main.go | 29 ++ docker/service/packager-plugin.Dockerfile | 11 + pkg/microservice/aslan/config/config.go | 4 + pkg/microservice/aslan/config/consts.go | 31 +- .../core/common/repository/models/queue.go | 123 ++--- .../models/task/artifact_package.go | 50 +++ .../common/repository/models/task/model.go | 37 +- .../aslan/core/common/service/base/convert.go | 8 + .../core/common/service/config_payload.go | 1 + .../service/workflow/artifact_task.go | 140 ++++++ .../core/workflow/service/workflow/convert.go | 162 +++---- .../service/workflow/workflow_task.go | 2 + pkg/microservice/packager/config/config.go | 33 ++ .../packager/core/service/packager.go | 48 ++ .../packager/core/service/service.go | 236 ++++++++++ .../packager/executor/executor.go | 46 ++ pkg/microservice/warpdrive/config/const.go | 41 +- .../service/taskcontroller/task_handler.go | 3 + .../service/taskcontroller/task_helper.go | 26 +- .../service/taskplugin/artifact_package.go | 422 ++++++++++++++++++ .../warpdrive/core/service/taskplugin/job.go | 15 +- .../core/service/taskplugin/utils.go | 38 +- .../warpdrive/core/service/types/packager.go | 27 ++ .../warpdrive/core/service/types/reaper.go | 9 +- .../service/types/task/artifact_package.go | 50 +++ .../core/service/types/task/config_payload.go | 3 + .../core/service/types/task/model.go | 21 + pkg/setting/consts.go | 7 + 29 files changed, 1401 insertions(+), 224 deletions(-) create mode 100644 cmd/packager/main.go create mode 100644 docker/service/packager-plugin.Dockerfile create mode 100644 pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go create mode 100644 pkg/microservice/packager/config/config.go create mode 100644 pkg/microservice/packager/core/service/packager.go create mode 100644 pkg/microservice/packager/core/service/service.go create mode 100644 pkg/microservice/packager/executor/executor.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go create mode 100644 pkg/microservice/warpdrive/core/service/types/packager.go create mode 100644 pkg/microservice/warpdrive/core/service/types/task/artifact_package.go diff --git a/Makefile b/Makefile index 08428072..224828be 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc VERSION ?= $(shell date +'%Y%m%d%H%M%S') -TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin resource-server ua warpdrive policy user picket config init +TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin packager-plugin resource-server ua warpdrive policy user picket config init REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) diff --git a/cmd/packager/main.go b/cmd/packager/main.go new file mode 100644 index 00000000..bd2fa8bc --- /dev/null +++ b/cmd/packager/main.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "log" + + "github.com/koderover/zadig/pkg/microservice/packager/executor" +) + +func main() { + if err := executor.Execute(); err != nil { + log.Fatalf("Failed to run packager, the error is: %+v", err) + } +} diff --git a/docker/service/packager-plugin.Dockerfile b/docker/service/packager-plugin.Dockerfile new file mode 100644 index 00000000..5ef3c5d5 --- /dev/null +++ b/docker/service/packager-plugin.Dockerfile @@ -0,0 +1,11 @@ +#golang.Dockerfile + +RUN go build -v -o /packager-plugin ./cmd/packager/main.go + +#alpine.Dockerfile + +WORKDIR /app + +COPY --from=build /packager-plugin . + +ENTRYPOINT ["/app/packager-plugin"] diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index c9c115c8..edca3a57 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -190,6 +190,10 @@ func PredatorImage() string { return viper.GetString(setting.ENVPredatorImage) } +func PackagerImage() string { + return viper.GetString(setting.EnvPackagerImage) +} + func DockerHosts() []string { return strings.Split(viper.GetString(setting.ENVDockerHosts), ",") } diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index a028737f..99f46821 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -83,6 +83,8 @@ const ( TestType PipelineType = "test" // ServiceType 服务 ServiceType PipelineType = "service" + // ArtifactPackageType package artifact + ArtifactType PipelineType = "artifact" ) type Status string @@ -117,20 +119,21 @@ const ( type TaskType string const ( - TaskPipeline TaskType = "pipeline" - TaskBuild TaskType = "buildv2" - TaskJenkinsBuild TaskType = "jenkins_build" - TaskArtifact TaskType = "artifact" - TaskArtifactDeploy TaskType = "artifact_deploy" - TaskDeploy TaskType = "deploy" - TaskTestingV2 TaskType = "testingv2" - TaskDistributeToS3 TaskType = "distribute2kodo" - TaskReleaseImage TaskType = "release_image" - TaskJira TaskType = "jira" - TaskDockerBuild TaskType = "docker_build" - TaskSecurity TaskType = "security" - TaskResetImage TaskType = "reset_image" - TaskDistribute TaskType = "distribute" + TaskPipeline TaskType = "pipeline" + TaskBuild TaskType = "buildv2" + TaskJenkinsBuild TaskType = "jenkins_build" + TaskArtifact TaskType = "artifact" + TaskArtifactDeploy TaskType = "artifact_deploy" + TaskDeploy TaskType = "deploy" + TaskTestingV2 TaskType = "testingv2" + TaskDistributeToS3 TaskType = "distribute2kodo" + TaskReleaseImage TaskType = "release_image" + TaskJira TaskType = "jira" + TaskDockerBuild TaskType = "docker_build" + TaskSecurity TaskType = "security" + TaskResetImage TaskType = "reset_image" + TaskDistribute TaskType = "distribute" + TaskArtifactPackage TaskType = "artifact_package" ) type DistributeType string diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 2286fb79..1a7247d3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -25,63 +25,50 @@ import ( ) type Queue struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` - // 是否允许同时运行多次 - MultiRun bool `bson:"multi_run" json:"multi_run"` - // target 服务名称, k8s为容器名称, 物理机为服务名 - Target string `bson:"target,omitempty" json:"target"` - // 使用预定义编译管理模块中的内容生成SubTasks, - // 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer - // 如果为空,则使用pipeline自定义SubTasks - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - // TaskArgs 单服务工作流任务参数 - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` - // WorkflowArgs 多服务工作流任务参数 - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` - // TestArgs 测试任务参数 - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` - // ServiceTaskArgs 脚本部署工作流任务参数 - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` - ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` - // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - - RwLock sync.Mutex `bson:"-" json:"-"` - - ResetImage bool `json:"resetImage" bson:"resetImage"` - - TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + Stages []*Stage `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + MultiRun bool `bson:"multi_run" json:"multi_run"` + Target string `bson:"target,omitempty" json:"target"` // target 服务名称, k8s为容器名称, 物理机为服务名 + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` // 使用预定义编译管理模块中的内容生成SubTasks, 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer 如果为空,则使用pipeline自定义SubTasks + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs 单服务工作流任务参数 + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs 测试任务参数 + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { @@ -108,6 +95,25 @@ type ServiceTaskArgs struct { Updatable bool `bson:"updatable" json:"updatable"` } +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` +} + +type ImagesByService struct { + ServiceName string `bson:"service_name" json:"service_name"` + Images []*ImageData `bson:"images" json:"images"` +} + +type ArtifactPackageTaskArgs struct { + ProjectName string `bson:"project_name" json:"project_name"` + EnvName string `bson:"env_name" json:"env_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `json:"source_registries" json:"source_registries"` + TargetRegistries []string `json:"target_registries" json:"target_registries"` +} + type ConfigPayload struct { Proxy Proxy `json:"proxy"` S3Storage S3Config `json:"s3_storage"` @@ -210,6 +216,9 @@ type ReleaseConfig struct { // PredatorImage sets docker build image // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string + // PackagerImage sets docker build image + // e.g. xxx.com/resources/predator-plugin:v0.1.0 + PackagerImage string } type ImageReleaseConfig struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go new file mode 100644 index 00000000..8f578b62 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type ArtifactPackage struct { + TaskType config.TaskType `bson:"type" json:"type"` + TaskStatus config.Status `bson:"status" json:"status"` + Enabled bool `bson:"enabled" json:"enabled"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + LogFile string `bson:"log_file" json:"log_file"` + + // source images + Images []*models.ImagesByService `bson:"images" json:"images"` + // source registries need auth to pull images + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + // target registries to push images + TargetRegistries []string `bson:"target_registries" json:"target_registries"` +} + +func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(ri, &task); err != nil { + return nil, fmt.Errorf("convert ReleaseImageTask to interface error: %v", err) + } + return task, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index c503d9c6..d1d23f20 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -28,7 +28,7 @@ import ( ) type Task struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` TaskID int64 `bson:"task_id" json:"task_id"` ProductName string `bson:"product_name" json:"product_name"` PipelineName string `bson:"pipeline_name" json:"pipeline_name"` @@ -48,7 +48,7 @@ type Task struct { TeamName string `bson:"team,omitempty" json:"team,omitempty"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` + AgentID string `bson:"agent_id" json:"agent_id"` // 是否允许同时运行多次 MultiRun bool `bson:"multi_run" json:"multi_run"` // target 服务名称, k8s为容器名称, 物理机为服务名 @@ -57,8 +57,7 @@ type Task struct { // 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer // 如果为空,则使用pipeline自定义SubTasks BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` // TaskArgs 单服务工作流任务参数 TaskArgs *models.TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 @@ -67,24 +66,22 @@ type Task struct { TestArgs *models.TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *models.ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + // ArtifactPackageTaskArgs arguments for artifact-package type tasks + ArtifactPackageTaskArgs *models.ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` // ConfigPayload 系统配置信息 - ConfigPayload *models.ConfigPayload `json:"config_payload,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*models.ProductService `bson:"services" json:"services"` - Render *models.RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + ConfigPayload *models.ConfigPayload `bson:"configpayload" json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*models.ProductService `bson:"services" json:"services"` + Render *models.RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - - RwLock sync.Mutex `bson:"-" json:"-"` - - ResetImage bool `json:"resetImage" bson:"resetImage"` - - TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `json:"resetImage" bson:"resetImage"` + TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/service/base/convert.go b/pkg/microservice/aslan/core/common/service/base/convert.go index b6913548..95ae1740 100644 --- a/pkg/microservice/aslan/core/common/service/base/convert.go +++ b/pkg/microservice/aslan/core/common/service/base/convert.go @@ -92,6 +92,14 @@ func ToReleaseImageTask(sb map[string]interface{}) (*task.ReleaseImage, error) { return t, nil } +func ToArtifactPackageImageTask(sb map[string]interface{}) (*task.ArtifactPackage, error) { + var t *task.ArtifactPackage + if err := task.IToi(sb, &t); err != nil { + return nil, fmt.Errorf("convert interface to ReleaseImageTask error: %v", err) + } + return t, nil +} + func ToJiraTask(sb map[string]interface{}) (*task.Jira, error) { var t *task.Jira if err := task.IToi(sb, &t); err != nil { diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index c4981fd5..87905f82 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -55,6 +55,7 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { ReaperImage: config.ReaperImage(), ReaperBinaryFile: config.ReaperBinaryFile(), PredatorImage: config.PredatorImage(), + PackagerImage: config.PackagerImage(), }, Docker: models.DockerConfig{ HostList: config.DockerHosts(), diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go new file mode 100644 index 00000000..00ca9b6c --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -0,0 +1,140 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workflow + +import ( + "fmt" + "sort" + + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/util" +) + +func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log *zap.SugaredLogger) error { + + // 获取全局configpayload + configPayload := commonservice.GetConfigPayload(0) + repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + + if err != nil { + log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) + return fmt.Errorf("failed to query registries") + } + + registriesInvolved := sets.NewString() + registriesInvolved.Insert(args.SourceRegistries...) + registriesInvolved.Insert(args.TargetRegistries...) + + configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) + for _, repo := range repos { + if !registriesInvolved.Has(repo.ID.Hex()) { + continue + } + // if the registry is SWR, we need to modify ak/sk according to the rule + if repo.RegProvider == config.SWRProvider { + ak := fmt.Sprintf("%s@%s", repo.Region, repo.AccessKey) + sk := util.ComputeHmacSha256(repo.AccessKey, repo.SecretKey) + repo.AccessKey = ak + repo.SecretKey = sk + } + configPayload.RepoConfigs[repo.ID.Hex()] = repo + } + + defaultS3, err := s3.FindDefaultS3() + if err != nil { + err = e.ErrFindDefaultS3Storage.AddDesc("default storage is required by distribute task") + return err + } + + defaultURL, err := defaultS3.GetEncryptedURL() + if err != nil { + err = e.ErrS3Storage.AddErr(err) + return err + } + + task := &task.Task{ + Type: config.ArtifactType, + ProductName: args.ProjectName, + Status: config.StatusCreated, + ArtifactPackageTaskArgs: args, + ConfigPayload: configPayload, + StorageURI: defaultURL, + } + + subTask, err := (&taskmodels.ArtifactPackage{ + TaskType: config.TaskArtifactPackage, + Enabled: true, + TaskStatus: "", + Timeout: 0, + StartTime: 0, + EndTime: 0, + LogFile: "", + Images: args.Images, + SourceRegistries: args.SourceRegistries, + TargetRegistries: args.TargetRegistries, + }).ToSubTask() + + if err != nil { + return err + } + + task.SubTasks = []map[string]interface{}{subTask} + + if err := ensurePipelineTask(task, "", log); err != nil { + log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) + return err + } + + stages := make([]*commonmodels.Stage, 0) + for _, subTask := range task.SubTasks { + AddSubtaskToStage(&stages, subTask, args.EnvName) + } + sort.Sort(ByStageKind(stages)) + task.Stages = stages + if len(task.Stages) == 0 { + return e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + } + + pipelineName := fmt.Sprintf("%s-%s-%s", args.ProjectName, args.EnvName, "artifact") + nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.ServiceTaskFmt, pipelineName)) + if err != nil { + log.Errorf("CreateServiceTask Counter.GetNextSeq error: %v", err) + return e.ErrGetCounter.AddDesc(err.Error()) + } + + task.SubTasks = []map[string]interface{}{} + task.TaskID = nextTaskID + task.PipelineName = pipelineName + + if err := CreateTask(task); err != nil { + log.Error(err) + return e.ErrCreateTask + } + + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 4003a1c6..9c8dc8c6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -31,91 +31,93 @@ import ( func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { return &task.Task{ - TaskID: queueTask.TaskID, - ProductName: queueTask.ProductName, - PipelineName: queueTask.PipelineName, - Type: queueTask.Type, - Status: queueTask.Status, - Description: queueTask.Description, - TaskCreator: queueTask.TaskCreator, - TaskRevoker: queueTask.TaskRevoker, - CreateTime: queueTask.CreateTime, - StartTime: queueTask.StartTime, - EndTime: queueTask.EndTime, - SubTasks: queueTask.SubTasks, - Stages: queueTask.Stages, - ReqID: queueTask.ReqID, - AgentHost: queueTask.AgentHost, - DockerHost: queueTask.DockerHost, - TeamName: queueTask.TeamName, - IsDeleted: queueTask.IsDeleted, - IsArchived: queueTask.IsArchived, - AgentID: queueTask.AgentID, - MultiRun: queueTask.MultiRun, - Target: queueTask.Target, - BuildModuleVer: queueTask.BuildModuleVer, - ServiceName: queueTask.ServiceName, - TaskArgs: queueTask.TaskArgs, - WorkflowArgs: queueTask.WorkflowArgs, - TestArgs: queueTask.TestArgs, - ServiceTaskArgs: queueTask.ServiceTaskArgs, - ConfigPayload: queueTask.ConfigPayload, - Error: queueTask.Error, - Services: queueTask.Services, - Render: queueTask.Render, - StorageURI: queueTask.StorageURI, - TestReports: queueTask.TestReports, - RwLock: queueTask.RwLock, - ResetImage: queueTask.ResetImage, - TriggerBy: queueTask.TriggerBy, - Features: queueTask.Features, - IsRestart: queueTask.IsRestart, - StorageEndpoint: queueTask.StorageEndpoint, + TaskID: queueTask.TaskID, + ProductName: queueTask.ProductName, + PipelineName: queueTask.PipelineName, + Type: queueTask.Type, + Status: queueTask.Status, + Description: queueTask.Description, + TaskCreator: queueTask.TaskCreator, + TaskRevoker: queueTask.TaskRevoker, + CreateTime: queueTask.CreateTime, + StartTime: queueTask.StartTime, + EndTime: queueTask.EndTime, + SubTasks: queueTask.SubTasks, + Stages: queueTask.Stages, + ReqID: queueTask.ReqID, + AgentHost: queueTask.AgentHost, + DockerHost: queueTask.DockerHost, + TeamName: queueTask.TeamName, + IsDeleted: queueTask.IsDeleted, + IsArchived: queueTask.IsArchived, + AgentID: queueTask.AgentID, + MultiRun: queueTask.MultiRun, + Target: queueTask.Target, + BuildModuleVer: queueTask.BuildModuleVer, + ServiceName: queueTask.ServiceName, + TaskArgs: queueTask.TaskArgs, + WorkflowArgs: queueTask.WorkflowArgs, + TestArgs: queueTask.TestArgs, + ServiceTaskArgs: queueTask.ServiceTaskArgs, + ArtifactPackageTaskArgs: queueTask.ArtifactPackageTaskArgs, + ConfigPayload: queueTask.ConfigPayload, + Error: queueTask.Error, + Services: queueTask.Services, + Render: queueTask.Render, + StorageURI: queueTask.StorageURI, + TestReports: queueTask.TestReports, + RwLock: queueTask.RwLock, + ResetImage: queueTask.ResetImage, + TriggerBy: queueTask.TriggerBy, + Features: queueTask.Features, + IsRestart: queueTask.IsRestart, + StorageEndpoint: queueTask.StorageEndpoint, } } func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { return &commonmodels.Queue{ - TaskID: task.TaskID, - ProductName: task.ProductName, - PipelineName: task.PipelineName, - Type: task.Type, - Status: task.Status, - Description: task.Description, - TaskCreator: task.TaskCreator, - TaskRevoker: task.TaskRevoker, - CreateTime: task.CreateTime, - StartTime: task.StartTime, - EndTime: task.EndTime, - SubTasks: task.SubTasks, - Stages: task.Stages, - ReqID: task.ReqID, - AgentHost: task.AgentHost, - DockerHost: task.DockerHost, - TeamName: task.TeamName, - IsDeleted: task.IsDeleted, - IsArchived: task.IsArchived, - AgentID: task.AgentID, - MultiRun: task.MultiRun, - Target: task.Target, - BuildModuleVer: task.BuildModuleVer, - ServiceName: task.ServiceName, - TaskArgs: task.TaskArgs, - WorkflowArgs: task.WorkflowArgs, - TestArgs: task.TestArgs, - ServiceTaskArgs: task.ServiceTaskArgs, - ConfigPayload: task.ConfigPayload, - Error: task.Error, - Services: task.Services, - Render: task.Render, - StorageURI: task.StorageURI, - TestReports: task.TestReports, - RwLock: task.RwLock, - ResetImage: task.ResetImage, - TriggerBy: task.TriggerBy, - Features: task.Features, - IsRestart: task.IsRestart, - StorageEndpoint: task.StorageEndpoint, + TaskID: task.TaskID, + ProductName: task.ProductName, + PipelineName: task.PipelineName, + Type: task.Type, + Status: task.Status, + Description: task.Description, + TaskCreator: task.TaskCreator, + TaskRevoker: task.TaskRevoker, + CreateTime: task.CreateTime, + StartTime: task.StartTime, + EndTime: task.EndTime, + SubTasks: task.SubTasks, + Stages: task.Stages, + ReqID: task.ReqID, + AgentHost: task.AgentHost, + DockerHost: task.DockerHost, + TeamName: task.TeamName, + IsDeleted: task.IsDeleted, + IsArchived: task.IsArchived, + AgentID: task.AgentID, + MultiRun: task.MultiRun, + Target: task.Target, + BuildModuleVer: task.BuildModuleVer, + ServiceName: task.ServiceName, + TaskArgs: task.TaskArgs, + WorkflowArgs: task.WorkflowArgs, + TestArgs: task.TestArgs, + ServiceTaskArgs: task.ServiceTaskArgs, + ArtifactPackageTaskArgs: task.ArtifactPackageTaskArgs, + ConfigPayload: task.ConfigPayload, + Error: task.Error, + Services: task.Services, + Render: task.Render, + StorageURI: task.StorageURI, + TestReports: task.TestReports, + RwLock: task.RwLock, + ResetImage: task.ResetImage, + TriggerBy: task.TriggerBy, + Features: task.Features, + IsRestart: task.IsRestart, + StorageEndpoint: task.StorageEndpoint, } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 7c349f5f..ef57ef16 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2179,6 +2179,8 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } case config.TaskDistribute: // 为了兼容历史数据类型,目前什么都不用做,避免出错 + case config.TaskArtifactPackage: + // do nothing default: return e.NewErrInvalidTaskType(string(pre.TaskType)) } diff --git a/pkg/microservice/packager/config/config.go b/pkg/microservice/packager/config/config.go new file mode 100644 index 00000000..76ed9e11 --- /dev/null +++ b/pkg/microservice/packager/config/config.go @@ -0,0 +1,33 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +import ( + "github.com/spf13/viper" + + // init the config first + _ "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" +) + +func JobConfigFile() string { + return viper.GetString(setting.JobConfigFile) +} + +func Home() string { + return viper.GetString(setting.Home) +} diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go new file mode 100644 index 00000000..d58b587b --- /dev/null +++ b/pkg/microservice/packager/core/service/packager.go @@ -0,0 +1,48 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +type ImageData struct { + ImageUrl string `yaml:"image_url"` + ImageName string `yaml:"image_name"` + ImageTag string `yaml:"image_tag"` + RegistryID string `yaml:"registry_id,omitempty"` +} + +// ImagesByService defines all images in a service +type ImagesByService struct { + ServiceName string `yaml:"service_name"` + Images []*ImageData `yaml:"images"` +} + +//DockerRegistry registry host/user/password +type DockerRegistry struct { + RegistryID string `yaml:"registry_id"` + Host string `yaml:"host"` + UserName string `yaml:"username"` + Password string `yaml:"password"` + Namespace string `yaml:"namespace"` +} + +// Context parameters for job to run with +type Context struct { + JobType string `yaml:"job_type"` + ProgressFile string `yaml:"progress_file"` + Images []*ImagesByService `yaml:"images"` + SourceRegistries []*DockerRegistry `yaml:"source_registries"` + TargetRegistries []*DockerRegistry `yaml:"target_registries"` +} diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go new file mode 100644 index 00000000..339ab5de --- /dev/null +++ b/pkg/microservice/packager/core/service/service.go @@ -0,0 +1,236 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" + + "github.com/koderover/zadig/pkg/microservice/packager/config" + "github.com/koderover/zadig/pkg/tool/log" +) + +type Packager struct { + Ctx *Context +} + +type PackageResult struct { + ServiceName string `json:"service_name"` + Result string `json:"result"` + ErrorMsg string `json:"error_msg"` + ImageData []*ImageData `json:"image_data"` +} + +func NewPackager() (*Packager, error) { + context, err := os.ReadFile(config.JobConfigFile()) + if err != nil { + return nil, err + } + + var ctx *Context + if err := yaml.Unmarshal(context, &ctx); err != nil { + return nil, err + } + + packager := &Packager{ + Ctx: ctx, + } + + return packager, nil +} + +func buildRegistryMap(registries []*DockerRegistry) map[string]*DockerRegistry { + ret := make(map[string]*DockerRegistry) + for _, registry := range registries { + ret[registry.RegistryID] = registry + } + return ret +} + +func base64EncodeAuth(auth *types.AuthConfig) (string, error) { + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(auth); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(buf.Bytes()), nil +} + +func buildTargetImage(imageName, imageTag, host, nameSpace string) string { + ret := "" + if len(nameSpace) > 0 { + ret = fmt.Sprintf("%s/%s/%s:%s", host, nameSpace, imageName, imageTag) + } else { + ret = fmt.Sprintf("%s/%s:%s", host, imageName, imageTag) + } + ret = strings.TrimPrefix(ret, "http://") + ret = strings.TrimPrefix(ret, "https://") + return ret +} + +func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) error { + targetImageUrlByRepo := make(map[string][]string) + for _, singleImage := range imageByService.Images { + options := types.ImagePullOptions{} + // for images from public repo,registryID won't be appointed + if len(singleImage.RegistryID) > 0 { + registryInfo, ok := allRegistries[singleImage.RegistryID] + if !ok { + return fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) + } + encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ + Username: registryInfo.UserName, + Password: registryInfo.Password, + ServerAddress: registryInfo.Host, + }) + if err != nil { + return errors.Wrapf(err, "faied to create docker pull auth data") + } + options.RegistryAuth = encodedAuth + } + + // pull image + _, err := dockerClient.ImagePull(context.TODO(), singleImage.ImageUrl, options) + if err != nil { + return errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) + } + + // tag image + for _, registry := range targetRegistries { + targetImage := buildTargetImage(singleImage.ImageName, singleImage.ImageTag, registry.Host, registry.Namespace) + err = dockerClient.ImageTag(context.TODO(), singleImage.ImageUrl, targetImage) + if err != nil { + return errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) + } + targetImageUrlByRepo[registry.RegistryID] = append(targetImageUrlByRepo[registry.RegistryID], targetImage) + } + } + + // push image + for _, registry := range targetRegistries { + encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ + Username: registry.UserName, + Password: registry.Password, + ServerAddress: registry.Host, + }) + if err != nil { + return errors.Wrapf(err, "faied to create docker push auth data") + } + options := types.ImagePushOptions{ + RegistryAuth: encodedAuth, + } + for _, targetImageUrl := range targetImageUrlByRepo[registry.RegistryID] { + _, err = dockerClient.ImagePush(context.TODO(), targetImageUrl, options) + if err != nil { + return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + } + } + } + return nil +} + +func (p *Packager) Exec() error { + + // init docker client + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + log.Errorf("failed to init docker client %s", err) + return err + } + + defer func() { + err = cli.Close() + if err != nil { + log.Errorf("failed to close client: %s", err) + } + }() + + // run docker info to ensure docker daemon connection + for i := 0; i < 120; i++ { + _, err := cli.Info(context.TODO()) + if err == nil { + break + } + log.Errorf("failed tor run docker info, try index: %d, err: %s", i, err) + time.Sleep(time.Second * 1) + } + + // create log file + if len(p.Ctx.ProgressFile) >= 0 { + if err := os.MkdirAll(filepath.Dir(p.Ctx.ProgressFile), 0770); err != nil { + return errors.Wrapf(err, "failed to create progress file dir") + } + file, err := os.Create(p.Ctx.ProgressFile) + if err != nil { + return errors.Wrapf(err, "failed to create progress file") + } + if err = file.Close(); err != nil { + return errors.Wrapf(err, "failed to close progress file") + } + } + + allRegistries := append(p.Ctx.SourceRegistries, p.Ctx.TargetRegistries...) + realTimeProgress := make([]*PackageResult, 0) + + for _, imageByService := range p.Ctx.Images { + result := &PackageResult{ + ServiceName: imageByService.ServiceName, + } + err = handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) + + if err != nil { + result.Result = "failed" + result.ErrorMsg = err.Error() + log.Errorf("[result][fail][%s][%s]", imageByService.ServiceName, err) + } else { + result.Result = "success" + result.ImageData = imageByService.Images + log.Infof("[result][success][%s]", imageByService.ServiceName) + } + + realTimeProgress = append(realTimeProgress, result) + + if len(p.Ctx.ProgressFile) > 0 { + bs, err := json.Marshal(realTimeProgress) + if err != nil { + log.Errorf("failed to marshal progress data %s", err) + continue + } + err = os.WriteFile(p.Ctx.ProgressFile, bs, 0644) + if err != nil { + log.Errorf("failed to write progress data %s", err) + continue + } + } + } + + // keep job alive for extra 10 seconds to make the runner be able to catch all progress info + // TODO need optimize + time.Sleep(time.Second * 10) + return nil +} diff --git a/pkg/microservice/packager/executor/executor.go b/pkg/microservice/packager/executor/executor.go new file mode 100644 index 00000000..840ca013 --- /dev/null +++ b/pkg/microservice/packager/executor/executor.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package executor + +import ( + commonconfig "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/packager/core/service" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" +) + +func Execute() error { + log.Init(&log.Config{ + Level: commonconfig.LogLevel(), + NoCaller: true, + NoLogLevel: true, + Development: commonconfig.Mode() != setting.ReleaseMode, + }) + + packager, err := service.NewPackager() + if err != nil { + log.Errorf("Failed to start packager, error: %s", err) + return err + } + + if err := packager.Exec(); err != nil { + log.Errorf("Failed to run packager, error: %s", err) + return err + } + + return nil +} diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 6e4fcaca..380b211f 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -19,20 +19,21 @@ package config type TaskType string const ( - TaskPipeline TaskType = "pipeline" - TaskBuild TaskType = "buildv2" - TaskArtifactDeploy TaskType = "artifact_deploy" - TaskJenkinsBuild TaskType = "jenkins_build" - TaskArtifact TaskType = "artifact" - TaskDeploy TaskType = "deploy" - TaskTestingV2 TaskType = "testingv2" - TaskDistributeToS3 TaskType = "distribute2kodo" - TaskReleaseImage TaskType = "release_image" - TaskJira TaskType = "jira" - TaskDockerBuild TaskType = "docker_build" - TaskSecurity TaskType = "security" - TaskResetImage TaskType = "reset_image" - TaskDistribute TaskType = "distribute" + TaskPipeline TaskType = "pipeline" + TaskBuild TaskType = "buildv2" + TaskArtifactDeploy TaskType = "artifact_deploy" + TaskJenkinsBuild TaskType = "jenkins_build" + TaskArtifact TaskType = "artifact" + TaskDeploy TaskType = "deploy" + TaskTestingV2 TaskType = "testingv2" + TaskDistributeToS3 TaskType = "distribute2kodo" + TaskReleaseImage TaskType = "release_image" + TaskJira TaskType = "jira" + TaskDockerBuild TaskType = "docker_build" + TaskSecurity TaskType = "security" + TaskResetImage TaskType = "reset_image" + TaskDistribute TaskType = "distribute" + TaskArtifactPackage TaskType = "artifact_package" ) type Status string @@ -55,16 +56,18 @@ const ( type PipelineType string const ( - // SingleType 单服务工作流 + // SingleType single-service workflow SingleType PipelineType = "single" - // WorkflowType 多服务工作流 + // WorkflowType multi-service workflow WorkflowType PipelineType = "workflow" - // FreestyleType 自由编排工作流 + // FreestyleType freeStyle workflow FreestyleType PipelineType = "freestyle" - // TestType 测试 + // TestType testing TestType PipelineType = "test" - // ServiceType 服务 + // ServiceType pipeline ServiceType PipelineType = "service" + // ArtifactType artifact build + ArtifactType PipelineType = "artifact" ) type NotifyType int diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 301be7bf..db98e084 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -411,6 +411,9 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } else if pipelineTask.Type == config.ServiceType { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.ServiceType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), servicename)), "_", "-", -1) + } else if pipelineTask.Type == config.ArtifactType { + fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.ArtifactType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), + "_", "-", -1) } plugin.Init(jobName, fileName, xl) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index b0a13595..f8b7a57e 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -136,6 +136,9 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv } else if pipelineTask.Type == config.ServiceType { xl.Info("pipeline type is service type: pipeline 3.0") pipelineTask.Stages[pos].SubTasks[servicename] = subTask + } else if pipelineTask.Type == config.ArtifactType { + xl.Info("pipeline type is artifact-package type: pipeline 3.0") + pipelineTask.Stages[pos].SubTasks[servicename] = subTask } } @@ -538,17 +541,18 @@ func getSubTaskTypeAndIsRestart(subTask map[string]interface{}) bool { func initTaskPlugins(execHandler *ExecHandler) { pluginConf := map[config.TaskType]plugins.Initiator{ - config.TaskJira: plugins.InitializeJiraTaskPlugin, - config.TaskBuild: plugins.InitializeBuildTaskPlugin, - config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, - config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, - config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, - config.TaskDeploy: plugins.InitializeDeployTaskPlugin, - config.TaskTestingV2: plugins.InitializeTestTaskPlugin, - config.TaskSecurity: plugins.InitializeSecurityPlugin, - config.TaskReleaseImage: plugins.InitializeReleaseImagePlugin, - config.TaskDistributeToS3: plugins.InitializeDistribute2S3TaskPlugin, - config.TaskResetImage: plugins.InitializeDeployTaskPlugin, + config.TaskJira: plugins.InitializeJiraTaskPlugin, + config.TaskBuild: plugins.InitializeBuildTaskPlugin, + config.TaskArtifactDeploy: plugins.InitializeArtifactTaskPlugin, + config.TaskJenkinsBuild: plugins.InitializeJenkinsBuildPlugin, + config.TaskDockerBuild: plugins.InitializeDockerBuildTaskPlugin, + config.TaskDeploy: plugins.InitializeDeployTaskPlugin, + config.TaskTestingV2: plugins.InitializeTestTaskPlugin, + config.TaskSecurity: plugins.InitializeSecurityPlugin, + config.TaskReleaseImage: plugins.InitializeReleaseImagePlugin, + config.TaskDistributeToS3: plugins.InitializeDistribute2S3TaskPlugin, + config.TaskResetImage: plugins.InitializeDeployTaskPlugin, + config.TaskArtifactPackage: plugins.InitializeArtifactPackagePlugin, } for name, pluginInitiator := range pluginConf { registerTaskPlugin(execHandler, name, pluginInitiator) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go new file mode 100644 index 00000000..a2eec1c0 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_package.go @@ -0,0 +1,422 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/wrapper" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/podexec" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +// InitializeArtifactPackagePlugin to ini +func InitializeArtifactPackagePlugin(taskType config.TaskType) TaskPlugin { + return &ArtifactPackageTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +type ArtifactPackageTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.ArtifactPackage + AckFunc func() + Log *zap.SugaredLogger + + RealTimeProgress string +} + +const ( + // ArtifactPackageBuildImageTaskTimeout ... + ArtifactPackageBuildImageTaskTimeout = 60 * 5 // 5 minutes +) + +// Init ... +func (p *ArtifactPackageTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.FileName = filename + // SetLogger ... + p.Log = xl +} + +func (p *ArtifactPackageTaskPlugin) SetAckFunc(f func()) { + p.AckFunc = f +} + +// Type ... +func (p *ArtifactPackageTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *ArtifactPackageTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *ArtifactPackageTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +func (p *ArtifactPackageTaskPlugin) SetProgress(progress string) { + p.Task.Progress = progress +} + +// TaskTimeout ... +func (p *ArtifactPackageTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = ArtifactPackageBuildImageTaskTimeout + } + return p.Task.Timeout +} + +func (p *ArtifactPackageTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + + sourceRegistries := make([]*types.DockerRegistry, 0) + for _, registryID := range pipelineTask.ArtifactPackageTaskArgs.SourceRegistries { + if registry, ok := pipelineTask.ConfigPayload.RepoConfigs[registryID]; ok { + sourceRegistries = append(sourceRegistries, &types.DockerRegistry{ + RegistryID: registryID, + Host: registry.RegAddr, + Namespace: registry.Namespace, + UserName: registry.AccessKey, + Password: registry.SecretKey, + }) + } + } + + targetRegistries := make([]*types.DockerRegistry, 0) + for _, registryID := range pipelineTask.ArtifactPackageTaskArgs.TargetRegistries { + if registry, ok := pipelineTask.ConfigPayload.RepoConfigs[registryID]; ok { + targetRegistries = append(targetRegistries, &types.DockerRegistry{ + RegistryID: registryID, + Host: registry.RegAddr, + Namespace: registry.Namespace, + UserName: registry.AccessKey, + Password: registry.SecretKey, + }) + } + } + + jobCtx := &types.ArtifactPackagerContext{ + JobType: setting.BuildChartPackage, + ProgressFile: setting.ProgressFile, + Images: pipelineTask.ArtifactPackageTaskArgs.Images, + SourceRegistries: sourceRegistries, + TargetRegistries: targetRegistries, + } + + jobCtxBytes, err := yaml.Marshal(jobCtx) + if err != nil { + msg := fmt.Sprintf("cannot mashal predetor.Context data: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + p.Task.Error = "" + + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("ensureDeleteConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + if err := createJobConfigMap( + p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient); err != nil { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.Log.Infof("succeed to create cm for artifact package job %s", p.JobName) + + job, err := buildJob(p.Type(), pipelineTask.ConfigPayload.Release.PackagerImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.LowRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + if err != nil { + msg := fmt.Sprintf("create release artifact package job context error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + msg := fmt.Sprintf("delete release artifact package job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + + job.Namespace = p.KubeNamespace + if err := updater.CreateJob(job, p.kubeClient); err != nil { + msg := fmt.Sprintf("create release image job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.Log.Infof("succeed to create artifact package job %s", p.JobName) +} + +// SetTask ... +func (p *ArtifactPackageTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToArtifactTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *ArtifactPackageTaskPlugin) GetTask() interface{} { + return p.Task +} + +// Wait ... +func (p *ArtifactPackageTaskPlugin) Wait(ctx context.Context) { + + status := p.waitJobStart() + if status != config.StatusRunning { + p.SetStatus(status) + return + } + + status = p.waitJobEnd(ctx, p.TaskTimeout()) + p.SetProgress(p.RealTimeProgress) + p.SetStatus(status) +} + +func (p *ArtifactPackageTaskPlugin) waitJobStart() config.Status { + namespace, jobName, kubeClient, xl := p.KubeNamespace, p.JobName, p.kubeClient, p.Log + xl.Infof("wait job to start: %s/%s", namespace, jobName) + podTimeout := time.After(120 * time.Second) + var started bool + for { + select { + case <-podTimeout: + return config.StatusTimeout + default: + job, _, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil { + xl.Errorf("get job failed, namespace:%s, jobName:%s, err:%v", namespace, jobName, err) + } + if job != nil { + started = job.Status.Active > 0 + } + } + if started { + break + } + + time.Sleep(time.Second) + } + return config.StatusRunning +} + +func (p *ArtifactPackageTaskPlugin) waitJobEnd(ctx context.Context, taskTimeout int) (status config.Status) { + namespace, jobName, kubeClient, xl := p.KubeNamespace, p.JobName, p.kubeClient, p.Log + xl.Infof("wait job to start: %s/%s", namespace, jobName) + timeout := time.After(time.Duration(taskTimeout) * time.Second) + + // 等待job 运行结束 + xl.Infof("wait job to end: %s %s", namespace, jobName) + for { + select { + case <-ctx.Done(): + return config.StatusCancelled + + case <-timeout: + return config.StatusTimeout + + default: + job, found, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil || !found { + xl.Errorf("failed to get pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + // pod is still running + if job.Status.Active != 0 { + + pods, err := getter.ListPods(namespace, labels.Set{"job-name": jobName}.AsSelector(), kubeClient) + if err != nil { + xl.Errorf("failed to find pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + + var done bool + for _, pod := range pods { + ipod := wrapper.Pod(pod) + if ipod.Pending() { + continue + } + if ipod.Failed() { + return config.StatusFailed + } + + if !ipod.Finished() { + progressInfo, err := getProgressInfo(namespace, ipod.Name, ipod.ContainerNames()[0], setting.ProgressFile) + if err != nil { + xl.Infof("failed to check dog food file %s %v", pods[0].Name, err) + break + } + p.RealTimeProgress = progressInfo + xl.Infof("find progress info %s", progressInfo) + p.SetProgress(progressInfo) + p.SetStatus(config.StatusRunning) + + if p.AckFunc != nil { + p.AckFunc() + } + } else { + done = true + } + } + + if done { + return config.StatusPassed + } + } else if job.Status.Succeeded != 0 { + return config.StatusPassed + } else { + return config.StatusFailed + } + } + + time.Sleep(time.Second * 5) + } + +} + +// Complete ... +func (p *ArtifactPackageTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + // 清理用户取消和超时的任务 + defer func() { + if p.Task.TaskStatus == config.StatusCancelled || p.Task.TaskStatus == config.StatusTimeout { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + } + return + } + }() + + // 保存实时日志到s3 + err := saveContainerLog(pipelineTask, p.KubeNamespace, "", p.FileName, jobLabel, p.kubeClient) + if err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + return + } + + p.Task.LogFile = p.JobName +} + +// IsTaskDone ... +func (p *ArtifactPackageTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *ArtifactPackageTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *ArtifactPackageTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *ArtifactPackageTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *ArtifactPackageTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *ArtifactPackageTaskPlugin) ResetError() { + p.Task.Error = "" +} + +func getProgressInfo(namespace string, pod string, container string, progressInfoFile string) (string, error) { + stdOut, stdErr, success, err := podexec.ExecWithOptions(podexec.ExecOptions{ + Command: []string{"cat", progressInfoFile}, + Namespace: namespace, + PodName: pod, + ContainerName: container, + }) + + if err != nil { + return "", err + } + if len(stdErr) != 0 { + return "", fmt.Errorf(stdErr) + } + if !success { + return "", fmt.Errorf("pod exec execute failed") + } + return stdOut, nil +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1d889e59..bfcce997 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -58,6 +58,7 @@ const ( defaultSecretEmail = "bot@koderover.com" PredatorPlugin = "predator-plugin" JenkinsPlugin = "jenkins-plugin" + PackagerPlugin = "packager-plugin" NormalSchedule = "normal" RequiredSchedule = "required" PreferredSchedule = "preferred" @@ -118,6 +119,7 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st if err = saveFile(buf, tempFileName); err == nil { var store *s3.S3 if store, err = s3.NewS3StorageFromEncryptedURI(pipelineTask.StorageURI); err != nil { + log.Errorf("failed to NewS3StorageFromEncryptedURI ") return err } if store.Subfolder != "" { @@ -364,12 +366,19 @@ const ( // getJobLabels get labels k-v map from JobLabel struct func getJobLabels(jobLabel *JobLabel) map[string]string { - return map[string]string{ + retMap := map[string]string{ jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.PipelineName), jobLabel.TaskID), jobLabelServiceKey: strings.ToLower(jobLabel.ServiceName), jobLabelSTypeKey: strings.Replace(jobLabel.TaskType, "_", "-", -1), jobLabelPTypeKey: jobLabel.PipelineType, } + // no need to add labels with empty value to a job + for k, v := range retMap { + if len(v) == 0 { + delete(retMap, k) + } + } + return retMap } func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx string, kubeClient client.Client) error { @@ -424,7 +433,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa reaperBinaryFile = strings.Replace(reaperBinaryFile, ResourceServer, ResourceServer+"."+currentNamespace, -1) } - if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { + if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", reaperBinaryFile) if pipelineTask.ConfigPayload.Proxy.EnableApplicationProxy && pipelineTask.ConfigPayload.Proxy.Type == "http" { reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL --proxy %s %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", @@ -505,7 +514,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa }, } - if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) { + if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { job.Spec.Template.Spec.Containers[0].Command = []string{"/bin/sh", "-c"} job.Spec.Template.Spec.Containers[0].Args = []string{reaperBootingScript} } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 6d411101..35236b81 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -98,7 +98,7 @@ type Preview struct { func ToPreview(sb map[string]interface{}) (*Preview, error) { var pre *Preview if err := IToi(sb, &pre); err != nil { - return nil, fmt.Errorf("convert interface to SubTaskPreview error: %v", err) + return nil, fmt.Errorf("convert interface to SubTaskPreview error: %s", err) } return pre, nil } @@ -106,15 +106,15 @@ func ToPreview(sb map[string]interface{}) (*Preview, error) { func ToBuildTask(sb map[string]interface{}) (*task.Build, error) { var t *task.Build if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to BuildTaskV2 error: %v", err) + return nil, fmt.Errorf("convert interface to BuildTaskV2 error: %s", err) } return t, nil } -func ToArtifactTask(sb map[string]interface{}) (*task.Artifact, error) { - var t *task.Artifact +func ToArtifactTask(sb map[string]interface{}) (*task.ArtifactPackage, error) { + var t *task.ArtifactPackage if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to ArtifactTask error: %v", err) + return nil, fmt.Errorf("convert interface to ArtifactTask error: %s", err) } return t, nil } @@ -122,7 +122,7 @@ func ToArtifactTask(sb map[string]interface{}) (*task.Artifact, error) { func ToDockerBuildTask(sb map[string]interface{}) (*task.DockerBuild, error) { var t *task.DockerBuild if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to DockerBuildTask error: %v", err) + return nil, fmt.Errorf("convert interface to DockerBuildTask error: %s", err) } return t, nil } @@ -130,7 +130,7 @@ func ToDockerBuildTask(sb map[string]interface{}) (*task.DockerBuild, error) { func ToDeployTask(sb map[string]interface{}) (*task.Deploy, error) { var t *task.Deploy if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to DeployTask error: %v", err) + return nil, fmt.Errorf("convert interface to DeployTask error: %s", err) } return t, nil } @@ -138,7 +138,7 @@ func ToDeployTask(sb map[string]interface{}) (*task.Deploy, error) { func ToTestingTask(sb map[string]interface{}) (*task.Testing, error) { var t *task.Testing if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to Testing error: %v", err) + return nil, fmt.Errorf("convert interface to Testing error: %s", err) } return t, nil } @@ -146,7 +146,7 @@ func ToTestingTask(sb map[string]interface{}) (*task.Testing, error) { func ToDistributeToS3Task(sb map[string]interface{}) (*task.DistributeToS3, error) { var t *task.DistributeToS3 if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to DistributeToS3Task error: %v", err) + return nil, fmt.Errorf("convert interface to DistributeToS3Task error: %s", err) } return t, nil } @@ -154,15 +154,23 @@ func ToDistributeToS3Task(sb map[string]interface{}) (*task.DistributeToS3, erro func ToReleaseImageTask(sb map[string]interface{}) (*task.ReleaseImage, error) { var t *task.ReleaseImage if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to ReleaseImageTask error: %v", err) + return nil, fmt.Errorf("convert interface to ReleaseImageTask error: %s", err) } return t, nil } +func ToArtifactPackageTask(sb map[string]interface{}) (*task.ArtifactPackageTaskArgs, error) { + var ret *task.ArtifactPackageTaskArgs + if err := IToi(sb, &ret); err != nil { + return nil, fmt.Errorf("convert interface to ArtifactPackageTaskArgs error: %s", err) + } + return ret, nil +} + func ToJiraTask(sb map[string]interface{}) (*task.Jira, error) { var t *task.Jira if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to JiraTask error: %v", err) + return nil, fmt.Errorf("convert interface to JiraTask error: %s", err) } return t, nil } @@ -170,7 +178,7 @@ func ToJiraTask(sb map[string]interface{}) (*task.Jira, error) { func ToSecurityTask(sb map[string]interface{}) (*task.Security, error) { var t *task.Security if err := IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to securityTask error: %v", err) + return nil, fmt.Errorf("convert interface to securityTask error: %s", err) } return t, nil } @@ -178,7 +186,7 @@ func ToSecurityTask(sb map[string]interface{}) (*task.Security, error) { func ToJenkinsBuildTask(sb map[string]interface{}) (*task.JenkinsBuild, error) { var task *task.JenkinsBuild if err := IToi(sb, &task); err != nil { - return nil, fmt.Errorf("convert interface to JenkinsBuildTask error: %v", err) + return nil, fmt.Errorf("convert interface to JenkinsBuildTask error: %s", err) } return task, nil } @@ -186,11 +194,11 @@ func ToJenkinsBuildTask(sb map[string]interface{}) (*task.JenkinsBuild, error) { func IToi(before interface{}, after interface{}) error { b, err := json.Marshal(before) if err != nil { - return fmt.Errorf("marshal task error: %v", err) + return fmt.Errorf("marshal task error: %s", err) } if err := json.Unmarshal(b, &after); err != nil { - return fmt.Errorf("unmarshal task error: %v", err) + return fmt.Errorf("unmarshal task error: %s", err) } return nil diff --git a/pkg/microservice/warpdrive/core/service/types/packager.go b/pkg/microservice/warpdrive/core/service/types/packager.go new file mode 100644 index 00000000..19aac50a --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/packager.go @@ -0,0 +1,27 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + +type ArtifactPackagerContext struct { + JobType string `yaml:"job_type"` + ProgressFile string `yaml:"progress_file"` + Images []*task.ImagesByService `yaml:"images"` + SourceRegistries []*DockerRegistry `yaml:"source_registries"` + TargetRegistries []*DockerRegistry `yaml:"target_registries"` +} diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index c3fa2fa7..ca4f4ec1 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -270,10 +270,11 @@ type GinkgoTest struct { // DockerRegistry 推送镜像到 docker registry 配置 type DockerRegistry struct { - Host string `yaml:"host"` - Namespace string `yaml:"namespace"` - UserName string `yaml:"username"` - Password string `yaml:"password"` + RegistryID string `yaml:"registry_id"` + Host string `yaml:"host"` + Namespace string `yaml:"namespace"` + UserName string `yaml:"username"` + Password string `yaml:"password"` } // Git ... diff --git a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go new file mode 100644 index 00000000..9ac39d95 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +) + +type ArtifactPackage struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + Progress string `bson:"progress" json:"progress"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + LogFile string `bson:"log_file" json:"log_file"` + + // source images + Images []*ImagesByService `bson:"images" json:"images"` + // target registries to push images + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + // target registries to push images + TargetRegistries []string `bson:"target_registries" json:"target_registries"` +} + +func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(ri, &task); err != nil { + return nil, fmt.Errorf("convert ReleaseImageTask to interface error: %v", err) + } + return task, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index cd6fb626..caca4a5a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -133,6 +133,9 @@ type ReleaseConfig struct { // PredatorImage sets docker build image // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string + // PackagerImage sets docker build image + // e.g. xxx.com/resources/predator-plugin:v0.1.0 + PackagerImage string } type ImageReleaseConfig struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index d6a0d975..e3f77606 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -64,6 +64,8 @@ type Task struct { TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + // ArtifactPackageTaskArgs arguments for artifact-package type tasks + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` // ConfigPayload 系统配置信息 ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -243,6 +245,25 @@ type ServiceTaskArgs struct { Updatable bool `bson:"updatable" json:"updatable"` } +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url" yaml:"image_url"` + ImageName string `bson:"image_name" json:"image_name" yaml:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag" yaml:"image_tag"` + RegistryID string `bson:"registry_id" json:"registry_id" yaml:"registry_id"` +} + +type ImagesByService struct { + ServiceName string `bson:"service_name" json:"service_name" yaml:"service_name"` + Images []*ImageData `bson:"images" json:"images" yaml:"images"` +} + +type ArtifactPackageTaskArgs struct { + ProductName string `bson:"product_name" json:"product_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + TargetRegistries []string `bson:"target_registries" json:"target_registries"` +} + type ProductService struct { ServiceName string `bson:"service_name" json:"service_name"` ProductName string `bson:"product_name" json:"product_name"` diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 02085f39..df2c1159 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -50,6 +50,7 @@ const ( ENVReaperImage = "REAPER_IMAGE" ENVReaperBinaryFile = "REAPER_BINARY_FILE" ENVPredatorImage = "PREDATOR_IMAGE" + EnvPackagerImage = "PACKAGER_IMAGE" ENVDockerHosts = "DOCKER_HOSTS" @@ -299,6 +300,10 @@ const ( ReleaseImageJob = "docker-release" ) +const ( + BuildChartPackage = "chart-package" +) + const ( JenkinsBuildJob = "jenkins-build" ) @@ -525,6 +530,8 @@ const MaxTries = 1 const DogFood = "/var/run/koderover-dog-food" +const ProgressFile = "/var/log/job-progress" + const ( ResponseError = "error" ResponseData = "response" -- Gitee From 3c001439de472b740df5ae98570ad27442c7b917 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 14:09:21 +0800 Subject: [PATCH 0960/2647] fix image missing bug Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 2 - .../packager/core/service/packager.go | 8 +- .../packager/core/service/service.go | 85 ++++++++++++++++--- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 961cdcfb..7bba266c 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -1348,8 +1348,6 @@ func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, err return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") } - log.Infof("###### the raw data is %s", string(b)) - index, err := helm.LoadIndex(b) if err != nil { return nil, errors.Wrapf(err, "failed to parse index.yaml") diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go index d58b587b..8f9e41da 100644 --- a/pkg/microservice/packager/core/service/packager.go +++ b/pkg/microservice/packager/core/service/packager.go @@ -17,10 +17,10 @@ limitations under the License. package service type ImageData struct { - ImageUrl string `yaml:"image_url"` - ImageName string `yaml:"image_name"` - ImageTag string `yaml:"image_tag"` - RegistryID string `yaml:"registry_id,omitempty"` + ImageUrl string `yaml:"image_url" json:"image_url"` + ImageName string `yaml:"image_name" json:"image_name"` + ImageTag string `yaml:"image_tag" json:"image_tag"` + RegistryID string `yaml:"registry_id" json:"registry_id"` } // ImagesByService defines all images in a service diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 339ab5de..ba4e036e 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -22,6 +22,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io" "os" "path/filepath" "strings" @@ -93,15 +94,68 @@ func buildTargetImage(imageName, imageTag, host, nameSpace string) string { return ret } -func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) error { +func pullImage(dockerClient *client.Client, imageUrl string, options *types.ImagePullOptions) error { + // pull image + pullResponse, err := dockerClient.ImagePull(context.TODO(), imageUrl, *options) + if err != nil { + return err + } + + defer func(pullResponse io.ReadCloser) { + err := pullResponse.Close() + if err != nil { + log.Errorf("failed to close response reader") + } + }(pullResponse) + + bs, err := io.ReadAll(pullResponse) + if err != nil { + return err + } + + if strings.Contains(string(bs), "error") { + log.Errorf("image push failed: %s", string(bs)) + return fmt.Errorf("failed to push image") + } + return nil +} + +func pushImage(dockerClient *client.Client, targetImageUrl string, options *types.ImagePushOptions) error { + pushResponse, err := dockerClient.ImagePush(context.TODO(), targetImageUrl, *options) + if err != nil { + return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + } + + defer func(pullResponse io.ReadCloser) { + err := pullResponse.Close() + if err != nil { + log.Errorf("failed to close response reader") + } + }(pushResponse) + + bs, err := io.ReadAll(pushResponse) + if err != nil { + return err + } + + if strings.Contains(string(bs), "error") { + log.Errorf("image push failed: %s", string(bs)) + return fmt.Errorf("failed to push image") + } + return nil +} + +func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) ([]*ImageData, error) { targetImageUrlByRepo := make(map[string][]string) + retImages := make([]*ImageData, 0) + for _, singleImage := range imageByService.Images { options := types.ImagePullOptions{} // for images from public repo,registryID won't be appointed if len(singleImage.RegistryID) > 0 { registryInfo, ok := allRegistries[singleImage.RegistryID] if !ok { - return fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) + return nil, fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) } encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ Username: registryInfo.UserName, @@ -109,15 +163,15 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ServerAddress: registryInfo.Host, }) if err != nil { - return errors.Wrapf(err, "faied to create docker pull auth data") + return nil, errors.Wrapf(err, "faied to create docker pull auth data") } options.RegistryAuth = encodedAuth } // pull image - _, err := dockerClient.ImagePull(context.TODO(), singleImage.ImageUrl, options) + err := pullImage(dockerClient, singleImage.ImageUrl, &options) if err != nil { - return errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) + return nil, errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) } // tag image @@ -125,9 +179,15 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri targetImage := buildTargetImage(singleImage.ImageName, singleImage.ImageTag, registry.Host, registry.Namespace) err = dockerClient.ImageTag(context.TODO(), singleImage.ImageUrl, targetImage) if err != nil { - return errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) + return nil, errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) } targetImageUrlByRepo[registry.RegistryID] = append(targetImageUrlByRepo[registry.RegistryID], targetImage) + retImages = append(retImages, &ImageData{ + ImageUrl: targetImage, + ImageName: singleImage.ImageName, + ImageTag: singleImage.ImageTag, + RegistryID: singleImage.RegistryID, + }) } } @@ -139,19 +199,20 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ServerAddress: registry.Host, }) if err != nil { - return errors.Wrapf(err, "faied to create docker push auth data") + return nil, errors.Wrapf(err, "faied to create docker push auth data") } options := types.ImagePushOptions{ RegistryAuth: encodedAuth, } + for _, targetImageUrl := range targetImageUrlByRepo[registry.RegistryID] { - _, err = dockerClient.ImagePush(context.TODO(), targetImageUrl, options) + err = pushImage(dockerClient, targetImageUrl, &options) if err != nil { - return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + return nil, errors.Wrapf(err, "failed to push image: %s", targetImageUrl) } } } - return nil + return retImages, nil } func (p *Packager) Exec() error { @@ -201,7 +262,7 @@ func (p *Packager) Exec() error { result := &PackageResult{ ServiceName: imageByService.ServiceName, } - err = handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) + images, err := handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) if err != nil { result.Result = "failed" @@ -209,7 +270,7 @@ func (p *Packager) Exec() error { log.Errorf("[result][fail][%s][%s]", imageByService.ServiceName, err) } else { result.Result = "success" - result.ImageData = imageByService.Images + result.ImageData = images log.Infof("[result][success][%s]", imageByService.ServiceName) } -- Gitee From b4a360f624ab9c208cbedc1dafd4ce20af623164 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 10 Dec 2021 14:11:22 +0800 Subject: [PATCH 0961/2647] fix conflict Signed-off-by: panxunying --- .../aslan/core/environment/service/environment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index b1d5206e..3e74fc9a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -550,7 +550,7 @@ func UpdateProductRegistry(namespace, registryID string, log *zap.SugaredLogger) log.Errorf("UpdateProductRegistry UpdateRegistry by namespace:%s registryID:%s error: %v", namespace, registryID, err) return e.ErrUpdateEnv.AddErr(err) } - kubeClient, err := kube.GetKubeClient(exitedProd.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } @@ -1025,7 +1025,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string if err != nil { return err } - kubeClient, err := kube.GetKubeClient(product.ClusterID) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) return err -- Gitee From 58e1ad95c72a07c16410e8d0be705924ae4b7f12 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 10 Dec 2021 14:15:11 +0800 Subject: [PATCH 0962/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/service/registry.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 301e0224..62c35be8 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -39,7 +39,7 @@ func findRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.Re resp, err := mongodb.NewRegistryNamespaceColl().Find(regOps) if err != nil { - log.Warnf("RegistryNamespace.Find error: %v", err) + log.Warnf("RegistryNamespace.Find error: %s", err) resp = &models.RegistryNamespace{ RegAddr: config.RegistryAddress(), AccessKey: config.RegistryAccessKey(), @@ -67,8 +67,8 @@ func FindDefaultRegistry(log *zap.SugaredLogger) (*models.RegistryNamespace, err func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{IsDefault: true}) if err != nil { - log.Errorf("get default registry error: %v", err) - return resp, fmt.Errorf("get default registry error: %v", err) + log.Errorf("get default registry error: %s", err) + return resp, fmt.Errorf("get default registry error: %s", err) } return resp, nil } @@ -76,8 +76,8 @@ func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamesp func ListRegistryNamespaces(log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) if err != nil { - log.Errorf("RegistryNamespace.List error: %v", err) - return resp, fmt.Errorf("RegistryNamespace.List error: %v", err) + log.Errorf("RegistryNamespace.List error: %s", err) + return resp, fmt.Errorf("RegistryNamespace.List error: %s", err) } return resp, nil } @@ -89,7 +89,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient reg, err = FindRegistryById(registryId, log) if err != nil { log.Errorf( - "service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", + "service.EnsureRegistrySecret: failed to find registry: %s error msg:%s", registryId, err, ) return err @@ -98,7 +98,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient reg, err = FindDefaultRegistry(log) if err != nil { log.Errorf( - "service.EnsureRegistrySecret: failed to find default candidate registry: %s %v", + "service.EnsureRegistrySecret: failed to find default candidate registry: %s %s", namespace, err, ) return err @@ -107,7 +107,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient err = kube.CreateOrUpdateRegistrySecret(namespace, reg, kubeClient) if err != nil { - log.Errorf("[%s] CreateDockerSecret error: %v", namespace, err) + log.Errorf("[%s] CreateDockerSecret error: %s", namespace, err) return e.ErrUpdateSecret.AddDesc(e.CreateDefaultRegistryErrMsg) } -- Gitee From c51ad65f638cb22415f6ba914586b928540d3c40 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 10 Dec 2021 14:24:40 +0800 Subject: [PATCH 0963/2647] filter k8s default namespaces Signed-off-by: liu deyi --- pkg/microservice/aslan/core/environment/service/kube.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index 7c8e0fa3..277fcf60 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -122,7 +123,7 @@ func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resou } return resp, err } - + filterK8sNamespaces := sets.NewString("kube-node-lease", "kube-public", "kube-system") for _, namespace := range namespaces { if value, IsExist := namespace.Labels[setting.EnvCreatedBy]; IsExist { if value == setting.EnvCreator { @@ -130,6 +131,10 @@ func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resou } } + if filterK8sNamespaces.Has(namespace.Name) { + continue + } + resp = append(resp, wrapper.Namespace(namespace).Resource()) } -- Gitee From 91321d8b09d56ca2ff631ece8b6366e924b49987 Mon Sep 17 00:00:00 2001 From: Landy Date: Fri, 10 Dec 2021 15:28:41 +0800 Subject: [PATCH 0964/2647] Revert "create delivery version" --- go.mod | 1 - go.sum | 5 - pkg/config/config.go | 4 - pkg/microservice/aslan/config/config.go | 4 - pkg/microservice/aslan/config/consts.go | 1 - .../repository/models/delivery_distribute.go | 23 +- .../repository/models/delivery_version.go | 34 +- .../core/common/repository/models/queue.go | 27 +- .../models/task/artifact_package.go | 25 - .../common/repository/models/task/model.go | 12 +- .../repository/mongodb/delivery_distribute.go | 9 +- .../repository/mongodb/delivery_version.go | 21 +- .../common/repository/mongodb/helm_repo.go | 32 +- .../repository/mongodb/template/product.go | 37 +- .../aslan/core/common/service/fs/s3.go | 30 +- .../aslan/core/common/service/s3/s3.go | 10 - .../aslan/core/common/service/service.go | 54 +- .../aslan/core/delivery/handler/router.go | 8 - .../aslan/core/delivery/handler/version.go | 283 ++-- .../aslan/core/delivery/service/docker.go | 57 - .../aslan/core/delivery/service/version.go | 1472 +---------------- .../aslan/core/environment/handler/helm.go | 40 - .../aslan/core/environment/handler/router.go | 3 - .../aslan/core/environment/service/helm.go | 259 --- .../aslan/core/project/service/project.go | 26 +- .../aslan/core/service/handler/helm.go | 21 +- .../aslan/core/service/service/helm.go | 60 +- .../aslan/core/system/handler/helm.go | 1 - .../service/workflow/artifact_task.go | 23 +- .../packager/core/service/packager.go | 8 +- .../packager/core/service/service.go | 85 +- .../service/types/task/artifact_package.go | 2 +- .../core/service/types/task/config_payload.go | 2 +- pkg/setting/consts.go | 25 - pkg/tool/errors/http_errors.go | 6 - 35 files changed, 314 insertions(+), 2396 deletions(-) delete mode 100644 pkg/microservice/aslan/core/delivery/service/docker.go delete mode 100644 pkg/microservice/aslan/core/environment/handler/helm.go delete mode 100644 pkg/microservice/aslan/core/environment/service/helm.go diff --git a/go.mod b/go.mod index 0f9924dc..c00c9190 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v4 v4.1.1 - github.com/chartmuseum/helm-push v0.10.1 github.com/coocood/freecache v1.1.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b diff --git a/go.sum b/go.sum index 123658a6..79c316c2 100644 --- a/go.sum +++ b/go.sum @@ -286,8 +286,6 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/chartmuseum/helm-push v0.10.1 h1:NqStAmarEy0GnrDCk0zubGKeRmkhOm/rRi5au8h76BA= -github.com/chartmuseum/helm-push v0.10.1/go.mod h1:s6xTICU31jKdLkOXS+GgaR61E+oU4h8TWb1yZcHq8OE= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -539,7 +537,6 @@ github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -2353,8 +2350,6 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao= -k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= diff --git a/pkg/config/config.go b/pkg/config/config.go index 4f2ef7d9..593eff9c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -210,10 +210,6 @@ func ObjectStorageTemplatePath(name, kind string) string { return filepath.Join("templates", kind, name) } -func ObjectStorageDeliveryVersionPath(project string) string { - return filepath.Join("delivery-distributes", "files", project) -} - func ObjectStorageChartTemplatePath(name string) string { return ObjectStorageTemplatePath(name, setting.ChartTemplatesPath) } diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 121add2c..edca3a57 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -234,10 +234,6 @@ func LocalServicePathWithRevision(project, service string, revision int64) strin return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("%d", revision)) } -func LocalDeliveryChartPathWithRevision(project, service string, revision int64) string { - return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("delivery/%d", revision)) -} - func ServiceNameWithRevision(serviceName string, revision int64) string { return fmt.Sprintf("%s-%d", serviceName, revision) } diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 9b91eb4c..99f46821 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -141,7 +141,6 @@ type DistributeType string const ( File DistributeType = "file" Image DistributeType = "image" - Chart DistributeType = "chart" ) type NotifyType int diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go index 4365fa13..db24294d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go @@ -25,23 +25,16 @@ import ( type DeliveryDistribute struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ReleaseID primitive.ObjectID `bson:"release_id" json:"releaseId"` - ServiceName string `bson:"service_name" json:"serviceName,omitempty"` + ServiceName string `bson:"service_name" json:"serviceName"` DistributeType config.DistributeType `bson:"distribute_type" json:"distributeType"` RegistryName string `bson:"registry_name" json:"registryName"` - ChartVersion string `bson:"chart_version" json:"chartVersion,omitempty"` - ChartName string `bson:"chart_name" json:"chartName,omitempty"` - ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName,omitempty"` - SubDistributes []*DeliveryDistribute `bson:"-" json:"subDistributes,omitempty"` - Namespace string `bson:"namespace" json:"namespace,omitempty"` - PackageFile string `bson:"package_file" json:"packageFile,omitempty"` - RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey,omitempty"` - DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl,omitempty"` - S3StorageID string `bson:"s3_storage_id" json:"s3StorageID"` - StorageURL string `bson:"-" json:"storageUrl"` - StorageBucket string `bson:"-" json:"storageBucket"` - SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl,omitempty"` - StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty,omitempty"` - EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty,omitempty"` + Namespace string `bson:"namespace" json:"namespace"` + PackageFile string `bson:"package_file" json:"packageFile"` + RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey"` + DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl"` + SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index ed851379..459d7f96 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -20,30 +20,18 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -type DeliveryVersionProgress struct { - SuccessChartCount int `json:"successChartCount"` - TotalChartCount int `json:"totalChartCount"` - PackageUploadStatus string `json:"packageStatus"` - Error string `json:"error"` -} - type DeliveryVersion struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Version string `bson:"version" json:"version"` - ProductName string `bson:"product_name" json:"productName"` - WorkflowName string `bson:"workflow_name" json:"workflowName"` - Type string `bson:"type" json:"type"` - TaskID int `bson:"task_id" json:"taskId"` - Desc string `bson:"desc" json:"desc"` - Labels []string `bson:"labels" json:"labels"` - ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` - Status string `bson:"status" json:"status"` - Error string `bson:"error" json:"-"` - Progress *DeliveryVersionProgress `bson:"-" json:"progress"` - CreateArgument interface{} `bson:"createArgument" json:"-"` - CreatedBy string `bson:"created_by" json:"createdBy"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Version string `bson:"version" json:"version"` + ProductName string `bson:"product_name" json:"productName"` + WorkflowName string `bson:"workflow_name" json:"workflowName"` + TaskID int `bson:"task_id" json:"taskId"` + Desc string `bson:"desc" json:"desc"` + Labels []string `bson:"labels" json:"labels"` + ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` + CreatedBy string `bson:"created_by" json:"createdBy"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } func (DeliveryVersion) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 5c472c3e..1a7247d3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -49,13 +49,13 @@ type Queue struct { IsArchived bool `bson:"is_archived" json:"is_archived"` AgentID string `bson:"agent_id" json:"agent_id"` MultiRun bool `bson:"multi_run" json:"multi_run"` - Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` + Target string `bson:"target,omitempty" json:"target"` // target 服务名称, k8s为容器名称, 物理机为服务名 + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` // 使用预定义编译管理模块中的内容生成SubTasks, 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer 如果为空,则使用pipeline自定义SubTasks ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs 单服务工作流任务参数 + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs 测试任务参数 + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -96,10 +96,9 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" json:"image_tag"` - RegistryID string `bson:"registry_id" json:"registry_id"` + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` } type ImagesByService struct { @@ -109,10 +108,10 @@ type ImagesByService struct { type ArtifactPackageTaskArgs struct { ProjectName string `bson:"project_name" json:"project_name"` - EnvName string `bson:"env_name" json:"env_name"` - Images []*ImagesByService `bson:"images" json:"images"` - SourceRegistries []string `json:"source_registries" json:"source_registries"` - TargetRegistries []string `json:"target_registries" json:"target_registries"` + EnvName string `bson:"env_name" json:"env_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `json:"source_registries" json:"source_registries"` + TargetRegistries []string `json:"target_registries" json:"target_registries"` } type ConfigPayload struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go index 89f89de4..8f578b62 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go @@ -17,27 +17,12 @@ limitations under the License. package task import ( - "encoding/json" "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) -type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageNamespace string `bson:"image_namespace" json:"image_namespace"` - ImageTag string `bson:"image_tag" json:"image_tag"` -} - -type ServicePackageResult struct { - ServiceName string `json:"service_name"` - Result string `json:"result"` - ErrorMsg string `json:"error_msg"` - ImageData []*ImageData `json:"image_data"` -} - type ArtifactPackage struct { TaskType config.TaskType `bson:"type" json:"type"` TaskStatus config.Status `bson:"status" json:"status"` @@ -47,7 +32,6 @@ type ArtifactPackage struct { StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` - Progress string `bson:"progress" json:"progress"` // source images Images []*models.ImagesByService `bson:"images" json:"images"` @@ -64,12 +48,3 @@ func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { } return task, nil } - -func (ri *ArtifactPackage) GetProgress() ([]*ServicePackageResult, error) { - if len(ri.Progress) == 0 { - return nil, nil - } - ret := make([]*ServicePackageResult, 0) - err := json.Unmarshal([]byte(ri.Progress), &ret) - return ret, err -} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index d4783d2e..d1d23f20 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -76,12 +76,12 @@ type Task struct { StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `bson:"resetImage" json:"resetImage"` - TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `json:"resetImage" bson:"resetImage"` + TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go index 98a9c39e..0b3ce01a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go @@ -35,7 +35,6 @@ type DeliveryDistributeArgs struct { ID string `json:"id"` ReleaseID string `json:"releaseId"` ServiceName string `json:"serviceName"` - ChartName string `json:"chartName"` DistributeType config.DistributeType `json:"distributeType"` } @@ -63,7 +62,6 @@ func (c *DeliveryDistributeColl) EnsureIndex(ctx context.Context) error { Keys: bson.D{ bson.E{Key: "release_id", Value: 1}, bson.E{Key: "service_name", Value: 1}, - bson.E{Key: "chart_name", Value: 1}, bson.E{Key: "deleted_at", Value: 1}, }, Options: options.Index().SetUnique(false), @@ -117,11 +115,8 @@ func (c *DeliveryDistributeColl) Find(args *DeliveryDistributeArgs) ([]*models.D } query := bson.M{"release_id": releaseID, "deleted_at": 0} - if len(args.DistributeType) > 0 { - query["distribute_type"] = string(args.DistributeType) - } - if len(args.ChartName) > 0 { - query["chart_name"] = args.ChartName + if args.DistributeType != "" { + query["distribute_type"] = config.File } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index e0adcfc8..bae5fc66 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -34,7 +34,6 @@ import ( type DeliveryVersionArgs struct { ID string `json:"id"` ProductName string `json:"productName"` - Version string `json:"version"` WorkflowName string `json:"workflowName"` TaskID int `json:"taskId"` PerPage int `json:"perPage"` @@ -90,6 +89,7 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver return nil, errors.New("nil delivery_version args") } + var resp []*models.DeliveryVersion query := bson.M{"deleted_at": 0} if args.ProductName != "" { query["product_name"] = args.ProductName @@ -113,7 +113,6 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver return nil, err } - resp := make([]*models.DeliveryVersion, 0) err = cursor.All(ctx, &resp) if err != nil { return nil, err @@ -165,13 +164,7 @@ func (c *DeliveryVersionColl) Get(args *DeliveryVersionArgs) (*models.DeliveryVe resp := new(models.DeliveryVersion) var query map[string]interface{} if args.ID != "" { - oid, err := primitive.ObjectIDFromHex(args.ID) - if err != nil { - return nil, err - } - query = bson.M{"_id": oid, "deleted_at": 0} - } else if len(args.Version) > 0 { - query = bson.M{"product_name": args.ProductName, "version": args.Version, "deleted_at": 0} + query = bson.M{"_id": args.ID, "deleted_at": 0} } else { query = bson.M{"product_name": args.ProductName, "workflow_name": args.WorkflowName, "task_id": args.TaskID, "deleted_at": 0} } @@ -197,16 +190,6 @@ func (c *DeliveryVersionColl) Insert(args *models.DeliveryVersion) error { return nil } -func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr string) error { - query := bson.M{"version": versionName, "deleted_at": 0} - change := bson.M{"$set": bson.M{ - "status": status, - "error": errorStr, - }} - _, err := c.UpdateOne(context.TODO(), query, change) - return err -} - func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { if args == nil { return errors.New("nil delivery_version args") diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go b/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go index 86fff875..6d773167 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go @@ -37,11 +37,6 @@ type HelmRepoColl struct { coll string } -type HelmRepoFindOption struct { - Id string - RepoName string -} - func NewHelmRepoColl() *HelmRepoColl { name := models.HelmRepo{}.TableName() coll := &HelmRepoColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} @@ -54,12 +49,7 @@ func (c *HelmRepoColl) GetCollectionName() string { } func (c *HelmRepoColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.M{"repo_name": 1}, - Options: options.Index().SetUnique(false), - } - _, err := c.Indexes().CreateOne(ctx, mod) - return err + return nil } func (c *HelmRepoColl) Create(args *models.HelmRepo) error { @@ -74,26 +64,6 @@ func (c *HelmRepoColl) Create(args *models.HelmRepo) error { return err } -func (c *HelmRepoColl) Find(opt *HelmRepoFindOption) (*models.HelmRepo, error) { - query := bson.M{} - if len(opt.Id) > 0 { - oid, err := primitive.ObjectIDFromHex(opt.Id) - if err != nil { - return nil, err - } - query["_id"] = oid - } - if len(opt.RepoName) > 0 { - query["repo_name"] = opt.RepoName - } - ret := new(models.HelmRepo) - err := c.FindOne(context.TODO(), query).Decode(ret) - if err != nil { - return nil, err - } - return ret, nil -} - func (c *HelmRepoColl) Update(id string, args *models.HelmRepo) error { oid, err := primitive.ObjectIDFromHex(id) if err != nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 474d2e96..4cf141c2 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -39,7 +39,6 @@ type ProjectInfo struct { UpdatedBy string `bson:"update_by"` OnboardStatus int `bson:"onboarding_status"` Public bool `bson:"public"` - DeployType string `bson:"deploy_type"` } type ProductColl struct { @@ -83,8 +82,8 @@ func (c *ProductColl) FindProjectName(project string) (*template.Product, error) } func (c *ProductColl) ListNames(inNames []string) ([]string, error) { - res, err := c.listProjects(inNames, bson.M{ - "product_name": "$product_name", + res, err := c.listProjects(inNames, bson.D{ + {"product_name", 1}, }) if err != nil { return nil, err @@ -99,34 +98,29 @@ func (c *ProductColl) ListNames(inNames []string) ([]string, error) { } func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error) { - return c.listProjects(inNames, bson.M{ - "product_name": "$product_name", - "project_name": "$project_name", - "description": "$description", - "update_time": "$update_time", - "update_by": "$update_by", - "onboarding_status": "$onboarding_status", - "public": "$public", - "deploy_type": "$product_feature.deploy_type", + return c.listProjects(inNames, bson.D{ + {"product_name", 1}, + {"project_name", 1}, + {"description", 1}, + {"update_time", 1}, + {"update_by", 1}, + {"onboarding_status", 1}, + {"public", 1}, }) } -func (c *ProductColl) listProjects(inNames []string, projection bson.M) ([]*ProjectInfo, error) { +func (c *ProductColl) listProjects(inNames []string, projection bson.D) ([]*ProjectInfo, error) { + opts := options.Find() filter := bson.M{} if len(inNames) > 0 { filter["product_name"] = bson.M{"$in": inNames} } - pipeline := []bson.M{ - { - "$match": filter, - }, - { - "$project": projection, - }, + if len(projection) > 0 { + opts.SetProjection(projection) } - cursor, err := c.Collection.Aggregate(context.TODO(), pipeline) + cursor, err := c.Collection.Find(context.TODO(), filter, opts) if err != nil { return nil, err } @@ -136,6 +130,7 @@ func (c *ProductColl) listProjects(inNames []string, projection bson.M) ([]*Proj if err != nil { return nil, err } + return res, nil } diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index dfe02ea2..67c5db65 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -24,35 +24,15 @@ import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" fsutil "github.com/koderover/zadig/pkg/util/fs" ) -func ArchiveAndUploadFilesToSpecifiedS3(fileTree fs.FS, names []string, s3Base, s3Id string, logger *zap.SugaredLogger) error { - s3Storage, err := s3service.FindS3ById(s3Id) - if err != nil { - logger.Errorf("Failed to find default s3, err:%v", err) - return err - } - return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) -} - -func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, logger *zap.SugaredLogger) error { - s3Storage, err := s3service.FindDefaultS3() - if err != nil { - logger.Errorf("Failed to find default s3, err:%v", err) - return err - } - return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) -} - -// archiveAndUploadFiles archive local files and upload to default s3 storage +// ArchiveAndUploadFilesToS3 archive local files and upload to default s3 storage // if multiple names appointed, s3storage.copy will be used to handle extra names -func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Storage *s3.S3, logger *zap.SugaredLogger) error { - +func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, logger *zap.SugaredLogger) error { if len(names) == 0 { return fmt.Errorf("names not appointed") } @@ -73,7 +53,11 @@ func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Stor logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) return err } - + s3Storage, err := s3service.FindDefaultS3() + if err != nil { + logger.Errorf("Failed to find default s3, err:%v", err) + return err + } forcedPathStyle := true if s3Storage.Provider == setting.ProviderSourceAli { forcedPathStyle = false diff --git a/pkg/microservice/aslan/core/common/service/s3/s3.go b/pkg/microservice/aslan/core/common/service/s3/s3.go index fc363b61..24655148 100644 --- a/pkg/microservice/aslan/core/common/service/s3/s3.go +++ b/pkg/microservice/aslan/core/common/service/s3/s3.go @@ -148,16 +148,6 @@ func FindDefaultS3() (*S3, error) { return &S3{S3Storage: storage}, nil } -func FindS3ById(id string) (*S3, error) { - storage, err := commonrepo.NewS3StorageColl().Find(id) - if err != nil { - log.Warnf("Failed to find s3 in db, err: %s", err) - return nil, err - } - - return &S3{S3Storage: storage}, nil -} - // 获取内置的s3 func FindInternalS3() *S3 { storage := &models.S3Storage{ diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index fbcf8086..78040931 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -21,7 +21,6 @@ import ( "encoding/json" "errors" "fmt" - "net/url" "regexp" "strings" templ "text/template" @@ -91,9 +90,8 @@ type ServiceProductMap struct { } var ( - imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) - imageSpaceParseRegex = regexp.MustCompile(`\/(.*?)\/`) - presetPatterns = []map[string]string{ + imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) + presetPatterns = []map[string]string{ {setting.PathSearchComponentImage: "image.repository", setting.PathSearchComponentTag: "image.tag"}, {setting.PathSearchComponentImage: "image"}, } @@ -616,54 +614,6 @@ func ExtractImageName(imageURI string) string { return "" } -// ExtractImageRegistry extract registry url from total image uri -func ExtractImageRegistry(imageURI string) (string, error) { - subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) - exNames := imageParseRegex.SubexpNames() - for i, matchedStr := range subMatchAll { - if i != 0 && matchedStr != "" && matchedStr != ":" { - if exNames[i] == "repo" { - u, err := url.Parse(matchedStr) - if err != nil { - return "", err - } - if len(u.Scheme) > 0 { - matchedStr = strings.TrimPrefix(matchedStr, fmt.Sprintf("%s://", u.Scheme)) - } - return matchedStr, nil - } - } - } - return "", fmt.Errorf("failed to extract registry url") -} - -// ExtractImageTag extract image tag from total image uri -func ExtractImageTag(imageURI string) string { - subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) - exNames := imageParseRegex.SubexpNames() - for i, matchedStr := range subMatchAll { - if i != 0 && matchedStr != "" && matchedStr != ":" { - if exNames[i] == "tag" { - return matchedStr - } - } - } - return "" -} - -// ExtractRegistryNamespace extract registry namespace form image uri -func ExtractRegistryNamespace(imageURI string) string { - imageURI = strings.TrimPrefix(imageURI, "http://") - imageURI = strings.TrimPrefix(imageURI, "https://") - - firstIndex := strings.Index(imageURI, "/") - lastIndex := strings.LastIndex(imageURI, "/") - if lastIndex == firstIndex { - return "" - } - return strings.TrimPrefix(imageURI[firstIndex:lastIndex], "/") -} - func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 7b58bcad..889b7e7a 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -45,14 +45,6 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.GET("/:id", GetDeliveryVersion) deliveryRelease.GET("", ListDeliveryVersion) deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) - - deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) - deliveryRelease.POST("/helm/global-variables", ApplyDeliveryGlobalVariables) - deliveryRelease.GET("/helm/charts", DownloadDeliveryChart) - deliveryRelease.GET("/helm/charts/version", GetChartVersionFromRepo) - deliveryRelease.GET("/helm/charts/preview", PreviewGetDeliveryChart) - deliveryRelease.GET("/helm/charts/filePath", GetDeliveryChartFilePath) - deliveryRelease.GET("/helm/charts/fileContent", GetDeliveryChartFileContent) } deliveryPackage := router.Group("packages") diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 39a4e551..44ea5e99 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -17,15 +17,19 @@ limitations under the License. package handler import ( + "encoding/json" "fmt" - "net/http" + "strconv" "strings" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" deliveryservice "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -61,31 +65,189 @@ func GetDeliveryVersion(c *gin.Context) { } version := new(commonrepo.DeliveryVersionArgs) version.ID = ID - ctx.Resp, ctx.Err = deliveryservice.GetDetailReleaseData(version, ctx.Logger) + ctx.Resp, ctx.Err = deliveryservice.GetDeliveryVersion(version, ctx.Logger) +} + +type ReleaseInfo struct { + VersionInfo *commonmodels.DeliveryVersion `json:"versionInfo"` + BuildInfo []*commonmodels.DeliveryBuild `json:"buildInfo"` + DeployInfo []*commonmodels.DeliveryDeploy `json:"deployInfo"` + TestInfo []*commonmodels.DeliveryTest `json:"testInfo"` + DistributeInfo []*commonmodels.DeliveryDistribute `json:"distributeInfo"` + SecurityInfo []*DeliverySecurityStats `json:"securityStatsInfo"` +} + +type DeliverySecurityStats struct { + ImageName string `json:"imageName"` + ImageID string `json:"imageId"` + DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` +} + +type DeliverySecurityStatsInfo struct { + Total int `json:"total"` + Unknown int `json:"unkown"` + Negligible int `json:"negligible"` + Low int `json:"low"` + Medium int `json:"medium"` + High int `json:"high"` + Critical int `json:"critical"` } func ListDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(deliveryservice.ListDeliveryVersionArgs) - err := c.BindQuery(args) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return + taskIDStr := c.Query("taskId") + var taskID = 0 + var err error + if taskIDStr != "" { + taskID, err = strconv.Atoi(taskIDStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } } - if args.Page <= 0 { - args.Page = 1 + perPageStr := c.Query("per_page") + pageStr := c.Query("page") + var ( + perPage int + page int + ) + if perPageStr == "" { + perPage = 20 + } else { + perPage, err = strconv.Atoi(perPageStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("perPage args err :%s", err)) + return + } } - if args.PerPage <= 0 { - args.PerPage = 20 + + if pageStr == "" { + page = 1 + } else { + page, err = strconv.Atoi(pageStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("page args err :%s", err)) + return + } } - if len(args.Verbosity) == 0 { - args.Verbosity = deliveryservice.VerbosityDetailed + + serviceName := c.Query("serviceName") + + version := new(commonrepo.DeliveryVersionArgs) + version.ProductName = c.Query("projectName") + version.WorkflowName = c.Query("workflowName") + version.TaskID = taskID + version.PerPage = perPage + version.Page = page + deliveryVersions, err := deliveryservice.FindDeliveryVersion(version, ctx.Logger) + + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } + releaseInfos := make([]*ReleaseInfo, 0) + for _, deliveryVersion := range deliveryVersions { + releaseInfo := new(ReleaseInfo) + //versionInfo + releaseInfo.VersionInfo = deliveryVersion + + //deployInfo + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := deliveryservice.FindDeliveryDeploy(deliveryDeployArgs, ctx.Logger) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + } + // 查询条件serviceName + if serviceName != "" { + match := false + for _, deliveryDeploy := range deliveryDeploys { + if deliveryDeploy.ServiceName == serviceName { + match = true + break + } + } + if !match { + continue + } + } + // 将serviceName替换为服务名/服务组件的形式,用于前端展示 + for _, deliveryDeploy := range deliveryDeploys { + if deliveryDeploy.ContainerName != "" { + deliveryDeploy.ServiceName = deliveryDeploy.ServiceName + "/" + deliveryDeploy.ContainerName + } + } + releaseInfo.DeployInfo = deliveryDeploys + + //buildInfo + deliveryBuildArgs := new(commonrepo.DeliveryBuildArgs) + deliveryBuildArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryBuilds, err := deliveryservice.FindDeliveryBuild(deliveryBuildArgs, ctx.Logger) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + } + releaseInfo.BuildInfo = deliveryBuilds + + //testInfo + deliveryTestArgs := new(commonrepo.DeliveryTestArgs) + deliveryTestArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryTests, err := deliveryservice.FindDeliveryTest(deliveryTestArgs, ctx.Logger) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + } + releaseInfo.TestInfo = deliveryTests + + //securityStatsInfo + deliverySecurityStatss := make([]*DeliverySecurityStats, 0) + if pipelineTask, err := workflowservice.GetPipelineTaskV2(int64(deliveryVersion.TaskID), deliveryVersion.WorkflowName, config.WorkflowType, ctx.Logger); err == nil { + for _, subStage := range pipelineTask.Stages { + if subStage.TaskType == config.TaskSecurity { + subSecurityTaskMap := subStage.SubTasks + for _, subTask := range subSecurityTaskMap { + securityInfo, _ := base.ToSecurityTask(subTask) + + deliverySecurityStats := new(DeliverySecurityStats) + deliverySecurityStats.ImageName = securityInfo.ImageName + deliverySecurityStats.ImageID = securityInfo.ImageID + deliverySecurityStatsMap, err := deliveryservice.FindDeliverySecurityStatistics(securityInfo.ImageID, ctx.Logger) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + } + var transErr error + b, err := json.Marshal(deliverySecurityStatsMap) + if err != nil { + transErr = fmt.Errorf("marshal task error: %v", err) + } + + if err := json.Unmarshal(b, &deliverySecurityStats.DeliverySecurityStatsInfo); err != nil { + transErr = fmt.Errorf("unmarshal task error: %v", err) + } + if transErr != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + } + + deliverySecurityStatss = append(deliverySecurityStatss, deliverySecurityStats) + } + break + } + } + releaseInfo.SecurityInfo = deliverySecurityStatss + } + + //distributeInfo + deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) + deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDistributes, _ := deliveryservice.FindDeliveryDistribute(deliveryDistributeArgs, ctx.Logger) + + releaseInfo.DistributeInfo = deliveryDistributes - ctx.Resp, ctx.Err = deliveryservice.ListDeliveryVersion(args, ctx.Logger) + releaseInfos = append(releaseInfos, releaseInfo) + } + ctx.Err = err + ctx.Resp = releaseInfos } func getFileName(fileName string) string { @@ -164,20 +326,6 @@ func ListPackagesVersion(c *gin.Context) { ctx.Resp = fileInfoList } -func CreateHelmDeliveryVersion(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(deliveryservice.CreateHelmDeliveryVersionArgs) - err := c.ShouldBindJSON(args) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return - } - args.CreateBy = ctx.UserName - ctx.Err = deliveryservice.CreateHelmDeliveryVersion(args, ctx.Logger) -} - func DeleteDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -223,82 +371,3 @@ func ListDeliveryServiceNames(c *gin.Context) { productName := c.Query("projectName") ctx.Resp, ctx.Err = deliveryservice.ListDeliveryServiceNames(productName, ctx.Logger) } - -func DownloadDeliveryChart(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - versionName := c.Query("version") - chartName := c.Query("chartName") - projectName := c.Query("projectName") - - fileBytes, fileName, err := deliveryservice.DownloadDeliveryChart(projectName, versionName, chartName, ctx.Logger) - if err != nil { - ctx.Err = err - return - } - - c.Writer.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName)) - c.Data(http.StatusOK, "application/octet-stream", fileBytes) -} - -func GetChartVersionFromRepo(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - chartName := c.Query("chartName") - chartRepoName := c.Query("chartRepoName") - - ctx.Resp, ctx.Err = deliveryservice.GetChartVersions(chartName, chartRepoName) -} - -func PreviewGetDeliveryChart(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - versionName := c.Query("version") - chartName := c.Query("chartName") - projectName := c.Query("projectName") - - ctx.Resp, ctx.Err = deliveryservice.PreviewDeliveryChart(projectName, versionName, chartName, ctx.Logger) -} - -func GetDeliveryChartFilePath(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(deliveryservice.DeliveryChartFilePathArgs) - err := c.BindQuery(args) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return - } - - ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFilePath(args, ctx.Logger) -} - -func GetDeliveryChartFileContent(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(deliveryservice.DeliveryChartFileContentArgs) - err := c.BindQuery(args) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return - } - ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFileContent(args, ctx.Logger) -} - -func ApplyDeliveryGlobalVariables(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(deliveryservice.DeliveryVariablesApplyArgs) - err := c.BindJSON(args) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return - } - ctx.Resp, ctx.Err = deliveryservice.ApplyDeliveryGlobalVariables(args, ctx.Logger) -} diff --git a/pkg/microservice/aslan/core/delivery/service/docker.go b/pkg/microservice/aslan/core/delivery/service/docker.go deleted file mode 100644 index 65a35941..00000000 --- a/pkg/microservice/aslan/core/delivery/service/docker.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import "os/exec" - -const dockerExe = "/usr/local/bin/docker" - -func dockerLogin(user, password, registry string) *exec.Cmd { - return exec.Command( - dockerExe, - "login", - "-u", user, - "-p", password, - registry, - ) -} - -func dockerInfo() *exec.Cmd { - return exec.Command(dockerExe, "info") -} - -func dockerPush(fullImage string) *exec.Cmd { - args := []string{ - "push", - fullImage, - } - return exec.Command(dockerExe, args...) -} - -func dockerPull(image string) *exec.Cmd { - args := []string{"pull", image} - return exec.Command(dockerExe, args...) -} - -func dockerTag(sourceFullImage, targetFullImage string) *exec.Cmd { - args := []string{ - "tag", - sourceFullImage, - targetFullImage, - } - return exec.Command(dockerExe, args...) -} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7bba266c..1c013f68 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -17,189 +17,21 @@ limitations under the License. package service import ( - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - "time" - - cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" - "github.com/chartmuseum/helm-push/pkg/helm" - "github.com/hashicorp/go-multierror" - "github.com/otiai10/copy" - "github.com/pkg/errors" - "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" - chartloader "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" - workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/types" - fsutil "github.com/koderover/zadig/pkg/util/fs" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" -) - -const ( - VerbosityBrief string = "brief" // brief delivery data - VerbosityDetailed string = "detailed" // detailed delivery version with total data ) -type DeliveryVersionFilter struct { - ServiceName string -} - -type CreateHelmDeliveryVersionOption struct { - EnableOfflineDist bool `json:"enableOfflineDist"` - S3StorageID string `json:"s3StorageID"` -} - -type CreateHelmDeliveryVersionChartData struct { - ServiceName string `json:"serviceName"` - Version string `json:"version,omitempty"` - ValuesYamlContent string `json:"valuesYamlContent"` -} - -type CreateHelmDeliveryVersionArgs struct { - CreateBy string `json:"-"` - ProductName string `json:"productName"` - Retry bool `json:"retry"` - Version string `json:"version"` - Desc string `json:"desc"` - EnvName string `json:"envName"` - Labels []string `json:"labels"` - ImageRepoName string `json:"imageRepoName"` - *DeliveryVersionChartData -} - -type DeliveryVersionChartData struct { - GlobalVariables string `json:"globalVariables"` - ChartRepoName string `json:"chartRepoName"` - ImageRegistryID string `json:"imageRegistryID"` - ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` - Options *CreateHelmDeliveryVersionOption `json:"options"` -} - -type DeliveryChartData struct { - ChartData *CreateHelmDeliveryVersionChartData - ServiceObj *commonmodels.Service - ProductService *commonmodels.ProductService - RenderChart *template.RenderChart - RenderSet *commonmodels.RenderSet -} - -type DeliveryChartResp struct { - FileInfos []*types.FileInfo `json:"fileInfos"` -} - -type DeliveryChartFilePathArgs struct { - Dir string `json:"dir"` - ProjectName string `json:"projectName"` - ChartName string `json:"chartName"` - Version string `json:"version"` -} - -type DeliveryChartFileContentArgs struct { - FilePath string `json:"filePath"` - FileName string `json:"fileName"` - ProjectName string `json:"projectName"` - ChartName string `json:"chartName"` - Version string `json:"version"` -} - -type DeliveryVariablesApplyArgs struct { - GlobalVariables string `json:"globalVariables,omitempty"` - ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` -} - -type ListDeliveryVersionArgs struct { - Page int `form:"page"` - PerPage int `form:"per_page"` - TaskId int `form:"taskId"` - ServiceName string `form:"serviceName"` - Verbosity string `form:"verbosity"` - ProjectName string `form:"projectName"` - WorkflowName string `form:"workflowName"` -} - -type ReleaseInfo struct { - VersionInfo *commonmodels.DeliveryVersion `json:"versionInfo"` - BuildInfo []*commonmodels.DeliveryBuild `json:"buildInfo,omitempty"` - DeployInfo []*commonmodels.DeliveryDeploy `json:"deployInfo,omitempty"` - TestInfo []*commonmodels.DeliveryTest `json:"testInfo,omitempty"` - DistributeInfo []*commonmodels.DeliveryDistribute `json:"distributeInfo,omitempty"` - SecurityInfo []*DeliverySecurityStats `json:"securityStatsInfo,omitempty"` -} - -type DeliverySecurityStatsInfo struct { - Total int `json:"total"` - Unknown int `json:"unkown"` - Negligible int `json:"negligible"` - Low int `json:"low"` - Medium int `json:"medium"` - High int `json:"high"` - Critical int `json:"critical"` -} - -type DeliverySecurityStats struct { - ImageName string `json:"imageName"` - ImageID string `json:"imageId"` - DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` -} - -type ImageUrlDetail struct { - ImageUrl string - Name string - Registry string - Tag string -} - -type ServiceImageDetails struct { - ServiceName string - Images []*ImageUrlDetail - Registries []string -} - -type ChartVersionResp struct { - ChartName string `json:"chartName"` - ChartVersion string `json:"chartVersion"` -} - func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { - versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) - if err != nil { - log.Errorf("get deliveryVersion error: %v", err) - return nil, e.ErrGetDeliveryVersion - } - return versionData, err -} - -func GetDetailReleaseData(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*ReleaseInfo, error) { - versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) + resp, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { log.Errorf("get deliveryVersion error: %v", err) return nil, e.ErrGetDeliveryVersion } - return buildListReleaseResp(VerbosityDetailed, versionData, nil, log) + return resp, err } func FindDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) ([]*commonmodels.DeliveryVersion, error) { @@ -220,1303 +52,29 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } -func filterReleases(filter *DeliveryVersionFilter, deliveryVersion *commonmodels.DeliveryVersion, logger *zap.SugaredLogger) bool { - if filter == nil { - return true - } - if filter.ServiceName != "" { - deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) - deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, logger) - if err != nil { - return true - } - match := false - for _, deliveryDeploy := range deliveryDeploys { - if deliveryDeploy.ServiceName == filter.ServiceName { - match = true - break - } - } - if !match { - return false - } - } - return true -} - -func buildBriefRelease(deliveryVersion *commonmodels.DeliveryVersion, _ *zap.SugaredLogger) (*ReleaseInfo, error) { - return &ReleaseInfo{ - VersionInfo: deliveryVersion, - }, nil -} - -func buildDetailedRelease(deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { - releaseInfo := new(ReleaseInfo) - //versionInfo - releaseInfo.VersionInfo = deliveryVersion - - //deployInfo - deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) - deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, logger) - if err != nil { - return nil, err - } - if filterOpt != nil { - if !filterReleases(filterOpt, deliveryVersion, logger) { - return nil, nil - } - } - // 将serviceName替换为服务名/服务组件的形式,用于前端展示 - for _, deliveryDeploy := range deliveryDeploys { - if deliveryDeploy.ContainerName != "" { - deliveryDeploy.ServiceName = deliveryDeploy.ServiceName + "/" + deliveryDeploy.ContainerName - } - } - releaseInfo.DeployInfo = deliveryDeploys - - //buildInfo - deliveryBuildArgs := new(commonrepo.DeliveryBuildArgs) - deliveryBuildArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryBuilds, err := FindDeliveryBuild(deliveryBuildArgs, logger) - if err != nil { - return nil, err - } - releaseInfo.BuildInfo = deliveryBuilds - - //testInfo - deliveryTestArgs := new(commonrepo.DeliveryTestArgs) - deliveryTestArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryTests, err := FindDeliveryTest(deliveryTestArgs, logger) - if err != nil { - return nil, err - } - releaseInfo.TestInfo = deliveryTests - - //securityStatsInfo - deliverySecurityStatss := make([]*DeliverySecurityStats, 0) - if pipelineTask, err := workflowservice.GetPipelineTaskV2(int64(deliveryVersion.TaskID), deliveryVersion.WorkflowName, config.WorkflowType, logger); err == nil { - for _, subStage := range pipelineTask.Stages { - if subStage.TaskType == config.TaskSecurity { - subSecurityTaskMap := subStage.SubTasks - for _, subTask := range subSecurityTaskMap { - securityInfo, _ := base.ToSecurityTask(subTask) - - deliverySecurityStats := new(DeliverySecurityStats) - deliverySecurityStats.ImageName = securityInfo.ImageName - deliverySecurityStats.ImageID = securityInfo.ImageID - deliverySecurityStatsMap, err := FindDeliverySecurityStatistics(securityInfo.ImageID, logger) - if err != nil { - return nil, err - } - var transErr error - b, err := json.Marshal(deliverySecurityStatsMap) - if err != nil { - transErr = fmt.Errorf("marshal task error: %v", err) - } - if err := json.Unmarshal(b, &deliverySecurityStats.DeliverySecurityStatsInfo); err != nil { - transErr = fmt.Errorf("unmarshal task error: %v", err) - } - if transErr != nil { - return nil, transErr - } - - deliverySecurityStatss = append(deliverySecurityStatss, deliverySecurityStats) - } - break - } - } - releaseInfo.SecurityInfo = deliverySecurityStatss - } - - //distributeInfo - deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) - deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDistributes, _ := FindDeliveryDistribute(deliveryDistributeArgs, logger) - releaseInfo.DistributeInfo = deliveryDistributes - - // fill some data for helm delivery releases - processReleaseRespData(releaseInfo) - - return releaseInfo, nil -} - -func buildListReleaseResp(verbosity string, deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { - switch verbosity { - case VerbosityBrief: - return buildBriefRelease(deliveryVersion, logger) - case VerbosityDetailed: - return buildDetailedRelease(deliveryVersion, filterOpt, logger) - default: - return buildDetailedRelease(deliveryVersion, filterOpt, logger) - } -} +func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]string, error) { + serviceNames := sets.String{} -func ListDeliveryVersion(args *ListDeliveryVersionArgs, logger *zap.SugaredLogger) ([]*ReleaseInfo, error) { - versionListArgs := new(commonrepo.DeliveryVersionArgs) - versionListArgs.ProductName = args.ProjectName - versionListArgs.WorkflowName = args.WorkflowName - versionListArgs.TaskID = args.TaskId - versionListArgs.PerPage = args.PerPage - versionListArgs.Page = args.Page - deliveryVersions, err := FindDeliveryVersion(versionListArgs, logger) + version := new(commonrepo.DeliveryVersionArgs) + version.ProductName = productName + deliveryVersions, err := FindDeliveryVersion(version, log) if err != nil { - return nil, err + log.Errorf("FindDeliveryVersion failed, err:%v", err) + return serviceNames.List(), err } - releaseInfos := make([]*ReleaseInfo, 0) for _, deliveryVersion := range deliveryVersions { - releaseInfo, err := buildListReleaseResp(args.Verbosity, deliveryVersion, &DeliveryVersionFilter{ServiceName: args.ServiceName}, logger) + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, log) if err != nil { - return nil, err - } - if releaseInfo == nil { - continue - } - releaseInfos = append(releaseInfos, releaseInfo) - } - return releaseInfos, nil -} - -// fill release -func processReleaseRespData(release *ReleaseInfo) { - if release.VersionInfo.Type != setting.DeliveryVersionTypeChart { - return - } - - distributeMap := make(map[string][]*commonmodels.DeliveryDistribute) - for _, distributeImage := range release.DistributeInfo { - if distributeImage.DistributeType != config.Image { - continue - } - distributeMap[distributeImage.ChartName] = append(distributeMap[distributeImage.ChartName], distributeImage) - } - - chartDistributeCount := 0 - distributes := make([]*commonmodels.DeliveryDistribute, 0) - for _, distribute := range release.DistributeInfo { - if distribute.DistributeType == config.Image { + log.Errorf("FindDeliveryDeploy failed, ReleaseID:%s, err:%v", deliveryVersion.ID, err) continue } - switch distribute.DistributeType { - case config.Chart: - chartDistributeCount++ - distribute.SubDistributes = distributeMap[distribute.ChartName] - case config.File: - s3Storage, err := commonrepo.NewS3StorageColl().Find(distribute.S3StorageID) - if err != nil { - log.Errorf("failed to query s3 storageID: %s, err: %s", distribute.S3StorageID, err) - } else { - distribute.StorageURL = s3Storage.Endpoint - distribute.StorageBucket = s3Storage.Bucket - } - } - distributes = append(distributes, distribute) - } - release.DistributeInfo = distributes - - release.VersionInfo.Progress = buildDeliveryProgressInfo(release.VersionInfo, chartDistributeCount) -} - -func buildDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion, successfulChartCount int) *commonmodels.DeliveryVersionProgress { - if deliveryVersion.Type != setting.DeliveryVersionTypeChart { - return nil - } - - progress := &commonmodels.DeliveryVersionProgress{ - SuccessChartCount: successfulChartCount, - TotalChartCount: 0, - PackageUploadStatus: "", - Error: "", - } - if deliveryVersion.Status == setting.DeliveryVersionStatusSuccess { - progress.TotalChartCount = successfulChartCount - progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusSuccess - return progress - } - - argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) - if err != nil { - log.Errorf("failed to marshal arguments, versionName: %s err %s", deliveryVersion.Version, err) - return progress - } - createArgs := new(DeliveryVersionChartData) - err = json.Unmarshal(argsBytes, createArgs) - if err != nil { - log.Errorf("failed to unMarshal arguments, versionName: %s err %s", deliveryVersion.Version, err) - return progress - } - - progress.TotalChartCount = len(createArgs.ChartDatas) - - if deliveryVersion.Status == setting.DeliveryVersionStatusFailed { - progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusFailed - progress.Error = deliveryVersion.Error - return progress - } - - if len(createArgs.ChartDatas) > successfulChartCount { - progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusWaiting - return progress - } - - progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusUploading - return progress - -} - -func getChartTGZDir(productName, versionName string) string { - tmpDir := os.TempDir() - return filepath.Join(tmpDir, "chart-tgz", productName, versionName) -} - -func getChartExpandDir(productName, versionName string) string { - tmpDir := os.TempDir() - return filepath.Join(tmpDir, "chart", productName, versionName) -} - -func getProductEnvInfo(productName, envName string) (*commonmodels.Product, error) { - productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: productName, - EnvName: envName, - }) - if err != nil { - log.Errorf("failed to query product info, productName: %s envName: %s err: %s", productName, envName, err) - return nil, fmt.Errorf("failed to query product info, productName: %s envName: %s", productName, envName) - } - return productInfo, nil -} - -func getChartRepoData(repoName string) (*commonmodels.HelmRepo, error) { - return commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: repoName}) -} - -func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { - client, err := cm.NewClient( - cm.URL(repo.URL), - cm.Username(repo.Username), - cm.Password(repo.Password), - // need support more auth types - ) - if err != nil { - return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", repo.RepoName) - } - return client, nil -} - -// find all images in one single chart -func extractImages(productService *commonmodels.ProductService, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { - imageUrlsSet := sets.NewString() - for _, container := range productService.Containers { - imageUrlsSet.Insert(container.Image) - } - - ret := &ServiceImageDetails{ - ServiceName: productService.ServiceName, - Images: make([]*ImageUrlDetail, 0), - } - - registrySet := sets.NewString() - - for _, imageUrl := range imageUrlsSet.List() { - - registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) - } - registryUrl = strings.TrimSuffix(registryUrl, "/") - - imageName := commonservice.ExtractImageName(imageUrl) - imageTag := commonservice.ExtractImageTag(imageUrl) - - registryID := "" - // used source registry - if registry, ok := registryMap[registryUrl]; ok { - registryID = registry.ID.Hex() - registrySet.Insert(registryID) - } - - ret.Images = append(ret.Images, &ImageUrlDetail{ - ImageUrl: imageUrl, - Name: imageName, - Tag: imageTag, - Registry: registryID, - }) - } - - ret.Registries = registrySet.List() - return ret, nil -} - -// ensure chart files exist -func ensureChartFiles(chartData *DeliveryChartData) (string, error) { - serviceObj := chartData.ServiceObj - revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) - deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) - if exists, _ := fsutil.DirExists(deliveryChartPath); exists { - return deliveryChartPath, nil - } - - serviceName, revision := serviceObj.ServiceName, serviceObj.Revision - basePath := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) - if err := commonservice.PreloadServiceManifestsByRevision(basePath, serviceObj); err != nil { - log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) - // use the latest version when it fails to download the specific version - basePath = config.LocalServicePath(serviceObj.ProductName, serviceName) - if err = commonservice.PreLoadServiceManifests(basePath, serviceObj); err != nil { - log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) - return "", err - } - } - - fullPath := filepath.Join(basePath, serviceObj.ServiceName) - err := copy.Copy(fullPath, deliveryChartPath) - if err != nil { - return "", err - } - - mergedValuesYaml, err := helmtool.MergeOverrideValues(chartData.RenderChart.ValuesYaml, chartData.RenderSet.DefaultValues, chartData.RenderChart.GetOverrideYaml(), chartData.RenderChart.OverrideValues) - if err != nil { - return "", err - } - err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644) - if err != nil { - return "", errors.Wrapf(err, "failed to write values.yaml") - } - - return deliveryChartPath, nil -} - -func buildChartPackage(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, registryMap map[string]*commonmodels.RegistryNamespace) error { - serviceObj := chartData.ServiceObj - - deliveryChartPath, err := ensureChartFiles(chartData) - if err != nil { - return err - } - - valuesYamlData := make(map[string]interface{}) - valuesFilePath := filepath.Join(deliveryChartPath, setting.ValuesYaml) - valueYamlContent, err := os.ReadFile(valuesFilePath) - if err != nil { - return errors.Wrapf(err, "failed to read values.yaml for service %s", serviceObj.ServiceName) - } - err = yaml.Unmarshal(valueYamlContent, &valuesYamlData) - if err != nil { - return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) - } - - // write values.yaml file before load - if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly - if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { - log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) - return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) - } - valueYamlContent = []byte(chartData.ChartData.ValuesYamlContent) - } else if len(globalVariables) > 0 { // merge global variables - valueYamlContent, err = yamlutil.Merge([][]byte{valueYamlContent, []byte(globalVariables)}) - if err != nil { - return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) - } - } - err = os.WriteFile(valuesFilePath, valueYamlContent, 0644) - if err != nil { - return errors.Wrapf(err, "failed to write values.yaml file for service %s", serviceObj.ServiceName) - } - - //load chart info from local storage - chartRequested, err := chartloader.Load(deliveryChartPath) - if err != nil { - return errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) - } - - //set metadata - chartRequested.Metadata.Name = chartData.ChartData.ServiceName - chartRequested.Metadata.Version = chartData.ChartData.Version - chartRequested.Metadata.AppVersion = chartData.ChartData.Version - - //create local chart package - chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) - if err != nil { - return err - } - - client, err := createChartRepoClient(chartRepo) - if err != nil { - return errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) - } - - log.Infof("pushing chart %s to %s...", filepath.Base(chartPackagePath), chartRepo.URL) - resp, err := client.UploadChartPackage(chartPackagePath, false) - if err != nil { - return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) - } - err = handlePushResponse(resp) - if err != nil { - return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) - } - return nil -} - -func handlePushResponse(resp *http.Response) error { - if resp.StatusCode != 201 && resp.StatusCode != 202 { - b, err := ioutil.ReadAll(resp.Body) - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - if err != nil { - return err - } - return getChartmuseumError(b, resp.StatusCode) - } - log.Infof("push chart to chart repo done") - return nil -} - -func getChartmuseumError(b []byte, code int) error { - var er struct { - Error string `json:"error"` - } - err := json.Unmarshal(b, &er) - if err != nil || er.Error == "" { - return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) - } - return errors.Errorf("%d: %s", code, er.Error) -} - -func makeChartTGZFileDir(productName, versionName string) (string, error) { - path := getChartTGZDir(productName, versionName) - if err := os.RemoveAll(path); err != nil { - if !os.IsExist(err) { - return "", errors.Wrapf(err, "failed to claer dir for chart tgz files") - } - } - err := os.MkdirAll(path, 0777) - if err != nil { - return "", errors.Wrapf(err, "failed to create chart tgz dir for version: %s", versionName) - } - return path, nil -} - -func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { - if args.Retry { - return RetryCreateHelmDeliveryVersion(args.ProductName, args.Version, logger) - } else { - return CreateNewHelmDeliveryVersion(args, logger) - } -} - -// validate chartInfo, make sure service is in environment -// prepare data set for chart delivery -func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productInfo *commonmodels.Product) (map[string]*DeliveryChartData, error) { - - renderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Revision: productInfo.Render.Revision, - Name: productInfo.Render.Name, - }) - if err != nil { - return nil, fmt.Errorf("failed to find renderSet: %s, revision: %d", productInfo.Render.Name, productInfo.Render.Revision) - } - chartMap := make(map[string]*template.RenderChart) - for _, rChart := range renderSet.ChartInfos { - chartMap[rChart.ServiceName] = rChart - } - - chartDataMap := make(map[string]*DeliveryChartData) - serviceMap := productInfo.GetServiceMap() - for _, chartData := range chartDatas { - if productService, ok := serviceMap[chartData.ServiceName]; ok { - serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: chartData.ServiceName, - Revision: productService.Revision, - Type: setting.HelmDeployType, - ProductName: productInfo.ProductName, - }) - if err != nil { - return nil, fmt.Errorf("failed to query service: %s", chartData.ServiceName) - } - renderChart, ok := chartMap[chartData.ServiceName] - if !ok { - return nil, fmt.Errorf("can't find renderChart for service: %s", chartData.ServiceName) - } - chartDataMap[chartData.ServiceName] = &DeliveryChartData{ - ChartData: chartData, - RenderChart: renderChart, - ServiceObj: serviceObj, - ProductService: productService, - RenderSet: renderSet, - } - } else { - return nil, fmt.Errorf("service %s not found in environment", chartData.ServiceName) - } - } - return chartDataMap, nil -} - -func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { - registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) - if err != nil { - return nil, fmt.Errorf("failed to query registries") - } - ret := make(map[string]*commonmodels.RegistryNamespace) - for _, singleRegistry := range registries { - fullUrl := fmt.Sprintf("%s/%s", singleRegistry.RegAddr, singleRegistry.Namespace) - fullUrl = strings.TrimSuffix(fullUrl, "/") - u, _ := url.Parse(fullUrl) - if len(u.Scheme) > 0 { - fullUrl = strings.TrimPrefix(fullUrl, fmt.Sprintf("%s://", u.Scheme)) - } - ret[fullUrl] = singleRegistry - } - return ret, nil -} - -func buildArtifactTaskArgs(projectName, envName string, imagesMap *sync.Map) *commonmodels.ArtifactPackageTaskArgs { - imageArgs := make([]*commonmodels.ImagesByService, 0) - sourceRegistry := sets.NewString() - imagesMap.Range(func(key, value interface{}) bool { - imageDetail := value.(*ServiceImageDetails) - imagesByService := &commonmodels.ImagesByService{ - ServiceName: imageDetail.ServiceName, - Images: make([]*commonmodels.ImageData, 0), - } - for _, image := range imageDetail.Images { - imagesByService.Images = append(imagesByService.Images, &commonmodels.ImageData{ - ImageUrl: image.ImageUrl, - ImageName: image.Name, - ImageTag: image.Tag, - RegistryID: image.Registry, - }) - } - imageArgs = append(imageArgs, imagesByService) - sourceRegistry.Insert(imageDetail.Registries...) - return true - }) - - ret := &commonmodels.ArtifactPackageTaskArgs{ - ProjectName: projectName, - EnvName: envName, - Images: imageArgs, - SourceRegistries: sourceRegistry.List(), - } - return ret -} - -// insert delivery distribution data for single chart, include image and chart -func insertDeliveryDistributions(result *task.ServicePackageResult, chartVersion string, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData) error { - for _, image := range result.ImageData { - err := commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ - ReleaseID: deliveryVersion.ID, - ServiceName: image.ImageName, // image name - ChartName: result.ServiceName, - DistributeType: config.Image, - RegistryName: image.ImageUrl, - Namespace: commonservice.ExtractRegistryNamespace(image.ImageUrl), - CreatedAt: time.Now().Unix(), - }) - if err != nil { - log.Errorf("failed to insert image distribute data, chartName: %s, err: %s", result.ServiceName, err) - return fmt.Errorf("failed to insert image distribute data, chartName: %s", result.ServiceName) - } - } - - err := commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ - ReleaseID: deliveryVersion.ID, - DistributeType: config.Chart, - ChartName: result.ServiceName, - ChartVersion: chartVersion, - ChartRepoName: args.ChartRepoName, - SubDistributes: nil, - CreatedAt: time.Now().Unix(), - }) - if err != nil { - log.Errorf("failed to insert chart distribute data, chartName: %s, err: %s", result.ServiceName, err) - return fmt.Errorf("failed to insert chart distribute data, chartName: %s", result.ServiceName) - } - return nil -} - -func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData, logger *zap.SugaredLogger) (err error) { - defer func() { - if err != nil { - deliveryVersion.Status = setting.DeliveryVersionStatusFailed - deliveryVersion.Error = err.Error() - } - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) - if err != nil { - logger.Errorf("failed to update delivery version data, name: %s error: %s", deliveryVersion.Version, err) - } - }() - - var errLock sync.Mutex - errorList := &multierror.Error{} - - appendError := func(err error) { - errLock.Lock() - defer errLock.Unlock() - errorList = multierror.Append(errorList, err) - } - - dir, err := makeChartTGZFileDir(deliveryVersion.ProductName, deliveryVersion.Version) - if err != nil { - return err - } - repoInfo, err := getChartRepoData(args.ChartRepoName) - if err != nil { - log.Errorf("failed to query chart-repo info, productName: %s, err: %s", deliveryVersion.ProductName, err) - return fmt.Errorf("failed to query chart-repo info, productName: %s, repoName: %s", deliveryVersion.ProductName, args.ChartRepoName) - } - - registryMap, err := buildRegistryMap() - if err != nil { - return fmt.Errorf("failed to build registry map") - } - - imagesDataMap := &sync.Map{} - - // push charts to repo - wg := sync.WaitGroup{} - for _, chartData := range chartDataMap { - wg.Add(1) - go func(cData *DeliveryChartData) { - defer wg.Done() - err := buildChartPackage(cData, repoInfo, dir, args.GlobalVariables, registryMap) - if err != nil { - logger.Errorf("failed to build chart package, serviceName: %s err: %s", cData.ChartData.ServiceName, err) - appendError(err) - return - } - imageData, err := extractImages(cData.ProductService, registryMap) - if err != nil { - logger.Errorf("failed to extract image data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) - appendError(err) - return - } - imagesDataMap.Store(cData.ServiceObj.ServiceName, imageData) - }(chartData) - } - wg.Wait() - - if errorList.ErrorOrNil() != nil { - err = errorList.ErrorOrNil() - return - } - - // create task to deal with images - // offline docker images are not supported - taskArgs := buildArtifactTaskArgs(deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, imagesDataMap) - taskArgs.TargetRegistries = []string{args.ImageRegistryID} - taskID, err := workflowservice.CreateArtifactPackageTask(taskArgs, deliveryVersion.Version, logger) - if err != nil { - return err - } - deliveryVersion.TaskID = int(taskID) - - // start a new routine to check task results - go waitVersionDone(deliveryVersion) - - return -} - -func updateVersionStatus(versionName, status, errStr string) { - err := commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionName, status, errStr) - if err != nil { - log.Errorf("failed to update version status, name: %s, err: %s", versionName, err) - } -} - -func taskFinished(status config.Status) bool { - return status == config.StatusPassed || status == config.StatusFailed || status == config.StatusTimeout || status == config.StatusCancelled -} - -func waitVersionDone(deliveryVersion *commonmodels.DeliveryVersion) { - waitTimeout := time.After(60 * time.Minute * 2) - for { - select { - case <-waitTimeout: - updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, "timeout") - return - default: - done, err := checkVersionStatus(deliveryVersion) - if err != nil { - updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, err.Error()) - return - } - if done { - return - } - } - time.Sleep(time.Second * 5) - } -} - -func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, error) { - if deliveryVersion.Status == setting.DeliveryVersionStatusSuccess || deliveryVersion.Status == setting.DeliveryVersionStatusFailed { - return true, nil - } - pipelineName := fmt.Sprintf("%s-%s-%s", deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, "artifact") - taskData, err := commonrepo.NewTaskColl().Find(int64(deliveryVersion.TaskID), pipelineName, config.ArtifactType) - if err != nil { - return false, fmt.Errorf("failed to query taskData, id: %d, pipelineName: %s", deliveryVersion.TaskID) - } - - if len(taskData.Stages) != 1 { - return false, fmt.Errorf("invalid task data, stage length not leagal") - } - - argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) - if err != nil { - return false, errors.Wrapf(err, "failed to marshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) - } - createArgs := new(DeliveryVersionChartData) - err = json.Unmarshal(argsBytes, createArgs) - if err != nil { - return false, errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) - } - - distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ - DistributeType: config.Chart, - ReleaseID: deliveryVersion.ID.Hex(), - }) - if err != nil { - return false, errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) - } - - // for charts has been successfully handled, download charts directly - successCharts := sets.NewString() - for _, distribute := range distributes { - successCharts.Insert(distribute.ChartName) - } - - stage := taskData.Stages[0] - errorList := &multierror.Error{} - - allTaskDone := true - for _, taskData := range stage.SubTasks { - artifactPackageArgs, err := base.ToArtifactPackageImageTask(taskData) - if err != nil { - return false, errors.Wrapf(err, "failed to generate origin artifact task data") - } - - progressData, err := artifactPackageArgs.GetProgress() - if err != nil { - return false, errors.Wrapf(err, "failed to get progress data from data") - } - progressDataMap := make(map[string]*task.ServicePackageResult) - for _, singleResult := range progressData { - progressDataMap[singleResult.ServiceName] = singleResult - } - - for _, chartData := range createArgs.ChartDatas { - // service artifact has been marked as success - if successCharts.Has(chartData.ServiceName) { - continue - } - if singleResult, ok := progressDataMap[chartData.ServiceName]; ok { - if singleResult.Result != "success" { - errorList = multierror.Append(errorList, fmt.Errorf("failed to build image distribute for service:%s ", singleResult.ServiceName)) - continue - } - err = insertDeliveryDistributions(singleResult, chartData.Version, deliveryVersion, createArgs) - if err != nil { - errorList = multierror.Append(errorList, fmt.Errorf("failed to insert distribute data for service:%s ", singleResult.ServiceName)) - continue - } - successCharts.Insert(chartData.ServiceName) - } - } - - if !taskFinished(artifactPackageArgs.TaskStatus) { - allTaskDone = false - } - if len(artifactPackageArgs.Error) > 0 { - errorList = multierror.Append(errorList, fmt.Errorf(artifactPackageArgs.Error)) - } - } - - if allTaskDone { - if successCharts.Len() == len(createArgs.ChartDatas) { - deliveryVersion.Status = setting.DeliveryVersionStatusSuccess - } else { - deliveryVersion.Status = setting.DeliveryVersionStatusFailed - } - } - if errorList.ErrorOrNil() != nil { - deliveryVersion.Error = errorList.Error() - } - updateVersionStatus(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) - return allTaskDone, nil -} - -func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { - // need appoint chart info - if len(args.ChartDatas) == 0 { - return e.ErrCreateDeliveryVersion.AddDesc("no chart info appointed") - } - - // prepare data - productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) - if err != nil { - log.Infof("failed to query product info, productName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) - } - - // validate necessary params - if len(args.ChartRepoName) == 0 { - return e.ErrCreateDeliveryVersion.AddDesc("chart repo not appointed") - } - if len(args.ImageRegistryID) == 0 { - return e.ErrCreateDeliveryVersion.AddDesc("image registry not appointed") - } - - chartDataMap, err := prepareChartData(args.ChartDatas, productInfo) - if err != nil { - return e.ErrCreateDeliveryVersion.AddErr(err) - } - - productInfo.ID, _ = primitive.ObjectIDFromHex("") - - versionObj := &commonmodels.DeliveryVersion{ - Version: args.Version, - ProductName: args.ProductName, - Type: setting.DeliveryVersionTypeChart, - Desc: args.Desc, - Labels: args.Labels, - ProductEnvInfo: productInfo, - Status: setting.DeliveryVersionStatusCreating, - CreateArgument: args.DeliveryVersionChartData, - CreatedBy: args.CreateBy, - CreatedAt: time.Now().Unix(), - DeletedAt: 0, - } - - err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) - if err != nil { - return err - } - - err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) - if err != nil { - logger.Errorf("failed to insert version data, err: %s", err) - return e.ErrCreateDeliveryVersion.AddErr(fmt.Errorf("failed to insert delivery version: %s", versionObj.Version)) - } - - return nil -} - -func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap.SugaredLogger) error { - deliveryVersion, err := commonrepo.NewDeliveryVersionColl().Get(&commonrepo.DeliveryVersionArgs{ - ProductName: projectName, - Version: versionName, - }) - if err != nil { - logger.Errorf("failed to query delivery version data, verisonName: %s, error: %s", versionName, err) - return fmt.Errorf("failed to query delivery version data, verisonName: %s", versionName) - } - - if deliveryVersion.Status != setting.DeliveryVersionStatusFailed { - return fmt.Errorf("can't reCreate version with status:%s", deliveryVersion.Status) - } - - argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) - if err != nil { - return errors.Wrapf(err, "failed to marshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) - } - createArgs := new(DeliveryVersionChartData) - err = json.Unmarshal(argsBytes, createArgs) - if err != nil { - return errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) - } - - productInfoSnap := deliveryVersion.ProductEnvInfo - - distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ - DistributeType: config.Chart, - ReleaseID: deliveryVersion.ID.Hex(), - }) - if err != nil { - return errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) - } - - // for charts has been successfully handled, download charts directly - successCharts := sets.NewString() - for _, distribute := range distributes { - if distribute.DistributeType != config.Chart { - continue - } - _, err := downloadChart(deliveryVersion, distribute) - if err != nil { - log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", distribute.ChartName, err) - continue - } - successCharts.Insert(distribute.ChartName) - } - - chartsToBeHandled := make([]*CreateHelmDeliveryVersionChartData, 0) - for _, chartConfig := range createArgs.ChartDatas { - if successCharts.Has(chartConfig.ServiceName) { - continue - } - chartsToBeHandled = append(chartsToBeHandled, chartConfig) - } - - chartDataMap, err := prepareChartData(chartsToBeHandled, productInfoSnap) - if err != nil { - return e.ErrCreateDeliveryVersion.AddErr(err) - } - - err = buildDeliveryCharts(chartDataMap, deliveryVersion, createArgs, logger) - if err != nil { - return err - } - - // update status - deliveryVersion.Status = setting.DeliveryVersionStatusRetrying - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, "") - if err != nil { - logger.Errorf("failed to update delivery status, name: %s, err: %s", deliveryVersion.Version, err) - return fmt.Errorf("failed to update delivery status, name: %s", deliveryVersion.Version) - } - - return nil -} - -func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]string, error) { - serviceNames := sets.String{} - - version := new(commonrepo.DeliveryVersionArgs) - version.ProductName = productName - deliveryVersions, err := FindDeliveryVersion(version, log) - if err != nil { - log.Errorf("FindDeliveryVersion failed, err:%v", err) - return serviceNames.List(), err - } - - for _, deliveryVersion := range deliveryVersions { - deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) - deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, log) - if err != nil { - log.Errorf("FindDeliveryDeploy failed, ReleaseID:%s, err:%v", deliveryVersion.ID, err) - continue - } - for _, deliveryDeploy := range deliveryDeploys { - serviceNames.Insert(deliveryDeploy.ServiceName) + for _, deliveryDeploy := range deliveryDeploys { + serviceNames.Insert(deliveryDeploy.ServiceName) } } return serviceNames.UnsortedList(), nil } - -func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *commonmodels.DeliveryDistribute) (string, error) { - productName, versionName := deliveryVersion.ProductName, deliveryVersion.Version - chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.ChartName, chartInfo.ChartVersion) - chartTGZFileParent := getChartTGZDir(productName, versionName) - chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) - if _, err := os.Stat(chartTGZFilePath); err == nil { - // local cache exists - log.Infof("local cache exists, path %s", chartTGZFilePath) - return chartTGZFilePath, nil - } - - chartRepo, err := getChartRepoData(chartInfo.ChartRepoName) - if err != nil { - return "", fmt.Errorf("failed to query chart-repo info, repoName %s", chartInfo.ChartRepoName) - } - - client, err := createChartRepoClient(chartRepo) - if err != nil { - return "", err - } - - if err = os.MkdirAll(chartTGZFileParent, 0644); err != nil { - return "", errors.Wrapf(err, "failed to craete tgz parent dir") - } - - out, err := os.Create(chartTGZFilePath) - if err != nil { - _ = os.RemoveAll(chartTGZFilePath) - return "", errors.Wrapf(err, "failed to create chart tgz file") - } - - response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) - if err != nil { - return "", errors.Wrapf(err, "failed to download file") - } - - if response.StatusCode != 200 { - return "", errors.Wrapf(err, "download file failed %s", chartTGZName) - } - defer func() { _ = response.Body.Close() }() - - b, err := ioutil.ReadAll(response.Body) - if err != nil { - return "", errors.Wrapf(err, "failed to read response data") - } - - defer func(out *os.File) { - _ = out.Close() - }(out) - - err = ioutil.WriteFile(chartTGZFilePath, b, 0644) - if err != nil { - return "", err - } - return chartTGZFilePath, nil -} - -func getChartDistributeInfo(releaseID, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { - distributes, _ := FindDeliveryDistribute(&commonrepo.DeliveryDistributeArgs{ - ReleaseID: releaseID, - ChartName: chartName, - DistributeType: config.Chart, - }, log) - - if len(distributes) != 1 { - log.Warnf("find chart %s failed, expect count %d, found count %d, release_id: %s", chartName, 1, len(distributes), releaseID) - return nil, fmt.Errorf("can't find target charts") - } - - chartInfo := distributes[0] - return chartInfo, nil -} - -func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) ([]byte, string, error) { - - filePath, err := preDownloadChart(projectName, version, chartName, log) - if err != nil { - return nil, "", err - } - - fileBytes, err := os.ReadFile(filePath) - if err != nil { - return nil, "", err - } - - return fileBytes, filepath.Base(filePath), err -} - -func preDownloadChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { - deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ - ProductName: projectName, - Version: versionName, - }, log) - if err != nil { - return "", fmt.Errorf("failed to query delivery info") - } - - chartInfo, err := getChartDistributeInfo(deliveryInfo.ID.Hex(), chartName, log) - if err != nil { - return "", err - } - // prepare chart data - filePath, err := downloadChart(deliveryInfo, chartInfo) - if err != nil { - return "", err - } - return filePath, err -} - -func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, error) { - - chartRepo, err := getChartRepoData(chartRepoName) - if err != nil { - return nil, fmt.Errorf("failed to query chart-repo info, repoName %s", chartRepoName) - } - - client, err := createChartRepoClient(chartRepo) - if err != nil { - return nil, errors.Wrapf(err, "failed to create chart repo client") - } - - resp, err := client.DownloadFile("index.yaml") - if err != nil { - return nil, errors.Wrapf(err, "failed to download index.yaml") - } - - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") - } - - index, err := helm.LoadIndex(b) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse index.yaml") - } - - chartNameList := strings.Split(chartName, ",") - chartNameSet := sets.NewString(chartNameList...) - - ret := make([]*ChartVersionResp, 0) - - for name, entry := range index.Entries { - if !chartNameSet.Has(name) { - continue - } - if len(entry) == 0 { - continue - } - latestEntry := entry[0] - ret = append(ret, &ChartVersionResp{ - ChartName: name, - ChartVersion: latestEntry.Version, - }) - } - - return ret, nil -} - -func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { - - deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ - ProductName: projectName, - Version: versionName, - }, log) - if err != nil { - return "", fmt.Errorf("failed to query delivery info") - } - - chartDistribute, err := getChartDistributeInfo(deliveryInfo.ID.Hex(), chartName, log) - if err != nil { - return "", err - } - dstDir := getChartExpandDir(projectName, versionName) - dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartDistribute.ChartName, chartDistribute.ChartVersion)) - - filePath, err := preDownloadChart(projectName, versionName, chartName, log) - if err != nil { - return "", err - } - - file, err := os.Open(filePath) - if err != nil { - return "", errors.Wrap(err, "unable to open tarball") - } - defer func() { _ = file.Close() }() - - err = chartutil.Expand(dstDir, file) - if err != nil { - log.Errorf("failed to uncompress file: %s", filePath) - return "", errors.Wrapf(err, "failed to uncompress file") - } - return dstDir, nil -} - -func PreviewDeliveryChart(projectName, version, chartName string, log *zap.SugaredLogger) (*DeliveryChartResp, error) { - - dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) - if err != nil { - return nil, err - } - - ret := &DeliveryChartResp{ - FileInfos: make([]*types.FileInfo, 0), - } - - var fis []*types.FileInfo - files, err := os.ReadDir(filepath.Join(dstDir, chartName)) - if err != nil { - return nil, err - } - - for _, file := range files { - info, _ := file.Info() - if info == nil { - continue - } - fi := &types.FileInfo{ - Parent: "", - Name: file.Name(), - Size: info.Size(), - Mode: file.Type(), - ModTime: info.ModTime().Unix(), - IsDir: file.IsDir(), - } - - fis = append(fis, fi) - } - ret.FileInfos = fis - return ret, nil -} - -// load chart file infos -func loadChartFileInfos(fileDir, chartName string, dir string) ([]*types.FileInfo, error) { - var fis []*types.FileInfo - files, err := os.ReadDir(filepath.Join(fileDir, chartName, dir)) - if err != nil { - return nil, e.ErrFilePath.AddDesc(err.Error()) - } - - for _, file := range files { - info, _ := file.Info() - if info == nil { - continue - } - fi := &types.FileInfo{ - Parent: dir, - Name: file.Name(), - Size: info.Size(), - Mode: file.Type(), - ModTime: info.ModTime().Unix(), - IsDir: file.IsDir(), - } - fis = append(fis, fi) - } - return fis, nil -} - -func GetDeliveryChartFilePath(args *DeliveryChartFilePathArgs, log *zap.SugaredLogger) ([]*types.FileInfo, error) { - projectName, version, chartName := args.ProjectName, args.Version, args.ChartName - dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) - if err != nil { - return nil, nil - } - - fileInfos, err := loadChartFileInfos(dstDir, chartName, args.Dir) - if err != nil { - return nil, err - } - return fileInfos, nil -} - -func GetDeliveryChartFileContent(args *DeliveryChartFileContentArgs, log *zap.SugaredLogger) (string, error) { - projectName, version, chartName := args.ProjectName, args.Version, args.ChartName - dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) - if err != nil { - return "", nil - } - - file := filepath.Join(dstDir, chartName, args.FilePath, args.FileName) - fileContent, err := os.ReadFile(file) - if err != nil { - log.Errorf("Failed to read file %s, err: %s", file, err) - return "", e.ErrFileContent.AddDesc(err.Error()) - } - - return string(fileContent), nil -} - -func ApplyDeliveryGlobalVariables(args *DeliveryVariablesApplyArgs, logger *zap.SugaredLogger) (interface{}, error) { - ret := new(DeliveryVariablesApplyArgs) - for _, chartData := range args.ChartDatas { - mergedYaml, err := yamlutil.Merge([][]byte{[]byte(chartData.ValuesYamlContent), []byte(args.GlobalVariables)}) - if err != nil { - logger.Errorf("failed to merge gobal variables for service: %s", chartData.ServiceName) - return nil, errors.Wrapf(err, "failed to merge global variables for service: %s", chartData.ServiceName) - } - ret.ChartDatas = append(ret.ChartDatas, &CreateHelmDeliveryVersionChartData{ - ServiceName: chartData.ServiceName, - ValuesYamlContent: string(mergedYaml), - }) - } - return ret, nil -} diff --git a/pkg/microservice/aslan/core/environment/handler/helm.go b/pkg/microservice/aslan/core/environment/handler/helm.go deleted file mode 100644 index 9e097cd9..00000000 --- a/pkg/microservice/aslan/core/environment/handler/helm.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" - internalhandler "github.com/koderover/zadig/pkg/shared/handler" -) - -func ListReleases(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Param("name") - projectName := c.Query("projectName") - ctx.Resp, ctx.Err = service.ListReleases(projectName, envName, ctx.Logger) -} - -func GetChartInfos(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Param("name") - servicesName := c.Query("serviceName") - projectName := c.Query("projectName") - ctx.Resp, ctx.Err = service.GetChartInfos(projectName, envName, servicesName, ctx.Logger) -} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 4204db67..f439c59c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -108,9 +108,6 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/groups", ListGroups) environments.GET("/:name/workloads", ListWorkloadsInEnv) - environments.GET("/:name/helm/releases", ListReleases) - environments.GET("/:name/helm/charts", GetChartInfos) - environments.GET("/:name/services/:serviceName", GetService) environments.PUT("/:name/services/:serviceName", gin2.UpdateOperationLogStatus, UpdateService) environments.POST("/:name/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go deleted file mode 100644 index 255a2ba7..00000000 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ /dev/null @@ -1,259 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "sync" - - "github.com/hashicorp/go-multierror" - "github.com/otiai10/copy" - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" - "github.com/koderover/zadig/pkg/setting" - e "github.com/koderover/zadig/pkg/tool/errors" - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/util" -) - -type HelmReleaseResp struct { - ReleaseName string `json:"releaseName"` - ServiceName string `json:"serviceName"` - Revision int `json:"revision"` - Chart string `json:"chart"` - AppVersion string `json:"appVersion"` -} - -type ChartInfo struct { - ServiceName string `json:"serviceName"` - Revision int64 `json:"revision"` -} - -type HelmChartsResp struct { - ChartInfos []*ChartInfo `json:"chartInfos"` - FileInfos []*types.FileInfo `json:"fileInfos"` -} - -func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmReleaseResp, error) { - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - prod, err := commonrepo.NewProductColl().Find(opt) - if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) - } - - restConfig, err := kube.GetRESTConfig(prod.ClusterID) - if err != nil { - log.Errorf("GetRESTConfig error: %v", err) - return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) - } - helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) - if err != nil { - log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) - return nil, e.ErrCreateDeliveryVersion.AddErr(err) - } - - releases, err := helmClient.ListDeployedReleases() - if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddErr(err) - } - - ret := make([]*HelmReleaseResp, 0, len(releases)) - for _, release := range releases { - ret = append(ret, &HelmReleaseResp{ - ReleaseName: release.Name, - ServiceName: util.ExtraServiceName(release.Name, prod.Namespace), - Revision: release.Version, - Chart: release.Chart.Name(), - AppVersion: release.Chart.AppVersion(), - }) - } - return ret, nil -} - -func loadChartFilesInfo(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { - base := config.LocalServicePathWithRevision(productName, serviceName, revision) - - var fis []*types.FileInfo - files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) - if err != nil { - log.Warnf("failed to read chart info for service %s with revision %d", serviceName, revision) - base = config.LocalServicePath(productName, serviceName) - files, err = os.ReadDir(filepath.Join(base, serviceName, dir)) - if err != nil { - return nil, err - } - } - - for _, file := range files { - info, _ := file.Info() - if info == nil { - continue - } - fi := &types.FileInfo{ - Parent: dir, - Name: file.Name(), - Size: info.Size(), - Mode: file.Type(), - ModTime: info.ModTime().Unix(), - IsDir: file.IsDir(), - } - - fis = append(fis, fi) - } - return fis, nil -} - -//prepare chart version data -func prepareChartVersionData(productName string, serviceObj *models.Service, renderChart *template.RenderChart, renderset *models.RenderSet) error { - serviceName, revision := serviceObj.ServiceName, serviceObj.Revision - base := config.LocalServicePathWithRevision(productName, serviceName, revision) - if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { - log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) - // use the latest version when it fails to download the specific version - base = config.LocalServicePath(productName, serviceName) - if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { - log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) - return err - } - } - - fullPath := filepath.Join(base, serviceObj.ServiceName) - deliveryChartPath := filepath.Join(config.LocalDeliveryChartPathWithRevision(productName, serviceObj.ServiceName, serviceObj.Revision), serviceObj.ServiceName) - err := copy.Copy(fullPath, deliveryChartPath) - if err != nil { - return err - } - - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderset.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) - if err != nil { - return err - } - - // write values.yaml - if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644); err != nil { - return err - } - - return nil -} - -func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLogger) (*HelmChartsResp, error) { - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - prod, err := commonrepo.NewProductColl().Find(opt) - if err != nil { - return nil, e.ErrGetHelmCharts.AddErr(err) - } - renderSet, err := FindHelmRenderSet(productName, prod.Render.Name, log) - if err != nil { - log.Errorf("[%s][P:%s] find product renderset error: %v", envName, productName, err) - return nil, e.ErrGetHelmCharts.AddErr(err) - } - - chartMap := make(map[string]*template.RenderChart) - for _, chart := range renderSet.ChartInfos { - chartMap[chart.ServiceName] = chart - } - - allServiceMap := prod.GetServiceMap() - serviceMap := make(map[string]*models.ProductService) - - //validate data, make sure service and chart info exists - if len(serviceName) > 0 { - serviceList := strings.Split(serviceName, ",") - for _, singleService := range serviceList { - if service, ok := allServiceMap[singleService]; ok { - serviceMap[service.ServiceName] = service - } else { - return nil, e.ErrGetHelmCharts.AddDesc(fmt.Sprintf("failed to find service %s in target namespace", singleService)) - } - } - } else { - serviceMap = allServiceMap - } - - if len(serviceMap) == 0 { - return nil, nil - } - - ret := &HelmChartsResp{ - ChartInfos: make([]*ChartInfo, 0), - FileInfos: make([]*types.FileInfo, 0), - } - - errList := new(multierror.Error) - wg := sync.WaitGroup{} - - for _, service := range serviceMap { - ret.ChartInfos = append(ret.ChartInfos, &ChartInfo{ - ServiceName: service.ServiceName, - Revision: service.Revision, - }) - wg.Add(1) - // download chart info with particular version - go func(serviceName string, revision int64) { - defer wg.Done() - serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ProductName: productName, - ServiceName: serviceName, - Revision: revision, - Type: setting.HelmDeployType, - }) - if err != nil { - log.Errorf("failed to query services name: %s, revision: %d, error: %s", serviceName, revision, err) - errList = multierror.Append(errList, fmt.Errorf("failed to query service, serviceName: %s, revision: %d", serviceName, revision)) - return - } - renderChart, ok := chartMap[serviceName] - if !ok { - errList = multierror.Append(errList, fmt.Errorf("failed to find render chart for service %s in target namespace", serviceName)) - return - } - err = prepareChartVersionData(productName, serviceObj, renderChart, renderSet) - if err != nil { - errList = multierror.Append(errList, fmt.Errorf("failed to prepare chart info for service %s", serviceObj.ServiceName)) - return - } - }(service.ServiceName, service.Revision) - } - wg.Wait() - - if errList.ErrorOrNil() != nil { - return nil, errList.ErrorOrNil() - } - - // expand file info for first service - serviceToExpand := ret.ChartInfos[0].ServiceName - fis, err := loadChartFilesInfo(productName, serviceToExpand, serviceMap[serviceToExpand].Revision, "") - if err != nil { - log.Errorf("Failed to load service file info, err: %s", err) - return nil, e.ErrListTemplate.AddErr(err) - } - ret.FileInfos = fis - - return ret, nil -} diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 196d63db..4c9e1fb9 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -41,13 +41,12 @@ type ProjectListOptions struct { type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation - Alias string `json:"alias"` - Desc string `json:"desc"` - UpdatedAt int64 `json:"updatedAt"` - UpdatedBy string `json:"updatedBy"` - Onboard bool `json:"onboard"` - Public bool `json:"public"` - DeployType string `json:"deployType"` + Alias string `json:"alias"` + Desc string `json:"desc"` + UpdatedAt int64 `json:"updatedAt"` + UpdatedBy string `json:"updatedBy"` + Onboard bool `json:"onboard"` + Public bool `json:"public"` } type ProjectBriefRepresentation struct { @@ -99,13 +98,12 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, Envs: nameWithEnvMap[name], }, - Alias: info.Alias, - Desc: info.Desc, - UpdatedAt: info.UpdatedAt, - UpdatedBy: info.UpdatedBy, - Onboard: info.OnboardStatus != 0, - Public: info.Public, - DeployType: info.DeployType, + Alias: info.Alias, + Desc: info.Desc, + UpdatedAt: info.UpdatedAt, + UpdatedBy: info.UpdatedBy, + Onboard: info.OnboardStatus != 0, + Public: info.Public, }) } diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index d1462e36..0573b098 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -46,30 +46,13 @@ func GetHelmServiceModule(c *gin.Context) { func GetFilePath(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - revision := int64(0) - var err error - if len(c.Query("revision")) > 0 { - revision, err = strconv.ParseInt(c.Query("revision"), 10, 64) - } - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") - return - } - ctx.Resp, ctx.Err = svcservice.GetFilePath(c.Param("serviceName"), c.Param("productName"), revision, c.Query("dir"), ctx.Logger) + ctx.Resp, ctx.Err = svcservice.GetFilePath(c.Param("serviceName"), c.Param("productName"), c.Query("dir"), ctx.Logger) } func GetFileContent(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - - param := new(svcservice.GetFileContentParam) - err := c.ShouldBindQuery(param) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddErr(err) - return - } - - ctx.Resp, ctx.Err = svcservice.GetFileContent(c.Param("serviceName"), c.Param("productName"), param, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.GetFileContent(c.Param("serviceName"), c.Param("productName"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) } func CreateOrUpdateHelmService(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index a9d2dec2..562b3852 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -113,13 +113,6 @@ type ChartTemplateData struct { DefaultValuesYAML []byte // content of values.yaml in template } -type GetFileContentParam struct { - FilePath string `json:"filePath" form:"filePath"` - FileName string `json:"fileName" form:"fileName"` - Revision int64 `json:"revision" form:"revision"` - DeliveryVersion bool `json:"deliveryVersion" form:"deliveryVersion"` -} - func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { helmService := &HelmService{ ServiceInfos: []*models.Service{}, @@ -140,7 +133,7 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, helmService.ServiceInfos = services if len(services) > 0 { - fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, 0, "") + fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, "") if err != nil { log.Errorf("Failed to load service file info, err: %s", err) return nil, e.ErrListTemplate.AddErr(err) @@ -179,39 +172,24 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * return helmServiceModule, err } -func GetFilePath(serviceName, productName string, revision int64, dir string, _ *zap.SugaredLogger) ([]*types.FileInfo, error) { - return loadServiceFileInfos(productName, serviceName, revision, dir) +func GetFilePath(serviceName, productName, dir string, _ *zap.SugaredLogger) ([]*types.FileInfo, error) { + return loadServiceFileInfos(productName, serviceName, dir) } -func GetFileContent(serviceName, productName string, param *GetFileContentParam, log *zap.SugaredLogger) (string, error) { - filePath, fileName, revision, forDelivery := param.FilePath, param.FileName, param.Revision, param.DeliveryVersion +func GetFileContent(serviceName, productName, filePath, fileName string, log *zap.SugaredLogger) (string, error) { + base := config.LocalServicePath(productName, serviceName) + svc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, - Revision: revision, }) if err != nil { return "", e.ErrFileContent.AddDesc(err.Error()) } - base := config.LocalServicePath(productName, serviceName) - if revision > 0 { - base = config.LocalServicePathWithRevision(productName, serviceName, revision) - if err = commonservice.PreloadServiceManifestsByRevision(base, svc); err != nil { - log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - svc.Revision, svc.ServiceName) - } - } - if err != nil || revision == 0 { - base = config.LocalServicePath(productName, serviceName) - err = commonservice.PreLoadServiceManifests(base, svc) - if err != nil { - return "", e.ErrFileContent.AddDesc(err.Error()) - } - } - - if forDelivery { - base = config.LocalDeliveryChartPathWithRevision(productName, serviceName, revision) + err = commonservice.PreLoadServiceManifests(base, svc) + if err != nil { + return "", e.ErrFileContent.AddDesc(err.Error()) } file := filepath.Join(base, serviceName, filePath, fileName) @@ -934,7 +912,9 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return serviceObj, nil } -func loadServiceFileInfos(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { +func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileInfo, error) { + base := config.LocalServicePath(productName, serviceName) + svc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, @@ -943,22 +923,6 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s return nil, e.ErrFilePath.AddDesc(err.Error()) } - base := config.LocalServicePath(productName, serviceName) - if revision > 0 { - base = config.LocalServicePathWithRevision(productName, serviceName, revision) - if err = commonservice.PreloadServiceManifestsByRevision(base, svc); err != nil { - log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - svc.Revision, svc.ServiceName) - } - } - if err != nil || revision == 0 { - base = config.LocalServicePath(productName, serviceName) - err = commonservice.PreLoadServiceManifests(base, svc) - if err != nil { - return nil, e.ErrFilePath.AddDesc(err.Error()) - } - } - err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 7f92809e..31a84a53 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -49,7 +49,6 @@ func CreateHelmRepo(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid url") return } - ctx.Err = service.CreateHelmRepo(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index decf797e..00ca9b6c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -35,15 +35,15 @@ import ( "github.com/koderover/zadig/pkg/util" ) -// get global config payload -func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskCreator string, log *zap.SugaredLogger) (int64, error) { +func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log *zap.SugaredLogger) error { + // 获取全局configpayload configPayload := commonservice.GetConfigPayload(0) repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err != nil { log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) - return 0, fmt.Errorf("failed to query registries") + return fmt.Errorf("failed to query registries") } registriesInvolved := sets.NewString() @@ -68,13 +68,13 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC defaultS3, err := s3.FindDefaultS3() if err != nil { err = e.ErrFindDefaultS3Storage.AddDesc("default storage is required by distribute task") - return 0, err + return err } defaultURL, err := defaultS3.GetEncryptedURL() if err != nil { err = e.ErrS3Storage.AddErr(err) - return 0, err + return err } task := &task.Task{ @@ -82,7 +82,6 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC ProductName: args.ProjectName, Status: config.StatusCreated, ArtifactPackageTaskArgs: args, - TaskCreator: taskCreator, ConfigPayload: configPayload, StorageURI: defaultURL, } @@ -101,14 +100,14 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC }).ToSubTask() if err != nil { - return 0, err + return err } task.SubTasks = []map[string]interface{}{subTask} if err := ensurePipelineTask(task, "", log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) - return 0, err + return err } stages := make([]*commonmodels.Stage, 0) @@ -118,14 +117,14 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC sort.Sort(ByStageKind(stages)) task.Stages = stages if len(task.Stages) == 0 { - return 0, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + return e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) } pipelineName := fmt.Sprintf("%s-%s-%s", args.ProjectName, args.EnvName, "artifact") nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.ServiceTaskFmt, pipelineName)) if err != nil { log.Errorf("CreateServiceTask Counter.GetNextSeq error: %v", err) - return 0, e.ErrGetCounter.AddDesc(err.Error()) + return e.ErrGetCounter.AddDesc(err.Error()) } task.SubTasks = []map[string]interface{}{} @@ -134,8 +133,8 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC if err := CreateTask(task); err != nil { log.Error(err) - return 0, e.ErrCreateTask + return e.ErrCreateTask } - return nextTaskID, nil + return nil } diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go index 8f9e41da..d58b587b 100644 --- a/pkg/microservice/packager/core/service/packager.go +++ b/pkg/microservice/packager/core/service/packager.go @@ -17,10 +17,10 @@ limitations under the License. package service type ImageData struct { - ImageUrl string `yaml:"image_url" json:"image_url"` - ImageName string `yaml:"image_name" json:"image_name"` - ImageTag string `yaml:"image_tag" json:"image_tag"` - RegistryID string `yaml:"registry_id" json:"registry_id"` + ImageUrl string `yaml:"image_url"` + ImageName string `yaml:"image_name"` + ImageTag string `yaml:"image_tag"` + RegistryID string `yaml:"registry_id,omitempty"` } // ImagesByService defines all images in a service diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index ba4e036e..339ab5de 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -22,7 +22,6 @@ import ( "encoding/base64" "encoding/json" "fmt" - "io" "os" "path/filepath" "strings" @@ -94,68 +93,15 @@ func buildTargetImage(imageName, imageTag, host, nameSpace string) string { return ret } -func pullImage(dockerClient *client.Client, imageUrl string, options *types.ImagePullOptions) error { - // pull image - pullResponse, err := dockerClient.ImagePull(context.TODO(), imageUrl, *options) - if err != nil { - return err - } - - defer func(pullResponse io.ReadCloser) { - err := pullResponse.Close() - if err != nil { - log.Errorf("failed to close response reader") - } - }(pullResponse) - - bs, err := io.ReadAll(pullResponse) - if err != nil { - return err - } - - if strings.Contains(string(bs), "error") { - log.Errorf("image push failed: %s", string(bs)) - return fmt.Errorf("failed to push image") - } - return nil -} - -func pushImage(dockerClient *client.Client, targetImageUrl string, options *types.ImagePushOptions) error { - pushResponse, err := dockerClient.ImagePush(context.TODO(), targetImageUrl, *options) - if err != nil { - return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) - } - - defer func(pullResponse io.ReadCloser) { - err := pullResponse.Close() - if err != nil { - log.Errorf("failed to close response reader") - } - }(pushResponse) - - bs, err := io.ReadAll(pushResponse) - if err != nil { - return err - } - - if strings.Contains(string(bs), "error") { - log.Errorf("image push failed: %s", string(bs)) - return fmt.Errorf("failed to push image") - } - return nil -} - -func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) ([]*ImageData, error) { +func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) error { targetImageUrlByRepo := make(map[string][]string) - retImages := make([]*ImageData, 0) - for _, singleImage := range imageByService.Images { options := types.ImagePullOptions{} // for images from public repo,registryID won't be appointed if len(singleImage.RegistryID) > 0 { registryInfo, ok := allRegistries[singleImage.RegistryID] if !ok { - return nil, fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) + return fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) } encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ Username: registryInfo.UserName, @@ -163,15 +109,15 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ServerAddress: registryInfo.Host, }) if err != nil { - return nil, errors.Wrapf(err, "faied to create docker pull auth data") + return errors.Wrapf(err, "faied to create docker pull auth data") } options.RegistryAuth = encodedAuth } // pull image - err := pullImage(dockerClient, singleImage.ImageUrl, &options) + _, err := dockerClient.ImagePull(context.TODO(), singleImage.ImageUrl, options) if err != nil { - return nil, errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) + return errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) } // tag image @@ -179,15 +125,9 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri targetImage := buildTargetImage(singleImage.ImageName, singleImage.ImageTag, registry.Host, registry.Namespace) err = dockerClient.ImageTag(context.TODO(), singleImage.ImageUrl, targetImage) if err != nil { - return nil, errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) + return errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) } targetImageUrlByRepo[registry.RegistryID] = append(targetImageUrlByRepo[registry.RegistryID], targetImage) - retImages = append(retImages, &ImageData{ - ImageUrl: targetImage, - ImageName: singleImage.ImageName, - ImageTag: singleImage.ImageTag, - RegistryID: singleImage.RegistryID, - }) } } @@ -199,20 +139,19 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ServerAddress: registry.Host, }) if err != nil { - return nil, errors.Wrapf(err, "faied to create docker push auth data") + return errors.Wrapf(err, "faied to create docker push auth data") } options := types.ImagePushOptions{ RegistryAuth: encodedAuth, } - for _, targetImageUrl := range targetImageUrlByRepo[registry.RegistryID] { - err = pushImage(dockerClient, targetImageUrl, &options) + _, err = dockerClient.ImagePush(context.TODO(), targetImageUrl, options) if err != nil { - return nil, errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) } } } - return retImages, nil + return nil } func (p *Packager) Exec() error { @@ -262,7 +201,7 @@ func (p *Packager) Exec() error { result := &PackageResult{ ServiceName: imageByService.ServiceName, } - images, err := handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) + err = handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) if err != nil { result.Result = "failed" @@ -270,7 +209,7 @@ func (p *Packager) Exec() error { log.Errorf("[result][fail][%s][%s]", imageByService.ServiceName, err) } else { result.Result = "success" - result.ImageData = images + result.ImageData = imageByService.Images log.Infof("[result][success][%s]", imageByService.ServiceName) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go index 5ebe24fb..9ac39d95 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go @@ -26,7 +26,7 @@ type ArtifactPackage struct { TaskType config.TaskType `bson:"type" json:"type"` Enabled bool `bson:"enabled" json:"enabled"` TaskStatus config.Status `bson:"status" json:"status"` - Progress string `bson:"progress" json:"progress"` + Progress string `bson:"progress" json:"progress"` Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index beb9d603..caca4a5a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -134,7 +134,7 @@ type ReleaseConfig struct { // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string // PackagerImage sets docker build image - // e.g. xxx.com/resources/packager-plugin:v0.1.0 + // e.g. xxx.com/resources/predator-plugin:v0.1.0 PackagerImage string } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 250cc4b4..df2c1159 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -272,16 +272,6 @@ const ( FunctionTestType = "function" ) -const ( - DeliveryVersionTypeChart = "HelmChart" - DeliveryVersionTypeK8SWorkflow = "K8SWorkflow" -) - -const ( - DeliveryDeployTypeImage = "image" - DeliveryDeployTypeChart = "chart" -) - const ( AuthorizationHeader = "Authorization" ) @@ -337,21 +327,6 @@ const ( ProductStatusUnstable = "Unstable" ) -// DeliveryVersion status -const ( - DeliveryVersionStatusSuccess = "success" - DeliveryVersionStatusFailed = "failed" - DeliveryVersionStatusCreating = "creating" - DeliveryVersionStatusRetrying = "retrying" -) - -const ( - DeliveryVersionPackageStatusSuccess = "success" - DeliveryVersionPackageStatusFailed = "failed" - DeliveryVersionPackageStatusWaiting = "waiting" - DeliveryVersionPackageStatusUploading = "uploading" -) - const ( NormalModeProduct = "normal" ) diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index e0ffc2ee..45554ea7 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -680,10 +680,4 @@ var ( ErrUpdateExternalLink = NewHTTPError(6842, "更新链接失败") ErrDeleteExternalLink = NewHTTPError(6843, "删除链接失败") ErrListExternalLink = NewHTTPError(6844, "获取链接列表失败") - - //----------------------------------------------------------------------------------------------- - // helm releated Error Range: 6850 - 6869 - //----------------------------------------------------------------------------------------------- - ErrListHelmReleases = NewHTTPError(6850, "获取release失败") - ErrGetHelmCharts = NewHTTPError(6851, "获取chart信息失败") ) -- Gitee From 8f668de8f6367b425127ed1488acd7feb98e980a Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 15:36:07 +0800 Subject: [PATCH 0965/2647] optimize extract image logic Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/service.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index fbcf8086..7715df4b 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -651,17 +651,18 @@ func ExtractImageTag(imageURI string) string { return "" } -// ExtractRegistryNamespace extract registry namespace form image uri +// ExtractRegistryNamespace extract registry namespace from image uri func ExtractRegistryNamespace(imageURI string) string { imageURI = strings.TrimPrefix(imageURI, "http://") imageURI = strings.TrimPrefix(imageURI, "https://") - firstIndex := strings.Index(imageURI, "/") - lastIndex := strings.LastIndex(imageURI, "/") - if lastIndex == firstIndex { + imageComponent := strings.Split(imageURI, "/") + if len(imageComponent) <= 2 { return "" } - return strings.TrimPrefix(imageURI[firstIndex:lastIndex], "/") + + nsComponent := imageComponent[1 : len(imageComponent)-1] + return strings.Join(nsComponent, "/") } func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { -- Gitee From 641d26251cdbab21a5f9ad17bb03433b7e84225a Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 15:55:42 +0800 Subject: [PATCH 0966/2647] optimize code Signed-off-by: allenshen --- .../repository/models/delivery_distribute.go | 16 +++--- .../aslan/core/delivery/service/docker.go | 57 ------------------- 2 files changed, 8 insertions(+), 65 deletions(-) delete mode 100644 pkg/microservice/aslan/core/delivery/service/docker.go diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go index 4365fa13..732e24fc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go @@ -23,22 +23,22 @@ import ( ) type DeliveryDistribute struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ReleaseID primitive.ObjectID `bson:"release_id" json:"releaseId"` ServiceName string `bson:"service_name" json:"serviceName,omitempty"` DistributeType config.DistributeType `bson:"distribute_type" json:"distributeType"` RegistryName string `bson:"registry_name" json:"registryName"` - ChartVersion string `bson:"chart_version" json:"chartVersion,omitempty"` - ChartName string `bson:"chart_name" json:"chartName,omitempty"` - ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName,omitempty"` - SubDistributes []*DeliveryDistribute `bson:"-" json:"subDistributes,omitempty"` + ChartVersion string `bson:"chart_version" json:"chartVersion,omitempty"` + ChartName string `bson:"chart_name" json:"chartName,omitempty"` + ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName,omitempty"` + SubDistributes []*DeliveryDistribute `bson:"-" json:"subDistributes,omitempty"` Namespace string `bson:"namespace" json:"namespace,omitempty"` PackageFile string `bson:"package_file" json:"packageFile,omitempty"` RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey,omitempty"` DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl,omitempty"` - S3StorageID string `bson:"s3_storage_id" json:"s3StorageID"` - StorageURL string `bson:"-" json:"storageUrl"` - StorageBucket string `bson:"-" json:"storageBucket"` + S3StorageID string `bson:"s3_storage_id" json:"s3StorageID"` + StorageURL string `bson:"-" json:"storageUrl"` + StorageBucket string `bson:"-" json:"storageBucket"` SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl,omitempty"` StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty,omitempty"` EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty,omitempty"` diff --git a/pkg/microservice/aslan/core/delivery/service/docker.go b/pkg/microservice/aslan/core/delivery/service/docker.go deleted file mode 100644 index 65a35941..00000000 --- a/pkg/microservice/aslan/core/delivery/service/docker.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import "os/exec" - -const dockerExe = "/usr/local/bin/docker" - -func dockerLogin(user, password, registry string) *exec.Cmd { - return exec.Command( - dockerExe, - "login", - "-u", user, - "-p", password, - registry, - ) -} - -func dockerInfo() *exec.Cmd { - return exec.Command(dockerExe, "info") -} - -func dockerPush(fullImage string) *exec.Cmd { - args := []string{ - "push", - fullImage, - } - return exec.Command(dockerExe, args...) -} - -func dockerPull(image string) *exec.Cmd { - args := []string{"pull", image} - return exec.Command(dockerExe, args...) -} - -func dockerTag(sourceFullImage, targetFullImage string) *exec.Cmd { - args := []string{ - "tag", - sourceFullImage, - targetFullImage, - } - return exec.Command(dockerExe, args...) -} -- Gitee From ad661bd796ec54b7849cdb847246e54e221a064a Mon Sep 17 00:00:00 2001 From: Landy Date: Fri, 10 Dec 2021 16:06:37 +0800 Subject: [PATCH 0967/2647] Revert "Revert "create delivery version"" --- go.mod | 1 + go.sum | 5 + pkg/config/config.go | 4 + pkg/microservice/aslan/config/config.go | 4 + pkg/microservice/aslan/config/consts.go | 1 + .../repository/models/delivery_distribute.go | 23 +- .../repository/models/delivery_version.go | 34 +- .../core/common/repository/models/queue.go | 27 +- .../models/task/artifact_package.go | 25 + .../common/repository/models/task/model.go | 12 +- .../repository/mongodb/delivery_distribute.go | 9 +- .../repository/mongodb/delivery_version.go | 21 +- .../common/repository/mongodb/helm_repo.go | 32 +- .../repository/mongodb/template/product.go | 37 +- .../aslan/core/common/service/fs/s3.go | 30 +- .../aslan/core/common/service/s3/s3.go | 10 + .../aslan/core/common/service/service.go | 54 +- .../aslan/core/delivery/handler/router.go | 8 + .../aslan/core/delivery/handler/version.go | 283 ++-- .../aslan/core/delivery/service/docker.go | 57 + .../aslan/core/delivery/service/version.go | 1474 ++++++++++++++++- .../aslan/core/environment/handler/helm.go | 40 + .../aslan/core/environment/handler/router.go | 3 + .../aslan/core/environment/service/helm.go | 259 +++ .../aslan/core/project/service/project.go | 26 +- .../aslan/core/service/handler/helm.go | 21 +- .../aslan/core/service/service/helm.go | 60 +- .../aslan/core/system/handler/helm.go | 1 + .../service/workflow/artifact_task.go | 23 +- .../packager/core/service/packager.go | 8 +- .../packager/core/service/service.go | 85 +- .../service/types/task/artifact_package.go | 2 +- .../core/service/types/task/config_payload.go | 2 +- pkg/setting/consts.go | 25 + pkg/tool/errors/http_errors.go | 6 + 35 files changed, 2397 insertions(+), 315 deletions(-) create mode 100644 pkg/microservice/aslan/core/delivery/service/docker.go create mode 100644 pkg/microservice/aslan/core/environment/handler/helm.go create mode 100644 pkg/microservice/aslan/core/environment/service/helm.go diff --git a/go.mod b/go.mod index c00c9190..0f9924dc 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v4 v4.1.1 + github.com/chartmuseum/helm-push v0.10.1 github.com/coocood/freecache v1.1.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b diff --git a/go.sum b/go.sum index 79c316c2..123658a6 100644 --- a/go.sum +++ b/go.sum @@ -286,6 +286,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chartmuseum/helm-push v0.10.1 h1:NqStAmarEy0GnrDCk0zubGKeRmkhOm/rRi5au8h76BA= +github.com/chartmuseum/helm-push v0.10.1/go.mod h1:s6xTICU31jKdLkOXS+GgaR61E+oU4h8TWb1yZcHq8OE= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -537,6 +539,7 @@ github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc= github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w= @@ -2350,6 +2353,8 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8 k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao= +k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= diff --git a/pkg/config/config.go b/pkg/config/config.go index 593eff9c..4f2ef7d9 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -210,6 +210,10 @@ func ObjectStorageTemplatePath(name, kind string) string { return filepath.Join("templates", kind, name) } +func ObjectStorageDeliveryVersionPath(project string) string { + return filepath.Join("delivery-distributes", "files", project) +} + func ObjectStorageChartTemplatePath(name string) string { return ObjectStorageTemplatePath(name, setting.ChartTemplatesPath) } diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index edca3a57..121add2c 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -234,6 +234,10 @@ func LocalServicePathWithRevision(project, service string, revision int64) strin return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("%d", revision)) } +func LocalDeliveryChartPathWithRevision(project, service string, revision int64) string { + return configbase.LocalServicePathWithRevision(project, service, fmt.Sprintf("delivery/%d", revision)) +} + func ServiceNameWithRevision(serviceName string, revision int64) string { return fmt.Sprintf("%s-%d", serviceName, revision) } diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 99f46821..9b91eb4c 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -141,6 +141,7 @@ type DistributeType string const ( File DistributeType = "file" Image DistributeType = "image" + Chart DistributeType = "chart" ) type NotifyType int diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go index db24294d..4365fa13 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go @@ -25,16 +25,23 @@ import ( type DeliveryDistribute struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ReleaseID primitive.ObjectID `bson:"release_id" json:"releaseId"` - ServiceName string `bson:"service_name" json:"serviceName"` + ServiceName string `bson:"service_name" json:"serviceName,omitempty"` DistributeType config.DistributeType `bson:"distribute_type" json:"distributeType"` RegistryName string `bson:"registry_name" json:"registryName"` - Namespace string `bson:"namespace" json:"namespace"` - PackageFile string `bson:"package_file" json:"packageFile"` - RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey"` - DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl"` - SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl"` - StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + ChartVersion string `bson:"chart_version" json:"chartVersion,omitempty"` + ChartName string `bson:"chart_name" json:"chartName,omitempty"` + ChartRepoName string `bson:"chart_repo_name" json:"chartRepoName,omitempty"` + SubDistributes []*DeliveryDistribute `bson:"-" json:"subDistributes,omitempty"` + Namespace string `bson:"namespace" json:"namespace,omitempty"` + PackageFile string `bson:"package_file" json:"packageFile,omitempty"` + RemoteFileKey string `bson:"remote_file_key" json:"remoteFileKey,omitempty"` + DestStorageURL string `bson:"dest_storage_url" json:"destStorageUrl,omitempty"` + S3StorageID string `bson:"s3_storage_id" json:"s3StorageID"` + StorageURL string `bson:"-" json:"storageUrl"` + StorageBucket string `bson:"-" json:"storageBucket"` + SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go index 459d7f96..ed851379 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_version.go @@ -20,18 +20,30 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) +type DeliveryVersionProgress struct { + SuccessChartCount int `json:"successChartCount"` + TotalChartCount int `json:"totalChartCount"` + PackageUploadStatus string `json:"packageStatus"` + Error string `json:"error"` +} + type DeliveryVersion struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Version string `bson:"version" json:"version"` - ProductName string `bson:"product_name" json:"productName"` - WorkflowName string `bson:"workflow_name" json:"workflowName"` - TaskID int `bson:"task_id" json:"taskId"` - Desc string `bson:"desc" json:"desc"` - Labels []string `bson:"labels" json:"labels"` - ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` - CreatedBy string `bson:"created_by" json:"createdBy"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Version string `bson:"version" json:"version"` + ProductName string `bson:"product_name" json:"productName"` + WorkflowName string `bson:"workflow_name" json:"workflowName"` + Type string `bson:"type" json:"type"` + TaskID int `bson:"task_id" json:"taskId"` + Desc string `bson:"desc" json:"desc"` + Labels []string `bson:"labels" json:"labels"` + ProductEnvInfo *Product `bson:"product_env_info" json:"productEnvInfo"` + Status string `bson:"status" json:"status"` + Error string `bson:"error" json:"-"` + Progress *DeliveryVersionProgress `bson:"-" json:"progress"` + CreateArgument interface{} `bson:"createArgument" json:"-"` + CreatedBy string `bson:"created_by" json:"createdBy"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } func (DeliveryVersion) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index ab588767..8aee0760 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -49,13 +49,13 @@ type Queue struct { IsArchived bool `bson:"is_archived" json:"is_archived"` AgentID string `bson:"agent_id" json:"agent_id"` MultiRun bool `bson:"multi_run" json:"multi_run"` - Target string `bson:"target,omitempty" json:"target"` // target 服务名称, k8s为容器名称, 物理机为服务名 - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` // 使用预定义编译管理模块中的内容生成SubTasks, 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer 如果为空,则使用pipeline自定义SubTasks + Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs 单服务工作流任务参数 - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs 多服务工作流任务参数 - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs 测试任务参数 - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -96,9 +96,10 @@ type ServiceTaskArgs struct { } type ImageData struct { - ImageUrl string `bson:"image_url" json:"image_url"` - ImageName string `bson:"image_name" json:"image_name"` - ImageTag string `bson:"image_tag" json:"image_tag"` + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageTag string `bson:"image_tag" json:"image_tag"` + RegistryID string `bson:"registry_id" json:"registry_id"` } type ImagesByService struct { @@ -108,10 +109,10 @@ type ImagesByService struct { type ArtifactPackageTaskArgs struct { ProjectName string `bson:"project_name" json:"project_name"` - EnvName string `bson:"env_name" json:"env_name"` - Images []*ImagesByService `bson:"images" json:"images"` - SourceRegistries []string `json:"source_registries" json:"source_registries"` - TargetRegistries []string `json:"target_registries" json:"target_registries"` + EnvName string `bson:"env_name" json:"env_name"` + Images []*ImagesByService `bson:"images" json:"images"` + SourceRegistries []string `json:"source_registries" json:"source_registries"` + TargetRegistries []string `json:"target_registries" json:"target_registries"` } type ConfigPayload struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go index 8f578b62..89f89de4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/artifact_package.go @@ -17,12 +17,27 @@ limitations under the License. package task import ( + "encoding/json" "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) +type ImageData struct { + ImageUrl string `bson:"image_url" json:"image_url"` + ImageName string `bson:"image_name" json:"image_name"` + ImageNamespace string `bson:"image_namespace" json:"image_namespace"` + ImageTag string `bson:"image_tag" json:"image_tag"` +} + +type ServicePackageResult struct { + ServiceName string `json:"service_name"` + Result string `json:"result"` + ErrorMsg string `json:"error_msg"` + ImageData []*ImageData `json:"image_data"` +} + type ArtifactPackage struct { TaskType config.TaskType `bson:"type" json:"type"` TaskStatus config.Status `bson:"status" json:"status"` @@ -32,6 +47,7 @@ type ArtifactPackage struct { StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` + Progress string `bson:"progress" json:"progress"` // source images Images []*models.ImagesByService `bson:"images" json:"images"` @@ -48,3 +64,12 @@ func (ri *ArtifactPackage) ToSubTask() (map[string]interface{}, error) { } return task, nil } + +func (ri *ArtifactPackage) GetProgress() ([]*ServicePackageResult, error) { + if len(ri.Progress) == 0 { + return nil, nil + } + ret := make([]*ServicePackageResult, 0) + err := json.Unmarshal([]byte(ri.Progress), &ret) + return ret, err +} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index d1d23f20..d4783d2e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -76,12 +76,12 @@ type Task struct { StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `json:"resetImage" bson:"resetImage"` - TriggerBy *models.TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } //type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go index 0b3ce01a..98a9c39e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_distribute.go @@ -35,6 +35,7 @@ type DeliveryDistributeArgs struct { ID string `json:"id"` ReleaseID string `json:"releaseId"` ServiceName string `json:"serviceName"` + ChartName string `json:"chartName"` DistributeType config.DistributeType `json:"distributeType"` } @@ -62,6 +63,7 @@ func (c *DeliveryDistributeColl) EnsureIndex(ctx context.Context) error { Keys: bson.D{ bson.E{Key: "release_id", Value: 1}, bson.E{Key: "service_name", Value: 1}, + bson.E{Key: "chart_name", Value: 1}, bson.E{Key: "deleted_at", Value: 1}, }, Options: options.Index().SetUnique(false), @@ -115,8 +117,11 @@ func (c *DeliveryDistributeColl) Find(args *DeliveryDistributeArgs) ([]*models.D } query := bson.M{"release_id": releaseID, "deleted_at": 0} - if args.DistributeType != "" { - query["distribute_type"] = config.File + if len(args.DistributeType) > 0 { + query["distribute_type"] = string(args.DistributeType) + } + if len(args.ChartName) > 0 { + query["chart_name"] = args.ChartName } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index bae5fc66..e0adcfc8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -34,6 +34,7 @@ import ( type DeliveryVersionArgs struct { ID string `json:"id"` ProductName string `json:"productName"` + Version string `json:"version"` WorkflowName string `json:"workflowName"` TaskID int `json:"taskId"` PerPage int `json:"perPage"` @@ -89,7 +90,6 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver return nil, errors.New("nil delivery_version args") } - var resp []*models.DeliveryVersion query := bson.M{"deleted_at": 0} if args.ProductName != "" { query["product_name"] = args.ProductName @@ -113,6 +113,7 @@ func (c *DeliveryVersionColl) Find(args *DeliveryVersionArgs) ([]*models.Deliver return nil, err } + resp := make([]*models.DeliveryVersion, 0) err = cursor.All(ctx, &resp) if err != nil { return nil, err @@ -164,7 +165,13 @@ func (c *DeliveryVersionColl) Get(args *DeliveryVersionArgs) (*models.DeliveryVe resp := new(models.DeliveryVersion) var query map[string]interface{} if args.ID != "" { - query = bson.M{"_id": args.ID, "deleted_at": 0} + oid, err := primitive.ObjectIDFromHex(args.ID) + if err != nil { + return nil, err + } + query = bson.M{"_id": oid, "deleted_at": 0} + } else if len(args.Version) > 0 { + query = bson.M{"product_name": args.ProductName, "version": args.Version, "deleted_at": 0} } else { query = bson.M{"product_name": args.ProductName, "workflow_name": args.WorkflowName, "task_id": args.TaskID, "deleted_at": 0} } @@ -190,6 +197,16 @@ func (c *DeliveryVersionColl) Insert(args *models.DeliveryVersion) error { return nil } +func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr string) error { + query := bson.M{"version": versionName, "deleted_at": 0} + change := bson.M{"$set": bson.M{ + "status": status, + "error": errorStr, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { if args == nil { return errors.New("nil delivery_version args") diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go b/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go index 6d773167..86fff875 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/helm_repo.go @@ -37,6 +37,11 @@ type HelmRepoColl struct { coll string } +type HelmRepoFindOption struct { + Id string + RepoName string +} + func NewHelmRepoColl() *HelmRepoColl { name := models.HelmRepo{}.TableName() coll := &HelmRepoColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} @@ -49,7 +54,12 @@ func (c *HelmRepoColl) GetCollectionName() string { } func (c *HelmRepoColl) EnsureIndex(ctx context.Context) error { - return nil + mod := mongo.IndexModel{ + Keys: bson.M{"repo_name": 1}, + Options: options.Index().SetUnique(false), + } + _, err := c.Indexes().CreateOne(ctx, mod) + return err } func (c *HelmRepoColl) Create(args *models.HelmRepo) error { @@ -64,6 +74,26 @@ func (c *HelmRepoColl) Create(args *models.HelmRepo) error { return err } +func (c *HelmRepoColl) Find(opt *HelmRepoFindOption) (*models.HelmRepo, error) { + query := bson.M{} + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return nil, err + } + query["_id"] = oid + } + if len(opt.RepoName) > 0 { + query["repo_name"] = opt.RepoName + } + ret := new(models.HelmRepo) + err := c.FindOne(context.TODO(), query).Decode(ret) + if err != nil { + return nil, err + } + return ret, nil +} + func (c *HelmRepoColl) Update(id string, args *models.HelmRepo) error { oid, err := primitive.ObjectIDFromHex(id) if err != nil { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 4cf141c2..474d2e96 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -39,6 +39,7 @@ type ProjectInfo struct { UpdatedBy string `bson:"update_by"` OnboardStatus int `bson:"onboarding_status"` Public bool `bson:"public"` + DeployType string `bson:"deploy_type"` } type ProductColl struct { @@ -82,8 +83,8 @@ func (c *ProductColl) FindProjectName(project string) (*template.Product, error) } func (c *ProductColl) ListNames(inNames []string) ([]string, error) { - res, err := c.listProjects(inNames, bson.D{ - {"product_name", 1}, + res, err := c.listProjects(inNames, bson.M{ + "product_name": "$product_name", }) if err != nil { return nil, err @@ -98,29 +99,34 @@ func (c *ProductColl) ListNames(inNames []string) ([]string, error) { } func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error) { - return c.listProjects(inNames, bson.D{ - {"product_name", 1}, - {"project_name", 1}, - {"description", 1}, - {"update_time", 1}, - {"update_by", 1}, - {"onboarding_status", 1}, - {"public", 1}, + return c.listProjects(inNames, bson.M{ + "product_name": "$product_name", + "project_name": "$project_name", + "description": "$description", + "update_time": "$update_time", + "update_by": "$update_by", + "onboarding_status": "$onboarding_status", + "public": "$public", + "deploy_type": "$product_feature.deploy_type", }) } -func (c *ProductColl) listProjects(inNames []string, projection bson.D) ([]*ProjectInfo, error) { - opts := options.Find() +func (c *ProductColl) listProjects(inNames []string, projection bson.M) ([]*ProjectInfo, error) { filter := bson.M{} if len(inNames) > 0 { filter["product_name"] = bson.M{"$in": inNames} } - if len(projection) > 0 { - opts.SetProjection(projection) + pipeline := []bson.M{ + { + "$match": filter, + }, + { + "$project": projection, + }, } - cursor, err := c.Collection.Find(context.TODO(), filter, opts) + cursor, err := c.Collection.Aggregate(context.TODO(), pipeline) if err != nil { return nil, err } @@ -130,7 +136,6 @@ func (c *ProductColl) listProjects(inNames []string, projection bson.D) ([]*Proj if err != nil { return nil, err } - return res, nil } diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 67c5db65..dfe02ea2 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -24,15 +24,35 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" fsutil "github.com/koderover/zadig/pkg/util/fs" ) -// ArchiveAndUploadFilesToS3 archive local files and upload to default s3 storage -// if multiple names appointed, s3storage.copy will be used to handle extra names +func ArchiveAndUploadFilesToSpecifiedS3(fileTree fs.FS, names []string, s3Base, s3Id string, logger *zap.SugaredLogger) error { + s3Storage, err := s3service.FindS3ById(s3Id) + if err != nil { + logger.Errorf("Failed to find default s3, err:%v", err) + return err + } + return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) +} + func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, logger *zap.SugaredLogger) error { + s3Storage, err := s3service.FindDefaultS3() + if err != nil { + logger.Errorf("Failed to find default s3, err:%v", err) + return err + } + return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) +} + +// archiveAndUploadFiles archive local files and upload to default s3 storage +// if multiple names appointed, s3storage.copy will be used to handle extra names +func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Storage *s3.S3, logger *zap.SugaredLogger) error { + if len(names) == 0 { return fmt.Errorf("names not appointed") } @@ -53,11 +73,7 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, lo logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) return err } - s3Storage, err := s3service.FindDefaultS3() - if err != nil { - logger.Errorf("Failed to find default s3, err:%v", err) - return err - } + forcedPathStyle := true if s3Storage.Provider == setting.ProviderSourceAli { forcedPathStyle = false diff --git a/pkg/microservice/aslan/core/common/service/s3/s3.go b/pkg/microservice/aslan/core/common/service/s3/s3.go index 24655148..fc363b61 100644 --- a/pkg/microservice/aslan/core/common/service/s3/s3.go +++ b/pkg/microservice/aslan/core/common/service/s3/s3.go @@ -148,6 +148,16 @@ func FindDefaultS3() (*S3, error) { return &S3{S3Storage: storage}, nil } +func FindS3ById(id string) (*S3, error) { + storage, err := commonrepo.NewS3StorageColl().Find(id) + if err != nil { + log.Warnf("Failed to find s3 in db, err: %s", err) + return nil, err + } + + return &S3{S3Storage: storage}, nil +} + // 获取内置的s3 func FindInternalS3() *S3 { storage := &models.S3Storage{ diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 78040931..fbcf8086 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "regexp" "strings" templ "text/template" @@ -90,8 +91,9 @@ type ServiceProductMap struct { } var ( - imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) - presetPatterns = []map[string]string{ + imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) + imageSpaceParseRegex = regexp.MustCompile(`\/(.*?)\/`) + presetPatterns = []map[string]string{ {setting.PathSearchComponentImage: "image.repository", setting.PathSearchComponentTag: "image.tag"}, {setting.PathSearchComponentImage: "image"}, } @@ -614,6 +616,54 @@ func ExtractImageName(imageURI string) string { return "" } +// ExtractImageRegistry extract registry url from total image uri +func ExtractImageRegistry(imageURI string) (string, error) { + subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + if exNames[i] == "repo" { + u, err := url.Parse(matchedStr) + if err != nil { + return "", err + } + if len(u.Scheme) > 0 { + matchedStr = strings.TrimPrefix(matchedStr, fmt.Sprintf("%s://", u.Scheme)) + } + return matchedStr, nil + } + } + } + return "", fmt.Errorf("failed to extract registry url") +} + +// ExtractImageTag extract image tag from total image uri +func ExtractImageTag(imageURI string) string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageURI) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + if exNames[i] == "tag" { + return matchedStr + } + } + } + return "" +} + +// ExtractRegistryNamespace extract registry namespace form image uri +func ExtractRegistryNamespace(imageURI string) string { + imageURI = strings.TrimPrefix(imageURI, "http://") + imageURI = strings.TrimPrefix(imageURI, "https://") + + firstIndex := strings.Index(imageURI, "/") + lastIndex := strings.LastIndex(imageURI, "/") + if lastIndex == firstIndex { + return "" + } + return strings.TrimPrefix(imageURI[firstIndex:lastIndex], "/") +} + func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 889b7e7a..7b58bcad 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -45,6 +45,14 @@ func (*Router) Inject(router *gin.RouterGroup) { deliveryRelease.GET("/:id", GetDeliveryVersion) deliveryRelease.GET("", ListDeliveryVersion) deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) + + deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) + deliveryRelease.POST("/helm/global-variables", ApplyDeliveryGlobalVariables) + deliveryRelease.GET("/helm/charts", DownloadDeliveryChart) + deliveryRelease.GET("/helm/charts/version", GetChartVersionFromRepo) + deliveryRelease.GET("/helm/charts/preview", PreviewGetDeliveryChart) + deliveryRelease.GET("/helm/charts/filePath", GetDeliveryChartFilePath) + deliveryRelease.GET("/helm/charts/fileContent", GetDeliveryChartFileContent) } deliveryPackage := router.Group("packages") diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 44ea5e99..39a4e551 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -17,19 +17,15 @@ limitations under the License. package handler import ( - "encoding/json" "fmt" - "strconv" + "net/http" "strings" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" deliveryservice "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" - workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -65,189 +61,31 @@ func GetDeliveryVersion(c *gin.Context) { } version := new(commonrepo.DeliveryVersionArgs) version.ID = ID - ctx.Resp, ctx.Err = deliveryservice.GetDeliveryVersion(version, ctx.Logger) -} - -type ReleaseInfo struct { - VersionInfo *commonmodels.DeliveryVersion `json:"versionInfo"` - BuildInfo []*commonmodels.DeliveryBuild `json:"buildInfo"` - DeployInfo []*commonmodels.DeliveryDeploy `json:"deployInfo"` - TestInfo []*commonmodels.DeliveryTest `json:"testInfo"` - DistributeInfo []*commonmodels.DeliveryDistribute `json:"distributeInfo"` - SecurityInfo []*DeliverySecurityStats `json:"securityStatsInfo"` -} - -type DeliverySecurityStats struct { - ImageName string `json:"imageName"` - ImageID string `json:"imageId"` - DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` -} - -type DeliverySecurityStatsInfo struct { - Total int `json:"total"` - Unknown int `json:"unkown"` - Negligible int `json:"negligible"` - Low int `json:"low"` - Medium int `json:"medium"` - High int `json:"high"` - Critical int `json:"critical"` + ctx.Resp, ctx.Err = deliveryservice.GetDetailReleaseData(version, ctx.Logger) } func ListDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - taskIDStr := c.Query("taskId") - var taskID = 0 - var err error - if taskIDStr != "" { - taskID, err = strconv.Atoi(taskIDStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } + args := new(deliveryservice.ListDeliveryVersionArgs) + err := c.BindQuery(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } - perPageStr := c.Query("per_page") - pageStr := c.Query("page") - var ( - perPage int - page int - ) - if perPageStr == "" { - perPage = 20 - } else { - perPage, err = strconv.Atoi(perPageStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("perPage args err :%s", err)) - return - } + if args.Page <= 0 { + args.Page = 1 } - - if pageStr == "" { - page = 1 - } else { - page, err = strconv.Atoi(pageStr) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("page args err :%s", err)) - return - } + if args.PerPage <= 0 { + args.PerPage = 20 } - - serviceName := c.Query("serviceName") - - version := new(commonrepo.DeliveryVersionArgs) - version.ProductName = c.Query("projectName") - version.WorkflowName = c.Query("workflowName") - version.TaskID = taskID - version.PerPage = perPage - version.Page = page - deliveryVersions, err := deliveryservice.FindDeliveryVersion(version, ctx.Logger) - - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return + if len(args.Verbosity) == 0 { + args.Verbosity = deliveryservice.VerbosityDetailed } - releaseInfos := make([]*ReleaseInfo, 0) - for _, deliveryVersion := range deliveryVersions { - releaseInfo := new(ReleaseInfo) - //versionInfo - releaseInfo.VersionInfo = deliveryVersion - - //deployInfo - deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) - deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDeploys, err := deliveryservice.FindDeliveryDeploy(deliveryDeployArgs, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - // 查询条件serviceName - if serviceName != "" { - match := false - for _, deliveryDeploy := range deliveryDeploys { - if deliveryDeploy.ServiceName == serviceName { - match = true - break - } - } - if !match { - continue - } - } - // 将serviceName替换为服务名/服务组件的形式,用于前端展示 - for _, deliveryDeploy := range deliveryDeploys { - if deliveryDeploy.ContainerName != "" { - deliveryDeploy.ServiceName = deliveryDeploy.ServiceName + "/" + deliveryDeploy.ContainerName - } - } - releaseInfo.DeployInfo = deliveryDeploys - - //buildInfo - deliveryBuildArgs := new(commonrepo.DeliveryBuildArgs) - deliveryBuildArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryBuilds, err := deliveryservice.FindDeliveryBuild(deliveryBuildArgs, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - releaseInfo.BuildInfo = deliveryBuilds - - //testInfo - deliveryTestArgs := new(commonrepo.DeliveryTestArgs) - deliveryTestArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryTests, err := deliveryservice.FindDeliveryTest(deliveryTestArgs, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - releaseInfo.TestInfo = deliveryTests - - //securityStatsInfo - deliverySecurityStatss := make([]*DeliverySecurityStats, 0) - if pipelineTask, err := workflowservice.GetPipelineTaskV2(int64(deliveryVersion.TaskID), deliveryVersion.WorkflowName, config.WorkflowType, ctx.Logger); err == nil { - for _, subStage := range pipelineTask.Stages { - if subStage.TaskType == config.TaskSecurity { - subSecurityTaskMap := subStage.SubTasks - for _, subTask := range subSecurityTaskMap { - securityInfo, _ := base.ToSecurityTask(subTask) - - deliverySecurityStats := new(DeliverySecurityStats) - deliverySecurityStats.ImageName = securityInfo.ImageName - deliverySecurityStats.ImageID = securityInfo.ImageID - deliverySecurityStatsMap, err := deliveryservice.FindDeliverySecurityStatistics(securityInfo.ImageID, ctx.Logger) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - var transErr error - b, err := json.Marshal(deliverySecurityStatsMap) - if err != nil { - transErr = fmt.Errorf("marshal task error: %v", err) - } - - if err := json.Unmarshal(b, &deliverySecurityStats.DeliverySecurityStatsInfo); err != nil { - transErr = fmt.Errorf("unmarshal task error: %v", err) - } - if transErr != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - } - - deliverySecurityStatss = append(deliverySecurityStatss, deliverySecurityStats) - } - break - } - } - releaseInfo.SecurityInfo = deliverySecurityStatss - } - - //distributeInfo - deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) - deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDistributes, _ := deliveryservice.FindDeliveryDistribute(deliveryDistributeArgs, ctx.Logger) - - releaseInfo.DistributeInfo = deliveryDistributes - releaseInfos = append(releaseInfos, releaseInfo) - } - ctx.Err = err - ctx.Resp = releaseInfos + ctx.Resp, ctx.Err = deliveryservice.ListDeliveryVersion(args, ctx.Logger) } func getFileName(fileName string) string { @@ -326,6 +164,20 @@ func ListPackagesVersion(c *gin.Context) { ctx.Resp = fileInfoList } +func CreateHelmDeliveryVersion(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.CreateHelmDeliveryVersionArgs) + err := c.ShouldBindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + args.CreateBy = ctx.UserName + ctx.Err = deliveryservice.CreateHelmDeliveryVersion(args, ctx.Logger) +} + func DeleteDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -371,3 +223,82 @@ func ListDeliveryServiceNames(c *gin.Context) { productName := c.Query("projectName") ctx.Resp, ctx.Err = deliveryservice.ListDeliveryServiceNames(productName, ctx.Logger) } + +func DownloadDeliveryChart(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + versionName := c.Query("version") + chartName := c.Query("chartName") + projectName := c.Query("projectName") + + fileBytes, fileName, err := deliveryservice.DownloadDeliveryChart(projectName, versionName, chartName, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + c.Writer.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName)) + c.Data(http.StatusOK, "application/octet-stream", fileBytes) +} + +func GetChartVersionFromRepo(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + chartName := c.Query("chartName") + chartRepoName := c.Query("chartRepoName") + + ctx.Resp, ctx.Err = deliveryservice.GetChartVersions(chartName, chartRepoName) +} + +func PreviewGetDeliveryChart(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + versionName := c.Query("version") + chartName := c.Query("chartName") + projectName := c.Query("projectName") + + ctx.Resp, ctx.Err = deliveryservice.PreviewDeliveryChart(projectName, versionName, chartName, ctx.Logger) +} + +func GetDeliveryChartFilePath(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.DeliveryChartFilePathArgs) + err := c.BindQuery(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFilePath(args, ctx.Logger) +} + +func GetDeliveryChartFileContent(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.DeliveryChartFileContentArgs) + err := c.BindQuery(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = deliveryservice.GetDeliveryChartFileContent(args, ctx.Logger) +} + +func ApplyDeliveryGlobalVariables(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(deliveryservice.DeliveryVariablesApplyArgs) + err := c.BindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = deliveryservice.ApplyDeliveryGlobalVariables(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/delivery/service/docker.go b/pkg/microservice/aslan/core/delivery/service/docker.go new file mode 100644 index 00000000..65a35941 --- /dev/null +++ b/pkg/microservice/aslan/core/delivery/service/docker.go @@ -0,0 +1,57 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import "os/exec" + +const dockerExe = "/usr/local/bin/docker" + +func dockerLogin(user, password, registry string) *exec.Cmd { + return exec.Command( + dockerExe, + "login", + "-u", user, + "-p", password, + registry, + ) +} + +func dockerInfo() *exec.Cmd { + return exec.Command(dockerExe, "info") +} + +func dockerPush(fullImage string) *exec.Cmd { + args := []string{ + "push", + fullImage, + } + return exec.Command(dockerExe, args...) +} + +func dockerPull(image string) *exec.Cmd { + args := []string{"pull", image} + return exec.Command(dockerExe, args...) +} + +func dockerTag(sourceFullImage, targetFullImage string) *exec.Cmd { + args := []string{ + "tag", + sourceFullImage, + targetFullImage, + } + return exec.Command(dockerExe, args...) +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 1c013f68..7bba266c 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -17,21 +17,189 @@ limitations under the License. package service import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + "time" + + cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" + "github.com/chartmuseum/helm-push/pkg/helm" + "github.com/hashicorp/go-multierror" + "github.com/otiai10/copy" + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" + chartloader "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + fsutil "github.com/koderover/zadig/pkg/util/fs" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" +) + +const ( + VerbosityBrief string = "brief" // brief delivery data + VerbosityDetailed string = "detailed" // detailed delivery version with total data ) +type DeliveryVersionFilter struct { + ServiceName string +} + +type CreateHelmDeliveryVersionOption struct { + EnableOfflineDist bool `json:"enableOfflineDist"` + S3StorageID string `json:"s3StorageID"` +} + +type CreateHelmDeliveryVersionChartData struct { + ServiceName string `json:"serviceName"` + Version string `json:"version,omitempty"` + ValuesYamlContent string `json:"valuesYamlContent"` +} + +type CreateHelmDeliveryVersionArgs struct { + CreateBy string `json:"-"` + ProductName string `json:"productName"` + Retry bool `json:"retry"` + Version string `json:"version"` + Desc string `json:"desc"` + EnvName string `json:"envName"` + Labels []string `json:"labels"` + ImageRepoName string `json:"imageRepoName"` + *DeliveryVersionChartData +} + +type DeliveryVersionChartData struct { + GlobalVariables string `json:"globalVariables"` + ChartRepoName string `json:"chartRepoName"` + ImageRegistryID string `json:"imageRegistryID"` + ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` + Options *CreateHelmDeliveryVersionOption `json:"options"` +} + +type DeliveryChartData struct { + ChartData *CreateHelmDeliveryVersionChartData + ServiceObj *commonmodels.Service + ProductService *commonmodels.ProductService + RenderChart *template.RenderChart + RenderSet *commonmodels.RenderSet +} + +type DeliveryChartResp struct { + FileInfos []*types.FileInfo `json:"fileInfos"` +} + +type DeliveryChartFilePathArgs struct { + Dir string `json:"dir"` + ProjectName string `json:"projectName"` + ChartName string `json:"chartName"` + Version string `json:"version"` +} + +type DeliveryChartFileContentArgs struct { + FilePath string `json:"filePath"` + FileName string `json:"fileName"` + ProjectName string `json:"projectName"` + ChartName string `json:"chartName"` + Version string `json:"version"` +} + +type DeliveryVariablesApplyArgs struct { + GlobalVariables string `json:"globalVariables,omitempty"` + ChartDatas []*CreateHelmDeliveryVersionChartData `json:"chartDatas"` +} + +type ListDeliveryVersionArgs struct { + Page int `form:"page"` + PerPage int `form:"per_page"` + TaskId int `form:"taskId"` + ServiceName string `form:"serviceName"` + Verbosity string `form:"verbosity"` + ProjectName string `form:"projectName"` + WorkflowName string `form:"workflowName"` +} + +type ReleaseInfo struct { + VersionInfo *commonmodels.DeliveryVersion `json:"versionInfo"` + BuildInfo []*commonmodels.DeliveryBuild `json:"buildInfo,omitempty"` + DeployInfo []*commonmodels.DeliveryDeploy `json:"deployInfo,omitempty"` + TestInfo []*commonmodels.DeliveryTest `json:"testInfo,omitempty"` + DistributeInfo []*commonmodels.DeliveryDistribute `json:"distributeInfo,omitempty"` + SecurityInfo []*DeliverySecurityStats `json:"securityStatsInfo,omitempty"` +} + +type DeliverySecurityStatsInfo struct { + Total int `json:"total"` + Unknown int `json:"unkown"` + Negligible int `json:"negligible"` + Low int `json:"low"` + Medium int `json:"medium"` + High int `json:"high"` + Critical int `json:"critical"` +} + +type DeliverySecurityStats struct { + ImageName string `json:"imageName"` + ImageID string `json:"imageId"` + DeliverySecurityStatsInfo DeliverySecurityStatsInfo `json:"deliverySecurityStatsInfo"` +} + +type ImageUrlDetail struct { + ImageUrl string + Name string + Registry string + Tag string +} + +type ServiceImageDetails struct { + ServiceName string + Images []*ImageUrlDetail + Registries []string +} + +type ChartVersionResp struct { + ChartName string `json:"chartName"` + ChartVersion string `json:"chartVersion"` +} + func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { - resp, err := commonrepo.NewDeliveryVersionColl().Get(args) + versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { log.Errorf("get deliveryVersion error: %v", err) return nil, e.ErrGetDeliveryVersion } - return resp, err + return versionData, err +} + +func GetDetailReleaseData(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*ReleaseInfo, error) { + versionData, err := commonrepo.NewDeliveryVersionColl().Get(args) + if err != nil { + log.Errorf("get deliveryVersion error: %v", err) + return nil, e.ErrGetDeliveryVersion + } + return buildListReleaseResp(VerbosityDetailed, versionData, nil, log) } func FindDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) ([]*commonmodels.DeliveryVersion, error) { @@ -52,29 +220,1303 @@ func DeleteDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.Sugare return nil } -func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]string, error) { - serviceNames := sets.String{} +func filterReleases(filter *DeliveryVersionFilter, deliveryVersion *commonmodels.DeliveryVersion, logger *zap.SugaredLogger) bool { + if filter == nil { + return true + } + if filter.ServiceName != "" { + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, logger) + if err != nil { + return true + } + match := false + for _, deliveryDeploy := range deliveryDeploys { + if deliveryDeploy.ServiceName == filter.ServiceName { + match = true + break + } + } + if !match { + return false + } + } + return true +} - version := new(commonrepo.DeliveryVersionArgs) - version.ProductName = productName - deliveryVersions, err := FindDeliveryVersion(version, log) +func buildBriefRelease(deliveryVersion *commonmodels.DeliveryVersion, _ *zap.SugaredLogger) (*ReleaseInfo, error) { + return &ReleaseInfo{ + VersionInfo: deliveryVersion, + }, nil +} + +func buildDetailedRelease(deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { + releaseInfo := new(ReleaseInfo) + //versionInfo + releaseInfo.VersionInfo = deliveryVersion + + //deployInfo + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, logger) if err != nil { - log.Errorf("FindDeliveryVersion failed, err:%v", err) - return serviceNames.List(), err + return nil, err } + if filterOpt != nil { + if !filterReleases(filterOpt, deliveryVersion, logger) { + return nil, nil + } + } + // 将serviceName替换为服务名/服务组件的形式,用于前端展示 + for _, deliveryDeploy := range deliveryDeploys { + if deliveryDeploy.ContainerName != "" { + deliveryDeploy.ServiceName = deliveryDeploy.ServiceName + "/" + deliveryDeploy.ContainerName + } + } + releaseInfo.DeployInfo = deliveryDeploys + + //buildInfo + deliveryBuildArgs := new(commonrepo.DeliveryBuildArgs) + deliveryBuildArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryBuilds, err := FindDeliveryBuild(deliveryBuildArgs, logger) + if err != nil { + return nil, err + } + releaseInfo.BuildInfo = deliveryBuilds + + //testInfo + deliveryTestArgs := new(commonrepo.DeliveryTestArgs) + deliveryTestArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryTests, err := FindDeliveryTest(deliveryTestArgs, logger) + if err != nil { + return nil, err + } + releaseInfo.TestInfo = deliveryTests + + //securityStatsInfo + deliverySecurityStatss := make([]*DeliverySecurityStats, 0) + if pipelineTask, err := workflowservice.GetPipelineTaskV2(int64(deliveryVersion.TaskID), deliveryVersion.WorkflowName, config.WorkflowType, logger); err == nil { + for _, subStage := range pipelineTask.Stages { + if subStage.TaskType == config.TaskSecurity { + subSecurityTaskMap := subStage.SubTasks + for _, subTask := range subSecurityTaskMap { + securityInfo, _ := base.ToSecurityTask(subTask) + + deliverySecurityStats := new(DeliverySecurityStats) + deliverySecurityStats.ImageName = securityInfo.ImageName + deliverySecurityStats.ImageID = securityInfo.ImageID + deliverySecurityStatsMap, err := FindDeliverySecurityStatistics(securityInfo.ImageID, logger) + if err != nil { + return nil, err + } + var transErr error + b, err := json.Marshal(deliverySecurityStatsMap) + if err != nil { + transErr = fmt.Errorf("marshal task error: %v", err) + } + if err := json.Unmarshal(b, &deliverySecurityStats.DeliverySecurityStatsInfo); err != nil { + transErr = fmt.Errorf("unmarshal task error: %v", err) + } + if transErr != nil { + return nil, transErr + } + + deliverySecurityStatss = append(deliverySecurityStatss, deliverySecurityStats) + } + break + } + } + releaseInfo.SecurityInfo = deliverySecurityStatss + } + + //distributeInfo + deliveryDistributeArgs := new(commonrepo.DeliveryDistributeArgs) + deliveryDistributeArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDistributes, _ := FindDeliveryDistribute(deliveryDistributeArgs, logger) + releaseInfo.DistributeInfo = deliveryDistributes + // fill some data for helm delivery releases + processReleaseRespData(releaseInfo) + + return releaseInfo, nil +} + +func buildListReleaseResp(verbosity string, deliveryVersion *commonmodels.DeliveryVersion, filterOpt *DeliveryVersionFilter, logger *zap.SugaredLogger) (*ReleaseInfo, error) { + switch verbosity { + case VerbosityBrief: + return buildBriefRelease(deliveryVersion, logger) + case VerbosityDetailed: + return buildDetailedRelease(deliveryVersion, filterOpt, logger) + default: + return buildDetailedRelease(deliveryVersion, filterOpt, logger) + } +} + +func ListDeliveryVersion(args *ListDeliveryVersionArgs, logger *zap.SugaredLogger) ([]*ReleaseInfo, error) { + versionListArgs := new(commonrepo.DeliveryVersionArgs) + versionListArgs.ProductName = args.ProjectName + versionListArgs.WorkflowName = args.WorkflowName + versionListArgs.TaskID = args.TaskId + versionListArgs.PerPage = args.PerPage + versionListArgs.Page = args.Page + deliveryVersions, err := FindDeliveryVersion(versionListArgs, logger) + if err != nil { + return nil, err + } + + releaseInfos := make([]*ReleaseInfo, 0) for _, deliveryVersion := range deliveryVersions { - deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) - deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() - deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, log) + releaseInfo, err := buildListReleaseResp(args.Verbosity, deliveryVersion, &DeliveryVersionFilter{ServiceName: args.ServiceName}, logger) if err != nil { - log.Errorf("FindDeliveryDeploy failed, ReleaseID:%s, err:%v", deliveryVersion.ID, err) + return nil, err + } + if releaseInfo == nil { continue } - for _, deliveryDeploy := range deliveryDeploys { - serviceNames.Insert(deliveryDeploy.ServiceName) + releaseInfos = append(releaseInfos, releaseInfo) + } + return releaseInfos, nil +} + +// fill release +func processReleaseRespData(release *ReleaseInfo) { + if release.VersionInfo.Type != setting.DeliveryVersionTypeChart { + return + } + + distributeMap := make(map[string][]*commonmodels.DeliveryDistribute) + for _, distributeImage := range release.DistributeInfo { + if distributeImage.DistributeType != config.Image { + continue } + distributeMap[distributeImage.ChartName] = append(distributeMap[distributeImage.ChartName], distributeImage) } - return serviceNames.UnsortedList(), nil + chartDistributeCount := 0 + distributes := make([]*commonmodels.DeliveryDistribute, 0) + for _, distribute := range release.DistributeInfo { + if distribute.DistributeType == config.Image { + continue + } + switch distribute.DistributeType { + case config.Chart: + chartDistributeCount++ + distribute.SubDistributes = distributeMap[distribute.ChartName] + case config.File: + s3Storage, err := commonrepo.NewS3StorageColl().Find(distribute.S3StorageID) + if err != nil { + log.Errorf("failed to query s3 storageID: %s, err: %s", distribute.S3StorageID, err) + } else { + distribute.StorageURL = s3Storage.Endpoint + distribute.StorageBucket = s3Storage.Bucket + } + } + distributes = append(distributes, distribute) + } + release.DistributeInfo = distributes + + release.VersionInfo.Progress = buildDeliveryProgressInfo(release.VersionInfo, chartDistributeCount) +} + +func buildDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion, successfulChartCount int) *commonmodels.DeliveryVersionProgress { + if deliveryVersion.Type != setting.DeliveryVersionTypeChart { + return nil + } + + progress := &commonmodels.DeliveryVersionProgress{ + SuccessChartCount: successfulChartCount, + TotalChartCount: 0, + PackageUploadStatus: "", + Error: "", + } + if deliveryVersion.Status == setting.DeliveryVersionStatusSuccess { + progress.TotalChartCount = successfulChartCount + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusSuccess + return progress + } + + argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) + if err != nil { + log.Errorf("failed to marshal arguments, versionName: %s err %s", deliveryVersion.Version, err) + return progress + } + createArgs := new(DeliveryVersionChartData) + err = json.Unmarshal(argsBytes, createArgs) + if err != nil { + log.Errorf("failed to unMarshal arguments, versionName: %s err %s", deliveryVersion.Version, err) + return progress + } + + progress.TotalChartCount = len(createArgs.ChartDatas) + + if deliveryVersion.Status == setting.DeliveryVersionStatusFailed { + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusFailed + progress.Error = deliveryVersion.Error + return progress + } + + if len(createArgs.ChartDatas) > successfulChartCount { + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusWaiting + return progress + } + + progress.PackageUploadStatus = setting.DeliveryVersionPackageStatusUploading + return progress + +} + +func getChartTGZDir(productName, versionName string) string { + tmpDir := os.TempDir() + return filepath.Join(tmpDir, "chart-tgz", productName, versionName) +} + +func getChartExpandDir(productName, versionName string) string { + tmpDir := os.TempDir() + return filepath.Join(tmpDir, "chart", productName, versionName) +} + +func getProductEnvInfo(productName, envName string) (*commonmodels.Product, error) { + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + log.Errorf("failed to query product info, productName: %s envName: %s err: %s", productName, envName, err) + return nil, fmt.Errorf("failed to query product info, productName: %s envName: %s", productName, envName) + } + return productInfo, nil +} + +func getChartRepoData(repoName string) (*commonmodels.HelmRepo, error) { + return commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: repoName}) +} + +func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { + client, err := cm.NewClient( + cm.URL(repo.URL), + cm.Username(repo.Username), + cm.Password(repo.Password), + // need support more auth types + ) + if err != nil { + return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", repo.RepoName) + } + return client, nil +} + +// find all images in one single chart +func extractImages(productService *commonmodels.ProductService, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { + imageUrlsSet := sets.NewString() + for _, container := range productService.Containers { + imageUrlsSet.Insert(container.Image) + } + + ret := &ServiceImageDetails{ + ServiceName: productService.ServiceName, + Images: make([]*ImageUrlDetail, 0), + } + + registrySet := sets.NewString() + + for _, imageUrl := range imageUrlsSet.List() { + + registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) + } + registryUrl = strings.TrimSuffix(registryUrl, "/") + + imageName := commonservice.ExtractImageName(imageUrl) + imageTag := commonservice.ExtractImageTag(imageUrl) + + registryID := "" + // used source registry + if registry, ok := registryMap[registryUrl]; ok { + registryID = registry.ID.Hex() + registrySet.Insert(registryID) + } + + ret.Images = append(ret.Images, &ImageUrlDetail{ + ImageUrl: imageUrl, + Name: imageName, + Tag: imageTag, + Registry: registryID, + }) + } + + ret.Registries = registrySet.List() + return ret, nil +} + +// ensure chart files exist +func ensureChartFiles(chartData *DeliveryChartData) (string, error) { + serviceObj := chartData.ServiceObj + revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) + deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) + if exists, _ := fsutil.DirExists(deliveryChartPath); exists { + return deliveryChartPath, nil + } + + serviceName, revision := serviceObj.ServiceName, serviceObj.Revision + basePath := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceName, revision) + if err := commonservice.PreloadServiceManifestsByRevision(basePath, serviceObj); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) + // use the latest version when it fails to download the specific version + basePath = config.LocalServicePath(serviceObj.ProductName, serviceName) + if err = commonservice.PreLoadServiceManifests(basePath, serviceObj); err != nil { + log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) + return "", err + } + } + + fullPath := filepath.Join(basePath, serviceObj.ServiceName) + err := copy.Copy(fullPath, deliveryChartPath) + if err != nil { + return "", err + } + + mergedValuesYaml, err := helmtool.MergeOverrideValues(chartData.RenderChart.ValuesYaml, chartData.RenderSet.DefaultValues, chartData.RenderChart.GetOverrideYaml(), chartData.RenderChart.OverrideValues) + if err != nil { + return "", err + } + err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644) + if err != nil { + return "", errors.Wrapf(err, "failed to write values.yaml") + } + + return deliveryChartPath, nil +} + +func buildChartPackage(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, registryMap map[string]*commonmodels.RegistryNamespace) error { + serviceObj := chartData.ServiceObj + + deliveryChartPath, err := ensureChartFiles(chartData) + if err != nil { + return err + } + + valuesYamlData := make(map[string]interface{}) + valuesFilePath := filepath.Join(deliveryChartPath, setting.ValuesYaml) + valueYamlContent, err := os.ReadFile(valuesFilePath) + if err != nil { + return errors.Wrapf(err, "failed to read values.yaml for service %s", serviceObj.ServiceName) + } + err = yaml.Unmarshal(valueYamlContent, &valuesYamlData) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) + } + + // write values.yaml file before load + if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly + if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { + log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) + return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) + } + valueYamlContent = []byte(chartData.ChartData.ValuesYamlContent) + } else if len(globalVariables) > 0 { // merge global variables + valueYamlContent, err = yamlutil.Merge([][]byte{valueYamlContent, []byte(globalVariables)}) + if err != nil { + return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) + } + } + err = os.WriteFile(valuesFilePath, valueYamlContent, 0644) + if err != nil { + return errors.Wrapf(err, "failed to write values.yaml file for service %s", serviceObj.ServiceName) + } + + //load chart info from local storage + chartRequested, err := chartloader.Load(deliveryChartPath) + if err != nil { + return errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) + } + + //set metadata + chartRequested.Metadata.Name = chartData.ChartData.ServiceName + chartRequested.Metadata.Version = chartData.ChartData.Version + chartRequested.Metadata.AppVersion = chartData.ChartData.Version + + //create local chart package + chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) + if err != nil { + return err + } + + client, err := createChartRepoClient(chartRepo) + if err != nil { + return errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) + } + + log.Infof("pushing chart %s to %s...", filepath.Base(chartPackagePath), chartRepo.URL) + resp, err := client.UploadChartPackage(chartPackagePath, false) + if err != nil { + return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + } + err = handlePushResponse(resp) + if err != nil { + return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) + } + return nil +} + +func handlePushResponse(resp *http.Response) error { + if resp.StatusCode != 201 && resp.StatusCode != 202 { + b, err := ioutil.ReadAll(resp.Body) + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + if err != nil { + return err + } + return getChartmuseumError(b, resp.StatusCode) + } + log.Infof("push chart to chart repo done") + return nil +} + +func getChartmuseumError(b []byte, code int) error { + var er struct { + Error string `json:"error"` + } + err := json.Unmarshal(b, &er) + if err != nil || er.Error == "" { + return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) + } + return errors.Errorf("%d: %s", code, er.Error) +} + +func makeChartTGZFileDir(productName, versionName string) (string, error) { + path := getChartTGZDir(productName, versionName) + if err := os.RemoveAll(path); err != nil { + if !os.IsExist(err) { + return "", errors.Wrapf(err, "failed to claer dir for chart tgz files") + } + } + err := os.MkdirAll(path, 0777) + if err != nil { + return "", errors.Wrapf(err, "failed to create chart tgz dir for version: %s", versionName) + } + return path, nil +} + +func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { + if args.Retry { + return RetryCreateHelmDeliveryVersion(args.ProductName, args.Version, logger) + } else { + return CreateNewHelmDeliveryVersion(args, logger) + } +} + +// validate chartInfo, make sure service is in environment +// prepare data set for chart delivery +func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productInfo *commonmodels.Product) (map[string]*DeliveryChartData, error) { + + renderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + Revision: productInfo.Render.Revision, + Name: productInfo.Render.Name, + }) + if err != nil { + return nil, fmt.Errorf("failed to find renderSet: %s, revision: %d", productInfo.Render.Name, productInfo.Render.Revision) + } + chartMap := make(map[string]*template.RenderChart) + for _, rChart := range renderSet.ChartInfos { + chartMap[rChart.ServiceName] = rChart + } + + chartDataMap := make(map[string]*DeliveryChartData) + serviceMap := productInfo.GetServiceMap() + for _, chartData := range chartDatas { + if productService, ok := serviceMap[chartData.ServiceName]; ok { + serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: chartData.ServiceName, + Revision: productService.Revision, + Type: setting.HelmDeployType, + ProductName: productInfo.ProductName, + }) + if err != nil { + return nil, fmt.Errorf("failed to query service: %s", chartData.ServiceName) + } + renderChart, ok := chartMap[chartData.ServiceName] + if !ok { + return nil, fmt.Errorf("can't find renderChart for service: %s", chartData.ServiceName) + } + chartDataMap[chartData.ServiceName] = &DeliveryChartData{ + ChartData: chartData, + RenderChart: renderChart, + ServiceObj: serviceObj, + ProductService: productService, + RenderSet: renderSet, + } + } else { + return nil, fmt.Errorf("service %s not found in environment", chartData.ServiceName) + } + } + return chartDataMap, nil +} + +func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { + registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) + if err != nil { + return nil, fmt.Errorf("failed to query registries") + } + ret := make(map[string]*commonmodels.RegistryNamespace) + for _, singleRegistry := range registries { + fullUrl := fmt.Sprintf("%s/%s", singleRegistry.RegAddr, singleRegistry.Namespace) + fullUrl = strings.TrimSuffix(fullUrl, "/") + u, _ := url.Parse(fullUrl) + if len(u.Scheme) > 0 { + fullUrl = strings.TrimPrefix(fullUrl, fmt.Sprintf("%s://", u.Scheme)) + } + ret[fullUrl] = singleRegistry + } + return ret, nil +} + +func buildArtifactTaskArgs(projectName, envName string, imagesMap *sync.Map) *commonmodels.ArtifactPackageTaskArgs { + imageArgs := make([]*commonmodels.ImagesByService, 0) + sourceRegistry := sets.NewString() + imagesMap.Range(func(key, value interface{}) bool { + imageDetail := value.(*ServiceImageDetails) + imagesByService := &commonmodels.ImagesByService{ + ServiceName: imageDetail.ServiceName, + Images: make([]*commonmodels.ImageData, 0), + } + for _, image := range imageDetail.Images { + imagesByService.Images = append(imagesByService.Images, &commonmodels.ImageData{ + ImageUrl: image.ImageUrl, + ImageName: image.Name, + ImageTag: image.Tag, + RegistryID: image.Registry, + }) + } + imageArgs = append(imageArgs, imagesByService) + sourceRegistry.Insert(imageDetail.Registries...) + return true + }) + + ret := &commonmodels.ArtifactPackageTaskArgs{ + ProjectName: projectName, + EnvName: envName, + Images: imageArgs, + SourceRegistries: sourceRegistry.List(), + } + return ret +} + +// insert delivery distribution data for single chart, include image and chart +func insertDeliveryDistributions(result *task.ServicePackageResult, chartVersion string, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData) error { + for _, image := range result.ImageData { + err := commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + ServiceName: image.ImageName, // image name + ChartName: result.ServiceName, + DistributeType: config.Image, + RegistryName: image.ImageUrl, + Namespace: commonservice.ExtractRegistryNamespace(image.ImageUrl), + CreatedAt: time.Now().Unix(), + }) + if err != nil { + log.Errorf("failed to insert image distribute data, chartName: %s, err: %s", result.ServiceName, err) + return fmt.Errorf("failed to insert image distribute data, chartName: %s", result.ServiceName) + } + } + + err := commonrepo.NewDeliveryDistributeColl().Insert(&commonmodels.DeliveryDistribute{ + ReleaseID: deliveryVersion.ID, + DistributeType: config.Chart, + ChartName: result.ServiceName, + ChartVersion: chartVersion, + ChartRepoName: args.ChartRepoName, + SubDistributes: nil, + CreatedAt: time.Now().Unix(), + }) + if err != nil { + log.Errorf("failed to insert chart distribute data, chartName: %s, err: %s", result.ServiceName, err) + return fmt.Errorf("failed to insert chart distribute data, chartName: %s", result.ServiceName) + } + return nil +} + +func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVersion *commonmodels.DeliveryVersion, args *DeliveryVersionChartData, logger *zap.SugaredLogger) (err error) { + defer func() { + if err != nil { + deliveryVersion.Status = setting.DeliveryVersionStatusFailed + deliveryVersion.Error = err.Error() + } + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) + if err != nil { + logger.Errorf("failed to update delivery version data, name: %s error: %s", deliveryVersion.Version, err) + } + }() + + var errLock sync.Mutex + errorList := &multierror.Error{} + + appendError := func(err error) { + errLock.Lock() + defer errLock.Unlock() + errorList = multierror.Append(errorList, err) + } + + dir, err := makeChartTGZFileDir(deliveryVersion.ProductName, deliveryVersion.Version) + if err != nil { + return err + } + repoInfo, err := getChartRepoData(args.ChartRepoName) + if err != nil { + log.Errorf("failed to query chart-repo info, productName: %s, err: %s", deliveryVersion.ProductName, err) + return fmt.Errorf("failed to query chart-repo info, productName: %s, repoName: %s", deliveryVersion.ProductName, args.ChartRepoName) + } + + registryMap, err := buildRegistryMap() + if err != nil { + return fmt.Errorf("failed to build registry map") + } + + imagesDataMap := &sync.Map{} + + // push charts to repo + wg := sync.WaitGroup{} + for _, chartData := range chartDataMap { + wg.Add(1) + go func(cData *DeliveryChartData) { + defer wg.Done() + err := buildChartPackage(cData, repoInfo, dir, args.GlobalVariables, registryMap) + if err != nil { + logger.Errorf("failed to build chart package, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + appendError(err) + return + } + imageData, err := extractImages(cData.ProductService, registryMap) + if err != nil { + logger.Errorf("failed to extract image data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) + appendError(err) + return + } + imagesDataMap.Store(cData.ServiceObj.ServiceName, imageData) + }(chartData) + } + wg.Wait() + + if errorList.ErrorOrNil() != nil { + err = errorList.ErrorOrNil() + return + } + + // create task to deal with images + // offline docker images are not supported + taskArgs := buildArtifactTaskArgs(deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, imagesDataMap) + taskArgs.TargetRegistries = []string{args.ImageRegistryID} + taskID, err := workflowservice.CreateArtifactPackageTask(taskArgs, deliveryVersion.Version, logger) + if err != nil { + return err + } + deliveryVersion.TaskID = int(taskID) + + // start a new routine to check task results + go waitVersionDone(deliveryVersion) + + return +} + +func updateVersionStatus(versionName, status, errStr string) { + err := commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionName, status, errStr) + if err != nil { + log.Errorf("failed to update version status, name: %s, err: %s", versionName, err) + } +} + +func taskFinished(status config.Status) bool { + return status == config.StatusPassed || status == config.StatusFailed || status == config.StatusTimeout || status == config.StatusCancelled +} + +func waitVersionDone(deliveryVersion *commonmodels.DeliveryVersion) { + waitTimeout := time.After(60 * time.Minute * 2) + for { + select { + case <-waitTimeout: + updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, "timeout") + return + default: + done, err := checkVersionStatus(deliveryVersion) + if err != nil { + updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, err.Error()) + return + } + if done { + return + } + } + time.Sleep(time.Second * 5) + } +} + +func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, error) { + if deliveryVersion.Status == setting.DeliveryVersionStatusSuccess || deliveryVersion.Status == setting.DeliveryVersionStatusFailed { + return true, nil + } + pipelineName := fmt.Sprintf("%s-%s-%s", deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, "artifact") + taskData, err := commonrepo.NewTaskColl().Find(int64(deliveryVersion.TaskID), pipelineName, config.ArtifactType) + if err != nil { + return false, fmt.Errorf("failed to query taskData, id: %d, pipelineName: %s", deliveryVersion.TaskID) + } + + if len(taskData.Stages) != 1 { + return false, fmt.Errorf("invalid task data, stage length not leagal") + } + + argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) + if err != nil { + return false, errors.Wrapf(err, "failed to marshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) + } + createArgs := new(DeliveryVersionChartData) + err = json.Unmarshal(argsBytes, createArgs) + if err != nil { + return false, errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) + } + + distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ + DistributeType: config.Chart, + ReleaseID: deliveryVersion.ID.Hex(), + }) + if err != nil { + return false, errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) + } + + // for charts has been successfully handled, download charts directly + successCharts := sets.NewString() + for _, distribute := range distributes { + successCharts.Insert(distribute.ChartName) + } + + stage := taskData.Stages[0] + errorList := &multierror.Error{} + + allTaskDone := true + for _, taskData := range stage.SubTasks { + artifactPackageArgs, err := base.ToArtifactPackageImageTask(taskData) + if err != nil { + return false, errors.Wrapf(err, "failed to generate origin artifact task data") + } + + progressData, err := artifactPackageArgs.GetProgress() + if err != nil { + return false, errors.Wrapf(err, "failed to get progress data from data") + } + progressDataMap := make(map[string]*task.ServicePackageResult) + for _, singleResult := range progressData { + progressDataMap[singleResult.ServiceName] = singleResult + } + + for _, chartData := range createArgs.ChartDatas { + // service artifact has been marked as success + if successCharts.Has(chartData.ServiceName) { + continue + } + if singleResult, ok := progressDataMap[chartData.ServiceName]; ok { + if singleResult.Result != "success" { + errorList = multierror.Append(errorList, fmt.Errorf("failed to build image distribute for service:%s ", singleResult.ServiceName)) + continue + } + err = insertDeliveryDistributions(singleResult, chartData.Version, deliveryVersion, createArgs) + if err != nil { + errorList = multierror.Append(errorList, fmt.Errorf("failed to insert distribute data for service:%s ", singleResult.ServiceName)) + continue + } + successCharts.Insert(chartData.ServiceName) + } + } + + if !taskFinished(artifactPackageArgs.TaskStatus) { + allTaskDone = false + } + if len(artifactPackageArgs.Error) > 0 { + errorList = multierror.Append(errorList, fmt.Errorf(artifactPackageArgs.Error)) + } + } + + if allTaskDone { + if successCharts.Len() == len(createArgs.ChartDatas) { + deliveryVersion.Status = setting.DeliveryVersionStatusSuccess + } else { + deliveryVersion.Status = setting.DeliveryVersionStatusFailed + } + } + if errorList.ErrorOrNil() != nil { + deliveryVersion.Error = errorList.Error() + } + updateVersionStatus(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) + return allTaskDone, nil +} + +func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { + // need appoint chart info + if len(args.ChartDatas) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("no chart info appointed") + } + + // prepare data + productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) + if err != nil { + log.Infof("failed to query product info, productName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) + } + + // validate necessary params + if len(args.ChartRepoName) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("chart repo not appointed") + } + if len(args.ImageRegistryID) == 0 { + return e.ErrCreateDeliveryVersion.AddDesc("image registry not appointed") + } + + chartDataMap, err := prepareChartData(args.ChartDatas, productInfo) + if err != nil { + return e.ErrCreateDeliveryVersion.AddErr(err) + } + + productInfo.ID, _ = primitive.ObjectIDFromHex("") + + versionObj := &commonmodels.DeliveryVersion{ + Version: args.Version, + ProductName: args.ProductName, + Type: setting.DeliveryVersionTypeChart, + Desc: args.Desc, + Labels: args.Labels, + ProductEnvInfo: productInfo, + Status: setting.DeliveryVersionStatusCreating, + CreateArgument: args.DeliveryVersionChartData, + CreatedBy: args.CreateBy, + CreatedAt: time.Now().Unix(), + DeletedAt: 0, + } + + err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) + if err != nil { + return err + } + + err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) + if err != nil { + logger.Errorf("failed to insert version data, err: %s", err) + return e.ErrCreateDeliveryVersion.AddErr(fmt.Errorf("failed to insert delivery version: %s", versionObj.Version)) + } + + return nil +} + +func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap.SugaredLogger) error { + deliveryVersion, err := commonrepo.NewDeliveryVersionColl().Get(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }) + if err != nil { + logger.Errorf("failed to query delivery version data, verisonName: %s, error: %s", versionName, err) + return fmt.Errorf("failed to query delivery version data, verisonName: %s", versionName) + } + + if deliveryVersion.Status != setting.DeliveryVersionStatusFailed { + return fmt.Errorf("can't reCreate version with status:%s", deliveryVersion.Status) + } + + argsBytes, err := json.Marshal(deliveryVersion.CreateArgument) + if err != nil { + return errors.Wrapf(err, "failed to marshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) + } + createArgs := new(DeliveryVersionChartData) + err = json.Unmarshal(argsBytes, createArgs) + if err != nil { + return errors.Wrapf(err, "failed to unMarshal arguments, versionName: %s err: %s", deliveryVersion.Version, err) + } + + productInfoSnap := deliveryVersion.ProductEnvInfo + + distributes, err := commonrepo.NewDeliveryDistributeColl().Find(&commonrepo.DeliveryDistributeArgs{ + DistributeType: config.Chart, + ReleaseID: deliveryVersion.ID.Hex(), + }) + if err != nil { + return errors.Wrapf(err, "failed to query distrubutes, versionName: %s", deliveryVersion.Version) + } + + // for charts has been successfully handled, download charts directly + successCharts := sets.NewString() + for _, distribute := range distributes { + if distribute.DistributeType != config.Chart { + continue + } + _, err := downloadChart(deliveryVersion, distribute) + if err != nil { + log.Errorf("failed to download chart from chart repo, chartName: %s, err: %s", distribute.ChartName, err) + continue + } + successCharts.Insert(distribute.ChartName) + } + + chartsToBeHandled := make([]*CreateHelmDeliveryVersionChartData, 0) + for _, chartConfig := range createArgs.ChartDatas { + if successCharts.Has(chartConfig.ServiceName) { + continue + } + chartsToBeHandled = append(chartsToBeHandled, chartConfig) + } + + chartDataMap, err := prepareChartData(chartsToBeHandled, productInfoSnap) + if err != nil { + return e.ErrCreateDeliveryVersion.AddErr(err) + } + + err = buildDeliveryCharts(chartDataMap, deliveryVersion, createArgs, logger) + if err != nil { + return err + } + + // update status + deliveryVersion.Status = setting.DeliveryVersionStatusRetrying + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, "") + if err != nil { + logger.Errorf("failed to update delivery status, name: %s, err: %s", deliveryVersion.Version, err) + return fmt.Errorf("failed to update delivery status, name: %s", deliveryVersion.Version) + } + + return nil +} + +func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]string, error) { + serviceNames := sets.String{} + + version := new(commonrepo.DeliveryVersionArgs) + version.ProductName = productName + deliveryVersions, err := FindDeliveryVersion(version, log) + if err != nil { + log.Errorf("FindDeliveryVersion failed, err:%v", err) + return serviceNames.List(), err + } + + for _, deliveryVersion := range deliveryVersions { + deliveryDeployArgs := new(commonrepo.DeliveryDeployArgs) + deliveryDeployArgs.ReleaseID = deliveryVersion.ID.Hex() + deliveryDeploys, err := FindDeliveryDeploy(deliveryDeployArgs, log) + if err != nil { + log.Errorf("FindDeliveryDeploy failed, ReleaseID:%s, err:%v", deliveryVersion.ID, err) + continue + } + for _, deliveryDeploy := range deliveryDeploys { + serviceNames.Insert(deliveryDeploy.ServiceName) + } + } + + return serviceNames.UnsortedList(), nil +} + +func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *commonmodels.DeliveryDistribute) (string, error) { + productName, versionName := deliveryVersion.ProductName, deliveryVersion.Version + chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.ChartName, chartInfo.ChartVersion) + chartTGZFileParent := getChartTGZDir(productName, versionName) + chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) + if _, err := os.Stat(chartTGZFilePath); err == nil { + // local cache exists + log.Infof("local cache exists, path %s", chartTGZFilePath) + return chartTGZFilePath, nil + } + + chartRepo, err := getChartRepoData(chartInfo.ChartRepoName) + if err != nil { + return "", fmt.Errorf("failed to query chart-repo info, repoName %s", chartInfo.ChartRepoName) + } + + client, err := createChartRepoClient(chartRepo) + if err != nil { + return "", err + } + + if err = os.MkdirAll(chartTGZFileParent, 0644); err != nil { + return "", errors.Wrapf(err, "failed to craete tgz parent dir") + } + + out, err := os.Create(chartTGZFilePath) + if err != nil { + _ = os.RemoveAll(chartTGZFilePath) + return "", errors.Wrapf(err, "failed to create chart tgz file") + } + + response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) + if err != nil { + return "", errors.Wrapf(err, "failed to download file") + } + + if response.StatusCode != 200 { + return "", errors.Wrapf(err, "download file failed %s", chartTGZName) + } + defer func() { _ = response.Body.Close() }() + + b, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", errors.Wrapf(err, "failed to read response data") + } + + defer func(out *os.File) { + _ = out.Close() + }(out) + + err = ioutil.WriteFile(chartTGZFilePath, b, 0644) + if err != nil { + return "", err + } + return chartTGZFilePath, nil +} + +func getChartDistributeInfo(releaseID, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { + distributes, _ := FindDeliveryDistribute(&commonrepo.DeliveryDistributeArgs{ + ReleaseID: releaseID, + ChartName: chartName, + DistributeType: config.Chart, + }, log) + + if len(distributes) != 1 { + log.Warnf("find chart %s failed, expect count %d, found count %d, release_id: %s", chartName, 1, len(distributes), releaseID) + return nil, fmt.Errorf("can't find target charts") + } + + chartInfo := distributes[0] + return chartInfo, nil +} + +func DownloadDeliveryChart(projectName, version string, chartName string, log *zap.SugaredLogger) ([]byte, string, error) { + + filePath, err := preDownloadChart(projectName, version, chartName, log) + if err != nil { + return nil, "", err + } + + fileBytes, err := os.ReadFile(filePath) + if err != nil { + return nil, "", err + } + + return fileBytes, filepath.Base(filePath), err +} + +func preDownloadChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { + deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }, log) + if err != nil { + return "", fmt.Errorf("failed to query delivery info") + } + + chartInfo, err := getChartDistributeInfo(deliveryInfo.ID.Hex(), chartName, log) + if err != nil { + return "", err + } + // prepare chart data + filePath, err := downloadChart(deliveryInfo, chartInfo) + if err != nil { + return "", err + } + return filePath, err +} + +func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, error) { + + chartRepo, err := getChartRepoData(chartRepoName) + if err != nil { + return nil, fmt.Errorf("failed to query chart-repo info, repoName %s", chartRepoName) + } + + client, err := createChartRepoClient(chartRepo) + if err != nil { + return nil, errors.Wrapf(err, "failed to create chart repo client") + } + + resp, err := client.DownloadFile("index.yaml") + if err != nil { + return nil, errors.Wrapf(err, "failed to download index.yaml") + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") + } + + index, err := helm.LoadIndex(b) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse index.yaml") + } + + chartNameList := strings.Split(chartName, ",") + chartNameSet := sets.NewString(chartNameList...) + + ret := make([]*ChartVersionResp, 0) + + for name, entry := range index.Entries { + if !chartNameSet.Has(name) { + continue + } + if len(entry) == 0 { + continue + } + latestEntry := entry[0] + ret = append(ret, &ChartVersionResp{ + ChartName: name, + ChartVersion: latestEntry.Version, + }) + } + + return ret, nil +} + +func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { + + deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }, log) + if err != nil { + return "", fmt.Errorf("failed to query delivery info") + } + + chartDistribute, err := getChartDistributeInfo(deliveryInfo.ID.Hex(), chartName, log) + if err != nil { + return "", err + } + dstDir := getChartExpandDir(projectName, versionName) + dstDir = filepath.Join(dstDir, fmt.Sprintf("%s-%s", chartDistribute.ChartName, chartDistribute.ChartVersion)) + + filePath, err := preDownloadChart(projectName, versionName, chartName, log) + if err != nil { + return "", err + } + + file, err := os.Open(filePath) + if err != nil { + return "", errors.Wrap(err, "unable to open tarball") + } + defer func() { _ = file.Close() }() + + err = chartutil.Expand(dstDir, file) + if err != nil { + log.Errorf("failed to uncompress file: %s", filePath) + return "", errors.Wrapf(err, "failed to uncompress file") + } + return dstDir, nil +} + +func PreviewDeliveryChart(projectName, version, chartName string, log *zap.SugaredLogger) (*DeliveryChartResp, error) { + + dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) + if err != nil { + return nil, err + } + + ret := &DeliveryChartResp{ + FileInfos: make([]*types.FileInfo, 0), + } + + var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(dstDir, chartName)) + if err != nil { + return nil, err + } + + for _, file := range files { + info, _ := file.Info() + if info == nil { + continue + } + fi := &types.FileInfo{ + Parent: "", + Name: file.Name(), + Size: info.Size(), + Mode: file.Type(), + ModTime: info.ModTime().Unix(), + IsDir: file.IsDir(), + } + + fis = append(fis, fi) + } + ret.FileInfos = fis + return ret, nil +} + +// load chart file infos +func loadChartFileInfos(fileDir, chartName string, dir string) ([]*types.FileInfo, error) { + var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(fileDir, chartName, dir)) + if err != nil { + return nil, e.ErrFilePath.AddDesc(err.Error()) + } + + for _, file := range files { + info, _ := file.Info() + if info == nil { + continue + } + fi := &types.FileInfo{ + Parent: dir, + Name: file.Name(), + Size: info.Size(), + Mode: file.Type(), + ModTime: info.ModTime().Unix(), + IsDir: file.IsDir(), + } + fis = append(fis, fi) + } + return fis, nil +} + +func GetDeliveryChartFilePath(args *DeliveryChartFilePathArgs, log *zap.SugaredLogger) ([]*types.FileInfo, error) { + projectName, version, chartName := args.ProjectName, args.Version, args.ChartName + dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) + if err != nil { + return nil, nil + } + + fileInfos, err := loadChartFileInfos(dstDir, chartName, args.Dir) + if err != nil { + return nil, err + } + return fileInfos, nil +} + +func GetDeliveryChartFileContent(args *DeliveryChartFileContentArgs, log *zap.SugaredLogger) (string, error) { + projectName, version, chartName := args.ProjectName, args.Version, args.ChartName + dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) + if err != nil { + return "", nil + } + + file := filepath.Join(dstDir, chartName, args.FilePath, args.FileName) + fileContent, err := os.ReadFile(file) + if err != nil { + log.Errorf("Failed to read file %s, err: %s", file, err) + return "", e.ErrFileContent.AddDesc(err.Error()) + } + + return string(fileContent), nil +} + +func ApplyDeliveryGlobalVariables(args *DeliveryVariablesApplyArgs, logger *zap.SugaredLogger) (interface{}, error) { + ret := new(DeliveryVariablesApplyArgs) + for _, chartData := range args.ChartDatas { + mergedYaml, err := yamlutil.Merge([][]byte{[]byte(chartData.ValuesYamlContent), []byte(args.GlobalVariables)}) + if err != nil { + logger.Errorf("failed to merge gobal variables for service: %s", chartData.ServiceName) + return nil, errors.Wrapf(err, "failed to merge global variables for service: %s", chartData.ServiceName) + } + ret.ChartDatas = append(ret.ChartDatas, &CreateHelmDeliveryVersionChartData{ + ServiceName: chartData.ServiceName, + ValuesYamlContent: string(mergedYaml), + }) + } + return ret, nil } diff --git a/pkg/microservice/aslan/core/environment/handler/helm.go b/pkg/microservice/aslan/core/environment/handler/helm.go new file mode 100644 index 00000000..9e097cd9 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/helm.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListReleases(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Param("name") + projectName := c.Query("projectName") + ctx.Resp, ctx.Err = service.ListReleases(projectName, envName, ctx.Logger) +} + +func GetChartInfos(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Param("name") + servicesName := c.Query("serviceName") + projectName := c.Query("projectName") + ctx.Resp, ctx.Err = service.GetChartInfos(projectName, envName, servicesName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 18901f28..1d598a14 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -109,6 +109,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/groups", ListGroups) environments.GET("/:name/workloads", ListWorkloadsInEnv) + environments.GET("/:name/helm/releases", ListReleases) + environments.GET("/:name/helm/charts", GetChartInfos) + environments.GET("/:name/services/:serviceName", GetService) environments.PUT("/:name/services/:serviceName", gin2.UpdateOperationLogStatus, UpdateService) environments.POST("/:name/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go new file mode 100644 index 00000000..255a2ba7 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -0,0 +1,259 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/hashicorp/go-multierror" + "github.com/otiai10/copy" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" +) + +type HelmReleaseResp struct { + ReleaseName string `json:"releaseName"` + ServiceName string `json:"serviceName"` + Revision int `json:"revision"` + Chart string `json:"chart"` + AppVersion string `json:"appVersion"` +} + +type ChartInfo struct { + ServiceName string `json:"serviceName"` + Revision int64 `json:"revision"` +} + +type HelmChartsResp struct { + ChartInfos []*ChartInfo `json:"chartInfos"` + FileInfos []*types.FileInfo `json:"fileInfos"` +} + +func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmReleaseResp, error) { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + } + + restConfig, err := kube.GetRESTConfig(prod.ClusterID) + if err != nil { + log.Errorf("GetRESTConfig error: %v", err) + return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + } + helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + if err != nil { + log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) + return nil, e.ErrCreateDeliveryVersion.AddErr(err) + } + + releases, err := helmClient.ListDeployedReleases() + if err != nil { + return nil, e.ErrCreateDeliveryVersion.AddErr(err) + } + + ret := make([]*HelmReleaseResp, 0, len(releases)) + for _, release := range releases { + ret = append(ret, &HelmReleaseResp{ + ReleaseName: release.Name, + ServiceName: util.ExtraServiceName(release.Name, prod.Namespace), + Revision: release.Version, + Chart: release.Chart.Name(), + AppVersion: release.Chart.AppVersion(), + }) + } + return ret, nil +} + +func loadChartFilesInfo(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { + base := config.LocalServicePathWithRevision(productName, serviceName, revision) + + var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) + if err != nil { + log.Warnf("failed to read chart info for service %s with revision %d", serviceName, revision) + base = config.LocalServicePath(productName, serviceName) + files, err = os.ReadDir(filepath.Join(base, serviceName, dir)) + if err != nil { + return nil, err + } + } + + for _, file := range files { + info, _ := file.Info() + if info == nil { + continue + } + fi := &types.FileInfo{ + Parent: dir, + Name: file.Name(), + Size: info.Size(), + Mode: file.Type(), + ModTime: info.ModTime().Unix(), + IsDir: file.IsDir(), + } + + fis = append(fis, fi) + } + return fis, nil +} + +//prepare chart version data +func prepareChartVersionData(productName string, serviceObj *models.Service, renderChart *template.RenderChart, renderset *models.RenderSet) error { + serviceName, revision := serviceObj.ServiceName, serviceObj.Revision + base := config.LocalServicePathWithRevision(productName, serviceName, revision) + if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", revision, serviceName) + // use the latest version when it fails to download the specific version + base = config.LocalServicePath(productName, serviceName) + if err = commonservice.PreLoadServiceManifests(base, serviceObj); err != nil { + log.Errorf("failed to load chart info for service %v", serviceObj.ServiceName) + return err + } + } + + fullPath := filepath.Join(base, serviceObj.ServiceName) + deliveryChartPath := filepath.Join(config.LocalDeliveryChartPathWithRevision(productName, serviceObj.ServiceName, serviceObj.Revision), serviceObj.ServiceName) + err := copy.Copy(fullPath, deliveryChartPath) + if err != nil { + return err + } + + mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderset.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + if err != nil { + return err + } + + // write values.yaml + if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644); err != nil { + return err + } + + return nil +} + +func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLogger) (*HelmChartsResp, error) { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, e.ErrGetHelmCharts.AddErr(err) + } + renderSet, err := FindHelmRenderSet(productName, prod.Render.Name, log) + if err != nil { + log.Errorf("[%s][P:%s] find product renderset error: %v", envName, productName, err) + return nil, e.ErrGetHelmCharts.AddErr(err) + } + + chartMap := make(map[string]*template.RenderChart) + for _, chart := range renderSet.ChartInfos { + chartMap[chart.ServiceName] = chart + } + + allServiceMap := prod.GetServiceMap() + serviceMap := make(map[string]*models.ProductService) + + //validate data, make sure service and chart info exists + if len(serviceName) > 0 { + serviceList := strings.Split(serviceName, ",") + for _, singleService := range serviceList { + if service, ok := allServiceMap[singleService]; ok { + serviceMap[service.ServiceName] = service + } else { + return nil, e.ErrGetHelmCharts.AddDesc(fmt.Sprintf("failed to find service %s in target namespace", singleService)) + } + } + } else { + serviceMap = allServiceMap + } + + if len(serviceMap) == 0 { + return nil, nil + } + + ret := &HelmChartsResp{ + ChartInfos: make([]*ChartInfo, 0), + FileInfos: make([]*types.FileInfo, 0), + } + + errList := new(multierror.Error) + wg := sync.WaitGroup{} + + for _, service := range serviceMap { + ret.ChartInfos = append(ret.ChartInfos, &ChartInfo{ + ServiceName: service.ServiceName, + Revision: service.Revision, + }) + wg.Add(1) + // download chart info with particular version + go func(serviceName string, revision int64) { + defer wg.Done() + serviceObj, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ProductName: productName, + ServiceName: serviceName, + Revision: revision, + Type: setting.HelmDeployType, + }) + if err != nil { + log.Errorf("failed to query services name: %s, revision: %d, error: %s", serviceName, revision, err) + errList = multierror.Append(errList, fmt.Errorf("failed to query service, serviceName: %s, revision: %d", serviceName, revision)) + return + } + renderChart, ok := chartMap[serviceName] + if !ok { + errList = multierror.Append(errList, fmt.Errorf("failed to find render chart for service %s in target namespace", serviceName)) + return + } + err = prepareChartVersionData(productName, serviceObj, renderChart, renderSet) + if err != nil { + errList = multierror.Append(errList, fmt.Errorf("failed to prepare chart info for service %s", serviceObj.ServiceName)) + return + } + }(service.ServiceName, service.Revision) + } + wg.Wait() + + if errList.ErrorOrNil() != nil { + return nil, errList.ErrorOrNil() + } + + // expand file info for first service + serviceToExpand := ret.ChartInfos[0].ServiceName + fis, err := loadChartFilesInfo(productName, serviceToExpand, serviceMap[serviceToExpand].Revision, "") + if err != nil { + log.Errorf("Failed to load service file info, err: %s", err) + return nil, e.ErrListTemplate.AddErr(err) + } + ret.FileInfos = fis + + return ret, nil +} diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 4c9e1fb9..196d63db 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -41,12 +41,13 @@ type ProjectListOptions struct { type ProjectDetailedRepresentation struct { *ProjectBriefRepresentation - Alias string `json:"alias"` - Desc string `json:"desc"` - UpdatedAt int64 `json:"updatedAt"` - UpdatedBy string `json:"updatedBy"` - Onboard bool `json:"onboard"` - Public bool `json:"public"` + Alias string `json:"alias"` + Desc string `json:"desc"` + UpdatedAt int64 `json:"updatedAt"` + UpdatedBy string `json:"updatedBy"` + Onboard bool `json:"onboard"` + Public bool `json:"public"` + DeployType string `json:"deployType"` } type ProjectBriefRepresentation struct { @@ -98,12 +99,13 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, Envs: nameWithEnvMap[name], }, - Alias: info.Alias, - Desc: info.Desc, - UpdatedAt: info.UpdatedAt, - UpdatedBy: info.UpdatedBy, - Onboard: info.OnboardStatus != 0, - Public: info.Public, + Alias: info.Alias, + Desc: info.Desc, + UpdatedAt: info.UpdatedAt, + UpdatedBy: info.UpdatedBy, + Onboard: info.OnboardStatus != 0, + Public: info.Public, + DeployType: info.DeployType, }) } diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 0573b098..d1462e36 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -46,13 +46,30 @@ func GetHelmServiceModule(c *gin.Context) { func GetFilePath(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = svcservice.GetFilePath(c.Param("serviceName"), c.Param("productName"), c.Query("dir"), ctx.Logger) + revision := int64(0) + var err error + if len(c.Query("revision")) > 0 { + revision, err = strconv.ParseInt(c.Query("revision"), 10, 64) + } + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid revision number") + return + } + ctx.Resp, ctx.Err = svcservice.GetFilePath(c.Param("serviceName"), c.Param("productName"), revision, c.Query("dir"), ctx.Logger) } func GetFileContent(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = svcservice.GetFileContent(c.Param("serviceName"), c.Param("productName"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) + + param := new(svcservice.GetFileContentParam) + err := c.ShouldBindQuery(param) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = svcservice.GetFileContent(c.Param("serviceName"), c.Param("productName"), param, ctx.Logger) } func CreateOrUpdateHelmService(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 562b3852..a9d2dec2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -113,6 +113,13 @@ type ChartTemplateData struct { DefaultValuesYAML []byte // content of values.yaml in template } +type GetFileContentParam struct { + FilePath string `json:"filePath" form:"filePath"` + FileName string `json:"fileName" form:"fileName"` + Revision int64 `json:"revision" form:"revision"` + DeliveryVersion bool `json:"deliveryVersion" form:"deliveryVersion"` +} + func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { helmService := &HelmService{ ServiceInfos: []*models.Service{}, @@ -133,7 +140,7 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, helmService.ServiceInfos = services if len(services) > 0 { - fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, "") + fis, err := loadServiceFileInfos(services[0].ProductName, services[0].ServiceName, 0, "") if err != nil { log.Errorf("Failed to load service file info, err: %s", err) return nil, e.ErrListTemplate.AddErr(err) @@ -172,24 +179,39 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * return helmServiceModule, err } -func GetFilePath(serviceName, productName, dir string, _ *zap.SugaredLogger) ([]*types.FileInfo, error) { - return loadServiceFileInfos(productName, serviceName, dir) +func GetFilePath(serviceName, productName string, revision int64, dir string, _ *zap.SugaredLogger) ([]*types.FileInfo, error) { + return loadServiceFileInfos(productName, serviceName, revision, dir) } -func GetFileContent(serviceName, productName, filePath, fileName string, log *zap.SugaredLogger) (string, error) { - base := config.LocalServicePath(productName, serviceName) - +func GetFileContent(serviceName, productName string, param *GetFileContentParam, log *zap.SugaredLogger) (string, error) { + filePath, fileName, revision, forDelivery := param.FilePath, param.FileName, param.Revision, param.DeliveryVersion svc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, + Revision: revision, }) if err != nil { return "", e.ErrFileContent.AddDesc(err.Error()) } - err = commonservice.PreLoadServiceManifests(base, svc) - if err != nil { - return "", e.ErrFileContent.AddDesc(err.Error()) + base := config.LocalServicePath(productName, serviceName) + if revision > 0 { + base = config.LocalServicePathWithRevision(productName, serviceName, revision) + if err = commonservice.PreloadServiceManifestsByRevision(base, svc); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + svc.Revision, svc.ServiceName) + } + } + if err != nil || revision == 0 { + base = config.LocalServicePath(productName, serviceName) + err = commonservice.PreLoadServiceManifests(base, svc) + if err != nil { + return "", e.ErrFileContent.AddDesc(err.Error()) + } + } + + if forDelivery { + base = config.LocalDeliveryChartPathWithRevision(productName, serviceName, revision) } file := filepath.Join(base, serviceName, filePath, fileName) @@ -912,9 +934,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return serviceObj, nil } -func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileInfo, error) { - base := config.LocalServicePath(productName, serviceName) - +func loadServiceFileInfos(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { svc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: productName, ServiceName: serviceName, @@ -923,6 +943,22 @@ func loadServiceFileInfos(productName, serviceName, dir string) ([]*types.FileIn return nil, e.ErrFilePath.AddDesc(err.Error()) } + base := config.LocalServicePath(productName, serviceName) + if revision > 0 { + base = config.LocalServicePathWithRevision(productName, serviceName, revision) + if err = commonservice.PreloadServiceManifestsByRevision(base, svc); err != nil { + log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + svc.Revision, svc.ServiceName) + } + } + if err != nil || revision == 0 { + base = config.LocalServicePath(productName, serviceName) + err = commonservice.PreLoadServiceManifests(base, svc) + if err != nil { + return nil, e.ErrFilePath.AddDesc(err.Error()) + } + } + err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 31a84a53..7f92809e 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -49,6 +49,7 @@ func CreateHelmRepo(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid url") return } + ctx.Err = service.CreateHelmRepo(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index 00ca9b6c..decf797e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -35,15 +35,15 @@ import ( "github.com/koderover/zadig/pkg/util" ) -func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log *zap.SugaredLogger) error { +// get global config payload +func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskCreator string, log *zap.SugaredLogger) (int64, error) { - // 获取全局configpayload configPayload := commonservice.GetConfigPayload(0) repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err != nil { log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) - return fmt.Errorf("failed to query registries") + return 0, fmt.Errorf("failed to query registries") } registriesInvolved := sets.NewString() @@ -68,13 +68,13 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * defaultS3, err := s3.FindDefaultS3() if err != nil { err = e.ErrFindDefaultS3Storage.AddDesc("default storage is required by distribute task") - return err + return 0, err } defaultURL, err := defaultS3.GetEncryptedURL() if err != nil { err = e.ErrS3Storage.AddErr(err) - return err + return 0, err } task := &task.Task{ @@ -82,6 +82,7 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * ProductName: args.ProjectName, Status: config.StatusCreated, ArtifactPackageTaskArgs: args, + TaskCreator: taskCreator, ConfigPayload: configPayload, StorageURI: defaultURL, } @@ -100,14 +101,14 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * }).ToSubTask() if err != nil { - return err + return 0, err } task.SubTasks = []map[string]interface{}{subTask} if err := ensurePipelineTask(task, "", log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) - return err + return 0, err } stages := make([]*commonmodels.Stage, 0) @@ -117,14 +118,14 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * sort.Sort(ByStageKind(stages)) task.Stages = stages if len(task.Stages) == 0 { - return e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + return 0, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) } pipelineName := fmt.Sprintf("%s-%s-%s", args.ProjectName, args.EnvName, "artifact") nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.ServiceTaskFmt, pipelineName)) if err != nil { log.Errorf("CreateServiceTask Counter.GetNextSeq error: %v", err) - return e.ErrGetCounter.AddDesc(err.Error()) + return 0, e.ErrGetCounter.AddDesc(err.Error()) } task.SubTasks = []map[string]interface{}{} @@ -133,8 +134,8 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, log * if err := CreateTask(task); err != nil { log.Error(err) - return e.ErrCreateTask + return 0, e.ErrCreateTask } - return nil + return nextTaskID, nil } diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go index d58b587b..8f9e41da 100644 --- a/pkg/microservice/packager/core/service/packager.go +++ b/pkg/microservice/packager/core/service/packager.go @@ -17,10 +17,10 @@ limitations under the License. package service type ImageData struct { - ImageUrl string `yaml:"image_url"` - ImageName string `yaml:"image_name"` - ImageTag string `yaml:"image_tag"` - RegistryID string `yaml:"registry_id,omitempty"` + ImageUrl string `yaml:"image_url" json:"image_url"` + ImageName string `yaml:"image_name" json:"image_name"` + ImageTag string `yaml:"image_tag" json:"image_tag"` + RegistryID string `yaml:"registry_id" json:"registry_id"` } // ImagesByService defines all images in a service diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 339ab5de..ba4e036e 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -22,6 +22,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "io" "os" "path/filepath" "strings" @@ -93,15 +94,68 @@ func buildTargetImage(imageName, imageTag, host, nameSpace string) string { return ret } -func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) error { +func pullImage(dockerClient *client.Client, imageUrl string, options *types.ImagePullOptions) error { + // pull image + pullResponse, err := dockerClient.ImagePull(context.TODO(), imageUrl, *options) + if err != nil { + return err + } + + defer func(pullResponse io.ReadCloser) { + err := pullResponse.Close() + if err != nil { + log.Errorf("failed to close response reader") + } + }(pullResponse) + + bs, err := io.ReadAll(pullResponse) + if err != nil { + return err + } + + if strings.Contains(string(bs), "error") { + log.Errorf("image push failed: %s", string(bs)) + return fmt.Errorf("failed to push image") + } + return nil +} + +func pushImage(dockerClient *client.Client, targetImageUrl string, options *types.ImagePushOptions) error { + pushResponse, err := dockerClient.ImagePush(context.TODO(), targetImageUrl, *options) + if err != nil { + return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + } + + defer func(pullResponse io.ReadCloser) { + err := pullResponse.Close() + if err != nil { + log.Errorf("failed to close response reader") + } + }(pushResponse) + + bs, err := io.ReadAll(pushResponse) + if err != nil { + return err + } + + if strings.Contains(string(bs), "error") { + log.Errorf("image push failed: %s", string(bs)) + return fmt.Errorf("failed to push image") + } + return nil +} + +func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) ([]*ImageData, error) { targetImageUrlByRepo := make(map[string][]string) + retImages := make([]*ImageData, 0) + for _, singleImage := range imageByService.Images { options := types.ImagePullOptions{} // for images from public repo,registryID won't be appointed if len(singleImage.RegistryID) > 0 { registryInfo, ok := allRegistries[singleImage.RegistryID] if !ok { - return fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) + return nil, fmt.Errorf("failed to find source registry for image: %s", singleImage.ImageUrl) } encodedAuth, err := base64EncodeAuth(&types.AuthConfig{ Username: registryInfo.UserName, @@ -109,15 +163,15 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ServerAddress: registryInfo.Host, }) if err != nil { - return errors.Wrapf(err, "faied to create docker pull auth data") + return nil, errors.Wrapf(err, "faied to create docker pull auth data") } options.RegistryAuth = encodedAuth } // pull image - _, err := dockerClient.ImagePull(context.TODO(), singleImage.ImageUrl, options) + err := pullImage(dockerClient, singleImage.ImageUrl, &options) if err != nil { - return errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) + return nil, errors.Wrapf(err, "failed to pull image: %s", singleImage.ImageUrl) } // tag image @@ -125,9 +179,15 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri targetImage := buildTargetImage(singleImage.ImageName, singleImage.ImageTag, registry.Host, registry.Namespace) err = dockerClient.ImageTag(context.TODO(), singleImage.ImageUrl, targetImage) if err != nil { - return errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) + return nil, errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) } targetImageUrlByRepo[registry.RegistryID] = append(targetImageUrlByRepo[registry.RegistryID], targetImage) + retImages = append(retImages, &ImageData{ + ImageUrl: targetImage, + ImageName: singleImage.ImageName, + ImageTag: singleImage.ImageTag, + RegistryID: singleImage.RegistryID, + }) } } @@ -139,19 +199,20 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ServerAddress: registry.Host, }) if err != nil { - return errors.Wrapf(err, "faied to create docker push auth data") + return nil, errors.Wrapf(err, "faied to create docker push auth data") } options := types.ImagePushOptions{ RegistryAuth: encodedAuth, } + for _, targetImageUrl := range targetImageUrlByRepo[registry.RegistryID] { - _, err = dockerClient.ImagePush(context.TODO(), targetImageUrl, options) + err = pushImage(dockerClient, targetImageUrl, &options) if err != nil { - return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) + return nil, errors.Wrapf(err, "failed to push image: %s", targetImageUrl) } } } - return nil + return retImages, nil } func (p *Packager) Exec() error { @@ -201,7 +262,7 @@ func (p *Packager) Exec() error { result := &PackageResult{ ServiceName: imageByService.ServiceName, } - err = handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) + images, err := handleSingleService(imageByService, buildRegistryMap(allRegistries), p.Ctx.TargetRegistries, cli) if err != nil { result.Result = "failed" @@ -209,7 +270,7 @@ func (p *Packager) Exec() error { log.Errorf("[result][fail][%s][%s]", imageByService.ServiceName, err) } else { result.Result = "success" - result.ImageData = imageByService.Images + result.ImageData = images log.Infof("[result][success][%s]", imageByService.ServiceName) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go index 9ac39d95..5ebe24fb 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go +++ b/pkg/microservice/warpdrive/core/service/types/task/artifact_package.go @@ -26,7 +26,7 @@ type ArtifactPackage struct { TaskType config.TaskType `bson:"type" json:"type"` Enabled bool `bson:"enabled" json:"enabled"` TaskStatus config.Status `bson:"status" json:"status"` - Progress string `bson:"progress" json:"progress"` + Progress string `bson:"progress" json:"progress"` Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 97e9f7dc..82ee8535 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -136,7 +136,7 @@ type ReleaseConfig struct { // e.g. xxx.com/resources/predator-plugin:v0.1.0 PredatorImage string // PackagerImage sets docker build image - // e.g. xxx.com/resources/predator-plugin:v0.1.0 + // e.g. xxx.com/resources/packager-plugin:v0.1.0 PackagerImage string } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index df2c1159..250cc4b4 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -272,6 +272,16 @@ const ( FunctionTestType = "function" ) +const ( + DeliveryVersionTypeChart = "HelmChart" + DeliveryVersionTypeK8SWorkflow = "K8SWorkflow" +) + +const ( + DeliveryDeployTypeImage = "image" + DeliveryDeployTypeChart = "chart" +) + const ( AuthorizationHeader = "Authorization" ) @@ -327,6 +337,21 @@ const ( ProductStatusUnstable = "Unstable" ) +// DeliveryVersion status +const ( + DeliveryVersionStatusSuccess = "success" + DeliveryVersionStatusFailed = "failed" + DeliveryVersionStatusCreating = "creating" + DeliveryVersionStatusRetrying = "retrying" +) + +const ( + DeliveryVersionPackageStatusSuccess = "success" + DeliveryVersionPackageStatusFailed = "failed" + DeliveryVersionPackageStatusWaiting = "waiting" + DeliveryVersionPackageStatusUploading = "uploading" +) + const ( NormalModeProduct = "normal" ) diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 45554ea7..e0ffc2ee 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -680,4 +680,10 @@ var ( ErrUpdateExternalLink = NewHTTPError(6842, "更新链接失败") ErrDeleteExternalLink = NewHTTPError(6843, "删除链接失败") ErrListExternalLink = NewHTTPError(6844, "获取链接列表失败") + + //----------------------------------------------------------------------------------------------- + // helm releated Error Range: 6850 - 6869 + //----------------------------------------------------------------------------------------------- + ErrListHelmReleases = NewHTTPError(6850, "获取release失败") + ErrGetHelmCharts = NewHTTPError(6851, "获取chart信息失败") ) -- Gitee From 965f6411d4219fb0f570718b9120b0632eb39a41 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 16:24:32 +0800 Subject: [PATCH 0968/2647] codehost init Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 84 ++++++++++++++++++- .../core/codehost/pkg/oauth/github/github.go | 68 +++++++++++++++ .../core/codehost/pkg/oauth/oauth.go | 16 ++++ 3 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go create mode 100644 pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 37f2df69..c0c34742 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -18,7 +18,10 @@ package handler import ( "context" + "encoding/base64" + "encoding/json" "fmt" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" "net/http" "net/url" "strconv" @@ -73,6 +76,81 @@ func GetCodeHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetCodeHost(id, ctx.Logger) } +type State struct { + CodeHostID int `json:"code_host_id"` + RedirectURL string `json:"redirect_url"` +} + +func AuthCodeHostV2(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + idInt, err := strconv.Atoi(id) + if err != nil { + ctx.Err = err + return + } + codeHost, err := service.GetCodeHost(idInt, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + o, err := github.NewGithubOauth("", "", "", "") + if err != nil { + ctx.Err = err + return + } + stateStruct := State{ + CodeHostID: codeHost.ID, + RedirectURL: "", + } + bs, err := json.Marshal(stateStruct) + if err != nil { + ctx.Err = err + return + } + c.Redirect(http.StatusFound, o.LoginURL(base64.URLEncoding.EncodeToString(bs))) +} + +func CallbackV2(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + stateStr := c.Query("state") + bs, err := base64.URLEncoding.DecodeString(stateStr) + if err != nil { + ctx.Err = err + return + } + var state *State + if err := json.Unmarshal(bs, state); err != nil { + c.Redirect(http.StatusFound,state.RedirectURL) + return + } + codehost , err := service.GetCodeHost(state.CodeHostID,ctx.Logger) + if err != nil { + c.Redirect(http.StatusFound,state.RedirectURL) + return + } + o, err := github.NewGithubOauth("", "", "", "") + if err != nil { + c.Redirect(http.StatusFound,state.RedirectURL) + return + } + token , err := o.HandleCallback(c.Request) + if err !=nil { + c.Redirect(http.StatusFound,state.RedirectURL) + return + } + codehost.AccessToken = token.AccessToken + codehost.RefreshToken = token.RefreshToken + if _,err := service.UpdateCodeHostByToken(codehost,ctx.Logger);err !=nil { + c.Redirect(http.StatusFound,state.RedirectURL) + return + } +} + func AuthCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -128,11 +206,11 @@ func Callback(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() state := c.Query("state") - state , err := url.PathUnescape(state) + state, err := url.PathUnescape(state) if err != nil { ctx.Logger.Errorf("code_host_get_call_back PathUnescape state:%s err : %s", state, err) url := fmt.Sprintf("%s%s", state, "&errMessage=failed_to_parse_redirect_url") - c.Redirect(http.StatusFound,url) + c.Redirect(http.StatusFound, url) return } urlArray := strings.Split(state, "&codeHostId=") @@ -140,7 +218,7 @@ func Callback(c *gin.Context) { if len(urlArray) != 2 { ctx.Logger.Errorf("code_host_get_call_back split &codeHostId fail ,state: %s", state) url := fmt.Sprintf("%s?%s", frontEndUrl, "&errMessage=failed_to_parse_redirect_url") - c.Redirect(http.StatusFound,url) + c.Redirect(http.StatusFound, url) return } if strings.Contains(frontEndUrl, "errCode") { diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go new file mode 100644 index 00000000..1e0c44c3 --- /dev/null +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go @@ -0,0 +1,68 @@ +package github + +import ( + "context" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "golang.org/x/oauth2" + "golang.org/x/oauth2/github" + "net/http" +) + +type Oauth struct { + RedirectURI string `json:"redirectURI"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + HostName string `json:"host_name"` +} + +func NewGithubOauth (redirectURI ,clientID,clientSecret,hostName string)(oauth.CallbackOauth,error){ + return &Oauth{ + RedirectURI: redirectURI, + ClientID: clientID, + ClientSecret: clientSecret, + HostName: hostName, + } , nil +} + +func (g *Oauth)oauth2Config()*oauth2.Config{ + endpoint := github.Endpoint + if g.HostName != ""{ + endpoint = oauth2.Endpoint{ + AuthURL: g.HostName+"/login/oauth/authorize", + TokenURL: g.HostName+"/login/oauth/access_token", + } + } + return &oauth2.Config{ + ClientID: g.ClientID, + ClientSecret: g.ClientSecret, + Endpoint: endpoint, + RedirectURL: g.RedirectURI, + Scopes: []string{"repo","user"}, + } +} + +type oauth2Error struct { + error string + errorDescription string +} + +func (e *oauth2Error) Error() string { + if e.errorDescription == "" { + return e.error + } + return e.error + ": " + e.errorDescription +} + + +func (g *Oauth)LoginURL(state string)(loginURL string){ + return g.oauth2Config().AuthCodeURL(state) +} + +func (g *Oauth)HandleCallback(r *http.Request)(token *oauth2.Token,err error){ + q := r.URL.Query() + if errType := q.Get("error");errType !=""{ + return nil, &oauth2Error{errType, q.Get("error_description")} + } + oauth2Config := g.oauth2Config() + return oauth2Config.Exchange(context.Background(),q.Get("code")) +} \ No newline at end of file diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go new file mode 100644 index 00000000..747e2104 --- /dev/null +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go @@ -0,0 +1,16 @@ +package oauth + +import ( + "golang.org/x/oauth2" + "net/http" +) + +type CallbackOauth interface{ + LoginURL(state string)(loginURL string) + HandleCallback(r *http.Request)(token *oauth2.Token,err error) +} + +type Token struct { + AccessToken string + RefreshToken string +} \ No newline at end of file -- Gitee From 6ef8b905e792463d0266fc72dd2198a71c1725d5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 16:45:20 +0800 Subject: [PATCH 0969/2647] codehost init Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 193 ++---------------- .../core/codehost/pkg/oauth/github/github.go | 6 +- .../core/codehost/pkg/oauth/oauth.go | 10 + 3 files changed, 33 insertions(+), 176 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index c0c34742..1dc8fa58 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,22 +17,16 @@ limitations under the License. package handler import ( - "context" "encoding/base64" "encoding/json" - "fmt" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" - "net/http" - "net/url" - "strconv" - "strings" - "github.com/gin-gonic/gin" - "golang.org/x/oauth2" - + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "net/http" + "strconv" ) func CreateCodeHost(c *gin.Context) { @@ -81,26 +75,35 @@ type State struct { RedirectURL string `json:"redirect_url"` } -func AuthCodeHostV2(c *gin.Context) { +type AuthArgs struct { + RedirectURI string `json:"redirect_uri"` + HostName string `json:"host_name"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Provider string `json:"provider"` +} + +func AuthCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") - idInt, err := strconv.Atoi(id) - if err != nil { + var au AuthArgs + if err := c.ShouldBindJSON(&au);err !=nil { ctx.Err = err return } - codeHost, err := service.GetCodeHost(idInt, ctx.Logger) + idInt, err := strconv.Atoi(id) if err != nil { ctx.Err = err return } - o, err := github.NewGithubOauth("", "", "", "") + codeHost, err := service.GetCodeHost(idInt, ctx.Logger) if err != nil { ctx.Err = err return } + oauth := oauth.Factory(au.Provider,au.RedirectURI, au.ClientID, au.ClientSecret, au.HostName) stateStruct := State{ CodeHostID: codeHost.ID, RedirectURL: "", @@ -110,10 +113,10 @@ func AuthCodeHostV2(c *gin.Context) { ctx.Err = err return } - c.Redirect(http.StatusFound, o.LoginURL(base64.URLEncoding.EncodeToString(bs))) + c.Redirect(http.StatusFound, oauth.LoginURL(base64.URLEncoding.EncodeToString(bs))) } -func CallbackV2(c *gin.Context) { +func Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -151,162 +154,6 @@ func CallbackV2(c *gin.Context) { } } -func AuthCodeHost(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - provider := c.Query("provider") - redirect := c.Query("redirect") - redirectHost, err := url.Parse(redirect) - if err != nil { - ctx.Err = err - return - } - id := c.Param("id") - idInt, err := strconv.Atoi(id) - if err != nil { - ctx.Err = err - return - } - authStateString := fmt.Sprintf("%s%s%s%s%s", redirect, "&codeHostId=", id, "&provider=", provider) - codeHost, err := service.GetCodeHost(idInt, ctx.Logger) - if err != nil { - ctx.Err = err - return - } - callBackUrl := fmt.Sprintf("%s://%s%s", redirectHost.Scheme, redirectHost.Host, "/api/directory/codehosts/callback") - - var authUrl string - var tokenUrl string - var scopes []string - if provider == "gitlab" { - scopes = []string{"api", "read_user"} - authUrl = codeHost.Address + "/oauth/authorize" - tokenUrl = codeHost.Address + "/oauth/token" - } else if provider == "github" { - scopes = []string{"repo", "user"} - authUrl = codeHost.Address + "/login/oauth/authorize" - tokenUrl = codeHost.Address + "/login/oauth/access_token" - } - authConfig := &oauth2.Config{ - ClientID: codeHost.ApplicationId, - ClientSecret: codeHost.ClientSecret, - RedirectURL: callBackUrl, - Endpoint: oauth2.Endpoint{ - AuthURL: authUrl, - TokenURL: tokenUrl, - }, - Scopes: scopes, - } - redirectURL := authConfig.AuthCodeURL(authStateString) - c.Redirect(http.StatusFound, redirectURL) -} - -func Callback(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - state := c.Query("state") - state, err := url.PathUnescape(state) - if err != nil { - ctx.Logger.Errorf("code_host_get_call_back PathUnescape state:%s err : %s", state, err) - url := fmt.Sprintf("%s%s", state, "&errMessage=failed_to_parse_redirect_url") - c.Redirect(http.StatusFound, url) - return - } - urlArray := strings.Split(state, "&codeHostId=") - frontEndUrl := urlArray[0] - if len(urlArray) != 2 { - ctx.Logger.Errorf("code_host_get_call_back split &codeHostId fail ,state: %s", state) - url := fmt.Sprintf("%s?%s", frontEndUrl, "&errMessage=failed_to_parse_redirect_url") - c.Redirect(http.StatusFound, url) - return - } - if strings.Contains(frontEndUrl, "errCode") { - frontEndUrl = strings.Split(frontEndUrl, "?errCode")[0] - } - - gitlabError := c.Query("error") - if gitlabError != "" { - ctx.Logger.Warnf("code_host_get_call_back_gitlab user denied err: %s", gitlabError) - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=access_denied") - c.Redirect(http.StatusFound, url) - return - } - stateURL, err := url.Parse(state) - if err != nil { - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=failed_to_parse_redirect_url") - http.Redirect(c.Writer, c.Request, url, http.StatusFound) - return - } - codeHostArray := strings.Split(urlArray[1], "&provider=") - codeHostID, err := strconv.Atoi(codeHostArray[0]) - if err != nil { - ctx.Logger.Errorf("code_host_get_call_back_gitlab codeHostID convert err : %s", err) - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=codeHostID convert failed") - c.Redirect(http.StatusFound, url) - return - } - code := c.Query("code") - iCodehost, err := service.GetCodeHost(codeHostID, ctx.Logger) - if err != nil { - ctx.Logger.Errorf("code_host_get_call_back_gitlab GetCodeHostByID err: %s", err) - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=get codehost failed") - c.Redirect(http.StatusFound, url) - return - } - - callBackUrl := fmt.Sprintf("%s://%s%s", stateURL.Scheme, stateURL.Host, "/api/directory/codehosts/callback") - authConfig := &oauth2.Config{ - ClientID: iCodehost.ApplicationId, - ClientSecret: iCodehost.ClientSecret, - RedirectURL: callBackUrl, - //RedirectURL: "http://localhost:34001/directory/codehosts/callback", - } - if codeHostArray[1] == "gitlab" { - authConfig.Scopes = []string{"api", "read_user"} - authConfig.Endpoint = oauth2.Endpoint{ - AuthURL: fmt.Sprintf("%s%s", iCodehost.Address, "/oauth/authorize"), - TokenURL: fmt.Sprintf("%s%s", iCodehost.Address, "/oauth/token"), - } - } else if codeHostArray[1] == "github" { - authConfig.Scopes = []string{"repo", "user"} - authConfig.Endpoint = oauth2.Endpoint{ - AuthURL: fmt.Sprintf("%s%s", iCodehost.Address, "/login/oauth/authorize"), - TokenURL: fmt.Sprintf("%s%s", iCodehost.Address, "/login/oauth/access_token"), - } - } - dc := http.DefaultClient - ctxx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) - token, err := authConfig.Exchange(ctxx, code) - if err != nil { - ctx.Logger.Errorf("code_host_get_call_back_gitlab Exchange err: %s", err) - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=exchange failed") - c.Redirect(http.StatusFound, url) - return - } - - iCodehost.AccessToken = token.AccessToken - iCodehost.RefreshToken = token.RefreshToken - ctx.Logger.Infof("%+v", iCodehost) - _, err = service.UpdateCodeHostByToken(iCodehost, ctx.Logger) - if err != nil { - ctx.Logger.Errorf("UpdateCodeHostByToken err: %s", err) - url := fmt.Sprintf("%s%s%s", frontEndUrl, "?", "&errMessage=update codehost failed") - c.Redirect(http.StatusFound, url) - return - } - - //redirect to front end - redirectURL := "" - if strings.Contains(frontEndUrl, "?succeed") { - redirectURL = fmt.Sprintf("%s%s", strings.Split(frontEndUrl, "?succeed")[0], "?succeed=true") - } else { - redirectURL = fmt.Sprintf("%s%s", frontEndUrl, "?succeed=true") - } - c.Redirect(http.StatusFound, redirectURL) - -} - func UpdateCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go index 1e0c44c3..0204fb35 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go @@ -15,13 +15,13 @@ type Oauth struct { HostName string `json:"host_name"` } -func NewGithubOauth (redirectURI ,clientID,clientSecret,hostName string)(oauth.CallbackOauth,error){ +func NewGithubOauth (redirectURI ,clientID,clientSecret,hostName string)oauth.CallbackOauth{ return &Oauth{ RedirectURI: redirectURI, ClientID: clientID, ClientSecret: clientSecret, HostName: hostName, - } , nil + } } func (g *Oauth)oauth2Config()*oauth2.Config{ @@ -65,4 +65,4 @@ func (g *Oauth)HandleCallback(r *http.Request)(token *oauth2.Token,err error){ } oauth2Config := g.oauth2Config() return oauth2Config.Exchange(context.Background(),q.Get("code")) -} \ No newline at end of file +} diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go index 747e2104..24b4203a 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go @@ -1,6 +1,7 @@ package oauth import ( + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" "golang.org/x/oauth2" "net/http" ) @@ -13,4 +14,13 @@ type CallbackOauth interface{ type Token struct { AccessToken string RefreshToken string +} + +func Factory(provider ,redirectURI ,clientID,clientSecret,hostName string)CallbackOauth{ + switch provider { + case"github": + return github.NewGithubOauth(redirectURI ,clientID,clientSecret,hostName) + default: + github.NewGithubOauth(redirectURI ,clientID,clientSecret,hostName) + } } \ No newline at end of file -- Gitee From dc21a4759e63c5722a700938cce03597083c62b5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 16:50:29 +0800 Subject: [PATCH 0970/2647] codehost init Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 1dc8fa58..dab228f6 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -21,7 +21,6 @@ import ( "encoding/json" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -126,8 +125,8 @@ func Callback(c *gin.Context) { ctx.Err = err return } - var state *State - if err := json.Unmarshal(bs, state); err != nil { + var state State + if err := json.Unmarshal(bs, &state); err != nil { c.Redirect(http.StatusFound,state.RedirectURL) return } @@ -136,11 +135,7 @@ func Callback(c *gin.Context) { c.Redirect(http.StatusFound,state.RedirectURL) return } - o, err := github.NewGithubOauth("", "", "", "") - if err != nil { - c.Redirect(http.StatusFound,state.RedirectURL) - return - } + o:= oauth.Factory(codehost.Type,state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) token , err := o.HandleCallback(c.Request) if err !=nil { c.Redirect(http.StatusFound,state.RedirectURL) -- Gitee From c4aa114b36509059747b245056355ccf538f4c3e Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 16:53:36 +0800 Subject: [PATCH 0971/2647] fmt Signed-off-by: mouuii --- .../core/codehost/pkg/oauth/github/github.go | 31 +++++++++---------- .../core/codehost/pkg/oauth/oauth.go | 18 +++++------ 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go index 0204fb35..012a810e 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go @@ -9,27 +9,27 @@ import ( ) type Oauth struct { - RedirectURI string `json:"redirectURI"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - HostName string `json:"host_name"` + RedirectURI string `json:"redirectURI"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + HostName string `json:"host_name"` } -func NewGithubOauth (redirectURI ,clientID,clientSecret,hostName string)oauth.CallbackOauth{ +func NewGithubOauth(redirectURI, clientID, clientSecret, hostName string) oauth.CallbackOauth { return &Oauth{ RedirectURI: redirectURI, ClientID: clientID, ClientSecret: clientSecret, - HostName: hostName, + HostName: hostName, } } -func (g *Oauth)oauth2Config()*oauth2.Config{ +func (g *Oauth) oauth2Config() *oauth2.Config { endpoint := github.Endpoint - if g.HostName != ""{ + if g.HostName != "" { endpoint = oauth2.Endpoint{ - AuthURL: g.HostName+"/login/oauth/authorize", - TokenURL: g.HostName+"/login/oauth/access_token", + AuthURL: g.HostName + "/login/oauth/authorize", + TokenURL: g.HostName + "/login/oauth/access_token", } } return &oauth2.Config{ @@ -37,7 +37,7 @@ func (g *Oauth)oauth2Config()*oauth2.Config{ ClientSecret: g.ClientSecret, Endpoint: endpoint, RedirectURL: g.RedirectURI, - Scopes: []string{"repo","user"}, + Scopes: []string{"repo", "user"}, } } @@ -53,16 +53,15 @@ func (e *oauth2Error) Error() string { return e.error + ": " + e.errorDescription } - -func (g *Oauth)LoginURL(state string)(loginURL string){ +func (g *Oauth) LoginURL(state string) (loginURL string) { return g.oauth2Config().AuthCodeURL(state) } -func (g *Oauth)HandleCallback(r *http.Request)(token *oauth2.Token,err error){ +func (g *Oauth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { q := r.URL.Query() - if errType := q.Get("error");errType !=""{ + if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} } oauth2Config := g.oauth2Config() - return oauth2Config.Exchange(context.Background(),q.Get("code")) + return oauth2Config.Exchange(context.Background(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go index 24b4203a..3623ab7b 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go @@ -6,21 +6,21 @@ import ( "net/http" ) -type CallbackOauth interface{ - LoginURL(state string)(loginURL string) - HandleCallback(r *http.Request)(token *oauth2.Token,err error) +type CallbackOauth interface { + LoginURL(state string) (loginURL string) + HandleCallback(r *http.Request) (token *oauth2.Token, err error) } type Token struct { - AccessToken string + AccessToken string RefreshToken string } -func Factory(provider ,redirectURI ,clientID,clientSecret,hostName string)CallbackOauth{ +func Factory(provider, redirectURI, clientID, clientSecret, hostName string) CallbackOauth { switch provider { - case"github": - return github.NewGithubOauth(redirectURI ,clientID,clientSecret,hostName) + case "github": + return github.NewGithubOauth(redirectURI, clientID, clientSecret, hostName) default: - github.NewGithubOauth(redirectURI ,clientID,clientSecret,hostName) + return github.NewGithubOauth(redirectURI, clientID, clientSecret, hostName) } -} \ No newline at end of file +} -- Gitee From 4c7d8a8275be4726b59578d688d8c48b3a7ff0ac Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 10 Dec 2021 16:55:26 +0800 Subject: [PATCH 0972/2647] fix cvm bug (#726) Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f4558788..7a73ad5b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1625,7 +1625,6 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } if serviceTmpl != nil { - build.Namespace = args.Env.Namespace build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) for _, envConfig := range serviceTmpl.EnvConfigs { -- Gitee From a88e90c4d9eb57fbcd4f0d38a1352a635e880af1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 17:02:35 +0800 Subject: [PATCH 0973/2647] fmt Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 38 ++++++----- .../core/codehost/pkg/oauth/gitlab/gitlab.go | 67 +++++++++++++++++++ .../core/codehost/pkg/oauth/oauth.go | 11 +-- 3 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index dab228f6..7f9ff41b 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -75,11 +75,11 @@ type State struct { } type AuthArgs struct { - RedirectURI string `json:"redirect_uri"` - HostName string `json:"host_name"` - ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + HostName string `json:"host_name"` + ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` - Provider string `json:"provider"` + Provider string `json:"provider"` } func AuthCodeHost(c *gin.Context) { @@ -88,7 +88,7 @@ func AuthCodeHost(c *gin.Context) { id := c.Param("id") var au AuthArgs - if err := c.ShouldBindJSON(&au);err !=nil { + if err := c.ShouldBindJSON(&au); err != nil { ctx.Err = err return } @@ -102,7 +102,11 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - oauth := oauth.Factory(au.Provider,au.RedirectURI, au.ClientID, au.ClientSecret, au.HostName) + oauth, err := oauth.Factory(au.Provider, au.RedirectURI, au.ClientID, au.ClientSecret, au.HostName) + if err != nil { + ctx.Err = err + return + } stateStruct := State{ CodeHostID: codeHost.ID, RedirectURL: "", @@ -127,24 +131,28 @@ func Callback(c *gin.Context) { } var state State if err := json.Unmarshal(bs, &state); err != nil { - c.Redirect(http.StatusFound,state.RedirectURL) + c.Redirect(http.StatusFound, state.RedirectURL) return } - codehost , err := service.GetCodeHost(state.CodeHostID,ctx.Logger) + codehost, err := service.GetCodeHost(state.CodeHostID, ctx.Logger) if err != nil { - c.Redirect(http.StatusFound,state.RedirectURL) + c.Redirect(http.StatusFound, state.RedirectURL) return } - o:= oauth.Factory(codehost.Type,state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) - token , err := o.HandleCallback(c.Request) - if err !=nil { - c.Redirect(http.StatusFound,state.RedirectURL) + o, err := oauth.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + if err != nil { + c.Redirect(http.StatusFound, state.RedirectURL) + return + } + token, err := o.HandleCallback(c.Request) + if err != nil { + c.Redirect(http.StatusFound, state.RedirectURL) return } codehost.AccessToken = token.AccessToken codehost.RefreshToken = token.RefreshToken - if _,err := service.UpdateCodeHostByToken(codehost,ctx.Logger);err !=nil { - c.Redirect(http.StatusFound,state.RedirectURL) + if _, err := service.UpdateCodeHostByToken(codehost, ctx.Logger); err != nil { + c.Redirect(http.StatusFound, state.RedirectURL) return } } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go new file mode 100644 index 00000000..2632a0d1 --- /dev/null +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go @@ -0,0 +1,67 @@ +package gitlab + +import ( + "context" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "golang.org/x/oauth2" + "golang.org/x/oauth2/gitlab" + "net/http" +) + +type Oauth struct { + RedirectURI string `json:"redirectURI"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + HostName string `json:"host_name"` +} + +func NewGitlabOauth(redirectURI, clientID, clientSecret, hostName string) oauth.CallbackOauth { + return &Oauth{ + RedirectURI: redirectURI, + ClientID: clientID, + ClientSecret: clientSecret, + HostName: hostName, + } +} + +func (g *Oauth) oauth2Config() *oauth2.Config { + endpoint := gitlab.Endpoint + if g.HostName != "" { + endpoint = oauth2.Endpoint{ + AuthURL: g.HostName + "/oauth/authorize", + TokenURL: g.HostName + "/oauth/token", + } + } + return &oauth2.Config{ + ClientID: g.ClientID, + ClientSecret: g.ClientSecret, + Endpoint: endpoint, + RedirectURL: g.RedirectURI, + Scopes: []string{"api", "read_user"}, + } +} + +type oauth2Error struct { + error string + errorDescription string +} + +func (e *oauth2Error) Error() string { + if e.errorDescription == "" { + return e.error + } + return e.error + ": " + e.errorDescription +} + +func (g *Oauth) LoginURL(state string) (loginURL string) { + return g.oauth2Config().AuthCodeURL(state) +} + +func (g *Oauth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { + q := r.URL.Query() + if errType := q.Get("error"); errType != "" { + return nil, &oauth2Error{errType, q.Get("error_description")} + } + oauth2Config := g.oauth2Config() + return oauth2Config.Exchange(context.Background(), q.Get("code")) +} diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go index 3623ab7b..818e9f4c 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go @@ -1,7 +1,9 @@ package oauth import ( + "errors" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab" "golang.org/x/oauth2" "net/http" ) @@ -16,11 +18,12 @@ type Token struct { RefreshToken string } -func Factory(provider, redirectURI, clientID, clientSecret, hostName string) CallbackOauth { +func Factory(provider, redirectURI, clientID, clientSecret, hostName string) (CallbackOauth, error) { switch provider { case "github": - return github.NewGithubOauth(redirectURI, clientID, clientSecret, hostName) - default: - return github.NewGithubOauth(redirectURI, clientID, clientSecret, hostName) + return github.NewGithubOauth(redirectURI, clientID, clientSecret, hostName), nil + case "gitlab": + return gitlab.NewGitlabOauth(redirectURI, clientID, clientSecret, hostName), nil } + return nil, errors.New("illegal provider") } -- Gitee From a837db4a9641f720d8444f03b16843c44ca49fd3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 18:31:21 +0800 Subject: [PATCH 0974/2647] fmt Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 60 ++--------------- .../core/codehost/service/codehost.go | 66 +++++++++++++++++++ 2 files changed, 70 insertions(+), 56 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 7f9ff41b..13998912 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,10 +17,7 @@ limitations under the License. package handler import ( - "encoding/base64" - "encoding/json" "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -69,11 +66,6 @@ func GetCodeHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetCodeHost(id, ctx.Logger) } -type State struct { - CodeHostID int `json:"code_host_id"` - RedirectURL string `json:"redirect_url"` -} - type AuthArgs struct { RedirectURI string `json:"redirect_uri"` HostName string `json:"host_name"` @@ -97,26 +89,11 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - codeHost, err := service.GetCodeHost(idInt, ctx.Logger) - if err != nil { - ctx.Err = err - return - } - oauth, err := oauth.Factory(au.Provider, au.RedirectURI, au.ClientID, au.ClientSecret, au.HostName) + url, err := service.AuthCodeHost(au.RedirectURI, au.HostName, au.ClientID, au.ClientSecret, au.Provider, idInt, ctx.Logger) if err != nil { ctx.Err = err - return - } - stateStruct := State{ - CodeHostID: codeHost.ID, - RedirectURL: "", } - bs, err := json.Marshal(stateStruct) - if err != nil { - ctx.Err = err - return - } - c.Redirect(http.StatusFound, oauth.LoginURL(base64.URLEncoding.EncodeToString(bs))) + c.Redirect(http.StatusFound, url) } func Callback(c *gin.Context) { @@ -124,37 +101,8 @@ func Callback(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() stateStr := c.Query("state") - bs, err := base64.URLEncoding.DecodeString(stateStr) - if err != nil { - ctx.Err = err - return - } - var state State - if err := json.Unmarshal(bs, &state); err != nil { - c.Redirect(http.StatusFound, state.RedirectURL) - return - } - codehost, err := service.GetCodeHost(state.CodeHostID, ctx.Logger) - if err != nil { - c.Redirect(http.StatusFound, state.RedirectURL) - return - } - o, err := oauth.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) - if err != nil { - c.Redirect(http.StatusFound, state.RedirectURL) - return - } - token, err := o.HandleCallback(c.Request) - if err != nil { - c.Redirect(http.StatusFound, state.RedirectURL) - return - } - codehost.AccessToken = token.AccessToken - codehost.RefreshToken = token.RefreshToken - if _, err := service.UpdateCodeHostByToken(codehost, ctx.Logger); err != nil { - c.Redirect(http.StatusFound, state.RedirectURL) - return - } + + service.Callback(stateStr, c.Request) } func UpdateCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 0e7b39be..65fae8b0 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -18,7 +18,10 @@ package service import ( "encoding/base64" + "encoding/json" "fmt" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "net/http" "time" "go.uber.org/zap" @@ -69,3 +72,66 @@ func UpdateCodeHostByToken(host *models.CodeHost, _ *zap.SugaredLogger) (*models func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().GetCodeHostByID(id) } + +type State struct { + CodeHostID int `json:"code_host_id"` + RedirectURL string `json:"redirect_url"` +} + +func AuthCodeHost(redirectURI, hostName, clientID, clientSecret, provider string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { + codeHost, err := GetCodeHost(codeHostID, logger) + if err != nil { + logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) + return "", err + } + oauth, err := oauth.Factory(provider, redirectURI, clientID, clientSecret, hostName) + if err != nil { + logger.Errorf("get Factory:%s err:%s", provider, err) + return "", err + } + stateStruct := State{ + CodeHostID: codeHost.ID, + RedirectURL: redirectURL, + } + bs, err := json.Marshal(stateStruct) + if err != nil { + logger.Errorf("Marshal err:%s", err) + return "", err + } + return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil +} + +func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) error { + bs, err := base64.URLEncoding.DecodeString(stateQuery) + if err != nil { + logger.Errorf("DecodeString err:%s", err) + return err + } + var state State + if err := json.Unmarshal(bs, &state); err != nil { + logger.Errorf("Unmarshal err:%s", err) + return err + } + codehost, err := GetCodeHost(state.CodeHostID, logger) + if err != nil { + logger.Errorf("GetCodeHost err:%s", err) + return err + } + o, err := oauth.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + if err != nil { + logger.Errorf("Factory err:%s", err) + return err + } + token, err := o.HandleCallback(r) + if err != nil { + logger.Errorf("HandleCallback err:%s", err) + return err + } + codehost.AccessToken = token.AccessToken + codehost.RefreshToken = token.RefreshToken + if _, err := UpdateCodeHostByToken(codehost, logger); err != nil { + logger.Errorf("UpdateCodeHostByToken err:%s", err) + return err + } + return nil +} -- Gitee From 17a8b95cc8b2d3dfe96cc3328bbf36c730f1e30e Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 18:38:56 +0800 Subject: [PATCH 0975/2647] fmt Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 6 +++++- .../core/codehost/service/codehost.go | 17 +++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 13998912..e0fc58c6 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -102,7 +102,11 @@ func Callback(c *gin.Context) { stateStr := c.Query("state") - service.Callback(stateStr, c.Request) + url, err := service.Callback(stateStr, c.Request, ctx.Logger) + if err != nil { + ctx.Logger.Errorf("Callback err:%s", err) + } + c.Redirect(http.StatusFound, url) } func UpdateCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 65fae8b0..a43ca6d2 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -101,37 +101,38 @@ func AuthCodeHost(redirectURI, hostName, clientID, clientSecret, provider string return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil } -func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) error { +func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (string, error) { bs, err := base64.URLEncoding.DecodeString(stateQuery) if err != nil { logger.Errorf("DecodeString err:%s", err) - return err + return "", err } + var state State if err := json.Unmarshal(bs, &state); err != nil { logger.Errorf("Unmarshal err:%s", err) - return err + return "", err } codehost, err := GetCodeHost(state.CodeHostID, logger) if err != nil { logger.Errorf("GetCodeHost err:%s", err) - return err + return state.RedirectURL, err } o, err := oauth.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { logger.Errorf("Factory err:%s", err) - return err + return state.RedirectURL, err } token, err := o.HandleCallback(r) if err != nil { logger.Errorf("HandleCallback err:%s", err) - return err + return state.RedirectURL, err } codehost.AccessToken = token.AccessToken codehost.RefreshToken = token.RefreshToken if _, err := UpdateCodeHostByToken(codehost, logger); err != nil { logger.Errorf("UpdateCodeHostByToken err:%s", err) - return err + return state.RedirectURL, err } - return nil + return state.RedirectURL, nil } -- Gitee From 2b8df2c50c61dde141009e24c178cfff4e40ed42 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 18:42:51 +0800 Subject: [PATCH 0976/2647] fmt Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 4 +--- .../systemconfig/core/codehost/service/codehost.go | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index e0fc58c6..6196638d 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -68,10 +68,8 @@ func GetCodeHost(c *gin.Context) { type AuthArgs struct { RedirectURI string `json:"redirect_uri"` - HostName string `json:"host_name"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` - Provider string `json:"provider"` } func AuthCodeHost(c *gin.Context) { @@ -89,7 +87,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - url, err := service.AuthCodeHost(au.RedirectURI, au.HostName, au.ClientID, au.ClientSecret, au.Provider, idInt, ctx.Logger) + url, err := service.AuthCodeHost(au.RedirectURI, idInt, ctx.Logger) if err != nil { ctx.Err = err } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index a43ca6d2..3a33ffef 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -78,15 +78,15 @@ type State struct { RedirectURL string `json:"redirect_url"` } -func AuthCodeHost(redirectURI, hostName, clientID, clientSecret, provider string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { +func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { codeHost, err := GetCodeHost(codeHostID, logger) if err != nil { logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err } - oauth, err := oauth.Factory(provider, redirectURI, clientID, clientSecret, hostName) + oauth, err := oauth.Factory(codeHost.Type, redirectURI, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { - logger.Errorf("get Factory:%s err:%s", provider, err) + logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err } stateStruct := State{ -- Gitee From 241d64be1a162566a2aa4c69f93e18d521520c3d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 18:43:36 +0800 Subject: [PATCH 0977/2647] fmt Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 6196638d..c676a634 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -67,9 +67,7 @@ func GetCodeHost(c *gin.Context) { } type AuthArgs struct { - RedirectURI string `json:"redirect_uri"` - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` + RedirectURI string `json:"redirect_uri"` } func AuthCodeHost(c *gin.Context) { -- Gitee From 030740bbddd87e9e6f3112488191d3ef9530000d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 18:48:44 +0800 Subject: [PATCH 0978/2647] fmt Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 3 ++- .../systemconfig/core/codehost/pkg/oauth/github/github.go | 4 ++-- .../systemconfig/core/codehost/pkg/oauth/oauth.go | 6 +++--- .../systemconfig/core/codehost/service/codehost.go | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index c676a634..d00e6a7d 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -68,6 +68,7 @@ func GetCodeHost(c *gin.Context) { type AuthArgs struct { RedirectURI string `json:"redirect_uri"` + CallbackURL string `json:"callback_url"` } func AuthCodeHost(c *gin.Context) { @@ -85,7 +86,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - url, err := service.AuthCodeHost(au.RedirectURI, idInt, ctx.Logger) + url, err := service.AuthCodeHost(au.RedirectURI, au.CallbackURL, idInt, ctx.Logger) if err != nil { ctx.Err = err } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go index 012a810e..31c769c0 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go @@ -15,9 +15,9 @@ type Oauth struct { HostName string `json:"host_name"` } -func NewGithubOauth(redirectURI, clientID, clientSecret, hostName string) oauth.CallbackOauth { +func NewGithubOauth(callbackURL, clientID, clientSecret, hostName string) oauth.CallbackOauth { return &Oauth{ - RedirectURI: redirectURI, + RedirectURI: callbackURL, ClientID: clientID, ClientSecret: clientSecret, HostName: hostName, diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go index 818e9f4c..f8f4bb32 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go @@ -18,12 +18,12 @@ type Token struct { RefreshToken string } -func Factory(provider, redirectURI, clientID, clientSecret, hostName string) (CallbackOauth, error) { +func Factory(provider, callbackURL, clientID, clientSecret, hostName string) (CallbackOauth, error) { switch provider { case "github": - return github.NewGithubOauth(redirectURI, clientID, clientSecret, hostName), nil + return github.NewGithubOauth(callbackURL, clientID, clientSecret, hostName), nil case "gitlab": - return gitlab.NewGitlabOauth(redirectURI, clientID, clientSecret, hostName), nil + return gitlab.NewGitlabOauth(callbackURL, clientID, clientSecret, hostName), nil } return nil, errors.New("illegal provider") } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 3a33ffef..2f698bef 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -78,20 +78,20 @@ type State struct { RedirectURL string `json:"redirect_url"` } -func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { +func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { codeHost, err := GetCodeHost(codeHostID, logger) if err != nil { logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err } - oauth, err := oauth.Factory(codeHost.Type, redirectURI, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + oauth, err := oauth.Factory(codeHost.Type, codeHostCallbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err } stateStruct := State{ CodeHostID: codeHost.ID, - RedirectURL: redirectURL, + RedirectURL: redirectURI, } bs, err := json.Marshal(stateStruct) if err != nil { -- Gitee From a92b53ff41096f60ba5e6c4ea2dbf94401dce632 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 19:06:38 +0800 Subject: [PATCH 0979/2647] fix disordered build info bug Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow_task.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f4558788..954438ec 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -538,7 +538,8 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, buildModuleArgs := &commonmodels.BuildModuleArgs{ Target: target.Name, ServiceName: target.ServiceName, - ProductName: target.ProductName, + //ProductName: target.ProductName, // TODO productName may be nil in some situation, need to figure out reason + ProductName: args.ProductTmplName, Variables: target.Envs, Env: env, } -- Gitee From fd249389d9ac468191616765e773a721ff5426a6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 19:24:27 +0800 Subject: [PATCH 0980/2647] fmt Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index d00e6a7d..6e1ef52d 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -66,27 +66,17 @@ func GetCodeHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetCodeHost(id, ctx.Logger) } -type AuthArgs struct { - RedirectURI string `json:"redirect_uri"` - CallbackURL string `json:"callback_url"` -} - func AuthCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") - var au AuthArgs - if err := c.ShouldBindJSON(&au); err != nil { - ctx.Err = err - return - } idInt, err := strconv.Atoi(id) if err != nil { ctx.Err = err return } - url, err := service.AuthCodeHost(au.RedirectURI, au.CallbackURL, idInt, ctx.Logger) + url, err := service.AuthCodeHost(c.Query("redirect_uri"), c.Query("callback_url"), idInt, ctx.Logger) if err != nil { ctx.Err = err } -- Gitee From 0d7006bb7e80dc1cc7622f48ed5f3994f02391d7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 19:30:18 +0800 Subject: [PATCH 0981/2647] fmt Signed-off-by: mouuii --- .../core/codehost/pkg/oauth/factory/factory.go | 18 ++++++++++++++++++ .../core/codehost/pkg/oauth/oauth.go | 13 ------------- .../core/codehost/service/codehost.go | 6 +++--- 3 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go new file mode 100644 index 00000000..698d27f6 --- /dev/null +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go @@ -0,0 +1,18 @@ +package factory + +import ( + "errors" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab" +) + +func Factory(provider, callbackURL, clientID, clientSecret, hostName string) (oauth.CallbackOauth, error) { + switch provider { + case "github": + return github.NewGithubOauth(callbackURL, clientID, clientSecret, hostName), nil + case "gitlab": + return gitlab.NewGitlabOauth(callbackURL, clientID, clientSecret, hostName), nil + } + return nil, errors.New("illegal provider") +} diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go index f8f4bb32..2edd649b 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go @@ -1,9 +1,6 @@ package oauth import ( - "errors" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab" "golang.org/x/oauth2" "net/http" ) @@ -17,13 +14,3 @@ type Token struct { AccessToken string RefreshToken string } - -func Factory(provider, callbackURL, clientID, clientSecret, hostName string) (CallbackOauth, error) { - switch provider { - case "github": - return github.NewGithubOauth(callbackURL, clientID, clientSecret, hostName), nil - case "gitlab": - return gitlab.NewGitlabOauth(callbackURL, clientID, clientSecret, hostName), nil - } - return nil, errors.New("illegal provider") -} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 2f698bef..10e28c76 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -20,7 +20,7 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory" "net/http" "time" @@ -84,7 +84,7 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err } - oauth, err := oauth.Factory(codeHost.Type, codeHostCallbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + oauth, err := factory.Factory(codeHost.Type, codeHostCallbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err @@ -118,7 +118,7 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st logger.Errorf("GetCodeHost err:%s", err) return state.RedirectURL, err } - o, err := oauth.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + o, err := factory.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { logger.Errorf("Factory err:%s", err) return state.RedirectURL, err -- Gitee From 313105a51e72045e264179ca1891b861297aed37 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 10 Dec 2021 19:46:43 +0800 Subject: [PATCH 0982/2647] fmt Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/handler/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 6e1ef52d..b8608c71 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -76,7 +76,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - url, err := service.AuthCodeHost(c.Query("redirect_uri"), c.Query("callback_url"), idInt, ctx.Logger) + url, err := service.AuthCodeHost(c.Query("redirect_url"), c.Query("callback_url"), idInt, ctx.Logger) if err != nil { ctx.Err = err } -- Gitee From 3e28caad6b32b785b9f8a44a728f8739b2e3d2b7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 10 Dec 2021 20:30:26 +0800 Subject: [PATCH 0983/2647] merge main Signed-off-by: liu deyi --- .../warpdrive/core/service/taskcontroller/task_handler.go | 1 + .../warpdrive/core/service/taskplugin/build_v3.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index a65189a2..9ceaa875 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -413,6 +413,7 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl "_", "-", -1) } else if pipelineTask.Type == config.WorkflowTypeV3 { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", config.WorkflowTypeV3, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type(), fmt.Sprintf("%s-job", pipelineTask.PipelineName))), + "_", "-", -1) } else if pipelineTask.Type == config.ArtifactType { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.ArtifactType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), "_", "-", -1) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go index 8e035957..47cfe978 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -194,7 +194,7 @@ func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pi } //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) if err != nil { msg := fmt.Sprintf("create build job context error: %v", err) p.Log.Error(msg) @@ -216,7 +216,7 @@ func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pi } // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 - if err := createOrUpdateRegistrySecrets(p.KubeNamespace, p.Task.Registries, p.kubeClient); err != nil { + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { msg := fmt.Sprintf("create secret error: %v", err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed @@ -286,7 +286,7 @@ func (p *BuildTaskV3Plugin) Complete(ctx context.Context, pipelineTask *task.Tas } }() - err := saveContainerLog(pipelineTask, p.KubeNamespace, p.FileName, jobLabel, p.kubeClient) + err := saveContainerLog(pipelineTask, p.KubeNamespace, "", p.FileName, jobLabel, p.kubeClient) if err != nil { p.Log.Error(err) p.Task.Error = err.Error() -- Gitee From e6623453e20cbaabdfc24ab79a835654666e0434 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 10 Dec 2021 20:32:02 +0800 Subject: [PATCH 0984/2647] changed helm chart package dependency to fix a memory leak Signed-off-by: Min Min --- go.mod | 24 +++++++++++--------- go.sum | 72 +++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index 0f9924dc..b62c82d8 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b github.com/docker/distribution v2.7.1+incompatible - github.com/docker/docker v20.10.7+incompatible + github.com/docker/docker v20.10.11+incompatible github.com/docker/go-connections v0.4.0 github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/dsnet/compress v0.0.1 // indirect @@ -41,7 +41,7 @@ require ( github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/mittwald/go-helm-client v0.8.2 + github.com/mittwald/go-helm-client v0.8.3 github.com/moby/buildkit v0.9.1 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect @@ -67,8 +67,8 @@ require ( github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect go.mongodb.org/mongo-driver v1.5.0 go.uber.org/zap v1.19.0 - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a - golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect @@ -79,25 +79,27 @@ require ( gorm.io/driver/mysql v1.1.2 gorm.io/gorm v1.21.16 helm.sh/helm/v3 v3.7.1 - k8s.io/api v0.22.1 - k8s.io/apimachinery v0.22.1 - k8s.io/apiserver v0.22.1 // indirect - k8s.io/client-go v0.22.1 - k8s.io/kubectl v0.22.1 - k8s.io/utils v0.0.0-20210802155522-efc7438f0176 + k8s.io/api v0.22.4 + k8s.io/apimachinery v0.22.4 + k8s.io/client-go v0.22.4 + k8s.io/kubectl v0.22.4 + k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.0 - sigs.k8s.io/yaml v1.2.0 + sigs.k8s.io/yaml v1.3.0 ) replace ( github.com/docker/distribution => github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible github.com/docker/docker => github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81 github.com/docker/go-connections => github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 + github.com/go-logr/logr => github.com/go-logr/logr v0.4.0 k8s.io/api => k8s.io/api v0.21.0 k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.21.0 k8s.io/apimachinery => k8s.io/apimachinery v0.21.0 k8s.io/cli-runtime => k8s.io/cli-runtime v0.21.0 k8s.io/client-go => k8s.io/client-go v0.21.0 + k8s.io/klog/v2 => k8s.io/klog/v2 v2.9.0 k8s.io/kubectl => k8s.io/kubectl v0.21.0 + oras.land/oras-go => oras.land/oras-go v0.4.0 ) diff --git a/go.sum b/go.sum index 123658a6..80995ae8 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,9 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -135,8 +136,9 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.0 h1:JukIZisrUXadA9pl3rMkjhiamxiB0cXiu+HGp/Y8cY8= github.com/Masterminds/squirrel v1.5.0/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE= +github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= @@ -158,8 +160,9 @@ github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2 github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M= +github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= @@ -339,6 +342,7 @@ github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= @@ -347,8 +351,9 @@ github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTV github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.3/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= -github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw= +github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -386,6 +391,7 @@ github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDG github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= @@ -435,12 +441,14 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -465,14 +473,16 @@ github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible h1:357nGVUC8gSpeSc2Axup8HfrfTLLUfWfCsCUhiQSKIg= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81 h1:VnJAjP/7FbY46wddaiKakrc1ogXW+3MlP7cg+S4j4Og= github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= +github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 h1:Z8LR4yJgA3vaXpBiUWcWdQZpB9h+dzj4NdLjP9Sfdb0= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= @@ -499,7 +509,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -574,8 +583,6 @@ github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTD github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= @@ -994,8 +1001,9 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= +github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= +github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -1070,8 +1078,9 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= +github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= @@ -1144,8 +1153,9 @@ github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= @@ -1162,10 +1172,11 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mittwald/go-helm-client v0.8.2 h1:tWPe+e4Bx2bAC0lY55nxuOxOvNEP4aBfCdizvGpM24U= -github.com/mittwald/go-helm-client v0.8.2/go.mod h1:DVtyXr7KDPIT44X320DkxF39nfbZn8KdjvibnN7iHnQ= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mittwald/go-helm-client v0.8.3 h1:4l8W1PBZiNyapjJjoyQUU3qI8yWhOhpdD6wWdG3Z33g= +github.com/mittwald/go-helm-client v0.8.3/go.mod h1:ZdBFetgIkFiJivTy+P/Ruz6y5YtQMk5BnMSja4+wMdA= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.9.1 h1:6noq8jvkaRs3OQGTIDf5U5Etkgq6zsPmQHGWi5yhh88= github.com/moby/buildkit v0.9.1/go.mod h1:oVZKk3TMm0MlDx7XxnlF0wKmcpyrzOs9GEp0VXKWFPk= @@ -1255,8 +1266,9 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -1695,8 +1707,9 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1804,8 +1817,9 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1948,6 +1962,7 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= @@ -2327,8 +2342,9 @@ k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.21.0/go.mod h1:w2YSn4/WIwYuxG5zJmcqtRdtqgW/J2JRgFAqps3bBpg= -k8s.io/apiserver v0.22.1 h1:Ul9Iv8OMB2s45h2tl5XWPpAZo1VPIJ/6N+MESeed7L8= k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= +k8s.io/apiserver v0.22.4 h1:L+220cy+94UWmyBl1kiVTklBXrBtKsbjlPV60eL2u6s= +k8s.io/apiserver v0.22.4/go.mod h1:38WmcUZiiy41A7Aty8/VorWRa8vDGqoUzDf2XYlku0E= k8s.io/cli-runtime v0.21.0 h1:/V2Kkxtf6x5NI2z+Sd/mIrq4FQyQ8jzZAUD6N5RnN7Y= k8s.io/cli-runtime v0.21.0/go.mod h1:XoaHP93mGPF37MkLbjGVYqg3S1MnsFdKtiA/RZzzxOo= k8s.io/client-go v0.21.0 h1:n0zzzJsAQmJngpC0IhgFcApZyoGXPrDIAD601HD09ag= @@ -2341,8 +2357,9 @@ k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeY k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= -k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= +k8s.io/component-base v0.22.4 h1:7qwLJnua2ppGNZrRGDQ0vhsFebI39VGbZ4zdR5ArViI= +k8s.io/component-base v0.22.4/go.mod h1:MrSaQy4a3tFVViff8TZL6JHYSewNCLshZCwHYM58v5A= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= @@ -2359,18 +2376,15 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c h1:jvamsI1tn9V0S8jicyX82qaFC0H/NKxv2e5mbqsgR80= +k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= @@ -2380,8 +2394,9 @@ k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= @@ -2421,7 +2436,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4= -- Gitee From a80a11f71a24c800c90cfc1fd412d3deca3cdf2c Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 10 Dec 2021 20:37:31 +0800 Subject: [PATCH 0985/2647] fix env update bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 72debf2a..43627a1e 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -426,7 +426,7 @@ func GetHelmChartVersions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Param("Name") + envName := c.Param("name") projectName := c.Query("projectName") ctx.Resp, ctx.Err = service.GetHelmChartVersions(projectName, envName, ctx.Logger) -- Gitee From 6bc91d0668556b6e0e493f1e47e1fdfceb7e57c6 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 10 Dec 2021 13:48:22 +0800 Subject: [PATCH 0986/2647] try to fix problem of node log hang Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/script.go | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 47cbf240..1182452c 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -206,6 +206,7 @@ func (r *Reaper) runScripts() error { // avoid non-blocking IO for stdout to workaround "stdout: write error" for _, script := range r.Ctx.Scripts { scripts = append(scripts, script) + // TODO: This may cause nodejs compilation problems, but it is not completely determined, so keep it for now. if strings.Contains(script, "yarn ") || strings.Contains(script, "npm ") || strings.Contains(script, "bower ") { scripts = append(scripts, "echo 'turn off O_NONBLOCK after using node'") scripts = append(scripts, "python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'") @@ -229,33 +230,22 @@ func (r *Reaper) runScripts() error { if err != nil { return err } - outScanner := bufio.NewScanner(cmdOutReader) - go func() { - for outScanner.Scan() { - fmt.Printf("%s\n", r.maskSecretEnvs(outScanner.Text())) - if len(r.Ctx.PostScripts) > 0 { - util.WriteFile(fileName, []byte(outScanner.Text()+"\n"), 0700) - } - } - }() - cmdErrReader, err := cmd.StderrPipe() - if err != nil { + if err := cmd.Start(); err != nil { return err } - errScanner := bufio.NewScanner(cmdErrReader) go func() { - for errScanner.Scan() { - fmt.Printf("%s\n", r.maskSecretEnvs(errScanner.Text())) + for outScanner.Scan() { + fmt.Printf("%s\n", r.maskSecretEnvs(outScanner.Text())) if len(r.Ctx.PostScripts) > 0 { - util.WriteFile(fileName, []byte(errScanner.Text()+"\n"), 0700) + util.WriteFile(fileName, []byte(outScanner.Text()+"\n"), 0700) } } }() - return cmd.Run() + return cmd.Wait() } func (r *Reaper) prepareScriptsEnv() []string { -- Gitee From 36c83de6634cc0cb4523c553444a7edaf3c999f3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 12 Dec 2021 12:32:26 +0800 Subject: [PATCH 0987/2647] fix sse log bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 384a27d5..33b43ff6 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -139,7 +139,7 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op Targets: []string{options.ServiceName}, }) } - if build != nil && build.PreBuild != nil { + if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" && build.PreBuild.Namespace != "" { options.ClusterID = build.PreBuild.ClusterID options.Namespace = build.PreBuild.Namespace } @@ -157,7 +157,7 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, selector := getPipelineSelector(options) // get cluster ID testing, _ := commonrepo.NewTestingColl().Find(getTestName(options.ServiceName), "") - if testing != nil && testing.PreTest != nil { + if testing != nil && testing.PreTest != nil && testing.PreTest.ClusterID != "" && testing.PreTest.Namespace != "" { options.ClusterID = testing.PreTest.ClusterID options.Namespace = testing.PreTest.Namespace } -- Gitee From 02fc98a87e426cb6fce58eec9b206082ec0a0c89 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 12 Dec 2021 12:37:22 +0800 Subject: [PATCH 0988/2647] add annotation Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 33b43ff6..1cd19e6d 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -139,6 +139,7 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op Targets: []string{options.ServiceName}, }) } + // Compatible with the situation where the old data has not been modified if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" && build.PreBuild.Namespace != "" { options.ClusterID = build.PreBuild.ClusterID options.Namespace = build.PreBuild.Namespace @@ -157,6 +158,7 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, selector := getPipelineSelector(options) // get cluster ID testing, _ := commonrepo.NewTestingColl().Find(getTestName(options.ServiceName), "") + // Compatible with the situation where the old data has not been modified if testing != nil && testing.PreTest != nil && testing.PreTest.ClusterID != "" && testing.PreTest.Namespace != "" { options.ClusterID = testing.PreTest.ClusterID options.Namespace = testing.PreTest.Namespace -- Gitee From f1c3dcbac14c2007f3415490b10c2d9448510b26 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 14:27:12 +0800 Subject: [PATCH 0989/2647] add log Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 4 ++++ .../systemconfig/core/codehost/pkg/oauth/github/github.go | 4 +--- .../systemconfig/core/codehost/service/codehost.go | 6 ++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index b8608c71..6361ac1f 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,6 +17,7 @@ limitations under the License. package handler import ( + "fmt" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" @@ -79,6 +80,8 @@ func AuthCodeHost(c *gin.Context) { url, err := service.AuthCodeHost(c.Query("redirect_url"), c.Query("callback_url"), idInt, ctx.Logger) if err != nil { ctx.Err = err + ctx.Logger.Errorf("auth url : %s,err %s", url, err) + return } c.Redirect(http.StatusFound, url) } @@ -92,6 +95,7 @@ func Callback(c *gin.Context) { url, err := service.Callback(stateStr, c.Request, ctx.Logger) if err != nil { ctx.Logger.Errorf("Callback err:%s", err) + url = fmt.Sprintf("%s&err=%s", url, err) } c.Redirect(http.StatusFound, url) } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go index 31c769c0..79c53c6a 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go @@ -1,7 +1,6 @@ package github import ( - "context" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" "golang.org/x/oauth2" "golang.org/x/oauth2/github" @@ -62,6 +61,5 @@ func (g *Oauth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} } - oauth2Config := g.oauth2Config() - return oauth2Config.Exchange(context.Background(), q.Get("code")) + return g.oauth2Config().Exchange(r.Context(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 10e28c76..eec79e71 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -76,6 +76,7 @@ func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { type State struct { CodeHostID int `json:"code_host_id"` RedirectURL string `json:"redirect_url"` + CallbackURl string `json:"callback_u_rl"` } func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { @@ -92,6 +93,7 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge stateStruct := State{ CodeHostID: codeHost.ID, RedirectURL: redirectURI, + CallbackURl: codeHostCallbackURL, } bs, err := json.Marshal(stateStruct) if err != nil { @@ -115,10 +117,10 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st } codehost, err := GetCodeHost(state.CodeHostID, logger) if err != nil { - logger.Errorf("GetCodeHost err:%s", err) + logger.Errorf("GetCodeHost err:%s,state:%+v", err, state) return state.RedirectURL, err } - o, err := factory.Factory(codehost.Type, state.RedirectURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + o, err := factory.Factory(codehost.Type, state.CallbackURl, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { logger.Errorf("Factory err:%s", err) return state.RedirectURL, err -- Gitee From 0f5e31d3cdf10d3fb07a2c6402fb8c6449206170 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Mon, 13 Dec 2021 14:39:39 +0800 Subject: [PATCH 0990/2647] testing add webhook (#690) Signed-off-by: mouuii --- .../core/common/service/webhook/client.go | 1 + .../aslan/core/common/service/workflow.go | 11 + .../aslan/core/workflow/handler/webhook.go | 7 +- .../core/workflow/service/webhook/github.go | 36 ++++ .../service/webhook/github_testing_task.go | 190 ++++++++++++++++++ .../core/workflow/testing/service/testing.go | 11 + 6 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go diff --git a/pkg/microservice/aslan/core/common/service/webhook/client.go b/pkg/microservice/aslan/core/common/service/webhook/client.go index a51ee745..6cce34ad 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/client.go +++ b/pkg/microservice/aslan/core/common/service/webhook/client.go @@ -26,6 +26,7 @@ const ( PipelinePrefix = "pipeline-" ColliePrefix = "collie-" ServicePrefix = "service-" + TestingPrefix = "testing-" taskTimeoutSecond = 10 ) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 6592413e..fef94323 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -273,6 +273,17 @@ func toHookSet(hooks interface{}) HookSet { codeHostID: h.CodeHostID, }) } + case []*models.TestingHook: + for _, h := range hs { + res.Insert(hookItem{ + hookUniqueID: hookUniqueID{ + name: h.MainRepo.Name, + owner: h.MainRepo.RepoOwner, + repo: h.MainRepo.RepoName, + }, + codeHostID: h.MainRepo.CodehostID, + }) + } } return res diff --git a/pkg/microservice/aslan/core/workflow/handler/webhook.go b/pkg/microservice/aslan/core/workflow/handler/webhook.go index fb9b8a50..0a6dcea6 100644 --- a/pkg/microservice/aslan/core/workflow/handler/webhook.go +++ b/pkg/microservice/aslan/core/workflow/handler/webhook.go @@ -72,6 +72,11 @@ func processGithub(payload []byte, req *http.Request, requestID string, log *zap log.Errorf("error happens to trigger workflow %v", err) errs = multierror.Append(errs, err) } - + //测试管理webhook + err = webhook.ProcessGithubWebHookForTest(payload, req, requestID, log) + if err != nil { + log.Errorf("error happens to trigger ProcessGithubWebHookForTest %v", err) + errs = multierror.Append(errs, err) + } return errs.ErrorOrNil() } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 19553661..595710dc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -381,6 +381,42 @@ func pushEventCommitsFiles(e *github.PushEvent) []string { return files } + +func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger)error{ + hookType := github.WebHookType(req) + if hookType == "integration_installation" || hookType == "installation" || hookType == "ping" { + return nil + } + + err := validateSecret(payload, []byte(gitservice.GetHookSecret()), req) + if err != nil { + return err + } + + event, err := github.ParseWebHook(github.WebHookType(req), payload) + if err != nil { + return err + } + + switch et := event.(type) { + case *github.PullRequestEvent: + err = TriggerTestByGithubEvent(et, requestID, log) + if err != nil { + log.Errorf("TriggerTestByGithubEvent error: %v", err) + return e.ErrGithubWebHook.AddErr(err) + } + + case *github.PushEvent: + err = TriggerTestByGithubEvent(et, requestID, log) + if err != nil { + log.Infof("TriggerTestByGithubEvent error: %v", err) + return e.ErrGithubWebHook.AddErr(err) + } + } + return nil +} + + func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { forwardedProto := req.Header.Get("X-Forwarded-Proto") forwardedHost := req.Header.Get("X-Forwarded-Host") diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go new file mode 100644 index 00000000..fcd494c9 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go @@ -0,0 +1,190 @@ +package webhook + +import ( + "regexp" + "strconv" + + "github.com/google/go-github/v35/github" + "github.com/hashicorp/go-multierror" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + testingservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" + "github.com/koderover/zadig/pkg/setting" +) + +func TriggerTestByGithubEvent(event interface{}, requestID string, log *zap.SugaredLogger) error { + //1.find configured testing + testingList, err := commonrepo.NewTestingColl().List(&commonrepo.ListTestOption{}) + if err != nil { + log.Errorf("failed to list testing %v", err) + return err + } + mErr := &multierror.Error{} + diffSrv := func(pullRequestEvent *github.PullRequestEvent, codehostId int) ([]string, error) { + return findChangedFilesOfPullRequest(pullRequestEvent, codehostId) + } + for _, testing := range testingList { + if testing.HookCtl != nil && testing.HookCtl.Enabled { + for _, item := range testing.HookCtl.Items { + if item.TestArgs == nil { + continue + } + matcher := createGithubEventMatcherForTesting(event, diffSrv, testing, log) + if matcher == nil { + continue + } + if matches, err := matcher.Match(item.MainRepo); err != nil { + mErr = multierror.Append(err) + } else if matches { + log.Infof("event match hook %v of %s", item.MainRepo, testing.Name) + var mergeRequestID, commitID string + if ev, isPr := event.(*github.PullRequestEvent); isPr { + mergeRequestID = strconv.Itoa(*ev.PullRequest.Number) + commitID = *ev.PullRequest.Head.SHA + } + args := matcher.UpdateTaskArgs(item.TestArgs, requestID) + args.MergeRequestID = mergeRequestID + args.CommitID = commitID + args.Source = setting.SourceFromGitlab + args.CodehostID = item.MainRepo.CodehostID + args.RepoOwner = item.MainRepo.RepoOwner + args.RepoName = item.MainRepo.RepoName + if resp, err := testingservice.CreateTestTask(args, log); err != nil { + log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) + mErr = multierror.Append(mErr, err) + } else { + log.Infof("succeed to create task %v", resp) + } + } else { + log.Debugf("event not matches %v", item.MainRepo) + } + } + } + } + return mErr.ErrorOrNil() +} + +type githubPushEventMatcherForTesting struct { + log *zap.SugaredLogger + testing *commonmodels.Testing + event *github.PushEvent +} + +type githubMergeEventMatcherForTesting struct { + diffFunc githubPullRequestDiffFunc + log *zap.SugaredLogger + testing *commonmodels.Testing + event *github.PullRequestEvent +} + +func (gpem *githubPushEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gpem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + return false, nil + } + } + hookRepo.Branch = getBranchFromRef(*ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + + return MatchChanges(hookRepo, changedFiles), nil + } + + return false, nil +} + +func (gpem *githubPushEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gpem.testing, + reqID: requestID, + } + + factory.Update(args) + return args +} + +func createGithubEventMatcherForTesting( + event interface{}, diffSrv githubPullRequestDiffFunc, testing *commonmodels.Testing, log *zap.SugaredLogger, +) gitEventMatcherForTesting { + switch evt := event.(type) { + case *github.PushEvent: + return &githubPushEventMatcherForTesting{ + testing: testing, + log: log, + event: evt, + } + case *github.PullRequestEvent: + return &githubMergeEventMatcherForTesting{ + diffFunc: diffSrv, + log: log, + event: evt, + testing: testing, + } + } + + return nil +} + +func (gmem *githubMergeEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gmem.event + // TODO: match codehost + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.PullRequest.Base.Ref); !matched { + return false, nil + } + } + hookRepo.Branch = *ev.PullRequest.Base.Ref + + if *ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err + } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(hookRepo, changedFiles), nil + } + + } + return false, nil +} + +func (gmem *githubMergeEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gmem.testing, + reqID: requestID, + } + + args = factory.Update(args) + + return args +} diff --git a/pkg/microservice/aslan/core/workflow/testing/service/testing.go b/pkg/microservice/aslan/core/workflow/testing/service/testing.go index 2647c29c..78d6daf6 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/testing.go @@ -32,6 +32,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" @@ -52,6 +53,11 @@ func CreateTesting(username string, testing *commonmodels.Testing, log *zap.Suga return e.ErrCreateTestModule.AddErr(err) } + err = commonservice.ProcessWebhook(testing.HookCtl.Items,nil,webhook.TestingPrefix+testing.Name,log) + if err != nil { + return e.ErrUpdateTestModule.AddErr(err) + } + testing.UpdateBy = username err = commonrepo.NewTestingColl().Create(testing) if err != nil { @@ -112,6 +118,11 @@ func UpdateTesting(username string, testing *commonmodels.Testing, log *zap.Suga commonservice.EnsureSecretEnvs(existed.PreTest.Envs, testing.PreTest.Envs) } + err = commonservice.ProcessWebhook(testing.HookCtl.Items,existed.HookCtl.Items,webhook.TestingPrefix+testing.Name,log) + if err != nil { + return e.ErrUpdateTestModule.AddErr(err) + } + testing.UpdateBy = username testing.UpdateTime = time.Now().Unix() -- Gitee From be50101f5eb3b29445608fca68751af08acde4b9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 13 Dec 2021 16:24:20 +0800 Subject: [PATCH 0991/2647] fix pod debug bug Signed-off-by: liu deyi --- pkg/microservice/podexec/core/service/ws_terminal.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/podexec/core/service/ws_terminal.go b/pkg/microservice/podexec/core/service/ws_terminal.go index dfb805ec..003ad1f5 100644 --- a/pkg/microservice/podexec/core/service/ws_terminal.go +++ b/pkg/microservice/podexec/core/service/ws_terminal.go @@ -35,6 +35,7 @@ import ( "k8s.io/client-go/tools/remotecommand" conf "github.com/koderover/zadig/pkg/microservice/podexec/config" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -206,7 +207,7 @@ func NewKubeOutClusterClient(clusterID string) (kubernetes.Interface, *rest.Conf var config *rest.Config var err error - if clusterID == "" { + if clusterID == "" || clusterID == setting.LocalClusterID { kubeConfig := "" config, err = clientcmd.BuildConfigFromFlags("", kubeConfig) if err != nil { -- Gitee From 691dbcf2acb2d671b336ee10a28cc1927958cb98 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 17:11:17 +0800 Subject: [PATCH 0992/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 8 ++- .../{pkg => internal}/oauth/github/github.go | 53 +++++++++--------- .../{pkg => internal}/oauth/gitlab/gitlab.go | 55 ++++++++++--------- .../codehost/{pkg => internal}/oauth/oauth.go | 5 +- .../codehost/pkg/oauth/factory/factory.go | 18 ------ .../core/codehost/service/codehost.go | 28 +++++++--- 6 files changed, 85 insertions(+), 82 deletions(-) rename pkg/microservice/systemconfig/core/codehost/{pkg => internal}/oauth/github/github.go (40%) rename pkg/microservice/systemconfig/core/codehost/{pkg => internal}/oauth/gitlab/gitlab.go (40%) rename pkg/microservice/systemconfig/core/codehost/{pkg => internal}/oauth/oauth.go (88%) delete mode 100644 pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 6361ac1f..efdcfb6e 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -18,12 +18,14 @@ package handler import ( "fmt" + "net/http" + "strconv" + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" - "net/http" - "strconv" ) func CreateCodeHost(c *gin.Context) { @@ -80,7 +82,7 @@ func AuthCodeHost(c *gin.Context) { url, err := service.AuthCodeHost(c.Query("redirect_url"), c.Query("callback_url"), idInt, ctx.Logger) if err != nil { ctx.Err = err - ctx.Logger.Errorf("auth url : %s,err %s", url, err) + ctx.Logger.Errorf("auth url: %s,id:%d,err: %s", url, idInt, err) return } c.Redirect(http.StatusFound, url) diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go similarity index 40% rename from pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go rename to pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go index 79c53c6a..8ddc6153 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go @@ -1,65 +1,68 @@ package github import ( - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "fmt" + "net/http" + "golang.org/x/oauth2" "golang.org/x/oauth2/github" - "net/http" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" ) -type Oauth struct { - RedirectURI string `json:"redirectURI"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - HostName string `json:"host_name"` +type oAuth struct { + RedirectURI string + ClientID string + ClientSecret string + Address string } -func NewGithubOauth(callbackURL, clientID, clientSecret, hostName string) oauth.CallbackOauth { - return &Oauth{ +func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { + return &oAuth{ RedirectURI: callbackURL, ClientID: clientID, ClientSecret: clientSecret, - HostName: hostName, + Address: address, } } -func (g *Oauth) oauth2Config() *oauth2.Config { +func (o *oAuth) oauth2Config() *oauth2.Config { endpoint := github.Endpoint - if g.HostName != "" { + if o.Address != "" { endpoint = oauth2.Endpoint{ - AuthURL: g.HostName + "/login/oauth/authorize", - TokenURL: g.HostName + "/login/oauth/access_token", + AuthURL: o.Address + "/login/oauth/authorize", + TokenURL: o.Address + "/login/oauth/access_token", } } return &oauth2.Config{ - ClientID: g.ClientID, - ClientSecret: g.ClientSecret, + ClientID: o.ClientID, + ClientSecret: o.ClientSecret, Endpoint: endpoint, - RedirectURL: g.RedirectURI, + RedirectURL: o.RedirectURI, Scopes: []string{"repo", "user"}, } } type oauth2Error struct { - error string - errorDescription string + error string + description string } func (e *oauth2Error) Error() string { - if e.errorDescription == "" { + if e.description == "" { return e.error } - return e.error + ": " + e.errorDescription + return fmt.Sprintf("%s: %s", e.error, e.description) } -func (g *Oauth) LoginURL(state string) (loginURL string) { - return g.oauth2Config().AuthCodeURL(state) +func (o *oAuth) LoginURL(state string) string { + return o.oauth2Config().AuthCodeURL(state) } -func (g *Oauth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { +func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} } - return g.oauth2Config().Exchange(r.Context(), q.Get("code")) + return o.oauth2Config().Exchange(r.Context(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go similarity index 40% rename from pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go rename to pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index 2632a0d1..51ea1fa1 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -1,67 +1,68 @@ package gitlab import ( - "context" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" + "fmt" + "net/http" + "golang.org/x/oauth2" "golang.org/x/oauth2/gitlab" - "net/http" + + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" ) -type Oauth struct { - RedirectURI string `json:"redirectURI"` - ClientID string `json:"clientID"` - ClientSecret string `json:"clientSecret"` - HostName string `json:"host_name"` +type oAuth struct { + RedirectURI string + ClientID string + ClientSecret string + Address string } -func NewGitlabOauth(redirectURI, clientID, clientSecret, hostName string) oauth.CallbackOauth { - return &Oauth{ +func New(redirectURI, clientID, clientSecret, address string) oauth.Oauth { + return &oAuth{ RedirectURI: redirectURI, ClientID: clientID, ClientSecret: clientSecret, - HostName: hostName, + Address: address, } } -func (g *Oauth) oauth2Config() *oauth2.Config { +func (o *oAuth) oauth2Config() *oauth2.Config { endpoint := gitlab.Endpoint - if g.HostName != "" { + if o.Address != "" { endpoint = oauth2.Endpoint{ - AuthURL: g.HostName + "/oauth/authorize", - TokenURL: g.HostName + "/oauth/token", + AuthURL: o.Address + "/oauth/authorize", + TokenURL: o.Address + "/oauth/token", } } return &oauth2.Config{ - ClientID: g.ClientID, - ClientSecret: g.ClientSecret, + ClientID: o.ClientID, + ClientSecret: o.ClientSecret, Endpoint: endpoint, - RedirectURL: g.RedirectURI, + RedirectURL: o.RedirectURI, Scopes: []string{"api", "read_user"}, } } type oauth2Error struct { - error string - errorDescription string + error string + description string } func (e *oauth2Error) Error() string { - if e.errorDescription == "" { + if e.description == "" { return e.error } - return e.error + ": " + e.errorDescription + return fmt.Sprintf("%s: %s", e.error, e.description) } -func (g *Oauth) LoginURL(state string) (loginURL string) { - return g.oauth2Config().AuthCodeURL(state) +func (o *oAuth) LoginURL(state string) string { + return o.oauth2Config().AuthCodeURL(state) } -func (g *Oauth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { +func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} } - oauth2Config := g.oauth2Config() - return oauth2Config.Exchange(context.Background(), q.Get("code")) + return o.oauth2Config().Exchange(r.Context(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go similarity index 88% rename from pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go rename to pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index 2edd649b..31540bf0 100644 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -1,11 +1,12 @@ package oauth import ( - "golang.org/x/oauth2" "net/http" + + "golang.org/x/oauth2" ) -type CallbackOauth interface { +type Oauth interface { LoginURL(state string) (loginURL string) HandleCallback(r *http.Request) (token *oauth2.Token, err error) } diff --git a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go b/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go deleted file mode 100644 index 698d27f6..00000000 --- a/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory/factory.go +++ /dev/null @@ -1,18 +0,0 @@ -package factory - -import ( - "errors" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/github" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/gitlab" -) - -func Factory(provider, callbackURL, clientID, clientSecret, hostName string) (oauth.CallbackOauth, error) { - switch provider { - case "github": - return github.NewGithubOauth(callbackURL, clientID, clientSecret, hostName), nil - case "gitlab": - return gitlab.NewGitlabOauth(callbackURL, clientID, clientSecret, hostName), nil - } - return nil, errors.New("illegal provider") -} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index eec79e71..7cf74bb6 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -19,15 +19,19 @@ package service import ( "encoding/base64" "encoding/json" + "errors" "fmt" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/pkg/oauth/factory" "net/http" "time" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth/github" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { @@ -73,10 +77,10 @@ func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().GetCodeHostByID(id) } -type State struct { +type state struct { CodeHostID int `json:"code_host_id"` RedirectURL string `json:"redirect_url"` - CallbackURl string `json:"callback_u_rl"` + CallbackURl string `json:"callback_url"` } func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { @@ -85,12 +89,12 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err } - oauth, err := factory.Factory(codeHost.Type, codeHostCallbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + oauth, err := NewOAuth(codeHost.Type, codeHostCallbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err } - stateStruct := State{ + stateStruct := state{ CodeHostID: codeHost.ID, RedirectURL: redirectURI, CallbackURl: codeHostCallbackURL, @@ -103,6 +107,16 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil } +func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oauth.Oauth, error) { + switch provider { + case systemconfig.GitHubProvider: + return github.New(callbackURL, clientID, clientSecret, address), nil + case systemconfig.GitLabProvider: + return gitlab.New(callbackURL, clientID, clientSecret, address), nil + } + return nil, errors.New("illegal provider") +} + func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (string, error) { bs, err := base64.URLEncoding.DecodeString(stateQuery) if err != nil { @@ -110,7 +124,7 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st return "", err } - var state State + var state state if err := json.Unmarshal(bs, &state); err != nil { logger.Errorf("Unmarshal err:%s", err) return "", err @@ -120,7 +134,7 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st logger.Errorf("GetCodeHost err:%s,state:%+v", err, state) return state.RedirectURL, err } - o, err := factory.Factory(codehost.Type, state.CallbackURl, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + o, err := NewOAuth(codehost.Type, state.CallbackURl, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { logger.Errorf("Factory err:%s", err) return state.RedirectURL, err -- Gitee From 3377094504a79f828b4b0bd82e5be36da47e0d44 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 17:31:44 +0800 Subject: [PATCH 0993/2647] add sleep Signed-off-by: mouuii --- pkg/microservice/systemconfig/core/codehost/service/codehost.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 7cf74bb6..05d170e7 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -139,6 +139,7 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st logger.Errorf("Factory err:%s", err) return state.RedirectURL, err } + time.Sleep(time.Second * 30) token, err := o.HandleCallback(r) if err != nil { logger.Errorf("HandleCallback err:%s", err) -- Gitee From b7db3efd313c854e5fd1eef66201d3fe1b5a5d31 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 17:36:20 +0800 Subject: [PATCH 0994/2647] add sleep Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 05d170e7..f2335fa6 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -32,6 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { @@ -139,7 +140,9 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st logger.Errorf("Factory err:%s", err) return state.RedirectURL, err } + log.Info("----------") time.Sleep(time.Second * 30) + log.Info("----------") token, err := o.HandleCallback(r) if err != nil { logger.Errorf("HandleCallback err:%s", err) -- Gitee From 86c1d64fa45cb4890ff0f6aa4f2a27245a6854fb Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 18:44:08 +0800 Subject: [PATCH 0995/2647] log Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index f2335fa6..8d128b70 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -141,6 +141,10 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st return state.RedirectURL, err } log.Info("----------") + for k, v := range r.URL.Query() { + log.Infof("%v---%v", k, v) + } + time.Sleep(time.Second * 30) log.Info("----------") token, err := o.HandleCallback(r) -- Gitee From 352551ff123c1adc1a2db4140bbe2825ee06532c Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 18:50:31 +0800 Subject: [PATCH 0996/2647] log Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 8d128b70..680d0304 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -140,13 +140,9 @@ func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (st logger.Errorf("Factory err:%s", err) return state.RedirectURL, err } - log.Info("----------") for k, v := range r.URL.Query() { log.Infof("%v---%v", k, v) } - - time.Sleep(time.Second * 30) - log.Info("----------") token, err := o.HandleCallback(r) if err != nil { logger.Errorf("HandleCallback err:%s", err) -- Gitee From 3977e09ba7f0d4cfc0e00dc5bf15f3890814c55f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 19:11:59 +0800 Subject: [PATCH 0997/2647] refactor code Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 26 ++++++++++--- .../core/codehost/service/codehost.go | 38 +++++-------------- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index efdcfb6e..122028b5 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,6 +17,8 @@ limitations under the License. package handler import ( + "encoding/base64" + "encoding/json" "fmt" "net/http" "strconv" @@ -82,7 +84,7 @@ func AuthCodeHost(c *gin.Context) { url, err := service.AuthCodeHost(c.Query("redirect_url"), c.Query("callback_url"), idInt, ctx.Logger) if err != nil { ctx.Err = err - ctx.Logger.Errorf("auth url: %s,id:%d,err: %s", url, idInt, err) + ctx.Logger.Errorf("auth err,id:%d,err: %s", idInt, err) return } c.Redirect(http.StatusFound, url) @@ -92,14 +94,26 @@ func Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - stateStr := c.Query("state") - - url, err := service.Callback(stateStr, c.Request, ctx.Logger) + bs, err := base64.URLEncoding.DecodeString(c.Query("state")) if err != nil { + ctx.Err = err + ctx.Logger.Errorf("DecodeString err:%s", err) + return + } + + var state service.State + if err := json.Unmarshal(bs, &state); err != nil { + ctx.Logger.Errorf("Unmarshal err:%s", err) + ctx.Err = err + return + } + + if err := service.HandleCallback(state.CodeHostID, state.CallbackURl, c.Request, ctx.Logger); err != nil { ctx.Logger.Errorf("Callback err:%s", err) - url = fmt.Sprintf("%s&err=%s", url, err) + url := fmt.Sprintf("%s&err=%s", state.RedirectURL, err) + c.Redirect(http.StatusFound, url) } - c.Redirect(http.StatusFound, url) + c.Redirect(http.StatusFound, state.RedirectURL+"&success=true") } func UpdateCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 680d0304..0290428e 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -32,7 +32,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/tool/log" ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { @@ -78,7 +77,7 @@ func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().GetCodeHostByID(id) } -type state struct { +type State struct { CodeHostID int `json:"code_host_id"` RedirectURL string `json:"redirect_url"` CallbackURl string `json:"callback_url"` @@ -95,7 +94,7 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err } - stateStruct := state{ + stateStruct := State{ CodeHostID: codeHost.ID, RedirectURL: redirectURI, CallbackURl: codeHostCallbackURL, @@ -118,41 +117,24 @@ func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oa return nil, errors.New("illegal provider") } -func Callback(stateQuery string, r *http.Request, logger *zap.SugaredLogger) (string, error) { - bs, err := base64.URLEncoding.DecodeString(stateQuery) +func HandleCallback(codeHostID int, callbackURL string, r *http.Request, logger *zap.SugaredLogger) error { + codehost, err := GetCodeHost(codeHostID, logger) if err != nil { - logger.Errorf("DecodeString err:%s", err) - return "", err - } - - var state state - if err := json.Unmarshal(bs, &state); err != nil { - logger.Errorf("Unmarshal err:%s", err) - return "", err + return err } - codehost, err := GetCodeHost(state.CodeHostID, logger) + o, err := NewOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { - logger.Errorf("GetCodeHost err:%s,state:%+v", err, state) - return state.RedirectURL, err - } - o, err := NewOAuth(codehost.Type, state.CallbackURl, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) - if err != nil { - logger.Errorf("Factory err:%s", err) - return state.RedirectURL, err - } - for k, v := range r.URL.Query() { - log.Infof("%v---%v", k, v) + return err } token, err := o.HandleCallback(r) if err != nil { - logger.Errorf("HandleCallback err:%s", err) - return state.RedirectURL, err + return err } codehost.AccessToken = token.AccessToken codehost.RefreshToken = token.RefreshToken if _, err := UpdateCodeHostByToken(codehost, logger); err != nil { logger.Errorf("UpdateCodeHostByToken err:%s", err) - return state.RedirectURL, err + return err } - return state.RedirectURL, nil + return nil } -- Gitee From a89a47c59063eec0c04f46b9c9e6ccb0a66e0040 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 19:18:14 +0800 Subject: [PATCH 0998/2647] refactor code Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 122028b5..c43f0423 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -110,10 +110,10 @@ func Callback(c *gin.Context) { if err := service.HandleCallback(state.CodeHostID, state.CallbackURl, c.Request, ctx.Logger); err != nil { ctx.Logger.Errorf("Callback err:%s", err) - url := fmt.Sprintf("%s&err=%s", state.RedirectURL, err) + url := fmt.Sprintf("%s?err=%s", state.RedirectURL, err) c.Redirect(http.StatusFound, url) } - c.Redirect(http.StatusFound, state.RedirectURL+"&success=true") + c.Redirect(http.StatusFound, state.RedirectURL+"?success=true") } func UpdateCodeHost(c *gin.Context) { -- Gitee From 0d7803085c64ae79eb465c7929aa5566ab053859 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 13 Dec 2021 19:45:03 +0800 Subject: [PATCH 0999/2647] encrypt Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 9 +++++---- .../systemconfig/core/codehost/service/codehost.go | 8 +++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index c43f0423..3e5f978e 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,7 +17,6 @@ limitations under the License. package handler import ( - "encoding/base64" "encoding/json" "fmt" "net/http" @@ -28,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/crypto" ) func CreateCodeHost(c *gin.Context) { @@ -94,15 +94,16 @@ func Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - bs, err := base64.URLEncoding.DecodeString(c.Query("state")) + aes, err := crypto.NewAes(crypto.GetAesKey()) + decrypted, err := aes.Decrypt(c.Query("state")) if err != nil { + ctx.Logger.Errorf("Decrypt err:%s", err) ctx.Err = err - ctx.Logger.Errorf("DecodeString err:%s", err) return } var state service.State - if err := json.Unmarshal(bs, &state); err != nil { + if err := json.Unmarshal([]byte(decrypted), &state); err != nil { ctx.Logger.Errorf("Unmarshal err:%s", err) ctx.Err = err return diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 0290428e..af00c736 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -32,6 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/crypto" ) func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { @@ -104,7 +105,12 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge logger.Errorf("Marshal err:%s", err) return "", err } - return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil + aes, err := crypto.NewAes(crypto.GetAesKey()) + encrypted, err := aes.Encrypt(string(bs)) + if err != nil { + return "", err + } + return oauth.LoginURL(encrypted), nil } func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oauth.Oauth, error) { -- Gitee From d39bb371ec7abdd03aced5c06e9155a0379c7a85 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 13 Dec 2021 19:50:15 +0800 Subject: [PATCH 1000/2647] fix copy environment objectid Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 06146080..890b1982 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -30,6 +30,7 @@ import ( "github.com/hashicorp/go-multierror" helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/strvals" @@ -828,7 +829,7 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH if err != nil { return fmt.Errorf("failed to query base product info, name %s", arg.BaseEnvName) } - + productInfo.ID = primitive.NilObjectID productInfo.Revision = 1 productInfo.EnvName = arg.EnvName productInfo.UpdateBy = userName -- Gitee From 59e88f1782aa540dafe2e183b79d7d1e3b4c55c6 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 13 Dec 2021 22:34:38 +0800 Subject: [PATCH 1001/2647] fix copyenv render name Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 890b1982..cfd21efb 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -857,6 +857,16 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH } } + productInfo.Render = nil + if len(productInfo.Services) > 0 { + for idxm, svrInfo := range productInfo.Services { + for idxn, svr := range svrInfo { + if svr.Render != nil { + productInfo.Services[idxm][idxn].Render = nil + } + } + } + } // default values defaultValuesYaml := arg.DefaultValues -- Gitee From 6d4db53b5924c5bcebd68c282e633ecc849c040c Mon Sep 17 00:00:00 2001 From: lou Date: Tue, 14 Dec 2021 10:29:01 +0800 Subject: [PATCH 1002/2647] add json tag for github config Signed-off-by: lou --- .../core/service/types/task/config_payload.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 82ee8535..61f65238 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -88,15 +88,15 @@ type S3Config struct { type GithubConfig struct { // github API access token - AccessToken string + AccessToken string `json:"access_token"` // github ssh key with base64 encoded - SSHKey string + SSHKey string `json:"ssh_key"` // github knownhost - KnownHost string + KnownHost string `json:"known_host"` // github app private key - AppKey string + AppKey string `json:"app_key"` // gihhub app id - AppID int + AppID int `json:"app_id"` } type GitlabConfig struct { -- Gitee From 68e19f97b753eb7a65474a6214b410fb50d0e0ea Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 14 Dec 2021 10:32:27 +0800 Subject: [PATCH 1003/2647] rm check array Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index cfd21efb..038d95f3 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -858,15 +858,14 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH } productInfo.Render = nil - if len(productInfo.Services) > 0 { - for idxm, svrInfo := range productInfo.Services { - for idxn, svr := range svrInfo { - if svr.Render != nil { - productInfo.Services[idxm][idxn].Render = nil - } + for idxm, svrInfo := range productInfo.Services { + for idxn, svr := range svrInfo { + if svr.Render != nil { + productInfo.Services[idxm][idxn].Render = nil } } } + // default values defaultValuesYaml := arg.DefaultValues -- Gitee From 8569f24109c223e64ad87d007aa0d6dc1b48ccd6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 18:34:38 +0800 Subject: [PATCH 1004/2647] code refactor Signed-off-by: mouuii --- .../systemconfig/core/codehost/handler/codehost.go | 2 +- .../systemconfig/core/codehost/internal/oauth/oauth.go | 5 ----- .../systemconfig/core/codehost/service/codehost.go | 4 ++-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 3e5f978e..6eb065f0 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -109,7 +109,7 @@ func Callback(c *gin.Context) { return } - if err := service.HandleCallback(state.CodeHostID, state.CallbackURl, c.Request, ctx.Logger); err != nil { + if err := service.HandleCallback(state.CodeHostID, state.CallbackURL, c.Request, ctx.Logger); err != nil { ctx.Logger.Errorf("Callback err:%s", err) url := fmt.Sprintf("%s?err=%s", state.RedirectURL, err) c.Redirect(http.StatusFound, url) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index 31540bf0..9d85af48 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -10,8 +10,3 @@ type Oauth interface { LoginURL(state string) (loginURL string) HandleCallback(r *http.Request) (token *oauth2.Token, err error) } - -type Token struct { - AccessToken string - RefreshToken string -} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index af00c736..b522a8b6 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -81,7 +81,7 @@ func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { type State struct { CodeHostID int `json:"code_host_id"` RedirectURL string `json:"redirect_url"` - CallbackURl string `json:"callback_url"` + CallbackURL string `json:"callback_url"` } func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { @@ -98,7 +98,7 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge stateStruct := State{ CodeHostID: codeHost.ID, RedirectURL: redirectURI, - CallbackURl: codeHostCallbackURL, + CallbackURL: codeHostCallbackURL, } bs, err := json.Marshal(stateStruct) if err != nil { -- Gitee From 750fb064f868d15fb0f9ecfebf3143cd42e454f9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 18:47:52 +0800 Subject: [PATCH 1005/2647] code refactor Signed-off-by: mouuii --- .../codehost/internal/oauth/github/github.go | 38 ++++++---------- .../codehost/internal/oauth/gitlab/gitlab.go | 44 +++++++------------ 2 files changed, 31 insertions(+), 51 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go index 8ddc6153..ffb752ed 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go @@ -11,35 +11,25 @@ import ( ) type oAuth struct { - RedirectURI string - ClientID string - ClientSecret string - Address string + oauth2Config *oauth2.Config } func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { - return &oAuth{ - RedirectURI: callbackURL, - ClientID: clientID, - ClientSecret: clientSecret, - Address: address, - } -} - -func (o *oAuth) oauth2Config() *oauth2.Config { endpoint := github.Endpoint - if o.Address != "" { + if address != "" { endpoint = oauth2.Endpoint{ - AuthURL: o.Address + "/login/oauth/authorize", - TokenURL: o.Address + "/login/oauth/access_token", + AuthURL: address + "/login/oauth/authorize", + TokenURL: address + "/login/oauth/access_token", } } - return &oauth2.Config{ - ClientID: o.ClientID, - ClientSecret: o.ClientSecret, - Endpoint: endpoint, - RedirectURL: o.RedirectURI, - Scopes: []string{"repo", "user"}, + return &oAuth{ + oauth2Config: &oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + Endpoint: endpoint, + RedirectURL: callbackURL, + Scopes: []string{"repo", "user"}, + }, } } @@ -56,7 +46,7 @@ func (e *oauth2Error) Error() string { } func (o *oAuth) LoginURL(state string) string { - return o.oauth2Config().AuthCodeURL(state) + return o.oauth2Config.AuthCodeURL(state) } func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { @@ -64,5 +54,5 @@ func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} } - return o.oauth2Config().Exchange(r.Context(), q.Get("code")) + return o.oauth2Config.Exchange(r.Context(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index 51ea1fa1..987477d8 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -5,41 +5,31 @@ import ( "net/http" "golang.org/x/oauth2" - "golang.org/x/oauth2/gitlab" + "golang.org/x/oauth2/github" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" ) type oAuth struct { - RedirectURI string - ClientID string - ClientSecret string - Address string + oauth2Config *oauth2.Config } -func New(redirectURI, clientID, clientSecret, address string) oauth.Oauth { - return &oAuth{ - RedirectURI: redirectURI, - ClientID: clientID, - ClientSecret: clientSecret, - Address: address, - } -} - -func (o *oAuth) oauth2Config() *oauth2.Config { - endpoint := gitlab.Endpoint - if o.Address != "" { +func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { + endpoint := github.Endpoint + if address != "" { endpoint = oauth2.Endpoint{ - AuthURL: o.Address + "/oauth/authorize", - TokenURL: o.Address + "/oauth/token", + AuthURL: address + "/oauth/authorize", + TokenURL: address + "/oauth/token", } } - return &oauth2.Config{ - ClientID: o.ClientID, - ClientSecret: o.ClientSecret, - Endpoint: endpoint, - RedirectURL: o.RedirectURI, - Scopes: []string{"api", "read_user"}, + return &oAuth{ + oauth2Config: &oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + Endpoint: endpoint, + RedirectURL: callbackURL, + Scopes: []string{"api", "read_user"}, + }, } } @@ -56,7 +46,7 @@ func (e *oauth2Error) Error() string { } func (o *oAuth) LoginURL(state string) string { - return o.oauth2Config().AuthCodeURL(state) + return o.oauth2Config.AuthCodeURL(state) } func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { @@ -64,5 +54,5 @@ func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} } - return o.oauth2Config().Exchange(r.Context(), q.Get("code")) + return o.oauth2Config.Exchange(r.Context(), q.Get("code")) } -- Gitee From 5e1b9c5385586b6e10324cd8bfedddb65116cee5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 19:10:57 +0800 Subject: [PATCH 1006/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 24 ++--------- .../core/codehost/service/codehost.go | 42 +++++++++++++++---- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 6eb065f0..1c0d16ed 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,8 +17,6 @@ limitations under the License. package handler import ( - "encoding/json" - "fmt" "net/http" "strconv" @@ -27,7 +25,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" - "github.com/koderover/zadig/pkg/tool/crypto" ) func CreateCodeHost(c *gin.Context) { @@ -94,27 +91,14 @@ func Callback(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - aes, err := crypto.NewAes(crypto.GetAesKey()) - decrypted, err := aes.Decrypt(c.Query("state")) + state := c.Query("state") + redirectURL, err := service.HandleCallback(state, c.Request, ctx.Logger) if err != nil { - ctx.Logger.Errorf("Decrypt err:%s", err) - ctx.Err = err - return - } - - var state service.State - if err := json.Unmarshal([]byte(decrypted), &state); err != nil { - ctx.Logger.Errorf("Unmarshal err:%s", err) + ctx.Logger.Errorf("Callback err:%s", err) ctx.Err = err return } - - if err := service.HandleCallback(state.CodeHostID, state.CallbackURL, c.Request, ctx.Logger); err != nil { - ctx.Logger.Errorf("Callback err:%s", err) - url := fmt.Sprintf("%s?err=%s", state.RedirectURL, err) - c.Redirect(http.StatusFound, url) - } - c.Redirect(http.StatusFound, state.RedirectURL+"?success=true") + c.Redirect(http.StatusFound, redirectURL) } func UpdateCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index b522a8b6..fd1b2113 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "time" "go.uber.org/zap" @@ -123,24 +124,49 @@ func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oa return nil, errors.New("illegal provider") } -func HandleCallback(codeHostID int, callbackURL string, r *http.Request, logger *zap.SugaredLogger) error { - codehost, err := GetCodeHost(codeHostID, logger) +func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { + aes, err := crypto.NewAes(crypto.GetAesKey()) + decrypted, err := aes.Decrypt(stateStr) + if err != nil { + logger.Errorf("Decrypt err:%s", err) + return "", err + } + + var state State + if err := json.Unmarshal([]byte(decrypted), &state); err != nil { + logger.Errorf("Unmarshal err:%s", err) + return "", err + } + codehost, err := GetCodeHost(state.CodeHostID, logger) if err != nil { - return err + return handle(state.RedirectURL, err) } - o, err := NewOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + o, err := NewOAuth(codehost.Type, state.CallbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { - return err + return handle(state.RedirectURL, err) } token, err := o.HandleCallback(r) if err != nil { - return err + return handle(state.RedirectURL, err) } codehost.AccessToken = token.AccessToken codehost.RefreshToken = token.RefreshToken if _, err := UpdateCodeHostByToken(codehost, logger); err != nil { logger.Errorf("UpdateCodeHostByToken err:%s", err) - return err + return handle(state.RedirectURL, err) + } + return handle(state.RedirectURL, nil) +} + +func handle(redirectURL string, err error) (string, error) { + u, parseErr := url.Parse(redirectURL) + if parseErr != nil { + return "", parseErr + } + if err != nil { + u.Query().Add("err", err.Error()) + } else { + u.Query().Add("success", "true") } - return nil + return u.String(), nil } -- Gitee From e6de2789bea1e7c35fc5ca08b9f380e556ceac19 Mon Sep 17 00:00:00 2001 From: dyliu Date: Tue, 14 Dec 2021 19:18:03 +0800 Subject: [PATCH 1007/2647] externally exposed artifact interface (#738) * externally exposed deliverable interface Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * fix import cycle not allowed problem Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * modify return field Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * modify function name Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- .../repository/mongodb/delivery_artifact.go | 17 +++-- .../aslan/core/delivery/handler/artifact.go | 17 ++++- .../aslan/core/delivery/handler/router.go | 1 + .../aslan/core/delivery/service/artifact.go | 9 +++ .../picket/client/aslan/artifact.go | 31 ++++++++ .../picket/client/aslan/delivery.go | 25 +++++++ .../picket/client/aslan/workflow.go | 11 --- .../picket/core/public/handler/public.go | 7 ++ .../picket/core/public/handler/router.go | 1 + .../picket/core/public/service/public.go | 72 +++++++++++++++++++ 10 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 pkg/microservice/picket/client/aslan/artifact.go create mode 100644 pkg/microservice/picket/client/aslan/delivery.go diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index e0e0cc75..a7fb5ebc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -140,13 +140,20 @@ func (c *DeliveryArtifactColl) Get(args *DeliveryArtifactArgs) (*models.Delivery return nil, errors.New("nil delivery_artifact args") } resp := new(models.DeliveryArtifact) - id, err := primitive.ObjectIDFromHex(args.ID) - if err != nil { - return nil, err + query := bson.M{} + if args.ID != "" { + id, err := primitive.ObjectIDFromHex(args.ID) + if err != nil { + return nil, err + } + query["_id"] = id + } + + if args.Image != "" { + query["image"] = args.Image } - query := bson.M{"_id": id} - err = c.FindOne(context.TODO(), query).Decode(&resp) + err := c.FindOne(context.TODO(), query).Decode(&resp) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/delivery/handler/artifact.go b/pkg/microservice/aslan/core/delivery/handler/artifact.go index a8eed0c3..2e84ec66 100644 --- a/pkg/microservice/aslan/core/delivery/handler/artifact.go +++ b/pkg/microservice/aslan/core/delivery/handler/artifact.go @@ -77,6 +77,22 @@ func ListDeliveryArtifacts(c *gin.Context) { ctx.Resp, ctx.Err = artifacts, err } +func GetDeliveryArtifactIDByImage(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + image := c.Query("image") + if image == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("image can't be empty!") + return + } + args := &commonrepo.DeliveryArtifactArgs{ + Image: image, + } + + ctx.Resp, ctx.Err = deliveryservice.GetDeliveryArtifactIDByImage(args, ctx.Logger) +} + func GetDeliveryArtifact(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -89,7 +105,6 @@ func GetDeliveryArtifact(c *gin.Context) { args := &commonrepo.DeliveryArtifactArgs{ ID: id, } - args.ID = id ctx.Resp, ctx.Err = deliveryservice.GetDeliveryArtifact(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 7b58bcad..11141b24 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -30,6 +30,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { deliveryArtifact.GET("", ListDeliveryArtifacts) deliveryArtifact.GET("/:id", GetDeliveryArtifact) + deliveryArtifact.GET("/image", GetDeliveryArtifactIDByImage) deliveryArtifact.POST("", CreateDeliveryArtifacts) deliveryArtifact.POST("/:id", UpdateDeliveryArtifact) deliveryArtifact.POST("/:id/activities", CreateDeliveryActivities) diff --git a/pkg/microservice/aslan/core/delivery/service/artifact.go b/pkg/microservice/aslan/core/delivery/service/artifact.go index ac941e49..cf8ee968 100644 --- a/pkg/microservice/aslan/core/delivery/service/artifact.go +++ b/pkg/microservice/aslan/core/delivery/service/artifact.go @@ -168,3 +168,12 @@ func getDeliveryActivitiesMap(deliveryActivities []*commonmodels.DeliveryActivit } return artifactMapInfo } + +func GetDeliveryArtifactIDByImage(deliveryArtifactArgs *commonrepo.DeliveryArtifactArgs, log *zap.SugaredLogger) (string, error) { + deliveryArtifact, err := commonrepo.NewDeliveryArtifactColl().Get(deliveryArtifactArgs) + if err != nil { + log.Errorf("get deliveryArtifact error: %s", err) + return "", e.ErrFindArtifact.AddErr(err) + } + return deliveryArtifact.ID.Hex(), nil +} diff --git a/pkg/microservice/picket/client/aslan/artifact.go b/pkg/microservice/picket/client/aslan/artifact.go new file mode 100644 index 00000000..9a5042f9 --- /dev/null +++ b/pkg/microservice/picket/client/aslan/artifact.go @@ -0,0 +1,31 @@ +package aslan + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) GetArtifactByImage(header http.Header, qs url.Values, image string) ([]byte, error) { + url := fmt.Sprintf("/delivery/artifacts/image") + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetQueryParam(image, image)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} + +func (c *Client) GetArtifactInfo(header http.Header, qs url.Values, id string) ([]byte, error) { + url := fmt.Sprintf("/delivery/artifacts/%s", id) + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/client/aslan/delivery.go b/pkg/microservice/picket/client/aslan/delivery.go new file mode 100644 index 00000000..58e11011 --- /dev/null +++ b/pkg/microservice/picket/client/aslan/delivery.go @@ -0,0 +1,25 @@ +package aslan + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) ListDelivery(header http.Header, qs url.Values, productName, workflowName, taskId, perPage, page string) ([]byte, error) { + url := fmt.Sprintf("/delivery/releases") + queryParams := make(map[string]string) + queryParams["productName"] = productName + queryParams["workflowName"] = workflowName + queryParams["taskId"] = taskId + queryParams["per_page"] = perPage + queryParams["page"] = page + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetQueryParams(queryParams)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index b1a50012..40bd045d 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -90,14 +90,3 @@ func (c *Client) ListWorkflowTask(header http.Header, qs url.Values, commitId st return res.Body(), nil } - -func (c *Client) ListDelivery(header http.Header, qs url.Values, productName, workflowName, taskId, perPage, page string) ([]byte, error) { - url := fmt.Sprintf("/delivery/releases?orgId=1&productName=%s&workflowName=%s&taskId=%s&per_page=%s&page=%s", productName, workflowName, taskId, perPage, page) - - res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) - if err != nil { - return nil, err - } - - return res.Body(), nil -} diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 17c209e3..78280759 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -123,3 +123,10 @@ func ListDelivery(c *gin.Context) { pageStr := c.Query("page") ctx.Resp, ctx.Err = service.ListDelivery(c.Request.Header, c.Request.URL.Query(), productName, workflowName, taskIDStr, perPageStr, pageStr, ctx.Logger) } + +func GetArtifactInfo(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + image := c.Query("image") + ctx.Resp, ctx.Err = service.GetArtifactInfo(c.Request.Header, c.Request.URL.Query(), image, ctx.Logger) +} diff --git a/pkg/microservice/picket/core/public/handler/router.go b/pkg/microservice/picket/core/public/handler/router.go index a4eeca51..d6fd88dd 100644 --- a/pkg/microservice/picket/core/public/handler/router.go +++ b/pkg/microservice/picket/core/public/handler/router.go @@ -31,5 +31,6 @@ func (*Router) Inject(router *gin.RouterGroup) { dev.POST("/workflowTask/id/:id/pipelines/:name/restart", RestartWorkflowTask) dev.GET("/workflowTask", ListWorkflowTask) dev.GET("/dc/releases", ListDelivery) + dev.GET("/dc/artifact", GetArtifactInfo) } } diff --git a/pkg/microservice/picket/core/public/service/public.go b/pkg/microservice/picket/core/public/service/public.go index f3d9ef9e..632d96e0 100644 --- a/pkg/microservice/picket/core/public/service/public.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -17,6 +17,8 @@ limitations under the License. package service import ( + "encoding/json" + "fmt" "net/http" "net/url" @@ -44,3 +46,73 @@ func ListWorkflowTask(header http.Header, qs url.Values, commitId string, _ *zap func ListDelivery(header http.Header, qs url.Values, productName string, workflowName string, taskId string, perPage string, page string, _ *zap.SugaredLogger) ([]byte, error) { return aslan.New().ListDelivery(header, qs, productName, workflowName, taskId, perPage, page) } + +type DeliveryArtifact struct { + Name string `json:"name"` + Type string `json:"type"` + Source string `json:"source"` + Image string `json:"image,omitempty"` + ImageHash string `json:"image_hash,omitempty"` + ImageTag string `json:"image_tag"` + ImageDigest string `json:"image_digest,omitempty"` + ImageSize int64 `json:"image_size,omitempty"` + Architecture string `json:"architecture,omitempty"` + Os string `json:"os,omitempty"` + PackageFileLocation string `json:"package_file_location,omitempty"` + PackageStorageURI string `json:"package_storage_uri,omitempty"` + CreatedBy string `json:"created_by"` + CreatedTime int64 `json:"created_time"` +} + +type DeliveryActivity struct { + Type string `json:"type"` + Content string `json:"content,omitempty"` + URL string `json:"url,omitempty"` + Commits []*ActivityCommit `json:"commits,omitempty"` + Issues []string `json:"issues,omitempty"` + Namespace string `json:"namespace,omitempty"` + EnvName string `json:"env_name,omitempty"` + PublishHosts []string `json:"publish_hosts,omitempty"` + PublishNamespaces []string `json:"publish_namespaces,omitempty"` + RemoteFileKey string `json:"remote_file_key,omitempty"` + DistStorageURL string `json:"dist_storage_url,omitempty"` + SrcStorageURL string `json:"src_storage_url,omitempty"` + StartTime int64 `json:"start_time,omitempty"` + EndTime int64 `json:"end_time,omitempty"` + CreatedBy string `json:"created_by"` + CreatedTime int64 `json:"created_time"` +} + +type ActivityCommit struct { + Address string `json:"address"` + Source string `json:"source,omitempty"` + RepoOwner string `json:"repo_owner"` + RepoName string `json:"repo_name"` + Branch string `json:"branch"` + PR int `json:"pr,omitempty"` + Tag string `json:"tag,omitempty"` + CommitID string `json:"commit_id,omitempty"` + CommitMessage string `json:"commit_message,omitempty"` + AuthorName string `json:"author_name,omitempty"` +} + +type DeliveryArtifactInfo struct { + *DeliveryArtifact + DeliveryActivities []*DeliveryActivity `json:"activities"` + DeliveryActivitiesMap map[string][]*DeliveryActivity `json:"sortedActivities,omitempty"` +} + +func GetArtifactInfo(header http.Header, qs url.Values, image string, _ *zap.SugaredLogger) (*DeliveryArtifactInfo, error) { + artifactID, err := aslan.New().GetArtifactByImage(header, qs, image) + if err != nil { + return nil, fmt.Errorf("failed to get artifact by image err:%s", err) + } + + body, err := aslan.New().GetArtifactInfo(header, qs, string(artifactID)) + var deliveryArtifactInfo *DeliveryArtifactInfo + if err = json.Unmarshal(body, &deliveryArtifactInfo); err != nil { + return nil, fmt.Errorf("failed to unmarshal deliveryArtifactInfo err:%s", err) + } + + return deliveryArtifactInfo, nil +} -- Gitee From b70aee7d86e519dd597129dfde5d5a0226cb732d Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 19:26:46 +0800 Subject: [PATCH 1008/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/handler/codehost.go | 2 +- .../core/codehost/service/codehost.go | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 1c0d16ed..4715f4f0 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -78,7 +78,7 @@ func AuthCodeHost(c *gin.Context) { ctx.Err = err return } - url, err := service.AuthCodeHost(c.Query("redirect_url"), c.Query("callback_url"), idInt, ctx.Logger) + url, err := service.AuthCodeHost(c.Query("redirect_url"), idInt, ctx.Logger) if err != nil { ctx.Err = err ctx.Logger.Errorf("auth err,id:%d,err: %s", idInt, err) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index fd1b2113..9e13f111 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -82,16 +82,21 @@ func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { type State struct { CodeHostID int `json:"code_host_id"` RedirectURL string `json:"redirect_url"` - CallbackURL string `json:"callback_url"` } -func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { +func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { codeHost, err := GetCodeHost(codeHostID, logger) if err != nil { logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err } - oauth, err := NewOAuth(codeHost.Type, codeHostCallbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + redirectParsedURL, err := url.Parse(redirectURL) + if err != nil { + logger.Errorf("Parse:%s err:%s", codeHostID, err) + return "", err + } + callbackURL := fmt.Sprintf("%s://%s/api/directory/codehosts/callback", redirectParsedURL.Scheme, redirectParsedURL.Host) + oauth, err := NewOAuth(codeHost.Type, callbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err @@ -99,7 +104,6 @@ func AuthCodeHost(redirectURI, codeHostCallbackURL string, codeHostID int, logge stateStruct := State{ CodeHostID: codeHost.ID, RedirectURL: redirectURI, - CallbackURL: codeHostCallbackURL, } bs, err := json.Marshal(stateStruct) if err != nil { @@ -141,7 +145,13 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) if err != nil { return handle(state.RedirectURL, err) } - o, err := NewOAuth(codehost.Type, state.CallbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + redirectParsedURL, err := url.Parse(state.RedirectURL) + if err != nil { + logger.Errorf("ParseURL:%s err:%s", state.RedirectURL, err) + return "", err + } + callbackURL := fmt.Sprintf("%s://%s/api/directory/codehosts/callback", redirectParsedURL.Scheme, redirectParsedURL.Host) + o, err := NewOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { return handle(state.RedirectURL, err) } -- Gitee From c0dfa02ba0580e716aac54d228255ec847ca9b9f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 19:33:22 +0800 Subject: [PATCH 1009/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/internal/oauth/gitlab/gitlab.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index 987477d8..a7168e3f 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -5,7 +5,7 @@ import ( "net/http" "golang.org/x/oauth2" - "golang.org/x/oauth2/github" + "golang.org/x/oauth2/gitlab" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" ) @@ -15,7 +15,7 @@ type oAuth struct { } func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { - endpoint := github.Endpoint + endpoint := gitlab.Endpoint if address != "" { endpoint = oauth2.Endpoint{ AuthURL: address + "/oauth/authorize", -- Gitee From 92e4e4ae89981e32cf65ad16d1e5a9f23fc8bdd6 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 14 Dec 2021 20:19:56 +0800 Subject: [PATCH 1010/2647] workflowtask (#736) * workflowtask Signed-off-by: mouuii * code refactor Signed-off-by: mouuii * code refactor Signed-off-by: mouuii --- .../core/common/repository/models/workflow.go | 1 + .../workflow/service/workflow/workflow_task.go | 14 +++++++++----- .../picket/core/public/handler/public.go | 18 +++++++++++++++++- .../picket/core/public/handler/router.go | 2 +- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3cd5e836..714dbef3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -168,6 +168,7 @@ type WorkflowTaskArgs struct { // 请求模式,openAPI表示外部客户调用 RequestMode string `json:"request_mode,omitempty"` IsParallel bool `json:"is_parallel" bson:"is_parallel"` + EnvName string `json:"env_name" bson:"-"` } type TestTaskArgs struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index a0752500..360b76b1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -451,17 +451,21 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } } - productTempl, err := template.NewProductColl().Find(args.ProductTmplName) + workflow, err := commonrepo.NewWorkflowColl().Find(args.WorkflowName) if err != nil { - log.Errorf("productTempl.Find error: %v", err) + log.Errorf("Workflow.Find error: %v", err) return nil, e.ErrFindWorkflow.AddDesc(err.Error()) } - workflow, err := commonrepo.NewWorkflowColl().Find(args.WorkflowName) + project, err := template.NewProductColl().Find(workflow.ProductTmplName) if err != nil { - log.Errorf("Workflow.Find error: %v", err) + log.Errorf("project.Find error: %v", err) return nil, e.ErrFindWorkflow.AddDesc(err.Error()) } + // developer don't pass args.ProductTmplName + if args.ProductTmplName == "" { + args.ProductTmplName = workflow.ProductTmplName + } args.IsParallel = workflow.IsParallel var env *commonmodels.Product @@ -563,7 +567,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, if deployEnv.Type == setting.PMDeployType { continue } - deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) + deployTask, err := deployEnvToSubTasks(deployEnv, env, project.Timeout) if err != nil { log.Errorf("deploy env to subtask error: %v", err) return nil, e.ErrCreateTask.AddErr(err) diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 78280759..602ee5e3 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -22,6 +22,7 @@ import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/picket/core/public/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -29,10 +30,24 @@ import ( func CreateWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - body, _ := c.GetRawData() + req := new(models.WorkflowTaskArgs) + if err := c.ShouldBindJSON(req); err != nil { + ctx.Err = err + ctx.Logger.Errorf("ShouldBindJSON err:%s", err) + return + } + req.Namespace = req.EnvName + req.RequestMode = "openAPI" + body, err := json.Marshal(req) + if err != nil { + ctx.Err = err + ctx.Logger.Errorf("Marshal err:%s", err) + return + } res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) if err != nil { ctx.Err = err + ctx.Logger.Errorf("CreateWorkflowTask err:%s", err) return } // to avoid customer feel confused ,return workflow_name instead of pipline_name @@ -40,6 +55,7 @@ func CreateWorkflowTask(c *gin.Context) { err = json.Unmarshal(res, &resp) if err != nil { ctx.Err = err + ctx.Logger.Errorf("Unmarshal err:%s", err) return } resp.WorkflowName = resp.PipelineName diff --git a/pkg/microservice/picket/core/public/handler/router.go b/pkg/microservice/picket/core/public/handler/router.go index d6fd88dd..550478ab 100644 --- a/pkg/microservice/picket/core/public/handler/router.go +++ b/pkg/microservice/picket/core/public/handler/router.go @@ -27,7 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { dev := router.Group("") { dev.POST("/workflowTask/create", CreateWorkflowTask) - dev.DELETE("workflowTask/id/:id/pipelines/:name/cancel", CancelWorkflowTask) + dev.POST("workflowTask/id/:id/pipelines/:name/cancel", CancelWorkflowTask) dev.POST("/workflowTask/id/:id/pipelines/:name/restart", RestartWorkflowTask) dev.GET("/workflowTask", ListWorkflowTask) dev.GET("/dc/releases", ListDelivery) -- Gitee From 93a045708a58908301108388bf63ab9b6d62d9f2 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 14 Dec 2021 20:44:25 +0800 Subject: [PATCH 1011/2647] roll back an image Signed-off-by: ddh27 --- .../service/workflow/pipeline_validation.go | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 1c33ddcc..c8a06270 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -52,6 +52,8 @@ import ( "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" + "github.com/koderover/zadig/pkg/util/converter" + "github.com/koderover/zadig/pkg/util/yaml" ) const ( @@ -718,13 +720,65 @@ func (c *ImageIllegal) Error() string { // find currently using image for services deployed by helm func findCurrentlyUsingImage(productInfo *commonmodels.Product, serviceName, containerName string) (string, error) { + image := "" + var imagePathSpec *commonmodels.ImagePathSpec + var render *commonmodels.RenderInfo for _, service := range productInfo.GetServiceMap() { if service.ServiceName == serviceName { for _, container := range service.Containers { if container.Name == containerName { - return container.Image, nil + image = container.Image + imagePathSpec = container.ImagePath } } + render = service.Render + break + } + } + + splitImage := strings.Split(image, ":") + if len(splitImage) != 2 { + return "", fmt.Errorf("failed to find image:%s format error", image) + } + if render == nil { + return image, nil + } + opt := &commonrepo.RenderSetFindOption{ + Name: render.Name, + Revision: render.Revision, + } + renderSetInfo, err := commonrepo.NewRenderSetColl().Find(opt) + if err != nil || renderSetInfo == nil { + return "", fmt.Errorf("failed to find renderSetInfo name:%s,revision:%v", opt.Name, opt.Revision) + } + + defaultValues := renderSetInfo.DefaultValues + for _, singleChart := range renderSetInfo.ChartInfos { + rca := &commonservice.RenderChartArg{} + rca.LoadFromRenderChartModel(singleChart) + if singleChart.ServiceName == serviceName { + for _, override := range rca.OverrideValues { + if override.Key == imagePathSpec.Tag { + return splitImage[0] + ":" + override.Value, nil + } + } + + overrideYaml := "" + if singleChart.OverrideYaml != nil { + overrideYaml = singleChart.OverrideYaml.YamlContent + } + yamlMap, err := yaml.MergeAndUnmarshal([][]byte{[]byte(defaultValues), []byte(overrideYaml)}) + if err != nil { + return "", fmt.Errorf("MergeAndUnmarshal image:%s format error:%s", image, err) + } + + flatten, err := converter.Flatten(yamlMap) + if err != nil { + return "", fmt.Errorf("Flatten image:%s format error:%s", image, err) + } + if imageTag, ok := flatten[imagePathSpec.Image]; ok { + return fmt.Sprintf("%s:%s", splitImage[0], imageTag), nil + } } } return "", fmt.Errorf("failed to find image url") -- Gitee From 745cfa4bf6643f43acb394897f9e575af633208e Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 21:04:55 +0800 Subject: [PATCH 1012/2647] code refactor Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 9e13f111..bb3c44ca 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -84,13 +84,13 @@ type State struct { RedirectURL string `json:"redirect_url"` } -func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (redirectURL string, err error) { +func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (string, error) { codeHost, err := GetCodeHost(codeHostID, logger) if err != nil { logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err } - redirectParsedURL, err := url.Parse(redirectURL) + redirectParsedURL, err := url.Parse(redirectURI) if err != nil { logger.Errorf("Parse:%s err:%s", codeHostID, err) return "", err -- Gitee From 350b6153b8d3e3c7f6912ec53bb57042032005a0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 21:07:26 +0800 Subject: [PATCH 1013/2647] code refactor Signed-off-by: mouuii --- .../systemconfig/core/codehost/internal/oauth/github/github.go | 2 +- .../systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go | 2 +- .../systemconfig/core/codehost/internal/oauth/oauth.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go index ffb752ed..04ec1f3e 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go @@ -49,7 +49,7 @@ func (o *oAuth) LoginURL(state string) string { return o.oauth2Config.AuthCodeURL(state) } -func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { +func (o *oAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index a7168e3f..7083c92a 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -49,7 +49,7 @@ func (o *oAuth) LoginURL(state string) string { return o.oauth2Config.AuthCodeURL(state) } -func (o *oAuth) HandleCallback(r *http.Request) (token *oauth2.Token, err error) { +func (o *oAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return nil, &oauth2Error{errType, q.Get("error_description")} diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index 9d85af48..2c6a4eda 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -8,5 +8,5 @@ import ( type Oauth interface { LoginURL(state string) (loginURL string) - HandleCallback(r *http.Request) (token *oauth2.Token, err error) + HandleCallback(r *http.Request) (*oauth2.Token, error) } -- Gitee From 4ce92693af194fcf02e6cd241a31c16dde0409e1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 21:19:23 +0800 Subject: [PATCH 1014/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/internal/oauth/github/github.go | 15 +-------------- .../core/codehost/internal/oauth/gitlab/gitlab.go | 15 +-------------- .../core/codehost/internal/oauth/oauth.go | 13 +++++++++++++ .../core/codehost/service/codehost.go | 12 +++++++----- 4 files changed, 22 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go index 04ec1f3e..2708e811 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go @@ -1,7 +1,6 @@ package github import ( - "fmt" "net/http" "golang.org/x/oauth2" @@ -33,18 +32,6 @@ func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { } } -type oauth2Error struct { - error string - description string -} - -func (e *oauth2Error) Error() string { - if e.description == "" { - return e.error - } - return fmt.Sprintf("%s: %s", e.error, e.description) -} - func (o *oAuth) LoginURL(state string) string { return o.oauth2Config.AuthCodeURL(state) } @@ -52,7 +39,7 @@ func (o *oAuth) LoginURL(state string) string { func (o *oAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { - return nil, &oauth2Error{errType, q.Get("error_description")} + return nil, &oauth.OAuth2Error{errType, q.Get("error_description")} } return o.oauth2Config.Exchange(r.Context(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index 7083c92a..405a53e7 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -1,7 +1,6 @@ package gitlab import ( - "fmt" "net/http" "golang.org/x/oauth2" @@ -33,18 +32,6 @@ func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { } } -type oauth2Error struct { - error string - description string -} - -func (e *oauth2Error) Error() string { - if e.description == "" { - return e.error - } - return fmt.Sprintf("%s: %s", e.error, e.description) -} - func (o *oAuth) LoginURL(state string) string { return o.oauth2Config.AuthCodeURL(state) } @@ -52,7 +39,7 @@ func (o *oAuth) LoginURL(state string) string { func (o *oAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { - return nil, &oauth2Error{errType, q.Get("error_description")} + return nil, &oauth.OAuth2Error{errType, q.Get("error_description")} } return o.oauth2Config.Exchange(r.Context(), q.Get("code")) } diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index 2c6a4eda..cf5673a8 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -1,6 +1,7 @@ package oauth import ( + "fmt" "net/http" "golang.org/x/oauth2" @@ -10,3 +11,15 @@ type Oauth interface { LoginURL(state string) (loginURL string) HandleCallback(r *http.Request) (*oauth2.Token, error) } + +type OAuth2Error struct { + Err string + Description string +} + +func (e *OAuth2Error) Error() string { + if e.Description == "" { + return e.Err + } + return fmt.Sprintf("%s: %s", e.Err, e.Description) +} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index bb3c44ca..dfe8e868 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -79,7 +79,7 @@ func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { return mongodb.NewCodehostColl().GetCodeHostByID(id) } -type State struct { +type state struct { CodeHostID int `json:"code_host_id"` RedirectURL string `json:"redirect_url"` } @@ -95,13 +95,13 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) logger.Errorf("Parse:%s err:%s", codeHostID, err) return "", err } - callbackURL := fmt.Sprintf("%s://%s/api/directory/codehosts/callback", redirectParsedURL.Scheme, redirectParsedURL.Host) + callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) oauth, err := NewOAuth(codeHost.Type, callbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) return "", err } - stateStruct := State{ + stateStruct := state{ CodeHostID: codeHost.ID, RedirectURL: redirectURI, } @@ -128,6 +128,8 @@ func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oa return nil, errors.New("illegal provider") } +const callback = "/api/directory/codehosts/callback" + func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { aes, err := crypto.NewAes(crypto.GetAesKey()) decrypted, err := aes.Decrypt(stateStr) @@ -136,7 +138,7 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) return "", err } - var state State + var state state if err := json.Unmarshal([]byte(decrypted), &state); err != nil { logger.Errorf("Unmarshal err:%s", err) return "", err @@ -150,7 +152,7 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) logger.Errorf("ParseURL:%s err:%s", state.RedirectURL, err) return "", err } - callbackURL := fmt.Sprintf("%s://%s/api/directory/codehosts/callback", redirectParsedURL.Scheme, redirectParsedURL.Host) + callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) o, err := NewOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { return handle(state.RedirectURL, err) -- Gitee From 272fbb09499a1d125478a487b1bbafa30ff51b53 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 21:21:12 +0800 Subject: [PATCH 1015/2647] code refactor Signed-off-by: mouuii --- .../systemconfig/core/codehost/internal/oauth/github/github.go | 2 +- .../systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go index 2708e811..729ae81b 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go @@ -13,7 +13,7 @@ type oAuth struct { oauth2Config *oauth2.Config } -func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { +func New(callbackURL, clientID, clientSecret, address string) *oAuth { endpoint := github.Endpoint if address != "" { endpoint = oauth2.Endpoint{ diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index 405a53e7..c90574ad 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -13,7 +13,7 @@ type oAuth struct { oauth2Config *oauth2.Config } -func New(callbackURL, clientID, clientSecret, address string) oauth.Oauth { +func New(callbackURL, clientID, clientSecret, address string) *oAuth { endpoint := gitlab.Endpoint if address != "" { endpoint = oauth2.Endpoint{ -- Gitee From 021aea52b089b389d66c8416aad0e6ac0658ebe5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 14 Dec 2021 21:22:49 +0800 Subject: [PATCH 1016/2647] code refactor Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index dfe8e868..55b69970 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -36,6 +36,8 @@ import ( "github.com/koderover/zadig/pkg/tool/crypto" ) +const callback = "/api/directory/codehosts/callback" + func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { if codehost.Type == "codehub" { codehost.IsReady = "2" @@ -128,8 +130,6 @@ func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oa return nil, errors.New("illegal provider") } -const callback = "/api/directory/codehosts/callback" - func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { aes, err := crypto.NewAes(crypto.GetAesKey()) decrypted, err := aes.Decrypt(stateStr) -- Gitee From 1214ed59e2df7e25a76634f185f4f4e7b4b5fcf9 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 15 Dec 2021 15:32:53 +0800 Subject: [PATCH 1017/2647] rollback helm image Signed-off-by: ddh27 --- .../service/workflow/pipeline_validation.go | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index c8a06270..60b32126 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -48,12 +48,12 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" - "github.com/koderover/zadig/pkg/util/yaml" ) const ( @@ -736,49 +736,42 @@ func findCurrentlyUsingImage(productInfo *commonmodels.Product, serviceName, con } } - splitImage := strings.Split(image, ":") - if len(splitImage) != 2 { - return "", fmt.Errorf("failed to find image:%s format error", image) - } - if render == nil { - return image, nil - } opt := &commonrepo.RenderSetFindOption{ - Name: render.Name, - Revision: render.Revision, + Name: render.Name, } renderSetInfo, err := commonrepo.NewRenderSetColl().Find(opt) - if err != nil || renderSetInfo == nil { + if err != nil { return "", fmt.Errorf("failed to find renderSetInfo name:%s,revision:%v", opt.Name, opt.Revision) } - defaultValues := renderSetInfo.DefaultValues for _, singleChart := range renderSetInfo.ChartInfos { - rca := &commonservice.RenderChartArg{} - rca.LoadFromRenderChartModel(singleChart) if singleChart.ServiceName == serviceName { - for _, override := range rca.OverrideValues { - if override.Key == imagePathSpec.Tag { - return splitImage[0] + ":" + override.Value, nil + valuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(singleChart.ValuesYaml)) + if err != nil { + return "", fmt.Errorf("YamlToFlatMap image:%s format error:%s", image, err) + } + imageRepo, ok1 := valuesYamlFlattenMap[imagePathSpec.Repo] + imageTag, ok2 := valuesYamlFlattenMap[imagePathSpec.Tag] + if ok1 && ok2 { + if fmt.Sprintf("%s:%s", imageRepo, imageTag) == image { + return image, nil } } - overrideYaml := "" - if singleChart.OverrideYaml != nil { - overrideYaml = singleChart.OverrideYaml.YamlContent - } - yamlMap, err := yaml.MergeAndUnmarshal([][]byte{[]byte(defaultValues), []byte(overrideYaml)}) + mergedValuesYaml, err := helmtool.MergeOverrideValues(singleChart.ValuesYaml, renderSetInfo.DefaultValues, singleChart.GetOverrideYaml(), singleChart.OverrideValues) if err != nil { - return "", fmt.Errorf("MergeAndUnmarshal image:%s format error:%s", image, err) + return "", fmt.Errorf("MergeOverrideValues name:%s,revision:%v,error:%s", opt.Name, opt.Revision, err) } - - flatten, err := converter.Flatten(yamlMap) + mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergedValuesYaml)) if err != nil { - return "", fmt.Errorf("Flatten image:%s format error:%s", image, err) + return "", fmt.Errorf("mergeYamlToFlatMap image:%s format error:%s", image, err) } - if imageTag, ok := flatten[imagePathSpec.Image]; ok { - return fmt.Sprintf("%s:%s", splitImage[0], imageTag), nil + imageRepo, ok1 = mergedValuesYamlFlattenMap[imagePathSpec.Repo] + imageTag, ok2 = mergedValuesYamlFlattenMap[imagePathSpec.Tag] + if ok1 && ok2 { + return fmt.Sprintf("%s:%s", imageRepo, imageTag), nil } + break } } return "", fmt.Errorf("failed to find image url") -- Gitee From 34efecab0f15c659e30f085ca4e5e7192a1739d8 Mon Sep 17 00:00:00 2001 From: dyliu Date: Wed, 15 Dec 2021 17:30:44 +0800 Subject: [PATCH 1018/2647] external workflow interface supports helm type (#741) * external workflow interface supports helm type Signed-off-by: liu deyi * modify api router Signed-off-by: liu deyi * add code annotaion Signed-off-by: liu deyi * modify function name Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 1 + .../service/workflow/workflow_task.go | 20 +++++++++++++++++-- .../picket/core/public/handler/router.go | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 714dbef3..61fcbb4e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -309,6 +309,7 @@ type HookPayload struct { type TargetArgs struct { Name string `bson:"name" json:"name"` ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` ProductName string `bson:"product_name" json:"product_name"` Build *BuildArgs `bson:"build" json:"build"` Deploy []DeployEnv `bson:"deloy" json:"deploy"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 360b76b1..08014a9c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -787,11 +787,14 @@ func AddDataToArgs(args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) for _, target := range args.Target { target.HasBuild = true // openAPI模式,传入的name是服务名称 - // 只支持k8s服务 + serviceType, err := getValidServiceType(target.ServiceType) + if err != nil { + return err + } opt := &commonrepo.ServiceFindOption{ ServiceName: target.Name, ProductName: workflow.ProductTmplName, - Type: setting.K8SDeployType, + Type: serviceType, ExcludeStatus: setting.ProductStatusDeleting} serviceTmpl, err := commonrepo.NewServiceColl().Find(opt) if err != nil { @@ -897,6 +900,19 @@ func AddDataToArgs(args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) return nil } +// Only supports k8s and helm two service types currently +func getValidServiceType(serviceType string) (string, error) { + switch serviceType { + // Compatible when the service_type is equal to empty + case setting.K8SDeployType, "": + return setting.K8SDeployType, nil + case setting.HelmDeployType: + return setting.HelmDeployType, nil + default: + return "", fmt.Errorf("Unsupported service type") + } +} + func dealWithNamespace(args *commonmodels.WorkflowTaskArgs) { args.Namespace = strings.TrimPrefix(args.Namespace, ",") args.Namespace = strings.TrimSuffix(args.Namespace, ",") diff --git a/pkg/microservice/picket/core/public/handler/router.go b/pkg/microservice/picket/core/public/handler/router.go index 550478ab..177292de 100644 --- a/pkg/microservice/picket/core/public/handler/router.go +++ b/pkg/microservice/picket/core/public/handler/router.go @@ -27,7 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { dev := router.Group("") { dev.POST("/workflowTask/create", CreateWorkflowTask) - dev.POST("workflowTask/id/:id/pipelines/:name/cancel", CancelWorkflowTask) + dev.POST("/workflowTask/id/:id/pipelines/:name/cancel", CancelWorkflowTask) dev.POST("/workflowTask/id/:id/pipelines/:name/restart", RestartWorkflowTask) dev.GET("/workflowTask", ListWorkflowTask) dev.GET("/dc/releases", ListDelivery) -- Gitee From 5fc8fa17ea8b9d09930c59bce25b978d51de6afa Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 16 Dec 2021 10:25:25 +0800 Subject: [PATCH 1019/2647] add license Signed-off-by: mouuii --- .../codehost/internal/oauth/github/github.go | 16 ++++++++++++++++ .../codehost/internal/oauth/gitlab/gitlab.go | 16 ++++++++++++++++ .../core/codehost/internal/oauth/oauth.go | 16 ++++++++++++++++ .../core/codehost/service/codehost.go | 4 ++-- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go index 729ae81b..b6cfba37 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package github import ( diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go index c90574ad..0f651626 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitlab import ( diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index cf5673a8..5c43e096 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package oauth import ( diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 55b69970..ac38ea9c 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -94,13 +94,13 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) } redirectParsedURL, err := url.Parse(redirectURI) if err != nil { - logger.Errorf("Parse:%s err:%s", codeHostID, err) + logger.Errorf("Parsere directURI:%s err:%s", redirectURI, err) return "", err } callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) oauth, err := NewOAuth(codeHost.Type, callbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { - logger.Errorf("get Factory:%s err:%s", codeHost.Type, err) + logger.Errorf("NewOAuth:%s err:%s", codeHost.Type, err) return "", err } stateStruct := state{ -- Gitee From 8f852b790fa24016b7bc8c55dcaecbf6bd477a41 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 16 Dec 2021 11:03:03 +0800 Subject: [PATCH 1020/2647] fix helm delivery bug Signed-off-by: allenshen --- .../repository/mongodb/delivery_version.go | 21 +++++++++++++-- .../aslan/core/delivery/service/version.go | 26 ++++++++++++------- .../aslan/core/environment/service/helm.go | 14 +++++++++- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index e0adcfc8..51e071d7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -197,8 +197,12 @@ func (c *DeliveryVersionColl) Insert(args *models.DeliveryVersion) error { return nil } -func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr string) error { - query := bson.M{"version": versionName, "deleted_at": 0} +func (c *DeliveryVersionColl) UpdateStatusByName(versionName, projectName, status, errorStr string) error { + query := bson.M{ + "version": versionName, + "product_name": projectName, + "deleted_at": 0, + } change := bson.M{"$set": bson.M{ "status": status, "error": errorStr, @@ -207,6 +211,19 @@ func (c *DeliveryVersionColl) UpdateStatusByName(versionName, status, errorStr s return err } +func (c *DeliveryVersionColl) UpdateTaskID(versionName, projectName string, taskID int32) error { + query := bson.M{ + "version": versionName, + "product_name": projectName, + "deleted_at": 0, + } + change := bson.M{"$set": bson.M{ + "task_id": taskID, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *DeliveryVersionColl) Update(args *models.DeliveryVersion) error { if args == nil { return errors.New("nil delivery_version args") diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7bba266c..d6a25a07 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -424,6 +424,11 @@ func buildDeliveryProgressInfo(deliveryVersion *commonmodels.DeliveryVersion, su return nil } + _, err := checkVersionStatus(deliveryVersion) + if err != nil { + updateVersionStatus(deliveryVersion.Version, deliveryVersion.ProductName, setting.DeliveryVersionStatusFailed, err.Error()) + } + progress := &commonmodels.DeliveryVersionProgress{ SuccessChartCount: successfulChartCount, TotalChartCount: 0, @@ -843,7 +848,7 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer deliveryVersion.Status = setting.DeliveryVersionStatusFailed deliveryVersion.Error = err.Error() } - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, deliveryVersion.Error) if err != nil { logger.Errorf("failed to update delivery version data, name: %s error: %s", deliveryVersion.Version, err) } @@ -912,15 +917,18 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return err } deliveryVersion.TaskID = int(taskID) - + err = commonrepo.NewDeliveryVersionColl().UpdateTaskID(deliveryVersion.Version, deliveryVersion.ProductName, int32(deliveryVersion.TaskID)) + if err != nil { + logger.Errorf("failed to update delivery version task_id, version: %s, task_id: %s, err: %s", deliveryVersion, deliveryVersion.ProductName, deliveryVersion.TaskID) + } // start a new routine to check task results go waitVersionDone(deliveryVersion) return } -func updateVersionStatus(versionName, status, errStr string) { - err := commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionName, status, errStr) +func updateVersionStatus(versionName, projectName, status, errStr string) { + err := commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionName, projectName, status, errStr) if err != nil { log.Errorf("failed to update version status, name: %s, err: %s", versionName, err) } @@ -935,12 +943,12 @@ func waitVersionDone(deliveryVersion *commonmodels.DeliveryVersion) { for { select { case <-waitTimeout: - updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, "timeout") + updateVersionStatus(deliveryVersion.Version, deliveryVersion.ProductName, setting.DeliveryVersionStatusFailed, "timeout") return default: done, err := checkVersionStatus(deliveryVersion) if err != nil { - updateVersionStatus(deliveryVersion.Version, setting.DeliveryVersionStatusFailed, err.Error()) + updateVersionStatus(deliveryVersion.Version, deliveryVersion.ProductName, setting.DeliveryVersionStatusFailed, err.Error()) return } if done { @@ -1015,7 +1023,7 @@ func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, er } if singleResult, ok := progressDataMap[chartData.ServiceName]; ok { if singleResult.Result != "success" { - errorList = multierror.Append(errorList, fmt.Errorf("failed to build image distribute for service:%s ", singleResult.ServiceName)) + errorList = multierror.Append(errorList, fmt.Errorf("failed to build image distribute for service:%s, err: %s ", singleResult.ServiceName, singleResult.ErrorMsg)) continue } err = insertDeliveryDistributions(singleResult, chartData.Version, deliveryVersion, createArgs) @@ -1045,7 +1053,7 @@ func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, er if errorList.ErrorOrNil() != nil { deliveryVersion.Error = errorList.Error() } - updateVersionStatus(deliveryVersion.Version, deliveryVersion.Status, deliveryVersion.Error) + updateVersionStatus(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, deliveryVersion.Error) return allTaskDone, nil } @@ -1173,7 +1181,7 @@ func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap // update status deliveryVersion.Status = setting.DeliveryVersionStatusRetrying - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.Status, "") + err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, "") if err != nil { logger.Errorf("failed to update delivery status, name: %s, err: %s", deliveryVersion.Version, err) return fmt.Errorf("failed to update delivery status, name: %s", deliveryVersion.Version) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 255a2ba7..7ab2157d 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -82,11 +83,22 @@ func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmR return nil, e.ErrCreateDeliveryVersion.AddErr(err) } + // filter releases, only list releases deployed by zadig + serviceMap := prod.GetServiceMap() + serviceSet := sets.NewString() + for serviceName := range serviceMap { + serviceSet.Insert(serviceName) + } + ret := make([]*HelmReleaseResp, 0, len(releases)) for _, release := range releases { + serviceName := util.ExtraServiceName(release.Name, prod.Namespace) + if !serviceSet.Has(serviceName) { + continue + } ret = append(ret, &HelmReleaseResp{ ReleaseName: release.Name, - ServiceName: util.ExtraServiceName(release.Name, prod.Namespace), + ServiceName: serviceName, Revision: release.Version, Chart: release.Chart.Name(), AppVersion: release.Chart.AppVersion(), -- Gitee From 42e651d46c652db10632f67068dd714aec493a26 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 16 Dec 2021 11:04:33 +0800 Subject: [PATCH 1021/2647] rollback image Signed-off-by: ddh27 --- .../aslan/core/environment/service/product.go | 52 ++++++++++++++++++- .../service/workflow/pipeline_validation.go | 49 +---------------- 2 files changed, 52 insertions(+), 49 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 7bd24813..5c5b6738 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -18,6 +18,7 @@ package service import ( "fmt" + "strings" "time" "go.uber.org/zap" @@ -32,7 +33,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util/converter" ) var DefaultCleanWhiteList = []string{"spockadmin"} @@ -135,7 +138,7 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode Type: serviceTmpl.Type, Revision: serviceTmpl.Revision, } - if serviceTmpl.Type == setting.K8SDeployType || serviceTmpl.Type == setting.HelmDeployType { + if serviceTmpl.Type == setting.K8SDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ @@ -145,6 +148,53 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode } serviceResp.Containers = append(serviceResp.Containers, container) } + } else if serviceTmpl.Type == setting.HelmDeployType { + serviceResp.Containers = make([]*commonmodels.Container, 0) + renderSet, err := commonservice.GetRenderSet(prodTmpl.ProductName, 0, log) + if err != nil { + errMsg := fmt.Sprintf("Failed to GetRenderSet for product template %s", prodTmpl.ProductName) + log.Error(errMsg) + return nil, e.ErrGetProduct.AddDesc(errMsg) + } + for _, c := range serviceTmpl.Containers { + image := c.Image + for _, rc := range renderSet.ChartInfos { + if rc.ServiceName == serviceTmpl.ServiceName { + mergeYaml, err := helmtool.MergeOverrideValues(rc.ValuesYaml, renderSet.DefaultValues, rc.GetOverrideYaml(), rc.OverrideValues) + if err != nil { + errMsg := fmt.Sprintf("Failed to MergeOverrideValues for product template %s,error:%s", prodTmpl.ProductName, err) + log.Error(errMsg) + return nil, e.ErrGetProduct.AddDesc(errMsg) + } + mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) + if err != nil { + errMsg := fmt.Sprintf("mergeYamlToFlatMap product template %s,error:%s", prodTmpl.ProductName, err) + log.Error(errMsg) + return nil, e.ErrGetProduct.AddDesc(errMsg) + } + imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] + imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] + if ok2 { + if ok1 { + image = fmt.Sprintf("%s:%s", imageRepo, imageTag) + } else { + splitImage := strings.Split(image, ":") + if len(splitImage) == 2 { + image = fmt.Sprintf("%s:%s", splitImage[0], imageTag) + } + } + } + break + } + } + + container := &commonmodels.Container{ + Name: c.Name, + Image: image, + ImagePath: c.ImagePath, + } + serviceResp.Containers = append(serviceResp.Containers, container) + } } servicesResp = append(servicesResp, serviceResp) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 60b32126..1c33ddcc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -48,12 +48,10 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" - "github.com/koderover/zadig/pkg/util/converter" ) const ( @@ -720,58 +718,13 @@ func (c *ImageIllegal) Error() string { // find currently using image for services deployed by helm func findCurrentlyUsingImage(productInfo *commonmodels.Product, serviceName, containerName string) (string, error) { - image := "" - var imagePathSpec *commonmodels.ImagePathSpec - var render *commonmodels.RenderInfo for _, service := range productInfo.GetServiceMap() { if service.ServiceName == serviceName { for _, container := range service.Containers { if container.Name == containerName { - image = container.Image - imagePathSpec = container.ImagePath + return container.Image, nil } } - render = service.Render - break - } - } - - opt := &commonrepo.RenderSetFindOption{ - Name: render.Name, - } - renderSetInfo, err := commonrepo.NewRenderSetColl().Find(opt) - if err != nil { - return "", fmt.Errorf("failed to find renderSetInfo name:%s,revision:%v", opt.Name, opt.Revision) - } - - for _, singleChart := range renderSetInfo.ChartInfos { - if singleChart.ServiceName == serviceName { - valuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(singleChart.ValuesYaml)) - if err != nil { - return "", fmt.Errorf("YamlToFlatMap image:%s format error:%s", image, err) - } - imageRepo, ok1 := valuesYamlFlattenMap[imagePathSpec.Repo] - imageTag, ok2 := valuesYamlFlattenMap[imagePathSpec.Tag] - if ok1 && ok2 { - if fmt.Sprintf("%s:%s", imageRepo, imageTag) == image { - return image, nil - } - } - - mergedValuesYaml, err := helmtool.MergeOverrideValues(singleChart.ValuesYaml, renderSetInfo.DefaultValues, singleChart.GetOverrideYaml(), singleChart.OverrideValues) - if err != nil { - return "", fmt.Errorf("MergeOverrideValues name:%s,revision:%v,error:%s", opt.Name, opt.Revision, err) - } - mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergedValuesYaml)) - if err != nil { - return "", fmt.Errorf("mergeYamlToFlatMap image:%s format error:%s", image, err) - } - imageRepo, ok1 = mergedValuesYamlFlattenMap[imagePathSpec.Repo] - imageTag, ok2 = mergedValuesYamlFlattenMap[imagePathSpec.Tag] - if ok1 && ok2 { - return fmt.Sprintf("%s:%s", imageRepo, imageTag), nil - } - break } } return "", fmt.Errorf("failed to find image url") -- Gitee From 00512fbae8cea31a20c18a9d8ad30eb65c6de73e Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 16 Dec 2021 11:52:02 +0800 Subject: [PATCH 1022/2647] remove update secret when create or update registry Signed-off-by: panxunying --- .../aslan/core/system/service/registry.go | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 192f5fbd..ce94cf6c 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -22,13 +22,9 @@ import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" - "github.com/koderover/zadig/pkg/setting" - kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util" ) @@ -80,24 +76,6 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa return fmt.Errorf("RegistryNamespace.Create error: %v", err) } } - - envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ExcludeSource: setting.SourceFromExternal}) - if err != nil { - log.Errorf("Failed to list namespaces to update") - } - for _, env := range envs { - go func(prod *commonmodels.Product) { - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) - if err != nil { - log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) - return - } - err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, args.ID.Hex(), kubeClient, log) - if err != nil { - log.Errorf("[updateRegistry] Failed to update registry secret for namespace: %s, the error is: %+v", prod.Namespace, err) - } - }(env) - } } else { hasDefault := false for _, registryInfo := range registryInfoList { @@ -155,24 +133,6 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam log.Errorf("RegistryNamespace.Update error: %v", err) return fmt.Errorf("RegistryNamespace.Update error: %v", err) } - - envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ExcludeSource: setting.SourceFromExternal}) - if err != nil { - log.Errorf("Failed to list namespaces to update") - } - for _, env := range envs { - go func(prod *commonmodels.Product) { - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), prod.ClusterID) - if err != nil { - log.Errorf("[updateRegistry] Failed to get kubecli for namespace: %s", prod.Namespace) - return - } - err = commonservice.EnsureDefaultRegistrySecret(prod.Namespace, id, kubeClient, log) - if err != nil { - log.Errorf("[updateRegistry] Failed to update registry secret for namespace: %s, the error is: %+v", prod.Namespace, err) - } - }(env) - } return nil } -- Gitee From 1f70588c241619199a25704702d8ec8b0ee682f3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 16 Dec 2021 14:16:58 +0800 Subject: [PATCH 1023/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index b423cf8e..a4c29c84 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -206,6 +206,8 @@ func (p *TriggerTaskPlugin) Wait(ctx context.Context) { default: time.Sleep(time.Second * 3) callbackPayloadObj, _ := p.getCallbackObj(p.taskId, p.pipelineName) + p.Log.Infof("callbackPayloadObj:%+v", callbackPayloadObj) + p.Log.Infof("callbackPayloadObj.payload:%+v", callbackPayloadObj.Payload) if callbackPayloadObj != nil { p.Task.CallbackType = callbackPayloadObj.Type p.Task.CallbackPayload = callbackPayloadObj.Payload -- Gitee From dbdc294d60f91c2578259fe3a2235d995d96d7a1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 16 Dec 2021 14:22:09 +0800 Subject: [PATCH 1024/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index a4c29c84..92b5c810 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -207,8 +207,8 @@ func (p *TriggerTaskPlugin) Wait(ctx context.Context) { time.Sleep(time.Second * 3) callbackPayloadObj, _ := p.getCallbackObj(p.taskId, p.pipelineName) p.Log.Infof("callbackPayloadObj:%+v", callbackPayloadObj) - p.Log.Infof("callbackPayloadObj.payload:%+v", callbackPayloadObj.Payload) if callbackPayloadObj != nil { + p.Log.Infof("callbackPayloadObj.payload:%+v", callbackPayloadObj.Payload) p.Task.CallbackType = callbackPayloadObj.Type p.Task.CallbackPayload = callbackPayloadObj.Payload if callbackPayloadObj.Status == "success" { -- Gitee From b9793567c5f0acda8b3bc235cc47544379d3dc4d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 16 Dec 2021 14:44:18 +0800 Subject: [PATCH 1025/2647] remove debug code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskcontroller/task_helper.go | 1 + pkg/microservice/warpdrive/core/service/taskplugin/trigger.go | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 9c23a805..64ae7820 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -138,6 +138,7 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv pipelineTask.Stages[pos].SubTasks[servicename] = subTask } else if pipelineTask.Type == config.WorkflowTypeV3 { xl.Info("pipeline type is workflow type: pipeline 3.0") + pipelineTask.Stages[pos].SubTasks[servicename] = subTask } else if pipelineTask.Type == config.ArtifactType { xl.Info("pipeline type is artifact-package type: pipeline 3.0") pipelineTask.Stages[pos].SubTasks[servicename] = subTask diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 92b5c810..b423cf8e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -206,9 +206,7 @@ func (p *TriggerTaskPlugin) Wait(ctx context.Context) { default: time.Sleep(time.Second * 3) callbackPayloadObj, _ := p.getCallbackObj(p.taskId, p.pipelineName) - p.Log.Infof("callbackPayloadObj:%+v", callbackPayloadObj) if callbackPayloadObj != nil { - p.Log.Infof("callbackPayloadObj.payload:%+v", callbackPayloadObj.Payload) p.Task.CallbackType = callbackPayloadObj.Type p.Task.CallbackPayload = callbackPayloadObj.Payload if callbackPayloadObj.Status == "success" { -- Gitee From 258ae2b49cb92a3459a0611f71656fb04a6cd46f Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 16 Dec 2021 15:30:48 +0800 Subject: [PATCH 1026/2647] rollback image workflowtask Signed-off-by: ddh27 --- .../aslan/core/common/service/environment.go | 8 +- .../core/environment/service/environment.go | 158 +++++++++++++----- .../aslan/core/environment/service/product.go | 52 +----- 3 files changed, 120 insertions(+), 98 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 3d4a41ea..416b63a6 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -64,9 +64,11 @@ func FillProductTemplateValuesYamls(tmpl *templatemodels.Product, log *zap.Sugar continue } tmpl.ChartInfos = append(tmpl.ChartInfos, &templatemodels.RenderChart{ - ServiceName: renderChart.ServiceName, - ChartVersion: renderChart.ChartVersion, - ValuesYaml: renderChart.ValuesYaml, + ServiceName: renderChart.ServiceName, + ChartVersion: renderChart.ChartVersion, + ValuesYaml: renderChart.ValuesYaml, + OverrideYaml: renderChart.OverrideYaml, + OverrideValues: renderChart.OverrideValues, }) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 3e74fc9a..07203d34 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -45,6 +45,7 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -696,6 +697,46 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe return e.ErrCreateEnv.AddDesc(err.Error()) } + errList := new(multierror.Error) + for _, arg := range args { + err = createSingleHelmProduct(templateProduct, requestID, userName, arg.RegistryID, arg, log) + if err != nil { + errList = multierror.Append(errList, err) + } + } + return errList.ErrorOrNil() +} + +func createSingleHelmProduct(templateProduct *template.Product, requestID, userName, registryID string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { + productObj := &commonmodels.Product{ + ProductName: templateProduct.ProductName, + Revision: 1, + Enabled: false, + EnvName: arg.EnvName, + UpdateBy: userName, + IsPublic: true, + ClusterID: arg.ClusterID, + Namespace: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + Source: setting.SourceFromHelm, + IsOpenSource: templateProduct.IsOpensource, + ChartInfos: templateProduct.ChartInfos, + IsForkedProduct: false, + RegistryID: registryID, + } + + customChartValueMap := make(map[string]*commonservice.RenderChartArg) + for _, singleCV := range arg.ChartValues { + customChartValueMap[singleCV.ServiceName] = singleCV + } + + for _, latestChart := range productObj.ChartInfos { + if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { + singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) + } + } + // default values + defaultValuesYaml := arg.DefaultValues + // generate service group data allServiceInfoMap := templateProduct.AllServiceInfoMap() var serviceGroup [][]*commonmodels.ProductService @@ -724,9 +765,40 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe if serviceTmpl.Type == setting.K8SDeployType || serviceTmpl.Type == setting.HelmDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { + image := c.Image + for _, rc := range productObj.ChartInfos { + if rc.ServiceName != serviceTmpl.ServiceName { + continue + } + mergeYaml, err := helmtool.MergeOverrideValues(rc.ValuesYaml, defaultValuesYaml, rc.GetOverrideYaml(), rc.OverrideValues) + if err != nil { + errMsg := fmt.Sprintf("Failed to MergeOverrideValues for product template %s,error:%s", productObj.ProductName, err) + log.Error(errMsg) + return e.ErrCreateEnv.AddDesc(errMsg) + } + mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) + if err != nil { + errMsg := fmt.Sprintf("mergeYamlToFlatMap product template %s,error:%s", productObj.ProductName, err) + log.Error(errMsg) + return e.ErrCreateEnv.AddDesc(errMsg) + } + imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] + imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] + if ok2 { + if ok1 { + image = fmt.Sprintf("%s:%s", imageRepo, imageTag) + } else { + splitImage := strings.Split(image, ":") + if len(splitImage) == 2 { + image = fmt.Sprintf("%s:%s", splitImage[0], imageTag) + } + } + } + } + container := &commonmodels.Container{ Name: c.Name, - Image: c.Image, + Image: image, ImagePath: c.ImagePath, } serviceResp.Containers = append(serviceResp.Containers, container) @@ -737,47 +809,7 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe serviceGroup = append(serviceGroup, servicesResp) } - errList := new(multierror.Error) - for _, arg := range args { - err = createSingleHelmProduct(templateProduct, serviceGroup, requestID, userName, arg.RegistryID, arg, log) - if err != nil { - errList = multierror.Append(errList, err) - } - } - return errList.ErrorOrNil() -} - -func createSingleHelmProduct(templateProduct *template.Product, serviceGroup [][]*commonmodels.ProductService, requestID, userName, registryID string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { - productObj := &commonmodels.Product{ - ProductName: templateProduct.ProductName, - Revision: 1, - Enabled: false, - EnvName: arg.EnvName, - UpdateBy: userName, - Services: serviceGroup, - IsPublic: true, - ClusterID: arg.ClusterID, - Namespace: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), - Source: setting.SourceFromHelm, - IsOpenSource: templateProduct.IsOpensource, - ChartInfos: templateProduct.ChartInfos, - IsForkedProduct: false, - RegistryID: registryID, - } - - customChartValueMap := make(map[string]*commonservice.RenderChartArg) - for _, singleCV := range arg.ChartValues { - customChartValueMap[singleCV.ServiceName] = singleCV - } - - for _, latestChart := range productObj.ChartInfos { - if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { - singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) - } - } - - // default values - defaultValuesYaml := arg.DefaultValues + productObj.Services = serviceGroup // insert renderset info into db if len(productObj.ChartInfos) > 0 { @@ -810,7 +842,7 @@ func UpdateProductRecycleDay(envName, productName string, recycleDay int) error return commonrepo.NewProductColl().UpdateProductRecycleDay(envName, productName, recycleDay) } -func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, renderSet *models.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -824,6 +856,44 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str log.Errorf("[%s][P:%s] GetProductTemplate error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } + + for _, svrs := range updateProd.Services { + for _, svr := range svrs { + for _, rc := range overrideCharts { + if rc.ServiceName == svr.ServiceName { + for _, c := range svr.Containers { + image := c.Image + mergeYaml, err := helmtool.MergeOverrideValues("", renderSet.DefaultValues, rc.OverrideYaml, rc.ToOverrideValueString()) + if err != nil { + errMsg := fmt.Sprintf("Failed to MergeOverrideValues for service %s,error:%s", svr.ServiceName, err) + log.Error(errMsg) + return e.ErrUpdateEnv.AddDesc(errMsg) + } + mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) + if err != nil { + errMsg := fmt.Sprintf("mergeYamlToFlatMap service %s,error:%s", svr.ServiceName, err) + log.Error(errMsg) + return e.ErrUpdateEnv.AddDesc(errMsg) + } + imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] + imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] + if ok2 { + if ok1 { + image = fmt.Sprintf("%s:%s", imageRepo, imageTag) + } else { + splitImage := strings.Split(image, ":") + if len(splitImage) == 2 { + image = fmt.Sprintf("%s:%s", splitImage[0], imageTag) + } + } + } + c.Image = image + } + } + } + } + } + productResp.Services = updateProd.Services // 设置产品状态为更新中 @@ -1186,7 +1256,7 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for env: %s", envName)) } - err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, log) + err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, renderSet, log) if err != nil { log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) return envStatuses, e.ErrUpdateEnv.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 5c5b6738..7bd24813 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -18,7 +18,6 @@ package service import ( "fmt" - "strings" "time" "go.uber.org/zap" @@ -33,9 +32,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util/converter" ) var DefaultCleanWhiteList = []string{"spockadmin"} @@ -138,7 +135,7 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode Type: serviceTmpl.Type, Revision: serviceTmpl.Revision, } - if serviceTmpl.Type == setting.K8SDeployType { + if serviceTmpl.Type == setting.K8SDeployType || serviceTmpl.Type == setting.HelmDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { container := &commonmodels.Container{ @@ -148,53 +145,6 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode } serviceResp.Containers = append(serviceResp.Containers, container) } - } else if serviceTmpl.Type == setting.HelmDeployType { - serviceResp.Containers = make([]*commonmodels.Container, 0) - renderSet, err := commonservice.GetRenderSet(prodTmpl.ProductName, 0, log) - if err != nil { - errMsg := fmt.Sprintf("Failed to GetRenderSet for product template %s", prodTmpl.ProductName) - log.Error(errMsg) - return nil, e.ErrGetProduct.AddDesc(errMsg) - } - for _, c := range serviceTmpl.Containers { - image := c.Image - for _, rc := range renderSet.ChartInfos { - if rc.ServiceName == serviceTmpl.ServiceName { - mergeYaml, err := helmtool.MergeOverrideValues(rc.ValuesYaml, renderSet.DefaultValues, rc.GetOverrideYaml(), rc.OverrideValues) - if err != nil { - errMsg := fmt.Sprintf("Failed to MergeOverrideValues for product template %s,error:%s", prodTmpl.ProductName, err) - log.Error(errMsg) - return nil, e.ErrGetProduct.AddDesc(errMsg) - } - mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) - if err != nil { - errMsg := fmt.Sprintf("mergeYamlToFlatMap product template %s,error:%s", prodTmpl.ProductName, err) - log.Error(errMsg) - return nil, e.ErrGetProduct.AddDesc(errMsg) - } - imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] - imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] - if ok2 { - if ok1 { - image = fmt.Sprintf("%s:%s", imageRepo, imageTag) - } else { - splitImage := strings.Split(image, ":") - if len(splitImage) == 2 { - image = fmt.Sprintf("%s:%s", splitImage[0], imageTag) - } - } - } - break - } - } - - container := &commonmodels.Container{ - Name: c.Name, - Image: image, - ImagePath: c.ImagePath, - } - serviceResp.Containers = append(serviceResp.Containers, container) - } } servicesResp = append(servicesResp, serviceResp) } -- Gitee From bb7ba8936e62ccec0eede58c7b0efb2233f5bf5b Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Thu, 16 Dec 2021 16:22:38 +0800 Subject: [PATCH 1027/2647] fix(git):fix the problem of upgrade git to the latest version in 16.04. Signed-off-by: leozhang2018 --- docker/base/amd64/ubuntu-xenial.Dockerfile | 15 --------------- docker/base/arm64/ubuntu-xenial.Dockerfile | 15 --------------- docker/service/reaper-plugin-xenial.Dockerfile | 1 - 3 files changed, 31 deletions(-) diff --git a/docker/base/amd64/ubuntu-xenial.Dockerfile b/docker/base/amd64/ubuntu-xenial.Dockerfile index ccb4277f..96fa38bd 100644 --- a/docker/base/amd64/ubuntu-xenial.Dockerfile +++ b/docker/base/amd64/ubuntu-xenial.Dockerfile @@ -1,16 +1 @@ FROM ubuntu:xenial - -# 修改镜像源和时区 -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \ - && apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \ - && apt-get install -y \ - tzdata \ - net-tools \ - dnsutils \ - ca-certificates \ - git \ - curl \ - lsof \ - telnet \ - && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ - && rm -rf /var/lib/apt/lists/* diff --git a/docker/base/arm64/ubuntu-xenial.Dockerfile b/docker/base/arm64/ubuntu-xenial.Dockerfile index e2f37c5f..5ba7f65d 100644 --- a/docker/base/arm64/ubuntu-xenial.Dockerfile +++ b/docker/base/arm64/ubuntu-xenial.Dockerfile @@ -1,16 +1 @@ FROM arm64v8/ubuntu:xenial - -# 修改镜像源和时区 -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \ - && apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \ - && apt-get install -y \ - tzdata \ - net-tools \ - dnsutils \ - ca-certificates \ - git \ - curl \ - lsof \ - telnet \ - && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ - && rm -rf /var/lib/apt/lists/* diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 6a65d682..f0f7bcbd 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -10,7 +10,6 @@ RUN sed -i s:http:https:g /etc/apt/sources.list RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata RUN apt-get clean && apt-get update && apt-get install -y \ curl \ - git \ netcat-openbsd \ wget \ build-essential \ -- Gitee From e3c6e38662b50d0f5cc76c34d1aea12a093e1a27 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 16 Dec 2021 16:54:27 +0800 Subject: [PATCH 1028/2647] fix service host missing bug Signed-off-by: allenshen --- .../environment/service/environment_group.go | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index ac0fabe6..da3b0e58 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -34,6 +34,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/util" ) @@ -151,13 +152,27 @@ func GetIngressInfo(product *commonmodels.Product, service *commonmodels.Service parsedYaml = util.ReplaceWrapLine(parsedYaml) yamlContentArray := releaseutil.SplitManifests(parsedYaml) hostInfos := make([]resource.HostInfo, 0) + for _, item := range yamlContentArray { - ing, err := serializer.NewDecoder().YamlToIngress([]byte(item)) - if err != nil || ing == nil { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(item)) + if err != nil { + log.Warnf("Failed to decode yaml to Unstructured, err: %s", err) continue } - - hostInfos = append(hostInfos, wrapper.Ingress(ing).HostInfo()...) + switch u.GetKind() { + case setting.Ingress: + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to init kubeClient, clusterID: %s", product.ClusterID) + return nil + } + ing, found, err := getter.GetIngress(product.Namespace, u.GetName(), kubeClient) + if err != nil || !found { + log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, product.Namespace, err) + continue + } + hostInfos = append(hostInfos, wrapper.Ingress(ing).HostInfo()...) + } } ingressInfo.HostInfo = hostInfos return ingressInfo -- Gitee From fd754e4c739931345f83364b41b8dd41128bf591 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 16 Dec 2021 19:28:57 +0800 Subject: [PATCH 1029/2647] add license in new file Signed-off-by: liu deyi --- .../aslan/core/common/handler/callback.go | 16 ++++++++++++++++ .../common/repository/models/callback_request.go | 16 ++++++++++++++++ .../common/repository/models/external_system.go | 16 ++++++++++++++++ .../common/repository/models/task/trigger.go | 16 ++++++++++++++++ .../core/common/repository/models/workflow_v3.go | 16 ++++++++++++++++ .../repository/mongodb/callback_request.go | 16 ++++++++++++++++ .../common/repository/mongodb/external_system.go | 16 ++++++++++++++++ .../common/repository/mongodb/workflow_v3.go | 16 ++++++++++++++++ .../aslan/core/common/service/callback.go | 16 ++++++++++++++++ .../aslan/core/common/service/workflow_v3.go | 16 ++++++++++++++++ .../aslan/core/system/handler/external_system.go | 16 ++++++++++++++++ .../aslan/core/system/service/external_system.go | 16 ++++++++++++++++ .../aslan/core/system/service/types.go | 16 ++++++++++++++++ .../aslan/core/workflow/handler/workflow_v3.go | 16 ++++++++++++++++ .../core/workflow/handler/workflow_v3_task.go | 16 ++++++++++++++++ .../service/webhook/github_testing_task.go | 16 ++++++++++++++++ .../core/workflow/service/workflow/types.go | 16 ++++++++++++++++ .../workflow/service/workflow/workflow_task.go | 3 +-- .../service/workflow/workflow_task_v3.go | 16 ++++++++++++++++ .../workflow/service/workflow/workflow_v3.go | 16 ++++++++++++++++ .../warpdrive/core/service/types/task/trigger.go | 16 ++++++++++++++++ 21 files changed, 321 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/handler/callback.go b/pkg/microservice/aslan/core/common/handler/callback.go index 0a41b5a5..212e347c 100644 --- a/pkg/microservice/aslan/core/common/handler/callback.go +++ b/pkg/microservice/aslan/core/common/handler/callback.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/common/repository/models/callback_request.go b/pkg/microservice/aslan/core/common/repository/models/callback_request.go index 9aced44f..28bc6f0f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/callback_request.go +++ b/pkg/microservice/aslan/core/common/repository/models/callback_request.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/microservice/aslan/core/common/repository/models/external_system.go b/pkg/microservice/aslan/core/common/repository/models/external_system.go index 264615e6..08d7e69e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/external_system.go +++ b/pkg/microservice/aslan/core/common/repository/models/external_system.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go index 8ee73797..1487b8fc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/trigger.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/trigger.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package task import ( diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go index 4567087f..4b33b049 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import ( diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go index 0a491fef..57c6a2dc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/callback_request.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go index 9e651d88..3fd2566f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/external_system.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go index 07a5f160..e0521b59 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v3.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/common/service/callback.go b/pkg/microservice/aslan/core/common/service/callback.go index 3edfa219..cd9da866 100644 --- a/pkg/microservice/aslan/core/common/service/callback.go +++ b/pkg/microservice/aslan/core/common/service/callback.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/common/service/workflow_v3.go b/pkg/microservice/aslan/core/common/service/workflow_v3.go index 02e1c143..16ae171b 100644 --- a/pkg/microservice/aslan/core/common/service/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/service/workflow_v3.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/system/handler/external_system.go b/pkg/microservice/aslan/core/system/handler/external_system.go index 41998fc9..785b1e2d 100644 --- a/pkg/microservice/aslan/core/system/handler/external_system.go +++ b/pkg/microservice/aslan/core/system/handler/external_system.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go index ed72a681..76c2f97c 100644 --- a/pkg/microservice/aslan/core/system/service/external_system.go +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/system/service/types.go b/pkg/microservice/aslan/core/system/service/types.go index db9dea3f..ab4aa310 100644 --- a/pkg/microservice/aslan/core/system/service/types.go +++ b/pkg/microservice/aslan/core/system/service/types.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service type ExternalSystemDetail struct { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go index fa221bb4..129395a2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index 323626bd..721ad9e4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go index fcd494c9..756781cc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package webhook import ( diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index e42985ca..d6fd439a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflow type WorkflowV3 struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index d308fc0b..188dbf2c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2207,7 +2207,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } } case config.TaskDistribute: - // 为了兼容历史数据类型,目前什么都不用做,避免出错 + // do nothing case config.TaskTrigger: t, err := base.ToTriggerTask(subTask) if err != nil { @@ -2221,7 +2221,6 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } } - // 为了兼容历史数据类型,目前什么都不用做,避免出错 case config.TaskArtifactPackage: // do nothing default: diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 7f0edb2f..6431c0d2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflow import ( diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index b708b0ef..ea9a1205 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflow import ( diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index da67fada..9c82d161 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package task import ( -- Gitee From b386565a91585990abe26f11b77ff05aa8ae631d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 16 Dec 2021 19:35:36 +0800 Subject: [PATCH 1030/2647] modify meessage Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/handler/log.go | 10 +++++----- .../core/workflow/service/workflow/workflow_v3.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 1243a524..25f83244 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -37,7 +37,7 @@ func GetBuildJobContainerLogs(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - // job名称使用全小写,避免出现subdomain错误 + // Use all lowercase job names to avoid subdomain errors ctx.Resp, ctx.Err = logservice.GetBuildJobContainerLogs( c.Param("pipelineName"), c.Param("serviceName"), @@ -55,7 +55,7 @@ func GetWorkflowBuildJobContainerLogs(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - // job名称使用全小写,避免出现subdomain错误 + // Use all lowercase job names to avoid subdomain errors ctx.Resp, ctx.Err = logservice.GetWorkflowBuildJobContainerLogs(strings.ToLower(c.Param("pipelineName")), c.Param("serviceName"), c.Query("type"), taskID, ctx.Logger) } @@ -69,7 +69,7 @@ func GetTestJobContainerLogs(c *gin.Context) { return } - // job名称使用全小写,避免出现subdomain错误 + // Use all lowercase job names to avoid subdomain errors ctx.Resp, ctx.Err = logservice.GetTestJobContainerLogs(c.Param("pipelineName"), c.Param("testName"), taskID, ctx.Logger) } @@ -83,7 +83,7 @@ func GetWorkflowTestJobContainerLogs(c *gin.Context) { return } - // job名称使用全小写,避免出现subdomain错误 + // Use all lowercase job names to avoid subdomain errors ctx.Resp, ctx.Err = logservice.GetWorkflowTestJobContainerLogs(c.Param("pipelineName"), c.Param("serviceName"), c.Query("workflowType"), taskID, ctx.Logger) } @@ -125,6 +125,6 @@ func GetWorkflowBuildV3JobContainerLogs(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - // job名称使用全小写,避免出现subdomain错误 + // Use all lowercase job names to avoid subdomain errors ctx.Resp, ctx.Err = logservice.GetWorkflowBuildV3JobContainerLogs(strings.ToLower(c.Param("workflowName")), c.Query("type"), taskID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index ea9a1205..cc9cb8cc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -37,7 +37,7 @@ import ( func CreateWorkflowV3(user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) (string, error) { if !checkWorkflowSubModules(workflowModel) { - errStr := "工作流没有子模块,请先设置子模块" + errStr := "Workflow has no sub-module, please set the sub-module first" return "", e.ErrUpsertWorkflow.AddDesc(errStr) } if err := ensureWorkflowV3(workflowModel, logger); err != nil { @@ -165,7 +165,7 @@ func GetWorkflowV3Detail(id string, logger *zap.SugaredLogger) (*WorkflowV3, err func UpdateWorkflowV3(id, user string, workflowModel *commonmodels.WorkflowV3, logger *zap.SugaredLogger) error { if !checkWorkflowSubModules(workflowModel) { - errStr := "工作流没有子模块,请先设置子模块" + errStr := "Workflow has no sub-module, please set the sub-module first" return e.ErrCreatePipeline.AddDesc(errStr) } workflowModel.UpdatedBy = user -- Gitee From 9088c9e72ac07b9951ba375abd1bd64fedb094ef Mon Sep 17 00:00:00 2001 From: lou Date: Thu, 16 Dec 2021 19:39:28 +0800 Subject: [PATCH 1031/2647] update rolebinding naming rule Signed-off-by: lou --- pkg/cli/initconfig/cmd/init.go | 5 +- pkg/cli/upgradeassistant/cmd/migrate.go | 6 +- pkg/cli/upgradeassistant/cmd/migrate/180.go | 90 +++++++++++++++++++ .../internal/upgradepath/versions.go | 2 + pkg/config/config.go | 4 + .../aslan/core/project/service/product.go | 9 +- pkg/microservice/aslan/core/service.go | 2 +- .../picket/core/filter/service/project.go | 12 ++- .../core/repository/mongodb/role_binding.go | 7 +- .../policy/core/service/role_binding.go | 17 ++++ pkg/setting/consts.go | 20 ++--- pkg/shared/client/policy/client.go | 2 +- 12 files changed, 142 insertions(+), 34 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/180.go diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index b27f0ed8..6b465f65 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -20,7 +20,6 @@ import ( _ "embed" "encoding/base64" "encoding/json" - "fmt" "time" "github.com/spf13/cobra" @@ -169,9 +168,9 @@ func reportRegister(domain, email string) error { func presetRoleBinding(uid string) error { return policy.NewDefault().CreateOrUpdateSystemRoleBinding(&policy.RoleBinding{ - Name: fmt.Sprintf(setting.RoleBindingNameFmt, setting.RoleAdmin, setting.PresetAccount, ""), + Name: config.RoleBindingNameFromUIDAndRole(uid, setting.SystemAdmin, "*"), UID: uid, - Role: setting.RoleAdmin, + Role: string(setting.SystemAdmin), }) } diff --git a/pkg/cli/upgradeassistant/cmd/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate.go index f02b2863..9e087d21 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate.go @@ -63,7 +63,11 @@ var migrateCmd = &cobra.Command{ } func run() error { - err := upgradepath.UpgradeWithBestPath(viper.GetString("fromVersion"), viper.GetString("toVersion")) + from := viper.GetString("fromVersion") + to := viper.GetString("toVersion") + + log.Infof("Migrating from %s to %s", from, to) + err := upgradepath.UpgradeWithBestPath(from, to) if err == nil { log.Info("Migration finished") } diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go new file mode 100644 index 00000000..7fda13d6 --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -0,0 +1,90 @@ +package migrate + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +func init() { + upgradepath.AddHandler(upgradepath.V171, upgradepath.V180, V171ToV180) + upgradepath.AddHandler(upgradepath.V180, upgradepath.V171, V180ToV171) +} + +// V171ToV180 update all the roleBinding names in this format "{uid}-{roleName}-{roleNamespace}" +// Caution: this migration contains unrecoverable changes, please back up the database in advance +func V171ToV180() error { + log.Info("Migrating data from 1.7.1 to 1.8.0") + + if err := updateAllRoleBindingNames(); err != nil { + log.Errorf("Failed to update roleBinding names, err: %s", err) + return err + } + + return nil +} + +func V180ToV171() error { + log.Info("Rollback data from 1.8.0 to 1.7.1") + return nil +} + +func updateAllRoleBindingNames() error { + coll := newRoleBindingColl() + rbs, err := coll.List() + if err != nil { + log.Errorf("Failed to list roleBindings, err: %s", err) + } + + var lastErr error + for _, rb := range rbs { + newName := roleBindingName(rb) + if newName == "" || rb.Name == newName { + continue + } + + log.Infof("Update roleBinding name in namespace %s from %s to %s", rb.Namespace, rb.Name, newName) + if err = updateRoleBindingName(coll, rb, newName); err != nil { + log.Warnf("Failed to update roleBinding, err: %s", err) + lastErr = err + continue + } + } + + return lastErr +} + +func roleBindingName(rb *models.RoleBinding) string { + if len(rb.Subjects) != 1 || rb.Subjects[0].Kind != models.UserKind { + return "" + } + + return config.RoleBindingNameFromUIDAndRole(rb.Subjects[0].UID, setting.RoleType(rb.RoleRef.Name), rb.RoleRef.Namespace) +} + +func updateRoleBindingName(c *mongodb.RoleBindingColl, rb *models.RoleBinding, newName string) error { + query := bson.M{"name": rb.Name, "namespace": rb.Namespace} + + change := bson.M{"$set": bson.M{ + "name": newName, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + +func newRoleBindingColl() *mongodb.RoleBindingColl { + name := models.RoleBinding{}.TableName() + return &mongodb.RoleBindingColl{ + Collection: mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection(name), + } +} diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index cdc71233..b165680d 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -31,6 +31,7 @@ const ( V160 V170 V171 + V180 ) var versionMap versions = map[string]int{ @@ -41,6 +42,7 @@ var versionMap versions = map[string]int{ "1.6.0": V160, "1.7.0": V170, "1.7.1": V171, + "1.8.0": V180, } type versions map[string]int diff --git a/pkg/config/config.go b/pkg/config/config.go index 4f2ef7d9..43d81251 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -265,3 +265,7 @@ func AdminPassword() string { func Namespace() string { return viper.GetString(setting.ENVNamespace) } + +func RoleBindingNameFromUIDAndRole(uid string, role setting.RoleType, roleNamespace string) string { + return fmt.Sprintf("%s-%s-%s", uid, role, roleNamespace) +} diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 836b2975..af4b058b 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -546,13 +546,12 @@ func ForkProduct(username, uid, requestID string, args *template.ForkProject, lo UpdateBy: username, } err = policy.NewDefault().CreateOrUpdateRoleBinding(args.ProductName, &policy.RoleBinding{ - Name: fmt.Sprintf(setting.RoleBindingNameFmt, args.ProductName, uid, args.ProductName), UID: uid, Role: string(setting.Contributor), Public: true, }) if err != nil { - log.Error("rolebinding error") + log.Errorf("Failed to create or update roleBinding, err: %s", err) return e.ErrForkProduct } @@ -568,10 +567,10 @@ func UnForkProduct(userID string, username, productName, workflowName, envName, } } - policyClient := policy.New() - err := policyClient.DeleteRoleBinding(fmt.Sprintf(setting.RoleBindingNameFmt, userID, setting.Contributor, productName), productName) + policyClient := policy.NewDefault() + err := policyClient.DeleteRoleBinding(configbase.RoleBindingNameFromUIDAndRole(userID, setting.Contributor, ""), productName) if err != nil { - log.Error("rolebinding delete error") + log.Errorf("Failed to delete roleBinding, err: %s", err) return e.ErrForkProduct } if err := commonservice.DeleteProduct(username, envName, productName, requestID, log); err != nil { diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 42d0268c..e504bc16 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -79,7 +79,7 @@ func StartControllers(stopCh <-chan struct{}) { } func registerPolicies() { - policyClient := policy.New() + policyClient := policy.NewWithRetry() var policies []*policy.Policy for _, r := range []policyGetter{ new(workflowhandler.Router), diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index 11bce797..ee6031a4 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -17,13 +17,13 @@ limitations under the License. package service import ( - "fmt" "net/http" "net/url" "strings" "go.uber.org/zap" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" "github.com/koderover/zadig/pkg/microservice/picket/client/opa" "github.com/koderover/zadig/pkg/setting" @@ -44,19 +44,17 @@ type allowedProjectsData struct { func CreateProject(header http.Header, body []byte, qs url.Values, args *CreateProjectArgs, logger *zap.SugaredLogger) ([]byte, error) { var rbs []*policy.RoleBinding for _, uid := range args.Admins { - roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, uid, setting.Admin, args.ProductName) rbs = append(rbs, &policy.RoleBinding{ - Name: roleBindingName, + Name: configbase.RoleBindingNameFromUIDAndRole(uid, setting.ProjectAdmin, ""), UID: uid, - Role: string(setting.Admin), + Role: string(setting.ProjectAdmin), Public: true, }) } if args.Public { - roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, args.ProductName) rbs = append(rbs, &policy.RoleBinding{ - Name: roleBindingName, + Name: configbase.RoleBindingNameFromUIDAndRole("*", setting.ReadOnly, ""), UID: "*", Role: string(setting.ReadOnly), Public: true, @@ -88,7 +86,7 @@ func CreateProject(header http.Header, body []byte, qs url.Values, args *CreateP func UpdateProject(header http.Header, qs url.Values, body []byte, projectName string, public bool, logger *zap.SugaredLogger) ([]byte, error) { // role binding - roleBindingName := fmt.Sprintf(setting.RoleBindingNameFmt, "*", setting.ReadOnly, projectName) + roleBindingName := configbase.RoleBindingNameFromUIDAndRole("*", setting.ReadOnly, "") if !public { if err := policy.NewDefault().DeleteRoleBinding(roleBindingName, projectName); err != nil { logger.Errorf("Failed to delete role binding, err: %s", err) diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index d4103326..ecb2e4d0 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -26,6 +26,7 @@ import ( "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -182,7 +183,11 @@ func (c *RoleBindingColl) BulkCreate(objs []*models.RoleBinding) error { ois = append(ois, obj) } - _, err := c.InsertMany(context.TODO(), ois) + res, err := c.InsertMany(context.TODO(), ois) + if mongo.IsDuplicateKeyError(err) { + log.Warnf("Duplicate key found, inserted IDs is %v", res.InsertedIDs) + return nil + } return err } diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index eb4b4129..d2d6d3e2 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -21,8 +21,10 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" ) type RoleBinding struct { @@ -135,6 +137,8 @@ func createRoleBindingObject(ns string, rb *RoleBinding, logger *zap.SugaredLogg return nil, fmt.Errorf("role %s not found", rb.Role) } + ensureRoleBindingName(ns, rb) + return &models.RoleBinding{ Name: rb.Name, Namespace: ns, @@ -145,3 +149,16 @@ func createRoleBindingObject(ns string, rb *RoleBinding, logger *zap.SugaredLogg }, }, nil } + +func ensureRoleBindingName(ns string, rb *RoleBinding) { + if rb.Name != "" { + return + } + + nsRole := ns + if rb.Public { + nsRole = "" + } + + rb.Name = config.RoleBindingNameFromUIDAndRole(rb.UID, setting.RoleType(rb.Role), nsRole) +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 250cc4b4..a39561a1 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -356,18 +356,8 @@ const ( NormalModeProduct = "normal" ) -// roles const ( - RoleOwnerID = 3 - RoleUserID = 4 - - RoleUser = "user" // 普通用户 - RoleOwner = "owner" // 项目管理员 - RoleAdmin = "admin" // 超级管理员 - RoleContributor = "contributor" //开源项目贡献者 - SystemUser = "system" - - GuestAccount = "guest2019" + SystemUser = "system" ) // events @@ -567,10 +557,10 @@ const ChartTemplatesPath = "charts" type RoleType string const ( - Contributor RoleType = "contributor" - ReadOnly RoleType = "read-only" - Admin RoleType = "project-admin" - RoleBindingNameFmt string = "user:%s,role:%s,project:%s" + Contributor RoleType = "contributor" + ReadOnly RoleType = "read-only" + ProjectAdmin RoleType = "project-admin" + SystemAdmin RoleType = "admin" ) // ModernWorkflowType 自由编排工作流 diff --git a/pkg/shared/client/policy/client.go b/pkg/shared/client/policy/client.go index b81bce82..3692ce00 100644 --- a/pkg/shared/client/policy/client.go +++ b/pkg/shared/client/policy/client.go @@ -31,7 +31,7 @@ type Client struct { host string } -func New() *Client { +func NewWithRetry() *Client { host := config.PolicyServiceAddress() c := httpclient.New( -- Gitee From 242605cbb57408fdd3924160ddbc6ec492206863 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 16 Dec 2021 19:41:49 +0800 Subject: [PATCH 1032/2647] remove encrypt Signed-off-by: mouuii --- .../core/codehost/service/codehost.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index ac38ea9c..9c25131d 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -33,7 +33,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/tool/crypto" ) const callback = "/api/directory/codehosts/callback" @@ -112,12 +111,7 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) logger.Errorf("Marshal err:%s", err) return "", err } - aes, err := crypto.NewAes(crypto.GetAesKey()) - encrypted, err := aes.Encrypt(string(bs)) - if err != nil { - return "", err - } - return oauth.LoginURL(encrypted), nil + return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil } func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oauth.Oauth, error) { @@ -131,15 +125,14 @@ func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oa } func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { - aes, err := crypto.NewAes(crypto.GetAesKey()) - decrypted, err := aes.Decrypt(stateStr) - if err != nil { - logger.Errorf("Decrypt err:%s", err) + // TODO:validate the code + decryptedState , err := base64.URLEncoding.DecodeString(stateStr) + if err !=nil { + logger.Errorf("DecodeString err:%s", err) return "", err } - var state state - if err := json.Unmarshal([]byte(decrypted), &state); err != nil { + if err := json.Unmarshal(decryptedState, &state); err != nil { logger.Errorf("Unmarshal err:%s", err) return "", err } -- Gitee From 6a02513f972d9d31876b3ba8df19ad62baee4f27 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 16 Dec 2021 20:01:04 +0800 Subject: [PATCH 1033/2647] add comment Signed-off-by: allenshen --- .../aslan/core/environment/service/environment_group.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index da3b0e58..5651da5b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -166,6 +166,8 @@ func GetIngressInfo(product *commonmodels.Product, service *commonmodels.Service log.Errorf("failed to init kubeClient, clusterID: %s", product.ClusterID) return nil } + // need to get ingress from k8s + // serializer.NewDecoder()YamlToIngress() only supports ingress resource with apiVersion: apiVersion: extensions/v1beta1 ing, found, err := getter.GetIngress(product.Namespace, u.GetName(), kubeClient) if err != nil || !found { log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, product.Namespace, err) -- Gitee From 410f99eea5da4148aad60f85acf2dff5ed792503 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 16 Dec 2021 20:04:28 +0800 Subject: [PATCH 1034/2647] remove encrypt Signed-off-by: mouuii --- .../codehost/internal/oauth/github/github.go | 61 ------------------- .../codehost/internal/oauth/gitlab/gitlab.go | 61 ------------------- .../core/codehost/internal/oauth/oauth.go | 29 ++++++++- .../core/codehost/service/codehost.go | 16 +++-- 4 files changed, 37 insertions(+), 130 deletions(-) delete mode 100644 pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go delete mode 100644 pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go deleted file mode 100644 index b6cfba37..00000000 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/github/github.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package github - -import ( - "net/http" - - "golang.org/x/oauth2" - "golang.org/x/oauth2/github" - - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" -) - -type oAuth struct { - oauth2Config *oauth2.Config -} - -func New(callbackURL, clientID, clientSecret, address string) *oAuth { - endpoint := github.Endpoint - if address != "" { - endpoint = oauth2.Endpoint{ - AuthURL: address + "/login/oauth/authorize", - TokenURL: address + "/login/oauth/access_token", - } - } - return &oAuth{ - oauth2Config: &oauth2.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - Endpoint: endpoint, - RedirectURL: callbackURL, - Scopes: []string{"repo", "user"}, - }, - } -} - -func (o *oAuth) LoginURL(state string) string { - return o.oauth2Config.AuthCodeURL(state) -} - -func (o *oAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { - q := r.URL.Query() - if errType := q.Get("error"); errType != "" { - return nil, &oauth.OAuth2Error{errType, q.Get("error_description")} - } - return o.oauth2Config.Exchange(r.Context(), q.Get("code")) -} diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go deleted file mode 100644 index 0f651626..00000000 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab/gitlab.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gitlab - -import ( - "net/http" - - "golang.org/x/oauth2" - "golang.org/x/oauth2/gitlab" - - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" -) - -type oAuth struct { - oauth2Config *oauth2.Config -} - -func New(callbackURL, clientID, clientSecret, address string) *oAuth { - endpoint := gitlab.Endpoint - if address != "" { - endpoint = oauth2.Endpoint{ - AuthURL: address + "/oauth/authorize", - TokenURL: address + "/oauth/token", - } - } - return &oAuth{ - oauth2Config: &oauth2.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - Endpoint: endpoint, - RedirectURL: callbackURL, - Scopes: []string{"api", "read_user"}, - }, - } -} - -func (o *oAuth) LoginURL(state string) string { - return o.oauth2Config.AuthCodeURL(state) -} - -func (o *oAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { - q := r.URL.Query() - if errType := q.Get("error"); errType != "" { - return nil, &oauth.OAuth2Error{errType, q.Get("error_description")} - } - return o.oauth2Config.Exchange(r.Context(), q.Get("code")) -} diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index 5c43e096..fda921db 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -23,9 +23,8 @@ import ( "golang.org/x/oauth2" ) -type Oauth interface { - LoginURL(state string) (loginURL string) - HandleCallback(r *http.Request) (*oauth2.Token, error) +type OAuth struct { + oauth2Config *oauth2.Config } type OAuth2Error struct { @@ -39,3 +38,27 @@ func (e *OAuth2Error) Error() string { } return fmt.Sprintf("%s: %s", e.Err, e.Description) } + +func New(callbackURL, clientID, clientSecret string,scopes []string,endpoint oauth2.Endpoint) *OAuth { + return &OAuth{ + oauth2Config: &oauth2.Config{ + ClientID: clientID, + ClientSecret: clientSecret, + Endpoint: endpoint, + RedirectURL: callbackURL, + Scopes: []string{"api", "read_user"}, + }, + } +} + +func (o *OAuth) LoginURL(state string) string { + return o.oauth2Config.AuthCodeURL(state) +} + +func (o *OAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { + q := r.URL.Query() + if errType := q.Get("error"); errType != "" { + return nil, &OAuth2Error{errType, q.Get("error_description")} + } + return o.oauth2Config.Exchange(r.Context(), q.Get("code")) +} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 9c25131d..53e1317e 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -26,10 +26,9 @@ import ( "time" "go.uber.org/zap" + "golang.org/x/oauth2" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth/github" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth/gitlab" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -114,18 +113,25 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil } -func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (oauth.Oauth, error) { +func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (*oauth.OAuth, error) { switch provider { case systemconfig.GitHubProvider: - return github.New(callbackURL, clientID, clientSecret, address), nil + return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"},oauth2.Endpoint{ + AuthURL: address+"/login/oauth/authorize", + TokenURL: address+"/login/oauth/access_token", + }), nil case systemconfig.GitLabProvider: - return gitlab.New(callbackURL, clientID, clientSecret, address), nil + return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"},oauth2.Endpoint{ + AuthURL: address+"/oauth/authorize", + TokenURL: address+"/oauth/token", + }), nil } return nil, errors.New("illegal provider") } func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { // TODO:validate the code + // https://www.jianshu.com/p/c7c8f51713b6 decryptedState , err := base64.URLEncoding.DecodeString(stateStr) if err !=nil { logger.Errorf("DecodeString err:%s", err) -- Gitee From 332f48c62d91ab3c2b9de07def6aaef1babe4c7a Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 16 Dec 2021 20:16:58 +0800 Subject: [PATCH 1035/2647] remove encrypt Signed-off-by: mouuii --- .../core/codehost/service/codehost.go | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 53e1317e..12e877ac 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -96,7 +96,7 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) return "", err } callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) - oauth, err := NewOAuth(codeHost.Type, callbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + oauth, err := newOAuth(codeHost.Type, callbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("NewOAuth:%s err:%s", codeHost.Type, err) return "", err @@ -113,22 +113,6 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) return oauth.LoginURL(base64.URLEncoding.EncodeToString(bs)), nil } -func NewOAuth(provider, callbackURL, clientID, clientSecret, address string) (*oauth.OAuth, error) { - switch provider { - case systemconfig.GitHubProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"},oauth2.Endpoint{ - AuthURL: address+"/login/oauth/authorize", - TokenURL: address+"/login/oauth/access_token", - }), nil - case systemconfig.GitLabProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"},oauth2.Endpoint{ - AuthURL: address+"/oauth/authorize", - TokenURL: address+"/oauth/token", - }), nil - } - return nil, errors.New("illegal provider") -} - func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { // TODO:validate the code // https://www.jianshu.com/p/c7c8f51713b6 @@ -137,36 +121,52 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) logger.Errorf("DecodeString err:%s", err) return "", err } - var state state - if err := json.Unmarshal(decryptedState, &state); err != nil { + var sta state + if err := json.Unmarshal(decryptedState, &sta); err != nil { logger.Errorf("Unmarshal err:%s", err) return "", err } - codehost, err := GetCodeHost(state.CodeHostID, logger) + codehost, err := GetCodeHost(sta.CodeHostID, logger) if err != nil { - return handle(state.RedirectURL, err) + return handle(sta.RedirectURL, err) } - redirectParsedURL, err := url.Parse(state.RedirectURL) + redirectParsedURL, err := url.Parse(sta.RedirectURL) if err != nil { - logger.Errorf("ParseURL:%s err:%s", state.RedirectURL, err) + logger.Errorf("ParseURL:%s err:%s", sta.RedirectURL, err) return "", err } callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) - o, err := NewOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + o, err := newOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { - return handle(state.RedirectURL, err) + return handle(sta.RedirectURL, err) } token, err := o.HandleCallback(r) if err != nil { - return handle(state.RedirectURL, err) + return handle(sta.RedirectURL, err) } codehost.AccessToken = token.AccessToken codehost.RefreshToken = token.RefreshToken if _, err := UpdateCodeHostByToken(codehost, logger); err != nil { logger.Errorf("UpdateCodeHostByToken err:%s", err) - return handle(state.RedirectURL, err) + return handle(sta.RedirectURL, err) } - return handle(state.RedirectURL, nil) + return handle(sta.RedirectURL, nil) +} + +func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*oauth.OAuth, error) { + switch provider { + case systemconfig.GitHubProvider: + return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"},oauth2.Endpoint{ + AuthURL: address+"/login/oauth/authorize", + TokenURL: address+"/login/oauth/access_token", + }), nil + case systemconfig.GitLabProvider: + return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"},oauth2.Endpoint{ + AuthURL: address+"/oauth/authorize", + TokenURL: address+"/oauth/token", + }), nil + } + return nil, errors.New("illegal provider") } func handle(redirectURL string, err error) (string, error) { -- Gitee From 4303fe2d475aa27da60202b04ec5c4db7ff781a1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 16 Dec 2021 20:23:33 +0800 Subject: [PATCH 1036/2647] remove encrypt Signed-off-by: mouuii --- .../core/codehost/internal/oauth/oauth.go | 4 ++-- .../core/codehost/service/codehost.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index fda921db..e92e653e 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -39,14 +39,14 @@ func (e *OAuth2Error) Error() string { return fmt.Sprintf("%s: %s", e.Err, e.Description) } -func New(callbackURL, clientID, clientSecret string,scopes []string,endpoint oauth2.Endpoint) *OAuth { +func New(callbackURL, clientID, clientSecret string, scopes []string, endpoint oauth2.Endpoint) *OAuth { return &OAuth{ oauth2Config: &oauth2.Config{ ClientID: clientID, ClientSecret: clientSecret, Endpoint: endpoint, RedirectURL: callbackURL, - Scopes: []string{"api", "read_user"}, + Scopes: scopes, }, } } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 12e877ac..37116116 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -116,8 +116,8 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) (string, error) { // TODO:validate the code // https://www.jianshu.com/p/c7c8f51713b6 - decryptedState , err := base64.URLEncoding.DecodeString(stateStr) - if err !=nil { + decryptedState, err := base64.URLEncoding.DecodeString(stateStr) + if err != nil { logger.Errorf("DecodeString err:%s", err) return "", err } @@ -156,14 +156,14 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*oauth.OAuth, error) { switch provider { case systemconfig.GitHubProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"},oauth2.Endpoint{ - AuthURL: address+"/login/oauth/authorize", - TokenURL: address+"/login/oauth/access_token", + return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"}, oauth2.Endpoint{ + AuthURL: address + "/login/oauth/authorize", + TokenURL: address + "/login/oauth/access_token", }), nil case systemconfig.GitLabProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"},oauth2.Endpoint{ - AuthURL: address+"/oauth/authorize", - TokenURL: address+"/oauth/token", + return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"}, oauth2.Endpoint{ + AuthURL: address + "/oauth/authorize", + TokenURL: address + "/oauth/token", }), nil } return nil, errors.New("illegal provider") -- Gitee From 24cd8c906321963e382a3b556fa19851b14e7d57 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 16 Dec 2021 20:32:48 +0800 Subject: [PATCH 1037/2647] remove encrypt Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 37116116..53e26bbb 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -156,12 +156,12 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*oauth.OAuth, error) { switch provider { case systemconfig.GitHubProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"}, oauth2.Endpoint{ + return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"}, oauth2.Endpoint{ AuthURL: address + "/login/oauth/authorize", TokenURL: address + "/login/oauth/access_token", }), nil case systemconfig.GitLabProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"repo", "user"}, oauth2.Endpoint{ + return oauth.New(callbackURL, clientID, clientSecret, []string{"api", "read_user"}, oauth2.Endpoint{ AuthURL: address + "/oauth/authorize", TokenURL: address + "/oauth/token", }), nil -- Gitee From 84ad49e61ce6446842662e8f687e9a7cc986a1c5 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 16 Dec 2021 20:55:23 +0800 Subject: [PATCH 1038/2647] fix copyenv format Signed-off-by: ddh27 --- .../aslan/core/environment/handler/environment.go | 4 ++-- .../aslan/core/environment/service/environment.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 5ad625f8..30e286cc 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -174,9 +174,9 @@ func copyHelmProduct(c *gin.Context, ctx *internalhandler.Context) { createArgs := make([]*service.CreateHelmProductArg, 0) data, err := c.GetRawData() if err != nil { - log.Errorf("copyHelmProduct c.GetRawData() err : %v", err) + log.Errorf("copyHelmProduct c.GetRawData() err : %s", err) } else if err = json.Unmarshal(data, &createArgs); err != nil { - log.Errorf("copyHelmProduct json.Unmarshal err : %v", err) + log.Errorf("copyHelmProduct json.Unmarshal err : %s", err) } if err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 038d95f3..ee0cdb57 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -827,7 +827,7 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH EnvName: arg.BaseEnvName, }) if err != nil { - return fmt.Errorf("failed to query base product info, name %s", arg.BaseEnvName) + return fmt.Errorf("failed to query base product info name :%s,envname:%s", productName, arg.BaseEnvName) } productInfo.ID = primitive.NilObjectID productInfo.Revision = 1 @@ -881,8 +881,8 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH ChartInfos: productInfo.ChartInfos, }, log) if err != nil { - log.Errorf("rennderset create fail when copy creating helm product, productName: %s", arg.ProductName) - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", arg.ProductName)) + log.Errorf("rennderset create fail when copy creating helm product, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err)) } } -- Gitee From 40e3dd5ebd3dd4faf085813ba462d5bbe057750b Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 16 Dec 2021 22:15:49 +0800 Subject: [PATCH 1039/2647] fix delivery version create fail bug Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 69 ++++++++++++++++--- .../aslan/core/environment/service/helm.go | 28 ++++++-- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index d6a25a07..d4a0cc08 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -49,12 +49,14 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util/converter" fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -105,6 +107,7 @@ type DeliveryChartData struct { ProductService *commonmodels.ProductService RenderChart *template.RenderChart RenderSet *commonmodels.RenderSet + ValuesInEnv map[string]interface{} } type DeliveryChartResp struct { @@ -511,14 +514,36 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { } // find all images in one single chart -func extractImages(productService *commonmodels.ProductService, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { +func extractImages(cData *DeliveryChartData, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { + + flatMap, err := converter.Flatten(cData.ValuesInEnv) + if err != nil { + return nil, err + } + + serviceObj := cData.ServiceObj + imagePathSpecs := make([]map[string]string, 0) + for _, container := range serviceObj.Containers { + imageSearchRule := &template.ImageSearchingRule{ + Repo: container.ImagePath.Repo, + Image: container.ImagePath.Image, + Tag: container.ImagePath.Tag, + } + pattern := imageSearchRule.GetSearchingPattern() + imagePathSpecs = append(imagePathSpecs, pattern) + } + imageUrlsSet := sets.NewString() - for _, container := range productService.Containers { - imageUrlsSet.Insert(container.Image) + for _, spec := range imagePathSpecs { + imageUrl, err := commonservice.GeneImageURI(spec, flatMap) + if err != nil { + return nil, err + } + imageUrlsSet.Insert(imageUrl) } ret := &ServiceImageDetails{ - ServiceName: productService.ServiceName, + ServiceName: cData.ProductService.ServiceName, Images: make([]*ImageUrlDetail, 0), } @@ -555,7 +580,7 @@ func extractImages(productService *commonmodels.ProductService, registryMap map[ } // ensure chart files exist -func ensureChartFiles(chartData *DeliveryChartData) (string, error) { +func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) (string, error) { serviceObj := chartData.ServiceObj revisionBasePath := config.LocalDeliveryChartPathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) deliveryChartPath := filepath.Join(revisionBasePath, serviceObj.ServiceName) @@ -581,11 +606,30 @@ func ensureChartFiles(chartData *DeliveryChartData) (string, error) { return "", err } - mergedValuesYaml, err := helmtool.MergeOverrideValues(chartData.RenderChart.ValuesYaml, chartData.RenderSet.DefaultValues, chartData.RenderChart.GetOverrideYaml(), chartData.RenderChart.OverrideValues) + restConfig, err := kube.GetRESTConfig(prod.ClusterID) + if err != nil { + log.Errorf("get rest config error: %v", err) + return "", err + } + helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + if err != nil { + log.Errorf("[%s][%s] init helm client error: %v", prod.ProductName, prod.Namespace, err) + return "", err + } + + releaseName := fmt.Sprintf("%s-%s", prod.Namespace, serviceObj.ServiceName) + valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { + log.Errorf("failed to get values map data %s", err) return "", err } - err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644) + + currentValuesYaml, err := yaml.Marshal(valuesMap) + if err != nil { + return "", err + } + + err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), currentValuesYaml, 0644) if err != nil { return "", errors.Wrapf(err, "failed to write values.yaml") } @@ -593,10 +637,10 @@ func ensureChartFiles(chartData *DeliveryChartData) (string, error) { return deliveryChartPath, nil } -func buildChartPackage(chartData *DeliveryChartData, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, registryMap map[string]*commonmodels.RegistryNamespace) error { +func buildChartPackage(chartData *DeliveryChartData, product *commonmodels.Product, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, registryMap map[string]*commonmodels.RegistryNamespace) error { serviceObj := chartData.ServiceObj - deliveryChartPath, err := ensureChartFiles(chartData) + deliveryChartPath, err := ensureChartFiles(chartData, product) if err != nil { return err } @@ -612,6 +656,9 @@ func buildChartPackage(chartData *DeliveryChartData, chartRepo *commonmodels.Hel return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) } + // hold the currently running yaml data + chartData.ValuesInEnv = valuesYamlData + // write values.yaml file before load if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { @@ -886,13 +933,13 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - err := buildChartPackage(cData, repoInfo, dir, args.GlobalVariables, registryMap) + err := buildChartPackage(cData, deliveryVersion.ProductEnvInfo, repoInfo, dir, args.GlobalVariables, registryMap) if err != nil { logger.Errorf("failed to build chart package, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) return } - imageData, err := extractImages(cData.ProductService, registryMap) + imageData, err := extractImages(cData, registryMap) if err != nil { logger.Errorf("failed to extract image data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 7ab2157d..0201c6bf 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -27,6 +27,7 @@ import ( "github.com/otiai10/copy" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -141,7 +142,8 @@ func loadChartFilesInfo(productName, serviceName string, revision int64, dir str } //prepare chart version data -func prepareChartVersionData(productName string, serviceObj *models.Service, renderChart *template.RenderChart, renderset *models.RenderSet) error { +func prepareChartVersionData(prod *models.Product, serviceObj *models.Service, renderChart *template.RenderChart, renderset *models.RenderSet) error { + productName := prod.ProductName serviceName, revision := serviceObj.ServiceName, serviceObj.Revision base := config.LocalServicePathWithRevision(productName, serviceName, revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { @@ -161,13 +163,31 @@ func prepareChartVersionData(productName string, serviceObj *models.Service, ren return err } - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, renderset.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + restConfig, err := kube.GetRESTConfig(prod.ClusterID) + if err != nil { + log.Errorf("get rest config error: %v", err) + return err + } + helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + if err != nil { + log.Errorf("[%s][%s] init helm client error: %v", prod.EnvName, productName, err) + return err + } + + releaseName := fmt.Sprintf("%s-%s", prod.Namespace, serviceObj.ServiceName) + valuesMap, err := helmClient.GetReleaseValues(releaseName, true) + if err != nil { + log.Errorf("failed to get values map data %s", err) + return err + } + + currentValuesYaml, err := yaml.Marshal(valuesMap) if err != nil { return err } // write values.yaml - if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), []byte(mergedValuesYaml), 0644); err != nil { + if err = os.WriteFile(filepath.Join(deliveryChartPath, setting.ValuesYaml), currentValuesYaml, 0644); err != nil { return err } @@ -245,7 +265,7 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog errList = multierror.Append(errList, fmt.Errorf("failed to find render chart for service %s in target namespace", serviceName)) return } - err = prepareChartVersionData(productName, serviceObj, renderChart, renderSet) + err = prepareChartVersionData(prod, serviceObj, renderChart, renderSet) if err != nil { errList = multierror.Append(errList, fmt.Errorf("failed to prepare chart info for service %s", serviceObj.ServiceName)) return -- Gitee From 96e8ead6762af4486b8c7c74839852432cb9e5d5 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Thu, 16 Dec 2021 22:30:56 +0800 Subject: [PATCH 1040/2647] fix(reaper):forcing IPv4 transport with apt-get. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index f0f7bcbd..87054260 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -23,8 +23,8 @@ RUN apt-get clean && apt-get update && apt-get install -y \ librrd-dev \ sudo -# Upgrade Git to latest version -RUN apt-get install -y software-properties-common && add-apt-repository -y ppa:git-core/ppa && apt-get update && apt-get install -y git +# Upgrade Git to latest version,forcing IPv4 transport with apt-get +RUN apt-get -o Acquire::ForceIPv4=true install -y software-properties-common && add-apt-repository -y ppa:git-core/ppa && apt-get -o Acquire::ForceIPv4=true update && apt-get -o Acquire::ForceIPv4=true install -y git # 修改时区 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -- Gitee From 0dd89c76caf8fd3528ba962386f6261d0443fd70 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Thu, 16 Dec 2021 23:18:09 +0800 Subject: [PATCH 1041/2647] fix(reaper):do not using cache when build reaper. Signed-off-by: leozhang2018 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 224828be..1294f2d3 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/amd64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @docker build -f docker/dist/amd64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . + @docker build --progress=plain --no-cache -f docker/dist/amd64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.amd64: %.image.amd64 -- Gitee From 717bd4ce8fa7c1240f65fe0c585e3a395d6ce620 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 17 Dec 2021 09:56:12 +0800 Subject: [PATCH 1042/2647] update after review Signed-off-by: lou --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 7fda13d6..9c86a431 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -43,6 +43,7 @@ func updateAllRoleBindingNames() error { rbs, err := coll.List() if err != nil { log.Errorf("Failed to list roleBindings, err: %s", err) + return err } var lastErr error -- Gitee From 4293adea3e529c4ebc69cb900acc5f4eeef6b751 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 10:13:50 +0800 Subject: [PATCH 1043/2647] fix(dockerfile):using multi run in file. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 87054260..c529ab94 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -24,7 +24,14 @@ RUN apt-get clean && apt-get update && apt-get install -y \ sudo # Upgrade Git to latest version,forcing IPv4 transport with apt-get -RUN apt-get -o Acquire::ForceIPv4=true install -y software-properties-common && add-apt-repository -y ppa:git-core/ppa && apt-get -o Acquire::ForceIPv4=true update && apt-get -o Acquire::ForceIPv4=true install -y git +RUN apt-get -o Acquire::ForceIPv4=true install -y software-properties-common + +RUN add-apt-repository -y ppa:git-core/ppa + +# Forcing IPv4 transport with apt-get +RUN apt-get -o Acquire::ForceIPv4=true update + +RUN apt-get -o Acquire::ForceIPv4=true install -y git # 修改时区 RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -- Gitee From 7b206dfd6e61a0bdbed0d4c0b62f49af5a5ca2ae Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 17 Dec 2021 10:18:23 +0800 Subject: [PATCH 1044/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/service/codehost.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 53e26bbb..36865e28 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -92,11 +92,15 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) } redirectParsedURL, err := url.Parse(redirectURI) if err != nil { - logger.Errorf("Parsere directURI:%s err:%s", redirectURI, err) + logger.Errorf("Parse redirectURI:%s err:%s", redirectURI, err) return "", err } - callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) - oauth, err := newOAuth(codeHost.Type, callbackURL, codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + callbackURL := url.URL{ + Scheme: redirectParsedURL.Scheme, + Host: redirectParsedURL.Host, + Path: callback, + } + oauth, err := newOAuth(codeHost.Type, callbackURL.String(), codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("NewOAuth:%s err:%s", codeHost.Type, err) return "", err @@ -135,8 +139,12 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) logger.Errorf("ParseURL:%s err:%s", sta.RedirectURL, err) return "", err } - callbackURL := fmt.Sprintf("%s://%s%s", redirectParsedURL.Scheme, redirectParsedURL.Host, callback) - o, err := newOAuth(codehost.Type, callbackURL, codehost.ApplicationId, codehost.ClientSecret, codehost.Address) + callbackURL := url.URL{ + Scheme: redirectParsedURL.Scheme, + Host: redirectParsedURL.Host, + Path: callback, + } + o, err := newOAuth(codehost.Type, callbackURL.String(), codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { return handle(sta.RedirectURL, err) } -- Gitee From 73889c586de2c663b559b4d81a94a95ddca1a6ed Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 17 Dec 2021 10:24:42 +0800 Subject: [PATCH 1045/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/delivery/service/version.go | 7 ++++--- pkg/microservice/aslan/core/environment/service/helm.go | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index d4a0cc08..aa1b790d 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -56,6 +56,7 @@ import ( helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" @@ -608,16 +609,16 @@ func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) restConfig, err := kube.GetRESTConfig(prod.ClusterID) if err != nil { - log.Errorf("get rest config error: %v", err) + log.Errorf("get rest config error: %s", err) return "", err } helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) if err != nil { - log.Errorf("[%s][%s] init helm client error: %v", prod.ProductName, prod.Namespace, err) + log.Errorf("[%s][%s] init helm client error: %s", prod.ProductName, prod.Namespace, err) return "", err } - releaseName := fmt.Sprintf("%s-%s", prod.Namespace, serviceObj.ServiceName) + releaseName := util.GeneHelmReleaseName(prod.Namespace, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { log.Errorf("failed to get values map data %s", err) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 0201c6bf..b7295f19 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -165,16 +165,16 @@ func prepareChartVersionData(prod *models.Product, serviceObj *models.Service, r restConfig, err := kube.GetRESTConfig(prod.ClusterID) if err != nil { - log.Errorf("get rest config error: %v", err) + log.Errorf("get rest config error: %s", err) return err } helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) if err != nil { - log.Errorf("[%s][%s] init helm client error: %v", prod.EnvName, productName, err) + log.Errorf("[%s][%s] init helm client error: %s", prod.EnvName, productName, err) return err } - releaseName := fmt.Sprintf("%s-%s", prod.Namespace, serviceObj.ServiceName) + releaseName := util.GeneHelmReleaseName(prod.Namespace, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { log.Errorf("failed to get values map data %s", err) -- Gitee From bd2ce24236be024abe08cee0788cd3b11c6683ec Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 10:27:33 +0800 Subject: [PATCH 1046/2647] fix(dockerfile):add PPA to source list manually. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index c529ab94..4fca5e7f 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -24,9 +24,11 @@ RUN apt-get clean && apt-get update && apt-get install -y \ sudo # Upgrade Git to latest version,forcing IPv4 transport with apt-get -RUN apt-get -o Acquire::ForceIPv4=true install -y software-properties-common +RUN apt-get -o Acquire::ForceIPv4=true install -y software-properties-common -RUN add-apt-repository -y ppa:git-core/ppa +RUN echo "deb http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list + +RUN echo "deb-src http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list # Forcing IPv4 transport with apt-get RUN apt-get -o Acquire::ForceIPv4=true update -- Gitee From 3dfd3fbac4eb2c11bfb4987b224fdab26234ea02 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 10:43:25 +0800 Subject: [PATCH 1047/2647] fix(dockerfile):add signing key manually. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 4fca5e7f..99aa6565 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -30,6 +30,8 @@ RUN echo "deb http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/ RUN echo "deb-src http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list +RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E1DD270288B4E6030699E45FA1715D88E1DF1F24 + # Forcing IPv4 transport with apt-get RUN apt-get -o Acquire::ForceIPv4=true update -- Gitee From 5dab4024eca5b205bd9eb201bdabffc7158db12b Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 17 Dec 2021 10:50:18 +0800 Subject: [PATCH 1048/2647] code refactor Signed-off-by: mouuii --- .../core/codehost/service/codehost.go | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 36865e28..18b6d62c 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -130,15 +130,15 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) logger.Errorf("Unmarshal err:%s", err) return "", err } - codehost, err := GetCodeHost(sta.CodeHostID, logger) - if err != nil { - return handle(sta.RedirectURL, err) - } redirectParsedURL, err := url.Parse(sta.RedirectURL) if err != nil { logger.Errorf("ParseURL:%s err:%s", sta.RedirectURL, err) return "", err } + codehost, err := GetCodeHost(sta.CodeHostID, logger) + if err != nil { + return handle(redirectParsedURL, err) + } callbackURL := url.URL{ Scheme: redirectParsedURL.Scheme, Host: redirectParsedURL.Host, @@ -146,19 +146,19 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) } o, err := newOAuth(codehost.Type, callbackURL.String(), codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { - return handle(sta.RedirectURL, err) + return handle(redirectParsedURL, err) } token, err := o.HandleCallback(r) if err != nil { - return handle(sta.RedirectURL, err) + return handle(redirectParsedURL, err) } codehost.AccessToken = token.AccessToken codehost.RefreshToken = token.RefreshToken if _, err := UpdateCodeHostByToken(codehost, logger); err != nil { logger.Errorf("UpdateCodeHostByToken err:%s", err) - return handle(sta.RedirectURL, err) + return handle(redirectParsedURL, err) } - return handle(sta.RedirectURL, nil) + return handle(redirectParsedURL, nil) } func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*oauth.OAuth, error) { @@ -177,15 +177,11 @@ func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*o return nil, errors.New("illegal provider") } -func handle(redirectURL string, err error) (string, error) { - u, parseErr := url.Parse(redirectURL) - if parseErr != nil { - return "", parseErr - } +func handle(url *url.URL, err error) (string, error) { if err != nil { - u.Query().Add("err", err.Error()) + url.Query().Add("err", err.Error()) } else { - u.Query().Add("success", "true") + url.Query().Add("success", "true") } - return u.String(), nil + return url.String(), nil } -- Gitee From 4a5a9358ad9cff9b55ee11b9ada73cf2a4eeabc0 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 11:01:57 +0800 Subject: [PATCH 1049/2647] fix(dockerfile):use 80 port to retrive key. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 99aa6565..1aaae883 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -30,7 +30,7 @@ RUN echo "deb http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/ RUN echo "deb-src http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list -RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E1DD270288B4E6030699E45FA1715D88E1DF1F24 +RUN apt-key adv --keyserver keyserver.ubuntu.com:80 --recv-keys E1DD270288B4E6030699E45FA1715D88E1DF1F24 # Forcing IPv4 transport with apt-get RUN apt-get -o Acquire::ForceIPv4=true update -- Gitee From 94285e8bd45425242f9f6927fd3a76b98e51d671 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 17 Dec 2021 11:11:34 +0800 Subject: [PATCH 1050/2647] code refactor Signed-off-by: mouuii --- .../systemconfig/core/codehost/service/codehost.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 18b6d62c..0700bc51 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -95,12 +95,8 @@ func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) logger.Errorf("Parse redirectURI:%s err:%s", redirectURI, err) return "", err } - callbackURL := url.URL{ - Scheme: redirectParsedURL.Scheme, - Host: redirectParsedURL.Host, - Path: callback, - } - oauth, err := newOAuth(codeHost.Type, callbackURL.String(), codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) + redirectParsedURL.Path = callback + oauth, err := newOAuth(codeHost.Type, redirectParsedURL.String(), codeHost.ApplicationId, codeHost.ClientSecret, codeHost.Address) if err != nil { logger.Errorf("NewOAuth:%s err:%s", codeHost.Type, err) return "", err -- Gitee From e11c84ad35aed68a6cc3af04aa3d51dcff7cbf12 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 17 Dec 2021 11:17:51 +0800 Subject: [PATCH 1051/2647] optimize code Signed-off-by: liu deyi --- .../common/repository/models/workflow_v3.go | 40 +++++++++---------- .../core/workflow/service/workflow/types.go | 38 +++++++++--------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go index 4b33b049..70c6830b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v3.go @@ -34,35 +34,33 @@ type WorkflowV3 struct { UpdateTime int64 `bson:"update_time" json:"update_time"` } +type ParameterSettingType string + +const ( + StringType ParameterSettingType = "string" + ChoiceType ParameterSettingType = "choice" + ExternalType ParameterSettingType = "external" +) + type ParameterSetting struct { // External type parameter will NOT use this key. - Key string `bson:"key" json:"key"` - // Type list: - // string - // choice - // external - Type string `bson:"type" json:"type"` - //DefaultValue is the + Key string `bson:"key" json:"key"` + Type ParameterSettingType `bson:"type" json:"type"` + //DefaultValue DefaultValue string `bson:"default_value" json:"default_value"` - // choiceOption 是枚举的所有选项 + // choiceOption Are all options enumerated ChoiceOption []string `bson:"choice_option" json:"choice_option"` - // ExternalSetting 是外部系统获取变量的配置 + // ExternalSetting It is the configuration of the external system to obtain the variable ExternalSetting *ExternalSetting `bson:"external_setting" json:"external_setting"` } type ExternalSetting struct { - // 外部系统ID - SystemID string `bson:"system_id" json:"system_id"` - // Endpoint路径 - Endpoint string `bson:"endpoint" json:"endpoint"` - // 请求方法 - Method string `bson:"method" json:"method"` - // 请求头 - Headers []*KV `bson:"headers" json:"headers"` - // 请求体 - Body string `bson:"body" json:"body"` - // 外部变量配置 - Params []*ExternalParamMapping `bson:"params" json:"params"` + SystemID string `bson:"system_id" json:"system_id"` + Endpoint string `bson:"endpoint" json:"endpoint"` + Method string `bson:"method" json:"method"` + Headers []*KV `bson:"headers" json:"headers"` + Body string `bson:"body" json:"body"` + Params []*ExternalParamMapping `bson:"params" json:"params"` } type KV struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/types.go b/pkg/microservice/aslan/core/workflow/service/workflow/types.go index d6fd439a..cc30372b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/types.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/types.go @@ -31,35 +31,33 @@ type WorkflowV3Brief struct { ProjectName string `json:"project_name"` } +type ParameterSettingType string + +const ( + StringType ParameterSettingType = "string" + ChoiceType ParameterSettingType = "choice" + ExternalType ParameterSettingType = "external" +) + type ParameterSetting struct { // External type parameter will NOT use this key. - Key string `json:"key"` - // Type list: - // string - // choice - // external - Type string `json:"type"` + Key string `json:"key"` + Type ParameterSettingType `json:"type"` //DefaultValue is the DefaultValue string `json:"default_value"` - // choiceOption 是枚举的所有选项 + // choiceOption Are all options enumerated ChoiceOption []string `json:"choice_option"` - // ExternalSetting 是外部系统获取变量的配置 + // ExternalSetting It is the configuration of the external system to obtain the variable ExternalSetting *ExternalSetting `json:"external_setting"` } type ExternalSetting struct { - // 外部系统ID - SystemID string `json:"system_id"` - // Endpoint路径 - Endpoint string `json:"endpoint"` - // 请求方法 - Method string `json:"method"` - // 请求头 - Headers []*KV `json:"headers"` - // 请求体 - Body string `json:"body"` - // 外部变量配置 - Params []*ExternalParamMapping `json:"params"` + SystemID string `json:"system_id"` + Endpoint string `json:"endpoint"` + Method string `json:"method"` + Headers []*KV `json:"headers"` + Body string `json:"body"` + Params []*ExternalParamMapping `json:"params"` } type KV struct { -- Gitee From 88dcbab22490822d2df87eb1d34a0da1781f62f4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 17 Dec 2021 11:24:21 +0800 Subject: [PATCH 1052/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_v3.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go index cc9cb8cc..aaa91ecd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v3.go @@ -201,20 +201,20 @@ func GetWorkflowV3Args(id string, logger *zap.SugaredLogger) ([]*WorkflowV3TaskA switch param.Type { case "string": resp = append(resp, &WorkflowV3TaskArgs{ - Type: param.Type, + Type: string(StringType), Key: param.Key, Value: param.DefaultValue, }) case "choice": resp = append(resp, &WorkflowV3TaskArgs{ - Type: param.Type, + Type: string(ChoiceType), Key: param.Key, Value: param.DefaultValue, Choice: param.ChoiceOption, }) case "external": externalEnv := &WorkflowV3TaskArgs{ - Type: param.Type, + Type: string(ExternalType), } for _, kv := range param.ExternalSetting.Params { if kv.Display { -- Gitee From 5e3c5bcf5d80e23cf6cd8123897f0052d2cd5a0d Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 11:54:58 +0800 Subject: [PATCH 1053/2647] fix(dockerfile):import auth key manually. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 1aaae883..97595b63 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -24,13 +24,14 @@ RUN apt-get clean && apt-get update && apt-get install -y \ sudo # Upgrade Git to latest version,forcing IPv4 transport with apt-get -RUN apt-get -o Acquire::ForceIPv4=true install -y software-properties-common RUN echo "deb http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list RUN echo "deb-src http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list -RUN apt-key adv --keyserver keyserver.ubuntu.com:80 --recv-keys E1DD270288B4E6030699E45FA1715D88E1DF1F24 +RUN echo '-----BEGIN PGP PUBLIC KEY BLOCK----- \nVersion: \nComment: Hostname: keyserver.ubuntu.com \n \nxo0ESXjaGwEEAMA26F3+mnRW8uRqASMsEa5EsmgvUpLD7EKpC7903OpiMGSvZ2sE \n34g7W6nUQY0R//AZS2iW4ZXfvdhQTQuuPlHM6Q3iUAt+nyXcf9xBlscs8Gm722u4 \njAtFtBS4BMQRhRRfWTHwJIOM6OpGIccjPe8pQfIeoRxkKJxlehzw2mU1ABEBAAHN \nKExhdW5jaHBhZCBQUEEgZm9yIFVidW50dSBHaXQgTWFpbnRhaW5lcnPCtgQTAQIA \nIAUCSXjaGwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEKFxXYjh3x8k/zMD \n/RKBMjavvFl71YBazSOGl2YfSsZiR/ANsby3+rUaULb8uxzCHXAQnlH5vdtLSPry \naLBvzCU8C3C02qNT8jRacU2752zsCkCi1SLRSOXdI/ATJHza5aTvYV93rTITBhU4 \nsJQeK9RW0CtaDRAxJsn/Dr6J3lL/c9m9cT5fFpxOIsF4 \n=7kFR \n-----END PGP PUBLIC KEY BLOCK----- \n' > key + +RUN apt-key add key # Forcing IPv4 transport with apt-get RUN apt-get -o Acquire::ForceIPv4=true update -- Gitee From 0e2593a3b26f5588a2f00540049fc77daaaabee1 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 14:15:55 +0800 Subject: [PATCH 1054/2647] fix(dockerfile):try to use ustc's proxy for ppa Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 97595b63..1e04ecc5 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -25,9 +25,9 @@ RUN apt-get clean && apt-get update && apt-get install -y \ # Upgrade Git to latest version,forcing IPv4 transport with apt-get -RUN echo "deb http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list +RUN echo "deb http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list -RUN echo "deb-src http://ppa.launchpad.net/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list +RUN echo "deb-src http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list RUN echo '-----BEGIN PGP PUBLIC KEY BLOCK----- \nVersion: \nComment: Hostname: keyserver.ubuntu.com \n \nxo0ESXjaGwEEAMA26F3+mnRW8uRqASMsEa5EsmgvUpLD7EKpC7903OpiMGSvZ2sE \n34g7W6nUQY0R//AZS2iW4ZXfvdhQTQuuPlHM6Q3iUAt+nyXcf9xBlscs8Gm722u4 \njAtFtBS4BMQRhRRfWTHwJIOM6OpGIccjPe8pQfIeoRxkKJxlehzw2mU1ABEBAAHN \nKExhdW5jaHBhZCBQUEEgZm9yIFVidW50dSBHaXQgTWFpbnRhaW5lcnPCtgQTAQIA \nIAUCSXjaGwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEKFxXYjh3x8k/zMD \n/RKBMjavvFl71YBazSOGl2YfSsZiR/ANsby3+rUaULb8uxzCHXAQnlH5vdtLSPry \naLBvzCU8C3C02qNT8jRacU2752zsCkCi1SLRSOXdI/ATJHza5aTvYV93rTITBhU4 \nsJQeK9RW0CtaDRAxJsn/Dr6J3lL/c9m9cT5fFpxOIsF4 \n=7kFR \n-----END PGP PUBLIC KEY BLOCK----- \n' > key -- Gitee From 614429fb12f54fe65edf1028d1fd746b86281c2c Mon Sep 17 00:00:00 2001 From: landylee007 Date: Fri, 17 Dec 2021 14:29:08 +0800 Subject: [PATCH 1055/2647] typo:enhencement -> enhancement Signed-off-by: landylee007 --- .github/ISSUE_TEMPLATE/enhancement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md index 52c0c74d..3e19b041 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -1,7 +1,7 @@ --- name: Enhancement about: Suggest enhancements to existing features -title: "[enhencement]" +title: "[enhancement]" labels: enhancement assignees: lilianzhu -- Gitee From 34f36dd3ae420220b4878d85fa76f1633f4379c4 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Fri, 17 Dec 2021 14:35:34 +0800 Subject: [PATCH 1056/2647] fix(dockerfile):add some comment. Signed-off-by: leozhang2018 --- docker/service/reaper-plugin-xenial.Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 1e04ecc5..3d36050d 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -24,11 +24,12 @@ RUN apt-get clean && apt-get update && apt-get install -y \ sudo # Upgrade Git to latest version,forcing IPv4 transport with apt-get - RUN echo "deb http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list +# Using 'launchpad.proxy.ustclug.org' to reverse proxy for 'launchpad.net'. Detail:https://lug.ustc.edu.cn/wiki/mirrors/help/revproxy/ RUN echo "deb-src http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list +# Import key manually to solve bad network problems RUN echo '-----BEGIN PGP PUBLIC KEY BLOCK----- \nVersion: \nComment: Hostname: keyserver.ubuntu.com \n \nxo0ESXjaGwEEAMA26F3+mnRW8uRqASMsEa5EsmgvUpLD7EKpC7903OpiMGSvZ2sE \n34g7W6nUQY0R//AZS2iW4ZXfvdhQTQuuPlHM6Q3iUAt+nyXcf9xBlscs8Gm722u4 \njAtFtBS4BMQRhRRfWTHwJIOM6OpGIccjPe8pQfIeoRxkKJxlehzw2mU1ABEBAAHN \nKExhdW5jaHBhZCBQUEEgZm9yIFVidW50dSBHaXQgTWFpbnRhaW5lcnPCtgQTAQIA \nIAUCSXjaGwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEKFxXYjh3x8k/zMD \n/RKBMjavvFl71YBazSOGl2YfSsZiR/ANsby3+rUaULb8uxzCHXAQnlH5vdtLSPry \naLBvzCU8C3C02qNT8jRacU2752zsCkCi1SLRSOXdI/ATJHza5aTvYV93rTITBhU4 \nsJQeK9RW0CtaDRAxJsn/Dr6J3lL/c9m9cT5fFpxOIsF4 \n=7kFR \n-----END PGP PUBLIC KEY BLOCK----- \n' > key RUN apt-key add key @@ -38,16 +39,16 @@ RUN apt-get -o Acquire::ForceIPv4=true update RUN apt-get -o Acquire::ForceIPv4=true install -y git -# 修改时区 +# Change timezone to Asia/Shanghai RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -# 安装 docker client +# Install docker client RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin -# 替换tar(适配cephfs) +# Replaces the default tar(for cephfs) RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar COPY --from=build /reaper /usr/local/bin -- Gitee From d68e32ba7211f6bc60131d0662fd96315825e130 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 17 Dec 2021 15:23:28 +0800 Subject: [PATCH 1057/2647] fix bug Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 07203d34..7cb22d3f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -859,11 +859,11 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str for _, svrs := range updateProd.Services { for _, svr := range svrs { - for _, rc := range overrideCharts { + for _, rc := range renderSet.ChartInfos { if rc.ServiceName == svr.ServiceName { for _, c := range svr.Containers { image := c.Image - mergeYaml, err := helmtool.MergeOverrideValues("", renderSet.DefaultValues, rc.OverrideYaml, rc.ToOverrideValueString()) + mergeYaml, err := helmtool.MergeOverrideValues("", renderSet.DefaultValues, rc.GetOverrideYaml(), rc.OverrideValues) if err != nil { errMsg := fmt.Sprintf("Failed to MergeOverrideValues for service %s,error:%s", svr.ServiceName, err) log.Error(errMsg) -- Gitee From 35719cfe430769bd3f3faed55a243f09fe15fa8e Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 17 Dec 2021 15:38:31 +0800 Subject: [PATCH 1058/2647] whether to add a service display option in the workflow (#745) * add the option of whether to display the service in the workflow Signed-off-by: liu deyi * whether to add a service display option in the workflow Signed-off-by: liu deyi * fix panic problem Signed-off-by: liu deyi * fix panic problem Signed-off-by: liu deyi * modify show service logic Signed-off-by: liu deyi * optimize code logic Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 8 +++--- .../service/workflow/workflow_task.go | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 61fcbb4e..a02950c1 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -200,8 +200,9 @@ type BuildStage struct { // BuildModule ... type BuildModule struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` - BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` + Target *ServiceModuleTarget `bson:"target" json:"target"` + HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` + BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` } type ArtifactStage struct { @@ -211,7 +212,8 @@ type ArtifactStage struct { // ArtifactModule ... type ArtifactModule struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` + HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` + Target *ServiceModuleTarget `bson:"target" json:"target"` } type TestStage struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 08014a9c..b1c34c47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -188,6 +188,27 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D return resp } +func getHideServiceModules(workflow *commonmodels.Workflow) sets.String { + hideServiceModules := sets.NewString() + if workflow.BuildStage != nil && workflow.BuildStage.Enabled { + for _, buildModule := range workflow.BuildStage.Modules { + if buildModule.HideServiceModule { + hideServiceModules.Insert(strings.Join([]string{buildModule.Target.ProductName, buildModule.Target.ServiceName, buildModule.Target.ServiceModule}, SplitSymbol)) + } + } + } + + if workflow.ArtifactStage != nil && workflow.ArtifactStage.Enabled { + for _, artifactModule := range workflow.ArtifactStage.Modules { + if artifactModule.HideServiceModule { + hideServiceModules.Insert(strings.Join([]string{artifactModule.Target.ProductName, artifactModule.Target.ServiceName, artifactModule.Target.ServiceModule}, SplitSymbol)) + } + } + } + + return hideServiceModules +} + func getProjectTargets(productName string) []string { var targets []string productTmpl, err := template.NewProductColl().Find(productName) @@ -323,9 +344,13 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) targetMap := getProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) + hideServiceModules := getHideServiceModules(workflow) targets := make([]*commonmodels.TargetArgs, 0) if (workflow.BuildStage != nil && workflow.BuildStage.Enabled) || (workflow.ArtifactStage != nil && workflow.ArtifactStage.Enabled) { for _, container := range projectTargets { + if hideServiceModules.Has(container) { + continue + } if _, ok := targetMap[container]; !ok { continue } -- Gitee From bd8077b6c006140952fdffa0d68eb5eaf6ae7d3b Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 17 Dec 2021 15:45:13 +0800 Subject: [PATCH 1059/2647] fix bug Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7cb22d3f..8a38401a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -863,7 +863,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str if rc.ServiceName == svr.ServiceName { for _, c := range svr.Containers { image := c.Image - mergeYaml, err := helmtool.MergeOverrideValues("", renderSet.DefaultValues, rc.GetOverrideYaml(), rc.OverrideValues) + mergeYaml, err := helmtool.MergeOverrideValues(rc.ValuesYaml, renderSet.DefaultValues, rc.GetOverrideYaml(), rc.OverrideValues) if err != nil { errMsg := fmt.Sprintf("Failed to MergeOverrideValues for service %s,error:%s", svr.ServiceName, err) log.Error(errMsg) -- Gitee From b237ad7115dd5b3a88dfe1e4812435a3d925e8d0 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 17 Dec 2021 15:51:01 +0800 Subject: [PATCH 1060/2647] add template store rules Signed-off-by: lou --- .../core/service/bundle/exemption_urls.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 16ea310e..06c0f10c 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -256,6 +256,30 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"DELETE"}, Endpoints: []string{"api/v1/system-rolebindings/?*"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/template/yaml"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/template/yaml/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/template/dockerfile"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/template/dockerfile/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/template/charts"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/aslan/template/charts/?*"}, + }, } // actions which are allowed for project admins. -- Gitee From 58767265a3c800ead06ca94b57ffca2c0f2842b2 Mon Sep 17 00:00:00 2001 From: lou Date: Fri, 17 Dec 2021 18:40:53 +0800 Subject: [PATCH 1061/2647] add copyright header Signed-off-by: lou --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 16 ++++++++++++++++ .../service/webhook/github_testing_task.go | 16 ++++++++++++++++ pkg/microservice/picket/client/aslan/artifact.go | 16 ++++++++++++++++ pkg/microservice/picket/client/aslan/delivery.go | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 9c86a431..f9db3df9 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package migrate import ( diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go index fcd494c9..756781cc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package webhook import ( diff --git a/pkg/microservice/picket/client/aslan/artifact.go b/pkg/microservice/picket/client/aslan/artifact.go index 9a5042f9..abb5a36b 100644 --- a/pkg/microservice/picket/client/aslan/artifact.go +++ b/pkg/microservice/picket/client/aslan/artifact.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( diff --git a/pkg/microservice/picket/client/aslan/delivery.go b/pkg/microservice/picket/client/aslan/delivery.go index 58e11011..2eaad396 100644 --- a/pkg/microservice/picket/client/aslan/delivery.go +++ b/pkg/microservice/picket/client/aslan/delivery.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( -- Gitee From d2b2d9da9fd48c2aaa59cf3c78ad0c4ff316653d Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Fri, 17 Dec 2021 18:43:11 +0800 Subject: [PATCH 1062/2647] remove permission (#769) * remove permission for K8s/HelmChart template Signed-off-by: fansi * remove permission Signed-off-by: fansi Co-authored-by: fansi Co-authored-by: fansi --- pkg/microservice/aslan/core/project/handler/policy.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index 25760b9e..b494095e 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -38,8 +38,6 @@ rules: endpoint: "/api/aslan/project/products/?*" - method: PUT endpoint: "/api/aslan/project/products/?*/searching-rules" - - method: POST - endpoint: "/api/aslan/service/template/reload" - action: create_service alias: "新建服务" description: "" @@ -48,16 +46,10 @@ rules: endpoint: "/api/aslan/service/pm/?*" - method: POST endpoint: "/api/aslan/service/services" - - method: GET - endpoint: "/api/aslan/service/loader/preload/?*/?*" - method: POST endpoint: "/api/aslan/service/loader/load/?*/?*" - method: POST endpoint: "/api/aslan/service/helm/services" - - method: POST - endpoint: "/api/aslan/service/template/load" - - method: GET - endpoint: "/api/aslan/template/charts/?*/variables" - method: POST endpoint: "/api/aslan/service/helm/services/bulk" - action: delete_service -- Gitee From c19b39b9fb5f35662a81f3367b8b7162414bccd4 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 17 Dec 2021 18:52:42 +0800 Subject: [PATCH 1063/2647] format image pull secret name Signed-off-by: panxunying --- .../warpdrive/core/service/taskplugin/job.go | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 521c55ea..022c9aa0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -25,6 +25,7 @@ import ( "net" "os" "path" + "regexp" "sort" "strconv" "strings" @@ -549,6 +550,18 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa return job, nil } +func formatRegistryName(namespaceInRegistry string) (string, error) { + reg, err := regexp.Compile("[^a-zA-Z0-9]+") + if err != nil { + return "", err + } + processedName := reg.ReplaceAllString(namespaceInRegistry, "") + processedName = strings.ToLower(processedName) + if len(processedName) > 237 { + processedName = processedName[:237] + } + return processedName, nil +} func createOrUpdateRegistrySecrets(namespace, registryID string, registries []*task.RegistryNamespace, kubeClient client.Client) error { for _, reg := range registries { @@ -558,9 +571,13 @@ func createOrUpdateRegistrySecrets(namespace, registryID string, registries []*t arr := strings.Split(reg.Namespace, "/") namespaceInRegistry := arr[len(arr)-1] - secretName := namespaceInRegistry + registrySecretSuffix + filteredName, err := formatRegistryName(namespaceInRegistry) + if err != nil { + return err + } + secretName := filteredName + registrySecretSuffix if reg.RegType != "" { - secretName = namespaceInRegistry + "-" + reg.RegType + registrySecretSuffix + secretName = filteredName + "-" + reg.RegType + registrySecretSuffix } if reg.ID == registryID { secretName = setting.DefaultImagePullSecret -- Gitee From fd48739daeab6838f62cdaec031c44ecb257f70d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 17 Dec 2021 19:35:48 +0800 Subject: [PATCH 1064/2647] push image data bug Signed-off-by: mouuii --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 224828be..1f45c229 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ .PHONY: all IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc -VERSION ?= $(shell date +'%Y%m%d%H%M%S') +VERSION := $(shell date +'%Y%m%d%H%M%S') TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin packager-plugin resource-server ua warpdrive policy user picket config init REAPER_OS= focal xenial bionic -- Gitee From 0b4fa48920119fb7328be1b7f36edd8c536e9772 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 17 Dec 2021 19:49:37 +0800 Subject: [PATCH 1065/2647] push image data bug Signed-off-by: mouuii --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1f45c229..52926469 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ .PHONY: all IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc -VERSION := $(shell date +'%Y%m%d%H%M%S') - +VERSION ?= $(shell date +'%Y%m%d%H%M%S') +VERSION := $(VERSION) TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin packager-plugin resource-server ua warpdrive policy user picket config init REAPER_OS= focal xenial bionic -- Gitee From bc21a6e656902aa152c34a618cf9826231e2e9eb Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 17 Dec 2021 20:49:01 +0800 Subject: [PATCH 1066/2647] fix review Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ff75eff4..7ac3bc37 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -764,7 +764,17 @@ func createSingleHelmProduct(templateProduct *template.Product, requestID, userN Type: serviceTmpl.Type, Revision: serviceTmpl.Revision, } - if serviceTmpl.Type == setting.K8SDeployType || serviceTmpl.Type == setting.HelmDeployType { + if serviceTmpl.Type == setting.K8SDeployType { + serviceResp.Containers = make([]*commonmodels.Container, 0) + for _, c := range serviceTmpl.Containers { + container := &commonmodels.Container{ + Name: c.Name, + Image: c.Image, + ImagePath: c.ImagePath, + } + serviceResp.Containers = append(serviceResp.Containers, container) + } + } else if serviceTmpl.Type == setting.HelmDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { image := c.Image @@ -772,30 +782,13 @@ func createSingleHelmProduct(templateProduct *template.Product, requestID, userN if rc.ServiceName != serviceTmpl.ServiceName { continue } - mergeYaml, err := helmtool.MergeOverrideValues(rc.ValuesYaml, defaultValuesYaml, rc.GetOverrideYaml(), rc.OverrideValues) - if err != nil { - errMsg := fmt.Sprintf("Failed to MergeOverrideValues for product template %s,error:%s", productObj.ProductName, err) - log.Error(errMsg) - return e.ErrCreateEnv.AddDesc(errMsg) - } - mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) + var err error + image, err = genImageFromYaml(c, rc.ValuesYaml, defaultValuesYaml, rc.GetOverrideYaml(), rc.OverrideValues) if err != nil { - errMsg := fmt.Sprintf("mergeYamlToFlatMap product template %s,error:%s", productObj.ProductName, err) + errMsg := fmt.Sprintf("genImageFromYaml product template %s,service name:%s,error:%s", productObj.ProductName, rc.ServiceName, err) log.Error(errMsg) return e.ErrCreateEnv.AddDesc(errMsg) } - imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] - imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] - if ok2 { - if ok1 { - image = fmt.Sprintf("%s:%s", imageRepo, imageTag) - } else { - splitImage := strings.Split(image, ":") - if len(splitImage) == 2 { - image = fmt.Sprintf("%s:%s", splitImage[0], imageTag) - } - } - } } container := &commonmodels.Container{ @@ -940,43 +933,22 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } + productRespMap := productResp.GetServiceMap() for _, svrs := range updateProd.Services { for _, svr := range svrs { - for _, rc := range renderSet.ChartInfos { - if rc.ServiceName == svr.ServiceName { - for _, c := range svr.Containers { - image := c.Image - mergeYaml, err := helmtool.MergeOverrideValues(rc.ValuesYaml, renderSet.DefaultValues, rc.GetOverrideYaml(), rc.OverrideValues) - if err != nil { - errMsg := fmt.Sprintf("Failed to MergeOverrideValues for service %s,error:%s", svr.ServiceName, err) - log.Error(errMsg) - return e.ErrUpdateEnv.AddDesc(errMsg) - } - mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) - if err != nil { - errMsg := fmt.Sprintf("mergeYamlToFlatMap service %s,error:%s", svr.ServiceName, err) - log.Error(errMsg) - return e.ErrUpdateEnv.AddDesc(errMsg) - } - imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] - imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] - if ok2 { - if ok1 { - image = fmt.Sprintf("%s:%s", imageRepo, imageTag) - } else { - splitImage := strings.Split(image, ":") - if len(splitImage) == 2 { - image = fmt.Sprintf("%s:%s", splitImage[0], imageTag) - } - } - } - c.Image = image + ps, ok := productRespMap[svr.ServiceName] + if !ok { + continue + } + for _, svrc := range svr.Containers { + for _, psc := range ps.Containers { + if svrc.Name == psc.Name { + svrc.Image = psc.Image } } } } } - productResp.Services = updateProd.Services // 设置产品状态为更新中 @@ -1022,6 +994,29 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } +func genImageFromYaml(c *models.Container, valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { + mergeYaml, err := helmtool.MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues) + if err != nil { + return "", err + } + mergedValuesYamlFlattenMap, err := converter.YamlToFlatMap([]byte(mergeYaml)) + if err != nil { + return "", err + } + imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] + imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] + if ok2 { + if ok1 { + return fmt.Sprintf("%s:%s", imageRepo, imageTag), nil + } + splitImage := strings.Split(c.Image, ":") + if len(splitImage) == 2 { + return fmt.Sprintf("%s:%s", splitImage[0], imageTag), nil + } + } + return c.Image, nil +} + func prepareEstimatedData(productName, envName, serviceName, usageScenario, defaultValues string, log *zap.SugaredLogger) (string, string, error) { var err error templateService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ -- Gitee From 1816db4a9595eeb6e38adf1a3f002b818b0edc54 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 17 Dec 2021 20:54:39 +0800 Subject: [PATCH 1067/2647] fix review Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7ac3bc37..96cf3c3f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -918,7 +918,7 @@ func UpdateProductRecycleDay(envName, productName string, recycleDay int) error return commonrepo.NewProductColl().UpdateProductRecycleDay(envName, productName, recycleDay) } -func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, renderSet *models.RenderSet, log *zap.SugaredLogger) error { +func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -1334,7 +1334,7 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for env: %s", envName)) } - err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, renderSet, log) + err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, log) if err != nil { log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) return envStatuses, e.ErrUpdateEnv.AddDesc(err.Error()) -- Gitee From 7971a3fed57884f67f867d666cd53d1f47490973 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 17 Dec 2021 21:01:10 +0800 Subject: [PATCH 1068/2647] fix review Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 96cf3c3f..ce3a5852 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -944,6 +944,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str for _, psc := range ps.Containers { if svrc.Name == psc.Name { svrc.Image = psc.Image + break } } } -- Gitee From 12cbed225359f33ae934c91fac3dbfe2a1001b65 Mon Sep 17 00:00:00 2001 From: ruchikabobade Date: Fri, 17 Dec 2021 18:34:20 +0530 Subject: [PATCH 1069/2647] Issue 763: fix assignement to nil map Signed-off-by: ruchikabobade --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index a9d2dec2..d4c2ff51 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -990,7 +990,7 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s // UpdateHelmService TODO need to be deprecated func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { - var serviceMap map[string]int64 + serviceMap := make(map[string]int64) for _, helmServiceInfo := range args.HelmServiceInfos { opt := &commonrepo.ServiceFindOption{ -- Gitee From ae4a7caf858febade740bca206635a01a08172e1 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Fri, 17 Dec 2021 22:16:28 +0800 Subject: [PATCH 1070/2647] update contributors list Signed-off-by: landylee007 --- GOVERNANCE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 0b4feee4..bc24515d 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -61,4 +61,7 @@ The council will make a decision for the nomination. The candidate has to have d - [jiangpeipei327](https://github.com/jiangpeipei327) - [lyzhang1999](https://github.com/lyzhang1999) - [avtion](https://github.com/avtion) -- [wbjxxzx](https://github.com/wbjxxzx) \ No newline at end of file +- [wbjxxzx](https://github.com/wbjxxzx) +- [jtr109](https://github.com/jtr109) +- [huangzexiong](https://github.com/huangzexiong) +- [ruchikabobade](https://github.com/ruchikabobade) -- Gitee From 5fa5afae2da28369b8d6d3be4b90f1e8965a24d6 Mon Sep 17 00:00:00 2001 From: ruchikabobade Date: Sat, 18 Dec 2021 07:56:34 +0530 Subject: [PATCH 1071/2647] Issue 762: Drop unnecessary use of the blank identifier Signed-off-by: ruchikabobade --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- .../aslan/core/workflow/service/workflow/workflow.go | 2 +- pkg/microservice/picket/core/filter/service/codehost.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ee0cdb57..ef97e855 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1258,7 +1258,7 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo } // extract values.yaml and update renderset - for envName, _ := range productMap { + for envName := range productMap { renderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ Name: commonservice.GetProductEnvNamespace(envName, productName, ""), }) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index a2f167ff..95064402 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -149,7 +149,7 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { } workflowSlice := sets.NewString() - for workflowName, _ := range createArgs.argsMap { + for workflowName := range createArgs.argsMap { _, err := FindWorkflow(workflowName, log) if err == nil { workflowSlice.Insert(workflowName) diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go index f46a051f..55a4e172 100644 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ b/pkg/microservice/picket/core/filter/service/codehost.go @@ -27,7 +27,7 @@ func ListCodeHost(_ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { if err != nil { return nil, err } - for k, _ := range list { + for k := range list { list[k].AccessKey = "***" list[k].SecretKey = "***" list[k].AccessToken = "***" -- Gitee From 060c7954473b6a7afe5f59ebacdddc58b3519fcc Mon Sep 17 00:00:00 2001 From: Min Min Date: Sat, 18 Dec 2021 12:17:54 +0800 Subject: [PATCH 1072/2647] changed fork document to keep it up to date Signed-off-by: Min Min --- community/dev/contributor-workflow.md | 15 ++++++++++++--- community/dev/fork_var1.png | Bin 0 -> 66812 bytes community/dev/fork_var2.png | Bin 0 -> 34962 bytes community/dev/fork_var3.png | Bin 0 -> 32941 bytes 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 community/dev/fork_var1.png create mode 100644 community/dev/fork_var2.png create mode 100644 community/dev/fork_var3.png diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index e7289a5a..39a29166 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -53,9 +53,18 @@ Zadig 为您提供云上测试环境 [https://os.koderover.com](https://os.koder ![Fork project](./fork-zadig-option.png) -在弹出的窗口中,点击 zadig 选项,将其中的 endpoint.FQDN 中的 `githubid` 改成您的 GitHub ID,否则可能无法访问。 - -![Update githubid](./fork-zadig-vars.png) +在弹出的窗口中,点击 zadig 选项,如图修改如下字段: +1. endpoint.FQDN=${github_id}.ko.coderover.cn +2. global.extensions.extAuth.extauthzServerRef=zadig-env-${github_id} +3. dex.fullnameOverride=zadig-${github_id}-dex +4. dex.config.issuer=http://zadig-${github_id}-dex +5. dex.config.staticClients[0].redirectURISs[0]=http:// ${github_id}.ko.coderover.cn/api/v1/callback + +![Fork_var1](./fork_var1.png) +![Fork_var2](./fork_var2.png) +![Fork_var3](./fork_var3.png) + +请注意,如果没有按照以上规则修改参数,可能会导致环境无法创建/创建环境后无法正常访问! Fork 完成后,您将获得一个 Zadig 测试环境。 diff --git a/community/dev/fork_var1.png b/community/dev/fork_var1.png new file mode 100644 index 0000000000000000000000000000000000000000..666c36488666000cc5d05b9ec6c57b28491e34c4 GIT binary patch literal 66812 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 KaFL*%&;J3i-}&|c literal 0 HcmV?d00001 diff --git a/community/dev/fork_var3.png b/community/dev/fork_var3.png new file mode 100644 index 0000000000000000000000000000000000000000..ff872da6ae610b8cdcf14ebf87f9d5d82685a6a6 GIT binary patch literal 32941 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 Jmb}sT{eOm{=)M2| literal 0 HcmV?d00001 -- Gitee From dc1e0400758f25bb0d864bbae733a2d10eae97cf Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sat, 18 Dec 2021 12:41:30 +0800 Subject: [PATCH 1073/2647] Complete global.extensions.extAuth parameters Signed-off-by: zhangyifei --- community/dev/contributor-workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index 39a29166..e0a313a9 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -55,7 +55,7 @@ Zadig 为您提供云上测试环境 [https://os.koderover.com](https://os.koder 在弹出的窗口中,点击 zadig 选项,如图修改如下字段: 1. endpoint.FQDN=${github_id}.ko.coderover.cn -2. global.extensions.extAuth.extauthzServerRef=zadig-env-${github_id} +2. global.extensions.extAuth.extauthzServerRef.namespace=zadig-env-${github_id} 3. dex.fullnameOverride=zadig-${github_id}-dex 4. dex.config.issuer=http://zadig-${github_id}-dex 5. dex.config.staticClients[0].redirectURISs[0]=http:// ${github_id}.ko.coderover.cn/api/v1/callback -- Gitee From 167839f97abd2146d8745190deca3ba6fb955489 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sat, 18 Dec 2021 12:49:34 +0800 Subject: [PATCH 1074/2647] Improve the directory structure Signed-off-by: zhangyifei --- community/dev/contributor-workflow.md | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index e0a313a9..ed8857fe 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -1,20 +1,16 @@ # Zadig 开发流程 -## 目录 - -- [Zadig 开发流程](#zadig-开发流程) - - [目录](#目录) - - [1. 克隆代码](#1-克隆代码) - - [2. 本地开发环境搭建](#2-本地开发环境搭建) - - [前端环境](#前端环境) - - [后端环境](#后端环境) - - [3. 贡献代码](#3-贡献代码) - - [4. 调试](#4-调试) - - [方式一:云上代码测试](#方式一云上代码测试) - - [方式二:本地代码测试](#方式二本地代码测试) - - [前端本地测试](#前端本地测试) - - [后端本地测试](#后端本地测试) +- [1. 克隆代码](#1-克隆代码) +- [2. 本地开发环境搭建](#2-本地开发环境搭建) + - [前端环境](#前端环境) + - [后端环境](#后端环境) +- [3. 贡献代码](#3-贡献代码) +- [4. 调试](#4-调试) + - [方式一:云上代码测试](#方式一云上代码测试) + - [方式二:本地代码测试](#方式二本地代码测试) + - [前端本地测试](#前端本地测试) + - [后端本地测试](#后端本地测试) ## 1. 克隆代码 -- Gitee From e848973b0fb290acbdc68b610f050497a0f56bbe Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 20 Dec 2021 11:57:20 +0800 Subject: [PATCH 1075/2647] added authz for common workflow Signed-off-by: Min Min --- .../aslan/core/workflow/handler/policy.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 8a8351d6..2aaa9c74 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -30,6 +30,12 @@ rules: endpoint: "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*" - method: GET endpoint: "/api/directory/workflowTask" + - method: GET + endpoint: "/api/aslan/workflow/v3" + - method: GET + endpoint: "/api/aslan/workflow/v3/?*" + - method: GET + endpoint: "/api/aslan/workflow/v3/?*/args" - action: edit_workflow alias: "编辑工作流" description: "" @@ -46,6 +52,8 @@ rules: endpoint: "/api/aslan/workflow/workflow/preset/?*" - method: GET endpoint: "/api/aslan/testing/testdetail" + - method: PUT + endpoint: "/api/aslan/workflow/v3/?*" - action: create_workflow alias: "新建工作流" description: "" @@ -64,12 +72,16 @@ rules: value: "placeholder" - method: GET endpoint: "/api/aslan/testing/testdetail" + - method: POST + endpoint: "/api/aslan/workflow/v3" - action: delete_workflow alias: "删除工作流" description: "" rules: - method: DELETE endpoint: "/api/aslan/workflow/workflow/?*" + - method: DELETE + endpoint: "/api/aslan/workflow/v3/?*" - action: run_workflow alias: "执行工作流" description: "" @@ -96,3 +108,11 @@ rules: value: "placeholder" - method: GET endpoint: "/api/aslan/project/products/?*/services" + - method: GET + endpoint: "/api/aslan/workflow/v3/?*/args" + - method: POST + endpoint: "/api/aslan/workflow/v3/workflowtask" + - method: POST + endpoint: "/api/aslan/workflow/v3/workflowtask/id/?*/name/?*/restart" + - method: DELETE + endpoint: "/api/aslan/workflow/v3/workflowtask/id/?*/name/?*" -- Gitee From 8a5d7bf44eb8d7da3766125fa3364d99c1539061 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 20 Dec 2021 13:59:16 +0800 Subject: [PATCH 1076/2647] fix comments Signed-off-by: allenshen --- pkg/microservice/aslan/core/delivery/service/version.go | 2 +- pkg/microservice/aslan/core/environment/service/helm.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index aa1b790d..1d822a6b 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -621,7 +621,7 @@ func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) releaseName := util.GeneHelmReleaseName(prod.Namespace, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { - log.Errorf("failed to get values map data %s", err) + log.Errorf("failed to get values map data, err: %s", err) return "", err } diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index b7295f19..743a7594 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -177,7 +177,7 @@ func prepareChartVersionData(prod *models.Product, serviceObj *models.Service, r releaseName := util.GeneHelmReleaseName(prod.Namespace, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { - log.Errorf("failed to get values map data %s", err) + log.Errorf("failed to get values map data, err: %s", err) return err } -- Gitee From a38ad648b6fb92126c5d9e5ca17bd56b246b1f9b Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 20 Dec 2021 15:38:21 +0800 Subject: [PATCH 1077/2647] fix review GeneImageURI Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7793e2c5..4c229efe 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1004,18 +1004,16 @@ func genImageFromYaml(c *models.Container, valuesYaml, defaultValues, overrideYa if err != nil { return "", err } - imageRepo, ok1 := mergedValuesYamlFlattenMap[c.ImagePath.Repo] - imageTag, ok2 := mergedValuesYamlFlattenMap[c.ImagePath.Tag] - if ok2 { - if ok1 { - return fmt.Sprintf("%s:%s", imageRepo, imageTag), nil - } - splitImage := strings.Split(c.Image, ":") - if len(splitImage) == 2 { - return fmt.Sprintf("%s:%s", splitImage[0], imageTag), nil - } + imageRule := templatemodels.ImageSearchingRule{ + Repo: c.ImagePath.Repo, + Image: c.ImagePath.Image, + Tag: c.ImagePath.Tag, + } + image, err := commonservice.GeneImageURI(imageRule.GetSearchingPattern(), mergedValuesYamlFlattenMap) + if err != nil { + return "", err } - return c.Image, nil + return image, nil } func prepareEstimatedData(productName, envName, serviceName, usageScenario, defaultValues string, log *zap.SugaredLogger) (string, string, error) { -- Gitee From 5295c7f60eff91b3f55891c01c63cecad31945cb Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 20 Dec 2021 16:37:32 +0800 Subject: [PATCH 1078/2647] change emailhsot to type bool Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 60 ++++++++++-- .../internal/repository/models/emailhost.go | 37 ++++++++ .../internal/repository/mongodb/emailhost.go | 94 +++++++++++++++++++ 3 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/emailhost.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/emailhost.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index c8329c78..a01fbb14 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -44,7 +44,6 @@ func V160ToV170() error { log.Errorf("Failed to change codehost type, err: %s", err) return err } - log.Info("Start to refresh all webhook secrets") err = refreshWebHookSecret(gitservice.GetHookSecret()) if err != nil { @@ -52,19 +51,31 @@ func V160ToV170() error { return err } + err = changeEmailHostType() + if err != nil { + log.Errorf("Failed to change emailhost type , err: %s", err) + return err + } + return nil } func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") + err := rollbackEmailhostType() + if err != nil { + log.Errorf("Failed to rollback emailhost type, err: %s", err) + return err + } + token := getWebHookTokenFromOrganization() if token == "" { return nil } log.Info("Start to rollback all webhook secrets") - err := refreshWebHookSecret(token) + err = refreshWebHookSecret(token) if err != nil { log.Errorf("Failed to refresh webhook secret, err: %s", err) return err @@ -145,19 +156,56 @@ func getCodeHostByAddressAndOwner(address, owner string, all []*internalmodels.C return nil } +// change tls from string to bool type +func changeEmailHostType() error { + emailHosts, err := internalmongodb.NewEmailHostColl().List() + if err != nil { + log.Errorf("Failed to list emailhosts , err: %s", err) + return err + } + for _, v := range emailHosts { + if value, ok := v.IsTLS.(string); ok { + if err := internalmongodb.NewEmailHostColl().ChangeType(v.ID, value); err != nil { + log.Warnf("Failed to change id:%v istls:%s , err: %s", v.ID, value, err) + return err + } + } + } + return nil +} + +// rollback tls from bool to string type +func rollbackEmailhostType() error { + // get all codehosts + codeHosts, err := internalmongodb.NewEmailHostColl().List() + if err != nil { + log.Errorf("Failed to list codehosts, err: %s", err) + return err + } + for _, v := range codeHosts { + if value, ok := v.IsTLS.(bool); ok { + if err := internalmongodb.NewEmailHostColl().RollbackType(v.ID, value); err != nil { + log.Warnf("Failed to rollback id:%v istls:%v , err: %s", v.ID, value, err) + return err + } + } + } + return nil +} + // change type "1,2,3,4" to "github,gitlab..." func changeCodehostType() error { // get all codehosts codeHosts, err := internalmongodb.NewCodehostColl().List() if err != nil { - log.Errorf("fail to list codehosts, err: %s", err) + log.Errorf("Failed to list codehosts, err: %s", err) return err } var finalErr error // change type to readable string for _, v := range codeHosts { if err := internalmongodb.NewCodehostColl().ChangeType(v.ID, v.Type); err != nil { - log.Warnf("fail to change id:%d type:%s , err: %s", v.ID, v.Type, err) + log.Warnf("Failed to change id:%d type:%s , err: %s", v.ID, v.Type, err) finalErr = err } } @@ -169,14 +217,14 @@ func rollbackCodehostType() error { // get all codehosts codeHosts, err := internalmongodb.NewCodehostColl().List() if err != nil { - log.Errorf("fail to list codehosts, err: %s", err) + log.Errorf("Failed to list codehosts, err: %s", err) return err } var finalErr error // rollback change type to readable string for _, v := range codeHosts { if err := internalmongodb.NewCodehostColl().RollbackType(v.ID, v.Type); err != nil { - log.Warnf("fail to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) + log.Warnf("Failed to rollback id:%d type:%s , err: %s", v.ID, v.Type, err) finalErr = err continue } diff --git a/pkg/cli/upgradeassistant/internal/repository/models/emailhost.go b/pkg/cli/upgradeassistant/internal/repository/models/emailhost.go new file mode 100644 index 00000000..dbf28fcb --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/emailhost.go @@ -0,0 +1,37 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type EmailHost struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Port int `bson:"port" json:"port"` + Username string `bson:"username" json:"username"` + Password string `bson:"password" json:"password"` + IsTLS interface{} `bson:"is_tls" json:"isTLS"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` +} + +func (EmailHost) TableName() string { + return "email_host" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/emailhost.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/emailhost.go new file mode 100644 index 00000000..6c68464e --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/emailhost.go @@ -0,0 +1,94 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +func NewEmailHostColl() *EmailHostColl { + name := models.EmailHost{}.TableName() + coll := &EmailHostColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +type EmailHostColl struct { + *mongo.Collection + + coll string +} + +func (c *EmailHostColl) List() ([]*models.EmailHost, error) { + emailhosts := make([]*models.EmailHost, 0) + query := bson.M{"deleted_at": 0} + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &emailhosts) + if err != nil { + return nil, err + } + + return emailhosts, nil +} + +func (c *EmailHostColl) ChangeType(ID primitive.ObjectID, isTLS string) error { + query := bson.M{"_id": ID} + changedTLS := false + if isTLS == "2" { + changedTLS = true + } + change := bson.M{"$set": bson.M{ + "is_tls": changedTLS, + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Errorf("emailhost update fail,err:%s", err) + return err + } + log.Infof("success to change id:%v isTLS:%s to %v", ID, isTLS, changedTLS) + return nil +} + +func (c *EmailHostColl) RollbackType(ID primitive.ObjectID, isTLS bool) error { + query := bson.M{"_id": ID} + changedTLS := "1" + if isTLS == true { + changedTLS = "2" + } + change := bson.M{"$set": bson.M{ + "is_tls": changedTLS, + }} + _, err := c.Collection.UpdateOne(context.TODO(), query, change) + if err != nil { + log.Errorf("emailhost update fail,err:%s", err) + return err + } + log.Infof("success to change id:%v isTLS:%v to %s", ID, isTLS, changedTLS) + return nil +} -- Gitee From f4cd250cab5788782b2382a9182eab11b3358860 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 20 Dec 2021 17:01:06 +0800 Subject: [PATCH 1079/2647] fix review Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4c229efe..0d5ed168 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -776,13 +776,13 @@ func createSingleHelmProduct(templateProduct *template.Product, requestID, userN } } else if serviceTmpl.Type == setting.HelmDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) + var err error for _, c := range serviceTmpl.Containers { image := c.Image for _, rc := range productObj.ChartInfos { if rc.ServiceName != serviceTmpl.ServiceName { continue } - var err error image, err = genImageFromYaml(c, rc.ValuesYaml, defaultValuesYaml, rc.GetOverrideYaml(), rc.OverrideValues) if err != nil { errMsg := fmt.Sprintf("genImageFromYaml product template %s,service name:%s,error:%s", productObj.ProductName, rc.ServiceName, err) -- Gitee From d9e98437db937c46f27e94b5d68acd241b7a3071 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Mon, 20 Dec 2021 17:24:49 +0800 Subject: [PATCH 1080/2647] Revert "fix(git):fix the problem of upgrade git to the latest version in 16.04." This reverts commit e2fbe9dbe3dd82ffca767c577514fd6a49a94b77. Signed-off-by: leozhang2018 --- docker/base/amd64/ubuntu-xenial.Dockerfile | 15 +++++++++++++++ docker/base/arm64/ubuntu-xenial.Dockerfile | 15 +++++++++++++++ docker/service/reaper-plugin-xenial.Dockerfile | 1 + 3 files changed, 31 insertions(+) diff --git a/docker/base/amd64/ubuntu-xenial.Dockerfile b/docker/base/amd64/ubuntu-xenial.Dockerfile index 96fa38bd..ccb4277f 100644 --- a/docker/base/amd64/ubuntu-xenial.Dockerfile +++ b/docker/base/amd64/ubuntu-xenial.Dockerfile @@ -1 +1,16 @@ FROM ubuntu:xenial + +# 修改镜像源和时区 +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \ + && apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \ + && apt-get install -y \ + tzdata \ + net-tools \ + dnsutils \ + ca-certificates \ + git \ + curl \ + lsof \ + telnet \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/base/arm64/ubuntu-xenial.Dockerfile b/docker/base/arm64/ubuntu-xenial.Dockerfile index 5ba7f65d..e2f37c5f 100644 --- a/docker/base/arm64/ubuntu-xenial.Dockerfile +++ b/docker/base/arm64/ubuntu-xenial.Dockerfile @@ -1 +1,16 @@ FROM arm64v8/ubuntu:xenial + +# 修改镜像源和时区 +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \ + && apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \ + && apt-get install -y \ + tzdata \ + net-tools \ + dnsutils \ + ca-certificates \ + git \ + curl \ + lsof \ + telnet \ + && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 3d36050d..32c9c387 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -10,6 +10,7 @@ RUN sed -i s:http:https:g /etc/apt/sources.list RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata RUN apt-get clean && apt-get update && apt-get install -y \ curl \ + git \ netcat-openbsd \ wget \ build-essential \ -- Gitee From 7b902f45a75d2468d9ebfd634b60ac12848c5b4c Mon Sep 17 00:00:00 2001 From: fengzixu Date: Mon, 20 Dec 2021 10:53:15 +0000 Subject: [PATCH 1081/2647] fix: remove the unsed global variable Signed-off-by: fengzixu --- pkg/microservice/aslan/core/common/service/service.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 7715df4b..3affb77e 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -91,9 +91,8 @@ type ServiceProductMap struct { } var ( - imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) - imageSpaceParseRegex = regexp.MustCompile(`\/(.*?)\/`) - presetPatterns = []map[string]string{ + imageParseRegex = regexp.MustCompile(`(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?`) + presetPatterns = []map[string]string{ {setting.PathSearchComponentImage: "image.repository", setting.PathSearchComponentTag: "image.tag"}, {setting.PathSearchComponentImage: "image"}, } -- Gitee From 3fad58681c8f70ff395b596b99af1b2f887c2f2a Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 20 Dec 2021 19:01:24 +0800 Subject: [PATCH 1082/2647] add new contributor @fengzixu Signed-off-by: zhangyifei --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index bc24515d..76a3d41c 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -65,3 +65,4 @@ The council will make a decision for the nomination. The candidate has to have d - [jtr109](https://github.com/jtr109) - [huangzexiong](https://github.com/huangzexiong) - [ruchikabobade](https://github.com/ruchikabobade) +- [fengzixu](https://github.com/fengzixu) -- Gitee From 97d471e1017c322b28de00994d1ecc251d0ea1ad Mon Sep 17 00:00:00 2001 From: lou Date: Mon, 20 Dec 2021 21:04:27 +0800 Subject: [PATCH 1083/2647] add missing rules Signed-off-by: lou --- .../aslan/core/delivery/handler/policy.yaml | 26 +++++++++++++-- .../core/environment/handler/policy.yaml | 32 +++++++++++++++++++ .../aslan/core/project/handler/policy.yaml | 2 ++ .../aslan/core/workflow/handler/policy.yaml | 2 ++ .../core/workflow/testing/handler/policy.yaml | 2 ++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml index fe2625db..e003c9ae 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -9,10 +9,32 @@ rules: - method: GET endpoint: "/api/aslan/delivery/releases" - method: GET - endpoint: "/api/directory/dc/releases" + endpoint: "/api/aslan/delivery/servicenames" + - method: GET + endpoint: "/api/aslan/delivery/releases/?*" + - method: GET + endpoint: "/api/aslan/delivery/releases/helm/charts" - action: delete_delivery alias: "删除版本" description: "" rules: - method: DELETE - endpoint: "/api/aslan/delivery/releases/?*" \ No newline at end of file + endpoint: "/api/aslan/delivery/releases/?*" + - action: create_delivery + alias: "创建版本" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/delivery/releases/helm" + - method: GET + endpoint: "/api/aslan/environment/environments" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/helm/releases" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/helm/charts" + - method: GET + endpoint: "/api/aslan/service/helm/?*/?*/fileContent" + - method: POST + endpoint: "/api/aslan/delivery/releases/helm/global-variables" + - method: GET + endpoint: "/api/aslan/delivery/releases/helm/charts/version" diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 4447b0ff..c8b554d3 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -51,6 +51,29 @@ rules: endpoint: "/api/aslan/project/products/?*/services" - method: GET endpoint: "/api/aslan/environment/revision/products" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/workloads" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/workloads$" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/helm/releases" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/helm/charts" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - action: create_environment alias: "新建集成环境" description: "" @@ -108,6 +131,13 @@ rules: endpoint: "/api/aslan/service/workloads" - method: GET endpoint: "/api/aslan/project/products/?*/services" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/registry" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/registry$" + matchAttributes: + - key: "production" + value: "false" - action: manage_environment alias: "管理服务实例" description: "" @@ -159,6 +189,8 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: GET endpoint: "/api/podexec/?*/?*/?*/?*/podExec" + - method: POST + endpoint: "/api/aslan/workflow/servicetask" - action: delete_environment alias: "删除集成环境" description: "" diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/policy.yaml index b494095e..377b5cfc 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/policy.yaml @@ -22,6 +22,8 @@ rules: endpoint: "/api/aslan/service/helm/?*/?*/fileContent" - method: GET endpoint: "/api/aslan/service/helm/?*/?*/serviceModule" + - method: GET + endpoint: "/api/aslan/service/services/?*/pm" - action: edit_service alias: "编辑服务" description: "" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 2aaa9c74..09765726 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -36,6 +36,8 @@ rules: endpoint: "/api/aslan/workflow/v3/?*" - method: GET endpoint: "/api/aslan/workflow/v3/?*/args" + - method: GET + endpoint: "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*" - action: edit_workflow alias: "编辑工作流" description: "" diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml index 56b2c62a..a6085908 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -16,6 +16,8 @@ rules: endpoint: "/api/aslan/workflow/workflow/testName/?*" - method: GET endpoint: "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*" + - method: GET + endpoint: "/api/aslan/testing/workspace/workflow/?*/taskId/?*" - action: edit_test alias: "编辑测试" description: "" -- Gitee From 93618e511b56a8fbb39914f4c53cdf2075b878cc Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 20 Dec 2021 22:10:54 +0800 Subject: [PATCH 1084/2647] fix registry get wrong product Signed-off-by: panxunying --- .../aslan/core/common/repository/mongodb/product.go | 4 ++-- .../aslan/core/environment/handler/environment.go | 9 ++++----- .../aslan/core/environment/service/environment.go | 12 ++++++------ .../core/workflow/service/workflow/workflow_task.go | 4 ++-- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 023ba43f..eaefcbd8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -245,8 +245,8 @@ func (c *ProductColl) UpdateErrors(owner, productName, errorMsg string) error { return err } -func (c *ProductColl) UpdateRegistry(namespace, registryId string) error { - query := bson.M{"namespace": namespace} +func (c *ProductColl) UpdateRegistry(envName, productName, registryId string) error { + query := bson.M{"env_name": envName, "product_name": productName} change := bson.M{"$set": bson.M{ "registry_id": registryId, }} diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 30e286cc..bfe6e1af 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -56,7 +56,6 @@ type NamespaceResource struct { type UpdateProductRegistryRequest struct { RegistryID string `json:"registry_id"` - Namespace string `json:"namespace"` } func ListProducts(c *gin.Context) { @@ -295,7 +294,7 @@ func UpdateProduct(c *gin.Context) { func UpdateProductRegistry(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - + envName := c.Param("name") projectName := c.Query("projectName") args := new(UpdateProductRegistryRequest) data, err := c.GetRawData() @@ -309,16 +308,16 @@ func UpdateProductRegistry(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", args.Namespace, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Err = service.UpdateProductRegistry(args.Namespace, args.RegistryID, ctx.Logger) + ctx.Err = service.UpdateProductRegistry(envName, projectName, args.RegistryID, ctx.Logger) if ctx.Err != nil { - ctx.Logger.Errorf("failed to update product %s %s: %v", args.Namespace, args.RegistryID, ctx.Err) + ctx.Logger.Errorf("failed to update product %s %s: %v", envName, args.RegistryID, ctx.Err) } } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0d5ed168..60d19f6e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -541,16 +541,16 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return nil } -func UpdateProductRegistry(namespace, registryID string, log *zap.SugaredLogger) (err error) { - opt := &commonrepo.ProductFindOptions{Namespace: namespace} +func UpdateProductRegistry(envName, productName, registryID string, log *zap.SugaredLogger) (err error) { + opt := &commonrepo.ProductFindOptions{EnvName: envName, Name: productName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("UpdateProductRegistry find product by namespace:%s,error: %v", namespace, err) + log.Errorf("UpdateProductRegistry find product by envName:%s,error: %v", envName, err) return e.ErrUpdateEnv.AddDesc(e.EnvNotFoundErrMsg) } - err = commonrepo.NewProductColl().UpdateRegistry(namespace, registryID) + err = commonrepo.NewProductColl().UpdateRegistry(envName, productName, registryID) if err != nil { - log.Errorf("UpdateProductRegistry UpdateRegistry by namespace:%s registryID:%s error: %v", namespace, registryID, err) + log.Errorf("UpdateProductRegistry UpdateRegistry by envName:%s registryID:%s error: %v", envName, registryID, err) return e.ErrUpdateEnv.AddErr(err) } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), exitedProd.ClusterID) @@ -560,7 +560,7 @@ func UpdateProductRegistry(namespace, registryID string, log *zap.SugaredLogger) err = ensureKubeEnv(exitedProd.Namespace, registryID, kubeClient, log) if err != nil { - log.Errorf("UpdateProductRegistry ensureKubeEnv by namespace:%s,error: %v", namespace, err) + log.Errorf("UpdateProductRegistry ensureKubeEnv by envName:%s,error: %v", envName, err) return err } return nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 33bf7674..4f750692 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1865,10 +1865,10 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) } - opt := &commonrepo.ProductFindOptions{Name: pt.ProductName} + opt := &commonrepo.ProductFindOptions{EnvName: envName, Name: pt.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("can't find product by name:%s error msg: %v", pt.ProductName, err) + log.Errorf("can't find product by envName:%s error msg: %v", envName, err) return e.ErrFindRegistry.AddDesc(err.Error()) } -- Gitee From 78ddc22760d963876a63b694a7fbe8cd2bcc4414 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 20 Dec 2021 23:45:38 +0800 Subject: [PATCH 1085/2647] fix link of cli usage Signed-off-by: zhangyifei --- community/dev/contributor-workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index ed8857fe..dd7b0374 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -95,4 +95,4 @@ yarn run dev #### 后端本地测试 -使用 Zadig CLI 进行本地调试,使用方式参见 [Zadig CLI 使用指南](https://docs.koderover.com/zadig/cli/kodespace-usage-for-contributor) +使用 Zadig CLI 进行本地调试,使用方式参见 [Zadig CLI 使用指南](https://docs.koderover.com/zadig/v1.7.1/cli/kodespace-usage-for-contributor/) -- Gitee From bb69e928c7f9b7f605dfa1a35813849c40afdbe8 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 21 Dec 2021 17:34:49 +0800 Subject: [PATCH 1086/2647] remove permission for GET api/aslan/system/registry/namespaces (#798) Signed-off-by: fansi Co-authored-by: fansi --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index fb26d5f2..60466ae2 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -137,7 +137,7 @@ var systemAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/proxyManage/?*"}, }, { - Methods: []string{"GET", "POST"}, + Methods: []string{"POST"}, Endpoints: []string{"api/aslan/system/registry/namespaces"}, }, { -- Gitee From 49a89a49f861ee2acc98f7fd565250ac34d2b39a Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 22 Dec 2021 14:00:36 +0800 Subject: [PATCH 1087/2647] page Signed-off-by: mouuii --- .../aslan/core/code/handler/codehost.go | 31 +++++++++++++++++-- .../aslan/core/code/service/branch.go | 8 +++-- .../aslan/core/code/service/namespace.go | 2 -- .../aslan/core/code/service/project.go | 8 +++-- .../aslan/core/code/service/repo.go | 2 +- pkg/tool/git/gitlab/branch.go | 4 +-- 6 files changed, 44 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 5c2ac180..f7f552d7 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -62,6 +62,12 @@ func CodeHostGetNamespaceList(c *gin.Context) { ctx.Resp, ctx.Err = service.CodeHostListNamespaces(chID, keyword, ctx.Logger) } +type CodeHostListProjectsArgs struct { + PerPage int `json:"perPage" form:"per_page,default:30"` + Page int `json:"page" form:"page,default:1"` + Key string `json:"key" form:"key"` +} + func CodeHostGetProjectsList(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -69,7 +75,6 @@ func CodeHostGetProjectsList(c *gin.Context) { namespaceType := c.DefaultQuery("type", "group") codehostID := c.Param("codehostId") namespace := c.Param("namespace") - keyword := c.Query("key") if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") @@ -88,15 +93,29 @@ func CodeHostGetProjectsList(c *gin.Context) { return } + args := &CodeHostListProjectsArgs{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + chID, _ := strconv.Atoi(codehostID) ctx.Resp, ctx.Err = service.CodeHostListProjects( chID, strings.Replace(namespace, "%2F", "/", -1), namespaceType, - keyword, + args.Page, + args.PerPage, + args.Key, ctx.Logger) } +type CodeHostGetBranchListArgs struct { + PerPage int `json:"perPage" form:"per_page,default:30"` + Page int `json:"page" form:"page,default:1"` + Key string `json:"key" form:"key"` +} + func CodeHostGetBranchList(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -104,6 +123,11 @@ func CodeHostGetBranchList(c *gin.Context) { codehostID := c.Param("codehostId") namespace := c.Param("namespace") projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id + args := new(CodeHostGetBranchListArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") @@ -123,6 +147,9 @@ func CodeHostGetBranchList(c *gin.Context) { chID, projectName, strings.Replace(namespace, "%2F", "/", -1), + args.Page, + args.PerPage, + args.Key, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 91c2fcf8..b5cab187 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -30,7 +30,7 @@ import ( "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListBranches(codeHostID int, projectName, namespace string, log *zap.SugaredLogger) ([]*Branch, error) { +func CodeHostListBranches(codeHostID int, projectName, namespace string, page int, perPage int, key string, log *zap.SugaredLogger) ([]*Branch, error) { ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") @@ -43,7 +43,11 @@ func CodeHostListBranches(codeHostID int, projectName, namespace string, log *za return nil, e.ErrCodehostListBranches.AddDesc(err.Error()) } - brList, err := client.ListBranches(namespace, projectName, nil) + brList, err := client.ListBranches(namespace, projectName, key, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 4633e85b..7e052f80 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -35,8 +35,6 @@ const ( OrgKind = "org" GroupKind = "group" UserKind = "user" - page = 1 - perPage = 100 CodeHostCodeHub = "codehub" ) diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 3a210a1c..414c5dec 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -30,7 +30,7 @@ import ( "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword string, log *zap.SugaredLogger) ([]*Project, error) { +func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, perPage int, keyword string, log *zap.SugaredLogger) ([]*Project, error) { ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { log.Error(err) @@ -53,7 +53,11 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType, keyword stri } // user - projects, err := client.ListUserProjects(namespace, keyword, nil) + projects, err := client.ListUserProjects(namespace, keyword, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 1bc4114a..9a8e3466 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -77,7 +77,7 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( if info.Source == CodeHostCodeHub { projectName = info.RepoUUID } - info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), log) + info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), 0, 0, "", log) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() diff --git a/pkg/tool/git/gitlab/branch.go b/pkg/tool/git/gitlab/branch.go index 40763dd0..8ddef945 100644 --- a/pkg/tool/git/gitlab/branch.go +++ b/pkg/tool/git/gitlab/branch.go @@ -19,9 +19,9 @@ package gitlab import "github.com/xanzy/go-gitlab" // ListBranches lists branches by projectID <- urlEncode(namespace/projectName) -func (c *Client) ListBranches(owner, repo string, opts *ListOptions) ([]*gitlab.Branch, error) { +func (c *Client) ListBranches(owner, repo, key string, opts *ListOptions) ([]*gitlab.Branch, error) { branches, err := wrap(paginated(func(o *gitlab.ListOptions) ([]interface{}, *gitlab.Response, error) { - bs, r, err := c.Branches.ListBranches(generateProjectName(owner, repo), &gitlab.ListBranchesOptions{ListOptions: *o}) + bs, r, err := c.Branches.ListBranches(generateProjectName(owner, repo), &gitlab.ListBranchesOptions{ListOptions: *o, Search: &key}) var res []interface{} for _, b := range bs { res = append(res, b) -- Gitee From 4f4712189775f3812576e781aa25c81b30017389 Mon Sep 17 00:00:00 2001 From: Markus Hatvan Date: Wed, 22 Dec 2021 10:35:23 +0100 Subject: [PATCH 1088/2647] chore: solve the problem of importing the same package multiple times - merge duplicate imports into one - fix additional minor reported issues --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 8 +- .../repository/models/delivery_distribute.go | 4 +- .../core/common/repository/models/queue.go | 4 +- .../aslan/core/common/service/delivery.go | 39 ++++---- .../aslan/core/common/service/fs/s3.go | 3 +- .../aslan/core/common/service/product.go | 24 +++-- .../aslan/core/common/service/service.go | 20 ++--- .../aslan/core/delivery/service/version.go | 5 +- .../core/environment/service/environment.go | 89 +++++++++---------- .../aslan/core/service/handler/service.go | 5 +- .../aslan/core/service/service/helm.go | 3 +- .../aslan/core/service/service/service.go | 23 +++-- .../aslan/core/setting/service/user.go | 9 +- .../aslan/core/templatestore/handler/chart.go | 17 ++-- .../aslan/core/templatestore/service/yaml.go | 11 ++- .../service/workflow/artifact_task.go | 3 +- .../picket/client/aslan/delivery.go | 3 +- .../systemconfig/config/config.go | 1 - .../core/service/taskplugin/deploy.go | 3 +- .../core/service/taskplugin/trigger.go | 2 +- 20 files changed, 127 insertions(+), 149 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index a8ea77f0..7287f292 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -20,9 +20,7 @@ import ( "fmt" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/setting" @@ -40,7 +38,7 @@ func init() { func V140ToV150() error { log.Info("Migrating data from 1.4.0 to 1.5.0") - products, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ ExcludeStatus: setting.ProductStatusDeleting, Source: setting.SourceFromHelm, }) @@ -134,7 +132,7 @@ func fillImageParseInfo(prod *commonmodels.Product) error { } // find match rule -func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[string]string, container *models.Container) error { +func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[string]string, container *commonmodels.Container) error { for _, searchResult := range matchedPath { imageURI, err := commonservice.GeneImageURI(searchResult, flatMap) if err != nil { @@ -144,7 +142,7 @@ func findImageByContainerName(flatMap map[string]interface{}, matchedPath []map[ if container.Name != commonservice.ExtractImageName(imageURI) { continue } - container.ImagePath = &models.ImagePathSpec{ + container.ImagePath = &commonmodels.ImagePathSpec{ Repo: searchResult[setting.PathSearchComponentRepo], Image: searchResult[setting.PathSearchComponentImage], Tag: searchResult[setting.PathSearchComponentTag], diff --git a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go index 732e24fc..bbe82db5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go +++ b/pkg/microservice/aslan/core/common/repository/models/delivery_distribute.go @@ -40,8 +40,8 @@ type DeliveryDistribute struct { StorageURL string `bson:"-" json:"storageUrl"` StorageBucket string `bson:"-" json:"storageBucket"` SrcStorageURL string `bson:"src_storage_url" json:"srcStorageUrl,omitempty"` - StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty,omitempty"` - EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` CreatedAt int64 `bson:"created_at" json:"created_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 8aee0760..250f975b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -111,8 +111,8 @@ type ArtifactPackageTaskArgs struct { ProjectName string `bson:"project_name" json:"project_name"` EnvName string `bson:"env_name" json:"env_name"` Images []*ImagesByService `bson:"images" json:"images"` - SourceRegistries []string `json:"source_registries" json:"source_registries"` - TargetRegistries []string `json:"target_registries" json:"target_registries"` + SourceRegistries []string `bson:"source_registries" json:"source_registries"` + TargetRegistries []string `bson:"target_registries" json:"target_registries"` } type ConfigPayload struct { diff --git a/pkg/microservice/aslan/core/common/service/delivery.go b/pkg/microservice/aslan/core/common/service/delivery.go index 74baaf6f..eeb4aa2d 100644 --- a/pkg/microservice/aslan/core/common/service/delivery.go +++ b/pkg/microservice/aslan/core/common/service/delivery.go @@ -26,7 +26,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -36,30 +35,30 @@ import ( ) func DeleteDeliveryInfos(productName string, log *zap.SugaredLogger) error { - deliveryVersions, err := mongodb.NewDeliveryVersionColl().ListDeliveryVersions(productName) + deliveryVersions, err := commonrepo.NewDeliveryVersionColl().ListDeliveryVersions(productName) if err != nil { log.Errorf("delete DeleteDeliveryInfo error: %v", err) return e.ErrDeleteDeliveryVersion } errList := new(multierror.Error) for _, deliveryVersion := range deliveryVersions { - err = mongodb.NewDeliveryVersionColl().Delete(deliveryVersion.ID.Hex()) + err = commonrepo.NewDeliveryVersionColl().Delete(deliveryVersion.ID.Hex()) if err != nil { errList = multierror.Append(errList, fmt.Errorf("DeliveryVersion delete %s error: %v", deliveryVersion.ID.String(), err)) } - err = mongodb.NewDeliveryBuildColl().Delete(deliveryVersion.ID.Hex()) + err = commonrepo.NewDeliveryBuildColl().Delete(deliveryVersion.ID.Hex()) if err != nil { errList = multierror.Append(errList, fmt.Errorf("DeliveryBuild delete %s error: %v", deliveryVersion.ID.String(), err)) } - err = mongodb.NewDeliveryDeployColl().Delete(deliveryVersion.ID.Hex()) + err = commonrepo.NewDeliveryDeployColl().Delete(deliveryVersion.ID.Hex()) if err != nil { errList = multierror.Append(errList, fmt.Errorf("DeliveryDeploy delete %s error: %v", deliveryVersion.ID.String(), err)) } - err = mongodb.NewDeliveryTestColl().Delete(deliveryVersion.ID.Hex()) + err = commonrepo.NewDeliveryTestColl().Delete(deliveryVersion.ID.Hex()) if err != nil { errList = multierror.Append(errList, fmt.Errorf("DeliveryTest delete %s error: %v", deliveryVersion.ID.String(), err)) } - err = mongodb.NewDeliveryDistributeColl().Delete(deliveryVersion.ID.Hex()) + err = commonrepo.NewDeliveryDistributeColl().Delete(deliveryVersion.ID.Hex()) if err != nil { errList = multierror.Append(errList, fmt.Errorf("DeliveryDistribute delete %s error: %v", deliveryVersion.ID.String(), err)) } @@ -72,7 +71,7 @@ func DeleteDeliveryInfos(productName string, log *zap.SugaredLogger) error { } func AddDeliveryVersion(taskID int, productName, workflowName string, pipelineTask *taskmodels.Task, logger *zap.SugaredLogger) error { - deliveryVersionArgs := &mongodb.DeliveryVersionArgs{ + deliveryVersionArgs := &commonrepo.DeliveryVersionArgs{ ProductName: productName, WorkflowName: workflowName, TaskID: taskID, @@ -108,8 +107,8 @@ func AddDeliveryVersion(taskID int, productName, workflowName string, pipelineTa return err } -func GetDeliveryVersion(args *mongodb.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { - resp, err := mongodb.NewDeliveryVersionColl().Get(args) +func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { + resp, err := commonrepo.NewDeliveryVersionColl().Get(args) if err != nil { log.Errorf("get deliveryVersion error: %v", err) return nil, e.ErrGetDeliveryVersion @@ -118,7 +117,7 @@ func GetDeliveryVersion(args *mongodb.DeliveryVersionArgs, log *zap.SugaredLogge } func InsertDeliveryVersion(args *commonmodels.DeliveryVersion, log *zap.SugaredLogger) error { - err := mongodb.NewDeliveryVersionColl().Insert(args) + err := commonrepo.NewDeliveryVersionColl().Insert(args) if err != nil { log.Errorf("insert deliveryVersion error: %v", err) return e.ErrCreateDeliveryVersion @@ -389,7 +388,7 @@ func getProductEnvInfo(pipelineTask *taskmodels.Task, log *zap.SugaredLogger) (* } func insertDeliveryBuild(args *commonmodels.DeliveryBuild, log *zap.SugaredLogger) error { - err := mongodb.NewDeliveryBuildColl().Insert(args) + err := commonrepo.NewDeliveryBuildColl().Insert(args) if err != nil { log.Errorf("insert deliveryBuild error: %v", err) return e.ErrCreateDeliveryBuild @@ -398,7 +397,7 @@ func insertDeliveryBuild(args *commonmodels.DeliveryBuild, log *zap.SugaredLogge } func insertDeliveryDeploy(args *commonmodels.DeliveryDeploy, log *zap.SugaredLogger) error { - err := mongodb.NewDeliveryDeployColl().Insert(args) + err := commonrepo.NewDeliveryDeployColl().Insert(args) if err != nil { log.Errorf("insert deliveryDeploy error: %v", err) return e.ErrCreateDeliveryDeploy @@ -407,7 +406,7 @@ func insertDeliveryDeploy(args *commonmodels.DeliveryDeploy, log *zap.SugaredLog } func InsertDeliveryTest(args *commonmodels.DeliveryTest, log *zap.SugaredLogger) error { - err := mongodb.NewDeliveryTestColl().Insert(args) + err := commonrepo.NewDeliveryTestColl().Insert(args) if err != nil { log.Errorf("insert deliveryTest error: %v", err) return e.ErrCreateDeliveryTest @@ -416,7 +415,7 @@ func InsertDeliveryTest(args *commonmodels.DeliveryTest, log *zap.SugaredLogger) } func insertDeliveryDistribute(args *commonmodels.DeliveryDistribute, log *zap.SugaredLogger) error { - err := mongodb.NewDeliveryDistributeColl().Insert(args) + err := commonrepo.NewDeliveryDistributeColl().Insert(args) if err != nil { log.Errorf("insert deliveryDistribute error: %v", err) return e.ErrCreateDeliveryDistribute @@ -425,7 +424,7 @@ func insertDeliveryDistribute(args *commonmodels.DeliveryDistribute, log *zap.Su } func updateDeliveryVersion(args *commonmodels.DeliveryVersion, log *zap.SugaredLogger) error { - err := mongodb.NewDeliveryVersionColl().Update(args) + err := commonrepo.NewDeliveryVersionColl().Update(args) if err != nil { log.Errorf("update deliveryVersion error: %v", err) return e.ErrUpdateDeliveryVersion @@ -451,8 +450,8 @@ func updateServiceImage(serviceName, image, containerName string, product *commo func getServiceRenderYAML(productInfo *commonmodels.Product, containers []*commonmodels.Container, serviceName, deployType string, log *zap.SugaredLogger) (string, error) { if deployType == setting.K8SDeployType { - opt := &mongodb.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} - newRender, err := mongodb.NewRenderSetColl().Find(opt) + opt := &commonrepo.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} + newRender, err := commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("[%s][P:%s]renderset Find error: %v", productInfo.EnvName, productInfo.ProductName, err) return "", fmt.Errorf("get pure yaml %s error: %v", serviceName, err) @@ -463,13 +462,13 @@ func getServiceRenderYAML(productInfo *commonmodels.Product, containers []*commo return "", fmt.Errorf("service %s not found", serviceName) } // 获取服务模板 - serviceFindOption := &mongodb.ServiceFindOption{ + serviceFindOption := &commonrepo.ServiceFindOption{ ServiceName: serviceName, ProductName: serviceInfo.ProductName, Type: setting.K8SDeployType, Revision: serviceInfo.Revision, } - svcTmpl, err := mongodb.NewServiceColl().Find(serviceFindOption) + svcTmpl, err := commonrepo.NewServiceColl().Find(serviceFindOption) if err != nil { return "", fmt.Errorf("service template %s error: %v", serviceName, err) } diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index dfe02ea2..b3e52a3f 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -24,7 +24,6 @@ import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" @@ -51,7 +50,7 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, lo // archiveAndUploadFiles archive local files and upload to default s3 storage // if multiple names appointed, s3storage.copy will be used to handle extra names -func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Storage *s3.S3, logger *zap.SugaredLogger) error { +func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Storage *s3service.S3, logger *zap.SugaredLogger) error { if len(names) == 0 { return fmt.Errorf("names not appointed") diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 4e4249b9..2f1cb87b 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -28,9 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -47,7 +45,7 @@ const ( func DeleteProduct(username, envName, productName, requestID string, log *zap.SugaredLogger) (err error) { eventStart := time.Now().Unix() - productInfo, err := mongodb.NewProductColl().Find(&mongodb.ProductFindOptions{Name: productName, EnvName: envName}) + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) if err != nil { log.Errorf("find product error: %v", err) return e.ErrDeleteEnv.AddDesc("not found") @@ -64,7 +62,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } // 设置产品状态 - err = mongodb.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusDeleting) + err = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusDeleting) if err != nil { log.Errorf("[%s][%s] update product status error: %v", username, productInfo.Namespace, err) return e.ErrDeleteEnv.AddDesc("更新集成环境状态失败: " + err.Error()) @@ -75,7 +73,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su switch productInfo.Source { case setting.SourceFromHelm: - err = mongodb.NewProductColl().Delete(envName, productName) + err = commonrepo.NewProductColl().Delete(envName, productName) if err != nil { log.Errorf("Product.Delete error: %v", err) } @@ -87,7 +85,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su // 发送删除产品失败消息给用户 title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) SendErrorMessage(username, title, requestID, err, log) - _ = mongodb.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) + _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) } else { title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) content := fmt.Sprintf("namespace:%s", productInfo.Namespace) @@ -122,7 +120,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } }() case setting.SourceFromExternal: - err = mongodb.NewProductColl().Delete(envName, productName) + err = commonrepo.NewProductColl().Delete(envName, productName) if err != nil { log.Errorf("Product.Delete error: %v", err) } @@ -133,13 +131,13 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("project not found error:%s", err) } if tempProduct.ProductFeature != nil && tempProduct.ProductFeature.CreateEnvType == setting.SourceFromExternal { - workloadStat, err := mongodb.NewWorkLoadsStatColl().Find(productInfo.ClusterID, productInfo.Namespace) + workloadStat, err := commonrepo.NewWorkLoadsStatColl().Find(productInfo.ClusterID, productInfo.Namespace) if err != nil { log.Errorf("workflowStat not found error:%s", err) } if workloadStat != nil { workloadStat.Workloads = filterWorkloadsByEnv(workloadStat.Workloads, productInfo.EnvName) - if err := mongodb.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat); err != nil { + if err := commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat); err != nil { log.Errorf("update workloads fail error:%s", err) } } @@ -189,7 +187,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su // 发送删除产品失败消息给用户 title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) SendErrorMessage(username, title, requestID, err, log) - _ = mongodb.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) + _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) } else { title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) content := fmt.Sprintf("namespace:%s", productInfo.Namespace) @@ -224,7 +222,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su // return //} - err = mongodb.NewProductColl().Delete(envName, productName) + err = commonrepo.NewProductColl().Delete(envName, productName) if err != nil { log.Errorf("Product.Delete error: %v", err) } @@ -233,8 +231,8 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return nil } -func filterWorkloadsByEnv(exist []models.Workload, env string) []models.Workload { - result := make([]models.Workload, 0) +func filterWorkloadsByEnv(exist []commonmodels.Workload, env string) []commonmodels.Workload { + result := make([]commonmodels.Workload, 0) for _, v := range exist { if v.EnvName != env { result = append(result, v) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 3affb77e..406fc036 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -30,9 +30,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -664,7 +662,7 @@ func ExtractRegistryNamespace(imageURI string) string { return strings.Join(nsComponent, "/") } -func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*models.Container, error) { +func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*commonmodels.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { return nil, err @@ -673,16 +671,16 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s if err != nil { return nil, err } - ret := make([]*models.Container, 0) + ret := make([]*commonmodels.Container, 0) for _, searchResult := range matchedPath { imageUrl, err := GeneImageURI(searchResult, flatMap) if err != nil { return nil, err } - ret = append(ret, &models.Container{ + ret = append(ret, &commonmodels.Container{ Name: ExtractImageName(imageUrl), Image: imageUrl, - ImagePath: &models.ImagePathSpec{ + ImagePath: &commonmodels.ImagePathSpec{ Repo: searchResult[setting.PathSearchComponentRepo], Image: searchResult[setting.PathSearchComponentImage], Tag: searchResult[setting.PathSearchComponentTag], @@ -692,7 +690,7 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s return ret, nil } -func ParseImagesByRules(nested map[string]interface{}, matchRules []*template.ImageSearchingRule) ([]*models.Container, error) { +func ParseImagesByRules(nested map[string]interface{}, matchRules []*templatemodels.ImageSearchingRule) ([]*commonmodels.Container, error) { patterns := make([]map[string]string, 0) for _, rule := range matchRules { if !rule.InUse { @@ -725,7 +723,7 @@ func getServiceParsePatterns(productName string) ([]map[string]string, error) { } // ParseImagesForProductService for product service -func ParseImagesForProductService(nested map[string]interface{}, serviceName, productName string) ([]*models.Container, error) { +func ParseImagesForProductService(nested map[string]interface{}, serviceName, productName string) ([]*commonmodels.Container, error) { patterns, err := getServiceParsePatterns(productName) if err != nil { log.Errorf("failed to get image parse patterns for service %s in project %s, err: %s", serviceName, productName, err) @@ -739,10 +737,10 @@ func ParseImagesByPresetRules(flatMap map[string]interface{}) ([]map[string]stri return yamlutil.SearchByPattern(flatMap, presetPatterns) } -func GetPresetRules() []*template.ImageSearchingRule { - ret := make([]*template.ImageSearchingRule, 0, len(presetPatterns)) +func GetPresetRules() []*templatemodels.ImageSearchingRule { + ret := make([]*templatemodels.ImageSearchingRule, 0, len(presetPatterns)) for id, pattern := range presetPatterns { - ret = append(ret, &template.ImageSearchingRule{ + ret = append(ret, &templatemodels.ImageSearchingRule{ Repo: pattern[setting.PathSearchComponentRepo], Image: pattern[setting.PathSearchComponentImage], Tag: pattern[setting.PathSearchComponentTag], diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 1d822a6b..251076de 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -45,7 +45,6 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" @@ -808,7 +807,7 @@ func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productI } func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { - registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) + registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err != nil { return nil, fmt.Errorf("failed to query registries") } @@ -1014,7 +1013,7 @@ func checkVersionStatus(deliveryVersion *commonmodels.DeliveryVersion) (bool, er pipelineName := fmt.Sprintf("%s-%s-%s", deliveryVersion.ProductName, deliveryVersion.ProductEnvInfo.EnvName, "artifact") taskData, err := commonrepo.NewTaskColl().Find(int64(deliveryVersion.TaskID), pipelineName, config.ArtifactType) if err != nil { - return false, fmt.Errorf("failed to query taskData, id: %d, pipelineName: %s", deliveryVersion.TaskID) + return false, fmt.Errorf("failed to query taskData, id: %d, pipelineName: %s", deliveryVersion.TaskID, pipelineName) } if len(taskData.Stages) != 1 { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 60d19f6e..e3ee8cf9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,11 +46,8 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -103,25 +100,25 @@ type EnvResp struct { } type ProductResp struct { - ID string `json:"id"` - ProductName string `json:"product_name"` - Namespace string `json:"namespace"` - Status string `json:"status"` - Error string `json:"error"` - EnvName string `json:"env_name"` - UpdateBy string `json:"update_by"` - UpdateTime int64 `json:"update_time"` - Services [][]string `json:"services"` - Render *commonmodels.RenderInfo `json:"render"` - Vars []*template.RenderKV `json:"vars"` - IsPublic bool `json:"isPublic"` - ClusterID string `json:"cluster_id,omitempty"` - ClusterName string `json:"cluster_name,omitempty"` - RecycleDay int `json:"recycle_day"` - IsProd bool `json:"is_prod"` - IsLocal bool `json:"is_local"` - Source string `json:"source"` - RegisterID string `json:"registry_id"` + ID string `json:"id"` + ProductName string `json:"product_name"` + Namespace string `json:"namespace"` + Status string `json:"status"` + Error string `json:"error"` + EnvName string `json:"env_name"` + UpdateBy string `json:"update_by"` + UpdateTime int64 `json:"update_time"` + Services [][]string `json:"services"` + Render *commonmodels.RenderInfo `json:"render"` + Vars []*templatemodels.RenderKV `json:"vars"` + IsPublic bool `json:"isPublic"` + ClusterID string `json:"cluster_id,omitempty"` + ClusterName string `json:"cluster_name,omitempty"` + RecycleDay int `json:"recycle_day"` + IsProd bool `json:"is_prod"` + IsLocal bool `json:"is_local"` + Source string `json:"source"` + RegisterID string `json:"registry_id"` } type ProductParams struct { @@ -566,7 +563,7 @@ func UpdateProductRegistry(envName, productName, registryID string, log *zap.Sug return nil } -func UpdateProductV2(envName, productName, user, requestID string, force bool, kvs []*template.RenderKV, log *zap.SugaredLogger) (err error) { +func UpdateProductV2(envName, productName, user, requestID string, force bool, kvs []*templatemodels.RenderKV, log *zap.SugaredLogger) (err error) { // 根据产品名称和产品创建者到数据库中查找已有产品记录 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} exitedProd, err := commonrepo.NewProductColl().Find(opt) @@ -709,7 +706,7 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe return errList.ErrorOrNil() } -func createSingleHelmProduct(templateProduct *template.Product, requestID, userName, registryID string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { +func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, userName, registryID string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { productObj := &commonmodels.Product{ ProductName: templateProduct.ProductName, Revision: 1, @@ -995,7 +992,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return nil } -func genImageFromYaml(c *models.Container, valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { +func genImageFromYaml(c *commonmodels.Container, valuesYaml, defaultValues, overrideYaml, overrideValues string) (string, error) { mergeYaml, err := helmtool.MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues) if err != nil { return "", err @@ -1041,8 +1038,8 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa } // find chart info from cur render set - opt := &mongodb.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} - renderSet, err := mongodb.NewRenderSetColl().Find(opt) + opt := &commonrepo.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} + renderSet, err := commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("renderset Find error, productName:%s, envName:%s, err:%s", productInfo.ProductName, productInfo.EnvName, err) return "", "", fmt.Errorf("failed to query renderset info, name %s", productInfo.Render.Name) @@ -1112,7 +1109,7 @@ func GeneEstimatedValues(productName, envName, serviceName, scene, format string } // check if override values or yaml content changes -func checkOverrideValuesChange(source *template.RenderChart, args *commonservice.RenderChartArg) bool { +func checkOverrideValuesChange(source *templatemodels.RenderChart, args *commonservice.RenderChartArg) bool { if source.OverrideValues != args.ToOverrideValueString() || source.GetOverrideYaml() != args.OverrideYaml { return true } @@ -1138,8 +1135,8 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string } // render charts need to be updated - updatedRcList := make([]*template.RenderChart, 0) - updatedRCMap := make(map[string]*template.RenderChart) + updatedRcList := make([]*templatemodels.RenderChart, 0) + updatedRCMap := make(map[string]*templatemodels.RenderChart) // default values change if args.DefaultValues != productRenderset.DefaultValues { @@ -1180,7 +1177,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string return ensureKubeEnv(product.Namespace, product.RegistryID, kubeClient, log) } -func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*template.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { +func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*templatemodels.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -1410,7 +1407,7 @@ func GetProductIngress(productName string, log *zap.SugaredLogger) ([]*ProductIn func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ([]*commonmodels.HelmVersions, error) { var ( helmVersions = make([]*commonmodels.HelmVersions, 0) - chartInfoMap = make(map[string]*template.RenderChart) + chartInfoMap = make(map[string]*templatemodels.RenderChart) ) opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) @@ -1447,7 +1444,7 @@ func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ( } for _, serviceGroup := range prodTmpl.Services { for _, serviceName := range serviceGroup { - serviceListOpt.InServices = append(serviceListOpt.InServices, &template.ServiceInfo{ + serviceListOpt.InServices = append(serviceListOpt.InServices, &templatemodels.ServiceInfo{ Name: serviceName, Owner: productName, }) @@ -1540,7 +1537,7 @@ func GetEstimatedRenderCharts(productName, envName, serviceNameListStr string, l if _, ok := rcMap[serviceName]; ok { continue } - serviceOption.InServices = append(serviceOption.InServices, &template.ServiceInfo{ + serviceOption.InServices = append(serviceOption.InServices, &templatemodels.ServiceInfo{ Name: serviceName, Owner: productName, }) @@ -2098,7 +2095,7 @@ func preCreateProduct(envName string, args *commonmodels.Product, kubeClient cli args.Vars = nil - var productTmpl *template.Product + var productTmpl *templatemodels.Product // 查询产品模板 productTmpl, err = templaterepo.NewProductColl().Find(productTemplateName) if err != nil { @@ -2139,7 +2136,7 @@ func preCreateProduct(envName string, args *commonmodels.Product, kubeClient cli return nil } -func preCreateNSAndSecret(productFeature *template.ProductFeature) bool { +func preCreateNSAndSecret(productFeature *templatemodels.ProductFeature) bool { if productFeature == nil { return true } @@ -2280,7 +2277,7 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { +func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) @@ -2289,7 +2286,7 @@ func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderCha return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, helmClient) } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2358,7 +2355,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel } }() - chartInfoMap := make(map[string]*template.RenderChart) + chartInfoMap := make(map[string]*templatemodels.RenderChart) for _, renderChart := range args.ChartInfos { chartInfoMap[renderChart.ServiceName] = renderChart } @@ -2510,7 +2507,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic func updateProductGroup(username, productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { var ( - renderChartMap = make(map[string]*template.RenderChart) + renderChartMap = make(map[string]*templatemodels.RenderChart) productServiceMap = make(map[string]*commonmodels.ProductService) productTemplServiceMap = make(map[string]*commonmodels.ProductService) ) @@ -2643,7 +2640,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return nil, err } - latestRenderSetMap := make(map[string]*template.RenderChart) + latestRenderSetMap := make(map[string]*templatemodels.RenderChart) for _, renderInfo := range latestRenderSet.ChartInfos { latestRenderSetMap[renderInfo.ServiceName] = renderInfo } @@ -2656,7 +2653,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes renderChartArgMap[singleArg.ServiceName] = singleArg } - newChartInfos := make([]*template.RenderChart, 0) + newChartInfos := make([]*templatemodels.RenderChart, 0) defaultValues := "" switch updateType { case UpdateTypeSystem: @@ -2677,13 +2674,13 @@ func diffRenderSet(username, productName, envName, updateType string, productRes defaultValues = currentEnvRenderSet.DefaultValues // 环境里面的变量 - currentEnvRenderSetMap := make(map[string]*template.RenderChart) + currentEnvRenderSetMap := make(map[string]*templatemodels.RenderChart) for _, renderInfo := range currentEnvRenderSet.ChartInfos { currentEnvRenderSetMap[renderInfo.ServiceName] = renderInfo } - tmpCurrentChartInfoMap := make(map[string]*template.RenderChart) - tmpLatestChartInfoMap := make(map[string]*template.RenderChart) + tmpCurrentChartInfoMap := make(map[string]*templatemodels.RenderChart) + tmpLatestChartInfoMap := make(map[string]*templatemodels.RenderChart) //过滤掉服务编排没有的服务,这部分不需要做diff for _, serviceNameGroup := range productTemp.Services { for _, serviceName := range serviceNameGroup { @@ -2812,7 +2809,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel return e.ErrUpdateEnv.AddErr(err) } - renderChartMap := make(map[string]*template.RenderChart) + renderChartMap := make(map[string]*templatemodels.RenderChart) for _, renderChart := range productResp.ChartInfos { renderChartMap[renderChart.ServiceName] = renderChart } diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 495551d7..e76b7a5d 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -27,7 +27,6 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" svcservice "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -166,7 +165,7 @@ type K8sWorkloadsArgs struct { func UpdateWorkloads(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(service.UpdateWorkloadsArgs) + args := new(svcservice.UpdateWorkloadsArgs) err := c.ShouldBindJSON(args) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid UpdateWorkloadsArgs") @@ -178,7 +177,7 @@ func UpdateWorkloads(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Err = service.UpdateWorkloads(c, ctx.RequestID, ctx.UserName, product, env, *args, ctx.Logger) + ctx.Err = svcservice.UpdateWorkloads(c, ctx.RequestID, ctx.UserName, product, env, *args, ctx.Logger) } func CreateK8sWorkloads(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d4c2ff51..5ac0d28a 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -37,7 +37,6 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templatedata "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -829,7 +828,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { LoadPath: args.FilePath, } case setting.SourceFromChartTemplate: - yamlData := &templatedata.CustomYaml{ + yamlData := &templatemodels.CustomYaml{ YamlContent: args.ValuesYaml, } variables := make([]*models.Variable, 0, len(args.Variables)) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 1bd83639..4fea0923 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -35,7 +35,6 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -255,7 +254,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv return serviceOption, nil } -func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []models.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { +func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []commonmodels.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s] error: %v", namespace, err) @@ -270,7 +269,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product // todo Add data filter var ( - workloadsTmp []models.Workload + workloadsTmp []commonmodels.Workload mu sync.Mutex ) @@ -315,14 +314,14 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product mu.Lock() defer mu.Unlock() - workloadsTmp = append(workloadsTmp, models.Workload{ + workloadsTmp = append(workloadsTmp, commonmodels.Workload{ EnvName: envName, Name: tempWorkload.Name, Type: tempWorkload.Type, ProductName: productName, }) - return CreateWorkloadTemplate(username, &models.Service{ + return CreateWorkloadTemplate(username, &commonmodels.Service{ ServiceName: tempWorkload.Name, Yaml: string(bs), ProductName: productName, @@ -505,7 +504,7 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN delete(diff, v.Name) continue } - if err = CreateWorkloadTemplate(username, &models.Service{ + if err = CreateWorkloadTemplate(username, &commonmodels.Service{ ServiceName: v.Name, Yaml: string(bs), ProductName: productName, @@ -527,15 +526,15 @@ func UpdateWorkloads(ctx context.Context, requestID, username, productName, envN return commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat) } -func updateWorkloads(existWorkloads []models.Workload, diff map[string]*ServiceWorkloadsUpdateAction, envName string, productName string) (result []models.Workload) { - existWorkloadsMap := map[string]models.Workload{} +func updateWorkloads(existWorkloads []commonmodels.Workload, diff map[string]*ServiceWorkloadsUpdateAction, envName string, productName string) (result []commonmodels.Workload) { + existWorkloadsMap := map[string]commonmodels.Workload{} for _, v := range existWorkloads { existWorkloadsMap[v.Name] = v } for _, v := range diff { switch v.Operation { case "add": - vv := models.Workload{ + vv := commonmodels.Workload{ EnvName: envName, Name: v.Name, Type: v.Type, @@ -552,9 +551,9 @@ func updateWorkloads(existWorkloads []models.Workload, diff map[string]*ServiceW return result } -func replaceWorkloads(existWorkloads []models.Workload, newWorkloads []models.Workload, envName string) []models.Workload { - var result []models.Workload - workloadMap := map[string]models.Workload{} +func replaceWorkloads(existWorkloads []commonmodels.Workload, newWorkloads []commonmodels.Workload, envName string) []commonmodels.Workload { + var result []commonmodels.Workload + workloadMap := map[string]commonmodels.Workload{} for _, workload := range existWorkloads { if workload.EnvName != envName { workloadMap[workload.Name] = workload diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go index 9e2e2fda..bfc0f010 100644 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ b/pkg/microservice/aslan/core/setting/service/user.go @@ -30,7 +30,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -140,7 +139,7 @@ var ( ObjectMeta: metav1.ObjectMeta{ Name: config.RoleBindingNameEdit, }, - Rules: []rbacv1beta1.PolicyRule{rbacv1beta1.PolicyRule{ + Rules: []rbacv1beta1.PolicyRule{{ Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"pods", "deployments", "configmaps", "crobjobs", "daemonsets", "ingresses", "jobs", "secrets", "services", "statefulsets"}, @@ -149,7 +148,7 @@ var ( ObjectMeta: metav1.ObjectMeta{ Name: config.RoleBindingNameView, }, - Rules: []rbacv1beta1.PolicyRule{rbacv1beta1.PolicyRule{ + Rules: []rbacv1beta1.PolicyRule{{ Verbs: []string{"get", "watch", "list"}, APIGroups: []string{"*"}, Resources: []string{"*"}, @@ -218,7 +217,7 @@ func ensureServiceAccountAndRolebinding(namespace string, projectsEnvCanEdit []s projectsEnvCanView = res } - canEditProducts := []*models.Product{} + canEditProducts := []*commonmodels.Product{} if len(projectsEnvCanEdit) != 0 { canEditProducts, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanEdit}) if err != nil { @@ -231,7 +230,7 @@ func ensureServiceAccountAndRolebinding(namespace string, projectsEnvCanEdit []s log.Warnf("CreateRoleBinding err: %s", err) } } - canViewProducts := []*models.Product{} + canViewProducts := []*commonmodels.Product{} if len(projectsEnvCanView) != 0 { canViewProducts, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanView}) if err != nil { diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 54276fb3..873c493e 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -20,7 +20,6 @@ import ( "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/errors" @@ -36,14 +35,14 @@ func GetChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetChartTemplate(c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = templateservice.GetChartTemplate(c.Param("name"), ctx.Logger) } func GetTemplateVariables(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetChartTemplateVariables(c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = templateservice.GetChartTemplateVariables(c.Param("name"), ctx.Logger) } func ListFiles(c *gin.Context) { @@ -51,14 +50,14 @@ func ListFiles(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() // TODO: support to return multiple files in a bulk - ctx.Resp, ctx.Err = service.GetFileContentForTemplate(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) + ctx.Resp, ctx.Err = templateservice.GetFileContentForTemplate(c.Param("name"), c.Query("filePath"), c.Query("fileName"), ctx.Logger) } func ListChartTemplates(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListChartTemplates(ctx.Logger) + ctx.Resp, ctx.Err = templateservice.ListChartTemplates(ctx.Logger) } func AddChartTemplate(c *gin.Context) { @@ -71,7 +70,7 @@ func AddChartTemplate(c *gin.Context) { return } - ctx.Err = service.AddChartTemplate(args.Name, args.DownloadFromSourceArgs, ctx.Logger) + ctx.Err = templateservice.AddChartTemplate(args.Name, args.DownloadFromSourceArgs, ctx.Logger) } func UpdateChartTemplate(c *gin.Context) { @@ -84,7 +83,7 @@ func UpdateChartTemplate(c *gin.Context) { return } - ctx.Err = service.UpdateChartTemplate(c.Param("name"), args.DownloadFromSourceArgs, ctx.Logger) + ctx.Err = templateservice.UpdateChartTemplate(c.Param("name"), args.DownloadFromSourceArgs, ctx.Logger) } func UpdateChartTemplateVariables(c *gin.Context) { @@ -97,12 +96,12 @@ func UpdateChartTemplateVariables(c *gin.Context) { return } - ctx.Err = service.UpdateChartTemplateVariables(c.Param("name"), args, ctx.Logger) + ctx.Err = templateservice.UpdateChartTemplateVariables(c.Param("name"), args, ctx.Logger) } func RemoveChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.RemoveChartTemplate(c.Param("name"), ctx.Logger) + ctx.Err = templateservice.RemoveChartTemplate(c.Param("name"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index e8857f4f..ac55502a 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -24,7 +24,6 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" ) @@ -42,7 +41,7 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error Value: variable.Value, }) } - err := mongodb.NewYamlTemplateColl().Create(&models.YamlTemplate{ + err := commonrepo.NewYamlTemplateColl().Create(&models.YamlTemplate{ Name: template.Name, Content: template.Content, Variables: vars, @@ -61,7 +60,7 @@ func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLo Value: variable.Value, }) } - err := mongodb.NewYamlTemplateColl().Update( + err := commonrepo.NewYamlTemplateColl().Update( id, &models.YamlTemplate{ Name: template.Name, @@ -77,7 +76,7 @@ func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLo func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*YamlListObject, int, error) { resp := make([]*YamlListObject, 0) - templateList, total, err := mongodb.NewYamlTemplateColl().List(pageNum, pageSize) + templateList, total, err := commonrepo.NewYamlTemplateColl().List(pageNum, pageSize) if err != nil { logger.Errorf("list dockerfile template error: %s", err) return resp, 0, err @@ -93,7 +92,7 @@ func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*Yaml func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, error) { resp := new(YamlDetail) - yamlTemplate, err := mongodb.NewYamlTemplateColl().GetById(id) + yamlTemplate, err := commonrepo.NewYamlTemplateColl().GetById(id) if err != nil { logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) return nil, err @@ -121,7 +120,7 @@ func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { if len(ref) > 0 { return errors.New("this template is in use") } - err = mongodb.NewYamlTemplateColl().DeleteByID(id) + err = commonrepo.NewYamlTemplateColl().DeleteByID(id) if err != nil { logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index decf797e..0fad2b09 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -25,7 +25,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -77,7 +76,7 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC return 0, err } - task := &task.Task{ + task := &taskmodels.Task{ Type: config.ArtifactType, ProductName: args.ProjectName, Status: config.StatusCreated, diff --git a/pkg/microservice/picket/client/aslan/delivery.go b/pkg/microservice/picket/client/aslan/delivery.go index 2eaad396..5649c3ed 100644 --- a/pkg/microservice/picket/client/aslan/delivery.go +++ b/pkg/microservice/picket/client/aslan/delivery.go @@ -17,7 +17,6 @@ limitations under the License. package aslan import ( - "fmt" "net/http" "net/url" @@ -25,7 +24,7 @@ import ( ) func (c *Client) ListDelivery(header http.Header, qs url.Values, productName, workflowName, taskId, perPage, page string) ([]byte, error) { - url := fmt.Sprintf("/delivery/releases") + url := "/delivery/releases" queryParams := make(map[string]string) queryParams["productName"] = productName queryParams["workflowName"] = workflowName diff --git a/pkg/microservice/systemconfig/config/config.go b/pkg/microservice/systemconfig/config/config.go index d93f30b0..bc3f164b 100644 --- a/pkg/microservice/systemconfig/config/config.go +++ b/pkg/microservice/systemconfig/config/config.go @@ -19,7 +19,6 @@ package config import ( "github.com/spf13/viper" - _ "github.com/koderover/zadig/pkg/config" configbase "github.com/koderover/zadig/pkg/config" ) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 3c5d364d..076f9b2f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -50,7 +50,6 @@ import ( s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" - "github.com/koderover/zadig/pkg/util/fs" fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -456,7 +455,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } } - chartPath, err = fs.RelativeToCurrentPath(path) + chartPath, err = fsutil.RelativeToCurrentPath(path) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index b423cf8e..658a207d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -123,7 +123,7 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi p.pipelineName = pipelineTask.PipelineName p.taskId = pipelineTask.TaskID p.Log.Infof("succeed to create trigger task %s", p.JobName) - ctx, p.cancel = context.WithCancel(context.Background()) + _, p.cancel = context.WithCancel(context.Background()) httpClient := httpclient.New( httpclient.SetHostURL(p.Task.URL), ) -- Gitee From 36016b1a8af452b914df68ed88b9b7df0445f4ae Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 23 Dec 2021 16:19:39 +0800 Subject: [PATCH 1089/2647] upgrade helm version (#802) Signed-off-by: allenshen --- go.mod | 2 +- go.sum | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index b62c82d8..ab0efb22 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b gorm.io/driver/mysql v1.1.2 gorm.io/gorm v1.21.16 - helm.sh/helm/v3 v3.7.1 + helm.sh/helm/v3 v3.7.2 k8s.io/api v0.22.4 k8s.io/apimachinery v0.22.4 k8s.io/client-go v0.22.4 diff --git a/go.sum b/go.sum index 80995ae8..b5a6e209 100644 --- a/go.sum +++ b/go.sum @@ -719,6 +719,7 @@ github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.7.3/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= @@ -2319,8 +2320,9 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= helm.sh/helm/v3 v3.7.0/go.mod h1:DajHtQTe8KrjNmvy5gxWkosFKaADrS3uRS5EkDtsmI4= -helm.sh/helm/v3 v3.7.1 h1:kED/HWx09QHHSJhYaJY6ttj/BhmzBmT1oupKslncibY= helm.sh/helm/v3 v3.7.1/go.mod h1:3eOeBD3Z+O/ELiuu19zynZSN8jP1ErXLuyP21SZeMq8= +helm.sh/helm/v3 v3.7.2 h1:xn1OxcZEpgKpp4CCpPz1KKUyb9gAtTouXV2E3S8ChYQ= +helm.sh/helm/v3 v3.7.2/go.mod h1:UXuiAn0+FfBpqbiMuwWt8/aAKkfJvnWLBJ6f4HcFs0M= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -- Gitee From 06e6369799673710646c049e830cd0e5893c22f4 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 23 Dec 2021 16:19:50 +0800 Subject: [PATCH 1090/2647] fix name re-use error when install/upgrade helm charts (#808) Signed-off-by: allenshen --- .../core/environment/service/environment.go | 49 +++++++++++-------- .../aslan/core/environment/service/image.go | 2 +- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 60d19f6e..7cdd6abe 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -168,7 +168,7 @@ type RawYamlResp struct { YamlContent string `json:"yamlContent"` } -type intervalExecutorHandler func(data *commonmodels.Service, log *zap.SugaredLogger) error +type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) @@ -2280,16 +2280,16 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { +func installOrUpgradeHelmChart(namespace string, renderChart *template.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client) error { mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err } - return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, helmClient) + return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, isRetry, helmClient) } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, helmClient helmclient.Client) error { +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *template.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client) error { base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2310,12 +2310,16 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha } chartSpec := &helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(namespace, serviceObj.ServiceName), - ChartName: chartPath, - Namespace: namespace, - Version: renderChart.ChartVersion, - ValuesYaml: valuesYaml, - UpgradeCRDs: true, + ReleaseName: util.GeneHelmReleaseName(namespace, serviceObj.ServiceName), + ChartName: chartPath, + Namespace: namespace, + Version: renderChart.ChartVersion, + ValuesYaml: valuesYaml, + UpgradeCRDs: true, + CleanupOnFail: true, + } + if isRetry { + chartSpec.Replace = true } if timeout > 0 { chartSpec.Wait = true @@ -2363,11 +2367,12 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, logger *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, helmClient) + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, isRetry, helmClient) if err != nil { - return err + log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) + return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) } return nil } @@ -2469,20 +2474,22 @@ func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, servic bo := backoff.NewConstantBackOff(time.Second * 3) retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) + isRetry := false _ = backoff.Retry(func() error { failedServices := make([]*commonmodels.Service, 0) - errList = intervalExecutor(interval, serviceList, &failedServices, handler, log) + errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, log) if len(errList) == 0 { return nil } log.Infof("%d services waiting to retry", len(failedServices)) serviceList = failedServices + isRetry = true return fmt.Errorf("%d services apply failed", len(errList)) }, retryBo) return errList } -func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { +func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } @@ -2495,7 +2502,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic for _, data := range serviceList { go func() { defer wg.Done() - err := handler(data, log) + err := handler(data, isRetry, log) if err != nil { errList = append(errList, err) *failedServices = append(*failedServices, data) @@ -2570,10 +2577,11 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, log *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, helmClient) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, isRetry, helmClient) if err != nil { + log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) } return nil @@ -2817,10 +2825,11 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(service *commonmodels.Service, log *zap.SugaredLogger) error { + handler := func(service *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, helmClient) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, isRetry, helmClient) if err != nil { + log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) } return nil diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index e6d344b0..f47af546 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -268,7 +268,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri } // when replace image, should not wait - err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, 0, helmClient) + err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, 0, false, helmClient) if err != nil { return err } -- Gitee From 4cb43b6c0f35ef609f83c152d0a5cd804167a7c7 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 23 Dec 2021 21:39:05 +0800 Subject: [PATCH 1091/2647] add delivery version finish hook notify (#809) * add delivery version finish hook notify Signed-off-by: allenshen * update container name to service module Signed-off-by: allenshen * change err format Signed-off-by: allenshen --- .../repository/models/template/product.go | 53 +++-- .../repository/mongodb/delivery_version.go | 1 + .../repository/mongodb/template/product.go | 1 + .../aslan/core/delivery/handler/version.go | 2 +- .../aslan/core/delivery/service/version.go | 213 ++++++++++++++++-- 5 files changed, 222 insertions(+), 48 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 64940bca..19e15377 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -45,29 +45,30 @@ type Product struct { // onboarding状态,0表示onboarding完成,1、2、3、4代表当前onboarding所在的步骤 OnboardingStatus int `bson:"onboarding_status" json:"onboarding_status"` // CI场景的onboarding流程创建的ci工作流id,用于前端跳转 - CiPipelineID string `bson:"-" json:"ci_pipeline_id"` - Role string `bson:"-" json:"role,omitempty"` - PermissionUUIDs []string `bson:"-" json:"permissionUUIDs"` - TotalServiceNum int `bson:"-" json:"total_service_num"` - LatestServiceUpdateTime int64 `bson:"-" json:"latest_service_update_time"` - LatestServiceUpdateBy string `bson:"-" json:"latest_service_update_by"` - TotalBuildNum int `bson:"-" json:"total_build_num"` - LatestBuildUpdateTime int64 `bson:"-" json:"latest_build_update_time"` - LatestBuildUpdateBy string `bson:"-" json:"latest_build_update_by"` - TotalTestNum int `bson:"-" json:"total_test_num"` - LatestTestUpdateTime int64 `bson:"-" json:"latest_test_update_time"` - LatestTestUpdateBy string `bson:"-" json:"latest_test_update_by"` - TotalEnvNum int `bson:"-" json:"total_env_num"` - LatestEnvUpdateTime int64 `bson:"-" json:"latest_env_update_time"` - LatestEnvUpdateBy string `bson:"-" json:"latest_env_update_by"` - TotalWorkflowNum int `bson:"-" json:"total_workflow_num"` - LatestWorkflowUpdateTime int64 `bson:"-" json:"latest_workflow_update_time"` - LatestWorkflowUpdateBy string `bson:"-" json:"latest_workflow_update_by"` - TotalEnvTemplateServiceNum int `bson:"-" json:"total_env_template_service_num"` - IsOpensource bool `bson:"is_opensource" json:"is_opensource"` - CustomImageRule *CustomRule `bson:"custom_image_rule,omitempty" json:"custom_image_rule,omitempty"` - CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` - Public bool `bson:"public,omitempty" json:"public"` + CiPipelineID string `bson:"-" json:"ci_pipeline_id"` + Role string `bson:"-" json:"role,omitempty"` + PermissionUUIDs []string `bson:"-" json:"permissionUUIDs"` + TotalServiceNum int `bson:"-" json:"total_service_num"` + LatestServiceUpdateTime int64 `bson:"-" json:"latest_service_update_time"` + LatestServiceUpdateBy string `bson:"-" json:"latest_service_update_by"` + TotalBuildNum int `bson:"-" json:"total_build_num"` + LatestBuildUpdateTime int64 `bson:"-" json:"latest_build_update_time"` + LatestBuildUpdateBy string `bson:"-" json:"latest_build_update_by"` + TotalTestNum int `bson:"-" json:"total_test_num"` + LatestTestUpdateTime int64 `bson:"-" json:"latest_test_update_time"` + LatestTestUpdateBy string `bson:"-" json:"latest_test_update_by"` + TotalEnvNum int `bson:"-" json:"total_env_num"` + LatestEnvUpdateTime int64 `bson:"-" json:"latest_env_update_time"` + LatestEnvUpdateBy string `bson:"-" json:"latest_env_update_by"` + TotalWorkflowNum int `bson:"-" json:"total_workflow_num"` + LatestWorkflowUpdateTime int64 `bson:"-" json:"latest_workflow_update_time"` + LatestWorkflowUpdateBy string `bson:"-" json:"latest_workflow_update_by"` + TotalEnvTemplateServiceNum int `bson:"-" json:"total_env_template_service_num"` + IsOpensource bool `bson:"is_opensource" json:"is_opensource"` + CustomImageRule *CustomRule `bson:"custom_image_rule,omitempty" json:"custom_image_rule,omitempty"` + CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` + DeliveryVersionHook *DeliveryVersionHook `bson:"delivery_version_hook" json:"delivery_version_hook"` + Public bool `bson:"public,omitempty" json:"public"` } type ServiceInfo struct { @@ -150,6 +151,12 @@ type CustomRule struct { TagRule string `bson:"tag_rule,omitempty" json:"tag_rule,omitempty"` } +type DeliveryVersionHook struct { + Enable bool `bson:"enable" json:"enable"` + HookHost string `bson:"hook_host" json:"hook_host"` + Path string `bson:"path" json:"path"` +} + func (Product) TableName() string { return "template_product" } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go index 51e071d7..fac84960 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_version.go @@ -64,6 +64,7 @@ func (c *DeliveryVersionColl) EnsureIndex(ctx context.Context) error { { Keys: bson.D{ bson.E{Key: "org_id", Value: 1}, + bson.E{Key: "product_name", Value: 1}, bson.E{Key: "workflow_name", Value: 1}, bson.E{Key: "version", Value: 1}, bson.E{Key: "deleted_at", Value: 1}, diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 474d2e96..ad624611 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -258,6 +258,7 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { "image_searching_rules": args.ImageSearchingRules, "custom_tar_rule": args.CustomTarRule, "custom_image_rule": args.CustomImageRule, + "delivery_version_hook": args.DeliveryVersionHook, "public": args.Public, }} diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 39a4e551..22a0f959 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -249,7 +249,7 @@ func GetChartVersionFromRepo(c *gin.Context) { chartName := c.Query("chartName") chartRepoName := c.Query("chartRepoName") - ctx.Resp, ctx.Err = deliveryservice.GetChartVersions(chartName, chartRepoName) + ctx.Resp, ctx.Err = deliveryservice.GetChartVersion(chartName, chartRepoName) } func PreviewGetDeliveryChart(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 1d822a6b..ea65b7c8 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "bytes" "encoding/json" "fmt" "io" @@ -24,6 +25,7 @@ import ( "net/http" "net/url" "os" + "path" "path/filepath" "strings" "sync" @@ -47,6 +49,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" @@ -186,6 +189,29 @@ type ServiceImageDetails struct { type ChartVersionResp struct { ChartName string `json:"chartName"` ChartVersion string `json:"chartVersion"` + Url string `json:"url"` +} + +type DeliveryVersionPayloadImage struct { + ServiceModule string `json:"service_module"` + Image string `json:"image"` +} + +type DeliveryVersionPayloadChart struct { + ChartName string `json:"chart_name"` + ChartVersion string `json:"chart_version"` + ChartUrl string `json:"chart_url"` + Images []*DeliveryVersionPayloadImage `json:"images"` +} + +type DeliveryVersionHookPayload struct { + ProjectName string `json:"project_name"` + Version string `json:"version"` + Status string `json:"status"` + Error string `json:"error"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + Charts []*DeliveryVersionPayloadChart `json:"charts"` } func GetDeliveryVersion(args *commonrepo.DeliveryVersionArgs, log *zap.SugaredLogger) (*commonmodels.DeliveryVersion, error) { @@ -389,12 +415,12 @@ func processReleaseRespData(release *ReleaseInfo) { return } - distributeMap := make(map[string][]*commonmodels.DeliveryDistribute) + distributeImageMap := make(map[string][]*commonmodels.DeliveryDistribute) for _, distributeImage := range release.DistributeInfo { if distributeImage.DistributeType != config.Image { continue } - distributeMap[distributeImage.ChartName] = append(distributeMap[distributeImage.ChartName], distributeImage) + distributeImageMap[distributeImage.ChartName] = append(distributeImageMap[distributeImage.ChartName], distributeImage) } chartDistributeCount := 0 @@ -406,7 +432,7 @@ func processReleaseRespData(release *ReleaseInfo) { switch distribute.DistributeType { case config.Chart: chartDistributeCount++ - distribute.SubDistributes = distributeMap[distribute.ChartName] + distribute.SubDistributes = distributeImageMap[distribute.ChartName] case config.File: s3Storage, err := commonrepo.NewS3StorageColl().Find(distribute.S3StorageID) if err != nil { @@ -739,17 +765,17 @@ func getChartmuseumError(b []byte, code int) error { } func makeChartTGZFileDir(productName, versionName string) (string, error) { - path := getChartTGZDir(productName, versionName) - if err := os.RemoveAll(path); err != nil { + dirPath := getChartTGZDir(productName, versionName) + if err := os.RemoveAll(dirPath); err != nil { if !os.IsExist(err) { return "", errors.Wrapf(err, "failed to claer dir for chart tgz files") } } - err := os.MkdirAll(path, 0777) + err := os.MkdirAll(dirPath, 0777) if err != nil { return "", errors.Wrapf(err, "failed to create chart tgz dir for version: %s", versionName) } - return path, nil + return dirPath, nil } func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap.SugaredLogger) error { @@ -896,10 +922,7 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer deliveryVersion.Status = setting.DeliveryVersionStatusFailed deliveryVersion.Error = err.Error() } - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, deliveryVersion.Error) - if err != nil { - logger.Errorf("failed to update delivery version data, name: %s error: %s", deliveryVersion.Version, err) - } + updateVersionStatus(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, deliveryVersion.Error) }() var errLock sync.Mutex @@ -975,7 +998,113 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return } +// send hook +func sendVersionDeliveryHook(projectName, version, host, urlPath string) error { + + deliveryVersion, err := commonrepo.NewDeliveryVersionColl().Get(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: version, + }) + if err != nil { + return err + } + + ret := &DeliveryVersionHookPayload{ + ProjectName: projectName, + Version: version, + Status: setting.DeliveryVersionStatusSuccess, + Error: "", + StartTime: deliveryVersion.CreatedAt, + EndTime: time.Now().Unix(), + Charts: make([]*DeliveryVersionPayloadChart, 0), + } + + //distributes image + chart + deliveryDistributes, err := FindDeliveryDistribute(&commonrepo.DeliveryDistributeArgs{ + ReleaseID: deliveryVersion.ID.Hex(), + }, log.SugaredLogger()) + if err != nil { + return err + } + + distributeImageMap := make(map[string][]*DeliveryVersionPayloadImage) + for _, distributeImage := range deliveryDistributes { + if distributeImage.DistributeType != config.Image { + continue + } + distributeImageMap[distributeImage.ChartName] = append(distributeImageMap[distributeImage.ChartName], &DeliveryVersionPayloadImage{ + ServiceModule: distributeImage.ServiceName, + Image: distributeImage.RegistryName, + }) + } + + chartRepoName := "" + for _, distribute := range deliveryDistributes { + if distribute.DistributeType != config.Chart { + continue + } + ret.Charts = append(ret.Charts, &DeliveryVersionPayloadChart{ + ChartName: distribute.ChartName, + ChartVersion: distribute.ChartVersion, + Images: distributeImageMap[distribute.ChartName], + }) + chartRepoName = distribute.ChartRepoName + } + err = fillChartUrl(ret.Charts, chartRepoName) + if err != nil { + return err + } + + u, err := url.Parse(host) + if err != nil { + return err + } + u.Path = path.Join(u.Path, urlPath) + + reqBody, err := json.Marshal(ret) + if err != nil { + log.Errorf("marshal json args error: %s", err) + return err + } + + request, err := http.NewRequest("POST", u.String(), bytes.NewBuffer(reqBody)) + if err != nil { + return err + } + request.Header.Set("Content-Type", "application/json") + + client := http.Client{} + resp, err := client.Do(request) + if err != nil { + return err + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("hook request send code error: %d", resp.StatusCode) + } + + return nil +} + func updateVersionStatus(versionName, projectName, status, errStr string) { + // send hook info when version build finished + if status == setting.DeliveryVersionStatusSuccess { + templateProduct, err := templaterepo.NewProductColl().Find(projectName) + if err != nil { + log.Errorf("updateVersionStatus failed to find template product: %s, err: %s", projectName, err) + } else { + hookConfig := templateProduct.DeliveryVersionHook + if hookConfig != nil && hookConfig.Enable { + err = sendVersionDeliveryHook(projectName, versionName, hookConfig.HookHost, hookConfig.Path) + if err != nil { + log.Errorf("updateVersionStatus failed to send version delivery hook, projectName: %s, err: %s", projectName, err) + } + } + } + } + err := commonrepo.NewDeliveryVersionColl().UpdateStatusByName(versionName, projectName, status, errStr) if err != nil { log.Errorf("failed to update version status, name: %s, err: %s", versionName, err) @@ -987,7 +1116,7 @@ func taskFinished(status config.Status) bool { } func waitVersionDone(deliveryVersion *commonmodels.DeliveryVersion) { - waitTimeout := time.After(60 * time.Minute * 2) + waitTimeout := time.After(60 * time.Minute * 1) for { select { case <-waitTimeout: @@ -1147,17 +1276,17 @@ func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *z DeletedAt: 0, } - err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) - if err != nil { - return err - } - err = commonrepo.NewDeliveryVersionColl().Insert(versionObj) if err != nil { logger.Errorf("failed to insert version data, err: %s", err) return e.ErrCreateDeliveryVersion.AddErr(fmt.Errorf("failed to insert delivery version: %s", versionObj.Version)) } + err = buildDeliveryCharts(chartDataMap, versionObj, args.DeliveryVersionChartData, logger) + if err != nil { + return err + } + return nil } @@ -1229,11 +1358,7 @@ func RetryCreateHelmDeliveryVersion(projectName, versionName string, logger *zap // update status deliveryVersion.Status = setting.DeliveryVersionStatusRetrying - err = commonrepo.NewDeliveryVersionColl().UpdateStatusByName(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, "") - if err != nil { - logger.Errorf("failed to update delivery status, name: %s, err: %s", deliveryVersion.Version, err) - return fmt.Errorf("failed to update delivery status, name: %s", deliveryVersion.Version) - } + updateVersionStatus(deliveryVersion.Version, deliveryVersion.ProductName, deliveryVersion.Status, deliveryVersion.Error) return nil } @@ -1374,8 +1499,7 @@ func preDownloadChart(projectName, versionName, chartName string, log *zap.Sugar return filePath, err } -func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, error) { - +func getIndexInfoFromChartRepo(chartRepoName string) (*helm.Index, error) { chartRepo, err := getChartRepoData(chartRepoName) if err != nil { return nil, fmt.Errorf("failed to query chart-repo info, repoName %s", chartRepoName) @@ -1408,6 +1532,47 @@ func GetChartVersions(chartName, chartRepoName string) ([]*ChartVersionResp, err if err != nil { return nil, errors.Wrapf(err, "failed to parse index.yaml") } + return index, nil +} + +func fillChartUrl(charts []*DeliveryVersionPayloadChart, chartRepoName string) error { + index, err := getIndexInfoFromChartRepo(chartRepoName) + if err != nil { + return err + } + chartMap := make(map[string]*DeliveryVersionPayloadChart) + for _, chart := range charts { + chartMap[chart.ChartName] = chart + } + + for name, entries := range index.Entries { + + chart, ok := chartMap[name] + if !ok { + continue + } + if len(entries) == 0 { + continue + } + + for _, entry := range entries { + if entry.Version == chart.ChartVersion { + if len(entry.URLs) > 0 { + chart.ChartUrl = entry.URLs[0] + } + break + } + } + } + return nil +} + +func GetChartVersion(chartName, chartRepoName string) ([]*ChartVersionResp, error) { + + index, err := getIndexInfoFromChartRepo(chartRepoName) + if err != nil { + return nil, err + } chartNameList := strings.Split(chartName, ",") chartNameSet := sets.NewString(chartNameList...) -- Gitee From a4eb0f2840071a7caee4c3860bbd8d19dc8064cd Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 23 Dec 2021 21:45:33 +0800 Subject: [PATCH 1092/2647] Workflow supports task execution that only configures and distributes (#805) * format image pull secret name Signed-off-by: panxunying * add open api extand Signed-off-by: allenshen * fix callback bug Signed-off-by: allenshen * fix registry get wrong product Signed-off-by: panxunying * remove permission for GET api/aslan/system/registry/namespaces (#798) Signed-off-by: fansi Co-authored-by: fansi * add openapi expand Signed-off-by: allenshen * add release image task Signed-off-by: liu deyi * store workflowargs Signed-off-by: liu deyi * release workflow do not update webhook comment Signed-off-by: liu deyi * return resp Signed-off-by: liu deyi * add default registry Signed-off-by: liu deyi * modify annotation Signed-off-by: liu deyi * modify annotation to english Signed-off-by: liu deyi * modify serviceName to serviceModule Signed-off-by: liu deyi * modify err output Signed-off-by: liu deyi Co-authored-by: panxunying Co-authored-by: allenshen Co-authored-by: fansi404 <89375976+fansi404@users.noreply.github.com> Co-authored-by: fansi --- .../common/repository/models/task/model.go | 7 - .../core/common/repository/models/workflow.go | 13 + .../core/common/service/notify/client.go | 115 +++++ .../service/workflow/pipeline_task.go | 1 + .../workflow/service/workflow/service_task.go | 3 +- .../service/workflow/workflow_task.go | 420 ++++++++++++------ pkg/microservice/cron/core/service/types.go | 1 + .../picket/client/aslan/workflow.go | 12 + .../picket/core/public/handler/public.go | 13 +- .../picket/core/public/handler/router.go | 1 + .../picket/core/public/service/public.go | 108 +++++ .../core/service/types/task/model.go | 8 + pkg/setting/consts.go | 2 + 13 files changed, 567 insertions(+), 137 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index d4783d2e..6ee47564 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -84,13 +84,6 @@ type Task struct { StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } -//type RenderInfo struct { -// Name string `bson:"name" json:"name"` -// Revision int64 `bson:"revision" json:"revision"` -// ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` -// Description string `bson:"description" json:"description"` -//} - func (Task) TableName() string { return "pipeline_task_v2" } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index a02950c1..71a98c9f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -128,6 +128,11 @@ type TaskArgs struct { CodeHostID int `bson:"codehost_id" json:"codehost_id"` } +type CallbackArgs struct { + CallbackUrl string `bson:"callback_url" json:"callback_url"` // url-encoded full path + CallbackVars map[string]interface{} `bson:"callback_vars" json:"callback_vars"` // custom defied vars, will be set to body of callback request +} + // WorkflowTaskArgs 多服务工作流任务参数 type WorkflowTaskArgs struct { WorkflowName string `bson:"workflow_name" json:"workflow_name"` @@ -169,6 +174,14 @@ type WorkflowTaskArgs struct { RequestMode string `json:"request_mode,omitempty"` IsParallel bool `json:"is_parallel" bson:"is_parallel"` EnvName string `json:"env_name" bson:"-"` + + Callback *CallbackArgs `bson:"callback" json:"callback"` + ReleaseImages []*ReleaseImage `bson:"release_images,omitempty" json:"release_images,omitempty"` +} + +type ReleaseImage struct { + Image string `bson:"image" json:"image"` + ServiceModule string `bson:"service_module" json:"service_module"` } type TestTaskArgs struct { diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index d08af852..e12987b2 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -17,19 +17,33 @@ limitations under the License. package notify import ( + "bytes" "encoding/json" "fmt" + "net/http" + "net/url" + + "github.com/pkg/errors" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/wechat" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) const sevendays int64 = 60 * 60 * 24 * 7 +type WorkflowTaskImage struct { + Image string `json:"image"` + ServiceName string `json:"service_name"` + RegistryRepo string `json:"registry_repo"` +} + type client struct { notifyColl *mongodb.NotifyColl pipelineColl *mongodb.PipelineColl @@ -194,9 +208,13 @@ func (c *client) ProccessNotify(notify *models.Notify) error { logger.Infof("pipeline get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdatePipelineWebhookComment(task, logger) } else if ctx.Type == config.WorkflowType { + if task.TaskCreator == setting.RequestModeOpenAPI { + return nil + } logger.Infof("workflow get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookComment(task, logger) _ = c.scmNotifyService.UpdateDiffNote(task, logger) + } else if ctx.Type == config.TestType { logger.Infof("test get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookCommentForTest(task, logger) @@ -208,6 +226,12 @@ func (c *client) ProccessNotify(notify *models.Notify) error { return fmt.Errorf("SendWechatMessage err : %v", err) } + // send callback requests + err = c.sendCallbackRequest(task) + if err != nil { + logger.Errorf("failed to send callback request for workflow: %s, taskID: %d, err: %s", task.PipelineName, task.TaskID, err) + } + for _, receiver := range receivers { subs, err := c.subscriptionColl.List(notify.Receiver) if err != nil { @@ -262,3 +286,94 @@ func (c *client) sendSubscribedNotify(notify *models.Notify) error { } return nil } + +func taskFinished(task *task.Task) bool { + status := task.Status + if status == config.StatusPassed || status == config.StatusFailed || status == config.StatusTimeout || status == config.StatusCancelled { + return true + } + return false +} + +func getImages(task *task.Task) ([]*WorkflowTaskImage, error) { + ret := make([]*WorkflowTaskImage, 0) + for _, stages := range task.Stages { + if stages.TaskType != config.TaskBuild || stages.Status != config.StatusPassed { + continue + } + for _, subTask := range stages.SubTasks { + buildInfo, err := base.ToBuildTask(subTask) + if err != nil { + log.Errorf("get buildInfo ToBuildTask failed ! err:%s", err) + return nil, err + } + + WorkflowTaskImage := &WorkflowTaskImage{ + Image: buildInfo.JobCtx.Image, + ServiceName: buildInfo.ServiceName, + } + if buildInfo.DockerBuildStatus != nil { + WorkflowTaskImage.RegistryRepo = buildInfo.DockerBuildStatus.RegistryRepo + } + ret = append(ret, WorkflowTaskImage) + } + } + return ret, nil +} + +// send callback request when workflow is finished +func (c *client) sendCallbackRequest(task *task.Task) error { + if !taskFinished(task) { + return nil + } + + if task.WorkflowArgs == nil { + return nil + } + + callback := task.WorkflowArgs.Callback + if callback == nil { + return nil + } + + callbackUrl, err := url.PathUnescape(callback.CallbackUrl) + if err != nil { + return errors.Wrapf(err, "failed to unescape callback url") + } + + responseBody := make(map[string]interface{}) + + // set task status data + responseBody["status"] = task.Status + responseBody["task_id"] = task.TaskID + responseBody["workflow_name"] = task.PipelineName + responseBody["error"] = task.Error + + // set custom kvs + responseBody["vars"] = callback.CallbackVars + + // set images + responseBody["images"], err = getImages(task) + + reqBody, err := json.Marshal(responseBody) + if err != nil { + log.Errorf("marshal json args error: %s", err) + return err + } + + req, err := http.NewRequest("POST", callbackUrl, bytes.NewBuffer(reqBody)) + req.Header.Set("Content-Type", "application/json") + + var resp *http.Response + client := &http.Client{} + resp, err = client.Do(req) + if err != nil { + return err + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("callback response code error: %d", resp.StatusCode) + } + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index f60fea43..e7411bdf 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -277,6 +277,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C scmnotify.NewService().UpdatePipelineWebhookComment(pt, log) resp := &CreateTaskResp{ + ProjectName: args.ProductName, PipelineName: args.PipelineName, TaskID: nextTaskID, } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index d55f83d7..0c83ecd8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -180,6 +180,7 @@ func ListServiceWorkflows(productName, envName, serviceName, serviceType string, } type CreateTaskResp struct { + ProjectName string `json:"project_name"` PipelineName string `json:"pipeline_name"` TaskID int64 `json:"task_id"` } @@ -284,7 +285,7 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge return nil, e.ErrCreateTask } - createTaskResps = append(createTaskResps, &CreateTaskResp{PipelineName: pipelineName, TaskID: nextTaskID}) + createTaskResps = append(createTaskResps, &CreateTaskResp{ProjectName: product.ProductName, PipelineName: pipelineName, TaskID: nextTaskID}) } return createTaskResps, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4f750692..eba9a66d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -466,14 +466,18 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, return nil, fmt.Errorf("args should not be nil") } - // RequestMode=openAPI表示外部客户调用API,部分数据需要获取并补充到args中 - if args.RequestMode == "openAPI" { + // RequestMode=openAPI means that external clients call the API, and some data needs to be obtained and added to args + if args.RequestMode == setting.RequestModeOpenAPI { log.Info("CreateWorkflowTask from openAPI") - err := AddDataToArgs(args, log) + resp, err := AddDataToArgsOrCreateReleaseImageTask(args, log) if err != nil { log.Errorf("AddDataToArgs error: %v", err) return nil, err } + // create release image task + if resp != nil { + return resp, nil + } } workflow, err := commonrepo.NewWorkflowColl().Find(args.WorkflowName) @@ -515,12 +519,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } } - nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.WorkflowTaskFmt, args.WorkflowName)) - if err != nil { - log.Errorf("Counter.GetNextSeq error: %v", err) - return nil, e.ErrGetCounter.AddDesc(err.Error()) - } - // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) if len(env.RegistryID) == 0 { @@ -535,23 +533,13 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, env.RegistryID = reg.ID.Hex() } configPayload.RegistryID = env.RegistryID - repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) - if err == nil { - configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) - for _, repo := range repos { - // if the registry is SWR, we need to modify ak/sk according to the rule - if repo.RegProvider == config.SWRProvider { - ak := fmt.Sprintf("%s@%s", repo.Region, repo.AccessKey) - sk := util.ComputeHmacSha256(repo.AccessKey, repo.SecretKey) - repo.AccessKey = ak - repo.SecretKey = sk - } - configPayload.RepoConfigs[repo.ID.Hex()] = repo - } - } - configPayload.IgnoreCache = args.IgnoreCache - configPayload.ResetCache = args.ResetCache + nextTaskID, err := generateNextTaskID(args.WorkflowName) + if err != nil { + return nil, err + } + // modify configPayload + modifyConfigPayload(configPayload, args.IgnoreCache, args.ResetCache) distributeS3StoreURL, defaultS3StoreURL, err := getDefaultAndDestS3StoreURL(workflow, log) if err != nil { @@ -785,144 +773,316 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } _ = scmnotify.NewService().UpdateWebhookComment(task, log) - resp := &CreateTaskResp{PipelineName: args.WorkflowName, TaskID: nextTaskID} + resp := &CreateTaskResp{ + ProjectName: args.ProductTmplName, + PipelineName: args.WorkflowName, + TaskID: nextTaskID, + } return resp, nil } -func AddDataToArgs(args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) error { - if args.Target == nil || len(args.Target) == 0 { - return errors.New("target cannot be empty") +func generateNextTaskID(workflowName string) (int64, error) { + nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.WorkflowTaskFmt, workflowName)) + if err != nil { + log.Errorf("Counter.GetNextSeq error: %s", err) + return 0, e.ErrGetCounter.AddDesc(err.Error()) } + return nextTaskID, nil +} - builds, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{}) - if err != nil { - log.Errorf("[Build.List] error: %v", err) - return e.ErrListBuildModule.AddErr(err) +func modifyConfigPayload(configPayload *commonmodels.ConfigPayload, ignoreCache, resetCache bool) { + repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + if err == nil { + configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) + for _, repo := range repos { + // if the registry is SWR, we need to modify ak/sk according to the rule + if repo.RegProvider == config.SWRProvider { + ak := fmt.Sprintf("%s@%s", repo.Region, repo.AccessKey) + sk := util.ComputeHmacSha256(repo.AccessKey, repo.SecretKey) + repo.AccessKey = ak + repo.SecretKey = sk + } + configPayload.RepoConfigs[repo.ID.Hex()] = repo + } + } + + configPayload.IgnoreCache = ignoreCache + configPayload.ResetCache = resetCache + +} + +// add data to workflow args or create release image task +func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) (*CreateTaskResp, error) { + if len(args.Target) == 0 && len(args.ReleaseImages) == 0 { + return nil, errors.New("target and images cannot be empty at the same time") } workflow, err := commonrepo.NewWorkflowColl().Find(args.WorkflowName) if err != nil { - log.Errorf("[Workflow.Find] error: %v", err) - return e.ErrFindWorkflow.AddErr(err) + log.Errorf("[Workflow.Find] error: %s", err) + return nil, e.ErrFindWorkflow.AddErr(err) } - - args.ProductTmplName = workflow.ProductTmplName - - // 补全target信息 - for _, target := range args.Target { - target.HasBuild = true - // openAPI模式,传入的name是服务名称 - serviceType, err := getValidServiceType(target.ServiceType) - if err != nil { - return err - } - opt := &commonrepo.ServiceFindOption{ - ServiceName: target.Name, - ProductName: workflow.ProductTmplName, - Type: serviceType, - ExcludeStatus: setting.ProductStatusDeleting} - serviceTmpl, err := commonrepo.NewServiceColl().Find(opt) + if len(args.Target) > 0 { + builds, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{}) if err != nil { - log.Errorf("[ServiceTmpl.Find] error: %v", err) - return e.ErrGetService.AddErr(err) + log.Errorf("[Build.List] error: %s", err) + return nil, e.ErrListBuildModule.AddErr(err) } + args.ProductTmplName = workflow.ProductTmplName + + // Complete target information + for _, target := range args.Target { + target.HasBuild = true + // OpenAPI mode, the name passed in is the service name + serviceType, err := getValidServiceType(target.ServiceType) + if err != nil { + return nil, err + } + opt := &commonrepo.ServiceFindOption{ + ServiceName: target.Name, + ProductName: workflow.ProductTmplName, + Type: serviceType, + ExcludeStatus: setting.ProductStatusDeleting} + serviceTmpl, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("[ServiceTmpl.Find] error: %s", err) + return nil, e.ErrGetService.AddErr(err) + } + + // Complete the deploy information in the target + deploys := make([]commonmodels.DeployEnv, 0) + for _, container := range serviceTmpl.Containers { + // If the service component in the service is built, put the service component into deploy + for _, build := range builds { + for _, moduleTarget := range build.Targets { + serviceModuleTarget := &commonmodels.ServiceModuleTarget{ + ProductName: serviceTmpl.ProductName, + ServiceName: serviceTmpl.ServiceName, + ServiceModule: container.Name, + } + if reflect.DeepEqual(moduleTarget, serviceModuleTarget) { + deployEnv := commonmodels.DeployEnv{Env: serviceTmpl.ServiceName + "/" + container.Name, Type: serviceTmpl.Type, ProductName: serviceTmpl.ProductName} + deploys = append(deploys, deployEnv) + } + } + } + } + target.Deploy = deploys - // 补全target中的deploy信息 - deploys := make([]commonmodels.DeployEnv, 0) - for _, container := range serviceTmpl.Containers { - // 如果服务中的服务组件存在构建,将该服务组件放进deploy + // Complete the build information in the target for _, build := range builds { - for _, moduleTarget := range build.Targets { + if len(build.Targets) == 0 { + continue + } + // If the service component owned by the build contains any service component of the service, the match is considered successful + match := false + for _, container := range serviceTmpl.Containers { serviceModuleTarget := &commonmodels.ServiceModuleTarget{ ProductName: serviceTmpl.ProductName, ServiceName: serviceTmpl.ServiceName, ServiceModule: container.Name, } - if reflect.DeepEqual(moduleTarget, serviceModuleTarget) { - deployEnv := commonmodels.DeployEnv{Env: serviceTmpl.ServiceName + "/" + container.Name, Type: serviceTmpl.Type, ProductName: serviceTmpl.ProductName} - deploys = append(deploys, deployEnv) + for _, buildTarget := range build.Targets { + if reflect.DeepEqual(buildTarget, serviceModuleTarget) { + target.Name = container.Name + target.ServiceName = serviceTmpl.ServiceName + match = true + break + } } } - } - } - target.Deploy = deploys - - // 补全target中的build信息 - for _, build := range builds { - if len(build.Targets) == 0 { - continue - } - // 如果该构建拥有的服务组件 包含 该服务的任一服务组件,则认为匹配成功 - match := false - for _, container := range serviceTmpl.Containers { - serviceModuleTarget := &commonmodels.ServiceModuleTarget{ - ProductName: serviceTmpl.ProductName, - ServiceName: serviceTmpl.ServiceName, - ServiceModule: container.Name, - } - for _, buildTarget := range build.Targets { - if reflect.DeepEqual(buildTarget, serviceModuleTarget) { - target.Name = container.Name - match = true - break + if !match { + continue + } + // If the service component is successfully matched, match the warehouse name from the built warehouse list and add the repo information + if target.Build == nil { + continue + } + for _, buildRepo := range build.Repos { + for _, targetRepo := range target.Build.Repos { + if targetRepo.RepoName == buildRepo.RepoName { + // openAPI only pass repoName, branch, pr + targetRepo.Source = buildRepo.Source + targetRepo.RepoOwner = buildRepo.RepoOwner + targetRepo.RemoteName = buildRepo.RemoteName + targetRepo.CodehostID = buildRepo.CodehostID + targetRepo.CheckoutPath = buildRepo.CheckoutPath + } } } } - if !match { - continue - } - // 服务组件匹配成功,则从该构建的仓库列表 匹配仓库名称,并添加repo信息 - if target.Build == nil { - continue + } + // Complete test information + if workflow.TestStage != nil && workflow.TestStage.Enabled { + tests := make([]*commonmodels.TestArgs, 0) + for _, testName := range workflow.TestStage.TestNames { + moduleTest, err := commonrepo.NewTestingColl().Find(testName, "") + if err != nil { + log.Errorf("[Testing.Find] TestModuleName:%s, error:%s", testName, err) + continue + } + test := &commonmodels.TestArgs{ + TestModuleName: testName, + Namespace: args.Namespace, + Builds: moduleTest.Repos, + } + tests = append(tests, test) } - for _, buildRepo := range build.Repos { - for _, targetRepo := range target.Build.Repos { - if targetRepo.RepoName == buildRepo.RepoName { - // openAPI仅上传了repoName、branch、pr - targetRepo.Source = buildRepo.Source - targetRepo.RepoOwner = buildRepo.RepoOwner - targetRepo.RemoteName = buildRepo.RemoteName - targetRepo.CodehostID = buildRepo.CodehostID - targetRepo.CheckoutPath = buildRepo.CheckoutPath - } + + for _, testEntity := range workflow.TestStage.Tests { + moduleTest, err := commonrepo.NewTestingColl().Find(testEntity.Name, "") + if err != nil { + log.Errorf("[Testing.Find] TestModuleName:%s, error:%s", testEntity.Name, err) + continue } + test := &commonmodels.TestArgs{ + TestModuleName: testEntity.Name, + Namespace: args.Namespace, + Builds: moduleTest.Repos, + } + tests = append(tests, test) } + args.Tests = tests } + + return nil, nil } - // 补全test信息 - if workflow.TestStage != nil && workflow.TestStage.Enabled { - tests := make([]*commonmodels.TestArgs, 0) - for _, testName := range workflow.TestStage.TestNames { - moduleTest, err := commonrepo.NewTestingColl().Find(testName, "") - if err != nil { - log.Errorf("[Testing.Find] TestModuleName:%s, error:%v", testName, err) - continue - } - test := &commonmodels.TestArgs{ - TestModuleName: testName, - Namespace: args.Namespace, - Builds: moduleTest.Repos, + + return createReleaseImageTask(workflow, args, log) +} + +func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) (*CreateTaskResp, error) { + // Get global configPayload + configPayload := commonservice.GetConfigPayload(0) + + nextTaskID, err := generateNextTaskID(workflow.Name) + if err != nil { + return nil, err + } + // modify configPayload + modifyConfigPayload(configPayload, false, false) + + // add default registry + reg, err := commonservice.FindDefaultRegistry(log) + if err != nil { + log.Errorf("can't find default candidate registry: %s", err) + return nil, e.ErrFindRegistry.AddDesc(err.Error()) + } + configPayload.Registry.Addr = reg.RegAddr + configPayload.Registry.AccessKey = reg.AccessKey + configPayload.Registry.SecretKey = reg.SecretKey + configPayload.Registry.Namespace = reg.Namespace + + distributeS3StoreURL, defaultS3StoreURL, err := getDefaultAndDestS3StoreURL(workflow, log) + if err != nil { + log.Errorf("getDefaultAndDestS3StoreUrl workflow name:[%s] err:%s", workflow.Name, err) + return nil, e.ErrCreateTask.AddErr(err) + } + + stages := make([]*commonmodels.Stage, 0) + // Generate distributed subtask + if workflow.DistributeStage != nil && workflow.DistributeStage.Enabled { + var ( + distributeTasks []map[string]interface{} + err error + subTasks = make([]map[string]interface{}, 0) + ) + + for _, imageInfo := range args.ReleaseImages { + for _, distribute := range workflow.DistributeStage.Distributes { + if distribute.Target == nil { + continue + } + if distribute.Target.ServiceModule == imageInfo.ServiceModule { + distributeTasks, err = formatDistributeSubtasks( + workflow.DistributeStage.Releases, + workflow.DistributeStage.ImageRepo, + workflow.DistributeStage.JumpBoxHost, + distributeS3StoreURL, + distribute, + ) + if err != nil { + log.Errorf("distrbiute stages to subtasks error: %s", err) + return nil, e.ErrCreateTask.AddErr(err) + } + } } - tests = append(tests, test) - } + subTasks = append(subTasks, distributeTasks...) - for _, testEntity := range workflow.TestStage.Tests { - moduleTest, err := commonrepo.NewTestingColl().Find(testEntity.Name, "") - if err != nil { - log.Errorf("[Testing.Find] TestModuleName:%s, error:%v", testEntity.Name, err) - continue + // Fill in the associated content between subtasks + task := &task.Task{ + TaskID: nextTaskID, + PipelineName: workflow.Name, + TaskCreator: setting.RequestModeOpenAPI, + ReqID: args.ReqID, + SubTasks: subTasks, + ServiceName: imageInfo.ServiceModule, + ConfigPayload: configPayload, + TaskArgs: &commonmodels.TaskArgs{PipelineName: workflow.Name, TaskCreator: setting.RequestModeOpenAPI, Deploy: commonmodels.DeployArgs{Image: imageInfo.Image}}, + ProductName: workflow.ProductTmplName, + } + sort.Sort(ByTaskKind(task.SubTasks)) + + if err := ensurePipelineTask(task, "", log); err != nil { + log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%s", task.ID, task.PipelineName, err) + if err, ok := err.(*ContainerNotFound); ok { + err := e.NewWithExtras( + e.ErrCreateTaskFailed.AddErr(err), + "container doesn't exists", map[string]interface{}{ + "productName": err.ProductName, + "envName": err.EnvName, + "serviceName": err.ServiceName, + "containerName": err.Container, + }) + return nil, err + } + if _, ok := err.(*ImageIllegal); ok { + return nil, e.ErrCreateTask.AddDesc("IMAGE is illegal") + } + return nil, e.ErrCreateTask.AddDesc(err.Error()) } - test := &commonmodels.TestArgs{ - TestModuleName: testEntity.Name, - Namespace: args.Namespace, - Builds: moduleTest.Repos, + + for _, stask := range task.SubTasks { + AddSubtaskToStage(&stages, stask, imageInfo.ServiceModule) } - tests = append(tests, test) } - args.Tests = tests } - return nil + sort.Sort(ByStageKind(stages)) + task := &task.Task{ + WorkflowArgs: args, + TaskID: nextTaskID, + Type: config.WorkflowType, + ProductName: workflow.ProductTmplName, + PipelineName: workflow.Name, + TaskCreator: setting.RequestModeOpenAPI, + ReqID: args.ReqID, + Status: config.StatusCreated, + Stages: stages, + ConfigPayload: configPayload, + StorageURI: defaultS3StoreURL, + } + + if len(task.Stages) <= 0 { + errMessage := fmt.Sprintf("%s or %s", e.PipelineSubTaskNotFoundErrMsg, "Invalid service module") + return nil, e.ErrCreateTask.AddDesc(errMessage) + } + + endpoint := fmt.Sprintf("%s-%s:9000", config.Namespace(), ClusterStorageEP) + task.StorageEndpoint = endpoint + + if err := CreateTask(task); err != nil { + log.Errorf("workflow Create task:[%v] err:%s", task, err) + return nil, e.ErrCreateTask + } + + resp := &CreateTaskResp{ + ProjectName: workflow.ProductTmplName, + PipelineName: workflow.Name, + TaskID: nextTaskID, + } + + return resp, nil } // Only supports k8s and helm two service types currently @@ -1596,7 +1756,11 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } _ = scmnotify.NewService().UpdateWebhookComment(task, log) - resp := &CreateTaskResp{PipelineName: args.WorkflowName, TaskID: nextTaskID} + resp := &CreateTaskResp{ + ProjectName: workflow.ProductTmplName, + PipelineName: args.WorkflowName, + TaskID: nextTaskID, + } return resp, nil } diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 33079be5..6b1401fc 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -292,6 +292,7 @@ type TargetArgs struct { Name string `bson:"name" json:"name"` // 服务名称 ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` Build *BuildArgs `bson:"build" json:"build"` Version string `bson:"version" json:"version"` Deploy []DeployEnv `bson:"deloy" json:"deploy"` diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 40bd045d..3dc67a48 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -80,6 +80,7 @@ func (c *Client) RestartWorkflowTask(header http.Header, qs url.Values, id strin return res.StatusCode(), nil } + func (c *Client) ListWorkflowTask(header http.Header, qs url.Values, commitId string) ([]byte, error) { url := fmt.Sprintf("/workflow/v2/status/task/info?commitId=%s", commitId) @@ -90,3 +91,14 @@ func (c *Client) ListWorkflowTask(header http.Header, qs url.Values, commitId st return res.Body(), nil } + +func (c *Client) GetDetailedWorkflowTask(header http.Header, qs url.Values, taskID, name string) ([]byte, error) { + url := fmt.Sprintf("/workflow/workflowtask/id/%s/pipelines/%s", taskID, name) + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 602ee5e3..a3136401 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -24,6 +24,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/picket/core/public/service" + "github.com/koderover/zadig/pkg/setting" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -37,7 +38,8 @@ func CreateWorkflowTask(c *gin.Context) { return } req.Namespace = req.EnvName - req.RequestMode = "openAPI" + req.RequestMode = setting.RequestModeOpenAPI + body, err := json.Marshal(req) if err != nil { ctx.Err = err @@ -129,6 +131,15 @@ func ListWorkflowTask(c *gin.Context) { ctx.Resp, ctx.Err = service.ListWorkflowTask(c.Request.Header, c.Request.URL.Query(), commitId, ctx.Logger) } +func GetWorkflowDetail(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + id := c.Param("id") + name := c.Param("name") + c.Header("Content-Type", "application/json") + ctx.Resp, ctx.Err = service.GetDetailedWorkflowTask(c.Request.Header, c.Request.URL.Query(), id, name, ctx.Logger) +} + func ListDelivery(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/picket/core/public/handler/router.go b/pkg/microservice/picket/core/public/handler/router.go index 177292de..d3dafc99 100644 --- a/pkg/microservice/picket/core/public/handler/router.go +++ b/pkg/microservice/picket/core/public/handler/router.go @@ -30,6 +30,7 @@ func (*Router) Inject(router *gin.RouterGroup) { dev.POST("/workflowTask/id/:id/pipelines/:name/cancel", CancelWorkflowTask) dev.POST("/workflowTask/id/:id/pipelines/:name/restart", RestartWorkflowTask) dev.GET("/workflowTask", ListWorkflowTask) + dev.GET("/workflowTask/id/:id/pipelines/:name", GetWorkflowDetail) dev.GET("/dc/releases", ListDelivery) dev.GET("/dc/artifact", GetArtifactInfo) } diff --git a/pkg/microservice/picket/core/public/service/public.go b/pkg/microservice/picket/core/public/service/public.go index 632d96e0..8a29ac85 100644 --- a/pkg/microservice/picket/core/public/service/public.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -24,9 +24,43 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" + "github.com/koderover/zadig/pkg/tool/log" ) +type WorkflowTaskImage struct { + Image string `json:"image"` + ServiceName string `json:"service_name"` + RegistryRepo string `json:"registry_repo"` +} + +type WorkflowTaskTarget struct { + Name string `json:"name"` + ServiceType string `json:"service_type"` + Build *WorkflowTaskTargetBuild `json:"build"` +} + +type WorkflowTaskTargetBuild struct { + Repos []*WorkflowTaskTargetRepo `json:"repos"` +} + +type WorkflowTaskTargetRepo struct { + RepoName string `json:"repo_name"` + Branch string `json:"branch"` + Pr int `json:"pr"` +} + +type WorkflowTaskDetail struct { + WorkflowName string `json:"workflow_name"` + EnvName string `json:"env_name"` + Targets []*WorkflowTaskTarget `json:"targets"` + Images []*WorkflowTaskImage `json:"images"` + Status string `json:"status"` +} + func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.SugaredLogger) ([]byte, error) { return aslan.New().CreateWorkflowTask(header, qs, body) } @@ -43,6 +77,80 @@ func ListWorkflowTask(header http.Header, qs url.Values, commitId string, _ *zap return aslan.New().ListWorkflowTask(header, qs, commitId) } +func getImages(task *task.Task) ([]*WorkflowTaskImage, error) { + ret := make([]*WorkflowTaskImage, 0) + for _, stages := range task.Stages { + if stages.TaskType != config.TaskBuild || stages.Status != config.StatusPassed { + continue + } + for _, subTask := range stages.SubTasks { + buildInfo, err := base.ToBuildTask(subTask) + if err != nil { + log.Errorf("get buildInfo ToBuildTask failed ! err:%s", err) + return nil, err + } + + WorkflowTaskImage := &WorkflowTaskImage{ + Image: buildInfo.JobCtx.Image, + ServiceName: buildInfo.ServiceName, + } + if buildInfo.DockerBuildStatus != nil { + WorkflowTaskImage.RegistryRepo = buildInfo.DockerBuildStatus.RegistryRepo + } + ret = append(ret, WorkflowTaskImage) + } + } + return ret, nil +} + +func GetDetailedWorkflowTask(header http.Header, qs url.Values, taskID, name string, _ *zap.SugaredLogger) ([]byte, error) { + body, err := aslan.New().GetDetailedWorkflowTask(header, qs, taskID, name) + if err != nil { + return nil, err + } + var workflowTask *task.Task = &task.Task{} + if err = json.Unmarshal(body, workflowTask); err != nil { + return nil, fmt.Errorf("failed to unmarshal workflowTask err:%s", err) + } + + resp := &WorkflowTaskDetail{ + WorkflowName: workflowTask.PipelineName, + EnvName: workflowTask.WorkflowArgs.EnvName, + Targets: make([]*WorkflowTaskTarget, 0), + Status: string(workflowTask.Status), + Images: make([]*WorkflowTaskImage, 0), + } + + if workflowTask.WorkflowArgs != nil { + for _, singleTarget := range workflowTask.WorkflowArgs.Target { + target := &WorkflowTaskTarget{ + Name: singleTarget.ServiceName, // return service name instead of container name + ServiceType: singleTarget.ServiceType, + Build: &WorkflowTaskTargetBuild{ + Repos: make([]*WorkflowTaskTargetRepo, 0), + }, + } + if singleTarget.Build != nil { + for _, repo := range singleTarget.Build.Repos { + target.Build.Repos = append(target.Build.Repos, &WorkflowTaskTargetRepo{ + RepoName: repo.RepoName, + Branch: repo.Branch, + Pr: repo.PR, + }) + } + } + resp.Targets = append(resp.Targets, target) + } + } + + resp.Images, err = getImages(workflowTask) + if err != nil { + return nil, err + } + + return json.Marshal(resp) +} + func ListDelivery(header http.Header, qs url.Values, productName string, workflowName string, taskId string, perPage string, page string, _ *zap.SugaredLogger) ([]byte, error) { return aslan.New().ListDelivery(header, qs, productName, workflowName, taskId, perPage, page) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index e3f77606..2b783acf 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -119,6 +119,11 @@ type DeployArgs struct { PackageFile string `json:"package_file"` } +type CallbackArgs struct { + CallbackUrl string `bson:"callback_url" json:"callback_url"` // url-encoded full path + CallbackVars map[string]interface{} `bson:"callback_vars" json:"callback_vars"` // custom defied vars, will be set to body of callback request +} + type WorkflowTaskArgs struct { WorkflowName string `bson:"workflow_name" json:"workflow_name"` ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` @@ -158,6 +163,8 @@ type WorkflowTaskArgs struct { // 请求模式,openAPI表示外部客户调用 RequestMode string `json:"request_mode,omitempty"` IsParallel bool `json:"is_parallel" bson:"is_parallel"` + + Callback *CallbackArgs `bson:"callback" json:"callback"` } type TestArgs struct { @@ -204,6 +211,7 @@ type HookPayload struct { type TargetArgs struct { Name string `bson:"name" json:"name"` ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` Build *BuildArgs `bson:"build" json:"build"` Version string `bson:"version" json:"version"` Deploy []DeployEnv `bson:"deloy" json:"deploy"` diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 9b2b7f2c..c6d67039 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -584,3 +584,5 @@ const ( ) const LocalClusterID = "0123456789abcdef12345678" + +const RequestModeOpenAPI = "openAPI" -- Gitee From 92ab9e82ff1e97760661b4b6227a07bd11652617 Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 23 Dec 2021 21:59:48 +0800 Subject: [PATCH 1093/2647] modify err message (#810) Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index eba9a66d..44fb5af6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -814,7 +814,7 @@ func modifyConfigPayload(configPayload *commonmodels.ConfigPayload, ignoreCache, // add data to workflow args or create release image task func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) (*CreateTaskResp, error) { if len(args.Target) == 0 && len(args.ReleaseImages) == 0 { - return nil, errors.New("target and images cannot be empty at the same time") + return nil, errors.New("target and release_images cannot be empty at the same time") } workflow, err := commonrepo.NewWorkflowColl().Find(args.WorkflowName) @@ -954,7 +954,7 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) (*CreateTaskResp, error) { // Get global configPayload configPayload := commonservice.GetConfigPayload(0) - + args.ProductTmplName = workflow.ProductTmplName nextTaskID, err := generateNextTaskID(workflow.Name) if err != nil { return nil, err -- Gitee From 808a069a35b8404bc3898db89a8af9fe6b7c482b Mon Sep 17 00:00:00 2001 From: leozhang Date: Fri, 24 Dec 2021 13:46:56 +0800 Subject: [PATCH 1094/2647] fix(permission):adding exception routes '/workflows' (#812) Signed-off-by: leozhang2018 --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 60466ae2..61309aca 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -74,7 +74,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET"}, - Endpoints: []string{"", "signin", "setup", "static/**", "v1/**", "mobile/**", "productpipelines/**", "favicon.ico"}, + Endpoints: []string{"", "signin", "setup", "static/**", "v1/**", "mobile/**", "workflows/**", "favicon.ico"}, }, { Methods: []string{"GET"}, -- Gitee From cafa869092e502fd22165b2f0f38a4030b1bc3f0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 24 Dec 2021 17:17:01 +0800 Subject: [PATCH 1095/2647] jenkins plugin adds registry update Signed-off-by: liu deyi --- .../common/repository/models/task/jenkins.go | 36 ++++++++++--------- .../core/workflow/service/workflow/convert.go | 7 ++++ .../core/service/taskplugin/jenkins_plugin.go | 9 +++++ .../core/service/types/task/build.go | 2 +- .../core/service/types/task/jenkins.go | 35 +++++++++--------- 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 8e3c5c59..92efaa3d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" ) @@ -41,23 +42,24 @@ type JenkinsBuildParam struct { // JenkinsBuild ... type JenkinsBuild struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - ServiceName string `bson:"service_name" json:"service_name"` - Service string `bson:"service" json:"service"` - OnSetup string `bson:"setup,omitempty" json:"setup,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - Error string `bson:"error" json:"error,omitempty"` - ResReq setting.Request `bson:"res_req" json:"res_req"` - ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` - JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` - JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - LogFile string `bson:"log_file" json:"log_file"` - Image string `bson:"image,omitempty" json:"image,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ServiceName string `bson:"service_name" json:"service_name"` + Service string `bson:"service" json:"service"` + OnSetup string `bson:"setup,omitempty" json:"setup,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + Error string `bson:"error" json:"error,omitempty"` + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` + JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` + Registries []*models.RegistryNamespace `bson:"-" json:"registries"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + LogFile string `bson:"log_file" json:"log_file"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` } // ToSubTask ... diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 9c8dc8c6..9ce3b598 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -25,6 +25,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -153,6 +154,11 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z return nil, e.ErrConvertSubTasks.AddErr(err) } + registries, err := commonservice.ListRegistryNamespaces(log) + if err != nil { + return nil, e.ErrConvertSubTasks.AddErr(err) + } + jenkinsBuildParams := make([]*task.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range jenkinsBuildOption.JenkinsBuildArgs.JenkinsBuildParams { jenkinsBuildParams = append(jenkinsBuildParams, &task.JenkinsBuildParam{ @@ -179,6 +185,7 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z Username: jenkinsIntegrations[0].Username, Password: jenkinsIntegrations[0].Password, }, + Registries: registries, } bst, err := build.ToSubTask() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 1879bb0a..0aa458bf 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -174,6 +174,15 @@ func (j *JenkinsBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, p return } job.Namespace = j.KubeNamespace + + // Set the access permissions of the private mirror warehouse integrated into KodeRover to the namespace + if err := createOrUpdateRegistrySecrets(j.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, j.Task.Registries, j.kubeClient); err != nil { + msg := fmt.Sprintf("create secret error: %v", err) + j.Log.Error(msg) + j.Task.TaskStatus = config.StatusFailed + j.Task.Error = msg + return + } if err := updater.CreateJob(job, j.kubeClient); err != nil { msg := fmt.Sprintf("create jenkins build job error: %v", err) j.Log.Error(msg) diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 06b968bc..be0200cf 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -49,7 +49,7 @@ type Build struct { ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` LogFile string `bson:"log_file" json:"log_file"` InstallCtx []*Install `bson:"-" json:"install_ctx,omitempty"` - Registries []*RegistryNamespace `bson:"-" json:"registries"` + Registries []*RegistryNamespace `bson:"-" json:"registries"` StaticCheckStatus *StaticCheckStatus `bson:"static_check_status,omitempty" json:"static_check_status,omitempty"` UTStatus *UTStatus `bson:"ut_status,omitempty" json:"ut_status,omitempty"` DockerBuildStatus *DockerBuildStatus `bson:"docker_build_status,omitempty" json:"docker_build_status,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go index 4606af19..85457418 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go +++ b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go @@ -41,23 +41,24 @@ type JenkinsBuildParam struct { // JenkinsBuild ... type JenkinsBuild struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - ServiceName string `bson:"service_name" json:"service_name"` - Service string `bson:"service" json:"service"` - OnSetup string `bson:"setup,omitempty" json:"setup,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - Error string `bson:"error" json:"error,omitempty"` - ResReq setting.Request `bson:"res_req" json:"res_req"` - ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` - JenkinsIntegration *JenkinsIntegration `bson:"jenkins_integration" json:"jenkins_integration"` - JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - LogFile string `bson:"log_file" json:"log_file"` - Image string `bson:"image,omitempty" json:"image,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ServiceName string `bson:"service_name" json:"service_name"` + Service string `bson:"service" json:"service"` + OnSetup string `bson:"setup,omitempty" json:"setup,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + Error string `bson:"error" json:"error,omitempty"` + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + JenkinsIntegration *JenkinsIntegration `bson:"jenkins_integration" json:"jenkins_integration"` + JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` + Registries []*RegistryNamespace `bson:"-" json:"registries"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + LogFile string `bson:"log_file" json:"log_file"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` } func (j *JenkinsBuild) ToSubTask() (map[string]interface{}, error) { -- Gitee From 385739700a36639fca3087ac5b69bd989bfe098c Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 24 Dec 2021 18:58:00 +0800 Subject: [PATCH 1096/2647] openapi distribution workflow adds service name field (#816) Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/workflow.go | 1 + .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 71a98c9f..8a81fdbc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -181,6 +181,7 @@ type WorkflowTaskArgs struct { type ReleaseImage struct { Image string `bson:"image" json:"image"` + ServiceName string `bson:"service_name" json:"service_name"` ServiceModule string `bson:"service_module" json:"service_module"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 44fb5af6..634bf3a8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -993,7 +993,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. if distribute.Target == nil { continue } - if distribute.Target.ServiceModule == imageInfo.ServiceModule { + if distribute.Target.ServiceModule == imageInfo.ServiceModule && distribute.Target.ServiceName == imageInfo.ServiceName { distributeTasks, err = formatDistributeSubtasks( workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, -- Gitee From 0e03330bdae5e9dba3fd2d5debb5d0920bad2415 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 24 Dec 2021 18:58:10 +0800 Subject: [PATCH 1097/2647] Feature/openapi testing data (#813) * add test report data in workflow detail api Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- .../picket/core/public/service/public.go | 120 ++++++++++++++---- 1 file changed, 93 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/picket/core/public/service/public.go b/pkg/microservice/picket/core/public/service/public.go index 8a29ac85..3a29ebaa 100644 --- a/pkg/microservice/picket/core/public/service/public.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -25,6 +25,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" @@ -43,6 +44,20 @@ type WorkflowTaskTarget struct { Build *WorkflowTaskTargetBuild `json:"build"` } +type WorkflowTaskFunctionTestReport struct { + Tests int `json:"tests"` + Successes int `json:"successes"` + Failures int `json:"failures"` + Skips int `json:"skips"` + Errors int `json:"errors"` + DetailUrl string `json:"detail_url"` +} + +type WorkflowTaskTestReport struct { + TestName string `json:"test_name"` + FunctionTestReport *WorkflowTaskFunctionTestReport `json:"function_test_report"` +} + type WorkflowTaskTargetBuild struct { Repos []*WorkflowTaskTargetRepo `json:"repos"` } @@ -54,11 +69,12 @@ type WorkflowTaskTargetRepo struct { } type WorkflowTaskDetail struct { - WorkflowName string `json:"workflow_name"` - EnvName string `json:"env_name"` - Targets []*WorkflowTaskTarget `json:"targets"` - Images []*WorkflowTaskImage `json:"images"` - Status string `json:"status"` + WorkflowName string `json:"workflow_name"` + EnvName string `json:"env_name"` + Targets []*WorkflowTaskTarget `json:"targets"` + Images []*WorkflowTaskImage `json:"images"` + TestReports []*WorkflowTaskTestReport `json:"test_reports"` + Status string `json:"status"` } func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.SugaredLogger) ([]byte, error) { @@ -77,6 +93,32 @@ func ListWorkflowTask(header http.Header, qs url.Values, commitId string, _ *zap return aslan.New().ListWorkflowTask(header, qs, commitId) } +func getTargets(task *task.Task) ([]*WorkflowTaskTarget, error) { + ret := make([]*WorkflowTaskTarget, 0) + if task.WorkflowArgs != nil { + for _, singleTarget := range task.WorkflowArgs.Target { + target := &WorkflowTaskTarget{ + Name: singleTarget.ServiceName, // return service name instead of container name + ServiceType: singleTarget.ServiceType, + Build: &WorkflowTaskTargetBuild{ + Repos: make([]*WorkflowTaskTargetRepo, 0), + }, + } + if singleTarget.Build != nil { + for _, repo := range singleTarget.Build.Repos { + target.Build.Repos = append(target.Build.Repos, &WorkflowTaskTargetRepo{ + RepoName: repo.RepoName, + Branch: repo.Branch, + Pr: repo.PR, + }) + } + } + ret = append(ret, target) + } + } + return ret, nil +} + func getImages(task *task.Task) ([]*WorkflowTaskImage, error) { ret := make([]*WorkflowTaskImage, 0) for _, stages := range task.Stages { @@ -103,6 +145,44 @@ func getImages(task *task.Task) ([]*WorkflowTaskImage, error) { return ret, nil } +func getTestReports(task *task.Task) ([]*WorkflowTaskTestReport, error) { + ret := make([]*WorkflowTaskTestReport, 0) + + for testName, testData := range task.TestReports { + tr := new(models.TestReport) + bs, err := json.Marshal(testData) + if err != nil { + return nil, err + } + err = json.Unmarshal(bs, tr) + if err != nil { + return nil, err + } + // TODO currently only return function test data + if tr.FunctionTestSuite == nil { + continue + } + + detailUrl := fmt.Sprintf("/v1/projects/detail/%s/pipelines/multi/testcase/%s/%d/test/%s?is_workflow=1&service_name=%s&test_type=function", + task.ProductName, task.PipelineName, task.TaskID, fmt.Sprintf("%s-%d-test", task.PipelineName, task.TaskID), testName) + + fts := tr.FunctionTestSuite + ret = append(ret, &WorkflowTaskTestReport{ + TestName: testName, + FunctionTestReport: &WorkflowTaskFunctionTestReport{ + Tests: fts.Tests, + Successes: fts.Successes, + Failures: fts.Failures, + Skips: fts.Skips, + Errors: fts.Errors, + DetailUrl: detailUrl, + }, + }) + } + + return ret, nil +} + func GetDetailedWorkflowTask(header http.Header, qs url.Values, taskID, name string, _ *zap.SugaredLogger) ([]byte, error) { body, err := aslan.New().GetDetailedWorkflowTask(header, qs, taskID, name) if err != nil { @@ -116,31 +196,12 @@ func GetDetailedWorkflowTask(header http.Header, qs url.Values, taskID, name str resp := &WorkflowTaskDetail{ WorkflowName: workflowTask.PipelineName, EnvName: workflowTask.WorkflowArgs.EnvName, - Targets: make([]*WorkflowTaskTarget, 0), Status: string(workflowTask.Status), - Images: make([]*WorkflowTaskImage, 0), } - if workflowTask.WorkflowArgs != nil { - for _, singleTarget := range workflowTask.WorkflowArgs.Target { - target := &WorkflowTaskTarget{ - Name: singleTarget.ServiceName, // return service name instead of container name - ServiceType: singleTarget.ServiceType, - Build: &WorkflowTaskTargetBuild{ - Repos: make([]*WorkflowTaskTargetRepo, 0), - }, - } - if singleTarget.Build != nil { - for _, repo := range singleTarget.Build.Repos { - target.Build.Repos = append(target.Build.Repos, &WorkflowTaskTargetRepo{ - RepoName: repo.RepoName, - Branch: repo.Branch, - Pr: repo.PR, - }) - } - } - resp.Targets = append(resp.Targets, target) - } + resp.Targets, err = getTargets(workflowTask) + if err != nil { + return nil, err } resp.Images, err = getImages(workflowTask) @@ -148,6 +209,11 @@ func GetDetailedWorkflowTask(header http.Header, qs url.Values, taskID, name str return nil, err } + resp.TestReports, err = getTestReports(workflowTask) + if err != nil { + return nil, err + } + return json.Marshal(resp) } -- Gitee From 878a3a8b179e607cb34875dbf0429221dd32e98f Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 24 Dec 2021 19:35:40 +0800 Subject: [PATCH 1098/2647] pm Signed-off-by: mouuii --- .../core/common/repository/mongodb/service.go | 22 +++ .../aslan/core/common/service/environment.go | 2 + .../aslan/core/common/service/service.go | 3 +- .../core/environment/handler/policy.yaml | 6 + .../aslan/core/environment/service/pm.go | 2 + .../aslan/core/service/handler/router.go | 2 +- .../aslan/core/service/handler/service.go | 10 +- .../aslan/core/service/service/pm.go | 1 - .../aslan/core/service/service/service.go | 133 ++++++++++++++---- .../core/service/scheduler/schedule_env.go | 25 +++- pkg/microservice/cron/core/service/service.go | 1 + 11 files changed, 171 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 30342b4c..3d91d245 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -253,6 +253,28 @@ func (c *ServiceColl) Create(args *models.Service) error { return err } +func (c *ServiceColl) UpdateByManu(args *models.Service) error { + // avoid panic issue + if args == nil { + return errors.New("nil ServiceTmplObject") + } + args.ProductName = strings.TrimSpace(args.ProductName) + args.ServiceName = strings.TrimSpace(args.ServiceName) + + query := bson.M{"product_name": args.ProductName, "service_name": args.ServiceName, "revision": args.Revision} + + changeMap := bson.M{ + "create_by": args.CreateBy, + "create_time": time.Now().Unix(), + "env_configs": args.EnvConfigs, + "env_statuses": args.EnvStatuses, + } + //非容器部署服务在探活过程中会更新health_check相关的参数,其他的情况只会更新服务共享属性 + change := bson.M{"$set": changeMap} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *ServiceColl) Update(args *models.Service) error { // avoid panic issue if args == nil { diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 3d4a41ea..dbba7d7c 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -86,6 +86,8 @@ type ServiceResp struct { Ready string `json:"ready"` EnvStatuses []*models.EnvStatus `json:"env_statuses,omitempty"` WorkLoadType string `json:"workLoadType"` + Revision int64 `json:"revision"` + EnvConfigs []*models.EnvConfig `json:"env_configs"` } type IngressInfo struct { diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 7715df4b..d9891d35 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -70,6 +70,8 @@ type ServiceTmplObject struct { EnvStatuses []*commonmodels.EnvStatus `json:"env_statuses,omitempty"` From string `json:"from,omitempty"` HealthChecks []*commonmodels.PmHealthCheck `json:"health_checks"` + IsManu bool `json:"is_manu"` + EnvName string `json:"env_name"` } type ServiceProductMap struct { @@ -434,7 +436,6 @@ func UpdatePmServiceTemplate(username string, args *ServiceTmplBuildObject, log return err } preService.HealthChecks = args.ServiceTmplObject.HealthChecks - preService.EnvConfigs = args.ServiceTmplObject.EnvConfigs preService.Revision = rev preService.CreateBy = username preService.BuildName = args.Build.Name diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 4447b0ff..7e6af25d 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -90,6 +90,12 @@ rules: matchAttributes: - key: "production" value: "false" + - method: PUT + endpoint: "/api/aslan/service/services" + resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments/?*/envRecycle" resourceType: "Environment" diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 27c810e3..8ac297b9 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -98,10 +98,12 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService ServiceName: service.ServiceName, Type: service.Type, EnvName: envName, + Revision: service.Revision, } serviceTmpl, err := commonservice.GetServiceTemplate( service.ServiceName, setting.PMDeployType, service.ProductName, "", service.Revision, p.log, ) + gp.EnvConfigs = serviceTmpl.EnvConfigs if err != nil { gp.Status = setting.PodFailed mutex.Lock() diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index e83c9403..7e5d15ac 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -50,7 +50,7 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.GET("/:name/:type", GetServiceTemplate) k8s.GET("/:name", GetServiceTemplateOption) k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) - k8s.PUT("", GetServiceTemplateObjectProductName, gin2.UpdateOperationLogStatus, UpdateServiceTemplate) + k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) k8s.PUT("/yaml/validator", YamlValidator) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 495551d7..2a9e64ad 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -99,17 +99,13 @@ func UpdateServiceTemplate(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := new(commonservice.ServiceTmplObject) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateServiceTemplate c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateServiceTemplate json.Unmarshal err : %v", err) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return } if args.Username != "system" { internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) } - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) args.Username = ctx.UserName ctx.Err = svcservice.UpdateServiceTemplate(args) } diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index 8d7916d0..49e1e0ae 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -82,7 +82,6 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug Revision: args.ServiceTmplObject.Revision, Visibility: args.ServiceTmplObject.Visibility, HealthChecks: args.ServiceTmplObject.HealthChecks, - EnvConfigs: args.ServiceTmplObject.EnvConfigs, CreateTime: time.Now().Unix(), CreateBy: username, BuildName: args.Build.Name, diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 1bd83639..81547c39 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -776,41 +776,124 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { envStatuses := make([]*commonmodels.EnvStatus, 0) // 去掉检查状态中不存在的环境和主机 - for _, envStatus := range args.EnvStatuses { - var existEnv, existHost bool + if !args.IsManu { + for _, envStatus := range args.EnvStatuses { + var existEnv, existHost bool + + envName := envStatus.EnvName + if _, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: envName, + }); err == nil { + existEnv = true + } - envName := envStatus.EnvName - if _, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: args.ProductName, - EnvName: envName, - }); err == nil { - existEnv = true + op := commonrepo.FindPrivateKeyOption{ + Address: envStatus.Address, + ID: envStatus.HostID, + } + if _, err := commonrepo.NewPrivateKeyColl().Find(op); err == nil { + existHost = true + } + + if existEnv && existHost { + envStatuses = append(envStatuses, envStatus) + } + } + } + + if args.IsManu { + // 是前端调用 + changeEnvStatus := []*commonmodels.EnvStatus{} + changeEnvConfigs := []*commonmodels.EnvConfig{} + if len(args.EnvConfigs) == 0 { + for _, v := range currentService.EnvConfigs { + if v.EnvName != args.EnvName { + changeEnvConfigs = append(changeEnvConfigs, v) + } + } + } else { + changeEnvConfigs = append(changeEnvConfigs, args.EnvConfigs...) + envConfigsSet := sets.String{} + for _, v := range changeEnvConfigs { + envConfigsSet.Insert(v.EnvName) + } + for _, v := range currentService.EnvConfigs { + if !envConfigsSet.Has(v.EnvName) { + changeEnvConfigs = append(changeEnvConfigs, v) + } + } } + // generate env status for this env + privateKeys := []*models.PrivateKey{} + for _, envConfig := range args.EnvConfigs { + privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) + if err != nil { + log.Errorf("ListNameByArgs ids err:%s", err) + return err + } - op := commonrepo.FindPrivateKeyOption{ - Address: envStatus.Address, - ID: envStatus.HostID, + privateKeys2, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + return err + } + privateKeys = append(privateKeys, privateKeys2...) + } + privateKeysSet := sets.NewString() + for _, v := range privateKeys { + tmp := models.EnvStatus{ + HostID: v.ID.Hex(), + EnvName: args.EnvName, + Address: v.IP, + } + if !privateKeysSet.Has(tmp.HostID) { + changeEnvStatus = append(changeEnvStatus, &tmp) + privateKeysSet.Insert(tmp.HostID) + } } - if _, err := commonrepo.NewPrivateKeyColl().Find(op); err == nil { - existHost = true + changeEnvStatusSet := sets.String{} + for _, v := range changeEnvConfigs { + changeEnvStatusSet.Insert(v.EnvName) + } + for _, v := range currentService.EnvStatuses { + if !changeEnvStatusSet.Has(v.EnvName) { + changeEnvStatus = append(changeEnvStatus, v) + } } - if existEnv && existHost { - envStatuses = append(envStatuses, envStatus) + updateArgs := &commonmodels.Service{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + Visibility: args.Visibility, + Revision: args.Revision, + Type: args.Type, + CreateBy: args.Username, + EnvConfigs: changeEnvConfigs, + EnvStatuses: changeEnvStatus, + } + return commonrepo.NewServiceColl().UpdateByManu(updateArgs) + } else { + updateArgs := &commonmodels.Service{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + Visibility: args.Visibility, + Revision: args.Revision, + Type: args.Type, + CreateBy: args.Username, + EnvConfigs: args.EnvConfigs, + EnvStatuses: envStatuses, } + return commonrepo.NewServiceColl().Update(updateArgs) } - updateArgs := &commonmodels.Service{ - ProductName: args.ProductName, - ServiceName: args.ServiceName, - Visibility: args.Visibility, - Revision: args.Revision, - Type: args.Type, - CreateBy: args.Username, - EnvConfigs: args.EnvConfigs, - EnvStatuses: envStatuses, +} + +func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { + for _, privateKey := range privateKeys { + ips.Insert(privateKey.IP) } - return commonrepo.NewServiceColl().Update(updateArgs) + return ips } func YamlValidator(args *YamlValidatorReq) []string { diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index 8116a5e9..6d2fc068 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -32,6 +32,8 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/setting" ) @@ -84,7 +86,21 @@ func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { if envConfig.EnvName != env.EnvName { continue } - for _, hostID := range envConfig.HostIDs { + privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) + if err != nil { + log.Errorf("ListNameByArgs ids err:%s", err) + continue + } + ips := sets.NewString() + ips = extractHostIPs(privateKeys, ips) + privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + continue + } + ips = extractHostIPs(privateKeys, ips) + + for _, hostID := range ips.List() { for _, healthCheck := range svc.HealthChecks { key := "service-" + serviceRevision.ServiceName + "-" + setting.PMDeployType + "-" + env.EnvName + "-" + hostID + "-" + healthCheck.Protocol + "-" + strconv.Itoa(healthCheck.Port) + "-" + healthCheck.Path @@ -111,6 +127,13 @@ func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { } } +func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { + for _, privateKey := range privateKeys { + ips.Insert(privateKey.IP) + } + return ips +} + func (c *CronClient) RunScheduledService(svc *service.Service, healthCheck *service.PmHealthCheck, address, envName, hostID string, log *zap.SugaredLogger) { log.Infof("[%s]start to Run ScheduledService...", svc.ServiceName) var ( diff --git a/pkg/microservice/cron/core/service/service.go b/pkg/microservice/cron/core/service/service.go index b146140f..00998f49 100644 --- a/pkg/microservice/cron/core/service/service.go +++ b/pkg/microservice/cron/core/service/service.go @@ -75,6 +75,7 @@ type ProductResp struct { type EnvConfig struct { EnvName string `json:"env_name"` HostIDs []string `json:"host_ids"` + Labels []string `json:"labels"` } type Service struct { -- Gitee From a4d6c5bdac191a759eecfb521a3a556d2c1f6056 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 24 Dec 2021 19:52:03 +0800 Subject: [PATCH 1099/2647] pm Signed-off-by: mouuii --- .../core/service/scheduler/schedule_env.go | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index 6d2fc068..ee4bd4e3 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -33,7 +33,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/setting" ) @@ -86,21 +85,22 @@ func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { if envConfig.EnvName != env.EnvName { continue } - privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) - if err != nil { - log.Errorf("ListNameByArgs ids err:%s", err) - continue - } - ips := sets.NewString() - ips = extractHostIPs(privateKeys, ips) - privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) - if err != nil { - log.Errorf("ListNameByArgs labels err:%s", err) - continue - } - ips = extractHostIPs(privateKeys, ips) - - for _, hostID := range ips.List() { + for _, hostID := range envConfig.HostIDs { + //privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) + //if err != nil { + // log.Errorf("ListNameByArgs ids err:%s", err) + // continue + //} + //ips := sets.NewString() + //ips = extractHostIPs(privateKeys, ips) + //privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + //if err != nil { + // log.Errorf("ListNameByArgs labels err:%s", err) + // continue + //} + //ips = extractHostIPs(privateKeys, ips) + + //for _, hostID := range ips.List() { for _, healthCheck := range svc.HealthChecks { key := "service-" + serviceRevision.ServiceName + "-" + setting.PMDeployType + "-" + env.EnvName + "-" + hostID + "-" + healthCheck.Protocol + "-" + strconv.Itoa(healthCheck.Port) + "-" + healthCheck.Path -- Gitee From 5068512e8789967554729bd240edf07c0f880853 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 24 Dec 2021 19:58:51 +0800 Subject: [PATCH 1100/2647] pm Signed-off-by: mouuii --- .../core/service/scheduler/schedule_env.go | 54 ++++++------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index ee4bd4e3..b0608f53 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -81,48 +81,28 @@ func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { } continue } - for _, envConfig := range svc.EnvConfigs { - if envConfig.EnvName != env.EnvName { + for _, envStatus := range svc.EnvStatuses { + if envStatus.EnvName != env.EnvName { continue } - for _, hostID := range envConfig.HostIDs { - //privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) - //if err != nil { - // log.Errorf("ListNameByArgs ids err:%s", err) - // continue - //} - //ips := sets.NewString() - //ips = extractHostIPs(privateKeys, ips) - //privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) - //if err != nil { - // log.Errorf("ListNameByArgs labels err:%s", err) - // continue - //} - //ips = extractHostIPs(privateKeys, ips) - - //for _, hostID := range ips.List() { - for _, healthCheck := range svc.HealthChecks { - key := "service-" + serviceRevision.ServiceName + "-" + setting.PMDeployType + "-" + - env.EnvName + "-" + hostID + "-" + healthCheck.Protocol + "-" + strconv.Itoa(healthCheck.Port) + "-" + healthCheck.Path - taskMap[key] = true - if _, ok := c.lastServiceSchedulers[key]; ok && reflect.DeepEqual(serviceRevision, c.lastServiceSchedulers[key]) { - continue - } else { - c.lastServiceSchedulers[key] = serviceRevision - } - hostInfo, _ := c.AslanCli.GetHostInfo(hostID, log) - if hostInfo == nil { - continue - } - newScheduler := gocron.NewScheduler() - BuildScheduledEnvJob(newScheduler, healthCheck).Do(c.RunScheduledService, svc, healthCheck, hostInfo.IP, env.EnvName, hostID, log) - c.Schedulers[key] = newScheduler - log.Infof("[%s] service schedulers..", key) - c.Schedulers[key].Start() + + for _, healthCheck := range svc.HealthChecks { + key := "service-" + serviceRevision.ServiceName + "-" + setting.PMDeployType + "-" + + env.EnvName + "-" + envStatus.HostID + "-" + healthCheck.Protocol + "-" + strconv.Itoa(healthCheck.Port) + "-" + healthCheck.Path + taskMap[key] = true + if _, ok := c.lastServiceSchedulers[key]; ok && reflect.DeepEqual(serviceRevision, c.lastServiceSchedulers[key]) { + continue + } else { + c.lastServiceSchedulers[key] = serviceRevision } + newScheduler := gocron.NewScheduler() + BuildScheduledEnvJob(newScheduler, healthCheck).Do(c.RunScheduledService, svc, healthCheck, envStatus.Address, env.EnvName, envStatus.HostID, log) + c.Schedulers[key] = newScheduler + log.Infof("[%s] service schedulers..", key) + c.Schedulers[key].Start() } - break } + break } } } -- Gitee From 6433eda7391bbbfd6f0074f25cea348730bf59fc Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 24 Dec 2021 20:52:40 +0800 Subject: [PATCH 1101/2647] pm Signed-off-by: mouuii --- .../aslan/core/service/service/service.go | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 81547c39..539f6fb1 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -806,25 +806,17 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { // 是前端调用 changeEnvStatus := []*commonmodels.EnvStatus{} changeEnvConfigs := []*commonmodels.EnvConfig{} - if len(args.EnvConfigs) == 0 { - for _, v := range currentService.EnvConfigs { - if v.EnvName != args.EnvName { - changeEnvConfigs = append(changeEnvConfigs, v) - } - } - } else { - changeEnvConfigs = append(changeEnvConfigs, args.EnvConfigs...) - envConfigsSet := sets.String{} - for _, v := range changeEnvConfigs { - envConfigsSet.Insert(v.EnvName) - } - for _, v := range currentService.EnvConfigs { - if !envConfigsSet.Has(v.EnvName) { - changeEnvConfigs = append(changeEnvConfigs, v) - } + + changeEnvConfigs = append(changeEnvConfigs, args.EnvConfigs...) + envConfigsSet := sets.String{} + for _, v := range changeEnvConfigs { + envConfigsSet.Insert(v.EnvName) + } + for _, v := range currentService.EnvConfigs { + if !envConfigsSet.Has(v.EnvName) { + changeEnvConfigs = append(changeEnvConfigs, v) } } - // generate env status for this env privateKeys := []*models.PrivateKey{} for _, envConfig := range args.EnvConfigs { privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) @@ -852,16 +844,17 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { privateKeysSet.Insert(tmp.HostID) } } - changeEnvStatusSet := sets.String{} - for _, v := range changeEnvConfigs { - changeEnvStatusSet.Insert(v.EnvName) - } + + // get env status + for _, v := range currentService.EnvStatuses { - if !changeEnvStatusSet.Has(v.EnvName) { + if v.EnvName != args.EnvName { changeEnvStatus = append(changeEnvStatus, v) } } + // generate env status for this env + updateArgs := &commonmodels.Service{ ProductName: args.ProductName, ServiceName: args.ServiceName, -- Gitee From 21959bd71407cf8ea2f93febaac22b22496ced34 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sat, 25 Dec 2021 20:08:17 +0800 Subject: [PATCH 1102/2647] add new contributor @xrfinbupt Signed-off-by: landylee007 --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index bc24515d..0df2d387 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -65,3 +65,4 @@ The council will make a decision for the nomination. The candidate has to have d - [jtr109](https://github.com/jtr109) - [huangzexiong](https://github.com/huangzexiong) - [ruchikabobade](https://github.com/ruchikabobade) +- [xrfinbupt](https://github.com/xrfinbupt) -- Gitee From 866d4b15cab762e10b278783b5d86dce12acbc2d Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sat, 25 Dec 2021 20:10:05 +0800 Subject: [PATCH 1103/2647] add new contributor @xrfinbupt Signed-off-by: landylee007 --- GOVERNANCE.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 50f7bb45..26dce3e5 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -65,8 +65,5 @@ The council will make a decision for the nomination. The candidate has to have d - [jtr109](https://github.com/jtr109) - [huangzexiong](https://github.com/huangzexiong) - [ruchikabobade](https://github.com/ruchikabobade) -<<<<<<< HEAD - [xrfinbupt](https://github.com/xrfinbupt) -======= -- [fengzixu](https://github.com/fengzixu) ->>>>>>> 0e03330bdae5e9dba3fd2d5debb5d0920bad2415 +- [fengzixu](https://github.com/fengzixu) \ No newline at end of file -- Gitee From b64d356c6ed4d5b6a05edb8ff0b76776dad6ffc7 Mon Sep 17 00:00:00 2001 From: landylee007 Date: Sat, 25 Dec 2021 20:10:27 +0800 Subject: [PATCH 1104/2647] add new contributor @xrfinbupt Signed-off-by: landylee007 --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 26dce3e5..63252f66 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -65,5 +65,5 @@ The council will make a decision for the nomination. The candidate has to have d - [jtr109](https://github.com/jtr109) - [huangzexiong](https://github.com/huangzexiong) - [ruchikabobade](https://github.com/ruchikabobade) -- [xrfinbupt](https://github.com/xrfinbupt) -- [fengzixu](https://github.com/fengzixu) \ No newline at end of file +- [fengzixu](https://github.com/fengzixu) +- [xrfinbupt](https://github.com/xrfinbupt) \ No newline at end of file -- Gitee From 5b3fff47845f742963b82b1e92e8360c91f312c5 Mon Sep 17 00:00:00 2001 From: dehuaduan <30834299+ddh27@users.noreply.github.com> Date: Mon, 27 Dec 2021 10:58:10 +0800 Subject: [PATCH 1105/2647] The workflow trigger configuration supports trigger-yaml As Code in the GitLab Code repository (#818) * modify workflow webhook (#799) * modify workflow webhook Signed-off-by: liu deyi * modify workflow webhook Signed-off-by: liu deyi * remove unused code Signed-off-by: liu deyi * revert code Signed-off-by: liu deyi * webhook as code Signed-off-by: ddh27 * webhook as code Signed-off-by: ddh27 * current repo Signed-off-by: ddh27 * add log Signed-off-by: ddh27 * add log Signed-off-by: ddh27 * fix trigger yaml Signed-off-by: ddh27 * fix panic Signed-off-by: ddh27 * fix panic Signed-off-by: ddh27 * fix merge source target branch Signed-off-by: ddh27 * fix merge source target branch Signed-off-by: ddh27 * fix event Signed-off-by: ddh27 * test log Signed-off-by: ddh27 * test log Signed-off-by: ddh27 * fix test overwide Signed-off-by: ddh27 * service_module Signed-off-by: ddh27 * auto_cancle Signed-off-by: ddh27 * pr source target Signed-off-by: ddh27 * test baseUri Signed-off-by: ddh27 * test log Signed-off-by: ddh27 * autocancle Signed-off-by: ddh27 * x-forwarded-proto Signed-off-by: ddh27 * checkTriggerYamlParams Signed-off-by: ddh27 * trigger_example Signed-off-by: ddh27 * fix review Signed-off-by: ddh27 Co-authored-by: dyliu Co-authored-by: ddh27 --- examples/webhook/trigger_example.yaml | 58 ++++ .../core/common/repository/models/workflow.go | 10 +- .../service/webhook/auto_cancel_task.go | 7 +- .../core/workflow/service/webhook/github.go | 12 +- .../service/webhook/github_workflow_task.go | 5 +- .../core/workflow/service/webhook/gitlab.go | 12 +- .../service/webhook/gitlab_workflow_task.go | 311 ++++++++++++++++-- .../workflow/service/webhook/trigger_yaml.go | 84 +++++ .../core/workflow/service/webhook/utils.go | 102 ++++++ 9 files changed, 553 insertions(+), 48 deletions(-) create mode 100644 examples/webhook/trigger_example.yaml create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go diff --git a/examples/webhook/trigger_example.yaml b/examples/webhook/trigger_example.yaml new file mode 100644 index 00000000..45245b98 --- /dev/null +++ b/examples/webhook/trigger_example.yaml @@ -0,0 +1,58 @@ +# 定义触发的步骤 +stages: + - build + - deploy + - test +# 构建参数 +build: + - name: service_name # 服务名 + service_module: service_module_name # 服务组件名 + variables: # zadig 系统中需要先声明出来才能指定,否则这边配置也无效 + - name: k1 + value: v1 +# 部署策略 +deploy: + strategy: single # single(更新指定环境)/dynamic(动态选择空闲环境)/base(更新基准环境,需配置 env_recycle_policy 和 base_env) + envs_name: + - dev + + # strategy 值为 base 时才需配置以下变量 + # env_recycle_policy: success # success(成功后销毁)/always(每次销毁)/never(每次保留) + # base_env: dev +# 测试参数 +test: + - name: test_name + repo: + strategy: currentRepo # default(在 zadig 平台默认配置的代码仓库信息)/currentRepo(使用当前变动的代码信息) + variables: + - name: k1 + value: v1 +# 触发条件规则 +rules: + branchs: + - 'feature*' + events: + - pull_request + - push + strategy: + # 自动取消,如果您希望只构建最新的提交,则使用这个选项会自动取消队列中的任务 + auto_cancel: true + # 指定服务与代码目录的关系 + match_folders: + match_folders_tree: + - name: service1 + service_module: service1 + file_tree: + - src/service1 + - ci + - name: service2 + service_module: service2 + file_tree: + - src/service2 + - ci +# 缓存配置 +cache_set: + # 不使用 docker 缓存 + ignore_cache: false + # 不使用工作空间缓存 + reset_cache: false \ No newline at end of file diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 8a81fdbc..3ed775c9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -62,10 +62,12 @@ type WorkflowHookCtrl struct { } type WorkflowHook struct { - AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` - CheckPatchSetChange bool `bson:"check_patch_set_change" json:"check_patch_set_change"` - MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` + AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` + CheckPatchSetChange bool `bson:"check_patch_set_change" json:"check_patch_set_change"` + MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` + IsYaml bool `bson:"is_yaml,omitempty" json:"is_yaml,omitempty"` + YamlPath string `bson:"yaml_path,omitempty" json:"yaml_path,omitempty"` } type MainHookRepo struct { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go index d083fbf7..cf1008fc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go @@ -89,7 +89,11 @@ func AutoCancelWorkflowTask(autoCancelOpt *AutoCancelOpt, task *task.Task, log * } for _, item := range workflow.HookCtl.Items { - if item.AutoCancel { + autoCancel := item.AutoCancel + if item.IsYaml && autoCancelOpt.YamlHookPath == item.YamlPath { + autoCancel = autoCancelOpt.AutoCancel + } + if autoCancel { if item.MainRepo.RepoOwner != autoCancelOpt.MainRepo.RepoOwner || item.MainRepo.RepoName != autoCancelOpt.MainRepo.RepoName || item.MainRepo.Branch != autoCancelOpt.MainRepo.Branch || @@ -122,6 +126,7 @@ func AutoCancelTestTask(autoCancelOpt *AutoCancelOpt, task *task.Task, log *zap. } for _, item := range test.HookCtl.Items { + // TODO:testing support webhook as code trigger.yaml if item.AutoCancel { if item.MainRepo.RepoOwner != autoCancelOpt.MainRepo.RepoOwner || item.MainRepo.RepoName != autoCancelOpt.MainRepo.RepoName || diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 595710dc..93326430 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -381,8 +381,7 @@ func pushEventCommitsFiles(e *github.PushEvent) []string { return files } - -func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger)error{ +func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { hookType := github.WebHookType(req) if hookType == "integration_installation" || hookType == "installation" || hookType == "ping" { return nil @@ -416,7 +415,6 @@ func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID st return nil } - func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { forwardedProto := req.Header.Get("X-Forwarded-Proto") forwardedHost := req.Header.Get("X-Forwarded-Host") @@ -489,9 +487,12 @@ type AutoCancelOpt struct { MainRepo *commonmodels.MainHookRepo WorkflowArgs *commonmodels.WorkflowTaskArgs TestArgs *commonmodels.TestTaskArgs + IsYaml bool + AutoCancel bool + YamlHookPath string } -func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.DeployEnv { +func getProductTargetMap(prod *commonmodels.Product, isYaml bool) map[string][]commonmodels.DeployEnv { resp := make(map[string][]commonmodels.DeployEnv) if prod.Source == setting.SourceFromExternal { services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) @@ -505,6 +506,9 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D } return resp } + if isYaml { + return resp + } for _, services := range prod.Services { for _, serviceObj := range services { switch serviceObj.Type { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 3cd1f374..5d408655 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -170,6 +170,7 @@ func (gmem *githubMergeEventMatcher) UpdateTaskArgs( type workflowArgsFactory struct { workflow *commonmodels.Workflow reqID string + IsYaml bool } func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, repo *types.Repository) *commonmodels.WorkflowTaskArgs { @@ -189,7 +190,7 @@ func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *comm target.Build.Repos = append(target.Build.Repos, repo) if len(target.Deploy) == 0 { if targetMap == nil { - targetMap = getProductTargetMap(product) + targetMap = getProductTargetMap(product, waf.IsYaml) } serviceModuleTarget := fmt.Sprintf("%s%s%s%s%s", args.ProductTmplName, SplitSymbol, target.ServiceName, SplitSymbol, target.Name) target.Deploy = targetMap[serviceModuleTarget] @@ -200,7 +201,7 @@ func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *comm target.Builds = append(target.Builds, repo) } - if workflow.TestStage != nil && workflow.TestStage.Enabled { + if !waf.IsYaml && workflow.TestStage != nil && workflow.TestStage.Enabled { testArgs := make([]*commonmodels.TestArgs, 0) for _, testName := range workflow.TestStage.TestNames { testArgs = append(testArgs, &commonmodels.TestArgs{ diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 7fafcdec..e7687d0a 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -29,9 +29,9 @@ import ( "github.com/xanzy/go-gitlab" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -61,10 +61,10 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log return err } - forwardedProto := req.Header.Get("X-Forwarded-Proto") - forwardedHost := req.Header.Get("X-Forwarded-Host") - baseURI := fmt.Sprintf("%s://%s", forwardedProto, forwardedHost) - + // forwardedProto := req.Header.Get("X-Forwarded-Proto") + // forwardedHost := req.Header.Get("X-Forwarded-Host") + // baseURI := fmt.Sprintf("%s://%s", forwardedProto, forwardedHost) + baseURI := config.SystemAddress() var eventPush *EventPush var pushEvent *gitlab.PushEvent var mergeEvent *gitlab.MergeEvent @@ -84,7 +84,7 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log } } - go collie.CallGitlabWebHook(forwardedProto, forwardedHost, payload, string(eventType), log) + //go collie.CallGitlabWebHook(forwardedProto, forwardedHost, payload, string(eventType), log) switch event := event.(type) { case *gitlab.PushEvent: diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index d8fdc3a4..d05d7dda 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -17,6 +17,7 @@ limitations under the License. package webhook import ( + "encoding/json" "fmt" "regexp" "strconv" @@ -27,6 +28,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/xanzy/go-gitlab" "go.uber.org/zap" + "gopkg.in/yaml.v3" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -47,10 +49,13 @@ import ( type gitlabMergeRequestDiffFunc func(event *gitlab.MergeEvent, id int) ([]string, error) type gitlabMergeEventMatcher struct { - diffFunc gitlabMergeRequestDiffFunc - log *zap.SugaredLogger - workflow *commonmodels.Workflow - event *gitlab.MergeEvent + diffFunc gitlabMergeRequestDiffFunc + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *gitlab.MergeEvent + trigger *TriggerYaml + isYaml bool + yamlServiceChanged []BuildServices } func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { @@ -60,14 +65,27 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) if !EventConfigured(hookRepo, config.HookEventPr) { return false, nil } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { - return false, nil - } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { + if gmem.isYaml { + refFlag := false + for _, ref := range gmem.trigger.Rules.Branchs { + if matched, _ := regexp.MatchString(ref, getBranchFromRef(hookRepo.Branch)); matched { + refFlag = true + break + } + } + if !refFlag { + return false, nil + } + } else { + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { return false, nil } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { + return false, nil + } + } } hookRepo.Branch = ev.ObjectAttributes.TargetBranch if ev.ObjectAttributes.State == "opened" { @@ -78,7 +96,11 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) return false, err } gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) - + if gmem.isYaml { + serviceChangeds := ServicesMatchChangesFiles(gmem.trigger.Rules.MatchFolders, hookRepo, changedFiles) + gmem.yamlServiceChanged = serviceChangeds + return len(serviceChangeds) != 0, nil + } return MatchChanges(hookRepo, changedFiles), nil } } @@ -88,9 +110,22 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) func (gmem *gitlabMergeEventMatcher) UpdateTaskArgs( product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { + if gmem.isYaml { + var targets []*commonmodels.TargetArgs + for _, target := range args.Target { + for _, bs := range gmem.yamlServiceChanged { + if target.Name == bs.ServiceModule && target.ServiceName == bs.Name { + targets = append(targets, target) + break + } + } + } + args.Target = targets + } factory := &workflowArgsFactory{ workflow: gmem.workflow, reqID: requestID, + IsYaml: gmem.isYaml, } args = factory.Update(product, args, &types.Repository{ @@ -105,7 +140,7 @@ func (gmem *gitlabMergeEventMatcher) UpdateTaskArgs( } func createGitlabEventMatcher( - event interface{}, diffSrv gitlabMergeRequestDiffFunc, workflow *commonmodels.Workflow, log *zap.SugaredLogger, + event interface{}, diffSrv gitlabMergeRequestDiffFunc, workflow *commonmodels.Workflow, isyaml bool, trigger *TriggerYaml, log *zap.SugaredLogger, ) gitEventMatcher { switch evt := event.(type) { case *gitlab.PushEvent: @@ -113,6 +148,8 @@ func createGitlabEventMatcher( workflow: workflow, log: log, event: evt, + trigger: trigger, + isYaml: isyaml, } case *gitlab.MergeEvent: return &gitlabMergeEventMatcher{ @@ -120,6 +157,8 @@ func createGitlabEventMatcher( log: log, event: evt, workflow: workflow, + trigger: trigger, + isYaml: isyaml, } } @@ -127,9 +166,12 @@ func createGitlabEventMatcher( } type gitlabPushEventMatcher struct { - log *zap.SugaredLogger - workflow *commonmodels.Workflow - event *gitlab.PushEvent + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *gitlab.PushEvent + trigger *TriggerYaml + isYaml bool + yamlServiceChanged []BuildServices } func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { @@ -138,16 +180,29 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( if !EventConfigured(hookRepo, config.HookEventPush) { return false, nil } - - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { - return false, nil - } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + if gpem.isYaml { + refFlag := false + for _, ref := range gpem.trigger.Rules.Branchs { + if matched, _ := regexp.MatchString(ref, getBranchFromRef(ev.Ref)); matched { + refFlag = true + break + } + } + if !refFlag { + return false, nil + } + } else { + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { return false, nil } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil + } + } } + hookRepo.Branch = getBranchFromRef(ev.Ref) var changedFiles []string @@ -173,7 +228,11 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( changedFiles = append(changedFiles, diff.NewPath) changedFiles = append(changedFiles, diff.OldPath) } - + if gpem.isYaml { + serviceChangeds := ServicesMatchChangesFiles(gpem.trigger.Rules.MatchFolders, hookRepo, changedFiles) + gpem.yamlServiceChanged = serviceChangeds + return len(serviceChangeds) != 0, nil + } return MatchChanges(hookRepo, changedFiles), nil } @@ -183,9 +242,22 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { + + if gpem.isYaml { + var targets []*commonmodels.TargetArgs + for _, target := range args.Target { + for _, bs := range gpem.yamlServiceChanged { + if target.Name == bs.ServiceModule && target.ServiceName == bs.Name { + targets = append(targets, target) + } + } + } + args.Target = targets + } factory := &workflowArgsFactory{ workflow: gpem.workflow, reqID: requestID, + IsYaml: gpem.isYaml, } factory.Update(product, args, &types.Repository{ @@ -198,6 +270,153 @@ func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( return args } +func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Workflow, workFlowArgs *commonmodels.WorkflowTaskArgs, item *commonmodels.WorkflowHook, branref string, prId int) error { + svcType, err := getServiceTypeByProject(workflow.ProductTmplName) + if err != nil { + return fmt.Errorf("getServiceTypeByProduct ProductTmplName:%s err:%s", workflow.ProductTmplName, err) + } + deployed := false + for _, stage := range triggerYaml.Stages { + if stage == "deploy" { + deployed = true + break + } + } + if svcType == setting.BasicFacilityCVM { + deployed = true + } + + ch, err := systemconfig.New().GetCodeHost(item.MainRepo.CodehostID) + if err != nil { + return fmt.Errorf("GetCodeHost codehostId:%d err:%s", item.MainRepo.CodehostID, err) + } + cli, err := gitlabtool.NewClient(ch.Address, ch.AccessToken) + if err != nil { + return fmt.Errorf("gitlabtool.NewClient codehostId:%d err:%s", item.MainRepo.CodehostID, err) + } + zadigTriggerYamls, err := cli.GetYAMLContents(item.MainRepo.RepoOwner, item.MainRepo.RepoName, item.YamlPath, branref, false, false) + if err != nil { + return fmt.Errorf("GetYAMLContents repoowner:%s reponame:%s ref:%s triggeryaml:%s err:%s", item.MainRepo.RepoOwner, item.MainRepo.RepoName, item.YamlPath, branref, err) + } + if len(zadigTriggerYamls) == 0 { + return fmt.Errorf("GetYAMLContents repoowner:%s reponame:%s ref:%s triggeryaml:%s ;content is empty", item.MainRepo.RepoOwner, item.MainRepo.RepoName, item.YamlPath, branref) + } + log.Infof("zadig-Trigger Yaml info:%s", zadigTriggerYamls[0]) + err = yaml.Unmarshal([]byte(zadigTriggerYamls[0]), triggerYaml) + if err != nil { + return fmt.Errorf("yaml.Unmarshal err:%s", err) + } + triggerYamlByt, err := json.Marshal(triggerYaml) + if err != nil { + return err + } + log.Infof("triggerYaml struct info:%s", string(triggerYamlByt)) + err = checkTriggerYamlParams(triggerYaml) + if err != nil { + return fmt.Errorf("checkTriggerYamlParams yamlPath:%s err:%s", item.YamlPath, err) + } + workFlowArgs.Namespace = strings.Join(triggerYaml.Deploy.Envsname, ",") + workFlowArgs.WorkflowName = workflow.Name + workFlowArgs.BaseNamespace = triggerYaml.Deploy.BaseNamespace + workFlowArgs.ProductTmplName = workflow.ProductTmplName + if triggerYaml.CacheSet != nil { + workFlowArgs.IgnoreCache = triggerYaml.CacheSet.IgnoreCache + workFlowArgs.ResetCache = triggerYaml.CacheSet.ResetCache + } + workFlowArgs.EnvRecyclePolicy = triggerYaml.Deploy.EnvRecyclePolicy + workFlowArgs.EnvUpdatePolicy = triggerYaml.Deploy.Strategy + item.MainRepo.Events = triggerYaml.Rules.Events + if triggerYaml.Rules.Strategy != nil { + item.AutoCancel = triggerYaml.Rules.Strategy.AutoCancel + } + //test + tests := make([]*commonmodels.TestArgs, 0) + for _, test := range triggerYaml.Test { + moduleTest, err := commonrepo.NewTestingColl().Find(test.Name, workflow.ProductTmplName) + if err != nil { + log.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%v", test.Name, workflow.Name, workflow.ProductTmplName, err) + return fmt.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%s", test.Name, workflow.Name, workflow.ProductTmplName, err) + } + envs := make([]*commonmodels.KeyVal, 0) + for _, env := range test.Variables { + envElem := &commonmodels.KeyVal{ + Key: env.Name, + Value: env.Value, + } + envs = append(envs, envElem) + } + testArg := &commonmodels.TestArgs{ + TestModuleName: test.Name, + Envs: envs, + } + if test.Repo.Strategy == "currentRepo" { + for _, repo := range moduleTest.Repos { + if repo.RepoName == item.MainRepo.RepoName && repo.RepoOwner == item.MainRepo.RepoOwner { + repo.Branch = item.MainRepo.Branch + repo.PR = prId + } + } + } + testArg.Builds = moduleTest.Repos + tests = append(tests, testArg) + } + workFlowArgs.Tests = tests + testsRepo, err := json.Marshal(workFlowArgs.Tests) + if err != nil { + log.Errorf("json.Marshal workflowname:%s,productTmplName:%s,error:%s", workflow.Name, workflow.ProductTmplName, err) + return fmt.Errorf("json.Marshal workflowname:%s,productTmplName:%s,error:%s", workflow.Name, workflow.ProductTmplName, err) + } + log.Infof("moduleTests workflowname:%s,productTmplName:%s,info:%s", workflow.Name, workflow.ProductTmplName, string(testsRepo)) + //target + targets := make([]*commonmodels.TargetArgs, 0) + for _, svr := range triggerYaml.Rules.MatchFolders.MatchFoldersTree { + targetElem := &commonmodels.TargetArgs{ + Name: svr.ServiceModule, + ProductName: workflow.ProductTmplName, + ServiceName: svr.Name, + ServiceType: svcType, + } + opt := &commonrepo.BuildFindOption{ + ServiceName: svr.Name, + ProductName: workflow.ProductTmplName, + Targets: []string{svr.ServiceModule}, + } + resp, err := commonrepo.NewBuildColl().Find(opt) + if err != nil { + log.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) + return fmt.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) + } + for _, repo := range resp.Repos { + if repo.RepoName == item.MainRepo.RepoName && repo.RepoOwner == item.MainRepo.RepoOwner { + repo.Branch = branref + repo.PR = prId + } + } + targetElem.Build = &commonmodels.BuildArgs{Repos: resp.Repos} + + targetElem.Deploy = []commonmodels.DeployEnv{} + if deployed { + targetElem.Deploy = append(targetElem.Deploy, commonmodels.DeployEnv{Env: svr.ServiceModule + "/" + svr.Name, Type: targetElem.ServiceType}) + } + var envs []*commonmodels.KeyVal + for _, bsvr := range triggerYaml.Build { + if bsvr.Name == svr.Name && bsvr.ServiceModule == svr.ServiceModule { + for _, env := range bsvr.Variables { + envElem := &commonmodels.KeyVal{ + Key: env.Name, + Value: env.Value, + } + envs = append(envs, envElem) + } + } + } + targetElem.Envs = envs + targets = append(targets, targetElem) + } + workFlowArgs.Target = targets + return nil +} + func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { // TODO: cache workflow // 1. find configured workflow @@ -221,12 +440,42 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, log.Debugf("find %d hooks in workflow %s", len(workflow.HookCtl.Items), workflow.Name) for _, item := range workflow.HookCtl.Items { - if item.WorkflowArgs == nil { + if item.WorkflowArgs == nil && !item.IsYaml { continue } + triggerYaml := &TriggerYaml{} + workFlowArgs := &commonmodels.WorkflowTaskArgs{} + var pushEvent *gitlab.PushEvent + var mergeEvent *gitlab.MergeEvent + prID := 0 + branref := "" + switch evt := event.(type) { + case *gitlab.PushEvent: + pushEvent = evt + branref = pushEvent.Ref + case *gitlab.MergeEvent: + mergeEvent = evt + if mergeEvent.ObjectAttributes.Source.PathWithNamespace != mergeEvent.ObjectAttributes.Target.PathWithNamespace { + branref = mergeEvent.ObjectAttributes.TargetBranch + } else { + branref = mergeEvent.ObjectAttributes.SourceBranch + } + prID = evt.ObjectAttributes.IID + item.MainRepo.Branch = getBranchFromRef(mergeEvent.ObjectAttributes.TargetBranch) + } + if item.IsYaml { + err := UpdateWorkflowTaskArgs(triggerYaml, workflow, workFlowArgs, item, branref, prID) + if err != nil { + log.Errorf("UpdateWorkflowTaskArgs %s", err) + return fmt.Errorf("UpdateWorkflowTaskArgs %s", err) + } + item.WorkflowArgs = workFlowArgs + } else { + workFlowArgs = item.WorkflowArgs + } // 2. match webhook - matcher := createGitlabEventMatcher(event, diffSrv, workflow, log) + matcher := createGitlabEventMatcher(event, diffSrv, workflow, item.IsYaml, triggerYaml, log) if matcher == nil { continue } @@ -252,11 +501,9 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } isMergeRequest := false - prID := 0 var mergeRequestID, commitID string if ev, isPr := event.(*gitlab.MergeEvent); isPr { isMergeRequest = true - prID = ev.ObjectAttributes.IID // 如果是merge request,且该webhook触发器配置了自动取消, // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 @@ -267,14 +514,16 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, CommitID: commitID, TaskType: config.WorkflowType, MainRepo: item.MainRepo, - WorkflowArgs: item.WorkflowArgs, + WorkflowArgs: workFlowArgs, + IsYaml: item.IsYaml, + AutoCancel: item.AutoCancel, + YamlHookPath: item.YamlPath, } err := AutoCancelTask(autoCancelOpt, log) if err != nil { log.Errorf("failed to auto cancel workflow task when receive event %v due to %v ", event, err) mErr = multierror.Append(mErr, err) } - if notification == nil { notification, _ = scmnotify.NewService().SendInitWebhookComment( item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, log, @@ -286,10 +535,10 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } if notification != nil { - item.WorkflowArgs.NotificationID = notification.ID.Hex() + workFlowArgs.NotificationID = notification.ID.Hex() } - args := matcher.UpdateTaskArgs(prod, item.WorkflowArgs, item.MainRepo, requestID) + args := matcher.UpdateTaskArgs(prod, workFlowArgs, item.MainRepo, requestID) args.MergeRequestID = mergeRequestID args.CommitID = commitID args.Source = setting.SourceFromGitlab diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go new file mode 100644 index 00000000..6a617b47 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -0,0 +1,84 @@ +package webhook + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" +) + +type TriggerYaml struct { + Stages []string `yaml:"stages"` + Build []*BuildServices `yaml:"build"` + Deploy *Deploy `yaml:"deploy"` + Test []*Test `yaml:"test"` + Rules *Rules `yaml:"rules"` + CacheSet *CacheSet `yaml:"cache_set"` +} + +type Build struct { + Services []*BuildServices `yaml:"services"` +} + +type BuildServices struct { + Name string `yaml:"name"` + ServiceModule string `yaml:"service_module"` + Variables []*Variables `yaml:"variables"` +} + +type Variables struct { + Name string `yaml:"name"` + Value string `yaml:"value"` +} + +/* +# 选项: +# 更新指定环境:single +# 动态选择空闲环境:all +# 更新基准环境:base,需要设置回收策略:success/always/never +*/ +type Deploy struct { + Strategy string `yaml:"strategy"` + Envsname []string `yaml:"envs_name"` + EnvRecyclePolicy string `yaml:"env_recycle_policy"` + BaseNamespace string `yaml:"base_env"` +} + +type Test struct { + Name string `yaml:"name"` + Repo *Repo `yaml:"repo"` + Variables []*Variables `yaml:"variables"` +} + +/* +# default:在 zadig 平台默认配置的代码仓库信息 +# currentRepo:使用当前变动的代码信息 +*/ +type Repo struct { + Strategy string `yaml:"strategy"` +} + +type Rules struct { + Branchs []string `yaml:"branchs"` + Events []config.HookEventType `yaml:"events"` + Strategy *StrategyRules `yaml:"strategy"` + MatchFolders *MatchFoldersElem `yaml:"match_folders"` +} + +type StrategyRules struct { + AutoCancel bool `yaml:"auto_cancel"` +} + +type MatchFoldersElem struct { + MatchSwitch string `yaml:"match_switch"` + MatchFoldersTree []*MatchFoldersTree `yaml:"match_folders_tree"` + MatchFoldersSpecific string `yaml:"match_folders_specific"` +} + +type MatchFoldersTree struct { + Name string `yaml:"name"` + ServiceModule string `yaml:"service_module"` + FileTree []string `yaml:"file_tree"` +} + +type CacheSet struct { + IgnoreCache bool `yaml:"ignore_cache"` + ResetCache bool `yaml:"reset_cache"` +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 57951755..37845daf 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -22,6 +22,7 @@ import ( "fmt" "path" "strings" + "sync" "github.com/google/go-github/v35/github" "github.com/hashicorp/go-multierror" @@ -33,6 +34,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -510,3 +512,103 @@ func EventConfigured(m *commonmodels.MainHookRepo, event config.HookEventType) b return false } + +func ServicesMatchChangesFiles(mf *MatchFoldersElem, m *commonmodels.MainHookRepo, files []string) []BuildServices { + resMactchSvr := []BuildServices{} + var wg sync.WaitGroup + var mutex sync.Mutex + for _, mftreeElem := range mf.MatchFoldersTree { + wg.Add(1) + go func(mftree *MatchFoldersTree) { + defer wg.Done() + mf := MatchFolders(mftree.FileTree) + for _, file := range files { + if matches := mf.ContainsFile(file); matches { + mutex.Lock() + resMactchSvr = append(resMactchSvr, BuildServices{Name: mftree.Name, ServiceModule: mftree.ServiceModule}) + mutex.Unlock() + break + } + } + }(mftreeElem) + } + wg.Wait() + return resMactchSvr +} + +func getServiceTypeByProject(productName string) (string, error) { + projectInfo, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + return "", err + } + projectType := setting.K8SDeployType + if projectInfo == nil || projectInfo.ProductFeature == nil { + return projectType, nil + } else if projectInfo.ProductFeature.BasicFacility == setting.BasicFacilityK8S { + if projectInfo.ProductFeature.DeployType == setting.HelmDeployType { + return setting.HelmDeployType, nil + } + return projectType, nil + } else if projectInfo.ProductFeature.BasicFacility == setting.BasicFacilityCVM { + return setting.PMDeployType, nil + } + return projectType, nil +} + +func checkTriggerYamlParams(triggerYaml *TriggerYaml) error { + //check stages + for _, stage := range triggerYaml.Stages { + if stage != "build" && stage != "deploy" && stage != "test" { + return fmt.Errorf("stages must build or deploy or test") + } + } + //check build + for _, bd := range triggerYaml.Build { + if bd.Name == "" || bd.ServiceModule == "" { + return fmt.Errorf("build.name or build.service_module is empty") + } + } + //check deploy + if triggerYaml.Deploy == nil { + return fmt.Errorf("deploy must be exist") + } + if triggerYaml.Deploy.BaseNamespace != "" { + if triggerYaml.Deploy.EnvRecyclePolicy != "success" && triggerYaml.Deploy.EnvRecyclePolicy != "always" && triggerYaml.Deploy.EnvRecyclePolicy != "never" { + return fmt.Errorf("deploy.env_recycle_policy must success/always/never") + } + } + //check test + for _, tt := range triggerYaml.Test { + if tt.Repo == nil { + return fmt.Errorf("test.repo.strategy must default/currentRepo") + } + if tt.Repo.Strategy != "default" && tt.Repo.Strategy != "currentRepo" { + return fmt.Errorf("test.repo.strategy must default/currentRepo") + } + } + //check rule + if triggerYaml.Rules == nil { + return fmt.Errorf("rules must exist") + } + if len(triggerYaml.Rules.Branchs) == 0 { + return fmt.Errorf("rules.baranch must exist") + } + for _, ev := range triggerYaml.Rules.Events { + if ev != "pull_request" && ev != "push" { + return fmt.Errorf("rules.event must be pull_request or push ") + } + } + if triggerYaml.Rules.MatchFolders == nil { + return fmt.Errorf("rules.match_folders must exist") + } + for _, mf := range triggerYaml.Rules.MatchFolders.MatchFoldersTree { + if mf.Name == "" || mf.ServiceModule == "" { + return fmt.Errorf("match_folders.match_folders_tree.name or match_folders.match_folders_tree.service_module is empty") + } + if len(mf.FileTree) == 0 { + return fmt.Errorf("match_folders.match_folders_tree.file_tree is empty") + } + } + + return nil +} -- Gitee From 3c51705fbe7d67d2486e2e716d8322f102abc455 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 27 Dec 2021 11:13:13 +0800 Subject: [PATCH 1106/2647] optimize ua logic Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate.go | 35 +++++++- pkg/cli/upgradeassistant/cmd/migrate/150.go | 6 +- pkg/cli/upgradeassistant/cmd/migrate/170.go | 8 +- pkg/cli/upgradeassistant/cmd/migrate/180.go | 6 +- .../upgradeassistant/cmd/migrate/migrate.go | 3 +- .../internal/upgradepath/dag.go | 18 +++- .../internal/upgradepath/versions.go | 90 +++---------------- version/version.go | 2 +- 8 files changed, 76 insertions(+), 92 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate.go index 9e087d21..13208bc8 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate.go @@ -19,17 +19,18 @@ package cmd import ( "context" "fmt" + "sort" "time" "github.com/spf13/cobra" "github.com/spf13/viper" + "k8s.io/apimachinery/pkg/util/sets" _ "github.com/koderover/zadig/pkg/cli/upgradeassistant/cmd/migrate" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" - "github.com/koderover/zadig/version" ) const oldestVersion = "1.3.0" @@ -38,7 +39,7 @@ func init() { rootCmd.AddCommand(migrateCmd) migrateCmd.PersistentFlags().StringP("from-version", "f", oldestVersion, "current version to migrate from") - migrateCmd.PersistentFlags().StringP("to-version", "t", version.Version, "target version to migrate to") + migrateCmd.PersistentFlags().StringP("to-version", "t", "", "target version to migrate to") _ = viper.BindPFlag("fromVersion", migrateCmd.PersistentFlags().Lookup("from-version")) _ = viper.BindPFlag("toVersion", migrateCmd.PersistentFlags().Lookup("to-version")) } @@ -67,6 +68,36 @@ func run() error { to := viper.GetString("toVersion") log.Infof("Migrating from %s to %s", from, to) + + versionSets := sets.NewString() + + for _, rh := range upgradepath.RegisteredHandlers { + versionSets.Insert(rh.FromVersion, rh.ToVersion) + } + upgradepath.VersionDatas = versionSets.List() + sort.Strings(upgradepath.VersionDatas) + + if len(from) == 0 { + from = oldestVersion + } + if len(to) == 0 { + to = upgradepath.VersionDatas[len(upgradepath.VersionDatas)-1] + } + versionSets.Insert(from, to) + upgradepath.VersionDatas = versionSets.List() + sort.Strings(upgradepath.VersionDatas) + + // add default handlers + for i := 0; i < len(upgradepath.VersionDatas); i++ { + upgradepath.AddHandler(i, i+1, upgradepath.DefaultUpgradeHandler) + upgradepath.AddHandler(i+1, i, upgradepath.DefaultRollBackHandler) + } + + // add custom handlers + for _, rh := range upgradepath.RegisteredHandlers { + upgradepath.AddHandler(upgradepath.VersionDatas.VersionIndex(rh.FromVersion), upgradepath.VersionDatas.VersionIndex(rh.ToVersion), rh.Fn) + } + err := upgradepath.UpgradeWithBestPath(from, to) if err == nil { log.Info("Migration finished") diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index a8ea77f0..c019d634 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -31,15 +31,15 @@ import ( ) func init() { - upgradepath.AddHandler(upgradepath.V140, upgradepath.V150, V140ToV150) - upgradepath.AddHandler(upgradepath.V150, upgradepath.V140, V150ToV140) + upgradepath.RegisterHandler("1.4.0", "1.5.0", V140ToV150) + upgradepath.RegisterHandler("1.5.0", "1.4.0", V150ToV140) } // V140ToV150 fill image path data for old data in product.services.containers // use preset rules as patterns: {"image": "repository", "tag": "tag"}, {"image": "image"} func V140ToV150() error { log.Info("Migrating data from 1.4.0 to 1.5.0") - + return nil products, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ ExcludeStatus: setting.ProductStatusDeleting, Source: setting.SourceFromHelm, diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index a01fbb14..290bf225 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -30,14 +30,14 @@ import ( ) func init() { - upgradepath.AddHandler(upgradepath.V160, upgradepath.V170, V160ToV170) - upgradepath.AddHandler(upgradepath.V170, upgradepath.V160, V170ToV160) + upgradepath.RegisterHandler("1.6.0", "1.7.0", V160ToV170) + upgradepath.RegisterHandler("1.7.0", "1.6.0", V170ToV160) } // V160ToV170 refreshes the secret of all webhooks func V160ToV170() error { log.Info("Migrating data from 1.6.0 to 1.7.0") - + return nil log.Info("Start to change codeHost type") err := changeCodehostType() if err != nil { @@ -62,7 +62,7 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") - + return nil err := rollbackEmailhostType() if err != nil { log.Errorf("Failed to rollback emailhost type, err: %s", err) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index f9db3df9..76330af9 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -32,15 +32,15 @@ import ( ) func init() { - upgradepath.AddHandler(upgradepath.V171, upgradepath.V180, V171ToV180) - upgradepath.AddHandler(upgradepath.V180, upgradepath.V171, V180ToV171) + upgradepath.RegisterHandler("1.7.1", "1.8.0", V171ToV180) + upgradepath.RegisterHandler("1.8.0", "1.7.1", V180ToV171) } // V171ToV180 update all the roleBinding names in this format "{uid}-{roleName}-{roleNamespace}" // Caution: this migration contains unrecoverable changes, please back up the database in advance func V171ToV180() error { log.Info("Migrating data from 1.7.1 to 1.8.0") - + return nil if err := updateAllRoleBindingNames(); err != nil { log.Errorf("Failed to update roleBinding names, err: %s", err) return err diff --git a/pkg/cli/upgradeassistant/cmd/migrate/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate/migrate.go index cac8c4ab..fb55eb3f 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/migrate.go @@ -31,7 +31,7 @@ import ( const oldServiceTemplateCounterName = "service:%s&type:%s" func init() { - upgradepath.AddHandler(upgradepath.V130, upgradepath.V131, V130ToV131) + upgradepath.RegisterHandler("1.3.0", "1.3.1", V130ToV131) // upgradepath.AddHandler(upgradepath.V131, upgradepath.V130, V131ToV130) } @@ -41,6 +41,7 @@ func init() { // 3. Change the ServiceTemplateCounterName format func V130ToV131() error { log.Info("Migrating data from 1.3.0 to 1.3.1") + return nil allServices, err := mongodb.NewServiceColl().ListMaxRevisions(nil) if err != nil { diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/dag.go b/pkg/cli/upgradeassistant/internal/upgradepath/dag.go index 3a98764c..0319ded5 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/dag.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/dag.go @@ -31,6 +31,22 @@ type upgradePath struct { var dag = dijkstra.NewGraph() var handlerMap = make(map[upgradePath]handler) +type RegisteredHandler struct { + FromVersion string + ToVersion string + Fn handler +} + +var RegisteredHandlers = make([]*RegisteredHandler, 0) + +func RegisterHandler(fromVersion, toVersion string, fn handler) { + RegisteredHandlers = append(RegisteredHandlers, &RegisteredHandler{ + FromVersion: fromVersion, + ToVersion: toVersion, + Fn: fn, + }) +} + func AddHandler(from, to int, fn handler) { if v, err := dag.GetVertex(from); err != nil || v.ID != from { dag.AddVertex(from) @@ -46,7 +62,7 @@ func AddHandler(from, to int, fn handler) { } func UpgradeWithBestPath(from, to string) error { - return upgradeWithBestPath(versionMap.From(from), versionMap.To(to)) + return upgradeWithBestPath(VersionDatas.VersionIndex(from), VersionDatas.VersionIndex(to)) } func upgradeWithBestPath(from, to int) error { diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index b165680d..5451d8e1 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -17,98 +17,34 @@ limitations under the License. package upgradepath import ( - "sort" - "github.com/blang/semver/v4" + "github.com/koderover/zadig/pkg/tool/log" ) -const ( - Latest = iota + 1 - V130 - V131 - V140 - V150 - V160 - V170 - V171 - V180 -) - -var versionMap versions = map[string]int{ - "1.3.0": V130, - "1.3.1": V131, - "1.4.0": V140, - "1.5.0": V150, - "1.6.0": V160, - "1.7.0": V170, - "1.7.1": V171, - "1.8.0": V180, -} +var VersionDatas versionList -type versions map[string]int +type versionList []string -func (v versions) From(version string) int { - f, err := semver.Make(version) - if err != nil { - return Latest - } - res, ok := v[f.FinalizeVersion()] - if !ok { - return Latest - } - - return res -} - -func (v versions) To(version string) int { +func (v versionList) VersionIndex(version string) int { t, err := semver.Make(version) if err != nil { - return Latest - } - res, ok := v[t.FinalizeVersion()] - if !ok { - return Latest + return 0 } - return res -} - -func (v versions) Max() int { - return v[v.MaxVersionString()] -} - -func (v versions) MaxVersionString() string { - max, _ := semver.Make("0.0.1") - for ver := range v { - sv, _ := semver.Make(ver) - if sv.GT(max) { - max = sv + for index, _version := range VersionDatas { + if _version == t.FinalizeVersion() { + return index } } - - return max.FinalizeVersion() -} - -func init() { - versionList := make([]string, 0, len(versionMap)) - for v := range versionMap { - versionList = append(versionList, v) - } - - sort.Strings(versionList) - - for i := 0; i < len(versionList)-1; i++ { - lowVersion := versionList[i] - highVersion := versionList[i+1] - AddHandler(versionMap[lowVersion], versionMap[highVersion], defaultUpgradeHandler) - AddHandler(versionMap[highVersion], versionMap[lowVersion], defaultRollBackHandler) - } + return 0 } -func defaultUpgradeHandler() error { +func DefaultUpgradeHandler() error { + log.Info("default upgrade handler ") return nil } -func defaultRollBackHandler() error { +func DefaultRollBackHandler() error { + log.Info("default rollback handler ") return nil } diff --git a/version/version.go b/version/version.go index 1faa9cfc..575a6345 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.7.1" + Version = "1.8.0" BuildNumber = "0" GitCommit = "" ) -- Gitee From 222b70d38100fea733436ad0bc07246d1dd25ae2 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 27 Dec 2021 11:15:01 +0800 Subject: [PATCH 1107/2647] remove test code Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 1 - pkg/cli/upgradeassistant/cmd/migrate/170.go | 2 -- pkg/cli/upgradeassistant/cmd/migrate/180.go | 1 - pkg/cli/upgradeassistant/cmd/migrate/migrate.go | 1 - 4 files changed, 5 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index c019d634..172e2adb 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -39,7 +39,6 @@ func init() { // use preset rules as patterns: {"image": "repository", "tag": "tag"}, {"image": "image"} func V140ToV150() error { log.Info("Migrating data from 1.4.0 to 1.5.0") - return nil products, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ ExcludeStatus: setting.ProductStatusDeleting, Source: setting.SourceFromHelm, diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index 290bf225..d93e54ad 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -37,7 +37,6 @@ func init() { // V160ToV170 refreshes the secret of all webhooks func V160ToV170() error { log.Info("Migrating data from 1.6.0 to 1.7.0") - return nil log.Info("Start to change codeHost type") err := changeCodehostType() if err != nil { @@ -62,7 +61,6 @@ func V160ToV170() error { func V170ToV160() error { log.Info("Rollback data from 1.7.0 to 1.6.0") - return nil err := rollbackEmailhostType() if err != nil { log.Errorf("Failed to rollback emailhost type, err: %s", err) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 76330af9..18b299c7 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -40,7 +40,6 @@ func init() { // Caution: this migration contains unrecoverable changes, please back up the database in advance func V171ToV180() error { log.Info("Migrating data from 1.7.1 to 1.8.0") - return nil if err := updateAllRoleBindingNames(); err != nil { log.Errorf("Failed to update roleBinding names, err: %s", err) return err diff --git a/pkg/cli/upgradeassistant/cmd/migrate/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate/migrate.go index fb55eb3f..6d123b4e 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/migrate.go @@ -41,7 +41,6 @@ func init() { // 3. Change the ServiceTemplateCounterName format func V130ToV131() error { log.Info("Migrating data from 1.3.0 to 1.3.1") - return nil allServices, err := mongodb.NewServiceColl().ListMaxRevisions(nil) if err != nil { -- Gitee From 62ded775256f538c4a8f9d62a86b4a4696ba3c3d Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 27 Dec 2021 11:41:07 +0800 Subject: [PATCH 1108/2647] fix bug Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate.go index 13208bc8..c139fb76 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate.go @@ -88,7 +88,7 @@ func run() error { sort.Strings(upgradepath.VersionDatas) // add default handlers - for i := 0; i < len(upgradepath.VersionDatas); i++ { + for i := 0; i < len(upgradepath.VersionDatas)-1; i++ { upgradepath.AddHandler(i, i+1, upgradepath.DefaultUpgradeHandler) upgradepath.AddHandler(i+1, i, upgradepath.DefaultRollBackHandler) } -- Gitee From 162b493c2bf478fc171d82fcc81c35e7f239e41e Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 27 Dec 2021 13:48:12 +0800 Subject: [PATCH 1109/2647] another uri Signed-off-by: mouuii --- .../core/common/repository/mongodb/service.go | 1 - .../aslan/core/common/service/service.go | 1 - .../aslan/core/service/handler/router.go | 1 + .../aslan/core/service/handler/service.go | 16 ++ .../aslan/core/service/service/service.go | 183 +++++++++--------- 5 files changed, 112 insertions(+), 90 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 3d91d245..20d0d3d9 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -269,7 +269,6 @@ func (c *ServiceColl) UpdateByManu(args *models.Service) error { "env_configs": args.EnvConfigs, "env_statuses": args.EnvStatuses, } - //非容器部署服务在探活过程中会更新health_check相关的参数,其他的情况只会更新服务共享属性 change := bson.M{"$set": changeMap} _, err := c.UpdateOne(context.TODO(), query, change) return err diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index d9891d35..fe58efef 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -70,7 +70,6 @@ type ServiceTmplObject struct { EnvStatuses []*commonmodels.EnvStatus `json:"env_statuses,omitempty"` From string `json:"from,omitempty"` HealthChecks []*commonmodels.PmHealthCheck `json:"health_checks"` - IsManu bool `json:"is_manu"` EnvName string `json:"env_name"` } diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 7e5d15ac..2312e9b3 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -51,6 +51,7 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.GET("/:name", GetServiceTemplateOption) k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) + k8s.PUT("/pm/service", gin2.UpdateOperationLogStatus, UpdateServiceTemplateForPM) k8s.PUT("/yaml/validator", YamlValidator) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 2a9e64ad..178a8959 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -110,6 +110,22 @@ func UpdateServiceTemplate(c *gin.Context) { ctx.Err = svcservice.UpdateServiceTemplate(args) } +func UpdateServiceTemplateForPM(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonservice.ServiceTmplObject) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + if args.Username != "system" { + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) + } + args.Username = ctx.UserName + ctx.Err = svcservice.UpdateServiceTemplateForPM(args) +} + type ValidatorResp struct { Message string `json:"message"` } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 539f6fb1..f34f7070 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -776,110 +776,117 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { envStatuses := make([]*commonmodels.EnvStatus, 0) // 去掉检查状态中不存在的环境和主机 - if !args.IsManu { - for _, envStatus := range args.EnvStatuses { - var existEnv, existHost bool - - envName := envStatus.EnvName - if _, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: args.ProductName, - EnvName: envName, - }); err == nil { - existEnv = true - } + for _, envStatus := range args.EnvStatuses { + var existEnv, existHost bool - op := commonrepo.FindPrivateKeyOption{ - Address: envStatus.Address, - ID: envStatus.HostID, - } - if _, err := commonrepo.NewPrivateKeyColl().Find(op); err == nil { - existHost = true - } + envName := envStatus.EnvName + if _, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: envName, + }); err == nil { + existEnv = true + } - if existEnv && existHost { - envStatuses = append(envStatuses, envStatus) - } + op := commonrepo.FindPrivateKeyOption{ + Address: envStatus.Address, + ID: envStatus.HostID, + } + if _, err := commonrepo.NewPrivateKeyColl().Find(op); err == nil { + existHost = true } + + if existEnv && existHost { + envStatuses = append(envStatuses, envStatus) + } + } + + updateArgs := &commonmodels.Service{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + Visibility: args.Visibility, + Revision: args.Revision, + Type: args.Type, + CreateBy: args.Username, + EnvConfigs: args.EnvConfigs, + EnvStatuses: envStatuses, } + return commonrepo.NewServiceColl().Update(updateArgs) - if args.IsManu { - // 是前端调用 - changeEnvStatus := []*commonmodels.EnvStatus{} - changeEnvConfigs := []*commonmodels.EnvConfig{} +} + +func UpdateServiceTemplateForPM(args *commonservice.ServiceTmplObject) error { + currentService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + Revision: args.Revision, + }) + if err != nil { + log.Errorf("Can not find service with option %+v", args) + return err + } + // 是前端调用 + changeEnvStatus := []*commonmodels.EnvStatus{} + changeEnvConfigs := []*commonmodels.EnvConfig{} - changeEnvConfigs = append(changeEnvConfigs, args.EnvConfigs...) - envConfigsSet := sets.String{} - for _, v := range changeEnvConfigs { - envConfigsSet.Insert(v.EnvName) + changeEnvConfigs = append(changeEnvConfigs, args.EnvConfigs...) + envConfigsSet := sets.String{} + for _, v := range changeEnvConfigs { + envConfigsSet.Insert(v.EnvName) + } + for _, v := range currentService.EnvConfigs { + if !envConfigsSet.Has(v.EnvName) { + changeEnvConfigs = append(changeEnvConfigs, v) } - for _, v := range currentService.EnvConfigs { - if !envConfigsSet.Has(v.EnvName) { - changeEnvConfigs = append(changeEnvConfigs, v) - } + } + privateKeys := []*models.PrivateKey{} + for _, envConfig := range args.EnvConfigs { + privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) + if err != nil { + log.Errorf("ListNameByArgs ids err:%s", err) + return err } - privateKeys := []*models.PrivateKey{} - for _, envConfig := range args.EnvConfigs { - privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) - if err != nil { - log.Errorf("ListNameByArgs ids err:%s", err) - return err - } - privateKeys2, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) - if err != nil { - log.Errorf("ListNameByArgs labels err:%s", err) - return err - } - privateKeys = append(privateKeys, privateKeys2...) - } - privateKeysSet := sets.NewString() - for _, v := range privateKeys { - tmp := models.EnvStatus{ - HostID: v.ID.Hex(), - EnvName: args.EnvName, - Address: v.IP, - } - if !privateKeysSet.Has(tmp.HostID) { - changeEnvStatus = append(changeEnvStatus, &tmp) - privateKeysSet.Insert(tmp.HostID) - } + privateKeys2, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + return err } - - // get env status - - for _, v := range currentService.EnvStatuses { - if v.EnvName != args.EnvName { - changeEnvStatus = append(changeEnvStatus, v) - } + privateKeys = append(privateKeys, privateKeys2...) + } + privateKeysSet := sets.NewString() + for _, v := range privateKeys { + tmp := models.EnvStatus{ + HostID: v.ID.Hex(), + EnvName: args.EnvName, + Address: v.IP, + } + if !privateKeysSet.Has(tmp.HostID) { + changeEnvStatus = append(changeEnvStatus, &tmp) + privateKeysSet.Insert(tmp.HostID) } + } - // generate env status for this env + // get env status - updateArgs := &commonmodels.Service{ - ProductName: args.ProductName, - ServiceName: args.ServiceName, - Visibility: args.Visibility, - Revision: args.Revision, - Type: args.Type, - CreateBy: args.Username, - EnvConfigs: changeEnvConfigs, - EnvStatuses: changeEnvStatus, - } - return commonrepo.NewServiceColl().UpdateByManu(updateArgs) - } else { - updateArgs := &commonmodels.Service{ - ProductName: args.ProductName, - ServiceName: args.ServiceName, - Visibility: args.Visibility, - Revision: args.Revision, - Type: args.Type, - CreateBy: args.Username, - EnvConfigs: args.EnvConfigs, - EnvStatuses: envStatuses, + for _, v := range currentService.EnvStatuses { + if v.EnvName != args.EnvName { + changeEnvStatus = append(changeEnvStatus, v) } - return commonrepo.NewServiceColl().Update(updateArgs) } + // generate env status for this env + + updateArgs := &commonmodels.Service{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + Visibility: args.Visibility, + Revision: args.Revision, + Type: args.Type, + CreateBy: args.Username, + EnvConfigs: changeEnvConfigs, + EnvStatuses: changeEnvStatus, + } + return commonrepo.NewServiceColl().UpdateByManu(updateArgs) } func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { -- Gitee From 93c7ae9739dd862a0111fb6b175707197cf3058d Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 27 Dec 2021 13:50:34 +0800 Subject: [PATCH 1110/2647] remove unused code Signed-off-by: mouuii --- pkg/microservice/aslan/core/service/service/service.go | 1 - .../cron/core/service/scheduler/schedule_env.go | 8 -------- 2 files changed, 9 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index f34f7070..9ad27c7f 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -811,7 +811,6 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { EnvStatuses: envStatuses, } return commonrepo.NewServiceColl().Update(updateArgs) - } func UpdateServiceTemplateForPM(args *commonservice.ServiceTmplObject) error { diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index b0608f53..203832c2 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -32,7 +32,6 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/setting" ) @@ -107,13 +106,6 @@ func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { } } -func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { - for _, privateKey := range privateKeys { - ips.Insert(privateKey.IP) - } - return ips -} - func (c *CronClient) RunScheduledService(svc *service.Service, healthCheck *service.PmHealthCheck, address, envName, hostID string, log *zap.SugaredLogger) { log.Infof("[%s]start to Run ScheduledService...", svc.ServiceName) var ( -- Gitee From a024e08681169198693c2ed222b87bbe343672d8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 27 Dec 2021 13:56:05 +0800 Subject: [PATCH 1111/2647] change url Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/service.go | 2 +- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- pkg/microservice/aslan/core/service/handler/router.go | 2 +- pkg/microservice/aslan/core/service/handler/service.go | 4 ++-- pkg/microservice/aslan/core/service/service/service.go | 5 ++--- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 20d0d3d9..64ed61c6 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -253,7 +253,7 @@ func (c *ServiceColl) Create(args *models.Service) error { return err } -func (c *ServiceColl) UpdateByManu(args *models.Service) error { +func (c *ServiceColl) UpdateByPart(args *models.Service) error { // avoid panic issue if args == nil { return errors.New("nil ServiceTmplObject") diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 7e6af25d..d96bf140 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -91,7 +91,7 @@ rules: - key: "production" value: "false" - method: PUT - endpoint: "/api/aslan/service/services" + endpoint: "/api/aslan/service/services/pm/part" resourceType: "Environment" matchAttributes: - key: "production" diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 2312e9b3..d80b7db3 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -51,7 +51,7 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.GET("/:name", GetServiceTemplateOption) k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) - k8s.PUT("/pm/service", gin2.UpdateOperationLogStatus, UpdateServiceTemplateForPM) + k8s.PUT("/pm/part", gin2.UpdateOperationLogStatus, UpdateServiceTemplateByPart) k8s.PUT("/yaml/validator", YamlValidator) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 178a8959..9c811014 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -110,7 +110,7 @@ func UpdateServiceTemplate(c *gin.Context) { ctx.Err = svcservice.UpdateServiceTemplate(args) } -func UpdateServiceTemplateForPM(c *gin.Context) { +func UpdateServiceTemplateByPart(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -123,7 +123,7 @@ func UpdateServiceTemplateForPM(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) } args.Username = ctx.UserName - ctx.Err = svcservice.UpdateServiceTemplateForPM(args) + ctx.Err = svcservice.UpdateServiceTemplateByPart(args) } type ValidatorResp struct { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 9ad27c7f..e1b3249c 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -813,7 +813,7 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { return commonrepo.NewServiceColl().Update(updateArgs) } -func UpdateServiceTemplateForPM(args *commonservice.ServiceTmplObject) error { +func UpdateServiceTemplateByPart(args *commonservice.ServiceTmplObject) error { currentService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: args.ProductName, ServiceName: args.ServiceName, @@ -874,7 +874,6 @@ func UpdateServiceTemplateForPM(args *commonservice.ServiceTmplObject) error { } // generate env status for this env - updateArgs := &commonmodels.Service{ ProductName: args.ProductName, ServiceName: args.ServiceName, @@ -885,7 +884,7 @@ func UpdateServiceTemplateForPM(args *commonservice.ServiceTmplObject) error { EnvConfigs: changeEnvConfigs, EnvStatuses: changeEnvStatus, } - return commonrepo.NewServiceColl().UpdateByManu(updateArgs) + return commonrepo.NewServiceColl().UpdateByPart(updateArgs) } func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { -- Gitee From 6c1fc7aea10c04835fdbb0e3d3ea098190252b86 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 27 Dec 2021 14:38:26 +0800 Subject: [PATCH 1112/2647] add test log Signed-off-by: ddh27 --- .../service/webhook/auto_cancel_task.go | 7 ------- .../service/webhook/gitlab_workflow_task.go | 8 ++++++-- .../workflow/service/webhook/trigger_yaml.go | 19 +++++++++++++------ 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go index cf1008fc..e0e338ad 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/auto_cancel_task.go @@ -94,13 +94,6 @@ func AutoCancelWorkflowTask(autoCancelOpt *AutoCancelOpt, task *task.Task, log * autoCancel = autoCancelOpt.AutoCancel } if autoCancel { - if item.MainRepo.RepoOwner != autoCancelOpt.MainRepo.RepoOwner || - item.MainRepo.RepoName != autoCancelOpt.MainRepo.RepoName || - item.MainRepo.Branch != autoCancelOpt.MainRepo.Branch || - item.MainRepo.Source != autoCancelOpt.MainRepo.Source { - continue - } - if err := commonservice.CancelTask(task.TaskCreator, task.PipelineName, task.TaskID, task.Type, task.ReqID, log); err != nil { log.Errorf("CancelRunningTask failed,task.TaskCreator:%s, task.PipelineName:%s, task.TaskID:%d, task.Type:%s, error: %v", task.TaskCreator, task.PipelineName, task.TaskID, task.Type, err) continue diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index d05d7dda..eee88d6c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -394,9 +394,10 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor } targetElem.Build = &commonmodels.BuildArgs{Repos: resp.Repos} - targetElem.Deploy = []commonmodels.DeployEnv{} + targetElem.Deploy = make([]commonmodels.DeployEnv, 0) if deployed { targetElem.Deploy = append(targetElem.Deploy, commonmodels.DeployEnv{Env: svr.ServiceModule + "/" + svr.Name, Type: targetElem.ServiceType}) + log.Infof("deploy env :%s,%s", svr.ServiceModule+"/"+svr.Name, targetElem.ServiceType) } var envs []*commonmodels.KeyVal for _, bsvr := range triggerYaml.Build { @@ -411,6 +412,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor } } targetElem.Envs = envs + log.Infof("targetElem:%v", targetElem) targets = append(targets, targetElem) } workFlowArgs.Target = targets @@ -474,6 +476,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } else { workFlowArgs = item.WorkflowArgs } + log.Infof("UpdateWorkflowTaskArgs:%v", workFlowArgs.Target) // 2. match webhook matcher := createGitlabEventMatcher(event, diffSrv, workflow, item.IsYaml, triggerYaml, log) if matcher == nil { @@ -490,7 +493,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, log.Debugf("event not matches %v", item.MainRepo) continue } - + log.Infof("UpdateTaskArgs:%v", item.WorkflowArgs.Target) log.Infof("event match hook %v of %s", item.MainRepo, workflow.Name) namespace := strings.Split(item.WorkflowArgs.Namespace, ",")[0] opt := &commonrepo.ProductFindOptions{Name: workflow.ProductTmplName, EnvName: namespace} @@ -539,6 +542,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } args := matcher.UpdateTaskArgs(prod, workFlowArgs, item.MainRepo, requestID) + log.Infof("UpdateTaskArgs:%v", args.Target) args.MergeRequestID = mergeRequestID args.CommitID = commitID args.Source = setting.SourceFromGitlab diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go index 6a617b47..80a3d55c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -29,10 +29,14 @@ type Variables struct { } /* -# 选项: -# 更新指定环境:single -# 动态选择空闲环境:all -# 更新基准环境:base,需要设置回收策略:success/always/never +Strategy + single: + Update to the specified environment; + dynamic: + Dynamic selection of idle environment; + base: + Create a new environment based on the baseline environment + And Need to set EnvRecyclePolicy:success/always/never */ type Deploy struct { Strategy string `yaml:"strategy"` @@ -48,8 +52,11 @@ type Test struct { } /* -# default:在 zadig 平台默认配置的代码仓库信息 -# currentRepo:使用当前变动的代码信息 +Strategy + default: + Code repository information configured by default on the zadig platform + currentRepo: + Use current changed code information */ type Repo struct { Strategy string `yaml:"strategy"` -- Gitee From abbfa4a59d1d70e4c452e47479f844b2aaad22e0 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 27 Dec 2021 15:00:30 +0800 Subject: [PATCH 1113/2647] fix deploy Signed-off-by: ddh27 --- .../service/webhook/gitlab_workflow_task.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index eee88d6c..4109c7e2 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -275,16 +275,6 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("getServiceTypeByProduct ProductTmplName:%s err:%s", workflow.ProductTmplName, err) } - deployed := false - for _, stage := range triggerYaml.Stages { - if stage == "deploy" { - deployed = true - break - } - } - if svcType == setting.BasicFacilityCVM { - deployed = true - } ch, err := systemconfig.New().GetCodeHost(item.MainRepo.CodehostID) if err != nil { @@ -315,6 +305,16 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("checkTriggerYamlParams yamlPath:%s err:%s", item.YamlPath, err) } + deployed := false + for _, stage := range triggerYaml.Stages { + if stage == "deploy" { + deployed = true + break + } + } + if svcType == setting.BasicFacilityCVM { + deployed = true + } workFlowArgs.Namespace = strings.Join(triggerYaml.Deploy.Envsname, ",") workFlowArgs.WorkflowName = workflow.Name workFlowArgs.BaseNamespace = triggerYaml.Deploy.BaseNamespace -- Gitee From 7257618065dc974919cf4ae579332c3dcb743934 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 27 Dec 2021 15:17:22 +0800 Subject: [PATCH 1114/2647] change url Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/service.go | 2 +- .../aslan/core/service/handler/router.go | 2 +- .../aslan/core/service/handler/service.go | 6 +++--- .../aslan/core/service/service/service.go | 13 ++++++------- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 64ed61c6..8cd0171a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -253,7 +253,7 @@ func (c *ServiceColl) Create(args *models.Service) error { return err } -func (c *ServiceColl) UpdateByPart(args *models.Service) error { +func (c *ServiceColl) UpdateServiceHealthCheckStatus(args *models.Service) error { // avoid panic issue if args == nil { return errors.New("nil ServiceTmplObject") diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index d80b7db3..de6a48a7 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -51,7 +51,6 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.GET("/:name", GetServiceTemplateOption) k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) - k8s.PUT("/pm/part", gin2.UpdateOperationLogStatus, UpdateServiceTemplateByPart) k8s.PUT("/yaml/validator", YamlValidator) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) @@ -78,6 +77,7 @@ func (*Router) Inject(router *gin.RouterGroup) { pm := router.Group("pm") { + pm.PUT("/part", gin2.UpdateOperationLogStatus, UpdateServiceHealthCheckStatus) pm.POST("/:productName", gin2.UpdateOperationLogStatus, CreatePMService) pm.PUT("/:productName", gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) } diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 9c811014..092f24b8 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -107,10 +107,10 @@ func UpdateServiceTemplate(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) } args.Username = ctx.UserName - ctx.Err = svcservice.UpdateServiceTemplate(args) + ctx.Err = svcservice.UpdateServiceVisibility(args) } -func UpdateServiceTemplateByPart(c *gin.Context) { +func UpdateServiceHealthCheckStatus(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -123,7 +123,7 @@ func UpdateServiceTemplateByPart(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), "", ctx.Logger) } args.Username = ctx.UserName - ctx.Err = svcservice.UpdateServiceTemplateByPart(args) + ctx.Err = svcservice.UpdateServiceHealthCheckStatus(args) } type ValidatorResp struct { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index e1b3249c..f6c86689 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -744,7 +744,7 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap return GetServiceOption(args, log) } -func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { +func UpdateServiceVisibility(args *commonservice.ServiceTmplObject) error { currentService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: args.ProductName, ServiceName: args.ServiceName, @@ -775,7 +775,7 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { } envStatuses := make([]*commonmodels.EnvStatus, 0) - // 去掉检查状态中不存在的环境和主机 + //remove the status not have env or host for _, envStatus := range args.EnvStatuses { var existEnv, existHost bool @@ -813,7 +813,7 @@ func UpdateServiceTemplate(args *commonservice.ServiceTmplObject) error { return commonrepo.NewServiceColl().Update(updateArgs) } -func UpdateServiceTemplateByPart(args *commonservice.ServiceTmplObject) error { +func UpdateServiceHealthCheckStatus(args *commonservice.ServiceTmplObject) error { currentService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: args.ProductName, ServiceName: args.ServiceName, @@ -823,7 +823,6 @@ func UpdateServiceTemplateByPart(args *commonservice.ServiceTmplObject) error { log.Errorf("Can not find service with option %+v", args) return err } - // 是前端调用 changeEnvStatus := []*commonmodels.EnvStatus{} changeEnvConfigs := []*commonmodels.EnvConfig{} @@ -845,12 +844,12 @@ func UpdateServiceTemplateByPart(args *commonservice.ServiceTmplObject) error { return err } - privateKeys2, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + privateKeysByLabels, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) if err != nil { log.Errorf("ListNameByArgs labels err:%s", err) return err } - privateKeys = append(privateKeys, privateKeys2...) + privateKeys = append(privateKeys, privateKeysByLabels...) } privateKeysSet := sets.NewString() for _, v := range privateKeys { @@ -884,7 +883,7 @@ func UpdateServiceTemplateByPart(args *commonservice.ServiceTmplObject) error { EnvConfigs: changeEnvConfigs, EnvStatuses: changeEnvStatus, } - return commonrepo.NewServiceColl().UpdateByPart(updateArgs) + return commonrepo.NewServiceColl().UpdateServiceHealthCheckStatus(updateArgs) } func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { -- Gitee From 635586d329a5241b6f13a1c551fa96b8e5c010ab Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 27 Dec 2021 15:21:19 +0800 Subject: [PATCH 1115/2647] change url Signed-off-by: mouuii --- pkg/microservice/aslan/core/service/handler/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index de6a48a7..450bae46 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -77,7 +77,7 @@ func (*Router) Inject(router *gin.RouterGroup) { pm := router.Group("pm") { - pm.PUT("/part", gin2.UpdateOperationLogStatus, UpdateServiceHealthCheckStatus) + pm.PUT("/healthCheckUpdate", gin2.UpdateOperationLogStatus, UpdateServiceHealthCheckStatus) pm.POST("/:productName", gin2.UpdateOperationLogStatus, CreatePMService) pm.PUT("/:productName", gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) } -- Gitee From 5f543b843d723fddb73c69058eb6e0bc60f16361 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 27 Dec 2021 15:50:39 +0800 Subject: [PATCH 1116/2647] fix deploy and ErrNoDocuments Signed-off-by: ddh27 --- .../service/webhook/gitlab_workflow_task.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 4109c7e2..d3c05179 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -27,6 +27,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/xanzy/go-gitlab" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "gopkg.in/yaml.v3" @@ -335,6 +336,9 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor moduleTest, err := commonrepo.NewTestingColl().Find(test.Name, workflow.ProductTmplName) if err != nil { log.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%v", test.Name, workflow.Name, workflow.ProductTmplName, err) + if err != mongo.ErrNoDocuments { + continue + } return fmt.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%s", test.Name, workflow.Name, workflow.ProductTmplName, err) } envs := make([]*commonmodels.KeyVal, 0) @@ -384,6 +388,9 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor resp, err := commonrepo.NewBuildColl().Find(opt) if err != nil { log.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) + if err != mongo.ErrNoDocuments { + continue + } return fmt.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) } for _, repo := range resp.Repos { @@ -396,8 +403,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor targetElem.Deploy = make([]commonmodels.DeployEnv, 0) if deployed { - targetElem.Deploy = append(targetElem.Deploy, commonmodels.DeployEnv{Env: svr.ServiceModule + "/" + svr.Name, Type: targetElem.ServiceType}) - log.Infof("deploy env :%s,%s", svr.ServiceModule+"/"+svr.Name, targetElem.ServiceType) + targetElem.Deploy = append(targetElem.Deploy, commonmodels.DeployEnv{Env: svr.Name + "/" + svr.ServiceModule, Type: targetElem.ServiceType}) } var envs []*commonmodels.KeyVal for _, bsvr := range triggerYaml.Build { @@ -412,7 +418,6 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor } } targetElem.Envs = envs - log.Infof("targetElem:%v", targetElem) targets = append(targets, targetElem) } workFlowArgs.Target = targets @@ -476,7 +481,6 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } else { workFlowArgs = item.WorkflowArgs } - log.Infof("UpdateWorkflowTaskArgs:%v", workFlowArgs.Target) // 2. match webhook matcher := createGitlabEventMatcher(event, diffSrv, workflow, item.IsYaml, triggerYaml, log) if matcher == nil { @@ -493,7 +497,6 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, log.Debugf("event not matches %v", item.MainRepo) continue } - log.Infof("UpdateTaskArgs:%v", item.WorkflowArgs.Target) log.Infof("event match hook %v of %s", item.MainRepo, workflow.Name) namespace := strings.Split(item.WorkflowArgs.Namespace, ",")[0] opt := &commonrepo.ProductFindOptions{Name: workflow.ProductTmplName, EnvName: namespace} @@ -542,7 +545,6 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } args := matcher.UpdateTaskArgs(prod, workFlowArgs, item.MainRepo, requestID) - log.Infof("UpdateTaskArgs:%v", args.Target) args.MergeRequestID = mergeRequestID args.CommitID = commitID args.Source = setting.SourceFromGitlab -- Gitee From 671a332118614947c047ffe5d791917b2f7b368c Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 27 Dec 2021 16:05:13 +0800 Subject: [PATCH 1117/2647] fix ErrNoDocuments Signed-off-by: ddh27 --- .../core/workflow/service/webhook/gitlab_workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index d3c05179..42db0035 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -336,7 +336,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor moduleTest, err := commonrepo.NewTestingColl().Find(test.Name, workflow.ProductTmplName) if err != nil { log.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%v", test.Name, workflow.Name, workflow.ProductTmplName, err) - if err != mongo.ErrNoDocuments { + if err == mongo.ErrNoDocuments { continue } return fmt.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%s", test.Name, workflow.Name, workflow.ProductTmplName, err) @@ -388,7 +388,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor resp, err := commonrepo.NewBuildColl().Find(opt) if err != nil { log.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) - if err != mongo.ErrNoDocuments { + if err == mongo.ErrNoDocuments { continue } return fmt.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) -- Gitee From 0f310448ac32380bccb19ddd0499a5b3a8d5cce5 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 27 Dec 2021 16:36:02 +0800 Subject: [PATCH 1118/2647] fix review Signed-off-by: ddh27 --- .../aslan/core/common/repository/mongodb/webhook.go | 4 ++++ .../core/workflow/service/webhook/gitlab_workflow_task.go | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go b/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go index 2f68b259..67bb6060 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/webhook.go @@ -151,3 +151,7 @@ func (c *WebHookColl) Update(owner, repo, address, hookID string) error { _, err := c.UpdateOne(context.TODO(), query, change) return err } + +func IsErrNoDocuments(err error) bool { + return err == mongo.ErrNoDocuments +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 42db0035..2fb2227d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -27,7 +27,6 @@ import ( "github.com/hashicorp/go-multierror" "github.com/xanzy/go-gitlab" - "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "gopkg.in/yaml.v3" @@ -336,7 +335,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor moduleTest, err := commonrepo.NewTestingColl().Find(test.Name, workflow.ProductTmplName) if err != nil { log.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%v", test.Name, workflow.Name, workflow.ProductTmplName, err) - if err == mongo.ErrNoDocuments { + if commonrepo.IsErrNoDocuments(err) { continue } return fmt.Errorf("fail to find test TestModuleName:%s, workflowname:%s,productTmplName:%s,error:%s", test.Name, workflow.Name, workflow.ProductTmplName, err) @@ -388,7 +387,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor resp, err := commonrepo.NewBuildColl().Find(opt) if err != nil { log.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) - if err == mongo.ErrNoDocuments { + if commonrepo.IsErrNoDocuments(err) { continue } return fmt.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) -- Gitee From fba89837fa79185479997d01b763f52d8de580f6 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 27 Dec 2021 16:50:29 +0800 Subject: [PATCH 1119/2647] fix open api callback not working bug (#821) Signed-off-by: allenshen --- .../aslan/core/common/service/notify/client.go | 11 +++++------ .../core/workflow/service/workflow/workflow_task.go | 1 + 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index e12987b2..6cf31fca 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -209,6 +209,11 @@ func (c *client) ProccessNotify(notify *models.Notify) error { _ = c.scmNotifyService.UpdatePipelineWebhookComment(task, logger) } else if ctx.Type == config.WorkflowType { if task.TaskCreator == setting.RequestModeOpenAPI { + // send callback requests + err = c.sendCallbackRequest(task) + if err != nil { + logger.Errorf("failed to send callback request for workflow: %s, taskID: %d, err: %s", task.PipelineName, task.TaskID, err) + } return nil } logger.Infof("workflow get task #%d notify, status: %s", ctx.TaskID, ctx.Status) @@ -226,12 +231,6 @@ func (c *client) ProccessNotify(notify *models.Notify) error { return fmt.Errorf("SendWechatMessage err : %v", err) } - // send callback requests - err = c.sendCallbackRequest(task) - if err != nil { - logger.Errorf("failed to send callback request for workflow: %s, taskID: %d, err: %s", task.PipelineName, task.TaskID, err) - } - for _, receiver := range receivers { subs, err := c.subscriptionColl.List(notify.Receiver) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 634bf3a8..54de87b1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -478,6 +478,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, if resp != nil { return resp, nil } + taskCreator = setting.RequestModeOpenAPI } workflow, err := commonrepo.NewWorkflowColl().Find(args.WorkflowName) -- Gitee From 1cdeb638ad8ddc4790d121ad9086aeb146908667 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 27 Dec 2021 17:29:33 +0800 Subject: [PATCH 1120/2647] fix open api callback not working bug (#822) * fix open api callback not working bug Signed-off-by: allenshen * update err format Signed-off-by: allenshen --- .../core/common/service/notify/client.go | 3 + .../service/workflow/workflow_task.go | 56 ++++++++++++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index 6cf31fca..62df8121 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -326,6 +326,9 @@ func (c *client) sendCallbackRequest(task *task.Task) error { return nil } + //TODO remove this log after openApi becomes stable + log.Infof("sending callback request for workflow:%s, taskID:%d", task.PipelineName, task.TaskID) + if task.WorkflowArgs == nil { return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 54de87b1..481ee0ee 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -19,6 +19,7 @@ package workflow import ( "errors" "fmt" + "net/url" "reflect" "regexp" "sort" @@ -952,6 +953,24 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, return createReleaseImageTask(workflow, args, log) } +func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { + registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + if err != nil { + return nil, fmt.Errorf("failed to query registries") + } + ret := make(map[string]*commonmodels.RegistryNamespace) + for _, singleRegistry := range registries { + fullUrl := fmt.Sprintf("%s/%s", singleRegistry.RegAddr, singleRegistry.Namespace) + fullUrl = strings.TrimSuffix(fullUrl, "/") + u, _ := url.Parse(fullUrl) + if len(u.Scheme) > 0 { + fullUrl = strings.TrimPrefix(fullUrl, fmt.Sprintf("%s://", u.Scheme)) + } + ret[fullUrl] = singleRegistry + } + return ret, nil +} + func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels.WorkflowTaskArgs, log *zap.SugaredLogger) (*CreateTaskResp, error) { // Get global configPayload configPayload := commonservice.GetConfigPayload(0) @@ -963,16 +982,37 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. // modify configPayload modifyConfigPayload(configPayload, false, false) - // add default registry - reg, err := commonservice.FindDefaultRegistry(log) + registryMap, err := buildRegistryMap() if err != nil { - log.Errorf("can't find default candidate registry: %s", err) - return nil, e.ErrFindRegistry.AddDesc(err.Error()) + log.Errorf("failed to build registry map, err: %s", err) + // use default registry + reg, err := commonservice.FindDefaultRegistry(log) + if err != nil { + log.Errorf("can't find default candidate registry, err: %s", err) + return nil, e.ErrFindRegistry.AddDesc(err.Error()) + } + configPayload.Registry.Addr = reg.RegAddr + configPayload.Registry.AccessKey = reg.AccessKey + configPayload.Registry.SecretKey = reg.SecretKey + configPayload.Registry.Namespace = reg.Namespace + } else { + // extract registry from image + for _, releaseImage := range args.ReleaseImages { + registryUrl, err := commonservice.ExtractImageRegistry(releaseImage.Image) + if err != nil { + log.Errorf("failed to extract image registry, image:%s err: %s", releaseImage.Image, err) + continue + } + registryUrl = strings.TrimSuffix(registryUrl, "/") + if reg, ok := registryMap[registryUrl]; ok { + configPayload.Registry.Addr = reg.RegAddr + configPayload.Registry.AccessKey = reg.AccessKey + configPayload.Registry.SecretKey = reg.SecretKey + configPayload.Registry.Namespace = reg.Namespace + break + } + } } - configPayload.Registry.Addr = reg.RegAddr - configPayload.Registry.AccessKey = reg.AccessKey - configPayload.Registry.SecretKey = reg.SecretKey - configPayload.Registry.Namespace = reg.Namespace distributeS3StoreURL, defaultS3StoreURL, err := getDefaultAndDestS3StoreURL(workflow, log) if err != nil { -- Gitee From cab18880f9e4268017ba13190d93e7961757c2da Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 27 Dec 2021 17:40:40 +0800 Subject: [PATCH 1121/2647] _ Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/handler/codehost.go | 6 +++--- pkg/microservice/aslan/core/code/service/branch.go | 2 +- pkg/microservice/aslan/core/code/service/repo.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index f7f552d7..baeb438c 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -63,7 +63,7 @@ func CodeHostGetNamespaceList(c *gin.Context) { } type CodeHostListProjectsArgs struct { - PerPage int `json:"perPage" form:"per_page,default:30"` + PerPage int `json:"per_page" form:"per_page,default:30"` Page int `json:"page" form:"page,default:1"` Key string `json:"key" form:"key"` } @@ -111,7 +111,7 @@ func CodeHostGetProjectsList(c *gin.Context) { } type CodeHostGetBranchListArgs struct { - PerPage int `json:"perPage" form:"per_page,default:30"` + PerPage int `json:"per_page" form:"per_page,default:30"` Page int `json:"page" form:"page,default:1"` Key string `json:"key" form:"key"` } @@ -147,9 +147,9 @@ func CodeHostGetBranchList(c *gin.Context) { chID, projectName, strings.Replace(namespace, "%2F", "/", -1), + args.Key, args.Page, args.PerPage, - args.Key, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index b5cab187..122aa475 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -30,7 +30,7 @@ import ( "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListBranches(codeHostID int, projectName, namespace string, page int, perPage int, key string, log *zap.SugaredLogger) ([]*Branch, error) { +func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*Branch, error) { ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 9a8e3466..86134965 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -77,7 +77,7 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( if info.Source == CodeHostCodeHub { projectName = info.RepoUUID } - info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), 0, 0, "", log) + info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "",0, 0, , log) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() -- Gitee From 2d43862a1e8a83fd3c6d9fcba232cd8a14bfcaf2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 27 Dec 2021 19:06:43 +0800 Subject: [PATCH 1122/2647] store registry Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/jenkins.go | 2 +- pkg/microservice/warpdrive/core/service/types/task/jenkins.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 92efaa3d..23304ebd 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -54,7 +54,7 @@ type JenkinsBuild struct { ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` - Registries []*models.RegistryNamespace `bson:"-" json:"registries"` + Registries []*models.RegistryNamespace `bson:"registries" json:"registries"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go index 85457418..29eaa7fe 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go +++ b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go @@ -53,7 +53,7 @@ type JenkinsBuild struct { ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"jenkins_integration" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` - Registries []*RegistryNamespace `bson:"-" json:"registries"` + Registries []*RegistryNamespace `bson:"registries" json:"registries"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` -- Gitee From e9648f7cf59f05255dd065b628e8c5d9d3706a79 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 27 Dec 2021 19:24:21 +0800 Subject: [PATCH 1123/2647] do not store registry Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/jenkins.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 23304ebd..92efaa3d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -54,7 +54,7 @@ type JenkinsBuild struct { ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` - Registries []*models.RegistryNamespace `bson:"registries" json:"registries"` + Registries []*models.RegistryNamespace `bson:"-" json:"registries"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` -- Gitee From 814e2da7b22400def8473e69407341c7ace28f48 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 27 Dec 2021 20:06:12 +0800 Subject: [PATCH 1124/2647] store registry Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/jenkins.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 92efaa3d..23304ebd 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -54,7 +54,7 @@ type JenkinsBuild struct { ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` - Registries []*models.RegistryNamespace `bson:"-" json:"registries"` + Registries []*models.RegistryNamespace `bson:"registries" json:"registries"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` -- Gitee From a43d0cd32be51d2722655042181ce993b76e5aa3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 27 Dec 2021 20:09:41 +0800 Subject: [PATCH 1125/2647] create secret Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/jenkins_plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 0aa458bf..945171b7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -157,7 +157,7 @@ func (j *JenkinsBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, p } j.Log.Infof("succeed to create cm for jenkins build job %s", j.JobName) - job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, []*task.RegistryNamespace{}) + job, err := buildJob(j.Type(), pipelineTask.ConfigPayload.JenkinsBuildConfig.JenkinsBuildImage, j.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, setting.MinRequest, setting.MinRequestSpec, pipelineCtx, pipelineTask, j.Task.Registries) if err != nil { msg := fmt.Sprintf("create jenkins build job context error: %v", err) j.Log.Error(msg) -- Gitee From b4275b3838f9dd3b15cd0c7f2bc85a7785d0c0e3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 28 Dec 2021 11:47:37 +0800 Subject: [PATCH 1126/2647] field add omitempty attribute Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/jenkins.go | 8 ++++---- .../warpdrive/core/service/types/task/jenkins.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 23304ebd..35e945ef 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -48,15 +48,15 @@ type JenkinsBuild struct { ServiceName string `bson:"service_name" json:"service_name"` Service string `bson:"service" json:"service"` OnSetup string `bson:"setup,omitempty" json:"setup,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - Error string `bson:"error" json:"error,omitempty"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` ResReq setting.Request `bson:"res_req" json:"res_req"` ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"-" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"-" json:"jenkins_build_args"` Registries []*models.RegistryNamespace `bson:"registries" json:"registries"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` Image string `bson:"image,omitempty" json:"image,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go index 29eaa7fe..fb4e00c1 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/jenkins.go +++ b/pkg/microservice/warpdrive/core/service/types/task/jenkins.go @@ -47,15 +47,15 @@ type JenkinsBuild struct { ServiceName string `bson:"service_name" json:"service_name"` Service string `bson:"service" json:"service"` OnSetup string `bson:"setup,omitempty" json:"setup,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - Error string `bson:"error" json:"error,omitempty"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` ResReq setting.Request `bson:"res_req" json:"res_req"` ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` JenkinsIntegration *JenkinsIntegration `bson:"jenkins_integration" json:"jenkins_integration"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` Registries []*RegistryNamespace `bson:"registries" json:"registries"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` LogFile string `bson:"log_file" json:"log_file"` Image string `bson:"image,omitempty" json:"image,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` -- Gitee From f81694ac68cfa26034af52fe0dbfe2116a685e2f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 28 Dec 2021 14:05:54 +0800 Subject: [PATCH 1127/2647] review Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 2 +- pkg/microservice/aslan/core/service/service/service.go | 9 +++------ .../cron/core/service/scheduler/schedule_env.go | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index d96bf140..a1373616 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -91,7 +91,7 @@ rules: - key: "production" value: "false" - method: PUT - endpoint: "/api/aslan/service/services/pm/part" + endpoint: "/api/aslan/service/services/pm/healthCheckUpdate" resourceType: "Environment" matchAttributes: - key: "production" diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index f6c86689..3476a0ff 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -751,7 +751,7 @@ func UpdateServiceVisibility(args *commonservice.ServiceTmplObject) error { Revision: args.Revision, }) if err != nil { - log.Errorf("Can not find service with option %+v", args) + log.Errorf("Can not find service with option %+v. Error: %s", args, err) return err } @@ -775,7 +775,7 @@ func UpdateServiceVisibility(args *commonservice.ServiceTmplObject) error { } envStatuses := make([]*commonmodels.EnvStatus, 0) - //remove the status not have env or host + // Remove environments and hosts that do not exist in the check status for _, envStatus := range args.EnvStatuses { var existEnv, existHost bool @@ -820,7 +820,7 @@ func UpdateServiceHealthCheckStatus(args *commonservice.ServiceTmplObject) error Revision: args.Revision, }) if err != nil { - log.Errorf("Can not find service with option %+v", args) + log.Errorf("Can not find service with option %+v. Error: %s", args, err) return err } changeEnvStatus := []*commonmodels.EnvStatus{} @@ -863,15 +863,12 @@ func UpdateServiceHealthCheckStatus(args *commonservice.ServiceTmplObject) error privateKeysSet.Insert(tmp.HostID) } } - // get env status - for _, v := range currentService.EnvStatuses { if v.EnvName != args.EnvName { changeEnvStatus = append(changeEnvStatus, v) } } - // generate env status for this env updateArgs := &commonmodels.Service{ ProductName: args.ProductName, diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index 203832c2..92f36ceb 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -91,9 +91,9 @@ func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { taskMap[key] = true if _, ok := c.lastServiceSchedulers[key]; ok && reflect.DeepEqual(serviceRevision, c.lastServiceSchedulers[key]) { continue - } else { - c.lastServiceSchedulers[key] = serviceRevision } + c.lastServiceSchedulers[key] = serviceRevision + newScheduler := gocron.NewScheduler() BuildScheduledEnvJob(newScheduler, healthCheck).Do(c.RunScheduledService, svc, healthCheck, envStatus.Address, env.EnvName, envStatus.HostID, log) c.Schedulers[key] = newScheduler -- Gitee From 13bdcbc40e0de5919a7d3c6e56a918d2980eeea3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 28 Dec 2021 14:07:26 +0800 Subject: [PATCH 1128/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/handler/codehost.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index baeb438c..ba5f0741 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -63,7 +63,7 @@ func CodeHostGetNamespaceList(c *gin.Context) { } type CodeHostListProjectsArgs struct { - PerPage int `json:"per_page" form:"per_page,default:30"` + PerPage int `json:"per_page" form:"per_page,default:30"` Page int `json:"page" form:"page,default:1"` Key string `json:"key" form:"key"` } @@ -111,7 +111,7 @@ func CodeHostGetProjectsList(c *gin.Context) { } type CodeHostGetBranchListArgs struct { - PerPage int `json:"per_page" form:"per_page,default:30"` + PerPage int `json:"per_page" form:"per_page,default:30"` Page int `json:"page" form:"page,default:1"` Key string `json:"key" form:"key"` } -- Gitee From d3be1d1799e3efdb0c3e341a0c43eb9c353e5628 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 28 Dec 2021 14:08:05 +0800 Subject: [PATCH 1129/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/service/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 86134965..c330a00e 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -77,7 +77,7 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( if info.Source == CodeHostCodeHub { projectName = info.RepoUUID } - info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "",0, 0, , log) + info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "", 0, 0, log) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() -- Gitee From b92ac6f1fb2e4bd28e6f8bc12fda76b7d446f595 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 28 Dec 2021 14:18:13 +0800 Subject: [PATCH 1130/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index a1373616..da339ae6 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -91,7 +91,7 @@ rules: - key: "production" value: "false" - method: PUT - endpoint: "/api/aslan/service/services/pm/healthCheckUpdate" + endpoint: "/api/aslan/service/pm/healthCheckUpdate" resourceType: "Environment" matchAttributes: - key: "production" -- Gitee From fa0f7983d91d59e25be5c9f2b000ea1b7495bd05 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 28 Dec 2021 19:52:09 +0800 Subject: [PATCH 1131/2647] init collaboration Signed-off-by: panxunying --- .../handler/collaboration_instance.go | 17 ++ .../handler/collaboration_mode.go | 97 ++++++++++ .../core/collaboration/handler/router.go | 36 ++++ .../models/collaboration_instance.go | 54 ++++++ .../repository/models/collaboration_mode.go | 54 ++++++ .../mongodb/collaboration_instance.go | 40 +++++ .../repository/mongodb/collaboration_mode.go | 168 ++++++++++++++++++ .../service/collaboration_instance.go | 1 + .../service/collaboration_mode.go | 68 +++++++ pkg/microservice/aslan/server/rest/router.go | 3 +- 10 files changed, 537 insertions(+), 1 deletion(-) create mode 100644 pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go create mode 100644 pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go create mode 100644 pkg/microservice/aslan/core/collaboration/handler/router.go create mode 100644 pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go create mode 100644 pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go create mode 100644 pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go create mode 100644 pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go create mode 100644 pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go create mode 100644 pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go new file mode 100644 index 00000000..9aef1a5e --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -0,0 +1,17 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go new file mode 100644 index 00000000..e3a7060f --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -0,0 +1,97 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "encoding/json" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +func GetCollaborationMode(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetCollaborationModes(ctx.UserName, ctx.Logger) +} + +func CreateCollaborationMode(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.CollaborationMode) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateCollaborationMode c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateCollaborationMode json.Unmarshal err : %v", err) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "协作模式", args.Name, string(data), ctx.Logger) + + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = service.CreateCollaborationMode(ctx.UserName, args, ctx.Logger) +} + +func UpdateCollaborationMode(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.CollaborationMode) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateCollaborationMode c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("UpdateCollaborationMode json.Unmarshal err : %v", err) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "更新", "协作模式", args.Name, string(data), ctx.Logger) + + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = service.UpdateCollaborationMode(ctx.UserName, args, ctx.Logger) +} + +func DeleteCollaborationMode(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") + return + } + name := c.Param("name") + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "协作模式", name, "", ctx.Logger) + + ctx.Err = service.DeleteCollaborationMode(ctx.UserName, projectName, name, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go new file mode 100644 index 00000000..973d92ce --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -0,0 +1,36 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + gin2 "github.com/koderover/zadig/pkg/middleware/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + + collaborations := router.Group("collaborations") + { + collaborations.GET("", GetCollaborationMode) + collaborations.PUT("", gin2.UpdateOperationLogStatus, UpdateCollaborationMode) + collaborations.POST("", gin2.UpdateOperationLogStatus, CreateCollaborationMode) + collaborations.DELETE("", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) + } +} diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go new file mode 100644 index 00000000..c0b7fea1 --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type CollaborationInstance struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProjectName string `bson:"project_name" json:"project_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + CollaborationName string `bson:"collaboration_name" json:"collaboration_name"` + Revision int64 `bson:"revision" json:"revision"` + User string `bson:"user" json:"user"` + PolicyName string `bson:"policy_name" json:"policy_name"` + Workflows []ProductCIItem `json:"workflows" bson:"workflows"` + Products []WorkflowCIItem `bson:"products" json:"products"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` +} +type ProductCIItem struct { + Name string `bson:"name" json:"name"` + BaseName string `bson:"base_name" json:"base_name"` + CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` + Verbs []string `bson:"verbs" json:"verbs"` +} + +type WorkflowCIItem struct { + Name string `bson:"name" json:"name"` + BaseName string `bson:"base_name" json:"base_name"` + CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` + Verbs []string `bson:"verbs" json:"verbs"` +} + +func (CollaborationInstance) TableName() string { + return "collaboration_instance" +} diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go new file mode 100644 index 00000000..460fae2e --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type CollaborationMode struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + //foreign key:template_product/product_name + ProjectName string `bson:"project_name" json:"project_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Name string `bson:"name,omitempty" json:"name"` + Revision int64 `bson:"revision" json:"revision"` + Members []string `bson:"members" json:"members"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + Workflows []ProductCMItem `json:"workflows" bson:"workflows"` + Products []WorkflowCMItem `bson:"products" json:"products"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` +} + +type ProductCMItem struct { + Name string `bson:"name" json:"name"` + CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` + Verbs []string `bson:"verbs" json:"verbs"` +} + +type WorkflowCMItem struct { + Name string `bson:"name" json:"name"` + CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` + Verbs []string `bson:"verbs" json:"verbs"` +} + +func (CollaborationMode) TableName() string { + return "collaboration_mode" +} diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go new file mode 100644 index 00000000..ad5eb7eb --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type CollaborationInstanceColl struct { + *mongo.Collection + + coll string +} + +func NewCollaborationInstanceColl() *CollaborationInstanceColl { + name := models.CollaborationInstance{}.TableName() + return &CollaborationInstanceColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *CollaborationInstanceColl) GetCollectionName() string { + return c.coll +} diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go new file mode 100644 index 00000000..b8fddae2 --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -0,0 +1,168 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type CollaborationModeFindOptions struct { + ProjectName string + Name string +} + +type CollaborationModeColl struct { + *mongo.Collection + + coll string +} + +func NewCollaborationModeColl() *CollaborationModeColl { + name := models.CollaborationMode{}.TableName() + return &CollaborationModeColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *CollaborationModeColl) GetCollectionName() string { + return c.coll +} + +func (c *CollaborationModeColl) Update(username string, args *models.CollaborationMode) error { + if args == nil { + return errors.New("nil CollaborationMode") + } + res, err := c.Find(&CollaborationModeFindOptions{ + Name: args.Name, + ProjectName: args.ProjectName, + }) + if err != nil { + return err + } + if res != nil { + return errors.New("CollaborationMode has exist") + } + query := bson.M{"name": args.Name, "project_name": args.ProjectName} + change := bson.M{"$set": bson.M{ + "update_time": time.Now().Unix(), + "update_by": username, + "members": args.Members, + "workflows": args.Workflows, + "revision": res.Revision + 1, + "products": args.Products, + }} + + _, err = c.UpdateOne(context.TODO(), query, change) + + return err +} + +func (c *CollaborationModeColl) Create(userName string, args *models.CollaborationMode) error { + // avoid panic issue + if args == nil { + return errors.New("nil CollaborationMode") + } + res, err := c.Find(&CollaborationModeFindOptions{ + Name: args.Name, + ProjectName: args.ProjectName, + }) + if err != nil { + return err + } + if res != nil { + return errors.New("CollaborationMode has exist") + } + now := time.Now().Unix() + args.CreateBy = userName + args.UpdateBy = userName + args.CreateTime = now + args.UpdateTime = now + args.Revision = 1 + args.IsDeleted = false + _, err = c.InsertOne(context.TODO(), args) + return err +} + +func (c *CollaborationModeColl) Delete(username, projectName, name string) error { + res, err := c.Find(&CollaborationModeFindOptions{ + Name: name, + ProjectName: projectName, + }) + if err != nil { + return err + } + if res != nil { + return errors.New("CollaborationMode has exist") + } + query := bson.M{"name": name, "project_name": projectName} + change := bson.M{"$set": bson.M{ + "delete_time": time.Now().Unix(), + "delete_by": username, + "is_deleted": true, + }} + _, err = c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *CollaborationModeColl) Find(opt *CollaborationModeFindOptions) (*models.CollaborationMode, error) { + res := &models.CollaborationMode{} + query := bson.M{} + if opt.Name != "" { + query["name"] = opt.Name + } + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + query["is_deleted"] = false + err := c.FindOne(context.TODO(), query).Decode(res) + return res, err +} + +func (c *CollaborationModeColl) List(opt *CollaborationModeFindOptions) ([]*models.CollaborationMode, error) { + var ret []*models.CollaborationMode + query := bson.M{} + if opt.Name != "" { + query["name"] = opt.Name + } + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + query["is_deleted"] = false + + ctx := context.Background() + opts := options.Find() + opts.SetSort(bson.D{{"create_time", -1}}) + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &ret) + if err != nil { + return nil, err + } + + return ret, nil +} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go new file mode 100644 index 00000000..6d43c336 --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -0,0 +1 @@ +package service diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go new file mode 100644 index 00000000..e351735d --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go @@ -0,0 +1,68 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" +) + +type GetCollaborationModeResp struct { + Collaborations []*models.CollaborationMode `json:"collaborations"` +} + +func CreateCollaborationMode(userName string, collaborationMode *models.CollaborationMode, logger *zap.SugaredLogger) error { + err := mongodb.NewCollaborationModeColl().Create(userName, collaborationMode) + if err != nil { + logger.Errorf("CreateCollaborationMode error, err msg:%s", err) + return err + } + return nil +} + +func UpdateCollaborationMode(userName string, collaborationMode *models.CollaborationMode, logger *zap.SugaredLogger) error { + err := mongodb.NewCollaborationModeColl().Update(userName, collaborationMode) + if err != nil { + logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) + return err + } + return nil +} + +func DeleteCollaborationMode(username, projectName, name string, logger *zap.SugaredLogger) error { + err := mongodb.NewCollaborationModeColl().Delete(username, projectName, name) + if err != nil { + logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) + return err + } + return nil +} + +func GetCollaborationModes(projectName string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { + collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeFindOptions{ + ProjectName: projectName, + }) + if err != nil { + logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) + return nil, err + } + return &GetCollaborationModeResp{ + Collaborations: collaborations, + }, nil +} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 77d68e65..1d27760b 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -23,6 +23,7 @@ import ( buildhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/build/handler" codehosthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/code/handler" + collaborationhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/handler" commonhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/common/handler" cronhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/cron/handler" deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" @@ -36,7 +37,6 @@ import ( templatehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/handler" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" - // Note: have to load docs for swagger to work. See https://blog.csdn.net/weixin_43249914/article/details/103035711 _ "github.com/koderover/zadig/pkg/microservice/aslan/server/rest/doc" ) @@ -84,6 +84,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/testing": new(testinghandler.Router), "/api/cluster": new(multiclusterhandler.Router), "/api/template": new(templatehandler.Router), + "/api/v1": new(collaborationhandler.Router), } { r.Inject(router.Group(name)) } -- Gitee From 54911ccc6495853dbecf27de0a741efa50ef7d50 Mon Sep 17 00:00:00 2001 From: dehuaduan <30834299+ddh27@users.noreply.github.com> Date: Tue, 28 Dec 2021 20:11:27 +0800 Subject: [PATCH 1132/2647] fix different services triggered by webhook to build and deploy to the same environment in parallel (#825) * fix webhook parallel Signed-off-by: ddh27 * rename func Signed-off-by: ddh27 Co-authored-by: ddh27 --- .../core/workflow/service/workflow/pipeline_controller.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go index d368d0b5..73fb95b8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go @@ -419,8 +419,8 @@ func ParallelRunningAndQueuedTasks(currentTask *task.Task) bool { return false } //优先处理webhook - if currentTask.TaskCreator == setting.WebhookTaskCreator { - return HandlerWebhookTask(currentTask) + if currentTask.TaskCreator == setting.WebhookTaskCreator && checkWebhookTaskNamespace(currentTask) { + return true } //判断如果当前的task没有部署,直接放到队列里执行 currentTaskDeployServices := sets.NewString() @@ -464,8 +464,7 @@ func ParallelRunningAndQueuedTasks(currentTask *task.Task) bool { return true } -// HandlerWebhookTask 处理webhook逻辑 -func HandlerWebhookTask(currentTask *task.Task) bool { +func checkWebhookTaskNamespace(currentTask *task.Task) bool { runningNamespaces := sets.String{} for _, t := range ListTasks() { if t.Status != config.StatusRunning && t.Status != config.StatusQueued { -- Gitee From ab3442e66521bae54f53df6574e2551cd77cac8c Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 28 Dec 2021 20:41:16 +0800 Subject: [PATCH 1133/2647] helm install pending Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 3 +++ pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7cdd6abe..d23d2d33 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2317,6 +2317,9 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha ValuesYaml: valuesYaml, UpgradeCRDs: true, CleanupOnFail: true, + Wait: true, + Timeout: 10 * time.Minute, + Atomic: true, } if isRetry { chartSpec.Replace = true diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 3c5d364d..aa6a4bba 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -542,6 +542,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * SkipCRDs: false, UpgradeCRDs: true, Timeout: time.Second * DeployTimeout, + Wait: true, + Atomic: true, } if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { -- Gitee From 3ade07ee4c66cf675e79bd74083208f661ff5984 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 29 Dec 2021 11:17:21 +0800 Subject: [PATCH 1134/2647] upgrade go-helm-client Signed-off-by: ddh27 --- go.mod | 2 +- go.sum | 41 +++++++++++++---------------------------- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index ab0efb22..55d2483a 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/mittwald/go-helm-client v0.8.3 + github.com/mittwald/go-helm-client v0.8.4 github.com/moby/buildkit v0.9.1 github.com/nsqio/go-nsq v1.0.7 github.com/nwaples/rardecode v1.0.0 // indirect diff --git a/go.sum b/go.sum index b5a6e209..92c5c3f2 100644 --- a/go.sum +++ b/go.sum @@ -111,9 +111,8 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -160,9 +159,8 @@ github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2 github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.18/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim v0.8.21 h1:btRfUDThBE5IKcvI8O8jOiIkujUsAMBSRsYDYmEi6oM= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M= -github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim/test v0.0.0-20200826032352-301c83a30e7c/go.mod h1:30A5igQ91GEmhYJF8TaRP79pMBOYynRsyOByfVV0dU4= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= @@ -342,7 +340,6 @@ github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/containerd v1.4.1-0.20201117152358-0edc412565dc/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= @@ -351,9 +348,8 @@ github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTV github.com/containerd/containerd v1.5.2/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.3/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= github.com/containerd/containerd v1.5.4/go.mod h1:sx18RgvW6ABJ4iYUw7Q5x7bgFOAB9B6G7+yO0XBc4zw= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw= -github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -391,7 +387,6 @@ github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDG github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= @@ -441,14 +436,12 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= -github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -473,16 +466,14 @@ github.com/docker/cli v0.0.0-20190925022749-754388324470/go.mod h1:JLrzqnKDaYBop github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.0-beta1.0.20201029214301-1d20b15adc38+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.7+incompatible h1:pv/3NqibQKphWZiAskMzdz8w0PRbtTaEB+f6NwdU7Is= github.com/docker/cli v20.10.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= -github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible h1:357nGVUC8gSpeSc2Axup8HfrfTLLUfWfCsCUhiQSKIg= github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81 h1:VnJAjP/7FbY46wddaiKakrc1ogXW+3MlP7cg+S4j4Og= github.com/docker/docker v0.0.0-20180502112750-51a9119f6b81/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= -github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969 h1:Z8LR4yJgA3vaXpBiUWcWdQZpB9h+dzj4NdLjP9Sfdb0= github.com/docker/go-connections v0.3.1-0.20180212134524-7beb39f0b969/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= @@ -1002,9 +993,8 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE= github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= -github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w= -github.com/jmoiron/sqlx v1.3.4/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -1079,9 +1069,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= @@ -1154,9 +1143,8 @@ github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= @@ -1173,11 +1161,10 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mittwald/go-helm-client v0.8.3 h1:4l8W1PBZiNyapjJjoyQUU3qI8yWhOhpdD6wWdG3Z33g= -github.com/mittwald/go-helm-client v0.8.3/go.mod h1:ZdBFetgIkFiJivTy+P/Ruz6y5YtQMk5BnMSja4+wMdA= +github.com/mittwald/go-helm-client v0.8.4 h1:2T/MUkgUjeP+h2P0YYyuwXN8jB/l90oQ12JQ5+9h9tI= +github.com/mittwald/go-helm-client v0.8.4/go.mod h1:GtL4+taCH4ADXO+aimCEQ5EtPRSGPSHcT6d/fB3coFk= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= github.com/moby/buildkit v0.9.1 h1:6noq8jvkaRs3OQGTIDf5U5Etkgq6zsPmQHGWi5yhh88= github.com/moby/buildkit v0.9.1/go.mod h1:oVZKk3TMm0MlDx7XxnlF0wKmcpyrzOs9GEp0VXKWFPk= @@ -1267,9 +1254,8 @@ github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQ github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc10/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -2320,7 +2306,6 @@ gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= helm.sh/helm/v3 v3.7.0/go.mod h1:DajHtQTe8KrjNmvy5gxWkosFKaADrS3uRS5EkDtsmI4= -helm.sh/helm/v3 v3.7.1/go.mod h1:3eOeBD3Z+O/ELiuu19zynZSN8jP1ErXLuyP21SZeMq8= helm.sh/helm/v3 v3.7.2 h1:xn1OxcZEpgKpp4CCpPz1KKUyb9gAtTouXV2E3S8ChYQ= helm.sh/helm/v3 v3.7.2/go.mod h1:UXuiAn0+FfBpqbiMuwWt8/aAKkfJvnWLBJ6f4HcFs0M= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -- Gitee From ed4c966941bc4b60a3dbb3333dd69b895aceb8fe Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 29 Dec 2021 14:06:45 +0800 Subject: [PATCH 1135/2647] add collaboration instance Signed-off-by: panxunying --- .../mongodb/collaboration_instance.go | 20 +++++++++++++++++++ .../service/collaboration_instance.go | 19 ++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index ad5eb7eb..588be59b 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -17,6 +17,9 @@ limitations under the License. package mongodb import ( + "context" + + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -24,6 +27,10 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) +type CollaborationInstanceFindOptions struct { + ProjectName string + UserUID string +} type CollaborationInstanceColl struct { *mongo.Collection @@ -38,3 +45,16 @@ func NewCollaborationInstanceColl() *CollaborationInstanceColl { func (c *CollaborationInstanceColl) GetCollectionName() string { return c.coll } + +func (c *CollaborationInstanceColl) Find(opt *CollaborationInstanceFindOptions) (*models.CollaborationInstance, error) { + res := &models.CollaborationInstance{} + query := bson.M{} + if opt.UserUID != "" { + query["user_uid"] = opt.UserUID + } + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + err := c.FindOne(context.TODO(), query).Decode(res) + return res, err +} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 6d43c336..91146e98 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1 +1,20 @@ package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" +) + +func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { + collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeFindOptions{ + ProjectName: projectName, + }) + if err != nil { + logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) + return nil, err + } + return &GetCollaborationModeResp{ + Collaborations: collaborations, + }, nil +} -- Gitee From cbda9c773215531d93231711f681116435764238 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 29 Dec 2021 14:25:32 +0800 Subject: [PATCH 1136/2647] set helm-client timeout Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d23d2d33..9f5564f1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2318,7 +2318,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha UpgradeCRDs: true, CleanupOnFail: true, Wait: true, - Timeout: 10 * time.Minute, + Timeout: 5 * time.Minute, Atomic: true, } if isRetry { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index aa6a4bba..2da0290a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -541,7 +541,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ValuesYaml: replacedMergedValuesYaml, SkipCRDs: false, UpgradeCRDs: true, - Timeout: time.Second * DeployTimeout, + Timeout: 5 * time.Minute, Wait: true, Atomic: true, } -- Gitee From 8149c3bb4288bc7fc7f6c685f5d868c5b109c277 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 29 Dec 2021 14:28:32 +0800 Subject: [PATCH 1137/2647] Add new contributor mhatvan Signed-off-by: zhangyifei --- GOVERNANCE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 63252f66..7a688603 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -66,4 +66,5 @@ The council will make a decision for the nomination. The candidate has to have d - [huangzexiong](https://github.com/huangzexiong) - [ruchikabobade](https://github.com/ruchikabobade) - [fengzixu](https://github.com/fengzixu) -- [xrfinbupt](https://github.com/xrfinbupt) \ No newline at end of file +- [xrfinbupt](https://github.com/xrfinbupt) +- [mhatvan](https://github.com/mhatvan) -- Gitee From 93786613f2ace7d82cfc3c1f7b1e192ed6ab1370 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 29 Dec 2021 14:44:30 +0800 Subject: [PATCH 1138/2647] add registry image data compatible Signed-off-by: panxunying <641770806@qq.com> --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 35 +++ .../internal/repository/models/product.go | 134 +++++++++++ .../repository/models/registry_namespace.go | 62 ++++++ .../internal/repository/mongodb/product.go | 209 ++++++++++++++++++ .../repository/mongodb/registry_namespace.go | 131 +++++++++++ 5 files changed, 571 insertions(+) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/product.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/product.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 18b299c7..f9cc33fb 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -22,6 +22,8 @@ import ( "go.mongodb.org/mongo-driver/bson" + internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" @@ -45,6 +47,13 @@ func V171ToV180() error { return err } + log.Info("Start to patchProductRegistryID") + err := patchProductRegistryID() + if err != nil { + log.Errorf("Failed to patchProductRegistryID, err: %s", err) + return err + } + return nil } @@ -53,6 +62,32 @@ func V180ToV171() error { return nil } +func patchProductRegistryID() error { + // get all products + products, err := internalmongodb.NewProductColl().List(&internalmongodb.ProductListOptions{}) + if err != nil { + log.Errorf("fail to list products, err: %s", err) + return err + } + registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) + if err != nil { + log.Errorf("fail to find default registry, err: %s", err) + return err + } + // change type to readable string + for _, v := range products { + if len(v.RegistryID) == 0 { + v.RegistryID = registry.ID.Hex() + } + } + err = internalmongodb.NewProductColl().UpdateAllRegistry(products) + if err != nil { + log.Errorf("fail to update products, err: %s", err) + return err + } + return nil +} + func updateAllRoleBindingNames() error { coll := newRoleBindingColl() rbs, err := coll.List() diff --git a/pkg/cli/upgradeassistant/internal/repository/models/product.go b/pkg/cli/upgradeassistant/internal/repository/models/product.go new file mode 100644 index 00000000..b9a6a038 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/product.go @@ -0,0 +1,134 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" +) + +type ProductAuthType string +type ProductPermission string + +// Vars不做保存,只做input参数 +type Product struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + Revision int64 `bson:"revision" json:"revision"` + Enabled bool `bson:"enabled" json:"enabled"` + EnvName string `bson:"env_name" json:"env_name"` + UpdateBy string `bson:"update_by" json:"update_by"` + Auth []*ProductAuth `bson:"auth" json:"auth"` + Visibility string `bson:"-" json:"visibility"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + Error string `bson:"error" json:"error"` + Vars []*templatemodels.RenderKV `bson:"vars,omitempty" json:"vars,omitempty"` + ChartInfos []*templatemodels.RenderChart `bson:"-" json:"chart_infos,omitempty"` + IsPublic bool `bson:"is_public" json:"isPublic"` + RoleIDs []int64 `bson:"role_ids" json:"roleIds"` + ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` + RecycleDay int `bson:"recycle_day" json:"recycle_day"` + Source string `bson:"source" json:"source"` + IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` + RegistryID string `bson:"registry_id" json:"registry_id"` + IsForkedProduct bool `bson:"-" json:"-"` +} + +type RenderInfo struct { + Name string `bson:"name" json:"name"` + Revision int64 `bson:"revision" json:"revision"` + ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` + Description string `bson:"description" json:"description"` +} + +type ProductAuth struct { + Type ProductAuthType `bson:"type" json:"type"` + Name string `bson:"name" json:"name"` + Permissions []ProductPermission `bson:"permissions" json:"permissions"` +} + +// ImagePathSpec paths in yaml used to parse image +type ImagePathSpec struct { + Repo string `bson:"repo,omitempty" json:"repo,omitempty"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + Tag string `bson:"tag,omitempty" json:"tag,omitempty"` +} + +// Container ... +type Container struct { + Name string `bson:"name" json:"name"` + Image string `bson:"image" json:"image"` + ImagePath *ImagePathSpec `bson:"image_path,omitempty" json:"imagePath,omitempty"` +} + +type EnvConfig struct { + EnvName string `bson:"env_name,omitempty" json:"env_name"` + HostIDs []string `bson:"host_ids,omitempty" json:"host_ids"` + Labels []string `bson:"labels,omitempty" json:"labels"` +} + +type ProductService struct { + ServiceName string `bson:"service_name" json:"service_name"` + ProductName string `bson:"product_name" json:"product_name"` + Type string `bson:"type" json:"type"` + Revision int64 `bson:"revision" json:"revision"` + Containers []*Container `bson:"containers" json:"containers,omitempty"` + Render *RenderInfo `bson:"render,omitempty" json:"render,omitempty"` // 记录每个服务render信息 便于更新单个服务 + EnvConfigs []*EnvConfig `bson:"-" json:"env_configs,omitempty"` +} + +type ServiceConfig struct { + ConfigName string `bson:"config_name" json:"config_name"` + Revision int64 `bson:"revision" json:"revision"` +} + +func (Product) TableName() string { + return "product" +} + +func (p *Product) GetNamespace() string { + return p.ProductName + "-env-" + p.EnvName +} + +func (p *Product) GetGroupServiceNames() [][]string { + var resp [][]string + for _, group := range p.Services { + services := make([]string, 0, len(group)) + for _, service := range group { + services = append(services, service.ServiceName) + } + resp = append(resp, services) + } + return resp +} + +func (p *Product) GetServiceMap() map[string]*ProductService { + ret := make(map[string]*ProductService) + for _, group := range p.Services { + for _, svc := range group { + ret[svc.ServiceName] = svc + } + } + + return ret +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go b/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go new file mode 100644 index 00000000..707493e3 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go @@ -0,0 +1,62 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "errors" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type RegistryNamespace struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + RegAddr string `bson:"reg_addr" json:"reg_addr"` + RegType string `bson:"reg_type" json:"reg_type"` + RegProvider string `bson:"reg_provider" json:"reg_provider"` + IsDefault bool `bson:"is_default" json:"is_default"` + Namespace string `bson:"namespace" json:"namespace"` + AccessKey string `bson:"access_key" json:"access_key"` + SecretKey string `bson:"secret_key" json:"secret_key"` + Region string `bson:"region,omitempty" json:"region,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` +} + +func (ns *RegistryNamespace) Validate() error { + + if ns.RegAddr == "" { + return errors.New("empty reg_addr") + } + + if ns.RegProvider == "" { + return errors.New("empty reg_provider") + } + + if ns.Namespace == "" { + return errors.New("empty namespace") + } + + return nil +} + +func (RegistryNamespace) TableName() string { + return "registry_namespace" +} + +//const CandidateRegType = "candidate" +// +//const DistRegType = "dist" diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go new file mode 100644 index 00000000..c4c92f41 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go @@ -0,0 +1,209 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ProductFindOptions struct { + Name string + EnvName string + Namespace string +} + +// ClusterId is a primitive.ObjectID{}.Hex() +type ProductListOptions struct { + EnvName string + Name string + IsPublic bool + ClusterID string + IsSortByUpdateTime bool + IsSortByProductName bool + ExcludeStatus string + ExcludeSource string + Source string + InProjects []string + InEnvs []string +} + +type projectEnvs struct { + ID projectID `bson:"_id"` + ProjectName string `bson:"project_name"` + Envs []string `bson:"envs"` +} + +type projectID struct { + ProductName string `bson:"product_name"` +} + +type ProductColl struct { + *mongo.Collection + + coll string +} + +func NewProductColl() *ProductColl { + name := models.Product{}.TableName() + return &ProductColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *ProductColl) GetCollectionName() string { + return c.coll +} + +func (c *ProductColl) EnsureIndex(ctx context.Context) error { + mod := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }, + { + Keys: bson.D{ + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "update_time", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mod) + + return err +} + +type ProductEnvFindOptions struct { + Name string + Namespace string +} + +func (c *ProductColl) FindEnv(opt *ProductEnvFindOptions) (*models.Product, error) { + query := bson.M{} + if opt.Name != "" { + query["product_name"] = opt.Name + } + + if opt.Namespace != "" { + query["namespace"] = opt.Namespace + } + + ret := new(models.Product) + err := c.FindOne(context.TODO(), query).Decode(ret) + return ret, err +} + +func (c *ProductColl) Find(opt *ProductFindOptions) (*models.Product, error) { + res := &models.Product{} + query := bson.M{} + if opt.Name != "" { + query["product_name"] = opt.Name + } + if opt.EnvName != "" { + query["env_name"] = opt.EnvName + } + if opt.Namespace != "" { + query["namespace"] = opt.Namespace + } + + err := c.FindOne(context.TODO(), query).Decode(res) + return res, err +} + +func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { + var ret []*models.Product + query := bson.M{} + + if opt == nil { + opt = &ProductListOptions{} + } + if opt.EnvName != "" { + query["env_name"] = opt.EnvName + } else if len(opt.InEnvs) > 0 { + query["env_name"] = bson.M{"$in": opt.InEnvs} + } + if opt.Name != "" { + query["product_name"] = opt.Name + } + if opt.IsPublic { + query["is_public"] = opt.IsPublic + } + if opt.ClusterID != "" { + query["cluster_id"] = opt.ClusterID + } + if opt.Source != "" { + query["source"] = opt.Source + } + if opt.ExcludeSource != "" { + query["source"] = bson.M{"$ne": opt.ExcludeSource} + } + if opt.ExcludeStatus != "" { + query["status"] = bson.M{"$ne": opt.ExcludeStatus} + } + if len(opt.InProjects) > 0 { + query["product_name"] = bson.M{"$in": opt.InProjects} + } + + ctx := context.Background() + opts := options.Find() + if opt.IsSortByUpdateTime { + opts.SetSort(bson.D{{"update_time", -1}}) + } + if opt.IsSortByProductName { + opts.SetSort(bson.D{{"product_name", 1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (c *ProductColl) UpdateAllRegistry(envs []*models.Product) error { + if len(envs) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, env := range envs { + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", env.ID}}). + SetUpdate(bson.D{{"$set", bson.D{{"registry_id", env.RegistryID}}}}), + ) + } + _, err := c.BulkWrite(context.TODO(), ms) + + return err +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go new file mode 100644 index 00000000..6804be05 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go @@ -0,0 +1,131 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type FindRegOps struct { + ID string `json:"id"` + RegAddr string `json:"reg_addr"` + RegType string `json:"reg_type"` + RegProvider string `json:"reg_provider"` + IsDefault bool `json:"is_default"` + Namespace string `json:"namespace"` +} + +type RegistryNamespaceColl struct { + *mongo.Collection + + coll string +} + +func NewRegistryNamespaceColl() *RegistryNamespaceColl { + name := models.RegistryNamespace{}.TableName() + coll := &RegistryNamespaceColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (r *RegistryNamespaceColl) GetCollectionName() string { + return r.coll +} + +func (r *RegistryNamespaceColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "org_id", Value: 1}, + bson.E{Key: "is_default", Value: 1}, + bson.E{Key: "reg_type", Value: 1}, + }, + Options: options.Index().SetUnique(false), + } + + _, err := r.Indexes().CreateOne(ctx, mod) + return err +} + +func (opt FindRegOps) getQuery() bson.M { + query := bson.M{} + + if opt.ID != "" { + oid, err := primitive.ObjectIDFromHex(opt.ID) + if err == nil { + query["_id"] = oid + } + } + + if opt.IsDefault { + query["is_default"] = true + } + + if opt.RegType != "" { + query["reg_type"] = opt.RegType + } + + if opt.RegAddr != "" { + query["reg_addr"] = opt.RegAddr + } + + if opt.RegProvider != "" { + query["reg_provider"] = opt.RegProvider + } + + if opt.Namespace != "" { + query["namespace"] = opt.Namespace + } + return query +} + +func (r *RegistryNamespaceColl) Find(opt *FindRegOps) (*models.RegistryNamespace, error) { + query := opt.getQuery() + + res := &models.RegistryNamespace{} + err := r.FindOne(context.TODO(), query).Decode(res) + + return res, err +} + +func (r *RegistryNamespaceColl) FindAll(opt *FindRegOps) ([]*models.RegistryNamespace, error) { + query := opt.getQuery() + + ctx := context.Background() + opts := options.Find() + resp := make([]*models.RegistryNamespace, 0) + + cursor, err := r.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, err +} -- Gitee From dab5dd718d403c28feb232001bb964bfe06c3f92 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 29 Dec 2021 15:37:31 +0800 Subject: [PATCH 1139/2647] remove list all renderset logic Signed-off-by: allenshen --- .../common/repository/mongodb/render_set.go | 40 +++++++- .../core/environment/service/environment.go | 16 +--- .../core/environment/service/revision.go | 91 ++++++++++--------- 3 files changed, 88 insertions(+), 59 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go index 8778156e..5ca09748 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go @@ -19,6 +19,7 @@ package mongodb import ( "context" "errors" + "fmt" "time" "go.mongodb.org/mongo-driver/bson" @@ -32,15 +33,16 @@ import ( type RenderSetListOption struct { // if Revision == 0 then search max revision of RenderSet - Revision int64 - ProductTmpl string + ProductTmpl string + Revisions []int64 + RendersetName string } // RenderSetFindOption ... type RenderSetFindOption struct { // if Revision == 0 then search max revision of RenderSet - Revision int64 - Name string + Revision int64 + Name string } type RenderSetPipeResp struct { @@ -80,6 +82,36 @@ func (c *RenderSetColl) EnsureIndex(ctx context.Context) error { return err } +func (c *RenderSetColl) ListRendersets(opt *RenderSetListOption) ([]*models.RenderSet, error) { + query := bson.M{} + if len(opt.ProductTmpl) > 0 { + query["product_tmpl"] = opt.ProductTmpl + } + if len(opt.RendersetName) > 0 { + query["name"] = opt.RendersetName + } + if len(opt.Revisions) > 0 { + query["revision"] = bson.M{"$in": opt.Revisions} + } + + if len(query) == 0 { + return nil, fmt.Errorf("query with no filter is not allowed") + } + + var rendersets []*models.RenderSet + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &rendersets) + if err != nil { + return nil, err + } + + return rendersets, err + +} + func (c *RenderSetColl) List(opt *RenderSetListOption) ([]*models.RenderSet, error) { var pipeResp []*RenderSetPipeResp var pipeline []bson.M diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7cdd6abe..b74f1359 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -376,25 +376,17 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com updateProd.Namespace = existedProd.Namespace var allServices []*commonmodels.Service - var allRenders []*commonmodels.RenderSet var prodRevs *ProductRevision - allServices, err = commonrepo.NewServiceColl().ListAllRevisions() + // list services with max revision of project + allServices, err = commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: productName}) if err != nil { - log.Errorf("ListAllRevisions error: %v", err) + log.Errorf("ListAllRevisions error: %s", err) err = e.ErrUpdateEnv.AddDesc(err.Error()) return } - // 获取所有渲染配置最新模板信息 - allRenders, err = commonrepo.NewRenderSetColl().ListAllRenders() - if err != nil { - log.Errorf("ListAllRevisions error: %v", err) - err = e.ErrUpdateEnv.AddDesc(err.Error()) - return - } - - prodRevs, err = GetProductRevision(existedProd, allServices, allRenders, renderSet, log) + prodRevs, err = GetProductRevision(existedProd, allServices, log) if err != nil { err = e.ErrUpdateEnv.AddDesc(e.GetEnvRevErrMsg) return diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index ffa22b27..2c26c89c 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -19,6 +19,8 @@ package service import ( "fmt" + "k8s.io/apimachinery/pkg/util/sets" + "go.uber.org/zap" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -32,29 +34,15 @@ import ( func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) (prodRevs []*ProductRevision, err error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName}) - // 获取所有服务模板最新模板信息 - allServiceTmpls, err := commonrepo.NewServiceColl().ListAllRevisions() - if err != nil { - log.Errorf("ListAllRevisions error: %v", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - // 获取所有渲染配置最新模板信息 - allRenders, err := commonrepo.NewRenderSetColl().ListAllRenders() + // list services with max revision of project + allServiceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: productName}) if err != nil { log.Errorf("ListAllRevisions error: %v", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) } for _, prod := range products { - newRender := &commonmodels.RenderSet{} - if prod.Render != nil { - newRender, err = commonservice.GetRenderSet(prod.Render.Name, 0, log) - if err != nil { - return prodRevs, err - } - } - - prodRev, err := GetProductRevision(prod, allServiceTmpls, allRenders, newRender, log) + prodRev, err := GetProductRevision(prod, allServiceTmpls, log) if err != nil { log.Error(err) return prodRevs, err @@ -64,6 +52,7 @@ func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) ( return prodRevs, nil } +// ListProductsRevisionByFacility called by service cron func ListProductsRevisionByFacility(basicFacility string, log *zap.SugaredLogger) ([]*ProductRevision, error) { var ( err error @@ -88,30 +77,16 @@ func ListProductsRevisionByFacility(basicFacility string, log *zap.SugaredLogger return prodRevs, e.ErrListProducts.AddDesc(err.Error()) } - // 获取所有服务模板最新模板信息 - allServiceTmpls, err := commonrepo.NewServiceColl().ListAllRevisions() - if err != nil { - log.Errorf("ListAllRevisions error: %s", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - - // 获取所有渲染配置最新模板信息 - allRenders, err := commonrepo.NewRenderSetColl().ListAllRenders() - if err != nil { - log.Errorf("ListAllRevisions error: %s", err) - return prodRevs, e.ErrListProducts.AddDesc(err.Error()) - } - for _, prod := range products { - newRender := &commonmodels.RenderSet{} - if prod.Render != nil { - newRender, err = commonservice.GetRenderSet(prod.Render.Name, 0, log) - if err != nil { - return prodRevs, err - } + + // find all service templates with max revisions + allServiceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: prod.ProductName}) + if err != nil { + log.Errorf("ListAllRevisions error: %s", err) + return prodRevs, e.ErrListProducts.AddDesc(err.Error()) } - prodRev, err := GetProductRevision(prod, allServiceTmpls, allRenders, newRender, log) + prodRev, err := GetProductRevision(prod, allServiceTmpls, log) if err != nil { log.Error(err) return prodRevs, err @@ -121,7 +96,7 @@ func ListProductsRevisionByFacility(basicFacility string, log *zap.SugaredLogger return prodRevs, nil } -func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*commonmodels.Service, allRender []*commonmodels.RenderSet, newRender *commonmodels.RenderSet, log *zap.SugaredLogger) (*ProductRevision, error) { +func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*commonmodels.Service, log *zap.SugaredLogger) (*ProductRevision, error) { prodRev := new(ProductRevision) @@ -150,8 +125,38 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common prodRev.Updatable = true } + var allRenders []*commonmodels.RenderSet + var newRender *commonmodels.RenderSet + if prodTmpl.ProductFeature != nil && prodTmpl.ProductFeature.DeployType == setting.K8SDeployType { + rendersetName := "" + if product.Render != nil { + rendersetName = product.Render.Name + newRender, err = commonservice.GetRenderSet(product.Render.Name, 0, log) + if err != nil { + return prodRev, err + } + } + + // get all rendersets used by product services + renderRevision := sets.NewInt64() + for _, productService := range product.GetServiceMap() { + if productService.Render != nil { + renderRevision.Insert(productService.Render.Revision) + } + } + allRenders, err = commonrepo.NewRenderSetColl().ListRendersets(&commonrepo.RenderSetListOption{ + Revisions: renderRevision.List(), + ProductTmpl: productTemplatName, + RendersetName: rendersetName, + }) + if err != nil { + log.Errorf("ListAllRevisions error: %s", err) + return prodRev, e.ErrListProducts.AddDesc(err.Error()) + } + } + // 交叉对比已创建的服务组和服务组模板 - prodRev.ServiceRevisions, err = compareGroupServicesRev(prodTmpl.Services, product, allServiceTmpls, allRender, newRender, log) + prodRev.ServiceRevisions, err = compareGroupServicesRev(prodTmpl.Services, product, allServiceTmpls, allRenders, newRender, log) if err != nil { log.Error(err) return nil, e.ErrGetProductRevision.AddDesc(err.Error()) @@ -204,9 +209,9 @@ func compareServicesRev(serviceTmplNames []string, services []*commonmodels.Prod serviceRevs := make([]*SvcRevision, 0) - serviceMap := make(map[string]*commonmodels.ProductService) + productServiceMap := make(map[string]*commonmodels.ProductService) for _, service := range services { - serviceMap[service.ServiceName] = service + productServiceMap[service.ServiceName] = service } serviceTmplMap := make(map[string]string) @@ -217,7 +222,7 @@ func compareServicesRev(serviceTmplNames []string, services []*commonmodels.Prod for _, serviceTmplName := range serviceTmplNames { // 如果已创建的服务不包括新增的服务模板, 则认为是新增服务 // 新增服务如果涉及多个部署方式,目前把所有部署方式都默认加进来 - if _, ok := serviceMap[serviceTmplName]; !ok { + if _, ok := productServiceMap[serviceTmplName]; !ok { newServiceTmpls, err := getMaxServices(allServiceTmpls, serviceTmplName) if err != nil { log.Error(err) -- Gitee From 7bf61da33ee17cbe117587c4a8efd2b06895ae3a Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 29 Dec 2021 15:47:10 +0800 Subject: [PATCH 1140/2647] optimize go import Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/revision.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 2c26c89c..2c46c0e2 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -19,9 +19,8 @@ package service import ( "fmt" - "k8s.io/apimachinery/pkg/util/sets" - "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" -- Gitee From 5c1857b8a134b5f33f19605580b317c8656f9849 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 30 Dec 2021 10:33:57 +0800 Subject: [PATCH 1141/2647] add collaboration instance Signed-off-by: panxunying <641770806@qq.com> --- .../mongodb/collaboration_instance.go | 26 +++++++++++++++++++ .../service/collaboration_instance.go | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 588be59b..d431050b 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -21,6 +21,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" @@ -58,3 +59,28 @@ func (c *CollaborationInstanceColl) Find(opt *CollaborationInstanceFindOptions) err := c.FindOne(context.TODO(), query).Decode(res) return res, err } + +func (c *CollaborationInstanceColl) List(opt *CollaborationModeFindOptions) ([]*models.CollaborationInstance, error) { + var ret []*models.CollaborationInstance + query := bson.M{} + if opt.Name != "" { + query["name"] = opt.Name + } + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + ctx := context.Background() + opts := options.Find() + opts.SetSort(bson.D{{"create_time", -1}}) + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &ret) + if err != nil { + return nil, err + } + + return ret, nil +} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 91146e98..ac6bf63c 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -3,10 +3,11 @@ package service import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" ) -func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { +func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) ([]*models.CollaborationInstance, error) { collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeFindOptions{ ProjectName: projectName, }) -- Gitee From b1fce4db02bfebc510c87c60e56e5dd83f0e94e3 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 30 Dec 2021 10:54:22 +0800 Subject: [PATCH 1142/2647] optimize Signed-off-by: panxunying --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 7 +++---- .../upgradeassistant/internal/repository/models/product.go | 5 ++--- .../internal/repository/models/registry_namespace.go | 5 ----- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index f9cc33fb..c3ac23ac 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -23,7 +23,6 @@ import ( "go.mongodb.org/mongo-driver/bson" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" - "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" @@ -66,12 +65,12 @@ func patchProductRegistryID() error { // get all products products, err := internalmongodb.NewProductColl().List(&internalmongodb.ProductListOptions{}) if err != nil { - log.Errorf("fail to list products, err: %s", err) + log.Errorf("Fail to list products, err: %s", err) return err } registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) if err != nil { - log.Errorf("fail to find default registry, err: %s", err) + log.Errorf("Fail to find default registry, err: %s", err) return err } // change type to readable string @@ -82,7 +81,7 @@ func patchProductRegistryID() error { } err = internalmongodb.NewProductColl().UpdateAllRegistry(products) if err != nil { - log.Errorf("fail to update products, err: %s", err) + log.Errorf("Fail to update products, err: %s", err) return err } return nil diff --git a/pkg/cli/upgradeassistant/internal/repository/models/product.go b/pkg/cli/upgradeassistant/internal/repository/models/product.go index b9a6a038..5d367697 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/product.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/product.go @@ -25,7 +25,7 @@ import ( type ProductAuthType string type ProductPermission string -// Vars不做保存,只做input参数 +// Vars do not save, only input parameters type Product struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ProductName string `bson:"product_name" json:"product_name"` @@ -74,7 +74,6 @@ type ImagePathSpec struct { Tag string `bson:"tag,omitempty" json:"tag,omitempty"` } -// Container ... type Container struct { Name string `bson:"name" json:"name"` Image string `bson:"image" json:"image"` @@ -93,7 +92,7 @@ type ProductService struct { Type string `bson:"type" json:"type"` Revision int64 `bson:"revision" json:"revision"` Containers []*Container `bson:"containers" json:"containers,omitempty"` - Render *RenderInfo `bson:"render,omitempty" json:"render,omitempty"` // 记录每个服务render信息 便于更新单个服务 + Render *RenderInfo `bson:"render,omitempty" json:"render,omitempty"` // Record the render information of each service to facilitate the update of a single service EnvConfigs []*EnvConfig `bson:"-" json:"env_configs,omitempty"` } diff --git a/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go b/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go index 707493e3..6a159859 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/registry_namespace.go @@ -37,7 +37,6 @@ type RegistryNamespace struct { } func (ns *RegistryNamespace) Validate() error { - if ns.RegAddr == "" { return errors.New("empty reg_addr") } @@ -56,7 +55,3 @@ func (ns *RegistryNamespace) Validate() error { func (RegistryNamespace) TableName() string { return "registry_namespace" } - -//const CandidateRegType = "candidate" -// -//const DistRegType = "dist" -- Gitee From 128e12559b6a93a6cc432f7738a9c5f3ceeea90a Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 30 Dec 2021 10:54:50 +0800 Subject: [PATCH 1143/2647] add envstatus onboarding Signed-off-by: mouuii --- .../aslan/core/common/service/service.go | 2 + .../aslan/core/environment/service/pm.go | 42 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 869257cb..79fa799a 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -437,6 +437,8 @@ func UpdatePmServiceTemplate(username string, args *ServiceTmplBuildObject, log preService.Revision = rev preService.CreateBy = username preService.BuildName = args.Build.Name + preService.EnvConfigs = args.ServiceTmplObject.EnvConfigs + preService.EnvStatuses = args.ServiceTmplObject.EnvStatuses if err := commonrepo.NewServiceColl().Delete(preService.ServiceName, setting.PMDeployType, args.ServiceTmplObject.ProductName, setting.ProductStatusDeleting, preService.Revision); err != nil { return err diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 8ac297b9..3a104ed4 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -24,9 +24,11 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -103,6 +105,13 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService serviceTmpl, err := commonservice.GetServiceTemplate( service.ServiceName, setting.PMDeployType, service.ProductName, "", service.Revision, p.log, ) + if len(serviceTmpl.EnvConfigs) > 0 { + for _, envconfig := range serviceTmpl.EnvConfigs { + if envconfig.EnvName == envName { + gp.EnvConfigs = []*models.EnvConfig{envconfig} + } + } + } gp.EnvConfigs = serviceTmpl.EnvConfigs if err != nil { gp.Status = setting.PodFailed @@ -167,10 +176,41 @@ func (p *PMService) createGroup(envName, productName, username string, group []* envConfig := &commonmodels.EnvConfig{ EnvName: currentEnvConfig.EnvName, HostIDs: currentEnvConfig.HostIDs, + Labels: currentEnvConfig.Labels, } oldEnvConfigs = append(oldEnvConfigs, envConfig) } + //privateKeys := []*models.PrivateKey{} + changeEnvStatus := []*commonmodels.EnvStatus{} + for _, envConfig := range oldEnvConfigs { + tmpPrivateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) + if err != nil { + log.Errorf("ListNameByArgs ids err:%s", err) + return err + } + + privateKeysByLabels, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + return err + } + tmpPrivateKeys = append(tmpPrivateKeys, privateKeysByLabels...) + privateKeysSet := sets.NewString() + for _, v := range tmpPrivateKeys { + tmp := models.EnvStatus{ + HostID: v.ID.Hex(), + EnvName: envConfig.EnvName, + Address: v.IP, + } + if !privateKeysSet.Has(tmp.HostID) { + changeEnvStatus = append(changeEnvStatus, &tmp) + privateKeysSet.Insert(tmp.HostID) + } + } + //privateKeys = append(privateKeys, tmpPrivateKeys...) + } + args := &commonservice.ServiceTmplBuildObject{ ServiceTmplObject: &commonservice.ServiceTmplObject{ ProductName: serviceTempl.ProductName, @@ -181,7 +221,7 @@ func (p *PMService) createGroup(envName, productName, username string, group []* Username: username, HealthChecks: serviceTempl.HealthChecks, EnvConfigs: oldEnvConfigs, - EnvStatuses: []*commonmodels.EnvStatus{}, + EnvStatuses: changeEnvStatus, From: "createEnv", }, Build: &commonmodels.Build{Name: serviceTempl.BuildName}, -- Gitee From 0f7a9063f9b07046823d2c642933d200cfa53411 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 30 Dec 2021 10:57:55 +0800 Subject: [PATCH 1144/2647] optimize Signed-off-by: panxunying --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index c3ac23ac..a5321e61 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -65,12 +65,12 @@ func patchProductRegistryID() error { // get all products products, err := internalmongodb.NewProductColl().List(&internalmongodb.ProductListOptions{}) if err != nil { - log.Errorf("Fail to list products, err: %s", err) + log.Errorf("Failed to list products, err: %s", err) return err } registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) if err != nil { - log.Errorf("Fail to find default registry, err: %s", err) + log.Errorf("Failed to find default registry, err: %s", err) return err } // change type to readable string @@ -81,7 +81,7 @@ func patchProductRegistryID() error { } err = internalmongodb.NewProductColl().UpdateAllRegistry(products) if err != nil { - log.Errorf("Fail to update products, err: %s", err) + log.Errorf("Failed to update products, err: %s", err) return err } return nil -- Gitee From 37f7d8e1d7801d2a461c92cac28df8dd948df86f Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 30 Dec 2021 11:05:35 +0800 Subject: [PATCH 1145/2647] add envstatus onboarding Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/pm.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 3a104ed4..12bd4a71 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -112,7 +112,6 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService } } } - gp.EnvConfigs = serviceTmpl.EnvConfigs if err != nil { gp.Status = setting.PodFailed mutex.Lock() -- Gitee From 89f2ae77aa95d3ac1d65cd43f7a726ab8b469206 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 30 Dec 2021 11:14:16 +0800 Subject: [PATCH 1146/2647] add envstatus onboarding Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/pm.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 12bd4a71..acf24fb2 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -180,7 +180,6 @@ func (p *PMService) createGroup(envName, productName, username string, group []* oldEnvConfigs = append(oldEnvConfigs, envConfig) } - //privateKeys := []*models.PrivateKey{} changeEnvStatus := []*commonmodels.EnvStatus{} for _, envConfig := range oldEnvConfigs { tmpPrivateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) @@ -207,7 +206,6 @@ func (p *PMService) createGroup(envName, productName, username string, group []* privateKeysSet.Insert(tmp.HostID) } } - //privateKeys = append(privateKeys, tmpPrivateKeys...) } args := &commonservice.ServiceTmplBuildObject{ -- Gitee From b5bfb0f0be8cec3bd480f22b6079c1b46484a569 Mon Sep 17 00:00:00 2001 From: dehuaduan <30834299+ddh27@users.noreply.github.com> Date: Thu, 30 Dec 2021 11:19:25 +0800 Subject: [PATCH 1147/2647] fix webhook Influence each other (#836) Signed-off-by: ddh27 Co-authored-by: ddh27 --- .../core/workflow/service/webhook/gitlab_workflow_task.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 2fb2227d..2a0dbb94 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -473,8 +473,9 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, if item.IsYaml { err := UpdateWorkflowTaskArgs(triggerYaml, workflow, workFlowArgs, item, branref, prID) if err != nil { - log.Errorf("UpdateWorkflowTaskArgs %s", err) - return fmt.Errorf("UpdateWorkflowTaskArgs %s", err) + log.Warnf("UpdateWorkflowTaskArgs %s", err) + mErr = multierror.Append(mErr, err) + continue } item.WorkflowArgs = workFlowArgs } else { -- Gitee From bb63099ffb6f068d39af1b736b43147ae1e78b54 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 30 Dec 2021 11:34:20 +0800 Subject: [PATCH 1148/2647] update get docker registry url logic Signed-off-by: allenshen --- .../workflow/service/workflow/pipeline_validation.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 4 ++-- pkg/util/url.go | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 1c33ddcc..fb6ca7c9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -587,7 +587,7 @@ func replaceVariable(customRule *template.CustomRule, candidate *candidate) stri // GetImage suffix 可以是 branch name 或者 pr number func GetImage(registry *commonmodels.RegistryNamespace, suffix string) string { - return fmt.Sprintf("%s/%s/%s", util.GetURLHostName(registry.RegAddr), registry.Namespace, suffix) + return fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, suffix) } // GetPackageFile suffix 可以是 branch name 或者 pr number diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 481ee0ee..3a30c2e7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2197,7 +2197,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorkflowTaskCreator} } registry := pt.ConfigPayload.RepoConfigs[pt.WorkflowArgs.RegistryID] - t.Image = fmt.Sprintf("%s/%s/%s", util.GetURLHostName(registry.RegAddr), registry.Namespace, t.Image) + t.Image = fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, t.Image) pt.TaskArgs.Deploy.Image = t.Image t.RegistryID = pt.WorkflowArgs.RegistryID @@ -2403,7 +2403,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e for _, repoImage := range t.Releases { if v, ok := pt.ConfigPayload.RepoConfigs[repoImage.RepoID]; ok { repoImage.Name = util.ReplaceRepo(pt.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) - repoImage.Host = util.GetURLHostName(v.RegAddr) + repoImage.Host = util.TrimURLScheme(v.RegAddr) repoImage.Namespace = v.Namespace repos = append(repos, repoImage) } diff --git a/pkg/util/url.go b/pkg/util/url.go index 8ca48093..4d4bb85e 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -22,6 +22,14 @@ import ( "strings" ) +func TrimURLScheme(urlAddr string) string { + uri, err := url.Parse(urlAddr) + if err != nil { + return urlAddr + } + return strings.Join([]string{uri.Host, uri.Path}, "") +} + // GetURLHostName ... func GetURLHostName(urlAddr string) string { @@ -35,7 +43,7 @@ func GetURLHostName(urlAddr string) string { func ReplaceRepo(origin, addr, namespace string) string { parts := strings.SplitN(origin, "/", -1) return strings.Join([]string{ - GetURLHostName(addr), + TrimURLScheme(addr), namespace, parts[len(parts)-1], }, "/") -- Gitee From d40cdbb935639e6cacc159c8f925e5e5f2f3a64c Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 30 Dec 2021 13:35:50 +0800 Subject: [PATCH 1149/2647] add envstatus onboarding Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/pm.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index acf24fb2..8128f10f 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -105,13 +105,13 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService serviceTmpl, err := commonservice.GetServiceTemplate( service.ServiceName, setting.PMDeployType, service.ProductName, "", service.Revision, p.log, ) - if len(serviceTmpl.EnvConfigs) > 0 { - for _, envconfig := range serviceTmpl.EnvConfigs { - if envconfig.EnvName == envName { - gp.EnvConfigs = []*models.EnvConfig{envconfig} - } + + for _, envconfig := range serviceTmpl.EnvConfigs { + if envconfig.EnvName == envName { + gp.EnvConfigs = []*models.EnvConfig{envconfig} } } + if err != nil { gp.Status = setting.PodFailed mutex.Lock() -- Gitee From feafa25982570491765458faac25d55cf9271157 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 30 Dec 2021 14:19:26 +0800 Subject: [PATCH 1150/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/pm.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 8128f10f..2c8a11d4 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -109,6 +109,7 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService for _, envconfig := range serviceTmpl.EnvConfigs { if envconfig.EnvName == envName { gp.EnvConfigs = []*models.EnvConfig{envconfig} + break } } -- Gitee From 7c9610d580e733484a931090c4158edede0ca42f Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 30 Dec 2021 14:41:34 +0800 Subject: [PATCH 1151/2647] optimize code Signed-off-by: allenshen --- pkg/util/url.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/util/url.go b/pkg/util/url.go index 4d4bb85e..09c212c4 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -23,11 +23,11 @@ import ( ) func TrimURLScheme(urlAddr string) string { - uri, err := url.Parse(urlAddr) + uri, err := url.Parse(strings.TrimSuffix(urlAddr, "/")) if err != nil { return urlAddr } - return strings.Join([]string{uri.Host, uri.Path}, "") + return uri.Host + uri.Path } // GetURLHostName ... -- Gitee From f93de2e2b089fe2f910641644e960218a7b1c577 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 30 Dec 2021 15:20:31 +0800 Subject: [PATCH 1152/2647] remove a feature which automatically add resource spec to the template Signed-off-by: Min Min --- .../core/environment/service/environment.go | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 1c9eb477..542e4ab5 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -37,7 +37,6 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" @@ -1770,8 +1769,6 @@ func upsertService(isUpdate bool, env *commonmodels.Product, switch res := obj.(type) { case *appsv1.Deployment: - // Inject resource request and limit - applySystemResourceRequirements(&res.Spec.Template.Spec) // Inject imagePullSecrets if qn-registry-secret is not set applySystemImagePullSecrets(&res.Spec.Template.Spec) @@ -1782,8 +1779,6 @@ func upsertService(isUpdate bool, env *commonmodels.Product, continue } case *appsv1.StatefulSet: - // Inject resource request and limit - applySystemResourceRequirements(&res.Spec.Template.Spec) // Inject imagePullSecrets if qn-registry-secret is not set applySystemImagePullSecrets(&res.Spec.Template.Spec) @@ -1816,7 +1811,6 @@ func upsertService(isUpdate bool, env *commonmodels.Product, obj.ObjectMeta.Labels = kube.MergeLabels(labels, obj.ObjectMeta.Labels) obj.Spec.Template.ObjectMeta.Labels = kube.MergeLabels(labels, obj.Spec.Template.ObjectMeta.Labels) - applySystemResourceRequirements(&obj.Spec.Template.Spec) // Inject imagePullSecrets if qn-registry-secret is not set applySystemImagePullSecrets(&obj.Spec.Template.Spec) @@ -1851,7 +1845,6 @@ func upsertService(isUpdate bool, env *commonmodels.Product, obj.Spec.JobTemplate.ObjectMeta.Labels = kube.MergeLabels(labels, obj.Spec.JobTemplate.ObjectMeta.Labels) obj.Spec.JobTemplate.Spec.Template.ObjectMeta.Labels = kube.MergeLabels(labels, obj.Spec.JobTemplate.Spec.Template.ObjectMeta.Labels) - applySystemResourceRequirements(&obj.Spec.JobTemplate.Spec.Template.Spec) // Inject imagePullSecrets if qn-registry-secret is not set applySystemImagePullSecrets(&obj.Spec.JobTemplate.Spec.Template.Spec) @@ -2194,35 +2187,6 @@ func applyUpdatedAnnotations(annotations map[string]string) map[string]string { return annotations } -func applySystemResourceRequirements(podSpec *corev1.PodSpec) { - for i, container := range podSpec.Containers { - - if container.Resources.Limits == nil { - podSpec.Containers[i].Resources.Limits = corev1.ResourceList{} - } - - if container.Resources.Limits.Cpu().String() == "0" { - podSpec.Containers[i].Resources.Limits[corev1.ResourceCPU] = resource.MustParse("500m") - } - - if container.Resources.Limits.Memory().String() == "0" { - podSpec.Containers[i].Resources.Limits[corev1.ResourceMemory] = resource.MustParse("200Mi") - } - - if container.Resources.Requests == nil { - podSpec.Containers[i].Resources.Requests = corev1.ResourceList{} - } - - if container.Resources.Requests.Cpu().String() == "0" { - podSpec.Containers[i].Resources.Requests[corev1.ResourceCPU] = resource.MustParse("10m") - } - - if container.Resources.Requests.Memory().String() == "0" { - podSpec.Containers[i].Resources.Requests[corev1.ResourceMemory] = resource.MustParse("100Mi") - } - } -} - func applySystemImagePullSecrets(podSpec *corev1.PodSpec) { for _, secret := range podSpec.ImagePullSecrets { if secret.Name == setting.DefaultImagePullSecret { -- Gitee From b7e2f71f425408597a182bcf0088523b9e766b75 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 30 Dec 2021 16:22:56 +0800 Subject: [PATCH 1153/2647] added picket filter api for workflow V3 to avoid authorization problem Signed-off-by: Min Min --- .../picket/client/aslan/workflow.go | 11 ++++++++++ .../picket/core/filter/handler/router.go | 1 + .../picket/core/filter/handler/workflow.go | 7 +++++++ .../picket/core/filter/service/workflow.go | 21 +++++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 3dc67a48..35d0601b 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -36,6 +36,17 @@ func (c *Client) ListWorkflows(header http.Header, qs url.Values) ([]byte, error return res.Body(), nil } +func (c *Client) ListWorkflowsV3(header http.Header, qs url.Values) ([]byte, error) { + url := "/workflow/v3" + + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} + func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.Values) ([]byte, error) { url := fmt.Sprintf("/workflow/workflow/testName/%s", testName) diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index b50e5a32..0bbf3c27 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -35,6 +35,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflows.GET("testName/:testName", ListTestWorkflows) workflows.GET("", ListWorkflows) + workflows.GET("v3", ListWorkflowsV3) } rolebindings := router.Group("rolebindings") diff --git a/pkg/microservice/picket/core/filter/handler/workflow.go b/pkg/microservice/picket/core/filter/handler/workflow.go index dde21a05..c405c869 100644 --- a/pkg/microservice/picket/core/filter/handler/workflow.go +++ b/pkg/microservice/picket/core/filter/handler/workflow.go @@ -37,3 +37,10 @@ func ListWorkflows(c *gin.Context) { ctx.Resp, ctx.Err = service.ListWorkflows(c.Request.Header, c.Request.URL.Query(), ctx.Logger) } + +func ListWorkflowsV3(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListWorkflowsV3(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/workflow.go b/pkg/microservice/picket/core/filter/service/workflow.go index d1d32122..6664fb57 100644 --- a/pkg/microservice/picket/core/filter/service/workflow.go +++ b/pkg/microservice/picket/core/filter/service/workflow.go @@ -54,6 +54,27 @@ func ListWorkflows(header http.Header, qs url.Values, logger *zap.SugaredLogger) return aslan.New().ListWorkflows(header, qs) } +func ListWorkflowsV3(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { + rules := []*rule{{ + method: "/api/aslan/workflow/workflow", + endpoint: "GET", + }} + names, err := getAllowedProjects(header, rules, config.AND, logger) + if err != nil { + logger.Errorf("Failed to get allowed project names, err: %s", err) + return nil, err + } + if len(names) == 0 { + return nil, nil + } + if !(len(names) == 1 && names[0] == "*") { + for _, name := range names { + qs.Add("projects", name) + } + } + return aslan.New().ListWorkflowsV3(header, qs) +} + func ListTestWorkflows(testName string, header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]byte, error) { rules := []*rule{{ method: "/api/aslan/workflow/workflow", -- Gitee From 5d04f54be4a773239f02f59e60b9e94c10e796d6 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 30 Dec 2021 16:35:01 +0800 Subject: [PATCH 1154/2647] optimize error display when pushing/pulling image for helm delivery version Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 16 +++-- .../packager/core/service/service.go | 67 +++++++++---------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 95ed3af3..7c1fe3e4 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -677,13 +677,6 @@ func buildChartPackage(chartData *DeliveryChartData, product *commonmodels.Produ if err != nil { return errors.Wrapf(err, "failed to read values.yaml for service %s", serviceObj.ServiceName) } - err = yaml.Unmarshal(valueYamlContent, &valuesYamlData) - if err != nil { - return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) - } - - // hold the currently running yaml data - chartData.ValuesInEnv = valuesYamlData // write values.yaml file before load if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly @@ -698,6 +691,15 @@ func buildChartPackage(chartData *DeliveryChartData, product *commonmodels.Produ return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) } } + + err = yaml.Unmarshal(valueYamlContent, &valuesYamlData) + if err != nil { + return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) + } + + // hold the currently running yaml data + chartData.ValuesInEnv = valuesYamlData + err = os.WriteFile(valuesFilePath, valueYamlContent, 0644) if err != nil { return errors.Wrapf(err, "failed to write values.yaml file for service %s", serviceObj.ServiceName) diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index ba4e036e..2659a6d1 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -30,6 +30,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" + "github.com/docker/docker/pkg/jsonmessage" "github.com/pkg/errors" "gopkg.in/yaml.v3" @@ -49,13 +50,13 @@ type PackageResult struct { } func NewPackager() (*Packager, error) { - context, err := os.ReadFile(config.JobConfigFile()) + contextData, err := os.ReadFile(config.JobConfigFile()) if err != nil { return nil, err } var ctx *Context - if err := yaml.Unmarshal(context, &ctx); err != nil { + if err := yaml.Unmarshal(contextData, &ctx); err != nil { return nil, err } @@ -94,55 +95,53 @@ func buildTargetImage(imageName, imageTag, host, nameSpace string) string { return ret } +func ExtractErrorDetail(in io.Reader) error { + dec := json.NewDecoder(in) + for { + var jm jsonmessage.JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + + if jm.Error != nil { + return jm.Error + } + } + return nil +} + func pullImage(dockerClient *client.Client, imageUrl string, options *types.ImagePullOptions) error { + + log.Infof("pulling image: %s", imageUrl) + // pull image pullResponse, err := dockerClient.ImagePull(context.TODO(), imageUrl, *options) if err != nil { return err } - defer func(pullResponse io.ReadCloser) { - err := pullResponse.Close() - if err != nil { - log.Errorf("failed to close response reader") - } - }(pullResponse) - - bs, err := io.ReadAll(pullResponse) - if err != nil { - return err - } + defer pullResponse.Close() - if strings.Contains(string(bs), "error") { - log.Errorf("image push failed: %s", string(bs)) - return fmt.Errorf("failed to push image") - } - return nil + err = ExtractErrorDetail(pullResponse) + return err } func pushImage(dockerClient *client.Client, targetImageUrl string, options *types.ImagePushOptions) error { + + log.Infof("pushing image: %s", targetImageUrl) + pushResponse, err := dockerClient.ImagePush(context.TODO(), targetImageUrl, *options) if err != nil { return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) } - defer func(pullResponse io.ReadCloser) { - err := pullResponse.Close() - if err != nil { - log.Errorf("failed to close response reader") - } - }(pushResponse) - - bs, err := io.ReadAll(pushResponse) - if err != nil { - return err - } + defer pushResponse.Close() - if strings.Contains(string(bs), "error") { - log.Errorf("image push failed: %s", string(bs)) - return fmt.Errorf("failed to push image") - } - return nil + err = ExtractErrorDetail(pushResponse) + return err } func handleSingleService(imageByService *ImagesByService, allRegistries map[string]*DockerRegistry, targetRegistries []*DockerRegistry, dockerClient *client.Client) ([]*ImageData, error) { -- Gitee From 0ea5f34b620093dc9a7a60acc8b295d25db760e6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 30 Dec 2021 17:05:26 +0800 Subject: [PATCH 1155/2647] Clusters can be selected in the project Signed-off-by: liu deyi --- .../common/repository/models/k8s_cluster.go | 4 +- .../models/project_cluster_relation.go | 17 +++ .../repository/models/template/product.go | 1 + .../mongodb/projectClusterRelation.go | 101 ++++++++++++++++++ .../core/multicluster/handler/clusters.go | 2 +- .../core/multicluster/service/clusters.go | 48 ++++++++- .../aslan/core/project/service/product.go | 17 ++- pkg/microservice/aslan/core/service.go | 1 + 8 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 81668968..2ce8dc18 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -57,8 +57,8 @@ type K8SClusterInfo struct { } type AdvancedConfig struct { - Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` - NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` } type NodeSelectorRequirement struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go b/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go new file mode 100644 index 00000000..ab5301a9 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go @@ -0,0 +1,17 @@ +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type ProjectClusterRelation struct { + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + ProjectName string `json:"project_name" bson:"project_name"` + ClusterID string `json:"cluster_id" bson:"cluster_id"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` +} + +func (ProjectClusterRelation) TableName() string { + return "project_cluster_relation" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 19e15377..29d72c43 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -64,6 +64,7 @@ type Product struct { LatestWorkflowUpdateTime int64 `bson:"-" json:"latest_workflow_update_time"` LatestWorkflowUpdateBy string `bson:"-" json:"latest_workflow_update_by"` TotalEnvTemplateServiceNum int `bson:"-" json:"total_env_template_service_num"` + ClusterIDs []string `bson:"-" json:"cluster_ids"` IsOpensource bool `bson:"is_opensource" json:"is_opensource"` CustomImageRule *CustomRule `bson:"custom_image_rule,omitempty" json:"custom_image_rule,omitempty"` CustomTarRule *CustomRule `bson:"custom_tar_rule,omitempty" json:"custom_tar_rule,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go b/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go new file mode 100644 index 00000000..32730a1c --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go @@ -0,0 +1,101 @@ +package mongodb + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ProjectClusterRelationColl struct { + *mongo.Collection + + coll string +} + +func NewProjectClusterRelationColl() *ProjectClusterRelationColl { + name := models.ProjectClusterRelation{}.TableName() + return &ProjectClusterRelationColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ProjectClusterRelationColl) GetCollectionName() string { + return c.coll +} + +func (c *ProjectClusterRelationColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "cluster_id", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + return err +} + +func (c *ProjectClusterRelationColl) Create(args *models.ProjectClusterRelation) error { + if args == nil { + return errors.New("nil PrivateKey info") + } + + args.CreatedAt = time.Now().Unix() + _, err := c.InsertOne(context.TODO(), args) + + return err +} + +type ProjectClusterRelationOption struct { + ProjectName string + ClusterID string +} + +func (c *ProjectClusterRelationColl) List(opt *ProjectClusterRelationOption) ([]*models.ProjectClusterRelation, error) { + query := bson.M{} + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + + if opt.ClusterID != "" { + query["cluster_id"] = opt.ClusterID + } + + var resp []*models.ProjectClusterRelation + ctx := context.Background() + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *ProjectClusterRelationColl) Delete(opt *ProjectClusterRelationOption) error { + query := bson.M{} + if opt.ProjectName != "" { + query["project_name"] = opt.ProjectName + } + + if opt.ClusterID != "" { + query["cluster_id"] = opt.ClusterID + } + + _, err := c.DeleteMany(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index ca1ed744..1c9f4554 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -37,7 +37,7 @@ func ListClusters(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.ListClusters(clusters, ctx.Logger) + ctx.Resp, ctx.Err = service.ListClusters(clusters, c.Query("projectName"), ctx.Logger) } func GetCluster(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index dc7f954a..c915dbbe 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -52,8 +52,9 @@ type K8SCluster struct { } type AdvancedConfig struct { - Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` - NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []string `json:"node_labels,omitempty" bson:"node_labels,omitempty"` + ProjectNames []string `json:"project_names" bson:"project_names"` } func (k *K8SCluster) Clean() error { @@ -67,8 +68,16 @@ func (k *K8SCluster) Clean() error { return nil } -func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { +func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { idSet := sets.NewString(ids...) + if projectName != "" { + projectClusterRelations, _ := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ + ProjectName: projectName, + }) + for _, projectClusterRelation := range projectClusterRelations { + idSet.Insert(projectClusterRelation.ClusterID) + } + } cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: idSet.UnsortedList()}) if err != nil { logger.Errorf("Failed to list clusters, err: %s", err) @@ -78,10 +87,12 @@ func ListClusters(ids []string, logger *zap.SugaredLogger) ([]*K8SCluster, error var res []*K8SCluster for _, c := range cs { var advancedConfig *AdvancedConfig + if c.AdvancedConfig != nil { advancedConfig = &AdvancedConfig{ - Strategy: c.AdvancedConfig.Strategy, - NodeLabels: convertToNodeLabels(c.AdvancedConfig.NodeLabels), + Strategy: c.AdvancedConfig.Strategy, + NodeLabels: convertToNodeLabels(c.AdvancedConfig.NodeLabels), + ProjectNames: getProjectNames(c.ID.Hex(), logger), } } res = append(res, &K8SCluster{ @@ -107,6 +118,18 @@ func GetCluster(id string, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, return s.GetCluster(id, logger) } +func getProjectNames(clusterID string, logger *zap.SugaredLogger) (projectNames []string) { + projectClusterRelations, err := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ClusterID: clusterID}) + if err != nil { + logger.Errorf("Failed to list projectClusterRelation, err:%s", err) + return []string{} + } + for _, projectClusterRelation := range projectClusterRelations { + projectNames = append(projectNames, projectClusterRelation.ProjectName) + } + return projectNames +} + func convertToNodeLabels(nodeSelectorRequirements []*commonmodels.NodeSelectorRequirement) []string { var nodeLabels []string for _, nodeSelectorRequirement := range nodeSelectorRequirements { @@ -168,6 +191,21 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com if args.AdvancedConfig != nil { advancedConfig.Strategy = args.AdvancedConfig.Strategy advancedConfig.NodeLabels = convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels) + // Delete all projects associated with clusterID + err := commonrepo.NewProjectClusterRelationColl().Delete(&commonrepo.ProjectClusterRelationOption{ClusterID: id}) + if err != nil { + logger.Errorf("Failed to delete projectClusterRelation err:%s", err) + } + for _, projectName := range args.AdvancedConfig.ProjectNames { + err = commonrepo.NewProjectClusterRelationColl().Create(&commonmodels.ProjectClusterRelation{ + ProjectName: projectName, + ClusterID: id, + CreatedBy: args.CreatedBy, + }) + if err != nil { + logger.Errorf("Failed to create projectClusterRelation err:%s", err) + } + } } cluster := &commonmodels.K8SCluster{ Name: args.Name, diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 91a43e32..b11697d8 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -93,7 +93,7 @@ func ListOpenSourceProduct(log *zap.SugaredLogger) ([]*template.Product, error) // CreateProductTemplate 创建产品模板 func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err error) { kvs := args.Vars - // 不保存vas + // do not save vars args.Vars = nil err = commonservice.ValidateKVs(kvs, args.AllServiceInfos(), log) @@ -105,6 +105,21 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err return e.ErrCreateProduct.AddDesc(err.Error()) } + err = commonrepo.NewProjectClusterRelationColl().Delete(&commonrepo.ProjectClusterRelationOption{ProjectName: args.ProductName}) + if err != nil { + log.Errorf("Failed to delete projectClusterRelation, err:%s", err) + } + for _, clusterID := range args.ClusterIDs { + err = commonrepo.NewProjectClusterRelationColl().Create(&commonmodels.ProjectClusterRelation{ + ProjectName: args.ProductName, + ClusterID: clusterID, + CreatedBy: args.UpdateBy, + }) + if err != nil { + log.Errorf("Failed to delete projectClusterRelation, err:%s", err) + } + } + err = templaterepo.NewProductColl().Create(args) if err != nil { log.Errorf("ProductTmpl.Create error: %v", err) diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 2209c7eb..1dbece5a 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -215,6 +215,7 @@ func initDatabase() { systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), + commonrepo.NewProjectClusterRelationColl(), } { wg.Add(1) go func(r indexer) { -- Gitee From a51887b2d94216b48c5626493f8ef4e3c3f22cfd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 30 Dec 2021 17:07:02 +0800 Subject: [PATCH 1156/2647] Clusters can be selected in the project Signed-off-by: liu deyi --- .../models/project_cluster_relation.go | 16 ++++++++++++++++ .../repository/mongodb/projectClusterRelation.go | 16 ++++++++++++++++ .../workflow/service/webhook/trigger_yaml.go | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go b/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go index ab5301a9..7f6fd422 100644 --- a/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go +++ b/pkg/microservice/aslan/core/common/repository/models/project_cluster_relation.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import ( diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go b/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go index 32730a1c..bf3889fa 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go index 80a3d55c..0658e3bf 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package webhook import ( -- Gitee From 1cf3004520ba20852db7804805339e4fdb9a3bc8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 30 Dec 2021 17:09:27 +0800 Subject: [PATCH 1157/2647] modify annotation Signed-off-by: liu deyi --- .../core/common/repository/mongodb/projectClusterRelation.go | 2 +- pkg/microservice/aslan/core/project/service/product.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go b/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go index bf3889fa..00f9016a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go @@ -63,7 +63,7 @@ func (c *ProjectClusterRelationColl) EnsureIndex(ctx context.Context) error { func (c *ProjectClusterRelationColl) Create(args *models.ProjectClusterRelation) error { if args == nil { - return errors.New("nil PrivateKey info") + return errors.New("nil projectClusterRelation info") } args.CreatedAt = time.Now().Unix() diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index b11697d8..5b0a8043 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -116,7 +116,7 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err CreatedBy: args.UpdateBy, }) if err != nil { - log.Errorf("Failed to delete projectClusterRelation, err:%s", err) + log.Errorf("Failed to create projectClusterRelation, err:%s", err) } } -- Gitee From cbdb77116ba89368b7a9ce244f1f02df427242a5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 30 Dec 2021 20:05:26 +0800 Subject: [PATCH 1158/2647] add GetCollaborationUpdate Signed-off-by: panxunying --- .../models/collaboration_instance.go | 4 +- .../repository/models/collaboration_mode.go | 4 +- .../mongodb/collaboration_instance.go | 23 ++- .../repository/mongodb/collaboration_mode.go | 29 +++ .../service/collaboration_instance.go | 179 +++++++++++++++++- 5 files changed, 227 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index c0b7fea1..c5c679cf 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -29,8 +29,8 @@ type CollaborationInstance struct { Revision int64 `bson:"revision" json:"revision"` User string `bson:"user" json:"user"` PolicyName string `bson:"policy_name" json:"policy_name"` - Workflows []ProductCIItem `json:"workflows" bson:"workflows"` - Products []WorkflowCIItem `bson:"products" json:"products"` + Workflows []WorkflowCIItem `json:"workflows" bson:"workflows"` + Products []ProductCIItem `bson:"products" json:"products"` CreateBy string `bson:"create_by" json:"create_by"` UpdateBy string `bson:"update_by" json:"update_by"` } diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go index 460fae2e..790b9cda 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -30,8 +30,8 @@ type CollaborationMode struct { Revision int64 `bson:"revision" json:"revision"` Members []string `bson:"members" json:"members"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - Workflows []ProductCMItem `json:"workflows" bson:"workflows"` - Products []WorkflowCMItem `bson:"products" json:"products"` + Workflows []WorkflowCMItem `json:"workflows" bson:"workflows"` + Products []ProductCMItem `bson:"products" json:"products"` CreateBy string `bson:"create_by" json:"create_by"` UpdateBy string `bson:"update_by" json:"update_by"` } diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index d431050b..a35c69b3 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -47,6 +47,23 @@ func (c *CollaborationInstanceColl) GetCollectionName() string { return c.coll } +func (c *CollaborationInstanceColl) EnsureIndex(ctx context.Context) error { + mod := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "collaboration_name", Value: 1}, + bson.E{Key: "user_uid", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mod) + + return err +} + func (c *CollaborationInstanceColl) Find(opt *CollaborationInstanceFindOptions) (*models.CollaborationInstance, error) { res := &models.CollaborationInstance{} query := bson.M{} @@ -60,11 +77,11 @@ func (c *CollaborationInstanceColl) Find(opt *CollaborationInstanceFindOptions) return res, err } -func (c *CollaborationInstanceColl) List(opt *CollaborationModeFindOptions) ([]*models.CollaborationInstance, error) { +func (c *CollaborationInstanceColl) List(opt *CollaborationInstanceFindOptions) ([]*models.CollaborationInstance, error) { var ret []*models.CollaborationInstance query := bson.M{} - if opt.Name != "" { - query["name"] = opt.Name + if opt.UserUID != "" { + query["user_uid"] = opt.UserUID } if opt.ProjectName != "" { query["project_name"] = opt.ProjectName diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index b8fddae2..f64951b3 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -33,6 +33,7 @@ import ( type CollaborationModeFindOptions struct { ProjectName string Name string + Members string } type CollaborationModeColl struct { @@ -50,6 +51,31 @@ func (c *CollaborationModeColl) GetCollectionName() string { return c.coll } +func (c *CollaborationModeColl) EnsureIndex(ctx context.Context) error { + mod := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "members", Value: 1}, + bson.E{Key: "is_deleted", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + { + Keys: bson.D{ + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "name", Value: 1}, + bson.E{Key: "is_deleted", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mod) + + return err +} + func (c *CollaborationModeColl) Update(username string, args *models.CollaborationMode) error { if args == nil { return errors.New("nil CollaborationMode") @@ -149,6 +175,9 @@ func (c *CollaborationModeColl) List(opt *CollaborationModeFindOptions) ([]*mode if opt.ProjectName != "" { query["project_name"] = opt.ProjectName } + if opt.Members != "" { + query["members"] = opt.Members + } query["is_deleted"] = false ctx := context.Background() diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index ac6bf63c..b5558bb4 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1,21 +1,190 @@ package service import ( + "fmt" + "reflect" + "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" ) -func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) ([]*models.CollaborationInstance, error) { +type GetCollaborationUpdateResp struct { + Update []UpdateItem `json:"update"` + New []models.CollaborationMode `json:"new"` + Delete []models.CollaborationInstance `json:"delete"` +} +type UpdateItem struct { + CollaborationMode string `json:"collaboration_mode"` + DeleteSpec DeleteSpec `json:"delete_spec"` + UpdateSpec UpdateSpec `json:"update_spec"` + NewSpec NewSpec `json:"new_spec"` +} +type DeleteSpec struct { + Workflows []models.WorkflowCIItem `json:"workflows"` + Products []models.ProductCIItem `json:"products"` +} +type UpdateSpec struct { + Workflows []UpdateWorkflowItem `json:"workflows"` + Products []UpdateProductItem `json:"products"` +} +type NewSpec struct { + Workflows []models.WorkflowCMItem `json:"workflows"` + Products []models.ProductCMItem `json:"products"` +} + +type UpdateWorkflowItem struct { + Old models.WorkflowCMItem `json:"old"` + New models.WorkflowCIItem `json:"new"` +} + +type UpdateProductItem struct { + Old models.ProductCMItem `json:"old"` + New models.ProductCIItem `json:"new"` +} + +func getUpdateWorkflowDiff(cmwMap map[string]models.WorkflowCMItem, ciwMap map[string]models.WorkflowCIItem) ( + []UpdateWorkflowItem, []models.WorkflowCMItem, []models.WorkflowCIItem) { + var updateWorkflowItems []UpdateWorkflowItem + var newWorkflowItems []models.WorkflowCMItem + var deleteWorkflowItems []models.WorkflowCIItem + for name, cm := range cmwMap { + if ci, ok := ciwMap[name]; ok { + if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { + updateWorkflowItems = append(updateWorkflowItems, UpdateWorkflowItem{ + Old: cm, + New: ci, + }) + } + } else { + newWorkflowItems = append(newWorkflowItems, cm) + } + } + for name, ci := range ciwMap { + if cm, ok := cmwMap[name]; ok { + if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { + deleteWorkflowItems = append(deleteWorkflowItems, ci) + } + } + } + return updateWorkflowItems, newWorkflowItems, deleteWorkflowItems +} + +func getUpdateProductDiff(cmpMap map[string]models.ProductCMItem, cipMap map[string]models.ProductCIItem) ( + []UpdateProductItem, []models.ProductCMItem, []models.ProductCIItem) { + var updateProductItems []UpdateProductItem + var newProductItems []models.ProductCMItem + var deleteProductItems []models.ProductCIItem + for name, cm := range cmpMap { + if ci, ok := cipMap[name]; ok { + if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { + updateProductItems = append(updateProductItems, UpdateProductItem{ + Old: cm, + New: ci, + }) + } + } else { + newProductItems = append(newProductItems, cm) + } + } + for name, ci := range cipMap { + if cm, ok := cmpMap[name]; ok { + if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { + deleteProductItems = append(deleteProductItems, ci) + } + } + } + return updateProductItems, newProductItems, deleteProductItems +} + +func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstance) UpdateItem { + cmwMap := make(map[string]models.WorkflowCMItem) + for _, workflow := range cm.Workflows { + cmwMap[workflow.Name] = workflow + } + ciwMap := make(map[string]models.WorkflowCIItem) + for _, workflow := range ci.Workflows { + ciwMap[workflow.Name] = workflow + } + updateWorkflowItems, newWorkflowItems, deleteWorkflowItems := getUpdateWorkflowDiff(cmwMap, ciwMap) + cmpMap := make(map[string]models.ProductCMItem) + for _, product := range cm.Products { + cmpMap[product.Name] = product + } + cipMap := make(map[string]models.ProductCIItem) + for _, product := range ci.Products { + cipMap[product.Name] = product + } + updateProductItems, newProductItems, deleteProductItems := getUpdateProductDiff(cmpMap, cipMap) + return UpdateItem{ + NewSpec: NewSpec{ + Workflows: newWorkflowItems, + Products: newProductItems, + }, + UpdateSpec: UpdateSpec{ + Workflows: updateWorkflowItems, + Products: updateProductItems, + }, + DeleteSpec: DeleteSpec{ + Workflows: deleteWorkflowItems, + Products: deleteProductItems, + }, + } +} + +func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*models.CollaborationInstance) (*GetCollaborationUpdateResp, error) { + var updateItems []UpdateItem + var newItems []models.CollaborationMode + var deleteItems []models.CollaborationInstance + for name, cm := range cmMap { + if ci, ok := ciMap[name]; ok { + if cm.Revision < ci.Revision { + return nil, fmt.Errorf("CollaborationMode:%s revision error", name) + } else if cm.Revision > ci.Revision { + updateItems = append(updateItems, getUpdateDiff(cm, ci)) + } + } else { + newItems = append(newItems, *cm) + } + } + for name, ci := range ciMap { + if _, ok := cmMap[name]; !ok { + deleteItems = append(deleteItems, *ci) + } + } + return &GetCollaborationUpdateResp{ + Update: updateItems, + New: newItems, + Delete: deleteItems, + }, nil +} + +func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeFindOptions{ ProjectName: projectName, + Members: uid, }) if err != nil { - logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) + logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err } - return &GetCollaborationModeResp{ - Collaborations: collaborations, - }, nil + cmMap := make(map[string]*models.CollaborationMode) + for _, collaboration := range collaborations { + cmMap[collaboration.Name] = collaboration + } + collaborationInstances, err := mongodb.NewCollaborationInstanceColl().List(&mongodb.CollaborationInstanceFindOptions{ + ProjectName: projectName, + UserUID: uid, + }) + ciMap := make(map[string]*models.CollaborationInstance) + for _, instance := range collaborationInstances { + ciMap[instance.CollaborationName] = instance + } + resp, err := getDiff(cmMap, ciMap) + if err != nil { + logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) + return nil, err + } + return resp, nil } -- Gitee From 6bc7fd71c6945e62f99f5dcca16f00b0390b3f7d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 30 Dec 2021 22:17:26 +0800 Subject: [PATCH 1159/2647] delete project cluster relation when deleting project or clutster Signed-off-by: liu deyi --- .../core/multicluster/service/clusters.go | 4 ++++ .../aslan/core/project/service/product.go | 23 +++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index c915dbbe..16a2013b 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -231,6 +231,10 @@ func DeleteCluster(username, clusterID string, logger *zap.SugaredLogger) error s, _ := kube.NewService("") + if err = commonrepo.NewProjectClusterRelationColl().Delete(&commonrepo.ProjectClusterRelationOption{ClusterID: clusterID}); err != nil { + logger.Errorf("Failed to delete projectClusterRelation err:%s", err) + } + return s.DeleteCluster(username, clusterID, logger) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 5b0a8043..64de1b48 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -361,23 +361,23 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug envs, _ := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) for _, env := range envs { if err = commonrepo.NewProductColl().UpdateStatus(env.EnvName, productName, setting.ProductStatusDeleting); err != nil { - log.Errorf("DeleteProductTemplate Update product Status error: %v", err) + log.Errorf("DeleteProductTemplate Update product Status error: %s", err) return e.ErrDeleteProduct } } if err = commonservice.DeleteRenderSet(productName, log); err != nil { - log.Errorf("DeleteProductTemplate DeleteRenderSet err: %v", err) + log.Errorf("DeleteProductTemplate DeleteRenderSet err: %s", err) return err } if err = DeleteTestModules(productName, requestID, log); err != nil { - log.Errorf("DeleteProductTemplate Delete productName %s test err: %v", productName, err) + log.Errorf("DeleteProductTemplate Delete productName %s test err: %s", productName, err) return err } if err = commonservice.DeleteWorkflows(productName, requestID, log); err != nil { - log.Errorf("DeleteProductTemplate Delete productName %s workflow err: %v", productName, err) + log.Errorf("DeleteProductTemplate Delete productName %s workflow err: %s", productName, err) return err } @@ -387,28 +387,33 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } if err = commonservice.DeletePipelines(productName, requestID, log); err != nil { - log.Errorf("DeleteProductTemplate Delete productName %s pipeline err: %v", productName, err) + log.Errorf("DeleteProductTemplate Delete productName %s pipeline err: %s", productName, err) return err } - //删除自由编排工作流 + // delete projectClusterRelation + if err = commonrepo.NewProjectClusterRelationColl().Delete(&commonrepo.ProjectClusterRelationOption{ProjectName: productName}); err != nil { + log.Errorf("DeleteProductTemplate Delete productName %s ProjectClusterRelation err: %s", productName, err) + } + + // Delete freestyle workflow cl := configclient.New(configbase.ConfigServiceAddress()) if enable, err := cl.CheckFeature(setting.ModernWorkflowType); err == nil && enable { collieClient := collie.New(config.CollieAPIAddress()) if err = collieClient.DeleteCIPipelines(productName, log); err != nil { - log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %v", productName, err) + log.Errorf("DeleteProductTemplate Delete productName %s freestyle pipeline err: %s", productName, err) } } err = templaterepo.NewProductColl().Delete(productName) if err != nil { - log.Errorf("ProductTmpl.Delete error: %v", err) + log.Errorf("ProductTmpl.Delete error: %s", err) return e.ErrDeleteProduct } err = commonrepo.NewCounterColl().Delete(fmt.Sprintf("product:%s", productName)) if err != nil { - log.Errorf("Counter.Delete error: %v", err) + log.Errorf("Counter.Delete error: %s", err) return err } -- Gitee From a677888b801bf8253440025972edd1711187a85b Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 29 Dec 2021 15:00:02 +0800 Subject: [PATCH 1160/2647] Added ECR support for zadig Signed-off-by: Min Min --- pkg/microservice/aslan/config/consts.go | 3 +- .../repository/models/registry_namespace.go | 7 +- .../aslan/core/common/service/registry.go | 107 +++++++++++++---- .../core/common/service/registry/service.go | 110 +++++++++++++++++- .../aslan/core/delivery/service/version.go | 2 +- .../core/environment/service/environment.go | 2 +- .../aslan/core/environment/service/product.go | 2 +- .../aslan/core/system/handler/registry.go | 19 ++- .../aslan/core/system/service/registry.go | 81 +++++-------- .../service/workflow/artifact_task.go | 11 +- .../workflow/service/workflow/nsq_handlers.go | 4 +- .../service/workflow/pipeline_task.go | 8 +- .../service/workflow/pipeline_validation.go | 2 +- .../service/workflow/workflow_task.go | 28 ++--- 14 files changed, 252 insertions(+), 134 deletions(-) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 0e4e487b..76a93ce5 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -202,7 +202,8 @@ const ( ) const ( - SWRProvider = "swr" + RegistryTypeSWR = "swr" + RegistryTypeAWS = "ecr" ) const ( diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index 707493e3..e3b7e193 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -20,6 +20,8 @@ import ( "errors" "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" ) type RegistryNamespace struct { @@ -28,7 +30,7 @@ type RegistryNamespace struct { RegType string `bson:"reg_type" json:"reg_type"` RegProvider string `bson:"reg_provider" json:"reg_provider"` IsDefault bool `bson:"is_default" json:"is_default"` - Namespace string `bson:"namespace" json:"namespace"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` AccessKey string `bson:"access_key" json:"access_key"` SecretKey string `bson:"secret_key" json:"secret_key"` Region string `bson:"region,omitempty" json:"region,omitempty"` @@ -46,7 +48,8 @@ func (ns *RegistryNamespace) Validate() error { return errors.New("empty reg_provider") } - if ns.Namespace == "" { + // if the registry type is aws ECR, then the RegAddr is the registry's full + if ns.Namespace == "" && ns.RegProvider != config.RegistryTypeAWS { return errors.New("empty namespace") } diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 62c35be8..4af487a5 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -17,8 +17,15 @@ limitations under the License. package service import ( + "encoding/base64" + "errors" "fmt" + "strings" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client" @@ -30,11 +37,11 @@ import ( "github.com/koderover/zadig/pkg/util" ) -func FindRegistryById(registryId string, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { - return findRegisty(&mongodb.FindRegOps{ID: registryId}, log) +func FindRegistryById(registryId string, getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { + return findRegisty(&mongodb.FindRegOps{ID: registryId}, getRealCredential, log) } -func findRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { +func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { // TODO: 多租户适配 resp, err := mongodb.NewRegistryNamespaceColl().Find(regOps) @@ -48,37 +55,58 @@ func findRegisty(regOps *mongodb.FindRegOps, log *zap.SugaredLogger) (*models.Re } } - ak := resp.AccessKey - sk := resp.SecretKey - if resp.RegProvider == config.SWRProvider { - ak = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) - sk = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) + if getRealCredential { + switch resp.RegProvider { + case config.RegistryTypeSWR: + resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) + resp.SecretKey = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) + case config.RegistryTypeAWS: + realAK, realSK, err := getAWSRegistryCredential(resp.AccessKey, resp.SecretKey, resp.Region) + if err != nil { + log.Errorf("Failed to get keypair from aws, the error is: %s", err) + return nil, err + } + resp.AccessKey = realAK + resp.SecretKey = realSK + default: + break + } } - resp.AccessKey = ak - resp.SecretKey = sk return resp, nil } -func FindDefaultRegistry(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { - return findRegisty(&mongodb.FindRegOps{IsDefault: true}, log) -} - -func GetDefaultRegistryNamespace(log *zap.SugaredLogger) (*models.RegistryNamespace, error) { - resp, err := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{IsDefault: true}) - if err != nil { - log.Errorf("get default registry error: %s", err) - return resp, fmt.Errorf("get default registry error: %s", err) - } - return resp, nil +func FindDefaultRegistry(getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { + return findRegisty(&mongodb.FindRegOps{IsDefault: true}, getRealCredential, log) } -func ListRegistryNamespaces(log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { +func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) if err != nil { log.Errorf("RegistryNamespace.List error: %s", err) return resp, fmt.Errorf("RegistryNamespace.List error: %s", err) } + + if getRealCredential { + for _, reg := range resp { + switch reg.RegProvider { + case config.RegistryTypeSWR: + reg.AccessKey = fmt.Sprintf("%s@%s", reg.Region, reg.AccessKey) + reg.SecretKey = util.ComputeHmacSha256(reg.AccessKey, reg.SecretKey) + case config.RegistryTypeAWS: + realAK, realSK, err := getAWSRegistryCredential(reg.AccessKey, reg.SecretKey, reg.Region) + if err != nil { + log.Errorf("Failed to get keypair from aws, the error is: %s", err) + return nil, err + } + reg.AccessKey = realAK + reg.SecretKey = realSK + default: + break + } + } + } + return resp, nil } @@ -86,7 +114,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient var reg *models.RegistryNamespace var err error if len(registryId) > 0 { - reg, err = FindRegistryById(registryId, log) + reg, err = FindRegistryById(registryId, true, log) if err != nil { log.Errorf( "service.EnsureRegistrySecret: failed to find registry: %s error msg:%s", @@ -95,7 +123,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient return err } } else { - reg, err = FindDefaultRegistry(log) + reg, err = FindDefaultRegistry(true, log) if err != nil { log.Errorf( "service.EnsureRegistrySecret: failed to find default candidate registry: %s %s", @@ -113,3 +141,34 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient return nil } + +// TODO: implement in-memory cache for this function to avoid rate limits. +func getAWSRegistryCredential(AK, SK, Region string) (string, string, error) { + creds := credentials.NewStaticCredentials(AK, SK, "") + config := &aws.Config{ + Region: aws.String(Region), + Credentials: creds, + } + sess, err := session.NewSession(config) + if err != nil { + return "", "", err + } + svc := ecr.New(sess) + input := &ecr.GetAuthorizationTokenInput{} + + result, err := svc.GetAuthorizationToken(input) + if err != nil { + return "", "", err + } + // since the new AWS ECR will give a token that has access to ALL the repository, we use the first token + encodedToken := *result.AuthorizationData[0].AuthorizationToken + rawDecodedText, err := base64.StdEncoding.DecodeString(encodedToken) + if err != nil { + return "", "", err + } + keypair := strings.Split(string(rawDecodedText), ":") + if len(keypair) != 2 { + return "", "", errors.New("decode keypair from aws response error") + } + return keypair[0], keypair[1], nil +} diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 91fb00b0..43e1aadf 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -28,6 +28,10 @@ import ( "sync" "time" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" "github.com/docker/distribution" "github.com/docker/distribution/manifest/schema2" "github.com/docker/distribution/reference" @@ -77,8 +81,10 @@ type Service interface { func NewV2Service(provider string) Service { switch provider { - case config.SWRProvider: + case config.RegistryTypeSWR: return &SwrService{} + case config.RegistryTypeAWS: + return &EcrService{} default: return &v2RegistryService{} } @@ -451,3 +457,105 @@ func (s *SwrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.Suga return &commonmodels.DeliveryImage{}, nil } + +type EcrService struct { +} + +func (s *EcrService) getECRService(ep Endpoint, log *zap.SugaredLogger) (*ecr.ECR, error) { + creds := credentials.NewStaticCredentials(ep.Ak, ep.Sk, "") + config := &aws.Config{ + Region: aws.String(ep.Region), + Credentials: creds, + } + sess, err := session.NewSession(config) + if err != nil { + log.Errorf("Failed to create aws session, err: %s", err) + return nil, err + } + return ecr.New(sess), nil +} + +func (s *EcrService) ListRepoImages(option ListRepoImagesOption, log *zap.SugaredLogger) (resp *ReposResp, err error) { + svc, err := s.getECRService(option.Endpoint, log) + if err != nil { + return nil, err + } + + var wg wait.Group + var mutex sync.RWMutex + resp = &ReposResp{Total: len(option.Repos)} + resultChan := make(chan *Repo) + defer close(resultChan) + + for _, repo := range option.Repos { + name := repo + wg.Start(func() { + input := &ecr.BatchGetImageInput{ + RepositoryName: aws.String(name), + } + result, err := svc.BatchGetImage(input) + if err != nil { + log.Errorf("Failed to get image information from aws, error: %s", err) + return + } + var koderoverTags, customTags, sortedTags []string + for _, image := range result.Images { + if *image.RepositoryName != name { + continue + } + tagArray := strings.Split(*image.ImageId.ImageTag, "-") + if len(tagArray) > 1 && len(tagArray[0]) == 14 { + if _, err := time.Parse("20060102150405", tagArray[0]); err == nil { + koderoverTags = append(koderoverTags, *image.ImageId.ImageTag) + continue + } + } + customTags = append(customTags, *image.ImageId.ImageTag) + } + + sort.Sort(sort.Reverse(sort.StringSlice(koderoverTags))) + sortedTags = append(koderoverTags, customTags...) + + mutex.Lock() + resp.Repos = append(resp.Repos, &Repo{ + Name: name, + Tags: sortedTags, + }) + mutex.Unlock() + }) + + } + wg.Wait() + + return resp, nil +} + +func (s *EcrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (di *commonmodels.DeliveryImage, err error) { + svc, err := s.getECRService(option.Endpoint, log) + if err != nil { + return nil, err + } + input := &ecr.DescribeImagesInput{ + ImageIds: []*ecr.ImageIdentifier{ + { + ImageTag: aws.String(option.Tag), + }, + }, + RepositoryName: aws.String(option.Image), + } + result, err := svc.DescribeImages(input) + if err != nil { + err = errors.Wrapf(err, "failed to get image info of %s:%s", option.Image, option.Tag) + return + } + for _, imageDetail := range result.ImageDetails { + return &commonmodels.DeliveryImage{ + RepoName: option.Image, + TagName: option.Tag, + CreationTime: imageDetail.ImagePushedAt.String(), + ImageDigest: *imageDetail.ImageDigest, + ImageSize: *imageDetail.ImageSizeInBytes, + }, nil + } + return &commonmodels.DeliveryImage{}, nil +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 95ed3af3..38a6ce4a 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -833,7 +833,7 @@ func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productI } func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { - registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + registries, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) if err != nil { return nil, fmt.Errorf("failed to query registries") } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 542e4ab5..8c703a96 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -185,7 +185,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) } var res []*EnvResp - reg, err := commonservice.FindDefaultRegistry(log) + reg, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("FindDefaultRegistry error: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 7bd24813..c1ef2713 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -170,7 +170,7 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( } if len(prod.RegistryID) == 0 { - reg, err := commonservice.FindDefaultRegistry(log) + reg, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %s", username, envName, productName, err) return nil, err diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index b9a77ed8..e8d94e5d 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -25,7 +25,6 @@ import ( "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -44,7 +43,7 @@ func GetDefaultRegistryNamespace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - reg, err := commonservice.GetDefaultRegistryNamespace(ctx.Logger) + reg, err := commonservice.FindDefaultRegistry(true, ctx.Logger) if err != nil { ctx.Err = err return @@ -64,7 +63,7 @@ func GetRegistryNamespace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - reg, err := commonservice.FindRegistryById(c.Param("id"), ctx.Logger) + reg, err := commonservice.FindRegistryById(c.Param("id"), false, ctx.Logger) if err != nil { ctx.Err = err return @@ -82,7 +81,7 @@ func ListRegistryNamespaces(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = commonservice.ListRegistryNamespaces(ctx.Logger) + ctx.Resp, ctx.Err = commonservice.ListRegistryNamespaces(false, ctx.Logger) } func CreateRegistryNamespace(c *gin.Context) { @@ -166,14 +165,14 @@ func ListImages(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() //判断当前registryId是否为空 - regOps := new(commonrepo.FindRegOps) registryID := c.Query("registryId") + var registryInfo *commonmodels.RegistryNamespace + var err error if registryID != "" { - regOps.ID = registryID + registryInfo, err = commonservice.FindRegistryById(registryID, false, ctx.Logger) } else { - regOps.IsDefault = true + registryInfo, err = commonservice.FindDefaultRegistry(false, ctx.Logger) } - registryInfo, err := service.GetRegistryNamespace(regOps, ctx.Logger) if err != nil { ctx.Logger.Errorf("can't find candidate registry err :%v", err) ctx.Resp = make([]*service.RepoImgResp, 0) @@ -195,9 +194,7 @@ func ListRepoImages(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - regOps := new(commonrepo.FindRegOps) - regOps.IsDefault = true - registryInfo, err := service.GetRegistryNamespace(regOps, ctx.Logger) + registryInfo, err := commonservice.FindDefaultRegistry(false, ctx.Logger) if err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index ce94cf6c..1428721f 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -24,6 +24,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util" @@ -51,7 +52,7 @@ const ( // ListRegistries 为了抹掉ak和sk的数据 func ListRegistries(log *zap.SugaredLogger) ([]*commonmodels.RegistryNamespace, error) { - registryNamespaces, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + registryNamespaces, err := commonservice.ListRegistryNamespaces(false, log) if err != nil { log.Errorf("RegistryNamespace.List error: %v", err) return registryNamespaces, fmt.Errorf("RegistryNamespace.List error: %v", err) @@ -66,25 +67,19 @@ func ListRegistries(log *zap.SugaredLogger) ([]*commonmodels.RegistryNamespace, func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { regOps := new(commonrepo.FindRegOps) regOps.IsDefault = true - registryInfoList, _ := GetRegistryNamespaces(regOps, log) - if args.IsDefault { - for _, registryInfo := range registryInfoList { - registryInfo.IsDefault = false - err := UpdateRegistryNamespaceDefault(registryInfo, log) - if err != nil { - log.Errorf("updateRegistry error: %v", err) - return fmt.Errorf("RegistryNamespace.Create error: %v", err) - } + defaultReg, err := commonservice.FindDefaultRegistry(false, log) + if err != nil { + log.Warnf("failed to find the default registry, the error is: %s", err) + } + if args.IsDefault && defaultReg != nil { + defaultReg.IsDefault = false + err := UpdateRegistryNamespaceDefault(defaultReg, log) + if err != nil { + log.Errorf("updateRegistry error: %v", err) + return fmt.Errorf("RegistryNamespace.Create error: %v", err) } } else { - hasDefault := false - for _, registryInfo := range registryInfoList { - if registryInfo.IsDefault { - hasDefault = true - break - } - } - if !hasDefault { + if defaultReg == nil { log.Errorf("create registry error: There must be at least 1 default registry") return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } @@ -103,26 +98,20 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { regOps := new(commonrepo.FindRegOps) regOps.IsDefault = true - registryInfoList, _ := GetRegistryNamespaces(regOps, log) - if args.IsDefault { - for _, registryInfo := range registryInfoList { - registryInfo.IsDefault = false - err := UpdateRegistryNamespaceDefault(registryInfo, log) - if err != nil { - log.Errorf("updateRegistry error: %v", err) - return fmt.Errorf("RegistryNamespace.Update error: %v", err) - } + defaultReg, err := commonservice.FindDefaultRegistry(false, log) + if err != nil { + log.Warnf("failed to find the default registry, the error is: %s", err) + } + if args.IsDefault && defaultReg != nil { + defaultReg.IsDefault = false + err := UpdateRegistryNamespaceDefault(defaultReg, log) + if err != nil { + log.Errorf("updateRegistry error: %v", err) + return fmt.Errorf("RegistryNamespace.Update error: %v", err) } } else { - hasDefault := false - for _, registryInfo := range registryInfoList { - if registryInfo.ID != args.ID { - hasDefault = true - break - } - } - if !hasDefault { - log.Errorf("update registry error: There must be at least 1 default registry") + if defaultReg == nil { + log.Errorf("create registry error: There must be at least 1 default registry") return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } } @@ -146,7 +135,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { repoInfos := make([]*RepoInfo, 0) - resp, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + resp, err := commonservice.ListRegistryNamespaces(false, log) if err != nil { log.Errorf("RegistryNamespace.List error: %v", err) return nil, fmt.Errorf("RegistryNamespace.List error: %v", err) @@ -164,15 +153,6 @@ func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { return repoInfos, nil } -func GetRegistryNamespace(regOps *commonrepo.FindRegOps, log *zap.SugaredLogger) (*commonmodels.RegistryNamespace, error) { - resp, err := commonrepo.NewRegistryNamespaceColl().Find(regOps) - if err != nil { - log.Errorf("RegistryNamespace.get error: %v", err) - return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) - } - return resp, nil -} - func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, logger *zap.SugaredLogger) ([]*RepoImgResp, error) { repos, err := registry.NewV2Service(registryInfo.RegProvider).ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ @@ -242,15 +222,6 @@ func GetRepoTags(registryInfo *commonmodels.RegistryNamespace, name string, log return resp, err } -func GetRegistryNamespaces(regOps *commonrepo.FindRegOps, log *zap.SugaredLogger) ([]*commonmodels.RegistryNamespace, error) { - resp, err := commonrepo.NewRegistryNamespaceColl().FindAll(regOps) - if err != nil { - log.Errorf("RegistryNamespace.findAll error: %+v", err) - return nil, fmt.Errorf("RegistryNamespace.findAll error: %v", err) - } - return resp, nil -} - func UpdateRegistryNamespaceDefault(args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { if err := commonrepo.NewRegistryNamespaceColl().Update(args.ID.Hex(), args); err != nil { log.Errorf("UpdateRegistryNamespaceDefault.Update error: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index 0fad2b09..b4adcf7c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -31,14 +31,12 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/util" ) // get global config payload func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskCreator string, log *zap.SugaredLogger) (int64, error) { - configPayload := commonservice.GetConfigPayload(0) - repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + repos, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) @@ -54,13 +52,6 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC if !registriesInvolved.Has(repo.ID.Hex()) { continue } - // if the registry is SWR, we need to modify ak/sk according to the rule - if repo.RegProvider == config.SWRProvider { - ak := fmt.Sprintf("%s@%s", repo.Region, repo.AccessKey) - sk := util.ComputeHmacSha256(repo.AccessKey, repo.SecretKey) - repo.AccessKey = ak - repo.SecretKey = sk - } configPayload.RepoConfigs[repo.ID.Hex()] = repo } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index f81f6318..8aaa2cf0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -916,9 +916,7 @@ func upsertWorkflowStat(args *commonmodels.WorkflowStat, log *zap.SugaredLogger) } func getImageInfo(repoName, tag string, log *zap.SugaredLogger) (*commonmodels.DeliveryImage, error) { - regOps := new(commonrepo.FindRegOps) - regOps.IsDefault = true - registryInfo, err := commonrepo.NewRegistryNamespaceColl().Find(regOps) + registryInfo, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("RegistryNamespace.get error: %v", err) return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index e7411bdf..3ad5aeca 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -114,7 +114,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } if build.Registries == nil { - registries, err := commonservice.ListRegistryNamespaces(log) + registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } else { @@ -148,7 +148,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } if testing.Registries == nil { - registries, err := commonservice.ListRegistryNamespaces(log) + registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } else { @@ -232,7 +232,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } } - repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + repos, err := commonservice.ListRegistryNamespaces(false, log) if err != nil { return nil, e.ErrCreateTask.AddErr(err) } @@ -579,7 +579,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testTask.JobCtx.Caches = testModule.Caches testTask.JobCtx.ArtifactPaths = testModule.ArtifactPaths if testTask.Registries == nil { - registries, err := commonservice.ListRegistryNamespaces(log) + registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } else { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index fb6ca7c9..34c68bd1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -665,7 +665,7 @@ func SetCandidateRegistry(payload *commonmodels.ConfigPayload, log *zap.SugaredL return nil } - reg, err := commonservice.FindDefaultRegistry(log) + reg, err := commonservice.FindDefaultRegistry(true, log) if err != nil { log.Errorf("can't find default candidate registry: %v", err) return e.ErrFindRegistry.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 3a30c2e7..a923b52a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -524,10 +524,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) if len(env.RegistryID) == 0 { - op := &commonrepo.FindRegOps{ - IsDefault: true, - } - reg, err := commonrepo.NewRegistryNamespaceColl().Find(op) + reg, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("get default registry error: %v", err) return nil, e.ErrGetCounter.AddDesc(err.Error()) @@ -793,17 +790,10 @@ func generateNextTaskID(workflowName string) (int64, error) { } func modifyConfigPayload(configPayload *commonmodels.ConfigPayload, ignoreCache, resetCache bool) { - repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + repos, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) if err == nil { configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) for _, repo := range repos { - // if the registry is SWR, we need to modify ak/sk according to the rule - if repo.RegProvider == config.SWRProvider { - ak := fmt.Sprintf("%s@%s", repo.Region, repo.AccessKey) - sk := util.ComputeHmacSha256(repo.AccessKey, repo.SecretKey) - repo.AccessKey = ak - repo.SecretKey = sk - } configPayload.RepoConfigs[repo.ID.Hex()] = repo } } @@ -954,7 +944,7 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, } func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { - registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + registries, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) if err != nil { return nil, fmt.Errorf("failed to query registries") } @@ -986,7 +976,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. if err != nil { log.Errorf("failed to build registry map, err: %s", err) // use default registry - reg, err := commonservice.FindDefaultRegistry(log) + reg, err := commonservice.FindDefaultRegistry(true, log) if err != nil { log.Errorf("can't find default candidate registry, err: %s", err) return nil, e.ErrFindRegistry.AddDesc(err.Error()) @@ -1436,7 +1426,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * testArgs := args.Tests testCreator := args.WorkflowTaskCreator - registries, err := commonservice.ListRegistryNamespaces(log) + registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } @@ -1573,7 +1563,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) - repos, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + repos, err := commonservice.ListRegistryNamespaces(true, log) if err == nil { configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) for _, repo := range repos { @@ -1832,7 +1822,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL return nil, e.ErrConvertSubTasks.AddErr(err) } - registries, err := commonservice.ListRegistryNamespaces(log) + registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } @@ -2085,14 +2075,14 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // 注意: 其他任务从 pt.TaskArgs.Deploy.Image 获取, 必须要有编译任务 var reg *commonmodels.RegistryNamespace if len(exitedProd.RegistryID) > 0 { - reg, err = commonservice.FindRegistryById(exitedProd.RegistryID, log) + reg, err = commonservice.FindRegistryById(exitedProd.RegistryID, true, log) if err != nil { log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", exitedProd.RegistryID, err) return e.ErrFindRegistry.AddDesc(err.Error()) } } else { - reg, err = commonservice.FindDefaultRegistry(log) + reg, err = commonservice.FindDefaultRegistry(true, log) if err != nil { log.Errorf("can't find default candidate registry: %v", err) return e.ErrFindRegistry.AddDesc(err.Error()) -- Gitee From 15e649e20155b53ce413413c4a70458385a5d297 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 29 Dec 2021 15:33:57 +0800 Subject: [PATCH 1161/2647] minor bugfix Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/convert.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 9ce3b598..6f5831c7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -154,7 +154,7 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z return nil, e.ErrConvertSubTasks.AddErr(err) } - registries, err := commonservice.ListRegistryNamespaces(log) + registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } -- Gitee From 2ab370509c9e165d8c9809d229a2781105d96540 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 29 Dec 2021 16:19:56 +0800 Subject: [PATCH 1162/2647] decipher ecr namespace from ecr uri Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 4af487a5..b97518ca 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -68,6 +68,9 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su } resp.AccessKey = realAK resp.SecretKey = realSK + // for AWS, since there are no namespaces, we manually decrypt the namespace from URI + arr := strings.Split(resp.RegAddr, "/") + resp.Namespace = arr[len(arr)-1] default: break } @@ -101,6 +104,9 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* } reg.AccessKey = realAK reg.SecretKey = realSK + // for AWS, since there are no namespaces, we manually decrypt the namespace from URI + arr := strings.Split(reg.RegAddr, "/") + reg.Namespace = arr[len(arr)-1] default: break } -- Gitee From 3f1f1cdc08f48d991e26139156729209b6df98c7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 29 Dec 2021 16:21:27 +0800 Subject: [PATCH 1163/2647] revert changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index b97518ca..4af487a5 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -68,9 +68,6 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su } resp.AccessKey = realAK resp.SecretKey = realSK - // for AWS, since there are no namespaces, we manually decrypt the namespace from URI - arr := strings.Split(resp.RegAddr, "/") - resp.Namespace = arr[len(arr)-1] default: break } @@ -104,9 +101,6 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* } reg.AccessKey = realAK reg.SecretKey = realSK - // for AWS, since there are no namespaces, we manually decrypt the namespace from URI - arr := strings.Split(reg.RegAddr, "/") - reg.Namespace = arr[len(arr)-1] default: break } -- Gitee From 5e2d8044c678e9f8f8516695eb8fd193b02ace20 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 29 Dec 2021 16:30:26 +0800 Subject: [PATCH 1164/2647] add compatibility function for aws ecr secret Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/job.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 99cd25d3..8528c7a7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -464,6 +464,11 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa for _, reg := range registries { arr := strings.Split(reg.Namespace, "/") namespaceInRegistry := arr[len(arr)-1] + // for AWS ECR, there are no namespace, thus we need to find the NS from the URI + if namespaceInRegistry == "" { + uriDecipher := strings.Split(reg.RegAddr, "/") + namespaceInRegistry = uriDecipher[len(uriDecipher)-1] + } secretName := namespaceInRegistry + registrySecretSuffix if reg.RegType != "" { secretName = namespaceInRegistry + "-" + reg.RegType + registrySecretSuffix @@ -574,6 +579,11 @@ func createOrUpdateRegistrySecrets(namespace, registryID string, registries []*t arr := strings.Split(reg.Namespace, "/") namespaceInRegistry := arr[len(arr)-1] + // for AWS ECR, there are no namespace, thus we need to find the NS from the URI + if namespaceInRegistry == "" { + uriDecipher := strings.Split(reg.RegAddr, "/") + namespaceInRegistry = uriDecipher[len(uriDecipher)-1] + } filteredName, err := formatRegistryName(namespaceInRegistry) if err != nil { return err -- Gitee From 26a971d9fec93de4f9489b9d85185ffe6c9e7656 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 29 Dec 2021 18:05:07 +0800 Subject: [PATCH 1165/2647] bugfix for generating image info for AWS ECR Signed-off-by: Min Min --- .../workflow/service/workflow/pipeline_validation.go | 3 +++ .../core/workflow/service/workflow/workflow_task.go | 12 ++++++++++-- pkg/util/url.go | 8 +++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 34c68bd1..b16ec597 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -587,6 +587,9 @@ func replaceVariable(customRule *template.CustomRule, candidate *candidate) stri // GetImage suffix 可以是 branch name 或者 pr number func GetImage(registry *commonmodels.RegistryNamespace, suffix string) string { + if registry.RegProvider == config.RegistryTypeAWS { + return fmt.Sprintf("%s/%s", util.TrimURLScheme(registry.RegAddr), suffix) + } return fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, suffix) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index a923b52a..def7a9f4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2131,10 +2131,14 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // } // } //} + registryRepo := reg.RegAddr + "/" + reg.Namespace + if reg.RegProvider == config.RegistryTypeAWS { + registryRepo = reg.RegAddr + } t.DockerBuildStatus = &task.DockerBuildStatus{ ImageName: t.JobCtx.Image, - RegistryRepo: reg.RegAddr + "/" + reg.Namespace, + RegistryRepo: registryRepo, } t.UTStatus = &task.UTStatus{} t.StaticCheckStatus = &task.StaticCheckStatus{} @@ -2187,7 +2191,11 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorkflowTaskCreator} } registry := pt.ConfigPayload.RepoConfigs[pt.WorkflowArgs.RegistryID] - t.Image = fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, t.Image) + if registry.RegProvider == config.RegistryTypeAWS { + t.Image = fmt.Sprintf("%s/%s", util.TrimURLScheme(registry.RegAddr), t.Image) + } else { + t.Image = fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, t.Image) + } pt.TaskArgs.Deploy.Image = t.Image t.RegistryID = pt.WorkflowArgs.RegistryID diff --git a/pkg/util/url.go b/pkg/util/url.go index 09c212c4..6a1e1252 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -42,9 +42,15 @@ func GetURLHostName(urlAddr string) string { func ReplaceRepo(origin, addr, namespace string) string { parts := strings.SplitN(origin, "/", -1) + if namespace != "" { + return strings.Join([]string{ + TrimURLScheme(addr), + namespace, + parts[len(parts)-1], + }, "/") + } return strings.Join([]string{ TrimURLScheme(addr), - namespace, parts[len(parts)-1], }, "/") } -- Gitee From 31cb089ebdc53fad4f3b91716b76c8e0bc9e172d Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 30 Dec 2021 11:11:17 +0800 Subject: [PATCH 1166/2647] added in-memory cache for aws ecr token to avoid rate limits Signed-off-by: Min Min --- .../aslan/core/common/service/registry.go | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 4af487a5..72234cda 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -21,6 +21,8 @@ import ( "errors" "fmt" "strings" + "sync" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -37,6 +39,21 @@ import ( "github.com/koderover/zadig/pkg/util" ) +var awsKeyMap sync.Map + +type awsKeyWithExpiration struct { + AccessKey string + SecretKey string + Expiration int64 +} + +func (k *awsKeyWithExpiration) IsExpired() bool { + if time.Now().Unix() > k.Expiration { + return true + } + return false +} + func FindRegistryById(registryId string, getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { return findRegisty(&mongodb.FindRegOps{ID: registryId}, getRealCredential, log) } @@ -61,7 +78,7 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) resp.SecretKey = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) case config.RegistryTypeAWS: - realAK, realSK, err := getAWSRegistryCredential(resp.AccessKey, resp.SecretKey, resp.Region) + realAK, realSK, err := getAWSRegistryCredential(resp.ID.Hex(), resp.AccessKey, resp.SecretKey, resp.Region) if err != nil { log.Errorf("Failed to get keypair from aws, the error is: %s", err) return nil, err @@ -94,7 +111,7 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* reg.AccessKey = fmt.Sprintf("%s@%s", reg.Region, reg.AccessKey) reg.SecretKey = util.ComputeHmacSha256(reg.AccessKey, reg.SecretKey) case config.RegistryTypeAWS: - realAK, realSK, err := getAWSRegistryCredential(reg.AccessKey, reg.SecretKey, reg.Region) + realAK, realSK, err := getAWSRegistryCredential(reg.ID.Hex(), reg.AccessKey, reg.SecretKey, reg.Region) if err != nil { log.Errorf("Failed to get keypair from aws, the error is: %s", err) return nil, err @@ -142,8 +159,17 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient return nil } -// TODO: implement in-memory cache for this function to avoid rate limits. -func getAWSRegistryCredential(AK, SK, Region string) (string, string, error) { +func getAWSRegistryCredential(ID, AK, SK, Region string) (string, string, error) { + // first we try to get ak/sk from our memory cache + obj, ok := awsKeyMap.Load(ID) + if ok { + keypair, ok := obj.(awsKeyWithExpiration) + if ok { + if !keypair.IsExpired() { + return keypair.AccessKey, keypair.SecretKey, nil + } + } + } creds := credentials.NewStaticCredentials(AK, SK, "") config := &aws.Config{ Region: aws.String(Region), @@ -170,5 +196,11 @@ func getAWSRegistryCredential(AK, SK, Region string) (string, string, error) { if len(keypair) != 2 { return "", "", errors.New("decode keypair from aws response error") } + // cache the aws ak/sk + awsKeyMap.Store(ID, awsKeyWithExpiration{ + AccessKey: keypair[0], + SecretKey: keypair[1], + Expiration: time.Now().Add(10 * time.Hour).Unix(), + }) return keypair[0], keypair[1], nil } -- Gitee From f61f04ed179355ca2db1bdedb80bfa9f4cea9f96 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 30 Dec 2021 11:19:29 +0800 Subject: [PATCH 1167/2647] added some debug logs Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 72234cda..4f262535 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -166,6 +166,7 @@ func getAWSRegistryCredential(ID, AK, SK, Region string) (string, string, error) keypair, ok := obj.(awsKeyWithExpiration) if ok { if !keypair.IsExpired() { + fmt.Printf("Getting aws ak/sk from memory cache: ak[%s], sk[%s]", keypair.AccessKey, keypair.SecretKey) return keypair.AccessKey, keypair.SecretKey, nil } } -- Gitee From b85d7b3f866b69728e99e7dbf94010f018bb7ce6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 30 Dec 2021 14:06:26 +0800 Subject: [PATCH 1168/2647] debugged list images from aws ecr Signed-off-by: Min Min --- .../aslan/core/common/service/registry/service.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 43e1aadf..8ca997d8 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -490,27 +490,24 @@ func (s *EcrService) ListRepoImages(option ListRepoImagesOption, log *zap.Sugare for _, repo := range option.Repos { name := repo wg.Start(func() { - input := &ecr.BatchGetImageInput{ + input := &ecr.ListImagesInput{ RepositoryName: aws.String(name), } - result, err := svc.BatchGetImage(input) + result, err := svc.ListImages(input) if err != nil { log.Errorf("Failed to get image information from aws, error: %s", err) return } var koderoverTags, customTags, sortedTags []string - for _, image := range result.Images { - if *image.RepositoryName != name { - continue - } - tagArray := strings.Split(*image.ImageId.ImageTag, "-") + for _, image := range result.ImageIds { + tagArray := strings.Split(*image.ImageTag, "-") if len(tagArray) > 1 && len(tagArray[0]) == 14 { if _, err := time.Parse("20060102150405", tagArray[0]); err == nil { - koderoverTags = append(koderoverTags, *image.ImageId.ImageTag) + koderoverTags = append(koderoverTags, *image.ImageTag) continue } } - customTags = append(customTags, *image.ImageId.ImageTag) + customTags = append(customTags, *image.ImageTag) } sort.Sort(sort.Reverse(sort.StringSlice(koderoverTags))) -- Gitee From 0fd44f16bb45bdab5ccca9761f97447cd2ada749 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 31 Dec 2021 10:53:38 +0800 Subject: [PATCH 1169/2647] optimize json message decode logic Signed-off-by: allenshen --- pkg/microservice/packager/core/service/service.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 2659a6d1..158f7b9c 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -97,15 +97,11 @@ func buildTargetImage(imageName, imageTag, host, nameSpace string) string { func ExtractErrorDetail(in io.Reader) error { dec := json.NewDecoder(in) - for { + for dec.More() { var jm jsonmessage.JSONMessage if err := dec.Decode(&jm); err != nil { - if err == io.EOF { - break - } return err } - if jm.Error != nil { return jm.Error } @@ -114,10 +110,7 @@ func ExtractErrorDetail(in io.Reader) error { } func pullImage(dockerClient *client.Client, imageUrl string, options *types.ImagePullOptions) error { - log.Infof("pulling image: %s", imageUrl) - - // pull image pullResponse, err := dockerClient.ImagePull(context.TODO(), imageUrl, *options) if err != nil { return err @@ -130,9 +123,7 @@ func pullImage(dockerClient *client.Client, imageUrl string, options *types.Imag } func pushImage(dockerClient *client.Client, targetImageUrl string, options *types.ImagePushOptions) error { - log.Infof("pushing image: %s", targetImageUrl) - pushResponse, err := dockerClient.ImagePush(context.TODO(), targetImageUrl, *options) if err != nil { return errors.Wrapf(err, "failed to push image: %s", targetImageUrl) -- Gitee From a2bcaf0790ae6a6e1628e6e028d9a9a03e1625d5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 11:09:50 +0800 Subject: [PATCH 1170/2647] added pull secret refreshment for aws ecr when restart/replacing image Signed-off-by: Min Min --- .../aslan/core/environment/service/image.go | 15 +++++++++ .../aslan/core/environment/service/service.go | 32 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index f47af546..a2b30b55 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -297,6 +297,21 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } + // aws secrets needs to be refreshed + regs, err := commonservice.ListRegistryNamespaces(true, log) + if err != nil { + log.Errorf("Failed to get registries to update container images, the error is: %s\n", err) + return err + } + for _, reg := range regs { + if reg.RegProvider == config.RegistryTypeAWS { + if err := kube.CreateOrUpdateRegistrySecret(namespace, reg, kubeClient); err != nil { + retErr := fmt.Errorf("failed to update pull secret for registry: %s, the error is: %s", reg.ID.Hex(), err) + log.Errorf("%s\n", retErr.Error()) + return retErr + } + } + } eventStart := time.Now().Unix() diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 572d6d87..ccd31225 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -147,6 +147,22 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { return err } + // aws secrets needs to be refreshed + regs, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) + if err != nil { + log.Errorf("Failed to get registries to restart container, the error is: %s\n", err) + return err + } + for _, reg := range regs { + if reg.RegProvider == config.RegistryTypeAWS { + if err := kube.CreateOrUpdateRegistrySecret(prod.Namespace, reg, kubeClient); err != nil { + retErr := fmt.Errorf("failed to update pull secret for registry: %s, the error is: %s", reg.ID.Hex(), err) + log.Errorf("%s\n", retErr.Error()) + return retErr + } + } + } + switch args.Type { case setting.Deployment: err = updater.RestartDeployment(prod.Namespace, args.Name, kubeClient) @@ -329,6 +345,22 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return err } + // aws secrets needs to be refreshed + regs, err := commonservice.ListRegistryNamespaces(true, log) + if err != nil { + log.Errorf("Failed to get registries to restart container, the error is: %s\n", err) + return err + } + for _, reg := range regs { + if reg.RegProvider == config.RegistryTypeAWS { + if err := kube.CreateOrUpdateRegistrySecret(productObj.Namespace, reg, kubeClient); err != nil { + retErr := fmt.Errorf("failed to update pull secret for registry: %s, the error is: %s", reg.ID.Hex(), err) + log.Errorf("%s\n", retErr.Error()) + return retErr + } + } + } + switch productObj.Source { case setting.SourceFromHelm, setting.SourceFromExternal: errList := new(multierror.Error) -- Gitee From 2ecff2368b2097d2c8d9382d702ffd0fe9f0f77a Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 31 Dec 2021 11:12:26 +0800 Subject: [PATCH 1171/2647] fix helm deploy pending Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 60 ++++++++++++------- .../service/environment_creator.go | 2 +- .../aslan/core/environment/service/image.go | 7 ++- .../core/service/taskplugin/deploy.go | 22 ++++--- pkg/setting/consts.go | 3 + pkg/tool/kube/updater/secret.go | 11 ++++ 6 files changed, 71 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 12d13451..cfc152cd 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -32,6 +32,7 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" + helmrelease "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/strvals" appsv1 "k8s.io/api/apps/v1" @@ -164,7 +165,7 @@ type RawYamlResp struct { YamlContent string `json:"yamlContent"` } -type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error +type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, cl client.Client, log *zap.SugaredLogger) error func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) @@ -2233,16 +2234,16 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client) error { +func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, cl client.Client) error { mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err } - return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, isRetry, helmClient) + return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, isRetry, helmClient, cl) } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client) error { +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, cl client.Client) error { base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2270,9 +2271,6 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha ValuesYaml: valuesYaml, UpgradeCRDs: true, CleanupOnFail: true, - Wait: true, - Timeout: 5 * time.Minute, - Atomic: true, } if isRetry { chartSpec.Replace = true @@ -2281,14 +2279,23 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha chartSpec.Wait = true chartSpec.Timeout = timeout } + var release *helmrelease.Release + if release, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { + if release != nil && (release.Info.Status == helmrelease.StatusPendingInstall || release.Info.Status == helmrelease.StatusPendingUpgrade) { + secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", release.Name, release.Version) + deleteErr := updater.DeleteSecretWithName(release.Namespace, secretName, cl) + if deleteErr != nil { + err = errors.WithMessagef(err, "deleteSecretWithName:%s,error:%s", secretName, deleteErr) + return err + } + } return err } return nil } -func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { +func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, cl client.Client, log *zap.SugaredLogger) { var ( err error errList = &multierror.Error{} @@ -2323,9 +2330,10 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, logger *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, cl client.Client, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, 0, isRetry, helmClient) + timeout := time.Second * setting.DeployTimeout + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, timeout, isRetry, helmClient, cl) if err != nil { log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) @@ -2357,7 +2365,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, cl, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2426,14 +2434,14 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { +func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, cl client.Client, log *zap.SugaredLogger) []error { bo := backoff.NewConstantBackOff(time.Second * 3) retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) isRetry := false _ = backoff.Retry(func() error { failedServices := make([]*commonmodels.Service, 0) - errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, log) + errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, cl, log) if len(errList) == 0 { return nil } @@ -2445,7 +2453,7 @@ func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, servic return errList } -func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { +func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, cl client.Client, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } @@ -2458,7 +2466,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic for _, data := range serviceList { go func() { defer wg.Done() - err := handler(data, isRetry, log) + err := handler(data, isRetry, cl, log) if err != nil { errList = append(errList, err) *failedServices = append(*failedServices, data) @@ -2477,6 +2485,10 @@ func updateProductGroup(username, productName, envName, updateType string, produ productServiceMap = make(map[string]*commonmodels.ProductService) productTemplServiceMap = make(map[string]*commonmodels.ProductService) ) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productResp.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } restConfig, err := kube.GetRESTConfig(productResp.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) @@ -2533,9 +2545,9 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, cl client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, isRetry, helmClient) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, isRetry, helmClient, cl) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) @@ -2573,7 +2585,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, kubeClient, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2776,14 +2788,18 @@ func updateProductVariable(productName, envName string, productResp *commonmodel return e.ErrUpdateEnv.AddErr(err) } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productResp.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } renderChartMap := make(map[string]*templatemodels.RenderChart) for _, renderChart := range productResp.ChartInfos { renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(service *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { + handler := func(service *commonmodels.Service, isRetry bool, cl client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, isRetry, helmClient) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, isRetry, helmClient, cl) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) @@ -2813,7 +2829,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, log) + groupServiceErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, kubeClient, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 5c32ebfd..3e9472ee 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -220,7 +220,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P eventStart := time.Now().Unix() - go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, log) + go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, kubeClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index f47af546..3a04e893 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -41,6 +41,7 @@ import ( "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util/converter" yamlutil "github.com/koderover/zadig/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" ) type UpdateContainerImageArgs struct { @@ -208,7 +209,7 @@ func prepareData(namespace, serviceName string, containerName string, product *m return } -func updateContainerForHelmChart(serviceName, resType, image, containerName string, product *models.Product) error { +func updateContainerForHelmChart(serviceName, resType, image, containerName string, product *models.Product, cl client.Client) error { var ( replaceValuesMap map[string]interface{} replacedValuesYaml string @@ -268,7 +269,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri } // when replace image, should not wait - err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, 0, false, helmClient) + err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, 0, false, helmClient, cl) if err != nil { return err } @@ -310,7 +311,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } - err = updateContainerForHelmChart(serviceName, args.Type, args.Image, args.ContainerName, product) + err = updateContainerForHelmChart(serviceName, args.Type, args.Image, args.ContainerName, product, kubeClient) if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 440cc8dd..7a9be464 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -27,6 +27,7 @@ import ( helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.uber.org/zap" + helmrelease "helm.sh/helm/v3/pkg/release" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" @@ -83,8 +84,6 @@ func (p *DeployTaskPlugin) SetAckFunc(func()) { } const ( - // DeployTimeout ... - DeployTimeout = 60 * 10 // 10 minutes imageUrlParseRegexString = `(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?` ) @@ -117,7 +116,7 @@ func (p *DeployTaskPlugin) SetStatus(status config.Status) { // TaskTimeout ... func (p *DeployTaskPlugin) TaskTimeout() int { if p.Task.Timeout == 0 { - p.Task.Timeout = DeployTimeout + p.Task.Timeout = setting.DeployTimeout } else { if !p.Task.IsRestart { p.Task.Timeout = p.Task.Timeout * 60 @@ -540,17 +539,24 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ValuesYaml: replacedMergedValuesYaml, SkipCRDs: false, UpgradeCRDs: true, - Timeout: 5 * time.Minute, + Timeout: time.Second * setting.DeployTimeout, Wait: true, - Atomic: true, } - - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { + var release *helmrelease.Release + if release, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", p.Task.Namespace, p.Task.ServiceName) - return + + if release != nil && (release.Info.Status == helmrelease.StatusPendingInstall || release.Info.Status == helmrelease.StatusPendingUpgrade) { + secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", release.Name, release.Version) + deleteErr := updater.DeleteSecretWithName(release.Namespace, secretName, p.kubeClient) + if deleteErr != nil { + err = errors.WithMessagef(err, "deleteSecretWithName:%s,error:%s", secretName, deleteErr) + return + } + } } //替换环境变量中的chartInfos diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index c6d67039..5b4f04f2 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -586,3 +586,6 @@ const ( const LocalClusterID = "0123456789abcdef12345678" const RequestModeOpenAPI = "openAPI" + +// DeployTimeout ... +const DeployTimeout = 60 * 10 // 10 minutes diff --git a/pkg/tool/kube/updater/secret.go b/pkg/tool/kube/updater/secret.go index 731998f0..55e6166c 100644 --- a/pkg/tool/kube/updater/secret.go +++ b/pkg/tool/kube/updater/secret.go @@ -18,6 +18,7 @@ package updater import ( corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -29,3 +30,13 @@ func DeleteSecrets(ns string, selector labels.Selector, cl client.Client) error func UpdateOrCreateSecret(s *corev1.Secret, cl client.Client) error { return updateOrCreateObject(s, cl) } + +func DeleteSecretWithName(ns, name string, cl client.Client) error { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + } + return deleteObjects(secret, cl) +} -- Gitee From 04e7b28c3e8161c7c2436759435837e6f45f7eee Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 31 Dec 2021 11:12:47 +0800 Subject: [PATCH 1172/2647] Support to select projects when creating cluster management Signed-off-by: liu deyi --- .../core/common/repository/models/k8s_cluster.go | 5 +++-- .../aslan/core/common/service/kube/service.go | 13 +++++++++++++ .../aslan/core/multicluster/service/clusters.go | 5 +++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 2ce8dc18..5f2d7fe3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -57,8 +57,9 @@ type K8SClusterInfo struct { } type AdvancedConfig struct { - Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` - NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` + Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` + NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` + ProjectNames []string `json:"project_names" bson:"-"` } type NodeSelectorRequirement struct { diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index d31ee877..e6521752 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -30,7 +30,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/crypto" @@ -110,6 +112,17 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, id string, logger *z return nil, e.ErrCreateCluster.AddErr(err) } + for _, projectName := range cluster.AdvancedConfig.ProjectNames { + err = commonrepo.NewProjectClusterRelationColl().Create(&commonmodels.ProjectClusterRelation{ + ProjectName: projectName, + ClusterID: cluster.ID.Hex(), + CreatedBy: cluster.CreatedBy, + }) + if err != nil { + logger.Errorf("Failed to create projectClusterRelation err:%s", err) + } + } + token, err := crypto.AesEncrypt(cluster.ID.Hex()) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 16a2013b..17b8ffc3 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -167,8 +167,9 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K var advancedConfig *commonmodels.AdvancedConfig if args.AdvancedConfig != nil { advancedConfig = &commonmodels.AdvancedConfig{ - Strategy: args.AdvancedConfig.Strategy, - NodeLabels: convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels), + Strategy: args.AdvancedConfig.Strategy, + NodeLabels: convertToNodeSelectorRequirements(args.AdvancedConfig.NodeLabels), + ProjectNames: args.AdvancedConfig.ProjectNames, } } cluster := &commonmodels.K8SCluster{ -- Gitee From b87b5909d99c0a8bed22b65150f7d40ed939a1d3 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 11:29:35 +0800 Subject: [PATCH 1173/2647] changed expiration time to parameter Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 4f262535..d3703ac0 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -39,6 +39,8 @@ import ( "github.com/koderover/zadig/pkg/util" ) +var expirationTime = 10 * time.Hour + var awsKeyMap sync.Map type awsKeyWithExpiration struct { @@ -201,7 +203,7 @@ func getAWSRegistryCredential(ID, AK, SK, Region string) (string, string, error) awsKeyMap.Store(ID, awsKeyWithExpiration{ AccessKey: keypair[0], SecretKey: keypair[1], - Expiration: time.Now().Add(10 * time.Hour).Unix(), + Expiration: time.Now().Add(expirationTime).Unix(), }) return keypair[0], keypair[1], nil } -- Gitee From 5374e2251ad006775480805499e87298fd1b9026 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 31 Dec 2021 11:31:33 +0800 Subject: [PATCH 1174/2647] do not store projectNames Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/k8s_cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 5f2d7fe3..337bd1b9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -59,7 +59,7 @@ type K8SClusterInfo struct { type AdvancedConfig struct { Strategy string `json:"strategy,omitempty" bson:"strategy,omitempty"` NodeLabels []*NodeSelectorRequirement `json:"node_labels,omitempty" bson:"node_labels,omitempty"` - ProjectNames []string `json:"project_names" bson:"-"` + ProjectNames []string `json:"-" bson:"-"` } type NodeSelectorRequirement struct { -- Gitee From 8cdbe85021e17eca1d7ce2711366ccb1fc6ccb5e Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 11:33:58 +0800 Subject: [PATCH 1175/2647] added some warning comments Signed-off-by: Min Min --- .../common/repository/models/registry_namespace.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index e3b7e193..baf90d55 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -30,12 +30,14 @@ type RegistryNamespace struct { RegType string `bson:"reg_type" json:"reg_type"` RegProvider string `bson:"reg_provider" json:"reg_provider"` IsDefault bool `bson:"is_default" json:"is_default"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - AccessKey string `bson:"access_key" json:"access_key"` - SecretKey string `bson:"secret_key" json:"secret_key"` - Region string `bson:"region,omitempty" json:"region,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` + // Namespace is NOT a required field, this could be empty when the registry is AWS ECR or so. + // use with CAUTION !!!! + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + AccessKey string `bson:"access_key" json:"access_key"` + SecretKey string `bson:"secret_key" json:"secret_key"` + Region string `bson:"region,omitempty" json:"region,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` } func (ns *RegistryNamespace) Validate() error { -- Gitee From 98cefee24ecbef5bd0d6a35a5dca0d0f2415c41e Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 11:47:23 +0800 Subject: [PATCH 1176/2647] changed the way we find ns Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 8528c7a7..cce70f6f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -466,8 +466,8 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa namespaceInRegistry := arr[len(arr)-1] // for AWS ECR, there are no namespace, thus we need to find the NS from the URI if namespaceInRegistry == "" { - uriDecipher := strings.Split(reg.RegAddr, "/") - namespaceInRegistry = uriDecipher[len(uriDecipher)-1] + uriDecipher := strings.Split(reg.RegAddr, ".") + namespaceInRegistry = uriDecipher[0] } secretName := namespaceInRegistry + registrySecretSuffix if reg.RegType != "" { @@ -581,8 +581,8 @@ func createOrUpdateRegistrySecrets(namespace, registryID string, registries []*t namespaceInRegistry := arr[len(arr)-1] // for AWS ECR, there are no namespace, thus we need to find the NS from the URI if namespaceInRegistry == "" { - uriDecipher := strings.Split(reg.RegAddr, "/") - namespaceInRegistry = uriDecipher[len(uriDecipher)-1] + uriDecipher := strings.Split(reg.RegAddr, ".") + namespaceInRegistry = uriDecipher[0] } filteredName, err := formatRegistryName(namespaceInRegistry) if err != nil { -- Gitee From 75de7d69c9feb0e5bd58e0784b9ad77b4846d8d5 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 31 Dec 2021 14:30:59 +0800 Subject: [PATCH 1177/2647] fix log hang during building artifact Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/script.go | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 43fadc53..2461f2af 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -20,6 +20,7 @@ import ( "bufio" "encoding/base64" "fmt" + "io" "io/ioutil" "os" "os/exec" @@ -226,24 +227,23 @@ func (r *Reaper) runScripts() error { //如果文件不存在就创建文件,避免后面使用变量出错 util.WriteFile(fileName, []byte{}, 0700) - cmdOutReader, err := cmd.StdoutPipe() + needPersistentLog := len(r.Ctx.PostScripts) > 0 + + cmdStdoutReader, err := cmd.StdoutPipe() if err != nil { return err } - outScanner := bufio.NewScanner(cmdOutReader) + go r.handleCmdOutput(cmdStdoutReader, needPersistentLog, fileName) - if err := cmd.Start(); err != nil { + cmdStdErrReader, err := cmd.StderrPipe() + if err != nil { return err } + go r.handleCmdOutput(cmdStdErrReader, needPersistentLog, fileName) - go func() { - for outScanner.Scan() { - fmt.Printf("%s\n", r.maskSecretEnvs(outScanner.Text())) - if len(r.Ctx.PostScripts) > 0 { - util.WriteFile(fileName, []byte(outScanner.Text()+"\n"), 0700) - } - } - }() + if err := cmd.Start(); err != nil { + return err + } return cmd.Wait() } @@ -399,3 +399,28 @@ func (r *Reaper) downloadArtifactFile() error { } return nil } + +func (r *Reaper) handleCmdOutput(pipe io.ReadCloser, needPersistentLog bool, logFile string) { + reader := bufio.NewReader(pipe) + + for { + lineBytes, err := reader.ReadBytes('\n') + if err != nil { + if err == io.EOF { + break + } + + log.Errorf("Failed to read stdout log: %s", err) + break + } + + fmt.Printf("%s", string(lineBytes)) + + if needPersistentLog { + err := util.WriteFile(logFile, lineBytes, 0700) + if err != nil { + log.Warnf("Failed to write stdout file: %s", err) + } + } + } +} -- Gitee From 63e7b779ce9625ca2f3724f56029465942368519 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 31 Dec 2021 15:11:17 +0800 Subject: [PATCH 1178/2647] fix ListRelease latest Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 33 ++++++++++++++----- .../core/service/taskplugin/deploy.go | 29 +++++++++++----- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index cfc152cd..09d690d9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2279,15 +2279,30 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha chartSpec.Wait = true chartSpec.Timeout = timeout } - var release *helmrelease.Release - if release, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { - - if release != nil && (release.Info.Status == helmrelease.StatusPendingInstall || release.Info.Status == helmrelease.StatusPendingUpgrade) { - secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", release.Name, release.Version) - deleteErr := updater.DeleteSecretWithName(release.Namespace, secretName, cl) - if deleteErr != nil { - err = errors.WithMessagef(err, "deleteSecretWithName:%s,error:%s", secretName, deleteErr) - return err + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to Install helm chart %s/%s", + namespace, serviceObj.ServiceName) + + hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) + if errHistory != nil { + err = errors.WithMessagef( + err, + "failed to ListReleaseHistory: %s,error:%s", + chartSpec.ReleaseName, errHistory) + return err + } + if len(hrs) > 0 { + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { + secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", rel.Name, rel.Version) + deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, cl) + if deleteErr != nil { + err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) + return err + } } } return err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 7a9be464..b85c1861 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -28,6 +28,7 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" helmrelease "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" @@ -542,21 +543,33 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Timeout: time.Second * setting.DeployTimeout, Wait: true, } - var release *helmrelease.Release - if release, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", p.Task.Namespace, p.Task.ServiceName) - if release != nil && (release.Info.Status == helmrelease.StatusPendingInstall || release.Info.Status == helmrelease.StatusPendingUpgrade) { - secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", release.Name, release.Version) - deleteErr := updater.DeleteSecretWithName(release.Namespace, secretName, p.kubeClient) - if deleteErr != nil { - err = errors.WithMessagef(err, "deleteSecretWithName:%s,error:%s", secretName, deleteErr) - return + hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) + if errHistory != nil { + err = errors.WithMessagef( + err, + "failed to ListReleaseHistory: %s,error:%s", + chartSpec.ReleaseName, errHistory) + return + } + if len(hrs) > 0 { + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { + secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", rel.Name, rel.Version) + deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, p.kubeClient) + if deleteErr != nil { + err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) + return + } } } + return } //替换环境变量中的chartInfos -- Gitee From 2d9d4d4af9fc8b6af4d6c944c5dcc2b233c0e339 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 15:12:34 +0800 Subject: [PATCH 1179/2647] use integrated object storage instead of the default zadig-minio Signed-off-by: Min Min --- .../aslan/core/common/service/config_payload.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 87905f82..5a17f53b 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -104,5 +104,18 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { } payload.K8SClusters = K8SClusterResp } + + objectStorage, _ := mongodb.NewS3StorageColl().FindDefault() + if objectStorage != nil { + payload.S3Storage.Endpoint = objectStorage.Endpoint + payload.S3Storage.Sk = objectStorage.Sk + payload.S3Storage.Ak = objectStorage.Ak + payload.S3Storage.Path = objectStorage.Subfolder + payload.S3Storage.Bucket = objectStorage.Bucket + payload.S3Storage.Protocol = "https" + if objectStorage.Insecure { + payload.S3Storage.Protocol = "http" + } + } return payload } -- Gitee From 944ecfa0514c417d63bd75c31206f66bfc4da9ea Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 31 Dec 2021 15:41:27 +0800 Subject: [PATCH 1180/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/kube/service.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index e6521752..c5c9b5a1 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -112,14 +112,16 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, id string, logger *z return nil, e.ErrCreateCluster.AddErr(err) } - for _, projectName := range cluster.AdvancedConfig.ProjectNames { - err = commonrepo.NewProjectClusterRelationColl().Create(&commonmodels.ProjectClusterRelation{ - ProjectName: projectName, - ClusterID: cluster.ID.Hex(), - CreatedBy: cluster.CreatedBy, - }) - if err != nil { - logger.Errorf("Failed to create projectClusterRelation err:%s", err) + if cluster.AdvancedConfig != nil { + for _, projectName := range cluster.AdvancedConfig.ProjectNames { + err = commonrepo.NewProjectClusterRelationColl().Create(&commonmodels.ProjectClusterRelation{ + ProjectName: projectName, + ClusterID: cluster.ID.Hex(), + CreatedBy: cluster.CreatedBy, + }) + if err != nil { + logger.Errorf("Failed to create projectClusterRelation err:%s", err) + } } } -- Gitee From 749674a420c43294d65d8c69ed454418974d3298 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 31 Dec 2021 16:27:52 +0800 Subject: [PATCH 1181/2647] replace image registry when build helm delivery version Signed-off-by: allenshen --- .../aslan/core/common/service/image.go | 103 ++++++++++ .../aslan/core/delivery/service/version.go | 192 ++++++++++-------- .../aslan/core/environment/service/image.go | 94 +-------- 3 files changed, 211 insertions(+), 178 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/image.go diff --git a/pkg/microservice/aslan/core/common/service/image.go b/pkg/microservice/aslan/core/common/service/image.go new file mode 100644 index 00000000..9707dafe --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/image.go @@ -0,0 +1,103 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "strings" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/util/converter" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" +) + +// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx +func resolveImageUrl(imageUrl string) map[string]string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + result := make(map[string]string) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + result[exNames[i]] = matchedStr + } + } + return result +} + +// AssignImageData assign image url data into match data +// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path +// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value +func AssignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { + ret := make(map[string]interface{}) + // total image url assigned into one single value + if len(matchData) == 1 { + for _, v := range matchData { + ret[v] = imageUrl + } + return ret, nil + } + + resolvedImageUrl := resolveImageUrl(imageUrl) + + // image url assigned into repo/image+tag + if len(matchData) == 3 { + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] + ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] + return ret, nil + } + + if len(matchData) == 2 { + // image url assigned into repo/image + tag + if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { + ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] + for k, imagePath := range matchData { + if k == setting.PathSearchComponentTag { + continue + } + ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) + break + } + return ret, nil + } + // image url assigned into repo + image(tag) + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) + return ret, nil + } + + return nil, fmt.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) +} + +// ReplaceImage replace image defines in yaml by new version +func ReplaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { + nestedMap, err := converter.Expand(imageValuesMap) + if err != nil { + return "", err + } + bs, err := yaml.Marshal(nestedMap) + if err != nil { + return "", err + } + mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) + if err != nil { + return "", err + } + return string(mergedBs), nil +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7c1fe3e4..8a69c0a5 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -539,72 +539,6 @@ func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { return client, nil } -// find all images in one single chart -func extractImages(cData *DeliveryChartData, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { - - flatMap, err := converter.Flatten(cData.ValuesInEnv) - if err != nil { - return nil, err - } - - serviceObj := cData.ServiceObj - imagePathSpecs := make([]map[string]string, 0) - for _, container := range serviceObj.Containers { - imageSearchRule := &template.ImageSearchingRule{ - Repo: container.ImagePath.Repo, - Image: container.ImagePath.Image, - Tag: container.ImagePath.Tag, - } - pattern := imageSearchRule.GetSearchingPattern() - imagePathSpecs = append(imagePathSpecs, pattern) - } - - imageUrlsSet := sets.NewString() - for _, spec := range imagePathSpecs { - imageUrl, err := commonservice.GeneImageURI(spec, flatMap) - if err != nil { - return nil, err - } - imageUrlsSet.Insert(imageUrl) - } - - ret := &ServiceImageDetails{ - ServiceName: cData.ProductService.ServiceName, - Images: make([]*ImageUrlDetail, 0), - } - - registrySet := sets.NewString() - - for _, imageUrl := range imageUrlsSet.List() { - - registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) - } - registryUrl = strings.TrimSuffix(registryUrl, "/") - - imageName := commonservice.ExtractImageName(imageUrl) - imageTag := commonservice.ExtractImageTag(imageUrl) - - registryID := "" - // used source registry - if registry, ok := registryMap[registryUrl]; ok { - registryID = registry.ID.Hex() - registrySet.Insert(registryID) - } - - ret.Images = append(ret.Images, &ImageUrlDetail{ - ImageUrl: imageUrl, - Name: imageName, - Tag: imageTag, - Registry: registryID, - }) - } - - ret.Registries = registrySet.List() - return ret, nil -} - // ensure chart files exist func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) (string, error) { serviceObj := chartData.ServiceObj @@ -663,38 +597,120 @@ func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) return deliveryChartPath, nil } -func buildChartPackage(chartData *DeliveryChartData, product *commonmodels.Product, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, registryMap map[string]*commonmodels.RegistryNamespace) error { +// handleImageRegistry update image registry to target registry +func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) ([]byte, *ServiceImageDetails, error) { + + flatMap, err := converter.YamlToFlatMap(valuesYaml) + if err != nil { + return nil, nil, err + } + + retValuesYaml := string(valuesYaml) + + serviceObj := chartData.ServiceObj + imagePathSpecs := make([]map[string]string, 0) + for _, container := range serviceObj.Containers { + imageSearchRule := &template.ImageSearchingRule{ + Repo: container.ImagePath.Repo, + Image: container.ImagePath.Image, + Tag: container.ImagePath.Tag, + } + pattern := imageSearchRule.GetSearchingPattern() + imagePathSpecs = append(imagePathSpecs, pattern) + } + + imageDetail := &ServiceImageDetails{ + ServiceName: serviceObj.ServiceName, + Images: make([]*ImageUrlDetail, 0), + } + + registrySet := sets.NewString() + for _, spec := range imagePathSpecs { + imageUrl, err := commonservice.GeneImageURI(spec, flatMap) + if err != nil { + return nil, nil, err + } + + targetImageUrl := util.ReplaceRepo(imageUrl, targetRegistry.RegAddr, targetRegistry.Namespace) + + registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) + } + registryUrl = strings.TrimSuffix(registryUrl, "/") + + imageName := commonservice.ExtractImageName(imageUrl) + imageTag := commonservice.ExtractImageTag(imageUrl) + + registryID := "" + // used source registry + if registry, ok := registryMap[registryUrl]; ok { + registryID = registry.ID.Hex() + registrySet.Insert(registryID) + } + + imageDetail.Images = append(imageDetail.Images, &ImageUrlDetail{ + ImageUrl: imageUrl, + Name: imageName, + Tag: imageTag, + Registry: registryID, + }) + + // assign image to values.yaml + replaceValuesMap, err := commonservice.AssignImageData(targetImageUrl, spec) + if err != nil { + return nil, nil, fmt.Errorf("failed to pase image uri %s, err %s", targetImageUrl, err) + } + + // replace image into final merged values.yaml + retValuesYaml, err = commonservice.ReplaceImage(retValuesYaml, replaceValuesMap) + if err != nil { + return nil, nil, err + } + } + + return []byte(retValuesYaml), imageDetail, nil +} + +func handleSingleChart(chartData *DeliveryChartData, product *commonmodels.Product, chartRepo *commonmodels.HelmRepo, dir string, globalVariables string, + targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) (*ServiceImageDetails, error) { serviceObj := chartData.ServiceObj deliveryChartPath, err := ensureChartFiles(chartData, product) if err != nil { - return err + return nil, err } valuesYamlData := make(map[string]interface{}) valuesFilePath := filepath.Join(deliveryChartPath, setting.ValuesYaml) valueYamlContent, err := os.ReadFile(valuesFilePath) if err != nil { - return errors.Wrapf(err, "failed to read values.yaml for service %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "failed to read values.yaml for service %s", serviceObj.ServiceName) } // write values.yaml file before load if len(chartData.ChartData.ValuesYamlContent) > 0 { // values.yaml was edited directly if err = yaml.Unmarshal([]byte(chartData.ChartData.ValuesYamlContent), map[string]interface{}{}); err != nil { log.Errorf("invalid yaml content, serviceName: %s, yamlContent: %s", serviceObj.ServiceName, chartData.ChartData.ValuesYamlContent) - return errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "invalid yaml content for service: %s", serviceObj.ServiceName) } valueYamlContent = []byte(chartData.ChartData.ValuesYamlContent) } else if len(globalVariables) > 0 { // merge global variables valueYamlContent, err = yamlutil.Merge([][]byte{valueYamlContent, []byte(globalVariables)}) if err != nil { - return errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "failed to merge global variables for service: %s", serviceObj.ServiceName) } } + // replace image url + valueYamlContent, imageDetail, err := handleImageRegistry(valueYamlContent, chartData, targetRegistry, registryMap) + if err != nil { + return nil, errors.Wrapf(err, "failed to handle image registry for service: %s", serviceObj.ServiceName) + } + err = yaml.Unmarshal(valueYamlContent, &valuesYamlData) if err != nil { - return errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "failed to unmarshal values.yaml for service %s", serviceObj.ServiceName) } // hold the currently running yaml data @@ -702,13 +718,13 @@ func buildChartPackage(chartData *DeliveryChartData, product *commonmodels.Produ err = os.WriteFile(valuesFilePath, valueYamlContent, 0644) if err != nil { - return errors.Wrapf(err, "failed to write values.yaml file for service %s", serviceObj.ServiceName) + return nil, errors.Wrapf(err, "failed to write values.yaml file for service %s", serviceObj.ServiceName) } //load chart info from local storage chartRequested, err := chartloader.Load(deliveryChartPath) if err != nil { - return errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) + return nil, errors.Wrapf(err, "failed to load chart info, path %s", deliveryChartPath) } //set metadata @@ -719,24 +735,24 @@ func buildChartPackage(chartData *DeliveryChartData, product *commonmodels.Produ //create local chart package chartPackagePath, err := helm.CreateChartPackage(&helm.Chart{Chart: chartRequested}, dir) if err != nil { - return err + return nil, err } client, err := createChartRepoClient(chartRepo) if err != nil { - return errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) + return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) } log.Infof("pushing chart %s to %s...", filepath.Base(chartPackagePath), chartRepo.URL) resp, err := client.UploadChartPackage(chartPackagePath, false) if err != nil { - return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + return nil, errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) } err = handlePushResponse(resp) if err != nil { - return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) + return nil, errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) } - return nil + return imageDetail, nil } func handlePushResponse(resp *http.Response) error { @@ -950,26 +966,28 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer return fmt.Errorf("failed to build registry map") } + var targetRegistry *commonmodels.RegistryNamespace + for _, registry := range registryMap { + if registry.ID.Hex() == args.ImageRegistryID { + targetRegistry = registry + break + } + } + imagesDataMap := &sync.Map{} - // push charts to repo wg := sync.WaitGroup{} for _, chartData := range chartDataMap { wg.Add(1) go func(cData *DeliveryChartData) { defer wg.Done() - err := buildChartPackage(cData, deliveryVersion.ProductEnvInfo, repoInfo, dir, args.GlobalVariables, registryMap) + // generate new chart data, push to chart repo, extract related images + imageData, err := handleSingleChart(cData, deliveryVersion.ProductEnvInfo, repoInfo, dir, args.GlobalVariables, targetRegistry, registryMap) if err != nil { logger.Errorf("failed to build chart package, serviceName: %s err: %s", cData.ChartData.ServiceName, err) appendError(err) return } - imageData, err := extractImages(cData, registryMap) - if err != nil { - logger.Errorf("failed to extract image data, serviceName: %s err: %s", cData.ChartData.ServiceName, err) - appendError(err) - return - } imagesDataMap.Store(cData.ServiceObj.ServiceName, imageData) }(chartData) } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index f47af546..61ae9f96 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -18,14 +18,11 @@ package service import ( "fmt" - "regexp" - "strings" "time" helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "k8s.io/client-go/rest" - "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -39,8 +36,6 @@ import ( helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util/converter" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type UpdateContainerImageArgs struct { @@ -53,14 +48,6 @@ type UpdateContainerImageArgs struct { Image string `json:"image"` } -const ( - imageUrlParseRegexString = `(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?` -) - -var ( - imageParseRegex = regexp.MustCompile(imageUrlParseRegexString) -) - func getValidMatchData(spec *models.ImagePathSpec) map[string]string { ret := make(map[string]string) if spec.Repo != "" { @@ -75,81 +62,6 @@ func getValidMatchData(spec *models.ImagePathSpec) map[string]string { return ret } -// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx -func resolveImageUrl(imageUrl string) map[string]string { - subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) - result := make(map[string]string) - exNames := imageParseRegex.SubexpNames() - for i, matchedStr := range subMatchAll { - if i != 0 && matchedStr != "" && matchedStr != ":" { - result[exNames[i]] = matchedStr - } - } - return result -} - -// replace image defines in yaml by new version -func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { - nestedMap, err := converter.Expand(imageValuesMap) - if err != nil { - return "", err - } - bs, err := yaml.Marshal(nestedMap) - if err != nil { - return "", err - } - mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) - if err != nil { - return "", err - } - return string(mergedBs), nil -} - -// AssignImageData assign image url data into match data -// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path -// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value -func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { - ret := make(map[string]interface{}) - // total image url assigned into one single value - if len(matchData) == 1 { - for _, v := range matchData { - ret[v] = imageUrl - } - return ret, nil - } - - resolvedImageUrl := resolveImageUrl(imageUrl) - - // image url assigned into repo/image+tag - if len(matchData) == 3 { - ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") - ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] - ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] - return ret, nil - } - - if len(matchData) == 2 { - // image url assigned into repo/image + tag - if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { - ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] - for k, imagePath := range matchData { - if k == setting.PathSearchComponentTag { - continue - } - ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) - break - } - return ret, nil - } - // image url assigned into repo + image(tag) - ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") - ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) - return ret, nil - } - - return nil, fmt.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) -} - // prepare necessary data from db func prepareData(namespace, serviceName string, containerName string, product *models.Product) (targetContainer *models.Container, targetChart *templatemodels.RenderChart, renderSet *models.RenderSet, serviceObj *models.Service, err error) { @@ -228,13 +140,13 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri targetContainer.Image = image // prepare image replace info - replaceValuesMap, err = assignImageData(image, getValidMatchData(targetContainer.ImagePath)) + replaceValuesMap, err = commonservice.AssignImageData(image, getValidMatchData(targetContainer.ImagePath)) if err != nil { return fmt.Errorf("failed to pase image uri %s/%s, err %s", namespace, serviceName, err.Error()) } // replace image into service's values.yaml - replacedValuesYaml, err = replaceImage(targetChart.ValuesYaml, replaceValuesMap) + replacedValuesYaml, err = commonservice.ReplaceImage(targetChart.ValuesYaml, replaceValuesMap) if err != nil { return fmt.Errorf("failed to replace image uri %s/%s, err %s", namespace, serviceName, err.Error()) @@ -253,7 +165,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri } // replace image into final merged values.yaml - replacedMergedValuesYaml, err = replaceImage(mergedValuesYaml, replaceValuesMap) + replacedMergedValuesYaml, err = commonservice.ReplaceImage(mergedValuesYaml, replaceValuesMap) if err != nil { return err } -- Gitee From 488d66604728b902b707f9e1bf55b2ac6f193c1e Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 31 Dec 2021 16:49:45 +0800 Subject: [PATCH 1182/2647] fix missing update of get registry url Signed-off-by: allenshen --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index ce94cf6c..5e23b3a7 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -190,7 +190,7 @@ func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, for _, repo := range repos.Repos { for _, tag := range repo.Tags { img := &RepoImgResp{ - Host: util.GetURLHostName(registryInfo.RegAddr), + Host: util.TrimURLScheme(registryInfo.RegAddr), Owner: repo.Namespace, Name: repo.Name, Tag: tag, -- Gitee From e59744c59fc8db1ee9c8a439e1ecc465844e59d1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 17:00:29 +0800 Subject: [PATCH 1183/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/common/service/registry.go | 78 +++++++++---------- .../core/common/service/registry/service.go | 22 +++--- 2 files changed, 48 insertions(+), 52 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index d3703ac0..f47bc187 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -50,10 +50,7 @@ type awsKeyWithExpiration struct { } func (k *awsKeyWithExpiration) IsExpired() bool { - if time.Now().Unix() > k.Expiration { - return true - } - return false + return time.Now().Unix() > k.Expiration } func FindRegistryById(registryId string, getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { @@ -75,21 +72,20 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su } if getRealCredential { - switch resp.RegProvider { - case config.RegistryTypeSWR: - resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) - resp.SecretKey = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) - case config.RegistryTypeAWS: - realAK, realSK, err := getAWSRegistryCredential(resp.ID.Hex(), resp.AccessKey, resp.SecretKey, resp.Region) - if err != nil { - log.Errorf("Failed to get keypair from aws, the error is: %s", err) - return nil, err - } - resp.AccessKey = realAK - resp.SecretKey = realSK - default: - break + return resp, nil + } + switch resp.RegProvider { + case config.RegistryTypeSWR: + resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) + resp.SecretKey = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) + case config.RegistryTypeAWS: + realAK, realSK, err := getAWSRegistryCredential(resp.ID.Hex(), resp.AccessKey, resp.SecretKey, resp.Region) + if err != nil { + log.Errorf("Failed to get keypair from aws, the error is: %s", err) + return nil, err } + resp.AccessKey = realAK + resp.SecretKey = realSK } return resp, nil @@ -106,26 +102,25 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* return resp, fmt.Errorf("RegistryNamespace.List error: %s", err) } - if getRealCredential { - for _, reg := range resp { - switch reg.RegProvider { - case config.RegistryTypeSWR: - reg.AccessKey = fmt.Sprintf("%s@%s", reg.Region, reg.AccessKey) - reg.SecretKey = util.ComputeHmacSha256(reg.AccessKey, reg.SecretKey) - case config.RegistryTypeAWS: - realAK, realSK, err := getAWSRegistryCredential(reg.ID.Hex(), reg.AccessKey, reg.SecretKey, reg.Region) - if err != nil { - log.Errorf("Failed to get keypair from aws, the error is: %s", err) - return nil, err - } - reg.AccessKey = realAK - reg.SecretKey = realSK - default: - break + if !getRealCredential { + return resp, nil + } + + for _, reg := range resp { + switch reg.RegProvider { + case config.RegistryTypeSWR: + reg.AccessKey = fmt.Sprintf("%s@%s", reg.Region, reg.AccessKey) + reg.SecretKey = util.ComputeHmacSha256(reg.AccessKey, reg.SecretKey) + case config.RegistryTypeAWS: + realAK, realSK, err := getAWSRegistryCredential(reg.ID.Hex(), reg.AccessKey, reg.SecretKey, reg.Region) + if err != nil { + log.Errorf("Failed to get keypair from aws, the error is: %s", err) + return nil, err } + reg.AccessKey = realAK + reg.SecretKey = realSK } } - return resp, nil } @@ -161,21 +156,20 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient return nil } -func getAWSRegistryCredential(ID, AK, SK, Region string) (string, string, error) { +func getAWSRegistryCredential(id, ak, sk, region string) (realAK string, realSK string, err error) { // first we try to get ak/sk from our memory cache - obj, ok := awsKeyMap.Load(ID) + obj, ok := awsKeyMap.Load(id) if ok { keypair, ok := obj.(awsKeyWithExpiration) if ok { if !keypair.IsExpired() { - fmt.Printf("Getting aws ak/sk from memory cache: ak[%s], sk[%s]", keypair.AccessKey, keypair.SecretKey) return keypair.AccessKey, keypair.SecretKey, nil } } } - creds := credentials.NewStaticCredentials(AK, SK, "") + creds := credentials.NewStaticCredentials(ak, sk, "") config := &aws.Config{ - Region: aws.String(Region), + Region: aws.String(region), Credentials: creds, } sess, err := session.NewSession(config) @@ -197,10 +191,10 @@ func getAWSRegistryCredential(ID, AK, SK, Region string) (string, string, error) } keypair := strings.Split(string(rawDecodedText), ":") if len(keypair) != 2 { - return "", "", errors.New("decode keypair from aws response error") + return "", "", errors.New("format of keypair is invalid") } // cache the aws ak/sk - awsKeyMap.Store(ID, awsKeyWithExpiration{ + awsKeyMap.Store(id, awsKeyWithExpiration{ AccessKey: keypair[0], SecretKey: keypair[1], Expiration: time.Now().Add(expirationTime).Unix(), diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 8ca997d8..3e811869 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -82,9 +82,9 @@ type Service interface { func NewV2Service(provider string) Service { switch provider { case config.RegistryTypeSWR: - return &SwrService{} + return &swrService{} case config.RegistryTypeAWS: - return &EcrService{} + return &ecrService{} default: return &v2RegistryService{} } @@ -362,10 +362,10 @@ func (s *v2RegistryService) ListRepoImages(option ListRepoImagesOption, log *zap return resp, nil } -type SwrService struct { +type swrService struct { } -func (s *SwrService) createClient(ep Endpoint) (cli *swr.SwrClient) { +func (s *swrService) createClient(ep Endpoint) (cli *swr.SwrClient) { endpoint := fmt.Sprintf("https://swr-api.%s.myhuaweicloud.com", ep.Region) auth := basic.NewCredentialsBuilder(). WithAk(ep.Ak). @@ -380,7 +380,7 @@ func (s *SwrService) createClient(ep Endpoint) (cli *swr.SwrClient) { return client } -func (s *SwrService) ListRepoImages(option ListRepoImagesOption, log *zap.SugaredLogger) (resp *ReposResp, err error) { +func (s *swrService) ListRepoImages(option ListRepoImagesOption, log *zap.SugaredLogger) (resp *ReposResp, err error) { swrCli := s.createClient(option.Endpoint) var wg wait.Group @@ -435,7 +435,7 @@ func (s *SwrService) ListRepoImages(option ListRepoImagesOption, log *zap.Sugare } -func (s *SwrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (di *commonmodels.DeliveryImage, err error) { +func (s *swrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (di *commonmodels.DeliveryImage, err error) { swrCli := s.createClient(option.Endpoint) request := &model.ListRepositoryTagsRequest{Tag: &option.Tag, Namespace: option.Namespace, Repository: option.Image} @@ -458,10 +458,10 @@ func (s *SwrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.Suga return &commonmodels.DeliveryImage{}, nil } -type EcrService struct { +type ecrService struct { } -func (s *EcrService) getECRService(ep Endpoint, log *zap.SugaredLogger) (*ecr.ECR, error) { +func (s *ecrService) getECRService(ep Endpoint, log *zap.SugaredLogger) (*ecr.ECR, error) { creds := credentials.NewStaticCredentials(ep.Ak, ep.Sk, "") config := &aws.Config{ Region: aws.String(ep.Region), @@ -475,7 +475,7 @@ func (s *EcrService) getECRService(ep Endpoint, log *zap.SugaredLogger) (*ecr.EC return ecr.New(sess), nil } -func (s *EcrService) ListRepoImages(option ListRepoImagesOption, log *zap.SugaredLogger) (resp *ReposResp, err error) { +func (s *ecrService) ListRepoImages(option ListRepoImagesOption, log *zap.SugaredLogger) (resp *ReposResp, err error) { svc, err := s.getECRService(option.Endpoint, log) if err != nil { return nil, err @@ -527,7 +527,7 @@ func (s *EcrService) ListRepoImages(option ListRepoImagesOption, log *zap.Sugare return resp, nil } -func (s *EcrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (di *commonmodels.DeliveryImage, err error) { +func (s *ecrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (di *commonmodels.DeliveryImage, err error) { svc, err := s.getECRService(option.Endpoint, log) if err != nil { return nil, err @@ -545,6 +545,8 @@ func (s *EcrService) GetImageInfo(option GetRepoImageDetailOption, log *zap.Suga err = errors.Wrapf(err, "failed to get image info of %s:%s", option.Image, option.Tag) return } + // since only one image tag is passed, only one image detail will be in this detail list + // so only the first one will be used for _, imageDetail := range result.ImageDetails { return &commonmodels.DeliveryImage{ RepoName: option.Image, -- Gitee From 915d778e2ef8cfbbac50ce01aafe02b1ecb2f06a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 31 Dec 2021 17:16:54 +0800 Subject: [PATCH 1184/2647] fix reaper problem Signed-off-by: liu deyi --- .../common/repository/models/task/docker_build.go | 3 ++- .../workflow/service/workflow/workflow_task.go | 15 ++++++++++----- .../reaper/core/service/meta/types.go | 3 ++- .../warpdrive/core/service/types/task/build.go | 3 ++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go index 8d334a4f..900780bc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go @@ -55,12 +55,13 @@ func (db *DockerBuild) ToSubTask() (map[string]interface{}, error) { // ImageBuild: build image镜像全称, e.g. xxx.com/release-candidates/image:tag type DockerBuildCtx struct { Source string `yaml:"source" bson:"source" json:"source"` - TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` + //TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + DockerTemplateContent string `yaml:"docker_template_content" bson:"docker_template_content" json:"docker_template_content"` } type FileArchiveCtx struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 481ee0ee..d84d12b5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1962,12 +1962,17 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.EnableProxy = module.PreBuild.EnableProxy if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { + dockerTemplateContent := "" + if module.PostBuild.DockerBuild.TemplateID != "" { + dockerTemplateContent = "" + } build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ - Source: module.PostBuild.DockerBuild.Source, - TemplateID: module.PostBuild.DockerBuild.TemplateID, - WorkDir: module.PostBuild.DockerBuild.WorkDir, - DockerFile: module.PostBuild.DockerBuild.DockerFile, - BuildArgs: module.PostBuild.DockerBuild.BuildArgs, + Source: module.PostBuild.DockerBuild.Source, + //TemplateID: module.PostBuild.DockerBuild.TemplateID, + WorkDir: module.PostBuild.DockerBuild.WorkDir, + DockerFile: module.PostBuild.DockerBuild.DockerFile, + BuildArgs: module.PostBuild.DockerBuild.BuildArgs, + DockerTemplateContent: dockerTemplateContent, } } diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 254e8cb6..34a21b45 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -133,12 +133,13 @@ type ArtifactInfo struct { // ImageBuild: build image镜像全称, e.g. xxx.com/spock-release-candidates/image:tag type DockerBuildCtx struct { Source string `yaml:"source" bson:"source" json:"source"` - TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` + //TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + DockerTemplateContent string `yaml:"docker_template_content" bson:"docker_template_content" json:"docker_template_content"` } func (c *DockerBuildCtx) GetDockerFile() string { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index be0200cf..d342c098 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -210,7 +210,8 @@ type DockerBuildCtx struct { BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` Source string `yaml:"source" bson:"source" json:"source"` - TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + //TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + DockerTemplateContent string `yaml:"docker_template_content" bson:"docker_template_content" json:"docker_template_content"` } type FileArchiveCtx struct { -- Gitee From 1e3b27f283ac3e811e009690d455d2ba7046be5f Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 17:22:13 +0800 Subject: [PATCH 1185/2647] code review changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index f47bc187..729c8ce5 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -71,7 +71,7 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su } } - if getRealCredential { + if !getRealCredential { return resp, nil } switch resp.RegProvider { -- Gitee From 2332f625e2ad374c489ab416ed3c7942dae24583 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 31 Dec 2021 18:03:15 +0800 Subject: [PATCH 1186/2647] code review changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/environment/service/image.go | 2 +- pkg/microservice/aslan/core/environment/service/service.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index a2b30b55..ede1eaa8 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -300,7 +300,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log // aws secrets needs to be refreshed regs, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { - log.Errorf("Failed to get registries to update container images, the error is: %s\n", err) + log.Errorf("Failed to get registries to update container images, the error is: %s", err) return err } for _, reg := range regs { diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index ccd31225..593ce613 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -150,7 +150,7 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { // aws secrets needs to be refreshed regs, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) if err != nil { - log.Errorf("Failed to get registries to restart container, the error is: %s\n", err) + log.Errorf("Failed to get registries to restart container, the error is: %s", err) return err } for _, reg := range regs { @@ -348,7 +348,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e // aws secrets needs to be refreshed regs, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { - log.Errorf("Failed to get registries to restart container, the error is: %s\n", err) + log.Errorf("Failed to get registries to restart container, the error is: %s", err) return err } for _, reg := range regs { -- Gitee From f4882c62fb308857726365d07a99effeea6e6e47 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 4 Jan 2022 10:42:37 +0800 Subject: [PATCH 1187/2647] fix review Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 34 +++++++++---------- pkg/setting/consts.go | 1 - 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 528a70b5..7ccfbd8a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -165,7 +165,7 @@ type RawYamlResp struct { YamlContent string `json:"yamlContent"` } -type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, cl client.Client, log *zap.SugaredLogger) error +type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) @@ -2234,16 +2234,16 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, cl client.Client) error { +func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err } - return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, isRetry, helmClient, cl) + return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, isRetry, helmClient, kubecli) } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, cl client.Client) error { +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2298,7 +2298,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha rel := hrs[0] if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", rel.Name, rel.Version) - deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, cl) + deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, kubecli) if deleteErr != nil { err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) return err @@ -2310,7 +2310,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha return nil } -func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, cl client.Client, log *zap.SugaredLogger) { +func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, kubecli client.Client, log *zap.SugaredLogger) { var ( err error errList = &multierror.Error{} @@ -2345,10 +2345,10 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, cl client.Client, logger *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] timeout := time.Second * setting.DeployTimeout - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, timeout, isRetry, helmClient, cl) + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, timeout, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) @@ -2380,7 +2380,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, cl, log) + serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, kubecli, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2449,14 +2449,14 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, cl client.Client, log *zap.SugaredLogger) []error { +func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, kubecli client.Client, log *zap.SugaredLogger) []error { bo := backoff.NewConstantBackOff(time.Second * 3) retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) isRetry := false _ = backoff.Retry(func() error { failedServices := make([]*commonmodels.Service, 0) - errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, cl, log) + errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, kubecli, log) if len(errList) == 0 { return nil } @@ -2468,7 +2468,7 @@ func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, servic return errList } -func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, cl client.Client, log *zap.SugaredLogger) []error { +func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, kubecli client.Client, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } @@ -2481,7 +2481,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic for _, data := range serviceList { go func() { defer wg.Done() - err := handler(data, isRetry, cl, log) + err := handler(data, isRetry, kubecli, log) if err != nil { errList = append(errList, err) *failedServices = append(*failedServices, data) @@ -2560,9 +2560,9 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, cl client.Client, log *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, isRetry, helmClient, cl) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) @@ -2812,9 +2812,9 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(service *commonmodels.Service, isRetry bool, cl client.Client, log *zap.SugaredLogger) error { + handler := func(service *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, isRetry, helmClient, cl) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 5b4f04f2..cab6319f 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -587,5 +587,4 @@ const LocalClusterID = "0123456789abcdef12345678" const RequestModeOpenAPI = "openAPI" -// DeployTimeout ... const DeployTimeout = 60 * 10 // 10 minutes -- Gitee From ae2fea4cb0ef0ca2a3d19489037036ddafbb3b1e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 4 Jan 2022 11:37:43 +0800 Subject: [PATCH 1188/2647] timeout done Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 20 ++++++++++++++----- .../core/service/taskplugin/deploy.go | 20 ++++++++++++++----- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7ccfbd8a..e0befaff 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2279,12 +2279,22 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha chartSpec.Wait = true chartSpec.Timeout = timeout } - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { - err = errors.WithMessagef( - err, - "failed to Install helm chart %s/%s", - namespace, serviceObj.ServiceName) + done := make(chan bool) + go func(chan bool) { + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to Install helm chart %s/%s", + namespace, serviceObj.ServiceName) + } else { + done <- true + } + }(done) + + select { + case <-done: + case <-time.After(chartSpec.Timeout + 5*time.Second): hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) if errHistory != nil { err = errors.WithMessagef( diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index b85c1861..72a3f86f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -543,12 +543,22 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Timeout: time.Second * setting.DeployTimeout, Wait: true, } - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { - err = errors.WithMessagef( - err, - "failed to Install helm chart %s/%s", - p.Task.Namespace, p.Task.ServiceName) + done := make(chan bool) + go func(chan bool) { + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to Install helm chart %s/%s", + p.Task.Namespace, p.Task.ServiceName) + } else { + done <- true + } + }(done) + + select { + case <-done: + case <-time.After(time.Second * (setting.DeployTimeout + 5)): hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) if errHistory != nil { err = errors.WithMessagef( -- Gitee From ec3b0bdbc9ff127d41ba91e379850a3964839bfc Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 4 Jan 2022 12:02:21 +0800 Subject: [PATCH 1189/2647] add suggested next chart verison Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7b780974..f71a593c 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -31,6 +31,7 @@ import ( "sync" "time" + "github.com/blang/semver/v4" cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" "github.com/hashicorp/go-multierror" @@ -186,9 +187,10 @@ type ServiceImageDetails struct { } type ChartVersionResp struct { - ChartName string `json:"chartName"` - ChartVersion string `json:"chartVersion"` - Url string `json:"url"` + ChartName string `json:"chartName"` + ChartVersion string `json:"chartVersion"` + NextChartVersion string `json:"nextChartVersion"` + Url string `json:"url"` } type DeliveryVersionPayloadImage struct { @@ -669,6 +671,7 @@ func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, target } } + imageDetail.Registries = registrySet.List() return []byte(retValuesYaml), imageDetail, nil } @@ -1595,6 +1598,7 @@ func GetChartVersion(chartName, chartRepoName string) ([]*ChartVersionResp, erro chartNameList := strings.Split(chartName, ",") chartNameSet := sets.NewString(chartNameList...) + existedChartSet := sets.NewString() ret := make([]*ChartVersionResp, 0) @@ -1606,9 +1610,33 @@ func GetChartVersion(chartName, chartRepoName string) ([]*ChartVersionResp, erro continue } latestEntry := entry[0] + + // generate suggested next chart version + nextVersion := latestEntry.Version + t, err := semver.Make(latestEntry.Version) + if err != nil { + log.Errorf("failed to parse current version: %s, err: %s", latestEntry.Version, err) + } else { + t.Patch = t.Patch + 1 + nextVersion = t.String() + } + + ret = append(ret, &ChartVersionResp{ + ChartName: name, + ChartVersion: latestEntry.Version, + NextChartVersion: nextVersion, + }) + existedChartSet.Insert(name) + } + + for _, singleChartName := range chartNameList { + if existedChartSet.Has(singleChartName) { + continue + } ret = append(ret, &ChartVersionResp{ - ChartName: name, - ChartVersion: latestEntry.Version, + ChartName: singleChartName, + ChartVersion: "", + NextChartVersion: "1.0.0", }) } -- Gitee From d1cacb215b5a7a1b6451e24dcee45f1f60b5e355 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 4 Jan 2022 13:35:54 +0800 Subject: [PATCH 1190/2647] timeout done Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 19 +++++++++++---- .../core/service/taskplugin/deploy.go | 24 +++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index e0befaff..16664074 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2281,20 +2281,20 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha } done := make(chan bool) + defer close(done) go func(chan bool) { if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", namespace, serviceObj.ServiceName) + done <- false } else { done <- true } }(done) - select { - case <-done: - case <-time.After(chartSpec.Timeout + 5*time.Second): + pendingStatusProcess := func(typ string) error { hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) if errHistory != nil { err = errors.WithMessagef( @@ -2315,9 +2315,20 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha } } } + if err != nil { + err = fmt.Errorf("failed to install %s:%s", typ, err) + } return err } - return nil + select { + case d := <-done: + if !d { + err = pendingStatusProcess("normal") + } + case <-time.After(chartSpec.Timeout + 5*time.Second): + err = pendingStatusProcess("timeout") + } + return err } func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, kubecli client.Client, log *zap.SugaredLogger) { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 72a3f86f..08916cb0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -545,27 +545,27 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } done := make(chan bool) + defer close(done) go func(chan bool) { if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", p.Task.Namespace, p.Task.ServiceName) + done <- false } else { done <- true } }(done) - select { - case <-done: - case <-time.After(time.Second * (setting.DeployTimeout + 5)): + pendingStatusProcess := func(typ string) error { hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) if errHistory != nil { err = errors.WithMessagef( err, "failed to ListReleaseHistory: %s,error:%s", chartSpec.ReleaseName, errHistory) - return + return err } if len(hrs) > 0 { releaseutil.Reverse(hrs, releaseutil.SortByRevision) @@ -575,10 +575,24 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, p.kubeClient) if deleteErr != nil { err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) - return + return err } } } + if err != nil { + err = fmt.Errorf("failed to install %s:%s", typ, err) + } + return err + } + select { + case d := <-done: + if !d { + err = pendingStatusProcess("normal") + } + case <-time.After(chartSpec.Timeout + 5*time.Second): + err = pendingStatusProcess("timeout") + } + if err != nil { return } -- Gitee From 46f83bbac203de5cf7d84c169bd7b84cddb73660 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 13:50:32 +0800 Subject: [PATCH 1191/2647] fix reaper problem Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index d84d12b5..acb5e361 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1964,7 +1964,8 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { dockerTemplateContent := "" if module.PostBuild.DockerBuild.TemplateID != "" { - dockerTemplateContent = "" + //dockerfileDetail, err := service.GetDockerfileTemplateDetail(module.PostBuild.DockerBuild.TemplateID, log) + //dockerTemplateContent = dockerfileDetail.Content } build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ Source: module.PostBuild.DockerBuild.Source, -- Gitee From 4aa65e85cc4f5a4d4d5a7e5a3fff37fb38ae6bed Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 4 Jan 2022 15:28:51 +0800 Subject: [PATCH 1192/2647] wait for reading logs to complete before calling cmd.Wait Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/script.go | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 2461f2af..43a3970d 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -27,6 +27,7 @@ import ( "path" "path/filepath" "strings" + "sync" "github.com/koderover/zadig/pkg/microservice/reaper/config" "github.com/koderover/zadig/pkg/microservice/reaper/internal/s3" @@ -229,22 +230,38 @@ func (r *Reaper) runScripts() error { needPersistentLog := len(r.Ctx.PostScripts) > 0 + var wg sync.WaitGroup + cmdStdoutReader, err := cmd.StdoutPipe() if err != nil { return err } - go r.handleCmdOutput(cmdStdoutReader, needPersistentLog, fileName) + + wg.Add(1) + go func() { + defer wg.Done() + + r.handleCmdOutput(cmdStdoutReader, needPersistentLog, fileName) + }() cmdStdErrReader, err := cmd.StderrPipe() if err != nil { return err } - go r.handleCmdOutput(cmdStdErrReader, needPersistentLog, fileName) + + wg.Add(1) + go func() { + defer wg.Done() + + r.handleCmdOutput(cmdStdErrReader, needPersistentLog, fileName) + }() if err := cmd.Start(); err != nil { return err } + wg.Wait() + return cmd.Wait() } @@ -410,7 +427,7 @@ func (r *Reaper) handleCmdOutput(pipe io.ReadCloser, needPersistentLog bool, log break } - log.Errorf("Failed to read stdout log: %s", err) + log.Errorf("Failed to read log when processing cmd output: %s", err) break } @@ -419,7 +436,7 @@ func (r *Reaper) handleCmdOutput(pipe io.ReadCloser, needPersistentLog bool, log if needPersistentLog { err := util.WriteFile(logFile, lineBytes, 0700) if err != nil { - log.Warnf("Failed to write stdout file: %s", err) + log.Warnf("Failed to write file when processing cmd output: %s", err) } } } -- Gitee From 65db8788baea7170545281efe2ea2e93e0b08947 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 4 Jan 2022 15:43:55 +0800 Subject: [PATCH 1193/2647] space bugfix Signed-off-by: mouuii --- pkg/microservice/aslan/core/service/service/service.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c33b9a2e..a62f3967 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1244,7 +1244,7 @@ func setCurrentContainerImages(args *commonmodels.Service) error { var srvContainers []*commonmodels.Container for _, data := range args.KubeYamls { yamlDataArray := SplitYaml(data) - for _, yamlData := range yamlDataArray { + for index, yamlData := range yamlDataArray { resKind := new(KubeResourceKind) //在Unmarshal之前填充渲染变量{{.}} yamlData = config.RenderTemplateAlias.ReplaceAllLiteralString(yamlData, "ssssssss") @@ -1256,6 +1256,9 @@ func setCurrentContainerImages(args *commonmodels.Service) error { } if resKind == nil { + if index == 0 { + continue + } return errors.New("nil ReourceKind") } -- Gitee From 294a59a0eab531bc37963c2ec9571269251b38cc Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 4 Jan 2022 15:46:54 +0800 Subject: [PATCH 1194/2647] add filter Signed-off-by: panxunying --- .../handler/collaboration_mode.go | 5 - .../core/collaboration/handler/router.go | 2 +- .../repository/models/collaboration_mode.go | 2 +- .../repository/mongodb/collaboration_mode.go | 32 ++++--- .../service/collaboration_instance.go | 95 +++++++++++++++++++ pkg/microservice/aslan/server/rest/router.go | 30 +++--- pkg/microservice/aslan/server/rest/server.go | 1 + pkg/middleware/gin/collaboration.go | 29 ++++++ pkg/util/random.go | 4 + 9 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 pkg/middleware/gin/collaboration.go diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index e3a7060f..2849b5d5 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -50,11 +50,6 @@ func CreateCollaborationMode(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "协作模式", args.Name, string(data), ctx.Logger) - if err := c.BindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - ctx.Err = service.CreateCollaborationMode(ctx.UserName, args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 973d92ce..8f454df3 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -31,6 +31,6 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations.GET("", GetCollaborationMode) collaborations.PUT("", gin2.UpdateOperationLogStatus, UpdateCollaborationMode) collaborations.POST("", gin2.UpdateOperationLogStatus, CreateCollaborationMode) - collaborations.DELETE("", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) + collaborations.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) } } diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go index 790b9cda..2ce9d748 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -26,7 +26,7 @@ type CollaborationMode struct { ProjectName string `bson:"project_name" json:"project_name"` CreateTime int64 `bson:"create_time" json:"create_time"` UpdateTime int64 `bson:"update_time" json:"update_time"` - Name string `bson:"name,omitempty" json:"name"` + Name string `bson:"name" json:"name"` Revision int64 `bson:"revision" json:"revision"` Members []string `bson:"members" json:"members"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index f64951b3..11abd56e 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -32,8 +32,9 @@ import ( type CollaborationModeFindOptions struct { ProjectName string - Name string + Name string ` ` Members string + IsDeleted bool } type CollaborationModeColl struct { @@ -110,15 +111,14 @@ func (c *CollaborationModeColl) Create(userName string, args *models.Collaborati if args == nil { return errors.New("nil CollaborationMode") } - res, err := c.Find(&CollaborationModeFindOptions{ + _, err := c.Find(&CollaborationModeFindOptions{ Name: args.Name, ProjectName: args.ProjectName, + IsDeleted: false, }) - if err != nil { - return err - } - if res != nil { - return errors.New("CollaborationMode has exist") + + if err == nil { + return errors.New("该项目下的协作模式已存在") } now := time.Now().Unix() args.CreateBy = userName @@ -132,17 +132,19 @@ func (c *CollaborationModeColl) Create(userName string, args *models.Collaborati } func (c *CollaborationModeColl) Delete(username, projectName, name string) error { - res, err := c.Find(&CollaborationModeFindOptions{ + _, err := c.Find(&CollaborationModeFindOptions{ Name: name, ProjectName: projectName, + IsDeleted: false, }) - if err != nil { - return err - } - if res != nil { - return errors.New("CollaborationMode has exist") - } - query := bson.M{"name": name, "project_name": projectName} + if err == nil { + deleteQuery := bson.M{"name": name, "project_name": projectName, "is_deleted": true} + _, err = c.DeleteOne(context.TODO(), deleteQuery) + if err != nil { + return err + } + } + query := bson.M{"name": name, "project_name": projectName, "is_deleted": false} change := bson.M{"$set": bson.M{ "delete_time": time.Now().Unix(), "delete_by": username, diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index b5558bb4..bf410277 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -8,6 +8,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" + "github.com/koderover/zadig/pkg/util" ) type GetCollaborationUpdateResp struct { @@ -44,6 +45,26 @@ type UpdateProductItem struct { New models.ProductCIItem `json:"new"` } +type Workflow struct { + CollaborationType string `json:"collaboration_type"` + BaseName string `json:"base_name"` + CollaborationMode string `json:"collaboration_mode"` + Name string `json:"name"` + Description string `json:"description"` +} + +type Product struct { + CollaborationType string `json:"collaboration_type"` + BaseName string `json:"base_name"` + CollaborationMode string `json:"collaboration_mode"` + Name string `json:"name"` +} +type GetCollaborationNewResp struct { + Code int64 `json:"code"` + Workflow []Workflow `json:"workflow"` + Product []Product `json:"product"` +} + func getUpdateWorkflowDiff(cmwMap map[string]models.WorkflowCMItem, ciwMap map[string]models.WorkflowCIItem) ( []UpdateWorkflowItem, []models.WorkflowCMItem, []models.WorkflowCIItem) { var updateWorkflowItems []UpdateWorkflowItem @@ -188,3 +209,77 @@ func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) } return resp, nil } +func buildName(baseName, modeName, userName string) string { + return modeName + baseName + userName + util.GetRandomString(6) +} + +func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { + var newWorkflow []Workflow + var newProduct []Product + updateResp, err := GetCollaborationUpdate(projectName, uid, logger) + if err != nil { + logger.Errorf("GetCollaborationNew error, err msg:%s", err) + return nil, err + } + for _, mode := range updateResp.New { + for _, workflow := range mode.Workflows { + newWorkflow = append(newWorkflow, Workflow{ + CollaborationType: workflow.CollaborationType, + BaseName: workflow.Name, + CollaborationMode: mode.Name, + Name: buildName(workflow.Name, mode.Name, userName), + }) + } + for _, product := range mode.Products { + newProduct = append(newProduct, Product{ + CollaborationType: product.CollaborationType, + BaseName: product.Name, + CollaborationMode: mode.Name, + Name: buildName(product.Name, mode.Name, userName), + }) + } + } + for _, item := range updateResp.Update { + for _, workflow := range item.NewSpec.Workflows { + newWorkflow = append(newWorkflow, Workflow{ + CollaborationType: workflow.CollaborationType, + BaseName: workflow.Name, + CollaborationMode: item.CollaborationMode, + Name: buildName(workflow.Name, item.CollaborationMode, userName), + }) + } + for _, product := range item.NewSpec.Products { + newProduct = append(newProduct, Product{ + CollaborationType: product.CollaborationType, + BaseName: product.Name, + CollaborationMode: item.CollaborationMode, + Name: buildName(product.Name, item.CollaborationMode, userName), + }) + } + for _, workflow := range item.UpdateSpec.Workflows { + if workflow.Old.CollaborationType == "share" && workflow.New.CollaborationType == "new" { + newWorkflow = append(newWorkflow, Workflow{ + CollaborationType: workflow.New.CollaborationType, + BaseName: workflow.New.BaseName, + CollaborationMode: item.CollaborationMode, + Name: buildName(workflow.New.BaseName, item.CollaborationMode, userName), + }) + } + } + for _, product := range item.UpdateSpec.Products { + if product.Old.CollaborationType == "share" && product.New.CollaborationType == "new" { + newProduct = append(newProduct, Product{ + CollaborationType: product.New.CollaborationType, + BaseName: product.New.BaseName, + CollaborationMode: item.CollaborationMode, + Name: buildName(product.New.BaseName, item.CollaborationMode, userName), + }) + } + } + } + return &GetCollaborationNewResp{ + Code: 10000, + Workflow: newWorkflow, + Product: newProduct, + }, nil +} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 1d27760b..4d6f8e70 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -70,21 +70,21 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { router.GET("/api/kodespace/downloadUrl", commonhandler.GetToolDownloadURL) for name, r := range map[string]injector{ - "/api/project": new(projecthandler.Router), - "/api/code": new(codehosthandler.Router), - "/api/system": new(systemhandler.Router), - "/api/service": new(servicehandler.Router), - "/api/setting": new(settinghandler.Router), - "/api/environment": new(environmenthandler.Router), - "/api/cron": new(cronhandler.Router), - "/api/workflow": new(workflowhandler.Router), - "/api/build": new(buildhandler.Router), - "/api/delivery": new(deliveryhandler.Router), - "/api/logs": new(loghandler.Router), - "/api/testing": new(testinghandler.Router), - "/api/cluster": new(multiclusterhandler.Router), - "/api/template": new(templatehandler.Router), - "/api/v1": new(collaborationhandler.Router), + "/api/project": new(projecthandler.Router), + "/api/code": new(codehosthandler.Router), + "/api/system": new(systemhandler.Router), + "/api/service": new(servicehandler.Router), + "/api/setting": new(settinghandler.Router), + "/api/environment": new(environmenthandler.Router), + "/api/cron": new(cronhandler.Router), + "/api/workflow": new(workflowhandler.Router), + "/api/build": new(buildhandler.Router), + "/api/delivery": new(deliveryhandler.Router), + "/api/logs": new(loghandler.Router), + "/api/testing": new(testinghandler.Router), + "/api/cluster": new(multiclusterhandler.Router), + "/api/template": new(templatehandler.Router), + "/api/collaboration": new(collaborationhandler.Router), } { r.Inject(router.Group(name)) } diff --git a/pkg/microservice/aslan/server/rest/server.go b/pkg/microservice/aslan/server/rest/server.go index 18843b0a..54094ad5 100644 --- a/pkg/microservice/aslan/server/rest/server.go +++ b/pkg/microservice/aslan/server/rest/server.go @@ -52,6 +52,7 @@ func (s *engine) injectMiddlewares() { if s.mode == gin.TestMode { return } + g.Use(ginmiddleware.GetCollaborationNew()) g.Use(ginmiddleware.Response()) g.Use(ginmiddleware.RequestID()) g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go new file mode 100644 index 00000000..550d3b05 --- /dev/null +++ b/pkg/middleware/gin/collaboration.go @@ -0,0 +1,29 @@ +package gin + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetCollaborationNew() gin.HandlerFunc { + return func(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + c.Next() + } + newResp, err := service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) + if err != nil { + ctx.Err = err + } + if len(newResp.Product) == 0 && len(newResp.Workflow) == 0 { + c.Next() + } else { + ctx.Resp = newResp + } + } +} diff --git a/pkg/util/random.go b/pkg/util/random.go index 1cbd6b4d..0ae20360 100644 --- a/pkg/util/random.go +++ b/pkg/util/random.go @@ -23,6 +23,10 @@ import ( "github.com/google/uuid" ) +func init() { + rand.Seed(time.Now().UnixNano()) +} + func UUID() string { return uuid.New().String() } -- Gitee From ef96559e30d1cfb7af979b6e33f93953c44038d6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 16:01:03 +0800 Subject: [PATCH 1195/2647] fix reaper multi cluster problem Signed-off-by: liu deyi --- .../repository/models/chart.go | 36 ++++--- .../repository/models/dockerfile.go | 0 .../common/repository/models/yaml_template.go | 8 +- .../repository/mongodb/chart.go | 5 +- .../repository/mongodb/dockerfile.go | 2 +- ...elation.go => project_cluster_relation.go} | 0 .../service/dockerfile}/chart.go | 99 ++++++++++--------- .../service/dockerfile}/dockerfile.go | 29 +++--- .../service/dockerfile}/types.go | 49 +++++---- .../service/dockerfile}/yaml.go | 42 ++++---- .../core/common/service/template_store.go | 2 +- pkg/microservice/aslan/core/service.go | 11 +-- .../aslan/core/service/service/helm.go | 25 +++-- .../aslan/core/templatestore/handler/chart.go | 5 +- .../core/templatestore/handler/dockerfile.go | 2 +- .../aslan/core/templatestore/handler/yaml.go | 5 +- .../workflow/service/workflow/nsq_handlers.go | 12 +-- .../service/workflow/workflow_task.go | 6 +- .../reaper/core/service/client/client.go | 41 -------- .../reaper/core/service/client/dockerfile.go | 60 ----------- .../reaper/core/service/reaper/reaper.go | 16 ++- 21 files changed, 175 insertions(+), 280 deletions(-) rename pkg/microservice/aslan/core/{templatestore => common}/repository/models/chart.go (41%) rename pkg/microservice/aslan/core/{templatestore => common}/repository/models/dockerfile.go (100%) rename pkg/microservice/aslan/core/{templatestore => common}/repository/mongodb/chart.go (92%) rename pkg/microservice/aslan/core/{templatestore => common}/repository/mongodb/dockerfile.go (97%) rename pkg/microservice/aslan/core/common/repository/mongodb/{projectClusterRelation.go => project_cluster_relation.go} (100%) rename pkg/microservice/aslan/core/{templatestore/service => common/service/dockerfile}/chart.go (79%) rename pkg/microservice/aslan/core/{templatestore/service => common/service/dockerfile}/dockerfile.go (85%) rename pkg/microservice/aslan/core/{templatestore/service => common/service/dockerfile}/types.go (52%) rename pkg/microservice/aslan/core/{templatestore/service => common/service/dockerfile}/yaml.go (83%) delete mode 100644 pkg/microservice/reaper/core/service/client/client.go delete mode 100644 pkg/microservice/reaper/core/service/client/dockerfile.go diff --git a/pkg/microservice/aslan/core/templatestore/repository/models/chart.go b/pkg/microservice/aslan/core/common/repository/models/chart.go similarity index 41% rename from pkg/microservice/aslan/core/templatestore/repository/models/chart.go rename to pkg/microservice/aslan/core/common/repository/models/chart.go index 48c02f04..3f7135e1 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/models/chart.go +++ b/pkg/microservice/aslan/core/common/repository/models/chart.go @@ -16,27 +16,31 @@ limitations under the License. package models -import commonmodes "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - type Chart struct { - Name string `bson:"name" json:"name"` - Source string `bson:"source" json:"source"` - Owner string `bson:"owner" json:"owner"` - Repo string `bson:"repo" json:"repo"` - Path string `bson:"path" json:"path"` - Branch string `bson:"branch" json:"branch"` - CodeHostID int `bson:"codehost_id" json:"codeHostID"` - Revision int64 `bson:"revision" json:"revision"` - Variables []*commonmodes.Variable `bson:"variables" json:"variables"` - Sha1 string `bson:"sha1" json:"sha1"` + Name string `bson:"name" json:"name"` + Source string `bson:"source" json:"source"` + Owner string `bson:"owner" json:"owner"` + Repo string `bson:"repo" json:"repo"` + Path string `bson:"path" json:"path"` + Branch string `bson:"branch" json:"branch"` + CodeHostID int `bson:"codehost_id" json:"codeHostID"` + Revision int64 `bson:"revision" json:"revision"` + ChartVariables []*ChartVariable `bson:"variables" json:"variables"` + Sha1 string `bson:"sha1" json:"sha1"` +} + +type ChartVariable struct { + Key string `json:"key"` + Value string `json:"value,omitempty"` + Description string `json:"description,omitempty"` } -func (chart *Chart) GetVariableMap() map[string]*commonmodes.Variable { - if len(chart.Variables) == 0 { +func (chart *Chart) GetVariableMap() map[string]*ChartVariable { + if len(chart.ChartVariables) == 0 { return nil } - ret := make(map[string]*commonmodes.Variable) - for _, v := range chart.Variables { + ret := make(map[string]*ChartVariable) + for _, v := range chart.ChartVariables { ret[v.Key] = v } return ret diff --git a/pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go b/pkg/microservice/aslan/core/common/repository/models/dockerfile.go similarity index 100% rename from pkg/microservice/aslan/core/templatestore/repository/models/dockerfile.go rename to pkg/microservice/aslan/core/common/repository/models/dockerfile.go diff --git a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go index f3347106..825005ae 100644 --- a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go +++ b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go @@ -19,10 +19,10 @@ package models import "go.mongodb.org/mongo-driver/bson/primitive" type YamlTemplate struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Content string `bson:"content" json:"content"` - Variables []*Variable `bson:"variables" json:"variables"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Content string `bson:"content" json:"content"` + ChartVariables []*ChartVariable `bson:"variables" json:"variables"` } type Variable struct { diff --git a/pkg/microservice/aslan/core/templatestore/repository/mongodb/chart.go b/pkg/microservice/aslan/core/common/repository/mongodb/chart.go similarity index 92% rename from pkg/microservice/aslan/core/templatestore/repository/mongodb/chart.go rename to pkg/microservice/aslan/core/common/repository/mongodb/chart.go index 2de408cc..0fe2ed25 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/mongodb/chart.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/chart.go @@ -26,8 +26,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -143,5 +142,5 @@ func (c *ChartColl) Delete(name string) error { func getNextRevision(name string) (int64, error) { template := fmt.Sprintf(chartTemplateCounterName, name) - return commonrepo.NewCounterColl().GetNextSeq(template) + return NewCounterColl().GetNextSeq(template) } diff --git a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go b/pkg/microservice/aslan/core/common/repository/mongodb/dockerfile.go similarity index 97% rename from pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go rename to pkg/microservice/aslan/core/common/repository/mongodb/dockerfile.go index 36b131df..f5e36be4 100644 --- a/pkg/microservice/aslan/core/templatestore/repository/mongodb/dockerfile.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/dockerfile.go @@ -26,7 +26,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go b/pkg/microservice/aslan/core/common/repository/mongodb/project_cluster_relation.go similarity index 100% rename from pkg/microservice/aslan/core/common/repository/mongodb/projectClusterRelation.go rename to pkg/microservice/aslan/core/common/repository/mongodb/project_cluster_relation.go diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/common/service/dockerfile/chart.go similarity index 79% rename from pkg/microservice/aslan/core/templatestore/service/chart.go rename to pkg/microservice/aslan/core/common/service/dockerfile/chart.go index c8f26471..33aefa37 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/common/service/dockerfile/chart.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package service +package dockerfile import ( "fmt" @@ -29,10 +29,11 @@ import ( "k8s.io/apimachinery/pkg/util/wait" configbase "github.com/koderover/zadig/pkg/config" - commonmodes "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -46,12 +47,12 @@ var ( ) type ChartTemplateListResp struct { - SystemVariables []*Variable `json:"systemVariables"` - ChartTemplates []*Chart `json:"chartTemplates"` + SystemVariables []*commonmodels.ChartVariable `json:"systemVariables"` + ChartTemplates []*Chart `json:"chartTemplates"` } func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { - chart, err := mongodb.NewChartColl().Get(name) + chart, err := commonrepo.NewChartColl().Get(name) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", name, err) return nil, err @@ -71,9 +72,9 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { return nil, err } - variables := make([]*Variable, 0, len(chart.Variables)) - for _, v := range chart.Variables { - variables = append(variables, &Variable{ + variables := make([]*commonmodels.ChartVariable, 0, len(chart.ChartVariables)) + for _, v := range chart.ChartVariables { + variables = append(variables, &commonmodels.ChartVariable{ Key: v.Key, Value: v.Value, }) @@ -91,16 +92,16 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { }, nil } -func GetChartTemplateVariables(name string, logger *zap.SugaredLogger) ([]*Variable, error) { +func GetChartTemplateVariables(name string, logger *zap.SugaredLogger) ([]*commonmodels.ChartVariable, error) { chart, err := mongodb.NewChartColl().Get(name) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", name, err) return nil, err } - variables := make([]*Variable, 0) - for _, v := range chart.Variables { - variables = append(variables, &Variable{ + variables := make([]*commonmodels.ChartVariable, 0) + for _, v := range chart.ChartVariables { + variables = append(variables, &commonmodels.ChartVariable{ Key: v.Key, Value: v.Value, }) @@ -108,10 +109,10 @@ func GetChartTemplateVariables(name string, logger *zap.SugaredLogger) ([]*Varia return variables, nil } -func getChartTemplateDefaultVariables() []*Variable { - resp := make([]*Variable, 0) +func getChartTemplateDefaultVariables() []*commonmodels.ChartVariable { + resp := make([]*commonmodels.ChartVariable, 0) for key, description := range ChartTemplateDefaultSystemVariable { - resp = append(resp, &Variable{ + resp = append(resp, &commonmodels.ChartVariable{ Key: key, Description: description, }) @@ -206,22 +207,22 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. return errors.Wrapf(err, "failed to prase variables") } - variables := make([]*commonmodes.Variable, 0, len(variablesNames)) + variables := make([]*commonmodels.ChartVariable, 0, len(variablesNames)) for _, v := range variablesNames { - variables = append(variables, &commonmodes.Variable{ + variables = append(variables, &commonmodels.ChartVariable{ Key: v, }) } return mongodb.NewChartColl().Create(&models.Chart{ - Name: name, - Owner: args.Owner, - Repo: args.Repo, - Path: args.Path, - Branch: args.Branch, - CodeHostID: args.CodehostID, - Sha1: sha1, - Variables: variables, + Name: name, + Owner: args.Owner, + Repo: args.Repo, + Path: args.Path, + Branch: args.Branch, + CodeHostID: args.CodehostID, + Sha1: sha1, + ChartVariables: variables, }) } @@ -248,11 +249,11 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z return errors.Wrapf(err, "failed to prase variables") } - variables := make([]*commonmodes.Variable, 0, len(variablesNames)) + variables := make([]*commonmodels.ChartVariable, 0, len(variablesNames)) curVariableMap := chart.GetVariableMap() for _, vName := range variablesNames { - variable := &commonmodes.Variable{ + variable := &commonmodels.ChartVariable{ Key: vName, } if v, ok := curVariableMap[vName]; ok { @@ -262,18 +263,18 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z } return mongodb.NewChartColl().Update(&models.Chart{ - Name: name, - Owner: args.Owner, - Repo: args.Repo, - Path: args.Path, - Branch: args.Branch, - CodeHostID: args.CodehostID, - Sha1: sha1, - Variables: variables, + Name: name, + Owner: args.Owner, + Repo: args.Repo, + Path: args.Path, + Branch: args.Branch, + CodeHostID: args.CodehostID, + Sha1: sha1, + ChartVariables: variables, }) } -func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.SugaredLogger) error { +func UpdateChartTemplateVariables(name string, args []*commonmodels.Variable, logger *zap.SugaredLogger) error { chart, err := mongodb.NewChartColl().Get(name) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", name, err) @@ -281,9 +282,9 @@ func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.Sug } requestKeys := sets.NewString() - variables := make([]*commonmodes.Variable, 0) + variables := make([]*commonmodels.ChartVariable, 0) for _, variable := range args { - variables = append(variables, &commonmodes.Variable{ + variables = append(variables, &commonmodels.ChartVariable{ Key: variable.Key, Value: variable.Value, }) @@ -291,7 +292,7 @@ func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.Sug } curKeys := sets.NewString() - for _, variable := range chart.Variables { + for _, variable := range chart.ChartVariables { curKeys.Insert(variable.Key) } @@ -300,14 +301,14 @@ func UpdateChartTemplateVariables(name string, args []*Variable, logger *zap.Sug } err = mongodb.NewChartColl().Update(&models.Chart{ - Name: name, - Owner: chart.Owner, - Repo: chart.Repo, - Path: chart.Path, - Branch: chart.Branch, - CodeHostID: chart.CodeHostID, - Sha1: chart.Sha1, - Variables: variables, + Name: name, + Owner: chart.Owner, + Repo: chart.Repo, + Path: chart.Path, + Branch: chart.Branch, + CodeHostID: chart.CodeHostID, + Sha1: chart.Sha1, + ChartVariables: variables, }) if err != nil { diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/common/service/dockerfile/dockerfile.go similarity index 85% rename from pkg/microservice/aslan/core/templatestore/service/dockerfile.go rename to pkg/microservice/aslan/core/common/service/dockerfile/dockerfile.go index 95f33151..9f00b28c 100644 --- a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go +++ b/pkg/microservice/aslan/core/common/service/dockerfile/dockerfile.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package service +package dockerfile import ( "errors" @@ -25,14 +25,13 @@ import ( dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" "go.uber.org/zap" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" ) func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredLogger) error { - err := mongodb.NewDockerfileTemplateColl().Create(&models.DockerfileTemplate{ + err := commonrepo.NewDockerfileTemplateColl().Create(&commonmodels.DockerfileTemplate{ Name: template.Name, Content: template.Content, }) @@ -43,9 +42,9 @@ func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredL } func UpdateDockerfileTemplate(id string, template *DockerfileTemplate, logger *zap.SugaredLogger) error { - err := mongodb.NewDockerfileTemplateColl().Update( + err := commonrepo.NewDockerfileTemplateColl().Update( id, - &models.DockerfileTemplate{ + &commonmodels.DockerfileTemplate{ Name: template.Name, Content: template.Content, }, @@ -58,7 +57,7 @@ func UpdateDockerfileTemplate(id string, template *DockerfileTemplate, logger *z func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*DockerfileListObject, int, error) { resp := make([]*DockerfileListObject, 0) - templateList, total, err := mongodb.NewDockerfileTemplateColl().List(pageNum, pageSize) + templateList, total, err := commonrepo.NewDockerfileTemplateColl().List(pageNum, pageSize) if err != nil { logger.Errorf("list dockerfile template error: %s", err) return resp, 0, err @@ -74,7 +73,7 @@ func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*DockerfileDetail, error) { resp := new(DockerfileDetail) - dockerfileTemplate, err := mongodb.NewDockerfileTemplateColl().GetById(id) + dockerfileTemplate, err := commonrepo.NewDockerfileTemplateColl().GetById(id) if err != nil { logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) return nil, err @@ -99,7 +98,7 @@ func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { if len(ref) > 0 { return errors.New("this template is in use") } - err = mongodb.NewDockerfileTemplateColl().DeleteByID(id) + err = commonrepo.NewDockerfileTemplateColl().DeleteByID(id) if err != nil { logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) } @@ -136,24 +135,24 @@ func ValidateDockerfileTemplate(template string, logger *zap.SugaredLogger) erro return nil } -func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { - ret := make([]*Variable, 0) +func getVariables(s string, logger *zap.SugaredLogger) ([]*commonmodels.ChartVariable, error) { + ret := make([]*commonmodels.ChartVariable, 0) reader := strings.NewReader(s) result, err := dockerfileparser.Parse(reader) if err != nil { logger.Errorf("Failed to parse the dockerfile from source, the error is: %s", err) - return []*Variable{}, err + return []*commonmodels.ChartVariable{}, err } stages, metaArgs, err := dockerfileinstructions.Parse(result.AST) if err != nil { logger.Errorf("Failed to parse stages from generated dockerfile AST, the error is: %s", err) - return []*Variable{}, err + return []*commonmodels.ChartVariable{}, err } keyMap := make(map[string]int) for _, metaArg := range metaArgs { for _, arg := range metaArg.Args { if keyMap[arg.Key] == 0 { - ret = append(ret, &Variable{ + ret = append(ret, &commonmodels.ChartVariable{ Key: arg.Key, Value: arg.ValueString(), }) @@ -174,7 +173,7 @@ func getVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { } // if key has not been added yet if keyMap[key] == 0 { - ret = append(ret, &Variable{ + ret = append(ret, &commonmodels.ChartVariable{ Key: key, Value: value, }) diff --git a/pkg/microservice/aslan/core/templatestore/service/types.go b/pkg/microservice/aslan/core/common/service/dockerfile/types.go similarity index 52% rename from pkg/microservice/aslan/core/templatestore/service/types.go rename to pkg/microservice/aslan/core/common/service/dockerfile/types.go index 613fb6b1..285f698b 100644 --- a/pkg/microservice/aslan/core/templatestore/service/types.go +++ b/pkg/microservice/aslan/core/common/service/dockerfile/types.go @@ -14,18 +14,21 @@ See the License for the specific language governing permissions and limitations under the License. */ -package service +package dockerfile -import "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" +) type Chart struct { - Name string `json:"name"` - CodehostID int `json:"codehostID"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Branch string `json:"branch"` - Path string `json:"path"` - Variables []*Variable `json:"variables,omitempty"` + Name string `json:"name"` + CodehostID int `json:"codehostID"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Branch string `json:"branch"` + Path string `json:"path"` + Variables []*models.ChartVariable `json:"variables,omitempty"` Files []*fs.FileInfo `json:"files,omitempty"` } @@ -41,16 +44,10 @@ type DockerfileListObject struct { } type DockerfileDetail struct { - ID string `json:"id"` - Name string `json:"name"` - Content string `json:"content"` - Variables []*Variable `json:"variable"` -} - -type Variable struct { - Key string `json:"key"` - Value string `json:"value,omitempty"` - Description string `json:"description,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*models.ChartVariable `json:"variable"` } type BuildReference struct { @@ -59,9 +56,9 @@ type BuildReference struct { } type YamlTemplate struct { - Name string `json:"name"` - Content string `json:"content"` - Variable []*Variable `json:"variable"` + Name string `json:"name"` + Content string `json:"content"` + Variable []*models.ChartVariable `json:"variable"` } type YamlListObject struct { @@ -70,10 +67,10 @@ type YamlListObject struct { } type YamlDetail struct { - ID string `json:"id"` - Name string `json:"name"` - Content string `json:"content"` - Variables []*Variable `json:"variable"` + ID string `json:"id"` + Name string `json:"name"` + Content string `json:"content"` + Variables []*models.ChartVariable `json:"variable"` } type ServiceReference struct { diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go similarity index 83% rename from pkg/microservice/aslan/core/templatestore/service/yaml.go rename to pkg/microservice/aslan/core/common/service/dockerfile/yaml.go index ac55502a..cbbe58d4 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package service +package dockerfile import ( "errors" @@ -34,17 +34,17 @@ var DefaultSystemVariable = map[string]string{ } func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { - vars := make([]*models.Variable, 0) + vars := make([]*models.ChartVariable, 0) for _, variable := range template.Variable { - vars = append(vars, &models.Variable{ + vars = append(vars, &models.ChartVariable{ Key: variable.Key, Value: variable.Value, }) } err := commonrepo.NewYamlTemplateColl().Create(&models.YamlTemplate{ - Name: template.Name, - Content: template.Content, - Variables: vars, + Name: template.Name, + Content: template.Content, + ChartVariables: vars, }) if err != nil { logger.Errorf("create dockerfile template error: %s", err) @@ -53,9 +53,9 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error } func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { - vars := make([]*models.Variable, 0) + vars := make([]*models.ChartVariable, 0) for _, variable := range template.Variable { - vars = append(vars, &models.Variable{ + vars = append(vars, &models.ChartVariable{ Key: variable.Key, Value: variable.Value, }) @@ -63,9 +63,9 @@ func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLo err := commonrepo.NewYamlTemplateColl().Update( id, &models.YamlTemplate{ - Name: template.Name, - Content: template.Content, - Variables: vars, + Name: template.Name, + Content: template.Content, + ChartVariables: vars, }, ) if err != nil { @@ -97,9 +97,9 @@ func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, e logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) return nil, err } - variables := make([]*Variable, 0) - for _, v := range yamlTemplate.Variables { - variables = append(variables, &Variable{ + variables := make([]*models.ChartVariable, 0) + for _, v := range yamlTemplate.ChartVariables { + variables = append(variables, &models.ChartVariable{ Key: v.Key, Value: v.Value, }) @@ -143,19 +143,19 @@ func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceR return ret, nil } -func GetYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) { - resp := make([]*Variable, 0) +func GetYamlVariables(s string, logger *zap.SugaredLogger) ([]*models.ChartVariable, error) { + resp := make([]*models.ChartVariable, 0) regex, err := regexp.Compile(setting.RegExpParameter) if err != nil { logger.Errorf("Cannot get regexp from the expression: %s, the error is: %s", setting.RegExpParameter, err) - return []*Variable{}, err + return []*models.ChartVariable{}, err } params := regex.FindAllString(s, -1) keyMap := make(map[string]int) for _, param := range params { key := getParameterKey(param) if keyMap[key] == 0 { - resp = append(resp, &Variable{ + resp = append(resp, &models.ChartVariable{ Key: key, }) keyMap[key] = 1 @@ -164,10 +164,10 @@ func GetYamlVariables(s string, logger *zap.SugaredLogger) ([]*Variable, error) return resp, nil } -func GetSystemDefaultVariables() []*Variable { - resp := make([]*Variable, 0) +func GetSystemDefaultVariables() []*models.ChartVariable { + resp := make([]*models.ChartVariable, 0) for key, description := range DefaultSystemVariable { - resp = append(resp, &Variable{ + resp = append(resp, &models.ChartVariable{ Key: key, Description: description, }) diff --git a/pkg/microservice/aslan/core/common/service/template_store.go b/pkg/microservice/aslan/core/common/service/template_store.go index b97b14a9..68d5269c 100644 --- a/pkg/microservice/aslan/core/common/service/template_store.go +++ b/pkg/microservice/aslan/core/common/service/template_store.go @@ -16,7 +16,7 @@ limitations under the License. package service -import "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" +import "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" func GetDockerfileTemplateContent(id string) (string, error) { dockerfileTemplate, err := mongodb.NewDockerfileTemplateColl().GetById(id) diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 1dbece5a..d06114f6 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -36,7 +36,6 @@ import ( projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" - templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" @@ -206,16 +205,12 @@ func initDatabase() { commonrepo.NewWorkLoadsStatColl(), commonrepo.NewServicesInExternalEnvColl(), commonrepo.NewExternalLinkColl(), - - templaterepo.NewChartColl(), - templaterepo.NewDockerfileTemplateColl(), - - templaterepo.NewChartColl(), - templaterepo.NewDockerfileTemplateColl(), + commonrepo.NewChartColl(), + commonrepo.NewDockerfileTemplateColl(), + commonrepo.NewProjectClusterRelationColl(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), - commonrepo.NewProjectClusterRelationColl(), } { wg.Add(1) go func(r indexer) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 5ac0d28a..849fbff9 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -37,13 +37,12 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - templatestore "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -53,9 +52,9 @@ import ( ) type HelmService struct { - ServiceInfos []*models.Service `json:"service_infos"` - FileInfos []*types.FileInfo `json:"file_infos"` - Services [][]string `json:"services"` + ServiceInfos []*commonmodels.Service `json:"service_infos"` + FileInfos []*types.FileInfo `json:"file_infos"` + Services [][]string `json:"services"` } type HelmServiceArgs struct { @@ -72,8 +71,8 @@ type HelmServiceInfo struct { } type HelmServiceModule struct { - ServiceModules []*ServiceModule `json:"service_modules"` - Service *models.Service `json:"service,omitempty"` + ServiceModules []*ServiceModule `json:"service_modules"` + Service *commonmodels.Service `json:"service,omitempty"` } type Chart struct { @@ -106,7 +105,7 @@ type helmServiceCreationArgs struct { type ChartTemplateData struct { TemplateName string - TemplateData *templatestore.Chart + TemplateData *commonmodels.Chart ChartName string ChartVersion string DefaultValuesYAML []byte // content of values.yaml in template @@ -121,7 +120,7 @@ type GetFileContentParam struct { func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, error) { helmService := &HelmService{ - ServiceInfos: []*models.Service{}, + ServiceInfos: []*commonmodels.Service{}, FileInfos: []*types.FileInfo{}, Services: [][]string{}, } @@ -224,7 +223,7 @@ func GetFileContent(serviceName, productName string, param *GetFileContentParam, } func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (*ChartTemplateData, error) { - templateChart, err := mongodb.NewChartColl().Get(templateName) + templateChart, err := commonrepo.NewChartColl().Get(templateName) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", templateName, err) return nil, fmt.Errorf("failed to get chart template: %s", templateName) @@ -874,7 +873,7 @@ func renderVariablesToYaml(valuesYaml string, productName, serviceName string, v return valuesYaml, nil } -func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*models.Service, error) { +func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*commonmodels.Service, error) { chartName, chartVersion, err := readChartYAML(fsTree, args.ServiceName, logger) if err != nil { logger.Errorf("Failed to read chart.yaml, err %s", err) @@ -894,7 +893,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, errors.Wrapf(err, "Failed to parse service from yaml") } - serviceObj := &models.Service{ + serviceObj := &commonmodels.Service{ ServiceName: args.ServiceName, Type: setting.HelmDeployType, Revision: args.ServiceRevision, @@ -911,7 +910,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg SrcPath: args.RepoLink, CreateFrom: geneCreationDetail(args), Source: args.Source, - HelmChart: &models.HelmChart{ + HelmChart: &commonmodels.HelmChart{ Name: chartName, Version: chartVersion, ValuesYaml: valuesYaml, diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 873c493e..41ed7a80 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -19,8 +19,9 @@ package handler import ( "github.com/gin-gonic/gin" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/errors" ) @@ -90,7 +91,7 @@ func UpdateChartTemplateVariables(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := make([]*templateservice.Variable, 0) + args := make([]*commonmodels.Variable, 0) if err := c.ShouldBindJSON(&args); err != nil { ctx.Err = errors.ErrInvalidParam.AddErr(err) return diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 76f16a8d..873bfd9f 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -21,7 +21,7 @@ import ( "github.com/gin-gonic/gin" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 9a0619a3..1a93b084 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -18,8 +18,9 @@ package handler import ( "github.com/gin-gonic/gin" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -57,7 +58,7 @@ type listYamlQuery struct { } type ListYamlResp struct { - SystemVariables []*templateservice.Variable `json:"system_variables"` + SystemVariables []*commonmodels.ChartVariable `json:"system_variables"` YamlTemplates []*templateservice.YamlListObject `json:"yaml_template"` Total int `json:"total"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 8aaa2cf0..d0b42e4e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -238,12 +238,12 @@ func getRawFileContent(codehostID int, repo, owner, branch, filePath string) ([] func (h *TaskAckHandler) getDockerfileContent(build *types.Repository, ctx *task.DockerBuildCtx) string { switch ctx.Source { case setting.DockerfileSourceTemplate: - content, err := commonservice.GetDockerfileTemplateContent(ctx.TemplateID) - if err != nil { - h.log.Errorf("Failed to get dockerfile template content, err: %v", err) - return "" - } - return content + //content, err := commonservice.GetDockerfileTemplateContent(ctx.TemplateID) + //if err != nil { + // h.log.Errorf("Failed to get dockerfile template content, err: %v", err) + // return "" + //} + return ctx.DockerTemplateContent case setting.DockerfileSourceLocal: path := ctx.DockerFile pathArray := strings.Split(path, "/") diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e3954aff..adb98a82 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -39,6 +39,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" "github.com/koderover/zadig/pkg/setting" @@ -1954,8 +1955,9 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { dockerTemplateContent := "" if module.PostBuild.DockerBuild.TemplateID != "" { - //dockerfileDetail, err := service.GetDockerfileTemplateDetail(module.PostBuild.DockerBuild.TemplateID, log) - //dockerTemplateContent = dockerfileDetail.Content + if dockerfileDetail, err := dockerfile.GetDockerfileTemplateDetail(module.PostBuild.DockerBuild.TemplateID, log); err == nil { + dockerTemplateContent = dockerfileDetail.Content + } } build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ Source: module.PostBuild.DockerBuild.Source, diff --git a/pkg/microservice/reaper/core/service/client/client.go b/pkg/microservice/reaper/core/service/client/client.go deleted file mode 100644 index e72b2ec3..00000000 --- a/pkg/microservice/reaper/core/service/client/client.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package client - -import ( - "net/http" - "net/http/cookiejar" -) - -type Client struct { - APIBase string - Token string - Conn *http.Client -} - -// NewAslanClient is to get aslan client func -func NewAslanClient(host, token string) *Client { - jar, _ := cookiejar.New(nil) - - c := &Client{ - Token: token, - APIBase: host, - Conn: &http.Client{Transport: http.DefaultTransport, Jar: jar}, - } - - return c -} diff --git a/pkg/microservice/reaper/core/service/client/dockerfile.go b/pkg/microservice/reaper/core/service/client/dockerfile.go deleted file mode 100644 index 32e2db86..00000000 --- a/pkg/microservice/reaper/core/service/client/dockerfile.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package client - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - - "github.com/pkg/errors" - - "github.com/koderover/zadig/pkg/tool/log" -) - -type Dockerfile struct { - ID string `json:"id"` - Name string `json:"name"` - Content string `json:"content"` -} - -func (c *Client) GetDockerfile(id string) (*Dockerfile, error) { - resp := new(Dockerfile) - var err error - - url := fmt.Sprintf("%s/api/template/dockerfile/%s", c.APIBase, id) - request, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Errorf("new http request error: %v", err) - return nil, err - } - // No auth required - var ret *http.Response - if ret, err = c.Conn.Do(request); err == nil { - defer func() { _ = ret.Body.Close() }() - var body []byte - body, err = ioutil.ReadAll(ret.Body) - if err == nil { - if err = json.Unmarshal(body, &resp); err == nil { - return resp, nil - } - } - } - - return resp, errors.WithMessage(err, "failed to get dockerfile template") -} diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 08f8e1fa..3ece35e0 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -30,10 +30,8 @@ import ( "gopkg.in/yaml.v3" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/reaper/config" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/archive" - "github.com/koderover/zadig/pkg/microservice/reaper/core/service/client" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/meta" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" @@ -333,15 +331,15 @@ func (r *Reaper) runDockerBuild() error { func (r *Reaper) prepareDockerfile() error { if r.Ctx.DockerBuildCtx.Source == setting.DockerfileSourceTemplate { - aslanClient := client.NewAslanClient(configbase.AslanServiceAddress(), r.Ctx.APIToken) - dockerfile, err := aslanClient.GetDockerfile(r.Ctx.DockerBuildCtx.TemplateID) - if err != nil { - return err - } - reader := strings.NewReader(dockerfile.Content) + //aslanClient := client.NewAslanClient(configbase.AslanServiceAddress(), r.Ctx.APIToken) + //dockerfile, err := aslanClient.GetDockerfile(r.Ctx.DockerBuildCtx.TemplateID) + //if err != nil { + // return err + //} + reader := strings.NewReader(r.Ctx.DockerBuildCtx.DockerTemplateContent) readcloser := io.NopCloser(reader) path := fmt.Sprintf("/%s", setting.ZadigDockerfilePath) - err = fs.SaveFile(readcloser, path) + err := fs.SaveFile(readcloser, path) if err != nil { return err } -- Gitee From 7b5689d96a4fda5e53932344384a24730ad19fcf Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 16:12:26 +0800 Subject: [PATCH 1196/2647] remove docker template id Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/docker_build.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index d9abed53..cb9d526d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -135,7 +135,6 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi //Docker build context DockerBuildCtx: &task.DockerBuildCtx{ Source: p.Task.Source, - TemplateID: p.Task.TemplateID, WorkDir: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.WorkDir), DockerFile: fmt.Sprintf("%s/%s", pipelineCtx.Workspace, p.Task.DockerFile), ImageName: p.Task.Image, -- Gitee From de028eaa16f74ac310002ae236540f45bfbeab59 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 4 Jan 2022 16:13:45 +0800 Subject: [PATCH 1197/2647] optimize Signed-off-by: panxunying --- .../handler/collaboration_mode.go | 13 +++++--- .../repository/mongodb/collaboration_mode.go | 32 +++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index 2849b5d5..3f6b94a3 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -43,9 +43,13 @@ func CreateCollaborationMode(c *gin.Context) { data, err := c.GetRawData() if err != nil { log.Errorf("CreateCollaborationMode c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateCollaborationMode json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "协作模式", args.Name, string(data), ctx.Logger) @@ -61,18 +65,17 @@ func UpdateCollaborationMode(c *gin.Context) { data, err := c.GetRawData() if err != nil { log.Errorf("UpdateCollaborationMode c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return } if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateCollaborationMode json.Unmarshal err : %v", err) - } - - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "更新", "协作模式", args.Name, string(data), ctx.Logger) - - if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "更新", "协作模式", args.Name, string(data), ctx.Logger) + ctx.Err = service.UpdateCollaborationMode(ctx.UserName, args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index 11abd56e..d02452b1 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -84,26 +84,24 @@ func (c *CollaborationModeColl) Update(username string, args *models.Collaborati res, err := c.Find(&CollaborationModeFindOptions{ Name: args.Name, ProjectName: args.ProjectName, + IsDeleted: false, }) - if err != nil { + if err == nil { + query := bson.M{"name": args.Name, "project_name": args.ProjectName, "is_deleted": false} + change := bson.M{"$set": bson.M{ + "update_time": time.Now().Unix(), + "update_by": username, + "members": args.Members, + "workflows": args.Workflows, + "revision": res.Revision + 1, + "products": args.Products, + }} + + _, err = c.UpdateOne(context.TODO(), query, change) + return err } - if res != nil { - return errors.New("CollaborationMode has exist") - } - query := bson.M{"name": args.Name, "project_name": args.ProjectName} - change := bson.M{"$set": bson.M{ - "update_time": time.Now().Unix(), - "update_by": username, - "members": args.Members, - "workflows": args.Workflows, - "revision": res.Revision + 1, - "products": args.Products, - }} - - _, err = c.UpdateOne(context.TODO(), query, change) - - return err + return nil } func (c *CollaborationModeColl) Create(userName string, args *models.CollaborationMode) error { -- Gitee From b01b1fda111e6dac89f8e69fd8e4be5db1d97ee0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 4 Jan 2022 16:21:17 +0800 Subject: [PATCH 1198/2647] add interface to get user's permission Signed-off-by: mouuii --- .../policy/core/handler/permission.go | 38 ++++++++++++ .../policy/core/handler/router.go | 4 ++ .../policy/core/repository/mongodb/role.go | 18 ++++++ .../policy/core/service/permission.go | 60 +++++++++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 pkg/microservice/policy/core/handler/permission.go create mode 100644 pkg/microservice/policy/core/service/permission.go diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go new file mode 100644 index 00000000..a81a9f86 --- /dev/null +++ b/pkg/microservice/policy/core/handler/permission.go @@ -0,0 +1,38 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/policy/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func GetUserPermission(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + ctx.Resp, ctx.Err = service.GetPermission(projectName, c.Param("uid")) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 67d2fbd2..4c54752a 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -93,4 +93,8 @@ func (*Router) Inject(router *gin.RouterGroup) { { policySvrHealthz.GET("", Healthz) } + policyUserPermission := router.Group("permission") + { + policyUserPermission.GET("/:uid", GetUserPermission) + } } diff --git a/pkg/microservice/policy/core/repository/mongodb/role.go b/pkg/microservice/policy/core/repository/mongodb/role.go index ff13c67f..c17986ac 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role.go +++ b/pkg/microservice/policy/core/repository/mongodb/role.go @@ -113,6 +113,24 @@ func (c *RoleColl) ListBy(projectName string) ([]*models.Role, error) { return res, nil } +func (c *RoleColl) ListBySpaceAndName(projectName string, name string) ([]*models.Role, error) { + var res []*models.Role + + ctx := context.Background() + query := bson.M{"namespace": projectName, "name": name} + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} + func (c *RoleColl) Create(obj *models.Role) error { if obj == nil { return fmt.Errorf("nil object") diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go new file mode 100644 index 00000000..72747d56 --- /dev/null +++ b/pkg/microservice/policy/core/service/permission.go @@ -0,0 +1,60 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" +) + +func GetPermission(ns, uid string) (map[string][]string, error) { + roles := []*models.Role{} + rolebindingsSpeicy, err := mongodb.NewRoleBindingColl().ListBy(ns, "*") + if err != nil { + return nil, err + } + + // 1.2 get normal rolebindings + rolebindingsNormal, err := mongodb.NewRoleBindingColl().ListBy(ns, uid) + if err != nil { + return nil, err + } + + rolebindings := append(rolebindingsSpeicy, rolebindingsNormal...) + for _, v := range rolebindings { + tmpRoles, err := mongodb.NewRoleColl().ListBySpaceAndName(v.RoleRef.Namespace, v.RoleRef.Name) + if err != nil { + continue + } + roles = append(roles, tmpRoles...) + } + rolesResp := map[string][]string{} + resourceSet := sets.NewString() + for _, v := range roles { + for _, vv := range v.Rules { + if len(vv.Resources) > 0 { + if resourceSet.Has(vv.Resources[0]) { + vv.Verbs = append(rolesResp[vv.Resources[0]]) + } + rolesResp[vv.Resources[0]] = vv.Verbs + } + } + } + return rolesResp, nil +} -- Gitee From 7f52bbaed39ed2e5a9c32be44ddb0a0e5d006851 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 16:26:30 +0800 Subject: [PATCH 1199/2647] remove template id Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/job.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index cce70f6f..53b200f7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -299,12 +299,13 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.DockerBuildCtx != nil { ctx.DockerBuildCtx = &task.DockerBuildCtx{ - Source: b.JobCtx.DockerBuildCtx.Source, - TemplateID: b.JobCtx.DockerBuildCtx.TemplateID, - WorkDir: b.JobCtx.DockerBuildCtx.WorkDir, - DockerFile: b.JobCtx.DockerBuildCtx.DockerFile, - ImageName: b.JobCtx.DockerBuildCtx.ImageName, - BuildArgs: b.JobCtx.DockerBuildCtx.BuildArgs, + Source: b.JobCtx.DockerBuildCtx.Source, + //TemplateID: b.JobCtx.DockerBuildCtx.TemplateID, + WorkDir: b.JobCtx.DockerBuildCtx.WorkDir, + DockerFile: b.JobCtx.DockerBuildCtx.DockerFile, + ImageName: b.JobCtx.DockerBuildCtx.ImageName, + BuildArgs: b.JobCtx.DockerBuildCtx.BuildArgs, + DockerTemplateContent: b.JobCtx.DockerBuildCtx.DockerTemplateContent, } } -- Gitee From 43d5ff817bbc24800a08f7c6737edcff44190b45 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 4 Jan 2022 16:38:44 +0800 Subject: [PATCH 1200/2647] optimize Signed-off-by: panxunying --- .../core/collaboration/handler/collaboration_mode.go | 8 ++++++-- .../core/collaboration/service/collaboration_instance.go | 2 +- pkg/microservice/aslan/server/rest/server.go | 2 +- pkg/middleware/gin/collaboration.go | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index 3f6b94a3..6c9320ed 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -31,8 +31,12 @@ import ( func GetCollaborationMode(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.GetCollaborationModes(ctx.UserName, ctx.Logger) + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") + return + } + ctx.Resp, ctx.Err = service.GetCollaborationModes(projectName, ctx.Logger) } func CreateCollaborationMode(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index bf410277..098bfd10 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -210,7 +210,7 @@ func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) return resp, nil } func buildName(baseName, modeName, userName string) string { - return modeName + baseName + userName + util.GetRandomString(6) + return modeName + "-" + baseName + "-" + userName + "-" + util.GetRandomString(6) } func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { diff --git a/pkg/microservice/aslan/server/rest/server.go b/pkg/microservice/aslan/server/rest/server.go index 54094ad5..1ab48845 100644 --- a/pkg/microservice/aslan/server/rest/server.go +++ b/pkg/microservice/aslan/server/rest/server.go @@ -52,10 +52,10 @@ func (s *engine) injectMiddlewares() { if s.mode == gin.TestMode { return } - g.Use(ginmiddleware.GetCollaborationNew()) g.Use(ginmiddleware.Response()) g.Use(ginmiddleware.RequestID()) g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) + g.Use(ginmiddleware.GetCollaborationNew()) g.Use(gin.Recovery()) } diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index 550d3b05..bf8b9d1b 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -19,11 +19,13 @@ func GetCollaborationNew() gin.HandlerFunc { newResp, err := service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) if err != nil { ctx.Err = err + c.Abort() } if len(newResp.Product) == 0 && len(newResp.Workflow) == 0 { c.Next() } else { ctx.Resp = newResp + c.Abort() } } } -- Gitee From 2c0e4598b20c9754676efdec15fe4fac4ffcdf0c Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 4 Jan 2022 17:03:33 +0800 Subject: [PATCH 1201/2647] add interface to get user's permission Signed-off-by: mouuii --- pkg/microservice/policy/core/service/permission.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 72747d56..b2c3c366 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -25,7 +25,12 @@ import ( func GetPermission(ns, uid string) (map[string][]string, error) { roles := []*models.Role{} - rolebindingsSpeicy, err := mongodb.NewRoleBindingColl().ListBy(ns, "*") + rolebindingsReadOnly, err := mongodb.NewRoleBindingColl().ListBy(ns, "*") + if err != nil { + return nil, err + } + + rolebindingsAdmin, err := mongodb.NewRoleBindingColl().ListBy("*", uid) if err != nil { return nil, err } @@ -36,7 +41,8 @@ func GetPermission(ns, uid string) (map[string][]string, error) { return nil, err } - rolebindings := append(rolebindingsSpeicy, rolebindingsNormal...) + rolebindings := append(rolebindingsReadOnly, rolebindingsNormal...) + rolebindings = append(rolebindings, rolebindingsAdmin...) for _, v := range rolebindings { tmpRoles, err := mongodb.NewRoleColl().ListBySpaceAndName(v.RoleRef.Namespace, v.RoleRef.Name) if err != nil { -- Gitee From e214816db5dad8c918d4f08831275cae2044a6e4 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 4 Jan 2022 17:42:48 +0800 Subject: [PATCH 1202/2647] add collaborations policy Signed-off-by: panxunying --- .../policy/core/service/bundle/exemption_urls.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 61309aca..ff8a2be8 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -120,6 +120,10 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"PUT"}, Endpoints: []string{"api/aslan/cluster/clusters/?*/reconnect"}, }, + { + Methods: []string{"GET", "POST", "PUT", "DELETE"}, + Endpoints: []string{"api/collaboration/collaborations"}, + }, { Methods: []string{"POST", "PUT"}, Endpoints: []string{"api/aslan/system/install"}, @@ -308,6 +312,10 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/v1/users/search"}, }, + { + Methods: []string{"GET", "POST", "PUT", "DELETE"}, + Endpoints: []string{"api/collaboration/collaborations"}, + }, { Methods: []string{"GET"}, Endpoints: []string{"api/v1/roles"}, -- Gitee From a11c3657f7526a684b8c4ad1b2a4db5a3ea59076 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 18:48:00 +0800 Subject: [PATCH 1203/2647] remove annotation code Signed-off-by: liu deyi --- .../common/repository/models/task/docker_build.go | 13 ++++++------- .../core/workflow/service/workflow/nsq_handlers.go | 5 ----- .../core/workflow/service/workflow/workflow_task.go | 3 +-- pkg/microservice/reaper/core/service/meta/types.go | 13 ++++++------- .../reaper/core/service/reaper/reaper.go | 9 ++------- .../warpdrive/core/service/taskplugin/job.go | 3 +-- .../warpdrive/core/service/types/task/build.go | 13 ++++++------- 7 files changed, 22 insertions(+), 37 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go index 900780bc..387ec8cf 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/docker_build.go @@ -54,13 +54,12 @@ func (db *DockerBuild) ToSubTask() (map[string]interface{}, error) { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/release-candidates/image:tag type DockerBuildCtx struct { - Source string `yaml:"source" bson:"source" json:"source"` - WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` - DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` - ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` - BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` - ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` - //TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + Source string `yaml:"source" bson:"source" json:"source"` + WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` + DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` + ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` + BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` + ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` DockerTemplateContent string `yaml:"docker_template_content" bson:"docker_template_content" json:"docker_template_content"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index d0b42e4e..72904801 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -238,11 +238,6 @@ func getRawFileContent(codehostID int, repo, owner, branch, filePath string) ([] func (h *TaskAckHandler) getDockerfileContent(build *types.Repository, ctx *task.DockerBuildCtx) string { switch ctx.Source { case setting.DockerfileSourceTemplate: - //content, err := commonservice.GetDockerfileTemplateContent(ctx.TemplateID) - //if err != nil { - // h.log.Errorf("Failed to get dockerfile template content, err: %v", err) - // return "" - //} return ctx.DockerTemplateContent case setting.DockerfileSourceLocal: path := ctx.DockerFile diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index adb98a82..4e4ae314 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1960,8 +1960,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } } build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ - Source: module.PostBuild.DockerBuild.Source, - //TemplateID: module.PostBuild.DockerBuild.TemplateID, + Source: module.PostBuild.DockerBuild.Source, WorkDir: module.PostBuild.DockerBuild.WorkDir, DockerFile: module.PostBuild.DockerBuild.DockerFile, BuildArgs: module.PostBuild.DockerBuild.BuildArgs, diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 34a21b45..574258ac 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -132,13 +132,12 @@ type ArtifactInfo struct { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/spock-release-candidates/image:tag type DockerBuildCtx struct { - Source string `yaml:"source" bson:"source" json:"source"` - WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` - DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` - ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` - BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` - ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` - //TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + Source string `yaml:"source" bson:"source" json:"source"` + WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` + DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` + ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` + BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` + ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` DockerTemplateContent string `yaml:"docker_template_content" bson:"docker_template_content" json:"docker_template_content"` } diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 3ece35e0..3d76dac9 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -331,15 +331,10 @@ func (r *Reaper) runDockerBuild() error { func (r *Reaper) prepareDockerfile() error { if r.Ctx.DockerBuildCtx.Source == setting.DockerfileSourceTemplate { - //aslanClient := client.NewAslanClient(configbase.AslanServiceAddress(), r.Ctx.APIToken) - //dockerfile, err := aslanClient.GetDockerfile(r.Ctx.DockerBuildCtx.TemplateID) - //if err != nil { - // return err - //} reader := strings.NewReader(r.Ctx.DockerBuildCtx.DockerTemplateContent) - readcloser := io.NopCloser(reader) + readCloser := io.NopCloser(reader) path := fmt.Sprintf("/%s", setting.ZadigDockerfilePath) - err := fs.SaveFile(readcloser, path) + err := fs.SaveFile(readCloser, path) if err != nil { return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 53b200f7..ea7daf3b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -299,8 +299,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.DockerBuildCtx != nil { ctx.DockerBuildCtx = &task.DockerBuildCtx{ - Source: b.JobCtx.DockerBuildCtx.Source, - //TemplateID: b.JobCtx.DockerBuildCtx.TemplateID, + Source: b.JobCtx.DockerBuildCtx.Source, WorkDir: b.JobCtx.DockerBuildCtx.WorkDir, DockerFile: b.JobCtx.DockerBuildCtx.DockerFile, ImageName: b.JobCtx.DockerBuildCtx.ImageName, diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index d342c098..ec47fe2c 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -204,13 +204,12 @@ type SSH struct { // DockerFile: dockerfile名称, 默认为Dockerfile // ImageBuild: build image镜像全称, e.g. xxx.com/release-candidates/image:tag type DockerBuildCtx struct { - WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` - DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` - ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` - BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` - ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` - Source string `yaml:"source" bson:"source" json:"source"` - //TemplateID string `yaml:"template_id" bson:"template_id" json:"template_id"` + WorkDir string `yaml:"work_dir" bson:"work_dir" json:"work_dir"` + DockerFile string `yaml:"docker_file" bson:"docker_file" json:"docker_file"` + ImageName string `yaml:"image_name" bson:"image_name" json:"image_name"` + BuildArgs string `yaml:"build_args" bson:"build_args" json:"build_args"` + ImageReleaseTag string `yaml:"image_release_tag,omitempty" bson:"image_release_tag,omitempty" json:"image_release_tag"` + Source string `yaml:"source" bson:"source" json:"source"` DockerTemplateContent string `yaml:"docker_template_content" bson:"docker_template_content" json:"docker_template_content"` } -- Gitee From a7c019b709e626ba4de74f498a29d313ba95ad1e Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 4 Jan 2022 19:00:03 +0800 Subject: [PATCH 1204/2647] optimize Signed-off-by: panxunying --- pkg/middleware/gin/collaboration.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index bf8b9d1b..871112dd 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -15,6 +15,7 @@ func GetCollaborationNew() gin.HandlerFunc { projectName := c.Query("projectName") if projectName == "" { c.Next() + return } newResp, err := service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) if err != nil { -- Gitee From fcb677a17ddd6c9fea308616cbed37bf5d331dcd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 19:41:40 +0800 Subject: [PATCH 1205/2647] revert code Signed-off-by: liu deyi --- .../common/repository/models/yaml_template.go | 8 +++---- .../core/common/service/dockerfile/yaml.go | 22 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go index 825005ae..f3347106 100644 --- a/pkg/microservice/aslan/core/common/repository/models/yaml_template.go +++ b/pkg/microservice/aslan/core/common/repository/models/yaml_template.go @@ -19,10 +19,10 @@ package models import "go.mongodb.org/mongo-driver/bson/primitive" type YamlTemplate struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Content string `bson:"content" json:"content"` - ChartVariables []*ChartVariable `bson:"variables" json:"variables"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Content string `bson:"content" json:"content"` + Variables []*Variable `bson:"variables" json:"variables"` } type Variable struct { diff --git a/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go b/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go index cbbe58d4..455ccec3 100644 --- a/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go +++ b/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go @@ -34,17 +34,17 @@ var DefaultSystemVariable = map[string]string{ } func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { - vars := make([]*models.ChartVariable, 0) + vars := make([]*models.Variable, 0) for _, variable := range template.Variable { - vars = append(vars, &models.ChartVariable{ + vars = append(vars, &models.Variable{ Key: variable.Key, Value: variable.Value, }) } err := commonrepo.NewYamlTemplateColl().Create(&models.YamlTemplate{ - Name: template.Name, - Content: template.Content, - ChartVariables: vars, + Name: template.Name, + Content: template.Content, + Variables: vars, }) if err != nil { logger.Errorf("create dockerfile template error: %s", err) @@ -53,9 +53,9 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error } func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { - vars := make([]*models.ChartVariable, 0) + vars := make([]*models.Variable, 0) for _, variable := range template.Variable { - vars = append(vars, &models.ChartVariable{ + vars = append(vars, &models.Variable{ Key: variable.Key, Value: variable.Value, }) @@ -63,9 +63,9 @@ func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLo err := commonrepo.NewYamlTemplateColl().Update( id, &models.YamlTemplate{ - Name: template.Name, - Content: template.Content, - ChartVariables: vars, + Name: template.Name, + Content: template.Content, + Variables: vars, }, ) if err != nil { @@ -98,7 +98,7 @@ func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, e return nil, err } variables := make([]*models.ChartVariable, 0) - for _, v := range yamlTemplate.ChartVariables { + for _, v := range yamlTemplate.Variables { variables = append(variables, &models.ChartVariable{ Key: v.Key, Value: v.Value, -- Gitee From fd2b6dac5ae66e9f27dc02b5faace0551cfcf258 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 4 Jan 2022 19:49:17 +0800 Subject: [PATCH 1206/2647] Modify package name Signed-off-by: liu deyi --- .../core/common/service/{dockerfile => template}/chart.go | 0 .../common/service/{dockerfile => template}/dockerfile.go | 0 .../core/common/service/{dockerfile => template}/types.go | 0 .../core/common/service/{dockerfile => template}/yaml.go | 0 pkg/microservice/aslan/core/templatestore/handler/chart.go | 2 +- .../aslan/core/templatestore/handler/dockerfile.go | 2 +- pkg/microservice/aslan/core/templatestore/handler/yaml.go | 2 +- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 8 files changed, 5 insertions(+), 5 deletions(-) rename pkg/microservice/aslan/core/common/service/{dockerfile => template}/chart.go (100%) rename pkg/microservice/aslan/core/common/service/{dockerfile => template}/dockerfile.go (100%) rename pkg/microservice/aslan/core/common/service/{dockerfile => template}/types.go (100%) rename pkg/microservice/aslan/core/common/service/{dockerfile => template}/yaml.go (100%) diff --git a/pkg/microservice/aslan/core/common/service/dockerfile/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go similarity index 100% rename from pkg/microservice/aslan/core/common/service/dockerfile/chart.go rename to pkg/microservice/aslan/core/common/service/template/chart.go diff --git a/pkg/microservice/aslan/core/common/service/dockerfile/dockerfile.go b/pkg/microservice/aslan/core/common/service/template/dockerfile.go similarity index 100% rename from pkg/microservice/aslan/core/common/service/dockerfile/dockerfile.go rename to pkg/microservice/aslan/core/common/service/template/dockerfile.go diff --git a/pkg/microservice/aslan/core/common/service/dockerfile/types.go b/pkg/microservice/aslan/core/common/service/template/types.go similarity index 100% rename from pkg/microservice/aslan/core/common/service/dockerfile/types.go rename to pkg/microservice/aslan/core/common/service/template/types.go diff --git a/pkg/microservice/aslan/core/common/service/dockerfile/yaml.go b/pkg/microservice/aslan/core/common/service/template/yaml.go similarity index 100% rename from pkg/microservice/aslan/core/common/service/dockerfile/yaml.go rename to pkg/microservice/aslan/core/common/service/template/yaml.go diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 41ed7a80..fa466cb5 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -20,8 +20,8 @@ import ( "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/errors" ) diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 873bfd9f..bf37f6be 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -21,7 +21,7 @@ import ( "github.com/gin-gonic/gin" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 1a93b084..9836f148 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -20,7 +20,7 @@ import ( "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4e4ae314..1658c0cb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -39,9 +39,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/dockerfile" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" + templ "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -1955,7 +1955,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if module.PostBuild != nil && module.PostBuild.DockerBuild != nil { dockerTemplateContent := "" if module.PostBuild.DockerBuild.TemplateID != "" { - if dockerfileDetail, err := dockerfile.GetDockerfileTemplateDetail(module.PostBuild.DockerBuild.TemplateID, log); err == nil { + if dockerfileDetail, err := templ.GetDockerfileTemplateDetail(module.PostBuild.DockerBuild.TemplateID, log); err == nil { dockerTemplateContent = dockerfileDetail.Content } } -- Gitee From 84ecfbf2f845cab05ded0a354d9aa873bf3257d8 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 4 Jan 2022 19:56:46 +0800 Subject: [PATCH 1207/2647] add resource-server and dind in agent.yaml Signed-off-by: zhangyifei --- pkg/microservice/aslan/config/config.go | 4 + .../aslan/core/common/service/kube/service.go | 288 ++++++++++++++++-- .../core/multicluster/service/clusters.go | 2 +- pkg/setting/consts.go | 1 + 4 files changed, 277 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 121add2c..57917c6a 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -98,6 +98,10 @@ func HubAgentImage() string { return viper.GetString(setting.ENVHubAgentImage) } +func ResourceServerImage() string { + return viper.GetString(setting.ENVResourceServerImage) +} + func KodespaceVersion() string { return viper.GetString(setting.ENVKodespaceVersion) } diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index c5c9b5a1..a9bb662b 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -221,7 +221,7 @@ func (s *Service) ListConnectedClusters(logger *zap.SugaredLogger) ([]*models.K8 return clusters, nil } -func (s *Service) GetYaml(id, agentImage, aslanURL, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { +func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { var ( cluster *models.K8SCluster err error @@ -250,18 +250,20 @@ func (s *Service) GetYaml(id, agentImage, aslanURL, hubURI string, useDeployment if cluster.Namespace == "" { err = YamlTemplate.Execute(buffer, TemplateSchema{ - HubAgentImage: agentImage, - ClientToken: token, - HubServerBaseAddr: hubBase.String(), - UseDeployment: useDeployment, + HubAgentImage: agentImage, + ResourceServerImage: rsImage, + ClientToken: token, + HubServerBaseAddr: hubBase.String(), + UseDeployment: useDeployment, }) } else { err = YamlTemplateForNamespace.Execute(buffer, TemplateSchema{ - HubAgentImage: agentImage, - ClientToken: token, - HubServerBaseAddr: hubBase.String(), - UseDeployment: useDeployment, - Namespace: cluster.Namespace, + HubAgentImage: agentImage, + ResourceServerImage: rsImage, + ClientToken: token, + HubServerBaseAddr: hubBase.String(), + UseDeployment: useDeployment, + Namespace: cluster.Namespace, }) } @@ -273,11 +275,12 @@ func (s *Service) GetYaml(id, agentImage, aslanURL, hubURI string, useDeployment } type TemplateSchema struct { - HubAgentImage string - ClientToken string - HubServerBaseAddr string - Namespace string - UseDeployment bool + HubAgentImage string + ResourceServerImage string + ClientToken string + HubServerBaseAddr string + Namespace string + UseDeployment bool } var YamlTemplate = template.Must(template.New("agentYaml").Parse(` @@ -386,13 +389,138 @@ spec: memory: 1Gi requests: cpu: 100m - memory: 256Mi + memory: 256Mi {{- if .UseDeployment }} replicas: 1 {{- else }} updateStrategy: type: RollingUpdate {{- end }} + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: resource-server + namespace: koderover-agent + labels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig + template: + metadata: + labels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig + spec: + containers: + - image: {{.ResourceServerImage}} + imagePullPolicy: Always + name: resource-server + resources: + limits: + cpu: 500m + memory: 500Mi + requests: + cpu: 100m + memory: 100Mi + +--- + +apiVersion: v1 +kind: Service +metadata: + name: resource-server + namespace: koderover-agent + labels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig + +--- + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: dind + namespace: koderover-agent + labels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig +spec: + serviceName: dind + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig + template: + metadata: + labels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + containers: + - name: dind + image: ccr.ccs.tencentyun.com/koderover-public/library-docker:stable-dind + args: + - --mtu=1376 + env: + - name: DOCKER_TLS_CERTDIR + value: "" + securityContext: + privileged: true + ports: + - protocol: TCP + containerPort: 2375 + resources: + limits: + cpu: "4" + memory: 8Gi + requests: + cpu: 100m + memory: 128Mi + +--- + +apiVersion: v1 +kind: Service +metadata: + name: dind + namespace: koderover-agent + labels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig +spec: + ports: + - name: dind + protocol: TCP + port: 2375 + targetPort: 2375 + clusterIP: None + selector: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig `)) var YamlTemplateForNamespace = template.Must(template.New("agentYaml").Parse(` @@ -491,11 +619,137 @@ spec: memory: 1Gi requests: cpu: 100m - memory: 256Mi + memory: 256Mi {{- if .UseDeployment }} replicas: 1 {{- else }} updateStrategy: type: RollingUpdate {{- end }} + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: resource-server + namespace: {{.Namespace}} + labels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig + template: + metadata: + labels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig + spec: + containers: + - image: {{.ResourceServerImage}} + imagePullPolicy: Always + name: resource-server + resources: + limits: + cpu: 500m + memory: 500Mi + requests: + cpu: 100m + memory: 100Mi + +--- + +apiVersion: v1 +kind: Service +metadata: + name: resource-server + namespace: {{.Namespace}} + labels: + app.kubernetes.io/component: resource-server + app.kubernetes.io/instance: zadig-zadig + app.kubernetes.io/name: zadig +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app.kubernetes.io/component: resource-server + app.kubernetes.io/name: zadig + +--- + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: dind + namespace: {{.Namespace}} + labels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig +spec: + serviceName: dind + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig + template: + metadata: + labels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + topologyKey: kubernetes.io/hostname + containers: + - name: dind + image: ccr.ccs.tencentyun.com/koderover-public/library-docker:stable-dind + args: + - --mtu=1376 + env: + - name: DOCKER_TLS_CERTDIR + value: "" + securityContext: + privileged: true + ports: + - protocol: TCP + containerPort: 2375 + resources: + limits: + cpu: "4" + memory: 8Gi + requests: + cpu: 100m + memory: 128Mi + +--- + +apiVersion: v1 +kind: Service +metadata: + name: dind + namespace: {{.Namespace}} + labels: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig +spec: + ports: + - name: dind + protocol: TCP + port: 2375 + targetPort: 2375 + clusterIP: None + selector: + app.kubernetes.io/component: dind + app.kubernetes.io/name: zadig `)) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 17b8ffc3..17d1e821 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -260,5 +260,5 @@ func ProxyAgent(writer gin.ResponseWriter, request *http.Request) { func GetYaml(id, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { s, _ := kube.NewService("") - return s.GetYaml(id, config.HubAgentImage(), configbase.SystemAddress(), hubURI, useDeployment, logger) + return s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), hubURI, useDeployment, logger) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index cab6319f..903377ce 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -28,6 +28,7 @@ const ( ENVMongoDBConnectionString = "MONGODB_CONNECTION_STRING" ENVAslanDBName = "ASLAN_DB" ENVHubAgentImage = "HUB_AGENT_IMAGE" + ENVResourceServerImage = "RESOURCE_SERVER_IMAGE" ENVMysqlUser = "MYSQL_USER" ENVMysqlPassword = "MYSQL_PASSWORD" ENVMysqlHost = "MYSQL_HOST" -- Gitee From 6966c8ee212f4536f52f31efddf705145667518c Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 5 Jan 2022 12:10:13 +0800 Subject: [PATCH 1208/2647] update test case Signed-off-by: allenshen --- pkg/util/yaml/path_search_test.go | 369 ++++++------------------------ 1 file changed, 70 insertions(+), 299 deletions(-) diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 2b1fbd17..2310e0b0 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -26,7 +26,8 @@ import ( var testYaml1 = ` env: dev image: - repository: go-sample-site + repo: repo.com + name: go-sample-site tag: "0.2.1" imagePullSecrets: - name: default-secret @@ -36,12 +37,14 @@ var testYaml2 = ` env: dev svc1: image: - repository: go-sample-site + repo: repo.com + name: go-sample-site tag: "0.2.1" svc2: image: - repository: go-sample-site-2 - tag: "0.2.2" + repo: repo.com + name: go-sample-site + tag: "0.2.1" imagePullSecrets: - name: default-secret ` @@ -50,144 +53,24 @@ var testYaml3 = ` env: dev svc1: image: - repository: go-sample-site:0.2.1 -svc2: - image: - repository: go-sample-site-2:0.2.2 -svc3: - image: - repository: go-sample-site-3:0.2.3 -imagePullSecrets: - - name: default-secret -` - -var testYaml4 = ` -env: dev -svc1: - image: - repository: go-sample-site - tag: 0.2.1 + repo: repo.com + name: go-sample-site + tag: "0.2.1" svc2: image: - repository: go-sample-site-2:0.2.2 -svc3: - image: - repository: go-sample-site-3:0.2.3 -svc4: - image: - repositoryNew: go-sample-site-3 - tagNew: 0.2.4 -svc5: - second: - image: - repositorySpec: go-sample-site-3 - tagNew: 0.2.4 + repo: repo.com + detail: + name: go-sample-site + tag: "0.2.1" +fakeSvc: + repo: notrepo.com imagePullSecrets: - name: default-secret ` -var testYaml5 = ` -# Default values for go-sample-site. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -env: dev - -ingressClassName: koderover-admin-nginx - -image: - repository: ccr.ccs.tencentyun.com/trial/go-sample-site - pullPolicy: IfNotPresent - tag: "0.1.0" - -testSpec: - imageNew: - repo: ccr.ccs.tencentyun.com/trial/go-sample-site-new - pullPolicy: IfNotPresent - tag: "0.2.1" - -imagePullSecrets: - - name: default-registry-secret - -nameOverride: "" -fullnameOverride: "" - -service: - type: ClusterIP - port: 8080 -` - -var testYaml6 = ` -env: dev - -ingressClassName: koderover-admin-nginx - -global: - hub: ccr.ccs.tencentyun.com/trial - tag: latest - -testSpec: - imageNew: - image: go-sample-site-new - pullPolicy: IfNotPresent - -svc1: - image: svc1-image - -svc2: - image: svc2-image - -svc3: - image: svc3-image - -imagePullSecrets: - - name: default-registry-secret - -nameOverride: "" -fullnameOverride: "" - -service: - type: ClusterIP - port: 8080 -` - -var testYaml7 = ` -env: dev - -ingressClassName: koderover-admin-nginx - -repoData1: - global: - hub: ccr.ccs.tencentyun.com/trial - tag: latest - -testSpec: - imageNew: - image: go-sample-site-new - pullPolicy: IfNotPresent - -svc1: - image: svc1-image - -svc2: - image: svc2-image - -imagePullSecrets: - - name: default-registry-secret - -nameOverride: "" -fullnameOverride: "" - -service: - type: ClusterIP - port: 8080 -` - var testYaml8 = ` env: dev -ingressClassName: koderover-admin-nginx - repoData1: global: hub: ccr.ccs.tencentyun.com/trial @@ -199,9 +82,8 @@ testSpec: tag: 0.1.0 svc1: - image: - image: svc1-image - tag: 0.2.0 + image: svc1-image + tag: 0.2.0 svc2: image: @@ -219,44 +101,6 @@ service: port: 8080 ` -var testYaml9 = ` -env: dev - -ingressClassName: koderover-admin-nginx - -repoData1: - global: - hub: ccr.ccs.tencentyun.com/trial - -svc1: - image: - service: - image: service-image - tagInfo: - tag: 0.1.0 - repo: - ccr.ccs.tencentyun.com/trial - -svc2: - image: - service: - image: service-image-2 - tagInfo: - tag: 0.2.0 - repo: - ccr.ccs.tencentyun.com/trial - -imagePullSecrets: - - name: default-registry-secret - -nameOverride: "" -fullnameOverride: "" - -service: - type: ClusterIP - port: 8080 -` - var err error var matedPaths []map[string]string var lcpErr error @@ -264,129 +108,60 @@ var lcpMatedPaths []map[string]string var _ = Describe("Testing search", func() { Context("search matched paths from yaml", func() { - //It("single match", func() { - // pattern := []map[string]string{ - // {"image": "repository", "tag": "tag"}, - // } - // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) - // matedPaths, err = SearchByPattern(flatMap, pattern) - // Expect(err).NotTo(HaveOccurred()) - // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - // Expect(lcpErr).NotTo(HaveOccurred()) - // Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) - // Expect(matedPaths).To(Equal([]map[string]string{{"image": "image.repository", "tag": "image.tag"}})) - //}) - // - //It("multiple match", func() { - // pattern := []map[string]string{ - // {"image": "repository", "tag": "tag"}, - // } - // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) - // matedPaths, err = SearchByPattern(flatMap, pattern) - // Expect(err).NotTo(HaveOccurred()) - // - // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - // Expect(lcpErr).NotTo(HaveOccurred()) - // Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) - // - // Expect(matedPaths).Should(ConsistOf([]map[string]string{ - // {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, - // {"image": "svc2.image.repository", "tag": "svc2.image.tag"}, - // })) - //}) - // - //It("multiple match pattern 3", func() { - // pattern := []map[string]string{ - // {"image": "repository"}, - // } - // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml3)) - // matedPaths, err = SearchByPattern(flatMap, pattern) - // Expect(err).NotTo(HaveOccurred()) - // - // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - // Expect(lcpErr).NotTo(HaveOccurred()) - // Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) - // - // Expect(matedPaths).Should(ConsistOf([]map[string]string{ - // {"image": "svc1.image.repository"}, - // {"image": "svc2.image.repository"}, - // {"image": "svc3.image.repository"}, - // })) - //}) - // - //It("multiple match pattern complex", func() { - // pattern := []map[string]string{ - // {"image": "repository", "tag": "tag"}, - // {"image": "repository"}, - // {"image": "repositoryNew", "tag": "tagNew"}, - // {"image": "image.repositorySpec", "tag": "tagNew"}, - // } - // flatMap, _ := converter.YamlToFlatMap([]byte(testYaml4)) - // matedPaths, err = SearchByPattern(flatMap, pattern) - // Expect(err).NotTo(HaveOccurred()) - // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - // Expect(lcpErr).NotTo(HaveOccurred()) - // Expect(matedPaths).Should(ConsistOf([]map[string]string{ - // {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, - // {"image": "svc2.image.repository"}, - // {"image": "svc3.image.repository"}, - // {"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}, - // {"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}, - // })) - // - // Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ - // {"image": "svc1.image.repository", "tag": "svc1.image.tag"}, - // {"image": "svc1.image.repository"}, - // {"image": "svc2.image.repository"}, - // {"image": "svc3.image.repository"}, - // {"image": "svc4.image.repositoryNew", "tag": "svc4.image.tagNew"}, - // {"image": "svc5.second.image.repositorySpec", "tag": "svc5.second.tagNew"}, - // })) - // - //}) - // - //It("multiple match pattern complex2", func() { - // pattern := []map[string]string{ - // {"image": "image", "tag": "global.tag", "repo": "global.hub"}, - // } - // flatMap, err := converter.YamlToFlatMap([]byte(testYaml6)) - // matedPaths, err = SearchByPattern(flatMap, pattern) - // Expect(err).NotTo(HaveOccurred()) - // - // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - // Expect(lcpErr).NotTo(HaveOccurred()) - // - // Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ - // {"image": "testSpec.imageNew.image", "repo": "global.hub", "tag": "global.tag"}, - // {"image": "svc1.image", "repo": "global.hub", "tag": "global.tag"}, - // {"image": "svc2.image", "repo": "global.hub", "tag": "global.tag"}, - // {"image": "svc3.image", "repo": "global.hub", "tag": "global.tag"}, - // })) - //}) - // - //It("multiple match pattern complex 3", func() { - // pattern := []map[string]string{ - // {"image": "image", "tag": "global.tag", "repo": "global.hub"}, - // } - // flatMap, err := converter.YamlToFlatMap([]byte(testYaml7)) - // matedPaths, err = SearchByPattern(flatMap, pattern) - // Expect(err).NotTo(HaveOccurred()) - // - // lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - // Expect(lcpErr).NotTo(HaveOccurred()) - // - // Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ - // {"image": "testSpec.imageNew.image", "repo": "repoData1.global.hub", "tag": "repoData1.global.tag"}, - // {"image": "svc1.image", "repo": "repoData1.global.hub", "tag": "repoData1.global.tag"}, - // {"image": "svc2.image", "repo": "repoData1.global.hub", "tag": "repoData1.global.tag"}, - // })) - //}) + It("single match", func() { + pattern := []map[string]string{ + {"repo": "repo", "tag": "tag", "image": "name"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) + matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + Expect(lcpErr).NotTo(HaveOccurred()) + Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "image.name", "repo": "image.repo", "tag": "image.tag"}, + })) + }) + + It("multiple match", func() { + pattern := []map[string]string{ + {"repo": "repo", "tag": "tag", "image": "name"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) + + lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + Expect(lcpErr).NotTo(HaveOccurred()) + //Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "svc1.image.name", "repo": "svc1.image.repo", "tag": "svc1.image.tag"}, + {"image": "svc2.image.name", "repo": "svc2.image.repo", "tag": "svc2.image.tag"}, + })) + }) + + It("multiple match pattern 2", func() { + pattern := []map[string]string{ + {"repo": "repo", "tag": "tag", "image": "name"}, + } + flatMap, err := converter.YamlToFlatMap([]byte(testYaml3)) + //matedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) - It("multiple match pattern complex 4", func() { + lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) + Expect(lcpErr).NotTo(HaveOccurred()) + //Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "svc1.image.name", "repo": "svc1.image.repo", "tag": "svc1.image.tag"}, + {"image": "svc2.image.detail.name", "repo": "svc2.image.repo", "tag": "svc2.image.detail.tag"}, + })) + }) + + It("multiple match pattern 3", func() { pattern := []map[string]string{ - {"image": "image", "tag": "tag", "repo": "repo"}, + {"image": "image", "tag": "tag", "repo": "global.hub"}, } - flatMap, _ := converter.YamlToFlatMap([]byte(testYaml9)) + flatMap, err := converter.YamlToFlatMap([]byte(testYaml8)) matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) @@ -395,13 +170,9 @@ var _ = Describe("Testing search", func() { Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ {"image": "testSpec.image.image", "repo": "repoData1.global.hub", "tag": "testSpec.image.tag"}, - {"image": "svc1.image.image", "repo": "repoData1.global.hub", "tag": "svc1.image.tag"}, + {"image": "svc1.image", "repo": "repoData1.global.hub", "tag": "svc1.tag"}, {"image": "svc2.image.image", "repo": "repoData1.global.hub", "tag": "svc2.image.tag"}, })) - - //Expect(lcpMatedPaths).Should(ContainElement( - // ConsistOf(map[string]string{"image": "testSpec.image.image", "repo": "repoData1.global.hub", "tag": "repoData1.image.tag"}), - //)) }) }) -- Gitee From c6d96025fccfd9762e85dc4b5ad36f8c15a06cb4 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 5 Jan 2022 14:07:02 +0800 Subject: [PATCH 1209/2647] add open api run workflow resp data Signed-off-by: allenshen --- pkg/microservice/picket/core/public/handler/public.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index a3136401..5a42387a 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -66,6 +66,7 @@ func CreateWorkflowTask(c *gin.Context) { } type CreateWorkflowTaskResp struct { + ProjectName string `json:"project_name"` PipelineName string `json:"pipeline_name,omitempty"` WorkflowName string `json:"workflow_name"` TaskID int64 `json:"task_id"` -- Gitee From 7f2407390dc674eeb3ed791cd8dddc62ceae70c2 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 5 Jan 2022 14:13:53 +0800 Subject: [PATCH 1210/2647] =?UTF-8?q?optimize=20and=20add=20workflow?= =?UTF-8?q?=E3=80=81product=20with=20baseName=20baserefs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: panxunying --- .../aslan/core/collaboration/config/consts.go | 8 ++++++ .../models/collaboration_instance.go | 20 +++++++------ .../repository/models/collaboration_mode.go | 16 ++++++----- .../service/collaboration_instance.go | 19 +++++++------ .../core/common/repository/models/product.go | 2 ++ .../core/common/repository/models/workflow.go | 2 ++ .../aslan/core/common/service/product.go | 4 ++- .../aslan/core/common/service/workflow.go | 16 ++++++----- .../core/environment/service/environment.go | 28 +++++++++++-------- .../workflow/service/workflow/workflow.go | 4 +++ 10 files changed, 74 insertions(+), 45 deletions(-) create mode 100644 pkg/microservice/aslan/core/collaboration/config/consts.go diff --git a/pkg/microservice/aslan/core/collaboration/config/consts.go b/pkg/microservice/aslan/core/collaboration/config/consts.go new file mode 100644 index 00000000..507a29b5 --- /dev/null +++ b/pkg/microservice/aslan/core/collaboration/config/consts.go @@ -0,0 +1,8 @@ +package config + +type CollaborationType string + +const ( + CollaborationShare CollaborationType = "share" + CollaborationNew CollaborationType = "new" +) diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index c5c679cf..b61413e8 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -18,6 +18,8 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" ) type CollaborationInstance struct { @@ -35,18 +37,18 @@ type CollaborationInstance struct { UpdateBy string `bson:"update_by" json:"update_by"` } type ProductCIItem struct { - Name string `bson:"name" json:"name"` - BaseName string `bson:"base_name" json:"base_name"` - CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` - RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` - Verbs []string `bson:"verbs" json:"verbs"` + Name string `bson:"name" json:"name"` + BaseName string `bson:"base_name" json:"base_name"` + CollaborationType config.CollaborationType `bson:"collaboration_type" json:"collaboration_type"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` + Verbs []string `bson:"verbs" json:"verbs"` } type WorkflowCIItem struct { - Name string `bson:"name" json:"name"` - BaseName string `bson:"base_name" json:"base_name"` - CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` - Verbs []string `bson:"verbs" json:"verbs"` + Name string `bson:"name" json:"name"` + BaseName string `bson:"base_name" json:"base_name"` + CollaborationType config.CollaborationType `bson:"collaboration_type" json:"collaboration_type"` + Verbs []string `bson:"verbs" json:"verbs"` } func (CollaborationInstance) TableName() string { diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go index 2ce9d748..32e11d01 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -18,6 +18,8 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" ) type CollaborationMode struct { @@ -37,16 +39,16 @@ type CollaborationMode struct { } type ProductCMItem struct { - Name string `bson:"name" json:"name"` - CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` - RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` - Verbs []string `bson:"verbs" json:"verbs"` + Name string `bson:"name" json:"name"` + CollaborationType config.CollaborationType `bson:"collaboration_type" json:"collaboration_type"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` + Verbs []string `bson:"verbs" json:"verbs"` } type WorkflowCMItem struct { - Name string `bson:"name" json:"name"` - CollaborationType string `bson:"collaboration_type" json:"collaboration_type"` - Verbs []string `bson:"verbs" json:"verbs"` + Name string `bson:"name" json:"name"` + CollaborationType config.CollaborationType `bson:"collaboration_type" json:"collaboration_type"` + Verbs []string `bson:"verbs" json:"verbs"` } func (CollaborationMode) TableName() string { diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 098bfd10..9a2f828c 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -6,6 +6,7 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" "github.com/koderover/zadig/pkg/util" @@ -46,18 +47,18 @@ type UpdateProductItem struct { } type Workflow struct { - CollaborationType string `json:"collaboration_type"` - BaseName string `json:"base_name"` - CollaborationMode string `json:"collaboration_mode"` - Name string `json:"name"` - Description string `json:"description"` + CollaborationType config.CollaborationType `json:"collaboration_type"` + BaseName string `json:"base_name"` + CollaborationMode string `json:"collaboration_mode"` + Name string `json:"name"` + Description string `json:"description"` } type Product struct { - CollaborationType string `json:"collaboration_type"` - BaseName string `json:"base_name"` - CollaborationMode string `json:"collaboration_mode"` - Name string `json:"name"` + CollaborationType config.CollaborationType `json:"collaboration_type"` + BaseName string `json:"base_name"` + CollaborationMode string `json:"collaboration_mode"` + Name string `json:"name"` } type GetCollaborationNewResp struct { Code int64 `json:"code"` diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index af7ecc6b..79fc29c3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -51,6 +51,8 @@ type Product struct { Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` RegistryID string `bson:"registry_id" json:"registry_id"` + BaseRefs []string `bson:"base_refs" json:"base_refs"` + BaseName string `bson:"base_name" json:"base_name"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3ed775c9..e122ba9b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -49,6 +49,8 @@ type Workflow struct { DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + BaseName string `bson:"base_name" json:"base_name"` + BaseRefs []string `bson:"base_refs" json:"base_refs"` // ResetImage indicate whether reset image to original version after completion ResetImage bool `json:"reset_image" bson:"reset_image"` diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 2f1cb87b..c532e601 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -50,7 +50,9 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("find product error: %v", err) return e.ErrDeleteEnv.AddDesc("not found") } - + if len(productInfo.BaseRefs) > 0 { + return fmt.Errorf("this is a base environment, collaborations:%v is related", productInfo.BaseRefs) + } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrDeleteEnv.AddErr(err) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index fef94323..e9cb027f 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -54,6 +54,15 @@ func DeleteWorkflows(productName, requestID string, log *zap.SugaredLogger) erro } func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log *zap.SugaredLogger) error { + // 在删除前,先将workflow查出来,用于删除gerrit webhook + workflow, err := mongodb.NewWorkflowColl().Find(workflowName) + if err != nil { + log.Errorf("Workflow.Find error: %v", err) + return e.ErrDeleteWorkflow.AddDesc(err.Error()) + } + if len(workflow.BaseRefs) > 0 { + return fmt.Errorf("this is a base workflow, collaborations:%v is related", workflow.BaseRefs) + } taskQueue, err := mongodb.NewQueueColl().List(&mongodb.ListQueueOption{}) if err != nil { log.Errorf("List queued task error: %v", err) @@ -68,13 +77,6 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, } } - // 在删除前,先将workflow查出来,用于删除gerrit webhook - workflow, err := mongodb.NewWorkflowColl().Find(workflowName) - if err != nil { - log.Errorf("Workflow.Find error: %v", err) - return e.ErrDeleteWorkflow.AddDesc(err.Error()) - } - if !isDeletingProductTmpl { prod, err := template.NewProductColl().Find(workflow.ProductTmplName) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 16664074..fb8b7342 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -85,18 +85,20 @@ type EnvStatus struct { } type EnvResp struct { - ProjectName string `json:"projectName"` - Status string `json:"status"` - Error string `json:"error"` - Name string `json:"name"` - UpdateBy string `json:"updateBy"` - UpdateTime int64 `json:"updateTime"` - IsPublic bool `json:"isPublic"` - ClusterName string `json:"clusterName"` - ClusterID string `json:"cluster_id"` - Production bool `json:"production"` - Source string `json:"source"` - RegistryID string `json:"registry_id"` + ProjectName string `json:"projectName"` + Status string `json:"status"` + Error string `json:"error"` + Name string `json:"name"` + UpdateBy string `json:"updateBy"` + UpdateTime int64 `json:"updateTime"` + IsPublic bool `json:"isPublic"` + ClusterName string `json:"clusterName"` + ClusterID string `json:"cluster_id"` + Production bool `json:"production"` + Source string `json:"source"` + RegistryID string `json:"registry_id"` + BaseRefs []string `json:"base_refs"` + BaseName string `json:"base_name"` } type ProductResp struct { @@ -217,6 +219,8 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) UpdateBy: env.UpdateBy, RegistryID: env.RegistryID, ClusterID: env.ClusterID, + BaseRefs: env.BaseRefs, + BaseName: env.BaseName, }) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 95064402..3e581d92 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -61,6 +61,8 @@ type Workflow struct { AverageExecutionTime float64 `json:"averageExecutionTime"` SuccessRate float64 `json:"successRate"` Description string `json:"description,omitempty"` + BaseName string `json:"base_name"` + BaseRefs []string `json:"base_refs"` } type TaskInfo struct { @@ -540,6 +542,8 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] Schedules: w.Schedules, EnabledStages: stages, Description: w.Description, + BaseName: w.BaseName, + BaseRefs: w.BaseRefs, }) workflowNames = append(workflowNames, w.Name) -- Gitee From 873d0684b3a930a39a674a30faddf4e36e518070 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 5 Jan 2022 14:29:29 +0800 Subject: [PATCH 1211/2647] review Signed-off-by: mouuii --- .../policy/core/handler/permission.go | 2 +- .../policy/core/service/permission.go | 26 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index a81a9f86..ed4af8a0 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -34,5 +34,5 @@ func GetUserPermission(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GetPermission(projectName, c.Param("uid")) + ctx.Resp, ctx.Err = service.GetPermission(projectName, c.Param("uid"), ctx.Logger) } diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index b2c3c366..1fe63eca 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -17,27 +17,32 @@ limitations under the License. package service import ( + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" ) -func GetPermission(ns, uid string) (map[string][]string, error) { +// GetPermission user's permission for frontend +func GetPermission(ns, uid string, log *zap.SugaredLogger) (map[string][]string, error) { roles := []*models.Role{} rolebindingsReadOnly, err := mongodb.NewRoleBindingColl().ListBy(ns, "*") if err != nil { + log.Errorf("list readonly RoleBinding err:%s,ns:%s,uid:*", err, ns) return nil, err } rolebindingsAdmin, err := mongodb.NewRoleBindingColl().ListBy("*", uid) if err != nil { + log.Errorf("list admin RoleBinding err:%s,ns:*,uid:%s", err) return nil, err } // 1.2 get normal rolebindings rolebindingsNormal, err := mongodb.NewRoleBindingColl().ListBy(ns, uid) if err != nil { + log.Errorf("list normal RoleBindings err:%s,ns:%s,uid:%s", err, ns, uid) return nil, err } @@ -50,17 +55,22 @@ func GetPermission(ns, uid string) (map[string][]string, error) { } roles = append(roles, tmpRoles...) } + rolesSet := map[string]sets.String{} rolesResp := map[string][]string{} - resourceSet := sets.NewString() - for _, v := range roles { - for _, vv := range v.Rules { - if len(vv.Resources) > 0 { - if resourceSet.Has(vv.Resources[0]) { - vv.Verbs = append(rolesResp[vv.Resources[0]]) + for _, role := range roles { + for _, rule := range role.Rules { + for _, resource := range rule.Resources { + if verbsSet, ok := rolesSet[resource]; ok { + verbsSet.Insert(rule.Verbs...) + rolesSet[resource] = verbsSet + } else { + rolesSet[resource] = sets.NewString(rule.Verbs...) } - rolesResp[vv.Resources[0]] = vv.Verbs } } } + for k, v := range rolesSet { + rolesResp[k] = v.List() + } return rolesResp, nil } -- Gitee From bd7e8984e2c91ea54cf22e75095cd7e3016855b2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 5 Jan 2022 14:57:54 +0800 Subject: [PATCH 1212/2647] optimize cluster logic Signed-off-by: liu deyi --- .../core/multicluster/service/clusters.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 17b8ffc3..4b62b141 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -70,24 +70,29 @@ func (k *K8SCluster) Clean() error { func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ([]*K8SCluster, error) { idSet := sets.NewString(ids...) + cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: idSet.UnsortedList()}) + if err != nil { + logger.Errorf("Failed to list clusters, err: %s", err) + return nil, err + } + + existClusterID := sets.NewString() if projectName != "" { projectClusterRelations, _ := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ ProjectName: projectName, }) for _, projectClusterRelation := range projectClusterRelations { - idSet.Insert(projectClusterRelation.ClusterID) + existClusterID.Insert(projectClusterRelation.ClusterID) } } - cs, err := commonrepo.NewK8SClusterColl().List(&commonrepo.ClusterListOpts{IDs: idSet.UnsortedList()}) - if err != nil { - logger.Errorf("Failed to list clusters, err: %s", err) - return nil, err - } var res []*K8SCluster for _, c := range cs { - var advancedConfig *AdvancedConfig + if !existClusterID.Has(c.ID.Hex()) { + continue + } + var advancedConfig *AdvancedConfig if c.AdvancedConfig != nil { advancedConfig = &AdvancedConfig{ Strategy: c.AdvancedConfig.Strategy, -- Gitee From c628d50ea2e9db35faea2c40269c44a269ad7c42 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 5 Jan 2022 18:55:39 +0800 Subject: [PATCH 1213/2647] fix project cluster problem Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 36 ++++++++- .../internal/repository/models/180/cluster.go | 29 ++++++++ .../internal/repository/models/180/project.go | 25 +++++++ .../models/180/project_cluster_relation.go | 33 +++++++++ .../internal/repository/models/product.go | 2 +- .../repository/mongodb/180/cluster.go | 66 +++++++++++++++++ .../repository/mongodb/180/project.go | 70 ++++++++++++++++++ .../mongodb/180/project_cluster_relation.go | 74 +++++++++++++++++++ 8 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/180/project.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index a5321e61..a2b9d742 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -22,7 +22,9 @@ import ( "go.mongodb.org/mongo-driver/bson" + _80Models "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb/180" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" @@ -47,12 +49,17 @@ func V171ToV180() error { } log.Info("Start to patchProductRegistryID") - err := patchProductRegistryID() - if err != nil { + if err := patchProductRegistryID(); err != nil { log.Errorf("Failed to patchProductRegistryID, err: %s", err) return err } + log.Info("Start to addProjectClusterRelation") + if err := initProjectClusterRelation(); err != nil { + log.Errorf("Failed to initProjectClusterRelation, err: %s", err) + return err + } + return nil } @@ -138,3 +145,28 @@ func newRoleBindingColl() *mongodb.RoleBindingColl { Collection: mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection(name), } } + +func initProjectClusterRelation() error { + projects, err := _80.NewProductColl().List() + if err != nil { + log.Errorf("Failed to list projects, err: %s", err) + return err + } + clusters, err := _80.NewK8SClusterColl().List() + if err != nil { + log.Errorf("Failed to list clusters, err: %s", err) + return err + } + + for _, project := range projects { + for _, cluster := range clusters { + if err := _80.NewProjectClusterRelationColl().Create(&_80Models.ProjectClusterRelation{ + ProjectName: project.ProductName, + ClusterID: cluster.ID.Hex(), + }); err != nil { + log.Warnf("Failed to create projectClusterRelation, err: %s", err) + } + } + } + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go new file mode 100644 index 00000000..221149ce --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _80 + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type K8SCluster struct { + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` +} + +func (K8SCluster) TableName() string { + return "k8s_cluster" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/project.go b/pkg/cli/upgradeassistant/internal/repository/models/180/project.go new file mode 100644 index 00000000..4fb6f07e --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/180/project.go @@ -0,0 +1,25 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _80 + +type Project struct { + ProductName string `bson:"product_name" json:"product_name"` +} + +func (Project) TableName() string { + return "template_product" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go new file mode 100644 index 00000000..8045cafc --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go @@ -0,0 +1,33 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _80 + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type ProjectClusterRelation struct { + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + ProjectName string `json:"project_name" bson:"project_name"` + ClusterID string `json:"cluster_id" bson:"cluster_id"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` +} + +func (ProjectClusterRelation) TableName() string { + return "project_cluster_relation" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/product.go b/pkg/cli/upgradeassistant/internal/repository/models/product.go index 5d367697..dac991e6 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/product.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/product.go @@ -51,7 +51,7 @@ type Product struct { Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` RegistryID string `bson:"registry_id" json:"registry_id"` - IsForkedProduct bool `bson:"-" json:"-"` + IsForkedProduct bool `bson:"-" json:"-"` } type RenderInfo struct { diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go new file mode 100644 index 00000000..420affa9 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _80 + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type K8SClusterColl struct { + *mongo.Collection + + coll string +} + +func NewK8SClusterColl() *K8SClusterColl { + name := _80.K8SCluster{}.TableName() + return &K8SClusterColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *K8SClusterColl) GetCollectionName() string { + return c.coll +} + +func (c *K8SClusterColl) EnsureIndex(_ context.Context) error { + return nil +} + +func (c *K8SClusterColl) List() ([]*_80.K8SCluster, error) { + var clusters []*_80.K8SCluster + + query := bson.M{} + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &clusters) + if err != nil { + return nil, err + } + + return clusters, err +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go new file mode 100644 index 00000000..773f82ba --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go @@ -0,0 +1,70 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _80 + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ProductColl struct { + *mongo.Collection + + coll string +} + +func NewProductColl() *ProductColl { + name := _80.Project{}.TableName() + return &ProductColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *ProductColl) GetCollectionName() string { + return c.coll +} + +func (c *ProductColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"product_name": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *ProductColl) List() ([]*_80.Project, error) { + var resp []*_80.Project + + cursor, err := c.Collection.Find(context.TODO(), bson.M{}) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go new file mode 100644 index 00000000..2aa85f64 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go @@ -0,0 +1,74 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package _80 + +import ( + "context" + "errors" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ProjectClusterRelationColl struct { + *mongo.Collection + + coll string +} + +func NewProjectClusterRelationColl() *ProjectClusterRelationColl { + name := _80.ProjectClusterRelation{}.TableName() + return &ProjectClusterRelationColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ProjectClusterRelationColl) GetCollectionName() string { + return c.coll +} + +func (c *ProjectClusterRelationColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "cluster_id", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + return err +} + +func (c *ProjectClusterRelationColl) Create(args *_80.ProjectClusterRelation) error { + if args == nil { + return errors.New("nil projectClusterRelation info") + } + + args.CreatedAt = time.Now().Unix() + args.CreatedBy = "system" + _, err := c.InsertOne(context.TODO(), args) + + return err +} -- Gitee From 34938056625a6884089208e0ecda7a95a97e6821 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 5 Jan 2022 20:32:47 +0800 Subject: [PATCH 1214/2647] fix 1.7.1->1.8.0 error without default registry Signed-off-by: panxunying --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index a5321e61..1ac03b58 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -21,6 +21,7 @@ import ( "fmt" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" @@ -65,11 +66,17 @@ func patchProductRegistryID() error { // get all products products, err := internalmongodb.NewProductColl().List(&internalmongodb.ProductListOptions{}) if err != nil { + if err == mongo.ErrNoDocuments { + return nil + } log.Errorf("Failed to list products, err: %s", err) return err } registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) - if err != nil { + if err == nil { + if err == mongo.ErrNoDocuments { + return nil + } log.Errorf("Failed to find default registry, err: %s", err) return err } -- Gitee From 67ee0f3ef0d26b75ab29f42ab0e62c528bd61722 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 5 Jan 2022 20:37:55 +0800 Subject: [PATCH 1215/2647] fix 1.7.1->1.8.0 error without default registry Signed-off-by: panxunying --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 1ac03b58..a28689a3 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -73,7 +73,7 @@ func patchProductRegistryID() error { return err } registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) - if err == nil { + if err != nil { if err == mongo.ErrNoDocuments { return nil } -- Gitee From bc86b8b4ce83dbd9d95680b4d3f21ec4cd8b2388 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 6 Jan 2022 11:01:03 +0800 Subject: [PATCH 1216/2647] add env baserefs params Signed-off-by: panxunying --- .../aslan/core/environment/service/environment.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index fb8b7342..6306caab 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,6 +46,7 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -192,6 +193,15 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) if err != nil { log.Errorf("FindDefaultRegistry error: %v", err) return nil, err + } + collaborationModes, err := service.GetCollaborationModes(projectName, log) + if err != nil { + log.Errorf("GetCollaborationModes error: %v", err) + return nil, err + } + envCMMap := make(map[string][]string) + for _, cm := range collaborationModes { + } for _, env := range envs { clusterID := env.ClusterID -- Gitee From 8f8a00443b4c6880b2a247c156c9dd73618768c4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 6 Jan 2022 11:09:09 +0800 Subject: [PATCH 1217/2647] add error log for uploading cache Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/reaper.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 3d76dac9..37f01922 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -468,7 +468,10 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } if upStreamErr == nil { - _ = r.CompressCache(r.Ctx.StorageURI) + err = r.CompressCache(r.Ctx.StorageURI) + if err != nil { + log.Errorf("Failed to run compress cache, err: %s", err) + } } return err -- Gitee From f70a2041346ac8eb508963117532d5ec0bc9dff7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 14:40:03 +0800 Subject: [PATCH 1218/2647] fix project clsuter relation problem Signed-off-by: liu deyi --- .../service/environment_creator.go | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 3e9472ee..19cc476b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -123,7 +123,20 @@ func newHelmProductCreator() *HelmProductCreator { } func (creator *HelmProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), args.ClusterID) + clusterID := args.ClusterID + if clusterID == "" { + projectClusterRelations, err := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ + ProjectName: args.ProductName, + }) + if err != nil { + return e.ErrCreateEnv.AddDesc("Failed to get the cluster info selected in the project") + } + if len(projectClusterRelations) > 0 { + clusterID = projectClusterRelations[0].ClusterID + } + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s][%s] GetKubeClient error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) @@ -280,7 +293,20 @@ func newDefaultProductCreator() *DefaultProductCreator { } func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), args.ClusterID) + // get project cluster relation + clusterID := args.ClusterID + if clusterID == "" { + projectClusterRelations, err := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ + ProjectName: args.ProductName, + }) + if err != nil { + return e.ErrCreateEnv.AddDesc("Failed to get the cluster info selected in the project") + } + if len(projectClusterRelations) > 0 { + clusterID = projectClusterRelations[0].ClusterID + } + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { return e.ErrCreateEnv.AddErr(err) } -- Gitee From 5f7479fc52840994fcdc5788664a78668389d414 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 14:56:18 +0800 Subject: [PATCH 1219/2647] modify upgradeassistant Signed-off-by: liu deyi --- .github/labeler.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index c31f7da4..e1d4eb4e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -33,7 +33,10 @@ service/hubserver: - pkg/microservice/hubserver/**/* service/hubagent: - - pkg/microservice/hubagent/**/* +- pkg/microservice/hubagent/**/* + +service/upgradeassistant: +- pkg/microservice/upgradeassistant/**/* common: - pkg/config/**/* -- Gitee From 2df8042917b83f9fc7510aa9b006f645dfa89c9f Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 6 Jan 2022 14:57:04 +0800 Subject: [PATCH 1220/2647] update list workflow and produtc Signed-off-by: panxunying --- .../handler/collaboration_mode.go | 2 +- .../repository/mongodb/collaboration_mode.go | 13 +++- .../service/collaboration_instance.go | 6 +- .../service/collaboration_mode.go | 70 +++++++++++++++++-- .../core/environment/service/environment.go | 17 ++--- .../workflow/service/workflow/workflow.go | 14 +++- 6 files changed, 101 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index 6c9320ed..a6b2d161 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -36,7 +36,7 @@ func GetCollaborationMode(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - ctx.Resp, ctx.Err = service.GetCollaborationModes(projectName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetCollaborationModes([]string{projectName}, ctx.Logger) } func CreateCollaborationMode(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index d02452b1..be364136 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -37,6 +37,13 @@ type CollaborationModeFindOptions struct { IsDeleted bool } +type CollaborationModeListOptions struct { + Projects []string + Name string ` ` + Members string + IsDeleted bool +} + type CollaborationModeColl struct { *mongo.Collection @@ -166,14 +173,14 @@ func (c *CollaborationModeColl) Find(opt *CollaborationModeFindOptions) (*models return res, err } -func (c *CollaborationModeColl) List(opt *CollaborationModeFindOptions) ([]*models.CollaborationMode, error) { +func (c *CollaborationModeColl) List(opt *CollaborationModeListOptions) ([]*models.CollaborationMode, error) { var ret []*models.CollaborationMode query := bson.M{} if opt.Name != "" { query["name"] = opt.Name } - if opt.ProjectName != "" { - query["project_name"] = opt.ProjectName + if len(opt.Projects) > 0 { + query["project_name"] = bson.M{"$in": opt.Projects} } if opt.Members != "" { query["members"] = opt.Members diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 9a2f828c..7a637bdf 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -183,9 +183,9 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model } func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { - collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeFindOptions{ - ProjectName: projectName, - Members: uid, + collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ + Projects: []string{projectName}, + Members: uid, }) if err != nil { logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go index e351735d..0f367230 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go @@ -17,8 +17,11 @@ limitations under the License. package service import ( + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" + config2 "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" ) @@ -54,12 +57,71 @@ func DeleteCollaborationMode(username, projectName, name string, logger *zap.Sug return nil } -func GetCollaborationModes(projectName string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { - collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeFindOptions{ - ProjectName: projectName, +func GetEnvCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { + collaborationModes, err := GetCollaborationModes(projects, logger) + if err != nil { + logger.Errorf("GetCollaborationModes error: %v", err) + return nil, err + } + return buildEnvCMMap(collaborationModes.Collaborations), nil +} + +func GetWorkflowCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { + collaborationModes, err := GetCollaborationModes(projects, logger) + if err != nil { + logger.Errorf("GetCollaborationModes error: %v", err) + return nil, err + } + return buildWorkflowCMMap(collaborationModes.Collaborations), nil +} + +func buildEnvCMMap(collaborations []*models.CollaborationMode) map[string]sets.String { + envCMMap := make(map[string]sets.String) + for _, cm := range collaborations { + for _, product := range cm.Products { + if product.CollaborationType == config2.CollaborationShare { + continue + } + if cmSet, ok := envCMMap[cm.ProjectName+" "+product.Name]; ok { + cmSet.Insert(cm.Name) + envCMMap[cm.ProjectName+" "+product.Name] = cmSet + } else { + cmSet := sets.NewString(cm.Name) + envCMMap[cm.ProjectName+" "+product.Name] = cmSet + } + } + } + return envCMMap +} + +func buildWorkflowCMMap(collaborations []*models.CollaborationMode) map[string]sets.String { + workflowCMMap := make(map[string]sets.String) + for _, cm := range collaborations { + for _, workflow := range cm.Workflows { + if workflow.CollaborationType == config2.CollaborationShare { + continue + } + if cmSet, ok := workflowCMMap[cm.ProjectName+" "+workflow.Name]; ok { + cmSet.Insert(cm.Name) + workflowCMMap[cm.ProjectName+" "+workflow.Name] = cmSet + } else { + cmSet := sets.NewString(cm.Name) + workflowCMMap[cm.ProjectName+" "+workflow.Name] = cmSet + } + } + } + return workflowCMMap +} + +func GetCollaborationModes(projects []string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { + collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ + Projects: projects, }) if err != nil { - logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) + if err == mongo.ErrNoDocuments { + return &GetCollaborationModeResp{}, nil + } + logger.Errorf("GetCollaborationModes error, err msg:%s", err) return nil, err } return &GetCollaborationModeResp{ diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 6306caab..34fe688e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -194,14 +194,10 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) log.Errorf("FindDefaultRegistry error: %v", err) return nil, err } - collaborationModes, err := service.GetCollaborationModes(projectName, log) + + envCMMap, err := service.GetEnvCMMap([]string{projectName}, log) if err != nil { - log.Errorf("GetCollaborationModes error: %v", err) return nil, err - } - envCMMap := make(map[string][]string) - for _, cm := range collaborationModes { - } for _, env := range envs { clusterID := env.ClusterID @@ -215,7 +211,12 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) production = cluster.Production clusterName = cluster.Name } - + var baseRefs []string + if cmSet, ok := envCMMap[env.ProductName+" "+env.EnvName]; ok { + for _, cm := range cmSet.List() { + baseRefs = append(baseRefs, cm) + } + } res = append(res, &EnvResp{ ProjectName: projectName, Name: env.EnvName, @@ -229,7 +230,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) UpdateBy: env.UpdateBy, RegistryID: env.RegistryID, ClusterID: env.ClusterID, - BaseRefs: env.BaseRefs, + BaseRefs: baseRefs, BaseName: env.BaseName, }) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 3e581d92..34a197e9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -517,6 +518,10 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] var workflowNames []string var res []*Workflow + workflowCMMap, err := service.GetWorkflowCMMap(projects, log) + if err != nil { + return nil, err + } for _, w := range workflows { stages := make([]string, 0, 4) if w.BuildStage != nil && w.BuildStage.Enabled { @@ -531,7 +536,12 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] if w.DistributeStage != nil && w.DistributeStage.Enabled { stages = append(stages, "distribute") } - + var baseRefs []string + if cmSet, ok := workflowCMMap[w.ProductTmplName+" "+w.Name]; ok { + for _, cm := range cmSet.List() { + baseRefs = append(baseRefs, cm) + } + } res = append(res, &Workflow{ Name: w.Name, ProjectName: w.ProductTmplName, @@ -543,7 +553,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] EnabledStages: stages, Description: w.Description, BaseName: w.BaseName, - BaseRefs: w.BaseRefs, + BaseRefs: baseRefs, }) workflowNames = append(workflowNames, w.Name) -- Gitee From a74dee7789b3ea1b95358df2b730928c46db2d60 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 6 Jan 2022 15:21:49 +0800 Subject: [PATCH 1221/2647] update delete workflow and produtc Signed-off-by: panxunying --- .../aslan/core/common/repository/models/product.go | 1 - .../aslan/core/common/repository/models/workflow.go | 1 - pkg/microservice/aslan/core/common/service/product.go | 9 +++++++-- pkg/microservice/aslan/core/common/service/workflow.go | 9 +++++++-- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 79fc29c3..51bd357d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -51,7 +51,6 @@ type Product struct { Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` RegistryID string `bson:"registry_id" json:"registry_id"` - BaseRefs []string `bson:"base_refs" json:"base_refs"` BaseName string `bson:"base_name" json:"base_name"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index e122ba9b..2197c380 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -50,7 +50,6 @@ type Workflow struct { NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` BaseName string `bson:"base_name" json:"base_name"` - BaseRefs []string `bson:"base_refs" json:"base_refs"` // ResetImage indicate whether reset image to original version after completion ResetImage bool `json:"reset_image" bson:"reset_image"` diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index c532e601..7a15ab6a 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -28,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -50,8 +51,12 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("find product error: %v", err) return e.ErrDeleteEnv.AddDesc("not found") } - if len(productInfo.BaseRefs) > 0 { - return fmt.Errorf("this is a base environment, collaborations:%v is related", productInfo.BaseRefs) + envCMMap, err := service.GetEnvCMMap([]string{productName}, log) + if err != nil { + return err + } + if cmSets, ok := envCMMap[productName+" "+envName]; ok { + return fmt.Errorf("this is a base environment, collaborations:%v is related", cmSets.List()) } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index e9cb027f..caf07b58 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -24,6 +24,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -60,8 +61,12 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log.Errorf("Workflow.Find error: %v", err) return e.ErrDeleteWorkflow.AddDesc(err.Error()) } - if len(workflow.BaseRefs) > 0 { - return fmt.Errorf("this is a base workflow, collaborations:%v is related", workflow.BaseRefs) + workflowCMMap, err := service.GetWorkflowCMMap([]string{workflow.ProductTmplName}, log) + if err != nil { + return err + } + if cmSets, ok := workflowCMMap[workflow.ProductTmplName+" "+workflowName]; ok { + return fmt.Errorf("this is a base workflow, collaborations:%v is related", cmSets.List()) } taskQueue, err := mongodb.NewQueueColl().List(&mongodb.ListQueueOption{}) if err != nil { -- Gitee From 2b5ba49545b081d0e16e7aca47b36a4a7a0bd706 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 6 Jan 2022 15:40:36 +0800 Subject: [PATCH 1222/2647] optimize and update clean product Signed-off-by: panxunying --- .../service/collaboration_mode.go | 22 ++++++++++++++----- .../aslan/core/common/service/product.go | 2 +- .../aslan/core/common/service/workflow.go | 2 +- .../core/environment/service/environment.go | 2 +- .../aslan/core/environment/service/product.go | 10 +++++++-- .../workflow/service/workflow/workflow.go | 2 +- 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go index 0f367230..c9403362 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go @@ -57,6 +57,14 @@ func DeleteCollaborationMode(username, projectName, name string, logger *zap.Sug return nil } +func BuildEnvCMMapKey(projectName string, envName string) string { + return projectName + " " + envName +} + +func BuildWorkflowCMMapKey(projectName string, workflowName string) string { + return projectName + " " + workflowName +} + func GetEnvCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { collaborationModes, err := GetCollaborationModes(projects, logger) if err != nil { @@ -82,12 +90,13 @@ func buildEnvCMMap(collaborations []*models.CollaborationMode) map[string]sets.S if product.CollaborationType == config2.CollaborationShare { continue } - if cmSet, ok := envCMMap[cm.ProjectName+" "+product.Name]; ok { + key := BuildEnvCMMapKey(cm.ProjectName, product.Name) + if cmSet, ok := envCMMap[key]; ok { cmSet.Insert(cm.Name) - envCMMap[cm.ProjectName+" "+product.Name] = cmSet + envCMMap[key] = cmSet } else { cmSet := sets.NewString(cm.Name) - envCMMap[cm.ProjectName+" "+product.Name] = cmSet + envCMMap[key] = cmSet } } } @@ -101,12 +110,13 @@ func buildWorkflowCMMap(collaborations []*models.CollaborationMode) map[string]s if workflow.CollaborationType == config2.CollaborationShare { continue } - if cmSet, ok := workflowCMMap[cm.ProjectName+" "+workflow.Name]; ok { + key := BuildWorkflowCMMapKey(cm.ProjectName, workflow.Name) + if cmSet, ok := workflowCMMap[key]; ok { cmSet.Insert(cm.Name) - workflowCMMap[cm.ProjectName+" "+workflow.Name] = cmSet + workflowCMMap[key] = cmSet } else { cmSet := sets.NewString(cm.Name) - workflowCMMap[cm.ProjectName+" "+workflow.Name] = cmSet + workflowCMMap[key] = cmSet } } } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 7a15ab6a..826c3bb1 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -55,7 +55,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if err != nil { return err } - if cmSets, ok := envCMMap[productName+" "+envName]; ok { + if cmSets, ok := envCMMap[service.BuildEnvCMMapKey(productName, envName)]; ok { return fmt.Errorf("this is a base environment, collaborations:%v is related", cmSets.List()) } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index caf07b58..18b274ee 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -65,7 +65,7 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, if err != nil { return err } - if cmSets, ok := workflowCMMap[workflow.ProductTmplName+" "+workflowName]; ok { + if cmSets, ok := workflowCMMap[service.BuildWorkflowCMMapKey(workflow.ProductTmplName, workflowName)]; ok { return fmt.Errorf("this is a base workflow, collaborations:%v is related", cmSets.List()) } taskQueue, err := mongodb.NewQueueColl().List(&mongodb.ListQueueOption{}) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 34fe688e..7a15f6cd 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -212,7 +212,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) clusterName = cluster.Name } var baseRefs []string - if cmSet, ok := envCMMap[env.ProductName+" "+env.EnvName]; ok { + if cmSet, ok := envCMMap[service.BuildEnvCMMapKey(env.ProductName, env.EnvName)]; ok { for _, cm := range cmSet.List() { baseRefs = append(baseRefs, cm) } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index c1ef2713..af12f27f 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -47,7 +48,10 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { log.Errorf("[Product.List] error: %v", err) return } - + envCMMap, err := service.GetEnvCMMap([]string{}, log) + if err != nil { + return + } wl := sets.NewString(DefaultCleanWhiteList...) wl.Insert(config.CleanSkippedList()...) for _, product := range products { @@ -58,7 +62,9 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { if product.RecycleDay == 0 { continue } - + if _, ok := envCMMap[service.BuildEnvCMMapKey(product.ProductName, product.EnvName)]; ok { + continue + } if time.Now().Unix()-product.UpdateTime > int64(60*60*24*product.RecycleDay) { //title := "系统清理产品信息" //content := fmt.Sprintf("环境 [%s] 已经连续%d天没有使用, 系统已自动删除该环境, 如有需要请重新创建。", product.EnvName, product.RecycleDay) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 34a197e9..d336cb0a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -537,7 +537,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] stages = append(stages, "distribute") } var baseRefs []string - if cmSet, ok := workflowCMMap[w.ProductTmplName+" "+w.Name]; ok { + if cmSet, ok := workflowCMMap[service.BuildWorkflowCMMapKey(w.ProductTmplName, w.Name)]; ok { for _, cm := range cmSet.List() { baseRefs = append(baseRefs, cm) } -- Gitee From bb92d3f9e11402767042acded814c9f7739086c2 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 6 Jan 2022 16:15:52 +0800 Subject: [PATCH 1223/2647] add delete by project Signed-off-by: panxunying --- .../collaboration/repository/mongodb/collaboration_mode.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index be364136..f95dbb8a 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -136,6 +136,12 @@ func (c *CollaborationModeColl) Create(userName string, args *models.Collaborati return err } +func (c *CollaborationModeColl) DeleteByProject(project string) error { + query := bson.M{"project_name": project} + _, err := c.DeleteMany(context.TODO(), query) + return err +} + func (c *CollaborationModeColl) Delete(username, projectName, name string) error { _, err := c.Find(&CollaborationModeFindOptions{ Name: name, -- Gitee From 0ff217686fd1eaf5cd82079496feabbe8b24818f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 16:53:22 +0800 Subject: [PATCH 1224/2647] modify cluster list logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/multicluster/service/clusters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 4b62b141..f6808820 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -88,7 +88,7 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( var res []*K8SCluster for _, c := range cs { - if !existClusterID.Has(c.ID.Hex()) { + if projectName != "" && !existClusterID.Has(c.ID.Hex()) { continue } -- Gitee From a424c7bc53994b0938636ccf34000eaca379dd90 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 6 Jan 2022 17:15:42 +0800 Subject: [PATCH 1225/2647] rm private key Signed-off-by: mouuii --- .../core/common/service/pm/env_status.go | 56 ++++++++++++++++++ .../aslan/core/common/service/product.go | 6 +- .../aslan/core/environment/service/pm.go | 33 ++--------- .../aslan/core/service/service/pm.go | 9 ++- .../aslan/core/system/handler/private_key.go | 2 +- .../aslan/core/system/service/private_key.go | 58 ++++++++++++++++++- .../service/workflow/workflow_task.go | 2 +- pkg/setting/consts.go | 1 + 8 files changed, 132 insertions(+), 35 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/pm/env_status.go diff --git a/pkg/microservice/aslan/core/common/service/pm/env_status.go b/pkg/microservice/aslan/core/common/service/pm/env_status.go new file mode 100644 index 00000000..934e1758 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/pm/env_status.go @@ -0,0 +1,56 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pm + +import ( + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" +) + +func GenerateEnvStatus(envConfigs []*commonmodels.EnvConfig, log *zap.SugaredLogger) ([]*commonmodels.EnvStatus, error) { + changeEnvStatus := []*commonmodels.EnvStatus{} + for _, envConfig := range envConfigs { + tmpPrivateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) + if err != nil { + log.Errorf("ListNameByArgs ids err:%s", err) + return nil, err + } + + privateKeysByLabels, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) + if err != nil { + log.Errorf("ListNameByArgs labels err:%s", err) + return nil, err + } + tmpPrivateKeys = append(tmpPrivateKeys, privateKeysByLabels...) + privateKeysSet := sets.NewString() + for _, v := range tmpPrivateKeys { + tmp := commonmodels.EnvStatus{ + HostID: v.ID.Hex(), + EnvName: envConfig.EnvName, + Address: v.IP, + } + if !privateKeysSet.Has(tmp.HostID) { + changeEnvStatus = append(changeEnvStatus, &tmp) + privateKeysSet.Insert(tmp.HostID) + } + } + } + return changeEnvStatus, nil +} diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 2f1cb87b..17f8d6bf 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -178,7 +178,11 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("remove services in external env error:%s", err) } } - + case setting.SourceFromPM: + err = commonrepo.NewProductColl().Delete(envName, productName) + if err != nil { + log.Errorf("Product.Delete error: %v", err) + } default: go func() { var err error diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 2c8a11d4..4ea46797 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -32,6 +31,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/pm" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -181,34 +181,11 @@ func (p *PMService) createGroup(envName, productName, username string, group []* oldEnvConfigs = append(oldEnvConfigs, envConfig) } - changeEnvStatus := []*commonmodels.EnvStatus{} - for _, envConfig := range oldEnvConfigs { - tmpPrivateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) - if err != nil { - log.Errorf("ListNameByArgs ids err:%s", err) - return err - } - - privateKeysByLabels, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) - if err != nil { - log.Errorf("ListNameByArgs labels err:%s", err) - return err - } - tmpPrivateKeys = append(tmpPrivateKeys, privateKeysByLabels...) - privateKeysSet := sets.NewString() - for _, v := range tmpPrivateKeys { - tmp := models.EnvStatus{ - HostID: v.ID.Hex(), - EnvName: envConfig.EnvName, - Address: v.IP, - } - if !privateKeysSet.Has(tmp.HostID) { - changeEnvStatus = append(changeEnvStatus, &tmp) - privateKeysSet.Insert(tmp.HostID) - } - } + changeEnvStatus, err := pm.GenerateEnvStatus(oldEnvConfigs, log.NopSugaredLogger()) + if err != nil { + log.Errorf("GenerateEnvStatus err:%s", err) + return err } - args := &commonservice.ServiceTmplBuildObject{ ServiceTmplObject: &commonservice.ServiceTmplObject{ ProductName: serviceTempl.ProductName, diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index 49e1e0ae..4b2012af 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -28,6 +28,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/pm" commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -74,7 +75,11 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug if err := commonrepo.NewServiceColl().Delete(args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Type, args.ServiceTmplObject.ProductName, setting.ProductStatusDeleting, args.ServiceTmplObject.Revision); err != nil { log.Errorf("pmService.delete %s error: %v", args.ServiceTmplObject.ServiceName, err) } - + envStatus, err := pm.GenerateEnvStatus(args.ServiceTmplObject.EnvConfigs, log) + if err != nil { + log.Errorf("GenerateEnvStatus %s", err) + return err + } serviceObj := &commonmodels.Service{ ServiceName: args.ServiceTmplObject.ServiceName, Type: args.ServiceTmplObject.Type, @@ -82,6 +87,8 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug Revision: args.ServiceTmplObject.Revision, Visibility: args.ServiceTmplObject.Visibility, HealthChecks: args.ServiceTmplObject.HealthChecks, + EnvConfigs: args.ServiceTmplObject.EnvConfigs, + EnvStatuses: envStatus, CreateTime: time.Now().Unix(), CreateBy: username, BuildName: args.Build.Name, diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index a6967d13..21e3e7bb 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -101,7 +101,7 @@ func DeletePrivateKey(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "资源管理-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) - ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.Logger) + ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.Logger, ctx.UserName) } func ListLabels(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index bc9d8aec..3323dd30 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -18,10 +18,13 @@ package service import ( "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/pm" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -39,7 +42,7 @@ func GetPrivateKey(id string, log *zap.SugaredLogger) (*commonmodels.PrivateKey, ID: id, }) if err != nil { - log.Errorf("PrivateKey.Find %s error: %v", id, err) + log.Errorf("PrivateKey.Find %s error: %s", id, err) return resp, e.ErrGetPrivateKey } return resp, nil @@ -71,7 +74,7 @@ func UpdatePrivateKey(id string, args *commonmodels.PrivateKey, log *zap.Sugared return nil } -func DeletePrivateKey(id string, log *zap.SugaredLogger) error { +func DeletePrivateKey(id string, log *zap.SugaredLogger, userName string) error { // 检查该私钥是否被引用 buildOpt := &commonrepo.BuildListOption{PrivateKeyID: id} builds, err := commonrepo.NewBuildColl().List(buildOpt) @@ -82,9 +85,58 @@ func DeletePrivateKey(id string, log *zap.SugaredLogger) error { err = commonrepo.NewPrivateKeyColl().Delete(id) if err != nil { - log.Errorf("PrivateKey.Delete %s error: %v", id, err) + log.Errorf("PrivateKey.Delete %s error: %s", id, err) return e.ErrDeletePrivateKey } + // update releated services , which contains the privateKey + services, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{Type: "pm"}) + if err != nil { + return err + } + for _, service := range services { + hostIDsSet := sets.NewString() + for _, config := range service.EnvConfigs { + hostIDsSet.Insert(config.HostIDs...) + } + if !hostIDsSet.Has(id) { + continue + } + // has related hostID + envConfigs := []*commonmodels.EnvConfig{} + for _, config := range service.EnvConfigs { + hostIdsSet := sets.NewString(config.HostIDs...) + if hostIdsSet.Has(id) { + hostIdsSet.Delete(id) + config.HostIDs = hostIdsSet.List() + } + envConfigs = append(envConfigs, config) + } + + envStatus, err := pm.GenerateEnvStatus(service.EnvConfigs, log) + if err != nil { + log.Errorf("GenerateEnvStatus err:%s", err) + continue + } + args := &commonservice.ServiceTmplBuildObject{ + ServiceTmplObject: &commonservice.ServiceTmplObject{ + ProductName: service.ProductName, + ServiceName: service.ServiceName, + Visibility: service.Visibility, + Revision: service.Revision, + Type: service.Type, + Username: userName, + HealthChecks: service.HealthChecks, + EnvConfigs: envConfigs, + EnvStatuses: envStatus, + From: "deletePriveteKey", + }, + Build: &commonmodels.Build{Name: service.BuildName}, + } + if err := commonservice.UpdatePmServiceTemplate(userName, args, log); err != nil { + log.Errorf("UpdatePmServiceTemplate err :%s", err) + continue + } + } return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 1658c0cb..9fea2ba8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1916,7 +1916,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL //私钥信息可能被更新,而构建中存储的信息是旧的,需要根据id获取最新的私钥信息 latestKeyInfo, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ID: sshID}) if err != nil || latestKeyInfo == nil { - log.Errorf("PrivateKey.Find failed, id:%s, err:%v", sshID, err) + log.Errorf("PrivateKey.Find failed, id:%s, err:%s", sshID, err) continue } ssh := new(task.SSH) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 903377ce..9b0fbdb6 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -247,6 +247,7 @@ const ( SourceFromExternal = "external" // service from yaml template ServiceSourceTemplate = "template" + SourceFromPM = "pm" ProdENV = "prod" TestENV = "test" -- Gitee From 159167da7c27e29eb3935657fadd8facb9d53948 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 17:16:02 +0800 Subject: [PATCH 1226/2647] modify Copyright date Signed-off-by: liu deyi --- hack/update-copyright.sh | 2 +- .../upgradeassistant/internal/repository/models/180/cluster.go | 2 +- .../upgradeassistant/internal/repository/models/180/project.go | 2 +- .../internal/repository/models/180/project_cluster_relation.go | 2 +- .../upgradeassistant/internal/repository/mongodb/180/cluster.go | 2 +- .../upgradeassistant/internal/repository/mongodb/180/project.go | 2 +- .../internal/repository/mongodb/180/project_cluster_relation.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/hack/update-copyright.sh b/hack/update-copyright.sh index 2d57741e..1533051b 100755 --- a/hack/update-copyright.sh +++ b/hack/update-copyright.sh @@ -1,7 +1,7 @@ #!/bin/bash -e # -# Copyright 2021 The KodeRover Authors. +# Copyright 2022 The KodeRover Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go index 221149ce..4e64596c 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/project.go b/pkg/cli/upgradeassistant/internal/repository/models/180/project.go index 4fb6f07e..45679d04 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/180/project.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/180/project.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go index 8045cafc..fca2a21b 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go index 420affa9..91394c6e 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go index 773f82ba..b17ef2cf 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go index 2aa85f64..4fd3fec5 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 4fd964a9e8bd6496dcd500a16ca919d3b21a0ca2 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 6 Jan 2022 17:31:47 +0800 Subject: [PATCH 1227/2647] Fixed bug Workflow names cannot be displayed in build logs with capital letters Signed-off-by: ddh27 --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index ea7daf3b..35c1e274 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -124,9 +124,9 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st return err } if store.Subfolder != "" { - store.Subfolder = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, pipelineTask.PipelineName, pipelineTask.TaskID, "log") + store.Subfolder = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, strings.ToLower(pipelineTask.PipelineName), pipelineTask.TaskID, "log") } else { - store.Subfolder = fmt.Sprintf("%s/%d/%s", pipelineTask.PipelineName, pipelineTask.TaskID, "log") + store.Subfolder = fmt.Sprintf("%s/%d/%s", strings.ToLower(pipelineTask.PipelineName), pipelineTask.TaskID, "log") } forcedPathStyle := true if store.Provider == setting.ProviderSourceAli { -- Gitee From 259d385d8e2167590eb1e9817e3584e6591d1a95 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 17:36:59 +0800 Subject: [PATCH 1228/2647] remove index code Signed-off-by: liu deyi --- .../internal/repository/mongodb/180/cluster.go | 10 +++------- .../internal/repository/mongodb/180/project.go | 16 ++-------------- .../mongodb/180/project_cluster_relation.go | 15 --------------- 3 files changed, 5 insertions(+), 36 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go index 91394c6e..6a42bbdb 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go @@ -45,22 +45,18 @@ func (c *K8SClusterColl) GetCollectionName() string { return c.coll } -func (c *K8SClusterColl) EnsureIndex(_ context.Context) error { - return nil -} - func (c *K8SClusterColl) List() ([]*_80.K8SCluster, error) { var clusters []*_80.K8SCluster query := bson.M{} cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { - return nil, err + return nil, nil } err = cursor.All(context.TODO(), &clusters) if err != nil { - return nil, err + return nil, nil } - return clusters, err + return clusters, nil } diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go index b17ef2cf..994363c9 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go @@ -21,7 +21,6 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -43,27 +42,16 @@ func (c *ProductColl) GetCollectionName() string { return c.coll } -func (c *ProductColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.M{"product_name": 1}, - Options: options.Index().SetUnique(true), - } - - _, err := c.Indexes().CreateOne(ctx, mod) - - return err -} - func (c *ProductColl) List() ([]*_80.Project, error) { var resp []*_80.Project cursor, err := c.Collection.Find(context.TODO(), bson.M{}) if err != nil { - return nil, err + return nil, nil } err = cursor.All(context.TODO(), &resp) if err != nil { - return nil, err + return nil, nil } return resp, nil diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go index 4fd3fec5..1f7e9997 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go @@ -21,9 +21,7 @@ import ( "errors" "time" - "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -48,19 +46,6 @@ func (c *ProjectClusterRelationColl) GetCollectionName() string { return c.coll } -func (c *ProjectClusterRelationColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.D{ - bson.E{Key: "project_name", Value: 1}, - bson.E{Key: "cluster_id", Value: 1}, - }, - Options: options.Index().SetUnique(true), - } - - _, err := c.Indexes().CreateOne(ctx, mod) - return err -} - func (c *ProjectClusterRelationColl) Create(args *_80.ProjectClusterRelation) error { if args == nil { return errors.New("nil projectClusterRelation info") -- Gitee From 9178c1b91be9d8fbf3759f3f97b0dff9afe218d2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 17:44:20 +0800 Subject: [PATCH 1229/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/environment/service/environment_creator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 19cc476b..1f750b2c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -295,6 +295,7 @@ func newDefaultProductCreator() *DefaultProductCreator { func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { // get project cluster relation clusterID := args.ClusterID + log.Infof("clusterID:%s", clusterID) if clusterID == "" { projectClusterRelations, err := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ ProjectName: args.ProductName, -- Gitee From 3bb2661a1b70b3073e0b2fdcd7641a82990a3167 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 18:38:21 +0800 Subject: [PATCH 1230/2647] save cluster id Signed-off-by: liu deyi --- .../aslan/core/environment/service/environment_creator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 1f750b2c..e9811f2a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -222,6 +222,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P args.Status = setting.ProductStatusCreating args.RecycleDay = config.DefaultRecycleDay() + args.ClusterID = clusterID if args.IsForkedProduct { args.RecycleDay = 7 } @@ -295,7 +296,6 @@ func newDefaultProductCreator() *DefaultProductCreator { func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { // get project cluster relation clusterID := args.ClusterID - log.Infof("clusterID:%s", clusterID) if clusterID == "" { projectClusterRelations, err := commonrepo.NewProjectClusterRelationColl().List(&commonrepo.ProjectClusterRelationOption{ ProjectName: args.ProductName, @@ -362,6 +362,7 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model args.Status = setting.ProductStatusCreating args.RecycleDay = config.DefaultRecycleDay() + args.ClusterID = clusterID err = commonrepo.NewProductColl().Create(args) if err != nil { log.Errorf("[%s][%s] create product record error: %v", args.EnvName, args.ProductName, err) -- Gitee From 892588c6fb7149764214bbad56e5c735a2785810 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 6 Jan 2022 18:51:08 +0800 Subject: [PATCH 1231/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/system/handler/private_key.go | 2 +- pkg/microservice/aslan/core/system/service/private_key.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 21e3e7bb..4ef782bb 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -101,7 +101,7 @@ func DeletePrivateKey(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "资源管理-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) - ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.Logger, ctx.UserName) + ctx.Err = service.DeletePrivateKey(c.Param("id"), ctx.UserName, ctx.Logger) } func ListLabels(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 3323dd30..847992c8 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -74,7 +74,7 @@ func UpdatePrivateKey(id string, args *commonmodels.PrivateKey, log *zap.Sugared return nil } -func DeletePrivateKey(id string, log *zap.SugaredLogger, userName string) error { +func DeletePrivateKey(id string, userName string, log *zap.SugaredLogger) error { // 检查该私钥是否被引用 buildOpt := &commonrepo.BuildListOption{PrivateKeyID: id} builds, err := commonrepo.NewBuildColl().List(buildOpt) -- Gitee From 12e502999739da9d1d2fb291ad75e59a1f4fa86c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 19:29:01 +0800 Subject: [PATCH 1232/2647] modify pkg name Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 9 ++++----- .../repository/models/{180 => }/cluster.go | 2 +- .../repository/models/{180 => }/project.go | 2 +- .../{180 => }/project_cluster_relation.go | 2 +- .../repository/mongodb/{180 => }/cluster.go | 10 +++++----- .../repository/mongodb/{180 => }/project.go | 18 +++++++++--------- .../{180 => }/project_cluster_relation.go | 8 ++++---- 7 files changed, 25 insertions(+), 26 deletions(-) rename pkg/cli/upgradeassistant/internal/repository/models/{180 => }/cluster.go (98%) rename pkg/cli/upgradeassistant/internal/repository/models/{180 => }/project.go (98%) rename pkg/cli/upgradeassistant/internal/repository/models/{180 => }/project_cluster_relation.go (98%) rename pkg/cli/upgradeassistant/internal/repository/mongodb/{180 => }/cluster.go (84%) rename pkg/cli/upgradeassistant/internal/repository/mongodb/{180 => }/project.go (72%) rename pkg/cli/upgradeassistant/internal/repository/mongodb/{180 => }/project_cluster_relation.go (84%) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index a2b9d742..2361aad2 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -22,9 +22,8 @@ import ( "go.mongodb.org/mongo-driver/bson" - _80Models "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" - _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb/180" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" @@ -147,12 +146,12 @@ func newRoleBindingColl() *mongodb.RoleBindingColl { } func initProjectClusterRelation() error { - projects, err := _80.NewProductColl().List() + projects, err := internalmongodb.NewProjectColl().List() if err != nil { log.Errorf("Failed to list projects, err: %s", err) return err } - clusters, err := _80.NewK8SClusterColl().List() + clusters, err := internalmongodb.NewK8SClusterColl().List() if err != nil { log.Errorf("Failed to list clusters, err: %s", err) return err @@ -160,7 +159,7 @@ func initProjectClusterRelation() error { for _, project := range projects { for _, cluster := range clusters { - if err := _80.NewProjectClusterRelationColl().Create(&_80Models.ProjectClusterRelation{ + if err := internalmongodb.NewProjectClusterRelationColl().Create(&internalmodels.ProjectClusterRelation{ ProjectName: project.ProductName, ClusterID: cluster.ID.Hex(), }); err != nil { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/models/cluster.go similarity index 98% rename from pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go rename to pkg/cli/upgradeassistant/internal/repository/models/cluster.go index 4e64596c..6cb614af 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/180/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/cluster.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package _80 +package models import ( "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/project.go b/pkg/cli/upgradeassistant/internal/repository/models/project.go similarity index 98% rename from pkg/cli/upgradeassistant/internal/repository/models/180/project.go rename to pkg/cli/upgradeassistant/internal/repository/models/project.go index 45679d04..33108cf3 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/180/project.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/project.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package _80 +package models type Project struct { ProductName string `bson:"product_name" json:"product_name"` diff --git a/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/models/project_cluster_relation.go similarity index 98% rename from pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go rename to pkg/cli/upgradeassistant/internal/repository/models/project_cluster_relation.go index fca2a21b..57d8fcd4 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/180/project_cluster_relation.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/project_cluster_relation.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package _80 +package models import ( "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go similarity index 84% rename from pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go rename to pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go index 6a42bbdb..1e97b9dd 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package _80 +package mongodb import ( "context" @@ -22,7 +22,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + models "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/config" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -34,7 +34,7 @@ type K8SClusterColl struct { } func NewK8SClusterColl() *K8SClusterColl { - name := _80.K8SCluster{}.TableName() + name := models.K8SCluster{}.TableName() return &K8SClusterColl{ Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name, @@ -45,8 +45,8 @@ func (c *K8SClusterColl) GetCollectionName() string { return c.coll } -func (c *K8SClusterColl) List() ([]*_80.K8SCluster, error) { - var clusters []*_80.K8SCluster +func (c *K8SClusterColl) List() ([]*models.K8SCluster, error) { + var clusters []*models.K8SCluster query := bson.M{} cursor, err := c.Collection.Find(context.TODO(), query) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/project.go similarity index 72% rename from pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go rename to pkg/cli/upgradeassistant/internal/repository/mongodb/project.go index 994363c9..02e8633e 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/project.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package _80 +package mongodb import ( "context" @@ -22,28 +22,28 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" - _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + models "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/config" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -type ProductColl struct { +type ProjectColl struct { *mongo.Collection coll string } -func NewProductColl() *ProductColl { - name := _80.Project{}.TableName() - return &ProductColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +func NewProjectColl() *ProjectColl { + name := models.Project{}.TableName() + return &ProjectColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } -func (c *ProductColl) GetCollectionName() string { +func (c *ProjectColl) GetCollectionName() string { return c.coll } -func (c *ProductColl) List() ([]*_80.Project, error) { - var resp []*_80.Project +func (c *ProjectColl) List() ([]*models.Project, error) { + var resp []*models.Project cursor, err := c.Collection.Find(context.TODO(), bson.M{}) if err != nil { diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/project_cluster_relation.go similarity index 84% rename from pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go rename to pkg/cli/upgradeassistant/internal/repository/mongodb/project_cluster_relation.go index 1f7e9997..a9b0e393 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/180/project_cluster_relation.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/project_cluster_relation.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package _80 +package mongodb import ( "context" @@ -23,7 +23,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" - _80 "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models/180" + models "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/config" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -35,7 +35,7 @@ type ProjectClusterRelationColl struct { } func NewProjectClusterRelationColl() *ProjectClusterRelationColl { - name := _80.ProjectClusterRelation{}.TableName() + name := models.ProjectClusterRelation{}.TableName() return &ProjectClusterRelationColl{ Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name, @@ -46,7 +46,7 @@ func (c *ProjectClusterRelationColl) GetCollectionName() string { return c.coll } -func (c *ProjectClusterRelationColl) Create(args *_80.ProjectClusterRelation) error { +func (c *ProjectClusterRelationColl) Create(args *models.ProjectClusterRelation) error { if args == nil { return errors.New("nil projectClusterRelation info") } -- Gitee From d9f405d571c2c431c281408b2b5de2a54f668dc5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 19:36:21 +0800 Subject: [PATCH 1233/2647] modify error return logic Signed-off-by: liu deyi --- .../internal/repository/mongodb/cluster.go | 7 +++++-- .../internal/repository/mongodb/project.go | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go index 1e97b9dd..247fd3f4 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go @@ -51,11 +51,14 @@ func (c *K8SClusterColl) List() ([]*models.K8SCluster, error) { query := bson.M{} cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { - return nil, nil + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, err } err = cursor.All(context.TODO(), &clusters) if err != nil { - return nil, nil + return nil, err } return clusters, nil diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/project.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/project.go index 02e8633e..b596036d 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/project.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/project.go @@ -47,11 +47,14 @@ func (c *ProjectColl) List() ([]*models.Project, error) { cursor, err := c.Collection.Find(context.TODO(), bson.M{}) if err != nil { - return nil, nil + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, err } err = cursor.All(context.TODO(), &resp) if err != nil { - return nil, nil + return nil, err } return resp, nil -- Gitee From 66a184d973568c96552771ce1ad4c79711611fc6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 21:01:02 +0800 Subject: [PATCH 1234/2647] If it is a local cluster, we need to return the project relationship Signed-off-by: liu deyi --- pkg/microservice/aslan/core/multicluster/service/clusters.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 73fe118c..37560e67 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -92,6 +92,10 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( continue } + if c.ID.Hex() == setting.LocalClusterID { + c.AdvancedConfig = &commonmodels.AdvancedConfig{} + } + var advancedConfig *AdvancedConfig if c.AdvancedConfig != nil { advancedConfig = &AdvancedConfig{ -- Gitee From 7e50eefaeb085c296d6afd76020be24baa040620 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 6 Jan 2022 21:01:22 +0800 Subject: [PATCH 1235/2647] optimize Signed-off-by: panxunying --- .../handler/collaboration_instance.go | 19 +++++++++ .../core/collaboration/handler/router.go | 1 + .../repository/models/collaboration_mode.go | 1 + .../service/collaboration_instance.go | 41 +++++++++++++++++-- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index 9aef1a5e..53114921 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -15,3 +15,22 @@ limitations under the License. */ package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func GetCollaborationNew(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") + return + } + ctx.Resp, ctx.Err = service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 8f454df3..50a56732 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -32,5 +32,6 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations.PUT("", gin2.UpdateOperationLogStatus, UpdateCollaborationMode) collaborations.POST("", gin2.UpdateOperationLogStatus, CreateCollaborationMode) collaborations.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) + collaborations.GET("/new", GetCollaborationNew) } } diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go index 32e11d01..11ce4b9a 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -32,6 +32,7 @@ type CollaborationMode struct { Revision int64 `bson:"revision" json:"revision"` Members []string `bson:"members" json:"members"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + DeployType string `bson:"deploy_type" json:"deploy_type"` Workflows []WorkflowCMItem `json:"workflows" bson:"workflows"` Products []ProductCMItem `bson:"products" json:"products"` CreateBy string `bson:"create_by" json:"create_by"` diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 7a637bdf..6d41aeec 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -5,10 +5,13 @@ import ( "reflect" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/util" ) @@ -19,6 +22,7 @@ type GetCollaborationUpdateResp struct { } type UpdateItem struct { CollaborationMode string `json:"collaboration_mode"` + DeployType string `json:"deploy_type"` DeleteSpec DeleteSpec `json:"delete_spec"` UpdateSpec UpdateSpec `json:"update_spec"` NewSpec NewSpec `json:"new_spec"` @@ -55,10 +59,12 @@ type Workflow struct { } type Product struct { - CollaborationType config.CollaborationType `json:"collaboration_type"` - BaseName string `json:"base_name"` - CollaborationMode string `json:"collaboration_mode"` - Name string `json:"name"` + CollaborationType config.CollaborationType `json:"collaboration_type"` + BaseName string `json:"base_name"` + CollaborationMode string `json:"collaboration_mode"` + Name string `json:"name"` + DeployType string `json:"deploy_type"` + Vars []*templatemodels.RenderKV `json:"vars"` } type GetCollaborationNewResp struct { Code int64 `json:"code"` @@ -140,6 +146,8 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } updateProductItems, newProductItems, deleteProductItems := getUpdateProductDiff(cmpMap, cipMap) return UpdateItem{ + CollaborationMode: cm.Name, + DeployType: cm.DeployType, NewSpec: NewSpec{ Workflows: newWorkflowItems, Products: newProductItems, @@ -217,6 +225,7 @@ func buildName(baseName, modeName, userName string) string { func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { var newWorkflow []Workflow var newProduct []Product + var newProductName sets.String updateResp, err := GetCollaborationUpdate(projectName, uid, logger) if err != nil { logger.Errorf("GetCollaborationNew error, err msg:%s", err) @@ -237,7 +246,9 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL BaseName: product.Name, CollaborationMode: mode.Name, Name: buildName(product.Name, mode.Name, userName), + DeployType: mode.DeployType, }) + newProductName.Insert(product.Name) } } for _, item := range updateResp.Update { @@ -255,7 +266,9 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL BaseName: product.Name, CollaborationMode: item.CollaborationMode, Name: buildName(product.Name, item.CollaborationMode, userName), + DeployType: item.DeployType, }) + newProductName.Insert(product.Name) } for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == "share" && workflow.New.CollaborationType == "new" { @@ -273,11 +286,31 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL CollaborationType: product.New.CollaborationType, BaseName: product.New.BaseName, CollaborationMode: item.CollaborationMode, + DeployType: item.DeployType, Name: buildName(product.New.BaseName, item.CollaborationMode, userName), }) + newProductName.Insert(product.New.BaseName) } } } + products, err := mongodb2.NewProductColl().List(&mongodb2.ProductListOptions{ + InProjects: []string{projectName}, + InEnvs: newProductName.List(), + }) + if err != nil { + return nil, err + } + productVarsMap := make(map[string][]*templatemodels.RenderKV) + for _, product := range products { + productVarsMap[product.EnvName] = product.Vars + } + for _, product := range newProduct { + if vars, ok := productVarsMap[product.BaseName]; ok { + product.Vars = vars + } else { + return nil, fmt.Errorf("product:%s not exist", product.BaseName) + } + } return &GetCollaborationNewResp{ Code: 10000, Workflow: newWorkflow, -- Gitee From e2964ce9401827535175147ef1e73476354599b8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 21:20:44 +0800 Subject: [PATCH 1236/2647] If it is a local cluster, we need to return the project relationship Signed-off-by: liu deyi --- pkg/microservice/aslan/core/multicluster/service/clusters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 37560e67..c4670a15 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -92,7 +92,7 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( continue } - if c.ID.Hex() == setting.LocalClusterID { + if c.ID.Hex() == setting.LocalClusterID && c.AdvancedConfig == nil { c.AdvancedConfig = &commonmodels.AdvancedConfig{} } -- Gitee From 57507445c173bb26931c3b347afb6c02a1384f0f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 21:22:41 +0800 Subject: [PATCH 1237/2647] add code annotation Signed-off-by: liu deyi --- pkg/microservice/aslan/core/multicluster/service/clusters.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index c4670a15..3b487af4 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -92,6 +92,8 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( continue } + // If it is a local cluster and the advanced configuration is empty, the project information needs to be returned, + // otherwise the front end will report an error if c.ID.Hex() == setting.LocalClusterID && c.AdvancedConfig == nil { c.AdvancedConfig = &commonmodels.AdvancedConfig{} } -- Gitee From b568c0270dcab37c528168846c435706b6a1a36a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 21:25:12 +0800 Subject: [PATCH 1238/2647] add code annotation Signed-off-by: liu deyi --- pkg/microservice/aslan/core/multicluster/service/clusters.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 3b487af4..b6383237 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -92,9 +92,9 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( continue } - // If it is a local cluster and the advanced configuration is empty, the project information needs to be returned, + // If the advanced configuration is empty, the project information needs to be returned, // otherwise the front end will report an error - if c.ID.Hex() == setting.LocalClusterID && c.AdvancedConfig == nil { + if c.AdvancedConfig == nil { c.AdvancedConfig = &commonmodels.AdvancedConfig{} } -- Gitee From f8b4d75356982183b05a20aaab98ff19448d07f1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 6 Jan 2022 20:11:35 +0800 Subject: [PATCH 1239/2647] revert code Signed-off-by: liu deyi --- .../internal/repository/mongodb/product.go | 3 +++ .../internal/repository/mongodb/registry_namespace.go | 11 ++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go index c4c92f41..d88cb2a9 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/product.go @@ -179,6 +179,9 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { } cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { + if err == mongo.ErrNoDocuments { + return nil, nil + } return nil, err } diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go index 6804be05..e814edd2 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/registry_namespace.go @@ -106,8 +106,14 @@ func (r *RegistryNamespaceColl) Find(opt *FindRegOps) (*models.RegistryNamespace res := &models.RegistryNamespace{} err := r.FindOne(context.TODO(), query).Decode(res) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, nil + } + return nil, err + } - return res, err + return res, nil } func (r *RegistryNamespaceColl) FindAll(opt *FindRegOps) ([]*models.RegistryNamespace, error) { @@ -119,6 +125,9 @@ func (r *RegistryNamespaceColl) FindAll(opt *FindRegOps) ([]*models.RegistryName cursor, err := r.Collection.Find(ctx, query, opts) if err != nil { + if err == mongo.ErrNoDocuments { + return nil, nil + } return nil, err } -- Gitee From c31e88563351b88e43b78eefba6e682784fb1a4a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 7 Jan 2022 10:38:26 +0800 Subject: [PATCH 1240/2647] cherry pick to main Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 53b5440e..2361aad2 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -21,7 +21,6 @@ import ( "fmt" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" @@ -72,17 +71,11 @@ func patchProductRegistryID() error { // get all products products, err := internalmongodb.NewProductColl().List(&internalmongodb.ProductListOptions{}) if err != nil { - if err == mongo.ErrNoDocuments { - return nil - } log.Errorf("Failed to list products, err: %s", err) return err } registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) if err != nil { - if err == mongo.ErrNoDocuments { - return nil - } log.Errorf("Failed to find default registry, err: %s", err) return err } -- Gitee From b8ac001253712c5d95efda2e1aded36e773aa01f Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 7 Jan 2022 10:47:55 +0800 Subject: [PATCH 1241/2647] fix 1.7.1->180 registry nil pointer Signed-off-by: panxunying --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 2361aad2..56d74258 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -74,11 +74,17 @@ func patchProductRegistryID() error { log.Errorf("Failed to list products, err: %s", err) return err } + if len(products) == 0 { + return nil + } registry, err := internalmongodb.NewRegistryNamespaceColl().Find(&internalmongodb.FindRegOps{IsDefault: true}) if err != nil { log.Errorf("Failed to find default registry, err: %s", err) return err } + if registry == nil { + return nil + } // change type to readable string for _, v := range products { if len(v.RegistryID) == 0 { -- Gitee From f974b64d92153475014d3083799482eb9198b78a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 7 Jan 2022 14:39:26 +0800 Subject: [PATCH 1242/2647] add local cluster project relation Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 56d74258..65ffa285 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -21,6 +21,7 @@ import ( "fmt" "go.mongodb.org/mongo-driver/bson" + "k8s.io/apimachinery/pkg/util/sets" internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" @@ -162,12 +163,20 @@ func initProjectClusterRelation() error { log.Errorf("Failed to list clusters, err: %s", err) return err } + clusterIDs := sets.NewString() + for _, cluster := range clusters { + clusterIDs.Insert(cluster.ID.Hex()) + } + + if !clusterIDs.Has(setting.LocalClusterID) { + clusterIDs.Insert(setting.LocalClusterID) + } for _, project := range projects { - for _, cluster := range clusters { + for _, clusterID := range clusterIDs.List() { if err := internalmongodb.NewProjectClusterRelationColl().Create(&internalmodels.ProjectClusterRelation{ ProjectName: project.ProductName, - ClusterID: cluster.ID.Hex(), + ClusterID: clusterID, }); err != nil { log.Warnf("Failed to create projectClusterRelation, err: %s", err) } -- Gitee From ba77c1150050b839ea72740ba77139bb74a072e2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 7 Jan 2022 15:01:06 +0800 Subject: [PATCH 1243/2647] data sync Signed-off-by: mouuii --- .../aslan/core/environment/service/pm.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 4ea46797..487929b9 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -172,16 +172,28 @@ func (p *PMService) createGroup(envName, productName, username string, group []* } if serviceTempl != nil { oldEnvConfigs := serviceTempl.EnvConfigs + newEnvConfigs := []*commonmodels.EnvConfig{} + // rm not exist env + for _, v := range oldEnvConfigs { + if _, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: v.EnvName, + }); err == nil { + if envName != v.EnvName { + newEnvConfigs = append(newEnvConfigs, v) + } + } + } for _, currentEnvConfig := range productService.EnvConfigs { envConfig := &commonmodels.EnvConfig{ EnvName: currentEnvConfig.EnvName, HostIDs: currentEnvConfig.HostIDs, Labels: currentEnvConfig.Labels, } - oldEnvConfigs = append(oldEnvConfigs, envConfig) + newEnvConfigs = append(newEnvConfigs, envConfig) } - changeEnvStatus, err := pm.GenerateEnvStatus(oldEnvConfigs, log.NopSugaredLogger()) + changeEnvStatus, err := pm.GenerateEnvStatus(newEnvConfigs, log.NopSugaredLogger()) if err != nil { log.Errorf("GenerateEnvStatus err:%s", err) return err @@ -195,7 +207,7 @@ func (p *PMService) createGroup(envName, productName, username string, group []* Type: serviceTempl.Type, Username: username, HealthChecks: serviceTempl.HealthChecks, - EnvConfigs: oldEnvConfigs, + EnvConfigs: newEnvConfigs, EnvStatuses: changeEnvStatus, From: "createEnv", }, -- Gitee From 34f77113c1fdb3c6a8f1edadba9ae8af1158cafb Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 7 Jan 2022 16:28:19 +0800 Subject: [PATCH 1244/2647] optimize Signed-off-by: panxunying --- .../service/collaboration_instance.go | 61 +++++++++++++------ .../common/repository/mongodb/render_set.go | 29 +++++++++ .../core/environment/service/renderset.go | 3 +- 3 files changed, 73 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 6d41aeec..961e58bd 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -10,8 +10,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" + models2 "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/util" ) @@ -67,9 +68,9 @@ type Product struct { Vars []*templatemodels.RenderKV `json:"vars"` } type GetCollaborationNewResp struct { - Code int64 `json:"code"` - Workflow []Workflow `json:"workflow"` - Product []Product `json:"product"` + Code int64 `json:"code"` + Workflow []*Workflow `json:"workflow"` + Product []*Product `json:"product"` } func getUpdateWorkflowDiff(cmwMap map[string]models.WorkflowCMItem, ciwMap map[string]models.WorkflowCIItem) ( @@ -223,9 +224,9 @@ func buildName(baseName, modeName, userName string) string { } func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { - var newWorkflow []Workflow - var newProduct []Product - var newProductName sets.String + var newWorkflow []*Workflow + var newProduct []*Product + newProductName := sets.String{} updateResp, err := GetCollaborationUpdate(projectName, uid, logger) if err != nil { logger.Errorf("GetCollaborationNew error, err msg:%s", err) @@ -233,7 +234,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } for _, mode := range updateResp.New { for _, workflow := range mode.Workflows { - newWorkflow = append(newWorkflow, Workflow{ + newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.CollaborationType, BaseName: workflow.Name, CollaborationMode: mode.Name, @@ -241,7 +242,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL }) } for _, product := range mode.Products { - newProduct = append(newProduct, Product{ + newProduct = append(newProduct, &Product{ CollaborationType: product.CollaborationType, BaseName: product.Name, CollaborationMode: mode.Name, @@ -253,7 +254,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } for _, item := range updateResp.Update { for _, workflow := range item.NewSpec.Workflows { - newWorkflow = append(newWorkflow, Workflow{ + newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.CollaborationType, BaseName: workflow.Name, CollaborationMode: item.CollaborationMode, @@ -261,7 +262,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL }) } for _, product := range item.NewSpec.Products { - newProduct = append(newProduct, Product{ + newProduct = append(newProduct, &Product{ CollaborationType: product.CollaborationType, BaseName: product.Name, CollaborationMode: item.CollaborationMode, @@ -272,7 +273,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == "share" && workflow.New.CollaborationType == "new" { - newWorkflow = append(newWorkflow, Workflow{ + newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.New.CollaborationType, BaseName: workflow.New.BaseName, CollaborationMode: item.CollaborationMode, @@ -282,7 +283,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } for _, product := range item.UpdateSpec.Products { if product.Old.CollaborationType == "share" && product.New.CollaborationType == "new" { - newProduct = append(newProduct, Product{ + newProduct = append(newProduct, &Product{ CollaborationType: product.New.CollaborationType, BaseName: product.New.BaseName, CollaborationMode: item.CollaborationMode, @@ -293,16 +294,13 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } } } - products, err := mongodb2.NewProductColl().List(&mongodb2.ProductListOptions{ - InProjects: []string{projectName}, - InEnvs: newProductName.List(), - }) + renderSets, err := getRenderSetKvs(projectName, newProductName.List()) if err != nil { return nil, err } productVarsMap := make(map[string][]*templatemodels.RenderKV) - for _, product := range products { - productVarsMap[product.EnvName] = product.Vars + for _, set := range renderSets { + productVarsMap[set.EnvName] = set.KVs } for _, product := range newProduct { if vars, ok := productVarsMap[product.BaseName]; ok { @@ -317,3 +315,28 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL Product: newProduct, }, nil } + +func getRenderSetKvs(projectName string, envs []string) ([]models2.RenderSet, error) { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + InProjects: []string{projectName}, + InEnvs: envs, + }) + if err != nil { + return nil, err + } + var findOpts []commonrepo.RenderSetFindOption + for _, product := range products { + findOpts = append(findOpts, commonrepo.RenderSetFindOption{ + Revision: product.Revision, + Name: product.Namespace, + }) + } + renderSets, err := commonrepo.NewRenderSetColl().ListByFindOpts(&commonrepo.RenderSetListOption{ + ProductTmpl: projectName, + FindOpts: findOpts, + }) + if err != nil { + return nil, err + } + return renderSets, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go index 5ca09748..e52d02ed 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go @@ -36,6 +36,7 @@ type RenderSetListOption struct { ProductTmpl string Revisions []int64 RendersetName string + FindOpts []RenderSetFindOption } // RenderSetFindOption ... @@ -112,6 +113,34 @@ func (c *RenderSetColl) ListRendersets(opt *RenderSetListOption) ([]*models.Rend } +func (c *RenderSetColl) ListByFindOpts(opt *RenderSetListOption) ([]models.RenderSet, error) { + var resp []models.RenderSet + condition := bson.A{} + if len(opt.FindOpts) == 0 { + return nil, nil + } + for _, findOpt := range opt.FindOpts { + condition = append(condition, bson.M{ + "name": findOpt.Name, + "revision": findOpt.Revision, + }) + } + projectCon := bson.A{} + projectCon = append(projectCon, bson.M{"product_tmpl": opt.ProductTmpl}) + filter := bson.D{{"$or", condition}, {"$and", projectCon}} + cursor, err := c.Collection.Find(context.TODO(), filter) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &resp); err != nil { + return nil, err + } + return resp, nil +} + func (c *RenderSetColl) List(opt *RenderSetListOption) ([]*models.RenderSet, error) { var pipeResp []*RenderSetPipeResp var pipeline []bson.M diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index f87764d2..3cf21c5d 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -25,12 +25,13 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + "github.com/pkg/errors" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" - "github.com/pkg/errors" ) type DefaultValuesResp struct { -- Gitee From 1e04dd1d9151f96b6edc1befa53d9ef838e278ae Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 7 Jan 2022 16:54:20 +0800 Subject: [PATCH 1245/2647] create local cluster in ua Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/180.go | 12 +++++++-- .../internal/repository/models/cluster.go | 6 ++++- .../internal/repository/mongodb/cluster.go | 26 +++++++++++++++++++ .../common/repository/mongodb/k8s_cluster.go | 3 +++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/180.go b/pkg/cli/upgradeassistant/cmd/migrate/180.go index 65ffa285..7281b931 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/180.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/180.go @@ -19,6 +19,7 @@ package migrate import ( "context" "fmt" + "time" "go.mongodb.org/mongo-driver/bson" "k8s.io/apimachinery/pkg/util/sets" @@ -168,9 +169,16 @@ func initProjectClusterRelation() error { clusterIDs.Insert(cluster.ID.Hex()) } - if !clusterIDs.Has(setting.LocalClusterID) { - clusterIDs.Insert(setting.LocalClusterID) + // insert local cluster + if err = internalmongodb.NewK8SClusterColl().Create(&internalmodels.K8SCluster{ + Name: fmt.Sprintf("%s-%s", "local", time.Now().Format("20060102150405")), + Status: setting.Normal, + Local: true, + }, setting.LocalClusterID); err != nil { + log.Errorf("Failed to create local cluster, err: %s", err) + return err } + clusterIDs.Insert(setting.LocalClusterID) for _, project := range projects { for _, clusterID := range clusterIDs.List() { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/cluster.go b/pkg/cli/upgradeassistant/internal/repository/models/cluster.go index 6cb614af..af5ac0f8 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/cluster.go @@ -17,11 +17,15 @@ limitations under the License. package models import ( + "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" ) type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + Local bool `json:"local" bson:"local"` } func (K8SCluster) TableName() string { diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go index 247fd3f4..6bd7cb27 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go @@ -20,6 +20,7 @@ import ( "context" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" models "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" @@ -63,3 +64,28 @@ func (c *K8SClusterColl) List() ([]*models.K8SCluster, error) { return clusters, nil } + +func (c *K8SClusterColl) Create(cluster *models.K8SCluster, id string) error { + if id != "" { + cluster.ID, _ = primitive.ObjectIDFromHex(id) + if _, err := c.Get(id); err == nil { + return nil + } + } + _, err := c.InsertOne(context.TODO(), cluster) + return err +} + +// Get ... +func (c *K8SClusterColl) Get(id string) (*models.K8SCluster, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + + query := bson.M{"_id": oid} + res := &models.K8SCluster{} + err = c.FindOne(context.TODO(), query).Decode(res) + + return res, err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index a9464a01..7544a5b1 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -70,6 +70,9 @@ func (c *K8SClusterColl) Delete(id string) error { func (c *K8SClusterColl) Create(cluster *models.K8SCluster, id string) error { if id != "" { cluster.ID, _ = primitive.ObjectIDFromHex(id) + if _, err := c.Get(id); err == nil { + return nil + } } res, err := c.InsertOne(context.TODO(), cluster) if oid, ok := res.InsertedID.(primitive.ObjectID); ok { -- Gitee From 3e82508540c2e0481e2d5e676b812d32ba215402 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 7 Jan 2022 17:05:13 +0800 Subject: [PATCH 1246/2647] create local cluster in ua Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go index 6bd7cb27..444b57bd 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/cluster.go @@ -68,6 +68,7 @@ func (c *K8SClusterColl) List() ([]*models.K8SCluster, error) { func (c *K8SClusterColl) Create(cluster *models.K8SCluster, id string) error { if id != "" { cluster.ID, _ = primitive.ObjectIDFromHex(id) + // If the local cluster already exists, do not insert,and return nil if _, err := c.Get(id); err == nil { return nil } @@ -76,7 +77,6 @@ func (c *K8SClusterColl) Create(cluster *models.K8SCluster, id string) error { return err } -// Get ... func (c *K8SClusterColl) Get(id string) (*models.K8SCluster, error) { oid, err := primitive.ObjectIDFromHex(id) if err != nil { -- Gitee From d0c284bffe80d154b220dd555f86b97ea6726db0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 7 Jan 2022 17:07:57 +0800 Subject: [PATCH 1247/2647] add annotation Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/k8s_cluster.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 7544a5b1..fb046ecc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -70,6 +70,7 @@ func (c *K8SClusterColl) Delete(id string) error { func (c *K8SClusterColl) Create(cluster *models.K8SCluster, id string) error { if id != "" { cluster.ID, _ = primitive.ObjectIDFromHex(id) + // If the local cluster already exists, do not insert,and return nil if _, err := c.Get(id); err == nil { return nil } -- Gitee From 04c727acbdb665c403abc0af17ee6e7c116582b7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 7 Jan 2022 17:32:52 +0800 Subject: [PATCH 1248/2647] fixed a bug where getDefaultRegistry returned an unexpected response Signed-off-by: Min Min --- .../aslan/core/common/service/registry.go | 18 ++++++++++-------- .../core/environment/service/environment.go | 2 +- .../aslan/core/environment/service/product.go | 2 +- .../aslan/core/system/handler/registry.go | 10 +++++----- .../aslan/core/system/service/registry.go | 8 ++++---- .../workflow/service/workflow/nsq_handlers.go | 2 +- .../service/workflow/pipeline_validation.go | 2 +- .../workflow/service/workflow/workflow_task.go | 8 ++++---- 8 files changed, 27 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 729c8ce5..7b1019cf 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -53,13 +53,14 @@ func (k *awsKeyWithExpiration) IsExpired() bool { return time.Now().Unix() > k.Expiration } -func FindRegistryById(registryId string, getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { +func FindRegistryById(registryId string, getRealCredential bool, log *zap.SugaredLogger) (reg *models.RegistryNamespace, isSystemDefault bool, err error) { return findRegisty(&mongodb.FindRegOps{ID: registryId}, getRealCredential, log) } -func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { +func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.SugaredLogger) (reg *models.RegistryNamespace, isSystemDefault bool, err error) { // TODO: 多租户适配 resp, err := mongodb.NewRegistryNamespaceColl().Find(regOps) + isSystemDefault = false if err != nil { log.Warnf("RegistryNamespace.Find error: %s", err) @@ -69,10 +70,11 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su SecretKey: config.RegistrySecretKey(), Namespace: config.RegistryNamespace(), } + isSystemDefault = true } if !getRealCredential { - return resp, nil + return resp, isSystemDefault, nil } switch resp.RegProvider { case config.RegistryTypeSWR: @@ -82,16 +84,16 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su realAK, realSK, err := getAWSRegistryCredential(resp.ID.Hex(), resp.AccessKey, resp.SecretKey, resp.Region) if err != nil { log.Errorf("Failed to get keypair from aws, the error is: %s", err) - return nil, err + return nil, isSystemDefault, err } resp.AccessKey = realAK resp.SecretKey = realSK } - return resp, nil + return resp, isSystemDefault, nil } -func FindDefaultRegistry(getRealCredential bool, log *zap.SugaredLogger) (*models.RegistryNamespace, error) { +func FindDefaultRegistry(getRealCredential bool, log *zap.SugaredLogger) (reg *models.RegistryNamespace, isSystemDefault bool, err error) { return findRegisty(&mongodb.FindRegOps{IsDefault: true}, getRealCredential, log) } @@ -128,7 +130,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient var reg *models.RegistryNamespace var err error if len(registryId) > 0 { - reg, err = FindRegistryById(registryId, true, log) + reg, _, err = FindRegistryById(registryId, true, log) if err != nil { log.Errorf( "service.EnsureRegistrySecret: failed to find registry: %s error msg:%s", @@ -137,7 +139,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient return err } } else { - reg, err = FindDefaultRegistry(true, log) + reg, _, err = FindDefaultRegistry(true, log) if err != nil { log.Errorf( "service.EnsureRegistrySecret: failed to find default candidate registry: %s %s", diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 16664074..80b10d2d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -186,7 +186,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) } var res []*EnvResp - reg, err := commonservice.FindDefaultRegistry(false, log) + reg, _, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("FindDefaultRegistry error: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index c1ef2713..6ba30b3d 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -170,7 +170,7 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( } if len(prod.RegistryID) == 0 { - reg, err := commonservice.FindDefaultRegistry(false, log) + reg, _, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("[User:%s][EnvName:%s][Product:%s] FindDefaultRegistry error: %s", username, envName, productName, err) return nil, err diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index e8d94e5d..9c6e950a 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -43,7 +43,7 @@ func GetDefaultRegistryNamespace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - reg, err := commonservice.FindDefaultRegistry(true, ctx.Logger) + reg, _, err := commonservice.FindDefaultRegistry(true, ctx.Logger) if err != nil { ctx.Err = err return @@ -63,7 +63,7 @@ func GetRegistryNamespace(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - reg, err := commonservice.FindRegistryById(c.Param("id"), false, ctx.Logger) + reg, _, err := commonservice.FindRegistryById(c.Param("id"), false, ctx.Logger) if err != nil { ctx.Err = err return @@ -169,9 +169,9 @@ func ListImages(c *gin.Context) { var registryInfo *commonmodels.RegistryNamespace var err error if registryID != "" { - registryInfo, err = commonservice.FindRegistryById(registryID, false, ctx.Logger) + registryInfo, _, err = commonservice.FindRegistryById(registryID, false, ctx.Logger) } else { - registryInfo, err = commonservice.FindDefaultRegistry(false, ctx.Logger) + registryInfo, _, err = commonservice.FindDefaultRegistry(false, ctx.Logger) } if err != nil { ctx.Logger.Errorf("can't find candidate registry err :%v", err) @@ -194,7 +194,7 @@ func ListRepoImages(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - registryInfo, err := commonservice.FindDefaultRegistry(false, ctx.Logger) + registryInfo, _, err := commonservice.FindDefaultRegistry(false, ctx.Logger) if err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index dc1a5c86..351f6397 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -67,7 +67,7 @@ func ListRegistries(log *zap.SugaredLogger) ([]*commonmodels.RegistryNamespace, func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { regOps := new(commonrepo.FindRegOps) regOps.IsDefault = true - defaultReg, err := commonservice.FindDefaultRegistry(false, log) + defaultReg, isSystemDefault, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Warnf("failed to find the default registry, the error is: %s", err) } @@ -79,7 +79,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa return fmt.Errorf("RegistryNamespace.Create error: %v", err) } } else { - if defaultReg == nil { + if isSystemDefault { log.Errorf("create registry error: There must be at least 1 default registry") return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } @@ -98,7 +98,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { regOps := new(commonrepo.FindRegOps) regOps.IsDefault = true - defaultReg, err := commonservice.FindDefaultRegistry(false, log) + defaultReg, isSystemDefault, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Warnf("failed to find the default registry, the error is: %s", err) } @@ -110,7 +110,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam return fmt.Errorf("RegistryNamespace.Update error: %v", err) } } else { - if defaultReg == nil { + if isSystemDefault { log.Errorf("create registry error: There must be at least 1 default registry") return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 72904801..c3cc38bc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -911,7 +911,7 @@ func upsertWorkflowStat(args *commonmodels.WorkflowStat, log *zap.SugaredLogger) } func getImageInfo(repoName, tag string, log *zap.SugaredLogger) (*commonmodels.DeliveryImage, error) { - registryInfo, err := commonservice.FindDefaultRegistry(false, log) + registryInfo, _, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("RegistryNamespace.get error: %v", err) return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index b16ec597..ab394a9a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -668,7 +668,7 @@ func SetCandidateRegistry(payload *commonmodels.ConfigPayload, log *zap.SugaredL return nil } - reg, err := commonservice.FindDefaultRegistry(true, log) + reg, _, err := commonservice.FindDefaultRegistry(true, log) if err != nil { log.Errorf("can't find default candidate registry: %v", err) return e.ErrFindRegistry.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 9fea2ba8..6f47beb5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -525,7 +525,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) if len(env.RegistryID) == 0 { - reg, err := commonservice.FindDefaultRegistry(false, log) + reg, _, err := commonservice.FindDefaultRegistry(false, log) if err != nil { log.Errorf("get default registry error: %v", err) return nil, e.ErrGetCounter.AddDesc(err.Error()) @@ -977,7 +977,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. if err != nil { log.Errorf("failed to build registry map, err: %s", err) // use default registry - reg, err := commonservice.FindDefaultRegistry(true, log) + reg, _, err := commonservice.FindDefaultRegistry(true, log) if err != nil { log.Errorf("can't find default candidate registry, err: %s", err) return nil, e.ErrFindRegistry.AddDesc(err.Error()) @@ -2082,14 +2082,14 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // 注意: 其他任务从 pt.TaskArgs.Deploy.Image 获取, 必须要有编译任务 var reg *commonmodels.RegistryNamespace if len(exitedProd.RegistryID) > 0 { - reg, err = commonservice.FindRegistryById(exitedProd.RegistryID, true, log) + reg, _, err = commonservice.FindRegistryById(exitedProd.RegistryID, true, log) if err != nil { log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", exitedProd.RegistryID, err) return e.ErrFindRegistry.AddDesc(err.Error()) } } else { - reg, err = commonservice.FindDefaultRegistry(true, log) + reg, _, err = commonservice.FindDefaultRegistry(true, log) if err != nil { log.Errorf("can't find default candidate registry: %v", err) return e.ErrFindRegistry.AddDesc(err.Error()) -- Gitee From 0b3ab61d95613be927dbc98853df701f3ed010ea Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 7 Jan 2022 18:17:05 +0800 Subject: [PATCH 1249/2647] fix bug of uploading workspace cache Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/reaper.go | 81 ++++++++++--------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 37f01922..187ebb82 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -424,56 +424,57 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } - // should archive file first, since compress cache will clean the workspace - if upStreamErr == nil { - if r.Ctx.ArtifactInfo == nil { - if err = r.archiveS3Files(); err != nil { - log.Errorf("archiveFiles err %v", err) - return err - } - // 运行构建后置脚本 - if err = r.RunPostScripts(); err != nil { - log.Errorf("RunPostScripts err %v", err) - return err - } - } else { - if err = r.downloadArtifactFile(); err != nil { - log.Errorf("download archiveFiles err %v", err) - return err - } - } + if upStreamErr != nil { + return err + } - if r.Ctx.ArtifactPath != "" { - if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { - log.Errorf("artifactsUpload err %s", err) - return err - } + if r.Ctx.ArtifactInfo == nil { + if err = r.archiveS3Files(); err != nil { + log.Errorf("archiveFiles err %v", err) + return err } - - // 运行物理机部署脚本 - if err = r.RunPMDeployScripts(); err != nil { - log.Errorf("RunPMDeployScripts err %v", err) + // 运行构建后置脚本 + if err = r.RunPostScripts(); err != nil { + log.Errorf("RunPostScripts err %v", err) return err } - - // create dog food file to tell wd that task is finished - dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(time.Now().Format(time.RFC3339)), 0644) - if dogFoodErr != nil { - log.Infof("failed to create dog food %v", dogFoodErr) - } else { - // end here - r.dogFeed = true - log.Infof("build end. duration: %.2f seconds", time.Since(r.StartTime).Seconds()) + } else { + if err = r.downloadArtifactFile(); err != nil { + log.Errorf("download archiveFiles err %v", err) + return err } } - if upStreamErr == nil { - err = r.CompressCache(r.Ctx.StorageURI) - if err != nil { - log.Errorf("Failed to run compress cache, err: %s", err) + if r.Ctx.ArtifactPath != "" { + if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { + log.Errorf("artifactsUpload err %s", err) + return err } } + // 运行物理机部署脚本 + if err = r.RunPMDeployScripts(); err != nil { + log.Errorf("RunPMDeployScripts err %v", err) + return err + } + + // Upload workspace cache. + // Note: Whether the cache is uploaded successfully or not cannot hinder the progress of the overall process, + // so only exceptions are printed here and the process is not interrupted. + if err := r.CompressCache(r.Ctx.StorageURI); err != nil { + log.Warnf("Failed to run compress cache: %s", err) + } + + // create dog food file to tell wd that task is finished + dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(time.Now().Format(time.RFC3339)), 0644) + if dogFoodErr != nil { + log.Errorf("Failed to create dog food: %s", dogFoodErr) + } else { + // end here + r.dogFeed = true + log.Infof("build end. duration: %.2f seconds", time.Since(r.StartTime).Seconds()) + } + return err } -- Gitee From 1b6ab29a6b6c56d4a2a4ca048d237b78ad5481bb Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 7 Jan 2022 18:35:02 +0800 Subject: [PATCH 1250/2647] refine comments Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/reaper.go | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 187ebb82..4caec3d9 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -368,9 +368,7 @@ func (r *Reaper) Exec() error { return r.runDockerBuild() } -// AfterExec ... func (r *Reaper) AfterExec(upStreamErr error) error { - var err error if r.Ctx.GinkgoTest != nil && r.Ctx.GinkgoTest.ResultPath != "" { resultPath := r.Ctx.GinkgoTest.ResultPath if !strings.HasPrefix(resultPath, "/") { @@ -380,81 +378,76 @@ func (r *Reaper) AfterExec(upStreamErr error) error { r.Ctx.TestType = setting.FunctionTest } if r.Ctx.TestType == setting.FunctionTest { - log.Info("merging test result") - // 解析功能测试的测试结果目录的文件,对数据进行统计,将最终的统计结果写入到一个本地文件中 - if err = mergeGinkgoTestResults( + log.Info("Merge test result.") + if err := mergeGinkgoTestResults( r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir, r.StartTime, ); err != nil { - log.Errorf("function err %v", err) + log.Errorf("Failed to merge results of ginkgo test: %s", err) return err } } else if r.Ctx.TestType == setting.PerformanceTest { - log.Info("performance test result") - // 解析性能测试的测试结果目录的文件,对数据进行统计,将最终的统计结果写入到一个本地文件中 - if err = JmeterTestResults( + log.Info("Archive performance test result.") + if err := JmeterTestResults( r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir, ); err != nil { - log.Errorf("performance err %v", err) + log.Errorf("Failed to archive results of performance test: %s", err) return err } } - // 将归档文件上传到S3 + if len(r.Ctx.GinkgoTest.ArtifactPaths) > 0 { - if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, r.Ctx.GinkgoTest.ArtifactPaths); err != nil { - log.Errorf("artifactsUpload err %s", err) + if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, r.Ctx.GinkgoTest.ArtifactPaths); err != nil { + log.Errorf("Failed to upload artifacts: %s", err) return err } } - // 将上面生成的统计结果文件上传到S3 - if err = r.archiveTestFiles(); err != nil { - log.Errorf("archiveTestFiles err %v", err) + if err := r.archiveTestFiles(); err != nil { + log.Errorf("Failed to archive test files: %s", err) return err } - // 将HTML测试报告上传到S3 - if err = r.archiveHTMLTestReportFile(); err != nil { - log.Errorf("archiveHTMLTestReportFile err %v", err) + + if err := r.archiveHTMLTestReportFile(); err != nil { + log.Errorf("Failed to archive html test report: %s", err) return err } } if upStreamErr != nil { - return err + return nil } if r.Ctx.ArtifactInfo == nil { - if err = r.archiveS3Files(); err != nil { - log.Errorf("archiveFiles err %v", err) + if err := r.archiveS3Files(); err != nil { + log.Errorf("Failed to archive S3 files: %s", err) return err } - // 运行构建后置脚本 - if err = r.RunPostScripts(); err != nil { - log.Errorf("RunPostScripts err %v", err) + if err := r.RunPostScripts(); err != nil { + log.Errorf("Failed to run postscripts: %s", err) return err } } else { - if err = r.downloadArtifactFile(); err != nil { - log.Errorf("download archiveFiles err %v", err) + if err := r.downloadArtifactFile(); err != nil { + log.Errorf("Failed to download artifact files: %s", err) return err } } if r.Ctx.ArtifactPath != "" { - if err = artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { - log.Errorf("artifactsUpload err %s", err) + if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { + log.Errorf("Failed to upload artifacts: %s", err) return err } } - // 运行物理机部署脚本 - if err = r.RunPMDeployScripts(); err != nil { - log.Errorf("RunPMDeployScripts err %v", err) + if err := r.RunPMDeployScripts(); err != nil { + log.Errorf("Failed to run deploy scripts on physical machine: %s", err) return err } @@ -465,17 +458,16 @@ func (r *Reaper) AfterExec(upStreamErr error) error { log.Warnf("Failed to run compress cache: %s", err) } - // create dog food file to tell wd that task is finished + // Create dog food file to tell wd that task has finished. dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(time.Now().Format(time.RFC3339)), 0644) if dogFoodErr != nil { log.Errorf("Failed to create dog food: %s", dogFoodErr) } else { - // end here r.dogFeed = true - log.Infof("build end. duration: %.2f seconds", time.Since(r.StartTime).Seconds()) + log.Infof("Build end. Duration: %.2f seconds.", time.Since(r.StartTime).Seconds()) } - return err + return nil } func (r *Reaper) DogFeed() bool { -- Gitee From 970c9f076c532cdf45cd45e85c86746fd0acd30a Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 7 Jan 2022 18:55:54 +0800 Subject: [PATCH 1251/2647] fixed an error where set a registry to not-default willl cause unexpected error Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 351f6397..1a745b5a 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -110,7 +110,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam return fmt.Errorf("RegistryNamespace.Update error: %v", err) } } else { - if isSystemDefault { + if isSystemDefault || (args.RegAddr == defaultReg.RegAddr && args.Namespace == defaultReg.Namespace) { log.Errorf("create registry error: There must be at least 1 default registry") return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } -- Gitee From d5f3fb496c5cfb3c3cbd19b37977ba7c6a3c4e54 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 7 Jan 2022 19:05:07 +0800 Subject: [PATCH 1252/2647] code review changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 1a745b5a..0ec67bdf 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -71,7 +71,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa if err != nil { log.Warnf("failed to find the default registry, the error is: %s", err) } - if args.IsDefault && defaultReg != nil { + if args.IsDefault && defaultReg != nil && !isSystemDefault { defaultReg.IsDefault = false err := UpdateRegistryNamespaceDefault(defaultReg, log) if err != nil { @@ -102,7 +102,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam if err != nil { log.Warnf("failed to find the default registry, the error is: %s", err) } - if args.IsDefault && defaultReg != nil { + if args.IsDefault && defaultReg != nil && !isSystemDefault { defaultReg.IsDefault = false err := UpdateRegistryNamespaceDefault(defaultReg, log) if err != nil { @@ -110,7 +110,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam return fmt.Errorf("RegistryNamespace.Update error: %v", err) } } else { - if isSystemDefault || (args.RegAddr == defaultReg.RegAddr && args.Namespace == defaultReg.Namespace) { + if isSystemDefault || id == defaultReg.ID.Hex() { log.Errorf("create registry error: There must be at least 1 default registry") return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } -- Gitee From 3fd1370ab65b8c78b8eb36e85a8b7f3ef08b7d09 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 7 Jan 2022 19:16:25 +0800 Subject: [PATCH 1253/2647] share service bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/revision.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 2c46c0e2..be9a6853 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -34,7 +34,8 @@ func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) ( products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName}) // list services with max revision of project - allServiceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: productName}) + // TODO refactor code + allServiceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{}) if err != nil { log.Errorf("ListAllRevisions error: %v", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) -- Gitee From 7339554b06ef802bdc5f1f353b800b1f73b40b37 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 7 Jan 2022 19:26:01 +0800 Subject: [PATCH 1254/2647] remove check image registry id Signed-off-by: panxunying --- .../aslan/core/environment/handler/environment.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index bfe6e1af..158a3634 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -247,11 +247,6 @@ func CreateProduct(c *gin.Context) { return } - if args.RegistryID == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("RegistryId can not be null!") - return - } - args.UpdateBy = ctx.UserName ctx.Err = service.CreateProduct( ctx.UserName, ctx.RequestID, args, ctx.Logger, -- Gitee From 1b53a3b6cd2607506995e8a4dfac64719e4cebe6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 7 Jan 2022 20:32:26 +0800 Subject: [PATCH 1255/2647] bugfix Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 0ec67bdf..853cc712 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -71,12 +71,14 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa if err != nil { log.Warnf("failed to find the default registry, the error is: %s", err) } - if args.IsDefault && defaultReg != nil && !isSystemDefault { - defaultReg.IsDefault = false - err := UpdateRegistryNamespaceDefault(defaultReg, log) - if err != nil { - log.Errorf("updateRegistry error: %v", err) - return fmt.Errorf("RegistryNamespace.Create error: %v", err) + if args.IsDefault { + if defaultReg != nil && !isSystemDefault { + defaultReg.IsDefault = false + err := UpdateRegistryNamespaceDefault(defaultReg, log) + if err != nil { + log.Errorf("updateRegistry error: %v", err) + return fmt.Errorf("RegistryNamespace.Create error: %v", err) + } } } else { if isSystemDefault { @@ -102,12 +104,14 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam if err != nil { log.Warnf("failed to find the default registry, the error is: %s", err) } - if args.IsDefault && defaultReg != nil && !isSystemDefault { - defaultReg.IsDefault = false - err := UpdateRegistryNamespaceDefault(defaultReg, log) - if err != nil { - log.Errorf("updateRegistry error: %v", err) - return fmt.Errorf("RegistryNamespace.Update error: %v", err) + if args.IsDefault { + if defaultReg != nil && !isSystemDefault { + defaultReg.IsDefault = false + err := UpdateRegistryNamespaceDefault(defaultReg, log) + if err != nil { + log.Errorf("updateRegistry error: %v", err) + return fmt.Errorf("RegistryNamespace.Update error: %v", err) + } } } else { if isSystemDefault || id == defaultReg.ID.Hex() { -- Gitee From ff0e5ba72eb845e098cc123a40b924bacfcfa1b9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 7 Jan 2022 21:08:17 +0800 Subject: [PATCH 1256/2647] fix secret bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 80b10d2d..1e1e0d10 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2307,7 +2307,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha releaseutil.Reverse(hrs, releaseutil.SortByRevision) rel := hrs[0] if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { - secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", rel.Name, rel.Version) + secretName := fmt.Sprintf("sh.helm.release.v1.%s.v%d", rel.Name, rel.Version) deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, kubecli) if deleteErr != nil { err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 08916cb0..50448522 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -571,7 +571,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * releaseutil.Reverse(hrs, releaseutil.SortByRevision) rel := hrs[0] if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { - secretName := fmt.Sprintf("sh.helm.release.v1.%s.v.%d", rel.Name, rel.Version) + secretName := fmt.Sprintf("sh.helm.release.v1.%s.v%d", rel.Name, rel.Version) deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, p.kubeClient) if deleteErr != nil { err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) -- Gitee From 1ddd2bdca12d36845fb4f9d478f6bb401677cd3d Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 10 Jan 2022 11:53:43 +0800 Subject: [PATCH 1257/2647] im notify Signed-off-by: ddh27 --- pkg/microservice/aslan/config/consts.go | 1 + .../common/repository/models/task/model.go | 62 +++ .../core/common/repository/models/testing.go | 1 + .../common/service/instantmessage/dingTalk.go | 44 ++ .../common/service/instantmessage/lark.go | 175 ++++++++ .../{wechat => instantmessage}/service.go | 408 ++++++++++++------ .../service/instantmessage/wechatwork.go | 55 +++ .../core/common/service/notify/client.go | 40 +- 8 files changed, 644 insertions(+), 142 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go create mode 100644 pkg/microservice/aslan/core/common/service/instantmessage/lark.go rename pkg/microservice/aslan/core/common/service/{wechat => instantmessage}/service.go (39%) create mode 100644 pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 76a93ce5..1b8aca66 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -104,6 +104,7 @@ const ( StatusQueued Status = "queued" StatusBlocked Status = "blocked" QueueItemPending Status = "pending" + StatusChanged Status = "changed" ) type TaskStatus string diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 6ee47564..6e80028c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -100,3 +100,65 @@ func IToi(before interface{}, after interface{}) error { return nil } + +type TestReport struct { + FunctionTestSuite *TestSuite `bson:"function_test_suite,omitempty" json:"functionTestSuite,omitempty"` + PerformanceTestSuites []*PerformanceTestSuite `bson:"performance_test_suite,omitempty" json:"performanceTestSuite,omitempty"` + Security map[string]map[string]int `bson:"security,omitempty" json:"security,omitempty"` +} + +// TestSuite ... +type TestSuite struct { + // 总数=tests+skips 成功=tests-failures-errors + Tests int `bson:"tests" json:"tests" xml:"tests,attr"` + Failures int `bson:"failures" json:"failures" xml:"failures,attr"` + Successes int `bson:"successes,omitempty" json:"successes,omitempty" xml:"successes,attr,omitempty"` + Skips int `bson:"skips" json:"skips" xml:"skips,attr"` + Errors int `bson:"errors,omitempty" json:"errors" xml:"errors,attr,omitempty"` + Time float64 `bson:"time" json:"time" xml:"time,attr"` + SystemOut string `bson:"system_out,omitempty" json:"system_out" xml:"system-out,omitempty"` + SystemErr string `bson:"system-err,omitempty" json:"system_err" xml:"system-err,omitempty"` + TestCases []TestCase `bson:"testcase" json:"testcase" xml:"testcase"` + SuiteType string `bson:"-" json:"-" xml:"-"` + Name string `bson:"name" json:"-" xml:"-"` +} + +type PerformanceTestSuite struct { + Label string `bson:"label" json:"label"` + Samples string `bson:"samples" json:"samples"` + Average string `bson:"average" json:"average"` + Min string `bson:"min" json:"min"` + Max string `bson:"max" json:"max"` + Line string `bson:"line" json:"line"` + StdDev string `bson:"std_dev" json:"stdDev"` + Error string `bson:"error" json:"error"` + Throughput string `bson:"throughput" json:"throughput"` + ReceivedKb string `bson:"received_kb" json:"receivedKb"` + AvgByte string `bson:"avg_byte" json:"avgByte"` +} + +type Skipped struct { +} + +type Failure struct { + Message string `bson:"message" json:"message" xml:"message,attr"` + Type string `bson:"type" json:"type" xml:"type,attr"` + Text string `bson:"text" json:"text" xml:",chardata"` +} + +type TestCase struct { + Name string `bson:"tc_name" json:"tc_name" xml:"name,attr"` + ClassName string `bson:"classname" json:"classname" xml:"classname,attr"` + Time float64 `bson:"time" json:"time" xml:"time,attr"` + Failure *Failure `bson:"failure,omitempty" json:"failure" xml:"failure,omitempty"` + Skipped *Skipped `bson:"skipped,omitempty" json:"skipped" xml:"skipped,omitempty"` + SystemOut string `bson:"system_out,omitempty" json:"system_out" xml:"system-out,omitempty"` + SystemErr string `bson:"system-err,omitempty" json:"system_err" xml:"system-err,omitempty"` + Error *Error `bson:"error,omitempty" json:"error" xml:"error,omitempty"` +} + +type Error struct { + Message string `bson:"message" json:"message" xml:"message,attr"` + Type string `bson:"type" json:"type" xml:"type,attr"` + Text string `bson:"text" json:"text" xml:",chardata"` +} diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index 005f1eb8..ff0b9649 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -50,6 +50,7 @@ type Testing struct { Workflows []*Workflow `bson:"-" json:"workflows,omitempty"` Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` } diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go b/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go new file mode 100644 index 00000000..01bac888 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go @@ -0,0 +1,44 @@ +package instantmessage + +const ( + dingDingType = "dingding" +) + +type DingDingMessage struct { + MsgType string `json:"msgtype"` + MarkDown *DingDingMarkDown `json:"markdown"` + At *DingDingAt `json:"at"` +} + +type DingDingMarkDown struct { + Title string `json:"title"` + Text string `json:"text"` +} + +type DingDingAt struct { + AtMobiles []string `json:"atMobiles"` + IsAtAll bool `json:"isAtAll"` +} + +func (w *Service) sendDingDingMessage(uri string, title string, content string, atMobiles []string) error { + message := &DingDingMessage{ + MsgType: msgType, + MarkDown: &DingDingMarkDown{ + Title: title, + Text: content, + }, + } + if len(atMobiles) > 0 { + message.At = &DingDingAt{ + AtMobiles: atMobiles, + IsAtAll: false, + } + } else { + message.At = &DingDingAt{ + IsAtAll: true, + } + } + + _, err := w.SendMessageRequest(uri, message) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go new file mode 100644 index 00000000..40f38d2f --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go @@ -0,0 +1,175 @@ +package instantmessage + +import ( + "strings" +) + +const ( + feiShuType = "feishu" + feishuCardType = "interactive" + feishuHeaderTemplate = "turquoise" + feiShuTagText = "plain_text" + feishuTagMd = "lark_md" + feishuTagAction = "action" + feishuTagDiv = "div" + feishuTagButton = "button" +) + +type LarkCardReq struct { + MsgType string `json:"msg_type"` + Card *LarkCard `json:"card"` +} + +type LarkCard struct { + Config *Config `json:"config"` + Header *Header `json:"header"` + I18NElements *I18NElements `json:"i18n_elements"` +} + +type Config struct { + WideScreenMode bool `json:"wide_screen_mode"` +} + +type Title struct { + Content string `json:"content"` + Tag string `json:"tag"` +} + +type Header struct { + Template string `json:"template"` + Title Title `json:"title"` +} + +type TextElem struct { + Content string `json:"content"` + Tag string `json:"tag"` +} + +type Field struct { + IsShort bool `json:"is_short"` + Text TextElem `json:"text"` +} + +type Action struct { + Tag string `json:"tag"` + Text TextElem `json:"text"` + Type string `json:"type"` + URL string `json:"url"` +} + +type ZhCn struct { + Fields []*Field `json:"fields,omitempty"` + Tag string `json:"tag"` + Actions []*Action `json:"actions,omitempty"` +} + +type I18NElements struct { + ZhCn []*ZhCn `json:"zh_cn"` +} + +type FeiShuMessage struct { + Title string `json:"title"` + Text string `json:"text"` +} + +type FeiShuMessageV2 struct { + MsgType string `json:"msg_type"` + Content FeiShuContentV2 `json:"content"` +} + +type FeiShuContentV2 struct { + Text string `json:"text"` +} + +func NewLarkCard() *LarkCard { + return &LarkCard{ + Config: &Config{}, + Header: &Header{}, + I18NElements: &I18NElements{ZhCn: make([]*ZhCn, 0)}, + } +} + +func (lc *LarkCard) SetConfig(wideScreenMode bool) { + if lc.Config == nil { + lc.Config = &Config{} + } + lc.Config.WideScreenMode = wideScreenMode +} + +func (lc *LarkCard) SetHeader(template, title, tag string) { + if lc.Header == nil { + lc.Header = &Header{} + } + lc.Header.Template = template + lc.Header.Title = Title{ + Content: title, + Tag: tag, + } +} + +func (lc *LarkCard) AddI18NElementsZhcnFeild(content string) { + if lc.I18NElements == nil { + lc.I18NElements = &I18NElements{ + ZhCn: make([]*ZhCn, 0), + } + } + field := &Field{ + IsShort: false, + Text: TextElem{ + Content: content, + Tag: feishuTagMd, + }, + } + zhcnElem := &ZhCn{ + Fields: []*Field{field}, + Tag: feishuTagDiv, + } + lc.I18NElements.ZhCn = append(lc.I18NElements.ZhCn, zhcnElem) +} + +func (lc *LarkCard) AddI18NElementsZhcnAction(content, url string) { + if lc.I18NElements == nil { + lc.I18NElements = &I18NElements{ + ZhCn: make([]*ZhCn, 0), + } + } + action := &Action{ + Tag: feishuTagButton, + Text: TextElem{Content: content, Tag: feiShuTagText}, + Type: "primary", + URL: url, + } + zhcnElem := &ZhCn{ + Actions: []*Action{action}, + Tag: feishuTagAction, + } + lc.I18NElements.ZhCn = append(lc.I18NElements.ZhCn, zhcnElem) +} + +func (w *Service) sendFeishuMessage(uri string, lcMsg *LarkCard) error { + var message interface{} + message = LarkCardReq{ + MsgType: feishuCardType, + Card: lcMsg, + } + _, err := w.SendMessageRequest(uri, message) + return err +} + +func (w *Service) sendFeishuMessageOfSingleType(title, uri, content string) error { + var message interface{} + message = &FeiShuMessage{ + Title: title, + Text: content, + } + if strings.Contains(uri, "bot/v2/hook") { + message = &FeiShuMessageV2{ + MsgType: "text", + Content: FeiShuContentV2{ + Text: content, + }, + } + } + _, err := w.SendMessageRequest(uri, message) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/wechat/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go similarity index 39% rename from pkg/microservice/aslan/core/common/service/wechat/service.go rename to pkg/microservice/aslan/core/common/service/instantmessage/service.go index 579df39f..78dc2fc8 100644 --- a/pkg/microservice/aslan/core/common/service/wechat/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package wechat +package instantmessage import ( "bytes" @@ -36,68 +36,28 @@ import ( ) const ( - msgType = "markdown" - markdownColorInfo = "info" - markdownColorComment = "comment" - markdownColorWarning = "warning" - singleInfo = "single" - multiInfo = "multi" - dingDingType = "dingding" - feiShuType = "feishu" + msgType = "markdown" + singleInfo = "single" + multiInfo = "multi" ) -//wechat -type Messsage struct { - MsgType string `json:"msgtype"` - Text *Text `json:"markdown"` -} - -type Text struct { - Content string `json:"content"` -} - -//dingding -type DingDingMessage struct { - MsgType string `json:"msgtype"` - MarkDown *DingDingMarkDown `json:"markdown"` - At *DingDingAt `json:"at"` -} - -type DingDingMarkDown struct { - Title string `json:"title"` - Text string `json:"text"` -} - -type DingDingAt struct { - AtMobiles []string `json:"atMobiles"` - IsAtAll bool `json:"isAtAll"` -} - -type FeiShuMessage struct { - Title string `json:"title"` - Text string `json:"text"` -} - -type FeiShuMessageV2 struct { - MsgType string `json:"msg_type"` - Content FeiShuContentV2 `json:"content"` -} - -type FeiShuContentV2 struct { - Text string `json:"text"` -} +type TextType string type Service struct { - proxyColl *mongodb.ProxyColl - workflowColl *mongodb.WorkflowColl - pipelineColl *mongodb.PipelineColl + proxyColl *mongodb.ProxyColl + workflowColl *mongodb.WorkflowColl + pipelineColl *mongodb.PipelineColl + testingColl *mongodb.TestingColl + testTaskStatColl *mongodb.TestTaskStatColl } func NewWeChatClient() *Service { return &Service{ - proxyColl: mongodb.NewProxyColl(), - workflowColl: mongodb.NewWorkflowColl(), - pipelineColl: mongodb.NewPipelineColl(), + proxyColl: mongodb.NewProxyColl(), + workflowColl: mongodb.NewWorkflowColl(), + pipelineColl: mongodb.NewPipelineColl(), + testingColl: mongodb.NewTestingColl(), + testTaskStatColl: mongodb.NewTestTaskStatColl(), } } @@ -129,18 +89,21 @@ func (w *Service) SendMessageRequest(uri string, message interface{}) ([]byte, e return res.Body(), nil } -func (w *Service) SendWechatMessage(task *task.Task) error { +func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool) error { var ( - uri = "" - content = "" - webHookType = "" - atMobiles []string - isAtAll bool + uri = "" + content = "" + webHookType = "" + atMobiles []string + isAtAll bool + title = "" + buttonContent = "" + buttonURL = "" ) if task.Type == config.SingleType { resp, err := w.pipelineColl.Find(&mongodb.PipelineFindOption{Name: task.PipelineName}) if err != nil { - log.Errorf("Pipeline find err :%v", err) + log.Errorf("Pipeline find err :%s", err) return err } if resp.NotifyCtl == nil { @@ -168,14 +131,14 @@ func (w *Service) SendWechatMessage(task *task.Task) error { IsAtAll: isAtAll, }) if err != nil { - log.Errorf("pipeline CreateNotifyBody err :%v", err) + log.Errorf("pipeline createNotifyBody err :%s", err) return err } } } else if task.Type == config.WorkflowType { resp, err := w.workflowColl.Find(task.PipelineName) if err != nil { - log.Errorf("Workflow find err :%v", err) + log.Errorf("Workflow find err :%s", err) return err } if resp.NotifyCtl == nil { @@ -193,7 +156,7 @@ func (w *Service) SendWechatMessage(task *task.Task) error { } else { uri = resp.NotifyCtl.WeChatWebHook } - content, err = w.createNotifyBody(&wechatNotification{ + title, content, buttonContent, buttonURL, err = w.createNotifyBodyOfWorkflowIM(&wechatNotification{ Task: task, BaseURI: configbase.SystemAddress(), IsSingle: false, @@ -203,7 +166,43 @@ func (w *Service) SendWechatMessage(task *task.Task) error { IsAtAll: isAtAll, }) if err != nil { - log.Errorf("workflow CreateNotifyBody err :%v", err) + log.Errorf("workflow createNotifyBodyOfWorkflowIM err :%s", err) + return err + } + } + } else if task.Type == config.TestType { + resp, err := w.testingColl.Find(task.PipelineName, task.ProductName) + if err != nil { + log.Errorf("testing find err :%s", err) + return err + } + if resp.NotifyCtl == nil { + log.Infof("testing notifyCtl is not set!") + return nil + } + statusSets := sets.NewString(resp.NotifyCtl.NotifyTypes...) + if resp.NotifyCtl.Enabled && (statusSets.Has(string(task.Status)) || (testTaskStatusChanged && statusSets.Has(string(config.StatusChanged)))) { + webHookType = resp.NotifyCtl.WebHookType + if webHookType == dingDingType { + uri = resp.NotifyCtl.DingDingWebHook + atMobiles = resp.NotifyCtl.AtMobiles + isAtAll = resp.NotifyCtl.IsAtAll + } else if webHookType == feiShuType { + uri = resp.NotifyCtl.FeiShuWebHook + } else { + uri = resp.NotifyCtl.WeChatWebHook + } + title, content, err = w.createNotifyBodyOfTestIM(resp.Desc, &wechatNotification{ + Task: task, + BaseURI: configbase.SystemAddress(), + IsSingle: false, + WebHookType: webHookType, + TotalTime: time.Now().Unix() - task.StartTime, + AtMobiles: atMobiles, + IsAtAll: isAtAll, + }) + if err != nil { + log.Errorf("testing createNotifyBodyOfTestIM err :%s", err) return err } } @@ -211,63 +210,44 @@ func (w *Service) SendWechatMessage(task *task.Task) error { if uri != "" && content != "" { if webHookType == dingDingType { - message := &DingDingMessage{ - MsgType: msgType, - MarkDown: &DingDingMarkDown{ - Title: "工作流状态", - Text: content, - }, + if task.Type == config.SingleType { + title = "工作流状态" } - if len(atMobiles) > 0 { - message.At = &DingDingAt{ - AtMobiles: atMobiles, - IsAtAll: false, - } - } else { - message.At = &DingDingAt{ - IsAtAll: true, - } - } - - _, err := w.SendMessageRequest(uri, message) + err := w.sendDingDingMessage(uri, title, content, atMobiles) if err != nil { - log.Errorf("SendDingDingMessageRequest err : %v", err) + log.Errorf("sendDingDingMessage err : %s", err) return err } - } else if webHookType == feiShuType { - var message interface{} - message = &FeiShuMessage{ - Title: "工作流状态", - Text: content, - } - if strings.Contains(uri, "bot/v2/hook") { - message = &FeiShuMessageV2{ - MsgType: "text", - Content: FeiShuContentV2{ - Text: content, - }, + if task.Type == config.SingleType { + err := w.sendFeishuMessageOfSingleType("工作流状态", uri, content) + if err != nil { + log.Errorf("sendFeishuMessageOfSingleType Request err : %s", err) + return err } + return nil } - _, err := w.SendMessageRequest(uri, message) + lc := NewLarkCard() + lc.SetConfig(true) + lc.SetHeader(feishuHeaderTemplate, title, feiShuTagText) + lc.AddI18NElementsZhcnFeild(content) + lc.AddI18NElementsZhcnAction(buttonContent, buttonURL) + err := w.sendFeishuMessage(uri, lc) if err != nil { - log.Errorf("SendFeiShuMessageRequest err : %v", err) + log.Errorf("SendFeiShuMessageRequest err : %s", err) return err } } else { - message := &Messsage{ - MsgType: msgType, - Text: &Text{ - Content: content, - }, + typeText := weChatTextTypeMarkdown + if task.Type == config.SingleType { + typeText = weChatTextTypeText } - _, err := w.SendMessageRequest(uri, message) + err := w.SendWeChatWorkMessage(typeText, uri, title+content) if err != nil { - log.Errorf("SendWeChatMessageRequest err : %v", err) + log.Errorf("SendWeChatWorkMessage err : %s", err) return err } } - } return nil } @@ -298,31 +278,170 @@ func (w *Service) createNotifyBody(weChatNotification *wechatNotification) (cont } } - tmpl := template.Must(template.New("notify").Funcs(template.FuncMap{ - "getColor": func(status config.Status) string { - if status == config.StatusPassed { - return markdownColorInfo - } else if status == config.StatusTimeout || status == config.StatusCancelled { - return markdownColorComment - } else if status == config.StatusFailed { - return markdownColorWarning + tplcontent, err := getTplExec(tmplSource, weChatNotification) + return tplcontent, err +} + +func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotification) (string, string, string, string, error) { + + tplTitle := " ####
👍{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}
\n" + tplcontent := "**创建者**:{{.Task.TaskCreator}} **持续时间**:{{ .TotalTime}} 秒 \n" + + "**环境信息**:{{.Task.WorkflowArgs.EnvName}} **开始时间**:{{.Task.StartTime}} \n" + build := "" + deploy := "" + test := "" + distribute := "" + for _, subStage := range weChatNotification.Task.Stages { + switch subStage.TaskType { + case config.TaskBuild: + build = "**构建**:\n" + for _, sb := range subStage.SubTasks { + buildSt, err := base.ToBuildTask(sb) + if err != nil { + return "", "", "", "", err + } + branch := "" + commitID := "" + commitMsg := "" + gitCommitURL := "" + for idx, build := range buildSt.JobCtx.Builds { + if idx == 0 || build.IsPrimary { + branch = "Branch-" + build.Branch + commitID = build.CommitID + commitMsg = build.CommitMessage + gitCommitURL = fmt.Sprintf("%s/%s/%s/commit/%s", build.Address, build.RepoOwner, build.RepoName, commitID) + } + } + build += fmt.Sprintf("- %s/%s status:%s [%s-%s](%s) commmitMsg:%s\n", buildSt.ServiceName, buildSt.Service, buildSt.BuildStatus.Status, branch, commitID, gitCommitURL, commitMsg) } - return markdownColorComment - }, - "isSingle": func(isSingle bool) string { - if isSingle { - return singleInfo + case config.TaskArtifact: + + case config.TaskDeploy: + deploy = "**部署**\n" + for svrModule, sb := range subStage.SubTasks { + deploySt, err := base.ToDeployTask(sb) + if err != nil { + return "", "", "", "", err + } + deploy += fmt.Sprintf("- %s/%s status:%s image:%s\n", svrModule, deploySt.ServiceName, deploySt.TaskStatus, deploySt.Image) } - return multiInfo - }, - }).Parse(tmplSource)) - buffer := bytes.NewBufferString("") + case config.TaskTestingV2: + test = "**测试**\n" + for _, sb := range subStage.SubTasks { + testSt, err := base.ToTestingTask(sb) + if err != nil { + return "", "", "", "", err + } + if weChatNotification.Task.TestReports == nil { + continue + } + for testname, report := range weChatNotification.Task.TestReports { + if testname != testSt.TestName { + continue + } + tr := task.TestReport{} + if task.IToi(report, tr) != nil { + log.Errorf("parse TestReport failed, err:%s", err) + continue + } + url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s\n", testSt.TestName) + //status:="{{if eq .WebHookType \"feishu\"}}{{.Task.Status}}{{else}}{{.Task.Status}}{{end}} \n" + test += fmt.Sprintf("- [%s](%s) status:%s", testSt.TestName, url, testSt.TaskStatus) + if tr.FunctionTestSuite == nil { + test += "\n" + continue + } + test += fmt.Sprintf(":%d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + } + } + case config.TaskDistribute, config.TaskDistributeToS3: + build = "**分发**:\n" + for _, sb := range subStage.SubTasks { + distributeSt, err := base.ToDistributeToS3Task(sb) + if err != nil { + return "", "", "", "", err + } + distribute += fmt.Sprintf("- %s status:%s", distributeSt.ServiceName, distributeSt.TaskStatus) + } + } + } - if err = tmpl.Execute(buffer, &weChatNotification); err != nil { - return + if weChatNotification.WebHookType == dingDingType { + if len(weChatNotification.AtMobiles) > 0 && !weChatNotification.IsAtAll { + tplcontent = fmt.Sprintf("%s \n 相关人员:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) + } + } + buttonContent := "点击查看更多信息" + workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/pipelines/{{ isSingle .IsSingle }}/{{.Task.PipelineName}}/{{.Task.TaskID}}" + moreInformation := fmt.Sprintf("[%s](%s)", buttonContent, workflowDetailURL) + if weChatNotification.WebHookType == feiShuType { + tplcontent = fmt.Sprintf("%s%s%s%s%s", tplcontent, build, deploy, test, distribute) + } else { + tplcontent = fmt.Sprintf("%s%s%s%s%s%s", tplcontent, build, deploy, test, distribute, moreInformation) } - return buffer.String(), nil + tplExecContent, _ := getTplExec(tplcontent, weChatNotification) + tplExecTitle, _ := getTplExec(tplTitle, weChatNotification) + execButtonContent, _ := getTplExec(buttonContent, weChatNotification) + execButtonURL, _ := getTplExec(workflowDetailURL, weChatNotification) + return tplExecTitle, tplExecContent, execButtonContent, execButtonURL, nil +} + +func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wechatNotification) (string, string, error) { + tplTitle := " ####
👍{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}
\n" + tplcontent := "**创建者**:{{.Task.TaskCreator}} **持续时间**:{{ .TotalTime}} 秒 \n" + + "**测试描述**: \n " + desc + + "**测试结果** \n" + for _, stage := range weChatNotification.Task.Stages { + if stage.TaskType != config.TaskTestingV2 { + continue + } + + for testName, subTask := range stage.SubTasks { + testInfo, err := base.ToTestingTask(subTask) + if err != nil { + log.Errorf("parse testInfo failed, err:%s", err) + continue + } + if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestReportPath != "" { + url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s\n", testInfo.TestName) + //status:="{{if eq .WebHookType \"feishu\"}}{{.Task.Status}}{{else}}{{.Task.Status}}{{end}} \n" + tplcontent += fmt.Sprintf("- [%s](%s) status:%s", testName, url, testInfo.TaskStatus) + if weChatNotification.Task.TestReports == nil { + continue + } + for testname, report := range weChatNotification.Task.TestReports { + if testname != testInfo.TestName { + continue + } + tr := task.TestReport{} + if task.IToi(report, tr) != nil { + log.Errorf("parse TestReport failed, err:%s", err) + continue + } + if tr.FunctionTestSuite == nil { + tplcontent += "\n" + continue + } + tplcontent += fmt.Sprintf(":%d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + } + } + } + } + + if weChatNotification.WebHookType == dingDingType { + if len(weChatNotification.AtMobiles) > 0 && !weChatNotification.IsAtAll { + tplcontent = fmt.Sprintf("%s - 相关人员:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) + } + } + + workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/pipelines/{{ isSingle .IsSingle }}/{{.Task.PipelineName}}/{{.Task.TaskID}}" + moreInformation := fmt.Sprintf("[点击查看更多信息](%s)", workflowDetailURL) + tplcontent += moreInformation + + tplExecContent, err := getTplExec(tplcontent, weChatNotification) + tplExecTitle, err := getTplExec(tplTitle, weChatNotification) + return tplExecTitle, tplExecContent, err } func getHTMLTestReport(task *task.Task) []string { @@ -351,3 +470,38 @@ func getHTMLTestReport(task *task.Task) []string { return testNames } + +func getTplExec(tplcontent string, weChatNotification *wechatNotification) (string, error) { + tmpl := template.Must(template.New("notify").Funcs(template.FuncMap{ + "getColor": func(status config.Status) string { + if status == config.StatusPassed { + return markdownColorInfo + } else if status == config.StatusTimeout || status == config.StatusCancelled { + return markdownColorComment + } else if status == config.StatusFailed { + return markdownColorWarning + } + return markdownColorComment + }, + "isSingle": func(isSingle bool) string { + if isSingle { + return singleInfo + } + return multiInfo + }, + "taskStatus": func(status config.Status) string { + if status == config.StatusPassed { + return "执行成功" + } + return "执行失败" + }, + }).Parse(tplcontent)) + + buffer := bytes.NewBufferString("") + if err := tmpl.Execute(buffer, &weChatNotification); err != nil { + log.Errorf("getTplExec Execute err:%s", err) + return "", fmt.Errorf("getTplExec Execute err:%s", err) + + } + return buffer.String(), nil +} diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go new file mode 100644 index 00000000..fbcb61b4 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go @@ -0,0 +1,55 @@ +package instantmessage + +import ( + "fmt" +) + +const ( + markdownColorInfo = "info" + markdownColorComment = "comment" + markdownColorWarning = "warning" + weChatTextTypeText TextType = "text" + weChatTextTypeMarkdown TextType = "markdown" +) + +type WeChatWorkCard struct { + MsgType string `json:"msgtype"` + Markdown Markdown `json:"markdown"` +} +type Markdown struct { + Content string `json:"content"` +} + +//wechat +type Messsage struct { + MsgType string `json:"msgtype"` + Text *Text `json:"markdown"` +} + +type Text struct { + Content string `json:"content"` +} + +func (w *Service) SendWeChatWorkMessage(textType TextType, uri, content string) error { + var message interface{} + if textType == weChatTextTypeText { + message = &Messsage{ + MsgType: msgType, + Text: &Text{ + Content: content, + }, + } + } else if textType == weChatTextTypeMarkdown { + message = &WeChatWorkCard{ + MsgType: msgType, + Markdown: Markdown{ + Content: content, + }, + } + } else { + return fmt.Errorf("SendWeChatWorkMessage err:%s", "WeChatWork textType is invalid") + } + + _, err := w.SendMessageRequest(uri, message) + return err +} \ No newline at end of file diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index 62df8121..dc51af1b 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -30,8 +30,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/instantmessage" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/wechat" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -45,22 +45,22 @@ type WorkflowTaskImage struct { } type client struct { - notifyColl *mongodb.NotifyColl - pipelineColl *mongodb.PipelineColl - subscriptionColl *mongodb.SubscriptionColl - taskColl *mongodb.TaskColl - scmNotifyService *scmnotify.Service - WeChatService *wechat.Service + notifyColl *mongodb.NotifyColl + pipelineColl *mongodb.PipelineColl + subscriptionColl *mongodb.SubscriptionColl + taskColl *mongodb.TaskColl + scmNotifyService *scmnotify.Service + InstantmessageService *instantmessage.Service } func NewNotifyClient() *client { return &client{ - notifyColl: mongodb.NewNotifyColl(), - pipelineColl: mongodb.NewPipelineColl(), - subscriptionColl: mongodb.NewSubscriptionColl(), - taskColl: mongodb.NewTaskColl(), - scmNotifyService: scmnotify.NewService(), - WeChatService: wechat.NewWeChatClient(), + notifyColl: mongodb.NewNotifyColl(), + pipelineColl: mongodb.NewPipelineColl(), + subscriptionColl: mongodb.NewSubscriptionColl(), + taskColl: mongodb.NewTaskColl(), + scmNotifyService: scmnotify.NewService(), + InstantmessageService: instantmessage.NewWeChatClient(), } } @@ -197,6 +197,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { receivers := []string{notify.Receiver} logger := log.SugaredLogger() task.Status = ctx.Status + testTaskStatusChanged := false if ctx.Type == config.SingleType { pipline, err := c.pipelineColl.Find(&mongodb.PipelineFindOption{Name: ctx.PipelineName}) if err != nil { @@ -223,12 +224,21 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } else if ctx.Type == config.TestType { logger.Infof("test get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookCommentForTest(task, logger) + if ctx.TaskID > 1 { + testPreTask, err := c.taskColl.Find(ctx.TaskID-1, ctx.PipelineName, ctx.Type) + if err != nil { + return fmt.Errorf("get test previous task #%d notify, status: %s", ctx.TaskID-1, ctx.Status) + } + if testPreTask.Status != task.Status { + testTaskStatusChanged = true + } + } } //发送微信通知 - err = c.WeChatService.SendWechatMessage(task) + err = c.InstantmessageService.SendInstantMessage(task, testTaskStatusChanged) if err != nil { - return fmt.Errorf("SendWechatMessage err : %v", err) + return fmt.Errorf("SendInstantMessage err : %v", err) } for _, receiver := range receivers { -- Gitee From 861f4630e5c047d19936184760ff16423d3ef7e5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 10 Jan 2022 20:26:00 +0800 Subject: [PATCH 1258/2647] GetCollaborationNew add workflow desc Signed-off-by: panxunying --- .../service/collaboration_instance.go | 21 +++++++++++++++++++ .../common/repository/mongodb/workflow.go | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 961e58bd..0ee5153a 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -309,6 +309,27 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL return nil, fmt.Errorf("product:%s not exist", product.BaseName) } } + var workNames []string + for _, workflow := range newWorkflow { + workNames = append(workNames, workflow.Name) + } + workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{ + Projects: []string{projectName}, + Names: workNames, + }) + if err != nil { + logger.Errorf("GetCollaborationNew list workflows:%v error:%s", workNames, err) + return nil, err + } + workflowDescMap := make(map[string]string) + for _, workflow := range workflows { + workflowDescMap[workflow.Name] = workflow.Description + } + for _, workflow := range newWorkflow { + if desc, ok := workflowDescMap[workflow.Name]; ok { + workflow.Description = desc + } + } return &GetCollaborationNewResp{ Code: 10000, Workflow: newWorkflow, diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index f874a6fa..a3b0b528 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -33,6 +33,7 @@ import ( type ListWorkflowOption struct { IsSort bool Projects []string + Names []string } type WorkflowColl struct { @@ -66,6 +67,9 @@ func (c *WorkflowColl) List(opt *ListWorkflowOption) ([]*models.Workflow, error) if len(opt.Projects) > 0 { query["product_tmpl_name"] = bson.M{"$in": opt.Projects} } + if len(opt.Names) > 0 { + query["name"] = bson.M{"$in": opt.Names} + } resp := make([]*models.Workflow, 0) ctx := context.Background() -- Gitee From 6b2bb2116b9c18f54628486bf1d8850f84461dbd Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 10 Jan 2022 21:07:11 +0800 Subject: [PATCH 1259/2647] fix nil pointer Signed-off-by: panxunying --- .../service/collaboration_instance.go | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 0ee5153a..0acead07 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -298,15 +298,17 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL if err != nil { return nil, err } - productVarsMap := make(map[string][]*templatemodels.RenderKV) - for _, set := range renderSets { - productVarsMap[set.EnvName] = set.KVs - } - for _, product := range newProduct { - if vars, ok := productVarsMap[product.BaseName]; ok { - product.Vars = vars - } else { - return nil, fmt.Errorf("product:%s not exist", product.BaseName) + if renderSets != nil { + productVarsMap := make(map[string][]*templatemodels.RenderKV) + for _, set := range renderSets { + productVarsMap[set.EnvName] = set.KVs + } + for _, product := range newProduct { + if vars, ok := productVarsMap[product.BaseName]; ok { + product.Vars = vars + } else { + return nil, fmt.Errorf("product:%s not exist", product.BaseName) + } } } var workNames []string -- Gitee From 3be3d6600bdc6a054dc3cf21b37eaa26ca34c2e8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 11 Jan 2022 14:42:47 +0800 Subject: [PATCH 1260/2647] fix the problem that the local cluster cannot connect to the minio across ns Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/s3/s3.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/s3/s3.go b/pkg/microservice/aslan/core/common/service/s3/s3.go index fc363b61..f7a71dfb 100644 --- a/pkg/microservice/aslan/core/common/service/s3/s3.go +++ b/pkg/microservice/aslan/core/common/service/s3/s3.go @@ -137,7 +137,7 @@ func FindDefaultS3() (*S3, error) { S3Storage: &models.S3Storage{ Ak: config.S3StorageAK(), Sk: config.S3StorageSK(), - Endpoint: config.S3StorageEndpoint(), + Endpoint: getEndpoint(), Bucket: config.S3StorageBucket(), Insecure: config.S3StorageProtocol() == "http", Provider: setting.ProviderSourceSystemDefault, @@ -148,6 +148,13 @@ func FindDefaultS3() (*S3, error) { return &S3{S3Storage: storage}, nil } +func getEndpoint() string { + const svc = "zadig-minio" + endpoint := config.S3StorageEndpoint() + newEndpoint := fmt.Sprintf("%s.%s%s", svc, config.Namespace(), strings.TrimPrefix(endpoint, svc)) + return newEndpoint +} + func FindS3ById(id string) (*S3, error) { storage, err := commonrepo.NewS3StorageColl().Find(id) if err != nil { @@ -163,7 +170,7 @@ func FindInternalS3() *S3 { storage := &models.S3Storage{ Ak: config.S3StorageAK(), Sk: config.S3StorageSK(), - Endpoint: config.S3StorageEndpoint(), + Endpoint: getEndpoint(), Bucket: config.S3StorageBucket(), Insecure: config.S3StorageProtocol() == "http", } -- Gitee From 1df8a6ee8be972a04eea230ff8c0a6459bbf368e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 11 Jan 2022 18:32:55 +0800 Subject: [PATCH 1261/2647] If the cluster has not changed, the last httpTransport is returned Signed-off-by: liu deyi --- pkg/tool/remotedialer/server.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/tool/remotedialer/server.go b/pkg/tool/remotedialer/server.go index 6473704c..8503de3c 100644 --- a/pkg/tool/remotedialer/server.go +++ b/pkg/tool/remotedialer/server.go @@ -129,10 +129,6 @@ func (r *Server) GetTransport(clusterCaCert string, clientKey string) (http.Roun r.Lock() defer r.Unlock() - if r.httpTransport != nil && r.caCert == clusterCaCert { - return r.httpTransport, nil - } - transport := &http.Transport{} if clusterCaCert != "" { certBytes, err := base64.StdEncoding.DecodeString(clusterCaCert) -- Gitee From 7e5c6d8723d7baf5b7a0f869e8ac1aa30b54bb45 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 11 Jan 2022 18:53:35 +0800 Subject: [PATCH 1262/2647] edit template Signed-off-by: ddh27 --- pkg/microservice/aslan/config/consts.go | 1 + .../common/service/instantmessage/lark.go | 27 +++-- .../common/service/instantmessage/service.go | 99 +++++++++++++------ .../service/instantmessage/wechatwork.go | 17 +++- .../core/common/service/notify/client.go | 2 +- 5 files changed, 104 insertions(+), 42 deletions(-) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 1b8aca66..16cf47d6 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -105,6 +105,7 @@ const ( StatusBlocked Status = "blocked" QueueItemPending Status = "pending" StatusChanged Status = "changed" + StatusNotRun Status = "notRun" ) type TaskStatus string diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go index 40f38d2f..2e14bbc6 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go @@ -2,17 +2,21 @@ package instantmessage import ( "strings" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" ) const ( - feiShuType = "feishu" - feishuCardType = "interactive" - feishuHeaderTemplate = "turquoise" - feiShuTagText = "plain_text" - feishuTagMd = "lark_md" - feishuTagAction = "action" - feishuTagDiv = "div" - feishuTagButton = "button" + feiShuType = "feishu" + feishuCardType = "interactive" + feishuHeaderTemplateTurquoise = "turquoise" + feishuHeaderTemplateGreen = "green" + feishuHeaderTemplateRed = "red" + feiShuTagText = "plain_text" + feishuTagMd = "lark_md" + feishuTagAction = "action" + feishuTagDiv = "div" + feishuTagButton = "button" ) type LarkCardReq struct { @@ -173,3 +177,10 @@ func (w *Service) sendFeishuMessageOfSingleType(title, uri, content string) erro _, err := w.SendMessageRequest(uri, message) return err } + +func getColorTemplateWithStatus(status config.Status) string { + if status == config.StatusPassed { + return feishuHeaderTemplateGreen + } + return feishuHeaderTemplateRed +} diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 78dc2fc8..03124341 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -171,7 +171,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } } } else if task.Type == config.TestType { - resp, err := w.testingColl.Find(task.PipelineName, task.ProductName) + resp, err := w.testingColl.Find(strings.TrimSuffix(task.PipelineName, "-job"), task.ProductName) if err != nil { log.Errorf("testing find err :%s", err) return err @@ -192,7 +192,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } else { uri = resp.NotifyCtl.WeChatWebHook } - title, content, err = w.createNotifyBodyOfTestIM(resp.Desc, &wechatNotification{ + title, content, buttonContent, buttonURL, err = w.createNotifyBodyOfTestIM(resp.Desc, &wechatNotification{ Task: task, BaseURI: configbase.SystemAddress(), IsSingle: false, @@ -227,9 +227,10 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } return nil } + lc := NewLarkCard() lc.SetConfig(true) - lc.SetHeader(feishuHeaderTemplate, title, feiShuTagText) + lc.SetHeader(getColorTemplateWithStatus(task.Status), title, feiShuTagText) lc.AddI18NElementsZhcnFeild(content) lc.AddI18NElementsZhcnAction(buttonContent, buttonURL) err := w.sendFeishuMessage(uri, lc) @@ -284,9 +285,9 @@ func (w *Service) createNotifyBody(weChatNotification *wechatNotification) (cont func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotification) (string, string, string, string, error) { - tplTitle := " ####
👍{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}
\n" + tplTitle := "####{{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}}\n" tplcontent := "**创建者**:{{.Task.TaskCreator}} **持续时间**:{{ .TotalTime}} 秒 \n" + - "**环境信息**:{{.Task.WorkflowArgs.EnvName}} **开始时间**:{{.Task.StartTime}} \n" + "**环境信息**:{{.Task.WorkflowArgs.Namespace}} **开始时间**:{{ getStartTime .Task.StartTime}} \n" build := "" deploy := "" test := "" @@ -306,13 +307,24 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific gitCommitURL := "" for idx, build := range buildSt.JobCtx.Builds { if idx == 0 || build.IsPrimary { - branch = "Branch-" + build.Branch - commitID = build.CommitID + branch = build.Branch + if len(build.CommitID) > 8 { + commitID = build.CommitID[0:8] + } commitMsg = build.CommitMessage gitCommitURL = fmt.Sprintf("%s/%s/%s/commit/%s", build.Address, build.RepoOwner, build.RepoName, commitID) } } - build += fmt.Sprintf("- %s/%s status:%s [%s-%s](%s) commmitMsg:%s\n", buildSt.ServiceName, buildSt.Service, buildSt.BuildStatus.Status, branch, commitID, gitCommitURL, commitMsg) + if buildSt.BuildStatus.Status == "" { + buildSt.BuildStatus.Status = config.StatusNotRun + } + if weChatNotification.WebHookType == weChatWorkType { + build += fmt.Sprintf("- %s/%s", getColorWithStatus(buildSt.BuildStatus.Status), buildSt.ServiceName, buildSt.Service) + } else { + build += fmt.Sprintf("- %s/%s", buildSt.ServiceName, buildSt.Service) + } + build += fmt.Sprintf("- {{if ne .WebHookType \"wechat\"}}%s/%s{{else}}%s/%s{{end}}", buildSt.ServiceName, buildSt.Service, buildSt.ServiceName, buildSt.Service) + build += fmt.Sprintf(" status:%s [%s-%s](%s) commitMsg:%s\n", buildSt.BuildStatus.Status, branch, commitID, gitCommitURL, commitMsg) } case config.TaskArtifact: @@ -323,7 +335,15 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if err != nil { return "", "", "", "", err } - deploy += fmt.Sprintf("- %s/%s status:%s image:%s\n", svrModule, deploySt.ServiceName, deploySt.TaskStatus, deploySt.Image) + if deploySt.TaskStatus == "" { + deploySt.TaskStatus = config.StatusNotRun + } + if weChatNotification.WebHookType == weChatWorkType { + deploy += fmt.Sprintf("- %s/%s", getColorWithStatus(deploySt.TaskStatus), svrModule, deploySt.ServiceName) + } else { + deploy += fmt.Sprintf("- %s/%s", svrModule, deploySt.ServiceName) + } + deploy += fmt.Sprintf(" status:%s image:%s\n", deploySt.TaskStatus, deploySt.Image) } case config.TaskTestingV2: test = "**测试**\n" @@ -332,7 +352,16 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if err != nil { return "", "", "", "", err } + if testSt.TaskStatus == "" { + testSt.TaskStatus = config.StatusNotRun + } + if weChatNotification.WebHookType == weChatWorkType { + test += fmt.Sprintf("- %s status:%s", getColorWithStatus(testSt.TaskStatus), testSt.TestName, testSt.TaskStatus) + } else { + test += fmt.Sprintf("- %s status:%s", testSt.TestName, testSt.TaskStatus) + } if weChatNotification.Task.TestReports == nil { + test += "\n" continue } for testname, report := range weChatNotification.Task.TestReports { @@ -344,11 +373,7 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific log.Errorf("parse TestReport failed, err:%s", err) continue } - url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s\n", testSt.TestName) - //status:="{{if eq .WebHookType \"feishu\"}}{{.Task.Status}}{{else}}{{.Task.Status}}{{end}} \n" - test += fmt.Sprintf("- [%s](%s) status:%s", testSt.TestName, url, testSt.TaskStatus) if tr.FunctionTestSuite == nil { - test += "\n" continue } test += fmt.Sprintf(":%d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) @@ -361,7 +386,14 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if err != nil { return "", "", "", "", err } - distribute += fmt.Sprintf("- %s status:%s", distributeSt.ServiceName, distributeSt.TaskStatus) + if distributeSt.TaskStatus == "" { + distributeSt.TaskStatus = config.StatusNotRun + } + if weChatNotification.WebHookType == weChatWorkType { + distribute += fmt.Sprintf("- %s status:%s", getColorWithStatus(distributeSt.TaskStatus), distributeSt.ServiceName, distributeSt.TaskStatus) + } else { + distribute += fmt.Sprintf("- %s status:%s", distributeSt.ServiceName, distributeSt.TaskStatus) + } } } } @@ -387,11 +419,11 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific return tplExecTitle, tplExecContent, execButtonContent, execButtonURL, nil } -func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wechatNotification) (string, string, error) { - tplTitle := " ####
👍{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}
\n" - tplcontent := "**创建者**:{{.Task.TaskCreator}} **持续时间**:{{ .TotalTime}} 秒 \n" + - "**测试描述**: \n " + desc + - "**测试结果** \n" +func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wechatNotification) (string, string, string, string, error) { + tplTitle := "####{{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}}\n" + tplcontent := "**创建者**:{{.Task.TaskCreator}} **开始时间**:{{ getStartTime .Task.StartTime}} **持续时间**:{{ .TotalTime}} 秒 \n" + + "**测试描述**: " + desc + "\n" + + "**测试结果**: " for _, stage := range weChatNotification.Task.Stages { if stage.TaskType != config.TaskTestingV2 { continue @@ -405,9 +437,9 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech } if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestReportPath != "" { url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s\n", testInfo.TestName) - //status:="{{if eq .WebHookType \"feishu\"}}{{.Task.Status}}{{else}}{{.Task.Status}}{{end}} \n" tplcontent += fmt.Sprintf("- [%s](%s) status:%s", testName, url, testInfo.TaskStatus) if weChatNotification.Task.TestReports == nil { + tplcontent += "\n" continue } for testname, report := range weChatNotification.Task.TestReports { @@ -420,7 +452,6 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech continue } if tr.FunctionTestSuite == nil { - tplcontent += "\n" continue } tplcontent += fmt.Sprintf(":%d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) @@ -434,14 +465,17 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech tplcontent = fmt.Sprintf("%s - 相关人员:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) } } - - workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/pipelines/{{ isSingle .IsSingle }}/{{.Task.PipelineName}}/{{.Task.TaskID}}" - moreInformation := fmt.Sprintf("[点击查看更多信息](%s)", workflowDetailURL) - tplcontent += moreInformation - - tplExecContent, err := getTplExec(tplcontent, weChatNotification) - tplExecTitle, err := getTplExec(tplTitle, weChatNotification) - return tplExecTitle, tplExecContent, err + buttonContent := "点击查看更多信息" + workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/test/detail/function/{{.Task.PipelineName}}/{{.Task.TaskID}}" + moreInformation := fmt.Sprintf("[%s](%s)", buttonContent, workflowDetailURL) + if weChatNotification.WebHookType != feiShuType { + tplcontent += moreInformation + } else { + workflowDetailURL, _ = getTplExec(workflowDetailURL, weChatNotification) + } + tplExecContent, _ := getTplExec(tplcontent, weChatNotification) + tplExecTitle, _ := getTplExec(tplTitle, weChatNotification) + return tplExecTitle, tplExecContent, buttonContent, workflowDetailURL,nil } func getHTMLTestReport(task *task.Task) []string { @@ -491,9 +525,12 @@ func getTplExec(tplcontent string, weChatNotification *wechatNotification) (stri }, "taskStatus": func(status config.Status) string { if status == config.StatusPassed { - return "执行成功" + return "执行成功👍" } - return "执行失败" + return "执行失败⚠️" + }, + "getStartTime": func(startTime int64) string { + return time.Unix(startTime, 0).Format("2006-01-02 15:04:05") }, }).Parse(tplcontent)) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go index fbcb61b4..e9ce1152 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go @@ -2,9 +2,12 @@ package instantmessage import ( "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" ) const ( + weChatWorkType = "wechat" markdownColorInfo = "info" markdownColorComment = "comment" markdownColorWarning = "warning" @@ -20,7 +23,6 @@ type Markdown struct { Content string `json:"content"` } -//wechat type Messsage struct { MsgType string `json:"msgtype"` Text *Text `json:"markdown"` @@ -52,4 +54,15 @@ func (w *Service) SendWeChatWorkMessage(textType TextType, uri, content string) _, err := w.SendMessageRequest(uri, message) return err -} \ No newline at end of file +} + +func getColorWithStatus(status config.Status) string { + if status == config.StatusPassed { + return markdownColorInfo + } else if status == config.StatusTimeout || status == config.StatusCancelled || status == config.StatusNotRun { + return markdownColorComment + } else if status == config.StatusFailed { + return markdownColorWarning + } + return markdownColorComment +} diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index dc51af1b..7f894c27 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -235,7 +235,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } } - //发送微信通知 + //发送IM通知 err = c.InstantmessageService.SendInstantMessage(task, testTaskStatusChanged) if err != nil { return fmt.Errorf("SendInstantMessage err : %v", err) -- Gitee From bb575025080a6ff853319a76f1f73251e75f496c Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 11 Jan 2022 22:03:15 +0800 Subject: [PATCH 1263/2647] edit template Signed-off-by: ddh27 --- .../common/service/instantmessage/service.go | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 03124341..7c9a5302 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -285,7 +285,7 @@ func (w *Service) createNotifyBody(weChatNotification *wechatNotification) (cont func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotification) (string, string, string, string, error) { - tplTitle := "####{{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}}\n" + tplTitle := "#### {{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}} \n" tplcontent := "**创建者**:{{.Task.TaskCreator}} **持续时间**:{{ .TotalTime}} 秒 \n" + "**环境信息**:{{.Task.WorkflowArgs.Namespace}} **开始时间**:{{ getStartTime .Task.StartTime}} \n" build := "" @@ -295,7 +295,7 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific for _, subStage := range weChatNotification.Task.Stages { switch subStage.TaskType { case config.TaskBuild: - build = "**构建**:\n" + build = "**构建** \n" for _, sb := range subStage.SubTasks { buildSt, err := base.ToBuildTask(sb) if err != nil { @@ -323,13 +323,12 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific } else { build += fmt.Sprintf("- %s/%s", buildSt.ServiceName, buildSt.Service) } - build += fmt.Sprintf("- {{if ne .WebHookType \"wechat\"}}%s/%s{{else}}%s/%s{{end}}", buildSt.ServiceName, buildSt.Service, buildSt.ServiceName, buildSt.Service) - build += fmt.Sprintf(" status:%s [%s-%s](%s) commitMsg:%s\n", buildSt.BuildStatus.Status, branch, commitID, gitCommitURL, commitMsg) + build += fmt.Sprintf(" status:%s [%s-%s](%s) commitMsg:%s \n", buildSt.BuildStatus.Status, branch, commitID, gitCommitURL, commitMsg) } case config.TaskArtifact: case config.TaskDeploy: - deploy = "**部署**\n" + deploy = "**部署** \n" for svrModule, sb := range subStage.SubTasks { deploySt, err := base.ToDeployTask(sb) if err != nil { @@ -343,10 +342,10 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific } else { deploy += fmt.Sprintf("- %s/%s", svrModule, deploySt.ServiceName) } - deploy += fmt.Sprintf(" status:%s image:%s\n", deploySt.TaskStatus, deploySt.Image) + deploy += fmt.Sprintf(" status:%s image:%s \n", deploySt.TaskStatus, deploySt.Image) } case config.TaskTestingV2: - test = "**测试**\n" + test = "**测试** \n" for _, sb := range subStage.SubTasks { testSt, err := base.ToTestingTask(sb) if err != nil { @@ -361,9 +360,10 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific test += fmt.Sprintf("- %s status:%s", testSt.TestName, testSt.TaskStatus) } if weChatNotification.Task.TestReports == nil { - test += "\n" + test += " \n" continue } + suffixEndline := false for testname, report := range weChatNotification.Task.TestReports { if testname != testSt.TestName { continue @@ -376,11 +376,15 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if tr.FunctionTestSuite == nil { continue } - test += fmt.Sprintf(":%d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + suffixEndline = true + test += fmt.Sprintf(":%d(成功)%d(失败)%d(总数) \n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + } + if !suffixEndline { + test += " \n" } } case config.TaskDistribute, config.TaskDistributeToS3: - build = "**分发**:\n" + build = "**分发** \n" for _, sb := range subStage.SubTasks { distributeSt, err := base.ToDistributeToS3Task(sb) if err != nil { @@ -390,9 +394,9 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific distributeSt.TaskStatus = config.StatusNotRun } if weChatNotification.WebHookType == weChatWorkType { - distribute += fmt.Sprintf("- %s status:%s", getColorWithStatus(distributeSt.TaskStatus), distributeSt.ServiceName, distributeSt.TaskStatus) + distribute += fmt.Sprintf("- %s status:%s \n", getColorWithStatus(distributeSt.TaskStatus), distributeSt.ServiceName, distributeSt.TaskStatus) } else { - distribute += fmt.Sprintf("- %s status:%s", distributeSt.ServiceName, distributeSt.TaskStatus) + distribute += fmt.Sprintf("- %s status:%s \n", distributeSt.ServiceName, distributeSt.TaskStatus) } } } @@ -420,10 +424,10 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific } func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wechatNotification) (string, string, string, string, error) { - tplTitle := "####{{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}}\n" + tplTitle := "#### {{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}} \n" tplcontent := "**创建者**:{{.Task.TaskCreator}} **开始时间**:{{ getStartTime .Task.StartTime}} **持续时间**:{{ .TotalTime}} 秒 \n" + - "**测试描述**: " + desc + "\n" + - "**测试结果**: " + "**测试描述**: " + desc + " \n" + + "**测试结果** \n" for _, stage := range weChatNotification.Task.Stages { if stage.TaskType != config.TaskTestingV2 { continue @@ -437,11 +441,12 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech } if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestReportPath != "" { url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s\n", testInfo.TestName) - tplcontent += fmt.Sprintf("- [%s](%s) status:%s", testName, url, testInfo.TaskStatus) + tplcontent += fmt.Sprintf("- [%s](%s) status:%s ", testName, url, testInfo.TaskStatus) if weChatNotification.Task.TestReports == nil { tplcontent += "\n" continue } + suffixEndline := false for testname, report := range weChatNotification.Task.TestReports { if testname != testInfo.TestName { continue @@ -454,7 +459,11 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech if tr.FunctionTestSuite == nil { continue } - tplcontent += fmt.Sprintf(":%d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + suffixEndline = true + tplcontent += fmt.Sprintf(" %d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + } + if !suffixEndline { + tplcontent += "\n" } } } @@ -469,13 +478,14 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/test/detail/function/{{.Task.PipelineName}}/{{.Task.TaskID}}" moreInformation := fmt.Sprintf("[%s](%s)", buttonContent, workflowDetailURL) if weChatNotification.WebHookType != feiShuType { - tplcontent += moreInformation + tplcontent = fmt.Sprintf("%s%s%s", tplTitle, tplcontent, moreInformation) } else { + tplTitle, _ = getTplExec(tplTitle, weChatNotification) workflowDetailURL, _ = getTplExec(workflowDetailURL, weChatNotification) } tplExecContent, _ := getTplExec(tplcontent, weChatNotification) - tplExecTitle, _ := getTplExec(tplTitle, weChatNotification) - return tplExecTitle, tplExecContent, buttonContent, workflowDetailURL,nil + + return tplTitle, tplExecContent, buttonContent, workflowDetailURL, nil } func getHTMLTestReport(task *task.Task) []string { -- Gitee From 6773ab54b7ac8a4f0e78e0b4e8e3510562d24180 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 12 Jan 2022 09:51:40 +0800 Subject: [PATCH 1264/2647] optimize code Signed-off-by: allenshen --- pkg/util/yaml/path_search.go | 353 +++++++++++++++++++++----- pkg/util/yaml/path_search_lcp.go | 394 ------------------------------ pkg/util/yaml/path_search_test.go | 41 ++-- 3 files changed, 320 insertions(+), 468 deletions(-) delete mode 100644 pkg/util/yaml/path_search_lcp.go diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index 480c4d76..855f248b 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -19,7 +19,14 @@ package yaml import ( "errors" "fmt" + "math" "strings" + + "k8s.io/utils/strings/slices" +) + +const ( + separator = "." ) func getKey(prefix, k string) string { @@ -59,7 +66,238 @@ type pathSearcher struct { resultSet map[string]*pathSearchRuntime //prefix => search result } -func (isr *pathSearcher) handleKV(k string) { +type patternDataMap map[string]map[string]*patternData + +// check if contains empty patterns +func preFilterSearchPaths(configs []map[string]string) error { + for _, singleConfig := range configs { + for k, v := range singleConfig { + if k == "" || v == "" { + return errors.New("empty pattern name or pattern path is not supported") + } + } + } + return nil +} + +func (pdm patternDataMap) addPatternMap(name, rPath, aPath string) *patternData { + pData := &patternData{ + name: name, + relativePath: rPath, + absolutePath: aPath, + } + if _, ok := pdm[name]; !ok { + pdm[name] = make(map[string]*patternData) + } + pdm[name][aPath] = pData + return pData +} + +func (pdm patternDataMap) getAPathByName(name string) []string { + ret := make([]string, 0) + if pData, ok := pdm[name]; ok { + for _, data := range pData { + ret = append(ret, data.absolutePath) + } + } + return ret +} + +type patternData struct { + name string + relativePath string + absolutePath string + usedPrefix string +} + +type pathResult struct { + name string + absolutePath string +} + +type pathResultSet struct { + resultSet []*pathResult + prefix string +} + +// paths share the same longest prefix +type relativePathSet struct { + paths []string +} + +type relativePathChecker struct { + relativePaths map[string]*relativePathSet +} + +func strSliceContainsAnother(source, target []string) bool { + for _, str := range target { + if !slices.Contains(source, str) { + return false + } + } + return true +} + +// check if the input path set contains relative paths +func (checker *relativePathChecker) checkMatchRelativePath(paths []string) bool { + for _, path := range paths { + if relativePath, ok := checker.relativePaths[path]; ok { + if strSliceContainsAnother(paths, relativePath.paths) { + return true + } else { + return false + } + } + } + return true +} + +func newRelativeChecker(rPaths [][]string) *relativePathChecker { + ret := &relativePathChecker{relativePaths: map[string]*relativePathSet{}} + for _, pathSet := range rPaths { + relativeData := &relativePathSet{ + paths: pathSet, + } + for _, path := range pathSet { + ret.relativePaths[path] = relativeData + } + } + return ret +} + +func longestCommonPrefix(strs []string) string { + n := len(strs) + // 输入为空直接返回空 + if n == 0 { + return "" + // 只输入一个字符串那么它自己就是最长公共后缀 + } else if n == 1 { + return strs[0] + // 如果基准字符串是空,那么直接返回空 + } else if len(strs[0]) == 0 { + return "" + } + // 最长公共前缀再长也不可能比最短的输入字符串还要长对吧。输入源越多,基准字符串不是最短字符串的可能性越大。 + // 因此进行初步优化,求输入源中最短的那个字符串长度,然后把基准字符串按该长度重新截取 + minStrLen := math.MaxInt32 + for i := 0; i < n; i++ { + if minStrLen > len(strs[i]) { + minStrLen = len(strs[i]) + } + } + prefix := strs[0][0:minStrLen] + // 标志位,如果为true说明在其它所有字符串中都找到了相同的前缀 + var allFound bool + for { + allFound = true + // 循环其它字符串 + for i := 1; i < n; i++ { + // 查询是否包含基准字符串,因为求的是前缀,所以index值为0 + if strings.Index(strs[i], prefix) != 0 { + // 不包含的话削掉基准字符串的尾端以便开展下次查询工作 + prefix = prefix[0 : len(prefix)-1] + // 标志位置为false说明查询失败 + allFound = false + // 跳出吧,也没必要再查其它字符串了 + break + } + } + // 如果查询成功,跳出大循环,直接返回当前基准字符串就行了。所以该算法最好的情况就是第一次查询就成功。 + // 如果基准字符串都被砍光了,说明没有最长公共后缀,所以该算法的最差的情况就是最后一个字符串和基准字符串完全没有公共前缀,而中间的字符串都能找到公共前缀,这会导致循环minStrLen * (n - 1)次。 + if allFound || len(prefix) == 0 { + break + } + } + return prefix +} + +func findCombinations(arr [][]string, checker *relativePathChecker) [][]string { + ret := make([][]string, 0) + n := len(arr) + indices := make([]int, n, n) + for { + singleSlice := make([]string, 0) + for i := 0; i < n; i++ { + singleSlice = append(singleSlice, arr[i][indices[i]]) + } + if len(singleSlice) > 0 { + if checker.checkMatchRelativePath(singleSlice) { + ret = append(ret, singleSlice) + } + } + + // find the rightmost array that has more + // elements left after the current element + // in that array + next := n - 1 + for { + if next >= 0 && (indices[next]+1 >= len(arr[next])) { + next-- + } else { + break + } + } + if next < 0 { + break + } + // if found move to next element in that + // array + indices[next] = indices[next] + 1 + + // for all arrays to the right of this + // array current index again points to + // first element + for i := next + 1; i < n; i++ { + indices[i] = 0 + } + } + + return ret +} + +func toPathResult(prefix string, path []string, aPatternData map[string]*patternData) *pathResultSet { + ret := &pathResultSet{ + prefix: prefix, + } + for _, aPath := range path { + pData, _ := aPatternData[aPath] + if len(pData.usedPrefix) <= len(prefix) { + pData.usedPrefix = prefix + } + ret.resultSet = append(ret.resultSet, &pathResult{ + name: pData.name, + absolutePath: aPath, + }) + } + return ret +} + +func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData) []map[string]string { + ret := make([]map[string]string, 0) + for _, singleSet := range resultSets { + retMap := make(map[string]string) + + useLongestPrefix := true + for _, result := range singleSet.resultSet { + pData := aPatternData[result.absolutePath] + if len(pData.usedPrefix) != len(singleSet.prefix) { + useLongestPrefix = false + break + } + } + if !useLongestPrefix { + continue + } + + for _, result := range singleSet.resultSet { + retMap[result.name] = result.absolutePath + } + ret = append(ret, retMap) + } + return ret +} + +func (isr *pathSearcher) checkRelativePath(k string) { expectPaths, preMatch := isr.pattern, false for _, ep := range expectPaths { if strings.HasSuffix(k, ep) { @@ -126,69 +364,66 @@ func (isr *pathSearcher) handleKV(k string) { } } -// check if contains empty patterns -func preFilterSearchPaths(configs []map[string]string) error { - for _, singleConfig := range configs { - for k, v := range singleConfig { - if k == "" || v == "" { - return errors.New("empty pattern name or pattern path is not supported") - } +func (isr *pathSearcher) getRelativePath() [][]string { + ret := make([][]string, 0) + for _, fSet := range isr.resultSet { + // multi keys share the same prefix + if len(fSet.foundSet) <= 1 { + continue + } + set := make([]string, 0) + for _, searchInfo := range fSet.foundSet { + set = append(set, searchInfo.absolutePath) } + ret = append(ret, set) } - return nil + return ret } -// build pathSearcher object for every pattern -func searchPaths(patterns []map[string]string, sourceMap map[string]interface{}) ([]*pathSearcher, error) { - if len(patterns) == 0 { - return nil, nil - } - err := preFilterSearchPaths(patterns) - if err != nil { - return nil, err +func searchByLCP(flatMap map[string]interface{}, pattern map[string]string) ([]map[string]string, error) { + + patternDataMap := patternDataMap{} + allPatternData := make(map[string]*patternData) // absolutePath => patternData + relativeSearcher := &pathSearcher{ + pattern: pattern, + resultSet: make(map[string]*pathSearchRuntime), } - rtSlice := make([]*pathSearcher, 0) - for _, cfg := range patterns { - rt := &pathSearcher{ - pattern: cfg, - resultSet: make(map[string]*pathSearchRuntime), + for absolutePath := range flatMap { + for patternName, relativePath := range pattern { + if relativePath != absolutePath && !strings.HasSuffix(absolutePath, fmt.Sprintf(".%s", relativePath)) { + continue + } + pData := patternDataMap.addPatternMap(patternName, relativePath, absolutePath) + allPatternData[absolutePath] = pData } - rtSlice = append(rtSlice, rt) + relativeSearcher.checkRelativePath(absolutePath) } - for k := range sourceMap { - for _, rt := range rtSlice { - rt.handleKV(k) + + relativePaths := relativeSearcher.getRelativePath() + fmt.Printf("the relative paths is %v \n", relativePaths) + checker := newRelativeChecker(relativePaths) + + allKeys := make([][]string, 0) + for name, _ := range patternDataMap { + pathList := patternDataMap.getAPathByName(name) + if len(pathList) == 0 { + return nil, nil } + allKeys = append(allKeys, pathList) } - return rtSlice, nil -} -// merge results and filter duplicates paths -func mergeResults(isrList []*pathSearcher) []map[string]string { - ret := make([]map[string]string, 0) - usedPath := make(map[string]int) - for _, isr := range isrList { - for _, fSet := range isr.resultSet { - retSet := make(map[string]string) - if !fSet.checkAllFinish() { - continue - } - duplicateCheckPass := true - for _, searchInfo := range fSet.foundSet { - if _, ok := usedPath[searchInfo.absolutePath]; ok { - duplicateCheckPass = false - break - } - retSet[searchInfo.pathName] = searchInfo.absolutePath - usedPath[searchInfo.absolutePath] = 1 - } - if duplicateCheckPass { - ret = append(ret, retSet) - } - } + allCombinations := findCombinations(allKeys, checker) + + results := make([]*pathResultSet, 0) + + for _, cb := range allCombinations { + lp := longestCommonPrefix(cb) + result := toPathResult(lp, cb, allPatternData) + results = append(results, result) } - return ret + + return processResults(results, allPatternData), nil } // SearchByPattern find all matched absolute paths from yaml by the pattern appointed @@ -210,11 +445,19 @@ func mergeResults(isrList []*pathSearcher) []map[string]string { // ----------- yaml end // pattern: []{"image": "repository", "tag": "tag"} // output: []{{"image": "svc1.image.repository", "tag": "svc1.image.tag"}, {"image": "svc2.image.repository", "tag": "svc2.image.tag"}} -func SearchByPattern(flatMap map[string]interface{}, pattern []map[string]string) ([]map[string]string, error) { - foundResult, err := searchPaths(pattern, flatMap) +func SearchByPattern(flatMap map[string]interface{}, patterns []map[string]string) ([]map[string]string, error) { + err := preFilterSearchPaths(patterns) if err != nil { return nil, err } - ret := mergeResults(foundResult) + + ret := make([]map[string]string, 0) + for _, singlePattern := range patterns { + pRet, err := searchByLCP(flatMap, singlePattern) + if err != nil { + return nil, err + } + ret = append(ret, pRet...) + } return ret, nil } diff --git a/pkg/util/yaml/path_search_lcp.go b/pkg/util/yaml/path_search_lcp.go deleted file mode 100644 index 0417bb8d..00000000 --- a/pkg/util/yaml/path_search_lcp.go +++ /dev/null @@ -1,394 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package yaml - -import ( - "fmt" - "math" - "strings" - - "k8s.io/utils/strings/slices" -) - -const ( - separator = "." -) - -type patternDataMap map[string]map[string]*patternData - -func (pdm patternDataMap) addPatternMap(name, rPath, aPath string) *patternData { - pData := &patternData{ - name: name, - relativePath: rPath, - absolutePath: aPath, - } - if _, ok := pdm[name]; !ok { - pdm[name] = make(map[string]*patternData) - } - pdm[name][aPath] = pData - return pData -} - -func (pdm patternDataMap) getAPathByName(name string) []string { - ret := make([]string, 0) - if pData, ok := pdm[name]; ok { - for _, data := range pData { - ret = append(ret, data.absolutePath) - } - } - return ret -} - -type patternData struct { - name string - relativePath string - absolutePath string - usedPrefix string -} - -type pathResult struct { - name string - absolutePath string -} - -type pathResultSet struct { - resultSet []*pathResult - prefix string -} - -// paths share the same longest prefix -type relativePathSet struct { - paths []string -} - -type relativePathChecker struct { - relativePaths map[string]*relativePathSet -} - -func strSliceContainsAnother(source, target []string) bool { - for _, str := range target { - if !slices.Contains(source, str) { - return false - } - } - return true -} - -// check if the input path set contains relative paths -func (checker *relativePathChecker) checkMatchRelativePath(paths []string) bool { - for _, path := range paths { - if relativePath, ok := checker.relativePaths[path]; ok { - if strSliceContainsAnother(paths, relativePath.paths) { - return true - } else { - return false - } - } - } - return true -} - -func newRelativeChecker(rPaths [][]string) *relativePathChecker { - ret := &relativePathChecker{relativePaths: map[string]*relativePathSet{}} - for _, pathSet := range rPaths { - relativeData := &relativePathSet{ - paths: pathSet, - } - for _, path := range pathSet { - ret.relativePaths[path] = relativeData - } - } - return ret -} - -func longestCommonPrefix(strs []string) string { - n := len(strs) - // 输入为空直接返回空 - if n == 0 { - return "" - // 只输入一个字符串那么它自己就是最长公共后缀 - } else if n == 1 { - return strs[0] - // 如果基准字符串是空,那么直接返回空 - } else if len(strs[0]) == 0 { - return "" - } - // 最长公共前缀再长也不可能比最短的输入字符串还要长对吧。输入源越多,基准字符串不是最短字符串的可能性越大。 - // 因此进行初步优化,求输入源中最短的那个字符串长度,然后把基准字符串按该长度重新截取 - minStrLen := math.MaxInt32 - for i := 0; i < n; i++ { - if minStrLen > len(strs[i]) { - minStrLen = len(strs[i]) - } - } - prefix := strs[0][0:minStrLen] - // 标志位,如果为true说明在其它所有字符串中都找到了相同的前缀 - var allFound bool - for { - allFound = true - // 循环其它字符串 - for i := 1; i < n; i++ { - // 查询是否包含基准字符串,因为求的是前缀,所以index值为0 - if strings.Index(strs[i], prefix) != 0 { - // 不包含的话削掉基准字符串的尾端以便开展下次查询工作 - prefix = prefix[0 : len(prefix)-1] - // 标志位置为false说明查询失败 - allFound = false - // 跳出吧,也没必要再查其它字符串了 - break - } - } - // 如果查询成功,跳出大循环,直接返回当前基准字符串就行了。所以该算法最好的情况就是第一次查询就成功。 - // 如果基准字符串都被砍光了,说明没有最长公共后缀,所以该算法的最差的情况就是最后一个字符串和基准字符串完全没有公共前缀,而中间的字符串都能找到公共前缀,这会导致循环minStrLen * (n - 1)次。 - if allFound || len(prefix) == 0 { - break - } - } - return prefix -} - -func findCombinations(arr [][]string, checker *relativePathChecker) [][]string { - ret := make([][]string, 0) - n := len(arr) - indices := make([]int, n, n) - for { - singleSlice := make([]string, 0) - for i := 0; i < n; i++ { - singleSlice = append(singleSlice, arr[i][indices[i]]) - } - if len(singleSlice) > 0 { - if checker.checkMatchRelativePath(singleSlice) { - ret = append(ret, singleSlice) - } - } - - // find the rightmost array that has more - // elements left after the current element - // in that array - next := n - 1 - for { - if next >= 0 && (indices[next]+1 >= len(arr[next])) { - next-- - } else { - break - } - } - if next < 0 { - break - } - // if found move to next element in that - // array - indices[next] = indices[next] + 1 - - // for all arrays to the right of this - // array current index again points to - // first element - for i := next + 1; i < n; i++ { - indices[i] = 0 - } - } - - return ret -} - -func toPathResult(prefix string, path []string, aPatternData map[string]*patternData) *pathResultSet { - ret := &pathResultSet{ - prefix: prefix, - } - for _, aPath := range path { - pData, _ := aPatternData[aPath] - if len(pData.usedPrefix) <= len(prefix) { - pData.usedPrefix = prefix - } - ret.resultSet = append(ret.resultSet, &pathResult{ - name: pData.name, - absolutePath: aPath, - }) - } - return ret -} - -func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData) []map[string]string { - ret := make([]map[string]string, 0) - for _, singleSet := range resultSets { - retMap := make(map[string]string) - - useLongestPrefix := true - for _, result := range singleSet.resultSet { - pData := aPatternData[result.absolutePath] - if len(pData.usedPrefix) != len(singleSet.prefix) { - useLongestPrefix = false - break - } - } - if !useLongestPrefix { - continue - } - - for _, result := range singleSet.resultSet { - retMap[result.name] = result.absolutePath - } - ret = append(ret, retMap) - } - return ret -} - -func (isr *pathSearcher) checkRelativePath(k string) { - expectPaths, preMatch := isr.pattern, false - for _, ep := range expectPaths { - if strings.HasSuffix(k, ep) { - preMatch = true - break - } - } - if !preMatch { - return - } - - //优先匹配当前已经match了一部分path的情形 - for prefix, fs := range isr.resultSet { - if fs.checkAllFinish() { - continue - } - if !strings.HasPrefix(k, prefix) { - continue - } - match := false - for pathName, ep := range expectPaths { - //已经找到了相关的实际path pass - if _, ok := fs.foundSet[ep]; ok { - continue - } else { - if !strings.HasSuffix(k, ep) { - continue - } - if getKey(prefix, ep) != k { - break - } - fs.foundSet[ep] = &singlePathSearchInfo{ - pathName: pathName, - relativePath: ep, - absolutePath: k, - } - match = true - } - } - if match { - return - } - } - - for pathName, ep := range expectPaths { - if !strings.HasSuffix(k, ep) { - continue - } - prefix := strings.TrimSuffix(k, ep) - prefix = strings.Trim(prefix, ".") - fs := &pathSearchRuntime{ - pattern: expectPaths, - prefix: prefix, - foundSet: map[string]*singlePathSearchInfo{ - ep: &singlePathSearchInfo{ - pathName: pathName, - relativePath: ep, - absolutePath: k, - }, - }, - } - isr.resultSet[prefix] = fs - return - } -} - -func (isr *pathSearcher) getRelativePath() [][]string { - ret := make([][]string, 0) - for _, fSet := range isr.resultSet { - // multi keys share the same prefix - if len(fSet.foundSet) <= 1 { - continue - } - set := make([]string, 0) - for _, searchInfo := range fSet.foundSet { - set = append(set, searchInfo.absolutePath) - } - ret = append(ret, set) - } - return ret -} - -func searchByLCP(flatMap map[string]interface{}, pattern map[string]string) ([]map[string]string, error) { - - patternDataMap := patternDataMap{} - allPatternData := make(map[string]*patternData) // absolutePath => patternData - relativeSearcher := &pathSearcher{ - pattern: pattern, - resultSet: make(map[string]*pathSearchRuntime), - } - - for absolutePath := range flatMap { - for patternName, relativePath := range pattern { - if relativePath != absolutePath && !strings.HasSuffix(absolutePath, fmt.Sprintf(".%s", relativePath)) { - continue - } - pData := patternDataMap.addPatternMap(patternName, relativePath, absolutePath) - allPatternData[absolutePath] = pData - } - relativeSearcher.checkRelativePath(absolutePath) - } - - relativePaths := relativeSearcher.getRelativePath() - fmt.Printf("the relative paths is %v \n", relativePaths) - checker := newRelativeChecker(relativePaths) - - allKeys := make([][]string, 0) - for name, _ := range patternDataMap { - pathList := patternDataMap.getAPathByName(name) - if len(pathList) == 0 { - return nil, nil - } - allKeys = append(allKeys, pathList) - } - - allCombinations := findCombinations(allKeys, checker) - - results := make([]*pathResultSet, 0) - - for _, cb := range allCombinations { - lp := longestCommonPrefix(cb) - result := toPathResult(lp, cb, allPatternData) - results = append(results, result) - } - - return processResults(results, allPatternData), nil -} - -func SearchByPatternLCP(flatMap map[string]interface{}, patterns []map[string]string) ([]map[string]string, error) { - err := preFilterSearchPaths(patterns) - if err != nil { - return nil, err - } - - ret := make([]map[string]string, 0) - for _, singlePattern := range patterns { - pRet, err := searchByLCP(flatMap, singlePattern) - if err != nil { - return nil, err - } - ret = append(ret, pRet...) - } - return ret, nil -} diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 2310e0b0..2dfc3e38 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -102,22 +102,29 @@ service: ` var err error -var matedPaths []map[string]string -var lcpErr error var lcpMatedPaths []map[string]string var _ = Describe("Testing search", func() { Context("search matched paths from yaml", func() { - It("single match", func() { + It("single absolute match", func() { pattern := []map[string]string{ {"repo": "repo", "tag": "tag", "image": "name"}, } flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) - matedPaths, err = SearchByPattern(flatMap, pattern) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "image.name", "repo": "image.repo", "tag": "image.tag"}, + })) + }) + + It("single relative match", func() { + pattern := []map[string]string{ + {"repo": "image.repo", "tag": "image.tag", "image": "image.name"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - Expect(lcpErr).NotTo(HaveOccurred()) - Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ {"image": "image.name", "repo": "image.repo", "tag": "image.tag"}, })) @@ -129,9 +136,8 @@ var _ = Describe("Testing search", func() { } flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) - lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - Expect(lcpErr).NotTo(HaveOccurred()) - //Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ {"image": "svc1.image.name", "repo": "svc1.image.repo", "tag": "svc1.image.tag"}, @@ -139,17 +145,15 @@ var _ = Describe("Testing search", func() { })) }) - It("multiple match pattern 2", func() { + It("complex multiple match", func() { pattern := []map[string]string{ {"repo": "repo", "tag": "tag", "image": "name"}, } flatMap, err := converter.YamlToFlatMap([]byte(testYaml3)) - //matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - Expect(lcpErr).NotTo(HaveOccurred()) - //Expect(lcpMatedPaths).To(ConsistOf(matedPaths)) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ {"image": "svc1.image.name", "repo": "svc1.image.repo", "tag": "svc1.image.tag"}, @@ -157,16 +161,15 @@ var _ = Describe("Testing search", func() { })) }) - It("multiple match pattern 3", func() { + It("complex match with pattern component reuse", func() { pattern := []map[string]string{ {"image": "image", "tag": "tag", "repo": "global.hub"}, } flatMap, err := converter.YamlToFlatMap([]byte(testYaml8)) - matedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) - lcpMatedPaths, lcpErr = SearchByPatternLCP(flatMap, pattern) - Expect(lcpErr).NotTo(HaveOccurred()) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ {"image": "testSpec.image.image", "repo": "repoData1.global.hub", "tag": "testSpec.image.tag"}, -- Gitee From ccacf87996aea9b490badfe1fcd1ec47fa7a8265 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 12 Jan 2022 10:06:21 +0800 Subject: [PATCH 1265/2647] optimize code Signed-off-by: allenshen --- pkg/util/yaml/path_search.go | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index 855f248b..b88f911a 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -165,45 +165,31 @@ func newRelativeChecker(rPaths [][]string) *relativePathChecker { return ret } -func longestCommonPrefix(strs []string) string { - n := len(strs) - // 输入为空直接返回空 +// find the longest common prefix from string list +func longestCommonPrefix(strList []string) string { + n := len(strList) if n == 0 { return "" - // 只输入一个字符串那么它自己就是最长公共后缀 } else if n == 1 { - return strs[0] - // 如果基准字符串是空,那么直接返回空 - } else if len(strs[0]) == 0 { - return "" + return strList[0] } - // 最长公共前缀再长也不可能比最短的输入字符串还要长对吧。输入源越多,基准字符串不是最短字符串的可能性越大。 - // 因此进行初步优化,求输入源中最短的那个字符串长度,然后把基准字符串按该长度重新截取 minStrLen := math.MaxInt32 for i := 0; i < n; i++ { - if minStrLen > len(strs[i]) { - minStrLen = len(strs[i]) + if minStrLen > len(strList[i]) { + minStrLen = len(strList[i]) } } - prefix := strs[0][0:minStrLen] - // 标志位,如果为true说明在其它所有字符串中都找到了相同的前缀 + prefix := strList[0][0:minStrLen] var allFound bool for { allFound = true - // 循环其它字符串 for i := 1; i < n; i++ { - // 查询是否包含基准字符串,因为求的是前缀,所以index值为0 - if strings.Index(strs[i], prefix) != 0 { - // 不包含的话削掉基准字符串的尾端以便开展下次查询工作 + if strings.Index(strList[i], prefix) != 0 { prefix = prefix[0 : len(prefix)-1] - // 标志位置为false说明查询失败 allFound = false - // 跳出吧,也没必要再查其它字符串了 break } } - // 如果查询成功,跳出大循环,直接返回当前基准字符串就行了。所以该算法最好的情况就是第一次查询就成功。 - // 如果基准字符串都被砍光了,说明没有最长公共后缀,所以该算法的最差的情况就是最后一个字符串和基准字符串完全没有公共前缀,而中间的字符串都能找到公共前缀,这会导致循环minStrLen * (n - 1)次。 if allFound || len(prefix) == 0 { break } @@ -380,7 +366,7 @@ func (isr *pathSearcher) getRelativePath() [][]string { return ret } -func searchByLCP(flatMap map[string]interface{}, pattern map[string]string) ([]map[string]string, error) { +func search(flatMap map[string]interface{}, pattern map[string]string) ([]map[string]string, error) { patternDataMap := patternDataMap{} allPatternData := make(map[string]*patternData) // absolutePath => patternData @@ -453,7 +439,7 @@ func SearchByPattern(flatMap map[string]interface{}, patterns []map[string]strin ret := make([]map[string]string, 0) for _, singlePattern := range patterns { - pRet, err := searchByLCP(flatMap, singlePattern) + pRet, err := search(flatMap, singlePattern) if err != nil { return nil, err } -- Gitee From 7665c2174047fe9340d1e90b21e797f2eed812e5 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 12 Jan 2022 14:33:24 +0800 Subject: [PATCH 1266/2647] fix helm InstallOrUpgradeChart timeout panic Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 6 ++++-- .../warpdrive/core/service/taskplugin/deploy.go | 1 - pkg/setting/consts.go | 2 ++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 1e1e0d10..951db867 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2583,7 +2583,8 @@ func updateProductGroup(username, productName, envName, updateType string, produ handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, 0, isRetry, helmClient, kubecli) + timeout := time.Second * setting.DeployTimeout + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, timeout, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) @@ -2835,7 +2836,8 @@ func updateProductVariable(productName, envName string, productResp *commonmodel handler := func(service *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, 0, isRetry, helmClient, kubecli) + timeout := time.Second * setting.UpdateEnvTimeout + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, timeout, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 50448522..6f5776c1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -545,7 +545,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } done := make(chan bool) - defer close(done) go func(chan bool) { if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { err = errors.WithMessagef( diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 9b0fbdb6..a7f753fb 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -590,3 +590,5 @@ const LocalClusterID = "0123456789abcdef12345678" const RequestModeOpenAPI = "openAPI" const DeployTimeout = 60 * 10 // 10 minutes + +const UpdateEnvTimeout = 60 * 5 // 5 minutes -- Gitee From d3037d38c8e0691988f429b780aa92ea2ee5ee31 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 12 Jan 2022 14:34:46 +0800 Subject: [PATCH 1267/2647] fix helm InstallOrUpgradeChart timeout panic Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 951db867..d304919c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2281,7 +2281,6 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha } done := make(chan bool) - defer close(done) go func(chan bool) { if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { err = errors.WithMessagef( -- Gitee From c7d21cba221a4ec157c575d10a32397ae98806d2 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 12 Jan 2022 16:43:46 +0800 Subject: [PATCH 1268/2647] fix timeout Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 2 +- pkg/setting/consts.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d304919c..f61ecee4 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2835,7 +2835,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel handler := func(service *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - timeout := time.Second * setting.UpdateEnvTimeout + timeout := setting.UpdateEnvTimeout err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, timeout, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a7f753fb..e0273b8c 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -16,6 +16,8 @@ limitations under the License. package setting +import "time" + const LocalConfig = "local.env" // envs @@ -591,4 +593,4 @@ const RequestModeOpenAPI = "openAPI" const DeployTimeout = 60 * 10 // 10 minutes -const UpdateEnvTimeout = 60 * 5 // 5 minutes +const UpdateEnvTimeout = 60 * 5 * time.Second -- Gitee From dc44b4b2e2962746f5214430d9c1b33a9bf7244d Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 13 Jan 2022 13:58:39 +0800 Subject: [PATCH 1269/2647] list jenkins integration API is now public Signed-off-by: Min Min --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 61309aca..77d41609 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -80,6 +80,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/directory/codehosts/callback"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/system/jenkins/integration"}, + }, { Methods: []string{"GET"}, Endpoints: []string{"api/v1/features/?*"}, -- Gitee From c114979e7450eab7f6de5af28cbd41d640078671 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 13 Jan 2022 14:05:04 +0800 Subject: [PATCH 1270/2647] optimize Signed-off-by: panxunying --- .../core/collaboration/handler/router.go | 2 +- .../service/collaboration_instance.go | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 50a56732..16e6e4f5 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -29,7 +29,7 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations := router.Group("collaborations") { collaborations.GET("", GetCollaborationMode) - collaborations.PUT("", gin2.UpdateOperationLogStatus, UpdateCollaborationMode) + collaborations.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateCollaborationMode) collaborations.POST("", gin2.UpdateOperationLogStatus, CreateCollaborationMode) collaborations.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) collaborations.GET("/new", GetCollaborationNew) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 0acead07..a6bb49ca 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -234,19 +234,27 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } for _, mode := range updateResp.New { for _, workflow := range mode.Workflows { + name := workflow.Name + if workflow.CollaborationType == config.CollaborationNew { + name = buildName(workflow.Name, mode.Name, userName) + } newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.CollaborationType, BaseName: workflow.Name, CollaborationMode: mode.Name, - Name: buildName(workflow.Name, mode.Name, userName), + Name: name, }) } for _, product := range mode.Products { + name := product.Name + if product.CollaborationType == config.CollaborationNew { + name = buildName(product.Name, mode.Name, userName) + } newProduct = append(newProduct, &Product{ CollaborationType: product.CollaborationType, BaseName: product.Name, CollaborationMode: mode.Name, - Name: buildName(product.Name, mode.Name, userName), + Name: name, DeployType: mode.DeployType, }) newProductName.Insert(product.Name) @@ -254,19 +262,27 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } for _, item := range updateResp.Update { for _, workflow := range item.NewSpec.Workflows { + name := workflow.Name + if workflow.CollaborationType == config.CollaborationNew { + name = buildName(workflow.Name, item.CollaborationMode, userName) + } newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.CollaborationType, BaseName: workflow.Name, CollaborationMode: item.CollaborationMode, - Name: buildName(workflow.Name, item.CollaborationMode, userName), + Name: name, }) } for _, product := range item.NewSpec.Products { + name := product.Name + if product.CollaborationType == config.CollaborationNew { + name = buildName(product.Name, item.CollaborationMode, userName) + } newProduct = append(newProduct, &Product{ CollaborationType: product.CollaborationType, BaseName: product.Name, CollaborationMode: item.CollaborationMode, - Name: buildName(product.Name, item.CollaborationMode, userName), + Name: name, DeployType: item.DeployType, }) newProductName.Insert(product.Name) -- Gitee From c6e8ed0adcdcb9db7995387ad4230c8867090c86 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 13 Jan 2022 17:11:18 +0800 Subject: [PATCH 1271/2647] Fixed a bug where s3 subpath will have an influence on job creation Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 35c1e274..34998a1d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -501,7 +501,6 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa ImagePullPolicy: corev1.PullIfNotPresent, Name: labels["s-type"], Image: jobImage, - WorkingDir: pipelineTask.ConfigPayload.S3Storage.Path, Env: []corev1.EnvVar{ { Name: "JOB_CONFIG_FILE", -- Gitee From 9a4b193b26f88436c3b04791b7db1858524fe56b Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 13 Jan 2022 18:15:51 +0800 Subject: [PATCH 1272/2647] added an API to do the same stuff Signed-off-by: Min Min --- .../aslan/core/system/handler/jenkins.go | 16 ++++++++++++++++ .../aslan/core/system/handler/router.go | 1 + .../policy/core/service/bundle/exemption_urls.go | 4 ---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/jenkins.go b/pkg/microservice/aslan/core/system/handler/jenkins.go index 3f062081..67d79b41 100644 --- a/pkg/microservice/aslan/core/system/handler/jenkins.go +++ b/pkg/microservice/aslan/core/system/handler/jenkins.go @@ -27,6 +27,22 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) +type CheckJenkinsIntegrationResp struct { + Exists bool `json:"exists"` +} + +func CheckJenkinsIntegration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + resp, err := service.ListJenkinsIntegration(ctx.Logger) + if err != nil || len(resp) == 0 { + ctx.Resp = &CheckJenkinsIntegrationResp{Exists: false} + return + } + ctx.Resp = &CheckJenkinsIntegrationResp{Exists: false} +} + func CreateJenkinsIntegration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 42ee31aa..3824489b 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -104,6 +104,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- jenkins := router.Group("jenkins") { + jenkins.GET("/existion", CheckJenkinsIntegration) jenkins.POST("/integration", CreateJenkinsIntegration) jenkins.GET("/integration", ListJenkinsIntegration) jenkins.PUT("/integration/:id", UpdateJenkinsIntegration) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 77d41609..61309aca 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -80,10 +80,6 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/directory/codehosts/callback"}, }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/system/jenkins/integration"}, - }, { Methods: []string{"GET"}, Endpoints: []string{"api/v1/features/?*"}, -- Gitee From 67b60fc161b7aa4a6ffd73b66c2df14644439e1f Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 13 Jan 2022 18:21:39 +0800 Subject: [PATCH 1273/2647] bugfix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/handler/router.go | 2 +- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 3824489b..f6c0b081 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -104,7 +104,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- jenkins := router.Group("jenkins") { - jenkins.GET("/existion", CheckJenkinsIntegration) + jenkins.GET("/exist", CheckJenkinsIntegration) jenkins.POST("/integration", CreateJenkinsIntegration) jenkins.GET("/integration", ListJenkinsIntegration) jenkins.PUT("/integration/:id", UpdateJenkinsIntegration) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 61309aca..fac09e5e 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -84,6 +84,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/v1/features/?*"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/system/jenkins/exist"}, + }, { Methods: []string{"POST"}, Endpoints: []string{"api/callback"}, -- Gitee From 0ba9c65662ceea1fd75985c2859fdd55c800ff9e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 13 Jan 2022 20:32:28 +0800 Subject: [PATCH 1274/2647] fix deletesecret bug Signed-off-by: ddh27 --- pkg/tool/kube/updater/secret.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/kube/updater/secret.go b/pkg/tool/kube/updater/secret.go index 55e6166c..0d7edd7a 100644 --- a/pkg/tool/kube/updater/secret.go +++ b/pkg/tool/kube/updater/secret.go @@ -38,5 +38,5 @@ func DeleteSecretWithName(ns, name string, cl client.Client) error { Name: name, }, } - return deleteObjects(secret, cl) + return deleteObject(secret, cl) } -- Gitee From 2c72835b9445b30d37e0b09a36321088d42e061f Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 13 Jan 2022 21:10:01 +0800 Subject: [PATCH 1275/2647] dont upload cache if user dont enable cache Signed-off-by: zhangyifei --- pkg/microservice/reaper/core/service/reaper/reaper.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 4caec3d9..44f017b7 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -416,7 +416,6 @@ func (r *Reaper) AfterExec(upStreamErr error) error { log.Errorf("Failed to archive html test report: %s", err) return err } - } if upStreamErr != nil { @@ -451,11 +450,13 @@ func (r *Reaper) AfterExec(upStreamErr error) error { return err } - // Upload workspace cache. + // Upload workspace cache if user uses workspace cache. // Note: Whether the cache is uploaded successfully or not cannot hinder the progress of the overall process, // so only exceptions are printed here and the process is not interrupted. - if err := r.CompressCache(r.Ctx.StorageURI); err != nil { - log.Warnf("Failed to run compress cache: %s", err) + if !r.Ctx.CleanWorkspace && !r.Ctx.ResetCache { + if err := r.CompressCache(r.Ctx.StorageURI); err != nil { + log.Warnf("Failed to run compress cache: %s", err) + } } // Create dog food file to tell wd that task has finished. -- Gitee From 3b6a8d1fd131b3b9c0f555a357262e5c63ed20fa Mon Sep 17 00:00:00 2001 From: fansi Date: Thu, 13 Jan 2022 20:15:35 +0800 Subject: [PATCH 1276/2647] add go mod for golang tutorial Signed-off-by: fansi --- examples/microservice-demo/backend/go.mod | 3 +++ examples/multi-service-demo/Makefile | 2 +- examples/multi-service-demo/go.mod | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 examples/microservice-demo/backend/go.mod create mode 100644 examples/multi-service-demo/go.mod diff --git a/examples/microservice-demo/backend/go.mod b/examples/microservice-demo/backend/go.mod new file mode 100644 index 00000000..2090ee85 --- /dev/null +++ b/examples/microservice-demo/backend/go.mod @@ -0,0 +1,3 @@ +module microservice-demo/backend + +go 1.17 diff --git a/examples/multi-service-demo/Makefile b/examples/multi-service-demo/Makefile index 54a96417..ceda2f29 100644 --- a/examples/multi-service-demo/Makefile +++ b/examples/multi-service-demo/Makefile @@ -7,4 +7,4 @@ build-service2: build-service3: - cd src/service3 && go clean && GOOS=linux GOARCH=amd64 go build -o service3 service3.go \ No newline at end of file + cd src/service3 && go clean && GOOS=linux GOARCH=amd64 go build -o service3 service3.go diff --git a/examples/multi-service-demo/go.mod b/examples/multi-service-demo/go.mod new file mode 100644 index 00000000..9a50caa1 --- /dev/null +++ b/examples/multi-service-demo/go.mod @@ -0,0 +1,3 @@ +module multi-service-demo + +go 1.17 -- Gitee From 1866804487f6b2e952b462ddf29b88777d63fd34 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 14 Jan 2022 09:55:57 +0800 Subject: [PATCH 1277/2647] changed the job policy from ifNotPresent to Always Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 35c1e274..9d327f6f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -498,7 +498,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa ImagePullSecrets: ImagePullSecrets, Containers: []corev1.Container{ { - ImagePullPolicy: corev1.PullIfNotPresent, + ImagePullPolicy: corev1.PullAlways, Name: labels["s-type"], Image: jobImage, WorkingDir: pipelineTask.ConfigPayload.S3Storage.Path, -- Gitee From 0e00062ba46c7f8b06d707405cb15b051cb9f33c Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 14 Jan 2022 10:15:13 +0800 Subject: [PATCH 1278/2647] code review changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/handler/jenkins.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/handler/jenkins.go b/pkg/microservice/aslan/core/system/handler/jenkins.go index 67d79b41..e1a8b99c 100644 --- a/pkg/microservice/aslan/core/system/handler/jenkins.go +++ b/pkg/microservice/aslan/core/system/handler/jenkins.go @@ -40,7 +40,7 @@ func CheckJenkinsIntegration(c *gin.Context) { ctx.Resp = &CheckJenkinsIntegrationResp{Exists: false} return } - ctx.Resp = &CheckJenkinsIntegrationResp{Exists: false} + ctx.Resp = &CheckJenkinsIntegrationResp{Exists: true} } func CreateJenkinsIntegration(c *gin.Context) { -- Gitee From 58346ece7b11746b68fbb5ff369e5737e28a6a8d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 11:10:19 +0800 Subject: [PATCH 1279/2647] label Signed-off-by: mouuii --- .../core/common/repository/mongodb/product.go | 9 ++ .../common/repository/mongodb/workflow.go | 12 ++ .../aslan/core/label/config/consts.go | 24 +++ .../aslan/core/label/handler/labels.go | 117 +++++++++++++++ .../aslan/core/label/handler/labelsbinding.go | 67 +++++++++ .../aslan/core/label/handler/router.go | 40 +++++ .../aslan/core/label/repository/dto/label.go | 39 +++++ .../core/label/repository/models/label.go | 34 +++++ .../label/repository/models/labelbinding.go | 34 +++++ .../core/label/repository/mongodb/label.go | 123 +++++++++++++++ .../label/repository/mongodb/labelbinding.go | 138 +++++++++++++++++ .../aslan/core/label/service/label.go | 142 ++++++++++++++++++ .../aslan/core/label/service/labelsbinding.go | 62 ++++++++ pkg/microservice/aslan/core/service.go | 3 + pkg/microservice/aslan/server/rest/router.go | 3 + 15 files changed, 847 insertions(+) create mode 100644 pkg/microservice/aslan/core/label/config/consts.go create mode 100644 pkg/microservice/aslan/core/label/handler/labels.go create mode 100644 pkg/microservice/aslan/core/label/handler/labelsbinding.go create mode 100644 pkg/microservice/aslan/core/label/handler/router.go create mode 100644 pkg/microservice/aslan/core/label/repository/dto/label.go create mode 100644 pkg/microservice/aslan/core/label/repository/models/label.go create mode 100644 pkg/microservice/aslan/core/label/repository/models/labelbinding.go create mode 100644 pkg/microservice/aslan/core/label/repository/mongodb/label.go create mode 100644 pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go create mode 100644 pkg/microservice/aslan/core/label/service/label.go create mode 100644 pkg/microservice/aslan/core/label/service/labelsbinding.go diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index eaefcbd8..152f0a92 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -23,6 +23,7 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -105,6 +106,7 @@ func (c *ProductColl) EnsureIndex(ctx context.Context) error { type ProductEnvFindOptions struct { Name string Namespace string + ID string } func (c *ProductColl) FindEnv(opt *ProductEnvFindOptions) (*models.Product, error) { @@ -116,6 +118,13 @@ func (c *ProductColl) FindEnv(opt *ProductEnvFindOptions) (*models.Product, erro if opt.Namespace != "" { query["namespace"] = opt.Namespace } + if opt.ID != "" { + oid, err := primitive.ObjectIDFromHex(opt.ID) + if err != nil { + return nil, err + } + query["_id"] = oid + } ret := new(models.Product) err := c.FindOne(context.TODO(), query).Decode(ret) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index a3b0b528..ec4fa9fb 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -22,6 +22,7 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -128,6 +129,17 @@ func (c *WorkflowColl) Find(name string) (*models.Workflow, error) { return res, err } +func (c *WorkflowColl) FindByID(id string) (*models.Workflow, error) { + res := &models.Workflow{} + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + query := bson.M{"_id": oid} + err = c.FindOne(context.TODO(), query).Decode(res) + return res, err +} + func (c *WorkflowColl) Delete(name string) error { query := bson.M{"name": name} diff --git a/pkg/microservice/aslan/core/label/config/consts.go b/pkg/microservice/aslan/core/label/config/consts.go new file mode 100644 index 00000000..6910afe2 --- /dev/null +++ b/pkg/microservice/aslan/core/label/config/consts.go @@ -0,0 +1,24 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +type ResourceType string + +const ( + ResourceTypeWorkflow ResourceType = "workflow" + ResourceTypeProduct ResourceType = "product" +) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go new file mode 100644 index 00000000..c3bfa262 --- /dev/null +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -0,0 +1,117 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "strconv" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/dto" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +type ListLabelsArgs struct { + Key string `json:"key" form:"key"` + Values []string `json:"values" form:"values"` + LabelType string `json:"type" form:"type"` +} + +func ListLabels(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + listLabelsArgs := new(ListLabelsArgs) + if err := c.ShouldBindQuery(listLabelsArgs); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.ListLabels(listLabelsArgs.Key, listLabelsArgs.Values, listLabelsArgs.LabelType) +} + +func createLabelValidate(lb *models.Label) error { + if lb.Key == "" || lb.Value == "" { + return e.ErrInvalidParam.AddDesc("invalid label args") + } + return nil +} + +func CreateLabel(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + label := new(models.Label) + if err := c.ShouldBindJSON(label); err != nil { + ctx.Err = err + return + } + + if err := createLabelValidate(label); err != nil { + ctx.Err = err + return + } + label.CreateBy = ctx.UserName + ctx.Err = service.CreateLabel(label) +} + +func DeleteLabel(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("id must not be empty") + return + } + force := c.Query("force") + forceBool, _ := strconv.ParseBool(force) + ctx.Err = service.DeleteLabel(id, forceBool, ctx.Logger) +} + +type ListResourceByLabelsReq struct { + LabelFilters []dto.LabelFilter `json:"label_filters"` +} + +func ListResourceByLabels(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + listResourceByLabelsReq := new(ListResourceByLabelsReq) + if err := c.ShouldBindJSON(listResourceByLabelsReq); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.ListResourcesByLabels(listResourceByLabelsReq.LabelFilters, ctx.Logger) +} + +type ListLabelsByResourceReq struct { + ResourceID string `json:"resource_id"` + ResourceType string `json:"resource_type"` +} + +func ListLabelsByResource(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + listLabelsByResourceReq := new(ListLabelsByResourceReq) + if err := c.ShouldBindJSON(listLabelsByResourceReq); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.ListLabelsByResourceID(listLabelsByResourceReq.ResourceID, listLabelsByResourceReq.ResourceType, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/labelsbinding.go new file mode 100644 index 00000000..1275534d --- /dev/null +++ b/pkg/microservice/aslan/core/label/handler/labelsbinding.go @@ -0,0 +1,67 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func ListLabelsBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListLabelsBinding() +} + +func createLBValidate(lb *models.LabelBinding) error { + if lb.LabelID == "" || lb.ResourceType == "" || lb.ResourceID == "" { + return e.ErrInvalidParam.AddDesc("invalid labelbinding args") + } + return nil +} + +func CreateLabelsBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + labelBinding := new(models.LabelBinding) + if err := c.ShouldBindJSON(labelBinding); err != nil { + ctx.Err = err + return + } + if err := createLBValidate(labelBinding); err != nil { + ctx.Err = err + return + } + ctx.Err = service.CreateLabelsBinding(labelBinding, ctx.UserName, ctx.Logger) +} + +func DeleteLabelsBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + id := c.Param("id") + if id == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("id must not be empty") + } + + ctx.Err = service.DeleteLabelsBinding(id) +} diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go new file mode 100644 index 00000000..c945ad44 --- /dev/null +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -0,0 +1,40 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + labels := router.Group("labels") + { + labels.GET("", ListLabels) + labels.POST("", CreateLabel) + labels.DELETE(":id", DeleteLabel) + labels.POST("/resourcebylabels", ListResourceByLabels) + labels.POST("/labelsbyresource", ListLabelsByResource) + } + labelBindings := router.Group("labelbindings") + { + labelBindings.GET("", ListLabelsBinding) + labelBindings.POST("", CreateLabelsBinding) + labelBindings.DELETE(":id", DeleteLabelsBinding) + } +} diff --git a/pkg/microservice/aslan/core/label/repository/dto/label.go b/pkg/microservice/aslan/core/label/repository/dto/label.go new file mode 100644 index 00000000..c9541291 --- /dev/null +++ b/pkg/microservice/aslan/core/label/repository/dto/label.go @@ -0,0 +1,39 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dto + +type LabelFilter struct { + Key string `json:"key"` + Values []string `json:"values"` +} + +type ResourceLabel struct { + ResourceID string `json:"resource_id"` + ResourceType string `json:"resource_type"` + Labels []Label `json:"labels"` +} + +type Label struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type LabelResource struct { + Key string `json:"key"` + Value string `json:"value"` + ResourceID string `json:"resource_id"` +} diff --git a/pkg/microservice/aslan/core/label/repository/models/label.go b/pkg/microservice/aslan/core/label/repository/models/label.go new file mode 100644 index 00000000..bc232641 --- /dev/null +++ b/pkg/microservice/aslan/core/label/repository/models/label.go @@ -0,0 +1,34 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Label struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` +} + +func (Label) TableName() string { + return "label" +} diff --git a/pkg/microservice/aslan/core/label/repository/models/labelbinding.go b/pkg/microservice/aslan/core/label/repository/models/labelbinding.go new file mode 100644 index 00000000..23d4e57c --- /dev/null +++ b/pkg/microservice/aslan/core/label/repository/models/labelbinding.go @@ -0,0 +1,34 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type LabelBinding struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ResourceType string `bson:"resource_type" json:"resource_type"` + ResourceID string `bson:"resource_id" json:"resource_id"` + LabelID string `bson:"label_id" json:"label_id"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` +} + +func (LabelBinding) TableName() string { + return "labelbinding" +} diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go new file mode 100644 index 00000000..ab216a19 --- /dev/null +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -0,0 +1,123 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type LabelColl struct { + *mongo.Collection + + coll string +} + +func NewLabelColl() *LabelColl { + name := models.Label{}.TableName() + return &LabelColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *LabelColl) GetCollectionName() string { + return c.coll +} + +func (c *LabelColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "key", Value: 1}, + bson.E{Key: "value", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + return err +} + +func (c *LabelColl) Create(args *models.Label) error { + args.CreateTime = time.Now().Unix() + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *LabelColl) Delete(id string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + query := bson.M{"_id": oid} + _, err = c.DeleteOne(context.TODO(), query) + return err +} + +func (c *LabelColl) Find(id string) (*models.Label, error) { + res := new(models.Label) + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + opts := options.FindOne() + query := bson.M{"_id": oid} + err = c.FindOne(context.TODO(), query, opts).Decode(res) + return res, err +} + +type ListLabelOpt struct { + Key string + Values []string + Type string +} + +func (c *LabelColl) List(opt *ListLabelOpt) ([]*models.Label, error) { + var res []*models.Label + + ctx := context.Background() + + query := bson.M{} + if opt.Key != "" { + query["key"] = opt.Key + } + if len(opt.Values) != 0 { + query["value"] = bson.M{"$in": opt.Values} + } + if opt.Type != "" { + query["type"] = opt.Type + } + opts := options.Find() + opts.SetSort(bson.D{{"key", 1}}) + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go new file mode 100644 index 00000000..585fe8c0 --- /dev/null +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -0,0 +1,138 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type LabelBindingColl struct { + *mongo.Collection + + coll string +} + +func NewLabelBindingColl() *LabelBindingColl { + name := models.LabelBinding{}.TableName() + return &LabelBindingColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *LabelBindingColl) GetCollectionName() string { + return c.coll +} + +func (c *LabelBindingColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "resource_id", Value: 1}, + bson.E{Key: "label_id", Value: 1}, + bson.E{Key: "resource_type", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + return err +} + +func (c *LabelBindingColl) Create(args *models.LabelBinding) error { + args.CreateTime = time.Now().Unix() + _, err := c.InsertOne(context.TODO(), args) + return err +} + +type LabelBindingCollFindOpt struct { + LabelID string + LabelIDs []string + ResourceID string + ResourceType string +} + +func (c *LabelBindingColl) FindByOpt(opt *LabelBindingCollFindOpt) (*models.LabelBinding, error) { + res := &models.LabelBinding{} + query := bson.M{} + if opt.LabelID != "" { + query["label_id"] = opt.LabelID + } + err := c.FindOne(context.TODO(), query).Decode(res) + return res, err +} + +func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.LabelBinding, error) { + var ret []*models.LabelBinding + query := bson.M{} + if opt.LabelID != "" { + query["label_id"] = opt.LabelID + } + if len(opt.LabelIDs) != 0 { + query["label_id"] = bson.M{"$in": opt.LabelIDs} + } + if opt.ResourceID != "" { + query["resource_id"] = opt.ResourceID + } + if opt.ResourceType != "" { + query["resource_type"] = opt.ResourceType + } + ctx := context.Background() + opts := options.Find() + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &ret) + if err != nil { + return nil, err + } + return ret, err +} + +func (c *LabelBindingColl) Delete(id string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + query := bson.M{"_id": oid} + _, err = c.DeleteOne(context.TODO(), query) + return err +} + +func (c *LabelBindingColl) DeleteMany(ids []string) error { + query := bson.M{} + var oids []primitive.ObjectID + for _, id := range ids { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + oids = append(oids, oid) + } + query["_id"] = bson.M{"$in": oids} + + _, err := c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go new file mode 100644 index 00000000..d04d8580 --- /dev/null +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -0,0 +1,142 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/dto" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func CreateLabel(label *models.Label) error { + return mongodb.NewLabelColl().Create(label) +} + +func ListLabels(key string, values []string, labelType string) ([]*models.Label, error) { + opt := &mongodb.ListLabelOpt{ + Key: key, + Values: values, + Type: labelType, + } + return mongodb.NewLabelColl().List(opt) +} + +func ListResourcesByLabels(filter []dto.LabelFilter, logger *zap.SugaredLogger) ([]dto.ResourceLabel, error) { + // find the label id + labelM := map[string]*models.Label{} + for _, v := range filter { + labels, err := mongodb.NewLabelColl().List(&mongodb.ListLabelOpt{ + Key: v.Key, + Values: v.Values, + }) + if err != nil { + continue + } + for _, label := range labels { + labelM[label.ID.Hex()] = label + } + } + labelIds := []string{} + for k, _ := range labelM { + labelIds = append(labelIds, k) + } + // find the labelBindings + labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIds}) + if err != nil { + logger.Errorf("list labelbinding err:%s", err) + return nil, err + } + + var res []dto.ResourceLabel + for _, v := range labelBindings { + labelResource, err := ListLabelsByResourceID(v.ResourceID, v.ResourceType, logger) + if err != nil { + continue + } + var labels []dto.Label + for _, v := range labelResource { + label := dto.Label{ + Key: v.Key, + Value: v.Value, + } + labels = append(labels, label) + } + + res = append(res, dto.ResourceLabel{ + ResourceID: v.ResourceID, + ResourceType: v.ResourceType, + Labels: labels, + }) + } + + return res, nil +} + +func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.SugaredLogger) ([]dto.LabelResource, error) { + labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{ + ResourceID: resourceID, + ResourceType: resourceType, + }) + if err != nil { + logger.Errorf("list labelbinding err:%s", err) + return nil, err + } + var labelResources []dto.LabelResource + for _, v := range labelBindings { + label, err := mongodb.NewLabelColl().Find(v.LabelID) + if err != nil { + continue + } + labelResources = append(labelResources, dto.LabelResource{ + Key: label.Key, + Value: label.Value, + ResourceID: v.ResourceID, + }) + } + return labelResources, nil +} + +func DeleteLabel(id string, force bool, logger *zap.SugaredLogger) error { + + // query if the label already bind resources + res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelID: id}) + if err != nil { + logger.Errorf("list labelbingding err:%s", err) + return err + } + // force delete : delete related labelBindings + if force { + var ids []string + for _, labelBindings := range res { + ids = append(ids, labelBindings.ID.Hex()) + } + + if err := mongodb.NewLabelBindingColl().DeleteMany(ids); err != nil { + logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) + return err + } + } + // non force delete : can not delete label when label has bind resources + if len(res) > 0 && !force { + logger.Error("the label has bind resources,can not delete") + return e.ErrForbidden.AddDesc("the label has bind resources") + } + return mongodb.NewLabelColl().Delete(id) +} diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go new file mode 100644 index 00000000..5e85ba2b --- /dev/null +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -0,0 +1,62 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "go.uber.org/zap" + + commondb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" +) + +func ListLabelsBinding() ([]*models.LabelBinding, error) { + return mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{}) +} + +func CreateLabelsBinding(binding *models.LabelBinding, userName string, logger *zap.SugaredLogger) error { + // check label exist + + if _, err := mongodb.NewLabelColl().Find(binding.LabelID); err != nil { + logger.Errorf("find label err:%s", err) + return err + } + //check resource exist + switch binding.ResourceType { + case string(config.ResourceTypeWorkflow): + _, err := commondb.NewWorkflowColl().FindByID(binding.ResourceID) + if err != nil { + logger.Errorf("can not find related resource err:%s", err) + return err + } + case string(config.ResourceTypeProduct): + _, err := commondb.NewProductColl().FindEnv(&commondb.ProductEnvFindOptions{ + ID: binding.ResourceID, + }) + if err != nil { + logger.Errorf("can not find related resource err:%s", err) + return err + } + } + binding.CreateBy = userName + return mongodb.NewLabelBindingColl().Create(binding) +} + +func DeleteLabelsBinding(id string) error { + return mongodb.NewLabelBindingColl().Delete(id) +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index d06114f6..1bdeaf74 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -33,6 +33,7 @@ import ( deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" @@ -211,6 +212,8 @@ func initDatabase() { systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), + labelMongodb.NewLabelColl(), + labelMongodb.NewLabelBindingColl(), } { wg.Add(1) go func(r indexer) { diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 4d6f8e70..7c632d05 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -28,6 +28,7 @@ import ( cronhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/cron/handler" deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" + labelhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/label/handler" loghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/log/handler" multiclusterhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/handler" projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" @@ -37,6 +38,7 @@ import ( templatehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/handler" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" + // Note: have to load docs for swagger to work. See https://blog.csdn.net/weixin_43249914/article/details/103035711 _ "github.com/koderover/zadig/pkg/microservice/aslan/server/rest/doc" ) @@ -85,6 +87,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/cluster": new(multiclusterhandler.Router), "/api/template": new(templatehandler.Router), "/api/collaboration": new(collaborationhandler.Router), + "/api/label": new(labelhandler.Router), } { r.Inject(router.Group(name)) } -- Gitee From 36c8d3922f05a3219cc58e892dc4d09c983ec8a6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 14 Jan 2022 11:11:40 +0800 Subject: [PATCH 1280/2647] add data stat inerface Signed-off-by: liu deyi --- .../aslan/core/stat/dashboard/repository/models/build.go | 1 + .../aslan/core/stat/dashboard/repository/models/deploy.go | 1 + .../aslan/core/stat/dashboard/repository/mongodb/build.go | 1 + .../aslan/core/stat/dashboard/repository/mongodb/deploy.go | 1 + pkg/microservice/aslan/core/stat/dashboard/service/build.go | 1 + pkg/microservice/aslan/core/stat/dashboard/service/deploy.go | 1 + pkg/microservice/aslan/core/stat/dashboard/service/overview.go | 1 + pkg/microservice/aslan/core/stat/dashboard/service/test.go | 1 + pkg/microservice/aslan/core/stat/overview/handler/build.go | 1 + pkg/microservice/aslan/core/stat/overview/handler/deploy.go | 1 + pkg/microservice/aslan/core/stat/overview/handler/overview.go | 1 + pkg/microservice/aslan/core/stat/overview/handler/router.go | 1 + pkg/microservice/aslan/core/stat/overview/handler/test.go | 1 + 13 files changed, 13 insertions(+) create mode 100644 pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/service/build.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/service/deploy.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/service/overview.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/service/test.go create mode 100644 pkg/microservice/aslan/core/stat/overview/handler/build.go create mode 100644 pkg/microservice/aslan/core/stat/overview/handler/deploy.go create mode 100644 pkg/microservice/aslan/core/stat/overview/handler/overview.go create mode 100644 pkg/microservice/aslan/core/stat/overview/handler/router.go create mode 100644 pkg/microservice/aslan/core/stat/overview/handler/test.go diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go b/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go new file mode 100644 index 00000000..2640e7f9 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go @@ -0,0 +1 @@ +package models diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go new file mode 100644 index 00000000..2640e7f9 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go @@ -0,0 +1 @@ +package models diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go new file mode 100644 index 00000000..6e598913 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go @@ -0,0 +1 @@ +package mongodb diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go new file mode 100644 index 00000000..6e598913 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go @@ -0,0 +1 @@ +package mongodb diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/build.go b/pkg/microservice/aslan/core/stat/dashboard/service/build.go new file mode 100644 index 00000000..6d43c336 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/service/build.go @@ -0,0 +1 @@ +package service diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go new file mode 100644 index 00000000..6d43c336 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go @@ -0,0 +1 @@ +package service diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/overview.go b/pkg/microservice/aslan/core/stat/dashboard/service/overview.go new file mode 100644 index 00000000..6d43c336 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/service/overview.go @@ -0,0 +1 @@ +package service diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/test.go b/pkg/microservice/aslan/core/stat/dashboard/service/test.go new file mode 100644 index 00000000..6d43c336 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/service/test.go @@ -0,0 +1 @@ +package service diff --git a/pkg/microservice/aslan/core/stat/overview/handler/build.go b/pkg/microservice/aslan/core/stat/overview/handler/build.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/overview/handler/build.go @@ -0,0 +1 @@ +package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/deploy.go b/pkg/microservice/aslan/core/stat/overview/handler/deploy.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/overview/handler/deploy.go @@ -0,0 +1 @@ +package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/overview.go b/pkg/microservice/aslan/core/stat/overview/handler/overview.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/overview/handler/overview.go @@ -0,0 +1 @@ +package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/router.go b/pkg/microservice/aslan/core/stat/overview/handler/router.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/overview/handler/router.go @@ -0,0 +1 @@ +package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/test.go b/pkg/microservice/aslan/core/stat/overview/handler/test.go new file mode 100644 index 00000000..abeebd16 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/overview/handler/test.go @@ -0,0 +1 @@ +package handler -- Gitee From 4fb75d773d4c98dfa5f30f6b7c7a7ec68d5d9ec8 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 13:49:23 +0800 Subject: [PATCH 1281/2647] optimize Signed-off-by: panxunying --- .../handler/collaboration_mode.go | 3 +- .../service/collaboration_instance.go | 40 +++++--- .../service/collaboration_mode.go | 89 ----------------- .../collaboration/collaboration_mode.go | 97 +++++++++++++++++++ .../aslan/core/common/service/product.go | 6 +- .../aslan/core/common/service/workflow.go | 6 +- .../core/environment/service/environment.go | 6 +- .../aslan/core/environment/service/product.go | 6 +- .../workflow/service/workflow/workflow.go | 6 +- 9 files changed, 142 insertions(+), 117 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index a6b2d161..6c8bc54a 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -23,6 +23,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -36,7 +37,7 @@ func GetCollaborationMode(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - ctx.Resp, ctx.Err = service.GetCollaborationModes([]string{projectName}, ctx.Logger) + ctx.Resp, ctx.Err = collaboration.GetCollaborationModes([]string{projectName}, ctx.Logger) } func CreateCollaborationMode(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index a6bb49ca..9d68d154 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -13,6 +13,7 @@ import ( models2 "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/util" ) @@ -60,12 +61,14 @@ type Workflow struct { } type Product struct { - CollaborationType config.CollaborationType `json:"collaboration_type"` - BaseName string `json:"base_name"` - CollaborationMode string `json:"collaboration_mode"` - Name string `json:"name"` - DeployType string `json:"deploy_type"` - Vars []*templatemodels.RenderKV `json:"vars"` + CollaborationType config.CollaborationType `json:"collaboration_type"` + BaseName string `json:"base_name"` + CollaborationMode string `json:"collaboration_mode"` + Name string `json:"name"` + DeployType string `json:"deploy_type"` + Vars []*templatemodels.RenderKV `json:"vars"` + DefaultValues string `json:"default_values"` + RenderCharts []*commonservice.RenderChartArg `json:"render_charts"` } type GetCollaborationNewResp struct { Code int64 `json:"code"` @@ -310,18 +313,20 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL } } } - renderSets, err := getRenderSetKvs(projectName, newProductName.List()) + renderSets, err := getRenderSet(projectName, newProductName.List()) if err != nil { return nil, err } if renderSets != nil { - productVarsMap := make(map[string][]*templatemodels.RenderKV) + productRenderSetMap := make(map[string]models2.RenderSet) for _, set := range renderSets { - productVarsMap[set.EnvName] = set.KVs + productRenderSetMap[set.EnvName] = set } for _, product := range newProduct { - if vars, ok := productVarsMap[product.BaseName]; ok { - product.Vars = vars + if set, ok := productRenderSetMap[product.BaseName]; ok { + product.Vars = set.KVs + product.DefaultValues = set.DefaultValues + product.RenderCharts = buildRenderChartArg(set.ChartInfos, product.BaseName) } else { return nil, fmt.Errorf("product:%s not exist", product.BaseName) } @@ -355,7 +360,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL }, nil } -func getRenderSetKvs(projectName string, envs []string) ([]models2.RenderSet, error) { +func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ InProjects: []string{projectName}, InEnvs: envs, @@ -379,3 +384,14 @@ func getRenderSetKvs(projectName string, envs []string) ([]models2.RenderSet, er } return renderSets, nil } + +func buildRenderChartArg(chartInfos []*templatemodels.RenderChart, envName string) []*commonservice.RenderChartArg { + ret := make([]*commonservice.RenderChartArg, 0) + for _, singleChart := range chartInfos { + rcaObj := new(commonservice.RenderChartArg) + rcaObj.LoadFromRenderChartModel(singleChart) + rcaObj.EnvName = envName + ret = append(ret, rcaObj) + } + return ret +} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go index c9403362..973308df 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go @@ -17,19 +17,12 @@ limitations under the License. package service import ( - "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" - config2 "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" ) -type GetCollaborationModeResp struct { - Collaborations []*models.CollaborationMode `json:"collaborations"` -} - func CreateCollaborationMode(userName string, collaborationMode *models.CollaborationMode, logger *zap.SugaredLogger) error { err := mongodb.NewCollaborationModeColl().Create(userName, collaborationMode) if err != nil { @@ -56,85 +49,3 @@ func DeleteCollaborationMode(username, projectName, name string, logger *zap.Sug } return nil } - -func BuildEnvCMMapKey(projectName string, envName string) string { - return projectName + " " + envName -} - -func BuildWorkflowCMMapKey(projectName string, workflowName string) string { - return projectName + " " + workflowName -} - -func GetEnvCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { - collaborationModes, err := GetCollaborationModes(projects, logger) - if err != nil { - logger.Errorf("GetCollaborationModes error: %v", err) - return nil, err - } - return buildEnvCMMap(collaborationModes.Collaborations), nil -} - -func GetWorkflowCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { - collaborationModes, err := GetCollaborationModes(projects, logger) - if err != nil { - logger.Errorf("GetCollaborationModes error: %v", err) - return nil, err - } - return buildWorkflowCMMap(collaborationModes.Collaborations), nil -} - -func buildEnvCMMap(collaborations []*models.CollaborationMode) map[string]sets.String { - envCMMap := make(map[string]sets.String) - for _, cm := range collaborations { - for _, product := range cm.Products { - if product.CollaborationType == config2.CollaborationShare { - continue - } - key := BuildEnvCMMapKey(cm.ProjectName, product.Name) - if cmSet, ok := envCMMap[key]; ok { - cmSet.Insert(cm.Name) - envCMMap[key] = cmSet - } else { - cmSet := sets.NewString(cm.Name) - envCMMap[key] = cmSet - } - } - } - return envCMMap -} - -func buildWorkflowCMMap(collaborations []*models.CollaborationMode) map[string]sets.String { - workflowCMMap := make(map[string]sets.String) - for _, cm := range collaborations { - for _, workflow := range cm.Workflows { - if workflow.CollaborationType == config2.CollaborationShare { - continue - } - key := BuildWorkflowCMMapKey(cm.ProjectName, workflow.Name) - if cmSet, ok := workflowCMMap[key]; ok { - cmSet.Insert(cm.Name) - workflowCMMap[key] = cmSet - } else { - cmSet := sets.NewString(cm.Name) - workflowCMMap[key] = cmSet - } - } - } - return workflowCMMap -} - -func GetCollaborationModes(projects []string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { - collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ - Projects: projects, - }) - if err != nil { - if err == mongo.ErrNoDocuments { - return &GetCollaborationModeResp{}, nil - } - logger.Errorf("GetCollaborationModes error, err msg:%s", err) - return nil, err - } - return &GetCollaborationModeResp{ - Collaborations: collaborations, - }, nil -} diff --git a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go new file mode 100644 index 00000000..864cd77e --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go @@ -0,0 +1,97 @@ +package collaboration + +import ( + "go.mongodb.org/mongo-driver/mongo" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" + + config2 "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" +) + +type GetCollaborationModeResp struct { + Collaborations []*models.CollaborationMode `json:"collaborations"` +} + +func BuildEnvCMMapKey(projectName string, envName string) string { + return projectName + " " + envName +} + +func BuildWorkflowCMMapKey(projectName string, workflowName string) string { + return projectName + " " + workflowName +} + +func GetEnvCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { + collaborationModes, err := GetCollaborationModes(projects, logger) + if err != nil { + logger.Errorf("GetCollaborationModes error: %v", err) + return nil, err + } + return buildEnvCMMap(collaborationModes.Collaborations), nil +} + +func GetWorkflowCMMap(projects []string, logger *zap.SugaredLogger) (map[string]sets.String, error) { + collaborationModes, err := GetCollaborationModes(projects, logger) + if err != nil { + logger.Errorf("GetCollaborationModes error: %v", err) + return nil, err + } + return buildWorkflowCMMap(collaborationModes.Collaborations), nil +} + +func buildEnvCMMap(collaborations []*models.CollaborationMode) map[string]sets.String { + envCMMap := make(map[string]sets.String) + for _, cm := range collaborations { + for _, product := range cm.Products { + if product.CollaborationType == config2.CollaborationShare { + continue + } + key := BuildEnvCMMapKey(cm.ProjectName, product.Name) + if cmSet, ok := envCMMap[key]; ok { + cmSet.Insert(cm.Name) + envCMMap[key] = cmSet + } else { + cmSet := sets.NewString(cm.Name) + envCMMap[key] = cmSet + } + } + } + return envCMMap +} + +func buildWorkflowCMMap(collaborations []*models.CollaborationMode) map[string]sets.String { + workflowCMMap := make(map[string]sets.String) + for _, cm := range collaborations { + for _, workflow := range cm.Workflows { + if workflow.CollaborationType == config2.CollaborationShare { + continue + } + key := BuildWorkflowCMMapKey(cm.ProjectName, workflow.Name) + if cmSet, ok := workflowCMMap[key]; ok { + cmSet.Insert(cm.Name) + workflowCMMap[key] = cmSet + } else { + cmSet := sets.NewString(cm.Name) + workflowCMMap[key] = cmSet + } + } + } + return workflowCMMap +} + +func GetCollaborationModes(projects []string, logger *zap.SugaredLogger) (*GetCollaborationModeResp, error) { + collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ + Projects: projects, + }) + if err != nil { + if err == mongo.ErrNoDocuments { + return &GetCollaborationModeResp{}, nil + } + logger.Errorf("GetCollaborationModes error, err msg:%s", err) + return nil, err + } + return &GetCollaborationModeResp{ + Collaborations: collaborations, + }, nil +} diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 826c3bb1..a67fb4f9 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -28,10 +28,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" @@ -51,11 +51,11 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("find product error: %v", err) return e.ErrDeleteEnv.AddDesc("not found") } - envCMMap, err := service.GetEnvCMMap([]string{productName}, log) + envCMMap, err := collaboration.GetEnvCMMap([]string{productName}, log) if err != nil { return err } - if cmSets, ok := envCMMap[service.BuildEnvCMMapKey(productName, envName)]; ok { + if cmSets, ok := envCMMap[collaboration.BuildEnvCMMapKey(productName, envName)]; ok { return fmt.Errorf("this is a base environment, collaborations:%v is related", cmSets.List()) } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 18b274ee..6e3e9c3a 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -24,10 +24,10 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" @@ -61,11 +61,11 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log.Errorf("Workflow.Find error: %v", err) return e.ErrDeleteWorkflow.AddDesc(err.Error()) } - workflowCMMap, err := service.GetWorkflowCMMap([]string{workflow.ProductTmplName}, log) + workflowCMMap, err := collaboration.GetWorkflowCMMap([]string{workflow.ProductTmplName}, log) if err != nil { return err } - if cmSets, ok := workflowCMMap[service.BuildWorkflowCMMapKey(workflow.ProductTmplName, workflowName)]; ok { + if cmSets, ok := workflowCMMap[collaboration.BuildWorkflowCMMapKey(workflow.ProductTmplName, workflowName)]; ok { return fmt.Errorf("this is a base workflow, collaborations:%v is related", cmSets.List()) } taskQueue, err := mongodb.NewQueueColl().List(&mongodb.ListQueueOption{}) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7a15f6cd..5d9acb47 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,12 +46,12 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" @@ -195,7 +195,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) return nil, err } - envCMMap, err := service.GetEnvCMMap([]string{projectName}, log) + envCMMap, err := collaboration.GetEnvCMMap([]string{projectName}, log) if err != nil { return nil, err } @@ -212,7 +212,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) clusterName = cluster.Name } var baseRefs []string - if cmSet, ok := envCMMap[service.BuildEnvCMMapKey(env.ProductName, env.EnvName)]; ok { + if cmSet, ok := envCMMap[collaboration.BuildEnvCMMapKey(env.ProductName, env.EnvName)]; ok { for _, cm := range cmSet.List() { baseRefs = append(baseRefs, cm) } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index af12f27f..003d76cf 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -24,12 +24,12 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -48,7 +48,7 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { log.Errorf("[Product.List] error: %v", err) return } - envCMMap, err := service.GetEnvCMMap([]string{}, log) + envCMMap, err := collaboration.GetEnvCMMap([]string{}, log) if err != nil { return } @@ -62,7 +62,7 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { if product.RecycleDay == 0 { continue } - if _, ok := envCMMap[service.BuildEnvCMMapKey(product.ProductName, product.EnvName)]; ok { + if _, ok := envCMMap[collaboration.BuildEnvCMMapKey(product.ProductName, product.EnvName)]; ok { continue } if time.Now().Unix()-product.UpdateTime > int64(60*60*24*product.RecycleDay) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index d336cb0a..6d8c7d36 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -27,11 +27,11 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -518,7 +518,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] var workflowNames []string var res []*Workflow - workflowCMMap, err := service.GetWorkflowCMMap(projects, log) + workflowCMMap, err := collaboration.GetWorkflowCMMap(projects, log) if err != nil { return nil, err } @@ -537,7 +537,7 @@ func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([] stages = append(stages, "distribute") } var baseRefs []string - if cmSet, ok := workflowCMMap[service.BuildWorkflowCMMapKey(w.ProductTmplName, w.Name)]; ok { + if cmSet, ok := workflowCMMap[collaboration.BuildWorkflowCMMapKey(w.ProductTmplName, w.Name)]; ok { for _, cm := range cmSet.List() { baseRefs = append(baseRefs, cm) } -- Gitee From 2b3f8dab35bfc25c6b5b459878ef4e26e53fe6fc Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 14 Jan 2022 13:57:22 +0800 Subject: [PATCH 1282/2647] add data stat inerface Signed-off-by: liu deyi --- .../repository/mongodb/delivery_artifact.go | 12 + .../common/repository/mongodb/k8s_cluster.go | 12 + .../core/common/repository/mongodb/product.go | 12 + .../core/common/repository/mongodb/service.go | 11 +- .../repository/mongodb/template/product.go | 12 + .../common/repository/mongodb/workflow.go | 12 + .../core/stat/dashboard/handler/build.go | 47 +++ .../core/stat/dashboard/handler/deploy.go | 47 +++ .../core/stat/dashboard/handler/overview.go | 31 ++ .../core/stat/dashboard/handler/router.go | 33 ++ .../aslan/core/stat/dashboard/handler/test.go | 31 ++ .../stat/dashboard/repository/models/build.go | 51 +++ .../dashboard/repository/models/deploy.go | 46 +++ .../dashboard/repository/mongodb/build.go | 244 +++++++++++++ .../dashboard/repository/mongodb/deploy.go | 337 ++++++++++++++++++ .../core/stat/dashboard/service/build.go | 76 ++++ .../core/stat/dashboard/service/deploy.go | 78 ++++ .../core/stat/dashboard/service/overview.go | 79 ++++ .../aslan/core/stat/dashboard/service/test.go | 80 +++++ .../aslan/core/stat/overview/handler/build.go | 1 - .../core/stat/overview/handler/deploy.go | 1 - .../core/stat/overview/handler/overview.go | 1 - .../core/stat/overview/handler/router.go | 1 - .../aslan/core/stat/overview/handler/test.go | 1 - pkg/microservice/aslan/server/rest/router.go | 2 + 25 files changed, 1249 insertions(+), 9 deletions(-) create mode 100644 pkg/microservice/aslan/core/stat/dashboard/handler/build.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/handler/overview.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/handler/router.go create mode 100644 pkg/microservice/aslan/core/stat/dashboard/handler/test.go delete mode 100644 pkg/microservice/aslan/core/stat/overview/handler/build.go delete mode 100644 pkg/microservice/aslan/core/stat/overview/handler/deploy.go delete mode 100644 pkg/microservice/aslan/core/stat/overview/handler/overview.go delete mode 100644 pkg/microservice/aslan/core/stat/overview/handler/router.go delete mode 100644 pkg/microservice/aslan/core/stat/overview/handler/test.go diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index a7fb5ebc..2677db17 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -81,6 +81,18 @@ func (c *DeliveryArtifactColl) EnsureIndex(ctx context.Context) error { return err } +func (c *DeliveryArtifactColl) Count() (int64, error) { + query := bson.M{} + + ctx := context.Background() + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return 0, err + } + + return count, nil +} + func (c *DeliveryArtifactColl) List(args *DeliveryArtifactArgs) ([]*models.DeliveryArtifact, int, error) { if args == nil { return nil, 0, errors.New("nil delivery_artifact args") diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index fb046ecc..926eb912 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -119,6 +119,18 @@ func (c *K8SClusterColl) HasDuplicateName(id, name string) (bool, error) { return count > 0, nil } +func (c *K8SClusterColl) Count() (int64, error) { + query := bson.M{} + + ctx := context.Background() + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return 0, err + } + + return count, nil +} + func (c *K8SClusterColl) List(opts *ClusterListOpts) ([]*models.K8SCluster, error) { var clusters []*models.K8SCluster diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index eaefcbd8..75d8ba1f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -139,6 +139,18 @@ func (c *ProductColl) Find(opt *ProductFindOptions) (*models.Product, error) { return res, err } +func (c *ProductColl) EnvCount() (int64, error) { + query := bson.M{"status": bson.M{"$ne": setting.ProductStatusDeleting}} + + ctx := context.Background() + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return 0, err + } + + return count, nil +} + func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { var ret []*models.Product query := bson.M{} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 8cd0171a..7dedf953 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -485,12 +485,15 @@ func (c *ServiceColl) ListMaxRevisions(opt *ServiceListOption) ([]*models.Servic } func (c *ServiceColl) Count(productName string) (int, error) { + match := bson.M{ + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + } + if productName != "" { + match["product_name"] = productName + } pipeline := []bson.M{ { - "$match": bson.M{ - "product_name": productName, - "status": bson.M{"$ne": setting.ProductStatusDeleting}, - }, + "$match": match, }, { "$group": bson.M{ diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index ad624611..60d9b78d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -139,6 +139,18 @@ func (c *ProductColl) listProjects(inNames []string, projection bson.M) ([]*Proj return res, nil } +func (c *ProductColl) Count() (int64, error) { + query := bson.M{} + + ctx := context.Background() + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return 0, err + } + + return count, nil +} + func (c *ProductColl) List() ([]*template.Product, error) { var resp []*template.Product diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index f874a6fa..d6216b2b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -86,6 +86,18 @@ func (c *WorkflowColl) List(opt *ListWorkflowOption) ([]*models.Workflow, error) return resp, nil } +func (c *WorkflowColl) Count() (int64, error) { + query := bson.M{} + + ctx := context.Background() + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return 0, err + } + + return count, nil +} + func (c *WorkflowColl) ListByTestName(testName string) ([]*models.Workflow, error) { // 查询test_stage.tests中存在此testName 或者 test_stage.test_names存在此testName的工作流 match := []bson.M{ diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/build.go b/pkg/microservice/aslan/core/stat/dashboard/handler/build.go new file mode 100644 index 00000000..2ec851dd --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/build.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + e "github.com/koderover/zadig/pkg/tool/errors" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +type GetBuildStatArgs struct { + StartDate int64 `json:"startDate" form:"startDate,default:0"` + EndDate int64 `json:"endDate" form:"endDate,default:0"` +} + +func GetBuildStat(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(GetBuildStatArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Resp, ctx.Err = service.GetBuildTotalAndSuccess(&models.BuildStatOption{ + StartDate: args.StartDate, + EndDate: args.EndDate, + }, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go new file mode 100644 index 00000000..c22a149c --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go @@ -0,0 +1,47 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + e "github.com/koderover/zadig/pkg/tool/errors" + + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +type GetDeployStatArgs struct { + StartDate int64 `json:"startDate" form:"startDate,default:0"` + EndDate int64 `json:"endDate" form:"endDate,default:0"` +} + +func GetDeployStat(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(GetDeployStatArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Resp, ctx.Err = service.GetDeployDailyTotalAndSuccess(&models.DeployStatOption{ + StartDate: args.StartDate, + EndDate: args.EndDate, + }, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go b/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go new file mode 100644 index 00000000..d916e9b4 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetOverviewStat(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetOverviewStat(ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/router.go b/pkg/microservice/aslan/core/stat/dashboard/handler/router.go new file mode 100644 index 00000000..214e99e6 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/router.go @@ -0,0 +1,33 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + stat := router.Group("stat") + { + stat.GET("/overview", GetOverviewStat) + stat.GET("/build", GetBuildStat) + stat.GET("/deploy", GetDeployStat) + stat.GET("/test", GetTestDashboard) + } +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/test.go b/pkg/microservice/aslan/core/stat/dashboard/handler/test.go new file mode 100644 index 00000000..2c5b5a62 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/test.go @@ -0,0 +1,31 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetTestDashboard(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetTestDashboard(ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go b/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go index 2640e7f9..b1ca70d7 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go @@ -1 +1,52 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models + +type BuildStat struct { + ProductName string `bson:"product_name" json:"productName"` + TotalSuccess int `bson:"total_success" json:"totalSuccess"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` + TotalTimeout int `bson:"total_timeout" json:"totalTimeout"` + TotalDuration int64 `bson:"total_duration" json:"totalDuration"` + TotalBuildCount int `bson:"total_build_count" json:"totalBuildCount"` + MaxDuration int64 `bson:"max_duration" json:"maxDuration"` + MaxDurationPipeline *PipelineInfo `bson:"max_duration_pipeline" json:"maxDurationPipeline"` + Date string `bson:"date" json:"date"` + CreateTime int64 `bson:"create_time" json:"createTime"` + UpdateTime int64 `bson:"update_time" json:"updateTime"` +} + +// PipelineInfo +type PipelineInfo struct { + TaskID int64 `bson:"task_id" json:"taskId"` + PipelineName string `bson:"pipeline_name" json:"pipelineName"` + Type string `bson:"type" json:"type"` + MaxDuration int64 `bson:"max_duration" json:"maxDuration"` +} + +type BuildStatOption struct { + StartDate int64 + EndDate int64 + Limit int + Skip int + IsAsc bool + ProductNames []string +} + +func (BuildStat) TableName() string { + return "build_stat" +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go index 2640e7f9..be476cff 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go @@ -1 +1,47 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models + +type DeployStatOption struct { + StartDate int64 + EndDate int64 + Limit int + Skip int + IsAsc bool + IsMaxDeploy bool + ProductNames []string +} + +type DeployStat struct { + ProductName string `bson:"product_name" json:"productName"` + TotalTaskSuccess int `bson:"total_task_success" json:"totalTaskSuccess"` + TotalTaskFailure int `bson:"total_task_failure" json:"totalTaskFailure"` + TotalDeploySuccess int `bson:"total_deploy_success" json:"totalDeploySuccess"` + TotalDeployFailure int `bson:"total_deploy_failure" json:"totalDeployFailure"` + MaxDeployServiceNum int `bson:"max_deploy_service_num" json:"maxDeployServiceNum"` + MaxDeployServiceFailureNum int `bson:"max_deploy_service_failure_num" json:"maxDeployServiceFailureNum"` + MaxDeployServiceName string `bson:"max_deploy_service_name" json:"maxDeployServiceName"` + MaxDeployFailureServiceNum int `bson:"max_deploy_failure_service_num" json:"maxDeployFailureServiceNum"` + MaxDeployFailureServiceName string `bson:"max_deploy_failure_service_name" json:"maxDeployFailureServiceName"` + Date string `bson:"date" json:"date"` + CreateTime int64 `bson:"create_time" json:"createTime"` + UpdateTime int64 `bson:"update_time" json:"updateTime"` +} + +func (DeployStat) TableName() string { + return "deploy_stat" +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go index 6e598913..83d07e76 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go @@ -1 +1,245 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +// BuildPipeResp ... +type BuildPipeResp struct { + ID BuildItem `bson:"_id" json:"_id"` + TotalSuccess int `bson:"total_success" json:"total_success"` + TotalBuildCount int `bson:"total_build_count" json:"total_build_count"` +} + +// BuildItem ... +type BuildItem struct { + Date string `bson:"date" json:"date"` + TotalSuccess int `bson:"total_success" json:"total_success"` + TotalBuildCount int `bson:"total_build_count" json:"total_build_count"` +} + +type BuildDailyPipeResp struct { + ID BuildDailyItem `bson:"_id" json:"_id"` + TotalSuccess int `bson:"total_success" json:"total_success"` + TotalFailure int `bson:"total_failure" json:"total_failure"` + TotalBuildCount int `bson:"total_build_count" json:"total_build_count"` +} + +type BuildDailyItem struct { + Date string `bson:"date" json:"date"` + TotalSuccess int `bson:"total_success" json:"total_success"` + TotalFailure int `bson:"total_failure" json:"total_failure"` + TotalBuildCount int `bson:"total_build_count" json:"total_build_count"` +} + +type BuildStatColl struct { + *mongo.Collection + + coll string +} + +func NewBuildStatColl() *BuildStatColl { + name := models.BuildStat{}.TableName() + return &BuildStatColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *BuildStatColl) GetCollectionName() string { + return c.coll +} + +func (c *BuildStatColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "date", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *BuildStatColl) Create(args *models.BuildStat) error { + if args == nil { + return errors.New("nil buildStat args") + } + + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *BuildStatColl) Update(args *models.BuildStat) error { + if args == nil { + return errors.New("nil buildStat args") + } + + query := bson.M{"date": args.Date, "product_name": args.ProductName} + update := bson.M{"$set": args} + _, err := c.UpdateOne(context.TODO(), query, update) + return err +} + +func (c *BuildStatColl) FindCount() (int, error) { + count, err := c.EstimatedDocumentCount(context.TODO()) + if err != nil { + return 0, err + } + return int(count), nil +} + +func (c *BuildStatColl) ListBuildStat(option *models.BuildStatOption) ([]*models.BuildStat, error) { + resp := make([]*models.BuildStat, 0) + query := bson.M{} + + if len(option.ProductNames) > 0 { + query["product_name"] = bson.M{"$in": option.ProductNames} + } + + if option.StartDate > 0 { + query["create_time"] = bson.M{"$gte": option.StartDate, "$lte": option.EndDate} + } + + opt := &options.FindOptions{} + + if option.Limit > 0 { + opt.SetSort(bson.D{{"max_duration", -1}}).SetSkip(int64(option.Skip)).SetLimit(int64(option.Limit)) + } else if option.IsAsc { + opt.SetSort(bson.D{{"create_time", 1}}) + } else { + opt.SetSort(bson.D{{"create_time", -1}}) + } + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, err + } + + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *BuildStatColl) GetBuildTotalAndSuccess() ([]*BuildItem, error) { + var result []*BuildPipeResp + var pipeline []bson.M + var resp []*BuildItem + + pipeline = append(pipeline, + bson.M{ + "$group": bson.M{ + "_id": bson.M{ + "date": "$date", + }, + "total_success": bson.M{ + "$sum": "$total_success", + }, + "total_build_count": bson.M{ + "$sum": "$total_build_count", + }, + }, + }) + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &result); err != nil { + return nil, err + } + for _, res := range result { + buildItem := &BuildItem{ + TotalSuccess: res.TotalSuccess, + TotalBuildCount: res.TotalBuildCount, + } + resp = append(resp, buildItem) + } + + return resp, nil +} + +func (c *BuildStatColl) GetBuildDailyTotal(args *models.BuildStatOption) ([]*BuildDailyItem, error) { + var result []*BuildDailyPipeResp + var resp []*BuildDailyItem + + pipeline := []bson.M{{ + "$sort": bson.M{ + "create_time": 1, + }, + }} + + if args.StartDate > 0 || args.EndDate > 0 { + timeRange := bson.M{} + if args.StartDate > 0 { + timeRange["$gte"] = args.StartDate + } + if args.EndDate > 0 { + timeRange["$lte"] = args.EndDate + } + pipeline = append(pipeline, bson.M{"$match": bson.M{"create_time": timeRange}}) + } + + pipeline = append(pipeline, bson.M{ + "$group": bson.M{ + "_id": bson.M{ + "date": "$date", + }, + "total_success": bson.M{ + "$sum": "$total_success", + }, + "total_failure": bson.M{ + "$sum": "$total_failure", + }, + "total_build_count": bson.M{ + "$sum": "$total_build_count", + }, + }, + }) + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &result); err != nil { + return nil, err + } + for _, res := range result { + buildDailyItem := &BuildDailyItem{ + Date: res.ID.Date, + TotalSuccess: res.TotalSuccess, + TotalFailure: res.TotalFailure, + TotalBuildCount: res.TotalBuildCount, + } + resp = append(resp, buildDailyItem) + } + + return resp, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go index 6e598913..fe6988f7 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go @@ -1 +1,338 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type DeployTotalPipeResp struct { + ID DeployTotalItem `bson:"_id" json:"_id"` + TotalSuccess int `bson:"total_deploy_success" json:"total_deploy_success"` + TotalFailure int `bson:"total_deploy_failure" json:"total_deploy_failure"` +} + +type DeployTotalItem struct { + ProductName string `bson:"product_name" json:"product_name"` + TotalSuccess int `bson:"total_deploy_success" json:"total_deploy_success"` + TotalFailure int `bson:"total_deploy_failure" json:"total_deploy_failure"` +} + +type DeployDailyPipeResp struct { + ID DeployDailyItem `bson:"_id" json:"_id"` + TotalSuccess int `bson:"total_deploy_success" json:"total_deploy_success"` + TotalFailure int `bson:"total_deploy_failure" json:"total_deploy_failure"` +} + +type DeployDailyItem struct { + Date string `bson:"date" json:"date"` + TotalSuccess int `bson:"total_deploy_success" json:"total_deploy_success"` + TotalFailure int `bson:"total_deploy_failure" json:"total_deploy_failure"` +} + +type DeployPipeResp struct { + ID DeployPipeInfo `bson:"_id" json:"_id"` + MaxDeployServiceNum int `bson:"max_deploy_service_num" json:"maxDeployServiceNum"` +} + +type DeployPipeInfo struct { + MaxDeployServiceName string `bson:"max_deploy_service_name" json:"maxDeployServiceName"` +} + +type DeployFailurePipeResp struct { + ID DeployFailurePipeInfo `bson:"_id" json:"_id"` + MaxDeployFailureServiceNum int `bson:"max_deploy_failure_service_num" json:"maxDeployFailureServiceNum"` +} + +type DeployFailurePipeInfo struct { + ProductName string `bson:"product_name" json:"productName"` + MaxDeployFailureServiceName string `bson:"max_deploy_failure_service_name" json:"maxDeployFailureServiceName"` +} + +type DeployStatColl struct { + *mongo.Collection + + coll string +} + +func NewDeployStatColl() *DeployStatColl { + name := models.DeployStat{}.TableName() + return &DeployStatColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *DeployStatColl) GetCollectionName() string { + return c.coll +} + +func (c *DeployStatColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "date", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *DeployStatColl) Create(args *models.DeployStat) error { + if args == nil { + return errors.New("nil deployStat args") + } + + _, err := c.InsertOne(context.TODO(), args) + return err +} + +type DeployStatGetOption struct { + MaxDeployServiceNum int + ServiceName string +} + +func (c *DeployStatColl) Get(args *DeployStatGetOption) (*models.DeployStat, error) { + ret := new(models.DeployStat) + query := bson.M{"max_deploy_service_num": args.MaxDeployServiceNum, "max_deploy_service_name": args.ServiceName} + err := c.FindOne(context.TODO(), query).Decode(ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (c *DeployStatColl) Update(args *models.DeployStat) error { + if args == nil { + return errors.New("nil deployStat args") + } + + query := bson.M{ + "date": args.Date, + "product_name": args.ProductName, + } + update := bson.M{"$set": args} + _, err := c.UpdateOne(context.TODO(), query, update) + return err +} + +func (c *DeployStatColl) FindCount() (int, error) { + count, err := c.CountDocuments(context.TODO(), bson.M{}) + return int(count), err +} + +func (c *DeployStatColl) GetDeployTotalAndSuccess() ([]*DeployTotalItem, error) { + var result []*DeployTotalPipeResp + var resp []*DeployTotalItem + + pipeline := []bson.M{{ + "$group": bson.M{ + "_id": bson.M{ + "product_name": "$product_name", + }, + "total_deploy_success": bson.M{ + "$sum": "$total_deploy_success", + }, + "total_deploy_failure": bson.M{ + "$sum": "$total_deploy_failure", + }, + }, + }} + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &result); err != nil { + return nil, err + } + for _, res := range result { + deployItem := &DeployTotalItem{ + ProductName: res.ID.ProductName, + TotalSuccess: res.TotalSuccess, + TotalFailure: res.TotalFailure, + } + resp = append(resp, deployItem) + } + + return resp, nil +} + +func (c *DeployStatColl) ListDeployStat(option *models.DeployStatOption) ([]*models.DeployStat, error) { + ret := make([]*models.DeployStat, 0) + query := bson.M{} + if option.Limit > 0 { + var deployStatQuery []bson.M + if len(option.ProductNames) > 0 { + query["product_name"] = bson.M{"$in": option.ProductNames} + } + if option.StartDate > 0 { + query["create_time"] = bson.M{ + "$gte": option.StartDate, + "$lte": option.EndDate, + } + } + deployStatQuery = append(deployStatQuery, bson.M{"$match": query}) + + if option.IsMaxDeploy { + var results []DeployPipeResp + deployStatQuery = append(deployStatQuery, + bson.M{"$group": bson.M{ + "_id": bson.M{"max_deploy_service_name": "$max_deploy_service_name"}, + "max_deploy_service_num": bson.M{"$max": "$max_deploy_service_num"}}, + }) + deployStatQuery = append(deployStatQuery, bson.M{"$sort": bson.M{"max_deploy_service_num": -1}}) + deployStatQuery = append(deployStatQuery, bson.M{"$limit": 5}) + deployStatQuery = append(deployStatQuery, bson.M{"$skip": 0}) + + cursor, err := c.Aggregate(context.TODO(), deployStatQuery) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &results); err != nil { + return nil, err + } + for _, result := range results { + deployItem := &models.DeployStat{ + MaxDeployServiceName: result.ID.MaxDeployServiceName, + MaxDeployServiceNum: result.MaxDeployServiceNum, + } + ret = append(ret, deployItem) + } + } else { + var results []DeployFailurePipeResp + + deployStatQuery = append(deployStatQuery, + bson.M{"$group": bson.M{ + "_id": bson.M{ + "max_deploy_failure_service_name": "$max_deploy_failure_service_name", + "product_name": "$product_name", + }, + "max_deploy_failure_service_num": bson.M{ + "$max": "$max_deploy_failure_service_num", + }, + }}) + deployStatQuery = append(deployStatQuery, bson.M{"$sort": bson.M{"max_deploy_failure_service_num": -1}}) + deployStatQuery = append(deployStatQuery, bson.M{"$limit": 5}) + deployStatQuery = append(deployStatQuery, bson.M{"$skip": 0}) + cursor, err := c.Aggregate(context.TODO(), deployStatQuery) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &results); err != nil { + return nil, err + } + for _, result := range results { + deployItem := &models.DeployStat{ + ProductName: result.ID.ProductName, + MaxDeployFailureServiceName: result.ID.MaxDeployFailureServiceName, + MaxDeployFailureServiceNum: result.MaxDeployFailureServiceNum, + } + ret = append(ret, deployItem) + } + } + return ret, nil + } + + if len(option.ProductNames) > 0 { + query["product_name"] = bson.M{"$in": option.ProductNames} + } + + if option.StartDate > 0 { + query["create_time"] = bson.M{"$gte": option.StartDate, "$lte": option.EndDate} + } + + opt := &options.FindOptions{} + if option.IsAsc { + opt.SetSort(bson.D{{"create_time", 1}}) + } else { + opt.SetSort(bson.D{{"create_time", -1}}) + } + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (c *DeployStatColl) GetDeployDailyTotal(args *models.DeployStatOption) ([]*DeployDailyItem, error) { + var result []*DeployDailyPipeResp + var resp []*DeployDailyItem + + pipeline := []bson.M{{ + "$sort": bson.M{ + "create_time": 1, + }, + }} + + if args.StartDate > 0 || args.EndDate > 0 { + timeRange := bson.M{} + if args.StartDate > 0 { + timeRange["$gte"] = args.StartDate + } + if args.EndDate > 0 { + timeRange["$lte"] = args.EndDate + } + pipeline = append(pipeline, bson.M{"$match": bson.M{"create_time": timeRange}}) + } + + pipeline = append(pipeline, bson.M{ + "$group": bson.M{ + "_id": bson.M{ + "date": "$date", + }, + "total_deploy_success": bson.M{ + "$sum": "$total_deploy_success", + }, + "total_deploy_failure": bson.M{ + "$sum": "$total_deploy_failure", + }, + }, + }) + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &result); err != nil { + return nil, err + } + for _, res := range result { + deployDailyItem := &DeployDailyItem{ + Date: res.ID.Date, + TotalSuccess: res.TotalSuccess, + TotalFailure: res.TotalFailure, + } + resp = append(resp, deployDailyItem) + } + + return resp, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/build.go b/pkg/microservice/aslan/core/stat/dashboard/service/build.go index 6d43c336..219e98c0 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/build.go @@ -1 +1,77 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service + +import ( + "sort" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + "go.uber.org/zap" + + repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb" +) + +type dashboardBuild struct { + Total int `json:"total"` + Success int `json:"success"` + DashboardBuildDailys []*dashboardBuildDaily `json:"data,omitempty"` +} + +type dashboardBuildDaily struct { + Date string `json:"date"` + Success int `json:"success"` + Failure int `json:"failure"` + Total int `json:"total"` +} + +func GetBuildTotalAndSuccess(args *models.BuildStatOption, log *zap.SugaredLogger) (*dashboardBuild, error) { + var ( + dashboardBuild = new(dashboardBuild) + dashboardBuildDailys = make([]*dashboardBuildDaily, 0) + total = 0 + success = 0 + ) + if buildItems, err := repo.NewBuildStatColl().GetBuildTotalAndSuccess(); err == nil { + for _, buildItem := range buildItems { + success += buildItem.TotalSuccess + total += buildItem.TotalBuildCount + } + dashboardBuild.Success = success + dashboardBuild.Total = total + } else { + log.Errorf("Failed to getBuildTotalAndSuccess err:%s", err) + return nil, err + } + + if buildDailyItems, err := repo.NewBuildStatColl().GetBuildDailyTotal(args); err == nil { + sort.SliceStable(buildDailyItems, func(i, j int) bool { return buildDailyItems[i].Date < buildDailyItems[j].Date }) + for _, buildDailyItem := range buildDailyItems { + dashboardBuildDaily := new(dashboardBuildDaily) + dashboardBuildDaily.Date = buildDailyItem.Date + dashboardBuildDaily.Success = buildDailyItem.TotalSuccess + dashboardBuildDaily.Failure = buildDailyItem.TotalFailure + dashboardBuildDaily.Total = buildDailyItem.TotalBuildCount + + dashboardBuildDailys = append(dashboardBuildDailys, dashboardBuildDaily) + } + dashboardBuild.DashboardBuildDailys = dashboardBuildDailys + } else { + log.Errorf("Failed to getDeployDailyTotal err:%s", err) + return nil, err + } + return dashboardBuild, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go index 6d43c336..2c5fe045 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go @@ -1 +1,79 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service + +import ( + "sort" + + "go.uber.org/zap" + + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb" +) + +type dashboardDeploy struct { + Total int `json:"total"` + Success int `json:"success"` + DashboardDeployDailys []*dashboardDeployDaily `json:"data,omitempty"` +} + +type dashboardDeployDaily struct { + Date string `json:"date"` + Success int `json:"success"` + Failure int `json:"failure"` + Total int `json:"total"` +} + +func GetDeployDailyTotalAndSuccess(args *models.DeployStatOption, log *zap.SugaredLogger) (*dashboardDeploy, error) { + var ( + dashboardDeploy = new(dashboardDeploy) + dashboardDeployDailys = make([]*dashboardDeployDaily, 0) + failure int + success int + ) + + if deployItems, err := repo.NewDeployStatColl().GetDeployTotalAndSuccess(); err == nil { + for _, deployItem := range deployItems { + success += deployItem.TotalSuccess + failure += deployItem.TotalFailure + } + dashboardDeploy.Total = success + failure + dashboardDeploy.Success = success + } else { + log.Errorf("Failed to getDeployTotalAndSuccess err:%s", err) + return nil, err + } + + if deployDailyItems, err := repo.NewDeployStatColl().GetDeployDailyTotal(args); err == nil { + sort.SliceStable(deployDailyItems, func(i, j int) bool { return deployDailyItems[i].Date < deployDailyItems[j].Date }) + for _, deployDailyItem := range deployDailyItems { + dashboardDeployDaily := new(dashboardDeployDaily) + dashboardDeployDaily.Date = deployDailyItem.Date + dashboardDeployDaily.Success = deployDailyItem.TotalSuccess + dashboardDeployDaily.Failure = deployDailyItem.TotalFailure + dashboardDeployDaily.Total = deployDailyItem.TotalFailure + deployDailyItem.TotalSuccess + + dashboardDeployDailys = append(dashboardDeployDailys, dashboardDeployDaily) + } + dashboardDeploy.DashboardDeployDailys = dashboardDeployDailys + } else { + log.Errorf("Failed to getDeployDailyTotal err:%s", err) + return nil, err + } + + return dashboardDeploy, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/overview.go b/pkg/microservice/aslan/core/stat/dashboard/service/overview.go index 6d43c336..5cb289b5 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/overview.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/overview.go @@ -1 +1,80 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service + +import ( + "go.uber.org/zap" + + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" +) + +type Overview struct { + ProjectCount int `json:"project_count"` + ClusterCount int `json:"cluster_count"` + ServiceCount int `json:"service_count"` + WorkflowCount int `json:"workflow_count"` + EnvCount int `json:"env_count"` + ArtifactCount int `json:"artifact_count"` +} + +func GetOverviewStat(log *zap.SugaredLogger) (*Overview, error) { + overview := new(Overview) + projectCount, err := templaterepo.NewProductColl().Count() + if err != nil { + log.Errorf("Failed to get project count err:%s", err) + return nil, err + } + overview.ProjectCount = int(projectCount) + + clusterCount, err := commonrepo.NewK8SClusterColl().Count() + if err != nil { + log.Errorf("Failed to get cluster count err:%s", err) + return nil, err + } + overview.ClusterCount = int(clusterCount) + + workflowCount, err := commonrepo.NewWorkflowColl().Count() + if err != nil { + log.Errorf("Failed to get workflow count err:%s", err) + return nil, err + } + overview.WorkflowCount = int(workflowCount) + + envCount, err := commonrepo.NewProductColl().EnvCount() + if err != nil { + log.Errorf("Failed to get env count err:%s", err) + return nil, err + } + overview.EnvCount = int(envCount) + + artifactCount, err := commonrepo.NewDeliveryArtifactColl().Count() + if err != nil { + log.Errorf("Failed to get artifact count err:%s", err) + return nil, err + } + overview.ArtifactCount = int(artifactCount) + + serviceCount, err := commonrepo.NewServiceColl().Count("") + if err != nil { + log.Errorf("Failed to get service count err:%s", err) + return nil, err + } + overview.ServiceCount = int(serviceCount) + + return nil, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/test.go b/pkg/microservice/aslan/core/stat/dashboard/service/test.go index 6d43c336..a104dbde 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/test.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/test.go @@ -1 +1,81 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service + +import ( + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" +) + +type testDashboard struct { + TotalCaseCount int `json:"total_case_count"` + TotalExecCount int `json:"total_exec_count"` + Success int `json:"success"` + AverageDuration int64 `json:"average_curation"` +} + +func GetTestDashboard(log *zap.SugaredLogger) (*testDashboard, error) { + var ( + testDashboard = new(testDashboard) + existTestTasks = make([]*models.TestTaskStat, 0) + totalCaseCount = 0 + totalSuccess = 0 + totalFailure = 0 + totalDuration int64 + ) + + tests, err := commonrepo.NewTestingColl().List(&commonrepo.ListTestOption{}) + if err != nil { + log.Errorf("Failed to list testing err:%s", err) + return nil, err + } + + testNames := sets.String{} + for _, test := range tests { + testNames.Insert(test.Name) + } + testTasks, err := commonrepo.NewTestTaskStatColl().ListTestTask() + if err != nil { + log.Errorf("Failed to list TestTaskStat err:%s", err) + return nil, err + } + for _, testTask := range testTasks { + if testNames.Has(testTask.Name) { + existTestTasks = append(existTestTasks, testTask) + } + } + + for _, existTestTask := range existTestTasks { + totalCaseCount += existTestTask.TestCaseNum + totalSuccess += existTestTask.TotalSuccess + totalFailure += existTestTask.TotalFailure + totalDuration += existTestTask.TotalDuration + } + testDashboard.TotalCaseCount = totalCaseCount + testDashboard.TotalExecCount = totalSuccess + totalFailure + testDashboard.Success = totalSuccess + if testDashboard.TotalExecCount == 0 { + testDashboard.AverageDuration = 0 + return testDashboard, nil + } + testDashboard.AverageDuration = totalDuration / int64(testDashboard.TotalExecCount) + + return testDashboard, nil +} diff --git a/pkg/microservice/aslan/core/stat/overview/handler/build.go b/pkg/microservice/aslan/core/stat/overview/handler/build.go deleted file mode 100644 index abeebd16..00000000 --- a/pkg/microservice/aslan/core/stat/overview/handler/build.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/deploy.go b/pkg/microservice/aslan/core/stat/overview/handler/deploy.go deleted file mode 100644 index abeebd16..00000000 --- a/pkg/microservice/aslan/core/stat/overview/handler/deploy.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/overview.go b/pkg/microservice/aslan/core/stat/overview/handler/overview.go deleted file mode 100644 index abeebd16..00000000 --- a/pkg/microservice/aslan/core/stat/overview/handler/overview.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/router.go b/pkg/microservice/aslan/core/stat/overview/handler/router.go deleted file mode 100644 index abeebd16..00000000 --- a/pkg/microservice/aslan/core/stat/overview/handler/router.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/pkg/microservice/aslan/core/stat/overview/handler/test.go b/pkg/microservice/aslan/core/stat/overview/handler/test.go deleted file mode 100644 index abeebd16..00000000 --- a/pkg/microservice/aslan/core/stat/overview/handler/test.go +++ /dev/null @@ -1 +0,0 @@ -package handler diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 77d68e65..8459a7fe 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -32,6 +32,7 @@ import ( projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" servicehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/service/handler" settinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/setting/handler" + dashboardhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/handler" systemhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/system/handler" templatehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/handler" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" @@ -84,6 +85,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/testing": new(testinghandler.Router), "/api/cluster": new(multiclusterhandler.Router), "/api/template": new(templatehandler.Router), + "/api/stat": new(dashboardhandler.Router), } { r.Inject(router.Group(name)) } -- Gitee From e4e1bc274a6a540be329780a4db9a4106ce8d475 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 14:00:13 +0800 Subject: [PATCH 1283/2647] bulk-create labels Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 30 +++++++++++++------ .../aslan/core/label/handler/labelsbinding.go | 6 ++-- .../aslan/core/label/handler/router.go | 10 +++---- .../core/label/repository/mongodb/label.go | 17 +++++++++-- .../aslan/core/label/service/label.go | 4 +-- 5 files changed, 45 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index c3bfa262..c3d497b7 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -17,9 +17,11 @@ limitations under the License. package handler import ( + "fmt" "strconv" "github.com/gin-gonic/gin" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/dto" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" @@ -45,29 +47,39 @@ func ListLabels(c *gin.Context) { ctx.Resp, ctx.Err = service.ListLabels(listLabelsArgs.Key, listLabelsArgs.Values, listLabelsArgs.LabelType) } -func createLabelValidate(lb *models.Label) error { - if lb.Key == "" || lb.Value == "" { - return e.ErrInvalidParam.AddDesc("invalid label args") +func createLabelValidate(lbs []*models.Label) error { + keyValues := sets.NewString() + for _, v := range lbs { + if v.Key == "" || v.Value == "" { + return e.ErrInvalidParam.AddDesc("invalid label args") + } + keyValue := fmt.Sprintf("%s-%s", v.Key, v.Value) + if keyValues.Has(keyValue) { + return e.ErrInvalidParam.AddDesc(fmt.Sprintf("duplicate key-value:%s", keyValue)) + } + keyValues.Insert(keyValue) } return nil } -func CreateLabel(c *gin.Context) { +func CreateLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - label := new(models.Label) - if err := c.ShouldBindJSON(label); err != nil { + labels := make([]*models.Label, 0) + if err := c.ShouldBindJSON(&labels); err != nil { ctx.Err = err return } - if err := createLabelValidate(label); err != nil { + if err := createLabelValidate(labels); err != nil { ctx.Err = err return } - label.CreateBy = ctx.UserName - ctx.Err = service.CreateLabel(label) + for _, v := range labels { + v.CreateBy = ctx.UserName + } + ctx.Err = service.CreateLabels(labels) } func DeleteLabel(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/labelsbinding.go index 1275534d..494080ed 100644 --- a/pkg/microservice/aslan/core/label/handler/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/handler/labelsbinding.go @@ -25,7 +25,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListLabelsBinding(c *gin.Context) { +func ListLabelBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -39,7 +39,7 @@ func createLBValidate(lb *models.LabelBinding) error { return nil } -func CreateLabelsBinding(c *gin.Context) { +func CreateLabelBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -55,7 +55,7 @@ func CreateLabelsBinding(c *gin.Context) { ctx.Err = service.CreateLabelsBinding(labelBinding, ctx.UserName, ctx.Logger) } -func DeleteLabelsBinding(c *gin.Context) { +func DeleteLabelBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() id := c.Param("id") diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index c945ad44..9209fed9 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -26,15 +26,15 @@ func (*Router) Inject(router *gin.RouterGroup) { labels := router.Group("labels") { labels.GET("", ListLabels) - labels.POST("", CreateLabel) - labels.DELETE(":id", DeleteLabel) + labels.POST("/bulk-create", CreateLabels) + labels.DELETE("/:id", DeleteLabel) labels.POST("/resourcebylabels", ListResourceByLabels) labels.POST("/labelsbyresource", ListLabelsByResource) } labelBindings := router.Group("labelbindings") { - labelBindings.GET("", ListLabelsBinding) - labelBindings.POST("", CreateLabelsBinding) - labelBindings.DELETE(":id", DeleteLabelsBinding) + labelBindings.GET("", ListLabelBindings) + labelBindings.POST("", CreateLabelBindings) + labelBindings.DELETE("/bulk-delete", DeleteLabelBindings) } } diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index ab216a19..ac6b6302 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -58,9 +58,20 @@ func (c *LabelColl) EnsureIndex(ctx context.Context) error { return err } -func (c *LabelColl) Create(args *models.Label) error { - args.CreateTime = time.Now().Unix() - _, err := c.InsertOne(context.TODO(), args) +func (c *LabelColl) BulkCreate(labels []*models.Label) error { + if len(labels) == 0 { + return nil + } + for _, label := range labels { + label.CreateTime = time.Now().Unix() + } + + var ois []interface{} + for _, obj := range labels { + ois = append(ois, obj) + } + + _, err := c.InsertMany(context.TODO(), ois) return err } diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index d04d8580..d558cfa4 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -25,8 +25,8 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func CreateLabel(label *models.Label) error { - return mongodb.NewLabelColl().Create(label) +func CreateLabels(labels []*models.Label) error { + return mongodb.NewLabelColl().BulkCreate(labels) } func ListLabels(key string, values []string, labelType string) ([]*models.Label, error) { -- Gitee From 2d78e4857b986710ee903bfd59c1f43998fbbd87 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 14:24:03 +0800 Subject: [PATCH 1284/2647] bulk-create labelbings Signed-off-by: mouuii --- .../core/common/repository/mongodb/product.go | 10 +---- .../common/repository/mongodb/workflow.go | 13 +----- .../aslan/core/label/handler/labelsbinding.go | 14 +++--- .../label/repository/mongodb/labelbinding.go | 16 +++++-- .../aslan/core/label/service/labelsbinding.go | 45 +++++++++++++++---- 5 files changed, 58 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 152f0a92..26a6765c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -23,7 +23,6 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -52,6 +51,7 @@ type ProductListOptions struct { Source string InProjects []string InEnvs []string + InIDs []string } type projectEnvs struct { @@ -106,7 +106,6 @@ func (c *ProductColl) EnsureIndex(ctx context.Context) error { type ProductEnvFindOptions struct { Name string Namespace string - ID string } func (c *ProductColl) FindEnv(opt *ProductEnvFindOptions) (*models.Product, error) { @@ -118,13 +117,6 @@ func (c *ProductColl) FindEnv(opt *ProductEnvFindOptions) (*models.Product, erro if opt.Namespace != "" { query["namespace"] = opt.Namespace } - if opt.ID != "" { - oid, err := primitive.ObjectIDFromHex(opt.ID) - if err != nil { - return nil, err - } - query["_id"] = oid - } ret := new(models.Product) err := c.FindOne(context.TODO(), query).Decode(ret) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index ec4fa9fb..2bd56df9 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -22,7 +22,6 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -35,6 +34,7 @@ type ListWorkflowOption struct { IsSort bool Projects []string Names []string + Ids []string } type WorkflowColl struct { @@ -129,17 +129,6 @@ func (c *WorkflowColl) Find(name string) (*models.Workflow, error) { return res, err } -func (c *WorkflowColl) FindByID(id string) (*models.Workflow, error) { - res := &models.Workflow{} - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return nil, err - } - query := bson.M{"_id": oid} - err = c.FindOne(context.TODO(), query).Decode(res) - return res, err -} - func (c *WorkflowColl) Delete(name string) error { query := bson.M{"name": name} diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/labelsbinding.go index 494080ed..9baa8954 100644 --- a/pkg/microservice/aslan/core/label/handler/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/handler/labelsbinding.go @@ -19,7 +19,6 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -32,8 +31,8 @@ func ListLabelBindings(c *gin.Context) { ctx.Resp, ctx.Err = service.ListLabelsBinding() } -func createLBValidate(lb *models.LabelBinding) error { - if lb.LabelID == "" || lb.ResourceType == "" || lb.ResourceID == "" { +func createLBValidate(cr *service.CreateLabelBindingsArgs) error { + if cr.LabelID == "" || cr.ResourceType == "" || len(cr.ResourceIDs) == 0 { return e.ErrInvalidParam.AddDesc("invalid labelbinding args") } return nil @@ -43,16 +42,17 @@ func CreateLabelBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - labelBinding := new(models.LabelBinding) - if err := c.ShouldBindJSON(labelBinding); err != nil { + createLabelBindingsArgs := new(service.CreateLabelBindingsArgs) + if err := c.ShouldBindJSON(createLabelBindingsArgs); err != nil { ctx.Err = err return } - if err := createLBValidate(labelBinding); err != nil { + if err := createLBValidate(createLabelBindingsArgs); err != nil { ctx.Err = err return } - ctx.Err = service.CreateLabelsBinding(labelBinding, ctx.UserName, ctx.Logger) + createLabelBindingsArgs.CreateBy = ctx.UserName + ctx.Err = service.CreateLabelsBinding(createLabelBindingsArgs, ctx.Logger) } func DeleteLabelBindings(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index 585fe8c0..083bb8a0 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -59,9 +59,19 @@ func (c *LabelBindingColl) EnsureIndex(ctx context.Context) error { return err } -func (c *LabelBindingColl) Create(args *models.LabelBinding) error { - args.CreateTime = time.Now().Unix() - _, err := c.InsertOne(context.TODO(), args) +func (c *LabelBindingColl) CreateMany(labelBindings []*models.LabelBinding) error { + if len(labelBindings) == 0 { + return nil + } + for _, labelBinding := range labelBindings { + labelBinding.CreateTime = time.Now().Unix() + } + + var ois []interface{} + for _, obj := range labelBindings { + ois = append(ois, obj) + } + _, err := c.InsertMany(context.TODO(), ois) return err } diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 5e85ba2b..fe436f5d 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -23,38 +23,65 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" ) func ListLabelsBinding() ([]*models.LabelBinding, error) { return mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{}) } -func CreateLabelsBinding(binding *models.LabelBinding, userName string, logger *zap.SugaredLogger) error { - // check label exist +type CreateLabelBindingsArgs struct { + ResourceType string `bson:"resource_type" json:"resource_type"` + ResourceIDs []string `bson:"resource_ids" json:"resource_ids"` + LabelID string `bson:"label_id" json:"label_id"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` +} - if _, err := mongodb.NewLabelColl().Find(binding.LabelID); err != nil { +func CreateLabelsBinding(cr *CreateLabelBindingsArgs, logger *zap.SugaredLogger) error { + // check label exist + if _, err := mongodb.NewLabelColl().Find(cr.LabelID); err != nil { logger.Errorf("find label err:%s", err) return err } //check resource exist - switch binding.ResourceType { + switch cr.ResourceType { case string(config.ResourceTypeWorkflow): - _, err := commondb.NewWorkflowColl().FindByID(binding.ResourceID) + wks, err := commondb.NewWorkflowColl().List(&commondb.ListWorkflowOption{ + Ids: cr.ResourceIDs, + }) if err != nil { logger.Errorf("can not find related resource err:%s", err) return err } + if len(wks) != len(cr.ResourceIDs) { + logger.Errorf("can not find related resource err:%s", err) + return e.ErrForbidden.AddDesc("can not find related resource") + } case string(config.ResourceTypeProduct): - _, err := commondb.NewProductColl().FindEnv(&commondb.ProductEnvFindOptions{ - ID: binding.ResourceID, + prs, err := commondb.NewProductColl().List(&commondb.ProductListOptions{ + InIDs: cr.ResourceIDs, }) if err != nil { logger.Errorf("can not find related resource err:%s", err) return err } + if len(prs) != len(cr.ResourceIDs) { + logger.Errorf("can not find related resource err:%s", err) + return e.ErrForbidden.AddDesc("can not find related resource") + } + } + var labelBindings []*models.LabelBinding + for _, v := range cr.ResourceIDs { + labelBinding := &models.LabelBinding{ + ResourceType: cr.ResourceType, + ResourceID: v, + LabelID: cr.LabelID, + CreateBy: cr.CreateBy, + } + labelBindings = append(labelBindings, labelBinding) } - binding.CreateBy = userName - return mongodb.NewLabelBindingColl().Create(binding) + return mongodb.NewLabelBindingColl().CreateMany(labelBindings) } func DeleteLabelsBinding(id string) error { -- Gitee From ea6e0ab8bd9083821e1348bf57ce60dc8ae924ac Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 14:29:47 +0800 Subject: [PATCH 1285/2647] bulk-create labelbings Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/product.go | 12 ++++++++++++ .../aslan/core/common/repository/mongodb/workflow.go | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 26a6765c..65f36269 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -23,6 +23,7 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -173,6 +174,17 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { if len(opt.InProjects) > 0 { query["product_name"] = bson.M{"$in": opt.InProjects} } + if len(opt.InIDs) > 0 { + var oids []primitive.ObjectID + for _, id := range opt.InIDs { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + oids = append(oids, oid) + } + query["_id"] = bson.M{"$in": oids} + } ctx := context.Background() opts := options.Find() diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index 2bd56df9..5837b067 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -22,6 +22,7 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -71,6 +72,17 @@ func (c *WorkflowColl) List(opt *ListWorkflowOption) ([]*models.Workflow, error) if len(opt.Names) > 0 { query["name"] = bson.M{"$in": opt.Names} } + if len(opt.Ids) > 0 { + var oids []primitive.ObjectID + for _, id := range opt.Ids { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + oids = append(oids, oid) + } + query["_id"] = bson.M{"$in": opt.Ids} + } resp := make([]*models.Workflow, 0) ctx := context.Background() -- Gitee From 5815292198c6650a80c333c8c6358a4029102d8d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 15:31:33 +0800 Subject: [PATCH 1286/2647] bulk-create labelbings Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 18 ++++-- .../aslan/core/label/handler/labelsbinding.go | 10 +-- .../aslan/core/label/handler/router.go | 3 +- .../aslan/core/label/repository/dto/label.go | 39 ----------- .../core/label/repository/mongodb/label.go | 18 ++++++ .../label/repository/mongodb/labelbinding.go | 15 ++--- .../aslan/core/label/service/label.go | 64 ++++++++++++++++--- .../aslan/core/label/service/labelsbinding.go | 8 ++- 8 files changed, 102 insertions(+), 73 deletions(-) delete mode 100644 pkg/microservice/aslan/core/label/repository/dto/label.go diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index c3d497b7..342280a2 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -23,7 +23,6 @@ import ( "github.com/gin-gonic/gin" "k8s.io/apimachinery/pkg/util/sets" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/dto" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" @@ -82,6 +81,17 @@ func CreateLabels(c *gin.Context) { ctx.Err = service.CreateLabels(labels) } +//DeleteLabels can only bulk delete labels which not bind reousrces +func DeleteLabels(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + deleteLabelsArgs := new(service.DeleteLabelsArgs) + if err := c.ShouldBindJSON(deleteLabelsArgs); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("json bind fail") + } + ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, ctx.Logger) +} + func DeleteLabel(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -96,14 +106,10 @@ func DeleteLabel(c *gin.Context) { ctx.Err = service.DeleteLabel(id, forceBool, ctx.Logger) } -type ListResourceByLabelsReq struct { - LabelFilters []dto.LabelFilter `json:"label_filters"` -} - func ListResourceByLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - listResourceByLabelsReq := new(ListResourceByLabelsReq) + listResourceByLabelsReq := new(service.ListResourceByLabelsReq) if err := c.ShouldBindJSON(listResourceByLabelsReq); err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/labelsbinding.go index 9baa8954..9e7a9413 100644 --- a/pkg/microservice/aslan/core/label/handler/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/handler/labelsbinding.go @@ -58,10 +58,12 @@ func CreateLabelBindings(c *gin.Context) { func DeleteLabelBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - id := c.Param("id") - if id == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("id must not be empty") + + deleteLabelsBindingsArgs := new(service.DeleteLabelsBindingsArgs) + if err := c.ShouldBindJSON(deleteLabelsBindingsArgs); err != nil { + ctx.Err = err + return } - ctx.Err = service.DeleteLabelsBinding(id) + ctx.Err = service.DeleteLabelsBindings(deleteLabelsBindingsArgs.IDs) } diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 9209fed9..68059b91 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -28,6 +28,7 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.GET("", ListLabels) labels.POST("/bulk-create", CreateLabels) labels.DELETE("/:id", DeleteLabel) + labels.POST("/bulk-delete", DeleteLabels) labels.POST("/resourcebylabels", ListResourceByLabels) labels.POST("/labelsbyresource", ListLabelsByResource) } @@ -35,6 +36,6 @@ func (*Router) Inject(router *gin.RouterGroup) { { labelBindings.GET("", ListLabelBindings) labelBindings.POST("", CreateLabelBindings) - labelBindings.DELETE("/bulk-delete", DeleteLabelBindings) + labelBindings.POST("/bulk-delete", DeleteLabelBindings) } } diff --git a/pkg/microservice/aslan/core/label/repository/dto/label.go b/pkg/microservice/aslan/core/label/repository/dto/label.go deleted file mode 100644 index c9541291..00000000 --- a/pkg/microservice/aslan/core/label/repository/dto/label.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package dto - -type LabelFilter struct { - Key string `json:"key"` - Values []string `json:"values"` -} - -type ResourceLabel struct { - ResourceID string `json:"resource_id"` - ResourceType string `json:"resource_type"` - Labels []Label `json:"labels"` -} - -type Label struct { - Key string `json:"key"` - Value string `json:"value"` -} - -type LabelResource struct { - Key string `json:"key"` - Value string `json:"value"` - ResourceID string `json:"resource_id"` -} diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index ac6b6302..5524b6de 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -85,6 +85,24 @@ func (c *LabelColl) Delete(id string) error { return err } +func (c *LabelColl) BulkDelete(ids []string) error { + if len(ids) == 0 { + return nil + } + var oids []primitive.ObjectID + for _, v := range ids { + oid, err := primitive.ObjectIDFromHex(v) + if err != nil { + return err + } + oids = append(oids, oid) + } + query := bson.M{} + query["_id"] = bson.M{"$in": oids} + _, err := c.DeleteMany(context.TODO(), query) + return err +} + func (c *LabelColl) Find(id string) (*models.Label, error) { res := new(models.Label) oid, err := primitive.ObjectIDFromHex(id) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index 083bb8a0..cb68fc57 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -121,17 +121,10 @@ func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.La return ret, err } -func (c *LabelBindingColl) Delete(id string) error { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return err +func (c *LabelBindingColl) BulkDelete(ids []string) error { + if len(ids) == 0 { + return nil } - query := bson.M{"_id": oid} - _, err = c.DeleteOne(context.TODO(), query) - return err -} - -func (c *LabelBindingColl) DeleteMany(ids []string) error { query := bson.M{} var oids []primitive.ObjectID for _, id := range ids { @@ -143,6 +136,6 @@ func (c *LabelBindingColl) DeleteMany(ids []string) error { } query["_id"] = bson.M{"$in": oids} - _, err := c.DeleteOne(context.TODO(), query) + _, err := c.DeleteMany(context.TODO(), query) return err } diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index d558cfa4..30bd35b3 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -19,12 +19,33 @@ package service import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/dto" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" e "github.com/koderover/zadig/pkg/tool/errors" ) +type LabelFilter struct { + Key string `json:"key"` + Values []string `json:"values"` +} + +type ResourceLabel struct { + ResourceID string `json:"resource_id"` + ResourceType string `json:"resource_type"` + Labels []Label `json:"labels"` +} + +type Label struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type LabelResource struct { + Key string `json:"key"` + Value string `json:"value"` + ResourceID string `json:"resource_id"` +} + func CreateLabels(labels []*models.Label) error { return mongodb.NewLabelColl().BulkCreate(labels) } @@ -38,7 +59,11 @@ func ListLabels(key string, values []string, labelType string) ([]*models.Label, return mongodb.NewLabelColl().List(opt) } -func ListResourcesByLabels(filter []dto.LabelFilter, logger *zap.SugaredLogger) ([]dto.ResourceLabel, error) { +type ListResourceByLabelsReq struct { + LabelFilters []LabelFilter `json:"label_filters"` +} + +func ListResourcesByLabels(filter []LabelFilter, logger *zap.SugaredLogger) ([]ResourceLabel, error) { // find the label id labelM := map[string]*models.Label{} for _, v := range filter { @@ -64,22 +89,22 @@ func ListResourcesByLabels(filter []dto.LabelFilter, logger *zap.SugaredLogger) return nil, err } - var res []dto.ResourceLabel + var res []ResourceLabel for _, v := range labelBindings { labelResource, err := ListLabelsByResourceID(v.ResourceID, v.ResourceType, logger) if err != nil { continue } - var labels []dto.Label + var labels []Label for _, v := range labelResource { - label := dto.Label{ + label := Label{ Key: v.Key, Value: v.Value, } labels = append(labels, label) } - res = append(res, dto.ResourceLabel{ + res = append(res, ResourceLabel{ ResourceID: v.ResourceID, ResourceType: v.ResourceType, Labels: labels, @@ -89,7 +114,7 @@ func ListResourcesByLabels(filter []dto.LabelFilter, logger *zap.SugaredLogger) return res, nil } -func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.SugaredLogger) ([]dto.LabelResource, error) { +func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.SugaredLogger) ([]LabelResource, error) { labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{ ResourceID: resourceID, ResourceType: resourceType, @@ -98,13 +123,13 @@ func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.Sugared logger.Errorf("list labelbinding err:%s", err) return nil, err } - var labelResources []dto.LabelResource + var labelResources []LabelResource for _, v := range labelBindings { label, err := mongodb.NewLabelColl().Find(v.LabelID) if err != nil { continue } - labelResources = append(labelResources, dto.LabelResource{ + labelResources = append(labelResources, LabelResource{ Key: label.Key, Value: label.Value, ResourceID: v.ResourceID, @@ -113,6 +138,25 @@ func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.Sugared return labelResources, nil } +type DeleteLabelsArgs struct { + IDs []string +} + +func DeleteLabels(ids []string, logger *zap.SugaredLogger) error { + if len(ids) == 0 { + return nil + } + res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: ids}) + if err != nil { + logger.Errorf("list labelbingding err:%s", err) + return err + } + if len(res) > 0 { + return e.ErrForbidden.AddDesc("some label has already bind resource, can not delete") + } + return mongodb.NewLabelColl().BulkDelete(ids) +} + func DeleteLabel(id string, force bool, logger *zap.SugaredLogger) error { // query if the label already bind resources @@ -128,7 +172,7 @@ func DeleteLabel(id string, force bool, logger *zap.SugaredLogger) error { ids = append(ids, labelBindings.ID.Hex()) } - if err := mongodb.NewLabelBindingColl().DeleteMany(ids); err != nil { + if err := mongodb.NewLabelBindingColl().BulkDelete(ids); err != nil { logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) return err } diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index fe436f5d..15c3f516 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -84,6 +84,10 @@ func CreateLabelsBinding(cr *CreateLabelBindingsArgs, logger *zap.SugaredLogger) return mongodb.NewLabelBindingColl().CreateMany(labelBindings) } -func DeleteLabelsBinding(id string) error { - return mongodb.NewLabelBindingColl().Delete(id) +type DeleteLabelsBindingsArgs struct { + IDs []string +} + +func DeleteLabelsBindings(ids []string) error { + return mongodb.NewLabelBindingColl().BulkDelete(ids) } -- Gitee From eaf23910c4e58d411b81a27dac0bb24c53912423 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 15:31:39 +0800 Subject: [PATCH 1287/2647] optimize Signed-off-by: panxunying --- .../collaboration/repository/mongodb/collaboration_mode.go | 7 ++++--- .../core/collaboration/service/collaboration_instance.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index f95dbb8a..ea823ac2 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -40,7 +40,7 @@ type CollaborationModeFindOptions struct { type CollaborationModeListOptions struct { Projects []string Name string ` ` - Members string + Members []string IsDeleted bool } @@ -188,8 +188,9 @@ func (c *CollaborationModeColl) List(opt *CollaborationModeListOptions) ([]*mode if len(opt.Projects) > 0 { query["project_name"] = bson.M{"$in": opt.Projects} } - if opt.Members != "" { - query["members"] = opt.Members + if len(opt.Members) > 0 { + opt.Members = append(opt.Members, "*") + query["members"] = bson.M{"$in": opt.Members} } query["is_deleted"] = false diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 9d68d154..d1892833 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -197,7 +197,7 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ Projects: []string{projectName}, - Members: uid, + Members: []string{uid}, }) if err != nil { logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) -- Gitee From 33af736c10465edb5a16162861ec809f4ddb2b8e Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 17:51:59 +0800 Subject: [PATCH 1288/2647] forcedelete Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 14 +++++++++++-- .../aslan/core/label/service/label.go | 21 ++++++++++++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 342280a2..9bbd5485 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -89,7 +89,13 @@ func DeleteLabels(c *gin.Context) { if err := c.ShouldBindJSON(deleteLabelsArgs); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("json bind fail") } - ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, ctx.Logger) + force := c.Query("force") + forceBool, err := strconv.ParseBool(force) + if err != nil { + ctx.Err = err + return + } + ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.Logger) } func DeleteLabel(c *gin.Context) { @@ -102,7 +108,11 @@ func DeleteLabel(c *gin.Context) { return } force := c.Query("force") - forceBool, _ := strconv.ParseBool(force) + forceBool, err := strconv.ParseBool(force) + if err != nil { + ctx.Err = err + return + } ctx.Err = service.DeleteLabel(id, forceBool, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 30bd35b3..c171fd5f 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -142,7 +142,7 @@ type DeleteLabelsArgs struct { IDs []string } -func DeleteLabels(ids []string, logger *zap.SugaredLogger) error { +func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) error { if len(ids) == 0 { return nil } @@ -151,13 +151,24 @@ func DeleteLabels(ids []string, logger *zap.SugaredLogger) error { logger.Errorf("list labelbingding err:%s", err) return err } - if len(res) > 0 { + if forceDelete { + var ids []string + for _, labelBindings := range res { + ids = append(ids, labelBindings.ID.Hex()) + } + + if err := mongodb.NewLabelBindingColl().BulkDelete(ids); err != nil { + logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) + return err + } + } + if len(res) > 0 && !forceDelete { return e.ErrForbidden.AddDesc("some label has already bind resource, can not delete") } return mongodb.NewLabelColl().BulkDelete(ids) } -func DeleteLabel(id string, force bool, logger *zap.SugaredLogger) error { +func DeleteLabel(id string, forceDelete bool, logger *zap.SugaredLogger) error { // query if the label already bind resources res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelID: id}) @@ -166,7 +177,7 @@ func DeleteLabel(id string, force bool, logger *zap.SugaredLogger) error { return err } // force delete : delete related labelBindings - if force { + if forceDelete { var ids []string for _, labelBindings := range res { ids = append(ids, labelBindings.ID.Hex()) @@ -178,7 +189,7 @@ func DeleteLabel(id string, force bool, logger *zap.SugaredLogger) error { } } // non force delete : can not delete label when label has bind resources - if len(res) > 0 && !force { + if len(res) > 0 && !forceDelete { logger.Error("the label has bind resources,can not delete") return e.ErrForbidden.AddDesc("the label has bind resources") } -- Gitee From e00c688d3a98f2060986de74b93b70a14193b031 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 14 Jan 2022 17:55:47 +0800 Subject: [PATCH 1289/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/docker.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/docker.go b/pkg/microservice/reaper/core/service/reaper/docker.go index cfa97073..49746005 100644 --- a/pkg/microservice/reaper/core/service/reaper/docker.go +++ b/pkg/microservice/reaper/core/service/reaper/docker.go @@ -16,11 +16,15 @@ limitations under the License. package reaper -import "os/exec" +import ( + "fmt" + "os/exec" +) const dockerExe = "/usr/local/bin/docker" func dockerLogin(user, password, registry string) *exec.Cmd { + fmt.Println("login docker with credential:", "username:", user, ", password:", password, ", registry:", registry) return exec.Command( dockerExe, "login", -- Gitee From 6adf5c7f3c2d9e0d3bdab55720ec72bee1dfe6b6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 18:12:13 +0800 Subject: [PATCH 1290/2647] forcedelete Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 12 ++------ .../aslan/core/label/handler/router.go | 2 +- .../core/label/repository/mongodb/label.go | 29 +++++++++++++++++++ .../aslan/core/label/service/label.go | 21 ++++++++++---- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 9bbd5485..53c6cbee 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -29,21 +29,15 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -type ListLabelsArgs struct { - Key string `json:"key" form:"key"` - Values []string `json:"values" form:"values"` - LabelType string `json:"type" form:"type"` -} - func ListLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - listLabelsArgs := new(ListLabelsArgs) - if err := c.ShouldBindQuery(listLabelsArgs); err != nil { + listLabelsArgs := make([]*service.ListLabelsArgs, 0) + if err := c.ShouldBindJSON(&listLabelsArgs); err != nil { ctx.Err = err return } - ctx.Resp, ctx.Err = service.ListLabels(listLabelsArgs.Key, listLabelsArgs.Values, listLabelsArgs.LabelType) + ctx.Resp, ctx.Err = service.ListLabels(listLabelsArgs) } func createLabelValidate(lbs []*models.Label) error { diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 68059b91..3dc9716e 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -25,7 +25,7 @@ type Router struct{} func (*Router) Inject(router *gin.RouterGroup) { labels := router.Group("labels") { - labels.GET("", ListLabels) + labels.POST("/filter", ListLabels) labels.POST("/bulk-create", CreateLabels) labels.DELETE("/:id", DeleteLabel) labels.POST("/bulk-delete", DeleteLabels) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 5524b6de..e533d8ed 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -150,3 +150,32 @@ func (c *LabelColl) List(opt *ListLabelOpt) ([]*models.Label, error) { } return res, nil } + +func (c *LabelColl) Filter(opts []*ListLabelOpt) ([]*models.Label, error) { + var res []*models.Label + ctx := context.Background() + + conditions := bson.A{} + for _, findOpt := range opts { + con := bson.M{ + "key": findOpt.Key, + } + if len(findOpt.Values) > 0 { + con["value"] = bson.M{"$in": findOpt.Values} + } + + conditions = append(conditions, con) + } + + filter := bson.D{{"$or", conditions}} + cursor, err := c.Collection.Find(ctx, filter) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index c171fd5f..0cadf454 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -50,13 +50,22 @@ func CreateLabels(labels []*models.Label) error { return mongodb.NewLabelColl().BulkCreate(labels) } -func ListLabels(key string, values []string, labelType string) ([]*models.Label, error) { - opt := &mongodb.ListLabelOpt{ - Key: key, - Values: values, - Type: labelType, +type ListLabelsArgs struct { + Key string `json:"key" form:"key"` + Values []string `json:"values" form:"values"` +} + +func ListLabels(args []*ListLabelsArgs) ([]*models.Label, error) { + opts := make([]*mongodb.ListLabelOpt, 0) + for _, v := range args { + opt := &mongodb.ListLabelOpt{ + Key: v.Key, + Values: v.Values, + } + opts = append(opts, opt) } - return mongodb.NewLabelColl().List(opt) + + return mongodb.NewLabelColl().Filter(opts) } type ListResourceByLabelsReq struct { -- Gitee From 66c6416b053bdd9e39cf0e5f671cf3fee1d203d5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 14 Jan 2022 18:17:35 +0800 Subject: [PATCH 1291/2647] forcedelete Signed-off-by: mouuii --- .../core/label/repository/mongodb/label.go | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index e533d8ed..3637b67b 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -154,20 +154,24 @@ func (c *LabelColl) List(opt *ListLabelOpt) ([]*models.Label, error) { func (c *LabelColl) Filter(opts []*ListLabelOpt) ([]*models.Label, error) { var res []*models.Label ctx := context.Background() - conditions := bson.A{} - for _, findOpt := range opts { - con := bson.M{ - "key": findOpt.Key, - } - if len(findOpt.Values) > 0 { - con["value"] = bson.M{"$in": findOpt.Values} + if len(opts) > 0 { + for _, findOpt := range opts { + con := bson.M{ + "key": findOpt.Key, + } + if len(findOpt.Values) > 0 { + con["value"] = bson.M{"$in": findOpt.Values} + } + + conditions = append(conditions, con) } - - conditions = append(conditions, con) + } + filter := bson.D{} + if len(conditions) > 0 { + filter = bson.D{{"$or", conditions}} } - filter := bson.D{{"$or", conditions}} cursor, err := c.Collection.Find(ctx, filter) if err != nil { return nil, err -- Gitee From 47365be4aa35ef592a51116c45b8d8e466bddc24 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 14 Jan 2022 18:21:52 +0800 Subject: [PATCH 1292/2647] debug Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 7b1019cf..8cf09bbe 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -79,7 +79,9 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su switch resp.RegProvider { case config.RegistryTypeSWR: resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) + fmt.Println("Getting huawei secretkey with AK:", resp.AccessKey, ", SK:", resp.SecretKey) resp.SecretKey = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) + fmt.Println("The result is:", resp.SecretKey) case config.RegistryTypeAWS: realAK, realSK, err := getAWSRegistryCredential(resp.ID.Hex(), resp.AccessKey, resp.SecretKey, resp.Region) if err != nil { -- Gitee From 5d8cbd85c40b3fbad6a49c16fcae9cc19e1156e3 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 18:27:08 +0800 Subject: [PATCH 1293/2647] optimize Signed-off-by: panxunying --- .../core/collaboration/service/collaboration_instance.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index d1892833..acf6b1be 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -67,8 +67,8 @@ type Product struct { Name string `json:"name"` DeployType string `json:"deploy_type"` Vars []*templatemodels.RenderKV `json:"vars"` - DefaultValues string `json:"default_values"` - RenderCharts []*commonservice.RenderChartArg `json:"render_charts"` + DefaultValues string `json:"defaultValues"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } type GetCollaborationNewResp struct { Code int64 `json:"code"` @@ -326,7 +326,7 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL if set, ok := productRenderSetMap[product.BaseName]; ok { product.Vars = set.KVs product.DefaultValues = set.DefaultValues - product.RenderCharts = buildRenderChartArg(set.ChartInfos, product.BaseName) + product.ChartValues = buildRenderChartArg(set.ChartInfos, product.BaseName) } else { return nil, fmt.Errorf("product:%s not exist", product.BaseName) } -- Gitee From cc0587e11da0e3125fa99e8008c39c931a3189ce Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 14 Jan 2022 18:28:14 +0800 Subject: [PATCH 1294/2647] final debug Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 4 +--- pkg/microservice/reaper/core/service/reaper/docker.go | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 8cf09bbe..11043ba1 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -78,10 +78,8 @@ func findRegisty(regOps *mongodb.FindRegOps, getRealCredential bool, log *zap.Su } switch resp.RegProvider { case config.RegistryTypeSWR: - resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) - fmt.Println("Getting huawei secretkey with AK:", resp.AccessKey, ", SK:", resp.SecretKey) resp.SecretKey = util.ComputeHmacSha256(resp.AccessKey, resp.SecretKey) - fmt.Println("The result is:", resp.SecretKey) + resp.AccessKey = fmt.Sprintf("%s@%s", resp.Region, resp.AccessKey) case config.RegistryTypeAWS: realAK, realSK, err := getAWSRegistryCredential(resp.ID.Hex(), resp.AccessKey, resp.SecretKey, resp.Region) if err != nil { diff --git a/pkg/microservice/reaper/core/service/reaper/docker.go b/pkg/microservice/reaper/core/service/reaper/docker.go index 49746005..ec5e62ee 100644 --- a/pkg/microservice/reaper/core/service/reaper/docker.go +++ b/pkg/microservice/reaper/core/service/reaper/docker.go @@ -17,14 +17,12 @@ limitations under the License. package reaper import ( - "fmt" "os/exec" ) const dockerExe = "/usr/local/bin/docker" func dockerLogin(user, password, registry string) *exec.Cmd { - fmt.Println("login docker with credential:", "username:", user, ", password:", password, ", registry:", registry) return exec.Command( dockerExe, "login", -- Gitee From b3b7fc044402aed1bb6b304f440d350c4a8d237e Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 19:02:00 +0800 Subject: [PATCH 1295/2647] policy->policy_meta Signed-off-by: panxunying --- ...stration.go => policy_meta_registration.go} | 0 .../models/{policy.go => policy_meta.go} | 16 ++++++++-------- ...stration.go => policy_meta_registration.go} | 18 +++++++++--------- .../policy/core/service/bundle/opa_bundle.go | 4 ++-- .../bundle/resource_endpoint_mapping.go | 2 +- .../policy/core/service/policy_registration.go | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) rename pkg/microservice/policy/core/handler/{policy_registration.go => policy_meta_registration.go} (100%) rename pkg/microservice/policy/core/repository/models/{policy.go => policy_meta.go} (78%) rename pkg/microservice/policy/core/repository/mongodb/{policy_registration.go => policy_meta_registration.go} (79%) diff --git a/pkg/microservice/policy/core/handler/policy_registration.go b/pkg/microservice/policy/core/handler/policy_meta_registration.go similarity index 100% rename from pkg/microservice/policy/core/handler/policy_registration.go rename to pkg/microservice/policy/core/handler/policy_meta_registration.go diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy_meta.go similarity index 78% rename from pkg/microservice/policy/core/repository/models/policy.go rename to pkg/microservice/policy/core/repository/models/policy_meta.go index 8b333f60..c1008f53 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy_meta.go @@ -16,14 +16,14 @@ limitations under the License. package models -type Policy struct { - Resource string `bson:"resource" json:"resource"` - Alias string `bson:"alias" json:"alias"` - Description string `bson:"description" json:"description"` - Rules []*PolicyRule `bson:"rules" json:"rules"` +type PolicyMeta struct { + Resource string `bson:"resource" json:"resource"` + Alias string `bson:"alias" json:"alias"` + Description string `bson:"description" json:"description"` + Rules []*PolicyMetaRule `bson:"rules" json:"rules"` } -type PolicyRule struct { +type PolicyMetaRule struct { Action string `bson:"action" json:"action"` Alias string `bson:"alias" json:"alias"` Description string `bson:"description" json:"description"` @@ -43,6 +43,6 @@ type Attribute struct { Value string `bson:"value" json:"value"` } -func (Policy) TableName() string { - return "policy" +func (PolicyMeta) TableName() string { + return "policy_meta" } diff --git a/pkg/microservice/policy/core/repository/mongodb/policy_registration.go b/pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go similarity index 79% rename from pkg/microservice/policy/core/repository/mongodb/policy_registration.go rename to pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go index 344486d0..2814ed5e 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy_registration.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go @@ -29,25 +29,25 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -type PolicyColl struct { +type PolicyMetaColl struct { *mongo.Collection coll string } -func NewPolicyColl() *PolicyColl { - name := models.Policy{}.TableName() - return &PolicyColl{ +func NewPolicyColl() *PolicyMetaColl { + name := models.PolicyMeta{}.TableName() + return &PolicyMetaColl{ Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name, } } -func (c *PolicyColl) GetCollectionName() string { +func (c *PolicyMetaColl) GetCollectionName() string { return c.coll } -func (c *PolicyColl) EnsureIndex(ctx context.Context) error { +func (c *PolicyMetaColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ Keys: bson.M{"resource": 1}, Options: options.Index().SetUnique(true), @@ -58,8 +58,8 @@ func (c *PolicyColl) EnsureIndex(ctx context.Context) error { return err } -func (c *PolicyColl) List() ([]*models.Policy, error) { - var res []*models.Policy +func (c *PolicyMetaColl) List() ([]*models.PolicyMeta, error) { + var res []*models.PolicyMeta ctx := context.Background() cursor, err := c.Collection.Find(ctx, bson.M{}) @@ -75,7 +75,7 @@ func (c *PolicyColl) List() ([]*models.Policy, error) { return res, nil } -func (c *PolicyColl) UpdateOrCreate(obj *models.Policy) error { +func (c *PolicyMetaColl) UpdateOrCreate(obj *models.PolicyMeta) error { if obj == nil { return fmt.Errorf("nil object") } diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 32c66cbd..d938d0e5 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -193,7 +193,7 @@ func (o roleBindings) Less(i, j int) bool { return o[i].UID < o[j].UID } -func generateOPARoles(roles []*models.Role, policies []*models.Policy) *opaRoles { +func generateOPARoles(roles []*models.Role, policies []*models.PolicyMeta) *opaRoles { data := &opaRoles{} resourceMappings := getResourceActionMappings(policies) @@ -257,7 +257,7 @@ func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { return data } -func generateOPAExemptionURLs(policies []*models.Policy) *exemptionURLs { +func generateOPAExemptionURLs(policies []*models.PolicyMeta) *exemptionURLs { data := &exemptionURLs{} for _, r := range publicURLs { diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index 92a02637..075c6bea 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -45,7 +45,7 @@ func (m resourceActionMappings) GetRules(resource string, actions []string) []*r return res } -func getResourceActionMappings(policies []*models.Policy) resourceActionMappings { +func getResourceActionMappings(policies []*models.PolicyMeta) resourceActionMappings { data := make(resourceActionMappings) for _, p := range policies { if _, ok := data[p.Resource]; !ok { diff --git a/pkg/microservice/policy/core/service/policy_registration.go b/pkg/microservice/policy/core/service/policy_registration.go index bee56d20..cf0f0b07 100644 --- a/pkg/microservice/policy/core/service/policy_registration.go +++ b/pkg/microservice/policy/core/service/policy_registration.go @@ -64,14 +64,14 @@ type PolicyRuleDefinition struct { } func CreateOrUpdatePolicyRegistration(p *Policy, _ *zap.SugaredLogger) error { - obj := &models.Policy{ + obj := &models.PolicyMeta{ Resource: p.Resource, Alias: p.Alias, Description: p.Description, } for _, r := range p.Rules { - rule := &models.PolicyRule{ + rule := &models.PolicyMetaRule{ Action: r.Action, Alias: r.Alias, Description: r.Description, -- Gitee From a3c79e1f8c41b5ad06c438ae53e9bbd14ff17264 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 19:24:25 +0800 Subject: [PATCH 1296/2647] optimize Signed-off-by: panxunying --- .../core/repository/mongodb/policy_meta_registration.go | 2 +- pkg/microservice/policy/core/service.go | 2 +- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 2 +- pkg/microservice/policy/core/service/policy_registration.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go b/pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go index 2814ed5e..a1c14664 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy_meta_registration.go @@ -35,7 +35,7 @@ type PolicyMetaColl struct { coll string } -func NewPolicyColl() *PolicyMetaColl { +func NewPolicyMetaColl() *PolicyMetaColl { name := models.PolicyMeta{}.TableName() return &PolicyMetaColl{ Collection: mongotool.Database(config.MongoDatabase()).Collection(name), diff --git a/pkg/microservice/policy/core/service.go b/pkg/microservice/policy/core/service.go index 5b20babe..ff16117c 100644 --- a/pkg/microservice/policy/core/service.go +++ b/pkg/microservice/policy/core/service.go @@ -90,7 +90,7 @@ func initDatabase(ctx context.Context) { for _, r := range []indexer{ mongodb.NewRoleColl(), mongodb.NewRoleBindingColl(), - mongodb.NewPolicyColl(), + mongodb.NewPolicyMetaColl(), } { wg.Add(1) go func(r indexer) { diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index d938d0e5..ec230749 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -324,7 +324,7 @@ func GenerateOPABundle() error { if err != nil { log.Errorf("Failed to list roleBindings, err: %s", err) } - ps, err := mongodb.NewPolicyColl().List() + ps, err := mongodb.NewPolicyMetaColl().List() if err != nil { log.Errorf("Failed to list policies, err: %s", err) } diff --git a/pkg/microservice/policy/core/service/policy_registration.go b/pkg/microservice/policy/core/service/policy_registration.go index cf0f0b07..447d85fd 100644 --- a/pkg/microservice/policy/core/service/policy_registration.go +++ b/pkg/microservice/policy/core/service/policy_registration.go @@ -93,11 +93,11 @@ func CreateOrUpdatePolicyRegistration(p *Policy, _ *zap.SugaredLogger) error { obj.Rules = append(obj.Rules, rule) } - return mongodb.NewPolicyColl().UpdateOrCreate(obj) + return mongodb.NewPolicyMetaColl().UpdateOrCreate(obj) } func GetPolicyRegistrationDefinitions(_ *zap.SugaredLogger) ([]*PolicyDefinition, error) { - policies, err := mongodb.NewPolicyColl().List() + policies, err := mongodb.NewPolicyMetaColl().List() if err != nil { return nil, err } -- Gitee From b952d24e6bf0145240a5334a6bc5cc4360c7c3e5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 19:36:57 +0800 Subject: [PATCH 1297/2647] optimize Signed-off-by: panxunying --- .../policy/core/handler/policy_meta_registration.go | 2 +- .../{policy_registration.go => policy_meta_registration.go} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename pkg/microservice/policy/core/service/{policy_registration.go => policy_meta_registration.go} (96%) diff --git a/pkg/microservice/policy/core/handler/policy_meta_registration.go b/pkg/microservice/policy/core/handler/policy_meta_registration.go index 748bdfa8..6d0a8417 100644 --- a/pkg/microservice/policy/core/handler/policy_meta_registration.go +++ b/pkg/microservice/policy/core/handler/policy_meta_registration.go @@ -27,7 +27,7 @@ func CreateOrUpdatePolicyRegistration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := &service.Policy{} + args := &service.PolicyMeta{} if err := c.ShouldBindJSON(args); err != nil { ctx.Err = err return diff --git a/pkg/microservice/policy/core/service/policy_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go similarity index 96% rename from pkg/microservice/policy/core/service/policy_registration.go rename to pkg/microservice/policy/core/service/policy_meta_registration.go index 447d85fd..8131a51d 100644 --- a/pkg/microservice/policy/core/service/policy_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -23,7 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" ) -type Policy struct { +type PolicyMeta struct { Resource string `json:"resource"` Alias string `json:"alias"` Description string `json:"description"` @@ -63,7 +63,7 @@ type PolicyRuleDefinition struct { Description string `json:"description"` } -func CreateOrUpdatePolicyRegistration(p *Policy, _ *zap.SugaredLogger) error { +func CreateOrUpdatePolicyRegistration(p *PolicyMeta, _ *zap.SugaredLogger) error { obj := &models.PolicyMeta{ Resource: p.Resource, Alias: p.Alias, -- Gitee From c685f741966ef25ccc538478ee2d89944b6eaebf Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 14 Jan 2022 20:07:23 +0800 Subject: [PATCH 1298/2647] add policy Signed-off-by: panxunying --- .../policy/core/handler/policy.go | 255 ++++++++++++++++++ .../policy/core/handler/policy_binding.go | 178 ++++++++++++ .../policy/core/handler/router.go | 20 ++ .../policy/core/repository/models/common.go | 30 +++ .../policy/core/repository/models/policy.go | 29 ++ .../core/repository/models/policy_binding.go | 43 +++ .../policy/core/repository/models/role.go | 17 -- .../core/repository/models/role_binding.go | 15 -- .../policy/core/repository/mongodb/policy.go | 183 +++++++++++++ .../core/repository/mongodb/policy_binding.go | 205 ++++++++++++++ .../policy/core/service/common.go | 9 + .../policy/core/service/policy.go | 152 +++++++++++ .../policy/core/service/policy_binding.go | 145 ++++++++++ pkg/microservice/policy/core/service/role.go | 6 - .../policy/core/service/role_binding.go | 2 - 15 files changed, 1249 insertions(+), 40 deletions(-) create mode 100644 pkg/microservice/policy/core/handler/policy.go create mode 100644 pkg/microservice/policy/core/handler/policy_binding.go create mode 100644 pkg/microservice/policy/core/repository/models/common.go create mode 100644 pkg/microservice/policy/core/repository/models/policy.go create mode 100644 pkg/microservice/policy/core/repository/models/policy_binding.go create mode 100644 pkg/microservice/policy/core/repository/mongodb/policy.go create mode 100644 pkg/microservice/policy/core/repository/mongodb/policy_binding.go create mode 100644 pkg/microservice/policy/core/service/common.go create mode 100644 pkg/microservice/policy/core/service/policy.go create mode 100644 pkg/microservice/policy/core/service/policy_binding.go diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go new file mode 100644 index 00000000..ffac73a8 --- /dev/null +++ b/pkg/microservice/policy/core/handler/policy.go @@ -0,0 +1,255 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/policy/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +type deletePoliciesArgs struct { + Names []string `json:"names"` +} + +func CreatePolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Err = service.CreatePolicy(projectName, args, ctx.Logger) +} + +func UpdatePolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + name := c.Param("name") + args.Name = name + + ctx.Err = service.UpdatePolicy(projectName, args, ctx.Logger) +} + +func UpdateOrCreatePolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + args.Name = c.Param("name") + + ctx.Err = service.UpdateOrCreatePolicy(projectName, args, ctx.Logger) +} + +func UpdatePublicPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + name := c.Param("name") + args.Name = name + ctx.Err = service.UpdatePolicy("", args, ctx.Logger) +} + +func UpdateOrCreatePublicPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + name := c.Param("name") + args.Name = name + ctx.Err = service.UpdateOrCreatePolicy("", args, ctx.Logger) +} + +func ListPolicies(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + ctx.Resp, ctx.Err = service.ListPolicies(projectName, ctx.Logger) +} + +func GetPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + ctx.Resp, ctx.Err = service.GetPolicy(projectName, c.Param("name"), ctx.Logger) +} + +func CreatePublicPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.CreatePolicy("", args, ctx.Logger) +} + +func ListPublicPolicies(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListPolicies("", ctx.Logger) + return +} + +func GetPublicPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetPolicy("", c.Param("name"), ctx.Logger) +} + +func DeletePolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + name := c.Param("name") + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + ctx.Err = service.DeletePolicy(name, projectName, ctx.Logger) +} + +func DeletePolicies(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") + return + } + + args := &deletePoliciesArgs{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.DeletePolicies(args.Names, projectName, ctx.Logger) +} + +func DeletePublicPolicies(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + name := c.Param("name") + ctx.Err = service.DeletePolicy(name, "", ctx.Logger) + return +} + +func CreateSystemPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.CreatePolicy("*", args, ctx.Logger) +} + +func UpdateOrCreateSystemPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.Policy{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + name := c.Param("name") + args.Name = name + ctx.Err = service.UpdateOrCreatePolicy("*", args, ctx.Logger) +} + +func ListSystemPolicies(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListPolicies("*", ctx.Logger) + return +} + +func DeleteSystemPolicy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + name := c.Param("name") + ctx.Err = service.DeletePolicy(name, "*", ctx.Logger) + return +} diff --git a/pkg/microservice/policy/core/handler/policy_binding.go b/pkg/microservice/policy/core/handler/policy_binding.go new file mode 100644 index 00000000..2cf5c15a --- /dev/null +++ b/pkg/microservice/policy/core/handler/policy_binding.go @@ -0,0 +1,178 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/policy/core/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +type deletePolicyBindingsArgs struct { + Names []string `json:"names"` +} + +func UpdatePolicyBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + args := &service.PolicyBinding{} + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("bind json fail %s") + } + ctx.Err = service.UpdateOrCreatePolicyBinding(projectName, args, ctx.Logger) +} + +func CreatePolicyBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + args := make([]*service.PolicyBinding, 0) + if c.Query("bulk") == "true" { + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = err + return + } + } else { + rb := &service.PolicyBinding{} + if err := c.ShouldBindJSON(rb); err != nil { + ctx.Err = err + return + } + args = append(args, rb) + } + + ctx.Err = service.CreatePolicyBindings(projectName, args, ctx.Logger) +} + +func ListPolicyBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Resp, ctx.Err = service.ListPolicyBindings(projectName, "", ctx.Logger) +} + +func DeletePolicyBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + name := c.Param("name") + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + ctx.Err = service.DeletePolicyBinding(name, projectName, ctx.Logger) +} + +func DeletePolicyBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + userID := c.Query("userID") + + args := &deletePolicyBindingsArgs{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.DeletePolicyBindings(args.Names, projectName, userID, ctx.Logger) +} + +func DeleteSystemPolicyBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + name := c.Param("name") + ctx.Err = service.DeletePolicyBinding(name, service.SystemScope, ctx.Logger) +} + +func CreateSystemPolicyBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.PolicyBinding{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + args.Public = false + + ctx.Err = service.CreatePolicyBindings(service.SystemScope, []*service.PolicyBinding{args}, ctx.Logger) +} + +func CreateOrUpdateSystemPolicyBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &service.PolicyBinding{} + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = err + return + } + + args.Public = false + + ctx.Err = service.CreateOrUpdateSystemPolicyBinding(service.SystemScope, args, ctx.Logger) +} + +func ListSystemPolicyBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListPolicyBindings(service.SystemScope, "", ctx.Logger) +} + +func ListUserPolicyBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + uid := c.Query("uid") + if uid == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("uid is empty") + return + } + projectName := c.Query("projectName") + if projectName == "" { + projectName = service.SystemScope + } + + ctx.Resp, ctx.Err = service.ListPolicyBindings(projectName, uid, ctx.Logger) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 4c54752a..fe3bd940 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -34,6 +34,17 @@ func (*Router) Inject(router *gin.RouterGroup) { roles.DELETE("/:name", DeleteRole) } + policies := router.Group("policies") + { + policies.POST("", CreatePolicy) + policies.POST("/bulk-delete", DeletePolicies) + policies.PATCH("/:name", UpdatePolicy) + policies.PUT("/:name", UpdateOrCreatePolicy) + policies.GET("", ListPolicies) + policies.GET("/:name", GetPolicy) + policies.DELETE("/:name", DeletePolicy) + } + publicRoles := router.Group("public-roles") { publicRoles.POST("", CreatePublicRole) @@ -61,6 +72,15 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings.POST("/bulk-delete", DeleteRoleBindings) } + policyBindings := router.Group("policybindings") + { + policyBindings.POST("", CreatePolicyBinding) + policyBindings.PUT("/:name", UpdatePolicyBinding) + policyBindings.GET("", ListPolicyBindings) + policyBindings.DELETE("/:name", DeletePolicyBinding) + policyBindings.POST("/bulk-delete", DeletePolicyBindings) + } + systemRoleBindings := router.Group("system-rolebindings") { systemRoleBindings.POST("", CreateSystemRoleBinding) diff --git a/pkg/microservice/policy/core/repository/models/common.go b/pkg/microservice/policy/core/repository/models/common.go new file mode 100644 index 00000000..4ee7ebf1 --- /dev/null +++ b/pkg/microservice/policy/core/repository/models/common.go @@ -0,0 +1,30 @@ +package models + +type SubjectKind string + +const ( + MethodAll = "*" + KindResource = "resource" + UserKind SubjectKind = "user" + GroupKind SubjectKind = "group" +) + +// Rule holds information that describes a policy rule, but does not contain information +// about whom the rule applies to. +// If Kind is "resource", verbs are resource actions, while resources are resource names +type Rule struct { + // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. + Verbs []string `bson:"verbs" json:"verbs"` + + // Resources is a list of resources this rule applies to. '*' represents all resources. + Resources []string `bson:"resources" json:"resources"` + Kind string `bson:"kind" json:"kind"` +} + +// Subject contains a reference to the object or user identities a role binding applies to. +type Subject struct { + // Kind of object being referenced. allowed values are "User", "Group". + Kind SubjectKind `bson:"kind" json:"kind"` + // unique identifier of the object being referenced. + UID string `bson:"uid" json:"uid"` +} diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go new file mode 100644 index 00000000..cb0d674f --- /dev/null +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +// Policy is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a PolicyBinding. +// for a cluster scoped Policy, namespace is empty. +type Policy struct { + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Rules []*Rule `bson:"rules" json:"rules"` +} + +func (Policy) TableName() string { + return "policy" +} diff --git a/pkg/microservice/policy/core/repository/models/policy_binding.go b/pkg/microservice/policy/core/repository/models/policy_binding.go new file mode 100644 index 00000000..66900693 --- /dev/null +++ b/pkg/microservice/policy/core/repository/models/policy_binding.go @@ -0,0 +1,43 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +// PolicyBinding references a Policy, but does not contain it. It adds who information via Subjects. +// PolicyBinding in a given namespace only have effect in that namespace. +// for a cluster scoped PolicyBinding, namespace is empty. +type PolicyBinding struct { + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + + // Subjects holds references to the objects the Policy applies to. + Subjects []*Subject `bson:"subjects" json:"subjects"` + + // PolicyRef can reference a namespaced or cluster scoped Policy. + PolicyRef *PolicyRef `bson:"policy_ref" json:"PolicyRef"` +} + +// PolicyRef contains information that points to the policy being used +type PolicyRef struct { + Name string `bson:"name" json:"name"` + + // Namespace of the referenced object. if the object is cluster scoped, namespace is empty. + Namespace string `bson:"namespace" json:"namespace"` +} + +func (PolicyBinding) TableName() string { + return "policybinding" +} diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 63c51034..7662a500 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -16,11 +16,6 @@ limitations under the License. package models -const ( - MethodAll = "*" - KindResource = "resource" -) - // Role is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding. // for a cluster scoped Role, namespace is empty. type Role struct { @@ -29,18 +24,6 @@ type Role struct { Rules []*Rule `bson:"rules" json:"rules"` } -// Rule holds information that describes a policy rule, but does not contain information -// about whom the rule applies to. -// If Kind is "resource", verbs are resource actions, while resources are resource names -type Rule struct { - // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. - Verbs []string `bson:"verbs" json:"verbs"` - - // Resources is a list of resources this rule applies to. '*' represents all resources. - Resources []string `bson:"resources" json:"resources"` - Kind string `bson:"kind" json:"kind"` -} - func (Role) TableName() string { return "role" } diff --git a/pkg/microservice/policy/core/repository/models/role_binding.go b/pkg/microservice/policy/core/repository/models/role_binding.go index 64d24dca..fa75f046 100644 --- a/pkg/microservice/policy/core/repository/models/role_binding.go +++ b/pkg/microservice/policy/core/repository/models/role_binding.go @@ -16,13 +16,6 @@ limitations under the License. package models -type SubjectKind string - -const ( - UserKind SubjectKind = "user" - GroupKind SubjectKind = "group" -) - // RoleBinding references a role, but does not contain it. It adds who information via Subjects. // RoleBindings in a given namespace only have effect in that namespace. // for a cluster scoped RoleBinding, namespace is empty. @@ -37,14 +30,6 @@ type RoleBinding struct { RoleRef *RoleRef `bson:"role_ref" json:"roleRef"` } -// Subject contains a reference to the object or user identities a role binding applies to. -type Subject struct { - // Kind of object being referenced. allowed values are "User", "Group". - Kind SubjectKind `bson:"kind" json:"kind"` - // unique identifier of the object being referenced. - UID string `bson:"uid" json:"uid"` -} - // RoleRef contains information that points to the role being used type RoleRef struct { Name string `bson:"name" json:"name"` diff --git a/pkg/microservice/policy/core/repository/mongodb/policy.go b/pkg/microservice/policy/core/repository/mongodb/policy.go new file mode 100644 index 00000000..2cf76559 --- /dev/null +++ b/pkg/microservice/policy/core/repository/mongodb/policy.go @@ -0,0 +1,183 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type PolicyColl struct { + *mongo.Collection + + coll string +} + +func NewPolicyColl() *PolicyColl { + name := models.Policy{}.TableName() + return &PolicyColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *PolicyColl) GetCollectionName() string { + return c.coll +} + +func (c *PolicyColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "namespace", Value: 1}, + bson.E{Key: "name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *PolicyColl) Get(ns, name string) (*models.Policy, bool, error) { + res := &models.Policy{} + + query := bson.M{"namespace": ns, "name": name} + err := c.FindOne(context.TODO(), query).Decode(res) + if err != nil { + if err == mongo.ErrNoDocuments { + return nil, false, nil + } + + return nil, false, err + } + + return res, true, nil +} + +func (c *PolicyColl) List() ([]*models.Policy, error) { + var res []*models.Policy + + ctx := context.Background() + + cursor, err := c.Collection.Find(ctx, bson.M{}) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *PolicyColl) ListBy(projectName string) ([]*models.Policy, error) { + var res []*models.Policy + + ctx := context.Background() + query := bson.M{"namespace": projectName} + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *PolicyColl) ListBySpaceAndName(projectName string, name string) ([]*models.Policy, error) { + var res []*models.Policy + + ctx := context.Background() + query := bson.M{"namespace": projectName, "name": name} + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *PolicyColl) Create(obj *models.Policy) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + + return err +} + +func (c *PolicyColl) Delete(name string, projectName string) error { + query := bson.M{"name": name, "namespace": projectName} + _, err := c.DeleteOne(context.TODO(), query) + return err +} + +func (c *PolicyColl) DeleteMany(names []string, projectName string) error { + query := bson.M{"namespace": projectName} + if len(names) > 0 { + query["name"] = bson.M{"$in": names} + } + _, err := c.Collection.DeleteMany(context.TODO(), query) + return err +} + +func (c *PolicyColl) UpdatePolicy(obj *models.Policy) error { + // avoid panic issue + if obj == nil { + return errors.New("nil Policy") + } + + query := bson.M{"name": obj.Name, "namespace": obj.Namespace} + change := bson.M{"$set": bson.M{ + "rules": obj.Rules, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *PolicyColl) UpdateOrCreate(obj *models.Policy) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + query := bson.M{"name": obj.Name, "namespace": obj.Namespace} + opts := options.Replace().SetUpsert(true) + _, err := c.ReplaceOne(context.TODO(), query, obj, opts) + + return err +} diff --git a/pkg/microservice/policy/core/repository/mongodb/policy_binding.go b/pkg/microservice/policy/core/repository/mongodb/policy_binding.go new file mode 100644 index 00000000..be848b31 --- /dev/null +++ b/pkg/microservice/policy/core/repository/mongodb/policy_binding.go @@ -0,0 +1,205 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ListPolicyOptions struct { + PolicyName, PolicyNamespace string +} + +type PolicyBindingColl struct { + *mongo.Collection + + coll string +} + +func NewPolicyBindingColl() *PolicyBindingColl { + name := models.PolicyBinding{}.TableName() + return &PolicyBindingColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *PolicyBindingColl) GetCollectionName() string { + return c.coll +} + +func (c *PolicyBindingColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "namespace", Value: 1}, + bson.E{Key: "name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *PolicyBindingColl) List(opts ...*ListPolicyOptions) ([]*models.PolicyBinding, error) { + var res []*models.PolicyBinding + + ctx := context.Background() + query := bson.M{} + if len(opts) > 0 { + opt := opts[0] + if opt.PolicyName != "" { + query["policy_ref.name"] = opt.PolicyName + query["policy_ref.namespace"] = opt.PolicyNamespace + } + } + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *PolicyBindingColl) ListBy(projectName, uid string) ([]*models.PolicyBinding, error) { + var res []*models.PolicyBinding + + ctx := context.Background() + query := bson.M{"namespace": projectName} + if uid != "" { + query["subjects.uid"] = uid + query["subjects.kind"] = models.UserKind + } + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *PolicyBindingColl) Delete(name string, projectName string) error { + query := bson.M{"name": name, "namespace": projectName} + _, err := c.DeleteOne(context.TODO(), query) + return err +} + +func (c *PolicyBindingColl) DeleteMany(names []string, projectName string, userID string) error { + query := bson.M{} + if projectName != "" { + query["namespace"] = projectName + } + if len(names) > 0 { + query["name"] = bson.M{"$in": names} + } + + if userID != "" { + query = bson.M{"subjects.uid": userID} + } + _, err := c.Collection.DeleteMany(context.TODO(), query) + + return err +} + +func (c *PolicyBindingColl) DeleteByPolicy(policyName string, projectName string) error { + query := bson.M{"policy_ref.name": policyName, "policy_ref.namespace": projectName} + // if projectName == "", delete all policybindings in all namespaces + if projectName != "" { + query["namespace"] = projectName + } + _, err := c.Collection.DeleteMany(context.TODO(), query) + + return err +} + +func (c *PolicyBindingColl) DeleteByPolicies(policyNames []string, projectName string) error { + if projectName == "" { + return fmt.Errorf("projectName is empty") + } + if len(policyNames) == 0 { + return nil + } + + query := bson.M{"policy_ref.name": bson.M{"$in": policyNames}, "policy_ref.namespace": projectName, "namespace": projectName} + _, err := c.Collection.DeleteMany(context.TODO(), query) + + return err +} + +func (c *PolicyBindingColl) Create(obj *models.PolicyBinding) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + _, err := c.InsertOne(context.TODO(), obj) + + return err +} + +func (c *PolicyBindingColl) BulkCreate(objs []*models.PolicyBinding) error { + if len(objs) == 0 { + return nil + } + + var ois []interface{} + for _, obj := range objs { + ois = append(ois, obj) + } + + res, err := c.InsertMany(context.TODO(), ois) + if mongo.IsDuplicateKeyError(err) { + log.Warnf("Duplicate key found, inserted IDs is %v", res.InsertedIDs) + return nil + } + + return err +} + +func (c *PolicyBindingColl) UpdateOrCreate(obj *models.PolicyBinding) error { + if obj == nil { + return fmt.Errorf("nil object") + } + + query := bson.M{"name": obj.Name, "namespace": obj.Namespace} + opts := options.Replace().SetUpsert(true) + _, err := c.ReplaceOne(context.TODO(), query, obj, opts) + + return err +} diff --git a/pkg/microservice/policy/core/service/common.go b/pkg/microservice/policy/core/service/common.go new file mode 100644 index 00000000..50e725a4 --- /dev/null +++ b/pkg/microservice/policy/core/service/common.go @@ -0,0 +1,9 @@ +package service + +type Rule struct { + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` +} + +const SystemScope = "*" diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go new file mode 100644 index 00000000..1ce6ea9b --- /dev/null +++ b/pkg/microservice/policy/core/service/policy.go @@ -0,0 +1,152 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +type Policy struct { + Name string `json:"name"` + Rules []*Rule `json:"rules,omitempty"` +} + +func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { + obj := &models.Policy{ + Name: policy.Name, + Namespace: ns, + } + + for _, r := range policy.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + }) + } + + return mongodb.NewPolicyColl().Create(obj) +} + +func UpdatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { + obj := &models.Policy{ + Name: policy.Name, + Namespace: ns, + } + + for _, r := range policy.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + }) + } + return mongodb.NewPolicyColl().UpdatePolicy(obj) +} + +func UpdateOrCreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { + obj := &models.Policy{ + Name: policy.Name, + Namespace: ns, + } + + for _, r := range policy.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + }) + } + return mongodb.NewPolicyColl().UpdateOrCreate(obj) +} + +func ListPolicies(projectName string, _ *zap.SugaredLogger) ([]*Policy, error) { + var policies []*Policy + projectPolicies, err := mongodb.NewPolicyColl().ListBy(projectName) + if err != nil { + return nil, err + } + for _, v := range projectPolicies { + // frontend doesn't need to see contributor role + if v.Name == string(setting.Contributor) { + continue + } + policies = append(policies, &Policy{ + Name: v.Name, + }) + } + return policies, nil +} + +func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { + r, found, err := mongodb.NewPolicyColl().Get(ns, name) + if err != nil { + return nil, err + } else if !found { + return nil, fmt.Errorf("policy %s not found", name) + } + + res := &Policy{ + Name: r.Name, + } + for _, ru := range r.Rules { + res.Rules = append(res.Rules, &Rule{ + Verbs: ru.Verbs, + Kind: ru.Kind, + Resources: ru.Resources, + }) + } + + return res, nil +} + +func DeletePolicy(name string, projectName string, logger *zap.SugaredLogger) error { + err := mongodb.NewPolicyColl().Delete(name, projectName) + if err != nil { + logger.Errorf("Failed to delete policy %s in project %s, err: %s", name, projectName, err) + return err + } + + return mongodb.NewPolicyBindingColl().DeleteByPolicy(name, projectName) +} + +func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogger) error { + if len(names) == 0 { + return nil + } + if projectName == "" { + return fmt.Errorf("projectName is empty") + } + + if names[0] == "*" { + names = []string{} + } + + err := mongodb.NewPolicyColl().DeleteMany(names, projectName) + if err != nil { + logger.Errorf("Failed to delete policies %s in project %s, err: %s", names, projectName, err) + return err + } + + return mongodb.NewPolicyBindingColl().DeleteByPolicies(names, projectName) +} diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go new file mode 100644 index 00000000..39e35f7e --- /dev/null +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -0,0 +1,145 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" +) + +type PolicyBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Policy string `json:"policy"` + Public bool `json:"public"` +} + +func CreatePolicyBindings(ns string, rbs []*PolicyBinding, logger *zap.SugaredLogger) error { + var objs []*models.PolicyBinding + for _, rb := range rbs { + obj, err := createPolicyBindingObject(ns, rb, logger) + if err != nil { + return err + } + + objs = append(objs, obj) + } + + return mongodb.NewPolicyBindingColl().BulkCreate(objs) +} + +func CreateOrUpdateSystemPolicyBinding(ns string, rb *PolicyBinding, logger *zap.SugaredLogger) error { + + obj, err := createPolicyBindingObject(ns, rb, logger) + if err != nil { + return err + } + return mongodb.NewPolicyBindingColl().UpdateOrCreate(obj) +} + +func UpdateOrCreatePolicyBinding(ns string, rb *PolicyBinding, logger *zap.SugaredLogger) error { + obj, err := createPolicyBindingObject(ns, rb, logger) + if err != nil { + return err + } + return mongodb.NewPolicyBindingColl().UpdateOrCreate(obj) +} + +func ListPolicyBindings(ns, uid string, _ *zap.SugaredLogger) ([]*PolicyBinding, error) { + var policyBindings []*PolicyBinding + modelPolicyBindings, err := mongodb.NewPolicyBindingColl().ListBy(ns, uid) + if err != nil { + return nil, err + } + + for _, v := range modelPolicyBindings { + policyBindings = append(policyBindings, &PolicyBinding{ + Name: v.Name, + Policy: v.PolicyRef.Name, + UID: v.Subjects[0].UID, + Public: v.PolicyRef.Namespace == "", + }) + } + + return policyBindings, nil +} + +func ListPolicyBindingsByPolicy(ns, policyName string, publicPolicy bool, _ *zap.SugaredLogger) ([]*PolicyBinding, error) { + var policyBindings []*PolicyBinding + + policyNamespace := ns + if publicPolicy { + policyNamespace = "" + } + modelPolicyBindings, err := mongodb.NewPolicyBindingColl().List(&mongodb.ListPolicyOptions{PolicyName: policyName, PolicyNamespace: policyNamespace}) + if err != nil { + return nil, err + } + + for _, v := range modelPolicyBindings { + policyBindings = append(policyBindings, &PolicyBinding{ + Name: v.Name, + Policy: v.PolicyRef.Name, + UID: v.Subjects[0].UID, + Public: v.PolicyRef.Namespace == "", + }) + } + + return policyBindings, nil +} + +func DeletePolicyBinding(name string, projectName string, _ *zap.SugaredLogger) error { + return mongodb.NewPolicyBindingColl().Delete(name, projectName) +} + +func DeletePolicyBindings(names []string, projectName string, userID string, _ *zap.SugaredLogger) error { + + if len(names) == 1 && names[0] == "*" { + names = []string{} + } + + return mongodb.NewPolicyBindingColl().DeleteMany(names, projectName, userID) +} + +func createPolicyBindingObject(ns string, rb *PolicyBinding, logger *zap.SugaredLogger) (*models.PolicyBinding, error) { + nsPolicy := ns + if rb.Public { + nsPolicy = "" + } + policy, found, err := mongodb.NewPolicyColl().Get(nsPolicy, rb.Policy) + if err != nil { + logger.Errorf("Failed to get policy %s in namespace %s, err: %s", rb.Policy, nsPolicy, err) + return nil, err + } else if !found { + logger.Errorf("Policy %s is not found in namespace %s", rb.Policy, nsPolicy) + return nil, fmt.Errorf("policy %s not found", rb.Policy) + } + + return &models.PolicyBinding{ + Name: rb.Name, + Namespace: ns, + Subjects: []*models.Subject{{Kind: models.UserKind, UID: rb.UID}}, + PolicyRef: &models.PolicyRef{ + Name: policy.Name, + Namespace: policy.Namespace, + }, + }, nil +} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index c8179e62..57e1ccce 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -31,12 +31,6 @@ type Role struct { Rules []*Rule `json:"rules,omitempty"` } -type Rule struct { - Verbs []string `json:"verbs"` - Resources []string `json:"resources"` - Kind string `json:"kind"` -} - func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index d2d6d3e2..de4554be 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -34,8 +34,6 @@ type RoleBinding struct { Public bool `json:"public"` } -const SystemScope = "*" - func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger) error { var objs []*models.RoleBinding for _, rb := range rbs { -- Gitee From 60deb3243d735fe2970ae6d11766217e263d09b8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Sat, 15 Jan 2022 20:09:44 +0800 Subject: [PATCH 1299/2647] no wait when installing helm chart Signed-off-by: allenshen --- .../core/environment/service/environment.go | 8 +++++--- .../core/service/taskplugin/deploy.go | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f61ecee4..f95f752d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2368,6 +2368,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] timeout := time.Second * setting.DeployTimeout + timeout = 0 err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, timeout, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) @@ -2400,7 +2401,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, kubecli, log) + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, kubecli, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2583,6 +2584,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] timeout := time.Second * setting.DeployTimeout + timeout = 0 err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, timeout, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) @@ -2621,7 +2623,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, kubeClient, log) + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, kubeClient, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2866,7 +2868,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := intervalExecutorWithRetry(5, time.Millisecond*2500, serviceList, handler, kubeClient, log) + groupServiceErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, kubeClient, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 6f5776c1..5c5b9888 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -544,6 +544,23 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Wait: true, } + isFailedStatus := func() bool { + hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 5) + if errHistory != nil { + return false + } + if len(hrs) > 0 { + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + return rel.Info.Status == helmrelease.StatusFailed + } + return false + } + if isFailedStatus := isFailedStatus(); isFailedStatus { + chartSpec.Replace = true + return + } + done := make(chan bool) go func(chan bool) { if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { @@ -583,6 +600,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } return err } + select { case d := <-done: if !d { -- Gitee From 414c6c2a371773f13d82805d16dbf273754be3fe Mon Sep 17 00:00:00 2001 From: allenshen Date: Sat, 15 Jan 2022 20:25:15 +0800 Subject: [PATCH 1300/2647] update logic Signed-off-by: allenshen --- .../warpdrive/core/service/taskplugin/deploy.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 5c5b9888..7af21c00 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -544,7 +544,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Wait: true, } - isFailedStatus := func() bool { + isFailedOrPendingStatus := func() bool { hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 5) if errHistory != nil { return false @@ -552,11 +552,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if len(hrs) > 0 { releaseutil.Reverse(hrs, releaseutil.SortByRevision) rel := hrs[0] - return rel.Info.Status == helmrelease.StatusFailed + return rel.Info.Status == helmrelease.StatusFailed || rel.Info.Status == helmrelease.StatusPendingUpgrade || rel.Info.Status == helmrelease.StatusPendingInstall } return false } - if isFailedStatus := isFailedStatus(); isFailedStatus { + if isFailedStatus := isFailedOrPendingStatus(); isFailedStatus { chartSpec.Replace = true return } -- Gitee From c900212f7c3c6bb52cf0ef21743234169798868a Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 10:18:12 +0800 Subject: [PATCH 1301/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/repository/mongodb/label.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 3637b67b..23341512 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -155,6 +155,7 @@ func (c *LabelColl) Filter(opts []*ListLabelOpt) ([]*models.Label, error) { var res []*models.Label ctx := context.Background() conditions := bson.A{} + filter := bson.D{} if len(opts) > 0 { for _, findOpt := range opts { con := bson.M{ @@ -166,9 +167,6 @@ func (c *LabelColl) Filter(opts []*ListLabelOpt) ([]*models.Label, error) { conditions = append(conditions, con) } - } - filter := bson.D{} - if len(conditions) > 0 { filter = bson.D{{"$or", conditions}} } -- Gitee From 343f631763c8d473275c3ceff4a77b5a8323f603 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 10:22:50 +0800 Subject: [PATCH 1302/2647] review Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/handler/labels.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 53c6cbee..1db8b2cc 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -82,6 +82,7 @@ func DeleteLabels(c *gin.Context) { deleteLabelsArgs := new(service.DeleteLabelsArgs) if err := c.ShouldBindJSON(deleteLabelsArgs); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("json bind fail") + return } force := c.Query("force") forceBool, err := strconv.ParseBool(force) -- Gitee From cefcaf2758d0c1662832090211f19db1c3b306ba Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 10:33:42 +0800 Subject: [PATCH 1303/2647] review Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 1db8b2cc..1b6a7ce7 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -41,16 +41,10 @@ func ListLabels(c *gin.Context) { } func createLabelValidate(lbs []*models.Label) error { - keyValues := sets.NewString() for _, v := range lbs { if v.Key == "" || v.Value == "" { return e.ErrInvalidParam.AddDesc("invalid label args") } - keyValue := fmt.Sprintf("%s-%s", v.Key, v.Value) - if keyValues.Has(keyValue) { - return e.ErrInvalidParam.AddDesc(fmt.Sprintf("duplicate key-value:%s", keyValue)) - } - keyValues.Insert(keyValue) } return nil } @@ -69,10 +63,18 @@ func CreateLabels(c *gin.Context) { ctx.Err = err return } + filteredLabels := make([]*models.Label, 0) + keyValues := sets.NewString() for _, v := range labels { + keyValue := fmt.Sprintf("%s-%s", v.Key, v.Value) + if keyValues.Has(keyValue) { + continue + } + keyValues.Insert(keyValue) v.CreateBy = ctx.UserName + filteredLabels = append(filteredLabels, v) } - ctx.Err = service.CreateLabels(labels) + ctx.Err = service.CreateLabels(filteredLabels) } //DeleteLabels can only bulk delete labels which not bind reousrces -- Gitee From a8dc5565e10c3bb4eef331b6c4de3c268dd04756 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 11:14:26 +0800 Subject: [PATCH 1304/2647] review add default logic Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/service/labelsbinding.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 15c3f516..d354eb5a 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -17,6 +17,8 @@ limitations under the License. package service import ( + "fmt" + "go.uber.org/zap" commondb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -70,6 +72,9 @@ func CreateLabelsBinding(cr *CreateLabelBindingsArgs, logger *zap.SugaredLogger) logger.Errorf("can not find related resource err:%s", err) return e.ErrForbidden.AddDesc("can not find related resource") } + default: + errMsg := fmt.Sprintf("resource type not allowed :%s", cr.ResourceType) + return e.ErrInternalError.AddDesc(errMsg) } var labelBindings []*models.LabelBinding for _, v := range cr.ResourceIDs { -- Gitee From 309772b66f78448ba522c21cd3c06c7f21c863a7 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 17 Jan 2022 11:37:39 +0800 Subject: [PATCH 1305/2647] IM notify Signed-off-by: ddh27 --- .../common/service/instantmessage/dingTalk.go | 16 + .../common/service/instantmessage/lark.go | 39 ++- .../common/service/instantmessage/service.go | 277 +++++++++--------- .../service/instantmessage/wechatwork.go | 29 +- .../core/common/service/notify/client.go | 2 +- pkg/util/time.go | 23 ++ 6 files changed, 229 insertions(+), 157 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go b/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go index 01bac888..52d5dc4e 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package instantmessage const ( diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go index 2e14bbc6..5fabb963 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go @@ -1,7 +1,24 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package instantmessage import ( "strings" + "sync" "github.com/koderover/zadig/pkg/microservice/aslan/config" ) @@ -111,12 +128,13 @@ func (lc *LarkCard) SetHeader(template, title, tag string) { } } -func (lc *LarkCard) AddI18NElementsZhcnFeild(content string) { +func (lc *LarkCard) AddI18NElementsZhcnFeild(content string, isCreatefield bool) { if lc.I18NElements == nil { lc.I18NElements = &I18NElements{ ZhCn: make([]*ZhCn, 0), } } + field := &Field{ IsShort: false, Text: TextElem{ @@ -124,11 +142,22 @@ func (lc *LarkCard) AddI18NElementsZhcnFeild(content string) { Tag: feishuTagMd, }, } - zhcnElem := &ZhCn{ - Fields: []*Field{field}, - Tag: feishuTagDiv, + var mutex sync.RWMutex + mutex.Lock() + defer mutex.Unlock() + lengthZhCn := len(lc.I18NElements.ZhCn) + if isCreatefield || lengthZhCn == 0 { + zhcnElem := &ZhCn{ + Fields: []*Field{field}, + Tag: feishuTagDiv, + } + lc.I18NElements.ZhCn = append(lc.I18NElements.ZhCn, zhcnElem) + return + } + lengthFields := len(lc.I18NElements.ZhCn[lengthZhCn-1].Fields) + if lengthFields > 0 { + lc.I18NElements.ZhCn[lengthZhCn-1].Fields = append(lc.I18NElements.ZhCn[lengthZhCn-1].Fields, field) } - lc.I18NElements.ZhCn = append(lc.I18NElements.ZhCn, zhcnElem) } func (lc *LarkCard) AddI18NElementsZhcnAction(content, url string) { diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 7c9a5302..971cce20 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -33,6 +33,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" ) const ( @@ -91,14 +92,13 @@ func (w *Service) SendMessageRequest(uri string, message interface{}) ([]byte, e func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool) error { var ( - uri = "" - content = "" - webHookType = "" - atMobiles []string - isAtAll bool - title = "" - buttonContent = "" - buttonURL = "" + uri = "" + content = "" + webHookType = "" + atMobiles []string + isAtAll bool + title = "" + larkCard *LarkCard ) if task.Type == config.SingleType { resp, err := w.pipelineColl.Find(&mongodb.PipelineFindOption{Name: task.PipelineName}) @@ -156,7 +156,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } else { uri = resp.NotifyCtl.WeChatWebHook } - title, content, buttonContent, buttonURL, err = w.createNotifyBodyOfWorkflowIM(&wechatNotification{ + title, content, larkCard, err = w.createNotifyBodyOfWorkflowIM(&wechatNotification{ Task: task, BaseURI: configbase.SystemAddress(), IsSingle: false, @@ -192,7 +192,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } else { uri = resp.NotifyCtl.WeChatWebHook } - title, content, buttonContent, buttonURL, err = w.createNotifyBodyOfTestIM(resp.Desc, &wechatNotification{ + title, content, larkCard, err = w.createNotifyBodyOfTestIM(resp.Desc, &wechatNotification{ Task: task, BaseURI: configbase.SystemAddress(), IsSingle: false, @@ -208,7 +208,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } } - if uri != "" && content != "" { + if uri != "" && (content != "" || larkCard != nil) { if webHookType == dingDingType { if task.Type == config.SingleType { title = "工作流状态" @@ -228,12 +228,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool return nil } - lc := NewLarkCard() - lc.SetConfig(true) - lc.SetHeader(getColorTemplateWithStatus(task.Status), title, feiShuTagText) - lc.AddI18NElementsZhcnFeild(content) - lc.AddI18NElementsZhcnAction(buttonContent, buttonURL) - err := w.sendFeishuMessage(uri, lc) + err := w.sendFeishuMessage(uri, larkCard) if err != nil { log.Errorf("SendFeiShuMessageRequest err : %s", err) return err @@ -243,7 +238,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool if task.Type == config.SingleType { typeText = weChatTextTypeText } - err := w.SendWeChatWorkMessage(typeText, uri, title+content) + err := w.SendWeChatWorkMessage(typeText, uri, content) if err != nil { log.Errorf("SendWeChatWorkMessage err : %s", err) return err @@ -283,151 +278,136 @@ func (w *Service) createNotifyBody(weChatNotification *wechatNotification) (cont return tplcontent, err } -func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotification) (string, string, string, string, error) { +func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotification) (string, string, *LarkCard, error) { + tplTitle := "{{if ne .WebHookType \"feishu\"}}#### {{end}}{{getIcon .Task.Status }}{{if eq .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流 {{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}} \n" + tplBaseInfo := []string{"{{if eq .WebHookType \"dingding\"}}##### {{end}}**执行用户**:{{.Task.TaskCreator}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**环境信息**:{{.Task.WorkflowArgs.Namespace}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**开始时间**:{{ getStartTime .Task.StartTime}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**持续时间**:{{ getDuration .TotalTime}} \n", + } - tplTitle := "#### {{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}} \n" - tplcontent := "**创建者**:{{.Task.TaskCreator}} **持续时间**:{{ .TotalTime}} 秒 \n" + - "**环境信息**:{{.Task.WorkflowArgs.Namespace}} **开始时间**:{{ getStartTime .Task.StartTime}} \n" - build := "" - deploy := "" + build := []string{} test := "" - distribute := "" for _, subStage := range weChatNotification.Task.Stages { switch subStage.TaskType { case config.TaskBuild: - build = "**构建** \n" for _, sb := range subStage.SubTasks { + buildElemTemp := "" buildSt, err := base.ToBuildTask(sb) if err != nil { - return "", "", "", "", err + return "", "", nil, err } - branch := "" - commitID := "" - commitMsg := "" - gitCommitURL := "" - for idx, build := range buildSt.JobCtx.Builds { - if idx == 0 || build.IsPrimary { - branch = build.Branch - if len(build.CommitID) > 8 { - commitID = build.CommitID[0:8] + branch, commitID, commitMsg, gitCommitURL := "", "", "", "" + for idx, buildRepo := range buildSt.JobCtx.Builds { + if idx == 0 || buildRepo.IsPrimary { + branch = buildRepo.Branch + if len(buildRepo.CommitID) > 8 { + commitID = buildRepo.CommitID[0:8] + } + commitMsgs := strings.Split(buildRepo.CommitMessage, "\n") + if len(commitMsgs) > 0 { + commitMsg = commitMsgs[0] } - commitMsg = build.CommitMessage - gitCommitURL = fmt.Sprintf("%s/%s/%s/commit/%s", build.Address, build.RepoOwner, build.RepoName, commitID) + if len(commitMsg) > 40 { + commitMsg = commitMsg[0:40] + } + gitCommitURL = fmt.Sprintf("%s/%s/%s/commit/%s", buildRepo.Address, buildRepo.RepoOwner, buildRepo.RepoName, commitID) } } if buildSt.BuildStatus.Status == "" { buildSt.BuildStatus.Status = config.StatusNotRun } - if weChatNotification.WebHookType == weChatWorkType { - build += fmt.Sprintf("- %s/%s", getColorWithStatus(buildSt.BuildStatus.Status), buildSt.ServiceName, buildSt.Service) - } else { - build += fmt.Sprintf("- %s/%s", buildSt.ServiceName, buildSt.Service) - } - build += fmt.Sprintf(" status:%s [%s-%s](%s) commitMsg:%s \n", buildSt.BuildStatus.Status, branch, commitID, gitCommitURL, commitMsg) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**服务名称**:%s \n", buildSt.ServiceName) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.JobCtx.Image) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:[Branch-%s %s](%s) \n", branch, commitID, gitCommitURL) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**提交信息**:%s \n", commitMsg) + build = append(build, buildElemTemp) } - case config.TaskArtifact: - case config.TaskDeploy: - deploy = "**部署** \n" - for svrModule, sb := range subStage.SubTasks { - deploySt, err := base.ToDeployTask(sb) - if err != nil { - return "", "", "", "", err - } - if deploySt.TaskStatus == "" { - deploySt.TaskStatus = config.StatusNotRun - } - if weChatNotification.WebHookType == weChatWorkType { - deploy += fmt.Sprintf("- %s/%s", getColorWithStatus(deploySt.TaskStatus), svrModule, deploySt.ServiceName) - } else { - deploy += fmt.Sprintf("- %s/%s", svrModule, deploySt.ServiceName) - } - deploy += fmt.Sprintf(" status:%s image:%s \n", deploySt.TaskStatus, deploySt.Image) - } case config.TaskTestingV2: - test = "**测试** \n" + test = "{{if eq .WebHookType \"dingding\"}}##### {{end}}**测试结果** \n" for _, sb := range subStage.SubTasks { testSt, err := base.ToTestingTask(sb) if err != nil { - return "", "", "", "", err + return "", "", nil, err } if testSt.TaskStatus == "" { testSt.TaskStatus = config.StatusNotRun } - if weChatNotification.WebHookType == weChatWorkType { - test += fmt.Sprintf("- %s status:%s", getColorWithStatus(testSt.TaskStatus), testSt.TestName, testSt.TaskStatus) - } else { - test += fmt.Sprintf("- %s status:%s", testSt.TestName, testSt.TaskStatus) - } + test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testSt.TestModuleName) if weChatNotification.Task.TestReports == nil { - test += " \n" + test += fmt.Sprintf("%s \n", testSt.TaskStatus) continue } - suffixEndline := false + for testname, report := range weChatNotification.Task.TestReports { - if testname != testSt.TestName { + if testname != testSt.TestModuleName { continue } - tr := task.TestReport{} + tr := &task.TestReport{} if task.IToi(report, tr) != nil { log.Errorf("parse TestReport failed, err:%s", err) continue } if tr.FunctionTestSuite == nil { + test += fmt.Sprintf("%s \n", testSt.TaskStatus) continue } - suffixEndline = true - test += fmt.Sprintf(":%d(成功)%d(失败)%d(总数) \n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) - } - if !suffixEndline { - test += " \n" - } - } - case config.TaskDistribute, config.TaskDistributeToS3: - build = "**分发** \n" - for _, sb := range subStage.SubTasks { - distributeSt, err := base.ToDistributeToS3Task(sb) - if err != nil { - return "", "", "", "", err - } - if distributeSt.TaskStatus == "" { - distributeSt.TaskStatus = config.StatusNotRun - } - if weChatNotification.WebHookType == weChatWorkType { - distribute += fmt.Sprintf("- %s status:%s \n", getColorWithStatus(distributeSt.TaskStatus), distributeSt.ServiceName, distributeSt.TaskStatus) - } else { - distribute += fmt.Sprintf("- %s status:%s \n", distributeSt.ServiceName, distributeSt.TaskStatus) + test += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) } } } } - if weChatNotification.WebHookType == dingDingType { - if len(weChatNotification.AtMobiles) > 0 && !weChatNotification.IsAtAll { - tplcontent = fmt.Sprintf("%s \n 相关人员:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) - } - } buttonContent := "点击查看更多信息" workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/pipelines/{{ isSingle .IsSingle }}/{{.Task.PipelineName}}/{{.Task.TaskID}}" moreInformation := fmt.Sprintf("[%s](%s)", buttonContent, workflowDetailURL) - if weChatNotification.WebHookType == feiShuType { - tplcontent = fmt.Sprintf("%s%s%s%s%s", tplcontent, build, deploy, test, distribute) - } else { - tplcontent = fmt.Sprintf("%s%s%s%s%s%s", tplcontent, build, deploy, test, distribute, moreInformation) + tplTitle, _ = getTplExec(tplTitle, weChatNotification) + + if weChatNotification.WebHookType != feiShuType { + tplcontent := strings.Join(tplBaseInfo, "") + tplcontent += strings.Join(build, "") + tplcontent = fmt.Sprintf("%s%s", tplcontent, test) + if weChatNotification.WebHookType == dingDingType { + if len(weChatNotification.AtMobiles) > 0 && !weChatNotification.IsAtAll { + tplcontent = fmt.Sprintf("%s{{if eq .WebHookType \"dingding\"}}##### {{end}}**相关人员**:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) + } + } + tplcontent = fmt.Sprintf("%s%s%s", tplTitle, tplcontent, moreInformation) + tplExecContent, _ := getTplExec(tplcontent, weChatNotification) + return tplTitle, tplExecContent, nil, nil } - tplExecContent, _ := getTplExec(tplcontent, weChatNotification) - tplExecTitle, _ := getTplExec(tplTitle, weChatNotification) - execButtonContent, _ := getTplExec(buttonContent, weChatNotification) - execButtonURL, _ := getTplExec(workflowDetailURL, weChatNotification) - return tplExecTitle, tplExecContent, execButtonContent, execButtonURL, nil + lc := NewLarkCard() + lc.SetConfig(true) + lc.SetHeader(getColorTemplateWithStatus(weChatNotification.Task.Status), tplTitle, feiShuTagText) + for idx, feildContent := range tplBaseInfo { + feildExecContent, _ := getTplExec(feildContent, weChatNotification) + lc.AddI18NElementsZhcnFeild(feildExecContent, idx == 0) + } + for _, feildContent := range build { + feildExecContent, _ := getTplExec(feildContent, weChatNotification) + lc.AddI18NElementsZhcnFeild(feildExecContent, true) + } + if test != "" { + test, _ = getTplExec(test, weChatNotification) + lc.AddI18NElementsZhcnFeild(test, true) + } + workflowDetailURL, _ = getTplExec(workflowDetailURL, weChatNotification) + lc.AddI18NElementsZhcnAction(buttonContent, workflowDetailURL) + return "", "", lc, nil } -func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wechatNotification) (string, string, string, string, error) { - tplTitle := "#### {{if ne .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}} \n" - tplcontent := "**创建者**:{{.Task.TaskCreator}} **开始时间**:{{ getStartTime .Task.StartTime}} **持续时间**:{{ .TotalTime}} 秒 \n" + - "**测试描述**: " + desc + " \n" + - "**测试结果** \n" +func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wechatNotification) (string, string, *LarkCard, error) { + + tplTitle := "{{if ne .WebHookType \"feishu\"}}#### {{end}}{{getIcon .Task.Status }}{{if eq .WebHookType \"wechat\"}}工作流{{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{else}}工作流 {{.Task.PipelineName}} #{{.Task.TaskID}} {{ taskStatus .Task.Status }}{{end}} \n" + tplBaseInfo := []string{"{{if eq .WebHookType \"dingding\"}}##### {{end}}**执行用户**:{{.Task.TaskCreator}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**持续时间**:{{ getDuration .TotalTime}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**开始时间**:{{ getStartTime .Task.StartTime}} \n", + "{{if eq .WebHookType \"dingding\"}}##### {{end}}**测试描述**:" + desc + " \n", + } + + tplTestCaseInfo := "{{if eq .WebHookType \"dingding\"}}##### {{end}}**测试结果** \n" for _, stage := range weChatNotification.Task.Stages { if stage.TaskType != config.TaskTestingV2 { continue @@ -439,53 +419,65 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech log.Errorf("parse testInfo failed, err:%s", err) continue } - if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestReportPath != "" { - url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s\n", testInfo.TestName) - tplcontent += fmt.Sprintf("- [%s](%s) status:%s ", testName, url, testInfo.TaskStatus) + if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestResultPath != "" { + url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testInfo.TestName) + tplTestCaseInfo += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testName, url) if weChatNotification.Task.TestReports == nil { - tplcontent += "\n" + tplTestCaseInfo += fmt.Sprintf("%s \n ", testInfo.TaskStatus) continue } - suffixEndline := false for testname, report := range weChatNotification.Task.TestReports { - if testname != testInfo.TestName { + if testname != testInfo.TestModuleName { continue } - tr := task.TestReport{} + tr := &task.TestReport{} if task.IToi(report, tr) != nil { log.Errorf("parse TestReport failed, err:%s", err) continue } if tr.FunctionTestSuite == nil { + tplTestCaseInfo += fmt.Sprintf("%s \n ", testInfo.TaskStatus) continue } - suffixEndline = true - tplcontent += fmt.Sprintf(" %d(成功)%d(失败)%d(总数)\n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) - } - if !suffixEndline { - tplcontent += "\n" + tplTestCaseInfo += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) } } } } - if weChatNotification.WebHookType == dingDingType { - if len(weChatNotification.AtMobiles) > 0 && !weChatNotification.IsAtAll { - tplcontent = fmt.Sprintf("%s - 相关人员:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) - } - } buttonContent := "点击查看更多信息" workflowDetailURL := "{{.BaseURI}}/v1/projects/detail/{{.Task.ProductName}}/test/detail/function/{{.Task.PipelineName}}/{{.Task.TaskID}}" - moreInformation := fmt.Sprintf("[%s](%s)", buttonContent, workflowDetailURL) + moreInformation := fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}[%s](%s)", buttonContent, workflowDetailURL) + + tplTitle, _ = getTplExec(tplTitle, weChatNotification) + if weChatNotification.WebHookType != feiShuType { + tplcontent := strings.Join(tplBaseInfo, "") + tplcontent = fmt.Sprintf("%s%s", tplcontent, tplTestCaseInfo) + if weChatNotification.WebHookType == dingDingType { + if len(weChatNotification.AtMobiles) > 0 && !weChatNotification.IsAtAll { + tplcontent = fmt.Sprintf("%s{{if eq .WebHookType \"dingding\"}}##### {{end}}**相关人员**:@%s \n", tplcontent, strings.Join(weChatNotification.AtMobiles, "@")) + } + } tplcontent = fmt.Sprintf("%s%s%s", tplTitle, tplcontent, moreInformation) - } else { - tplTitle, _ = getTplExec(tplTitle, weChatNotification) - workflowDetailURL, _ = getTplExec(workflowDetailURL, weChatNotification) + tplExecContent, _ := getTplExec(tplcontent, weChatNotification) + return tplTitle, tplExecContent, nil, nil } - tplExecContent, _ := getTplExec(tplcontent, weChatNotification) + lc := NewLarkCard() + lc.SetConfig(true) + lc.SetHeader(getColorTemplateWithStatus(weChatNotification.Task.Status), tplTitle, feiShuTagText) + for idx, feildContent := range tplBaseInfo { + feildExecContent, _ := getTplExec(feildContent, weChatNotification) + lc.AddI18NElementsZhcnFeild(feildExecContent, idx == 0) + } + if tplTestCaseInfo != "" { + tplTestCaseInfo, _ = getTplExec(tplTestCaseInfo, weChatNotification) + lc.AddI18NElementsZhcnFeild(tplTestCaseInfo, true) + } + workflowDetailURL, _ = getTplExec(workflowDetailURL, weChatNotification) + lc.AddI18NElementsZhcnAction(buttonContent, workflowDetailURL) - return tplTitle, tplExecContent, buttonContent, workflowDetailURL, nil + return "", "", lc, nil } func getHTMLTestReport(task *task.Task) []string { @@ -535,13 +527,22 @@ func getTplExec(tplcontent string, weChatNotification *wechatNotification) (stri }, "taskStatus": func(status config.Status) string { if status == config.StatusPassed { - return "执行成功👍" + return "执行成功" + } + return "执行失败" + }, + "getIcon": func(status config.Status) string { + if status == config.StatusPassed { + return "👍" } - return "执行失败⚠️" + return "⚠️" }, "getStartTime": func(startTime int64) string { return time.Unix(startTime, 0).Format("2006-01-02 15:04:05") }, + "getDuration": func(startTime int64) string { + return util.GetResolveTimeHms(startTime) + }, }).Parse(tplcontent)) buffer := bytes.NewBufferString("") diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go index e9ce1152..33a69531 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go @@ -1,9 +1,23 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package instantmessage import ( "fmt" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" ) const ( @@ -55,14 +69,3 @@ func (w *Service) SendWeChatWorkMessage(textType TextType, uri, content string) _, err := w.SendMessageRequest(uri, message) return err } - -func getColorWithStatus(status config.Status) string { - if status == config.StatusPassed { - return markdownColorInfo - } else if status == config.StatusTimeout || status == config.StatusCancelled || status == config.StatusNotRun { - return markdownColorComment - } else if status == config.StatusFailed { - return markdownColorWarning - } - return markdownColorComment -} diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index 7f894c27..5bf23cf6 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -229,7 +229,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { if err != nil { return fmt.Errorf("get test previous task #%d notify, status: %s", ctx.TaskID-1, ctx.Status) } - if testPreTask.Status != task.Status { + if testPreTask.Status != task.Status && task.Status != config.StatusRunning { testTaskStatusChanged = true } } diff --git a/pkg/util/time.go b/pkg/util/time.go index 81d1e35d..29f5c5ba 100644 --- a/pkg/util/time.go +++ b/pkg/util/time.go @@ -42,3 +42,26 @@ func Age(unixTime int64) string { } return "0s" } + +func ResolveTime(seconds int64) (hour, minute, second int64) { + var day = seconds / (24 * 3600) + hour = (seconds - day*3600*24) / 3600 + minute = (seconds - day*24*3600 - hour*3600) / 60 + second = seconds - day*24*3600 - hour*3600 - minute*60 + return +} + +func GetResolveTimeHms(seconds int64) string { + duration := "" + h, m, s := ResolveTime(seconds) + if h > 0 { + duration = fmt.Sprintf("%dh", h) + } + if m >= 0 { + duration += fmt.Sprintf("%dm", m) + } + if s >= 0 { + duration += fmt.Sprintf("%ds", s) + } + return duration +} -- Gitee From d144fbfbf66bc47d037b7963965185277e73995a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 17 Jan 2022 11:44:37 +0800 Subject: [PATCH 1306/2647] enhance workflow variable type Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/build.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 1cf8392d..c9d405e5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -136,9 +136,11 @@ type ServiceModuleTarget struct { } type KeyVal struct { - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - IsCredential bool `bson:"is_credential" json:"is_credential"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + IsCredential bool `bson:"is_credential" json:"is_credential"` } type Item struct { -- Gitee From d538167f39623098c63e41e7181876921143afd4 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 17 Jan 2022 11:53:39 +0800 Subject: [PATCH 1307/2647] edit title Signed-off-by: ddh27 --- .../aslan/core/common/service/instantmessage/service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 971cce20..464a1d16 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -528,6 +528,10 @@ func getTplExec(tplcontent string, weChatNotification *wechatNotification) (stri "taskStatus": func(status config.Status) string { if status == config.StatusPassed { return "执行成功" + } else if status == config.StatusCancelled { + return "执行取消" + } else if status == config.StatusTimeout { + return "执行超时" } return "执行失败" }, -- Gitee From e50a319b63d0d682c7c20cafc91d0e93511acdfe Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 17 Jan 2022 12:06:52 +0800 Subject: [PATCH 1308/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 6f47beb5..1e2dfbe6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -432,6 +432,8 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) Key: moduleEnv.Key, Value: moduleEnv.Value, IsCredential: moduleEnv.IsCredential, + ChoiceOption: moduleEnv.ChoiceOption, + Type: moduleEnv.Type, }) } } -- Gitee From cdc8b2faa757d9688a5a611b5d91128e53dc0c01 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 17 Jan 2022 14:27:08 +0800 Subject: [PATCH 1309/2647] wd add field Signed-off-by: liu deyi --- .../warpdrive/core/service/types/task/build.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index ec47fe2c..c0e28f01 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -227,10 +227,19 @@ func (buildCtx *DockerBuildCtx) GetDockerFile() string { return buildCtx.DockerFile } +type ParameterSettingType string + +const ( + StringType ParameterSettingType = "string" + ChoiceType ParameterSettingType = "choice" +) + type KeyVal struct { - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - IsCredential bool `bson:"is_credential" json:"is_credential"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + IsCredential bool `bson:"is_credential" json:"is_credential"` } type Repository struct { -- Gitee From 20025773affaa8038876ede9283e61c48d2042a3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 14:32:46 +0800 Subject: [PATCH 1310/2647] query resources Signed-off-by: mouuii --- .../common/repository/mongodb/workflow.go | 2 +- .../aslan/core/label/handler/labels.go | 20 +++++--- .../aslan/core/label/handler/router.go | 6 ++- .../core/label/repository/mongodb/label.go | 12 +++++ .../label/repository/mongodb/labelbinding.go | 4 ++ .../aslan/core/label/service/label.go | 48 +++++++++++++++++++ 6 files changed, 83 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index 5837b067..fa2d91e1 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -81,7 +81,7 @@ func (c *WorkflowColl) List(opt *ListWorkflowOption) ([]*models.Workflow, error) } oids = append(oids, oid) } - query["_id"] = bson.M{"$in": opt.Ids} + query["_id"] = bson.M{"$in": oids} } resp := make([]*models.Workflow, 0) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 1b6a7ce7..fe69bb98 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -124,19 +124,27 @@ func ListResourceByLabels(c *gin.Context) { ctx.Resp, ctx.Err = service.ListResourcesByLabels(listResourceByLabelsReq.LabelFilters, ctx.Logger) } -type ListLabelsByResourceReq struct { - ResourceID string `json:"resource_id"` - ResourceType string `json:"resource_type"` -} - func ListLabelsByResource(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - listLabelsByResourceReq := new(ListLabelsByResourceReq) + listLabelsByResourceReq := new(service.ListLabelsByResourceReq) if err := c.ShouldBindJSON(listLabelsByResourceReq); err != nil { ctx.Err = err return } ctx.Resp, ctx.Err = service.ListLabelsByResourceID(listLabelsByResourceReq.ResourceID, listLabelsByResourceReq.ResourceType, ctx.Logger) } + +func ListLabelsByResources(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + listLabelsByResourcesReq := new(service.ListLabelsByResourcesReq) + + if err := c.ShouldBindJSON(&listLabelsByResourcesReq); err != nil { + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.ListLabelsByResourceIDs(listLabelsByResourcesReq, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 3dc9716e..5cacf854 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -29,8 +29,10 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.POST("/bulk-create", CreateLabels) labels.DELETE("/:id", DeleteLabel) labels.POST("/bulk-delete", DeleteLabels) - labels.POST("/resourcebylabels", ListResourceByLabels) - labels.POST("/labelsbyresource", ListLabelsByResource) + labels.POST("/resources-bylabels", ListResourceByLabels) + labels.POST("/labels-byresource", ListLabelsByResource) + labels.POST("/labels-byresources", ListLabelsByResources) + } labelBindings := router.Group("labelbindings") { diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 23341512..6853cfa9 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -119,6 +119,7 @@ type ListLabelOpt struct { Key string Values []string Type string + IDs []string } func (c *LabelColl) List(opt *ListLabelOpt) ([]*models.Label, error) { @@ -136,6 +137,17 @@ func (c *LabelColl) List(opt *ListLabelOpt) ([]*models.Label, error) { if opt.Type != "" { query["type"] = opt.Type } + if len(opt.IDs) != 0 { + var oids []primitive.ObjectID + for _, id := range opt.IDs { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + oids = append(oids, oid) + } + query["_id"] = bson.M{"$in": oids} + } opts := options.Find() opts.SetSort(bson.D{{"key", 1}}) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index cb68fc57..337ad713 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -79,6 +79,7 @@ type LabelBindingCollFindOpt struct { LabelID string LabelIDs []string ResourceID string + ResourcesIDs []string ResourceType string } @@ -104,6 +105,9 @@ func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.La if opt.ResourceID != "" { query["resource_id"] = opt.ResourceID } + if len(opt.ResourcesIDs) != 0 { + query["resource_id"] = bson.M{"$in": opt.ResourcesIDs} + } if opt.ResourceType != "" { query["resource_type"] = opt.ResourceType } diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 0cadf454..8f0b5274 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -18,6 +18,7 @@ package service import ( "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -123,6 +124,16 @@ func ListResourcesByLabels(filter []LabelFilter, logger *zap.SugaredLogger) ([]R return res, nil } +type ListLabelsByResourceReq struct { + ResourceID string `json:"resource_id"` + ResourceType string `json:"resource_type"` +} + +type ListLabelsByResourcesReq struct { + ResourceIDs []string `json:"resource_ids"` + ResourceType string `json:"resource_type"` +} + func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.SugaredLogger) ([]LabelResource, error) { labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{ ResourceID: resourceID, @@ -147,6 +158,43 @@ func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.Sugared return labelResources, nil } +func ListLabelsByResourceIDs(resources *ListLabelsByResourcesReq, logger *zap.SugaredLogger) (map[string][]LabelResource, error) { + labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{ + ResourcesIDs: resources.ResourceIDs, + ResourceType: resources.ResourceType, + }) + if err != nil { + logger.Errorf("list labelbinding err:%s", err) + return nil, err + } + labelIDs := sets.NewString() + for _, v := range labelBindings { + labelIDs.Insert(v.LabelID) + } + labelM := make(map[string]*models.Label, 0) + labels, err := mongodb.NewLabelColl().List(&mongodb.ListLabelOpt{ + IDs: labelIDs.List(), + }) + for _, v := range labels { + labelM[v.ID.Hex()] = v + } + if err != nil { + return nil, err + } + resources2LabelResourceM := make(map[string][]LabelResource, 0) + for _, v := range labelBindings { + if label, ok := labelM[v.LabelID]; ok { + resources2LabelResourceM[v.ResourceID] = append(resources2LabelResourceM[v.ResourceID], LabelResource{ + Key: label.Key, + Value: label.Value, + ResourceID: v.ResourceID, + }) + } + + } + return resources2LabelResourceM, nil +} + type DeleteLabelsArgs struct { IDs []string } -- Gitee From f8b689b695bf01d7b8c44a0e55ff2088b9207ae8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 15:47:57 +0800 Subject: [PATCH 1311/2647] branch page Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/handler/codehost.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index ba5f0741..af2700e4 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -63,8 +63,8 @@ func CodeHostGetNamespaceList(c *gin.Context) { } type CodeHostListProjectsArgs struct { - PerPage int `json:"per_page" form:"per_page,default:30"` - Page int `json:"page" form:"page,default:1"` + PerPage int `json:"per_page" form:"per_page,default=30"` + Page int `json:"page" form:"page,default=1"` Key string `json:"key" form:"key"` } @@ -111,8 +111,8 @@ func CodeHostGetProjectsList(c *gin.Context) { } type CodeHostGetBranchListArgs struct { - PerPage int `json:"per_page" form:"per_page,default:30"` - Page int `json:"page" form:"page,default:1"` + PerPage int `json:"per_page" form:"per_page,default=30"` + Page int `json:"page" form:"page,default=1"` Key string `json:"key" form:"key"` } -- Gitee From e4cbc1c888f6e061437d7da690f24553a2e0457d Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 17 Jan 2022 16:00:45 +0800 Subject: [PATCH 1312/2647] optimize code Signed-off-by: allenshen --- .../core/environment/service/environment.go | 21 ++++++------------- .../aslan/core/environment/service/image.go | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f95f752d..d1540df2 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2234,16 +2234,16 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { +func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err } - return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, timeout, isRetry, helmClient, kubecli) + return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, isRetry, helmClient, kubecli) } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, timeout time.Duration, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2275,10 +2275,6 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha if isRetry { chartSpec.Replace = true } - if timeout > 0 { - chartSpec.Wait = true - chartSpec.Timeout = timeout - } done := make(chan bool) go func(chan bool) { @@ -2367,9 +2363,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] - timeout := time.Second * setting.DeployTimeout - timeout = 0 - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, timeout, isRetry, helmClient, kubecli) + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) @@ -2583,9 +2577,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] - timeout := time.Second * setting.DeployTimeout - timeout = 0 - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, timeout, isRetry, helmClient, kubecli) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) @@ -2837,8 +2829,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel handler := func(service *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - timeout := setting.UpdateEnvTimeout - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, timeout, isRetry, helmClient, kubecli) + err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, isRetry, helmClient, kubecli) if err != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 3a0c454d..e607a716 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -181,7 +181,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri } // when replace image, should not wait - err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, 0, false, helmClient, cl) + err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, false, helmClient, cl) if err != nil { return err } -- Gitee From 142c34f548cd9f39592f365847d969bb4082d4bb Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 17 Jan 2022 16:34:28 +0800 Subject: [PATCH 1313/2647] fix review Signed-off-by: ddh27 --- .../common/repository/models/task/model.go | 3 +-- .../common/service/instantmessage/service.go | 21 ++++++++++++----- .../service/instantmessage/wechatwork.go | 2 ++ .../core/common/service/notify/client.go | 5 ++-- pkg/util/time.go | 23 ------------------- 5 files changed, 20 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 6e80028c..3e28083b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -107,9 +107,8 @@ type TestReport struct { Security map[string]map[string]int `bson:"security,omitempty" json:"security,omitempty"` } -// TestSuite ... type TestSuite struct { - // 总数=tests+skips 成功=tests-failures-errors + // totalNum=tests+skips successNum=tests-failures-errors Tests int `bson:"tests" json:"tests" xml:"tests,attr"` Failures int `bson:"failures" json:"failures" xml:"failures,attr"` Successes int `bson:"successes,omitempty" json:"successes,omitempty" xml:"successes,attr,omitempty"` diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 464a1d16..295d2efb 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -19,6 +19,7 @@ package instantmessage import ( "bytes" "fmt" + "strconv" "strings" "text/template" "time" @@ -33,7 +34,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util" ) const ( @@ -42,8 +42,6 @@ const ( multiInfo = "multi" ) -type TextType string - type Service struct { proxyColl *mongodb.ProxyColl workflowColl *mongodb.WorkflowColl @@ -353,7 +351,10 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific test += fmt.Sprintf("%s \n", testSt.TaskStatus) continue } - test += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + totalNum := tr.FunctionTestSuite.Tests + tr.FunctionTestSuite.Skips + failedNum := tr.FunctionTestSuite.Failures + tr.FunctionTestSuite.Errors + successNum := tr.FunctionTestSuite.Tests - failedNum + test += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", successNum, failedNum, totalNum) } } } @@ -439,7 +440,10 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech tplTestCaseInfo += fmt.Sprintf("%s \n ", testInfo.TaskStatus) continue } - tplTestCaseInfo += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", tr.FunctionTestSuite.Successes, tr.FunctionTestSuite.Failures, tr.FunctionTestSuite.Tests) + totalNum := tr.FunctionTestSuite.Tests + tr.FunctionTestSuite.Skips + failedNum := tr.FunctionTestSuite.Failures + tr.FunctionTestSuite.Errors + successNum := tr.FunctionTestSuite.Tests - failedNum + tplTestCaseInfo += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", successNum, failedNum, totalNum) } } } @@ -545,7 +549,12 @@ func getTplExec(tplcontent string, weChatNotification *wechatNotification) (stri return time.Unix(startTime, 0).Format("2006-01-02 15:04:05") }, "getDuration": func(startTime int64) string { - return util.GetResolveTimeHms(startTime) + duration, er := time.ParseDuration(strconv.FormatInt(startTime, 10) + "s") + if er != nil { + log.Errorf("getTplExec ParseDuration err:%s", er) + return "0s" + } + return duration.String() }, }).Parse(tplcontent)) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go index 33a69531..bea4a6cc 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go @@ -29,6 +29,8 @@ const ( weChatTextTypeMarkdown TextType = "markdown" ) +type TextType string + type WeChatWorkCard struct { MsgType string `json:"msgtype"` Markdown Markdown `json:"markdown"` diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index 5bf23cf6..d8b951a9 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -227,7 +227,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { if ctx.TaskID > 1 { testPreTask, err := c.taskColl.Find(ctx.TaskID-1, ctx.PipelineName, ctx.Type) if err != nil { - return fmt.Errorf("get test previous task #%d notify, status: %s", ctx.TaskID-1, ctx.Status) + return fmt.Errorf("get test previous task #%d notify, status: %s,err:%s", ctx.TaskID-1, ctx.Status, err) } if testPreTask.Status != task.Status && task.Status != config.StatusRunning { testTaskStatusChanged = true @@ -235,10 +235,9 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } } - //发送IM通知 err = c.InstantmessageService.SendInstantMessage(task, testTaskStatusChanged) if err != nil { - return fmt.Errorf("SendInstantMessage err : %v", err) + return fmt.Errorf("SendInstantMessage err : %s", err) } for _, receiver := range receivers { diff --git a/pkg/util/time.go b/pkg/util/time.go index 29f5c5ba..81d1e35d 100644 --- a/pkg/util/time.go +++ b/pkg/util/time.go @@ -42,26 +42,3 @@ func Age(unixTime int64) string { } return "0s" } - -func ResolveTime(seconds int64) (hour, minute, second int64) { - var day = seconds / (24 * 3600) - hour = (seconds - day*3600*24) / 3600 - minute = (seconds - day*24*3600 - hour*3600) / 60 - second = seconds - day*24*3600 - hour*3600 - minute*60 - return -} - -func GetResolveTimeHms(seconds int64) string { - duration := "" - h, m, s := ResolveTime(seconds) - if h > 0 { - duration = fmt.Sprintf("%dh", h) - } - if m >= 0 { - duration += fmt.Sprintf("%dm", m) - } - if s >= 0 { - duration += fmt.Sprintf("%ds", s) - } - return duration -} -- Gitee From 7bf4ed6982f78923d7d9c0c636d9ba8805c8d3e2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 17 Jan 2022 16:48:11 +0800 Subject: [PATCH 1314/2647] modify copyright date Signed-off-by: liu deyi --- hack/boilerplate.go.txt | 2 +- pkg/microservice/aslan/core/workflow/handler/workflow_task.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 65eb5b0b..725b5825 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 6440e978..eeb4b866 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From b542593c372731c25f52016a08f0a95d2eb05593 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 17 Jan 2022 16:55:30 +0800 Subject: [PATCH 1315/2647] fix review Signed-off-by: ddh27 --- .../aslan/core/common/service/instantmessage/dingTalk.go | 4 ++-- .../aslan/core/common/service/instantmessage/lark.go | 5 ++--- .../aslan/core/common/service/instantmessage/service.go | 2 +- .../aslan/core/common/service/instantmessage/wechatwork.go | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go b/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go index 52d5dc4e..75895de8 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/dingTalk.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ type DingDingAt struct { IsAtAll bool `json:"isAtAll"` } -func (w *Service) sendDingDingMessage(uri string, title string, content string, atMobiles []string) error { +func (w *Service) sendDingDingMessage(uri, title, content string, atMobiles []string) error { message := &DingDingMessage{ MsgType: msgType, MarkDown: &DingDingMarkDown{ diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go index 5fabb963..18789800 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/lark.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/lark.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -180,8 +180,7 @@ func (lc *LarkCard) AddI18NElementsZhcnAction(content, url string) { } func (w *Service) sendFeishuMessage(uri string, lcMsg *LarkCard) error { - var message interface{} - message = LarkCardReq{ + message := LarkCardReq{ MsgType: feishuCardType, Card: lcMsg, } diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 295d2efb..d82d9f7b 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go index bea4a6cc..49af4bb1 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/wechatwork.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 60ebb0f556c7cebc6829cb4b5e53536bfd20353a Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 17 Jan 2022 17:02:40 +0800 Subject: [PATCH 1316/2647] optimize Signed-off-by: panxunying --- .../aslan/core/label/handler/router.go | 6 ++--- .../policy/core/handler/policy.go | 20 +++++++-------- pkg/microservice/policy/core/handler/role.go | 16 ++++++------ .../policy/core/repository/models/common.go | 10 ++++++-- .../policy/core/repository/models/policy.go | 11 +++++--- .../policy/core/service/common.go | 13 +++++++--- .../policy/core/service/policy.go | 25 +++++++++++-------- 7 files changed, 61 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 5cacf854..685569a6 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -29,9 +29,9 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.POST("/bulk-create", CreateLabels) labels.DELETE("/:id", DeleteLabel) labels.POST("/bulk-delete", DeleteLabels) - labels.POST("/resources-bylabels", ListResourceByLabels) - labels.POST("/labels-byresource", ListLabelsByResource) - labels.POST("/labels-byresources", ListLabelsByResources) + labels.POST("/resources-by-labels", ListResourceByLabels) + labels.POST("/labels-by-resource", ListLabelsByResource) + labels.POST("/labels-by-resources", ListLabelsByResources) } labelBindings := router.Group("labelbindings") diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go index ffac73a8..226c6105 100644 --- a/pkg/microservice/policy/core/handler/policy.go +++ b/pkg/microservice/policy/core/handler/policy.go @@ -99,7 +99,7 @@ func UpdatePublicPolicy(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdatePolicy("", args, ctx.Logger) + ctx.Err = service.UpdatePolicy(service.PublicScope, args, ctx.Logger) } func UpdateOrCreatePublicPolicy(c *gin.Context) { @@ -113,7 +113,7 @@ func UpdateOrCreatePublicPolicy(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateOrCreatePolicy("", args, ctx.Logger) + ctx.Err = service.UpdateOrCreatePolicy(service.PublicScope, args, ctx.Logger) } func ListPolicies(c *gin.Context) { @@ -152,14 +152,14 @@ func CreatePublicPolicy(c *gin.Context) { return } - ctx.Err = service.CreatePolicy("", args, ctx.Logger) + ctx.Err = service.CreatePolicy(service.PublicScope, args, ctx.Logger) } func ListPublicPolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListPolicies("", ctx.Logger) + ctx.Resp, ctx.Err = service.ListPolicies(service.PublicScope, ctx.Logger) return } @@ -167,7 +167,7 @@ func GetPublicPolicy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetPolicy("", c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetPolicy(service.PublicScope, c.Param("name"), ctx.Logger) } func DeletePolicy(c *gin.Context) { @@ -207,7 +207,7 @@ func DeletePublicPolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - ctx.Err = service.DeletePolicy(name, "", ctx.Logger) + ctx.Err = service.DeletePolicy(name, service.PublicScope, ctx.Logger) return } @@ -221,7 +221,7 @@ func CreateSystemPolicy(c *gin.Context) { return } - ctx.Err = service.CreatePolicy("*", args, ctx.Logger) + ctx.Err = service.CreatePolicy(service.SystemScope, args, ctx.Logger) } func UpdateOrCreateSystemPolicy(c *gin.Context) { @@ -235,14 +235,14 @@ func UpdateOrCreateSystemPolicy(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateOrCreatePolicy("*", args, ctx.Logger) + ctx.Err = service.UpdateOrCreatePolicy(service.SystemScope, args, ctx.Logger) } func ListSystemPolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListPolicies("*", ctx.Logger) + ctx.Resp, ctx.Err = service.ListPolicies(service.SystemScope, ctx.Logger) return } @@ -250,6 +250,6 @@ func DeleteSystemPolicy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - ctx.Err = service.DeletePolicy(name, "*", ctx.Logger) + ctx.Err = service.DeletePolicy(name, service.SystemScope, ctx.Logger) return } diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index e9db1338..913a0233 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -152,14 +152,14 @@ func CreatePublicRole(c *gin.Context) { return } - ctx.Err = service.CreateRole("", args, ctx.Logger) + ctx.Err = service.CreateRole(service.PublicScope, args, ctx.Logger) } func ListPublicRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListRoles("", ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoles(service.PublicScope, ctx.Logger) return } @@ -167,7 +167,7 @@ func GetPublicRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetRole("", c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetRole(service.PublicScope, c.Param("name"), ctx.Logger) } func DeleteRole(c *gin.Context) { @@ -207,7 +207,7 @@ func DeletePublicRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - ctx.Err = service.DeleteRole(name, "", ctx.Logger) + ctx.Err = service.DeleteRole(name, service.PublicScope, ctx.Logger) return } @@ -221,7 +221,7 @@ func CreateSystemRole(c *gin.Context) { return } - ctx.Err = service.CreateRole("*", args, ctx.Logger) + ctx.Err = service.CreateRole(service.SystemScope, args, ctx.Logger) } func UpdateOrCreateSystemRole(c *gin.Context) { @@ -235,14 +235,14 @@ func UpdateOrCreateSystemRole(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateOrCreateRole("*", args, ctx.Logger) + ctx.Err = service.UpdateOrCreateRole(service.SystemScope, args, ctx.Logger) } func ListSystemRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListRoles("*", ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoles(service.SystemScope, ctx.Logger) return } @@ -250,6 +250,6 @@ func DeleteSystemRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - ctx.Err = service.DeleteRole(name, "*", ctx.Logger) + ctx.Err = service.DeleteRole(name, service.SystemScope, ctx.Logger) return } diff --git a/pkg/microservice/policy/core/repository/models/common.go b/pkg/microservice/policy/core/repository/models/common.go index 4ee7ebf1..c9d7faed 100644 --- a/pkg/microservice/policy/core/repository/models/common.go +++ b/pkg/microservice/policy/core/repository/models/common.go @@ -17,8 +17,14 @@ type Rule struct { Verbs []string `bson:"verbs" json:"verbs"` // Resources is a list of resources this rule applies to. '*' represents all resources. - Resources []string `bson:"resources" json:"resources"` - Kind string `bson:"kind" json:"kind"` + Resources []string `bson:"resources" json:"resources"` + Kind string `bson:"kind" json:"kind"` + MatchAttributes []MatchAttribute `json:"match_attributes"` +} + +type MatchAttribute struct { + Key string `json:"key"` + Value string `json:"value"` } // Subject contains a reference to the object or user identities a role binding applies to. diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index cb0d674f..abba69d0 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -19,9 +19,14 @@ package models // Policy is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a PolicyBinding. // for a cluster scoped Policy, namespace is empty. type Policy struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Rules []*Rule `bson:"rules" json:"rules"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Description string `json:"description"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Rules []*Rule `bson:"rules" json:"rules"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` } func (Policy) TableName() string { diff --git a/pkg/microservice/policy/core/service/common.go b/pkg/microservice/policy/core/service/common.go index 50e725a4..d068de13 100644 --- a/pkg/microservice/policy/core/service/common.go +++ b/pkg/microservice/policy/core/service/common.go @@ -1,9 +1,16 @@ package service +import ( + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" +) + type Rule struct { - Verbs []string `json:"verbs"` - Resources []string `json:"resources"` - Kind string `json:"kind"` + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + MatchAttributes []models.MatchAttribute `json:"match_attributes"` + RelatedResources []string `json:"related_resources"` } const SystemScope = "*" +const PublicScope = "" diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 1ce6ea9b..af63e97e 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -23,12 +23,17 @@ import ( "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" - "github.com/koderover/zadig/pkg/setting" ) type Policy struct { - Name string `json:"name"` - Rules []*Rule `json:"rules,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + UpdateTime int64 `json:"update_time"` + Rules []*Rule `json:"rules,omitempty"` +} + +type ListPolicyResp struct { + Policies []Policy `json:"policies"` } func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { @@ -39,9 +44,10 @@ func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { for _, r := range policy.Rules { obj.Rules = append(obj.Rules, &models.Rule{ - Verbs: r.Verbs, - Kind: r.Kind, - Resources: r.Resources, + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + MatchAttributes: r.MatchAttributes, }) } @@ -87,12 +93,9 @@ func ListPolicies(projectName string, _ *zap.SugaredLogger) ([]*Policy, error) { return nil, err } for _, v := range projectPolicies { - // frontend doesn't need to see contributor role - if v.Name == string(setting.Contributor) { - continue - } policies = append(policies, &Policy{ - Name: v.Name, + Name: v.Name, + Description: v.Description, }) } return policies, nil -- Gitee From 2e094bc6f4974de22989a245205273be8330e1a7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 18:49:44 +0800 Subject: [PATCH 1317/2647] get workflow resource Signed-off-by: mouuii --- .../aslan/core/workflow/handler/bundle.go | 31 ++++++++++++ .../aslan/core/workflow/handler/router.go | 5 ++ .../core/workflow/service/workflow/bundle.go | 47 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 pkg/microservice/aslan/core/workflow/handler/bundle.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/bundle.go diff --git a/pkg/microservice/aslan/core/workflow/handler/bundle.go b/pkg/microservice/aslan/core/workflow/handler/bundle.go new file mode 100644 index 00000000..d1edc396 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/bundle.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetBundleResources(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = workflow.GetBundleResources() +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 02aee1b6..bb9c0c1e 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -160,4 +160,9 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) } + + bundles := router.Group("bundle-resources") + { + bundles.GET("", GetBundleResources) + } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go new file mode 100644 index 00000000..ce6da272 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -0,0 +1,47 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workflow + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/log" +) + +type resourceSpec struct { + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec map[string]interface{} `json:"spec"` +} + +func GetBundleResources() ([]*resourceSpec, error) { + var res []*resourceSpec + workflows, err := mongodb.NewWorkflowColl().List(nil) + if err != nil { + log.Error("Failed to list workflows , err:%s", err) + return nil, err + } + // get labels by workflow resources ids + // TODO - mouuii + for _, workflow := range workflows { + res = append(res, &resourceSpec{ + ResourceID: workflow.ID.Hex(), + ProjectName: workflow.ProductTmplName, + Spec: map[string]interface{}{}, + }) + } + return res, nil +} -- Gitee From d3ede99e103bb27d31686a12f82cf9d929a05dc4 Mon Sep 17 00:00:00 2001 From: fansi Date: Mon, 17 Jan 2022 20:22:50 +0800 Subject: [PATCH 1318/2647] add log for microservice-demo Signed-off-by: fansi --- examples/microservice-demo/backend/backend.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/microservice-demo/backend/backend.go b/examples/microservice-demo/backend/backend.go index eccde3bb..8c89b5d4 100644 --- a/examples/microservice-demo/backend/backend.go +++ b/examples/microservice-demo/backend/backend.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "log" "net/http" ) @@ -44,6 +45,8 @@ func buildStamp(w http.ResponseWriter, req *http.Request) { func main() { + log.Print("hello, welcome to the microservice world.\n") + http.HandleFunc("/", hello) http.HandleFunc("/api/buildstamp", buildStamp) http.HandleFunc("/headers", headers) -- Gitee From a19e64de5d089e187adadadb67bf04611b12a798 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 17 Jan 2022 21:27:55 +0800 Subject: [PATCH 1319/2647] change id to projectname + name Signed-off-by: mouuii --- .../core/common/repository/mongodb/product.go | 36 ++++ .../common/repository/mongodb/workflow.go | 36 ++++ .../aslan/core/label/handler/labels.go | 48 ++---- .../aslan/core/label/handler/labelsbinding.go | 16 +- .../aslan/core/label/handler/router.go | 7 +- .../label/repository/models/labelbinding.go | 3 +- .../core/label/repository/mongodb/label.go | 105 ++++-------- .../label/repository/mongodb/labelbinding.go | 3 +- .../aslan/core/label/service/label.go | 159 ++---------------- .../aslan/core/label/service/labelsbinding.go | 116 ++++++++----- 10 files changed, 216 insertions(+), 313 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 65f36269..b5053be8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -141,6 +141,42 @@ func (c *ProductColl) Find(opt *ProductFindOptions) (*models.Product, error) { return res, err } +type Product struct { + Name string `json:"name"` + ProjectName string `json:"projectName"` +} + +type ListProductOpt struct { + Products []Product +} + +func (c *ProductColl) ListByProducts(opt ListProductOpt) ([]*models.Product, error) { + var res []*models.Product + + if len(opt.Products) == 0 { + return nil, nil + } + condition := bson.A{} + for _, pro := range opt.Products { + condition = append(condition, bson.M{ + "name": pro.Name, + "product_name": pro.ProjectName, + }) + } + filter := bson.D{{"$or", condition}} + cursor, err := c.Collection.Find(context.TODO(), filter) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + return res, nil +} + func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { var ret []*models.Product query := bson.M{} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index fa2d91e1..c3bb2ff8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -38,6 +38,42 @@ type ListWorkflowOption struct { Ids []string } +type Workflow struct { + Name string `json:"name"` + ProjectName string `json:"projectName"` +} + +type ListWorkflowOpt struct { + Workflows []Workflow +} + +func (c *WorkflowColl) ListByWorkflows(opt ListWorkflowOpt) ([]*models.Workflow, error) { + var res []*models.Workflow + + if len(opt.Workflows) == 0 { + return nil, nil + } + condition := bson.A{} + for _, workflow := range opt.Workflows { + condition = append(condition, bson.M{ + "name": workflow.Name, + "product_tmpl_name": workflow.ProjectName, + }) + } + filter := bson.D{{"$or", condition}} + cursor, err := c.Collection.Find(context.TODO(), filter) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + return res, nil +} + type WorkflowColl struct { *mongo.Collection diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index fe69bb98..889932a0 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -32,7 +32,7 @@ import ( func ListLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - listLabelsArgs := make([]*service.ListLabelsArgs, 0) + listLabelsArgs := new(service.ListLabelsArgs) if err := c.ShouldBindJSON(&listLabelsArgs); err != nil { ctx.Err = err return @@ -40,8 +40,8 @@ func ListLabels(c *gin.Context) { ctx.Resp, ctx.Err = service.ListLabels(listLabelsArgs) } -func createLabelValidate(lbs []*models.Label) error { - for _, v := range lbs { +func createLabelValidate(lbs *service.CreateLabelsArgs) error { + for _, v := range lbs.Labels { if v.Key == "" || v.Value == "" { return e.ErrInvalidParam.AddDesc("invalid label args") } @@ -53,7 +53,7 @@ func CreateLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - labels := make([]*models.Label, 0) + labels := new(service.CreateLabelsArgs) if err := c.ShouldBindJSON(&labels); err != nil { ctx.Err = err return @@ -65,14 +65,18 @@ func CreateLabels(c *gin.Context) { } filteredLabels := make([]*models.Label, 0) keyValues := sets.NewString() - for _, v := range labels { + for _, v := range labels.Labels { keyValue := fmt.Sprintf("%s-%s", v.Key, v.Value) if keyValues.Has(keyValue) { continue } keyValues.Insert(keyValue) - v.CreateBy = ctx.UserName - filteredLabels = append(filteredLabels, v) + tmpModel := models.Label{ + Key: v.Key, + Value: v.Value, + CreateBy: ctx.UserName, + } + filteredLabels = append(filteredLabels, &tmpModel) } ctx.Err = service.CreateLabels(filteredLabels) } @@ -95,24 +99,6 @@ func DeleteLabels(c *gin.Context) { ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.Logger) } -func DeleteLabel(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - id := c.Param("id") - if id == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("id must not be empty") - return - } - force := c.Query("force") - forceBool, err := strconv.ParseBool(force) - if err != nil { - ctx.Err = err - return - } - ctx.Err = service.DeleteLabel(id, forceBool, ctx.Logger) -} - func ListResourceByLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -124,18 +110,6 @@ func ListResourceByLabels(c *gin.Context) { ctx.Resp, ctx.Err = service.ListResourcesByLabels(listResourceByLabelsReq.LabelFilters, ctx.Logger) } -func ListLabelsByResource(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - listLabelsByResourceReq := new(service.ListLabelsByResourceReq) - if err := c.ShouldBindJSON(listLabelsByResourceReq); err != nil { - ctx.Err = err - return - } - ctx.Resp, ctx.Err = service.ListLabelsByResourceID(listLabelsByResourceReq.ResourceID, listLabelsByResourceReq.ResourceType, ctx.Logger) -} - func ListLabelsByResources(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/labelsbinding.go index 9e7a9413..2a401e65 100644 --- a/pkg/microservice/aslan/core/label/handler/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/handler/labelsbinding.go @@ -24,16 +24,11 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListLabelBindings(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.ListLabelsBinding() -} - func createLBValidate(cr *service.CreateLabelBindingsArgs) error { - if cr.LabelID == "" || cr.ResourceType == "" || len(cr.ResourceIDs) == 0 { - return e.ErrInvalidParam.AddDesc("invalid labelbinding args") + for _, v := range cr.CreateLabelBindings { + if v.LabelID == "" || v.Resource.Type == "" { + return e.ErrInvalidParam.AddDesc("invalid labelbinding args") + } } return nil } @@ -51,8 +46,7 @@ func CreateLabelBindings(c *gin.Context) { ctx.Err = err return } - createLabelBindingsArgs.CreateBy = ctx.UserName - ctx.Err = service.CreateLabelsBinding(createLabelBindingsArgs, ctx.Logger) + ctx.Err = service.CreateLabelBindings(createLabelBindingsArgs, ctx.UserName, ctx.Logger) } func DeleteLabelBindings(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 5cacf854..b519b7bd 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -27,16 +27,13 @@ func (*Router) Inject(router *gin.RouterGroup) { { labels.POST("/filter", ListLabels) labels.POST("/bulk-create", CreateLabels) - labels.DELETE("/:id", DeleteLabel) labels.POST("/bulk-delete", DeleteLabels) - labels.POST("/resources-bylabels", ListResourceByLabels) - labels.POST("/labels-byresource", ListLabelsByResource) - labels.POST("/labels-byresources", ListLabelsByResources) + labels.POST("/resources-by-labels", ListResourceByLabels) + labels.POST("/labels-by-resources", ListLabelsByResources) } labelBindings := router.Group("labelbindings") { - labelBindings.GET("", ListLabelBindings) labelBindings.POST("", CreateLabelBindings) labelBindings.POST("/bulk-delete", DeleteLabelBindings) } diff --git a/pkg/microservice/aslan/core/label/repository/models/labelbinding.go b/pkg/microservice/aslan/core/label/repository/models/labelbinding.go index 23d4e57c..e67f86e6 100644 --- a/pkg/microservice/aslan/core/label/repository/models/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/models/labelbinding.go @@ -23,7 +23,8 @@ import ( type LabelBinding struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ResourceType string `bson:"resource_type" json:"resource_type"` - ResourceID string `bson:"resource_id" json:"resource_id"` + ResourceName string `bson:"resource_name" json:"resource_name"` + ProjectName string `bson:"project_name" json:"project_name"` LabelID string `bson:"label_id" json:"label_id"` CreateBy string `bson:"create_by" json:"create_by"` CreateTime int64 `bson:"create_time" json:"create_time"` diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 6853cfa9..9b8bf61e 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -1,38 +1,32 @@ /* Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - package mongodb import ( "context" "time" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) type LabelColl struct { *mongo.Collection - coll string } @@ -40,11 +34,9 @@ func NewLabelColl() *LabelColl { name := models.Label{}.TableName() return &LabelColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } - func (c *LabelColl) GetCollectionName() string { return c.coll } - func (c *LabelColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ Keys: bson.D{ @@ -53,11 +45,9 @@ func (c *LabelColl) EnsureIndex(ctx context.Context) error { }, Options: options.Index().SetUnique(true), } - _, err := c.Indexes().CreateOne(ctx, mod) return err } - func (c *LabelColl) BulkCreate(labels []*models.Label) error { if len(labels) == 0 { return nil @@ -65,16 +55,13 @@ func (c *LabelColl) BulkCreate(labels []*models.Label) error { for _, label := range labels { label.CreateTime = time.Now().Unix() } - var ois []interface{} for _, obj := range labels { ois = append(ois, obj) } - _, err := c.InsertMany(context.TODO(), ois) return err } - func (c *LabelColl) Delete(id string) error { oid, err := primitive.ObjectIDFromHex(id) if err != nil { @@ -84,7 +71,6 @@ func (c *LabelColl) Delete(id string) error { _, err = c.DeleteOne(context.TODO(), query) return err } - func (c *LabelColl) BulkDelete(ids []string) error { if len(ids) == 0 { return nil @@ -115,78 +101,61 @@ func (c *LabelColl) Find(id string) (*models.Label, error) { return res, err } +type Label struct { + Key string `json:"key"` + Value string `json:"value"` +} + type ListLabelOpt struct { - Key string - Values []string - Type string - IDs []string + Labels []Label } -func (c *LabelColl) List(opt *ListLabelOpt) ([]*models.Label, error) { +func (c *LabelColl) List(opt ListLabelOpt) ([]*models.Label, error) { var res []*models.Label - - ctx := context.Background() - - query := bson.M{} - if opt.Key != "" { - query["key"] = opt.Key + if len(opt.Labels) == 0 { + return nil, nil } - if len(opt.Values) != 0 { - query["value"] = bson.M{"$in": opt.Values} + condition := bson.A{} + for _, label := range opt.Labels { + condition = append(condition, bson.M{ + "key": label.Key, + "value": label.Value, + }) } - if opt.Type != "" { - query["type"] = opt.Type + filter := bson.D{{"$or", condition}} + cursor, err := c.Collection.Find(context.TODO(), filter) + if err == mongo.ErrNoDocuments { + return nil, nil } - if len(opt.IDs) != 0 { - var oids []primitive.ObjectID - for _, id := range opt.IDs { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return nil, err - } - oids = append(oids, oid) - } - query["_id"] = bson.M{"$in": oids} - } - opts := options.Find() - opts.SetSort(bson.D{{"key", 1}}) - - cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } - - err = cursor.All(ctx, &res) - if err != nil { + if err := cursor.All(context.TODO(), &res); err != nil { return nil, err } return res, nil } - -func (c *LabelColl) Filter(opts []*ListLabelOpt) ([]*models.Label, error) { +func (c *LabelColl) ListByIDs(ids []string) ([]*models.Label, error) { var res []*models.Label ctx := context.Background() - conditions := bson.A{} - filter := bson.D{} - if len(opts) > 0 { - for _, findOpt := range opts { - con := bson.M{ - "key": findOpt.Key, - } - if len(findOpt.Values) > 0 { - con["value"] = bson.M{"$in": findOpt.Values} + query := bson.M{} + if len(ids) != 0 { + var oids []primitive.ObjectID + for _, id := range ids { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err } - - conditions = append(conditions, con) + oids = append(oids, oid) } - filter = bson.D{{"$or", conditions}} + query["_id"] = bson.M{"$in": oids} } - - cursor, err := c.Collection.Find(ctx, filter) + opts := options.Find() + opts.SetSort(bson.D{{"key", 1}}) + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } - err = cursor.All(ctx, &res) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index 337ad713..cc6e4623 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -48,7 +48,8 @@ func (c *LabelBindingColl) GetCollectionName() string { func (c *LabelBindingColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ Keys: bson.D{ - bson.E{Key: "resource_id", Value: 1}, + bson.E{Key: "resource_name", Value: 1}, + bson.E{Key: "project_name", Value: 1}, bson.E{Key: "label_id", Value: 1}, bson.E{Key: "resource_type", Value: 1}, }, diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 8f0b5274..cae2d5c5 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -18,7 +18,6 @@ package service import ( "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -52,21 +51,15 @@ func CreateLabels(labels []*models.Label) error { } type ListLabelsArgs struct { - Key string `json:"key" form:"key"` - Values []string `json:"values" form:"values"` + Labels []mongodb.Label `json:"labels"` } -func ListLabels(args []*ListLabelsArgs) ([]*models.Label, error) { - opts := make([]*mongodb.ListLabelOpt, 0) - for _, v := range args { - opt := &mongodb.ListLabelOpt{ - Key: v.Key, - Values: v.Values, - } - opts = append(opts, opt) - } +type CreateLabelsArgs struct { + Labels []mongodb.Label `json:"labels"` +} - return mongodb.NewLabelColl().Filter(opts) +func ListLabels(args *ListLabelsArgs) ([]*models.Label, error) { + return mongodb.NewLabelColl().List(mongodb.ListLabelOpt{args.Labels}) } type ListResourceByLabelsReq struct { @@ -74,54 +67,8 @@ type ListResourceByLabelsReq struct { } func ListResourcesByLabels(filter []LabelFilter, logger *zap.SugaredLogger) ([]ResourceLabel, error) { - // find the label id - labelM := map[string]*models.Label{} - for _, v := range filter { - labels, err := mongodb.NewLabelColl().List(&mongodb.ListLabelOpt{ - Key: v.Key, - Values: v.Values, - }) - if err != nil { - continue - } - for _, label := range labels { - labelM[label.ID.Hex()] = label - } - } - labelIds := []string{} - for k, _ := range labelM { - labelIds = append(labelIds, k) - } - // find the labelBindings - labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIds}) - if err != nil { - logger.Errorf("list labelbinding err:%s", err) - return nil, err - } - - var res []ResourceLabel - for _, v := range labelBindings { - labelResource, err := ListLabelsByResourceID(v.ResourceID, v.ResourceType, logger) - if err != nil { - continue - } - var labels []Label - for _, v := range labelResource { - label := Label{ - Key: v.Key, - Value: v.Value, - } - labels = append(labels, label) - } - - res = append(res, ResourceLabel{ - ResourceID: v.ResourceID, - ResourceType: v.ResourceType, - Labels: labels, - }) - } - - return res, nil + // TODO - mouuii + return nil, nil } type ListLabelsByResourceReq struct { @@ -134,65 +81,8 @@ type ListLabelsByResourcesReq struct { ResourceType string `json:"resource_type"` } -func ListLabelsByResourceID(resourceID, resourceType string, logger *zap.SugaredLogger) ([]LabelResource, error) { - labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{ - ResourceID: resourceID, - ResourceType: resourceType, - }) - if err != nil { - logger.Errorf("list labelbinding err:%s", err) - return nil, err - } - var labelResources []LabelResource - for _, v := range labelBindings { - label, err := mongodb.NewLabelColl().Find(v.LabelID) - if err != nil { - continue - } - labelResources = append(labelResources, LabelResource{ - Key: label.Key, - Value: label.Value, - ResourceID: v.ResourceID, - }) - } - return labelResources, nil -} - func ListLabelsByResourceIDs(resources *ListLabelsByResourcesReq, logger *zap.SugaredLogger) (map[string][]LabelResource, error) { - labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{ - ResourcesIDs: resources.ResourceIDs, - ResourceType: resources.ResourceType, - }) - if err != nil { - logger.Errorf("list labelbinding err:%s", err) - return nil, err - } - labelIDs := sets.NewString() - for _, v := range labelBindings { - labelIDs.Insert(v.LabelID) - } - labelM := make(map[string]*models.Label, 0) - labels, err := mongodb.NewLabelColl().List(&mongodb.ListLabelOpt{ - IDs: labelIDs.List(), - }) - for _, v := range labels { - labelM[v.ID.Hex()] = v - } - if err != nil { - return nil, err - } - resources2LabelResourceM := make(map[string][]LabelResource, 0) - for _, v := range labelBindings { - if label, ok := labelM[v.LabelID]; ok { - resources2LabelResourceM[v.ResourceID] = append(resources2LabelResourceM[v.ResourceID], LabelResource{ - Key: label.Key, - Value: label.Value, - ResourceID: v.ResourceID, - }) - } - - } - return resources2LabelResourceM, nil + return nil, nil } type DeleteLabelsArgs struct { @@ -218,37 +108,10 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) return err } + return mongodb.NewLabelColl().BulkDelete(ids) } if len(res) > 0 && !forceDelete { return e.ErrForbidden.AddDesc("some label has already bind resource, can not delete") } - return mongodb.NewLabelColl().BulkDelete(ids) -} - -func DeleteLabel(id string, forceDelete bool, logger *zap.SugaredLogger) error { - - // query if the label already bind resources - res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelID: id}) - if err != nil { - logger.Errorf("list labelbingding err:%s", err) - return err - } - // force delete : delete related labelBindings - if forceDelete { - var ids []string - for _, labelBindings := range res { - ids = append(ids, labelBindings.ID.Hex()) - } - - if err := mongodb.NewLabelBindingColl().BulkDelete(ids); err != nil { - logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) - return err - } - } - // non force delete : can not delete label when label has bind resources - if len(res) > 0 && !forceDelete { - logger.Error("the label has bind resources,can not delete") - return e.ErrForbidden.AddDesc("the label has bind resources") - } - return mongodb.NewLabelColl().Delete(id) + return nil } diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index d354eb5a..302f155d 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -28,61 +28,93 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListLabelsBinding() ([]*models.LabelBinding, error) { - return mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{}) +type CreateLabelBinding struct { + Resource Resource + LabelID string `bson:"label_id" json:"label_id"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` } type CreateLabelBindingsArgs struct { - ResourceType string `bson:"resource_type" json:"resource_type"` - ResourceIDs []string `bson:"resource_ids" json:"resource_ids"` - LabelID string `bson:"label_id" json:"label_id"` - CreateBy string `bson:"create_by" json:"create_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` + CreateLabelBindings []CreateLabelBinding `json:"create_label_bindings"` } -func CreateLabelsBinding(cr *CreateLabelBindingsArgs, logger *zap.SugaredLogger) error { +type Resource struct { + Name string `json:"name"` + Type string `json:"type"` + ProjectName string `json:"project_name"` +} + +func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *zap.SugaredLogger) error { // check label exist - if _, err := mongodb.NewLabelColl().Find(cr.LabelID); err != nil { - logger.Errorf("find label err:%s", err) + labelIDs := []string{} + for _, binding := range cr.CreateLabelBindings { + labelIDs = append(labelIDs, binding.LabelID) + binding.CreateBy = userName + } + + labels, err := mongodb.NewLabelColl().ListByIDs(labelIDs) + if err != nil { + logger.Errorf("find labels err:%s", err) return err } + if len(labels) != len(labelIDs) { + logger.Errorf("there's labels not exists") + return fmt.Errorf("there's labels not exists") + } //check resource exist - switch cr.ResourceType { - case string(config.ResourceTypeWorkflow): - wks, err := commondb.NewWorkflowColl().List(&commondb.ListWorkflowOption{ - Ids: cr.ResourceIDs, - }) - if err != nil { - logger.Errorf("can not find related resource err:%s", err) - return err - } - if len(wks) != len(cr.ResourceIDs) { - logger.Errorf("can not find related resource err:%s", err) - return e.ErrForbidden.AddDesc("can not find related resource") - } - case string(config.ResourceTypeProduct): - prs, err := commondb.NewProductColl().List(&commondb.ProductListOptions{ - InIDs: cr.ResourceIDs, - }) - if err != nil { - logger.Errorf("can not find related resource err:%s", err) - return err - } - if len(prs) != len(cr.ResourceIDs) { - logger.Errorf("can not find related resource err:%s", err) - return e.ErrForbidden.AddDesc("can not find related resource") + m := make(map[string][]CreateLabelBinding) + for _, binding := range cr.CreateLabelBindings { + m[binding.Resource.Type] = append(m[binding.Resource.Type], binding) + } + for k, v := range m { + switch k { + case string(config.ResourceTypeWorkflow): + workflows := []commondb.Workflow{} + for _, vv := range v { + workflow := commondb.Workflow{ + Name: vv.Resource.Name, + ProjectName: vv.Resource.ProjectName, + } + workflows = append(workflows, workflow) + } + wks, err := commondb.NewWorkflowColl().ListByWorkflows(commondb.ListWorkflowOpt{workflows}) + if err != nil { + logger.Errorf("can not find related resource err:%s", err) + return err + } + if len(wks) != len(v) { + logger.Errorf("can not find related resource err:%s", err) + return e.ErrForbidden.AddDesc("can not find related resource") + } + case string(config.ResourceTypeProduct): + products := []commondb.Product{} + for _, vv := range v { + product := commondb.Product{ + Name: vv.Resource.Name, + ProjectName: vv.Resource.ProjectName, + } + products = append(products, product) + } + pros, err := commondb.NewProductColl().ListByProducts(commondb.ListProductOpt{products}) + if err != nil { + logger.Errorf("can not find related resource err:%s", err) + return err + } + if len(pros) != len(v) { + logger.Errorf("can not find related resource err:%s", err) + return e.ErrForbidden.AddDesc("can not find related resource") + } } - default: - errMsg := fmt.Sprintf("resource type not allowed :%s", cr.ResourceType) - return e.ErrInternalError.AddDesc(errMsg) } + var labelBindings []*models.LabelBinding - for _, v := range cr.ResourceIDs { + for _, v := range cr.CreateLabelBindings { labelBinding := &models.LabelBinding{ - ResourceType: cr.ResourceType, - ResourceID: v, - LabelID: cr.LabelID, - CreateBy: cr.CreateBy, + ResourceType: v.Resource.Type, + ResourceName: v.Resource.Name, + LabelID: v.LabelID, + CreateBy: v.CreateBy, } labelBindings = append(labelBindings, labelBinding) } -- Gitee From 518303de76a4e7bdd0768dc853a0dbf24aa86e78 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 17 Jan 2022 23:09:22 +0800 Subject: [PATCH 1320/2647] minor improvement to the validate bucket function Signed-off-by: Min Min --- pkg/tool/s3/client.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index 9f8cd450..7681b133 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -65,18 +65,13 @@ func NewClient(endpoint, ak, sk string, insecure, forcedPathStyle bool) (*Client // Validate the existence of bucket func (c *Client) ValidateBucket(bucketName string) error { - listBucketInput := &s3.ListBucketsInput{} - bucketListResp, err := c.ListBuckets(listBucketInput) + listObjectInput := &s3.ListObjectsInput{Bucket: aws.String(bucketName)} + _, err := c.ListObjects(listObjectInput) if err != nil { return fmt.Errorf("validate S3 error: %s", err.Error()) } - for _, bucket := range bucketListResp.Buckets { - if *bucket.Name == bucketName { - return nil - } - } - return fmt.Errorf("validate s3 error: given bucket does not exist") + return nil } func (c *Client) DownloadWithOption(bucketName, objectKey, dest string, option *DownloadOption) error { -- Gitee From 3b2f9035e7f8c9251dbb13aa9316a3fd45c7fd0a Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 18 Jan 2022 11:41:47 +0800 Subject: [PATCH 1321/2647] labels refactor Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 2 +- .../aslan/core/label/handler/labelsbinding.go | 13 --- .../aslan/core/label/handler/router.go | 2 - .../label/repository/mongodb/labelbinding.go | 38 +++++++ .../aslan/core/label/service/label.go | 104 ++++++++++++++---- .../aslan/core/label/service/labelsbinding.go | 8 +- 6 files changed, 123 insertions(+), 44 deletions(-) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 889932a0..22704e79 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -120,5 +120,5 @@ func ListLabelsByResources(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.ListLabelsByResourceIDs(listLabelsByResourcesReq, ctx.Logger) + ctx.Resp, ctx.Err = service.ListLabelsByResources(listLabelsByResourcesReq.Resources, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/labelsbinding.go index 2a401e65..3b566f8f 100644 --- a/pkg/microservice/aslan/core/label/handler/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/handler/labelsbinding.go @@ -48,16 +48,3 @@ func CreateLabelBindings(c *gin.Context) { } ctx.Err = service.CreateLabelBindings(createLabelBindingsArgs, ctx.UserName, ctx.Logger) } - -func DeleteLabelBindings(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - deleteLabelsBindingsArgs := new(service.DeleteLabelsBindingsArgs) - if err := c.ShouldBindJSON(deleteLabelsBindingsArgs); err != nil { - ctx.Err = err - return - } - - ctx.Err = service.DeleteLabelsBindings(deleteLabelsBindingsArgs.IDs) -} diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index b519b7bd..1a8fdf83 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -30,11 +30,9 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.POST("/bulk-delete", DeleteLabels) labels.POST("/resources-by-labels", ListResourceByLabels) labels.POST("/labels-by-resources", ListLabelsByResources) - } labelBindings := router.Group("labelbindings") { labelBindings.POST("", CreateLabelBindings) - labelBindings.POST("/bulk-delete", DeleteLabelBindings) } } diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index cc6e4623..5fe3d963 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -144,3 +144,41 @@ func (c *LabelBindingColl) BulkDelete(ids []string) error { _, err := c.DeleteMany(context.TODO(), query) return err } + +type Resource struct { + Name string `json:"name"` + ProjectName string `json:"project_name"` + Type string `json:"type"` +} + +type ListLabelBindingsByResources struct { + Resources []Resource +} + +func (c *LabelBindingColl) ListByResources(opt ListLabelBindingsByResources) ([]*models.LabelBinding, error) { + var res []*models.LabelBinding + + if len(opt.Resources) == 0 { + return nil, nil + } + condition := bson.A{} + for _, resource := range opt.Resources { + condition = append(condition, bson.M{ + "resource_type": resource.Type, + "resource_name": resource.Name, + "project_name": resource.ProjectName, + }) + } + filter := bson.D{{"$or", condition}} + cursor, err := c.Collection.Find(context.TODO(), filter) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index cae2d5c5..cf8611b3 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -17,27 +17,20 @@ limitations under the License. package service import ( + "fmt" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" e "github.com/koderover/zadig/pkg/tool/errors" ) -type LabelFilter struct { - Key string `json:"key"` - Values []string `json:"values"` -} - type ResourceLabel struct { - ResourceID string `json:"resource_id"` - ResourceType string `json:"resource_type"` - Labels []Label `json:"labels"` -} - -type Label struct { - Key string `json:"key"` - Value string `json:"value"` + ResourceName string `json:"resource_name"` + ProjectName string `json:"project_name"` + ResourceType string `json:"resource_type"` } type LabelResource struct { @@ -63,12 +56,47 @@ func ListLabels(args *ListLabelsArgs) ([]*models.Label, error) { } type ListResourceByLabelsReq struct { - LabelFilters []LabelFilter `json:"label_filters"` + LabelFilters []mongodb.Label `json:"label_filters"` } -func ListResourcesByLabels(filter []LabelFilter, logger *zap.SugaredLogger) ([]ResourceLabel, error) { - // TODO - mouuii - return nil, nil +func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) (map[string][]mongodb.Resource, error) { + // 1.find labels by label filters + labels, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{Labels: filters}) + if err != nil { + logger.Errorf("labels ListByOpt err:%s", err) + return nil, err + } + // 2.find labelBindings by label ids + labelIDSet := sets.NewString() + labelsM := make(map[string]string) + for _, v := range labels { + labelIDSet.Insert(v.ID.Hex()) + labelsM[v.ID.Hex()] = fmt.Sprintf("%s-%s", v.Key, v.Value) + } + + labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIDSet.List()}) + if err != nil { + logger.Errorf("labelBindings ListByOpt err:%s", err) + return nil, err + } + + // 3.find labels by resourceName-projectName + res := make(map[string][]mongodb.Resource) + for _, v := range labelBindings { + resource := mongodb.Resource{ + Name: v.ResourceName, + ProjectName: v.ProjectName, + Type: v.ResourceType, + } + labelString, _ := labelsM[v.LabelID] + if resources, ok := res[labelString]; ok { + res[labelString] = append(resources, resource) + } else { + res[labelString] = []mongodb.Resource{resource} + } + } + + return res, nil } type ListLabelsByResourceReq struct { @@ -77,12 +105,46 @@ type ListLabelsByResourceReq struct { } type ListLabelsByResourcesReq struct { - ResourceIDs []string `json:"resource_ids"` - ResourceType string `json:"resource_type"` + Resources []mongodb.Resource `json:"resources"` } -func ListLabelsByResourceIDs(resources *ListLabelsByResourcesReq, logger *zap.SugaredLogger) (map[string][]LabelResource, error) { - return nil, nil +func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogger) (map[string][]*models.Label, error) { + //1. find the labelBindings by resources + labelBindings, err := mongodb.NewLabelBindingColl().ListByResources(mongodb.ListLabelBindingsByResources{Resources: resources}) + if err != nil { + return nil, err + } + //2.find labels by labelBindings + labelIDSet := sets.NewString() + for _, v := range labelBindings { + labelIDSet.Insert(v.LabelID) + } + labels, err := mongodb.NewLabelColl().ListByIDs(labelIDSet.List()) + if err != nil { + return nil, err + } + labelM := make(map[string]*models.Label) + for _, label := range labels { + labelM[label.ID.Hex()] = label + } + // 3. iterate resources + res := make(map[string][]*models.Label) + for _, labelBinding := range labelBindings { + resourceKey := fmt.Sprintf("%s-%s", labelBinding.ResourceType, labelBinding.ResourceName) + + label := &models.Label{} + if label, ok := labelM[labelBinding.LabelID]; !ok { + return nil, fmt.Errorf("can not find label %v", label) + } + + if arr, ok := res[resourceKey]; ok { + res[resourceKey] = append(arr, label) + } else { + res[resourceKey] = []*models.Label{label} + } + } + + return res, nil } type DeleteLabelsArgs struct { diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 302f155d..134519fd 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -29,7 +29,7 @@ import ( ) type CreateLabelBinding struct { - Resource Resource + Resource mongodb.Resource LabelID string `bson:"label_id" json:"label_id"` CreateBy string `bson:"create_by" json:"create_by"` CreateTime int64 `bson:"create_time" json:"create_time"` @@ -39,12 +39,6 @@ type CreateLabelBindingsArgs struct { CreateLabelBindings []CreateLabelBinding `json:"create_label_bindings"` } -type Resource struct { - Name string `json:"name"` - Type string `json:"type"` - ProjectName string `json:"project_name"` -} - func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *zap.SugaredLogger) error { // check label exist labelIDs := []string{} -- Gitee From 22b9deb6aa108ffd8900e85473e8cba195880257 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 18 Jan 2022 13:41:57 +0800 Subject: [PATCH 1322/2647] fix host env deploy bug Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 7af21c00..8b42e78a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -653,7 +653,7 @@ func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, pr s := &types.ServiceTmpl{} _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ - "productName": productName, + "projectName": productName, "revision": fmt.Sprintf("%d", revision), })) if err != nil { -- Gitee From 66db2237514a9a98e546df3984a0cad8e716aa13 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 18 Jan 2022 14:43:34 +0800 Subject: [PATCH 1323/2647] labels bugfix Signed-off-by: mouuii --- .../aslan/core/label/service/label.go | 20 +++++++++++------- .../aslan/core/label/service/labelsbinding.go | 21 ++++++++++--------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index cf8611b3..6e335ba5 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -155,25 +155,29 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err if len(ids) == 0 { return nil } + res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: ids}) if err != nil { logger.Errorf("list labelbingding err:%s", err) return err } + + if !forceDelete && len(res) > 0 { + return e.ErrForbidden.AddDesc("some label has already bind resource, can not delete") + } + if forceDelete { - var ids []string - for _, labelBindings := range res { - ids = append(ids, labelBindings.ID.Hex()) + var labelBindingIDs []string + for _, labelBinding := range res { + labelBindingIDs = append(ids, labelBinding.ID.Hex()) } - if err := mongodb.NewLabelBindingColl().BulkDelete(ids); err != nil { + if err := mongodb.NewLabelBindingColl().BulkDelete(labelBindingIDs); err != nil { logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) return err } return mongodb.NewLabelColl().BulkDelete(ids) } - if len(res) > 0 && !forceDelete { - return e.ErrForbidden.AddDesc("some label has already bind resource, can not delete") - } - return nil + + return mongodb.NewLabelColl().BulkDelete(ids) } diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 134519fd..4a32e869 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -29,10 +29,10 @@ import ( ) type CreateLabelBinding struct { - Resource mongodb.Resource - LabelID string `bson:"label_id" json:"label_id"` - CreateBy string `bson:"create_by" json:"create_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` + Resource mongodb.Resource `json:"resource"` + LabelID string `bson:"label_id" json:"label_id"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` } type CreateLabelBindingsArgs struct { @@ -53,8 +53,8 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z return err } if len(labels) != len(labelIDs) { - logger.Errorf("there's labels not exists") - return fmt.Errorf("there's labels not exists") + logger.Errorf("there're labels not exist") + return fmt.Errorf("there're labels not exist") } //check resource exist m := make(map[string][]CreateLabelBinding) @@ -78,8 +78,8 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z return err } if len(wks) != len(v) { - logger.Errorf("can not find related resource err:%s", err) - return e.ErrForbidden.AddDesc("can not find related resource") + logger.Errorf("there're resources not exist") + return e.ErrForbidden.AddDesc("there're resources not exist") } case string(config.ResourceTypeProduct): products := []commondb.Product{} @@ -96,8 +96,8 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z return err } if len(pros) != len(v) { - logger.Errorf("can not find related resource err:%s", err) - return e.ErrForbidden.AddDesc("can not find related resource") + logger.Errorf("there're resources not exist") + return e.ErrForbidden.AddDesc("there're resources not exist") } } } @@ -107,6 +107,7 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z labelBinding := &models.LabelBinding{ ResourceType: v.Resource.Type, ResourceName: v.Resource.Name, + ProjectName: v.Resource.ProjectName, LabelID: v.LabelID, CreateBy: v.CreateBy, } -- Gitee From 4ee64adb77552fe87772955816ea38ef3aee99cb Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 18 Jan 2022 14:58:05 +0800 Subject: [PATCH 1324/2647] resource same can not bind bug Signed-off-by: mouuii --- .../aslan/core/label/service/labelsbinding.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 4a32e869..9879317a 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -20,6 +20,7 @@ import ( "fmt" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" commondb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" @@ -30,9 +31,9 @@ import ( type CreateLabelBinding struct { Resource mongodb.Resource `json:"resource"` - LabelID string `bson:"label_id" json:"label_id"` - CreateBy string `bson:"create_by" json:"create_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` + LabelID string `json:"label_id"` + CreateBy string `json:"create_by"` + CreateTime int64 `json:"create_time"` } type CreateLabelBindingsArgs struct { @@ -65,37 +66,41 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z switch k { case string(config.ResourceTypeWorkflow): workflows := []commondb.Workflow{} + resourceSet := sets.NewString() for _, vv := range v { workflow := commondb.Workflow{ Name: vv.Resource.Name, ProjectName: vv.Resource.ProjectName, } workflows = append(workflows, workflow) + resourceSet.Insert(fmt.Sprintf("%s-%s", vv.Resource.Name, vv.Resource.ProjectName)) } wks, err := commondb.NewWorkflowColl().ListByWorkflows(commondb.ListWorkflowOpt{workflows}) if err != nil { logger.Errorf("can not find related resource err:%s", err) return err } - if len(wks) != len(v) { + if len(wks) != resourceSet.Len() { logger.Errorf("there're resources not exist") return e.ErrForbidden.AddDesc("there're resources not exist") } case string(config.ResourceTypeProduct): products := []commondb.Product{} + resourceSet := sets.NewString() for _, vv := range v { product := commondb.Product{ Name: vv.Resource.Name, ProjectName: vv.Resource.ProjectName, } products = append(products, product) + resourceSet.Insert(fmt.Sprintf("%s-%s", vv.Resource.Name, vv.Resource.ProjectName)) } pros, err := commondb.NewProductColl().ListByProducts(commondb.ListProductOpt{products}) if err != nil { logger.Errorf("can not find related resource err:%s", err) return err } - if len(pros) != len(v) { + if len(pros) != resourceSet.Len() { logger.Errorf("there're resources not exist") return e.ErrForbidden.AddDesc("there're resources not exist") } -- Gitee From 28b82dc87690cb3a4631c2dfbf783a39c4b06276 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 18 Jan 2022 15:18:09 +0800 Subject: [PATCH 1325/2647] add label to policy Signed-off-by: panxunying --- .../aslan/core/label/handler/labels.go | 2 +- .../aslan/core/label/handler/router.go | 2 +- .../aslan/core/label/service/label.go | 43 ++++++------ .../policy/core/service/policy.go | 48 ++++++++++++-- pkg/shared/client/label/client.go | 40 +++++++++++ pkg/shared/client/label/label.go | 66 +++++++++++++++++++ 6 files changed, 172 insertions(+), 29 deletions(-) create mode 100644 pkg/shared/client/label/client.go create mode 100644 pkg/shared/client/label/label.go diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 22704e79..1f431860 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -99,7 +99,7 @@ func DeleteLabels(c *gin.Context) { ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.Logger) } -func ListResourceByLabels(c *gin.Context) { +func ListResourcesByLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() listResourceByLabelsReq := new(service.ListResourceByLabelsReq) diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 1a8fdf83..85ecb22c 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -28,7 +28,7 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.POST("/filter", ListLabels) labels.POST("/bulk-create", CreateLabels) labels.POST("/bulk-delete", DeleteLabels) - labels.POST("/resources-by-labels", ListResourceByLabels) + labels.POST("/resources-by-labels", ListResourcesByLabels) labels.POST("/labels-by-resources", ListLabelsByResources) } labelBindings := router.Group("labelbindings") diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index cf8611b3..0dfd19e0 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -27,18 +27,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -type ResourceLabel struct { - ResourceName string `json:"resource_name"` - ProjectName string `json:"project_name"` - ResourceType string `json:"resource_type"` -} - -type LabelResource struct { - Key string `json:"key"` - Value string `json:"value"` - ResourceID string `json:"resource_id"` -} - func CreateLabels(labels []*models.Label) error { return mongodb.NewLabelColl().BulkCreate(labels) } @@ -59,7 +47,15 @@ type ListResourceByLabelsReq struct { LabelFilters []mongodb.Label `json:"label_filters"` } -func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) (map[string][]mongodb.Resource, error) { +type ListResourcesByLabelsResp struct { + Resources map[string][]mongodb.Resource `json:"resources"` +} + +func BuildLabelString(key string, value string) string { + return fmt.Sprintf("%s-%s", key, value) +} + +func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) (*ListResourcesByLabelsResp, error) { // 1.find labels by label filters labels, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{Labels: filters}) if err != nil { @@ -71,7 +67,7 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( labelsM := make(map[string]string) for _, v := range labels { labelIDSet.Insert(v.ID.Hex()) - labelsM[v.ID.Hex()] = fmt.Sprintf("%s-%s", v.Key, v.Value) + labelsM[v.ID.Hex()] = BuildLabelString(v.Key, v.Value) } labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIDSet.List()}) @@ -96,19 +92,20 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( } } - return res, nil -} - -type ListLabelsByResourceReq struct { - ResourceID string `json:"resource_id"` - ResourceType string `json:"resource_type"` + return &ListResourcesByLabelsResp{ + Resources: res, + }, nil } type ListLabelsByResourcesReq struct { Resources []mongodb.Resource `json:"resources"` } -func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogger) (map[string][]*models.Label, error) { +type ListLabelsByResourcesResp struct { + Labels map[string][]*models.Label `json:"labels"` +} + +func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogger) (*ListLabelsByResourcesResp, error) { //1. find the labelBindings by resources labelBindings, err := mongodb.NewLabelBindingColl().ListByResources(mongodb.ListLabelBindingsByResources{Resources: resources}) if err != nil { @@ -144,7 +141,9 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg } } - return res, nil + return &ListLabelsByResourcesResp{ + Labels: res, + }, nil } type DeleteLabelsArgs struct { diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index af63e97e..fe9b762e 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -20,6 +20,11 @@ import ( "fmt" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" + + mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" + "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" @@ -108,18 +113,51 @@ func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { } else if !found { return nil, fmt.Errorf("policy %s not found", name) } - res := &Policy{ Name: r.Name, } + var labels []mongodb2.Label + labelSet := sets.NewString() for _, ru := range r.Rules { res.Rules = append(res.Rules, &Rule{ - Verbs: ru.Verbs, - Kind: ru.Kind, - Resources: ru.Resources, + Verbs: ru.Verbs, + Kind: ru.Kind, + Resources: ru.Resources, + MatchAttributes: ru.MatchAttributes, }) + for _, ma := range ru.MatchAttributes { + labelString := service.BuildLabelString(ma.Key, ma.Value) + if !labelSet.Has(labelString) { + labelSet.Insert(labelString) + labels = append(labels, mongodb2.Label{ + Key: ma.Key, + Value: ma.Value, + }) + } + } + } + req := label.ListResourcesByLabelsReq{ + LabelFilters: labels, + } + labelClient := label.New() + resp, err := labelClient.ListResourcesByLabels(req) + if err != nil { + return nil, err + } + for i, rule := range res.Rules { + var relatedResources []string + for _, ma := range rule.MatchAttributes { + labelString := service.BuildLabelString(ma.Key, ma.Value) + if resources, ok := resp.Resources[labelString]; ok { + for _, resource := range resources { + if resource.Type == rule.Resources[0] { + relatedResources = append(relatedResources, resource.Name) + } + } + } + } + res.Rules[i].RelatedResources = relatedResources } - return res, nil } diff --git a/pkg/shared/client/label/client.go b/pkg/shared/client/label/client.go new file mode 100644 index 00000000..3b40ffbe --- /dev/null +++ b/pkg/shared/client/label/client.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package label + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.ConfigServiceAddress() + c := httpclient.New( + httpclient.SetHostURL(host + "/api"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/label/label.go b/pkg/shared/client/label/label.go new file mode 100644 index 00000000..67558e54 --- /dev/null +++ b/pkg/shared/client/label/label.go @@ -0,0 +1,66 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package label + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" +) + +type ListResourcesByLabelsReq struct { + LabelFilters []mongodb.Label `json:"label_filters"` +} + +type ListResourcesByLabelsResp struct { + Resources map[string][]mongodb.Resource `json:"resources"` +} + +func (c *Client) ListResourcesByLabels(request ListResourcesByLabelsReq) (*ListResourcesByLabelsResp, error) { + url := "/label/labels/resources-by-labels" + + var resources *ListResourcesByLabelsResp + _, err := c.Post(url, httpclient.SetBody(request), httpclient.SetResult(&resources)) + if err != nil { + log.Errorf("Failed to add audit log, error: %s", err) + return nil, err + } + + return resources, nil +} + +type ListLabelsByResourcesReq struct { + Resources []mongodb.Resource `json:"resources"` +} + +type ListLabelsByResourcesResp struct { + Labels map[string][]*models.Label `json:"labels"` +} + +func (c *Client) ListLabelsByResources(request ListLabelsByResourcesReq) (*ListLabelsByResourcesResp, error) { + url := "/label/labels/labels-by-resources" + + var resources *ListLabelsByResourcesResp + _, err := c.Post(url, httpclient.SetBody(request), httpclient.SetResult(&resources)) + if err != nil { + log.Errorf("Failed to add audit log, error: %s", err) + return nil, err + } + + return resources, nil +} -- Gitee From 4b6016e7843224a07f180c2dd2a078fced35b787 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 18 Jan 2022 15:34:58 +0800 Subject: [PATCH 1326/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/service/label.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 6e335ba5..4c203b8c 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -60,12 +60,16 @@ type ListResourceByLabelsReq struct { } func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) (map[string][]mongodb.Resource, error) { + res := make(map[string][]mongodb.Resource) // 1.find labels by label filters labels, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{Labels: filters}) if err != nil { logger.Errorf("labels ListByOpt err:%s", err) return nil, err } + if len(labels) == 0 { + return res, nil + } // 2.find labelBindings by label ids labelIDSet := sets.NewString() labelsM := make(map[string]string) @@ -81,7 +85,6 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( } // 3.find labels by resourceName-projectName - res := make(map[string][]mongodb.Resource) for _, v := range labelBindings { resource := mongodb.Resource{ Name: v.ResourceName, @@ -99,11 +102,6 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( return res, nil } -type ListLabelsByResourceReq struct { - ResourceID string `json:"resource_id"` - ResourceType string `json:"resource_type"` -} - type ListLabelsByResourcesReq struct { Resources []mongodb.Resource `json:"resources"` } @@ -132,8 +130,8 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg for _, labelBinding := range labelBindings { resourceKey := fmt.Sprintf("%s-%s", labelBinding.ResourceType, labelBinding.ResourceName) - label := &models.Label{} - if label, ok := labelM[labelBinding.LabelID]; !ok { + label, ok := labelM[labelBinding.LabelID] + if !ok { return nil, fmt.Errorf("can not find label %v", label) } -- Gitee From c8eae8c30442158441c03667ce1a502c5dcf3bbf Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 18 Jan 2022 15:44:42 +0800 Subject: [PATCH 1327/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/service/label.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 4c203b8c..7a1e74cc 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -61,6 +61,9 @@ type ListResourceByLabelsReq struct { func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) (map[string][]mongodb.Resource, error) { res := make(map[string][]mongodb.Resource) + if len(filters) == 0 { + return res, nil + } // 1.find labels by label filters labels, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{Labels: filters}) if err != nil { -- Gitee From 8f72eebc721f4138bc985116ff5f0d7d22ae6510 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 18 Jan 2022 15:54:34 +0800 Subject: [PATCH 1328/2647] add syncCollaboration Signed-off-by: panxunying --- .../collaboration/handler/collaboration_instance.go | 11 +++++++++++ .../collaboration/service/collaboration_instance.go | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index 53114921..3bd4bdb6 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -34,3 +34,14 @@ func GetCollaborationNew(c *gin.Context) { } ctx.Resp, ctx.Err = service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) } + +func SyncCollaborationInstance(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") + return + } + ctx.Err = service.SyncCollaborationInstance(projectName, ctx.UserID, ctx.UserName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index acf6b1be..dae82438 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -225,7 +225,9 @@ func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) func buildName(baseName, modeName, userName string) string { return modeName + "-" + baseName + "-" + userName + "-" + util.GetRandomString(6) } - +func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.SugaredLogger) error { + return nil +} func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { var newWorkflow []*Workflow var newProduct []*Product -- Gitee From de865cfd8e8b6c60b39bcee68d4b4872e7b94845 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 18 Jan 2022 15:58:56 +0800 Subject: [PATCH 1329/2647] update rego Signed-off-by: panxunying --- .../core/service/bundle/rego/authz.rego | 152 ++++++++++++++++-- 1 file changed, 135 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 9fc4edcf..9676ee1d 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -26,8 +26,12 @@ response = r { response = r { allow + roles := all_roles r := { - "allowed": true + "allowed": true, + "headers": { + "Roles": json.marshal(roles), + } } } @@ -36,9 +40,16 @@ response = r { is_authenticated not allow rule_is_matched_for_filtering + roles := all_roles + role_resource := user_role_allowed_resources + policy_resource := user_policy_allowed_resources + resource := role_resource | policy_resource r := { "allowed": true, - "headers": {"Resources": concat(",", user_allowed_resources)} + "headers": { + "Resources": json.marshal(resource), + "Roles": json.marshal(roles), + } } } @@ -77,7 +88,17 @@ access_is_granted { some rule - allowed_plain_rules[rule] + allowed_role_plain_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) +} + +access_is_granted { + not url_is_privileged + + some rule + + allowed_policy_plain_rules[rule] rule.method == http_request.method glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) } @@ -85,7 +106,17 @@ access_is_granted { access_is_granted { some rule - allowed_attributive_rules[rule] + allowed_role_attributive_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + + all_attributes_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) +} + +access_is_granted { + some rule + + allowed_policy_attributive_rules[rule] rule.method == http_request.method glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) @@ -93,20 +124,47 @@ access_is_granted { } rule_is_matched_for_filtering { - count(user_matched_rule_for_filtering) > 0 + count(user_matched_role_rule_for_filtering) > 0 +} + +rule_is_matched_for_filtering { + count(user_matched_policy_rule_for_filtering) > 0 } # get all resources which matches the attributes -user_allowed_resources[resourceID] { +user_role_allowed_resources[resourceID] { some rule - user_matched_rule_for_filtering[rule] + user_matched_role_rule_for_filtering[rule] res := data.resources[rule.resourceType][_] project_name_is_match(res) - not attributes_mismatch(rule.matchAttributes, res) + attributes_match(rule.matchAttributes, res) resourceID := res.resourceID } +user_policy_allowed_resources[resourceID] { + some rule + + user_matched_policy_rule_for_filtering[rule] + res := data.resources[rule.resourceType][_] + project_name_is_match(res) + attributes_match(rule.matchAttributes, res) + resourceID := res.resourceID +} + +attributes_match(attributes, res) { + count(attributes) == 0 +} + +attributes_match(attributes, res) { + attribute := attributes[_] + attribute_match(attribute, res) +} + +attribute_match(attribute, res) { + res.spec[attribute.key] == attribute.value +} + project_name_is_match(res) { res.projectName != "" res.projectName == project_name @@ -116,10 +174,19 @@ project_name_is_match(res) { res.projectName == "" } -user_matched_rule_for_filtering[rule] { +user_matched_policy_rule_for_filtering[rule] { some rule - allowed_attributive_rules[rule] + allowed_policy_attributive_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + not rule.idRegex +} + +user_matched_role_rule_for_filtering[rule] { + some rule + + allowed_role_attributive_rules[rule] rule.method == http_request.method glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) not rule.idRegex @@ -205,6 +272,11 @@ user_projects[project] { data.bindings.role_bindings[i].uid == claims.uid project := data.bindings.role_bindings[i].bindings[_].namespace } +user_projects[project] { + some i + data.bindings.policy_bindings[i].uid == claims.uid + project := data.bindings.policy_bindings[i].bindings[_].namespace +} # get all projects which are visible by all users (the user name is "*") user_projects[project] { @@ -213,6 +285,12 @@ user_projects[project] { project := data.bindings.role_bindings[i].bindings[_].namespace } +user_projects[project] { + some i + data.bindings.policy_bindings[i].uid == "*" + project := data.bindings.policy_bindings[i].bindings[_].namespace +} + # all projects which are allowed by current user user_allowed_projects[project] { some project @@ -246,6 +324,7 @@ all_roles[role_ref] { role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } + # only roles under the given project are allowed allowed_roles[role_ref] { some i @@ -262,7 +341,21 @@ allowed_roles[role_ref] { role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } -allowed_rules[rule] { +allowed_policies[policy_ref] { + some i + data.bindings.policy_bindings[i].uid == claims.uid + data.bindings.policy_bindings[i].bindings[j].namespace == project_name + policy_ref := data.bindings.policy_bindings[i].bindings[j].policy_refs[_] +} + +allowed_policies[policy_ref] { + some i + data.bindings.policy_bindings[i].uid == "*" + data.bindings.policy_bindings[i].bindings[_].namespace == project_name + policy_ref := data.bindings.policy_bindings[i].bindings[j].policy_refs[_] +} + +allowed_role_rules[rule] { some role_ref allowed_roles[role_ref] @@ -272,19 +365,44 @@ allowed_rules[rule] { rule := data.roles.roles[i].rules[_] } -allowed_plain_rules[rule] { - rule := allowed_rules[_] +allowed_policy_rules[rule] { + some policy_ref + allowed_policies[policy_ref] + + some i + data.policies.policies[i].name == policy_ref.name + data.policies.policies[i].namespace == policy_ref.namespace + rule := data.policies.policies[i].rules[_] +} + +allowed_policy_plain_rules[rule] { + rule := allowed_policy_rules[_] not rule.matchAttributes not rule.matchExpressions } -allowed_attributive_rules[rule] { - rule := allowed_rules[_] +allowed_role_plain_rules[rule] { + rule := allowed_role_rules[_] + not rule.matchAttributes + not rule.matchExpressions +} + +allowed_policy_attributive_rules[rule] { + rule := allowed_policy_rules[_] + rule.matchAttributes +} + +allowed_role_attributive_rules[rule] { + rule := allowed_role_rules[_] rule.matchAttributes } +allowed_policy_attributive_rules[rule] { + rule := allowed_policy_rules[_] + rule.matchExpressions +} -allowed_attributive_rules[rule] { - rule := allowed_rules[_] +allowed_role_attributive_rules[rule] { + rule := allowed_role_rules[_] rule.matchExpressions } -- Gitee From 35676734e357a2a3a017c681fe05166686f422bb Mon Sep 17 00:00:00 2001 From: fansi Date: Tue, 18 Jan 2022 22:00:28 +0800 Subject: [PATCH 1330/2647] enhance log print Signed-off-by: fansi --- examples/microservice-demo/backend/backend.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/microservice-demo/backend/backend.go b/examples/microservice-demo/backend/backend.go index 8c89b5d4..978dfb05 100644 --- a/examples/microservice-demo/backend/backend.go +++ b/examples/microservice-demo/backend/backend.go @@ -45,7 +45,7 @@ func buildStamp(w http.ResponseWriter, req *http.Request) { func main() { - log.Print("hello, welcome to the microservice world.\n") + log.Println("Hello, welcome to the microservice world.") http.HandleFunc("/", hello) http.HandleFunc("/api/buildstamp", buildStamp) -- Gitee From a4bae080c194a780b6a79d4df371a31c92ee27cb Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 18 Jan 2022 10:55:42 +0800 Subject: [PATCH 1331/2647] improve PR template Signed-off-by: zhangyifei --- .github/pull_request_template.md | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 99528951..00ff9d19 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,22 +1,13 @@ ### What this PR does / Why we need it: -Issue Number: close #xxx - -Problem Summary: ### What is changed and how it works? -What's Changed: - -How it Works: - -### Check List - - -- [ ] Docs have been added / updated -- [ ] Unit test / Integration test for the changes have been added -- [ ] Manual test (add detailed scripts or steps below) -- [ ] No code +### Does this PR introduce a user-facing change? -## More information \ No newline at end of file +- [ ] API change +- [ ] database schema change +- [ ] behavioral change +- [ ] change in non-functional attributes such as efficiency or availability +- [ ] fix of a previous issue -- Gitee From fcd3a106d9e266193bc1fd9a2d89bac541cdf3f7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 19 Jan 2022 11:00:25 +0800 Subject: [PATCH 1332/2647] resource for workflows Signed-off-by: mouuii --- .../aslan/core/label/service/label.go | 3 +- .../aslan/core/workflow/handler/bundle.go | 2 +- .../core/workflow/service/workflow/bundle.go | 39 ++++++++++++++++--- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 45172e0e..582a6b1e 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -115,6 +115,7 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg //1. find the labelBindings by resources labelBindings, err := mongodb.NewLabelBindingColl().ListByResources(mongodb.ListLabelBindingsByResources{Resources: resources}) if err != nil { + logger.Errorf("NewLabelBindingColl ListByResources err:%s", err) return nil, err } //2.find labels by labelBindings @@ -133,7 +134,7 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg // 3. iterate resources res := make(map[string][]*models.Label) for _, labelBinding := range labelBindings { - resourceKey := fmt.Sprintf("%s-%s", labelBinding.ResourceType, labelBinding.ResourceName) + resourceKey := fmt.Sprintf("%s-%s-%s", labelBinding.ResourceType, labelBinding.ProjectName, labelBinding.ResourceName) label, ok := labelM[labelBinding.LabelID] if !ok { diff --git a/pkg/microservice/aslan/core/workflow/handler/bundle.go b/pkg/microservice/aslan/core/workflow/handler/bundle.go index d1edc396..95117076 100644 --- a/pkg/microservice/aslan/core/workflow/handler/bundle.go +++ b/pkg/microservice/aslan/core/workflow/handler/bundle.go @@ -27,5 +27,5 @@ func GetBundleResources(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.GetBundleResources() + ctx.Resp, ctx.Err = workflow.GetBundleResources(ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index ce6da272..7bd49666 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -17,7 +17,14 @@ limitations under the License. package workflow import ( + "fmt" + + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" + labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/tool/log" ) @@ -27,21 +34,43 @@ type resourceSpec struct { Spec map[string]interface{} `json:"spec"` } -func GetBundleResources() ([]*resourceSpec, error) { +func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { var res []*resourceSpec workflows, err := mongodb.NewWorkflowColl().List(nil) if err != nil { log.Error("Failed to list workflows , err:%s", err) return nil, err } + // get labels by workflow resources ids + var resources []labeldb.Resource + + for _, workflow := range workflows { + resource := labeldb.Resource{ + Name: workflow.Name, + ProjectName: workflow.ProductTmplName, + Type: string(config.ResourceTypeWorkflow), + } + resources = append(resources, resource) + } + labelsResp, err := service.ListLabelsByResources(resources, logger) + if err != nil { + return nil, err + } + // TODO - mouuii for _, workflow := range workflows { - res = append(res, &resourceSpec{ - ResourceID: workflow.ID.Hex(), + resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeWorkflow, workflow.ProductTmplName, workflow.Name) + resourceSpec := &resourceSpec{ + ResourceID: workflow.Name, ProjectName: workflow.ProductTmplName, - Spec: map[string]interface{}{}, - }) + } + if labels, ok := labelsResp.Labels[resourceKey]; ok { + for _, v := range labels { + resourceSpec.Spec[v.Key] = v.Value + } + } + res = append(res, resourceSpec) } return res, nil } -- Gitee From 3f2f3b19a2748dbac67524b01da56d32d53c673d Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 19 Jan 2022 14:08:16 +0800 Subject: [PATCH 1333/2647] add sync instance Signed-off-by: panxunying --- .../models/collaboration_instance.go | 2 - .../mongodb/collaboration_instance.go | 75 ++++++++++ .../service/collaboration_instance.go | 131 ++++++++++++++++-- .../core/label/repository/mongodb/label.go | 8 +- pkg/microservice/aslan/core/service.go | 3 + 5 files changed, 199 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index b61413e8..5781b728 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -33,8 +33,6 @@ type CollaborationInstance struct { PolicyName string `bson:"policy_name" json:"policy_name"` Workflows []WorkflowCIItem `json:"workflows" bson:"workflows"` Products []ProductCIItem `bson:"products" json:"products"` - CreateBy string `bson:"create_by" json:"create_by"` - UpdateBy string `bson:"update_by" json:"update_by"` } type ProductCIItem struct { Name string `bson:"name" json:"name"` diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index a35c69b3..eda4b7d7 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -18,6 +18,8 @@ package mongodb import ( "context" + "errors" + "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -30,8 +32,13 @@ import ( type CollaborationInstanceFindOptions struct { ProjectName string + Name string UserUID string } + +type CollaborationInstanceListOptions struct { + FindOpts []CollaborationInstanceFindOptions +} type CollaborationInstanceColl struct { *mongo.Collection @@ -77,6 +84,74 @@ func (c *CollaborationInstanceColl) Find(opt *CollaborationInstanceFindOptions) return res, err } +func (c *CollaborationInstanceColl) Create(args *models.CollaborationInstance) error { + // avoid panic issue + if args == nil { + return errors.New("nil CollaborationInstance") + } + now := time.Now().Unix() + args.CreateTime = now + args.UpdateTime = now + args.Revision = 1 + _, err := c.InsertOne(context.TODO(), args) + return err +} + +// TODO: Update->BulkUpdate + +func (c *CollaborationInstanceColl) Update(uid string, args *models.CollaborationInstance) error { + if args == nil { + return errors.New("nil CollaborationMode") + } + + query := bson.M{"name": args.CollaborationName, "project_name": args.ProjectName, "user_uid": uid} + change := bson.M{"$set": bson.M{ + "update_time": time.Now().Unix(), + "workflows": args.Workflows, + "revision": args.Revision, + "products": args.Products, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + + return err +} + +func (c *CollaborationInstanceColl) BulkCreate(args []*models.CollaborationInstance) error { + if len(args) == 0 { + return nil + } + now := time.Now().Unix() + for _, arg := range args { + arg.CreateTime = now + arg.UpdateTime = now + arg.Revision = 1 + } + var ois []interface{} + for _, obj := range args { + ois = append(ois, obj) + } + _, err := c.InsertMany(context.TODO(), ois) + return err +} + +func (c *CollaborationInstanceColl) BulkDelete(opt CollaborationInstanceListOptions) error { + condition := bson.A{} + if len(opt.FindOpts) == 0 { + return nil + } + for _, findOpt := range opt.FindOpts { + condition = append(condition, bson.M{ + "project_name": findOpt.ProjectName, + "user_uid": findOpt.UserUID, + "collaboration_name": findOpt.Name, + }) + } + filter := bson.D{{"$or", condition}} + _, err := c.DeleteMany(context.TODO(), filter) + return err +} + func (c *CollaborationInstanceColl) List(opt *CollaborationInstanceFindOptions) ([]*models.CollaborationInstance, error) { var ret []*models.CollaborationInstance query := bson.M{} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index dae82438..1277d59d 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -18,9 +18,10 @@ import ( ) type GetCollaborationUpdateResp struct { - Update []UpdateItem `json:"update"` - New []models.CollaborationMode `json:"new"` - Delete []models.CollaborationInstance `json:"delete"` + UpdateInstance []models.CollaborationInstance `json:"update_instance"` + Update []UpdateItem `json:"update"` + New []models.CollaborationMode `json:"new"` + Delete []models.CollaborationInstance `json:"delete"` } type UpdateItem struct { CollaborationMode string `json:"collaboration_mode"` @@ -167,8 +168,51 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } } -func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*models.CollaborationInstance) (*GetCollaborationUpdateResp, error) { +func buildPolicyName(mode string, userName string) string { + return mode + "-" + userName + "-" + util.GetRandomString(6) +} + +func genCollaborationInstance(mode models.CollaborationMode, uid string, userName string) *models.CollaborationInstance { + var workflows []models.WorkflowCIItem + for _, workflow := range mode.Workflows { + name := workflow.Name + if workflow.CollaborationType == config.CollaborationNew { + name = buildName(workflow.Name, mode.Name, userName) + } + workflows = append(workflows, models.WorkflowCIItem{ + Name: name, + BaseName: workflow.Name, + Verbs: workflow.Verbs, + CollaborationType: workflow.CollaborationType, + }) + } + var products []models.ProductCIItem + for _, product := range mode.Products { + name := product.Name + if product.CollaborationType == config.CollaborationNew { + name = buildName(product.Name, mode.Name, userName) + } + products = append(products, models.ProductCIItem{ + Name: name, + BaseName: product.Name, + CollaborationType: product.CollaborationType, + Verbs: product.Verbs, + }) + } + return &models.CollaborationInstance{ + ProjectName: mode.Name, + CollaborationName: mode.Name, + User: uid, + PolicyName: buildPolicyName(mode.Name, userName), + Revision: mode.Revision, + Workflows: workflows, + Products: products, + } +} + +func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*models.CollaborationInstance, uid, userName string) (*GetCollaborationUpdateResp, error) { var updateItems []UpdateItem + var updateInstance []models.CollaborationInstance var newItems []models.CollaborationMode var deleteItems []models.CollaborationInstance for name, cm := range cmMap { @@ -177,6 +221,8 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model return nil, fmt.Errorf("CollaborationMode:%s revision error", name) } else if cm.Revision > ci.Revision { updateItems = append(updateItems, getUpdateDiff(cm, ci)) + instance := genCollaborationInstance(*cm, uid, userName) + updateInstance = append(updateInstance, *instance) } } else { newItems = append(newItems, *cm) @@ -188,13 +234,14 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model } } return &GetCollaborationUpdateResp{ - Update: updateItems, - New: newItems, - Delete: deleteItems, + UpdateInstance: updateInstance, + Update: updateItems, + New: newItems, + Delete: deleteItems, }, nil } -func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { +func GetCollaborationUpdate(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ Projects: []string{projectName}, Members: []string{uid}, @@ -215,7 +262,7 @@ func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) for _, instance := range collaborationInstances { ciMap[instance.CollaborationName] = instance } - resp, err := getDiff(cmMap, ciMap) + resp, err := getDiff(cmMap, ciMap, uid, userName) if err != nil { logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err @@ -225,18 +272,64 @@ func GetCollaborationUpdate(projectName, uid string, logger *zap.SugaredLogger) func buildName(baseName, modeName, userName string) string { return modeName + "-" + baseName + "-" + userName + "-" + util.GetRandomString(6) } + +func syncInstance(updateResp *GetCollaborationUpdateResp, userName, uid string, + logger *zap.SugaredLogger) error { + var instances []*models.CollaborationInstance + for _, mode := range updateResp.New { + instances = append(instances, genCollaborationInstance(mode, uid, userName)) + } + err := mongodb.NewCollaborationInstanceColl().BulkCreate(instances) + if err != nil { + logger.Errorf("syncInstance BulkCreate error, error msg:%s", err) + return err + } + for _, instance := range updateResp.UpdateInstance { + err = mongodb.NewCollaborationInstanceColl().Update(uid, &instance) + if err != nil { + logger.Errorf("syncInstance Update error, error msg:%s", err) + return err + } + } + var findOpts []mongodb.CollaborationInstanceFindOptions + for _, instance := range updateResp.Delete { + findOpts = append(findOpts, mongodb.CollaborationInstanceFindOptions{ + Name: instance.CollaborationName, + ProjectName: instance.ProjectName, + UserUID: instance.User, + }) + } + err = mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ + FindOpts: findOpts, + }) + return err +} + +func syncPolicy(updateResp *GetCollaborationUpdateResp, userName, uid string, + logger *zap.SugaredLogger) error { + for _, mode := range updateResp.New { + + } +} + func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.SugaredLogger) error { + updateResp, err := GetCollaborationUpdate(projectName, uid, userName, logger) + if err != nil { + logger.Errorf("GetCollaborationNew error, err msg:%s", err) + return err + } + err = syncInstance(updateResp, userName, uid, logger) + if err != nil { + return err + } return nil } -func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { + +func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, userName string, + logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { var newWorkflow []*Workflow var newProduct []*Product newProductName := sets.String{} - updateResp, err := GetCollaborationUpdate(projectName, uid, logger) - if err != nil { - logger.Errorf("GetCollaborationNew error, err msg:%s", err) - return nil, err - } for _, mode := range updateResp.New { for _, workflow := range mode.Workflows { name := workflow.Name @@ -361,6 +454,14 @@ func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredL Product: newProduct, }, nil } +func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { + updateResp, err := GetCollaborationUpdate(projectName, uid, userName, logger) + if err != nil { + logger.Errorf("GetCollaborationNew error, err msg:%s", err) + return nil, err + } + return getCollaborationNew(updateResp, projectName, userName, logger) +} func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 9b8bf61e..c8669eee 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -10,19 +10,21 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + package mongodb import ( "context" "time" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) type LabelColl struct { diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 1bdeaf74..fc458295 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -26,6 +26,7 @@ import ( commonconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" + modeMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" @@ -214,6 +215,8 @@ func initDatabase() { systemrepo.NewOperationLogColl(), labelMongodb.NewLabelColl(), labelMongodb.NewLabelBindingColl(), + modeMongodb.NewCollaborationModeColl(), + modeMongodb.NewCollaborationInstanceColl(), } { wg.Add(1) go func(r indexer) { -- Gitee From a500c8814514356fa8f6196cb91a7970f8b5ca76 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 19 Jan 2022 13:55:41 +0800 Subject: [PATCH 1334/2647] fix shared service update fail bug Signed-off-by: allenshen --- .../core/environment/service/environment.go | 48 ++++++++++++++++++- .../core/environment/service/revision.go | 7 ++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d1540df2..10a8f915 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -358,6 +358,52 @@ func AutoUpdateProduct(envNames []string, productName, requestID string, force b } +// getServicesWithMaxRevision get all services template with max revision including involved shared services +func getServicesWithMaxRevision(projectName string) ([]*commonmodels.Service, error) { + // list services with max revision defined in project + allServices, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: projectName}) + if err != nil { + log.Errorf("failed to find services in project: %s, err: %s", projectName, err) + return nil, fmt.Errorf("failed to find services in project: %s", projectName) + } + + prodTmpl, err := templaterepo.NewProductColl().Find(projectName) + if err != nil { + log.Error(fmt.Sprintf("[ProductTmpl.Find] %s error: %v", projectName, err)) + return nil, fmt.Errorf("failed to find project tempalte: %s", projectName) + } + + // list services with max revision of shared services + if prodTmpl.SharedServices != nil { + servicesByProject := make(map[string][]string) + for _, serviceInfo := range prodTmpl.SharedServices { + servicesByProject[serviceInfo.Owner] = append(servicesByProject[serviceInfo.Owner], serviceInfo.Name) + } + + for sourceProject, services := range servicesByProject { + inService := make([]*templatemodels.ServiceInfo, 0) + for _, serviceName := range services { + inService = append(inService, &templatemodels.ServiceInfo{ + Name: serviceName, + Owner: sourceProject, + }) + } + + sharedServices, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ + ProductName: sourceProject, + InServices: inService, + }) + if err != nil { + log.Errorf("ListSharedServiceRevisions error: %s", err) + return nil, fmt.Errorf("failed to find shared service templates, projectName: %s, err: %s", sourceProject, err) + } + allServices = append(allServices, sharedServices...) + } + } + + return allServices, nil +} + func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *commonmodels.RenderSet, log *zap.SugaredLogger) (err error) { // 设置产品新的renderinfo updateProd.Render = &commonmodels.RenderInfo{ @@ -376,7 +422,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com var prodRevs *ProductRevision // list services with max revision of project - allServices, err = commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: productName}) + allServices, err = getServicesWithMaxRevision(productName) if err != nil { log.Errorf("ListAllRevisions error: %s", err) err = e.ErrUpdateEnv.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index be9a6853..ceccdb76 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -35,7 +35,12 @@ func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) ( // list services with max revision of project // TODO refactor code - allServiceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{}) + allServiceTmpls, err := getServicesWithMaxRevision(productName) + if err != nil { + log.Errorf("ListAllRevisions error: %s", err) + err = e.ErrListProducts.AddDesc(err.Error()) + return + } if err != nil { log.Errorf("ListAllRevisions error: %v", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) -- Gitee From 2d569bd9ca2513176e14f2206b2f94af56dae44f Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 19 Jan 2022 14:15:47 +0800 Subject: [PATCH 1335/2647] remove duplicated err check statement Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/revision.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index ceccdb76..33044c71 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -36,11 +36,6 @@ func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) ( // list services with max revision of project // TODO refactor code allServiceTmpls, err := getServicesWithMaxRevision(productName) - if err != nil { - log.Errorf("ListAllRevisions error: %s", err) - err = e.ErrListProducts.AddDesc(err.Error()) - return - } if err != nil { log.Errorf("ListAllRevisions error: %v", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) -- Gitee From 4237dcd70800737203c83c67303961ba4fabdf88 Mon Sep 17 00:00:00 2001 From: fansi Date: Wed, 19 Jan 2022 14:42:53 +0800 Subject: [PATCH 1336/2647] update time for test report Signed-off-by: fansi --- examples/voting-app/function-test/fail/result_1.xml | 10 +++++----- examples/voting-app/function-test/pass/result_1.xml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/voting-app/function-test/fail/result_1.xml b/examples/voting-app/function-test/fail/result_1.xml index abe52156..b56b4455 100644 --- a/examples/voting-app/function-test/fail/result_1.xml +++ b/examples/voting-app/function-test/fail/result_1.xml @@ -4,11 +4,11 @@ - + - - + + - - + + \ No newline at end of file diff --git a/examples/voting-app/function-test/pass/result_1.xml b/examples/voting-app/function-test/pass/result_1.xml index abe52156..bbe04641 100644 --- a/examples/voting-app/function-test/pass/result_1.xml +++ b/examples/voting-app/function-test/pass/result_1.xml @@ -4,11 +4,11 @@ - + - - + + - - + + \ No newline at end of file -- Gitee From d02fa90a93e3a116ca1104e45f85a88b988e5b1f Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 19 Jan 2022 14:58:49 +0800 Subject: [PATCH 1337/2647] bingd Signed-off-by: mouuii --- .../core/common/repository/mongodb/product.go | 2 +- .../aslan/core/environment/service/bundle.go | 30 +++++- .../core/workflow/service/workflow/bundle.go | 4 +- .../policy/core/service/bundle/opa_bundle.go | 96 +++++++++++++++++-- 4 files changed, 120 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index b5053be8..b86677b6 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -159,7 +159,7 @@ func (c *ProductColl) ListByProducts(opt ListProductOpt) ([]*models.Product, err condition := bson.A{} for _, pro := range opt.Products { condition = append(condition, bson.M{ - "name": pro.Name, + "env_name": pro.Name, "product_name": pro.ProjectName, }) } diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 114b2f92..2914e097 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -17,10 +17,14 @@ limitations under the License. package service import ( + "fmt" "strconv" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" + labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/tool/log" ) @@ -50,6 +54,21 @@ func GetBundleResources() ([]*resourceSpec, error) { clusterMap[cls.ID.Hex()] = cls } + // get labels by workflow resources ids + var resources []labeldb.Resource + for _, env := range envs { + resource := labeldb.Resource{ + Name: env.EnvName, + ProjectName: env.ProductName, + Type: string(config.ResourceTypeProduct), + } + resources = append(resources, resource) + } + labelsResp, err := service.ListLabelsByResources(resources, nil) + if err != nil { + return nil, err + } + for _, env := range envs { clusterID := env.ClusterID production := false @@ -58,13 +77,20 @@ func GetBundleResources() ([]*resourceSpec, error) { production = cluster.Production } - res = append(res, &resourceSpec{ + resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeProduct, env.ProductName, env.EnvName) + resourceSpec := &resourceSpec{ ResourceID: env.EnvName, ProjectName: env.ProductName, Spec: map[string]interface{}{ "production": strconv.FormatBool(production), }, - }) + } + if labels, ok := labelsResp.Labels[resourceKey]; ok { + for _, v := range labels { + resourceSpec.Spec[v.Key] = v.Value + } + } + res = append(res, resourceSpec) } return res, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 7bd49666..ce2540ae 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -36,7 +36,7 @@ type resourceSpec struct { func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { var res []*resourceSpec - workflows, err := mongodb.NewWorkflowColl().List(nil) + workflows, err := mongodb.NewWorkflowColl().List(&mongodb.ListWorkflowOption{}) if err != nil { log.Error("Failed to list workflows , err:%s", err) return nil, err @@ -44,7 +44,6 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { // get labels by workflow resources ids var resources []labeldb.Resource - for _, workflow := range workflows { resource := labeldb.Resource{ Name: workflow.Name, @@ -64,6 +63,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec := &resourceSpec{ ResourceID: workflow.Name, ProjectName: workflow.ProductTmplName, + Spec: make(map[string]interface{}), } if labels, ok := labelsResp.Labels[resourceKey]; ok { for _, v := range labels { diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index ec230749..4eaa6198 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -31,9 +31,11 @@ const ( manifestPath = ".manifest" policyPath = "authz.rego" rolesPath = "roles/data.json" + policiesPath = "policies/data.json" rolebindingsPath = "bindings/data.json" - exemptionsPath = "exemptions/data.json" - resourcesPath = "resources/data.json" + + exemptionsPath = "exemptions/data.json" + resourcesPath = "resources/data.json" policyRoot = "rbac" rolesRoot = "roles" @@ -67,8 +69,13 @@ type opaRoles struct { Roles roles `json:"roles"` } +type opaPolicies struct { + Roles roles `json:"policies"` +} + type opaRoleBindings struct { - RoleBindings roleBindings `json:"role_bindings"` + RoleBindings roleBindings `json:"role_bindings"` + PolicyBindings roleBindings `json:"policy_bindings"` } type role struct { @@ -91,6 +98,18 @@ type Attribute struct { Value string `json:"value"` } +func NewFrom(attrs []models.MatchAttribute) Attributes { + var att Attributes + for _, v := range attrs { + a := Attribute{ + Key: v.Key, + Value: v.Value, + } + att = append(att, &a) + } + return att +} + type Attributes []*Attribute func (a Attributes) LessOrEqual(other Attributes) bool { @@ -226,7 +245,40 @@ func generateOPARoles(roles []*models.Role, policies []*models.PolicyMeta) *opaR return data } -func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { +func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.PolicyMeta) *opaPolicies { + data := &opaPolicies{} + resourceMappings := getResourceActionMappings(policyMetas) + + for _, policy := range policies { + opaRole := &role{Name: policy.Name, Namespace: policy.Namespace} + for _, r := range policy.Rules { + if r.Kind == models.KindResource { + for _, res := range r.Resources { + opaRole.Rules = append(opaRole.Rules, resourceMappings.GetRules(res, r.Verbs)...) + } + } else { + if len(r.Verbs) == 1 && r.Verbs[0] == models.MethodAll { + r.Verbs = AllMethods + } + for _, v := range r.Verbs { + for _, endpoint := range r.Resources { + opaRole.Rules = append(opaRole.Rules, &rule{Method: v, Endpoint: endpoint}) + } + } + } + + for _, v := range opaRole.Rules { + v.MatchAttributes = NewFrom(r.MatchAttributes) + } + } + sort.Sort(opaRole.Rules) + data.Roles = append(data.Roles, opaRole) + } + sort.Sort(data.Roles) + return data +} + +func generateOPARoleBindings(rbs []*models.RoleBinding, pbs []*models.PolicyBinding) *opaRoleBindings { data := &opaRoleBindings{} userRoleMap := make(map[string]map[string][]*roleRef) @@ -254,6 +306,31 @@ func generateOPARoleBindings(rbs []*models.RoleBinding) *opaRoleBindings { sort.Sort(data.RoleBindings) + userPolicyMap := make(map[string]map[string][]*roleRef) + + for _, rb := range pbs { + for _, s := range rb.Subjects { + if s.Kind == models.UserKind { + if _, ok := userPolicyMap[s.UID]; !ok { + userPolicyMap[s.UID] = make(map[string][]*roleRef) + } + userPolicyMap[s.UID][rb.Namespace] = append(userRoleMap[s.UID][rb.Namespace], &roleRef{Name: rb.PolicyRef.Name, Namespace: rb.PolicyRef.Namespace}) + } + } + } + + for u, nb := range userPolicyMap { + var bindingsData []*binding + for n, b := range nb { + sort.Sort(roleRefs(b)) + bindingsData = append(bindingsData, &binding{Namespace: n, RoleRefs: b}) + } + sort.Sort(bindings(bindingsData)) + data.PolicyBindings = append(data.PolicyBindings, &roleBinding{UID: u, Bindings: bindingsData}) + } + + sort.Sort(data.PolicyBindings) + return data } @@ -324,7 +401,11 @@ func GenerateOPABundle() error { if err != nil { log.Errorf("Failed to list roleBindings, err: %s", err) } - ps, err := mongodb.NewPolicyMetaColl().List() + pms, err := mongodb.NewPolicyMetaColl().List() + if err != nil { + log.Errorf("Failed to list policyMetas, err: %s", err) + } + policies, err := mongodb.NewPolicyColl().List() if err != nil { log.Errorf("Failed to list policies, err: %s", err) } @@ -332,9 +413,10 @@ func GenerateOPABundle() error { bundle := &opa.Bundle{ Data: []*opa.DataSpec{ {Data: generateOPAPolicy(), Path: policyPath}, - {Data: generateOPARoles(rs, ps), Path: rolesPath}, + {Data: generateOPARoles(rs, pms), Path: rolesPath}, + {Data: generateOPAPolicies(policies, pms), Path: policiesPath}, {Data: generateOPARoleBindings(bs), Path: rolebindingsPath}, - {Data: generateOPAExemptionURLs(ps), Path: exemptionsPath}, + {Data: generateOPAExemptionURLs(pms), Path: exemptionsPath}, {Data: generateResourceBundle(), Path: resourcesPath}, }, Roots: []string{policyRoot, rolesRoot, rolebindingsRoot, exemptionsRoot, resourcesRoot}, -- Gitee From 7c584a9c24c4889e56604f7f75b06e6433a63dd3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 19 Jan 2022 14:59:29 +0800 Subject: [PATCH 1338/2647] bingd Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 4eaa6198..172f1b9a 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -401,6 +401,12 @@ func GenerateOPABundle() error { if err != nil { log.Errorf("Failed to list roleBindings, err: %s", err) } + + pbs, err := mongodb.NewPolicyBindingColl().List() + if err != nil { + log.Errorf("Failed to list roleBindings, err: %s", err) + } + pms, err := mongodb.NewPolicyMetaColl().List() if err != nil { log.Errorf("Failed to list policyMetas, err: %s", err) @@ -415,7 +421,7 @@ func GenerateOPABundle() error { {Data: generateOPAPolicy(), Path: policyPath}, {Data: generateOPARoles(rs, pms), Path: rolesPath}, {Data: generateOPAPolicies(policies, pms), Path: policiesPath}, - {Data: generateOPARoleBindings(bs), Path: rolebindingsPath}, + {Data: generateOPARoleBindings(bs, pbs), Path: rolebindingsPath}, {Data: generateOPAExemptionURLs(pms), Path: exemptionsPath}, {Data: generateResourceBundle(), Path: resourcesPath}, }, -- Gitee From f6822f72dfc87d549ed6d9b1ae8f97ac7a767d63 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 19 Jan 2022 15:19:37 +0800 Subject: [PATCH 1339/2647] clear webhook when deleting test modules Signed-off-by: allenshen --- .../aslan/core/common/service/testing.go | 17 +++++- .../core/workflow/testing/handler/testing.go | 3 +- .../core/workflow/testing/service/testing.go | 52 +------------------ 3 files changed, 20 insertions(+), 52 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/testing.go b/pkg/microservice/aslan/core/common/service/testing.go index 5f2dc5df..bed18705 100644 --- a/pkg/microservice/aslan/core/common/service/testing.go +++ b/pkg/microservice/aslan/core/common/service/testing.go @@ -23,6 +23,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -52,7 +54,20 @@ func Delete(name, productName string, log *zap.SugaredLogger) error { return e.ErrDeleteTestModule.AddDesc("empty Name") } - err := mongodb.NewTestingColl().Delete(name, productName) + // find test data to delete webhook + testModule, err := commonrepo.NewTestingColl().Find(name, productName) + if err != nil { + log.Errorf("Workflow.Find error: %v", err) + return e.ErrDeleteTestModule.AddDesc(err.Error()) + } + + // delete webhooks of test + err = ProcessWebhook(nil, testModule.HookCtl.Items, webhook.TestingPrefix+name, log) + if err != nil { + return e.ErrDeleteTestModule.AddErr(err) + } + + err = mongodb.NewTestingColl().Delete(name, productName) if err != nil { log.Errorf("[Testing.Delete] %s error: %v", name, err) return e.ErrDeleteTestModule.AddErr(err) diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/testing.go b/pkg/microservice/aslan/core/workflow/testing/handler/testing.go index 33837f80..d7df15dd 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/testing.go @@ -24,6 +24,7 @@ import ( "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -127,7 +128,7 @@ func DeleteTestModule(c *gin.Context) { return } - ctx.Err = service.DeleteTestModule(name, c.Query("projectName"), ctx.RequestID, ctx.Logger) + ctx.Err = commonservice.DeleteTestModule(name, c.Query("projectName"), ctx.RequestID, ctx.Logger) } func GetHTMLTestReport(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/testing/service/testing.go b/pkg/microservice/aslan/core/workflow/testing/service/testing.go index 78d6daf6..0ade2e7d 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/testing.go @@ -53,7 +53,7 @@ func CreateTesting(username string, testing *commonmodels.Testing, log *zap.Suga return e.ErrCreateTestModule.AddErr(err) } - err = commonservice.ProcessWebhook(testing.HookCtl.Items,nil,webhook.TestingPrefix+testing.Name,log) + err = commonservice.ProcessWebhook(testing.HookCtl.Items, nil, webhook.TestingPrefix+testing.Name, log) if err != nil { return e.ErrUpdateTestModule.AddErr(err) } @@ -118,7 +118,7 @@ func UpdateTesting(username string, testing *commonmodels.Testing, log *zap.Suga commonservice.EnsureSecretEnvs(existed.PreTest.Envs, testing.PreTest.Envs) } - err = commonservice.ProcessWebhook(testing.HookCtl.Items,existed.HookCtl.Items,webhook.TestingPrefix+testing.Name,log) + err = commonservice.ProcessWebhook(testing.HookCtl.Items, existed.HookCtl.Items, webhook.TestingPrefix+testing.Name, log) if err != nil { return e.ErrUpdateTestModule.AddErr(err) } @@ -287,54 +287,6 @@ func ListCronjob(name, jobType string) ([]*commonmodels.Cronjob, error) { }) } -func DeleteTestModule(name, productName, requestID string, log *zap.SugaredLogger) error { - opt := new(commonrepo.ListQueueOption) - taskQueue, err := commonrepo.NewQueueColl().List(opt) - if err != nil { - log.Errorf("List queued task error: %v", err) - return fmt.Errorf("list queued task error: %v", err) - } - pipelineName := fmt.Sprintf("%s-%s", name, "job") - // 当task还在运行时,先取消任务 - for _, task := range taskQueue { - if task.PipelineName == pipelineName && task.Type == config.TestType { - if err = commonservice.CancelTaskV2("system", task.PipelineName, task.TaskID, config.TestType, requestID, log); err != nil { - log.Errorf("test task still running,cancel pipeline %s task %d", task.PipelineName, task.TaskID) - } - } - } - - return DeleteTestingModule(name, productName, log) -} - -func DeleteTestingModule(name, productName string, log *zap.SugaredLogger) error { - if len(name) == 0 { - return e.ErrDeleteTestModule.AddDesc("empty Name") - } - - err := commonrepo.NewTestingColl().Delete(name, productName) - if err != nil { - log.Errorf("[Testing.Delete] %s error: %v", name, err) - return e.ErrDeleteTestModule.AddErr(err) - } - - if err := commonrepo.NewTaskColl().DeleteByPipelineNameAndType(fmt.Sprintf("%s-%s", name, "job"), config.TestType); err != nil { - log.Errorf("[Testing.Delete] PipelineTaskV2.DeleteByPipelineNameAndType test %s error: %v", name, err) - } - - if err := commonrepo.NewTestTaskStatColl().Delete(name); err != nil { - log.Errorf("[TestTaskStat.Delete] %s error: %v", name, err) - } - - pipelineName := fmt.Sprintf("%s-%s", name, "job") - counterName := fmt.Sprintf(setting.TestTaskFmt, pipelineName) - if err := commonrepo.NewCounterColl().Delete(counterName); err != nil { - log.Errorf("[Counter.Delete] counterName:%s, error: %v", counterName, err) - } - - return nil -} - func GetHTMLTestReport(pipelineName, pipelineType, taskIDStr, testName string, log *zap.SugaredLogger) (string, error) { if err := validateTestReportParam(pipelineName, pipelineType, taskIDStr, testName, log); err != nil { return "", e.ErrGetTestReport.AddErr(err) -- Gitee From 83d264181d01ce4d0472694a56eb09041dfee95c Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 19 Jan 2022 15:39:58 +0800 Subject: [PATCH 1340/2647] refactor Signed-off-by: mouuii --- .../policy/core/service/bundle/opa_bundle.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 172f1b9a..708f3da5 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -28,11 +28,11 @@ import ( ) const ( - manifestPath = ".manifest" - policyPath = "authz.rego" - rolesPath = "roles/data.json" - policiesPath = "policies/data.json" - rolebindingsPath = "bindings/data.json" + manifestPath = ".manifest" + policyRegoPath = "authz.rego" + rolesPath = "roles/data.json" + policiesPath = "policies/data.json" + bindingsPath = "bindings/data.json" exemptionsPath = "exemptions/data.json" resourcesPath = "resources/data.json" @@ -385,7 +385,7 @@ func generateOPAExemptionURLs(policies []*models.PolicyMeta) *exemptionURLs { return data } -func generateOPAPolicy() []byte { +func generateOPAPolicyRego() []byte { return authz } @@ -418,10 +418,10 @@ func GenerateOPABundle() error { bundle := &opa.Bundle{ Data: []*opa.DataSpec{ - {Data: generateOPAPolicy(), Path: policyPath}, + {Data: generateOPAPolicyRego(), Path: policyRegoPath}, {Data: generateOPARoles(rs, pms), Path: rolesPath}, {Data: generateOPAPolicies(policies, pms), Path: policiesPath}, - {Data: generateOPARoleBindings(bs, pbs), Path: rolebindingsPath}, + {Data: generateOPARoleBindings(bs, pbs), Path: bindingsPath}, {Data: generateOPAExemptionURLs(pms), Path: exemptionsPath}, {Data: generateResourceBundle(), Path: resourcesPath}, }, -- Gitee From 2157f8f42a50e6240a014a261e73fd1b9243f3b1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 19 Jan 2022 16:37:38 +0800 Subject: [PATCH 1341/2647] fix deploy return bug Signed-off-by: allenshen --- .../core/environment/service/environment.go | 7 ++- .../core/service/taskplugin/deploy.go | 57 ++++++++----------- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d1540df2..f2838d95 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -26,13 +26,14 @@ import ( "sync" "time" + "helm.sh/helm/v3/pkg/storage" + "github.com/cenkalti/backoff/v4" "github.com/hashicorp/go-multierror" helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" - helmrelease "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/strvals" appsv1 "k8s.io/api/apps/v1" @@ -2301,8 +2302,8 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha if len(hrs) > 0 { releaseutil.Reverse(hrs, releaseutil.SortByRevision) rel := hrs[0] - if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { - secretName := fmt.Sprintf("sh.helm.release.v1.%s.v%d", rel.Name, rel.Version) + if rel.Info.Status.IsPending() { + secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, rel.Name, rel.Version) deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, kubecli) if deleteErr != nil { err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 8b42e78a..31e8dbef 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -24,10 +24,11 @@ import ( "strings" "time" + "helm.sh/helm/v3/pkg/storage" + helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.uber.org/zap" - helmrelease "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" @@ -544,36 +545,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Wait: true, } - isFailedOrPendingStatus := func() bool { - hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 5) - if errHistory != nil { - return false - } - if len(hrs) > 0 { - releaseutil.Reverse(hrs, releaseutil.SortByRevision) - rel := hrs[0] - return rel.Info.Status == helmrelease.StatusFailed || rel.Info.Status == helmrelease.StatusPendingUpgrade || rel.Info.Status == helmrelease.StatusPendingInstall - } - return false - } - if isFailedStatus := isFailedOrPendingStatus(); isFailedStatus { - chartSpec.Replace = true - return - } - - done := make(chan bool) - go func(chan bool) { - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { - err = errors.WithMessagef( - err, - "failed to Install helm chart %s/%s", - p.Task.Namespace, p.Task.ServiceName) - done <- false - } else { - done <- true - } - }(done) - pendingStatusProcess := func(typ string) error { hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) if errHistory != nil { @@ -586,8 +557,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if len(hrs) > 0 { releaseutil.Reverse(hrs, releaseutil.SortByRevision) rel := hrs[0] - if rel.Info.Status == helmrelease.StatusPendingInstall || rel.Info.Status == helmrelease.StatusPendingUpgrade { - secretName := fmt.Sprintf("sh.helm.release.v1.%s.v%d", rel.Name, rel.Version) + if rel.Info.Status.IsPending() { + secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, rel.Name, rel.Version) deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, p.kubeClient) if deleteErr != nil { err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) @@ -601,6 +572,26 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return err } + // ensure release status not in pending + err = pendingStatusProcess("ensureStatus") + if err != nil { + err = errors.Wrapf(err, "failed to delete pending status for release: %s", chartSpec.ReleaseName) + return + } + + done := make(chan bool) + go func(chan bool) { + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to Install helm chart %s/%s", + p.Task.Namespace, p.Task.ServiceName) + done <- false + } else { + done <- true + } + }(done) + select { case d := <-done: if !d { -- Gitee From 1598128e1db5ddaa0c7a9db2d7b54bb1c622a2a0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 19 Jan 2022 16:38:09 +0800 Subject: [PATCH 1342/2647] fix deploy return bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 3 +-- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f2838d95..1dd77509 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -26,8 +26,6 @@ import ( "sync" "time" - "helm.sh/helm/v3/pkg/storage" - "github.com/cenkalti/backoff/v4" "github.com/hashicorp/go-multierror" helmclient "github.com/mittwald/go-helm-client" @@ -35,6 +33,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/strvals" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 31e8dbef..8f043aca 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -24,12 +24,11 @@ import ( "strings" "time" - "helm.sh/helm/v3/pkg/storage" - helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v3/pkg/storage" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" -- Gitee From 5bf9c2e37343b6bedac219ed726e2196c4cea962 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 19 Jan 2022 16:43:27 +0800 Subject: [PATCH 1343/2647] router Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/router.go | 2 +- pkg/shared/client/policy/policy.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index fe3bd940..07dbbcd9 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -99,7 +99,7 @@ func (*Router) Inject(router *gin.RouterGroup) { bundles.GET("/:name", DownloadBundle) } - policyRegistrations := router.Group("policies") + policyRegistrations := router.Group("policymetas") { policyRegistrations.PUT("/:resourceName", CreateOrUpdatePolicyRegistration) } diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index a511f330..56a91402 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -57,7 +57,7 @@ type RoleBinding struct { } func (c *Client) CreateOrUpdatePolicy(p *Policy) error { - url := fmt.Sprintf("/policies/%s", p.Resource) + url := fmt.Sprintf("/policymetas/%s", p.Resource) _, err := c.Put(url, httpclient.SetBody(p)) -- Gitee From b8978b7e5ff50c4f3d69f85afdfcbd0aeccba5cf Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 19 Jan 2022 17:13:10 +0800 Subject: [PATCH 1344/2647] add sync policy Signed-off-by: panxunying --- .../service/collaboration_instance.go | 136 +++++++++++++++-- .../delivery/handler/policy_definitions.go | 6 +- .../environment/handler/policy_definitions.go | 8 +- .../project/handler/policy_definitions.go | 6 +- pkg/microservice/aslan/core/service.go | 6 +- .../workflow/handler/policy_definitions.go | 6 +- .../testing/handler/policy_definitions.go | 6 +- .../policy/core/handler/policy.go | 10 +- .../policy/core/handler/router.go | 4 +- .../policy/core/repository/mongodb/policy.go | 12 ++ .../policy/core/service/policy.go | 22 ++- pkg/shared/client/policy/policy.go | 121 ++++----------- pkg/shared/client/policy/role.go | 143 ++++++++++++++++++ 13 files changed, 353 insertions(+), 133 deletions(-) create mode 100644 pkg/shared/client/policy/role.go diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 1277d59d..d6314942 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -14,6 +14,7 @@ import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/util" ) @@ -168,11 +169,11 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } } -func buildPolicyName(mode string, userName string) string { - return mode + "-" + userName + "-" + util.GetRandomString(6) +func buildPolicyName(projectName, mode string, userName string) string { + return projectName + "-" + mode + "-" + userName + "-" + util.GetRandomString(6) } -func genCollaborationInstance(mode models.CollaborationMode, uid string, userName string) *models.CollaborationInstance { +func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, userName string) *models.CollaborationInstance { var workflows []models.WorkflowCIItem for _, workflow := range mode.Workflows { name := workflow.Name @@ -203,14 +204,15 @@ func genCollaborationInstance(mode models.CollaborationMode, uid string, userNam ProjectName: mode.Name, CollaborationName: mode.Name, User: uid, - PolicyName: buildPolicyName(mode.Name, userName), + PolicyName: buildPolicyName(projectName, mode.Name, userName), Revision: mode.Revision, Workflows: workflows, Products: products, } } -func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*models.CollaborationInstance, uid, userName string) (*GetCollaborationUpdateResp, error) { +func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*models.CollaborationInstance, projectName, + uid, userName string) (*GetCollaborationUpdateResp, error) { var updateItems []UpdateItem var updateInstance []models.CollaborationInstance var newItems []models.CollaborationMode @@ -221,7 +223,7 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model return nil, fmt.Errorf("CollaborationMode:%s revision error", name) } else if cm.Revision > ci.Revision { updateItems = append(updateItems, getUpdateDiff(cm, ci)) - instance := genCollaborationInstance(*cm, uid, userName) + instance := genCollaborationInstance(*cm, projectName, uid, userName) updateInstance = append(updateInstance, *instance) } } else { @@ -262,7 +264,7 @@ func GetCollaborationUpdate(projectName, uid, userName string, logger *zap.Sugar for _, instance := range collaborationInstances { ciMap[instance.CollaborationName] = instance } - resp, err := getDiff(cmMap, ciMap, uid, userName) + resp, err := getDiff(cmMap, ciMap, projectName, uid, userName) if err != nil { logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err @@ -273,22 +275,25 @@ func buildName(baseName, modeName, userName string) string { return modeName + "-" + baseName + "-" + userName + "-" + util.GetRandomString(6) } -func syncInstance(updateResp *GetCollaborationUpdateResp, userName, uid string, - logger *zap.SugaredLogger) error { +func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, userName, uid string, + logger *zap.SugaredLogger) (map[string]string, error) { var instances []*models.CollaborationInstance + modePolicyMap := make(map[string]string) for _, mode := range updateResp.New { - instances = append(instances, genCollaborationInstance(mode, uid, userName)) + instance := genCollaborationInstance(mode, projectName, uid, userName) + instances = append(instances, instance) + modePolicyMap[mode.Name] = instance.PolicyName } err := mongodb.NewCollaborationInstanceColl().BulkCreate(instances) if err != nil { logger.Errorf("syncInstance BulkCreate error, error msg:%s", err) - return err + return nil, err } for _, instance := range updateResp.UpdateInstance { err = mongodb.NewCollaborationInstanceColl().Update(uid, &instance) if err != nil { logger.Errorf("syncInstance Update error, error msg:%s", err) - return err + return nil, err } } var findOpts []mongodb.CollaborationInstanceFindOptions @@ -302,14 +307,111 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, userName, uid string, err = mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ FindOpts: findOpts, }) - return err + if err != nil { + return nil, err + } + return modePolicyMap, nil +} + +func buildPolicyDescription(mode, userName string) string { + return mode + " " + userName + "的权限" } -func syncPolicy(updateResp *GetCollaborationUpdateResp, userName, uid string, +func syncPolicy(updateResp *GetCollaborationUpdateResp, modePolicyMap map[string]string, projectName, userName, uid string, logger *zap.SugaredLogger) error { + var policies []*policy.Policy + for _, mode := range updateResp.New { + var rules []*policy.Rule + var policyName string + if name, ok := modePolicyMap[mode.Name]; ok { + policyName = name + } else { + return fmt.Errorf("mode:%s not exist policyName", mode.Name) + } + for _, workflow := range mode.Workflows { + rules = append(rules, &policy.Rule{ + Verbs: workflow.Verbs, + Kind: "resource", + Resources: []string{"Workflow"}, + MatchAttributes: []policy.MatchAttribute{ + { + Key: "policy", + Value: policyName, + }, + }, + }) + } + for _, product := range mode.Products { + rules = append(rules, &policy.Rule{ + Verbs: product.Verbs, + Kind: "resource", + Resources: []string{"Product"}, + MatchAttributes: []policy.MatchAttribute{ + { + Key: "policy", + Value: policyName, + }, + }, + }) + } + policies = append(policies, &policy.Policy{ + Name: policyName, + Description: buildPolicyDescription(mode.Name, userName), + Rules: rules, + }) + } + err := policy.NewDefault().CreatePolicies(policy.CreatePoliciesArgs{ + Policies: policies, + }) + if err != nil { + logger.Errorf("syncPolicy error, error msg:%s", err) + return err + } + var updatePolicies []*policy.Policy + for _, instance := range updateResp.UpdateInstance { + var rules []*policy.Rule + for _, workflow := range instance.Workflows { + rules = append(rules, &policy.Rule{ + Verbs: workflow.Verbs, + Kind: "resource", + Resources: []string{"Workflow"}, + MatchAttributes: []policy.MatchAttribute{ + { + Key: "policy", + Value: instance.PolicyName, + }, + }, + }) + } + policies = append(policies, &policy.Policy{ + Name: instance.PolicyName, + Rules: rules, + }) + } + for _, updatePolicy := range updatePolicies { + err = policy.NewDefault().UpdatePolicy(projectName, updatePolicy) + if err != nil { + return err + } + } + var deletePolicies []string + for _, instance := range updateResp.Delete { + deletePolicies = append(deletePolicies, instance.PolicyName) + } + err = policy.NewDefault().DeletePolicies(projectName, policy.DeletePoliciesArgs{ + Names: deletePolicies, + }) + if err != nil { + return err + } + return nil +} + +func syncLabel(updateResp *GetCollaborationUpdateResp) error { for _, mode := range updateResp.New { } + return nil } func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.SugaredLogger) error { @@ -318,7 +420,11 @@ func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.Su logger.Errorf("GetCollaborationNew error, err msg:%s", err) return err } - err = syncInstance(updateResp, userName, uid, logger) + modePolicyMap, err := syncInstance(updateResp, projectName, userName, uid, logger) + if err != nil { + return err + } + err = syncPolicy(updateResp, modePolicyMap, projectName, userName, uid, logger) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go index 377fb358..dfe7b02b 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go @@ -28,13 +28,13 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() []*policy.Policy { - res := &policy.Policy{} +func (*Router) Policies() []*policy.PolicyMeta { + res := &policy.PolicyMeta{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { // should not have happened here log.DPanic(err) } - return []*policy.Policy{res} + return []*policy.PolicyMeta{res} } diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go index 46500ee8..5d1fb402 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go @@ -35,15 +35,15 @@ const ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() []*policy.Policy { - res := &policy.Policy{} +func (*Router) Policies() []*policy.PolicyMeta { + res := &policy.PolicyMeta{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { // should not have happened here log.DPanic(err) } - productionPolicy := &policy.Policy{} + productionPolicy := &policy.PolicyMeta{} _ = yaml.Unmarshal(policyDefinitions, productionPolicy) productionPolicy.Resource = productionPolicyResource productionPolicy.Alias = productionPolicyAlias @@ -58,5 +58,5 @@ func (*Router) Policies() []*policy.Policy { } } - return []*policy.Policy{res, productionPolicy} + return []*policy.PolicyMeta{res, productionPolicy} } diff --git a/pkg/microservice/aslan/core/project/handler/policy_definitions.go b/pkg/microservice/aslan/core/project/handler/policy_definitions.go index 377fb358..dfe7b02b 100644 --- a/pkg/microservice/aslan/core/project/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/project/handler/policy_definitions.go @@ -28,13 +28,13 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() []*policy.Policy { - res := &policy.Policy{} +func (*Router) Policies() []*policy.PolicyMeta { + res := &policy.PolicyMeta{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { // should not have happened here log.DPanic(err) } - return []*policy.Policy{res} + return []*policy.PolicyMeta{res} } diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index fc458295..aab3488b 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -52,7 +52,7 @@ const ( ) type policyGetter interface { - Policies() []*policy.Policy + Policies() []*policy.PolicyMeta } type Controller interface { @@ -81,7 +81,7 @@ func StartControllers(stopCh <-chan struct{}) { func registerPolicies() { policyClient := policy.NewWithRetry() - var policies []*policy.Policy + var policies []*policy.PolicyMeta for _, r := range []policyGetter{ new(workflowhandler.Router), new(environmenthandler.Router), @@ -93,7 +93,7 @@ func registerPolicies() { } for _, p := range policies { - err := policyClient.CreateOrUpdatePolicy(p) + err := policyClient.CreateOrUpdatePolicyRegistration(p) if err != nil { // should not have happened here log.DPanic(err) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go index 377fb358..dfe7b02b 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go @@ -28,13 +28,13 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() []*policy.Policy { - res := &policy.Policy{} +func (*Router) Policies() []*policy.PolicyMeta { + res := &policy.PolicyMeta{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { // should not have happened here log.DPanic(err) } - return []*policy.Policy{res} + return []*policy.PolicyMeta{res} } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go index 377fb358..dfe7b02b 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go @@ -28,13 +28,13 @@ import ( //go:embed policy.yaml var policyDefinitions []byte -func (*Router) Policies() []*policy.Policy { - res := &policy.Policy{} +func (*Router) Policies() []*policy.PolicyMeta { + res := &policy.PolicyMeta{} err := yaml.Unmarshal(policyDefinitions, res) if err != nil { // should not have happened here log.DPanic(err) } - return []*policy.Policy{res} + return []*policy.PolicyMeta{res} } diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go index 226c6105..11f30938 100644 --- a/pkg/microservice/policy/core/handler/policy.go +++ b/pkg/microservice/policy/core/handler/policy.go @@ -27,12 +27,15 @@ import ( type deletePoliciesArgs struct { Names []string `json:"names"` } +type createPoliciesArgs struct { + Policies []*service.Policy `json:"policies"` +} -func CreatePolicy(c *gin.Context) { +func CreatePolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := &service.Policy{} + args := &createPoliciesArgs{} if err := c.ShouldBindJSON(args); err != nil { ctx.Err = err return @@ -43,8 +46,7 @@ func CreatePolicy(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") return } - - ctx.Err = service.CreatePolicy(projectName, args, ctx.Logger) + ctx.Err = service.CreatePolicies(projectName, args.Policies, ctx.Logger) } func UpdatePolicy(c *gin.Context) { diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index fe3bd940..0866feb2 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -36,7 +36,7 @@ func (*Router) Inject(router *gin.RouterGroup) { policies := router.Group("policies") { - policies.POST("", CreatePolicy) + policies.POST("", CreatePolicies) policies.POST("/bulk-delete", DeletePolicies) policies.PATCH("/:name", UpdatePolicy) policies.PUT("/:name", UpdateOrCreatePolicy) @@ -99,7 +99,7 @@ func (*Router) Inject(router *gin.RouterGroup) { bundles.GET("/:name", DownloadBundle) } - policyRegistrations := router.Group("policies") + policyRegistrations := router.Group("policymetas") { policyRegistrations.PUT("/:resourceName", CreateOrUpdatePolicyRegistration) } diff --git a/pkg/microservice/policy/core/repository/mongodb/policy.go b/pkg/microservice/policy/core/repository/mongodb/policy.go index 2cf76559..9c26ae5e 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy.go @@ -141,6 +141,18 @@ func (c *PolicyColl) Create(obj *models.Policy) error { return err } +func (c *PolicyColl) BulkCreate(args []*models.Policy) error { + if len(args) == 0 { + return nil + } + var ois []interface{} + for _, obj := range args { + ois = append(ois, obj) + } + _, err := c.InsertMany(context.TODO(), ois) + return err +} + func (c *PolicyColl) Delete(name string, projectName string) error { query := bson.M{"name": name, "namespace": projectName} _, err := c.DeleteOne(context.TODO(), query) diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index fe9b762e..92dc930f 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -55,10 +55,30 @@ func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { MatchAttributes: r.MatchAttributes, }) } - return mongodb.NewPolicyColl().Create(obj) } +func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { + var objs []*models.Policy + for _, policy := range policies { + obj := &models.Policy{ + Name: policy.Name, + Namespace: ns, + } + + for _, r := range policy.Rules { + obj.Rules = append(obj.Rules, &models.Rule{ + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + MatchAttributes: r.MatchAttributes, + }) + } + objs = append(objs, obj) + } + return mongodb.NewPolicyColl().BulkCreate(objs) +} + func UpdatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index a511f330..dff99de2 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -20,124 +20,61 @@ import ( "fmt" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" ) type Policy struct { - Resource string `json:"resource"` - Alias string `json:"alias"` + Name string `json:"name"` Description string `json:"description"` Rules []*Rule `json:"rules"` } type Rule struct { - Action string `json:"action"` - Alias string `json:"alias"` - Description string `json:"description"` - Rules []*ActionRule `json:"rules"` + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + MatchAttributes []MatchAttribute `json:"match_attributes"` } -type ActionRule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` - ResourceType string `json:"resourceType,omitempty"` - IDRegex string `json:"idRegex,omitempty"` - MatchAttributes []*Attribute `json:"matchAttributes,omitempty"` -} - -type Attribute struct { +type MatchAttribute struct { Key string `json:"key"` Value string `json:"value"` } -type RoleBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Role string `json:"role"` - Public bool `json:"public"` -} - -func (c *Client) CreateOrUpdatePolicy(p *Policy) error { - url := fmt.Sprintf("/policies/%s", p.Resource) - - _, err := c.Put(url, httpclient.SetBody(p)) - - return err -} - -func (c *Client) ListRoleBindings(projectName string) ([]*RoleBinding, error) { - url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) - - res := make([]*RoleBinding, 0) - _, err := c.Get(url, httpclient.SetResult(&res)) - - return res, err -} - -func (c *Client) CreateOrUpdateRoleBinding(projectName string, roleBinding *RoleBinding) error { - url := fmt.Sprintf("/rolebindings/%s?projectName=%s", roleBinding.Name, projectName) - _, err := c.Put(url, httpclient.SetBody(roleBinding)) - return err -} - -func (c *Client) CreateOrUpdateSystemRoleBinding(roleBinding *RoleBinding) error { - url := fmt.Sprintf("/system-rolebindings/%s", roleBinding.Name) - _, err := c.Put(url, httpclient.SetBody(roleBinding)) - return err -} - -func (c *Client) DeleteRoleBinding(name string, projectName string) error { - url := fmt.Sprintf("/rolebindings/%s?projectName=%s", name, projectName) - _, err := c.Delete(url) - return err +type CreatePoliciesArgs struct { + Policies []*Policy `json:"policies"` } -type NameArgs struct { +type DeletePoliciesArgs struct { Names []string `json:"names"` } -func (c *Client) DeleteRoleBindings(names []string, projectName string) error { - url := fmt.Sprintf("/rolebindings/bulk-delete?projectName=%s", projectName) - nameArgs := &NameArgs{} - for _, v := range names { - nameArgs.Names = append(nameArgs.Names, v) - } - _, err := c.Post(url, httpclient.SetBody(nameArgs)) - return err -} +func (c *Client) CreatePolicies(request CreatePoliciesArgs) error { + url := "/policies" -func (c *Client) DeleteRoles(names []string, projectName string) error { - url := fmt.Sprintf("/roles/bulk-delete?projectName=%s", projectName) - nameArgs := &NameArgs{} - for _, v := range names { - nameArgs.Names = append(nameArgs.Names, v) + _, err := c.Post(url, httpclient.SetBody(request)) + if err != nil { + log.Errorf("Failed to add audit log, error: %s", err) + return err } - _, err := c.Post(url, httpclient.SetBody(nameArgs)) - return err -} -func (c *Client) CreateSystemRole(name string, role *Role) error { - url := fmt.Sprintf("/system-roles/%s", name) - _, err := c.Put(url, httpclient.SetBody(role)) - return err + return nil } -func (c *Client) CreatePublicRole(name string, role *Role) error { - url := fmt.Sprintf("/public-roles/%s", name) - _, err := c.Put(url, httpclient.SetBody(role)) - return err -} +func (c *Client) DeletePolicies(ns string, request DeletePoliciesArgs) error { + url := fmt.Sprintf("/policies?projectName=%s", ns) + + _, err := c.Delete(url, httpclient.SetBody(request)) + if err != nil { + log.Errorf("Failed to add audit log, error: %s", err) + return err + } -type Role struct { - Name string `json:"name"` - Rules []*struct { - Verbs []string `json:"verbs"` - Resources []string `json:"resources"` - Kind string `json:"kind"` - } `json:"rules"` + return nil } -func (c *Client) Healthz() error { - url := "/healthz" - _, err := c.Get(url) +func (c *Client) UpdatePolicy(ns string, policy *Policy) error { + url := fmt.Sprintf("/policies/%s?projectName=%s", policy.Name, ns) + _, err := c.Put(url, httpclient.SetBody(policy)) return err } diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go new file mode 100644 index 00000000..f8e65a44 --- /dev/null +++ b/pkg/shared/client/policy/role.go @@ -0,0 +1,143 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package policy + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type PolicyMeta struct { + Resource string `json:"resource"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*RuleMeta `json:"rules"` +} + +type RuleMeta struct { + Action string `json:"action"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*ActionRule `json:"rules"` +} + +type ActionRule struct { + Method string `json:"method"` + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType,omitempty"` + IDRegex string `json:"idRegex,omitempty"` + MatchAttributes []*Attribute `json:"matchAttributes,omitempty"` +} + +type Attribute struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type RoleBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Public bool `json:"public"` +} + +func (c *Client) CreateOrUpdatePolicyRegistration(p *PolicyMeta) error { + url := fmt.Sprintf("/policymetas/%s", p.Resource) + + _, err := c.Put(url, httpclient.SetBody(p)) + + return err +} + +func (c *Client) ListRoleBindings(projectName string) ([]*RoleBinding, error) { + url := fmt.Sprintf("/rolebindings?projectName=%s", projectName) + + res := make([]*RoleBinding, 0) + _, err := c.Get(url, httpclient.SetResult(&res)) + + return res, err +} + +func (c *Client) CreateOrUpdateRoleBinding(projectName string, roleBinding *RoleBinding) error { + url := fmt.Sprintf("/rolebindings/%s?projectName=%s", roleBinding.Name, projectName) + _, err := c.Put(url, httpclient.SetBody(roleBinding)) + return err +} + +func (c *Client) CreateOrUpdateSystemRoleBinding(roleBinding *RoleBinding) error { + url := fmt.Sprintf("/system-rolebindings/%s", roleBinding.Name) + _, err := c.Put(url, httpclient.SetBody(roleBinding)) + return err +} + +func (c *Client) DeleteRoleBinding(name string, projectName string) error { + url := fmt.Sprintf("/rolebindings/%s?projectName=%s", name, projectName) + _, err := c.Delete(url) + return err +} + +type NameArgs struct { + Names []string `json:"names"` +} + +func (c *Client) DeleteRoleBindings(names []string, projectName string) error { + url := fmt.Sprintf("/rolebindings/bulk-delete?projectName=%s", projectName) + nameArgs := &NameArgs{} + for _, v := range names { + nameArgs.Names = append(nameArgs.Names, v) + } + _, err := c.Post(url, httpclient.SetBody(nameArgs)) + return err +} + +func (c *Client) DeleteRoles(names []string, projectName string) error { + url := fmt.Sprintf("/roles/bulk-delete?projectName=%s", projectName) + nameArgs := &NameArgs{} + for _, v := range names { + nameArgs.Names = append(nameArgs.Names, v) + } + _, err := c.Post(url, httpclient.SetBody(nameArgs)) + return err +} + +func (c *Client) CreateSystemRole(name string, role *Role) error { + url := fmt.Sprintf("/system-roles/%s", name) + _, err := c.Put(url, httpclient.SetBody(role)) + return err +} + +func (c *Client) CreatePublicRole(name string, role *Role) error { + url := fmt.Sprintf("/public-roles/%s", name) + _, err := c.Put(url, httpclient.SetBody(role)) + return err +} + +type Role struct { + Name string `json:"name"` + Rules []*struct { + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + } `json:"rules"` +} + +func (c *Client) Healthz() error { + url := "/healthz" + _, err := c.Get(url) + return err +} -- Gitee From 555a98d05e0b0f9179c9dba733f5b127ad61883b Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 10:27:15 +0800 Subject: [PATCH 1345/2647] add compatibility for old projects without features Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/revision.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 33044c71..8724d7ec 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -127,7 +127,7 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common var allRenders []*commonmodels.RenderSet var newRender *commonmodels.RenderSet - if prodTmpl.ProductFeature != nil && prodTmpl.ProductFeature.DeployType == setting.K8SDeployType { + if prodTmpl.ProductFeature == nil || prodTmpl.ProductFeature.DeployType == setting.K8SDeployType { rendersetName := "" if product.Render != nil { rendersetName = product.Render.Name -- Gitee From 8f8a0818940ee87743dd003d9f2d39285abd310f Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 10:41:38 +0800 Subject: [PATCH 1346/2647] remove TODO tag Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/revision.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 8724d7ec..98da5434 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -34,7 +34,6 @@ func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) ( products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName}) // list services with max revision of project - // TODO refactor code allServiceTmpls, err := getServicesWithMaxRevision(productName) if err != nil { log.Errorf("ListAllRevisions error: %v", err) -- Gitee From 41df0601e0a5330417faad7a2b3b12dd24a954a9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 15:58:14 +0800 Subject: [PATCH 1347/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 10a8f915..5c365fcb 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -363,14 +363,12 @@ func getServicesWithMaxRevision(projectName string) ([]*commonmodels.Service, er // list services with max revision defined in project allServices, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: projectName}) if err != nil { - log.Errorf("failed to find services in project: %s, err: %s", projectName, err) - return nil, fmt.Errorf("failed to find services in project: %s", projectName) + return nil, errors.Wrapf(err, "failed to find services in project %s", projectName) } prodTmpl, err := templaterepo.NewProductColl().Find(projectName) if err != nil { - log.Error(fmt.Sprintf("[ProductTmpl.Find] %s error: %v", projectName, err)) - return nil, fmt.Errorf("failed to find project tempalte: %s", projectName) + return nil, errors.Wrapf(err, "failed to find project tempalte %s", projectName) } // list services with max revision of shared services @@ -394,8 +392,7 @@ func getServicesWithMaxRevision(projectName string) ([]*commonmodels.Service, er InServices: inService, }) if err != nil { - log.Errorf("ListSharedServiceRevisions error: %s", err) - return nil, fmt.Errorf("failed to find shared service templates, projectName: %s, err: %s", sourceProject, err) + return nil, errors.Wrapf(err, "failed to find shared service templates, projectName: %s", sourceProject) } allServices = append(allServices, sharedServices...) } -- Gitee From 35db0ee04722aeff437786b1403e04841717688e Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 20 Jan 2022 16:20:46 +0800 Subject: [PATCH 1348/2647] permission Signed-off-by: mouuii --- .../policy/core/handler/permission.go | 21 ++++++ .../policy/core/handler/router.go | 2 + .../core/repository/mongodb/role_binding.go | 36 ++++++++++ .../policy/core/service/permission.go | 72 ++++++++++++++----- 4 files changed, 114 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index ed4af8a0..3733c8fa 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -19,6 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" "github.com/koderover/zadig/pkg/microservice/policy/core/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -36,3 +37,23 @@ func GetUserPermission(c *gin.Context) { ctx.Resp, ctx.Err = service.GetPermission(projectName, c.Param("uid"), ctx.Logger) } + +type GetUserResourcesPermissionReq struct { + ProjectName string `json:"project_name" form:"project_name"` + Uid string `json:"uid" form:"uid"` + Resources []string `json:"resources" form:"resources"` + ResourceType config.ResourceType `json:"resource_type" form:"resource_type"` +} + +func GetUserResourcesPermission(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := new(GetUserResourcesPermissionReq) + + if err := c.ShouldBindQuery(req); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + service.GetResourcesPermission(req.Uid, req.ProjectName, req.ResourceType, req.Resources, ctx.Logger) +} diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index fe3bd940..77f5afe5 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -115,6 +115,8 @@ func (*Router) Inject(router *gin.RouterGroup) { } policyUserPermission := router.Group("permission") { + policyUserPermission.GET("resources", GetUserResourcePermission) policyUserPermission.GET("/:uid", GetUserPermission) + } } diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index ecb2e4d0..16ec80f8 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -203,3 +203,39 @@ func (c *RoleBindingColl) UpdateOrCreate(obj *models.RoleBinding) error { return err } + +type RoleBindingMeta struct { + Uid string `json:"uid"` + Namespace string `json:"namespace"` +} + +type ListRoleBindingsOpt struct { + RoleBindingMetas []RoleBindingMeta +} + +func (c *RoleBindingColl) ListByRoleBindingOpt(opt ListRoleBindingsOpt) ([]*models.RoleBinding, error) { + var res []*models.RoleBinding + + if len(opt.RoleBindingMetas) == 0 { + return nil, nil + } + condition := bson.A{} + for _, meta := range opt.RoleBindingMetas { + condition = append(condition, bson.M{ + "namespace": meta.Namespace, + "subjects.uid": meta.Uid, + }) + } + filter := bson.D{{"$or", condition}} + cursor, err := c.Collection.Find(context.TODO(), filter) + if err == mongo.ErrNoDocuments { + return nil, nil + } + if err != nil { + return nil, err + } + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + return res, nil +} diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 1fe63eca..a49d82f1 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -27,28 +27,25 @@ import ( // GetPermission user's permission for frontend func GetPermission(ns, uid string, log *zap.SugaredLogger) (map[string][]string, error) { roles := []*models.Role{} - rolebindingsReadOnly, err := mongodb.NewRoleBindingColl().ListBy(ns, "*") - if err != nil { - log.Errorf("list readonly RoleBinding err:%s,ns:%s,uid:*", err, ns) - return nil, err + var metas []mongodb.RoleBindingMeta + roleBindingReadOnlyMeta := mongodb.RoleBindingMeta{ + Uid: "*", + Namespace: ns, } - - rolebindingsAdmin, err := mongodb.NewRoleBindingColl().ListBy("*", uid) - if err != nil { - log.Errorf("list admin RoleBinding err:%s,ns:*,uid:%s", err) - return nil, err + roleBindingsAdminMeta := mongodb.RoleBindingMeta{ + Uid: uid, + Namespace: "*", } - - // 1.2 get normal rolebindings - rolebindingsNormal, err := mongodb.NewRoleBindingColl().ListBy(ns, uid) + roleBindingCommonMeta := mongodb.RoleBindingMeta{ + Uid: uid, + Namespace: ns, + } + metas = append(metas, roleBindingReadOnlyMeta, roleBindingsAdminMeta, roleBindingCommonMeta) + roleBindings, err := mongodb.NewRoleBindingColl().ListByRoleBindingOpt(mongodb.ListRoleBindingsOpt{RoleBindingMetas: metas}) if err != nil { - log.Errorf("list normal RoleBindings err:%s,ns:%s,uid:%s", err, ns, uid) return nil, err } - - rolebindings := append(rolebindingsReadOnly, rolebindingsNormal...) - rolebindings = append(rolebindings, rolebindingsAdmin...) - for _, v := range rolebindings { + for _, v := range roleBindings { tmpRoles, err := mongodb.NewRoleColl().ListBySpaceAndName(v.RoleRef.Namespace, v.RoleRef.Name) if err != nil { continue @@ -74,3 +71,44 @@ func GetPermission(ns, uid string, log *zap.SugaredLogger) (map[string][]string, } return rolesResp, nil } + +func GetResourcesPermission(uid string, projectName string, resourceType string, resources []string, log *zap.SugaredLogger) (map[string][]string, error) { + + // 1. get all policyBindings + policyBindings, err := ListPolicyBindings(uid, projectName, log) + if err != nil { + return nil, err + } + var policies []*Policy + for _, v := range policyBindings { + policy, err := GetPolicy(projectName, v.Name, log) + if err != nil { + continue + } + policies = append(policies, policy) + } + + queryResourceSet := sets.NewString(resources...) + resourceM := make(map[string]sets.String) + for _, policy := range policies { + for _, rule := range policy.Rules { + if rule.Resources[0] == resourceType { + for _, resource := range rule.RelatedResources { + if queryResourceSet.Has(resource) { + if v, ok := resourceM[resource]; ok { + resourceM[resource] = v.Insert(resource) + } else { + resourceM[resource] = sets.NewString(resource) + } + } + } + } + + } + } + resourceRes := make(map[string][]string) + for k, v := range resourceM { + resourceRes[k] = v.List() + } + return resourceRes, nil +} -- Gitee From 16f0f0defdd2fbfdf77047d295cc42d8cf0cea09 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 16:27:45 +0800 Subject: [PATCH 1349/2647] update log Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/testing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/testing.go b/pkg/microservice/aslan/core/common/service/testing.go index bed18705..bdc4f2bc 100644 --- a/pkg/microservice/aslan/core/common/service/testing.go +++ b/pkg/microservice/aslan/core/common/service/testing.go @@ -57,7 +57,7 @@ func Delete(name, productName string, log *zap.SugaredLogger) error { // find test data to delete webhook testModule, err := commonrepo.NewTestingColl().Find(name, productName) if err != nil { - log.Errorf("Workflow.Find error: %v", err) + log.Errorf("failed to find test module: %s", err) return e.ErrDeleteTestModule.AddDesc(err.Error()) } -- Gitee From a0a14bc9a5d43dbd5da4af1bd6591bdec2bf37e5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 16:30:41 +0800 Subject: [PATCH 1350/2647] update log Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5c365fcb..95da449b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -368,7 +368,7 @@ func getServicesWithMaxRevision(projectName string) ([]*commonmodels.Service, er prodTmpl, err := templaterepo.NewProductColl().Find(projectName) if err != nil { - return nil, errors.Wrapf(err, "failed to find project tempalte %s", projectName) + return nil, errors.Wrapf(err, "failed to find project: %s", projectName) } // list services with max revision of shared services -- Gitee From 21624dc84306f353925236d97e98d023a1978416 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 16:41:32 +0800 Subject: [PATCH 1351/2647] remove useless log Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/testing.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/testing.go b/pkg/microservice/aslan/core/common/service/testing.go index bdc4f2bc..98dfe7d1 100644 --- a/pkg/microservice/aslan/core/common/service/testing.go +++ b/pkg/microservice/aslan/core/common/service/testing.go @@ -57,7 +57,6 @@ func Delete(name, productName string, log *zap.SugaredLogger) error { // find test data to delete webhook testModule, err := commonrepo.NewTestingColl().Find(name, productName) if err != nil { - log.Errorf("failed to find test module: %s", err) return e.ErrDeleteTestModule.AddDesc(err.Error()) } -- Gitee From 88c1cd32600bf160fad99770244bb8fbfccdd707 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 20 Jan 2022 18:33:39 +0800 Subject: [PATCH 1352/2647] permission Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/config/consts.go | 4 ++-- pkg/microservice/policy/core/handler/permission.go | 11 +++++------ pkg/microservice/policy/core/handler/router.go | 4 ++-- pkg/microservice/policy/core/service/permission.go | 13 ++++++------- pkg/shared/client/label/client.go | 2 +- pkg/shared/client/policy/policy.go | 2 +- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/label/config/consts.go b/pkg/microservice/aslan/core/label/config/consts.go index 6910afe2..8bb25327 100644 --- a/pkg/microservice/aslan/core/label/config/consts.go +++ b/pkg/microservice/aslan/core/label/config/consts.go @@ -19,6 +19,6 @@ package config type ResourceType string const ( - ResourceTypeWorkflow ResourceType = "workflow" - ResourceTypeProduct ResourceType = "product" + ResourceTypeWorkflow ResourceType = "Workflow" + ResourceTypeProduct ResourceType = "Environment" ) diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index 3733c8fa..41f3741b 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -19,7 +19,6 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" "github.com/koderover/zadig/pkg/microservice/policy/core/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -39,10 +38,10 @@ func GetUserPermission(c *gin.Context) { } type GetUserResourcesPermissionReq struct { - ProjectName string `json:"project_name" form:"project_name"` - Uid string `json:"uid" form:"uid"` - Resources []string `json:"resources" form:"resources"` - ResourceType config.ResourceType `json:"resource_type" form:"resource_type"` + ProjectName string `json:"project_name" form:"project_name"` + Uid string `json:"uid" form:"uid"` + Resources []string `json:"resources" form:"resources"` + ResourceType string `json:"resource_type" form:"resource_type"` } func GetUserResourcesPermission(c *gin.Context) { @@ -55,5 +54,5 @@ func GetUserResourcesPermission(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - service.GetResourcesPermission(req.Uid, req.ProjectName, req.ResourceType, req.Resources, ctx.Logger) + ctx.Resp, ctx.Err = service.GetResourcesPermission(req.Uid, req.ProjectName, req.ResourceType, req.Resources, ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 77f5afe5..90e0ce07 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -99,7 +99,7 @@ func (*Router) Inject(router *gin.RouterGroup) { bundles.GET("/:name", DownloadBundle) } - policyRegistrations := router.Group("policies") + policyRegistrations := router.Group("policymetas") { policyRegistrations.PUT("/:resourceName", CreateOrUpdatePolicyRegistration) } @@ -115,7 +115,7 @@ func (*Router) Inject(router *gin.RouterGroup) { } policyUserPermission := router.Group("permission") { - policyUserPermission.GET("resources", GetUserResourcePermission) + policyUserPermission.GET("resources", GetUserResourcesPermission) policyUserPermission.GET("/:uid", GetUserPermission) } diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index a49d82f1..3a7ae0de 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -75,13 +75,13 @@ func GetPermission(ns, uid string, log *zap.SugaredLogger) (map[string][]string, func GetResourcesPermission(uid string, projectName string, resourceType string, resources []string, log *zap.SugaredLogger) (map[string][]string, error) { // 1. get all policyBindings - policyBindings, err := ListPolicyBindings(uid, projectName, log) + policyBindings, err := ListPolicyBindings(projectName, uid, log) if err != nil { return nil, err } var policies []*Policy for _, v := range policyBindings { - policy, err := GetPolicy(projectName, v.Name, log) + policy, err := GetPolicy(projectName, v.Policy, log) if err != nil { continue } @@ -90,16 +90,15 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, queryResourceSet := sets.NewString(resources...) resourceM := make(map[string]sets.String) + for _, v := range resources { + resourceM[v] = sets.NewString() + } for _, policy := range policies { for _, rule := range policy.Rules { if rule.Resources[0] == resourceType { for _, resource := range rule.RelatedResources { if queryResourceSet.Has(resource) { - if v, ok := resourceM[resource]; ok { - resourceM[resource] = v.Insert(resource) - } else { - resourceM[resource] = sets.NewString(resource) - } + resourceM[resource] = resourceM[resource].Insert(rule.Verbs...) } } } diff --git a/pkg/shared/client/label/client.go b/pkg/shared/client/label/client.go index 3b40ffbe..e350ed68 100644 --- a/pkg/shared/client/label/client.go +++ b/pkg/shared/client/label/client.go @@ -28,7 +28,7 @@ type Client struct { } func New() *Client { - host := config.ConfigServiceAddress() + host := config.AslanServiceAddress() c := httpclient.New( httpclient.SetHostURL(host + "/api"), ) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index a511f330..56a91402 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -57,7 +57,7 @@ type RoleBinding struct { } func (c *Client) CreateOrUpdatePolicy(p *Policy) error { - url := fmt.Sprintf("/policies/%s", p.Resource) + url := fmt.Sprintf("/policymetas/%s", p.Resource) _, err := c.Put(url, httpclient.SetBody(p)) -- Gitee From f6ae60060e7694815d1d65af90f76b5d692a07d3 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 20 Jan 2022 18:59:27 +0800 Subject: [PATCH 1353/2647] Workflow history task lists support filtering Signed-off-by: ddh27 --- .../core/common/repository/models/workflow.go | 3 +- .../core/common/repository/mongodb/task.go | 38 +++++- .../core/workflow/handler/pipeline_task.go | 2 +- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_task.go | 15 ++- .../service/webhook/codehub_workflow_task.go | 3 + .../service/webhook/gerrit_workflow_task.go | 6 +- .../service/webhook/github_workflow_task.go | 3 + .../service/webhook/gitlab_workflow_task.go | 4 +- .../service/workflow/pipeline_task.go | 124 +++++++++++++++--- 10 files changed, 174 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3ed775c9..aebf841d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -77,6 +77,7 @@ type MainHookRepo struct { RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` Branch string `bson:"branch" json:"branch"` + Committer string `bson:"committer" json:"committer"` MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` CodehostID int `bson:"codehost_id" json:"codehost_id"` Events []config.HookEventType `bson:"events" json:"events"` @@ -169,7 +170,7 @@ type WorkflowTaskArgs struct { CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` - + Committer string `bson:"committer" json:"committer"` //github check run HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` // 请求模式,openAPI表示外部客户调用 diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index 333bb5fb..f3365ca8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -36,6 +36,7 @@ type ListTaskOption struct { PipelineName string PipelineNames []string Status config.Status + Statuss []string Team string TeamID int Limit int @@ -44,7 +45,9 @@ type ListTaskOption struct { Type config.PipelineType CreateTime int64 TaskCreator string + TaskCreators []string Source string + Committers []string MergeRequestID string NeedTriggerBy bool NeedAllData bool @@ -265,15 +268,22 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) if option.PipelineName != "" { query["pipeline_name"] = option.PipelineName } - if option.Status != "" { + if len(option.Statuss) > 0 { + query["status"] = bson.M{"$in": option.Statuss} + } else if option.Status != "" { query["status"] = option.Status } if option.Team != "" { query["team"] = option.Team } - if option.TaskCreator != "" { + if len(option.TaskCreators) > 0 { + query["task_creator"] = bson.M{"$in": option.TaskCreators} + } else if option.TaskCreator != "" { query["task_creator"] = option.TaskCreator } + if len(option.Committers) > 0 { + query["workflow_args.committer"] = bson.M{"$in": option.Committers} + } if option.Source != "" { query["trigger_by.source"] = option.Source } @@ -390,7 +400,10 @@ func (c *TaskColl) Create(args *task.Task) error { type CountTaskOption struct { PipelineNames []string + Statuss []string Status config.TaskStatus + TaskCreators []string + Committers []string Type config.PipelineType } @@ -400,10 +413,18 @@ func (c *TaskColl) Count(option *CountTaskOption) (ret int, err error) { } query := bson.M{} + if len(option.TaskCreators) > 0 { + query["task_creator"] = bson.M{"$in": option.TaskCreators} + } + if len(option.Committers) > 0 { + query["workflow_args.committer"] = bson.M{"$in": option.Committers} + } if option.PipelineNames != nil { query["pipeline_name"] = bson.M{"$in": option.PipelineNames} } - if option.Status != "" { + if len(option.Statuss) > 0 { + query["status"] = bson.M{"$in": option.Statuss} + } else if option.Status != "" { query["status"] = option.Status } if option.Type != "" { @@ -591,3 +612,14 @@ func (c *TaskColl) ArchiveHistoryPipelineTask(pipelineName string, taskType conf return err } + +func (c *TaskColl) DistinctFeildsPipelineTask(fieldName, projectName, pipelineName string, typeString config.PipelineType, isNeedAllData bool) ([]interface{}, error) { + + query := bson.M{"product_name": projectName, "pipeline_name": pipelineName, "type": typeString, "is_deleted": false} + if isNeedAllData { + createTime := timeutil.BeginningOfDay().AddDate(0, -3, 0).Unix() + query["create_time"] = bson.M{"$gt": createTime} + } + res, err := c.Distinct(context.TODO(), fieldName, query) + return res, err +} diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go index 476b8412..37b7b2c0 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go @@ -106,7 +106,7 @@ func ListPipelineTasksResult(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid start at number") return } - ctx.Resp, ctx.Err = workflow.ListPipelineTasksV2Result(c.Param("name"), config.SingleType, maxResult, startAt, ctx.Logger) + ctx.Resp, ctx.Err = workflow.ListPipelineTasksV2Result(c.Param("name"), config.SingleType, "", []string{}, maxResult, startAt, ctx.Logger) } func GetPipelineTask(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 02aee1b6..0443c9b5 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -125,6 +125,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowtask.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTask) workflowtask.PUT("", gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) workflowtask.GET("/max/:max/start/:start/pipelines/:name", ListWorkflowTasksResult) + workflowtask.GET("filters/pipelines/:name", GetFiltersPipeline) workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) workflowtask.POST("/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTask) workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index eeb4b866..97f4d597 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -21,6 +21,7 @@ import ( "encoding/json" "io/ioutil" "strconv" + "strings" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" @@ -132,7 +133,19 @@ func ListWorkflowTasksResult(c *gin.Context) { if workflowType == string(config.TestType) { workflowTypeString = config.TestType } - ctx.Resp, ctx.Err = workflow.ListPipelineTasksV2Result(c.Param("name"), workflowTypeString, maxResult, startAt, ctx.Logger) + filters := c.Query("filters") + filtersList := strings.Split(filters, ",") + ctx.Resp, ctx.Err = workflow.ListPipelineTasksV2Result(c.Param("name"), workflowTypeString, c.Query("queryType"), filtersList, maxResult, startAt, ctx.Logger) +} + +func GetFiltersPipeline(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + if c.Query("workflowType") != string(config.WorkflowType) { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid workflowType") + return + } + ctx.Resp, ctx.Err = workflow.GetFiltersPipelineTaskV2(c.Query("projectName"), c.Param("name"), c.Query("queryType"), config.WorkflowType, ctx.Logger) } func GetWorkflowTask(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go index 10f08d79..c88a216f 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go @@ -43,6 +43,7 @@ func (cmem *codehubMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { if ev.ObjectAttributes.State == "opened" { + hookRepo.Committer = ev.User.Username return true, nil } } @@ -79,6 +80,7 @@ func (cpem *codehubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ev := cpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { + hookRepo.Committer = ev.UserUsername return true, nil } } @@ -198,6 +200,7 @@ func TriggerWorkflowByCodehubEvent(event interface{}, baseURI, requestID string, args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner args.RepoName = item.MainRepo.RepoName + args.Committer = item.MainRepo.Committer // 3. create task with args if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { log.Errorf("failed to create workflow task when receive push event %v due to %v ", event, err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index 6ed9cd1a..dd34a412 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -179,6 +179,7 @@ func (gruem *gerritChangeMergedEventMatcher) Match(hookRepo *commonmodels.MainHo existEventNames = append(existEventNames, string(eventName)) } if sets.NewString(existEventNames...).Has(event.Type) { + hookRepo.Committer = event.Submitter.Username return true, nil } } @@ -210,7 +211,7 @@ type gerritPatchsetCreatedEventMatcher struct { Event *patchsetCreatedEvent } -func (gpcem *gerritPatchsetCreatedEventMatcher) Match(*commonmodels.MainHookRepo) (bool, error) { +func (gpcem *gerritPatchsetCreatedEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { event := gpcem.Event if event == nil { return false, fmt.Errorf("event doesn't match") @@ -222,6 +223,7 @@ func (gpcem *gerritPatchsetCreatedEventMatcher) Match(*commonmodels.MainHookRepo existEventNames = append(existEventNames, string(eventName)) } if sets.NewString(existEventNames...).Has(event.Type) { + hookRepo.Committer = event.PatchSet.Author.Username return true, nil } } @@ -380,7 +382,7 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base workflowArgs.CodehostID = item.MainRepo.CodehostID workflowArgs.RepoOwner = item.MainRepo.RepoOwner workflowArgs.RepoName = item.MainRepo.RepoName - + workflowArgs.Committer = item.MainRepo.Committer if resp, err := workflowservice.CreateWorkflowTask(workflowArgs, setting.WebhookTaskCreator, log); err != nil { log.Errorf("TriggerWorkflowByGerritEvent failed to create workflow task when receive push event %v due to %v ", event, err) errorList = multierror.Append(errorList, err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 5d408655..5280774e 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -70,6 +70,7 @@ func (gpem *githubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( } } hookRepo.Branch = getBranchFromRef(*ev.Ref) + hookRepo.Committer = *ev.Pusher.Login var changedFiles []string for _, commit := range ev.Commits { changedFiles = append(changedFiles, commit.Added...) @@ -133,6 +134,7 @@ func (gmem *githubMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) } } hookRepo.Branch = *ev.PullRequest.Base.Ref + hookRepo.Committer = *ev.PullRequest.User.Login if *ev.PullRequest.State == "open" { var changedFiles []string changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) @@ -321,6 +323,7 @@ func TriggerWorkflowByGithubEvent(event interface{}, baseURI, deliveryID, reques args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner args.RepoName = item.MainRepo.RepoName + args.Committer = item.MainRepo.Committer args.HookPayload = hookPayload // 3. create task with args diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 2a0dbb94..2935076b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -88,6 +88,7 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) } } hookRepo.Branch = ev.ObjectAttributes.TargetBranch + hookRepo.Committer = ev.User.Username if ev.ObjectAttributes.State == "opened" { var changedFiles []string changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) @@ -204,7 +205,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( } hookRepo.Branch = getBranchFromRef(ev.Ref) - + hookRepo.Committer = ev.UserUsername var changedFiles []string detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) if err != nil { @@ -551,6 +552,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner args.RepoName = item.MainRepo.RepoName + args.Committer = item.MainRepo.Committer // 3. create task with args if item.WorkflowArgs.BaseNamespace == "" { if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 3ad5aeca..47532941 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -293,20 +293,41 @@ type TaskResult struct { } // ListPipelineTasksV2Result 工作流任务分页信息 -func ListPipelineTasksV2Result(name string, typeString config.PipelineType, maxResult, startAt int, log *zap.SugaredLogger) (*TaskResult, error) { +func ListPipelineTasksV2Result(name string, typeString config.PipelineType, queryType string, filters []string, maxResult, startAt int, log *zap.SugaredLogger) (*TaskResult, error) { ret := &TaskResult{MaxResult: maxResult, StartAt: startAt} var err error - ret.Data, err = commonrepo.NewTaskColl().List(&commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Detail: true, Type: typeString}) + var listTaskOpt *commonrepo.ListTaskOption + var countTaskOpt *commonrepo.CountTaskOption + serviceNameFiltersMap := make(map[string]interface{}, len(filters)) + switch queryType { + case "creator": + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, TaskCreators: filters, Detail: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, TaskCreators: filters, Type: typeString} + case "committer": + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Committers: filters, Detail: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Committers: filters, Type: typeString} + case "serviceName": + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Detail: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} + for _, svc := range filters { + serviceNameFiltersMap[svc] = nil + } + + case "taskStatus": + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuss: filters, Detail: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Statuss: filters, Type: typeString} + default: + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Detail: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} + } + ret.Data, err = commonrepo.NewTaskColl().List(listTaskOpt) if err != nil { - log.Errorf("PipelineTaskV2.List error: %v", err) + log.Errorf("PipelineTaskV2.List:%s error: %s", listTaskOpt, err) return ret, e.ErrListTasks } - - // 获取任务的服务列表 var buildStage, deployStage *commonmodels.Stage for _, t := range ret.Data { t.BuildServices = []string{} - for _, stage := range t.Stages { if stage.TaskType == config.TaskBuild { buildStage = stage @@ -315,27 +336,45 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, maxR deployStage = stage } } - - // 有构建返回构建的服务,没构建返回部署的服务 if buildStage != nil { for serviceName := range buildStage.SubTasks { + if queryType == "serviceName" { + _, ok := serviceNameFiltersMap[serviceName] + if !ok { + continue + } + } t.BuildServices = append(t.BuildServices, serviceName) } } else if deployStage != nil { for serviceName := range deployStage.SubTasks { + if queryType == "serviceName" { + _, ok := serviceNameFiltersMap[serviceName] + if !ok { + continue + } + } t.BuildServices = append(t.BuildServices, serviceName) } } - - // 清理,下次循环再使用 - buildStage = nil - deployStage = nil + buildStage, deployStage = nil, nil } - - pipelineList := []string{name} - ret.Total, err = commonrepo.NewTaskColl().Count(&commonrepo.CountTaskOption{PipelineNames: pipelineList, Type: typeString}) + if queryType == "serviceName" { + ret.Total = len(ret.Data) + if startAt > ret.Total { + ret.Data = []*commonrepo.TaskPreview{} + return ret, nil + } + if startAt+maxResult <= ret.Total { + ret.Data = ret.Data[startAt : startAt+maxResult] + } else { + ret.Data = ret.Data[startAt:ret.Total] + } + return ret, nil + } + ret.Total, err = commonrepo.NewTaskColl().Count(countTaskOpt) if err != nil { - log.Errorf("PipelineTaskV2.List error: %v", err) + log.Errorf("PipelineTaskV2.List Count error: %v", err) return ret, e.ErrCountTasks } return ret, nil @@ -352,6 +391,59 @@ func GetPipelineTaskV2(taskID int64, pipelineName string, typeString config.Pipe return resp, nil } +func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeString config.PipelineType, log *zap.SugaredLogger) ([]interface{}, error) { + + resp := []interface{}{} + var err error + fieldName := "" + switch querytype { + case "creator": + fieldName = "task_creator" + resp, err = commonrepo.NewTaskColl().DistinctFeildsPipelineTask(fieldName, projectName, pipelineName, typeString, false) + if err != nil { + log.Errorf("[%s] DistinctFeildsPipelineTask fieldName: %s error: %s", fieldName, pipelineName, err) + return resp, e.ErrGetTask + } + case "committer": + fieldName = "committer" + resp, err = commonrepo.NewTaskColl().DistinctFeildsPipelineTask(fieldName, projectName, pipelineName, typeString, false) + if err != nil { + log.Errorf("[%s] DistinctFeildsPipelineTask fieldName: %s error: %s", fieldName, pipelineName, err) + return resp, e.ErrGetTask + } + case "serviceName": + data, err := commonrepo.NewTaskColl().List(&commonrepo.ListTaskOption{PipelineName: pipelineName, Detail: true, Type: typeString}) + if err != nil { + log.Errorf("PipelineTaskV2.List error: %s", err) + return resp, e.ErrListTasks + } + var buildStage, deployStage *commonmodels.Stage + for _, t := range data { + for _, stage := range t.Stages { + if stage.TaskType == config.TaskBuild { + buildStage = stage + } + if stage.TaskType == config.TaskDeploy { + deployStage = stage + } + } + if buildStage != nil { + for serviceName := range buildStage.SubTasks { + resp = append(resp, serviceName) + } + } else if deployStage != nil { + for serviceName := range deployStage.SubTasks { + resp = append(resp, serviceName) + } + } + buildStage, deployStage = nil, nil + } + default: + return resp, fmt.Errorf("queryType parameter is invalid") + } + return resp, nil +} + func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, typeString config.PipelineType, log *zap.SugaredLogger) error { t, err := commonrepo.NewTaskColl().Find(taskID, pipelineName, typeString) if err != nil { -- Gitee From 77fef99fc536d31e43b447f5b850f372135bbaf7 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 19:42:08 +0800 Subject: [PATCH 1354/2647] fix service webhook be deleted unexpected bug Signed-off-by: allenshen --- .../core/common/repository/mongodb/service.go | 59 +++++++++++++++++ .../core/common/service/webhook/controller.go | 63 ++++++++++++++++--- 2 files changed, 113 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 8cd0171a..0ceaed80 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -60,6 +60,10 @@ type ServiceListOption struct { NotInServices []*templatemodels.ServiceInfo } +type ServiceAggregateResult struct { + ServiceID primitive.ObjectID `bson:"service_id"` +} + type ServiceColl struct { *mongo.Collection @@ -429,6 +433,61 @@ func (c *ServiceColl) ListAllRevisions() ([]*models.Service, error) { return resp, err } +func (c *ServiceColl) ListMaxRevisionsByGroup(serviceName, serviceType string) ([]*models.Service, error) { + + pipeline := []bson.M{ + { + "$match": bson.M{ + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + "service_name": serviceName, + "type": serviceType, + }, + }, + { + "$group": bson.M{ + "_id": bson.M{ + "product_name": "$product_name", + "service_name": "$service_name", + }, + "revision": bson.M{"$max": "$revision"}, + "service_id": bson.M{"$last": "$_id"}, + }, + }, + } + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + + res := make([]*ServiceAggregateResult, 0) + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + + var ids []primitive.ObjectID + for _, service := range res { + ids = append(ids, service.ServiceID) + } + + if len(ids) == 0 { + return nil, nil + } + + var resp []*models.Service + query := bson.M{"_id": bson.M{"$in": ids}} + cursor, err = c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + func (c *ServiceColl) ListMaxRevisions(opt *ServiceListOption) ([]*models.Service, error) { preMatch := bson.M{"status": bson.M{"$ne": setting.ProductStatusDeleting}} postMatch := bson.M{} diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index 0297b64c..bf414e38 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -18,6 +18,7 @@ package webhook import ( "fmt" + "strings" "sync" "time" @@ -29,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" + codehostdb "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" ) @@ -114,6 +116,38 @@ func (c *controller) processNextWorkItem() bool { return true } +func ensureSafeDelete(ref, repoName, repoOwner, repoAddress string) (bool, error) { + // only handle webhooks created by service + if !strings.HasPrefix(ref, ServicePrefix) || !strings.HasSuffix(ref, "trigger") { + return true, nil + } + + serviceName := strings.TrimPrefix(ref, ServicePrefix) + serviceName = strings.TrimSuffix(serviceName, "-trigger") + + // find existing service with same name + services, err := mongodb.NewServiceColl().ListMaxRevisionsByGroup(serviceName, setting.K8SDeployType) + if err != nil { + return false, err + } + + // if the webhook reference is used by other + for _, service := range services { + if service.RepoName != repoName || service.RepoOwner != repoOwner { + continue + } + codeHostInfo, err := codehostdb.NewCodehostColl().GetCodeHostByID(service.CodehostID) + if err == nil { + if codeHostInfo.Address != repoAddress { + continue + } else { + return false, nil + } + } + } + return true, nil +} + func removeWebhook(t *task, logger *zap.Logger) { coll := mongodb.NewWebHookColl() var cl hookCreateDeleter @@ -146,27 +180,38 @@ func removeWebhook(t *task, logger *zap.Logger) { logger = logger.With(zap.String("hookID", webhook.HookID)) logger.Info("Removing webhook") - updated, err := coll.RemoveReference(t.owner, t.repo, t.address, t.ref) + + //ensure safe remove, same reference may be used by multiple services + safe, err := ensureSafeDelete(t.ref, t.repo, t.owner, t.address) if err != nil { t.err = err t.doneCh <- struct{}{} return } - if len(updated.References) == 0 { - logger.Info("Deleting webhook") - err = cl.DeleteWebHook(t.owner, t.repo, webhook.HookID) + if safe { + updated, err := coll.RemoveReference(t.owner, t.repo, t.address, t.ref) if err != nil { - logger.Error("Failed to delete webhook", zap.Error(err)) t.err = err t.doneCh <- struct{}{} return } - err = coll.Delete(t.owner, t.repo, t.address) - if err != nil { - logger.Error("Failed to delete webhook record in db", zap.Error(err)) - t.err = err + if len(updated.References) == 0 { + logger.Info("Deleting webhook") + err = cl.DeleteWebHook(t.owner, t.repo, webhook.HookID) + if err != nil { + logger.Error("Failed to delete webhook", zap.Error(err)) + t.err = err + t.doneCh <- struct{}{} + return + } + + err = coll.Delete(t.owner, t.repo, t.address) + if err != nil { + logger.Error("Failed to delete webhook record in db", zap.Error(err)) + t.err = err + } } } -- Gitee From a77b621f03338ebeca449581bb59d496beb662cc Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 20:14:47 +0800 Subject: [PATCH 1355/2647] optimize func name Signed-off-by: allenshen --- .../aslan/core/common/repository/mongodb/service.go | 2 +- .../aslan/core/common/service/webhook/controller.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 0ceaed80..cd1bf214 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -433,7 +433,7 @@ func (c *ServiceColl) ListAllRevisions() ([]*models.Service, error) { return resp, err } -func (c *ServiceColl) ListMaxRevisionsByGroup(serviceName, serviceType string) ([]*models.Service, error) { +func (c *ServiceColl) ListMaxRevisionsByProject(serviceName, serviceType string) ([]*models.Service, error) { pipeline := []bson.M{ { diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index bf414e38..49f32a5e 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -126,12 +126,12 @@ func ensureSafeDelete(ref, repoName, repoOwner, repoAddress string) (bool, error serviceName = strings.TrimSuffix(serviceName, "-trigger") // find existing service with same name - services, err := mongodb.NewServiceColl().ListMaxRevisionsByGroup(serviceName, setting.K8SDeployType) + services, err := mongodb.NewServiceColl().ListMaxRevisionsByProject(serviceName, setting.K8SDeployType) if err != nil { return false, err } - // if the webhook reference is used by other + // check if the webhook reference points to other existing service for _, service := range services { if service.RepoName != repoName || service.RepoOwner != repoOwner { continue -- Gitee From fb10313e8f5c10120a674d7bd59a3f1ebf710885 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 20 Jan 2022 20:35:05 +0800 Subject: [PATCH 1356/2647] webhook support tag event Signed-off-by: liu deyi --- pkg/microservice/aslan/config/consts.go | 1 + .../service/webhook/github_testing_task.go | 48 +++++++- .../service/webhook/github_workflow_task.go | 53 ++++++++ .../core/workflow/service/webhook/gitlab.go | 26 +++- .../service/webhook/gitlab_testing_task.go | 52 ++++++++ .../service/webhook/gitlab_workflow_task.go | 114 ++++++++++++++++++ .../core/workflow/service/webhook/utils.go | 4 +- pkg/tool/git/hook.go | 1 + 8 files changed, 293 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 16cf47d6..d285e50f 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -188,6 +188,7 @@ type HookEventType string const ( HookEventPush = HookEventType("push") HookEventPr = HookEventType("pull_request") + HookEventTag = HookEventType("tag") HookEventUpdated = HookEventType("ref-updated") ) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go index 756781cc..ca902589 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go @@ -64,7 +64,7 @@ func TriggerTestByGithubEvent(event interface{}, requestID string, log *zap.Suga args := matcher.UpdateTaskArgs(item.TestArgs, requestID) args.MergeRequestID = mergeRequestID args.CommitID = commitID - args.Source = setting.SourceFromGitlab + args.Source = setting.SourceFromGithub args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner args.RepoName = item.MainRepo.RepoName @@ -136,6 +136,46 @@ func (gpem *githubPushEventMatcherForTesting) UpdateTaskArgs(args *commonmodels. return args } +type githubTagEventMatcherForTesting struct { + log *zap.SugaredLogger + testing *commonmodels.Testing + event *github.CreateEvent +} + +func (gtem githubTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + return false, nil + } + } + hookRepo.Branch = getBranchFromRef(*ev.Ref) + + return true, nil + } + + return false, nil +} + +func (gtem githubTagEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gtem.testing, + reqID: requestID, + } + + factory.Update(args) + return args +} + func createGithubEventMatcherForTesting( event interface{}, diffSrv githubPullRequestDiffFunc, testing *commonmodels.Testing, log *zap.SugaredLogger, ) gitEventMatcherForTesting { @@ -153,6 +193,12 @@ func createGithubEventMatcherForTesting( event: evt, testing: testing, } + case *github.CreateEvent: + return &githubTagEventMatcherForTesting{ + testing: testing, + log: log, + event: evt, + } } return nil diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 5d408655..f52e8b0e 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -223,6 +223,53 @@ func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *comm return args } +type githubTagEventMatcher struct { + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *github.CreateEvent +} + +func (gtem githubTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } + if isRegular { + // Do not use regexp.MustCompile to avoid panic + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + return false, nil + } + } + hookRepo.Branch = getBranchFromRef(*ev.Ref) + + return true, nil + } + + return false, nil +} + +func (gtem githubTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { + factory := &workflowArgsFactory{ + workflow: gtem.workflow, + reqID: requestID, + } + + factory.Update(product, args, &types.Repository{ + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + Branch: hookRepo.Branch, + }) + + return args +} + func createGithubEventMatcher( event interface{}, diffSrv githubPullRequestDiffFunc, workflow *commonmodels.Workflow, log *zap.SugaredLogger, ) gitEventMatcher { @@ -240,6 +287,12 @@ func createGithubEventMatcher( event: evt, workflow: workflow, } + case *github.CreateEvent: + return &githubTagEventMatcher{ + workflow: workflow, + log: log, + event: evt, + } } return nil diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index e7687d0a..c8567804 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -61,13 +61,11 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log return err } - // forwardedProto := req.Header.Get("X-Forwarded-Proto") - // forwardedHost := req.Header.Get("X-Forwarded-Host") - // baseURI := fmt.Sprintf("%s://%s", forwardedProto, forwardedHost) baseURI := config.SystemAddress() var eventPush *EventPush var pushEvent *gitlab.PushEvent var mergeEvent *gitlab.MergeEvent + var tagEvent *gitlab.TagEvent var errorList = &multierror.Error{} switch event.(type) { @@ -99,6 +97,8 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log pushEvent = event case *gitlab.MergeEvent: mergeEvent = event + case *gitlab.TagEvent: + tagEvent = event } //触发更新服务模板webhook if eventPush != nil { @@ -180,6 +180,26 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log }() } + if tagEvent != nil { + // workflow webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerWorkflowByGitlabEvent(tagEvent, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() + + //test webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerTestByGitlabEvent(tagEvent, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() + } + wg.Wait() return errorList.ErrorOrNil() diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index 3c23fe42..9c36e852 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -97,6 +97,52 @@ func (gpem *gitlabPushEventMatcherForTesting) UpdateTaskArgs(args *commonmodels. return args } +type gitlabTagEventMatcherForTesting struct { + log *zap.SugaredLogger + testing *commonmodels.Testing + event *gitlab.TagEvent +} + +func (gtem gitlabTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil + } + } + hookRepo.Branch = getBranchFromRef(ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + + return MatchChanges(hookRepo, changedFiles), nil + } + + return false, nil +} + +func (gtem gitlabTagEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gtem.testing, + reqID: requestID, + } + + factory.Update(args) + return args +} + // TriggerTestByGitlabEvent 测试管理模块的触发器任务 func TriggerTestByGitlabEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { // 1. find configured testing @@ -203,6 +249,12 @@ func createGitlabEventMatcherForTesting( event: evt, testing: testing, } + case *gitlab.TagEvent: + return &gitlabTagEventMatcherForTesting{ + testing: testing, + log: log, + event: evt, + } } return nil diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 2a0dbb94..56649a8d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -160,6 +160,14 @@ func createGitlabEventMatcher( trigger: trigger, isYaml: isyaml, } + case *gitlab.TagEvent: + return &gitlabTagEventMatcher{ + workflow: workflow, + log: log, + event: evt, + trigger: trigger, + isYaml: isyaml, + } } return nil @@ -270,6 +278,108 @@ func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( return args } +type gitlabTagEventMatcher struct { + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *gitlab.TagEvent + trigger *TriggerYaml + isYaml bool + yamlServiceChanged []BuildServices +} + +func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + if gtem.isYaml { + refFlag := false + for _, ref := range gtem.trigger.Rules.Branchs { + if matched, _ := regexp.MatchString(ref, getBranchFromRef(ev.Ref)); matched { + refFlag = true + break + } + } + if !refFlag { + return false, nil + } + } else { + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil + } + } + } + + hookRepo.Branch = getBranchFromRef(ev.Ref) + + var changedFiles []string + detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) + if err != nil { + gtem.log.Errorf("GetCodehostDetail error: %s", err) + return false, err + } + + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken) + if err != nil { + gtem.log.Errorf("NewClient error: %s", err) + return false, err + } + + // compare接口获取两个commit之间的最终的改动 + diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) + if err != nil { + gtem.log.Errorf("Failed to get push event diffs, error: %s", err) + return false, err + } + for _, diff := range diffs { + changedFiles = append(changedFiles, diff.NewPath) + changedFiles = append(changedFiles, diff.OldPath) + } + if gtem.isYaml { + serviceChangeds := ServicesMatchChangesFiles(gtem.trigger.Rules.MatchFolders, hookRepo, changedFiles) + gtem.yamlServiceChanged = serviceChangeds + return len(serviceChangeds) != 0, nil + } + return MatchChanges(hookRepo, changedFiles), nil + } + + return false, nil +} + +func (gtem gitlabTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { + if gtem.isYaml { + var targets []*commonmodels.TargetArgs + for _, target := range args.Target { + for _, bs := range gtem.yamlServiceChanged { + if target.Name == bs.ServiceModule && target.ServiceName == bs.Name { + targets = append(targets, target) + } + } + } + args.Target = targets + } + factory := &workflowArgsFactory{ + workflow: gtem.workflow, + reqID: requestID, + IsYaml: gtem.isYaml, + } + + factory.Update(product, args, &types.Repository{ + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + Branch: hookRepo.Branch, + }) + + return args +} + func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Workflow, workFlowArgs *commonmodels.WorkflowTaskArgs, item *commonmodels.WorkflowHook, branref string, prId int) error { svcType, err := getServiceTypeByProject(workflow.ProductTmplName) if err != nil { @@ -453,6 +563,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, workFlowArgs := &commonmodels.WorkflowTaskArgs{} var pushEvent *gitlab.PushEvent var mergeEvent *gitlab.MergeEvent + var tagEvent *gitlab.TagEvent prID := 0 branref := "" switch evt := event.(type) { @@ -468,6 +579,9 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } prID = evt.ObjectAttributes.IID item.MainRepo.Branch = getBranchFromRef(mergeEvent.ObjectAttributes.TargetBranch) + case *gitlab.TagEvent: + tagEvent = evt + branref = tagEvent.Ref } if item.IsYaml { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 37845daf..a5c700da 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -594,8 +594,8 @@ func checkTriggerYamlParams(triggerYaml *TriggerYaml) error { return fmt.Errorf("rules.baranch must exist") } for _, ev := range triggerYaml.Rules.Events { - if ev != "pull_request" && ev != "push" { - return fmt.Errorf("rules.event must be pull_request or push ") + if ev != "pull_request" && ev != "push" && ev != "tag" { + return fmt.Errorf("rules.event must be pull_request or push or tag") } } if triggerYaml.Rules.MatchFolders == nil { diff --git a/pkg/tool/git/hook.go b/pkg/tool/git/hook.go index d7780b0a..d887b59b 100644 --- a/pkg/tool/git/hook.go +++ b/pkg/tool/git/hook.go @@ -19,6 +19,7 @@ package git const ( PushEvent = "push" PullRequestEvent = "pull_request" + TagEvent = "tag" CheckRunEvent = "check_run" BranchOrTagCreateEvent = "create" ) -- Gitee From ad3f0c787274df2a649a795b01f343bfe511450e Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 21 Jan 2022 10:54:18 +0800 Subject: [PATCH 1357/2647] add sync label Signed-off-by: panxunying --- .../service/collaboration_instance.go | 124 ++++++++++++++++-- .../{labelsbinding.go => label_binding.go} | 18 ++- .../aslan/core/label/handler/labels.go | 2 +- .../label/repository/mongodb/labelbinding.go | 29 +++- .../aslan/core/label/service/label.go | 42 +++++- .../aslan/core/label/service/labelsbinding.go | 43 +++--- pkg/shared/client/label/label.go | 71 +++++++++- pkg/shared/client/label/label_binding.go | 65 +++++++++ 8 files changed, 342 insertions(+), 52 deletions(-) rename pkg/microservice/aslan/core/label/handler/{labelsbinding.go => label_binding.go} (74%) create mode 100644 pkg/shared/client/label/label_binding.go diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index d6314942..106b5e84 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -14,6 +14,8 @@ import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" + "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/util" ) @@ -26,6 +28,7 @@ type GetCollaborationUpdateResp struct { } type UpdateItem struct { CollaborationMode string `json:"collaboration_mode"` + PolicyName string `json:"policy_name"` DeployType string `json:"deploy_type"` DeleteSpec DeleteSpec `json:"delete_spec"` UpdateSpec UpdateSpec `json:"update_spec"` @@ -153,6 +156,7 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc updateProductItems, newProductItems, deleteProductItems := getUpdateProductDiff(cmpMap, cipMap) return UpdateItem{ CollaborationMode: cm.Name, + PolicyName: ci.PolicyName, DeployType: cm.DeployType, NewSpec: NewSpec{ Workflows: newWorkflowItems, @@ -276,13 +280,13 @@ func buildName(baseName, modeName, userName string) string { } func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, userName, uid string, - logger *zap.SugaredLogger) (map[string]string, error) { + logger *zap.SugaredLogger) (map[string]*models.CollaborationInstance, error) { var instances []*models.CollaborationInstance - modePolicyMap := make(map[string]string) + modeInstanceMap := make(map[string]*models.CollaborationInstance) for _, mode := range updateResp.New { instance := genCollaborationInstance(mode, projectName, uid, userName) instances = append(instances, instance) - modePolicyMap[mode.Name] = instance.PolicyName + modeInstanceMap[mode.Name] = instance } err := mongodb.NewCollaborationInstanceColl().BulkCreate(instances) if err != nil { @@ -310,21 +314,21 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, userName, if err != nil { return nil, err } - return modePolicyMap, nil + return modeInstanceMap, nil } func buildPolicyDescription(mode, userName string) string { return mode + " " + userName + "的权限" } -func syncPolicy(updateResp *GetCollaborationUpdateResp, modePolicyMap map[string]string, projectName, userName, uid string, +func syncPolicy(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[string]*models.CollaborationInstance, projectName, userName string, logger *zap.SugaredLogger) error { var policies []*policy.Policy for _, mode := range updateResp.New { var rules []*policy.Rule var policyName string - if name, ok := modePolicyMap[mode.Name]; ok { - policyName = name + if instance, ok := modeInstanceMap[mode.Name]; ok { + policyName = instance.PolicyName } else { return fmt.Errorf("mode:%s not exist policyName", mode.Name) } @@ -407,10 +411,112 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, modePolicyMap map[string return nil } -func syncLabel(updateResp *GetCollaborationUpdateResp) error { +func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[string]*models.CollaborationInstance, + projectName string, logger *zap.SugaredLogger) error { + var labels []label.Label + var newBindings []label.LabelBinding for _, mode := range updateResp.New { + instance, ok := modeInstanceMap[mode.Name] + if ok { + labels = append(labels, label.Label{ + Key: "policy", + Value: instance.PolicyName, + }) + } else { + return fmt.Errorf("mode:%s not exist policyName", mode.Name) + } + } + var labelModels []label.LabelModel + for _, item := range updateResp.Update { + labelModels = append(labelModels, label.LabelModel{ + Key: "policy", + Value: item.PolicyName, + }) + } + for _, item := range updateResp.Delete { + labelModels = append(labelModels, label.LabelModel{ + Key: "policy", + Value: item.PolicyName, + }) + } + labelListResp, err := label.New().ListLabels(label.ListLabelsArgs{ + Labels: labelModels, + }) + labelIdMap := make(map[string]string) + for _, label := range labelListResp.Labels { + labelIdMap[service.BuildLabelString(label.Key, label.Value)] = label.ID.Hex() + } + labelResp, err := label.New().CreateLabels(label.CreateLabelsArgs{ + Labels: labels, + }) + + if err != nil { + logger.Errorf("create labels error, error msg:%s", err) + return err + } + for _, mode := range updateResp.New { + instance, ok := modeInstanceMap[mode.Name] + if !ok { + return fmt.Errorf("mode:%s not exist instance", mode.Name) + } + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", instance.PolicyName)] + if !ok { + return fmt.Errorf("label:%s create error", instance.PolicyName) + } + for _, workflow := range instance.Workflows { + newBindings = append(newBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: workflow.Name, + ProjectName: projectName, + Type: "Workflow", + }, + }) + } + for _, product := range instance.Products { + newBindings = append(newBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: product.Name, + ProjectName: projectName, + Type: "Product", + }, + }) + } + } + for _, item := range updateResp.Update { + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", item.PolicyName)] + if !ok { + return fmt.Errorf("label:%s create error", item.PolicyName) + } + for _, workflow := range item.NewSpec.Workflows { + newBindings = append(newBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: workflow.Name, + Type: "Workflow", + ProjectName: projectName, + }, + }) + } + for _, product := range item.NewSpec.Products { + newBindings = append(newBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: product.Name, + Type: "Product", + ProjectName: projectName, + }, + }) + } + for _, workflow := range item.UpdateSpec.Workflows { + if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == + config.CollaborationNew { + } + } } + return nil } @@ -424,7 +530,7 @@ func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.Su if err != nil { return err } - err = syncPolicy(updateResp, modePolicyMap, projectName, userName, uid, logger) + err = syncPolicy(updateResp, modePolicyMap, projectName, userName, logger) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/label/handler/labelsbinding.go b/pkg/microservice/aslan/core/label/handler/label_binding.go similarity index 74% rename from pkg/microservice/aslan/core/label/handler/labelsbinding.go rename to pkg/microservice/aslan/core/label/handler/label_binding.go index 3b566f8f..03eef7ae 100644 --- a/pkg/microservice/aslan/core/label/handler/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/handler/label_binding.go @@ -25,7 +25,7 @@ import ( ) func createLBValidate(cr *service.CreateLabelBindingsArgs) error { - for _, v := range cr.CreateLabelBindings { + for _, v := range cr.LabelBindings { if v.LabelID == "" || v.Resource.Type == "" { return e.ErrInvalidParam.AddDesc("invalid labelbinding args") } @@ -48,3 +48,19 @@ func CreateLabelBindings(c *gin.Context) { } ctx.Err = service.CreateLabelBindings(createLabelBindingsArgs, ctx.UserName, ctx.Logger) } + +func DeleteLabelBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + createLabelBindingsArgs := new(service.CreateLabelBindingsArgs) + if err := c.ShouldBindJSON(createLabelBindingsArgs); err != nil { + ctx.Err = err + return + } + if err := createLBValidate(createLabelBindingsArgs); err != nil { + ctx.Err = err + return + } + ctx.Err = service.CreateLabelBindings(createLabelBindingsArgs, ctx.UserName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 1f431860..bef4f96d 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -78,7 +78,7 @@ func CreateLabels(c *gin.Context) { } filteredLabels = append(filteredLabels, &tmpModel) } - ctx.Err = service.CreateLabels(filteredLabels) + ctx.Resp, ctx.Err = service.CreateLabels(filteredLabels) } //DeleteLabels can only bulk delete labels which not bind reousrces diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index 5fe3d963..962cbbb5 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -60,7 +60,14 @@ func (c *LabelBindingColl) EnsureIndex(ctx context.Context) error { return err } -func (c *LabelBindingColl) CreateMany(labelBindings []*models.LabelBinding) error { +type LabelBinding struct { + Resource Resource `json:"resource"` + LabelID string `json:"label_id"` + CreateBy string `json:"create_by"` + CreateTime int64 `json:"create_time"` +} + +func (c *LabelBindingColl) CreateMany(labelBindings []*LabelBinding) error { if len(labelBindings) == 0 { return nil } @@ -126,7 +133,7 @@ func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.La return ret, err } -func (c *LabelBindingColl) BulkDelete(ids []string) error { +func (c *LabelBindingColl) BulkDeleteByIds(ids []string) error { if len(ids) == 0 { return nil } @@ -145,6 +152,24 @@ func (c *LabelBindingColl) BulkDelete(ids []string) error { return err } +func (c *LabelBindingColl) BulkDelete(labelBindings []*LabelBinding) error { + if len(labelBindings) == 0 { + return nil + } + condition := bson.A{} + for _, binding := range labelBindings { + condition = append(condition, bson.M{ + "label_id": binding.LabelID, + "resource_name": binding.Resource.Name, + "project_name": binding.Resource.ProjectName, + "resource_type": binding.Resource.Type, + }) + } + query := bson.D{{"$or", condition}} + _, err := c.DeleteMany(context.TODO(), query) + return err +} + type Resource struct { Name string `json:"name"` ProjectName string `json:"project_name"` diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 45172e0e..b74a8246 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -27,20 +27,52 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func CreateLabels(labels []*models.Label) error { - return mongodb.NewLabelColl().BulkCreate(labels) +type CreateLabelsResp struct { + LabelMap map[string]string `json:"label_map"` +} + +func CreateLabels(labels []*models.Label) (*CreateLabelsResp, error) { + err := mongodb.NewLabelColl().BulkCreate(labels) + if err != nil { + return nil, err + } + var labelModels []mongodb.Label + for _, label := range labels { + labelModels = append(labelModels, mongodb.Label{ + Key: label.Key, + Value: label.Value, + }) + } + var result map[string]string + resp, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{ + Labels: labelModels, + }) + for _, label := range resp { + result[BuildLabelString(label.Key, label.Value)] = label.ID.Hex() + } + return &CreateLabelsResp{ + LabelMap: result, + }, err } type ListLabelsArgs struct { Labels []mongodb.Label `json:"labels"` } +type ListLabelsResp struct { + Labels []*models.Label `json:"labels"` +} + type CreateLabelsArgs struct { Labels []mongodb.Label `json:"labels"` } -func ListLabels(args *ListLabelsArgs) ([]*models.Label, error) { - return mongodb.NewLabelColl().List(mongodb.ListLabelOpt{args.Labels}) +func ListLabels(args *ListLabelsArgs) (*ListLabelsResp, error) { + labels, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{args.Labels}) + if err != nil { + return nil, err + } + return &ListLabelsResp{Labels: labels}, nil } type ListResourceByLabelsReq struct { @@ -177,7 +209,7 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err labelBindingIDs = append(ids, labelBinding.ID.Hex()) } - if err := mongodb.NewLabelBindingColl().BulkDelete(labelBindingIDs); err != nil { + if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) return err } diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 9879317a..e83470e9 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -24,26 +24,18 @@ import ( commondb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" e "github.com/koderover/zadig/pkg/tool/errors" ) -type CreateLabelBinding struct { - Resource mongodb.Resource `json:"resource"` - LabelID string `json:"label_id"` - CreateBy string `json:"create_by"` - CreateTime int64 `json:"create_time"` -} - type CreateLabelBindingsArgs struct { - CreateLabelBindings []CreateLabelBinding `json:"create_label_bindings"` + LabelBindings []*mongodb.LabelBinding `json:"label_bindings"` } func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *zap.SugaredLogger) error { // check label exist labelIDs := []string{} - for _, binding := range cr.CreateLabelBindings { + for _, binding := range cr.LabelBindings { labelIDs = append(labelIDs, binding.LabelID) binding.CreateBy = userName } @@ -58,8 +50,8 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z return fmt.Errorf("there're labels not exist") } //check resource exist - m := make(map[string][]CreateLabelBinding) - for _, binding := range cr.CreateLabelBindings { + m := make(map[string][]*mongodb.LabelBinding) + for _, binding := range cr.LabelBindings { m[binding.Resource.Type] = append(m[binding.Resource.Type], binding) } for k, v := range m { @@ -107,24 +99,21 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z } } - var labelBindings []*models.LabelBinding - for _, v := range cr.CreateLabelBindings { - labelBinding := &models.LabelBinding{ - ResourceType: v.Resource.Type, - ResourceName: v.Resource.Name, - ProjectName: v.Resource.ProjectName, - LabelID: v.LabelID, - CreateBy: v.CreateBy, - } - labelBindings = append(labelBindings, labelBinding) - } - return mongodb.NewLabelBindingColl().CreateMany(labelBindings) + return mongodb.NewLabelBindingColl().CreateMany(cr.LabelBindings) +} + +type DeleteLabelBindingsArgs struct { + LabelBindings []*mongodb.LabelBinding `json:"label_bindings"` +} + +func DeleteLabelBindings(dr *DeleteLabelBindingsArgs, userName string, logger *zap.SugaredLogger) error { + return mongodb.NewLabelBindingColl().BulkDelete(dr.LabelBindings) } -type DeleteLabelsBindingsArgs struct { +type DeleteLabelsBindingsByIdsArgs struct { IDs []string } -func DeleteLabelsBindings(ids []string) error { - return mongodb.NewLabelBindingColl().BulkDelete(ids) +func DeleteLabelsBindingsByIds(ids []string) error { + return mongodb.NewLabelBindingColl().BulkDeleteByIds(ids) } diff --git a/pkg/shared/client/label/label.go b/pkg/shared/client/label/label.go index 67558e54..d6a0ef2f 100644 --- a/pkg/shared/client/label/label.go +++ b/pkg/shared/client/label/label.go @@ -17,18 +17,33 @@ limitations under the License. package label import ( - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" ) +type Label struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` +} + type ListResourcesByLabelsReq struct { - LabelFilters []mongodb.Label `json:"label_filters"` + LabelFilters []Label `json:"label_filters"` +} + +type Resource struct { + Name string `json:"name"` + ProjectName string `json:"project_name"` + Type string `json:"type"` } type ListResourcesByLabelsResp struct { - Resources map[string][]mongodb.Resource `json:"resources"` + Resources map[string][]Resource `json:"resources"` } func (c *Client) ListResourcesByLabels(request ListResourcesByLabelsReq) (*ListResourcesByLabelsResp, error) { @@ -44,12 +59,42 @@ func (c *Client) ListResourcesByLabels(request ListResourcesByLabelsReq) (*ListR return resources, nil } +type LabelModel struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type ListLabelsArgs struct { + Labels []LabelModel `json:"labels"` +} + +type ListLabelsResp struct { + Labels []Label `json:"labels"` +} + +func (c *Client) ListLabels(request ListLabelsArgs) (*ListLabelsResp, error) { + url := "/label/labels" + + var resources *ListLabelsResp + _, err := c.Post(url, httpclient.SetBody(request), httpclient.SetResult(&resources)) + if err != nil { + log.Errorf("Failed to ListLabels, error: %s", err) + return nil, err + } + + return resources, nil +} + type ListLabelsByResourcesReq struct { - Resources []mongodb.Resource `json:"resources"` + Resources []Resource `json:"resources"` } type ListLabelsByResourcesResp struct { - Labels map[string][]*models.Label `json:"labels"` + Labels map[string][]*Label `json:"labels"` +} + +type CreateLabelsArgs struct { + Labels []Label `json:"labels"` } func (c *Client) ListLabelsByResources(request ListLabelsByResourcesReq) (*ListLabelsByResourcesResp, error) { @@ -58,7 +103,19 @@ func (c *Client) ListLabelsByResources(request ListLabelsByResourcesReq) (*ListL var resources *ListLabelsByResourcesResp _, err := c.Post(url, httpclient.SetBody(request), httpclient.SetResult(&resources)) if err != nil { - log.Errorf("Failed to add audit log, error: %s", err) + log.Errorf("Failed to ListLabelsByResources, error: %s", err) + return nil, err + } + + return resources, nil +} + +func (c *Client) CreateLabels(request CreateLabelsArgs) (*CreateLabelsResp, error) { + url := "/label/labels" + var resources *CreateLabelsResp + _, err := c.Post(url, httpclient.SetBody(request), httpclient.SetResult(&resources)) + if err != nil { + log.Errorf("Failed to CreateLabels, error: %s", err) return nil, err } diff --git a/pkg/shared/client/label/label_binding.go b/pkg/shared/client/label/label_binding.go new file mode 100644 index 00000000..3f0b7eb1 --- /dev/null +++ b/pkg/shared/client/label/label_binding.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package label + +import ( + "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" +) + +type CreateLabelsResp struct { + LabelMap map[string]string `json:"label_map"` +} + +type CreateLabelBindingsArgs struct { + LabelBindings []LabelBinding `json:"label_bindings"` +} + +type LabelBinding struct { + Resource Resource `json:"resource"` + LabelID string `json:"label_id"` + CreateBy string `json:"create_by"` + CreateTime int64 `json:"create_time"` +} + +type DeleteLabelBindingsArgs struct { + LabelBindings []LabelBinding `json:"label_bindings"` +} + +func (c *Client) CreateLabelBindings(request CreateLabelBindingsArgs) error { + url := "/label/labelbindings" + + _, err := c.Post(url, httpclient.SetBody(request)) + if err != nil { + log.Errorf("Failed to CreateLabelBindings, error: %s", err) + return err + } + + return nil +} + +func (c *Client) DeleteLabelBindings(request DeleteLabelBindingsArgs) error { + url := "/label/labelbindings" + + _, err := c.Delete(url, httpclient.SetBody(request)) + if err != nil { + log.Errorf("Failed to DeleteLabelBindings, error: %s", err) + return err + } + + return nil +} -- Gitee From a6bd58c809d463e1e37c5d9732f0f8c620d9236f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 11:05:59 +0800 Subject: [PATCH 1358/2647] remove unused code Signed-off-by: liu deyi --- pkg/tool/git/hook.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/tool/git/hook.go b/pkg/tool/git/hook.go index d887b59b..d7780b0a 100644 --- a/pkg/tool/git/hook.go +++ b/pkg/tool/git/hook.go @@ -19,7 +19,6 @@ package git const ( PushEvent = "push" PullRequestEvent = "pull_request" - TagEvent = "tag" CheckRunEvent = "check_run" BranchOrTagCreateEvent = "create" ) -- Gitee From 1e6c3c3ef6b37ffb5e77f326b493b0468de719fa Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 21 Jan 2022 11:47:09 +0800 Subject: [PATCH 1359/2647] permission Signed-off-by: mouuii --- pkg/shared/client/policy/policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 56a91402..a511f330 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -57,7 +57,7 @@ type RoleBinding struct { } func (c *Client) CreateOrUpdatePolicy(p *Policy) error { - url := fmt.Sprintf("/policymetas/%s", p.Resource) + url := fmt.Sprintf("/policies/%s", p.Resource) _, err := c.Put(url, httpclient.SetBody(p)) -- Gitee From 2351c13422e832a00340a06f727a68ed7efd5c99 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 21 Jan 2022 12:52:02 +0800 Subject: [PATCH 1360/2647] fixed a bug where list regisrtries will not return the correct SK for huawei registry Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 11043ba1..501c5b36 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -111,8 +111,8 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* for _, reg := range resp { switch reg.RegProvider { case config.RegistryTypeSWR: - reg.AccessKey = fmt.Sprintf("%s@%s", reg.Region, reg.AccessKey) reg.SecretKey = util.ComputeHmacSha256(reg.AccessKey, reg.SecretKey) + reg.AccessKey = fmt.Sprintf("%s@%s", reg.Region, reg.AccessKey) case config.RegistryTypeAWS: realAK, realSK, err := getAWSRegistryCredential(reg.ID.Hex(), reg.AccessKey, reg.SecretKey, reg.Region) if err != nil { -- Gitee From 13994da1cb7d024cb7f56c3c1a93efe67d5e54b2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 21 Jan 2022 13:48:49 +0800 Subject: [PATCH 1361/2647] permission Signed-off-by: mouuii --- pkg/shared/client/policy/policy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 56a91402..a511f330 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -57,7 +57,7 @@ type RoleBinding struct { } func (c *Client) CreateOrUpdatePolicy(p *Policy) error { - url := fmt.Sprintf("/policymetas/%s", p.Resource) + url := fmt.Sprintf("/policies/%s", p.Resource) _, err := c.Put(url, httpclient.SetBody(p)) -- Gitee From 9d1b78df8c226fdcdde39f21ac2da22b9927deab Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 21 Jan 2022 13:53:58 +0800 Subject: [PATCH 1362/2647] permission Signed-off-by: mouuii --- pkg/microservice/policy/core/service/policy.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 92dc930f..6ad2b73d 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -22,7 +22,6 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/shared/client/label" @@ -136,7 +135,7 @@ func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { res := &Policy{ Name: r.Name, } - var labels []mongodb2.Label + var labels []label.Label labelSet := sets.NewString() for _, ru := range r.Rules { res.Rules = append(res.Rules, &Rule{ @@ -149,7 +148,7 @@ func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { labelString := service.BuildLabelString(ma.Key, ma.Value) if !labelSet.Has(labelString) { labelSet.Insert(labelString) - labels = append(labels, mongodb2.Label{ + labels = append(labels, label.Label{ Key: ma.Key, Value: ma.Value, }) -- Gitee From cd1640360f9eac1ba6785f16f6b0f6741436d81a Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 21 Jan 2022 13:54:45 +0800 Subject: [PATCH 1363/2647] fix Workflow history task lists support filtering Signed-off-by: ddh27 --- .../core/common/repository/models/workflow.go | 2 +- .../service/workflow/pipeline_task.go | 32 +++++++++++++------ .../core/service/types/task/model.go | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index aebf841d..e5327891 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -170,7 +170,7 @@ type WorkflowTaskArgs struct { CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` - Committer string `bson:"committer" json:"committer"` + Committer string `bson:"committer,omitempty" json:"committer,omitempty"` //github check run HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` // 请求模式,openAPI表示外部客户调用 diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 47532941..b903c1d7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -298,7 +298,11 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer var err error var listTaskOpt *commonrepo.ListTaskOption var countTaskOpt *commonrepo.CountTaskOption + var restp []*commonrepo.TaskPreview serviceNameFiltersMap := make(map[string]interface{}, len(filters)) + if len(filters) == 0 || (len(filters) == 1 && filters[0] == "") { + queryType = "" + } switch queryType { case "creator": listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, TaskCreators: filters, Detail: true, Type: typeString} @@ -312,7 +316,6 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer for _, svc := range filters { serviceNameFiltersMap[svc] = nil } - case "taskStatus": listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuss: filters, Detail: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Statuss: filters, Type: typeString} @@ -320,13 +323,13 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Detail: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} } - ret.Data, err = commonrepo.NewTaskColl().List(listTaskOpt) + restp, err = commonrepo.NewTaskColl().List(listTaskOpt) if err != nil { log.Errorf("PipelineTaskV2.List:%s error: %s", listTaskOpt, err) return ret, e.ErrListTasks } var buildStage, deployStage *commonmodels.Stage - for _, t := range ret.Data { + for _, t := range restp { t.BuildServices = []string{} for _, stage := range t.Stages { if stage.TaskType == config.TaskBuild { @@ -336,12 +339,13 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer deployStage = stage } } + existSvc := false if buildStage != nil { for serviceName := range buildStage.SubTasks { if queryType == "serviceName" { _, ok := serviceNameFiltersMap[serviceName] - if !ok { - continue + if ok { + existSvc = true } } t.BuildServices = append(t.BuildServices, serviceName) @@ -350,14 +354,17 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer for serviceName := range deployStage.SubTasks { if queryType == "serviceName" { _, ok := serviceNameFiltersMap[serviceName] - if !ok { - continue + if ok { + existSvc = true } } t.BuildServices = append(t.BuildServices, serviceName) } } buildStage, deployStage = nil, nil + if existSvc { + ret.Data = append(ret.Data, t) + } } if queryType == "serviceName" { ret.Total = len(ret.Data) @@ -372,6 +379,7 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer } return ret, nil } + ret.Data = restp ret.Total, err = commonrepo.NewTaskColl().Count(countTaskOpt) if err != nil { log.Errorf("PipelineTaskV2.List Count error: %v", err) @@ -405,7 +413,7 @@ func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeS return resp, e.ErrGetTask } case "committer": - fieldName = "committer" + fieldName = "workflow_args.committer" resp, err = commonrepo.NewTaskColl().DistinctFeildsPipelineTask(fieldName, projectName, pipelineName, typeString, false) if err != nil { log.Errorf("[%s] DistinctFeildsPipelineTask fieldName: %s error: %s", fieldName, pipelineName, err) @@ -418,6 +426,7 @@ func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeS return resp, e.ErrListTasks } var buildStage, deployStage *commonmodels.Stage + svcSets := sets.NewString() for _, t := range data { for _, stage := range t.Stages { if stage.TaskType == config.TaskBuild { @@ -429,15 +438,18 @@ func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeS } if buildStage != nil { for serviceName := range buildStage.SubTasks { - resp = append(resp, serviceName) + svcSets.Insert(serviceName) } } else if deployStage != nil { for serviceName := range deployStage.SubTasks { - resp = append(resp, serviceName) + svcSets.Insert(serviceName) } } buildStage, deployStage = nil, nil } + for svc := range svcSets { + resp = append(resp, svc) + } default: return resp, fmt.Errorf("queryType parameter is invalid") } diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 2b783acf..5a73843d 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -157,7 +157,7 @@ type WorkflowTaskArgs struct { CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` - + Committer string `bson:"committer,omitempty" json:"committer,omitempty"` //github check run HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` // 请求模式,openAPI表示外部客户调用 -- Gitee From 9dd9b15d69bedca112ebb22e863610f78e9faf86 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 21 Jan 2022 14:08:59 +0800 Subject: [PATCH 1364/2647] add sync label Signed-off-by: panxunying --- .../service/collaboration_instance.go | 101 ++++++++++++++++-- .../aslan/core/label/handler/label_binding.go | 15 +-- .../aslan/core/label/handler/router.go | 1 + pkg/shared/client/label/label.go | 12 ++- 4 files changed, 115 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 106b5e84..cc99c9c9 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -415,6 +415,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin projectName string, logger *zap.SugaredLogger) error { var labels []label.Label var newBindings []label.LabelBinding + var deleteBindings []label.LabelBinding for _, mode := range updateResp.New { instance, ok := modeInstanceMap[mode.Name] if ok { @@ -461,7 +462,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin } labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", instance.PolicyName)] if !ok { - return fmt.Errorf("label:%s create error", instance.PolicyName) + return fmt.Errorf("label:%s not exist", instance.PolicyName) } for _, workflow := range instance.Workflows { newBindings = append(newBindings, label.LabelBinding{ @@ -487,7 +488,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin for _, item := range updateResp.Update { labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", item.PolicyName)] if !ok { - return fmt.Errorf("label:%s create error", item.PolicyName) + return fmt.Errorf("label:%s not exist", item.PolicyName) } for _, workflow := range item.NewSpec.Workflows { newBindings = append(newBindings, label.LabelBinding{ @@ -512,11 +513,98 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == config.CollaborationNew { - + newBindings = append(newBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: workflow.New.Name, + Type: "Workflow", + ProjectName: projectName, + }, + }) + } + if workflow.Old.CollaborationType == config.CollaborationNew && workflow.New.CollaborationType == + config.CollaborationShare { + deleteBindings = append(deleteBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: workflow.New.Name, + Type: "Workflow", + ProjectName: projectName, + }, + }) + } + } + for _, product := range item.UpdateSpec.Products { + if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == + config.CollaborationNew { + newBindings = append(newBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: product.New.Name, + Type: "Product", + ProjectName: projectName, + }, + }) + } + if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == + config.CollaborationShare { + deleteBindings = append(deleteBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: product.Old.Name, + Type: "Product", + ProjectName: projectName, + }, + }) } } } - + var deleteLabels []string + for _, item := range updateResp.Delete { + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", item.PolicyName)] + if !ok { + return fmt.Errorf("label:%s not exist", item.PolicyName) + } + for _, workflow := range item.Workflows { + deleteBindings = append(deleteBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: workflow.Name, + ProjectName: projectName, + Type: "Workflow", + }, + }) + } + for _, product := range item.Products { + deleteBindings = append(deleteBindings, label.LabelBinding{ + LabelID: labelId, + Resource: label.Resource{ + Name: product.Name, + ProjectName: projectName, + Type: "Product", + }, + }) + } + } + err = label.New().DeleteLabels(deleteLabels) + if err != nil { + logger.Errorf("delete labels error:%s", err) + return err + } + err = label.New().DeleteLabelBindings(label.DeleteLabelBindingsArgs{ + LabelBindings: deleteBindings, + }) + if err != nil { + logger.Errorf("delete labelbindings error:%s", err) + return err + } + err = label.New().CreateLabelBindings(label.CreateLabelBindingsArgs{ + LabelBindings: newBindings, + }) + if err != nil { + logger.Errorf("create labelbindings error:%s", err) + return err + } return nil } @@ -526,14 +614,15 @@ func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.Su logger.Errorf("GetCollaborationNew error, err msg:%s", err) return err } - modePolicyMap, err := syncInstance(updateResp, projectName, userName, uid, logger) + modeInstanceMap, err := syncInstance(updateResp, projectName, userName, uid, logger) if err != nil { return err } - err = syncPolicy(updateResp, modePolicyMap, projectName, userName, logger) + err = syncPolicy(updateResp, modeInstanceMap, projectName, userName, logger) if err != nil { return err } + err = syncLabel(updateResp, modeInstanceMap, projectName, logger) return nil } diff --git a/pkg/microservice/aslan/core/label/handler/label_binding.go b/pkg/microservice/aslan/core/label/handler/label_binding.go index 03eef7ae..c6003154 100644 --- a/pkg/microservice/aslan/core/label/handler/label_binding.go +++ b/pkg/microservice/aslan/core/label/handler/label_binding.go @@ -19,13 +19,14 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" ) -func createLBValidate(cr *service.CreateLabelBindingsArgs) error { - for _, v := range cr.LabelBindings { +func lBValidate(lb []*mongodb.LabelBinding) error { + for _, v := range lb { if v.LabelID == "" || v.Resource.Type == "" { return e.ErrInvalidParam.AddDesc("invalid labelbinding args") } @@ -42,7 +43,7 @@ func CreateLabelBindings(c *gin.Context) { ctx.Err = err return } - if err := createLBValidate(createLabelBindingsArgs); err != nil { + if err := lBValidate(createLabelBindingsArgs.LabelBindings); err != nil { ctx.Err = err return } @@ -53,14 +54,14 @@ func DeleteLabelBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - createLabelBindingsArgs := new(service.CreateLabelBindingsArgs) - if err := c.ShouldBindJSON(createLabelBindingsArgs); err != nil { + deleteLabelBindingsArgs := new(service.DeleteLabelBindingsArgs) + if err := c.ShouldBindJSON(deleteLabelBindingsArgs); err != nil { ctx.Err = err return } - if err := createLBValidate(createLabelBindingsArgs); err != nil { + if err := lBValidate(deleteLabelBindingsArgs.LabelBindings); err != nil { ctx.Err = err return } - ctx.Err = service.CreateLabelBindings(createLabelBindingsArgs, ctx.UserName, ctx.Logger) + ctx.Err = service.DeleteLabelBindings(deleteLabelBindingsArgs, ctx.UserName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 85ecb22c..83328ea5 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -34,5 +34,6 @@ func (*Router) Inject(router *gin.RouterGroup) { labelBindings := router.Group("labelbindings") { labelBindings.POST("", CreateLabelBindings) + labelBindings.DELETE("", DeleteLabelBindings) } } diff --git a/pkg/shared/client/label/label.go b/pkg/shared/client/label/label.go index d6a0ef2f..091b0590 100644 --- a/pkg/shared/client/label/label.go +++ b/pkg/shared/client/label/label.go @@ -118,6 +118,16 @@ func (c *Client) CreateLabels(request CreateLabelsArgs) (*CreateLabelsResp, erro log.Errorf("Failed to CreateLabels, error: %s", err) return nil, err } - return resources, nil } + +func (c *Client) DeleteLabels(ids []string) error { + url := "/label/labels?force=true" + _, err := c.Delete(url, httpclient.SetBody(ids)) + if err != nil { + log.Errorf("Failed to CreateLabels, error: %s", err) + return err + } + + return nil +} -- Gitee From 1386eca78b40750ddb0dd553abfd2a12fc53b374 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 21 Jan 2022 15:00:59 +0800 Subject: [PATCH 1365/2647] fixed aslan calling itself bug Signed-off-by: Min Min --- pkg/middleware/gin/audit_log.go | 5 +- pkg/shared/client/aslan/audit_log.go | 83 ---------------------------- pkg/shared/handler/base.go | 17 +++++- 3 files changed, 16 insertions(+), 89 deletions(-) delete mode 100644 pkg/shared/client/aslan/audit_log.go diff --git a/pkg/middleware/gin/audit_log.go b/pkg/middleware/gin/audit_log.go index e3c1a6c0..879b3ed3 100644 --- a/pkg/middleware/gin/audit_log.go +++ b/pkg/middleware/gin/audit_log.go @@ -19,8 +19,7 @@ package gin import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/shared/client/aslan" + systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" "github.com/koderover/zadig/pkg/util/ginzap" ) @@ -31,7 +30,7 @@ func UpdateOperationLogStatus(c *gin.Context) { if c.GetString("operationLogID") == "" { return } - err := aslan.New(config.AslanServiceAddress()).UpdateAuditLog(c.GetString("operationLogID"), c.Writer.Status(), log) + err := systemservice.UpdateOperation(c.GetString("operationLogID"), c.Writer.Status(), log) if err != nil { log.Errorf("UpdateOperation err:%v", err) } diff --git a/pkg/shared/client/aslan/audit_log.go b/pkg/shared/client/aslan/audit_log.go deleted file mode 100644 index f41cab61..00000000 --- a/pkg/shared/client/aslan/audit_log.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package aslan - -import ( - "fmt" - "time" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/tool/httpclient" -) - -type operationLog struct { - Username string `json:"username"` - ProductName string `json:"product_name"` - Method string `json:"method"` - Function string `json:"function"` - Name string `json:"name"` - RequestBody string `json:"request_body"` - Status int `json:"status"` - CreatedAt int64 `json:"created_at"` -} - -type updateOperationArgs struct { - Status int `json:"status"` -} - -type AddAuditLogResp struct { - OperationLogID string `json:"id"` -} - -func (c *Client) AddAuditLog(username, productName, method, function, detail, requestBody string, log *zap.SugaredLogger) (string, error) { - url := "/system/operation" - req := operationLog{ - Username: username, - ProductName: productName, - Method: method, - Function: function, - Name: detail, - RequestBody: requestBody, - Status: 0, - CreatedAt: time.Now().Unix(), - } - - var operationLogID AddAuditLogResp - _, err := c.Post(url, httpclient.SetBody(req), httpclient.SetResult(&operationLogID)) - if err != nil { - log.Errorf("Failed to add audit log, error: %s", err) - return "", err - } - - return operationLogID.OperationLogID, nil -} - -func (c *Client) UpdateAuditLog(id string, status int, log *zap.SugaredLogger) error { - url := fmt.Sprintf("/system/operation/%s", id) - req := updateOperationArgs{ - Status: status, - } - - _, err := c.Put(url, httpclient.SetBody(req)) - if err != nil { - log.Errorf("Failed to update audit log, error: %s", err) - return err - } - - return nil -} diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index ee84aded..3146e541 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -22,13 +22,14 @@ import ( "fmt" "reflect" "strings" + "time" "github.com/gin-gonic/gin" "go.uber.org/zap" - "github.com/koderover/zadig/pkg/config" + systemmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/models" + systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/util/ginzap" ) @@ -118,7 +119,17 @@ func JSONResponse(c *gin.Context, ctx *Context) { // InsertOperationLog 插入操作日志 func InsertOperationLog(c *gin.Context, username, productName, method, function, detail, requestBody string, logger *zap.SugaredLogger) { - operationLogID, err := aslan.New(config.AslanServiceAddress()).AddAuditLog(username, productName, method, function, detail, requestBody, logger) + req := &systemmodels.OperationLog{ + Username: username, + ProductName: productName, + Method: method, + Function: function, + Name: detail, + RequestBody: requestBody, + Status: 0, + CreatedAt: time.Now().Unix(), + } + operationLogID, err := systemservice.InsertOperation(req, logger) if err != nil { logger.Errorf("InsertOperation err:%v", err) } -- Gitee From 2217df2c147e51765006a0fb83b4a507ede4ace5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 15:15:44 +0800 Subject: [PATCH 1366/2647] modify code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/gitlab_workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 56649a8d..526631ac 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -346,7 +346,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo gtem.yamlServiceChanged = serviceChangeds return len(serviceChangeds) != 0, nil } - return MatchChanges(hookRepo, changedFiles), nil + return true, nil } return false, nil -- Gitee From 47a93a7720a5fa45cac013e5aa74f0a459138599 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 15:45:40 +0800 Subject: [PATCH 1367/2647] optimize code Signed-off-by: liu deyi --- .../service/webhook/gitlab_testing_task.go | 15 ++++----------- .../service/webhook/gitlab_workflow_task.go | 6 +++--- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index 9c36e852..daa8a368 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -110,24 +110,17 @@ func (gtem gitlabTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHoo return false, nil } isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { return false, nil } if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { return false, nil } } - hookRepo.Branch = getBranchFromRef(ev.Ref) - var changedFiles []string - for _, commit := range ev.Commits { - changedFiles = append(changedFiles, commit.Added...) - changedFiles = append(changedFiles, commit.Removed...) - changedFiles = append(changedFiles, commit.Modified...) - } - - return MatchChanges(hookRepo, changedFiles), nil + hookRepo.Branch = ev.Project.DefaultBranch + return true, nil } return false, nil diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 526631ac..936199af 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -306,17 +306,17 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } } else { isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { return false, nil } if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { return false, nil } } } - hookRepo.Branch = getBranchFromRef(ev.Ref) + hookRepo.Branch = ev.Project.DefaultBranch var changedFiles []string detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) -- Gitee From 4aea30a8f85b977af208b6941d07144739120356 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 21 Jan 2022 16:53:11 +0800 Subject: [PATCH 1368/2647] edit format Signed-off-by: ddh27 --- .../aslan/core/common/repository/mongodb/task.go | 15 +++++++-------- .../aslan/core/workflow/handler/router.go | 2 +- .../workflow/service/workflow/pipeline_task.go | 9 ++++----- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index f3365ca8..fca649a0 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -36,7 +36,7 @@ type ListTaskOption struct { PipelineName string PipelineNames []string Status config.Status - Statuss []string + Statuses []string Team string TeamID int Limit int @@ -268,8 +268,8 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) if option.PipelineName != "" { query["pipeline_name"] = option.PipelineName } - if len(option.Statuss) > 0 { - query["status"] = bson.M{"$in": option.Statuss} + if len(option.Statuses) > 0 { + query["status"] = bson.M{"$in": option.Statuses} } else if option.Status != "" { query["status"] = option.Status } @@ -400,7 +400,7 @@ func (c *TaskColl) Create(args *task.Task) error { type CountTaskOption struct { PipelineNames []string - Statuss []string + Statuses []string Status config.TaskStatus TaskCreators []string Committers []string @@ -422,8 +422,8 @@ func (c *TaskColl) Count(option *CountTaskOption) (ret int, err error) { if option.PipelineNames != nil { query["pipeline_name"] = bson.M{"$in": option.PipelineNames} } - if len(option.Statuss) > 0 { - query["status"] = bson.M{"$in": option.Statuss} + if len(option.Statuses) > 0 { + query["status"] = bson.M{"$in": option.Statuses} } else if option.Status != "" { query["status"] = option.Status } @@ -613,8 +613,7 @@ func (c *TaskColl) ArchiveHistoryPipelineTask(pipelineName string, taskType conf return err } -func (c *TaskColl) DistinctFeildsPipelineTask(fieldName, projectName, pipelineName string, typeString config.PipelineType, isNeedAllData bool) ([]interface{}, error) { - +func (c *TaskColl) DistinctFieldsPipelineTask(fieldName, projectName, pipelineName string, typeString config.PipelineType, isNeedAllData bool) ([]interface{}, error) { query := bson.M{"product_name": projectName, "pipeline_name": pipelineName, "type": typeString, "is_deleted": false} if isNeedAllData { createTime := timeutil.BeginningOfDay().AddDate(0, -3, 0).Unix() diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 0443c9b5..5c671691 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -125,7 +125,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowtask.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTask) workflowtask.PUT("", gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) workflowtask.GET("/max/:max/start/:start/pipelines/:name", ListWorkflowTasksResult) - workflowtask.GET("filters/pipelines/:name", GetFiltersPipeline) + workflowtask.GET("/filters/pipelines/:name", GetFiltersPipeline) workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) workflowtask.POST("/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTask) workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index b903c1d7..f282cbd4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -317,8 +317,8 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer serviceNameFiltersMap[svc] = nil } case "taskStatus": - listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuss: filters, Detail: true, Type: typeString} - countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Statuss: filters, Type: typeString} + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuses: filters, Detail: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Statuses: filters, Type: typeString} default: listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Detail: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} @@ -400,21 +400,20 @@ func GetPipelineTaskV2(taskID int64, pipelineName string, typeString config.Pipe } func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeString config.PipelineType, log *zap.SugaredLogger) ([]interface{}, error) { - resp := []interface{}{} var err error fieldName := "" switch querytype { case "creator": fieldName = "task_creator" - resp, err = commonrepo.NewTaskColl().DistinctFeildsPipelineTask(fieldName, projectName, pipelineName, typeString, false) + resp, err = commonrepo.NewTaskColl().DistinctFieldsPipelineTask(fieldName, projectName, pipelineName, typeString, false) if err != nil { log.Errorf("[%s] DistinctFeildsPipelineTask fieldName: %s error: %s", fieldName, pipelineName, err) return resp, e.ErrGetTask } case "committer": fieldName = "workflow_args.committer" - resp, err = commonrepo.NewTaskColl().DistinctFeildsPipelineTask(fieldName, projectName, pipelineName, typeString, false) + resp, err = commonrepo.NewTaskColl().DistinctFieldsPipelineTask(fieldName, projectName, pipelineName, typeString, false) if err != nil { log.Errorf("[%s] DistinctFeildsPipelineTask fieldName: %s error: %s", fieldName, pipelineName, err) return resp, e.ErrGetTask -- Gitee From 91dd46b0f492f29e72982ccdab5ea4ab6d48e91e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 17:17:13 +0800 Subject: [PATCH 1369/2647] optimize code Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gitlab_workflow_task.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 936199af..83e548bc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -182,6 +182,8 @@ type gitlabPushEventMatcher struct { yamlServiceChanged []BuildServices } +const commitID = "0000000000000000000000000000000000000000" + func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { @@ -226,6 +228,9 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( return false, err } + if ev.Before == commitID || ev.After == commitID { + return true, nil + } // compare接口获取两个commit之间的最终的改动 diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) if err != nil { @@ -334,7 +339,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo // compare接口获取两个commit之间的最终的改动 diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) if err != nil { - gtem.log.Errorf("Failed to get push event diffs, error: %s", err) + gtem.log.Errorf("Failed to get tag event diffs, error: %s", err) return false, err } for _, diff := range diffs { -- Gitee From b2b77dc4e3429cbd0e15cd68a67edadfcf572acb Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 17:23:19 +0800 Subject: [PATCH 1370/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/webhook/gitlab_workflow_task.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 83e548bc..4ae9a4df 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -182,8 +182,6 @@ type gitlabPushEventMatcher struct { yamlServiceChanged []BuildServices } -const commitID = "0000000000000000000000000000000000000000" - func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { @@ -228,9 +226,6 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( return false, err } - if ev.Before == commitID || ev.After == commitID { - return true, nil - } // compare接口获取两个commit之间的最终的改动 diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) if err != nil { @@ -283,6 +278,8 @@ func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( return args } +const commitID = "0000000000000000000000000000000000000000" + type gitlabTagEventMatcher struct { log *zap.SugaredLogger workflow *commonmodels.Workflow @@ -323,6 +320,10 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo hookRepo.Branch = ev.Project.DefaultBranch + if ev.Before == commitID || ev.After == commitID { + return true, nil + } + var changedFiles []string detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) if err != nil { -- Gitee From 74d036a5fe4828725ba2cd8a22a9ffcd53ec006a Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 21 Jan 2022 17:40:40 +0800 Subject: [PATCH 1371/2647] permission Signed-off-by: mouuii --- .../core/repository/mongodb/role_binding.go | 8 +- .../policy/core/service/permission.go | 86 ++++++++++++------- pkg/microservice/policy/core/service/role.go | 12 +++ .../policy/core/service/role_binding.go | 46 +++++----- 4 files changed, 95 insertions(+), 57 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 16ec80f8..c02dbf9c 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -204,23 +204,23 @@ func (c *RoleBindingColl) UpdateOrCreate(obj *models.RoleBinding) error { return err } -type RoleBindingMeta struct { +type RoleBinding struct { Uid string `json:"uid"` Namespace string `json:"namespace"` } type ListRoleBindingsOpt struct { - RoleBindingMetas []RoleBindingMeta + RoleBindings []RoleBinding } func (c *RoleBindingColl) ListByRoleBindingOpt(opt ListRoleBindingsOpt) ([]*models.RoleBinding, error) { var res []*models.RoleBinding - if len(opt.RoleBindingMetas) == 0 { + if len(opt.RoleBindings) == 0 { return nil, nil } condition := bson.A{} - for _, meta := range opt.RoleBindingMetas { + for _, meta := range opt.RoleBindings { condition = append(condition, bson.M{ "namespace": meta.Namespace, "subjects.uid": meta.Uid, diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 3a7ae0de..699b4af6 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -20,38 +20,22 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" - "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" ) // GetPermission user's permission for frontend -func GetPermission(ns, uid string, log *zap.SugaredLogger) (map[string][]string, error) { - roles := []*models.Role{} - var metas []mongodb.RoleBindingMeta - roleBindingReadOnlyMeta := mongodb.RoleBindingMeta{ - Uid: "*", - Namespace: ns, - } - roleBindingsAdminMeta := mongodb.RoleBindingMeta{ - Uid: uid, - Namespace: "*", - } - roleBindingCommonMeta := mongodb.RoleBindingMeta{ - Uid: uid, - Namespace: ns, - } - metas = append(metas, roleBindingReadOnlyMeta, roleBindingsAdminMeta, roleBindingCommonMeta) - roleBindings, err := mongodb.NewRoleBindingColl().ListByRoleBindingOpt(mongodb.ListRoleBindingsOpt{RoleBindingMetas: metas}) +func GetPermission(ns, uid string, logger *zap.SugaredLogger) (map[string][]string, error) { + //1.get user's all role in some project + roleBindings, err := ListUserAllRoleBindings(ns, uid) if err != nil { + logger.Errorf("ListUserAllRoleBindings err:%s", err) return nil, err } - for _, v := range roleBindings { - tmpRoles, err := mongodb.NewRoleColl().ListBySpaceAndName(v.RoleRef.Namespace, v.RoleRef.Name) - if err != nil { - continue - } - roles = append(roles, tmpRoles...) + roles, err := ListUserAllRolesByRoleBindings(roleBindings) + if err != nil { + return nil, err } + //2.get user's all rules rolesSet := map[string]sets.String{} rolesResp := map[string][]string{} for _, role := range roles { @@ -72,17 +56,19 @@ func GetPermission(ns, uid string, log *zap.SugaredLogger) (map[string][]string, return rolesResp, nil } -func GetResourcesPermission(uid string, projectName string, resourceType string, resources []string, log *zap.SugaredLogger) (map[string][]string, error) { - +//GetResourcesPermission get resources action list for frontend to show icon +func GetResourcesPermission(uid string, projectName string, resourceType string, resources []string, logger *zap.SugaredLogger) (map[string][]string, error) { // 1. get all policyBindings - policyBindings, err := ListPolicyBindings(projectName, uid, log) + policyBindings, err := ListPolicyBindings(projectName, uid, logger) if err != nil { + logger.Errorf("ListPolicyBindings err:%s", err) return nil, err } var policies []*Policy for _, v := range policyBindings { - policy, err := GetPolicy(projectName, v.Policy, log) + policy, err := GetPolicy(projectName, v.Policy, logger) if err != nil { + logger.Warnf("GetPolicy err:%s", err) continue } policies = append(policies, policy) @@ -105,6 +91,48 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, } } + // 2. get all roleBindings + roleBindings, err := ListUserAllRoleBindings(projectName, uid) + if err != nil { + logger.Errorf("ListUserAllRoleBindings err:%s", err) + return nil, err + } + roles, err := ListUserAllRolesByRoleBindings(roleBindings) + if err != nil { + logger.Errorf("ListUserAllRolesByRoleBindings err:%s", err) + return nil, err + } + for _, role := range roles { + for _, rule := range role.Rules { + if rule.Resources[0] == resourceType && resourceType != string(config.ResourceTypeProduct) { + for k, v := range resourceM { + resourceM[k] = v.Insert(rule.Verbs...) + } + } + //if rule.Resources[0] == resourceType && resourceType == string(config.ResourceTypeProduct) { + // //get cluster map + // clusterMap := make(map[string]*commonmodels.K8SCluster) + // clusters, err := commonrepo.NewK8SClusterColl().List(nil) + // if err != nil { + // log.Errorf("Failed to list clusters in db, err: %s", err) + // return nil, err + // } + // for _, cls := range clusters { + // clusterMap[cls.ID.Hex()] = cls + // } + // for k, v := range resourceM { + // var isProduct bool + // if product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: projectName, EnvName: k}); err != nil { + // if cluster, ok := clusterMap[product.ClusterID]; ok { + // isProduct = cluster.Production + // } + // } + // ruleIsProduct rule.MatchAttributes[0].Value + // } + //} + } + } + resourceRes := make(map[string][]string) for k, v := range resourceM { resourceRes[k] = v.List() diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 57e1ccce..9990f1cb 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -150,3 +150,15 @@ func DeleteRoles(names []string, projectName string, logger *zap.SugaredLogger) return mongodb.NewRoleBindingColl().DeleteByRoles(names, projectName) } + +func ListUserAllRolesByRoleBindings(roleBindings []*models.RoleBinding) ([]*models.Role, error) { + var roles []*models.Role + for _, v := range roleBindings { + tmpRoles, err := mongodb.NewRoleColl().ListBySpaceAndName(v.RoleRef.Namespace, v.RoleRef.Name) + if err != nil { + continue + } + roles = append(roles, tmpRoles...) + } + return roles, nil +} diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index de4554be..22850db7 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -84,30 +84,6 @@ func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, err return roleBindings, nil } -func ListRoleBindingsByRole(ns, roleName string, publicRole bool, _ *zap.SugaredLogger) ([]*RoleBinding, error) { - var roleBindings []*RoleBinding - - roleNamespace := ns - if publicRole { - roleNamespace = "" - } - modelRoleBindings, err := mongodb.NewRoleBindingColl().List(&mongodb.ListOptions{RoleName: roleName, RoleNamespace: roleNamespace}) - if err != nil { - return nil, err - } - - for _, v := range modelRoleBindings { - roleBindings = append(roleBindings, &RoleBinding{ - Name: v.Name, - Role: v.RoleRef.Name, - UID: v.Subjects[0].UID, - Public: v.RoleRef.Namespace == "", - }) - } - - return roleBindings, nil -} - func DeleteRoleBinding(name string, projectName string, _ *zap.SugaredLogger) error { return mongodb.NewRoleBindingColl().Delete(name, projectName) } @@ -160,3 +136,25 @@ func ensureRoleBindingName(ns string, rb *RoleBinding) { rb.Name = config.RoleBindingNameFromUIDAndRole(rb.UID, setting.RoleType(rb.Role), nsRole) } + +func ListUserAllRoleBindings(projectName, uid string) ([]*models.RoleBinding, error) { + var rbs []mongodb.RoleBinding + roleBindingReadOnly := mongodb.RoleBinding{ + Uid: "*", + Namespace: projectName, + } + roleBindingsAdmin := mongodb.RoleBinding{ + Uid: uid, + Namespace: "*", + } + roleBindingCommon := mongodb.RoleBinding{ + Uid: uid, + Namespace: projectName, + } + rbs = append(rbs, roleBindingReadOnly, roleBindingsAdmin, roleBindingCommon) + roleBindings, err := mongodb.NewRoleBindingColl().ListByRoleBindingOpt(mongodb.ListRoleBindingsOpt{RoleBindings: rbs}) + if err != nil { + return nil, err + } + return roleBindings, nil +} -- Gitee From a9d2479249f61a1c9f7bb9fefeb564429b984c68 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 21 Jan 2022 17:45:27 +0800 Subject: [PATCH 1372/2647] permission Signed-off-by: mouuii --- .../policy/core/service/permission.go | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 699b4af6..468f41ec 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -19,8 +19,6 @@ package service import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" ) // GetPermission user's permission for frontend @@ -104,35 +102,13 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, } for _, role := range roles { for _, rule := range role.Rules { - if rule.Resources[0] == resourceType && resourceType != string(config.ResourceTypeProduct) { + if rule.Resources[0] == resourceType { for k, v := range resourceM { resourceM[k] = v.Insert(rule.Verbs...) } } - //if rule.Resources[0] == resourceType && resourceType == string(config.ResourceTypeProduct) { - // //get cluster map - // clusterMap := make(map[string]*commonmodels.K8SCluster) - // clusters, err := commonrepo.NewK8SClusterColl().List(nil) - // if err != nil { - // log.Errorf("Failed to list clusters in db, err: %s", err) - // return nil, err - // } - // for _, cls := range clusters { - // clusterMap[cls.ID.Hex()] = cls - // } - // for k, v := range resourceM { - // var isProduct bool - // if product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: projectName, EnvName: k}); err != nil { - // if cluster, ok := clusterMap[product.ClusterID]; ok { - // isProduct = cluster.Production - // } - // } - // ruleIsProduct rule.MatchAttributes[0].Value - // } - //} } } - resourceRes := make(map[string][]string) for k, v := range resourceM { resourceRes[k] = v.List() -- Gitee From 535842cd79e8fad3dd87a75eb02669d0764f6bc7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 18:09:02 +0800 Subject: [PATCH 1373/2647] pull tag code if tag trigger webhook Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/workflow.go | 1 + .../workflow/service/webhook/github_workflow_task.go | 11 ++++++++++- .../workflow/service/webhook/gitlab_workflow_task.go | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3ed775c9..d70b953a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -77,6 +77,7 @@ type MainHookRepo struct { RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` Branch string `bson:"branch" json:"branch"` + Tag string `bson:"tag" json:"tag"` MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` CodehostID int `bson:"codehost_id" json:"codehost_id"` Events []config.HookEventType `bson:"events" json:"events"` diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index f52e8b0e..8427ed7f 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -91,6 +91,14 @@ func getBranchFromRef(ref string) string { return ref } +func getTagFromRef(ref string) string { + prefix := "refs/tags/" + if strings.HasPrefix(ref, prefix) { + return ref[len(prefix):] + } + + return ref +} func (gpem *githubPushEventMatcher) UpdateTaskArgs( product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, ) *commonmodels.WorkflowTaskArgs { @@ -246,7 +254,7 @@ func (gtem githubTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo return false, nil } } - hookRepo.Branch = getBranchFromRef(*ev.Ref) + hookRepo.Tag = getTagFromRef(*ev.Ref) return true, nil } @@ -265,6 +273,7 @@ func (gtem githubTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, RepoName: hookRepo.RepoName, RepoOwner: hookRepo.RepoOwner, Branch: hookRepo.Branch, + Tag: hookRepo.Tag, }) return args diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 4ae9a4df..7b472718 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -318,8 +318,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } } - hookRepo.Branch = ev.Project.DefaultBranch - + hookRepo.Tag = getTagFromRef(ev.Ref) if ev.Before == commitID || ev.After == commitID { return true, nil } @@ -381,6 +380,7 @@ func (gtem gitlabTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, RepoName: hookRepo.RepoName, RepoOwner: hookRepo.RepoOwner, Branch: hookRepo.Branch, + Tag: hookRepo.Tag, }) return args -- Gitee From 0f25a81db7e7a85944d47433bde5e3856dc36b44 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 21 Jan 2022 18:43:21 +0800 Subject: [PATCH 1374/2647] edit format Signed-off-by: ddh27 --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index f282cbd4..a87f719a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -343,8 +343,7 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer if buildStage != nil { for serviceName := range buildStage.SubTasks { if queryType == "serviceName" { - _, ok := serviceNameFiltersMap[serviceName] - if ok { + if _, ok := serviceNameFiltersMap[serviceName]; ok { existSvc = true } } @@ -353,8 +352,7 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer } else if deployStage != nil { for serviceName := range deployStage.SubTasks { if queryType == "serviceName" { - _, ok := serviceNameFiltersMap[serviceName] - if ok { + if _, ok := serviceNameFiltersMap[serviceName]; ok { existSvc = true } } -- Gitee From 7466658a990bc02161751c094ea41e49e5aaa46e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 18:54:17 +0800 Subject: [PATCH 1375/2647] modify github webhook Signed-off-by: liu deyi --- .../core/workflow/service/webhook/github.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 93326430..2acaffa6 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -401,14 +401,20 @@ func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID st case *github.PullRequestEvent: err = TriggerTestByGithubEvent(et, requestID, log) if err != nil { - log.Errorf("TriggerTestByGithubEvent error: %v", err) + log.Errorf("TriggerTestByGithubEvent error: %s", err) return e.ErrGithubWebHook.AddErr(err) } case *github.PushEvent: err = TriggerTestByGithubEvent(et, requestID, log) if err != nil { - log.Infof("TriggerTestByGithubEvent error: %v", err) + log.Infof("TriggerTestByGithubEvent error: %s", err) + return e.ErrGithubWebHook.AddErr(err) + } + case *github.CreateEvent: + err = TriggerTestByGithubEvent(et, requestID, log) + if err != nil { + log.Infof("TriggerTestByGithubEvent error: %s", err) return e.ErrGithubWebHook.AddErr(err) } } @@ -476,6 +482,12 @@ func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, l log.Infof("pushEventToPipelineTasks error: %v", err) return e.ErrGithubWebHook.AddErr(err) } + case *github.CreateEvent: + err = TriggerWorkflowByGithubEvent(et, baseURI, deliveryID, requestID, log) + if err != nil { + log.Errorf("prEventToPipelineTasks error: %s", err) + return e.ErrGithubWebHook.AddErr(err) + } } return nil } -- Gitee From e7f92384a9407bd889a46c1f51321776dccef546 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 21 Jan 2022 18:55:23 +0800 Subject: [PATCH 1376/2647] Fixed concurrency issues with Aslan and WarpDrive helm Install Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 52 ++----------------- .../core/service/taskplugin/deploy.go | 8 ++- 2 files changed, 11 insertions(+), 49 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 12a33b9f..3759bd40 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -33,7 +33,6 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/strvals" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -2319,52 +2318,11 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha chartSpec.Replace = true } - done := make(chan bool) - go func(chan bool) { - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { - err = errors.WithMessagef( - err, - "failed to Install helm chart %s/%s", - namespace, serviceObj.ServiceName) - done <- false - } else { - done <- true - } - }(done) - - pendingStatusProcess := func(typ string) error { - hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) - if errHistory != nil { - err = errors.WithMessagef( - err, - "failed to ListReleaseHistory: %s,error:%s", - chartSpec.ReleaseName, errHistory) - return err - } - if len(hrs) > 0 { - releaseutil.Reverse(hrs, releaseutil.SortByRevision) - rel := hrs[0] - if rel.Info.Status.IsPending() { - secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, rel.Name, rel.Version) - deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, kubecli) - if deleteErr != nil { - err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) - return err - } - } - } - if err != nil { - err = fmt.Errorf("failed to install %s:%s", typ, err) - } - return err - } - select { - case d := <-done: - if !d { - err = pendingStatusProcess("normal") - } - case <-time.After(chartSpec.Timeout + 5*time.Second): - err = pendingStatusProcess("timeout") + if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to Install helm chart %s/%s", + namespace, serviceObj.ServiceName) } return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 8f043aca..f2974c10 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -566,7 +566,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } } if err != nil { - err = fmt.Errorf("failed to install %s:%s", typ, err) + return err + } else { + if typ == "timeout" { + err = fmt.Errorf("failed to upgrade %s", typ) + } } return err } @@ -596,7 +600,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if !d { err = pendingStatusProcess("normal") } - case <-time.After(chartSpec.Timeout + 5*time.Second): + case <-time.After(chartSpec.Timeout + 30*time.Second): err = pendingStatusProcess("timeout") } if err != nil { -- Gitee From 20bd7d15eb8ba8835fd6e39bdb551c25933b64e9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 21 Jan 2022 19:38:00 +0800 Subject: [PATCH 1377/2647] optimize code Signed-off-by: liu deyi --- .../core/workflow/service/webhook/github_testing_task.go | 6 +++--- .../core/workflow/service/webhook/github_workflow_task.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go index ca902589..f27b978c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go @@ -149,16 +149,16 @@ func (gtem githubTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHoo return false, nil } isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + if !isRegular && hookRepo.Branch != *ev.Repo.DefaultBranch { return false, nil } if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.Repo.DefaultBranch); !matched { return false, nil } } - hookRepo.Branch = getBranchFromRef(*ev.Ref) + hookRepo.Branch = *ev.Repo.DefaultBranch return true, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index cd53d9f1..bded5564 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -247,12 +247,12 @@ func (gtem githubTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + if !isRegular && hookRepo.Branch != *ev.Repo.DefaultBranch { return false, nil } if isRegular { // Do not use regexp.MustCompile to avoid panic - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.Repo.DefaultBranch); !matched { return false, nil } } -- Gitee From 350cc95cdef27dad76283ed3af3d08891584f98f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 10:57:52 +0800 Subject: [PATCH 1378/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/gitlab_workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index f3b60881..87e365f5 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -293,6 +293,7 @@ type gitlabTagEventMatcher struct { func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gtem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + gtem.log.Infof("hookRepo.events:%+v", hookRepo.Events) if !EventConfigured(hookRepo, config.HookEventTag) { return false, nil } -- Gitee From 3ba9f7831ba187975b14f4e889bf6cb65facf947 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 11:10:38 +0800 Subject: [PATCH 1379/2647] resource bundles Signed-off-by: mouuii --- .../aslan/core/environment/service/bundle.go | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 2914e097..91447afe 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -18,9 +18,7 @@ package service import ( "fmt" - "strconv" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -43,17 +41,6 @@ func GetBundleResources() ([]*resourceSpec, error) { return nil, err } - clusterMap := make(map[string]*models.K8SCluster) - clusters, err := mongodb.NewK8SClusterColl().List(nil) - if err != nil { - log.Errorf("Failed to list clusters in db, err: %s", err) - return nil, err - } - - for _, cls := range clusters { - clusterMap[cls.ID.Hex()] = cls - } - // get labels by workflow resources ids var resources []labeldb.Resource for _, env := range envs { @@ -70,20 +57,11 @@ func GetBundleResources() ([]*resourceSpec, error) { } for _, env := range envs { - clusterID := env.ClusterID - production := false - cluster, ok := clusterMap[clusterID] - if ok { - production = cluster.Production - } - resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeProduct, env.ProductName, env.EnvName) resourceSpec := &resourceSpec{ ResourceID: env.EnvName, ProjectName: env.ProductName, - Spec: map[string]interface{}{ - "production": strconv.FormatBool(production), - }, + Spec: map[string]interface{}{}, } if labels, ok := labelsResp.Labels[resourceKey]; ok { for _, v := range labels { -- Gitee From a7d2627f468150a26734b46d60a0e0390c6f1154 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 11:14:54 +0800 Subject: [PATCH 1380/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/webhook/gitlab_workflow_task.go | 9 ++++----- .../aslan/core/workflow/service/webhook/utils.go | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 87e365f5..a0c86e44 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -98,7 +98,7 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) } gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) if gmem.isYaml { - serviceChangeds := ServicesMatchChangesFiles(gmem.trigger.Rules.MatchFolders, hookRepo, changedFiles) + serviceChangeds := ServicesMatchChangesFiles(gmem.trigger.Rules.MatchFolders, changedFiles) gmem.yamlServiceChanged = serviceChangeds return len(serviceChangeds) != 0, nil } @@ -238,7 +238,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( changedFiles = append(changedFiles, diff.OldPath) } if gpem.isYaml { - serviceChangeds := ServicesMatchChangesFiles(gpem.trigger.Rules.MatchFolders, hookRepo, changedFiles) + serviceChangeds := ServicesMatchChangesFiles(gpem.trigger.Rules.MatchFolders, changedFiles) gpem.yamlServiceChanged = serviceChangeds return len(serviceChangeds) != 0, nil } @@ -293,14 +293,13 @@ type gitlabTagEventMatcher struct { func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gtem.event if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - gtem.log.Infof("hookRepo.events:%+v", hookRepo.Events) if !EventConfigured(hookRepo, config.HookEventTag) { return false, nil } if gtem.isYaml { refFlag := false for _, ref := range gtem.trigger.Rules.Branchs { - if matched, _ := regexp.MatchString(ref, getBranchFromRef(ev.Ref)); matched { + if matched, _ := regexp.MatchString(ref, ev.Project.DefaultBranch); matched { refFlag = true break } @@ -349,7 +348,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo changedFiles = append(changedFiles, diff.OldPath) } if gtem.isYaml { - serviceChangeds := ServicesMatchChangesFiles(gtem.trigger.Rules.MatchFolders, hookRepo, changedFiles) + serviceChangeds := ServicesMatchChangesFiles(gtem.trigger.Rules.MatchFolders, changedFiles) gtem.yamlServiceChanged = serviceChangeds return len(serviceChangeds) != 0, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index a5c700da..0b6ce917 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -513,7 +513,7 @@ func EventConfigured(m *commonmodels.MainHookRepo, event config.HookEventType) b return false } -func ServicesMatchChangesFiles(mf *MatchFoldersElem, m *commonmodels.MainHookRepo, files []string) []BuildServices { +func ServicesMatchChangesFiles(mf *MatchFoldersElem, files []string) []BuildServices { resMactchSvr := []BuildServices{} var wg sync.WaitGroup var mutex sync.Mutex -- Gitee From 9981724c45937a21031fa6fa1997e87d54a52952 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 11:18:54 +0800 Subject: [PATCH 1381/2647] fmt Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/permission.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index 41f3741b..0e017783 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -39,7 +39,7 @@ func GetUserPermission(c *gin.Context) { type GetUserResourcesPermissionReq struct { ProjectName string `json:"project_name" form:"project_name"` - Uid string `json:"uid" form:"uid"` + Uid string `json:"uid" form:"uid"` Resources []string `json:"resources" form:"resources"` ResourceType string `json:"resource_type" form:"resource_type"` } -- Gitee From 54470f39a8f9324ac221bf02298b684735c3edd6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 11:32:29 +0800 Subject: [PATCH 1382/2647] modify annotation Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/service/webhook/github.go | 2 +- .../core/workflow/service/webhook/gitlab_workflow_task.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 2acaffa6..4459176e 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -485,7 +485,7 @@ func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, l case *github.CreateEvent: err = TriggerWorkflowByGithubEvent(et, baseURI, deliveryID, requestID, log) if err != nil { - log.Errorf("prEventToPipelineTasks error: %s", err) + log.Errorf("tagEventToPipelineTasks error: %s", err) return e.ErrGithubWebHook.AddErr(err) } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index a0c86e44..47005935 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -320,6 +320,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } hookRepo.Tag = getTagFromRef(ev.Ref) + // if ev.Before == commitID || ev.After == commitID { return true, nil } @@ -337,7 +338,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo return false, err } - // compare接口获取两个commit之间的最终的改动 + // The compare interface gets the final changes between the two commits diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) if err != nil { gtem.log.Errorf("Failed to get tag event diffs, error: %s", err) -- Gitee From 271cd0d2874399653c77ee98556841dd20d40b9a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 11:53:51 +0800 Subject: [PATCH 1383/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/dashboard/service/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/build.go b/pkg/microservice/aslan/core/stat/dashboard/service/build.go index 219e98c0..d2a0678a 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/build.go @@ -19,9 +19,9 @@ package service import ( "sort" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb" ) -- Gitee From 86b775e391acb4f3055afefd29c7b58eebb1f275 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 11:55:22 +0800 Subject: [PATCH 1384/2647] modify log output Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/github.go | 4 ++-- .../core/workflow/service/webhook/gitlab_workflow_task.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 4459176e..03584e5b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -408,13 +408,13 @@ func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID st case *github.PushEvent: err = TriggerTestByGithubEvent(et, requestID, log) if err != nil { - log.Infof("TriggerTestByGithubEvent error: %s", err) + log.Errorf("TriggerTestByGithubEvent error: %s", err) return e.ErrGithubWebHook.AddErr(err) } case *github.CreateEvent: err = TriggerTestByGithubEvent(et, requestID, log) if err != nil { - log.Infof("TriggerTestByGithubEvent error: %s", err) + log.Errorf("TriggerTestByGithubEvent error: %s", err) return e.ErrGithubWebHook.AddErr(err) } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 47005935..09a9244c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -320,7 +320,6 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } hookRepo.Tag = getTagFromRef(ev.Ref) - // if ev.Before == commitID || ev.After == commitID { return true, nil } -- Gitee From a2b41de9a8fcc9434b7d3e2f5dae923237e5603f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 14:15:14 +0800 Subject: [PATCH 1385/2647] add admin logic Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/permission.go | 4 ++-- pkg/microservice/policy/core/service/permission.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index 0e017783..699555ad 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -40,8 +40,8 @@ func GetUserPermission(c *gin.Context) { type GetUserResourcesPermissionReq struct { ProjectName string `json:"project_name" form:"project_name"` Uid string `json:"uid" form:"uid"` - Resources []string `json:"resources" form:"resources"` - ResourceType string `json:"resource_type" form:"resource_type"` + Resources []string `json:"resources" form:"resources"` + ResourceType string `json:"resource_type" form:"resource_type"` } func GetUserResourcesPermission(c *gin.Context) { diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 468f41ec..d5593eec 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -101,6 +101,10 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, return nil, err } for _, role := range roles { + if role.Name == "admin" || role.Name == "project-admin" { + resourceM["*"] = sets.NewString("*") + break + } for _, rule := range role.Rules { if rule.Resources[0] == resourceType { for k, v := range resourceM { -- Gitee From 784ae67a02aedefecf330b9b566316a9a2fdfea5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 14:17:55 +0800 Subject: [PATCH 1386/2647] add admin logic Signed-off-by: mouuii --- pkg/microservice/policy/core/service/permission.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index d5593eec..89006d37 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -19,6 +19,8 @@ package service import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/setting" ) // GetPermission user's permission for frontend @@ -101,7 +103,7 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, return nil, err } for _, role := range roles { - if role.Name == "admin" || role.Name == "project-admin" { + if role.Name == string(setting.SystemAdmin) || role.Name == string(setting.ProjectAdmin) { resourceM["*"] = sets.NewString("*") break } -- Gitee From 89c19454b298cbb315ccc84a7bdb800ca0e9b81d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 14:32:31 +0800 Subject: [PATCH 1387/2647] modify Copyright date Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/dashboard/handler/build.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/handler/overview.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/handler/test.go | 2 +- .../aslan/core/stat/dashboard/repository/models/build.go | 2 +- .../aslan/core/stat/dashboard/repository/models/deploy.go | 2 +- .../aslan/core/stat/dashboard/repository/mongodb/deploy.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/service/build.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/service/deploy.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/service/overview.go | 2 +- pkg/microservice/aslan/core/stat/dashboard/service/test.go | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/build.go b/pkg/microservice/aslan/core/stat/dashboard/handler/build.go index 2ec851dd..2fdc9b0b 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/build.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go index c22a149c..a5efa28a 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go b/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go index d916e9b4..c92c7827 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/test.go b/pkg/microservice/aslan/core/stat/dashboard/handler/test.go index 2c5b5a62..ce219cf3 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/test.go +++ b/pkg/microservice/aslan/core/stat/dashboard/handler/test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go b/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go index b1ca70d7..67b30b12 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go index be476cff..2baaaacb 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go index fe6988f7..cc58a44c 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/build.go b/pkg/microservice/aslan/core/stat/dashboard/service/build.go index d2a0678a..86fdcbfd 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/build.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/build.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go b/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go index 2c5fe045..df634166 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/overview.go b/pkg/microservice/aslan/core/stat/dashboard/service/overview.go index 5cb289b5..b7790e7d 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/overview.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/overview.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/test.go b/pkg/microservice/aslan/core/stat/dashboard/service/test.go index a104dbde..04ff69e5 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/test.go +++ b/pkg/microservice/aslan/core/stat/dashboard/service/test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 5a956250bd667389b1f693f1cd5a7c58faea05a6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 14:44:16 +0800 Subject: [PATCH 1388/2647] optimize code Signed-off-by: liu deyi --- .../core/workflow/service/webhook/github.go | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 03584e5b..8eef4a0c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -398,25 +398,14 @@ func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID st } switch et := event.(type) { - case *github.PullRequestEvent: - err = TriggerTestByGithubEvent(et, requestID, log) - if err != nil { - log.Errorf("TriggerTestByGithubEvent error: %s", err) - return e.ErrGithubWebHook.AddErr(err) - } - - case *github.PushEvent: - err = TriggerTestByGithubEvent(et, requestID, log) - if err != nil { - log.Errorf("TriggerTestByGithubEvent error: %s", err) - return e.ErrGithubWebHook.AddErr(err) - } - case *github.CreateEvent: - err = TriggerTestByGithubEvent(et, requestID, log) - if err != nil { + case *github.PullRequestEvent, *github.PushEvent, *github.CreateEvent: + if err = TriggerTestByGithubEvent(et, requestID, log); err != nil { log.Errorf("TriggerTestByGithubEvent error: %s", err) return e.ErrGithubWebHook.AddErr(err) } + default: + log.Warn("Unsupported event type") + return nil } return nil } -- Gitee From f9118443d1d742b36f7b642e6fcf3c609923f547 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 15:14:19 +0800 Subject: [PATCH 1389/2647] log Signed-off-by: mouuii --- .../aslan/core/environment/handler/bundle.go | 2 +- .../aslan/core/environment/service/bundle.go | 10 ++++++---- pkg/microservice/aslan/core/label/config/consts.go | 4 ++-- pkg/microservice/policy/core/service/policy.go | 5 ++--- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/bundle.go b/pkg/microservice/aslan/core/environment/handler/bundle.go index aa8dac3b..af39f02c 100644 --- a/pkg/microservice/aslan/core/environment/handler/bundle.go +++ b/pkg/microservice/aslan/core/environment/handler/bundle.go @@ -27,5 +27,5 @@ func GetBundleResources(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetBundleResources() + ctx.Resp, ctx.Err = service.GetBundleResources(ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 91447afe..0f27dd6e 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -19,11 +19,12 @@ package service import ( "fmt" + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" - "github.com/koderover/zadig/pkg/tool/log" ) type resourceSpec struct { @@ -32,12 +33,12 @@ type resourceSpec struct { Spec map[string]interface{} `json:"spec"` } -func GetBundleResources() ([]*resourceSpec, error) { +func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { var res []*resourceSpec envs, err := mongodb.NewProductColl().List(nil) if err != nil { - log.Errorf("Failed to list envs, err: %s", err) + logger.Errorf("Failed to list envs, err: %s", err) return nil, err } @@ -51,8 +52,9 @@ func GetBundleResources() ([]*resourceSpec, error) { } resources = append(resources, resource) } - labelsResp, err := service.ListLabelsByResources(resources, nil) + labelsResp, err := service.ListLabelsByResources(resources, logger) if err != nil { + logger.Errorf("ListLabelsByResources err:%s", err) return nil, err } diff --git a/pkg/microservice/aslan/core/label/config/consts.go b/pkg/microservice/aslan/core/label/config/consts.go index 6910afe2..8bb25327 100644 --- a/pkg/microservice/aslan/core/label/config/consts.go +++ b/pkg/microservice/aslan/core/label/config/consts.go @@ -19,6 +19,6 @@ package config type ResourceType string const ( - ResourceTypeWorkflow ResourceType = "workflow" - ResourceTypeProduct ResourceType = "product" + ResourceTypeWorkflow ResourceType = "Workflow" + ResourceTypeProduct ResourceType = "Environment" ) diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 92dc930f..6ad2b73d 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -22,7 +22,6 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/shared/client/label" @@ -136,7 +135,7 @@ func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { res := &Policy{ Name: r.Name, } - var labels []mongodb2.Label + var labels []label.Label labelSet := sets.NewString() for _, ru := range r.Rules { res.Rules = append(res.Rules, &Rule{ @@ -149,7 +148,7 @@ func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { labelString := service.BuildLabelString(ma.Key, ma.Value) if !labelSet.Has(labelString) { labelSet.Insert(labelString) - labels = append(labels, mongodb2.Label{ + labels = append(labels, label.Label{ Key: ma.Key, Value: ma.Value, }) -- Gitee From ae6c3ac5712afd865eb2020d0737dfb4f924ede5 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 24 Jan 2022 15:56:39 +0800 Subject: [PATCH 1390/2647] fix service workLoadType undefined Signed-off-by: ddh27 --- .../aslan/core/environment/service/service.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 593ce613..93306ab2 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -202,6 +202,14 @@ func GetService(envName, productName, serviceName string, workLoadType string, l namespace := env.Namespace switch env.Source { case setting.SourceFromExternal, setting.SourceFromHelm: + if workLoadType == "undefined" && env.Source == setting.SourceFromExternal { + svcOpt := &commonrepo.ServiceFindOption{ProductName: productName, ServiceName: serviceName, Type: setting.K8SDeployType} + modelSvc, err := commonrepo.NewServiceColl().Find(svcOpt) + if err != nil { + return nil, e.ErrGetService.AddErr(err) + } + workLoadType = modelSvc.WorkloadType + } k8sServices, _ := getter.ListServices(namespace, nil, kubeClient) switch workLoadType { case setting.StatefulSet: -- Gitee From bb0293b90ab1b7484fcf3a55866a1e905d3ffc4a Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 24 Jan 2022 16:02:41 +0800 Subject: [PATCH 1391/2647] add devopds code Signed-off-by: allenshen --- .../stat/{dashboard => }/handler/build.go | 6 +- .../aslan/core/stat/handler/build_stat.go | 119 ++++ .../stat/{dashboard => }/handler/deploy.go | 6 +- .../aslan/core/stat/handler/deploy_stat.go | 92 +++ .../stat/{dashboard => }/handler/overview.go | 2 +- .../aslan/core/stat/handler/router.go | 59 ++ .../core/stat/{dashboard => }/handler/test.go | 2 +- .../aslan/core/stat/handler/test_stat.go | 93 +++ .../repository/models/build.go | 0 .../repository/models/deploy.go | 0 .../router.go => repository/models/test.go} | 32 +- .../repository/mongodb/build.go | 2 +- .../repository/mongodb/deploy.go | 2 +- .../core/stat/repository/mongodb/test.go | 118 ++++ .../stat/{dashboard => }/service/build.go | 4 +- .../aslan/core/stat/service/build_stat.go | 486 ++++++++++++++++ .../stat/{dashboard => }/service/deploy.go | 4 +- .../aslan/core/stat/service/deploy_stat.go | 389 +++++++++++++ .../stat/{dashboard => }/service/overview.go | 0 .../core/stat/{dashboard => }/service/test.go | 0 .../aslan/core/stat/service/test_stat.go | 528 ++++++++++++++++++ pkg/microservice/aslan/server/rest/router.go | 4 +- 22 files changed, 1917 insertions(+), 31 deletions(-) rename pkg/microservice/aslan/core/stat/{dashboard => }/handler/build.go (88%) create mode 100644 pkg/microservice/aslan/core/stat/handler/build_stat.go rename pkg/microservice/aslan/core/stat/{dashboard => }/handler/deploy.go (88%) create mode 100644 pkg/microservice/aslan/core/stat/handler/deploy_stat.go rename pkg/microservice/aslan/core/stat/{dashboard => }/handler/overview.go (91%) create mode 100644 pkg/microservice/aslan/core/stat/handler/router.go rename pkg/microservice/aslan/core/stat/{dashboard => }/handler/test.go (91%) create mode 100644 pkg/microservice/aslan/core/stat/handler/test_stat.go rename pkg/microservice/aslan/core/stat/{dashboard => }/repository/models/build.go (100%) rename pkg/microservice/aslan/core/stat/{dashboard => }/repository/models/deploy.go (100%) rename pkg/microservice/aslan/core/stat/{dashboard/handler/router.go => repository/models/test.go} (35%) rename pkg/microservice/aslan/core/stat/{dashboard => }/repository/mongodb/build.go (99%) rename pkg/microservice/aslan/core/stat/{dashboard => }/repository/mongodb/deploy.go (99%) create mode 100644 pkg/microservice/aslan/core/stat/repository/mongodb/test.go rename pkg/microservice/aslan/core/stat/{dashboard => }/service/build.go (95%) create mode 100644 pkg/microservice/aslan/core/stat/service/build_stat.go rename pkg/microservice/aslan/core/stat/{dashboard => }/service/deploy.go (97%) create mode 100644 pkg/microservice/aslan/core/stat/service/deploy_stat.go rename pkg/microservice/aslan/core/stat/{dashboard => }/service/overview.go (100%) rename pkg/microservice/aslan/core/stat/{dashboard => }/service/test.go (100%) create mode 100644 pkg/microservice/aslan/core/stat/service/test_stat.go diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/build.go b/pkg/microservice/aslan/core/stat/handler/build.go similarity index 88% rename from pkg/microservice/aslan/core/stat/dashboard/handler/build.go rename to pkg/microservice/aslan/core/stat/handler/build.go index 2fdc9b0b..f9dba4c3 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/build.go +++ b/pkg/microservice/aslan/core/stat/handler/build.go @@ -18,11 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) type GetBuildStatArgs struct { diff --git a/pkg/microservice/aslan/core/stat/handler/build_stat.go b/pkg/microservice/aslan/core/stat/handler/build_stat.go new file mode 100644 index 00000000..d70235f5 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/handler/build_stat.go @@ -0,0 +1,119 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +type getStatReq struct { + StartDate int64 `json:"startDate,omitempty"` + EndDate int64 `json:"endDate,omitempty"` + ProductNames []string `json:"productNames"` +} + +func GetAllPipelineTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = service.GetAllPipelineTask(ctx.Logger) +} + +func GetBuildDailyAverageMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.GetBuildDailyAverageMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetBuildDailyMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.GetBuildDailyMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetBuildHealthMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.GetBuildHealthMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetLatestTenBuildMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + // TODO: ????????????????? + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + // TODO: ↓ only this is used + ctx.Resp, ctx.Err = service.GetLatestTenBuildMeasure(args.ProductNames, ctx.Logger) +} + +func GetTenDurationMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.GetTenDurationMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetBuildTrendMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.GetBuildTrendMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go b/pkg/microservice/aslan/core/stat/handler/deploy.go similarity index 88% rename from pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go rename to pkg/microservice/aslan/core/stat/handler/deploy.go index a5efa28a..51f3832f 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/deploy.go +++ b/pkg/microservice/aslan/core/stat/handler/deploy.go @@ -18,11 +18,11 @@ package handler import ( "github.com/gin-gonic/gin" - e "github.com/koderover/zadig/pkg/tool/errors" - models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) type GetDeployStatArgs struct { diff --git a/pkg/microservice/aslan/core/stat/handler/deploy_stat.go b/pkg/microservice/aslan/core/stat/handler/deploy_stat.go new file mode 100644 index 00000000..24ca1c1e --- /dev/null +++ b/pkg/microservice/aslan/core/stat/handler/deploy_stat.go @@ -0,0 +1,92 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func InitDeployStat(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = service.InitDeployStat(ctx.Logger) +} + +func GetPipelineHealthMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetPipelineHealthMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetDeployHealthMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetDeployHealthMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetDeployWeeklyMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetDeployWeeklyMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetDeployTopFiveHigherMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetDeployTopFiveHigherMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetDeployTopFiveFailureMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetDeployTopFiveFailureMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go b/pkg/microservice/aslan/core/stat/handler/overview.go similarity index 91% rename from pkg/microservice/aslan/core/stat/dashboard/handler/overview.go rename to pkg/microservice/aslan/core/stat/handler/overview.go index c92c7827..74646eaf 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/overview.go +++ b/pkg/microservice/aslan/core/stat/handler/overview.go @@ -19,7 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/aslan/core/stat/handler/router.go b/pkg/microservice/aslan/core/stat/handler/router.go new file mode 100644 index 00000000..7adc0546 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/handler/router.go @@ -0,0 +1,59 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" +) + +type Router struct{} + +func (*Router) Inject(router *gin.RouterGroup) { + dashboard := router.Group("dashboard") + { + dashboard.GET("/overview", GetOverviewStat) + dashboard.GET("/build", GetBuildStat) + dashboard.GET("/deploy", GetDeployStat) + dashboard.GET("/test", GetTestDashboard) + } + + quality := router.Group("quality") + { + //buildquality + quality.GET("/initBuildquality", GetAllPipelineTask) + quality.POST("/buildAverageMeasure", GetBuildDailyAverageMeasure) + quality.POST("/buildDailyMeasure", GetBuildDailyMeasure) + quality.POST("/buildHealthMeasure", GetBuildHealthMeasure) + quality.POST("/buildLatestTenMeasure", GetLatestTenBuildMeasure) + quality.POST("/buildTenDurationMeasure", GetTenDurationMeasure) + quality.POST("/buildTrend", GetBuildTrendMeasure) + //testquality + quality.GET("/initTestquality", InitTestStat) + quality.POST("/testAverageMeasure", GetTestAverageMeasure) + quality.POST("/testCaseMeasure", GetTestCaseMeasure) + quality.POST("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) + quality.POST("/testHealthMeasure", GetTestHealthMeasure) + quality.POST("/testTrend", GetTestTrendMeasure) + //deployquality + quality.GET("/initDeployquality", InitDeployStat) + quality.POST("/pipelineHealthMeasure", GetPipelineHealthMeasure) + quality.POST("/deployHealthMeasure", GetDeployHealthMeasure) + quality.POST("/deployWeeklyMeasure", GetDeployWeeklyMeasure) + quality.POST("/deployTopFiveHigherMeasure", GetDeployTopFiveHigherMeasure) + quality.POST("/deployTopFiveFailureMeasure", GetDeployTopFiveFailureMeasure) + } +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/test.go b/pkg/microservice/aslan/core/stat/handler/test.go similarity index 91% rename from pkg/microservice/aslan/core/stat/dashboard/handler/test.go rename to pkg/microservice/aslan/core/stat/handler/test.go index ce219cf3..8c6899e0 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/test.go +++ b/pkg/microservice/aslan/core/stat/handler/test.go @@ -19,7 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) diff --git a/pkg/microservice/aslan/core/stat/handler/test_stat.go b/pkg/microservice/aslan/core/stat/handler/test_stat.go new file mode 100644 index 00000000..16ab5612 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/handler/test_stat.go @@ -0,0 +1,93 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func InitTestStat(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + + ctx.Err = service.InitTestStat(ctx.Logger) +} + +func GetTestAverageMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetTestAverageMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetTestCaseMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetTestCaseMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetTestDeliveryDeployMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetTestDeliveryDeployMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetTestHealthMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetTestHealthMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} + +func GetTestTrendMeasure(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + //params validate + args := new(getStatReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Resp, ctx.Err = service.GetTestTrendMeasure(args.StartDate, args.EndDate, args.ProductNames, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go b/pkg/microservice/aslan/core/stat/repository/models/build.go similarity index 100% rename from pkg/microservice/aslan/core/stat/dashboard/repository/models/build.go rename to pkg/microservice/aslan/core/stat/repository/models/build.go diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go b/pkg/microservice/aslan/core/stat/repository/models/deploy.go similarity index 100% rename from pkg/microservice/aslan/core/stat/dashboard/repository/models/deploy.go rename to pkg/microservice/aslan/core/stat/repository/models/deploy.go diff --git a/pkg/microservice/aslan/core/stat/dashboard/handler/router.go b/pkg/microservice/aslan/core/stat/repository/models/test.go similarity index 35% rename from pkg/microservice/aslan/core/stat/dashboard/handler/router.go rename to pkg/microservice/aslan/core/stat/repository/models/test.go index 214e99e6..d2addd02 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/handler/router.go +++ b/pkg/microservice/aslan/core/stat/repository/models/test.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,20 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. */ -package handler +package models -import ( - "github.com/gin-gonic/gin" -) - -type Router struct{} +type TestStat struct { + ProductName string `bson:"product_name" json:"productName"` + TotalSuccess int `bson:"total_success" json:"totalSuccess"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` + TotalTimeout int `bson:"total_timeout" json:"totalTimeout"` + TotalDuration int64 `bson:"total_duration" json:"totalDuration"` + TotalTestCount int `bson:"total_test_count" json:"totalTestCount"` + TotalDeployCount int `bson:"total_deploy_count" json:"totalDeployCount"` + TotalTestCase int `bson:"total_test_case" json:"totalTestCase"` + Date string `bson:"date" json:"date"` + CreateTime int64 `bson:"create_time" json:"createTime"` + UpdateTime int64 `bson:"update_time" json:"updateTime"` +} -func (*Router) Inject(router *gin.RouterGroup) { - stat := router.Group("stat") - { - stat.GET("/overview", GetOverviewStat) - stat.GET("/build", GetBuildStat) - stat.GET("/deploy", GetDeployStat) - stat.GET("/test", GetTestDashboard) - } +func (TestStat) TableName() string { + return "test_stat" } diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go b/pkg/microservice/aslan/core/stat/repository/mongodb/build.go similarity index 99% rename from pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go rename to pkg/microservice/aslan/core/stat/repository/mongodb/build.go index 83d07e76..b3072b13 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/stat/repository/mongodb/build.go @@ -25,7 +25,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "github.com/koderover/zadig/pkg/microservice/aslan/config" - models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) diff --git a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go b/pkg/microservice/aslan/core/stat/repository/mongodb/deploy.go similarity index 99% rename from pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go rename to pkg/microservice/aslan/core/stat/repository/mongodb/deploy.go index cc58a44c..24b44f5a 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb/deploy.go +++ b/pkg/microservice/aslan/core/stat/repository/mongodb/deploy.go @@ -25,7 +25,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "github.com/koderover/zadig/pkg/microservice/aslan/config" - models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) diff --git a/pkg/microservice/aslan/core/stat/repository/mongodb/test.go b/pkg/microservice/aslan/core/stat/repository/mongodb/test.go new file mode 100644 index 00000000..04c16951 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/repository/mongodb/test.go @@ -0,0 +1,118 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type TestStatOption struct { + StartDate int64 + EndDate int64 + IsAsc bool + ProductNames []string +} + +type TestStatColl struct { + *mongo.Collection + + coll string +} + +func NewTestStatColl() *TestStatColl { + name := models.TestStat{}.TableName() + return &TestStatColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *TestStatColl) GetCollectionName() string { + return c.coll +} + +func (c *TestStatColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "date", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *TestStatColl) Create(args *models.TestStat) error { + if args == nil { + return errors.New("nil testStat args") + } + + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *TestStatColl) FindCount() (int, error) { + cnt, err := c.Collection.CountDocuments(context.TODO(), bson.M{}) + return int(cnt), err +} + +func (c *TestStatColl) ListTestStat(option *TestStatOption) (testStats []*models.TestStat, err error) { + testStats = make([]*models.TestStat, 0) + query := bson.M{} + if len(option.ProductNames) > 0 { + query["product_name"] = bson.M{"$in": option.ProductNames} + } + + if option.StartDate > 0 { + query["create_time"] = bson.M{"$gte": option.StartDate, "$lte": option.EndDate} + } + + opt := &options.FindOptions{} + if option.IsAsc { + opt.SetSort(bson.D{{"create_time", 1}}) + } else { + opt.SetSort(bson.D{{"create_time", -1}}) + } + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &testStats) + if err != nil { + return nil, err + } + return testStats, nil +} + +func (c *TestStatColl) Update(args *models.TestStat) error { + if args == nil { + return errors.New("nil testStat args") + } + + query := bson.M{"date": args.Date, "product_name": args.ProductName} + _, err := c.UpdateOne(context.TODO(), query, args) + return err +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/build.go b/pkg/microservice/aslan/core/stat/service/build.go similarity index 95% rename from pkg/microservice/aslan/core/stat/dashboard/service/build.go rename to pkg/microservice/aslan/core/stat/service/build.go index 86fdcbfd..7b09064f 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/build.go +++ b/pkg/microservice/aslan/core/stat/service/build.go @@ -21,8 +21,8 @@ import ( "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" - repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/mongodb" ) type dashboardBuild struct { diff --git a/pkg/microservice/aslan/core/stat/service/build_stat.go b/pkg/microservice/aslan/core/stat/service/build_stat.go new file mode 100644 index 00000000..98dff3ea --- /dev/null +++ b/pkg/microservice/aslan/core/stat/service/build_stat.go @@ -0,0 +1,486 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "math" + "sort" + "time" + + "github.com/jinzhu/now" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonmongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/mongodb" +) + +func GetAllPipelineTask(log *zap.SugaredLogger) error { + option := &commonmongodb.ListAllTaskOption{Type: config.WorkflowType} + count, err := mongodb.NewBuildStatColl().FindCount() + if err != nil { + log.Errorf("BuildStat FindCount err:%v", err) + return fmt.Errorf("BuildStat FindCount err:%v", err) + } + if count > 0 { + option.CreateTime = time.Now().AddDate(0, 0, -1).Unix() + } + //获取所有的项目名称 + allProducts, err := templaterepo.NewProductColl().List() + if err != nil { + log.Errorf("BuildStat ProductTmpl List err:%v", err) + return fmt.Errorf("BuildStat ProductTmpl List err:%v", err) + } + for _, product := range allProducts { + option.ProductNames = []string{product.ProductName} + allTasks, err := commonmongodb.NewTaskColl().ListAllTasks(option) + if err != nil { + log.Errorf("pipeline list err:%v", err) + return fmt.Errorf("pipeline list err:%v", err) + } + taskDateMap := make(map[string][]*taskmodels.Task) + if len(allTasks) > 0 { + //将task的时间戳转成日期,以日期为单位分组 + for _, task := range allTasks { + time := time.Unix(task.CreateTime, 0) + date := time.Format(config.Date) + if _, isExist := taskDateMap[date]; isExist { + taskDateMap[date] = append(taskDateMap[date], task) + } else { + tasks := make([]*taskmodels.Task, 0) + tasks = append(tasks, task) + taskDateMap[date] = tasks + } + } + } else { + localTime := time.Now().AddDate(0, 0, -1).In(time.Local) + date := localTime.Format(config.Date) + taskDateMap[date] = []*taskmodels.Task{ + {Stages: []*commonmodels.Stage{}}, + } + } + + taskDateKeys := make([]string, 0, len(taskDateMap)) + for taskDateMapKey := range taskDateMap { + taskDateKeys = append(taskDateKeys, taskDateMapKey) + } + sort.Strings(taskDateKeys) + + for _, taskDate := range taskDateKeys { + var ( + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + totalDuration int64 + maxDurationPipelines = make([]*models.PipelineInfo, 0) + ) + //循环task任务获取需要的数据 + for _, taskPreview := range taskDateMap[taskDate] { + stages := taskPreview.Stages + for _, subStage := range stages { + taskType := subStage.TaskType + switch taskType { + case config.TaskBuild: + // 获取构建时长 + for _, subTask := range subStage.SubTasks { + buildInfo, err := base.ToBuildTask(subTask) + if err != nil { + log.Errorf("BuildStat ToBuildTask err:%v", err) + continue + } + if buildInfo.TaskStatus == config.StatusPassed { + totalSuccess++ + } else if buildInfo.TaskStatus == config.StatusFailed { + totalFailure++ + } else if buildInfo.TaskStatus == config.StatusTimeout { + totalTimeout++ + } else { + continue + } + + totalDuration += buildInfo.EndTime - buildInfo.StartTime + maxDurationPipeline := new(models.PipelineInfo) + maxDurationPipeline.PipelineName = taskPreview.PipelineName + maxDurationPipeline.TaskID = taskPreview.TaskID + maxDurationPipeline.Type = string(taskPreview.Type) + maxDurationPipeline.MaxDuration = buildInfo.EndTime - buildInfo.StartTime + + maxDurationPipelines = append(maxDurationPipelines, maxDurationPipeline) + } + } + } + } + //比较maxDurationPipeline的MaxDuration的最大值 + sort.SliceStable(maxDurationPipelines, func(i, j int) bool { return maxDurationPipelines[i].MaxDuration > maxDurationPipelines[j].MaxDuration }) + buildStat := new(models.BuildStat) + buildStat.ProductName = product.ProductName + buildStat.TotalSuccess = totalSuccess + buildStat.TotalFailure = totalFailure + buildStat.TotalTimeout = totalTimeout + buildStat.TotalDuration = totalDuration + if len(maxDurationPipelines) > 0 { + buildStat.TotalBuildCount = len(maxDurationPipelines) + buildStat.MaxDuration = maxDurationPipelines[0].MaxDuration + buildStat.MaxDurationPipeline = maxDurationPipelines[0] + } else { + buildStat.TotalBuildCount = 0 + buildStat.MaxDuration = 0 + buildStat.MaxDurationPipeline = &models.PipelineInfo{} + } + buildStat.Date = taskDate + tt, _ := time.ParseInLocation(config.Date, taskDate, time.Local) + buildStat.CreateTime = tt.Unix() + buildStat.UpdateTime = time.Now().Unix() + + err := mongodb.NewBuildStatColl().Create(buildStat) + if err != nil { //插入失败就更新 + err = mongodb.NewBuildStatColl().Update(buildStat) + if err != nil { + log.Errorf("BuildStat Update err:%v", err) + continue + } + } + } + } + return nil +} + +type dailyBuildStat struct { + Date string `bson:"date" json:"date"` + AverageDuration int `bson:"average_duration" json:"averageDuration"` +} + +func GetBuildDailyAverageMeasure(startDate int64, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*dailyBuildStat, error) { + buildStats, err := mongodb.NewBuildStatColl().ListBuildStat(&models.BuildStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListBuildStat err:%v", err) + return nil, fmt.Errorf("ListBuildStat err:%v", err) + } + buildStatMap := make(map[string][]*models.BuildStat) + for _, buildStat := range buildStats { + if _, isExist := buildStatMap[buildStat.Date]; isExist { + buildStatMap[buildStat.Date] = append(buildStatMap[buildStat.Date], buildStat) + } else { + tempBuildStats := make([]*models.BuildStat, 0) + tempBuildStats = append(tempBuildStats, buildStat) + buildStatMap[buildStat.Date] = tempBuildStats + } + } + buildStatDateKeys := make([]string, 0, len(buildStatMap)) + for buildStatDateMapKey := range buildStatMap { + buildStatDateKeys = append(buildStatDateKeys, buildStatDateMapKey) + } + sort.Strings(buildStatDateKeys) + buildStatDailyArgs := make([]*dailyBuildStat, 0) + for _, buildStatDateKey := range buildStatDateKeys { + totalDuration := 0 + totalBuildCount := 0 + for _, buildStat := range buildStatMap[buildStatDateKey] { + totalDuration += int(buildStat.TotalDuration) + totalBuildCount += buildStat.TotalBuildCount + } + buildStatDailyArg := new(dailyBuildStat) + buildStatDailyArg.Date = buildStatDateKey + if totalBuildCount > 0 { + buildStatDailyArg.AverageDuration = int(math.Floor(float64(totalDuration)/float64(totalBuildCount) + 0.5)) + } + + buildStatDailyArgs = append(buildStatDailyArgs, buildStatDailyArg) + } + return buildStatDailyArgs, nil +} + +type buildStatDaily struct { + Date string `bson:"date" json:"date"` + Success int `bson:"success" json:"success"` + Failure int `bson:"failure" json:"failure"` +} + +func GetBuildDailyMeasure(startDate int64, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*buildStatDaily, error) { + buildStats, err := mongodb.NewBuildStatColl().ListBuildStat(&models.BuildStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListBuildStat err:%v", err) + return nil, fmt.Errorf("ListBuildStat err:%v", err) + } + + buildStatMap := make(map[string][]*models.BuildStat) + for _, buildStat := range buildStats { + if _, isExist := buildStatMap[buildStat.Date]; isExist { + buildStatMap[buildStat.Date] = append(buildStatMap[buildStat.Date], buildStat) + } else { + tempBuildStats := make([]*models.BuildStat, 0) + tempBuildStats = append(tempBuildStats, buildStat) + buildStatMap[buildStat.Date] = tempBuildStats + } + + } + buildStatDateKeys := make([]string, 0, len(buildStatMap)) + for buildStatDateMapKey := range buildStatMap { + buildStatDateKeys = append(buildStatDateKeys, buildStatDateMapKey) + } + sort.Strings(buildStatDateKeys) + + buildStatDailys := make([]*buildStatDaily, 0) + for _, buildStatDateKey := range buildStatDateKeys { + totalSuccess := 0 + totalFailure := 0 + for _, buildStat := range buildStatMap[buildStatDateKey] { + totalSuccess += buildStat.TotalSuccess + totalFailure += buildStat.TotalFailure + } + + buildStatDaily := new(buildStatDaily) + buildStatDaily.Date = buildStatDateKey + buildStatDaily.Success = totalSuccess + buildStatDaily.Failure = totalFailure + + buildStatDailys = append(buildStatDailys, buildStatDaily) + } + return buildStatDailys, nil +} + +type buildStatTotal struct { + TotalSuccess int `json:"totalSuccess"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` +} + +func GetBuildHealthMeasure(startDate int64, endDate int64, productNames []string, log *zap.SugaredLogger) (*buildStatTotal, error) { + buildStats, err := mongodb.NewBuildStatColl().ListBuildStat(&models.BuildStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListBuildStat err:%v", err) + return nil, fmt.Errorf("ListBuildStat err:%v", err) + } + var ( + totalSuccess = 0 + totalFailure = 0 + ) + for _, buildStat := range buildStats { + totalSuccess += buildStat.TotalSuccess + totalFailure += buildStat.TotalFailure + } + buildStatTotal := &buildStatTotal{ + TotalSuccess: totalSuccess, + TotalFailure: totalFailure, + } + return buildStatTotal, nil +} + +type buildStatLatestTen struct { + *models.PipelineInfo + + ProductName string `bson:"product_name" json:"productName"` + Duration int64 `bson:"duration" json:"duration"` + Status string `bson:"status" json:"status"` + TaskCreator string `bson:"task_creator" json:"taskCreator"` + CreateTime int64 `bson:"create_time" json:"createTime"` +} + +func GetLatestTenBuildMeasure(productNames []string, log *zap.SugaredLogger) ([]*buildStatLatestTen, error) { + latestTenTasks, err := commonmongodb.NewTaskColl().ListAllTasks(&commonmongodb.ListAllTaskOption{Type: config.WorkflowType, Limit: config.LatestDay, Skip: 0, ProductNames: productNames}) + if err != nil { + log.Errorf("pipeline ListAllTasks err:%v", err) + return nil, fmt.Errorf("pipeline ListAllTasks err:%v", err) + } + latestTenPipelines := make([]*buildStatLatestTen, 0) + for _, latestTask := range latestTenTasks { + buildStatLatestTen := &buildStatLatestTen{ + PipelineInfo: &models.PipelineInfo{ + TaskID: latestTask.TaskID, + Type: string(latestTask.Type), + PipelineName: latestTask.PipelineName, + }, + ProductName: latestTask.ProductName, + Duration: latestTask.EndTime - latestTask.StartTime, + Status: string(latestTask.Status), + TaskCreator: latestTask.TaskCreator, + CreateTime: latestTask.CreateTime, + } + latestTenPipelines = append(latestTenPipelines, buildStatLatestTen) + } + + return latestTenPipelines, nil +} + +func GetTenDurationMeasure(startDate int64, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*buildStatLatestTen, error) { + maxTenDurationBuildStats, err := mongodb.NewBuildStatColl().ListBuildStat(&models.BuildStatOption{StartDate: startDate, EndDate: endDate, Limit: config.LatestDay, Skip: 0, ProductNames: productNames}) + if err != nil { + log.Errorf("ListBuildStat err:%v", err) + return nil, fmt.Errorf("ListBuildStat err:%v", err) + } + latestTenPipelines := make([]*buildStatLatestTen, 0) + for _, buidStat := range maxTenDurationBuildStats { + task, err := commonmongodb.NewTaskColl().Find(buidStat.MaxDurationPipeline.TaskID, buidStat.MaxDurationPipeline.PipelineName, config.PipelineType(buidStat.MaxDurationPipeline.Type)) + if err != nil { + log.Errorf("PipelineTask Find err:%v", err) + continue + } + + buildStatLatestTen := &buildStatLatestTen{ + PipelineInfo: buidStat.MaxDurationPipeline, + ProductName: task.ProductName, + Duration: task.EndTime - task.StartTime, + Status: string(task.Status), + TaskCreator: task.TaskCreator, + CreateTime: task.CreateTime, + } + latestTenPipelines = append(latestTenPipelines, buildStatLatestTen) + } + return latestTenPipelines, nil +} + +type buildTrend struct { + *CurrentDay + Sum []*sumData `json:"sum"` +} + +type CurrentDay struct { + Success int `json:"success"` + Failure int `json:"failure"` + Timeout int `json:"timeout"` +} + +type sumData struct { + *CurrentDay + Day int64 `json:"day"` +} + +func GetBuildTrendMeasure(startDate int64, endDate int64, productNames []string, log *zap.SugaredLogger) (*buildTrend, error) { + dayStartTimestamp := now.BeginningOfDay().Unix() + var ( + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + ) + allTasks, err := commonmongodb.NewTaskColl().ListAllTasks(&commonmongodb.ListAllTaskOption{Type: config.WorkflowType, CreateTime: dayStartTimestamp, ProductNames: productNames}) + if err != nil { + log.Errorf("ListAllTasks err:%v", err) + return nil, fmt.Errorf("ListAllTasks err:%v", err) + } + for _, taskPreview := range allTasks { + stages := taskPreview.Stages + for _, subStage := range stages { + taskType := subStage.TaskType + switch taskType { + case config.TaskBuild: + // 获取构建时长 + for _, subTask := range subStage.SubTasks { + buildInfo, err := base.ToBuildTask(subTask) + if err != nil { + log.Errorf("BuildStat ToBuildTask err:%v", err) + continue + } + if buildInfo.TaskStatus == config.StatusPassed { + totalSuccess++ + } else if buildInfo.TaskStatus == config.StatusFailed { + totalFailure++ + } else if buildInfo.TaskStatus == config.StatusTimeout { + totalTimeout++ + } else { + continue + } + } + } + } + } + curr := &CurrentDay{ + Success: totalSuccess, + Failure: totalFailure, + Timeout: totalTimeout, + } + sumDatas := make([]*sumData, 0) + buildStats, err := mongodb.NewBuildStatColl().ListBuildStat(&models.BuildStatOption{StartDate: startDate, EndDate: endDate, IsAsc: false, ProductNames: productNames}) + if err != nil { + log.Errorf("ListBuildStat err:%v", err) + return nil, fmt.Errorf("ListBuildStat err:%v", err) + } + buildStatMap := make(map[string][]*models.BuildStat) + for _, buildStat := range buildStats { + if _, isExist := buildStatMap[buildStat.Date]; isExist { + buildStatMap[buildStat.Date] = append(buildStatMap[buildStat.Date], buildStat) + } else { + tempBuildStats := make([]*models.BuildStat, 0) + tempBuildStats = append(tempBuildStats, buildStat) + buildStatMap[buildStat.Date] = tempBuildStats + } + } + buildStatDateKeys := make([]string, 0, len(buildStatMap)) + for buildStatDateMapKey := range buildStatMap { + buildStatDateKeys = append(buildStatDateKeys, buildStatDateMapKey) + } + sort.Sort(sort.Reverse(sort.StringSlice(buildStatDateKeys))) + + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + if len(buildStatMap) <= config.Day { + for _, buildStat := range buildStats { + totalSuccess += buildStat.TotalSuccess + totalFailure += buildStat.TotalFailure + totalTimeout += buildStat.TotalTimeout + } + sumData := &sumData{ + Day: time.Now().Unix(), + CurrentDay: &CurrentDay{ + Success: totalSuccess, + Failure: totalFailure, + Timeout: totalTimeout, + }, + } + sumDatas = append(sumDatas, sumData) + } else { + for index, buildStatDate := range buildStatDateKeys { + for _, buildStat := range buildStatMap[buildStatDate] { + totalSuccess += buildStat.TotalSuccess + totalFailure += buildStat.TotalFailure + totalTimeout += buildStat.TotalTimeout + } + + if ((index + 1) % config.Day) == 0 { + date, err := time.ParseInLocation(config.Date, buildStatDate, time.Local) + if err != nil { + log.Errorf("Failed to parse date: %s, the error is: %+v", buildStatDate, err) + return nil, err + } + sumData := &sumData{ + Day: date.Unix(), + CurrentDay: &CurrentDay{ + Success: totalSuccess, + Failure: totalFailure, + Timeout: totalTimeout, + }, + } + sumDatas = append(sumDatas, sumData) + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + } + } + } + + buildTrend := &buildTrend{ + CurrentDay: curr, + Sum: sumDatas, + } + + return buildTrend, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go b/pkg/microservice/aslan/core/stat/service/deploy.go similarity index 97% rename from pkg/microservice/aslan/core/stat/dashboard/service/deploy.go rename to pkg/microservice/aslan/core/stat/service/deploy.go index df634166..d05a4d2a 100644 --- a/pkg/microservice/aslan/core/stat/dashboard/service/deploy.go +++ b/pkg/microservice/aslan/core/stat/service/deploy.go @@ -21,8 +21,8 @@ import ( "go.uber.org/zap" - models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/models" - repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/repository/mongodb" + models "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + repo "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/mongodb" ) type dashboardDeploy struct { diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go new file mode 100644 index 00000000..b8f16e71 --- /dev/null +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -0,0 +1,389 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "sort" + "time" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonmongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/mongodb" +) + +type serviceInfo struct { + ServiceName string + DeploySuccess int + DeployFailure int +} + +type serviceTotalInfo struct { + ServiceName string + DeployTotal int + DeployTotalFailure int +} + +func InitDeployStat(log *zap.SugaredLogger) error { + option := &commonmongodb.ListAllTaskOption{Type: config.WorkflowType} + count, err := mongodb.NewDeployStatColl().FindCount() + if err != nil { + log.Errorf("deployStat FindCount err:%v", err) + return fmt.Errorf("deployStat FindCount err:%v", err) + } + if count > 0 { + option.CreateTime = time.Now().AddDate(0, 0, -1).Unix() + } + //获取所有的项目名称 + allProducts, err := templaterepo.NewProductColl().List() + if err != nil { + log.Errorf("deployStat ProductTmpl List err:%v", err) + return fmt.Errorf("deployStat ProductTmpl List err:%v", err) + } + for _, product := range allProducts { + option.ProductNames = []string{product.ProductName} + allTasks, err := commonmongodb.NewTaskColl().ListAllTasks(option) + if err != nil { + log.Errorf("pipeline list err:%v", err) + return fmt.Errorf("pipeline list err:%v", err) + } + taskDateMap := make(map[string][]*taskmodels.Task) + if len(allTasks) > 0 { + //将task的时间戳转成日期,以日期为单位分组 + for _, task := range allTasks { + createTime := time.Unix(task.CreateTime, 0) + date := createTime.Format(config.Date) + if _, isExist := taskDateMap[date]; isExist { + taskDateMap[date] = append(taskDateMap[date], task) + } else { + tasks := make([]*taskmodels.Task, 0) + tasks = append(tasks, task) + taskDateMap[date] = tasks + } + } + } else { + currentTime := time.Now().AddDate(0, 0, -1).In(time.Local) + date := currentTime.Format(config.Date) + taskDateMap[date] = []*taskmodels.Task{ + {Stages: []*commonmodels.Stage{}}, + } + } + + taskDateKeys := make([]string, 0, len(taskDateMap)) + for taskDateMapKey := range taskDateMap { + taskDateKeys = append(taskDateKeys, taskDateMapKey) + } + sort.Strings(taskDateKeys) + + for _, taskDate := range taskDateKeys { + var ( + totalTaskSuccess = 0 + totalTaskFailure = 0 + totalDeploySuccess = 0 + totalDeployFailure = 0 + deployServiceInfos = make([]*serviceInfo, 0) + maxDeployFailureServiceMap = make(map[string][]*serviceInfo) + serviceInfoTotals = make([]*serviceTotalInfo, 0) + ) + //循环task任务获取需要的数据 + for _, taskPreview := range taskDateMap[taskDate] { + stages := taskPreview.Stages + taskStatus := taskPreview.Status + switch taskStatus { + case config.StatusPassed: + totalTaskSuccess++ + case config.StatusFailed: + totalTaskFailure++ + } + for _, subStage := range stages { + taskType := subStage.TaskType + switch taskType { + case config.TaskDeploy: + for _, subTask := range subStage.SubTasks { + deployInfo, err := base.ToDeployTask(subTask) + serviceInfo := new(serviceInfo) + serviceInfo.ServiceName = deployInfo.ServiceName + + if err != nil { + log.Errorf("deployStat ToDeployTask err:%v", err) + continue + } + + if deployInfo.TaskStatus == config.StatusPassed { + totalDeploySuccess++ + serviceInfo.DeploySuccess = 1 + } else if deployInfo.TaskStatus == config.StatusFailed { + totalDeployFailure++ + serviceInfo.DeployFailure = 1 + } else { + continue + } + deployServiceInfos = append(deployServiceInfos, serviceInfo) + } + } + } + } + //以服务名称分组 + for _, svcInfo := range deployServiceInfos { + if _, isExsit := maxDeployFailureServiceMap[svcInfo.ServiceName]; isExsit { + maxDeployFailureServiceMap[svcInfo.ServiceName] = append(maxDeployFailureServiceMap[svcInfo.ServiceName], svcInfo) + } else { + serviceInfos := make([]*serviceInfo, 0) + serviceInfos = append(serviceInfos, svcInfo) + maxDeployFailureServiceMap[svcInfo.ServiceName] = serviceInfos + } + } + //统计部署次数最高和失败最高的服务 + for serviceName, serviceInfos := range maxDeployFailureServiceMap { + totalDeploy := 0 + totalFailure := 0 + for _, serviceInfo := range serviceInfos { + totalFailure += serviceInfo.DeployFailure + totalDeploy += totalFailure + serviceInfo.DeploySuccess + } + serviceTotalInfo := &serviceTotalInfo{ + ServiceName: serviceName, + DeployTotal: totalDeploy, + DeployTotalFailure: totalFailure, + } + serviceInfoTotals = append(serviceInfoTotals, serviceTotalInfo) + } + + deployStat := new(models.DeployStat) + deployStat.ProductName = product.ProductName + deployStat.TotalTaskSuccess = totalTaskSuccess + deployStat.TotalTaskFailure = totalTaskFailure + deployStat.TotalDeploySuccess = totalDeploySuccess + deployStat.TotalDeployFailure = totalDeployFailure + deployStat.Date = taskDate + tt, _ := time.ParseInLocation(config.Date, taskDate, time.Local) + deployStat.CreateTime = tt.Unix() + deployStat.UpdateTime = time.Now().Unix() + if len(serviceInfoTotals) > 0 { + sort.SliceStable(serviceInfoTotals, func(i, j int) bool { return serviceInfoTotals[i].DeployTotal > serviceInfoTotals[j].DeployTotal }) + deployStat.MaxDeployServiceNum = serviceInfoTotals[0].DeployTotal + deployStat.MaxDeployServiceFailureNum = serviceInfoTotals[0].DeployTotalFailure + deployStat.MaxDeployServiceName = serviceInfoTotals[0].ServiceName + + sort.SliceStable(serviceInfoTotals, func(i, j int) bool { + return serviceInfoTotals[i].DeployTotalFailure > serviceInfoTotals[j].DeployTotalFailure + }) + deployStat.MaxDeployFailureServiceNum = serviceInfoTotals[0].DeployTotalFailure + deployStat.MaxDeployFailureServiceName = serviceInfoTotals[0].ServiceName + } else { + deployStat.MaxDeployServiceNum = 0 + deployStat.MaxDeployServiceFailureNum = 0 + deployStat.MaxDeployServiceName = "" + deployStat.MaxDeployFailureServiceNum = 0 + deployStat.MaxDeployFailureServiceName = "" + } + err := mongodb.NewDeployStatColl().Create(deployStat) + if err != nil { //插入失败就更新 + err = mongodb.NewDeployStatColl().Update(deployStat) + if err != nil { + log.Errorf("deployStat Update err:%v", err) + continue + } + } + } + } + return nil +} + +type deployStatTotal struct { + TotalSuccess int `bson:"total_success" json:"totalSuccess"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` +} + +func GetPipelineHealthMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) (*deployStatTotal, error) { + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListDeployStat err:%v", err) + return nil, fmt.Errorf("ListDeployStat err:%v", err) + } + var ( + totalSuccess = 0 + totalFailure = 0 + ) + for _, deployStat := range deployStats { + totalSuccess += deployStat.TotalTaskSuccess + totalFailure += deployStat.TotalTaskFailure + } + deployStatTotal := &deployStatTotal{ + TotalSuccess: totalSuccess, + TotalFailure: totalFailure, + } + return deployStatTotal, nil +} + +func GetDeployHealthMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) (*deployStatTotal, error) { + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListDeployStat err:%v", err) + return nil, fmt.Errorf("ListDeployStat err:%v", err) + } + var ( + totalSuccess = 0 + totalFailure = 0 + ) + for _, deployStat := range deployStats { + totalSuccess += deployStat.TotalDeploySuccess + totalFailure += deployStat.TotalDeployFailure + } + deployStatTotal := &deployStatTotal{ + TotalSuccess: totalSuccess, + TotalFailure: totalFailure, + } + return deployStatTotal, nil +} + +type deployStatWeekly struct { + Day int64 `bson:"day" json:"day"` + TotalSuccess int `bson:"total_success" json:"totalSuccess"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` +} + +func GetDeployWeeklyMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*deployStatWeekly, error) { + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListDeployStat err:%v", err) + return nil, fmt.Errorf("ListDeployStat err:%v", err) + } + deployStatMap := make(map[string][]*models.DeployStat) + for _, deployStat := range deployStats { + if _, isExist := deployStatMap[deployStat.Date]; isExist { + deployStatMap[deployStat.Date] = append(deployStatMap[deployStat.Date], deployStat) + } else { + tempDeployStats := make([]*models.DeployStat, 0) + tempDeployStats = append(tempDeployStats, deployStat) + deployStatMap[deployStat.Date] = tempDeployStats + } + } + deployStatDateKeys := make([]string, 0, len(deployStatMap)) + for deployStatDateMapKey := range deployStatMap { + deployStatDateKeys = append(deployStatDateKeys, deployStatDateMapKey) + } + sort.Sort(sort.Reverse(sort.StringSlice(deployStatDateKeys))) + + var ( + totalSuccess = 0 + totalFailure = 0 + ) + deployStatWeeklys := make([]*deployStatWeekly, 0) + if len(deployStatDateKeys) <= config.Day && len(deployStatDateKeys) > 0 { + for _, deployStat := range deployStats { + totalSuccess += deployStat.TotalDeploySuccess + totalFailure += deployStat.TotalDeployFailure + } + deployStatWeekly := &deployStatWeekly{ + Day: time.Now().Unix(), + TotalSuccess: totalSuccess, + TotalFailure: totalFailure, + } + deployStatWeeklys = append(deployStatWeeklys, deployStatWeekly) + } else { + for index, deployStatDate := range deployStatDateKeys { + for _, deployStat := range deployStatMap[deployStatDate] { + totalSuccess += deployStat.TotalDeploySuccess + totalFailure += deployStat.TotalDeployFailure + } + + if ((index + 1) % config.Day) == 0 { + date, err := time.ParseInLocation(config.Date, deployStatDate, time.Local) + if err != nil { + log.Errorf("Failed to parse date: %s, the error is: %+v", deployStatDate, err) + return nil, err + } + deployStatWeekly := &deployStatWeekly{ + Day: date.Unix(), + TotalSuccess: totalSuccess, + TotalFailure: totalFailure, + } + deployStatWeeklys = append(deployStatWeeklys, deployStatWeekly) + totalSuccess = 0 + totalFailure = 0 + } + } + } + + return deployStatWeeklys, nil +} + +type deployHigherStat struct { + ServiceName string `bson:"service_name" json:"serviceName"` + TotalSuccess int `bson:"total_success" json:"totalSuccess"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` +} + +func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*deployHigherStat, error) { + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListDeployStat err:%v", err) + return nil, fmt.Errorf("ListDeployStat err:%v", err) + } + deployHigherStats := make([]*deployHigherStat, 0) + for _, deployStat := range deployStats { + tempDeployStat, err := mongodb.NewDeployStatColl().Get(&mongodb.DeployStatGetOption{ServiceName: deployStat.MaxDeployServiceName, MaxDeployServiceNum: deployStat.MaxDeployServiceNum}) + if err != nil { + log.Errorf("Get deployStat err:%v", err) + continue + } + deployHigherStat := &deployHigherStat{ + ServiceName: tempDeployStat.MaxDeployServiceName, + TotalSuccess: tempDeployStat.MaxDeployServiceNum - tempDeployStat.MaxDeployServiceFailureNum, + TotalFailure: tempDeployStat.MaxDeployServiceFailureNum, + } + if tempDeployStat.MaxDeployServiceName != "" { + deployHigherStats = append(deployHigherStats, deployHigherStat) + } + + } + return deployHigherStats, nil +} + +type deployFailureHigherStat struct { + ProductName string `bson:"product_name" json:"productName"` + ServiceName string `bson:"service_name" json:"serviceName"` + TotalFailure int `bson:"total_failure" json:"totalFailure"` +} + +func GetDeployTopFiveFailureMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*deployFailureHigherStat, error) { + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListDeployStat err:%v", err) + return nil, fmt.Errorf("ListDeployStat err:%v", err) + } + deployFailureHigherStats := make([]*deployFailureHigherStat, 0) + for _, deployStat := range deployStats { + deployFailureHigherStat := &deployFailureHigherStat{ + ProductName: deployStat.ProductName, + ServiceName: deployStat.MaxDeployFailureServiceName, + TotalFailure: deployStat.MaxDeployFailureServiceNum, + } + if deployStat.MaxDeployFailureServiceName != "" { + deployFailureHigherStats = append(deployFailureHigherStats, deployFailureHigherStat) + } + } + return deployFailureHigherStats, nil +} diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/overview.go b/pkg/microservice/aslan/core/stat/service/overview.go similarity index 100% rename from pkg/microservice/aslan/core/stat/dashboard/service/overview.go rename to pkg/microservice/aslan/core/stat/service/overview.go diff --git a/pkg/microservice/aslan/core/stat/dashboard/service/test.go b/pkg/microservice/aslan/core/stat/service/test.go similarity index 100% rename from pkg/microservice/aslan/core/stat/dashboard/service/test.go rename to pkg/microservice/aslan/core/stat/service/test.go diff --git a/pkg/microservice/aslan/core/stat/service/test_stat.go b/pkg/microservice/aslan/core/stat/service/test_stat.go new file mode 100644 index 00000000..3495f2ab --- /dev/null +++ b/pkg/microservice/aslan/core/stat/service/test_stat.go @@ -0,0 +1,528 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "math" + "os" + "sort" + "strings" + "time" + + "github.com/jinzhu/now" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonmongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util" +) + +func InitTestStat(log *zap.SugaredLogger) error { + option := &commonmongodb.ListAllTaskOption{Type: config.WorkflowType} + count, err := mongodb.NewTestStatColl().FindCount() + if err != nil { + log.Errorf("testStat FindCount err:%v", err) + return fmt.Errorf("testStat FindCount err:%v", err) + } + if count > 0 { + option.CreateTime = time.Now().AddDate(0, 0, -1).Unix() + } + //获取所有的项目名称 + allProducts, err := templaterepo.NewProductColl().List() + if err != nil { + log.Errorf("testStat ProductTmpl List err:%v", err) + return fmt.Errorf("testStat ProductTmpl List err:%v", err) + } + for _, product := range allProducts { + option.ProductNames = []string{product.ProductName} + allTasks, err := commonmongodb.NewTaskColl().ListAllTasks(option) + if err != nil { + log.Errorf("pipeline list err:%v", err) + return fmt.Errorf("pipeline list err:%v", err) + } + + taskDateMap := make(map[string][]*taskmodels.Task) + if len(allTasks) > 0 { + //将task的时间戳转成日期,以日期为单位分组 + for _, task := range allTasks { + time := time.Unix(task.CreateTime, 0) + date := time.Format(config.Date) + taskDateMap[date] = append(taskDateMap[date], task) + } + } else { + time := time.Now().AddDate(0, 0, -1) + date := time.Format(config.Date) + taskDateMap[date] = []*taskmodels.Task{ + {Stages: []*commonmodels.Stage{}}, + } + } + + taskDateKeys := make([]string, 0, len(taskDateMap)) + for taskDateMapKey := range taskDateMap { + taskDateKeys = append(taskDateKeys, taskDateMapKey) + } + sort.Strings(taskDateKeys) + + defaultS3Storage, _ := s3service.FindDefaultS3() + + for _, taskDate := range taskDateKeys { + var ( + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + totalDuration int64 + totalDeployCount = 0 + totalTestCount = 0 + totalTestCase = 0 + ) + //循环task任务获取需要的数据 + for _, taskPreview := range taskDateMap[taskDate] { + stages := taskPreview.Stages + for _, subStage := range stages { + taskType := subStage.TaskType + switch taskType { + case config.TaskTestingV2: + // 获取构建时长 + for _, subTask := range subStage.SubTasks { + testInfo, err := base.ToTestingTask(subTask) + if err != nil { + log.Errorf("TestStat ToTestingTask err:%v", err) + continue + } + if testInfo.TaskStatus == config.StatusPassed { + totalSuccess++ + } else if testInfo.TaskStatus == config.StatusFailed { + totalFailure++ + } else if testInfo.TaskStatus == config.StatusTimeout { + totalTimeout++ + } else { + continue + } + + totalDuration += testInfo.EndTime - testInfo.StartTime + totalTestCount++ + + if testInfo.JobCtx.TestType != setting.PerformanceTest { + func() { + testJobName := strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s-%s", + config.WorkflowType, taskPreview.PipelineName, taskPreview.TaskID, config.TaskTestingV2, testInfo.TestModuleName)), "_", "-", -1) + + filename, err := util.GenerateTmpFile() + defer func() { + _ = os.Remove(filename) + }() + + if err != nil { + log.Errorf("failed to genarate tmp file, err: %s", err) + return + } + + objectKey := defaultS3Storage.GetObjectPath(fmt.Sprintf("%s/%d/%s/%s", taskPreview.PipelineName, taskPreview.TaskID, "test", testJobName)) + forcedPathStyle := true + if defaultS3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + client, err := s3tool.NewClient(defaultS3Storage.Endpoint, defaultS3Storage.Ak, defaultS3Storage.Sk, defaultS3Storage.Insecure, forcedPathStyle) + if err != nil { + log.Errorf("Failed to create s3 client for download, error: %s", err) + return + } + if err = client.Download(defaultS3Storage.Bucket, objectKey, filename); err != nil { + log.Errorf("s3Service download err:%s", err) + return + } + + b, err := ioutil.ReadFile(filename) + if err != nil { + log.Error(fmt.Sprintf("InitTestStat get local test result file error: %v", err)) + return + } + testReport := new(commonmodels.TestReport) + if err := xml.Unmarshal(b, &testReport.FunctionTestSuite); err != nil { + log.Error(fmt.Sprintf("unmarshal it report xml error: %v", err)) + return + } + totalTestCase += testReport.FunctionTestSuite.Tests + }() + } + } + case config.TaskDeploy: + totalDeployCount++ + } + } + } + + testStat := new(models.TestStat) + testStat.ProductName = product.ProductName + testStat.TotalSuccess = totalSuccess + testStat.TotalFailure = totalFailure + testStat.TotalTimeout = totalTimeout + testStat.TotalDuration = totalDuration + testStat.TotalTestCount = totalTestCount + testStat.TotalDeployCount = totalDeployCount + testStat.TotalTestCase = totalTestCase + testStat.Date = taskDate + tt, _ := time.ParseInLocation(config.Date, taskDate, time.Local) + testStat.CreateTime = tt.Unix() + testStat.UpdateTime = time.Now().Unix() + + err := mongodb.NewTestStatColl().Create(testStat) + if err != nil { //插入失败就更新 + err = mongodb.NewTestStatColl().Update(testStat) + if err != nil { + log.Errorf("TestStat Update err:%v", err) + continue + } + } + } + } + return nil +} + +type testStatDailyArg struct { + Date string `json:"date"` + AverageDuration int `json:"averageDuration"` +} + +func GetTestAverageMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*testStatDailyArg, error) { + testStats, err := mongodb.NewTestStatColl().ListTestStat(&mongodb.TestStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListTestStat err:%v", err) + return nil, fmt.Errorf("ListTestStat err:%v", err) + } + testStatMap := make(map[string][]*models.TestStat) + for _, testStat := range testStats { + if _, isExist := testStatMap[testStat.Date]; isExist { + testStatMap[testStat.Date] = append(testStatMap[testStat.Date], testStat) + } else { + tempTestStats := make([]*models.TestStat, 0) + tempTestStats = append(tempTestStats, testStat) + testStatMap[testStat.Date] = tempTestStats + } + } + testStatDateKeys := make([]string, 0, len(testStatMap)) + for testStatDateMapKey := range testStatMap { + testStatDateKeys = append(testStatDateKeys, testStatDateMapKey) + } + sort.Strings(testStatDateKeys) + testStatDailyArgs := make([]*testStatDailyArg, 0) + for _, testStatDateKey := range testStatDateKeys { + totalDuration := 0 + totalTestCount := 0 + for _, testStat := range testStatMap[testStatDateKey] { + totalDuration += int(testStat.TotalDuration) + totalTestCount += testStat.TotalTestCount + } + testStatDailyArg := new(testStatDailyArg) + testStatDailyArg.Date = testStatDateKey + if totalTestCount > 0 { + testStatDailyArg.AverageDuration = int(math.Floor(float64(totalDuration)/float64(totalTestCount) + 0.5)) + } + + testStatDailyArgs = append(testStatDailyArgs, testStatDailyArg) + } + return testStatDailyArgs, nil +} + +type testCaseStat struct { + Day int64 `json:"day"` + TestCase int `json:"testCase"` +} + +func GetTestCaseMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*testCaseStat, error) { + testStats, err := mongodb.NewTestStatColl().ListTestStat(&mongodb.TestStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListTestStat err:%v", err) + return nil, fmt.Errorf("ListTestStat err:%v", err) + } + testStatMap := make(map[string][]*models.TestStat) + for _, testStat := range testStats { + if _, isExist := testStatMap[testStat.Date]; isExist { + testStatMap[testStat.Date] = append(testStatMap[testStat.Date], testStat) + } else { + tempTestStats := make([]*models.TestStat, 0) + tempTestStats = append(tempTestStats, testStat) + testStatMap[testStat.Date] = tempTestStats + } + } + testStatDateKeys := make([]string, 0, len(testStatMap)) + for testStatDateMapKey := range testStatMap { + testStatDateKeys = append(testStatDateKeys, testStatDateMapKey) + } + sort.Sort(sort.Reverse(sort.StringSlice(testStatDateKeys))) + testCaseStats := make([]*testCaseStat, 0) + totalTestCase := 0 + if len(testStatMap) <= config.Day { + for _, testStat := range testStats { + totalTestCase += testStat.TotalTestCount * testStat.TotalTestCase + } + testCaseStat := &testCaseStat{ + Day: time.Now().Unix(), + TestCase: totalTestCase, + } + testCaseStats = append(testCaseStats, testCaseStat) + } else { + for index, testStatDate := range testStatDateKeys { + for _, testStat := range testStatMap[testStatDate] { + totalTestCase += testStat.TotalTestCount * testStat.TotalTestCase + } + + if ((index + 1) % config.Day) == 0 { + day, err := time.ParseInLocation("2006-01-02", testStatDate, time.Local) + if err != nil { + log.Errorf("Failed to parse %s as time, err:", testStatDate, err) + return nil, err + } + testCaseStat := &testCaseStat{ + Day: day.Unix(), + TestCase: totalTestCase, + } + testCaseStats = append(testCaseStats, testCaseStat) + totalTestCase = 0 + } + } + } + + return testCaseStats, nil +} + +type testDeployStat struct { + Day int64 `json:"day"` + DeliveryDeploy int `json:"deliveryDeploy"` +} + +func GetTestDeliveryDeployMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*testDeployStat, error) { + testStats, err := mongodb.NewTestStatColl().ListTestStat(&mongodb.TestStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListTestStat err:%v", err) + return nil, fmt.Errorf("ListTestStat err:%v", err) + } + testStatMap := make(map[string][]*models.TestStat) + for _, testStat := range testStats { + if _, isExist := testStatMap[testStat.Date]; isExist { + testStatMap[testStat.Date] = append(testStatMap[testStat.Date], testStat) + } else { + tempTestStats := make([]*models.TestStat, 0) + tempTestStats = append(tempTestStats, testStat) + testStatMap[testStat.Date] = tempTestStats + } + } + testStatDateKeys := make([]string, 0, len(testStatMap)) + for testStatDateMapKey := range testStatMap { + testStatDateKeys = append(testStatDateKeys, testStatDateMapKey) + } + sort.Sort(sort.Reverse(sort.StringSlice(testStatDateKeys))) + testDeployStats := make([]*testDeployStat, 0) + totalDeploy := 0 + if len(testStatMap) <= config.Day { + for _, testStat := range testStats { + totalDeploy += testStat.TotalDeployCount + } + testDeployStat := &testDeployStat{ + Day: time.Now().Unix(), + DeliveryDeploy: totalDeploy, + } + testDeployStats = append(testDeployStats, testDeployStat) + } else { + for index, testStatDate := range testStatDateKeys { + for _, testStat := range testStatMap[testStatDate] { + totalDeploy += testStat.TotalDeployCount + } + + if ((index + 1) % config.Day) == 0 { + day, err := time.ParseInLocation("2006-01-02", testStatDate, time.Local) + if err != nil { + log.Errorf("Failed to parse %s as time, err:", testStatDate, err) + return nil, err + } + testDeployStat := &testDeployStat{ + Day: day.Unix(), + DeliveryDeploy: totalDeploy, + } + testDeployStats = append(testDeployStats, testDeployStat) + totalDeploy = 0 + } + } + } + + return testDeployStats, nil +} + +type testStatTotal struct { + TotalSuccess int `json:"totalSuccess"` + TotalFailure int `json:"totalFailure"` +} + +func GetTestHealthMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) (*testStatTotal, error) { + testStats, err := mongodb.NewTestStatColl().ListTestStat(&mongodb.TestStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + if err != nil { + log.Errorf("ListTestStat err:%v", err) + return nil, fmt.Errorf("ListTestStat err:%v", err) + } + var ( + totalSuccess = 0 + totalFailure = 0 + ) + for _, testStat := range testStats { + totalSuccess += testStat.TotalSuccess + totalFailure += testStat.TotalFailure + } + testStatTotal := &testStatTotal{ + TotalSuccess: totalSuccess, + TotalFailure: totalFailure, + } + return testStatTotal, nil +} + +type testTrend struct { + *CurrentDay + Sum []*sumData `json:"sum"` +} + +func GetTestTrendMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) (*testTrend, error) { + todayDate := now.BeginningOfDay().Unix() + var ( + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + ) + allTasks, err := commonmongodb.NewTaskColl().ListAllTasks(&commonmongodb.ListAllTaskOption{Type: config.WorkflowType, CreateTime: todayDate, ProductNames: productNames}) + if err != nil { + log.Errorf("ListAllTasks err:%v", err) + return nil, fmt.Errorf("ListAllTasks err:%v", err) + } + for _, taskPreview := range allTasks { + stages := taskPreview.Stages + for _, subStage := range stages { + taskType := subStage.TaskType + switch taskType { + case config.TaskTestingV2: + // 获取构建时长 + for _, subTask := range subStage.SubTasks { + testInfo, err := base.ToTestingTask(subTask) + if err != nil { + log.Errorf("TestStat ToTestingTask err:%v", err) + continue + } + if testInfo.TaskStatus == config.StatusPassed { + totalSuccess++ + } else if testInfo.TaskStatus == config.StatusFailed { + totalFailure++ + } else if testInfo.TaskStatus == config.StatusTimeout { + totalTimeout++ + } else { + continue + } + } + } + } + } + currDay := &CurrentDay{ + Success: totalSuccess, + Failure: totalFailure, + Timeout: totalTimeout, + } + sumDatas := make([]*sumData, 0) + + testStats, err := mongodb.NewTestStatColl().ListTestStat(&mongodb.TestStatOption{StartDate: startDate, EndDate: endDate, IsAsc: false, ProductNames: productNames}) + if err != nil { + log.Errorf("ListTestStat err:%v", err) + return nil, fmt.Errorf("ListTestStat err:%v", err) + } + testStatMap := make(map[string][]*models.TestStat) + for _, testStat := range testStats { + if _, isExist := testStatMap[testStat.Date]; isExist { + testStatMap[testStat.Date] = append(testStatMap[testStat.Date], testStat) + } else { + tempTestStats := make([]*models.TestStat, 0) + tempTestStats = append(tempTestStats, testStat) + testStatMap[testStat.Date] = tempTestStats + } + } + testStatDateKeys := make([]string, 0, len(testStatMap)) + for testStatDateMapKey := range testStatMap { + testStatDateKeys = append(testStatDateKeys, testStatDateMapKey) + } + sort.Sort(sort.Reverse(sort.StringSlice(testStatDateKeys))) + + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + if len(testStatMap) <= config.Day { + for _, testStat := range testStats { + totalSuccess += testStat.TotalSuccess + totalFailure += testStat.TotalFailure + totalTimeout += testStat.TotalTimeout + } + sumData := &sumData{ + Day: time.Now().Unix(), + CurrentDay: &CurrentDay{ + Success: totalSuccess, + Failure: totalFailure, + Timeout: totalTimeout, + }, + } + sumDatas = append(sumDatas, sumData) + } else { + for index, testStatDate := range testStatDateKeys { + for _, testStat := range testStatMap[testStatDate] { + totalSuccess += testStat.TotalSuccess + totalFailure += testStat.TotalFailure + totalTimeout += testStat.TotalTimeout + } + + if ((index + 1) % config.Day) == 0 { + day, err := time.ParseInLocation("2006-01-02", testStatDate, time.Local) + if err != nil { + log.Errorf("Failed to parse %s as time, err:", testStatDate, err) + return nil, err + } + sumData := &sumData{ + Day: day.Unix(), + CurrentDay: &CurrentDay{ + Success: totalSuccess, + Failure: totalFailure, + Timeout: totalTimeout, + }, + } + sumDatas = append(sumDatas, sumData) + totalSuccess = 0 + totalFailure = 0 + totalTimeout = 0 + } + } + } + + testTrend := &testTrend{ + CurrentDay: currDay, + Sum: sumDatas, + } + + return testTrend, nil +} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 8459a7fe..718cce61 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -32,7 +32,7 @@ import ( projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" servicehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/service/handler" settinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/setting/handler" - dashboardhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/dashboard/handler" + stathandler "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/handler" systemhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/system/handler" templatehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/handler" workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" @@ -85,7 +85,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/testing": new(testinghandler.Router), "/api/cluster": new(multiclusterhandler.Router), "/api/template": new(templatehandler.Router), - "/api/stat": new(dashboardhandler.Router), + "/api/stat": new(stathandler.Router), } { r.Inject(router.Group(name)) } -- Gitee From 792ee1ebd01220b28e2f429bfc0b9919aefca14a Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 24 Jan 2022 16:27:02 +0800 Subject: [PATCH 1392/2647] optimize http method Signed-off-by: allenshen --- .../aslan/core/stat/handler/router.go | 32 +++++++++---------- .../cron/core/service/client/stat.go | 12 +++---- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/handler/router.go b/pkg/microservice/aslan/core/stat/handler/router.go index 7adc0546..0b0aac6c 100644 --- a/pkg/microservice/aslan/core/stat/handler/router.go +++ b/pkg/microservice/aslan/core/stat/handler/router.go @@ -35,25 +35,25 @@ func (*Router) Inject(router *gin.RouterGroup) { { //buildquality quality.GET("/initBuildquality", GetAllPipelineTask) - quality.POST("/buildAverageMeasure", GetBuildDailyAverageMeasure) - quality.POST("/buildDailyMeasure", GetBuildDailyMeasure) - quality.POST("/buildHealthMeasure", GetBuildHealthMeasure) - quality.POST("/buildLatestTenMeasure", GetLatestTenBuildMeasure) - quality.POST("/buildTenDurationMeasure", GetTenDurationMeasure) - quality.POST("/buildTrend", GetBuildTrendMeasure) + quality.GET("/buildAverageMeasure", GetBuildDailyAverageMeasure) + quality.GET("/buildDailyMeasure", GetBuildDailyMeasure) + quality.GET("/buildHealthMeasure", GetBuildHealthMeasure) + quality.GET("/buildLatestTenMeasure", GetLatestTenBuildMeasure) + quality.GET("/buildTenDurationMeasure", GetTenDurationMeasure) + quality.GET("/buildTrend", GetBuildTrendMeasure) //testquality quality.GET("/initTestquality", InitTestStat) - quality.POST("/testAverageMeasure", GetTestAverageMeasure) - quality.POST("/testCaseMeasure", GetTestCaseMeasure) - quality.POST("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) - quality.POST("/testHealthMeasure", GetTestHealthMeasure) - quality.POST("/testTrend", GetTestTrendMeasure) + quality.GET("/testAverageMeasure", GetTestAverageMeasure) + quality.GET("/testCaseMeasure", GetTestCaseMeasure) + quality.GET("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) + quality.GET("/testHealthMeasure", GetTestHealthMeasure) + quality.GET("/testTrend", GetTestTrendMeasure) //deployquality quality.GET("/initDeployquality", InitDeployStat) - quality.POST("/pipelineHealthMeasure", GetPipelineHealthMeasure) - quality.POST("/deployHealthMeasure", GetDeployHealthMeasure) - quality.POST("/deployWeeklyMeasure", GetDeployWeeklyMeasure) - quality.POST("/deployTopFiveHigherMeasure", GetDeployTopFiveHigherMeasure) - quality.POST("/deployTopFiveFailureMeasure", GetDeployTopFiveFailureMeasure) + quality.GET("/pipelineHealthMeasure", GetPipelineHealthMeasure) + quality.GET("/deployHealthMeasure", GetDeployHealthMeasure) + quality.GET("/deployWeeklyMeasure", GetDeployWeeklyMeasure) + quality.GET("/deployTopFiveHigherMeasure", GetDeployTopFiveHigherMeasure) + quality.GET("/deployTopFiveFailureMeasure", GetDeployTopFiveFailureMeasure) } } diff --git a/pkg/microservice/cron/core/service/client/stat.go b/pkg/microservice/cron/core/service/client/stat.go index 17c3d147..b341e165 100644 --- a/pkg/microservice/cron/core/service/client/stat.go +++ b/pkg/microservice/cron/core/service/client/stat.go @@ -26,23 +26,23 @@ import ( func (c *Client) InitStatData(log *zap.SugaredLogger) error { //build - url := fmt.Sprintf("%s/api/quality/stat/initBuildStat", configbase.AslanxServiceAddress()) + url := fmt.Sprintf("%s/api/stat/quality/initBuildStat", configbase.AslanServiceAddress()) log.Info("start init buildStat..") - err := c.sendRequest(url) + _, err := c.sendPostRequest(url, nil, log) if err != nil { log.Errorf("trigger init buildStat error :%v", err) } //test - url = fmt.Sprintf("%s/api/quality/stat/initTestStat", configbase.AslanxServiceAddress()) + url = fmt.Sprintf("%s/api/stat/quality/initTestStat", configbase.AslanServiceAddress()) log.Info("start init testStat..") - err = c.sendRequest(url) + _, err = c.sendPostRequest(url, nil, log) if err != nil { log.Errorf("trigger init testStat error :%v", err) } //deploy - url = fmt.Sprintf("%s/api/quality/stat/initDeployStat", configbase.AslanxServiceAddress()) + url = fmt.Sprintf("%s/api/stat/quality/initDeployStat", configbase.AslanServiceAddress()) log.Info("start init deployStat..") - err = c.sendRequest(url) + _, err = c.sendPostRequest(url, nil, log) if err != nil { log.Errorf("trigger init deployStat error :%v", err) } -- Gitee From 3dfafaaaa220cd6b83142b14ccf7587784421a77 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 24 Jan 2022 16:53:59 +0800 Subject: [PATCH 1393/2647] update http method Signed-off-by: allenshen --- pkg/microservice/aslan/core/stat/handler/router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/handler/router.go b/pkg/microservice/aslan/core/stat/handler/router.go index 0b0aac6c..7c7eed14 100644 --- a/pkg/microservice/aslan/core/stat/handler/router.go +++ b/pkg/microservice/aslan/core/stat/handler/router.go @@ -34,7 +34,7 @@ func (*Router) Inject(router *gin.RouterGroup) { quality := router.Group("quality") { //buildquality - quality.GET("/initBuildquality", GetAllPipelineTask) + quality.POST("/initBuildquality", GetAllPipelineTask) quality.GET("/buildAverageMeasure", GetBuildDailyAverageMeasure) quality.GET("/buildDailyMeasure", GetBuildDailyMeasure) quality.GET("/buildHealthMeasure", GetBuildHealthMeasure) @@ -42,14 +42,14 @@ func (*Router) Inject(router *gin.RouterGroup) { quality.GET("/buildTenDurationMeasure", GetTenDurationMeasure) quality.GET("/buildTrend", GetBuildTrendMeasure) //testquality - quality.GET("/initTestquality", InitTestStat) + quality.POST("/initTestquality", InitTestStat) quality.GET("/testAverageMeasure", GetTestAverageMeasure) quality.GET("/testCaseMeasure", GetTestCaseMeasure) quality.GET("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) quality.GET("/testHealthMeasure", GetTestHealthMeasure) quality.GET("/testTrend", GetTestTrendMeasure) //deployquality - quality.GET("/initDeployquality", InitDeployStat) + quality.POST("/initDeployquality", InitDeployStat) quality.GET("/pipelineHealthMeasure", GetPipelineHealthMeasure) quality.GET("/deployHealthMeasure", GetDeployHealthMeasure) quality.GET("/deployWeeklyMeasure", GetDeployWeeklyMeasure) -- Gitee From 851cc741b9256547f40d671f0ba20c4ca31fb09a Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 24 Jan 2022 17:49:06 +0800 Subject: [PATCH 1394/2647] feature: create/update environment on existing namespace Signed-off-by: Min Min --- .../core/common/repository/models/product.go | 2 ++ .../core/common/repository/mongodb/product.go | 30 +++++++++++++++++++ .../aslan/core/environment/handler/kube.go | 3 +- .../service/environment_creator.go | 18 ++++++----- .../aslan/core/environment/service/kube.go | 10 ++++++- pkg/setting/consts.go | 5 ++++ 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index af7ecc6b..84b85773 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -51,6 +51,8 @@ type Product struct { Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` RegistryID string `bson:"registry_id" json:"registry_id"` + // IsExisted is true if this environment is created from an existing one + IsExisted bool `bson:"is_existed" json:"is_existed"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index eaefcbd8..98adf11c 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -23,8 +23,10 @@ import ( "time" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -368,3 +370,31 @@ func (c *ProductColl) UpdateAll(envs []*models.Product) error { return err } + +type nsObject struct { + ID primitive.ObjectID `bson:"_id"` + Namespace string `bson:"namespace"` +} + +func (c *ProductColl) ListExistedNamespace() ([]string, error) { + nsList := make([]*nsObject, 0) + resp := sets.NewString() + selector := bson.D{ + {"namespace", 1}, + } + query := bson.M{"is_existed": true} + opt := options.Find() + opt.SetProjection(selector) + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &nsList) + if err != nil { + return nil, err + } + for _, obj := range nsList { + resp.Insert(obj.Namespace) + } + return resp.List(), nil +} diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index 869fae79..2a66ea01 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -55,7 +55,8 @@ func ListAvailableNamespaces(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() clusterID := c.Query("clusterId") - ctx.Resp, ctx.Err = service.ListAvailableNamespaces(clusterID, ctx.Logger) + listType := c.Query("type") + ctx.Resp, ctx.Err = service.ListAvailableNamespaces(clusterID, listType, ctx.Logger) } func ListServicePods(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index e9811f2a..b57f6014 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -317,14 +317,16 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model if args.Namespace == "" { args.Namespace = namespace } - _, found, err := getter.GetNamespace(args.Namespace, kubeClient) - if err != nil { - log.Errorf("GetNamespace error: %v", err) - return e.ErrCreateEnv.AddDesc(err.Error()) - } - if found { - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("%s[%s]%s", "namespace", args.Namespace, "已经存在,请换个环境名称尝试!")) - } + // todo DELETE THIS PART WHEN EXISTING NAMESAPCE IS SUPPORTED + //_, found, err := getter.GetNamespace(args.Namespace, kubeClient) + //if err != nil { + // log.Errorf("GetNamespace error: %v", err) + // return e.ErrCreateEnv.AddDesc(err.Error()) + //} + // + //if found { + // return e.ErrCreateEnv.AddDesc(fmt.Sprintf("%s[%s]%s", "namespace", args.Namespace, "已经存在,请换个环境名称尝试!")) + //} //创建角色环境之间的关联关系 //todo 创建环境暂时不指定角色 diff --git a/pkg/microservice/aslan/core/environment/service/kube.go b/pkg/microservice/aslan/core/environment/service/kube.go index 8bd9222b..7030a1a3 100644 --- a/pkg/microservice/aslan/core/environment/service/kube.go +++ b/pkg/microservice/aslan/core/environment/service/kube.go @@ -111,7 +111,7 @@ func ListPodEvents(envName, productName, podName string, log *zap.SugaredLogger) } // ListAvailableNamespaces lists available namespaces created by non-koderover -func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { +func ListAvailableNamespaces(clusterID, listType string, log *zap.SugaredLogger) ([]*resource.Namespace, error) { resp := make([]*resource.Namespace, 0) kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { @@ -127,6 +127,14 @@ func ListAvailableNamespaces(clusterID string, log *zap.SugaredLogger) ([]*resou return resp, err } filterK8sNamespaces := sets.NewString("kube-node-lease", "kube-public", "kube-system") + if listType == setting.ListNamespaceTypeCreate { + nsList, err := commonrepo.NewProductColl().ListExistedNamespace() + if err != nil { + log.Errorf("Failed to list existed namespace from the env List, error: %s", err) + return nil, err + } + filterK8sNamespaces.Insert(nsList...) + } for _, namespace := range namespaces { if value, IsExist := namespace.Labels[setting.EnvCreatedBy]; IsExist { if value == setting.EnvCreator { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index e0273b8c..98188f5f 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -594,3 +594,8 @@ const RequestModeOpenAPI = "openAPI" const DeployTimeout = 60 * 10 // 10 minutes const UpdateEnvTimeout = 60 * 5 * time.Second + +// list namespace type +const ( + ListNamespaceTypeCreate = "create" +) -- Gitee From c6379badf15c5abfbbc4fe26bb39b73ff40fb1ce Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 24 Jan 2022 18:59:15 +0800 Subject: [PATCH 1395/2647] add sync resource Signed-off-by: panxunying --- .../aslan/core/collaboration/config/consts.go | 7 + .../service/collaboration_instance.go | 132 +++++++++++++++++- .../common/repository/mongodb/workflow.go | 16 +++ .../core/environment/service/environment.go | 94 +++++++++++++ .../workflow/service/workflow/workflow.go | 41 ++++++ 5 files changed, 289 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/collaboration/config/consts.go b/pkg/microservice/aslan/core/collaboration/config/consts.go index 507a29b5..3ef58efd 100644 --- a/pkg/microservice/aslan/core/collaboration/config/consts.go +++ b/pkg/microservice/aslan/core/collaboration/config/consts.go @@ -6,3 +6,10 @@ const ( CollaborationShare CollaborationType = "share" CollaborationNew CollaborationType = "new" ) + +type DeployType string + +const ( + K8sDeploy DeployType = "k8s" + HelmDeploy DeployType = "helm" +) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index cc99c9c9..2f8390a3 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -14,7 +14,9 @@ import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + service2 "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/util" @@ -81,6 +83,11 @@ type GetCollaborationNewResp struct { Product []*Product `json:"product"` } +type GetCollaborationDeleteResp struct { + Workflows []string + Products []string +} + func getUpdateWorkflowDiff(cmwMap map[string]models.WorkflowCMItem, ciwMap map[string]models.WorkflowCIItem) ( []UpdateWorkflowItem, []models.WorkflowCMItem, []models.WorkflowCIItem) { var updateWorkflowItems []UpdateWorkflowItem @@ -608,7 +615,104 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin return nil } -func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.SugaredLogger) error { +func syncResource(products SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, + logger *zap.SugaredLogger) error { + err := syncNewResource(products, updateResp, projectName, userName, requestID, logger) + if err != nil { + return err + } + err = syncDeleteResource(updateResp, userName, projectName, requestID, logger) + if err != nil { + return err + } + return nil +} + +func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projectName, requestID string, + log *zap.SugaredLogger) (err error) { + deleteResp := getCollaborationDelete(updateResp) + for _, product := range deleteResp.Products { + err := commonservice.DeleteProduct(username, product, projectName, requestID, log) + if err != nil { + return err + } + } + for _, workflow := range deleteResp.Workflows { + err := commonservice.DeleteWorkflow(workflow, requestID, false, log) + if err != nil { + return err + } + } + return nil +} + +func syncNewResource(products SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, + logger *zap.SugaredLogger) error { + newResp, err := getCollaborationNew(updateResp, projectName, userName, logger) + if err != nil { + return err + } + var newWorkflows []workflowservice.WorkflowCopyItem + for _, workflow := range newResp.Workflow { + if workflow.CollaborationType == config.CollaborationNew { + newWorkflows = append(newWorkflows, workflowservice.WorkflowCopyItem{ + ProjectName: projectName, + Old: workflow.BaseName, + New: workflow.Name, + }) + } + } + err = workflowservice.BulkCopyWorkflow(workflowservice.BulkCopyWorkflowArgs{ + Items: newWorkflows, + }, userName, logger) + if err != nil { + return err + } + productMap := make(map[string]Product) + for _, product := range products.Products { + productMap[product.BaseName] = product + } + var yamlProductItems []service2.YamlProductItem + var helmProductArgs []service2.HelmProductItem + for _, product := range newResp.Product { + if productArg, ok := productMap[product.BaseName]; ok { + if product.DeployType == string(config.HelmDeploy) { + yamlProductItems = append(yamlProductItems, service2.YamlProductItem{ + OldName: product.BaseName, + NewName: product.Name, + Vars: productArg.Vars, + }) + } + if product.DeployType == string(config.K8sDeploy) { + helmProductArgs = append(helmProductArgs, service2.HelmProductItem{ + OldName: product.BaseName, + NewName: product.Name, + DefaultValues: product.DefaultValues, + ChartValues: product.ChartValues, + }) + } + } + } + err = service2.BulkCopyYamlProduct(projectName, userName, requestID, service2.CopyYamlProductArg{ + Items: yamlProductItems, + }, logger) + if err != nil { + return err + } + err = service2.BulkCopyHelmProduct(projectName, userName, requestID, service2.CopyHelmProductArg{ + Items: helmProductArgs, + }, logger) + if err != nil { + return err + } + return nil +} + +type SyncCollaborationInstanceArgs struct { + Products []Product `json:"products"` +} + +func SyncCollaborationInstance(products SyncCollaborationInstanceArgs, projectName, uid, userName, requestID string, logger *zap.SugaredLogger) error { updateResp, err := GetCollaborationUpdate(projectName, uid, userName, logger) if err != nil { logger.Errorf("GetCollaborationNew error, err msg:%s", err) @@ -618,6 +722,7 @@ func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.Su if err != nil { return err } + err = syncResource(products, updateResp, projectName, userName, requestID, logger) err = syncPolicy(updateResp, modeInstanceMap, projectName, userName, logger) if err != nil { return err @@ -626,6 +731,31 @@ func SyncCollaborationInstance(projectName, uid, userName string, logger *zap.Su return nil } +func getCollaborationDelete(updateResp *GetCollaborationUpdateResp) *GetCollaborationDeleteResp { + productSet := sets.String{} + workflowSet := sets.String{} + for _, item := range updateResp.Delete { + for _, product := range item.Products { + productSet.Insert(product.Name) + } + for _, workflow := range item.Workflows { + workflowSet.Insert(workflow.Name) + } + } + for _, item := range updateResp.Update { + for _, deleteWorkflow := range item.DeleteSpec.Workflows { + workflowSet.Insert(deleteWorkflow.Name) + } + for _, deleteProduct := range item.DeleteSpec.Products { + productSet.Insert(deleteProduct.Name) + } + } + return &GetCollaborationDeleteResp{ + Workflows: workflowSet.List(), + Products: productSet.List(), + } +} + func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { var newWorkflow []*Workflow diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index c3bb2ff8..b3f472cc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -195,6 +195,22 @@ func (c *WorkflowColl) Create(args *models.Workflow) error { return err } +func (c *WorkflowColl) BulkCreate(args []*models.Workflow) error { + if len(args) == 0 { + return nil + } + for _, arg := range args { + arg.CreateTime = time.Now().Unix() + arg.UpdateTime = time.Now().Unix() + } + var ois []interface{} + for _, obj := range args { + ois = append(ois, obj) + } + _, err := c.InsertMany(context.TODO(), ois) + return err +} + func (c *WorkflowColl) Replace(args *models.Workflow) error { if args == nil { return errors.New("nil Workflow args") diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5d9acb47..1d74d9e4 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -830,6 +830,100 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, return CreateProduct(userName, requestID, productObj, log) } +type YamlProductItem struct { + OldName string + NewName string + Vars []*templatemodels.RenderKV +} +type CopyYamlProductArg struct { + Items []YamlProductItem +} + +type HelmProductItem struct { + OldName string + NewName string + DefaultValues string + ChartValues []*commonservice.RenderChartArg +} + +type CopyHelmProductArg struct { + Items []HelmProductItem +} + +func BulkCopyHelmProduct(projectName, user, requestID string, arg CopyHelmProductArg, log *zap.SugaredLogger) error { + if len(arg.Items) == 0 { + return nil + } + var envs []string + for _, item := range arg.Items { + envs = append(envs, item.OldName) + } + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: projectName, + InEnvs: envs, + }) + if err != nil { + return err + } + productMap := make(map[string]*commonmodels.Product) + for _, product := range products { + productMap[product.EnvName] = product + } + var args []*CreateHelmProductArg + for _, item := range arg.Items { + if product, ok := productMap[item.OldName]; ok { + args = append(args, &CreateHelmProductArg{ + ProductName: projectName, + EnvName: item.NewName, + Namespace: product.Namespace, + ClusterID: product.ClusterID, + DefaultValues: item.DefaultValues, + RegistryID: product.RegistryID, + BaseEnvName: product.BaseName, + ChartValues: item.ChartValues, + }) + } else { + return fmt.Errorf("product:%s not exist", item.OldName) + } + } + return CopyHelmProduct(projectName, user, requestID, args, log) +} + +func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProductArg, log *zap.SugaredLogger) error { + if len(arg.Items) == 0 { + return nil + } + var envs []string + for _, item := range arg.Items { + envs = append(envs, item.OldName) + } + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: projectName, + InEnvs: envs, + }) + if err != nil { + return err + } + productMap := make(map[string]*commonmodels.Product) + for _, product := range products { + productMap[product.EnvName] = product + } + + for _, item := range arg.Items { + if product, ok := productMap[item.OldName]; ok { + product.EnvName = item.NewName + product.Vars = item.Vars + err = CreateProduct(user, requestID, product, log) + if err != nil { + return err + } + } else { + return fmt.Errorf("product:%s not exist", item.OldName) + } + } + return nil +} + // CreateProduct create a new product with its dependent stacks func CreateProduct(user, requestID string, args *commonmodels.Product, log *zap.SugaredLogger) (err error) { log.Infof("[%s][P:%s] CreateProduct", args.EnvName, args.ProductName) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 6d8c7d36..726763fa 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -667,3 +667,44 @@ func CopyWorkflow(oldWorkflowName, newWorkflowName, username string, log *zap.Su return commonrepo.NewWorkflowColl().Create(oldWorkflow) } + +type WorkflowCopyItem struct { + ProjectName string + Old string + New string +} + +type BulkCopyWorkflowArgs struct { + Items []WorkflowCopyItem `json:"items"` +} + +func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.SugaredLogger) error { + var workflows []commonrepo.Workflow + workflowMap := make(map[string]string) + for _, item := range args.Items { + workflows = append(workflows, commonrepo.Workflow{ + ProjectName: item.ProjectName, + Name: item.Old, + }) + workflowMap[item.ProjectName+"~"+item.Old] = item.New + } + oldWorkflows, err := commonrepo.NewWorkflowColl().ListByWorkflows(commonrepo.ListWorkflowOpt{ + Workflows: workflows, + }) + if err != nil { + log.Error(err) + return e.ErrGetPipeline.AddErr(err) + } + var newWorkflows []*commonmodels.Workflow + for _, workflow := range oldWorkflows { + if newName, ok := workflowMap[workflow.ProductTmplName+"~"+workflow.Name]; ok { + workflow.UpdateBy = username + workflow.Name = newName + workflow.ID = primitive.NewObjectID() + newWorkflows = append(newWorkflows, workflow) + } else { + return fmt.Errorf("workflow:%s not exist", workflow.ProductTmplName+"~"+workflow.Name) + } + } + return commonrepo.NewWorkflowColl().BulkCreate(newWorkflows) +} -- Gitee From c4a933af02b0a5418bac37e23cbdbab32df4a684 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 24 Jan 2022 19:15:51 +0800 Subject: [PATCH 1396/2647] add sync resource Signed-off-by: panxunying --- .../core/collaboration/handler/collaboration_instance.go | 7 ++++++- .../aslan/core/collaboration/handler/router.go | 1 + .../core/collaboration/service/collaboration_instance.go | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index 3bd4bdb6..a56edca2 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -39,9 +39,14 @@ func SyncCollaborationInstance(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") + args := new(service.SyncCollaborationInstanceArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } if projectName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - ctx.Err = service.SyncCollaborationInstance(projectName, ctx.UserID, ctx.UserName, ctx.Logger) + ctx.Err = service.SyncCollaborationInstance(args, projectName, ctx.UserID, ctx.UserName, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 16e6e4f5..f83811f1 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -33,5 +33,6 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations.POST("", gin2.UpdateOperationLogStatus, CreateCollaborationMode) collaborations.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) collaborations.GET("/new", GetCollaborationNew) + collaborations.POST("/sync", SyncCollaborationInstance) } } diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 2f8390a3..a9cb6730 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -615,7 +615,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin return nil } -func syncResource(products SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, +func syncResource(products *SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, logger *zap.SugaredLogger) error { err := syncNewResource(products, updateResp, projectName, userName, requestID, logger) if err != nil { @@ -646,7 +646,7 @@ func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projec return nil } -func syncNewResource(products SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, +func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, logger *zap.SugaredLogger) error { newResp, err := getCollaborationNew(updateResp, projectName, userName, logger) if err != nil { @@ -712,7 +712,7 @@ type SyncCollaborationInstanceArgs struct { Products []Product `json:"products"` } -func SyncCollaborationInstance(products SyncCollaborationInstanceArgs, projectName, uid, userName, requestID string, logger *zap.SugaredLogger) error { +func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectName, uid, userName, requestID string, logger *zap.SugaredLogger) error { updateResp, err := GetCollaborationUpdate(projectName, uid, userName, logger) if err != nil { logger.Errorf("GetCollaborationNew error, err msg:%s", err) -- Gitee From c3231dbeb29410237ac169e5eac80f1104b5f9ad Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 19:41:37 +0800 Subject: [PATCH 1397/2647] repo add committer Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gerrit_workflow_task.go | 2 +- .../core/workflow/service/webhook/github_workflow_task.go | 3 ++- .../core/workflow/service/webhook/gitlab_workflow_task.go | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index dd34a412..37c255c0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -223,7 +223,7 @@ func (gpcem *gerritPatchsetCreatedEventMatcher) Match(hookRepo *commonmodels.Mai existEventNames = append(existEventNames, string(eventName)) } if sets.NewString(existEventNames...).Has(event.Type) { - hookRepo.Committer = event.PatchSet.Author.Username + hookRepo.Committer = event.Uploader.Username return true, nil } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index bded5564..65625126 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -70,7 +70,7 @@ func (gpem *githubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( } } hookRepo.Branch = getBranchFromRef(*ev.Ref) - hookRepo.Committer = *ev.Pusher.Login + hookRepo.Committer = *ev.Pusher.Name var changedFiles []string for _, commit := range ev.Commits { changedFiles = append(changedFiles, commit.Added...) @@ -257,6 +257,7 @@ func (gtem githubTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } } hookRepo.Tag = getTagFromRef(*ev.Ref) + hookRepo.Committer = *ev.Sender.Name return true, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 09a9244c..5085e049 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -319,6 +319,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } } + hookRepo.Committer = ev.UserName hookRepo.Tag = getTagFromRef(ev.Ref) if ev.Before == commitID || ev.After == commitID { return true, nil -- Gitee From 0c0649837637ce6e94470df7733a61b3641ce2f6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 19:51:39 +0800 Subject: [PATCH 1398/2647] add role env Signed-off-by: mouuii --- .../policy/core/service/permission.go | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 89006d37..2630ce43 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -17,9 +17,14 @@ limitations under the License. package service import ( + "fmt" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/setting" ) @@ -104,15 +109,48 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, } for _, role := range roles { if role.Name == string(setting.SystemAdmin) || role.Name == string(setting.ProjectAdmin) { - resourceM["*"] = sets.NewString("*") + for k, _ := range resourceM { + resourceM[k] = sets.NewString("*") + } break } for _, rule := range role.Rules { - if rule.Resources[0] == resourceType { + if rule.Resources[0] == resourceType && resourceType != string(config.ResourceTypeProduct) { for k, v := range resourceM { resourceM[k] = v.Insert(rule.Verbs...) } } + if (rule.Resources[0] == "Environment" || rule.Resources[0] == "ProductionEnvironment") && resourceType == string(config.ResourceTypeProduct) { + var rs []mongodb.Resource + for _, v := range resources { + r := mongodb.Resource{ + Name: v, + ProjectName: projectName, + Type: string(config.ResourceTypeProduct), + } + rs = append(rs, r) + } + labels, err := service.ListLabelsByResources(rs, logger) + if err != nil { + continue + } + for _, resource := range resources { + resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeProduct, projectName, resource) + if labels, ok := labels.Labels[resourceKey]; ok { + for _, label := range labels { + if label.Key == "production" { + if rule.Resources[0] == "Environment" && label.Value == "false" { + resourceM[resource] = resourceM[resource].Insert(rule.Verbs...) + } + if rule.Resources[0] == "ProductionEnvironment" && label.Value == "true" { + resourceM[resource] = resourceM[resource].Insert(rule.Verbs...) + } + } + } + } + } + + } } } resourceRes := make(map[string][]string) -- Gitee From cec877d16f181934712bbfe1af4107737e0bc6b1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 20:04:35 +0800 Subject: [PATCH 1399/2647] merge dashboard code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/service/build.go | 2 +- pkg/microservice/aslan/core/stat/service/deploy.go | 2 +- pkg/microservice/aslan/core/stat/service/overview.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/service/build.go b/pkg/microservice/aslan/core/stat/service/build.go index 7b09064f..f589f1e2 100644 --- a/pkg/microservice/aslan/core/stat/service/build.go +++ b/pkg/microservice/aslan/core/stat/service/build.go @@ -28,7 +28,7 @@ import ( type dashboardBuild struct { Total int `json:"total"` Success int `json:"success"` - DashboardBuildDailys []*dashboardBuildDaily `json:"data,omitempty"` + DashboardBuildDailys []*dashboardBuildDaily `json:"data"` } type dashboardBuildDaily struct { diff --git a/pkg/microservice/aslan/core/stat/service/deploy.go b/pkg/microservice/aslan/core/stat/service/deploy.go index d05a4d2a..7051262c 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy.go +++ b/pkg/microservice/aslan/core/stat/service/deploy.go @@ -28,7 +28,7 @@ import ( type dashboardDeploy struct { Total int `json:"total"` Success int `json:"success"` - DashboardDeployDailys []*dashboardDeployDaily `json:"data,omitempty"` + DashboardDeployDailys []*dashboardDeployDaily `json:"data"` } type dashboardDeployDaily struct { diff --git a/pkg/microservice/aslan/core/stat/service/overview.go b/pkg/microservice/aslan/core/stat/service/overview.go index b7790e7d..42fe239d 100644 --- a/pkg/microservice/aslan/core/stat/service/overview.go +++ b/pkg/microservice/aslan/core/stat/service/overview.go @@ -76,5 +76,5 @@ func GetOverviewStat(log *zap.SugaredLogger) (*Overview, error) { } overview.ServiceCount = int(serviceCount) - return nil, nil + return overview, nil } -- Gitee From d6e24efb207b7e2a17b951bcaaf4a23c59028172 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 24 Jan 2022 20:52:58 +0800 Subject: [PATCH 1400/2647] fix bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/label/service/label.go | 1 + pkg/microservice/policy/core/service/permission.go | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index b74a8246..aec9844f 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -149,6 +149,7 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg if err != nil { return nil, err } + //2.find labels by labelBindings labelIDSet := sets.NewString() for _, v := range labelBindings { diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 2630ce43..8479c9c4 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -23,9 +23,8 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/label" ) // GetPermission user's permission for frontend @@ -121,22 +120,23 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, } } if (rule.Resources[0] == "Environment" || rule.Resources[0] == "ProductionEnvironment") && resourceType == string(config.ResourceTypeProduct) { - var rs []mongodb.Resource + var rs []label.Resource for _, v := range resources { - r := mongodb.Resource{ + r := label.Resource{ Name: v, ProjectName: projectName, Type: string(config.ResourceTypeProduct), } rs = append(rs, r) } - labels, err := service.ListLabelsByResources(rs, logger) + + labelRes, err := label.New().ListLabelsByResources(label.ListLabelsByResourcesReq{rs}) if err != nil { continue } for _, resource := range resources { resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeProduct, projectName, resource) - if labels, ok := labels.Labels[resourceKey]; ok { + if labels, ok := labelRes.Labels[resourceKey]; ok { for _, label := range labels { if label.Key == "production" { if rule.Resources[0] == "Environment" && label.Value == "false" { -- Gitee From 559c18ba9f8b884bfbd0c5993547648e4aee84de Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 24 Jan 2022 21:08:39 +0800 Subject: [PATCH 1401/2647] fix webhook remained bug when deleting product Signed-off-by: allenshen --- pkg/microservice/aslan/core/project/service/product.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 64de1b48..8ea435ed 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -420,9 +420,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug services, _ := commonrepo.NewServiceColl().ListMaxRevisions( &commonrepo.ServiceListOption{ProductName: productName, Type: setting.K8SDeployType}, ) - for _, s := range services { - commonservice.ProcessServiceWebhook(nil, s, s.ServiceName, log) - } //删除交付中心 //删除构建/删除测试/删除服务 @@ -432,7 +429,13 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug _ = commonrepo.NewServiceColl().Delete("", "", productName, "", 0) _ = commonservice.DeleteDeliveryInfos(productName, log) _ = DeleteProductsAsync(userName, productName, requestID, log) + + // delete service webhooks after services are deleted + for _, s := range services { + commonservice.ProcessServiceWebhook(nil, s, s.ServiceName, log) + } }() + // 删除workload go func() { workloads, _ := commonrepo.NewWorkLoadsStatColl().FindByProductName(productName) -- Gitee From 798f3c99c655d4a72a72d29b492e041130ed9af3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 21:25:19 +0800 Subject: [PATCH 1402/2647] fix ascode tag trigger bug Signed-off-by: liu deyi --- .../service/webhook/gitlab_workflow_task.go | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 09a9244c..a0553196 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -279,8 +279,6 @@ func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( return args } -const commitID = "0000000000000000000000000000000000000000" - type gitlabTagEventMatcher struct { log *zap.SugaredLogger workflow *commonmodels.Workflow @@ -320,38 +318,7 @@ func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bo } hookRepo.Tag = getTagFromRef(ev.Ref) - if ev.Before == commitID || ev.After == commitID { - return true, nil - } - - var changedFiles []string - detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) - if err != nil { - gtem.log.Errorf("GetCodehostDetail error: %s", err) - return false, err - } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken) - if err != nil { - gtem.log.Errorf("NewClient error: %s", err) - return false, err - } - - // The compare interface gets the final changes between the two commits - diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) - if err != nil { - gtem.log.Errorf("Failed to get tag event diffs, error: %s", err) - return false, err - } - for _, diff := range diffs { - changedFiles = append(changedFiles, diff.NewPath) - changedFiles = append(changedFiles, diff.OldPath) - } - if gtem.isYaml { - serviceChangeds := ServicesMatchChangesFiles(gtem.trigger.Rules.MatchFolders, changedFiles) - gtem.yamlServiceChanged = serviceChangeds - return len(serviceChangeds) != 0, nil - } return true, nil } @@ -362,11 +329,7 @@ func (gtem gitlabTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, if gtem.isYaml { var targets []*commonmodels.TargetArgs for _, target := range args.Target { - for _, bs := range gtem.yamlServiceChanged { - if target.Name == bs.ServiceModule && target.ServiceName == bs.Name { - targets = append(targets, target) - } - } + targets = append(targets, target) } args.Target = targets } -- Gitee From ae6fa30bacfd64995eb7c79900dea52186a8e686 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 21:44:33 +0800 Subject: [PATCH 1403/2647] modify method Signed-off-by: liu deyi --- .../aslan/core/stat/handler/router.go | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/handler/router.go b/pkg/microservice/aslan/core/stat/handler/router.go index 7c7eed14..b48d4515 100644 --- a/pkg/microservice/aslan/core/stat/handler/router.go +++ b/pkg/microservice/aslan/core/stat/handler/router.go @@ -35,25 +35,25 @@ func (*Router) Inject(router *gin.RouterGroup) { { //buildquality quality.POST("/initBuildquality", GetAllPipelineTask) - quality.GET("/buildAverageMeasure", GetBuildDailyAverageMeasure) - quality.GET("/buildDailyMeasure", GetBuildDailyMeasure) - quality.GET("/buildHealthMeasure", GetBuildHealthMeasure) - quality.GET("/buildLatestTenMeasure", GetLatestTenBuildMeasure) - quality.GET("/buildTenDurationMeasure", GetTenDurationMeasure) - quality.GET("/buildTrend", GetBuildTrendMeasure) + quality.POST("/buildAverageMeasure", GetBuildDailyAverageMeasure) + quality.POST("/buildDailyMeasure", GetBuildDailyMeasure) + quality.POST("/buildHealthMeasure", GetBuildHealthMeasure) + quality.POST("/buildLatestTenMeasure", GetLatestTenBuildMeasure) + quality.POST("/buildTenDurationMeasure", GetTenDurationMeasure) + quality.POST("/buildTrend", GetBuildTrendMeasure) //testquality quality.POST("/initTestquality", InitTestStat) - quality.GET("/testAverageMeasure", GetTestAverageMeasure) - quality.GET("/testCaseMeasure", GetTestCaseMeasure) - quality.GET("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) - quality.GET("/testHealthMeasure", GetTestHealthMeasure) - quality.GET("/testTrend", GetTestTrendMeasure) + quality.POST("/testAverageMeasure", GetTestAverageMeasure) + quality.POST("/testCaseMeasure", GetTestCaseMeasure) + quality.POST("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) + quality.POST("/testHealthMeasure", GetTestHealthMeasure) + quality.POST("/testTrend", GetTestTrendMeasure) //deployquality quality.POST("/initDeployquality", InitDeployStat) - quality.GET("/pipelineHealthMeasure", GetPipelineHealthMeasure) - quality.GET("/deployHealthMeasure", GetDeployHealthMeasure) - quality.GET("/deployWeeklyMeasure", GetDeployWeeklyMeasure) - quality.GET("/deployTopFiveHigherMeasure", GetDeployTopFiveHigherMeasure) - quality.GET("/deployTopFiveFailureMeasure", GetDeployTopFiveFailureMeasure) + quality.POST("/pipelineHealthMeasure", GetPipelineHealthMeasure) + quality.POST("/deployHealthMeasure", GetDeployHealthMeasure) + quality.POST("/deployWeeklyMeasure", GetDeployWeeklyMeasure) + quality.POST("/deployTopFiveHigherMeasure", GetDeployTopFiveHigherMeasure) + quality.POST("/deployTopFiveFailureMeasure", GetDeployTopFiveFailureMeasure) } } -- Gitee From 93acda219ff2ca32818d79d26104d7406cde6351 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 24 Jan 2022 21:54:34 +0800 Subject: [PATCH 1404/2647] modify api path and deleting unused code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/handler/router.go | 12 ++++++------ .../aslan/core/stat/handler/test_stat.go | 1 - .../aslan/core/stat/repository/mongodb/build.go | 2 -- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/handler/router.go b/pkg/microservice/aslan/core/stat/handler/router.go index b48d4515..fa409dbb 100644 --- a/pkg/microservice/aslan/core/stat/handler/router.go +++ b/pkg/microservice/aslan/core/stat/handler/router.go @@ -33,23 +33,23 @@ func (*Router) Inject(router *gin.RouterGroup) { quality := router.Group("quality") { - //buildquality - quality.POST("/initBuildquality", GetAllPipelineTask) + //buildStat + quality.POST("/initBuildStat", GetAllPipelineTask) quality.POST("/buildAverageMeasure", GetBuildDailyAverageMeasure) quality.POST("/buildDailyMeasure", GetBuildDailyMeasure) quality.POST("/buildHealthMeasure", GetBuildHealthMeasure) quality.POST("/buildLatestTenMeasure", GetLatestTenBuildMeasure) quality.POST("/buildTenDurationMeasure", GetTenDurationMeasure) quality.POST("/buildTrend", GetBuildTrendMeasure) - //testquality - quality.POST("/initTestquality", InitTestStat) + //testStat + quality.POST("/initTestStat", InitTestStat) quality.POST("/testAverageMeasure", GetTestAverageMeasure) quality.POST("/testCaseMeasure", GetTestCaseMeasure) quality.POST("/testDeliveryDeploy", GetTestDeliveryDeployMeasure) quality.POST("/testHealthMeasure", GetTestHealthMeasure) quality.POST("/testTrend", GetTestTrendMeasure) - //deployquality - quality.POST("/initDeployquality", InitDeployStat) + //deployStat + quality.POST("/initDeployStat", InitDeployStat) quality.POST("/pipelineHealthMeasure", GetPipelineHealthMeasure) quality.POST("/deployHealthMeasure", GetDeployHealthMeasure) quality.POST("/deployWeeklyMeasure", GetDeployWeeklyMeasure) diff --git a/pkg/microservice/aslan/core/stat/handler/test_stat.go b/pkg/microservice/aslan/core/stat/handler/test_stat.go index 16ab5612..49417ccb 100644 --- a/pkg/microservice/aslan/core/stat/handler/test_stat.go +++ b/pkg/microservice/aslan/core/stat/handler/test_stat.go @@ -27,7 +27,6 @@ import ( func InitTestStat(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - //params validate ctx.Err = service.InitTestStat(ctx.Logger) } diff --git a/pkg/microservice/aslan/core/stat/repository/mongodb/build.go b/pkg/microservice/aslan/core/stat/repository/mongodb/build.go index b3072b13..7d32bf81 100644 --- a/pkg/microservice/aslan/core/stat/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/stat/repository/mongodb/build.go @@ -29,14 +29,12 @@ import ( mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -// BuildPipeResp ... type BuildPipeResp struct { ID BuildItem `bson:"_id" json:"_id"` TotalSuccess int `bson:"total_success" json:"total_success"` TotalBuildCount int `bson:"total_build_count" json:"total_build_count"` } -// BuildItem ... type BuildItem struct { Date string `bson:"date" json:"date"` TotalSuccess int `bson:"total_success" json:"total_success"` -- Gitee From b45ce89278f9450cbf18c55b76feb2ae12c49cdd Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 20 Jan 2022 17:21:25 +0800 Subject: [PATCH 1405/2647] increase buffer for webhook trigger channel Signed-off-by: allenshen --- .../aslan/core/common/service/webhook/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index 0297b64c..6a7bd119 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -50,7 +50,7 @@ var c *controller func webhookController() *controller { once.Do(func() { c = &controller{ - queue: make(chan *task, 100), + queue: make(chan *task, 500), logger: log.Logger(), } }) -- Gitee From 54a01af3b81bf3e42cac85f4caa1312b4b4b8231 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 25 Jan 2022 11:42:13 +0800 Subject: [PATCH 1406/2647] mask secret envs when running user's script Signed-off-by: zhangyifei --- pkg/microservice/reaper/core/service/reaper/script.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 43a3970d..dcc88010 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -431,7 +431,7 @@ func (r *Reaper) handleCmdOutput(pipe io.ReadCloser, needPersistentLog bool, log break } - fmt.Printf("%s", string(lineBytes)) + fmt.Printf("%s", r.maskSecretEnvs(string(lineBytes))) if needPersistentLog { err := util.WriteFile(logFile, lineBytes, 0700) -- Gitee From f80241f95f538cde077f87662b5ec4d78fe56a91 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 25 Jan 2022 13:38:32 +0800 Subject: [PATCH 1407/2647] optimize Signed-off-by: panxunying --- .../core/collaboration/service/collaboration_instance.go | 2 +- pkg/middleware/gin/collaboration.go | 2 +- pkg/shared/client/policy/policy.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index a9cb6730..7d8cdba0 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -371,7 +371,7 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[stri Rules: rules, }) } - err := policy.NewDefault().CreatePolicies(policy.CreatePoliciesArgs{ + err := policy.NewDefault().CreatePolicies(projectName, policy.CreatePoliciesArgs{ Policies: policies, }) if err != nil { diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index 871112dd..2611325c 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -13,7 +13,7 @@ func GetCollaborationNew() gin.HandlerFunc { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" { + if projectName == "" || c.Request.URL.Path == "/api/collaboration/collaborations/sync" { c.Next() return } diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index dff99de2..0f4dd76a 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -49,8 +49,8 @@ type DeletePoliciesArgs struct { Names []string `json:"names"` } -func (c *Client) CreatePolicies(request CreatePoliciesArgs) error { - url := "/policies" +func (c *Client) CreatePolicies(ns string, request CreatePoliciesArgs) error { + url := fmt.Sprintf("/policies?projectName=%s", ns) _, err := c.Post(url, httpclient.SetBody(request)) if err != nil { -- Gitee From c0687cb11a58d2f88a5c22f70cc363e4baeeb3df Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 25 Jan 2022 14:01:14 +0800 Subject: [PATCH 1408/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/cron/core/service/scheduler/scheduler.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 5bcb91f3..ddab8b24 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -72,7 +72,6 @@ const ( SystemCapacityGC = "SystemCapacityGC" //InitHealthCheckScheduler InitHealthCheckScheduler = "InitHealthCheckScheduler" - ) // NewCronClient ... @@ -140,7 +139,7 @@ func (c *CronClient) Init() { // 自由编排工作流定时任务触发 cl := configclient.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType);err ==nil && enable{ + if enable, err := cl.CheckFeature(setting.ModernWorkflowType); err == nil && enable { c.InitColliePipelineScheduler() } @@ -156,7 +155,6 @@ func (c *CronClient) Init() { c.InitHealthCheckScheduler() } - // InitCleanJobScheduler ... func (c *CronClient) InitCleanJobScheduler() { @@ -212,7 +210,9 @@ func (c *CronClient) InitBuildStatScheduler() { c.Schedulers[InitStatScheduler] = gocron.NewScheduler() - c.Schedulers[InitStatScheduler].Every(1).Day().At("01:00").Do(c.AslanCli.InitStatData, c.log) + //c.Schedulers[InitStatScheduler].Every(1).Day().At("01:00").Do(c.AslanCli.InitStatData, c.log) + + c.Schedulers[InitStatScheduler].Every(10).Minutes().Do(c.AslanCli.InitStatData, c.log) c.Schedulers[InitStatScheduler].Start() } -- Gitee From 7df924ab941b952fcbb7795dfb06cf7e05fbd8df Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 25 Jan 2022 14:35:25 +0800 Subject: [PATCH 1409/2647] Supports multiple rollback policies for image versions Signed-off-by: ddh27 --- .../core/common/repository/models/queue.go | 90 ++++++++++--------- .../common/repository/models/task/deploy.go | 42 ++++----- .../common/repository/models/task/model.go | 16 ++-- .../core/common/repository/models/workflow.go | 3 +- .../core/workflow/service/workflow/convert.go | 2 + .../service/workflow/pipeline_task.go | 3 + .../service/workflow/workflow_task.go | 58 ++++++------ pkg/microservice/warpdrive/config/const.go | 1 + .../service/taskcontroller/task_handler.go | 23 +++++ .../core/service/types/task/deploy.go | 42 ++++----- .../core/service/types/task/model.go | 18 ++-- pkg/setting/consts.go | 9 ++ 12 files changed, 179 insertions(+), 128 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 250f975b..4c299d8c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -22,53 +22,55 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" ) type Queue struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` - MultiRun bool `bson:"multi_run" json:"multi_run"` - Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows - ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` - ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `bson:"resetImage" json:"resetImage"` - TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + Stages []*Stage `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + MultiRun bool `bson:"multi_run" json:"multi_run"` + Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` + TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/deploy.go b/pkg/microservice/aslan/core/common/repository/models/task/deploy.go index 7ac5c6c2..c00f36af 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/deploy.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/deploy.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" ) type Resource struct { @@ -31,26 +32,27 @@ type Resource struct { // Deploy 容器部署任务 type Deploy struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - Namespace string `bson:"namespace" json:"namespace"` - EnvName string `bson:"env_name" json:"env_name"` - ProductName string `bson:"product_name" json:"product_name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceType string `bson:"service_type" json:"service_type"` - ServiceRevision int64 `bson:"service_revision,omitempty" json:"service_revision,omitempty"` - ContainerName string `bson:"container_name" json:"container_name"` - Image string `bson:"image" json:"image"` - Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` - ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` - ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources"` - SkipWaiting bool `bson:"skipWaiting" json:"skipWaiting"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - ResetImage bool `bson:"reset_image" json:"reset_image"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + Namespace string `bson:"namespace" json:"namespace"` + EnvName string `bson:"env_name" json:"env_name"` + ProductName string `bson:"product_name" json:"product_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type" json:"service_type"` + ServiceRevision int64 `bson:"service_revision,omitempty" json:"service_revision,omitempty"` + ContainerName string `bson:"container_name" json:"container_name"` + Image string `bson:"image" json:"image"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` + ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources"` + SkipWaiting bool `bson:"skipWaiting" json:"skipWaiting"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + ResetImage bool `bson:"reset_image" json:"reset_image"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` } // SetNamespace ... diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 3e28083b..64631bdb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" ) type Task struct { @@ -75,13 +76,14 @@ type Task struct { Render *models.RenderInfo `bson:"render" json:"render"` StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `bson:"resetImage" json:"resetImage"` - TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` + TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } func (Task) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index b78523af..3a942d06 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -51,7 +51,8 @@ type Workflow struct { HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` // ResetImage indicate whether reset image to original version after completion - ResetImage bool `json:"reset_image" bson:"reset_image"` + ResetImage bool `bson:"reset_image" json:"reset_image"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy,omitempty" json:"reset_image_policy,omitempty"` // IsParallel 控制单一工作流的任务是否支持并行处理 IsParallel bool `json:"is_parallel" bson:"is_parallel"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 6f5831c7..51e259d2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -69,6 +69,7 @@ func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { TestReports: queueTask.TestReports, RwLock: queueTask.RwLock, ResetImage: queueTask.ResetImage, + ResetImagePolicy: queueTask.ResetImagePolicy, TriggerBy: queueTask.TriggerBy, Features: queueTask.Features, IsRestart: queueTask.IsRestart, @@ -115,6 +116,7 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { TestReports: task.TestReports, RwLock: task.RwLock, ResetImage: task.ResetImage, + ResetImagePolicy: task.ResetImagePolicy, TriggerBy: task.TriggerBy, Features: task.Features, IsRestart: task.IsRestart, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index a87f719a..a91e6512 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -597,8 +597,10 @@ func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, t } case config.TaskDeploy: resetImage := false + resetImagePolicy := setting.ResetImagePolicyTaskCompletedOrder if workflow, err := commonrepo.NewWorkflowColl().Find(t.PipelineName); err == nil { resetImage = workflow.ResetImage + resetImagePolicy = workflow.ResetImagePolicy } timeout := 0 if productTempl, err := template.NewProductColl().Find(t.ProductName); err == nil { @@ -611,6 +613,7 @@ func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, t deployInfo.Timeout = timeout deployInfo.IsRestart = true deployInfo.ResetImage = resetImage + deployInfo.ResetImagePolicy = resetImagePolicy if newDeployInfo, err := deployInfo.ToSubTask(); err == nil { subDeployTaskMap[serviceName] = newDeployInfo } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 1e2dfbe6..a7e46d4b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -733,20 +733,21 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, CommitID: args.CommitID, } task := &task.Task{ - TaskID: nextTaskID, - Type: config.WorkflowType, - ProductName: workflow.ProductTmplName, - PipelineName: args.WorkflowName, - Description: args.Description, - TaskCreator: taskCreator, - ReqID: args.ReqID, - Status: config.StatusCreated, - Stages: stages, - WorkflowArgs: args, - ConfigPayload: configPayload, - StorageURI: defaultS3StoreURL, - ResetImage: workflow.ResetImage, - TriggerBy: triggerBy, + TaskID: nextTaskID, + Type: config.WorkflowType, + ProductName: workflow.ProductTmplName, + PipelineName: args.WorkflowName, + Description: args.Description, + TaskCreator: taskCreator, + ReqID: args.ReqID, + Status: config.StatusCreated, + Stages: stages, + WorkflowArgs: args, + ConfigPayload: configPayload, + StorageURI: defaultS3StoreURL, + ResetImage: workflow.ResetImage, + ResetImagePolicy: workflow.ResetImagePolicy, + TriggerBy: triggerBy, } if len(task.Stages) <= 0 { @@ -1752,20 +1753,21 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator CommitID: args.CommitID, } task := &task.Task{ - TaskID: nextTaskID, - Type: config.WorkflowType, - ProductName: workflow.ProductTmplName, - PipelineName: args.WorkflowName, - Description: args.Description, - TaskCreator: taskCreator, - ReqID: args.ReqID, - Status: config.StatusCreated, - Stages: stages, - WorkflowArgs: args, - ConfigPayload: configPayload, - StorageURI: defaultS3StoreURL, - ResetImage: workflow.ResetImage, - TriggerBy: triggerBy, + TaskID: nextTaskID, + Type: config.WorkflowType, + ProductName: workflow.ProductTmplName, + PipelineName: args.WorkflowName, + Description: args.Description, + TaskCreator: taskCreator, + ReqID: args.ReqID, + Status: config.StatusCreated, + Stages: stages, + WorkflowArgs: args, + ConfigPayload: configPayload, + StorageURI: defaultS3StoreURL, + ResetImage: workflow.ResetImage, + ResetImagePolicy: workflow.ResetImagePolicy, + TriggerBy: triggerBy, } if len(task.Stages) <= 0 { diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 5510adff..505c6d28 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -41,6 +41,7 @@ const ( type Status string const ( + StatusInit Status = "init" StatusDisabled Status = "disabled" StatusCreated Status = "created" StatusRunning Status = "running" diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 9ceaa875..2a924c25 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -358,8 +358,31 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe } } + updatePipelineStatus(pipelineTask, xl) + deployStageStatus := config.StatusInit + testStageStatus := config.StatusInit for stagePosition, stage := range pipelineTask.Stages { + if stage.TaskType == config.TaskDeploy { + deployStageStatus = stage.Status + } else if stage.TaskType == config.TaskTestingV2 { + testStageStatus = stage.Status + } if stage.AfterAll { + if stage.TaskType == config.TaskResetImage { + switch pipelineTask.ResetImagePolicy { + case setting.ResetImagePolicyTaskCompleted, setting.ResetImagePolicyTaskCompletedOrder: + case setting.ResetImagePolicyDeployFailed: + if deployStageStatus == config.StatusInit || (deployStageStatus != config.StatusFailed && deployStageStatus != config.StatusCancelled && deployStageStatus != config.StatusTimeout) { + continue + } + case setting.ResetImagePolicyTestFailed: + if testStageStatus == config.StatusInit || (testStageStatus != config.StatusFailed && testStageStatus != config.StatusCancelled && testStageStatus != config.StatusTimeout) { + continue + } + default: + continue + } + } h.runStage(stagePosition, stage) } } diff --git a/pkg/microservice/warpdrive/core/service/types/task/deploy.go b/pkg/microservice/warpdrive/core/service/types/task/deploy.go index 2014e12d..c706557a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/deploy.go +++ b/pkg/microservice/warpdrive/core/service/types/task/deploy.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/setting" ) type Resource struct { @@ -31,26 +32,27 @@ type Resource struct { // Deploy 容器部署任务 type Deploy struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - Namespace string `bson:"namespace" json:"namespace"` - EnvName string `bson:"env_name" json:"env_name"` - ProductName string `bson:"product_name" json:"product_name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceType string `bson:"service_type" json:"service_type"` - ServiceRevision int64 `bson:"service_revision,omitempty" json:"service_revision,omitempty"` - ContainerName string `bson:"container_name" json:"container_name"` - Image string `bson:"image" json:"image"` - Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` - ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` - ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources"` - SkipWaiting bool `bson:"skipWaiting" json:"skipWaiting"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - ResetImage bool `bson:"reset_image" json:"reset_image"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + Namespace string `bson:"namespace" json:"namespace"` + EnvName string `bson:"env_name" json:"env_name"` + ProductName string `bson:"product_name" json:"product_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type" json:"service_type"` + ServiceRevision int64 `bson:"service_revision,omitempty" json:"service_revision,omitempty"` + ContainerName string `bson:"container_name" json:"container_name"` + Image string `bson:"image" json:"image"` + Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time,omitempty" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time,omitempty" json:"end_time,omitempty"` + ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` + ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources"` + SkipWaiting bool `bson:"skipWaiting" json:"skipWaiting"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + ResetImage bool `bson:"reset_image" json:"reset_image"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` } // SetNamespace ... diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 5a73843d..cc333453 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/setting" ) type Task struct { @@ -73,14 +74,15 @@ type Task struct { Render *RenderInfo `bson:"render" json:"render"` StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` // interface{} 为types.TestReport - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `json:"resetImage" bson:"resetImage"` - TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` - ArtifactInfo *ArtifactInfo `bson:"artifact_info" json:"artifact_info"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` + TriggerBy *TriggerBy `json:"trigger_by,omitempty" bson:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ArtifactInfo *ArtifactInfo `bson:"artifact_info" json:"artifact_info"` } type RenderInfo struct { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index e0273b8c..47196fb0 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -587,6 +587,15 @@ const ( Abnormal K8SClusterStatus = "abnormal" ) +type ResetImagePolicyType string + +const ( + ResetImagePolicyTaskCompleted ResetImagePolicyType = "taskCompleted" + ResetImagePolicyTaskCompletedOrder ResetImagePolicyType = "" + ResetImagePolicyDeployFailed ResetImagePolicyType = "deployFailed" + ResetImagePolicyTestFailed ResetImagePolicyType = "testFailed" +) + const LocalClusterID = "0123456789abcdef12345678" const RequestModeOpenAPI = "openAPI" -- Gitee From 20b18c0c9af0172cbdd8e68d2f811b3e6059a291 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 25 Jan 2022 14:52:29 +0800 Subject: [PATCH 1410/2647] debug cron code Signed-off-by: liu deyi --- pkg/microservice/cron/core/service/scheduler/scheduler.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index ddab8b24..9166ccd1 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -207,14 +207,15 @@ func (c *CronClient) InitColliePipelineScheduler() { // InitBuildStatScheduler ... func (c *CronClient) InitBuildStatScheduler() { - + fmt.Println(".... InitBuildStatScheduler start ....") c.Schedulers[InitStatScheduler] = gocron.NewScheduler() //c.Schedulers[InitStatScheduler].Every(1).Day().At("01:00").Do(c.AslanCli.InitStatData, c.log) - c.Schedulers[InitStatScheduler].Every(10).Minutes().Do(c.AslanCli.InitStatData, c.log) + c.Schedulers[InitStatScheduler].Every(5).Minutes().Do(c.AslanCli.InitStatData, c.log) c.Schedulers[InitStatScheduler].Start() + fmt.Println(".... InitBuildStatScheduler end....") } // InitOperationStatScheduler ... -- Gitee From 070dcfa32090659d517e37a47b1b9313f7125386 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 25 Jan 2022 15:18:45 +0800 Subject: [PATCH 1411/2647] delete debug code Signed-off-by: liu deyi --- pkg/microservice/cron/core/service/scheduler/scheduler.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 9166ccd1..1f6ffa95 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -207,15 +207,11 @@ func (c *CronClient) InitColliePipelineScheduler() { // InitBuildStatScheduler ... func (c *CronClient) InitBuildStatScheduler() { - fmt.Println(".... InitBuildStatScheduler start ....") c.Schedulers[InitStatScheduler] = gocron.NewScheduler() - //c.Schedulers[InitStatScheduler].Every(1).Day().At("01:00").Do(c.AslanCli.InitStatData, c.log) - - c.Schedulers[InitStatScheduler].Every(5).Minutes().Do(c.AslanCli.InitStatData, c.log) + c.Schedulers[InitStatScheduler].Every(1).Day().At("01:00").Do(c.AslanCli.InitStatData, c.log) c.Schedulers[InitStatScheduler].Start() - fmt.Println(".... InitBuildStatScheduler end....") } // InitOperationStatScheduler ... -- Gitee From 2a8027916cd8b03ab6b584f67d04eff7764fb336 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 25 Jan 2022 16:43:45 +0800 Subject: [PATCH 1412/2647] support Artifact Signed-off-by: ddh27 --- .../warpdrive/core/service/taskcontroller/task_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 2a924c25..154e2cbd 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -362,7 +362,7 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe deployStageStatus := config.StatusInit testStageStatus := config.StatusInit for stagePosition, stage := range pipelineTask.Stages { - if stage.TaskType == config.TaskDeploy { + if stage.TaskType == config.TaskDeploy || stage.TaskType == config.TaskArtifact { deployStageStatus = stage.Status } else if stage.TaskType == config.TaskTestingV2 { testStageStatus = stage.Status -- Gitee From 5a9f1c1ae46f579ba6e252cf0c1cf2ee55e64f30 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 25 Jan 2022 17:11:43 +0800 Subject: [PATCH 1413/2647] change name Signed-off-by: mouuii --- pkg/config/config.go | 4 ++++ .../collaboration/service/collaboration_instance.go | 13 +++++++------ .../aslan/core/environment/service/bundle.go | 7 ++++--- pkg/microservice/aslan/core/label/service/label.go | 4 ++-- .../aslan/core/workflow/service/workflow/bundle.go | 7 ++++--- 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 43d81251..329c2ae3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -269,3 +269,7 @@ func Namespace() string { func RoleBindingNameFromUIDAndRole(uid string, role setting.RoleType, roleNamespace string) string { return fmt.Sprintf("%s-%s-%s", uid, role, roleNamespace) } + +func BuildResourceKey(resourceType, projectName, labelBinding string) string { + return fmt.Sprintf("%s-%s-%s", resourceType, projectName, labelBinding) +} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 7d8cdba0..b3c950d7 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -15,6 +15,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" service2 "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + config2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/shared/client/label" @@ -356,7 +357,7 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[stri rules = append(rules, &policy.Rule{ Verbs: product.Verbs, Kind: "resource", - Resources: []string{"Product"}, + Resources: []string{string(config2.ResourceTypeProduct)}, MatchAttributes: []policy.MatchAttribute{ { Key: "policy", @@ -487,7 +488,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin Resource: label.Resource{ Name: product.Name, ProjectName: projectName, - Type: "Product", + Type: string(config2.ResourceTypeProduct), }, }) } @@ -512,7 +513,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin LabelID: labelId, Resource: label.Resource{ Name: product.Name, - Type: "Product", + Type: string(config2.ResourceTypeProduct), ProjectName: projectName, }, }) @@ -548,7 +549,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin LabelID: labelId, Resource: label.Resource{ Name: product.New.Name, - Type: "Product", + Type: string(config2.ResourceTypeProduct), ProjectName: projectName, }, }) @@ -559,7 +560,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin LabelID: labelId, Resource: label.Resource{ Name: product.Old.Name, - Type: "Product", + Type: string(config2.ResourceTypeProduct), ProjectName: projectName, }, }) @@ -588,7 +589,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin Resource: label.Resource{ Name: product.Name, ProjectName: projectName, - Type: "Product", + Type: string(config2.ResourceTypeProduct), }, }) } diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 0f27dd6e..390c4cea 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -17,10 +17,9 @@ limitations under the License. package service import ( - "fmt" - "go.uber.org/zap" + config2 "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -59,7 +58,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { } for _, env := range envs { - resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeProduct, env.ProductName, env.EnvName) + resourceKey := config2.BuildResourceKey(string(config.ResourceTypeProduct), env.ProductName, env.EnvName) resourceSpec := &resourceSpec{ ResourceID: env.EnvName, ProjectName: env.ProductName, @@ -69,6 +68,8 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { for _, v := range labels { resourceSpec.Spec[v.Key] = v.Value } + } else { + logger.Warnf("can not found resource key :%s", resourceKey) } res = append(res, resourceSpec) } diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index aea06f26..87c7b5d5 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -22,6 +22,7 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" e "github.com/koderover/zadig/pkg/tool/errors" @@ -166,8 +167,7 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg // 3. iterate resources res := make(map[string][]*models.Label) for _, labelBinding := range labelBindings { - resourceKey := fmt.Sprintf("%s-%s-%s", labelBinding.ResourceType, labelBinding.ProjectName, labelBinding.ResourceName) - + resourceKey := config.BuildResourceKey(labelBinding.ResourceType, labelBinding.ProjectName, labelBinding.ResourceName) label, ok := labelM[labelBinding.LabelID] if !ok { return nil, fmt.Errorf("can not find label %v", label) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index ce2540ae..07ef26b8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -17,10 +17,9 @@ limitations under the License. package workflow import ( - "fmt" - "go.uber.org/zap" + config2 "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -59,7 +58,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { // TODO - mouuii for _, workflow := range workflows { - resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeWorkflow, workflow.ProductTmplName, workflow.Name) + resourceKey := config2.BuildResourceKey(string(config.ResourceTypeWorkflow), workflow.ProductTmplName, workflow.Name) resourceSpec := &resourceSpec{ ResourceID: workflow.Name, ProjectName: workflow.ProductTmplName, @@ -69,6 +68,8 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { for _, v := range labels { resourceSpec.Spec[v.Key] = v.Value } + } else { + logger.Warnf("can not found resource key :%s", resourceKey) } res = append(res, resourceSpec) } -- Gitee From dfd607d85e1a8840baf9e15fced711c44dc056ed Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 25 Jan 2022 17:14:51 +0800 Subject: [PATCH 1414/2647] rm todo Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/service/workflow/bundle.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 07ef26b8..7ea5301f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -56,7 +56,6 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { return nil, err } - // TODO - mouuii for _, workflow := range workflows { resourceKey := config2.BuildResourceKey(string(config.ResourceTypeWorkflow), workflow.ProductTmplName, workflow.Name) resourceSpec := &resourceSpec{ -- Gitee From 4e19d9d2a11928432048dd9f9b3968085b60a589 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 25 Jan 2022 17:20:29 +0800 Subject: [PATCH 1415/2647] makefile & dockerfile change to improve build efficiency Signed-off-by: Min Min --- .dockerignore | 1 + Makefile | 83 ++++++++++++------- cmd/{systemconfig => config}/main.go | 0 cmd/{hubagent => hub-agent}/main.go | 0 cmd/{hubserver => hub-server}/main.go | 0 cmd/{initconfig => init}/main.go | 0 cmd/{jenkinsplugin => jenkins-plugin}/main.go | 0 cmd/{packager => packager-plugin}/main.go | 0 cmd/{predator => predator-plugin}/main.go | 0 cmd/{upgradeassistant => ua}/main.go | 0 docker/base/amd64/ubuntu-bionic.Dockerfile | 2 +- docker/base/amd64/ubuntu-focal.Dockerfile | 2 +- docker/base/amd64/ubuntu-xenial.Dockerfile | 17 +--- docker/base/arm64/ubuntu-bionic.Dockerfile | 2 +- docker/base/arm64/ubuntu-focal.Dockerfile | 2 +- docker/base/arm64/ubuntu-xenial.Dockerfile | 16 +--- .../base/image/bionic-base-amd64.Dockerfile | 31 +++++++ .../base/image/bionic-base-arm64.Dockerfile | 31 +++++++ docker/base/image/focal-base-amd64.Dockerfile | 31 +++++++ docker/base/image/focal-base-arm64.Dockerfile | 31 +++++++ .../base/image/xenial-base-amd64.Dockerfile | 53 ++++++++++++ .../base/image/xenial-base-arm64.Dockerfile | 53 ++++++++++++ docker/service/aslan.Dockerfile | 6 +- docker/service/config.Dockerfile | 6 +- docker/service/cron.Dockerfile | 6 +- docker/service/hub-agent.Dockerfile | 8 +- docker/service/hub-server.Dockerfile | 8 +- docker/service/init.Dockerfile | 7 +- docker/service/jenkins-plugin.Dockerfile | 6 +- docker/service/packager-plugin.Dockerfile | 6 +- docker/service/picket.Dockerfile | 6 +- docker/service/podexec.Dockerfile | 7 +- docker/service/policy.Dockerfile | 6 +- docker/service/predator-plugin.Dockerfile | 6 +- .../service/reaper-plugin-bionic.Dockerfile | 37 +-------- docker/service/reaper-plugin-focal.Dockerfile | 37 +-------- .../service/reaper-plugin-xenial.Dockerfile | 54 +----------- docker/service/resource-server.Dockerfile | 6 +- docker/service/ua.Dockerfile | 7 +- docker/service/user.Dockerfile | 6 +- docker/service/warpdrive.Dockerfile | 6 +- docker/ua.Dockerfile.template | 10 --- 42 files changed, 315 insertions(+), 281 deletions(-) rename cmd/{systemconfig => config}/main.go (100%) rename cmd/{hubagent => hub-agent}/main.go (100%) rename cmd/{hubserver => hub-server}/main.go (100%) rename cmd/{initconfig => init}/main.go (100%) rename cmd/{jenkinsplugin => jenkins-plugin}/main.go (100%) rename cmd/{packager => packager-plugin}/main.go (100%) rename cmd/{predator => predator-plugin}/main.go (100%) rename cmd/{upgradeassistant => ua}/main.go (100%) create mode 100644 docker/base/image/bionic-base-amd64.Dockerfile create mode 100644 docker/base/image/bionic-base-arm64.Dockerfile create mode 100644 docker/base/image/focal-base-amd64.Dockerfile create mode 100644 docker/base/image/focal-base-arm64.Dockerfile create mode 100644 docker/base/image/xenial-base-amd64.Dockerfile create mode 100644 docker/base/image/xenial-base-arm64.Dockerfile delete mode 100644 docker/ua.Dockerfile.template diff --git a/.dockerignore b/.dockerignore index a4bf91f5..669b1c7d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ !/go.sum !/resource-server-nginx.conf !/version +!/docker diff --git a/Makefile b/Makefile index b79293ae..81720410 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc VERSION ?= $(shell date +'%Y%m%d%H%M%S') VERSION := $(VERSION) -TARGETS = aslan cron hub-agent hub-server jenkins-plugin podexec predator-plugin packager-plugin resource-server ua warpdrive policy user picket config init +TARGETS = aslan config cron hub-agent hub-server init jenkins-plugin packager-plugin picket podexec policy predator-plugin ua user warpdrive REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) @@ -12,29 +12,28 @@ ALL_PUSH=$(TARGETS:=.push) ALL_REAPER=$(REAPER_OS:=.reaper.image) ALL_REAPER_PUSH=$(REAPER_OS:=.reaper.push) -all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) $(ALL_REAPER:=.amd64) $(ALL_REAPER:=.arm64) -all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.arm64) +all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) $(ALL_REAPER:=.amd64) $(ALL_REAPER:=.arm64) resource-server.build.amd64 resource-server.build.arm64 +all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 -all.amd64: $(ALL_IMAGES:=.amd64) $(ALL_REAPER:=.amd64) -all.arm64: $(ALL_IMAGES:=.amd64) $(ALL_REAPER:=.arm64) -allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) -allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) +all.amd64: $(ALL_IMAGES:=.amd64) $(ALL_REAPER:=.amd64) resource-server.build.amd64 +all.arm64: $(ALL_IMAGES:=.amd64) $(ALL_REAPER:=.arm64) resource-server.build.arm64 +allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) resource-server.upload.amd64 +allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.upload.arm64 %.reaper.push.arm64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-arm64-$* %.reaper.push.arm64: %.reaper.image.arm64 @docker push ${MAKE_IMAGE} %.reaper.image.arm64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-arm64-$* +%.reaper.image.arm64: export GOOS = linux +%.reaper.image.arm64: export GOARCH = arm64 %.reaper.image.arm64: @mkdir -p docker/dist/arm64 + @go build -v -o docker/dist/reaper cmd/reaper/main.go @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#golang.Dockerfile/ {' -e 'r docker/base/arm64/golang.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-bionic.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-bionic.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/arm64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/arm64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/arm64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @docker build -f docker/dist/arm64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . %.reaper.push.amd64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-amd64-$* @@ -42,33 +41,31 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) @docker push ${MAKE_IMAGE} %.reaper.image.amd64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-amd64-$* +%.reaper.image.amd64: export GOOS = linux +%.reaper.image.amd64: export GOARCH = arm64 %.reaper.image.amd64: @mkdir -p docker/dist/amd64 + @go build -v -o docker/dist/reaper cmd/reaper/main.go @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#golang.Dockerfile/ {' -e 'r docker/base/amd64/golang.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-bionic.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-bionic.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/amd64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @docker build --progress=plain --no-cache -f docker/dist/amd64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . + @docker build -f docker/dist/amd64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.amd64: %.image.amd64 - docker push ${MAKE_IMAGE} + @docker push ${MAKE_IMAGE} %.image.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 +%.image.amd64: export GOOS = linux +%.image.amd64: export GOARCH = amd64 %.image.amd64: @mkdir -p docker/dist/amd64 @cp docker/service/$*.Dockerfile docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#golang.Dockerfile/ {' -e 'r docker/base/amd64/golang.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#ubuntu-bionic.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-bionic.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/amd64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile + @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile + @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile + @go build -v -o docker/dist/$* cmd/$*/main.go @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 @@ -76,18 +73,46 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) docker push ${MAKE_IMAGE} %.image.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-arm64 +%.image.arm64: export GOOS = linux +%.image.arm64: export GOARCH = arm64 %.image.arm64: @mkdir -p docker/dist/arm64 @cp docker/service/$*.Dockerfile docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#golang.Dockerfile/ {' -e 'r docker/base/arm64/golang.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#ubuntu-bionic.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-bionic.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/arm64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/arm64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/arm64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile + @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile + @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile + @go build -v -o docker/dist/$* cmd/$*/main.go @docker build -f docker/dist/arm64/$*.Dockerfile --tag ${MAKE_IMAGE} . +# USING BUILD AND UPLOAD INSTEAD OF IMAGE AND PUSH TO AVOID COLLISION +resource-server.build.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 +resource-server.build.amd64: export GOOS = linux +resource-server.build.amd64: export GOARCH = amd64 +resource-server.build.amd64: + @mkdir -p docker/dist/amd64 + @cp docker/service/resource-server.Dockerfile docker/dist/amd64/resource-server.Dockerfile + @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/resource-server.Dockerfile + @go build -v -o docker/dist/reaper cmd/reaper/main.go + @docker build -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . + +resource-server.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 +resource-server.upload.amd64: resource-server.build.amd64 + @docker push ${MAKE_IMAGE} + +resource-server.build.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-arm64 +resource-server.build.arm64: export GOOS = linux +resource-server.build.arm64: export GOARCH = arm64 +resource-server.build.arm64: + @mkdir -p docker/dist/arm64 + @cp docker/service/resource-server.Dockerfile docker/dist/arm64/resource-server.Dockerfile + @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/arm64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/arm64/resource-server.Dockerfile + @go build -v -o docker/dist/reaper cmd/reaper/main.go + @docker build -f docker/dist/arm64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . + +resource-server.upload.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-arm64 +resource-server.upload.arm64: resource-server.build.arm64 + @docker push ${MAKE_IMAGE} + .PHONY: clean clean: @rm -rf docker/dist diff --git a/cmd/systemconfig/main.go b/cmd/config/main.go similarity index 100% rename from cmd/systemconfig/main.go rename to cmd/config/main.go diff --git a/cmd/hubagent/main.go b/cmd/hub-agent/main.go similarity index 100% rename from cmd/hubagent/main.go rename to cmd/hub-agent/main.go diff --git a/cmd/hubserver/main.go b/cmd/hub-server/main.go similarity index 100% rename from cmd/hubserver/main.go rename to cmd/hub-server/main.go diff --git a/cmd/initconfig/main.go b/cmd/init/main.go similarity index 100% rename from cmd/initconfig/main.go rename to cmd/init/main.go diff --git a/cmd/jenkinsplugin/main.go b/cmd/jenkins-plugin/main.go similarity index 100% rename from cmd/jenkinsplugin/main.go rename to cmd/jenkins-plugin/main.go diff --git a/cmd/packager/main.go b/cmd/packager-plugin/main.go similarity index 100% rename from cmd/packager/main.go rename to cmd/packager-plugin/main.go diff --git a/cmd/predator/main.go b/cmd/predator-plugin/main.go similarity index 100% rename from cmd/predator/main.go rename to cmd/predator-plugin/main.go diff --git a/cmd/upgradeassistant/main.go b/cmd/ua/main.go similarity index 100% rename from cmd/upgradeassistant/main.go rename to cmd/ua/main.go diff --git a/docker/base/amd64/ubuntu-bionic.Dockerfile b/docker/base/amd64/ubuntu-bionic.Dockerfile index 80043633..1a18b751 100644 --- a/docker/base/amd64/ubuntu-bionic.Dockerfile +++ b/docker/base/amd64/ubuntu-bionic.Dockerfile @@ -1 +1 @@ -FROM ubuntu:bionic +FROM ccr.ccs.tencentyun.com/koderover-public/build-base:bionic-amd64 diff --git a/docker/base/amd64/ubuntu-focal.Dockerfile b/docker/base/amd64/ubuntu-focal.Dockerfile index 9cc9f1a3..789e5914 100644 --- a/docker/base/amd64/ubuntu-focal.Dockerfile +++ b/docker/base/amd64/ubuntu-focal.Dockerfile @@ -1 +1 @@ -FROM ubuntu:focal +FROM ccr.ccs.tencentyun.com/koderover-public/build-base:focal-amd64 diff --git a/docker/base/amd64/ubuntu-xenial.Dockerfile b/docker/base/amd64/ubuntu-xenial.Dockerfile index ccb4277f..25232f62 100644 --- a/docker/base/amd64/ubuntu-xenial.Dockerfile +++ b/docker/base/amd64/ubuntu-xenial.Dockerfile @@ -1,16 +1 @@ -FROM ubuntu:xenial - -# 修改镜像源和时区 -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \ - && apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \ - && apt-get install -y \ - tzdata \ - net-tools \ - dnsutils \ - ca-certificates \ - git \ - curl \ - lsof \ - telnet \ - && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ - && rm -rf /var/lib/apt/lists/* +FROM ccr.ccs.tencentyun.com/koderover-public/build-base:xenial-amd64 diff --git a/docker/base/arm64/ubuntu-bionic.Dockerfile b/docker/base/arm64/ubuntu-bionic.Dockerfile index 97609247..6a6b4aaf 100644 --- a/docker/base/arm64/ubuntu-bionic.Dockerfile +++ b/docker/base/arm64/ubuntu-bionic.Dockerfile @@ -1 +1 @@ -FROM arm64v8/ubuntu:bionic +FROM ccr.ccs.tencentyun.com/koderover-public/build-base:bionic-arm64 diff --git a/docker/base/arm64/ubuntu-focal.Dockerfile b/docker/base/arm64/ubuntu-focal.Dockerfile index e416d243..da5e8899 100644 --- a/docker/base/arm64/ubuntu-focal.Dockerfile +++ b/docker/base/arm64/ubuntu-focal.Dockerfile @@ -1 +1 @@ -FROM arm64v8/ubuntu:focal +FROM ccr.ccs.tencentyun.com/koderover-public/build-base:focal-arm64 diff --git a/docker/base/arm64/ubuntu-xenial.Dockerfile b/docker/base/arm64/ubuntu-xenial.Dockerfile index e2f37c5f..593fdef5 100644 --- a/docker/base/arm64/ubuntu-xenial.Dockerfile +++ b/docker/base/arm64/ubuntu-xenial.Dockerfile @@ -1,16 +1,2 @@ -FROM arm64v8/ubuntu:xenial +FROM ccr.ccs.tencentyun.com/koderover-public/build-base:xenial-arm64 -# 修改镜像源和时区 -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list \ - && apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates \ - && apt-get install -y \ - tzdata \ - net-tools \ - dnsutils \ - ca-certificates \ - git \ - curl \ - lsof \ - telnet \ - && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ - && rm -rf /var/lib/apt/lists/* diff --git a/docker/base/image/bionic-base-amd64.Dockerfile b/docker/base/image/bionic-base-amd64.Dockerfile new file mode 100644 index 00000000..25da2529 --- /dev/null +++ b/docker/base/image/bionic-base-amd64.Dockerfile @@ -0,0 +1,31 @@ +FROM ubuntu:bionic + +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata +RUN apt-get install -y \ + curl \ + git \ + netcat-openbsd \ + wget \ + build-essential \ + libfontconfig \ + libsasl2-dev \ + libfreetype6-dev \ + libpcre3-dev \ + pkg-config \ + cmake \ + python \ + librrd-dev \ + sudo + +# 修改时区 +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 安装 docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + +# 替换tar(适配cephfs) +RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/bionic-base-arm64.Dockerfile b/docker/base/image/bionic-base-arm64.Dockerfile new file mode 100644 index 00000000..f8ab8cd5 --- /dev/null +++ b/docker/base/image/bionic-base-arm64.Dockerfile @@ -0,0 +1,31 @@ +FROM arm64v8/ubuntu:bionic + +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata +RUN apt-get install -y \ + curl \ + git \ + netcat-openbsd \ + wget \ + build-essential \ + libfontconfig \ + libsasl2-dev \ + libfreetype6-dev \ + libpcre3-dev \ + pkg-config \ + cmake \ + python \ + librrd-dev \ + sudo + +# 修改时区 +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 安装 docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + +# 替换tar(适配cephfs) +RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/focal-base-amd64.Dockerfile b/docker/base/image/focal-base-amd64.Dockerfile new file mode 100644 index 00000000..f812104f --- /dev/null +++ b/docker/base/image/focal-base-amd64.Dockerfile @@ -0,0 +1,31 @@ +FROM ubuntu:focal + +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata +RUN apt-get install -y \ + curl \ + git \ + netcat-openbsd \ + wget \ + build-essential \ + libfontconfig \ + libsasl2-dev \ + libfreetype6-dev \ + libpcre3-dev \ + pkg-config \ + cmake \ + python \ + librrd-dev \ + sudo + +# 修改时区 +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 安装 docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + +# 替换tar(适配cephfs) +RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/focal-base-arm64.Dockerfile b/docker/base/image/focal-base-arm64.Dockerfile new file mode 100644 index 00000000..68b54570 --- /dev/null +++ b/docker/base/image/focal-base-arm64.Dockerfile @@ -0,0 +1,31 @@ +FROM arm64v8/ubuntu:focal + +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata +RUN apt-get install -y \ + curl \ + git \ + netcat-openbsd \ + wget \ + build-essential \ + libfontconfig \ + libsasl2-dev \ + libfreetype6-dev \ + libpcre3-dev \ + pkg-config \ + cmake \ + python \ + librrd-dev \ + sudo + +# 修改时区 +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 安装 docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + +# 替换tar(适配cephfs) +RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/xenial-base-amd64.Dockerfile b/docker/base/image/xenial-base-amd64.Dockerfile new file mode 100644 index 00000000..27ab04b5 --- /dev/null +++ b/docker/base/image/xenial-base-amd64.Dockerfile @@ -0,0 +1,53 @@ +FROM ubuntu:xenial + +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata +RUN apt-get clean && apt-get update && apt-get install -y \ + curl \ + netcat-openbsd \ + wget \ + build-essential \ + libfontconfig \ + libsasl2-dev \ + libfreetype6-dev \ + libpcre3-dev \ + pkg-config \ + cmake \ + python \ + librrd-dev \ + python \ + net-tools \ + dnsutils \ + ca-certificates \ + lsof \ + telnet \ + sudo + +# Upgrade Git to latest version,forcing IPv4 transport with apt-get +RUN echo "deb http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list + +# Using 'launchpad.proxy.ustclug.org' to reverse proxy for 'launchpad.net'. Detail:https://lug.ustc.edu.cn/wiki/mirrors/help/revproxy/ +RUN echo "deb-src http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list + +# Import key manually to solve bad network problems +RUN echo '-----BEGIN PGP PUBLIC KEY BLOCK----- \nVersion: \nComment: Hostname: keyserver.ubuntu.com \n \nxo0ESXjaGwEEAMA26F3+mnRW8uRqASMsEa5EsmgvUpLD7EKpC7903OpiMGSvZ2sE \n34g7W6nUQY0R//AZS2iW4ZXfvdhQTQuuPlHM6Q3iUAt+nyXcf9xBlscs8Gm722u4 \njAtFtBS4BMQRhRRfWTHwJIOM6OpGIccjPe8pQfIeoRxkKJxlehzw2mU1ABEBAAHN \nKExhdW5jaHBhZCBQUEEgZm9yIFVidW50dSBHaXQgTWFpbnRhaW5lcnPCtgQTAQIA \nIAUCSXjaGwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEKFxXYjh3x8k/zMD \n/RKBMjavvFl71YBazSOGl2YfSsZiR/ANsby3+rUaULb8uxzCHXAQnlH5vdtLSPry \naLBvzCU8C3C02qNT8jRacU2752zsCkCi1SLRSOXdI/ATJHza5aTvYV93rTITBhU4 \nsJQeK9RW0CtaDRAxJsn/Dr6J3lL/c9m9cT5fFpxOIsF4 \n=7kFR \n-----END PGP PUBLIC KEY BLOCK----- \n' > key + +RUN apt-key add key + +# Forcing IPv4 transport with apt-get +RUN apt-get -o Acquire::ForceIPv4=true update + +RUN apt-get -o Acquire::ForceIPv4=true install -y git + +# Change timezone to Asia/Shanghai +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# Install docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + + +# Replaces the default tar(for cephfs) +RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/xenial-base-arm64.Dockerfile b/docker/base/image/xenial-base-arm64.Dockerfile new file mode 100644 index 00000000..00ec977c --- /dev/null +++ b/docker/base/image/xenial-base-arm64.Dockerfile @@ -0,0 +1,53 @@ +FROM arm64v8/ubuntu:xenial + +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates +RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata +RUN apt-get clean && apt-get update && apt-get install -y \ + curl \ + netcat-openbsd \ + wget \ + build-essential \ + libfontconfig \ + libsasl2-dev \ + libfreetype6-dev \ + libpcre3-dev \ + pkg-config \ + cmake \ + python \ + librrd-dev \ + python \ + net-tools \ + dnsutils \ + ca-certificates \ + lsof \ + telnet \ + sudo + +# Upgrade Git to latest version,forcing IPv4 transport with apt-get +RUN echo "deb http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list + +# Using 'launchpad.proxy.ustclug.org' to reverse proxy for 'launchpad.net'. Detail:https://lug.ustc.edu.cn/wiki/mirrors/help/revproxy/ +RUN echo "deb-src http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list + +# Import key manually to solve bad network problems +RUN echo '-----BEGIN PGP PUBLIC KEY BLOCK----- \nVersion: \nComment: Hostname: keyserver.ubuntu.com \n \nxo0ESXjaGwEEAMA26F3+mnRW8uRqASMsEa5EsmgvUpLD7EKpC7903OpiMGSvZ2sE \n34g7W6nUQY0R//AZS2iW4ZXfvdhQTQuuPlHM6Q3iUAt+nyXcf9xBlscs8Gm722u4 \njAtFtBS4BMQRhRRfWTHwJIOM6OpGIccjPe8pQfIeoRxkKJxlehzw2mU1ABEBAAHN \nKExhdW5jaHBhZCBQUEEgZm9yIFVidW50dSBHaXQgTWFpbnRhaW5lcnPCtgQTAQIA \nIAUCSXjaGwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEKFxXYjh3x8k/zMD \n/RKBMjavvFl71YBazSOGl2YfSsZiR/ANsby3+rUaULb8uxzCHXAQnlH5vdtLSPry \naLBvzCU8C3C02qNT8jRacU2752zsCkCi1SLRSOXdI/ATJHza5aTvYV93rTITBhU4 \nsJQeK9RW0CtaDRAxJsn/Dr6J3lL/c9m9cT5fFpxOIsF4 \n=7kFR \n-----END PGP PUBLIC KEY BLOCK----- \n' > key + +RUN apt-key add key + +# Forcing IPv4 transport with apt-get +RUN apt-get -o Acquire::ForceIPv4=true update + +RUN apt-get -o Acquire::ForceIPv4=true install -y git + +# Change timezone to Asia/Shanghai +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# Install docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + + +# Replaces the default tar(for cephfs) +RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/service/aslan.Dockerfile b/docker/service/aslan.Dockerfile index 434810fa..e55afc58 100644 --- a/docker/service/aslan.Dockerfile +++ b/docker/service/aslan.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /aslan ./cmd/aslan/main.go - #alpine-git.Dockerfile WORKDIR /app -COPY --from=build /aslan . +ADD docker/dist/aslan . ENTRYPOINT ["/app/aslan"] diff --git a/docker/service/config.Dockerfile b/docker/service/config.Dockerfile index 83a3f2ff..bdb7a8e4 100644 --- a/docker/service/config.Dockerfile +++ b/docker/service/config.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /config ./cmd/systemconfig/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /config . +ADD docker/dist/config . ENTRYPOINT ["/app/config"] diff --git a/docker/service/cron.Dockerfile b/docker/service/cron.Dockerfile index b0c60a1b..162acad4 100644 --- a/docker/service/cron.Dockerfile +++ b/docker/service/cron.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /cron ./cmd/cron/main.go - #alpine-git.Dockerfile WORKDIR /app -COPY --from=build /cron . +ADD docker/dist/cron . ENTRYPOINT ["/app/cron"] diff --git a/docker/service/hub-agent.Dockerfile b/docker/service/hub-agent.Dockerfile index 41138061..f69cb6fd 100644 --- a/docker/service/hub-agent.Dockerfile +++ b/docker/service/hub-agent.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /hubagent ./cmd/hubagent/main.go - #alpine-git.Dockerfile WORKDIR /app -COPY --from=build /hubagent . +ADD docker/dist/hub-agent . -ENTRYPOINT ["/app/hubagent"] +ENTRYPOINT ["/app/hub-agent"] diff --git a/docker/service/hub-server.Dockerfile b/docker/service/hub-server.Dockerfile index 7bb4ae3e..ba316837 100644 --- a/docker/service/hub-server.Dockerfile +++ b/docker/service/hub-server.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /hubserver ./cmd/hubserver/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /hubserver . +ADD docker/dist/hub-server . -ENTRYPOINT ["/app/hubserver"] +ENTRYPOINT ["/app/hub-server"] diff --git a/docker/service/init.Dockerfile b/docker/service/init.Dockerfile index d8acda07..be650b5a 100644 --- a/docker/service/init.Dockerfile +++ b/docker/service/init.Dockerfile @@ -1,10 +1,7 @@ -#golang.Dockerfile - -RUN go build -o /init ./cmd/initconfig/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /init /app/init + +ADD docker/dist/init /app/init ENTRYPOINT ["/app/init"] diff --git a/docker/service/jenkins-plugin.Dockerfile b/docker/service/jenkins-plugin.Dockerfile index 4fd1ec66..0b6bd988 100644 --- a/docker/service/jenkins-plugin.Dockerfile +++ b/docker/service/jenkins-plugin.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /jenkins-plugin ./cmd/jenkinsplugin/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /jenkins-plugin . +ADD docker/dist/jenkins-plugin . ENTRYPOINT ["/app/jenkins-plugin"] diff --git a/docker/service/packager-plugin.Dockerfile b/docker/service/packager-plugin.Dockerfile index 5ef3c5d5..d5f32961 100644 --- a/docker/service/packager-plugin.Dockerfile +++ b/docker/service/packager-plugin.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /packager-plugin ./cmd/packager/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /packager-plugin . +ADD docker/dist/packager-plugin . ENTRYPOINT ["/app/packager-plugin"] diff --git a/docker/service/picket.Dockerfile b/docker/service/picket.Dockerfile index 09a92d7f..242375f5 100644 --- a/docker/service/picket.Dockerfile +++ b/docker/service/picket.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /picket ./cmd/picket/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /picket . +ADD docker/dist/picket . ENTRYPOINT ["/app/picket"] diff --git a/docker/service/podexec.Dockerfile b/docker/service/podexec.Dockerfile index 657bfae6..1ef16630 100644 --- a/docker/service/podexec.Dockerfile +++ b/docker/service/podexec.Dockerfile @@ -1,10 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /podexec ./cmd/podexec/... - #alpine-git.Dockerfile WORKDIR /app -COPY --from=build /podexec /app/podexec + +ADD docker/dist/podexec . ENTRYPOINT ["/app/podexec"] diff --git a/docker/service/policy.Dockerfile b/docker/service/policy.Dockerfile index bb831439..bd3cc58c 100644 --- a/docker/service/policy.Dockerfile +++ b/docker/service/policy.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /policy ./cmd/policy/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /policy . +ADD docker/dist/policy . ENTRYPOINT ["/app/policy"] diff --git a/docker/service/predator-plugin.Dockerfile b/docker/service/predator-plugin.Dockerfile index edc2f1c6..099fa4eb 100644 --- a/docker/service/predator-plugin.Dockerfile +++ b/docker/service/predator-plugin.Dockerfile @@ -1,7 +1,3 @@ -#golang.Dockerfile - -RUN go build -v -o /predator-plugin ./cmd/predator/main.go - #ubuntu-xenial.Dockerfile # 安装 docker client @@ -12,6 +8,6 @@ RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o do WORKDIR /app -COPY --from=build /predator-plugin . +ADD docker/dist/predator-plugin . ENTRYPOINT ["/app/predator-plugin"] diff --git a/docker/service/reaper-plugin-bionic.Dockerfile b/docker/service/reaper-plugin-bionic.Dockerfile index a5bb8f3e..1a80de58 100644 --- a/docker/service/reaper-plugin-bionic.Dockerfile +++ b/docker/service/reaper-plugin-bionic.Dockerfile @@ -1,40 +1,5 @@ -#golang.Dockerfile - -RUN go build -v -o /reaper ./cmd/reaper/main.go - #ubuntu-bionic.Dockerfile -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list -RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates -RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata -RUN apt-get install -y \ - curl \ - git \ - netcat-openbsd \ - wget \ - build-essential \ - libfontconfig \ - libsasl2-dev \ - libfreetype6-dev \ - libpcre3-dev \ - pkg-config \ - cmake \ - python \ - librrd-dev \ - sudo - -# 修改时区 -RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime - -# 安装 docker client -RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ - tar -xvzf docker.tgz &&\ - mv docker/* /usr/local/bin - - -# 替换tar(适配cephfs) -RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar - -COPY --from=build /reaper /usr/local/bin +COPY docker/dist/reaper /usr/local/bin ENTRYPOINT ["reaper"] diff --git a/docker/service/reaper-plugin-focal.Dockerfile b/docker/service/reaper-plugin-focal.Dockerfile index ed2d6abf..c94f758d 100644 --- a/docker/service/reaper-plugin-focal.Dockerfile +++ b/docker/service/reaper-plugin-focal.Dockerfile @@ -1,40 +1,5 @@ -#golang.Dockerfile - -RUN go build -v -o /reaper ./cmd/reaper/main.go - #ubuntu-focal.Dockerfile -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list -RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates -RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata -RUN apt-get install -y \ - curl \ - git \ - netcat-openbsd \ - wget \ - build-essential \ - libfontconfig \ - libsasl2-dev \ - libfreetype6-dev \ - libpcre3-dev \ - pkg-config \ - cmake \ - python \ - librrd-dev \ - sudo - -# 修改时区 -RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime - -# 安装 docker client -RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ - tar -xvzf docker.tgz &&\ - mv docker/* /usr/local/bin - - -# 替换tar(适配cephfs) -RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar - -COPY --from=build /reaper /usr/local/bin +COPY docker/dist/reaper /usr/local/bin ENTRYPOINT ["reaper"] diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile index 32c9c387..9257f0b8 100644 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ b/docker/service/reaper-plugin-xenial.Dockerfile @@ -1,57 +1,5 @@ -#golang.Dockerfile - -RUN go build -v -o /reaper ./cmd/reaper/main.go - #ubuntu-xenial.Dockerfile -RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list -RUN apt-get clean && apt-get update && apt-get install -y apt-transport-https ca-certificates -RUN sed -i s:http:https:g /etc/apt/sources.list -RUN DEBIAN_FRONTEND=noninteractive apt install -y tzdata -RUN apt-get clean && apt-get update && apt-get install -y \ - curl \ - git \ - netcat-openbsd \ - wget \ - build-essential \ - libfontconfig \ - libsasl2-dev \ - libfreetype6-dev \ - libpcre3-dev \ - pkg-config \ - cmake \ - python \ - librrd-dev \ - sudo - -# Upgrade Git to latest version,forcing IPv4 transport with apt-get -RUN echo "deb http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list - -# Using 'launchpad.proxy.ustclug.org' to reverse proxy for 'launchpad.net'. Detail:https://lug.ustc.edu.cn/wiki/mirrors/help/revproxy/ -RUN echo "deb-src http://launchpad.proxy.ustclug.org/git-core/ppa/ubuntu xenial main" >> /etc/apt/sources.list - -# Import key manually to solve bad network problems -RUN echo '-----BEGIN PGP PUBLIC KEY BLOCK----- \nVersion: \nComment: Hostname: keyserver.ubuntu.com \n \nxo0ESXjaGwEEAMA26F3+mnRW8uRqASMsEa5EsmgvUpLD7EKpC7903OpiMGSvZ2sE \n34g7W6nUQY0R//AZS2iW4ZXfvdhQTQuuPlHM6Q3iUAt+nyXcf9xBlscs8Gm722u4 \njAtFtBS4BMQRhRRfWTHwJIOM6OpGIccjPe8pQfIeoRxkKJxlehzw2mU1ABEBAAHN \nKExhdW5jaHBhZCBQUEEgZm9yIFVidW50dSBHaXQgTWFpbnRhaW5lcnPCtgQTAQIA \nIAUCSXjaGwIbAwYLCQgHAwIEFQIIAwQWAgMBAh4BAheAAAoJEKFxXYjh3x8k/zMD \n/RKBMjavvFl71YBazSOGl2YfSsZiR/ANsby3+rUaULb8uxzCHXAQnlH5vdtLSPry \naLBvzCU8C3C02qNT8jRacU2752zsCkCi1SLRSOXdI/ATJHza5aTvYV93rTITBhU4 \nsJQeK9RW0CtaDRAxJsn/Dr6J3lL/c9m9cT5fFpxOIsF4 \n=7kFR \n-----END PGP PUBLIC KEY BLOCK----- \n' > key - -RUN apt-key add key - -# Forcing IPv4 transport with apt-get -RUN apt-get -o Acquire::ForceIPv4=true update - -RUN apt-get -o Acquire::ForceIPv4=true install -y git - -# Change timezone to Asia/Shanghai -RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime - -# Install docker client -RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ - tar -xvzf docker.tgz &&\ - mv docker/* /usr/local/bin - - -# Replaces the default tar(for cephfs) -RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar - -COPY --from=build /reaper /usr/local/bin +COPY docker/dist/reaper /usr/local/bin ENTRYPOINT ["reaper"] diff --git a/docker/service/resource-server.Dockerfile b/docker/service/resource-server.Dockerfile index 2eb253c2..e76ca0ec 100644 --- a/docker/service/resource-server.Dockerfile +++ b/docker/service/resource-server.Dockerfile @@ -1,12 +1,8 @@ -#golang.Dockerfile - -RUN go build -o /reaper ./cmd/reaper/main.go - #nginx.Dockerfile WORKDIR /app ADD resource-server-nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=build /reaper . +ADD docker/dist/reaper . EXPOSE 80 diff --git a/docker/service/ua.Dockerfile b/docker/service/ua.Dockerfile index f3b7a43a..6b0323f5 100644 --- a/docker/service/ua.Dockerfile +++ b/docker/service/ua.Dockerfile @@ -1,10 +1,7 @@ -#golang.Dockerfile - -RUN go build -o /ua ./cmd/upgradeassistant/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /ua /app/ua + +ADD docker/dist/ua . ENTRYPOINT ["/app/ua"] \ No newline at end of file diff --git a/docker/service/user.Dockerfile b/docker/service/user.Dockerfile index 2ff14203..044c398b 100644 --- a/docker/service/user.Dockerfile +++ b/docker/service/user.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /user ./cmd/user/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /user . +ADD docker/dist/user . ENTRYPOINT ["/app/user"] diff --git a/docker/service/warpdrive.Dockerfile b/docker/service/warpdrive.Dockerfile index f7d0a44a..767b01c1 100644 --- a/docker/service/warpdrive.Dockerfile +++ b/docker/service/warpdrive.Dockerfile @@ -1,11 +1,7 @@ -#golang.Dockerfile - -RUN go build -v -o /warpdrive ./cmd/warpdrive/main.go - #alpine.Dockerfile WORKDIR /app -COPY --from=build /warpdrive . +COPY docker/dist/warpdrive . ENTRYPOINT ["/app/warpdrive"] diff --git a/docker/ua.Dockerfile.template b/docker/ua.Dockerfile.template deleted file mode 100644 index 18484199..00000000 --- a/docker/ua.Dockerfile.template +++ /dev/null @@ -1,10 +0,0 @@ -#golang-deps.Dockerfile.inc - -RUN go build -o /ua ./cmd/upgradeassistant/main.go - -#alpine-base.Dockerfile.inc - -WORKDIR /app -COPY --from=build /ua /app/ua - -ENTRYPOINT ["/app/ua"] -- Gitee From 2cabbede11099042cb191fad76f247144c2ba726 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 25 Jan 2022 17:41:12 +0800 Subject: [PATCH 1416/2647] code review changes Signed-off-by: Min Min --- Makefile | 28 ++++++------------- .../base/image/bionic-base-amd64.Dockerfile | 6 ++-- .../base/image/bionic-base-arm64.Dockerfile | 6 ++-- docker/base/image/focal-base-amd64.Dockerfile | 6 ++-- docker/base/image/focal-base-arm64.Dockerfile | 6 ++-- docker/service/predator-plugin.Dockerfile | 2 +- 6 files changed, 21 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 81720410..aa85bd71 100644 --- a/Makefile +++ b/Makefile @@ -25,11 +25,9 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @docker push ${MAKE_IMAGE} %.reaper.image.arm64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-arm64-$* -%.reaper.image.arm64: export GOOS = linux -%.reaper.image.arm64: export GOARCH = arm64 %.reaper.image.arm64: @mkdir -p docker/dist/arm64 - @go build -v -o docker/dist/reaper cmd/reaper/main.go + @GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @@ -41,11 +39,9 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @docker push ${MAKE_IMAGE} %.reaper.image.amd64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-amd64-$* -%.reaper.image.amd64: export GOOS = linux -%.reaper.image.amd64: export GOARCH = arm64 %.reaper.image.amd64: @mkdir -p docker/dist/amd64 - @go build -v -o docker/dist/reaper cmd/reaper/main.go + @GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @@ -57,15 +53,13 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @docker push ${MAKE_IMAGE} %.image.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 -%.image.amd64: export GOOS = linux -%.image.amd64: export GOARCH = amd64 %.image.amd64: @mkdir -p docker/dist/amd64 @cp docker/service/$*.Dockerfile docker/dist/amd64/$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/amd64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @go build -v -o docker/dist/$* cmd/$*/main.go + @GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 @@ -73,26 +67,22 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up docker push ${MAKE_IMAGE} %.image.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-arm64 -%.image.arm64: export GOOS = linux -%.image.arm64: export GOARCH = arm64 %.image.arm64: @mkdir -p docker/dist/arm64 @cp docker/service/$*.Dockerfile docker/dist/arm64/$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/arm64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @go build -v -o docker/dist/$* cmd/$*/main.go + @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/arm64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile + @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile + @GOOS=linux GOARCH=arm64 go build -v -o docker/dist/$* cmd/$*/main.go @docker build -f docker/dist/arm64/$*.Dockerfile --tag ${MAKE_IMAGE} . # USING BUILD AND UPLOAD INSTEAD OF IMAGE AND PUSH TO AVOID COLLISION resource-server.build.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 -resource-server.build.amd64: export GOOS = linux -resource-server.build.amd64: export GOARCH = amd64 resource-server.build.amd64: @mkdir -p docker/dist/amd64 @cp docker/service/resource-server.Dockerfile docker/dist/amd64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/resource-server.Dockerfile - @go build -v -o docker/dist/reaper cmd/reaper/main.go + @GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go @docker build -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 @@ -100,13 +90,11 @@ resource-server.upload.amd64: resource-server.build.amd64 @docker push ${MAKE_IMAGE} resource-server.build.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-arm64 -resource-server.build.arm64: export GOOS = linux -resource-server.build.arm64: export GOARCH = arm64 resource-server.build.arm64: @mkdir -p docker/dist/arm64 @cp docker/service/resource-server.Dockerfile docker/dist/arm64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/arm64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/arm64/resource-server.Dockerfile - @go build -v -o docker/dist/reaper cmd/reaper/main.go + @GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go @docker build -f docker/dist/arm64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-arm64 diff --git a/docker/base/image/bionic-base-amd64.Dockerfile b/docker/base/image/bionic-base-amd64.Dockerfile index 25da2529..3f7b22ca 100644 --- a/docker/base/image/bionic-base-amd64.Dockerfile +++ b/docker/base/image/bionic-base-amd64.Dockerfile @@ -19,13 +19,13 @@ RUN apt-get install -y \ librrd-dev \ sudo -# 修改时区 +# timezone modification RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -# 安装 docker client +# install docker client RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin -# 替换tar(适配cephfs) +# replace tar(compatible for cephfs) RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/bionic-base-arm64.Dockerfile b/docker/base/image/bionic-base-arm64.Dockerfile index f8ab8cd5..3c6f2b79 100644 --- a/docker/base/image/bionic-base-arm64.Dockerfile +++ b/docker/base/image/bionic-base-arm64.Dockerfile @@ -19,13 +19,13 @@ RUN apt-get install -y \ librrd-dev \ sudo -# 修改时区 +# timezone modification RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -# 安装 docker client +# install docker client RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin -# 替换tar(适配cephfs) +# replace tar(compatible for cephfs) RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/focal-base-amd64.Dockerfile b/docker/base/image/focal-base-amd64.Dockerfile index f812104f..0c73eebc 100644 --- a/docker/base/image/focal-base-amd64.Dockerfile +++ b/docker/base/image/focal-base-amd64.Dockerfile @@ -19,13 +19,13 @@ RUN apt-get install -y \ librrd-dev \ sudo -# 修改时区 +# timezone modification RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -# 安装 docker client +# install docker client RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin -# 替换tar(适配cephfs) +# replace tar(compatible for cephfs) RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/base/image/focal-base-arm64.Dockerfile b/docker/base/image/focal-base-arm64.Dockerfile index 68b54570..360d90fa 100644 --- a/docker/base/image/focal-base-arm64.Dockerfile +++ b/docker/base/image/focal-base-arm64.Dockerfile @@ -19,13 +19,13 @@ RUN apt-get install -y \ librrd-dev \ sudo -# 修改时区 +# timezone modification RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -# 安装 docker client +# install docker client RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin -# 替换tar(适配cephfs) +# replace tar(compatible for cephfs) RUN rm /bin/tar && curl -fsSL http://resource.koderover.com/tar -o /bin/tar && chmod +x /bin/tar diff --git a/docker/service/predator-plugin.Dockerfile b/docker/service/predator-plugin.Dockerfile index 099fa4eb..30f7b385 100644 --- a/docker/service/predator-plugin.Dockerfile +++ b/docker/service/predator-plugin.Dockerfile @@ -1,6 +1,6 @@ #ubuntu-xenial.Dockerfile -# 安装 docker client +# install docker client RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ tar -xvzf docker.tgz &&\ mv docker/* /usr/local/bin &&\ -- Gitee From 0c3cf464761587dfb707218fc8ab13a89d8fdaf3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 25 Jan 2022 18:01:00 +0800 Subject: [PATCH 1417/2647] support relese with status superseded Signed-off-by: allenshen --- .../core/service/taskplugin/deploy.go | 86 +++++++++---------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index f2974c10..f4fa75c5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -28,7 +28,6 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" @@ -54,6 +53,7 @@ import ( "github.com/koderover/zadig/pkg/util/converter" fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" + helmrelease "helm.sh/helm/v3/pkg/release" ) // InitializeDeployTaskPlugin to initiate deploy task plugin and return ref @@ -531,57 +531,55 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } - chartSpec := helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName), - ChartName: chartPath, - Namespace: p.Task.Namespace, - ReuseValues: true, - Version: renderChart.ChartVersion, - ValuesYaml: replacedMergedValuesYaml, - SkipCRDs: false, - UpgradeCRDs: true, - Timeout: time.Second * setting.DeployTimeout, - Wait: true, - } + releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) - pendingStatusProcess := func(typ string) error { - hrs, errHistory := helmClient.ListReleaseHistory(chartSpec.ReleaseName, 10) + ensureUpgrade := func() error { + hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) if errHistory != nil { - err = errors.WithMessagef( - err, - "failed to ListReleaseHistory: %s,error:%s", - chartSpec.ReleaseName, errHistory) - return err + // list history should not block deploy operation, error will be logged instead of returned + p.Log.Errorf("failed to list release history, release: %s, err: %s", releaseName, errHistory) + return nil } - if len(hrs) > 0 { - releaseutil.Reverse(hrs, releaseutil.SortByRevision) - rel := hrs[0] - if rel.Info.Status.IsPending() { - secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, rel.Name, rel.Version) - deleteErr := updater.DeleteSecretWithName(rel.Namespace, secretName, p.kubeClient) - if deleteErr != nil { - err = errors.WithMessagef(err, "failed to deleteSecretWithName:%s,error:%s", secretName, deleteErr) - return err - } - } + if len(hrs) == 0 { + return nil } - if err != nil { - return err - } else { - if typ == "timeout" { - err = fmt.Errorf("failed to upgrade %s", typ) + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + + // for release in superseded status or stuck in pending status , uninstall the service first + if rel.Info.Status == helmrelease.StatusSuperseded || (rel.Info.Status.IsPending() && time.Now().Sub(rel.Info.LastDeployed.Time).Seconds() > setting.DeployTimeout) { + p.Log.Infof("uninstalling release: %s in status: %s", releaseName, rel.Info.Status) + removeSpec := &helmclient.ChartSpec{ + ReleaseName: releaseName, + ChartName: chartPath, + Namespace: p.Task.Namespace, + Timeout: time.Second * setting.DeployTimeout, + Wait: true, } + err = helmClient.UninstallRelease(removeSpec) + return errors.Wrapf(err, "failed to uninstall superseded release: %s, err: %s", removeSpec.ReleaseName, err) } - return err + return nil } - // ensure release status not in pending - err = pendingStatusProcess("ensureStatus") + err = ensureUpgrade() if err != nil { - err = errors.Wrapf(err, "failed to delete pending status for release: %s", chartSpec.ReleaseName) return } + chartSpec := helmclient.ChartSpec{ + ReleaseName: releaseName, + ChartName: chartPath, + Namespace: p.Task.Namespace, + ReuseValues: true, + Version: renderChart.ChartVersion, + ValuesYaml: replacedMergedValuesYaml, + SkipCRDs: false, + UpgradeCRDs: true, + Timeout: time.Second * setting.DeployTimeout, + Wait: true, + } + done := make(chan bool) go func(chan bool) { if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { @@ -596,12 +594,10 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * }(done) select { - case d := <-done: - if !d { - err = pendingStatusProcess("normal") - } + case <-done: + break case <-time.After(chartSpec.Timeout + 30*time.Second): - err = pendingStatusProcess("timeout") + err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) } if err != nil { return -- Gitee From 34b5b26d9d7d05e090c02c169065a857c5469c09 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 25 Jan 2022 18:05:38 +0800 Subject: [PATCH 1418/2647] add replace parameter Signed-off-by: allenshen --- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index f4fa75c5..78969ab6 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -578,6 +578,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * UpgradeCRDs: true, Timeout: time.Second * setting.DeployTimeout, Wait: true, + Replace: true, } done := make(chan bool) -- Gitee From 35687f1f7d1f9dd85d252311c731248ec8ed42b4 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 25 Jan 2022 18:08:10 +0800 Subject: [PATCH 1419/2647] remove useless code Signed-off-by: allenshen --- pkg/util/yaml/path_search.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index b88f911a..787556d0 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -387,7 +387,6 @@ func search(flatMap map[string]interface{}, pattern map[string]string) ([]map[st } relativePaths := relativeSearcher.getRelativePath() - fmt.Printf("the relative paths is %v \n", relativePaths) checker := newRelativeChecker(relativePaths) allKeys := make([][]string, 0) -- Gitee From 88f83f9fa63e2954f060f0cb56ab3d5c033cac6b Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 25 Jan 2022 19:53:12 +0800 Subject: [PATCH 1420/2647] rm queue redundant code Signed-off-by: ddh27 --- .../core/common/repository/models/queue.go | 90 +++++++++---------- .../core/workflow/service/workflow/convert.go | 2 - .../service/taskcontroller/task_handler.go | 2 - 3 files changed, 44 insertions(+), 50 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 4c299d8c..250f975b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -22,55 +22,53 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/setting" ) type Queue struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` - MultiRun bool `bson:"multi_run" json:"multi_run"` - Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows - ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` - ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `bson:"resetImage" json:"resetImage"` - ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` - TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + Stages []*Stage `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + MultiRun bool `bson:"multi_run" json:"multi_run"` + Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 51e259d2..6f5831c7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -69,7 +69,6 @@ func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { TestReports: queueTask.TestReports, RwLock: queueTask.RwLock, ResetImage: queueTask.ResetImage, - ResetImagePolicy: queueTask.ResetImagePolicy, TriggerBy: queueTask.TriggerBy, Features: queueTask.Features, IsRestart: queueTask.IsRestart, @@ -116,7 +115,6 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { TestReports: task.TestReports, RwLock: task.RwLock, ResetImage: task.ResetImage, - ResetImagePolicy: task.ResetImagePolicy, TriggerBy: task.TriggerBy, Features: task.Features, IsRestart: task.IsRestart, diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 154e2cbd..7ef555b1 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -379,8 +379,6 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe if testStageStatus == config.StatusInit || (testStageStatus != config.StatusFailed && testStageStatus != config.StatusCancelled && testStageStatus != config.StatusTimeout) { continue } - default: - continue } } h.runStage(stagePosition, stage) -- Gitee From fb0356beccfa470cd992c40403bffded59d3aa4e Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 25 Jan 2022 20:38:13 +0800 Subject: [PATCH 1421/2647] update version Signed-off-by: allenshen --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 575a6345..55085813 100644 --- a/version/version.go +++ b/version/version.go @@ -17,7 +17,7 @@ limitations under the License. package version var ( - Version = "1.8.0" + Version = "1.9.0" BuildNumber = "0" GitCommit = "" ) -- Gitee From 6de2036c9efa329a98e19b3840b5c8b6a49d6b36 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 26 Jan 2022 10:22:56 +0800 Subject: [PATCH 1422/2647] optimize Signed-off-by: panxunying --- .../collaboration/repository/models/collaboration_instance.go | 2 +- .../core/collaboration/service/collaboration_instance.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index 5781b728..f24391f3 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -29,7 +29,7 @@ type CollaborationInstance struct { UpdateTime int64 `bson:"update_time" json:"update_time"` CollaborationName string `bson:"collaboration_name" json:"collaboration_name"` Revision int64 `bson:"revision" json:"revision"` - User string `bson:"user" json:"user"` + UserUID string `bson:"user_uid" json:"user_uid"` PolicyName string `bson:"policy_name" json:"policy_name"` Workflows []WorkflowCIItem `json:"workflows" bson:"workflows"` Products []ProductCIItem `bson:"products" json:"products"` diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index b3c950d7..a6f66002 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -215,7 +215,7 @@ func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, u return &models.CollaborationInstance{ ProjectName: mode.Name, CollaborationName: mode.Name, - User: uid, + UserUID: uid, PolicyName: buildPolicyName(projectName, mode.Name, userName), Revision: mode.Revision, Workflows: workflows, @@ -313,7 +313,7 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, userName, findOpts = append(findOpts, mongodb.CollaborationInstanceFindOptions{ Name: instance.CollaborationName, ProjectName: instance.ProjectName, - UserUID: instance.User, + UserUID: instance.UserUID, }) } err = mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ -- Gitee From da79429a97f4361aaf246dc9d9357c6f20aec4d8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 10:24:17 +0800 Subject: [PATCH 1423/2647] get resources Signed-off-by: mouuii --- pkg/shared/handler/base.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index ee84aded..0edd06a0 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -78,8 +78,12 @@ func GetResourcesInHeader(c *gin.Context) ([]string, bool) { if res == "" { return nil, true } + var resources []string + if err := json.Unmarshal([]byte(res), &resources); err != nil { + return nil, false + } - return strings.Split(res, ","), true + return resources, true } func getUserFromJWT(token string) (jwtClaims, error) { -- Gitee From dd9f68792637f885b0a1f832046340fc806fdb3d Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 11:00:45 +0800 Subject: [PATCH 1424/2647] change get req to post req Signed-off-by: mouuii --- .../core/environment/handler/environment.go | 13 +++++++++++++ .../policy/core/handler/permission.go | 2 +- pkg/microservice/policy/core/handler/router.go | 2 +- pkg/shared/client/policy/policy.go | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index bfe6e1af..565e48fb 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -34,6 +34,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/policy" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/shared/kube/resource" e "github.com/koderover/zadig/pkg/tool/errors" @@ -72,6 +73,18 @@ func ListProducts(c *gin.Context) { ctx.Resp = []*service.ProductResp{} return } + req := &policy.ResourcePermissionReq{ + ProjectName: projectName, + Uid: ctx.UserID, + Resources: envNames, + ResourceType: "Environment", + } + resourcePermission, err := policy.NewDefault().GetResourcePermission(req) + if err != nil { + ctx.Err = err + return + } + fmt.Println("%v", resourcePermission) switch c.Query(setting.Subresource) { case setting.IngressSubresource: diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index 699555ad..3c0486a6 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -50,7 +50,7 @@ func GetUserResourcesPermission(c *gin.Context) { req := new(GetUserResourcesPermissionReq) - if err := c.ShouldBindQuery(req); err != nil { + if err := c.ShouldBindJSON(req); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) return } diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 89f227d6..b167dcdc 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -115,7 +115,7 @@ func (*Router) Inject(router *gin.RouterGroup) { } policyUserPermission := router.Group("permission") { - policyUserPermission.GET("resources", GetUserResourcesPermission) + policyUserPermission.POST("resources", GetUserResourcesPermission) policyUserPermission.GET("/:uid", GetUserPermission) } diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 0f4dd76a..fb8a9f6e 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -78,3 +78,21 @@ func (c *Client) UpdatePolicy(ns string, policy *Policy) error { _, err := c.Put(url, httpclient.SetBody(policy)) return err } + +type ResourcePermissionReq struct { + ProjectName string `json:"project_name" form:"project_name"` + Uid string `json:"uid" form:"uid"` + Resources []string `json:"resources" form:"resources"` + ResourceType string `json:"resource_type" form:"resource_type"` +} + +func (c *Client) GetResourcePermission(req *ResourcePermissionReq) (map[string][]string, error) { + url := fmt.Sprintf("/permission/resources") + result := make(map[string][]string) + _, err := c.Post(url, httpclient.SetResult(req), httpclient.SetResult(&result)) + if err != nil { + log.Errorf("Failed to get resourcePermission,err: %s", err) + return nil, err + } + return result, nil +} -- Gitee From 4553a31b836ab04ab90dd5a0ad0f4fb8bb325690 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 26 Jan 2022 11:21:15 +0800 Subject: [PATCH 1425/2647] disable CGO build Signed-off-by: Min Min --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index aa85bd71..98a7c92a 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up %.reaper.image.arm64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-arm64-$* %.reaper.image.arm64: @mkdir -p docker/dist/arm64 - @GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile @@ -41,7 +41,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up %.reaper.image.amd64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-amd64-$* %.reaper.image.amd64: @mkdir -p docker/dist/amd64 - @GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile @@ -59,7 +59,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/amd64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile - @GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 @@ -73,7 +73,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/arm64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/arm64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile - @GOOS=linux GOARCH=arm64 go build -v -o docker/dist/$* cmd/$*/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -o docker/dist/$* cmd/$*/main.go @docker build -f docker/dist/arm64/$*.Dockerfile --tag ${MAKE_IMAGE} . # USING BUILD AND UPLOAD INSTEAD OF IMAGE AND PUSH TO AVOID COLLISION @@ -82,7 +82,7 @@ resource-server.build.amd64: @mkdir -p docker/dist/amd64 @cp docker/service/resource-server.Dockerfile docker/dist/amd64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/resource-server.Dockerfile - @GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go @docker build -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 @@ -94,7 +94,7 @@ resource-server.build.arm64: @mkdir -p docker/dist/arm64 @cp docker/service/resource-server.Dockerfile docker/dist/arm64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/arm64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/arm64/resource-server.Dockerfile - @GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go @docker build -f docker/dist/arm64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-arm64 -- Gitee From 54a95d9aa016f3369e4f1bc4fb9fc1af76e19690 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 26 Jan 2022 12:13:03 +0800 Subject: [PATCH 1426/2647] remove uninstall release logic Signed-off-by: allenshen --- .../warpdrive/core/service/taskplugin/deploy.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 78969ab6..9d1530af 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -547,17 +547,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * rel := hrs[0] // for release in superseded status or stuck in pending status , uninstall the service first - if rel.Info.Status == helmrelease.StatusSuperseded || (rel.Info.Status.IsPending() && time.Now().Sub(rel.Info.LastDeployed.Time).Seconds() > setting.DeployTimeout) { - p.Log.Infof("uninstalling release: %s in status: %s", releaseName, rel.Info.Status) - removeSpec := &helmclient.ChartSpec{ - ReleaseName: releaseName, - ChartName: chartPath, - Namespace: p.Task.Namespace, - Timeout: time.Second * setting.DeployTimeout, - Wait: true, - } - err = helmClient.UninstallRelease(removeSpec) - return errors.Wrapf(err, "failed to uninstall superseded release: %s, err: %s", removeSpec.ReleaseName, err) + if rel.Info.Status == helmrelease.StatusSuperseded || rel.Info.Status.IsPending() { + return fmt.Errorf("failed to upgrade release: %s with exceptional status: %s", releaseName, rel.Info.Status) } return nil } -- Gitee From 13dd75024b851d057c1b1bf5a6be9d8a000478b7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 26 Jan 2022 14:45:45 +0800 Subject: [PATCH 1427/2647] deleted selector label to avoid bugs Signed-off-by: Min Min --- .../aslan/core/environment/service/environment.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 3759bd40..d5a5abd4 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1785,18 +1785,6 @@ func upsertService(isUpdate bool, env *commonmodels.Product, u.Object = setFieldValueIsNotExist(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") } - // Inject selector: s-product and s-service - selector, _, err := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") - if err != nil { - selector = nil - } - - err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") - if err != nil { - log.Errorf("merge selector failed err:%s", err) - u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") - } - jsonData, err := u.MarshalJSON() if err != nil { log.Errorf("Failed to marshal JSON, manifest is\n%v\n, error: %v", u, err) -- Gitee From f8fcf165f737732da599c598c0d44916b258dd20 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 15:08:46 +0800 Subject: [PATCH 1428/2647] ua for policy and env labels Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 258 ++++++++++++++++++ .../internal/repository/models/oldpolicy.go | 28 ++ .../label/repository/mongodb/labelbinding.go | 21 +- 3 files changed, 301 insertions(+), 6 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/1100.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go new file mode 100644 index 00000000..07d0a96e --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -0,0 +1,258 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrate + +import ( + "context" + "fmt" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + labelModel "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + + "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +func init() { + upgradepath.RegisterHandler("1.9.0", "1.10.0", V190ToV1100) + upgradepath.RegisterHandler("1.10.0", "1.9.0", V1100ToV190) +} + +// V190ToV1100 generate labelBindings for production environment +func V190ToV1100() error { + log.Info("Migrating data from 1.9.0 to 1.10.0") + if err := generateProductionEnv(); err != nil { + log.Errorf("Failed to generateProductionEnv, err: %s", err) + return err + } + if err := changePolicyCollectionName(); err != nil { + log.Errorf("Failed to changePolicyCollectionName, err: %s", err) + return err + } + return nil +} + +func changePolicyCollectionName() error { + var res []*models.PolicyMeta + // + ctx := context.Background() + cursor, err := newPolicyColl().Find(ctx, bson.M{}) + if err != nil { + return err + } + + err = cursor.All(ctx, &res) + if err != nil { + return err + } + + var ois []interface{} + for _, obj := range res { + ois = append(ois, obj) + } + if _, err = newPolicyMetaColl().InsertMany(context.TODO(), ois); err != nil { + return err + } + //delete collection + return newPolicyColl().Drop(ctx) +} + +func V1100ToV190() error { + if err := deleteLabelAndLabelBindings(); err != nil { + log.Errorf("Failed to generateProductionEnv, err: %s", err) + return err + } + return nil +} + +func deleteLabelAndLabelBindings() error { + lisOpt := labelMongodb.ListLabelOpt{ + []labelMongodb.Label{ + { + Key: "production", + Value: "false", + }, + { + Key: "production", + Value: "true", + }, + }, + } + labels, err := newLabelColl().List(lisOpt) + if err != nil { + return err + } + if len(labels) != 2 { + log.Errorf("production labels len not equal 2,current is %d\n", len(labels)) + return fmt.Errorf("production labels len not equal 2,current is %d\n", len(labels)) + } + var labelIDs []string + for _, label := range labels { + labelIDs = append(labelIDs, label.ID.Hex()) + } + + res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIDs}) + if err != nil { + log.Errorf("list labelbingding err:%s", err) + return err + } + + var labelBindingIDs []string + for _, labelBinding := range res { + labelBindingIDs = append(labelBindingIDs, labelBinding.ID.Hex()) + } + + if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { + return err + } + return mongodb.NewLabelColl().BulkDelete(labelIDs) + +} + +func generateProductionEnv() error { + // create label key=product value=true + err := createLabels() + if err != nil { + log.Errorf("fail to createLabels , err:%s", err) + return err + } + + // create labelBindings for environment resources + if err := createLabelBindings(); err != nil { + log.Errorf("fail to createLabelBindings,err:%s", err) + return err + } + + return nil +} + +func newLabelColl() *labelMongodb.LabelColl { + name := labelModel.Label{}.TableName() + return &labelMongodb.LabelColl{ + Collection: mongotool.Database(fmt.Sprintf("%s", config.MongoDatabase())).Collection(name), + } +} + +func newPolicyColl() *mongo.Collection { + collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("policy") + return collection +} + +func newPolicyMetaColl() *mongo.Collection { + collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("policy_meta") + return collection +} + +func createLabels() error { + var toCreateLabels []*labelModel.Label + productEnvLabel := &labelModel.Label{ + Type: "system", + Key: "production", + Value: "true", + CreateBy: "system", + CreateTime: time.Now().Unix(), + } + nonProductEnvLabel := &labelModel.Label{ + Type: "system", + Key: "production", + Value: "false", + CreateBy: "system", + CreateTime: time.Now().Unix(), + } + toCreateLabels = append(toCreateLabels, productEnvLabel, nonProductEnvLabel) + if err := newLabelColl().BulkCreate(toCreateLabels); err != nil { + return err + } + return nil +} + +func createLabelBindings() error { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{}) + if err != nil { + return err + } + clusterMap := make(map[string]*commonmodels.K8SCluster) + clusters, err := commonrepo.NewK8SClusterColl().List(nil) + if err != nil { + log.Errorf("Failed to list clusters in db, err: %s", err) + return err + } + + for _, cls := range clusters { + clusterMap[cls.ID.Hex()] = cls + } + lisOpt := labelMongodb.ListLabelOpt{ + []labelMongodb.Label{ + { + Key: "production", + Value: "false", + }, + { + Key: "production", + Value: "true", + }, + }, + } + labels, err := newLabelColl().List(lisOpt) + if err != nil { + return err + } + if len(labels) != 2 { + return fmt.Errorf("production labels len not equal 2") + } + var productLabelID, nonProductLabelID string + for _, label := range labels { + if label.Value == "false" { + nonProductLabelID = label.ID.Hex() + } else { + productLabelID = label.ID.Hex() + } + } + var labelBindings []*labelMongodb.LabelBinding + for _, env := range envs { + lb := &labelMongodb.LabelBinding{ + Resource: labelMongodb.Resource{ + Name: env.EnvName, + ProjectName: env.ProductName, + Type: "Environment", + }, + CreateBy: "system", + } + cluster, ok := clusterMap[env.ClusterID] + if ok && cluster.Production { + lb.LabelID = productLabelID + } else { + lb.LabelID = nonProductLabelID + } + labelBindings = append(labelBindings, lb) + } + + if err := labelMongodb.NewLabelBindingColl().CreateMany(labelBindings); err != nil { + return err + } + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go new file mode 100644 index 00000000..21bc6c12 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -0,0 +1,28 @@ +package models + +type PolicyMeta struct { + Resource string `bson:"resource" json:"resource"` + Alias string `bson:"alias" json:"alias"` + Description string `bson:"description" json:"description"` + Rules []*PolicyMetaRule `bson:"rules" json:"rules"` +} + +type PolicyMetaRule struct { + Action string `bson:"action" json:"action"` + Alias string `bson:"alias" json:"alias"` + Description string `bson:"description" json:"description"` + Rules []*ActionRule `bson:"rules" json:"rules"` +} + +type ActionRule struct { + Method string `bson:"method" json:"method"` + Endpoint string `bson:"endpoint" json:"endpoint"` + ResourceType string `bson:"resource_type,omitempty" json:"resourceType,omitempty"` + IDRegex string `bson:"id_regex,omitempty" json:"idRegex,omitempty"` + MatchAttributes []Attribute `bson:"match_attributes,omitempty" json:"matchAttributes,omitempty"` +} + +type Attribute struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` +} diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index 962cbbb5..51a4ec77 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -61,22 +61,31 @@ func (c *LabelBindingColl) EnsureIndex(ctx context.Context) error { } type LabelBinding struct { - Resource Resource `json:"resource"` - LabelID string `json:"label_id"` - CreateBy string `json:"create_by"` - CreateTime int64 `json:"create_time"` + Resource Resource `json:"resource" bson:"resource"` + LabelID string `json:"label_id" bson:"label_id"` + CreateBy string `json:"create_by" bson:"create_by"` + CreateTime int64 `json:"create_time" bson:"create_time"` } func (c *LabelBindingColl) CreateMany(labelBindings []*LabelBinding) error { if len(labelBindings) == 0 { return nil } + var lbs []models.LabelBinding for _, labelBinding := range labelBindings { - labelBinding.CreateTime = time.Now().Unix() + tmplb := models.LabelBinding{ + ResourceType: labelBinding.Resource.Type, + ResourceName: labelBinding.Resource.Name, + ProjectName: labelBinding.Resource.ProjectName, + LabelID: labelBinding.LabelID, + CreateBy: labelBinding.CreateBy, + CreateTime: time.Now().Unix(), + } + lbs = append(lbs, tmplb) } var ois []interface{} - for _, obj := range labelBindings { + for _, obj := range lbs { ois = append(ois, obj) } _, err := c.InsertMany(context.TODO(), ois) -- Gitee From 9da0a27537921787903653eda30c703beccfe99d Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 15:10:11 +0800 Subject: [PATCH 1429/2647] add copyright Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 2 +- .../internal/repository/models/oldpolicy.go | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 07d0a96e..607a5bac 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 21bc6c12..1a445407 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type PolicyMeta struct { -- Gitee From a411b4c8c341e6e9a5935f095741a32a7bcd7ca8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 26 Jan 2022 15:37:49 +0800 Subject: [PATCH 1430/2647] If deleting a default image repository, automatically set a default value Signed-off-by: liu deyi --- .../aslan/core/system/service/registry.go | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 853cc712..92a3f2db 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -130,10 +130,36 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam } func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { + registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + if err != nil { + log.Errorf("RegistryNamespace.FindAll error: %s", err) + return err + } + var ( + isDefault = false + registryNamespaces []*commonmodels.RegistryNamespace + ) + // whether it is the default registry + for _, registry := range registries { + if registry.ID.Hex() == id && registry.IsDefault { + isDefault = true + continue + } + registryNamespaces = append(registryNamespaces, registry) + } + if err := commonrepo.NewRegistryNamespaceColl().Delete(id); err != nil { - log.Errorf("RegistryNamespace.Delete error: %v", err) + log.Errorf("RegistryNamespace.Delete error: %s", err) return err } + + if isDefault && len(registryNamespaces) > 0 { + registryNamespaces[0].IsDefault = true + if err := commonrepo.NewRegistryNamespaceColl().Update(registryNamespaces[0].ID.Hex(), registryNamespaces[0]); err != nil { + log.Errorf("RegistryNamespace.Update error: %s", err) + return err + } + } return nil } -- Gitee From 4b8fb83318d346e67c8051db79a55778c57409b6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 26 Jan 2022 15:47:54 +0800 Subject: [PATCH 1431/2647] fixed operation log failed to update status code Signed-off-by: Min Min --- pkg/shared/handler/base.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index 3146e541..860fbaee 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -133,7 +133,7 @@ func InsertOperationLog(c *gin.Context, username, productName, method, function, if err != nil { logger.Errorf("InsertOperation err:%v", err) } - c.Set("operationLogID", operationLogID) + c.Set("operationLogID", operationLogID.OperationLogID) } // responseHelper recursively finds all nil slice in the given interface, -- Gitee From 38719112be862ddfd0e34f74f1add723a4b37213 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 17:42:40 +0800 Subject: [PATCH 1432/2647] delete project Signed-off-by: mouuii --- .../mongodb/collaboration_instance.go | 8 ++++ .../aslan/core/project/service/product.go | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index eda4b7d7..0c746aca 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -152,6 +152,14 @@ func (c *CollaborationInstanceColl) BulkDelete(opt CollaborationInstanceListOpti return err } +func (c *CollaborationInstanceColl) DeleteByProject(projectName string) error { + query := bson.M{} + query["project_name"] = projectName + _, err := c.DeleteMany(context.TODO(), query) + return err + +} + func (c *CollaborationInstanceColl) List(opt *CollaborationInstanceFindOptions) ([]*models.CollaborationInstance, error) { var ret []*models.CollaborationInstance query := bson.M{} diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 64de1b48..4ee735e2 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "encoding/json" "fmt" "regexp" @@ -25,17 +26,22 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" @@ -44,6 +50,7 @@ import ( configclient "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) type CustomParseDataArgs struct { @@ -454,9 +461,43 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug ProductName: productName, }) }() + // delete collaboration_mode and collaboration_instance + go func() { + // find all collaboration mode in this project + res, err := collaboration.GetCollaborationModes([]string{productName}, log) + if err != nil { + log.Errorf("GetCollaborationModes err: %s", err) + } + // delete all collaborationMode + for _, mode := range res.Collaborations { + if err := service.DeleteCollaborationMode(userName, productName, mode.Name, log); err != nil { + log.Errorf("DeleteCollaborationMode err: %s", err) + } + } + // delete all collaborationIns + if err := mongodb.NewCollaborationInstanceColl().DeleteByProject(productName); err != nil { + log.Errorf("NewCollaborationInstanceColl DeleteByProject err:%s", err) + } + + }() + + go func() { + query := bson.M{} + query["namespace"] = productName + _, err := newPolicyMetaColl().DeleteMany(context.Background(), query) + if err != nil { + log.Errorf("newPolicyMetaColl delete err: %s", err) + } + }() + // delete policy return nil } +func newPolicyMetaColl() *mongo.Collection { + collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("policy_meta") + return collection +} + func ForkProduct(username, uid, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) -- Gitee From ea02347437786211d955eb966837d6261739c3c8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 17:44:36 +0800 Subject: [PATCH 1433/2647] delete project Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 4ee735e2..57729ec6 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -480,7 +480,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug } }() - + // delete policy go func() { query := bson.M{} query["namespace"] = productName @@ -489,7 +489,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug log.Errorf("newPolicyMetaColl delete err: %s", err) } }() - // delete policy + return nil } -- Gitee From 378e9a81a6eb836a348d2faa0c4a14dd2540c161 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 26 Jan 2022 18:07:41 +0800 Subject: [PATCH 1434/2647] Microservice deployment statistics increase sorting Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/service/deploy_stat.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go index b8f16e71..f4a0d3ed 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy_stat.go +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -357,8 +357,10 @@ func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []stri if tempDeployStat.MaxDeployServiceName != "" { deployHigherStats = append(deployHigherStats, deployHigherStat) } - } + sort.SliceStable(deployHigherStats, func(i, j int) bool { + return deployHigherStats[i].TotalFailure+deployHigherStats[i].TotalSuccess < deployHigherStats[j].TotalFailure+deployHigherStats[j].TotalSuccess + }) return deployHigherStats, nil } @@ -385,5 +387,9 @@ func GetDeployTopFiveFailureMeasure(startDate, endDate int64, productNames []str deployFailureHigherStats = append(deployFailureHigherStats, deployFailureHigherStat) } } + + sort.SliceStable(deployFailureHigherStats, func(i, j int) bool { + return deployFailureHigherStats[i].TotalFailure < deployFailureHigherStats[j].TotalFailure + }) return deployFailureHigherStats, nil } -- Gitee From 3353555019b8eaad803034655a079cfa0b68a5ed Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 26 Jan 2022 18:11:09 +0800 Subject: [PATCH 1435/2647] Microservice deployment statistics increase sorting Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/service/deploy_stat.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go index f4a0d3ed..c124b6aa 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy_stat.go +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -359,7 +359,7 @@ func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []stri } } sort.SliceStable(deployHigherStats, func(i, j int) bool { - return deployHigherStats[i].TotalFailure+deployHigherStats[i].TotalSuccess < deployHigherStats[j].TotalFailure+deployHigherStats[j].TotalSuccess + return deployHigherStats[i].TotalFailure+deployHigherStats[i].TotalSuccess > deployHigherStats[j].TotalFailure+deployHigherStats[j].TotalSuccess }) return deployHigherStats, nil } @@ -389,7 +389,7 @@ func GetDeployTopFiveFailureMeasure(startDate, endDate int64, productNames []str } sort.SliceStable(deployFailureHigherStats, func(i, j int) bool { - return deployFailureHigherStats[i].TotalFailure < deployFailureHigherStats[j].TotalFailure + return deployFailureHigherStats[i].TotalFailure > deployFailureHigherStats[j].TotalFailure }) return deployFailureHigherStats, nil } -- Gitee From 0bef7cdb42f2b018e5ff89d67e3dcf39ae4c7806 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 26 Jan 2022 18:12:14 +0800 Subject: [PATCH 1436/2647] delete project Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 607a5bac..5c850403 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -81,11 +81,41 @@ func changePolicyCollectionName() error { return newPolicyColl().Drop(ctx) } +func rollbackChangePolicyCollectionName() error { + var res []*models.PolicyMeta + // + ctx := context.Background() + cursor, err := newPolicyMetaColl().Find(ctx, bson.M{}) + if err != nil { + return err + } + + err = cursor.All(ctx, &res) + if err != nil { + return err + } + + var ois []interface{} + for _, obj := range res { + ois = append(ois, obj) + } + if _, err = newPolicyColl().InsertMany(context.TODO(), ois); err != nil { + return err + } + //delete collection + return newPolicyMetaColl().Drop(ctx) +} + func V1100ToV190() error { if err := deleteLabelAndLabelBindings(); err != nil { log.Errorf("Failed to generateProductionEnv, err: %s", err) return err } + + if err := rollbackChangePolicyCollectionName(); err != nil { + log.Errorf("Failed to rollbackChangePolicyCollectionName") + return err + } return nil } -- Gitee From 42c16a44b72f89655273258f44a92dca641cee92 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 26 Jan 2022 18:22:51 +0800 Subject: [PATCH 1437/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/service/deploy_stat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go index c124b6aa..034c1129 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy_stat.go +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -359,7 +359,7 @@ func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []stri } } sort.SliceStable(deployHigherStats, func(i, j int) bool { - return deployHigherStats[i].TotalFailure+deployHigherStats[i].TotalSuccess > deployHigherStats[j].TotalFailure+deployHigherStats[j].TotalSuccess + return (deployHigherStats[i].TotalSuccess + deployHigherStats[i].TotalFailure) > (deployHigherStats[j].TotalSuccess + deployHigherStats[j].TotalFailure) }) return deployHigherStats, nil } -- Gitee From 34225131308ec02af18a07c0ed8ee03790fcb07a Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 26 Jan 2022 18:24:52 +0800 Subject: [PATCH 1438/2647] debugs Signed-off-by: Min Min --- pkg/microservice/aslan/core/environment/service/environment.go | 3 +++ pkg/microservice/aslan/core/environment/service/product.go | 1 + 2 files changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d5a5abd4..93b3b543 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -91,6 +91,7 @@ type EnvResp struct { UpdateBy string `json:"updateBy"` UpdateTime int64 `json:"updateTime"` IsPublic bool `json:"isPublic"` + IsExisted bool `json:"is_existed"` ClusterName string `json:"clusterName"` ClusterID string `json:"cluster_id"` Production bool `json:"production"` @@ -116,6 +117,7 @@ type ProductResp struct { RecycleDay int `json:"recycle_day"` IsProd bool `json:"is_prod"` IsLocal bool `json:"is_local"` + IsExisted bool `json:"is_existed"` Source string `json:"source"` RegisterID string `json:"registry_id"` } @@ -207,6 +209,7 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ProjectName: projectName, Name: env.EnvName, IsPublic: env.IsPublic, + IsExisted: env.IsExisted, ClusterName: clusterName, Source: env.Source, Production: production, diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 6ba30b3d..5927582f 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -195,6 +195,7 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar Error: prod.Error, Vars: prod.Vars[:], IsPublic: prod.IsPublic, + IsExisted: prod.IsExisted, ClusterID: prod.ClusterID, RecycleDay: prod.RecycleDay, Source: prod.Source, -- Gitee From 14917d387dbc8b830974f2147a4fa8229a5e86a6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 26 Jan 2022 18:28:08 +0800 Subject: [PATCH 1439/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/service/deploy_stat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go index 034c1129..6f84cb73 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy_stat.go +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -359,7 +359,7 @@ func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []stri } } sort.SliceStable(deployHigherStats, func(i, j int) bool { - return (deployHigherStats[i].TotalSuccess + deployHigherStats[i].TotalFailure) > (deployHigherStats[j].TotalSuccess + deployHigherStats[j].TotalFailure) + return deployHigherStats[i].TotalSuccess > deployHigherStats[j].TotalSuccess }) return deployHigherStats, nil } -- Gitee From eeb4f4feacf416a869e6f7295bbdc108b1778f07 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 26 Jan 2022 18:39:09 +0800 Subject: [PATCH 1440/2647] fix resetImagePolicy Signed-off-by: ddh27 --- .../core/common/repository/models/queue.go | 90 ++++++++++--------- .../core/workflow/service/workflow/convert.go | 2 + 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 250f975b..4c299d8c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -22,53 +22,55 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" ) type Queue struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` - MultiRun bool `bson:"multi_run" json:"multi_run"` - Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName - BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing - ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows - ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` - ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - Services [][]*ProductService `bson:"services" json:"services"` - Render *RenderInfo `bson:"render" json:"render"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - RwLock sync.Mutex `bson:"-" json:"-"` - ResetImage bool `bson:"resetImage" json:"resetImage"` - TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` - Features []string `bson:"features" json:"features"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + Stages []*Stage `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamID int `bson:"team_id,omitempty" json:"team_id,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + MultiRun bool `bson:"multi_run" json:"multi_run"` + Target string `bson:"target,omitempty" json:"target"` // target service name, for k8s: containerName, for pm: serviceName + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` // TaskArgs job parameters for single-service workflow + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // WorkflowArgs job parameters for multi-service workflow + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` // TestArgs parameters for testing + ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ServiceTaskArgs parameters for script-deployed workflows + ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + ConfigPayload *ConfigPayload `bson:"configpayload,omitempty" json:"config_payload"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*ProductService `bson:"services" json:"services"` + Render *RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` + TriggerBy *TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` } type TriggerBy struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 6f5831c7..51e259d2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -69,6 +69,7 @@ func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { TestReports: queueTask.TestReports, RwLock: queueTask.RwLock, ResetImage: queueTask.ResetImage, + ResetImagePolicy: queueTask.ResetImagePolicy, TriggerBy: queueTask.TriggerBy, Features: queueTask.Features, IsRestart: queueTask.IsRestart, @@ -115,6 +116,7 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { TestReports: task.TestReports, RwLock: task.RwLock, ResetImage: task.ResetImage, + ResetImagePolicy: task.ResetImagePolicy, TriggerBy: task.TriggerBy, Features: task.Features, IsRestart: task.IsRestart, -- Gitee From 9a711d544d14c15fbd6a196fd6a2edca80ef37fb Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 27 Jan 2022 10:36:33 +0800 Subject: [PATCH 1441/2647] add logs Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 5c850403..61369e72 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -157,6 +157,7 @@ func deleteLabelAndLabelBindings() error { } if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { + log.Errorf("Failed to BulkDeleteByIds, err: %s", err) return err } return mongodb.NewLabelColl().BulkDelete(labelIDs) @@ -215,6 +216,7 @@ func createLabels() error { } toCreateLabels = append(toCreateLabels, productEnvLabel, nonProductEnvLabel) if err := newLabelColl().BulkCreate(toCreateLabels); err != nil { + log.Errorf("fail to BulkCreate , err:%s", err) return err } return nil @@ -223,12 +225,13 @@ func createLabels() error { func createLabelBindings() error { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{}) if err != nil { + log.Errorf("fail to List ProductColl, err:%s", err) return err } clusterMap := make(map[string]*commonmodels.K8SCluster) clusters, err := commonrepo.NewK8SClusterColl().List(nil) if err != nil { - log.Errorf("Failed to list clusters in db, err: %s", err) + log.Errorf("Failed to list clusters, err: %s", err) return err } @@ -249,6 +252,7 @@ func createLabelBindings() error { } labels, err := newLabelColl().List(lisOpt) if err != nil { + log.Errorf("Failed to list labels, err: %s", err) return err } if len(labels) != 2 { @@ -282,6 +286,7 @@ func createLabelBindings() error { } if err := labelMongodb.NewLabelBindingColl().CreateMany(labelBindings); err != nil { + log.Errorf("Failed toCreateMany labels, err: %s", err) return err } return nil -- Gitee From 02bdca69a2d67d7e492748310a43ed5bbde9920a Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 27 Jan 2022 10:38:03 +0800 Subject: [PATCH 1442/2647] add logs Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 61369e72..b11e3115 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -62,11 +62,13 @@ func changePolicyCollectionName() error { ctx := context.Background() cursor, err := newPolicyColl().Find(ctx, bson.M{}) if err != nil { + log.Errorf("Failed to Find Policies, err: %s", err) return err } err = cursor.All(ctx, &res) if err != nil { + log.Errorf("Failed to cursor.All, err: %s", err) return err } @@ -75,6 +77,7 @@ func changePolicyCollectionName() error { ois = append(ois, obj) } if _, err = newPolicyMetaColl().InsertMany(context.TODO(), ois); err != nil { + log.Errorf("Failed to InsertMany policyMetas, err: %s", err) return err } //delete collection @@ -92,6 +95,7 @@ func rollbackChangePolicyCollectionName() error { err = cursor.All(ctx, &res) if err != nil { + log.Errorf("Failed to cursor.All, err: %s", err) return err } @@ -100,6 +104,7 @@ func rollbackChangePolicyCollectionName() error { ois = append(ois, obj) } if _, err = newPolicyColl().InsertMany(context.TODO(), ois); err != nil { + log.Errorf("Failed to InsertMany, err: %s", err) return err } //delete collection -- Gitee From d99a079720fe443b42798d385f1bfa7e3ef94320 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 25 Jan 2022 22:15:23 +0800 Subject: [PATCH 1443/2647] support pvc cache Signed-off-by: zhangyifei --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 160 +++++++ .../internal/repository/models/build.go | 160 +++++++ .../internal/repository/models/testing.go | 100 ++++ .../internal/repository/models/workflow.go | 439 ++++++++++++++++++ .../internal/repository/models/workflow_v3.go | 87 ++++ .../internal/repository/mongodb/build.go | 280 +++++++++++ .../internal/repository/mongodb/testing.go | 179 +++++++ .../aslan/core/build/service/build.go | 2 - .../core/common/repository/models/build.go | 37 +- .../common/repository/models/k8s_cluster.go | 2 + .../common/repository/models/task/build.go | 17 +- .../common/repository/models/task/testing.go | 7 + .../core/common/repository/models/testing.go | 22 +- .../common/repository/mongodb/k8s_cluster.go | 1 + .../core/common/repository/mongodb/s3.go | 46 ++ .../aslan/core/multicluster/handler/cache.go | 37 ++ .../core/multicluster/handler/clusters.go | 3 + .../aslan/core/multicluster/handler/router.go | 3 + .../aslan/core/multicluster/service/cache.go | 111 +++++ .../core/multicluster/service/clusters.go | 89 ++++ .../core/multicluster/service/constants.go | 57 +++ pkg/microservice/aslan/core/service.go | 4 + .../service/workflow/pipeline_task.go | 24 +- .../service/workflow/workflow_task.go | 10 + .../workflow/testing/handler/test_task.go | 1 - .../workflow/testing/service/test_task.go | 1 - .../core/service/archive/file_archive.go | 1 - .../reaper/core/service/meta/types.go | 9 +- .../reaper/core/service/reaper/git.go | 9 +- .../reaper/core/service/reaper/reaper.go | 85 +--- .../reaper/core/service/reaper/script.go | 1 - .../service/taskcontroller/task_handler.go | 4 - .../core/service/taskplugin/build.go | 27 +- .../warpdrive/core/service/taskplugin/job.go | 35 +- .../core/service/taskplugin/testing.go | 27 +- .../warpdrive/core/service/types/reaper.go | 8 + .../core/service/types/task/build.go | 19 +- .../core/service/types/task/testing.go | 7 + .../core/service/types/task/warpdrive.go | 10 + pkg/shared/handler/base.go | 1 + pkg/types/cache.go | 55 +++ 41 files changed, 2035 insertions(+), 142 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/1100.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/build.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/testing.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/workflow.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/build.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go create mode 100644 pkg/microservice/aslan/core/multicluster/handler/cache.go create mode 100644 pkg/microservice/aslan/core/multicluster/service/cache.go create mode 100644 pkg/microservice/aslan/core/multicluster/service/constants.go create mode 100644 pkg/types/cache.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go new file mode 100644 index 00000000..a64e441c --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -0,0 +1,160 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrate + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" +) + +func init() { + upgradepath.RegisterHandler("1.9.0", "1.10.0", V190ToV1100) + upgradepath.RegisterHandler("1.10.0", "1.9.0", V1100ToV190) +} + +// V190ToV1100 migrates data `caches` and `pre_build.clean_workspace` fields in `zadig.module_build` and `zadig.module_testing` +// to new fields `cache_enable`, `cache_dir_type` and `cache_user_dir`. +func V190ToV1100() error { + log.Info("Migrate data from 1.9.0 to 1.10.0.") + + log.Info("Migrate data in `zadig.module_build`.") + if err := migrateModuleBuild(); err != nil { + return fmt.Errorf("failed to migrate data in `zadig.module_build`: %s", err) + } + + log.Info("Migrate data in `zadig.module_testing`.") + if err := migrateModuleTesting(); err != nil { + return fmt.Errorf("failed to migrate data in `zadig.module_testing`: %s", err) + } + + return nil +} + +// Since the old data has not been changed, no changes are required. +func V1100ToV190() error { + log.Info("Rollback data from 1.10.0 to 1.9.0") + return nil +} + +func migrateModuleBuild() error { + buildCol := internalmongodb.NewBuildColl() + + builds, err := buildCol.List(&internalmongodb.BuildListOption{}) + if err != nil { + return fmt.Errorf("failed to list all data in `zadig.module_build`: %s", err) + } + + var ms []mongo.WriteModel + for _, build := range builds { + if err := migrateOneBuild(build); err != nil { + log.Errorf("Failed to migrate data: %v. Err: %s", build, err) + continue + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", build.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"cache_enable", build.CacheEnable}, + {"cache_dir_type", build.CacheDirType}, + {"cache_user_dir", build.CacheUserDir}}}, + }), + ) + } + + _, err = buildCol.BulkWrite(context.TODO(), ms) + + return err +} + +func migrateModuleTesting() error { + testingCol := internalmongodb.NewTestingColl() + + testings, err := testingCol.List(&internalmongodb.ListTestOption{}) + if err != nil { + return fmt.Errorf("failed to list all data in `zadig.module_testing`: %s", err) + } + + var ms []mongo.WriteModel + for _, testing := range testings { + if err := migrateOneTesting(testing); err != nil { + log.Errorf("Failed to migrate data: %v. Err: %s", testing, err) + continue + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", testing.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"cache_enable", testing.CacheEnable}, + {"cache_dir_type", testing.CacheDirType}, + {"cache_user_dir", testing.CacheUserDir}}}, + }), + ) + } + + _, err = testingCol.BulkWrite(context.TODO(), ms) + + return err +} + +func migrateOneBuild(build *internalmodels.Build) error { + if build.PreBuild.CleanWorkspace { + return nil + } + + build.CacheEnable = true + + if len(build.Caches) == 0 { + build.CacheDirType = types.WorkspaceCacheDir + return nil + } + + build.CacheDirType = types.UserDefinedCacheDir + build.CacheUserDir = build.Caches[0] + + return nil +} + +func migrateOneTesting(testing *internalmodels.Testing) error { + if testing.PreTest.CleanWorkspace { + return nil + } + + testing.CacheEnable = true + + if len(testing.Caches) == 0 { + testing.CacheDirType = types.WorkspaceCacheDir + return nil + } + + testing.CacheDirType = types.UserDefinedCacheDir + testing.CacheUserDir = testing.Caches[0] + + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/build.go b/pkg/cli/upgradeassistant/internal/repository/models/build.go new file mode 100644 index 00000000..84916113 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/build.go @@ -0,0 +1,160 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" +) + +type Build struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + Timeout int `bson:"timeout" json:"timeout"` + Targets []*ServiceModuleTarget `bson:"targets" json:"targets"` + Description string `bson:"desc,omitempty" json:"desc"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + Repos []*types.Repository `bson:"repos" json:"repos"` + PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` + JenkinsBuild *JenkinsBuild `bson:"jenkins_build,omitempty" json:"jenkins_build,omitempty"` + Scripts string `bson:"scripts" json:"scripts"` + PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` + + // TODO: Deprecated. + Caches []string `bson:"caches" json:"caches"` + + ProductName string `bson:"product_name" json:"product_name"` + SSHs []string `bson:"sshs,omitempty" json:"sshs,omitempty"` + PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` + + // New since V1.10.0. + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` +} + +// PreBuild prepares an environment for a job +type PreBuild struct { + // TODO: Deprecated. + CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` + // ResReq defines job requested resources + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + // BuildOS defines job image OS, it supports 12.04, 14.04, 16.04 + BuildOS string `bson:"build_os" json:"build_os"` + ImageFrom string `bson:"image_from" json:"image_from"` + ImageID string `bson:"image_id" json:"image_id"` + // Installs defines apps to be installed for build + Installs []*Item `bson:"installs,omitempty" json:"installs"` + // Envs stores user defined env key val for build + Envs []*KeyVal `bson:"envs,omitempty" json:"envs"` + // EnableProxy + EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy"` + // Parameters + Parameters []*Parameter `bson:"parameters,omitempty" json:"parameters"` + // UploadPkg uploads package to s3 + UploadPkg bool `bson:"upload_pkg" json:"upload_pkg"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + + // TODO: Deprecated. + Namespace string `bson:"namespace" json:"namespace"` +} + +type BuildObj struct { + Targets []string + Description string + Repos []*types.Repository + PreBuild *PreBuild + Scripts string + MainFile string + PostBuild *PostBuild + Caches []string +} + +type PostBuild struct { + DockerBuild *DockerBuild `bson:"docker_build,omitempty" json:"docker_build"` + FileArchive *FileArchive `bson:"file_archive,omitempty" json:"file_archive,omitempty"` + Scripts string `bson:"scripts" json:"scripts"` +} + +type FileArchive struct { + FileLocation string `bson:"file_location" json:"file_location"` +} + +type DockerBuild struct { + // WorkDir docker run path + WorkDir string `bson:"work_dir" json:"work_dir"` + // DockerFile name, default is Dockerfile + DockerFile string `bson:"docker_file" json:"docker_file"` + // BuildArgs docker build args + BuildArgs string `bson:"build_args,omitempty" json:"build_args"` + // Source whether dockerfile comes from template or existing file + Source string `bson:"source" json:"source"` + // TemplateId is the id of the template dockerfile + TemplateID string `bson:"template_id" json:"template_id"` + // TemplateName is the name of the template dockerfile + TemplateName string `bson:"template_name" json:"template_name"` +} + +type JenkinsBuild struct { + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParam []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` +} + +type JenkinsBuildParam struct { + Name string `json:"name"` + Value interface{} `json:"value"` +} + +type Parameter struct { + Name string `bson:"name" json:"name"` + DefaultValue string `bson:"default_value" json:"default_value"` + ParamVal []*ParamVal `bson:"param_val" json:"param_val"` +} + +type ParamVal struct { + Target string `bson:"target" json:"target"` + Value string `bson:"value" json:"value"` +} + +type ServiceModuleTarget struct { + ProductName string `bson:"product_name" json:"product_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` +} + +type KeyVal struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + IsCredential bool `bson:"is_credential" json:"is_credential"` +} + +type Item struct { + Name string `bson:"name" json:"name"` + Version string `bson:"version" json:"version"` +} + +func (Build) TableName() string { + return "module_build" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/testing.go b/pkg/cli/upgradeassistant/internal/repository/models/testing.go new file mode 100644 index 00000000..1f48c8df --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/testing.go @@ -0,0 +1,100 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" +) + +type Testing struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + ProductName string `bson:"product_name" json:"product_name"` + Desc string `bson:"desc" json:"desc"` + Timeout int `bson:"timeout" json:"timeout"` + Team string `bson:"team" json:"team"` + Repos []*types.Repository `bson:"repos,omitempty" json:"repos"` + PreTest *PreTest `bson:"pre_test" json:"pre_test"` + Scripts string `bson:"scripts" json:"scripts"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + TestResultPath string `bson:"test_result_path" json:"test_result_path"` + TestReportPath string `bson:"test_report_path" json:"test_report_path"` + Threshold int `bson:"threshold" json:"threshold"` + TestType string `bson:"test_type" json:"test_type"` + + // TODO: Deprecated. + Caches []string `bson:"caches" json:"caches"` + + ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` + TestCaseNum int `bson:"-" json:"test_case_num,omitempty"` + ExecuteNum int `bson:"-" json:"execute_num,omitempty"` + PassRate float64 `bson:"-" json:"pass_rate,omitempty"` + AvgDuration float64 `bson:"-" json:"avg_duration,omitempty"` + Workflows []*Workflow `bson:"-" json:"workflows,omitempty"` + Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` + HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` + + // New since V1.10.0. + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` +} + +type TestingHookCtrl struct { + Enabled bool `bson:"enabled" json:"enabled"` + Items []*TestingHook `bson:"items" json:"items"` +} + +type TestingHook struct { + AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` + MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` + TestArgs *TestTaskArgs `bson:"test_args" json:"test_args"` +} + +// PreTest prepares an environment for a job +type PreTest struct { + // TODO: Deprecated. + CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` + + // BuildOS defines job image OS, it supports 12.04, 14.04, 16.04 + BuildOS string `bson:"build_os" json:"build_os"` + ImageFrom string `bson:"image_from" json:"image_from"` + ImageID string `bson:"image_id" json:"image_id"` + // ResReq defines job requested resources + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + // Installs defines apps to be installed for build + Installs []*Item `bson:"installs,omitempty" json:"installs"` + // Envs stores user defined env key val for build + Envs []*KeyVal `bson:"envs,omitempty" json:"envs"` + // EnableProxy + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + + // TODO: Deprecated. + Namespace string `bson:"namespace" json:"namespace"` +} + +func (Testing) TableName() string { + return "module_testing" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/workflow.go b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go new file mode 100644 index 00000000..70e743a2 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go @@ -0,0 +1,439 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "fmt" + + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" +) + +type Workflow struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Enabled bool `bson:"enabled" json:"enabled"` + ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + EnvName string `bson:"env_name" json:"env_name,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + UpdateBy string `bson:"update_by" json:"update_by,omitempty"` + CreateBy string `bson:"create_by" json:"create_by,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + CreateTime int64 `bson:"create_time" json:"create_time"` + Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` + ScheduleEnabled bool `bson:"schedule_enabled" json:"schedule_enabled"` + Slack *Slack `bson:"slack,omitempty" json:"slack,omitempty"` + BuildStage *BuildStage `bson:"build_stage" json:"build_stage"` + ArtifactStage *ArtifactStage `bson:"artifact_stage,omitempty" json:"artifact_stage,omitempty"` + TestStage *TestStage `bson:"test_stage" json:"test_stage"` + SecurityStage *SecurityStage `bson:"security_stage" json:"security_stage"` + DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + + // ResetImage indicate whether reset image to original version after completion + ResetImage bool `bson:"reset_image" json:"reset_image"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy,omitempty" json:"reset_image_policy,omitempty"` + IsParallel bool `json:"is_parallel" bson:"is_parallel"` +} + +type WorkflowHookCtrl struct { + Enabled bool `bson:"enabled" json:"enabled"` + Items []*WorkflowHook `bson:"items" json:"items"` +} + +type WorkflowHook struct { + AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` + CheckPatchSetChange bool `bson:"check_patch_set_change" json:"check_patch_set_change"` + MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` + IsYaml bool `bson:"is_yaml,omitempty" json:"is_yaml,omitempty"` + YamlPath string `bson:"yaml_path,omitempty" json:"yaml_path,omitempty"` +} + +type MainHookRepo struct { + Name string `bson:"name,omitempty" json:"name,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` + Branch string `bson:"branch" json:"branch"` + Tag string `bson:"tag" json:"tag"` + Committer string `bson:"committer" json:"committer"` + MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` + CodehostID int `bson:"codehost_id" json:"codehost_id"` + Events []config.HookEventType `bson:"events" json:"events"` + Label string `bson:"label" json:"label"` + Revision string `bson:"revision" json:"revision"` + IsRegular bool `bson:"is_regular" json:"is_regular"` +} + +func (m MainHookRepo) GetLabelValue() string { + if m.Label == "" { + return "Code-Review" + } + + return m.Label +} + +type ScheduleCtrl struct { + Enabled bool `bson:"enabled" json:"enabled"` + Items []*Schedule `bson:"items" json:"items"` +} + +type Schedule struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Number uint64 `bson:"number" json:"number"` + Frequency string `bson:"frequency" json:"frequency"` + Time string `bson:"time" json:"time"` + MaxFailures int `bson:"max_failures,omitempty" json:"max_failures,omitempty"` + TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` + WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args,omitempty" json:"workflow_args,omitempty"` + TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` + Type config.ScheduleType `bson:"type" json:"type"` + Cron string `bson:"cron" json:"cron"` + IsModified bool `bson:"-" json:"-"` + Enabled bool `bson:"enabled" json:"enabled"` +} + +type TaskArgs struct { + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Builds []*types.Repository `bson:"builds" json:"builds"` + BuildArgs []*KeyVal `bson:"build_args" json:"build_args"` + Deploy DeployArgs `bson:"deploy" json:"deploy"` + Test TestArgs `bson:"test" json:"test,omitempty"` + HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + ReqID string `bson:"req_id" json:"req_id"` + IsQiNiu bool `bson:"is_qiniu" json:"is_qiniu"` + NotificationID string `bson:"notification_id" json:"notification_id"` + CodeHostID int `bson:"codehost_id" json:"codehost_id"` +} + +type CallbackArgs struct { + CallbackUrl string `bson:"callback_url" json:"callback_url"` // url-encoded full path + CallbackVars map[string]interface{} `bson:"callback_vars" json:"callback_vars"` // custom defied vars, will be set to body of callback request +} + +type WorkflowTaskArgs struct { + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + Namespace string `bson:"namespace" json:"namespace"` + BaseNamespace string `bson:"base_namespace,omitempty" json:"base_namespace,omitempty"` + EnvRecyclePolicy string `bson:"env_recycle_policy,omitempty" json:"env_recycle_policy,omitempty"` + EnvUpdatePolicy string `bson:"env_update_policy,omitempty" json:"env_update_policy,omitempty"` + Target []*TargetArgs `bson:"targets" json:"targets"` + Artifact []*ArtifactArgs `bson:"artifact_args" json:"artifact_args"` + Tests []*TestArgs `bson:"tests" json:"tests"` + VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` + ReqID string `bson:"req_id" json:"req_id"` + RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` + StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` + DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` + WorkflowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` + // Ignore docker build cache + IgnoreCache bool `json:"ignore_cache" bson:"ignore_cache"` + // Ignore workspace cache and reset volume + ResetCache bool `json:"reset_cache" bson:"reset_cache"` + + // NotificationID is the id of scmnotify.Notification + NotificationID string `bson:"notification_id" json:"notification_id"` + + MergeRequestID string `bson:"merge_request_id" json:"merge_request_id"` + CommitID string `bson:"commit_id" json:"commit_id"` + Source string `bson:"source" json:"source"` + CodehostID int `bson:"codehost_id" json:"codehost_id"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` + Committer string `bson:"committer,omitempty" json:"committer,omitempty"` + //github check run + HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` + RequestMode string `json:"request_mode,omitempty"` + IsParallel bool `json:"is_parallel" bson:"is_parallel"` + EnvName string `json:"env_name" bson:"-"` + + Callback *CallbackArgs `bson:"callback" json:"callback"` + ReleaseImages []*ReleaseImage `bson:"release_images,omitempty" json:"release_images,omitempty"` +} + +type ReleaseImage struct { + Image string `bson:"image" json:"image"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` +} + +type TestTaskArgs struct { + ProductName string `bson:"product_name" json:"product_name"` + TestName string `bson:"test_name" json:"test_name"` + TestTaskCreator string `bson:"test_task_creator" json:"test_task_creator"` + NotificationID string `bson:"notification_id" json:"notification_id"` + ReqID string `bson:"req_id" json:"req_id"` + MergeRequestID string `bson:"merge_request_id" json:"merge_request_id"` + CommitID string `bson:"commit_id" json:"commit_id"` + Source string `bson:"source" json:"source"` + CodehostID int `bson:"codehost_id" json:"codehost_id"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` +} + +type Slack struct { + Channel string `bson:"channel" json:"channel"` + Enabled bool `bson:"enabled" json:"enabled"` + Notifiers []string `bson:"notifiers" json:"notifiers"` + NotifyType config.SlackNotifyType `bson:"notify_type" json:"notify_type"` +} + +type BuildStage struct { + Enabled bool `bson:"enabled" json:"enabled"` + Modules []*BuildModule `bson:"modules" json:"modules"` +} + +type BuildModule struct { + Target *ServiceModuleTarget `bson:"target" json:"target"` + HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` + BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` +} + +type ArtifactStage struct { + Enabled bool `bson:"enabled" json:"enabled"` + Modules []*ArtifactModule `bson:"modules" json:"modules"` +} + +type ArtifactModule struct { + HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` + Target *ServiceModuleTarget `bson:"target" json:"target"` +} + +type TestStage struct { + Enabled bool `bson:"enabled" json:"enabled"` + TestNames []string `bson:"test_names,omitempty" json:"test_names,omitempty"` + Tests []*TestExecArgs `bson:"tests,omitempty" json:"tests,omitempty"` +} + +type TestExecArgs struct { + Name string `bson:"test_name" json:"test_name"` + Envs []*KeyVal `bson:"envs" json:"envs"` +} + +type SecurityStage struct { + Enabled bool `bson:"enabled" json:"enabled"` +} + +type DistributeStage struct { + Enabled bool `bson:"enabled" json:"enabled"` + S3StorageID string `bson:"s3_storage_id" json:"s3_storage_id"` + ImageRepo string `bson:"image_repo" json:"image_repo"` + JumpBoxHost string `bson:"jump_box_host" json:"jump_box_host"` + Distributes []*ProductDistribute `bson:"distributes" json:"distributes"` + + // repos to release images + Releases []RepoImage `bson:"releases" json:"releases"` +} + +type RepoImage struct { + RepoID string `json:"repo_id" bson:"repo_id"` + Name string `json:"name" bson:"name" yaml:"name"` + Username string `json:"-" yaml:"username"` + Password string `json:"-" yaml:"password"` + Host string `json:"host" yaml:"host"` + Namespace string `json:"namespace" yaml:"namespace"` +} + +type ProductDistribute struct { + Target *ServiceModuleTarget `bson:"target" json:"target"` + ImageDistribute bool `bson:"image_distribute" json:"image_distribute"` + JumpBoxDistribute bool `bson:"jump_box_distribute" json:"jump_box_distribute"` + QstackDistribute bool `bson:"qstack_distribute" json:"qstack_distribute"` +} + +type NotifyCtl struct { + Enabled bool `bson:"enabled" json:"enabled"` + WebHookType string `bson:"webhook_type" json:"webhook_type"` + WeChatWebHook string `bson:"weChat_webHook,omitempty" json:"weChat_webHook,omitempty"` + DingDingWebHook string `bson:"dingding_webhook,omitempty" json:"dingding_webhook,omitempty"` + FeiShuWebHook string `bson:"feishu_webhook,omitempty" json:"feishu_webhook,omitempty"` + AtMobiles []string `bson:"at_mobiles,omitempty" json:"at_mobiles,omitempty"` + IsAtAll bool `bson:"is_at_all,omitempty" json:"is_at_all,omitempty"` + NotifyTypes []string `bson:"notify_type" json:"notify_type"` +} + +type TaskInfo struct { + TaskID int64 `bson:"task_id" json:"task_id"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Status config.Status `bson:"status" json:"status"` +} + +type DeployArgs struct { + Namespace string `json:"namespace"` + Tag string `json:"suffix"` + // Format: xxx.com/{namespace}/{service name}:{timestamp}-{suffix}} + // timestamp format: 20060102150405 + Image string `json:"image"` + // Format: {service name}-{timestamp}-{suffix}}.tar.gz + // timestamp format: 20060102150405 + PackageFile string `json:"package_file"` +} + +type TestArgs struct { + Namespace string `bson:"namespace" json:"namespace"` + TestModuleName string `bson:"test_module_name" json:"test_module_name"` + Envs []*KeyVal `bson:"envs" json:"envs"` + Builds []*types.Repository `bson:"builds" json:"builds"` +} + +type HookPayload struct { + Owner string `bson:"owner" json:"owner,omitempty"` + Repo string `bson:"repo" json:"repo,omitempty"` + Branch string `bson:"branch" json:"branch,omitempty"` + Ref string `bson:"ref" json:"ref,omitempty"` + IsPr bool `bson:"is_pr" json:"is_pr,omitempty"` + CheckRunID int64 `bson:"check_run_id" json:"check_run_id,omitempty"` + DeliveryID string `bson:"delivery_id" json:"delivery_id,omitempty"` +} + +type TargetArgs struct { + Name string `bson:"name" json:"name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Build *BuildArgs `bson:"build" json:"build"` + Deploy []DeployEnv `bson:"deloy" json:"deploy"` + Image string `bson:"image" json:"image"` + BinFile string `bson:"bin_file" json:"bin_file"` + Envs []*KeyVal `bson:"envs" json:"envs"` + HasBuild bool `bson:"has_build" json:"has_build"` + JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` +} + +type JenkinsBuildArgs struct { + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParams []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` +} + +type BuildArgs struct { + Repos []*types.Repository `bson:"repos" json:"repos"` +} + +type DeployEnv struct { + Env string `json:"env"` + Type string `json:"type"` + ProductName string `json:"product_name,omitempty"` +} + +type ArtifactArgs struct { + Name string `bson:"name" json:"name"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` + TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` + FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` + URL string `bson:"url,omitempty" json:"url,omitempty"` +} + +type VersionArgs struct { + Enabled bool `bson:"enabled" json:"enabled"` + Version string `bson:"version" json:"version"` + Desc string `bson:"desc" json:"desc"` + Labels []string `bson:"labels" json:"labels"` +} + +type BuildModuleArgs struct { + BuildName string + Target string + ServiceName string + ProductName string + Variables []*KeyVal + Env *Product + WorkflowName string + TaskID int64 + FileName string + URL string + TaskType string +} + +func (Workflow) TableName() string { + return "workflow" +} + +func (d *DistributeStage) IsDistributeS3Enabled() bool { + if d != nil && d.Enabled { + for _, d := range d.Distributes { + if d.QstackDistribute { + return true + } + } + } + + return false +} + +//Validate validate schedule setting +func (schedule *Schedule) Validate() error { + switch schedule.Type { + case config.TimingSchedule: + if schedule.Number <= 0 { + schedule.Number = 1 + } + if err := isValidJobTime(schedule.Time); err != nil { + return err + } + if schedule.Frequency != setting.FrequencyDay && + schedule.Frequency != setting.FrequencyMondy && + schedule.Frequency != setting.FrequencyTuesday && + schedule.Frequency != setting.FrequencyWednesday && + schedule.Frequency != setting.FrequencyThursday && + schedule.Frequency != setting.FrequencyFriday && + schedule.Frequency != setting.FrequencySaturday && + schedule.Frequency != setting.FrequencySunday { + return fmt.Errorf("%s 定时任务频率错误", e.InvalidFormatErrMsg) + } + return nil + + case config.GapSchedule: + if schedule.Frequency != setting.FrequencyHours && + schedule.Frequency != setting.FrequencyMinutes { + return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) + } + if schedule.Number <= 0 { + return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0", e.InvalidFormatErrMsg) + } + return nil + + default: + return fmt.Errorf("%s 间隔任务模式未设置", e.InvalidFormatErrMsg) + } +} + +func isValidJobTime(t string) error { + + hour := int((t[0]-'0')*10 + (t[1] - '0')) + min := int((t[3]-'0')*10 + (t[4] - '0')) + + if hour < 0 || hour > 23 || min < 0 || min > 59 { + return fmt.Errorf("%s 时间范围: 小时[0-24] 分钟[0-59]", e.InvalidFormatErrMsg) + } + + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go b/pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go new file mode 100644 index 00000000..d9a528ce --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go @@ -0,0 +1,87 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "github.com/koderover/zadig/pkg/types" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type WorkflowV3 struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + ProjectName string `bson:"project_name" json:"project_name"` + Description string `bson:"description" json:"description"` + Parameters []*ParameterSetting `bson:"parameters" json:"parameters"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + CreatedBy string `bson:"created_by" json:"created_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdatedBy string `bson:"updated_by" json:"updated_by"` + UpdateTime int64 `bson:"update_time" json:"update_time"` +} + +type ParameterSettingType string + +const ( + StringType ParameterSettingType = "string" + ChoiceType ParameterSettingType = "choice" + ExternalType ParameterSettingType = "external" +) + +type ParameterSetting struct { + // External type parameter will NOT use this key. + Key string `bson:"key" json:"key"` + Type ParameterSettingType `bson:"type" json:"type"` + //DefaultValue + DefaultValue string `bson:"default_value" json:"default_value"` + // choiceOption Are all options enumerated + ChoiceOption []string `bson:"choice_option" json:"choice_option"` + // ExternalSetting It is the configuration of the external system to obtain the variable + ExternalSetting *ExternalSetting `bson:"external_setting" json:"external_setting"` +} + +type ExternalSetting struct { + SystemID string `bson:"system_id" json:"system_id"` + Endpoint string `bson:"endpoint" json:"endpoint"` + Method string `bson:"method" json:"method"` + Headers []*KV `bson:"headers" json:"headers"` + Body string `bson:"body" json:"body"` + Params []*ExternalParamMapping `bson:"params" json:"params"` +} + +type KV struct { + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` +} + +type ExternalParamMapping struct { + ParamKey string `bson:"param_key" json:"param_key"` + ResponseKey string `bson:"response_key" json:"response_key"` + Display bool `bson:"display" json:"display"` +} + +type WorkflowV3Args struct { + ID string `bson:"id" json:"id"` + ProjectName string `bson:"project_name" json:"project_name"` + Name string `bson:"name" json:"name"` + Builds []*types.Repository `bson:"builds" json:"builds"` + BuildArgs []*KeyVal `bson:"build_args" json:"build_args"` +} + +func (WorkflowV3) TableName() string { + return "workflow_v3" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go new file mode 100644 index 00000000..0a8ae6f2 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go @@ -0,0 +1,280 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type BuildListOption struct { + Name string + Targets []string + ProductName string + ServiceName string + IsSort bool + BuildOS string + BasicImageID string + PrivateKeyID string +} + +type BuildFindOption struct { + Name string + Targets []string + ServiceName string + ProductName string +} + +type BuildColl struct { + *mongo.Collection + + coll string +} + +func NewBuildColl() *BuildColl { + name := models.Build{}.TableName() + return &BuildColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *BuildColl) GetCollectionName() string { + return c.coll +} + +func (c *BuildColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "revision", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *BuildColl) Find(opt *BuildFindOption) (*models.Build, error) { + if opt == nil { + return nil, errors.New("nil FindOption") + } + + query := bson.M{} + + if len(opt.Name) != 0 { + query["name"] = opt.Name + } + + if len(opt.Targets) > 0 { + query["targets.service_module"] = bson.M{"$in": opt.Targets} + } + + if len(opt.ServiceName) > 0 { + query["targets.service_name"] = opt.ServiceName + } + + if len(opt.ProductName) != 0 { + query["product_name"] = opt.ProductName + } + + resp := new(models.Build) + err := c.Collection.FindOne(context.TODO(), query).Decode(resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *BuildColl) List(opt *BuildListOption) ([]*models.Build, error) { + if opt == nil { + return nil, errors.New("nil ListOption") + } + + query := bson.M{} + if len(strings.TrimSpace(opt.Name)) != 0 { + query["name"] = opt.Name + } + if len(opt.Targets) > 0 { + query["targets.service_module"] = bson.M{"$in": opt.Targets} + } + + if len(opt.ServiceName) > 0 { + query["targets.service_name"] = opt.ServiceName + } + + if len(opt.ProductName) != 0 { + query["product_name"] = opt.ProductName + } + if len(opt.BuildOS) != 0 { + query["pre_build.build_os"] = opt.BuildOS + } + if len(opt.BasicImageID) != 0 { + query["pre_build.image_id"] = opt.BasicImageID + } + if len(opt.PrivateKeyID) != 0 { + query["ssh.id"] = opt.PrivateKeyID + } + + var resp []*models.Build + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"update_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *BuildColl) Delete(name, productName string) error { + query := bson.M{} + if len(name) != 0 { + query["name"] = name + } + if len(productName) != 0 { + query["product_name"] = productName + } + + if len(query) == 0 { + return nil + } + + _, err := c.DeleteMany(context.TODO(), query) + return err +} + +func (c *BuildColl) Create(build *models.Build) error { + if build == nil { + return errors.New("nil Module args") + } + + build.Name = strings.TrimSpace(build.Name) + build.UpdateTime = time.Now().Unix() + + //double check + buildModel, err := c.Find(&BuildFindOption{Name: build.Name}) + if err == nil { + return fmt.Errorf("%s%s", buildModel.ProductName, "项目中有相同的构建名称存在,请检查!") + } + + _, err = c.Collection.InsertOne(context.TODO(), build) + + return err +} + +func (c *BuildColl) Update(build *models.Build) error { + if build == nil { + return errors.New("nil Module args") + } + + query := bson.M{"name": build.Name} + if build.ProductName != "" { + query["product_name"] = build.ProductName + } + + updateBuild := bson.M{"$set": build} + + _, err := c.Collection.UpdateOne(context.TODO(), query, updateBuild) + return err +} + +func (c *BuildColl) UpdateTargets(name, productName string, targets []*models.ServiceModuleTarget) error { + query := bson.M{"name": name} + if productName != "" { + query["product_name"] = productName + } + + change := bson.M{"$set": bson.M{ + "targets": targets, + }} + _, err := c.Collection.UpdateMany(context.TODO(), query, change) + return err +} + +func (c *BuildColl) UpdateBuildParam(name, productName string, params []*models.Parameter) error { + query := bson.M{"name": name} + if productName != "" { + query["product_name"] = productName + } + + change := bson.M{"$set": bson.M{ + "pre_build.parameters": params, + }} + _, err := c.UpdateMany(context.TODO(), query, change) + return err +} + +// DistinctTargets finds modules distinct service templates +func (c *BuildColl) DistinctTargets(excludeModule []string, productName string) (map[string]bool, error) { + query := bson.M{} + + if len(excludeModule) != 0 { + query["name"] = bson.M{"$nin": excludeModule} + } + if len(productName) != 0 { + query["product_name"] = productName + } + + serviceModuleTargets, err := c.Distinct(context.TODO(), "targets", query) + if err != nil { + return nil, err + } + + resp := make(map[string]bool) + for _, serviceModuleTarget := range serviceModuleTargets { + if moduleTarget, ok := serviceModuleTarget.(models.ServiceModuleTarget); ok { + target := fmt.Sprintf("%s-%s-%s", moduleTarget.ProductName, moduleTarget.ServiceName, moduleTarget.ServiceModule) + resp[target] = true + } + } + + return resp, err +} + +func (c *BuildColl) GetDockerfileTemplateReference(templateID string) ([]*models.Build, error) { + ret := make([]*models.Build, 0) + query := bson.M{"post_build.docker_build.template_id": templateID} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &ret) + if err != nil { + return nil, err + } + return ret, nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go new file mode 100644 index 00000000..c427fc3e --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go @@ -0,0 +1,179 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ListTestOption struct { + ProductName string + TestType string + IsSort bool + BuildOS string + BasicImageID string +} + +type TestingColl struct { + *mongo.Collection + + coll string +} + +func NewTestingColl() *TestingColl { + name := models.Testing{}.TableName() + return &TestingColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *TestingColl) GetCollectionName() string { + return c.coll +} + +func (c *TestingColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"name": 1}, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *TestingColl) List(opt *ListTestOption) ([]*models.Testing, error) { + query := bson.M{} + + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.TestType) > 0 { + query["test_type"] = opt.TestType + } + if len(opt.BuildOS) > 0 { + query["pre_test.build_os"] = opt.BuildOS + } + if len(opt.BasicImageID) != 0 { + query["pre_test.image_id"] = opt.BasicImageID + } + + var resp []*models.Testing + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"update_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *TestingColl) Find(name, productName string) (*models.Testing, error) { + query := bson.M{} + + query["name"] = name + + if productName != "" { + query["product_name"] = productName + } + + resp := new(models.Testing) + + err := c.FindOne(context.TODO(), query).Decode(resp) + return resp, err +} + +func (c *TestingColl) Delete(name, productName string) error { + query := bson.M{} + if name != "" { + query["name"] = name + } + if productName != "" { + query["product_name"] = productName + } + if len(query) == 0 { + return nil + } + _, err := c.DeleteMany(context.TODO(), query) + return err +} + +func (c *TestingColl) ListWithScheduleEnabled() ([]*models.Testing, error) { + resp := make([]*models.Testing, 0) + query := bson.M{"schedule_enabled": true} + + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *TestingColl) Create(testing *models.Testing) error { + if testing == nil { + return errors.New("nil testing args") + } + + testing.Name = strings.TrimSpace(testing.Name) + testing.UpdateTime = time.Now().Unix() + + test, err := c.Find(testing.Name, "") + if err == nil { + return fmt.Errorf("%s%s", test.ProductName, "项目中有相同的测试名称存在,请检查!") + } + + _, err = c.InsertOne(context.TODO(), testing) + return err +} + +func (c *TestingColl) Update(testing *models.Testing) error { + if testing == nil { + return errors.New("nil testing args") + } + + query := bson.M{"name": testing.Name} + + change := bson.M{"$set": testing} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 848247e0..d74ac8d5 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -109,7 +109,6 @@ func ListBuild(name, targets, productName string, log *zap.SugaredLogger) ([]*Bu } func CreateBuild(username string, build *commonmodels.Build, log *zap.SugaredLogger) error { - if len(build.Name) == 0 { return e.ErrCreateBuildModule.AddDesc("empty name") } @@ -154,7 +153,6 @@ func UpdateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog } func DeleteBuild(name, productName string, log *zap.SugaredLogger) error { - if len(name) == 0 { return e.ErrDeleteBuildModule.AddDesc("empty name") } diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index c9d405e5..0456f17a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -32,23 +32,32 @@ type Build struct { // 在任一编译配置模板中只能出现一次 // 对于k8s部署是传入容器名称 // 对于物理机部署是服务名称 - Targets []*ServiceModuleTarget `bson:"targets" json:"targets"` - Description string `bson:"desc,omitempty" json:"desc"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - Repos []*types.Repository `bson:"repos" json:"repos"` - PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` - JenkinsBuild *JenkinsBuild `bson:"jenkins_build,omitempty" json:"jenkins_build,omitempty"` - Scripts string `bson:"scripts" json:"scripts"` - PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` - Caches []string `bson:"caches" json:"caches"` - ProductName string `bson:"product_name" json:"product_name"` - SSHs []string `bson:"sshs,omitempty" json:"sshs,omitempty"` - PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` + Targets []*ServiceModuleTarget `bson:"targets" json:"targets"` + Description string `bson:"desc,omitempty" json:"desc"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + Repos []*types.Repository `bson:"repos" json:"repos"` + PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` + JenkinsBuild *JenkinsBuild `bson:"jenkins_build,omitempty" json:"jenkins_build,omitempty"` + Scripts string `bson:"scripts" json:"scripts"` + PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` + + // TODO: Deprecated. + Caches []string `bson:"caches" json:"caches"` + + ProductName string `bson:"product_name" json:"product_name"` + SSHs []string `bson:"sshs,omitempty" json:"sshs,omitempty"` + PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` + + // New since V1.10.0. + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } // PreBuild prepares an environment for a job type PreBuild struct { + // TODO: Deprecated. CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` // ResReq defines job requested resources ResReq setting.Request `bson:"res_req" json:"res_req"` @@ -68,6 +77,8 @@ type PreBuild struct { // UploadPkg uploads package to s3 UploadPkg bool `bson:"upload_pkg" json:"upload_pkg"` ClusterID string `bson:"cluster_id" json:"cluster_id"` + + // TODO: Deprecated. Namespace string `bson:"namespace" json:"namespace"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 337bd1b9..21043be5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -21,6 +21,7 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type K8SCluster struct { @@ -41,6 +42,7 @@ type K8SCluster struct { Token string `json:"token" bson:"-"` Provider int8 `json:"provider" bson:"provider"` Local bool `json:"local" bson:"local"` + Cache types.Cache `json:"cache" bson:"cache"` } type K8SClusterResp struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 0455c06e..d5353efc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -61,6 +61,12 @@ type Build struct { EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` + + // New since V1.10.0. + Cache types.Cache `bson:"cache" json:"cache"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } type ArtifactInfo struct { @@ -123,9 +129,11 @@ type DockerBuildStatus struct { } type JobCtx struct { - EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` - Proxy *models.Proxy `bson:"proxy" json:"proxy"` - CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + Proxy *models.Proxy `bson:"proxy" json:"proxy"` + + // TODO: Deprecated. + CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` // BuildJobCtx Builds []*types.Repository `bson:"builds" json:"builds"` @@ -147,8 +155,7 @@ type JobCtx struct { DockerBuildCtx *DockerBuildCtx `bson:"docker_build_ctx,omitempty" json:"docker_build_ctx,omitempty"` FileArchiveCtx *FileArchiveCtx `bson:"file_archive_ctx,omitempty" json:"file_archive_ctx,omitempty"` // TestType - TestType string `bson:"test_type" json:"test_type"` - // Caches + TestType string `bson:"test_type" json:"test_type"` Caches []string `bson:"caches" json:"caches"` ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/testing.go b/pkg/microservice/aslan/core/common/repository/models/task/testing.go index 0105494f..cca8438a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/testing.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type Testing struct { @@ -49,6 +50,12 @@ type Testing struct { Registries []*models.RegistryNamespace `bson:"-" json:"registries"` ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` Namespace string `bson:"namespace" json:"namespace"` + + // New since V1.10.0. + Cache types.Cache `bson:"cache" json:"cache"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } func (t *Testing) ToSubTask() (map[string]interface{}, error) { diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index ff0b9649..243b6f35 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -38,10 +38,13 @@ type Testing struct { // Junit 测试报告 TestResultPath string `bson:"test_result_path" json:"test_result_path"` // html 测试报告 - TestReportPath string `bson:"test_report_path" json:"test_report_path"` - Threshold int `bson:"threshold" json:"threshold"` - TestType string `bson:"test_type" json:"test_type"` - Caches []string `bson:"caches" json:"caches"` + TestReportPath string `bson:"test_report_path" json:"test_report_path"` + Threshold int `bson:"threshold" json:"threshold"` + TestType string `bson:"test_type" json:"test_type"` + + // TODO: Deprecated. + Caches []string `bson:"caches" json:"caches"` + ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` TestCaseNum int `bson:"-" json:"test_case_num,omitempty"` ExecuteNum int `bson:"-" json:"execute_num,omitempty"` @@ -52,6 +55,11 @@ type Testing struct { HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` + + // New since V1.10.0. + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } type TestingHookCtrl struct { @@ -67,7 +75,9 @@ type TestingHook struct { // PreTest prepares an environment for a job type PreTest struct { + // TODO: Deprecated. CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` + // BuildOS defines job image OS, it supports 12.04, 14.04, 16.04 BuildOS string `bson:"build_os" json:"build_os"` ImageFrom string `bson:"image_from" json:"image_from"` @@ -82,7 +92,9 @@ type PreTest struct { // EnableProxy EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` ClusterID string `bson:"cluster_id" json:"cluster_id"` - Namespace string `bson:"namespace" json:"namespace"` + + // TODO: Deprecated. + Namespace string `bson:"namespace" json:"namespace"` } func (Testing) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 926eb912..c1670260 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -210,6 +210,7 @@ func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster, id stri "namespace": cluster.Namespace, "production": cluster.Production, "advanced_config": cluster.AdvancedConfig, + "cache": cluster.Cache, }}, ) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/s3.go b/pkg/microservice/aslan/core/common/repository/mongodb/s3.go index a79e666c..7b5f448a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/s3.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/s3.go @@ -18,6 +18,8 @@ package mongodb import ( "context" + "fmt" + "strings" "time" "go.mongodb.org/mongo-driver/bson" @@ -27,6 +29,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/tool/crypto" + "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -201,3 +204,46 @@ func (c *S3StorageColl) FindAll() ([]*models.S3Storage, error) { return storages, nil } + +func (c *S3StorageColl) InitData() error { + minioEndpoint := config.S3StorageEndpoint() + endpointInfo := strings.Split(minioEndpoint, ":") + if len(endpointInfo) != 2 { + return fmt.Errorf("invalid endpoint of minio: %s", minioEndpoint) + } + minioEndpoint = fmt.Sprintf("%s.%s.svc.cluster.local:%s", endpointInfo[0], config.Namespace(), endpointInfo[1]) + + // Check whether minio has been integrated. + err := c.FindOne(context.TODO(), bson.M{"endpoint": minioEndpoint}).Err() + if err == nil { + log.Infof("Has found %s.", minioEndpoint) + return nil + } + if err != mongo.ErrNoDocuments { + return fmt.Errorf("failed to operate on mongodb: %s", err) + } + + // Check whether there's default S3 system. + var setDefault bool + err = c.FindOne(context.TODO(), bson.M{"is_default": true}).Err() + if err != nil { + if err != mongo.ErrNoDocuments { + return fmt.Errorf("failed to operate on mongodb: %s", err) + } + + setDefault = true + } + + minioStorage := models.S3Storage{ + Ak: config.S3StorageAK(), + Sk: config.S3StorageSK(), + Endpoint: minioEndpoint, + Bucket: config.S3StorageBucket(), + IsDefault: setDefault, + Insecure: true, + Provider: 0, + } + log.Infof("Begin to integrate minio storage: %s", minioEndpoint) + + return c.Create(&minioStorage) +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/cache.go b/pkg/microservice/aslan/core/multicluster/handler/cache.go new file mode 100644 index 00000000..4b9e1ba0 --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/handler/cache.go @@ -0,0 +1,37 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ListStorageClasses(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListStorageClasses(c, c.Param("id")) +} + +func ListPVCs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListPVCs(c, c.Param("id"), c.Param("namespace")) +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index 1c9f4554..1ae09c4c 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" ) func ListClusters(c *gin.Context) { @@ -75,11 +76,13 @@ func UpdateCluster(c *gin.Context) { args := new(service.K8SCluster) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) + log.Errorf("Failed to bind data: %s", err) return } if err := args.Clean(); err != nil { ctx.Err = e.ErrInvalidParam.AddErr(err) + log.Errorf("Failed to clean args: %s", err) return } diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 22438e95..4f02a1e8 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -45,4 +45,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { bundles.GET("", GetBundleResources) } + + router.GET("/:id/storageclasses", ListStorageClasses) + router.GET("/:id/:namespace/pvcs", ListPVCs) } diff --git a/pkg/microservice/aslan/core/multicluster/service/cache.go b/pkg/microservice/aslan/core/multicluster/service/cache.go new file mode 100644 index 00000000..9893e4af --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/service/cache.go @@ -0,0 +1,111 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/log" +) + +type PVC struct { + Name string `json:"name"` + StorageSizeInBytes int64 `json:"storage_size_in_bytes"` +} + +func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) { + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + var namespace string + switch clusterID { + case setting.LocalClusterID: + // For cluster-level resources, we need to explicitly configure the namespace to be empty. + namespace = "" + default: + if namespace != AttachedClusterNamespace { + return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, AttachedClusterNamespace) + } + } + + scList := &storagev1.StorageClassList{} + err = kclient.List(ctx, scList, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list storageclasses: %s", err) + } + log.Infof("StorageClass Length: %v", len(scList.Items)) + + storageclasses := make([]string, 0, len(scList.Items)) + for _, item := range scList.Items { + if !StorageProvisioner(item.Provisioner).IsNFS() { + continue + } + + storageclasses = append(storageclasses, item.Name) + } + + return storageclasses, nil +} + +func ListPVCs(ctx context.Context, clusterID, namespace string) ([]PVC, error) { + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + switch clusterID { + case setting.LocalClusterID: + // For now caller may not know exactly which namespace Zadig is deployed to, so a correction is made here. + namespace = config.Namespace() + default: + if namespace != AttachedClusterNamespace { + return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, AttachedClusterNamespace) + } + } + + pvcList := &corev1.PersistentVolumeClaimList{} + err = kclient.List(ctx, pvcList, client.InNamespace(namespace)) + if err != nil { + return nil, err + } + log.Infof("PVC Length: %d", len(pvcList.Items)) + + pvcs := make([]PVC, 0, len(pvcList.Items)) + for _, item := range pvcList.Items { + // Don't support `corev1.PersistentVolumeBlock` because the PVC would be mounted on multiple nodes. + if item.Spec.VolumeMode != nil && *item.Spec.VolumeMode == corev1.PersistentVolumeBlock { + continue + } + + pvcs = append(pvcs, PVC{ + Name: item.Name, + StorageSizeInBytes: item.Spec.Resources.Requests.Storage().Value(), + }) + } + + return pvcs, nil +} diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index b6383237..d3b6e75f 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "fmt" "net/http" "regexp" @@ -25,7 +26,11 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" + "sigs.k8s.io/controller-runtime/pkg/client" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -33,7 +38,9 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) @@ -49,6 +56,7 @@ type K8SCluster struct { CreatedBy string `json:"createdBy"` Provider int8 `json:"provider"` Local bool `json:"local"` + Cache types.Cache `json:"cache"` } type AdvancedConfig struct { @@ -117,6 +125,7 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( Provider: c.Provider, Local: c.Local, AdvancedConfig: advancedConfig, + Cache: c.Cache, }) } @@ -219,11 +228,91 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com } } } + + // If the user does not configure a cache, object storage is used by default. + if args.Cache.MediumType == "" { + args.Cache.MediumType = types.ObjectMedium + + defaultStorage, err := commonrepo.NewS3StorageColl().FindDefault() + if err != nil { + return nil, fmt.Errorf("failed to find default object storage: %s", err) + } + + args.Cache.ObjectProperties = types.ObjectProperties{ + ID: defaultStorage.ID.Hex(), + } + } + + // If the user chooses to use dynamically generated storage resources, the system automatically creates the PVC. + // TODO: If the PVC is not successfully bound to the PV, it is necessary to consider how to expose this abnormal information. + // Depends on product design. + if args.Cache.MediumType == types.NFSMedium && args.Cache.NFSProperties.ProvisionType == types.DynamicProvision { + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), id) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + var namespace string + switch id { + case setting.LocalClusterID: + namespace = config.Namespace() + default: + namespace = AttachedClusterNamespace + } + + pvcName := fmt.Sprintf("cache-%s-%d", args.Cache.NFSProperties.StorageClass, args.Cache.NFSProperties.StorageSizeInGiB) + pvc := &corev1.PersistentVolumeClaim{} + err = kclient.Get(context.TODO(), client.ObjectKey{ + Name: pvcName, + Namespace: namespace, + }, pvc) + if err == nil { + logger.Infof("PVC %s eixsts in %s", pvcName, namespace) + args.Cache.NFSProperties.PVC = pvcName + } else if !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to find PVC %s in %s: %s", pvcName, namespace, err) + } else { + filesystemVolume := corev1.PersistentVolumeFilesystem + storageQuantity, err := resource.ParseQuantity(fmt.Sprintf("%dGi", args.Cache.NFSProperties.StorageSizeInGiB)) + if err != nil { + return nil, fmt.Errorf("failed to parse storage size: %d. err: %s", args.Cache.NFSProperties.StorageSizeInGiB, err) + } + + pvc = &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvcName, + Namespace: namespace, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &args.Cache.NFSProperties.StorageClass, + AccessModes: []corev1.PersistentVolumeAccessMode{ + corev1.ReadWriteMany, + }, + VolumeMode: &filesystemVolume, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: storageQuantity, + }, + }, + }, + } + + err = kclient.Create(context.TODO(), pvc) + if err != nil { + return nil, fmt.Errorf("failed to create PVC %s in %s: %s", pvcName, namespace, err) + } + + logger.Infof("Successfully create PVC %s in %s", pvcName, namespace) + args.Cache.NFSProperties.PVC = pvcName + } + } + cluster := &commonmodels.K8SCluster{ Name: args.Name, Description: args.Description, AdvancedConfig: advancedConfig, Production: args.Production, + Cache: args.Cache, } return s.UpdateCluster(id, cluster, logger) } diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go new file mode 100644 index 00000000..4629e1c0 --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -0,0 +1,57 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import "strings" + +// AttachedClusterNamespace is the namespace Zadig uses in attached cluster. +// Note: **Restricted because of product design since v1.9.0**. +const AttachedClusterNamespace = "koderover-agent" + +// StorageProvisioner is a storage type +type StorageProvisioner string + +const ( + // AliCloudNASProvisioner is the provisioner of NFS storage in AliCloud. + AliCloudNASProvisioner StorageProvisioner = "nasplugin.csi.alibabacloud.com" + + // TencentCloudCFSProvisioner is the provisioner of NFS storage in TencentCloud. + TencentCloudCFSProvisioner StorageProvisioner = "com.tencent.cloud.csi.cfs" + + // HuaweiCloudSFSProvisioner is the provisioner of SFS storage in HuaweiCloud. + HuaweiCloudSFSProvisioner StorageProvisioner = "sfsturbo.csi.everest.io" + + // HuaweiCloudNASProvisioner is the provisioner of NAS storage in HuaweiCloud. + HuaweiCloudNASProvisioner StorageProvisioner = "nas.csi.everest.io" +) + +// Note: For the convenience of users, we filter the storage types for known cloud vendors to prevent users from mistakenly selecting storage +// types that do not support the ``ReadWriteMany`` policy. +// If the user builds their own storage, it is not checked because there is no clear information to judge. +func (s StorageProvisioner) IsNFS() bool { + if strings.Contains(string(s), "alibabacloud.com") && s != AliCloudNASProvisioner { + return false + } + if strings.Contains(string(s), "com.tencent.cloud") && s != TencentCloudCFSProvisioner { + return false + } + if strings.Contains(string(s), "everest.io") && !(s == HuaweiCloudSFSProvisioner || s == HuaweiCloudNASProvisioner) { + return false + } + + return true +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index d06114f6..63fbef62 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -226,6 +226,10 @@ func initDatabase() { // 初始化数据 commonrepo.NewInstallColl().InitInstallData(systemservice.InitInstallMap()) commonrepo.NewBasicImageColl().InitBasicImageData(systemservice.InitbasicImageInfos()) + + if err := commonrepo.NewS3StorageColl().InitData(); err != nil { + log.Warnf("Failed to init S3 data: %s", err) + } } type indexer interface { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index a91e6512..8da4cf68 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -635,6 +635,12 @@ func RestartPipelineTaskV2(userName string, taskID int64, pipelineName string, t func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log *zap.SugaredLogger) (*task.Testing, error) { var resp *task.Testing + testTask := &task.Testing{ + TaskType: config.TaskTestingV2, + Enabled: true, + TestName: "test", + } + allTestings, err := commonrepo.NewTestingColl().List(&commonrepo.ListTestOption{ProductName: args.ProductName, TestType: ""}) if err != nil { log.Errorf("testArgsToTestSubtask TestingModule.List error: %v", err) @@ -657,6 +663,17 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * } testArg.TestModuleName = args.TestName + + testTask.CacheEnable = testing.CacheEnable + testTask.CacheDirType = testing.CacheDirType + testTask.CacheUserDir = testing.CacheUserDir + + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(testing.PreTest.ClusterID) + if err != nil { + return resp, e.ErrListTestModule.AddDesc(err.Error()) + } + testTask.Cache = clusterInfo.Cache + break } } @@ -666,12 +683,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * log.Errorf("[%s]get TestingModule error: %v", args.TestName, err) return resp, err } - testTask := &task.Testing{ - TaskType: config.TaskTestingV2, - Enabled: true, - TestName: "test", - Timeout: testModule.Timeout, - } + testTask.Timeout = testModule.Timeout testTask.TestModuleName = testModule.Name testTask.JobCtx.TestType = testModule.TestType diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index a7e46d4b..1d02a035 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1852,6 +1852,16 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL ClusterID: module.PreBuild.ClusterID, } + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(module.PreBuild.ClusterID) + if err != nil { + return nil, e.ErrConvertSubTasks.AddErr(err) + } + + build.Cache = clusterInfo.Cache + build.CacheEnable = module.CacheEnable + build.CacheDirType = module.CacheDirType + build.CacheUserDir = module.CacheUserDir + if args.TaskType != "" { build.TaskType = config.TaskArtifactDeploy } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go b/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go index 099cebcf..ae154428 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/test_task.go @@ -62,7 +62,6 @@ func CreateTestTask(c *gin.Context) { } ctx.Resp, ctx.Err = service.CreateTestTask(args, ctx.Logger) - // 发送通知 if ctx.Err != nil { commonservice.SendFailedTaskMessage(ctx.UserName, args.ProductName, args.TestName, ctx.RequestID, config.TestType, ctx.Err, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/test_task.go b/pkg/microservice/aslan/core/workflow/testing/service/test_task.go index 30cbb763..af206085 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/test_task.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/test_task.go @@ -57,7 +57,6 @@ func CreateTestTask(args *commonmodels.TestTaskArgs, log *zap.SugaredLogger) (*C return nil, e.ErrGetCounter.AddDesc(err.Error()) } - // 获取全局configpayload configPayload := commonservice.GetConfigPayload(0) defaultS3Store, err := s3.FindDefaultS3() diff --git a/pkg/microservice/reaper/core/service/archive/file_archive.go b/pkg/microservice/reaper/core/service/archive/file_archive.go index 5857925a..6124542d 100644 --- a/pkg/microservice/reaper/core/service/archive/file_archive.go +++ b/pkg/microservice/reaper/core/service/archive/file_archive.go @@ -244,7 +244,6 @@ func (c *WorkspaceAchiever) Achieve(target string) ([]string, error) { } return c.paths, nil - } // The path contains shell variables, use linux's own ability to render diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 574258ac..60f4bd61 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -25,15 +25,16 @@ import ( "github.com/koderover/zadig/pkg/microservice/reaper/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) -// Context ... type Context struct { // API token 服务访问使用的api token APIToken string `yaml:"api_token"` // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` + // TODO: Deprecated. // CleanWorkspace 是否清理工作目录 [选填, 默认为 false] CleanWorkspace bool `yaml:"clean_workspace"` @@ -117,6 +118,12 @@ type Context struct { ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` ArtifactPath string `yaml:"artifact_path"` AesKey string `yaml:"aes_key"` + + // New since V1.10.0. + CacheEnable bool `yaml:"cache_enable"` + Cache types.Cache `yaml:"cache"` + CacheDirType types.CacheDirType `yaml:"cache_dir_type"` + CacheUserDir string `yaml:"cache_user_dir"` } type ArtifactInfo struct { diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 01fdeed9..089c4cd3 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -162,7 +162,14 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { } // 预防非正常退出导致git被锁住 - _ = os.Remove(path.Join(workDir, "/.git/index.lock")) + indexLockPath := path.Join(workDir, "/.git/index.lock") + if err := os.RemoveAll(indexLockPath); err != nil { + log.Errorf("Failed to remove %s: %s", indexLockPath, err) + } + shallowLockPath := path.Join(workDir, "/.git/shallow.lock") + if err := os.RemoveAll(shallowLockPath); err != nil { + log.Errorf("Failed to remove %s: %s", shallowLockPath, err) + } if isDirEmpty(filepath.Join(workDir, ".git")) { cmds = append(cmds, &c.Command{Cmd: c.InitGit(workDir)}) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 44f017b7..e4a0d4fa 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -31,21 +31,18 @@ import ( "gopkg.in/yaml.v3" "github.com/koderover/zadig/pkg/microservice/reaper/config" - "github.com/koderover/zadig/pkg/microservice/reaper/core/service/archive" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/meta" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util/fs" ) const ( - // ReadmeScriptFile ... ReadmeScriptFile = "readme_script.sh" - // ReadmeFile ... - ReadmeFile = "/tmp/README" + ReadmeFile = "/tmp/README" ) -// Reaper ... type Reaper struct { Ctx *meta.Context StartTime time.Time @@ -82,33 +79,25 @@ func (r *Reaper) GetCacheFile() string { return filepath.Join(r.Ctx.Workspace, "reaper.tar.gz") } -func (r *Reaper) archiveCustomCaches(wd, dest string, caches []string) ([]string, error) { - fileAchiever := archive.NewWorkspaceAchiever(r.Ctx.StorageURI, r.Ctx.PipelineName, r.Ctx.ServiceName, wd, r.Ctx.AesKey, caches, []string{}, r.getUserEnvs()) - - // list files matches caches - return fileAchiever.Achieve(dest) -} - func (r *Reaper) CompressCache(storageURI string) error { + log.Infof("StorageURI: %s, r.ActiveWorkspace: %s", storageURI, r.ActiveWorkspace) + err := r.EnsureActiveWorkspace(r.ActiveWorkspace) if err != nil { log.Errorf("EnsureActiveWorkspace err:%v", err) return err } - if len(r.Ctx.Caches) > 0 { - log.Infof("custom caches will be cached") - caches, err := r.archiveCustomCaches(r.ActiveWorkspace, r.GetCacheFile(), r.Ctx.Caches) - if err != nil { - return err - } - log.Infof("succeed to cache [%s]", strings.Join(caches, ",")) - } else { - log.Infof("workspace will be cached in background") - if err := r.cm.Archive(r.ActiveWorkspace, r.GetCacheFile()); err != nil { - return err - } - log.Info("succeed to cache workspace") + + cacheDir := "/workspace" + if r.Ctx.CacheDirType == types.UserDefinedCacheDir { + cacheDir = r.Ctx.CacheUserDir + } + + log.Infof("Data in `%s` will be cached.", cacheDir) + if err := r.cm.Archive(cacheDir, r.GetCacheFile()); err != nil { + return fmt.Errorf("failed to cache %s: %s", cacheDir, err) } + log.Infof("Succeed to cache %s", cacheDir) // remove workspace err = os.RemoveAll(r.ActiveWorkspace) @@ -141,28 +130,23 @@ func (r *Reaper) EnsureActiveWorkspace(workspace string) error { r.ActiveWorkspace = tempWorkspace return os.Chdir(r.ActiveWorkspace) } + err := os.MkdirAll(workspace, os.ModePerm) if err != nil { return fmt.Errorf("failed to create workspace: %v", err) } r.ActiveWorkspace = workspace + return os.Chdir(r.ActiveWorkspace) } -// BeforeExec ... func (r *Reaper) BeforeExec() error { - workspace := "/workspace" + r.StartTime = time.Now() + workspace := "/workspace" if r.Ctx.ClassicBuild { workspace = r.Ctx.Workspace } - - r.StartTime = time.Now() - - if err := os.RemoveAll(workspace); err != nil { - log.Warning(err.Error()) - } - if err := r.EnsureActiveWorkspace(workspace); err != nil { return err } @@ -175,7 +159,6 @@ func (r *Reaper) BeforeExec() error { time.Sleep(time.Second * 1) } - // 检查是否需要登录docker registry if r.Ctx.DockerRegistry != nil { if r.Ctx.DockerRegistry.UserName != "" { log.Infof("login docker registry %s", r.Ctx.DockerRegistry.Host) @@ -190,39 +173,26 @@ func (r *Reaper) BeforeExec() error { } } - // CleanWorkspace=True 意思是不使用缓存,ResetCache=True 意思是当次工作流不使用缓存 - // 如果 CleanWorkspace=True,永远不使用缓存 - // 如果 CleanWorkspace=False,本次工作流 ResetCache=False,使用缓存;本次工作流 ResetCache=True,不使用缓存 - // TODO: CleanWorkspace 和 ResetCache 严重词不达意,需要改成更合理的值 - if !r.Ctx.CleanWorkspace && !r.Ctx.ResetCache { - // 恢复缓存 - //if _, err := os.Stat(r.GetCacheFile()); err == nil { - // 解压缓存 + if r.Ctx.CacheEnable && r.Ctx.Cache.MediumType == types.ObjectMedium { log.Info("extracting workspace ...") if err := r.DecompressCache(); err != nil { - log.Infof("no previous cache is found: %v", err) - //if err = os.Remove(r.GetCacheFile()); err != nil { - // log.Warningf("failed to remove cache file %s: %v", r.GetCacheFile(), err) - //} + // If the workflow runs for the first time, there may be no cache. + log.Infof("no previous cache is found: %s", err) } else { log.Info("succeed to extract workspace") } - //} } - // 创建SSH目录 if err := os.MkdirAll(path.Join(os.Getenv("HOME"), "/.ssh"), os.ModePerm); err != nil { return fmt.Errorf("create ssh folder error: %v", err) } - // 创建发布目录 if r.Ctx.Archive != nil && len(r.Ctx.Archive.Dir) > 0 { if err := os.MkdirAll(r.Ctx.Archive.Dir, os.ModePerm); err != nil { return fmt.Errorf("create DistDir error: %v", err) } } - // 检查是否需要配置Gitub/Gitlab if r.Ctx.Git != nil { if err := r.Ctx.Git.WriteGithubSSHFile(); err != nil { return fmt.Errorf("write github ssh file error: %v", err) @@ -241,14 +211,13 @@ func (r *Reaper) BeforeExec() error { } } - // 清理测试目录 if r.Ctx.GinkgoTest != nil && len(r.Ctx.GinkgoTest.ResultPath) > 0 { r.Ctx.GinkgoTest.ResultPath = filepath.Join(r.ActiveWorkspace, r.Ctx.GinkgoTest.ResultPath) log.Infof("clean test result path %s", r.Ctx.GinkgoTest.ResultPath) if err := os.RemoveAll(r.Ctx.GinkgoTest.ResultPath); err != nil { log.Warning(err.Error()) } - // 创建测试目录 + if err := os.MkdirAll(r.Ctx.GinkgoTest.ResultPath, os.ModePerm); err != nil { return fmt.Errorf("create test result path error: %v", err) } @@ -342,25 +311,19 @@ func (r *Reaper) prepareDockerfile() error { return nil } -// Exec ... func (r *Reaper) Exec() error { - - // 运行安装脚本 if err := r.runIntallationScripts(); err != nil { return err } - // 运行Git命令 if err := r.runGitCmds(); err != nil { return err } - // 生成Git commits信息 if err := r.createReadme(ReadmeFile); err != nil { log.Warningf("create readme file error: %v", err) } - // 运行用户脚本 if err := r.runScripts(); err != nil { return err } @@ -450,10 +413,10 @@ func (r *Reaper) AfterExec(upStreamErr error) error { return err } - // Upload workspace cache if user uses workspace cache. + // Upload workspace cache if the user turns on caching and uses object storage. // Note: Whether the cache is uploaded successfully or not cannot hinder the progress of the overall process, // so only exceptions are printed here and the process is not interrupted. - if !r.Ctx.CleanWorkspace && !r.Ctx.ResetCache { + if r.Ctx.CacheEnable && r.Ctx.Cache.MediumType == types.ObjectMedium { if err := r.CompressCache(r.Ctx.StorageURI); err != nil { log.Warnf("Failed to run compress cache: %s", err) } diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index dcc88010..be31a946 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -150,7 +150,6 @@ func (r *Reaper) runIntallationScripts() error { } func (r *Reaper) createReadme(file string) error { - if r.Ctx.Archive == nil || len(r.Ctx.Repos) == 0 { return nil } diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 7ef555b1..5a812686 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -45,7 +45,6 @@ var ( xl *zap.SugaredLogger ) -// ExecHandler ... // Sender: sender to send ack/notification // TaskPlugins: registered task plugin initiators to initiate specific plugin to execute task type ExecHandler struct { @@ -53,10 +52,8 @@ type ExecHandler struct { TaskPlugins map[config.TaskType]plugins.Initiator } -// CancelHandler ... type CancelHandler struct{} -// HandleMessage ... // Message handler to handle task execution message func (h *ExecHandler) HandleMessage(message *nsq.Message) error { defer func() { @@ -523,7 +520,6 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } func Logger(pipelineTask *task.Task) *zap.SugaredLogger { - // 初始化Logger l := log.Logger() if pipelineTask != nil { l.With(zap.String(setting.RequestID, pipelineTask.ReqID)) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index a3a625e1..86ffe0f8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -38,8 +38,7 @@ import ( ) const ( - // BuildTaskV2Timeout ... - BuildTaskV2Timeout = 60 * 60 * 3 // 60 minutes + BuildTaskV2Timeout = 60 * 60 * 3 // 180 minutes ) // InitializeBuildTaskPlugin to initialize build task plugin, and return reference @@ -67,7 +66,6 @@ func (p *BuildTaskPlugin) SetAckFunc(ack func()) { p.ack = ack } -// Init ... func (p *BuildTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { p.JobName = jobname p.Log = xl @@ -78,17 +76,14 @@ func (p *BuildTaskPlugin) Type() config.TaskType { return p.Name } -// Status ... func (p *BuildTaskPlugin) Status() config.Status { return p.Task.TaskStatus } -// SetStatus ... func (p *BuildTaskPlugin) SetStatus(status config.Status) { p.Task.TaskStatus = status } -// TaskTimeout ... func (p *BuildTaskPlugin) TaskTimeout() int { if p.Task.Timeout == 0 { p.Task.Timeout = BuildTaskV2Timeout @@ -107,6 +102,15 @@ func (p *BuildTaskPlugin) SetBuildStatusCompleted(status config.Status) { //TODO: Binded Archive File logic func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + if p.Task.CacheEnable && !pipelineTask.ConfigPayload.ResetCache { + pipelineCtx.CacheEnable = true + pipelineCtx.Cache = p.Task.Cache + pipelineCtx.CacheDirType = p.Task.CacheDirType + pipelineCtx.CacheUserDir = p.Task.CacheUserDir + } else { + pipelineCtx.CacheEnable = false + } + p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace @@ -304,7 +308,6 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Log.Infof("succeed to create build job %s", p.JobName) } -// Wait ... func (p *BuildTaskPlugin) Wait(ctx context.Context) { status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) p.SetBuildStatusCompleted(status) @@ -334,7 +337,6 @@ func (p *BuildTaskPlugin) Wait(ctx context.Context) { p.SetStatus(status) } -// Complete ... func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { jobLabel := &JobLabel{ PipelineName: pipelineTask.PipelineName, @@ -369,22 +371,20 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, p.Task.LogFile = p.FileName } -// SetTask ... func (p *BuildTaskPlugin) SetTask(t map[string]interface{}) error { task, err := ToBuildTask(t) if err != nil { return err } p.Task = task + return nil } -// GetTask ... func (p *BuildTaskPlugin) GetTask() interface{} { return p.Task } -// IsTaskDone ... func (p *BuildTaskPlugin) IsTaskDone() bool { if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { return true @@ -392,7 +392,6 @@ func (p *BuildTaskPlugin) IsTaskDone() bool { return false } -// IsTaskFailed ... func (p *BuildTaskPlugin) IsTaskFailed() bool { if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { return true @@ -400,22 +399,18 @@ func (p *BuildTaskPlugin) IsTaskFailed() bool { return false } -// SetStartTime ... func (p *BuildTaskPlugin) SetStartTime() { p.Task.StartTime = time.Now().Unix() } -// SetEndTime ... func (p *BuildTaskPlugin) SetEndTime() { p.Task.EndTime = time.Now().Unix() } -// IsTaskEnabled ... func (p *BuildTaskPlugin) IsTaskEnabled() bool { return p.Task.Enabled } -// ResetError ... func (p *BuildTaskPlugin) ResetError() { p.Task.Error = "" } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 50b2099f..cf8505b5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -52,6 +52,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" + commontypes "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" ) @@ -166,7 +167,6 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st return nil } -// JobCtxBuilder ... type JobCtxBuilder struct { JobName string ArchiveFile string @@ -187,13 +187,12 @@ func replaceWrapLine(script string) string { // BuildReaperContext builds a yaml func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName string) *types.Context { - ctx := &types.Context{ APIToken: pipelineTask.ConfigPayload.APIToken, Workspace: b.PipelineCtx.Workspace, CleanWorkspace: b.JobCtx.CleanWorkspace, IgnoreCache: pipelineTask.ConfigPayload.IgnoreCache, - ResetCache: pipelineTask.ConfigPayload.ResetCache, + // ResetCache: pipelineTask.ConfigPayload.ResetCache, Proxy: &types.Proxy{ Type: pipelineTask.ConfigPayload.Proxy.Type, Address: pipelineTask.ConfigPayload.Proxy.Address, @@ -226,6 +225,14 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName StorageEndpoint: pipelineTask.StorageEndpoint, AesKey: pipelineTask.ConfigPayload.AesKey, } + + if b.PipelineCtx.CacheEnable && !pipelineTask.ConfigPayload.ResetCache { + ctx.CacheEnable = true + ctx.Cache = b.PipelineCtx.Cache + ctx.CacheDirType = b.PipelineCtx.CacheDirType + ctx.CacheUserDir = b.PipelineCtx.CacheUserDir + } + for _, install := range b.Installs { inst := &types.Install{ // TODO: 之后可以适配 install.Scripts 为[]string @@ -522,6 +529,28 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa }, } + if ctx.CacheEnable && ctx.Cache.MediumType == commontypes.NFSMedium { + volumeName := "build-cache" + job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: ctx.Cache.NFSProperties.PVC, + }, + }, + }) + + mountPath := ctx.CacheUserDir + if ctx.CacheDirType == commontypes.WorkspaceCacheDir { + mountPath = "/workspace" + } + + job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + }) + } + if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { job.Spec.Template.Spec.Containers[0].Command = []string{"/bin/sh", "-c"} job.Spec.Template.Spec.Containers[0].Args = []string{reaperBootingScript} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 5afb0e7a..7e2c766a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -41,7 +41,6 @@ import ( "github.com/koderover/zadig/pkg/util" ) -//InitializeTestTaskPlugin ... func InitializeTestTaskPlugin(taskType config.TaskType) TaskPlugin { return &TestPlugin{ Name: taskType, @@ -64,34 +63,27 @@ func (p *TestPlugin) SetAckFunc(func()) { } const ( - // TestingV2TaskTimeout ... TestingV2TaskTimeout = 60 * 60 // 60 minutes ) -// Init ... func (p *TestPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { p.JobName = jobname p.FileName = filename - // SetLogger ... p.Log = xl } -// Type ... func (p *TestPlugin) Type() config.TaskType { return p.Name } -// Status ... func (p *TestPlugin) Status() config.Status { return p.Task.TaskStatus } -// SetStatus ... func (p *TestPlugin) SetStatus(status config.Status) { p.Task.TaskStatus = status } -// TaskTimeout ... func (p *TestPlugin) TaskTimeout() int { if p.Task.Timeout == 0 { p.Task.Timeout = TestingV2TaskTimeout @@ -104,6 +96,15 @@ func (p *TestPlugin) TaskTimeout() int { } func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + if p.Task.CacheEnable && !pipelineTask.ConfigPayload.ResetCache { + pipelineCtx.CacheEnable = true + pipelineCtx.Cache = p.Task.Cache + pipelineCtx.CacheDirType = p.Task.CacheDirType + pipelineCtx.CacheUserDir = p.Task.CacheUserDir + } else { + pipelineCtx.CacheEnable = false + } + p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace @@ -134,9 +135,8 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } } pipelineCtx.DockerHost = pipelineTask.DockerHost - // 重置错误信息 + // Reset error message. p.Task.Error = "" - // 获取测试相关的namespace var linkedNamespace string var envName string if pipelineTask.Type == config.SingleType { @@ -266,13 +266,11 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } } -// Wait ... func (p *TestPlugin) Wait(ctx context.Context) { status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) p.SetStatus(status) } -// Complete ... func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { pipelineName := pipelineTask.PipelineName pipelineTaskID := pipelineTask.TaskID @@ -494,7 +492,6 @@ func (p *TestPlugin) IsTaskDone() bool { return false } -// IsTaskFailed ... func (p *TestPlugin) IsTaskFailed() bool { if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { return true @@ -502,22 +499,18 @@ func (p *TestPlugin) IsTaskFailed() bool { return false } -// SetStartTime ... func (p *TestPlugin) SetStartTime() { p.Task.StartTime = time.Now().Unix() } -// SetEndTime ... func (p *TestPlugin) SetEndTime() { p.Task.EndTime = time.Now().Unix() } -// IsTaskEnabled .. func (p *TestPlugin) IsTaskEnabled() bool { return p.Task.Enabled } -// ResetError ... func (p *TestPlugin) ResetError() { p.Task.Error = "" } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 0e2a1538..bfc68dbe 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -23,6 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) // Context ... @@ -32,6 +33,7 @@ type Context struct { // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` + // TODO: Deprecated. // CleanWorkspace 是否清理工作目录 [选填, 默认为 false] CleanWorkspace bool `yaml:"clean_workspace"` @@ -115,6 +117,12 @@ type Context struct { ArtifactInfo *ArtifactInfo `yaml:"artifact_info"` ArtifactPath string `yaml:"artifact_path"` AesKey string `yaml:"aes_key"` + + // New since V1.10.0. + CacheEnable bool `yaml:"cache_enable"` + Cache types.Cache `yaml:"cache"` + CacheDirType types.CacheDirType `yaml:"cache_dir_type"` + CacheUserDir string `yaml:"cache_user_dir"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index c0e28f01..386d8b8d 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type Build struct { @@ -59,6 +60,12 @@ type Build struct { EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` + + // New since V1.10.0. + Cache types.Cache `bson:"cache" json:"cache"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } type ArtifactInfo struct { @@ -148,9 +155,11 @@ type DockerBuildStatus struct { } type JobCtx struct { - EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` - Proxy *Proxy `bson:"proxy" json:"proxy"` - CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + Proxy *Proxy `bson:"proxy" json:"proxy"` + + // TODO: Deprecated. + CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` // BuildJobCtx Builds []*Repository `bson:"builds" json:"builds"` @@ -173,8 +182,10 @@ type JobCtx struct { FileArchiveCtx *FileArchiveCtx `bson:"file_archive_ctx,omitempty" json:"file_archive_ctx,omitempty"` // TestType TestType string `bson:"test_type" json:"test_type"` - // Caches + + // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` + // buildV3 ArtifactPath string `bson:"artifact_path,omitempty" json:"artifact_path,omitempty"` ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/testing.go b/pkg/microservice/warpdrive/core/service/types/task/testing.go index d443b718..fca2eb86 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/testing.go +++ b/pkg/microservice/warpdrive/core/service/types/task/testing.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type Testing struct { @@ -48,6 +49,12 @@ type Testing struct { Registries []*RegistryNamespace `bson:"-" json:"registries"` ClusterID string `bson:"cluster_id" json:"cluster_id"` Namespace string `bson:"namespace" json:"namespace"` + + // New since V1.10.0. + Cache types.Cache `bson:"cache" json:"cache"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } func (t *Testing) ToSubTask() (map[string]interface{}, error) { diff --git a/pkg/microservice/warpdrive/core/service/types/task/warpdrive.go b/pkg/microservice/warpdrive/core/service/types/task/warpdrive.go index 15a79145..20c84420 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/warpdrive.go +++ b/pkg/microservice/warpdrive/core/service/types/task/warpdrive.go @@ -16,6 +16,10 @@ limitations under the License. package task +import ( + "github.com/koderover/zadig/pkg/types" +) + type PipelineCtx struct { DockerHost string Workspace string @@ -23,6 +27,12 @@ type PipelineCtx struct { DockerMountDir string ConfigMapMountDir string MultiRun bool + + // New since V1.10.0. + Cache types.Cache + CacheEnable bool + CacheDirType types.CacheDirType + CacheUserDir string } type JobCtxBuilder struct { diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index 860fbaee..d2cd0339 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -48,6 +48,7 @@ type jwtClaims struct { UID string `json:"uid"` } +// TODO: We need to implement a `context.Context` that conforms to the golang standard library. func NewContext(c *gin.Context) *Context { logger := ginzap.WithContext(c).Sugar() var claims jwtClaims diff --git a/pkg/types/cache.go b/pkg/types/cache.go new file mode 100644 index 00000000..58ffcf3e --- /dev/null +++ b/pkg/types/cache.go @@ -0,0 +1,55 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type MediumType string + +const ( + ObjectMedium MediumType = "object" + NFSMedium MediumType = "nfs" +) + +type ProvisionType string + +const ( + DynamicProvision ProvisionType = "dynamic" + StaticProvision ProvisionType = "static" +) + +type ObjectProperties struct { + ID string `json:"id" bson:"id"` +} + +type NFSProperties struct { + ProvisionType ProvisionType `json:"provision_type" bson:"provision_type"` + StorageClass string `json:"storage_class" bson:"storage_class"` + StorageSizeInGiB int64 `json:"storage_size_in_gib" bson:"storage_size_in_gib"` + PVC string `json:"pvc" bson:"pvc"` +} + +type Cache struct { + MediumType MediumType `json:"medium_type" bson:"medium_type"` + ObjectProperties ObjectProperties `json:"object_properties" bson:"object_properties"` + NFSProperties NFSProperties `json:"nfs_properties" bson:"nfs_properties"` +} + +type CacheDirType string + +const ( + WorkspaceCacheDir CacheDirType = "workspace" + UserDefinedCacheDir CacheDirType = "user_defined" +) -- Gitee From e9c429bf5d7a02cc4d2c5aa3b2206aa544374c7c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 27 Jan 2022 12:03:31 +0800 Subject: [PATCH 1444/2647] fix devops data issue Signed-off-by: liu deyi --- .../common/repository/mongodb/delivery_artifact.go | 12 ------------ .../aslan/core/stat/service/deploy_stat.go | 4 ++-- pkg/microservice/aslan/core/stat/service/overview.go | 4 ++-- pkg/microservice/aslan/core/stat/service/test.go | 2 +- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index 2677db17..a7fb5ebc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -81,18 +81,6 @@ func (c *DeliveryArtifactColl) EnsureIndex(ctx context.Context) error { return err } -func (c *DeliveryArtifactColl) Count() (int64, error) { - query := bson.M{} - - ctx := context.Background() - count, err := c.Collection.CountDocuments(ctx, query) - if err != nil { - return 0, err - } - - return count, nil -} - func (c *DeliveryArtifactColl) List(args *DeliveryArtifactArgs) ([]*models.DeliveryArtifact, int, error) { if args == nil { return nil, 0, errors.New("nil delivery_artifact args") diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go index 6f84cb73..59cb9388 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy_stat.go +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -337,7 +337,7 @@ type deployHigherStat struct { } func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*deployHigherStat, error) { - deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames, Limit: 5}) if err != nil { log.Errorf("ListDeployStat err:%v", err) return nil, fmt.Errorf("ListDeployStat err:%v", err) @@ -371,7 +371,7 @@ type deployFailureHigherStat struct { } func GetDeployTopFiveFailureMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*deployFailureHigherStat, error) { - deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames}) + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames, Limit: 5}) if err != nil { log.Errorf("ListDeployStat err:%v", err) return nil, fmt.Errorf("ListDeployStat err:%v", err) diff --git a/pkg/microservice/aslan/core/stat/service/overview.go b/pkg/microservice/aslan/core/stat/service/overview.go index 42fe239d..1d08e0fd 100644 --- a/pkg/microservice/aslan/core/stat/service/overview.go +++ b/pkg/microservice/aslan/core/stat/service/overview.go @@ -62,12 +62,12 @@ func GetOverviewStat(log *zap.SugaredLogger) (*Overview, error) { } overview.EnvCount = int(envCount) - artifactCount, err := commonrepo.NewDeliveryArtifactColl().Count() + _, artifactCount, err := commonrepo.NewDeliveryArtifactColl().List(&commonrepo.DeliveryArtifactArgs{}) if err != nil { log.Errorf("Failed to get artifact count err:%s", err) return nil, err } - overview.ArtifactCount = int(artifactCount) + overview.ArtifactCount = artifactCount serviceCount, err := commonrepo.NewServiceColl().Count("") if err != nil { diff --git a/pkg/microservice/aslan/core/stat/service/test.go b/pkg/microservice/aslan/core/stat/service/test.go index 04ff69e5..e759fd85 100644 --- a/pkg/microservice/aslan/core/stat/service/test.go +++ b/pkg/microservice/aslan/core/stat/service/test.go @@ -28,7 +28,7 @@ type testDashboard struct { TotalCaseCount int `json:"total_case_count"` TotalExecCount int `json:"total_exec_count"` Success int `json:"success"` - AverageDuration int64 `json:"average_curation"` + AverageDuration int64 `json:"average_duration"` } func GetTestDashboard(log *zap.SugaredLogger) (*testDashboard, error) { -- Gitee From cc0aca16401e956f2f1c1a4e42e2f9a7d2d9e28c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 27 Jan 2022 13:51:13 +0800 Subject: [PATCH 1445/2647] fix devops data issue Signed-off-by: liu deyi --- pkg/microservice/aslan/core/stat/service/deploy_stat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/stat/service/deploy_stat.go b/pkg/microservice/aslan/core/stat/service/deploy_stat.go index 59cb9388..ac63b6d2 100644 --- a/pkg/microservice/aslan/core/stat/service/deploy_stat.go +++ b/pkg/microservice/aslan/core/stat/service/deploy_stat.go @@ -337,7 +337,7 @@ type deployHigherStat struct { } func GetDeployTopFiveHigherMeasure(startDate, endDate int64, productNames []string, log *zap.SugaredLogger) ([]*deployHigherStat, error) { - deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames, Limit: 5}) + deployStats, err := mongodb.NewDeployStatColl().ListDeployStat(&models.DeployStatOption{StartDate: startDate, EndDate: endDate, IsAsc: true, ProductNames: productNames, Limit: 5, IsMaxDeploy: true}) if err != nil { log.Errorf("ListDeployStat err:%v", err) return nil, fmt.Errorf("ListDeployStat err:%v", err) -- Gitee From 9e70298ec9f15612087a52192503a2b21170d837 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 27 Jan 2022 14:41:42 +0800 Subject: [PATCH 1446/2647] add proxy Signed-off-by: mouuii --- .../core/codehost/internal/oauth/oauth.go | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index e92e653e..b1347626 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -17,10 +17,14 @@ limitations under the License. package oauth import ( + "context" "fmt" "net/http" + "net/url" "golang.org/x/oauth2" + + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" ) type OAuth struct { @@ -60,5 +64,21 @@ func (o *OAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { if errType := q.Get("error"); errType != "" { return nil, &OAuth2Error{errType, q.Get("error_description")} } - return o.oauth2Config.Exchange(r.Context(), q.Get("code")) + + httpClient := http.DefaultClient + // if set http proxy + proxies, err := commonrepo.NewProxyColl().List(&commonrepo.ProxyArgs{}) + if err == nil && len(proxies) != 0 && proxies[0].EnableRepoProxy { + port := proxies[0].Port + ip := proxies[0].Address + proxyRawUrl := fmt.Sprintf("http://%s:%s", ip, port) + proxyUrl, _ := url.Parse(proxyRawUrl) + httpClient.Transport = &http.Transport{ + Proxy: http.ProxyURL(proxyUrl), + } + } + + ctx := context.Background() + ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient) + return o.oauth2Config.Exchange(ctx, q.Get("code")) } -- Gitee From 53e30589253d2ec7befae909e754ab67cb9e65c6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 27 Jan 2022 15:27:03 +0800 Subject: [PATCH 1447/2647] add proxy Signed-off-by: mouuii --- .../systemconfig/core/codehost/internal/oauth/oauth.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index b1347626..b11a1ab3 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -72,10 +72,13 @@ func (o *OAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { port := proxies[0].Port ip := proxies[0].Address proxyRawUrl := fmt.Sprintf("http://%s:%s", ip, port) - proxyUrl, _ := url.Parse(proxyRawUrl) - httpClient.Transport = &http.Transport{ - Proxy: http.ProxyURL(proxyUrl), + proxyUrl, err2 := url.Parse(proxyRawUrl) + if err2 == nil { + httpClient.Transport = &http.Transport{ + Proxy: http.ProxyURL(proxyUrl), + } } + } ctx := context.Background() -- Gitee From da302c47fe852b6db52f8e6137ed0b01ad1abd71 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 27 Jan 2022 15:40:44 +0800 Subject: [PATCH 1448/2647] optimize Signed-off-by: panxunying --- .../handler/collaboration_instance.go | 16 +- .../handler/collaboration_mode.go | 12 +- .../service/collaboration_instance.go | 303 +++++++++--------- .../core/environment/service/environment.go | 9 + .../aslan/core/label/handler/labels.go | 21 +- .../core/label/repository/mongodb/label.go | 1 + .../aslan/core/label/service/label.go | 24 +- .../aslan/core/label/service/labelsbinding.go | 6 +- pkg/middleware/gin/collaboration.go | 2 +- pkg/shared/client/label/client.go | 2 +- pkg/shared/handler/base.go | 38 ++- 11 files changed, 241 insertions(+), 193 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index a56edca2..6eea3d4e 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -17,11 +17,14 @@ limitations under the License. package handler import ( + "encoding/json" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" ) func GetCollaborationNew(c *gin.Context) { @@ -32,7 +35,7 @@ func GetCollaborationNew(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - ctx.Resp, ctx.Err = service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) + ctx.Resp, ctx.Err = service.GetCollaborationNew(projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.Logger) } func SyncCollaborationInstance(c *gin.Context) { @@ -40,7 +43,14 @@ func SyncCollaborationInstance(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") args := new(service.SyncCollaborationInstanceArgs) - if err := c.ShouldBindQuery(args); err != nil { + data, err := c.GetRawData() + if err != nil { + log.Errorf("SyncCollaborationInstance c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("SyncCollaborationInstance json.Unmarshal err : %v", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } @@ -48,5 +58,5 @@ func SyncCollaborationInstance(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can not be empty") return } - ctx.Err = service.SyncCollaborationInstance(args, projectName, ctx.UserID, ctx.UserName, ctx.RequestID, ctx.Logger) + ctx.Err = service.SyncCollaborationInstance(args, projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index 6c8bc54a..e844bef5 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -57,9 +57,9 @@ func CreateCollaborationMode(c *gin.Context) { return } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "协作模式", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.Account, args.ProjectName, "新增", "协作模式", args.Name, string(data), ctx.Logger) - ctx.Err = service.CreateCollaborationMode(ctx.UserName, args, ctx.Logger) + ctx.Err = service.CreateCollaborationMode(ctx.Account, args, ctx.Logger) } func UpdateCollaborationMode(c *gin.Context) { @@ -79,9 +79,9 @@ func UpdateCollaborationMode(c *gin.Context) { return } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "更新", "协作模式", args.Name, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.Account, args.ProjectName, "更新", "协作模式", args.Name, string(data), ctx.Logger) - ctx.Err = service.UpdateCollaborationMode(ctx.UserName, args, ctx.Logger) + ctx.Err = service.UpdateCollaborationMode(ctx.Account, args, ctx.Logger) } func DeleteCollaborationMode(c *gin.Context) { @@ -94,7 +94,7 @@ func DeleteCollaborationMode(c *gin.Context) { return } name := c.Param("name") - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "协作模式", name, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.Account, projectName, "删除", "协作模式", name, "", ctx.Logger) - ctx.Err = service.DeleteCollaborationMode(ctx.UserName, projectName, name, ctx.Logger) + ctx.Err = service.DeleteCollaborationMode(ctx.Account, projectName, name, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index a6f66002..abc384f8 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -16,11 +16,10 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" service2 "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" config2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" + mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/util" ) type GetCollaborationUpdateResp struct { @@ -74,9 +73,9 @@ type Product struct { CollaborationMode string `json:"collaboration_mode"` Name string `json:"name"` DeployType string `json:"deploy_type"` - Vars []*templatemodels.RenderKV `json:"vars"` - DefaultValues string `json:"defaultValues"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + Vars []*templatemodels.RenderKV `json:"vars,omitempty"` + DefaultValues string `json:"defaultValues,omitempty"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues,omitempty"` } type GetCollaborationNewResp struct { Code int64 `json:"code"` @@ -181,16 +180,16 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } } -func buildPolicyName(projectName, mode string, userName string) string { - return projectName + "-" + mode + "-" + userName + "-" + util.GetRandomString(6) +func buildPolicyName(projectName, mode string, identityType, userName string) string { + return projectName + "-" + mode + "-" + identityType + "-" + userName } -func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, userName string) *models.CollaborationInstance { +func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, identityType, userName string) *models.CollaborationInstance { var workflows []models.WorkflowCIItem for _, workflow := range mode.Workflows { name := workflow.Name if workflow.CollaborationType == config.CollaborationNew { - name = buildName(workflow.Name, mode.Name, userName) + name = buildName(workflow.Name, mode.Name, identityType, userName) } workflows = append(workflows, models.WorkflowCIItem{ Name: name, @@ -203,7 +202,7 @@ func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, u for _, product := range mode.Products { name := product.Name if product.CollaborationType == config.CollaborationNew { - name = buildName(product.Name, mode.Name, userName) + name = buildName(product.Name, mode.Name, identityType, userName) } products = append(products, models.ProductCIItem{ Name: name, @@ -213,10 +212,10 @@ func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, u }) } return &models.CollaborationInstance{ - ProjectName: mode.Name, + ProjectName: mode.ProjectName, CollaborationName: mode.Name, UserUID: uid, - PolicyName: buildPolicyName(projectName, mode.Name, userName), + PolicyName: buildPolicyName(projectName, mode.Name, identityType, userName), Revision: mode.Revision, Workflows: workflows, Products: products, @@ -224,7 +223,7 @@ func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, u } func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*models.CollaborationInstance, projectName, - uid, userName string) (*GetCollaborationUpdateResp, error) { + uid, identityType, userName string) (*GetCollaborationUpdateResp, error) { var updateItems []UpdateItem var updateInstance []models.CollaborationInstance var newItems []models.CollaborationMode @@ -235,7 +234,7 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model return nil, fmt.Errorf("CollaborationMode:%s revision error", name) } else if cm.Revision > ci.Revision { updateItems = append(updateItems, getUpdateDiff(cm, ci)) - instance := genCollaborationInstance(*cm, projectName, uid, userName) + instance := genCollaborationInstance(*cm, projectName, uid, identityType, userName) updateInstance = append(updateInstance, *instance) } } else { @@ -255,7 +254,7 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model }, nil } -func GetCollaborationUpdate(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { +func GetCollaborationUpdate(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ Projects: []string{projectName}, Members: []string{uid}, @@ -276,36 +275,36 @@ func GetCollaborationUpdate(projectName, uid, userName string, logger *zap.Sugar for _, instance := range collaborationInstances { ciMap[instance.CollaborationName] = instance } - resp, err := getDiff(cmMap, ciMap, projectName, uid, userName) + resp, err := getDiff(cmMap, ciMap, projectName, uid, identityType, userName) if err != nil { logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err } return resp, nil } -func buildName(baseName, modeName, userName string) string { - return modeName + "-" + baseName + "-" + userName + "-" + util.GetRandomString(6) +func buildName(baseName, modeName, identityType, userName string) string { + return modeName + "-" + baseName + "-" + identityType + "-" + userName } -func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, userName, uid string, - logger *zap.SugaredLogger) (map[string]*models.CollaborationInstance, error) { +func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName, uid string, + logger *zap.SugaredLogger) error { var instances []*models.CollaborationInstance modeInstanceMap := make(map[string]*models.CollaborationInstance) for _, mode := range updateResp.New { - instance := genCollaborationInstance(mode, projectName, uid, userName) + instance := genCollaborationInstance(mode, projectName, uid, identityType, userName) instances = append(instances, instance) modeInstanceMap[mode.Name] = instance } err := mongodb.NewCollaborationInstanceColl().BulkCreate(instances) if err != nil { logger.Errorf("syncInstance BulkCreate error, error msg:%s", err) - return nil, err + return err } for _, instance := range updateResp.UpdateInstance { err = mongodb.NewCollaborationInstanceColl().Update(uid, &instance) if err != nil { logger.Errorf("syncInstance Update error, error msg:%s", err) - return nil, err + return err } } var findOpts []mongodb.CollaborationInstanceFindOptions @@ -316,30 +315,23 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, userName, UserUID: instance.UserUID, }) } - err = mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ + return mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ FindOpts: findOpts, }) - if err != nil { - return nil, err - } - return modeInstanceMap, nil } func buildPolicyDescription(mode, userName string) string { return mode + " " + userName + "的权限" } -func syncPolicy(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[string]*models.CollaborationInstance, projectName, userName string, +func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName string, logger *zap.SugaredLogger) error { var policies []*policy.Policy for _, mode := range updateResp.New { var rules []*policy.Rule - var policyName string - if instance, ok := modeInstanceMap[mode.Name]; ok { - policyName = instance.PolicyName - } else { - return fmt.Errorf("mode:%s not exist policyName", mode.Name) - } + + policyName := buildPolicyName(projectName, mode.Name, identityType, userName) + for _, workflow := range mode.Workflows { rules = append(rules, &policy.Rule{ Verbs: workflow.Verbs, @@ -410,83 +402,99 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[stri for _, instance := range updateResp.Delete { deletePolicies = append(deletePolicies, instance.PolicyName) } - err = policy.NewDefault().DeletePolicies(projectName, policy.DeletePoliciesArgs{ - Names: deletePolicies, - }) - if err != nil { - return err + if len(deletePolicies) > 0 { + err = policy.NewDefault().DeletePolicies(projectName, policy.DeletePoliciesArgs{ + Names: deletePolicies, + }) + if err != nil { + return err + } } return nil } -func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[string]*models.CollaborationInstance, - projectName string, logger *zap.SugaredLogger) error { - var labels []label.Label - var newBindings []label.LabelBinding - var deleteBindings []label.LabelBinding +func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName string, logger *zap.SugaredLogger) error { + var labels []mongodb2.Label + var newBindings []*mongodb2.LabelBinding + var deleteBindings []*mongodb2.LabelBinding for _, mode := range updateResp.New { - instance, ok := modeInstanceMap[mode.Name] - if ok { - labels = append(labels, label.Label{ - Key: "policy", - Value: instance.PolicyName, - }) - } else { - return fmt.Errorf("mode:%s not exist policyName", mode.Name) - } + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildPolicyName(projectName, mode.Name, identityType, userName), + }) + } - var labelModels []label.LabelModel + labelResp, err := service.CreateLabels(&service.CreateLabelsArgs{ + Labels: labels, + }, userName) + + if err != nil { + logger.Errorf("create labels error, error msg:%s", err) + return err + } + var labelModels []mongodb2.Label for _, item := range updateResp.Update { - labelModels = append(labelModels, label.LabelModel{ + labelModels = append(labelModels, mongodb2.Label{ Key: "policy", Value: item.PolicyName, }) } for _, item := range updateResp.Delete { - labelModels = append(labelModels, label.LabelModel{ + labelModels = append(labelModels, mongodb2.Label{ Key: "policy", Value: item.PolicyName, }) } - labelListResp, err := label.New().ListLabels(label.ListLabelsArgs{ - Labels: labelModels, - }) - labelIdMap := make(map[string]string) - for _, label := range labelListResp.Labels { - labelIdMap[service.BuildLabelString(label.Key, label.Value)] = label.ID.Hex() - } - labelResp, err := label.New().CreateLabels(label.CreateLabelsArgs{ - Labels: labels, - }) + if len(labelModels) > 0 { + labelListResp, err := service.ListLabels(&service.ListLabelsArgs{ + Labels: labelModels, + }) + if err != nil { + return err + } + if labelListResp != nil { + return fmt.Errorf("label not exist %v", labelModels) + } + if len(labelListResp.Labels) != len(labelModels) { + return fmt.Errorf("label not exist %v:%v", labelModels, labelListResp.Labels) + } + labelIdMap := make(map[string]string) + + for _, label := range labelListResp.Labels { + labelIdMap[service.BuildLabelString(label.Key, label.Value)] = label.ID.Hex() + } - if err != nil { - logger.Errorf("create labels error, error msg:%s", err) - return err } + for _, mode := range updateResp.New { - instance, ok := modeInstanceMap[mode.Name] - if !ok { - return fmt.Errorf("mode:%s not exist instance", mode.Name) - } - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", instance.PolicyName)] + policyName := buildPolicyName(projectName, mode.Name, identityType, userName) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", policyName)] if !ok { - return fmt.Errorf("label:%s not exist", instance.PolicyName) + return fmt.Errorf("label:%s not exist", policyName) } - for _, workflow := range instance.Workflows { - newBindings = append(newBindings, label.LabelBinding{ + for _, workflow := range mode.Workflows { + name := workflow.Name + if workflow.CollaborationType == config.CollaborationNew { + name = buildName(workflow.Name, mode.Name, identityType, userName) + } + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ - Name: workflow.Name, + Resource: mongodb2.Resource{ + Name: name, ProjectName: projectName, Type: "Workflow", }, }) } - for _, product := range instance.Products { - newBindings = append(newBindings, label.LabelBinding{ + for _, product := range mode.Products { + name := product.Name + if product.CollaborationType == config.CollaborationNew { + name = buildName(product.Name, mode.Name, identityType, userName) + } + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ - Name: product.Name, + Resource: mongodb2.Resource{ + Name: name, ProjectName: projectName, Type: string(config2.ResourceTypeProduct), }, @@ -499,9 +507,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin return fmt.Errorf("label:%s not exist", item.PolicyName) } for _, workflow := range item.NewSpec.Workflows { - newBindings = append(newBindings, label.LabelBinding{ + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: workflow.Name, Type: "Workflow", ProjectName: projectName, @@ -509,9 +517,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin }) } for _, product := range item.NewSpec.Products { - newBindings = append(newBindings, label.LabelBinding{ + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: product.Name, Type: string(config2.ResourceTypeProduct), ProjectName: projectName, @@ -521,9 +529,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == config.CollaborationNew { - newBindings = append(newBindings, label.LabelBinding{ + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: workflow.New.Name, Type: "Workflow", ProjectName: projectName, @@ -532,9 +540,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin } if workflow.Old.CollaborationType == config.CollaborationNew && workflow.New.CollaborationType == config.CollaborationShare { - deleteBindings = append(deleteBindings, label.LabelBinding{ + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: workflow.New.Name, Type: "Workflow", ProjectName: projectName, @@ -545,9 +553,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin for _, product := range item.UpdateSpec.Products { if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == config.CollaborationNew { - newBindings = append(newBindings, label.LabelBinding{ + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: product.New.Name, Type: string(config2.ResourceTypeProduct), ProjectName: projectName, @@ -556,9 +564,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin } if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == config.CollaborationShare { - deleteBindings = append(deleteBindings, label.LabelBinding{ + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: product.Old.Name, Type: string(config2.ResourceTypeProduct), ProjectName: projectName, @@ -574,9 +582,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin return fmt.Errorf("label:%s not exist", item.PolicyName) } for _, workflow := range item.Workflows { - deleteBindings = append(deleteBindings, label.LabelBinding{ + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: workflow.Name, ProjectName: projectName, Type: "Workflow", @@ -584,9 +592,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin }) } for _, product := range item.Products { - deleteBindings = append(deleteBindings, label.LabelBinding{ + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, - Resource: label.Resource{ + Resource: mongodb2.Resource{ Name: product.Name, ProjectName: projectName, Type: string(config2.ResourceTypeProduct), @@ -594,31 +602,38 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, modeInstanceMap map[strin }) } } - err = label.New().DeleteLabels(deleteLabels) - if err != nil { - logger.Errorf("delete labels error:%s", err) - return err + if len(deleteLabels) > 0 { + err = service.DeleteLabels(deleteLabels, true, logger) + if err != nil { + logger.Errorf("delete labels error:%s", err) + return err + } } - err = label.New().DeleteLabelBindings(label.DeleteLabelBindingsArgs{ - LabelBindings: deleteBindings, - }) - if err != nil { - logger.Errorf("delete labelbindings error:%s", err) - return err + if len(deleteBindings) > 0 { + err = service.DeleteLabelBindings(&service.DeleteLabelBindingsArgs{ + LabelBindings: deleteBindings, + }, userName, logger) + if err != nil { + logger.Errorf("delete labelbindings error:%s", err) + return err + } } - err = label.New().CreateLabelBindings(label.CreateLabelBindingsArgs{ - LabelBindings: newBindings, - }) - if err != nil { - logger.Errorf("create labelbindings error:%s", err) - return err + if len(newBindings) > 0 { + err = service.CreateLabelBindings(&service.CreateLabelBindingsArgs{ + LabelBindings: newBindings, + }, userName, logger) + if err != nil { + logger.Errorf("create labelbindings error:%s", err) + return err + } } + return nil } -func syncResource(products *SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, +func syncResource(products *SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, identityType, userName, requestID string, logger *zap.SugaredLogger) error { - err := syncNewResource(products, updateResp, projectName, userName, requestID, logger) + err := syncNewResource(products, updateResp, projectName, identityType, userName, requestID, logger) if err != nil { return err } @@ -647,9 +662,9 @@ func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projec return nil } -func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, userName, requestID string, +func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCollaborationUpdateResp, projectName, identityType, userName, requestID string, logger *zap.SugaredLogger) error { - newResp, err := getCollaborationNew(updateResp, projectName, userName, logger) + newResp, err := getCollaborationNew(updateResp, projectName, identityType, userName, logger) if err != nil { return err } @@ -677,14 +692,7 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol var helmProductArgs []service2.HelmProductItem for _, product := range newResp.Product { if productArg, ok := productMap[product.BaseName]; ok { - if product.DeployType == string(config.HelmDeploy) { - yamlProductItems = append(yamlProductItems, service2.YamlProductItem{ - OldName: product.BaseName, - NewName: product.Name, - Vars: productArg.Vars, - }) - } - if product.DeployType == string(config.K8sDeploy) { + if productArg.DeployType == string(config.HelmDeploy) { helmProductArgs = append(helmProductArgs, service2.HelmProductItem{ OldName: product.BaseName, NewName: product.Name, @@ -692,6 +700,13 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol ChartValues: product.ChartValues, }) } + if productArg.DeployType == string(config.K8sDeploy) { + yamlProductItems = append(yamlProductItems, service2.YamlProductItem{ + OldName: product.BaseName, + NewName: product.Name, + Vars: productArg.Vars, + }) + } } } err = service2.BulkCopyYamlProduct(projectName, userName, requestID, service2.CopyYamlProductArg{ @@ -713,22 +728,22 @@ type SyncCollaborationInstanceArgs struct { Products []Product `json:"products"` } -func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectName, uid, userName, requestID string, logger *zap.SugaredLogger) error { - updateResp, err := GetCollaborationUpdate(projectName, uid, userName, logger) +func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectName, uid, identityType, userName, requestID string, logger *zap.SugaredLogger) error { + updateResp, err := GetCollaborationUpdate(projectName, uid, identityType, userName, logger) if err != nil { logger.Errorf("GetCollaborationNew error, err msg:%s", err) return err } - modeInstanceMap, err := syncInstance(updateResp, projectName, userName, uid, logger) + err = syncInstance(updateResp, projectName, identityType, userName, uid, logger) if err != nil { return err } - err = syncResource(products, updateResp, projectName, userName, requestID, logger) - err = syncPolicy(updateResp, modeInstanceMap, projectName, userName, logger) + err = syncResource(products, updateResp, projectName, identityType, userName, requestID, logger) + err = syncPolicy(updateResp, projectName, identityType, userName, logger) if err != nil { return err } - err = syncLabel(updateResp, modeInstanceMap, projectName, logger) + err = syncLabel(updateResp, projectName, identityType, userName, logger) return nil } @@ -757,7 +772,7 @@ func getCollaborationDelete(updateResp *GetCollaborationUpdateResp) *GetCollabor } } -func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, userName string, +func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { var newWorkflow []*Workflow var newProduct []*Product @@ -766,7 +781,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us for _, workflow := range mode.Workflows { name := workflow.Name if workflow.CollaborationType == config.CollaborationNew { - name = buildName(workflow.Name, mode.Name, userName) + name = buildName(workflow.Name, mode.Name, identityType, userName) } newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.CollaborationType, @@ -778,7 +793,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us for _, product := range mode.Products { name := product.Name if product.CollaborationType == config.CollaborationNew { - name = buildName(product.Name, mode.Name, userName) + name = buildName(product.Name, mode.Name, identityType, userName) } newProduct = append(newProduct, &Product{ CollaborationType: product.CollaborationType, @@ -794,7 +809,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us for _, workflow := range item.NewSpec.Workflows { name := workflow.Name if workflow.CollaborationType == config.CollaborationNew { - name = buildName(workflow.Name, item.CollaborationMode, userName) + name = buildName(workflow.Name, item.CollaborationMode, identityType, userName) } newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.CollaborationType, @@ -806,7 +821,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us for _, product := range item.NewSpec.Products { name := product.Name if product.CollaborationType == config.CollaborationNew { - name = buildName(product.Name, item.CollaborationMode, userName) + name = buildName(product.Name, item.CollaborationMode, identityType, userName) } newProduct = append(newProduct, &Product{ CollaborationType: product.CollaborationType, @@ -823,7 +838,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us CollaborationType: workflow.New.CollaborationType, BaseName: workflow.New.BaseName, CollaborationMode: item.CollaborationMode, - Name: buildName(workflow.New.BaseName, item.CollaborationMode, userName), + Name: buildName(workflow.New.BaseName, item.CollaborationMode, identityType, userName), }) } } @@ -834,7 +849,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us BaseName: product.New.BaseName, CollaborationMode: item.CollaborationMode, DeployType: item.DeployType, - Name: buildName(product.New.BaseName, item.CollaborationMode, userName), + Name: buildName(product.New.BaseName, item.CollaborationMode, identityType, userName), }) newProductName.Insert(product.New.BaseName) } @@ -886,13 +901,13 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, us Product: newProduct, }, nil } -func GetCollaborationNew(projectName, uid, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { - updateResp, err := GetCollaborationUpdate(projectName, uid, userName, logger) +func GetCollaborationNew(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { + updateResp, err := GetCollaborationUpdate(projectName, uid, identityType, userName, logger) if err != nil { logger.Errorf("GetCollaborationNew error, err msg:%s", err) return nil, err } - return getCollaborationNew(updateResp, projectName, userName, logger) + return getCollaborationNew(updateResp, projectName, identityType, userName, logger) } func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 1d74d9e4..5c441357 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -913,6 +913,15 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc if product, ok := productMap[item.OldName]; ok { product.EnvName = item.NewName product.Vars = item.Vars + product.Namespace = projectName + "-env-" + product.EnvName + product.Render.Name = product.Namespace + product.ID = primitive.NilObjectID + for _, services := range product.Services { + for _, service := range services { + service.ServiceName = product.Namespace + service.Render.Name = product.Namespace + } + } err = CreateProduct(user, requestID, product, log) if err != nil { return err diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index bef4f96d..df6ca54c 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -17,13 +17,10 @@ limitations under the License. package handler import ( - "fmt" "strconv" "github.com/gin-gonic/gin" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -63,22 +60,8 @@ func CreateLabels(c *gin.Context) { ctx.Err = err return } - filteredLabels := make([]*models.Label, 0) - keyValues := sets.NewString() - for _, v := range labels.Labels { - keyValue := fmt.Sprintf("%s-%s", v.Key, v.Value) - if keyValues.Has(keyValue) { - continue - } - keyValues.Insert(keyValue) - tmpModel := models.Label{ - Key: v.Key, - Value: v.Value, - CreateBy: ctx.UserName, - } - filteredLabels = append(filteredLabels, &tmpModel) - } - ctx.Resp, ctx.Err = service.CreateLabels(filteredLabels) + + ctx.Resp, ctx.Err = service.CreateLabels(labels, ctx.UserName) } //DeleteLabels can only bulk delete labels which not bind reousrces diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index c8669eee..0fe2be5a 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -106,6 +106,7 @@ func (c *LabelColl) Find(id string) (*models.Label, error) { type Label struct { Key string `json:"key"` Value string `json:"value"` + Type string `json:"type"` } type ListLabelOpt struct { diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 87c7b5d5..8df613c7 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -32,19 +32,35 @@ type CreateLabelsResp struct { LabelMap map[string]string `json:"label_map"` } -func CreateLabels(labels []*models.Label) (*CreateLabelsResp, error) { - err := mongodb.NewLabelColl().BulkCreate(labels) +func CreateLabels(arg *CreateLabelsArgs, userName string) (*CreateLabelsResp, error) { + filteredLabels := make([]*models.Label, 0) + keyValues := sets.NewString() + for _, v := range arg.Labels { + keyValue := fmt.Sprintf("%s-%s", v.Key, v.Value) + if keyValues.Has(keyValue) { + continue + } + keyValues.Insert(keyValue) + tmpModel := models.Label{ + Key: v.Key, + Value: v.Value, + CreateBy: userName, + Type: v.Type, + } + filteredLabels = append(filteredLabels, &tmpModel) + } + err := mongodb.NewLabelColl().BulkCreate(filteredLabels) if err != nil { return nil, err } var labelModels []mongodb.Label - for _, label := range labels { + for _, label := range filteredLabels { labelModels = append(labelModels, mongodb.Label{ Key: label.Key, Value: label.Value, }) } - var result map[string]string + result := make(map[string]string) resp, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{ Labels: labelModels, }) diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index e83470e9..ae9d85a3 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -34,13 +34,13 @@ type CreateLabelBindingsArgs struct { func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *zap.SugaredLogger) error { // check label exist - labelIDs := []string{} + labelIDs := sets.String{} for _, binding := range cr.LabelBindings { - labelIDs = append(labelIDs, binding.LabelID) + labelIDs.Insert(binding.LabelID) binding.CreateBy = userName } - labels, err := mongodb.NewLabelColl().ListByIDs(labelIDs) + labels, err := mongodb.NewLabelColl().ListByIDs(labelIDs.List()) if err != nil { logger.Errorf("find labels err:%s", err) return err diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index 2611325c..332c8609 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -17,7 +17,7 @@ func GetCollaborationNew() gin.HandlerFunc { c.Next() return } - newResp, err := service.GetCollaborationNew(projectName, ctx.UserID, ctx.UserName, ctx.Logger) + newResp, err := service.GetCollaborationNew(projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.Logger) if err != nil { ctx.Err = err c.Abort() diff --git a/pkg/shared/client/label/client.go b/pkg/shared/client/label/client.go index 3b40ffbe..e350ed68 100644 --- a/pkg/shared/client/label/client.go +++ b/pkg/shared/client/label/client.go @@ -28,7 +28,7 @@ type Client struct { } func New() *Client { - host := config.ConfigServiceAddress() + host := config.AslanServiceAddress() c := httpclient.New( httpclient.SetHostURL(host + "/api"), ) diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index ee84aded..88438f80 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -24,6 +24,7 @@ import ( "strings" "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt" "go.uber.org/zap" "github.com/koderover/zadig/pkg/config" @@ -34,17 +35,28 @@ import ( // Context struct type Context struct { - Logger *zap.SugaredLogger - Err error - Resp interface{} - UserName string - UserID string - RequestID string + Logger *zap.SugaredLogger + Err error + Resp interface{} + Account string + UserName string + UserID string + IdentityType string + RequestID string } type jwtClaims struct { - Name string `json:"name"` - UID string `json:"uid"` + Name string `json:"name"` + Email string `json:"email"` + UID string `json:"uid"` + Account string `json:"preferred_username"` + FederatedClaims FederatedClaims `json:"federated_claims"` + jwt.StandardClaims +} + +type FederatedClaims struct { + ConnectorId string `json:"connector_id"` + UserId string `json:"user_id"` } func NewContext(c *gin.Context) *Context { @@ -61,10 +73,12 @@ func NewContext(c *gin.Context) *Context { } return &Context{ - UserName: claims.Name, - UserID: claims.UID, - Logger: ginzap.WithContext(c).Sugar(), - RequestID: c.GetString(setting.RequestID), + UserName: claims.Name, + UserID: claims.UID, + Account: claims.Account, + IdentityType: claims.FederatedClaims.ConnectorId, + Logger: ginzap.WithContext(c).Sugar(), + RequestID: c.GetString(setting.RequestID), } } -- Gitee From 45ce24cb5dcc2fe9f6482c97848bb84b957143b0 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 27 Jan 2022 16:16:23 +0800 Subject: [PATCH 1449/2647] debug Signed-off-by: Min Min --- .../aslan/core/environment/handler/environment.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 158a3634..9dabd677 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -91,11 +91,6 @@ func UpdateMultiProducts(c *gin.Context) { return } - if c.Query("auto") != "true" { - ctx.Err = e.ErrInvalidParam.AddDesc("auto=true is required") - return - } - args := new(UpdateEnvs) data, err := c.GetRawData() if err != nil { -- Gitee From f030799be2d2050fa0094ce1d53224edf5987ebf Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 27 Jan 2022 17:27:16 +0800 Subject: [PATCH 1450/2647] root Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 708f3da5..de57837b 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -42,6 +42,7 @@ const ( rolebindingsRoot = "bindings" exemptionsRoot = "exemptions" resourcesRoot = "resources" + policiesRoot = "policies" ) type expressionOperator string @@ -425,7 +426,7 @@ func GenerateOPABundle() error { {Data: generateOPAExemptionURLs(pms), Path: exemptionsPath}, {Data: generateResourceBundle(), Path: resourcesPath}, }, - Roots: []string{policyRoot, rolesRoot, rolebindingsRoot, exemptionsRoot, resourcesRoot}, + Roots: []string{policyRoot, rolesRoot, rolebindingsRoot, exemptionsRoot, resourcesRoot, policiesRoot}, } hash, err := bundle.Rehash() -- Gitee From ff1963b7b2d2d91e38ce546be10a0525b4b1e5e5 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 27 Jan 2022 17:34:08 +0800 Subject: [PATCH 1451/2647] remove unnecessary files Signed-off-by: zhangyifei --- .../internal/repository/models/build.go | 118 +---- .../internal/repository/models/testing.go | 58 +-- .../internal/repository/models/workflow.go | 439 ------------------ .../internal/repository/models/workflow_v3.go | 87 ---- .../internal/repository/mongodb/build.go | 213 --------- .../internal/repository/mongodb/testing.go | 113 ----- 6 files changed, 4 insertions(+), 1024 deletions(-) delete mode 100644 pkg/cli/upgradeassistant/internal/repository/models/workflow.go delete mode 100644 pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go diff --git a/pkg/cli/upgradeassistant/internal/repository/models/build.go b/pkg/cli/upgradeassistant/internal/repository/models/build.go index 84916113..5e2d01e4 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/build.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/build.go @@ -19,33 +19,16 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" ) type Build struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - Source string `bson:"source,omitempty" json:"source,omitempty"` - Timeout int `bson:"timeout" json:"timeout"` - Targets []*ServiceModuleTarget `bson:"targets" json:"targets"` - Description string `bson:"desc,omitempty" json:"desc"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - Repos []*types.Repository `bson:"repos" json:"repos"` - PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` - JenkinsBuild *JenkinsBuild `bson:"jenkins_build,omitempty" json:"jenkins_build,omitempty"` - Scripts string `bson:"scripts" json:"scripts"` - PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` - ProductName string `bson:"product_name" json:"product_name"` - SSHs []string `bson:"sshs,omitempty" json:"sshs,omitempty"` - PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` - // New since V1.10.0. CacheEnable bool `bson:"cache_enable" json:"cache_enable"` CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` @@ -56,103 +39,6 @@ type Build struct { type PreBuild struct { // TODO: Deprecated. CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` - // ResReq defines job requested resources - ResReq setting.Request `bson:"res_req" json:"res_req"` - ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` - // BuildOS defines job image OS, it supports 12.04, 14.04, 16.04 - BuildOS string `bson:"build_os" json:"build_os"` - ImageFrom string `bson:"image_from" json:"image_from"` - ImageID string `bson:"image_id" json:"image_id"` - // Installs defines apps to be installed for build - Installs []*Item `bson:"installs,omitempty" json:"installs"` - // Envs stores user defined env key val for build - Envs []*KeyVal `bson:"envs,omitempty" json:"envs"` - // EnableProxy - EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy"` - // Parameters - Parameters []*Parameter `bson:"parameters,omitempty" json:"parameters"` - // UploadPkg uploads package to s3 - UploadPkg bool `bson:"upload_pkg" json:"upload_pkg"` - ClusterID string `bson:"cluster_id" json:"cluster_id"` - - // TODO: Deprecated. - Namespace string `bson:"namespace" json:"namespace"` -} - -type BuildObj struct { - Targets []string - Description string - Repos []*types.Repository - PreBuild *PreBuild - Scripts string - MainFile string - PostBuild *PostBuild - Caches []string -} - -type PostBuild struct { - DockerBuild *DockerBuild `bson:"docker_build,omitempty" json:"docker_build"` - FileArchive *FileArchive `bson:"file_archive,omitempty" json:"file_archive,omitempty"` - Scripts string `bson:"scripts" json:"scripts"` -} - -type FileArchive struct { - FileLocation string `bson:"file_location" json:"file_location"` -} - -type DockerBuild struct { - // WorkDir docker run path - WorkDir string `bson:"work_dir" json:"work_dir"` - // DockerFile name, default is Dockerfile - DockerFile string `bson:"docker_file" json:"docker_file"` - // BuildArgs docker build args - BuildArgs string `bson:"build_args,omitempty" json:"build_args"` - // Source whether dockerfile comes from template or existing file - Source string `bson:"source" json:"source"` - // TemplateId is the id of the template dockerfile - TemplateID string `bson:"template_id" json:"template_id"` - // TemplateName is the name of the template dockerfile - TemplateName string `bson:"template_name" json:"template_name"` -} - -type JenkinsBuild struct { - JobName string `bson:"job_name" json:"job_name"` - JenkinsBuildParam []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` -} - -type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` -} - -type Parameter struct { - Name string `bson:"name" json:"name"` - DefaultValue string `bson:"default_value" json:"default_value"` - ParamVal []*ParamVal `bson:"param_val" json:"param_val"` -} - -type ParamVal struct { - Target string `bson:"target" json:"target"` - Value string `bson:"value" json:"value"` -} - -type ServiceModuleTarget struct { - ProductName string `bson:"product_name" json:"product_name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` -} - -type KeyVal struct { - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` - IsCredential bool `bson:"is_credential" json:"is_credential"` -} - -type Item struct { - Name string `bson:"name" json:"name"` - Version string `bson:"version" json:"version"` } func (Build) TableName() string { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/testing.go b/pkg/cli/upgradeassistant/internal/repository/models/testing.go index 1f48c8df..24d80f1a 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/testing.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/testing.go @@ -19,80 +19,26 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" ) type Testing struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - ProductName string `bson:"product_name" json:"product_name"` - Desc string `bson:"desc" json:"desc"` - Timeout int `bson:"timeout" json:"timeout"` - Team string `bson:"team" json:"team"` - Repos []*types.Repository `bson:"repos,omitempty" json:"repos"` - PreTest *PreTest `bson:"pre_test" json:"pre_test"` - Scripts string `bson:"scripts" json:"scripts"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - TestResultPath string `bson:"test_result_path" json:"test_result_path"` - TestReportPath string `bson:"test_report_path" json:"test_report_path"` - Threshold int `bson:"threshold" json:"threshold"` - TestType string `bson:"test_type" json:"test_type"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + PreTest *PreTest `bson:"pre_test" json:"pre_test"` // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` - ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` - TestCaseNum int `bson:"-" json:"test_case_num,omitempty"` - ExecuteNum int `bson:"-" json:"execute_num,omitempty"` - PassRate float64 `bson:"-" json:"pass_rate,omitempty"` - AvgDuration float64 `bson:"-" json:"avg_duration,omitempty"` - Workflows []*Workflow `bson:"-" json:"workflows,omitempty"` - Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` - HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` - NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` - ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` - // New since V1.10.0. CacheEnable bool `bson:"cache_enable" json:"cache_enable"` CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } -type TestingHookCtrl struct { - Enabled bool `bson:"enabled" json:"enabled"` - Items []*TestingHook `bson:"items" json:"items"` -} - -type TestingHook struct { - AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` - MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` - TestArgs *TestTaskArgs `bson:"test_args" json:"test_args"` -} - // PreTest prepares an environment for a job type PreTest struct { // TODO: Deprecated. CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` - - // BuildOS defines job image OS, it supports 12.04, 14.04, 16.04 - BuildOS string `bson:"build_os" json:"build_os"` - ImageFrom string `bson:"image_from" json:"image_from"` - ImageID string `bson:"image_id" json:"image_id"` - // ResReq defines job requested resources - ResReq setting.Request `bson:"res_req" json:"res_req"` - ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` - // Installs defines apps to be installed for build - Installs []*Item `bson:"installs,omitempty" json:"installs"` - // Envs stores user defined env key val for build - Envs []*KeyVal `bson:"envs,omitempty" json:"envs"` - // EnableProxy - EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` - ClusterID string `bson:"cluster_id" json:"cluster_id"` - - // TODO: Deprecated. - Namespace string `bson:"namespace" json:"namespace"` } func (Testing) TableName() string { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/workflow.go b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go deleted file mode 100644 index 70e743a2..00000000 --- a/pkg/cli/upgradeassistant/internal/repository/models/workflow.go +++ /dev/null @@ -1,439 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package models - -import ( - "fmt" - - "go.mongodb.org/mongo-driver/bson/primitive" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/setting" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/types" -) - -type Workflow struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Enabled bool `bson:"enabled" json:"enabled"` - ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - EnvName string `bson:"env_name" json:"env_name,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - UpdateBy string `bson:"update_by" json:"update_by,omitempty"` - CreateBy string `bson:"create_by" json:"create_by,omitempty"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - CreateTime int64 `bson:"create_time" json:"create_time"` - Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` - ScheduleEnabled bool `bson:"schedule_enabled" json:"schedule_enabled"` - Slack *Slack `bson:"slack,omitempty" json:"slack,omitempty"` - BuildStage *BuildStage `bson:"build_stage" json:"build_stage"` - ArtifactStage *ArtifactStage `bson:"artifact_stage,omitempty" json:"artifact_stage,omitempty"` - TestStage *TestStage `bson:"test_stage" json:"test_stage"` - SecurityStage *SecurityStage `bson:"security_stage" json:"security_stage"` - DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` - NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` - HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` - - // ResetImage indicate whether reset image to original version after completion - ResetImage bool `bson:"reset_image" json:"reset_image"` - ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy,omitempty" json:"reset_image_policy,omitempty"` - IsParallel bool `json:"is_parallel" bson:"is_parallel"` -} - -type WorkflowHookCtrl struct { - Enabled bool `bson:"enabled" json:"enabled"` - Items []*WorkflowHook `bson:"items" json:"items"` -} - -type WorkflowHook struct { - AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` - CheckPatchSetChange bool `bson:"check_patch_set_change" json:"check_patch_set_change"` - MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` - IsYaml bool `bson:"is_yaml,omitempty" json:"is_yaml,omitempty"` - YamlPath string `bson:"yaml_path,omitempty" json:"yaml_path,omitempty"` -} - -type MainHookRepo struct { - Name string `bson:"name,omitempty" json:"name,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - Source string `bson:"source,omitempty" json:"source,omitempty"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoName string `bson:"repo_name" json:"repo_name"` - Branch string `bson:"branch" json:"branch"` - Tag string `bson:"tag" json:"tag"` - Committer string `bson:"committer" json:"committer"` - MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` - Events []config.HookEventType `bson:"events" json:"events"` - Label string `bson:"label" json:"label"` - Revision string `bson:"revision" json:"revision"` - IsRegular bool `bson:"is_regular" json:"is_regular"` -} - -func (m MainHookRepo) GetLabelValue() string { - if m.Label == "" { - return "Code-Review" - } - - return m.Label -} - -type ScheduleCtrl struct { - Enabled bool `bson:"enabled" json:"enabled"` - Items []*Schedule `bson:"items" json:"items"` -} - -type Schedule struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Number uint64 `bson:"number" json:"number"` - Frequency string `bson:"frequency" json:"frequency"` - Time string `bson:"time" json:"time"` - MaxFailures int `bson:"max_failures,omitempty" json:"max_failures,omitempty"` - TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` - WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args,omitempty" json:"workflow_args,omitempty"` - TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` - Type config.ScheduleType `bson:"type" json:"type"` - Cron string `bson:"cron" json:"cron"` - IsModified bool `bson:"-" json:"-"` - Enabled bool `bson:"enabled" json:"enabled"` -} - -type TaskArgs struct { - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Builds []*types.Repository `bson:"builds" json:"builds"` - BuildArgs []*KeyVal `bson:"build_args" json:"build_args"` - Deploy DeployArgs `bson:"deploy" json:"deploy"` - Test TestArgs `bson:"test" json:"test,omitempty"` - HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - ReqID string `bson:"req_id" json:"req_id"` - IsQiNiu bool `bson:"is_qiniu" json:"is_qiniu"` - NotificationID string `bson:"notification_id" json:"notification_id"` - CodeHostID int `bson:"codehost_id" json:"codehost_id"` -} - -type CallbackArgs struct { - CallbackUrl string `bson:"callback_url" json:"callback_url"` // url-encoded full path - CallbackVars map[string]interface{} `bson:"callback_vars" json:"callback_vars"` // custom defied vars, will be set to body of callback request -} - -type WorkflowTaskArgs struct { - WorkflowName string `bson:"workflow_name" json:"workflow_name"` - ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - Namespace string `bson:"namespace" json:"namespace"` - BaseNamespace string `bson:"base_namespace,omitempty" json:"base_namespace,omitempty"` - EnvRecyclePolicy string `bson:"env_recycle_policy,omitempty" json:"env_recycle_policy,omitempty"` - EnvUpdatePolicy string `bson:"env_update_policy,omitempty" json:"env_update_policy,omitempty"` - Target []*TargetArgs `bson:"targets" json:"targets"` - Artifact []*ArtifactArgs `bson:"artifact_args" json:"artifact_args"` - Tests []*TestArgs `bson:"tests" json:"tests"` - VersionArgs *VersionArgs `bson:"version_args,omitempty" json:"version_args,omitempty"` - ReqID string `bson:"req_id" json:"req_id"` - RegistryID string `bson:"registry_id,omitempty" json:"registry_id,omitempty"` - StorageID string `bson:"storage_id,omitempty" json:"storage_id,omitempty"` - DistributeEnabled bool `bson:"distribute_enabled" json:"distribute_enabled"` - WorkflowTaskCreator string `bson:"workflow_task_creator" json:"workflow_task_creator"` - // Ignore docker build cache - IgnoreCache bool `json:"ignore_cache" bson:"ignore_cache"` - // Ignore workspace cache and reset volume - ResetCache bool `json:"reset_cache" bson:"reset_cache"` - - // NotificationID is the id of scmnotify.Notification - NotificationID string `bson:"notification_id" json:"notification_id"` - - MergeRequestID string `bson:"merge_request_id" json:"merge_request_id"` - CommitID string `bson:"commit_id" json:"commit_id"` - Source string `bson:"source" json:"source"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoName string `bson:"repo_name" json:"repo_name"` - Committer string `bson:"committer,omitempty" json:"committer,omitempty"` - //github check run - HookPayload *HookPayload `bson:"hook_payload" json:"hook_payload,omitempty"` - RequestMode string `json:"request_mode,omitempty"` - IsParallel bool `json:"is_parallel" bson:"is_parallel"` - EnvName string `json:"env_name" bson:"-"` - - Callback *CallbackArgs `bson:"callback" json:"callback"` - ReleaseImages []*ReleaseImage `bson:"release_images,omitempty" json:"release_images,omitempty"` -} - -type ReleaseImage struct { - Image string `bson:"image" json:"image"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` -} - -type TestTaskArgs struct { - ProductName string `bson:"product_name" json:"product_name"` - TestName string `bson:"test_name" json:"test_name"` - TestTaskCreator string `bson:"test_task_creator" json:"test_task_creator"` - NotificationID string `bson:"notification_id" json:"notification_id"` - ReqID string `bson:"req_id" json:"req_id"` - MergeRequestID string `bson:"merge_request_id" json:"merge_request_id"` - CommitID string `bson:"commit_id" json:"commit_id"` - Source string `bson:"source" json:"source"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoName string `bson:"repo_name" json:"repo_name"` -} - -type Slack struct { - Channel string `bson:"channel" json:"channel"` - Enabled bool `bson:"enabled" json:"enabled"` - Notifiers []string `bson:"notifiers" json:"notifiers"` - NotifyType config.SlackNotifyType `bson:"notify_type" json:"notify_type"` -} - -type BuildStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - Modules []*BuildModule `bson:"modules" json:"modules"` -} - -type BuildModule struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` - HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` - BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` -} - -type ArtifactStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - Modules []*ArtifactModule `bson:"modules" json:"modules"` -} - -type ArtifactModule struct { - HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` - Target *ServiceModuleTarget `bson:"target" json:"target"` -} - -type TestStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - TestNames []string `bson:"test_names,omitempty" json:"test_names,omitempty"` - Tests []*TestExecArgs `bson:"tests,omitempty" json:"tests,omitempty"` -} - -type TestExecArgs struct { - Name string `bson:"test_name" json:"test_name"` - Envs []*KeyVal `bson:"envs" json:"envs"` -} - -type SecurityStage struct { - Enabled bool `bson:"enabled" json:"enabled"` -} - -type DistributeStage struct { - Enabled bool `bson:"enabled" json:"enabled"` - S3StorageID string `bson:"s3_storage_id" json:"s3_storage_id"` - ImageRepo string `bson:"image_repo" json:"image_repo"` - JumpBoxHost string `bson:"jump_box_host" json:"jump_box_host"` - Distributes []*ProductDistribute `bson:"distributes" json:"distributes"` - - // repos to release images - Releases []RepoImage `bson:"releases" json:"releases"` -} - -type RepoImage struct { - RepoID string `json:"repo_id" bson:"repo_id"` - Name string `json:"name" bson:"name" yaml:"name"` - Username string `json:"-" yaml:"username"` - Password string `json:"-" yaml:"password"` - Host string `json:"host" yaml:"host"` - Namespace string `json:"namespace" yaml:"namespace"` -} - -type ProductDistribute struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` - ImageDistribute bool `bson:"image_distribute" json:"image_distribute"` - JumpBoxDistribute bool `bson:"jump_box_distribute" json:"jump_box_distribute"` - QstackDistribute bool `bson:"qstack_distribute" json:"qstack_distribute"` -} - -type NotifyCtl struct { - Enabled bool `bson:"enabled" json:"enabled"` - WebHookType string `bson:"webhook_type" json:"webhook_type"` - WeChatWebHook string `bson:"weChat_webHook,omitempty" json:"weChat_webHook,omitempty"` - DingDingWebHook string `bson:"dingding_webhook,omitempty" json:"dingding_webhook,omitempty"` - FeiShuWebHook string `bson:"feishu_webhook,omitempty" json:"feishu_webhook,omitempty"` - AtMobiles []string `bson:"at_mobiles,omitempty" json:"at_mobiles,omitempty"` - IsAtAll bool `bson:"is_at_all,omitempty" json:"is_at_all,omitempty"` - NotifyTypes []string `bson:"notify_type" json:"notify_type"` -} - -type TaskInfo struct { - TaskID int64 `bson:"task_id" json:"task_id"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Status config.Status `bson:"status" json:"status"` -} - -type DeployArgs struct { - Namespace string `json:"namespace"` - Tag string `json:"suffix"` - // Format: xxx.com/{namespace}/{service name}:{timestamp}-{suffix}} - // timestamp format: 20060102150405 - Image string `json:"image"` - // Format: {service name}-{timestamp}-{suffix}}.tar.gz - // timestamp format: 20060102150405 - PackageFile string `json:"package_file"` -} - -type TestArgs struct { - Namespace string `bson:"namespace" json:"namespace"` - TestModuleName string `bson:"test_module_name" json:"test_module_name"` - Envs []*KeyVal `bson:"envs" json:"envs"` - Builds []*types.Repository `bson:"builds" json:"builds"` -} - -type HookPayload struct { - Owner string `bson:"owner" json:"owner,omitempty"` - Repo string `bson:"repo" json:"repo,omitempty"` - Branch string `bson:"branch" json:"branch,omitempty"` - Ref string `bson:"ref" json:"ref,omitempty"` - IsPr bool `bson:"is_pr" json:"is_pr,omitempty"` - CheckRunID int64 `bson:"check_run_id" json:"check_run_id,omitempty"` - DeliveryID string `bson:"delivery_id" json:"delivery_id,omitempty"` -} - -type TargetArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - Build *BuildArgs `bson:"build" json:"build"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` - Image string `bson:"image" json:"image"` - BinFile string `bson:"bin_file" json:"bin_file"` - Envs []*KeyVal `bson:"envs" json:"envs"` - HasBuild bool `bson:"has_build" json:"has_build"` - JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` -} - -type JenkinsBuildArgs struct { - JobName string `bson:"job_name" json:"job_name"` - JenkinsBuildParams []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` -} - -type BuildArgs struct { - Repos []*types.Repository `bson:"repos" json:"repos"` -} - -type DeployEnv struct { - Env string `json:"env"` - Type string `json:"type"` - ProductName string `json:"product_name,omitempty"` -} - -type ArtifactArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image,omitempty" json:"image,omitempty"` - Deploy []DeployEnv `bson:"deploy" json:"deploy"` - WorkflowName string `bson:"workflow_name,omitempty" json:"workflow_name,omitempty"` - TaskID int64 `bson:"task_id,omitempty" json:"task_id,omitempty"` - FileName string `bson:"file_name,omitempty" json:"file_name,omitempty"` - URL string `bson:"url,omitempty" json:"url,omitempty"` -} - -type VersionArgs struct { - Enabled bool `bson:"enabled" json:"enabled"` - Version string `bson:"version" json:"version"` - Desc string `bson:"desc" json:"desc"` - Labels []string `bson:"labels" json:"labels"` -} - -type BuildModuleArgs struct { - BuildName string - Target string - ServiceName string - ProductName string - Variables []*KeyVal - Env *Product - WorkflowName string - TaskID int64 - FileName string - URL string - TaskType string -} - -func (Workflow) TableName() string { - return "workflow" -} - -func (d *DistributeStage) IsDistributeS3Enabled() bool { - if d != nil && d.Enabled { - for _, d := range d.Distributes { - if d.QstackDistribute { - return true - } - } - } - - return false -} - -//Validate validate schedule setting -func (schedule *Schedule) Validate() error { - switch schedule.Type { - case config.TimingSchedule: - if schedule.Number <= 0 { - schedule.Number = 1 - } - if err := isValidJobTime(schedule.Time); err != nil { - return err - } - if schedule.Frequency != setting.FrequencyDay && - schedule.Frequency != setting.FrequencyMondy && - schedule.Frequency != setting.FrequencyTuesday && - schedule.Frequency != setting.FrequencyWednesday && - schedule.Frequency != setting.FrequencyThursday && - schedule.Frequency != setting.FrequencyFriday && - schedule.Frequency != setting.FrequencySaturday && - schedule.Frequency != setting.FrequencySunday { - return fmt.Errorf("%s 定时任务频率错误", e.InvalidFormatErrMsg) - } - return nil - - case config.GapSchedule: - if schedule.Frequency != setting.FrequencyHours && - schedule.Frequency != setting.FrequencyMinutes { - return fmt.Errorf("%s 间隔任务频率错误", e.InvalidFormatErrMsg) - } - if schedule.Number <= 0 { - return fmt.Errorf("%s 间隔循环时间间隔不能小于等于0", e.InvalidFormatErrMsg) - } - return nil - - default: - return fmt.Errorf("%s 间隔任务模式未设置", e.InvalidFormatErrMsg) - } -} - -func isValidJobTime(t string) error { - - hour := int((t[0]-'0')*10 + (t[1] - '0')) - min := int((t[3]-'0')*10 + (t[4] - '0')) - - if hour < 0 || hour > 23 || min < 0 || min > 59 { - return fmt.Errorf("%s 时间范围: 小时[0-24] 分钟[0-59]", e.InvalidFormatErrMsg) - } - - return nil -} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go b/pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go deleted file mode 100644 index d9a528ce..00000000 --- a/pkg/cli/upgradeassistant/internal/repository/models/workflow_v3.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package models - -import ( - "github.com/koderover/zadig/pkg/types" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -type WorkflowV3 struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - ProjectName string `bson:"project_name" json:"project_name"` - Description string `bson:"description" json:"description"` - Parameters []*ParameterSetting `bson:"parameters" json:"parameters"` - SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - CreatedBy string `bson:"created_by" json:"created_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdatedBy string `bson:"updated_by" json:"updated_by"` - UpdateTime int64 `bson:"update_time" json:"update_time"` -} - -type ParameterSettingType string - -const ( - StringType ParameterSettingType = "string" - ChoiceType ParameterSettingType = "choice" - ExternalType ParameterSettingType = "external" -) - -type ParameterSetting struct { - // External type parameter will NOT use this key. - Key string `bson:"key" json:"key"` - Type ParameterSettingType `bson:"type" json:"type"` - //DefaultValue - DefaultValue string `bson:"default_value" json:"default_value"` - // choiceOption Are all options enumerated - ChoiceOption []string `bson:"choice_option" json:"choice_option"` - // ExternalSetting It is the configuration of the external system to obtain the variable - ExternalSetting *ExternalSetting `bson:"external_setting" json:"external_setting"` -} - -type ExternalSetting struct { - SystemID string `bson:"system_id" json:"system_id"` - Endpoint string `bson:"endpoint" json:"endpoint"` - Method string `bson:"method" json:"method"` - Headers []*KV `bson:"headers" json:"headers"` - Body string `bson:"body" json:"body"` - Params []*ExternalParamMapping `bson:"params" json:"params"` -} - -type KV struct { - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` -} - -type ExternalParamMapping struct { - ParamKey string `bson:"param_key" json:"param_key"` - ResponseKey string `bson:"response_key" json:"response_key"` - Display bool `bson:"display" json:"display"` -} - -type WorkflowV3Args struct { - ID string `bson:"id" json:"id"` - ProjectName string `bson:"project_name" json:"project_name"` - Name string `bson:"name" json:"name"` - Builds []*types.Repository `bson:"builds" json:"builds"` - BuildArgs []*KeyVal `bson:"build_args" json:"build_args"` -} - -func (WorkflowV3) TableName() string { - return "workflow_v3" -} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go index 0a8ae6f2..b181749d 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go @@ -19,9 +19,6 @@ package mongodb import ( "context" "errors" - "fmt" - "strings" - "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -33,21 +30,6 @@ import ( ) type BuildListOption struct { - Name string - Targets []string - ProductName string - ServiceName string - IsSort bool - BuildOS string - BasicImageID string - PrivateKeyID string -} - -type BuildFindOption struct { - Name string - Targets []string - ServiceName string - ProductName string } type BuildColl struct { @@ -61,91 +43,16 @@ func NewBuildColl() *BuildColl { return &BuildColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } -func (c *BuildColl) GetCollectionName() string { - return c.coll -} - -func (c *BuildColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.D{ - bson.E{Key: "name", Value: 1}, - bson.E{Key: "revision", Value: 1}, - }, - Options: options.Index().SetUnique(true), - } - - _, err := c.Indexes().CreateOne(ctx, mod) - - return err -} - -func (c *BuildColl) Find(opt *BuildFindOption) (*models.Build, error) { - if opt == nil { - return nil, errors.New("nil FindOption") - } - - query := bson.M{} - - if len(opt.Name) != 0 { - query["name"] = opt.Name - } - - if len(opt.Targets) > 0 { - query["targets.service_module"] = bson.M{"$in": opt.Targets} - } - - if len(opt.ServiceName) > 0 { - query["targets.service_name"] = opt.ServiceName - } - - if len(opt.ProductName) != 0 { - query["product_name"] = opt.ProductName - } - - resp := new(models.Build) - err := c.Collection.FindOne(context.TODO(), query).Decode(resp) - if err != nil { - return nil, err - } - return resp, nil -} - func (c *BuildColl) List(opt *BuildListOption) ([]*models.Build, error) { if opt == nil { return nil, errors.New("nil ListOption") } query := bson.M{} - if len(strings.TrimSpace(opt.Name)) != 0 { - query["name"] = opt.Name - } - if len(opt.Targets) > 0 { - query["targets.service_module"] = bson.M{"$in": opt.Targets} - } - - if len(opt.ServiceName) > 0 { - query["targets.service_name"] = opt.ServiceName - } - - if len(opt.ProductName) != 0 { - query["product_name"] = opt.ProductName - } - if len(opt.BuildOS) != 0 { - query["pre_build.build_os"] = opt.BuildOS - } - if len(opt.BasicImageID) != 0 { - query["pre_build.image_id"] = opt.BasicImageID - } - if len(opt.PrivateKeyID) != 0 { - query["ssh.id"] = opt.PrivateKeyID - } var resp []*models.Build ctx := context.Background() opts := options.Find() - if opt.IsSort { - opts.SetSort(bson.D{{"update_time", -1}}) - } cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { return nil, err @@ -158,123 +65,3 @@ func (c *BuildColl) List(opt *BuildListOption) ([]*models.Build, error) { return resp, nil } - -func (c *BuildColl) Delete(name, productName string) error { - query := bson.M{} - if len(name) != 0 { - query["name"] = name - } - if len(productName) != 0 { - query["product_name"] = productName - } - - if len(query) == 0 { - return nil - } - - _, err := c.DeleteMany(context.TODO(), query) - return err -} - -func (c *BuildColl) Create(build *models.Build) error { - if build == nil { - return errors.New("nil Module args") - } - - build.Name = strings.TrimSpace(build.Name) - build.UpdateTime = time.Now().Unix() - - //double check - buildModel, err := c.Find(&BuildFindOption{Name: build.Name}) - if err == nil { - return fmt.Errorf("%s%s", buildModel.ProductName, "项目中有相同的构建名称存在,请检查!") - } - - _, err = c.Collection.InsertOne(context.TODO(), build) - - return err -} - -func (c *BuildColl) Update(build *models.Build) error { - if build == nil { - return errors.New("nil Module args") - } - - query := bson.M{"name": build.Name} - if build.ProductName != "" { - query["product_name"] = build.ProductName - } - - updateBuild := bson.M{"$set": build} - - _, err := c.Collection.UpdateOne(context.TODO(), query, updateBuild) - return err -} - -func (c *BuildColl) UpdateTargets(name, productName string, targets []*models.ServiceModuleTarget) error { - query := bson.M{"name": name} - if productName != "" { - query["product_name"] = productName - } - - change := bson.M{"$set": bson.M{ - "targets": targets, - }} - _, err := c.Collection.UpdateMany(context.TODO(), query, change) - return err -} - -func (c *BuildColl) UpdateBuildParam(name, productName string, params []*models.Parameter) error { - query := bson.M{"name": name} - if productName != "" { - query["product_name"] = productName - } - - change := bson.M{"$set": bson.M{ - "pre_build.parameters": params, - }} - _, err := c.UpdateMany(context.TODO(), query, change) - return err -} - -// DistinctTargets finds modules distinct service templates -func (c *BuildColl) DistinctTargets(excludeModule []string, productName string) (map[string]bool, error) { - query := bson.M{} - - if len(excludeModule) != 0 { - query["name"] = bson.M{"$nin": excludeModule} - } - if len(productName) != 0 { - query["product_name"] = productName - } - - serviceModuleTargets, err := c.Distinct(context.TODO(), "targets", query) - if err != nil { - return nil, err - } - - resp := make(map[string]bool) - for _, serviceModuleTarget := range serviceModuleTargets { - if moduleTarget, ok := serviceModuleTarget.(models.ServiceModuleTarget); ok { - target := fmt.Sprintf("%s-%s-%s", moduleTarget.ProductName, moduleTarget.ServiceName, moduleTarget.ServiceModule) - resp[target] = true - } - } - - return resp, err -} - -func (c *BuildColl) GetDockerfileTemplateReference(templateID string) ([]*models.Build, error) { - ret := make([]*models.Build, 0) - query := bson.M{"post_build.docker_build.template_id": templateID} - - cursor, err := c.Collection.Find(context.TODO(), query) - if err != nil { - return nil, err - } - err = cursor.All(context.TODO(), &ret) - if err != nil { - return nil, err - } - return ret, nil -} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go index c427fc3e..ee420afd 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/testing.go @@ -18,10 +18,6 @@ package mongodb import ( "context" - "errors" - "fmt" - "strings" - "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -33,11 +29,6 @@ import ( ) type ListTestOption struct { - ProductName string - TestType string - IsSort bool - BuildOS string - BasicImageID string } type TestingColl struct { @@ -55,39 +46,12 @@ func (c *TestingColl) GetCollectionName() string { return c.coll } -func (c *TestingColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.M{"name": 1}, - Options: options.Index().SetUnique(true), - } - - _, err := c.Indexes().CreateOne(ctx, mod) - - return err -} - func (c *TestingColl) List(opt *ListTestOption) ([]*models.Testing, error) { query := bson.M{} - if len(opt.ProductName) > 0 { - query["product_name"] = opt.ProductName - } - if len(opt.TestType) > 0 { - query["test_type"] = opt.TestType - } - if len(opt.BuildOS) > 0 { - query["pre_test.build_os"] = opt.BuildOS - } - if len(opt.BasicImageID) != 0 { - query["pre_test.image_id"] = opt.BasicImageID - } - var resp []*models.Testing ctx := context.Background() opts := options.Find() - if opt.IsSort { - opts.SetSort(bson.D{{"update_time", -1}}) - } cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { return nil, err @@ -100,80 +64,3 @@ func (c *TestingColl) List(opt *ListTestOption) ([]*models.Testing, error) { return resp, nil } - -func (c *TestingColl) Find(name, productName string) (*models.Testing, error) { - query := bson.M{} - - query["name"] = name - - if productName != "" { - query["product_name"] = productName - } - - resp := new(models.Testing) - - err := c.FindOne(context.TODO(), query).Decode(resp) - return resp, err -} - -func (c *TestingColl) Delete(name, productName string) error { - query := bson.M{} - if name != "" { - query["name"] = name - } - if productName != "" { - query["product_name"] = productName - } - if len(query) == 0 { - return nil - } - _, err := c.DeleteMany(context.TODO(), query) - return err -} - -func (c *TestingColl) ListWithScheduleEnabled() ([]*models.Testing, error) { - resp := make([]*models.Testing, 0) - query := bson.M{"schedule_enabled": true} - - cursor, err := c.Collection.Find(context.TODO(), query) - if err != nil { - return nil, err - } - - err = cursor.All(context.TODO(), &resp) - if err != nil { - return nil, err - } - - return resp, nil -} - -func (c *TestingColl) Create(testing *models.Testing) error { - if testing == nil { - return errors.New("nil testing args") - } - - testing.Name = strings.TrimSpace(testing.Name) - testing.UpdateTime = time.Now().Unix() - - test, err := c.Find(testing.Name, "") - if err == nil { - return fmt.Errorf("%s%s", test.ProductName, "项目中有相同的测试名称存在,请检查!") - } - - _, err = c.InsertOne(context.TODO(), testing) - return err -} - -func (c *TestingColl) Update(testing *models.Testing) error { - if testing == nil { - return errors.New("nil testing args") - } - - query := bson.M{"name": testing.Name} - - change := bson.M{"$set": testing} - - _, err := c.UpdateOne(context.TODO(), query, change) - return err -} -- Gitee From 41c29217c1551a0e95a16f0609900785d5bfb605 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 27 Jan 2022 17:37:29 +0800 Subject: [PATCH 1452/2647] check whether there's nil pointer in ua Signed-off-by: zhangyifei --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index a64e441c..2b645482 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -124,7 +124,7 @@ func migrateModuleTesting() error { } func migrateOneBuild(build *internalmodels.Build) error { - if build.PreBuild.CleanWorkspace { + if build.PreBuild != nil && build.PreBuild.CleanWorkspace { return nil } @@ -142,7 +142,7 @@ func migrateOneBuild(build *internalmodels.Build) error { } func migrateOneTesting(testing *internalmodels.Testing) error { - if testing.PreTest.CleanWorkspace { + if testing.PreTest != nil && testing.PreTest.CleanWorkspace { return nil } -- Gitee From 14e32bc50ba2d0100f06f3f8c88a12a2fa68b20c Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 27 Jan 2022 18:57:18 +0800 Subject: [PATCH 1453/2647] fix style Signed-off-by: zhangyifei --- pkg/microservice/aslan/core/multicluster/service/cache.go | 2 +- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 -- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 2 ++ pkg/microservice/warpdrive/core/service/taskplugin/testing.go | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/multicluster/service/cache.go b/pkg/microservice/aslan/core/multicluster/service/cache.go index 9893e4af..52422b58 100644 --- a/pkg/microservice/aslan/core/multicluster/service/cache.go +++ b/pkg/microservice/aslan/core/multicluster/service/cache.go @@ -57,7 +57,7 @@ func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) if err != nil { return nil, fmt.Errorf("failed to list storageclasses: %s", err) } - log.Infof("StorageClass Length: %v", len(scList.Items)) + log.Infof("StorageClass Length: %d", len(scList.Items)) storageclasses := make([]string, 0, len(scList.Items)) for _, item := range scList.Items { diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index e4a0d4fa..98251195 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -80,8 +80,6 @@ func (r *Reaper) GetCacheFile() string { } func (r *Reaper) CompressCache(storageURI string) error { - log.Infof("StorageURI: %s, r.ActiveWorkspace: %s", storageURI, r.ActiveWorkspace) - err := r.EnsureActiveWorkspace(r.ActiveWorkspace) if err != nil { log.Errorf("EnsureActiveWorkspace err:%v", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 86ffe0f8..9594a2ab 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -111,6 +111,8 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe pipelineCtx.CacheEnable = false } + // TODO: Since the namespace field has been used continuously since v1.10.0, the processing logic related to namespace needs to + // be deleted in v1.11.0. p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 7e2c766a..b6e4ac04 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -105,6 +105,8 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC pipelineCtx.CacheEnable = false } + // TODO: Since the namespace field has been used continuously since v1.10.0, the processing logic related to namespace needs to + // be deleted in v1.11.0. p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace if p.Task.Namespace != "" { p.KubeNamespace = p.Task.Namespace -- Gitee From a15f9bdcb2178e818245a482502e50b21c1ce2e9 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 27 Jan 2022 20:18:31 +0800 Subject: [PATCH 1454/2647] support AWS EFS Signed-off-by: zhangyifei --- .../aslan/core/multicluster/service/constants.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go index 4629e1c0..341ca05c 100644 --- a/pkg/microservice/aslan/core/multicluster/service/constants.go +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -37,6 +37,9 @@ const ( // HuaweiCloudNASProvisioner is the provisioner of NAS storage in HuaweiCloud. HuaweiCloudNASProvisioner StorageProvisioner = "nas.csi.everest.io" + + // AWSEFSProvisioner is the provisioner of EFS storage in AWS. + AWSEFSProvisioner StorageProvisioner = "efs.csi.aws.com" ) // Note: For the convenience of users, we filter the storage types for known cloud vendors to prevent users from mistakenly selecting storage @@ -52,6 +55,9 @@ func (s StorageProvisioner) IsNFS() bool { if strings.Contains(string(s), "everest.io") && !(s == HuaweiCloudSFSProvisioner || s == HuaweiCloudNASProvisioner) { return false } + if strings.Contains(string(s), "aws.com") && s != AWSEFSProvisioner { + return false + } return true } -- Gitee From 198bcef3b80af1dea2d81ec3f3603461fbf2d30e Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 28 Jan 2022 10:54:03 +0800 Subject: [PATCH 1455/2647] optimize Signed-off-by: panxunying --- .../mongodb/collaboration_instance.go | 1 - .../service/collaboration_instance.go | 26 +++++++++++-- .../core/environment/handler/environment.go | 37 +++++++++---------- .../core/environment/service/environment.go | 6 --- .../policy/core/handler/policy_binding.go | 16 ++------ pkg/shared/client/policy/policy.go | 23 ++++++++++++ 6 files changed, 67 insertions(+), 42 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 0c746aca..197514f4 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -125,7 +125,6 @@ func (c *CollaborationInstanceColl) BulkCreate(args []*models.CollaborationInsta for _, arg := range args { arg.CreateTime = now arg.UpdateTime = now - arg.Revision = 1 } var ois []interface{} for _, obj := range args { diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index abc384f8..198f3c72 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -149,7 +149,7 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } ciwMap := make(map[string]models.WorkflowCIItem) for _, workflow := range ci.Workflows { - ciwMap[workflow.Name] = workflow + ciwMap[workflow.BaseName] = workflow } updateWorkflowItems, newWorkflowItems, deleteWorkflowItems := getUpdateWorkflowDiff(cmwMap, ciwMap) cmpMap := make(map[string]models.ProductCMItem) @@ -158,7 +158,7 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } cipMap := make(map[string]models.ProductCIItem) for _, product := range ci.Products { - cipMap[product.Name] = product + cipMap[product.BaseName] = product } updateProductItems, newProductItems, deleteProductItems := getUpdateProductDiff(cmpMap, cipMap) return UpdateItem{ @@ -324,9 +324,14 @@ func buildPolicyDescription(mode, userName string) string { return mode + " " + userName + "的权限" } -func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName string, +func buildPolicybindingName(uid, policyName, projectName string) string { + return uid + "-" + policyName + "-" + projectName +} + +func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName, uid string, logger *zap.SugaredLogger) error { var policies []*policy.Policy + var policyBindings []*policy.PolicyBinding for _, mode := range updateResp.New { var rules []*policy.Rule @@ -363,6 +368,12 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp Description: buildPolicyDescription(mode.Name, userName), Rules: rules, }) + policyBindings = append(policyBindings, &policy.PolicyBinding{ + Name: buildPolicybindingName(uid, policyName, projectName), + UID: uid, + Policy: policyName, + Public: false, + }) } err := policy.NewDefault().CreatePolicies(projectName, policy.CreatePoliciesArgs{ Policies: policies, @@ -371,6 +382,11 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp logger.Errorf("syncPolicy error, error msg:%s", err) return err } + err = policy.NewDefault().CreatePolicyBinding(projectName, policyBindings) + if err != nil { + logger.Errorf("syncPolicyBindings error, error msg:%s", err) + return err + } var updatePolicies []*policy.Policy for _, instance := range updateResp.UpdateInstance { var rules []*policy.Rule @@ -399,8 +415,10 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp } } var deletePolicies []string + var deletePolicybindings []string for _, instance := range updateResp.Delete { deletePolicies = append(deletePolicies, instance.PolicyName) + deletePolicybindings = append(deletePolicybindings, buildPolicybindingName(uid, instance.PolicyName, projectName)) } if len(deletePolicies) > 0 { err = policy.NewDefault().DeletePolicies(projectName, policy.DeletePoliciesArgs{ @@ -739,7 +757,7 @@ func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectN return err } err = syncResource(products, updateResp, projectName, identityType, userName, requestID, logger) - err = syncPolicy(updateResp, projectName, identityType, userName, logger) + err = syncPolicy(updateResp, projectName, identityType, userName, uid, logger) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 565e48fb..62ae12ba 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -34,7 +34,6 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/policy" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/shared/kube/resource" e "github.com/koderover/zadig/pkg/tool/errors" @@ -73,24 +72,24 @@ func ListProducts(c *gin.Context) { ctx.Resp = []*service.ProductResp{} return } - req := &policy.ResourcePermissionReq{ - ProjectName: projectName, - Uid: ctx.UserID, - Resources: envNames, - ResourceType: "Environment", - } - resourcePermission, err := policy.NewDefault().GetResourcePermission(req) - if err != nil { - ctx.Err = err - return - } - fmt.Println("%v", resourcePermission) - - switch c.Query(setting.Subresource) { - case setting.IngressSubresource: - ctx.Resp, ctx.Err = service.GetProductIngress(projectName, ctx.Logger) - return - } + //req := &policy.ResourcePermissionReq{ + // ProjectName: projectName, + // Uid: ctx.UserID, + // Resources: envNames, + // ResourceType: "Environment", + //} + //resourcePermission, err := policy.NewDefault().GetResourcePermission(req) + //if err != nil { + // ctx.Err = err + // return + //} + //fmt.Println("%v", resourcePermission) + // + //switch c.Query(setting.Subresource) { + //case setting.IngressSubresource: + // ctx.Resp, ctx.Err = service.GetProductIngress(projectName, ctx.Logger) + // return + //} ctx.Resp, ctx.Err = service.ListProducts(projectName, envNames, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5c441357..afb17722 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -916,12 +916,6 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc product.Namespace = projectName + "-env-" + product.EnvName product.Render.Name = product.Namespace product.ID = primitive.NilObjectID - for _, services := range product.Services { - for _, service := range services { - service.ServiceName = product.Namespace - service.Render.Name = product.Namespace - } - } err = CreateProduct(user, requestID, product, log) if err != nil { return err diff --git a/pkg/microservice/policy/core/handler/policy_binding.go b/pkg/microservice/policy/core/handler/policy_binding.go index 2cf5c15a..289c8522 100644 --- a/pkg/microservice/policy/core/handler/policy_binding.go +++ b/pkg/microservice/policy/core/handler/policy_binding.go @@ -55,18 +55,10 @@ func CreatePolicyBinding(c *gin.Context) { } args := make([]*service.PolicyBinding, 0) - if c.Query("bulk") == "true" { - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = err - return - } - } else { - rb := &service.PolicyBinding{} - if err := c.ShouldBindJSON(rb); err != nil { - ctx.Err = err - return - } - args = append(args, rb) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = err + return } ctx.Err = service.CreatePolicyBindings(projectName, args, ctx.Logger) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index fb8a9f6e..1f1257c1 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -49,6 +49,19 @@ type DeletePoliciesArgs struct { Names []string `json:"names"` } +type PolicyBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Policy string `json:"policy"` + Public bool `json:"public"` +} + +func (c *Client) CreatePolicyBinding(projectName string, policyBindings []*PolicyBinding) error { + url := fmt.Sprintf("/policybindings?projectName=%s", projectName) + _, err := c.Put(url, httpclient.SetBody(policyBindings)) + return err +} + func (c *Client) CreatePolicies(ns string, request CreatePoliciesArgs) error { url := fmt.Sprintf("/policies?projectName=%s", ns) @@ -73,6 +86,16 @@ func (c *Client) DeletePolicies(ns string, request DeletePoliciesArgs) error { return nil } +func (c *Client) DeletePolicyBindings(names []string, projectName string) error { + url := fmt.Sprintf("/policybindings/bulk-delete?projectName=%s", projectName) + nameArgs := &NameArgs{} + for _, v := range names { + nameArgs.Names = append(nameArgs.Names, v) + } + _, err := c.Post(url, httpclient.SetBody(nameArgs)) + return err +} + func (c *Client) UpdatePolicy(ns string, policy *Policy) error { url := fmt.Sprintf("/policies/%s?projectName=%s", policy.Name, ns) _, err := c.Put(url, httpclient.SetBody(policy)) -- Gitee From 966e56197875bf0e6c1d2d4c809b96b729bb9fc5 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 28 Jan 2022 11:21:45 +0800 Subject: [PATCH 1456/2647] add default cache when creating cluster Signed-off-by: zhangyifei --- .../aslan/core/multicluster/service/clusters.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index d3b6e75f..3fac6224 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -192,6 +192,21 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K ProjectNames: args.AdvancedConfig.ProjectNames, } } + + // If the user does not configure a cache, object storage is used by default. + if args.Cache.MediumType == "" { + args.Cache.MediumType = types.ObjectMedium + + defaultStorage, err := commonrepo.NewS3StorageColl().FindDefault() + if err != nil { + return nil, fmt.Errorf("failed to find default object storage: %s", err) + } + + args.Cache.ObjectProperties = types.ObjectProperties{ + ID: defaultStorage.ID.Hex(), + } + } + cluster := &commonmodels.K8SCluster{ Name: args.Name, Description: args.Description, @@ -201,6 +216,7 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K Provider: args.Provider, CreatedAt: args.CreatedAt, CreatedBy: args.CreatedBy, + Cache: args.Cache, } return s.CreateCluster(cluster, args.ID, logger) -- Gitee From 182f70d33c38f02ddbafdfc853872dabf83de6ae Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 28 Jan 2022 15:24:22 +0800 Subject: [PATCH 1457/2647] support for getting logs of abnormal Pods on EKS Signed-off-by: zhangyifei --- .../warpdrive/core/service/taskplugin/job.go | 2 ++ pkg/tool/kube/containerlog/log.go | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index cf8505b5..bd518099 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -521,6 +521,8 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa }, VolumeMounts: getVolumeMounts(ctx), Resources: getResourceRequirements(resReq, resReqSpec), + + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, }, }, Volumes: getVolumes(jobName), diff --git a/pkg/tool/kube/containerlog/log.go b/pkg/tool/kube/containerlog/log.go index 305c97c3..0e9d320b 100644 --- a/pkg/tool/kube/containerlog/log.go +++ b/pkg/tool/kube/containerlog/log.go @@ -18,15 +18,30 @@ package containerlog import ( "context" + "fmt" "io" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/log" ) func GetContainerLogs(namespace, podName, containerName string, follow bool, tailLines int64, out io.Writer, clientset kubernetes.Interface) error { readCloser, err := GetContainerLogStream(context.TODO(), namespace, podName, containerName, follow, tailLines, clientset) if err != nil { + log.Warnf("Failed to get pod log from stream: %s. Try to get logs from pod object.", err) + + // For Serverless K8s, we may not be able to get logs from Pod in the Failed state, but we can configure + // `container.terminationMessagePolicy=FallbackToLogsOnError` to get the latest exception information from the Pod Object. + pod, err := clientset.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get pod %s in %s: %s", podName, namespace, err) + } + + _, err = out.Write([]byte(pod.Status.ContainerStatuses[0].State.Terminated.Message)) + return err } -- Gitee From f6ae848fd4b0132e1ec35ee123316d2c68358366 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 28 Jan 2022 16:19:25 +0800 Subject: [PATCH 1458/2647] add helm client logic Signed-off-by: allenshen --- pkg/tool/helmclient/helmclient.go | 239 +++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 1 deletion(-) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 93a2a0fd..58051199 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -17,11 +17,21 @@ limitations under the License. package helmclient import ( + "context" "encoding/json" "fmt" "strings" + "helm.sh/helm/v3/pkg/downloader" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/storage/driver" + hc "github.com/mittwald/go-helm-client" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/strvals" "k8s.io/client-go/rest" "sigs.k8s.io/yaml" @@ -30,15 +40,27 @@ import ( yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) +type HelmClient struct { + *hc.HelmClient +} + // NewClientFromRestConf returns a new Helm client constructed with the provided REST config options func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error) { - return hc.NewClientFromRestConf(&hc.RestConfClientOptions{ + hcClient, err := hc.NewClientFromRestConf(&hc.RestConfClientOptions{ Options: &hc.Options{ Namespace: ns, DebugLog: log.Debugf, }, RestConfig: restConfig, }) + if err != nil { + return nil, err + } + + helmClient := hcClient.(*hc.HelmClient) + return &HelmClient{ + helmClient, + }, nil } type KV struct { @@ -82,3 +104,218 @@ func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues } return string(bs), nil } + +// check weather to install or upgrade chart by current status +// return error if neither install nor upgrade action is legal +func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) { + // find history of particular release + releases, err := hClient.ListReleaseHistory(spec.ReleaseName, 10) + if err != nil && err != driver.ErrReleaseNotFound { + log.Infof("###### get release history fail") + return false, err + } + // release not found, install operation + if len(releases) == 0 { + return true, nil + } + + releaseutil.Reverse(releases, releaseutil.SortByRevision) + lastRelease := releases[0] + + // pending status + if lastRelease.Info.Status.IsPending() { + return false, fmt.Errorf("another operation (install/upgrade/rollback) is in progress, please try later") + } + + // release with deployed/failed/superseded status: normal upgrade operation + if lastRelease.Info.Status == release.StatusDeployed || lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded { + return false, nil + } + + // find deployed revision with status deployed from history, would be upgrade operation + for _, rel := range releases { + if rel.Info.Status == release.StatusDeployed { + return false, nil + } + } + + // if replace set to true, install will be a legal operation + if st := lastRelease.Info.Status; spec.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) { + return true, nil + } + + return false, fmt.Errorf("can't install or upgrade chart with status: %s", lastRelease.Info.Status) +} + +// getChart returns a chart matching the provided chart name and options. +func (hClient *HelmClient) getChart(chartName string, chartPathOptions *action.ChartPathOptions) (*chart.Chart, string, error) { + + chartPath, err := chartPathOptions.LocateChart(chartName, hClient.HelmClient.Settings) + if err != nil { + return nil, "", err + } + + helmChart, err := loader.Load(chartPath) + if err != nil { + return nil, "", err + } + + if helmChart.Metadata.Deprecated { + hClient.HelmClient.DebugLog("WARNING: This chart (%q) is deprecated", helmChart.Metadata.Name) + } + + return helmChart, chartPath, err +} + +func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { + + c := hClient.HelmClient + client := action.NewInstall(c.ActionConfig) + mergeInstallOptions(spec, client) + + if client.Version == "" { + client.Version = ">0.0.0-0" + } + + helmChart, chartPath, err := hClient.getChart(spec.ChartName, &client.ChartPathOptions) + if err != nil { + return nil, err + } + + if helmChart.Metadata.Type != "" && helmChart.Metadata.Type != "application" { + return nil, fmt.Errorf( + "chart %q has an unsupported type and is not installable: %q", + helmChart.Metadata.Name, + helmChart.Metadata.Type, + ) + } + + if req := helmChart.Metadata.Dependencies; req != nil { + if err := action.CheckDependencies(helmChart, req); err != nil { + if client.DependencyUpdate { + man := &downloader.Manager{ + ChartPath: chartPath, + Keyring: client.ChartPathOptions.Keyring, + SkipUpdate: false, + Getters: c.Providers, + RepositoryConfig: c.Settings.RepositoryConfig, + RepositoryCache: c.Settings.RepositoryCache, + } + if err := man.Update(); err != nil { + return nil, err + } + } else { + return nil, err + } + } + } + + values, err := spec.GetValuesMap() + if err != nil { + return nil, err + } + + rel, err := client.RunWithContext(ctx, helmChart, values) + if err != nil { + return rel, err + } + + c.DebugLog("release installed successfully: %s/%s-%s", rel.Name, rel.Chart.Metadata.Name, rel.Chart.Metadata.Version) + + return rel, nil +} + +func (hClient *HelmClient) upgradeChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { + + c := hClient.HelmClient + client := action.NewUpgrade(c.ActionConfig) + mergeUpgradeOptions(spec, client) + + if client.Version == "" { + client.Version = ">0.0.0-0" + } + + helmChart, _, err := hClient.getChart(spec.ChartName, &client.ChartPathOptions) + if err != nil { + return nil, err + } + + if req := helmChart.Metadata.Dependencies; req != nil { + if err := action.CheckDependencies(helmChart, req); err != nil { + return nil, err + } + } + + values, err := spec.GetValuesMap() + if err != nil { + return nil, err + } + + if !spec.SkipCRDs && spec.UpgradeCRDs { + c.DebugLog("upgrading crds") + err = c.upgradeCRDs(ctx, helmChart) + if err != nil { + return nil, err + } + } + + rel, err := client.RunWithContext(ctx, spec.ReleaseName, helmChart, values) + if err != nil { + return rel, err + } + + c.DebugLog("release upgraded successfully: %s/%s-%s", rel.Name, rel.Chart.Metadata.Name, rel.Chart.Metadata.Version) + + return rel, nil +} + +// InstallOrUpgradeChart install or upgrade helm chart, use the same rule with helm to determine weather to install or upgrade +func (hClient *HelmClient) InstallOrUpgradeChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { + install, err := hClient.isInstallOperation(spec) + if err != nil { + return nil, err + } + + if install { + return hClient.installChart(ctx, spec) + } else { + return hClient.InstallOrUpgradeChart(ctx, spec) + } +} + +// mergeInstallOptions merges values of the provided chart to helm install options used by the client. +func mergeInstallOptions(chartSpec *hc.ChartSpec, installOptions *action.Install) { + installOptions.CreateNamespace = chartSpec.CreateNamespace + installOptions.DisableHooks = chartSpec.DisableHooks + installOptions.Replace = chartSpec.Replace + installOptions.Wait = chartSpec.Wait + installOptions.DependencyUpdate = chartSpec.DependencyUpdate + installOptions.Timeout = chartSpec.Timeout + installOptions.Namespace = chartSpec.Namespace + installOptions.ReleaseName = chartSpec.ReleaseName + installOptions.Version = chartSpec.Version + installOptions.GenerateName = chartSpec.GenerateName + installOptions.NameTemplate = chartSpec.NameTemplate + installOptions.Atomic = chartSpec.Atomic + installOptions.SkipCRDs = chartSpec.SkipCRDs + installOptions.DryRun = chartSpec.DryRun + installOptions.SubNotes = chartSpec.SubNotes +} + +// mergeUpgradeOptions merges values of the provided chart to helm upgrade options used by the client. +func mergeUpgradeOptions(chartSpec *hc.ChartSpec, upgradeOptions *action.Upgrade) { + upgradeOptions.Version = chartSpec.Version + upgradeOptions.Namespace = chartSpec.Namespace + upgradeOptions.Timeout = chartSpec.Timeout + upgradeOptions.Wait = chartSpec.Wait + upgradeOptions.DisableHooks = chartSpec.DisableHooks + upgradeOptions.Force = chartSpec.Force + upgradeOptions.ResetValues = chartSpec.ResetValues + upgradeOptions.ReuseValues = chartSpec.ReuseValues + upgradeOptions.Recreate = chartSpec.Recreate + upgradeOptions.MaxHistory = chartSpec.MaxHistory + upgradeOptions.Atomic = chartSpec.Atomic + upgradeOptions.CleanupOnFail = chartSpec.CleanupOnFail + upgradeOptions.DryRun = chartSpec.DryRun + upgradeOptions.SubNotes = chartSpec.SubNotes +} -- Gitee From ec6eab46352f4fb392a1ac4430f14f301dc9c097 Mon Sep 17 00:00:00 2001 From: mouuii Date: Sat, 29 Jan 2022 12:24:00 +0800 Subject: [PATCH 1459/2647] policy ref Signed-off-by: mouuii --- .../policy/core/service/bundle/opa_bundle.go | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index de57837b..6c65e5c2 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -75,8 +75,8 @@ type opaPolicies struct { } type opaRoleBindings struct { - RoleBindings roleBindings `json:"role_bindings"` - PolicyBindings roleBindings `json:"policy_bindings"` + RoleBindings roleBindings `json:"role_bindings"` + PolicyBindings policyBindings `json:"policy_bindings"` } type role struct { @@ -151,11 +151,21 @@ type roleBinding struct { Bindings bindings `json:"bindings"` } +type policyBinding struct { + UID string `json:"uid"` + Bindings bindingPolicys `json:"bindings"` +} + type binding struct { Namespace string `json:"namespace"` RoleRefs roleRefs `json:"role_refs"` } +type bindingPolicy struct { + Namespace string `json:"namespace"` + RoleRefs roleRefs `json:"policy_refs"` +} + type roleRef struct { Name string `json:"name"` Namespace string `json:"namespace"` @@ -194,6 +204,14 @@ func (o bindings) Less(i, j int) bool { return o[i].Namespace < o[j].Namespace } +type bindingPolicys []*bindingPolicy + +func (o bindingPolicys) Len() int { return len(o) } +func (o bindingPolicys) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o bindingPolicys) Less(i, j int) bool { + return o[i].Namespace < o[j].Namespace +} + type roleRefs []*roleRef func (o roleRefs) Len() int { return len(o) } @@ -213,6 +231,14 @@ func (o roleBindings) Less(i, j int) bool { return o[i].UID < o[j].UID } +type policyBindings []*policyBinding + +func (o policyBindings) Len() int { return len(o) } +func (o policyBindings) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o policyBindings) Less(i, j int) bool { + return o[i].UID < o[j].UID +} + func generateOPARoles(roles []*models.Role, policies []*models.PolicyMeta) *opaRoles { data := &opaRoles{} resourceMappings := getResourceActionMappings(policies) @@ -279,7 +305,7 @@ func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.Policy return data } -func generateOPARoleBindings(rbs []*models.RoleBinding, pbs []*models.PolicyBinding) *opaRoleBindings { +func generateOPABindings(rbs []*models.RoleBinding, pbs []*models.PolicyBinding) *opaRoleBindings { data := &opaRoleBindings{} userRoleMap := make(map[string]map[string][]*roleRef) @@ -321,13 +347,13 @@ func generateOPARoleBindings(rbs []*models.RoleBinding, pbs []*models.PolicyBind } for u, nb := range userPolicyMap { - var bindingsData []*binding + var bindingsData []*bindingPolicy for n, b := range nb { sort.Sort(roleRefs(b)) - bindingsData = append(bindingsData, &binding{Namespace: n, RoleRefs: b}) + bindingsData = append(bindingsData, &bindingPolicy{Namespace: n, RoleRefs: b}) } - sort.Sort(bindings(bindingsData)) - data.PolicyBindings = append(data.PolicyBindings, &roleBinding{UID: u, Bindings: bindingsData}) + sort.Sort(bindingPolicys(bindingsData)) + data.PolicyBindings = append(data.PolicyBindings, &policyBinding{UID: u, Bindings: bindingsData}) } sort.Sort(data.PolicyBindings) @@ -422,7 +448,7 @@ func GenerateOPABundle() error { {Data: generateOPAPolicyRego(), Path: policyRegoPath}, {Data: generateOPARoles(rs, pms), Path: rolesPath}, {Data: generateOPAPolicies(policies, pms), Path: policiesPath}, - {Data: generateOPARoleBindings(bs, pbs), Path: bindingsPath}, + {Data: generateOPABindings(bs, pbs), Path: bindingsPath}, {Data: generateOPAExemptionURLs(pms), Path: exemptionsPath}, {Data: generateResourceBundle(), Path: resourcesPath}, }, -- Gitee From fe4913f092b0e0eab525141fc093d6b10f54786d Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sat, 29 Jan 2022 15:14:24 +0800 Subject: [PATCH 1460/2647] improve cache strategy Signed-off-by: zhangyifei --- .../aslan/core/common/service/s3/s3.go | 8 +++ .../aslan/core/multicluster/service/cache.go | 4 +- .../core/multicluster/service/clusters.go | 56 +++++++++++-------- .../core/multicluster/service/constants.go | 3 + .../service/workflow/pipeline_task.go | 13 +++-- .../service/workflow/workflow_task.go | 13 +++-- 6 files changed, 62 insertions(+), 35 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/s3/s3.go b/pkg/microservice/aslan/core/common/service/s3/s3.go index f7a71dfb..e3381f24 100644 --- a/pkg/microservice/aslan/core/common/service/s3/s3.go +++ b/pkg/microservice/aslan/core/common/service/s3/s3.go @@ -126,6 +126,14 @@ func (s *S3) Validate() error { return errors.New("required field is missing") } + defaultStorage, err := FindDefaultS3() + if err != nil { + return fmt.Errorf("failed to find default object storage: %s", err) + } + if s.ID == defaultStorage.ID && !s.IsDefault { + return errors.New("current storage is default and a default object storage must be set") + } + return nil } diff --git a/pkg/microservice/aslan/core/multicluster/service/cache.go b/pkg/microservice/aslan/core/multicluster/service/cache.go index 52422b58..aa7bf8bf 100644 --- a/pkg/microservice/aslan/core/multicluster/service/cache.go +++ b/pkg/microservice/aslan/core/multicluster/service/cache.go @@ -47,9 +47,7 @@ func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) // For cluster-level resources, we need to explicitly configure the namespace to be empty. namespace = "" default: - if namespace != AttachedClusterNamespace { - return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, AttachedClusterNamespace) - } + namespace = AttachedClusterNamespace } scList := &storagev1.StorageClassList{} diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 3fac6224..8c6ecc43 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -193,18 +193,10 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K } } - // If the user does not configure a cache, object storage is used by default. - if args.Cache.MediumType == "" { - args.Cache.MediumType = types.ObjectMedium - - defaultStorage, err := commonrepo.NewS3StorageColl().FindDefault() - if err != nil { - return nil, fmt.Errorf("failed to find default object storage: %s", err) - } - - args.Cache.ObjectProperties = types.ObjectProperties{ - ID: defaultStorage.ID.Hex(), - } + // If user does not configure a cache for the cluster, object storage is used by default. + err := setClusterCache(args) + if err != nil { + return nil, fmt.Errorf("failed to set cache for cluster %s: %s", args.ID, err) } cluster := &commonmodels.K8SCluster{ @@ -245,18 +237,10 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com } } - // If the user does not configure a cache, object storage is used by default. - if args.Cache.MediumType == "" { - args.Cache.MediumType = types.ObjectMedium - - defaultStorage, err := commonrepo.NewS3StorageColl().FindDefault() - if err != nil { - return nil, fmt.Errorf("failed to find default object storage: %s", err) - } - - args.Cache.ObjectProperties = types.ObjectProperties{ - ID: defaultStorage.ID.Hex(), - } + // If user does not configure a cache for the cluster, object storage is used by default. + err := setClusterCache(args) + if err != nil { + return nil, fmt.Errorf("failed to set cache for cluster %s: %s", id, err) } // If the user chooses to use dynamically generated storage resources, the system automatically creates the PVC. @@ -378,3 +362,27 @@ func GetYaml(id, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ( return s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), hubURI, useDeployment, logger) } + +func setClusterCache(args *K8SCluster) error { + if args.Cache.MediumType != "" { + return nil + } + + defaultStorage, err := commonrepo.NewS3StorageColl().FindDefault() + if err != nil { + return fmt.Errorf("failed to find default object storage for cluster %s: %s", args.ID, err) + } + + // Since the attached cluster cannot access the minio in the local cluster, if the default object storage + // is minio, the attached cluster cannot be configured to use minio as a cache. + if args.ID != setting.LocalClusterID && strings.Contains(defaultStorage.Endpoint, ZadigMinioSVC) { + return nil + } + + args.Cache.MediumType = types.ObjectMedium + args.Cache.ObjectProperties = types.ObjectProperties{ + ID: defaultStorage.ID.Hex(), + } + + return nil +} diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go index 341ca05c..f7d3b123 100644 --- a/pkg/microservice/aslan/core/multicluster/service/constants.go +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -61,3 +61,6 @@ func (s StorageProvisioner) IsNFS() bool { return true } + +// ZadigMinioSVC is the service name of minio. +const ZadigMinioSVC = "zadig-minio" diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 8da4cf68..365a06c5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -664,16 +664,21 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testArg.TestModuleName = args.TestName - testTask.CacheEnable = testing.CacheEnable - testTask.CacheDirType = testing.CacheDirType - testTask.CacheUserDir = testing.CacheUserDir - clusterInfo, err := commonrepo.NewK8SClusterColl().Get(testing.PreTest.ClusterID) if err != nil { return resp, e.ErrListTestModule.AddDesc(err.Error()) } testTask.Cache = clusterInfo.Cache + // If the cluster is not configured with a cache medium, the cache cannot be used, so don't enable cache explicitly. + if testTask.Cache.MediumType == "" { + testTask.CacheEnable = false + } else { + testTask.CacheEnable = testing.CacheEnable + testTask.CacheDirType = testing.CacheDirType + testTask.CacheUserDir = testing.CacheUserDir + } + break } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 1d02a035..e9f33926 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1856,11 +1856,16 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } - build.Cache = clusterInfo.Cache - build.CacheEnable = module.CacheEnable - build.CacheDirType = module.CacheDirType - build.CacheUserDir = module.CacheUserDir + + // If the cluster is not configured with a cache medium, the cache cannot be used, so don't enable cache explicitly. + if build.Cache.MediumType == "" { + build.CacheEnable = false + } else { + build.CacheEnable = module.CacheEnable + build.CacheDirType = module.CacheDirType + build.CacheUserDir = module.CacheUserDir + } if args.TaskType != "" { build.TaskType = config.TaskArtifactDeploy -- Gitee From eac89e3f6d6309d0f741b05b56a9afd835a89879 Mon Sep 17 00:00:00 2001 From: panxunying Date: Sat, 29 Jan 2022 16:07:00 +0800 Subject: [PATCH 1461/2647] optimize Signed-off-by: panxunying --- .../mongodb/collaboration_instance.go | 2 +- .../service/collaboration_instance.go | 141 +++++++++++------- .../policy/core/service/bundle/opa_bundle.go | 2 +- pkg/shared/client/policy/policy.go | 2 +- 4 files changed, 90 insertions(+), 57 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 197514f4..72b9d47a 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -104,7 +104,7 @@ func (c *CollaborationInstanceColl) Update(uid string, args *models.Collaboratio return errors.New("nil CollaborationMode") } - query := bson.M{"name": args.CollaborationName, "project_name": args.ProjectName, "user_uid": uid} + query := bson.M{"collaboration_name": args.CollaborationName, "project_name": args.ProjectName, "user_uid": uid} change := bson.M{"$set": bson.M{ "update_time": time.Now().Unix(), "workflows": args.Workflows, diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 198f3c72..500ce2c0 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -50,13 +50,13 @@ type NewSpec struct { } type UpdateWorkflowItem struct { - Old models.WorkflowCMItem `json:"old"` - New models.WorkflowCIItem `json:"new"` + Old models.WorkflowCIItem `json:"old"` + New models.WorkflowCMItem `json:"new"` } type UpdateProductItem struct { - Old models.ProductCMItem `json:"old"` - New models.ProductCIItem `json:"new"` + Old models.ProductCIItem `json:"old"` + New models.ProductCMItem `json:"new"` } type Workflow struct { @@ -97,8 +97,8 @@ func getUpdateWorkflowDiff(cmwMap map[string]models.WorkflowCMItem, ciwMap map[s if ci, ok := ciwMap[name]; ok { if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { updateWorkflowItems = append(updateWorkflowItems, UpdateWorkflowItem{ - Old: cm, - New: ci, + Old: ci, + New: cm, }) } } else { @@ -106,10 +106,8 @@ func getUpdateWorkflowDiff(cmwMap map[string]models.WorkflowCMItem, ciwMap map[s } } for name, ci := range ciwMap { - if cm, ok := cmwMap[name]; ok { - if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { - deleteWorkflowItems = append(deleteWorkflowItems, ci) - } + if _, ok := cmwMap[name]; !ok { + deleteWorkflowItems = append(deleteWorkflowItems, ci) } } return updateWorkflowItems, newWorkflowItems, deleteWorkflowItems @@ -124,8 +122,8 @@ func getUpdateProductDiff(cmpMap map[string]models.ProductCMItem, cipMap map[str if ci, ok := cipMap[name]; ok { if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { updateProductItems = append(updateProductItems, UpdateProductItem{ - Old: cm, - New: ci, + Old: ci, + New: cm, }) } } else { @@ -133,10 +131,8 @@ func getUpdateProductDiff(cmpMap map[string]models.ProductCMItem, cipMap map[str } } for name, ci := range cipMap { - if cm, ok := cmpMap[name]; ok { - if !reflect.DeepEqual(cm.Verbs, ci.Verbs) || cm.CollaborationType != ci.CollaborationType { - deleteProductItems = append(deleteProductItems, ci) - } + if _, ok := cmpMap[name]; !ok { + deleteProductItems = append(deleteProductItems, ci) } } return updateProductItems, newProductItems, deleteProductItems @@ -295,13 +291,15 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, identityT instances = append(instances, instance) modeInstanceMap[mode.Name] = instance } - err := mongodb.NewCollaborationInstanceColl().BulkCreate(instances) - if err != nil { - logger.Errorf("syncInstance BulkCreate error, error msg:%s", err) - return err + if len(instances) > 0 { + err := mongodb.NewCollaborationInstanceColl().BulkCreate(instances) + if err != nil { + logger.Errorf("syncInstance BulkCreate error, error msg:%s", err) + return err + } } for _, instance := range updateResp.UpdateInstance { - err = mongodb.NewCollaborationInstanceColl().Update(uid, &instance) + err := mongodb.NewCollaborationInstanceColl().Update(uid, &instance) if err != nil { logger.Errorf("syncInstance Update error, error msg:%s", err) return err @@ -375,17 +373,21 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp Public: false, }) } - err := policy.NewDefault().CreatePolicies(projectName, policy.CreatePoliciesArgs{ - Policies: policies, - }) - if err != nil { - logger.Errorf("syncPolicy error, error msg:%s", err) - return err + if len(policies) > 0 { + err := policy.NewDefault().CreatePolicies(projectName, policy.CreatePoliciesArgs{ + Policies: policies, + }) + if err != nil { + logger.Errorf("syncPolicy error, error msg:%s", err) + return err + } } - err = policy.NewDefault().CreatePolicyBinding(projectName, policyBindings) - if err != nil { - logger.Errorf("syncPolicyBindings error, error msg:%s", err) - return err + if len(policyBindings) > 0 { + err := policy.NewDefault().CreatePolicyBinding(projectName, policyBindings) + if err != nil { + logger.Errorf("syncPolicyBindings error, error msg:%s", err) + return err + } } var updatePolicies []*policy.Policy for _, instance := range updateResp.UpdateInstance { @@ -403,13 +405,13 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp }, }) } - policies = append(policies, &policy.Policy{ + updatePolicies = append(updatePolicies, &policy.Policy{ Name: instance.PolicyName, Rules: rules, }) } for _, updatePolicy := range updatePolicies { - err = policy.NewDefault().UpdatePolicy(projectName, updatePolicy) + err := policy.NewDefault().UpdatePolicy(projectName, updatePolicy) if err != nil { return err } @@ -421,13 +423,19 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp deletePolicybindings = append(deletePolicybindings, buildPolicybindingName(uid, instance.PolicyName, projectName)) } if len(deletePolicies) > 0 { - err = policy.NewDefault().DeletePolicies(projectName, policy.DeletePoliciesArgs{ + err := policy.NewDefault().DeletePolicies(projectName, policy.DeletePoliciesArgs{ Names: deletePolicies, }) if err != nil { return err } } + if len(deletePolicybindings) > 0 { + err := policy.NewDefault().DeletePolicyBindings(deletePolicybindings, projectName) + if err != nil { + return err + } + } return nil } @@ -686,6 +694,9 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol if err != nil { return err } + if newResp.Workflow == nil && newResp.Product == nil { + return nil + } var newWorkflows []workflowservice.WorkflowCopyItem for _, workflow := range newResp.Workflow { if workflow.CollaborationType == config.CollaborationNew { @@ -696,12 +707,15 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol }) } } - err = workflowservice.BulkCopyWorkflow(workflowservice.BulkCopyWorkflowArgs{ - Items: newWorkflows, - }, userName, logger) - if err != nil { - return err + if len(newWorkflows) > 0 { + err = workflowservice.BulkCopyWorkflow(workflowservice.BulkCopyWorkflowArgs{ + Items: newWorkflows, + }, userName, logger) + if err != nil { + return err + } } + productMap := make(map[string]Product) for _, product := range products.Products { productMap[product.BaseName] = product @@ -727,17 +741,21 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol } } } - err = service2.BulkCopyYamlProduct(projectName, userName, requestID, service2.CopyYamlProductArg{ - Items: yamlProductItems, - }, logger) - if err != nil { - return err + if len(yamlProductItems) > 0 { + err = service2.BulkCopyYamlProduct(projectName, userName, requestID, service2.CopyYamlProductArg{ + Items: yamlProductItems, + }, logger) + if err != nil { + return err + } } - err = service2.BulkCopyHelmProduct(projectName, userName, requestID, service2.CopyHelmProductArg{ - Items: helmProductArgs, - }, logger) - if err != nil { - return err + if len(helmProductArgs) > 0 { + err = service2.BulkCopyHelmProduct(projectName, userName, requestID, service2.CopyHelmProductArg{ + Items: helmProductArgs, + }, logger) + if err != nil { + return err + } } return nil } @@ -752,6 +770,9 @@ func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectN logger.Errorf("GetCollaborationNew error, err msg:%s", err) return err } + if updateResp.Update == nil && updateResp.UpdateInstance == nil && updateResp.New == nil && updateResp.Delete == nil { + return nil + } err = syncInstance(updateResp, projectName, identityType, userName, uid, logger) if err != nil { return err @@ -783,6 +804,18 @@ func getCollaborationDelete(updateResp *GetCollaborationUpdateResp) *GetCollabor for _, deleteProduct := range item.DeleteSpec.Products { productSet.Insert(deleteProduct.Name) } + for _, workflow := range item.UpdateSpec.Workflows { + if workflow.Old.CollaborationType == config.CollaborationNew && + workflow.New.CollaborationType == config.CollaborationShare { + workflowSet.Insert(workflow.Old.Name) + } + } + for _, product := range item.UpdateSpec.Products { + if product.Old.CollaborationType == config.CollaborationNew && + product.New.CollaborationType == config.CollaborationShare { + workflowSet.Insert(product.Old.Name) + } + } } return &GetCollaborationDeleteResp{ Workflows: workflowSet.List(), @@ -854,9 +887,9 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id if workflow.Old.CollaborationType == "share" && workflow.New.CollaborationType == "new" { newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.New.CollaborationType, - BaseName: workflow.New.BaseName, + BaseName: workflow.Old.BaseName, CollaborationMode: item.CollaborationMode, - Name: buildName(workflow.New.BaseName, item.CollaborationMode, identityType, userName), + Name: buildName(workflow.Old.BaseName, item.CollaborationMode, identityType, userName), }) } } @@ -864,12 +897,12 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id if product.Old.CollaborationType == "share" && product.New.CollaborationType == "new" { newProduct = append(newProduct, &Product{ CollaborationType: product.New.CollaborationType, - BaseName: product.New.BaseName, + BaseName: product.Old.BaseName, CollaborationMode: item.CollaborationMode, DeployType: item.DeployType, - Name: buildName(product.New.BaseName, item.CollaborationMode, identityType, userName), + Name: buildName(product.Old.BaseName, item.CollaborationMode, identityType, userName), }) - newProductName.Insert(product.New.BaseName) + newProductName.Insert(product.Old.BaseName) } } } diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 6c65e5c2..ab81cc8b 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -37,7 +37,7 @@ const ( exemptionsPath = "exemptions/data.json" resourcesPath = "resources/data.json" - policyRoot = "rbac" + policyRoot = "policies" rolesRoot = "roles" rolebindingsRoot = "bindings" exemptionsRoot = "exemptions" diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 1f1257c1..4d52df01 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -58,7 +58,7 @@ type PolicyBinding struct { func (c *Client) CreatePolicyBinding(projectName string, policyBindings []*PolicyBinding) error { url := fmt.Sprintf("/policybindings?projectName=%s", projectName) - _, err := c.Put(url, httpclient.SetBody(policyBindings)) + _, err := c.Post(url, httpclient.SetBody(policyBindings)) return err } -- Gitee From 3c00e81b64457f440dff82ca55d0e3ab2cd49128 Mon Sep 17 00:00:00 2001 From: mouuii Date: Sat, 29 Jan 2022 16:28:13 +0800 Subject: [PATCH 1462/2647] policy ref Signed-off-by: mouuii --- .../core/service/bundle/resource_endpoint_mapping.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index 075c6bea..d4fcb559 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -38,7 +38,17 @@ func (m resourceActionMappings) GetRules(resource string, actions []string) []*r var res []*rule for action, r := range mappings { if all || actionSet.Has(action) { - res = append(res, r...) + for _, rr := range r { + rrr := &rule{ + Method: rr.Method, + Endpoint: rr.Endpoint, + ResourceType: rr.ResourceType, + IDRegex: rr.IDRegex, + MatchAttributes: rr.MatchAttributes, + MatchExpressions: rr.MatchExpressions, + } + res = append(res, rrr) + } } } -- Gitee From f29668cdfa54585c5ddc5630e894dbfbc94f5115 Mon Sep 17 00:00:00 2001 From: mouuii Date: Sat, 29 Jan 2022 16:32:24 +0800 Subject: [PATCH 1463/2647] bug fix Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 6c65e5c2..5e34b54e 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -277,6 +277,7 @@ func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.Policy resourceMappings := getResourceActionMappings(policyMetas) for _, policy := range policies { + //TODO - mouuii change role model to policy model opaRole := &role{Name: policy.Name, Namespace: policy.Namespace} for _, r := range policy.Rules { if r.Kind == models.KindResource { -- Gitee From 8a1e83d4c562670b7db2279fbeba13e048bf4c48 Mon Sep 17 00:00:00 2001 From: panxunying Date: Sat, 29 Jan 2022 17:25:04 +0800 Subject: [PATCH 1464/2647] optimize Signed-off-by: panxunying --- .../service/collaboration_instance.go | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 500ce2c0..c672dab1 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -447,17 +447,23 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildPolicyName(projectName, mode.Name, identityType, userName), + Type: "system", }) } - labelResp, err := service.CreateLabels(&service.CreateLabelsArgs{ - Labels: labels, - }, userName) + var labelResp *service.CreateLabelsResp + var err error + if len(labels) > 0 { + labelResp, err = service.CreateLabels(&service.CreateLabelsArgs{ + Labels: labels, + }, userName) - if err != nil { - logger.Errorf("create labels error, error msg:%s", err) - return err + if err != nil { + logger.Errorf("create labels error, error msg:%s", err) + return err + } } + var labelModels []mongodb2.Label for _, item := range updateResp.Update { labelModels = append(labelModels, mongodb2.Label{ @@ -478,7 +484,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType if err != nil { return err } - if labelListResp != nil { + if labelListResp == nil { return fmt.Errorf("label not exist %v", labelModels) } if len(labelListResp.Labels) != len(labelModels) { -- Gitee From 485ed3475039c83f3d1f942d85ee0dad35007949 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sat, 29 Jan 2022 18:48:20 +0800 Subject: [PATCH 1465/2647] support parse envs in cache dir Signed-off-by: zhangyifei --- .../reaper/core/service/meta/types.go | 1 + .../reaper/core/service/reaper/reaper.go | 79 +++++++++++++------ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 60f4bd61..55fabd40 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -86,6 +86,7 @@ type Context struct { // Git Github/Gitlab 配置 Git *Git `yaml:"git"` + // TODO: Deprecated. // Caches Caches配置 Caches []string `yaml:"caches"` diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 98251195..4b981b48 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -47,6 +47,7 @@ type Reaper struct { Ctx *meta.Context StartTime time.Time ActiveWorkspace string + UserEnvs map[string]string cm CacheManager dogFeed bool } @@ -72,6 +73,26 @@ func NewReaper() (*Reaper, error) { cm: NewTarCacheManager(ctx.StorageURI, ctx.PipelineName, ctx.ServiceName, ctx.AesKey), } + workspace := "/workspace" + if reaper.Ctx.ClassicBuild { + workspace = reaper.Ctx.Workspace + } + err = reaper.EnsureActiveWorkspace(workspace) + if err != nil { + return nil, fmt.Errorf("failed to ensure active workspace `%s`: %s", workspace, err) + } + + userEnvs := reaper.getUserEnvs() + reaper.UserEnvs = make(map[string]string, len(userEnvs)) + for _, env := range userEnvs { + items := strings.Split(env, "=") + if len(items) != 2 { + continue + } + + reaper.UserEnvs[items[0]] = items[1] + } + return reaper, nil } @@ -80,15 +101,10 @@ func (r *Reaper) GetCacheFile() string { } func (r *Reaper) CompressCache(storageURI string) error { - err := r.EnsureActiveWorkspace(r.ActiveWorkspace) - if err != nil { - log.Errorf("EnsureActiveWorkspace err:%v", err) - return err - } - - cacheDir := "/workspace" + cacheDir := r.ActiveWorkspace if r.Ctx.CacheDirType == types.UserDefinedCacheDir { - cacheDir = r.Ctx.CacheUserDir + // Note: Product supports using environment variables, so we need to parsing the directory path here. + cacheDir = r.renderUserEnv(r.Ctx.CacheUserDir) } log.Infof("Data in `%s` will be cached.", cacheDir) @@ -98,7 +114,7 @@ func (r *Reaper) CompressCache(storageURI string) error { log.Infof("Succeed to cache %s", cacheDir) // remove workspace - err = os.RemoveAll(r.ActiveWorkspace) + err := os.RemoveAll(r.ActiveWorkspace) if err != nil { log.Errorf("RemoveAll err:%v", err) return err @@ -107,16 +123,25 @@ func (r *Reaper) CompressCache(storageURI string) error { } func (r *Reaper) DecompressCache() error { - _ = r.EnsureActiveWorkspace(r.ActiveWorkspace) - if err := r.cm.Unarchive(r.GetCacheFile(), r.ActiveWorkspace); err != nil { - if strings.Contains(err.Error(), "decompression OK") { - // could met decompression OK, trailing garbage ignored - return nil - } - return err + cacheDir := r.ActiveWorkspace + if r.Ctx.CacheDirType == types.UserDefinedCacheDir { + // Note: Product supports using environment variables, so we need to parsing the directory path here. + cacheDir = r.renderUserEnv(r.Ctx.CacheUserDir) } - return nil + err := r.EnsureDir(cacheDir) + if err != nil { + return fmt.Errorf("failed to ensure cache dir `%s`: %s", cacheDir, err) + } + + log.Infof("Cache will be decompressed to %s.", cacheDir) + err = r.cm.Unarchive(r.GetCacheFile(), cacheDir) + if err != nil && strings.Contains(err.Error(), "decompression OK") { + // could met decompression OK, trailing garbage ignored + err = nil + } + + return err } func (r *Reaper) EnsureActiveWorkspace(workspace string) error { @@ -138,17 +163,13 @@ func (r *Reaper) EnsureActiveWorkspace(workspace string) error { return os.Chdir(r.ActiveWorkspace) } +func (r *Reaper) EnsureDir(dir string) error { + return os.MkdirAll(dir, os.ModePerm) +} + func (r *Reaper) BeforeExec() error { r.StartTime = time.Now() - workspace := "/workspace" - if r.Ctx.ClassicBuild { - workspace = r.Ctx.Workspace - } - if err := r.EnsureActiveWorkspace(workspace); err != nil { - return err - } - log.Info("wait for docker daemon to start ...") for i := 0; i < 15; i++ { if err := dockerInfo().Run(); err == nil { @@ -492,3 +513,11 @@ func (r *Reaper) getUserEnvs() []string { return envs } + +func (r *Reaper) renderUserEnv(raw string) string { + mapper := func(env string) string { + return r.UserEnvs[env] + } + + return os.Expand(raw, mapper) +} -- Gitee From e3bc3283f94c014243ba4cacc917aa682b39482e Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sun, 30 Jan 2022 11:42:29 +0800 Subject: [PATCH 1466/2647] adjust job completion logic to ensure wd logic works as expected Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/reaper.go | 13 ----- pkg/microservice/reaper/executor/executor.go | 49 +++++++++++++------ .../warpdrive/core/service/taskplugin/job.go | 25 ++++++---- pkg/types/job.go | 24 +++++++++ 4 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 pkg/types/job.go diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 4b981b48..d36960bb 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -441,22 +441,9 @@ func (r *Reaper) AfterExec(upStreamErr error) error { } } - // Create dog food file to tell wd that task has finished. - dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(time.Now().Format(time.RFC3339)), 0644) - if dogFoodErr != nil { - log.Errorf("Failed to create dog food: %s", dogFoodErr) - } else { - r.dogFeed = true - log.Infof("Build end. Duration: %.2f seconds.", time.Since(r.StartTime).Seconds()) - } - return nil } -func (r *Reaper) DogFeed() bool { - return r.dogFeed -} - func (r *Reaper) maskSecret(secrets []string, message string) string { out := message diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 76ca7a0c..27ccab8d 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -17,14 +17,15 @@ limitations under the License. package executor import ( + "fmt" + "io/ioutil" "time" - "github.com/hashicorp/go-multierror" - commonconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/reaper" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) func Execute() error { @@ -36,34 +37,50 @@ func Execute() error { }) start := time.Now() - log.Info("build start") - - r, err := reaper.NewReaper() - if err != nil { - log.Fatal(err) - } + log.Info("Build start.") + var err error defer func() { - // when dog is feed, the following log has been printed - if !r.DogFeed() { - log.Infof("build end. duration: %.2f seconds", time.Since(start).Seconds()) + // Create dog food file to tell wd that task has finished. + resultMsg := types.JobSuccess + if err != nil { + resultMsg = types.JobFail + } + log.Infof("Job Status: %s", resultMsg) + + dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(resultMsg), 0644) + if dogFoodErr != nil { + log.Errorf("Failed to create dog food: %s", dogFoodErr) + } else { + log.Infof("Build end. Duration: %.2f seconds.", time.Since(start).Seconds()) } + + // Note: Mark the task has been completed through the dogfood file, indirectly notify wd to do follow-up + // operations, and wait for a fixed time. + // Since `wd` will automatically delete the job after detecting the dogfile, this time has little + // effect on the overall construction time. + time.Sleep(1 * time.Minute) }() - var errs *multierror.Error + var r *reaper.Reaper + r, err = reaper.NewReaper() + if err != nil { + return fmt.Errorf("failed to new reaper: %s", err) + } + if err = r.BeforeExec(); err != nil { - log.Fatal(err) + return fmt.Errorf("failed to prepare before building: %s", err) } if r.Ctx.ArtifactInfo == nil { if err = r.Exec(); err != nil { - errs = multierror.Append(errs, err) + return fmt.Errorf("failed to build: %s", err) } } if err = r.AfterExec(err); err != nil { - errs = multierror.Append(errs, err) + return fmt.Errorf("failed to work after building: %s", err) } - return errs.ErrorOrNil() + return nil } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index bd518099..8612fb50 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -832,7 +832,8 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName return config.StatusFailed } - var done bool + var done, exists bool + var jobStatus commontypes.JobStatus for _, pod := range pods { ipod := wrapper.Pod(pod) if ipod.Pending() { @@ -843,9 +844,9 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName } if !ipod.Finished() { - exists, err := checkDogFoodExistsInContainer(namespace, ipod.Name, ipod.ContainerNames()[0]) + jobStatus, exists, err = checkDogFoodExistsInContainer(namespace, ipod.Name, ipod.ContainerNames()[0]) if err != nil { - xl.Infof("failed to check dog food file %s %v", pods[0].Name, err) + xl.Errorf("Failed to check dog food file %s %v", pods[0].Name, err) break } if !exists { @@ -856,8 +857,14 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName } if done { - xl.Infof("dog food is found, stop to wait %s", job.Name) - return config.StatusPassed + xl.Infof("Dog food is found, stop to wait %s. Job status: %s.", job.Name, jobStatus) + + switch jobStatus { + case commontypes.JobFail: + return config.StatusFailed + default: + return config.StatusPassed + } } } else if job.Status.Succeeded != 0 { return config.StatusPassed @@ -871,15 +878,15 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName } -func checkDogFoodExistsInContainer(namespace string, pod string, container string) (bool, error) { - _, _, success, err := podexec.ExecWithOptions(podexec.ExecOptions{ - Command: []string{"test", "-f", setting.DogFood}, +func checkDogFoodExistsInContainer(namespace string, pod string, container string) (commontypes.JobStatus, bool, error) { + stdout, _, success, err := podexec.ExecWithOptions(podexec.ExecOptions{ + Command: []string{"/bin/sh", "-c", fmt.Sprintf("test -f %[1]s && cat %[1]s", setting.DogFood)}, Namespace: namespace, PodName: pod, ContainerName: container, }) - return success, err + return commontypes.JobStatus(stdout), success, err } func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.Affinity { diff --git a/pkg/types/job.go b/pkg/types/job.go new file mode 100644 index 00000000..37604362 --- /dev/null +++ b/pkg/types/job.go @@ -0,0 +1,24 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type JobStatus string + +const ( + JobSuccess JobStatus = "success" + JobFail JobStatus = "fail" +) -- Gitee From f6e7a8fb29f079a701eb2a0af16283ded397cc3a Mon Sep 17 00:00:00 2001 From: panxunying Date: Sun, 30 Jan 2022 12:35:01 +0800 Subject: [PATCH 1467/2647] update sync labels Signed-off-by: panxunying --- .../service/collaboration_instance.go | 267 +++++++++++++----- .../policy/core/service/bundle/opa_bundle.go | 2 +- 2 files changed, 193 insertions(+), 76 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index c672dab1..ba892ba2 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -339,11 +339,11 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp rules = append(rules, &policy.Rule{ Verbs: workflow.Verbs, Kind: "resource", - Resources: []string{"Workflow"}, + Resources: []string{string(config2.ResourceTypeWorkflow)}, MatchAttributes: []policy.MatchAttribute{ { Key: "policy", - Value: policyName, + Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), }, }, }) @@ -356,7 +356,7 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp MatchAttributes: []policy.MatchAttribute{ { Key: "policy", - Value: policyName, + Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name), }, }, }) @@ -396,11 +396,24 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp rules = append(rules, &policy.Rule{ Verbs: workflow.Verbs, Kind: "resource", - Resources: []string{"Workflow"}, + Resources: []string{string(config2.ResourceTypeWorkflow)}, MatchAttributes: []policy.MatchAttribute{ { Key: "policy", - Value: instance.PolicyName, + Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), + }, + }, + }) + } + for _, product := range instance.Products { + rules = append(rules, &policy.Rule{ + Verbs: product.Verbs, + Kind: "resource", + Resources: []string{string(config2.ResourceTypeProduct)}, + MatchAttributes: []policy.MatchAttribute{ + { + Key: "policy", + Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), }, }, }) @@ -439,17 +452,100 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp return nil } +func buildLabelValue(projectName, mode, identityType, userName, resourceType, resourceName string) string { + return projectName + "-" + mode + "-" + identityType + "-" + userName + "-" + resourceType + "-" + resourceName +} + func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName string, logger *zap.SugaredLogger) error { var labels []mongodb2.Label + var deleteLabels []mongodb2.Label var newBindings []*mongodb2.LabelBinding var deleteBindings []*mongodb2.LabelBinding for _, mode := range updateResp.New { - labels = append(labels, mongodb2.Label{ - Key: "policy", - Value: buildPolicyName(projectName, mode.Name, identityType, userName), - Type: "system", - }) - + for _, workflow := range mode.Workflows { + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, mode.Name, identityType, userName, + string(config2.ResourceTypeWorkflow), workflow.Name), + Type: "system", + }) + } + for _, product := range mode.Products { + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, mode.Name, identityType, userName, + string(config2.ResourceTypeProduct), product.Name), + Type: "system", + }) + } + } + for _, item := range updateResp.Update { + for _, workflow := range item.NewSpec.Workflows { + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, + string(config2.ResourceTypeWorkflow), workflow.Name), + Type: "system", + }) + } + for _, product := range item.NewSpec.Products { + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, + string(config2.ResourceTypeProduct), product.Name), + Type: "system", + }) + } + for _, workflow := range item.DeleteSpec.Workflows { + deleteLabels = append(deleteLabels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, + string(config2.ResourceTypeWorkflow), workflow.Name), + Type: "system", + }) + } + for _, product := range item.DeleteSpec.Products { + deleteLabels = append(deleteLabels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, + string(config2.ResourceTypeProduct), product.Name), + Type: "system", + }) + } + for _, workflow := range item.DeleteSpec.Workflows { + deleteLabels = append(deleteLabels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, + string(config2.ResourceTypeWorkflow), workflow.BaseName), + Type: "system", + }) + } + for _, product := range item.DeleteSpec.Products { + deleteLabels = append(deleteLabels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, + string(config2.ResourceTypeProduct), product.BaseName), + Type: "system", + }) + } + } + for _, instance := range updateResp.Delete { + for _, workflow := range instance.Workflows { + deleteLabels = append(deleteLabels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, + string(config2.ResourceTypeWorkflow), workflow.BaseName), + Type: "system", + }) + } + for _, product := range instance.Products { + deleteLabels = append(deleteLabels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, + string(config2.ResourceTypeProduct), product.BaseName), + Type: "system", + }) + } } var labelResp *service.CreateLabelsResp var err error @@ -464,31 +560,54 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } } - var labelModels []mongodb2.Label - for _, item := range updateResp.Update { - labelModels = append(labelModels, mongodb2.Label{ - Key: "policy", - Value: item.PolicyName, + if len(deleteLabels) > 0 { + resp, err := service.ListLabels(&service.ListLabelsArgs{ + Labels: deleteLabels, }) + if err != nil { + return err + } + if len(resp.Labels) != len(deleteLabels) { + return fmt.Errorf("deletelabels not exist,%v:%v", resp.Labels, deleteLabels) + } + var ids []string + for _, label := range resp.Labels { + ids = append(ids, label.ID.Hex()) + } + err = service.DeleteLabels(ids, true, logger) + if err != nil { + logger.Errorf("delete labels error, error msg:%s", err) + return err + } } - for _, item := range updateResp.Delete { - labelModels = append(labelModels, mongodb2.Label{ - Key: "policy", - Value: item.PolicyName, - }) + + for _, item := range updateResp.Update { + for _, workflow := range item.UpdateSpec.Workflows { + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name), + }) + } + for _, product := range item.UpdateSpec.Products { + labels = append(labels, mongodb2.Label{ + Key: "policy", + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), product.New.Name), + }) + } + } - if len(labelModels) > 0 { + if len(labels) > 0 { labelListResp, err := service.ListLabels(&service.ListLabelsArgs{ - Labels: labelModels, + Labels: labels, }) if err != nil { return err } if labelListResp == nil { - return fmt.Errorf("label not exist %v", labelModels) + return fmt.Errorf("label not exist %v", labels) } - if len(labelListResp.Labels) != len(labelModels) { - return fmt.Errorf("label not exist %v:%v", labelModels, labelListResp.Labels) + if len(labelListResp.Labels) != len(labels) { + return fmt.Errorf("label not exist %v:%v", labels, labelListResp.Labels) } labelIdMap := make(map[string]string) @@ -499,12 +618,12 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } for _, mode := range updateResp.New { - policyName := buildPolicyName(projectName, mode.Name, identityType, userName) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", policyName)] - if !ok { - return fmt.Errorf("label:%s not exist", policyName) - } for _, workflow := range mode.Workflows { + labelValue := buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } name := workflow.Name if workflow.CollaborationType == config.CollaborationNew { name = buildName(workflow.Name, mode.Name, identityType, userName) @@ -519,6 +638,11 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }) } for _, product := range mode.Products { + labelValue := buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } name := product.Name if product.CollaborationType == config.CollaborationNew { name = buildName(product.Name, mode.Name, identityType, userName) @@ -534,11 +658,13 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } } for _, item := range updateResp.Update { - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", item.PolicyName)] - if !ok { - return fmt.Errorf("label:%s not exist", item.PolicyName) - } + for _, workflow := range item.NewSpec.Workflows { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -549,6 +675,11 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }) } for _, product := range item.NewSpec.Products { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -561,6 +692,11 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == config.CollaborationNew { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -572,6 +708,11 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } if workflow.Old.CollaborationType == config.CollaborationNew && workflow.New.CollaborationType == config.CollaborationShare { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -585,6 +726,11 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType for _, product := range item.UpdateSpec.Products { if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == config.CollaborationNew { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -596,6 +742,11 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == config.CollaborationShare { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name) + labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -607,37 +758,13 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } } } - var deleteLabels []string - for _, item := range updateResp.Delete { - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", item.PolicyName)] - if !ok { - return fmt.Errorf("label:%s not exist", item.PolicyName) - } - for _, workflow := range item.Workflows { - deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ - LabelID: labelId, - Resource: mongodb2.Resource{ - Name: workflow.Name, - ProjectName: projectName, - Type: "Workflow", - }, - }) - } - for _, product := range item.Products { - deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ - LabelID: labelId, - Resource: mongodb2.Resource{ - Name: product.Name, - ProjectName: projectName, - Type: string(config2.ResourceTypeProduct), - }, - }) - } - } - if len(deleteLabels) > 0 { - err = service.DeleteLabels(deleteLabels, true, logger) + + if len(newBindings) > 0 { + err = service.CreateLabelBindings(&service.CreateLabelBindingsArgs{ + LabelBindings: newBindings, + }, userName, logger) if err != nil { - logger.Errorf("delete labels error:%s", err) + logger.Errorf("create labelbindings error:%s", err) return err } } @@ -650,16 +777,6 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType return err } } - if len(newBindings) > 0 { - err = service.CreateLabelBindings(&service.CreateLabelBindingsArgs{ - LabelBindings: newBindings, - }, userName, logger) - if err != nil { - logger.Errorf("create labelbindings error:%s", err) - return err - } - } - return nil } diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 652ddb47..5e34b54e 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -37,7 +37,7 @@ const ( exemptionsPath = "exemptions/data.json" resourcesPath = "resources/data.json" - policyRoot = "policies" + policyRoot = "rbac" rolesRoot = "roles" rolebindingsRoot = "bindings" exemptionsRoot = "exemptions" -- Gitee From 8cae0c50218667bd2736a707c0f2c4288f1ed6b1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 30 Jan 2022 14:00:11 +0800 Subject: [PATCH 1468/2647] update helm client logic Signed-off-by: allenshen --- go.mod | 1 + .../core/service/taskplugin/deploy.go | 4 +- pkg/tool/helmclient/helmclient.go | 172 +++++++++++++++++- 3 files changed, 167 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 55d2483a..850b3ed9 100644 --- a/go.mod +++ b/go.mod @@ -80,6 +80,7 @@ require ( gorm.io/gorm v1.21.16 helm.sh/helm/v3 v3.7.2 k8s.io/api v0.22.4 + k8s.io/apiextensions-apiserver v0.22.4 k8s.io/apimachinery v0.22.4 k8s.io/client-go v0.22.4 k8s.io/kubectl v0.22.4 diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 9d1530af..e477f9db 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -53,7 +53,6 @@ import ( "github.com/koderover/zadig/pkg/util/converter" fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" - helmrelease "helm.sh/helm/v3/pkg/release" ) // InitializeDeployTaskPlugin to initiate deploy task plugin and return ref @@ -546,8 +545,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * releaseutil.Reverse(hrs, releaseutil.SortByRevision) rel := hrs[0] - // for release in superseded status or stuck in pending status , uninstall the service first - if rel.Info.Status == helmrelease.StatusSuperseded || rel.Info.Status.IsPending() { + if rel.Info.Status.IsPending() { return fmt.Errorf("failed to upgrade release: %s with exceptional status: %s", releaseName, rel.Info.Status) } return nil diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 58051199..6e375eb5 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -20,19 +20,22 @@ import ( "context" "encoding/json" "fmt" + "reflect" "strings" - "helm.sh/helm/v3/pkg/downloader" - + hc "github.com/mittwald/go-helm-client" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/storage/driver" - - hc "github.com/mittwald/go-helm-client" + "helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/strvals" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" "sigs.k8s.io/yaml" @@ -105,6 +108,159 @@ func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues return string(bs), nil } +// upgradeCRDs upgrades the CRDs of the provided chart. +func (hClient *HelmClient) upgradeCRDs(ctx context.Context, chartInstance *chart.Chart) error { + cfg, err := hClient.ActionConfig.RESTClientGetter.ToRESTConfig() + if err != nil { + return err + } + + k8sClient, err := clientset.NewForConfig(cfg) + if err != nil { + return err + } + + for _, crd := range chartInstance.CRDObjects() { + if err := hClient.upgradeCRD(ctx, k8sClient, crd); err != nil { + return err + } + hClient.DebugLog("CRD %s upgraded successfully for chart: %s", crd.Name, chartInstance.Metadata.Name) + } + + return nil +} + +// upgradeCRDV1Beta1 upgrades a CRD of the v1 API version using the provided k8s client and CRD yaml. +func (hClient *HelmClient) upgradeCRDV1(ctx context.Context, cl *clientset.Clientset, rawCRD []byte) error { + var crdObj v1.CustomResourceDefinition + if err := yaml.Unmarshal(rawCRD, &crdObj); err != nil { + return err + } + + existingCRDObj, err := cl.ApiextensionsV1().CustomResourceDefinitions().Get(ctx, crdObj.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + // Check to ensure that no previously existing API version is deleted through the upgrade. + if len(existingCRDObj.Spec.Versions) > len(crdObj.Spec.Versions) { + hClient.DebugLog("WARNING: new version of CRD %q would remove an existing API version, skipping upgrade", crdObj.Name) + return nil + } + + // Check that the storage version does not change through the update. + oldStorageVersion := v1.CustomResourceDefinitionVersion{} + + for _, oldVersion := range existingCRDObj.Spec.Versions { + if oldVersion.Storage { + oldStorageVersion = oldVersion + } + } + + i := 0 + + for _, newVersion := range crdObj.Spec.Versions { + if newVersion.Storage { + i++ + if newVersion.Name != oldStorageVersion.Name { + return fmt.Errorf("ERROR: storage version of CRD %q changed, aborting upgrade", crdObj.Name) + } + } + if i > 1 { + return fmt.Errorf("ERROR: more than one storage version set on CRD %q, aborting upgrade", crdObj.Name) + } + } + + if reflect.DeepEqual(existingCRDObj.Spec.Versions, crdObj.Spec.Versions) { + hClient.DebugLog("INFO: new version of CRD %q contains no changes, skipping upgrade", crdObj.Name) + return nil + } + + crdObj.ResourceVersion = existingCRDObj.ResourceVersion + if _, err := cl.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, &crdObj, metav1.UpdateOptions{DryRun: []string{"All"}}); err != nil { + return err + } + hClient.DebugLog("upgrade ran successful for CRD (dry run): %s", crdObj.Name) + + if _, err := cl.ApiextensionsV1().CustomResourceDefinitions().Update(ctx, &crdObj, metav1.UpdateOptions{}); err != nil { + return err + } + hClient.DebugLog("upgrade ran successful for CRD: %s", crdObj.Name) + + return nil +} + +// upgradeCRDV1Beta1 upgrades a CRD of the v1beta1 API version using the provided k8s client and CRD yaml. +func (hClient *HelmClient) upgradeCRDV1Beta1(ctx context.Context, cl *clientset.Clientset, rawCRD []byte) error { + var crdObj v1beta1.CustomResourceDefinition + if err := yaml.Unmarshal(rawCRD, &crdObj); err != nil { + return err + } + existingCRDObj, err := cl.ApiextensionsV1beta1().CustomResourceDefinitions().Get(ctx, crdObj.Name, metav1.GetOptions{}) + if err != nil { + return err + } + + // Check that the storage version does not change through the update. + oldStorageVersion := v1beta1.CustomResourceDefinitionVersion{} + + for _, oldVersion := range existingCRDObj.Spec.Versions { + if oldVersion.Storage { + oldStorageVersion = oldVersion + } + } + + i := 0 + + for _, newVersion := range crdObj.Spec.Versions { + if newVersion.Storage { + i++ + if newVersion.Name != oldStorageVersion.Name { + return fmt.Errorf("ERROR: storage version of CRD %q changed, aborting upgrade", crdObj.Name) + } + } + if i > 1 { + return fmt.Errorf("ERROR: more than one storage version set on CRD %q, aborting upgrade", crdObj.Name) + } + } + + if reflect.DeepEqual(existingCRDObj.Spec.Versions, crdObj.Spec.Versions) { + hClient.DebugLog("INFO: new version of CRD %q contains no changes, skipping upgrade", crdObj.Name) + return nil + } + + crdObj.ResourceVersion = existingCRDObj.ResourceVersion + if _, err := cl.ApiextensionsV1beta1().CustomResourceDefinitions().Update(ctx, &crdObj, metav1.UpdateOptions{DryRun: []string{"All"}}); err != nil { + return err + } + hClient.DebugLog("upgrade ran successful for CRD (dry run): %s", crdObj.Name) + + if _, err = cl.ApiextensionsV1beta1().CustomResourceDefinitions().Update(ctx, &crdObj, metav1.UpdateOptions{}); err != nil { + return err + } + hClient.DebugLog("upgrade ran successful for CRD: %s", crdObj.Name) + + return nil +} + +// upgradeCRD upgrades the CRD 'crd' using the provided k8s client. +func (hClient *HelmClient) upgradeCRD(ctx context.Context, k8sClient *clientset.Clientset, crd chart.CRD) error { + var typeMeta metav1.TypeMeta + err := yaml.Unmarshal(crd.File.Data, &typeMeta) + if err != nil { + return err + } + + switch typeMeta.APIVersion { + default: + return fmt.Errorf("WARNING: failed to upgrade CRD %q: unsupported api-version %q", crd.Name, typeMeta.APIVersion) + case "apiextensions.k8s.io/v1beta1": + return hClient.upgradeCRDV1Beta1(ctx, k8sClient, crd.File.Data) + case "apiextensions.k8s.io/v1": + return hClient.upgradeCRDV1(ctx, k8sClient, crd.File.Data) + } +} + // check weather to install or upgrade chart by current status // return error if neither install nor upgrade action is legal func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) { @@ -253,7 +409,7 @@ func (hClient *HelmClient) upgradeChart(ctx context.Context, spec *hc.ChartSpec) if !spec.SkipCRDs && spec.UpgradeCRDs { c.DebugLog("upgrading crds") - err = c.upgradeCRDs(ctx, helmChart) + err = hClient.upgradeCRDs(ctx, helmChart) if err != nil { return nil, err } @@ -277,9 +433,11 @@ func (hClient *HelmClient) InstallOrUpgradeChart(ctx context.Context, spec *hc.C } if install { + log.Infof("##### installing chart") return hClient.installChart(ctx, spec) } else { - return hClient.InstallOrUpgradeChart(ctx, spec) + log.Infof("###### upgrading chart") + return hClient.upgradeChart(ctx, spec) } } -- Gitee From 4e52b39ef727d103d9060d4cbaee86ec45173b44 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sun, 30 Jan 2022 14:17:04 +0800 Subject: [PATCH 1469/2647] change print format Signed-off-by: zhangyifei --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 8612fb50..1b586156 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -846,7 +846,7 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName if !ipod.Finished() { jobStatus, exists, err = checkDogFoodExistsInContainer(namespace, ipod.Name, ipod.ContainerNames()[0]) if err != nil { - xl.Errorf("Failed to check dog food file %s %v", pods[0].Name, err) + xl.Errorf("Failed to check dog food file %s: %s.", pods[0].Name, err) break } if !exists { -- Gitee From fd3466bc1a0ad215fb7caa79489a7d935e57cf73 Mon Sep 17 00:00:00 2001 From: panxunying Date: Sun, 30 Jan 2022 15:29:23 +0800 Subject: [PATCH 1470/2647] update sync labels Signed-off-by: panxunying --- .../policy/core/service/bundle/opa_bundle.go | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 5e34b54e..4a298e3b 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -20,6 +20,8 @@ import ( "net/http" "sort" + "k8s.io/apimachinery/pkg/util/sets" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" @@ -294,9 +296,26 @@ func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.Policy } } } - + attrSets := sets.String{} + var attributes []*Attribute for _, v := range opaRole.Rules { - v.MatchAttributes = NewFrom(r.MatchAttributes) + for _, attribute := range r.MatchAttributes { + a := Attribute{ + Key: attribute.Key, + Value: attribute.Value, + } + if !attrSets.Has(attribute.Key + "-" + attribute.Value) { + attributes = append(attributes, &a) + attrSets.Insert(attribute.Key + "-" + attribute.Value) + } + } + for _, attribute := range v.MatchAttributes { + if !attrSets.Has(attribute.Key + "-" + attribute.Value) { + attributes = append(attributes, attribute) + attrSets.Insert(attribute.Key + "-" + attribute.Value) + } + } + v.MatchAttributes = attributes } } sort.Sort(opaRole.Rules) -- Gitee From 978dfb64838a224d11b143d768f740981a209829 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 7 Feb 2022 15:24:48 +0800 Subject: [PATCH 1471/2647] fix bug of inconsistent secret name Signed-off-by: zhangyifei --- .../warpdrive/core/service/taskplugin/job.go | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1b586156..90f90eef 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -469,16 +469,9 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa }, } for _, reg := range registries { - arr := strings.Split(reg.Namespace, "/") - namespaceInRegistry := arr[len(arr)-1] - // for AWS ECR, there are no namespace, thus we need to find the NS from the URI - if namespaceInRegistry == "" { - uriDecipher := strings.Split(reg.RegAddr, ".") - namespaceInRegistry = uriDecipher[0] - } - secretName := namespaceInRegistry + registrySecretSuffix - if reg.RegType != "" { - secretName = namespaceInRegistry + "-" + reg.RegType + registrySecretSuffix + secretName, err := genRegistrySecretName(reg) + if err != nil { + return nil, fmt.Errorf("failed to generate registry secret name: %s", err) } secret := corev1.LocalObjectReference{ @@ -588,8 +581,11 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa return job, nil } + +// Note: The name of a Secret object must be a valid DNS subdomain name: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names func formatRegistryName(namespaceInRegistry string) (string, error) { - reg, err := regexp.Compile("[^a-zA-Z0-9]+") + reg, err := regexp.Compile("[^a-zA-Z0-9\\.-]+") if err != nil { return "", err } @@ -607,23 +603,9 @@ func createOrUpdateRegistrySecrets(namespace, registryID string, registries []*t continue } - arr := strings.Split(reg.Namespace, "/") - namespaceInRegistry := arr[len(arr)-1] - // for AWS ECR, there are no namespace, thus we need to find the NS from the URI - if namespaceInRegistry == "" { - uriDecipher := strings.Split(reg.RegAddr, ".") - namespaceInRegistry = uriDecipher[0] - } - filteredName, err := formatRegistryName(namespaceInRegistry) + secretName, err := genRegistrySecretName(reg) if err != nil { - return err - } - secretName := filteredName + registrySecretSuffix - if reg.RegType != "" { - secretName = filteredName + "-" + reg.RegType + registrySecretSuffix - } - if reg.ID == registryID { - secretName = setting.DefaultImagePullSecret + return fmt.Errorf("failed to generate registry secret name: %s", err) } data := make(map[string][]byte) @@ -958,3 +940,30 @@ func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.A } return nil } + +func genRegistrySecretName(reg *task.RegistryNamespace) (string, error) { + if reg.IsDefault { + return setting.DefaultImagePullSecret, nil + } + + arr := strings.Split(reg.Namespace, "/") + namespaceInRegistry := arr[len(arr)-1] + + // for AWS ECR, there are no namespace, thus we need to find the NS from the URI + if namespaceInRegistry == "" { + uriDecipher := strings.Split(reg.RegAddr, ".") + namespaceInRegistry = uriDecipher[0] + } + + filteredName, err := formatRegistryName(namespaceInRegistry) + if err != nil { + return "", err + } + + secretName := filteredName + registrySecretSuffix + if reg.RegType != "" { + secretName = filteredName + "-" + reg.RegType + registrySecretSuffix + } + + return secretName, nil +} -- Gitee From 6d4d60865e74c6492e06607b31d00c76ae14e4a6 Mon Sep 17 00:00:00 2001 From: tbbrave Date: Mon, 7 Feb 2022 18:17:04 +0800 Subject: [PATCH 1472/2647] fix Sprintf type error Signed-off-by: tbbrave --- .../systemconfig/core/codehost/internal/oauth/oauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index b11a1ab3..d14227a5 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -71,7 +71,7 @@ func (o *OAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { if err == nil && len(proxies) != 0 && proxies[0].EnableRepoProxy { port := proxies[0].Port ip := proxies[0].Address - proxyRawUrl := fmt.Sprintf("http://%s:%s", ip, port) + proxyRawUrl := fmt.Sprintf("http://%s:%d", ip, port) proxyUrl, err2 := url.Parse(proxyRawUrl) if err2 == nil { httpClient.Transport = &http.Transport{ -- Gitee From 98292da01794e138dd6a9da064b32a420b8d3ed2 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 7 Feb 2022 18:28:19 +0800 Subject: [PATCH 1473/2647] add new contributor @tbbrave Signed-off-by: zhangyifei --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 7a688603..94d3f1cc 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -68,3 +68,4 @@ The council will make a decision for the nomination. The candidate has to have d - [fengzixu](https://github.com/fengzixu) - [xrfinbupt](https://github.com/xrfinbupt) - [mhatvan](https://github.com/mhatvan) +- [tbbrave](https://github.com/tbbrave) -- Gitee From 0597df81d1de74a8bdf6c3adc916482e7d9d678e Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 7 Feb 2022 22:51:30 +0800 Subject: [PATCH 1474/2647] improved the lantency problem for environments, used informer for resource watch & List Signed-off-by: Min Min --- .../aslan/core/common/service/environment.go | 14 +++--- .../aslan/core/common/service/kube/status.go | 44 +++++++++++++++++ .../aslan/core/environment/service/env.go | 5 +- .../environment/service/environment_group.go | 16 ++++--- .../aslan/core/environment/service/k8s.go | 13 +++-- .../aslan/core/environment/service/pm.go | 7 +-- .../aslan/core/environment/service/service.go | 5 +- pkg/shared/kube/client/client.go | 8 ++++ pkg/tool/kube/client/cluster.go | 17 +++++++ pkg/tool/kube/getter/deployment.go | 8 ++++ pkg/tool/kube/getter/ingress.go | 18 +++---- pkg/tool/kube/getter/pod.go | 9 ++++ pkg/tool/kube/getter/service.go | 8 ++++ pkg/tool/kube/getter/statefulset.go | 8 ++++ pkg/tool/kube/informer/informer.go | 48 +++++++++++++++++++ pkg/tool/kube/multicluster/service.go | 31 ++++++++++++ pkg/tool/remotedialer/session.go | 1 + 17 files changed, 220 insertions(+), 40 deletions(-) create mode 100644 pkg/tool/kube/informer/informer.go diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 58cc4daa..88b1af00 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -41,6 +41,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/util" jsonutil "github.com/koderover/zadig/pkg/util/json" ) @@ -217,14 +218,14 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Infof("Start to list workloads in namespace %s", namespace) var resp = make([]*ServiceResp, 0) - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } - + informer := informer.NewInformer(clusterID, namespace, cls) var workLoads []*Workload - listDeployments, err := getter.ListDeployments(namespace, nil, kubeClient) + listDeployments, err := getter.ListDeploymentsWithCache(nil, informer) if err != nil { log.Errorf("[%s][%s] create product record error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) @@ -232,7 +233,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p for _, v := range listDeployments { workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) } - statefulSets, err := getter.ListStatefulSets(namespace, nil, kubeClient) + statefulSets, err := getter.ListStatefulSetsWithCache(nil, informer) if err != nil { log.Errorf("[%s][%s] create product record error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) @@ -268,15 +269,14 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } hostInfos := make([]resource.HostInfo, 0) - // get all ingresses - if ingresses, err := getter.ListIngresses(namespace, nil, kubeClient); err == nil { + if ingresses, err := getter.ListIngresses(nil, informer); err == nil { for _, ingress := range ingresses { hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) } } // get all services - allServices, err := getter.ListServices(namespace, nil, kubeClient) + allServices, err := getter.ListServicesWithCache(nil, informer) if err != nil { log.Errorf("[%s][%s] list service error: %s", envName, namespace, err) } diff --git a/pkg/microservice/aslan/core/common/service/kube/status.go b/pkg/microservice/aslan/core/common/service/kube/status.go index cf2460ad..aa848a63 100644 --- a/pkg/microservice/aslan/core/common/service/kube/status.go +++ b/pkg/microservice/aslan/core/common/service/kube/status.go @@ -21,6 +21,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/setting" @@ -71,3 +72,46 @@ func GetSelectedPodsInfo(ns string, selector labels.Selector, kubeClient client. return setting.PodRunning, ready, images } + +func GetSelectedPodsInfoWithCache(selector labels.Selector, informer informers.SharedInformerFactory, log *zap.SugaredLogger) (string, string, []string) { + pods, err := getter.ListPodsWithCache(selector, informer) + if err != nil { + return setting.PodError, setting.PodNotReady, nil + } + + if len(pods) == 0 { + return setting.PodNonStarted, setting.PodNotReady, nil + } + + imageSet := sets.String{} + for _, pod := range pods { + ipod := wrapper.Pod(pod) + imageSet.Insert(ipod.Containers()...) + } + images := imageSet.List() + + ready := setting.PodReady + + succeededPods := 0 + for _, pod := range pods { + iPod := wrapper.Pod(pod) + + // skip if pod is a succeeded job + if iPod.Succeeded() { + succeededPods++ + continue + } + + // 如果服务中任意一个pod不在running状态,返回unstable + if !iPod.Ready() { + return setting.PodUnstable, setting.PodNotReady, images + } + } + + // return "Succeeded, Completed" if all pods are succeeded Job pods + if len(pods) == succeededPods { + return string(corev1.PodSucceeded), setting.JobReady, images + } + + return setting.PodRunning, ready, images +} diff --git a/pkg/microservice/aslan/core/environment/service/env.go b/pkg/microservice/aslan/core/environment/service/env.go index 477694aa..b213381a 100644 --- a/pkg/microservice/aslan/core/environment/service/env.go +++ b/pkg/microservice/aslan/core/environment/service/env.go @@ -18,6 +18,7 @@ package service import ( "go.uber.org/zap" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -27,9 +28,9 @@ import ( type envHandle interface { createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error - listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, kubeClient client.Client, productInfo *commonmodels.Product) []*commonservice.ServiceResp + listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp updateService(args *SvcOptArgs) error - queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient client.Client) (string, string, []string) + queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient informers.SharedInformerFactory) (string, string, []string) } func envHandleFunc(projectType string, log *zap.SugaredLogger) envHandle { diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index 5651da5b..61939d82 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -22,7 +22,7 @@ import ( "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/informers" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/util" ) @@ -68,22 +69,23 @@ func ListGroups(serviceName, envName, productName string, perPage, page int, log //将获取到的所有服务按照名称进行排序 sort.SliceStable(allServices, func(i, j int) bool { return allServices[i].ServiceName < allServices[j].ServiceName }) - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), productInfo.ClusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, productName, err) return resp, count, e.ErrListGroups.AddDesc(err.Error()) } + inf := informer.NewInformer(productInfo.ClusterID, productInfo.Namespace, cls) //这种针对的是获取所有数据的接口,内部调用 if page == 0 && perPage == 0 { - resp = envHandleFunc(getProjectType(productName), log).listGroupServices(allServices, envName, productName, kubeClient, productInfo) + resp = envHandleFunc(getProjectType(productName), log).listGroupServices(allServices, envName, productName, inf, productInfo) return resp, count, nil } //针对获取环境状态的接口请求,这里不需要一次性获取所有的服务,先获取十条的数据,有异常的可以直接返回,不需要继续往下获取 if page == -1 && perPage == -1 { // 获取环境的状态 - resp = listGroupServiceStatus(allServices, envName, productName, kubeClient, productInfo, log) + resp = listGroupServiceStatus(allServices, envName, productName, inf, productInfo, log) return resp, count, nil } @@ -96,11 +98,11 @@ func ListGroups(serviceName, envName, productName string, perPage, page int, log } currentServices = allServices[currentPage*perPage:] } - resp = envHandleFunc(getProjectType(productName), log).listGroupServices(currentServices, envName, productName, kubeClient, productInfo) + resp = envHandleFunc(getProjectType(productName), log).listGroupServices(currentServices, envName, productName, inf, productInfo) return resp, count, nil } -func listGroupServiceStatus(allServices []*commonmodels.ProductService, envName, productName string, kubeClient client.Client, productInfo *commonmodels.Product, log *zap.SugaredLogger) []*commonservice.ServiceResp { +func listGroupServiceStatus(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product, log *zap.SugaredLogger) []*commonservice.ServiceResp { var ( count = len(allServices) currentServices = make([]*commonmodels.ProductService, 0) @@ -113,7 +115,7 @@ func listGroupServiceStatus(allServices []*commonmodels.ProductService, envName, } else { currentServices = allServices[page*perPage:] } - resp = envHandleFunc(getProjectType(productName), log).listGroupServices(currentServices, envName, productName, kubeClient, productInfo) + resp = envHandleFunc(getProjectType(productName), log).listGroupServices(currentServices, envName, productName, informer, productInfo) allRunning := true for _, serviceResp := range resp { // Service是物理机部署时,无需判断状态 diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 24e4f5a1..0b74a85c 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -47,11 +48,11 @@ type K8sService struct { // If service doesnt have pods, service status = success (all objects created) or failed (fail to create some objects). // 正常:StatusRunning or StatusSucceed // 错误:StatusError or StatusFailed -func (k *K8sService) queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient client.Client) (string, string, []string) { +func (k *K8sService) queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, informer informers.SharedInformerFactory) (string, string, []string) { k.log.Infof("queryServiceStatus of service: %s of product: %s in namespace %s", serviceTmpl.ServiceName, productName, namespace) if len(serviceTmpl.Containers) > 0 { // 有容器时,根据pods status判断服务状态 - return queryPodsStatus(namespace, productName, serviceTmpl.ServiceName, kubeClient, k.log) + return queryPodsStatus(namespace, productName, serviceTmpl.ServiceName, informer, k.log) } return setting.PodSucceeded, setting.PodReady, []string{} @@ -135,9 +136,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return nil } -// TODO: LOU: improve the status scope and definition, like pod status, service status, environment, cluster status, ... -// TODO: LOU: rewrite it -func (k *K8sService) listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, kubeClient client.Client, productInfo *commonmodels.Product) []*commonservice.ServiceResp { +func (k *K8sService) listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp { var wg sync.WaitGroup var resp []*commonservice.ServiceResp var mutex sync.RWMutex @@ -164,8 +163,8 @@ func (k *K8sService) listGroupServices(allServices []*commonmodels.ProductServic gp.ProductName = serviceTmpl.ProductName // 查询group下所有pods信息 - if kubeClient != nil { - gp.Status, gp.Ready, gp.Images = k.queryServiceStatus(productInfo.Namespace, envName, productName, serviceTmpl, kubeClient) + if informer != nil { + gp.Status, gp.Ready, gp.Images = k.queryServiceStatus(productInfo.Namespace, envName, productName, serviceTmpl, informer) // 如果产品正在创建中,且service status为ERROR(POD还没创建出来),则判断为Pending,尚未开始创建 if productInfo.Status == setting.ProductStatusCreating && gp.Status == setting.PodError { gp.Status = setting.PodPending diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 487929b9..2d9c827b 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -42,7 +43,7 @@ type PMService struct { log *zap.SugaredLogger } -func (p *PMService) queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient client.Client) (string, string, []string) { +func (p *PMService) queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, informer informers.SharedInformerFactory) (string, string, []string) { p.log.Infof("queryServiceStatus of service: %s of product: %s in namespace %s", serviceTmpl.ServiceName, productName, namespace) pipelineName := fmt.Sprintf("%s-%s-%s", serviceTmpl.ServiceName, envName, "job") taskObj, err := commonrepo.NewTaskColl().FindTask(pipelineName, config.ServiceType) @@ -53,7 +54,7 @@ func (p *PMService) queryServiceStatus(namespace, envName, productName string, s return setting.PodPending, setting.PodNotReady, []string{} } - return queryPodsStatus(namespace, "", serviceTmpl.ServiceName, kubeClient, p.log) + return queryPodsStatus(namespace, "", serviceTmpl.ServiceName, informer, p.log) } func (p *PMService) updateService(args *SvcOptArgs) error { @@ -87,7 +88,7 @@ func (p *PMService) updateService(args *SvcOptArgs) error { return nil } -func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, kubeClient client.Client, productInfo *commonmodels.Product) []*commonservice.ServiceResp { +func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp { var wg sync.WaitGroup var resp []*commonservice.ServiceResp var mutex sync.RWMutex diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 93306ab2..3baed84c 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -25,6 +25,7 @@ import ( "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -444,7 +445,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return nil } -func queryPodsStatus(namespace, productName, serviceName string, kubeClient client.Client, log *zap.SugaredLogger) (string, string, []string) { +func queryPodsStatus(namespace, productName, serviceName string, informer informers.SharedInformerFactory, log *zap.SugaredLogger) (string, string, []string) { ls := labels.Set{} if productName != "" { ls[setting.ProductLabel] = productName @@ -452,7 +453,7 @@ func queryPodsStatus(namespace, productName, serviceName string, kubeClient clie if serviceName != "" { ls[setting.ServiceLabel] = serviceName } - return kube.GetSelectedPodsInfo(namespace, ls.AsSelector(), kubeClient, log) + return kube.GetSelectedPodsInfoWithCache(ls.AsSelector(), informer, log) } // validateServiceContainer validate container with envName like dev diff --git a/pkg/shared/kube/client/client.go b/pkg/shared/kube/client/client.go index 8374adb5..60966057 100644 --- a/pkg/shared/kube/client/client.go +++ b/pkg/shared/kube/client/client.go @@ -33,6 +33,14 @@ func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { return multicluster.GetKubeClient(hubServerAddr, clusterID) } +func GetKubeClientSet(hubServerAddr, clusterID string) (*kubernetes.Clientset, error) { + if clusterID == setting.LocalClusterID { + clusterID = "" + } + + return multicluster.GetKubeClientSet(hubServerAddr, clusterID) +} + func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { if clusterID == setting.LocalClusterID { clusterID = "" diff --git a/pkg/tool/kube/client/cluster.go b/pkg/tool/kube/client/cluster.go index d4b70be0..1adb6fda 100644 --- a/pkg/tool/kube/client/cluster.go +++ b/pkg/tool/kube/client/cluster.go @@ -22,6 +22,7 @@ import ( "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -34,6 +35,7 @@ import ( var once sync.Once var c cluster.Cluster +var k *kubernetes.Clientset // Cluster is a singleton, it will be initialized only once. func Cluster() cluster.Cluster { @@ -48,6 +50,21 @@ func Cluster() cluster.Cluster { return c } +func NewClientSet() *kubernetes.Clientset { + if k != nil { + return k + } + config, err := rest.InClusterConfig() + if err != nil { + panic(err) + } + k, err = kubernetes.NewForConfig(config) + if err != nil { + panic(err) + } + return k +} + func Client() client.Client { return Cluster().GetClient() } diff --git a/pkg/tool/kube/getter/deployment.go b/pkg/tool/kube/getter/deployment.go index 3a343d2d..6d3595c9 100644 --- a/pkg/tool/kube/getter/deployment.go +++ b/pkg/tool/kube/getter/deployment.go @@ -20,6 +20,7 @@ import ( appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -47,6 +48,13 @@ func ListDeployments(ns string, selector labels.Selector, cl client.Client) ([]* return res, err } +func ListDeploymentsWithCache(selector labels.Selector, lister informers.SharedInformerFactory) ([]*appsv1.Deployment, error) { + if selector == nil { + selector = labels.NewSelector() + } + return lister.Apps().V1().Deployments().Lister().List(selector) +} + func ListDeploymentsYaml(ns string, selector labels.Selector, cl client.Client) ([][]byte, error) { gvk := schema.GroupVersionKind{ Group: "apps", diff --git a/pkg/tool/kube/getter/ingress.go b/pkg/tool/kube/getter/ingress.go index 04ef661f..ca47d918 100644 --- a/pkg/tool/kube/getter/ingress.go +++ b/pkg/tool/kube/getter/ingress.go @@ -20,6 +20,7 @@ import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -33,19 +34,12 @@ func GetIngress(ns, name string, cl client.Client) (*extensionsv1beta1.Ingress, return g, found, err } -func ListIngresses(ns string, selector labels.Selector, cl client.Client) ([]*extensionsv1beta1.Ingress, error) { - // ingress is moved to networking.k8s.io/v1beta1 from extensions/v1beta1. - is := &extensionsv1beta1.IngressList{} - err := ListResourceInCache(ns, selector, nil, is, cl) - if err != nil { - return nil, err +// ListIngresses gets the ingress (extensions/v1beta1) from the informer +func ListIngresses(selector labels.Selector, lister informers.SharedInformerFactory) ([]*extensionsv1beta1.Ingress, error) { + if selector == nil { + selector = labels.NewSelector() } - - var res []*extensionsv1beta1.Ingress - for i := range is.Items { - res = append(res, &is.Items[i]) - } - return res, err + return lister.Extensions().V1beta1().Ingresses().Lister().List(selector) } func ListIngressesYaml(ns string, selector labels.Selector, cl client.Client) ([][]byte, error) { diff --git a/pkg/tool/kube/getter/pod.go b/pkg/tool/kube/getter/pod.go index 8927b4fd..0153f459 100644 --- a/pkg/tool/kube/getter/pod.go +++ b/pkg/tool/kube/getter/pod.go @@ -19,6 +19,7 @@ package getter import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -45,3 +46,11 @@ func ListPods(ns string, selector labels.Selector, cl client.Client) ([]*corev1. } return res, err } + +func ListPodsWithCache(selector labels.Selector, informer informers.SharedInformerFactory) ([]*corev1.Pod, error) { + if selector == nil { + selector = labels.NewSelector() + } + + return informer.Core().V1().Pods().Lister().List(selector) +} diff --git a/pkg/tool/kube/getter/service.go b/pkg/tool/kube/getter/service.go index 1cfb5549..cbf0ca1f 100644 --- a/pkg/tool/kube/getter/service.go +++ b/pkg/tool/kube/getter/service.go @@ -20,6 +20,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -47,6 +48,13 @@ func ListServices(ns string, selector labels.Selector, cl client.Client) ([]*cor return res, err } +func ListServicesWithCache(selector labels.Selector, lister informers.SharedInformerFactory) ([]*corev1.Service, error) { + if selector == nil { + selector = labels.NewSelector() + } + return lister.Core().V1().Services().Lister().List(selector) +} + func ListServicesYaml(ns string, selector labels.Selector, cl client.Client) ([][]byte, error) { gvk := schema.GroupVersionKind{ Group: "", diff --git a/pkg/tool/kube/getter/statefulset.go b/pkg/tool/kube/getter/statefulset.go index 31e3a84f..7c7bbedc 100644 --- a/pkg/tool/kube/getter/statefulset.go +++ b/pkg/tool/kube/getter/statefulset.go @@ -20,6 +20,7 @@ import ( appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -47,6 +48,13 @@ func ListStatefulSets(ns string, selector labels.Selector, cl client.Client) ([] return res, err } +func ListStatefulSetsWithCache(selector labels.Selector, lister informers.SharedInformerFactory) ([]*appsv1.StatefulSet, error) { + if selector == nil { + selector = labels.NewSelector() + } + return lister.Apps().V1().StatefulSets().Lister().List(selector) +} + func ListStatefulSetsYaml(ns string, selector labels.Selector, cl client.Client) ([][]byte, error) { gvk := schema.GroupVersionKind{ Group: "apps", diff --git a/pkg/tool/kube/informer/informer.go b/pkg/tool/kube/informer/informer.go new file mode 100644 index 00000000..5780bacc --- /dev/null +++ b/pkg/tool/kube/informer/informer.go @@ -0,0 +1,48 @@ +package informer + +import ( + "fmt" + "sync" + "time" + + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/setting" +) + +var InformersMap sync.Map + +// NewInformer initialize and start an informer for specific namespace in given cluster. +// Currently the informer will NOT stop unless the service is down +// If you want to watch a new resource, remember to register here +// Current list: +// - Deployment +// - StatefulSet +// - Service +// - Pod +// - Ingress (extentions/v1beta1) <- as of version 1.9.0, this is the resource we watch +func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informers.SharedInformerFactory { + // this is a stupid compatibility code + if clusterID == "" { + clusterID = setting.LocalClusterID + } + key := fmt.Sprintf("%s-%s", clusterID, namespace) + if informer, ok := InformersMap.Load(key); ok { + return informer.(informers.SharedInformerFactory) + } + opts := informers.WithNamespace(namespace) + informerFactory := informers.NewSharedInformerFactoryWithOptions(cls, time.Minute, opts) + // register the resources to be watched + informerFactory.Apps().V1().Deployments().Lister() + informerFactory.Apps().V1().StatefulSets().Lister() + informerFactory.Core().V1().Services().Lister() + informerFactory.Core().V1().Pods().Lister() + informerFactory.Extensions().V1beta1().Ingresses().Lister() + // Never stops unless the pod is killed + informerFactory.Start(make(<-chan struct{})) + // wait for the cache to be synced for the first time + informerFactory.WaitForCacheSync(make(<-chan struct{})) + InformersMap.Store(key, informerFactory) + return informerFactory +} diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index 067c8740..c4f2e9ef 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -44,6 +44,19 @@ func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { return clusterService.GetKubeClient(clusterID) } +func GetKubeClientSet(hubServerAddr, clusterID string) (*kubernetes.Clientset, error) { + if clusterID == "" { + return krkubeclient.NewClientSet(), nil + } + + clusterService, err := NewAgent(hubServerAddr) + if err != nil { + return nil, fmt.Errorf("failed to get clusterService: %v", err) + } + + return clusterService.GetClientGoKubeClient(clusterID) +} + func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { if clusterID == "" { return krkubeclient.APIReader(), nil @@ -126,6 +139,15 @@ func (s *Agent) GetKubeClient(clusterID string) (client.Client, error) { return krkubeclient.NewClientFromAPIConfig(generateAPIConfig(clusterID, s.hubServerAddr)) } +func (s *Agent) GetClientGoKubeClient(clusterID string) (*kubernetes.Clientset, error) { + config := generateRestConfig(clusterID, s.hubServerAddr) + cl, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + return cl, nil +} + func (s *Agent) ClusterConnected(clusterID string) bool { if err := s.hubClient.HasSession(clusterID); err != nil { return false @@ -164,3 +186,12 @@ func generateAPIConfig(clusterID, hubServerAddr string) *api.Config { CurrentContext: "hubserver", } } + +func generateRestConfig(clusterID, hubServerAddr string) *rest.Config { + return &rest.Config{ + Host: fmt.Sprintf("%s/kube/%s", hubServerAddr, clusterID), + TLSClientConfig: rest.TLSClientConfig{ + Insecure: true, + }, + } +} diff --git a/pkg/tool/remotedialer/session.go b/pkg/tool/remotedialer/session.go index 0baaec17..3ff8dc5f 100644 --- a/pkg/tool/remotedialer/session.go +++ b/pkg/tool/remotedialer/session.go @@ -127,6 +127,7 @@ func (s *Session) Serve(ctx context.Context) (int, error) { for { msType, reader, err := s.conn.NextReader() + s.conn.conn.ReadMessage() if err != nil { return 400, err } -- Gitee From a4a8e8a2c29275b1e8acc7a644c11f23da64bde1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 8 Feb 2022 11:17:39 +0800 Subject: [PATCH 1475/2647] remove useless code Signed-off-by: allenshen --- .../core/environment/service/environment.go | 60 ++++++++----------- .../aslan/core/environment/service/image.go | 2 +- pkg/tool/helmclient/helmclient.go | 3 - 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 93b3b543..577fc79d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -166,7 +166,7 @@ type RawYamlResp struct { YamlContent string `json:"yamlContent"` } -type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error +type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) @@ -2267,16 +2267,16 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { +func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client) error { mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) return err } - return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, isRetry, helmClient, kubecli) + return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, isRetry, helmClient) } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client, kubecli client.Client) error { +func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client) error { base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2353,9 +2353,9 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, logger *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, logger *zap.SugaredLogger) error { renderChart := chartInfoMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, isRetry, helmClient, kubecli) + err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, isRetry, helmClient) if err != nil { log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) @@ -2387,7 +2387,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, kubecli, log) + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2456,14 +2456,14 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, kubecli client.Client, log *zap.SugaredLogger) []error { +func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { bo := backoff.NewConstantBackOff(time.Second * 3) retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) isRetry := false _ = backoff.Retry(func() error { failedServices := make([]*commonmodels.Service, 0) - errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, kubecli, log) + errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, log) if len(errList) == 0 { return nil } @@ -2475,7 +2475,7 @@ func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, servic return errList } -func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, kubecli client.Client, log *zap.SugaredLogger) []error { +func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } @@ -2486,15 +2486,15 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic defer ticker.Stop() for _, data := range serviceList { - go func() { + go func(singleService *commonmodels.Service) { defer wg.Done() - err := handler(data, isRetry, kubecli, log) + err := handler(singleService, isRetry, log) if err != nil { errList = append(errList, err) - *failedServices = append(*failedServices, data) - log.Errorf("service:%s apply failed, err %s", data.ServiceName, err) + *failedServices = append(*failedServices, singleService) + log.Errorf("service:%s apply failed, err %s", singleService.ServiceName, err) } - }() + }(data) <-ticker.C } wg.Wait() @@ -2507,10 +2507,6 @@ func updateProductGroup(username, productName, envName, updateType string, produ productServiceMap = make(map[string]*commonmodels.ProductService) productTemplServiceMap = make(map[string]*commonmodels.ProductService) ) - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productResp.ClusterID) - if err != nil { - return e.ErrUpdateEnv.AddErr(err) - } restConfig, err := kube.GetRESTConfig(productResp.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) @@ -2567,12 +2563,12 @@ func updateProductGroup(username, productName, envName, updateType string, produ renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { renderChart := renderChartMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, isRetry, helmClient, kubecli) - if err != nil { + errInstall := installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, isRetry, helmClient) + if errInstall != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) - return errors.Wrapf(err, "failed to install or upgrade service %s", serviceObj.ServiceName) + return errors.Wrapf(errInstall, "failed to install or upgrade service %s", serviceObj.ServiceName) } return nil } @@ -2607,7 +2603,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, kubeClient, log) + serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2810,21 +2806,17 @@ func updateProductVariable(productName, envName string, productResp *commonmodel return e.ErrUpdateEnv.AddErr(err) } - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productResp.ClusterID) - if err != nil { - return e.ErrUpdateEnv.AddErr(err) - } renderChartMap := make(map[string]*templatemodels.RenderChart) for _, renderChart := range productResp.ChartInfos { renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(service *commonmodels.Service, isRetry bool, kubecli client.Client, log *zap.SugaredLogger) error { + handler := func(service *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { renderChart := renderChartMap[service.ServiceName] - err = installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, isRetry, helmClient, kubecli) - if err != nil { - log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, err) - return errors.Wrapf(err, "failed to upgrade service %s", service.ServiceName) + errInstall := installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, isRetry, helmClient) + if errInstall != nil { + log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, errInstall) + return errors.Wrapf(errInstall, "failed to upgrade service %s", service.ServiceName) } return nil } @@ -2851,7 +2843,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, kubeClient, log) + groupServiceErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index e607a716..938f40ff 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -181,7 +181,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri } // when replace image, should not wait - err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, false, helmClient, cl) + err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, false, helmClient) if err != nil { return err } diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 6e375eb5..22e96a0e 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -267,7 +267,6 @@ func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) // find history of particular release releases, err := hClient.ListReleaseHistory(spec.ReleaseName, 10) if err != nil && err != driver.ErrReleaseNotFound { - log.Infof("###### get release history fail") return false, err } // release not found, install operation @@ -433,10 +432,8 @@ func (hClient *HelmClient) InstallOrUpgradeChart(ctx context.Context, spec *hc.C } if install { - log.Infof("##### installing chart") return hClient.installChart(ctx, spec) } else { - log.Infof("###### upgrading chart") return hClient.upgradeChart(ctx, spec) } } -- Gitee From 873f97c167fcc9acaa9d2e9fd0a8bf87e9fe37c2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 8 Feb 2022 11:28:09 +0800 Subject: [PATCH 1476/2647] some minor tweaks to the logic Signed-off-by: Min Min --- .../aslan/core/common/service/product.go | 4 +++ pkg/setting/consts.go | 4 +++ pkg/tool/kube/informer/informer.go | 30 ++++++++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 17f8d6bf..075104ed 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -36,6 +36,7 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -51,6 +52,9 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return e.ErrDeleteEnv.AddDesc("not found") } + // delete informer's cache + informer.DeleteInformer(productInfo.ClusterID, productInfo.Namespace) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrDeleteEnv.AddErr(err) diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 127bae2d..2c15370c 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -608,3 +608,7 @@ const UpdateEnvTimeout = 60 * 5 * time.Second const ( ListNamespaceTypeCreate = "create" ) + +const ( + InformerNamingConvention = "%s-%s" +) diff --git a/pkg/tool/kube/informer/informer.go b/pkg/tool/kube/informer/informer.go index 5780bacc..3bab764e 100644 --- a/pkg/tool/kube/informer/informer.go +++ b/pkg/tool/kube/informer/informer.go @@ -12,6 +12,7 @@ import ( ) var InformersMap sync.Map +var StopChanMap sync.Map // NewInformer initialize and start an informer for specific namespace in given cluster. // Currently the informer will NOT stop unless the service is down @@ -27,7 +28,7 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe if clusterID == "" { clusterID = setting.LocalClusterID } - key := fmt.Sprintf("%s-%s", clusterID, namespace) + key := fmt.Sprintf(setting.InformerNamingConvention, clusterID, namespace) if informer, ok := InformersMap.Load(key); ok { return informer.(informers.SharedInformerFactory) } @@ -39,10 +40,31 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe informerFactory.Core().V1().Services().Lister() informerFactory.Core().V1().Pods().Lister() informerFactory.Extensions().V1beta1().Ingresses().Lister() - // Never stops unless the pod is killed - informerFactory.Start(make(<-chan struct{})) + // stop channel will be stored for future stop + stopchan := make(chan struct{}) + informerFactory.Start(stopchan) // wait for the cache to be synced for the first time - informerFactory.WaitForCacheSync(make(<-chan struct{})) + informerFactory.WaitForCacheSync(make(chan struct{})) + // in case there is a concurrent situation, we find if there is one informer in the map + if _, ok := InformersMap.Load(key); ok { + // if we found that stop channel + if stopchan, ok := StopChanMap.Load(key); ok { + close(stopchan.(chan struct{})) + } + } InformersMap.Store(key, informerFactory) + StopChanMap.Store(key, stopchan) return informerFactory } + +func DeleteInformer(clusterID, namespace string) { + key := fmt.Sprintf(setting.InformerNamingConvention, clusterID, namespace) + // if informer exists + if _, ok := InformersMap.Load(key); ok { + // if we found that stop channel + if stopchan, ok := StopChanMap.Load(key); ok { + close(stopchan.(chan struct{})) + } + } + InformersMap.Delete(key) +} -- Gitee From 720b7334ca634c2a97aa1d65b96a3a0598eba503 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 8 Feb 2022 13:54:19 +0800 Subject: [PATCH 1477/2647] delete useless code Signed-off-by: Min Min --- pkg/tool/remotedialer/session.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/tool/remotedialer/session.go b/pkg/tool/remotedialer/session.go index 3ff8dc5f..0baaec17 100644 --- a/pkg/tool/remotedialer/session.go +++ b/pkg/tool/remotedialer/session.go @@ -127,7 +127,6 @@ func (s *Session) Serve(ctx context.Context) (int, error) { for { msType, reader, err := s.conn.NextReader() - s.conn.conn.ReadMessage() if err != nil { return 400, err } -- Gitee From a9534072a4aef2ea07e28bc3070e85adde9bad08 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 8 Feb 2022 14:06:36 +0800 Subject: [PATCH 1478/2647] bugfix Signed-off-by: panxunying --- .../service/collaboration_instance.go | 7 ++- .../core/environment/service/environment.go | 1 + .../policy/core/service/bundle/opa_bundle.go | 62 +++++++++++-------- .../core/service/bundle/rego/authz.rego | 8 ++- .../bundle/resource_endpoint_mapping.go | 54 ++++++++++++++++ .../policy/core/service/policy.go | 14 +++-- 6 files changed, 110 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index ba892ba2..3d8f978a 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -591,7 +591,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType for _, product := range item.UpdateSpec.Products { labels = append(labels, mongodb2.Label{ Key: "policy", - Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), product.New.Name), + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name), }) } @@ -901,6 +901,9 @@ func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectN return err } err = syncResource(products, updateResp, projectName, identityType, userName, requestID, logger) + if err != nil { + return err + } err = syncPolicy(updateResp, projectName, identityType, userName, uid, logger) if err != nil { return err @@ -936,7 +939,7 @@ func getCollaborationDelete(updateResp *GetCollaborationUpdateResp) *GetCollabor for _, product := range item.UpdateSpec.Products { if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == config.CollaborationShare { - workflowSet.Insert(product.Old.Name) + productSet.Insert(product.Old.Name) } } } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index afb17722..72c736b5 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -916,6 +916,7 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc product.Namespace = projectName + "-env-" + product.EnvName product.Render.Name = product.Namespace product.ID = primitive.NilObjectID + product.Render.Revision = 0 err = CreateProduct(user, requestID, product, log) if err != nil { return err diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 4a298e3b..c15b4207 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -279,14 +279,45 @@ func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.Policy resourceMappings := getResourceActionMappings(policyMetas) for _, policy := range policies { + verbAttrMap := make(map[string]sets.String) + resourceVerbs := make(map[string]sets.String) + for _, r := range policy.Rules { + for _, verb := range r.Verbs { + if verbs, ok := resourceVerbs[r.Resources[0]]; ok { + for _, v := range r.Verbs { + verbs.Insert(v) + } + resourceVerbs[r.Resources[0]] = verbs + } else { + verbSet := sets.String{} + for _, v := range r.Verbs { + verbSet.Insert(v) + } + resourceVerbs[r.Resources[0]] = verbSet + } + if attrs, ok := verbAttrMap[verb]; ok { + for _, attribute := range r.MatchAttributes { + attrs.Insert(attribute.Key + "&&" + attribute.Value) + } + verbAttrMap[verb] = attrs + } else { + attrSet := sets.String{} + for _, attribute := range r.MatchAttributes { + attrSet.Insert(attribute.Key + "&&" + attribute.Value) + } + verbAttrMap[verb] = attrSet + } + } + } + //TODO - mouuii change role model to policy model opaRole := &role{Name: policy.Name, Namespace: policy.Namespace} + for resource, verbs := range resourceVerbs { + ruleList := resourceMappings.GetPolicyRules(resource, verbs.List(), verbAttrMap) + opaRole.Rules = append(opaRole.Rules, ruleList...) + } for _, r := range policy.Rules { - if r.Kind == models.KindResource { - for _, res := range r.Resources { - opaRole.Rules = append(opaRole.Rules, resourceMappings.GetRules(res, r.Verbs)...) - } - } else { + if r.Kind != models.KindResource { if len(r.Verbs) == 1 && r.Verbs[0] == models.MethodAll { r.Verbs = AllMethods } @@ -296,27 +327,6 @@ func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.Policy } } } - attrSets := sets.String{} - var attributes []*Attribute - for _, v := range opaRole.Rules { - for _, attribute := range r.MatchAttributes { - a := Attribute{ - Key: attribute.Key, - Value: attribute.Value, - } - if !attrSets.Has(attribute.Key + "-" + attribute.Value) { - attributes = append(attributes, &a) - attrSets.Insert(attribute.Key + "-" + attribute.Value) - } - } - for _, attribute := range v.MatchAttributes { - if !attrSets.Has(attribute.Key + "-" + attribute.Value) { - attributes = append(attributes, attribute) - attrSets.Insert(attribute.Key + "-" + attribute.Value) - } - } - v.MatchAttributes = attributes - } } sort.Sort(opaRole.Rules) data.Roles = append(data.Roles, opaRole) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 9676ee1d..fbcf6022 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -197,8 +197,12 @@ all_attributes_match(attributes, resourceType, resourceID) { res.resourceID == resourceID project_name_is_match(res) - # a && b <=> !(!a || !b), De Morgan’s laws, see details in https://www.fugue.co/blog/5-tips-for-using-the-rego-language-for-open-policy-agent-opa - not attributes_mismatch(attributes, res) + attributes_match(attributes, res) +} + +attributes_match(attributes, res) { + attribute := attributes[_] + attribute_match(attribute, res) } attributes_mismatch(attributes, res) { diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index d4fcb559..86dadcb2 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -17,6 +17,8 @@ limitations under the License. package bundle import ( + "strings" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" @@ -55,6 +57,58 @@ func (m resourceActionMappings) GetRules(resource string, actions []string) []*r return res } +func (m resourceActionMappings) GetPolicyRules(resource string, actions []string, verbAttrMap map[string]sets.String) []*rule { + mappings, ok := m[resource] + if !ok { + return nil + } + + all := false + if len(actions) == 1 && actions[0] == models.MethodAll { + all = true + } + actionSet := sets.NewString(actions...) + var res []*rule + for action, r := range mappings { + var attr Attributes + if attrs, ok := verbAttrMap[action]; ok { + for _, s := range attrs.List() { + attrString := strings.Split(s, "&&") + attr = append(attr, &Attribute{ + Key: attrString[0], + Value: attrString[1], + }) + } + } + if all || actionSet.Has(action) { + for _, rr := range r { + attrSets := sets.String{} + rrAttr := attr + for _, attribute := range rrAttr { + attrSets.Insert(attribute.Key + "&&" + attribute.Value) + } + for _, attribute := range rr.MatchAttributes { + if !attrSets.Has(attribute.Key + "&&" + attribute.Value) { + rrAttr = append(rrAttr, attribute) + attrSets.Insert(attribute.Key + "&&" + attribute.Value) + } + } + rrr := &rule{ + Method: rr.Method, + Endpoint: rr.Endpoint, + ResourceType: rr.ResourceType, + IDRegex: rr.IDRegex, + MatchAttributes: rrAttr, + MatchExpressions: rr.MatchExpressions, + } + res = append(res, rrr) + } + } + } + + return res +} + func getResourceActionMappings(policies []*models.PolicyMeta) resourceActionMappings { data := make(resourceActionMappings) for _, p := range policies { diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 6ad2b73d..3b4a6beb 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -86,9 +86,10 @@ func UpdatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { for _, r := range policy.Rules { obj.Rules = append(obj.Rules, &models.Rule{ - Verbs: r.Verbs, - Kind: r.Kind, - Resources: r.Resources, + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + MatchAttributes: r.MatchAttributes, }) } return mongodb.NewPolicyColl().UpdatePolicy(obj) @@ -102,9 +103,10 @@ func UpdateOrCreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error for _, r := range policy.Rules { obj.Rules = append(obj.Rules, &models.Rule{ - Verbs: r.Verbs, - Kind: r.Kind, - Resources: r.Resources, + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + MatchAttributes: r.MatchAttributes, }) } return mongodb.NewPolicyColl().UpdateOrCreate(obj) -- Gitee From 60a2423427e28b116c8def46b4e788df219c7036 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 8 Feb 2022 17:46:04 +0800 Subject: [PATCH 1479/2647] fix status bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/common/service/github/status.go | 2 +- .../warpdrive/core/service/taskplugin/github/status.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/github/status.go b/pkg/microservice/aslan/core/common/service/github/status.go index 501960b9..1f48793a 100644 --- a/pkg/microservice/aslan/core/common/service/github/status.go +++ b/pkg/microservice/aslan/core/common/service/github/status.go @@ -47,7 +47,7 @@ type StatusOptions struct { } func (c *Client) UpdateCheckStatus(opt *StatusOptions) error { - sc := setting.ProductName + sc := setting.ProductName + opt.PipeName _, err := c.CreateStatus( context.TODO(), opt.Owner, opt.Repo, opt.Ref, &github.RepoStatus{ diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go b/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go index 8fdc8b31..1a62595f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go @@ -47,7 +47,7 @@ type StatusOptions struct { } func (c *Client) UpdateCheckStatus(opt *StatusOptions) error { - sc := setting.ProductName + sc := setting.ProductName + opt.PipeName _, err := c.CreateStatus( context.TODO(), opt.Owner, opt.Repo, opt.Ref, &github.RepoStatus{ -- Gitee From ae63967b1c9d1881cadaa88e945b7e3fe51abbaf Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 8 Feb 2022 18:36:19 +0800 Subject: [PATCH 1480/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/common/service/kube/status.go | 50 +------------------ .../aslan/core/environment/service/service.go | 2 +- pkg/tool/kube/client/cluster.go | 14 ++---- pkg/tool/kube/informer/informer.go | 26 +++++++++- pkg/tool/kube/multicluster/service.go | 8 +-- 5 files changed, 33 insertions(+), 67 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/status.go b/pkg/microservice/aslan/core/common/service/kube/status.go index aa848a63..58c17a42 100644 --- a/pkg/microservice/aslan/core/common/service/kube/status.go +++ b/pkg/microservice/aslan/core/common/service/kube/status.go @@ -22,58 +22,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/wrapper" "github.com/koderover/zadig/pkg/tool/kube/getter" ) -// TODO: improve this function -func GetSelectedPodsInfo(ns string, selector labels.Selector, kubeClient client.Client, log *zap.SugaredLogger) (string, string, []string) { - pods, err := getter.ListPods(ns, selector, kubeClient) - if err != nil { - return setting.PodError, setting.PodNotReady, nil - } - - if len(pods) == 0 { - return setting.PodNonStarted, setting.PodNotReady, nil - } - - imageSet := sets.String{} - for _, pod := range pods { - ipod := wrapper.Pod(pod) - imageSet.Insert(ipod.Containers()...) - } - images := imageSet.List() - - ready := setting.PodReady - - succeededPods := 0 - for _, pod := range pods { - iPod := wrapper.Pod(pod) - - // skip if pod is a succeeded job - if iPod.Succeeded() { - succeededPods++ - continue - } - - // 如果服务中任意一个pod不在running状态,返回unstable - if !iPod.Ready() { - return setting.PodUnstable, setting.PodNotReady, images - } - } - - // return "Succeeded, Completed" if all pods are succeeded Job pods - if len(pods) == succeededPods { - return string(corev1.PodSucceeded), setting.JobReady, images - } - - return setting.PodRunning, ready, images -} - -func GetSelectedPodsInfoWithCache(selector labels.Selector, informer informers.SharedInformerFactory, log *zap.SugaredLogger) (string, string, []string) { +func GetSelectedPodsInfo(selector labels.Selector, informer informers.SharedInformerFactory, log *zap.SugaredLogger) (string, string, []string) { pods, err := getter.ListPodsWithCache(selector, informer) if err != nil { return setting.PodError, setting.PodNotReady, nil @@ -96,19 +51,16 @@ func GetSelectedPodsInfoWithCache(selector labels.Selector, informer informers.S for _, pod := range pods { iPod := wrapper.Pod(pod) - // skip if pod is a succeeded job if iPod.Succeeded() { succeededPods++ continue } - // 如果服务中任意一个pod不在running状态,返回unstable if !iPod.Ready() { return setting.PodUnstable, setting.PodNotReady, images } } - // return "Succeeded, Completed" if all pods are succeeded Job pods if len(pods) == succeededPods { return string(corev1.PodSucceeded), setting.JobReady, images } diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 3baed84c..557ec3ab 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -453,7 +453,7 @@ func queryPodsStatus(namespace, productName, serviceName string, informer inform if serviceName != "" { ls[setting.ServiceLabel] = serviceName } - return kube.GetSelectedPodsInfoWithCache(ls.AsSelector(), informer, log) + return kube.GetSelectedPodsInfo(ls.AsSelector(), informer, log) } // validateServiceContainer validate container with envName like dev diff --git a/pkg/tool/kube/client/cluster.go b/pkg/tool/kube/client/cluster.go index 1adb6fda..dc83846c 100644 --- a/pkg/tool/kube/client/cluster.go +++ b/pkg/tool/kube/client/cluster.go @@ -35,7 +35,6 @@ import ( var once sync.Once var c cluster.Cluster -var k *kubernetes.Clientset // Cluster is a singleton, it will be initialized only once. func Cluster() cluster.Cluster { @@ -50,19 +49,16 @@ func Cluster() cluster.Cluster { return c } -func NewClientSet() *kubernetes.Clientset { - if k != nil { - return k - } +func NewClientSet() (*kubernetes.Clientset, error) { config, err := rest.InClusterConfig() if err != nil { - panic(err) + return nil, err } - k, err = kubernetes.NewForConfig(config) + k, err := kubernetes.NewForConfig(config) if err != nil { - panic(err) + return nil, err } - return k + return k, nil } func Client() client.Client { diff --git a/pkg/tool/kube/informer/informer.go b/pkg/tool/kube/informer/informer.go index 3bab764e..aca68349 100644 --- a/pkg/tool/kube/informer/informer.go +++ b/pkg/tool/kube/informer/informer.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package informer import ( @@ -28,7 +44,7 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe if clusterID == "" { clusterID = setting.LocalClusterID } - key := fmt.Sprintf(setting.InformerNamingConvention, clusterID, namespace) + key := generateInformerKey(clusterID, namespace) if informer, ok := InformersMap.Load(key); ok { return informer.(informers.SharedInformerFactory) } @@ -50,6 +66,7 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe // if we found that stop channel if stopchan, ok := StopChanMap.Load(key); ok { close(stopchan.(chan struct{})) + StopChanMap.Delete(key) } } InformersMap.Store(key, informerFactory) @@ -58,13 +75,18 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe } func DeleteInformer(clusterID, namespace string) { - key := fmt.Sprintf(setting.InformerNamingConvention, clusterID, namespace) + key := generateInformerKey(clusterID, namespace) // if informer exists if _, ok := InformersMap.Load(key); ok { // if we found that stop channel if stopchan, ok := StopChanMap.Load(key); ok { close(stopchan.(chan struct{})) + StopChanMap.Delete(key) } } InformersMap.Delete(key) } + +func generateInformerKey(clusterID, namespace string) string { + return fmt.Sprintf(setting.InformerNamingConvention, clusterID, namespace) +} diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index c4f2e9ef..882f5ea6 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -46,7 +46,7 @@ func GetKubeClient(hubServerAddr, clusterID string) (client.Client, error) { func GetKubeClientSet(hubServerAddr, clusterID string) (*kubernetes.Clientset, error) { if clusterID == "" { - return krkubeclient.NewClientSet(), nil + return krkubeclient.NewClientSet() } clusterService, err := NewAgent(hubServerAddr) @@ -141,11 +141,7 @@ func (s *Agent) GetKubeClient(clusterID string) (client.Client, error) { func (s *Agent) GetClientGoKubeClient(clusterID string) (*kubernetes.Clientset, error) { config := generateRestConfig(clusterID, s.hubServerAddr) - cl, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - return cl, nil + return kubernetes.NewForConfig(config) } func (s *Agent) ClusterConnected(clusterID string) bool { -- Gitee From 52148854b2caf24b866b0b7e9d98a918e3fe9994 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 8 Feb 2022 18:55:28 +0800 Subject: [PATCH 1481/2647] bugfix Signed-off-by: panxunying --- .../service/collaboration_instance.go | 166 +++++++++++++----- pkg/middleware/gin/collaboration.go | 7 + 2 files changed, 125 insertions(+), 48 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 3d8f978a..cb721ec8 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -81,6 +81,7 @@ type GetCollaborationNewResp struct { Code int64 `json:"code"` Workflow []*Workflow `json:"workflow"` Product []*Product `json:"product"` + IfSync bool `json:"ifSync"` } type GetCollaborationDeleteResp struct { @@ -496,22 +497,6 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Type: "system", }) } - for _, workflow := range item.DeleteSpec.Workflows { - deleteLabels = append(deleteLabels, mongodb2.Label{ - Key: "policy", - Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, - string(config2.ResourceTypeWorkflow), workflow.Name), - Type: "system", - }) - } - for _, product := range item.DeleteSpec.Products { - deleteLabels = append(deleteLabels, mongodb2.Label{ - Key: "policy", - Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, - string(config2.ResourceTypeProduct), product.Name), - Type: "system", - }) - } for _, workflow := range item.DeleteSpec.Workflows { deleteLabels = append(deleteLabels, mongodb2.Label{ Key: "policy", @@ -528,6 +513,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Type: "system", }) } + } for _, instance := range updateResp.Delete { for _, workflow := range instance.Workflows { @@ -547,10 +533,9 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }) } } - var labelResp *service.CreateLabelsResp var err error if len(labels) > 0 { - labelResp, err = service.CreateLabels(&service.CreateLabelsArgs{ + _, err := service.CreateLabels(&service.CreateLabelsArgs{ Labels: labels, }, userName) @@ -596,6 +581,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } } + labelIdMap := make(map[string]string) if len(labels) > 0 { labelListResp, err := service.ListLabels(&service.ListLabelsArgs{ Labels: labels, @@ -609,7 +595,6 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType if len(labelListResp.Labels) != len(labels) { return fmt.Errorf("label not exist %v:%v", labels, labelListResp.Labels) } - labelIdMap := make(map[string]string) for _, label := range labelListResp.Labels { labelIdMap[service.BuildLabelString(label.Key, label.Value)] = label.ID.Hex() @@ -620,7 +605,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType for _, mode := range updateResp.New { for _, workflow := range mode.Workflows { labelValue := buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } @@ -633,13 +618,13 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Resource: mongodb2.Resource{ Name: name, ProjectName: projectName, - Type: "Workflow", + Type: string(config2.ResourceTypeWorkflow), }, }) } for _, product := range mode.Products { labelValue := buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } @@ -658,29 +643,66 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } } for _, item := range updateResp.Update { - for _, workflow := range item.NewSpec.Workflows { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } + name := workflow.Name + if workflow.CollaborationType == config.CollaborationNew { + name = buildName(workflow.Name, item.CollaborationMode, identityType, userName) + } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ - Name: workflow.Name, - Type: "Workflow", + Name: name, + Type: string(config2.ResourceTypeWorkflow), ProjectName: projectName, }, }) } for _, product := range item.NewSpec.Products { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } + name := product.Name + if product.CollaborationType == config.CollaborationNew { + name = buildName(product.Name, item.CollaborationMode, identityType, userName) + } newBindings = append(newBindings, &mongodb2.LabelBinding{ + LabelID: labelId, + Resource: mongodb2.Resource{ + Name: name, + Type: string(config2.ResourceTypeProduct), + ProjectName: projectName, + }, + }) + } + for _, workflow := range item.DeleteSpec.Workflows { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName) + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ + LabelID: labelId, + Resource: mongodb2.Resource{ + Name: workflow.Name, + Type: string(config2.ResourceTypeWorkflow), + ProjectName: projectName, + }, + }) + } + for _, product := range item.DeleteSpec.Products { + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName) + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] + if !ok { + return fmt.Errorf("label:%s not exist", labelValue) + } + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ Name: product.Name, @@ -690,63 +712,91 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }) } for _, workflow := range item.UpdateSpec.Workflows { - if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == - config.CollaborationNew { + if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == config.CollaborationNew { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ - Name: workflow.New.Name, - Type: "Workflow", + Name: buildName(workflow.New.Name, item.CollaborationMode, identityType, userName), + Type: string(config2.ResourceTypeWorkflow), + ProjectName: projectName, + }, + }) + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ + LabelID: labelId, + Resource: mongodb2.Resource{ + Name: workflow.Old.BaseName, + Type: string(config2.ResourceTypeWorkflow), ProjectName: projectName, }, }) } - if workflow.Old.CollaborationType == config.CollaborationNew && workflow.New.CollaborationType == - config.CollaborationShare { + if workflow.Old.CollaborationType == config.CollaborationNew && workflow.New.CollaborationType == config.CollaborationShare { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } - deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ + newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ Name: workflow.New.Name, - Type: "Workflow", + Type: string(config2.ResourceTypeWorkflow), + ProjectName: projectName, + }, + }) + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ + LabelID: labelId, + Resource: mongodb2.Resource{ + Name: workflow.Old.Name, + Type: string(config2.ResourceTypeWorkflow), ProjectName: projectName, }, }) } } for _, product := range item.UpdateSpec.Products { - if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == - config.CollaborationNew { + if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == config.CollaborationNew { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } newBindings = append(newBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ - Name: product.New.Name, + Name: buildName(product.New.Name, item.CollaborationMode, identityType, userName), + Type: string(config2.ResourceTypeProduct), + ProjectName: projectName, + }, + }) + deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ + LabelID: labelId, + Resource: mongodb2.Resource{ + Name: product.Old.BaseName, Type: string(config2.ResourceTypeProduct), ProjectName: projectName, }, }) } - if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == - config.CollaborationShare { + if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == config.CollaborationShare { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name) - labelId, ok := labelResp.LabelMap[service.BuildLabelString("policy", labelValue)] + labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) } + newBindings = append(newBindings, &mongodb2.LabelBinding{ + LabelID: labelId, + Resource: mongodb2.Resource{ + Name: product.New.Name, + Type: string(config2.ResourceTypeProduct), + ProjectName: projectName, + }, + }) deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ LabelID: labelId, Resource: mongodb2.Resource{ @@ -898,17 +948,24 @@ func SyncCollaborationInstance(products *SyncCollaborationInstanceArgs, projectN } err = syncInstance(updateResp, projectName, identityType, userName, uid, logger) if err != nil { + logger.Errorf("syncInstance error, err msg:%s", err) return err } err = syncResource(products, updateResp, projectName, identityType, userName, requestID, logger) if err != nil { + logger.Errorf("syncResource error, err msg:%s", err) return err } err = syncPolicy(updateResp, projectName, identityType, userName, uid, logger) if err != nil { + logger.Errorf("syncPolicy error, err msg:%s", err) return err } err = syncLabel(updateResp, projectName, identityType, userName, logger) + if err != nil { + logger.Errorf("syncLabel error, err msg:%s", err) + return err + } return nil } @@ -917,18 +974,26 @@ func getCollaborationDelete(updateResp *GetCollaborationUpdateResp) *GetCollabor workflowSet := sets.String{} for _, item := range updateResp.Delete { for _, product := range item.Products { - productSet.Insert(product.Name) + if product.CollaborationType == config.CollaborationNew { + productSet.Insert(product.Name) + } } for _, workflow := range item.Workflows { - workflowSet.Insert(workflow.Name) + if workflow.CollaborationType == config.CollaborationNew { + workflowSet.Insert(workflow.Name) + } } } for _, item := range updateResp.Update { for _, deleteWorkflow := range item.DeleteSpec.Workflows { - workflowSet.Insert(deleteWorkflow.Name) + if deleteWorkflow.CollaborationType == config.CollaborationNew { + workflowSet.Insert(deleteWorkflow.Name) + } } for _, deleteProduct := range item.DeleteSpec.Products { - productSet.Insert(deleteProduct.Name) + if deleteProduct.CollaborationType == config.CollaborationNew { + productSet.Insert(deleteProduct.Name) + } } for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == config.CollaborationNew && @@ -1072,10 +1137,15 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id workflow.Description = desc } } + ifSync := false + if len(newWorkflow) == 0 && len(newProductName) == 0 && len(updateResp.Update) != 0 || len(updateResp.Delete) != 0 { + ifSync = true + } return &GetCollaborationNewResp{ Code: 10000, Workflow: newWorkflow, Product: newProduct, + IfSync: ifSync, }, nil } func GetCollaborationNew(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index 332c8609..cf04e1a4 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -22,6 +22,13 @@ func GetCollaborationNew() gin.HandlerFunc { ctx.Err = err c.Abort() } + if newResp.IfSync { + err := service.SyncCollaborationInstance(nil, projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.RequestID, ctx.Logger) + if err != nil { + ctx.Err = err + c.Abort() + } + } if len(newResp.Product) == 0 && len(newResp.Workflow) == 0 { c.Next() } else { -- Gitee From 09d551d91ffd4d3e3dd6767debc9bd8b179d5a99 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 8 Feb 2022 19:42:44 +0800 Subject: [PATCH 1482/2647] add line Signed-off-by: mouuii --- pkg/microservice/aslan/core/common/service/github/status.go | 2 +- .../warpdrive/core/service/taskplugin/github/status.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/github/status.go b/pkg/microservice/aslan/core/common/service/github/status.go index 1f48793a..18b8edb2 100644 --- a/pkg/microservice/aslan/core/common/service/github/status.go +++ b/pkg/microservice/aslan/core/common/service/github/status.go @@ -47,7 +47,7 @@ type StatusOptions struct { } func (c *Client) UpdateCheckStatus(opt *StatusOptions) error { - sc := setting.ProductName + opt.PipeName + sc := setting.ProductName + "/" + opt.PipeName _, err := c.CreateStatus( context.TODO(), opt.Owner, opt.Repo, opt.Ref, &github.RepoStatus{ diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go b/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go index 1a62595f..b39985d7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/github/status.go @@ -47,7 +47,7 @@ type StatusOptions struct { } func (c *Client) UpdateCheckStatus(opt *StatusOptions) error { - sc := setting.ProductName + opt.PipeName + sc := setting.ProductName + "/" + opt.PipeName _, err := c.CreateStatus( context.TODO(), opt.Owner, opt.Repo, opt.Ref, &github.RepoStatus{ -- Gitee From e516b1e7b729ab6b252973cd6b3f24a8a8c7a345 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 8 Feb 2022 19:57:24 +0800 Subject: [PATCH 1483/2647] add policy registry url Signed-off-by: panxunying --- .../core/service/bundle/exemption_urls.go | 68 +++++++++++++++++++ pkg/shared/client/policy/policy.go | 4 +- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index ff8a2be8..65715c85 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -256,6 +256,22 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"DELETE"}, Endpoints: []string{"api/v1/system-roles/?*"}, }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/public-policies"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/v1/public-policies/?*"}, + }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/v1/system-policies"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/system-policies/?*"}, + }, { Methods: []string{"GET", "POST"}, Endpoints: []string{"api/v1/system-rolebindings"}, @@ -264,6 +280,14 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"DELETE"}, Endpoints: []string{"api/v1/system-rolebindings/?*"}, }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/v1/system-policybindings"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/system-policybindings/?*"}, + }, { Methods: []string{"POST"}, Endpoints: []string{"api/aslan/template/yaml"}, @@ -344,6 +368,34 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/v1/public-roles/?*"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/policies"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/policies/?*"}, + }, + { + Methods: []string{"POST", "DELETE"}, + Endpoints: []string{"api/v1/policies"}, + }, + { + Methods: []string{"PUT", "DELETE"}, + Endpoints: []string{"api/v1/policies/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/policies/bulk-delete"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/public-policies"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/public-policies/?*"}, + }, { Methods: []string{"GET", "POST"}, Endpoints: []string{"api/v1/rolebindings"}, @@ -360,6 +412,22 @@ var projectAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/v1/rolebindings/bulk-delete"}, }, + { + Methods: []string{"GET", "POST"}, + Endpoints: []string{"api/v1/policybindings"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/picket/policybindings"}, + }, + { + Methods: []string{"DELETE"}, + Endpoints: []string{"api/v1/policybindings/?*"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/v1/policybindings/bulk-delete"}, + }, } var adminURLs = append(systemAdminURLs, projectAdminURLs...) diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 4d52df01..5329082e 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -75,9 +75,9 @@ func (c *Client) CreatePolicies(ns string, request CreatePoliciesArgs) error { } func (c *Client) DeletePolicies(ns string, request DeletePoliciesArgs) error { - url := fmt.Sprintf("/policies?projectName=%s", ns) + url := fmt.Sprintf("/policies/bulk-delete?projectName=%s", ns) - _, err := c.Delete(url, httpclient.SetBody(request)) + _, err := c.Post(url, httpclient.SetBody(request)) if err != nil { log.Errorf("Failed to add audit log, error: %s", err) return err -- Gitee From 750ca77c4a25477cfe1d4a435e0b3456b8114b05 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 9 Feb 2022 10:13:58 +0800 Subject: [PATCH 1484/2647] code review changes II Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/environment.go | 5 ++++- pkg/tool/kube/client/cluster.go | 6 +----- pkg/tool/kube/informer/informer.go | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 88b1af00..45eb5c0d 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -269,10 +269,13 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } hostInfos := make([]resource.HostInfo, 0) - if ingresses, err := getter.ListIngresses(nil, informer); err == nil { + ingresses, err := getter.ListIngresses(nil, informer) + if err == nil { for _, ingress := range ingresses { hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) } + } else { + log.Warnf("Failed to list ingresses, the error is: %s", err) } // get all services diff --git a/pkg/tool/kube/client/cluster.go b/pkg/tool/kube/client/cluster.go index dc83846c..6b575f94 100644 --- a/pkg/tool/kube/client/cluster.go +++ b/pkg/tool/kube/client/cluster.go @@ -54,11 +54,7 @@ func NewClientSet() (*kubernetes.Clientset, error) { if err != nil { return nil, err } - k, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err - } - return k, nil + return kubernetes.NewForConfig(config) } func Client() client.Client { diff --git a/pkg/tool/kube/informer/informer.go b/pkg/tool/kube/informer/informer.go index aca68349..eac06f4a 100644 --- a/pkg/tool/kube/informer/informer.go +++ b/pkg/tool/kube/informer/informer.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 62093717e65adefb7b27db8f10445b6a2250fef5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 9 Feb 2022 11:07:12 +0800 Subject: [PATCH 1485/2647] resolve comments Signed-off-by: allenshen --- pkg/tool/helmclient/helmclient.go | 35 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 22e96a0e..4c99c1e8 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -19,6 +19,7 @@ package helmclient import ( "context" "encoding/json" + "errors" "fmt" "reflect" "strings" @@ -252,12 +253,12 @@ func (hClient *HelmClient) upgradeCRD(ctx context.Context, k8sClient *clientset. } switch typeMeta.APIVersion { - default: - return fmt.Errorf("WARNING: failed to upgrade CRD %q: unsupported api-version %q", crd.Name, typeMeta.APIVersion) case "apiextensions.k8s.io/v1beta1": return hClient.upgradeCRDV1Beta1(ctx, k8sClient, crd.File.Data) case "apiextensions.k8s.io/v1": return hClient.upgradeCRDV1(ctx, k8sClient, crd.File.Data) + default: + return fmt.Errorf("WARNING: failed to upgrade CRD %q: unsupported api-version %q", crd.Name, typeMeta.APIVersion) } } @@ -279,7 +280,7 @@ func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) // pending status if lastRelease.Info.Status.IsPending() { - return false, fmt.Errorf("another operation (install/upgrade/rollback) is in progress, please try later") + return false, errors.New("another operation (install/upgrade/rollback) is in progress, please try later") } // release with deployed/failed/superseded status: normal upgrade operation @@ -304,7 +305,6 @@ func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) // getChart returns a chart matching the provided chart name and options. func (hClient *HelmClient) getChart(chartName string, chartPathOptions *action.ChartPathOptions) (*chart.Chart, string, error) { - chartPath, err := chartPathOptions.LocateChart(chartName, hClient.HelmClient.Settings) if err != nil { return nil, "", err @@ -323,7 +323,6 @@ func (hClient *HelmClient) getChart(chartName string, chartPathOptions *action.C } func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { - c := hClient.HelmClient client := action.NewInstall(c.ActionConfig) mergeInstallOptions(spec, client) @@ -347,19 +346,18 @@ func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) if req := helmChart.Metadata.Dependencies; req != nil { if err := action.CheckDependencies(helmChart, req); err != nil { - if client.DependencyUpdate { - man := &downloader.Manager{ - ChartPath: chartPath, - Keyring: client.ChartPathOptions.Keyring, - SkipUpdate: false, - Getters: c.Providers, - RepositoryConfig: c.Settings.RepositoryConfig, - RepositoryCache: c.Settings.RepositoryCache, - } - if err := man.Update(); err != nil { - return nil, err - } - } else { + if !client.DependencyUpdate { + return nil, err + } + man := &downloader.Manager{ + ChartPath: chartPath, + Keyring: client.ChartPathOptions.Keyring, + SkipUpdate: false, + Getters: c.Providers, + RepositoryConfig: c.Settings.RepositoryConfig, + RepositoryCache: c.Settings.RepositoryCache, + } + if err := man.Update(); err != nil { return nil, err } } @@ -381,7 +379,6 @@ func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) } func (hClient *HelmClient) upgradeChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { - c := hClient.HelmClient client := action.NewUpgrade(c.ActionConfig) mergeUpgradeOptions(spec, client) -- Gitee From 91ed386e5e738e76208ea2dcba3a63c61412a045 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 9 Feb 2022 14:04:05 +0800 Subject: [PATCH 1486/2647] fix sync collaboration bugs Signed-off-by: panxunying --- .../service/collaboration_instance.go | 43 ++++--------------- .../collaboration/collaboration_mode.go | 7 --- .../core/environment/service/environment.go | 24 +++++++---- .../aslan/core/label/service/label.go | 2 +- .../workflow/service/workflow/workflow.go | 16 ++++--- 5 files changed, 33 insertions(+), 59 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index cb721ec8..875b7776 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -681,36 +681,6 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }, }) } - for _, workflow := range item.DeleteSpec.Workflows { - labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName) - labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] - if !ok { - return fmt.Errorf("label:%s not exist", labelValue) - } - deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ - LabelID: labelId, - Resource: mongodb2.Resource{ - Name: workflow.Name, - Type: string(config2.ResourceTypeWorkflow), - ProjectName: projectName, - }, - }) - } - for _, product := range item.DeleteSpec.Products { - labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName) - labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] - if !ok { - return fmt.Errorf("label:%s not exist", labelValue) - } - deleteBindings = append(deleteBindings, &mongodb2.LabelBinding{ - LabelID: labelId, - Resource: mongodb2.Resource{ - Name: product.Name, - Type: string(config2.ResourceTypeProduct), - ProjectName: projectName, - }, - }) - } for _, workflow := range item.UpdateSpec.Workflows { if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == config.CollaborationNew { labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name) @@ -877,6 +847,7 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol ProjectName: projectName, Old: workflow.BaseName, New: workflow.Name, + BaseName: workflow.BaseName, }) } } @@ -901,15 +872,17 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol helmProductArgs = append(helmProductArgs, service2.HelmProductItem{ OldName: product.BaseName, NewName: product.Name, + BaseName: product.BaseName, DefaultValues: product.DefaultValues, ChartValues: product.ChartValues, }) } if productArg.DeployType == string(config.K8sDeploy) { yamlProductItems = append(yamlProductItems, service2.YamlProductItem{ - OldName: product.BaseName, - NewName: product.Name, - Vars: productArg.Vars, + OldName: product.BaseName, + NewName: product.Name, + BaseName: product.BaseName, + Vars: productArg.Vars, }) } } @@ -1075,7 +1048,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id newProductName.Insert(product.Name) } for _, workflow := range item.UpdateSpec.Workflows { - if workflow.Old.CollaborationType == "share" && workflow.New.CollaborationType == "new" { + if workflow.Old.CollaborationType == config.CollaborationShare && workflow.New.CollaborationType == config.CollaborationNew { newWorkflow = append(newWorkflow, &Workflow{ CollaborationType: workflow.New.CollaborationType, BaseName: workflow.Old.BaseName, @@ -1085,7 +1058,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id } } for _, product := range item.UpdateSpec.Products { - if product.Old.CollaborationType == "share" && product.New.CollaborationType == "new" { + if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == config.CollaborationNew { newProduct = append(newProduct, &Product{ CollaborationType: product.New.CollaborationType, BaseName: product.Old.BaseName, diff --git a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go index 864cd77e..08ed50a5 100644 --- a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go +++ b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go @@ -5,7 +5,6 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - config2 "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" ) @@ -44,9 +43,6 @@ func buildEnvCMMap(collaborations []*models.CollaborationMode) map[string]sets.S envCMMap := make(map[string]sets.String) for _, cm := range collaborations { for _, product := range cm.Products { - if product.CollaborationType == config2.CollaborationShare { - continue - } key := BuildEnvCMMapKey(cm.ProjectName, product.Name) if cmSet, ok := envCMMap[key]; ok { cmSet.Insert(cm.Name) @@ -64,9 +60,6 @@ func buildWorkflowCMMap(collaborations []*models.CollaborationMode) map[string]s workflowCMMap := make(map[string]sets.String) for _, cm := range collaborations { for _, workflow := range cm.Workflows { - if workflow.CollaborationType == config2.CollaborationShare { - continue - } key := BuildWorkflowCMMapKey(cm.ProjectName, workflow.Name) if cmSet, ok := workflowCMMap[key]; ok { cmSet.Insert(cm.Name) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 72c736b5..087a9941 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -155,6 +155,7 @@ type CreateHelmProductArg struct { RegistryID string `json:"registry_id"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` BaseEnvName string `json:"baseEnvName"` + BaseName string `json:"base_name,omitempty"` } type UpdateMultiHelmProductArg struct { @@ -831,19 +832,21 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, } type YamlProductItem struct { - OldName string - NewName string - Vars []*templatemodels.RenderKV + OldName string `json:"old_name"` + NewName string `json:"new_name"` + BaseName string `json:"base_name"` + Vars []*templatemodels.RenderKV `json:"vars"` } type CopyYamlProductArg struct { - Items []YamlProductItem + Items []YamlProductItem `json:"items"` } type HelmProductItem struct { - OldName string - NewName string - DefaultValues string - ChartValues []*commonservice.RenderChartArg + OldName string `json:"old_name"` + NewName string `json:"new_name"` + BaseName string `json:"base_name"` + DefaultValues string `json:"default_values"` + ChartValues []*commonservice.RenderChartArg `json:"chart_values"` } type CopyHelmProductArg struct { @@ -875,11 +878,12 @@ func BulkCopyHelmProduct(projectName, user, requestID string, arg CopyHelmProduc args = append(args, &CreateHelmProductArg{ ProductName: projectName, EnvName: item.NewName, - Namespace: product.Namespace, + Namespace: "helm-env-" + item.NewName, ClusterID: product.ClusterID, DefaultValues: item.DefaultValues, RegistryID: product.RegistryID, BaseEnvName: product.BaseName, + BaseName: item.BaseName, ChartValues: item.ChartValues, }) } else { @@ -917,6 +921,7 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc product.Render.Name = product.Namespace product.ID = primitive.NilObjectID product.Render.Revision = 0 + product.BaseName = item.BaseName err = CreateProduct(user, requestID, product, log) if err != nil { return err @@ -961,6 +966,7 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH productInfo.EnvName = arg.EnvName productInfo.UpdateBy = userName productInfo.ClusterID = arg.ClusterID + productInfo.BaseName = arg.BaseName productInfo.Namespace = commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace) opt := &commonrepo.RenderSetFindOption{ diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 304dd5ab..91b1df05 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -187,7 +187,7 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg resourceKey := config.BuildResourceKey(labelBinding.ResourceType, labelBinding.ProjectName, labelBinding.ResourceName) label, ok := labelM[labelBinding.LabelID] if !ok { - return nil, fmt.Errorf("can not find label %v", label) + return nil, fmt.Errorf("can not find label %v", labelBinding.LabelID) } if arr, ok := res[resourceKey]; ok { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 726763fa..30669d9e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -669,9 +669,10 @@ func CopyWorkflow(oldWorkflowName, newWorkflowName, username string, log *zap.Su } type WorkflowCopyItem struct { - ProjectName string - Old string - New string + ProjectName string `json:"project_name"` + Old string `json:"old"` + New string `json:"new"` + BaseName string `json:"base_name"` } type BulkCopyWorkflowArgs struct { @@ -680,13 +681,13 @@ type BulkCopyWorkflowArgs struct { func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.SugaredLogger) error { var workflows []commonrepo.Workflow - workflowMap := make(map[string]string) + workflowMap := make(map[string]WorkflowCopyItem) for _, item := range args.Items { workflows = append(workflows, commonrepo.Workflow{ ProjectName: item.ProjectName, Name: item.Old, }) - workflowMap[item.ProjectName+"~"+item.Old] = item.New + workflowMap[item.ProjectName+"~"+item.Old] = item } oldWorkflows, err := commonrepo.NewWorkflowColl().ListByWorkflows(commonrepo.ListWorkflowOpt{ Workflows: workflows, @@ -697,9 +698,10 @@ func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.Sugar } var newWorkflows []*commonmodels.Workflow for _, workflow := range oldWorkflows { - if newName, ok := workflowMap[workflow.ProductTmplName+"~"+workflow.Name]; ok { + if item, ok := workflowMap[workflow.ProductTmplName+"~"+workflow.Name]; ok { workflow.UpdateBy = username - workflow.Name = newName + workflow.Name = item.New + workflow.BaseName = item.BaseName workflow.ID = primitive.NewObjectID() newWorkflows = append(newWorkflows, workflow) } else { -- Gitee From e11a496a4edefbc723ee765f93b81eadde621aa0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 9 Feb 2022 14:18:59 +0800 Subject: [PATCH 1487/2647] delete collbaration first Signed-off-by: mouuii --- .../aslan/core/project/service/product.go | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 57729ec6..641daee0 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -383,6 +383,12 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return err } + // delete collaboration_mode and collaboration_instance + if err := DeleteCollabrationMode(productName, userName, log); err != nil { + log.Errorf("DeleteCollabrationMode err:%s", err) + return err + } + if err = commonservice.DeleteWorkflows(productName, requestID, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s workflow err: %s", productName, err) return err @@ -461,25 +467,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug ProductName: productName, }) }() - // delete collaboration_mode and collaboration_instance - go func() { - // find all collaboration mode in this project - res, err := collaboration.GetCollaborationModes([]string{productName}, log) - if err != nil { - log.Errorf("GetCollaborationModes err: %s", err) - } - // delete all collaborationMode - for _, mode := range res.Collaborations { - if err := service.DeleteCollaborationMode(userName, productName, mode.Name, log); err != nil { - log.Errorf("DeleteCollaborationMode err: %s", err) - } - } - // delete all collaborationIns - if err := mongodb.NewCollaborationInstanceColl().DeleteByProject(productName); err != nil { - log.Errorf("NewCollaborationInstanceColl DeleteByProject err:%s", err) - } - - }() // delete policy go func() { query := bson.M{} @@ -939,3 +926,25 @@ func reParseServices(userName string, serviceList []*commonmodels.Service, match return nil } + +func DeleteCollabrationMode(productName string, userName string, log *zap.SugaredLogger) error { + // find all collaboration mode in this project + res, err := collaboration.GetCollaborationModes([]string{productName}, log) + if err != nil { + log.Errorf("GetCollaborationModes err: %s", err) + return err + } + // delete all collaborationMode + for _, mode := range res.Collaborations { + if err := service.DeleteCollaborationMode(userName, productName, mode.Name, log); err != nil { + log.Errorf("DeleteCollaborationMode err: %s", err) + return err + } + } + // delete all collaborationIns + if err := mongodb.NewCollaborationInstanceColl().DeleteByProject(productName); err != nil { + log.Errorf("NewCollaborationInstanceColl DeleteByProject err:%s", err) + return err + } + return nil +} -- Gitee From a73e3adf02e83069a4d9551b8792ef691519f2c0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 9 Feb 2022 14:51:03 +0800 Subject: [PATCH 1488/2647] delete policy Signed-off-by: mouuii --- .../aslan/core/project/service/product.go | 31 +++++++++---------- .../policy/core/service/policy.go | 6 ++-- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 641daee0..dfc08a61 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -17,7 +17,6 @@ limitations under the License. package service import ( - "context" "encoding/json" "fmt" "regexp" @@ -26,8 +25,6 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" @@ -50,7 +47,6 @@ import ( configclient "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) type CustomParseDataArgs struct { @@ -389,6 +385,11 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return err } + if err = DeletePolicy(productName, log); err != nil { + log.Errorf("DeletePolicy productName %s err: %s", productName, err) + return err + } + if err = commonservice.DeleteWorkflows(productName, requestID, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s workflow err: %s", productName, err) return err @@ -468,23 +469,10 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug }) }() // delete policy - go func() { - query := bson.M{} - query["namespace"] = productName - _, err := newPolicyMetaColl().DeleteMany(context.Background(), query) - if err != nil { - log.Errorf("newPolicyMetaColl delete err: %s", err) - } - }() return nil } -func newPolicyMetaColl() *mongo.Collection { - collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("policy_meta") - return collection -} - func ForkProduct(username, uid, requestID string, args *template.ForkProject, log *zap.SugaredLogger) error { prodTmpl, err := templaterepo.NewProductColl().Find(args.ProductName) @@ -948,3 +936,12 @@ func DeleteCollabrationMode(productName string, userName string, log *zap.Sugare } return nil } + +func DeletePolicy(productName string, log *zap.SugaredLogger) error { + policy.NewDefault() + if err := policy.NewDefault().DeletePolicies(productName, policy.DeletePoliciesArgs{}); err != nil { + log.Errorf("DeletePolicies err :%s", err) + return err + } + return nil +} diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 3b4a6beb..54db2e4c 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -193,9 +193,9 @@ func DeletePolicy(name string, projectName string, logger *zap.SugaredLogger) er } func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogger) error { - if len(names) == 0 { - return nil - } + //if len(names) == 0 { + // return nil + //} if projectName == "" { return fmt.Errorf("projectName is empty") } -- Gitee From 19acd74d262d73fea374848f7a8a865731d99d9a Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 9 Feb 2022 14:55:38 +0800 Subject: [PATCH 1489/2647] add fail lock Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 577fc79d..0330f687 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2480,6 +2480,7 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic return nil } wg := sync.WaitGroup{} + var failLock sync.Mutex wg.Add(len(serviceList)) errList := make([]error, 0) ticker := time.NewTicker(interval) @@ -2490,6 +2491,8 @@ func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Servic defer wg.Done() err := handler(singleService, isRetry, log) if err != nil { + failLock.Lock() + defer failLock.Unlock() errList = append(errList, err) *failedServices = append(*failedServices, singleService) log.Errorf("service:%s apply failed, err %s", singleService.ServiceName, err) -- Gitee From 52f4802017f13acd5f9730bbb55f9161cf5c5e42 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 9 Feb 2022 15:01:31 +0800 Subject: [PATCH 1490/2647] delete policy Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 4 +++- pkg/microservice/policy/core/service/policy.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index dfc08a61..cd09aabd 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -939,7 +939,9 @@ func DeleteCollabrationMode(productName string, userName string, log *zap.Sugare func DeletePolicy(productName string, log *zap.SugaredLogger) error { policy.NewDefault() - if err := policy.NewDefault().DeletePolicies(productName, policy.DeletePoliciesArgs{}); err != nil { + if err := policy.NewDefault().DeletePolicies(productName, policy.DeletePoliciesArgs{ + Names: []string{}, + }); err != nil { log.Errorf("DeletePolicies err :%s", err) return err } diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 54db2e4c..6cbeae7e 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -200,7 +200,7 @@ func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogge return fmt.Errorf("projectName is empty") } - if names[0] == "*" { + if len(names) == 1 && names[0] == "*" { names = []string{} } -- Gitee From 505ee430a36d3e29820f86beaf7fe0f34a79cb41 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 9 Feb 2022 15:47:08 +0800 Subject: [PATCH 1491/2647] delete policy Signed-off-by: mouuii --- .../policy/core/repository/mongodb/policy_binding.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/policy_binding.go b/pkg/microservice/policy/core/repository/mongodb/policy_binding.go index be848b31..69c30869 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy_binding.go @@ -153,11 +153,11 @@ func (c *PolicyBindingColl) DeleteByPolicies(policyNames []string, projectName s if projectName == "" { return fmt.Errorf("projectName is empty") } - if len(policyNames) == 0 { - return nil - } - query := bson.M{"policy_ref.name": bson.M{"$in": policyNames}, "policy_ref.namespace": projectName, "namespace": projectName} + query := bson.M{"policy_ref.namespace": projectName, "namespace": projectName} + if len(policyNames) != 0 { + query["policy_ref.name"] = bson.M{"$in": policyNames} + } _, err := c.Collection.DeleteMany(context.TODO(), query) return err -- Gitee From 8de929e8c130405484aa1a85101d7556344ca3c0 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 9 Feb 2022 16:02:13 +0800 Subject: [PATCH 1492/2647] merge main Signed-off-by: panxunying --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 6 ++---- .../aslan/core/environment/service/environment.go | 2 +- pkg/microservice/aslan/server/rest/router.go | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 3f7908d3..21377c7e 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -24,11 +24,11 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" "github.com/koderover/zadig/pkg/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -58,7 +58,6 @@ func V190ToV1100() error { return err } - if err := migrateModuleBuild(); err != nil { return fmt.Errorf("failed to migrate data in `zadig.module_build`: %s", err) } @@ -309,10 +308,9 @@ func createLabelBindings() error { log.Errorf("Failed toCreateMany labels, err: %s", err) return err } + return nil } - - func migrateModuleBuild() error { buildCol := internalmongodb.NewBuildColl() diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 86c6d0c9..0d876d2c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -99,7 +99,7 @@ type EnvResp struct { RegistryID string `json:"registry_id"` BaseRefs []string `json:"base_refs"` BaseName string `json:"base_name"` - IsExisted bool `json:"is_existed"` + IsExisted bool `json:"is_existed"` } type ProductResp struct { diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 4af0da80..59ea4ddf 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -89,7 +89,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/template": new(templatehandler.Router), "/api/collaboration": new(collaborationhandler.Router), "/api/label": new(labelhandler.Router), - "/api/stat": new(stathandler.Router), + "/api/stat": new(stathandler.Router), } { r.Inject(router.Group(name)) } -- Gitee From d9f5487a18ead65d1cdbec9d3a1f571aeb2705fe Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Feb 2022 10:45:16 +0800 Subject: [PATCH 1493/2647] delete labels Signed-off-by: mouuii --- .../aslan/core/label/handler/labels.go | 9 ++++++ .../aslan/core/label/handler/router.go | 1 + .../core/label/repository/mongodb/label.go | 30 +++++++++++++++++++ .../aslan/core/label/service/label.go | 24 +++++++++++++++ .../aslan/core/project/service/product.go | 14 +++++++++ pkg/shared/client/label/label.go | 11 +++++++ 6 files changed, 89 insertions(+) diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index df6ca54c..5f18299b 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -82,6 +82,15 @@ func DeleteLabels(c *gin.Context) { ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.Logger) } +func DeleteLabelsAndBindingsByProject(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + + ctx.Err = service.DeleteLabelsAndBindingsByProject(projectName, ctx.Logger) +} + func ListResourcesByLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 83328ea5..2dfa72e8 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -28,6 +28,7 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.POST("/filter", ListLabels) labels.POST("/bulk-create", CreateLabels) labels.POST("/bulk-delete", DeleteLabels) + labels.POST("/bulk-delete-project", DeleteLabelsAndBindingsByProject) labels.POST("/resources-by-labels", ListResourcesByLabels) labels.POST("/labels-by-resources", ListLabelsByResources) } diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 0fe2be5a..572b3c3e 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -91,6 +91,35 @@ func (c *LabelColl) BulkDelete(ids []string) error { return err } +func (c *LabelColl) BulkDeleteByProject(projectName string) error { + if projectName == "" { + return nil + } + + query := bson.M{} + query["project_name"] = projectName + _, err := c.DeleteMany(context.TODO(), query) + return err +} + +func (c *LabelColl) ListByProjectName(projectName string) ([]*models.Label, error) { + query := bson.M{} + var res []*models.Label + query["project_name"] = projectName + opts := options.Find() + ctx := context.Background() + opts.SetSort(bson.D{{"key", 1}}) + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + return res, nil +} + func (c *LabelColl) Find(id string) (*models.Label, error) { res := new(models.Label) oid, err := primitive.ObjectIDFromHex(id) @@ -138,6 +167,7 @@ func (c *LabelColl) List(opt ListLabelOpt) ([]*models.Label, error) { } return res, nil } + func (c *LabelColl) ListByIDs(ids []string) ([]*models.Label, error) { var res []*models.Label ctx := context.Background() diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 91b1df05..4d9cd473 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -236,3 +236,27 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err return mongodb.NewLabelColl().BulkDelete(ids) } + +func DeleteLabelsAndBindingsByProject(projectName string, logger *zap.SugaredLogger) error { + + labels, err := mongodb.NewLabelColl().ListByProjectName(projectName) + if err != nil { + return err + } + + var labelBindingIDs []string + for _, labelBinding := range labels { + labelBindingIDs = append(labelBindingIDs, labelBinding.ID.Hex()) + } + + if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { + logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) + return err + } + + if err := mongodb.NewLabelColl().BulkDeleteByProject(projectName); err != nil { + logger.Errorf("DeleteLabelsByProject err:%s", err) + return err + } + return nil +} diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index cd09aabd..a4355d8d 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -43,6 +43,7 @@ import ( environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" configclient "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" @@ -390,6 +391,11 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug return err } + if err = DeleteLabels(productName, log); err != nil { + log.Errorf("DeleteLabels productName %s err: %s", productName, err) + return err + } + if err = commonservice.DeleteWorkflows(productName, requestID, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s workflow err: %s", productName, err) return err @@ -947,3 +953,11 @@ func DeletePolicy(productName string, log *zap.SugaredLogger) error { } return nil } + +func DeleteLabels(productName string, log *zap.SugaredLogger) error { + if err := label.New().DeleteLabelsByProject(productName); err != nil { + log.Errorf("DeleteLabels err: %s", err) + return err + } + return nil +} diff --git a/pkg/shared/client/label/label.go b/pkg/shared/client/label/label.go index 091b0590..eb3dc664 100644 --- a/pkg/shared/client/label/label.go +++ b/pkg/shared/client/label/label.go @@ -85,6 +85,17 @@ func (c *Client) ListLabels(request ListLabelsArgs) (*ListLabelsResp, error) { return resources, nil } +func (c *Client) DeleteLabelsByProject(projectName string) error { + url := "/label/labels/bulk-delete-project" + "?projectName=" + projectName + + _, err := c.Post(url) + if err != nil { + log.Errorf("Failed to DeleteLabelsByProject, error: %s", err) + return err + } + return nil +} + type ListLabelsByResourcesReq struct { Resources []Resource `json:"resources"` } -- Gitee From 68940a65f37c27f7053f060b5f840af321f01122 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Feb 2022 11:04:42 +0800 Subject: [PATCH 1494/2647] delete labels Signed-off-by: mouuii --- .../service/collaboration_instance.go | 18 ++++++++++----- .../aslan/core/label/handler/labels.go | 9 -------- .../aslan/core/label/handler/router.go | 1 - .../core/label/repository/models/label.go | 13 ++++++----- .../core/label/repository/mongodb/label.go | 7 +++--- .../aslan/core/label/service/label.go | 22 ++++++++++++++----- .../aslan/core/project/service/product.go | 6 ++--- 7 files changed, 43 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 875b7776..d838d180 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -468,7 +468,8 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: "system", + Type: "system", + ProjectName: projectName, }) } for _, product := range mode.Products { @@ -476,7 +477,8 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: "system", + Type: "system", + ProjectName: projectName, }) } } @@ -486,7 +488,8 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: "system", + Type: "system", + ProjectName: projectName, }) } for _, product := range item.NewSpec.Products { @@ -494,7 +497,8 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: "system", + Type: "system", + ProjectName: projectName, }) } for _, workflow := range item.DeleteSpec.Workflows { @@ -502,7 +506,8 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: "system", + Type: "system", + ProjectName: projectName, }) } for _, product := range item.DeleteSpec.Products { @@ -510,7 +515,8 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: "system", + Type: "system", + ProjectName: projectName, }) } diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index 5f18299b..df6ca54c 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -82,15 +82,6 @@ func DeleteLabels(c *gin.Context) { ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.Logger) } -func DeleteLabelsAndBindingsByProject(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - projectName := c.Query("projectName") - - ctx.Err = service.DeleteLabelsAndBindingsByProject(projectName, ctx.Logger) -} - func ListResourcesByLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/label/handler/router.go b/pkg/microservice/aslan/core/label/handler/router.go index 2dfa72e8..83328ea5 100644 --- a/pkg/microservice/aslan/core/label/handler/router.go +++ b/pkg/microservice/aslan/core/label/handler/router.go @@ -28,7 +28,6 @@ func (*Router) Inject(router *gin.RouterGroup) { labels.POST("/filter", ListLabels) labels.POST("/bulk-create", CreateLabels) labels.POST("/bulk-delete", DeleteLabels) - labels.POST("/bulk-delete-project", DeleteLabelsAndBindingsByProject) labels.POST("/resources-by-labels", ListResourcesByLabels) labels.POST("/labels-by-resources", ListLabelsByResources) } diff --git a/pkg/microservice/aslan/core/label/repository/models/label.go b/pkg/microservice/aslan/core/label/repository/models/label.go index bc232641..84848263 100644 --- a/pkg/microservice/aslan/core/label/repository/models/label.go +++ b/pkg/microservice/aslan/core/label/repository/models/label.go @@ -21,12 +21,13 @@ import ( ) type Label struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type string `bson:"type" json:"type"` - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - CreateBy string `bson:"create_by" json:"create_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` + ProjectName string `bson:"project_name" json:"project_name"` } func (Label) TableName() string { diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 572b3c3e..50ed7969 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -133,9 +133,10 @@ func (c *LabelColl) Find(id string) (*models.Label, error) { } type Label struct { - Key string `json:"key"` - Value string `json:"value"` - Type string `json:"type"` + Key string `json:"key"` + Value string `json:"value"` + Type string `json:"type"` + ProjectName string `json:"project_name"` } type ListLabelOpt struct { diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 4d9cd473..115c6b2d 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -42,10 +42,11 @@ func CreateLabels(arg *CreateLabelsArgs, userName string) (*CreateLabelsResp, er } keyValues.Insert(keyValue) tmpModel := models.Label{ - Key: v.Key, - Value: v.Value, - CreateBy: userName, - Type: v.Type, + Key: v.Key, + Value: v.Value, + CreateBy: userName, + Type: v.Type, + ProjectName: v.ProjectName, } filteredLabels = append(filteredLabels, &tmpModel) } @@ -244,8 +245,19 @@ func DeleteLabelsAndBindingsByProject(projectName string, logger *zap.SugaredLog return err } + ids := []string{} + for _, label := range labels { + ids = append(ids, label.ID.Hex()) + } + + labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: ids}) + if err != nil { + logger.Errorf("list labelbingding err:%s", err) + return err + } + var labelBindingIDs []string - for _, labelBinding := range labels { + for _, labelBinding := range labelBindings { labelBindingIDs = append(labelBindingIDs, labelBinding.ID.Hex()) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index a4355d8d..eaea14c1 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -41,9 +41,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + service2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" configclient "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" @@ -955,8 +955,8 @@ func DeletePolicy(productName string, log *zap.SugaredLogger) error { } func DeleteLabels(productName string, log *zap.SugaredLogger) error { - if err := label.New().DeleteLabelsByProject(productName); err != nil { - log.Errorf("DeleteLabels err: %s", err) + if err := service2.DeleteLabelsAndBindingsByProject(productName, log); err != nil { + log.Errorf("delete labels and bindings by project fail , err :%s", err) return err } return nil -- Gitee From fc5bca7f86f0b2c899db99b96dfaa1eccbdd4a38 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Feb 2022 11:05:20 +0800 Subject: [PATCH 1495/2647] remove useless logic Signed-off-by: mouuii --- pkg/microservice/policy/core/service/policy.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 6cbeae7e..5747420e 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -193,9 +193,7 @@ func DeletePolicy(name string, projectName string, logger *zap.SugaredLogger) er } func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogger) error { - //if len(names) == 0 { - // return nil - //} + if projectName == "" { return fmt.Errorf("projectName is empty") } -- Gitee From 74311f55ad107b23559af802ea3fad8a3e5cfdf5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Feb 2022 11:06:05 +0800 Subject: [PATCH 1496/2647] remove useless logic Signed-off-by: mouuii --- pkg/shared/client/label/label.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/shared/client/label/label.go b/pkg/shared/client/label/label.go index eb3dc664..091b0590 100644 --- a/pkg/shared/client/label/label.go +++ b/pkg/shared/client/label/label.go @@ -85,17 +85,6 @@ func (c *Client) ListLabels(request ListLabelsArgs) (*ListLabelsResp, error) { return resources, nil } -func (c *Client) DeleteLabelsByProject(projectName string) error { - url := "/label/labels/bulk-delete-project" + "?projectName=" + projectName - - _, err := c.Post(url) - if err != nil { - log.Errorf("Failed to DeleteLabelsByProject, error: %s", err) - return err - } - return nil -} - type ListLabelsByResourcesReq struct { Resources []Resource `json:"resources"` } -- Gitee From ba02a171f922b836beb0e22495fd1f1f7b519df9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Feb 2022 11:07:19 +0800 Subject: [PATCH 1497/2647] remove useless logic Signed-off-by: mouuii --- pkg/microservice/policy/core/service/policy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 5747420e..78fbb709 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -193,7 +193,6 @@ func DeletePolicy(name string, projectName string, logger *zap.SugaredLogger) er } func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogger) error { - if projectName == "" { return fmt.Errorf("projectName is empty") } -- Gitee From a05e92c81c8a14c84ec9f0f74566e9ba9fec94ee Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 10 Feb 2022 14:49:21 +0800 Subject: [PATCH 1498/2647] Keep the image when updating the environment Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 88 +++++++++++++++---- 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 93b3b543..af2a72a6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1094,9 +1094,20 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa switch usageScenario { case usageScenarioUpdateEnv: imageRelatedKey := sets.NewString() - for _, container := range templateService.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + proSvcMap := productInfo.GetServiceMap() + proSvc := proSvcMap[serviceName] + if proSvc != nil { + existUpdate, err := checkServiceImageUpdate(productName, serviceName, proSvc) + if err != nil { + log.Errorf("checkServiceImageUpdate, productName %s,svcname %s,err %s", productName, serviceName, err) + return "", "", fmt.Errorf("checkServiceImageUpdate,productName %s, svcname %s,err %s", productName, serviceName, err) + } + if !existUpdate { + for _, container := range templateService.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } + } } } @@ -1344,16 +1355,16 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo } } - serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(args.ProductName) - if err != nil { - log.Infof("query services from product: %s fail, error %s", args.ProductName, err.Error()) - return envStatuses, e.ErrUpdateEnv.AddDesc("failed to query services") - } + // serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(args.ProductName) + // if err != nil { + // log.Infof("query services from product: %s fail, error %s", args.ProductName, err.Error()) + // return envStatuses, e.ErrUpdateEnv.AddDesc("failed to query services") + // } - serviceMap := make(map[string]*commonmodels.Service) - for _, singleService := range serviceList { - serviceMap[singleService.ServiceName] = singleService - } + // serviceMap := make(map[string]*commonmodels.Service) + // for _, singleService := range serviceList { + // serviceMap[singleService.ServiceName] = singleService + // } // extract values.yaml and update renderset for envName := range productMap { @@ -2694,15 +2705,30 @@ func diffRenderSet(username, productName, envName, updateType string, productRes } } - serviceMap := productResp.GetServiceMap() + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + productCur, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + log.Errorf("GetProduct envName:%s, productName:%s, err:%s", envName, productName, err) + return nil, fmt.Errorf("GetProduct envName:%s, productName:%s, err:%s", envName, productName, err) + } + serviceMap := productCur.GetServiceMap() + serviceRespMap := productResp.GetServiceMap() for serviceName, latestChartInfo := range tmpLatestChartInfoMap { if currentChartInfo, ok := tmpCurrentChartInfoMap[serviceName]; ok { - serviceInfo := serviceMap[serviceName] + serviceInfoResp := serviceRespMap[serviceName] + serviceInfoCur := serviceMap[serviceName] imageRelatedKey := sets.NewString() - if serviceInfo != nil { - for _, container := range serviceInfo.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + if serviceInfoResp != nil && serviceInfoCur != nil { + existUpdate, err := checkServiceImageUpdate(productName, serviceName, serviceInfoCur) + if err != nil { + log.Errorf("checkServiceImageUpdate,productName %s,svcname %s,err %s", productName, serviceName, err) + return nil, fmt.Errorf("checkServiceImageUpdate,productName %s,svcname %s,err %s", productName, serviceName, err) + } + if !existUpdate { + for _, container := range serviceInfoResp.Containers { + if container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + } } } } @@ -2750,6 +2776,32 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return renderSet, nil } +//checkServiceImageUpdate If the service does not do any mirroring iterations on the platform, the latest YAML is used when updating the environment +func checkServiceImageUpdate(productName, serviceName string, serviceInfo *commonmodels.ProductService) (bool, error) { + existUpdate := false + curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + Revision: serviceInfo.Revision, + }) + if err != nil { + log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfo.Revision, err) + return existUpdate, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfo.Revision, err) + } + +L: + for _, curContainer := range curEnvService.Containers { + for _, proContainer := range serviceInfo.Containers { + if curContainer.Image == proContainer.Image { + existUpdate = true + break L + } + } + } + return existUpdate, nil +} + // for keys exist in both yaml, current values will override latest values // only for images func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey sets.String) ([]byte, error) { -- Gitee From 1e77ecfcb325fa9a5c9f6704d781bdd9e390f479 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Feb 2022 15:32:11 +0800 Subject: [PATCH 1499/2647] add service webhook logic Signed-off-by: allenshen --- .../core/common/repository/mongodb/service.go | 22 +++++---- .../aslan/core/common/service/service.go | 16 ++++--- .../aslan/core/service/service/helm.go | 17 ++++++- .../core/workflow/service/webhook/github.go | 14 +++--- .../core/workflow/service/webhook/gitlab.go | 2 +- .../core/workflow/service/webhook/utils.go | 48 +++++++++++++++++++ 6 files changed, 93 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index c7c028f6..b802a7bb 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -37,15 +37,16 @@ import ( ) type ServiceFindOption struct { - ServiceName string - Revision int64 - Type string - Source string - ProductName string - ExcludeStatus string - CodehostID int - RepoName string - BranchName string + ServiceName string + Revision int64 + Type string + Source string + ProductName string + ExcludeStatus string + CodehostID int + RepoName string + BranchName string + IgnoreNoDocumentErr bool } type ServiceListOption struct { @@ -206,6 +207,9 @@ func (c *ServiceColl) Find(opt *ServiceFindOption) (*models.Service, error) { err := c.FindOne(context.TODO(), query, opts).Decode(service) if err != nil { + if err == mongo.ErrNoDocuments && opt.IgnoreNoDocumentErr { + return nil, nil + } return nil, err } diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 1f2e521b..5ff73965 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -500,15 +500,19 @@ func DeleteServiceWebhookByName(serviceName, productName string, logger *zap.Sug ProcessServiceWebhook(nil, svc, serviceName, logger) } -func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName string, logger *zap.SugaredLogger) { - // helm service doesn't support webhook - if current != nil && current.Type == setting.HelmDeployType { - return +func needProcessWebhook(source string) bool { + if source == setting.ServiceSourceTemplate || source == setting.SourceFromZadig || source == setting.SourceFromGerrit || + source == "" || source == setting.SourceFromExternal || source == setting.SourceFromChartTemplate { + return false } + return true +} + +func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName string, logger *zap.SugaredLogger) { var action string var updatedHooks, currentHooks []*webhook.WebHook if updated != nil { - if updated.Source == setting.ServiceSourceTemplate || updated.Source == setting.SourceFromZadig || updated.Source == setting.SourceFromGerrit || updated.Source == "" || updated.Source == setting.SourceFromExternal { + if !needProcessWebhook(updated.Source) { return } action = "add" @@ -519,7 +523,7 @@ func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName s updatedHooks = append(updatedHooks, &webhook.WebHook{Owner: updated.RepoOwner, Repo: updated.RepoName, Address: address, Name: "trigger", CodeHostID: updated.CodehostID}) } if current != nil { - if current.Source == setting.ServiceSourceTemplate || current.Source == setting.SourceFromZadig || current.Source == setting.SourceFromGerrit || current.Source == "" || current.Source == setting.SourceFromExternal { + if !needProcessWebhook(current.Source) { return } action = "remove" diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 849fbff9..154145f6 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -554,8 +554,8 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC err = copyChartRevision(projectName, serviceName, rev) if err != nil { - log.Errorf("Failed to copy file %s, err: %s", args.Name, err) - finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", args.Name) + log.Errorf("Failed to copy file %s, err: %s", serviceName, err) + finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", serviceName) return } @@ -919,11 +919,24 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg log.Infof("Starting to create service %s with revision %d", args.ServiceName, args.ServiceRevision) + currentSvcTmpl, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ProductName: args.ProductName, + ServiceName: args.ServiceName, + ExcludeStatus: setting.ProductStatusDeleting, + IgnoreNoDocumentErr: true, + }) + if err != nil { + log.Errorf("Failed to find current service template %s error: %s", args.ServiceName, err) + return nil, err + } + if err = commonrepo.NewServiceColl().Create(serviceObj); err != nil { log.Errorf("Failed to create service %s error: %s", args.ServiceName, err) return nil, err } + commonservice.ProcessServiceWebhook(serviceObj, currentSvcTmpl, args.ServiceName, logger) + if err = templaterepo.NewProductColl().AddService(args.ProductName, args.ServiceName); err != nil { log.Errorf("Failed to add service %s to project %s, err: %s", args.ProductName, args.ServiceName, err) return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 8eef4a0c..92edc261 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -33,7 +33,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collie" gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" @@ -430,13 +429,14 @@ func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, l return err } - go collie.CallGithubWebHook(forwardedProto, forwardedHost, payload, github.WebHookType(req), log) + //go collie.CallGithubWebHook(forwardedProto, forwardedHost, payload, github.WebHookType(req), log) deliveryID := github.DeliveryID(req) log.Infof("[Webhook] event: %s delivery id: %s received", hookType, deliveryID) switch et := event.(type) { case *github.PullRequestEvent: + log.Infof("###### the PullRequestEvent is %+v", *et) if *et.Action != "opened" && *et.Action != "synchronize" { return nil } @@ -448,11 +448,9 @@ func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, l } case *github.PushEvent: - //触发更新服务模板webhook - if pushEvent, isPush := event.(*github.PushEvent); isPush { - if err = updateServiceTemplateByGithubPush(pushEvent, log); err != nil { - log.Errorf("updateServiceTemplateByGithubPush failed, error:%v", err) - } + // sync service template + if err = updateServiceTemplateByGithubPush(et, log); err != nil { + log.Errorf("updateServiceTemplateByGithubPush failed, error:%v", err) } //add webhook user @@ -597,7 +595,7 @@ func updateServiceTemplateByGithubPush(pushEvent *github.PushEvent, log *zap.Sug func GetGithubServiceTemplates() ([]*commonmodels.Service, error) { opt := &commonrepo.ServiceListOption{ - Type: setting.K8SDeployType, + //Type: setting.K8SDeployType, Source: setting.SourceFromGithub, } return commonrepo.NewServiceColl().ListMaxRevisions(opt) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index c8567804..6fcf73df 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -332,7 +332,7 @@ func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) func GetGitlabServiceTemplates() ([]*commonmodels.Service, error) { opt := &commonrepo.ServiceListOption{ - Type: setting.K8SDeployType, + //Type: setting.K8SDeployType, Source: setting.SourceFromGitlab, } return commonrepo.NewServiceColl().ListMaxRevisions(opt) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 0b6ce917..9041fe4d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -24,6 +24,8 @@ import ( "strings" "sync" + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" + "github.com/google/go-github/v35/github" "github.com/hashicorp/go-multierror" "github.com/xanzy/go-gitlab" @@ -133,6 +135,52 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar } log.Infof("find %d containers in service %s", len(args.Containers), args.ServiceName) + } else if args.Type == setting.HelmDeployType { + if args.Source == setting.SourceFromGitlab { + // Set args.Commit + if err := syncLatestCommit(args); err != nil { + log.Errorf("Sync change log from gitlab failed, error: %v", err) + return err + } + + _, err := service.CreateOrUpdateHelmServiceFromGitRepo(args.ProductName, &service.HelmServiceCreationArgs{ + HelmLoadSource: service.HelmLoadSource{ + Source: service.LoadFromRepo, + }, + CreatedBy: "system", + CreateFrom: &service.CreateFromRepo{ + CodehostID: args.CodehostID, + Owner: args.RepoOwner, + Repo: args.RepoName, + Branch: args.BranchName, + Paths: []string{args.LoadPath}, + }, + }, log) + + if err != nil { + log.Errorf("Sync content from github failed, error: %v", err) + return err + } + } else if args.Source == setting.SourceFromGithub { + _, err := service.CreateOrUpdateHelmServiceFromGitRepo(args.ProductName, &service.HelmServiceCreationArgs{ + HelmLoadSource: service.HelmLoadSource{ + Source: service.LoadFromRepo, + }, + CreatedBy: "system", + CreateFrom: &service.CreateFromRepo{ + CodehostID: args.CodehostID, + Owner: args.RepoOwner, + Repo: args.RepoName, + Branch: args.BranchName, + Paths: []string{args.LoadPath}, + }, + }, log) + + if err != nil { + log.Errorf("Sync content from github failed, error: %v", err) + return err + } + } } // 设置新的版本号 -- Gitee From 81b960dd7e03d4828939794c31bb0869f9f2f073 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Feb 2022 15:34:50 +0800 Subject: [PATCH 1500/2647] fix version delivery hook send fail bug Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index f71a593c..d722ac8e 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -25,7 +25,6 @@ import ( "net/http" "net/url" "os" - "path" "path/filepath" "strings" "sync" @@ -1077,11 +1076,13 @@ func sendVersionDeliveryHook(projectName, version, host, urlPath string) error { return err } - u, err := url.Parse(host) + targetPath := fmt.Sprintf("%s/%s", host, strings.TrimPrefix(urlPath, "/")) + + // validate url + _, err = url.Parse(targetPath) if err != nil { return err } - u.Path = path.Join(u.Path, urlPath) reqBody, err := json.Marshal(ret) if err != nil { @@ -1089,7 +1090,7 @@ func sendVersionDeliveryHook(projectName, version, host, urlPath string) error { return err } - request, err := http.NewRequest("POST", u.String(), bytes.NewBuffer(reqBody)) + request, err := http.NewRequest("POST", targetPath, bytes.NewBuffer(reqBody)) if err != nil { return err } @@ -1113,6 +1114,19 @@ func sendVersionDeliveryHook(projectName, version, host, urlPath string) error { func updateVersionStatus(versionName, projectName, status, errStr string) { // send hook info when version build finished if status == setting.DeliveryVersionStatusSuccess { + + versionInfo, err := commonrepo.NewDeliveryVersionColl().Get(&commonrepo.DeliveryVersionArgs{ + ProductName: projectName, + Version: versionName, + }) + if err != nil { + log.Errorf("failed to find version: %s of project %s", versionName, projectName) + return + } + if versionInfo.Status == status { + return + } + templateProduct, err := templaterepo.NewProductColl().Find(projectName) if err != nil { log.Errorf("updateVersionStatus failed to find template product: %s, err: %s", projectName, err) -- Gitee From 6724a80000718d6810d7ee04165b4919295ac1d0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Feb 2022 15:50:27 +0800 Subject: [PATCH 1501/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index d722ac8e..e308e029 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -1020,16 +1020,8 @@ func buildDeliveryCharts(chartDataMap map[string]*DeliveryChartData, deliveryVer } // send hook -func sendVersionDeliveryHook(projectName, version, host, urlPath string) error { - - deliveryVersion, err := commonrepo.NewDeliveryVersionColl().Get(&commonrepo.DeliveryVersionArgs{ - ProductName: projectName, - Version: version, - }) - if err != nil { - return err - } - +func sendVersionDeliveryHook(deliveryVersion *commonmodels.DeliveryVersion, host, urlPath string) error { + projectName, version := deliveryVersion.ProductName, deliveryVersion.Version ret := &DeliveryVersionHookPayload{ ProjectName: projectName, Version: version, @@ -1105,7 +1097,7 @@ func sendVersionDeliveryHook(projectName, version, host, urlPath string) error { defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("hook request send code error: %d", resp.StatusCode) + return fmt.Errorf("hook request send to url: %s got error code : %d", targetPath, resp.StatusCode) } return nil @@ -1133,7 +1125,7 @@ func updateVersionStatus(versionName, projectName, status, errStr string) { } else { hookConfig := templateProduct.DeliveryVersionHook if hookConfig != nil && hookConfig.Enable { - err = sendVersionDeliveryHook(projectName, versionName, hookConfig.HookHost, hookConfig.Path) + err = sendVersionDeliveryHook(versionInfo, hookConfig.HookHost, hookConfig.Path) if err != nil { log.Errorf("updateVersionStatus failed to send version delivery hook, projectName: %s, err: %s", projectName, err) } -- Gitee From d743c3429ca60638f39066ec8cf506eb0fad3327 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Feb 2022 16:01:46 +0800 Subject: [PATCH 1502/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/delivery/service/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index e308e029..a6d3cb75 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -1097,7 +1097,7 @@ func sendVersionDeliveryHook(deliveryVersion *commonmodels.DeliveryVersion, host defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("hook request send to url: %s got error code : %d", targetPath, resp.StatusCode) + return fmt.Errorf("hook request send to url: %s got error resp code : %d", targetPath, resp.StatusCode) } return nil @@ -1112,7 +1112,7 @@ func updateVersionStatus(versionName, projectName, status, errStr string) { Version: versionName, }) if err != nil { - log.Errorf("failed to find version: %s of project %s", versionName, projectName) + log.Errorf("failed to find version: %s of project %s, err: %s", versionName, projectName, err) return } if versionInfo.Status == status { -- Gitee From 8acc3598e48be97f5d36b6066618c95b93692bd7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 10 Feb 2022 16:20:51 +0800 Subject: [PATCH 1503/2647] code review final changes Signed-off-by: Min Min --- .../aslan/core/common/service/environment.go | 6 ++++- .../environment/service/environment_group.go | 6 ++++- pkg/tool/kube/informer/informer.go | 22 +++++++++++++++---- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 45eb5c0d..402185cb 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -223,7 +223,11 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Errorf("[%s][%s] error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } - informer := informer.NewInformer(clusterID, namespace, cls) + informer, err := informer.NewInformer(clusterID, namespace, cls) + if err != nil { + log.Errorf("[%s][%s] error: %v", envName, namespace, err) + return 0, resp, e.ErrListGroups.AddDesc(err.Error()) + } var workLoads []*Workload listDeployments, err := getter.ListDeploymentsWithCache(nil, informer) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index 61939d82..9f84696b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -74,7 +74,11 @@ func ListGroups(serviceName, envName, productName string, perPage, page int, log log.Errorf("[%s][%s] error: %v", envName, productName, err) return resp, count, e.ErrListGroups.AddDesc(err.Error()) } - inf := informer.NewInformer(productInfo.ClusterID, productInfo.Namespace, cls) + inf, err := informer.NewInformer(productInfo.ClusterID, productInfo.Namespace, cls) + if err != nil { + log.Errorf("[%s][%s] error: %v", envName, productName, err) + return resp, count, e.ErrListGroups.AddDesc(err.Error()) + } //这种针对的是获取所有数据的接口,内部调用 if page == 0 && perPage == 0 { diff --git a/pkg/tool/kube/informer/informer.go b/pkg/tool/kube/informer/informer.go index eac06f4a..e72e5864 100644 --- a/pkg/tool/kube/informer/informer.go +++ b/pkg/tool/kube/informer/informer.go @@ -21,6 +21,7 @@ import ( "sync" "time" + "k8s.io/apimachinery/pkg/util/version" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -39,14 +40,14 @@ var StopChanMap sync.Map // - Service // - Pod // - Ingress (extentions/v1beta1) <- as of version 1.9.0, this is the resource we watch -func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informers.SharedInformerFactory { +func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) (informers.SharedInformerFactory, error) { // this is a stupid compatibility code if clusterID == "" { clusterID = setting.LocalClusterID } key := generateInformerKey(clusterID, namespace) if informer, ok := InformersMap.Load(key); ok { - return informer.(informers.SharedInformerFactory) + return informer.(informers.SharedInformerFactory), nil } opts := informers.WithNamespace(namespace) informerFactory := informers.NewSharedInformerFactoryWithOptions(cls, time.Minute, opts) @@ -55,7 +56,20 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe informerFactory.Apps().V1().StatefulSets().Lister() informerFactory.Core().V1().Services().Lister() informerFactory.Core().V1().Pods().Lister() - informerFactory.Extensions().V1beta1().Ingresses().Lister() + versionInfo, err := cls.Discovery().ServerVersion() + if err != nil { + return nil, err + } + currVersion, _ := version.ParseGeneric(versionInfo.String()) + v122, _ := version.ParseGeneric("v1.22.0") + // if less than v1.22.0, then we look for the extensions/v1beta1 ingress + if currVersion.LessThan(v122) { + informerFactory.Extensions().V1beta1().Ingresses().Lister() + } else { + // otherwise above resource is deprecated, we watch for the k8s.networking.io/v1 ingress + informerFactory.Networking().V1().Ingresses().Lister() + } + // stop channel will be stored for future stop stopchan := make(chan struct{}) informerFactory.Start(stopchan) @@ -71,7 +85,7 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) informe } InformersMap.Store(key, informerFactory) StopChanMap.Store(key, stopchan) - return informerFactory + return informerFactory, nil } func DeleteInformer(clusterID, namespace string) { -- Gitee From 80b82b9f1acd5cb5f7784d1584d405b5acdf8dde Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Feb 2022 16:38:23 +0800 Subject: [PATCH 1504/2647] optimize code Signed-off-by: allenshen --- .../core/workflow/service/webhook/github.go | 4 -- .../core/workflow/service/webhook/gitlab.go | 1 - .../core/workflow/service/webhook/utils.go | 59 ++++++++----------- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 92edc261..dfdd8f30 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -429,14 +429,11 @@ func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, l return err } - //go collie.CallGithubWebHook(forwardedProto, forwardedHost, payload, github.WebHookType(req), log) - deliveryID := github.DeliveryID(req) log.Infof("[Webhook] event: %s delivery id: %s received", hookType, deliveryID) switch et := event.(type) { case *github.PullRequestEvent: - log.Infof("###### the PullRequestEvent is %+v", *et) if *et.Action != "opened" && *et.Action != "synchronize" { return nil } @@ -595,7 +592,6 @@ func updateServiceTemplateByGithubPush(pushEvent *github.PushEvent, log *zap.Sug func GetGithubServiceTemplates() ([]*commonmodels.Service, error) { opt := &commonrepo.ServiceListOption{ - //Type: setting.K8SDeployType, Source: setting.SourceFromGithub, } return commonrepo.NewServiceColl().ListMaxRevisions(opt) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 6fcf73df..2825dddb 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -332,7 +332,6 @@ func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) func GetGitlabServiceTemplates() ([]*commonmodels.Service, error) { opt := &commonrepo.ServiceListOption{ - //Type: setting.K8SDeployType, Source: setting.SourceFromGitlab, } return commonrepo.NewServiceColl().ListMaxRevisions(opt) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 9041fe4d..0a5f89cf 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -24,8 +24,6 @@ import ( "strings" "sync" - "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" - "github.com/google/go-github/v35/github" "github.com/hashicorp/go-multierror" "github.com/xanzy/go-gitlab" @@ -38,6 +36,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -81,6 +80,23 @@ func syncContent(args *commonmodels.Service, logger *zap.SugaredLogger) error { return nil } +func reloadServiceTmplFromGit(svc *commonmodels.Service, log *zap.SugaredLogger) error { + _, err := service.CreateOrUpdateHelmServiceFromGitRepo(svc.ProductName, &service.HelmServiceCreationArgs{ + HelmLoadSource: service.HelmLoadSource{ + Source: service.LoadFromRepo, + }, + CreatedBy: svc.CreateBy, + CreateFrom: &service.CreateFromRepo{ + CodehostID: svc.CodehostID, + Owner: svc.RepoOwner, + Repo: svc.RepoName, + Branch: svc.BranchName, + Paths: []string{svc.LoadPath}, + }, + }, log) + return err +} + // fillServiceTmpl 更新服务模板参数 func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.SugaredLogger) error { if args == nil { @@ -139,45 +155,18 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar if args.Source == setting.SourceFromGitlab { // Set args.Commit if err := syncLatestCommit(args); err != nil { - log.Errorf("Sync change log from gitlab failed, error: %v", err) + log.Errorf("Sync change log from gitlab failed, error: %s", err) return err } - _, err := service.CreateOrUpdateHelmServiceFromGitRepo(args.ProductName, &service.HelmServiceCreationArgs{ - HelmLoadSource: service.HelmLoadSource{ - Source: service.LoadFromRepo, - }, - CreatedBy: "system", - CreateFrom: &service.CreateFromRepo{ - CodehostID: args.CodehostID, - Owner: args.RepoOwner, - Repo: args.RepoName, - Branch: args.BranchName, - Paths: []string{args.LoadPath}, - }, - }, log) - - if err != nil { - log.Errorf("Sync content from github failed, error: %v", err) + if err := reloadServiceTmplFromGit(args, log); err != nil { + log.Errorf("Sync content from gitlab failed, error: %s", err) return err } - } else if args.Source == setting.SourceFromGithub { - _, err := service.CreateOrUpdateHelmServiceFromGitRepo(args.ProductName, &service.HelmServiceCreationArgs{ - HelmLoadSource: service.HelmLoadSource{ - Source: service.LoadFromRepo, - }, - CreatedBy: "system", - CreateFrom: &service.CreateFromRepo{ - CodehostID: args.CodehostID, - Owner: args.RepoOwner, - Repo: args.RepoName, - Branch: args.BranchName, - Paths: []string{args.LoadPath}, - }, - }, log) - if err != nil { - log.Errorf("Sync content from github failed, error: %v", err) + } else if args.Source == setting.SourceFromGithub { + if err := reloadServiceTmplFromGit(args, log); err != nil { + log.Errorf("Sync content from github failed, error: %s", err) return err } } -- Gitee From 3cadb058b609d7e56dedb2b4cb33bb63180ce79e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 10 Feb 2022 17:56:22 +0800 Subject: [PATCH 1505/2647] clean artifact cronJob Signed-off-by: ddh27 --- pkg/microservice/aslan/core/cron/service/cron.go | 4 ++++ .../aslan/core/delivery/service/version.go | 15 ++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/cron/service/cron.go b/pkg/microservice/aslan/core/cron/service/cron.go index be8d7487..fc53f2aa 100644 --- a/pkg/microservice/aslan/core/cron/service/cron.go +++ b/pkg/microservice/aslan/core/cron/service/cron.go @@ -39,9 +39,11 @@ func CleanJobCronJob(log *zap.SugaredLogger) { workflowSelector := labels.Set{"p-type": "workflow"}.AsSelector() singleSelector := labels.Set{"p-type": "single"}.AsSelector() testSelector := labels.Set{"p-type": "test"}.AsSelector() + artifactSelector := labels.Set{"p-type": "artifact"}.AsSelector() cleanJob(config.Namespace(), workflowSelector, kubeClient, log) cleanJob(config.Namespace(), singleSelector, kubeClient, log) cleanJob(config.Namespace(), testSelector, kubeClient, log) + cleanJob(config.Namespace(), artifactSelector, kubeClient, log) cleanServiceJob(kubeClient, log) log.Infof("finnish clean job...") } @@ -52,9 +54,11 @@ func CleanConfigmapCronJob(log *zap.SugaredLogger) { workflowSelector := labels.Set{"p-type": "workflow"}.AsSelector() singleSelector := labels.Set{"p-type": "single"}.AsSelector() testSelector := labels.Set{"p-type": "test"}.AsSelector() + artifactSelector := labels.Set{"p-type": "artifact"}.AsSelector() cleanConfigmap(config.Namespace(), workflowSelector, kubeClient, log) cleanConfigmap(config.Namespace(), singleSelector, kubeClient, log) cleanConfigmap(config.Namespace(), testSelector, kubeClient, log) + cleanConfigmap(config.Namespace(), artifactSelector, kubeClient, log) cleanServiceConfigmap(kubeClient, log) log.Infof("finnish clean configmap...") } diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index f71a593c..203212ec 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -1261,14 +1261,6 @@ func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *z if len(args.ChartDatas) == 0 { return e.ErrCreateDeliveryVersion.AddDesc("no chart info appointed") } - - // prepare data - productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) - if err != nil { - log.Infof("failed to query product info, productName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) - return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) - } - // validate necessary params if len(args.ChartRepoName) == 0 { return e.ErrCreateDeliveryVersion.AddDesc("chart repo not appointed") @@ -1276,7 +1268,12 @@ func CreateNewHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *z if len(args.ImageRegistryID) == 0 { return e.ErrCreateDeliveryVersion.AddDesc("image registry not appointed") } - + // prepare data + productInfo, err := getProductEnvInfo(args.ProductName, args.EnvName) + if err != nil { + log.Infof("failed to query product info, productName: %s envName %s, err: %s", args.ProductName, args.EnvName, err) + return e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to query product info, procutName: %s envName %s", args.ProductName, args.EnvName)) + } chartDataMap, err := prepareChartData(args.ChartDatas, productInfo) if err != nil { return e.ErrCreateDeliveryVersion.AddErr(err) -- Gitee From a6aeabbb8cce7e65893cbae4941432f1aa0d09fc Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Feb 2022 21:12:03 +0800 Subject: [PATCH 1506/2647] optimize batch helm upgrade operation Signed-off-by: allenshen --- .../core/environment/service/environment.go | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0330f687..90e521b9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2387,7 +2387,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2456,14 +2456,14 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis return updatedAllServices } -func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { +func batchExecutorWithRetry(retryCount uint64, interval time.Duration, serviceList []*commonmodels.Service, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { bo := backoff.NewConstantBackOff(time.Second * 3) retryBo := backoff.WithMaxRetries(bo, retryCount) errList := make([]error, 0) isRetry := false _ = backoff.Retry(func() error { failedServices := make([]*commonmodels.Service, 0) - errList = intervalExecutor(interval, serviceList, &failedServices, isRetry, handler, log) + errList = batchExecutor(interval, serviceList, &failedServices, isRetry, handler, log) if len(errList) == 0 { return nil } @@ -2475,32 +2475,20 @@ func intervalExecutorWithRetry(retryCount uint64, interval time.Duration, servic return errList } -func intervalExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { +func batchExecutor(interval time.Duration, serviceList []*commonmodels.Service, failedServices *[]*commonmodels.Service, isRetry bool, handler intervalExecutorHandler, log *zap.SugaredLogger) []error { if len(serviceList) == 0 { return nil } - wg := sync.WaitGroup{} - var failLock sync.Mutex - wg.Add(len(serviceList)) errList := make([]error, 0) - ticker := time.NewTicker(interval) - defer ticker.Stop() - for _, data := range serviceList { - go func(singleService *commonmodels.Service) { - defer wg.Done() - err := handler(singleService, isRetry, log) - if err != nil { - failLock.Lock() - defer failLock.Unlock() - errList = append(errList, err) - *failedServices = append(*failedServices, singleService) - log.Errorf("service:%s apply failed, err %s", singleService.ServiceName, err) - } - }(data) - <-ticker.C + err := handler(data, isRetry, log) + if err != nil { + errList = append(errList, err) + *failedServices = append(*failedServices, data) + log.Errorf("service:%s apply failed, err %s", data.ServiceName, err) + } + time.Sleep(time.Second) } - wg.Wait() return errList } @@ -2606,7 +2594,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ serviceList = append(serviceList, serviceObj) } - serviceGroupErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -2846,7 +2834,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := intervalExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + groupServiceErr := batchExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } -- Gitee From 8ed6286d610d899e6ade36f897a0eaf0f4472d82 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 10 Feb 2022 21:28:22 +0800 Subject: [PATCH 1507/2647] checkServiceImageUpdated Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index af2a72a6..7cc97650 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1097,10 +1097,10 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa proSvcMap := productInfo.GetServiceMap() proSvc := proSvcMap[serviceName] if proSvc != nil { - existUpdate, err := checkServiceImageUpdate(productName, serviceName, proSvc) + existUpdate, err := checkServiceImageUpdated(productName, serviceName, proSvc) if err != nil { - log.Errorf("checkServiceImageUpdate, productName %s,svcname %s,err %s", productName, serviceName, err) - return "", "", fmt.Errorf("checkServiceImageUpdate,productName %s, svcname %s,err %s", productName, serviceName, err) + log.Errorf("checkServiceImageUpdated, productName %s,svcname %s,err %s", productName, serviceName, err) + return "", "", fmt.Errorf("checkServiceImageUpdated,productName %s, svcname %s,err %s", productName, serviceName, err) } if !existUpdate { for _, container := range templateService.Containers { @@ -2719,10 +2719,10 @@ func diffRenderSet(username, productName, envName, updateType string, productRes serviceInfoCur := serviceMap[serviceName] imageRelatedKey := sets.NewString() if serviceInfoResp != nil && serviceInfoCur != nil { - existUpdate, err := checkServiceImageUpdate(productName, serviceName, serviceInfoCur) + existUpdate, err := checkServiceImageUpdated(productName, serviceName, serviceInfoCur) if err != nil { - log.Errorf("checkServiceImageUpdate,productName %s,svcname %s,err %s", productName, serviceName, err) - return nil, fmt.Errorf("checkServiceImageUpdate,productName %s,svcname %s,err %s", productName, serviceName, err) + log.Errorf("checkServiceImageUpdated,productName %s,svcname %s,err %s", productName, serviceName, err) + return nil, fmt.Errorf("checkServiceImageUpdated,productName %s,svcname %s,err %s", productName, serviceName, err) } if !existUpdate { for _, container := range serviceInfoResp.Containers { @@ -2776,8 +2776,8 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return renderSet, nil } -//checkServiceImageUpdate If the service does not do any mirroring iterations on the platform, the latest YAML is used when updating the environment -func checkServiceImageUpdate(productName, serviceName string, serviceInfo *commonmodels.ProductService) (bool, error) { +//checkServiceImageUpdated If the service does not do any mirroring iterations on the platform, the latest YAML is used when updating the environment +func checkServiceImageUpdated(productName, serviceName string, serviceInfo *commonmodels.ProductService) (bool, error) { existUpdate := false curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, -- Gitee From 85a007fcd4b1176d25df637a2cb6273d8fdeab54 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 11 Feb 2022 14:25:25 +0800 Subject: [PATCH 1508/2647] Remove useless code Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7cc97650..f272fb7c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1355,17 +1355,6 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo } } - // serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(args.ProductName) - // if err != nil { - // log.Infof("query services from product: %s fail, error %s", args.ProductName, err.Error()) - // return envStatuses, e.ErrUpdateEnv.AddDesc("failed to query services") - // } - - // serviceMap := make(map[string]*commonmodels.Service) - // for _, singleService := range serviceList { - // serviceMap[singleService.ServiceName] = singleService - // } - // extract values.yaml and update renderset for envName := range productMap { renderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ -- Gitee From 59a31af2caa3d9448ee3dc6f73fba6ddfd20b4a2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 14:51:47 +0800 Subject: [PATCH 1509/2647] add policymeta Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 8 ++++++++ pkg/microservice/aslan/core/workflow/handler/policy.yaml | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index a365c0d5..8af2e147 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -6,12 +6,17 @@ rules: alias: "查看集成环境" description: "" rules: + - method: GET + endpoint: "/api/aslan/environment/environments" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" matchAttributes: - key: "production" value: "false" + - method: GET + endpoint: "/api/aslan/environment/environments/?*" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" @@ -51,6 +56,9 @@ rules: endpoint: "/api/aslan/project/products/?*/services" - method: GET endpoint: "/api/aslan/environment/revision/products" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/workloads" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/environments/?*/workloads" resourceType: "Environment" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 09765726..d7f6525f 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -6,6 +6,12 @@ rules: alias: "查看工作流" description: "" rules: + - method: GET + endpoint: "api/aslan/workflow/workflow" + resourceType: "Workflow" + matchAttributes: + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "api/aslan/workflow/workflow" - method: GET -- Gitee From 1095611f100f67f50513571d708162b0b386c7e6 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 11 Feb 2022 15:04:12 +0800 Subject: [PATCH 1510/2647] fix policy error Signed-off-by: panxunying --- pkg/cli/initconfig/cmd/init.go | 5 +- pkg/cli/initconfig/cmd/read-project-only.yaml | 2 + .../aslan/core/workflow/handler/workflow.go | 8 ++- .../workflow/service/workflow/workflow.go | 4 +- .../policy/core/service/bundle/opa_bundle.go | 50 +++++++++++++++---- 5 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 pkg/cli/initconfig/cmd/read-project-only.yaml diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 6b465f65..30fafc4a 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -48,6 +48,9 @@ var contributor []byte //go:embed read-only.yaml var readOnly []byte +//go:embed read-project-only.yaml +var readProjectOnly []byte + //go:embed admin.yaml var admin []byte @@ -185,7 +188,7 @@ func presetRole() error { return policy.NewDefault().CreateSystemRole(systemRole.Name, systemRole) }) - rolesArray := [][]byte{readOnly, contributor, projectAdmin} + rolesArray := [][]byte{readOnly, readProjectOnly, contributor, projectAdmin} for _, v := range rolesArray { role := &policy.Role{} diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml new file mode 100644 index 00000000..6b0835a1 --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -0,0 +1,2 @@ +name: read-only +rules: \ No newline at end of file diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow.go b/pkg/microservice/aslan/core/workflow/handler/workflow.go index c73934a4..3f15d9c5 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow.go @@ -128,7 +128,13 @@ func ListWorkflows(c *gin.Context) { projects = []string{c.Query("projectName")} } - ctx.Resp, ctx.Err = workflow.ListWorkflows(projects, ctx.UserID, ctx.Logger) + workflowNames, found := internalhandler.GetResourcesInHeader(c) + if found && len(workflowNames) == 0 { + ctx.Resp = []*workflow.Workflow{} + return + } + + ctx.Resp, ctx.Err = workflow.ListWorkflows(projects, ctx.UserID, workflowNames, ctx.Logger) } func ListTestWorkflows(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 30669d9e..88d16ddf 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -503,14 +503,14 @@ func validateWorkflowHookNames(w *commonmodels.Workflow) error { return validateHookNames(names) } -func ListWorkflows(projects []string, userID string, log *zap.SugaredLogger) ([]*Workflow, error) { +func ListWorkflows(projects []string, userID string, names []string, log *zap.SugaredLogger) ([]*Workflow, error) { existingProjects, err := template.NewProductColl().ListNames(projects) if err != nil { log.Errorf("Failed to list projects, err: %s", err) return nil, e.ErrListWorkflow.AddDesc(err.Error()) } - workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{Projects: existingProjects}) + workflows, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{Projects: existingProjects, Names: names}) if err != nil { log.Errorf("Failed to list workflows, err: %s", err) return nil, e.ErrListWorkflow.AddDesc(err.Error()) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index c15b4207..ab88037c 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -241,18 +241,50 @@ func (o policyBindings) Less(i, j int) bool { return o[i].UID < o[j].UID } -func generateOPARoles(roles []*models.Role, policies []*models.PolicyMeta) *opaRoles { +func generateOPARoles(roles []*models.Role, policyMetas []*models.PolicyMeta) *opaRoles { data := &opaRoles{} - resourceMappings := getResourceActionMappings(policies) + resourceMappings := getResourceActionMappings(policyMetas) for _, ro := range roles { - opaRole := &role{Name: ro.Name, Namespace: ro.Namespace} + verbAttrMap := make(map[string]sets.String) + resourceVerbs := make(map[string]sets.String) for _, r := range ro.Rules { - if r.Kind == models.KindResource { - for _, res := range r.Resources { - opaRole.Rules = append(opaRole.Rules, resourceMappings.GetRules(res, r.Verbs)...) + for _, verb := range r.Verbs { + if verbs, ok := resourceVerbs[r.Resources[0]]; ok { + for _, v := range r.Verbs { + verbs.Insert(v) + } + resourceVerbs[r.Resources[0]] = verbs + } else { + verbSet := sets.String{} + for _, v := range r.Verbs { + verbSet.Insert(v) + } + resourceVerbs[r.Resources[0]] = verbSet + } + if attrs, ok := verbAttrMap[verb]; ok { + for _, attribute := range r.MatchAttributes { + attrs.Insert(attribute.Key + "&&" + attribute.Value) + } + verbAttrMap[verb] = attrs + } else { + attrSet := sets.String{} + for _, attribute := range r.MatchAttributes { + attrSet.Insert(attribute.Key + "&&" + attribute.Value) + } + verbAttrMap[verb] = attrSet } - } else { + } + } + + //TODO - mouuii change role model to policy model + opaRole := &role{Name: ro.Name, Namespace: ro.Namespace} + for resource, verbs := range resourceVerbs { + ruleList := resourceMappings.GetPolicyRules(resource, verbs.List(), verbAttrMap) + opaRole.Rules = append(opaRole.Rules, ruleList...) + } + for _, r := range ro.Rules { + if r.Kind != models.KindResource { if len(r.Verbs) == 1 && r.Verbs[0] == models.MethodAll { r.Verbs = AllMethods } @@ -262,15 +294,11 @@ func generateOPARoles(roles []*models.Role, policies []*models.PolicyMeta) *opaR } } } - } - sort.Sort(opaRole.Rules) data.Roles = append(data.Roles, opaRole) } - sort.Sort(data.Roles) - return data } -- Gitee From 35384f1701fb9104a7275dc319b22485c982d86f Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 15:09:56 +0800 Subject: [PATCH 1511/2647] add role Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 5 ++++- pkg/cli/initconfig/cmd/read-project-only.yaml | 19 +++++++++++++++++++ pkg/shared/client/policy/role.go | 7 ++++--- 3 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 pkg/cli/initconfig/cmd/read-project-only.yaml diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 6b465f65..60a57f6f 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -54,6 +54,9 @@ var admin []byte //go:embed project-admin.yaml var projectAdmin []byte +//go:embed read-project-only.yaml +var readProjectOnly []byte + var initCmd = &cobra.Command{ Use: "init", Short: "init system config", @@ -185,7 +188,7 @@ func presetRole() error { return policy.NewDefault().CreateSystemRole(systemRole.Name, systemRole) }) - rolesArray := [][]byte{readOnly, contributor, projectAdmin} + rolesArray := [][]byte{readOnly, contributor, projectAdmin, readProjectOnly} for _, v := range rolesArray { role := &policy.Role{} diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml new file mode 100644 index 00000000..e1c0fea5 --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -0,0 +1,19 @@ +name: read-project-only +namespace: '' +rules: + - verbs: + - get_workflow + resources: + - Workflow + kind: resource + matchattributes: + - key: placeholder + value: placeholder + - verbs: + - get_environment + resources: + - Environment + kind: resource + matchattributes: + - key: placeholder + value: placeholder \ No newline at end of file diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index f8e65a44..f6e319b5 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -130,9 +130,10 @@ func (c *Client) CreatePublicRole(name string, role *Role) error { type Role struct { Name string `json:"name"` Rules []*struct { - Verbs []string `json:"verbs"` - Resources []string `json:"resources"` - Kind string `json:"kind"` + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + MatchAttributes []MatchAttribute `json:"matchattributes,omitempty"` } `json:"rules"` } -- Gitee From 38a6df3f2677d8e32ac2d62033338f3de2009f70 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 15:14:31 +0800 Subject: [PATCH 1512/2647] fix conflict Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index cbae47b6..30fafc4a 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -57,9 +57,6 @@ var admin []byte //go:embed project-admin.yaml var projectAdmin []byte -//go:embed read-project-only.yaml -var readProjectOnly []byte - var initCmd = &cobra.Command{ Use: "init", Short: "init system config", -- Gitee From fb762fd358919e50c59c77c6697ca05c2d7ba2c3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 16:27:50 +0800 Subject: [PATCH 1513/2647] add group Signed-off-by: mouuii --- init.yaml | 63 +++++++++++++++++++ pkg/cli/initconfig/cmd/read-project-only.yaml | 4 +- .../core/environment/handler/policy.yaml | 2 + .../policy/core/repository/models/common.go | 2 +- pkg/microservice/policy/core/service/role.go | 14 +++-- pkg/shared/client/policy/role.go | 2 +- 6 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 init.yaml diff --git a/init.yaml b/init.yaml new file mode 100644 index 00000000..0f5af417 --- /dev/null +++ b/init.yaml @@ -0,0 +1,63 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: zadig-init + namespace: zadig +spec: + backoffLimit: 10 + completions: 2 + parallelism: 1 + template: + metadata: + creationTimestamp: null + labels: + spec: + containers: + - args: + - init + env: + - name: ADMIN_PASSWORD + value: zadig + - name: ADMIN_EMAIL + value: admin@example.com + - name: ADDRESS + value: http://poc.example.com + image: ccr.ccs.tencentyun.com/koderover-rc/init:20220211154419-amd64 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 10 + httpGet: + path: /healthz + port: 8081 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + name: init-job + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + startupProbe: + failureThreshold: 10 + httpGet: + path: /healthz + port: 8081 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + imagePullSecrets: + - name: qn-registry-secret + restartPolicy: Never + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml index de10fa46..e2075e24 100644 --- a/pkg/cli/initconfig/cmd/read-project-only.yaml +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -6,7 +6,7 @@ rules: resources: - Workflow kind: resource - matchattributes: + match_attributes: - key: placeholder value: placeholder - verbs: @@ -14,6 +14,6 @@ rules: resources: - Environment kind: resource - matchattributes: + match_attributes: - key: placeholder value: placeholder diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 8af2e147..a4761b94 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -48,6 +48,8 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" + - method: GET, + endpoint: "/api/aslan/environment/environments/?*/groups" - method: GET endpoint: "/api/aslan/environment/kube/events" - method: GET diff --git a/pkg/microservice/policy/core/repository/models/common.go b/pkg/microservice/policy/core/repository/models/common.go index c9d7faed..9851d229 100644 --- a/pkg/microservice/policy/core/repository/models/common.go +++ b/pkg/microservice/policy/core/repository/models/common.go @@ -19,7 +19,7 @@ type Rule struct { // Resources is a list of resources this rule applies to. '*' represents all resources. Resources []string `bson:"resources" json:"resources"` Kind string `bson:"kind" json:"kind"` - MatchAttributes []MatchAttribute `json:"match_attributes"` + MatchAttributes []MatchAttribute `bson:"match_attributes" json:"match_attributes"` } type MatchAttribute struct { diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 9990f1cb..79507051 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -39,9 +39,10 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { for _, r := range role.Rules { obj.Rules = append(obj.Rules, &models.Rule{ - Verbs: r.Verbs, - Kind: r.Kind, - Resources: r.Resources, + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + MatchAttributes: r.MatchAttributes, }) } @@ -72,9 +73,10 @@ func UpdateOrCreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { for _, r := range role.Rules { obj.Rules = append(obj.Rules, &models.Rule{ - Verbs: r.Verbs, - Kind: r.Kind, - Resources: r.Resources, + Verbs: r.Verbs, + Kind: r.Kind, + Resources: r.Resources, + MatchAttributes: r.MatchAttributes, }) } return mongodb.NewRoleColl().UpdateOrCreate(obj) diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index f6e319b5..660efec3 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -133,7 +133,7 @@ type Role struct { Verbs []string `json:"verbs"` Resources []string `json:"resources"` Kind string `json:"kind"` - MatchAttributes []MatchAttribute `json:"matchattributes,omitempty"` + MatchAttributes []MatchAttribute `json:"match_attributes,omitempty"` } `json:"rules"` } -- Gitee From 8e2f35979bfb49ed51dc819292c244b1aa8e3160 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 16:31:30 +0800 Subject: [PATCH 1514/2647] delete Signed-off-by: mouuii --- init.yaml | 63 ------------------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 init.yaml diff --git a/init.yaml b/init.yaml deleted file mode 100644 index 0f5af417..00000000 --- a/init.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: zadig-init - namespace: zadig -spec: - backoffLimit: 10 - completions: 2 - parallelism: 1 - template: - metadata: - creationTimestamp: null - labels: - spec: - containers: - - args: - - init - env: - - name: ADMIN_PASSWORD - value: zadig - - name: ADMIN_EMAIL - value: admin@example.com - - name: ADDRESS - value: http://poc.example.com - image: ccr.ccs.tencentyun.com/koderover-rc/init:20220211154419-amd64 - imagePullPolicy: IfNotPresent - livenessProbe: - failureThreshold: 10 - httpGet: - path: /healthz - port: 8081 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - name: init-job - resources: - limits: - cpu: 200m - memory: 256Mi - requests: - cpu: 100m - memory: 128Mi - startupProbe: - failureThreshold: 10 - httpGet: - path: /healthz - port: 8081 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - dnsPolicy: ClusterFirst - imagePullSecrets: - - name: qn-registry-secret - restartPolicy: Never - schedulerName: default-scheduler - securityContext: {} - terminationGracePeriodSeconds: 30 \ No newline at end of file -- Gitee From 54fd23f45319675df36d37626142dd005e1c5666 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Feb 2022 17:07:56 +0800 Subject: [PATCH 1515/2647] fix dockerhost bug Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/build.go | 14 +++++++++----- .../warpdrive/core/service/taskplugin/testing.go | 13 ++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 9594a2ab..44d3fc97 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -129,22 +129,26 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } // not local cluster - replaceDindServer := "." + DindServer + var ( + replaceDindServer = "." + DindServer + dockerHost = "" + ) + if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // replace namespace only - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) + dockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) + dockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) + dockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } - pipelineCtx.DockerHost = pipelineTask.DockerHost + pipelineCtx.DockerHost = dockerHost if pipelineTask.Type == config.WorkflowType { envName := pipelineTask.WorkflowArgs.Namespace diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index b6e4ac04..bdec6c89 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -121,22 +121,25 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.kubeClient = kubeClient } // not local cluster - replaceDindServer := "." + DindServer + var ( + replaceDindServer = "." + DindServer + dockerHost = "" + ) if p.Task.ClusterID != "" && p.Task.ClusterID != setting.LocalClusterID { if strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // replace namespace only - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) + dockerHost = strings.Replace(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace, KoderoverAgentNamespace, 1) } else { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) + dockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) } } else if p.Task.ClusterID == "" || p.Task.ClusterID == setting.LocalClusterID { if !strings.Contains(pipelineTask.DockerHost, pipelineTask.ConfigPayload.Build.KubeNamespace) { // add namespace - pipelineTask.DockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) + dockerHost = strings.Replace(pipelineTask.DockerHost, replaceDindServer, replaceDindServer+"."+pipelineTask.ConfigPayload.Build.KubeNamespace, 1) } } - pipelineCtx.DockerHost = pipelineTask.DockerHost + pipelineCtx.DockerHost = dockerHost // Reset error message. p.Task.Error = "" var linkedNamespace string -- Gitee From 9eb0b63ae54afea7254f3a020c35a067e79b206a Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 18:05:21 +0800 Subject: [PATCH 1516/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index a4761b94..0ad7c500 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -48,7 +48,7 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" - - method: GET, + - method: GET endpoint: "/api/aslan/environment/environments/?*/groups" - method: GET endpoint: "/api/aslan/environment/kube/events" -- Gitee From 98f6b7acfaa09883649cc4b82e1c0223be79e932 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 18:20:23 +0800 Subject: [PATCH 1517/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 0ad7c500..9a7e83d8 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -6,6 +6,8 @@ rules: alias: "查看集成环境" description: "" rules: + - method: GET + endpoint: "/api/aslan/environment/environments/?*/services/down" - method: GET endpoint: "/api/aslan/environment/environments" - method: GET @@ -16,7 +18,6 @@ rules: value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*" - resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" @@ -60,7 +61,6 @@ rules: endpoint: "/api/aslan/environment/revision/products" - method: GET endpoint: "/api/aslan/environment/environments/?*/workloads" - resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/environments/?*/workloads" resourceType: "Environment" -- Gitee From d8fbe41f472015df433e4545b663a6f8a114d725 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 18:39:06 +0800 Subject: [PATCH 1518/2647] bug Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 119 +----------------- .../core/environment/handler/policy.yaml | 84 ++++++------- 2 files changed, 43 insertions(+), 160 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 21377c7e..38825d81 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -19,7 +19,6 @@ package migrate import ( "context" "fmt" - "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" @@ -31,8 +30,6 @@ import ( "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" labelModel "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -49,10 +46,7 @@ func init() { // V190ToV1100 generate labelBindings for production environment func V190ToV1100() error { log.Info("Migrating data from 1.9.0 to 1.10.0") - if err := generateProductionEnv(); err != nil { - log.Errorf("Failed to generateProductionEnv, err: %s", err) - return err - } + if err := changePolicyCollectionName(); err != nil { log.Errorf("Failed to changePolicyCollectionName, err: %s", err) return err @@ -183,23 +177,6 @@ func deleteLabelAndLabelBindings() error { } -func generateProductionEnv() error { - // create label key=product value=true - err := createLabels() - if err != nil { - log.Errorf("fail to createLabels , err:%s", err) - return err - } - - // create labelBindings for environment resources - if err := createLabelBindings(); err != nil { - log.Errorf("fail to createLabelBindings,err:%s", err) - return err - } - - return nil -} - func newLabelColl() *labelMongodb.LabelColl { name := labelModel.Label{}.TableName() return &labelMongodb.LabelColl{ @@ -217,100 +194,6 @@ func newPolicyMetaColl() *mongo.Collection { return collection } -func createLabels() error { - var toCreateLabels []*labelModel.Label - productEnvLabel := &labelModel.Label{ - Type: "system", - Key: "production", - Value: "true", - CreateBy: "system", - CreateTime: time.Now().Unix(), - } - nonProductEnvLabel := &labelModel.Label{ - Type: "system", - Key: "production", - Value: "false", - CreateBy: "system", - CreateTime: time.Now().Unix(), - } - toCreateLabels = append(toCreateLabels, productEnvLabel, nonProductEnvLabel) - if err := newLabelColl().BulkCreate(toCreateLabels); err != nil { - log.Errorf("fail to BulkCreate , err:%s", err) - return err - } - return nil -} - -func createLabelBindings() error { - envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{}) - if err != nil { - log.Errorf("fail to List ProductColl, err:%s", err) - return err - } - clusterMap := make(map[string]*commonmodels.K8SCluster) - clusters, err := commonrepo.NewK8SClusterColl().List(nil) - if err != nil { - log.Errorf("Failed to list clusters, err: %s", err) - return err - } - - for _, cls := range clusters { - clusterMap[cls.ID.Hex()] = cls - } - lisOpt := labelMongodb.ListLabelOpt{ - []labelMongodb.Label{ - { - Key: "production", - Value: "false", - }, - { - Key: "production", - Value: "true", - }, - }, - } - labels, err := newLabelColl().List(lisOpt) - if err != nil { - log.Errorf("Failed to list labels, err: %s", err) - return err - } - if len(labels) != 2 { - return fmt.Errorf("production labels len not equal 2") - } - var productLabelID, nonProductLabelID string - for _, label := range labels { - if label.Value == "false" { - nonProductLabelID = label.ID.Hex() - } else { - productLabelID = label.ID.Hex() - } - } - var labelBindings []*labelMongodb.LabelBinding - for _, env := range envs { - lb := &labelMongodb.LabelBinding{ - Resource: labelMongodb.Resource{ - Name: env.EnvName, - ProjectName: env.ProductName, - Type: "Environment", - }, - CreateBy: "system", - } - cluster, ok := clusterMap[env.ClusterID] - if ok && cluster.Production { - lb.LabelID = productLabelID - } else { - lb.LabelID = nonProductLabelID - } - labelBindings = append(labelBindings, lb) - } - - if err := labelMongodb.NewLabelBindingColl().CreateMany(labelBindings); err != nil { - log.Errorf("Failed toCreateMany labels, err: %s", err) - return err - } - return nil -} - func migrateModuleBuild() error { buildCol := internalmongodb.NewBuildColl() diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index a4761b94..64838b82 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -12,8 +12,8 @@ rules: endpoint: "/api/aslan/environment/environments" resourceType: "Environment" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" @@ -22,22 +22,22 @@ rules: resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/groups" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/groups$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/kube/workloads" - method: GET @@ -66,22 +66,22 @@ rules: resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/workloads$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/releases" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/charts" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - action: create_environment @@ -92,8 +92,8 @@ rules: endpoint: "/api/aslan/environment/environments" resourceType: "Cluster" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: POST endpoint: "/api/aslan/service/workloads" - method: GET @@ -104,8 +104,8 @@ rules: endpoint: "api/aslan/cluster/clusters" resourceType: "Cluster" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - action: config_environment alias: "配置集成环境" description: "" @@ -114,35 +114,35 @@ rules: endpoint: "/api/aslan/environment/environments" resourceType: "Environment" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/service/pm/healthCheckUpdate" resourceType: "Environment" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*/envRecycle" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/envRecycle$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*/renderset" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/renderset$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/service/workloads" - method: GET @@ -152,8 +152,8 @@ rules: resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/registry$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - action: manage_environment alias: "管理服务实例" description: "" @@ -163,36 +163,36 @@ rules: resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scale" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" - method: POST endpoint: "/api/aslan/environment/image/deployment" - method: POST @@ -216,5 +216,5 @@ rules: resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" matchAttributes: - - key: "production" - value: "false" + - key: "placeholder" + value: "placeholder" -- Gitee From f0dbd80f93fea73bab91f6ad920eed3b73ff39b7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Feb 2022 18:44:35 +0800 Subject: [PATCH 1519/2647] local cluster add strategy Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/kube/service.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index a9bb662b..59f7c0fc 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -106,6 +106,9 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, id string, logger *z if id == setting.LocalClusterID { cluster.Status = setting.Normal cluster.Local = true + cluster.AdvancedConfig = &commonmodels.AdvancedConfig{ + Strategy: "normal", + } } err = s.coll.Create(cluster, id) if err != nil { -- Gitee From a983ad4a6db1d2ab977d91c5ee74a3dae60345c2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 18:46:11 +0800 Subject: [PATCH 1520/2647] bug Signed-off-by: mouuii --- init.yaml | 63 +++++++++++++++++++ pkg/cli/initconfig/cmd/read-project-only.yaml | 16 +++++ 2 files changed, 79 insertions(+) create mode 100644 init.yaml diff --git a/init.yaml b/init.yaml new file mode 100644 index 00000000..9d05c40d --- /dev/null +++ b/init.yaml @@ -0,0 +1,63 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: zadig-init + namespace: zadig +spec: + backoffLimit: 10 + completions: 2 + parallelism: 1 + template: + metadata: + creationTimestamp: null + labels: + spec: + containers: + - args: + - init + env: + - name: ADMIN_PASSWORD + value: zadig + - name: ADMIN_EMAIL + value: admin@example.com + - name: ADDRESS + value: http://poc.example.com + image: ccr.ccs.tencentyun.com/koderover-rc/init:20220211184058-amd64 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 10 + httpGet: + path: /healthz + port: 8081 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + name: init-job + resources: + limits: + cpu: 200m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + startupProbe: + failureThreshold: 10 + httpGet: + path: /healthz + port: 8081 + scheme: HTTP + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 1 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + imagePullSecrets: + - name: qn-registry-secret + restartPolicy: Never + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml index e2075e24..cabb74a8 100644 --- a/pkg/cli/initconfig/cmd/read-project-only.yaml +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -17,3 +17,19 @@ rules: match_attributes: - key: placeholder value: placeholder + - verbs: + - get_build + - get_service + resources: + - Service + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource + - verbs: + - get_delivery + resources: + - Delivery + kind: resource \ No newline at end of file -- Gitee From 22c9ce6bced53f267366bbcf834cc5a81198d81d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 18:50:20 +0800 Subject: [PATCH 1521/2647] bug Signed-off-by: mouuii --- init.yaml | 2 +- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/init.yaml b/init.yaml index 9d05c40d..57069c0d 100644 --- a/init.yaml +++ b/init.yaml @@ -22,7 +22,7 @@ spec: value: admin@example.com - name: ADDRESS value: http://poc.example.com - image: ccr.ccs.tencentyun.com/koderover-rc/init:20220211184058-amd64 + image: ccr.ccs.tencentyun.com/koderover-rc/init:20220211184641-amd64 imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 10 diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 64838b82..cf10e162 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -48,7 +48,7 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" - - method: GET, + - method: GET endpoint: "/api/aslan/environment/environments/?*/groups" - method: GET endpoint: "/api/aslan/environment/kube/events" -- Gitee From 6b73619a144708b81c18339a47726ede23d52abd Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 19:02:31 +0800 Subject: [PATCH 1522/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index cf10e162..9c4a7db4 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -6,6 +6,8 @@ rules: alias: "查看集成环境" description: "" rules: + - method: GET + endpoint: "/api/aslan/environment/environments/?*/services/down" - method: GET endpoint: "/api/aslan/environment/environments" - method: GET -- Gitee From c41ec819212ac19ed3d33885d0bda1c4947069a6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Feb 2022 19:07:45 +0800 Subject: [PATCH 1523/2647] bug Signed-off-by: mouuii --- init.yaml | 63 ------------------------------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 init.yaml diff --git a/init.yaml b/init.yaml deleted file mode 100644 index 57069c0d..00000000 --- a/init.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: batch/v1 -kind: Job -metadata: - name: zadig-init - namespace: zadig -spec: - backoffLimit: 10 - completions: 2 - parallelism: 1 - template: - metadata: - creationTimestamp: null - labels: - spec: - containers: - - args: - - init - env: - - name: ADMIN_PASSWORD - value: zadig - - name: ADMIN_EMAIL - value: admin@example.com - - name: ADDRESS - value: http://poc.example.com - image: ccr.ccs.tencentyun.com/koderover-rc/init:20220211184641-amd64 - imagePullPolicy: IfNotPresent - livenessProbe: - failureThreshold: 10 - httpGet: - path: /healthz - port: 8081 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - name: init-job - resources: - limits: - cpu: 200m - memory: 256Mi - requests: - cpu: 100m - memory: 128Mi - startupProbe: - failureThreshold: 10 - httpGet: - path: /healthz - port: 8081 - scheme: HTTP - initialDelaySeconds: 10 - periodSeconds: 5 - successThreshold: 1 - timeoutSeconds: 1 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - dnsPolicy: ClusterFirst - imagePullSecrets: - - name: qn-registry-secret - restartPolicy: Never - schedulerName: default-scheduler - securityContext: {} - terminationGracePeriodSeconds: 30 \ No newline at end of file -- Gitee From b76f46961f540a996662937ec3a9810cd96f044a Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 11 Feb 2022 22:41:12 +0800 Subject: [PATCH 1524/2647] fix tag build bug Signed-off-by: ddh27 --- .../service/workflow/pipeline_validation.go | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index ab394a9a..60d52920 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -359,27 +359,35 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { gitCli := git.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr()) //// 需要后端自动获取Branch当前Commit,并填写到build中 if build.CommitID == "" { - // 如果是仅填写Branch编译 - if build.Branch != "" && build.PR == 0 { + if build.Tag != "" && build.PR == 0 { + opt := &github.ListOptions{Page: 1, PerPage: 100} + tags, _, err := gitCli.Repositories.ListTags(context.Background(), build.RepoOwner, build.RepoName, opt) + if err != nil { + log.Errorf("failed to github ListTags err:%s", err) + return + } + for _, tag := range tags { + if *tag.Name == build.Tag { + build.CommitID = tag.Commit.GetSHA() + if tag.Commit.Message == nil { + commitInfo, _, err := gitCli.Repositories.GetCommit(context.Background(), build.RepoOwner, build.RepoName, build.CommitID) + if err != nil { + log.Errorf("failed to github GetCommit %s err:%s", tag.Commit.GetURL(), err) + return + } + build.CommitMessage = commitInfo.GetCommit().GetMessage() + } + build.AuthorName = tag.Commit.GetAuthor().GetName() + return + } + } + } else if build.Branch != "" && build.PR == 0 { branch, _, err := gitCli.Repositories.GetBranch(context.Background(), build.RepoOwner, build.RepoName, build.Branch) if err == nil { build.CommitID = *branch.Commit.SHA build.CommitMessage = *branch.Commit.Commit.Message build.AuthorName = *branch.Commit.Commit.Author.Name } - } else if build.Tag != "" && build.PR == 0 { - opt := &github.ListOptions{Page: 1, PerPage: 100} - tags, _, err := gitCli.Repositories.ListTags(context.Background(), build.RepoOwner, build.RepoName, opt) - if err == nil { - for _, tag := range tags { - if *tag.Name == build.Tag { - build.CommitID = *tag.Commit.SHA - build.CommitMessage = *tag.Commit.Message - build.AuthorName = *tag.Commit.Author.Name - return - } - } - } } else if build.PR > 0 { opt := &github.ListOptions{Page: 1, PerPage: 100} prCommits, _, err := gitCli.PullRequests.ListCommits(context.Background(), build.RepoOwner, build.RepoName, build.PR, opt) -- Gitee From 683d82b9185b2dad5886b847838e1c9f29cf3c7c Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 14 Feb 2022 10:10:56 +0800 Subject: [PATCH 1525/2647] support IM tag view Signed-off-by: ddh27 --- .../core/common/service/instantmessage/service.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index d82d9f7b..04c390f9 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -295,10 +295,14 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if err != nil { return "", "", nil, err } - branch, commitID, commitMsg, gitCommitURL := "", "", "", "" + branchTag, branchTagType, commitID, commitMsg, gitCommitURL := "", "Branch", "", "", "" for idx, buildRepo := range buildSt.JobCtx.Builds { if idx == 0 || buildRepo.IsPrimary { - branch = buildRepo.Branch + branchTag = buildRepo.Branch + if buildRepo.Tag != "" { + branchTagType = "Tag" + branchTag = buildRepo.Tag + } if len(buildRepo.CommitID) > 8 { commitID = buildRepo.CommitID[0:8] } @@ -306,8 +310,8 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if len(commitMsgs) > 0 { commitMsg = commitMsgs[0] } - if len(commitMsg) > 40 { - commitMsg = commitMsg[0:40] + if len(commitMsg) > 60 { + commitMsg = commitMsg[0:60] } gitCommitURL = fmt.Sprintf("%s/%s/%s/commit/%s", buildRepo.Address, buildRepo.RepoOwner, buildRepo.RepoName, commitID) } @@ -317,7 +321,7 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific } buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**服务名称**:%s \n", buildSt.ServiceName) buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.JobCtx.Image) - buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:[Branch-%s %s](%s) \n", branch, commitID, gitCommitURL) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:[%s-%s %s](%s) \n", branchTagType, branchTag, commitID, gitCommitURL) buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**提交信息**:%s \n", commitMsg) build = append(build, buildElemTemp) } -- Gitee From c81cc544c8e0b5baaad91041d91fc76e95ca3182 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 14 Feb 2022 10:18:29 +0800 Subject: [PATCH 1526/2647] revolve comments Signed-off-by: allenshen --- pkg/microservice/aslan/core/workflow/service/webhook/utils.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 0a5f89cf..be0e959d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -149,7 +149,6 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar if err := setCurrentContainerImages(args); err != nil { return err } - log.Infof("find %d containers in service %s", len(args.Containers), args.ServiceName) } else if args.Type == setting.HelmDeployType { if args.Source == setting.SourceFromGitlab { @@ -163,7 +162,6 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar log.Errorf("Sync content from gitlab failed, error: %s", err) return err } - } else if args.Source == setting.SourceFromGithub { if err := reloadServiceTmplFromGit(args, log); err != nil { log.Errorf("Sync content from github failed, error: %s", err) -- Gitee From 02ed195ad2f4aeac30d635a09f43c52d345e76c3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 14 Feb 2022 10:27:26 +0800 Subject: [PATCH 1527/2647] set default max history parameter Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 1 + pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 1 + 2 files changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 6df3af1f..16663440 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2304,6 +2304,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha ValuesYaml: valuesYaml, UpgradeCRDs: true, CleanupOnFail: true, + MaxHistory: 10, } if isRetry { chartSpec.Replace = true diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index e477f9db..38621521 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -568,6 +568,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Timeout: time.Second * setting.DeployTimeout, Wait: true, Replace: true, + MaxHistory: 10, } done := make(chan bool) -- Gitee From 4f1c206f95c4a5fd6fe7b62169d8f655e28bf344 Mon Sep 17 00:00:00 2001 From: lilianzhu Date: Mon, 14 Feb 2022 10:27:55 +0800 Subject: [PATCH 1528/2647] remove dead link Signed-off-by: lilianzhu --- CONTRIBUTING-zh-CN.md | 1 - CONTRIBUTING.md | 1 - 2 files changed, 2 deletions(-) diff --git a/CONTRIBUTING-zh-CN.md b/CONTRIBUTING-zh-CN.md index fb156f68..0745307c 100644 --- a/CONTRIBUTING-zh-CN.md +++ b/CONTRIBUTING-zh-CN.md @@ -167,4 +167,3 @@ swag init -d ./lib/microservice/aslan/ -g server/rest/router.go -o ./lib/microse ### 其它资源 - [项目的 maintainers](GOVERNANCE.md#maintainers) -- [API 文档](https://os.koderover.com/api/spock/apidocs/index.html) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24744da4..0631c882 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -180,4 +180,3 @@ more information. ### More resources - [Current maintainers](GOVERNANCE.md#maintainers) -- [API docs](https://os.koderover.com/api/spock/apidocs/index.html) -- Gitee From 09e3002a201893bc421a9852fc56ed687fdf4c4e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 14 Feb 2022 10:39:44 +0800 Subject: [PATCH 1529/2647] use const Signed-off-by: ddh27 --- .../core/common/service/instantmessage/service.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 04c390f9..b933e69a 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -42,6 +42,12 @@ const ( multiInfo = "multi" ) +const ( + BranchTagTypeBranch = "Branch" + BranchTagTypeTag = "Tag" + CommitMsgInterceptLength = 60 +) + type Service struct { proxyColl *mongodb.ProxyColl workflowColl *mongodb.WorkflowColl @@ -295,12 +301,12 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if err != nil { return "", "", nil, err } - branchTag, branchTagType, commitID, commitMsg, gitCommitURL := "", "Branch", "", "", "" + branchTag, branchTagType, commitID, commitMsg, gitCommitURL := "", BranchTagTypeBranch, "", "", "" for idx, buildRepo := range buildSt.JobCtx.Builds { if idx == 0 || buildRepo.IsPrimary { branchTag = buildRepo.Branch if buildRepo.Tag != "" { - branchTagType = "Tag" + branchTagType = BranchTagTypeTag branchTag = buildRepo.Tag } if len(buildRepo.CommitID) > 8 { @@ -310,8 +316,8 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if len(commitMsgs) > 0 { commitMsg = commitMsgs[0] } - if len(commitMsg) > 60 { - commitMsg = commitMsg[0:60] + if len(commitMsg) > CommitMsgInterceptLength { + commitMsg = commitMsg[0:CommitMsgInterceptLength] } gitCommitURL = fmt.Sprintf("%s/%s/%s/commit/%s", buildRepo.Address, buildRepo.RepoOwner, buildRepo.RepoName, commitID) } -- Gitee From 695357a663fd0abd91bd2b4c3026e34bcb11f7af Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 14 Feb 2022 10:41:59 +0800 Subject: [PATCH 1530/2647] use const Signed-off-by: ddh27 --- .../aslan/core/common/service/instantmessage/service.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index b933e69a..c4b8b70b 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -42,10 +42,12 @@ const ( multiInfo = "multi" ) +type BranchTagType string + const ( - BranchTagTypeBranch = "Branch" - BranchTagTypeTag = "Tag" - CommitMsgInterceptLength = 60 + BranchTagTypeBranch BranchTagType = "Branch" + BranchTagTypeTag BranchTagType = "Tag" + CommitMsgInterceptLength = 60 ) type Service struct { -- Gitee From 7568d7c2775119f74b6123fc00526e5c4278c36e Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Feb 2022 11:19:04 +0800 Subject: [PATCH 1531/2647] remove rollback delete labelbinding logic Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 60 -------------------- 1 file changed, 60 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 38825d81..401d3389 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -30,9 +30,6 @@ import ( "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/config" - labelModel "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" - labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" @@ -120,11 +117,6 @@ func rollbackChangePolicyCollectionName() error { } func V1100ToV190() error { - if err := deleteLabelAndLabelBindings(); err != nil { - log.Errorf("Failed to generateProductionEnv, err: %s", err) - return err - } - if err := rollbackChangePolicyCollectionName(); err != nil { log.Errorf("Failed to rollbackChangePolicyCollectionName") return err @@ -132,58 +124,6 @@ func V1100ToV190() error { return nil } -func deleteLabelAndLabelBindings() error { - lisOpt := labelMongodb.ListLabelOpt{ - []labelMongodb.Label{ - { - Key: "production", - Value: "false", - }, - { - Key: "production", - Value: "true", - }, - }, - } - labels, err := newLabelColl().List(lisOpt) - if err != nil { - return err - } - if len(labels) != 2 { - log.Errorf("production labels len not equal 2,current is %d\n", len(labels)) - return fmt.Errorf("production labels len not equal 2,current is %d\n", len(labels)) - } - var labelIDs []string - for _, label := range labels { - labelIDs = append(labelIDs, label.ID.Hex()) - } - - res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIDs}) - if err != nil { - log.Errorf("list labelbingding err:%s", err) - return err - } - - var labelBindingIDs []string - for _, labelBinding := range res { - labelBindingIDs = append(labelBindingIDs, labelBinding.ID.Hex()) - } - - if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { - log.Errorf("Failed to BulkDeleteByIds, err: %s", err) - return err - } - return mongodb.NewLabelColl().BulkDelete(labelIDs) - -} - -func newLabelColl() *labelMongodb.LabelColl { - name := labelModel.Label{}.TableName() - return &labelMongodb.LabelColl{ - Collection: mongotool.Database(fmt.Sprintf("%s", config.MongoDatabase())).Collection(name), - } -} - func newPolicyColl() *mongo.Collection { collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("policy") return collection -- Gitee From a8b08cdecb09950b999456d47785ebb0e1daa56b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Feb 2022 14:06:47 +0800 Subject: [PATCH 1532/2647] compatibility fix for creating environment on existing namespace Signed-off-by: Min Min --- .../aslan/core/environment/service/env.go | 2 +- .../core/environment/service/environment.go | 82 ++++++++++++++++++- .../service/environment_creator.go | 14 +++- .../aslan/core/environment/service/k8s.go | 16 +++- .../aslan/core/environment/service/pm.go | 2 +- .../aslan/core/environment/service/service.go | 12 ++- 6 files changed, 116 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/env.go b/pkg/microservice/aslan/core/environment/service/env.go index b213381a..f56c2a54 100644 --- a/pkg/microservice/aslan/core/environment/service/env.go +++ b/pkg/microservice/aslan/core/environment/service/env.go @@ -27,7 +27,7 @@ import ( ) type envHandle interface { - createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error + createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, inf informers.SharedInformerFactory, kubeClient client.Client) error listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp updateService(args *SvcOptArgs) error queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient informers.SharedInformerFactory) (string, string, []string) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 16663440..aaacaf39 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -37,10 +37,12 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + k8serror "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -57,6 +59,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -445,6 +448,19 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return e.ErrUpdateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), existedProd.ClusterID) + if err != nil { + log.Errorf("[%s][%s] error: %v", envName, namespace, err) + err = e.ErrUpdateEnv.AddDesc(err.Error()) + return + } + inf, err := informer.NewInformer(existedProd.ClusterID, namespace, cls) + if err != nil { + log.Errorf("[%s][%s] error: %v", envName, namespace, err) + err = e.ErrUpdateEnv.AddDesc(err.Error()) + return + } + // 遍历产品环境和产品模板交叉对比的结果 // 四个状态:待删除,待添加,待更新,无需更新 @@ -537,7 +553,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com updateProd, service, existedServices[service.ServiceName], - renderSet, kubeClient, log) + renderSet, inf, kubeClient, log) if err != nil { lock.Lock() switch e := err.(type) { @@ -1602,7 +1618,7 @@ func GetEstimatedRenderCharts(productName, envName, serviceNameListStr string, l return ret, nil } -func createGroups(envName, user, requestID string, args *commonmodels.Product, eventStart int64, renderSet *commonmodels.RenderSet, kubeClient client.Client, log *zap.SugaredLogger) { +func createGroups(envName, user, requestID string, args *commonmodels.Product, eventStart int64, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger) { var err error defer func() { status := setting.ProductStatusSuccess @@ -1629,7 +1645,7 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e }() for _, group := range args.Services { - err = envHandleFunc(getProjectType(args.ProductName), log).createGroup(envName, args.ProductName, user, group, renderSet, kubeClient) + err = envHandleFunc(getProjectType(args.ProductName), log).createGroup(envName, args.ProductName, user, group, renderSet, informer, kubeClient) if err != nil { args.Status = setting.ProductStatusFailed log.Errorf("createGroup error :%+v", err) @@ -1658,7 +1674,7 @@ func getProjectType(productName string) string { // upsertService 创建或者更新服务, 更新服务之前先创建服务需要的配置 func upsertService(isUpdate bool, env *commonmodels.Product, service *commonmodels.ProductService, prevSvc *commonmodels.ProductService, - renderSet *commonmodels.RenderSet, kubeClient client.Client, log *zap.SugaredLogger, + renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger, ) ([]*unstructured.Unstructured, error) { errList := &multierror.Error{ ErrorFormat: func(es []error) string { @@ -1764,10 +1780,20 @@ func upsertService(isUpdate bool, env *commonmodels.Product, } case setting.Deployment, setting.StatefulSet: + // compatibility flag, We add a match label in spec.selector field pre 1.10. + needSelectorLabel := false + u.SetNamespace(namespace) u.SetAPIVersion(setting.APIVersionAppsV1) u.SetLabels(kube.MergeLabels(labels, u.GetLabels())) + switch u.GetKind() { + case setting.Deployment: + needSelectorLabel = deploymentSelectorLabelExists(u.GetName(), namespace, informer, log) + case setting.StatefulSet: + needSelectorLabel = statefulsetSelectorLabelExists(u.GetName(), namespace, informer, log) + } + podLabels, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") if err != nil { podLabels = nil @@ -1788,6 +1814,20 @@ func upsertService(isUpdate bool, env *commonmodels.Product, u.Object = setFieldValueIsNotExist(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") } + if needSelectorLabel { + // Inject selector: s-product and s-service + selector, _, err := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") + if err != nil { + selector = nil + } + + err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") + if err != nil { + log.Errorf("merge selector failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") + } + } + jsonData, err := u.MarshalJSON() if err != nil { log.Errorf("Failed to marshal JSON, manifest is\n%v\n, error: %v", u, err) @@ -2908,3 +2948,37 @@ func setFieldValueIsNotExist(obj map[string]interface{}, value interface{}, fiel m[fields[len(fields)-1]] = value return obj } + +func deploymentSelectorLabelExists(resourceName, namespace string, informer informers.SharedInformerFactory, log *zap.SugaredLogger) bool { + deployment, err := informer.Apps().V1().Deployments().Lister().Deployments(namespace).Get(resourceName) + // default we assume the deployment is new so we don't need to add selector labels + if err != nil { + if !k8serror.IsNotFound(err) { + log.Errorf("Failed to find deployment in the namespace: %s, the error is: %s", namespace, err) + } + return false + } + // since the 2 predefined labels are always together, we just check for only one + // if the match label exists, we return true. otherwise we return false + if _, ok := deployment.Spec.Selector.MatchLabels["s-product"]; ok { + return true + } + return false +} + +func statefulsetSelectorLabelExists(resourceName, namespace string, informer informers.SharedInformerFactory, log *zap.SugaredLogger) bool { + deployment, err := informer.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(resourceName) + // default we assume the deployment is new so we don't need to add selector labels + if err != nil { + if !k8serror.IsNotFound(err) { + log.Errorf("Failed to find deployment in the namespace: %s, the error is: %s", namespace, err) + } + return false + } + // since the 2 predefined labels are always together, we just check for only one + // if the match label exists, we return true. otherwise we return false + if _, ok := deployment.Spec.Selector.MatchLabels["s-product"]; ok { + return true + } + return false +} diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index b57f6014..1531b0c5 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -34,6 +34,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" ) type CreateProductParam struct { @@ -282,7 +283,7 @@ func (creator *PMProductCreator) Create(user, requestID string, args *models.Pro return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, log) + go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, nil, log) return nil } @@ -312,6 +313,15 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + inf, err := informer.NewInformer(clusterID, args.Namespace, cls) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + //判断namespace是否存在 namespace := args.GetNamespace() if args.Namespace == "" { @@ -371,6 +381,6 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, kubeClient, log) + go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, inf, kubeClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 0b74a85c..8b02e2a9 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -37,6 +37,7 @@ import ( "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/informer" ) type K8sService struct { @@ -89,6 +90,15 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return e.ErrUpdateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), exitedProd.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + inf, err := informer.NewInformer(exitedProd.ClusterID, exitedProd.Namespace, cls) + if err != nil { + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + switch exitedProd.Status { case setting.ProductStatusCreating, setting.ProductStatusUpdating, setting.ProductStatusDeleting: k.log.Errorf("[%s][P:%s] Product is not in valid status", args.EnvName, args.ProductName) @@ -112,7 +122,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { exitedProd, svc, exitedProd.GetServiceMap()[svc.ServiceName], - newRender, kubeClient, k.log) + newRender, inf, kubeClient, k.log) // 如果创建依赖服务组有返回错误, 停止等待 if err != nil { @@ -189,7 +199,7 @@ func (k *K8sService) listGroupServices(allServices []*commonmodels.ProductServic return resp } -func (k *K8sService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error { +func (k *K8sService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client) error { k.log.Infof("[Namespace:%s][Product:%s] createGroup", envName, productName) updatableServiceNameList := make([]string, 0) @@ -222,7 +232,7 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] updatableServiceNameList = append(updatableServiceNameList, group[i].ServiceName) go func(svc *commonmodels.ProductService) { defer wg.Done() - items, err := upsertService(false, prod, svc, nil, renderSet, kubeClient, k.log) + items, err := upsertService(false, prod, svc, nil, renderSet, informer, kubeClient, k.log) if err != nil { lock.Lock() switch e := err.(type) { diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 2d9c827b..733e1d05 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -153,7 +153,7 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService return resp } -func (p *PMService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error { +func (p *PMService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, inf informers.SharedInformerFactory, kubeClient client.Client) error { p.log.Infof("[Namespace:%s][Product:%s] createGroup", envName, productName) // 异步创建无依赖的服务 diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 557ec3ab..b5565cba 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -39,6 +39,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -354,6 +355,15 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return err } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), productObj.ClusterID) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + inf, err := informer.NewInformer(productObj.ClusterID, productObj.Namespace, cls) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + // aws secrets needs to be refreshed regs, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { @@ -432,7 +442,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e productObj, productService, productService, - newRender, kubeClient, log) + newRender, inf, kubeClient, log) // 如果创建依赖服务组有返回错误, 停止等待 if err != nil { -- Gitee From 6e9f1c47bce129987aad41bb33316c71a0b28379 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 14 Feb 2022 14:18:25 +0800 Subject: [PATCH 1533/2647] optimize the ret value of pattern searching function Signed-off-by: allenshen --- pkg/util/yaml/path_search.go | 7 +++++-- pkg/util/yaml/path_search_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index 787556d0..919475cf 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -258,7 +258,7 @@ func toPathResult(prefix string, path []string, aPatternData map[string]*pattern return ret } -func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData) []map[string]string { +func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData, pattern map[string]string) []map[string]string { ret := make([]map[string]string, 0) for _, singleSet := range resultSets { retMap := make(map[string]string) @@ -278,6 +278,9 @@ func processResults(resultSets []*pathResultSet, aPatternData map[string]*patter for _, result := range singleSet.resultSet { retMap[result.name] = result.absolutePath } + if len(pattern) != len(retMap) { + continue + } ret = append(ret, retMap) } return ret @@ -408,7 +411,7 @@ func search(flatMap map[string]interface{}, pattern map[string]string) ([]map[st results = append(results, result) } - return processResults(results, allPatternData), nil + return processResults(results, allPatternData, pattern), nil } // SearchByPattern find all matched absolute paths from yaml by the pattern appointed diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 2dfc3e38..d3c97c80 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -101,6 +101,22 @@ service: port: 8080 ` +var testYaml9 = ` + image: + pullPolicy: IfNotPresent + + imagePullSecrets: + - name: default-registry-secret + + global: + hub: ccr.ccs.tencentyun.com/trial + + deploy: + image: + name: go-sample-site + tag: 0.1.0 +` + var err error var lcpMatedPaths []map[string]string @@ -178,5 +194,16 @@ var _ = Describe("Testing search", func() { })) }) + It("match all componments of pattern", func() { + pattern := []map[string]string{ + {"image": "image.repository", "tag": "image.tag"}, + {"image": "image"}, + {"repo": "global.hub", "tag": "deploy.image.tag", "image": "deploy.image.name"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml9)) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + }) + }) }) -- Gitee From 6ba09c80401c0d56af82c76f8a620159a6eed3d7 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 14 Feb 2022 15:08:47 +0800 Subject: [PATCH 1534/2647] add ut code Signed-off-by: allenshen --- pkg/util/yaml/path_search_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index d3c97c80..162e2f96 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -194,7 +194,7 @@ var _ = Describe("Testing search", func() { })) }) - It("match all componments of pattern", func() { + It("match all rules of pattern", func() { pattern := []map[string]string{ {"image": "image.repository", "tag": "image.tag"}, {"image": "image"}, @@ -203,6 +203,9 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml9)) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) Expect(err).NotTo(HaveOccurred()) + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "deploy.image.name", "repo": "global.hub", "tag": "deploy.image.tag"}, + })) }) }) -- Gitee From d89cec5ff5dcdced2e3436c6f84ce9dfd83fb45b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Feb 2022 15:26:09 +0800 Subject: [PATCH 1535/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/environment/service/environment.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index aaacaf39..b6fa3d38 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -37,7 +37,7 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" - k8serror "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" @@ -2953,7 +2953,7 @@ func deploymentSelectorLabelExists(resourceName, namespace string, informer info deployment, err := informer.Apps().V1().Deployments().Lister().Deployments(namespace).Get(resourceName) // default we assume the deployment is new so we don't need to add selector labels if err != nil { - if !k8serror.IsNotFound(err) { + if !apierrors.IsNotFound(err) { log.Errorf("Failed to find deployment in the namespace: %s, the error is: %s", namespace, err) } return false @@ -2970,7 +2970,7 @@ func statefulsetSelectorLabelExists(resourceName, namespace string, informer inf deployment, err := informer.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(resourceName) // default we assume the deployment is new so we don't need to add selector labels if err != nil { - if !k8serror.IsNotFound(err) { + if !apierrors.IsNotFound(err) { log.Errorf("Failed to find deployment in the namespace: %s, the error is: %s", namespace, err) } return false -- Gitee From f00b92dc06ba7517cd48ef26a6399dadb29c860f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Feb 2022 15:55:25 +0800 Subject: [PATCH 1536/2647] err format Signed-off-by: mouuii --- .../core/collaboration/handler/collaboration_instance.go | 4 ++-- .../core/collaboration/handler/collaboration_mode.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index 6eea3d4e..ed022553 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -45,12 +45,12 @@ func SyncCollaborationInstance(c *gin.Context) { args := new(service.SyncCollaborationInstanceArgs) data, err := c.GetRawData() if err != nil { - log.Errorf("SyncCollaborationInstance c.GetRawData() err : %v", err) + log.Errorf("SyncCollaborationInstance c.GetRawData() err: %s", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } if err = json.Unmarshal(data, args); err != nil { - log.Errorf("SyncCollaborationInstance json.Unmarshal err : %v", err) + log.Errorf("SyncCollaborationInstance json.Unmarshal err: %s", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index e844bef5..632d0efc 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -47,12 +47,12 @@ func CreateCollaborationMode(c *gin.Context) { args := new(commonmodels.CollaborationMode) data, err := c.GetRawData() if err != nil { - log.Errorf("CreateCollaborationMode c.GetRawData() err : %v", err) + log.Errorf("CreateCollaborationMode c.GetRawData() err: %s", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } if err = json.Unmarshal(data, args); err != nil { - log.Errorf("CreateCollaborationMode json.Unmarshal err : %v", err) + log.Errorf("CreateCollaborationMode json.Unmarshal err: %s", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } @@ -69,12 +69,12 @@ func UpdateCollaborationMode(c *gin.Context) { args := new(commonmodels.CollaborationMode) data, err := c.GetRawData() if err != nil { - log.Errorf("UpdateCollaborationMode c.GetRawData() err : %v", err) + log.Errorf("UpdateCollaborationMode c.GetRawData() err: %s", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateCollaborationMode json.Unmarshal err : %v", err) + log.Errorf("UpdateCollaborationMode json.Unmarshal err: %s", err) ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } -- Gitee From 1d262b910bb27467b3688107f03e9c510f72c3da Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Feb 2022 19:13:59 +0800 Subject: [PATCH 1537/2647] code review changes2 Signed-off-by: Min Min --- .../aslan/core/environment/service/environment.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index b6fa3d38..5aedcb36 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -451,14 +451,13 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), existedProd.ClusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) - err = e.ErrUpdateEnv.AddDesc(err.Error()) - return + return e.ErrUpdateEnv.AddDesc(err.Error()) + } inf, err := informer.NewInformer(existedProd.ClusterID, namespace, cls) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) - err = e.ErrUpdateEnv.AddDesc(err.Error()) - return + return e.ErrUpdateEnv.AddDesc(err.Error()) } // 遍历产品环境和产品模板交叉对比的结果 @@ -2967,7 +2966,7 @@ func deploymentSelectorLabelExists(resourceName, namespace string, informer info } func statefulsetSelectorLabelExists(resourceName, namespace string, informer informers.SharedInformerFactory, log *zap.SugaredLogger) bool { - deployment, err := informer.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(resourceName) + sts, err := informer.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(resourceName) // default we assume the deployment is new so we don't need to add selector labels if err != nil { if !apierrors.IsNotFound(err) { @@ -2977,7 +2976,7 @@ func statefulsetSelectorLabelExists(resourceName, namespace string, informer inf } // since the 2 predefined labels are always together, we just check for only one // if the match label exists, we return true. otherwise we return false - if _, ok := deployment.Spec.Selector.MatchLabels["s-product"]; ok { + if _, ok := sts.Spec.Selector.MatchLabels["s-product"]; ok { return true } return false -- Gitee From e1ce86cb2ce8f9a330dc82d0100c83202cdfd3a5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Feb 2022 20:19:49 +0800 Subject: [PATCH 1538/2647] clear id Signed-off-by: mouuii --- .../aslan/core/common/repository/mongodb/workflow.go | 7 +++---- pkg/microservice/aslan/core/common/service/workflow.go | 2 +- pkg/microservice/aslan/core/environment/service/bundle.go | 2 +- .../aslan/core/environment/service/environment.go | 4 ++-- .../aslan/core/environment/service/renderset.go | 3 +-- .../aslan/core/label/repository/mongodb/label.go | 7 +++---- .../aslan/core/workflow/service/workflow/bundle.go | 2 +- 7 files changed, 12 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go index a91344bc..d2c60805 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow.go @@ -211,14 +211,13 @@ func (c *WorkflowColl) BulkCreate(args []*models.Workflow) error { if len(args) == 0 { return nil } + var ois []interface{} for _, arg := range args { arg.CreateTime = time.Now().Unix() arg.UpdateTime = time.Now().Unix() + ois = append(ois, arg) } - var ois []interface{} - for _, obj := range args { - ois = append(ois, obj) - } + _, err := c.InsertMany(context.TODO(), ois) return err } diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 6e3e9c3a..d417cc35 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -55,7 +55,7 @@ func DeleteWorkflows(productName, requestID string, log *zap.SugaredLogger) erro } func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log *zap.SugaredLogger) error { - // 在删除前,先将workflow查出来,用于删除gerrit webhook + // query workflow before deleting, used to delete gerrit webhook workflow, err := mongodb.NewWorkflowColl().Find(workflowName) if err != nil { log.Errorf("Workflow.Find error: %v", err) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 390c4cea..fe992020 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -69,7 +69,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec.Spec[v.Key] = v.Value } } else { - logger.Warnf("can not found resource key :%s", resourceKey) + logger.Warnf("can not find resource key :%s", resourceKey) } res = append(res, resourceSpec) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index e4eeef54..8ab710cd 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -923,7 +923,7 @@ func BulkCopyHelmProduct(projectName, user, requestID string, arg CopyHelmProduc args = append(args, &CreateHelmProductArg{ ProductName: projectName, EnvName: item.NewName, - Namespace: "helm-env-" + item.NewName, + Namespace: "helm-env-" + projectName + "-" + item.NewName, ClusterID: product.ClusterID, DefaultValues: item.DefaultValues, RegistryID: product.RegistryID, @@ -964,7 +964,7 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc product.Vars = item.Vars product.Namespace = projectName + "-env-" + product.EnvName product.Render.Name = product.Namespace - product.ID = primitive.NilObjectID + util.Clear(&product.ID) product.Render.Revision = 0 product.BaseName = item.BaseName err = CreateProduct(user, requestID, product, log) diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 3cf21c5d..41958af4 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -21,12 +21,11 @@ import ( "strings" "sync" + "github.com/pkg/errors" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" - "github.com/pkg/errors" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 50ed7969..f435cef7 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -54,13 +54,12 @@ func (c *LabelColl) BulkCreate(labels []*models.Label) error { if len(labels) == 0 { return nil } + var ois []interface{} for _, label := range labels { label.CreateTime = time.Now().Unix() + ois = append(ois, label) } - var ois []interface{} - for _, obj := range labels { - ois = append(ois, obj) - } + _, err := c.InsertMany(context.TODO(), ois) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 7ea5301f..130dcdad 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -68,7 +68,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec.Spec[v.Key] = v.Value } } else { - logger.Warnf("can not found resource key :%s", resourceKey) + logger.Warnf("can not find resource key :%s", resourceKey) } res = append(res, resourceSpec) } -- Gitee From 5383461ed78a8c46340ceb6ed00684f7eac591f6 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 15 Feb 2022 10:56:33 +0800 Subject: [PATCH 1539/2647] fix Keep the image when updating the environment when a chart contains multiple services Signed-off-by: ddh27 --- .../core/environment/service/environment.go | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 16663440..4df38bf7 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1097,25 +1097,32 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa proSvcMap := productInfo.GetServiceMap() proSvc := proSvcMap[serviceName] if proSvc != nil { - existUpdate, err := checkServiceImageUpdated(productName, serviceName, proSvc) + curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + Revision: proSvc.Revision, + }) if err != nil { - log.Errorf("checkServiceImageUpdated, productName %s,svcname %s,err %s", productName, serviceName, err) - return "", "", fmt.Errorf("checkServiceImageUpdated,productName %s, svcname %s,err %s", productName, serviceName, err) - } - if !existUpdate { - for _, container := range templateService.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, proSvc.Revision, err) + return "", "", fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, proSvc.Revision, err) + } + L: + for _, curSvcContainers := range curEnvService.Containers { + if !checkServiceImageUpdated(curSvcContainers, proSvc) { + for _, container := range templateService.Containers { + if curSvcContainers.Name == container.Name && container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + continue L + } } } } } - curValuesYaml := "" if targetChart != nil { // service has been applied into environment, use current values.yaml curValuesYaml = targetChart.ValuesYaml } - // merge environment values mergedBs, err := overrideValues([]byte(curValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) if err != nil { @@ -2696,15 +2703,24 @@ func diffRenderSet(username, productName, envName, updateType string, productRes serviceInfoCur := serviceMap[serviceName] imageRelatedKey := sets.NewString() if serviceInfoResp != nil && serviceInfoCur != nil { - existUpdate, err := checkServiceImageUpdated(productName, serviceName, serviceInfoCur) + curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + Revision: serviceInfoCur.Revision, + }) if err != nil { - log.Errorf("checkServiceImageUpdated,productName %s,svcname %s,err %s", productName, serviceName, err) - return nil, fmt.Errorf("checkServiceImageUpdated,productName %s,svcname %s,err %s", productName, serviceName, err) + log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) + return nil, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) } - if !existUpdate { - for _, container := range serviceInfoResp.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + L: + for _, curSvcContainers := range curEnvService.Containers { + if !checkServiceImageUpdated(curSvcContainers, serviceInfoCur) { + for _, container := range serviceInfoResp.Containers { + if curSvcContainers.Name == container.Name && container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + continue L + } } } } @@ -2754,29 +2770,13 @@ func diffRenderSet(username, productName, envName, updateType string, productRes } //checkServiceImageUpdated If the service does not do any mirroring iterations on the platform, the latest YAML is used when updating the environment -func checkServiceImageUpdated(productName, serviceName string, serviceInfo *commonmodels.ProductService) (bool, error) { - existUpdate := false - curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: serviceName, - ProductName: productName, - Type: setting.HelmDeployType, - Revision: serviceInfo.Revision, - }) - if err != nil { - log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfo.Revision, err) - return existUpdate, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfo.Revision, err) - } - -L: - for _, curContainer := range curEnvService.Containers { - for _, proContainer := range serviceInfo.Containers { - if curContainer.Image == proContainer.Image { - existUpdate = true - break L - } +func checkServiceImageUpdated(curContainer *commonmodels.Container, serviceInfo *commonmodels.ProductService) bool { + for _, proContainer := range serviceInfo.Containers { + if curContainer.Name == proContainer.Name && curContainer.Image == proContainer.Image { + return true } } - return existUpdate, nil + return false } // for keys exist in both yaml, current values will override latest values -- Gitee From f5b265473d86a30ce24d5795662d382b72d6de38 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Feb 2022 11:04:51 +0800 Subject: [PATCH 1540/2647] code review Signed-off-by: mouuii --- .../aslan/core/label/repository/models/label.go | 2 +- pkg/microservice/aslan/core/label/service/label.go | 6 +++--- pkg/microservice/policy/core/handler/policy_binding.go | 2 +- .../policy/core/repository/models/policy_binding.go | 2 +- .../policy/core/repository/models/policy_meta.go | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/label/repository/models/label.go b/pkg/microservice/aslan/core/label/repository/models/label.go index 84848263..933d2744 100644 --- a/pkg/microservice/aslan/core/label/repository/models/label.go +++ b/pkg/microservice/aslan/core/label/repository/models/label.go @@ -27,7 +27,7 @@ type Label struct { Value string `bson:"value" json:"value"` CreateBy string `bson:"create_by" json:"create_by"` CreateTime int64 `bson:"create_time" json:"create_time"` - ProjectName string `bson:"project_name" json:"project_name"` + ProjectName string `bson:"project_name" json:"project_name"` } func (Label) TableName() string { diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 115c6b2d..3cfc0168 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -113,7 +113,7 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( // 1.find labels by label filters labels, err := mongodb.NewLabelColl().List(mongodb.ListLabelOpt{Labels: filters}) if err != nil { - logger.Errorf("labels ListByOpt err:%s", err) + logger.Errorf("find labels by label filters err:%s", err) return nil, err } if len(labels) == 0 { @@ -129,7 +129,7 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: labelIDSet.List()}) if err != nil { - logger.Errorf("labelBindings ListByOpt err:%s", err) + logger.Errorf("list labelBindings err:%s,ids:%v", err, labelIDSet.List()) return nil, err } @@ -165,7 +165,7 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg //1. find the labelBindings by resources labelBindings, err := mongodb.NewLabelBindingColl().ListByResources(mongodb.ListLabelBindingsByResources{Resources: resources}) if err != nil { - logger.Errorf("NewLabelBindingColl ListByResources err:%s", err) + logger.Errorf("find the labelBindings by resources err:%s", err) return nil, err } diff --git a/pkg/microservice/policy/core/handler/policy_binding.go b/pkg/microservice/policy/core/handler/policy_binding.go index 289c8522..82d08a62 100644 --- a/pkg/microservice/policy/core/handler/policy_binding.go +++ b/pkg/microservice/policy/core/handler/policy_binding.go @@ -39,7 +39,7 @@ func UpdatePolicyBinding(c *gin.Context) { } args := &service.PolicyBinding{} if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("bind json fail %s") + ctx.Err = e.ErrInvalidParam.AddDesc("bind json fail") } ctx.Err = service.UpdateOrCreatePolicyBinding(projectName, args, ctx.Logger) } diff --git a/pkg/microservice/policy/core/repository/models/policy_binding.go b/pkg/microservice/policy/core/repository/models/policy_binding.go index 66900693..5ec11fd9 100644 --- a/pkg/microservice/policy/core/repository/models/policy_binding.go +++ b/pkg/microservice/policy/core/repository/models/policy_binding.go @@ -27,7 +27,7 @@ type PolicyBinding struct { Subjects []*Subject `bson:"subjects" json:"subjects"` // PolicyRef can reference a namespaced or cluster scoped Policy. - PolicyRef *PolicyRef `bson:"policy_ref" json:"PolicyRef"` + PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` } // PolicyRef contains information that points to the policy being used diff --git a/pkg/microservice/policy/core/repository/models/policy_meta.go b/pkg/microservice/policy/core/repository/models/policy_meta.go index c1008f53..eaaecb7d 100644 --- a/pkg/microservice/policy/core/repository/models/policy_meta.go +++ b/pkg/microservice/policy/core/repository/models/policy_meta.go @@ -33,9 +33,9 @@ type PolicyMetaRule struct { type ActionRule struct { Method string `bson:"method" json:"method"` Endpoint string `bson:"endpoint" json:"endpoint"` - ResourceType string `bson:"resource_type,omitempty" json:"resourceType,omitempty"` + ResourceType string `bson:"resource_type,omitempty" json:"resource_type,omitempty"` IDRegex string `bson:"id_regex,omitempty" json:"idRegex,omitempty"` - MatchAttributes []Attribute `bson:"match_attributes,omitempty" json:"matchAttributes,omitempty"` + MatchAttributes []Attribute `bson:"match_attributes,omitempty" json:"match_attributes,omitempty"` } type Attribute struct { -- Gitee From 285680c01db3d1f3aee446048ecbbffefa0be966 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 15 Feb 2022 11:13:15 +0800 Subject: [PATCH 1541/2647] fix checkServiceImageUpdated Signed-off-by: ddh27 --- .../aslan/core/environment/service/environment.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4df38bf7..3a9a4527 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1108,10 +1108,10 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa return "", "", fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, proSvc.Revision, err) } L: - for _, curSvcContainers := range curEnvService.Containers { - if !checkServiceImageUpdated(curSvcContainers, proSvc) { + for _, curSvcContainer := range curEnvService.Containers { + if checkServiceImageUpdated(curSvcContainer, proSvc) { for _, container := range templateService.Containers { - if curSvcContainers.Name == container.Name && container.ImagePath != nil { + if curSvcContainer.Name == container.Name && container.ImagePath != nil { imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) continue L } @@ -2715,7 +2715,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes } L: for _, curSvcContainers := range curEnvService.Containers { - if !checkServiceImageUpdated(curSvcContainers, serviceInfoCur) { + if checkServiceImageUpdated(curSvcContainers, serviceInfoCur) { for _, container := range serviceInfoResp.Containers { if curSvcContainers.Name == container.Name && container.ImagePath != nil { imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) @@ -2773,10 +2773,10 @@ func diffRenderSet(username, productName, envName, updateType string, productRes func checkServiceImageUpdated(curContainer *commonmodels.Container, serviceInfo *commonmodels.ProductService) bool { for _, proContainer := range serviceInfo.Containers { if curContainer.Name == proContainer.Name && curContainer.Image == proContainer.Image { - return true + return false } } - return false + return true } // for keys exist in both yaml, current values will override latest values -- Gitee From 6a3b5dff089043335152ba7c72cc3862e6d6f49d Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Feb 2022 11:33:55 +0800 Subject: [PATCH 1542/2647] code format Signed-off-by: mouuii --- .../aslan/core/label/repository/mongodb/label.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index f435cef7..796e081a 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -36,9 +36,11 @@ func NewLabelColl() *LabelColl { name := models.Label{}.TableName() return &LabelColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } + func (c *LabelColl) GetCollectionName() string { return c.coll } + func (c *LabelColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ Keys: bson.D{ @@ -50,6 +52,7 @@ func (c *LabelColl) EnsureIndex(ctx context.Context) error { _, err := c.Indexes().CreateOne(ctx, mod) return err } + func (c *LabelColl) BulkCreate(labels []*models.Label) error { if len(labels) == 0 { return nil @@ -63,6 +66,7 @@ func (c *LabelColl) BulkCreate(labels []*models.Label) error { _, err := c.InsertMany(context.TODO(), ois) return err } + func (c *LabelColl) Delete(id string) error { oid, err := primitive.ObjectIDFromHex(id) if err != nil { @@ -72,6 +76,7 @@ func (c *LabelColl) Delete(id string) error { _, err = c.DeleteOne(context.TODO(), query) return err } + func (c *LabelColl) BulkDelete(ids []string) error { if len(ids) == 0 { return nil -- Gitee From 08bada531a829971aebdc088f66c59642bbfb56f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Feb 2022 11:50:49 +0800 Subject: [PATCH 1543/2647] code format Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 10 +++++----- .../aslan/core/label/repository/mongodb/label.go | 5 ++++- pkg/microservice/aslan/core/project/service/product.go | 1 - pkg/microservice/policy/core/handler/policy.go | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 401d3389..1b56be57 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -40,7 +40,8 @@ func init() { upgradepath.RegisterHandler("1.10.0", "1.9.0", V1100ToV190) } -// V190ToV1100 generate labelBindings for production environment +// V190ToV1100 migrates data `caches` and `pre_build.clean_workspace` fields in `zadig.module_build` and `zadig.module_testing` +// to new fields `cache_enable`, `cache_dir_type` and `cache_user_dir`; generate labelBindings for production environment func V190ToV1100() error { log.Info("Migrating data from 1.9.0 to 1.10.0") @@ -63,7 +64,7 @@ func V190ToV1100() error { func changePolicyCollectionName() error { var res []*models.PolicyMeta - // + ctx := context.Background() cursor, err := newPolicyColl().Find(ctx, bson.M{}) if err != nil { @@ -81,7 +82,7 @@ func changePolicyCollectionName() error { for _, obj := range res { ois = append(ois, obj) } - if _, err = newPolicyMetaColl().InsertMany(context.TODO(), ois); err != nil { + if _, err = newPolicyMetaColl().InsertMany(ctx, ois); err != nil { log.Errorf("Failed to InsertMany policyMetas, err: %s", err) return err } @@ -91,7 +92,6 @@ func changePolicyCollectionName() error { func rollbackChangePolicyCollectionName() error { var res []*models.PolicyMeta - // ctx := context.Background() cursor, err := newPolicyMetaColl().Find(ctx, bson.M{}) if err != nil { @@ -108,7 +108,7 @@ func rollbackChangePolicyCollectionName() error { for _, obj := range res { ois = append(ois, obj) } - if _, err = newPolicyColl().InsertMany(context.TODO(), ois); err != nil { + if _, err = newPolicyColl().InsertMany(ctx, ois); err != nil { log.Errorf("Failed to InsertMany, err: %s", err) return err } diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 796e081a..0305e37e 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -1,9 +1,12 @@ /* Copyright 2022 The KodeRover Authors. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 6ace5f81..1abb6e74 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -477,7 +477,6 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug ProductName: productName, }) }() - // delete policy return nil } diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go index 11f30938..52381557 100644 --- a/pkg/microservice/policy/core/handler/policy.go +++ b/pkg/microservice/policy/core/handler/policy.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 96b63321a9bc3c699a0d2b5bde90fc33cd6b6dd6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Feb 2022 14:34:56 +0800 Subject: [PATCH 1544/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 2 +- .../aslan/core/collaboration/config/consts.go | 16 ++++++++++++++++ .../handler/collaboration_instance.go | 2 +- .../handler/collaboration_mode.go | 2 +- .../aslan/core/collaboration/handler/router.go | 2 +- .../models/collaboration_instance.go | 2 +- .../mongodb/collaboration_instance.go | 1 - .../repository/mongodb/collaboration_mode.go | 6 +++--- .../core/environment/handler/environment.go | 18 ------------------ .../aslan/core/environment/handler/policy.yaml | 2 +- .../aslan/core/label/handler/labels.go | 2 +- .../aslan/core/label/service/label.go | 17 +++++------------ .../aslan/core/project/service/product.go | 2 +- 13 files changed, 32 insertions(+), 42 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 1b56be57..beb366d7 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -118,7 +118,7 @@ func rollbackChangePolicyCollectionName() error { func V1100ToV190() error { if err := rollbackChangePolicyCollectionName(); err != nil { - log.Errorf("Failed to rollbackChangePolicyCollectionName") + log.Errorf("Failed to rollbackChangePolicyCollectionName,err: %s", err) return err } return nil diff --git a/pkg/microservice/aslan/core/collaboration/config/consts.go b/pkg/microservice/aslan/core/collaboration/config/consts.go index 3ef58efd..99fd5015 100644 --- a/pkg/microservice/aslan/core/collaboration/config/consts.go +++ b/pkg/microservice/aslan/core/collaboration/config/consts.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config type CollaborationType string diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index ed022553..b0058397 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go index 632d0efc..4658d6a1 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_mode.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index f83811f1..b07ba0b9 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index f24391f3..92443614 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 72b9d47a..3a2bbc90 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -156,7 +156,6 @@ func (c *CollaborationInstanceColl) DeleteByProject(projectName string) error { query["project_name"] = projectName _, err := c.DeleteMany(context.TODO(), query) return err - } func (c *CollaborationInstanceColl) List(opt *CollaborationInstanceFindOptions) ([]*models.CollaborationInstance, error) { diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index ea823ac2..3f1d87de 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -32,14 +32,14 @@ import ( type CollaborationModeFindOptions struct { ProjectName string - Name string ` ` + Name string Members string IsDeleted bool } type CollaborationModeListOptions struct { Projects []string - Name string ` ` + Name string Members []string IsDeleted bool } @@ -123,7 +123,7 @@ func (c *CollaborationModeColl) Create(userName string, args *models.Collaborati }) if err == nil { - return errors.New("该项目下的协作模式已存在") + return errors.New("the collaborationMode is already exist in this project") } now := time.Now().Unix() args.CreateBy = userName diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 94009a86..f68ce5ed 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -72,24 +72,6 @@ func ListProducts(c *gin.Context) { ctx.Resp = []*service.ProductResp{} return } - //req := &policy.ResourcePermissionReq{ - // ProjectName: projectName, - // Uid: ctx.UserID, - // Resources: envNames, - // ResourceType: "Environment", - //} - //resourcePermission, err := policy.NewDefault().GetResourcePermission(req) - //if err != nil { - // ctx.Err = err - // return - //} - //fmt.Println("%v", resourcePermission) - // - //switch c.Query(setting.Subresource) { - //case setting.IngressSubresource: - // ctx.Resp, ctx.Err = service.GetProductIngress(projectName, ctx.Logger) - // return - //} ctx.Resp, ctx.Err = service.ListProducts(projectName, envNames, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index ac6dbbaa..ee6f397f 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -7,7 +7,7 @@ rules: description: "" rules: - method: GET - endpoint: "/api/aslan/environment/environments/?*/services/down" + endpoint: "/api/aslan/environment/environments/?*/services/?*" - method: GET endpoint: "/api/aslan/environment/environments" - method: GET diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index df6ca54c..b182d706 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -64,7 +64,7 @@ func CreateLabels(c *gin.Context) { ctx.Resp, ctx.Err = service.CreateLabels(labels, ctx.UserName) } -//DeleteLabels can only bulk delete labels which not bind reousrces +// DeleteLabels can only bulk delete labels which do not bind resources except add force param func DeleteLabels(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 3cfc0168..9d37a4d1 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -141,11 +141,7 @@ func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) ( Type: v.ResourceType, } labelString, _ := labelsM[v.LabelID] - if resources, ok := res[labelString]; ok { - res[labelString] = append(resources, resource) - } else { - res[labelString] = []mongodb.Resource{resource} - } + res[labelString] = append(res[labelString], resource) } return &ListResourcesByLabelsResp{ @@ -191,11 +187,8 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg return nil, fmt.Errorf("can not find label %v", labelBinding.LabelID) } - if arr, ok := res[resourceKey]; ok { - res[resourceKey] = append(arr, label) - } else { - res[resourceKey] = []*models.Label{label} - } + res[resourceKey] = append(res[resourceKey], label) + } return &ListLabelsByResourcesResp{ @@ -229,7 +222,7 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err } if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { - logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) + logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, labelBindingIDs) return err } return mongodb.NewLabelColl().BulkDelete(ids) @@ -262,7 +255,7 @@ func DeleteLabelsAndBindingsByProject(projectName string, logger *zap.SugaredLog } if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { - logger.Errorf("NewLabelBindingColl DeleteMany err :%s", err) + logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, labelBindingIDs) return err } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 1abb6e74..9be3d9ad 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -939,7 +939,7 @@ func DeleteCollabrationMode(productName string, userName string, log *zap.Sugare } // delete all collaborationIns if err := mongodb.NewCollaborationInstanceColl().DeleteByProject(productName); err != nil { - log.Errorf("NewCollaborationInstanceColl DeleteByProject err:%s", err) + log.Errorf("fail to DeleteByProject err:%s", err) return err } return nil -- Gitee From a13c429b5cfc3c287667cd3f44240145d160e6d1 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 15 Feb 2022 14:49:03 +0800 Subject: [PATCH 1545/2647] rego response add return rules Signed-off-by: panxunying --- .../core/service/bundle/rego/authz.rego | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index fbcf6022..5e952fc2 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -44,11 +44,15 @@ response = r { role_resource := user_role_allowed_resources policy_resource := user_policy_allowed_resources resource := role_resource | policy_resource + policy_rule := user_matched_policy_rule + role_rule := user_matched_role_rule + rule := policy_rule | role_rule r := { "allowed": true, "headers": { "Resources": json.marshal(resource), "Roles": json.marshal(roles), + "Rules": json.marshal(rule) } } } @@ -174,6 +178,24 @@ project_name_is_match(res) { res.projectName == "" } +user_matched_policy_rule[rule] { + some rule + + allowed_policy_attributive_rules[rule] + + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + +} + +user_matched_role_rule[rule] { + some rule + + allowed_role_attributive_rules[rule] + + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) + +} + user_matched_policy_rule_for_filtering[rule] { some rule @@ -192,6 +214,7 @@ user_matched_role_rule_for_filtering[rule] { not rule.idRegex } + all_attributes_match(attributes, resourceType, resourceID) { res := data.resources[resourceType][_] res.resourceID == resourceID -- Gitee From 6e544e1a7b0eb31394145d5d0749c3a6e6000e73 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Feb 2022 18:42:29 +0800 Subject: [PATCH 1546/2647] add extension stage Signed-off-by: liu deyi --- pkg/microservice/aslan/config/consts.go | 1 + .../repository/models/task/extension.go | 50 ++++ .../core/common/repository/models/workflow.go | 12 + .../aslan/core/common/service/base/convert.go | 8 + .../aslan/core/common/service/task.go | 8 + .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_task.go | 13 + .../core/workflow/service/workflow/sort.go | 1 + .../service/workflow/workflow_task.go | 41 ++- pkg/microservice/warpdrive/config/const.go | 1 + .../service/taskcontroller/task_helper.go | 1 + .../core/service/taskplugin/extension.go | 243 ++++++++++++++++++ .../core/service/taskplugin/utils.go | 8 + .../core/service/types/task/extension.go | 50 ++++ 14 files changed, 435 insertions(+), 3 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/task/extension.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/extension.go create mode 100644 pkg/microservice/warpdrive/core/service/types/task/extension.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index d285e50f..3cce480c 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -139,6 +139,7 @@ const ( TaskResetImage TaskType = "reset_image" TaskDistribute TaskType = "distribute" TaskTrigger TaskType = "trigger" + TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" ) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go new file mode 100644 index 00000000..3b987336 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -0,0 +1,50 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type Extension struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url,omitempty" json:"url,omitempty"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + Body string `bson:"body,omitempty" json:"body,omitempty"` + Method string `bson:"method" json:"method"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +// ToSubTask ... +func (t *Extension) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert extension to interface error: %s", err) + } + return task, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 3a942d06..91105935 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -47,6 +47,7 @@ type Workflow struct { TestStage *TestStage `bson:"test_stage" json:"test_stage"` SecurityStage *SecurityStage `bson:"security_stage" json:"security_stage"` DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` + ExtensionStage *ExtensionStage `bson:"extension_stage" json:"extension_stage"` NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` @@ -262,6 +263,17 @@ type DistributeStage struct { Releases []RepoImage `bson:"releases" json:"releases"` } +type ExtensionStage struct { + Enabled bool `bson:"enabled" json:"enabled"` + URL string `bson:"url" json:"url"` + Path string `bson:"path" json:"path"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Timeout int `bson:"timeout" json:"timeout"` + Headers []*KeyVal `bson:"headers" json:"headers"` + Body string `bson:"body" json:"body"` + Method string `bson:"method" json:"method"` +} + type RepoImage struct { RepoID string `json:"repo_id" bson:"repo_id"` Name string `json:"name" bson:"name" yaml:"name"` diff --git a/pkg/microservice/aslan/core/common/service/base/convert.go b/pkg/microservice/aslan/core/common/service/base/convert.go index 97ec5a08..6a44a7bd 100644 --- a/pkg/microservice/aslan/core/common/service/base/convert.go +++ b/pkg/microservice/aslan/core/common/service/base/convert.go @@ -131,3 +131,11 @@ func ToTriggerTask(sb map[string]interface{}) (*task.Trigger, error) { } return trigger, nil } + +func ToExtensionTask(sb map[string]interface{}) (*task.Extension, error) { + var extension *task.Extension + if err := task.IToi(sb, &extension); err != nil { + return nil, fmt.Errorf("convert interface to extensionTask error: %s", err) + } + return extension, nil +} diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index 010a2a52..66ff73b6 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" @@ -171,3 +172,10 @@ func covertTaskToQueue(t *task.Task) *models.Queue { CreateTime: t.CreateTime, } } + +func GetWorkflowTaskCallback(taskID int64, pipelineName string) (*commonmodels.CallbackRequest, error) { + return mongodb.NewCallbackRequestColl().Find(&mongodb.CallbackFindOption{ + TaskID: taskID, + PipelineName: pipelineName, + }) +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 5c671691..3cfc520c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -129,6 +129,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) workflowtask.POST("/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTask) workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) + workflowtask.GET("/callback/id/:id/name/:name", GetWorkflowTaskCallback) } serviceTask := router.Group("servicetask") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 97f4d597..cc4bcc1e 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -191,3 +191,16 @@ func CancelWorkflowTaskV2(c *gin.Context) { } ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.WorkflowType, ctx.RequestID, ctx.Logger) } + +func GetWorkflowTaskCallback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Resp, ctx.Err = commonservice.GetWorkflowTaskCallback(taskID, c.Param("name")) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go index 15ba83f4..29c67d47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go @@ -49,6 +49,7 @@ var SubtaskOrder = map[config.TaskType]int{ config.TaskType("release_image"): 14, config.TaskType("reset_image"): 15, config.TaskType("trigger"): 16, + config.TaskType("extension"): 17, } type ByStageKind []*commonmodels.Stage diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e9f33926..9f07a719 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -657,6 +657,15 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } } + if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { + extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage) + if err != nil { + log.Errorf("add extension task error: %s", err) + return nil, e.ErrCreateTask.AddErr(err) + } + subTasks = append(subTasks, extensionTask) + } + // 填充subtask之间关联内容 task := &task.Task{ TaskID: nextTaskID, @@ -1396,6 +1405,21 @@ func addSecurityToSubTasks() (map[string]interface{}, error) { return securityTask.ToSubTask() } +func addExtensionToSubTasks(stage *commonmodels.ExtensionStage) (map[string]interface{}, error) { + extensionTask := task.Extension{ + TaskType: config.TaskExtension, + Enabled: true, + URL: stage.URL, + Path: stage.Path, + Headers: stage.Headers, + Body: stage.Body, + Method: stage.Method, + IsCallback: stage.IsCallback, + Timeout: stage.Timeout, + } + return extensionTask.ToSubTask() +} + func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTaskArgs) *commonmodels.TaskArgs { resp := &commonmodels.TaskArgs{PipelineName: workflowArgs.WorkflowName, TaskCreator: workflowArgs.WorkflowTaskCreator} for _, build := range workflowArgs.Target { @@ -2480,7 +2504,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } } - case config.TaskDistribute: + case config.TaskDistribute, config.TaskArtifactPackage: // do nothing case config.TaskTrigger: t, err := base.ToTriggerTask(subTask) @@ -2495,8 +2519,19 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } } - case config.TaskArtifactPackage: - // do nothing + case config.TaskExtension: + t, err := base.ToExtensionTask(subTask) + if err != nil { + log.Error(err) + return err + } + + if t.Enabled { + pt.SubTasks[i], err = t.ToSubTask() + if err != nil { + return err + } + } default: return e.NewErrInvalidTaskType(string(pre.TaskType)) } diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 505c6d28..15bcdbc0 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -35,6 +35,7 @@ const ( TaskResetImage TaskType = "reset_image" TaskDistribute TaskType = "distribute" TaskTrigger TaskType = "trigger" + TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" ) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 64ae7820..9015428b 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -558,6 +558,7 @@ func initTaskPlugins(execHandler *ExecHandler) { config.TaskResetImage: plugins.InitializeDeployTaskPlugin, config.TaskTrigger: plugins.InitializeTriggerTaskPlugin, config.TaskArtifactPackage: plugins.InitializeArtifactPackagePlugin, + config.TaskExtension: plugins.InitializeExtensionTaskPlugin, } for name, pluginInitiator := range pluginConf { registerTaskPlugin(execHandler, name, pluginInitiator) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go new file mode 100644 index 00000000..1f552fdc --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -0,0 +1,243 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/client" + + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/tool/httpclient" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" +) + +const ( + // ExtensionTaskTimeout ... + ExtensionTaskTimeout = 60 * 60 * 1 // 60 minutes +) + +// InitializeExtensionTaskPlugin to initialize build task plugin, and return reference +func InitializeExtensionTaskPlugin(taskType config.TaskType) TaskPlugin { + return &ExtensionTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +// ExtensionTaskPlugin is Plugin, name should be compatible with task type +type ExtensionTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.Extension + Log *zap.SugaredLogger + cancel context.CancelFunc + ack func() + pipelineName string + taskId int64 +} + +func (p *ExtensionTaskPlugin) SetAckFunc(ack func()) { + p.ack = ack +} + +// Init ... +func (p *ExtensionTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl + p.FileName = filename +} + +func (p *ExtensionTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *ExtensionTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *ExtensionTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +// TaskTimeout ... +func (p *ExtensionTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = TriggerTaskTimeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *ExtensionTaskPlugin) SetExtensionStatusCompleted(status config.Status) { + p.Task.TaskStatus = status + p.Task.EndTime = time.Now().Unix() +} + +func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + var ( + err error + ) + defer func() { + if err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + return + } + }() + p.pipelineName = pipelineTask.PipelineName + p.taskId = pipelineTask.TaskID + p.Log.Infof("succeed to create extension task %s", p.JobName) + _, p.cancel = context.WithCancel(context.Background()) + httpClient := httpclient.New( + httpclient.SetHostURL(p.Task.URL), + ) + url := p.Task.Path + for _, header := range p.Task.Headers { + httpclient.SetHeader(header.Key, header.Value) + } + httpclient.SetHeader("X-Zadig-Event", "Workflow") + if p.Task.Method == "POST" { + _, err = httpClient.Post(url, httpclient.SetBody(p.Task.Body)) + } else if p.Task.Method == "GET" { + _, err = httpClient.Get(url) + } + if err != nil { + return + } + + if !p.Task.IsCallback { + p.SetExtensionStatusCompleted(config.StatusPassed) + } +} + +// Wait ... +func (p *ExtensionTaskPlugin) Wait(ctx context.Context) { + timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) + defer p.cancel() + + for { + select { + case <-ctx.Done(): + p.Task.TaskStatus = config.StatusCancelled + return + case <-timeout: + p.Task.TaskStatus = config.StatusTimeout + p.Task.Error = "timeout" + return + default: + time.Sleep(time.Second * 3) + callbackPayloadObj, _ := p.getCallbackObj(p.taskId, p.pipelineName) + if callbackPayloadObj != nil { + if callbackPayloadObj.Status == "success" { + p.Task.TaskStatus = config.StatusPassed + return + } + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = callbackPayloadObj.StatusMessage + return + } + + if p.IsTaskDone() { + return + } + } + } +} + +func (p *ExtensionTaskPlugin) getCallbackObj(taskID int64, pipelineName string) (*task.CallbackPayloadObj, error) { + url := fmt.Sprintf("/api/workflow/workflowtask/callback/id/%d/name/%s", taskID, pipelineName) + httpClient := httpclient.New( + httpclient.SetHostURL(configbase.AslanServiceAddress()), + ) + + CallbackPayloadObj := new(task.CallbackPayloadObj) + _, err := httpClient.Get(url, httpclient.SetResult(&CallbackPayloadObj)) + if err != nil { + return nil, err + } + return CallbackPayloadObj, nil +} + +// Complete ... +func (p *ExtensionTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { +} + +// SetTask ... +func (p *ExtensionTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToExtensionTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *ExtensionTaskPlugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *ExtensionTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *ExtensionTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *ExtensionTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *ExtensionTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *ExtensionTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *ExtensionTaskPlugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index f87ea0be..ff69bd93 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -211,3 +211,11 @@ func ToTriggerTask(sb map[string]interface{}) (*task.Trigger, error) { } return trigger, nil } + +func ToExtensionTask(sb map[string]interface{}) (*task.Extension, error) { + var extension *task.Extension + if err := task.IToi(sb, &extension); err != nil { + return nil, fmt.Errorf("convert interface to extensionTask error: %s", err) + } + return extension, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go new file mode 100644 index 00000000..9dee5376 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +) + +type Extension struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url,omitempty" json:"url,omitempty"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + Body string `bson:"body,omitempty" json:"body,omitempty"` + Method string `bson:"method" json:"method"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +// ToSubTask ... +func (t *Extension) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert extension to interface error: %s", err) + } + return task, nil +} -- Gitee From d4b489df588562495fa19baf40fc028c87124172 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Feb 2022 19:34:09 +0800 Subject: [PATCH 1547/2647] remove body and method field Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 2 -- .../service/workflow/workflow_task.go | 2 -- .../core/service/taskplugin/extension.go | 22 +++++++++++++------ .../core/service/types/task/extension.go | 4 +--- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 3b987336..831fdd73 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -31,8 +31,6 @@ type Extension struct { Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - Body string `bson:"body,omitempty" json:"body,omitempty"` - Method string `bson:"method" json:"method"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 9f07a719..52614635 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1412,8 +1412,6 @@ func addExtensionToSubTasks(stage *commonmodels.ExtensionStage) (map[string]inte URL: stage.URL, Path: stage.Path, Headers: stage.Headers, - Body: stage.Body, - Method: stage.Method, IsCallback: stage.IsCallback, Timeout: stage.Timeout, } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 1f552fdc..08f16923 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -18,6 +18,7 @@ package taskplugin import ( "context" + "encoding/json" "fmt" "time" @@ -103,7 +104,8 @@ func (p *ExtensionTaskPlugin) SetExtensionStatusCompleted(status config.Status) func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { var ( - err error + err error + body []byte ) defer func() { if err != nil { @@ -124,16 +126,22 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, for _, header := range p.Task.Headers { httpclient.SetHeader(header.Key, header.Value) } - httpclient.SetHeader("X-Zadig-Event", "Workflow") - if p.Task.Method == "POST" { - _, err = httpClient.Post(url, httpclient.SetBody(p.Task.Body)) - } else if p.Task.Method == "GET" { - _, err = httpClient.Get(url) + webhookPayload := &task.WebhookPayload{ + EventName: "workflow", + ProjectName: pipelineTask.ProductName, + TaskName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + //TaskOutput: []*task.TaskOutput{taskOutput}, + //TaskEnvs: pipelineTask.TaskArgs.BuildArgs, } + body, err = json.Marshal(webhookPayload) + for _, header := range p.Task.Headers { + httpclient.SetHeader(header.Key, header.Value) + } + _, err = httpClient.Post(url, httpclient.SetHeader("X-Zadig-Event", "Workflow"), httpclient.SetBody(body)) if err != nil { return } - if !p.Task.IsCallback { p.SetExtensionStatusCompleted(config.StatusPassed) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 9dee5376..716bcc00 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -31,8 +31,6 @@ type Extension struct { Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - Body string `bson:"body,omitempty" json:"body,omitempty"` - Method string `bson:"method" json:"method"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` -- Gitee From fa0f380f56e85f71c2009e666d18d6633da07681 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 16 Feb 2022 11:26:44 +0800 Subject: [PATCH 1548/2647] Webhook Trigger YamL logic optimization Signed-off-by: ddh27 --- examples/webhook/trigger_example.yaml | 4 +- .../service/webhook/gitlab_workflow_task.go | 34 +++++++---- .../workflow/service/webhook/trigger_yaml.go | 43 ++++++++++++-- .../core/workflow/service/webhook/utils.go | 56 +++++++++++++++---- 4 files changed, 106 insertions(+), 31 deletions(-) diff --git a/examples/webhook/trigger_example.yaml b/examples/webhook/trigger_example.yaml index 45245b98..a0d4ad2f 100644 --- a/examples/webhook/trigger_example.yaml +++ b/examples/webhook/trigger_example.yaml @@ -10,13 +10,13 @@ build: variables: # zadig 系统中需要先声明出来才能指定,否则这边配置也无效 - name: k1 value: v1 -# 部署策略 +# 部署策略 (该阶段不可缺) deploy: strategy: single # single(更新指定环境)/dynamic(动态选择空闲环境)/base(更新基准环境,需配置 env_recycle_policy 和 base_env) envs_name: - dev - # strategy 值为 base 时才需配置以下变量 + # strategy 值为 base 时才需配置以下变量;该策略仅pull_request时生效 # env_recycle_policy: success # success(成功后销毁)/always(每次销毁)/never(每次保留) # base_env: dev # 测试参数 diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index c353867a..94166fe8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -386,26 +386,22 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("checkTriggerYamlParams yamlPath:%s err:%s", item.YamlPath, err) } - deployed := false - for _, stage := range triggerYaml.Stages { - if stage == "deploy" { - deployed = true - break - } - } + deployed := existStage(StageDeploy, triggerYaml) if svcType == setting.BasicFacilityCVM { deployed = true } - workFlowArgs.Namespace = strings.Join(triggerYaml.Deploy.Envsname, ",") workFlowArgs.WorkflowName = workflow.Name - workFlowArgs.BaseNamespace = triggerYaml.Deploy.BaseNamespace workFlowArgs.ProductTmplName = workflow.ProductTmplName if triggerYaml.CacheSet != nil { workFlowArgs.IgnoreCache = triggerYaml.CacheSet.IgnoreCache workFlowArgs.ResetCache = triggerYaml.CacheSet.ResetCache } - workFlowArgs.EnvRecyclePolicy = triggerYaml.Deploy.EnvRecyclePolicy - workFlowArgs.EnvUpdatePolicy = triggerYaml.Deploy.Strategy + if triggerYaml.Deploy != nil { + workFlowArgs.Namespace = strings.Join(triggerYaml.Deploy.Envsname, ",") + workFlowArgs.BaseNamespace = triggerYaml.Deploy.BaseNamespace + workFlowArgs.EnvRecyclePolicy = string(triggerYaml.Deploy.EnvRecyclePolicy) + workFlowArgs.EnvUpdatePolicy = string(triggerYaml.Deploy.Strategy) + } item.MainRepo.Events = triggerYaml.Rules.Events if triggerYaml.Rules.Strategy != nil { item.AutoCancel = triggerYaml.Rules.Strategy.AutoCancel @@ -433,7 +429,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor TestModuleName: test.Name, Envs: envs, } - if test.Repo.Strategy == "currentRepo" { + if test.Repo.Strategy == TestRepoStrategyCurrentRepo { for _, repo := range moduleTest.Repos { if repo.RepoName == item.MainRepo.RepoName && repo.RepoOwner == item.MainRepo.RepoOwner { repo.Branch = item.MainRepo.Branch @@ -540,9 +536,17 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, switch evt := event.(type) { case *gitlab.PushEvent: pushEvent = evt + if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != pushEvent.Project.PathWithNamespace { + log.Debugf("event not matches repo: %v", item.MainRepo) + continue + } branref = pushEvent.Ref case *gitlab.MergeEvent: mergeEvent = evt + if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != mergeEvent.ObjectAttributes.Target.PathWithNamespace { + log.Debugf("event not matches repo: %v", item.MainRepo) + continue + } if mergeEvent.ObjectAttributes.Source.PathWithNamespace != mergeEvent.ObjectAttributes.Target.PathWithNamespace { branref = mergeEvent.ObjectAttributes.TargetBranch } else { @@ -552,6 +556,10 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, item.MainRepo.Branch = getBranchFromRef(mergeEvent.ObjectAttributes.TargetBranch) case *gitlab.TagEvent: tagEvent = evt + if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != tagEvent.Project.PathWithNamespace { + log.Debugf("event not matches repo: %v", item.MainRepo) + continue + } branref = tagEvent.Ref } @@ -656,6 +664,8 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, if err = CreateEnvAndTaskByPR(args, prID, requestID, log); err != nil { log.Infof("CreateRandomEnv err:%v", err) } + } else { + log.Warnf("It's not a PR event,BaseNamespace:%s", item.WorkflowArgs.BaseNamespace) } } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go index 0658e3bf..47284274 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -20,8 +20,39 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" ) +type Stage string + +const ( + StageBuild Stage = "build" + StageDeploy Stage = "deploy" + StageTest Stage = "test" +) + +type DeployStrategy string + +const ( + DeployStrategySingle DeployStrategy = "single" + DeployStrategyDynamic DeployStrategy = "dynamic" + DeployStrategyBase DeployStrategy = "base" +) + +type EnvRecyclePolicy string + +const ( + EnvRecyclePolicySuccess EnvRecyclePolicy = "success" + EnvRecyclePolicyAlways EnvRecyclePolicy = "always" + EnvRecyclePolicyNever EnvRecyclePolicy = "never" +) + +type TestRepoStrategy string + +const ( + TestRepoStrategyDefault TestRepoStrategy = "default" + TestRepoStrategyCurrentRepo TestRepoStrategy = "currentRepo" +) + type TriggerYaml struct { - Stages []string `yaml:"stages"` + Stages []Stage `yaml:"stages"` Build []*BuildServices `yaml:"build"` Deploy *Deploy `yaml:"deploy"` Test []*Test `yaml:"test"` @@ -55,10 +86,10 @@ Strategy And Need to set EnvRecyclePolicy:success/always/never */ type Deploy struct { - Strategy string `yaml:"strategy"` - Envsname []string `yaml:"envs_name"` - EnvRecyclePolicy string `yaml:"env_recycle_policy"` - BaseNamespace string `yaml:"base_env"` + Strategy DeployStrategy `yaml:"strategy"` + Envsname []string `yaml:"envs_name"` + EnvRecyclePolicy EnvRecyclePolicy `yaml:"env_recycle_policy"` + BaseNamespace string `yaml:"base_env"` } type Test struct { @@ -75,7 +106,7 @@ Strategy Use current changed code information */ type Repo struct { - Strategy string `yaml:"strategy"` + Strategy TestRepoStrategy `yaml:"strategy"` } type Rules struct { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index be0e959d..bdeff191 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -590,43 +590,77 @@ func getServiceTypeByProject(productName string) (string, error) { return projectType, nil } +func existStage(expectStage Stage, triggerYaml *TriggerYaml) bool { + for _, stage := range triggerYaml.Stages { + if stage == expectStage { + return true + } + } + return false +} + func checkTriggerYamlParams(triggerYaml *TriggerYaml) error { //check stages for _, stage := range triggerYaml.Stages { - if stage != "build" && stage != "deploy" && stage != "test" { - return fmt.Errorf("stages must build or deploy or test") + if stage != StageBuild && stage != StageDeploy && stage != StageTest { + return fmt.Errorf("stages must %s or %s or %s", StageBuild, StageDeploy, StageTest) } } + //check build + if len(triggerYaml.Build) == 0 { + return fmt.Errorf("build is empty") + } for _, bd := range triggerYaml.Build { if bd.Name == "" || bd.ServiceModule == "" { return fmt.Errorf("build.name or build.service_module is empty") } } + //check deploy if triggerYaml.Deploy == nil { - return fmt.Errorf("deploy must be exist") + return fmt.Errorf("deploy is empty") } - if triggerYaml.Deploy.BaseNamespace != "" { - if triggerYaml.Deploy.EnvRecyclePolicy != "success" && triggerYaml.Deploy.EnvRecyclePolicy != "always" && triggerYaml.Deploy.EnvRecyclePolicy != "never" { + if len(triggerYaml.Deploy.Envsname) == 0 { + return fmt.Errorf("deploy.envs_name is empty") + } + if triggerYaml.Deploy.Strategy != DeployStrategySingle && triggerYaml.Deploy.Strategy != DeployStrategyBase && triggerYaml.Deploy.Strategy != DeployStrategyDynamic { + return fmt.Errorf("deploy.strategy must %s or %s or %s", DeployStrategySingle, DeployStrategyDynamic, DeployStrategyBase) + } + if triggerYaml.Deploy.Strategy == DeployStrategyBase { + if triggerYaml.Deploy.BaseNamespace == "" { + return fmt.Errorf("deploy.base_env is empty") + } + if triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicySuccess && triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicyAlways && triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicyNever { return fmt.Errorf("deploy.env_recycle_policy must success/always/never") } + } else { + if triggerYaml.Deploy.BaseNamespace != "" { + return fmt.Errorf("deploy.base_env must empty") + } } + //check test - for _, tt := range triggerYaml.Test { - if tt.Repo == nil { - return fmt.Errorf("test.repo.strategy must default/currentRepo") + if existStage(StageTest, triggerYaml) { + if len(triggerYaml.Test) == 0 { + return fmt.Errorf("test is empty") } - if tt.Repo.Strategy != "default" && tt.Repo.Strategy != "currentRepo" { - return fmt.Errorf("test.repo.strategy must default/currentRepo") + for _, tt := range triggerYaml.Test { + if tt.Repo == nil { + return fmt.Errorf("test.repo.strategy must default/currentRepo") + } + if tt.Repo.Strategy != TestRepoStrategyDefault && tt.Repo.Strategy != TestRepoStrategyCurrentRepo { + return fmt.Errorf("test.repo.strategy must default/currentRepo") + } } } + //check rule if triggerYaml.Rules == nil { return fmt.Errorf("rules must exist") } if len(triggerYaml.Rules.Branchs) == 0 { - return fmt.Errorf("rules.baranch must exist") + return fmt.Errorf("rules.branchs must exist") } for _, ev := range triggerYaml.Rules.Events { if ev != "pull_request" && ev != "push" && ev != "tag" { -- Gitee From 60a944216820f9b46e3fc60ca999ded8e2823032 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 16 Feb 2022 11:58:19 +0800 Subject: [PATCH 1549/2647] use errors.New() Signed-off-by: ddh27 --- .../core/workflow/service/webhook/utils.go | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index bdeff191..92e10954 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -609,73 +609,73 @@ func checkTriggerYamlParams(triggerYaml *TriggerYaml) error { //check build if len(triggerYaml.Build) == 0 { - return fmt.Errorf("build is empty") + return errors.New("build is empty") } for _, bd := range triggerYaml.Build { if bd.Name == "" || bd.ServiceModule == "" { - return fmt.Errorf("build.name or build.service_module is empty") + return errors.New("build.name or build.service_module is empty") } } //check deploy if triggerYaml.Deploy == nil { - return fmt.Errorf("deploy is empty") + return errors.New("deploy is empty") } if len(triggerYaml.Deploy.Envsname) == 0 { - return fmt.Errorf("deploy.envs_name is empty") + return errors.New("deploy.envs_name is empty") } if triggerYaml.Deploy.Strategy != DeployStrategySingle && triggerYaml.Deploy.Strategy != DeployStrategyBase && triggerYaml.Deploy.Strategy != DeployStrategyDynamic { return fmt.Errorf("deploy.strategy must %s or %s or %s", DeployStrategySingle, DeployStrategyDynamic, DeployStrategyBase) } if triggerYaml.Deploy.Strategy == DeployStrategyBase { if triggerYaml.Deploy.BaseNamespace == "" { - return fmt.Errorf("deploy.base_env is empty") + return errors.New("deploy.base_env is empty") } if triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicySuccess && triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicyAlways && triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicyNever { - return fmt.Errorf("deploy.env_recycle_policy must success/always/never") + return errors.New("deploy.env_recycle_policy must success/always/never") } } else { if triggerYaml.Deploy.BaseNamespace != "" { - return fmt.Errorf("deploy.base_env must empty") + return errors.New("deploy.base_env must empty") } } //check test if existStage(StageTest, triggerYaml) { if len(triggerYaml.Test) == 0 { - return fmt.Errorf("test is empty") + return errors.New("test is empty") } for _, tt := range triggerYaml.Test { if tt.Repo == nil { - return fmt.Errorf("test.repo.strategy must default/currentRepo") + return errors.New("test.repo.strategy must default/currentRepo") } if tt.Repo.Strategy != TestRepoStrategyDefault && tt.Repo.Strategy != TestRepoStrategyCurrentRepo { - return fmt.Errorf("test.repo.strategy must default/currentRepo") + return errors.New("test.repo.strategy must default/currentRepo") } } } //check rule if triggerYaml.Rules == nil { - return fmt.Errorf("rules must exist") + return errors.New("rules must exist") } if len(triggerYaml.Rules.Branchs) == 0 { - return fmt.Errorf("rules.branchs must exist") + return errors.New("rules.branchs must exist") } for _, ev := range triggerYaml.Rules.Events { if ev != "pull_request" && ev != "push" && ev != "tag" { - return fmt.Errorf("rules.event must be pull_request or push or tag") + return errors.New("rules.event must be pull_request or push or tag") } } if triggerYaml.Rules.MatchFolders == nil { - return fmt.Errorf("rules.match_folders must exist") + return errors.New("rules.match_folders must exist") } for _, mf := range triggerYaml.Rules.MatchFolders.MatchFoldersTree { if mf.Name == "" || mf.ServiceModule == "" { - return fmt.Errorf("match_folders.match_folders_tree.name or match_folders.match_folders_tree.service_module is empty") + return errors.New("match_folders.match_folders_tree.name or match_folders.match_folders_tree.service_module is empty") } if len(mf.FileTree) == 0 { - return fmt.Errorf("match_folders.match_folders_tree.file_tree is empty") + return errors.New("match_folders.match_folders_tree.file_tree is empty") } } -- Gitee From 2f82b27c628148a1489ecebe1a97ee42dd893747 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Feb 2022 13:51:34 +0800 Subject: [PATCH 1550/2647] optimize code Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 38 +++++++++++++------ .../service/workflow/workflow_task.go | 2 + .../core/service/taskplugin/extension.go | 9 ++++- .../core/service/types/task/extension.go | 26 +++++++------ .../core/service/types/task/trigger.go | 3 ++ 5 files changed, 52 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 831fdd73..0ed82c50 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -24,18 +24,20 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url,omitempty" json:"url,omitempty"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image" json:"image"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } // ToSubTask ... @@ -46,3 +48,15 @@ func (t *Extension) ToSubTask() (map[string]interface{}, error) { } return task, nil } + +func (e *Extension) SetImage(image string) { + if image != "" { + e.Image = image + } +} + +func (e *Extension) SetServiceName(serviceName string) { + if serviceName != "" { + e.ServiceName = serviceName + } +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 52614635..2a94fd44 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2525,6 +2525,8 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { + t.SetImage(pt.TaskArgs.Deploy.Image) + t.SetServiceName(pt.ServiceName) pt.SubTasks[i], err = t.ToSubTask() if err != nil { return err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 08f16923..aad4b336 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -126,13 +126,18 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, for _, header := range p.Task.Headers { httpclient.SetHeader(header.Key, header.Value) } + taskOutput := &task.TaskOutput{ + Type: "image", + Value: p.Task.Image, + } webhookPayload := &task.WebhookPayload{ EventName: "workflow", ProjectName: pipelineTask.ProductName, TaskName: pipelineTask.PipelineName, TaskID: pipelineTask.TaskID, - //TaskOutput: []*task.TaskOutput{taskOutput}, - //TaskEnvs: pipelineTask.TaskArgs.BuildArgs, + ServiceName: p.Task.ServiceName, + Creator: pipelineTask.TaskCreator, + TaskOutput: []*task.TaskOutput{taskOutput}, } body, err = json.Marshal(webhookPayload) for _, header := range p.Task.Headers { diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 716bcc00..e1c3bebc 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -24,18 +24,20 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url,omitempty" json:"url,omitempty"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback,omitempty" json:"is_callback,omitempty"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image" json:"image"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } // ToSubTask ... diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 9c82d161..5d709261 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -60,6 +60,9 @@ type WebhookPayload struct { TaskID int64 `json:"task_id"` TaskOutput []*TaskOutput `json:"task_output"` TaskEnvs []*KeyVal `json:"task_envs"` + ServiceName string `json:"service_name"` + Image string `json:"image"` + Creator string `json:"creator"` } type TaskOutput struct { -- Gitee From af53a077e1d80c6fb181a4b350905059611de354 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 16 Feb 2022 14:40:31 +0800 Subject: [PATCH 1551/2647] not trigger Signed-off-by: mouuii --- .../core/common/service/scmnotify/scmnotify.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index 48bc25d3..70c3d85d 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -289,14 +289,14 @@ func (s *Service) UpdateDiffNote(task *task.Task, logger *zap.SugaredLogger) (er } // 更新resolved状态 - resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Resolved: &isAllTaskSucceed, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, resolveOpt) - if err != nil { - logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) - return err - } + //resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ + // Resolved: &isAllTaskSucceed, + //} + //_, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, resolveOpt) + //if err != nil { + // logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) + // return err + //} diffNote.Resolved = isAllTaskSucceed diffNote.Body = body -- Gitee From 396915c0c43de784e03230bab8239bccd3aa457b Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 16 Feb 2022 15:07:30 +0800 Subject: [PATCH 1552/2647] not trigger Signed-off-by: mouuii --- .../core/common/service/notify/client.go | 2 +- .../common/service/scmnotify/scmnotify.go | 147 +++++++++--------- .../aslan/core/common/service/task.go | 4 +- .../service/webhook/gitlab_workflow_task.go | 98 ------------ 4 files changed, 76 insertions(+), 175 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index d8b951a9..a65a138f 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -219,7 +219,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } logger.Infof("workflow get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookComment(task, logger) - _ = c.scmNotifyService.UpdateDiffNote(task, logger) + //_ = c.scmNotifyService.UpdateDiffNote(task, logger) } else if ctx.Type == config.TestType { logger.Infof("test get task #%d notify, status: %s", ctx.TaskID, ctx.Status) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index 70c3d85d..c933084d 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -23,7 +23,6 @@ import ( "os" "strings" - "github.com/xanzy/go-gitlab" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -235,79 +234,79 @@ func (s *Service) UpdateWebhookComment(task *task.Task, logger *zap.SugaredLogge } // UpdateDiffNote 调用gitlab接口更新DiffNote,并更新到数据库 -func (s *Service) UpdateDiffNote(task *task.Task, logger *zap.SugaredLogger) (err error) { - if task.WorkflowArgs.NotificationID == "" { - return - } - - var notification *models.Notification - if notification, err = s.Coll.Find(task.WorkflowArgs.NotificationID); err != nil { - logger.Errorf("can't find notification by id %s %s", task.WorkflowArgs.NotificationID, err) - return err - } - - isAllTaskSucceed := true - for _, nTask := range notification.Tasks { - if nTask.Status != config.TaskStatusFailed && nTask.Status != config.TaskStatusCancelled && - nTask.Status != config.TaskStatusPass && nTask.Status != config.TaskStatusTimeout { - // 存在任务没有执行完,直接返回 - return nil - } - // 任务都执行完了,确认是否有未成功的任务 - if nTask.Status != config.TaskStatusPass { - isAllTaskSucceed = false - break - } - } - - body := "KodeRover CI 检查通过" - if !isAllTaskSucceed { - body = "KodeRover CI 检查失败" - } - - opt := &mongodb.DiffNoteFindOpt{ - CodehostID: notification.CodehostID, - ProjectID: notification.ProjectID, - MergeRequestID: notification.PrID, - } - diffNote, err := s.DiffNoteColl.Find(opt) - if err != nil { - logger.Errorf("can't find notification by id %s %v", task.WorkflowArgs.NotificationID, err) - return err - } - - cli, _ := gitlab.NewOAuthClient(diffNote.Repo.OauthToken, gitlab.WithBaseURL(diffNote.Repo.Address)) - - // 更新note body - noteBodyOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Body: &body, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, noteBodyOpt) - if err != nil { - logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) - return err - } - - // 更新resolved状态 - //resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - // Resolved: &isAllTaskSucceed, - //} - //_, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, resolveOpt) - //if err != nil { - // logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) - // return err - //} - - diffNote.Resolved = isAllTaskSucceed - diffNote.Body = body - err = s.DiffNoteColl.Update(diffNote.ObjectID.Hex(), "", diffNote.Body, diffNote.Resolved) - if err != nil { - logger.Errorf("UpdateDiscussionInfo failed, err: %v", err) - return err - } - - return nil -} +//func (s *Service) UpdateDiffNote(task *task.Task, logger *zap.SugaredLogger) (err error) { +// if task.WorkflowArgs.NotificationID == "" { +// return +// } +// +// var notification *models.Notification +// if notification, err = s.Coll.Find(task.WorkflowArgs.NotificationID); err != nil { +// logger.Errorf("can't find notification by id %s %s", task.WorkflowArgs.NotificationID, err) +// return err +// } +// +// isAllTaskSucceed := true +// for _, nTask := range notification.Tasks { +// if nTask.Status != config.TaskStatusFailed && nTask.Status != config.TaskStatusCancelled && +// nTask.Status != config.TaskStatusPass && nTask.Status != config.TaskStatusTimeout { +// // 存在任务没有执行完,直接返回 +// return nil +// } +// // 任务都执行完了,确认是否有未成功的任务 +// if nTask.Status != config.TaskStatusPass { +// isAllTaskSucceed = false +// break +// } +// } +// +// body := "KodeRover CI 检查通过" +// if !isAllTaskSucceed { +// body = "KodeRover CI 检查失败" +// } +// +// opt := &mongodb.DiffNoteFindOpt{ +// CodehostID: notification.CodehostID, +// ProjectID: notification.ProjectID, +// MergeRequestID: notification.PrID, +// } +// diffNote, err := s.DiffNoteColl.Find(opt) +// if err != nil { +// logger.Errorf("can't find notification by id %s %v", task.WorkflowArgs.NotificationID, err) +// return err +// } +// +// cli, _ := gitlab.NewOAuthClient(diffNote.Repo.OauthToken, gitlab.WithBaseURL(diffNote.Repo.Address)) +// +// // 更新note body +// noteBodyOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ +// Body: &body, +// } +// _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, noteBodyOpt) +// if err != nil { +// logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) +// return err +// } +// +// // 更新resolved状态 +// //resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ +// // Resolved: &isAllTaskSucceed, +// //} +// //_, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, resolveOpt) +// //if err != nil { +// // logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) +// // return err +// //} +// +// diffNote.Resolved = isAllTaskSucceed +// diffNote.Body = body +// err = s.DiffNoteColl.Update(diffNote.ObjectID.Hex(), "", diffNote.Body, diffNote.Resolved) +// if err != nil { +// logger.Errorf("UpdateDiscussionInfo failed, err: %v", err) +// return err +// } +// +// return nil +//} func downloadReport(taskInfo *task.Task, fileName, testName string, logger *zap.SugaredLogger) (*models.TestSuite, error) { var store *s3.S3 diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index 010a2a52..dd99e47b 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -82,7 +82,7 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P if typeString == config.WorkflowType { _ = scmNotifyService.UpdateWebhookComment(t, log) - _ = scmNotifyService.UpdateDiffNote(t, log) + //_ = scmNotifyService.UpdateDiffNote(t, log) } else if typeString == config.TestType { _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { @@ -130,7 +130,7 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P if typeString == config.WorkflowType { _ = scmNotifyService.UpdateWebhookComment(t, log) - _ = scmNotifyService.UpdateDiffNote(t, log) + //_ = scmNotifyService.UpdateDiffNote(t, log) } else if typeString == config.TestType { _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index c353867a..a3ec7506 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -619,9 +619,6 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, notification, _ = scmnotify.NewService().SendInitWebhookComment( item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, log, ) - - // 初始化 gitlab diff_note - InitDiffNote(ev, item.MainRepo, log) } } @@ -678,101 +675,6 @@ func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([ return client.ListChangedFiles(event) } -// InitDiffNote 调用gitlab接口初始化DiffNote,并保存到数据库 -func InitDiffNote(ev *gitlab.MergeEvent, mainRepo *commonmodels.MainHookRepo, log *zap.SugaredLogger) error { - commitID := ev.ObjectAttributes.LastCommit.ID - body := "KodeRover CI 检查中..." - - // 调用gitlab api获取相关数据 - detail, err := systemconfig.New().GetCodeHost(mainRepo.CodehostID) - if err != nil { - log.Errorf("GetCodehostDetail failed, codehost:%d, err:%v", mainRepo.CodehostID, err) - return fmt.Errorf("failed to find codehost %d: %v", mainRepo.CodehostID, err) - } - cli, _ := gitlab.NewOAuthClient(detail.AccessToken, gitlab.WithBaseURL(detail.Address)) - - opt := &commonrepo.DiffNoteFindOpt{ - CodehostID: mainRepo.CodehostID, - ProjectID: mainRepo.RepoOwner + "/" + mainRepo.RepoName, - MergeRequestID: ev.ObjectAttributes.IID, - } - dn, err := commonrepo.NewDiffNoteColl().Find(opt) - if err == nil { - // 该pr的DiffNote已经创建过,且是同一个commit,则不处理 - if dn.CommitID == commitID { - return nil - } - // 不是同一个commit,则重置body和resolved - // 更新note body - noteBodyOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Body: &body, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(dn.Repo.ProjectID, dn.MergeRequestID, dn.DiscussionID, dn.NoteID, noteBodyOpt) - if err != nil { - log.Errorf("UpdateMergeRequestDiscussionNote failed, err:%v", err) - return err - } - - // 更新resolved状态 - resolved := false - resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Resolved: &resolved, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(dn.Repo.ProjectID, dn.MergeRequestID, dn.DiscussionID, dn.NoteID, resolveOpt) - if err != nil { - log.Errorf("UpdateMergeRequestDiscussionNote failed, err:%v", err) - return err - } - - // 更新到数据库 - dn.Resolved = resolved - dn.Body = body - err = commonrepo.NewDiffNoteColl().Update(dn.ObjectID.Hex(), commitID, dn.Body, dn.Resolved) - if err != nil { - log.Errorf("UpdateDiscussionInfo failed, err:%v", err) - return err - } - - return nil - } - - // 不存在则创建 - diffNote := &commonmodels.DiffNote{ - Repo: &commonmodels.RepoInfo{ - CodehostID: mainRepo.CodehostID, - Source: "gitlab", - ProjectID: mainRepo.RepoOwner + "/" + mainRepo.RepoName, - Address: detail.Address, - OauthToken: detail.AccessToken, - }, - MergeRequestID: ev.ObjectAttributes.IID, - CommitID: commitID, - Body: body, - } - - createOpt := &gitlab.CreateMergeRequestDiscussionOptions{ - Body: &diffNote.Body, - } - - discussion, _, err := cli.Discussions.CreateMergeRequestDiscussion(diffNote.Repo.ProjectID, diffNote.MergeRequestID, createOpt) - if err != nil { - log.Errorf("CreateMergeRequestDiscussion failed, err:%v", err) - return err - } - - diffNote.DiscussionID = discussion.ID - if len(discussion.Notes) > 0 { - diffNote.NoteID = discussion.Notes[0].ID - } - err = commonrepo.NewDiffNoteColl().Create(diffNote) - if err != nil { - log.Errorf("DiffNote.Create failed, err:%v", err) - return err - } - - return nil -} - var mutex sync.Mutex // CreateEnvAndTaskByPR 根据pr触发创建环境、使用工作流更新该创建的环境、根据环境删除策略删除环境 -- Gitee From fb5f85f9e9df4d4f37fce17de7cb038531127de5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 16 Feb 2022 15:10:37 +0800 Subject: [PATCH 1553/2647] not trigger Signed-off-by: mouuii --- .../core/common/service/notify/client.go | 1 - .../common/service/scmnotify/scmnotify.go | 75 ------------------- .../aslan/core/common/service/task.go | 2 - 3 files changed, 78 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index a65a138f..8f9e34c5 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -219,7 +219,6 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } logger.Infof("workflow get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookComment(task, logger) - //_ = c.scmNotifyService.UpdateDiffNote(task, logger) } else if ctx.Type == config.TestType { logger.Infof("test get task #%d notify, status: %s", ctx.TaskID, ctx.Status) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index c933084d..a265cfe6 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -233,81 +233,6 @@ func (s *Service) UpdateWebhookComment(task *task.Task, logger *zap.SugaredLogge return nil } -// UpdateDiffNote 调用gitlab接口更新DiffNote,并更新到数据库 -//func (s *Service) UpdateDiffNote(task *task.Task, logger *zap.SugaredLogger) (err error) { -// if task.WorkflowArgs.NotificationID == "" { -// return -// } -// -// var notification *models.Notification -// if notification, err = s.Coll.Find(task.WorkflowArgs.NotificationID); err != nil { -// logger.Errorf("can't find notification by id %s %s", task.WorkflowArgs.NotificationID, err) -// return err -// } -// -// isAllTaskSucceed := true -// for _, nTask := range notification.Tasks { -// if nTask.Status != config.TaskStatusFailed && nTask.Status != config.TaskStatusCancelled && -// nTask.Status != config.TaskStatusPass && nTask.Status != config.TaskStatusTimeout { -// // 存在任务没有执行完,直接返回 -// return nil -// } -// // 任务都执行完了,确认是否有未成功的任务 -// if nTask.Status != config.TaskStatusPass { -// isAllTaskSucceed = false -// break -// } -// } -// -// body := "KodeRover CI 检查通过" -// if !isAllTaskSucceed { -// body = "KodeRover CI 检查失败" -// } -// -// opt := &mongodb.DiffNoteFindOpt{ -// CodehostID: notification.CodehostID, -// ProjectID: notification.ProjectID, -// MergeRequestID: notification.PrID, -// } -// diffNote, err := s.DiffNoteColl.Find(opt) -// if err != nil { -// logger.Errorf("can't find notification by id %s %v", task.WorkflowArgs.NotificationID, err) -// return err -// } -// -// cli, _ := gitlab.NewOAuthClient(diffNote.Repo.OauthToken, gitlab.WithBaseURL(diffNote.Repo.Address)) -// -// // 更新note body -// noteBodyOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ -// Body: &body, -// } -// _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, noteBodyOpt) -// if err != nil { -// logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) -// return err -// } -// -// // 更新resolved状态 -// //resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ -// // Resolved: &isAllTaskSucceed, -// //} -// //_, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, resolveOpt) -// //if err != nil { -// // logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) -// // return err -// //} -// -// diffNote.Resolved = isAllTaskSucceed -// diffNote.Body = body -// err = s.DiffNoteColl.Update(diffNote.ObjectID.Hex(), "", diffNote.Body, diffNote.Resolved) -// if err != nil { -// logger.Errorf("UpdateDiscussionInfo failed, err: %v", err) -// return err -// } -// -// return nil -//} - func downloadReport(taskInfo *task.Task, fileName, testName string, logger *zap.SugaredLogger) (*models.TestSuite, error) { var store *s3.S3 var err error diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index dd99e47b..a6c2fb92 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -82,7 +82,6 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P if typeString == config.WorkflowType { _ = scmNotifyService.UpdateWebhookComment(t, log) - //_ = scmNotifyService.UpdateDiffNote(t, log) } else if typeString == config.TestType { _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { @@ -130,7 +129,6 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P if typeString == config.WorkflowType { _ = scmNotifyService.UpdateWebhookComment(t, log) - //_ = scmNotifyService.UpdateDiffNote(t, log) } else if typeString == config.TestType { _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { -- Gitee From 9a8c832430dd0638eed6f3809a54d4af600b098f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Feb 2022 15:33:55 +0800 Subject: [PATCH 1554/2647] modify extension struct Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 49 ++++++++++--------- .../service/workflow/artifact_task.go | 2 +- .../service/workflow/pipeline_task.go | 2 +- .../workflow/service/workflow/service_task.go | 2 +- .../service/workflow/workflow_task.go | 11 ++--- .../service/workflow/workflow_task_v3.go | 2 +- .../core/service/types/task/extension.go | 35 +++++++------ 7 files changed, 55 insertions(+), 48 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 0ed82c50..aface62b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -24,20 +24,25 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + ServiceInfos []*serviceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type serviceInfo struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` } // ToSubTask ... @@ -49,14 +54,10 @@ func (t *Extension) ToSubTask() (map[string]interface{}, error) { return task, nil } -func (e *Extension) SetImage(image string) { - if image != "" { - e.Image = image - } -} - -func (e *Extension) SetServiceName(serviceName string) { - if serviceName != "" { - e.ServiceName = serviceName - } +func (e *Extension) SetServiceInfo(serviceName, serviceModule, image string) { + e.ServiceInfos = append(e.ServiceInfos, &serviceInfo{ + ServiceName: serviceName, + ServiceModule: serviceModule, + Image: image, + }) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index b4adcf7c..37b1e3e1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -96,7 +96,7 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC task.SubTasks = []map[string]interface{}{subTask} - if err := ensurePipelineTask(task, "", log); err != nil { + if err := ensurePipelineTask(task, "", "", log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) return 0, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 365a06c5..6886fc5f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -242,7 +242,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C pt.ConfigPayload.RepoConfigs[repo.ID.Hex()] = repo } - if err := ensurePipelineTask(pt, pt.TaskArgs.Deploy.Namespace, log); err != nil { + if err := ensurePipelineTask(pt, pt.TaskArgs.Deploy.Namespace, "", log); err != nil { log.Errorf("Service.ensurePipelineTask failed %v %v", args, err) if err, ok := err.(*ContainerNotFound); ok { return nil, e.NewWithExtras( diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index 0c83ecd8..086df700 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -238,7 +238,7 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, "", log); err != nil { + if err := ensurePipelineTask(task, "", "", log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 2a94fd44..490d2d1d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -680,7 +680,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, args.Namespace, log); err != nil { + if err := ensurePipelineTask(task, args.Namespace, target.ServiceName, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%v", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -1067,7 +1067,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, "", log); err != nil { + if err := ensurePipelineTask(task, "", "", log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%s", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -1718,7 +1718,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, args.Namespace, log); err != nil { + if err := ensurePipelineTask(task, args.Namespace, "", log); err != nil { log.Errorf("workflow_task ensurePipelineTask task:[%v] err:%v", task, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -2047,7 +2047,7 @@ func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) set return ips } -func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) error { +func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.SugaredLogger) error { var ( buildEnvs []*commonmodels.KeyVal ) @@ -2525,8 +2525,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { - t.SetImage(pt.TaskArgs.Deploy.Image) - t.SetServiceName(pt.ServiceName) + t.SetServiceInfo(serviceName, pt.ServiceName, pt.TaskArgs.Deploy.Image) pt.SubTasks[i], err = t.ToSubTask() if err != nil { return err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 6431c0d2..9b394fc6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -119,7 +119,7 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str sort.Sort(ByTaskKind(pt.SubTasks)) - if err := ensurePipelineTask(pt, "", log); err != nil { + if err := ensurePipelineTask(pt, "", "", log); err != nil { log.Errorf("Service.ensurePipelineTask failed %v %s", args, err) if err, ok := err.(*ContainerNotFound); ok { return nil, e.NewWithExtras( diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index e1c3bebc..1553e2be 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -24,20 +24,27 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + ServiceName string `bson:"service_name" json:"service_name"` + Image string `bson:"image" json:"image"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + ServiceInfos []*serviceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type serviceInfo struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` } // ToSubTask ... -- Gitee From a123279aff0a4cd10305f3dc788076d621e020a5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Feb 2022 16:14:48 +0800 Subject: [PATCH 1555/2647] modify extension struct Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 48 +++++++++---------- .../core/service/types/task/extension.go | 36 ++++++-------- 2 files changed, 39 insertions(+), 45 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index aface62b..067d844f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -24,25 +24,21 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceInfos []*serviceInfo `bson:"service_infos" json:"service_infos"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` -} - -type serviceInfo struct { - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` - Image string `bson:"image" json:"image"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } // ToSubTask ... @@ -55,9 +51,13 @@ func (t *Extension) ToSubTask() (map[string]interface{}, error) { } func (e *Extension) SetServiceInfo(serviceName, serviceModule, image string) { - e.ServiceInfos = append(e.ServiceInfos, &serviceInfo{ - ServiceName: serviceName, - ServiceModule: serviceModule, - Image: image, - }) + if serviceName != "" { + e.ServiceName = serviceName + } + if serviceModule != "" { + e.ServiceModule = serviceModule + } + if image != "" { + e.Image = image + } } diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 1553e2be..4329d6c2 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -24,27 +24,21 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - ServiceName string `bson:"service_name" json:"service_name"` - Image string `bson:"image" json:"image"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceInfos []*serviceInfo `bson:"service_infos" json:"service_infos"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` -} - -type serviceInfo struct { - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` - Image string `bson:"image" json:"image"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } // ToSubTask ... -- Gitee From eda7622522c4639fe64b5e9ecf3e85bd4a2f43a2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Feb 2022 16:57:22 +0800 Subject: [PATCH 1556/2647] modify webhook payload Signed-off-by: liu deyi --- .../core/service/taskplugin/extension.go | 20 +++++++++---------- .../core/service/types/task/trigger.go | 19 +++++++++--------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index aad4b336..eb7e8855 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -126,18 +126,16 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, for _, header := range p.Task.Headers { httpclient.SetHeader(header.Key, header.Value) } - taskOutput := &task.TaskOutput{ - Type: "image", - Value: p.Task.Image, - } + webhookPayload := &task.WebhookPayload{ - EventName: "workflow", - ProjectName: pipelineTask.ProductName, - TaskName: pipelineTask.PipelineName, - TaskID: pipelineTask.TaskID, - ServiceName: p.Task.ServiceName, - Creator: pipelineTask.TaskCreator, - TaskOutput: []*task.TaskOutput{taskOutput}, + EventName: "workflow", + ProjectName: pipelineTask.ProductName, + TaskName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + ServiceName: p.Task.ServiceName, + ServiceModule: p.Task.ServiceModule, + Image: p.Task.Image, + Creator: pipelineTask.TaskCreator, } body, err = json.Marshal(webhookPayload) for _, header := range p.Task.Headers { diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 5d709261..c5a2f3c2 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,15 +54,16 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - EventName string `json:"event_name"` - ProjectName string `json:"project_name"` - TaskName string `json:"task_name"` - TaskID int64 `json:"task_id"` - TaskOutput []*TaskOutput `json:"task_output"` - TaskEnvs []*KeyVal `json:"task_envs"` - ServiceName string `json:"service_name"` - Image string `json:"image"` - Creator string `json:"creator"` + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output"` + TaskEnvs []*KeyVal `json:"task_envs"` + ServiceName string `json:"service_name"` + ServiceModule string `json:"service_module"` + Image string `json:"image"` + Creator string `json:"creator"` } type TaskOutput struct { -- Gitee From 4563c0b506892ab6c1d97ba536cd46ccde38e3df Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Feb 2022 17:30:29 +0800 Subject: [PATCH 1557/2647] remove unused field Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/workflow.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 91105935..5da9d633 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -270,8 +270,6 @@ type ExtensionStage struct { IsCallback bool `bson:"is_callback" json:"is_callback"` Timeout int `bson:"timeout" json:"timeout"` Headers []*KeyVal `bson:"headers" json:"headers"` - Body string `bson:"body" json:"body"` - Method string `bson:"method" json:"method"` } type RepoImage struct { -- Gitee From bd7a16fb5e347813de005c42b7399c03c124c7bd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Feb 2022 18:31:40 +0800 Subject: [PATCH 1558/2647] optimize code Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 46 ++++++++----------- .../service/workflow/workflow_task.go | 33 ++++--------- .../core/service/types/task/extension.go | 34 ++++++++------ 3 files changed, 48 insertions(+), 65 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 067d844f..7d3fbe7c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -24,21 +24,25 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` - Image string `bson:"image" json:"image"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + ServiceInfos []ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type ServiceInfo struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` } // ToSubTask ... @@ -49,15 +53,3 @@ func (t *Extension) ToSubTask() (map[string]interface{}, error) { } return task, nil } - -func (e *Extension) SetServiceInfo(serviceName, serviceModule, image string) { - if serviceName != "" { - e.ServiceName = serviceName - } - if serviceModule != "" { - e.ServiceModule = serviceModule - } - if image != "" { - e.Image = image - } -} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 490d2d1d..d9e3e91b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -657,15 +657,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } } - if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { - extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage) - if err != nil { - log.Errorf("add extension task error: %s", err) - return nil, e.ErrCreateTask.AddErr(err) - } - subTasks = append(subTasks, extensionTask) - } - // 填充subtask之间关联内容 task := &task.Task{ TaskID: nextTaskID, @@ -703,6 +694,15 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, AddSubtaskToStage(&stages, stask, target.Name) } } + // add extension to stage + if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { + extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage) + if err != nil { + log.Errorf("add extension task error: %s", err) + return nil, e.ErrCreateTask.AddErr(err) + } + AddSubtaskToStage(&stages, extensionTask, "extension") + } testTask := &task.Task{ TaskID: nextTaskID, @@ -1414,6 +1414,7 @@ func addExtensionToSubTasks(stage *commonmodels.ExtensionStage) (map[string]inte Headers: stage.Headers, IsCallback: stage.IsCallback, Timeout: stage.Timeout, + //ServiceInfos: serviceInfo } return extensionTask.ToSubTask() } @@ -2517,20 +2518,6 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug return err } } - case config.TaskExtension: - t, err := base.ToExtensionTask(subTask) - if err != nil { - log.Error(err) - return err - } - - if t.Enabled { - t.SetServiceInfo(serviceName, pt.ServiceName, pt.TaskArgs.Deploy.Image) - pt.SubTasks[i], err = t.ToSubTask() - if err != nil { - return err - } - } default: return e.NewErrInvalidTaskType(string(pre.TaskType)) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 4329d6c2..11be9780 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -24,21 +24,25 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` - Image string `bson:"image" json:"image"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` + ServiceInfos []ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type ServiceInfo struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` } // ToSubTask ... -- Gitee From ba583d4795fd92e672681678b7ad1229df85d086 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 11:16:40 +0800 Subject: [PATCH 1559/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 6 +--- .../aslan/core/collaboration/config/consts.go | 7 ---- .../core/collaboration/handler/router.go | 8 ++--- .../models/collaboration_instance.go | 34 +++++++++---------- .../service/collaboration_instance.go | 5 +-- .../aslan/core/environment/service/bundle.go | 4 +-- .../core/workflow/service/workflow/bundle.go | 4 +-- .../workflow/service/workflow/workflow.go | 2 +- .../policy/core/service/policy.go | 3 +- 9 files changed, 31 insertions(+), 42 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index beb366d7..877735e3 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -27,12 +27,10 @@ import ( internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" - "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/config" - "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "github.com/koderover/zadig/pkg/types" ) func init() { @@ -74,7 +72,6 @@ func changePolicyCollectionName() error { err = cursor.All(ctx, &res) if err != nil { - log.Errorf("Failed to cursor.All, err: %s", err) return err } @@ -100,7 +97,6 @@ func rollbackChangePolicyCollectionName() error { err = cursor.All(ctx, &res) if err != nil { - log.Errorf("Failed to cursor.All, err: %s", err) return err } diff --git a/pkg/microservice/aslan/core/collaboration/config/consts.go b/pkg/microservice/aslan/core/collaboration/config/consts.go index 99fd5015..c51ed2e1 100644 --- a/pkg/microservice/aslan/core/collaboration/config/consts.go +++ b/pkg/microservice/aslan/core/collaboration/config/consts.go @@ -22,10 +22,3 @@ const ( CollaborationShare CollaborationType = "share" CollaborationNew CollaborationType = "new" ) - -type DeployType string - -const ( - K8sDeploy DeployType = "k8s" - HelmDeploy DeployType = "helm" -) diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index b07ba0b9..1495160b 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -19,7 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" - gin2 "github.com/koderover/zadig/pkg/middleware/gin" + middleware "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -29,9 +29,9 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations := router.Group("collaborations") { collaborations.GET("", GetCollaborationMode) - collaborations.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateCollaborationMode) - collaborations.POST("", gin2.UpdateOperationLogStatus, CreateCollaborationMode) - collaborations.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteCollaborationMode) + collaborations.PUT("/:name", middleware.UpdateOperationLogStatus, UpdateCollaborationMode) + collaborations.POST("", middleware.UpdateOperationLogStatus, CreateCollaborationMode) + collaborations.DELETE("/:name", middleware.UpdateOperationLogStatus, DeleteCollaborationMode) collaborations.GET("/new", GetCollaborationNew) collaborations.POST("/sync", SyncCollaborationInstance) } diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index 92443614..d081a81e 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -23,30 +23,30 @@ import ( ) type CollaborationInstance struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - ProjectName string `bson:"project_name" json:"project_name"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - CollaborationName string `bson:"collaboration_name" json:"collaboration_name"` - Revision int64 `bson:"revision" json:"revision"` - UserUID string `bson:"user_uid" json:"user_uid"` - PolicyName string `bson:"policy_name" json:"policy_name"` - Workflows []WorkflowCIItem `json:"workflows" bson:"workflows"` - Products []ProductCIItem `bson:"products" json:"products"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProjectName string `bson:"project_name" json:"project_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + CollaborationName string `bson:"collaboration_name" json:"collaboration_name"` + Revision int64 `bson:"revision" json:"revision"` + UserUID string `bson:"user_uid" json:"user_uid"` + PolicyName string `bson:"policy_name" json:"policy_name"` + Workflows []WorkflowCIItem `bson:"workflows" json:"workflows"` + Products []ProductCIItem `bson:"products" json:"products"` } type ProductCIItem struct { - Name string `bson:"name" json:"name"` - BaseName string `bson:"base_name" json:"base_name"` + Name string `bson:"name" json:"name"` + BaseName string `bson:"base_name" json:"base_name"` CollaborationType config.CollaborationType `bson:"collaboration_type" json:"collaboration_type"` - RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` - Verbs []string `bson:"verbs" json:"verbs"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` + Verbs []string `bson:"verbs" json:"verbs"` } type WorkflowCIItem struct { - Name string `bson:"name" json:"name"` - BaseName string `bson:"base_name" json:"base_name"` + Name string `bson:"name" json:"name"` + BaseName string `bson:"base_name" json:"base_name"` CollaborationType config.CollaborationType `bson:"collaboration_type" json:"collaboration_type"` - Verbs []string `bson:"verbs" json:"verbs"` + Verbs []string `bson:"verbs" json:"verbs"` } func (CollaborationInstance) TableName() string { diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index d838d180..d97dc45e 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -19,6 +19,7 @@ import ( mongodb2 "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" ) @@ -874,7 +875,7 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol var helmProductArgs []service2.HelmProductItem for _, product := range newResp.Product { if productArg, ok := productMap[product.BaseName]; ok { - if productArg.DeployType == string(config.HelmDeploy) { + if productArg.DeployType == string(setting.HelmDeployType) { helmProductArgs = append(helmProductArgs, service2.HelmProductItem{ OldName: product.BaseName, NewName: product.Name, @@ -883,7 +884,7 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol ChartValues: product.ChartValues, }) } - if productArg.DeployType == string(config.K8sDeploy) { + if productArg.DeployType == string(setting.K8SDeployType) { yamlProductItems = append(yamlProductItems, service2.YamlProductItem{ OldName: product.BaseName, NewName: product.Name, diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index fe992020..2e6b905c 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -19,7 +19,7 @@ package service import ( "go.uber.org/zap" - config2 "github.com/koderover/zadig/pkg/config" + commonConfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -58,7 +58,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { } for _, env := range envs { - resourceKey := config2.BuildResourceKey(string(config.ResourceTypeProduct), env.ProductName, env.EnvName) + resourceKey := commonConfig.BuildResourceKey(string(config.ResourceTypeProduct), env.ProductName, env.EnvName) resourceSpec := &resourceSpec{ ResourceID: env.EnvName, ProjectName: env.ProductName, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 130dcdad..7ac2536d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -19,7 +19,7 @@ package workflow import ( "go.uber.org/zap" - config2 "github.com/koderover/zadig/pkg/config" + commonConfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" @@ -57,7 +57,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { } for _, workflow := range workflows { - resourceKey := config2.BuildResourceKey(string(config.ResourceTypeWorkflow), workflow.ProductTmplName, workflow.Name) + resourceKey := commonConfig.BuildResourceKey(string(config.ResourceTypeWorkflow), workflow.ProductTmplName, workflow.Name) resourceSpec := &resourceSpec{ ResourceID: workflow.Name, ProjectName: workflow.ProductTmplName, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 88d16ddf..430d8b7a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -687,7 +687,7 @@ func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.Sugar ProjectName: item.ProjectName, Name: item.Old, }) - workflowMap[item.ProjectName+"~"+item.Old] = item + workflowMap[item.ProjectName+"-"+item.Old] = item } oldWorkflows, err := commonrepo.NewWorkflowColl().ListByWorkflows(commonrepo.ListWorkflowOpt{ Workflows: workflows, diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 78fbb709..2f0cb5a3 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -23,10 +23,9 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" - "github.com/koderover/zadig/pkg/shared/client/label" - "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/shared/client/label" ) type Policy struct { -- Gitee From 6a91f374fed5ac7e79b31bdcb5ccbe9f65c46d0e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 11:56:29 +0800 Subject: [PATCH 1560/2647] optimize code Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 2 +- .../common/repository/models/task/model.go | 6 + .../service/workflow/artifact_task.go | 4 +- .../service/workflow/pipeline_task.go | 5 +- .../workflow/service/workflow/service_task.go | 13 +- .../service/workflow/workflow_task.go | 246 ++++++++++-------- .../service/workflow/workflow_task_v3.go | 4 +- .../core/service/types/task/extension.go | 2 +- 8 files changed, 156 insertions(+), 126 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 7d3fbe7c..238b6836 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -31,7 +31,7 @@ type Extension struct { Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceInfos []ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` + ServiceInfos []*ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 64631bdb..b6ebb2fc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -163,3 +163,9 @@ type Error struct { Type string `bson:"type" json:"type" xml:"type,attr"` Text string `bson:"text" json:"text" xml:",chardata"` } + +type TaskOpt struct { + Task *Task `json:"task"` + EnvName string `json:"env_name"` + ServiceInfos []*ServiceInfo `json:"service_infos"` +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index 37b1e3e1..a957ee37 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -96,7 +96,9 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC task.SubTasks = []map[string]interface{}{subTask} - if err := ensurePipelineTask(task, "", "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + }, log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) return 0, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 6886fc5f..d4ce0a8e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -242,7 +242,10 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C pt.ConfigPayload.RepoConfigs[repo.ID.Hex()] = repo } - if err := ensurePipelineTask(pt, pt.TaskArgs.Deploy.Namespace, "", log); err != nil { + if err := ensurePipelineTask(&task.TaskOpt{ + Task: pt, + EnvName: pt.TaskArgs.Deploy.Namespace, + }, log); err != nil { log.Errorf("Service.ensurePipelineTask failed %v %v", args, err) if err, ok := err.(*ContainerNotFound); ok { return nil, e.NewWithExtras( diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index 086df700..baffa417 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -21,19 +21,18 @@ import ( "fmt" "sort" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" - "github.com/koderover/zadig/pkg/types" - "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) const SplitSymbol = "&" @@ -226,7 +225,7 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge return nil, e.ErrCreateTask.AddErr(err) } - task := &task.Task{ + task := &taskmodels.Task{ Type: config.ServiceType, ProductName: args.ProductName, TaskCreator: args.ServiceTaskCreator, @@ -238,7 +237,9 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, "", "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + }, log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index d9e3e91b..06a85a30 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -34,7 +34,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -550,6 +550,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } stages := make([]*commonmodels.Stage, 0) + serviceInfos := make([]*taskmodels.ServiceInfo, 0) for _, target := range args.Target { var subTasks []map[string]interface{} var err error @@ -658,7 +659,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } // 填充subtask之间关联内容 - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: args.WorkflowName, TaskCreator: taskCreator, @@ -671,7 +672,11 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, args.Namespace, target.ServiceName, log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + EnvName: args.Namespace, + ServiceInfos: serviceInfos, + }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%v", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -696,7 +701,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } // add extension to stage if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { - extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage) + extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage, serviceInfos) if err != nil { log.Errorf("add extension task error: %s", err) return nil, e.ErrCreateTask.AddErr(err) @@ -704,7 +709,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, AddSubtaskToStage(&stages, extensionTask, "extension") } - testTask := &task.Task{ + testTask := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: args.WorkflowName, ProductName: args.ProductTmplName, @@ -741,7 +746,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, MergeRequestID: args.MergeRequestID, CommitID: args.CommitID, } - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, Type: config.WorkflowType, ProductName: workflow.ProductTmplName, @@ -1054,7 +1059,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. subTasks = append(subTasks, distributeTasks...) // Fill in the associated content between subtasks - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: workflow.Name, TaskCreator: setting.RequestModeOpenAPI, @@ -1067,7 +1072,9 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, "", "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%s", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -1093,7 +1100,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } sort.Sort(ByStageKind(stages)) - task := &task.Task{ + task := &taskmodels.Task{ WorkflowArgs: args, TaskID: nextTaskID, Type: config.WorkflowType, @@ -1255,7 +1262,7 @@ func GetS3Storage(id string, logger *zap.SugaredLogger) (*commonmodels.S3Storage func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Product, timeout int) (map[string]interface{}, error) { var ( resp map[string]interface{} - deployTask = task.Deploy{ + deployTask = taskmodels.Deploy{ TaskType: config.TaskDeploy, Enabled: true, Namespace: prodEnv.Namespace, @@ -1297,7 +1304,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.Product) (map[string]interface{}, error) { switch env.Type { case setting.K8SDeployType: - deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} + deployTask := taskmodels.Deploy{TaskType: config.TaskResetImage, Enabled: true} deployTask.Namespace = prodEnv.Namespace deployTask.ProductName = prodEnv.ProductName deployTask.SkipWaiting = true @@ -1312,7 +1319,7 @@ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.P deployTask.ContainerName = envList[1] return deployTask.ToSubTask() case setting.HelmDeployType: - deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} + deployTask := taskmodels.Deploy{TaskType: config.TaskResetImage, Enabled: true} deployTask.Namespace = prodEnv.Namespace deployTask.ProductName = prodEnv.ProductName deployTask.SkipWaiting = true @@ -1333,7 +1340,7 @@ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.P } func artifactToSubTasks(name, image string) (map[string]interface{}, error) { - artifactTask := task.Artifact{TaskType: config.TaskArtifact, Enabled: true} + artifactTask := taskmodels.Artifact{TaskType: config.TaskArtifact, Enabled: true} artifactTask.Name = name artifactTask.Image = image @@ -1344,7 +1351,7 @@ func formatDistributeSubtasks(releaseImages []commonmodels.RepoImage, imageRepo, var resp []map[string]interface{} if distribute.ImageDistribute { - t := task.ReleaseImage{ + t := taskmodels.ReleaseImage{ TaskType: config.TaskReleaseImage, Enabled: true, ImageRepo: imageRepo, @@ -1357,7 +1364,7 @@ func formatDistributeSubtasks(releaseImages []commonmodels.RepoImage, imageRepo, resp = append(resp, subtask) } if distribute.QstackDistribute && destStorageURL != "" { - task := task.DistributeToS3{ + task := taskmodels.DistributeToS3{ TaskType: config.TaskDistributeToS3, Enabled: true, DestStorageURL: destStorageURL, @@ -1389,7 +1396,7 @@ func AddJiraSubTask(moduleName, target, serviceName, productName string, log *za if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } - jira := &task.Jira{ + jira := &taskmodels.Jira{ TaskType: config.TaskJira, Enabled: true, } @@ -1401,20 +1408,20 @@ func AddJiraSubTask(moduleName, target, serviceName, productName string, log *za } func addSecurityToSubTasks() (map[string]interface{}, error) { - securityTask := task.Security{TaskType: config.TaskSecurity, Enabled: true} + securityTask := taskmodels.Security{TaskType: config.TaskSecurity, Enabled: true} return securityTask.ToSubTask() } -func addExtensionToSubTasks(stage *commonmodels.ExtensionStage) (map[string]interface{}, error) { - extensionTask := task.Extension{ - TaskType: config.TaskExtension, - Enabled: true, - URL: stage.URL, - Path: stage.Path, - Headers: stage.Headers, - IsCallback: stage.IsCallback, - Timeout: stage.Timeout, - //ServiceInfos: serviceInfo +func addExtensionToSubTasks(stage *commonmodels.ExtensionStage, serviceInfos []*taskmodels.ServiceInfo) (map[string]interface{}, error) { + extensionTask := taskmodels.Extension{ + TaskType: config.TaskExtension, + Enabled: true, + URL: stage.URL, + Path: stage.Path, + Headers: stage.Headers, + IsCallback: stage.IsCallback, + Timeout: stage.Timeout, + ServiceInfos: serviceInfos, } return extensionTask.ToSubTask() } @@ -1432,8 +1439,8 @@ func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTa } // TODO 和validation中转化testsubtask合并为一个方法 -func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log *zap.SugaredLogger) ([]*task.Testing, error) { - var resp []*task.Testing +func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, log *zap.SugaredLogger) ([]*taskmodels.Testing, error) { + var resp []*taskmodels.Testing var servicesArray []string var services string @@ -1469,7 +1476,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * return resp, err } - testTask := &task.Testing{ + testTask := &taskmodels.Testing{ TaskType: config.TaskTestingV2, Enabled: true, TestName: "test", @@ -1478,7 +1485,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * testTask.TestModuleName = testModule.Name testTask.JobCtx.TestType = testModule.TestType testTask.JobCtx.Builds = testModule.Repos - testTask.JobCtx.BuildSteps = append(testTask.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: testModule.Scripts}) + testTask.JobCtx.BuildSteps = append(testTask.JobCtx.BuildSteps, &taskmodels.BuildStep{BuildType: "shell", Scripts: testModule.Scripts}) testTask.JobCtx.ArtifactPaths = testModule.ArtifactPaths testTask.JobCtx.TestThreshold = testModule.Threshold @@ -1536,7 +1543,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * return resp, err } - // Iterate test jobctx builds, and replace it if params specified from task. + // Iterate test jobctx builds, and replace it if params specified from taskmodels. // 外部触发的pipeline if testCreator == setting.WebhookTaskCreator || testCreator == setting.CronTaskCreator { _ = SetTriggerBuilds(testTask.JobCtx.Builds, testArg.Builds, log) @@ -1705,7 +1712,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } // 填充subtask之间关联内容 - task := &task.Task{ + task := &taskmodels.Task{ ProductName: args.ProductTmplName, PipelineName: args.WorkflowName, TaskID: nextTaskID, @@ -1719,7 +1726,10 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, args.Namespace, "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + EnvName: args.Namespace, + }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask task:[%v] err:%v", task, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -1741,7 +1751,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } } - testTask := &task.Task{ + testTask := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: args.WorkflowName, ProductName: args.ProductTmplName, @@ -1775,7 +1785,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator MergeRequestID: args.MergeRequestID, CommitID: args.CommitID, } - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, Type: config.WorkflowType, ProductName: workflow.ProductTmplName, @@ -1856,13 +1866,13 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } for _, module := range modules { - build := &task.Build{ + build := &taskmodels.Build{ TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, ServiceName: args.Target, Service: args.ServiceName, - JobCtx: task.JobCtx{}, + JobCtx: taskmodels.JobCtx{}, ImageID: module.PreBuild.ImageID, BuildOS: module.PreBuild.BuildOS, ImageFrom: module.PreBuild.ImageFrom, @@ -1943,9 +1953,9 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Builds = make([]*types.Repository, 0) } - build.JobCtx.BuildSteps = []*task.BuildStep{} + build.JobCtx.BuildSteps = []*taskmodels.BuildStep{} if module.Scripts != "" { - build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: module.Scripts}) + build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &taskmodels.BuildStep{BuildType: "shell", Scripts: module.Scripts}) } if module.PMDeployScripts != "" && build.ServiceType == setting.PMDeployType { @@ -1953,7 +1963,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } if len(module.SSHs) > 0 && build.ServiceType == setting.PMDeployType { - privateKeys := make([]*task.SSH, 0) + privateKeys := make([]*taskmodels.SSH, 0) for _, sshID := range module.SSHs { //私钥信息可能被更新,而构建中存储的信息是旧的,需要根据id获取最新的私钥信息 latestKeyInfo, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ID: sshID}) @@ -1961,7 +1971,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL log.Errorf("PrivateKey.Find failed, id:%s, err:%s", sshID, err) continue } - ssh := new(task.SSH) + ssh := new(taskmodels.SSH) ssh.Name = latestKeyInfo.Name ssh.UserName = latestKeyInfo.UserName ssh.IP = latestKeyInfo.IP @@ -2001,7 +2011,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL dockerTemplateContent = dockerfileDetail.Content } } - build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ + build.JobCtx.DockerBuildCtx = &taskmodels.DockerBuildCtx{ Source: module.PostBuild.DockerBuild.Source, WorkDir: module.PostBuild.DockerBuild.WorkDir, DockerFile: module.PostBuild.DockerBuild.DockerFile, @@ -2011,7 +2021,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } if module.PostBuild != nil && module.PostBuild.FileArchive != nil { - build.JobCtx.FileArchiveCtx = &task.FileArchiveCtx{ + build.JobCtx.FileArchiveCtx = &taskmodels.FileArchiveCtx{ FileLocation: module.PostBuild.FileArchive.FileLocation, } } @@ -2023,7 +2033,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Caches = module.Caches if args.FileName != "" { - build.ArtifactInfo = &task.ArtifactInfo{ + build.ArtifactInfo = &taskmodels.ArtifactInfo{ URL: args.URL, WorkflowName: args.WorkflowName, TaskID: args.TaskID, @@ -2048,20 +2058,20 @@ func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) set return ips } -func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.SugaredLogger) error { +func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) error { var ( buildEnvs []*commonmodels.KeyVal ) // 验证 Subtask payload - err := validateSubTaskSetting(pt.PipelineName, pt.SubTasks) + err := validateSubTaskSetting(taskOpt.Task.PipelineName, taskOpt.Task.SubTasks) if err != nil { log.Errorf("Validate subtask setting failed: %+v", err) return err } //设置执行任务时参数 - for i, subTask := range pt.SubTasks { + for i, subTask := range taskOpt.Task.SubTasks { pre, err := base.ToPreview(subTask) if err != nil { @@ -2092,7 +2102,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug //} // 设置Pipeline对应的服务名称 if t.ServiceName != "" { - pt.ServiceName = t.ServiceName + taskOpt.Task.ServiceName = t.ServiceName } // 设置 build 安装脚本 @@ -2103,16 +2113,16 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug } // 外部触发的pipeline - if pt.TaskCreator == setting.WebhookTaskCreator || pt.TaskCreator == setting.CronTaskCreator { - SetTriggerBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || taskOpt.Task.TaskCreator == setting.CronTaskCreator { + SetTriggerBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Builds, log) } else { - setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) + setManunalBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Builds, log) } - opt := &commonrepo.ProductFindOptions{EnvName: envName, Name: pt.ProductName} + opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("can't find product by envName:%s error msg: %v", envName, err) + log.Errorf("can't find product by envName:%s error msg: %v", taskOpt.EnvName, err) return e.ErrFindRegistry.AddDesc(err.Error()) } @@ -2138,25 +2148,31 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug } } - t.JobCtx.Image = GetImage(reg, releaseCandidate(t, pt.TaskID, pt.ProductName, envName, "image")) - pt.TaskArgs.Deploy.Image = t.JobCtx.Image + t.JobCtx.Image = GetImage(reg, releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "image")) + taskOpt.Task.TaskArgs.Deploy.Image = t.JobCtx.Image + + taskOpt.ServiceInfos = append(taskOpt.ServiceInfos, &taskmodels.ServiceInfo{ + ServiceName: t.Service, + ServiceModule: t.ServiceName, + Image: t.JobCtx.Image, + }) - if pt.ConfigPayload != nil { - pt.ConfigPayload.Registry.Addr = reg.RegAddr - pt.ConfigPayload.Registry.AccessKey = reg.AccessKey - pt.ConfigPayload.Registry.SecretKey = reg.SecretKey - pt.ConfigPayload.Registry.Namespace = reg.Namespace + if taskOpt.Task.ConfigPayload != nil { + taskOpt.Task.ConfigPayload.Registry.Addr = reg.RegAddr + taskOpt.Task.ConfigPayload.Registry.AccessKey = reg.AccessKey + taskOpt.Task.ConfigPayload.Registry.SecretKey = reg.SecretKey + taskOpt.Task.ConfigPayload.Registry.Namespace = reg.Namespace } // 二进制文件名称 // 编译任务使用 t.JobCtx.PackageFile // 注意: 其他任务从 pt.TaskArgs.Deploy.PackageFile 获取, 必须要有编译任务 - t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, pt.TaskID, pt.ProductName, envName, "tar")) - pt.TaskArgs.Deploy.PackageFile = t.JobCtx.PackageFile + t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "tar")) + taskOpt.Task.TaskArgs.Deploy.PackageFile = t.JobCtx.PackageFile // 注入编译模块中用户定义环境变量 // 注意: 需要在pt.TaskArgs.Deploy设置完之后再设置环境变量 - t.JobCtx.EnvVars = append(t.JobCtx.EnvVars, prepareTaskEnvs(pt, log)...) + t.JobCtx.EnvVars = append(t.JobCtx.EnvVars, prepareTaskEnvs(taskOpt.Task, log)...) // 如果其他模块需要使用编译模块的环境变量进行渲染,需要设置buildEnvs buildEnvs = t.JobCtx.EnvVars @@ -2185,15 +2201,15 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug registryRepo = reg.RegAddr } - t.DockerBuildStatus = &task.DockerBuildStatus{ + t.DockerBuildStatus = &taskmodels.DockerBuildStatus{ ImageName: t.JobCtx.Image, RegistryRepo: registryRepo, } - t.UTStatus = &task.UTStatus{} - t.StaticCheckStatus = &task.StaticCheckStatus{} - t.BuildStatus = &task.BuildStatus{} + t.UTStatus = &taskmodels.UTStatus{} + t.StaticCheckStatus = &taskmodels.StaticCheckStatus{} + t.BuildStatus = &taskmodels.BuildStatus{} - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err @@ -2222,9 +2238,9 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug return &ImageIllegal{} } - pt.TaskArgs.Deploy.Image = image + taskOpt.Task.TaskArgs.Deploy.Image = image t.Image = image - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2236,19 +2252,19 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug return err } if t.Enabled { - if pt.TaskArgs == nil { - pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorkflowTaskCreator} + if taskOpt.Task.TaskArgs == nil { + taskOpt.Task.TaskArgs = &commonmodels.TaskArgs{PipelineName: taskOpt.Task.WorkflowArgs.WorkflowName, TaskCreator: taskOpt.Task.WorkflowArgs.WorkflowTaskCreator} } - registry := pt.ConfigPayload.RepoConfigs[pt.WorkflowArgs.RegistryID] + registry := taskOpt.Task.ConfigPayload.RepoConfigs[taskOpt.Task.WorkflowArgs.RegistryID] if registry.RegProvider == config.RegistryTypeAWS { t.Image = fmt.Sprintf("%s/%s", util.TrimURLScheme(registry.RegAddr), t.Image) } else { t.Image = fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, t.Image) } - pt.TaskArgs.Deploy.Image = t.Image - t.RegistryID = pt.WorkflowArgs.RegistryID + taskOpt.Task.TaskArgs.Deploy.Image = t.Image + t.RegistryID = taskOpt.Task.WorkflowArgs.RegistryID - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2260,7 +2276,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug return err } if t.Enabled { - t.Image = pt.TaskArgs.Deploy.Image + t.Image = taskOpt.Task.TaskArgs.Deploy.Image // 使用环境变量KEY渲染 docker build的各个参数 for _, env := range buildEnvs { @@ -2271,12 +2287,12 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug } } - err = SetCandidateRegistry(pt.ConfigPayload, log) + err = SetCandidateRegistry(taskOpt.Task.ConfigPayload, log) if err != nil { return err } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2296,26 +2312,26 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug // Iterate test jobctx builds, and replace it if params specified from task. // 外部触发的pipeline - if pt.TaskCreator == setting.WebhookTaskCreator || pt.TaskCreator == setting.CronTaskCreator { - SetTriggerBuilds(t.JobCtx.Builds, pt.TaskArgs.Test.Builds, log) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || taskOpt.Task.TaskCreator == setting.CronTaskCreator { + SetTriggerBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Test.Builds, log) } else { - setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Test.Builds, log) + setManunalBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Test.Builds, log) } - err = SetCandidateRegistry(pt.ConfigPayload, log) + err = SetCandidateRegistry(taskOpt.Task.ConfigPayload, log) if err != nil { return err } //use log path - if pt.ServiceName == "" { - pt.ServiceName = t.TestName + if taskOpt.Task.ServiceName == "" { + taskOpt.Task.ServiceName = t.TestName } // 设置敏感信息 t.JobCtx.EnvVars = append(t.JobCtx.EnvVars, envs...) - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2328,7 +2344,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug } if t.Enabled { - t.SetNamespace(pt.TaskArgs.Deploy.Namespace) + t.SetNamespace(taskOpt.Task.TaskArgs.Deploy.Namespace) image, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) if err != nil { log.Error(err) @@ -2337,7 +2353,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug t.SetImage(image) - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2352,8 +2368,8 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug if t.Enabled { // 从创建任务payload设置容器部署 - t.SetImage(pt.TaskArgs.Deploy.Image) - t.SetNamespace(pt.TaskArgs.Deploy.Namespace) + t.SetImage(taskOpt.Task.TaskArgs.Deploy.Image) + t.SetNamespace(taskOpt.Task.TaskArgs.Deploy.Namespace) _, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) if err != nil { @@ -2363,16 +2379,16 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug // Task creator can be webhook trigger or cronjob trigger or validated user // Validated user includes both that user is granted write permission or user is the owner of this product - if pt.TaskCreator == setting.WebhookTaskCreator || - pt.TaskCreator == setting.CronTaskCreator || - IsProductAuthed(pt.TaskCreator, t.Namespace, pt.ProductName, config.ProductWritePermission, log) { - log.Infof("Validating permission passed. product:%s, owner:%s, task executed by: %s", pt.ProductName, t.Namespace, pt.TaskCreator) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || + taskOpt.Task.TaskCreator == setting.CronTaskCreator || + IsProductAuthed(taskOpt.Task.TaskCreator, t.Namespace, taskOpt.Task.ProductName, config.ProductWritePermission, log) { + log.Infof("Validating permission passed. product:%s, owner:%s, task executed by: %s", taskOpt.Task.ProductName, t.Namespace, taskOpt.Task.TaskCreator) } else { - log.Errorf("permission denied. product:%s, owner:%s, task executed by: %s", pt.ProductName, t.Namespace, pt.TaskCreator) + log.Errorf("permission denied. product:%s, owner:%s, task executed by: %s", taskOpt.Task.ProductName, t.Namespace, taskOpt.Task.TaskCreator) return errors.New(e.ProductAccessDeniedErrMsg) } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2385,15 +2401,15 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug return err } if task.Enabled { - task.SetPackageFile(pt.TaskArgs.Deploy.PackageFile) + task.SetPackageFile(taskOpt.Task.TaskArgs.Deploy.PackageFile) - if pt.TeamName == "" { - task.ProductName = pt.ProductName + if taskOpt.Task.TeamName == "" { + task.ProductName = taskOpt.Task.ProductName } else { - task.ProductName = pt.TeamName + task.ProductName = taskOpt.Task.TeamName } - task.ServiceName = pt.ServiceName + task.ServiceName = taskOpt.Task.ServiceName if task.DestStorageURL == "" { var storage *commonmodels.S3Storage @@ -2415,7 +2431,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug return err } } - pt.SubTasks[i], err = task.ToSubTask() + taskOpt.Task.SubTasks[i], err = task.ToSubTask() if err != nil { return err } @@ -2430,11 +2446,11 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug if t.Enabled { // 从创建任务payload设置线上镜像分发任务 - t.SetImage(pt.TaskArgs.Deploy.Image) + t.SetImage(taskOpt.Task.TaskArgs.Deploy.Image) // 兼容老的任务配置 if len(t.Releases) == 0 { found := false - for id, v := range pt.ConfigPayload.RepoConfigs { + for id, v := range taskOpt.Task.ConfigPayload.RepoConfigs { if v.Namespace == t.ImageRepo { t.Releases = append(t.Releases, commonmodels.RepoImage{RepoID: id}) found = true @@ -2448,8 +2464,8 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug var repos []commonmodels.RepoImage for _, repoImage := range t.Releases { - if v, ok := pt.ConfigPayload.RepoConfigs[repoImage.RepoID]; ok { - repoImage.Name = util.ReplaceRepo(pt.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) + if v, ok := taskOpt.Task.ConfigPayload.RepoConfigs[repoImage.RepoID]; ok { + repoImage.Name = util.ReplaceRepo(taskOpt.Task.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) repoImage.Host = util.TrimURLScheme(v.RegAddr) repoImage.Namespace = v.Namespace repos = append(repos, repoImage) @@ -2463,7 +2479,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug t.ImageRelease = t.Releases[0].Name } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2478,13 +2494,13 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug FmtBuilds(t.Builds, log) if t.Enabled { // 外部触发的pipeline - if pt.TaskCreator == setting.WebhookTaskCreator || pt.TaskCreator == setting.CronTaskCreator { - SetTriggerBuilds(t.Builds, pt.TaskArgs.Builds, log) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || taskOpt.Task.TaskCreator == setting.CronTaskCreator { + SetTriggerBuilds(t.Builds, taskOpt.Task.TaskArgs.Builds, log) } else { - setManunalBuilds(t.Builds, pt.TaskArgs.Builds, log) + setManunalBuilds(t.Builds, taskOpt.Task.TaskArgs.Builds, log) } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2497,8 +2513,8 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug } if t.Enabled { - t.SetImageName(pt.TaskArgs.Deploy.Image) - pt.SubTasks[i], err = t.ToSubTask() + t.SetImageName(taskOpt.Task.TaskArgs.Deploy.Image) + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2513,7 +2529,7 @@ func ensurePipelineTask(pt *task.Task, envName, serviceName string, log *zap.Sug } if t.Enabled { - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 9b394fc6..62629b39 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -119,7 +119,9 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str sort.Sort(ByTaskKind(pt.SubTasks)) - if err := ensurePipelineTask(pt, "", "", log); err != nil { + if err := ensurePipelineTask(&task.TaskOpt{ + Task: pt, + }, log); err != nil { log.Errorf("Service.ensurePipelineTask failed %v %s", args, err) if err, ok := err.(*ContainerNotFound); ok { return nil, e.NewWithExtras( diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 11be9780..7506ebc4 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -31,7 +31,7 @@ type Extension struct { Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceInfos []ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` + ServiceInfos []*ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` -- Gitee From 86e88a5753800b65af6278ce5952dcbe40300d91 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 12:05:10 +0800 Subject: [PATCH 1561/2647] optimize code Signed-off-by: liu deyi --- .../core/service/taskplugin/extension.go | 14 ++++++-------- .../core/service/types/task/trigger.go | 18 ++++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index eb7e8855..6d2ef683 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -128,14 +128,12 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, } webhookPayload := &task.WebhookPayload{ - EventName: "workflow", - ProjectName: pipelineTask.ProductName, - TaskName: pipelineTask.PipelineName, - TaskID: pipelineTask.TaskID, - ServiceName: p.Task.ServiceName, - ServiceModule: p.Task.ServiceModule, - Image: p.Task.Image, - Creator: pipelineTask.TaskCreator, + EventName: "workflow", + ProjectName: pipelineTask.ProductName, + TaskName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + ServiceInfos: p.Task.ServiceInfos, + Creator: pipelineTask.TaskCreator, } body, err = json.Marshal(webhookPayload) for _, header := range p.Task.Headers { diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index c5a2f3c2..bce09fb1 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,16 +54,14 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - EventName string `json:"event_name"` - ProjectName string `json:"project_name"` - TaskName string `json:"task_name"` - TaskID int64 `json:"task_id"` - TaskOutput []*TaskOutput `json:"task_output"` - TaskEnvs []*KeyVal `json:"task_envs"` - ServiceName string `json:"service_name"` - ServiceModule string `json:"service_module"` - Image string `json:"image"` - Creator string `json:"creator"` + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output"` + TaskEnvs []*KeyVal `json:"task_envs"` + ServiceInfos []*ServiceInfo `json:"service_infos"` + Creator string `json:"creator"` } type TaskOutput struct { -- Gitee From 28ed1b3accdcd38142babeff1f54899b26c4a27a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 13:50:43 +0800 Subject: [PATCH 1562/2647] debug code Signed-off-by: liu deyi --- .../common/repository/models/task/extension.go | 10 +++++----- .../common/repository/models/task/model.go | 1 + .../workflow/service/workflow/workflow_task.go | 18 ++++++++++++------ .../core/service/types/task/extension.go | 4 ++-- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 238b6836..10fc03cd 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -30,8 +30,8 @@ type Extension struct { URL string `bson:"url" json:"url"` Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceInfos []*ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -40,9 +40,9 @@ type Extension struct { } type ServiceInfo struct { - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` - Image string `bson:"image" json:"image"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` } // ToSubTask ... diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index b6ebb2fc..9a74eb01 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -167,5 +167,6 @@ type Error struct { type TaskOpt struct { Task *Task `json:"task"` EnvName string `json:"env_name"` + ServiceName string `json:"service_name"` ServiceInfos []*ServiceInfo `json:"service_infos"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 06a85a30..5455663c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -675,6 +675,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, if err := ensurePipelineTask(&taskmodels.TaskOpt{ Task: task, EnvName: args.Namespace, + ServiceName: target.ServiceName, ServiceInfos: serviceInfos, }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%v", task.ID, task.PipelineName, err) @@ -701,12 +702,15 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } // add extension to stage if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { + for _, serviceInfo := range serviceInfos { + log.Infof("serviceInfo:%+v", serviceInfo) + } extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage, serviceInfos) if err != nil { log.Errorf("add extension task error: %s", err) return nil, e.ErrCreateTask.AddErr(err) } - AddSubtaskToStage(&stages, extensionTask, "extension") + AddSubtaskToStage(&stages, extensionTask, string(config.TaskExtension)) } testTask := &taskmodels.Task{ @@ -2151,11 +2155,13 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err t.JobCtx.Image = GetImage(reg, releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "image")) taskOpt.Task.TaskArgs.Deploy.Image = t.JobCtx.Image - taskOpt.ServiceInfos = append(taskOpt.ServiceInfos, &taskmodels.ServiceInfo{ - ServiceName: t.Service, - ServiceModule: t.ServiceName, - Image: t.JobCtx.Image, - }) + if taskOpt.ServiceName != "" { + taskOpt.ServiceInfos = append(taskOpt.ServiceInfos, &taskmodels.ServiceInfo{ + ServiceName: taskOpt.ServiceName, + ServiceModule: t.ServiceName, + Image: t.JobCtx.Image, + }) + } if taskOpt.Task.ConfigPayload != nil { taskOpt.Task.ConfigPayload.Registry.Addr = reg.RegAddr diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 7506ebc4..540b46ed 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -30,8 +30,8 @@ type Extension struct { URL string `bson:"url" json:"url"` Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers,omitempty"` - ServiceInfos []*ServiceInfo `bson:"service_infos,omitempty" json:"service_infos,omitempty"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` Error string `bson:"error,omitempty" json:"error,omitempty"` -- Gitee From 2774376906c7630cfc03bd75901eb6dd5e95c915 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 15:20:32 +0800 Subject: [PATCH 1563/2647] add type Signed-off-by: mouuii --- .../policy/core/repository/models/policy.go | 17 +++++++++-------- pkg/microservice/policy/core/service/policy.go | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index abba69d0..20b43bff 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -19,14 +19,15 @@ package models // Policy is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a PolicyBinding. // for a cluster scoped Policy, namespace is empty. type Policy struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Description string `json:"description"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - Rules []*Rule `bson:"rules" json:"rules"` - CreateBy string `bson:"create_by" json:"create_by"` - UpdateBy string `bson:"update_by" json:"update_by"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Description string `json:"description" json:"description"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Rules []*Rule `bson:"rules" json:"rules"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` + Type string `bson:"type" json:"type"` } func (Policy) TableName() string { diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 2f0cb5a3..7f642543 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -43,6 +43,7 @@ func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, + Type: "system", } for _, r := range policy.Rules { @@ -62,6 +63,7 @@ func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, + Type: "system", } for _, r := range policy.Rules { -- Gitee From c5b219d9dcc9a9f25fe75d5056039631ff73d54e Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 15:36:38 +0800 Subject: [PATCH 1564/2647] add type Signed-off-by: mouuii --- .../policy/core/repository/models/policy_binding.go | 7 ++++--- pkg/microservice/policy/core/service/policy_binding.go | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/repository/models/policy_binding.go b/pkg/microservice/policy/core/repository/models/policy_binding.go index 5ec11fd9..16eb6ef8 100644 --- a/pkg/microservice/policy/core/repository/models/policy_binding.go +++ b/pkg/microservice/policy/core/repository/models/policy_binding.go @@ -21,13 +21,14 @@ package models // for a cluster scoped PolicyBinding, namespace is empty. type PolicyBinding struct { Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` + Namespace string `bson:"namespace" json:"namespace"` // Subjects holds references to the objects the Policy applies to. - Subjects []*Subject `bson:"subjects" json:"subjects"` + Subjects []*Subject `bson:"subjects" json:"subjects"` // PolicyRef can reference a namespaced or cluster scoped Policy. - PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` + PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` + Type string `bson:"type" json:"type"` } // PolicyRef contains information that points to the policy being used diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index 39e35f7e..da7023da 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -141,5 +141,6 @@ func createPolicyBindingObject(ns string, rb *PolicyBinding, logger *zap.Sugared Name: policy.Name, Namespace: policy.Namespace, }, + Type: "system", }, nil } -- Gitee From 59ee47795fd392e56e266077da5760a6b71624f1 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 15:50:56 +0800 Subject: [PATCH 1565/2647] add type Signed-off-by: mouuii --- .../policy/core/repository/models/policy.go | 22 +++++++++++-------- .../core/repository/models/policy_binding.go | 8 +++++-- .../policy/core/service/policy.go | 5 +++-- .../policy/core/service/policy_binding.go | 3 ++- pkg/setting/consts.go | 10 +++++++++ 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index 20b43bff..5e4a418e 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -16,18 +16,22 @@ limitations under the License. package models +import ( + "github.com/koderover/zadig/pkg/setting" +) + // Policy is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a PolicyBinding. // for a cluster scoped Policy, namespace is empty. type Policy struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Description string `json:"description" json:"description"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - Rules []*Rule `bson:"rules" json:"rules"` - CreateBy string `bson:"create_by" json:"create_by"` - UpdateBy string `bson:"update_by" json:"update_by"` - Type string `bson:"type" json:"type"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Description string `json:"description" json:"description"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Rules []*Rule `bson:"rules" json:"rules"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` + Type setting.PolicyType `bson:"type" json:"type"` } func (Policy) TableName() string { diff --git a/pkg/microservice/policy/core/repository/models/policy_binding.go b/pkg/microservice/policy/core/repository/models/policy_binding.go index 16eb6ef8..50d8dab9 100644 --- a/pkg/microservice/policy/core/repository/models/policy_binding.go +++ b/pkg/microservice/policy/core/repository/models/policy_binding.go @@ -16,6 +16,10 @@ limitations under the License. package models +import ( + "github.com/koderover/zadig/pkg/setting" +) + // PolicyBinding references a Policy, but does not contain it. It adds who information via Subjects. // PolicyBinding in a given namespace only have effect in that namespace. // for a cluster scoped PolicyBinding, namespace is empty. @@ -27,8 +31,8 @@ type PolicyBinding struct { Subjects []*Subject `bson:"subjects" json:"subjects"` // PolicyRef can reference a namespaced or cluster scoped Policy. - PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` - Type string `bson:"type" json:"type"` + PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` + Type setting.PolicyBindingType `bson:"type" json:"type"` } // PolicyRef contains information that points to the policy being used diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 7f642543..48396216 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/label" ) @@ -43,7 +44,7 @@ func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, - Type: "system", + Type: setting.PolicyTypeSystem, } for _, r := range policy.Rules { @@ -63,7 +64,7 @@ func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, - Type: "system", + Type: setting.PolicyTypeSystem, } for _, r := range policy.Rules { diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index da7023da..1798f890 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -23,6 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" ) type PolicyBinding struct { @@ -141,6 +142,6 @@ func createPolicyBindingObject(ns string, rb *PolicyBinding, logger *zap.Sugared Name: policy.Name, Namespace: policy.Namespace, }, - Type: "system", + Type: setting.PolicyBindingTypeSystem, }, nil } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 2c15370c..ede87a23 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -612,3 +612,13 @@ const ( const ( InformerNamingConvention = "%s-%s" ) + +type PolicyType string +type PolicyBindingType string +type LabelType string + +const ( + PolicyTypeSystem PolicyType = "system" + PolicyBindingTypeSystem PolicyBindingType = "system" + LabelTypeSystem LabelType = "system" +) -- Gitee From e6f2182160ccad75e26e293bee651179f8e1a818 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 15:54:39 +0800 Subject: [PATCH 1566/2647] add type Signed-off-by: mouuii --- .../service/collaboration_instance.go | 18 ++++++++++-------- .../core/label/repository/mongodb/label.go | 9 +++++---- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index d97dc45e..85bd2c73 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -469,7 +469,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: "system", + Type: setting.LabelTypeSystem, ProjectName: projectName, }) } @@ -478,7 +478,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: "system", + Type: setting.LabelTypeSystem, ProjectName: projectName, }) } @@ -489,7 +489,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: "system", + Type: setting.LabelTypeSystem, ProjectName: projectName, }) } @@ -498,7 +498,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: "system", + Type: setting.LabelTypeSystem, ProjectName: projectName, }) } @@ -507,7 +507,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: "system", + Type: setting.LabelTypeSystem, ProjectName: projectName, }) } @@ -516,7 +516,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: "system", + Type: setting.LabelTypeSystem, ProjectName: projectName, }) } @@ -528,7 +528,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: "system", + Type: setting.LabelTypeSystem, }) } for _, product := range instance.Products { @@ -536,7 +536,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: "system", + Type: setting.LabelTypeSystem, }) } } @@ -578,12 +578,14 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name), + Type: setting.LabelTypeSystem, }) } for _, product := range item.UpdateSpec.Products { labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name), + Type: setting.LabelTypeSystem, }) } diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 0305e37e..09e390f7 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -27,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" + "github.com/koderover/zadig/pkg/setting" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -140,10 +141,10 @@ func (c *LabelColl) Find(id string) (*models.Label, error) { } type Label struct { - Key string `json:"key"` - Value string `json:"value"` - Type string `json:"type"` - ProjectName string `json:"project_name"` + Key string `json:"key"` + Value string `json:"value"` + Type setting.LabelType `json:"type"` + ProjectName string `json:"project_name"` } type ListLabelOpt struct { -- Gitee From 476473a6c97941ddae794373406713bc4f7702d2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 15:54:51 +0800 Subject: [PATCH 1567/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/model.go | 8 ++++---- .../aslan/core/workflow/service/workflow/workflow_task.go | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 9a74eb01..00f673ae 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -165,8 +165,8 @@ type Error struct { } type TaskOpt struct { - Task *Task `json:"task"` - EnvName string `json:"env_name"` - ServiceName string `json:"service_name"` - ServiceInfos []*ServiceInfo `json:"service_infos"` + Task *Task `json:"task"` + EnvName string `json:"env_name"` + ServiceName string `json:"service_name"` + ServiceInfos *[]*ServiceInfo `json:"service_infos"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 5455663c..e74407f6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -676,7 +676,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, Task: task, EnvName: args.Namespace, ServiceName: target.ServiceName, - ServiceInfos: serviceInfos, + ServiceInfos: &serviceInfos, }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%v", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { @@ -702,6 +702,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } // add extension to stage if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { + log.Infof("len(serviceInfos):", len(serviceInfos)) for _, serviceInfo := range serviceInfos { log.Infof("serviceInfo:%+v", serviceInfo) } @@ -2156,7 +2157,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err taskOpt.Task.TaskArgs.Deploy.Image = t.JobCtx.Image if taskOpt.ServiceName != "" { - taskOpt.ServiceInfos = append(taskOpt.ServiceInfos, &taskmodels.ServiceInfo{ + *taskOpt.ServiceInfos = append(*taskOpt.ServiceInfos, &taskmodels.ServiceInfo{ ServiceName: taskOpt.ServiceName, ServiceModule: t.ServiceName, Image: t.JobCtx.Image, -- Gitee From 32f488d1e25e6401f323770fb1230d18279b1df4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 16:11:47 +0800 Subject: [PATCH 1568/2647] remove debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e74407f6..09201dc8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -702,10 +702,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } // add extension to stage if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { - log.Infof("len(serviceInfos):", len(serviceInfos)) - for _, serviceInfo := range serviceInfos { - log.Infof("serviceInfo:%+v", serviceInfo) - } extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage, serviceInfos) if err != nil { log.Errorf("add extension task error: %s", err) -- Gitee From ad92a9e46c0842e6254ce432fcf1ad8b2e201465 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 16:13:20 +0800 Subject: [PATCH 1569/2647] remove environment list Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 5 ----- pkg/microservice/aslan/core/label/repository/models/label.go | 4 +++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index ee6f397f..5444c8b1 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -8,14 +8,9 @@ rules: rules: - method: GET endpoint: "/api/aslan/environment/environments/?*/services/?*" - - method: GET - endpoint: "/api/aslan/environment/environments" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*" - method: GET diff --git a/pkg/microservice/aslan/core/label/repository/models/label.go b/pkg/microservice/aslan/core/label/repository/models/label.go index 933d2744..49e7f1a1 100644 --- a/pkg/microservice/aslan/core/label/repository/models/label.go +++ b/pkg/microservice/aslan/core/label/repository/models/label.go @@ -18,11 +18,13 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/setting" ) type Label struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type string `bson:"type" json:"type"` + Type setting.LabelType `bson:"type" json:"type"` Key string `bson:"key" json:"key"` Value string `bson:"value" json:"value"` CreateBy string `bson:"create_by" json:"create_by"` -- Gitee From 93fb14b812d68eb434b824eeadd40b728764421c Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 16:39:38 +0800 Subject: [PATCH 1570/2647] remove environment/?* Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 5444c8b1..5f9f17f3 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -11,15 +11,10 @@ rules: - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/environments/?*" - method: GET endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/groups" resourceType: "Environment" -- Gitee From 01b266833d3521a33018c4ec1dfd15eacf0f5981 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 16:50:43 +0800 Subject: [PATCH 1571/2647] remove environment Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 5f9f17f3..c4bed1ae 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -6,8 +6,6 @@ rules: alias: "查看集成环境" description: "" rules: - - method: GET - endpoint: "/api/aslan/environment/environments/?*/services/?*" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" @@ -19,16 +17,10 @@ rules: endpoint: "/api/aslan/environment/environments/?*/groups" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/groups$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/kube/workloads" - method: GET @@ -39,8 +31,6 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/groups" - method: GET endpoint: "/api/aslan/environment/kube/events" - method: GET @@ -49,29 +39,18 @@ rules: endpoint: "/api/aslan/project/products/?*/services" - method: GET endpoint: "/api/aslan/environment/revision/products" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/workloads" - method: GET endpoint: "/api/aslan/environment/environments/?*/workloads" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/workloads$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/releases" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/charts" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - action: create_environment -- Gitee From 75aa993cd8b07c937a8b91ea098e342c5f51692c Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 17 Feb 2022 17:50:00 +0800 Subject: [PATCH 1572/2647] do not migrate when data is empty Signed-off-by: zhangyifei --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 2b645482..0a5e6408 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -67,6 +67,10 @@ func migrateModuleBuild() error { return fmt.Errorf("failed to list all data in `zadig.module_build`: %s", err) } + if len(builds) == 0 { + return nil + } + var ms []mongo.WriteModel for _, build := range builds { if err := migrateOneBuild(build); err != nil { @@ -99,6 +103,10 @@ func migrateModuleTesting() error { return fmt.Errorf("failed to list all data in `zadig.module_testing`: %s", err) } + if len(testings) == 0 { + return nil + } + var ms []mongo.WriteModel for _, testing := range testings { if err := migrateOneTesting(testing); err != nil { -- Gitee From c98e3bb5a1b7e9e03664a9cabe50808ae28772f4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 17:50:18 +0800 Subject: [PATCH 1573/2647] modify code after code reivew Signed-off-by: liu deyi --- .../core/common/repository/models/task/extension.go | 1 - .../warpdrive/core/service/taskplugin/extension.go | 10 +++++++--- .../warpdrive/core/service/taskplugin/trigger.go | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 10fc03cd..cc8549c3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -45,7 +45,6 @@ type ServiceInfo struct { Image string `bson:"image" json:"image"` } -// ToSubTask ... func (t *Extension) ToSubTask() (map[string]interface{}, error) { var task map[string]interface{} if err := IToi(t, &task); err != nil { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 6d2ef683..97db628d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -33,8 +33,9 @@ import ( ) const ( - // ExtensionTaskTimeout ... ExtensionTaskTimeout = 60 * 60 * 1 // 60 minutes + ZadigEvent = "X-Zadig-Event" + EventName = "Workflow" ) // InitializeExtensionTaskPlugin to initialize build task plugin, and return reference @@ -88,7 +89,7 @@ func (p *ExtensionTaskPlugin) SetStatus(status config.Status) { // TaskTimeout ... func (p *ExtensionTaskPlugin) TaskTimeout() int { if p.Task.Timeout == 0 { - p.Task.Timeout = TriggerTaskTimeout + p.Task.Timeout = ExtensionTaskTimeout } else { if !p.Task.IsRestart { p.Task.Timeout = p.Task.Timeout * 60 @@ -136,10 +137,13 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, Creator: pipelineTask.TaskCreator, } body, err = json.Marshal(webhookPayload) + if err != nil { + return + } for _, header := range p.Task.Headers { httpclient.SetHeader(header.Key, header.Value) } - _, err = httpClient.Post(url, httpclient.SetHeader("X-Zadig-Event", "Workflow"), httpclient.SetBody(body)) + _, err = httpClient.Post(url, httpclient.SetHeader(ZadigEvent, EventName), httpclient.SetBody(body)) if err != nil { return } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 658a207d..d23f8c51 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -148,7 +148,7 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi for _, header := range p.Task.Headers { httpclient.SetHeader(header.Key, header.Value) } - _, err = httpClient.Post(url, httpclient.SetHeader("X-Zadig-Event", "Workflow"), httpclient.SetBody(body)) + _, err = httpClient.Post(url, httpclient.SetHeader(ZadigEvent, EventName), httpclient.SetBody(body)) if err != nil { return } -- Gitee From c1e9854cc3f001a2e2fc58f3e0949c4a521db9c4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 19:31:42 +0800 Subject: [PATCH 1574/2647] not test Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index c4bed1ae..7c1d5916 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -60,9 +60,6 @@ rules: - method: POST endpoint: "/api/aslan/environment/environments" resourceType: "Cluster" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: POST endpoint: "/api/aslan/service/workloads" - method: GET @@ -72,9 +69,6 @@ rules: - method: GET endpoint: "api/aslan/cluster/clusters" resourceType: "Cluster" - matchAttributes: - - key: "placeholder" - value: "placeholder" - action: config_environment alias: "配置集成环境" description: "" @@ -82,36 +76,21 @@ rules: - method: PUT endpoint: "/api/aslan/environment/environments" resourceType: "Environment" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/service/pm/healthCheckUpdate" resourceType: "Environment" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*/envRecycle" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/envRecycle$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*/renderset" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/renderset$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/service/workloads" - method: GET @@ -120,9 +99,6 @@ rules: endpoint: "/api/aslan/environment/environments/?*/registry" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/registry$" - matchAttributes: - - key: "placeholder" - value: "placeholder" - action: manage_environment alias: "管理服务实例" description: "" @@ -131,37 +107,22 @@ rules: endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scale" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: POST endpoint: "/api/aslan/environment/image/deployment" - method: POST @@ -184,6 +145,3 @@ rules: endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" - matchAttributes: - - key: "placeholder" - value: "placeholder" -- Gitee From 72067622a67fdb376dc1cd41ba14761b4fe89e9b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 19:40:33 +0800 Subject: [PATCH 1575/2647] fix the same service module bug Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e9f33926..df031fbe 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -691,7 +691,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, target.Name) + AddSubtaskToStage(&stages, stask, target.ServiceName+"_"+target.Name) } } -- Gitee From 93279527e6862116dbba8c74ad18de3edd9314ce Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 20:04:50 +0800 Subject: [PATCH 1576/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index df031fbe..c17fc175 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -691,7 +691,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, target.ServiceName+"_"+target.Name) + AddSubtaskToStage(&stages, stask, target.Name) } } @@ -2520,7 +2520,7 @@ func AddSubtaskToStage(stages *[]*commonmodels.Stage, subTask map[string]interfa for _, stage := range *stages { if stage.TaskType == subTaskPre.TaskType { // deploy task 同一个组件可能有多个部署目标 - if subTaskPre.TaskType == config.TaskDeploy || subTaskPre.TaskType == config.TaskResetImage { + if subTaskPre.TaskType == config.TaskBuild || subTaskPre.TaskType == config.TaskDeploy || subTaskPre.TaskType == config.TaskResetImage { if _, ok := stage.SubTasks[target]; ok { stage.SubTasks[target+"_"+nextTargetID(stage.SubTasks, target)] = subTask } else { -- Gitee From 215d2bcbf77bf3ee887a0b19eb35865e68cbc634 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 17 Feb 2022 20:05:27 +0800 Subject: [PATCH 1577/2647] protection when pre_build.cluster_id is empty Signed-off-by: zhangyifei --- .../aslan/core/workflow/service/workflow/workflow_task.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e9f33926..c13200af 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1852,6 +1852,13 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL ClusterID: module.PreBuild.ClusterID, } + // In some old build configurations, the `pre_build.cluster_id` field is empty indicating that's a local cluster. + // We do a protection here to avoid query failure. + // Resaving the build configuration after v1.8.0 will automatically populate this field. + if module.PreBuild.ClusterID == "" { + module.PreBuild.ClusterID = setting.LocalClusterID + } + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(module.PreBuild.ClusterID) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) -- Gitee From 9298bdae6e0c3581c81de1cccaf876755c27edc3 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 17 Feb 2022 20:11:45 +0800 Subject: [PATCH 1578/2647] protection when pre_test.cluster_id is empty Signed-off-by: zhangyifei --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 365a06c5..4f91af95 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -664,6 +664,13 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testArg.TestModuleName = args.TestName + // In some old testing configurations, the `pre_test.cluster_id` field is empty indicating that's a local cluster. + // We do a protection here to avoid query failure. + // Resaving the testing configuration after v1.8.0 will automatically populate this field. + if testing.PreTest.ClusterID == "" { + testing.PreTest.ClusterID = setting.LocalClusterID + } + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(testing.PreTest.ClusterID) if err != nil { return resp, e.ErrListTestModule.AddDesc(err.Error()) -- Gitee From 2678e6f7e2571e5364960114a36ef9d467c023b3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 20:15:28 +0800 Subject: [PATCH 1579/2647] bug Signed-off-by: mouuii --- .../workflow/service/workflow/workflow.go | 4 +- result.json | 1022 +++++++++++++++++ 2 files changed, 1024 insertions(+), 2 deletions(-) create mode 100644 result.json diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 430d8b7a..4037a825 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -698,14 +698,14 @@ func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.Sugar } var newWorkflows []*commonmodels.Workflow for _, workflow := range oldWorkflows { - if item, ok := workflowMap[workflow.ProductTmplName+"~"+workflow.Name]; ok { + if item, ok := workflowMap[workflow.ProductTmplName+"-"+workflow.Name]; ok { workflow.UpdateBy = username workflow.Name = item.New workflow.BaseName = item.BaseName workflow.ID = primitive.NewObjectID() newWorkflows = append(newWorkflows, workflow) } else { - return fmt.Errorf("workflow:%s not exist", workflow.ProductTmplName+"~"+workflow.Name) + return fmt.Errorf("workflow:%s not exist", workflow.ProductTmplName+"-"+workflow.Name) } } return commonrepo.NewWorkflowColl().BulkCreate(newWorkflows) diff --git a/result.json b/result.json new file mode 100644 index 00000000..d87e9d0d --- /dev/null +++ b/result.json @@ -0,0 +1,1022 @@ +{ + "all_roles": [ + { + "name": "read-project-only", + "namespace": "" + } + ], + "allow": false, + "allowed_policies": [ + { + "name": "project41-asdf-system-z42", + "namespace": "project41" + }, + { + "name": "read-project-only", + "namespace": "" + } + ], + "allowed_policy_attributive_rules": [ + { + "endpoint": "/api/aslan/environment/environments/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "policy", + "value": "project41-asdf-system-z42-Environment-dev" + } + ], + "method": "DELETE", + "resourceType": "Environment" + } + ], + "allowed_policy_plain_rules": [], + "allowed_policy_rules": [ + { + "endpoint": "/api/aslan/environment/environments/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "policy", + "value": "project41-asdf-system-z42-Environment-dev" + } + ], + "method": "DELETE", + "resourceType": "Environment" + } + ], + "allowed_role_attributive_rules": [ + { + "endpoint": "/api/aslan/environment/configmaps", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/diff/products/?*/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/environments", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/groups", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/groups$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/helm/charts", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/helm/releases", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/services/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/services/", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/workloads", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/workloads$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/export/service", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/kube/events", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/kube/pods/?*/events", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/kube/workloads", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/revision/products", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/sse/pods/?*/containers/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/project/products/?*/services", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/workloads", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v3", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v3/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v3/?*/args", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflow/find/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/directory/workflowTask", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "api/aslan/workflow/workflow", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "api/aslan/workflow/workflow", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Workflow" + } + ], + "allowed_role_plain_rules": [ + { + "endpoint": "/api/aslan/build/build", + "method": "GET" + }, + { + "endpoint": "/api/aslan/build/build/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/build/targets", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/releases", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/releases/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/releases/helm/charts", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/servicenames", + "method": "GET" + }, + { + "endpoint": "/api/aslan/project/products/?*/searching-rules", + "method": "GET" + }, + { + "endpoint": "/api/aslan/project/products/?*/services", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/helm/?*/?*/fileContent", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/helm/?*/?*/filePath", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/helm/?*/?*/serviceModule", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services/?*/k8s", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services/?*/pm", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/test", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/test/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/testdetail", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/workspace/workflow/?*/taskId/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflow/testName/?*", + "method": "GET" + } + ], + "allowed_role_rules": [ + { + "endpoint": "/api/aslan/build/build", + "method": "GET" + }, + { + "endpoint": "/api/aslan/build/build/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/build/targets", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/releases", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/releases/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/releases/helm/charts", + "method": "GET" + }, + { + "endpoint": "/api/aslan/delivery/servicenames", + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/configmaps", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/diff/products/?*/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/environments", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/groups", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/groups$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/helm/charts", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/helm/releases", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/services/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/services/", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/environments/?*/workloads", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/workloads$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + }, + { + "endpoint": "/api/aslan/environment/export/service", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/kube/events", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/kube/pods/?*/events", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/kube/workloads", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/environment/revision/products", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/sse/pods/?*/containers/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/project/products/?*/searching-rules", + "method": "GET" + }, + { + "endpoint": "/api/aslan/project/products/?*/services", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/project/products/?*/services", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/helm/?*/?*/fileContent", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/helm/?*/?*/filePath", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/helm/?*/?*/serviceModule", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services/?*/k8s", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/services/?*/pm", + "method": "GET" + }, + { + "endpoint": "/api/aslan/service/workloads", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/test", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/test/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/testdetail", + "method": "GET" + }, + { + "endpoint": "/api/aslan/testing/workspace/workflow/?*/taskId/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v3", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v3/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/v3/?*/args", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflow/find/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflow/testName/?*", + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "/api/directory/workflowTask", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "api/aslan/workflow/workflow", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET" + }, + { + "endpoint": "api/aslan/workflow/workflow", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Workflow" + } + ], + "allowed_roles": [ + { + "name": "read-project-only", + "namespace": "" + } + ], + "bearer_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiejQyIiwiZW1haWwiOiJ6NEBxcS5jb20iLCJ1aWQiOiI4YTRjNGVjMS04ZmQ4LTExZWMtODk1YS1kZTRmYzVkYTQzMDEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ6NDIiLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6InN5c3RlbSIsInVzZXJfaWQiOiJ6NDIifSwiYXVkIjoiemFkaWciLCJleHAiOjE2NDUxODI1OTZ9.kFCdTG9SfPtDMbaiYfgfgRKY_hY2-lHrimJ6YK8J4xA", + "bearer_token_in_header": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiejQyIiwiZW1haWwiOiJ6NEBxcS5jb20iLCJ1aWQiOiI4YTRjNGVjMS04ZmQ4LTExZWMtODk1YS1kZTRmYzVkYTQzMDEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ6NDIiLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6InN5c3RlbSIsInVzZXJfaWQiOiJ6NDIifSwiYXVkIjoiemFkaWciLCJleHAiOjE2NDUxODI1OTZ9.kFCdTG9SfPtDMbaiYfgfgRKY_hY2-lHrimJ6YK8J4xA", + "claims": { + "aud": "zadig", + "email": "z4@qq.com", + "exp": 1645182596, + "federated_claims": { + "connector_id": "system", + "user_id": "z42" + }, + "name": "z42", + "preferred_username": "z42", + "uid": "8a4c4ec1-8fd8-11ec-895a-de4fc5da4301" + }, + "envs": { + "COMMAND_MODE": "unix2003", + "GO111MODULE": "on", + "GOPATH": "/Users/z/go", + "GOROOT": "/opt/homebrew/Cellar/go/1.17.3/libexec", + "HOME": "/Users/z", + "HOMEBREW_CELLAR": "/opt/homebrew/Cellar", + "HOMEBREW_PREFIX": "/opt/homebrew", + "HOMEBREW_REPOSITORY": "/opt/homebrew", + "INFOPATH": "/opt/homebrew/share/info:", + "LC_CTYPE": "en_US.UTF-8", + "LESS": "-R", + "LOGIN_SHELL": "1", + "LOGNAME": "z", + "LSCOLORS": "Gxfxcxdxbxegedabagacad", + "MANPATH": "/opt/homebrew/share/man::", + "OLDPWD": "/Users/z/Documents/GitHub/mzadig", + "PAGER": "less", + "PATH": "/opt/homebrew/Cellar/go/1.17.3/libexec/bin:/Users/z/go/bin:/opt/homebrew/opt/gnu-sed/libexec/gnubin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/z/go/bin", + "PWD": "/Users/z/Documents/GitHub/mzadig", + "SECRET_KEY": "9F11B4E503C7F2B577E5F9366BDDAB64", + "SHELL": "/bin/zsh", + "SHLVL": "1", + "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.5bXC9yvJnk/Listeners", + "TERM": "xterm-256color", + "TERMINAL_EMULATOR": "JetBrains-JediTerm", + "TERM_SESSION_ID": "ce8d8f7c-4d2e-413a-926a-7fe33e0b1238", + "TMPDIR": "/var/folders/ww/12zzzwxd2g50c516yxztbn140000gn/T/", + "USER": "z", + "XPC_FLAGS": "0x0", + "XPC_SERVICE_NAME": "0", + "ZSH": "/Users/z/.oh-my-zsh", + "_": "/opt/homebrew/bin/opa", + "__CFBundleIdentifier": "com.jetbrains.goland", + "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0", + "__INTELLIJ_COMMAND_HISTFILE__": "/Users/z/Library/Caches/JetBrains/GoLand2021.2/terminal/history/mzadig-history112" + }, + "is_authenticated": true, + "project_name": "project41", + "response": { + "allowed": false, + "http_status": 403 + }, + "secret": "9F11B4E503C7F2B577E5F9366BDDAB64", + "url_is_registered": true, + "user_allowed_projects": [], + "user_matched_policy_rule": [ + { + "endpoint": "/api/aslan/environment/environments/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "policy", + "value": "project41-asdf-system-z42-Environment-dev" + } + ], + "method": "DELETE", + "resourceType": "Environment" + } + ], + "user_matched_policy_rule_for_filtering": [], + "user_matched_role_rule": [ + { + "endpoint": "/api/aslan/environment/environments/?*", + "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", + "matchAttributes": [ + { + "key": "placeholder", + "value": "placeholder" + } + ], + "method": "GET", + "resourceType": "Environment" + } + ], + "user_matched_role_rule_for_filtering": [], + "user_policy_allowed_resources": [], + "user_projects": [ + "project41" + ], + "user_role_allowed_resources": [], + "user_visible_projects": [ + "project41" + ] +} -- Gitee From c661fa36413b6c5eeddb3ebfb9a0b89aa18adf52 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Feb 2022 20:37:11 +0800 Subject: [PATCH 1580/2647] bug Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 430d8b7a..4037a825 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -698,14 +698,14 @@ func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.Sugar } var newWorkflows []*commonmodels.Workflow for _, workflow := range oldWorkflows { - if item, ok := workflowMap[workflow.ProductTmplName+"~"+workflow.Name]; ok { + if item, ok := workflowMap[workflow.ProductTmplName+"-"+workflow.Name]; ok { workflow.UpdateBy = username workflow.Name = item.New workflow.BaseName = item.BaseName workflow.ID = primitive.NewObjectID() newWorkflows = append(newWorkflows, workflow) } else { - return fmt.Errorf("workflow:%s not exist", workflow.ProductTmplName+"~"+workflow.Name) + return fmt.Errorf("workflow:%s not exist", workflow.ProductTmplName+"-"+workflow.Name) } } return commonrepo.NewWorkflowColl().BulkCreate(newWorkflows) -- Gitee From 59acd018bb83270aa90b6f20ddb030081948d017 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 17 Feb 2022 20:49:07 +0800 Subject: [PATCH 1581/2647] openAPI token Signed-off-by: ddh27 --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 10 ++++ .../internal/repository/orm/addmysql.sql | 3 ++ .../internal/repository/orm/db.go | 50 +++++++++++++++++++ .../internal/repository/orm/dropmysql.sql | 5 ++ .../user/core/repository/models/user.go | 1 + .../user/core/service/init/mysql.sql | 1 + .../user/core/service/user/user.go | 36 ++++++++++++- 7 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql create mode 100644 pkg/cli/upgradeassistant/internal/repository/orm/db.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 0a5e6408..3ae7954c 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -25,6 +25,7 @@ import ( internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/orm" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -50,12 +51,21 @@ func V190ToV1100() error { return fmt.Errorf("failed to migrate data in `zadig.module_testing`: %s", err) } + log.Info("InitDBAndEditTables: ADD cloumn from mysql table `user`.") + if err := orm.InitDBAndEditTables(orm.DbEditActionAdd); err != nil { + return fmt.Errorf("InitDBAndEditTables: failed to ADD cloumn from mysql table `user`: %s", err) + } return nil } // Since the old data has not been changed, no changes are required. func V1100ToV190() error { log.Info("Rollback data from 1.10.0 to 1.9.0") + + log.Info("InitDBAndEditTables: drop cloumn from mysql table `user`.") + if err := orm.InitDBAndEditTables(orm.DbEditActionDrop); err != nil { + return fmt.Errorf("InitDBAndEditTables: failed to drop cloumn from mysql table `user`: %s", err) + } return nil } diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql new file mode 100644 index 00000000..2fb64081 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql @@ -0,0 +1,3 @@ +use user; + +ALTER TABLE `user` ADD `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken'; \ No newline at end of file diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/db.go b/pkg/cli/upgradeassistant/internal/repository/orm/db.go new file mode 100644 index 00000000..e9df7215 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/orm/db.go @@ -0,0 +1,50 @@ +package orm + +import ( + "database/sql" + _ "embed" + "fmt" + + _ "github.com/go-sql-driver/mysql" + + "github.com/koderover/zadig/pkg/config" +) + +type DbEditAction string + +const ( + DbEditActionAdd DbEditAction = "add" + DbEditActionDrop DbEditAction = "drop" +) + +//go:embed addmysql.sql +var addmysql []byte + +//go:embed dropmysql.sql +var dropmysql []byte + +func InitDBAndEditTables(action DbEditAction) error { + var mysql []byte + switch action { + case DbEditActionAdd: + mysql = addmysql + case DbEditActionDrop: + mysql = dropmysql + } + if len(mysql) == 0 { + return fmt.Errorf("%smysql.sql is empty", action) + } + db, err := sql.Open("mysql", fmt.Sprintf( + "%s:%s@tcp(%s)/?charset=utf8&multiStatements=true", + config.MysqlUser(), config.MysqlPassword(), config.MysqlHost(), + )) + if err != nil { + return err + } + defer db.Close() + _, err = db.Exec(string(mysql)) + if err != nil { + return err + } + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql new file mode 100644 index 00000000..cb67a73b --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql @@ -0,0 +1,5 @@ +use user; + +LOCK TABLES `user` WRITE; + ALTER TABLE `user` DROP COLUMN `api_token`; + UNLOCK TABLES; \ No newline at end of file diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go index dae08d0f..71fa4a34 100644 --- a/pkg/microservice/user/core/repository/models/user.go +++ b/pkg/microservice/user/core/repository/models/user.go @@ -24,6 +24,7 @@ type User struct { Email string `json:"email"` Phone string `json:"phone"` Account string `json:"account"` + APIToken string `gorm:"api_token" json:"api_token"` } // TableName sets the insert table name for this struct type diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql index bb77d2f9..8986ff93 100644 --- a/pkg/microservice/user/core/service/init/mysql.sql +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS `user`( `identity_type` varchar(32) NOT NULL DEFAULT 'unknown' COMMENT '用户来源', `phone` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号码', `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱', + `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken'; `created_at` int(11) unsigned NOT NULL COMMENT '创建时间', `updated_at` int(11) unsigned NOT NULL COMMENT '修改时间', UNIQUE KEY `account` (`account`,`identity_type`), diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 5741c31c..55dfbd88 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -71,6 +71,7 @@ type UserInfo struct { Email string `json:"email"` Phone string `json:"phone"` Account string `json:"account"` + APIToken string `json:"token"` } type Password struct { @@ -175,7 +176,40 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { return nil, err } userInfo := mergeUserLogin([]models.User{*user}, []models.UserLogin{*userLogin}, logger) - return &userInfo[0], nil + userInfoRes := &userInfo[0] + userInfoRes.APIToken = user.APIToken + //TODO Create a permanent OpenAPI token + if user.APIToken == "" { + token, err := login.CreateToken(&login.Claims{ + Name: user.Name, + UID: user.UID, + Email: user.Email, + PreferredUsername: user.Account, + StandardClaims: jwt.StandardClaims{ + Audience: setting.ProductName, + //24*365*100=876000 + ExpiresAt: time.Now().Add(876000 * time.Hour).Unix(), + }, + FederatedClaims: login.FederatedClaims{ + ConnectorId: user.IdentityType, + UserId: user.Account, + }, + }) + if err != nil { + logger.Errorf("LocalLogin user:%s create token error, error msg:%s", user.Account, err.Error()) + return nil, err + } + userInfoRes.APIToken = token + userWithToken := &models.User{ + APIToken: token, + } + err = orm.UpdateUser(uid, userWithToken, core.DB) + if err != nil { + logger.Errorf("UpdateUser user:%s save token error:%s", user.Account, err.Error()) + return nil, err + } + } + return userInfoRes, nil } func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { -- Gitee From a3899c20f8131c9b6f5fb08d4e1301cd7302766f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 20:49:46 +0800 Subject: [PATCH 1582/2647] debug code Signed-off-by: liu deyi --- .../service/taskcontroller/task_handler.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 5a812686..55682ec0 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -20,6 +20,8 @@ import ( "context" "encoding/json" "fmt" + "regexp" + "strconv" "strings" "time" @@ -296,12 +298,18 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { var tasks []*Task // 每个SubTask会initiate一个plugin instance来执行 + taskServiceMap := make(map[string]map[string]interface{}) for serviceName, subTask := range stage.SubTasks { var pluginInstance plugins.TaskPlugin xl.Infof("new sub task of service name: %s, type: %s", serviceName, stage.TaskType) pluginInstance = pluginInitiator(stage.TaskType) //xl.Errorf("%v", ctx.Value(CtxKeyBuildInfos)) + if _, ok := taskServiceMap[serviceName]; ok { + serviceName = serviceName + "_" + nextTargetID(taskServiceMap, serviceName) + } + tasks = append(tasks, NewTask(ctx, h.executeTask, pluginInstance, subTask, stagePosition, serviceName, xl)) + taskServiceMap[serviceName] = subTask } // 判断subTask是否是deploy,如果是的话判断是否是helm类型的服务, //todo helm类型的服务的部署暂时只支持串行执行 @@ -328,6 +336,17 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { h.SendAck() } +func nextTargetID(subTasks map[string]map[string]interface{}, target string) string { + count := 0 + for k := range subTasks { + if regexp.MustCompile(`^\Q` + target + `\E(_\d+)?$`).MatchString(k) { + count++ + } + } + + return strconv.Itoa(count) +} + // execute: PipelineTask Executor // 兼容支持1.0和2.0的数据结构 // 支持根据RunParallel参数指定的并发或串行执行 -- Gitee From e424d3dc0e1fcbf963e72fb90ade722981a7a384 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 21:06:45 +0800 Subject: [PATCH 1583/2647] debug code Signed-off-by: liu deyi --- .../service/workflow/workflow_task.go | 6 +++--- .../service/taskcontroller/task_handler.go | 19 ------------------- 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c17fc175..86406fe7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -691,7 +691,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, target.Name) + AddSubtaskToStage(&stages, stask, target.Name+"."+target.ServiceName) } } @@ -1837,7 +1837,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, - ServiceName: args.Target, + ServiceName: args.Target + "." + args.ServiceName, Service: args.ServiceName, JobCtx: task.JobCtx{}, ImageID: module.PreBuild.ImageID, @@ -2520,7 +2520,7 @@ func AddSubtaskToStage(stages *[]*commonmodels.Stage, subTask map[string]interfa for _, stage := range *stages { if stage.TaskType == subTaskPre.TaskType { // deploy task 同一个组件可能有多个部署目标 - if subTaskPre.TaskType == config.TaskBuild || subTaskPre.TaskType == config.TaskDeploy || subTaskPre.TaskType == config.TaskResetImage { + if subTaskPre.TaskType == config.TaskDeploy || subTaskPre.TaskType == config.TaskResetImage { if _, ok := stage.SubTasks[target]; ok { stage.SubTasks[target+"_"+nextTargetID(stage.SubTasks, target)] = subTask } else { diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 55682ec0..5a812686 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -20,8 +20,6 @@ import ( "context" "encoding/json" "fmt" - "regexp" - "strconv" "strings" "time" @@ -298,18 +296,12 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { var tasks []*Task // 每个SubTask会initiate一个plugin instance来执行 - taskServiceMap := make(map[string]map[string]interface{}) for serviceName, subTask := range stage.SubTasks { var pluginInstance plugins.TaskPlugin xl.Infof("new sub task of service name: %s, type: %s", serviceName, stage.TaskType) pluginInstance = pluginInitiator(stage.TaskType) //xl.Errorf("%v", ctx.Value(CtxKeyBuildInfos)) - if _, ok := taskServiceMap[serviceName]; ok { - serviceName = serviceName + "_" + nextTargetID(taskServiceMap, serviceName) - } - tasks = append(tasks, NewTask(ctx, h.executeTask, pluginInstance, subTask, stagePosition, serviceName, xl)) - taskServiceMap[serviceName] = subTask } // 判断subTask是否是deploy,如果是的话判断是否是helm类型的服务, //todo helm类型的服务的部署暂时只支持串行执行 @@ -336,17 +328,6 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { h.SendAck() } -func nextTargetID(subTasks map[string]map[string]interface{}, target string) string { - count := 0 - for k := range subTasks { - if regexp.MustCompile(`^\Q` + target + `\E(_\d+)?$`).MatchString(k) { - count++ - } - } - - return strconv.Itoa(count) -} - // execute: PipelineTask Executor // 兼容支持1.0和2.0的数据结构 // 支持根据RunParallel参数指定的并发或串行执行 -- Gitee From 40e02d4903febfe20af2e65dfcb0433ea2c6c93b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 21:19:32 +0800 Subject: [PATCH 1584/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 86406fe7..80493df1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -691,7 +691,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, target.Name+"."+target.ServiceName) + AddSubtaskToStage(&stages, stask, target.Name+"_"+target.ServiceName) } } @@ -1837,7 +1837,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, - ServiceName: args.Target + "." + args.ServiceName, + ServiceName: args.Target + "_" + args.ServiceName, Service: args.ServiceName, JobCtx: task.JobCtx{}, ImageID: module.PreBuild.ImageID, -- Gitee From 68bd1bd6d3151dc015aa5441170bd257bf702a09 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Feb 2022 21:29:18 +0800 Subject: [PATCH 1585/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 80493df1..317318a0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1263,7 +1263,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ log.Error(err) return nil, err } - deployTask.ServiceName = envList[0] + deployTask.ServiceName = envList[1] + "_" + envList[0] deployTask.ContainerName = envList[1] switch env.Type { -- Gitee From b019a0237597723b86f89298db417d9df5296c98 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 11:50:35 +0800 Subject: [PATCH 1586/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 317318a0..0ddb7f56 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1837,7 +1837,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, - ServiceName: args.Target + "_" + args.ServiceName, + ServiceName: args.Target, Service: args.ServiceName, JobCtx: task.JobCtx{}, ImageID: module.PreBuild.ImageID, @@ -2169,6 +2169,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e t.UTStatus = &task.UTStatus{} t.StaticCheckStatus = &task.StaticCheckStatus{} t.BuildStatus = &task.BuildStatus{} + t.ServiceName = t.ServiceName + "_" + t.Service pt.SubTasks[i], err = t.ToSubTask() @@ -2332,7 +2333,10 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e t.SetImage(pt.TaskArgs.Deploy.Image) t.SetNamespace(pt.TaskArgs.Deploy.Namespace) - _, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) + serviceName := t.ServiceName + serviceName = strings.TrimPrefix(serviceName, t.ContainerName+"_") + + _, err := validateServiceContainer(t.EnvName, t.ProductName, serviceName, t.ContainerName) if err != nil { log.Error(err) return err -- Gitee From 03b077850bbb64a2c56c120cd6c0ff7120b8e603 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 18 Feb 2022 13:48:48 +0800 Subject: [PATCH 1587/2647] production Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 48 +++++++++++++++++++ .../aslan/core/environment/service/bundle.go | 24 ++++++++++ 2 files changed, 72 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 7c1d5916..6ec461db 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -13,14 +13,23 @@ rules: endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/groups" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/groups$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/kube/workloads" - method: GET @@ -43,14 +52,23 @@ rules: endpoint: "/api/aslan/environment/environments/?*/workloads" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/workloads$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/releases" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/charts" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - action: create_environment @@ -80,6 +98,9 @@ rules: endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/service/pm/healthCheckUpdate" resourceType: "Environment" @@ -87,10 +108,16 @@ rules: endpoint: "/api/aslan/environment/environments/?*/envRecycle" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/envRecycle$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments/?*/renderset" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/renderset$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/service/workloads" - method: GET @@ -99,6 +126,9 @@ rules: endpoint: "/api/aslan/environment/environments/?*/registry" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/registry$" + matchAttributes: + - key: "production" + value: "false" - action: manage_environment alias: "管理服务实例" description: "" @@ -107,22 +137,37 @@ rules: endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scale" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/environment/image/deployment" - method: POST @@ -145,3 +190,6 @@ rules: endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 2e6b905c..35a1cd1b 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -17,13 +17,17 @@ limitations under the License. package service import ( + "strconv" + "go.uber.org/zap" commonConfig "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" labeldb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" + "github.com/koderover/zadig/pkg/tool/log" ) type resourceSpec struct { @@ -57,6 +61,18 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { return nil, err } + // production attribute + clusterMap := make(map[string]*models.K8SCluster) + clusters, err := mongodb.NewK8SClusterColl().List(nil) + if err != nil { + log.Errorf("Failed to list clusters in db, err: %s", err) + return nil, err + } + + for _, cls := range clusters { + clusterMap[cls.ID.Hex()] = cls + } + for _, env := range envs { resourceKey := commonConfig.BuildResourceKey(string(config.ResourceTypeProduct), env.ProductName, env.EnvName) resourceSpec := &resourceSpec{ @@ -71,6 +87,14 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { } else { logger.Warnf("can not find resource key :%s", resourceKey) } + + clusterID := env.ClusterID + production := false + cluster, ok := clusterMap[clusterID] + if ok { + production = cluster.Production + } + resourceSpec.Spec["production"] = strconv.FormatBool(production) res = append(res, resourceSpec) } -- Gitee From a69b36f6b7d31778800cd4ffd450c00206a1fc2d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 18 Feb 2022 14:03:28 +0800 Subject: [PATCH 1588/2647] code review Signed-off-by: mouuii --- .../service/collaboration_instance.go | 22 +++++++++++++------ .../policy/core/service/policy_binding.go | 1 - 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 85bd2c73..5eb60c54 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -29,6 +29,7 @@ type GetCollaborationUpdateResp struct { New []models.CollaborationMode `json:"new"` Delete []models.CollaborationInstance `json:"delete"` } + type UpdateItem struct { CollaborationMode string `json:"collaboration_mode"` PolicyName string `json:"policy_name"` @@ -37,14 +38,17 @@ type UpdateItem struct { UpdateSpec UpdateSpec `json:"update_spec"` NewSpec NewSpec `json:"new_spec"` } + type DeleteSpec struct { Workflows []models.WorkflowCIItem `json:"workflows"` Products []models.ProductCIItem `json:"products"` } + type UpdateSpec struct { Workflows []UpdateWorkflowItem `json:"workflows"` Products []UpdateProductItem `json:"products"` } + type NewSpec struct { Workflows []models.WorkflowCMItem `json:"workflows"` Products []models.ProductCMItem `json:"products"` @@ -78,6 +82,7 @@ type Product struct { DefaultValues string `json:"defaultValues,omitempty"` ChartValues []*commonservice.RenderChartArg `json:"chartValues,omitempty"` } + type GetCollaborationNewResp struct { Code int64 `json:"code"` Workflow []*Workflow `json:"workflow"` @@ -178,7 +183,7 @@ func getUpdateDiff(cm *models.CollaborationMode, ci *models.CollaborationInstanc } } -func buildPolicyName(projectName, mode string, identityType, userName string) string { +func buildPolicyName(projectName, mode, identityType, userName string) string { return projectName + "-" + mode + "-" + identityType + "-" + userName } @@ -280,6 +285,7 @@ func GetCollaborationUpdate(projectName, uid, identityType, userName string, log } return resp, nil } + func buildName(baseName, modeName, identityType, userName string) string { return modeName + "-" + baseName + "-" + identityType + "-" + userName } @@ -1089,13 +1095,14 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id productRenderSetMap[set.EnvName] = set } for _, product := range newProduct { - if set, ok := productRenderSetMap[product.BaseName]; ok { - product.Vars = set.KVs - product.DefaultValues = set.DefaultValues - product.ChartValues = buildRenderChartArg(set.ChartInfos, product.BaseName) - } else { + set, ok := productRenderSetMap[product.BaseName] + if !ok { return nil, fmt.Errorf("product:%s not exist", product.BaseName) } + + product.Vars = set.KVs + product.DefaultValues = set.DefaultValues + product.ChartValues = buildRenderChartArg(set.ChartInfos, product.BaseName) } } var workNames []string @@ -1130,6 +1137,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id IfSync: ifSync, }, nil } + func GetCollaborationNew(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { updateResp, err := GetCollaborationUpdate(projectName, uid, identityType, userName, logger) if err != nil { @@ -1165,7 +1173,7 @@ func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error } func buildRenderChartArg(chartInfos []*templatemodels.RenderChart, envName string) []*commonservice.RenderChartArg { - ret := make([]*commonservice.RenderChartArg, 0) + ret := make([]*commonservice.RenderChartArg, 0, len(chartInfos)) for _, singleChart := range chartInfos { rcaObj := new(commonservice.RenderChartArg) rcaObj.LoadFromRenderChartModel(singleChart) diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index 1798f890..0122ee52 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -48,7 +48,6 @@ func CreatePolicyBindings(ns string, rbs []*PolicyBinding, logger *zap.SugaredLo } func CreateOrUpdateSystemPolicyBinding(ns string, rb *PolicyBinding, logger *zap.SugaredLogger) error { - obj, err := createPolicyBindingObject(ns, rb, logger) if err != nil { return err -- Gitee From 8affeaf504419d5b139ab8891ba23f1e954b94a6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 14:09:27 +0800 Subject: [PATCH 1589/2647] modify serviceName in warpdrive Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/deploy.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 38621521..db694c81 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -213,22 +213,26 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } } + serviceName := p.Task.ServiceName + serviceName = strings.TrimPrefix(serviceName, p.Task.ContainerName+"_") + p.Task.ServiceName = serviceName if p.Task.ServiceType != setting.HelmDeployType { // get servcie info var ( serviceInfo *types.ServiceTmpl selector labels.Selector ) - serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, p.Task.ProductName, 0) + + serviceInfo, err = p.getService(ctx, serviceName, p.Task.ServiceType, p.Task.ProductName, 0) if err != nil { // Maybe it is a share service, the entity is not under the project - serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, "", 0) + serviceInfo, err = p.getService(ctx, serviceName, p.Task.ServiceType, "", 0) if err != nil { return } } if serviceInfo.WorkloadType == "" { - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() + selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: serviceName}.AsSelector() var deployments []*appsv1.Deployment deployments, err = getter.ListDeployments(p.Task.Namespace, selector, p.kubeClient) @@ -292,7 +296,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * switch serviceInfo.WorkloadType { case setting.StatefulSet: var statefulSet *appsv1.StatefulSet - statefulSet, _, err = getter.GetStatefulSet(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) + statefulSet, _, err = getter.GetStatefulSet(p.Task.Namespace, serviceName, p.kubeClient) if err != nil { return } @@ -318,7 +322,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } case setting.Deployment: var deployment *appsv1.Deployment - deployment, _, err = getter.GetDeployment(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) + deployment, _, err = getter.GetDeployment(p.Task.Namespace, serviceName, p.kubeClient) if err != nil { return } @@ -376,7 +380,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } else { rcsList = append(rcsList, RcsListFromStatefulSets(statefulSets)...) } - p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, rcsList) + p.findHelmAffectedResources(p.Task.Namespace, serviceName, rcsList) p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, p.Task.ServiceName, p.Task.ContainerName, p.Task.Namespace) -- Gitee From 7562c95099b520bd95f63acd490535dda74c7a47 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 14:19:21 +0800 Subject: [PATCH 1590/2647] modify serviceName in warpdrive Signed-off-by: liu deyi --- .../core/service/taskplugin/deploy.go | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index db694c81..b1669fe0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -215,7 +215,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * serviceName := p.Task.ServiceName serviceName = strings.TrimPrefix(serviceName, p.Task.ContainerName+"_") - p.Task.ServiceName = serviceName if p.Task.ServiceType != setting.HelmDeployType { // get servcie info var ( @@ -383,14 +382,14 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * p.findHelmAffectedResources(p.Task.Namespace, serviceName, rcsList) p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, - p.Task.ServiceName, p.Task.ContainerName, p.Task.Namespace) + serviceName, p.Task.ContainerName, p.Task.Namespace) productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) if err != nil { err = errors.WithMessagef( err, "failed to get product %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } @@ -406,7 +405,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * serviceRevisionInProduct := int64(0) var targetContainer *types.Container for _, service := range productInfo.GetServiceMap() { - if service.ServiceName == p.Task.ServiceName { + if service.ServiceName == serviceName { serviceRevisionInProduct = service.Revision for _, container := range service.Containers { if container.Name == p.Task.ContainerName { @@ -419,17 +418,17 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if targetContainer == nil { - err = errors.Errorf("failed to find target container %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to find target container %s from service %s", p.Task.ContainerName, serviceName) return } if targetContainer.ImagePath == nil { - err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, serviceName) return } for _, chartInfo := range renderInfo.ChartInfos { - if chartInfo.ServiceName == p.Task.ServiceName { + if chartInfo.ServiceName == serviceName { renderChart = chartInfo break } @@ -437,23 +436,23 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if renderChart == nil { err = errors.Errorf("failed to update container image in %s/%s,chart not found", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } // use revision of service currently applied in environment instead of the latest revision - path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, + path, errDownload := p.downloadService(pipelineTask.ProductName, serviceName, pipelineTask.StorageURI, serviceRevisionInProduct) if errDownload != nil { p.Log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - serviceRevisionInProduct, p.Task.ServiceName) - path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, + serviceRevisionInProduct, serviceName) + path, errDownload = p.downloadService(pipelineTask.ProductName, serviceName, pipelineTask.StorageURI, 0) if errDownload != nil { err = errors.WithMessagef( errDownload, "failed to download service %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } } @@ -478,7 +477,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to pase image uri %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } @@ -488,12 +487,12 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to replace image uri %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } if replacedValuesYaml == "" { err = errors.Errorf("failed to set new image uri into service's values.yaml %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } @@ -514,12 +513,12 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to replace image uri into helm values %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } if replacedMergedValuesYaml == "" { err = errors.Errorf("failed to set image uri into mreged values.yaml in %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } @@ -530,11 +529,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to create helm client %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) return } - releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) + releaseName := util.GeneHelmReleaseName(p.Task.Namespace, serviceName) ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) @@ -581,7 +580,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", - p.Task.Namespace, p.Task.ServiceName) + p.Task.Namespace, serviceName) done <- false } else { done <- true @@ -600,7 +599,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * //替换环境变量中的chartInfos for _, chartInfo := range renderInfo.ChartInfos { - if chartInfo.ServiceName == p.Task.ServiceName { + if chartInfo.ServiceName == serviceName { chartInfo.ValuesYaml = replacedValuesYaml break } @@ -617,7 +616,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to update renderset info %s/%s, renderset %s", - p.Task.Namespace, p.Task.ServiceName, renderInfo.Name) + p.Task.Namespace, serviceName, renderInfo.Name) } } } @@ -816,8 +815,9 @@ func (p *DeployTaskPlugin) Wait(ctx context.Context) { } timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) - - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() + serviceName := p.Task.ServiceName + serviceName = strings.TrimPrefix(serviceName, p.Task.ContainerName+"_") + selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: serviceName}.AsSelector() for { select { -- Gitee From d8c31db49f9ec7e47dd881d59e9f07de378630ad Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 18 Feb 2022 14:24:38 +0800 Subject: [PATCH 1591/2647] remove file Signed-off-by: mouuii --- result.json | 1022 --------------------------------------------------- 1 file changed, 1022 deletions(-) delete mode 100644 result.json diff --git a/result.json b/result.json deleted file mode 100644 index d87e9d0d..00000000 --- a/result.json +++ /dev/null @@ -1,1022 +0,0 @@ -{ - "all_roles": [ - { - "name": "read-project-only", - "namespace": "" - } - ], - "allow": false, - "allowed_policies": [ - { - "name": "project41-asdf-system-z42", - "namespace": "project41" - }, - { - "name": "read-project-only", - "namespace": "" - } - ], - "allowed_policy_attributive_rules": [ - { - "endpoint": "/api/aslan/environment/environments/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", - "matchAttributes": [ - { - "key": "policy", - "value": "project41-asdf-system-z42-Environment-dev" - } - ], - "method": "DELETE", - "resourceType": "Environment" - } - ], - "allowed_policy_plain_rules": [], - "allowed_policy_rules": [ - { - "endpoint": "/api/aslan/environment/environments/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", - "matchAttributes": [ - { - "key": "policy", - "value": "project41-asdf-system-z42-Environment-dev" - } - ], - "method": "DELETE", - "resourceType": "Environment" - } - ], - "allowed_role_attributive_rules": [ - { - "endpoint": "/api/aslan/environment/configmaps", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/diff/products/?*/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/environments", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/groups", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/groups$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/helm/charts", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/helm/releases", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/services/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/services/", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/workloads", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/workloads$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/export/service", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/kube/events", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/kube/pods/?*/events", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/kube/workloads", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/revision/products", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/sse/pods/?*/containers/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/project/products/?*/services", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/workloads", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v3", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v3/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v3/?*/args", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflow/find/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/directory/workflowTask", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "api/aslan/workflow/workflow", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "api/aslan/workflow/workflow", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Workflow" - } - ], - "allowed_role_plain_rules": [ - { - "endpoint": "/api/aslan/build/build", - "method": "GET" - }, - { - "endpoint": "/api/aslan/build/build/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/build/targets", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/releases", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/releases/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/releases/helm/charts", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/servicenames", - "method": "GET" - }, - { - "endpoint": "/api/aslan/project/products/?*/searching-rules", - "method": "GET" - }, - { - "endpoint": "/api/aslan/project/products/?*/services", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/helm/?*/?*/fileContent", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/helm/?*/?*/filePath", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/helm/?*/?*/serviceModule", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services/?*/k8s", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services/?*/pm", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/test", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/test/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/testdetail", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/workspace/workflow/?*/taskId/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflow/testName/?*", - "method": "GET" - } - ], - "allowed_role_rules": [ - { - "endpoint": "/api/aslan/build/build", - "method": "GET" - }, - { - "endpoint": "/api/aslan/build/build/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/build/targets", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/releases", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/releases/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/releases/helm/charts", - "method": "GET" - }, - { - "endpoint": "/api/aslan/delivery/servicenames", - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/configmaps", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/diff/products/?*/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/environments", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/groups", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/groups$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/helm/charts", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/helm/releases", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/services/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/services/", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/environments/?*/workloads", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)/workloads$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - }, - { - "endpoint": "/api/aslan/environment/export/service", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/kube/events", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/kube/pods/?*/events", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/kube/workloads", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/environment/revision/products", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/sse/pods/?*/containers/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/project/products/?*/searching-rules", - "method": "GET" - }, - { - "endpoint": "/api/aslan/project/products/?*/services", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/project/products/?*/services", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/helm/?*/?*/fileContent", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/helm/?*/?*/filePath", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/helm/?*/?*/serviceModule", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services/?*/k8s", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/services/?*/pm", - "method": "GET" - }, - { - "endpoint": "/api/aslan/service/workloads", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/test", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/test/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/testdetail", - "method": "GET" - }, - { - "endpoint": "/api/aslan/testing/workspace/workflow/?*/taskId/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v3", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v3/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/v3/?*/args", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflow/find/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflow/testName/?*", - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "/api/directory/workflowTask", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "api/aslan/workflow/workflow", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET" - }, - { - "endpoint": "api/aslan/workflow/workflow", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Workflow" - } - ], - "allowed_roles": [ - { - "name": "read-project-only", - "namespace": "" - } - ], - "bearer_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiejQyIiwiZW1haWwiOiJ6NEBxcS5jb20iLCJ1aWQiOiI4YTRjNGVjMS04ZmQ4LTExZWMtODk1YS1kZTRmYzVkYTQzMDEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ6NDIiLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6InN5c3RlbSIsInVzZXJfaWQiOiJ6NDIifSwiYXVkIjoiemFkaWciLCJleHAiOjE2NDUxODI1OTZ9.kFCdTG9SfPtDMbaiYfgfgRKY_hY2-lHrimJ6YK8J4xA", - "bearer_token_in_header": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiejQyIiwiZW1haWwiOiJ6NEBxcS5jb20iLCJ1aWQiOiI4YTRjNGVjMS04ZmQ4LTExZWMtODk1YS1kZTRmYzVkYTQzMDEiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ6NDIiLCJmZWRlcmF0ZWRfY2xhaW1zIjp7ImNvbm5lY3Rvcl9pZCI6InN5c3RlbSIsInVzZXJfaWQiOiJ6NDIifSwiYXVkIjoiemFkaWciLCJleHAiOjE2NDUxODI1OTZ9.kFCdTG9SfPtDMbaiYfgfgRKY_hY2-lHrimJ6YK8J4xA", - "claims": { - "aud": "zadig", - "email": "z4@qq.com", - "exp": 1645182596, - "federated_claims": { - "connector_id": "system", - "user_id": "z42" - }, - "name": "z42", - "preferred_username": "z42", - "uid": "8a4c4ec1-8fd8-11ec-895a-de4fc5da4301" - }, - "envs": { - "COMMAND_MODE": "unix2003", - "GO111MODULE": "on", - "GOPATH": "/Users/z/go", - "GOROOT": "/opt/homebrew/Cellar/go/1.17.3/libexec", - "HOME": "/Users/z", - "HOMEBREW_CELLAR": "/opt/homebrew/Cellar", - "HOMEBREW_PREFIX": "/opt/homebrew", - "HOMEBREW_REPOSITORY": "/opt/homebrew", - "INFOPATH": "/opt/homebrew/share/info:", - "LC_CTYPE": "en_US.UTF-8", - "LESS": "-R", - "LOGIN_SHELL": "1", - "LOGNAME": "z", - "LSCOLORS": "Gxfxcxdxbxegedabagacad", - "MANPATH": "/opt/homebrew/share/man::", - "OLDPWD": "/Users/z/Documents/GitHub/mzadig", - "PAGER": "less", - "PATH": "/opt/homebrew/Cellar/go/1.17.3/libexec/bin:/Users/z/go/bin:/opt/homebrew/opt/gnu-sed/libexec/gnubin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/z/go/bin", - "PWD": "/Users/z/Documents/GitHub/mzadig", - "SECRET_KEY": "9F11B4E503C7F2B577E5F9366BDDAB64", - "SHELL": "/bin/zsh", - "SHLVL": "1", - "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.5bXC9yvJnk/Listeners", - "TERM": "xterm-256color", - "TERMINAL_EMULATOR": "JetBrains-JediTerm", - "TERM_SESSION_ID": "ce8d8f7c-4d2e-413a-926a-7fe33e0b1238", - "TMPDIR": "/var/folders/ww/12zzzwxd2g50c516yxztbn140000gn/T/", - "USER": "z", - "XPC_FLAGS": "0x0", - "XPC_SERVICE_NAME": "0", - "ZSH": "/Users/z/.oh-my-zsh", - "_": "/opt/homebrew/bin/opa", - "__CFBundleIdentifier": "com.jetbrains.goland", - "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0", - "__INTELLIJ_COMMAND_HISTFILE__": "/Users/z/Library/Caches/JetBrains/GoLand2021.2/terminal/history/mzadig-history112" - }, - "is_authenticated": true, - "project_name": "project41", - "response": { - "allowed": false, - "http_status": 403 - }, - "secret": "9F11B4E503C7F2B577E5F9366BDDAB64", - "url_is_registered": true, - "user_allowed_projects": [], - "user_matched_policy_rule": [ - { - "endpoint": "/api/aslan/environment/environments/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", - "matchAttributes": [ - { - "key": "policy", - "value": "project41-asdf-system-z42-Environment-dev" - } - ], - "method": "DELETE", - "resourceType": "Environment" - } - ], - "user_matched_policy_rule_for_filtering": [], - "user_matched_role_rule": [ - { - "endpoint": "/api/aslan/environment/environments/?*", - "idRegex": "api/aslan/environment/environments/([\\w\\W]+?)$", - "matchAttributes": [ - { - "key": "placeholder", - "value": "placeholder" - } - ], - "method": "GET", - "resourceType": "Environment" - } - ], - "user_matched_role_rule_for_filtering": [], - "user_policy_allowed_resources": [], - "user_projects": [ - "project41" - ], - "user_role_allowed_resources": [], - "user_visible_projects": [ - "project41" - ] -} -- Gitee From ccd4419767e048d29fbb3454c37c61fcfa242793 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 18 Feb 2022 14:44:03 +0800 Subject: [PATCH 1592/2647] set the openapi token Signed-off-by: ddh27 --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 12 +++++----- .../internal/repository/orm/db.go | 23 +++++++++++++++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 3ae7954c..5df52cba 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -51,9 +51,9 @@ func V190ToV1100() error { return fmt.Errorf("failed to migrate data in `zadig.module_testing`: %s", err) } - log.Info("InitDBAndEditTables: ADD cloumn from mysql table `user`.") - if err := orm.InitDBAndEditTables(orm.DbEditActionAdd); err != nil { - return fmt.Errorf("InitDBAndEditTables: failed to ADD cloumn from mysql table `user`: %s", err) + log.Info("UpdateUserDBTables: ADD cloumn from mysql table `user`.") + if err := orm.UpdateUserDBTables(orm.DbEditActionAdd); err != nil { + return fmt.Errorf("UpdateUserDBTables: failed to ADD cloumn from mysql table `user`: %s", err) } return nil } @@ -62,9 +62,9 @@ func V190ToV1100() error { func V1100ToV190() error { log.Info("Rollback data from 1.10.0 to 1.9.0") - log.Info("InitDBAndEditTables: drop cloumn from mysql table `user`.") - if err := orm.InitDBAndEditTables(orm.DbEditActionDrop); err != nil { - return fmt.Errorf("InitDBAndEditTables: failed to drop cloumn from mysql table `user`: %s", err) + log.Info("UpdateUserDBTables: drop cloumn from mysql table `user`.") + if err := orm.UpdateUserDBTables(orm.DbEditActionDrop); err != nil { + return fmt.Errorf("UpdateUserDBTables: failed to drop cloumn from mysql table `user`: %s", err) } return nil } diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/db.go b/pkg/cli/upgradeassistant/internal/repository/orm/db.go index e9df7215..3fa9c9cd 100644 --- a/pkg/cli/upgradeassistant/internal/repository/orm/db.go +++ b/pkg/cli/upgradeassistant/internal/repository/orm/db.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package orm import ( @@ -23,7 +39,7 @@ var addmysql []byte //go:embed dropmysql.sql var dropmysql []byte -func InitDBAndEditTables(action DbEditAction) error { +func UpdateUserDBTables(action DbEditAction) error { var mysql []byte switch action { case DbEditActionAdd: @@ -43,8 +59,5 @@ func InitDBAndEditTables(action DbEditAction) error { } defer db.Close() _, err = db.Exec(string(mysql)) - if err != nil { - return err - } - return nil + return err } -- Gitee From 347327598ee6f016c8782bcf4a871d0e9cafab58 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Feb 2022 19:10:24 +0800 Subject: [PATCH 1593/2647] workflow & build concurrency settings Signed-off-by: Min Min --- .../core/common/repository/models/settings.go | 14 ++++ .../common/repository/mongodb/settings.go | 71 +++++++++++++++++++ pkg/microservice/aslan/core/service.go | 1 + .../aslan/core/system/handler/concurrency.go | 21 ++++++ .../aslan/core/system/handler/router.go | 7 ++ .../aslan/core/system/service/concurrency.go | 16 +++++ .../aslan/core/system/service/types.go | 5 ++ 7 files changed, 135 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/repository/models/settings.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/settings.go create mode 100644 pkg/microservice/aslan/core/system/handler/concurrency.go create mode 100644 pkg/microservice/aslan/core/system/service/concurrency.go diff --git a/pkg/microservice/aslan/core/common/repository/models/settings.go b/pkg/microservice/aslan/core/common/repository/models/settings.go new file mode 100644 index 00000000..43085ce9 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/settings.go @@ -0,0 +1,14 @@ +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type SystemSetting struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + WorkflowConcurrency int64 `bson:"workflow_concurrency" json:"workflow_concurrency"` + BuildConcurrency int64 `bson:"build_concurrency" json:"build_concurrency"` + UpdateTime int64 `bson:"update_time" json:"update_time"` +} + +func (SystemSetting) TableName() string { + return "system_setting" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go new file mode 100644 index 00000000..dd4710ba --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go @@ -0,0 +1,71 @@ +package mongodb + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type SystemSettingColl struct { + *mongo.Collection + + coll string +} + +func NewSystemSettingColl() *SystemSettingColl { + name := models.SystemSetting{}.TableName() + return &SystemSettingColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *SystemSettingColl) GetCollectionName() string { + return c.coll +} + +func (c *SystemSettingColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *SystemSettingColl) Get() (*models.SystemSetting, error) { + query := bson.M{} + resp := &models.SystemSetting{} + + err := c.FindOne(context.TODO(), query).Decode(resp) + return resp, err +} + +func (c *SystemSettingColl) InitSystemSettings() error { + _, err := c.Get() + // if we didn't find anything + if err != nil { + return c.Create(setting.LocalClusterID, &models.SystemSetting{ + WorkflowConcurrency: 2, + BuildConcurrency: 5, + }) + } + return nil +} + +func (c *SystemSettingColl) Create(id string, args *models.SystemSetting) error { + var objectID primitive.ObjectID + if id != "" { + objectID, _ = primitive.ObjectIDFromHex(id) + } else { + objectID = primitive.NewObjectID() + } + + args.UpdateTime = time.Now().Unix() + + query := bson.M{"_id": objectID} + change := bson.M{"$set": args} + _, err := c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 63fbef62..568a14c2 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -226,6 +226,7 @@ func initDatabase() { // 初始化数据 commonrepo.NewInstallColl().InitInstallData(systemservice.InitInstallMap()) commonrepo.NewBasicImageColl().InitBasicImageData(systemservice.InitbasicImageInfos()) + commonrepo.NewSystemSettingColl().InitSystemSettings() if err := commonrepo.NewS3StorageColl().InitData(); err != nil { log.Warnf("Failed to init S3 data: %s", err) diff --git a/pkg/microservice/aslan/core/system/handler/concurrency.go b/pkg/microservice/aslan/core/system/handler/concurrency.go new file mode 100644 index 00000000..8f96b5ad --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/concurrency.go @@ -0,0 +1,21 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetWorkflowConcurrency(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetWorkflowConcurrency() +} + +func UpdateWorkflowConcurrency(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index f6c0b081..2a5bc110 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -124,6 +124,13 @@ func (*Router) Inject(router *gin.RouterGroup) { capacity.POST("/clean", CleanCache) } + // workflow concurrency settings + concurrency := router.Group("concurrency") + { + concurrency.GET("/workflow", GetWorkflowConcurrency) + concurrency.POST("/workflow", UpdateWorkflowConcurrency) + } + // --------------------------------------------------------------------------------------- // 自定义镜像管理接口 // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go new file mode 100644 index 00000000..9f292488 --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -0,0 +1,16 @@ +package service + +import ( + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" +) + +func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { + setting, err := commonrepo.NewSystemSettingColl().Get() + if err != nil { + return nil, err + } + return &WorkflowConcurrencySettings{ + WorkflowConcurrency: setting.WorkflowConcurrency, + BuildConcurrency: setting.BuildConcurrency, + }, nil +} diff --git a/pkg/microservice/aslan/core/system/service/types.go b/pkg/microservice/aslan/core/system/service/types.go index ab4aa310..917fe8db 100644 --- a/pkg/microservice/aslan/core/system/service/types.go +++ b/pkg/microservice/aslan/core/system/service/types.go @@ -22,3 +22,8 @@ type ExternalSystemDetail struct { Server string `json:"server"` APIToken string `json:"api_token,omitempty"` } + +type WorkflowConcurrencySettings struct { + WorkflowConcurrency int64 `json:"workflow_concurrency"` + BuildConcurrency int64 `json:"build_concurrency"` +} -- Gitee From 8b617ad092a12017d29fab49cff3b6e637ebb51f Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 15 Feb 2022 11:16:06 +0800 Subject: [PATCH 1594/2647] finish APIs & dbs Signed-off-by: Min Min --- .../core/common/repository/mongodb/settings.go | 11 +++++++++++ .../aslan/core/system/handler/concurrency.go | 15 +++++++++++++++ .../aslan/core/system/service/concurrency.go | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go index dd4710ba..448f44f0 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go @@ -42,6 +42,17 @@ func (c *SystemSettingColl) Get() (*models.SystemSetting, error) { return resp, err } +func (c *SystemSettingColl) UpdateConcurrencySetting(workflowConcurrency, buildConcurrency int64) error { + id, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) + change := bson.M{"$set": bson.M{ + "workflow_concurrency": workflowConcurrency, + "build_concurrency": buildConcurrency, + }} + query := bson.M{"_id": id} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *SystemSettingColl) InitSystemSettings() error { _, err := c.Get() // if we didn't find anything diff --git a/pkg/microservice/aslan/core/system/handler/concurrency.go b/pkg/microservice/aslan/core/system/handler/concurrency.go index 8f96b5ad..4e53d86c 100644 --- a/pkg/microservice/aslan/core/system/handler/concurrency.go +++ b/pkg/microservice/aslan/core/system/handler/concurrency.go @@ -1,6 +1,8 @@ package handler import ( + "errors" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" @@ -18,4 +20,17 @@ func UpdateWorkflowConcurrency(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + args := new(service.WorkflowConcurrencySettings) + if err := c.BindJSON(args); err != nil { + ctx.Err = err + return + } + + // args validation + if args.BuildConcurrency <= 0 || args.WorkflowConcurrency <= 0 { + ctx.Err = errors.New("concurrency cannot be less than 1") + return + } + + ctx.Err = service.UpdateWorkflowConcurrency(args.WorkflowConcurrency, args.BuildConcurrency) } diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go index 9f292488..4e983da2 100644 --- a/pkg/microservice/aslan/core/system/service/concurrency.go +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -14,3 +14,7 @@ func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { BuildConcurrency: setting.BuildConcurrency, }, nil } + +func UpdateWorkflowConcurrency(workflowConcurrency, buildConcurrency int64) error { + return commonrepo.NewSystemSettingColl().UpdateConcurrencySetting(workflowConcurrency, buildConcurrency) +} -- Gitee From 9e091a3600f306625e4bd0ece2fb8e077bba313c Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 15 Feb 2022 15:18:59 +0800 Subject: [PATCH 1595/2647] added warpdrive rescale logic Signed-off-by: Min Min --- pkg/microservice/aslan/core/service.go | 1 + .../aslan/core/system/service/concurrency.go | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 568a14c2..f69000f5 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -195,6 +195,7 @@ func initDatabase() { commonrepo.NewStrategyColl(), commonrepo.NewStatsColl(), commonrepo.NewSubscriptionColl(), + commonrepo.NewSystemSettingColl(), commonrepo.NewTaskColl(), commonrepo.NewTestTaskStatColl(), commonrepo.NewTestingColl(), diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go index 4e983da2..aec204c8 100644 --- a/pkg/microservice/aslan/core/system/service/concurrency.go +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -1,7 +1,15 @@ package service import ( + "go.uber.org/zap" + + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/updater" ) func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { @@ -15,6 +23,17 @@ func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { }, nil } -func UpdateWorkflowConcurrency(workflowConcurrency, buildConcurrency int64) error { - return commonrepo.NewSystemSettingColl().UpdateConcurrencySetting(workflowConcurrency, buildConcurrency) +func UpdateWorkflowConcurrency(workflowConcurrency, buildConcurrency int64, log *zap.SugaredLogger) error { + // first update system configuration table + err := commonrepo.NewSystemSettingColl().UpdateConcurrencySetting(workflowConcurrency, buildConcurrency) + if err != nil { + log.Errorf("Failed to update system settings, the error is: %s", err) + return err + } + // then we update the warpdrive deployment, where the workflow concurrency happens + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + return e.ErrScaleService.AddErr(err) + } + return updater.ScaleDeployment(config.Namespace(), configbase.WarpDriveServiceName(), int(workflowConcurrency), kubeClient) } -- Gitee From 4c1cebf7f3ce27975effc1e601f99a5496afb62c Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 15 Feb 2022 15:24:21 +0800 Subject: [PATCH 1596/2647] avoid package collide Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/concurrency.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go index aec204c8..8debdcf5 100644 --- a/pkg/microservice/aslan/core/system/service/concurrency.go +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -13,13 +13,13 @@ import ( ) func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { - setting, err := commonrepo.NewSystemSettingColl().Get() + configuration, err := commonrepo.NewSystemSettingColl().Get() if err != nil { return nil, err } return &WorkflowConcurrencySettings{ - WorkflowConcurrency: setting.WorkflowConcurrency, - BuildConcurrency: setting.BuildConcurrency, + WorkflowConcurrency: configuration.WorkflowConcurrency, + BuildConcurrency: configuration.BuildConcurrency, }, nil } -- Gitee From ea808a9c79450166a7299ac9a97759471dfaddb4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 15 Feb 2022 16:49:15 +0800 Subject: [PATCH 1597/2647] add wd support for build concurrency Signed-off-by: Min Min --- .../aslan/core/common/repository/models/queue.go | 3 +++ .../aslan/core/common/service/config_payload.go | 8 +++++++- .../core/service/taskcontroller/task_handler.go | 11 +++++------ .../core/service/types/task/config_payload.go | 3 +++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 4c299d8c..93d630ef 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -150,6 +150,9 @@ type ConfigPayload struct { // RegistryID is the id of product registry RegistryID string `json:"registry_id"` + + // build concurrency settings + BuildConcurrency int64 `json:"build_concurrency"` } type AslanConfig struct { diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 5a17f53b..bf94e036 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -66,7 +66,8 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { JenkinsBuildConfig: models.JenkinsBuildConfig{ JenkinsBuildImage: config.JenkinsImage(), }, - AesKey: crypto.GetAesKey(), + AesKey: crypto.GetAesKey(), + BuildConcurrency: 5, } githubApps, _ := mongodb.NewGithubAppColl().Find() @@ -87,6 +88,11 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { payload.Proxy = *proxies[0] } + concurrencySettings, _ := mongodb.NewSystemSettingColl().Get() + if concurrencySettings != nil { + payload.BuildConcurrency = concurrencySettings.BuildConcurrency + } + privateKeys, _ := mongodb.NewPrivateKeyColl().List(&mongodb.PrivateKeyArgs{}) if len(privateKeys) != 0 { payload.PrivateKeys = privateKeys diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 5a812686..0b996300 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -265,7 +265,7 @@ func (h *ExecHandler) SendNotification() { } } -func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { +func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage, concurrency int64) { xl.Infof("start to execute pipeline stage: %s at position: %d", stage.TaskType, stagePosition) pluginInitiator, ok := h.TaskPlugins[stage.TaskType] if !ok { @@ -282,9 +282,8 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { // Default worker concurrency is 1, run tasks sequentially var workerConcurrency = 1 if runParallel { - // MaxWorkerInParallel is 5 for now - if len(stage.SubTasks) > maxWorkerInParallel { - workerConcurrency = maxWorkerInParallel + if len(stage.SubTasks) > int(concurrency) { + workerConcurrency = int(concurrency) } else { workerConcurrency = len(stage.SubTasks) } @@ -347,7 +346,7 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe // Stage之间仅支持串行 for stagePosition, stage := range pipelineTask.Stages { if !stage.AfterAll { - h.runStage(stagePosition, stage) + h.runStage(stagePosition, stage, pipelineTask.ConfigPayload.BuildConcurrency) // 如果一个Stage执行失败了,跳出执行循环,并且更新pipelinetask状态为失败,发送ACK,并返回 if stage.Status == config.StatusFailed || stage.Status == config.StatusCancelled || stage.Status == config.StatusTimeout { break @@ -378,7 +377,7 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe } } } - h.runStage(stagePosition, stage) + h.runStage(stagePosition, stage, pipelineTask.ConfigPayload.BuildConcurrency) } } diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 61f65238..aac740dc 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -55,6 +55,9 @@ type ConfigPayload struct { K8SClusters []*K8SCluster `json:"k8s_clusters"` RegistryID string `json:"registry_id"` + + // build concurrency settings + BuildConcurrency int64 `json:"build_concurrency"` } func (cp *ConfigPayload) GetGitKnownHost() string { -- Gitee From 4b64da155679c334d95f15a949a13fb47cdb9b8a Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 16 Feb 2022 16:16:14 +0800 Subject: [PATCH 1598/2647] minor bugfix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/handler/concurrency.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/handler/concurrency.go b/pkg/microservice/aslan/core/system/handler/concurrency.go index 4e53d86c..b96783f4 100644 --- a/pkg/microservice/aslan/core/system/handler/concurrency.go +++ b/pkg/microservice/aslan/core/system/handler/concurrency.go @@ -32,5 +32,5 @@ func UpdateWorkflowConcurrency(c *gin.Context) { return } - ctx.Err = service.UpdateWorkflowConcurrency(args.WorkflowConcurrency, args.BuildConcurrency) + ctx.Err = service.UpdateWorkflowConcurrency(args.WorkflowConcurrency, args.BuildConcurrency, ctx.Logger) } -- Gitee From 922266b4e6a2a5081e591208ffbbf56a45e0d225 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 16 Feb 2022 18:12:30 +0800 Subject: [PATCH 1599/2647] fixed a minor bug where user can scale down warpdrive when the pod is running Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/concurrency.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go index 8debdcf5..600dbbc9 100644 --- a/pkg/microservice/aslan/core/system/service/concurrency.go +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -1,11 +1,14 @@ package service import ( + "errors" + "go.uber.org/zap" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" @@ -24,6 +27,11 @@ func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { } func UpdateWorkflowConcurrency(workflowConcurrency, buildConcurrency int64, log *zap.SugaredLogger) error { + // check if there are running tasks + tasks := workflowservice.RunningPipelineTasks() + if len(tasks) > 0 { + return errors.New("workflow settings must be set when NO task is running") + } // first update system configuration table err := commonrepo.NewSystemSettingColl().UpdateConcurrencySetting(workflowConcurrency, buildConcurrency) if err != nil { -- Gitee From 2fc3f519b440311750eb6df9fae5f85059828511 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 16 Feb 2022 18:20:28 +0800 Subject: [PATCH 1600/2647] liscense Signed-off-by: Min Min --- .../core/common/repository/models/settings.go | 16 ++++++++++++++++ .../core/common/repository/mongodb/settings.go | 16 ++++++++++++++++ .../aslan/core/system/handler/concurrency.go | 16 ++++++++++++++++ .../aslan/core/system/service/concurrency.go | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/settings.go b/pkg/microservice/aslan/core/common/repository/models/settings.go index 43085ce9..d51d4cdc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/settings.go +++ b/pkg/microservice/aslan/core/common/repository/models/settings.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import "go.mongodb.org/mongo-driver/bson/primitive" diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go index 448f44f0..4870590d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package mongodb import ( diff --git a/pkg/microservice/aslan/core/system/handler/concurrency.go b/pkg/microservice/aslan/core/system/handler/concurrency.go index b96783f4..b6c9d4b1 100644 --- a/pkg/microservice/aslan/core/system/handler/concurrency.go +++ b/pkg/microservice/aslan/core/system/handler/concurrency.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go index 600dbbc9..38a56d45 100644 --- a/pkg/microservice/aslan/core/system/service/concurrency.go +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( -- Gitee From 50873fe9764f44f56a9fd1354b6b7a6bc940c23e Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 17 Feb 2022 15:22:25 +0800 Subject: [PATCH 1601/2647] add logic in init to persist warpdrive replicas Signed-off-by: Min Min --- pkg/cli/initconfig/cmd/init.go | 21 ++++++++++++++ .../core/service/taskcontroller/const.go | 22 --------------- pkg/shared/client/aslan/workflow.go | 28 +++++++++++++++++++ 3 files changed, 49 insertions(+), 22 deletions(-) delete mode 100644 pkg/microservice/warpdrive/core/service/taskcontroller/const.go create mode 100644 pkg/shared/client/aslan/workflow.go diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 6b465f65..45e749e9 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -31,7 +31,9 @@ import ( "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/shared/client/user" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" ) @@ -106,9 +108,28 @@ func initSystemConfig() error { return err } + if err := scaleWarpdrive(); err != nil { + log.Errorf("scale warpdrive err: %s", err) + return err + } + return nil } +func scaleWarpdrive() error { + cfg, err := aslan.New(config.AslanServiceAddress()).GetWorkflowConcurrencySetting() + if err == nil { + client, err := kubeclient.GetKubeClient(config.HubServerServiceAddress(), setting.LocalClusterID) + if err != nil { + return err + } + return updater.ScaleDeployment(config.Namespace(), config.WarpDriveServiceName(), int(cfg.WorkflowConcurrency), client) + } + + log.Errorf("Failed to get workflow concurrency settings, error: %s", err) + return err +} + func presetSystemAdmin(email string, password, domain string) (string, error) { r, err := user.New().SearchUser(&user.SearchUserArgs{Account: setting.PresetAccount}) if err != nil { diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/const.go b/pkg/microservice/warpdrive/core/service/taskcontroller/const.go deleted file mode 100644 index e7de59fd..00000000 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/const.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package taskcontroller - -const ( - // MaxWorkerInParallel is max worker concurrency - maxWorkerInParallel = 5 -) diff --git a/pkg/shared/client/aslan/workflow.go b/pkg/shared/client/aslan/workflow.go new file mode 100644 index 00000000..b03996d5 --- /dev/null +++ b/pkg/shared/client/aslan/workflow.go @@ -0,0 +1,28 @@ +package aslan + +import ( + "encoding/json" + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type workflowConcurrencySettingResp struct { + WorkflowConcurrency int64 `json:"workflow_concurrency"` + BuildConcurrency int64 `json:"build_concurrency"` +} + +func (c *Client) GetWorkflowConcurrencySetting() (*workflowConcurrencySettingResp, error) { + url := "/system/concurrency/workflow" + + resp := &workflowConcurrencySettingResp{} + res, err := c.Get(url, httpclient.SetResult(resp)) + if err != nil { + errorMessage := new(ErrorMessage) + err := json.Unmarshal(res.Body(), errorMessage) + if err != nil { + return nil, fmt.Errorf("failed to get workflow concurrency settings, error: %s", err) + } + } + return resp, nil +} -- Gitee From be241dce5eb956a24e9edaeba6d9d4e0b0cc3510 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 17 Feb 2022 15:22:54 +0800 Subject: [PATCH 1602/2647] copyright stuff Signed-off-by: Min Min --- pkg/shared/client/aslan/workflow.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/shared/client/aslan/workflow.go b/pkg/shared/client/aslan/workflow.go index b03996d5..b7812e6c 100644 --- a/pkg/shared/client/aslan/workflow.go +++ b/pkg/shared/client/aslan/workflow.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( -- Gitee From b26f4ed1b8f1a99dfbde24f210d4c28111533541 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 18 Feb 2022 14:50:43 +0800 Subject: [PATCH 1603/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/settings.go | 4 ++-- pkg/microservice/aslan/core/common/service/render.go | 6 +++--- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- pkg/microservice/aslan/core/service/service/helm.go | 2 +- pkg/microservice/aslan/core/service/service/pm.go | 4 ++-- pkg/microservice/aslan/core/service/service/service.go | 4 ++-- .../aslan/core/system/service/basic_images.go | 2 +- .../aslan/core/system/service/external_link.go | 2 +- .../aslan/core/system/service/external_system.go | 2 +- pkg/microservice/aslan/core/system/service/install.go | 2 +- .../aslan/core/system/service/private_key.go | 10 +++++----- pkg/microservice/aslan/core/system/service/proxy.go | 2 +- pkg/microservice/aslan/core/system/service/registry.go | 10 +++++----- .../core/workflow/service/webhook/trigger_yaml.go | 2 +- .../workflow/service/workflow/pipeline_controller.go | 2 +- .../core/workflow/service/workflow/pipeline_queue.go | 2 +- .../core/workflow/service/workflow/pipeline_task.go | 2 +- .../aslan/core/workflow/service/workflow/workflow.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 6 +++--- .../aslan/core/workflow/testing/service/it_report.go | 2 +- pkg/microservice/reaper/executor/executor.go | 2 +- pkg/tool/codehub/signer.go | 4 ++-- pkg/tool/kube/patcher/patcher.go | 2 +- pkg/util/fs/fs.go | 4 ++-- 24 files changed, 42 insertions(+), 42 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go index 4870590d..21634eb8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go @@ -73,7 +73,7 @@ func (c *SystemSettingColl) InitSystemSettings() error { _, err := c.Get() // if we didn't find anything if err != nil { - return c.Create(setting.LocalClusterID, &models.SystemSetting{ + return c.CreateOrUpdate(setting.LocalClusterID, &models.SystemSetting{ WorkflowConcurrency: 2, BuildConcurrency: 5, }) @@ -81,7 +81,7 @@ func (c *SystemSettingColl) InitSystemSettings() error { return nil } -func (c *SystemSettingColl) Create(id string, args *models.SystemSetting) error { +func (c *SystemSettingColl) CreateOrUpdate(id string, args *models.SystemSetting) error { var objectID primitive.ObjectID if id != "" { objectID, _ = primitive.ObjectIDFromHex(id) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index a695162c..1c147d4c 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -214,7 +214,7 @@ func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error return e.ErrCreateRenderSet.AddDesc(err.Error()) } if err := commonrepo.NewRenderSetColl().Create(args); err != nil { - errMsg := fmt.Sprintf("[RenderSet.Create] %s error: %v", args.Name, err) + errMsg := fmt.Sprintf("[RenderSet.CreateOrUpdate] %s error: %v", args.Name, err) log.Error(errMsg) return e.ErrCreateRenderSet.AddDesc(errMsg) } @@ -239,7 +239,7 @@ func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) e return e.ErrCreateRenderSet.AddDesc(err.Error()) } if err := commonrepo.NewRenderSetColl().Create(args); err != nil { - errMsg := fmt.Sprintf("[RenderSet.Create] %s error: %v", args.Name, err) + errMsg := fmt.Sprintf("[RenderSet.CreateOrUpdate] %s error: %v", args.Name, err) log.Error(errMsg) return e.ErrCreateRenderSet.AddDesc(errMsg) } @@ -534,7 +534,7 @@ func ensureRenderSetArgs(args *commonmodels.RenderSet) error { } log := log.SugaredLogger() if err := IsAllKeyCovered(args, log); err != nil { - return fmt.Errorf("[RenderSet.Create] %s error: %v", args.Name, err) + return fmt.Errorf("[RenderSet.CreateOrUpdate] %s error: %v", args.Name, err) } // 设置新的版本号 diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 8ea435ed..734223a4 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -122,7 +122,7 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err err = templaterepo.NewProductColl().Create(args) if err != nil { - log.Errorf("ProductTmpl.Create error: %v", err) + log.Errorf("ProductTmpl.CreateOrUpdate error: %v", err) return e.ErrCreateProduct.AddDesc(err.Error()) } @@ -136,7 +136,7 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err }, log) if err != nil { - log.Errorf("ProductTmpl.Create error: %v", err) + log.Errorf("ProductTmpl.CreateOrUpdate error: %v", err) // 创建渲染集失败,删除产品模板 return e.ErrCreateProduct.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 154145f6..93b6903b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1147,6 +1147,6 @@ func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, ChartInfos: mixtureChartInfos, }, log, ); err != nil { - log.Errorf("helmService.Create CreateHelmRenderSet error: %v", err) + log.Errorf("helmService.CreateOrUpdate CreateHelmRenderSet error: %v", err) } } diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index 4b2012af..138b50dc 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -95,7 +95,7 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug } if err := commonrepo.NewServiceColl().Create(serviceObj); err != nil { - log.Errorf("pmService.Create %s error: %v", args.ServiceTmplObject.ServiceName, err) + log.Errorf("pmService.CreateOrUpdate %s error: %v", args.ServiceTmplObject.ServiceName, err) return e.ErrCreateTemplate.AddDesc(err.Error()) } @@ -103,7 +103,7 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: args.Build.Name, ProductName: args.Build.ProductName}) if err != nil { if err := commonservice.CreateBuild(username, args.Build, log); err != nil { - log.Errorf("pmService.Create build %s error: %v", args.Build.Name, err) + log.Errorf("pmService.CreateOrUpdate build %s error: %v", args.Build.Name, err) if err2 := commonrepo.NewServiceColl().Delete(args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Type, args.ServiceTmplObject.ProductName, "", rev); err2 != nil { log.Errorf("pmService.delete %s error: %v", args.ServiceTmplObject.ServiceName, err2) } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index a62f3967..ee673f3c 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -627,7 +627,7 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za } if err := commonrepo.NewServiceColl().Create(args); err != nil { - log.Errorf("ServiceTmpl.Create %s error: %v", args.ServiceName, err) + log.Errorf("ServiceTmpl.CreateOrUpdate %s error: %v", args.ServiceName, err) return e.ErrCreateTemplate.AddDesc(err.Error()) } return nil @@ -718,7 +718,7 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap } if err := commonrepo.NewServiceColl().Create(args); err != nil { - log.Errorf("ServiceTmpl.Create %s error: %v", args.ServiceName, err) + log.Errorf("ServiceTmpl.CreateOrUpdate %s error: %v", args.ServiceName, err) return nil, e.ErrCreateTemplate.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/system/service/basic_images.go b/pkg/microservice/aslan/core/system/service/basic_images.go index 1fe3c19d..562f5cbd 100644 --- a/pkg/microservice/aslan/core/system/service/basic_images.go +++ b/pkg/microservice/aslan/core/system/service/basic_images.go @@ -94,7 +94,7 @@ func CreateBasicImage(args *commonmodels.BasicImage, log *zap.SugaredLogger) err err = commonrepo.NewBasicImageColl().Create(args) if err != nil { - log.Errorf("BasicImage.Create error: %v", err) + log.Errorf("BasicImage.CreateOrUpdate error: %v", err) return e.ErrCreateBasicImage } return nil diff --git a/pkg/microservice/aslan/core/system/service/external_link.go b/pkg/microservice/aslan/core/system/service/external_link.go index 812e3a01..f50538a2 100644 --- a/pkg/microservice/aslan/core/system/service/external_link.go +++ b/pkg/microservice/aslan/core/system/service/external_link.go @@ -36,7 +36,7 @@ func ListExternalLinks(log *zap.SugaredLogger) ([]*commonmodels.ExternalLink, er func CreateExternalLink(args *commonmodels.ExternalLink, log *zap.SugaredLogger) error { err := commonrepo.NewExternalLinkColl().Create(args) if err != nil { - log.Errorf("ExternalLink.Create error: %s", err) + log.Errorf("ExternalLink.CreateOrUpdate error: %s", err) return e.ErrCreateExternalLink.AddErr(err) } return nil diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go index 76c2f97c..64bd98ef 100644 --- a/pkg/microservice/aslan/core/system/service/external_system.go +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -31,7 +31,7 @@ func CreateExternalSystem(args *ExternalSystemDetail, log *zap.SugaredLogger) er APIToken: args.APIToken, }) if err != nil { - log.Errorf("Create external system error: %s", err) + log.Errorf("CreateOrUpdate external system error: %s", err) return e.ErrCreateExternalLink.AddErr(err) } return nil diff --git a/pkg/microservice/aslan/core/system/service/install.go b/pkg/microservice/aslan/core/system/service/install.go index 886be26c..2d5ec30b 100644 --- a/pkg/microservice/aslan/core/system/service/install.go +++ b/pkg/microservice/aslan/core/system/service/install.go @@ -31,7 +31,7 @@ func CreateInstall(args *commonmodels.Install, log *zap.SugaredLogger) error { err := commonrepo.NewInstallColl().Create(args) args.Name = strings.TrimSpace(args.Name) if err != nil { - log.Errorf("Install.Create error: %v", err) + log.Errorf("Install.CreateOrUpdate error: %v", err) return e.ErrCreateInstall } diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 847992c8..55bdf1e8 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -59,7 +59,7 @@ func CreatePrivateKey(args *commonmodels.PrivateKey, log *zap.SugaredLogger) err err := commonrepo.NewPrivateKeyColl().Create(args) if err != nil { - log.Errorf("PrivateKey.Create error: %v", err) + log.Errorf("PrivateKey.CreateOrUpdate error: %v", err) return e.ErrCreatePrivateKey } return nil @@ -158,8 +158,8 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str // return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") // } // currentPrivateKey.UpdateBy = username - // if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - // log.Errorf("PrivateKey.Create error: %s", err) + // if err := commonrepo.NewPrivateKeyColl().CreateOrUpdate(currentPrivateKey); err != nil { + // log.Errorf("PrivateKey.CreateOrUpdate error: %s", err) // return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") // } // } @@ -175,7 +175,7 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str currentPrivateKey.UpdateBy = username if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - log.Errorf("PrivateKey.Create error: %s", err) + log.Errorf("PrivateKey.CreateOrUpdate error: %s", err) return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") } } @@ -195,7 +195,7 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str } if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - log.Errorf("PrivateKey.Create error: %s", err) + log.Errorf("PrivateKey.CreateOrUpdate error: %s", err) return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") } } diff --git a/pkg/microservice/aslan/core/system/service/proxy.go b/pkg/microservice/aslan/core/system/service/proxy.go index 804f9e11..2bf6c465 100644 --- a/pkg/microservice/aslan/core/system/service/proxy.go +++ b/pkg/microservice/aslan/core/system/service/proxy.go @@ -70,7 +70,7 @@ func GetProxy(id string, log *zap.SugaredLogger) (*commonmodels.Proxy, error) { func CreateProxy(args *commonmodels.Proxy, log *zap.SugaredLogger) error { err := commonrepo.NewProxyColl().Create(args) if err != nil { - log.Errorf("Proxy.Create error: %v", err) + log.Errorf("Proxy.CreateOrUpdate error: %v", err) return e.ErrCreateProxy.AddErr(err) } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 92a3f2db..bf5ef35a 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -77,13 +77,13 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa err := UpdateRegistryNamespaceDefault(defaultReg, log) if err != nil { log.Errorf("updateRegistry error: %v", err) - return fmt.Errorf("RegistryNamespace.Create error: %v", err) + return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %v", err) } } } else { if isSystemDefault { log.Errorf("create registry error: There must be at least 1 default registry") - return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") + return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %s", "There must be at least 1 default registry") } } @@ -91,8 +91,8 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa args.Namespace = strings.TrimSpace(args.Namespace) if err := commonrepo.NewRegistryNamespaceColl().Create(args); err != nil { - log.Errorf("RegistryNamespace.Create error: %v", err) - return fmt.Errorf("RegistryNamespace.Create error: %v", err) + log.Errorf("RegistryNamespace.CreateOrUpdate error: %v", err) + return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %v", err) } return nil } @@ -116,7 +116,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam } else { if isSystemDefault || id == defaultReg.ID.Hex() { log.Errorf("create registry error: There must be at least 1 default registry") - return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") + return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %s", "There must be at least 1 default registry") } } args.UpdateBy = username diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go index 47284274..8ba0aad8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -82,7 +82,7 @@ Strategy dynamic: Dynamic selection of idle environment; base: - Create a new environment based on the baseline environment + CreateOrUpdate a new environment based on the baseline environment And Need to set EnvRecyclePolicy:success/always/never */ type Deploy struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go index 73fb95b8..a664d2c3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go @@ -250,7 +250,7 @@ func Push(pt *task.Task) error { } if err := commonrepo.NewQueueColl().Create(ConvertTaskToQueue(pt)); err != nil { - log.Errorf("pqColl.Create error: %v", err) + log.Errorf("pqColl.CreateOrUpdate error: %v", err) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go index a55588a3..5fea0cbd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go @@ -72,7 +72,7 @@ func (q *Queue) Push(pt *task.Task) error { } if err := q.pqColl.Create(ConvertTaskToQueue(pt)); err != nil { - q.log.Errorf("pqColl.Create error: %v", err) + q.log.Errorf("pqColl.CreateOrUpdate error: %v", err) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 4f91af95..54a8080a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -1124,7 +1124,7 @@ func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, log *zap.Sugar } client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { - log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) + log.Errorf("GetTestArtifactInfo CreateOrUpdate S3 client err:%+v", err) return nil, nil, fis, err } prefix := storage.GetObjectPath(dir) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 95064402..928d0dee 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -400,7 +400,7 @@ func CreateWorkflow(workflow *commonmodels.Workflow, log *zap.SugaredLogger) err } if err := commonrepo.NewWorkflowColl().Create(workflow); err != nil { - log.Errorf("Workflow.Create error: %v", err) + log.Errorf("Workflow.CreateOrUpdate error: %v", err) return e.ErrUpsertWorkflow.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c13200af..5fa04098 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -771,7 +771,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } if err := CreateTask(task); err != nil { - log.Errorf("workflow Create task:[%v] err:%v", task, err) + log.Errorf("workflow CreateOrUpdate task:[%v] err:%v", task, err) return nil, e.ErrCreateTask } @@ -1107,7 +1107,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. task.StorageEndpoint = endpoint if err := CreateTask(task); err != nil { - log.Errorf("workflow Create task:[%v] err:%s", task, err) + log.Errorf("workflow CreateOrUpdate task:[%v] err:%s", task, err) return nil, e.ErrCreateTask } @@ -1787,7 +1787,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } if err := CreateTask(task); err != nil { - log.Errorf("workflow Create task:[%v] err:%v", task, err) + log.Errorf("workflow CreateOrUpdate task:[%v] err:%v", task, err) return nil, e.ErrCreateTask } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/it_report.go b/pkg/microservice/aslan/core/workflow/testing/service/it_report.go index c34210ae..8fbf46b0 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/it_report.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/it_report.go @@ -84,7 +84,7 @@ func GetTestLocalTestSuite(serviceName string, log *zap.SugaredLogger) (*commonm } client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { - log.Errorf("GetTestLocalTestSuite Create S3 client err:%+v", err) + log.Errorf("GetTestLocalTestSuite CreateOrUpdate S3 client err:%+v", err) continue } objectKey := storage.GetObjectPath(fmt.Sprintf("%s/%d/%s/%s", pipelineName, pipelineTask.TaskID, "test", testJobName)) diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 27ccab8d..d4377c8d 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -41,7 +41,7 @@ func Execute() error { var err error defer func() { - // Create dog food file to tell wd that task has finished. + // CreateOrUpdate dog food file to tell wd that task has finished. resultMsg := types.JobSuccess if err != nil { resultMsg = types.JobFail diff --git a/pkg/tool/codehub/signer.go b/pkg/tool/codehub/signer.go index 133c49dd..6e3a23ed 100644 --- a/pkg/tool/codehub/signer.go +++ b/pkg/tool/codehub/signer.go @@ -139,7 +139,7 @@ func RequestPayload(r *http.Request) ([]byte, error) { return b, err } -// Create a "String to Sign". +// CreateOrUpdate a "String to Sign". func StringToSign(canonicalRequest string, t time.Time) (string, error) { hash := sha256.New() _, err := hash.Write([]byte(canonicalRequest)) @@ -150,7 +150,7 @@ func StringToSign(canonicalRequest string, t time.Time) (string, error) { Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil } -// Create the HWS Signature. +// CreateOrUpdate the HWS Signature. func SignStringToSign(stringToSign string, signingKey []byte) (string, error) { hm, err := hmacsha256(signingKey, stringToSign) return fmt.Sprintf("%x", hm), err diff --git a/pkg/tool/kube/patcher/patcher.go b/pkg/tool/kube/patcher/patcher.go index dc7fb8b7..174973c5 100644 --- a/pkg/tool/kube/patcher/patcher.go +++ b/pkg/tool/kube/patcher/patcher.go @@ -59,7 +59,7 @@ func GeneratePatchBytes(obj, modifiedObj runtime.Object) ([]byte, types.PatchTyp var lookupPatchMeta strategicpatch.LookupPatchMeta createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nis failed, err: %v" - // Create the versioned struct from the type defined in the restmapping + // CreateOrUpdate the versioned struct from the type defined in the restmapping // (which is the API version we'll be submitting the patch to) versionedObject, err := krkubeclient.Scheme().New(*gvk) switch { diff --git a/pkg/util/fs/fs.go b/pkg/util/fs/fs.go index 17be952a..b03ef48a 100644 --- a/pkg/util/fs/fs.go +++ b/pkg/util/fs/fs.go @@ -68,7 +68,7 @@ func SaveFile(src io.ReadCloser, dst string) error { // Extract top level directory. objectDir, _ := filepath.Split(dst) if objectDir != "" { - // Create any missing top level directories. + // CreateOrUpdate any missing top level directories. if err := os.MkdirAll(objectDir, 0700); err != nil { return err } @@ -80,7 +80,7 @@ func SaveFile(src io.ReadCloser, dst string) error { return err } - // Create a new file. + // CreateOrUpdate a new file. file, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return err -- Gitee From b7346c322b9c8739bcb45dec1a5d9a850d407992 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 14:54:17 +0800 Subject: [PATCH 1604/2647] modify serviceName in warpdrive Signed-off-by: liu deyi --- .../service/workflow/workflow_task.go | 10 +-- .../core/service/taskplugin/deploy.go | 66 +++++++++---------- 2 files changed, 37 insertions(+), 39 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 0ddb7f56..c42cf9b9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1263,8 +1263,8 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ log.Error(err) return nil, err } - deployTask.ServiceName = envList[1] + "_" + envList[0] - deployTask.ContainerName = envList[1] + deployTask.ServiceName = envList[0] + deployTask.ContainerName = envList[1] + "_" + envList[0] switch env.Type { case setting.K8SDeployType: @@ -2333,10 +2333,10 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e t.SetImage(pt.TaskArgs.Deploy.Image) t.SetNamespace(pt.TaskArgs.Deploy.Namespace) - serviceName := t.ServiceName - serviceName = strings.TrimPrefix(serviceName, t.ContainerName+"_") + containerName := t.ContainerName + containerName = strings.TrimSuffix(containerName, "_"+t.ServiceName) - _, err := validateServiceContainer(t.EnvName, t.ProductName, serviceName, t.ContainerName) + _, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, containerName) if err != nil { log.Error(err) return err diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index b1669fe0..c74cdf46 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -212,26 +212,25 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } } - - serviceName := p.Task.ServiceName - serviceName = strings.TrimPrefix(serviceName, p.Task.ContainerName+"_") + containerName := p.Task.ContainerName + containerName = strings.TrimSuffix(containerName, "_"+p.Task.ServiceName) + p.Task.ContainerName = containerName if p.Task.ServiceType != setting.HelmDeployType { // get servcie info var ( serviceInfo *types.ServiceTmpl selector labels.Selector ) - - serviceInfo, err = p.getService(ctx, serviceName, p.Task.ServiceType, p.Task.ProductName, 0) + serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, p.Task.ProductName, 0) if err != nil { // Maybe it is a share service, the entity is not under the project - serviceInfo, err = p.getService(ctx, serviceName, p.Task.ServiceType, "", 0) + serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, "", 0) if err != nil { return } } if serviceInfo.WorkloadType == "" { - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: serviceName}.AsSelector() + selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() var deployments []*appsv1.Deployment deployments, err = getter.ListDeployments(p.Task.Namespace, selector, p.kubeClient) @@ -295,7 +294,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * switch serviceInfo.WorkloadType { case setting.StatefulSet: var statefulSet *appsv1.StatefulSet - statefulSet, _, err = getter.GetStatefulSet(p.Task.Namespace, serviceName, p.kubeClient) + statefulSet, _, err = getter.GetStatefulSet(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) if err != nil { return } @@ -321,7 +320,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } case setting.Deployment: var deployment *appsv1.Deployment - deployment, _, err = getter.GetDeployment(p.Task.Namespace, serviceName, p.kubeClient) + deployment, _, err = getter.GetDeployment(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) if err != nil { return } @@ -379,17 +378,17 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } else { rcsList = append(rcsList, RcsListFromStatefulSets(statefulSets)...) } - p.findHelmAffectedResources(p.Task.Namespace, serviceName, rcsList) + p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, rcsList) p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, - serviceName, p.Task.ContainerName, p.Task.Namespace) + p.Task.ServiceName, p.Task.ContainerName, p.Task.Namespace) productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) if err != nil { err = errors.WithMessagef( err, "failed to get product %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } @@ -405,7 +404,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * serviceRevisionInProduct := int64(0) var targetContainer *types.Container for _, service := range productInfo.GetServiceMap() { - if service.ServiceName == serviceName { + if service.ServiceName == p.Task.ServiceName { serviceRevisionInProduct = service.Revision for _, container := range service.Containers { if container.Name == p.Task.ContainerName { @@ -418,17 +417,17 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if targetContainer == nil { - err = errors.Errorf("failed to find target container %s from service %s", p.Task.ContainerName, serviceName) + err = errors.Errorf("failed to find target container %s from service %s", p.Task.ContainerName, p.Task.ServiceName) return } if targetContainer.ImagePath == nil { - err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, serviceName) + err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) return } for _, chartInfo := range renderInfo.ChartInfos { - if chartInfo.ServiceName == serviceName { + if chartInfo.ServiceName == p.Task.ServiceName { renderChart = chartInfo break } @@ -436,23 +435,23 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if renderChart == nil { err = errors.Errorf("failed to update container image in %s/%s,chart not found", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } // use revision of service currently applied in environment instead of the latest revision - path, errDownload := p.downloadService(pipelineTask.ProductName, serviceName, + path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, serviceRevisionInProduct) if errDownload != nil { p.Log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - serviceRevisionInProduct, serviceName) - path, errDownload = p.downloadService(pipelineTask.ProductName, serviceName, + serviceRevisionInProduct, p.Task.ServiceName) + path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, 0) if errDownload != nil { err = errors.WithMessagef( errDownload, "failed to download service %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } } @@ -477,7 +476,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to pase image uri %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } @@ -487,12 +486,12 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to replace image uri %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } if replacedValuesYaml == "" { err = errors.Errorf("failed to set new image uri into service's values.yaml %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } @@ -513,12 +512,12 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to replace image uri into helm values %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } if replacedMergedValuesYaml == "" { err = errors.Errorf("failed to set image uri into mreged values.yaml in %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } @@ -529,11 +528,11 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to create helm client %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) return } - releaseName := util.GeneHelmReleaseName(p.Task.Namespace, serviceName) + releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) @@ -580,7 +579,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", - p.Task.Namespace, serviceName) + p.Task.Namespace, p.Task.ServiceName) done <- false } else { done <- true @@ -599,7 +598,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * //替换环境变量中的chartInfos for _, chartInfo := range renderInfo.ChartInfos { - if chartInfo.ServiceName == serviceName { + if chartInfo.ServiceName == p.Task.ServiceName { chartInfo.ValuesYaml = replacedValuesYaml break } @@ -616,7 +615,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * err = errors.WithMessagef( err, "failed to update renderset info %s/%s, renderset %s", - p.Task.Namespace, serviceName, renderInfo.Name) + p.Task.Namespace, p.Task.ServiceName, renderInfo.Name) } } } @@ -815,9 +814,8 @@ func (p *DeployTaskPlugin) Wait(ctx context.Context) { } timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) - serviceName := p.Task.ServiceName - serviceName = strings.TrimPrefix(serviceName, p.Task.ContainerName+"_") - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: serviceName}.AsSelector() + + selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() for { select { -- Gitee From 47653a3db0b3cf1d7be435945105170ab67cc1c4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 18 Feb 2022 15:02:56 +0800 Subject: [PATCH 1605/2647] revert files changed by accident Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/render.go | 6 +++--- pkg/microservice/aslan/core/project/service/product.go | 4 ++-- pkg/microservice/aslan/core/service/service/helm.go | 2 +- pkg/microservice/aslan/core/service/service/pm.go | 4 ++-- pkg/microservice/aslan/core/service/service/service.go | 4 ++-- .../aslan/core/system/service/basic_images.go | 2 +- .../aslan/core/system/service/external_link.go | 2 +- .../aslan/core/system/service/external_system.go | 2 +- pkg/microservice/aslan/core/system/service/install.go | 2 +- .../aslan/core/system/service/private_key.go | 10 +++++----- pkg/microservice/aslan/core/system/service/proxy.go | 2 +- pkg/microservice/aslan/core/system/service/registry.go | 10 +++++----- .../core/workflow/service/webhook/trigger_yaml.go | 2 +- .../workflow/service/workflow/pipeline_controller.go | 2 +- .../core/workflow/service/workflow/pipeline_queue.go | 2 +- .../core/workflow/service/workflow/pipeline_task.go | 2 +- .../aslan/core/workflow/service/workflow/workflow.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 6 +++--- .../aslan/core/workflow/testing/service/it_report.go | 2 +- pkg/microservice/reaper/executor/executor.go | 2 +- pkg/tool/codehub/signer.go | 4 ++-- pkg/tool/kube/patcher/patcher.go | 2 +- pkg/util/fs/fs.go | 4 ++-- 23 files changed, 40 insertions(+), 40 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 1c147d4c..a695162c 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -214,7 +214,7 @@ func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error return e.ErrCreateRenderSet.AddDesc(err.Error()) } if err := commonrepo.NewRenderSetColl().Create(args); err != nil { - errMsg := fmt.Sprintf("[RenderSet.CreateOrUpdate] %s error: %v", args.Name, err) + errMsg := fmt.Sprintf("[RenderSet.Create] %s error: %v", args.Name, err) log.Error(errMsg) return e.ErrCreateRenderSet.AddDesc(errMsg) } @@ -239,7 +239,7 @@ func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) e return e.ErrCreateRenderSet.AddDesc(err.Error()) } if err := commonrepo.NewRenderSetColl().Create(args); err != nil { - errMsg := fmt.Sprintf("[RenderSet.CreateOrUpdate] %s error: %v", args.Name, err) + errMsg := fmt.Sprintf("[RenderSet.Create] %s error: %v", args.Name, err) log.Error(errMsg) return e.ErrCreateRenderSet.AddDesc(errMsg) } @@ -534,7 +534,7 @@ func ensureRenderSetArgs(args *commonmodels.RenderSet) error { } log := log.SugaredLogger() if err := IsAllKeyCovered(args, log); err != nil { - return fmt.Errorf("[RenderSet.CreateOrUpdate] %s error: %v", args.Name, err) + return fmt.Errorf("[RenderSet.Create] %s error: %v", args.Name, err) } // 设置新的版本号 diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 734223a4..8ea435ed 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -122,7 +122,7 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err err = templaterepo.NewProductColl().Create(args) if err != nil { - log.Errorf("ProductTmpl.CreateOrUpdate error: %v", err) + log.Errorf("ProductTmpl.Create error: %v", err) return e.ErrCreateProduct.AddDesc(err.Error()) } @@ -136,7 +136,7 @@ func CreateProductTemplate(args *template.Product, log *zap.SugaredLogger) (err }, log) if err != nil { - log.Errorf("ProductTmpl.CreateOrUpdate error: %v", err) + log.Errorf("ProductTmpl.Create error: %v", err) // 创建渲染集失败,删除产品模板 return e.ErrCreateProduct.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 93b6903b..154145f6 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1147,6 +1147,6 @@ func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, ChartInfos: mixtureChartInfos, }, log, ); err != nil { - log.Errorf("helmService.CreateOrUpdate CreateHelmRenderSet error: %v", err) + log.Errorf("helmService.Create CreateHelmRenderSet error: %v", err) } } diff --git a/pkg/microservice/aslan/core/service/service/pm.go b/pkg/microservice/aslan/core/service/service/pm.go index 138b50dc..4b2012af 100644 --- a/pkg/microservice/aslan/core/service/service/pm.go +++ b/pkg/microservice/aslan/core/service/service/pm.go @@ -95,7 +95,7 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug } if err := commonrepo.NewServiceColl().Create(serviceObj); err != nil { - log.Errorf("pmService.CreateOrUpdate %s error: %v", args.ServiceTmplObject.ServiceName, err) + log.Errorf("pmService.Create %s error: %v", args.ServiceTmplObject.ServiceName, err) return e.ErrCreateTemplate.AddDesc(err.Error()) } @@ -103,7 +103,7 @@ func CreatePMService(username string, args *ServiceTmplBuildObject, log *zap.Sug build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: args.Build.Name, ProductName: args.Build.ProductName}) if err != nil { if err := commonservice.CreateBuild(username, args.Build, log); err != nil { - log.Errorf("pmService.CreateOrUpdate build %s error: %v", args.Build.Name, err) + log.Errorf("pmService.Create build %s error: %v", args.Build.Name, err) if err2 := commonrepo.NewServiceColl().Delete(args.ServiceTmplObject.ServiceName, args.ServiceTmplObject.Type, args.ServiceTmplObject.ProductName, "", rev); err2 != nil { log.Errorf("pmService.delete %s error: %v", args.ServiceTmplObject.ServiceName, err2) } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index ee673f3c..a62f3967 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -627,7 +627,7 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za } if err := commonrepo.NewServiceColl().Create(args); err != nil { - log.Errorf("ServiceTmpl.CreateOrUpdate %s error: %v", args.ServiceName, err) + log.Errorf("ServiceTmpl.Create %s error: %v", args.ServiceName, err) return e.ErrCreateTemplate.AddDesc(err.Error()) } return nil @@ -718,7 +718,7 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap } if err := commonrepo.NewServiceColl().Create(args); err != nil { - log.Errorf("ServiceTmpl.CreateOrUpdate %s error: %v", args.ServiceName, err) + log.Errorf("ServiceTmpl.Create %s error: %v", args.ServiceName, err) return nil, e.ErrCreateTemplate.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/system/service/basic_images.go b/pkg/microservice/aslan/core/system/service/basic_images.go index 562f5cbd..1fe3c19d 100644 --- a/pkg/microservice/aslan/core/system/service/basic_images.go +++ b/pkg/microservice/aslan/core/system/service/basic_images.go @@ -94,7 +94,7 @@ func CreateBasicImage(args *commonmodels.BasicImage, log *zap.SugaredLogger) err err = commonrepo.NewBasicImageColl().Create(args) if err != nil { - log.Errorf("BasicImage.CreateOrUpdate error: %v", err) + log.Errorf("BasicImage.Create error: %v", err) return e.ErrCreateBasicImage } return nil diff --git a/pkg/microservice/aslan/core/system/service/external_link.go b/pkg/microservice/aslan/core/system/service/external_link.go index f50538a2..812e3a01 100644 --- a/pkg/microservice/aslan/core/system/service/external_link.go +++ b/pkg/microservice/aslan/core/system/service/external_link.go @@ -36,7 +36,7 @@ func ListExternalLinks(log *zap.SugaredLogger) ([]*commonmodels.ExternalLink, er func CreateExternalLink(args *commonmodels.ExternalLink, log *zap.SugaredLogger) error { err := commonrepo.NewExternalLinkColl().Create(args) if err != nil { - log.Errorf("ExternalLink.CreateOrUpdate error: %s", err) + log.Errorf("ExternalLink.Create error: %s", err) return e.ErrCreateExternalLink.AddErr(err) } return nil diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go index 64bd98ef..76c2f97c 100644 --- a/pkg/microservice/aslan/core/system/service/external_system.go +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -31,7 +31,7 @@ func CreateExternalSystem(args *ExternalSystemDetail, log *zap.SugaredLogger) er APIToken: args.APIToken, }) if err != nil { - log.Errorf("CreateOrUpdate external system error: %s", err) + log.Errorf("Create external system error: %s", err) return e.ErrCreateExternalLink.AddErr(err) } return nil diff --git a/pkg/microservice/aslan/core/system/service/install.go b/pkg/microservice/aslan/core/system/service/install.go index 2d5ec30b..886be26c 100644 --- a/pkg/microservice/aslan/core/system/service/install.go +++ b/pkg/microservice/aslan/core/system/service/install.go @@ -31,7 +31,7 @@ func CreateInstall(args *commonmodels.Install, log *zap.SugaredLogger) error { err := commonrepo.NewInstallColl().Create(args) args.Name = strings.TrimSpace(args.Name) if err != nil { - log.Errorf("Install.CreateOrUpdate error: %v", err) + log.Errorf("Install.Create error: %v", err) return e.ErrCreateInstall } diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 55bdf1e8..847992c8 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -59,7 +59,7 @@ func CreatePrivateKey(args *commonmodels.PrivateKey, log *zap.SugaredLogger) err err := commonrepo.NewPrivateKeyColl().Create(args) if err != nil { - log.Errorf("PrivateKey.CreateOrUpdate error: %v", err) + log.Errorf("PrivateKey.Create error: %v", err) return e.ErrCreatePrivateKey } return nil @@ -158,8 +158,8 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str // return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") // } // currentPrivateKey.UpdateBy = username - // if err := commonrepo.NewPrivateKeyColl().CreateOrUpdate(currentPrivateKey); err != nil { - // log.Errorf("PrivateKey.CreateOrUpdate error: %s", err) + // if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { + // log.Errorf("PrivateKey.Create error: %s", err) // return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") // } // } @@ -175,7 +175,7 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str currentPrivateKey.UpdateBy = username if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - log.Errorf("PrivateKey.CreateOrUpdate error: %s", err) + log.Errorf("PrivateKey.Create error: %s", err) return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") } } @@ -195,7 +195,7 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str } if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - log.Errorf("PrivateKey.CreateOrUpdate error: %s", err) + log.Errorf("PrivateKey.Create error: %s", err) return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") } } diff --git a/pkg/microservice/aslan/core/system/service/proxy.go b/pkg/microservice/aslan/core/system/service/proxy.go index 2bf6c465..804f9e11 100644 --- a/pkg/microservice/aslan/core/system/service/proxy.go +++ b/pkg/microservice/aslan/core/system/service/proxy.go @@ -70,7 +70,7 @@ func GetProxy(id string, log *zap.SugaredLogger) (*commonmodels.Proxy, error) { func CreateProxy(args *commonmodels.Proxy, log *zap.SugaredLogger) error { err := commonrepo.NewProxyColl().Create(args) if err != nil { - log.Errorf("Proxy.CreateOrUpdate error: %v", err) + log.Errorf("Proxy.Create error: %v", err) return e.ErrCreateProxy.AddErr(err) } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index bf5ef35a..92a3f2db 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -77,13 +77,13 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa err := UpdateRegistryNamespaceDefault(defaultReg, log) if err != nil { log.Errorf("updateRegistry error: %v", err) - return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %v", err) + return fmt.Errorf("RegistryNamespace.Create error: %v", err) } } } else { if isSystemDefault { log.Errorf("create registry error: There must be at least 1 default registry") - return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %s", "There must be at least 1 default registry") + return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } } @@ -91,8 +91,8 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa args.Namespace = strings.TrimSpace(args.Namespace) if err := commonrepo.NewRegistryNamespaceColl().Create(args); err != nil { - log.Errorf("RegistryNamespace.CreateOrUpdate error: %v", err) - return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %v", err) + log.Errorf("RegistryNamespace.Create error: %v", err) + return fmt.Errorf("RegistryNamespace.Create error: %v", err) } return nil } @@ -116,7 +116,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam } else { if isSystemDefault || id == defaultReg.ID.Hex() { log.Errorf("create registry error: There must be at least 1 default registry") - return fmt.Errorf("RegistryNamespace.CreateOrUpdate error: %s", "There must be at least 1 default registry") + return fmt.Errorf("RegistryNamespace.Create error: %s", "There must be at least 1 default registry") } } args.UpdateBy = username diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go index 8ba0aad8..47284274 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -82,7 +82,7 @@ Strategy dynamic: Dynamic selection of idle environment; base: - CreateOrUpdate a new environment based on the baseline environment + Create a new environment based on the baseline environment And Need to set EnvRecyclePolicy:success/always/never */ type Deploy struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go index a664d2c3..73fb95b8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go @@ -250,7 +250,7 @@ func Push(pt *task.Task) error { } if err := commonrepo.NewQueueColl().Create(ConvertTaskToQueue(pt)); err != nil { - log.Errorf("pqColl.CreateOrUpdate error: %v", err) + log.Errorf("pqColl.Create error: %v", err) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go index 5fea0cbd..a55588a3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_queue.go @@ -72,7 +72,7 @@ func (q *Queue) Push(pt *task.Task) error { } if err := q.pqColl.Create(ConvertTaskToQueue(pt)); err != nil { - q.log.Errorf("pqColl.CreateOrUpdate error: %v", err) + q.log.Errorf("pqColl.Create error: %v", err) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 54a8080a..4f91af95 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -1124,7 +1124,7 @@ func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, log *zap.Sugar } client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { - log.Errorf("GetTestArtifactInfo CreateOrUpdate S3 client err:%+v", err) + log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) return nil, nil, fis, err } prefix := storage.GetObjectPath(dir) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 928d0dee..95064402 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -400,7 +400,7 @@ func CreateWorkflow(workflow *commonmodels.Workflow, log *zap.SugaredLogger) err } if err := commonrepo.NewWorkflowColl().Create(workflow); err != nil { - log.Errorf("Workflow.CreateOrUpdate error: %v", err) + log.Errorf("Workflow.Create error: %v", err) return e.ErrUpsertWorkflow.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 5fa04098..c13200af 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -771,7 +771,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } if err := CreateTask(task); err != nil { - log.Errorf("workflow CreateOrUpdate task:[%v] err:%v", task, err) + log.Errorf("workflow Create task:[%v] err:%v", task, err) return nil, e.ErrCreateTask } @@ -1107,7 +1107,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. task.StorageEndpoint = endpoint if err := CreateTask(task); err != nil { - log.Errorf("workflow CreateOrUpdate task:[%v] err:%s", task, err) + log.Errorf("workflow Create task:[%v] err:%s", task, err) return nil, e.ErrCreateTask } @@ -1787,7 +1787,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } if err := CreateTask(task); err != nil { - log.Errorf("workflow CreateOrUpdate task:[%v] err:%v", task, err) + log.Errorf("workflow Create task:[%v] err:%v", task, err) return nil, e.ErrCreateTask } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/it_report.go b/pkg/microservice/aslan/core/workflow/testing/service/it_report.go index 8fbf46b0..c34210ae 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/it_report.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/it_report.go @@ -84,7 +84,7 @@ func GetTestLocalTestSuite(serviceName string, log *zap.SugaredLogger) (*commonm } client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { - log.Errorf("GetTestLocalTestSuite CreateOrUpdate S3 client err:%+v", err) + log.Errorf("GetTestLocalTestSuite Create S3 client err:%+v", err) continue } objectKey := storage.GetObjectPath(fmt.Sprintf("%s/%d/%s/%s", pipelineName, pipelineTask.TaskID, "test", testJobName)) diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index d4377c8d..27ccab8d 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -41,7 +41,7 @@ func Execute() error { var err error defer func() { - // CreateOrUpdate dog food file to tell wd that task has finished. + // Create dog food file to tell wd that task has finished. resultMsg := types.JobSuccess if err != nil { resultMsg = types.JobFail diff --git a/pkg/tool/codehub/signer.go b/pkg/tool/codehub/signer.go index 6e3a23ed..133c49dd 100644 --- a/pkg/tool/codehub/signer.go +++ b/pkg/tool/codehub/signer.go @@ -139,7 +139,7 @@ func RequestPayload(r *http.Request) ([]byte, error) { return b, err } -// CreateOrUpdate a "String to Sign". +// Create a "String to Sign". func StringToSign(canonicalRequest string, t time.Time) (string, error) { hash := sha256.New() _, err := hash.Write([]byte(canonicalRequest)) @@ -150,7 +150,7 @@ func StringToSign(canonicalRequest string, t time.Time) (string, error) { Algorithm, t.UTC().Format(BasicDateFormat), hash.Sum(nil)), nil } -// CreateOrUpdate the HWS Signature. +// Create the HWS Signature. func SignStringToSign(stringToSign string, signingKey []byte) (string, error) { hm, err := hmacsha256(signingKey, stringToSign) return fmt.Sprintf("%x", hm), err diff --git a/pkg/tool/kube/patcher/patcher.go b/pkg/tool/kube/patcher/patcher.go index 174973c5..dc7fb8b7 100644 --- a/pkg/tool/kube/patcher/patcher.go +++ b/pkg/tool/kube/patcher/patcher.go @@ -59,7 +59,7 @@ func GeneratePatchBytes(obj, modifiedObj runtime.Object) ([]byte, types.PatchTyp var lookupPatchMeta strategicpatch.LookupPatchMeta createPatchErrFormat := "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nis failed, err: %v" - // CreateOrUpdate the versioned struct from the type defined in the restmapping + // Create the versioned struct from the type defined in the restmapping // (which is the API version we'll be submitting the patch to) versionedObject, err := krkubeclient.Scheme().New(*gvk) switch { diff --git a/pkg/util/fs/fs.go b/pkg/util/fs/fs.go index b03ef48a..17be952a 100644 --- a/pkg/util/fs/fs.go +++ b/pkg/util/fs/fs.go @@ -68,7 +68,7 @@ func SaveFile(src io.ReadCloser, dst string) error { // Extract top level directory. objectDir, _ := filepath.Split(dst) if objectDir != "" { - // CreateOrUpdate any missing top level directories. + // Create any missing top level directories. if err := os.MkdirAll(objectDir, 0700); err != nil { return err } @@ -80,7 +80,7 @@ func SaveFile(src io.ReadCloser, dst string) error { return err } - // CreateOrUpdate a new file. + // Create a new file. file, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) if err != nil { return err -- Gitee From d7752b5fd6ea8ca46f039d452bb08eeb464e5ef6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 15:03:12 +0800 Subject: [PATCH 1606/2647] modify containerName in warpdrive Signed-off-by: liu deyi --- .../core/service/taskplugin/deploy.go | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index c74cdf46..f86a0c39 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -214,7 +214,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } containerName := p.Task.ContainerName containerName = strings.TrimSuffix(containerName, "_"+p.Task.ServiceName) - p.Task.ContainerName = containerName if p.Task.ServiceType != setting.HelmDeployType { // get servcie info var ( @@ -247,8 +246,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * L: for _, deploy := range deployments { for _, container := range deploy.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -270,8 +269,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Loop: for _, sts := range statefulSets { for _, container := range sts.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -299,8 +298,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } for _, container := range statefulSet.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -325,8 +324,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } for _, container := range deployment.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -348,7 +347,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if !replaced { err = errors.Errorf( - "container %s is not found in resources with label %s", p.Task.ContainerName, selector) + "container %s is not found in resources with label %s", containerName, selector) return } } else if p.Task.ServiceType == setting.HelmDeployType { @@ -381,7 +380,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, rcsList) p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, - p.Task.ServiceName, p.Task.ContainerName, p.Task.Namespace) + p.Task.ServiceName, containerName, p.Task.Namespace) productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) if err != nil { @@ -407,7 +406,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if service.ServiceName == p.Task.ServiceName { serviceRevisionInProduct = service.Revision for _, container := range service.Containers { - if container.Name == p.Task.ContainerName { + if container.Name == containerName { targetContainer = container break } @@ -417,12 +416,12 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if targetContainer == nil { - err = errors.Errorf("failed to find target container %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to find target container %s from service %s", containerName, p.Task.ServiceName) return } if targetContainer.ImagePath == nil { - err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to get image path of %s from service %s", containerName, p.Task.ServiceName) return } -- Gitee From 022e65b5b890c41fa02f6724c4209be3c8eff301 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 16:40:46 +0800 Subject: [PATCH 1607/2647] modify containerName in aslan Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/handler/log.go | 6 +++++- pkg/microservice/aslan/core/log/handler/sse.go | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 25f83244..d5c8e754 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -56,7 +56,11 @@ func GetWorkflowBuildJobContainerLogs(c *gin.Context) { return } // Use all lowercase job names to avoid subdomain errors - ctx.Resp, ctx.Err = logservice.GetWorkflowBuildJobContainerLogs(strings.ToLower(c.Param("pipelineName")), c.Param("serviceName"), c.Query("type"), taskID, ctx.Logger) + containerName := c.Param("serviceName") + if strings.Contains(containerName, "_") { + containerName = strings.Split(containerName, "_")[0] + } + ctx.Resp, ctx.Err = logservice.GetWorkflowBuildJobContainerLogs(strings.ToLower(c.Param("pipelineName")), containerName, c.Query("type"), taskID, ctx.Logger) } func GetTestJobContainerLogs(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index cd1dc7ea..f7002522 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "strconv" + "strings" "github.com/gin-contrib/sse" "github.com/gin-gonic/gin" @@ -94,13 +95,18 @@ func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { } subTask := c.Query("subTask") + containerName := c.Param("serviceName") + if strings.Contains(containerName, "_") { + containerName = strings.Split(containerName, "_")[0] + } + options := &logservice.GetContainerOptions{ Namespace: config.Namespace(), PipelineName: c.Param("pipelineName"), SubTask: subTask, TailLines: tails, TaskID: taskID, - ServiceName: c.Param("serviceName"), + ServiceName: containerName, PipelineType: string(config.WorkflowType), EnvName: c.Query("envName"), ProductName: c.Query("projectName"), -- Gitee From d664b28b482c5c556e31d0b96e1d38ab1e07ee1f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 17:23:46 +0800 Subject: [PATCH 1608/2647] modify containerName in aslan Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/handler/log.go | 6 +----- pkg/microservice/aslan/core/log/handler/sse.go | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index d5c8e754..25f83244 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -56,11 +56,7 @@ func GetWorkflowBuildJobContainerLogs(c *gin.Context) { return } // Use all lowercase job names to avoid subdomain errors - containerName := c.Param("serviceName") - if strings.Contains(containerName, "_") { - containerName = strings.Split(containerName, "_")[0] - } - ctx.Resp, ctx.Err = logservice.GetWorkflowBuildJobContainerLogs(strings.ToLower(c.Param("pipelineName")), containerName, c.Query("type"), taskID, ctx.Logger) + ctx.Resp, ctx.Err = logservice.GetWorkflowBuildJobContainerLogs(strings.ToLower(c.Param("pipelineName")), c.Param("serviceName"), c.Query("type"), taskID, ctx.Logger) } func GetTestJobContainerLogs(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index f7002522..efad3a82 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "strconv" - "strings" "github.com/gin-contrib/sse" "github.com/gin-gonic/gin" @@ -95,10 +94,6 @@ func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { } subTask := c.Query("subTask") - containerName := c.Param("serviceName") - if strings.Contains(containerName, "_") { - containerName = strings.Split(containerName, "_")[0] - } options := &logservice.GetContainerOptions{ Namespace: config.Namespace(), @@ -106,7 +101,7 @@ func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { SubTask: subTask, TailLines: tails, TaskID: taskID, - ServiceName: containerName, + ServiceName: c.Param("serviceName"), PipelineType: string(config.WorkflowType), EnvName: c.Query("envName"), ProductName: c.Query("projectName"), -- Gitee From e557b1aa538741d3910833c1fd8c3d08fab992fc Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 18 Feb 2022 18:13:05 +0800 Subject: [PATCH 1609/2647] modify artifact in aslan Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c42cf9b9..21213a4d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1714,7 +1714,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, artifact.Name) + AddSubtaskToStage(&stages, stask, artifact.Name+"_"+artifact.ServiceName) } } -- Gitee From ff1b80bedf49786e3b6165ea29c7e453cc524e52 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 21 Feb 2022 11:29:33 +0800 Subject: [PATCH 1610/2647] modify artifact workflowtask logic Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 0794bd46..d01a90c4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1730,8 +1730,9 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator sort.Sort(ByTaskKind(task.SubTasks)) if err := ensurePipelineTask(&taskmodels.TaskOpt{ - Task: task, - EnvName: args.Namespace, + Task: task, + EnvName: args.Namespace, + IsWorkflowTask: true, }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask task:[%v] err:%v", task, err) if err, ok := err.(*ContainerNotFound); ok { -- Gitee From c80ed0aeaee6b05f2f0946733254ef8a47c54425 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 11:29:44 +0800 Subject: [PATCH 1611/2647] add bindings Signed-off-by: mouuii --- .../picket/client/policy/rolebinding.go | 19 ++++++ .../picket/core/filter/handler/rolebinding.go | 4 +- .../picket/core/filter/handler/router.go | 4 +- .../picket/core/filter/service/rolebinding.go | 66 +++++++++++++++++-- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go index b93fb933..394d944d 100644 --- a/pkg/microservice/picket/client/policy/rolebinding.go +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -30,6 +30,13 @@ type RoleBinding struct { Public bool `json:"public"` } +type PolicyBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Policy string `json:"policy"` + Public bool `json:"public"` +} + func (c *Client) ListRoleBindings(header http.Header, qs url.Values) ([]*RoleBinding, error) { url := "/rolebindings" @@ -42,6 +49,18 @@ func (c *Client) ListRoleBindings(header http.Header, qs url.Values) ([]*RoleBin return res, nil } +func (c *Client) ListPolicyBindings(header http.Header, qs url.Values) ([]*PolicyBinding, error) { + url := "/policybindings" + + res := make([]*PolicyBinding, 0) + _, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, nil +} + func (c *Client) DeleteRoleBindings(userID string, header http.Header, qs url.Values) ([]byte, error) { url := "rolebindings/bulk-delete" diff --git a/pkg/microservice/picket/core/filter/handler/rolebinding.go b/pkg/microservice/picket/core/filter/handler/rolebinding.go index d4c11471..4a857af3 100644 --- a/pkg/microservice/picket/core/filter/handler/rolebinding.go +++ b/pkg/microservice/picket/core/filter/handler/rolebinding.go @@ -24,7 +24,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListRoleBindings(c *gin.Context) { +func ListBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -34,5 +34,5 @@ func ListRoleBindings(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.ListRoleBindings(c.Request.Header, c.Request.URL.Query(), ctx.Logger) + ctx.Resp, ctx.Err = service.ListBindings(c.Request.Header, c.Request.URL.Query(), ctx.Logger) } diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 0bbf3c27..5851f946 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -38,9 +38,9 @@ func (*Router) Inject(router *gin.RouterGroup) { workflows.GET("v3", ListWorkflowsV3) } - rolebindings := router.Group("rolebindings") + rolebindings := router.Group("bindings") { - rolebindings.GET("", ListRoleBindings) + rolebindings.GET("", ListBindings) } codehosts := router.Group("codehosts") { diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 4b58931f..868b35df 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -37,7 +37,23 @@ type roleBinding struct { Account string `json:"account"` } -func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*roleBinding, error) { +type policyBinding struct { + *policy.PolicyBinding + Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` +} + +type Binding struct { + Roles []*roleBinding `json:"roles"` + Policies []*policyBinding `json:"policies"` + UserName string `json:"user_name"` + Email string `json:"email"` +} + +func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*Binding, error) { rbs, err := policy.New().ListRoleBindings(header, qs) if err != nil { logger.Errorf("Failed to list rolebindings, err: %s", err) @@ -53,14 +69,34 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg uidToRoleBinding[rb.UID] = append(uidToRoleBinding[rb.UID], &roleBinding{RoleBinding: rb}) } + pbs, err := policy.New().ListPolicyBindings(header, qs) + if err != nil { + logger.Errorf("Failed to list policybindings, err: %s", err) + return nil, err + } + + uidToPolicyBindings := make(map[string][]*policyBinding) + for _, pb := range pbs { + if pb.UID != allUsers { + uids = append(uids, pb.UID) + } + uidToPolicyBindings[pb.UID] = append(uidToPolicyBindings[pb.UID], &policyBinding{PolicyBinding: pb}) + } + users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uids}) if err != nil { logger.Errorf("Failed to list users, err: %s", err) return nil, err } - var res []*roleBinding + var res []*Binding + var roleBindings []*roleBinding + var policyBindings []*policyBinding for _, u := range users { + binding := &Binding{ + UserName: u.Name, + Email: u.Email, + } if rb, ok := uidToRoleBinding[u.UID]; ok { for _, r := range rb { r.Username = u.Name @@ -68,15 +104,31 @@ func ListRoleBindings(header http.Header, qs url.Values, logger *zap.SugaredLogg r.Phone = u.Phone r.IdentityType = u.IdentityType r.Account = u.Account - - res = append(res, r) + roleBindings = append(roleBindings, r) } - + binding.Roles = roleBindings + } + if pb, ok := uidToPolicyBindings[u.UID]; ok { + for _, p := range pb { + p.Username = u.Name + p.Email = u.Email + p.Phone = u.Phone + p.IdentityType = u.IdentityType + p.Account = u.Account + policyBindings = append(policyBindings, p) + } + binding.Roles = roleBindings } + res = append(res, binding) } // add all 'allUsers' roles - res = append(res, uidToRoleBinding[allUsers]...) - + AllUserBinding := &Binding{ + Roles: uidToRoleBinding[allUsers], + Policies: uidToPolicyBindings[allUsers], + UserName: "*", + Email: "", + } + res = append(res, AllUserBinding) return res, nil } -- Gitee From b96749f95b124a37ddafe3d972148e5f33fb84e8 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 21 Feb 2022 13:33:51 +0800 Subject: [PATCH 1612/2647] fix testing status for IM Signed-off-by: ddh27 --- .../core/common/repository/models/notify.go | 1 + .../core/common/service/notify/client.go | 1 + pkg/microservice/warpdrive/config/types.go | 27 +++++++++++++++++++ .../service/taskcontroller/task_handler.go | 3 ++- .../service/taskcontroller/task_helper.go | 10 +++---- .../warpdrive/core/service/types/notify.go | 1 + .../core/service/types/task/model.go | 12 +-------- 7 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 pkg/microservice/warpdrive/config/types.go diff --git a/pkg/microservice/aslan/core/common/repository/models/notify.go b/pkg/microservice/aslan/core/common/repository/models/notify.go index 88ae5d97..af7c0e9b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/notify.go +++ b/pkg/microservice/aslan/core/common/repository/models/notify.go @@ -46,6 +46,7 @@ type PipelineStatusCtx struct { Type config.PipelineType `bson:"type" json:"type"` Status config.Status `bson:"status" json:"status,omitempty"` TeamName string `bson:"team" json:"team"` + Stages []*Stage `bson:"-" json:"stages"` } type MessageCtx struct { diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index 8f9e34c5..bface2c9 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -232,6 +232,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { testTaskStatusChanged = true } } + task.Stages = ctx.Stages } err = c.InstantmessageService.SendInstantMessage(task, testTaskStatusChanged) diff --git a/pkg/microservice/warpdrive/config/types.go b/pkg/microservice/warpdrive/config/types.go new file mode 100644 index 00000000..ad651f9e --- /dev/null +++ b/pkg/microservice/warpdrive/config/types.go @@ -0,0 +1,27 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package config + +type Stage struct { + // 注意: 同一个stage暂时不能运行不同类型的Task + TaskType TaskType `bson:"type" json:"type"` + Status Status `bson:"status" json:"status"` + RunParallel bool `bson:"run_parallel" json:"run_parallel"` + Desc string `bson:"desc,omitempty" json:"desc,omitempty"` + SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + AfterAll bool `json:"after_all" bson:"after_all"` +} diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 0b996300..881b5756 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -248,6 +248,7 @@ func (h *ExecHandler) SendNotification() { Status: config.Status(pipelineTask.Status), TeamName: pipelineTask.TeamName, Type: pipelineTask.Type, + Stages: pipelineTask.Stages, }, CreateTime: time.Now().Unix(), IsRead: false, @@ -265,7 +266,7 @@ func (h *ExecHandler) SendNotification() { } } -func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage, concurrency int64) { +func (h *ExecHandler) runStage(stagePosition int, stage *config.Stage, concurrency int64) { xl.Infof("start to execute pipeline stage: %s at position: %d", stage.TaskType, stagePosition) pluginInitiator, ok := h.TaskPlugins[stage.TaskType] if !ok { diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 9015428b..1acc93e4 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -44,7 +44,7 @@ import ( // 1. 转换经过序列化的 SubTasks 到 Stages // Stage Map: *Stage -> map [service->subtask] func transformToStages(pipelineTask *task.Task, xl *zap.SugaredLogger) error { - var pipelineStages []*task.Stage + var pipelineStages []*config.Stage // 工作流1.0,单服务工作流,SubTasks一维数组 // Transform into stages and assign to stages // Task的数据结构中如果没有赋值Type,也按照1.0处理 @@ -57,7 +57,7 @@ func transformToStages(pipelineTask *task.Task, xl *zap.SugaredLogger) error { } // Pipeline 1.0中的一个Subtask对应到Pipeline 2.0中的一个Stage // Stage中subtasks仅有一个subtask, key为service_name - stage := &task.Stage{ + stage := &config.Stage{ TaskType: subTaskPreview.TaskType, // Pipeline 1.0中,每个type subtask只有一个,不存在并行执行 RunParallel: false, @@ -121,10 +121,10 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv // 同时更新stages // TODO: 完善Stage其他字段 if len(pipelineTask.Stages) == 0 { - pipelineTask.Stages = make([]*task.Stage, len(pipelineTask.SubTasks)) + pipelineTask.Stages = make([]*config.Stage, len(pipelineTask.SubTasks)) } if pipelineTask.Stages[pos] == nil { - pipelineTask.Stages[pos] = &task.Stage{} + pipelineTask.Stages[pos] = &config.Stage{} } pipelineTask.Stages[pos].SubTasks = map[string]map[string]interface{}{servicename: subTask} } else if pipelineTask.Type == config.WorkflowType { @@ -150,7 +150,7 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv func updatePipelineStageStatus(stageStatus config.Status, pipelineTask *task.Task, pos int, xl *zap.SugaredLogger) { xl.Infof("updating pipeline task, stage status: %s, stage position: %d", stageStatus, pos) if pipelineTask.Stages[pos] == nil { - pipelineTask.Stages[pos] = &task.Stage{} + pipelineTask.Stages[pos] = &config.Stage{} } pipelineTask.Stages[pos].Status = stageStatus } diff --git a/pkg/microservice/warpdrive/core/service/types/notify.go b/pkg/microservice/warpdrive/core/service/types/notify.go index fb876785..9e641c80 100644 --- a/pkg/microservice/warpdrive/core/service/types/notify.go +++ b/pkg/microservice/warpdrive/core/service/types/notify.go @@ -35,6 +35,7 @@ type PipelineStatusCtx struct { Type config.PipelineType `bson:"type" json:"type"` Status config.Status `bson:"status" json:"status,omitempty"` TeamName string `bson:"team" json:"team"` + Stages []*config.Stage `bson:"stages" json:"stages"` } // diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index cc333453..6d12aed9 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -39,7 +39,7 @@ type Task struct { StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` + Stages []*config.Stage `bson:"stages" json:"stages"` ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` DockerHost string `bson:"-" json:"docker_host,omitempty"` @@ -312,16 +312,6 @@ type TriggerBy struct { CommitID string `json:"commit_id,omitempty" bson:"commit_id,omitempty"` } -type Stage struct { - // 注意: 同一个stage暂时不能运行不同类型的Task - TaskType config.TaskType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status"` - RunParallel bool `bson:"run_parallel" json:"run_parallel"` - Desc string `bson:"desc,omitempty" json:"desc,omitempty"` - SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - AfterAll bool `json:"after_all" bson:"after_all"` -} - func (Task) TableName() string { return "pipeline_task_v2" } -- Gitee From 20e0c9987ce727efbfc457953ccf3b940a07965d Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 21 Feb 2022 13:38:38 +0800 Subject: [PATCH 1613/2647] fix user api_token Signed-off-by: ddh27 --- pkg/microservice/user/core/service/init/mysql.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql index 8986ff93..75603daf 100644 --- a/pkg/microservice/user/core/service/init/mysql.sql +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS `user`( `identity_type` varchar(32) NOT NULL DEFAULT 'unknown' COMMENT '用户来源', `phone` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号码', `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱', - `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken'; + `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken', `created_at` int(11) unsigned NOT NULL COMMENT '创建时间', `updated_at` int(11) unsigned NOT NULL COMMENT '修改时间', UNIQUE KEY `account` (`account`,`identity_type`), -- Gitee From 077027646d600808b83b4dc639dbbbfc4fd6344c Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 21 Feb 2022 13:45:41 +0800 Subject: [PATCH 1614/2647] fix testing status for IM Signed-off-by: ddh27 --- pkg/microservice/warpdrive/config/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/config/types.go b/pkg/microservice/warpdrive/config/types.go index ad651f9e..1281a1f8 100644 --- a/pkg/microservice/warpdrive/config/types.go +++ b/pkg/microservice/warpdrive/config/types.go @@ -17,7 +17,7 @@ limitations under the License. package config type Stage struct { - // 注意: 同一个stage暂时不能运行不同类型的Task + //Note: The same stage cannot temporarily run different types of tasks TaskType TaskType `bson:"type" json:"type"` Status Status `bson:"status" json:"status"` RunParallel bool `bson:"run_parallel" json:"run_parallel"` -- Gitee From 9e062be290f99ef4d50139089dddd25a2f4e909f Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 21 Feb 2022 14:00:04 +0800 Subject: [PATCH 1615/2647] fix testing status for IM Signed-off-by: ddh27 --- .../{config => core/service/common}/types.go | 8 +++++--- .../core/service/taskcontroller/task_handler.go | 3 ++- .../core/service/taskcontroller/task_helper.go | 11 ++++++----- .../warpdrive/core/service/types/notify.go | 7 +++++-- .../warpdrive/core/service/types/task/model.go | 3 ++- 5 files changed, 20 insertions(+), 12 deletions(-) rename pkg/microservice/warpdrive/{config => core/service/common}/types.go (83%) diff --git a/pkg/microservice/warpdrive/config/types.go b/pkg/microservice/warpdrive/core/service/common/types.go similarity index 83% rename from pkg/microservice/warpdrive/config/types.go rename to pkg/microservice/warpdrive/core/service/common/types.go index 1281a1f8..b57d7c59 100644 --- a/pkg/microservice/warpdrive/config/types.go +++ b/pkg/microservice/warpdrive/core/service/common/types.go @@ -14,12 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package config +package common + +import "github.com/koderover/zadig/pkg/microservice/warpdrive/config" type Stage struct { //Note: The same stage cannot temporarily run different types of tasks - TaskType TaskType `bson:"type" json:"type"` - Status Status `bson:"status" json:"status"` + TaskType config.TaskType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status"` RunParallel bool `bson:"run_parallel" json:"run_parallel"` Desc string `bson:"desc,omitempty" json:"desc,omitempty"` SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 881b5756..afac91fb 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -28,6 +28,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" plugins "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" @@ -266,7 +267,7 @@ func (h *ExecHandler) SendNotification() { } } -func (h *ExecHandler) runStage(stagePosition int, stage *config.Stage, concurrency int64) { +func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurrency int64) { xl.Infof("start to execute pipeline stage: %s at position: %d", stage.TaskType, stagePosition) pluginInitiator, ok := h.TaskPlugins[stage.TaskType] if !ok { diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 1acc93e4..939d55b1 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -29,6 +29,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" plugins "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/github" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" @@ -44,7 +45,7 @@ import ( // 1. 转换经过序列化的 SubTasks 到 Stages // Stage Map: *Stage -> map [service->subtask] func transformToStages(pipelineTask *task.Task, xl *zap.SugaredLogger) error { - var pipelineStages []*config.Stage + var pipelineStages []*common.Stage // 工作流1.0,单服务工作流,SubTasks一维数组 // Transform into stages and assign to stages // Task的数据结构中如果没有赋值Type,也按照1.0处理 @@ -57,7 +58,7 @@ func transformToStages(pipelineTask *task.Task, xl *zap.SugaredLogger) error { } // Pipeline 1.0中的一个Subtask对应到Pipeline 2.0中的一个Stage // Stage中subtasks仅有一个subtask, key为service_name - stage := &config.Stage{ + stage := &common.Stage{ TaskType: subTaskPreview.TaskType, // Pipeline 1.0中,每个type subtask只有一个,不存在并行执行 RunParallel: false, @@ -121,10 +122,10 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv // 同时更新stages // TODO: 完善Stage其他字段 if len(pipelineTask.Stages) == 0 { - pipelineTask.Stages = make([]*config.Stage, len(pipelineTask.SubTasks)) + pipelineTask.Stages = make([]*common.Stage, len(pipelineTask.SubTasks)) } if pipelineTask.Stages[pos] == nil { - pipelineTask.Stages[pos] = &config.Stage{} + pipelineTask.Stages[pos] = &common.Stage{} } pipelineTask.Stages[pos].SubTasks = map[string]map[string]interface{}{servicename: subTask} } else if pipelineTask.Type == config.WorkflowType { @@ -150,7 +151,7 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv func updatePipelineStageStatus(stageStatus config.Status, pipelineTask *task.Task, pos int, xl *zap.SugaredLogger) { xl.Infof("updating pipeline task, stage status: %s, stage position: %d", stageStatus, pos) if pipelineTask.Stages[pos] == nil { - pipelineTask.Stages[pos] = &config.Stage{} + pipelineTask.Stages[pos] = &common.Stage{} } pipelineTask.Stages[pos].Status = stageStatus } diff --git a/pkg/microservice/warpdrive/core/service/types/notify.go b/pkg/microservice/warpdrive/core/service/types/notify.go index 9e641c80..8b96e9ff 100644 --- a/pkg/microservice/warpdrive/core/service/types/notify.go +++ b/pkg/microservice/warpdrive/core/service/types/notify.go @@ -16,7 +16,10 @@ limitations under the License. package types -import "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +import ( + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" +) //// AnnouncementCtx ... //type AnnouncementCtx struct { @@ -35,7 +38,7 @@ type PipelineStatusCtx struct { Type config.PipelineType `bson:"type" json:"type"` Status config.Status `bson:"status" json:"status,omitempty"` TeamName string `bson:"team" json:"team"` - Stages []*config.Stage `bson:"stages" json:"stages"` + Stages []*common.Stage `bson:"stages" json:"stages"` } // diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 6d12aed9..2535b31a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" "github.com/koderover/zadig/pkg/setting" ) @@ -39,7 +40,7 @@ type Task struct { StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*config.Stage `bson:"stages" json:"stages"` + Stages []*common.Stage `bson:"stages" json:"stages"` ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` DockerHost string `bson:"-" json:"docker_host,omitempty"` -- Gitee From 9a9300e44a06001aca6a12b36b18c311aa3ba34c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 21 Feb 2022 14:03:05 +0800 Subject: [PATCH 1616/2647] remove blank Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/handler/sse.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index efad3a82..cd1dc7ea 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -94,7 +94,6 @@ func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { } subTask := c.Query("subTask") - options := &logservice.GetContainerOptions{ Namespace: config.Namespace(), PipelineName: c.Param("pipelineName"), -- Gitee From 7b76e401720b16b805854914bb43c192a566cf04 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 14:14:05 +0800 Subject: [PATCH 1617/2647] add bindings Signed-off-by: mouuii --- .../picket/core/filter/service/rolebinding.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 868b35df..8127a348 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -21,6 +21,7 @@ import ( "net/url" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/picket/client/policy" "github.com/koderover/zadig/pkg/shared/client/user" @@ -60,11 +61,11 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) return nil, err } - var uids []string + uidSets := sets.String{} uidToRoleBinding := make(map[string][]*roleBinding) for _, rb := range rbs { if rb.UID != allUsers { - uids = append(uids, rb.UID) + uidSets.Insert(rb.UID) } uidToRoleBinding[rb.UID] = append(uidToRoleBinding[rb.UID], &roleBinding{RoleBinding: rb}) } @@ -78,12 +79,12 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) uidToPolicyBindings := make(map[string][]*policyBinding) for _, pb := range pbs { if pb.UID != allUsers { - uids = append(uids, pb.UID) + uidSets.Insert(pb.UID) } uidToPolicyBindings[pb.UID] = append(uidToPolicyBindings[pb.UID], &policyBinding{PolicyBinding: pb}) } - users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uids}) + users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uidSets.List()}) if err != nil { logger.Errorf("Failed to list users, err: %s", err) return nil, err @@ -117,7 +118,7 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) p.Account = u.Account policyBindings = append(policyBindings, p) } - binding.Roles = roleBindings + binding.Policies = policyBindings } res = append(res, binding) } -- Gitee From 1cafb9b544236d869a834dae6d7d49b0705d3b35 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 14:55:28 +0800 Subject: [PATCH 1618/2647] vars return Signed-off-by: mouuii --- .../aslan/core/collaboration/service/collaboration_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 5eb60c54..0dc51249 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -78,7 +78,7 @@ type Product struct { CollaborationMode string `json:"collaboration_mode"` Name string `json:"name"` DeployType string `json:"deploy_type"` - Vars []*templatemodels.RenderKV `json:"vars,omitempty"` + Vars []*templatemodels.RenderKV `json:"vars"` DefaultValues string `json:"defaultValues,omitempty"` ChartValues []*commonservice.RenderChartArg `json:"chartValues,omitempty"` } -- Gitee From 52daf554b527095fa42c2f1747eaffcf2e25a6f9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 15:48:25 +0800 Subject: [PATCH 1619/2647] vars return Signed-off-by: mouuii --- .../core/collaboration/service/collaboration_instance.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 0dc51249..da20eed4 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -903,6 +903,14 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol } } if len(yamlProductItems) > 0 { + + pro, err := service2.GetInitProduct(projectName, logger) + if err != nil { + return err + } + for i, _ := range yamlProductItems { + yamlProductItems[i].Vars = append(yamlProductItems[i].Vars, pro.Vars...) + } err = service2.BulkCopyYamlProduct(projectName, userName, requestID, service2.CopyYamlProductArg{ Items: yamlProductItems, }, logger) -- Gitee From 4d1cc86473683d1918603828e67d1f8f64c143bd Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 15:56:50 +0800 Subject: [PATCH 1620/2647] vars add Signed-off-by: mouuii --- .../core/collaboration/service/collaboration_instance.go | 8 -------- .../aslan/core/environment/service/environment.go | 7 +++++++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index da20eed4..0dc51249 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -903,14 +903,6 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol } } if len(yamlProductItems) > 0 { - - pro, err := service2.GetInitProduct(projectName, logger) - if err != nil { - return err - } - for i, _ := range yamlProductItems { - yamlProductItems[i].Vars = append(yamlProductItems[i].Vars, pro.Vars...) - } err = service2.BulkCopyYamlProduct(projectName, userName, requestID, service2.CopyYamlProductArg{ Items: yamlProductItems, }, logger) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f9ac6630..d6958e85 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -942,6 +942,13 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc if len(arg.Items) == 0 { return nil } + pro, err := GetInitProduct(projectName, log) + if err != nil { + return err + } + for i, _ := range arg.Items { + arg.Items[i].Vars = append(arg.Items[i].Vars, pro.Vars...) + } var envs []string for _, item := range arg.Items { envs = append(envs, item.OldName) -- Gitee From d65c9c1f08219bbff48feb94effd9fef7a2130f5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 21 Feb 2022 17:11:26 +0800 Subject: [PATCH 1621/2647] update ListLabelsByResources continue if has error Signed-off-by: panxunying --- pkg/microservice/aslan/core/label/service/label.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 9d37a4d1..0fe66636 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -184,7 +184,8 @@ func ListLabelsByResources(resources []mongodb.Resource, logger *zap.SugaredLogg resourceKey := config.BuildResourceKey(labelBinding.ResourceType, labelBinding.ProjectName, labelBinding.ResourceName) label, ok := labelM[labelBinding.LabelID] if !ok { - return nil, fmt.Errorf("can not find label %v", labelBinding.LabelID) + logger.Errorf("can not find label %v", labelBinding) + continue } res[resourceKey] = append(res[resourceKey], label) -- Gitee From a5b179f97f6f4ed4d947fca1b68f7f249190e31b Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 21 Feb 2022 17:18:44 +0800 Subject: [PATCH 1622/2647] fix bug of scheduling build/test tasks Signed-off-by: zhangyifei --- .../aslan/core/log/service/sse.go | 20 +++++++++++++++---- .../aslan/core/multicluster/service/cache.go | 6 +++--- .../core/multicluster/service/clusters.go | 2 +- .../core/multicluster/service/constants.go | 4 ---- .../core/service/taskplugin/build.go | 10 +++++++--- .../core/service/taskplugin/testing.go | 11 +++++++--- pkg/setting/consts.go | 4 ++++ 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 1cd19e6d..869539aa 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -140,9 +140,15 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op }) } // Compatible with the situation where the old data has not been modified - if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" && build.PreBuild.Namespace != "" { + if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" { options.ClusterID = build.PreBuild.ClusterID - options.Namespace = build.PreBuild.Namespace + + switch build.PreBuild.ClusterID { + case setting.LocalClusterID: + options.Namespace = config.Namespace() + default: + options.Namespace = setting.AttachedClusterNamespace + } } } @@ -159,9 +165,15 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, // get cluster ID testing, _ := commonrepo.NewTestingColl().Find(getTestName(options.ServiceName), "") // Compatible with the situation where the old data has not been modified - if testing != nil && testing.PreTest != nil && testing.PreTest.ClusterID != "" && testing.PreTest.Namespace != "" { + if testing != nil && testing.PreTest != nil && testing.PreTest.ClusterID != "" { options.ClusterID = testing.PreTest.ClusterID - options.Namespace = testing.PreTest.Namespace + + switch testing.PreTest.ClusterID { + case setting.LocalClusterID: + options.Namespace = config.Namespace() + default: + options.Namespace = setting.AttachedClusterNamespace + } } waitAndGetLog(ctx, streamChan, selector, options, log) diff --git a/pkg/microservice/aslan/core/multicluster/service/cache.go b/pkg/microservice/aslan/core/multicluster/service/cache.go index aa7bf8bf..15c88f90 100644 --- a/pkg/microservice/aslan/core/multicluster/service/cache.go +++ b/pkg/microservice/aslan/core/multicluster/service/cache.go @@ -47,7 +47,7 @@ func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) // For cluster-level resources, we need to explicitly configure the namespace to be empty. namespace = "" default: - namespace = AttachedClusterNamespace + namespace = setting.AttachedClusterNamespace } scList := &storagev1.StorageClassList{} @@ -80,8 +80,8 @@ func ListPVCs(ctx context.Context, clusterID, namespace string) ([]PVC, error) { // For now caller may not know exactly which namespace Zadig is deployed to, so a correction is made here. namespace = config.Namespace() default: - if namespace != AttachedClusterNamespace { - return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, AttachedClusterNamespace) + if namespace != setting.AttachedClusterNamespace { + return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, setting.AttachedClusterNamespace) } } diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 8c6ecc43..99392419 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -257,7 +257,7 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com case setting.LocalClusterID: namespace = config.Namespace() default: - namespace = AttachedClusterNamespace + namespace = setting.AttachedClusterNamespace } pvcName := fmt.Sprintf("cache-%s-%d", args.Cache.NFSProperties.StorageClass, args.Cache.NFSProperties.StorageSizeInGiB) diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go index f7d3b123..114444b0 100644 --- a/pkg/microservice/aslan/core/multicluster/service/constants.go +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -18,10 +18,6 @@ package service import "strings" -// AttachedClusterNamespace is the namespace Zadig uses in attached cluster. -// Note: **Restricted because of product design since v1.9.0**. -const AttachedClusterNamespace = "koderover-agent" - // StorageProvisioner is a storage type type StorageProvisioner string diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 44d3fc97..0e89f64b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" @@ -113,9 +114,12 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe // TODO: Since the namespace field has been used continuously since v1.10.0, the processing logic related to namespace needs to // be deleted in v1.11.0. - p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace - if p.Task.Namespace != "" { - p.KubeNamespace = p.Task.Namespace + switch p.Task.ClusterID { + case setting.LocalClusterID: + p.KubeNamespace = zadigconfig.Namespace() + default: + p.KubeNamespace = setting.AttachedClusterNamespace + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index bdec6c89..f2c6e16f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -29,6 +29,7 @@ import ( "gopkg.in/yaml.v3" "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" @@ -107,9 +108,12 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC // TODO: Since the namespace field has been used continuously since v1.10.0, the processing logic related to namespace needs to // be deleted in v1.11.0. - p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace - if p.Task.Namespace != "" { - p.KubeNamespace = p.Task.Namespace + switch p.Task.ClusterID { + case setting.LocalClusterID: + p.KubeNamespace = zadigconfig.Namespace() + default: + p.KubeNamespace = setting.AttachedClusterNamespace + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) @@ -120,6 +124,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } p.kubeClient = kubeClient } + // not local cluster var ( replaceDindServer = "." + DindServer diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 2c15370c..4e558e77 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -612,3 +612,7 @@ const ( const ( InformerNamingConvention = "%s-%s" ) + +// AttachedClusterNamespace is the namespace Zadig uses in attached cluster. +// Note: **Restricted because of product design since v1.9.0**. +const AttachedClusterNamespace = "koderover-agent" -- Gitee From ac71f95b30f82f4ef7e3d8e90cfc32578fb9d45b Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 20:19:19 +0800 Subject: [PATCH 1623/2647] sshs Signed-off-by: mouuii --- pkg/microservice/aslan/core/common/repository/models/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 0456f17a..152748d6 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -46,7 +46,7 @@ type Build struct { Caches []string `bson:"caches" json:"caches"` ProductName string `bson:"product_name" json:"product_name"` - SSHs []string `bson:"sshs,omitempty" json:"sshs,omitempty"` + SSHs []string `bson:"sshs" json:"sshs"` PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` // New since V1.10.0. -- Gitee From 6714abf6474addbcde6c8973aa2388a8e47d18bd Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 21 Feb 2022 20:21:46 +0800 Subject: [PATCH 1624/2647] sshs Signed-off-by: mouuii --- .../aslan/core/common/repository/models/build.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 152748d6..4c38a63a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -43,10 +43,9 @@ type Build struct { PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` // TODO: Deprecated. - Caches []string `bson:"caches" json:"caches"` - + Caches []string `bson:"caches" json:"caches"` ProductName string `bson:"product_name" json:"product_name"` - SSHs []string `bson:"sshs" json:"sshs"` + SSHs []string `bson:"sshs" json:"sshs"` PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` // New since V1.10.0. -- Gitee From e623ee104a30cbaac6a3d64a8910c2c76165a3cd Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 21 Feb 2022 20:41:05 +0800 Subject: [PATCH 1625/2647] fix get test html report Signed-off-by: ddh27 --- .../common/service/instantmessage/service.go | 16 +++++++++++++--- .../aslan/core/common/service/notify/client.go | 2 +- .../core/workflow/testing/service/testing.go | 6 +++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index c4b8b70b..f5444632 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -344,7 +344,13 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if testSt.TaskStatus == "" { testSt.TaskStatus = config.StatusNotRun } - test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testSt.TestModuleName) + if testSt.JobCtx.TestType == setting.FunctionTest && testSt.JobCtx.TestReportPath != "" { + url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testSt.TestModuleName) + test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testSt.TestModuleName, url) + } else { + test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testSt.TestModuleName) + } + if weChatNotification.Task.TestReports == nil { test += fmt.Sprintf("%s \n", testSt.TaskStatus) continue @@ -433,8 +439,12 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech continue } if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestResultPath != "" { - url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testInfo.TestName) - tplTestCaseInfo += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testName, url) + if testInfo.JobCtx.TestReportPath != "" { + url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testName) + tplTestCaseInfo += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testName, url) + } else { + tplTestCaseInfo += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testName) + } if weChatNotification.Task.TestReports == nil { tplTestCaseInfo += fmt.Sprintf("%s \n ", testInfo.TaskStatus) continue diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index bface2c9..b07010b9 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -219,7 +219,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } logger.Infof("workflow get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookComment(task, logger) - + task.Stages = ctx.Stages } else if ctx.Type == config.TestType { logger.Infof("test get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookCommentForTest(task, logger) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/testing.go b/pkg/microservice/aslan/core/workflow/testing/service/testing.go index 0ade2e7d..2c8dae30 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/testing.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/testing.go @@ -298,9 +298,9 @@ func GetHTMLTestReport(pipelineName, pipelineType, taskIDStr, testName string, l return "", e.ErrGetTestReport.AddDesc("invalid taskID") } - task, err := commonrepo.NewTaskColl().Find(taskID, pipelineName, config.WorkflowType) + task, err := commonrepo.NewTaskColl().Find(taskID, pipelineName, config.PipelineType(pipelineType)) if err != nil { - log.Errorf("find task failed, pipelineName: %s, type: %s, taskID: %s, err: %s", pipelineName, config.WorkflowType, taskIDStr, err) + log.Errorf("find task failed, pipelineName: %s, type: %s, taskID: %s, err: %s", pipelineName, config.PipelineType(pipelineType), taskIDStr, err) return "", e.ErrGetTestReport.AddErr(err) } @@ -373,7 +373,7 @@ func validateTestReportParam(pipelineName, pipelineType, taskIDStr, testName str return fmt.Errorf("testName cannot be empty") } - if pipelineType != string(config.WorkflowType) { + if pipelineType != string(config.WorkflowType) && pipelineType != string(config.TestType) { log.Warn("pipelineType is invalid") return fmt.Errorf("pipelineType is invalid") } -- Gitee From e3913231a2a760f20bb72064221f448083d73d02 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 13:55:53 +0800 Subject: [PATCH 1626/2647] import helm chart from gerrit Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 19 ++- .../aslan/core/common/service/helm.go | 4 +- .../aslan/core/common/service/service.go | 1 - .../aslan/core/service/service/helm.go | 151 +++++++++++++++++- .../aslan/core/service/service/types.go | 3 +- 5 files changed, 164 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 2f3c711a..9d609131 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -47,17 +47,20 @@ func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) err } // SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. -func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string, logger *zap.SugaredLogger) error { +func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string, logger *zap.SugaredLogger, source ...string) error { var wg wait.Group var err error - wg.Start(func() { - err1 := saveInMemoryFilesToDisk(fileTree, localBase, logger) - if err1 != nil { - logger.Errorf("Failed to save files to disk, err: %s", err1) - err = err1 - } - }) + if len(source) == 0 { + wg.Start(func() { + err1 := saveInMemoryFilesToDisk(fileTree, localBase, logger) + if err1 != nil { + logger.Errorf("Failed to save files to disk, err: %s", err1) + err = err1 + } + }) + } + wg.Start(func() { err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) if err2 != nil { diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index f8ddc6c8..061a2440 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -80,11 +80,11 @@ func DownloadServiceManifests(base, projectName, serviceName string) error { return fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) } -func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS) error { +func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS, source ...string) error { localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) + return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger(), source) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 5ff73965..316dc2b4 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -482,7 +482,6 @@ func UpdatePmServiceTemplate(username string, args *ServiceTmplBuildObject, log return e.ErrUpdateService.AddDesc("update current build failed") } } - } if err := commonrepo.NewServiceColl().Create(preService); err != nil { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 154145f6..0af03b13 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -21,6 +21,7 @@ import ( "fmt" "io/fs" "os" + "path" "path/filepath" "strings" "sync" @@ -294,9 +295,11 @@ func copyChartRevision(projectName, serviceName string, revision int64) error { return nil } -func clearChartFiles(projectName, serviceName string, revision int64, logger *zap.SugaredLogger) { +func clearChartFiles(projectName, serviceName string, revision int64, logger *zap.SugaredLogger, source ...string) { clearChartFilesInS3Storage(projectName, serviceName, revision, logger) - clearLocalChartFiles(projectName, serviceName, revision, logger) + if len(source) == 0 { + clearLocalChartFiles(projectName, serviceName, revision, logger) + } } // clear chart files in s3 storage @@ -326,6 +329,8 @@ func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) case LoadFromChartTemplate: return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) + case LoadFromGerrit: + return CreateOrUpdateHelmServiceFromGerrit(projectName, args, logger) default: return nil, fmt.Errorf("invalid source") } @@ -458,6 +463,148 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *system return ch.Type, ch, nil } +func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { + var ( + filePaths []string + response = &BulkHelmServiceCreationResponse{} + base string + chartTree = afero.NewMemMapFs() + ) + repoArgs, ok := args.CreateFrom.(*CreateFromRepo) + if !ok { + filePaths = repoArgs.Paths + base = path.Join(config.S3StoragePath(), repoArgs.Repo) + } + + helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(repoArgs.Paths)) + var wg wait.Group + var mux sync.RWMutex + for _, p := range filePaths { + filePath := strings.TrimLeft(p, "/") + wg.Start(func() { + var ( + serviceName string + chartVersion string + valuesYAML []byte + finalErr error + ) + defer func() { + mux.Lock() + if finalErr != nil { + response.FailedServices = append(response.FailedServices, &FailedService{ + Path: filePath, + Error: finalErr.Error(), + }) + } else { + response.SuccessServices = append(response.SuccessServices, serviceName) + } + mux.Unlock() + }() + + //fsTree, err := fsservice.DownloadFilesFromSource( + // &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, + // func(chartTree afero.Fs) (string, error) { + // var err error + // serviceName, chartVersion, err = readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + // if err != nil { + // return serviceName, err + // } + // valuesYAML, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + // return serviceName, err + // }) + //if err != nil { + // log.Errorf("Failed to download files from source, err %s", err) + // finalErr = e.ErrCreateTemplate.AddErr(err) + // return + //} + + filePath := path.Join(base, filePath) + log.Infof("Loading chart under path %s", filePath) + serviceName, chartVersion, finalErr = readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + if finalErr != nil { + log.Errorf("readChartYAML err:%+v", finalErr) + return + } + valuesYAML, finalErr = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + if finalErr != nil { + log.Errorf("readValuesYAML err:%+v", finalErr) + return + } + log.Infof("serviceName:%s,chartVersion:%s", serviceName, chartVersion) + log.Infof("valuesYAML:%s", string(valuesYAML)) + log.Info("Found valid chart, Starting to save and upload files") + rev, err := getNextServiceRevision(projectName, serviceName) + if err != nil { + log.Errorf("Failed to get next revision for service %s, err: %s", serviceName, err) + finalErr = e.ErrCreateTemplate.AddErr(err) + return + } + + // clear files from s3 when error occurred in next stages + defer func() { + if finalErr != nil { + clearChartFiles(projectName, serviceName, rev, log, string(args.Source)) + } + }() + + // upload them to s3 + if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree), string(args.Source)); err != nil { + log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) + finalErr = e.ErrCreateTemplate.AddErr(err) + return + } + + err = copyChartRevision(projectName, serviceName, rev) + if err != nil { + log.Errorf("Failed to copy file %s, err: %s", serviceName, err) + finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", serviceName) + return + } + + //if codehostInfo != nil { + // repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) + //} + + svc, err := createOrUpdateHelmService( + afero.NewIOFS(chartTree), + &helmServiceCreationArgs{ + ChartName: serviceName, + ChartVersion: chartVersion, + ServiceRevision: rev, + MergedValues: string(valuesYAML), + ServiceName: serviceName, + FilePath: filePath, + ProductName: projectName, + CreateBy: args.CreatedBy, + CodehostID: repoArgs.CodehostID, + Owner: repoArgs.Owner, + Repo: repoArgs.Repo, + Branch: repoArgs.Branch, + //RepoLink: repoLink, + Source: string(args.Source), + }, + log, + ) + if err != nil { + log.Errorf("Failed to create service %s in project %s, error: %s", serviceName, projectName, err) + finalErr = e.ErrCreateTemplate.AddErr(err) + return + } + + helmRenderCharts = append(helmRenderCharts, &templatemodels.RenderChart{ + ServiceName: serviceName, + ChartVersion: svc.HelmChart.Version, + ValuesYaml: svc.HelmChart.ValuesYaml, + }) + }) + } + + wg.Wait() + + compareHelmVariable(helmRenderCharts, projectName, args.CreatedBy, log) + return response, nil +} + func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { var err error var repoLink string diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index 430c4783..a9cca690 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -27,7 +27,8 @@ import ( type LoadSource string const ( - LoadFromRepo LoadSource = "repo" + LoadFromRepo LoadSource = "repo" //exclude gerrit + LoadFromGerrit LoadSource = "gerrit" LoadFromPublicRepo LoadSource = "publicRepo" LoadFromChartTemplate LoadSource = "chartTemplate" ) -- Gitee From 7d59b3ad39c6fe1db8050fb84157f3f7babae6c5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 22 Feb 2022 14:09:05 +0800 Subject: [PATCH 1627/2647] conflix Signed-off-by: mouuii --- examples/webhook/trigger_example.yaml | 4 +- pkg/cli/initconfig/cmd/init.go | 21 ++ pkg/cli/upgradeassistant/cmd/migrate/1100.go | 19 ++ .../internal/repository/orm/addmysql.sql | 3 + .../internal/repository/orm/db.go | 63 ++++ .../internal/repository/orm/dropmysql.sql | 5 + pkg/microservice/aslan/config/consts.go | 1 + .../core/common/repository/models/build.go | 5 +- .../core/common/repository/models/notify.go | 1 + .../core/common/repository/models/queue.go | 3 + .../common/repository/models/settings.go} | 20 +- .../repository/models/task/extension.go | 54 ++++ .../common/repository/models/task/model.go | 8 + .../core/common/repository/models/workflow.go | 10 + .../common/repository/mongodb/settings.go | 98 +++++++ .../aslan/core/common/service/base/convert.go | 8 + .../core/common/service/config_payload.go | 8 +- .../core/common/service/notify/client.go | 2 +- .../common/service/scmnotify/scmnotify.go | 76 ----- .../aslan/core/common/service/task.go | 10 +- .../aslan/core/environment/service/env.go | 2 +- .../core/environment/service/environment.go | 157 +++++++--- .../service/environment_creator.go | 14 +- .../aslan/core/environment/service/k8s.go | 16 +- .../aslan/core/environment/service/pm.go | 2 +- .../aslan/core/environment/service/service.go | 12 +- .../aslan/core/log/service/sse.go | 20 +- .../aslan/core/multicluster/service/cache.go | 6 +- .../core/multicluster/service/clusters.go | 2 +- .../core/multicluster/service/constants.go | 4 - pkg/microservice/aslan/core/service.go | 2 + .../aslan/core/system/handler/concurrency.go | 52 ++++ .../aslan/core/system/handler/router.go | 7 + .../aslan/core/system/service/concurrency.go | 63 ++++ .../aslan/core/system/service/types.go | 5 + .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_task.go | 13 + .../service/webhook/gitlab_workflow_task.go | 132 ++------- .../workflow/service/webhook/trigger_yaml.go | 43 ++- .../core/workflow/service/webhook/utils.go | 70 +++-- .../service/workflow/artifact_task.go | 4 +- .../service/workflow/pipeline_task.go | 12 +- .../workflow/service/workflow/service_task.go | 13 +- .../core/workflow/service/workflow/sort.go | 1 + .../service/workflow/workflow_task.go | 274 +++++++++++------- .../service/workflow/workflow_task_v3.go | 4 +- .../user/core/repository/models/user.go | 1 + .../user/core/service/init/mysql.sql | 1 + .../user/core/service/user/user.go | 36 ++- pkg/microservice/warpdrive/config/const.go | 1 + .../warpdrive/core/service/common/types.go | 29 ++ .../service/taskcontroller/task_handler.go | 13 +- .../service/taskcontroller/task_helper.go | 12 +- .../core/service/taskplugin/build.go | 10 +- .../core/service/taskplugin/deploy.go | 29 +- .../core/service/taskplugin/extension.go | 256 ++++++++++++++++ .../core/service/taskplugin/testing.go | 11 +- .../core/service/taskplugin/trigger.go | 2 +- .../core/service/taskplugin/utils.go | 8 + .../warpdrive/core/service/types/notify.go | 6 +- .../core/service/types/task/config_payload.go | 3 + .../core/service/types/task/extension.go | 55 ++++ .../core/service/types/task/model.go | 13 +- .../core/service/types/task/trigger.go | 14 +- pkg/setting/consts.go | 4 + pkg/shared/client/aslan/workflow.go | 44 +++ pkg/util/yaml/path_search.go | 7 +- pkg/util/yaml/path_search_test.go | 30 ++ 68 files changed, 1478 insertions(+), 457 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql create mode 100644 pkg/cli/upgradeassistant/internal/repository/orm/db.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql rename pkg/microservice/{warpdrive/core/service/taskcontroller/const.go => aslan/core/common/repository/models/settings.go} (48%) create mode 100644 pkg/microservice/aslan/core/common/repository/models/task/extension.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/settings.go create mode 100644 pkg/microservice/aslan/core/system/handler/concurrency.go create mode 100644 pkg/microservice/aslan/core/system/service/concurrency.go create mode 100644 pkg/microservice/warpdrive/core/service/common/types.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/extension.go create mode 100644 pkg/microservice/warpdrive/core/service/types/task/extension.go create mode 100644 pkg/shared/client/aslan/workflow.go diff --git a/examples/webhook/trigger_example.yaml b/examples/webhook/trigger_example.yaml index 45245b98..a0d4ad2f 100644 --- a/examples/webhook/trigger_example.yaml +++ b/examples/webhook/trigger_example.yaml @@ -10,13 +10,13 @@ build: variables: # zadig 系统中需要先声明出来才能指定,否则这边配置也无效 - name: k1 value: v1 -# 部署策略 +# 部署策略 (该阶段不可缺) deploy: strategy: single # single(更新指定环境)/dynamic(动态选择空闲环境)/base(更新基准环境,需配置 env_recycle_policy 和 base_env) envs_name: - dev - # strategy 值为 base 时才需配置以下变量 + # strategy 值为 base 时才需配置以下变量;该策略仅pull_request时生效 # env_recycle_policy: success # success(成功后销毁)/always(每次销毁)/never(每次保留) # base_env: dev # 测试参数 diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 30fafc4a..7ecaf723 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -31,7 +31,9 @@ import ( "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/shared/client/user" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" ) @@ -109,9 +111,28 @@ func initSystemConfig() error { return err } + if err := scaleWarpdrive(); err != nil { + log.Errorf("scale warpdrive err: %s", err) + return err + } + return nil } +func scaleWarpdrive() error { + cfg, err := aslan.New(config.AslanServiceAddress()).GetWorkflowConcurrencySetting() + if err == nil { + client, err := kubeclient.GetKubeClient(config.HubServerServiceAddress(), setting.LocalClusterID) + if err != nil { + return err + } + return updater.ScaleDeployment(config.Namespace(), config.WarpDriveServiceName(), int(cfg.WorkflowConcurrency), client) + } + + log.Errorf("Failed to get workflow concurrency settings, error: %s", err) + return err +} + func presetSystemAdmin(email string, password, domain string) (string, error) { r, err := user.New().SearchUser(&user.SearchUserArgs{Account: setting.PresetAccount}) if err != nil { diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 877735e3..1f922dda 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -26,6 +26,7 @@ import ( "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/orm" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/tool/log" @@ -57,6 +58,10 @@ func V190ToV1100() error { return fmt.Errorf("failed to migrate data in `zadig.module_testing`: %s", err) } + log.Info("UpdateUserDBTables: ADD cloumn from mysql table `user`.") + if err := orm.UpdateUserDBTables(orm.DbEditActionAdd); err != nil { + return fmt.Errorf("UpdateUserDBTables: failed to ADD cloumn from mysql table `user`: %s", err) + } return nil } @@ -113,10 +118,16 @@ func rollbackChangePolicyCollectionName() error { } func V1100ToV190() error { + log.Info("Rollback data from 1.10.0 to 1.9.0") if err := rollbackChangePolicyCollectionName(); err != nil { log.Errorf("Failed to rollbackChangePolicyCollectionName,err: %s", err) return err } + + log.Info("UpdateUserDBTables: drop cloumn from mysql table `user`.") + if err := orm.UpdateUserDBTables(orm.DbEditActionDrop); err != nil { + return fmt.Errorf("UpdateUserDBTables: failed to drop cloumn from mysql table `user`: %s", err) + } return nil } @@ -138,6 +149,10 @@ func migrateModuleBuild() error { return fmt.Errorf("failed to list all data in `zadig.module_build`: %s", err) } + if len(builds) == 0 { + return nil + } + var ms []mongo.WriteModel for _, build := range builds { if err := migrateOneBuild(build); err != nil { @@ -170,6 +185,10 @@ func migrateModuleTesting() error { return fmt.Errorf("failed to list all data in `zadig.module_testing`: %s", err) } + if len(testings) == 0 { + return nil + } + var ms []mongo.WriteModel for _, testing := range testings { if err := migrateOneTesting(testing); err != nil { diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql new file mode 100644 index 00000000..2fb64081 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql @@ -0,0 +1,3 @@ +use user; + +ALTER TABLE `user` ADD `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken'; \ No newline at end of file diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/db.go b/pkg/cli/upgradeassistant/internal/repository/orm/db.go new file mode 100644 index 00000000..3fa9c9cd --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/orm/db.go @@ -0,0 +1,63 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package orm + +import ( + "database/sql" + _ "embed" + "fmt" + + _ "github.com/go-sql-driver/mysql" + + "github.com/koderover/zadig/pkg/config" +) + +type DbEditAction string + +const ( + DbEditActionAdd DbEditAction = "add" + DbEditActionDrop DbEditAction = "drop" +) + +//go:embed addmysql.sql +var addmysql []byte + +//go:embed dropmysql.sql +var dropmysql []byte + +func UpdateUserDBTables(action DbEditAction) error { + var mysql []byte + switch action { + case DbEditActionAdd: + mysql = addmysql + case DbEditActionDrop: + mysql = dropmysql + } + if len(mysql) == 0 { + return fmt.Errorf("%smysql.sql is empty", action) + } + db, err := sql.Open("mysql", fmt.Sprintf( + "%s:%s@tcp(%s)/?charset=utf8&multiStatements=true", + config.MysqlUser(), config.MysqlPassword(), config.MysqlHost(), + )) + if err != nil { + return err + } + defer db.Close() + _, err = db.Exec(string(mysql)) + return err +} diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql new file mode 100644 index 00000000..cb67a73b --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql @@ -0,0 +1,5 @@ +use user; + +LOCK TABLES `user` WRITE; + ALTER TABLE `user` DROP COLUMN `api_token`; + UNLOCK TABLES; \ No newline at end of file diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index d285e50f..3cce480c 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -139,6 +139,7 @@ const ( TaskResetImage TaskType = "reset_image" TaskDistribute TaskType = "distribute" TaskTrigger TaskType = "trigger" + TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" ) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 0456f17a..4c38a63a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -43,10 +43,9 @@ type Build struct { PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` // TODO: Deprecated. - Caches []string `bson:"caches" json:"caches"` - + Caches []string `bson:"caches" json:"caches"` ProductName string `bson:"product_name" json:"product_name"` - SSHs []string `bson:"sshs,omitempty" json:"sshs,omitempty"` + SSHs []string `bson:"sshs" json:"sshs"` PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` // New since V1.10.0. diff --git a/pkg/microservice/aslan/core/common/repository/models/notify.go b/pkg/microservice/aslan/core/common/repository/models/notify.go index 88ae5d97..af7c0e9b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/notify.go +++ b/pkg/microservice/aslan/core/common/repository/models/notify.go @@ -46,6 +46,7 @@ type PipelineStatusCtx struct { Type config.PipelineType `bson:"type" json:"type"` Status config.Status `bson:"status" json:"status,omitempty"` TeamName string `bson:"team" json:"team"` + Stages []*Stage `bson:"-" json:"stages"` } type MessageCtx struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 4c299d8c..93d630ef 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -150,6 +150,9 @@ type ConfigPayload struct { // RegistryID is the id of product registry RegistryID string `json:"registry_id"` + + // build concurrency settings + BuildConcurrency int64 `json:"build_concurrency"` } type AslanConfig struct { diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/const.go b/pkg/microservice/aslan/core/common/repository/models/settings.go similarity index 48% rename from pkg/microservice/warpdrive/core/service/taskcontroller/const.go rename to pkg/microservice/aslan/core/common/repository/models/settings.go index e7de59fd..d51d4cdc 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/const.go +++ b/pkg/microservice/aslan/core/common/repository/models/settings.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,9 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package taskcontroller +package models -const ( - // MaxWorkerInParallel is max worker concurrency - maxWorkerInParallel = 5 -) +import "go.mongodb.org/mongo-driver/bson/primitive" + +type SystemSetting struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + WorkflowConcurrency int64 `bson:"workflow_concurrency" json:"workflow_concurrency"` + BuildConcurrency int64 `bson:"build_concurrency" json:"build_concurrency"` + UpdateTime int64 `bson:"update_time" json:"update_time"` +} + +func (SystemSetting) TableName() string { + return "system_setting" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go new file mode 100644 index 00000000..cc8549c3 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -0,0 +1,54 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type Extension struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type ServiceInfo struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` +} + +func (t *Extension) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert extension to interface error: %s", err) + } + return task, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 64631bdb..ccdb9c16 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -163,3 +163,11 @@ type Error struct { Type string `bson:"type" json:"type" xml:"type,attr"` Text string `bson:"text" json:"text" xml:",chardata"` } + +type TaskOpt struct { + Task *Task `json:"task"` + EnvName string `json:"env_name"` + ServiceName string `json:"service_name"` + ServiceInfos *[]*ServiceInfo `json:"service_infos"` + IsWorkflowTask bool `json:"is_workflow_task"` +} diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 358a3f60..c868430e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -47,6 +47,7 @@ type Workflow struct { TestStage *TestStage `bson:"test_stage" json:"test_stage"` SecurityStage *SecurityStage `bson:"security_stage" json:"security_stage"` DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` + ExtensionStage *ExtensionStage `bson:"extension_stage" json:"extension_stage"` NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` BaseName string `bson:"base_name" json:"base_name"` @@ -263,6 +264,15 @@ type DistributeStage struct { Releases []RepoImage `bson:"releases" json:"releases"` } +type ExtensionStage struct { + Enabled bool `bson:"enabled" json:"enabled"` + URL string `bson:"url" json:"url"` + Path string `bson:"path" json:"path"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Timeout int `bson:"timeout" json:"timeout"` + Headers []*KeyVal `bson:"headers" json:"headers"` +} + type RepoImage struct { RepoID string `json:"repo_id" bson:"repo_id"` Name string `json:"name" bson:"name" yaml:"name"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go new file mode 100644 index 00000000..21634eb8 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go @@ -0,0 +1,98 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type SystemSettingColl struct { + *mongo.Collection + + coll string +} + +func NewSystemSettingColl() *SystemSettingColl { + name := models.SystemSetting{}.TableName() + return &SystemSettingColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *SystemSettingColl) GetCollectionName() string { + return c.coll +} + +func (c *SystemSettingColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *SystemSettingColl) Get() (*models.SystemSetting, error) { + query := bson.M{} + resp := &models.SystemSetting{} + + err := c.FindOne(context.TODO(), query).Decode(resp) + return resp, err +} + +func (c *SystemSettingColl) UpdateConcurrencySetting(workflowConcurrency, buildConcurrency int64) error { + id, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) + change := bson.M{"$set": bson.M{ + "workflow_concurrency": workflowConcurrency, + "build_concurrency": buildConcurrency, + }} + query := bson.M{"_id": id} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *SystemSettingColl) InitSystemSettings() error { + _, err := c.Get() + // if we didn't find anything + if err != nil { + return c.CreateOrUpdate(setting.LocalClusterID, &models.SystemSetting{ + WorkflowConcurrency: 2, + BuildConcurrency: 5, + }) + } + return nil +} + +func (c *SystemSettingColl) CreateOrUpdate(id string, args *models.SystemSetting) error { + var objectID primitive.ObjectID + if id != "" { + objectID, _ = primitive.ObjectIDFromHex(id) + } else { + objectID = primitive.NewObjectID() + } + + args.UpdateTime = time.Now().Unix() + + query := bson.M{"_id": objectID} + change := bson.M{"$set": args} + _, err := c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/base/convert.go b/pkg/microservice/aslan/core/common/service/base/convert.go index 97ec5a08..6a44a7bd 100644 --- a/pkg/microservice/aslan/core/common/service/base/convert.go +++ b/pkg/microservice/aslan/core/common/service/base/convert.go @@ -131,3 +131,11 @@ func ToTriggerTask(sb map[string]interface{}) (*task.Trigger, error) { } return trigger, nil } + +func ToExtensionTask(sb map[string]interface{}) (*task.Extension, error) { + var extension *task.Extension + if err := task.IToi(sb, &extension); err != nil { + return nil, fmt.Errorf("convert interface to extensionTask error: %s", err) + } + return extension, nil +} diff --git a/pkg/microservice/aslan/core/common/service/config_payload.go b/pkg/microservice/aslan/core/common/service/config_payload.go index 5a17f53b..bf94e036 100644 --- a/pkg/microservice/aslan/core/common/service/config_payload.go +++ b/pkg/microservice/aslan/core/common/service/config_payload.go @@ -66,7 +66,8 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { JenkinsBuildConfig: models.JenkinsBuildConfig{ JenkinsBuildImage: config.JenkinsImage(), }, - AesKey: crypto.GetAesKey(), + AesKey: crypto.GetAesKey(), + BuildConcurrency: 5, } githubApps, _ := mongodb.NewGithubAppColl().Find() @@ -87,6 +88,11 @@ func GetConfigPayload(codeHostID int) *models.ConfigPayload { payload.Proxy = *proxies[0] } + concurrencySettings, _ := mongodb.NewSystemSettingColl().Get() + if concurrencySettings != nil { + payload.BuildConcurrency = concurrencySettings.BuildConcurrency + } + privateKeys, _ := mongodb.NewPrivateKeyColl().List(&mongodb.PrivateKeyArgs{}) if len(privateKeys) != 0 { payload.PrivateKeys = privateKeys diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index d8b951a9..bface2c9 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -219,7 +219,6 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } logger.Infof("workflow get task #%d notify, status: %s", ctx.TaskID, ctx.Status) _ = c.scmNotifyService.UpdateWebhookComment(task, logger) - _ = c.scmNotifyService.UpdateDiffNote(task, logger) } else if ctx.Type == config.TestType { logger.Infof("test get task #%d notify, status: %s", ctx.TaskID, ctx.Status) @@ -233,6 +232,7 @@ func (c *client) ProccessNotify(notify *models.Notify) error { testTaskStatusChanged = true } } + task.Stages = ctx.Stages } err = c.InstantmessageService.SendInstantMessage(task, testTaskStatusChanged) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index 48bc25d3..a265cfe6 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -23,7 +23,6 @@ import ( "os" "strings" - "github.com/xanzy/go-gitlab" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -234,81 +233,6 @@ func (s *Service) UpdateWebhookComment(task *task.Task, logger *zap.SugaredLogge return nil } -// UpdateDiffNote 调用gitlab接口更新DiffNote,并更新到数据库 -func (s *Service) UpdateDiffNote(task *task.Task, logger *zap.SugaredLogger) (err error) { - if task.WorkflowArgs.NotificationID == "" { - return - } - - var notification *models.Notification - if notification, err = s.Coll.Find(task.WorkflowArgs.NotificationID); err != nil { - logger.Errorf("can't find notification by id %s %s", task.WorkflowArgs.NotificationID, err) - return err - } - - isAllTaskSucceed := true - for _, nTask := range notification.Tasks { - if nTask.Status != config.TaskStatusFailed && nTask.Status != config.TaskStatusCancelled && - nTask.Status != config.TaskStatusPass && nTask.Status != config.TaskStatusTimeout { - // 存在任务没有执行完,直接返回 - return nil - } - // 任务都执行完了,确认是否有未成功的任务 - if nTask.Status != config.TaskStatusPass { - isAllTaskSucceed = false - break - } - } - - body := "KodeRover CI 检查通过" - if !isAllTaskSucceed { - body = "KodeRover CI 检查失败" - } - - opt := &mongodb.DiffNoteFindOpt{ - CodehostID: notification.CodehostID, - ProjectID: notification.ProjectID, - MergeRequestID: notification.PrID, - } - diffNote, err := s.DiffNoteColl.Find(opt) - if err != nil { - logger.Errorf("can't find notification by id %s %v", task.WorkflowArgs.NotificationID, err) - return err - } - - cli, _ := gitlab.NewOAuthClient(diffNote.Repo.OauthToken, gitlab.WithBaseURL(diffNote.Repo.Address)) - - // 更新note body - noteBodyOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Body: &body, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, noteBodyOpt) - if err != nil { - logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) - return err - } - - // 更新resolved状态 - resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Resolved: &isAllTaskSucceed, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(diffNote.Repo.ProjectID, diffNote.MergeRequestID, diffNote.DiscussionID, diffNote.NoteID, resolveOpt) - if err != nil { - logger.Errorf("UpdateMergeRequestDiscussionNote failed, err: %v", err) - return err - } - - diffNote.Resolved = isAllTaskSucceed - diffNote.Body = body - err = s.DiffNoteColl.Update(diffNote.ObjectID.Hex(), "", diffNote.Body, diffNote.Resolved) - if err != nil { - logger.Errorf("UpdateDiscussionInfo failed, err: %v", err) - return err - } - - return nil -} - func downloadReport(taskInfo *task.Task, fileName, testName string, logger *zap.SugaredLogger) (*models.TestSuite, error) { var store *s3.S3 var err error diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index 010a2a52..4ad1c455 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" @@ -82,7 +83,6 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P if typeString == config.WorkflowType { _ = scmNotifyService.UpdateWebhookComment(t, log) - _ = scmNotifyService.UpdateDiffNote(t, log) } else if typeString == config.TestType { _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { @@ -130,7 +130,6 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P if typeString == config.WorkflowType { _ = scmNotifyService.UpdateWebhookComment(t, log) - _ = scmNotifyService.UpdateDiffNote(t, log) } else if typeString == config.TestType { _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { @@ -171,3 +170,10 @@ func covertTaskToQueue(t *task.Task) *models.Queue { CreateTime: t.CreateTime, } } + +func GetWorkflowTaskCallback(taskID int64, pipelineName string) (*commonmodels.CallbackRequest, error) { + return mongodb.NewCallbackRequestColl().Find(&mongodb.CallbackFindOption{ + TaskID: taskID, + PipelineName: pipelineName, + }) +} diff --git a/pkg/microservice/aslan/core/environment/service/env.go b/pkg/microservice/aslan/core/environment/service/env.go index b213381a..f56c2a54 100644 --- a/pkg/microservice/aslan/core/environment/service/env.go +++ b/pkg/microservice/aslan/core/environment/service/env.go @@ -27,7 +27,7 @@ import ( ) type envHandle interface { - createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error + createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, inf informers.SharedInformerFactory, kubeClient client.Client) error listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp updateService(args *SvcOptArgs) error queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient informers.SharedInformerFactory) (string, string, []string) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d6958e85..20bd640f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -37,10 +37,12 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -58,6 +60,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -461,6 +464,18 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com return e.ErrUpdateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), existedProd.ClusterID) + if err != nil { + log.Errorf("[%s][%s] error: %v", envName, namespace, err) + return e.ErrUpdateEnv.AddDesc(err.Error()) + + } + inf, err := informer.NewInformer(existedProd.ClusterID, namespace, cls) + if err != nil { + log.Errorf("[%s][%s] error: %v", envName, namespace, err) + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + // 遍历产品环境和产品模板交叉对比的结果 // 四个状态:待删除,待添加,待更新,无需更新 @@ -553,7 +568,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com updateProd, service, existedServices[service.ServiceName], - renderSet, kubeClient, log) + renderSet, inf, kubeClient, log) if err != nil { lock.Lock() switch e := err.(type) { @@ -1223,25 +1238,32 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa proSvcMap := productInfo.GetServiceMap() proSvc := proSvcMap[serviceName] if proSvc != nil { - existUpdate, err := checkServiceImageUpdated(productName, serviceName, proSvc) + curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + Revision: proSvc.Revision, + }) if err != nil { - log.Errorf("checkServiceImageUpdated, productName %s,svcname %s,err %s", productName, serviceName, err) - return "", "", fmt.Errorf("checkServiceImageUpdated,productName %s, svcname %s,err %s", productName, serviceName, err) - } - if !existUpdate { - for _, container := range templateService.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, proSvc.Revision, err) + return "", "", fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, proSvc.Revision, err) + } + L: + for _, curSvcContainer := range curEnvService.Containers { + if checkServiceImageUpdated(curSvcContainer, proSvc) { + for _, container := range templateService.Containers { + if curSvcContainer.Name == container.Name && container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + continue L + } } } } } - curValuesYaml := "" if targetChart != nil { // service has been applied into environment, use current values.yaml curValuesYaml = targetChart.ValuesYaml } - // merge environment values mergedBs, err := overrideValues([]byte(curValuesYaml), []byte(templateService.HelmChart.ValuesYaml), imageRelatedKey) if err != nil { @@ -1728,7 +1750,7 @@ func GetEstimatedRenderCharts(productName, envName, serviceNameListStr string, l return ret, nil } -func createGroups(envName, user, requestID string, args *commonmodels.Product, eventStart int64, renderSet *commonmodels.RenderSet, kubeClient client.Client, log *zap.SugaredLogger) { +func createGroups(envName, user, requestID string, args *commonmodels.Product, eventStart int64, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger) { var err error defer func() { status := setting.ProductStatusSuccess @@ -1755,7 +1777,7 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e }() for _, group := range args.Services { - err = envHandleFunc(getProjectType(args.ProductName), log).createGroup(envName, args.ProductName, user, group, renderSet, kubeClient) + err = envHandleFunc(getProjectType(args.ProductName), log).createGroup(envName, args.ProductName, user, group, renderSet, informer, kubeClient) if err != nil { args.Status = setting.ProductStatusFailed log.Errorf("createGroup error :%+v", err) @@ -1784,7 +1806,7 @@ func getProjectType(productName string) string { // upsertService 创建或者更新服务, 更新服务之前先创建服务需要的配置 func upsertService(isUpdate bool, env *commonmodels.Product, service *commonmodels.ProductService, prevSvc *commonmodels.ProductService, - renderSet *commonmodels.RenderSet, kubeClient client.Client, log *zap.SugaredLogger, + renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger, ) ([]*unstructured.Unstructured, error) { errList := &multierror.Error{ ErrorFormat: func(es []error) string { @@ -1890,10 +1912,20 @@ func upsertService(isUpdate bool, env *commonmodels.Product, } case setting.Deployment, setting.StatefulSet: + // compatibility flag, We add a match label in spec.selector field pre 1.10. + needSelectorLabel := false + u.SetNamespace(namespace) u.SetAPIVersion(setting.APIVersionAppsV1) u.SetLabels(kube.MergeLabels(labels, u.GetLabels())) + switch u.GetKind() { + case setting.Deployment: + needSelectorLabel = deploymentSelectorLabelExists(u.GetName(), namespace, informer, log) + case setting.StatefulSet: + needSelectorLabel = statefulsetSelectorLabelExists(u.GetName(), namespace, informer, log) + } + podLabels, _, err := unstructured.NestedStringMap(u.Object, "spec", "template", "metadata", "labels") if err != nil { podLabels = nil @@ -1914,6 +1946,20 @@ func upsertService(isUpdate bool, env *commonmodels.Product, u.Object = setFieldValueIsNotExist(u.Object, applyUpdatedAnnotations(podAnnotations), "spec", "template", "metadata", "annotations") } + if needSelectorLabel { + // Inject selector: s-product and s-service + selector, _, err := unstructured.NestedStringMap(u.Object, "spec", "selector", "matchLabels") + if err != nil { + selector = nil + } + + err = unstructured.SetNestedStringMap(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") + if err != nil { + log.Errorf("merge selector failed err:%s", err) + u.Object = setFieldValueIsNotExist(u.Object, kube.MergeLabels(labels, selector), "spec", "selector", "matchLabels") + } + } + jsonData, err := u.MarshalJSON() if err != nil { log.Errorf("Failed to marshal JSON, manifest is\n%v\n, error: %v", u, err) @@ -2822,15 +2868,24 @@ func diffRenderSet(username, productName, envName, updateType string, productRes serviceInfoCur := serviceMap[serviceName] imageRelatedKey := sets.NewString() if serviceInfoResp != nil && serviceInfoCur != nil { - existUpdate, err := checkServiceImageUpdated(productName, serviceName, serviceInfoCur) + curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + Revision: serviceInfoCur.Revision, + }) if err != nil { - log.Errorf("checkServiceImageUpdated,productName %s,svcname %s,err %s", productName, serviceName, err) - return nil, fmt.Errorf("checkServiceImageUpdated,productName %s,svcname %s,err %s", productName, serviceName, err) + log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) + return nil, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) } - if !existUpdate { - for _, container := range serviceInfoResp.Containers { - if container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + L: + for _, curSvcContainers := range curEnvService.Containers { + if checkServiceImageUpdated(curSvcContainers, serviceInfoCur) { + for _, container := range serviceInfoResp.Containers { + if curSvcContainers.Name == container.Name && container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + continue L + } } } } @@ -2880,29 +2935,13 @@ func diffRenderSet(username, productName, envName, updateType string, productRes } //checkServiceImageUpdated If the service does not do any mirroring iterations on the platform, the latest YAML is used when updating the environment -func checkServiceImageUpdated(productName, serviceName string, serviceInfo *commonmodels.ProductService) (bool, error) { - existUpdate := false - curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: serviceName, - ProductName: productName, - Type: setting.HelmDeployType, - Revision: serviceInfo.Revision, - }) - if err != nil { - log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfo.Revision, err) - return existUpdate, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfo.Revision, err) - } - -L: - for _, curContainer := range curEnvService.Containers { - for _, proContainer := range serviceInfo.Containers { - if curContainer.Image == proContainer.Image { - existUpdate = true - break L - } +func checkServiceImageUpdated(curContainer *commonmodels.Container, serviceInfo *commonmodels.ProductService) bool { + for _, proContainer := range serviceInfo.Containers { + if curContainer.Name == proContainer.Name && curContainer.Image == proContainer.Image { + return false } } - return existUpdate, nil + return true } // for keys exist in both yaml, current values will override latest values @@ -3034,3 +3073,37 @@ func setFieldValueIsNotExist(obj map[string]interface{}, value interface{}, fiel m[fields[len(fields)-1]] = value return obj } + +func deploymentSelectorLabelExists(resourceName, namespace string, informer informers.SharedInformerFactory, log *zap.SugaredLogger) bool { + deployment, err := informer.Apps().V1().Deployments().Lister().Deployments(namespace).Get(resourceName) + // default we assume the deployment is new so we don't need to add selector labels + if err != nil { + if !apierrors.IsNotFound(err) { + log.Errorf("Failed to find deployment in the namespace: %s, the error is: %s", namespace, err) + } + return false + } + // since the 2 predefined labels are always together, we just check for only one + // if the match label exists, we return true. otherwise we return false + if _, ok := deployment.Spec.Selector.MatchLabels["s-product"]; ok { + return true + } + return false +} + +func statefulsetSelectorLabelExists(resourceName, namespace string, informer informers.SharedInformerFactory, log *zap.SugaredLogger) bool { + sts, err := informer.Apps().V1().StatefulSets().Lister().StatefulSets(namespace).Get(resourceName) + // default we assume the deployment is new so we don't need to add selector labels + if err != nil { + if !apierrors.IsNotFound(err) { + log.Errorf("Failed to find deployment in the namespace: %s, the error is: %s", namespace, err) + } + return false + } + // since the 2 predefined labels are always together, we just check for only one + // if the match label exists, we return true. otherwise we return false + if _, ok := sts.Spec.Selector.MatchLabels["s-product"]; ok { + return true + } + return false +} diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index b57f6014..1531b0c5 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -34,6 +34,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" ) type CreateProductParam struct { @@ -282,7 +283,7 @@ func (creator *PMProductCreator) Create(user, requestID string, args *models.Pro return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, log) + go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, nil, log) return nil } @@ -312,6 +313,15 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + inf, err := informer.NewInformer(clusterID, args.Namespace, cls) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + //判断namespace是否存在 namespace := args.GetNamespace() if args.Namespace == "" { @@ -371,6 +381,6 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, kubeClient, log) + go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, inf, kubeClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 0b74a85c..8b02e2a9 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -37,6 +37,7 @@ import ( "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/informer" ) type K8sService struct { @@ -89,6 +90,15 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return e.ErrUpdateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), exitedProd.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + inf, err := informer.NewInformer(exitedProd.ClusterID, exitedProd.Namespace, cls) + if err != nil { + return e.ErrUpdateEnv.AddDesc(err.Error()) + } + switch exitedProd.Status { case setting.ProductStatusCreating, setting.ProductStatusUpdating, setting.ProductStatusDeleting: k.log.Errorf("[%s][P:%s] Product is not in valid status", args.EnvName, args.ProductName) @@ -112,7 +122,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { exitedProd, svc, exitedProd.GetServiceMap()[svc.ServiceName], - newRender, kubeClient, k.log) + newRender, inf, kubeClient, k.log) // 如果创建依赖服务组有返回错误, 停止等待 if err != nil { @@ -189,7 +199,7 @@ func (k *K8sService) listGroupServices(allServices []*commonmodels.ProductServic return resp } -func (k *K8sService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error { +func (k *K8sService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client) error { k.log.Infof("[Namespace:%s][Product:%s] createGroup", envName, productName) updatableServiceNameList := make([]string, 0) @@ -222,7 +232,7 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] updatableServiceNameList = append(updatableServiceNameList, group[i].ServiceName) go func(svc *commonmodels.ProductService) { defer wg.Done() - items, err := upsertService(false, prod, svc, nil, renderSet, kubeClient, k.log) + items, err := upsertService(false, prod, svc, nil, renderSet, informer, kubeClient, k.log) if err != nil { lock.Lock() switch e := err.(type) { diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 2d9c827b..733e1d05 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -153,7 +153,7 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService return resp } -func (p *PMService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, kubeClient client.Client) error { +func (p *PMService) createGroup(envName, productName, username string, group []*commonmodels.ProductService, renderSet *commonmodels.RenderSet, inf informers.SharedInformerFactory, kubeClient client.Client) error { p.log.Infof("[Namespace:%s][Product:%s] createGroup", envName, productName) // 异步创建无依赖的服务 diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 557ec3ab..b5565cba 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -39,6 +39,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -354,6 +355,15 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return err } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), productObj.ClusterID) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + inf, err := informer.NewInformer(productObj.ClusterID, productObj.Namespace, cls) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + // aws secrets needs to be refreshed regs, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { @@ -432,7 +442,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e productObj, productService, productService, - newRender, kubeClient, log) + newRender, inf, kubeClient, log) // 如果创建依赖服务组有返回错误, 停止等待 if err != nil { diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 1cd19e6d..869539aa 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -140,9 +140,15 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op }) } // Compatible with the situation where the old data has not been modified - if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" && build.PreBuild.Namespace != "" { + if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" { options.ClusterID = build.PreBuild.ClusterID - options.Namespace = build.PreBuild.Namespace + + switch build.PreBuild.ClusterID { + case setting.LocalClusterID: + options.Namespace = config.Namespace() + default: + options.Namespace = setting.AttachedClusterNamespace + } } } @@ -159,9 +165,15 @@ func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, // get cluster ID testing, _ := commonrepo.NewTestingColl().Find(getTestName(options.ServiceName), "") // Compatible with the situation where the old data has not been modified - if testing != nil && testing.PreTest != nil && testing.PreTest.ClusterID != "" && testing.PreTest.Namespace != "" { + if testing != nil && testing.PreTest != nil && testing.PreTest.ClusterID != "" { options.ClusterID = testing.PreTest.ClusterID - options.Namespace = testing.PreTest.Namespace + + switch testing.PreTest.ClusterID { + case setting.LocalClusterID: + options.Namespace = config.Namespace() + default: + options.Namespace = setting.AttachedClusterNamespace + } } waitAndGetLog(ctx, streamChan, selector, options, log) diff --git a/pkg/microservice/aslan/core/multicluster/service/cache.go b/pkg/microservice/aslan/core/multicluster/service/cache.go index aa7bf8bf..15c88f90 100644 --- a/pkg/microservice/aslan/core/multicluster/service/cache.go +++ b/pkg/microservice/aslan/core/multicluster/service/cache.go @@ -47,7 +47,7 @@ func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) // For cluster-level resources, we need to explicitly configure the namespace to be empty. namespace = "" default: - namespace = AttachedClusterNamespace + namespace = setting.AttachedClusterNamespace } scList := &storagev1.StorageClassList{} @@ -80,8 +80,8 @@ func ListPVCs(ctx context.Context, clusterID, namespace string) ([]PVC, error) { // For now caller may not know exactly which namespace Zadig is deployed to, so a correction is made here. namespace = config.Namespace() default: - if namespace != AttachedClusterNamespace { - return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, AttachedClusterNamespace) + if namespace != setting.AttachedClusterNamespace { + return nil, fmt.Errorf("invalid namespace in attached cluster: %s. Valid: %s", namespace, setting.AttachedClusterNamespace) } } diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 8c6ecc43..99392419 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -257,7 +257,7 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com case setting.LocalClusterID: namespace = config.Namespace() default: - namespace = AttachedClusterNamespace + namespace = setting.AttachedClusterNamespace } pvcName := fmt.Sprintf("cache-%s-%d", args.Cache.NFSProperties.StorageClass, args.Cache.NFSProperties.StorageSizeInGiB) diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go index f7d3b123..114444b0 100644 --- a/pkg/microservice/aslan/core/multicluster/service/constants.go +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -18,10 +18,6 @@ package service import "strings" -// AttachedClusterNamespace is the namespace Zadig uses in attached cluster. -// Note: **Restricted because of product design since v1.9.0**. -const AttachedClusterNamespace = "koderover-agent" - // StorageProvisioner is a storage type type StorageProvisioner string diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 2ce95ad4..5b7d9670 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -197,6 +197,7 @@ func initDatabase() { commonrepo.NewStrategyColl(), commonrepo.NewStatsColl(), commonrepo.NewSubscriptionColl(), + commonrepo.NewSystemSettingColl(), commonrepo.NewTaskColl(), commonrepo.NewTestTaskStatColl(), commonrepo.NewTestingColl(), @@ -232,6 +233,7 @@ func initDatabase() { // 初始化数据 commonrepo.NewInstallColl().InitInstallData(systemservice.InitInstallMap()) commonrepo.NewBasicImageColl().InitBasicImageData(systemservice.InitbasicImageInfos()) + commonrepo.NewSystemSettingColl().InitSystemSettings() if err := commonrepo.NewS3StorageColl().InitData(); err != nil { log.Warnf("Failed to init S3 data: %s", err) diff --git a/pkg/microservice/aslan/core/system/handler/concurrency.go b/pkg/microservice/aslan/core/system/handler/concurrency.go new file mode 100644 index 00000000..b6c9d4b1 --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/concurrency.go @@ -0,0 +1,52 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "errors" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetWorkflowConcurrency(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetWorkflowConcurrency() +} + +func UpdateWorkflowConcurrency(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.WorkflowConcurrencySettings) + if err := c.BindJSON(args); err != nil { + ctx.Err = err + return + } + + // args validation + if args.BuildConcurrency <= 0 || args.WorkflowConcurrency <= 0 { + ctx.Err = errors.New("concurrency cannot be less than 1") + return + } + + ctx.Err = service.UpdateWorkflowConcurrency(args.WorkflowConcurrency, args.BuildConcurrency, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index f6c0b081..2a5bc110 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -124,6 +124,13 @@ func (*Router) Inject(router *gin.RouterGroup) { capacity.POST("/clean", CleanCache) } + // workflow concurrency settings + concurrency := router.Group("concurrency") + { + concurrency.GET("/workflow", GetWorkflowConcurrency) + concurrency.POST("/workflow", UpdateWorkflowConcurrency) + } + // --------------------------------------------------------------------------------------- // 自定义镜像管理接口 // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/system/service/concurrency.go b/pkg/microservice/aslan/core/system/service/concurrency.go new file mode 100644 index 00000000..38a56d45 --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/concurrency.go @@ -0,0 +1,63 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "errors" + + "go.uber.org/zap" + + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +func GetWorkflowConcurrency() (*WorkflowConcurrencySettings, error) { + configuration, err := commonrepo.NewSystemSettingColl().Get() + if err != nil { + return nil, err + } + return &WorkflowConcurrencySettings{ + WorkflowConcurrency: configuration.WorkflowConcurrency, + BuildConcurrency: configuration.BuildConcurrency, + }, nil +} + +func UpdateWorkflowConcurrency(workflowConcurrency, buildConcurrency int64, log *zap.SugaredLogger) error { + // check if there are running tasks + tasks := workflowservice.RunningPipelineTasks() + if len(tasks) > 0 { + return errors.New("workflow settings must be set when NO task is running") + } + // first update system configuration table + err := commonrepo.NewSystemSettingColl().UpdateConcurrencySetting(workflowConcurrency, buildConcurrency) + if err != nil { + log.Errorf("Failed to update system settings, the error is: %s", err) + return err + } + // then we update the warpdrive deployment, where the workflow concurrency happens + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + return e.ErrScaleService.AddErr(err) + } + return updater.ScaleDeployment(config.Namespace(), configbase.WarpDriveServiceName(), int(workflowConcurrency), kubeClient) +} diff --git a/pkg/microservice/aslan/core/system/service/types.go b/pkg/microservice/aslan/core/system/service/types.go index ab4aa310..917fe8db 100644 --- a/pkg/microservice/aslan/core/system/service/types.go +++ b/pkg/microservice/aslan/core/system/service/types.go @@ -22,3 +22,8 @@ type ExternalSystemDetail struct { Server string `json:"server"` APIToken string `json:"api_token,omitempty"` } + +type WorkflowConcurrencySettings struct { + WorkflowConcurrency int64 `json:"workflow_concurrency"` + BuildConcurrency int64 `json:"build_concurrency"` +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 09809136..dfe98e90 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -129,6 +129,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) workflowtask.POST("/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTask) workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) + workflowtask.GET("/callback/id/:id/name/:name", GetWorkflowTaskCallback) } serviceTask := router.Group("servicetask") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 97f4d597..cc4bcc1e 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -191,3 +191,16 @@ func CancelWorkflowTaskV2(c *gin.Context) { } ctx.Err = commonservice.CancelTaskV2(ctx.UserName, c.Param("name"), taskID, config.WorkflowType, ctx.RequestID, ctx.Logger) } + +func GetWorkflowTaskCallback(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Resp, ctx.Err = commonservice.GetWorkflowTaskCallback(taskID, c.Param("name")) +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index c353867a..29b7a016 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -386,26 +386,22 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("checkTriggerYamlParams yamlPath:%s err:%s", item.YamlPath, err) } - deployed := false - for _, stage := range triggerYaml.Stages { - if stage == "deploy" { - deployed = true - break - } - } + deployed := existStage(StageDeploy, triggerYaml) if svcType == setting.BasicFacilityCVM { deployed = true } - workFlowArgs.Namespace = strings.Join(triggerYaml.Deploy.Envsname, ",") workFlowArgs.WorkflowName = workflow.Name - workFlowArgs.BaseNamespace = triggerYaml.Deploy.BaseNamespace workFlowArgs.ProductTmplName = workflow.ProductTmplName if triggerYaml.CacheSet != nil { workFlowArgs.IgnoreCache = triggerYaml.CacheSet.IgnoreCache workFlowArgs.ResetCache = triggerYaml.CacheSet.ResetCache } - workFlowArgs.EnvRecyclePolicy = triggerYaml.Deploy.EnvRecyclePolicy - workFlowArgs.EnvUpdatePolicy = triggerYaml.Deploy.Strategy + if triggerYaml.Deploy != nil { + workFlowArgs.Namespace = strings.Join(triggerYaml.Deploy.Envsname, ",") + workFlowArgs.BaseNamespace = triggerYaml.Deploy.BaseNamespace + workFlowArgs.EnvRecyclePolicy = string(triggerYaml.Deploy.EnvRecyclePolicy) + workFlowArgs.EnvUpdatePolicy = string(triggerYaml.Deploy.Strategy) + } item.MainRepo.Events = triggerYaml.Rules.Events if triggerYaml.Rules.Strategy != nil { item.AutoCancel = triggerYaml.Rules.Strategy.AutoCancel @@ -433,7 +429,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor TestModuleName: test.Name, Envs: envs, } - if test.Repo.Strategy == "currentRepo" { + if test.Repo.Strategy == TestRepoStrategyCurrentRepo { for _, repo := range moduleTest.Repos { if repo.RepoName == item.MainRepo.RepoName && repo.RepoOwner == item.MainRepo.RepoOwner { repo.Branch = item.MainRepo.Branch @@ -540,9 +536,17 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, switch evt := event.(type) { case *gitlab.PushEvent: pushEvent = evt + if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != pushEvent.Project.PathWithNamespace { + log.Debugf("event not matches repo: %v", item.MainRepo) + continue + } branref = pushEvent.Ref case *gitlab.MergeEvent: mergeEvent = evt + if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != mergeEvent.ObjectAttributes.Target.PathWithNamespace { + log.Debugf("event not matches repo: %v", item.MainRepo) + continue + } if mergeEvent.ObjectAttributes.Source.PathWithNamespace != mergeEvent.ObjectAttributes.Target.PathWithNamespace { branref = mergeEvent.ObjectAttributes.TargetBranch } else { @@ -552,6 +556,10 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, item.MainRepo.Branch = getBranchFromRef(mergeEvent.ObjectAttributes.TargetBranch) case *gitlab.TagEvent: tagEvent = evt + if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != tagEvent.Project.PathWithNamespace { + log.Debugf("event not matches repo: %v", item.MainRepo) + continue + } branref = tagEvent.Ref } @@ -619,9 +627,6 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, notification, _ = scmnotify.NewService().SendInitWebhookComment( item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, log, ) - - // 初始化 gitlab diff_note - InitDiffNote(ev, item.MainRepo, log) } } @@ -656,6 +661,8 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, if err = CreateEnvAndTaskByPR(args, prID, requestID, log); err != nil { log.Infof("CreateRandomEnv err:%v", err) } + } else { + log.Warnf("It's not a PR event,BaseNamespace:%s", item.WorkflowArgs.BaseNamespace) } } } @@ -678,101 +685,6 @@ func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([ return client.ListChangedFiles(event) } -// InitDiffNote 调用gitlab接口初始化DiffNote,并保存到数据库 -func InitDiffNote(ev *gitlab.MergeEvent, mainRepo *commonmodels.MainHookRepo, log *zap.SugaredLogger) error { - commitID := ev.ObjectAttributes.LastCommit.ID - body := "KodeRover CI 检查中..." - - // 调用gitlab api获取相关数据 - detail, err := systemconfig.New().GetCodeHost(mainRepo.CodehostID) - if err != nil { - log.Errorf("GetCodehostDetail failed, codehost:%d, err:%v", mainRepo.CodehostID, err) - return fmt.Errorf("failed to find codehost %d: %v", mainRepo.CodehostID, err) - } - cli, _ := gitlab.NewOAuthClient(detail.AccessToken, gitlab.WithBaseURL(detail.Address)) - - opt := &commonrepo.DiffNoteFindOpt{ - CodehostID: mainRepo.CodehostID, - ProjectID: mainRepo.RepoOwner + "/" + mainRepo.RepoName, - MergeRequestID: ev.ObjectAttributes.IID, - } - dn, err := commonrepo.NewDiffNoteColl().Find(opt) - if err == nil { - // 该pr的DiffNote已经创建过,且是同一个commit,则不处理 - if dn.CommitID == commitID { - return nil - } - // 不是同一个commit,则重置body和resolved - // 更新note body - noteBodyOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Body: &body, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(dn.Repo.ProjectID, dn.MergeRequestID, dn.DiscussionID, dn.NoteID, noteBodyOpt) - if err != nil { - log.Errorf("UpdateMergeRequestDiscussionNote failed, err:%v", err) - return err - } - - // 更新resolved状态 - resolved := false - resolveOpt := &gitlab.UpdateMergeRequestDiscussionNoteOptions{ - Resolved: &resolved, - } - _, _, err = cli.Discussions.UpdateMergeRequestDiscussionNote(dn.Repo.ProjectID, dn.MergeRequestID, dn.DiscussionID, dn.NoteID, resolveOpt) - if err != nil { - log.Errorf("UpdateMergeRequestDiscussionNote failed, err:%v", err) - return err - } - - // 更新到数据库 - dn.Resolved = resolved - dn.Body = body - err = commonrepo.NewDiffNoteColl().Update(dn.ObjectID.Hex(), commitID, dn.Body, dn.Resolved) - if err != nil { - log.Errorf("UpdateDiscussionInfo failed, err:%v", err) - return err - } - - return nil - } - - // 不存在则创建 - diffNote := &commonmodels.DiffNote{ - Repo: &commonmodels.RepoInfo{ - CodehostID: mainRepo.CodehostID, - Source: "gitlab", - ProjectID: mainRepo.RepoOwner + "/" + mainRepo.RepoName, - Address: detail.Address, - OauthToken: detail.AccessToken, - }, - MergeRequestID: ev.ObjectAttributes.IID, - CommitID: commitID, - Body: body, - } - - createOpt := &gitlab.CreateMergeRequestDiscussionOptions{ - Body: &diffNote.Body, - } - - discussion, _, err := cli.Discussions.CreateMergeRequestDiscussion(diffNote.Repo.ProjectID, diffNote.MergeRequestID, createOpt) - if err != nil { - log.Errorf("CreateMergeRequestDiscussion failed, err:%v", err) - return err - } - - diffNote.DiscussionID = discussion.ID - if len(discussion.Notes) > 0 { - diffNote.NoteID = discussion.Notes[0].ID - } - err = commonrepo.NewDiffNoteColl().Create(diffNote) - if err != nil { - log.Errorf("DiffNote.Create failed, err:%v", err) - return err - } - - return nil -} - var mutex sync.Mutex // CreateEnvAndTaskByPR 根据pr触发创建环境、使用工作流更新该创建的环境、根据环境删除策略删除环境 diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go index 0658e3bf..47284274 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/trigger_yaml.go @@ -20,8 +20,39 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" ) +type Stage string + +const ( + StageBuild Stage = "build" + StageDeploy Stage = "deploy" + StageTest Stage = "test" +) + +type DeployStrategy string + +const ( + DeployStrategySingle DeployStrategy = "single" + DeployStrategyDynamic DeployStrategy = "dynamic" + DeployStrategyBase DeployStrategy = "base" +) + +type EnvRecyclePolicy string + +const ( + EnvRecyclePolicySuccess EnvRecyclePolicy = "success" + EnvRecyclePolicyAlways EnvRecyclePolicy = "always" + EnvRecyclePolicyNever EnvRecyclePolicy = "never" +) + +type TestRepoStrategy string + +const ( + TestRepoStrategyDefault TestRepoStrategy = "default" + TestRepoStrategyCurrentRepo TestRepoStrategy = "currentRepo" +) + type TriggerYaml struct { - Stages []string `yaml:"stages"` + Stages []Stage `yaml:"stages"` Build []*BuildServices `yaml:"build"` Deploy *Deploy `yaml:"deploy"` Test []*Test `yaml:"test"` @@ -55,10 +86,10 @@ Strategy And Need to set EnvRecyclePolicy:success/always/never */ type Deploy struct { - Strategy string `yaml:"strategy"` - Envsname []string `yaml:"envs_name"` - EnvRecyclePolicy string `yaml:"env_recycle_policy"` - BaseNamespace string `yaml:"base_env"` + Strategy DeployStrategy `yaml:"strategy"` + Envsname []string `yaml:"envs_name"` + EnvRecyclePolicy EnvRecyclePolicy `yaml:"env_recycle_policy"` + BaseNamespace string `yaml:"base_env"` } type Test struct { @@ -75,7 +106,7 @@ Strategy Use current changed code information */ type Repo struct { - Strategy string `yaml:"strategy"` + Strategy TestRepoStrategy `yaml:"strategy"` } type Rules struct { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index be0e959d..92e10954 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -590,58 +590,92 @@ func getServiceTypeByProject(productName string) (string, error) { return projectType, nil } +func existStage(expectStage Stage, triggerYaml *TriggerYaml) bool { + for _, stage := range triggerYaml.Stages { + if stage == expectStage { + return true + } + } + return false +} + func checkTriggerYamlParams(triggerYaml *TriggerYaml) error { //check stages for _, stage := range triggerYaml.Stages { - if stage != "build" && stage != "deploy" && stage != "test" { - return fmt.Errorf("stages must build or deploy or test") + if stage != StageBuild && stage != StageDeploy && stage != StageTest { + return fmt.Errorf("stages must %s or %s or %s", StageBuild, StageDeploy, StageTest) } } + //check build + if len(triggerYaml.Build) == 0 { + return errors.New("build is empty") + } for _, bd := range triggerYaml.Build { if bd.Name == "" || bd.ServiceModule == "" { - return fmt.Errorf("build.name or build.service_module is empty") + return errors.New("build.name or build.service_module is empty") } } + //check deploy if triggerYaml.Deploy == nil { - return fmt.Errorf("deploy must be exist") + return errors.New("deploy is empty") } - if triggerYaml.Deploy.BaseNamespace != "" { - if triggerYaml.Deploy.EnvRecyclePolicy != "success" && triggerYaml.Deploy.EnvRecyclePolicy != "always" && triggerYaml.Deploy.EnvRecyclePolicy != "never" { - return fmt.Errorf("deploy.env_recycle_policy must success/always/never") + if len(triggerYaml.Deploy.Envsname) == 0 { + return errors.New("deploy.envs_name is empty") + } + if triggerYaml.Deploy.Strategy != DeployStrategySingle && triggerYaml.Deploy.Strategy != DeployStrategyBase && triggerYaml.Deploy.Strategy != DeployStrategyDynamic { + return fmt.Errorf("deploy.strategy must %s or %s or %s", DeployStrategySingle, DeployStrategyDynamic, DeployStrategyBase) + } + if triggerYaml.Deploy.Strategy == DeployStrategyBase { + if triggerYaml.Deploy.BaseNamespace == "" { + return errors.New("deploy.base_env is empty") + } + if triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicySuccess && triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicyAlways && triggerYaml.Deploy.EnvRecyclePolicy != EnvRecyclePolicyNever { + return errors.New("deploy.env_recycle_policy must success/always/never") + } + } else { + if triggerYaml.Deploy.BaseNamespace != "" { + return errors.New("deploy.base_env must empty") } } + //check test - for _, tt := range triggerYaml.Test { - if tt.Repo == nil { - return fmt.Errorf("test.repo.strategy must default/currentRepo") + if existStage(StageTest, triggerYaml) { + if len(triggerYaml.Test) == 0 { + return errors.New("test is empty") } - if tt.Repo.Strategy != "default" && tt.Repo.Strategy != "currentRepo" { - return fmt.Errorf("test.repo.strategy must default/currentRepo") + for _, tt := range triggerYaml.Test { + if tt.Repo == nil { + return errors.New("test.repo.strategy must default/currentRepo") + } + if tt.Repo.Strategy != TestRepoStrategyDefault && tt.Repo.Strategy != TestRepoStrategyCurrentRepo { + return errors.New("test.repo.strategy must default/currentRepo") + } } } + //check rule if triggerYaml.Rules == nil { - return fmt.Errorf("rules must exist") + return errors.New("rules must exist") } if len(triggerYaml.Rules.Branchs) == 0 { - return fmt.Errorf("rules.baranch must exist") + return errors.New("rules.branchs must exist") } for _, ev := range triggerYaml.Rules.Events { if ev != "pull_request" && ev != "push" && ev != "tag" { - return fmt.Errorf("rules.event must be pull_request or push or tag") + return errors.New("rules.event must be pull_request or push or tag") } } if triggerYaml.Rules.MatchFolders == nil { - return fmt.Errorf("rules.match_folders must exist") + return errors.New("rules.match_folders must exist") } for _, mf := range triggerYaml.Rules.MatchFolders.MatchFoldersTree { if mf.Name == "" || mf.ServiceModule == "" { - return fmt.Errorf("match_folders.match_folders_tree.name or match_folders.match_folders_tree.service_module is empty") + return errors.New("match_folders.match_folders_tree.name or match_folders.match_folders_tree.service_module is empty") } if len(mf.FileTree) == 0 { - return fmt.Errorf("match_folders.match_folders_tree.file_tree is empty") + return errors.New("match_folders.match_folders_tree.file_tree is empty") } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index b4adcf7c..a957ee37 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -96,7 +96,9 @@ func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskC task.SubTasks = []map[string]interface{}{subTask} - if err := ensurePipelineTask(task, "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + }, log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) return 0, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 365a06c5..6c1fd6fd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -242,7 +242,10 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C pt.ConfigPayload.RepoConfigs[repo.ID.Hex()] = repo } - if err := ensurePipelineTask(pt, pt.TaskArgs.Deploy.Namespace, log); err != nil { + if err := ensurePipelineTask(&task.TaskOpt{ + Task: pt, + EnvName: pt.TaskArgs.Deploy.Namespace, + }, log); err != nil { log.Errorf("Service.ensurePipelineTask failed %v %v", args, err) if err, ok := err.(*ContainerNotFound); ok { return nil, e.NewWithExtras( @@ -664,6 +667,13 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testArg.TestModuleName = args.TestName + // In some old testing configurations, the `pre_test.cluster_id` field is empty indicating that's a local cluster. + // We do a protection here to avoid query failure. + // Resaving the testing configuration after v1.8.0 will automatically populate this field. + if testing.PreTest.ClusterID == "" { + testing.PreTest.ClusterID = setting.LocalClusterID + } + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(testing.PreTest.ClusterID) if err != nil { return resp, e.ErrListTestModule.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index 0c83ecd8..baffa417 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -21,19 +21,18 @@ import ( "fmt" "sort" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" - "github.com/koderover/zadig/pkg/types" - "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) const SplitSymbol = "&" @@ -226,7 +225,7 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge return nil, e.ErrCreateTask.AddErr(err) } - task := &task.Task{ + task := &taskmodels.Task{ Type: config.ServiceType, ProductName: args.ProductName, TaskCreator: args.ServiceTaskCreator, @@ -238,7 +237,9 @@ func CreateServiceTask(args *commonmodels.ServiceTaskArgs, log *zap.SugaredLogge } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + }, log); err != nil { log.Errorf("CreateServiceTask ensurePipelineTask err : %v", err) return nil, err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go index 15ba83f4..29c67d47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go @@ -49,6 +49,7 @@ var SubtaskOrder = map[config.TaskType]int{ config.TaskType("release_image"): 14, config.TaskType("reset_image"): 15, config.TaskType("trigger"): 16, + config.TaskType("extension"): 17, } type ByStageKind []*commonmodels.Stage diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e9f33926..d01a90c4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -550,6 +551,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } stages := make([]*commonmodels.Stage, 0) + serviceInfos := make([]*taskmodels.ServiceInfo, 0) for _, target := range args.Target { var subTasks []map[string]interface{} var err error @@ -658,7 +660,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } // 填充subtask之间关联内容 - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: args.WorkflowName, TaskCreator: taskCreator, @@ -671,7 +673,13 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, args.Namespace, log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + EnvName: args.Namespace, + ServiceName: target.ServiceName, + ServiceInfos: &serviceInfos, + IsWorkflowTask: true, + }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%v", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -691,11 +699,20 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, target.Name) + AddSubtaskToStage(&stages, stask, target.Name+"_"+target.ServiceName) } } + // add extension to stage + if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { + extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage, serviceInfos) + if err != nil { + log.Errorf("add extension task error: %s", err) + return nil, e.ErrCreateTask.AddErr(err) + } + AddSubtaskToStage(&stages, extensionTask, string(config.TaskExtension)) + } - testTask := &task.Task{ + testTask := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: args.WorkflowName, ProductName: args.ProductTmplName, @@ -732,7 +749,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, MergeRequestID: args.MergeRequestID, CommitID: args.CommitID, } - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, Type: config.WorkflowType, ProductName: workflow.ProductTmplName, @@ -1045,7 +1062,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. subTasks = append(subTasks, distributeTasks...) // Fill in the associated content between subtasks - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: workflow.Name, TaskCreator: setting.RequestModeOpenAPI, @@ -1058,7 +1075,9 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, "", log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%s", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -1084,7 +1103,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } sort.Sort(ByStageKind(stages)) - task := &task.Task{ + task := &taskmodels.Task{ WorkflowArgs: args, TaskID: nextTaskID, Type: config.WorkflowType, @@ -1246,7 +1265,7 @@ func GetS3Storage(id string, logger *zap.SugaredLogger) (*commonmodels.S3Storage func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Product, timeout int) (map[string]interface{}, error) { var ( resp map[string]interface{} - deployTask = task.Deploy{ + deployTask = taskmodels.Deploy{ TaskType: config.TaskDeploy, Enabled: true, Namespace: prodEnv.Namespace, @@ -1264,7 +1283,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ return nil, err } deployTask.ServiceName = envList[0] - deployTask.ContainerName = envList[1] + deployTask.ContainerName = envList[1] + "_" + envList[0] switch env.Type { case setting.K8SDeployType: @@ -1288,7 +1307,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.Product) (map[string]interface{}, error) { switch env.Type { case setting.K8SDeployType: - deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} + deployTask := taskmodels.Deploy{TaskType: config.TaskResetImage, Enabled: true} deployTask.Namespace = prodEnv.Namespace deployTask.ProductName = prodEnv.ProductName deployTask.SkipWaiting = true @@ -1303,7 +1322,7 @@ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.P deployTask.ContainerName = envList[1] return deployTask.ToSubTask() case setting.HelmDeployType: - deployTask := task.Deploy{TaskType: config.TaskResetImage, Enabled: true} + deployTask := taskmodels.Deploy{TaskType: config.TaskResetImage, Enabled: true} deployTask.Namespace = prodEnv.Namespace deployTask.ProductName = prodEnv.ProductName deployTask.SkipWaiting = true @@ -1324,7 +1343,7 @@ func resetImageTaskToSubTask(env commonmodels.DeployEnv, prodEnv *commonmodels.P } func artifactToSubTasks(name, image string) (map[string]interface{}, error) { - artifactTask := task.Artifact{TaskType: config.TaskArtifact, Enabled: true} + artifactTask := taskmodels.Artifact{TaskType: config.TaskArtifact, Enabled: true} artifactTask.Name = name artifactTask.Image = image @@ -1335,7 +1354,7 @@ func formatDistributeSubtasks(releaseImages []commonmodels.RepoImage, imageRepo, var resp []map[string]interface{} if distribute.ImageDistribute { - t := task.ReleaseImage{ + t := taskmodels.ReleaseImage{ TaskType: config.TaskReleaseImage, Enabled: true, ImageRepo: imageRepo, @@ -1348,7 +1367,7 @@ func formatDistributeSubtasks(releaseImages []commonmodels.RepoImage, imageRepo, resp = append(resp, subtask) } if distribute.QstackDistribute && destStorageURL != "" { - task := task.DistributeToS3{ + task := taskmodels.DistributeToS3{ TaskType: config.TaskDistributeToS3, Enabled: true, DestStorageURL: destStorageURL, @@ -1380,7 +1399,7 @@ func AddJiraSubTask(moduleName, target, serviceName, productName string, log *za if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } - jira := &task.Jira{ + jira := &taskmodels.Jira{ TaskType: config.TaskJira, Enabled: true, } @@ -1392,10 +1411,24 @@ func AddJiraSubTask(moduleName, target, serviceName, productName string, log *za } func addSecurityToSubTasks() (map[string]interface{}, error) { - securityTask := task.Security{TaskType: config.TaskSecurity, Enabled: true} + securityTask := taskmodels.Security{TaskType: config.TaskSecurity, Enabled: true} return securityTask.ToSubTask() } +func addExtensionToSubTasks(stage *commonmodels.ExtensionStage, serviceInfos []*taskmodels.ServiceInfo) (map[string]interface{}, error) { + extensionTask := taskmodels.Extension{ + TaskType: config.TaskExtension, + Enabled: true, + URL: stage.URL, + Path: stage.Path, + Headers: stage.Headers, + IsCallback: stage.IsCallback, + Timeout: stage.Timeout, + ServiceInfos: serviceInfos, + } + return extensionTask.ToSubTask() +} + func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTaskArgs) *commonmodels.TaskArgs { resp := &commonmodels.TaskArgs{PipelineName: workflowArgs.WorkflowName, TaskCreator: workflowArgs.WorkflowTaskCreator} for _, build := range workflowArgs.Target { @@ -1409,8 +1442,8 @@ func workFlowArgsToTaskArgs(target string, workflowArgs *commonmodels.WorkflowTa } // TODO 和validation中转化testsubtask合并为一个方法 -func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log *zap.SugaredLogger) ([]*task.Testing, error) { - var resp []*task.Testing +func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, log *zap.SugaredLogger) ([]*taskmodels.Testing, error) { + var resp []*taskmodels.Testing var servicesArray []string var services string @@ -1446,7 +1479,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * return resp, err } - testTask := &task.Testing{ + testTask := &taskmodels.Testing{ TaskType: config.TaskTestingV2, Enabled: true, TestName: "test", @@ -1455,7 +1488,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * testTask.TestModuleName = testModule.Name testTask.JobCtx.TestType = testModule.TestType testTask.JobCtx.Builds = testModule.Repos - testTask.JobCtx.BuildSteps = append(testTask.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: testModule.Scripts}) + testTask.JobCtx.BuildSteps = append(testTask.JobCtx.BuildSteps, &taskmodels.BuildStep{BuildType: "shell", Scripts: testModule.Scripts}) testTask.JobCtx.ArtifactPaths = testModule.ArtifactPaths testTask.JobCtx.TestThreshold = testModule.Threshold @@ -1513,7 +1546,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *task.Task, log * return resp, err } - // Iterate test jobctx builds, and replace it if params specified from task. + // Iterate test jobctx builds, and replace it if params specified from taskmodels. // 外部触发的pipeline if testCreator == setting.WebhookTaskCreator || testCreator == setting.CronTaskCreator { _ = SetTriggerBuilds(testTask.JobCtx.Builds, testArg.Builds, log) @@ -1682,7 +1715,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } // 填充subtask之间关联内容 - task := &task.Task{ + task := &taskmodels.Task{ ProductName: args.ProductTmplName, PipelineName: args.WorkflowName, TaskID: nextTaskID, @@ -1696,7 +1729,11 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } sort.Sort(ByTaskKind(task.SubTasks)) - if err := ensurePipelineTask(task, args.Namespace, log); err != nil { + if err := ensurePipelineTask(&taskmodels.TaskOpt{ + Task: task, + EnvName: args.Namespace, + IsWorkflowTask: true, + }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask task:[%v] err:%v", task, err) if err, ok := err.(*ContainerNotFound); ok { err := e.NewWithExtras( @@ -1714,11 +1751,11 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } for _, stask := range task.SubTasks { - AddSubtaskToStage(&stages, stask, artifact.Name) + AddSubtaskToStage(&stages, stask, artifact.Name+"_"+artifact.ServiceName) } } - testTask := &task.Task{ + testTask := &taskmodels.Task{ TaskID: nextTaskID, PipelineName: args.WorkflowName, ProductName: args.ProductTmplName, @@ -1752,7 +1789,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator MergeRequestID: args.MergeRequestID, CommitID: args.CommitID, } - task := &task.Task{ + task := &taskmodels.Task{ TaskID: nextTaskID, Type: config.WorkflowType, ProductName: workflow.ProductTmplName, @@ -1833,13 +1870,13 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } for _, module := range modules { - build := &task.Build{ + build := &taskmodels.Build{ TaskType: config.TaskBuild, Enabled: true, InstallItems: module.PreBuild.Installs, ServiceName: args.Target, Service: args.ServiceName, - JobCtx: task.JobCtx{}, + JobCtx: taskmodels.JobCtx{}, ImageID: module.PreBuild.ImageID, BuildOS: module.PreBuild.BuildOS, ImageFrom: module.PreBuild.ImageFrom, @@ -1852,6 +1889,13 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL ClusterID: module.PreBuild.ClusterID, } + // In some old build configurations, the `pre_build.cluster_id` field is empty indicating that's a local cluster. + // We do a protection here to avoid query failure. + // Resaving the build configuration after v1.8.0 will automatically populate this field. + if module.PreBuild.ClusterID == "" { + module.PreBuild.ClusterID = setting.LocalClusterID + } + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(module.PreBuild.ClusterID) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) @@ -1920,9 +1964,9 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Builds = make([]*types.Repository, 0) } - build.JobCtx.BuildSteps = []*task.BuildStep{} + build.JobCtx.BuildSteps = []*taskmodels.BuildStep{} if module.Scripts != "" { - build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: module.Scripts}) + build.JobCtx.BuildSteps = append(build.JobCtx.BuildSteps, &taskmodels.BuildStep{BuildType: "shell", Scripts: module.Scripts}) } if module.PMDeployScripts != "" && build.ServiceType == setting.PMDeployType { @@ -1930,7 +1974,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } if len(module.SSHs) > 0 && build.ServiceType == setting.PMDeployType { - privateKeys := make([]*task.SSH, 0) + privateKeys := make([]*taskmodels.SSH, 0) for _, sshID := range module.SSHs { //私钥信息可能被更新,而构建中存储的信息是旧的,需要根据id获取最新的私钥信息 latestKeyInfo, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ID: sshID}) @@ -1938,7 +1982,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL log.Errorf("PrivateKey.Find failed, id:%s, err:%s", sshID, err) continue } - ssh := new(task.SSH) + ssh := new(taskmodels.SSH) ssh.Name = latestKeyInfo.Name ssh.UserName = latestKeyInfo.UserName ssh.IP = latestKeyInfo.IP @@ -1978,7 +2022,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL dockerTemplateContent = dockerfileDetail.Content } } - build.JobCtx.DockerBuildCtx = &task.DockerBuildCtx{ + build.JobCtx.DockerBuildCtx = &taskmodels.DockerBuildCtx{ Source: module.PostBuild.DockerBuild.Source, WorkDir: module.PostBuild.DockerBuild.WorkDir, DockerFile: module.PostBuild.DockerBuild.DockerFile, @@ -1988,7 +2032,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } if module.PostBuild != nil && module.PostBuild.FileArchive != nil { - build.JobCtx.FileArchiveCtx = &task.FileArchiveCtx{ + build.JobCtx.FileArchiveCtx = &taskmodels.FileArchiveCtx{ FileLocation: module.PostBuild.FileArchive.FileLocation, } } @@ -2000,7 +2044,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.Caches = module.Caches if args.FileName != "" { - build.ArtifactInfo = &task.ArtifactInfo{ + build.ArtifactInfo = &taskmodels.ArtifactInfo{ URL: args.URL, WorkflowName: args.WorkflowName, TaskID: args.TaskID, @@ -2025,20 +2069,20 @@ func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) set return ips } -func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) error { +func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) error { var ( buildEnvs []*commonmodels.KeyVal ) // 验证 Subtask payload - err := validateSubTaskSetting(pt.PipelineName, pt.SubTasks) + err := validateSubTaskSetting(taskOpt.Task.PipelineName, taskOpt.Task.SubTasks) if err != nil { log.Errorf("Validate subtask setting failed: %+v", err) return err } //设置执行任务时参数 - for i, subTask := range pt.SubTasks { + for i, subTask := range taskOpt.Task.SubTasks { pre, err := base.ToPreview(subTask) if err != nil { @@ -2069,7 +2113,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e //} // 设置Pipeline对应的服务名称 if t.ServiceName != "" { - pt.ServiceName = t.ServiceName + taskOpt.Task.ServiceName = t.ServiceName } // 设置 build 安装脚本 @@ -2080,16 +2124,16 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } // 外部触发的pipeline - if pt.TaskCreator == setting.WebhookTaskCreator || pt.TaskCreator == setting.CronTaskCreator { - SetTriggerBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || taskOpt.Task.TaskCreator == setting.CronTaskCreator { + SetTriggerBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Builds, log) } else { - setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Builds, log) + setManunalBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Builds, log) } - opt := &commonrepo.ProductFindOptions{EnvName: envName, Name: pt.ProductName} + opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("can't find product by envName:%s error msg: %v", envName, err) + log.Errorf("can't find product by envName:%s error msg: %v", taskOpt.EnvName, err) return e.ErrFindRegistry.AddDesc(err.Error()) } @@ -2115,25 +2159,33 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } } - t.JobCtx.Image = GetImage(reg, releaseCandidate(t, pt.TaskID, pt.ProductName, envName, "image")) - pt.TaskArgs.Deploy.Image = t.JobCtx.Image + t.JobCtx.Image = GetImage(reg, releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "image")) + taskOpt.Task.TaskArgs.Deploy.Image = t.JobCtx.Image - if pt.ConfigPayload != nil { - pt.ConfigPayload.Registry.Addr = reg.RegAddr - pt.ConfigPayload.Registry.AccessKey = reg.AccessKey - pt.ConfigPayload.Registry.SecretKey = reg.SecretKey - pt.ConfigPayload.Registry.Namespace = reg.Namespace + if taskOpt.ServiceName != "" { + *taskOpt.ServiceInfos = append(*taskOpt.ServiceInfos, &taskmodels.ServiceInfo{ + ServiceName: taskOpt.ServiceName, + ServiceModule: t.ServiceName, + Image: t.JobCtx.Image, + }) + } + + if taskOpt.Task.ConfigPayload != nil { + taskOpt.Task.ConfigPayload.Registry.Addr = reg.RegAddr + taskOpt.Task.ConfigPayload.Registry.AccessKey = reg.AccessKey + taskOpt.Task.ConfigPayload.Registry.SecretKey = reg.SecretKey + taskOpt.Task.ConfigPayload.Registry.Namespace = reg.Namespace } // 二进制文件名称 // 编译任务使用 t.JobCtx.PackageFile // 注意: 其他任务从 pt.TaskArgs.Deploy.PackageFile 获取, 必须要有编译任务 - t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, pt.TaskID, pt.ProductName, envName, "tar")) - pt.TaskArgs.Deploy.PackageFile = t.JobCtx.PackageFile + t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "tar")) + taskOpt.Task.TaskArgs.Deploy.PackageFile = t.JobCtx.PackageFile // 注入编译模块中用户定义环境变量 // 注意: 需要在pt.TaskArgs.Deploy设置完之后再设置环境变量 - t.JobCtx.EnvVars = append(t.JobCtx.EnvVars, prepareTaskEnvs(pt, log)...) + t.JobCtx.EnvVars = append(t.JobCtx.EnvVars, prepareTaskEnvs(taskOpt.Task, log)...) // 如果其他模块需要使用编译模块的环境变量进行渲染,需要设置buildEnvs buildEnvs = t.JobCtx.EnvVars @@ -2162,15 +2214,18 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e registryRepo = reg.RegAddr } - t.DockerBuildStatus = &task.DockerBuildStatus{ + t.DockerBuildStatus = &taskmodels.DockerBuildStatus{ ImageName: t.JobCtx.Image, RegistryRepo: registryRepo, } + t.UTStatus = &task.UTStatus{} t.StaticCheckStatus = &task.StaticCheckStatus{} t.BuildStatus = &task.BuildStatus{} - - pt.SubTasks[i], err = t.ToSubTask() + if taskOpt.IsWorkflowTask { + t.ServiceName = t.ServiceName + "_" + t.Service + } + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err @@ -2199,9 +2254,9 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return &ImageIllegal{} } - pt.TaskArgs.Deploy.Image = image + taskOpt.Task.TaskArgs.Deploy.Image = image t.Image = image - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2213,19 +2268,19 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } if t.Enabled { - if pt.TaskArgs == nil { - pt.TaskArgs = &commonmodels.TaskArgs{PipelineName: pt.WorkflowArgs.WorkflowName, TaskCreator: pt.WorkflowArgs.WorkflowTaskCreator} + if taskOpt.Task.TaskArgs == nil { + taskOpt.Task.TaskArgs = &commonmodels.TaskArgs{PipelineName: taskOpt.Task.WorkflowArgs.WorkflowName, TaskCreator: taskOpt.Task.WorkflowArgs.WorkflowTaskCreator} } - registry := pt.ConfigPayload.RepoConfigs[pt.WorkflowArgs.RegistryID] + registry := taskOpt.Task.ConfigPayload.RepoConfigs[taskOpt.Task.WorkflowArgs.RegistryID] if registry.RegProvider == config.RegistryTypeAWS { t.Image = fmt.Sprintf("%s/%s", util.TrimURLScheme(registry.RegAddr), t.Image) } else { t.Image = fmt.Sprintf("%s/%s/%s", util.TrimURLScheme(registry.RegAddr), registry.Namespace, t.Image) } - pt.TaskArgs.Deploy.Image = t.Image - t.RegistryID = pt.WorkflowArgs.RegistryID + taskOpt.Task.TaskArgs.Deploy.Image = t.Image + t.RegistryID = taskOpt.Task.WorkflowArgs.RegistryID - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2237,7 +2292,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } if t.Enabled { - t.Image = pt.TaskArgs.Deploy.Image + t.Image = taskOpt.Task.TaskArgs.Deploy.Image // 使用环境变量KEY渲染 docker build的各个参数 for _, env := range buildEnvs { @@ -2248,12 +2303,12 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } } - err = SetCandidateRegistry(pt.ConfigPayload, log) + err = SetCandidateRegistry(taskOpt.Task.ConfigPayload, log) if err != nil { return err } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2273,26 +2328,26 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // Iterate test jobctx builds, and replace it if params specified from task. // 外部触发的pipeline - if pt.TaskCreator == setting.WebhookTaskCreator || pt.TaskCreator == setting.CronTaskCreator { - SetTriggerBuilds(t.JobCtx.Builds, pt.TaskArgs.Test.Builds, log) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || taskOpt.Task.TaskCreator == setting.CronTaskCreator { + SetTriggerBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Test.Builds, log) } else { - setManunalBuilds(t.JobCtx.Builds, pt.TaskArgs.Test.Builds, log) + setManunalBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Test.Builds, log) } - err = SetCandidateRegistry(pt.ConfigPayload, log) + err = SetCandidateRegistry(taskOpt.Task.ConfigPayload, log) if err != nil { return err } //use log path - if pt.ServiceName == "" { - pt.ServiceName = t.TestName + if taskOpt.Task.ServiceName == "" { + taskOpt.Task.ServiceName = t.TestName } // 设置敏感信息 t.JobCtx.EnvVars = append(t.JobCtx.EnvVars, envs...) - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2305,7 +2360,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { - t.SetNamespace(pt.TaskArgs.Deploy.Namespace) + t.SetNamespace(taskOpt.Task.TaskArgs.Deploy.Namespace) image, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) if err != nil { log.Error(err) @@ -2314,7 +2369,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e t.SetImage(image) - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2329,10 +2384,15 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e if t.Enabled { // 从创建任务payload设置容器部署 - t.SetImage(pt.TaskArgs.Deploy.Image) - t.SetNamespace(pt.TaskArgs.Deploy.Namespace) + t.SetImage(taskOpt.Task.TaskArgs.Deploy.Image) + t.SetNamespace(taskOpt.Task.TaskArgs.Deploy.Namespace) + + containerName := t.ContainerName + if taskOpt.IsWorkflowTask { + containerName = strings.TrimSuffix(containerName, "_"+t.ServiceName) + } - _, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) + _, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, containerName) if err != nil { log.Error(err) return err @@ -2340,16 +2400,16 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e // Task creator can be webhook trigger or cronjob trigger or validated user // Validated user includes both that user is granted write permission or user is the owner of this product - if pt.TaskCreator == setting.WebhookTaskCreator || - pt.TaskCreator == setting.CronTaskCreator || - IsProductAuthed(pt.TaskCreator, t.Namespace, pt.ProductName, config.ProductWritePermission, log) { - log.Infof("Validating permission passed. product:%s, owner:%s, task executed by: %s", pt.ProductName, t.Namespace, pt.TaskCreator) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || + taskOpt.Task.TaskCreator == setting.CronTaskCreator || + IsProductAuthed(taskOpt.Task.TaskCreator, t.Namespace, taskOpt.Task.ProductName, config.ProductWritePermission, log) { + log.Infof("Validating permission passed. product:%s, owner:%s, task executed by: %s", taskOpt.Task.ProductName, t.Namespace, taskOpt.Task.TaskCreator) } else { - log.Errorf("permission denied. product:%s, owner:%s, task executed by: %s", pt.ProductName, t.Namespace, pt.TaskCreator) + log.Errorf("permission denied. product:%s, owner:%s, task executed by: %s", taskOpt.Task.ProductName, t.Namespace, taskOpt.Task.TaskCreator) return errors.New(e.ProductAccessDeniedErrMsg) } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2362,15 +2422,15 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } if task.Enabled { - task.SetPackageFile(pt.TaskArgs.Deploy.PackageFile) + task.SetPackageFile(taskOpt.Task.TaskArgs.Deploy.PackageFile) - if pt.TeamName == "" { - task.ProductName = pt.ProductName + if taskOpt.Task.TeamName == "" { + task.ProductName = taskOpt.Task.ProductName } else { - task.ProductName = pt.TeamName + task.ProductName = taskOpt.Task.TeamName } - task.ServiceName = pt.ServiceName + task.ServiceName = taskOpt.Task.ServiceName if task.DestStorageURL == "" { var storage *commonmodels.S3Storage @@ -2392,7 +2452,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e return err } } - pt.SubTasks[i], err = task.ToSubTask() + taskOpt.Task.SubTasks[i], err = task.ToSubTask() if err != nil { return err } @@ -2407,11 +2467,11 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e if t.Enabled { // 从创建任务payload设置线上镜像分发任务 - t.SetImage(pt.TaskArgs.Deploy.Image) + t.SetImage(taskOpt.Task.TaskArgs.Deploy.Image) // 兼容老的任务配置 if len(t.Releases) == 0 { found := false - for id, v := range pt.ConfigPayload.RepoConfigs { + for id, v := range taskOpt.Task.ConfigPayload.RepoConfigs { if v.Namespace == t.ImageRepo { t.Releases = append(t.Releases, commonmodels.RepoImage{RepoID: id}) found = true @@ -2425,8 +2485,8 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e var repos []commonmodels.RepoImage for _, repoImage := range t.Releases { - if v, ok := pt.ConfigPayload.RepoConfigs[repoImage.RepoID]; ok { - repoImage.Name = util.ReplaceRepo(pt.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) + if v, ok := taskOpt.Task.ConfigPayload.RepoConfigs[repoImage.RepoID]; ok { + repoImage.Name = util.ReplaceRepo(taskOpt.Task.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) repoImage.Host = util.TrimURLScheme(v.RegAddr) repoImage.Namespace = v.Namespace repos = append(repos, repoImage) @@ -2440,7 +2500,7 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e t.ImageRelease = t.Releases[0].Name } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2455,13 +2515,13 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e FmtBuilds(t.Builds, log) if t.Enabled { // 外部触发的pipeline - if pt.TaskCreator == setting.WebhookTaskCreator || pt.TaskCreator == setting.CronTaskCreator { - SetTriggerBuilds(t.Builds, pt.TaskArgs.Builds, log) + if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || taskOpt.Task.TaskCreator == setting.CronTaskCreator { + SetTriggerBuilds(t.Builds, taskOpt.Task.TaskArgs.Builds, log) } else { - setManunalBuilds(t.Builds, pt.TaskArgs.Builds, log) + setManunalBuilds(t.Builds, taskOpt.Task.TaskArgs.Builds, log) } - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } @@ -2474,13 +2534,13 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { - t.SetImageName(pt.TaskArgs.Deploy.Image) - pt.SubTasks[i], err = t.ToSubTask() + t.SetImageName(taskOpt.Task.TaskArgs.Deploy.Image) + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } } - case config.TaskDistribute: + case config.TaskDistribute, config.TaskArtifactPackage: // do nothing case config.TaskTrigger: t, err := base.ToTriggerTask(subTask) @@ -2490,13 +2550,11 @@ func ensurePipelineTask(pt *task.Task, envName string, log *zap.SugaredLogger) e } if t.Enabled { - pt.SubTasks[i], err = t.ToSubTask() + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } } - case config.TaskArtifactPackage: - // do nothing default: return e.NewErrInvalidTaskType(string(pre.TaskType)) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go index 6431c0d2..62629b39 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v3.go @@ -119,7 +119,9 @@ func CreateWorkflowTaskV3(args *commonmodels.WorkflowV3Args, username, reqID str sort.Sort(ByTaskKind(pt.SubTasks)) - if err := ensurePipelineTask(pt, "", log); err != nil { + if err := ensurePipelineTask(&task.TaskOpt{ + Task: pt, + }, log); err != nil { log.Errorf("Service.ensurePipelineTask failed %v %s", args, err) if err, ok := err.(*ContainerNotFound); ok { return nil, e.NewWithExtras( diff --git a/pkg/microservice/user/core/repository/models/user.go b/pkg/microservice/user/core/repository/models/user.go index dae08d0f..71fa4a34 100644 --- a/pkg/microservice/user/core/repository/models/user.go +++ b/pkg/microservice/user/core/repository/models/user.go @@ -24,6 +24,7 @@ type User struct { Email string `json:"email"` Phone string `json:"phone"` Account string `json:"account"` + APIToken string `gorm:"api_token" json:"api_token"` } // TableName sets the insert table name for this struct type diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql index bb77d2f9..75603daf 100644 --- a/pkg/microservice/user/core/service/init/mysql.sql +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS `user`( `identity_type` varchar(32) NOT NULL DEFAULT 'unknown' COMMENT '用户来源', `phone` varchar(16) NOT NULL DEFAULT '' COMMENT '手机号码', `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱', + `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken', `created_at` int(11) unsigned NOT NULL COMMENT '创建时间', `updated_at` int(11) unsigned NOT NULL COMMENT '修改时间', UNIQUE KEY `account` (`account`,`identity_type`), diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 5741c31c..55dfbd88 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -71,6 +71,7 @@ type UserInfo struct { Email string `json:"email"` Phone string `json:"phone"` Account string `json:"account"` + APIToken string `json:"token"` } type Password struct { @@ -175,7 +176,40 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { return nil, err } userInfo := mergeUserLogin([]models.User{*user}, []models.UserLogin{*userLogin}, logger) - return &userInfo[0], nil + userInfoRes := &userInfo[0] + userInfoRes.APIToken = user.APIToken + //TODO Create a permanent OpenAPI token + if user.APIToken == "" { + token, err := login.CreateToken(&login.Claims{ + Name: user.Name, + UID: user.UID, + Email: user.Email, + PreferredUsername: user.Account, + StandardClaims: jwt.StandardClaims{ + Audience: setting.ProductName, + //24*365*100=876000 + ExpiresAt: time.Now().Add(876000 * time.Hour).Unix(), + }, + FederatedClaims: login.FederatedClaims{ + ConnectorId: user.IdentityType, + UserId: user.Account, + }, + }) + if err != nil { + logger.Errorf("LocalLogin user:%s create token error, error msg:%s", user.Account, err.Error()) + return nil, err + } + userInfoRes.APIToken = token + userWithToken := &models.User{ + APIToken: token, + } + err = orm.UpdateUser(uid, userWithToken, core.DB) + if err != nil { + logger.Errorf("UpdateUser user:%s save token error:%s", user.Account, err.Error()) + return nil, err + } + } + return userInfoRes, nil } func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 505c6d28..15bcdbc0 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -35,6 +35,7 @@ const ( TaskResetImage TaskType = "reset_image" TaskDistribute TaskType = "distribute" TaskTrigger TaskType = "trigger" + TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" ) diff --git a/pkg/microservice/warpdrive/core/service/common/types.go b/pkg/microservice/warpdrive/core/service/common/types.go new file mode 100644 index 00000000..b57d7c59 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/common/types.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + +type Stage struct { + //Note: The same stage cannot temporarily run different types of tasks + TaskType config.TaskType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status"` + RunParallel bool `bson:"run_parallel" json:"run_parallel"` + Desc string `bson:"desc,omitempty" json:"desc,omitempty"` + SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + AfterAll bool `json:"after_all" bson:"after_all"` +} diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 5a812686..afac91fb 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -28,6 +28,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" plugins "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" @@ -248,6 +249,7 @@ func (h *ExecHandler) SendNotification() { Status: config.Status(pipelineTask.Status), TeamName: pipelineTask.TeamName, Type: pipelineTask.Type, + Stages: pipelineTask.Stages, }, CreateTime: time.Now().Unix(), IsRead: false, @@ -265,7 +267,7 @@ func (h *ExecHandler) SendNotification() { } } -func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { +func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurrency int64) { xl.Infof("start to execute pipeline stage: %s at position: %d", stage.TaskType, stagePosition) pluginInitiator, ok := h.TaskPlugins[stage.TaskType] if !ok { @@ -282,9 +284,8 @@ func (h *ExecHandler) runStage(stagePosition int, stage *task.Stage) { // Default worker concurrency is 1, run tasks sequentially var workerConcurrency = 1 if runParallel { - // MaxWorkerInParallel is 5 for now - if len(stage.SubTasks) > maxWorkerInParallel { - workerConcurrency = maxWorkerInParallel + if len(stage.SubTasks) > int(concurrency) { + workerConcurrency = int(concurrency) } else { workerConcurrency = len(stage.SubTasks) } @@ -347,7 +348,7 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe // Stage之间仅支持串行 for stagePosition, stage := range pipelineTask.Stages { if !stage.AfterAll { - h.runStage(stagePosition, stage) + h.runStage(stagePosition, stage, pipelineTask.ConfigPayload.BuildConcurrency) // 如果一个Stage执行失败了,跳出执行循环,并且更新pipelinetask状态为失败,发送ACK,并返回 if stage.Status == config.StatusFailed || stage.Status == config.StatusCancelled || stage.Status == config.StatusTimeout { break @@ -378,7 +379,7 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe } } } - h.runStage(stagePosition, stage) + h.runStage(stagePosition, stage, pipelineTask.ConfigPayload.BuildConcurrency) } } diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 64ae7820..939d55b1 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -29,6 +29,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" plugins "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/github" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" @@ -44,7 +45,7 @@ import ( // 1. 转换经过序列化的 SubTasks 到 Stages // Stage Map: *Stage -> map [service->subtask] func transformToStages(pipelineTask *task.Task, xl *zap.SugaredLogger) error { - var pipelineStages []*task.Stage + var pipelineStages []*common.Stage // 工作流1.0,单服务工作流,SubTasks一维数组 // Transform into stages and assign to stages // Task的数据结构中如果没有赋值Type,也按照1.0处理 @@ -57,7 +58,7 @@ func transformToStages(pipelineTask *task.Task, xl *zap.SugaredLogger) error { } // Pipeline 1.0中的一个Subtask对应到Pipeline 2.0中的一个Stage // Stage中subtasks仅有一个subtask, key为service_name - stage := &task.Stage{ + stage := &common.Stage{ TaskType: subTaskPreview.TaskType, // Pipeline 1.0中,每个type subtask只有一个,不存在并行执行 RunParallel: false, @@ -121,10 +122,10 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv // 同时更新stages // TODO: 完善Stage其他字段 if len(pipelineTask.Stages) == 0 { - pipelineTask.Stages = make([]*task.Stage, len(pipelineTask.SubTasks)) + pipelineTask.Stages = make([]*common.Stage, len(pipelineTask.SubTasks)) } if pipelineTask.Stages[pos] == nil { - pipelineTask.Stages[pos] = &task.Stage{} + pipelineTask.Stages[pos] = &common.Stage{} } pipelineTask.Stages[pos].SubTasks = map[string]map[string]interface{}{servicename: subTask} } else if pipelineTask.Type == config.WorkflowType { @@ -150,7 +151,7 @@ func updatePipelineSubTask(t interface{}, pipelineTask *task.Task, pos int, serv func updatePipelineStageStatus(stageStatus config.Status, pipelineTask *task.Task, pos int, xl *zap.SugaredLogger) { xl.Infof("updating pipeline task, stage status: %s, stage position: %d", stageStatus, pos) if pipelineTask.Stages[pos] == nil { - pipelineTask.Stages[pos] = &task.Stage{} + pipelineTask.Stages[pos] = &common.Stage{} } pipelineTask.Stages[pos].Status = stageStatus } @@ -558,6 +559,7 @@ func initTaskPlugins(execHandler *ExecHandler) { config.TaskResetImage: plugins.InitializeDeployTaskPlugin, config.TaskTrigger: plugins.InitializeTriggerTaskPlugin, config.TaskArtifactPackage: plugins.InitializeArtifactPackagePlugin, + config.TaskExtension: plugins.InitializeExtensionTaskPlugin, } for name, pluginInitiator := range pluginConf { registerTaskPlugin(execHandler, name, pluginInitiator) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 44d3fc97..0e89f64b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" @@ -113,9 +114,12 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe // TODO: Since the namespace field has been used continuously since v1.10.0, the processing logic related to namespace needs to // be deleted in v1.11.0. - p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace - if p.Task.Namespace != "" { - p.KubeNamespace = p.Task.Namespace + switch p.Task.ClusterID { + case setting.LocalClusterID: + p.KubeNamespace = zadigconfig.Namespace() + default: + p.KubeNamespace = setting.AttachedClusterNamespace + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 38621521..f86a0c39 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -212,7 +212,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } } - + containerName := p.Task.ContainerName + containerName = strings.TrimSuffix(containerName, "_"+p.Task.ServiceName) if p.Task.ServiceType != setting.HelmDeployType { // get servcie info var ( @@ -245,8 +246,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * L: for _, deploy := range deployments { for _, container := range deploy.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -268,8 +269,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * Loop: for _, sts := range statefulSets { for _, container := range sts.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -297,8 +298,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } for _, container := range statefulSet.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -323,8 +324,8 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } for _, container := range deployment.Spec.Template.Spec.Containers { - if container.Name == p.Task.ContainerName { - err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, p.Task.ContainerName, p.Task.Image, p.kubeClient) + if container.Name == containerName { + err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, containerName, p.Task.Image, p.kubeClient) if err != nil { err = errors.WithMessagef( err, @@ -346,7 +347,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if !replaced { err = errors.Errorf( - "container %s is not found in resources with label %s", p.Task.ContainerName, selector) + "container %s is not found in resources with label %s", containerName, selector) return } } else if p.Task.ServiceType == setting.HelmDeployType { @@ -379,7 +380,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, rcsList) p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, - p.Task.ServiceName, p.Task.ContainerName, p.Task.Namespace) + p.Task.ServiceName, containerName, p.Task.Namespace) productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) if err != nil { @@ -405,7 +406,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if service.ServiceName == p.Task.ServiceName { serviceRevisionInProduct = service.Revision for _, container := range service.Containers { - if container.Name == p.Task.ContainerName { + if container.Name == containerName { targetContainer = container break } @@ -415,12 +416,12 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } if targetContainer == nil { - err = errors.Errorf("failed to find target container %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to find target container %s from service %s", containerName, p.Task.ServiceName) return } if targetContainer.ImagePath == nil { - err = errors.Errorf("failed to get image path of %s from service %s", p.Task.ContainerName, p.Task.ServiceName) + err = errors.Errorf("failed to get image path of %s from service %s", containerName, p.Task.ServiceName) return } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go new file mode 100644 index 00000000..97db628d --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -0,0 +1,256 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/client" + + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/tool/httpclient" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" +) + +const ( + ExtensionTaskTimeout = 60 * 60 * 1 // 60 minutes + ZadigEvent = "X-Zadig-Event" + EventName = "Workflow" +) + +// InitializeExtensionTaskPlugin to initialize build task plugin, and return reference +func InitializeExtensionTaskPlugin(taskType config.TaskType) TaskPlugin { + return &ExtensionTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + } +} + +// ExtensionTaskPlugin is Plugin, name should be compatible with task type +type ExtensionTaskPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + Task *task.Extension + Log *zap.SugaredLogger + cancel context.CancelFunc + ack func() + pipelineName string + taskId int64 +} + +func (p *ExtensionTaskPlugin) SetAckFunc(ack func()) { + p.ack = ack +} + +// Init ... +func (p *ExtensionTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl + p.FileName = filename +} + +func (p *ExtensionTaskPlugin) Type() config.TaskType { + return p.Name +} + +// Status ... +func (p *ExtensionTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +// SetStatus ... +func (p *ExtensionTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status +} + +// TaskTimeout ... +func (p *ExtensionTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = ExtensionTaskTimeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *ExtensionTaskPlugin) SetExtensionStatusCompleted(status config.Status) { + p.Task.TaskStatus = status + p.Task.EndTime = time.Now().Unix() +} + +func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + var ( + err error + body []byte + ) + defer func() { + if err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + return + } + }() + p.pipelineName = pipelineTask.PipelineName + p.taskId = pipelineTask.TaskID + p.Log.Infof("succeed to create extension task %s", p.JobName) + _, p.cancel = context.WithCancel(context.Background()) + httpClient := httpclient.New( + httpclient.SetHostURL(p.Task.URL), + ) + url := p.Task.Path + for _, header := range p.Task.Headers { + httpclient.SetHeader(header.Key, header.Value) + } + + webhookPayload := &task.WebhookPayload{ + EventName: "workflow", + ProjectName: pipelineTask.ProductName, + TaskName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + ServiceInfos: p.Task.ServiceInfos, + Creator: pipelineTask.TaskCreator, + } + body, err = json.Marshal(webhookPayload) + if err != nil { + return + } + for _, header := range p.Task.Headers { + httpclient.SetHeader(header.Key, header.Value) + } + _, err = httpClient.Post(url, httpclient.SetHeader(ZadigEvent, EventName), httpclient.SetBody(body)) + if err != nil { + return + } + if !p.Task.IsCallback { + p.SetExtensionStatusCompleted(config.StatusPassed) + } +} + +// Wait ... +func (p *ExtensionTaskPlugin) Wait(ctx context.Context) { + timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) + defer p.cancel() + + for { + select { + case <-ctx.Done(): + p.Task.TaskStatus = config.StatusCancelled + return + case <-timeout: + p.Task.TaskStatus = config.StatusTimeout + p.Task.Error = "timeout" + return + default: + time.Sleep(time.Second * 3) + callbackPayloadObj, _ := p.getCallbackObj(p.taskId, p.pipelineName) + if callbackPayloadObj != nil { + if callbackPayloadObj.Status == "success" { + p.Task.TaskStatus = config.StatusPassed + return + } + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = callbackPayloadObj.StatusMessage + return + } + + if p.IsTaskDone() { + return + } + } + } +} + +func (p *ExtensionTaskPlugin) getCallbackObj(taskID int64, pipelineName string) (*task.CallbackPayloadObj, error) { + url := fmt.Sprintf("/api/workflow/workflowtask/callback/id/%d/name/%s", taskID, pipelineName) + httpClient := httpclient.New( + httpclient.SetHostURL(configbase.AslanServiceAddress()), + ) + + CallbackPayloadObj := new(task.CallbackPayloadObj) + _, err := httpClient.Get(url, httpclient.SetResult(&CallbackPayloadObj)) + if err != nil { + return nil, err + } + return CallbackPayloadObj, nil +} + +// Complete ... +func (p *ExtensionTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { +} + +// SetTask ... +func (p *ExtensionTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToExtensionTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *ExtensionTaskPlugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *ExtensionTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *ExtensionTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *ExtensionTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() +} + +// SetEndTime ... +func (p *ExtensionTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() +} + +// IsTaskEnabled ... +func (p *ExtensionTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *ExtensionTaskPlugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index bdec6c89..f2c6e16f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -29,6 +29,7 @@ import ( "gopkg.in/yaml.v3" "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" @@ -107,9 +108,12 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC // TODO: Since the namespace field has been used continuously since v1.10.0, the processing logic related to namespace needs to // be deleted in v1.11.0. - p.KubeNamespace = pipelineTask.ConfigPayload.Test.KubeNamespace - if p.Task.Namespace != "" { - p.KubeNamespace = p.Task.Namespace + switch p.Task.ClusterID { + case setting.LocalClusterID: + p.KubeNamespace = zadigconfig.Namespace() + default: + p.KubeNamespace = setting.AttachedClusterNamespace + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { msg := fmt.Sprintf("failed to get kube client: %s", err) @@ -120,6 +124,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } p.kubeClient = kubeClient } + // not local cluster var ( replaceDindServer = "." + DindServer diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index 658a207d..d23f8c51 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -148,7 +148,7 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi for _, header := range p.Task.Headers { httpclient.SetHeader(header.Key, header.Value) } - _, err = httpClient.Post(url, httpclient.SetHeader("X-Zadig-Event", "Workflow"), httpclient.SetBody(body)) + _, err = httpClient.Post(url, httpclient.SetHeader(ZadigEvent, EventName), httpclient.SetBody(body)) if err != nil { return } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index f87ea0be..ff69bd93 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -211,3 +211,11 @@ func ToTriggerTask(sb map[string]interface{}) (*task.Trigger, error) { } return trigger, nil } + +func ToExtensionTask(sb map[string]interface{}) (*task.Extension, error) { + var extension *task.Extension + if err := task.IToi(sb, &extension); err != nil { + return nil, fmt.Errorf("convert interface to extensionTask error: %s", err) + } + return extension, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/notify.go b/pkg/microservice/warpdrive/core/service/types/notify.go index fb876785..8b96e9ff 100644 --- a/pkg/microservice/warpdrive/core/service/types/notify.go +++ b/pkg/microservice/warpdrive/core/service/types/notify.go @@ -16,7 +16,10 @@ limitations under the License. package types -import "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +import ( + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" +) //// AnnouncementCtx ... //type AnnouncementCtx struct { @@ -35,6 +38,7 @@ type PipelineStatusCtx struct { Type config.PipelineType `bson:"type" json:"type"` Status config.Status `bson:"status" json:"status,omitempty"` TeamName string `bson:"team" json:"team"` + Stages []*common.Stage `bson:"stages" json:"stages"` } // diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index 61f65238..aac740dc 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -55,6 +55,9 @@ type ConfigPayload struct { K8SClusters []*K8SCluster `json:"k8s_clusters"` RegistryID string `json:"registry_id"` + + // build concurrency settings + BuildConcurrency int64 `json:"build_concurrency"` } func (cp *ConfigPayload) GetGitKnownHost() string { diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go new file mode 100644 index 00000000..540b46ed --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -0,0 +1,55 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" +) + +type Extension struct { + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` +} + +type ServiceInfo struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` +} + +// ToSubTask ... +func (t *Extension) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert extension to interface error: %s", err) + } + return task, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index cc333453..2535b31a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" "github.com/koderover/zadig/pkg/setting" ) @@ -39,7 +40,7 @@ type Task struct { StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - Stages []*Stage `bson:"stages" json:"stages"` + Stages []*common.Stage `bson:"stages" json:"stages"` ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` DockerHost string `bson:"-" json:"docker_host,omitempty"` @@ -312,16 +313,6 @@ type TriggerBy struct { CommitID string `json:"commit_id,omitempty" bson:"commit_id,omitempty"` } -type Stage struct { - // 注意: 同一个stage暂时不能运行不同类型的Task - TaskType config.TaskType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status"` - RunParallel bool `bson:"run_parallel" json:"run_parallel"` - Desc string `bson:"desc,omitempty" json:"desc,omitempty"` - SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - AfterAll bool `json:"after_all" bson:"after_all"` -} - func (Task) TableName() string { return "pipeline_task_v2" } diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 9c82d161..bce09fb1 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,12 +54,14 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - EventName string `json:"event_name"` - ProjectName string `json:"project_name"` - TaskName string `json:"task_name"` - TaskID int64 `json:"task_id"` - TaskOutput []*TaskOutput `json:"task_output"` - TaskEnvs []*KeyVal `json:"task_envs"` + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output"` + TaskEnvs []*KeyVal `json:"task_envs"` + ServiceInfos []*ServiceInfo `json:"service_infos"` + Creator string `json:"creator"` } type TaskOutput struct { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index ede87a23..a5c41cc4 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -622,3 +622,7 @@ const ( PolicyBindingTypeSystem PolicyBindingType = "system" LabelTypeSystem LabelType = "system" ) + +// AttachedClusterNamespace is the namespace Zadig uses in attached cluster. +// Note: **Restricted because of product design since v1.9.0**. +const AttachedClusterNamespace = "koderover-agent" diff --git a/pkg/shared/client/aslan/workflow.go b/pkg/shared/client/aslan/workflow.go new file mode 100644 index 00000000..b7812e6c --- /dev/null +++ b/pkg/shared/client/aslan/workflow.go @@ -0,0 +1,44 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aslan + +import ( + "encoding/json" + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type workflowConcurrencySettingResp struct { + WorkflowConcurrency int64 `json:"workflow_concurrency"` + BuildConcurrency int64 `json:"build_concurrency"` +} + +func (c *Client) GetWorkflowConcurrencySetting() (*workflowConcurrencySettingResp, error) { + url := "/system/concurrency/workflow" + + resp := &workflowConcurrencySettingResp{} + res, err := c.Get(url, httpclient.SetResult(resp)) + if err != nil { + errorMessage := new(ErrorMessage) + err := json.Unmarshal(res.Body(), errorMessage) + if err != nil { + return nil, fmt.Errorf("failed to get workflow concurrency settings, error: %s", err) + } + } + return resp, nil +} diff --git a/pkg/util/yaml/path_search.go b/pkg/util/yaml/path_search.go index 787556d0..919475cf 100644 --- a/pkg/util/yaml/path_search.go +++ b/pkg/util/yaml/path_search.go @@ -258,7 +258,7 @@ func toPathResult(prefix string, path []string, aPatternData map[string]*pattern return ret } -func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData) []map[string]string { +func processResults(resultSets []*pathResultSet, aPatternData map[string]*patternData, pattern map[string]string) []map[string]string { ret := make([]map[string]string, 0) for _, singleSet := range resultSets { retMap := make(map[string]string) @@ -278,6 +278,9 @@ func processResults(resultSets []*pathResultSet, aPatternData map[string]*patter for _, result := range singleSet.resultSet { retMap[result.name] = result.absolutePath } + if len(pattern) != len(retMap) { + continue + } ret = append(ret, retMap) } return ret @@ -408,7 +411,7 @@ func search(flatMap map[string]interface{}, pattern map[string]string) ([]map[st results = append(results, result) } - return processResults(results, allPatternData), nil + return processResults(results, allPatternData, pattern), nil } // SearchByPattern find all matched absolute paths from yaml by the pattern appointed diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 2dfc3e38..162e2f96 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -101,6 +101,22 @@ service: port: 8080 ` +var testYaml9 = ` + image: + pullPolicy: IfNotPresent + + imagePullSecrets: + - name: default-registry-secret + + global: + hub: ccr.ccs.tencentyun.com/trial + + deploy: + image: + name: go-sample-site + tag: 0.1.0 +` + var err error var lcpMatedPaths []map[string]string @@ -178,5 +194,19 @@ var _ = Describe("Testing search", func() { })) }) + It("match all rules of pattern", func() { + pattern := []map[string]string{ + {"image": "image.repository", "tag": "image.tag"}, + {"image": "image"}, + {"repo": "global.hub", "tag": "deploy.image.tag", "image": "deploy.image.name"}, + } + flatMap, _ := converter.YamlToFlatMap([]byte(testYaml9)) + lcpMatedPaths, err = SearchByPattern(flatMap, pattern) + Expect(err).NotTo(HaveOccurred()) + Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + {"image": "deploy.image.name", "repo": "global.hub", "tag": "deploy.image.tag"}, + })) + }) + }) }) -- Gitee From 11af6ece90cc9ff4e7f278247b80d4d5b9f95878 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 14:27:26 +0800 Subject: [PATCH 1628/2647] modify param type Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 061a2440..749d7a84 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -84,7 +84,7 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger(), source) + return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger(), source...) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { -- Gitee From 1be5938bb26b3015795c93ff3e63f731731c676a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 15:07:41 +0800 Subject: [PATCH 1629/2647] modify param type Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 0af03b13..23cd1db2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -470,13 +470,13 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr base string chartTree = afero.NewMemMapFs() ) - repoArgs, ok := args.CreateFrom.(*CreateFromRepo) - if !ok { + repoArgs, ok := args.CreateFrom.(CreateFromRepo) + if ok { filePaths = repoArgs.Paths base = path.Join(config.S3StoragePath(), repoArgs.Repo) } - helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(repoArgs.Paths)) + helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(filePaths)) var wg wait.Group var mux sync.RWMutex for _, p := range filePaths { -- Gitee From f88375916aa7d00786879e8e87f6f1fa10f96af6 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 22 Feb 2022 15:13:17 +0800 Subject: [PATCH 1630/2647] support get test html report Signed-off-by: ddh27 --- .../common/service/instantmessage/service.go | 127 ++++++++---------- .../service/workflow/pipeline_task.go | 1 + .../core/service/taskplugin/testing.go | 1 + 3 files changed, 55 insertions(+), 74 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index f5444632..6372ae53 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -337,43 +337,7 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific case config.TaskTestingV2: test = "{{if eq .WebHookType \"dingding\"}}##### {{end}}**测试结果** \n" for _, sb := range subStage.SubTasks { - testSt, err := base.ToTestingTask(sb) - if err != nil { - return "", "", nil, err - } - if testSt.TaskStatus == "" { - testSt.TaskStatus = config.StatusNotRun - } - if testSt.JobCtx.TestType == setting.FunctionTest && testSt.JobCtx.TestReportPath != "" { - url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testSt.TestModuleName) - test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testSt.TestModuleName, url) - } else { - test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testSt.TestModuleName) - } - - if weChatNotification.Task.TestReports == nil { - test += fmt.Sprintf("%s \n", testSt.TaskStatus) - continue - } - - for testname, report := range weChatNotification.Task.TestReports { - if testname != testSt.TestModuleName { - continue - } - tr := &task.TestReport{} - if task.IToi(report, tr) != nil { - log.Errorf("parse TestReport failed, err:%s", err) - continue - } - if tr.FunctionTestSuite == nil { - test += fmt.Sprintf("%s \n", testSt.TaskStatus) - continue - } - totalNum := tr.FunctionTestSuite.Tests + tr.FunctionTestSuite.Skips - failedNum := tr.FunctionTestSuite.Failures + tr.FunctionTestSuite.Errors - successNum := tr.FunctionTestSuite.Tests - failedNum - test += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", successNum, failedNum, totalNum) - } + test = genTestCaseText(test, sb, weChatNotification.Task.TestReports) } } } @@ -431,43 +395,8 @@ func (w *Service) createNotifyBodyOfTestIM(desc string, weChatNotification *wech if stage.TaskType != config.TaskTestingV2 { continue } - - for testName, subTask := range stage.SubTasks { - testInfo, err := base.ToTestingTask(subTask) - if err != nil { - log.Errorf("parse testInfo failed, err:%s", err) - continue - } - if testInfo.JobCtx.TestType == setting.FunctionTest && testInfo.JobCtx.TestResultPath != "" { - if testInfo.JobCtx.TestReportPath != "" { - url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testName) - tplTestCaseInfo += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testName, url) - } else { - tplTestCaseInfo += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testName) - } - if weChatNotification.Task.TestReports == nil { - tplTestCaseInfo += fmt.Sprintf("%s \n ", testInfo.TaskStatus) - continue - } - for testname, report := range weChatNotification.Task.TestReports { - if testname != testInfo.TestModuleName { - continue - } - tr := &task.TestReport{} - if task.IToi(report, tr) != nil { - log.Errorf("parse TestReport failed, err:%s", err) - continue - } - if tr.FunctionTestSuite == nil { - tplTestCaseInfo += fmt.Sprintf("%s \n ", testInfo.TaskStatus) - continue - } - totalNum := tr.FunctionTestSuite.Tests + tr.FunctionTestSuite.Skips - failedNum := tr.FunctionTestSuite.Failures + tr.FunctionTestSuite.Errors - successNum := tr.FunctionTestSuite.Tests - failedNum - tplTestCaseInfo += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", successNum, failedNum, totalNum) - } - } + for _, subTask := range stage.SubTasks { + tplTestCaseInfo = genTestCaseText(tplTestCaseInfo, subTask, weChatNotification.Task.TestReports) } } @@ -588,3 +517,53 @@ func getTplExec(tplcontent string, weChatNotification *wechatNotification) (stri } return buffer.String(), nil } + +func checkTestReportsExist(testModuleName string, testReports map[string]interface{}) bool { + for testname := range testReports { + if testname == testModuleName { + return true + } + } + return false +} + +func genTestCaseText(test string, subTask, testReports map[string]interface{}) string { + testSt, err := base.ToTestingTask(subTask) + if err != nil { + log.Errorf("parse testInfo failed, err:%s", err) + return test + } + if testSt.TaskStatus == "" { + testSt.TaskStatus = config.StatusNotRun + } + if testSt.JobCtx.TestType == setting.FunctionTest && testSt.JobCtx.TestReportPath != "" { + url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testSt.TestModuleName) + test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testSt.TestModuleName, url) + } else { + test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}%s: ", testSt.TestModuleName) + } + if testReports == nil || !checkTestReportsExist(testSt.TestModuleName, testReports) { + test += fmt.Sprintf("%s \n", testSt.TaskStatus) + return test + } + + for testname, report := range testReports { + if testname != testSt.TestModuleName { + continue + } + tr := &task.TestReport{} + if task.IToi(report, tr) != nil { + log.Errorf("parse TestReport failed, err:%s", err) + continue + } + if tr.FunctionTestSuite == nil { + test += fmt.Sprintf("%s \n", testSt.TaskStatus) + continue + } + totalNum := tr.FunctionTestSuite.Tests + tr.FunctionTestSuite.Skips + failedNum := tr.FunctionTestSuite.Failures + tr.FunctionTestSuite.Errors + successNum := tr.FunctionTestSuite.Tests - failedNum + test += fmt.Sprintf("%d(成功)%d(失败)%d(总数) \n", successNum, failedNum, totalNum) + } + return test +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 6c1fd6fd..b090ccb1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -706,6 +706,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testTask.JobCtx.BuildSteps = append(testTask.JobCtx.BuildSteps, &task.BuildStep{BuildType: "shell", Scripts: testModule.Scripts}) testTask.JobCtx.TestResultPath = testModule.TestResultPath + testTask.JobCtx.TestReportPath = testModule.TestReportPath testTask.JobCtx.TestThreshold = testModule.Threshold testTask.JobCtx.Caches = testModule.Caches testTask.JobCtx.ArtifactPaths = testModule.ArtifactPaths diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index f2c6e16f..36de8a09 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -187,6 +187,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC testReportFile = fmt.Sprintf("%s-%s-%d-%s-%s-html", config.WorkflowType, pipelineTask.PipelineName, pipelineTask.TaskID, config.TaskTestingV2, p.Task.TestModuleName) } else if pipelineTask.Type == config.TestType { fileName = fmt.Sprintf("%s-%s-%d-%s-%s", config.TestType, pipelineTask.PipelineName, pipelineTask.TaskID, config.TaskTestingV2, serviceName) + testReportFile = fmt.Sprintf("%s-%s-%d-%s-%s-html", config.TestType, pipelineTask.PipelineName, pipelineTask.TaskID, config.TaskTestingV2, p.Task.TestModuleName) } fileName = strings.Replace(strings.ToLower(fileName), "_", "-", -1) -- Gitee From b88d86f7490087e1cfb1cbe3d3da8401d5260773 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 15:42:31 +0800 Subject: [PATCH 1631/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 23cd1db2..db88cd9e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -18,6 +18,7 @@ package service import ( "bytes" + "encoding/json" "fmt" "io/fs" "os" @@ -470,11 +471,25 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr base string chartTree = afero.NewMemMapFs() ) - repoArgs, ok := args.CreateFrom.(CreateFromRepo) - if ok { - filePaths = repoArgs.Paths - base = path.Join(config.S3StoragePath(), repoArgs.Repo) - } + //repoArgs, ok := args.CreateFrom.(CreateFromRepo) + //if ok { + // filePaths = repoArgs.Paths + // base = path.Join(config.S3StoragePath(), repoArgs.Repo) + //} + resByte, resByteErr := json.Marshal(args.CreateFrom) + if resByteErr != nil { + log.Errorf("failed to json.Marshal err:%s", resByteErr) + return nil, resByteErr + } + var createFromRepo CreateFromRepo + jsonResErr := json.Unmarshal(resByte, &createFromRepo) + if jsonResErr != nil { + log.Errorf("failed to json.Unmarshal err:%s", resByteErr) + return nil, jsonResErr + } + + filePaths = createFromRepo.Paths + base = path.Join(config.S3StoragePath(), createFromRepo.Repo) helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(filePaths)) var wg wait.Group -- Gitee From bd1d80314a54c881d16fc93af5441c61db3616e4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 15:47:31 +0800 Subject: [PATCH 1632/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index db88cd9e..c0909006 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -591,10 +591,10 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr FilePath: filePath, ProductName: projectName, CreateBy: args.CreatedBy, - CodehostID: repoArgs.CodehostID, - Owner: repoArgs.Owner, - Repo: repoArgs.Repo, - Branch: repoArgs.Branch, + CodehostID: createFromRepo.CodehostID, + Owner: createFromRepo.Owner, + Repo: createFromRepo.Repo, + Branch: createFromRepo.Branch, //RepoLink: repoLink, Source: string(args.Source), }, -- Gitee From a4c66de64d42d0358924092aaadf624cd8408fcd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 15:57:09 +0800 Subject: [PATCH 1633/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index c0909006..e974650e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -471,11 +471,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr base string chartTree = afero.NewMemMapFs() ) - //repoArgs, ok := args.CreateFrom.(CreateFromRepo) - //if ok { - // filePaths = repoArgs.Paths - // base = path.Join(config.S3StoragePath(), repoArgs.Repo) - //} resByte, resByteErr := json.Marshal(args.CreateFrom) if resByteErr != nil { log.Errorf("failed to json.Marshal err:%s", resByteErr) @@ -535,12 +530,12 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePath := path.Join(base, filePath) log.Infof("Loading chart under path %s", filePath) - serviceName, chartVersion, finalErr = readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + serviceName, chartVersion, finalErr = readChartYAML(afero.NewIOFS(chartTree), filePath, log) if finalErr != nil { log.Errorf("readChartYAML err:%+v", finalErr) return } - valuesYAML, finalErr = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) + valuesYAML, finalErr = readValuesYAML(afero.NewIOFS(chartTree), filePath, log) if finalErr != nil { log.Errorf("readValuesYAML err:%+v", finalErr) return -- Gitee From ea52f3a10f28c8eac8edbf232834bd27ea9277b8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 16:10:24 +0800 Subject: [PATCH 1634/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e974650e..b673556f 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -469,7 +469,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePaths []string response = &BulkHelmServiceCreationResponse{} base string - chartTree = afero.NewMemMapFs() + chartTree = afero.NewOsFs() ) resByte, resByteErr := json.Marshal(args.CreateFrom) if resByteErr != nil { -- Gitee From f458b2a501c8296657439b8055b7d2a36c823998 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 17:06:26 +0800 Subject: [PATCH 1635/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 46 +++++++++++-------- pkg/util/file.go | 14 ++++++ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index b673556f..16e4d01c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -50,6 +50,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -511,31 +512,14 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr mux.Unlock() }() - //fsTree, err := fsservice.DownloadFilesFromSource( - // &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, - // func(chartTree afero.Fs) (string, error) { - // var err error - // serviceName, chartVersion, err = readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) - // if err != nil { - // return serviceName, err - // } - // valuesYAML, err = readValuesYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) - // return serviceName, err - // }) - //if err != nil { - // log.Errorf("Failed to download files from source, err %s", err) - // finalErr = e.ErrCreateTemplate.AddErr(err) - // return - //} - filePath := path.Join(base, filePath) log.Infof("Loading chart under path %s", filePath) - serviceName, chartVersion, finalErr = readChartYAML(afero.NewIOFS(chartTree), filePath, log) + serviceName, chartVersion, finalErr = readChartYAMLFromLocal(filePath, log) if finalErr != nil { log.Errorf("readChartYAML err:%+v", finalErr) return } - valuesYAML, finalErr = readValuesYAML(afero.NewIOFS(chartTree), filePath, log) + valuesYAML, finalErr = readValuesYAMLFromLocal(filePath, log) if finalErr != nil { log.Errorf("readValuesYAML err:%+v", finalErr) return @@ -954,6 +938,21 @@ func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (str return chart.Name, chart.Version, nil } +func readChartYAMLFromLocal(base string, logger *zap.SugaredLogger) (string, string, error) { + chartFile, err := util.ReadFile(filepath.Join(base, setting.ChartYaml)) + if err != nil { + logger.Errorf("Failed to read %s, err: %s", setting.ChartYaml, err) + return "", "", err + } + chart := new(Chart) + if err = yaml.Unmarshal(chartFile, chart); err != nil { + log.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ChartYaml, err) + return "", "", err + } + + return chart.Name, chart.Version, nil +} + func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, error) { content, err := fs.ReadFile(chartTree, filepath.Join(base, setting.ValuesYaml)) if err != nil { @@ -963,6 +962,15 @@ func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([] return content, nil } +func readValuesYAMLFromLocal(base string, logger *zap.SugaredLogger) ([]byte, error) { + content, err := util.ReadFile(filepath.Join(base, setting.ValuesYaml)) + if err != nil { + logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) + return nil, err + } + return content, nil +} + func geneCreationDetail(args *helmServiceCreationArgs) interface{} { switch args.Source { case setting.SourceFromGitlab, diff --git a/pkg/util/file.go b/pkg/util/file.go index 64ede022..30144213 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "io/ioutil" "os" ) @@ -46,3 +47,16 @@ func WriteFile(filename string, data []byte, perm os.FileMode) error { } return nil } + +func ReadFile(filename string) ([]byte, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + contentByte, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + return contentByte, nil +} -- Gitee From 3766064caa049dfb240c62ba13ce0274efac4c8d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 17:17:10 +0800 Subject: [PATCH 1636/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 18 ++++++++---------- .../aslan/core/common/service/helm.go | 4 ++-- .../aslan/core/service/service/helm.go | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 9d609131..d2639dca 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -47,19 +47,17 @@ func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) err } // SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. -func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string, logger *zap.SugaredLogger, source ...string) error { +func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string, logger *zap.SugaredLogger) error { var wg wait.Group var err error - if len(source) == 0 { - wg.Start(func() { - err1 := saveInMemoryFilesToDisk(fileTree, localBase, logger) - if err1 != nil { - logger.Errorf("Failed to save files to disk, err: %s", err1) - err = err1 - } - }) - } + wg.Start(func() { + err1 := saveInMemoryFilesToDisk(fileTree, localBase, logger) + if err1 != nil { + logger.Errorf("Failed to save files to disk, err: %s", err1) + err = err1 + } + }) wg.Start(func() { err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 749d7a84..f8ddc6c8 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -80,11 +80,11 @@ func DownloadServiceManifests(base, projectName, serviceName string) error { return fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) } -func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS, source ...string) error { +func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS) error { localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger(), source...) + return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 16e4d01c..9ee0b37c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -542,7 +542,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr }() // upload them to s3 - if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree), string(args.Source)); err != nil { + if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree)); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) finalErr = e.ErrCreateTemplate.AddErr(err) return -- Gitee From 516dac73e1059d20435daaab9df64bd0d3870cf8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 18:55:34 +0800 Subject: [PATCH 1637/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 27 +++++++++++++++++++ .../aslan/core/common/service/helm.go | 8 ++++++ .../aslan/core/service/service/helm.go | 6 ++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index d2639dca..679d0dc6 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -20,6 +20,7 @@ import ( "io/fs" "os" + "github.com/otiai10/copy" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" @@ -72,6 +73,32 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string return err } +// SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. +func CopyAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { + var wg wait.Group + var err error + + wg.Start(func() { + copyErr := copy.Copy(currentChartPath, localBase) + if copyErr != nil { + logger.Errorf("failed to copy chart info, err %s", err) + err = copyErr + } + }) + + wg.Start(func() { + err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) + if err2 != nil { + logger.Errorf("Failed to upload files to s3, err: %s", err2) + err = err2 + } + }) + + wg.Wait() + + return err +} + func saveInMemoryFilesToDisk(fileTree fs.FS, root string, logger *zap.SugaredLogger) error { ok, err := fsutil.DirExists(root) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index f8ddc6c8..5d5a8f2e 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -87,6 +87,14 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) } +func CopyAndUploadService(projectName, serviceName, currentChartPath string, copies []string, fileTree fs.FS) error { + localBase := config.LocalServicePath(projectName, serviceName) + s3Base := config.ObjectStorageServicePath(projectName, serviceName) + names := append([]string{serviceName}, copies...) + + return fsservice.CopyAndUploadFiles(fileTree, names, localBase, s3Base, currentChartPath, log.SugaredLogger()) +} + func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { tree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName, RepoLink: svc.SrcPath}, diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 9ee0b37c..1cfbf35b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -470,7 +470,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePaths []string response = &BulkHelmServiceCreationResponse{} base string - chartTree = afero.NewOsFs() + chartTree = afero.NewMemMapFs() ) resByte, resByteErr := json.Marshal(args.CreateFrom) if resByteErr != nil { @@ -541,8 +541,8 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr } }() - // upload them to s3 - if err = commonservice.SaveAndUploadService(projectName, serviceName, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree)); err != nil { + // copy to latest dir and upload to s3 + if err = commonservice.CopyAndUploadService(projectName, serviceName, filePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree)); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) finalErr = e.ErrCreateTemplate.AddErr(err) return -- Gitee From 1a1d7a9e0d854d8f24f1ef8981e2d16307d964e5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 19:14:12 +0800 Subject: [PATCH 1638/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 1cfbf35b..28e6aed9 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1039,7 +1039,17 @@ func renderVariablesToYaml(valuesYaml string, productName, serviceName string, v } func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*commonmodels.Service, error) { - chartName, chartVersion, err := readChartYAML(fsTree, args.ServiceName, logger) + var ( + chartName, chartVersion string + err error + ) + switch args.Source { + case string(LoadFromGerrit): + chartName, chartVersion, err = readChartYAMLFromLocal(args.FilePath, logger) + default: + chartName, chartVersion, err = readChartYAML(fsTree, args.ServiceName, logger) + } + if err != nil { logger.Errorf("Failed to read chart.yaml, err %s", err) return nil, err -- Gitee From e5a930cb7a2206c59e6a894f4121a570516bb22f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 19:57:21 +0800 Subject: [PATCH 1639/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 3 ++- pkg/microservice/aslan/core/service/service/helm.go | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 5d5a8f2e..f8e34c4c 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -18,6 +18,7 @@ package service import ( "io/fs" + "path" "github.com/27149chen/afero" "go.uber.org/zap" @@ -92,7 +93,7 @@ func CopyAndUploadService(projectName, serviceName, currentChartPath string, cop s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.CopyAndUploadFiles(fileTree, names, localBase, s3Base, currentChartPath, log.SugaredLogger()) + return fsservice.CopyAndUploadFiles(fileTree, names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 28e6aed9..a051eaca 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -516,16 +516,13 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr log.Infof("Loading chart under path %s", filePath) serviceName, chartVersion, finalErr = readChartYAMLFromLocal(filePath, log) if finalErr != nil { - log.Errorf("readChartYAML err:%+v", finalErr) return } valuesYAML, finalErr = readValuesYAMLFromLocal(filePath, log) if finalErr != nil { - log.Errorf("readValuesYAML err:%+v", finalErr) return } - log.Infof("serviceName:%s,chartVersion:%s", serviceName, chartVersion) - log.Infof("valuesYAML:%s", string(valuesYAML)) + log.Info("Found valid chart, Starting to save and upload files") rev, err := getNextServiceRevision(projectName, serviceName) if err != nil { -- Gitee From 2708a4632c507fd494b8dee97fec88c8a0fd96a4 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 22 Feb 2022 20:16:19 +0800 Subject: [PATCH 1640/2647] fix IM URL with statuspassed Signed-off-by: ddh27 --- .../aslan/core/common/service/instantmessage/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 6372ae53..db3f5778 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -536,7 +536,7 @@ func genTestCaseText(test string, subTask, testReports map[string]interface{}) s if testSt.TaskStatus == "" { testSt.TaskStatus = config.StatusNotRun } - if testSt.JobCtx.TestType == setting.FunctionTest && testSt.JobCtx.TestReportPath != "" { + if testSt.JobCtx.TestType == setting.FunctionTest && testSt.JobCtx.TestReportPath != "" && testSt.TaskStatus == config.StatusPassed { url := fmt.Sprintf("{{.BaseURI}}/api/aslan/testing/report?pipelineName={{.Task.PipelineName}}&pipelineType={{.Task.Type}}&taskID={{.Task.TaskID}}&testName=%s", testSt.TestModuleName) test += fmt.Sprintf("{{if ne .WebHookType \"feishu\"}} - {{end}}[%s](%s): ", testSt.TestModuleName, url) } else { -- Gitee From bac2fec091c9820a160c95c09cd1509dfec0ae93 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 22 Feb 2022 20:41:26 +0800 Subject: [PATCH 1641/2647] add recreate url Signed-off-by: mouuii --- .../policy/core/handler/role_binding.go | 23 +++++++++++++++++++ .../policy/core/handler/router.go | 1 + 2 files changed, 24 insertions(+) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 5f3218f0..5d87c484 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -115,6 +115,29 @@ func DeleteRoleBindings(c *gin.Context) { ctx.Err = service.DeleteRoleBindings(args.Names, projectName, userID, ctx.Logger) } +func RecreateRoleBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") + return + } + + args := make([]*service.RoleBinding, 0) + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = err + return + } + err := service.DeleteRoleBindings([]string{"*"}, projectName, c.Query("userID"), ctx.Logger) + if err != nil { + ctx.Err = err + return + } + ctx.Err = service.CreateRoleBindings(projectName, args, ctx.Logger) +} + func DeleteSystemRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index b167dcdc..c28c5473 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -70,6 +70,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings.GET("", ListRoleBindings) roleBindings.DELETE("/:name", DeleteRoleBinding) roleBindings.POST("/bulk-delete", DeleteRoleBindings) + roleBindings.POST("/recreate", RecreateRoleBindings) } policyBindings := router.Group("policybindings") -- Gitee From e2529e7180de6be94da55802deb0e5a1e6949453 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Feb 2022 21:26:23 +0800 Subject: [PATCH 1642/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index a051eaca..156a3c0b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -45,6 +45,7 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -487,6 +488,11 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePaths = createFromRepo.Paths base = path.Join(config.S3StoragePath(), createFromRepo.Repo) + codehostInfo, err := service.GetCodeHost(createFromRepo.CodehostID, log) + if err != nil { + log.Errorf("Failed to get source form repo data, err: %s", err.Error()) + return nil, err + } helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(filePaths)) var wg wait.Group var mux sync.RWMutex @@ -498,6 +504,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr chartVersion string valuesYAML []byte finalErr error + repoLink string ) defer func() { mux.Lock() @@ -552,9 +559,9 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr return } - //if codehostInfo != nil { - // repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) - //} + if codehostInfo != nil { + repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, createFromRepo.Owner, createFromRepo.Repo, "tree", createFromRepo.Branch, filePath) + } svc, err := createOrUpdateHelmService( afero.NewIOFS(chartTree), @@ -571,8 +578,8 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr Owner: createFromRepo.Owner, Repo: createFromRepo.Repo, Branch: createFromRepo.Branch, - //RepoLink: repoLink, - Source: string(args.Source), + RepoLink: repoLink, + Source: string(args.Source), }, log, ) -- Gitee From e2d216901c2f496b1358c5f0e8e30aa7ccf7a760 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 22 Feb 2022 21:45:18 +0800 Subject: [PATCH 1643/2647] update project return deployType Signed-off-by: panxunying --- .../core/common/repository/mongodb/template/product.go | 2 ++ pkg/microservice/aslan/core/project/service/project.go | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 60d9b78d..010fdbce 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -40,6 +40,8 @@ type ProjectInfo struct { OnboardStatus int `bson:"onboarding_status"` Public bool `bson:"public"` DeployType string `bson:"deploy_type"` + CreateEnvType string `bson:"create_env_type"` + BasicFacility string `bson:"basic_facility"` } type ProductColl struct { diff --git a/pkg/microservice/aslan/core/project/service/project.go b/pkg/microservice/aslan/core/project/service/project.go index 196d63db..8a4c72bf 100644 --- a/pkg/microservice/aslan/core/project/service/project.go +++ b/pkg/microservice/aslan/core/project/service/project.go @@ -94,6 +94,14 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge for name := range desiredSet { info := nameMap[name] + var deployType string + if info.CreateEnvType == "external" { + deployType = "external" + } else if info.BasicFacility == "cloud_host" { + deployType = "cloud_host" + } else { + deployType = info.DeployType + } res = append(res, &ProjectDetailedRepresentation{ ProjectBriefRepresentation: &ProjectBriefRepresentation{ ProjectMinimalRepresentation: &ProjectMinimalRepresentation{Name: name}, @@ -105,7 +113,7 @@ func listDetailedProjectInfos(opts *ProjectListOptions, logger *zap.SugaredLogge UpdatedBy: info.UpdatedBy, Onboard: info.OnboardStatus != 0, Public: info.Public, - DeployType: info.DeployType, + DeployType: deployType, }) } -- Gitee From 24ad2e68f821693f32e5f31207e05a5c0ed33a1c Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 23 Feb 2022 11:16:16 +0800 Subject: [PATCH 1644/2647] update project return deployType Signed-off-by: panxunying --- .../aslan/core/common/repository/mongodb/template/product.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 010fdbce..19d8f858 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -110,6 +110,8 @@ func (c *ProductColl) ListProjectBriefs(inNames []string) ([]*ProjectInfo, error "onboarding_status": "$onboarding_status", "public": "$public", "deploy_type": "$product_feature.deploy_type", + "create_env_type": "$product_feature.create_env_type", + "basic_facility": "$product_feature.basic_facility", }) } -- Gitee From 8429da89dcada6c496ad06ba0d4a00513d7fcd30 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 23 Feb 2022 13:45:54 +0800 Subject: [PATCH 1645/2647] add apis to support import serivces from helm chart Signed-off-by: allenshen --- .../core/common/repository/models/service.go | 6 + .../aslan/core/common/service/service.go | 2 +- .../aslan/core/delivery/service/version.go | 107 ++---------- .../aslan/core/service/handler/helm.go | 16 +- .../aslan/core/service/service/helm.go | 129 ++++++++++++++ .../aslan/core/service/service/types.go | 9 + .../aslan/core/system/handler/helm.go | 7 + .../aslan/core/system/handler/router.go | 1 + .../aslan/core/system/service/helm.go | 42 +++++ pkg/setting/consts.go | 2 + pkg/tool/helmclient/chartclient.go | 165 ++++++++++++++++++ 11 files changed, 390 insertions(+), 96 deletions(-) create mode 100644 pkg/tool/helmclient/chartclient.go diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 68bbdb5f..792f334d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -85,6 +85,12 @@ type CreateFromChartTemplate struct { Variables []*Variable `bson:"variables" json:"variables"` } +type CreateFromChartRepo struct { + ChartRepoName string `json:"chart_repo_name" bson:"chart_repo_name"` + ChartName string `json:"chart_name" bson:"chart_name"` + ChartVersion string `json:"chart_version" bson:"chart_version"` +} + type GUIConfig struct { Deployment interface{} `bson:"deployment,omitempty" json:"deployment,omitempty"` Ingress interface{} `bson:"ingress,omitempty" json:"ingress,omitempty"` diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 5ff73965..cbc4b4aa 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -502,7 +502,7 @@ func DeleteServiceWebhookByName(serviceName, productName string, logger *zap.Sug func needProcessWebhook(source string) bool { if source == setting.ServiceSourceTemplate || source == setting.SourceFromZadig || source == setting.SourceFromGerrit || - source == "" || source == setting.SourceFromExternal || source == setting.SourceFromChartTemplate { + source == "" || source == setting.SourceFromExternal || source == setting.SourceFromChartTemplate || source == setting.SourceFromChartRepo { return false } return true diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 79809176..39866269 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -31,7 +31,6 @@ import ( "time" "github.com/blang/semver/v4" - cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" @@ -523,21 +522,16 @@ func getProductEnvInfo(productName, envName string) (*commonmodels.Product, erro return productInfo, nil } -func getChartRepoData(repoName string) (*commonmodels.HelmRepo, error) { - return commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: repoName}) -} - -func createChartRepoClient(repo *commonmodels.HelmRepo) (*cm.Client, error) { - client, err := cm.NewClient( - cm.URL(repo.URL), - cm.Username(repo.Username), - cm.Password(repo.Password), - // need support more auth types - ) +func createChartRepoClient(repoName string) (*helmtool.ChartRepoClient, error) { + chartRepo, err := getChartRepoData(repoName) if err != nil { - return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", repo.RepoName) + return nil, err } - return client, nil + return helmtool.NewHelmChartRepoClient(chartRepo.URL, chartRepo.Username, chartRepo.Password) +} + +func getChartRepoData(repoName string) (*commonmodels.HelmRepo, error) { + return commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: repoName}) } // ensure chart files exist @@ -740,20 +734,16 @@ func handleSingleChart(chartData *DeliveryChartData, product *commonmodels.Produ return nil, err } - client, err := createChartRepoClient(chartRepo) + client, err := createChartRepoClient(chartRepo.RepoName) if err != nil { return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) } log.Infof("pushing chart %s to %s...", filepath.Base(chartPackagePath), chartRepo.URL) - resp, err := client.UploadChartPackage(chartPackagePath, false) + err = client.PushChart(chartPackagePath, false) if err != nil { return nil, errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) } - err = handlePushResponse(resp) - if err != nil { - return nil, errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) - } return imageDetail, nil } @@ -1426,50 +1416,12 @@ func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *com return chartTGZFilePath, nil } - chartRepo, err := getChartRepoData(chartInfo.ChartRepoName) - if err != nil { - return "", fmt.Errorf("failed to query chart-repo info, repoName %s", chartInfo.ChartRepoName) - } - - client, err := createChartRepoClient(chartRepo) + client, err := createChartRepoClient(chartInfo.ChartRepoName) if err != nil { return "", err } - if err = os.MkdirAll(chartTGZFileParent, 0644); err != nil { - return "", errors.Wrapf(err, "failed to craete tgz parent dir") - } - - out, err := os.Create(chartTGZFilePath) - if err != nil { - _ = os.RemoveAll(chartTGZFilePath) - return "", errors.Wrapf(err, "failed to create chart tgz file") - } - - response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) - if err != nil { - return "", errors.Wrapf(err, "failed to download file") - } - - if response.StatusCode != 200 { - return "", errors.Wrapf(err, "download file failed %s", chartTGZName) - } - defer func() { _ = response.Body.Close() }() - - b, err := ioutil.ReadAll(response.Body) - if err != nil { - return "", errors.Wrapf(err, "failed to read response data") - } - - defer func(out *os.File) { - _ = out.Close() - }(out) - - err = ioutil.WriteFile(chartTGZFilePath, b, 0644) - if err != nil { - return "", err - } - return chartTGZFilePath, nil + return chartTGZFilePath, client.DownloadChart(chartTGZFileParent, chartInfo.ChartName, chartInfo.ChartVersion) } func getChartDistributeInfo(releaseID, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { @@ -1525,39 +1477,11 @@ func preDownloadChart(projectName, versionName, chartName string, log *zap.Sugar } func getIndexInfoFromChartRepo(chartRepoName string) (*helm.Index, error) { - chartRepo, err := getChartRepoData(chartRepoName) - if err != nil { - return nil, fmt.Errorf("failed to query chart-repo info, repoName %s", chartRepoName) - } - - client, err := createChartRepoClient(chartRepo) + client, err := createChartRepoClient(chartRepoName) if err != nil { return nil, errors.Wrapf(err, "failed to create chart repo client") } - - resp, err := client.DownloadFile("index.yaml") - if err != nil { - return nil, errors.Wrapf(err, "failed to download index.yaml") - } - - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != 200 { - return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") - } - - index, err := helm.LoadIndex(b) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse index.yaml") - } - return index, nil + return client.FetchIndexYaml() } func fillChartUrl(charts []*DeliveryVersionPayloadChart, chartRepoName string) error { @@ -1571,7 +1495,6 @@ func fillChartUrl(charts []*DeliveryVersionPayloadChart, chartRepoName string) e } for name, entries := range index.Entries { - chart, ok := chartMap[name] if !ok { continue @@ -1647,7 +1570,6 @@ func GetChartVersion(chartName, chartRepoName string) ([]*ChartVersionResp, erro } func preDownloadAndUncompressChart(projectName, versionName, chartName string, log *zap.SugaredLogger) (string, error) { - deliveryInfo, err := GetDeliveryVersion(&commonrepo.DeliveryVersionArgs{ ProductName: projectName, Version: versionName, @@ -1683,7 +1605,6 @@ func preDownloadAndUncompressChart(projectName, versionName, chartName string, l } func PreviewDeliveryChart(projectName, version, chartName string, log *zap.SugaredLogger) (*DeliveryChartResp, error) { - dstDir, err := preDownloadAndUncompressChart(projectName, version, chartName, log) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index d1462e36..ae188c28 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -76,6 +76,12 @@ func CreateOrUpdateHelmService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be nil") + return + } + args := new(svcservice.HelmServiceCreationArgs) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") @@ -83,13 +89,19 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy = ctx.UserName - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(c.Query("projectName"), args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, ctx.Logger) } func CreateOrUpdateBulkHelmServices(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be nil") + return + } + args := new(svcservice.BulkHelmServiceCreationArgs) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") @@ -97,7 +109,7 @@ func CreateOrUpdateBulkHelmServices(c *gin.Context) { } args.CreatedBy = ctx.UserName - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(c.Query("projectName"), args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(projectName, args, ctx.Logger) } func UpdateHelmService(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 154145f6..3a53bedd 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -46,6 +46,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" yamlutil "github.com/koderover/zadig/pkg/util/yaml" @@ -101,6 +102,7 @@ type helmServiceCreationArgs struct { ValuePaths []string ValuesYaml string Variables []*Variable + ChartRepoName string } type ChartTemplateData struct { @@ -326,11 +328,132 @@ func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) case LoadFromChartTemplate: return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) + case LoadFromChartRepo: + return CreateOrUpdateHelmServiceFromChartRepo(projectName, args, logger) default: return nil, fmt.Errorf("invalid source") } } +func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { + chartRepoArgs, ok := args.CreateFrom.(*CreateFromChartRepo) + if !ok { + return nil, e.ErrCreateTemplate.AddDesc("invalid argument") + } + + chartRepo, err := commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: chartRepoArgs.ChartRepoName}) + if err != nil { + log.Errorf("failed to query chart-repo info, productName: %s, err: %s", projectName, err) + return nil, e.ErrCreateTemplate.AddDesc(fmt.Sprintf("failed to query chart-repo info, productName: %s, repoName: %s", projectName, chartRepoArgs.ChartRepoName)) + } + + chartClient, err := helmclient.NewHelmChartRepoClient(chartRepo.URL, chartRepo.Username, chartRepo.Password) + if err != nil { + return nil, e.ErrCreateTemplate.AddErr(errors.Wrapf(err, "failed to init chart client for repo: %s", chartRepo.RepoName)) + } + + index, err := chartClient.FetchIndexYaml() + if err != nil { + return nil, e.ErrCreateTemplate.AddErr(err) + } + + // validate chart with specific version exists in repo + foundChart := false + for name, entries := range index.Entries { + if name != chartRepoArgs.ChartName { + continue + } + for _, entry := range entries { + if entry.Version == chartRepoArgs.ChartVersion { + foundChart = true + break + } + } + break + } + if !foundChart { + return nil, e.ErrCreateTemplate.AddDesc(fmt.Sprintf("failed to find chart: %s-%s from chart repo", chartRepoArgs.ChartName, chartRepoArgs.ChartVersion)) + } + + localPath := config.LocalServicePath(projectName, chartRepoArgs.ChartName) + err = chartClient.DownloadAndExpand(chartRepoArgs.ChartName, chartRepoArgs.ChartVersion, localPath) + if err != nil { + return nil, e.ErrCreateTemplate.AddErr(err) + } + + serviceName := chartRepoArgs.ChartName + rev, err := getNextServiceRevision(projectName, serviceName) + if err != nil { + log.Errorf("Failed to get next revision for service %s, err: %s", serviceName, err) + return nil, e.ErrCreateTemplate.AddErr(err) + } + + var finalErr error + // clear files from both s3 and local when error occurred in next stages + defer func() { + if finalErr != nil { + clearChartFiles(projectName, serviceName, rev, log) + } + }() + + // read values.yaml + fsTree := os.DirFS(config.LocalServicePath(projectName, chartRepoArgs.ChartName)) + valuesYAML, err := readValuesYAML(fsTree, chartRepoArgs.ChartName, log) + if err != nil { + finalErr = e.ErrCreateTemplate.AddErr(err) + return nil, finalErr + } + + // upload to s3 storage + s3Base := config.ObjectStorageServicePath(projectName, serviceName) + err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{serviceName, fmt.Sprintf("%s-%d", serviceName, rev)}, s3Base, log) + if err != nil { + finalErr = e.ErrCreateTemplate.AddErr(err) + return nil, finalErr + } + + // copy service revision data from latest + err = copyChartRevision(projectName, serviceName, rev) + if err != nil { + log.Errorf("Failed to copy file %s, err: %s", serviceName, err) + finalErr = errors.Wrapf(err, "Failed to copy chart info, service %s", serviceName) + return nil, finalErr + } + + svc, err := createOrUpdateHelmService( + fsTree, + &helmServiceCreationArgs{ + ChartName: chartRepoArgs.ChartName, + ChartVersion: chartRepoArgs.ChartVersion, + ChartRepoName: chartRepoArgs.ChartRepoName, + ServiceRevision: rev, + MergedValues: string(valuesYAML), + ServiceName: serviceName, + ProductName: projectName, + CreateBy: args.CreatedBy, + Source: setting.SourceFromChartRepo, + }, + log, + ) + if err != nil { + log.Errorf("Failed to create service %s in project %s, error: %s", serviceName, projectName, err) + finalErr = e.ErrCreateTemplate.AddErr(err) + return nil, finalErr + } + + compareHelmVariable([]*templatemodels.RenderChart{ + { + ServiceName: args.Name, + ChartVersion: svc.HelmChart.Version, + ValuesYaml: svc.HelmChart.ValuesYaml, + }, + }, projectName, args.CreatedBy, log) + + return &BulkHelmServiceCreationResponse{ + SuccessServices: []string{serviceName}, + }, nil +} + func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { @@ -843,6 +966,12 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { ServiceName: args.ServiceName, Variables: variables, } + case setting.SourceFromChartRepo: + return models.CreateFromChartRepo{ + ChartRepoName: args.ChartRepoName, + ChartName: args.ChartName, + ChartVersion: args.ChartVersion, + } } return nil } diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index 430c4783..37aa034c 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -30,6 +30,7 @@ const ( LoadFromRepo LoadSource = "repo" LoadFromPublicRepo LoadSource = "publicRepo" LoadFromChartTemplate LoadSource = "chartTemplate" + LoadFromChartRepo LoadSource = "chartRepo" ) type HelmLoadSource struct { @@ -84,6 +85,12 @@ type CreateFromChartTemplate struct { Variables []*Variable `json:"variables"` } +type CreateFromChartRepo struct { + ChartRepoName string `json:"chartRepoName"` + ChartName string `json:"chartName"` + ChartVersion string `json:"chartVersion"` +} + func PublicRepoToPrivateRepoArgs(args *CreateFromPublicRepo) (*CreateFromRepo, error) { if args.RepoLink == "" { return nil, fmt.Errorf("empty link") @@ -113,6 +120,8 @@ func (a *HelmServiceCreationArgs) UnmarshalJSON(data []byte) error { a.CreateFrom = &CreateFromPublicRepo{} case LoadFromChartTemplate: a.CreateFrom = &CreateFromChartTemplate{} + case LoadFromChartRepo: + a.CreateFrom = &CreateFromChartRepo{} } type tmp HelmServiceCreationArgs diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 7f92809e..6aa6ac52 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -72,3 +72,10 @@ func DeleteHelmRepo(c *gin.Context) { ctx.Err = service.DeleteHelmRepo(c.Param("id"), ctx.Logger) } + +func ListCharts(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListCharts(c.Param("name"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 2a5bc110..7e6fb08b 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -152,6 +152,7 @@ func (*Router) Inject(router *gin.RouterGroup) { integration.POST("", CreateHelmRepo) integration.PUT("/:id", UpdateHelmRepo) integration.DELETE("/:id", DeleteHelmRepo) + integration.GET("/:name/index", ListCharts) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/system/service/helm.go b/pkg/microservice/aslan/core/system/service/helm.go index 585e2f5f..7ef62753 100644 --- a/pkg/microservice/aslan/core/system/service/helm.go +++ b/pkg/microservice/aslan/core/system/service/helm.go @@ -21,8 +21,18 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/helmclient" ) +type IndexFileResp struct { + Entries map[string][]*ChartVersion `json:"entries"` +} + +type ChartVersion struct { + ChartName string `json:"chartName"` + Version string `json:"version"` +} + func ListHelmRepos(log *zap.SugaredLogger) ([]*commonmodels.HelmRepo, error) { helmRepos, err := commonrepo.NewHelmRepoColl().List() if err != nil { @@ -56,3 +66,35 @@ func DeleteHelmRepo(id string, log *zap.SugaredLogger) error { } return nil } + +func ListCharts(name string, log *zap.SugaredLogger) (*IndexFileResp, error) { + chartRepo, err := commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: name}) + if err != nil { + return nil, err + } + + client, err := helmclient.NewHelmChartRepoClient(chartRepo.URL, chartRepo.Username, chartRepo.Password) + if err != nil { + return nil, err + } + + indexInfo, err := client.FetchIndexYaml() + if err != nil { + return nil, err + } + + indexResp := &IndexFileResp{ + Entries: make(map[string][]*ChartVersion), + } + + for name, entries := range indexInfo.Entries { + for _, chart := range entries { + indexResp.Entries[name] = append(indexResp.Entries[name], &ChartVersion{ + ChartName: chart.Name, + Version: chart.Version, + }) + } + } + + return indexResp, nil +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 2c15370c..25735426 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -240,6 +240,8 @@ const ( SourceFromChartTemplate = "chartTemplate" // SourceFromPublicRepo 配置来源为publicRepo SourceFromPublicRepo = "publicRepo" + // SourceFromChartRepo + SourceFromChartRepo = "chartRepo" // SourceFromGUI 配置来源为gui SourceFromGUI = "gui" diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go new file mode 100644 index 00000000..db63c451 --- /dev/null +++ b/pkg/tool/helmclient/chartclient.go @@ -0,0 +1,165 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helmclient + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" + "github.com/chartmuseum/helm-push/pkg/helm" + "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/chartutil" + + "github.com/koderover/zadig/pkg/tool/log" +) + +type ChartRepoClient struct { + *cm.Client +} + +func NewHelmChartRepoClient(url, userName, password string) (*ChartRepoClient, error) { + client, err := cm.NewClient( + cm.URL(url), + cm.Username(userName), + cm.Password(password), + // TODO need support more auth types + ) + if err != nil { + return nil, err + } + return &ChartRepoClient{client}, nil +} + +// FetchIndexYaml fetch index.yaml from remote chart repo +func (client *ChartRepoClient) FetchIndexYaml() (*helm.Index, error) { + resp, err := client.DownloadFile("index.yaml") + if err != nil { + return nil, errors.Wrapf(err, "failed to download index.yaml") + } + + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") + } + + index, err := helm.LoadIndex(b) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse index.yaml") + } + return index, nil +} + +func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath string) error { + chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) + chartTGZFilePath := filepath.Join(basePath, chartTGZName) + out, err := os.Create(chartTGZFilePath) + if err != nil { + _ = os.RemoveAll(chartTGZFilePath) + return errors.Wrapf(err, "failed to create chart tgz file") + } + + response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) + if err != nil { + return errors.Wrapf(err, "failed to download file") + } + + if response.StatusCode != 200 { + return errors.Wrapf(err, "download file failed") + } + defer func() { _ = response.Body.Close() }() + + b, err := ioutil.ReadAll(response.Body) + if err != nil { + return errors.Wrapf(err, "failed to read response data") + } + + defer func(out *os.File) { + _ = out.Close() + }(out) + + return ioutil.WriteFile(chartTGZFilePath, b, 0644) +} + +// DownloadAndExpand downloads chart from repo and expand chart files from tarball +func (client *ChartRepoClient) DownloadAndExpand(chartName, chartVersion, localPath string) error { + chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) + response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) + if err != nil { + return errors.Wrapf(err, "failed to download file") + } + + if response.StatusCode != 200 { + return errors.Wrapf(err, "download file failed") + } + defer func() { _ = response.Body.Close() }() + + return chartutil.Expand(localPath, response.Body) +} + +// PushChart push chart to remote repo +func (client *ChartRepoClient) PushChart(chartPackagePath string, force bool) error { + log.Infof("pushing chart %s", filepath.Base(chartPackagePath)) + resp, err := client.UploadChartPackage(chartPackagePath, false) + if err != nil { + return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + } + err = handlePushResponse(resp) + if err != nil { + return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) + } + return nil +} + +func getChartmuseumError(b []byte, code int) error { + var er struct { + Error string `json:"error"` + } + err := json.Unmarshal(b, &er) + if err != nil || er.Error == "" { + return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) + } + return errors.Errorf("%d: %s", code, er.Error) +} + +func handlePushResponse(resp *http.Response) error { + if resp.StatusCode != 201 && resp.StatusCode != 202 { + b, err := ioutil.ReadAll(resp.Body) + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + if err != nil { + return err + } + return getChartmuseumError(b, resp.StatusCode) + } + log.Infof("push chart to chart repo done") + return nil +} -- Gitee From d0ad351e070c46be54d32e3387098baaad5c3865 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 23 Feb 2022 13:48:53 +0800 Subject: [PATCH 1646/2647] update policy yaml Signed-off-by: panxunying --- .../aslan/core/delivery/handler/policy.yaml | 8 ++--- .../core/environment/handler/policy.yaml | 10 +++--- .../core/project/handler/build_policy.yaml | 36 +++++++++++++++++++ .../project/handler/policy_definitions.go | 19 +++++++--- .../{policy.yaml => service_policy.yaml} | 10 +++--- .../aslan/core/workflow/handler/policy.yaml | 10 +++--- .../core/workflow/testing/handler/policy.yaml | 12 +++---- 7 files changed, 75 insertions(+), 30 deletions(-) create mode 100644 pkg/microservice/aslan/core/project/handler/build_policy.yaml rename pkg/microservice/aslan/core/project/handler/{policy.yaml => service_policy.yaml} (95%) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml index e003c9ae..e79464f4 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -1,9 +1,9 @@ resource: Delivery -alias: "交付中心" +alias: "版本管理" description: "" rules: - action: get_delivery - alias: "查看版本" + alias: "查看" description: "" rules: - method: GET @@ -15,13 +15,13 @@ rules: - method: GET endpoint: "/api/aslan/delivery/releases/helm/charts" - action: delete_delivery - alias: "删除版本" + alias: "删除" description: "" rules: - method: DELETE endpoint: "/api/aslan/delivery/releases/?*" - action: create_delivery - alias: "创建版本" + alias: "创建" description: "" rules: - method: POST diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 6ec461db..69cc4134 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -1,9 +1,9 @@ resource: Environment -alias: "集成环境" +alias: "环境" description: "" rules: - action: get_environment - alias: "查看集成环境" + alias: "查看" description: "" rules: - method: GET @@ -72,7 +72,7 @@ rules: - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - action: create_environment - alias: "新建集成环境" + alias: "创建" description: "" rules: - method: POST @@ -88,7 +88,7 @@ rules: endpoint: "api/aslan/cluster/clusters" resourceType: "Cluster" - action: config_environment - alias: "配置集成环境" + alias: "配置" description: "" rules: - method: PUT @@ -183,7 +183,7 @@ rules: - method: POST endpoint: "/api/aslan/workflow/servicetask" - action: delete_environment - alias: "删除集成环境" + alias: "删除" description: "" rules: - method: DELETE diff --git a/pkg/microservice/aslan/core/project/handler/build_policy.yaml b/pkg/microservice/aslan/core/project/handler/build_policy.yaml new file mode 100644 index 00000000..dac83b14 --- /dev/null +++ b/pkg/microservice/aslan/core/project/handler/build_policy.yaml @@ -0,0 +1,36 @@ +resource: Service +alias: "构建" +description: "" +rules: + - action: get_build + alias: "查看" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/build/build" + - method: GET + endpoint: "/api/aslan/build/build/?*" + - method: GET + endpoint: "/api/aslan/build/targets" + - action: create_build + alias: "新建" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/build/build" + - action: edit_build + alias: "编辑" + description: "" + rules: + - method: PUT + endpoint: "/api/aslan/build/build" + - method: POST + endpoint: "/api/aslan/build/targets" + - method: POST + endpoint: "/api/aslan/build/build/targets" + - action: delete_build + alias: "删除" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/build/build" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/project/handler/policy_definitions.go b/pkg/microservice/aslan/core/project/handler/policy_definitions.go index dfe7b02b..63a37d48 100644 --- a/pkg/microservice/aslan/core/project/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/project/handler/policy_definitions.go @@ -25,16 +25,25 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) -//go:embed policy.yaml -var policyDefinitions []byte +//go:embed service_policy.yaml +var servicePolicyDefinitions []byte + +//go:embed build_policy.yaml +var buildPolicyDefinitions []byte func (*Router) Policies() []*policy.PolicyMeta { - res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyDefinitions, res) + serviceRes := &policy.PolicyMeta{} + err := yaml.Unmarshal(servicePolicyDefinitions, serviceRes) + if err != nil { + // should not have happened here + log.DPanic(err) + } + buildRes := &policy.PolicyMeta{} + err = yaml.Unmarshal(buildPolicyDefinitions, buildRes) if err != nil { // should not have happened here log.DPanic(err) } - return []*policy.PolicyMeta{res} + return []*policy.PolicyMeta{serviceRes, buildRes} } diff --git a/pkg/microservice/aslan/core/project/handler/policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml similarity index 95% rename from pkg/microservice/aslan/core/project/handler/policy.yaml rename to pkg/microservice/aslan/core/project/handler/service_policy.yaml index 377b5cfc..6ed43fe6 100644 --- a/pkg/microservice/aslan/core/project/handler/policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -1,9 +1,9 @@ resource: Service -alias: "项目配置" +alias: "服务" description: "" rules: - action: get_service - alias: "查看服务" + alias: "查看" description: "" rules: - method: GET @@ -25,7 +25,7 @@ rules: - method: GET endpoint: "/api/aslan/service/services/?*/pm" - action: edit_service - alias: "编辑服务" + alias: "编辑" description: "" rules: - method: POST @@ -41,7 +41,7 @@ rules: - method: PUT endpoint: "/api/aslan/project/products/?*/searching-rules" - action: create_service - alias: "新建服务" + alias: "新建" description: "" rules: - method: POST @@ -55,7 +55,7 @@ rules: - method: POST endpoint: "/api/aslan/service/helm/services/bulk" - action: delete_service - alias: "删除服务" + alias: "删除" description: "" rules: - method: DELETE diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index d7f6525f..2c4cccf7 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -3,7 +3,7 @@ alias: "工作流" description: "" rules: - action: get_workflow - alias: "查看工作流" + alias: "查看" description: "" rules: - method: GET @@ -45,7 +45,7 @@ rules: - method: GET endpoint: "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*" - action: edit_workflow - alias: "编辑工作流" + alias: "编辑" description: "" rules: - method: PUT @@ -63,7 +63,7 @@ rules: - method: PUT endpoint: "/api/aslan/workflow/v3/?*" - action: create_workflow - alias: "新建工作流" + alias: "新建" description: "" rules: - method: POST @@ -83,7 +83,7 @@ rules: - method: POST endpoint: "/api/aslan/workflow/v3" - action: delete_workflow - alias: "删除工作流" + alias: "删除" description: "" rules: - method: DELETE @@ -91,7 +91,7 @@ rules: - method: DELETE endpoint: "/api/aslan/workflow/v3/?*" - action: run_workflow - alias: "执行工作流" + alias: "执行" description: "" rules: - method: POST diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml index a6085908..a876ef3a 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -1,9 +1,9 @@ resource: Test -alias: "质量管理" +alias: "测试" description: "" rules: - action: get_test - alias: "查看测试" + alias: "查看" description: "" rules: - method: GET @@ -19,25 +19,25 @@ rules: - method: GET endpoint: "/api/aslan/testing/workspace/workflow/?*/taskId/?*" - action: edit_test - alias: "编辑测试" + alias: "编辑" description: "" rules: - method: PUT endpoint: "/api/aslan/testing/test" - action: delete_test - alias: "删除测试" + alias: "删除" description: "" rules: - method: DELETE endpoint: "/api/aslan/testing/test/?*" - action: create_test - alias: "新建测试" + alias: "新建" description: "" rules: - method: POST endpoint: "/api/aslan/testing/test" - action: run_test - alias: "执行测试" + alias: "执行" description: "" rules: - method: POST -- Gitee From c23d6d65ddb5095729decb51beb1806a129fb41a Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 23 Feb 2022 14:19:11 +0800 Subject: [PATCH 1647/2647] update policy yaml Signed-off-by: panxunying --- .../aslan/core/environment/handler/policy_definitions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go index 5d1fb402..3e0f001f 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go @@ -27,7 +27,7 @@ import ( const ( productionPolicyResource = "ProductionEnvironment" - productionPolicyAlias = "集成环境(类生产/预发布)" + productionPolicyAlias = "集成环境(生产/预发布)" productionKey = "production" productionValueTrue = "true" ) -- Gitee From e88487fc5f27ecec05888e204c5e66ef92156023 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 23 Feb 2022 16:15:22 +0800 Subject: [PATCH 1648/2647] fix read only Signed-off-by: panxunying --- .../policy/core/service/bundle/rego/authz.rego | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 5e952fc2..43fc1f50 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -355,6 +355,7 @@ all_roles[role_ref] { # only roles under the given project are allowed allowed_roles[role_ref] { some i + some j data.bindings.role_bindings[i].uid == claims.uid data.bindings.role_bindings[i].bindings[j].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] @@ -363,13 +364,15 @@ allowed_roles[role_ref] { # if the proejct is visible by all users (the user name is "*"), the bound roles are also allowed allowed_roles[role_ref] { some i + some j data.bindings.role_bindings[i].uid == "*" - data.bindings.role_bindings[i].bindings[_].namespace == project_name + data.bindings.role_bindings[i].bindings[j].namespace == project_name role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } allowed_policies[policy_ref] { some i + some j data.bindings.policy_bindings[i].uid == claims.uid data.bindings.policy_bindings[i].bindings[j].namespace == project_name policy_ref := data.bindings.policy_bindings[i].bindings[j].policy_refs[_] @@ -377,8 +380,9 @@ allowed_policies[policy_ref] { allowed_policies[policy_ref] { some i + some j data.bindings.policy_bindings[i].uid == "*" - data.bindings.policy_bindings[i].bindings[_].namespace == project_name + data.bindings.policy_bindings[i].bindings[j].namespace == project_name policy_ref := data.bindings.policy_bindings[i].bindings[j].policy_refs[_] } -- Gitee From fcbb81210e54dd46bff2359ca4b04a821c6f5179 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 23 Feb 2022 16:24:37 +0800 Subject: [PATCH 1649/2647] add comments Signed-off-by: panxunying --- .../aslan/core/collaboration/service/collaboration_instance.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 0dc51249..f790b067 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1131,6 +1131,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id ifSync = true } return &GetCollaborationNewResp{ + //Code 10000 means it is a filtered success result Code: 10000, Workflow: newWorkflow, Product: newProduct, -- Gitee From c599b4dccc7c4552b959c6d5fe134b33923a56a4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 23 Feb 2022 17:25:25 +0800 Subject: [PATCH 1650/2647] vars bug Signed-off-by: mouuii --- .../aslan/core/environment/service/environment.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 20bd640f..08514446 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -957,13 +957,7 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc if len(arg.Items) == 0 { return nil } - pro, err := GetInitProduct(projectName, log) - if err != nil { - return err - } - for i, _ := range arg.Items { - arg.Items[i].Vars = append(arg.Items[i].Vars, pro.Vars...) - } + var envs []string for _, item := range arg.Items { envs = append(envs, item.OldName) -- Gitee From 5d9e3c2872f81b7f4b87f1cef81633d08b8aa4cf Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 23 Feb 2022 17:29:00 +0800 Subject: [PATCH 1651/2647] helm env Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 08514446..6abed270 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -938,7 +938,7 @@ func BulkCopyHelmProduct(projectName, user, requestID string, arg CopyHelmProduc args = append(args, &CreateHelmProductArg{ ProductName: projectName, EnvName: item.NewName, - Namespace: "helm-env-" + projectName + "-" + item.NewName, + Namespace: projectName + "-" + "env" + "-" + item.NewName, ClusterID: product.ClusterID, DefaultValues: item.DefaultValues, RegistryID: product.RegistryID, -- Gitee From 13dd21269e0cb1195b44a771d7d3ff2c53cf0d8b Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 23 Feb 2022 18:02:37 +0800 Subject: [PATCH 1652/2647] enable proxy for gitlab Signed-off-by: Min Min --- pkg/microservice/aslan/core/code/service/branch.go | 2 +- .../aslan/core/code/service/merge_request.go | 2 +- .../aslan/core/code/service/namespace.go | 2 +- pkg/microservice/aslan/core/code/service/project.go | 2 +- pkg/microservice/aslan/core/code/service/tag.go | 2 +- .../aslan/core/common/service/gitlab/client.go | 3 ++- .../aslan/core/common/service/scmnotify/client.go | 2 +- .../workflow/service/webhook/gitlab_workflow_task.go | 6 +++--- .../aslan/core/workflow/service/webhook/utils.go | 2 +- .../core/workflow/service/workflow/nsq_handlers.go | 2 +- pkg/tool/git/gitlab/client.go | 12 ++++++++++-- 11 files changed, 23 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 122aa475..c608a2fa 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -37,7 +37,7 @@ func CodeHostListBranches(codeHostID int, projectName, namespace, key string, pa } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Errorf("get gitlab client failed, err:%v", err) return nil, e.ErrCodehostListBranches.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index bbf8c40e..2615d18b 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -37,7 +37,7 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Error(err) return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 7e052f80..616ccf7f 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -45,7 +45,7 @@ func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogg } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Error(err) return nil, e.ErrCodehostListNamespaces.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 414c5dec..8a0b0781 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -37,7 +37,7 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 4b9af5a0..9c5726b4 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -38,7 +38,7 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Error(err) return nil, e.ErrCodehostListTags.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/service/gitlab/client.go b/pkg/microservice/aslan/core/common/service/gitlab/client.go index 4d5051e0..166f4357 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/client.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/client.go @@ -17,6 +17,7 @@ limitations under the License. package gitlab import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/tool/git/gitlab" ) @@ -25,7 +26,7 @@ type Client struct { } func NewClient(address, accessToken string) (*Client, error) { - c, err := gitlab.NewClient(address, accessToken) + c, err := gitlab.NewClient(address, accessToken, config.ProxyHTTPSAddr()) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index 39964c6b..d20dec2a 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -61,7 +61,7 @@ func (c *Client) Comment(notify *models.Notification) error { } if strings.ToLower(codeHostDetail.Type) == "gitlab" { var note *gitlab.Note - cli, err := gitlabtool.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken) + cli, err := gitlabtool.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr()) if err != nil { c.logger.Errorf("create gitlab client failed err: %v", err) return fmt.Errorf("create gitlab client failed err: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 29b7a016..22f0d479 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -221,7 +221,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( return false, err } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken) + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr()) if err != nil { gpem.log.Errorf("NewClient error: %s", err) return false, err @@ -361,7 +361,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("GetCodeHost codehostId:%d err:%s", item.MainRepo.CodehostID, err) } - cli, err := gitlabtool.NewClient(ch.Address, ch.AccessToken) + cli, err := gitlabtool.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { return fmt.Errorf("gitlabtool.NewClient codehostId:%d err:%s", item.MainRepo.CodehostID, err) } @@ -676,7 +676,7 @@ func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([ return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken) + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 92e10954..29ba8ca0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -259,7 +259,7 @@ func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") } - client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken) + client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr()) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index c3cc38bc..d892275f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -222,7 +222,7 @@ func getRawFileContent(codehostID int, repo, owner, branch, filePath string) ([] } switch ch.Type { case setting.SourceFromGitlab: - cli, err := gitlab.NewClient(ch.Address, ch.AccessToken) + cli, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) if err != nil { return nil, errors.Wrapf(err, "Failed to get gitlab client") } diff --git a/pkg/tool/git/gitlab/client.go b/pkg/tool/git/gitlab/client.go index 2fe6053b..b58afde7 100644 --- a/pkg/tool/git/gitlab/client.go +++ b/pkg/tool/git/gitlab/client.go @@ -19,6 +19,8 @@ package gitlab import ( "fmt" "io" + "net/http" + "net/url" "github.com/xanzy/go-gitlab" @@ -43,8 +45,14 @@ type Client struct { *gitlab.Client } -func NewClient(address, accessToken string) (*Client, error) { - cli, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(address)) +func NewClient(address, accessToken, proxyAddr string) (*Client, error) { + proxyURL, err := url.Parse(proxyAddr) + if err != nil { + return nil, err + } + transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} + client := &http.Client{Transport: transport} + cli, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(address), gitlab.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("failed to create gitlab client, err: %s", err) } -- Gitee From af90bf9f40542fd252b9be7872b6526d4a495cf6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 23 Feb 2022 18:13:10 +0800 Subject: [PATCH 1653/2647] modify filepath value Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 156a3c0b..ac5593aa 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -45,7 +45,6 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -488,11 +487,11 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePaths = createFromRepo.Paths base = path.Join(config.S3StoragePath(), createFromRepo.Repo) - codehostInfo, err := service.GetCodeHost(createFromRepo.CodehostID, log) - if err != nil { - log.Errorf("Failed to get source form repo data, err: %s", err.Error()) - return nil, err - } + //codehostInfo, err := service.GetCodeHost(createFromRepo.CodehostID, log) + //if err != nil { + // log.Errorf("Failed to get source form repo data, err: %s", err.Error()) + // return nil, err + //} helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(filePaths)) var wg wait.Group var mux sync.RWMutex @@ -504,7 +503,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr chartVersion string valuesYAML []byte finalErr error - repoLink string ) defer func() { mux.Lock() @@ -519,13 +517,13 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr mux.Unlock() }() - filePath := path.Join(base, filePath) - log.Infof("Loading chart under path %s", filePath) - serviceName, chartVersion, finalErr = readChartYAMLFromLocal(filePath, log) + currentFilePath := path.Join(base, filePath) + log.Infof("Loading chart under path %s", currentFilePath) + serviceName, chartVersion, finalErr = readChartYAMLFromLocal(currentFilePath, log) if finalErr != nil { return } - valuesYAML, finalErr = readValuesYAMLFromLocal(filePath, log) + valuesYAML, finalErr = readValuesYAMLFromLocal(currentFilePath, log) if finalErr != nil { return } @@ -546,7 +544,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr }() // copy to latest dir and upload to s3 - if err = commonservice.CopyAndUploadService(projectName, serviceName, filePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree)); err != nil { + if err = commonservice.CopyAndUploadService(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree)); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) finalErr = e.ErrCreateTemplate.AddErr(err) return @@ -559,10 +557,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr return } - if codehostInfo != nil { - repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, createFromRepo.Owner, createFromRepo.Repo, "tree", createFromRepo.Branch, filePath) - } - svc, err := createOrUpdateHelmService( afero.NewIOFS(chartTree), &helmServiceCreationArgs{ @@ -578,7 +572,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr Owner: createFromRepo.Owner, Repo: createFromRepo.Repo, Branch: createFromRepo.Branch, - RepoLink: repoLink, Source: string(args.Source), }, log, -- Gitee From 5036676cafafa8bf813f47fa2a894b190ce49d6c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 23 Feb 2022 18:21:32 +0800 Subject: [PATCH 1654/2647] modify filepath value Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index ac5593aa..e07553ff 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -486,12 +486,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePaths = createFromRepo.Paths base = path.Join(config.S3StoragePath(), createFromRepo.Repo) - - //codehostInfo, err := service.GetCodeHost(createFromRepo.CodehostID, log) - //if err != nil { - // log.Errorf("Failed to get source form repo data, err: %s", err.Error()) - // return nil, err - //} helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(filePaths)) var wg wait.Group var mux sync.RWMutex @@ -1042,7 +1036,8 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg ) switch args.Source { case string(LoadFromGerrit): - chartName, chartVersion, err = readChartYAMLFromLocal(args.FilePath, logger) + base := path.Join(config.S3StoragePath(), args.Repo) + chartName, chartVersion, err = readChartYAMLFromLocal(filepath.Join(base, args.FilePath), logger) default: chartName, chartVersion, err = readChartYAML(fsTree, args.ServiceName, logger) } -- Gitee From c704ba3d0d0db20e549c65301a494c86a0cd81be Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 24 Feb 2022 11:29:16 +0800 Subject: [PATCH 1655/2647] system Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 2 +- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 39 +++++++++++++++++++ .../internal/repository/models/oldpolicy.go | 5 +++ .../service/collaboration_instance.go | 20 +++++----- .../core/label/repository/models/label.go | 14 +++---- .../core/label/repository/mongodb/label.go | 8 ++-- pkg/microservice/policy/core/handler/role.go | 21 +++++----- .../policy/core/handler/router.go | 14 +++---- .../policy/core/repository/models/policy.go | 18 ++++----- .../core/repository/models/policy_binding.go | 4 +- .../policy/core/repository/models/role.go | 11 ++++-- .../policy/core/service/policy.go | 4 +- .../policy/core/service/policy_binding.go | 2 +- pkg/microservice/policy/core/service/role.go | 6 ++- pkg/setting/consts.go | 9 ++--- pkg/shared/client/policy/role.go | 4 +- 16 files changed, 115 insertions(+), 66 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 7ecaf723..528cc0d9 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -217,7 +217,7 @@ func presetRole() error { log.DPanic(err) } g.Go(func() error { - return policy.NewDefault().CreatePublicRole(role.Name, role) + return policy.NewDefault().CreatePresetRole(role.Name, role) }) } if err := g.Wait(); err != nil { diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 1f922dda..c4a63b93 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -49,6 +49,11 @@ func V190ToV1100() error { return err } + if err := addPresetRoleSystemType(); err != nil { + log.Errorf("Failed to addPresetRoleSystemType, err: %s", err) + return err + } + if err := migrateModuleBuild(); err != nil { return fmt.Errorf("failed to migrate data in `zadig.module_build`: %s", err) } @@ -65,6 +70,35 @@ func V190ToV1100() error { return nil } +func addPresetRoleSystemType() error { + ctx := context.Background() + + var res []*models.Role + cursor, err := newRoleColl().Find(ctx, bson.M{}) + if err != nil { + log.Errorf("Failed to Find Policies, err: %s", err) + return err + } + + err = cursor.All(ctx, &res) + if err != nil { + return err + } + for _, v := range res { + if v.Name == "admin" || v.Name == "read-only" || v.Name == "project-admin" || v.Name == "contribute" || v.Name == "read-project-only" { + query := bson.M{"name": v.Name} + change := bson.M{"$set": bson.M{ + "type": "system", + }} + _, err := newRoleColl().UpdateOne(ctx, query, change) + if err != nil { + return err + } + } + } + return nil +} + func changePolicyCollectionName() error { var res []*models.PolicyMeta @@ -136,6 +170,11 @@ func newPolicyColl() *mongo.Collection { return collection } +func newRoleColl() *mongo.Collection { + collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("role") + return collection +} + func newPolicyMetaColl() *mongo.Collection { collection := mongotool.Database(fmt.Sprintf("%s_policy", config.MongoDatabase())).Collection("policy_meta") return collection diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 1a445407..7709a4c5 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -23,6 +23,11 @@ type PolicyMeta struct { Rules []*PolicyMetaRule `bson:"rules" json:"rules"` } +type Role struct { + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` +} + type PolicyMetaRule struct { Action string `bson:"action" json:"action"` Alias string `bson:"alias" json:"alias"` diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 0dc51249..4486ddb7 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -475,7 +475,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, ProjectName: projectName, }) } @@ -484,7 +484,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, ProjectName: projectName, }) } @@ -495,7 +495,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, ProjectName: projectName, }) } @@ -504,7 +504,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, ProjectName: projectName, }) } @@ -513,7 +513,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, ProjectName: projectName, }) } @@ -522,7 +522,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, ProjectName: projectName, }) } @@ -534,7 +534,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, }) } for _, product := range instance.Products { @@ -542,7 +542,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, }) } } @@ -584,14 +584,14 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, }) } for _, product := range item.UpdateSpec.Products { labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name), - Type: setting.LabelTypeSystem, + Type: setting.ResourceGenerateTypeSystem, }) } diff --git a/pkg/microservice/aslan/core/label/repository/models/label.go b/pkg/microservice/aslan/core/label/repository/models/label.go index 49e7f1a1..f2dd41c9 100644 --- a/pkg/microservice/aslan/core/label/repository/models/label.go +++ b/pkg/microservice/aslan/core/label/repository/models/label.go @@ -23,13 +23,13 @@ import ( ) type Label struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type setting.LabelType `bson:"type" json:"type"` - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - CreateBy string `bson:"create_by" json:"create_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` - ProjectName string `bson:"project_name" json:"project_name"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type setting.ResourceGenerateType `bson:"type" json:"type"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` + ProjectName string `bson:"project_name" json:"project_name"` } func (Label) TableName() string { diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 09e390f7..a2c6ad89 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -141,10 +141,10 @@ func (c *LabelColl) Find(id string) (*models.Label, error) { } type Label struct { - Key string `json:"key"` - Value string `json:"value"` - Type setting.LabelType `json:"type"` - ProjectName string `json:"project_name"` + Key string `json:"key"` + Value string `json:"value"` + Type setting.ResourceGenerateType `json:"type"` + ProjectName string `json:"project_name"` } type ListLabelOpt struct { diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 913a0233..f1201b1b 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -20,6 +20,7 @@ import ( "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/policy/core/service" + "github.com/koderover/zadig/pkg/setting" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -88,7 +89,7 @@ func UpdateOrCreateRole(c *gin.Context) { ctx.Err = service.UpdateOrCreateRole(projectName, args, ctx.Logger) } -func UpdatePublicRole(c *gin.Context) { +func UpdatePresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -99,10 +100,10 @@ func UpdatePublicRole(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateRole("", args, ctx.Logger) + ctx.Err = service.UpdateRole(service.PublicScope, args, ctx.Logger) } -func UpdateOrCreatePublicRole(c *gin.Context) { +func UpdateOrCreatePresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -113,7 +114,7 @@ func UpdateOrCreatePublicRole(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateOrCreateRole("", args, ctx.Logger) + ctx.Err = service.UpdateOrCreateRole(service.PublicScope, args, ctx.Logger) } func ListRoles(c *gin.Context) { @@ -142,7 +143,7 @@ func GetRole(c *gin.Context) { ctx.Resp, ctx.Err = service.GetRole(projectName, c.Param("name"), ctx.Logger) } -func CreatePublicRole(c *gin.Context) { +func CreatePresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -151,11 +152,11 @@ func CreatePublicRole(c *gin.Context) { ctx.Err = err return } - + args.Type = setting.ResourceGenerateTypeSystem ctx.Err = service.CreateRole(service.PublicScope, args, ctx.Logger) } -func ListPublicRoles(c *gin.Context) { +func ListPresetRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -163,7 +164,7 @@ func ListPublicRoles(c *gin.Context) { return } -func GetPublicRole(c *gin.Context) { +func GetPresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -203,7 +204,7 @@ func DeleteRoles(c *gin.Context) { ctx.Err = service.DeleteRoles(args.Names, projectName, ctx.Logger) } -func DeletePublicRole(c *gin.Context) { +func DeletePresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") @@ -220,7 +221,7 @@ func CreateSystemRole(c *gin.Context) { ctx.Err = err return } - + args.Type = setting.ResourceGenerateTypeSystem ctx.Err = service.CreateRole(service.SystemScope, args, ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index c28c5473..4c8d253d 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -45,14 +45,14 @@ func (*Router) Inject(router *gin.RouterGroup) { policies.DELETE("/:name", DeletePolicy) } - publicRoles := router.Group("public-roles") + publicRoles := router.Group("preset-roles") { - publicRoles.POST("", CreatePublicRole) - publicRoles.GET("", ListPublicRoles) - publicRoles.GET("/:name", GetPublicRole) - publicRoles.PATCH("/:name", UpdatePublicRole) - publicRoles.PUT("/:name", UpdateOrCreatePublicRole) - publicRoles.DELETE("/:name", DeletePublicRole) + publicRoles.POST("", CreatePresetRole) + publicRoles.GET("", ListPresetRoles) + publicRoles.GET("/:name", GetPresetRole) + publicRoles.PATCH("/:name", UpdatePresetRole) + publicRoles.PUT("/:name", UpdateOrCreatePresetRole) + publicRoles.DELETE("/:name", DeletePresetRole) } systemRoles := router.Group("system-roles") diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index 5e4a418e..6e08d9ba 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -23,15 +23,15 @@ import ( // Policy is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a PolicyBinding. // for a cluster scoped Policy, namespace is empty. type Policy struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Description string `json:"description" json:"description"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - Rules []*Rule `bson:"rules" json:"rules"` - CreateBy string `bson:"create_by" json:"create_by"` - UpdateBy string `bson:"update_by" json:"update_by"` - Type setting.PolicyType `bson:"type" json:"type"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Description string `json:"description" json:"description"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Rules []*Rule `bson:"rules" json:"rules"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` + Type setting.ResourceGenerateType `bson:"type" json:"type"` } func (Policy) TableName() string { diff --git a/pkg/microservice/policy/core/repository/models/policy_binding.go b/pkg/microservice/policy/core/repository/models/policy_binding.go index 50d8dab9..5cdf026e 100644 --- a/pkg/microservice/policy/core/repository/models/policy_binding.go +++ b/pkg/microservice/policy/core/repository/models/policy_binding.go @@ -31,8 +31,8 @@ type PolicyBinding struct { Subjects []*Subject `bson:"subjects" json:"subjects"` // PolicyRef can reference a namespaced or cluster scoped Policy. - PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` - Type setting.PolicyBindingType `bson:"type" json:"type"` + PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` + Type setting.ResourceGenerateType `bson:"type" json:"type"` } // PolicyRef contains information that points to the policy being used diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 7662a500..8f585473 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -16,12 +16,17 @@ limitations under the License. package models +import ( + "github.com/koderover/zadig/pkg/setting" +) + // Role is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding. // for a cluster scoped Role, namespace is empty. type Role struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Rules []*Rule `bson:"rules" json:"rules"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Rules []*Rule `bson:"rules" json:"rules"` + Type setting.ResourceGenerateType `bson:"type" json:"type"` } func (Role) TableName() string { diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 48396216..da10675c 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -44,7 +44,7 @@ func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, - Type: setting.PolicyTypeSystem, + Type: setting.ResourceGenerateTypeSystem, } for _, r := range policy.Rules { @@ -64,7 +64,7 @@ func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, - Type: setting.PolicyTypeSystem, + Type: setting.ResourceGenerateTypeSystem, } for _, r := range policy.Rules { diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index 0122ee52..15b20d6d 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -141,6 +141,6 @@ func createPolicyBindingObject(ns string, rb *PolicyBinding, logger *zap.Sugared Name: policy.Name, Namespace: policy.Namespace, }, - Type: setting.PolicyBindingTypeSystem, + Type: setting.ResourceGenerateTypeSystem, }, nil } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 79507051..2fbcc95c 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -27,14 +27,16 @@ import ( ) type Role struct { - Name string `json:"name"` - Rules []*Rule `json:"rules,omitempty"` + Name string `json:"name"` + Rules []*Rule `json:"rules,omitempty"` + Type setting.ResourceGenerateType `json:"type,omitempty"` } func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, Namespace: ns, + Type: role.Type, } for _, r := range role.Rules { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a5c41cc4..bf15653e 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -613,14 +613,11 @@ const ( InformerNamingConvention = "%s-%s" ) -type PolicyType string -type PolicyBindingType string -type LabelType string +type ResourceGenerateType string const ( - PolicyTypeSystem PolicyType = "system" - PolicyBindingTypeSystem PolicyBindingType = "system" - LabelTypeSystem LabelType = "system" + ResourceGenerateTypeSystem ResourceGenerateType = "system" + ResourceGenerateTypeUser ResourceGenerateType = "user" ) // AttachedClusterNamespace is the namespace Zadig uses in attached cluster. diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index 660efec3..b7f22edb 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -121,8 +121,8 @@ func (c *Client) CreateSystemRole(name string, role *Role) error { return err } -func (c *Client) CreatePublicRole(name string, role *Role) error { - url := fmt.Sprintf("/public-roles/%s", name) +func (c *Client) CreatePresetRole(name string, role *Role) error { + url := fmt.Sprintf("/preset-roles/%s", name) _, err := c.Put(url, httpclient.SetBody(role)) return err } -- Gitee From bee242841bbea222bd5826ecb436a60f76c64863 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 11:30:07 +0800 Subject: [PATCH 1656/2647] modify gerrit webhook logic Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 47 +++++++++++++------ .../core/workflow/service/webhook/gerrit.go | 18 +++---- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e07553ff..c22a06f1 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -104,6 +104,11 @@ type helmServiceCreationArgs struct { ValuePaths []string ValuesYaml string Variables []*Variable + GerritRepoName string + GerritBranchName string + GerritRemoteName string + GerritPath string + GerritCodeHostID int } type ChartTemplateData struct { @@ -554,19 +559,23 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr svc, err := createOrUpdateHelmService( afero.NewIOFS(chartTree), &helmServiceCreationArgs{ - ChartName: serviceName, - ChartVersion: chartVersion, - ServiceRevision: rev, - MergedValues: string(valuesYAML), - ServiceName: serviceName, - FilePath: filePath, - ProductName: projectName, - CreateBy: args.CreatedBy, - CodehostID: createFromRepo.CodehostID, - Owner: createFromRepo.Owner, - Repo: createFromRepo.Repo, - Branch: createFromRepo.Branch, - Source: string(args.Source), + ChartName: serviceName, + ChartVersion: chartVersion, + ServiceRevision: rev, + MergedValues: string(valuesYAML), + ServiceName: serviceName, + FilePath: filePath, + ProductName: projectName, + CreateBy: args.CreatedBy, + CodehostID: createFromRepo.CodehostID, + Owner: createFromRepo.Owner, + Repo: createFromRepo.Repo, + Branch: createFromRepo.Branch, + Source: string(args.Source), + GerritCodeHostID: createFromRepo.CodehostID, + GerritPath: currentFilePath, + GerritRepoName: createFromRepo.Repo, + GerritBranchName: createFromRepo.Repo, }, log, ) @@ -1075,7 +1084,6 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg BranchName: args.Branch, LoadPath: args.FilePath, SrcPath: args.RepoLink, - CreateFrom: geneCreationDetail(args), Source: args.Source, HelmChart: &commonmodels.HelmChart{ Name: chartName, @@ -1084,8 +1092,17 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg }, } - log.Infof("Starting to create service %s with revision %d", args.ServiceName, args.ServiceRevision) + switch args.Source { + case string(LoadFromGerrit): + serviceObj.GerritPath = args.GerritPath + serviceObj.GerritCodeHostID = args.GerritCodeHostID + serviceObj.GerritRepoName = args.GerritRepoName + serviceObj.GerritBranchName = args.GerritBranchName + default: + serviceObj.CreateFrom = geneCreationDetail(args) + } + log.Infof("Starting to create service %s with revision %d", args.ServiceName, args.ServiceRevision) currentSvcTmpl, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ProductName: args.ProductName, ServiceName: args.ServiceName, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index 34e733b1..fa526c32 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -120,7 +120,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro } } - filePath, err := os.Stat(path.Join(newBase, service.GerritPath)) + filePath, err := os.Stat(path.Join(newBase, service.LoadPath)) if err != nil { errs = multierror.Append(errs, err) } @@ -128,14 +128,14 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro var newYamlContent string var oldYamlContent string if filePath.IsDir() { - newFileInfos, err := ioutil.ReadDir(path.Join(newBase, service.GerritPath)) + newFileInfos, err := ioutil.ReadDir(path.Join(newBase, service.LoadPath)) if err != nil { errs = multierror.Append(errs, err) } newFileContents := make([]string, 0) for _, file := range newFileInfos { - if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.GerritPath, file.Name())); err == nil { + if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.LoadPath, file.Name())); err == nil { newFileContents = append(newFileContents, string(contentBytes)) } else { errs = multierror.Append(errs, err) @@ -144,14 +144,14 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro newYamlContent = strings.Join(newFileContents, setting.YamlFileSeperator) - oldFileInfos, err := ioutil.ReadDir(path.Join(oldBase, service.GerritPath)) + oldFileInfos, err := ioutil.ReadDir(path.Join(oldBase, service.LoadPath)) if err != nil { errs = multierror.Append(errs, err) } oldFileContents := make([]string, 0) for _, file := range oldFileInfos { - if contentBytes, err := ioutil.ReadFile(path.Join(oldBase, service.GerritPath, file.Name())); err == nil { + if contentBytes, err := ioutil.ReadFile(path.Join(oldBase, service.LoadPath, file.Name())); err == nil { oldFileContents = append(oldFileContents, string(contentBytes)) } else { errs = multierror.Append(errs, err) @@ -159,13 +159,13 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro } oldYamlContent = strings.Join(oldFileContents, setting.YamlFileSeperator) } else { - if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.GerritPath)); err == nil { + if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.LoadPath)); err == nil { newYamlContent = string(contentBytes) } else { errs = multierror.Append(errs, err) } - if contentBytes, err := ioutil.ReadFile(path.Join(oldBase, service.GerritPath)); err == nil { + if contentBytes, err := ioutil.ReadFile(path.Join(oldBase, service.LoadPath)); err == nil { oldYamlContent = string(contentBytes) } else { errs = multierror.Append(errs, err) @@ -173,7 +173,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro } if strings.Compare(newYamlContent, oldYamlContent) != 0 { - log.Infof("Started to sync service template %s from gerrit %s", service.ServiceName, service.GerritPath) + log.Infof("Started to sync service template %s from gerrit %s", service.ServiceName, service.LoadPath) service.CreateBy = "system" service.Yaml = newYamlContent err := SyncServiceTemplateFromGerrit(service, log) @@ -181,7 +181,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro errs = multierror.Append(errs, err) } } else { - log.Infof("Service template %s from gerrit %s is not affected, no sync", service.ServiceName, service.GerritPath) + log.Infof("Service template %s from gerrit %s is not affected, no sync", service.ServiceName, service.LoadPath) } } -- Gitee From 97982dd7c11bf7dfc15416c2c41097273526f2a0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 11:48:27 +0800 Subject: [PATCH 1657/2647] add gerrit webhook Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index c22a06f1..98a0ab67 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1119,7 +1119,15 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - commonservice.ProcessServiceWebhook(serviceObj, currentSvcTmpl, args.ServiceName, logger) + switch args.Source { + case string(LoadFromGerrit): + if err := createGerritWebhookByService(args.CodehostID, args.ServiceName, args.Repo, args.Branch); err != nil { + log.Errorf("Failed to create gerrit webhook, err: %s", err) + return nil, err + } + default: + commonservice.ProcessServiceWebhook(serviceObj, currentSvcTmpl, args.ServiceName, logger) + } if err = templaterepo.NewProductColl().AddService(args.ProductName, args.ServiceName); err != nil { log.Errorf("Failed to add service %s to project %s, err: %s", args.ProductName, args.ServiceName, err) -- Gitee From a51c5660b27d5b7b4c5565d6047aeccf08064b4f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 14:42:15 +0800 Subject: [PATCH 1658/2647] fill the value of remoteName Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 1 + pkg/microservice/aslan/core/service/service/loader.go | 3 +++ 2 files changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 98a0ab67..5379362d 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -576,6 +576,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr GerritPath: currentFilePath, GerritRepoName: createFromRepo.Repo, GerritBranchName: createFromRepo.Repo, + GerritRemoteName: "origin", }, log, ) diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index af9a2bf4..fb9a41bd 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -231,6 +231,9 @@ func preloadCodehubService(detail *systemconfig.CodeHost, repoName, repoUUID, br // 根据repo信息从gerrit加载服务 func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { + if remoteName == "" { + remoteName = "origin" + } base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { err = command.RunGitCmds(ch, repoOwner, repoName, branchName, remoteName) -- Gitee From fb1503468c3822d02a3e739972f82c0c56dc3786 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Feb 2022 15:19:01 +0800 Subject: [PATCH 1659/2647] added proxy support for gerrit & codehub Signed-off-by: Min Min --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 4 +- .../internal/repository/models/codehost.go | 1 + .../aslan/core/code/service/branch.go | 8 +-- .../aslan/core/code/service/merge_request.go | 4 +- .../aslan/core/code/service/namespace.go | 6 +- .../aslan/core/code/service/project.go | 8 +-- .../aslan/core/code/service/tag.go | 8 +-- .../aslan/core/code/service/workspace.go | 2 +- .../core/common/service/codehub/client.go | 4 +- .../aslan/core/common/service/fs/git.go | 6 +- .../core/common/service/github/client.go | 10 ++- .../core/common/service/gitlab/client.go | 5 +- .../core/common/service/scmnotify/client.go | 4 +- .../core/common/service/webhook/client.go | 69 ++++++++++--------- .../core/common/service/webhook/controller.go | 12 ++-- .../aslan/core/common/service/workflow.go | 21 +++--- .../aslan/core/service/service/loader.go | 8 +-- .../aslan/core/service/service/new_loader.go | 4 +- .../aslan/core/service/service/service.go | 2 +- .../core/workflow/service/webhook/gerrit.go | 2 +- .../service/webhook/gerrit_workflow_task.go | 2 +- .../service/webhook/github_workflow_task.go | 2 +- .../service/webhook/gitlab_workflow_task.go | 6 +- .../core/workflow/service/webhook/utils.go | 4 +- .../core/workflow/service/workflow/github.go | 2 +- .../core/workflow/service/workflow/gitlab.go | 23 +++++-- .../workflow/service/workflow/nsq_handlers.go | 4 +- .../service/workflow/pipeline_validation.go | 4 +- pkg/shared/client/systemconfig/codehost.go | 2 + pkg/tool/codehub/client.go | 32 ++++++--- pkg/tool/gerrit/client.go | 34 ++++++++- pkg/tool/git/gitlab/client.go | 18 +++-- 32 files changed, 198 insertions(+), 123 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index d93e54ad..ba6e60e0 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -116,9 +116,9 @@ func refreshWebHookSecret(secret string) error { switch ch.Type { case setting.SourceFromGithub: - cl = github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + cl = github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(ch.Address, ch.AccessToken) + cl, err = gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Warnf("Failed to create gitlab client, err: %s", err) continue diff --git a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go index c1bc5a24..58dafa2c 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go @@ -22,6 +22,7 @@ type CodeHost struct { Address string `bson:"address" json:"address"` Namespace string `bson:"namespace" json:"namespace"` AccessToken string `bson:"access_token" json:"accessToken"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index c608a2fa..27ecfe25 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -37,7 +37,7 @@ func CodeHostListBranches(codeHostID int, projectName, namespace, key string, pa } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Errorf("get gitlab client failed, err:%v", err) return nil, e.ErrCodehostListBranches.AddDesc(err.Error()) @@ -53,14 +53,14 @@ func CodeHostListBranches(codeHostID int, projectName, namespace, key string, pa } return ToBranches(brList), nil } else if ch.Type == gerrit.CodehostTypeGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken) + cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) branches, err := cli.ListBranches(projectName) if err != nil { return nil, err } return ToBranches(branches), nil } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region) + codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) branchList, err := codeHubClient.BranchList(projectName) if err != nil { return nil, err @@ -68,7 +68,7 @@ func CodeHostListBranches(codeHostID int, projectName, namespace, key string, pa return ToBranches(branchList), nil } else { // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) branches, err := gh.ListBranches(context.TODO(), namespace, projectName, nil) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 2615d18b..a1a2df3f 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -37,7 +37,7 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) @@ -57,7 +57,7 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo return nil, nil } else { // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) pullRequests, err := gh.ListPullRequests(context.TODO(), namespace, projectName, &github.PullRequestListOptions{ ListOptions: github.ListOptions{PerPage: 100}, }) diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 616ccf7f..6ee5b24a 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -45,7 +45,7 @@ func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogg } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListNamespaces.AddDesc(err.Error()) @@ -63,7 +63,7 @@ func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogg Kind: OrgKind, }}, nil } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region) + codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) codeHubNamespace, err := codeHubClient.NamespaceList() if err != nil { return nil, err @@ -71,7 +71,7 @@ func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogg return ToNamespaces(codeHubNamespace), nil } else { // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) namespaces := make([]*Namespace, 0) user, err := gh.GetAuthenticatedUser(context.TODO()) diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 8a0b0781..689efb13 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -37,7 +37,7 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) @@ -65,7 +65,7 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, return ToProjects(projects), nil } else if ch.Type == gerrit.CodehostTypeGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken) + cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) projects, err := cli.ListProjectsByKey(keyword) if err != nil { log.Error(err) @@ -73,7 +73,7 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, } return ToProjects(projects), nil } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region) + codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) projects, err := codeHubClient.RepoList(namespace, keyword, 100) if err != nil { log.Error(err) @@ -82,7 +82,7 @@ func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, return ToProjects(projects), nil } else { // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) repos, err := gh.ListRepositoriesForAuthenticatedUser(context.TODO(), nil) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 9c5726b4..efed9e4e 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -38,7 +38,7 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log } if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListTags.AddDesc(err.Error()) @@ -50,7 +50,7 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log } return ToTags(tags), nil } else if ch.Type == gerrit.CodehostTypeGerrit { - client := gerrit.NewClient(ch.Address, ch.AccessToken) + client := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) tags, err := client.ListTags(projectName) if err != nil { return nil, err @@ -58,7 +58,7 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log return ToTags(tags), nil } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region) + codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) tags, err := codeHubClient.TagList(projectName) if err != nil { return nil, err @@ -66,7 +66,7 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log return ToTags(tags), nil } else { // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) tags, err := gh.ListTags(context.TODO(), namespace, projectName, nil) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index a3b30f5f..cb77ea5c 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -195,7 +195,7 @@ func GetCodehubRepoInfo(codehostID int, repoUUID, branchName, path string, log * return fileInfos, e.ErrListWorkspace.AddDesc(err.Error()) } - codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region) + codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region, config.ProxyHTTPSAddr(), detail.EnableProxy) treeNodes, err := codeHubClient.FileTree(repoUUID, branchName, path) if err != nil { log.Errorf("Failed to list tree from codehub err:%s", err) diff --git a/pkg/microservice/aslan/core/common/service/codehub/client.go b/pkg/microservice/aslan/core/common/service/codehub/client.go index 8503b01c..74ca2288 100644 --- a/pkg/microservice/aslan/core/common/service/codehub/client.go +++ b/pkg/microservice/aslan/core/common/service/codehub/client.go @@ -24,8 +24,8 @@ type Client struct { *codehub.CodeHubClient } -func NewClient(ak, sk, region string) *Client { - c := codehub.NewCodeHubClient(ak, sk, region) +func NewClient(ak, sk, region, proxyAddr string, enableProxy bool) *Client { + c := codehub.NewCodeHubClient(ak, sk, region, proxyAddr, enableProxy) return &Client{ CodeHubClient: c, } diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index 4ab9082a..baf05358 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -97,7 +97,7 @@ type TreeGetter interface { } func GetPublicTreeGetter(repoLink string) (TreeGetter, error) { - return githubservice.NewClient("", config.ProxyHTTPSAddr()), nil + return githubservice.NewClient("", config.ProxyHTTPSAddr(), true), nil } func GetTreeGetter(codeHostID int) (TreeGetter, error) { @@ -109,9 +109,9 @@ func GetTreeGetter(codeHostID int) (TreeGetter, error) { switch ch.Type { case setting.SourceFromGithub: - return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()), nil + return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy), nil case setting.SourceFromGitlab: - return gitlabservice.NewClient(ch.Address, ch.AccessToken) + return gitlabservice.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) default: // should not have happened here log.DPanicf("invalid source: %s", ch.Type) diff --git a/pkg/microservice/aslan/core/common/service/github/client.go b/pkg/microservice/aslan/core/common/service/github/client.go index fc33e1db..522b851d 100644 --- a/pkg/microservice/aslan/core/common/service/github/client.go +++ b/pkg/microservice/aslan/core/common/service/github/client.go @@ -27,9 +27,15 @@ type Client struct { // InstallationToken string } -func NewClient(accessToken, proxyAddress string) *Client { +func NewClient(accessToken, proxyAddress string, enableProxy bool) *Client { + cfg := &github.Config{ + AccessToken: accessToken, + } + if enableProxy { + cfg.Proxy = proxyAddress + } return &Client{ - Client: github.NewClient(&github.Config{AccessToken: accessToken, Proxy: proxyAddress}), + Client: github.NewClient(cfg), } } diff --git a/pkg/microservice/aslan/core/common/service/gitlab/client.go b/pkg/microservice/aslan/core/common/service/gitlab/client.go index 166f4357..cf798e33 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/client.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/client.go @@ -17,7 +17,6 @@ limitations under the License. package gitlab import ( - "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/tool/git/gitlab" ) @@ -25,8 +24,8 @@ type Client struct { *gitlab.Client } -func NewClient(address, accessToken string) (*Client, error) { - c, err := gitlab.NewClient(address, accessToken, config.ProxyHTTPSAddr()) +func NewClient(address, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { + c, err := gitlab.NewClient(address, accessToken, proxyAddr, enableProxy) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index d20dec2a..feadce50 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -61,7 +61,7 @@ func (c *Client) Comment(notify *models.Notification) error { } if strings.ToLower(codeHostDetail.Type) == "gitlab" { var note *gitlab.Note - cli, err := gitlabtool.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr()) + cli, err := gitlabtool.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr(), codeHostDetail.EnableProxy) if err != nil { c.logger.Errorf("create gitlab client failed err: %v", err) return fmt.Errorf("create gitlab client failed err: %v", err) @@ -90,7 +90,7 @@ func (c *Client) Comment(notify *models.Notification) error { return fmt.Errorf("failed to comment gitlab due to %s/%d %v", notify.ProjectID, notify.PrID, err) } } else if strings.ToLower(codeHostDetail.Type) == gerrit.CodehostTypeGerrit { - cli := gerrit.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken) + cli := gerrit.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr(), codeHostDetail.EnableProxy) for _, task := range notify.Tasks { // create task created comment if !task.FirstCommented && task.Status == config.TaskStatusReady { diff --git a/pkg/microservice/aslan/core/common/service/webhook/client.go b/pkg/microservice/aslan/core/common/service/webhook/client.go index 6cce34ad..e5558ba8 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/client.go +++ b/pkg/microservice/aslan/core/common/service/webhook/client.go @@ -44,22 +44,23 @@ func NewClient() *client { type task struct { owner, repo, address, token, ref, ak, sk, region string from string - add bool + add, enableProxy bool err error doneCh chan struct{} } type TaskOption struct { - Name string - Owner string - Repo string - Address string - Token string - Ref string - From string - AK string - SK string - Region string + Name string + Owner string + Repo string + Address string + Token string + Ref string + From string + AK string + SK string + Region string + EnableProxy bool } func (c *client) AddWebHook(taskOption *TaskOption) error { @@ -68,17 +69,18 @@ func (c *client) AddWebHook(taskOption *TaskOption) error { } t := &task{ - owner: taskOption.Owner, - repo: taskOption.Repo, - address: taskOption.Address, - token: taskOption.Token, - ref: getFullReference(taskOption.Name, taskOption.Ref), - from: taskOption.From, - add: true, - ak: taskOption.AK, - sk: taskOption.SK, - region: taskOption.Region, - doneCh: make(chan struct{}), + owner: taskOption.Owner, + repo: taskOption.Repo, + address: taskOption.Address, + token: taskOption.Token, + ref: getFullReference(taskOption.Name, taskOption.Ref), + from: taskOption.From, + add: true, + enableProxy: taskOption.EnableProxy, + ak: taskOption.AK, + sk: taskOption.SK, + region: taskOption.Region, + doneCh: make(chan struct{}), } select { @@ -102,17 +104,18 @@ func (c *client) RemoveWebHook(taskOption *TaskOption) error { } t := &task{ - owner: taskOption.Owner, - repo: taskOption.Repo, - address: taskOption.Address, - token: taskOption.Token, - ref: getFullReference(taskOption.Name, taskOption.Ref), - from: taskOption.From, - add: false, - ak: taskOption.AK, - sk: taskOption.SK, - region: taskOption.Region, - doneCh: make(chan struct{}), + owner: taskOption.Owner, + repo: taskOption.Repo, + address: taskOption.Address, + token: taskOption.Token, + ref: getFullReference(taskOption.Name, taskOption.Ref), + from: taskOption.From, + add: false, + enableProxy: taskOption.EnableProxy, + ak: taskOption.AK, + sk: taskOption.SK, + region: taskOption.Region, + doneCh: make(chan struct{}), } select { diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index dea2d83b..a41d03cb 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -155,16 +155,16 @@ func removeWebhook(t *task, logger *zap.Logger) { switch t.from { case setting.SourceFromGithub: - cl = github.NewClient(t.token, config.ProxyHTTPSAddr()) + cl = github.NewClient(t.token, config.ProxyHTTPSAddr(), t.enableProxy) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(t.address, t.token) + cl, err = gitlab.NewClient(t.address, t.token, config.ProxyHTTPSAddr(), t.enableProxy) if err != nil { t.err = err t.doneCh <- struct{}{} return } case setting.SourceFromCodeHub: - cl = codehub.NewClient(t.ak, t.sk, t.region) + cl = codehub.NewClient(t.ak, t.sk, t.region, config.ProxyHTTPSAddr(), t.enableProxy) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} @@ -226,9 +226,9 @@ func addWebhook(t *task, logger *zap.Logger) { switch t.from { case setting.SourceFromGithub: - cl = github.NewClient(t.token, config.ProxyHTTPSAddr()) + cl = github.NewClient(t.token, config.ProxyHTTPSAddr(), t.enableProxy) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(t.address, t.token) + cl, err = gitlab.NewClient(t.address, t.token, config.ProxyHTTPSAddr(), t.enableProxy) if err != nil { t.err = err t.doneCh <- struct{}{} @@ -236,7 +236,7 @@ func addWebhook(t *task, logger *zap.Logger) { } case setting.SourceFromCodeHub: - cl = codehub.NewClient(t.ak, t.sk, t.region) + cl = codehub.NewClient(t.ak, t.sk, t.region, config.ProxyHTTPSAddr(), t.enableProxy) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index fef94323..c0f38fed 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -177,16 +177,17 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger switch ch.Type { case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: err = webhook.NewClient().RemoveWebHook(&webhook.TaskOption{ - Name: wh.name, - Owner: wh.owner, - Repo: wh.repo, - Address: ch.Address, - Token: ch.AccessToken, - AK: ch.AccessKey, - SK: ch.SecretKey, - Region: ch.Region, - Ref: name, - From: ch.Type, + Name: wh.name, + Owner: wh.owner, + Repo: wh.repo, + Address: ch.Address, + Token: ch.AccessToken, + AK: ch.AccessKey, + SK: ch.SecretKey, + Region: ch.Region, + EnableProxy: ch.EnableProxy, + Ref: name, + From: ch.Type, }) if err != nil { logger.Errorf("Failed to remove %s webhook %+v, err: %s", ch.Type, wh, err) diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index af9a2bf4..f7988931 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -176,7 +176,7 @@ func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, r func preloadCodehubService(detail *systemconfig.CodeHost, repoName, repoUUID, branchName, path string, isDir bool) ([]string, error) { var ret []string - codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region) + codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region, config.ProxyHTTPSAddr(), detail.EnableProxy) // 非文件夹情况下直接获取文件信息 if !isDir { if !isYaml(path) { @@ -239,7 +239,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re } } - gerritCli := gerrit.NewClient(ch.Address, ch.AccessToken) + gerritCli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) commit, err := gerritCli.GetCommitByBranch(repoName, branchName) if err != nil { log.Errorf("Failed to get latest commit info from repo: %s, the error is: %+v", repoName, err) @@ -370,7 +370,7 @@ func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loa // load codehub service func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, repoUUID, branchName string, args *LoadServiceReq, log *zap.SugaredLogger) error { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region) + codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) if !args.LoadFromDir { yamls, err := codeHubClient.GetYAMLContents(repoUUID, branchName, args.LoadPath, args.LoadFromDir, true) @@ -639,7 +639,7 @@ func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, rep } func validateServiceUpdateCodehub(detail *systemconfig.CodeHost, serviceName, repoName, repoUUID, branchName, loadPath string, isDir bool) error { - codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region) + codeHubClient := codehub.NewCodeHubClient(detail.AccessKey, detail.SecretKey, detail.Region, config.ProxyHTTPSAddr(), detail.EnableProxy) // 非文件夹情况下直接获取文件信息 if !isDir { if !isYaml(loadPath) { diff --git a/pkg/microservice/aslan/core/service/service/new_loader.go b/pkg/microservice/aslan/core/service/service/new_loader.go index 181c18e2..120dd7d1 100644 --- a/pkg/microservice/aslan/core/service/service/new_loader.go +++ b/pkg/microservice/aslan/core/service/service/new_loader.go @@ -239,9 +239,9 @@ type yamlLoader interface { func getLoader(ch *systemconfig.CodeHost) (yamlLoader, error) { switch ch.Type { case setting.SourceFromGithub: - return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()), nil + return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy), nil case setting.SourceFromGitlab: - return gitlabservice.NewClient(ch.Address, ch.AccessToken) + return gitlabservice.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) default: // should not have happened here log.DPanicf("invalid source: %s", ch.Type) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index a62f3967..00cbc083 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1443,7 +1443,7 @@ func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.CodeHo } ch, _ := systemconfig.New().GetCodeHost(service.GerritCodeHostID) - gerritCli := gerrit.NewClient(ch.Address, ch.AccessToken) + gerritCli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) commit, err := gerritCli.GetCommitByBranch(service.GerritRepoName, service.GerritBranchName) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index 34e733b1..6949c866 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -243,7 +243,7 @@ func syncGerritLatestCommit(service *commonmodels.Service) (*systemconfig.CodeHo return nil, err } - gerritCli := gerrit.NewClient(detail.Address, detail.AccessToken) + gerritCli := gerrit.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) commit, err := gerritCli.GetCommitByBranch(service.GerritRepoName, service.GerritBranchName) if err != nil { return detail, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index 37c255c0..ff21e617 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -436,7 +436,7 @@ func checkLatestTaskStaus(pipelineName, mergeRequestID, commitID string, detail } // 比较本次patchset 和 上一个触发任务的patchset 的change file是否相同 - cli := gerrit.NewClient(detail.Address, detail.AccessToken) + cli := gerrit.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) isDiff, err := cli.CompareTwoPatchset(mergeRequestID, commitID, tasks[0].TriggerBy.CommitID) if err != nil { log.Errorf("CompareTwoPatchset failed, mergeRequestID:%s, patchsetID:%s, oldPatchsetID:%s, err:%v", mergeRequestID, commitID, tasks[0].TriggerBy.CommitID, err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 65625126..7eb88708 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -412,7 +412,7 @@ func findChangedFilesOfPullRequest(event *github.PullRequestEvent, codehostID in return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } //pullrequest文件修改 - githubCli := git.NewClient(detail.AccessToken, config.ProxyHTTPSAddr()) + githubCli := git.NewClient(detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) commitComparison, _, err := githubCli.Repositories.CompareCommits(context.Background(), *event.PullRequest.Base.Repo.Owner.Login, *event.PullRequest.Base.Repo.Name, *event.PullRequest.Base.SHA, *event.PullRequest.Head.SHA) if err != nil { return nil, fmt.Errorf("failed to get changes from github, err: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 22f0d479..f144feb5 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -221,7 +221,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( return false, err } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) if err != nil { gpem.log.Errorf("NewClient error: %s", err) return false, err @@ -361,7 +361,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("GetCodeHost codehostId:%d err:%s", item.MainRepo.CodehostID, err) } - cli, err := gitlabtool.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + cli, err := gitlabtool.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { return fmt.Errorf("gitlabtool.NewClient codehostId:%d err:%s", item.MainRepo.CodehostID, err) } @@ -676,7 +676,7 @@ func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([ return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 29ba8ca0..b12346cb 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -244,7 +244,7 @@ func getCodehubClientByAddress(address string) (*codehub.Client, error) { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") } - client := codehub.NewClient(codehost.AccessKey, codehost.SecretKey, codehost.Region) + client := codehub.NewClient(codehost.AccessKey, codehost.SecretKey, codehost.Region, config.ProxyHTTPSAddr(), codehost.EnableProxy) return client, nil } @@ -259,7 +259,7 @@ func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") } - client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr()) + client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/github.go b/pkg/microservice/aslan/core/workflow/service/workflow/github.go index 57155407..4b7ca68b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/github.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/github.go @@ -90,7 +90,7 @@ func createGitCheck(pt *task.Task, log *zap.SugaredLogger) error { log.Errorf("Failed to get codeHost, err:%v", err) return e.ErrGithubUpdateStatus.AddErr(err) } - gc := github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gc := github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) return gc.UpdateCheckStatus(&github.StatusOptions{ Owner: hook.Owner, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go index 21549bde..401aaf61 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/gitlab.go @@ -19,9 +19,12 @@ package workflow import ( "errors" "fmt" + "net/http" + "net/url" "sort" "time" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/xanzy/go-gitlab" "go.uber.org/zap" @@ -47,8 +50,18 @@ func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) ( if ch.Type == setting.SourceFromGitlab { token, address := ch.AccessToken, ch.Address - - cli, err := gitlab.NewOAuthClient(token, gitlab.WithBaseURL(address)) + var client *http.Client + if ch.EnableProxy { + proxyURL, err := url.Parse(config.ProxyHTTPSAddr()) + if err != nil { + return nil, err + } + transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} + client = &http.Client{Transport: transport} + } else { + client = http.DefaultClient + } + cli, err := gitlab.NewOAuthClient(token, gitlab.WithBaseURL(address), gitlab.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("set base url failed, err:%v", err) } @@ -66,7 +79,7 @@ func QueryByBranch(id int, owner, name, branch string, log *zap.SugaredLogger) ( Message: br.Commit.Message, }, nil } else if ch.Type == setting.SourceFromGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken) + cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) commit, err := cli.GetCommitByBranch(name, branch) if err != nil { return nil, err @@ -113,7 +126,7 @@ func QueryByTag(id int, owner, name, tag string, log *zap.SugaredLogger) (*RepoC Message: br.Commit.Message, }, nil } else if ch.Type == setting.SourceFromGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken) + cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) commit, err := cli.GetCommitByTag(name, tag) if err != nil { return nil, err @@ -156,7 +169,7 @@ func GetLatestPrCommit(codehostID, pr int, namespace, projectName string, log *z } if ch.Type == gerrit.CodehostTypeGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken) + cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) change, err := cli.GetCurrentVersionByChangeID(projectName, pr) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index d892275f..b8f5ed42 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -222,13 +222,13 @@ func getRawFileContent(codehostID int, repo, owner, branch, filePath string) ([] } switch ch.Type { case setting.SourceFromGitlab: - cli, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr()) + cli, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { return nil, errors.Wrapf(err, "Failed to get gitlab client") } return cli.GetRawFile(repo, owner, branch, filePath) case setting.SourceFromGithub: - gitClient := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr()) + gitClient := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) return gitClient.GetFileContent(owner, repo, filePath, branch) default: return nil, fmt.Errorf("Failed to create client for codehostID: %d", codehostID) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 60d52920..994adcbe 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -343,7 +343,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { build.AuthorName = commit.AuthorName } } else if codeHostInfo.Type == systemconfig.CodeHubProvider { - codeHubClient := codehub.NewClient(codeHostInfo.AccessKey, codeHostInfo.SecretKey, codeHostInfo.Region) + codeHubClient := codehub.NewClient(codeHostInfo.AccessKey, codeHostInfo.SecretKey, codeHostInfo.Region, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) if build.CommitID == "" && build.Branch != "" { branchList, _ := codeHubClient.BranchList(build.RepoUUID) for _, branchInfo := range branchList { @@ -356,7 +356,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } } else { - gitCli := git.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr()) + gitCli := git.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) //// 需要后端自动获取Branch当前Commit,并填写到build中 if build.CommitID == "" { if build.Tag != "" && build.PR == 0 { diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index ef87d173..d863718d 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -41,6 +41,8 @@ type CodeHost struct { SecretKey string `json:"client_secret"` Username string `json:"username"` Password string `json:"password"` + // the field determine whether the proxy is enabled + EnableProxy bool `json:"enable_proxy"` } type Option struct { diff --git a/pkg/tool/codehub/client.go b/pkg/tool/codehub/client.go index cc25380e..db736c98 100644 --- a/pkg/tool/codehub/client.go +++ b/pkg/tool/codehub/client.go @@ -22,26 +22,32 @@ import ( "io" "io/ioutil" "net/http" + "net/url" "github.com/koderover/zadig/pkg/tool/log" ) type CodeHubClient struct { - AK string `json:"ak"` - SK string `json:"sk"` - Region string `json:"region"` + AK string `json:"ak"` + SK string `json:"sk"` + Region string `json:"region"` + ProxyAddr string `json:"proxy_addr"` + EnableProxy bool `json:"enable_proxy"` } -func NewCodeHubClient(ak, sk, region string) *CodeHubClient { +func NewCodeHubClient(ak, sk, region, proxyAddr string, enableProxy bool) *CodeHubClient { return &CodeHubClient{ - AK: ak, - SK: sk, - Region: region, + AK: ak, + SK: sk, + Region: region, + ProxyAddr: proxyAddr, + EnableProxy: enableProxy, } } // Just apply the signature and request the CodeHub interface func (c *CodeHubClient) sendRequest(method, path string, payload []byte) (io.ReadCloser, error) { + var client *http.Client r, err := http.NewRequest(method, fmt.Sprintf("%s.%s.%s%s", "https://codehub-ext", c.Region, "myhuaweicloud.com", path), ioutil.NopCloser(bytes.NewBuffer(payload))) if r == nil || err != nil { log.Errorf("http.NewRequest error:%s", err) @@ -57,7 +63,17 @@ func (c *CodeHubClient) sendRequest(method, path string, payload []byte) (io.Rea log.Errorf("signer.Sign error:%s", err) return nil, err } - resp, err := http.DefaultClient.Do(r) + if c.EnableProxy { + proxyURL, err := url.Parse(c.ProxyAddr) + if err != nil { + return nil, err + } + transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} + client = &http.Client{Transport: transport} + } else { + client = http.DefaultClient + } + resp, err := client.Do(r) if err != nil || resp == nil { log.Errorf("http.DefaultClient.Do error:%s", err) return nil, err diff --git a/pkg/tool/gerrit/client.go b/pkg/tool/gerrit/client.go index 74d3779a..ff6c257c 100644 --- a/pkg/tool/gerrit/client.go +++ b/pkg/tool/gerrit/client.go @@ -18,10 +18,12 @@ package gerrit import ( "fmt" + "net" "net/http" "net/url" "regexp" "strings" + "time" gerrit "github.com/andygrunwald/go-gerrit" ) @@ -32,8 +34,14 @@ type Client struct { cli *gerrit.Client } -func NewClient(address, accessToken string) *Client { - httpClient := &http.Client{Transport: &BasicAuthTransporter{EncodedUserPass: accessToken}} +func NewClient(address, accessToken, proxyAddr string, enableProxy bool) *Client { + httpClient := &http.Client{ + Transport: &BasicAuthTransporter{ + EncodedUserPass: accessToken, + ProxyAddr: proxyAddr, + EnableProxy: enableProxy, + }, + } cli, _ := gerrit.NewClient(address+"/a", httpClient) return &Client{cli: cli} } @@ -241,12 +249,32 @@ func (c *Client) CompareTwoPatchset(changeID, newPatchSetID, oldPatchSetID strin type BasicAuthTransporter struct { EncodedUserPass string + ProxyAddr string + EnableProxy bool } func (bt *BasicAuthTransporter) RoundTrip(req *http.Request) (*http.Response, error) { auth := "Basic " + bt.EncodedUserPass req.Header.Set("Authorization", auth) - return http.DefaultTransport.RoundTrip(req) + transport := &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + if bt.EnableProxy { + proxyURL, err := url.Parse(bt.ProxyAddr) + if err != nil { + return nil, err + } + transport.Proxy = http.ProxyURL(proxyURL) + } + return transport.RoundTrip(req) } var backslash = regexp.MustCompile("%2[F|f]") diff --git a/pkg/tool/git/gitlab/client.go b/pkg/tool/git/gitlab/client.go index b58afde7..44fbf8d7 100644 --- a/pkg/tool/git/gitlab/client.go +++ b/pkg/tool/git/gitlab/client.go @@ -45,13 +45,19 @@ type Client struct { *gitlab.Client } -func NewClient(address, accessToken, proxyAddr string) (*Client, error) { - proxyURL, err := url.Parse(proxyAddr) - if err != nil { - return nil, err +func NewClient(address, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { + var client *http.Client + if enableProxy { + proxyURL, err := url.Parse(proxyAddr) + if err != nil { + return nil, err + } + transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} + client = &http.Client{Transport: transport} + } else { + client = http.DefaultClient } - transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} - client := &http.Client{Transport: transport} + cli, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(address), gitlab.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("failed to create gitlab client, err: %s", err) -- Gitee From 9bfa8ea521b2efeb1b00adac49bcb250191d0a8d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 15:30:51 +0800 Subject: [PATCH 1660/2647] modify reponame Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/command/git_cmd.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/command/git_cmd.go b/pkg/microservice/aslan/core/common/service/command/git_cmd.go index d28708bb..7692e8b1 100644 --- a/pkg/microservice/aslan/core/common/service/command/git_cmd.go +++ b/pkg/microservice/aslan/core/common/service/command/git_cmd.go @@ -163,6 +163,10 @@ func buildGitCommands(repo *Repo) []*Command { os.MkdirAll(workDir, 0777) } + if strings.Contains(repoName, "-new") { + repo.Name = strings.TrimSuffix(repo.Name, "-new") + } + // 预防非正常退出导致git被锁住 _ = os.Remove(path.Join(workDir, "/.git/index.lock")) -- Gitee From 101273fabea67f4ac9fb03fd9fb3907b60e2d0db Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 24 Feb 2022 15:32:29 +0800 Subject: [PATCH 1661/2647] collaboration instance add recycle day and last visit time Signed-off-by: panxunying --- .../models/collaboration_instance.go | 2 ++ .../repository/models/collaboration_mode.go | 17 +++++++------- .../mongodb/collaboration_instance.go | 10 +++++---- .../service/collaboration_instance.go | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index d081a81e..6369dfc8 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -27,7 +27,9 @@ type CollaborationInstance struct { ProjectName string `bson:"project_name" json:"project_name"` CreateTime int64 `bson:"create_time" json:"create_time"` UpdateTime int64 `bson:"update_time" json:"update_time"` + LastVisitTime int64 `bson:"last_visit_time" json:"last_visit_time"` CollaborationName string `bson:"collaboration_name" json:"collaboration_name"` + RecycleDay int `bson:"recycle_day" json:"recycle_day"` Revision int64 `bson:"revision" json:"revision"` UserUID string `bson:"user_uid" json:"user_uid"` PolicyName string `bson:"policy_name" json:"policy_name"` diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go index 11ce4b9a..1280877c 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -28,15 +28,16 @@ type CollaborationMode struct { ProjectName string `bson:"project_name" json:"project_name"` CreateTime int64 `bson:"create_time" json:"create_time"` UpdateTime int64 `bson:"update_time" json:"update_time"` - Name string `bson:"name" json:"name"` + Name string `bson:"name" json:"name"` Revision int64 `bson:"revision" json:"revision"` - Members []string `bson:"members" json:"members"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - DeployType string `bson:"deploy_type" json:"deploy_type"` - Workflows []WorkflowCMItem `json:"workflows" bson:"workflows"` - Products []ProductCMItem `bson:"products" json:"products"` - CreateBy string `bson:"create_by" json:"create_by"` - UpdateBy string `bson:"update_by" json:"update_by"` + Members []string `bson:"members" json:"members"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + DeployType string `bson:"deploy_type" json:"deploy_type"` + RecycleDay int `bson:"recycle_day" json:"recycle_day"` + Workflows []WorkflowCMItem `bson:"workflows" json:"workflows" ` + Products []ProductCMItem `bson:"products" json:"products"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` } type ProductCMItem struct { diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 3a2bbc90..14d9a41a 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -106,10 +106,11 @@ func (c *CollaborationInstanceColl) Update(uid string, args *models.Collaboratio query := bson.M{"collaboration_name": args.CollaborationName, "project_name": args.ProjectName, "user_uid": uid} change := bson.M{"$set": bson.M{ - "update_time": time.Now().Unix(), - "workflows": args.Workflows, - "revision": args.Revision, - "products": args.Products, + "update_time": time.Now().Unix(), + "last_visit_time": args.LastVisitTime, + "workflows": args.Workflows, + "revision": args.Revision, + "products": args.Products, }} _, err := c.UpdateOne(context.TODO(), query, change) @@ -125,6 +126,7 @@ func (c *CollaborationInstanceColl) BulkCreate(args []*models.CollaborationInsta for _, arg := range args { arg.CreateTime = now arg.UpdateTime = now + arg.LastVisitTime = now } var ois []interface{} for _, obj := range args { diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index f790b067..f1ce47ae 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -3,6 +3,7 @@ package service import ( "fmt" "reflect" + "time" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" @@ -222,6 +223,7 @@ func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, i Revision: mode.Revision, Workflows: workflows, Products: products, + LastVisitTime: time.Now().Unix(), } } @@ -257,6 +259,17 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model }, nil } +func updateVisitTime(uid string, cis []models.CollaborationInstance, logger *zap.SugaredLogger) error { + for _, instance := range cis { + err := mongodb.NewCollaborationInstanceColl().Update(uid, &instance) + if err != nil { + logger.Errorf("syncInstance Update error, error msg:%s", err) + return err + } + } + return nil +} + func GetCollaborationUpdate(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationUpdateResp, error) { collaborations, err := mongodb.NewCollaborationModeColl().List(&mongodb.CollaborationModeListOptions{ Projects: []string{projectName}, @@ -274,6 +287,10 @@ func GetCollaborationUpdate(projectName, uid, identityType, userName string, log ProjectName: projectName, UserUID: uid, }) + if err != nil { + logger.Errorf("GetCollaborationInstance error, err msg:%s", err) + return nil, err + } ciMap := make(map[string]*models.CollaborationInstance) for _, instance := range collaborationInstances { ciMap[instance.CollaborationName] = instance @@ -283,6 +300,11 @@ func GetCollaborationUpdate(projectName, uid, identityType, userName string, log logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err } + err = updateVisitTime(uid, resp.UpdateInstance, logger) + if err != nil { + logger.Errorf("GetCollaborationUpdate updateVisitTime error, err msg:%s", err) + return nil, err + } return resp, nil } -- Gitee From 5108e20817ca35ea89b054367d03c74ff3fd9c1a Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 24 Feb 2022 16:22:27 +0800 Subject: [PATCH 1662/2647] fix if sync error state Signed-off-by: panxunying --- .../aslan/core/collaboration/service/collaboration_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index f1ce47ae..1f5c0604 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1149,7 +1149,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id } } ifSync := false - if len(newWorkflow) == 0 && len(newProductName) == 0 && len(updateResp.Update) != 0 || len(updateResp.Delete) != 0 { + if len(newWorkflow) == 0 && len(newProductName) == 0 && (len(updateResp.Update) != 0 || len(updateResp.Delete) != 0) { ifSync = true } return &GetCollaborationNewResp{ -- Gitee From 854a9e5f3912d39eb60e0a8505e8efa90347e0e6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Feb 2022 16:46:25 +0800 Subject: [PATCH 1663/2647] reaper & warpdrive changes for enabling per-repository proxy settings Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/meta/types.go | 1 + pkg/microservice/reaper/core/service/reaper/git.go | 10 +++++++--- .../warpdrive/core/service/taskplugin/job.go | 1 + .../warpdrive/core/service/types/reaper.go | 1 + .../warpdrive/core/service/types/task/build.go | 1 + pkg/types/repo.go | 1 + 6 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 55fabd40..bed14d73 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -263,6 +263,7 @@ type Repo struct { User string `yaml:"username"` Password string `yaml:"password"` CheckoutRef string `yaml:"checkout_ref"` + EnableProxy bool `yaml:"enable_proxy"` } // PRRef returns refs format diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 089c4cd3..cd3be307 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -48,10 +48,14 @@ func (r *Reaper) runGitCmds() error { // 如果存在github代码库,则设置代理,同时保证非github库不走代理 if r.Ctx.Proxy.EnableRepoProxy && r.Ctx.Proxy.Type == "http" { noProxy := "" + proxyFlag := false for _, repo := range r.Ctx.Repos { - if repo.Source == meta.ProviderGithub { - envs = append(envs, fmt.Sprintf("http_proxy=%s", r.Ctx.Proxy.GetProxyURL())) - envs = append(envs, fmt.Sprintf("https_proxy=%s", r.Ctx.Proxy.GetProxyURL())) + if repo.EnableProxy { + if !proxyFlag { + envs = append(envs, fmt.Sprintf("http_proxy=%s", r.Ctx.Proxy.GetProxyURL())) + envs = append(envs, fmt.Sprintf("https_proxy=%s", r.Ctx.Proxy.GetProxyURL())) + proxyFlag = true + } } else { uri, err := url.Parse(repo.Address) if err == nil { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 90f90eef..49e00137 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -265,6 +265,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName CheckoutRef: build.CheckoutRef, User: build.Username, Password: build.Password, + EnableProxy: build.EnableProxy, } ctx.Repos = append(ctx.Repos, repo) } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index bfc68dbe..9ec9e96e 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -219,6 +219,7 @@ type Repo struct { User string `yaml:"username"` Password string `yaml:"password"` CheckoutRef string `yaml:"checkout_ref"` + EnableProxy bool `yaml:"enable_proxy"` } // PRRef returns refs format diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 386d8b8d..8c685a8f 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -281,6 +281,7 @@ type Repository struct { ProjectUUID string `bson:"project_uuid,omitempty" json:"project_uuid,omitempty"` RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty"` + EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy,omitempty"` } type BuildStep struct { diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 24cf3b6b..8c9735c2 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -52,6 +52,7 @@ type Repository struct { RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty"` Username string `bson:"username,omitempty" json:"username,omitempty"` Password string `bson:"password,omitempty" json:"password,omitempty"` + EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy,omitempty"` } // GetReleaseCandidateTag 返回待发布对象Tag -- Gitee From 402fc3b53e4022806b106e54563a82b1dbc248ac Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 24 Feb 2022 16:59:17 +0800 Subject: [PATCH 1664/2647] fix webhook not working bug Signed-off-by: allenshen --- .../core/workflow/service/webhook/codehub.go | 6 +--- .../core/workflow/service/webhook/github.go | 9 ++---- .../core/workflow/service/webhook/gitlab.go | 8 +---- .../core/workflow/service/webhook/utils.go | 31 +++++++++++++------ 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go b/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go index 03faf1d8..76251be8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go @@ -168,11 +168,7 @@ func SyncServiceTemplateFromCodehub(service *commonmodels.Service, log *zap.Suga log.Errorf("ensureServiceTmpl error: %+v", err) return e.ErrValidateTemplate.AddDesc(err.Error()) } - // 更新到数据库,revision+1 - if err := commonrepo.NewServiceColl().Create(service); err != nil { - log.Errorf("Failed to sync service %s from codehub path %s error: %v", service.ServiceName, service.SrcPath, err) - return e.ErrCreateTemplate.AddDesc(err.Error()) - } + log.Infof("End of sync service template %s from codehub path %s", service.ServiceName, service.SrcPath) return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index dfdd8f30..71923bb8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -561,8 +561,7 @@ func updateServiceTemplateByGithubPush(pushEvent *github.PushEvent, log *zap.Sug errs := &multierror.Error{} for _, service := range serviceTmpls { - srcPath := service.SrcPath - _, _, _, _, path, _, err := GetOwnerRepoBranchPath(srcPath) + path, err := getServiceSrcPath(service) if err != nil { errs = multierror.Append(errs, err) } @@ -670,11 +669,7 @@ func SyncServiceTemplateFromGithub(service *commonmodels.Service, latestCommitID log.Errorf("ensure github serviceTmpl failed, error: %+v", err) return e.ErrValidateTemplate.AddDesc(err.Error()) } - // 更新到数据库,revision+1 - if err := commonrepo.NewServiceColl().Create(service); err != nil { - log.Errorf("Failed to sync service %s from github path %s error: %v", service.ServiceName, service.SrcPath, err) - return e.ErrCreateTemplate.AddDesc(err.Error()) - } + log.Infof("End of sync service template %s from github path %s", service.ServiceName, service.SrcPath) return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 2825dddb..5dad2966 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -300,8 +300,7 @@ func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) errs := &multierror.Error{} for _, service := range serviceTmpls { - srcPath := service.SrcPath - _, _, _, _, path, _, err := GetOwnerRepoBranchPath(srcPath) + path, err := getServiceSrcPath(service) if err != nil { errs = multierror.Append(errs, err) } @@ -367,11 +366,6 @@ func SyncServiceTemplateFromGitlab(service *commonmodels.Service, log *zap.Sugar log.Errorf("ensureServiceTmpl error: %+v", err) return e.ErrValidateTemplate.AddDesc(err.Error()) } - // 更新到数据库,revision+1 - if err := commonrepo.NewServiceColl().Create(service); err != nil { - log.Errorf("Failed to sync service %s from gitlab path %s error: %v", service.ServiceName, service.SrcPath, err) - return e.ErrCreateTemplate.AddDesc(err.Error()) - } log.Infof("End of sync service template %s from gitlab path %s", service.ServiceName, service.SrcPath) return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 92e10954..db05a482 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -150,6 +150,20 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar return err } log.Infof("find %d containers in service %s", len(args.Containers), args.ServiceName) + + // 设置新的版本号 + serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) + rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) + if err != nil { + return fmt.Errorf("get next service template revision error: %v", err) + } + + args.Revision = rev + // 更新到数据库,revision+1 + if err := commonrepo.NewServiceColl().Create(args); err != nil { + log.Errorf("Failed to sync service %s from github path %s error: %v", args.ServiceName, args.SrcPath, err) + return e.ErrCreateTemplate.AddDesc(err.Error()) + } } else if args.Type == setting.HelmDeployType { if args.Source == setting.SourceFromGitlab { // Set args.Commit @@ -170,15 +184,6 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar } } - // 设置新的版本号 - serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) - rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) - if err != nil { - return fmt.Errorf("get next service template revision error: %v", err) - } - - args.Revision = rev - return nil } @@ -681,3 +686,11 @@ func checkTriggerYamlParams(triggerYaml *TriggerYaml) error { return nil } + +func getServiceSrcPath(service *commonmodels.Service) (string, error) { + if service.LoadPath != "" { + return service.LoadPath, nil + } + _, _, _, _, p, _, err := GetOwnerRepoBranchPath(service.SrcPath) + return p, err +} -- Gitee From fd12c90d7f8939c7992bafd4f8b9eef53267673b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 17:30:48 +0800 Subject: [PATCH 1665/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 5 +++++ pkg/microservice/aslan/core/service/service/helm.go | 1 + 2 files changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index f8e34c4c..800658fc 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "fmt" "io/fs" "path" @@ -42,6 +43,7 @@ func ListHelmRepos(log *zap.SugaredLogger) ([]*commonmodels.HelmRepo, error) { } func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { + fmt.Println("PreLoadServiceManifests start ...") ok, err := fsutil.DirExists(base) if err != nil { log.Errorf("Failed to check if dir %s is exiting, err: %s", base, err) @@ -51,8 +53,11 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { return nil } + fmt.Println(fmt.Sprintf("ready to DownloadServiceManifests 。。。")) if err = DownloadServiceManifests(base, svc.ProductName, svc.ServiceName); err == nil { return nil + } else { + fmt.Println(fmt.Sprintf("DownloadServiceManifests err:%s", err)) } log.Warnf("Failed to download service from s3, err: %s", err) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 5379362d..aa10a1d2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1157,6 +1157,7 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s } if err != nil || revision == 0 { base = config.LocalServicePath(productName, serviceName) + fmt.Println("base:", base) err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) -- Gitee From d9a25d35b99bbaeadd675c9605f78fc1f45d8eb4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 17:58:34 +0800 Subject: [PATCH 1666/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 800658fc..796f33e0 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -43,7 +43,6 @@ func ListHelmRepos(log *zap.SugaredLogger) ([]*commonmodels.HelmRepo, error) { } func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { - fmt.Println("PreLoadServiceManifests start ...") ok, err := fsutil.DirExists(base) if err != nil { log.Errorf("Failed to check if dir %s is exiting, err: %s", base, err) @@ -53,15 +52,19 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { return nil } - fmt.Println(fmt.Sprintf("ready to DownloadServiceManifests 。。。")) if err = DownloadServiceManifests(base, svc.ProductName, svc.ServiceName); err == nil { return nil - } else { - fmt.Println(fmt.Sprintf("DownloadServiceManifests err:%s", err)) } + fmt.Println(fmt.Sprintf("DownloadServiceManifests err:%s", err)) + + fmt.Println(fmt.Sprintf("ready to preLoadServiceManifestsFromSource 。。。")) + log.Warnf("Failed to download service from s3, err: %s", err) + //if svc.Source == "gerrit" { + // return + //} return preLoadServiceManifestsFromSource(svc) } -- Gitee From 9d496cd86f0877940d1d44687289c49f2d9e5c59 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 19:02:34 +0800 Subject: [PATCH 1667/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 10 +++++++++- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 796f33e0..52d446b9 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -52,6 +52,7 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { return nil } + fmt.Println(fmt.Sprintf("PreLoadServiceManifests base : %s", base)) if err = DownloadServiceManifests(base, svc.ProductName, svc.ServiceName); err == nil { return nil } @@ -85,8 +86,14 @@ func PreloadServiceManifestsByRevision(base string, svc *commonmodels.Service) e } func DownloadServiceManifests(base, projectName, serviceName string) error { + fmt.Println(fmt.Sprintf("DownloadServiceManifests : %s", base)) s3Base := config.ObjectStorageServicePath(projectName, serviceName) - return fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) + err := fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) + if err != nil { + fmt.Println(fmt.Sprintf("DownloadAndExtractFilesFromS3 : %s", base)) + return err + } + return nil } func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS) error { @@ -105,6 +112,7 @@ func CopyAndUploadService(projectName, serviceName, currentChartPath string, cop } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { + fmt.Println(fmt.Sprintf("preLoadServiceManifestsFromSource start ...")) tree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName, RepoLink: svc.SrcPath}, func(afero.Fs) (string, error) { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index aa10a1d2..53a19c6a 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1157,13 +1157,13 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s } if err != nil || revision == 0 { base = config.LocalServicePath(productName, serviceName) - fmt.Println("base:", base) err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) } } + fmt.Println("loadServiceFileInfos base:", base) err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) -- Gitee From 7e696605c26cdba93c67f8d897aa612ddede2c6b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 19:20:50 +0800 Subject: [PATCH 1668/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 5 ++++- pkg/microservice/aslan/core/common/service/helm.go | 11 +---------- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index b3e52a3f..6582e407 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -122,20 +122,23 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if s3.Provider == setting.ProviderSourceAli { forcedPathStyle = false } + logger.Info(" DownloadAndExtractFilesFromS3: 1") client, err := s3tool.NewClient(s3.Endpoint, s3.Ak, s3.Sk, s3.Insecure, forcedPathStyle) if err != nil { logger.Errorf("Failed to create s3 client, err: %s", err) return err } + logger.Info(" DownloadAndExtractFilesFromS3: 2") if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return err } + logger.Info(" DownloadAndExtractFilesFromS3: 3") if err = fsutil.Untar(localPath, localBase); err != nil { logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) return err } - + logger.Info(" DownloadAndExtractFilesFromS3: 4") return nil } diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 52d446b9..c3387c28 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -52,20 +52,12 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { return nil } - fmt.Println(fmt.Sprintf("PreLoadServiceManifests base : %s", base)) if err = DownloadServiceManifests(base, svc.ProductName, svc.ServiceName); err == nil { return nil } - fmt.Println(fmt.Sprintf("DownloadServiceManifests err:%s", err)) - - fmt.Println(fmt.Sprintf("ready to preLoadServiceManifestsFromSource 。。。")) - log.Warnf("Failed to download service from s3, err: %s", err) - //if svc.Source == "gerrit" { - // return - //} return preLoadServiceManifestsFromSource(svc) } @@ -86,13 +78,12 @@ func PreloadServiceManifestsByRevision(base string, svc *commonmodels.Service) e } func DownloadServiceManifests(base, projectName, serviceName string) error { - fmt.Println(fmt.Sprintf("DownloadServiceManifests : %s", base)) s3Base := config.ObjectStorageServicePath(projectName, serviceName) err := fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) if err != nil { - fmt.Println(fmt.Sprintf("DownloadAndExtractFilesFromS3 : %s", base)) return err } + fmt.Println(fmt.Sprintf("DownloadAndExtractFilesFromS3 err: %v", err)) return nil } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 53a19c6a..edb6ec9c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1163,12 +1163,12 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s } } - fmt.Println("loadServiceFileInfos base:", base) err = commonservice.PreLoadServiceManifests(base, svc) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) } var fis []*types.FileInfo + fmt.Println(fmt.Sprintf("filepath.Join(base, serviceName, dir):", filepath.Join(base, serviceName, dir))) files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) -- Gitee From 3dcdf762eda45ff8f3c8e0a8f3be4ddda0896e47 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 19:32:54 +0800 Subject: [PATCH 1669/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 6582e407..e092196d 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -102,18 +102,20 @@ func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Stor } func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.SugaredLogger) error { + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 1")) tmpDir, err := os.MkdirTemp("", "") if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } defer os.RemoveAll(tmpDir) + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 2")) s3, err := s3service.FindDefaultS3() if err != nil { logger.Errorf("Failed to find default s3, err: %s", err) return err } - + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 3")) tarball := fmt.Sprintf("%s.tar.gz", name) localPath := filepath.Join(tmpDir, tarball) s3Path := filepath.Join(s3.Subfolder, s3Base, tarball) @@ -122,23 +124,23 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if s3.Provider == setting.ProviderSourceAli { forcedPathStyle = false } - logger.Info(" DownloadAndExtractFilesFromS3: 1") + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 4")) client, err := s3tool.NewClient(s3.Endpoint, s3.Ak, s3.Sk, s3.Insecure, forcedPathStyle) if err != nil { logger.Errorf("Failed to create s3 client, err: %s", err) return err } - logger.Info(" DownloadAndExtractFilesFromS3: 2") + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 5")) if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return err } - logger.Info(" DownloadAndExtractFilesFromS3: 3") + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 6")) if err = fsutil.Untar(localPath, localBase); err != nil { logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) return err } - logger.Info(" DownloadAndExtractFilesFromS3: 4") + fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 7")) return nil } -- Gitee From ca0fb565bb70078bc9aa8e06dae054ce9086532d Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 24 Feb 2022 19:06:57 +0800 Subject: [PATCH 1670/2647] support Prepare status & optimize logs of build process Signed-off-by: zhangyifei --- pkg/microservice/aslan/config/consts.go | 1 + .../reaper/core/service/reaper/reaper.go | 153 +++++++++--------- .../reaper/core/service/reaper/script.go | 1 - pkg/microservice/reaper/executor/executor.go | 10 +- pkg/microservice/warpdrive/config/const.go | 1 + .../service/taskcontroller/task_handler.go | 7 +- .../core/service/taskplugin/build.go | 2 + .../warpdrive/core/service/taskplugin/job.go | 46 ++++++ .../core/service/taskplugin/testing.go | 2 + 9 files changed, 141 insertions(+), 82 deletions(-) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 3cce480c..d2b37642 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -106,6 +106,7 @@ const ( QueueItemPending Status = "pending" StatusChanged Status = "changed" StatusNotRun Status = "notRun" + StatusPrepare Status = "prepare" ) type TaskStatus string diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index d36960bb..8180486e 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -49,7 +49,6 @@ type Reaper struct { ActiveWorkspace string UserEnvs map[string]string cm CacheManager - dogFeed bool } func NewReaper() (*Reaper, error) { @@ -63,9 +62,6 @@ func NewReaper() (*Reaper, error) { return nil, fmt.Errorf("cannot unmarshal job data: %v", err) } - // 初始化容器Envs, 格式为: "key=value". - // ctx.Envs = os.Environ() - // 初始化容器Path ctx.Paths = config.Path() reaper := &Reaper{ @@ -111,7 +107,7 @@ func (r *Reaper) CompressCache(storageURI string) error { if err := r.cm.Archive(cacheDir, r.GetCacheFile()); err != nil { return fmt.Errorf("failed to cache %s: %s", cacheDir, err) } - log.Infof("Succeed to cache %s", cacheDir) + log.Infof("Succeed to cache %s.", cacheDir) // remove workspace err := os.RemoveAll(r.ActiveWorkspace) @@ -170,35 +166,40 @@ func (r *Reaper) EnsureDir(dir string) error { func (r *Reaper) BeforeExec() error { r.StartTime = time.Now() - log.Info("wait for docker daemon to start ...") + log.Infof("Checking Docker Connectivity.") + startTimeCheckDocker := time.Now() for i := 0; i < 15; i++ { if err := dockerInfo().Run(); err == nil { break } time.Sleep(time.Second * 1) } + log.Infof("Check ended. Duration: %.2f seconds.", time.Since(startTimeCheckDocker).Seconds()) if r.Ctx.DockerRegistry != nil { if r.Ctx.DockerRegistry.UserName != "" { - log.Infof("login docker registry %s", r.Ctx.DockerRegistry.Host) + log.Infof("Logining Docker Registry: %s.", r.Ctx.DockerRegistry.Host) + startTimeDockerLogin := time.Now() cmd := dockerLogin(r.Ctx.DockerRegistry.UserName, r.Ctx.DockerRegistry.Password, r.Ctx.DockerRegistry.Host) var out bytes.Buffer cmd.Stdout = &out cmd.Stderr = &out if err := cmd.Run(); err != nil { - log.Errorf("docker login failed with error: %s\n%s", err, out.String()) - return fmt.Errorf("docker login failed with error: %s", err) + return fmt.Errorf("failed to login docker registry: %s", err) } + + log.Infof("Login ended. Duration: %.2f seconds.", time.Since(startTimeDockerLogin).Seconds()) } } if r.Ctx.CacheEnable && r.Ctx.Cache.MediumType == types.ObjectMedium { - log.Info("extracting workspace ...") + log.Info("Pulling Cache.") + startTimePullCache := time.Now() if err := r.DecompressCache(); err != nil { // If the workflow runs for the first time, there may be no cache. - log.Infof("no previous cache is found: %s", err) + log.Infof("Failed to pull cache: %s. Duration: %.2f seconds.", err, time.Since(startTimePullCache).Seconds()) } else { - log.Info("succeed to extract workspace") + log.Infof("Succeed to pull cache. Duration: %.2f seconds.", time.Since(startTimePullCache).Seconds()) } } @@ -232,7 +233,6 @@ func (r *Reaper) BeforeExec() error { if r.Ctx.GinkgoTest != nil && len(r.Ctx.GinkgoTest.ResultPath) > 0 { r.Ctx.GinkgoTest.ResultPath = filepath.Join(r.ActiveWorkspace, r.Ctx.GinkgoTest.ResultPath) - log.Infof("clean test result path %s", r.Ctx.GinkgoTest.ResultPath) if err := os.RemoveAll(r.Ctx.GinkgoTest.ResultPath); err != nil { log.Warning(err.Error()) } @@ -293,26 +293,35 @@ func (r *Reaper) dockerCommands() []*exec.Cmd { } func (r *Reaper) runDockerBuild() error { - if r.Ctx.DockerBuildCtx != nil { - err := r.prepareDockerfile() - if err != nil { - return err - } - if r.Ctx.Proxy != nil { - r.setProxy(r.Ctx.DockerBuildCtx, r.Ctx.Proxy) - } + if r.Ctx.DockerBuildCtx == nil { + return nil + } - envs := r.getUserEnvs() - for _, c := range r.dockerCommands() { - c.Stdout = os.Stdout - c.Stderr = os.Stderr - c.Dir = r.ActiveWorkspace - c.Env = envs - if err := c.Run(); err != nil { - return err - } + log.Info("Preparing Dockerfile.") + startTimePrepareDockerfile := time.Now() + err := r.prepareDockerfile() + if err != nil { + return fmt.Errorf("failed to prepare dockerfile: %s", err) + } + log.Infof("Preparation ended. Duration: %.2f seconds.", time.Since(startTimePrepareDockerfile).Seconds()) + + if r.Ctx.Proxy != nil { + r.setProxy(r.Ctx.DockerBuildCtx, r.Ctx.Proxy) + } + + log.Info("Runing Docker Build.") + startTimeDockerBuild := time.Now() + envs := r.getUserEnvs() + for _, c := range r.dockerCommands() { + c.Stdout = os.Stdout + c.Stderr = os.Stderr + c.Dir = r.ActiveWorkspace + c.Env = envs + if err := c.Run(); err != nil { + return fmt.Errorf("failed to run docker build: %s", err) } } + log.Infof("Docker build ended. Duration: %.2f seconds.", time.Since(startTimeDockerBuild).Seconds()) return nil } @@ -331,113 +340,107 @@ func (r *Reaper) prepareDockerfile() error { } func (r *Reaper) Exec() error { + log.Info("Installing Dependency Packages.") + startTimeInstallDeps := time.Now() if err := r.runIntallationScripts(); err != nil { - return err + return fmt.Errorf("failed to install dependency packages: %s", err) } + log.Infof("Install ended. Duration: %.2f seconds.", time.Since(startTimeInstallDeps).Seconds()) + log.Info("Cloning Repository.") + startTimeCloneRepo := time.Now() if err := r.runGitCmds(); err != nil { - return err + return fmt.Errorf("failed to clone repository: %s", err) } + log.Infof("Clone ended. Duration: %.2f seconds.", time.Since(startTimeCloneRepo).Seconds()) if err := r.createReadme(ReadmeFile); err != nil { - log.Warningf("create readme file error: %v", err) + log.Warningf("Failed to create README file: %s", err) } + log.Info("Executing User Build Script.") + startTimeRunBuildScript := time.Now() if err := r.runScripts(); err != nil { - return err + return fmt.Errorf("failed to execute user build script: %s", err) } + log.Infof("Execution ended. Duration: %.2f seconds.", time.Since(startTimeRunBuildScript).Seconds()) return r.runDockerBuild() } -func (r *Reaper) AfterExec(upStreamErr error) error { +func (r *Reaper) AfterExec() error { if r.Ctx.GinkgoTest != nil && r.Ctx.GinkgoTest.ResultPath != "" { resultPath := r.Ctx.GinkgoTest.ResultPath if !strings.HasPrefix(resultPath, "/") { resultPath = filepath.Join(r.ActiveWorkspace, resultPath) } + if r.Ctx.TestType == "" { r.Ctx.TestType = setting.FunctionTest } - if r.Ctx.TestType == setting.FunctionTest { - log.Info("Merge test result.") - if err := mergeGinkgoTestResults( - r.Ctx.Archive.File, - resultPath, - r.Ctx.Archive.Dir, - r.StartTime, - ); err != nil { - log.Errorf("Failed to merge results of ginkgo test: %s", err) - return err + + switch r.Ctx.TestType { + case setting.FunctionTest: + err := mergeGinkgoTestResults(r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir, r.StartTime) + if err != nil { + return fmt.Errorf("failed to merge test result: %s", err) } - } else if r.Ctx.TestType == setting.PerformanceTest { - log.Info("Archive performance test result.") - if err := JmeterTestResults( - r.Ctx.Archive.File, - resultPath, - r.Ctx.Archive.Dir, - ); err != nil { - log.Errorf("Failed to archive results of performance test: %s", err) - return err + case setting.PerformanceTest: + err := JmeterTestResults(r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir) + if err != nil { + return fmt.Errorf("failed to archive performance test result: %s", err) } } if len(r.Ctx.GinkgoTest.ArtifactPaths) > 0 { if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, r.Ctx.GinkgoTest.ArtifactPaths); err != nil { - log.Errorf("Failed to upload artifacts: %s", err) - return err + return fmt.Errorf("failed to upload artifacts: %s", err) } } if err := r.archiveTestFiles(); err != nil { - log.Errorf("Failed to archive test files: %s", err) - return err + return fmt.Errorf("failed to archive test files: %s", err) } if err := r.archiveHTMLTestReportFile(); err != nil { - log.Errorf("Failed to archive html test report: %s", err) - return err + return fmt.Errorf("failed to archive HTML test report: %s", err) } } - if upStreamErr != nil { - return nil - } - if r.Ctx.ArtifactInfo == nil { if err := r.archiveS3Files(); err != nil { - log.Errorf("Failed to archive S3 files: %s", err) - return err + return fmt.Errorf("failed to archive S3 files: %s", err) } + if err := r.RunPostScripts(); err != nil { - log.Errorf("Failed to run postscripts: %s", err) - return err + return fmt.Errorf("failed to run postscripts: %s", err) } } else { if err := r.downloadArtifactFile(); err != nil { - log.Errorf("Failed to download artifact files: %s", err) - return err + return fmt.Errorf("failed to download artifact files: %s", err) } } if r.Ctx.ArtifactPath != "" { if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { - log.Errorf("Failed to upload artifacts: %s", err) - return err + return fmt.Errorf("failed to upload artifacts: %s", err) } } if err := r.RunPMDeployScripts(); err != nil { - log.Errorf("Failed to run deploy scripts on physical machine: %s", err) - return err + return fmt.Errorf("failed to run deploy scripts on physical machine: %s", err) } // Upload workspace cache if the user turns on caching and uses object storage. // Note: Whether the cache is uploaded successfully or not cannot hinder the progress of the overall process, // so only exceptions are printed here and the process is not interrupted. if r.Ctx.CacheEnable && r.Ctx.Cache.MediumType == types.ObjectMedium { + log.Info("Uploading Build Cache.") + startTimeUploadBuildCache := time.Now() if err := r.CompressCache(r.Ctx.StorageURI); err != nil { - log.Warnf("Failed to run compress cache: %s", err) + log.Warnf("Failed to upload build cache: %s. Duration: %.2f seconds.", err, time.Since(startTimeUploadBuildCache).Seconds()) + } else { + log.Infof("Upload ended. Duration: %.2f seconds.", time.Since(startTimeUploadBuildCache).Seconds()) } } diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index be31a946..d1321fa5 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -165,7 +165,6 @@ func (r *Reaper) createReadme(file string) error { scripts = append(scripts, fmt.Sprintf("echo GIT-COMMIT: >> %s", file)) for _, repo := range r.Ctx.Repos { - if repo == nil || len(repo.Name) == 0 { continue } diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 27ccab8d..7b4562d7 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -37,7 +37,7 @@ func Execute() error { }) start := time.Now() - log.Info("Build start.") + log.Info("====================== Build Start ======================") var err error defer func() { @@ -50,11 +50,11 @@ func Execute() error { dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(resultMsg), 0644) if dogFoodErr != nil { - log.Errorf("Failed to create dog food: %s", dogFoodErr) - } else { - log.Infof("Build end. Duration: %.2f seconds.", time.Since(start).Seconds()) + log.Errorf("Failed to create dog food: %s.", dogFoodErr) } + log.Infof("====================== Build Ended. Duration: %.2f seconds ======================", time.Since(start).Seconds()) + // Note: Mark the task has been completed through the dogfood file, indirectly notify wd to do follow-up // operations, and wait for a fixed time. // Since `wd` will automatically delete the job after detecting the dogfile, this time has little @@ -78,7 +78,7 @@ func Execute() error { } } - if err = r.AfterExec(err); err != nil { + if err = r.AfterExec(); err != nil { return fmt.Errorf("failed to work after building: %s", err) } diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 15bcdbc0..3db83f95 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -55,6 +55,7 @@ const ( StatusQueued Status = "queued" StatusBlocked Status = "blocked" QueueItemPending Status = "pending" + StatusPrepare Status = "prepare" ) type PipelineType string diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index afac91fb..84b583d3 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -460,7 +460,12 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } // 设置 SubTask 初始状态 - plugin.SetStatus(config.StatusRunning) + switch plugin.Type() { + case config.TaskBuild, config.TaskTestingV2: + plugin.SetStatus(config.StatusPrepare) + default: + plugin.SetStatus(config.StatusRunning) + } // 设置 SubTask 开始时间 plugin.SetStartTime() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 0e89f64b..8025923b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -316,6 +316,8 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe return } p.Log.Infof("succeed to create build job %s", p.JobName) + + p.Task.TaskStatus = waitJobReady(ctx, p.KubeNamespace, p.JobName, p.kubeClient, p.Log) } func (p *BuildTaskPlugin) Wait(ctx context.Context) { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 90f90eef..5bcd7070 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -758,6 +758,52 @@ func waitJobEnd(ctx context.Context, taskTimeout int, namspace, jobName string, return waitJobEndWithFile(ctx, taskTimeout, namspace, jobName, false, kubeClient, xl) } +func waitJobReady(ctx context.Context, namespace, jobName string, kubeClient client.Client, xl *zap.SugaredLogger) (status config.Status) { + xl.Infof("Wait job to start: %s/%s", namespace, jobName) + podTimeout := time.After(120 * time.Second) + + var started bool + for { + select { + case <-podTimeout: + return config.StatusTimeout + default: + time.Sleep(time.Second) + + job, _, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil { + xl.Errorf("Failed to get job `%s` in namespace `%s`: %s", jobName, namespace, err) + continue + } + + podLabels := labels.Set{ + jobLabelPTypeKey: job.Labels[jobLabelPTypeKey], + jobLabelServiceKey: job.Labels[jobLabelServiceKey], + jobLabelTaskKey: job.Labels[jobLabelTaskKey], + jobLabelSTypeKey: job.Labels[jobLabelSTypeKey], + } + pods, err := getter.ListPods(namespace, podLabels.AsSelector(), kubeClient) + if err != nil { + xl.Errorf("Failed to get pods in namespace `%s` for Job `%s`: %s", namespace, jobName, err) + continue + } + + for _, pod := range pods { + if pod.Status.Phase == corev1.PodRunning { + started = true + break + } + } + } + + if started { + break + } + } + + return config.StatusRunning +} + func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, checkFile bool, kubeClient client.Client, xl *zap.SugaredLogger) (status config.Status) { xl.Infof("wait job to start: %s/%s", namespace, jobName) timeout := time.After(time.Duration(taskTimeout) * time.Second) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 36de8a09..9a1cc3a9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -275,6 +275,8 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.Task.Error = msg return } + + p.Task.TaskStatus = waitJobReady(ctx, p.KubeNamespace, p.JobName, p.kubeClient, p.Log) } func (p *TestPlugin) Wait(ctx context.Context) { -- Gitee From 80bf3308b97dc37782c21454f2cb61d77f2556cc Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 20:44:48 +0800 Subject: [PATCH 1671/2647] modify upload file Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 4 +- .../aslan/core/common/service/fs/s3.go | 67 ++++++++++++++++--- .../aslan/core/common/service/helm.go | 4 +- .../aslan/core/service/service/helm.go | 5 +- pkg/util/file.go | 55 +++++++++++++++ 5 files changed, 120 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 679d0dc6..fb17f241 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -74,7 +74,7 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string } // SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. -func CopyAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { +func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { var wg wait.Group var err error @@ -87,7 +87,7 @@ func CopyAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base, curre }) wg.Start(func() { - err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) + err2 := ArchiveAndUploadLocalFilesToS3(names, currentChartPath, s3Base, logger) if err2 != nil { logger.Errorf("Failed to upload files to s3, err: %s", err2) err = err2 diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index e092196d..f86b7d64 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -27,6 +27,7 @@ import ( s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -48,10 +49,64 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, lo return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) } +func ArchiveAndUploadLocalFilesToS3(names []string, currentBase, s3Base string, logger *zap.SugaredLogger) error { + if len(names) == 0 { + return fmt.Errorf("names not appointed") + } + + tmpDir, err := os.MkdirTemp("", "") + if err != nil { + logger.Errorf("Failed to create temp dir, err: %s", err) + return err + } + defer os.RemoveAll(tmpDir) + + name := names[0] + copies := names[1:] + + tarball := fmt.Sprintf("%s.tar.gz", name) + localPath := filepath.Join(tmpDir, tarball) + if err = util.Tar(currentBase, localPath); err != nil { + logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) + return err + } + + s3Storage, err := s3service.FindDefaultS3() + if err != nil { + logger.Errorf("Failed to find default s3, err:%v", err) + return err + } + forcedPathStyle := true + if s3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err != nil { + logger.Errorf("Failed to get s3 client, err: %s", err) + return err + } + s3Path := filepath.Join(s3Storage.Subfolder, s3Base, tarball) + if err = client.Upload(s3Storage.Bucket, localPath, s3Path); err != nil { + logger.Errorf("Failed to upload file %s to s3, err: %s", localPath, err) + return err + } + + // copy file to avoid duplicated file transfer + for _, copyName := range copies { + targetPath := filepath.Join(s3Storage.Subfolder, s3Base, fmt.Sprintf("%s.tar.gz", copyName)) + err = client.CopyObject(s3Storage.Bucket, s3Path, targetPath) + if err != nil { + logger.Errorf("Failed to copy object from %s to %s", s3Path, targetPath) + return err + } + } + + return nil +} + // archiveAndUploadFiles archive local files and upload to default s3 storage // if multiple names appointed, s3storage.copy will be used to handle extra names func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Storage *s3service.S3, logger *zap.SugaredLogger) error { - if len(names) == 0 { return fmt.Errorf("names not appointed") } @@ -102,20 +157,17 @@ func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Stor } func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.SugaredLogger) error { - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 1")) tmpDir, err := os.MkdirTemp("", "") if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } defer os.RemoveAll(tmpDir) - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 2")) s3, err := s3service.FindDefaultS3() if err != nil { logger.Errorf("Failed to find default s3, err: %s", err) return err } - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 3")) tarball := fmt.Sprintf("%s.tar.gz", name) localPath := filepath.Join(tmpDir, tarball) s3Path := filepath.Join(s3.Subfolder, s3Base, tarball) @@ -124,23 +176,22 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if s3.Provider == setting.ProviderSourceAli { forcedPathStyle = false } - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 4")) + client, err := s3tool.NewClient(s3.Endpoint, s3.Ak, s3.Sk, s3.Insecure, forcedPathStyle) if err != nil { logger.Errorf("Failed to create s3 client, err: %s", err) return err } - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 5")) + if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return err } - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 6")) + if err = fsutil.Untar(localPath, localBase); err != nil { logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) return err } - fmt.Println(fmt.Sprintf(" DownloadAndExtractFilesFromS3: 7")) return nil } diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index c3387c28..9c646fdd 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -94,12 +94,12 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) } -func CopyAndUploadService(projectName, serviceName, currentChartPath string, copies []string, fileTree fs.FS) error { +func CopyAndUploadService(projectName, serviceName, currentChartPath string, copies []string) error { localBase := config.LocalServicePath(projectName, serviceName) s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.CopyAndUploadFiles(fileTree, names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) + return fsservice.CopyAndUploadFiles(names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index edb6ec9c..a015400f 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -475,7 +475,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr filePaths []string response = &BulkHelmServiceCreationResponse{} base string - chartTree = afero.NewMemMapFs() ) resByte, resByteErr := json.Marshal(args.CreateFrom) if resByteErr != nil { @@ -543,7 +542,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr }() // copy to latest dir and upload to s3 - if err = commonservice.CopyAndUploadService(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}, afero.NewIOFS(chartTree)); err != nil { + if err = commonservice.CopyAndUploadService(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) finalErr = e.ErrCreateTemplate.AddErr(err) return @@ -557,7 +556,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr } svc, err := createOrUpdateHelmService( - afero.NewIOFS(chartTree), + nil, &helmServiceCreationArgs{ ChartName: serviceName, ChartVersion: chartVersion, diff --git a/pkg/util/file.go b/pkg/util/file.go index 30144213..3878e0b6 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -17,8 +17,13 @@ limitations under the License. package util import ( + "archive/tar" + "compress/gzip" + "io" "io/ioutil" "os" + "path/filepath" + "strings" ) func GenerateTmpFile() (string, error) { @@ -60,3 +65,53 @@ func ReadFile(filename string) ([]byte, error) { } return contentByte, nil } + +func Tar(src, dst string) (err error) { + // 创建文件 + fw, err := os.Create(dst) + if err != nil { + return + } + defer fw.Close() + + gw := gzip.NewWriter(fw) + defer gw.Close() + + tw := tar.NewWriter(gw) + defer tw.Close() + + return filepath.Walk(src, func(fileName string, fi os.FileInfo, err error) error { + // 因为这个闭包会返回个 error ,所以先要处理一下这个 + if err != nil { + return err + } + + hdr, err := tar.FileInfoHeader(fi, "") + if err != nil { + return err + } + + hdr.Name = strings.TrimPrefix(fileName, string(filepath.Separator)) + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + if !fi.Mode().IsRegular() { + return nil + } + + fr, err := os.Open(fileName) + defer fr.Close() + if err != nil { + return err + } + + _, err = io.Copy(tw, fr) + if err != nil { + return err + } + + return nil + }) +} -- Gitee From 52dd8c78f8347901e623bbda842e7a195bd8abf2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 20:52:14 +0800 Subject: [PATCH 1672/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 1 - pkg/microservice/aslan/core/service/service/helm.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 9c646fdd..fa68a8f7 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -83,7 +83,6 @@ func DownloadServiceManifests(base, projectName, serviceName string) error { if err != nil { return err } - fmt.Println(fmt.Sprintf("DownloadAndExtractFilesFromS3 err: %v", err)) return nil } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index a015400f..f5d608fe 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1166,8 +1166,8 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) } + var fis []*types.FileInfo - fmt.Println(fmt.Sprintf("filepath.Join(base, serviceName, dir):", filepath.Join(base, serviceName, dir))) files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) -- Gitee From 6975752c351210d8564219d092ab9c7f18457c76 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 21:09:42 +0800 Subject: [PATCH 1673/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/file.go | 2 +- pkg/microservice/aslan/core/service/service/helm.go | 1 + pkg/util/file.go | 2 -- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index fb17f241..a14f0dfe 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -87,7 +87,7 @@ func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath stri }) wg.Start(func() { - err2 := ArchiveAndUploadLocalFilesToS3(names, currentChartPath, s3Base, logger) + err2 := ArchiveAndUploadLocalFilesToS3(names, localBase, s3Base, logger) if err2 != nil { logger.Errorf("Failed to upload files to s3, err: %s", err2) err = err2 diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index f5d608fe..b711473d 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1168,6 +1168,7 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s } var fis []*types.FileInfo + files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) diff --git a/pkg/util/file.go b/pkg/util/file.go index 3878e0b6..4f6da048 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -67,7 +67,6 @@ func ReadFile(filename string) ([]byte, error) { } func Tar(src, dst string) (err error) { - // 创建文件 fw, err := os.Create(dst) if err != nil { return @@ -81,7 +80,6 @@ func Tar(src, dst string) (err error) { defer tw.Close() return filepath.Walk(src, func(fileName string, fi os.FileInfo, err error) error { - // 因为这个闭包会返回个 error ,所以先要处理一下这个 if err != nil { return err } -- Gitee From 3f3e2138a63734b60283daa04b2b7ef69a93900c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 21:28:36 +0800 Subject: [PATCH 1674/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index b711473d..91df5134 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1171,7 +1171,10 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) if err != nil { - return nil, e.ErrFilePath.AddDesc(err.Error()) + files, err = os.ReadDir(filepath.Join(base, base, serviceName, dir)) + if err != nil { + return nil, e.ErrFilePath.AddDesc(err.Error()) + } } for _, file := range files { -- Gitee From 5cf3f66c375ecc3bf861f6b3cf1693d015395f5d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Feb 2022 21:39:17 +0800 Subject: [PATCH 1675/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 91df5134..42f73ce1 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -223,8 +223,11 @@ func GetFileContent(serviceName, productName string, param *GetFileContentParam, file := filepath.Join(base, serviceName, filePath, fileName) fileContent, err := os.ReadFile(file) if err != nil { - log.Errorf("Failed to read file %s, err: %s", file, err) - return "", e.ErrFileContent.AddDesc(err.Error()) + fileContent, err = os.ReadFile(filepath.Join(base, base, serviceName, filePath, fileName)) + if err != nil { + log.Errorf("Failed to read file %s, err: %s", file, err) + return "", e.ErrFileContent.AddDesc(err.Error()) + } } return string(fileContent), nil -- Gitee From 373477dbaeb727675556a79bd39b05cfd6cfb17f Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 25 Feb 2022 01:36:09 +0800 Subject: [PATCH 1676/2647] added enable_proxy for codehost Signed-off-by: Min Min --- .../systemconfig/core/codehost/repository/models/codehost.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index cbbc49bd..dfa8245b 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -32,6 +32,7 @@ type CodeHost struct { CreatedAt int64 `bson:"created_at" json:"created_at"` UpdatedAt int64 `bson:"updated_at" json:"updated_at"` DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` } func (CodeHost) TableName() string { -- Gitee From 626ed68e9f4578f3cc59ad9ae3ce04022a3b913f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 10:45:35 +0800 Subject: [PATCH 1677/2647] adapt helm chart webhook Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 2 + .../core/workflow/service/webhook/gerrit.go | 46 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 42f73ce1..8455bffc 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -223,6 +223,7 @@ func GetFileContent(serviceName, productName string, param *GetFileContentParam, file := filepath.Join(base, serviceName, filePath, fileName) fileContent, err := os.ReadFile(file) if err != nil { + // Adapt to gerrit scenarios fileContent, err = os.ReadFile(filepath.Join(base, base, serviceName, filePath, fileName)) if err != nil { log.Errorf("Failed to read file %s, err: %s", file, err) @@ -1174,6 +1175,7 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) if err != nil { + // Adapt to gerrit scenarios files, err = os.ReadDir(filepath.Join(base, base, serviceName, dir)) if err != nil { return nil, e.ErrFilePath.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index fa526c32..c92dc0b1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -35,6 +35,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" gerritservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -176,8 +177,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro log.Infof("Started to sync service template %s from gerrit %s", service.ServiceName, service.LoadPath) service.CreateBy = "system" service.Yaml = newYamlContent - err := SyncServiceTemplateFromGerrit(service, log) - if err != nil { + if err := SyncServiceTemplateFromGerrit(service, log); err != nil { errs = multierror.Append(errs, err) } } else { @@ -195,7 +195,6 @@ func GetGerritWorkspaceBasePath(repoName string) (string, error) { // GetGerritServiceTemplates Get all service templates maintained in gerrit func GetGerritServiceTemplates() ([]*commonmodels.Service, error) { opt := &commonrepo.ServiceListOption{ - Type: setting.K8SDeployType, Source: setting.SourceFromGerrit, } return commonrepo.NewServiceColl().ListMaxRevisions(opt) @@ -208,21 +207,27 @@ func SyncServiceTemplateFromGerrit(service *commonmodels.Service, log *zap.Sugar return fmt.Errorf("SyncServiceTemplateFromGerrit Service template is not from gerrit") } - //同步commit信息 + // Sync commit information if _, err := syncGerritLatestCommit(service); err != nil { log.Errorf("SyncServiceTemplateFromGerrit Sync change log from gerrit failed service %s, error: %v", service.ServiceName, err) return err } - // 在Ensure过程中会检查source,如果source为gerrit,则同步gerrit内容到service中 - if err := ensureServiceTmpl(setting.WebhookTaskCreator, service, log); err != nil { - log.Errorf("SyncServiceTemplateFromGerrit ensureServiceTmpl error: %+v", err) - return e.ErrValidateTemplate.AddDesc(err.Error()) + if service.Type == setting.K8SDeployType { + // During the Ensure process, the source will be checked. If the source is gerrit, the gerrit content will be synchronized to the service. + if err := ensureServiceTmpl(setting.WebhookTaskCreator, service, log); err != nil { + log.Errorf("SyncServiceTemplateFromGerrit ensureServiceTmpl error: %+v", err) + return e.ErrValidateTemplate.AddDesc(err.Error()) + } + // Update to database, revision+1 + if err := commonrepo.NewServiceColl().Create(service); err != nil { + log.Errorf("SyncServiceTemplateFromGerrit Failed to sync service %s from gerrit path %s error: %v", service.ServiceName, service.SrcPath, err) + return e.ErrCreateTemplate.AddDesc(err.Error()) + } + return nil } - // 更新到数据库,revision+1 - if err := commonrepo.NewServiceColl().Create(service); err != nil { - log.Errorf("SyncServiceTemplateFromGerrit Failed to sync service %s from gerrit path %s error: %v", service.ServiceName, service.SrcPath, err) - return e.ErrCreateTemplate.AddDesc(err.Error()) + if err := reloadServiceTmplFromGerrit(service, log); err != nil { + return err } log.Infof("End of sync service template %s from gerrit path %s", service.ServiceName, service.SrcPath) return nil @@ -318,3 +323,20 @@ func ensureServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sug return nil } + +func reloadServiceTmplFromGerrit(svc *commonmodels.Service, log *zap.SugaredLogger) error { + _, err := service.CreateOrUpdateHelmServiceFromGerrit(svc.ProductName, &service.HelmServiceCreationArgs{ + HelmLoadSource: service.HelmLoadSource{ + Source: service.LoadFromGerrit, + }, + CreatedBy: svc.CreateBy, + CreateFrom: &service.CreateFromRepo{ + CodehostID: svc.CodehostID, + Owner: svc.RepoOwner, + Repo: svc.RepoName, + Branch: svc.BranchName, + Paths: []string{svc.LoadPath}, + }, + }, log) + return err +} -- Gitee From 9616273280f7d1d7aecc37fa0caaa786b68f5467 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 25 Feb 2022 10:58:52 +0800 Subject: [PATCH 1678/2647] upodate code annotaions Signed-off-by: allenshen --- pkg/microservice/aslan/core/workflow/service/webhook/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index db05a482..ce6896d1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -151,7 +151,7 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar } log.Infof("find %d containers in service %s", len(args.Containers), args.ServiceName) - // 设置新的版本号 + // generate new revision serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, args.ServiceName, args.ProductName) rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) if err != nil { @@ -159,7 +159,7 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar } args.Revision = rev - // 更新到数据库,revision+1 + // update service template if err := commonrepo.NewServiceColl().Create(args); err != nil { log.Errorf("Failed to sync service %s from github path %s error: %v", args.ServiceName, args.SrcPath, err) return e.ErrCreateTemplate.AddDesc(err.Error()) -- Gitee From d88e5454c923728fa43ae8e4b0addcbe6ac8c3b8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 11:15:10 +0800 Subject: [PATCH 1679/2647] fix k8s schdule bug Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/k8s_cluster.go | 4 ++-- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 2 ++ .../warpdrive/core/service/types/task/config_payload.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 21043be5..4f2cbfc5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -46,9 +46,9 @@ type K8SCluster struct { } type K8SClusterResp struct { - ID string `json:"id" bson:"_id,omitempty"` + ID string `json:"id" bson:"id,omitempty"` Name string `json:"name" bson:"name"` - AdvancedConfig *AdvancedConfig `json:"config,omitempty" bson:"config,omitempty"` + AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty" bson:"advanced_config,omitempty"` } type K8SClusterInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 90f90eef..91327f94 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -934,6 +934,8 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.AdvancedConfig { for _, K8SCluster := range K8SClusters { + fmt.Println(fmt.Sprintf("K8SCluster:%+v", K8SCluster.Name)) + fmt.Println(fmt.Sprintf("K8SCluster.AdvancedConfig:%+v", K8SCluster.AdvancedConfig)) if K8SCluster.ID == clusterID { return K8SCluster.AdvancedConfig } diff --git a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go index aac740dc..eac009db 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/config_payload.go +++ b/pkg/microservice/warpdrive/core/service/types/task/config_payload.go @@ -162,7 +162,7 @@ type PrivateKey struct { } type K8SCluster struct { - ID string `json:"id,omitempty" bson:"_id,omitempty"` + ID string `json:"id,omitempty" bson:"id,omitempty"` Name string `json:"name" bson:"name"` AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty" bson:"advanced_config,omitempty"` } -- Gitee From c17aeede1a601b7457d17fd7e82b9e10cb7cd9c4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 11:33:01 +0800 Subject: [PATCH 1680/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 91327f94..90f90eef 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -934,8 +934,6 @@ func addNodeAffinity(clusterID string, K8SClusters []*task.K8SCluster) *corev1.A func findClusterConfig(clusterID string, K8SClusters []*task.K8SCluster) *task.AdvancedConfig { for _, K8SCluster := range K8SClusters { - fmt.Println(fmt.Sprintf("K8SCluster:%+v", K8SCluster.Name)) - fmt.Println(fmt.Sprintf("K8SCluster.AdvancedConfig:%+v", K8SCluster.AdvancedConfig)) if K8SCluster.ID == clusterID { return K8SCluster.AdvancedConfig } -- Gitee From 348e3f40dd136355ce0dabd5138e262c40d3ca5b Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 25 Feb 2022 11:56:18 +0800 Subject: [PATCH 1681/2647] debugs Signed-off-by: Min Min --- .../systemconfig/core/codehost/repository/mongodb/codehost.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index aad783d1..d84ec9e9 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -155,6 +155,7 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, "region": host.Region, "username": host.Username, "password": host.Password, + "enable_proxy": host.EnableProxy, "updated_at": time.Now().Unix(), }} _, err := c.Collection.UpdateOne(context.TODO(), query, change) -- Gitee From 0506542868747885cebfb77e89270df6e3ae5d09 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 13:34:26 +0800 Subject: [PATCH 1682/2647] reset Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 50 ++++++++++++++- .../internal/repository/models/oldpolicy.go | 5 ++ .../service/collaboration_instance.go | 20 +++--- .../core/label/repository/models/label.go | 14 ++--- .../core/label/repository/mongodb/label.go | 8 +-- .../picket/client/policy/rolebinding.go | 10 +-- .../picket/core/filter/service/rolebinding.go | 23 ++++--- .../policy/core/handler/policy.go | 12 ++-- .../policy/core/handler/policy_binding.go | 62 ------------------- pkg/microservice/policy/core/handler/role.go | 16 ++--- .../policy/core/repository/models/policy.go | 18 +++--- .../core/repository/models/policy_binding.go | 4 +- .../policy/core/repository/models/role.go | 8 +-- .../policy/core/service/common.go | 2 +- .../policy/core/service/policy.go | 4 +- .../policy/core/service/policy_binding.go | 6 +- pkg/microservice/policy/core/service/role.go | 6 +- pkg/setting/consts.go | 6 +- 18 files changed, 135 insertions(+), 139 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index c4a63b93..09017527 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -29,6 +29,7 @@ import ( "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/orm" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" "github.com/koderover/zadig/pkg/types" @@ -70,6 +71,44 @@ func V190ToV1100() error { return nil } +func addPresetRoleBindingSystemType() error { + ctx := context.Background() + + var res []*models.RoleBinding + cursor, err := newRoleBindingColl().Find(ctx, bson.M{}) + if err != nil { + log.Errorf("Failed to Find Policies, err: %s", err) + return err + } + + err = cursor.All(ctx, &res) + if err != nil { + return err + } + for _, v := range res { + if v.Namespace == "*" { + query := bson.M{"name": v.Name} + change := bson.M{"$set": bson.M{ + "type": setting.ResourceTypeSystem, + }} + _, err := newRoleBindingColl().UpdateOne(ctx, query, change) + if err != nil { + return err + } + } else { + query := bson.M{"name": v.Name} + change := bson.M{"$set": bson.M{ + "type": setting.ResourceTypeCustom, + }} + _, err := newRoleBindingColl().UpdateOne(ctx, query, change) + if err != nil { + return err + } + } + } + return nil +} + func addPresetRoleSystemType() error { ctx := context.Background() @@ -85,7 +124,7 @@ func addPresetRoleSystemType() error { return err } for _, v := range res { - if v.Name == "admin" || v.Name == "read-only" || v.Name == "project-admin" || v.Name == "contribute" || v.Name == "read-project-only" { + if v.Namespace == "*" || v.Namespace == "" { query := bson.M{"name": v.Name} change := bson.M{"$set": bson.M{ "type": "system", @@ -94,6 +133,15 @@ func addPresetRoleSystemType() error { if err != nil { return err } + } else { + query := bson.M{"name": v.Name} + change := bson.M{"$set": bson.M{ + "type": "custom", + }} + _, err := newRoleColl().UpdateOne(ctx, query, change) + if err != nil { + return err + } } } return nil diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 7709a4c5..1d384956 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -28,6 +28,11 @@ type Role struct { Namespace string `bson:"namespace" json:"namespace"` } +type RoleBinding struct { + Name string `baso:"name" json:"name"` + Namespace string `baso:"namespace" json:"namespace"` +} + type PolicyMetaRule struct { Action string `bson:"action" json:"action"` Alias string `bson:"alias" json:"alias"` diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 4486ddb7..d62caadf 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -475,7 +475,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, ProjectName: projectName, }) } @@ -484,7 +484,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, ProjectName: projectName, }) } @@ -495,7 +495,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, ProjectName: projectName, }) } @@ -504,7 +504,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, ProjectName: projectName, }) } @@ -513,7 +513,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, ProjectName: projectName, }) } @@ -522,7 +522,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, ProjectName: projectName, }) } @@ -534,7 +534,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, }) } for _, product := range instance.Products { @@ -542,7 +542,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, }) } } @@ -584,14 +584,14 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.New.Name), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, }) } for _, product := range item.UpdateSpec.Products { labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name), - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, }) } diff --git a/pkg/microservice/aslan/core/label/repository/models/label.go b/pkg/microservice/aslan/core/label/repository/models/label.go index f2dd41c9..ef9c0f92 100644 --- a/pkg/microservice/aslan/core/label/repository/models/label.go +++ b/pkg/microservice/aslan/core/label/repository/models/label.go @@ -23,13 +23,13 @@ import ( ) type Label struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type setting.ResourceGenerateType `bson:"type" json:"type"` - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - CreateBy string `bson:"create_by" json:"create_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` - ProjectName string `bson:"project_name" json:"project_name"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type setting.ResourceType `bson:"type" json:"type"` + Key string `bson:"key" json:"key"` + Value string `bson:"value" json:"value"` + CreateBy string `bson:"create_by" json:"create_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` + ProjectName string `bson:"project_name" json:"project_name"` } func (Label) TableName() string { diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index a2c6ad89..3f493657 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -141,10 +141,10 @@ func (c *LabelColl) Find(id string) (*models.Label, error) { } type Label struct { - Key string `json:"key"` - Value string `json:"value"` - Type setting.ResourceGenerateType `json:"type"` - ProjectName string `json:"project_name"` + Key string `json:"key"` + Value string `json:"value"` + Type setting.ResourceType `json:"type"` + ProjectName string `json:"project_name"` } type ListLabelOpt struct { diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go index 394d944d..4db89991 100644 --- a/pkg/microservice/picket/client/policy/rolebinding.go +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -20,6 +20,7 @@ import ( "net/http" "net/url" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -31,10 +32,11 @@ type RoleBinding struct { } type PolicyBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Policy string `json:"policy"` - Public bool `json:"public"` + Name string `json:"name"` + UID string `json:"uid"` + Policy string `json:"policy"` + Preset bool `json:"preset"` + Type setting.ResourceType `json:"type"` } func (c *Client) ListRoleBindings(header http.Header, qs url.Values) ([]*RoleBinding, error) { diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 8127a348..1cc300b9 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/picket/client/policy" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/user" ) @@ -31,20 +32,22 @@ const allUsers = "*" type roleBinding struct { *policy.RoleBinding - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` - IdentityType string `json:"identity_type"` - Account string `json:"account"` + Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` + Type setting.ResourceType `json:"type"` } type policyBinding struct { *policy.PolicyBinding - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` - IdentityType string `json:"identity_type"` - Account string `json:"account"` + Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` + Type setting.ResourceType `json:"type"` } type Binding struct { diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go index 52381557..08930e64 100644 --- a/pkg/microservice/policy/core/handler/policy.go +++ b/pkg/microservice/policy/core/handler/policy.go @@ -101,7 +101,7 @@ func UpdatePublicPolicy(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdatePolicy(service.PublicScope, args, ctx.Logger) + ctx.Err = service.UpdatePolicy(service.PresetScope, args, ctx.Logger) } func UpdateOrCreatePublicPolicy(c *gin.Context) { @@ -115,7 +115,7 @@ func UpdateOrCreatePublicPolicy(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateOrCreatePolicy(service.PublicScope, args, ctx.Logger) + ctx.Err = service.UpdateOrCreatePolicy(service.PresetScope, args, ctx.Logger) } func ListPolicies(c *gin.Context) { @@ -154,14 +154,14 @@ func CreatePublicPolicy(c *gin.Context) { return } - ctx.Err = service.CreatePolicy(service.PublicScope, args, ctx.Logger) + ctx.Err = service.CreatePolicy(service.PresetScope, args, ctx.Logger) } func ListPublicPolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListPolicies(service.PublicScope, ctx.Logger) + ctx.Resp, ctx.Err = service.ListPolicies(service.PresetScope, ctx.Logger) return } @@ -169,7 +169,7 @@ func GetPublicPolicy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetPolicy(service.PublicScope, c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetPolicy(service.PresetScope, c.Param("name"), ctx.Logger) } func DeletePolicy(c *gin.Context) { @@ -209,7 +209,7 @@ func DeletePublicPolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - ctx.Err = service.DeletePolicy(name, service.PublicScope, ctx.Logger) + ctx.Err = service.DeletePolicy(name, service.PresetScope, ctx.Logger) return } diff --git a/pkg/microservice/policy/core/handler/policy_binding.go b/pkg/microservice/policy/core/handler/policy_binding.go index 82d08a62..2fd0e172 100644 --- a/pkg/microservice/policy/core/handler/policy_binding.go +++ b/pkg/microservice/policy/core/handler/policy_binding.go @@ -106,65 +106,3 @@ func DeletePolicyBindings(c *gin.Context) { ctx.Err = service.DeletePolicyBindings(args.Names, projectName, userID, ctx.Logger) } - -func DeleteSystemPolicyBinding(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - name := c.Param("name") - ctx.Err = service.DeletePolicyBinding(name, service.SystemScope, ctx.Logger) -} - -func CreateSystemPolicyBinding(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := &service.PolicyBinding{} - if err := c.ShouldBindJSON(args); err != nil { - ctx.Err = err - return - } - - args.Public = false - - ctx.Err = service.CreatePolicyBindings(service.SystemScope, []*service.PolicyBinding{args}, ctx.Logger) -} - -func CreateOrUpdateSystemPolicyBinding(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := &service.PolicyBinding{} - if err := c.ShouldBindJSON(args); err != nil { - ctx.Err = err - return - } - - args.Public = false - - ctx.Err = service.CreateOrUpdateSystemPolicyBinding(service.SystemScope, args, ctx.Logger) -} - -func ListSystemPolicyBindings(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.ListPolicyBindings(service.SystemScope, "", ctx.Logger) -} - -func ListUserPolicyBindings(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - uid := c.Query("uid") - if uid == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("uid is empty") - return - } - projectName := c.Query("projectName") - if projectName == "" { - projectName = service.SystemScope - } - - ctx.Resp, ctx.Err = service.ListPolicyBindings(projectName, uid, ctx.Logger) -} diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index f1201b1b..62338402 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -100,7 +100,7 @@ func UpdatePresetRole(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateRole(service.PublicScope, args, ctx.Logger) + ctx.Err = service.UpdateRole(service.PresetScope, args, ctx.Logger) } func UpdateOrCreatePresetRole(c *gin.Context) { @@ -114,7 +114,7 @@ func UpdateOrCreatePresetRole(c *gin.Context) { } name := c.Param("name") args.Name = name - ctx.Err = service.UpdateOrCreateRole(service.PublicScope, args, ctx.Logger) + ctx.Err = service.UpdateOrCreateRole(service.PresetScope, args, ctx.Logger) } func ListRoles(c *gin.Context) { @@ -152,15 +152,15 @@ func CreatePresetRole(c *gin.Context) { ctx.Err = err return } - args.Type = setting.ResourceGenerateTypeSystem - ctx.Err = service.CreateRole(service.PublicScope, args, ctx.Logger) + args.Type = setting.ResourceTypeSystem + ctx.Err = service.CreateRole(service.PresetScope, args, ctx.Logger) } func ListPresetRoles(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListRoles(service.PublicScope, ctx.Logger) + ctx.Resp, ctx.Err = service.ListRoles(service.PresetScope, ctx.Logger) return } @@ -168,7 +168,7 @@ func GetPresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetRole(service.PublicScope, c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetRole(service.PresetScope, c.Param("name"), ctx.Logger) } func DeleteRole(c *gin.Context) { @@ -208,7 +208,7 @@ func DeletePresetRole(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() name := c.Param("name") - ctx.Err = service.DeleteRole(name, service.PublicScope, ctx.Logger) + ctx.Err = service.DeleteRole(name, service.PresetScope, ctx.Logger) return } @@ -221,7 +221,7 @@ func CreateSystemRole(c *gin.Context) { ctx.Err = err return } - args.Type = setting.ResourceGenerateTypeSystem + args.Type = setting.ResourceTypeSystem ctx.Err = service.CreateRole(service.SystemScope, args, ctx.Logger) } diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index 6e08d9ba..482c9c22 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -23,15 +23,15 @@ import ( // Policy is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a PolicyBinding. // for a cluster scoped Policy, namespace is empty. type Policy struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Description string `json:"description" json:"description"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - Rules []*Rule `bson:"rules" json:"rules"` - CreateBy string `bson:"create_by" json:"create_by"` - UpdateBy string `bson:"update_by" json:"update_by"` - Type setting.ResourceGenerateType `bson:"type" json:"type"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Description string `json:"description" json:"description"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + Rules []*Rule `bson:"rules" json:"rules"` + CreateBy string `bson:"create_by" json:"create_by"` + UpdateBy string `bson:"update_by" json:"update_by"` + Type setting.ResourceType `bson:"type" json:"type"` } func (Policy) TableName() string { diff --git a/pkg/microservice/policy/core/repository/models/policy_binding.go b/pkg/microservice/policy/core/repository/models/policy_binding.go index 5cdf026e..d95a7796 100644 --- a/pkg/microservice/policy/core/repository/models/policy_binding.go +++ b/pkg/microservice/policy/core/repository/models/policy_binding.go @@ -31,8 +31,8 @@ type PolicyBinding struct { Subjects []*Subject `bson:"subjects" json:"subjects"` // PolicyRef can reference a namespaced or cluster scoped Policy. - PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` - Type setting.ResourceGenerateType `bson:"type" json:"type"` + PolicyRef *PolicyRef `bson:"policy_ref" json:"policy_ref"` + Type setting.ResourceType `bson:"type" json:"type"` } // PolicyRef contains information that points to the policy being used diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 8f585473..525f8336 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -23,10 +23,10 @@ import ( // Role is a namespaced or cluster scoped, logical grouping of PolicyRules that can be referenced as a unit by a RoleBinding. // for a cluster scoped Role, namespace is empty. type Role struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` - Rules []*Rule `bson:"rules" json:"rules"` - Type setting.ResourceGenerateType `bson:"type" json:"type"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Rules []*Rule `bson:"rules" json:"rules"` + Type setting.ResourceType `bson:"type" json:"type"` } func (Role) TableName() string { diff --git a/pkg/microservice/policy/core/service/common.go b/pkg/microservice/policy/core/service/common.go index d068de13..8381d540 100644 --- a/pkg/microservice/policy/core/service/common.go +++ b/pkg/microservice/policy/core/service/common.go @@ -13,4 +13,4 @@ type Rule struct { } const SystemScope = "*" -const PublicScope = "" +const PresetScope = "" diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index da10675c..f67a4aed 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -44,7 +44,7 @@ func CreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, } for _, r := range policy.Rules { @@ -64,7 +64,7 @@ func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ Name: policy.Name, Namespace: ns, - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, } for _, r := range policy.Rules { diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index 15b20d6d..27e8a509 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -30,7 +30,7 @@ type PolicyBinding struct { Name string `json:"name"` UID string `json:"uid"` Policy string `json:"policy"` - Public bool `json:"public"` + Preset bool `json:"preset"` } func CreatePolicyBindings(ns string, rbs []*PolicyBinding, logger *zap.SugaredLogger) error { @@ -121,7 +121,7 @@ func DeletePolicyBindings(names []string, projectName string, userID string, _ * func createPolicyBindingObject(ns string, rb *PolicyBinding, logger *zap.SugaredLogger) (*models.PolicyBinding, error) { nsPolicy := ns - if rb.Public { + if rb.Preset { nsPolicy = "" } policy, found, err := mongodb.NewPolicyColl().Get(nsPolicy, rb.Policy) @@ -141,6 +141,6 @@ func createPolicyBindingObject(ns string, rb *PolicyBinding, logger *zap.Sugared Name: policy.Name, Namespace: policy.Namespace, }, - Type: setting.ResourceGenerateTypeSystem, + Type: setting.ResourceTypeSystem, }, nil } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 2fbcc95c..1acdf687 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -27,9 +27,9 @@ import ( ) type Role struct { - Name string `json:"name"` - Rules []*Rule `json:"rules,omitempty"` - Type setting.ResourceGenerateType `json:"type,omitempty"` + Name string `json:"name"` + Rules []*Rule `json:"rules,omitempty"` + Type setting.ResourceType `json:"type,omitempty"` } func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index bf15653e..8adb3fab 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -613,11 +613,11 @@ const ( InformerNamingConvention = "%s-%s" ) -type ResourceGenerateType string +type ResourceType string const ( - ResourceGenerateTypeSystem ResourceGenerateType = "system" - ResourceGenerateTypeUser ResourceGenerateType = "user" + ResourceTypeSystem ResourceType = "system" + ResourceTypeCustom ResourceType = "custom" ) // AttachedClusterNamespace is the namespace Zadig uses in attached cluster. -- Gitee From 6a708c36db8ea7a16da6c883c90656147c2e82ca Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 25 Feb 2022 13:40:30 +0800 Subject: [PATCH 1683/2647] fix panic bug when query commits of gitlab Signed-off-by: allenshen --- .../aslan/core/service/service/helm.go | 10 ++++++++++ .../core/workflow/service/webhook/utils.go | 17 ++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 154145f6..1399a413 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -930,6 +930,16 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } + // update status of current service template to deleting + if currentSvcTmpl != nil { + err = commonrepo.NewServiceColl().UpdateStatus(args.ServiceName, args.ProductName, setting.ProductStatusDeleting) + if err != nil { + log.Errorf("Failed to set status of current service templates, serviceName: %s, err: %s", args.ServiceName, err) + return nil, err + } + } + + // create new service template if err = commonrepo.NewServiceColl().Create(serviceObj); err != nil { log.Errorf("Failed to create service %s error: %s", args.ServiceName, err) return nil, err diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index ce6896d1..34907a06 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -202,14 +202,25 @@ func syncLatestCommit(service *commonmodels.Service) error { return err } + if len(service.BranchName) > 0 { + branch = service.BranchName + } + if len(service.LoadPath) > 0 { + path = service.LoadPath + } + commit, err := GitlabGetLatestCommit(client, owner, repo, branch, path) if err != nil { return err } - service.Commit = &commonmodels.Commit{ - SHA: commit.ID, - Message: commit.Message, + + if commit != nil { + service.Commit = &commonmodels.Commit{ + SHA: commit.ID, + Message: commit.Message, + } } + return nil } -- Gitee From e6a59dada73e27d5f9fdf4154e0f0b8b92240cce Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 13:56:06 +0800 Subject: [PATCH 1684/2647] reset Signed-off-by: mouuii --- pkg/microservice/policy/core/handler/router.go | 14 +++++++------- .../policy/core/service/policy_binding.go | 4 ++-- .../policy/core/service/role_binding.go | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 4c8d253d..4d45d2dd 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -45,14 +45,14 @@ func (*Router) Inject(router *gin.RouterGroup) { policies.DELETE("/:name", DeletePolicy) } - publicRoles := router.Group("preset-roles") + presetRoles := router.Group("preset-roles") { - publicRoles.POST("", CreatePresetRole) - publicRoles.GET("", ListPresetRoles) - publicRoles.GET("/:name", GetPresetRole) - publicRoles.PATCH("/:name", UpdatePresetRole) - publicRoles.PUT("/:name", UpdateOrCreatePresetRole) - publicRoles.DELETE("/:name", DeletePresetRole) + presetRoles.POST("", CreatePresetRole) + presetRoles.GET("", ListPresetRoles) + presetRoles.GET("/:name", GetPresetRole) + presetRoles.PATCH("/:name", UpdatePresetRole) + presetRoles.PUT("/:name", UpdateOrCreatePresetRole) + presetRoles.DELETE("/:name", DeletePresetRole) } systemRoles := router.Group("system-roles") diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index 27e8a509..7236546d 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -75,7 +75,7 @@ func ListPolicyBindings(ns, uid string, _ *zap.SugaredLogger) ([]*PolicyBinding, Name: v.Name, Policy: v.PolicyRef.Name, UID: v.Subjects[0].UID, - Public: v.PolicyRef.Namespace == "", + Preset: v.PolicyRef.Namespace == "", }) } @@ -99,7 +99,7 @@ func ListPolicyBindingsByPolicy(ns, policyName string, publicPolicy bool, _ *zap Name: v.Name, Policy: v.PolicyRef.Name, UID: v.Subjects[0].UID, - Public: v.PolicyRef.Namespace == "", + Preset: v.PolicyRef.Namespace == "", }) } diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 22850db7..2cdf6453 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -31,7 +31,7 @@ type RoleBinding struct { Name string `json:"name"` UID string `json:"uid"` Role string `json:"role"` - Public bool `json:"public"` + Preset bool `json:"preset"` } func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger) error { @@ -77,7 +77,7 @@ func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, err Name: v.Name, Role: v.RoleRef.Name, UID: v.Subjects[0].UID, - Public: v.RoleRef.Namespace == "", + Preset: v.RoleRef.Namespace == "", }) } -- Gitee From 2d75a6a243b2752587a978429da7642f23741f97 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 14:04:00 +0800 Subject: [PATCH 1685/2647] optimize upload file code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 3 +- .../aslan/core/common/service/fs/s3.go | 109 +++++++++--------- .../aslan/core/service/service/helm.go | 14 +-- 3 files changed, 59 insertions(+), 67 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index a14f0dfe..5b2c7271 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -87,7 +87,8 @@ func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath stri }) wg.Start(func() { - err2 := ArchiveAndUploadLocalFilesToS3(names, localBase, s3Base, logger) + fileTree := os.DirFS(currentChartPath) + err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) if err2 != nil { logger.Errorf("Failed to upload files to s3, err: %s", err2) err = err2 diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index f86b7d64..bccbc33e 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -27,7 +27,6 @@ import ( s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" - "github.com/koderover/zadig/pkg/util" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -49,60 +48,60 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, lo return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) } -func ArchiveAndUploadLocalFilesToS3(names []string, currentBase, s3Base string, logger *zap.SugaredLogger) error { - if len(names) == 0 { - return fmt.Errorf("names not appointed") - } - - tmpDir, err := os.MkdirTemp("", "") - if err != nil { - logger.Errorf("Failed to create temp dir, err: %s", err) - return err - } - defer os.RemoveAll(tmpDir) - - name := names[0] - copies := names[1:] - - tarball := fmt.Sprintf("%s.tar.gz", name) - localPath := filepath.Join(tmpDir, tarball) - if err = util.Tar(currentBase, localPath); err != nil { - logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) - return err - } - - s3Storage, err := s3service.FindDefaultS3() - if err != nil { - logger.Errorf("Failed to find default s3, err:%v", err) - return err - } - forcedPathStyle := true - if s3Storage.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err != nil { - logger.Errorf("Failed to get s3 client, err: %s", err) - return err - } - s3Path := filepath.Join(s3Storage.Subfolder, s3Base, tarball) - if err = client.Upload(s3Storage.Bucket, localPath, s3Path); err != nil { - logger.Errorf("Failed to upload file %s to s3, err: %s", localPath, err) - return err - } - - // copy file to avoid duplicated file transfer - for _, copyName := range copies { - targetPath := filepath.Join(s3Storage.Subfolder, s3Base, fmt.Sprintf("%s.tar.gz", copyName)) - err = client.CopyObject(s3Storage.Bucket, s3Path, targetPath) - if err != nil { - logger.Errorf("Failed to copy object from %s to %s", s3Path, targetPath) - return err - } - } - - return nil -} +//func ArchiveAndUploadLocalFilesToS3(names []string, currentBase, s3Base string, logger *zap.SugaredLogger) error { +// if len(names) == 0 { +// return fmt.Errorf("names not appointed") +// } +// +// tmpDir, err := os.MkdirTemp("", "") +// if err != nil { +// logger.Errorf("Failed to create temp dir, err: %s", err) +// return err +// } +// defer os.RemoveAll(tmpDir) +// +// name := names[0] +// copies := names[1:] +// +// tarball := fmt.Sprintf("%s.tar.gz", name) +// localPath := filepath.Join(tmpDir, tarball) +// if err = util.Tar(currentBase, localPath); err != nil { +// logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) +// return err +// } +// +// s3Storage, err := s3service.FindDefaultS3() +// if err != nil { +// logger.Errorf("Failed to find default s3, err:%v", err) +// return err +// } +// forcedPathStyle := true +// if s3Storage.Provider == setting.ProviderSourceAli { +// forcedPathStyle = false +// } +// client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) +// if err != nil { +// logger.Errorf("Failed to get s3 client, err: %s", err) +// return err +// } +// s3Path := filepath.Join(s3Storage.Subfolder, s3Base, tarball) +// if err = client.Upload(s3Storage.Bucket, localPath, s3Path); err != nil { +// logger.Errorf("Failed to upload file %s to s3, err: %s", localPath, err) +// return err +// } +// +// // copy file to avoid duplicated file transfer +// for _, copyName := range copies { +// targetPath := filepath.Join(s3Storage.Subfolder, s3Base, fmt.Sprintf("%s.tar.gz", copyName)) +// err = client.CopyObject(s3Storage.Bucket, s3Path, targetPath) +// if err != nil { +// logger.Errorf("Failed to copy object from %s to %s", s3Path, targetPath) +// return err +// } +// } +// +// return nil +//} // archiveAndUploadFiles archive local files and upload to default s3 storage // if multiple names appointed, s3storage.copy will be used to handle extra names diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 8455bffc..b711473d 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -223,12 +223,8 @@ func GetFileContent(serviceName, productName string, param *GetFileContentParam, file := filepath.Join(base, serviceName, filePath, fileName) fileContent, err := os.ReadFile(file) if err != nil { - // Adapt to gerrit scenarios - fileContent, err = os.ReadFile(filepath.Join(base, base, serviceName, filePath, fileName)) - if err != nil { - log.Errorf("Failed to read file %s, err: %s", file, err) - return "", e.ErrFileContent.AddDesc(err.Error()) - } + log.Errorf("Failed to read file %s, err: %s", file, err) + return "", e.ErrFileContent.AddDesc(err.Error()) } return string(fileContent), nil @@ -1175,11 +1171,7 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s files, err := os.ReadDir(filepath.Join(base, serviceName, dir)) if err != nil { - // Adapt to gerrit scenarios - files, err = os.ReadDir(filepath.Join(base, base, serviceName, dir)) - if err != nil { - return nil, e.ErrFilePath.AddDesc(err.Error()) - } + return nil, e.ErrFilePath.AddDesc(err.Error()) } for _, file := range files { -- Gitee From b8c7b2d3cfeee0236bd4da3beacda3effaccbc18 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 14:39:08 +0800 Subject: [PATCH 1686/2647] add select Signed-off-by: mouuii --- .../policy/core/handler/role_binding.go | 4 ++-- pkg/microservice/policy/core/service/role.go | 15 +++++++++------ .../policy/core/service/role_binding.go | 4 ++-- pkg/setting/consts.go | 9 +++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index 5d87c484..e69b7d5d 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -156,7 +156,7 @@ func CreateSystemRoleBinding(c *gin.Context) { return } - args.Public = false + args.Preset = false ctx.Err = service.CreateRoleBindings(service.SystemScope, []*service.RoleBinding{args}, ctx.Logger) } @@ -171,7 +171,7 @@ func CreateOrUpdateSystemRoleBinding(c *gin.Context) { return } - args.Public = false + args.Preset = false ctx.Err = service.CreateOrUpdateSystemRoleBinding(service.SystemScope, args, ctx.Logger) } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 1acdf687..fa3afb6b 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -27,9 +27,10 @@ import ( ) type Role struct { - Name string `json:"name"` - Rules []*Rule `json:"rules,omitempty"` - Type setting.ResourceType `json:"type,omitempty"` + Name string `json:"name"` + Rules []*Rule `json:"rules,omitempty"` + Type setting.ResourceType `json:"type,omitempty"` + Select bool `json:"select,omitempty"` } func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { @@ -95,9 +96,11 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { if v.Name == string(setting.Contributor) { continue } - roles = append(roles, &Role{ - Name: v.Name, - }) + tmpRole := Role{Select: false} + if v.Name == string(setting.ReadProjectOnly) { + tmpRole.Select = true + } + roles = append(roles, &tmpRole) } return roles, nil } diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 2cdf6453..a5e723aa 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -99,7 +99,7 @@ func DeleteRoleBindings(names []string, projectName string, userID string, _ *za func createRoleBindingObject(ns string, rb *RoleBinding, logger *zap.SugaredLogger) (*models.RoleBinding, error) { nsRole := ns - if rb.Public { + if rb.Preset { nsRole = "" } role, found, err := mongodb.NewRoleColl().Get(nsRole, rb.Role) @@ -130,7 +130,7 @@ func ensureRoleBindingName(ns string, rb *RoleBinding) { } nsRole := ns - if rb.Public { + if rb.Preset { nsRole = "" } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 8adb3fab..0b0aab71 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -562,10 +562,11 @@ const ChartTemplatesPath = "charts" type RoleType string const ( - Contributor RoleType = "contributor" - ReadOnly RoleType = "read-only" - ProjectAdmin RoleType = "project-admin" - SystemAdmin RoleType = "admin" + Contributor RoleType = "contributor" + ReadOnly RoleType = "read-only" + ProjectAdmin RoleType = "project-admin" + SystemAdmin RoleType = "admin" + ReadProjectOnly RoleType = "read-project-only" ) // ModernWorkflowType 自由编排工作流 -- Gitee From f04b49d7b1e54b7288249c6aacf57d8bf3d7028d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 14:45:16 +0800 Subject: [PATCH 1687/2647] download file from source Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/s3.go | 2 +- .../aslan/core/common/service/helm.go | 46 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index bccbc33e..1a93b2d2 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -160,7 +160,7 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } - defer os.RemoveAll(tmpDir) + //defer os.RemoveAll(tmpDir) s3, err := s3service.FindDefaultS3() if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index fa68a8f7..49e02848 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -17,10 +17,15 @@ limitations under the License. package service import ( - "fmt" "io/fs" + "os" "path" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + + "github.com/koderover/zadig/pkg/setting" + "github.com/27149chen/afero" "go.uber.org/zap" @@ -57,8 +62,12 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { } log.Warnf("Failed to download service from s3, err: %s", err) - - return preLoadServiceManifestsFromSource(svc) + switch svc.Source { + case setting.SourceFromGerrit: + return preLoadServiceManifestsFromGerrit(svc) + default: + return preLoadServiceManifestsFromSource(svc) + } } func PreloadServiceManifestsByRevision(base string, svc *commonmodels.Service) error { @@ -79,11 +88,8 @@ func PreloadServiceManifestsByRevision(base string, svc *commonmodels.Service) e func DownloadServiceManifests(base, projectName, serviceName string) error { s3Base := config.ObjectStorageServicePath(projectName, serviceName) - err := fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) - if err != nil { - return err - } - return nil + + return fsservice.DownloadAndExtractFilesFromS3(serviceName, base, s3Base, log.SugaredLogger()) } func SaveAndUploadService(projectName, serviceName string, copies []string, fileTree fs.FS) error { @@ -102,7 +108,6 @@ func CopyAndUploadService(projectName, serviceName, currentChartPath string, cop } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { - fmt.Println(fmt.Sprintf("preLoadServiceManifestsFromSource start ...")) tree, err := fsservice.DownloadFilesFromSource( &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName, RepoLink: svc.SrcPath}, func(afero.Fs) (string, error) { @@ -120,3 +125,26 @@ func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { return nil } + +func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { + base := path.Join(config.S3StoragePath(), svc.GerritRepoName) + if err := os.RemoveAll(base); err != nil { + log.Errorf("Failed to remove dir, err:%s", err) + } + detail, err := systemconfig.New().GetCodeHost(svc.GerritCodeHostID) + if err != nil { + log.Errorf("Failed to GetCodehostDetail, err:%s", err) + return err + } + err = command.RunGitCmds(detail, "default", svc.GerritRepoName, svc.GerritBranchName, svc.GerritRemoteName) + if err != nil { + log.Errorf("Failed to runGitCmds, err:%s", err) + return err + } + // save files to disk and upload them to s3 + if err := CopyAndUploadService(svc.ProductName, svc.ServiceName, svc.GerritPath, nil); err != nil { + log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", svc.ServiceName, svc.ProductName, err) + return err + } + return nil +} -- Gitee From 755bfdbe1547ca2aeb12c5a95b3d4720a93d5368 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 14:59:46 +0800 Subject: [PATCH 1688/2647] create rolebindings Signed-off-by: mouuii --- .../policy/core/handler/role_binding.go | 15 +++++++-------- pkg/microservice/policy/core/handler/router.go | 2 +- pkg/microservice/policy/core/service/role.go | 11 +++++++---- .../policy/core/service/role_binding.go | 14 ++++++++++++++ 4 files changed, 29 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index e69b7d5d..fc445ba1 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -115,7 +115,7 @@ func DeleteRoleBindings(c *gin.Context) { ctx.Err = service.DeleteRoleBindings(args.Names, projectName, userID, ctx.Logger) } -func RecreateRoleBindings(c *gin.Context) { +func UpdateRoleBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -124,18 +124,17 @@ func RecreateRoleBindings(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("projectName is empty") return } - - args := make([]*service.RoleBinding, 0) - if err := c.ShouldBindJSON(&args); err != nil { - ctx.Err = err + userID := c.Query("userID") + if userID == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("userID is empty") return } - err := service.DeleteRoleBindings([]string{"*"}, projectName, c.Query("userID"), ctx.Logger) - if err != nil { + args := make([]*service.RoleBinding, 0) + if err := c.ShouldBindJSON(&args); err != nil { ctx.Err = err return } - ctx.Err = service.CreateRoleBindings(projectName, args, ctx.Logger) + ctx.Err = service.UpdateRoleBindings(projectName, args, c.Query("userID"), ctx.Logger) } func DeleteSystemRoleBinding(c *gin.Context) { diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 4d45d2dd..6e14b362 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -70,7 +70,7 @@ func (*Router) Inject(router *gin.RouterGroup) { roleBindings.GET("", ListRoleBindings) roleBindings.DELETE("/:name", DeleteRoleBinding) roleBindings.POST("/bulk-delete", DeleteRoleBindings) - roleBindings.POST("/recreate", RecreateRoleBindings) + roleBindings.POST("/update", UpdateRoleBindings) } policyBindings := router.Group("policybindings") diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index fa3afb6b..73994609 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -27,10 +27,13 @@ import ( ) type Role struct { - Name string `json:"name"` - Rules []*Rule `json:"rules,omitempty"` - Type setting.ResourceType `json:"type,omitempty"` - Select bool `json:"select,omitempty"` + Name string `json:"name"` + Rules []*Rule `json:"rules,omitempty"` + // if the role is created by user , set the type to "custom" + // if the role is created by system , set the type to "system" + Type setting.ResourceType `json:"type,omitempty"` + // frontend default select flag + Select bool `json:"select,omitempty"` } func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index a5e723aa..bda9d100 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -48,6 +48,20 @@ func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger return mongodb.NewRoleBindingColl().BulkCreate(objs) } +func UpdateRoleBindings(ns string, rbs []*RoleBinding, userID string, logger *zap.SugaredLogger) error { + err := DeleteRoleBindings([]string{"*"}, ns, userID, logger) + if err != nil { + logger.Errorf("delete rolebindings err:%s,ns:%s,userID:%s", err, ns, userID) + return err + } + err = CreateRoleBindings(ns, rbs, logger) + if err != nil { + logger.Errorf("create rolebings err:%s,ns:%s,userID:%s", err, ns, userID) + return err + } + return nil +} + func CreateOrUpdateSystemRoleBinding(ns string, rb *RoleBinding, logger *zap.SugaredLogger) error { obj, err := createRoleBindingObject(ns, rb, logger) -- Gitee From c5c39429d9fcd06692584a5f34cbe77e7dfd9101 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 15:01:42 +0800 Subject: [PATCH 1689/2647] optimize cdoe Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index b711473d..d16b9779 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1098,6 +1098,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg serviceObj.GerritCodeHostID = args.GerritCodeHostID serviceObj.GerritRepoName = args.GerritRepoName serviceObj.GerritBranchName = args.GerritBranchName + serviceObj.GerritRemoteName = args.GerritRemoteName default: serviceObj.CreateFrom = geneCreationDetail(args) } -- Gitee From a4700019e89e2524b3288a1ddcce4c60da7150d5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 15:08:51 +0800 Subject: [PATCH 1690/2647] modify branch value Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d16b9779..47bb5225 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -574,7 +574,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr GerritCodeHostID: createFromRepo.CodehostID, GerritPath: currentFilePath, GerritRepoName: createFromRepo.Repo, - GerritBranchName: createFromRepo.Repo, + GerritBranchName: createFromRepo.Branch, GerritRemoteName: "origin", }, log, -- Gitee From c032a4a9285c454680ef27d98dbf5e03c998d4bb Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 15:29:30 +0800 Subject: [PATCH 1691/2647] add user infog Signed-off-by: mouuii --- .../picket/core/filter/service/rolebinding.go | 18 ++++++++++++------ pkg/microservice/policy/core/service/role.go | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 1cc300b9..6ee5b5ec 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -51,10 +51,13 @@ type policyBinding struct { } type Binding struct { - Roles []*roleBinding `json:"roles"` - Policies []*policyBinding `json:"policies"` - UserName string `json:"user_name"` - Email string `json:"email"` + Roles []*roleBinding `json:"roles"` + Policies []*policyBinding `json:"policies"` + UserName string `json:"username"` + Email string `json:"email"` + Account string `json:"account"` + IdentityType string `json:"identity_type"` + Phone string `json:"phone"` } func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*Binding, error) { @@ -98,8 +101,11 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) var policyBindings []*policyBinding for _, u := range users { binding := &Binding{ - UserName: u.Name, - Email: u.Email, + UserName: u.Name, + Email: u.Email, + IdentityType: u.IdentityType, + Account: u.Account, + Phone: u.Phone, } if rb, ok := uidToRoleBinding[u.UID]; ok { for _, r := range rb { diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index 73994609..a4e8399b 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -99,7 +99,7 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { if v.Name == string(setting.Contributor) { continue } - tmpRole := Role{Select: false} + tmpRole := Role{Select: false, Name: v.Name, Type: v.Type} if v.Name == string(setting.ReadProjectOnly) { tmpRole.Select = true } -- Gitee From 2b1a07dd526c52ed75c640df89d4b113c837e8e2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 15:34:09 +0800 Subject: [PATCH 1692/2647] add uid Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/rolebinding.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 6ee5b5ec..01473e17 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -58,6 +58,7 @@ type Binding struct { Account string `json:"account"` IdentityType string `json:"identity_type"` Phone string `json:"phone"` + Uid string `json:"uid"` } func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) ([]*Binding, error) { @@ -106,6 +107,7 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) IdentityType: u.IdentityType, Account: u.Account, Phone: u.Phone, + Uid: u.UID, } if rb, ok := uidToRoleBinding[u.UID]; ok { for _, r := range rb { @@ -138,6 +140,7 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) Policies: uidToPolicyBindings[allUsers], UserName: "*", Email: "", + Uid: "*", } res = append(res, AllUserBinding) return res, nil -- Gitee From f93daaf15ef041a08ebab2c2803415ad3ababc00 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 15:40:16 +0800 Subject: [PATCH 1693/2647] optimize uploading file logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 2 +- pkg/microservice/aslan/core/common/service/helm.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 1a93b2d2..bccbc33e 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -160,7 +160,7 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } - //defer os.RemoveAll(tmpDir) + defer os.RemoveAll(tmpDir) s3, err := s3service.FindDefaultS3() if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 49e02848..c38eeada 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -104,7 +104,7 @@ func CopyAndUploadService(projectName, serviceName, currentChartPath string, cop s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.CopyAndUploadFiles(names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) + return fsservice.CopyAndUploadFiles(names, localBase, s3Base, currentChartPath, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { -- Gitee From d76b61a2529fd360e039843247f08113472dcd38 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 16:07:12 +0800 Subject: [PATCH 1694/2647] modify file path Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index c38eeada..49e02848 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -104,7 +104,7 @@ func CopyAndUploadService(projectName, serviceName, currentChartPath string, cop s3Base := config.ObjectStorageServicePath(projectName, serviceName) names := append([]string{serviceName}, copies...) - return fsservice.CopyAndUploadFiles(names, localBase, s3Base, currentChartPath, log.SugaredLogger()) + return fsservice.CopyAndUploadFiles(names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { -- Gitee From 6a2ba744fbed24e26a28dea1fb1476f3e414625c Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 16:18:59 +0800 Subject: [PATCH 1695/2647] system add rolebinding Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 1 + pkg/microservice/policy/core/service/role_binding.go | 9 +++++---- pkg/shared/client/policy/role.go | 10 ++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 528cc0d9..535f78d7 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -195,6 +195,7 @@ func presetRoleBinding(uid string) error { Name: config.RoleBindingNameFromUIDAndRole(uid, setting.SystemAdmin, "*"), UID: uid, Role: string(setting.SystemAdmin), + Type: setting.ResourceTypeSystem, }) } diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index bda9d100..68f58916 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -28,10 +28,11 @@ import ( ) type RoleBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Role string `json:"role"` - Preset bool `json:"preset"` + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Preset bool `json:"preset"` + Type setting.ResourceType `json:"type"` } func CreateRoleBindings(ns string, rbs []*RoleBinding, logger *zap.SugaredLogger) error { diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index b7f22edb..4e7988e5 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -19,6 +19,7 @@ package policy import ( "fmt" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -50,10 +51,11 @@ type Attribute struct { } type RoleBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Role string `json:"role"` - Public bool `json:"public"` + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Preset bool `json:"public"` + Type setting.ResourceType `json:"type"` } func (c *Client) CreateOrUpdatePolicyRegistration(p *PolicyMeta) error { -- Gitee From d613252cfe1f221ca14e5f11bdf9d9b959bb7b8c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 16:20:54 +0800 Subject: [PATCH 1696/2647] adapt to gerrit logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index bccbc33e..bc258d1f 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -188,8 +188,10 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S } if err = fsutil.Untar(localPath, localBase); err != nil { - logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) - return err + if err = fsutil.Untar(localPath, filepath.Join(localBase, name)); err != nil { + logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) + return err + } } return nil } -- Gitee From c6e471bc4065680f85eb668c6002d60cec6c1943 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 16:27:54 +0800 Subject: [PATCH 1697/2647] policybinding add system type Signed-off-by: mouuii --- .../collaboration/service/collaboration_instance.go | 3 ++- pkg/shared/client/policy/policy.go | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index d62caadf..c8e4debc 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -378,7 +378,8 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp Name: buildPolicybindingName(uid, policyName, projectName), UID: uid, Policy: policyName, - Public: false, + Preset: false, + Type: setting.ResourceTypeSystem, }) } if len(policies) > 0 { diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 5329082e..dfe32021 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -19,6 +19,7 @@ package policy import ( "fmt" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" ) @@ -50,10 +51,11 @@ type DeletePoliciesArgs struct { } type PolicyBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Policy string `json:"policy"` - Public bool `json:"public"` + Name string `json:"name"` + UID string `json:"uid"` + Policy string `json:"policy"` + Preset bool `json:"preset"` + Type setting.ResourceType `json:"type"` } func (c *Client) CreatePolicyBinding(projectName string, policyBindings []*PolicyBinding) error { -- Gitee From fe311abe0df18c142a7c5f8bfc153df49d714c8f Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 16:41:20 +0800 Subject: [PATCH 1698/2647] public to preset Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/service/product.go | 2 +- pkg/microservice/picket/core/filter/service/project.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 9be3d9ad..7211198d 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -597,7 +597,7 @@ func ForkProduct(username, uid, requestID string, args *template.ForkProject, lo err = policy.NewDefault().CreateOrUpdateRoleBinding(args.ProductName, &policy.RoleBinding{ UID: uid, Role: string(setting.Contributor), - Public: true, + Preset: true, }) if err != nil { log.Errorf("Failed to create or update roleBinding, err: %s", err) diff --git a/pkg/microservice/picket/core/filter/service/project.go b/pkg/microservice/picket/core/filter/service/project.go index ee6031a4..e62a0f96 100644 --- a/pkg/microservice/picket/core/filter/service/project.go +++ b/pkg/microservice/picket/core/filter/service/project.go @@ -48,7 +48,7 @@ func CreateProject(header http.Header, body []byte, qs url.Values, args *CreateP Name: configbase.RoleBindingNameFromUIDAndRole(uid, setting.ProjectAdmin, ""), UID: uid, Role: string(setting.ProjectAdmin), - Public: true, + Preset: true, }) } @@ -57,7 +57,7 @@ func CreateProject(header http.Header, body []byte, qs url.Values, args *CreateP Name: configbase.RoleBindingNameFromUIDAndRole("*", setting.ReadOnly, ""), UID: "*", Role: string(setting.ReadOnly), - Public: true, + Preset: true, }) } @@ -97,7 +97,7 @@ func UpdateProject(header http.Header, qs url.Values, body []byte, projectName s Name: roleBindingName, UID: "*", Role: string(setting.ReadOnly), - Public: true, + Preset: true, }); err != nil { logger.Errorf("Failed to create role binding, err: %s", err) return nil, err -- Gitee From e4e09e24b3fec5bb9e45de9318de1195aa3677b3 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 17:21:38 +0800 Subject: [PATCH 1699/2647] fix type bug Signed-off-by: mouuii --- .../picket/client/policy/rolebinding.go | 9 ++++---- .../picket/core/filter/service/rolebinding.go | 23 ++++++++----------- .../policy/core/service/policy_binding.go | 10 ++++---- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go index 4db89991..50a7a365 100644 --- a/pkg/microservice/picket/client/policy/rolebinding.go +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -25,10 +25,11 @@ import ( ) type RoleBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Role string `json:"role"` - Public bool `json:"public"` + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Preset bool `json:"preset"` + Type setting.ResourceType `json:"type"` } type PolicyBinding struct { diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 01473e17..3288f7bc 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -24,7 +24,6 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/picket/client/policy" - "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/user" ) @@ -32,22 +31,20 @@ const allUsers = "*" type roleBinding struct { *policy.RoleBinding - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` - IdentityType string `json:"identity_type"` - Account string `json:"account"` - Type setting.ResourceType `json:"type"` + Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` } type policyBinding struct { *policy.PolicyBinding - Username string `json:"username"` - Email string `json:"email"` - Phone string `json:"phone"` - IdentityType string `json:"identity_type"` - Account string `json:"account"` - Type setting.ResourceType `json:"type"` + Username string `json:"username"` + Email string `json:"email"` + Phone string `json:"phone"` + IdentityType string `json:"identity_type"` + Account string `json:"account"` } type Binding struct { diff --git a/pkg/microservice/policy/core/service/policy_binding.go b/pkg/microservice/policy/core/service/policy_binding.go index 7236546d..7a2f9c33 100644 --- a/pkg/microservice/policy/core/service/policy_binding.go +++ b/pkg/microservice/policy/core/service/policy_binding.go @@ -27,10 +27,11 @@ import ( ) type PolicyBinding struct { - Name string `json:"name"` - UID string `json:"uid"` - Policy string `json:"policy"` - Preset bool `json:"preset"` + Name string `json:"name"` + UID string `json:"uid"` + Policy string `json:"policy"` + Preset bool `json:"preset"` + Type setting.ResourceType `json:"type"` } func CreatePolicyBindings(ns string, rbs []*PolicyBinding, logger *zap.SugaredLogger) error { @@ -76,6 +77,7 @@ func ListPolicyBindings(ns, uid string, _ *zap.SugaredLogger) ([]*PolicyBinding, Policy: v.PolicyRef.Name, UID: v.Subjects[0].UID, Preset: v.PolicyRef.Namespace == "", + Type: v.Type, }) } -- Gitee From ece551fcd40035a209b16ef5e518c47800d74208 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 25 Feb 2022 18:31:32 +0800 Subject: [PATCH 1700/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 28 -------------- pkg/setting/consts.go | 3 +- pkg/tool/helmclient/chartclient.go | 37 ++++++++----------- 3 files changed, 17 insertions(+), 51 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 39866269..7fec33d6 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -20,8 +20,6 @@ import ( "bytes" "encoding/json" "fmt" - "io" - "io/ioutil" "net/http" "net/url" "os" @@ -747,32 +745,6 @@ func handleSingleChart(chartData *DeliveryChartData, product *commonmodels.Produ return imageDetail, nil } -func handlePushResponse(resp *http.Response) error { - if resp.StatusCode != 201 && resp.StatusCode != 202 { - b, err := ioutil.ReadAll(resp.Body) - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) - if err != nil { - return err - } - return getChartmuseumError(b, resp.StatusCode) - } - log.Infof("push chart to chart repo done") - return nil -} - -func getChartmuseumError(b []byte, code int) error { - var er struct { - Error string `json:"error"` - } - err := json.Unmarshal(b, &er) - if err != nil || er.Error == "" { - return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) - } - return errors.Errorf("%d: %s", code, er.Error) -} - func makeChartTGZFileDir(productName, versionName string) (string, error) { dirPath := getChartTGZDir(productName, versionName) if err := os.RemoveAll(dirPath); err != nil { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 25735426..4501f017 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -240,8 +240,7 @@ const ( SourceFromChartTemplate = "chartTemplate" // SourceFromPublicRepo 配置来源为publicRepo SourceFromPublicRepo = "publicRepo" - // SourceFromChartRepo - SourceFromChartRepo = "chartRepo" + SourceFromChartRepo = "chartRepo" // SourceFromGUI 配置来源为gui SourceFromGUI = "gui" diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go index db63c451..d03a123c 100644 --- a/pkg/tool/helmclient/chartclient.go +++ b/pkg/tool/helmclient/chartclient.go @@ -19,10 +19,8 @@ package helmclient import ( "encoding/json" "fmt" - "io" "io/ioutil" "net/http" - "os" "path/filepath" cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" @@ -57,16 +55,14 @@ func (client *ChartRepoClient) FetchIndexYaml() (*helm.Index, error) { return nil, errors.Wrapf(err, "failed to download index.yaml") } - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) + defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } - if resp.StatusCode != 200 { + if resp.StatusCode != http.StatusOK { return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") } @@ -80,30 +76,30 @@ func (client *ChartRepoClient) FetchIndexYaml() (*helm.Index, error) { func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath string) error { chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) chartTGZFilePath := filepath.Join(basePath, chartTGZName) - out, err := os.Create(chartTGZFilePath) - if err != nil { - _ = os.RemoveAll(chartTGZFilePath) - return errors.Wrapf(err, "failed to create chart tgz file") - } + //out, err := os.Create(chartTGZFilePath) + //if err != nil { + // _ = os.RemoveAll(chartTGZFilePath) + // return errors.Wrapf(err, "failed to create chart tgz file") + //} response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) if err != nil { return errors.Wrapf(err, "failed to download file") } - if response.StatusCode != 200 { + if response.StatusCode != http.StatusOK { return errors.Wrapf(err, "download file failed") } - defer func() { _ = response.Body.Close() }() + defer response.Body.Close() b, err := ioutil.ReadAll(response.Body) if err != nil { return errors.Wrapf(err, "failed to read response data") } - defer func(out *os.File) { - _ = out.Close() - }(out) + //defer func(out *os.File) { + // _ = out.Close() + //}(out) return ioutil.WriteFile(chartTGZFilePath, b, 0644) } @@ -131,6 +127,8 @@ func (client *ChartRepoClient) PushChart(chartPackagePath string, force bool) er if err != nil { return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) } + + defer resp.Body.Close() err = handlePushResponse(resp) if err != nil { return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) @@ -150,16 +148,13 @@ func getChartmuseumError(b []byte, code int) error { } func handlePushResponse(resp *http.Response) error { - if resp.StatusCode != 201 && resp.StatusCode != 202 { + if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted { b, err := ioutil.ReadAll(resp.Body) - defer func(Body io.ReadCloser) { - _ = Body.Close() - }(resp.Body) if err != nil { return err } return getChartmuseumError(b, resp.StatusCode) } - log.Infof("push chart to chart repo done") + log.Info("push chart to chart repo done") return nil } -- Gitee From 6ba8d35bcf7598a3c7f7e418b67f61abdf5b8b70 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 25 Feb 2022 18:34:30 +0800 Subject: [PATCH 1701/2647] remove useless code Signed-off-by: allenshen --- pkg/tool/helmclient/chartclient.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go index d03a123c..1de925ec 100644 --- a/pkg/tool/helmclient/chartclient.go +++ b/pkg/tool/helmclient/chartclient.go @@ -76,11 +76,6 @@ func (client *ChartRepoClient) FetchIndexYaml() (*helm.Index, error) { func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath string) error { chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) chartTGZFilePath := filepath.Join(basePath, chartTGZName) - //out, err := os.Create(chartTGZFilePath) - //if err != nil { - // _ = os.RemoveAll(chartTGZFilePath) - // return errors.Wrapf(err, "failed to create chart tgz file") - //} response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) if err != nil { @@ -96,11 +91,6 @@ func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath s if err != nil { return errors.Wrapf(err, "failed to read response data") } - - //defer func(out *os.File) { - // _ = out.Close() - //}(out) - return ioutil.WriteFile(chartTGZFilePath, b, 0644) } -- Gitee From 0ffe1ffae7b94de0365eabf9deb514ac6e0f0430 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 25 Feb 2022 16:11:10 +0800 Subject: [PATCH 1702/2647] fix bug of displaying real-time build logs Signed-off-by: zhangyifei --- .../aslan/core/log/service/sse.go | 32 +++++++++++++++---- .../core/multicluster/service/clusters.go | 7 +++- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 869539aa..5fd401e4 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -120,24 +120,41 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op return } log.Debugf("Start to get task container log.") - // Cloud host scenario reads real-time logs from the environment, so pipelineName is empty + + var serviceName string + serviceNames := strings.Split(options.ServiceName, "_") + switch len(serviceNames) { + case 1: + serviceName = serviceNames[0] + case 2: + // Note: Starting from V1.10.0, this field will be in the format of `ServiceComponent_ServiceName`. + serviceName = serviceNames[1] + } + + // Cloud host scenario reads real-time logs from the environment, so pipelineName is empty. if options.EnvName != "" && options.ProductName != "" && options.PipelineName == "" { - //修改pipelineName,判断pipelineName是否为空,为空代表是来自环境里面请求,不为空代表是来自工作流任务的请求 - options.PipelineName = fmt.Sprintf("%s-%s-%s", options.ServiceName, options.EnvName, "job") + // Modify pipelineName to check whether pipelineName is empty: + // - Empty pipelineName indicates requests from the environment + // - Non-empty pipelineName indicate requests from workflow tasks + options.PipelineName = fmt.Sprintf("%s-%s-%s", serviceName, options.EnvName, "job") if taskObj, err := commonrepo.NewTaskColl().FindTask(options.PipelineName, config.ServiceType); err == nil { options.TaskID = taskObj.TaskID } - // Need to get build info based on the project name and service component name, then get clusterID and namespace } else if options.ProductName != "" { build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ ProductName: options.ProductName, - Targets: []string{options.ServiceName}, + Targets: []string{serviceName}, }) if err != nil { // Maybe this service is a shared service build, err = commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ - Targets: []string{options.ServiceName}, + Targets: []string{serviceName}, }) + + if err != nil { + log.Errorf("Failed to query build for service %s: %s", serviceName, err) + return + } } // Compatible with the situation where the old data has not been modified if build != nil && build.PreBuild != nil && build.PreBuild.ClusterID != "" { @@ -194,16 +211,19 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la log.Errorf("GetContainerLogs, get client set error: %s", err) return } + err = watcher.WaitUntilPodRunning(PodCtx, options.Namespace, selector, clientSet) if err != nil { log.Errorf("GetContainerLogs, wait pod running error: %s", err) return } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), options.ClusterID) if err != nil { log.Errorf("GetContainerLogs, get kube client error: %s", err) return } + pods, err := getter.ListPods(options.Namespace, selector, kubeClient) if err != nil { log.Errorf("GetContainerLogs, get pod error: %+v", err) diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 99392419..35463273 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -375,7 +375,12 @@ func setClusterCache(args *K8SCluster) error { // Since the attached cluster cannot access the minio in the local cluster, if the default object storage // is minio, the attached cluster cannot be configured to use minio as a cache. - if args.ID != setting.LocalClusterID && strings.Contains(defaultStorage.Endpoint, ZadigMinioSVC) { + // + // Note: + // - Since the local cluster will be written to the database when Zadig is created, empty ID indicates + // that the cluster is attached. + // - Currently, we do not support attaching the local cluster again. + if (args.ID == "" || args.ID != setting.LocalClusterID) && strings.Contains(defaultStorage.Endpoint, ZadigMinioSVC) { return nil } -- Gitee From 3a02193987bb60beebdc10bed9289ebbba1f2b37 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Feb 2022 18:47:19 +0800 Subject: [PATCH 1703/2647] webhook adapt to helm for gerrit Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gerrit.go | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index c92dc0b1..ab369d08 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "io/fs" "io/ioutil" "net/http" "os" @@ -129,36 +130,18 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro var newYamlContent string var oldYamlContent string if filePath.IsDir() { - newFileInfos, err := ioutil.ReadDir(path.Join(newBase, service.LoadPath)) - if err != nil { + if newFileContents, err := readAllFileContentUnderDir(path.Join(newBase, service.LoadPath)); err == nil { + newYamlContent = strings.Join(newFileContents, setting.YamlFileSeperator) + } else { errs = multierror.Append(errs, err) } - newFileContents := make([]string, 0) - for _, file := range newFileInfos { - if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.LoadPath, file.Name())); err == nil { - newFileContents = append(newFileContents, string(contentBytes)) - } else { - errs = multierror.Append(errs, err) - } - } - - newYamlContent = strings.Join(newFileContents, setting.YamlFileSeperator) - - oldFileInfos, err := ioutil.ReadDir(path.Join(oldBase, service.LoadPath)) - if err != nil { + if oldFileContents, err := readAllFileContentUnderDir(path.Join(oldBase, service.LoadPath)); err == nil { + oldYamlContent = strings.Join(oldFileContents, setting.YamlFileSeperator) + } else { errs = multierror.Append(errs, err) } - oldFileContents := make([]string, 0) - for _, file := range oldFileInfos { - if contentBytes, err := ioutil.ReadFile(path.Join(oldBase, service.LoadPath, file.Name())); err == nil { - oldFileContents = append(oldFileContents, string(contentBytes)) - } else { - errs = multierror.Append(errs, err) - } - } - oldYamlContent = strings.Join(oldFileContents, setting.YamlFileSeperator) } else { if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.LoadPath)); err == nil { newYamlContent = string(contentBytes) @@ -340,3 +323,29 @@ func reloadServiceTmplFromGerrit(svc *commonmodels.Service, log *zap.SugaredLogg }, log) return err } + +// Get the contents of all files in a directory +func readAllFileContentUnderDir(localBase string) ([]string, error) { + fileTree := os.DirFS(localBase) + allFileContents := []string{} + err := fs.WalkDir(fileTree, ".", func(path string, entry fs.DirEntry, err error) error { + if err != nil { + return err + } + + mode := entry.Type() + switch { + case mode.IsRegular(): + fileContents, err := fs.ReadFile(fileTree, path) + if err != nil { + return err + } + + allFileContents = append(allFileContents, string(fileContents)) + return nil + default: + return nil + } + }) + return allFileContents, err +} -- Gitee From aba3a61609920a325764868df944e8fa93da3b97 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Feb 2022 18:47:56 +0800 Subject: [PATCH 1704/2647] fix preset bug Signed-off-by: mouuii --- pkg/shared/client/policy/role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index 4e7988e5..1aaa25c4 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -54,7 +54,7 @@ type RoleBinding struct { Name string `json:"name"` UID string `json:"uid"` Role string `json:"role"` - Preset bool `json:"public"` + Preset bool `json:"preset"` Type setting.ResourceType `json:"type"` } -- Gitee From f97851df3f261ee61f39cf5da042ecfcf587331b Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 25 Feb 2022 19:02:35 +0800 Subject: [PATCH 1705/2647] fix update environment update bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 3a53bedd..2b266eb2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -443,7 +443,7 @@ func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServic compareHelmVariable([]*templatemodels.RenderChart{ { - ServiceName: args.Name, + ServiceName: chartRepoArgs.ChartName, ChartVersion: svc.HelmChart.Version, ValuesYaml: svc.HelmChart.ValuesYaml, }, -- Gitee From 7fac4bef5e66c678487ab19eb204f03465be0cf8 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 25 Feb 2022 19:08:21 +0800 Subject: [PATCH 1706/2647] add collaboration recycle Signed-off-by: panxunying --- .../handler/collaboration_instance.go | 6 + .../core/collaboration/handler/router.go | 1 + .../models/collaboration_instance.go | 2 +- .../repository/models/collaboration_mode.go | 2 +- .../service/collaboration_instance.go | 109 ++++++++++++++++++ .../cron/core/service/client/cronjob.go | 11 ++ .../cron/core/service/scheduler/scheduler.go | 14 +++ .../policy/core/handler/policy.go | 7 ++ .../policy/core/handler/router.go | 1 + .../policy/core/repository/mongodb/policy.go | 22 +++- .../policy/core/service/policy.go | 38 +++++- pkg/shared/client/policy/policy.go | 11 ++ 12 files changed, 217 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go index b0058397..416f4424 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/handler/collaboration_instance.go @@ -60,3 +60,9 @@ func SyncCollaborationInstance(c *gin.Context) { } ctx.Err = service.SyncCollaborationInstance(args, projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.RequestID, ctx.Logger) } + +func CleanCIResources(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Err = service.CleanCIResources(ctx.UserName, ctx.RequestID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 1495160b..9dc40da2 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -32,6 +32,7 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations.PUT("/:name", middleware.UpdateOperationLogStatus, UpdateCollaborationMode) collaborations.POST("", middleware.UpdateOperationLogStatus, CreateCollaborationMode) collaborations.DELETE("/:name", middleware.UpdateOperationLogStatus, DeleteCollaborationMode) + collaborations.POST("/cron/clean", middleware.UpdateOperationLogStatus, CleanCIResources) collaborations.GET("/new", GetCollaborationNew) collaborations.POST("/sync", SyncCollaborationInstance) } diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go index 6369dfc8..5ecf7eae 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_instance.go @@ -29,7 +29,7 @@ type CollaborationInstance struct { UpdateTime int64 `bson:"update_time" json:"update_time"` LastVisitTime int64 `bson:"last_visit_time" json:"last_visit_time"` CollaborationName string `bson:"collaboration_name" json:"collaboration_name"` - RecycleDay int `bson:"recycle_day" json:"recycle_day"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` Revision int64 `bson:"revision" json:"revision"` UserUID string `bson:"user_uid" json:"user_uid"` PolicyName string `bson:"policy_name" json:"policy_name"` diff --git a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go index 1280877c..0f35475e 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/models/collaboration_mode.go @@ -33,7 +33,7 @@ type CollaborationMode struct { Members []string `bson:"members" json:"members"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` DeployType string `bson:"deploy_type" json:"deploy_type"` - RecycleDay int `bson:"recycle_day" json:"recycle_day"` + RecycleDay int64 `bson:"recycle_day" json:"recycle_day"` Workflows []WorkflowCMItem `bson:"workflows" json:"workflows" ` Products []ProductCMItem `bson:"products" json:"products"` CreateBy string `bson:"create_by" json:"create_by"` diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 1f5c0604..9725e494 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" ) @@ -1161,6 +1162,114 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id }, nil } +type DeleteCIResourcesRequest struct { + CollaborationInstances []models.CollaborationInstance `json:"collaboration_instances"` +} + +func CleanCIResources(userName, requestID string, logger *zap.SugaredLogger) error { + cis, err := mongodb.NewCollaborationInstanceColl().List(&mongodb.CollaborationInstanceFindOptions{}) + if err != nil { + return err + } + var fileterdInstances []*models.CollaborationInstance + for _, ci := range cis { + if (time.Now().Unix()-ci.LastVisitTime)/60 > ci.RecycleDay*24*60 { + fileterdInstances = append(fileterdInstances, ci) + } + } + return DeleteCIResources(userName, requestID, fileterdInstances, logger) +} + +func DeleteCIResources(userName, requestID string, cis []*models.CollaborationInstance, logger *zap.SugaredLogger) error { + var names string + var policyNames []string + if len(cis) == 0 { + return nil + } + var findOpts []mongodb.CollaborationInstanceFindOptions + + for _, ci := range cis { + findOpts = append(findOpts, mongodb.CollaborationInstanceFindOptions{ + ProjectName: ci.ProjectName, + Name: ci.CollaborationName, + UserUID: ci.UserUID, + }) + policyNames = append(policyNames, ci.PolicyName) + } + + err := mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ + FindOpts: findOpts, + }) + if err != nil { + logger.Errorf("BulkDelete CollaborationInstance error:%s", err) + return err + } + err = policy.NewDefault().DeletePolicies("", policy.DeletePoliciesArgs{ + Names: policyNames, + }) + if err != nil { + logger.Errorf("BulkDelete policy error:%s", err) + return err + } + names = cis[0].PolicyName + for i := 1; i < len(cis); i++ { + names = names + "," + cis[i].PolicyName + } + res, err := policy.NewDefault().GetPolicies(names) + if err != nil { + return err + } + var labels []label.LabelModel + labelSet := sets.String{} + for _, re := range res { + for _, rule := range re.Rules { + for _, attribute := range rule.MatchAttributes { + if attribute.Key != "placeholder" && attribute.Key != "production" && + !labelSet.Has(attribute.Key+"-"+attribute.Value) { + labels = append(labels, label.LabelModel{ + Key: attribute.Key, + Value: attribute.Value, + }) + labelSet.Insert(attribute.Key + "-" + attribute.Value) + } + } + } + } + labelRes, err := label.New().ListLabels(label.ListLabelsArgs{ + Labels: labels, + }) + if err != nil { + return err + } + var labelIds []string + for _, l := range labelRes.Labels { + labelIds = append(labelIds, l.ID.Hex()) + } + err = label.New().DeleteLabels(labelIds) + if err != nil { + return err + } + for _, ci := range cis { + for _, workflow := range ci.Workflows { + if workflow.CollaborationType == config.CollaborationNew { + err = commonservice.DeleteWorkflow(workflow.Name, requestID, false, logger) + if err != nil { + return err + } + } + } + for _, product := range ci.Products { + if product.CollaborationType == config.CollaborationNew { + err = commonservice.DeleteProduct(userName, product.Name, ci.ProjectName, requestID, logger) + if err != nil { + return err + } + } + } + } + return nil +} + func GetCollaborationNew(projectName, uid, identityType, userName string, logger *zap.SugaredLogger) (*GetCollaborationNewResp, error) { updateResp, err := GetCollaborationUpdate(projectName, uid, identityType, userName, logger) if err != nil { diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index c1c727cf..23ce5a51 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -66,6 +66,17 @@ func (c *Client) TriggerCleanProducts(log *zap.SugaredLogger) error { return err } +// TriggerCleanCIResources trigger clean CollaborationInstance Resources +func (c *Client) TriggerCleanCIResources(log *zap.SugaredLogger) error { + url := fmt.Sprintf("%s/collaborations/cron/clean", c.APIBase) + log.Info("start clean CollaborationInstance Resources..") + err := c.sendRequest(url) + if err != nil { + log.Errorf("trigger clean CollaborationInstance Resources error :%v", err) + } + return err +} + // RunPipelineTask ... func (c *Client) RunPipelineTask(args *service.TaskArgs, log *zap.SugaredLogger) error { url := fmt.Sprintf("%s/workflow/v2/tasks", c.APIBase) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 1f6ffa95..bfe943dd 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -60,6 +60,8 @@ const ( UpsertColliePipelineScheduler = "UpsertColliePipelineScheduler" //CleanProductScheduler ... CleanProductScheduler = "CleanProductScheduler" + //CleanCIResourcesScheduler ... + CleanCIResourcesScheduler = "CleanCIResourcesScheduler" //InitBuildStatScheduler InitStatScheduler = "InitStatScheduler" //InitOperationStatScheduler @@ -145,6 +147,8 @@ func (c *CronClient) Init() { // 定时清理环境 c.InitCleanProductScheduler() + // 定时清理协作模式资源 + c.InitCleanCIResourcesScheduler() // 定时初始化构建数据 c.InitBuildStatScheduler() // 定时器初始化话运营统计数据 @@ -175,6 +179,16 @@ func (c *CronClient) InitCleanProductScheduler() { c.Schedulers[CleanProductScheduler].Start() } +// InitCleanCIResourcesScheduler ... +func (c *CronClient) InitCleanCIResourcesScheduler() { + + c.Schedulers[CleanCIResourcesScheduler] = gocron.NewScheduler() + + c.Schedulers[CleanCIResourcesScheduler].Every(5).Minutes().Do(c.AslanCli.TriggerCleanCIResources, c.log) + + c.Schedulers[CleanCIResourcesScheduler].Start() +} + // InitJobScheduler ... func (c *CronClient) InitJobScheduler() { diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go index 52381557..f39c5750 100644 --- a/pkg/microservice/policy/core/handler/policy.go +++ b/pkg/microservice/policy/core/handler/policy.go @@ -144,6 +144,13 @@ func GetPolicy(c *gin.Context) { ctx.Resp, ctx.Err = service.GetPolicy(projectName, c.Param("name"), ctx.Logger) } +func GetPolicies(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetPolicies(c.Param("names"), ctx.Logger) +} + func CreatePublicPolicy(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index b167dcdc..a9a56b12 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -42,6 +42,7 @@ func (*Router) Inject(router *gin.RouterGroup) { policies.PUT("/:name", UpdateOrCreatePolicy) policies.GET("", ListPolicies) policies.GET("/:name", GetPolicy) + policies.GET("/bulk", GetPolicies) policies.DELETE("/:name", DeletePolicy) } diff --git a/pkg/microservice/policy/core/repository/mongodb/policy.go b/pkg/microservice/policy/core/repository/mongodb/policy.go index 9c26ae5e..f1cf1365 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy.go @@ -62,6 +62,23 @@ func (c *PolicyColl) EnsureIndex(ctx context.Context) error { return err } +func (c *PolicyColl) GetByNames(names []string) ([]*models.Policy, error) { + query := bson.M{} + query["name"] = bson.M{"$in": names} + + cursor, err := c.Collection.Aggregate(context.TODO(), query) + if err != nil { + return nil, err + } + + var res []*models.Policy + err = cursor.All(context.TODO(), &res) + if err != nil { + return nil, err + } + return res, nil +} + func (c *PolicyColl) Get(ns, name string) (*models.Policy, bool, error) { res := &models.Policy{} @@ -160,7 +177,10 @@ func (c *PolicyColl) Delete(name string, projectName string) error { } func (c *PolicyColl) DeleteMany(names []string, projectName string) error { - query := bson.M{"namespace": projectName} + query := bson.M{} + if len(projectName) > 0 { + query = bson.M{"namespace": projectName} + } if len(names) > 0 { query["name"] = bson.M{"$in": names} } diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 48396216..a2596641 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -18,6 +18,7 @@ package service import ( "fmt" + "strings" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" @@ -129,13 +130,26 @@ func ListPolicies(projectName string, _ *zap.SugaredLogger) ([]*Policy, error) { return policies, nil } -func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { - r, found, err := mongodb.NewPolicyColl().Get(ns, name) +func GetPolicies(names string, log *zap.SugaredLogger) ([]*Policy, error) { + splitedNames := strings.Split(names, ",") + policies, err := mongodb.NewPolicyColl().GetByNames(splitedNames) if err != nil { return nil, err - } else if !found { - return nil, fmt.Errorf("policy %s not found", name) } + var res []*Policy + for _, policy := range policies { + policy, err := buildPolicy(policy) + if err != nil { + log.Errorf("buildPolicy error:%s", err) + return nil, err + } + res = append(res, policy) + } + + return res, nil +} + +func buildPolicy(r *models.Policy) (*Policy, error) { res := &Policy{ Name: r.Name, } @@ -184,6 +198,22 @@ func GetPolicy(ns, name string, _ *zap.SugaredLogger) (*Policy, error) { return res, nil } +func GetPolicy(ns, name string, log *zap.SugaredLogger) (*Policy, error) { + r, found, err := mongodb.NewPolicyColl().Get(ns, name) + if err != nil { + return nil, err + } else if !found { + log.Errorf("policy %s not found", name) + return nil, fmt.Errorf("policy %s not found", name) + } + res, err := buildPolicy(r) + if err != nil { + log.Errorf("buildPolicy error:%s", err) + return nil, err + } + return res, nil +} + func DeletePolicy(name string, projectName string, logger *zap.SugaredLogger) error { err := mongodb.NewPolicyColl().Delete(name, projectName) if err != nil { diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 5329082e..50b9abd3 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -119,3 +119,14 @@ func (c *Client) GetResourcePermission(req *ResourcePermissionReq) (map[string][ } return result, nil } + +func (c *Client) GetPolicies(names string) ([]*Policy, error) { + url := fmt.Sprintf("/policies") + + var res []*Policy + _, err := c.Get(url, httpclient.SetQueryParam("names", names), httpclient.SetResult(res)) + if err != nil { + return nil, err + } + return res, nil +} -- Gitee From 77f2e6570af97394d8c649e880f3ff5467c02e3f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 10:24:10 +0800 Subject: [PATCH 1707/2647] add createFrom field Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 47bb5225..4218426e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -566,10 +566,6 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr FilePath: filePath, ProductName: projectName, CreateBy: args.CreatedBy, - CodehostID: createFromRepo.CodehostID, - Owner: createFromRepo.Owner, - Repo: createFromRepo.Repo, - Branch: createFromRepo.Branch, Source: string(args.Source), GerritCodeHostID: createFromRepo.CodehostID, GerritPath: currentFilePath, @@ -1090,6 +1086,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg Version: chartVersion, ValuesYaml: valuesYaml, }, + CreateFrom: geneCreationDetail(args), } switch args.Source { @@ -1099,8 +1096,6 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg serviceObj.GerritRepoName = args.GerritRepoName serviceObj.GerritBranchName = args.GerritBranchName serviceObj.GerritRemoteName = args.GerritRemoteName - default: - serviceObj.CreateFrom = geneCreationDetail(args) } log.Infof("Starting to create service %s with revision %d", args.ServiceName, args.ServiceRevision) -- Gitee From 1615d6a30196e4aa2bc9f9946c02bc23a5d50aa5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 11:15:06 +0800 Subject: [PATCH 1708/2647] remove resource warn log Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/service/bundle.go | 2 +- pkg/microservice/aslan/core/workflow/service/workflow/bundle.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 35a1cd1b..df971e18 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -85,7 +85,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec.Spec[v.Key] = v.Value } } else { - logger.Warnf("can not find resource key :%s", resourceKey) + //logger.Warnf("can not find resource key :%s", resourceKey) } clusterID := env.ClusterID diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 7ac2536d..73138341 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -68,7 +68,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec.Spec[v.Key] = v.Value } } else { - logger.Warnf("can not find resource key :%s", resourceKey) + //logger.Warnf("can not find resource key :%s", resourceKey) } res = append(res, resourceSpec) } -- Gitee From c6d212ea9ea80c15e2f1cb1d51e2e5c5de0747a6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 11:43:04 +0800 Subject: [PATCH 1709/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/gerrit.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index ab369d08..1250cbf7 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -25,9 +25,11 @@ import ( "net/http" "os" "path" + "path/filepath" "strings" "github.com/hashicorp/go-multierror" + "github.com/otiai10/copy" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -209,6 +211,19 @@ func SyncServiceTemplateFromGerrit(service *commonmodels.Service, log *zap.Sugar } return nil } + // remove old repo dir + oldRepoDir := filepath.Join(config.S3StoragePath(), service.GerritRepoName) + os.RemoveAll(oldRepoDir) + // copy new repo data to old dir data + if err := os.MkdirAll(oldRepoDir, 0775); err != nil { + return err + } + + newRepoDir := filepath.Join(config.S3StoragePath(), service.GerritRepoName+"-new") + if copyErr := copy.Copy(newRepoDir, oldRepoDir); copyErr != nil { + return copyErr + } + if err := reloadServiceTmplFromGerrit(service, log); err != nil { return err } -- Gitee From 660198050035245e237dcf7d6767bb5e29619000 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 11:51:23 +0800 Subject: [PATCH 1710/2647] remove unused code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/s3.go | 55 ------------------- 1 file changed, 55 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index bc258d1f..1e3a288c 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -48,61 +48,6 @@ func ArchiveAndUploadFilesToS3(fileTree fs.FS, names []string, s3Base string, lo return archiveAndUploadFiles(fileTree, names, s3Base, s3Storage, logger) } -//func ArchiveAndUploadLocalFilesToS3(names []string, currentBase, s3Base string, logger *zap.SugaredLogger) error { -// if len(names) == 0 { -// return fmt.Errorf("names not appointed") -// } -// -// tmpDir, err := os.MkdirTemp("", "") -// if err != nil { -// logger.Errorf("Failed to create temp dir, err: %s", err) -// return err -// } -// defer os.RemoveAll(tmpDir) -// -// name := names[0] -// copies := names[1:] -// -// tarball := fmt.Sprintf("%s.tar.gz", name) -// localPath := filepath.Join(tmpDir, tarball) -// if err = util.Tar(currentBase, localPath); err != nil { -// logger.Errorf("Failed to archive tarball %s, err: %s", localPath, err) -// return err -// } -// -// s3Storage, err := s3service.FindDefaultS3() -// if err != nil { -// logger.Errorf("Failed to find default s3, err:%v", err) -// return err -// } -// forcedPathStyle := true -// if s3Storage.Provider == setting.ProviderSourceAli { -// forcedPathStyle = false -// } -// client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) -// if err != nil { -// logger.Errorf("Failed to get s3 client, err: %s", err) -// return err -// } -// s3Path := filepath.Join(s3Storage.Subfolder, s3Base, tarball) -// if err = client.Upload(s3Storage.Bucket, localPath, s3Path); err != nil { -// logger.Errorf("Failed to upload file %s to s3, err: %s", localPath, err) -// return err -// } -// -// // copy file to avoid duplicated file transfer -// for _, copyName := range copies { -// targetPath := filepath.Join(s3Storage.Subfolder, s3Base, fmt.Sprintf("%s.tar.gz", copyName)) -// err = client.CopyObject(s3Storage.Bucket, s3Path, targetPath) -// if err != nil { -// logger.Errorf("Failed to copy object from %s to %s", s3Path, targetPath) -// return err -// } -// } -// -// return nil -//} - // archiveAndUploadFiles archive local files and upload to default s3 storage // if multiple names appointed, s3storage.copy will be used to handle extra names func archiveAndUploadFiles(fileTree fs.FS, names []string, s3Base string, s3Storage *s3service.S3, logger *zap.SugaredLogger) error { -- Gitee From 4f6fc5f93d4aea8e282374a4c9bccba94575976c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 12:03:53 +0800 Subject: [PATCH 1711/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/helm.go | 8 ++- .../aslan/core/service/service/helm.go | 51 +++++++++--------- pkg/util/file.go | 53 ------------------- 3 files changed, 28 insertions(+), 84 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 49e02848..eb59628d 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -21,18 +21,16 @@ import ( "os" "path" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - - "github.com/koderover/zadig/pkg/setting" - "github.com/27149chen/afero" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/log" fsutil "github.com/koderover/zadig/pkg/util/fs" ) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 4218426e..b5d0a2c0 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -50,7 +50,6 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/util" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -517,11 +516,11 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr currentFilePath := path.Join(base, filePath) log.Infof("Loading chart under path %s", currentFilePath) - serviceName, chartVersion, finalErr = readChartYAMLFromLocal(currentFilePath, log) + serviceName, chartVersion, finalErr = readChartYAML(os.DirFS(currentFilePath), currentFilePath, log) if finalErr != nil { return } - valuesYAML, finalErr = readValuesYAMLFromLocal(currentFilePath, log) + valuesYAML, finalErr = readValuesYAML(os.DirFS(currentFilePath), currentFilePath, log) if finalErr != nil { return } @@ -934,20 +933,20 @@ func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (str return chart.Name, chart.Version, nil } -func readChartYAMLFromLocal(base string, logger *zap.SugaredLogger) (string, string, error) { - chartFile, err := util.ReadFile(filepath.Join(base, setting.ChartYaml)) - if err != nil { - logger.Errorf("Failed to read %s, err: %s", setting.ChartYaml, err) - return "", "", err - } - chart := new(Chart) - if err = yaml.Unmarshal(chartFile, chart); err != nil { - log.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ChartYaml, err) - return "", "", err - } - - return chart.Name, chart.Version, nil -} +//func readChartYAMLFromLocal(base string, logger *zap.SugaredLogger) (string, string, error) { +// chartFile, err := util.ReadFile(filepath.Join(base, setting.ChartYaml)) +// if err != nil { +// logger.Errorf("Failed to read %s, err: %s", setting.ChartYaml, err) +// return "", "", err +// } +// chart := new(Chart) +// if err = yaml.Unmarshal(chartFile, chart); err != nil { +// log.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ChartYaml, err) +// return "", "", err +// } +// +// return chart.Name, chart.Version, nil +//} func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, error) { content, err := fs.ReadFile(chartTree, filepath.Join(base, setting.ValuesYaml)) @@ -958,14 +957,14 @@ func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([] return content, nil } -func readValuesYAMLFromLocal(base string, logger *zap.SugaredLogger) ([]byte, error) { - content, err := util.ReadFile(filepath.Join(base, setting.ValuesYaml)) - if err != nil { - logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) - return nil, err - } - return content, nil -} +//func readValuesYAMLFromLocal(base string, logger *zap.SugaredLogger) ([]byte, error) { +// content, err := util.ReadFile(filepath.Join(base, setting.ValuesYaml)) +// if err != nil { +// logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) +// return nil, err +// } +// return content, nil +//} func geneCreationDetail(args *helmServiceCreationArgs) interface{} { switch args.Source { @@ -1042,7 +1041,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg switch args.Source { case string(LoadFromGerrit): base := path.Join(config.S3StoragePath(), args.Repo) - chartName, chartVersion, err = readChartYAMLFromLocal(filepath.Join(base, args.FilePath), logger) + chartName, chartVersion, err = readChartYAML(os.DirFS(filepath.Join(base, args.FilePath)), args.ServiceName, logger) default: chartName, chartVersion, err = readChartYAML(fsTree, args.ServiceName, logger) } diff --git a/pkg/util/file.go b/pkg/util/file.go index 4f6da048..30144213 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -17,13 +17,8 @@ limitations under the License. package util import ( - "archive/tar" - "compress/gzip" - "io" "io/ioutil" "os" - "path/filepath" - "strings" ) func GenerateTmpFile() (string, error) { @@ -65,51 +60,3 @@ func ReadFile(filename string) ([]byte, error) { } return contentByte, nil } - -func Tar(src, dst string) (err error) { - fw, err := os.Create(dst) - if err != nil { - return - } - defer fw.Close() - - gw := gzip.NewWriter(fw) - defer gw.Close() - - tw := tar.NewWriter(gw) - defer tw.Close() - - return filepath.Walk(src, func(fileName string, fi os.FileInfo, err error) error { - if err != nil { - return err - } - - hdr, err := tar.FileInfoHeader(fi, "") - if err != nil { - return err - } - - hdr.Name = strings.TrimPrefix(fileName, string(filepath.Separator)) - - if err := tw.WriteHeader(hdr); err != nil { - return err - } - - if !fi.Mode().IsRegular() { - return nil - } - - fr, err := os.Open(fileName) - defer fr.Close() - if err != nil { - return err - } - - _, err = io.Copy(tw, fr) - if err != nil { - return err - } - - return nil - }) -} -- Gitee From 677dad8abde52697a9283ade44d06cc8cad52077 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 12:15:26 +0800 Subject: [PATCH 1712/2647] preset Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 09017527..916e8049 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -129,6 +129,9 @@ func addPresetRoleSystemType() error { change := bson.M{"$set": bson.M{ "type": "system", }} + if v.Name == "project-admin" || v.Name == "read-only" || v.Name == "read-project-only" { + change["preset"] = true + } _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { return err -- Gitee From 87505d46aefa5aaec1b3974218bc400ebfc1180e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 12:22:41 +0800 Subject: [PATCH 1713/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/service/service/helm.go | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e371539a..772d2b8b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -51,6 +51,7 @@ import ( "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -639,11 +640,11 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr currentFilePath := path.Join(base, filePath) log.Infof("Loading chart under path %s", currentFilePath) - serviceName, chartVersion, finalErr = readChartYAML(os.DirFS(currentFilePath), currentFilePath, log) + serviceName, chartVersion, finalErr = readChartYAMLFromLocal(currentFilePath, log) if finalErr != nil { return } - valuesYAML, finalErr = readValuesYAML(os.DirFS(currentFilePath), currentFilePath, log) + valuesYAML, finalErr = readValuesYAMLFromLocal(currentFilePath, log) if finalErr != nil { return } @@ -1056,20 +1057,20 @@ func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (str return chart.Name, chart.Version, nil } -//func readChartYAMLFromLocal(base string, logger *zap.SugaredLogger) (string, string, error) { -// chartFile, err := util.ReadFile(filepath.Join(base, setting.ChartYaml)) -// if err != nil { -// logger.Errorf("Failed to read %s, err: %s", setting.ChartYaml, err) -// return "", "", err -// } -// chart := new(Chart) -// if err = yaml.Unmarshal(chartFile, chart); err != nil { -// log.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ChartYaml, err) -// return "", "", err -// } -// -// return chart.Name, chart.Version, nil -//} +func readChartYAMLFromLocal(base string, logger *zap.SugaredLogger) (string, string, error) { + chartFile, err := util.ReadFile(filepath.Join(base, setting.ChartYaml)) + if err != nil { + logger.Errorf("Failed to read %s, err: %s", setting.ChartYaml, err) + return "", "", err + } + chart := new(Chart) + if err = yaml.Unmarshal(chartFile, chart); err != nil { + log.Errorf("Failed to unmarshal yaml %s, err: %s", setting.ChartYaml, err) + return "", "", err + } + + return chart.Name, chart.Version, nil +} func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([]byte, error) { content, err := fs.ReadFile(chartTree, filepath.Join(base, setting.ValuesYaml)) @@ -1080,14 +1081,14 @@ func readValuesYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) ([] return content, nil } -//func readValuesYAMLFromLocal(base string, logger *zap.SugaredLogger) ([]byte, error) { -// content, err := util.ReadFile(filepath.Join(base, setting.ValuesYaml)) -// if err != nil { -// logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) -// return nil, err -// } -// return content, nil -//} +func readValuesYAMLFromLocal(base string, logger *zap.SugaredLogger) ([]byte, error) { + content, err := util.ReadFile(filepath.Join(base, setting.ValuesYaml)) + if err != nil { + logger.Errorf("Failed to read %s, err: %s", setting.ValuesYaml, err) + return nil, err + } + return content, nil +} func geneCreationDetail(args *helmServiceCreationArgs) interface{} { switch args.Source { -- Gitee From 2444a3ca9997bcc00dc0fff1c900932493248800 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 13:21:54 +0800 Subject: [PATCH 1714/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 772d2b8b..ff25bbbd 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1171,7 +1171,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg switch args.Source { case string(LoadFromGerrit): base := path.Join(config.S3StoragePath(), args.Repo) - chartName, chartVersion, err = readChartYAML(os.DirFS(filepath.Join(base, args.FilePath)), args.ServiceName, logger) + chartName, chartVersion, err = readChartYAMLFromLocal(filepath.Join(base, args.FilePath), logger) default: chartName, chartVersion, err = readChartYAML(fsTree, args.ServiceName, logger) } -- Gitee From 7ea2bebc8351bb7bd3eed11dbb0cb4bebff87010 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 13:35:22 +0800 Subject: [PATCH 1715/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index ff25bbbd..a6ec0a39 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1170,7 +1170,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg ) switch args.Source { case string(LoadFromGerrit): - base := path.Join(config.S3StoragePath(), args.Repo) + base := path.Join(config.S3StoragePath(), args.GerritRepoName) chartName, chartVersion, err = readChartYAMLFromLocal(filepath.Join(base, args.FilePath), logger) default: chartName, chartVersion, err = readChartYAML(fsTree, args.ServiceName, logger) -- Gitee From bb8ad48e48578ad9378674bb12c2b3cc7ac73f22 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 13:43:16 +0800 Subject: [PATCH 1716/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/helm.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index a6ec0a39..e5aaee9a 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -689,6 +689,10 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr FilePath: filePath, ProductName: projectName, CreateBy: args.CreatedBy, + CodehostID: createFromRepo.CodehostID, + Owner: createFromRepo.Owner, + Repo: createFromRepo.Repo, + Branch: createFromRepo.Branch, Source: string(args.Source), GerritCodeHostID: createFromRepo.CodehostID, GerritPath: currentFilePath, -- Gitee From d483ef7a752aa184c581f10c61d11bfe0ea4565c Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 13:47:01 +0800 Subject: [PATCH 1717/2647] fix bug Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/rolebinding.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 3288f7bc..03a960d5 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -95,9 +95,10 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) } var res []*Binding - var roleBindings []*roleBinding + var policyBindings []*policyBinding for _, u := range users { + var roleBindings []*roleBinding binding := &Binding{ UserName: u.Name, Email: u.Email, -- Gitee From d318a30c1746effb14453b72c1482d0e31c3cbf1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 25 Feb 2022 14:40:07 +0800 Subject: [PATCH 1718/2647] remove the limit of creating environment on existing namespace for helm Signed-off-by: Min Min --- .../core/environment/service/environment_creator.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 1531b0c5..b1134b3e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -18,7 +18,6 @@ package service import ( "errors" - "fmt" "time" "go.uber.org/zap" @@ -33,7 +32,6 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/informer" ) @@ -148,15 +146,6 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P if args.Namespace == "" { args.Namespace = namespace } - _, found, err := getter.GetNamespace(args.Namespace, kubeClient) - if err != nil { - log.Errorf("GetNamespace error: %v", err) - return e.ErrCreateEnv.AddDesc(err.Error()) - } - if found { - log.Warnf("%s[%s]%s", "namespace", args.Namespace, "已经存在,请换个环境名称尝试!") - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("%s[%s]%s", "namespace", args.Namespace, "已经存在,请换个环境名称尝试!")) - } restConfig, err := kube.GetRESTConfig(args.ClusterID) if err != nil { -- Gitee From 5b2f47f924ce0aab69e760c18d69d0938f78e5c7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 28 Feb 2022 14:01:25 +0800 Subject: [PATCH 1719/2647] rebase main Signed-off-by: Min Min --- pkg/microservice/aslan/core/environment/service/environment.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 6abed270..357e3168 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -160,6 +160,7 @@ type CreateHelmProductArg struct { ChartValues []*commonservice.RenderChartArg `json:"chartValues"` BaseEnvName string `json:"baseEnvName"` BaseName string `json:"base_name,omitempty"` + IsExisted bool `json:"is_existed"` } type UpdateMultiHelmProductArg struct { @@ -789,6 +790,7 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, ChartInfos: templateProduct.ChartInfos, IsForkedProduct: false, RegistryID: registryID, + IsExisted: arg.IsExisted, } customChartValueMap := make(map[string]*commonservice.RenderChartArg) -- Gitee From f04f9e175c8727ab12cba1ccd82db3b944d7aa2e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Feb 2022 14:31:03 +0800 Subject: [PATCH 1720/2647] modify annotation Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 5b2c7271..7ceae9aa 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -73,7 +73,7 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string return err } -// SaveAndUploadFiles saves a tree of files to local disk, at the same time, archives them and uploads to object storage. +// CopyAndUploadFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { var wg wait.Group var err error -- Gitee From 7ce67b9ef6b267d5d3859d527c1f4ecc86684cb9 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 28 Feb 2022 14:32:39 +0800 Subject: [PATCH 1721/2647] decide proxy settings on runtime instead of saving it in db Signed-off-by: Min Min --- .../service/workflow/workflow_task.go | 19 +++++++++++++++++++ pkg/types/repo.go | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index d01a90c4..8d50255a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -552,7 +552,16 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, stages := make([]*commonmodels.Stage, 0) serviceInfos := make([]*taskmodels.ServiceInfo, 0) + // for the new proxy settings, we check for each repo & apply proxy if necessary for _, target := range args.Target { + for _, repo := range target.Build.Repos { + repoInfo, err := systemconfig.New().GetCodeHost(repo.CodehostID) + if err != nil { + log.Errorf("Failed to get proxy settings for codehost ID: %d, the error is: %s", repo.CodehostID, err) + return nil, err + } + repo.EnableProxy = repoInfo.EnableProxy + } var subTasks []map[string]interface{} var err error if target.JenkinsBuildArgs == nil { @@ -702,6 +711,16 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, AddSubtaskToStage(&stages, stask, target.Name+"_"+target.ServiceName) } } + for _, test := range args.Tests { + for _, build := range test.Builds { + repoInfo, err := systemconfig.New().GetCodeHost(build.CodehostID) + if err != nil { + log.Errorf("Failed to get proxy settings for codehost ID: %d, the error is: %s", build.CodehostID, err) + return nil, err + } + build.EnableProxy = repoInfo.EnableProxy + } + } // add extension to stage if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage, serviceInfos) diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 8c9735c2..9c6890ae 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -52,7 +52,8 @@ type Repository struct { RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty"` Username string `bson:"username,omitempty" json:"username,omitempty"` Password string `bson:"password,omitempty" json:"password,omitempty"` - EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy,omitempty"` + // Now EnableProxy is not something we store. We decide this on runtime + EnableProxy bool `bson:"-" json:"enable_proxy,omitempty"` } // GetReleaseCandidateTag 返回待发布对象Tag -- Gitee From 20f4f9e2f394b92ddd22e03207a34469aeeee34f Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 16:16:19 +0800 Subject: [PATCH 1722/2647] code review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 58 ++++++++++++------- .../internal/repository/models/oldpolicy.go | 15 ++++- .../aslan/core/environment/service/bundle.go | 1 + .../core/workflow/service/workflow/bundle.go | 1 + .../picket/core/filter/service/rolebinding.go | 16 +++-- 5 files changed, 61 insertions(+), 30 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 916e8049..2c63d112 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -55,6 +55,11 @@ func V190ToV1100() error { return err } + if err := addRoleBindingSystemType(); err != nil { + log.Errorf("Failed to addRoleBindingSystemType, err: %s", err) + return err + } + if err := migrateModuleBuild(); err != nil { return fmt.Errorf("failed to migrate data in `zadig.module_build`: %s", err) } @@ -71,7 +76,7 @@ func V190ToV1100() error { return nil } -func addPresetRoleBindingSystemType() error { +func changeToCustomType() error { ctx := context.Background() var res []*models.RoleBinding @@ -86,29 +91,41 @@ func addPresetRoleBindingSystemType() error { return err } for _, v := range res { - if v.Namespace == "*" { - query := bson.M{"name": v.Name} - change := bson.M{"$set": bson.M{ - "type": setting.ResourceTypeSystem, - }} - _, err := newRoleBindingColl().UpdateOne(ctx, query, change) - if err != nil { - return err - } - } else { - query := bson.M{"name": v.Name} - change := bson.M{"$set": bson.M{ - "type": setting.ResourceTypeCustom, - }} - _, err := newRoleBindingColl().UpdateOne(ctx, query, change) - if err != nil { - return err - } + if v.Namespace == "*" && v.RoleRef.Name == "admin" { + continue } + query := bson.M{"name": v.Name} + change := bson.M{"$set": bson.M{ + "type": setting.ResourceTypeCustom, + }} + _, err := newRoleBindingColl().UpdateOne(ctx, query, change) + if err != nil { + return err + } + } return nil } +func changeToSystemType() error { + query := bson.M{"namespace": "*", "role_ref.name": "admin"} + change := bson.M{"$set": bson.M{ + "type": setting.ResourceTypeSystem, + }} + _, err := newRoleBindingColl().UpdateOne(context.TODO(), query, change) + if err != nil { + return err + } + return nil +} + +func addRoleBindingSystemType() error { + if err := changeToSystemType(); err != nil { + return err + } + return changeToCustomType() +} + func addPresetRoleSystemType() error { ctx := context.Background() @@ -129,9 +146,6 @@ func addPresetRoleSystemType() error { change := bson.M{"$set": bson.M{ "type": "system", }} - if v.Name == "project-admin" || v.Name == "read-only" || v.Name == "read-project-only" { - change["preset"] = true - } _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { return err diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 1d384956..2e23ef3e 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -29,8 +29,19 @@ type Role struct { } type RoleBinding struct { - Name string `baso:"name" json:"name"` - Namespace string `baso:"namespace" json:"namespace"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + + // RoleRef can reference a namespaced or cluster scoped Role. + RoleRef *RoleRef `bson:"role_ref" json:"roleRef"` +} + +// RoleRef contains information that points to the role being used +type RoleRef struct { + Name string `bson:"name" json:"name"` + + // Namespace of the referenced object. if the object is cluster scoped, namespace is empty. + Namespace string `bson:"namespace" json:"namespace"` } type PolicyMetaRule struct { diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index df971e18..7a28f0d0 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -85,6 +85,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec.Spec[v.Key] = v.Value } } else { + // TODO improve the resource key not found logic //logger.Warnf("can not find resource key :%s", resourceKey) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 73138341..8cb48408 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -68,6 +68,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec.Spec[v.Key] = v.Value } } else { + //TODO improve the resource key not found logic //logger.Warnf("can not find resource key :%s", resourceKey) } res = append(res, resourceSpec) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 03a960d5..71cb6e6d 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -27,7 +27,7 @@ import ( "github.com/koderover/zadig/pkg/shared/client/user" ) -const allUsers = "*" +const ALLUsers = "*" type roleBinding struct { *policy.RoleBinding @@ -68,7 +68,7 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) uidSets := sets.String{} uidToRoleBinding := make(map[string][]*roleBinding) for _, rb := range rbs { - if rb.UID != allUsers { + if rb.UID != ALLUsers { uidSets.Insert(rb.UID) } uidToRoleBinding[rb.UID] = append(uidToRoleBinding[rb.UID], &roleBinding{RoleBinding: rb}) @@ -82,7 +82,7 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) uidToPolicyBindings := make(map[string][]*policyBinding) for _, pb := range pbs { - if pb.UID != allUsers { + if pb.UID != ALLUsers { uidSets.Insert(pb.UID) } uidToPolicyBindings[pb.UID] = append(uidToPolicyBindings[pb.UID], &policyBinding{PolicyBinding: pb}) @@ -117,6 +117,8 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) roleBindings = append(roleBindings, r) } binding.Roles = roleBindings + } else { + logger.Warnf("can not find user's role bindings , uid:%v", u.UID) } if pb, ok := uidToPolicyBindings[u.UID]; ok { for _, p := range pb { @@ -128,14 +130,16 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) policyBindings = append(policyBindings, p) } binding.Policies = policyBindings + } else { + logger.Warnf("can not find user's policy bindings , uid:%v", u.UID) } res = append(res, binding) } - // add all 'allUsers' roles + // add all 'ALLUsers' roles AllUserBinding := &Binding{ - Roles: uidToRoleBinding[allUsers], - Policies: uidToPolicyBindings[allUsers], + Roles: uidToRoleBinding[ALLUsers], + Policies: uidToPolicyBindings[ALLUsers], UserName: "*", Email: "", Uid: "*", -- Gitee From c39255873455d0d6c1f9364fde62a8994d599cbc Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 28 Feb 2022 17:22:27 +0800 Subject: [PATCH 1723/2647] fix updateProductImageByNs Signed-off-by: ddh27 --- .../aslan/core/workflow/service/workflow/nsq_handlers.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index c3cc38bc..c423646e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -196,7 +196,8 @@ func (h *TaskAckHandler) handle(message *nsq.Message) error { for _, deploy := range deploys { if deploy.Enabled && !pt.ResetImage { - if err := h.updateProductImageByNs(deploy.Namespace, deploy.ProductName, deploy.ServiceName, deploy.ContainerName, deploy.Image); err != nil { + containerName := strings.TrimSuffix(deploy.ContainerName, "_"+deploy.ServiceName) + if err := h.updateProductImageByNs(deploy.Namespace, deploy.ProductName, deploy.ServiceName, containerName, deploy.Image); err != nil { h.log.Errorf("updateProductImage %v error: %v", deploy, err) continue } else { -- Gitee From d5e9cd159b199d4931046e567569fe0d2d75ff6e Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 17:31:25 +0800 Subject: [PATCH 1724/2647] fix bug Signed-off-by: mouuii --- .../core/collaboration/service/collaboration_instance.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index ca9c8316..db0fef65 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -300,11 +300,7 @@ func GetCollaborationUpdate(projectName, uid, identityType, userName string, log logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err } - err = updateVisitTime(uid, resp.UpdateInstance, logger) - if err != nil { - logger.Errorf("GetCollaborationUpdate updateVisitTime error, err msg:%s", err) - return nil, err - } + return resp, nil } -- Gitee From 7c78619e5efcfe13deaf7a5a2408c8fdae66b0ff Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 17:43:20 +0800 Subject: [PATCH 1725/2647] fix bug Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 6 +++--- .../internal/repository/models/oldpolicy.go | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 2c63d112..67cfd8a1 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -91,7 +91,7 @@ func changeToCustomType() error { return err } for _, v := range res { - if v.Namespace == "*" && v.RoleRef.Name == "admin" { + if v.Namespace == "*" && v.RoleRef.Name == "admin" && v.Type != "" { continue } query := bson.M{"name": v.Name} @@ -144,7 +144,7 @@ func addPresetRoleSystemType() error { if v.Namespace == "*" || v.Namespace == "" { query := bson.M{"name": v.Name} change := bson.M{"$set": bson.M{ - "type": "system", + "type": setting.ResourceTypeSystem, }} _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { @@ -153,7 +153,7 @@ func addPresetRoleSystemType() error { } else { query := bson.M{"name": v.Name} change := bson.M{"$set": bson.M{ - "type": "custom", + "type": setting.ResourceTypeCustom, }} _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 2e23ef3e..29b0a4c0 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -16,6 +16,10 @@ limitations under the License. package models +import ( + "github.com/koderover/zadig/pkg/setting" +) + type PolicyMeta struct { Resource string `bson:"resource" json:"resource"` Alias string `bson:"alias" json:"alias"` @@ -33,7 +37,8 @@ type RoleBinding struct { Namespace string `bson:"namespace" json:"namespace"` // RoleRef can reference a namespaced or cluster scoped Role. - RoleRef *RoleRef `bson:"role_ref" json:"roleRef"` + RoleRef *RoleRef `bson:"role_ref" json:"roleRef"` + Type setting.ResourceType `bson:"type" json:"type"` } // RoleRef contains information that points to the role being used -- Gitee From 73e711ad866063f752e29dca7730c83bf99611f6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Feb 2022 20:48:24 +0800 Subject: [PATCH 1726/2647] init system type Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/admin.yaml | 1 + pkg/cli/initconfig/cmd/contributor.yaml | 1 + pkg/cli/initconfig/cmd/project-admin.yaml | 1 + pkg/cli/initconfig/cmd/read-only.yaml | 1 + pkg/cli/initconfig/cmd/read-project-only.yaml | 1 + pkg/microservice/policy/core/service/role.go | 1 + pkg/shared/client/policy/role.go | 3 ++- 7 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml index b64adb5c..fe11e12a 100644 --- a/pkg/cli/initconfig/cmd/admin.yaml +++ b/pkg/cli/initconfig/cmd/admin.yaml @@ -1,4 +1,5 @@ name: admin +type: system rules: - verbs: - "*" diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml index 34a8ebd2..f8f58b3e 100644 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -1,4 +1,5 @@ name: contributor +type: system rules: - verbs: - get_workflow diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml index 68938e3f..39a2908d 100644 --- a/pkg/cli/initconfig/cmd/project-admin.yaml +++ b/pkg/cli/initconfig/cmd/project-admin.yaml @@ -1,4 +1,5 @@ name: project-admin +type: system rules: - verbs: - "*" diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index d30a5f3e..faa8f0ca 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -1,4 +1,5 @@ name: read-only +type: system rules: - verbs: - get_workflow diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml index cabb74a8..cbed1912 100644 --- a/pkg/cli/initconfig/cmd/read-project-only.yaml +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -1,4 +1,5 @@ name: read-project-only +type: system namespace: '' rules: - verbs: diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index a4e8399b..cc902958 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -75,6 +75,7 @@ func UpdateOrCreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, Namespace: ns, + Type: role.Type, } for _, r := range role.Rules { diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index 1aaa25c4..247b56d4 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -130,7 +130,8 @@ func (c *Client) CreatePresetRole(name string, role *Role) error { } type Role struct { - Name string `json:"name"` + Name string `json:"name"` + Type setting.ResourceType `json:"type"` Rules []*struct { Verbs []string `json:"verbs"` Resources []string `json:"resources"` -- Gitee From 58931bc60596bab14c952f78845abbd7df12cb47 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 28 Feb 2022 21:35:21 +0800 Subject: [PATCH 1727/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/git.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index cd3be307..256a22d9 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -50,6 +50,7 @@ func (r *Reaper) runGitCmds() error { noProxy := "" proxyFlag := false for _, repo := range r.Ctx.Repos { + fmt.Printf("the repo in the build is: %+v \n", *repo) if repo.EnableProxy { if !proxyFlag { envs = append(envs, fmt.Sprintf("http_proxy=%s", r.Ctx.Proxy.GetProxyURL())) -- Gitee From 4f84cfc68db1aad524b605367ebc3923818c2e1c Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 28 Feb 2022 21:37:14 +0800 Subject: [PATCH 1728/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/git.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 256a22d9..57d8d977 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -44,7 +44,7 @@ func (r *Reaper) RunGitGc(folder string) error { func (r *Reaper) runGitCmds() error { envs := r.getUserEnvs() - + fmt.Printf("the proxy we have is: %s \n", r.Ctx.Proxy.GetProxyURL()) // 如果存在github代码库,则设置代理,同时保证非github库不走代理 if r.Ctx.Proxy.EnableRepoProxy && r.Ctx.Proxy.Type == "http" { noProxy := "" -- Gitee From e3a2b8310650574344363554e364711c4b9a050d Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 28 Feb 2022 22:20:06 +0800 Subject: [PATCH 1729/2647] Fix policy not showing update time and description Signed-off-by: panxunying --- .../service/collaboration_instance.go | 9 +++++--- .../policy/core/repository/models/policy.go | 2 +- .../policy/core/repository/mongodb/policy.go | 4 +++- .../policy/core/service/bundle/opa_bundle.go | 3 --- .../policy/core/service/policy.go | 23 ++++++++++++------- pkg/shared/client/policy/policy.go | 1 + 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index db0fef65..75ffad4f 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -345,7 +345,7 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, identityT } func buildPolicyDescription(mode, userName string) string { - return mode + " " + userName + "的权限" + return mode + " " + userName + " 的权限" } func buildPolicybindingName(uid, policyName, projectName string) string { @@ -389,6 +389,7 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp } policies = append(policies, &policy.Policy{ Name: policyName, + UpdateTime: time.Now().Unix(), Description: buildPolicyDescription(mode.Name, userName), Rules: rules, }) @@ -446,8 +447,10 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp }) } updatePolicies = append(updatePolicies, &policy.Policy{ - Name: instance.PolicyName, - Rules: rules, + Name: instance.PolicyName, + Description: buildPolicyDescription(instance.CollaborationName, userName), + UpdateTime: time.Now().Unix(), + Rules: rules, }) } for _, updatePolicy := range updatePolicies { diff --git a/pkg/microservice/policy/core/repository/models/policy.go b/pkg/microservice/policy/core/repository/models/policy.go index 482c9c22..56d221b4 100644 --- a/pkg/microservice/policy/core/repository/models/policy.go +++ b/pkg/microservice/policy/core/repository/models/policy.go @@ -25,7 +25,7 @@ import ( type Policy struct { Name string `bson:"name" json:"name"` Namespace string `bson:"namespace" json:"namespace"` - Description string `json:"description" json:"description"` + Description string `bson:"description" json:"description"` CreateTime int64 `bson:"create_time" json:"create_time"` UpdateTime int64 `bson:"update_time" json:"update_time"` Rules []*Rule `bson:"rules" json:"rules"` diff --git a/pkg/microservice/policy/core/repository/mongodb/policy.go b/pkg/microservice/policy/core/repository/mongodb/policy.go index 9c26ae5e..ee47aabd 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy.go @@ -176,7 +176,9 @@ func (c *PolicyColl) UpdatePolicy(obj *models.Policy) error { query := bson.M{"name": obj.Name, "namespace": obj.Namespace} change := bson.M{"$set": bson.M{ - "rules": obj.Rules, + "rules": obj.Rules, + "description": obj.Description, + "update_time": obj.UpdateTime, }} _, err := c.UpdateOne(context.TODO(), query, change) return err diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index ab88037c..17d79c6c 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -475,9 +475,6 @@ func generateOPAPolicyRego() []byte { } func GenerateOPABundle() error { - log.Info("Generating OPA bundle") - defer log.Info("OPA bundle is generated") - rs, err := mongodb.NewRoleColl().List() if err != nil { log.Errorf("Failed to list roles, err: %s", err) diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index f67a4aed..361352b2 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -62,9 +62,11 @@ func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { var objs []*models.Policy for _, policy := range policies { obj := &models.Policy{ - Name: policy.Name, - Namespace: ns, - Type: setting.ResourceTypeSystem, + Name: policy.Name, + Namespace: ns, + Description: policy.Description, + UpdateTime: policy.UpdateTime, + Type: setting.ResourceTypeSystem, } for _, r := range policy.Rules { @@ -80,10 +82,12 @@ func CreatePolicies(ns string, policies []*Policy, _ *zap.SugaredLogger) error { return mongodb.NewPolicyColl().BulkCreate(objs) } -func UpdatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { +func UpdatePolicy(ns string, policy *Policy, log *zap.SugaredLogger) error { obj := &models.Policy{ - Name: policy.Name, - Namespace: ns, + Name: policy.Name, + Namespace: ns, + UpdateTime: policy.UpdateTime, + Description: policy.Description, } for _, r := range policy.Rules { @@ -99,8 +103,10 @@ func UpdatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { func UpdateOrCreatePolicy(ns string, policy *Policy, _ *zap.SugaredLogger) error { obj := &models.Policy{ - Name: policy.Name, - Namespace: ns, + Name: policy.Name, + Namespace: ns, + UpdateTime: policy.UpdateTime, + Description: policy.Description, } for _, r := range policy.Rules { @@ -124,6 +130,7 @@ func ListPolicies(projectName string, _ *zap.SugaredLogger) ([]*Policy, error) { policies = append(policies, &Policy{ Name: v.Name, Description: v.Description, + UpdateTime: v.UpdateTime, }) } return policies, nil diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index dfe32021..713d9a86 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -27,6 +27,7 @@ import ( type Policy struct { Name string `json:"name"` Description string `json:"description"` + UpdateTime int64 `json:"update_time"` Rules []*Rule `json:"rules"` } -- Gitee From d224574eb785d02b80693c267e09140f7288ac8a Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 1 Mar 2022 01:05:05 +0800 Subject: [PATCH 1730/2647] bugfix Signed-off-by: Min Min --- .../service/workflow/workflow_task.go | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 8d50255a..6a24786a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -552,16 +552,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, stages := make([]*commonmodels.Stage, 0) serviceInfos := make([]*taskmodels.ServiceInfo, 0) - // for the new proxy settings, we check for each repo & apply proxy if necessary for _, target := range args.Target { - for _, repo := range target.Build.Repos { - repoInfo, err := systemconfig.New().GetCodeHost(repo.CodehostID) - if err != nil { - log.Errorf("Failed to get proxy settings for codehost ID: %d, the error is: %s", repo.CodehostID, err) - return nil, err - } - repo.EnableProxy = repoInfo.EnableProxy - } var subTasks []map[string]interface{} var err error if target.JenkinsBuildArgs == nil { @@ -711,16 +702,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, AddSubtaskToStage(&stages, stask, target.Name+"_"+target.ServiceName) } } - for _, test := range args.Tests { - for _, build := range test.Builds { - repoInfo, err := systemconfig.New().GetCodeHost(build.CodehostID) - if err != nil { - log.Errorf("Failed to get proxy settings for codehost ID: %d, the error is: %s", build.CodehostID, err) - return nil, err - } - build.EnableProxy = repoInfo.EnableProxy - } - } // add extension to stage if workflow.ExtensionStage != nil && workflow.ExtensionStage.Enabled { extensionTask, err := addExtensionToSubTasks(workflow.ExtensionStage, serviceInfos) @@ -1497,6 +1478,14 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, log.Errorf("[%s]get TestingModule error: %v", testArg.TestModuleName, err) return resp, err } + for _, repo := range testModule.Repos { + repoInfo, err := systemconfig.New().GetCodeHost(repo.CodehostID) + if err != nil { + log.Errorf("Failed to get proxy settings for codehost ID: %d, the error is: %s", repo.CodehostID, err) + return nil, err + } + repo.EnableProxy = repoInfo.EnableProxy + } testTask := &taskmodels.Testing{ TaskType: config.TaskTestingV2, @@ -1861,7 +1850,6 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL subTasks = make([]map[string]interface{}, 0) serviceTmpl *commonmodels.Service ) - opt := &commonrepo.BuildListOption{ Name: args.BuildName, ServiceName: args.ServiceName, @@ -1979,6 +1967,14 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } build.JobCtx.Builds = module.Repos + for _, repo := range build.JobCtx.Builds { + repoInfo, err := systemconfig.New().GetCodeHost(repo.CodehostID) + if err != nil { + log.Errorf("Failed to get proxy settings for codehost ID: %d, the error is: %s", repo.CodehostID, err) + return nil, err + } + repo.EnableProxy = repoInfo.EnableProxy + } if len(build.JobCtx.Builds) == 0 { build.JobCtx.Builds = make([]*types.Repository, 0) } -- Gitee From b1734a0158e179cb583b251c0e9951b15229360c Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 1 Mar 2022 01:07:05 +0800 Subject: [PATCH 1731/2647] remove debug logs Signed-off-by: Min Min --- pkg/microservice/reaper/core/service/reaper/git.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 57d8d977..a7b5590a 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -44,13 +44,11 @@ func (r *Reaper) RunGitGc(folder string) error { func (r *Reaper) runGitCmds() error { envs := r.getUserEnvs() - fmt.Printf("the proxy we have is: %s \n", r.Ctx.Proxy.GetProxyURL()) // 如果存在github代码库,则设置代理,同时保证非github库不走代理 if r.Ctx.Proxy.EnableRepoProxy && r.Ctx.Proxy.Type == "http" { noProxy := "" proxyFlag := false for _, repo := range r.Ctx.Repos { - fmt.Printf("the repo in the build is: %+v \n", *repo) if repo.EnableProxy { if !proxyFlag { envs = append(envs, fmt.Sprintf("http_proxy=%s", r.Ctx.Proxy.GetProxyURL())) -- Gitee From 6a5130c9ffad1b52a0b93cebcb40e7e3f17cc0e0 Mon Sep 17 00:00:00 2001 From: fansi Date: Tue, 1 Mar 2022 09:36:32 +0800 Subject: [PATCH 1732/2647] add automation test demo Signed-off-by: fansi --- examples/test-demo/go.mod | 8 ++ examples/test-demo/go.sum | 101 ++++++++++++++++++ .../test-multi-envs/multi_env_test.go | 70 ++++++++++++ examples/test-demo/test/demo_test.go | 48 +++++++++ 4 files changed, 227 insertions(+) create mode 100644 examples/test-demo/go.mod create mode 100644 examples/test-demo/go.sum create mode 100644 examples/test-demo/test-multi-envs/multi_env_test.go create mode 100644 examples/test-demo/test/demo_test.go diff --git a/examples/test-demo/go.mod b/examples/test-demo/go.mod new file mode 100644 index 00000000..43edddc7 --- /dev/null +++ b/examples/test-demo/go.mod @@ -0,0 +1,8 @@ +module mathbot + +go 1.16 + +require ( + github.com/onsi/ginkgo/v2 v2.0.0 + github.com/onsi/gomega v1.17.0 +) \ No newline at end of file diff --git a/examples/test-demo/go.sum b/examples/test-demo/go.sum new file mode 100644 index 00000000..252fae99 --- /dev/null +++ b/examples/test-demo/go.sum @@ -0,0 +1,101 @@ +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= \ No newline at end of file diff --git a/examples/test-demo/test-multi-envs/multi_env_test.go b/examples/test-demo/test-multi-envs/multi_env_test.go new file mode 100644 index 00000000..231d214c --- /dev/null +++ b/examples/test-demo/test-multi-envs/multi_env_test.go @@ -0,0 +1,70 @@ +package multi_envs_test + +import ( + "net/http" + "os" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestMultiEnvs(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "TestDemo Suite") +} + +type SiteConfig struct { + WWWSite string + DocSite string + InvalidSite string +} + +func GetConfig() SiteConfig { + if os.Getenv("ENV_NAME") == "test" { + return SiteConfig{ + WWWSite: "https://www.koderover-test.com", + DocSite: "https://docs.koderover-test.com", + InvalidSite: "https://www.koderover-test.com.cn", + } + } + return SiteConfig{ + WWWSite: "https://www.koderover.com", + DocSite: "https://docs.koderover.com", + InvalidSite: "https://www.koderover.com.cn", + } +} + +func LifeProbe(host string) (int, error) { + req, _ := http.NewRequest("GET", host, nil) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + response, err := client.Do(req) + if err != nil { + return 0, err + } + return response.StatusCode, err +} + +var _ = Describe("ZadigWebsitesLifeProbe", func() { + It("Case1: WWW website should be OK", func() { + host := GetConfig().WWWSite + code, err := LifeProbe(host) + Expect(err).Should(BeNil()) + Expect(code).Should(Equal(200)) + }) + + It("Case2: Docs website should be OK", func() { + host := GetConfig().DocSite + code, err := LifeProbe(host) + Expect(err).Should(BeNil()) + Expect(code).Should(Equal(200)) + }) + + It("Case3: Sorry, invalid site won't work", func() { + host := GetConfig().InvalidSite + _, err := LifeProbe(host) + Expect(err).Should(Not(BeNil())) + }) +}) diff --git a/examples/test-demo/test/demo_test.go b/examples/test-demo/test/demo_test.go new file mode 100644 index 00000000..0fd2f11c --- /dev/null +++ b/examples/test-demo/test/demo_test.go @@ -0,0 +1,48 @@ +package demo_test + +import ( + "net/http" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestDemo(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "TestDemo Suite") +} + +func HelloGeek(host string) (int, error) { + req, _ := http.NewRequest("GET", host, nil) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + response, err := client.Do(req) + if err != nil { + return 0, err + } + return response.StatusCode, err +} + +var _ = Describe("ZadigWebsitesLifeProbe", func() { + It("Case1: Hello, Welcome to https://www.koderover.com", func() { + host := "https://www.koderover.com" + code, err := HelloGeek(host) + Expect(err).Should(BeNil()) + Expect(code).Should(Equal(200)) + }) + + It("Case2: Hello, Welcome to https://docs.koderover.com/", func() { + host := "https://docs.koderover.com/" + code, err := HelloGeek(host) + Expect(err).Should(BeNil()) + Expect(code).Should(Equal(200)) + }) + + It("Case3: Sorry, https://www.koderover.com.cn won't work", func() { + host := "https://www.koderover.com.cn" + _, err := HelloGeek(host) + Expect(err).Should(Not(BeNil())) + }) +}) -- Gitee From 11c812f2290b42e38b158ab6eb3751baaa34f72f Mon Sep 17 00:00:00 2001 From: fansi Date: Tue, 1 Mar 2022 11:36:49 +0800 Subject: [PATCH 1733/2647] fix comments Signed-off-by: fansi --- examples/test-demo/go.mod | 2 +- examples/test-demo/test-multi-envs/multi_env_test.go | 2 +- examples/test-demo/test/demo_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/test-demo/go.mod b/examples/test-demo/go.mod index 43edddc7..283c891b 100644 --- a/examples/test-demo/go.mod +++ b/examples/test-demo/go.mod @@ -1,4 +1,4 @@ -module mathbot +module test-demo go 1.16 diff --git a/examples/test-demo/test-multi-envs/multi_env_test.go b/examples/test-demo/test-multi-envs/multi_env_test.go index 231d214c..cfd8e2ea 100644 --- a/examples/test-demo/test-multi-envs/multi_env_test.go +++ b/examples/test-demo/test-multi-envs/multi_env_test.go @@ -1,4 +1,4 @@ -package multi_envs_test +package test_multi_envs import ( "net/http" diff --git a/examples/test-demo/test/demo_test.go b/examples/test-demo/test/demo_test.go index 0fd2f11c..a99d3afa 100644 --- a/examples/test-demo/test/demo_test.go +++ b/examples/test-demo/test/demo_test.go @@ -1,4 +1,4 @@ -package demo_test +package test import ( "net/http" -- Gitee From 73a3da8ad9bcae4ff3e98044d68c6b4df8699c7c Mon Sep 17 00:00:00 2001 From: fansi Date: Tue, 1 Mar 2022 11:40:04 +0800 Subject: [PATCH 1734/2647] add license for test demo Signed-off-by: fansi --- .../test-demo/test-multi-envs/multi_env_test.go | 16 ++++++++++++++++ examples/test-demo/test/demo_test.go | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/examples/test-demo/test-multi-envs/multi_env_test.go b/examples/test-demo/test-multi-envs/multi_env_test.go index cfd8e2ea..d3156621 100644 --- a/examples/test-demo/test-multi-envs/multi_env_test.go +++ b/examples/test-demo/test-multi-envs/multi_env_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package test_multi_envs import ( diff --git a/examples/test-demo/test/demo_test.go b/examples/test-demo/test/demo_test.go index a99d3afa..2b80996c 100644 --- a/examples/test-demo/test/demo_test.go +++ b/examples/test-demo/test/demo_test.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package test import ( -- Gitee From f4213a3c42a0df28fa5ac9b2e977e5c32c115712 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 1 Mar 2022 12:00:17 +0800 Subject: [PATCH 1735/2647] add user can select db Signed-off-by: panxunying --- pkg/config/config.go | 4 ++++ pkg/microservice/user/core/service.go | 3 ++- pkg/microservice/user/core/service/init/mysql.sql | 4 ++-- pkg/setting/consts.go | 1 + 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 329c2ae3..c6123172 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -246,6 +246,10 @@ func MysqlUser() string { return viper.GetString(setting.ENVMysqlUser) } +func MysqlUserDB() string { + return viper.GetString(setting.ENVMysqlUserDB) +} + func MysqlPassword() string { return viper.GetString(setting.ENVMysqlPassword) } diff --git a/pkg/microservice/user/core/service.go b/pkg/microservice/user/core/service.go index 869f59fe..89296880 100644 --- a/pkg/microservice/user/core/service.go +++ b/pkg/microservice/user/core/service.go @@ -49,7 +49,8 @@ func initDBAndTables() { log.Panic(err) } defer db.Close() - _, err = db.Exec(string(mysql)) + initSql := fmt.Sprintf(string(mysql), config.MysqlUserDB(), config.MysqlUserDB()) + _, err = db.Exec(initSql) if err != nil { log.Panic(err) diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql index 75603daf..cd01afa7 100644 --- a/pkg/microservice/user/core/service/init/mysql.sql +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -1,5 +1,5 @@ -CREATE DATABASE IF NOT EXISTS user; -use user; +CREATE DATABASE IF NOT EXISTS %s; +use %s; CREATE TABLE IF NOT EXISTS `user_login`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL DEFAULT '0' COMMENT '用户id', diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 909a7fc9..d5a91f91 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -34,6 +34,7 @@ const ( ENVMysqlUser = "MYSQL_USER" ENVMysqlPassword = "MYSQL_PASSWORD" ENVMysqlHost = "MYSQL_HOST" + ENVMysqlUserDb = "MYSQL_USER_DB" // Aslan ENVPodName = "BE_POD_NAME" -- Gitee From aeb61ae30bc4aba610448383860ab9af0352735b Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 1 Mar 2022 12:22:11 +0800 Subject: [PATCH 1736/2647] add user can select db Signed-off-by: panxunying --- .../internal/repository/orm/addmysql.sql | 2 +- pkg/cli/upgradeassistant/internal/repository/orm/db.go | 9 +++++---- .../internal/repository/orm/dropmysql.sql | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql index 2fb64081..197aba07 100644 --- a/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql +++ b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql @@ -1,3 +1,3 @@ -use user; +use %s; ALTER TABLE `user` ADD `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken'; \ No newline at end of file diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/db.go b/pkg/cli/upgradeassistant/internal/repository/orm/db.go index 3fa9c9cd..81b60c34 100644 --- a/pkg/cli/upgradeassistant/internal/repository/orm/db.go +++ b/pkg/cli/upgradeassistant/internal/repository/orm/db.go @@ -40,12 +40,13 @@ var addmysql []byte var dropmysql []byte func UpdateUserDBTables(action DbEditAction) error { - var mysql []byte + var mysql string switch action { case DbEditActionAdd: - mysql = addmysql + mysql = fmt.Sprintf(string(addmysql), config.MysqlUserDB()) + case DbEditActionDrop: - mysql = dropmysql + mysql = fmt.Sprintf(string(dropmysql), config.MysqlUserDB()) } if len(mysql) == 0 { return fmt.Errorf("%smysql.sql is empty", action) @@ -58,6 +59,6 @@ func UpdateUserDBTables(action DbEditAction) error { return err } defer db.Close() - _, err = db.Exec(string(mysql)) + _, err = db.Exec(mysql) return err } diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql index cb67a73b..d973c425 100644 --- a/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql +++ b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql @@ -1,4 +1,4 @@ -use user; +use %s; LOCK TABLES `user` WRITE; ALTER TABLE `user` DROP COLUMN `api_token`; -- Gitee From ed7c20e60b22e3a991668fef21bf7631f909824b Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 1 Mar 2022 15:03:57 +0800 Subject: [PATCH 1737/2647] support user-defined env when using pvc Signed-off-by: zhangyifei --- .../service/workflow/pipeline_task.go | 1 + .../service/workflow/pipeline_validation.go | 1 + .../core/service/taskplugin/build.go | 26 +++++++++++++++++ .../core/service/taskplugin/testing.go | 29 +++++++++++++++++++ 4 files changed, 57 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index b090ccb1..3911fc3b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -737,6 +737,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * } } envs = append(envs, &commonmodels.KeyVal{Key: "TEST_URL", Value: GetLink(pt, configbase.SystemAddress(), config.TestType)}) + envs = append(envs, &commonmodels.KeyVal{Key: "WORKSPACE", Value: "/workspace"}) testTask.JobCtx.EnvVars = envs testTask.ImageID = testModule.PreTest.ImageID testTask.BuildOS = testModule.PreTest.BuildOS diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 994adcbe..5013af75 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -618,6 +618,7 @@ func prepareTaskEnvs(pt *task.Task, log *zap.SugaredLogger) []*commonmodels.KeyV &commonmodels.KeyVal{Key: "IMAGE", Value: pt.TaskArgs.Deploy.Image}, &commonmodels.KeyVal{Key: "PKG_FILE", Value: pt.TaskArgs.Deploy.PackageFile}, &commonmodels.KeyVal{Key: "LOG_FILE", Value: "/tmp/user_script.log"}, + &commonmodels.KeyVal{Key: "WORKSPACE", Value: "/workspace"}, ) // 设置编译模块参数化配置信息 diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 8025923b..63a99d0e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "math/rand" + "os" "strconv" "strings" "time" @@ -36,6 +37,7 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/types" ) const ( @@ -218,6 +220,12 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } } + // Since we allow users to use custom environment variables, variable resolution is required. + if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == types.NFSMedium && + pipelineCtx.CacheDirType == types.UserDefinedCacheDir { + pipelineCtx.CacheUserDir = p.renderEnv(pipelineCtx.CacheUserDir) + } + jobCtx := JobCtxBuilder{ JobName: p.JobName, PipelineCtx: pipelineCtx, @@ -426,3 +434,21 @@ func (p *BuildTaskPlugin) IsTaskEnabled() bool { func (p *BuildTaskPlugin) ResetError() { p.Task.Error = "" } + +// Note: Since there are few environment variables and few variables to be replaced, +// this method is temporarily used. +func (p *BuildTaskPlugin) renderEnv(data string) string { + mapper := func(data string) string { + for _, envar := range p.Task.JobCtx.EnvVars { + if data != envar.Key { + continue + } + + return envar.Value + } + + return fmt.Sprintf("$%s", data) + } + + return os.Expand(data, mapper) +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 9a1cc3a9..93935d01 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -39,6 +39,7 @@ import ( krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" s3tool "github.com/koderover/zadig/pkg/tool/s3" + commontypes "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" ) @@ -193,6 +194,16 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC fileName = strings.Replace(strings.ToLower(fileName), "_", "-", -1) testReportFile = strings.Replace(strings.ToLower(testReportFile), "_", "-", -1) + for _, env := range p.Task.JobCtx.EnvVars { + p.Log.Infof("[YIFEI] k: %s, v: %s", env.Key, env.Value) + } + + // Since we allow users to use custom environment variables, variable resolution is required. + if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == commontypes.NFSMedium && + pipelineCtx.CacheDirType == commontypes.UserDefinedCacheDir { + pipelineCtx.CacheUserDir = p.renderEnv(pipelineCtx.CacheUserDir) + } + jobCtx := JobCtxBuilder{ JobName: p.JobName, PipelineCtx: pipelineCtx, @@ -527,3 +538,21 @@ func (p *TestPlugin) IsTaskEnabled() bool { func (p *TestPlugin) ResetError() { p.Task.Error = "" } + +// Note: Since there are few environment variables and few variables to be replaced, +// this method is temporarily used. +func (p *TestPlugin) renderEnv(data string) string { + mapper := func(data string) string { + for _, envar := range p.Task.JobCtx.EnvVars { + if data != envar.Key { + continue + } + + return envar.Value + } + + return fmt.Sprintf("$%s", data) + } + + return os.Expand(data, mapper) +} -- Gitee From bce378fb546d51b2485e77f8e5e505868a8e10c6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 1 Mar 2022 17:48:02 +0800 Subject: [PATCH 1738/2647] remove tag Signed-off-by: mouuii --- pkg/microservice/aslan/core/common/repository/models/testing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index 243b6f35..d99a2c4a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -30,7 +30,7 @@ type Testing struct { Desc string `bson:"desc" json:"desc"` Timeout int `bson:"timeout" json:"timeout"` Team string `bson:"team" json:"team"` - Repos []*types.Repository `bson:"repos,omitempty" json:"repos"` + Repos []*types.Repository `bson:"repos" json:"repos"` PreTest *PreTest `bson:"pre_test" json:"pre_test"` Scripts string `bson:"scripts" json:"scripts"` UpdateTime int64 `bson:"update_time" json:"update_time"` -- Gitee From 3f17d69a267081ed6f1bee8dd640249bb2cc43ff Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 1 Mar 2022 20:06:32 +0800 Subject: [PATCH 1739/2647] add collaboration recycle Signed-off-by: panxunying --- .../core/collaboration/handler/router.go | 2 +- .../repository/mongodb/collaboration_mode.go | 1 + .../service/collaboration_instance.go | 42 ++++++++++++------- .../aslan/core/environment/service/bundle.go | 3 -- .../label/repository/mongodb/labelbinding.go | 31 ++++++++++---- .../aslan/core/label/service/label.go | 21 +--------- .../core/workflow/service/workflow/bundle.go | 3 -- .../cron/core/service/client/cronjob.go | 2 +- .../policy/core/handler/permission.go | 4 -- .../policy/core/handler/policy.go | 18 +------- .../policy/core/repository/mongodb/policy.go | 2 +- .../core/repository/mongodb/policy_binding.go | 6 +-- .../policy/core/service/policy.go | 7 ---- pkg/shared/client/policy/policy.go | 12 +++--- 14 files changed, 66 insertions(+), 88 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 9dc40da2..1807156a 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -32,7 +32,7 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations.PUT("/:name", middleware.UpdateOperationLogStatus, UpdateCollaborationMode) collaborations.POST("", middleware.UpdateOperationLogStatus, CreateCollaborationMode) collaborations.DELETE("/:name", middleware.UpdateOperationLogStatus, DeleteCollaborationMode) - collaborations.POST("/cron/clean", middleware.UpdateOperationLogStatus, CleanCIResources) + collaborations.GET("/cron/clean", CleanCIResources) collaborations.GET("/new", GetCollaborationNew) collaborations.POST("/sync", SyncCollaborationInstance) } diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go index 3f1d87de..41987f12 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_mode.go @@ -100,6 +100,7 @@ func (c *CollaborationModeColl) Update(username string, args *models.Collaborati "update_by": username, "members": args.Members, "workflows": args.Workflows, + "recycle_day": args.RecycleDay, "revision": res.Revision + 1, "products": args.Products, }} diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 62d9324b..8ac200b2 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -21,7 +21,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/label/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/label" "github.com/koderover/zadig/pkg/shared/client/policy" ) @@ -222,6 +221,7 @@ func genCollaborationInstance(mode models.CollaborationMode, projectName, uid, i UserUID: uid, PolicyName: buildPolicyName(projectName, mode.Name, identityType, userName), Revision: mode.Revision, + RecycleDay: mode.RecycleDay, Workflows: workflows, Products: products, LastVisitTime: time.Now().Unix(), @@ -260,9 +260,10 @@ func getDiff(cmMap map[string]*models.CollaborationMode, ciMap map[string]*model }, nil } -func updateVisitTime(uid string, cis []models.CollaborationInstance, logger *zap.SugaredLogger) error { +func updateVisitTime(uid string, cis []*models.CollaborationInstance, logger *zap.SugaredLogger) error { for _, instance := range cis { - err := mongodb.NewCollaborationInstanceColl().Update(uid, &instance) + instance.LastVisitTime = time.Now().Unix() + err := mongodb.NewCollaborationInstanceColl().Update(uid, instance) if err != nil { logger.Errorf("syncInstance Update error, error msg:%s", err) return err @@ -301,7 +302,11 @@ func GetCollaborationUpdate(projectName, uid, identityType, userName string, log logger.Errorf("GetCollaborationUpdate error, err msg:%s", err) return nil, err } - + err = updateVisitTime(uid, collaborationInstances, logger) + if err != nil { + logger.Errorf("GetCollaborationUpdate updateVisitTime error, err msg:%s", err) + return nil, err + } return resp, nil } @@ -1173,7 +1178,8 @@ func CleanCIResources(userName, requestID string, logger *zap.SugaredLogger) err } var fileterdInstances []*models.CollaborationInstance for _, ci := range cis { - if (time.Now().Unix()-ci.LastVisitTime)/60 > ci.RecycleDay*24*60 { + + if ci.RecycleDay != 0 && ((time.Now().Unix()-ci.LastVisitTime)/60 > ci.RecycleDay*24*60) { fileterdInstances = append(fileterdInstances, ci) } } @@ -1204,13 +1210,6 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn logger.Errorf("BulkDelete CollaborationInstance error:%s", err) return err } - err = policy.NewDefault().DeletePolicies("", policy.DeletePoliciesArgs{ - Names: policyNames, - }) - if err != nil { - logger.Errorf("BulkDelete policy error:%s", err) - return err - } names = cis[0].PolicyName for i := 1; i < len(cis); i++ { names = names + "," + cis[i].PolicyName @@ -1219,14 +1218,15 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn if err != nil { return err } - var labels []label.LabelModel + logger.Infof("policies:%v", res) + var labels []mongodb2.Label labelSet := sets.String{} for _, re := range res { for _, rule := range re.Rules { for _, attribute := range rule.MatchAttributes { if attribute.Key != "placeholder" && attribute.Key != "production" && !labelSet.Has(attribute.Key+"-"+attribute.Value) { - labels = append(labels, label.LabelModel{ + labels = append(labels, mongodb2.Label{ Key: attribute.Key, Value: attribute.Value, }) @@ -1235,7 +1235,8 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn } } } - labelRes, err := label.New().ListLabels(label.ListLabelsArgs{ + logger.Infof("labels:%v", labels) + labelRes, err := service.ListLabels(&service.ListLabelsArgs{ Labels: labels, }) if err != nil { @@ -1245,7 +1246,8 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn for _, l := range labelRes.Labels { labelIds = append(labelIds, l.ID.Hex()) } - err = label.New().DeleteLabels(labelIds) + logger.Infof("labelIds:%v", labelIds) + err = service.DeleteLabels(labelIds, true, logger) if err != nil { return err } @@ -1267,6 +1269,14 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn } } } + err = policy.NewDefault().DeletePolicies("", policy.DeletePoliciesArgs{ + Names: policyNames, + }) + if err != nil { + logger.Errorf("BulkDelete policy error:%s", err) + return err + } + return nil } diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 7a28f0d0..c0b109a4 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -84,9 +84,6 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { for _, v := range labels { resourceSpec.Spec[v.Key] = v.Value } - } else { - // TODO improve the resource key not found logic - //logger.Warnf("can not find resource key :%s", resourceKey) } clusterID := env.ClusterID diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index 51a4ec77..f003130c 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -129,8 +129,8 @@ func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.La query["resource_type"] = opt.ResourceType } ctx := context.Background() - opts := options.Find() - cursor, err := c.Collection.Find(ctx, query, opts) + + cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err } @@ -142,21 +142,38 @@ func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.La return ret, err } +func (c *LabelBindingColl) BulkDeleteByLabelIds(labelIds []string) error { + if len(labelIds) == 0 { + return nil + } + + condition := bson.A{} + for _, id := range labelIds { + condition = append(condition, bson.M{ + "label_id": id, + }) + } + query := bson.D{{"$or", condition}} + _, err := c.DeleteMany(context.TODO(), query) + return err +} + func (c *LabelBindingColl) BulkDeleteByIds(ids []string) error { if len(ids) == 0 { return nil } - query := bson.M{} - var oids []primitive.ObjectID + + condition := bson.A{} for _, id := range ids { oid, err := primitive.ObjectIDFromHex(id) if err != nil { return err } - oids = append(oids, oid) + condition = append(condition, bson.M{ + "_id": oid, + }) } - query["_id"] = bson.M{"$in": oids} - + query := bson.D{{"$or", condition}} _, err := c.DeleteMany(context.TODO(), query) return err } diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 0fe66636..b31310d1 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -25,7 +25,6 @@ import ( "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" - e "github.com/koderover/zadig/pkg/tool/errors" ) type CreateLabelsResp struct { @@ -206,27 +205,11 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err return nil } - res, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: ids}) - if err != nil { - logger.Errorf("list labelbingding err:%s", err) - return err - } - - if !forceDelete && len(res) > 0 { - return e.ErrForbidden.AddDesc("some label has already bind resource, can not delete") - } - if forceDelete { - var labelBindingIDs []string - for _, labelBinding := range res { - labelBindingIDs = append(ids, labelBinding.ID.Hex()) - } - - if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { - logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, labelBindingIDs) + if err := mongodb.NewLabelBindingColl().BulkDeleteByLabelIds(ids); err != nil { + logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, ids) return err } - return mongodb.NewLabelColl().BulkDelete(ids) } return mongodb.NewLabelColl().BulkDelete(ids) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 8cb48408..3a9c0f41 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -67,9 +67,6 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { for _, v := range labels { resourceSpec.Spec[v.Key] = v.Value } - } else { - //TODO improve the resource key not found logic - //logger.Warnf("can not find resource key :%s", resourceKey) } res = append(res, resourceSpec) } diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 23ce5a51..4f528488 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -68,7 +68,7 @@ func (c *Client) TriggerCleanProducts(log *zap.SugaredLogger) error { // TriggerCleanCIResources trigger clean CollaborationInstance Resources func (c *Client) TriggerCleanCIResources(log *zap.SugaredLogger) error { - url := fmt.Sprintf("%s/collaborations/cron/clean", c.APIBase) + url := fmt.Sprintf("%s/collaboration/collaborations/cron/clean", c.APIBase) log.Info("start clean CollaborationInstance Resources..") err := c.sendRequest(url) if err != nil { diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index 3c0486a6..fdf51bbf 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -29,10 +29,6 @@ func GetUserPermission(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } ctx.Resp, ctx.Err = service.GetPermission(projectName, c.Param("uid"), ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/policy.go b/pkg/microservice/policy/core/handler/policy.go index 151e3074..d19218ec 100644 --- a/pkg/microservice/policy/core/handler/policy.go +++ b/pkg/microservice/policy/core/handler/policy.go @@ -123,10 +123,6 @@ func ListPolicies(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } ctx.Resp, ctx.Err = service.ListPolicies(projectName, ctx.Logger) } @@ -136,10 +132,6 @@ func GetPolicy(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } ctx.Resp, ctx.Err = service.GetPolicy(projectName, c.Param("name"), ctx.Logger) } @@ -148,7 +140,7 @@ func GetPolicies(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetPolicies(c.Param("names"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetPolicies(c.Query("names"), ctx.Logger) } func CreatePublicPolicy(c *gin.Context) { @@ -185,10 +177,6 @@ func DeletePolicy(c *gin.Context) { name := c.Param("name") projectName := c.Query("projectName") - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } ctx.Err = service.DeletePolicy(name, projectName, ctx.Logger) } @@ -198,10 +186,6 @@ func DeletePolicies(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("args projectName can't be empty") - return - } args := &deletePoliciesArgs{} if err := c.ShouldBindJSON(args); err != nil { diff --git a/pkg/microservice/policy/core/repository/mongodb/policy.go b/pkg/microservice/policy/core/repository/mongodb/policy.go index 750d97ab..88b8919f 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy.go @@ -66,7 +66,7 @@ func (c *PolicyColl) GetByNames(names []string) ([]*models.Policy, error) { query := bson.M{} query["name"] = bson.M{"$in": names} - cursor, err := c.Collection.Aggregate(context.TODO(), query) + cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { return nil, err } diff --git a/pkg/microservice/policy/core/repository/mongodb/policy_binding.go b/pkg/microservice/policy/core/repository/mongodb/policy_binding.go index 69c30869..26575501 100644 --- a/pkg/microservice/policy/core/repository/mongodb/policy_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/policy_binding.go @@ -150,11 +150,11 @@ func (c *PolicyBindingColl) DeleteByPolicy(policyName string, projectName string } func (c *PolicyBindingColl) DeleteByPolicies(policyNames []string, projectName string) error { - if projectName == "" { - return fmt.Errorf("projectName is empty") + query := bson.M{} + if len(projectName) != 0 { + query = bson.M{"policy_ref.namespace": projectName, "namespace": projectName} } - query := bson.M{"policy_ref.namespace": projectName, "namespace": projectName} if len(policyNames) != 0 { query["policy_ref.name"] = bson.M{"$in": policyNames} } diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index d3c16f75..365f5f48 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -232,13 +232,6 @@ func DeletePolicy(name string, projectName string, logger *zap.SugaredLogger) er } func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogger) error { - if projectName == "" { - return fmt.Errorf("projectName is empty") - } - - if len(names) == 1 && names[0] == "*" { - names = []string{} - } err := mongodb.NewPolicyColl().DeleteMany(names, projectName) if err != nil { diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index 78937499..c3026b5d 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -70,7 +70,7 @@ func (c *Client) CreatePolicies(ns string, request CreatePoliciesArgs) error { _, err := c.Post(url, httpclient.SetBody(request)) if err != nil { - log.Errorf("Failed to add audit log, error: %s", err) + log.Errorf("Failed to CreatePolicies, error: %s", err) return err } @@ -82,7 +82,7 @@ func (c *Client) DeletePolicies(ns string, request DeletePoliciesArgs) error { _, err := c.Post(url, httpclient.SetBody(request)) if err != nil { - log.Errorf("Failed to add audit log, error: %s", err) + log.Errorf("Failed to DeletePolicies, error: %s", err) return err } @@ -124,11 +124,11 @@ func (c *Client) GetResourcePermission(req *ResourcePermissionReq) (map[string][ } func (c *Client) GetPolicies(names string) ([]*Policy, error) { - url := fmt.Sprintf("/policies") - - var res []*Policy - _, err := c.Get(url, httpclient.SetQueryParam("names", names), httpclient.SetResult(res)) + url := fmt.Sprintf("/policies/bulk") + res := make([]*Policy, 0) + _, err := c.Get(url, httpclient.SetQueryParam("names", names), httpclient.SetResult(&res)) if err != nil { + log.Errorf("Failed to GetPolicies,err: %s", err) return nil, err } return res, nil -- Gitee From 3b2534d0998166daab2b4e12719eb7691f8183da Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 1 Mar 2022 20:33:18 +0800 Subject: [PATCH 1740/2647] optimize Signed-off-by: panxunying --- .../service/collaboration_instance.go | 4 +-- .../cron/core/service/scheduler/scheduler.go | 29 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 8ac200b2..37dc2879 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1235,7 +1235,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn } } } - logger.Infof("labels:%v", labels) + labelRes, err := service.ListLabels(&service.ListLabelsArgs{ Labels: labels, }) @@ -1246,7 +1246,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn for _, l := range labelRes.Labels { labelIds = append(labelIds, l.ID.Hex()) } - logger.Infof("labelIds:%v", labelIds) + err = service.DeleteLabels(labelIds, true, logger) if err != nil { return err diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index bfe943dd..8b1fc7c2 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -50,29 +50,27 @@ type CronClient struct { } const ( - // CleanJobScheduler ... CleanJobScheduler = "CleanJobScheduler" - // UpsertWorkflowScheduler ... + UpsertWorkflowScheduler = "UpsertWorkflowScheduler" - // UpsertTestScheduler ... + UpsertTestScheduler = "UpsertTestScheduler" - // UpsertColliePipelineScheduler ... + UpsertColliePipelineScheduler = "UpsertColliePipelineScheduler" - //CleanProductScheduler ... + CleanProductScheduler = "CleanProductScheduler" - //CleanCIResourcesScheduler ... + CleanCIResourcesScheduler = "CleanCIResourcesScheduler" - //InitBuildStatScheduler + InitStatScheduler = "InitStatScheduler" - //InitOperationStatScheduler + InitOperationStatScheduler = "InitOperationStatScheduler" - //InitPullSonarStatScheduler InitPullSonarStatScheduler = "InitPullSonarStatScheduler" // SystemCapacityGC periodically triggers garbage collection for system data based on its retention policy. SystemCapacityGC = "SystemCapacityGC" - //InitHealthCheckScheduler + InitHealthCheckScheduler = "InitHealthCheckScheduler" ) @@ -147,7 +145,7 @@ func (c *CronClient) Init() { // 定时清理环境 c.InitCleanProductScheduler() - // 定时清理协作模式资源 + // clean collaboration instance resource every 5 minutes c.InitCleanCIResourcesScheduler() // 定时初始化构建数据 c.InitBuildStatScheduler() @@ -159,7 +157,6 @@ func (c *CronClient) Init() { c.InitHealthCheckScheduler() } -// InitCleanJobScheduler ... func (c *CronClient) InitCleanJobScheduler() { c.Schedulers[CleanJobScheduler] = gocron.NewScheduler() @@ -169,7 +166,6 @@ func (c *CronClient) InitCleanJobScheduler() { c.Schedulers[CleanJobScheduler].Start() } -// InitCleanProductScheduler ... func (c *CronClient) InitCleanProductScheduler() { c.Schedulers[CleanProductScheduler] = gocron.NewScheduler() @@ -179,7 +175,6 @@ func (c *CronClient) InitCleanProductScheduler() { c.Schedulers[CleanProductScheduler].Start() } -// InitCleanCIResourcesScheduler ... func (c *CronClient) InitCleanCIResourcesScheduler() { c.Schedulers[CleanCIResourcesScheduler] = gocron.NewScheduler() @@ -189,7 +184,6 @@ func (c *CronClient) InitCleanCIResourcesScheduler() { c.Schedulers[CleanCIResourcesScheduler].Start() } -// InitJobScheduler ... func (c *CronClient) InitJobScheduler() { c.Schedulers[UpsertWorkflowScheduler] = gocron.NewScheduler() @@ -199,7 +193,6 @@ func (c *CronClient) InitJobScheduler() { c.Schedulers[UpsertWorkflowScheduler].Start() } -// InitTestScheduler ... func (c *CronClient) InitTestScheduler() { c.Schedulers[UpsertTestScheduler] = gocron.NewScheduler() @@ -209,7 +202,6 @@ func (c *CronClient) InitTestScheduler() { c.Schedulers[UpsertTestScheduler].Start() } -// InitJobScheduler ... func (c *CronClient) InitColliePipelineScheduler() { c.Schedulers[UpsertColliePipelineScheduler] = gocron.NewScheduler() @@ -219,7 +211,6 @@ func (c *CronClient) InitColliePipelineScheduler() { c.Schedulers[UpsertColliePipelineScheduler].Start() } -// InitBuildStatScheduler ... func (c *CronClient) InitBuildStatScheduler() { c.Schedulers[InitStatScheduler] = gocron.NewScheduler() @@ -228,7 +219,6 @@ func (c *CronClient) InitBuildStatScheduler() { c.Schedulers[InitStatScheduler].Start() } -// InitOperationStatScheduler ... func (c *CronClient) InitOperationStatScheduler() { c.Schedulers[InitOperationStatScheduler] = gocron.NewScheduler() @@ -238,7 +228,6 @@ func (c *CronClient) InitOperationStatScheduler() { c.Schedulers[InitOperationStatScheduler].Start() } -// InitPullSonarStatScheduler ... func (c *CronClient) InitPullSonarStatScheduler() { c.Schedulers[InitPullSonarStatScheduler] = gocron.NewScheduler() -- Gitee From 10e0c25ed48cad7c38538055d7b81204e78381d2 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 1 Mar 2022 21:08:51 +0800 Subject: [PATCH 1741/2647] optimize Signed-off-by: panxunying --- .../core/collaboration/service/collaboration_instance.go | 2 +- pkg/microservice/cron/core/service/client/cronjob.go | 2 +- pkg/shared/client/policy/policy.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 37dc2879..488a7d07 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1218,7 +1218,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn if err != nil { return err } - logger.Infof("policies:%v", res) + var labels []mongodb2.Label labelSet := sets.String{} for _, re := range res { diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 4f528488..3d596ed3 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -72,7 +72,7 @@ func (c *Client) TriggerCleanCIResources(log *zap.SugaredLogger) error { log.Info("start clean CollaborationInstance Resources..") err := c.sendRequest(url) if err != nil { - log.Errorf("trigger clean CollaborationInstance Resources error :%v", err) + log.Errorf("trigger clean CollaborationInstance Resources error :%s", err) } return err } diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index c3026b5d..c7404043 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -70,7 +70,7 @@ func (c *Client) CreatePolicies(ns string, request CreatePoliciesArgs) error { _, err := c.Post(url, httpclient.SetBody(request)) if err != nil { - log.Errorf("Failed to CreatePolicies, error: %s", err) + log.Errorf("Failed to createPolicies, error: %s", err) return err } @@ -82,7 +82,7 @@ func (c *Client) DeletePolicies(ns string, request DeletePoliciesArgs) error { _, err := c.Post(url, httpclient.SetBody(request)) if err != nil { - log.Errorf("Failed to DeletePolicies, error: %s", err) + log.Errorf("Failed to deletePolicies, error: %s", err) return err } @@ -128,7 +128,7 @@ func (c *Client) GetPolicies(names string) ([]*Policy, error) { res := make([]*Policy, 0) _, err := c.Get(url, httpclient.SetQueryParam("names", names), httpclient.SetResult(&res)) if err != nil { - log.Errorf("Failed to GetPolicies,err: %s", err) + log.Errorf("Failed to getPolicies,err: %s", err) return nil, err } return res, nil -- Gitee From 8cb43b2342a0bca913b1b45ca3955de2ff64c244 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 1 Mar 2022 21:46:59 +0800 Subject: [PATCH 1742/2647] project admin have permission to get clusters Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/handler/build_policy.yaml | 2 ++ .../aslan/core/project/handler/service_policy.yaml | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/project/handler/build_policy.yaml b/pkg/microservice/aslan/core/project/handler/build_policy.yaml index dac83b14..31016b2b 100644 --- a/pkg/microservice/aslan/core/project/handler/build_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/build_policy.yaml @@ -12,6 +12,8 @@ rules: endpoint: "/api/aslan/build/build/?*" - method: GET endpoint: "/api/aslan/build/targets" + - method: GET + endpoint: "/api/aslan/cluster/clusters" - action: create_build alias: "新建" description: "" diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml index 6ed43fe6..d65da237 100644 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -24,6 +24,8 @@ rules: endpoint: "/api/aslan/service/helm/?*/?*/serviceModule" - method: GET endpoint: "/api/aslan/service/services/?*/pm" + - method: GET + endpoint: "/api/aslan/service/helm/?*/?*/fileContent" - action: edit_service alias: "编辑" description: "" @@ -72,6 +74,8 @@ rules: endpoint: "/api/aslan/build/build/?*" - method: GET endpoint: "/api/aslan/build/targets" + - method: GET + endpoint: "/api/aslan/cluster/clusters" - action: create_build alias: "新建构建配置" description: "" -- Gitee From a33fb691a5ada88b6b77e1a7e58fef40da74c6ed Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 2 Mar 2022 14:41:21 +0800 Subject: [PATCH 1743/2647] delete many bug Signed-off-by: mouuii --- pkg/microservice/policy/core/repository/mongodb/role_binding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index c02dbf9c..cd9a0216 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -131,7 +131,7 @@ func (c *RoleBindingColl) DeleteMany(names []string, projectName string, userID } if userID != "" { - query = bson.M{"subjects.uid": userID} + query["subjects.uid"] = userID } _, err := c.Collection.DeleteMany(context.TODO(), query) -- Gitee From ba828bc789b4b1aba9304ac69ed00c4f18262fb8 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 2 Mar 2022 15:07:49 +0800 Subject: [PATCH 1744/2647] fix bug of printing real-time logs Signed-off-by: zhangyifei --- .../aslan/core/log/service/sse.go | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 5fd401e4..42d34b5b 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -121,13 +121,14 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op } log.Debugf("Start to get task container log.") - var serviceName string + var serviceName, serviceModule string serviceNames := strings.Split(options.ServiceName, "_") switch len(serviceNames) { case 1: - serviceName = serviceNames[0] + serviceModule = serviceNames[0] case 2: - // Note: Starting from V1.10.0, this field will be in the format of `ServiceComponent_ServiceName`. + // Note: Starting from V1.10.0, this field will be in the format of `ServiceModule_ServiceName`. + serviceModule = serviceNames[0] serviceName = serviceNames[1] } @@ -141,16 +142,25 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op options.TaskID = taskObj.TaskID } } else if options.ProductName != "" { - build, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ + buildFindOptions := &commonrepo.BuildFindOption{ ProductName: options.ProductName, - Targets: []string{serviceName}, - }) + Targets: []string{serviceModule}, + } + if serviceName != "" { + buildFindOptions.ServiceName = serviceName + } + + build, err := commonrepo.NewBuildColl().Find(buildFindOptions) if err != nil { // Maybe this service is a shared service - build, err = commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ - Targets: []string{serviceName}, - }) + buildFindOptions := &commonrepo.BuildFindOption{ + Targets: []string{serviceModule}, + } + if serviceName != "" { + buildFindOptions.ServiceName = serviceName + } + build, err = commonrepo.NewBuildColl().Find(buildFindOptions) if err != nil { log.Errorf("Failed to query build for service %s: %s", serviceName, err) return -- Gitee From 9f949fcd31386e0b1bf546faf09e06daf5075256 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 2 Mar 2022 17:56:14 +0800 Subject: [PATCH 1745/2647] user list bug Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/rolebinding.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 71cb6e6d..2c098832 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -87,7 +87,9 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) } uidToPolicyBindings[pb.UID] = append(uidToPolicyBindings[pb.UID], &policyBinding{PolicyBinding: pb}) } - + if uidSets.Len() == 0 { + return []*Binding{}, nil + } users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uidSets.List()}) if err != nil { logger.Errorf("Failed to list users, err: %s", err) -- Gitee From 6143c32732edc6905cb87219cd28aba667881ffb Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 2 Mar 2022 19:08:00 +0800 Subject: [PATCH 1746/2647] non user Signed-off-by: mouuii --- .../picket/core/filter/service/rolebinding.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 2c098832..f80888e8 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -88,7 +88,15 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) uidToPolicyBindings[pb.UID] = append(uidToPolicyBindings[pb.UID], &policyBinding{PolicyBinding: pb}) } if uidSets.Len() == 0 { - return []*Binding{}, nil + // add all 'ALLUsers' roles + AllUserBinding := &Binding{ + Roles: uidToRoleBinding[ALLUsers], + Policies: uidToPolicyBindings[ALLUsers], + UserName: "*", + Email: "", + Uid: "*", + } + return []*Binding{AllUserBinding}, nil } users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uidSets.List()}) if err != nil { -- Gitee From 9dbbe3b73a52745b35675c896193e1d3bf5cd308 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 2 Mar 2022 21:00:22 +0800 Subject: [PATCH 1747/2647] add a whitelist to the filter Signed-off-by: panxunying --- pkg/middleware/gin/collaboration.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index cf04e1a4..a5d5ea7c 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -1,6 +1,8 @@ package gin import ( + "strings" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/service" @@ -13,7 +15,9 @@ func GetCollaborationNew() gin.HandlerFunc { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" || c.Request.URL.Path == "/api/collaboration/collaborations/sync" { + if projectName == "" || c.Request.URL.Path == "/api/collaboration/collaborations/sync" || + strings.Contains(c.Request.URL.Path, "/estimated-values") || + c.Request.URL.Path == "/api/aslan/environment/rendersets/default-values" { c.Next() return } -- Gitee From 0494e725b034a320eb28ce736d56aa4152204103 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 2 Mar 2022 21:09:50 +0800 Subject: [PATCH 1748/2647] Exclude ProductStatusDeleting Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/revision.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 98da5434..098ecd50 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -31,7 +31,7 @@ import ( ) func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) (prodRevs []*ProductRevision, err error) { - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName}) + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName, ExcludeStatus: setting.ProductStatusDeleting}) // list services with max revision of project allServiceTmpls, err := getServicesWithMaxRevision(productName) -- Gitee From d413995254c048f3e6112ec9b848e80b33a2383f Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 7 Mar 2022 10:00:14 +0800 Subject: [PATCH 1749/2647] Exclude ProductStatusUnknown Signed-off-by: ddh27 --- .../aslan/core/common/repository/mongodb/product.go | 6 +++--- pkg/microservice/aslan/core/environment/service/revision.go | 4 ++-- pkg/microservice/aslan/core/service/service/service.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index ed6f903e..b57963f2 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -48,7 +48,7 @@ type ProductListOptions struct { ClusterID string IsSortByUpdateTime bool IsSortByProductName bool - ExcludeStatus string + ExcludeStatus []string ExcludeSource string Source string InProjects []string @@ -217,8 +217,8 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { if opt.ExcludeSource != "" { query["source"] = bson.M{"$ne": opt.ExcludeSource} } - if opt.ExcludeStatus != "" { - query["status"] = bson.M{"$ne": opt.ExcludeStatus} + if len(opt.ExcludeStatus) > 0 { + query["status"] = bson.M{"$nin": opt.ExcludeStatus} } if len(opt.InProjects) > 0 { query["product_name"] = bson.M{"$in": opt.InProjects} diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 098ecd50..b73e8791 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -31,7 +31,7 @@ import ( ) func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) (prodRevs []*ProductRevision, err error) { - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName, ExcludeStatus: setting.ProductStatusDeleting}) + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName, ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}}) // list services with max revision of project allServiceTmpls, err := getServicesWithMaxRevision(productName) @@ -70,7 +70,7 @@ func ListProductsRevisionByFacility(basicFacility string, log *zap.SugaredLogger projectNames = append(projectNames, v.ProductName) } - products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ExcludeStatus: setting.ProductStatusDeleting, InProjects: projectNames}) + products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}, InProjects: projectNames}) if err != nil { log.Errorf("Collection.Product.List error: %s", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 00cbc083..52e36866 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -137,7 +137,7 @@ type YamlValidatorReq struct { func ListServicesInExtenalEnv(tmpResp *commonservice.ServiceTmplResp, log *zap.SugaredLogger) { opt := &commonrepo.ProductListOptions{ Source: setting.SourceFromExternal, - ExcludeStatus: setting.ProductStatusDeleting, + ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}, } products, err := commonrepo.NewProductColl().List(opt) if err != nil { -- Gitee From b4de78f355cdacde1017ad6c634c6343f42b4e12 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 7 Mar 2022 10:56:57 +0800 Subject: [PATCH 1750/2647] added missing proxy setting for aslan Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/command/git_cmd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/command/git_cmd.go b/pkg/microservice/aslan/core/common/service/command/git_cmd.go index 7692e8b1..fc7ceef1 100644 --- a/pkg/microservice/aslan/core/common/service/command/git_cmd.go +++ b/pkg/microservice/aslan/core/common/service/command/git_cmd.go @@ -97,7 +97,7 @@ func RunGitCmds(codehostDetail *systemconfig.CodeHost, repoOwner, repoName, bran tokens = append(tokens, repo.OauthToken) cmds = append(cmds, buildGitCommands(repo)...) - if repo.Source == setting.SourceFromGithub { + if codehostDetail.EnableProxy { httpsProxy := config.ProxyHTTPSAddr() httpProxy := config.ProxyHTTPAddr() if httpsProxy != "" { -- Gitee From 2b701c9ce270f6b6be3d3104d78211fad2fb65ee Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 7 Mar 2022 14:01:25 +0800 Subject: [PATCH 1751/2647] policy duplicate bug Signed-off-by: mouuii --- pkg/microservice/picket/core/filter/service/rolebinding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index f80888e8..338817d4 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -106,8 +106,8 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) var res []*Binding - var policyBindings []*policyBinding for _, u := range users { + var policyBindings []*policyBinding var roleBindings []*roleBinding binding := &Binding{ UserName: u.Name, -- Gitee From 4c2d288642b708ba43b31ff2b787cf6dbbce77b8 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 7 Mar 2022 14:41:01 +0800 Subject: [PATCH 1752/2647] fix Exclude ProductStatusUnknown Signed-off-by: ddh27 --- pkg/cli/upgradeassistant/cmd/migrate/150.go | 2 +- pkg/cli/upgradeassistant/cmd/migrate/migrate.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/150.go b/pkg/cli/upgradeassistant/cmd/migrate/150.go index c725af3e..a2bba446 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/150.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/150.go @@ -39,7 +39,7 @@ func V140ToV150() error { log.Info("Migrating data from 1.4.0 to 1.5.0") products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ - ExcludeStatus: setting.ProductStatusDeleting, + ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}, Source: setting.SourceFromHelm, }) if err != nil { diff --git a/pkg/cli/upgradeassistant/cmd/migrate/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate/migrate.go index 6d123b4e..b07c59e1 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/migrate.go @@ -58,7 +58,7 @@ func V130ToV131() error { log.Errorf("Failed to get projects, err: %s", err) return err } - allEnvs, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ExcludeStatus: setting.ProductStatusDeleting}) + allEnvs, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}}) if err != nil { log.Errorf("Failed to get envs, err: %s", err) return err @@ -157,7 +157,7 @@ func V131ToV130() error { log.Errorf("Failed to get projects, err: %s", err) return err } - allEnvs, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ExcludeStatus: setting.ProductStatusDeleting}) + allEnvs, err := mongodb.NewProductColl().List(&mongodb.ProductListOptions{ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}}) if err != nil { log.Errorf("Failed to get envs, err: %s", err) return err -- Gitee From db9ed90e670c5ec68517eea8802da28ad95e9795 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 7 Mar 2022 14:51:29 +0800 Subject: [PATCH 1753/2647] add flag for advanced settings in build & test Signed-off-by: Min Min --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 69 +++++++++++++++++ .../internal/repository/models/build.go | 6 ++ .../internal/repository/models/testing.go | 75 ++++++++++++++++++- .../core/common/repository/models/build.go | 8 +- .../core/common/repository/models/testing.go | 8 +- 5 files changed, 158 insertions(+), 8 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 67cfd8a1..a56dd9e4 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -272,6 +272,7 @@ func migrateModuleBuild() error { {"cache_enable", build.CacheEnable}, {"cache_dir_type", build.CacheDirType}, {"cache_user_dir", build.CacheUserDir}}}, + {"advanced_setting_modified", build.AdvancedSettingsModified}, }), ) } @@ -308,6 +309,7 @@ func migrateModuleTesting() error { {"cache_enable", testing.CacheEnable}, {"cache_dir_type", testing.CacheDirType}, {"cache_user_dir", testing.CacheUserDir}}}, + {"advanced_setting_modified", testing.AdvancedSettingsModified}, }), ) } @@ -332,6 +334,8 @@ func migrateOneBuild(build *internalmodels.Build) error { build.CacheDirType = types.UserDefinedCacheDir build.CacheUserDir = build.Caches[0] + build.AdvancedSettingsModified = buildHasModifiedAdvancedSetting(build) + return nil } @@ -350,5 +354,70 @@ func migrateOneTesting(testing *internalmodels.Testing) error { testing.CacheDirType = types.UserDefinedCacheDir testing.CacheUserDir = testing.Caches[0] + testing.AdvancedSettingsModified = testHasModifiedAdvancedSetting(testing) + return nil } + +func buildHasModifiedAdvancedSetting(build *internalmodels.Build) bool { + // default timeout is 60 + if build.Timeout != 60 { + return true + } + + // cache is disabled by default + if build.CacheEnable { + return true + } + + // default is local cluster, but it is a relatively new field, so we also check for empty + if build.PreBuild.ClusterID != "" && build.PreBuild.ClusterID != setting.LocalClusterID { + return true + } + + if build.PreBuild.ResReq != setting.LowRequest { + return true + } + + return false +} + +func testHasModifiedAdvancedSetting(testing *internalmodels.Testing) bool { + // default no artifact path exists + if testing.ArtifactPaths != nil && len(testing.ArtifactPaths) > 0 { + return true + } + + // default timeout is 60 + if testing.Timeout != 60 { + return true + } + + // cache is disabled by default + if testing.CacheEnable { + return true + } + + // default is local cluster, but it is a relatively new field, so we also check for empty + if testing.PreTest.ClusterID != "" && testing.PreTest.ClusterID != setting.LocalClusterID { + return true + } + + if testing.PreTest.ResReq != setting.LowRequest { + return true + } + + if testing.HookCtl != nil && testing.HookCtl.Enabled { + return true + } + + if testing.NotifyCtl != nil && testing.NotifyCtl.Enabled { + return true + } + + if testing.Schedules != nil && testing.Schedules.Enabled { + return true + } + + return false +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/build.go b/pkg/cli/upgradeassistant/internal/repository/models/build.go index 5e2d01e4..b35fffe1 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/build.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/build.go @@ -17,6 +17,7 @@ limitations under the License. package models import ( + "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" "github.com/koderover/zadig/pkg/types" @@ -25,6 +26,7 @@ import ( type Build struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` + Timeout int `bson:"timeout" json:"timeout"` // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` @@ -33,10 +35,14 @@ type Build struct { CacheEnable bool `bson:"cache_enable" json:"cache_enable"` CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + // New since V1.10.0. Only to tell the webpage should the advanced settings be displayed + AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` } // PreBuild prepares an environment for a job type PreBuild struct { + ClusterID string `bson:"cluster_id" json:"cluster_id"` + ResReq setting.Request `bson:"res_req" json:"res_req"` // TODO: Deprecated. CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` } diff --git a/pkg/cli/upgradeassistant/internal/repository/models/testing.go b/pkg/cli/upgradeassistant/internal/repository/models/testing.go index 24d80f1a..3bb8134f 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/testing.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/testing.go @@ -17,14 +17,22 @@ limitations under the License. package models import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" "github.com/koderover/zadig/pkg/types" ) type Testing struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - PreTest *PreTest `bson:"pre_test" json:"pre_test"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + PreTest *PreTest `bson:"pre_test" json:"pre_test"` + Timeout int `bson:"timeout" json:"timeout"` + HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` + + ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` @@ -33,14 +41,77 @@ type Testing struct { CacheEnable bool `bson:"cache_enable" json:"cache_enable"` CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + // New since V1.10.0. Only to tell the webpage should the advanced settings be displayed + AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` } // PreTest prepares an environment for a job type PreTest struct { + ClusterID string `bson:"cluster_id" json:"cluster_id"` + ResReq setting.Request `bson:"res_req" json:"res_req"` // TODO: Deprecated. CleanWorkspace bool `bson:"clean_workspace" json:"clean_workspace"` } +type TestingHookCtrl struct { + Enabled bool `bson:"enabled" json:"enabled"` + Items []*TestingHook `bson:"items" json:"items"` +} + +type ScheduleCtrl struct { + Enabled bool `bson:"enabled" json:"enabled"` + //Items []*Schedule `bson:"items" json:"items"` +} + +type NotifyCtl struct { + Enabled bool `bson:"enabled" json:"enabled"` + WebHookType string `bson:"webhook_type" json:"webhook_type"` + WeChatWebHook string `bson:"weChat_webHook,omitempty" json:"weChat_webHook,omitempty"` + DingDingWebHook string `bson:"dingding_webhook,omitempty" json:"dingding_webhook,omitempty"` + FeiShuWebHook string `bson:"feishu_webhook,omitempty" json:"feishu_webhook,omitempty"` + AtMobiles []string `bson:"at_mobiles,omitempty" json:"at_mobiles,omitempty"` + IsAtAll bool `bson:"is_at_all,omitempty" json:"is_at_all,omitempty"` + NotifyTypes []string `bson:"notify_type" json:"notify_type"` +} + +type TestingHook struct { + AutoCancel bool `bson:"auto_cancel" json:"auto_cancel"` + MainRepo *MainHookRepo `bson:"main_repo" json:"main_repo"` + TestArgs *TestTaskArgs `bson:"test_args" json:"test_args"` +} + +type MainHookRepo struct { + Name string `bson:"name,omitempty" json:"name,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` + Branch string `bson:"branch" json:"branch"` + Tag string `bson:"tag" json:"tag"` + Committer string `bson:"committer" json:"committer"` + MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` + CodehostID int `bson:"codehost_id" json:"codehost_id"` + Events []config.HookEventType `bson:"events" json:"events"` + Label string `bson:"label" json:"label"` + Revision string `bson:"revision" json:"revision"` + IsRegular bool `bson:"is_regular" json:"is_regular"` +} + +type TestTaskArgs struct { + ProductName string `bson:"product_name" json:"product_name"` + TestName string `bson:"test_name" json:"test_name"` + TestTaskCreator string `bson:"test_task_creator" json:"test_task_creator"` + NotificationID string `bson:"notification_id" json:"notification_id"` + ReqID string `bson:"req_id" json:"req_id"` + // webhook触发测试任务时,触发任务的repo、prID和commitID + MergeRequestID string `bson:"merge_request_id" json:"merge_request_id"` + CommitID string `bson:"commit_id" json:"commit_id"` + Source string `bson:"source" json:"source"` + CodehostID int `bson:"codehost_id" json:"codehost_id"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` +} + func (Testing) TableName() string { return "module_testing" } diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 4c38a63a..dcfb1698 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -49,9 +49,11 @@ type Build struct { PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` // New since V1.10.0. - CacheEnable bool `bson:"cache_enable" json:"cache_enable"` - CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` - CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + // New since V1.10.0. Only to tell the webpage should the advanced settings be displayed + AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` } // PreBuild prepares an environment for a job diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index d99a2c4a..386c8e42 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -57,9 +57,11 @@ type Testing struct { ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` // New since V1.10.0. - CacheEnable bool `bson:"cache_enable" json:"cache_enable"` - CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` - CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + // New since V1.10.0. Only to tell the webpage should the advanced settings be displayed + AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` } type TestingHookCtrl struct { -- Gitee From f5d1ff01d84fac8c5df67138db1bf25cbd2acbb5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 7 Mar 2022 14:55:26 +0800 Subject: [PATCH 1754/2647] fix the bug that helm has no value when sync collaboration Signed-off-by: panxunying --- .../service/collaboration_instance.go | 41 +++++++++++------ .../aslan/core/common/service/environment.go | 41 +++++++++++++++++ .../core/environment/handler/renderset.go | 4 +- .../core/environment/service/environment.go | 2 +- .../core/environment/service/renderset.go | 45 ------------------- 5 files changed, 73 insertions(+), 60 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 488a7d07..4c8b0e63 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1120,7 +1120,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id if renderSets != nil { productRenderSetMap := make(map[string]models2.RenderSet) for _, set := range renderSets { - productRenderSetMap[set.EnvName] = set + productRenderSetMap[set.EnvName] = *set } for _, product := range newProduct { set, ok := productRenderSetMap[product.BaseName] @@ -1130,7 +1130,17 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id product.Vars = set.KVs product.DefaultValues = set.DefaultValues - product.ChartValues = buildRenderChartArg(set.ChartInfos, product.BaseName) + } + } + if len(newProduct) > 0 && newProduct[0].DeployType == "helm" { + envChartsMap := getHelmRenderSet(projectName, newProductName.List(), logger) + for _, product := range newProduct { + chart, ok := envChartsMap[product.BaseName] + if !ok { + return nil, fmt.Errorf("product:%s not exist", product.BaseName) + } + + product.ChartValues = chart } } var workNames []string @@ -1289,7 +1299,7 @@ func GetCollaborationNew(projectName, uid, identityType, userName string, logger return getCollaborationNew(updateResp, projectName, identityType, userName, logger) } -func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error) { +func getRenderSet(projectName string, envs []string) ([]*models2.RenderSet, error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ InProjects: []string{projectName}, InEnvs: envs, @@ -1304,9 +1314,8 @@ func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error Name: product.Namespace, }) } - renderSets, err := commonrepo.NewRenderSetColl().ListByFindOpts(&commonrepo.RenderSetListOption{ + renderSets, err := commonrepo.NewRenderSetColl().List(&commonrepo.RenderSetListOption{ ProductTmpl: projectName, - FindOpts: findOpts, }) if err != nil { return nil, err @@ -1314,13 +1323,19 @@ func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error return renderSets, nil } -func buildRenderChartArg(chartInfos []*templatemodels.RenderChart, envName string) []*commonservice.RenderChartArg { - ret := make([]*commonservice.RenderChartArg, 0, len(chartInfos)) - for _, singleChart := range chartInfos { - rcaObj := new(commonservice.RenderChartArg) - rcaObj.LoadFromRenderChartModel(singleChart) - rcaObj.EnvName = envName - ret = append(ret, rcaObj) +func getHelmRenderSet(projectName string, envs []string, logger *zap.SugaredLogger) map[string][]*commonservice.RenderChartArg { + envChartsMap := make(map[string][]*commonservice.RenderChartArg) + for _, env := range envs { + renderChartArgs, err := commonservice.GetRenderCharts(projectName, env, "", logger) + if err != nil { + logger.Errorf("GetRenderCharts error:%s", err) + continue + } + envChartsMap[env] = renderChartArgs + for _, arg := range renderChartArgs { + logger.Infof("env:%s arg:%v", env, arg) + } } - return ret + + return envChartsMap } diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 402185cb..8b55e95e 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -97,6 +97,47 @@ type IngressInfo struct { HostInfo []resource.HostInfo `json:"host_info"` } +func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*RenderChartArg, error) { + + renderSetName := GetProductEnvNamespace(envName, productName, "") + + opt := &commonrepo.RenderSetFindOption{ + Name: renderSetName, + } + rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil { + return nil, err + } + + if !existed { + return nil, nil + } + + ret := make([]*RenderChartArg, 0) + + matchedRenderChartModels := make([]*templatemodels.RenderChart, 0) + if len(serviceName) == 0 { + matchedRenderChartModels = rendersetObj.ChartInfos + } else { + serverList := strings.Split(serviceName, ",") + stringSet := sets.NewString(serverList...) + for _, singleChart := range rendersetObj.ChartInfos { + if !stringSet.Has(singleChart.ServiceName) { + continue + } + matchedRenderChartModels = append(matchedRenderChartModels, singleChart) + } + } + + for _, singleChart := range matchedRenderChartModels { + rcaObj := new(RenderChartArg) + rcaObj.LoadFromRenderChartModel(singleChart) + rcaObj.EnvName = envName + ret = append(ret, rcaObj) + } + return ret, nil +} + // fill service display name if necessary func fillServiceDisplayName(svcList []*ServiceResp, productInfo *models.Product) { if productInfo.Source == setting.SourceFromHelm { diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 50aff572..33f29db9 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -20,6 +20,8 @@ import ( "strings" "github.com/gin-gonic/gin" + + service2 "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -39,7 +41,7 @@ func GetServiceRenderCharts(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GetRenderCharts(c.Query("projectName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) + ctx.Resp, ctx.Err = service2.GetRenderCharts(c.Query("projectName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) } func GetProductDefaultValues(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 357e3168..40f4d5e7 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1698,7 +1698,7 @@ func GetEstimatedRenderCharts(productName, envName, serviceNameListStr string, l } // find renderchart info in env - renderChartInEnv, err := GetRenderCharts(productName, envName, serviceNameListStr, log) + renderChartInEnv, err := commonservice.GetRenderCharts(productName, envName, serviceNameListStr, log) if err != nil { log.Errorf("find render charts in env fail, env %s err %s", envName, err.Error()) return nil, e.ErrGetRenderSet.AddDesc("failed to get render charts in env") diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 41958af4..2609a82e 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -18,17 +18,13 @@ package service import ( "fmt" - "strings" "sync" "github.com/pkg/errors" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -46,47 +42,6 @@ type YamlContentRequestArg struct { ValuesPaths string `json:"valuesPaths" form:"valuesPaths"` } -func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*commonservice.RenderChartArg, error) { - - renderSetName := commonservice.GetProductEnvNamespace(envName, productName, "") - - opt := &commonrepo.RenderSetFindOption{ - Name: renderSetName, - } - rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) - if err != nil { - return nil, err - } - - if !existed { - return nil, nil - } - - ret := make([]*commonservice.RenderChartArg, 0) - - matchedRenderChartModels := make([]*template.RenderChart, 0) - if len(serviceName) == 0 { - matchedRenderChartModels = rendersetObj.ChartInfos - } else { - serverList := strings.Split(serviceName, ",") - stringSet := sets.NewString(serverList...) - for _, singleChart := range rendersetObj.ChartInfos { - if !stringSet.Has(singleChart.ServiceName) { - continue - } - matchedRenderChartModels = append(matchedRenderChartModels, singleChart) - } - } - - for _, singleChart := range matchedRenderChartModels { - rcaObj := new(commonservice.RenderChartArg) - rcaObj.LoadFromRenderChartModel(singleChart) - rcaObj.EnvName = envName - ret = append(ret, rcaObj) - } - return ret, nil -} - func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*DefaultValuesResp, error) { ret := &DefaultValuesResp{} -- Gitee From 6927d02a1ef54d3019470943b65132214032019b Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 7 Mar 2022 14:56:51 +0800 Subject: [PATCH 1755/2647] minor bugfix for ua Signed-off-by: Min Min --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index a56dd9e4..6ffa935b 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -271,8 +271,9 @@ func migrateModuleBuild() error { bson.D{ {"cache_enable", build.CacheEnable}, {"cache_dir_type", build.CacheDirType}, - {"cache_user_dir", build.CacheUserDir}}}, - {"advanced_setting_modified", build.AdvancedSettingsModified}, + {"cache_user_dir", build.CacheUserDir}, + {"advanced_setting_modified", build.AdvancedSettingsModified}, + }}, }), ) } @@ -308,8 +309,9 @@ func migrateModuleTesting() error { bson.D{ {"cache_enable", testing.CacheEnable}, {"cache_dir_type", testing.CacheDirType}, - {"cache_user_dir", testing.CacheUserDir}}}, - {"advanced_setting_modified", testing.AdvancedSettingsModified}, + {"cache_user_dir", testing.CacheUserDir}, + {"advanced_setting_modified", testing.AdvancedSettingsModified}, + }}, }), ) } -- Gitee From 7db34bc31dccdb6bb4434a5f0164939ca5790e1d Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 7 Mar 2022 15:43:59 +0800 Subject: [PATCH 1756/2647] Common users can view the delivery center Signed-off-by: ddh27 --- pkg/microservice/aslan/core/delivery/handler/policy.yaml | 6 ------ pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 -- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 2 -- 3 files changed, 10 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml index e79464f4..758739d4 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -6,12 +6,6 @@ rules: alias: "查看" description: "" rules: - - method: GET - endpoint: "/api/aslan/delivery/releases" - - method: GET - endpoint: "/api/aslan/delivery/servicenames" - - method: GET - endpoint: "/api/aslan/delivery/releases/?*" - method: GET endpoint: "/api/aslan/delivery/releases/helm/charts" - action: delete_delivery diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 69cc4134..fe6b433c 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -82,8 +82,6 @@ rules: endpoint: "/api/aslan/service/workloads" - method: GET endpoint: "/api/aslan/project/products/?*/services" - - method: GET - endpoint: "/api/aslan/delivery/releases" - method: GET endpoint: "api/aslan/cluster/clusters" resourceType: "Cluster" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 2c4cccf7..267e7f49 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -106,8 +106,6 @@ rules: endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" - method: POST endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" - - method: GET - endpoint: "/api/aslan/delivery/releases" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" -- Gitee From 88f759a88f5e9b70f33f90999c1e61479971cb25 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 7 Mar 2022 15:48:43 +0800 Subject: [PATCH 1757/2647] fix the bug that read only has no service Signed-off-by: panxunying --- pkg/cli/initconfig/cmd/contributor.yaml | 6 +++- pkg/cli/initconfig/cmd/read-only.yaml | 5 +++ pkg/cli/initconfig/cmd/read-project-only.yaml | 6 +++- .../core/project/handler/build_policy.yaml | 2 +- .../core/project/handler/service_policy.yaml | 36 +------------------ 5 files changed, 17 insertions(+), 38 deletions(-) diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml index f8f58b3e..c9cfd423 100644 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -16,11 +16,15 @@ rules: - Environment kind: resource - verbs: - - get_build - get_service resources: - Service kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource - verbs: - get_test resources: diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index faa8f0ca..76feec19 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -22,6 +22,11 @@ rules: resources: - Service kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource - verbs: - get_test resources: diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml index cbed1912..105bfd1f 100644 --- a/pkg/cli/initconfig/cmd/read-project-only.yaml +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -19,11 +19,15 @@ rules: - key: placeholder value: placeholder - verbs: - - get_build - get_service resources: - Service kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource - verbs: - get_test resources: diff --git a/pkg/microservice/aslan/core/project/handler/build_policy.yaml b/pkg/microservice/aslan/core/project/handler/build_policy.yaml index 31016b2b..29b07c52 100644 --- a/pkg/microservice/aslan/core/project/handler/build_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/build_policy.yaml @@ -1,4 +1,4 @@ -resource: Service +resource: Build alias: "构建" description: "" rules: diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml index d65da237..3f04a647 100644 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -63,38 +63,4 @@ rules: - method: DELETE endpoint: "/api/aslan/service/services/?*/?*" - method: DELETE - endpoint: "/api/aslan/service/services/?*/pm" - - action: get_build - alias: "查看构建配置" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/build/build" - - method: GET - endpoint: "/api/aslan/build/build/?*" - - method: GET - endpoint: "/api/aslan/build/targets" - - method: GET - endpoint: "/api/aslan/cluster/clusters" - - action: create_build - alias: "新建构建配置" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/build/build" - - action: edit_build - alias: "编辑构建配置" - description: "" - rules: - - method: PUT - endpoint: "/api/aslan/build/build" - - method: POST - endpoint: "/api/aslan/build/targets" - - method: POST - endpoint: "/api/aslan/build/build/targets" - - action: delete_build - alias: "删除构建配置" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/build/build" \ No newline at end of file + endpoint: "/api/aslan/service/services/?*/pm" \ No newline at end of file -- Gitee From 071b7ca281586ab8d8df876d65a29b178631ace2 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 7 Mar 2022 15:58:58 +0800 Subject: [PATCH 1758/2647] optimize Signed-off-by: panxunying --- pkg/microservice/aslan/core/collaboration/config/consts.go | 7 +++++++ .../core/collaboration/service/collaboration_instance.go | 5 +---- .../aslan/core/environment/handler/renderset.go | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/config/consts.go b/pkg/microservice/aslan/core/collaboration/config/consts.go index c51ed2e1..58c11ea9 100644 --- a/pkg/microservice/aslan/core/collaboration/config/consts.go +++ b/pkg/microservice/aslan/core/collaboration/config/consts.go @@ -22,3 +22,10 @@ const ( CollaborationShare CollaborationType = "share" CollaborationNew CollaborationType = "new" ) + +type DeployType string + +const ( + DeployTypeHelm DeployType = "helm" + DeployTypeK8s DeployType = "k8s" +) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 4c8b0e63..99ca8279 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1132,7 +1132,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id product.DefaultValues = set.DefaultValues } } - if len(newProduct) > 0 && newProduct[0].DeployType == "helm" { + if len(newProduct) > 0 && newProduct[0].DeployType == string(config.DeployTypeHelm) { envChartsMap := getHelmRenderSet(projectName, newProductName.List(), logger) for _, product := range newProduct { chart, ok := envChartsMap[product.BaseName] @@ -1332,9 +1332,6 @@ func getHelmRenderSet(projectName string, envs []string, logger *zap.SugaredLogg continue } envChartsMap[env] = renderChartArgs - for _, arg := range renderChartArgs { - logger.Infof("env:%s arg:%v", env, arg) - } } return envChartsMap diff --git a/pkg/microservice/aslan/core/environment/handler/renderset.go b/pkg/microservice/aslan/core/environment/handler/renderset.go index 33f29db9..d3ccd777 100644 --- a/pkg/microservice/aslan/core/environment/handler/renderset.go +++ b/pkg/microservice/aslan/core/environment/handler/renderset.go @@ -21,7 +21,7 @@ import ( "github.com/gin-gonic/gin" - service2 "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -41,7 +41,7 @@ func GetServiceRenderCharts(c *gin.Context) { return } - ctx.Resp, ctx.Err = service2.GetRenderCharts(c.Query("projectName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) + ctx.Resp, ctx.Err = commonservice.GetRenderCharts(c.Query("projectName"), c.Query("envName"), c.Query("serviceName"), ctx.Logger) } func GetProductDefaultValues(c *gin.Context) { -- Gitee From b4734c4487ed24ac89227f5c76e46bdf4e7bdd13 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 7 Mar 2022 16:01:36 +0800 Subject: [PATCH 1759/2647] optimize Signed-off-by: panxunying --- pkg/microservice/aslan/core/collaboration/config/consts.go | 7 ------- .../core/collaboration/service/collaboration_instance.go | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/config/consts.go b/pkg/microservice/aslan/core/collaboration/config/consts.go index 58c11ea9..c51ed2e1 100644 --- a/pkg/microservice/aslan/core/collaboration/config/consts.go +++ b/pkg/microservice/aslan/core/collaboration/config/consts.go @@ -22,10 +22,3 @@ const ( CollaborationShare CollaborationType = "share" CollaborationNew CollaborationType = "new" ) - -type DeployType string - -const ( - DeployTypeHelm DeployType = "helm" - DeployTypeK8s DeployType = "k8s" -) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 99ca8279..f3e9ec80 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1132,7 +1132,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id product.DefaultValues = set.DefaultValues } } - if len(newProduct) > 0 && newProduct[0].DeployType == string(config.DeployTypeHelm) { + if len(newProduct) > 0 && newProduct[0].DeployType == setting.HelmDeployType { envChartsMap := getHelmRenderSet(projectName, newProductName.List(), logger) for _, product := range newProduct { chart, ok := envChartsMap[product.BaseName] -- Gitee From a26bb2701190bb40c12b0cee0f5e6f62737f2377 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 7 Mar 2022 16:06:47 +0800 Subject: [PATCH 1760/2647] optimize Signed-off-by: panxunying --- pkg/cli/initconfig/cmd/read-only.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml index 76feec19..0fe0f37b 100644 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -17,7 +17,6 @@ rules: - ProductionEnvironment kind: resource - verbs: - - get_build - get_service resources: - Service -- Gitee From e0ba4f093d23032236d7a00bdab83218b9b7adce Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 7 Mar 2022 16:20:21 +0800 Subject: [PATCH 1761/2647] sort imports Signed-off-by: Min Min --- pkg/cli/upgradeassistant/internal/repository/models/build.go | 2 +- .../upgradeassistant/internal/repository/models/testing.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/models/build.go b/pkg/cli/upgradeassistant/internal/repository/models/build.go index b35fffe1..8ff7b639 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/build.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/build.go @@ -17,9 +17,9 @@ limitations under the License. package models import ( - "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" ) diff --git a/pkg/cli/upgradeassistant/internal/repository/models/testing.go b/pkg/cli/upgradeassistant/internal/repository/models/testing.go index 3bb8134f..dc9d041c 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/testing.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/testing.go @@ -17,10 +17,10 @@ limitations under the License. package models import ( - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" ) -- Gitee From 037b3c3e9de66dfd5c324d6a6ba49b8a0c39b971 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 7 Mar 2022 18:15:20 +0800 Subject: [PATCH 1762/2647] use timeout set in project when deploying helm services Signed-off-by: allenshen --- .../core/service/taskplugin/deploy.go | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index f86a0c39..36e2e394 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -364,21 +364,6 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * helmClient helmclient.Client ) - rcsList := make([]ResourceComponentSet, 0) - deployments, errFindDeploy := getter.ListDeployments(p.Task.Namespace, nil, p.kubeClient) - if errFindDeploy != nil { - p.Log.Errorf("failed to list deployments in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, errFindDeploy) - } else { - rcsList = append(rcsList, RcsListFromDeployments(deployments)...) - } - statefulSets, errFindSts := getter.ListStatefulSets(p.Task.Namespace, nil, p.kubeClient) - if errFindSts != nil { - p.Log.Errorf("failed to list statefulsets in namespace %s, productName %s, err %s", p.Task.Namespace, p.Task.ProductName, errFindSts) - } else { - rcsList = append(rcsList, RcsListFromStatefulSets(statefulSets)...) - } - p.findHelmAffectedResources(p.Task.Namespace, p.Task.ServiceName, rcsList) - p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, p.Task.ServiceName, containerName, p.Task.Namespace) @@ -557,6 +542,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * return } + timeOut := p.TaskTimeout() chartSpec := helmclient.ChartSpec{ ReleaseName: releaseName, ChartName: chartPath, @@ -566,7 +552,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * ValuesYaml: replacedMergedValuesYaml, SkipCRDs: false, UpgradeCRDs: true, - Timeout: time.Second * setting.DeployTimeout, + Timeout: time.Second * time.Duration(timeOut), Wait: true, Replace: true, MaxHistory: 10, @@ -577,7 +563,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { err = errors.WithMessagef( err, - "failed to Install helm chart %s/%s", + "failed to upgrade helm chart %s/%s", p.Task.Namespace, p.Task.ServiceName) done <- false } else { @@ -588,7 +574,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * select { case <-done: break - case <-time.After(chartSpec.Timeout + 30*time.Second): + case <-time.After(chartSpec.Timeout + time.Minute): err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) } if err != nil { @@ -812,6 +798,12 @@ func (p *DeployTaskPlugin) Wait(ctx context.Context) { return } + // for services deployed by helm, use --wait option to ensure related resources are updated + if p.Task.ServiceType == setting.HelmDeployType { + p.Task.TaskStatus = config.StatusPassed + return + } + timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() -- Gitee From b58cd1b7df177d6f68c64e186813459d6413e108 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 7 Mar 2022 19:41:46 +0800 Subject: [PATCH 1763/2647] Specifies that the type of the key-value pair to be overridden is interface Signed-off-by: ddh27 --- pkg/microservice/aslan/core/common/service/render.go | 4 ++-- pkg/tool/helmclient/helmclient.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index a695162c..37c02115 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -44,8 +44,8 @@ type RepoConfig struct { } type KVPair struct { - Key string `json:"key"` - Value string `json:"value"` + Key string `json:"key"` + Value interface{} `json:"value"` } type RenderChartArg struct { diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 4c99c1e8..4254dfc5 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -68,8 +68,8 @@ func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error } type KV struct { - Key string `json:"key"` - Value string `json:"value"` + Key string `json:"key"` + Value interface{} `json:"value"` } // MergeOverrideValues merge override yaml and override kvs -- Gitee From 538ac9d08376d543427957625de2b4919677af51 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 7 Mar 2022 20:17:13 +0800 Subject: [PATCH 1764/2647] display different information for build/test Signed-off-by: zhangyifei --- .../reaper/core/service/reaper/reaper.go | 10 +++++++- pkg/microservice/reaper/executor/executor.go | 7 ++++-- pkg/types/reaper.go | 24 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 pkg/types/reaper.go diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 8180486e..9fcbe120 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -48,7 +48,9 @@ type Reaper struct { StartTime time.Time ActiveWorkspace string UserEnvs map[string]string - cm CacheManager + Type types.ReaperType + + cm CacheManager } func NewReaper() (*Reaper, error) { @@ -69,6 +71,12 @@ func NewReaper() (*Reaper, error) { cm: NewTarCacheManager(ctx.StorageURI, ctx.PipelineName, ctx.ServiceName, ctx.AesKey), } + if ctx.TestType == "" { + reaper.Type = types.BuildReaperType + } else { + reaper.Type = types.TestReaperType + } + workspace := "/workspace" if reaper.Ctx.ClassicBuild { workspace = reaper.Ctx.Workspace diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 7b4562d7..01385a8f 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -37,9 +37,9 @@ func Execute() error { }) start := time.Now() - log.Info("====================== Build Start ======================") var err error + var reaperType types.ReaperType defer func() { // Create dog food file to tell wd that task has finished. resultMsg := types.JobSuccess @@ -53,7 +53,7 @@ func Execute() error { log.Errorf("Failed to create dog food: %s.", dogFoodErr) } - log.Infof("====================== Build Ended. Duration: %.2f seconds ======================", time.Since(start).Seconds()) + log.Infof("====================== %s End. Duration: %.2f seconds ======================", reaperType, time.Since(start).Seconds()) // Note: Mark the task has been completed through the dogfood file, indirectly notify wd to do follow-up // operations, and wait for a fixed time. @@ -68,6 +68,9 @@ func Execute() error { return fmt.Errorf("failed to new reaper: %s", err) } + reaperType = r.Type + log.Infof("====================== %s Start ======================", reaperType) + if err = r.BeforeExec(); err != nil { return fmt.Errorf("failed to prepare before building: %s", err) } diff --git a/pkg/types/reaper.go b/pkg/types/reaper.go new file mode 100644 index 00000000..38282b43 --- /dev/null +++ b/pkg/types/reaper.go @@ -0,0 +1,24 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package types + +type ReaperType string + +const ( + BuildReaperType ReaperType = "Build" + TestReaperType ReaperType = "Test" +) -- Gitee From 9cdc9dc3cbe629efcf6db040723f09765bcf859a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 8 Mar 2022 10:43:20 +0800 Subject: [PATCH 1765/2647] Fix bugs in workflows triggered by delivery Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 6a24786a..4da79c11 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1283,7 +1283,9 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ return nil, err } deployTask.ServiceName = envList[0] - deployTask.ContainerName = envList[1] + "_" + envList[0] + if !strings.Contains(envList[1], "_") { + deployTask.ContainerName = envList[1] + "_" + envList[0] + } switch env.Type { case setting.K8SDeployType: -- Gitee From 89c6ccae37832cfdcc34a346a9a33e4280682abe Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 8 Mar 2022 10:45:38 +0800 Subject: [PATCH 1766/2647] all user binding not return Signed-off-by: mouuii --- .../picket/core/filter/service/rolebinding.go | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/picket/core/filter/service/rolebinding.go b/pkg/microservice/picket/core/filter/service/rolebinding.go index 338817d4..7fc43853 100644 --- a/pkg/microservice/picket/core/filter/service/rolebinding.go +++ b/pkg/microservice/picket/core/filter/service/rolebinding.go @@ -88,15 +88,18 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) uidToPolicyBindings[pb.UID] = append(uidToPolicyBindings[pb.UID], &policyBinding{PolicyBinding: pb}) } if uidSets.Len() == 0 { - // add all 'ALLUsers' roles - AllUserBinding := &Binding{ - Roles: uidToRoleBinding[ALLUsers], - Policies: uidToPolicyBindings[ALLUsers], - UserName: "*", - Email: "", - Uid: "*", + if len(uidToRoleBinding[ALLUsers]) != 0 || len(uidToPolicyBindings[ALLUsers]) != 0 { + // add all 'ALLUsers' roles + AllUserBinding := &Binding{ + Roles: uidToRoleBinding[ALLUsers], + Policies: uidToPolicyBindings[ALLUsers], + UserName: "*", + Email: "", + Uid: "*", + } + return []*Binding{AllUserBinding}, nil } - return []*Binding{AllUserBinding}, nil + return []*Binding{}, nil } users, err := user.New().ListUsers(&user.SearchArgs{UIDs: uidSets.List()}) if err != nil { @@ -147,13 +150,16 @@ func ListBindings(header http.Header, qs url.Values, logger *zap.SugaredLogger) } // add all 'ALLUsers' roles - AllUserBinding := &Binding{ - Roles: uidToRoleBinding[ALLUsers], - Policies: uidToPolicyBindings[ALLUsers], - UserName: "*", - Email: "", - Uid: "*", + if len(uidToRoleBinding[ALLUsers]) != 0 || len(uidToPolicyBindings[ALLUsers]) != 0 { + AllUserBinding := &Binding{ + Roles: uidToRoleBinding[ALLUsers], + Policies: uidToPolicyBindings[ALLUsers], + UserName: "*", + Email: "", + Uid: "*", + } + res = append(res, AllUserBinding) } - res = append(res, AllUserBinding) + return res, nil } -- Gitee From aae62d5e5d9314e0d39690f761a6d1a20cec299d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 8 Mar 2022 11:03:38 +0800 Subject: [PATCH 1767/2647] Fix bugs in workflows triggered by delivery Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4da79c11..06010406 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1283,6 +1283,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ return nil, err } deployTask.ServiceName = envList[0] + deployTask.ContainerName = envList[1] if !strings.Contains(envList[1], "_") { deployTask.ContainerName = envList[1] + "_" + envList[0] } -- Gitee From 8a6520c64bb27b9b1f9e2b313ef66076f314739c Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 8 Mar 2022 11:15:27 +0800 Subject: [PATCH 1768/2647] remove useless debug logs Signed-off-by: zhangyifei --- pkg/microservice/warpdrive/core/service/taskplugin/testing.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 93935d01..ef6b8254 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -194,10 +194,6 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC fileName = strings.Replace(strings.ToLower(fileName), "_", "-", -1) testReportFile = strings.Replace(strings.ToLower(testReportFile), "_", "-", -1) - for _, env := range p.Task.JobCtx.EnvVars { - p.Log.Infof("[YIFEI] k: %s, v: %s", env.Key, env.Value) - } - // Since we allow users to use custom environment variables, variable resolution is required. if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == commontypes.NFSMedium && pipelineCtx.CacheDirType == commontypes.UserDefinedCacheDir { -- Gitee From e1c73d7d35c75998065d787f6a58067566231e76 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 8 Mar 2022 11:16:52 +0800 Subject: [PATCH 1769/2647] filter add ifPassFilter Signed-off-by: panxunying --- pkg/middleware/gin/collaboration.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index a5d5ea7c..91a531f7 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -15,7 +15,10 @@ func GetCollaborationNew() gin.HandlerFunc { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - if projectName == "" || c.Request.URL.Path == "/api/collaboration/collaborations/sync" || + ifPassFilter := c.Query("ifPassFilter") + if ifPassFilter == "true" || + projectName == "" || + c.Request.URL.Path == "/api/collaboration/collaborations/sync" || strings.Contains(c.Request.URL.Path, "/estimated-values") || c.Request.URL.Path == "/api/aslan/environment/rendersets/default-values" { c.Next() -- Gitee From 6ca35960b6202a7fd47442816986df4d0e0d6d94 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 8 Mar 2022 11:18:23 +0800 Subject: [PATCH 1770/2647] make test result file optional Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/testing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 93935d01..07b68ad0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -393,7 +393,7 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv return } - if p.Task.JobCtx.TestType == setting.FunctionTest { + if p.Task.JobCtx.TestType == setting.FunctionTest && p.Task.JobCtx.TestResultPath != "" { b, err := os.ReadFile(tmpFilename) if err != nil { p.Log.Error(fmt.Sprintf("get test result file error: %v", err)) -- Gitee From 1556a26b26b0028c22e27e86ae9bdd85ab74b0a3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 8 Mar 2022 11:46:33 +0800 Subject: [PATCH 1771/2647] add support of cancel deploy for helm services Signed-off-by: allenshen --- pkg/microservice/warpdrive/core/service/taskplugin/deploy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 36e2e394..32778651 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -560,7 +560,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * done := make(chan bool) go func(chan bool) { - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), &chartSpec); err != nil { + if _, err = helmClient.InstallOrUpgradeChart(ctx, &chartSpec); err != nil { err = errors.WithMessagef( err, "failed to upgrade helm chart %s/%s", -- Gitee From bd210f14e3e9f1d7212fb2c748b82a3e20a1cfe0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 8 Mar 2022 14:43:51 +0800 Subject: [PATCH 1772/2647] branch page Signed-off-by: mouuii --- .../aslan/core/code/handler/codehost.go | 22 ++++++++++++++----- .../aslan/core/code/service/merge_request.go | 8 +++++-- .../aslan/core/code/service/repo.go | 4 ++-- .../aslan/core/code/service/tag.go | 8 +++++-- pkg/tool/git/gitlab/merge_request.go | 3 ++- pkg/tool/git/gitlab/tag.go | 4 ++-- 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index af2700e4..b472adc7 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -110,8 +110,8 @@ func CodeHostGetProjectsList(c *gin.Context) { ctx.Logger) } -type CodeHostGetBranchListArgs struct { - PerPage int `json:"per_page" form:"per_page,default=30"` +type CodeHostGetPageNateListArgs struct { + PerPage int `json:"per_page" form:"per_page,default=100"` Page int `json:"page" form:"page,default=1"` Key string `json:"key" form:"key"` } @@ -123,7 +123,7 @@ func CodeHostGetBranchList(c *gin.Context) { codehostID := c.Param("codehostId") namespace := c.Param("namespace") projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id - args := new(CodeHostGetBranchListArgs) + args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return @@ -160,7 +160,11 @@ func CodeHostGetTagList(c *gin.Context) { codehostID := c.Param("codehostId") namespace := c.Param("namespace") projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id - + args := new(CodeHostGetPageNateListArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") return @@ -175,7 +179,7 @@ func CodeHostGetTagList(c *gin.Context) { } chID, _ := strconv.Atoi(codehostID) - ctx.Resp, ctx.Err = service.CodeHostListTags(chID, projectName, strings.Replace(namespace, "%2F", "/", -1), ctx.Logger) + ctx.Resp, ctx.Err = service.CodeHostListTags(chID, projectName, strings.Replace(namespace, "%2F", "/", -1), args.Key, args.Page, args.PerPage, ctx.Logger) } func CodeHostGetPRList(c *gin.Context) { @@ -186,6 +190,12 @@ func CodeHostGetPRList(c *gin.Context) { namespace := c.Param("namespace") projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id + args := new(CodeHostGetPageNateListArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") return @@ -202,7 +212,7 @@ func CodeHostGetPRList(c *gin.Context) { targetBr := c.Query("targetBranch") chID, _ := strconv.Atoi(codehostID) - ctx.Resp, ctx.Err = service.CodeHostListPRs(chID, projectName, strings.Replace(namespace, "%2F", "/", -1), targetBr, ctx.Logger) + ctx.Resp, ctx.Err = service.CodeHostListPRs(chID, projectName, strings.Replace(namespace, "%2F", "/", -1), targetBr, args.Key, args.Page, args.PerPage, ctx.Logger) } func ListRepoInfos(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index bbf8c40e..d002b0bb 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -30,7 +30,7 @@ import ( "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, log *zap.SugaredLogger) ([]*PullRequest, error) { +func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, key string, page, perPage int, log *zap.SugaredLogger) ([]*PullRequest, error) { ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { return nil, e.ErrCodehostListPrs.AddDesc("git client is nil") @@ -43,7 +43,11 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, lo return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) } - prs, err := client.ListOpenedProjectMergeRequests(namespace, projectName, targetBr, nil) + prs, err := client.ListOpenedProjectMergeRequests(namespace, projectName, targetBr, key, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }) if err != nil { log.Error(err) return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index c330a00e..77075ab4 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -58,7 +58,7 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( defer func() { wg.Done() }() - info.PRs, err = CodeHostListPRs(info.CodehostID, info.Repo, strings.Replace(info.Owner, "%2F", "/", -1), "", log) + info.PRs, err = CodeHostListPRs(info.CodehostID, info.Repo, strings.Replace(info.Owner, "%2F", "/", -1), "", "", 0, 0, log) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() @@ -98,7 +98,7 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( if info.Source == CodeHostCodeHub { projectName = info.RepoID } - info.Tags, err = CodeHostListTags(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), log) + info.Tags, err = CodeHostListTags(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "", 0, 0, log) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 4b9af5a0..7b17eecb 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -30,7 +30,7 @@ import ( "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListTags(codeHostID int, projectName string, namespace string, log *zap.SugaredLogger) ([]*Tag, error) { +func CodeHostListTags(codeHostID int, projectName string, namespace string, key string, page int, perPage int, log *zap.SugaredLogger) ([]*Tag, error) { ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { log.Error(err) @@ -44,7 +44,11 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, log return nil, e.ErrCodehostListTags.AddDesc(err.Error()) } - tags, err := client.ListTags(namespace, projectName, nil) + tags, err := client.ListTags(namespace, projectName, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }, key) if err != nil { return nil, err } diff --git a/pkg/tool/git/gitlab/merge_request.go b/pkg/tool/git/gitlab/merge_request.go index baa84c00..ed7624ce 100644 --- a/pkg/tool/git/gitlab/merge_request.go +++ b/pkg/tool/git/gitlab/merge_request.go @@ -18,13 +18,14 @@ package gitlab import "github.com/xanzy/go-gitlab" -func (c *Client) ListOpenedProjectMergeRequests(owner, repo, targetBranch string, opts *ListOptions) ([]*gitlab.MergeRequest, error) { +func (c *Client) ListOpenedProjectMergeRequests(owner, repo, targetBranch, key string, opts *ListOptions) ([]*gitlab.MergeRequest, error) { mergeRequests, err := wrap(paginated(func(o *gitlab.ListOptions) ([]interface{}, *gitlab.Response, error) { state := "opened" mopts := &gitlab.ListProjectMergeRequestsOptions{ State: &state, ListOptions: *o, + Search: &key, } if targetBranch != "" { mopts.TargetBranch = &targetBranch diff --git a/pkg/tool/git/gitlab/tag.go b/pkg/tool/git/gitlab/tag.go index 0ffb86e5..a840b84d 100644 --- a/pkg/tool/git/gitlab/tag.go +++ b/pkg/tool/git/gitlab/tag.go @@ -19,9 +19,9 @@ package gitlab import "github.com/xanzy/go-gitlab" // ListTags lists branches by projectID <- urlEncode(namespace/projectName) -func (c *Client) ListTags(owner, repo string, opts *ListOptions) ([]*gitlab.Tag, error) { +func (c *Client) ListTags(owner, repo string, opts *ListOptions, key string) ([]*gitlab.Tag, error) { tags, err := wrap(paginated(func(o *gitlab.ListOptions) ([]interface{}, *gitlab.Response, error) { - ts, r, err := c.Tags.ListTags(generateProjectName(owner, repo), &gitlab.ListTagsOptions{ListOptions: *o}) + ts, r, err := c.Tags.ListTags(generateProjectName(owner, repo), &gitlab.ListTagsOptions{ListOptions: *o, Search: &key}) var res []interface{} for _, t := range ts { res = append(res, t) -- Gitee From 06735f58631ea9cb9c28efa7505ae7caecc37f3e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 8 Mar 2022 14:57:45 +0800 Subject: [PATCH 1773/2647] fix shared service problem Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 06010406..036e8bee 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1873,6 +1873,14 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } + // The service may be a shared service + if len(modules) == 0 { + opt.ProductName = "" + modules, err = commonrepo.NewBuildColl().List(opt) + if err != nil { + return nil, e.ErrConvertSubTasks.AddErr(err) + } + } registries, err := commonservice.ListRegistryNamespaces(true, log) if err != nil { -- Gitee From 7b0401aaf7d07e4437304d505b044499e87813ed Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 8 Mar 2022 15:55:28 +0800 Subject: [PATCH 1774/2647] policy meta alias Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy_definitions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go index 3e0f001f..01369276 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/environment/handler/policy_definitions.go @@ -27,7 +27,7 @@ import ( const ( productionPolicyResource = "ProductionEnvironment" - productionPolicyAlias = "集成环境(生产/预发布)" + productionPolicyAlias = "环境(生产/预发布)" productionKey = "production" productionValueTrue = "true" ) -- Gitee From ca5ed27295809e5c605afd43804f62e70d64aef0 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 8 Mar 2022 16:05:04 +0800 Subject: [PATCH 1775/2647] fix override key-value pair to interface Signed-off-by: ddh27 --- pkg/tool/helmclient/helmclient.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 4254dfc5..a375ec4d 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -93,7 +93,7 @@ func MergeOverrideValues(valuesYaml, defaultValues, overrideYaml, overrideValues } kvStr := make([]string, 0) for _, kv := range kvList { - kvStr = append(kvStr, fmt.Sprintf("%s=%s", kv.Key, kv.Value)) + kvStr = append(kvStr, fmt.Sprintf("%s=%v", kv.Key, kv.Value)) } // override values for --set option err = strvals.ParseInto(strings.Join(kvStr, ","), valuesMap) -- Gitee From f5b5f9d2feb2fae2a25575d2e34f0392b602601c Mon Sep 17 00:00:00 2001 From: lilianzhu Date: Tue, 8 Mar 2022 17:05:18 +0800 Subject: [PATCH 1776/2647] fix typo Signed-off-by: lilianzhu --- .../aslan/core/common/service/product.go | 2 +- .../core/environment/handler/configmap.go | 4 ++-- .../core/environment/handler/environment.go | 18 +++++++++--------- .../aslan/core/environment/handler/image.go | 4 ++-- .../aslan/core/environment/handler/kube.go | 2 +- .../aslan/core/environment/handler/router.go | 2 +- .../aslan/core/environment/handler/service.go | 10 +++++----- .../aslan/core/environment/service/product.go | 2 +- .../aslan/core/project/service/product.go | 2 +- pkg/tool/errors/http_errors.go | 12 ++++++------ 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 874d965e..897c0bf7 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -77,7 +77,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su err = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusDeleting) if err != nil { log.Errorf("[%s][%s] update product status error: %v", username, productInfo.Namespace, err) - return e.ErrDeleteEnv.AddDesc("更新集成环境状态失败: " + err.Error()) + return e.ErrDeleteEnv.AddDesc("更新环境状态失败: " + err.Error()) } log.Infof("[%s] delete product %s", username, productInfo.Namespace) diff --git a/pkg/microservice/aslan/core/environment/handler/configmap.go b/pkg/microservice/aslan/core/environment/handler/configmap.go index bb75e30f..b9e41164 100644 --- a/pkg/microservice/aslan/core/environment/handler/configmap.go +++ b/pkg/microservice/aslan/core/environment/handler/configmap.go @@ -55,7 +55,7 @@ func UpdateConfigMap(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateConfigMap json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "集成环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -89,7 +89,7 @@ func RollBackConfigMap(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("RollBackConfigMap json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "回滚", "集成环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "回滚", "环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index f68ce5ed..c08cdda2 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -104,7 +104,7 @@ func UpdateMultiProducts(c *gin.Context) { } } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -145,7 +145,7 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { envNameList = append(envNameList, arg.EnvName) } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "集成环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( projectName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, @@ -181,7 +181,7 @@ func copyHelmProduct(c *gin.Context, ctx *internalhandler.Context) { envNameCopyList = append(envNameCopyList, arg.BaseEnvName+"-->"+arg.EnvName) } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "复制", "集成环境", strings.Join(envNameCopyList, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "复制", "环境", strings.Join(envNameCopyList, ","), string(data), ctx.Logger) ctx.Err = service.CopyHelmProduct( projectName, ctx.UserName, ctx.RequestID, createArgs, ctx.Logger, @@ -223,7 +223,7 @@ func CreateProduct(c *gin.Context) { } } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "集成环境", args.EnvName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "环境", args.EnvName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -259,7 +259,7 @@ func UpdateProduct(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", envName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -292,7 +292,7 @@ func UpdateProductRegistry(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", envName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -313,7 +313,7 @@ func UpdateProductRecycleDay(c *gin.Context) { projectName := c.Query("projectName") recycleDayStr := c.Query("recycleDay") - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境-环境回收", envName, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境-环境回收", envName, "", ctx.Logger) var ( recycleDay int @@ -418,7 +418,7 @@ func updateMultiHelmEnv(c *gin.Context, ctx *internalhandler.Context) { } } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) ctx.Resp, ctx.Err = service.UpdateMultipleHelmEnv( ctx.RequestID, args, ctx.Logger, @@ -486,7 +486,7 @@ func DeleteProduct(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "集成环境", envName, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境", envName, "", ctx.Logger) ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/image.go b/pkg/microservice/aslan/core/environment/handler/image.go index 45315376..2ee65c80 100644 --- a/pkg/microservice/aslan/core/environment/handler/image.go +++ b/pkg/microservice/aslan/core/environment/handler/image.go @@ -50,7 +50,7 @@ func UpdateStatefulSetContainerImage(c *gin.Context) { internalhandler.InsertOperationLog( c, ctx.UserName, args.ProductName, - "更新", "集成环境-服务镜像", + "更新", "环境-服务镜像", fmt.Sprintf("环境名称:%s,服务名称:%s,StatefulSet:%s", args.EnvName, args.ServiceName, args.Name), string(data), ctx.Logger) @@ -81,7 +81,7 @@ func UpdateDeploymentContainerImage(c *gin.Context) { internalhandler.InsertOperationLog( c, ctx.UserName, args.ProductName, - "更新", "集成环境-服务镜像", + "更新", "环境-服务镜像", fmt.Sprintf("环境名称:%s,服务名称:%s,Deployment:%s", args.EnvName, args.ServiceName, args.Name), string(data), ctx.Logger) diff --git a/pkg/microservice/aslan/core/environment/handler/kube.go b/pkg/microservice/aslan/core/environment/handler/kube.go index 2a66ea01..07905fa1 100644 --- a/pkg/microservice/aslan/core/environment/handler/kube.go +++ b/pkg/microservice/aslan/core/environment/handler/kube.go @@ -85,7 +85,7 @@ func DeletePod(c *gin.Context) { envName := c.Query("envName") productName := c.Query("projectName") - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "集成环境-服务实例", fmt.Sprintf("环境名称:%s,pod名称:%s", c.Query("envName"), c.Param("podName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "环境-服务实例", fmt.Sprintf("环境名称:%s,pod名称:%s", c.Query("envName"), c.Param("podName")), "", ctx.Logger) ctx.Err = service.DeletePod(envName, productName, podName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 1d598a14..df6f33c8 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -90,7 +90,7 @@ func (*Router) Inject(router *gin.RouterGroup) { } // --------------------------------------------------------------------------------------- - // 产品管理接口(集成环境) + // 产品管理接口(环境) // --------------------------------------------------------------------------------------- environments := router.Group("environments") { diff --git a/pkg/microservice/aslan/core/environment/handler/service.go b/pkg/microservice/aslan/core/environment/handler/service.go index 00ec97b0..34bd1a91 100644 --- a/pkg/microservice/aslan/core/environment/handler/service.go +++ b/pkg/microservice/aslan/core/environment/handler/service.go @@ -49,7 +49,7 @@ func RestartService(c *gin.Context) { ServiceName: c.Param("serviceName"), } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Param("name"), c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", c.Param("name"), c.Param("serviceName")), "", ctx.Logger) ctx.Err = service.RestartService(args.EnvName, args, ctx.Logger) } @@ -59,7 +59,7 @@ func UpdateService(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "集成环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", envName, c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境-单服务", fmt.Sprintf("环境名称:%s,服务名称:%s", envName, c.Param("serviceName")), "", ctx.Logger) svcRev := new(service.SvcRevision) if err := c.BindJSON(svcRev); err != nil { @@ -95,7 +95,7 @@ func RestartNewService(c *gin.Context) { c, ctx.UserName, c.Query("projectName"), "重启", - "集成环境-服务", + "环境-服务", fmt.Sprintf( "环境名称:%s,服务名称:%s,%s:%s", args.EnvName, args.ServiceName, args.Type, args.Name, ), @@ -122,7 +122,7 @@ func ScaleNewService(c *gin.Context) { c, ctx.UserName, projectName, "伸缩", - "集成环境-服务", + "环境-服务", fmt.Sprintf("环境名称:%s,%s:%s", envName, resourceType, name), "", ctx.Logger) @@ -148,7 +148,7 @@ func ScaleService(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "伸缩", "集成环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", envName, c.Param("serviceName")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "伸缩", "环境-服务", fmt.Sprintf("环境名称:%s,服务名称:%s", envName, c.Param("serviceName")), "", ctx.Logger) number, err := strconv.Atoi(c.Query("number")) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 86661b6a..9ae486f5 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -303,7 +303,7 @@ func CleanProducts() { for _, prod := range products { _, err := templaterepo.NewProductColl().Find(prod.ProductName) if err != nil && err.Error() == "not found" { - logger.Errorf("集成环境所属的项目不存在,准备删除此集成环境, namespace:%s, 项目:%s\n", prod.Namespace, prod.ProductName) + logger.Errorf("环境所属的项目不存在,准备删除此环境, namespace:%s, 项目:%s\n", prod.Namespace, prod.ProductName) err = commonservice.DeleteProduct("CleanProducts", prod.EnvName, prod.ProductName, "", logger) if err != nil { logger.Errorf("delete product failed, namespace:%s, err:%v\n", prod.Namespace, err) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 7211198d..f343b6f6 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -219,7 +219,7 @@ func UpdateProductTemplate(name string, args *template.Product, log *zap.Sugared } for _, envVars := range args.EnvVars { - //创建集成环境变量 + //创建环境变量 if err = commonservice.CreateRenderSet(&commonmodels.RenderSet{ EnvName: envVars.EnvName, Name: args.ProductName, diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index e0ffc2ee..174815c7 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -135,7 +135,7 @@ var ( // ErrGetProductRevision ... ErrGetProductRevision = NewHTTPError(6067, "获取产品版本失败") // ErrFindProduct ... - ErrFindProduct = NewHTTPError(6068, "获取集成环境失败") + ErrFindProduct = NewHTTPError(6068, "获取环境失败") // ErrGetProductAuth ... ErrGetProductAuth = NewHTTPError(6069, "获取产品权限失败") // ErrUpdateProductAuth ... @@ -145,15 +145,15 @@ var ( // ErrStopPatchProduct ... ErrStopPatchProduct = NewHTTPError(6072, "产品收集集成测试覆盖率失败") // ErrCreateEnv ... - ErrCreateEnv = NewHTTPError(6074, "创建集成环境失败") + ErrCreateEnv = NewHTTPError(6074, "创建环境失败") // ErrListEnvs ... - ErrListEnvs = NewHTTPError(6075, "列出集成环境失败") + ErrListEnvs = NewHTTPError(6075, "列出环境失败") // ErrUpdateEnv ... - ErrUpdateEnv = NewHTTPError(6076, "更新集成环境失败") + ErrUpdateEnv = NewHTTPError(6076, "更新环境失败") // ErrDeleteEnv ... - ErrDeleteEnv = NewHTTPError(6077, "删除集成环境失败") + ErrDeleteEnv = NewHTTPError(6077, "删除环境失败") // ErrGetEnv ... - ErrGetEnv = NewHTTPError(6078, "获取集成环境失败") + ErrGetEnv = NewHTTPError(6078, "获取环境失败") // ErrFindProductTmpl ... ErrFindProductTmpl = NewHTTPError(6079, "项目已删除,环境正在回收中") // TODO: max error code reached, sharing error code with create product -- Gitee From c1a7aef9bb74d0c1711134adace2febd72791aff Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 8 Mar 2022 17:07:34 +0800 Subject: [PATCH 1777/2647] add notes for usage of object storage as cache Signed-off-by: zhangyifei --- pkg/types/cache.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/types/cache.go b/pkg/types/cache.go index 58ffcf3e..5343e5e6 100644 --- a/pkg/types/cache.go +++ b/pkg/types/cache.go @@ -30,6 +30,11 @@ const ( StaticProvision ProvisionType = "static" ) +// Note: +// In the current design, when the object storage is used as the cache medium, the default object storage +// will be used as the cache medium, but in the near future users may be allowed to select different object storage +// as the cache medium according to the cluster. +// So it is temporarily reserved in `v1.10.0`, but do not use. type ObjectProperties struct { ID string `json:"id" bson:"id"` } -- Gitee From ab0de4e3ad8eef11a26c2061b7175616dfe49236 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 9 Mar 2022 16:51:57 +0800 Subject: [PATCH 1778/2647] fix test result file optional Signed-off-by: ddh27 --- .../reaper/core/service/reaper/reaper.go | 2 +- .../warpdrive/core/service/taskplugin/job.go | 10 ++++------ .../warpdrive/core/service/taskplugin/testing.go | 13 ++++++------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 9fcbe120..271ad933 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -377,7 +377,7 @@ func (r *Reaper) Exec() error { } func (r *Reaper) AfterExec() error { - if r.Ctx.GinkgoTest != nil && r.Ctx.GinkgoTest.ResultPath != "" { + if r.Ctx.GinkgoTest != nil { resultPath := r.Ctx.GinkgoTest.ResultPath if !strings.HasPrefix(resultPath, "/") { resultPath = filepath.Join(r.ActiveWorkspace, resultPath) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 98764e23..f83d7806 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -322,12 +322,10 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ctx.Caches = b.JobCtx.Caches - if b.JobCtx.TestResultPath != "" { - ctx.GinkgoTest = &types.GinkgoTest{ - ResultPath: b.JobCtx.TestResultPath, - TestReportPath: b.JobCtx.TestReportPath, - ArtifactPaths: b.JobCtx.ArtifactPaths, - } + ctx.GinkgoTest = &types.GinkgoTest{ + ResultPath: b.JobCtx.TestResultPath, + TestReportPath: b.JobCtx.TestReportPath, + ArtifactPaths: b.JobCtx.ArtifactPaths, } ctx.StorageEndpoint = pipelineTask.ConfigPayload.S3Storage.Endpoint diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index f95510be..5a532e04 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -337,9 +337,6 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv //如果用户配置了测试结果目录需要收集,则下载测试结果,发送到aslan server //Note here: p.Task.TestName目前只有默认值test - if p.Task.JobCtx.TestResultPath == "" { - return - } testReport := new(types.TestReport) if pipelineTask.TestReports == nil { @@ -366,10 +363,12 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv } s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) if err == nil { - prefix := store.GetObjectPath("/") - if files, err := s3client.ListFiles(store.Bucket, prefix, true); err == nil { - if len(files) > 0 { - p.Task.JobCtx.IsHasArtifact = true + if len(p.Task.JobCtx.ArtifactPaths) > 0 { + prefix := store.GetObjectPath("/") + if files, err := s3client.ListFiles(store.Bucket, prefix, true); err == nil { + if len(files) > 0 { + p.Task.JobCtx.IsHasArtifact = true + } } } } -- Gitee From 0542e9550ea1e6a531cd8abbc194f285a374ce76 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 9 Mar 2022 17:00:37 +0800 Subject: [PATCH 1779/2647] fix gerrit integration problem Signed-off-by: liu deyi --- .../core/codehost/repository/mongodb/codehost.go | 10 ++++++++-- .../systemconfig/core/codehost/service/codehost.go | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index d84ec9e9..f4f45ed0 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) @@ -146,7 +147,7 @@ func (c *CodehostColl) DeleteCodeHostByID(ID int) error { func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, error) { query := bson.M{"id": host.ID, "deleted_at": 0} - change := bson.M{"$set": bson.M{ + modifyValue := bson.M{ "type": host.Type, "address": host.Address, "namespace": host.Namespace, @@ -157,7 +158,12 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, "password": host.Password, "enable_proxy": host.EnableProxy, "updated_at": time.Now().Unix(), - }} + } + if host.Type == setting.SourceFromGerrit { + modifyValue["access_token"] = host.AccessToken + } + + change := bson.M{"$set": modifyValue} _, err := c.Collection.UpdateOne(context.TODO(), query, change) return host, err } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 0700bc51..97e65966 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -31,6 +31,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) @@ -68,6 +69,9 @@ func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { } func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { + if host.Type == setting.SourceFromGerrit { + host.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", host.Username, host.Password))) + } return mongodb.NewCodehostColl().UpdateCodeHost(host) } -- Gitee From 117542a87cb08601f2904f05781e584da8b60d1c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 9 Mar 2022 17:53:50 +0800 Subject: [PATCH 1780/2647] debug ode Signed-off-by: liu deyi --- .../systemconfig/core/codehost/service/codehost.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 97e65966..aab46105 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -68,10 +68,13 @@ func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { return mongodb.NewCodehostColl().DeleteCodeHostByID(id) } -func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { +func UpdateCodeHost(host *models.CodeHost, log *zap.SugaredLogger) (*models.CodeHost, error) { + log.Infof("git type:%s", host.Type) + log.Infof("git accesstoken before:%s", host.AccessToken) if host.Type == setting.SourceFromGerrit { host.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", host.Username, host.Password))) } + log.Infof("git accesstoken after:%s", host.AccessToken) return mongodb.NewCodehostColl().UpdateCodeHost(host) } -- Gitee From c0a6f8948e16c2262dd8226836517b936665b9f0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 9 Mar 2022 18:19:08 +0800 Subject: [PATCH 1781/2647] remove debug code Signed-off-by: liu deyi --- .../systemconfig/core/codehost/service/codehost.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index aab46105..97e65966 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -68,13 +68,10 @@ func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { return mongodb.NewCodehostColl().DeleteCodeHostByID(id) } -func UpdateCodeHost(host *models.CodeHost, log *zap.SugaredLogger) (*models.CodeHost, error) { - log.Infof("git type:%s", host.Type) - log.Infof("git accesstoken before:%s", host.AccessToken) +func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { if host.Type == setting.SourceFromGerrit { host.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", host.Username, host.Password))) } - log.Infof("git accesstoken after:%s", host.AccessToken) return mongodb.NewCodehostColl().UpdateCodeHost(host) } -- Gitee From 5cae5fa83e983d695844b4145032600699cab8e2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 10:48:13 +0800 Subject: [PATCH 1782/2647] fix pm panic problem Signed-off-by: liu deyi --- .../aslan/core/environment/service/pm.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 733e1d05..6e1c8cd3 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -107,13 +107,6 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService service.ServiceName, setting.PMDeployType, service.ProductName, "", service.Revision, p.log, ) - for _, envconfig := range serviceTmpl.EnvConfigs { - if envconfig.EnvName == envName { - gp.EnvConfigs = []*models.EnvConfig{envconfig} - break - } - } - if err != nil { gp.Status = setting.PodFailed mutex.Lock() @@ -122,6 +115,13 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService return } + for _, envconfig := range serviceTmpl.EnvConfigs { + if envconfig.EnvName == envName { + gp.EnvConfigs = []*models.EnvConfig{envconfig} + break + } + } + gp.ProductName = serviceTmpl.ProductName if len(serviceTmpl.EnvStatuses) > 0 { envStatuses := make([]*commonmodels.EnvStatus, 0) -- Gitee From b14ef599ae96a12e2e7c400b64d582c47adcad14 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 10 Mar 2022 11:34:56 +0800 Subject: [PATCH 1783/2647] add branchName query parameter Signed-off-by: allenshen --- pkg/microservice/aslan/core/code/handler/router.go | 6 ++++-- pkg/microservice/aslan/core/code/handler/workspace.go | 6 +++--- pkg/microservice/aslan/core/service/handler/loader.go | 7 ++++--- pkg/microservice/aslan/core/service/handler/router.go | 9 ++++++--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 9627fc2d..0fa6ecea 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -44,10 +44,12 @@ func (*Router) Inject(router *gin.RouterGroup) { { workspace.DELETE("", GetProductNameByWorkspacePipeline, gin2.UpdateOperationLogStatus, CleanWorkspace) workspace.GET("/file", GetWorkspaceFile) - workspace.GET("/git/:codehostId/:repoName/:branchName/:remoteName", GetGitRepoInfo) + //workspace.GET("/git/:codehostId/:repoName/:branchName/:remoteName", GetGitRepoInfo) + workspace.GET("/git/:codehostId/:repoName", GetGitRepoInfo) workspace.GET("/tree", GetRepoTree) - workspace.GET("/codehub/:codehostId/:repoUUID/:branchName", GetCodehubRepoInfo) + //workspace.GET("/codehub/:codehostId/:repoUUID/:branchName", GetCodehubRepoInfo) + workspace.GET("/codehub/:codehostId/:repoUUID", GetCodehubRepoInfo) } } diff --git a/pkg/microservice/aslan/core/code/handler/workspace.go b/pkg/microservice/aslan/core/code/handler/workspace.go index 1d980ace..4009a41d 100644 --- a/pkg/microservice/aslan/core/code/handler/workspace.go +++ b/pkg/microservice/aslan/core/code/handler/workspace.go @@ -105,13 +105,13 @@ func GetGitRepoInfo(c *gin.Context) { return } - branchName := c.Param("branchName") + branchName := c.Query("branchName") if branchName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty branch name") return } - remoteName := c.Param("remoteName") + remoteName := c.Query("remoteName") if remoteName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty remote name") return @@ -171,7 +171,7 @@ func GetCodehubRepoInfo(c *gin.Context) { return } - branchName := c.Param("branchName") + branchName := c.Query("branchName") if branchName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty branch name") return diff --git a/pkg/microservice/aslan/core/service/handler/loader.go b/pkg/microservice/aslan/core/service/handler/loader.go index 00b5675f..607b9099 100644 --- a/pkg/microservice/aslan/core/service/handler/loader.go +++ b/pkg/microservice/aslan/core/service/handler/loader.go @@ -45,7 +45,7 @@ func PreloadServiceTemplate(c *gin.Context) { return } - branchName := c.Param("branchName") + branchName := c.Query("branchName") path := c.Query("path") isDir := c.Query("isDir") == "true" @@ -74,7 +74,7 @@ func LoadServiceTemplate(c *gin.Context) { return } - branchName := c.Param("branchName") + branchName := c.Query("branchName") args := new(svcservice.LoadServiceReq) if err := c.BindJSON(args); err != nil { @@ -108,7 +108,8 @@ func ValidateServiceUpdate(c *gin.Context) { return } - branchName := c.Param("branchName") + //branchName := c.Param("branchName") + branchName := c.Query("branchName") path := c.Query("path") isDir := c.Query("isDir") == "true" diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 450bae46..bbfcad44 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -70,9 +70,12 @@ func (*Router) Inject(router *gin.RouterGroup) { loader := router.Group("loader") { - loader.GET("/preload/:codehostId/:branchName", PreloadServiceTemplate) - loader.POST("/load/:codehostId/:branchName", LoadServiceTemplate) - loader.GET("/validateUpdate/:codehostId/:branchName", ValidateServiceUpdate) + //loader.GET("/preload/:codehostId/:branchName", PreloadServiceTemplate) + loader.GET("/preload/:codehostId", PreloadServiceTemplate) + //loader.POST("/load/:codehostId/:branchName", LoadServiceTemplate) + loader.POST("/load/:codehostId", LoadServiceTemplate) + //loader.GET("/validateUpdate/:codehostId/:branchName", ValidateServiceUpdate) + loader.GET("/validateUpdate/:codehostId", ValidateServiceUpdate) } pm := router.Group("pm") -- Gitee From 6cb5d8cc0a511b8735d6dafb61619f5e23ec8bb0 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 10 Mar 2022 11:48:47 +0800 Subject: [PATCH 1784/2647] clean up jobs asynchronously to avoid waiting for the next operation Signed-off-by: zhangyifei --- pkg/microservice/reaper/executor/executor.go | 2 +- .../core/service/taskplugin/build.go | 20 +++++++++---------- .../warpdrive/core/service/taskplugin/job.go | 2 +- .../core/service/taskplugin/testing.go | 20 +++++++++---------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 01385a8f..770cbcc2 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -59,7 +59,7 @@ func Execute() error { // operations, and wait for a fixed time. // Since `wd` will automatically delete the job after detecting the dogfile, this time has little // effect on the overall construction time. - time.Sleep(1 * time.Minute) + time.Sleep(30 * time.Second) }() var r *reaper.Reaper diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 63a99d0e..9f7ce925 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -366,19 +366,17 @@ func (p *BuildTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, PipelineType: string(pipelineTask.Type), } - // 清理用户取消和超时的任务 + // Clean up tasks that user canceled or timed out. defer func() { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } + go func() { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + } - return + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + } + }() }() err := saveContainerLog(pipelineTask, p.KubeNamespace, p.Task.ClusterID, p.FileName, jobLabel, p.kubeClient) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 98764e23..dac3d9bf 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -111,7 +111,7 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st } if err := containerlog.GetContainerLogs(namespace, pods[0].Name, pods[0].Spec.Containers[0].Name, false, int64(0), buf, clientSet); err != nil { - return err + return fmt.Errorf("failed to get container logs: %s", err) } if tempFileName, err := util.GenerateTmpFile(); err == nil { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index f95510be..6dd29cfc 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -303,17 +303,17 @@ func (p *TestPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serv PipelineType: string(pipelineTask.Type), } - // 日志保存失败与否都清理job + // Clean up tasks that user canceled or timed out. defer func() { - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.Error = err.Error() - } - return + go func() { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + } + }() }() err := saveContainerLog(pipelineTask, p.KubeNamespace, p.Task.ClusterID, p.FileName, jobLabel, p.kubeClient) -- Gitee From b035b988313b5a07dc5009dca16d61807fae0ba0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Mar 2022 14:06:54 +0800 Subject: [PATCH 1785/2647] err message Signed-off-by: mouuii --- pkg/tool/errors/const.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/errors/const.go b/pkg/tool/errors/const.go index 0a42fc13..c4ed8c26 100644 --- a/pkg/tool/errors/const.go +++ b/pkg/tool/errors/const.go @@ -70,7 +70,7 @@ const ( const ( FindProductTmplErrMsg = "获取产品失败" - FindProductServiceErrMsg = "产品不包含任何服务" + FindProductServiceErrMsg = "不包含任何服务" ) // Service & Containers -- Gitee From 857b5d84a89c2518535285ef0035ffe7a185a1a5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 10 Mar 2022 15:34:17 +0800 Subject: [PATCH 1786/2647] vmbug Signed-off-by: mouuii --- .../core/environment/service/environment.go | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 40f4d5e7..0f16e2a1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -304,7 +304,12 @@ func AutoUpdateProduct(envNames []string, productName, requestID string, force b envStatuses := make([]*EnvStatus, 0) - if !force { + project, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + return nil, err + } + + if !force && project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { modifiedByENV := make(map[string][]*serviceInfo) for _, env := range envNames { p, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: env}) @@ -645,38 +650,43 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k if err != nil { return e.ErrUpdateEnv.AddErr(err) } - - if !force { + project, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + return err + } + if !force && project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { modifiedServices := getModifiedServiceFromObjectMetaList(kube.GetDirtyResources(exitedProd.Namespace, kubeClient)) if len(modifiedServices) > 0 { data, err := json.Marshal(modifiedServices) if err != nil { log.Errorf("Marshal failure: %v", err) } + log.Errorf("the following services are modified since last update: %s", data) return fmt.Errorf("the following services are modified since last update: %s", data) } } - err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, kubeClient, log) - - if err != nil { - log.Errorf("[%s][P:%s] service.UpdateProductV2 create kubeEnv error: %v", envName, productName, err) - return err - } + if project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { + err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, kubeClient, log) - err = commonservice.CreateRenderSet( - &commonmodels.RenderSet{ - Name: exitedProd.Namespace, - EnvName: envName, - ProductTmpl: productName, - KVs: kvs, - }, - log, - ) + if err != nil { + log.Errorf("[%s][P:%s] service.UpdateProductV2 create kubeEnv error: %v", envName, productName, err) + return err + } - if err != nil { - log.Errorf("[%s][P:%s] create renderset error: %v", envName, productName, err) - return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) + err = commonservice.CreateRenderSet( + &commonmodels.RenderSet{ + Name: exitedProd.Namespace, + EnvName: envName, + ProductTmpl: productName, + KVs: kvs, + }, + log, + ) + if err != nil { + log.Errorf("[%s][P:%s] create renderset error: %v", envName, productName, err) + return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) + } } // 检查renderinfo是否为空(适配历史product) -- Gitee From edffafb666ac302b9ecb161f6ca370f8a561ac62 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 16:53:17 +0800 Subject: [PATCH 1787/2647] helm chart template supports gerrit import Signed-off-by: liu deyi --- .../core/common/service/template/chart.go | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 33aefa37..74ef87f9 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -19,8 +19,10 @@ package dockerfile import ( "fmt" "os" + "path" "path/filepath" "regexp" + "strings" "github.com/27149chen/afero" "github.com/pkg/errors" @@ -29,12 +31,15 @@ import ( "k8s.io/apimachinery/pkg/util/wait" configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -196,11 +201,25 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. return fmt.Errorf("a chart template with name %s is already existing", name) } - sha1, err := processChartFromSource(name, args, logger) + ch, err := systemconfig.New().GetCodeHost(args.CodehostID) if err != nil { - logger.Errorf("Failed to create chart %s, err: %s", name, err) + log.Errorf("Failed to get codeHost by id %d, err: %s", args.CodehostID, err) return err } + var ( + sha1 string + loadErr error + ) + switch ch.Type { + case setting.SourceFromGerrit: + sha1, loadErr = processChartFromGerrit(name, args, logger) + default: + sha1, loadErr = processChartFromSource(name, args, logger) + } + if loadErr != nil { + logger.Errorf("Failed to create chart %s, err: %s", name, loadErr) + return loadErr + } variablesNames, err := parseTemplateVariables(name, args.Path, logger) if err != nil { @@ -401,3 +420,68 @@ func processChartFromSource(name string, args *fs.DownloadFromSourceArgs, logger return sha1, nil } + +func processChartFromGerrit(name string, args *fs.DownloadFromSourceArgs, logger *zap.SugaredLogger) (string, error) { + var ( + wg wait.Group + sha1 string + err error + ) + + base := path.Join(config.S3StoragePath(), args.Repo) + filePath := strings.TrimLeft(args.Path, "/") + currentChartPath := path.Join(base, filePath) + wg.Start(func() { + logger.Debug("Start to save and upload chart") + localBase := configbase.LocalChartTemplatePath(name) + s3Base := configbase.ObjectStorageChartTemplatePath(name) + + err1 := fs.CopyAndUploadFiles([]string{name}, localBase, s3Base, currentChartPath, logger) + if err1 != nil { + logger.Errorf("Failed to save files to disk, err: %s", err1) + err = err1 + return + } + + logger.Debug("Finish to save and upload chart") + }) + + wg.Start(func() { + logger.Debug("Start to calculate sha1 for chart") + tmpDir, err1 := os.MkdirTemp("", "") + if err1 != nil { + logger.Errorf("Failed to create temp dir, err: %s", err1) + err = err1 + return + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + fileName := fmt.Sprintf("%s.tar.gz", filepath.Base(args.Path)) + tarball := filepath.Join(tmpDir, fileName) + tree := os.DirFS(currentChartPath) + if err1 = fsutil.Tar(tree, tarball); err1 != nil { + logger.Errorf("Failed to archive files to %s, err: %s", tarball, err1) + err = err1 + return + } + + sha1, err1 = fsutil.Sha1(os.DirFS(tmpDir), fileName) + if err1 != nil { + logger.Errorf("Failed to calculate sha1 for file %s, err: %s", tarball, err1) + err = err1 + return + } + + logger.Debug("Finish to calculate sha1 for chart") + }) + + wg.Wait() + + if err != nil { + return "", err + } + + return sha1, nil +} -- Gitee From 6bff5b0c8d8e46d408368eb979700ae7b6240509 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 17:25:19 +0800 Subject: [PATCH 1788/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/template/chart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 74ef87f9..37916d4f 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -436,7 +436,7 @@ func processChartFromGerrit(name string, args *fs.DownloadFromSourceArgs, logger localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) - err1 := fs.CopyAndUploadFiles([]string{name}, localBase, s3Base, currentChartPath, logger) + err1 := fs.CopyAndUploadFiles([]string{name}, path.Join(localBase, path.Base(args.Path)), s3Base, currentChartPath, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) err = err1 -- Gitee From a9e04ec54e557fceb610fced6b523ef0a28dd28c Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 10 Mar 2022 17:28:12 +0800 Subject: [PATCH 1789/2647] print error msg when failing to run reaper Signed-off-by: zhangyifei --- pkg/microservice/reaper/executor/executor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/reaper/executor/executor.go b/pkg/microservice/reaper/executor/executor.go index 770cbcc2..d5f56ce0 100644 --- a/pkg/microservice/reaper/executor/executor.go +++ b/pkg/microservice/reaper/executor/executor.go @@ -45,6 +45,7 @@ func Execute() error { resultMsg := types.JobSuccess if err != nil { resultMsg = types.JobFail + log.Errorf("Failed to run: %s.", err) } log.Infof("Job Status: %s", resultMsg) -- Gitee From 772d5bac6a24cec1562c676c734312b5c02fa4a4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 17:51:42 +0800 Subject: [PATCH 1790/2647] modify the logic of updating helm chart template Signed-off-by: liu deyi --- .../core/common/service/template/chart.go | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 37916d4f..e09a7098 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -251,12 +251,27 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z logger.Errorf("Failed to get chart template %s, err: %s", name, err) return err } - - sha1, err := processChartFromSource(name, args, logger) + ch, err := systemconfig.New().GetCodeHost(args.CodehostID) if err != nil { - logger.Errorf("Failed to update chart %s, err: %s", name, err) + log.Errorf("Failed to get codeHost by id %d, err: %s", args.CodehostID, err) return err } + var ( + sha1 string + loadErr error + ) + + switch ch.Type { + case setting.SourceFromGerrit: + sha1, loadErr = processChartFromGerrit(name, args, logger) + default: + sha1, loadErr = processChartFromSource(name, args, logger) + } + + if loadErr != nil { + logger.Errorf("Failed to update chart %s, err: %s", name, loadErr) + return loadErr + } if chart.Sha1 == sha1 { logger.Debug("Chart %s has no changes, skip updating.", name) -- Gitee From b7ae9fb690fd6e4aefd7343d76432d2e4a86b210 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 10 Mar 2022 18:00:43 +0800 Subject: [PATCH 1791/2647] fix test result files null bug Signed-off-by: ddh27 --- pkg/microservice/reaper/core/service/reaper/reaper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 271ad933..1aa1b34e 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -379,7 +379,7 @@ func (r *Reaper) Exec() error { func (r *Reaper) AfterExec() error { if r.Ctx.GinkgoTest != nil { resultPath := r.Ctx.GinkgoTest.ResultPath - if !strings.HasPrefix(resultPath, "/") { + if resultPath != "" && !strings.HasPrefix(resultPath, "/") { resultPath = filepath.Join(r.ActiveWorkspace, resultPath) } -- Gitee From 943f7bd71577710787788ef52db1748650c784bf Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 18:28:08 +0800 Subject: [PATCH 1792/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/s3.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 1e3a288c..0b74664c 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -105,8 +105,8 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } - defer os.RemoveAll(tmpDir) - + //defer os.RemoveAll(tmpDir) + logger.Info("name: %s", name) s3, err := s3service.FindDefaultS3() if err != nil { logger.Errorf("Failed to find default s3, err: %s", err) @@ -126,17 +126,18 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S logger.Errorf("Failed to create s3 client, err: %s", err) return err } - + logger.Info("s3Path: %s", s3Path) if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return err } - + logger.Info("localPath: %s", localPath) + logger.Info("localBase: %s", localBase) if err = fsutil.Untar(localPath, localBase); err != nil { - if err = fsutil.Untar(localPath, filepath.Join(localBase, name)); err != nil { - logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) - return err - } + //if err = fsutil.Untar(localPath, filepath.Join(localBase, name)); err != nil { + logger.Errorf("Failed to extract tarball %s, err: %+v", localPath, err) + return err + //} } return nil } -- Gitee From 3f5b5dc7cee81dc0c179d2c83573896f2851344c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 18:42:33 +0800 Subject: [PATCH 1793/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 11 ++++------- .../aslan/core/common/service/template/chart.go | 7 ++++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 0b74664c..56eb4779 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -105,8 +105,7 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } - //defer os.RemoveAll(tmpDir) - logger.Info("name: %s", name) + defer os.RemoveAll(tmpDir) s3, err := s3service.FindDefaultS3() if err != nil { logger.Errorf("Failed to find default s3, err: %s", err) @@ -126,18 +125,16 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S logger.Errorf("Failed to create s3 client, err: %s", err) return err } - logger.Info("s3Path: %s", s3Path) + logger.Infof("s3Path: %s", s3Path) if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return err } - logger.Info("localPath: %s", localPath) - logger.Info("localBase: %s", localBase) + logger.Infof("localPath: %s", localPath) + logger.Infof("localBase: %s", localBase) if err = fsutil.Untar(localPath, localBase); err != nil { - //if err = fsutil.Untar(localPath, filepath.Join(localBase, name)); err != nil { logger.Errorf("Failed to extract tarball %s, err: %+v", localPath, err) return err - //} } return nil } diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index e09a7098..544dda5b 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -64,12 +64,15 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { } localBase := configbase.LocalChartTemplatePath(name) + base := filepath.Base(chart.Path) + if chart.Source == setting.SourceFromGerrit { + localBase = filepath.Join(localBase, base) + } s3Base := configbase.ObjectStorageChartTemplatePath(name) if err = fs.PreloadFiles(name, localBase, s3Base, logger); err != nil { return nil, err } - base := filepath.Base(chart.Path) localPath := filepath.Join(localBase, base) fis, err := fs.GetFileInfos(os.DirFS(localPath)) if err != nil { @@ -242,6 +245,7 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. CodeHostID: args.CodehostID, Sha1: sha1, ChartVariables: variables, + Source: ch.Type, }) } @@ -305,6 +309,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z CodeHostID: args.CodehostID, Sha1: sha1, ChartVariables: variables, + Source: ch.Type, }) } -- Gitee From 9a866247774e1cef4029ab2088a8494f5401a505 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 18:49:11 +0800 Subject: [PATCH 1794/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/template/chart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 544dda5b..38bd7b0d 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -65,7 +65,7 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { localBase := configbase.LocalChartTemplatePath(name) base := filepath.Base(chart.Path) - if chart.Source == setting.SourceFromGerrit { + if chart.Source == setting.SourceFromGerrit && !strings.Contains(localBase, base) { localBase = filepath.Join(localBase, base) } s3Base := configbase.ObjectStorageChartTemplatePath(name) -- Gitee From 7a7aea7d00e086b4cc5a1fbead78f6c2bd9d7206 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 19:05:06 +0800 Subject: [PATCH 1795/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/file.go | 3 ++- pkg/microservice/aslan/core/common/service/template/chart.go | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 7ceae9aa..af13e493 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -77,7 +77,8 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { var wg wait.Group var err error - + logger.Infof("CopyAndUploadFiles currentChartPath:%s", currentChartPath) + logger.Infof("CopyAndUploadFiles localBase:%s", localBase) wg.Start(func() { copyErr := copy.Copy(currentChartPath, localBase) if copyErr != nil { diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 38bd7b0d..8035068e 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -64,15 +64,12 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { } localBase := configbase.LocalChartTemplatePath(name) - base := filepath.Base(chart.Path) - if chart.Source == setting.SourceFromGerrit && !strings.Contains(localBase, base) { - localBase = filepath.Join(localBase, base) - } s3Base := configbase.ObjectStorageChartTemplatePath(name) if err = fs.PreloadFiles(name, localBase, s3Base, logger); err != nil { return nil, err } + base := filepath.Base(chart.Path) localPath := filepath.Join(localBase, base) fis, err := fs.GetFileInfos(os.DirFS(localPath)) if err != nil { -- Gitee From 6632e045f0b20fddf7f134a53823bc2597320246 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 10 Mar 2022 19:17:50 +0800 Subject: [PATCH 1796/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index af13e493..95af8e06 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -75,28 +75,28 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string // CopyAndUploadFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { - var wg wait.Group + //var wg wait.Group var err error logger.Infof("CopyAndUploadFiles currentChartPath:%s", currentChartPath) logger.Infof("CopyAndUploadFiles localBase:%s", localBase) - wg.Start(func() { - copyErr := copy.Copy(currentChartPath, localBase) - if copyErr != nil { - logger.Errorf("failed to copy chart info, err %s", err) - err = copyErr - } - }) - - wg.Start(func() { - fileTree := os.DirFS(currentChartPath) - err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) - if err2 != nil { - logger.Errorf("Failed to upload files to s3, err: %s", err2) - err = err2 - } - }) + //wg.Start(func() { + copyErr := copy.Copy(currentChartPath, localBase) + if copyErr != nil { + logger.Errorf("failed to copy chart info, err %s", err) + err = copyErr + } + //}) + + //wg.Start(func() { + fileTree := os.DirFS(localBase) + err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) + if err2 != nil { + logger.Errorf("Failed to upload files to s3, err: %s", err2) + err = err2 + } + //}) - wg.Wait() + //wg.Wait() return err } -- Gitee From 21cc141729a50b95c1112f22fac24e3e5d84d6e5 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Thu, 10 Mar 2022 19:46:54 +0800 Subject: [PATCH 1797/2647] add helm chart demo (#1158) * add helm chart demo Signed-off-by: fansi * cancel registry conf switch for Helm Chart demo Signed-off-by: fansi Co-authored-by: fansi --- .../full-charts/service1/Chart.yaml | 24 +++++++ .../service1/templates/_helpers.tpl | 63 +++++++++++++++++++ .../service1/templates/deployment.yaml | 37 +++++++++++ .../service1/templates/service.yaml | 16 +++++ .../full-charts/service1/values.yaml | 18 ++++++ .../full-charts/service2/Chart.yaml | 24 +++++++ .../service2/templates/_helpers.tpl | 63 +++++++++++++++++++ .../service2/templates/deployment.yaml | 37 +++++++++++ .../service2/templates/service.yaml | 16 +++++ .../full-charts/service2/values.yaml | 18 ++++++ 10 files changed, 316 insertions(+) create mode 100644 examples/multi-service-demo/full-charts/service1/Chart.yaml create mode 100644 examples/multi-service-demo/full-charts/service1/templates/_helpers.tpl create mode 100644 examples/multi-service-demo/full-charts/service1/templates/deployment.yaml create mode 100644 examples/multi-service-demo/full-charts/service1/templates/service.yaml create mode 100644 examples/multi-service-demo/full-charts/service1/values.yaml create mode 100644 examples/multi-service-demo/full-charts/service2/Chart.yaml create mode 100644 examples/multi-service-demo/full-charts/service2/templates/_helpers.tpl create mode 100644 examples/multi-service-demo/full-charts/service2/templates/deployment.yaml create mode 100644 examples/multi-service-demo/full-charts/service2/templates/service.yaml create mode 100644 examples/multi-service-demo/full-charts/service2/values.yaml diff --git a/examples/multi-service-demo/full-charts/service1/Chart.yaml b/examples/multi-service-demo/full-charts/service1/Chart.yaml new file mode 100644 index 00000000..ae9c80a0 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service1/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: service1 +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/examples/multi-service-demo/full-charts/service1/templates/_helpers.tpl b/examples/multi-service-demo/full-charts/service1/templates/_helpers.tpl new file mode 100644 index 00000000..2668feb5 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service1/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +helm.sh/chart: {{ include "chart.chart" . }} +{{ include "chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/examples/multi-service-demo/full-charts/service1/templates/deployment.yaml b/examples/multi-service-demo/full-charts/service1/templates/deployment.yaml new file mode 100644 index 00000000..e441d6b8 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service1/templates/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + spec: + containers: + - name: {{ .Values.fullnameOverride }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: Always + command: + - /workspace/{{ .Values.fullnameOverride }} + ports: + - protocol: TCP + containerPort: {{ .Values.port }} + resources: + limits: + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.mem }} + requests: + cpu: {{ .Values.resources.requests.cpu }} + memory: {{ .Values.resources.requests.mem }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} \ No newline at end of file diff --git a/examples/multi-service-demo/full-charts/service1/templates/service.yaml b/examples/multi-service-demo/full-charts/service1/templates/service.yaml new file mode 100644 index 00000000..d21ce064 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service1/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + type: NodePort + ports: + - protocol: TCP + port: {{ .Values.port }} + targetPort: {{ .Values.port }} \ No newline at end of file diff --git a/examples/multi-service-demo/full-charts/service1/values.yaml b/examples/multi-service-demo/full-charts/service1/values.yaml new file mode 100644 index 00000000..f91bbe3c --- /dev/null +++ b/examples/multi-service-demo/full-charts/service1/values.yaml @@ -0,0 +1,18 @@ +fullnameOverride: service1 +replicaCount: 1 +port: 20221 + +imagePullSecretsName: "default-registry-secret" + +image: + repository: "ccr.ccs.tencentyun.com/koderover-public/service1" + tag: "latest" + +resources: + requests: + cpu: 10m + mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi diff --git a/examples/multi-service-demo/full-charts/service2/Chart.yaml b/examples/multi-service-demo/full-charts/service2/Chart.yaml new file mode 100644 index 00000000..d7ba887a --- /dev/null +++ b/examples/multi-service-demo/full-charts/service2/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: service2 +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/examples/multi-service-demo/full-charts/service2/templates/_helpers.tpl b/examples/multi-service-demo/full-charts/service2/templates/_helpers.tpl new file mode 100644 index 00000000..2668feb5 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service2/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +helm.sh/chart: {{ include "chart.chart" . }} +{{ include "chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/examples/multi-service-demo/full-charts/service2/templates/deployment.yaml b/examples/multi-service-demo/full-charts/service2/templates/deployment.yaml new file mode 100644 index 00000000..e441d6b8 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service2/templates/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + spec: + containers: + - name: {{ .Values.fullnameOverride }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: Always + command: + - /workspace/{{ .Values.fullnameOverride }} + ports: + - protocol: TCP + containerPort: {{ .Values.port }} + resources: + limits: + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.mem }} + requests: + cpu: {{ .Values.resources.requests.cpu }} + memory: {{ .Values.resources.requests.mem }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} \ No newline at end of file diff --git a/examples/multi-service-demo/full-charts/service2/templates/service.yaml b/examples/multi-service-demo/full-charts/service2/templates/service.yaml new file mode 100644 index 00000000..d21ce064 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service2/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + type: NodePort + ports: + - protocol: TCP + port: {{ .Values.port }} + targetPort: {{ .Values.port }} \ No newline at end of file diff --git a/examples/multi-service-demo/full-charts/service2/values.yaml b/examples/multi-service-demo/full-charts/service2/values.yaml new file mode 100644 index 00000000..99eb1f7f --- /dev/null +++ b/examples/multi-service-demo/full-charts/service2/values.yaml @@ -0,0 +1,18 @@ +fullnameOverride: service2 +replicaCount: 1 +port: 20222 + +imagePullSecretsName: "default-registry-secret" + +image: + repository: "ccr.ccs.tencentyun.com/koderover-public/service2" + tag: "latest" + +resources: + requests: + cpu: 10m + mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi -- Gitee From 7b2548257333040bd8508e1765724ecbec362570 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Mar 2022 10:28:36 +0800 Subject: [PATCH 1798/2647] optimize untar logic Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 55 +++++++------ .../aslan/core/common/service/fs/s3.go | 45 ++++++++++- .../core/common/service/template/chart.go | 16 ++-- .../aslan/core/service/service/helm.go | 2 +- pkg/util/file.go | 79 +++++++++++++++++++ 5 files changed, 161 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 95af8e06..21b3b8ee 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -24,12 +24,13 @@ import ( "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/wait" + "github.com/koderover/zadig/pkg/setting" fsutil "github.com/koderover/zadig/pkg/util/fs" ) // PreloadFiles downloads a tarball from object storage and extracts it to a local path for further usage. // It happens only if files do not exist in local disk. -func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) error { +func PreloadFiles(name, localBase, s3Base, source string, logger *zap.SugaredLogger) error { ok, err := fsutil.DirExists(localBase) if err != nil { logger.Errorf("Failed to check if dir %s is exiting, err: %s", localBase, err) @@ -39,9 +40,17 @@ func PreloadFiles(name, localBase, s3Base string, logger *zap.SugaredLogger) err return nil } - if err = DownloadAndExtractFilesFromS3(name, localBase, s3Base, logger); err != nil { - logger.Errorf("Failed to download files from s3, err: %s", err) - return err + switch source { + case setting.SourceFromGerrit: + if err = GerritDownloadAndExtractFilesFromS3(name, localBase, s3Base, logger); err != nil { + logger.Errorf("Failed to download files from s3, err: %s", err) + return err + } + default: + if err = DownloadAndExtractFilesFromS3(name, localBase, s3Base, logger); err != nil { + logger.Errorf("Failed to download files from s3, err: %s", err) + return err + } } return nil @@ -75,28 +84,26 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string // CopyAndUploadFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { - //var wg wait.Group + var wg wait.Group var err error - logger.Infof("CopyAndUploadFiles currentChartPath:%s", currentChartPath) - logger.Infof("CopyAndUploadFiles localBase:%s", localBase) - //wg.Start(func() { - copyErr := copy.Copy(currentChartPath, localBase) - if copyErr != nil { - logger.Errorf("failed to copy chart info, err %s", err) - err = copyErr - } - //}) - - //wg.Start(func() { - fileTree := os.DirFS(localBase) - err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) - if err2 != nil { - logger.Errorf("Failed to upload files to s3, err: %s", err2) - err = err2 - } - //}) + wg.Start(func() { + copyErr := copy.Copy(currentChartPath, localBase) + if copyErr != nil { + logger.Errorf("failed to copy chart info, err %s", err) + err = copyErr + } + }) - //wg.Wait() + wg.Start(func() { + fileTree := os.DirFS(currentChartPath) + err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) + if err2 != nil { + logger.Errorf("Failed to upload files to s3, err: %s", err2) + err = err2 + } + }) + + wg.Wait() return err } diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 56eb4779..fd582f1c 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -27,6 +27,7 @@ import ( s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -125,13 +126,12 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S logger.Errorf("Failed to create s3 client, err: %s", err) return err } - logger.Infof("s3Path: %s", s3Path) + if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { logger.Errorf("Failed to download file from s3, err: %s", err) return err } - logger.Infof("localPath: %s", localPath) - logger.Infof("localBase: %s", localBase) + if err = fsutil.Untar(localPath, localBase); err != nil { logger.Errorf("Failed to extract tarball %s, err: %+v", localPath, err) return err @@ -165,3 +165,42 @@ func DeleteArchivedFileFromS3(names []string, s3Base string, logger *zap.Sugared return client.DeleteObjects(s3.Bucket, s3PathList) } + +func GerritDownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.SugaredLogger) error { + tmpDir, err := os.MkdirTemp("", "") + if err != nil { + logger.Errorf("Failed to create temp dir, err: %s", err) + } + defer os.RemoveAll(tmpDir) + s3, err := s3service.FindDefaultS3() + if err != nil { + logger.Errorf("Failed to find default s3, err: %s", err) + return err + } + tarball := fmt.Sprintf("%s.tar.gz", name) + localPath := filepath.Join(tmpDir, tarball) + s3Path := filepath.Join(s3.Subfolder, s3Base, tarball) + + forcedPathStyle := true + if s3.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + + client, err := s3tool.NewClient(s3.Endpoint, s3.Ak, s3.Sk, s3.Insecure, forcedPathStyle) + if err != nil { + logger.Errorf("Failed to create s3 client, err: %s", err) + return err + } + logger.Infof("s3Path: %s", s3Path) + if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { + logger.Errorf("Failed to download file from s3, err: %s", err) + return err + } + logger.Infof("localPath: %s", localPath) + logger.Infof("localBase: %s", localBase) + if err = util.UnTar(localPath, localBase); err != nil { + logger.Errorf("Failed to extract tarball %s, err: %+v", localPath, err) + return err + } + return nil +} diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 8035068e..49362173 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -65,7 +65,7 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) - if err = fs.PreloadFiles(name, localBase, s3Base, logger); err != nil { + if err = fs.PreloadFiles(name, localBase, s3Base, chart.Source, logger); err != nil { return nil, err } @@ -159,13 +159,13 @@ func GetFileContentForTemplate(name, filePath, fileName string, logger *zap.Suga return nil, err } - return getFileContent(name, chart.Path, filePath, fileName, logger) + return getFileContent(name, chart.Path, filePath, fileName, chart.Source, logger) } -func getFileContent(name, path, filePath, fileName string, logger *zap.SugaredLogger) ([]byte, error) { +func getFileContent(name, path, filePath, fileName, source string, logger *zap.SugaredLogger) ([]byte, error) { localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) - if err := fs.PreloadFiles(name, localBase, s3Base, logger); err != nil { + if err := fs.PreloadFiles(name, localBase, s3Base, source, logger); err != nil { return nil, err } @@ -180,8 +180,8 @@ func getFileContent(name, path, filePath, fileName string, logger *zap.SugaredLo return fileContent, nil } -func parseTemplateVariables(name, path string, logger *zap.SugaredLogger) ([]string, error) { - valueYamlContent, err := getFileContent(name, path, "", setting.ValuesYaml, logger) +func parseTemplateVariables(name, path, source string, logger *zap.SugaredLogger) ([]string, error) { + valueYamlContent, err := getFileContent(name, path, "", setting.ValuesYaml, source, logger) if err != nil { return nil, errors.Wrap(err, "failed to read values.yaml") } @@ -221,7 +221,7 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. return loadErr } - variablesNames, err := parseTemplateVariables(name, args.Path, logger) + variablesNames, err := parseTemplateVariables(name, args.Path, ch.Type, logger) if err != nil { return errors.Wrapf(err, "failed to prase variables") } @@ -279,7 +279,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z return nil } - variablesNames, err := parseTemplateVariables(name, args.Path, logger) + variablesNames, err := parseTemplateVariables(name, args.Path, ch.Type, logger) if err != nil { return errors.Wrapf(err, "failed to prase variables") } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e5aaee9a..d8d437a2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -242,7 +242,7 @@ func prepareChartTemplateData(templateName string, logger *zap.SugaredLogger) (* // get chart template from local disk localBase := configbase.LocalChartTemplatePath(templateName) s3Base := configbase.ObjectStorageChartTemplatePath(templateName) - if err = fsservice.PreloadFiles(templateName, localBase, s3Base, logger); err != nil { + if err = fsservice.PreloadFiles(templateName, localBase, s3Base, templateChart.Source, logger); err != nil { logger.Errorf("Failed to download template %s, err: %s", templateName, err) return nil, err } diff --git a/pkg/util/file.go b/pkg/util/file.go index 30144213..76ea797f 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -17,8 +17,13 @@ limitations under the License. package util import ( + "archive/tar" + "compress/gzip" + "fmt" + "io" "io/ioutil" "os" + "path/filepath" ) func GenerateTmpFile() (string, error) { @@ -60,3 +65,77 @@ func ReadFile(filename string) ([]byte, error) { } return contentByte, nil } + +func UnTar(src, dst string) (err error) { + // 打开准备解压的 tar 包 + fr, err := os.Open(src) + if err != nil { + return + } + defer fr.Close() + + // 将打开的文件先解压 + gr, err := gzip.NewReader(fr) + if err != nil { + return + } + defer gr.Close() + + // 通过 gr 创建 tar.Reader + tr := tar.NewReader(gr) + + // 现在已经获得了 tar.Reader 结构了,只需要循环里面的数据写入文件就可以了 + for { + hdr, err := tr.Next() + + switch { + case err == io.EOF: + return nil + case err != nil: + return err + case hdr == nil: + continue + } + + // 处理下保存路径,将要保存的目录加上 header 中的 Name + // 这个变量保存的有可能是目录,有可能是文件,所以就叫 FileDir 了…… + dstFileDir := filepath.Join(dst, hdr.Name) + + // 根据 header 的 Typeflag 字段,判断文件的类型 + switch hdr.Typeflag { + case tar.TypeDir: // 如果是目录时候,创建目录 + // 判断下目录是否存在,不存在就创建 + if b := ExistDir(dstFileDir); !b { + // 使用 MkdirAll 不使用 Mkdir ,就类似 Linux 终端下的 mkdir -p, + // 可以递归创建每一级目录 + if err := os.MkdirAll(dstFileDir, 0775); err != nil { + return err + } + } + case tar.TypeReg: // 如果是文件就写入到磁盘 + // 创建一个可以读写的文件,权限就使用 header 中记录的权限 + // 因为操作系统的 FileMode 是 int32 类型的,hdr 中的是 int64,所以转换下 + file, err := os.OpenFile(dstFileDir, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) + if err != nil { + return err + } + n, err := io.Copy(file, tr) + if err != nil { + return err + } + // 将解压结果输出显示 + fmt.Printf("成功解压: %s , 共处理了 %d 个字符\n", dstFileDir, n) + + // 不要忘记关闭打开的文件,因为它是在 for 循环中,不能使用 defer + // 如果想使用 defer 就放在一个单独的函数中 + file.Close() + } + } + return nil +} + +// 判断目录是否存在 +func ExistDir(dirname string) bool { + fi, err := os.Stat(dirname) + return (err == nil || os.IsExist(err)) && fi.IsDir() +} -- Gitee From 8ef9e8c575f06ef9edb6c8bf50bb272c19e71fb1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Mar 2022 11:39:25 +0800 Subject: [PATCH 1799/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 2 +- pkg/tool/git/github/repositories.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index fd582f1c..c2949d14 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -171,7 +171,7 @@ func GerritDownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger if err != nil { logger.Errorf("Failed to create temp dir, err: %s", err) } - defer os.RemoveAll(tmpDir) + //defer os.RemoveAll(tmpDir) s3, err := s3service.FindDefaultS3() if err != nil { logger.Errorf("Failed to find default s3, err: %s", err) diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 5bba3419..ea12dbac 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -317,7 +317,7 @@ func (c *Client) getTreeContents(ctx context.Context, owner, repo, branch, base, if err != nil { return err } - return afero.WriteFile(fs, fsutil.ShortenFileBase(base, path), []byte(content), 0644) + return afero.WriteFile(fs, fsutil.ShortenFileBase(base, path), []byte(content), 0666) } for _, fileOrDir := range directoryContent { -- Gitee From 4a0c9ab3be99c5ee86efa0d01fab841d1087e04d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Mar 2022 11:44:59 +0800 Subject: [PATCH 1800/2647] code info return tag&pr&branch Signed-off-by: mouuii --- .../aslan/core/code/service/repo.go | 66 +++++++++---------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 77075ab4..2a3a09ae 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -51,22 +51,19 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( var errList *multierror.Error for _, info := range infos { - //pb 代表pr and branch - if param == "" || param == "bp" { - wg.Add(1) - go func(info *GitRepoInfo) { - defer func() { - wg.Done() - }() - info.PRs, err = CodeHostListPRs(info.CodehostID, info.Repo, strings.Replace(info.Owner, "%2F", "/", -1), "", "", 0, 0, log) - if err != nil { - errList = multierror.Append(errList, err) - info.ErrorMsg = err.Error() - info.PRs = []*PullRequest{} - return - } - }(info) - } + wg.Add(1) + go func(info *GitRepoInfo) { + defer func() { + wg.Done() + }() + info.PRs, err = CodeHostListPRs(info.CodehostID, info.Repo, strings.Replace(info.Owner, "%2F", "/", -1), "", "", 0, 0, log) + if err != nil { + errList = multierror.Append(errList, err) + info.ErrorMsg = err.Error() + info.PRs = []*PullRequest{} + return + } + }(info) wg.Add(1) go func(info *GitRepoInfo) { @@ -87,26 +84,23 @@ func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ( }(info) - //bt 代表branch and tag - if param == "" || param == "bt" { - wg.Add(1) - go func(info *GitRepoInfo) { - defer func() { - wg.Done() - }() - projectName := info.Repo - if info.Source == CodeHostCodeHub { - projectName = info.RepoID - } - info.Tags, err = CodeHostListTags(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "", 0, 0, log) - if err != nil { - errList = multierror.Append(errList, err) - info.ErrorMsg = err.Error() - info.Tags = []*Tag{} - return - } - }(info) - } + wg.Add(1) + go func(info *GitRepoInfo) { + defer func() { + wg.Done() + }() + projectName := info.Repo + if info.Source == CodeHostCodeHub { + projectName = info.RepoID + } + info.Tags, err = CodeHostListTags(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "", 0, 0, log) + if err != nil { + errList = multierror.Append(errList, err) + info.ErrorMsg = err.Error() + info.Tags = []*Tag{} + return + } + }(info) } wg.Wait() -- Gitee From 8352464ec28e3541e39e795ab41d87d624a9b74c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Mar 2022 15:23:28 +0800 Subject: [PATCH 1801/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 32 ++------ .../aslan/core/common/service/fs/s3.go | 46 +++++------ .../aslan/core/common/service/helm.go | 4 +- .../core/common/service/template/chart.go | 3 +- pkg/tool/git/github/repositories.go | 2 +- pkg/util/file.go | 79 ------------------- 6 files changed, 29 insertions(+), 137 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 21b3b8ee..666cc404 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -42,7 +42,7 @@ func PreloadFiles(name, localBase, s3Base, source string, logger *zap.SugaredLog switch source { case setting.SourceFromGerrit: - if err = GerritDownloadAndExtractFilesFromS3(name, localBase, s3Base, logger); err != nil { + if err = DownloadAndCopyFilesFromGerrit(name, localBase, logger); err != nil { logger.Errorf("Failed to download files from s3, err: %s", err) return err } @@ -82,30 +82,14 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string return err } -// CopyAndUploadFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. -func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { - var wg wait.Group - var err error - wg.Start(func() { - copyErr := copy.Copy(currentChartPath, localBase) - if copyErr != nil { - logger.Errorf("failed to copy chart info, err %s", err) - err = copyErr - } - }) - - wg.Start(func() { - fileTree := os.DirFS(currentChartPath) - err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) - if err2 != nil { - logger.Errorf("Failed to upload files to s3, err: %s", err2) - err = err2 - } - }) - - wg.Wait() +// CopyFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. +func CopyFiles(localBase, currentChartPath string, logger *zap.SugaredLogger) error { + copyErr := copy.Copy(currentChartPath, localBase) + if copyErr != nil { + logger.Errorf("failed to copy chart info, err %s", copyErr) + } - return err + return copyErr } func saveInMemoryFilesToDisk(fileTree fs.FS, root string, logger *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index c2949d14..af2342ac 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -20,14 +20,19 @@ import ( "fmt" "io/fs" "os" + "path" "path/filepath" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" - "github.com/koderover/zadig/pkg/util" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -166,40 +171,25 @@ func DeleteArchivedFileFromS3(names []string, s3Base string, logger *zap.Sugared return client.DeleteObjects(s3.Bucket, s3PathList) } -func GerritDownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.SugaredLogger) error { - tmpDir, err := os.MkdirTemp("", "") - if err != nil { - logger.Errorf("Failed to create temp dir, err: %s", err) +func DownloadAndCopyFilesFromGerrit(name, localBase string, logger *zap.SugaredLogger) error { + chartTemplate, err := mongodb.NewChartColl().Get(name) + base := path.Join(config.S3StoragePath(), chartTemplate.Repo) + if err := os.RemoveAll(base); err != nil { + logger.Errorf("Failed to remove dir, err:%s", err) } - //defer os.RemoveAll(tmpDir) - s3, err := s3service.FindDefaultS3() + detail, err := systemconfig.New().GetCodeHost(chartTemplate.CodeHostID) if err != nil { - logger.Errorf("Failed to find default s3, err: %s", err) + log.Errorf("Failed to GetCodehostDetail, err:%s", err) return err } - tarball := fmt.Sprintf("%s.tar.gz", name) - localPath := filepath.Join(tmpDir, tarball) - s3Path := filepath.Join(s3.Subfolder, s3Base, tarball) - - forcedPathStyle := true - if s3.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - - client, err := s3tool.NewClient(s3.Endpoint, s3.Ak, s3.Sk, s3.Insecure, forcedPathStyle) + err = command.RunGitCmds(detail, "default", chartTemplate.Repo, chartTemplate.Branch, "origin") if err != nil { - logger.Errorf("Failed to create s3 client, err: %s", err) + log.Errorf("Failed to runGitCmds, err:%s", err) return err } - logger.Infof("s3Path: %s", s3Path) - if err = client.Download(s3.Bucket, s3Path, localPath); err != nil { - logger.Errorf("Failed to download file from s3, err: %s", err) - return err - } - logger.Infof("localPath: %s", localPath) - logger.Infof("localBase: %s", localBase) - if err = util.UnTar(localPath, localBase); err != nil { - logger.Errorf("Failed to extract tarball %s, err: %+v", localPath, err) + + if err := CopyFiles(localBase, path.Join(base, chartTemplate.Path), logger); err != nil { + log.Errorf("Failed to save or upload files for service %s, error: %s", name, err) return err } return nil diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index eb59628d..ff349654 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -99,10 +99,8 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file func CopyAndUploadService(projectName, serviceName, currentChartPath string, copies []string) error { localBase := config.LocalServicePath(projectName, serviceName) - s3Base := config.ObjectStorageServicePath(projectName, serviceName) - names := append([]string{serviceName}, copies...) - return fsservice.CopyAndUploadFiles(names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) + return fsservice.CopyFiles(path.Join(localBase, serviceName), currentChartPath, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 49362173..74547cee 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -451,9 +451,8 @@ func processChartFromGerrit(name string, args *fs.DownloadFromSourceArgs, logger wg.Start(func() { logger.Debug("Start to save and upload chart") localBase := configbase.LocalChartTemplatePath(name) - s3Base := configbase.ObjectStorageChartTemplatePath(name) - err1 := fs.CopyAndUploadFiles([]string{name}, path.Join(localBase, path.Base(args.Path)), s3Base, currentChartPath, logger) + err1 := fs.CopyFiles(path.Join(localBase, path.Base(args.Path)), currentChartPath, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) err = err1 diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index ea12dbac..5bba3419 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -317,7 +317,7 @@ func (c *Client) getTreeContents(ctx context.Context, owner, repo, branch, base, if err != nil { return err } - return afero.WriteFile(fs, fsutil.ShortenFileBase(base, path), []byte(content), 0666) + return afero.WriteFile(fs, fsutil.ShortenFileBase(base, path), []byte(content), 0644) } for _, fileOrDir := range directoryContent { diff --git a/pkg/util/file.go b/pkg/util/file.go index 76ea797f..30144213 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -17,13 +17,8 @@ limitations under the License. package util import ( - "archive/tar" - "compress/gzip" - "fmt" - "io" "io/ioutil" "os" - "path/filepath" ) func GenerateTmpFile() (string, error) { @@ -65,77 +60,3 @@ func ReadFile(filename string) ([]byte, error) { } return contentByte, nil } - -func UnTar(src, dst string) (err error) { - // 打开准备解压的 tar 包 - fr, err := os.Open(src) - if err != nil { - return - } - defer fr.Close() - - // 将打开的文件先解压 - gr, err := gzip.NewReader(fr) - if err != nil { - return - } - defer gr.Close() - - // 通过 gr 创建 tar.Reader - tr := tar.NewReader(gr) - - // 现在已经获得了 tar.Reader 结构了,只需要循环里面的数据写入文件就可以了 - for { - hdr, err := tr.Next() - - switch { - case err == io.EOF: - return nil - case err != nil: - return err - case hdr == nil: - continue - } - - // 处理下保存路径,将要保存的目录加上 header 中的 Name - // 这个变量保存的有可能是目录,有可能是文件,所以就叫 FileDir 了…… - dstFileDir := filepath.Join(dst, hdr.Name) - - // 根据 header 的 Typeflag 字段,判断文件的类型 - switch hdr.Typeflag { - case tar.TypeDir: // 如果是目录时候,创建目录 - // 判断下目录是否存在,不存在就创建 - if b := ExistDir(dstFileDir); !b { - // 使用 MkdirAll 不使用 Mkdir ,就类似 Linux 终端下的 mkdir -p, - // 可以递归创建每一级目录 - if err := os.MkdirAll(dstFileDir, 0775); err != nil { - return err - } - } - case tar.TypeReg: // 如果是文件就写入到磁盘 - // 创建一个可以读写的文件,权限就使用 header 中记录的权限 - // 因为操作系统的 FileMode 是 int32 类型的,hdr 中的是 int64,所以转换下 - file, err := os.OpenFile(dstFileDir, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) - if err != nil { - return err - } - n, err := io.Copy(file, tr) - if err != nil { - return err - } - // 将解压结果输出显示 - fmt.Printf("成功解压: %s , 共处理了 %d 个字符\n", dstFileDir, n) - - // 不要忘记关闭打开的文件,因为它是在 for 循环中,不能使用 defer - // 如果想使用 defer 就放在一个单独的函数中 - file.Close() - } - } - return nil -} - -// 判断目录是否存在 -func ExistDir(dirname string) bool { - fi, err := os.Stat(dirname) - return (err == nil || os.IsExist(err)) && fi.IsDir() -} -- Gitee From 62bafb26e13cc61ee249ebcab12c364ad31d8364 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Mar 2022 15:54:18 +0800 Subject: [PATCH 1802/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/fs/s3.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index af2342ac..b69d6cc8 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -138,7 +138,7 @@ func DownloadAndExtractFilesFromS3(name, localBase, s3Base string, logger *zap.S } if err = fsutil.Untar(localPath, localBase); err != nil { - logger.Errorf("Failed to extract tarball %s, err: %+v", localPath, err) + logger.Errorf("Failed to extract tarball %s, err: %s", localPath, err) return err } return nil @@ -188,8 +188,8 @@ func DownloadAndCopyFilesFromGerrit(name, localBase string, logger *zap.SugaredL return err } - if err := CopyFiles(localBase, path.Join(base, chartTemplate.Path), logger); err != nil { - log.Errorf("Failed to save or upload files for service %s, error: %s", name, err) + if err := CopyFiles(path.Join(localBase, path.Base(chartTemplate.Path)), path.Join(base, chartTemplate.Path), logger); err != nil { + log.Errorf("Failed to copy files for helm chart template %s, error: %s", name, err) return err } return nil -- Gitee From 5d61975e36ae4ad874c0b37302587ff869f31ebb Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Mar 2022 16:06:52 +0800 Subject: [PATCH 1803/2647] remove param Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/handler/codehost.go | 3 +-- pkg/microservice/aslan/core/code/service/repo.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index b472adc7..8aa0e229 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -225,8 +225,7 @@ func ListRepoInfos(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid repo args") return } - param := c.Query("param") - ctx.Resp, ctx.Err = service.ListRepoInfos(args.Infos, param, ctx.Logger) + ctx.Resp, ctx.Err = service.ListRepoInfos(args.Infos, ctx.Logger) } type BranchesRequest struct { diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 2a3a09ae..e2f02bef 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -45,7 +45,7 @@ type GitRepoInfo struct { } // ListRepoInfos ... -func ListRepoInfos(infos []*GitRepoInfo, param string, log *zap.SugaredLogger) ([]*GitRepoInfo, error) { +func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo, error) { var err error var wg sync.WaitGroup var errList *multierror.Error -- Gitee From aa58deafd641455fc8e9659456c292e1168f69ee Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 11 Mar 2022 16:35:25 +0800 Subject: [PATCH 1804/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/helm.go | 4 ++-- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index ff349654..45fb5407 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -97,7 +97,7 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) } -func CopyAndUploadService(projectName, serviceName, currentChartPath string, copies []string) error { +func CopyFiles(projectName, serviceName, currentChartPath string, copies []string) error { localBase := config.LocalServicePath(projectName, serviceName) return fsservice.CopyFiles(path.Join(localBase, serviceName), currentChartPath, log.SugaredLogger()) @@ -138,7 +138,7 @@ func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { return err } // save files to disk and upload them to s3 - if err := CopyAndUploadService(svc.ProductName, svc.ServiceName, svc.GerritPath, nil); err != nil { + if err := CopyFiles(svc.ProductName, svc.ServiceName, svc.GerritPath, nil); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", svc.ServiceName, svc.ProductName, err) return err } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d8d437a2..9a30498e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -665,7 +665,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr }() // copy to latest dir and upload to s3 - if err = commonservice.CopyAndUploadService(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}); err != nil { + if err = commonservice.CopyFiles(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) finalErr = e.ErrCreateTemplate.AddErr(err) return -- Gitee From 6ce6ff6e7cf4599ebd71d399765d723447ad3b34 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 11 Mar 2022 17:50:23 +0800 Subject: [PATCH 1805/2647] try to solve the problem of unstructured Signed-off-by: zhangyifei --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 850b3ed9..22f6cda8 100644 --- a/go.mod +++ b/go.mod @@ -85,7 +85,7 @@ require ( k8s.io/client-go v0.22.4 k8s.io/kubectl v0.22.4 k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a - sigs.k8s.io/controller-runtime v0.10.0 + sigs.k8s.io/controller-runtime v0.10.1 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 92c5c3f2..61dce814 100644 --- a/go.sum +++ b/go.sum @@ -2345,6 +2345,7 @@ k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGw k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.21.0/go.mod h1:qvtjz6X0USWXbgmbfXR+Agik4RZ3jv2Bgr5QnZzdPYw= k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= +k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= k8s.io/component-base v0.22.4 h1:7qwLJnua2ppGNZrRGDQ0vhsFebI39VGbZ4zdR5ArViI= k8s.io/component-base v0.22.4/go.mod h1:MrSaQy4a3tFVViff8TZL6JHYSewNCLshZCwHYM58v5A= k8s.io/component-helpers v0.21.0/go.mod h1:tezqefP7lxfvJyR+0a+6QtVrkZ/wIkyMLK4WcQ3Cj8U= @@ -2381,7 +2382,6 @@ k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= @@ -2404,8 +2404,8 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= -sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= +sigs.k8s.io/controller-runtime v0.10.1 h1:+eLHgY/VrJWnfg6iXUqhCUqNXgPH1NZeP9drNAAgWlg= +sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= sigs.k8s.io/kustomize/api v0.8.5/go.mod h1:M377apnKT5ZHJS++6H4rQoCHmWtt6qTpp3mbe7p6OLY= sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8= sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g= -- Gitee From b571a963ec92edd34ba55697d523528c6accf19d Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 11 Mar 2022 19:19:24 +0800 Subject: [PATCH 1806/2647] delete twice not err Signed-off-by: mouuii --- .../core/collaboration/service/collaboration_instance.go | 7 +++++-- pkg/microservice/aslan/core/common/service/product.go | 2 +- pkg/microservice/aslan/core/common/service/workflow.go | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index f3e9ec80..387d7139 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -5,6 +5,7 @@ import ( "reflect" "time" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" @@ -861,13 +862,15 @@ func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projec deleteResp := getCollaborationDelete(updateResp) for _, product := range deleteResp.Products { err := commonservice.DeleteProduct(username, product, projectName, requestID, log) - if err != nil { + if err != nil && err != mongo.ErrNoDocuments { + log.Infof("%v", err != mongo.ErrNoDocuments) return err } } for _, workflow := range deleteResp.Workflows { err := commonservice.DeleteWorkflow(workflow, requestID, false, log) - if err != nil { + log.Infof("%v", err != mongo.ErrNoDocuments) + if err != nil && err != mongo.ErrNoDocuments { return err } } diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 897c0bf7..6c0b43f3 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -50,7 +50,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) if err != nil { log.Errorf("find product error: %v", err) - return e.ErrDeleteEnv.AddDesc("not found") + return err } // delete informer's cache diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index ad5459da..60fd9ded 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -59,7 +59,7 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, workflow, err := mongodb.NewWorkflowColl().Find(workflowName) if err != nil { log.Errorf("Workflow.Find error: %v", err) - return e.ErrDeleteWorkflow.AddDesc(err.Error()) + return err } workflowCMMap, err := collaboration.GetWorkflowCMMap([]string{workflow.ProductTmplName}, log) if err != nil { -- Gitee From b0c1db89b18eee77affe5ea15f9abb341d2615a4 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 11 Mar 2022 19:20:58 +0800 Subject: [PATCH 1807/2647] fix gitlab webhook update service not work bug Signed-off-by: allenshen --- .../core/workflow/service/webhook/codehub.go | 3 +- .../core/workflow/service/webhook/github.go | 2 +- .../core/workflow/service/webhook/gitlab.go | 62 ++++++------------- .../core/workflow/service/webhook/utils.go | 38 ++++++++++-- pkg/shared/client/systemconfig/codehost.go | 1 - 5 files changed, 54 insertions(+), 52 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go b/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go index 76251be8..516729b1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/codehub.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" "net/http" - "strings" "time" "github.com/hashicorp/go-multierror" @@ -107,7 +106,7 @@ func updateServiceTemplateByCodehubPushEvent(event *codehub.PushEvent, log *zap. } for _, fileName := range fileNames { - if strings.Contains(fileName, path) { + if subElem(path, fileName) { affected = true break } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 71923bb8..4042b17b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -568,7 +568,7 @@ func updateServiceTemplateByGithubPush(pushEvent *github.PushEvent, log *zap.Sug // 判断PushEvent的Diffs中是否包含该服务模板的src_path affected := false for _, changeFile := range changeFiles { - if strings.Contains(changeFile, path) { + if subElem(path, changeFile) { affected = true break } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 5dad2966..9da84cff 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -17,11 +17,9 @@ limitations under the License. package webhook import ( - "encoding/json" "errors" "fmt" "net/http" - "strings" "sync" "time" @@ -62,7 +60,6 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log } baseURI := config.SystemAddress() - var eventPush *EventPush var pushEvent *gitlab.PushEvent var mergeEvent *gitlab.MergeEvent var tagEvent *gitlab.TagEvent @@ -82,30 +79,25 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log } } - //go collie.CallGitlabWebHook(forwardedProto, forwardedHost, payload, string(eventType), log) - switch event := event.(type) { case *gitlab.PushEvent: - eventPush = &EventPush{ - Before: event.Before, - After: event.After, - Ref: event.Ref, - CheckoutSha: event.CheckoutSHA, - ProjectID: event.ProjectID, - Body: string(payload), - } pushEvent = event + changeFiles := make([]string, 0) + for _, commit := range pushEvent.Commits { + changeFiles = append(changeFiles, commit.Added...) + changeFiles = append(changeFiles, commit.Removed...) + changeFiles = append(changeFiles, commit.Modified...) + } + pathWithNamespace := pushEvent.Project.PathWithNamespace + // trigger service template to re-sync from remote repo + if err = updateServiceTemplateByPushEvent(changeFiles, pathWithNamespace, log); err != nil { + errorList = multierror.Append(errorList, err) + } case *gitlab.MergeEvent: mergeEvent = event case *gitlab.TagEvent: tagEvent = event } - //触发更新服务模板webhook - if eventPush != nil { - if err = updateServiceTemplateByPushEvent(eventPush, log); err != nil { - errorList = multierror.Append(errorList, err) - } - } //触发工作流webhook和测试管理webhook var wg sync.WaitGroup @@ -265,32 +257,9 @@ type RepositoryInfo struct { VisibilityLevel int `json:"visibility_level"` } -func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) error { +func updateServiceTemplateByPushEvent(diffs []string, pathWithNamespace string, log *zap.SugaredLogger) error { log.Infof("EVENT: GITLAB WEBHOOK UPDATING SERVICE TEMPLATE") - gitlabEvent := &GitlabEvent{} - - err := json.Unmarshal([]byte(event.Body), gitlabEvent) - if err != nil { - log.Errorf("Get Project ID failed, error: %v", err) - return err - } - address, err := GetAddress(gitlabEvent.Project.WebURL) - if err != nil { - log.Errorf("GetGitlabAddress failed, error: %v", err) - return err - } - - client, err := getGitlabClientByAddress(address) - if err != nil { - return err - } - - diffs, err := client.Compare(event.ProjectID, event.Before, event.After) - if err != nil { - log.Errorf("Failed to get push event diffs, error: %v", err) - return err - } serviceTmpls, err := GetGitlabServiceTemplates() if err != nil { log.Errorf("Failed to get gitlab service templates, error: %v", err) @@ -300,6 +269,11 @@ func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) errs := &multierror.Error{} for _, service := range serviceTmpls { + // TODO need optimize, use repo namespace + if service.RepoOwner+"/"+service.RepoName != pathWithNamespace { + continue + } + path, err := getServiceSrcPath(service) if err != nil { errs = multierror.Append(errs, err) @@ -307,7 +281,7 @@ func updateServiceTemplateByPushEvent(event *EventPush, log *zap.SugaredLogger) // 判断PushEvent的Diffs中是否包含该服务模板的src_path affected := false for _, diff := range diffs { - if strings.Contains(diff.OldPath, path) || strings.Contains(diff.NewPath, path) { + if subElem(path, diff) { affected = true break } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index e7a952fd..d1ab6736 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -20,7 +20,9 @@ import ( "context" "errors" "fmt" + "os" "path" + "path/filepath" "strings" "sync" @@ -192,12 +194,12 @@ func syncLatestCommit(service *commonmodels.Service) error { return fmt.Errorf("url不能是空的") } - address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(service.SrcPath) + _, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(service.SrcPath) if err != nil { return fmt.Errorf("url 必须包含 owner/repo/tree/branch/path,具体请参考 Placeholder 提示") } - client, err := getGitlabClientByAddress(address) + client, err := getGitlabClientByCodehostId(service.CodehostID) if err != nil { return err } @@ -265,6 +267,21 @@ func getCodehubClientByAddress(address string) (*codehub.Client, error) { return client, nil } +func getGitlabClientByCodehostId(codehostId int) (*gitlabtool.Client, error) { + codehost, err := systemconfig.New().GetCodeHost(codehostId) + if err != nil { + log.Error(err) + return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") + } + client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) + if err != nil { + log.Error(err) + return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) + } + + return client, nil +} + func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { opt := &systemconfig.Option{ Address: address, @@ -350,12 +367,12 @@ func syncContentFromGitlab(userName string, args *commonmodels.Service) error { return nil } - address, owner, repo, branch, path, pathType, err := GetOwnerRepoBranchPath(args.SrcPath) + _, owner, repo, branch, path, pathType, err := GetOwnerRepoBranchPath(args.SrcPath) if err != nil { return fmt.Errorf("url format failed") } - client, err := getGitlabClientByAddress(address) + client, err := getGitlabClientByCodehostId(args.CodehostID) if err != nil { return err } @@ -705,3 +722,16 @@ func getServiceSrcPath(service *commonmodels.Service) (string, error) { _, _, _, _, p, _, err := GetOwnerRepoBranchPath(service.SrcPath) return p, err } + +// check if sub path is a part of parent path +func subElem(parent, sub string) bool { + up := ".." + string(os.PathSeparator) + rel, err := filepath.Rel(parent, sub) + if err != nil { + return false + } + if !strings.HasPrefix(rel, up) && rel != ".." { + return true + } + return false +} diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index d863718d..3bf6030d 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -49,7 +49,6 @@ type Option struct { CodeHostType string Address string Namespace string - CodeHostID int } func GetCodeHostInfo(opt *Option) (*CodeHost, error) { -- Gitee From 975d94de34865f57a6b8fc8cb8b6e1f751906f3f Mon Sep 17 00:00:00 2001 From: mouuii Date: Sat, 12 Mar 2022 11:15:03 +0800 Subject: [PATCH 1808/2647] replace insert idempotent Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1100.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1100.go b/pkg/cli/upgradeassistant/cmd/migrate/1100.go index 6ffa935b..443a2d3f 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1100.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1100.go @@ -22,6 +22,7 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" internalmodels "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" @@ -179,13 +180,13 @@ func changePolicyCollectionName() error { return err } - var ois []interface{} - for _, obj := range res { - ois = append(ois, obj) - } - if _, err = newPolicyMetaColl().InsertMany(ctx, ois); err != nil { - log.Errorf("Failed to InsertMany policyMetas, err: %s", err) - return err + for _, v := range res { + query := bson.M{"resource": v.Resource} + opts := options.Replace().SetUpsert(true) + _, err := newPolicyMetaColl().ReplaceOne(context.TODO(), query, v, opts) + if err != nil { + log.Errorf("relace one err:%v ,resource:%v", err, v.Resource) + } } //delete collection return newPolicyColl().Drop(ctx) -- Gitee From 73b2535861f05d37e1f624d0009a34ebf803abad Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 14 Mar 2022 10:28:38 +0800 Subject: [PATCH 1809/2647] supports the deployment of partially defined services in the environment Signed-off-by: panxunying --- .../core/environment/handler/environment.go | 32 +++++++- .../aslan/core/environment/handler/router.go | 1 + .../core/environment/service/environment.go | 79 +++++++++++++++---- pkg/util/array.go | 26 ++++++ 4 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 pkg/util/array.go diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index c08cdda2..4f354683 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -41,8 +41,14 @@ import ( ) type UpdateEnvs struct { - EnvNames []string `json:"env_names"` - UpdateType string `json:"update_type"` + EnvNames []string `json:"env_names"` + ServiceNames []string `json:"service_names"` + UpdateType string `json:"update_type"` + Vars []*template.RenderKV `json:"vars,omitempty"` +} + +type DeleteProductServicesRequest struct { + ServiceNames []string `json:"service_names"` } type ChartInfoArgs struct { @@ -114,7 +120,7 @@ func UpdateMultiProducts(c *gin.Context) { } force, _ := strconv.ParseBool(c.Query("force")) - ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, c.Query("projectName"), ctx.RequestID, force, ctx.Logger) + ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, args.ServiceNames, c.Query("projectName"), ctx.RequestID, force, args.Vars, ctx.Logger) } func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { @@ -269,7 +275,7 @@ func UpdateProduct(c *gin.Context) { force, _ := strconv.ParseBool(c.Query("force")) // update product asynchronously - ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, force, args.Vars, ctx.Logger) + ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, []string{}, force, args.Vars, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product %s %s: %v", envName, projectName, ctx.Err) } @@ -490,6 +496,24 @@ func DeleteProduct(c *gin.Context) { ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) } +func DeleteProductServices(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(DeleteProductServicesRequest) + data, err := c.GetRawData() + if err != nil { + log.Errorf("DeleteProductServices c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("DeleteProductServices json.Unmarshal err : %v", err) + } + projectName := c.Query("projectName") + envName := c.Param("name") + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境的服务", envName, "", ctx.Logger) + ctx.Err = service.DeleteProductServices(envName, projectName, args.ServiceNames, ctx.Logger) +} + func ListGroups(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index df6f33c8..1dca57ca 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -106,6 +106,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/helmChartVersions", GetHelmChartVersions) environments.GET("/:name/productInfo", GetProductInfo) environments.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProduct) + environments.PUT("/:name/services", gin2.UpdateOperationLogStatus, DeleteProductServices) environments.GET("/:name/groups", ListGroups) environments.GET("/:name/workloads", ListWorkloadsInEnv) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0f16e2a1..98814b77 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -296,7 +296,7 @@ func AutoCreateProduct(productName, envType, requestID string, log *zap.SugaredL var mutexAutoUpdate sync.RWMutex -func AutoUpdateProduct(envNames []string, productName, requestID string, force bool, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func AutoUpdateProduct(envNames, serviceNames []string, productName, requestID string, force bool, kvs []*templatemodels.RenderKV, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexAutoUpdate.Lock() defer func() { mutexAutoUpdate.Unlock() @@ -354,12 +354,7 @@ func AutoUpdateProduct(envNames []string, productName, requestID string, force b } for envName := range productMap { - productInfo, err := GetProduct(setting.SystemUser, envName, productName, log) - if err != nil { - log.Errorf("AutoUpdateProduct GetProduct err:%v", err) - return envStatuses, err - } - err = UpdateProductV2(envName, productName, setting.SystemUser, requestID, true, productInfo.Vars, log) + err = UpdateProductV2(envName, productName, setting.SystemUser, requestID, serviceNames, false, kvs, log) if err != nil { log.Errorf("AutoUpdateProduct UpdateProductV2 err:%v", err) return envStatuses, err @@ -428,7 +423,7 @@ func getServicesWithMaxRevision(projectName string) ([]*commonmodels.Service, er return allServices, nil } -func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *commonmodels.RenderSet, log *zap.SugaredLogger) (err error) { +func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels.Product, renderSet *commonmodels.RenderSet, log *zap.SugaredLogger) (err error) { // 设置产品新的renderinfo updateProd.Render = &commonmodels.RenderInfo{ Name: renderSet.Name, @@ -512,7 +507,7 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com // 首先更新一次数据库,将产品模板的最新编排更新到数据库 // 只更新编排,不更新服务revision等信息 - updatedServices := getUpdatedProductServices(updateProd, serviceRevisionMap, existedProd) + updatedServices := getUpdatedProductServices(serviceNames, updateProd, serviceRevisionMap, existedProd) updateProd.Status = setting.ProductStatusUpdating updateProd.Services = updatedServices @@ -601,7 +596,28 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com err = e.ErrUpdateEnv.AddDesc(err.Error()) return } - err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, groupServices) + var updateGroup []*commonmodels.ProductService + newServiceMap := make(map[string]*commonmodels.ProductService) + for _, service := range groupServices { + newServiceMap[service.ServiceName] = service + } + oldServiceMap := make(map[string]*commonmodels.ProductService) + for _, serviceGroup := range existedProd.Services { + for _, service := range serviceGroup { + oldServiceMap[service.ServiceName] = service + if newService, ok := newServiceMap[service.ServiceName]; ok { + updateGroup = append(updateGroup, newService) + continue + } + updateGroup = append(updateGroup, service) + } + } + for _, newService := range groupServices { + if _, ok := oldServiceMap[newService.ServiceName]; !ok { + updateGroup = append(updateGroup, newService) + } + } + err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, updateGroup) if err != nil { log.Errorf("Failed to update collection - service group %d. Error: %v", groupIndex, err) err = e.ErrUpdateEnv.AddDesc(err.Error()) @@ -637,7 +653,7 @@ func UpdateProductRegistry(envName, productName, registryID string, log *zap.Sug return nil } -func UpdateProductV2(envName, productName, user, requestID string, force bool, kvs []*templatemodels.RenderKV, log *zap.SugaredLogger) (err error) { +func UpdateProductV2(envName, productName, user, requestID string, serviceNames []string, force bool, kvs []*templatemodels.RenderKV, log *zap.SugaredLogger) (err error) { // 根据产品名称和产品创建者到数据库中查找已有产品记录 opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} exitedProd, err := commonrepo.NewProductColl().Find(opt) @@ -725,7 +741,7 @@ func UpdateProductV2(envName, productName, user, requestID string, force bool, k } go func() { - err := UpdateProduct(exitedProd, updateProd, renderSet, log) + err := UpdateProduct(serviceNames, exitedProd, updateProd, renderSet, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -1689,6 +1705,41 @@ func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ( return helmVersions, nil } +func DeleteProductServices(envName, productName string, serviceNames []string, log *zap.SugaredLogger) (err error) { + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) + if err != nil { + log.Errorf("find product error: %v", err) + return err + } + + for serviceGroupIndex, serviceGroup := range productInfo.Services { + var group []*commonmodels.ProductService + for _, service := range serviceGroup { + if !util.InArray(service.ServiceName, serviceNames) { + group = append(group, service) + } + } + err := commonrepo.NewProductColl().UpdateGroup(envName, productName, serviceGroupIndex, group) + if err != nil { + log.Errorf("update product error: %v", err) + return err + } + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + for _, name := range serviceNames { + selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: name}.AsSelector() + err = commonservice.DeleteResourcesAsync(productInfo.Namespace, selector, kubeClient, log) + if err != nil { + //删除失败仅记录失败日志 + log.Errorf("delete resource of service %s error:%v", name, err) + } + } + return nil +} + func GetEstimatedRenderCharts(productName, envName, serviceNameListStr string, log *zap.SugaredLogger) ([]*commonservice.RenderChartArg, error) { var serviceNameList []string @@ -2594,7 +2645,7 @@ func getServiceRevisionMap(serviceRevisionList []*SvcRevision) map[string]*SvcRe return serviceRevisionMap } -func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevisionMap map[string]*SvcRevision, currentProduct *commonmodels.Product) [][]*commonmodels.ProductService { +func getUpdatedProductServices(serviceNames []string, updateProduct *commonmodels.Product, serviceRevisionMap map[string]*SvcRevision, currentProduct *commonmodels.Product) [][]*commonmodels.ProductService { currentServices := make(map[string]*commonmodels.ProductService) for _, group := range currentProduct.Services { for _, service := range group { @@ -2607,7 +2658,7 @@ func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevis updatedGroups := make([]*commonmodels.ProductService, 0) for _, service := range group { serviceRevision, ok := serviceRevisionMap[service.ServiceName+service.Type] - if !ok { + if !ok || (len(serviceNames) > 0 && !util.InArray(service.ServiceName, serviceNames)) { //找不到 service revision continue } diff --git a/pkg/util/array.go b/pkg/util/array.go new file mode 100644 index 00000000..18224706 --- /dev/null +++ b/pkg/util/array.go @@ -0,0 +1,26 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +func InArray(target string, str_array []string) bool { + for _, element := range str_array { + if target == element { + return true + } + } + return false +} -- Gitee From c68e98246373225c773aeca0c750510bffb76fc1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Mar 2022 11:31:00 +0800 Subject: [PATCH 1810/2647] support for kubernetes version 1.22 Signed-off-by: Min Min --- .../aslan/core/common/service/kube/service.go | 4 +- .../aslan/core/common/service/product.go | 125 ++++--- .../core/environment/service/configmap.go | 25 +- .../core/environment/service/environment.go | 8 +- .../service/environment_creator.go | 10 - pkg/microservice/aslan/core/service.go | 1 - .../aslan/core/setting/handler/router.go | 30 -- .../aslan/core/setting/handler/user.go | 33 -- .../aslan/core/setting/service/user.go | 343 ------------------ pkg/microservice/aslan/server/rest/router.go | 2 - pkg/shared/kube/client/version.go | 22 ++ pkg/tool/kube/informer/informer.go | 6 +- pkg/tool/kube/updater/clusterrole.go | 31 +- pkg/tool/kube/updater/clusterrolebinding.go | 27 -- pkg/tool/kube/updater/configmap.go | 25 +- pkg/tool/kube/updater/cronjob.go | 17 +- pkg/tool/kube/updater/deployment.go | 15 +- pkg/tool/kube/updater/ingress.go | 36 +- pkg/tool/kube/updater/job.go | 16 +- pkg/tool/kube/updater/namespace.go | 33 +- pkg/tool/kube/updater/pod.go | 16 +- pkg/tool/kube/updater/pv.go | 19 +- pkg/tool/kube/updater/pvc.go | 19 +- pkg/tool/kube/updater/replicaset.go | 19 +- pkg/tool/kube/updater/role.go | 43 ++- pkg/tool/kube/updater/rolebinding.go | 28 +- pkg/tool/kube/updater/secret.go | 16 +- pkg/tool/kube/updater/service.go | 36 +- pkg/tool/kube/updater/serviceaccount.go | 17 +- pkg/tool/kube/updater/statefulset.go | 15 +- 30 files changed, 404 insertions(+), 633 deletions(-) delete mode 100644 pkg/microservice/aslan/core/setting/handler/router.go delete mode 100644 pkg/microservice/aslan/core/setting/handler/user.go delete mode 100644 pkg/microservice/aslan/core/setting/service/user.go create mode 100644 pkg/shared/kube/client/version.go delete mode 100644 pkg/tool/kube/updater/clusterrolebinding.go diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 59f7c0fc..381235d1 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -304,7 +304,7 @@ metadata: --- -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: koderover-agent-admin-binding @@ -537,7 +537,7 @@ metadata: --- -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: koderover-agent-admin-binding diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 897c0bf7..edcba23c 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -17,16 +17,11 @@ limitations under the License. package service import ( + "context" "fmt" "time" "github.com/hashicorp/go-multierror" - helmclient "github.com/mittwald/go-helm-client" - "go.uber.org/zap" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/sets" - "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -39,6 +34,11 @@ import ( helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/updater" + helmclient "github.com/mittwald/go-helm-client" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" ) const ( @@ -63,10 +63,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if cmSets, ok := envCMMap[collaboration.BuildEnvCMMapKey(productName, envName)]; ok { return fmt.Errorf("this is a base environment, collaborations:%v is related", cmSets.List()) } - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) - if err != nil { - return e.ErrDeleteEnv.AddErr(err) - } restConfig, err := kube.GetRESTConfig(productInfo.ClusterID) if err != nil { @@ -126,7 +122,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su //删除namespace s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() - if err1 := updater.DeleteMatchingNamespace(productInfo.Namespace, s, kubeClient); err1 != nil { + if err1 := DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) return } @@ -211,13 +207,8 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } }() - err = DeleteResourcesAsync(productInfo.Namespace, labels.Set{setting.ProductLabel: productName}.AsSelector(), kubeClient, log) - if err != nil { - err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) - return - } - - err = DeleteClusterResourceAsync(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), kubeClient, log) + // Delete Cluster level resources + err = DeleteClusterResource(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), productInfo.ClusterID, log) if err != nil { err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) @@ -225,19 +216,11 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() - if err1 := updater.DeleteMatchingNamespace(productInfo.Namespace, s, kubeClient); err1 != nil { + if err1 := DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) return } - //// 等待10分钟 - //err = WaitPodsToTerminate(productInfo.Namespace, selector, config.ServiceStartTimeout(), kubeClient, log) - //if err != nil { - // log.Errorf("wait port to terminate error: %v", err) - // err = e.ErrDeleteEnv.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) - // return - //} - err = commonrepo.NewProductColl().Delete(envName, productName) if err != nil { log.Errorf("Product.Delete error: %v", err) @@ -257,96 +240,104 @@ func filterWorkloadsByEnv(exist []commonmodels.Workload, env string) []commonmod return result } -func DeleteClusterResourceAsync(selector labels.Selector, kubeClient client.Client, log *zap.SugaredLogger) error { - log.Infof("[%s] delete cluster kube resources", selector) +func DeleteClusterResource(selector labels.Selector, clusterID string, log *zap.SugaredLogger) error { + log.Infof("Deleting cluster resources with selector: [%s]", selector) + + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", clusterID, err) + return err + } errors := new(multierror.Error) - if err := updater.DeleteClusterRoles(selector, kubeClient); err != nil { - log.Error(err) - errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteClusterRole error: %v", err)) + if err := updater.DeleteClusterRoles(selector, clientset); err != nil { + log.Errorf("failed to delete clusterRoles for clusterID: %s, the error is: %s", clusterID, err) + errors = multierror.Append(errors, err) } - if err := updater.DeleteClusterRoleBindings(selector, kubeClient); err != nil { - log.Error(err) - errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteClusterRoleBinding error: %v", err)) + if err := updater.DeletePersistentVolumes(selector, clientset); err != nil { + log.Errorf("failed to delete PV for clusterID: %s, the error is: %s", clusterID, err) + errors = multierror.Append(errors, err) } return errors.ErrorOrNil() } -// 根据namespace和selector删除所有资源 -func DeleteResourcesAsync(namespace string, selector labels.Selector, kubeClient client.Client, log *zap.SugaredLogger) error { - log.Infof("[%s][%s] delete kube resources", namespace, selector) +// DeleteNamespacedResource deletes the namespaced resources by labels. +func DeleteNamespacedResource(namespace string, selector labels.Selector, clusterID string, log *zap.SugaredLogger) error { + log.Infof("Deleting namespaced resources with selector: [%s] in namespace [%s]", selector, namespace) + + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", clusterID, err) + return err + } errors := new(multierror.Error) - if err := updater.DeleteDeployments(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteDeployments(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteDeployments error: %v", err)) } // could have replicas created by deployment - if err := updater.DeleteReplicaSets(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteReplicaSets(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteReplicaSets error: %v", err)) } - if err := updater.DeleteStatefulSets(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteStatefulSets(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteStatefulSets error: %v", err)) } - if err := updater.DeleteJobs(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteJobs(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteJobs error: %v", err)) } - if err := updater.DeleteServices(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteServices(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteServices error: %v", err)) } - if err := updater.DeleteIngresses(namespace, selector, kubeClient); err != nil { + // TODO: Questionable delete logic, needs further attention + if err := updater.DeleteIngresses(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteIngresses error: %v", err)) } - if err := updater.DeleteSecrets(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteSecrets(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteSecrets error: %v", err)) } - if err := updater.DeleteConfigMaps(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteConfigMaps(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteConfigMaps error: %v", err)) } - if err := updater.DeletePersistentVolumeClaims(namespace, selector, kubeClient); err != nil { + if err := updater.DeletePersistentVolumeClaims(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeletePersistentVolumeClaim error: %v", err)) } - if err := updater.DeletePersistentVolumes(namespace, selector, kubeClient); err != nil { - log.Error(err) - errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeletePersistentVolume error: %v", err)) - } - - if err := updater.DeleteServiceAccounts(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteServiceAccounts(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteServiceAccounts error: %v", err)) } - if err := updater.DeleteCronJobs(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteCronJobs(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteCronJobs error: %v", err)) } - if err := updater.DeleteRoleBindings(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteRoleBindings(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteRoleBinding error: %v", err)) } - if err := updater.DeleteRoles(namespace, selector, kubeClient); err != nil { + if err := updater.DeleteRoles(namespace, selector, clientset); err != nil { log.Error(err) errors = multierror.Append(errors, fmt.Errorf("kubeCli.DeleteRole error: %v", err)) } @@ -354,6 +345,28 @@ func DeleteResourcesAsync(namespace string, selector labels.Selector, kubeClient return errors.ErrorOrNil() } +func DeleteNamespaceIfMatch(namespace string, selector labels.Selector, clusterID string, log *zap.SugaredLogger) error { + log.Infof("Checking if namespace [%s] has matching labels: [%s]", namespace, selector.String()) + + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", clusterID, err) + return err + } + + ns, err := clientset.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) + if err != nil { + log.Errorf("failed to list namespace to delete matching namespace in cluster ID: %s, the error is: %s", clusterID, err) + return err + } + + if selector.Matches(labels.Set(ns.Labels)) { + return updater.DeleteNamespace(namespace, clientset) + } + + return nil +} + func GetProductEnvNamespace(envName, productName, namespace string) string { if namespace != "" { return namespace diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 1982dea0..94a7c56e 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -26,6 +26,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -160,6 +161,12 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID return e.ErrUpdateConfigMap.AddErr(err) } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrUpdateConfigMap.AddErr(err) + } + namespace := product.Namespace cfg, found, err := getter.GetConfigMap(namespace, args.ConfigName, kubeClient) if err != nil { @@ -205,7 +212,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID as[setting.LastUpdateTimeAnnotation] = util.FormatTime(time.Now()) cfg.SetAnnotations(as) - if err := updater.UpdateConfigMap(cfg, kubeClient); err != nil { + if err := updater.UpdateConfigMap(namespace, cfg, clientset); err != nil { log.Error(err) return e.ErrUpdateConfigMap.AddDesc(err.Error()) } @@ -216,7 +223,7 @@ func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID ServiceName: args.ServiceName, } - if err := restartPod(restartArgs, namespace, kubeClient, log); err != nil { + if err := restartPod(restartArgs, namespace, clientset, log); err != nil { log.Error(err) return e.ErrRestartService.AddDesc(err.Error()) } @@ -237,6 +244,12 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us return e.ErrUpdateConfigMap.AddErr(err) } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrUpdateConfigMap.AddErr(err) + } + namespace := product.Namespace srcCfg, found, err := getter.GetConfigMap(namespace, args.SrcConfigName, kubeClient) if err != nil { @@ -267,7 +280,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us if updateTime, ok := srcCfg.Labels[setting.UpdateTime]; ok { destinSrc.Labels[setting.UpdateTime] = updateTime } - if err := updater.UpdateConfigMap(destinSrc, kubeClient); err != nil { + if err := updater.UpdateConfigMap(namespace, destinSrc, clientset); err != nil { log.Error(err) return e.ErrUpdateConfigMap.AddDesc(err.Error()) } @@ -278,7 +291,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us ServiceName: args.ServiceName, } - if err := restartPod(restartArgs, namespace, kubeClient, log); err != nil { + if err := restartPod(restartArgs, namespace, clientset, log); err != nil { log.Error(err) return e.ErrRestartService.AddDesc(err.Error()) } @@ -346,9 +359,9 @@ func cleanArchiveConfigMap(namespace string, ls map[string]string, kubeClient cl } } -func restartPod(args *SvcOptArgs, ns string, kubeClient client.Client, log *zap.SugaredLogger) error { +func restartPod(args *SvcOptArgs, ns string, clientset *kubernetes.Clientset, log *zap.SugaredLogger) error { selector := labels.Set{setting.ProductLabel: args.ProductName, setting.ServiceLabel: args.ServiceName}.AsSelector() log.Infof("deleting pod from %s where %s", ns, selector) - return updater.DeletePods(ns, selector, kubeClient) + return updater.DeletePods(ns, selector, clientset) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0f16e2a1..4ca91fde 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -491,14 +491,14 @@ func UpdateProduct(existedProd, updateProd *commonmodels.Product, renderSet *com log.Infof("[%s][P:%s][S:%s] start to delete service", envName, productName, serviceRev.ServiceName) //根据namespace: EnvName, selector: productName + serviceName来删除属于该服务的所有资源 selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceRev.ServiceName}.AsSelector() - err = commonservice.DeleteResourcesAsync(namespace, selector, kubeClient, log) + err = commonservice.DeleteNamespacedResource(namespace, selector, existedProd.ClusterID, log) if err != nil { //删除失败仅记录失败日志 log.Errorf("delete resource of service %s error:%v", serviceRev.ServiceName, err) } clusterSelector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceRev.ServiceName, setting.EnvNameLabel: envName}.AsSelector() - err = commonservice.DeleteClusterResourceAsync(clusterSelector, kubeClient, log) + err = commonservice.DeleteClusterResource(clusterSelector, existedProd.ClusterID, log) if err != nil { //删除失败仅记录失败日志 log.Errorf("delete cluster resource of service %s error:%v", serviceRev.ServiceName, err) @@ -1883,12 +1883,9 @@ func upsertService(isUpdate bool, env *commonmodels.Product, switch u.GetKind() { case setting.Ingress: ls := kube.MergeLabels(labels, u.GetLabels()) - as := applySystemIngressTimeouts(u.GetAnnotations()) - as = applySystemIngressClass(as) u.SetNamespace(namespace) u.SetLabels(ls) - u.SetAnnotations(as) err = updater.CreateOrPatchUnstructured(u, kubeClient) if err != nil { @@ -1922,7 +1919,6 @@ func upsertService(isUpdate bool, env *commonmodels.Product, needSelectorLabel := false u.SetNamespace(namespace) - u.SetAPIVersion(setting.APIVersionAppsV1) u.SetLabels(kube.MergeLabels(labels, u.GetLabels())) switch u.GetKind() { diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index b1134b3e..64701c1c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -316,16 +316,6 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model if args.Namespace == "" { args.Namespace = namespace } - // todo DELETE THIS PART WHEN EXISTING NAMESAPCE IS SUPPORTED - //_, found, err := getter.GetNamespace(args.Namespace, kubeClient) - //if err != nil { - // log.Errorf("GetNamespace error: %v", err) - // return e.ErrCreateEnv.AddDesc(err.Error()) - //} - // - //if found { - // return e.ErrCreateEnv.AddDesc(fmt.Sprintf("%s[%s]%s", "namespace", args.Namespace, "已经存在,请换个环境名称尝试!")) - //} //创建角色环境之间的关联关系 //todo 创建环境暂时不指定角色 diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 5b7d9670..e69a8ccb 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -23,7 +23,6 @@ import ( "time" "github.com/hashicorp/go-multierror" - commonconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" modeMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" diff --git a/pkg/microservice/aslan/core/setting/handler/router.go b/pkg/microservice/aslan/core/setting/handler/router.go deleted file mode 100644 index 1148a608..00000000 --- a/pkg/microservice/aslan/core/setting/handler/router.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "github.com/gin-gonic/gin" -) - -type Router struct{} - -func (*Router) Inject(router *gin.RouterGroup) { - user := router.Group("user") - { - user.GET("/kube/config", GetUserKubeConfig) - } -} diff --git a/pkg/microservice/aslan/core/setting/handler/user.go b/pkg/microservice/aslan/core/setting/handler/user.go deleted file mode 100644 index b78904bb..00000000 --- a/pkg/microservice/aslan/core/setting/handler/user.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "github.com/gin-gonic/gin" - - settingservice "github.com/koderover/zadig/pkg/microservice/aslan/core/setting/service" - internalhandler "github.com/koderover/zadig/pkg/shared/handler" -) - -func GetUserKubeConfig(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - projectsEnvCanEdit := c.QueryArray("projectsEnvCanEdit") - projectsEnvCanView := c.QueryArray("projectsEnvCanView") - ctx.Resp, ctx.Err = settingservice.GetUserKubeConfig(ctx.UserID, projectsEnvCanEdit, projectsEnvCanView, ctx.Logger) -} diff --git a/pkg/microservice/aslan/core/setting/service/user.go b/pkg/microservice/aslan/core/setting/service/user.go deleted file mode 100644 index bfc0f010..00000000 --- a/pkg/microservice/aslan/core/setting/service/user.go +++ /dev/null @@ -1,343 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "bytes" - "encoding/base64" - "errors" - "html/template" - "time" - - "go.uber.org/zap" - corev1 "k8s.io/api/core/v1" - rbacv1beta1 "k8s.io/api/rbac/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" - "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/kube/wrapper" - krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" - "github.com/koderover/zadig/pkg/tool/kube/getter" - "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/tool/log" -) - -type kubeCfgTmplArgs struct { - CaCrtBase64 string - CaKeyBase64 string - KubeServerAddr string - Namespace string - User string - ClientCrtBase64 string - ClientKeyBase64 string -} - -func GetUserKubeConfig(userID string, projectsEnvCanEdit []string, projectsEnvCanView []string, log *zap.SugaredLogger) (string, error) { - saNamespace := config.Namespace() - if err := ensureClusterRole(log); err != nil { - log.Errorf("ensurClusterRole err: %s", err) - return "", err - } - if err := ensureServiceAccountAndRolebinding(saNamespace, projectsEnvCanEdit, projectsEnvCanView, userID, log); err != nil { - log.Errorf("ensureServiceAccountAndRolebinding err: %s", err) - return "", err - } - crt, token, err := getCrtAndToken(saNamespace, userID) - if err != nil { - log.Errorf("getCrtAndToken err: %s", err) - return "", err - } - args := &kubeCfgTmplArgs{ - KubeServerAddr: config.KubeServerAddr(), - CaCrtBase64: crt, - User: userID, - CaKeyBase64: token, - } - - return renderCfgTmpl(args) -} - -func getCrtAndToken(namespace, userID string) (string, string, error) { - kubeClient := krkubeclient.Client() - var sa *corev1.ServiceAccount - for i := 0; i < 5; i++ { - tmpsa, found, err := getter.GetServiceAccount(namespace, config.ServiceAccountNameForUser(userID), kubeClient) - if err != nil { - log.Errorf("GetServiceAccount err:%s name:%s namespace:%s", err, config.ServiceAccountNameForUser(userID), namespace) - return "", "", err - } else if found || len(sa.Secrets) > 0 { - sa = tmpsa - break - } - log.Warnf("waiting for the secret created") - time.Sleep(time.Second) - - } - if sa == nil { - log.Errorf("can not get user:%s service account in namespace:%s", userID, namespace) - return "", "", errors.New("serviceAccount not found") - } - - secret, found, err := getter.GetSecret(namespace, sa.Secrets[0].Name, kubeClient) - if err != nil { - log.Errorf("GetSecret:%s err: %s", sa.Secrets[0].Name, err) - return "", "", err - } else if !found { - log.Error("secret:%s not found in namespace:%s", sa.Secrets[0].Name, namespace) - return "", "", errors.New("secret not found") - } - return base64.StdEncoding.EncodeToString(secret.Data["ca.crt"]), string(secret.Data["token"]), nil -} - -func filterProductWithoutExternalCluster(products []*commonmodels.Product) []*commonmodels.Product { - var ret []*commonmodels.Product - kubeClient := krkubeclient.Client() - for _, product := range products { - // 过滤跨集群 - if product.ClusterID != "" { - continue - } - // 过滤外部环境托管 - sources := sets.NewString(setting.SourceFromZadig, setting.HelmDeployType) - // Compatible with the environment source created in the onboarding process is empty - if product.Source != "" && !sources.Has(product.Source) { - continue - } - // 过滤状态为Terminating的namespace - namespace, found, err := getter.GetNamespace(product.Namespace, kubeClient) - if !found || err != nil || wrapper.Namespace(namespace).Terminating() { - continue - } - - ret = append(ret, product) - } - - return ret -} - -var ( - clusterRoleEdit = &rbacv1beta1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: config.RoleBindingNameEdit, - }, - Rules: []rbacv1beta1.PolicyRule{{ - Verbs: []string{"*"}, - APIGroups: []string{"*"}, - Resources: []string{"pods", "deployments", "configmaps", "crobjobs", "daemonsets", "ingresses", "jobs", "secrets", "services", "statefulsets"}, - }}} - clusterRoleView = &rbacv1beta1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: config.RoleBindingNameView, - }, - Rules: []rbacv1beta1.PolicyRule{{ - Verbs: []string{"get", "watch", "list"}, - APIGroups: []string{"*"}, - Resources: []string{"*"}, - }}} -) - -func ensureClusterRole(log *zap.SugaredLogger) error { - if _, found, err := getter.GetClusterRole(config.RoleBindingNameEdit, krkubeclient.Client()); err != nil { - log.Errorf("GetClusterRole:%s err: %s", config.RoleBindingNameEdit, err) - return err - } else if !found { - if err := updater.CreateClusterRole(clusterRoleEdit, krkubeclient.Client()); err != nil { - log.Errorf("CreateClusterRole:%s err: %s", clusterRoleEdit, err) - return err - } - } - if _, found, err := getter.GetClusterRole(config.RoleBindingNameView, krkubeclient.Client()); err != nil { - log.Errorf("GetClusterRole:%s err: %s", config.RoleBindingNameView, err) - return err - } else if !found { - if err := updater.CreateClusterRole(clusterRoleView, krkubeclient.Client()); err != nil { - log.Errorf("CreateClusterRole:%s err: %s", clusterRoleView, err) - return err - } - } - return nil -} - -func ensureServiceAccountAndRolebinding(namespace string, projectsEnvCanEdit []string, projectsEnvCanView []string, userID string, log *zap.SugaredLogger) error { - serviceAccountName := config.ServiceAccountNameForUser(userID) - serviceAccount := &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: serviceAccountName, - Namespace: namespace, - }, - } - _, found, err := getter.GetServiceAccount(namespace, serviceAccountName, krkubeclient.Client()) - if err != nil { - log.Errorf("GetServiceAccount name:%s err:%s", err) - return err - } - if !found { - if err := updater.CreateServiceAccount(serviceAccount, krkubeclient.Client()); err != nil { - log.Errorf("CreateServiceAccount name:%s err:%s", serviceAccountName, err) - return err - } - } - - // caller provide a list of projects for which the user has permission to edit env or read env - // while []string{*} means all projects - if len(projectsEnvCanEdit) == 1 && projectsEnvCanEdit[0] == "*" { - res, err := templaterepo.NewProductColl().ListNames(nil) - if err != nil { - log.Errorf("ListProjectBriefs err:%s", err) - return err - } - projectsEnvCanEdit = res - } - - if len(projectsEnvCanView) == 1 && projectsEnvCanView[0] == "*" { - res, err := templaterepo.NewProductColl().ListNames(nil) - if err != nil { - log.Errorf("ListProjectBriefs err:%s", err) - return err - } - projectsEnvCanView = res - } - - canEditProducts := []*commonmodels.Product{} - if len(projectsEnvCanEdit) != 0 { - canEditProducts, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanEdit}) - if err != nil { - log.Warnf("[%s] Collections.Product.List error: %s", projectsEnvCanEdit, err) - } - } - canEditProducts = filterProductWithoutExternalCluster(canEditProducts) - for _, vv := range canEditProducts { - if err := CreateRoleBinding(vv.Namespace, namespace, serviceAccountName, config.RoleBindingNameEdit); err != nil { - log.Warnf("CreateRoleBinding err: %s", err) - } - } - canViewProducts := []*commonmodels.Product{} - if len(projectsEnvCanView) != 0 { - canViewProducts, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{InProjects: projectsEnvCanView}) - if err != nil { - log.Warnf("[%s] Collections.Product.List error: %s", projectsEnvCanView, err) - } - } - canViewProducts = filterProductWithoutExternalCluster(canViewProducts) - for _, vv := range canViewProducts { - if err := CreateRoleBinding(vv.Namespace, namespace, serviceAccountName, config.RoleBindingNameView); err != nil { - log.Warnf("CreateRoleBinding err: %s", err) - } - } - - return nil -} - -func renderCfgTmpl(args *kubeCfgTmplArgs) (string, error) { - buf := new(bytes.Buffer) - t := template.Must(template.New("cfg").Parse(kubeCfgTmpl)) - err := t.Execute(buf, args) - if err != nil { - return "", err - } - return buf.String(), nil -} - -const kubeCfgTmpl = ` -apiVersion: v1 -clusters: -- cluster: - certificate-authority-data: {{.CaCrtBase64}} - server: {{.KubeServerAddr}} - name: koderover -contexts: -- context: - cluster: koderover - user: {{.User}} - name: koderover -current-context: koderover -kind: Config -preferences: {} -users: -- name: {{.User}} - user: - token: {{.CaKeyBase64}} -` - -func CreateRoleBinding(rbNamespace, saNamspace, serviceAccountName, roleBindName string) error { - rolebinding, found, err := getter.GetRoleBinding(rbNamespace, roleBindName, krkubeclient.Client()) - subs := []rbacv1beta1.Subject{{ - Kind: "ServiceAccount", - Name: serviceAccountName, - Namespace: saNamspace, - }} - if err != nil { - log.Errorf("GetRoleBinding name:%s err: %s", roleBindName, err) - return err - } - if !found { - if err := updater.CreateRoleBinding(&rbacv1beta1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleBindName, - Namespace: rbNamespace, - }, - Subjects: subs, - RoleRef: rbacv1beta1.RoleRef{ - // APIGroup is the group for the resource being referenced - APIGroup: "rbac.authorization.k8s.io", - // Kind is the type of resource being referenced - Kind: "ClusterRole", - // Name is the name of resource being referenced - Name: roleBindName, - }, - }, krkubeclient.Client()); err != nil { - log.Errorf("create rolebinding:%s err: %s", roleBindName, err) - return err - } - } else { - isExist := false - for _, v := range rolebinding.Subjects { - if v.Name == serviceAccountName { - isExist = true - } - } - if isExist { - return nil - } else { - subs = append(subs, rolebinding.Subjects...) - } - } - if err := updater.UpdateRoleBinding(&rbacv1beta1.RoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: roleBindName, - Namespace: rbNamespace, - }, - Subjects: subs, - RoleRef: rbacv1beta1.RoleRef{ - // APIGroup is the group for the resource being referenced - APIGroup: "rbac.authorization.k8s.io", - // Kind is the type of resource being referenced - Kind: "ClusterRole", - // Name is the name of resource being referenced - Name: roleBindName, - }, - }, krkubeclient.Client()); err != nil { - log.Errorf("create rolebinding:%s err: %s", roleBindName, err) - return err - } - return nil -} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index 59ea4ddf..ad2f2aec 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -33,7 +33,6 @@ import ( multiclusterhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/handler" projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" servicehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/service/handler" - settinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/setting/handler" stathandler "github.com/koderover/zadig/pkg/microservice/aslan/core/stat/handler" systemhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/system/handler" templatehandler "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/handler" @@ -77,7 +76,6 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/code": new(codehosthandler.Router), "/api/system": new(systemhandler.Router), "/api/service": new(servicehandler.Router), - "/api/setting": new(settinghandler.Router), "/api/environment": new(environmenthandler.Router), "/api/cron": new(cronhandler.Router), "/api/workflow": new(workflowhandler.Router), diff --git a/pkg/shared/kube/client/version.go b/pkg/shared/kube/client/version.go new file mode 100644 index 00000000..89fcfbe7 --- /dev/null +++ b/pkg/shared/kube/client/version.go @@ -0,0 +1,22 @@ +package client + +import ( + "k8s.io/apimachinery/pkg/util/version" + k8sversion "k8s.io/apimachinery/pkg/version" +) + +var KubernetesVersion122 *version.Version + +func init() { + // as of zadig v1.11.0. Only kubernetes version 1.17+ is supported + // There is only 1 major api version change from v1.17+ + // More versions of kubernetes should be added if there are more API changes + // in future kubernetes version + v122, _ := version.ParseGeneric("v1.22.0") + KubernetesVersion122 = v122 +} + +func VersionLessThan122(ver *k8sversion.Info) bool { + currVersion, _ := version.ParseGeneric(ver.String()) + return currVersion.LessThan(KubernetesVersion122) +} diff --git a/pkg/tool/kube/informer/informer.go b/pkg/tool/kube/informer/informer.go index e72e5864..58c9169d 100644 --- a/pkg/tool/kube/informer/informer.go +++ b/pkg/tool/kube/informer/informer.go @@ -21,7 +21,7 @@ import ( "sync" "time" - "k8s.io/apimachinery/pkg/util/version" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -60,10 +60,8 @@ func NewInformer(clusterID, namespace string, cls *kubernetes.Clientset) (inform if err != nil { return nil, err } - currVersion, _ := version.ParseGeneric(versionInfo.String()) - v122, _ := version.ParseGeneric("v1.22.0") // if less than v1.22.0, then we look for the extensions/v1beta1 ingress - if currVersion.LessThan(v122) { + if kubeclient.VersionLessThan122(versionInfo) { informerFactory.Extensions().V1beta1().Ingresses().Lister() } else { // otherwise above resource is deprecated, we watch for the k8s.networking.io/v1 ingress diff --git a/pkg/tool/kube/updater/clusterrole.go b/pkg/tool/kube/updater/clusterrole.go index e4d6ef29..bb6f6852 100644 --- a/pkg/tool/kube/updater/clusterrole.go +++ b/pkg/tool/kube/updater/clusterrole.go @@ -17,15 +17,30 @@ limitations under the License. package updater import ( - rbacv1beta1 "k8s.io/api/rbac/v1beta1" + "context" + + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeleteClusterRoles(selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions("", selector, &rbacv1beta1.ClusterRole{}, cl) -} - -func CreateClusterRole(role *rbacv1beta1.ClusterRole, cl client.Client) error { - return createObject(role, cl) +func DeleteClusterRoles(selector labels.Selector, clientset *kubernetes.Clientset) error { + version, err := clientset.Discovery().ServerVersion() + if err != nil { + return err + } + deletePolicy := metav1.DeletePropagationForeground + if kubeclient.VersionLessThan122(version) { + return clientset.RbacV1beta1().ClusterRoles().DeleteCollection(context.TODO(), metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + } + return clientset.RbacV1().ClusterRoles().DeleteCollection(context.TODO(), metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, metav1.ListOptions{ + LabelSelector: selector.String(), + }) } diff --git a/pkg/tool/kube/updater/clusterrolebinding.go b/pkg/tool/kube/updater/clusterrolebinding.go deleted file mode 100644 index 7e3f2faf..00000000 --- a/pkg/tool/kube/updater/clusterrolebinding.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package updater - -import ( - rbacv1beta1 "k8s.io/api/rbac/v1beta1" - "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func DeleteClusterRoleBindings(selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions("", selector, &rbacv1beta1.ClusterRoleBinding{}, cl) -} diff --git a/pkg/tool/kube/updater/configmap.go b/pkg/tool/kube/updater/configmap.go index 6a79a8dd..fd9c61af 100644 --- a/pkg/tool/kube/updater/configmap.go +++ b/pkg/tool/kube/updater/configmap.go @@ -17,19 +17,36 @@ limitations under the License. package updater import ( + "context" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) -func DeleteConfigMaps(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &corev1.ConfigMap{}, cl) +func DeleteConfigMaps(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().ConfigMaps(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } -func UpdateConfigMap(cm *corev1.ConfigMap, cl client.Client) error { - return updateObject(cm, cl) +func UpdateConfigMap(namespace string, cm *corev1.ConfigMap, clientset *kubernetes.Clientset) error { + _, err := clientset.CoreV1().ConfigMaps(namespace).Update( + context.TODO(), + cm, + metav1.UpdateOptions{}, + ) + return err } func DeleteConfigMap(ns, name string, cl client.Client) error { diff --git a/pkg/tool/kube/updater/cronjob.go b/pkg/tool/kube/updater/cronjob.go index 2db5313e..9d3f2e65 100644 --- a/pkg/tool/kube/updater/cronjob.go +++ b/pkg/tool/kube/updater/cronjob.go @@ -17,13 +17,26 @@ limitations under the License. package updater import ( + "context" + batchv1beta1 "k8s.io/api/batch/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) -func DeleteCronJobs(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &batchv1beta1.CronJob{}, cl) +func DeleteCronJobs(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.BatchV1().CronJobs(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func CreateOrPatchCronJob(cj *batchv1beta1.CronJob, cl client.Client) error { diff --git a/pkg/tool/kube/updater/deployment.go b/pkg/tool/kube/updater/deployment.go index 220d3fe9..7964ae8c 100644 --- a/pkg/tool/kube/updater/deployment.go +++ b/pkg/tool/kube/updater/deployment.go @@ -18,6 +18,7 @@ package updater import ( "bytes" + "context" "fmt" "text/template" "time" @@ -25,6 +26,7 @@ import ( appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -65,8 +67,17 @@ func RestartDeployment(ns, name string, cl client.Client) error { return nil } -func DeleteDeployments(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &appsv1.Deployment{}, cl) +func DeleteDeployments(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.AppsV1().Deployments(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func UpdateDeploymentImage(ns, name, container, image string, cl client.Client) error { diff --git a/pkg/tool/kube/updater/ingress.go b/pkg/tool/kube/updater/ingress.go index 0df90bbd..c3f4dcc1 100644 --- a/pkg/tool/kube/updater/ingress.go +++ b/pkg/tool/kube/updater/ingress.go @@ -17,11 +17,39 @@ limitations under the License. package updater import ( - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + "context" + + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeleteIngresses(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &extensionsv1beta1.Ingress{}, cl) +func DeleteIngresses(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + version, err := clientset.Discovery().ServerVersion() + if err != nil { + return err + } + + deletePolicy := metav1.DeletePropagationForeground + + if kubeclient.VersionLessThan122(version) { + return clientset.ExtensionsV1beta1().Ingresses(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + } + + return clientset.NetworkingV1().Ingresses(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } diff --git a/pkg/tool/kube/updater/job.go b/pkg/tool/kube/updater/job.go index e3fc2542..4dfeee52 100644 --- a/pkg/tool/kube/updater/job.go +++ b/pkg/tool/kube/updater/job.go @@ -17,15 +17,27 @@ limitations under the License. package updater import ( + "context" + batchv1 "k8s.io/api/batch/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) -func DeleteJobs(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &batchv1.Job{}, cl) +func DeleteJobs(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.BatchV1().Jobs(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func CreateJob(job *batchv1.Job, cl client.Client) error { diff --git a/pkg/tool/kube/updater/namespace.go b/pkg/tool/kube/updater/namespace.go index d6c7a540..1d2f20d2 100644 --- a/pkg/tool/kube/updater/namespace.go +++ b/pkg/tool/kube/updater/namespace.go @@ -17,36 +17,19 @@ limitations under the License. package updater import ( + "context" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/koderover/zadig/pkg/tool/kube/getter" ) -func DeleteMatchingNamespace(ns string, selector labels.Selector, cl client.Client) error { - namespace, found, err := getter.GetNamespace(ns, cl) - if err != nil { - return err - } else if !found { - return nil - } - - if !selector.Matches(labels.Set(namespace.Labels)) { - return nil - } - - return DeleteNamespace(ns, cl) -} - -func DeleteNamespace(ns string, cl client.Client) error { - return deleteObjectWithDefaultOptions(&corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "", - Name: ns, - }, - }, cl) +func DeleteNamespace(name string, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().Namespaces().Delete(context.TODO(), name, metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }) } func CreateNamespace(ns *corev1.Namespace, cl client.Client) error { diff --git a/pkg/tool/kube/updater/pod.go b/pkg/tool/kube/updater/pod.go index 3e509b2b..a0422e51 100644 --- a/pkg/tool/kube/updater/pod.go +++ b/pkg/tool/kube/updater/pod.go @@ -17,14 +17,26 @@ limitations under the License. package updater import ( + "context" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) -func DeletePods(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &corev1.Pod{}, cl) +func DeletePods(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().Pods(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func DeletePod(ns, name string, cl client.Client) error { diff --git a/pkg/tool/kube/updater/pv.go b/pkg/tool/kube/updater/pv.go index ef3fd4e0..56a98b16 100644 --- a/pkg/tool/kube/updater/pv.go +++ b/pkg/tool/kube/updater/pv.go @@ -17,11 +17,22 @@ limitations under the License. package updater import ( - corev1 "k8s.io/api/core/v1" + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeletePersistentVolumes(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &corev1.PersistentVolume{}, cl) +func DeletePersistentVolumes(selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().PersistentVolumes().DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } diff --git a/pkg/tool/kube/updater/pvc.go b/pkg/tool/kube/updater/pvc.go index 2117dace..e75b0015 100644 --- a/pkg/tool/kube/updater/pvc.go +++ b/pkg/tool/kube/updater/pvc.go @@ -17,11 +17,22 @@ limitations under the License. package updater import ( - corev1 "k8s.io/api/core/v1" + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeletePersistentVolumeClaims(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &corev1.PersistentVolumeClaim{}, cl) +func DeletePersistentVolumeClaims(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().PersistentVolumeClaims(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } diff --git a/pkg/tool/kube/updater/replicaset.go b/pkg/tool/kube/updater/replicaset.go index 52593105..412d4bd0 100644 --- a/pkg/tool/kube/updater/replicaset.go +++ b/pkg/tool/kube/updater/replicaset.go @@ -17,11 +17,22 @@ limitations under the License. package updater import ( - appsv1 "k8s.io/api/apps/v1" + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeleteReplicaSets(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &appsv1.ReplicaSet{}, cl) +func DeleteReplicaSets(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.AppsV1().ReplicaSets(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } diff --git a/pkg/tool/kube/updater/role.go b/pkg/tool/kube/updater/role.go index c6ac3bb5..6c026d0e 100644 --- a/pkg/tool/kube/updater/role.go +++ b/pkg/tool/kube/updater/role.go @@ -17,19 +17,38 @@ limitations under the License. package updater import ( - rbacv1beta1 "k8s.io/api/rbac/v1beta1" + "context" + + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeleteRoles(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &rbacv1beta1.Role{}, cl) -} - -func CreateRole(role *rbacv1beta1.Role, cl client.Client) error { - return createObject(role, cl) -} - -func UpdateRole(role *rbacv1beta1.Role, cl client.Client) error { - return updateObject(role, cl) +func DeleteRoles(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + version, err := clientset.Discovery().ServerVersion() + if err != nil { + return err + } + deletePolicy := metav1.DeletePropagationForeground + if kubeclient.VersionLessThan122(version) { + return clientset.RbacV1beta1().Roles(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) + } + + return clientset.RbacV1().Roles(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } diff --git a/pkg/tool/kube/updater/rolebinding.go b/pkg/tool/kube/updater/rolebinding.go index 827bfb3f..ed5d1d24 100644 --- a/pkg/tool/kube/updater/rolebinding.go +++ b/pkg/tool/kube/updater/rolebinding.go @@ -17,20 +17,22 @@ limitations under the License. package updater import ( - rbacv1beta1 "k8s.io/api/rbac/v1beta1" + "context" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" + "k8s.io/client-go/kubernetes" ) -func DeleteRoleBindings(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &rbacv1beta1.RoleBinding{}, cl) -} - -func CreateRoleBinding(rb *rbacv1beta1.RoleBinding, cl client.Client) error { - return createObject(rb, cl) +func DeleteRoleBindings(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.RbacV1().RoleBindings(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } - -func UpdateRoleBinding(rb *rbacv1beta1.RoleBinding, cl client.Client) error { - return updateObject(rb, cl) -} - diff --git a/pkg/tool/kube/updater/secret.go b/pkg/tool/kube/updater/secret.go index 0d7edd7a..f6da831c 100644 --- a/pkg/tool/kube/updater/secret.go +++ b/pkg/tool/kube/updater/secret.go @@ -17,14 +17,26 @@ limitations under the License. package updater import ( + "context" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) -func DeleteSecrets(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &corev1.Secret{}, cl) +func DeleteSecrets(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().Secrets(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func UpdateOrCreateSecret(s *corev1.Secret, cl client.Client) error { diff --git a/pkg/tool/kube/updater/service.go b/pkg/tool/kube/updater/service.go index ad2192ac..270b74ae 100644 --- a/pkg/tool/kube/updater/service.go +++ b/pkg/tool/kube/updater/service.go @@ -17,39 +17,33 @@ limitations under the License. package updater import ( - corev1 "k8s.io/api/core/v1" + "context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/koderover/zadig/pkg/tool/kube/getter" + "k8s.io/client-go/kubernetes" ) -// service does not support deleteCollection -// see here for details: https://github.com/kubernetes/kubernetes/issues/68468#issuecomment-419981870 -func DeleteServices(ns string, selector labels.Selector, cl client.Client) error { - services, err := getter.ListServices(ns, selector, cl) +func DeleteServices(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + services, err := clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { return err } var lastErr error - for _, service := range services { - err := DeleteService(ns, service.Name, cl) + deletePolicy := metav1.DeletePropagationForeground + for _, svc := range services.Items { + err := clientset.CoreV1().Services(namespace).Delete( + context.TODO(), + svc.Name, + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + ) if err != nil { - lastErr = err } + lastErr = err } return lastErr } - -func DeleteService(ns, name string, cl client.Client) error { - deletePolicy := metav1.DeletePropagationForeground - return deleteObject(&corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: ns, - Name: name, - }, - }, cl, &client.DeleteOptions{PropagationPolicy: &deletePolicy}) -} diff --git a/pkg/tool/kube/updater/serviceaccount.go b/pkg/tool/kube/updater/serviceaccount.go index 020011ff..857c0233 100644 --- a/pkg/tool/kube/updater/serviceaccount.go +++ b/pkg/tool/kube/updater/serviceaccount.go @@ -17,13 +17,26 @@ limitations under the License. package updater import ( + "context" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) -func DeleteServiceAccounts(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &corev1.ServiceAccount{}, cl) +func DeleteServiceAccounts(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().ServiceAccounts(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func CreateServiceAccount(sa *corev1.ServiceAccount, cl client.Client) error { diff --git a/pkg/tool/kube/updater/statefulset.go b/pkg/tool/kube/updater/statefulset.go index 0f288df4..c093245f 100644 --- a/pkg/tool/kube/updater/statefulset.go +++ b/pkg/tool/kube/updater/statefulset.go @@ -18,10 +18,12 @@ package updater import ( "bytes" + "context" "fmt" "time" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -53,8 +55,17 @@ func RestartStatefulSet(ns, name string, cl client.Client) error { return nil } -func DeleteStatefulSets(ns string, selector labels.Selector, cl client.Client) error { - return deleteObjectsWithDefaultOptions(ns, selector, &appsv1.StatefulSet{}, cl) +func DeleteStatefulSets(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.AppsV1().StatefulSets(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } func UpdateStatefulSetImage(ns, name, container, image string, cl client.Client) error { -- Gitee From 5d23ff17bb20bb4d32ec977eeb6d2cc083823477 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 14:50:59 +0800 Subject: [PATCH 1811/2647] Supports the case where the image name and container name are different Signed-off-by: liu deyi --- .../core/common/repository/models/service.go | 5 +++-- .../aslan/core/service/service/service.go | 21 +++++++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 792f334d..4ceb814b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -130,8 +130,9 @@ type ImagePathSpec struct { // Container ... type Container struct { - Name string `bson:"name" json:"name"` - Image string `bson:"image" json:"image"` + Name string `bson:"name" json:"name"` + Image string `bson:"image" json:"image"` + ImageName string `bson:"image_name,omitempty" json:"image_name,omitempty"` ImagePath *ImagePathSpec `bson:"image_path,omitempty" json:"imagePath,omitempty"` } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 52e36866..988987b4 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -183,6 +183,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv for _, container := range args.Containers { serviceModule := new(ServiceModule) serviceModule.Container = container + serviceModule.ImageName = container.ImageName buildObj, _ := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ProductName: args.ProductName, ServiceName: args.ServiceName, Targets: []string{container.Name}}) if buildObj != nil { serviceModule.BuildName = buildObj.Name @@ -1313,8 +1314,9 @@ func getContainers(data string) ([]*commonmodels.Container, error) { } container := &commonmodels.Container{ - Name: nameStr, - Image: imageStr, + Name: nameStr, + Image: imageStr, + ImageName: getImageName(imageStr), } containers = append(containers, container) @@ -1352,8 +1354,9 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { } container := &commonmodels.Container{ - Name: nameStr, - Image: imageStr, + Name: nameStr, + Image: imageStr, + ImageName: getImageName(imageStr), } containers = append(containers, container) @@ -1362,6 +1365,16 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { return containers, nil } +func getImageName(image string) string { + imageNameStr := "" + imageArr := strings.Split(image, ":") + if len(imageArr) > 0 { + imageNameArr := strings.Split(imageArr[0], "/") + imageNameStr = imageNameArr[len(imageNameArr)-1] + } + return imageNameStr +} + func updateGerritWebhookByService(lastService, currentService *commonmodels.Service) error { var codehostDetail *systemconfig.CodeHost var err error -- Gitee From e1d4fbbdcc3fb7d418d95a11a4aa3a0ad7c8f475 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 14:53:13 +0800 Subject: [PATCH 1812/2647] codehost Signed-off-by: mouuii --- .../aslan/core/code/client/codehub/codehub.go | 46 ++++++++++++++++ .../aslan/core/code/client/gerrit/gerrit.go | 44 +++++++++++++++ .../aslan/core/code/client/github/github.go | 55 +++++++++++++++++++ .../aslan/core/code/client/gitlab/gitlab.go | 53 ++++++++++++++++++ .../aslan/core/code/client/interface.go | 52 ++++++++++++++++++ .../aslan/core/code/service/branch.go | 5 -- 6 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 pkg/microservice/aslan/core/code/client/codehub/codehub.go create mode 100644 pkg/microservice/aslan/core/code/client/gerrit/gerrit.go create mode 100644 pkg/microservice/aslan/core/code/client/github/github.go create mode 100644 pkg/microservice/aslan/core/code/client/gitlab/gitlab.go create mode 100644 pkg/microservice/aslan/core/code/client/interface.go diff --git a/pkg/microservice/aslan/core/code/client/codehub/codehub.go b/pkg/microservice/aslan/core/code/client/codehub/codehub.go new file mode 100644 index 00000000..9d8957f6 --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/codehub/codehub.go @@ -0,0 +1,46 @@ +package codehub + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/codehub" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +type Config struct{} + +type Client struct { + Client *codehub.CodeHubClient +} + +func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { + ch, err := systemconfig.New().GetCodeHost(id) + if err != nil { + return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + } + codehubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) + return &Client{Client: codehubClient}, nil +} + +func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { + bList, err := c.Client.BranchList(projectName) + if err != nil { + return nil, err + } + var res []*client.Branch + for _, o := range bList { + res = append(res, &client.Branch{ + Name: o.Name, + Protected: o.Protected, + Merged: o.Merged, + }) + } + return res, nil +} + +func (c *Client) ListTags() ([]*client.Tag, error) { + return nil, nil +} diff --git a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go new file mode 100644 index 00000000..da4499cf --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go @@ -0,0 +1,44 @@ +package gerrit + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/gerrit" +) + +type Config struct{} + +type Client struct { + Client *gerrit.Client +} + +func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { + ch, err := systemconfig.New().GetCodeHost(id) + if err != nil { + return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + } + gerritClient := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + return &Client{Client: gerritClient}, nil +} + +func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(projectName) + if err != nil { + return nil, err + } + var res []*client.Branch + for _, o := range bList { + res = append(res, &client.Branch{ + Name: o, + }) + } + return res, nil +} + +func (c *Client) ListTags() ([]*client.Tag, error) { + return nil, nil +} diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go new file mode 100644 index 00000000..64de46d4 --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -0,0 +1,55 @@ +package github + +import ( + "context" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/git/github" +) + +type Config struct { +} + +type Client struct { + Client *github.Client +} + +func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { + ch, err := systemconfig.New().GetCodeHost(id) + if err != nil { + return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + } + cfg := &github.Config{ + AccessToken: ch.AccessToken, + } + if ch.EnableProxy { + cfg.Proxy = config.ProxyHTTPSAddr() + } + return &Client{ + Client: github.NewClient(cfg), + }, nil +} + +func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(context.TODO(), namespace, projectName, nil) + if err != nil { + return nil, err + } + var res []*client.Branch + for _, o := range bList { + res = append(res, &client.Branch{ + Name: o.GetName(), + Protected: o.GetProtected(), + }) + } + return res, nil +} + +func (c *Client) ListTags() ([]*client.Tag, error) { + return nil, nil +} diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go new file mode 100644 index 00000000..cd2d57d1 --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -0,0 +1,53 @@ +package gitlab + +import ( + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/git/gitlab" +) + +type Config struct{} + +type Client struct { + Client *gitlab.Client +} + +func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { + ch, err := systemconfig.New().GetCodeHost(id) + if err != nil { + return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + } + client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + if err != nil { + return nil, err + } + return &Client{Client: client}, nil +} + +func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(namespace, projectName, key, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }) + if err != nil { + return nil, err + } + var res []*client.Branch + for _, b := range bList { + res = append(res, &client.Branch{ + Name: b.Name, + Protected: b.Protected, + Merged: b.Merged, + }) + } + return res, nil +} + +func (c *Client) ListTags() ([]*client.Tag, error) { + return nil, nil +} diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go new file mode 100644 index 00000000..49753637 --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -0,0 +1,52 @@ +package client + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/codehub" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gerrit" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/github" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" +) + +type CodeHostClient interface { + ListBranches(namespace, projectName, key string, page, perPage int) ([]*Branch, error) + ListTags() ([]*Tag, error) +} + +type Branch struct { + Name string `json:"name"` + Protected bool `json:"protected"` + Merged bool `json:"merged"` +} + +type Tag struct { + Name string `json:"name"` + ZipballURL string `json:"zipball_url"` + TarballURL string `json:"tarball_url"` + Message string `json:"message"` +} + +type ClientConfig interface { + Open(id int, logger *zap.SugaredLogger) (CodeHostClient, error) +} + +var ClientsConfig = map[string]func() ClientConfig{ + "gitlab": func() ClientConfig { return new(gitlab.Config) }, + "github": func() ClientConfig { return new(github.Config) }, + "gerrit": func() ClientConfig { return new(gerrit.Config) }, + "codehub": func() ClientConfig { return new(codehub.Config) }, +} + +func openClient(codehostType string, codehostID int) (CodeHostClient, error) { + var c CodeHostClient + + f, ok := ClientsConfig[codehostType] + if !ok { + return c, fmt.Errorf("unknow codehost type") + } + clientConfig := f() + return clientConfig.Open(codehostID), nil +} diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 27ecfe25..bf6b056c 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -23,7 +23,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" @@ -31,10 +30,6 @@ import ( ) func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*Branch, error) { - ch, err := systemconfig.New().GetCodeHost(codeHostID) - if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") - } if ch.Type == codeHostGitlab { client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) -- Gitee From 0d6ff63116633e95265a0fd9327edd840468e8a4 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 15:22:40 +0800 Subject: [PATCH 1813/2647] codehost Signed-off-by: mouuii --- .../aslan/core/code/client/interface.go | 33 ----------- .../aslan/core/code/client/open/open.go | 35 +++++++++++ .../aslan/core/code/service/branch.go | 59 ++++--------------- .../aslan/core/code/service/repo.go | 28 +++++---- 4 files changed, 62 insertions(+), 93 deletions(-) create mode 100644 pkg/microservice/aslan/core/code/client/open/open.go diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go index 49753637..aaf1d0bc 100644 --- a/pkg/microservice/aslan/core/code/client/interface.go +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -1,16 +1,5 @@ package client -import ( - "fmt" - - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/codehub" - "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gerrit" - "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/github" - "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" -) - type CodeHostClient interface { ListBranches(namespace, projectName, key string, page, perPage int) ([]*Branch, error) ListTags() ([]*Tag, error) @@ -28,25 +17,3 @@ type Tag struct { TarballURL string `json:"tarball_url"` Message string `json:"message"` } - -type ClientConfig interface { - Open(id int, logger *zap.SugaredLogger) (CodeHostClient, error) -} - -var ClientsConfig = map[string]func() ClientConfig{ - "gitlab": func() ClientConfig { return new(gitlab.Config) }, - "github": func() ClientConfig { return new(github.Config) }, - "gerrit": func() ClientConfig { return new(gerrit.Config) }, - "codehub": func() ClientConfig { return new(codehub.Config) }, -} - -func openClient(codehostType string, codehostID int) (CodeHostClient, error) { - var c CodeHostClient - - f, ok := ClientsConfig[codehostType] - if !ok { - return c, fmt.Errorf("unknow codehost type") - } - clientConfig := f() - return clientConfig.Open(codehostID), nil -} diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go new file mode 100644 index 00000000..ba2d4efb --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -0,0 +1,35 @@ +package open + +import ( + "fmt" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/codehub" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gerrit" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/github" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" +) + +type ClientConfig interface { + Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) +} + +var ClientsConfig = map[string]func() ClientConfig{ + "gitlab": func() ClientConfig { return new(gitlab.Config) }, + "github": func() ClientConfig { return new(github.Config) }, + "gerrit": func() ClientConfig { return new(gerrit.Config) }, + "codehub": func() ClientConfig { return new(codehub.Config) }, +} + +func OpenClient(codehostType string, codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { + var c client.CodeHostClient + + f, ok := ClientsConfig[codehostType] + if !ok { + return c, fmt.Errorf("unknow codehost type") + } + clientConfig := f() + return clientConfig.Open(codehostID, log) +} diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index bf6b056c..a6246618 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -17,57 +17,22 @@ limitations under the License. package service import ( - "context" - "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/tool/codehub" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*Branch, error) { - - if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - if err != nil { - log.Errorf("get gitlab client failed, err:%v", err) - return nil, e.ErrCodehostListBranches.AddDesc(err.Error()) - } - - brList, err := client.ListBranches(namespace, projectName, key, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, - NoPaginated: true, - }) - if err != nil { - return nil, err - } - return ToBranches(brList), nil - } else if ch.Type == gerrit.CodehostTypeGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - branches, err := cli.ListBranches(projectName) - if err != nil { - return nil, err - } - return ToBranches(branches), nil - } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) - branchList, err := codeHubClient.BranchList(projectName) - if err != nil { - return nil, err - } - return ToBranches(branchList), nil - } else { - // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - branches, err := gh.ListBranches(context.TODO(), namespace, projectName, nil) - if err != nil { - return nil, err - } - return ToBranches(branches), nil +func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.Branch, error) { + ch, err := systemconfig.New().GetCodeHost(codeHostID) + if err != nil { + return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + } + client, err := open.OpenClient(ch.Type, codeHostID, log) + if err != nil { + return nil, err } + return client.ListBranches(namespace, projectName, key, page, perPage) } diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index e2f02bef..a503cd14 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -23,6 +23,8 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" ) type RepoInfoList struct { @@ -30,18 +32,18 @@ type RepoInfoList struct { } type GitRepoInfo struct { - Owner string `json:"repo_owner"` - Repo string `json:"repo"` - CodehostID int `json:"codehost_id"` - Source string `json:"source"` - DefaultBranch string `json:"default_branch"` - ErrorMsg string `json:"error_msg"` // repo信息是否拉取成功 - Branches []*Branch `json:"branches"` - Tags []*Tag `json:"tags"` - PRs []*PullRequest `json:"prs"` - ProjectUUID string `json:"project_uuid,omitempty"` - RepoUUID string `json:"repo_uuid,omitempty"` - RepoID string `json:"repo_id,omitempty"` + Owner string `json:"repo_owner"` + Repo string `json:"repo"` + CodehostID int `json:"codehost_id"` + Source string `json:"source"` + DefaultBranch string `json:"default_branch"` + ErrorMsg string `json:"error_msg"` // repo信息是否拉取成功 + Branches []*client.Branch `json:"branches"` + Tags []*Tag `json:"tags"` + PRs []*PullRequest `json:"prs"` + ProjectUUID string `json:"project_uuid,omitempty"` + RepoUUID string `json:"repo_uuid,omitempty"` + RepoID string `json:"repo_id,omitempty"` } // ListRepoInfos ... @@ -78,7 +80,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() - info.Branches = []*Branch{} + info.Branches = []*client.Branch{} return } -- Gitee From 37b4def41b1c87c09fe36fb619ff65d7403a0eda Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 14 Mar 2022 15:38:44 +0800 Subject: [PATCH 1814/2647] fix comments Signed-off-by: allenshen --- .../aslan/core/workflow/service/webhook/utils.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index d1ab6736..c845528c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -271,7 +271,7 @@ func getGitlabClientByCodehostId(codehostId int) (*gitlabtool.Client, error) { codehost, err := systemconfig.New().GetCodeHost(codehostId) if err != nil { log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") + return nil, e.ErrCodehostListProjects.AddDesc(fmt.Sprintf("failed to get codehost:%d, err: %s", codehost, err)) } client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) if err != nil { @@ -724,10 +724,13 @@ func getServiceSrcPath(service *commonmodels.Service) (string, error) { } // check if sub path is a part of parent path +// eg: parent: k1/k2 sub: k1/k2/k3 return true +// parent k1/k2-2 sub: k1/k2/k3 return false func subElem(parent, sub string) bool { up := ".." + string(os.PathSeparator) rel, err := filepath.Rel(parent, sub) if err != nil { + log.Errorf("failed to check path is relative, parent: %s, sub: %s", parent, sub) return false } if !strings.HasPrefix(rel, up) && rel != ".." { -- Gitee From 2108f52987aa79dbc279a8eaf43905d4968523f9 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 16:10:21 +0800 Subject: [PATCH 1815/2647] add image_name field Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 1 + .../aslan/core/environment/service/product.go | 5 +++ .../service/workflow/workflow_task.go | 34 +++++++++++++++---- pkg/shared/kube/resource/workload.go | 5 +-- pkg/shared/kube/wrapper/deployment.go | 14 +++++++- 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index c868430e..5275895d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -340,6 +340,7 @@ type HookPayload struct { type TargetArgs struct { Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name" json:"image_name"` ServiceName string `bson:"service_name" json:"service_name"` ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` ProductName string `bson:"product_name" json:"product_name"` diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 9ae486f5..650ad3ce 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -144,10 +144,15 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode if serviceTmpl.Type == setting.K8SDeployType || serviceTmpl.Type == setting.HelmDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { + imageName := c.ImageName + if imageName == "" { + imageName = c.Name + } container := &commonmodels.Container{ Name: c.Name, Image: c.Image, ImagePath: c.ImagePath, + ImageName: imageName, } serviceResp.Containers = append(serviceResp.Containers, container) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 036e8bee..f9db6802 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -75,7 +75,7 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr return resp, e.ErrListBuildModule.AddDesc(err.Error()) } - targetMap := getProductTargetMap(product) + targetMap,_ := getProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) targets := make([]*commonmodels.TargetArgs, 0) for _, container := range projectTargets { @@ -125,8 +125,9 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr return resp, nil } -func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.DeployEnv { +func getProductTargetMap(prod *commonmodels.Product) (map[string][]commonmodels.DeployEnv, map[string]string) { resp := make(map[string][]commonmodels.DeployEnv) + imageNameM := make(map[string]string) if prod.Source == setting.SourceFromExternal { services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) @@ -160,9 +161,15 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} target := strings.Join([]string{service.ProductName, service.ServiceName, container.Name}, SplitSymbol) resp[target] = append(resp[target], deployEnv) + + imageName := container.ImageName + if imageName == "" { + imageName = container.Name + } + imageNameM[target] = imageName } } - return resp + return resp, imageNameM } for _, services := range prod.Services { for _, serviceObj := range services { @@ -173,6 +180,12 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) resp[target] = append(resp[target], deployEnv) + + imageName := container.ImageName + if imageName == "" { + imageName = container.Name + } + imageNameM[target] = imageName } case setting.PMDeployType: deployEnv := commonmodels.DeployEnv{Type: setting.PMDeployType, Env: serviceObj.ServiceName} @@ -184,11 +197,17 @@ func getProductTargetMap(prod *commonmodels.Product) map[string][]commonmodels.D deployEnv := commonmodels.DeployEnv{Type: setting.HelmDeployType, Env: env} target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) resp[target] = append(resp[target], deployEnv) + + imageName := container.ImageName + if imageName == "" { + imageName = container.Name + } + imageNameM[target] = imageName } } } } - return resp + return resp, imageNameM } func getHideServiceModules(workflow *commonmodels.Workflow) sets.String { @@ -345,7 +364,7 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) return resp, e.ErrListTestModule.AddDesc(err.Error()) } - targetMap := getProductTargetMap(product) + targetMap,imageNameM := getProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) hideServiceModules := getHideServiceModules(workflow) targets := make([]*commonmodels.TargetArgs, 0) @@ -367,8 +386,9 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) ServiceName: containerArr[1], ProductName: containerArr[0], Deploy: targetMap[container], - Build: &commonmodels.BuildArgs{}, - HasBuild: true, + ImageName: imageNameM[container], + Build: &commonmodels.BuildArgs{}, + HasBuild: true, } moBuild := findModuleByTargetAndVersion(allModules, container) if moBuild == nil { diff --git a/pkg/shared/kube/resource/workload.go b/pkg/shared/kube/resource/workload.go index fe8be9fc..fa713168 100644 --- a/pkg/shared/kube/resource/workload.go +++ b/pkg/shared/kube/resource/workload.go @@ -25,6 +25,7 @@ type Workload struct { } type ContainerImage struct { - Name string `json:"name"` - Image string `json:"image"` + Name string `json:"name"` + Image string `json:"image"` + ImageName string `json:"image_name"` } diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 6d0c5e79..5da26c11 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -17,6 +17,8 @@ limitations under the License. package wrapper import ( + "strings" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -57,7 +59,7 @@ func (w *deployment) WorkloadResource(pods []*corev1.Pod) *resource.Workload { } for _, c := range w.Spec.Template.Spec.Containers { - wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image}) + wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image, ImageName: getImageName(c.Image)}) } for _, p := range pods { @@ -92,3 +94,13 @@ func (w *deployment) GetContainers() []*resource.ContainerImage { } return containers } + +func getImageName(image string) string { + imageNameStr := "" + imageArr := strings.Split(image, ":") + if len(imageArr) > 0 { + imageNameArr := strings.Split(imageArr[0], "/") + imageNameStr = imageNameArr[len(imageNameArr)-1] + } + return imageNameStr +} -- Gitee From f676fcd90b7e3eed96dc26b12a3c318917fab097 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 16:14:20 +0800 Subject: [PATCH 1816/2647] codehost Signed-off-by: mouuii --- .../aslan/core/code/client/codehub/codehub.go | 42 ++++++++--- .../aslan/core/code/client/gerrit/gerrit.go | 41 ++++++++--- .../aslan/core/code/client/github/github.go | 67 +++++++++++++++--- .../aslan/core/code/client/gitlab/gitlab.go | 70 ++++++++++++++++--- .../aslan/core/code/client/interface.go | 18 ++++- .../aslan/core/code/client/open/open.go | 19 ++++- .../aslan/core/code/service/branch.go | 8 +-- .../aslan/core/code/service/merge_request.go | 53 ++------------ .../aslan/core/code/service/repo.go | 28 ++++---- .../aslan/core/code/service/tag.go | 55 ++------------- 10 files changed, 243 insertions(+), 158 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/codehub/codehub.go b/pkg/microservice/aslan/core/code/client/codehub/codehub.go index 9d8957f6..349091c4 100644 --- a/pkg/microservice/aslan/core/code/client/codehub/codehub.go +++ b/pkg/microservice/aslan/core/code/client/codehub/codehub.go @@ -5,23 +5,31 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" - e "github.com/koderover/zadig/pkg/tool/errors" ) -type Config struct{} +type Config struct { + ID int `json:"id"` + Address string `json:"address"` + Type string `json:"type"` + AccessToken string `json:"access_token"` + Namespace string `json:"namespace"` + Region string `json:"region"` + // the field and tag not consistent because of db field + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + Username string `json:"username"` + Password string `json:"password"` + // the field determine whether the proxy is enabled + EnableProxy bool `json:"enable_proxy"` +} type Client struct { Client *codehub.CodeHubClient } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - ch, err := systemconfig.New().GetCodeHost(id) - if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") - } - codehubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) + codehubClient := codehub.NewCodeHubClient(c.AccessKey, c.SecretKey, c.Region, config.ProxyHTTPSAddr(), c.EnableProxy) return &Client{Client: codehubClient}, nil } @@ -41,6 +49,22 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags() ([]*client.Tag, error) { +func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { + tags, err := c.Client.TagList(projectName) + if err != nil { + return nil, err + } + + var res []*client.Tag + for _, o := range tags { + res = append(res, &client.Tag{ + Name: o.Name, + }) + } + + return res, nil +} + +func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { return nil, nil } diff --git a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go index da4499cf..7c77d846 100644 --- a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go @@ -5,23 +5,31 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" ) -type Config struct{} +type Config struct { + ID int `json:"id"` + Address string `json:"address"` + Type string `json:"type"` + AccessToken string `json:"access_token"` + Namespace string `json:"namespace"` + Region string `json:"region"` + // the field and tag not consistent because of db field + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + Username string `json:"username"` + Password string `json:"password"` + // the field determine whether the proxy is enabled + EnableProxy bool `json:"enable_proxy"` +} type Client struct { Client *gerrit.Client } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - ch, err := systemconfig.New().GetCodeHost(id) - if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") - } - gerritClient := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + gerritClient := gerrit.NewClient(c.Address, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) return &Client{Client: gerritClient}, nil } @@ -39,6 +47,21 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags() ([]*client.Tag, error) { +func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { + result, err := c.Client.ListTags(projectName) + if err != nil { + return nil, err + } + var res []*client.Tag + for _, o := range result { + res = append(res, &client.Tag{ + Name: o.Ref, + Message: o.Message, + }) + } + return res, nil +} + +func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { return nil, nil } diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index 64de46d4..9478e592 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -3,16 +3,28 @@ package github import ( "context" + github2 "github.com/google/go-github/v35/github" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/git/github" ) type Config struct { + ID int `json:"id"` + Address string `json:"address"` + Type string `json:"type"` + AccessToken string `json:"access_token"` + Namespace string `json:"namespace"` + Region string `json:"region"` + // the field and tag not consistent because of db field + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + Username string `json:"username"` + Password string `json:"password"` + // the field determine whether the proxy is enabled + EnableProxy bool `json:"enable_proxy"` } type Client struct { @@ -20,14 +32,11 @@ type Client struct { } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - ch, err := systemconfig.New().GetCodeHost(id) - if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") - } + cfg := &github.Config{ - AccessToken: ch.AccessToken, + AccessToken: c.AccessToken, } - if ch.EnableProxy { + if c.EnableProxy { cfg.Proxy = config.ProxyHTTPSAddr() } return &Client{ @@ -50,6 +59,44 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags() ([]*client.Tag, error) { - return nil, nil +func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { + + tags, err := c.Client.ListTags(context.TODO(), namespace, projectName, nil) + if err != nil { + return nil, err + } + var res []*client.Tag + for _, o := range tags { + res = append(res, &client.Tag{ + Name: o.GetName(), + ZipballURL: o.GetZipballURL(), + TarballURL: o.GetTarballURL(), + }) + } + return res, nil +} + +func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { + prs, err := c.Client.ListPullRequests(context.TODO(), namespace, projectName, &github2.PullRequestListOptions{ + ListOptions: github2.ListOptions{PerPage: 100}, + }) + if err != nil { + return nil, err + } + var res []*client.PullRequest + for _, o := range prs { + res = append(res, &client.PullRequest{ + ID: o.GetNumber(), + CreatedAt: o.GetCreatedAt().Unix(), + UpdatedAt: o.GetUpdatedAt().Unix(), + State: o.GetState(), + User: o.GetUser().GetLogin(), + Number: o.GetNumber(), + AuthorUsername: o.GetUser().GetLogin(), + Title: o.GetTitle(), + SourceBranch: o.GetHead().GetRef(), + TargetBranch: o.GetBase().GetRef(), + }) + } + return res, nil } diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index cd2d57d1..1088917f 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -5,23 +5,32 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -type Config struct{} +type Config struct { + ID int `json:"id"` + Address string `json:"address"` + Type string `json:"type"` + AccessToken string `json:"access_token"` + Namespace string `json:"namespace"` + Region string `json:"region"` + // the field and tag not consistent because of db field + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + Username string `json:"username"` + Password string `json:"password"` + // the field determine whether the proxy is enabled + EnableProxy bool `json:"enable_proxy"` +} type Client struct { Client *gitlab.Client } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - ch, err := systemconfig.New().GetCodeHost(id) - if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") - } - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + + client, err := gitlab.NewClient(c.Address, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) if err != nil { return nil, err } @@ -48,6 +57,47 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags() ([]*client.Tag, error) { - return nil, nil +func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { + tags, err := c.Client.ListTags(namespace, projectName, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }, key) + if err != nil { + return nil, err + } + var res []*client.Tag + for _, o := range tags { + res = append(res, &client.Tag{ + Name: o.Name, + Message: o.Message, + }) + } + return res, nil +} + +func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { + prs, err := c.Client.ListOpenedProjectMergeRequests(namespace, projectName, targeBr, key, &gitlab.ListOptions{ + Page: page, + PerPage: perPage, + NoPaginated: true, + }) + if err != nil { + return nil, err + } + var res []*client.PullRequest + for _, o := range prs { + res = append(res, &client.PullRequest{ + ID: o.IID, + TargetBranch: o.TargetBranch, + SourceBranch: o.SourceBranch, + ProjectID: o.ProjectID, + Title: o.Title, + State: o.State, + CreatedAt: o.CreatedAt.Unix(), + UpdatedAt: o.UpdatedAt.Unix(), + AuthorUsername: o.Author.Username, + }) + } + return res, nil } diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go index aaf1d0bc..41b2eac4 100644 --- a/pkg/microservice/aslan/core/code/client/interface.go +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -2,7 +2,8 @@ package client type CodeHostClient interface { ListBranches(namespace, projectName, key string, page, perPage int) ([]*Branch, error) - ListTags() ([]*Tag, error) + ListTags(namespace, projectName, key string, page, perPage int) ([]*Tag, error) + ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*PullRequest, error) } type Branch struct { @@ -17,3 +18,18 @@ type Tag struct { TarballURL string `json:"tarball_url"` Message string `json:"message"` } + +type PullRequest struct { + ID int `json:"id"` + TargetBranch string `json:"targetBranch"` + SourceBranch string `json:"sourceBranch"` + ProjectID int `json:"projectId"` + Title string `json:"title"` + State string `json:"state"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` + AuthorUsername string `json:"authorUsername"` + Number int `json:"number"` + User string `json:"user"` + Base string `json:"base,omitempty"` +} diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index ba2d4efb..ba912720 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -1,6 +1,7 @@ package open import ( + "encoding/json" "fmt" "go.uber.org/zap" @@ -10,6 +11,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gerrit" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" ) type ClientConfig interface { @@ -23,13 +26,23 @@ var ClientsConfig = map[string]func() ClientConfig{ "codehub": func() ClientConfig { return new(codehub.Config) }, } -func OpenClient(codehostType string, codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { +func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { + ch, err := systemconfig.New().GetCodeHost(codehostID) + if err != nil { + return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + } var c client.CodeHostClient - - f, ok := ClientsConfig[codehostType] + f, ok := ClientsConfig[ch.Type] if !ok { return c, fmt.Errorf("unknow codehost type") } clientConfig := f() + bs, err := json.Marshal(ch) + if err != nil { + return nil, err + } + if err := json.Unmarshal(bs, clientConfig); err != nil { + return nil, err + } return clientConfig.Open(codehostID, log) } diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index a6246618..3eb21b08 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -21,16 +21,10 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" ) func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.Branch, error) { - ch, err := systemconfig.New().GetCodeHost(codeHostID) - if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") - } - client, err := open.OpenClient(ch.Type, codeHostID, log) + client, err := open.OpenClient(codeHostID, log) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index a3212b33..4d6597b1 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -17,57 +17,16 @@ limitations under the License. package service import ( - "context" - - "github.com/google/go-github/v35/github" "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/git/gitlab" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" ) -func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, key string, page, perPage int, log *zap.SugaredLogger) ([]*PullRequest, error) { - ch, err := systemconfig.New().GetCodeHost(codeHostID) +func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.PullRequest, error) { + client, err := open.OpenClient(codeHostID, log) if err != nil { - return nil, e.ErrCodehostListPrs.AddDesc("git client is nil") - } - - if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) - } - - prs, err := client.ListOpenedProjectMergeRequests(namespace, projectName, targetBr, key, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, - NoPaginated: true, - }) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListPrs.AddDesc(err.Error()) - } - - return ToPullRequests(prs), nil - - } else if ch.Type == gerrit.CodehostTypeGerrit { - return nil, nil - } else if ch.Type == CodeHostCodeHub { - return nil, nil - } else { - // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - pullRequests, err := gh.ListPullRequests(context.TODO(), namespace, projectName, &github.PullRequestListOptions{ - ListOptions: github.ListOptions{PerPage: 100}, - }) - if err != nil { - return nil, err - } - return ToPullRequests(pullRequests), nil + return nil, err } + return client.ListPrs(namespace, projectName, key, targetBr, page, perPage) } diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index a503cd14..2e175bab 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -32,18 +32,18 @@ type RepoInfoList struct { } type GitRepoInfo struct { - Owner string `json:"repo_owner"` - Repo string `json:"repo"` - CodehostID int `json:"codehost_id"` - Source string `json:"source"` - DefaultBranch string `json:"default_branch"` - ErrorMsg string `json:"error_msg"` // repo信息是否拉取成功 - Branches []*client.Branch `json:"branches"` - Tags []*Tag `json:"tags"` - PRs []*PullRequest `json:"prs"` - ProjectUUID string `json:"project_uuid,omitempty"` - RepoUUID string `json:"repo_uuid,omitempty"` - RepoID string `json:"repo_id,omitempty"` + Owner string `json:"repo_owner"` + Repo string `json:"repo"` + CodehostID int `json:"codehost_id"` + Source string `json:"source"` + DefaultBranch string `json:"default_branch"` + ErrorMsg string `json:"error_msg"` // repo信息是否拉取成功 + Branches []*client.Branch `json:"branches"` + Tags []*client.Tag `json:"tags"` + PRs []*client.PullRequest `json:"prs"` + ProjectUUID string `json:"project_uuid,omitempty"` + RepoUUID string `json:"repo_uuid,omitempty"` + RepoID string `json:"repo_id,omitempty"` } // ListRepoInfos ... @@ -62,7 +62,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() - info.PRs = []*PullRequest{} + info.PRs = []*client.PullRequest{} return } }(info) @@ -99,7 +99,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() - info.Tags = []*Tag{} + info.Tags = []*client.Tag{} return } }(info) diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 473fb51d..0d81a1cc 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -17,65 +17,24 @@ limitations under the License. package service import ( - "context" - "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/git/gitlab" ) -func CodeHostListTags(codeHostID int, projectName string, namespace string, key string, page int, perPage int, log *zap.SugaredLogger) ([]*Tag, error) { +func CodeHostListTags(codeHostID int, projectName string, namespace string, key string, page int, perPage int, log *zap.SugaredLogger) ([]*client.Tag, error) { ch, err := systemconfig.New().GetCodeHost(codeHostID) if err != nil { log.Error(err) return nil, e.ErrCodehostListTags.AddDesc("git client is nil") } - if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListTags.AddDesc(err.Error()) - } - - tags, err := client.ListTags(namespace, projectName, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, - NoPaginated: true, - }, key) - if err != nil { - return nil, err - } - return ToTags(tags), nil - } else if ch.Type == gerrit.CodehostTypeGerrit { - client := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - tags, err := client.ListTags(projectName) - if err != nil { - return nil, err - } - - return ToTags(tags), nil - } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) - tags, err := codeHubClient.TagList(projectName) - if err != nil { - return nil, err - } - return ToTags(tags), nil - } else { - // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - tags, err := gh.ListTags(context.TODO(), namespace, projectName, nil) - if err != nil { - return nil, err - } - - return ToTags(tags), nil + client, err := open.OpenClient(ch.ID, log) + if err != nil { + return nil, err } + return client.ListTags(namespace, projectName, key, page, perPage) } -- Gitee From cb81bc2709c5b45e97778d4a940dcaab8fd50297 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 16:39:50 +0800 Subject: [PATCH 1817/2647] codehost Signed-off-by: mouuii --- .../aslan/core/code/client/codehub/codehub.go | 10 +++---- .../aslan/core/code/client/gerrit/gerrit.go | 10 +++---- .../aslan/core/code/client/github/github.go | 12 ++++----- .../aslan/core/code/client/gitlab/gitlab.go | 26 +++++++++---------- .../aslan/core/code/client/interface.go | 18 ++++++++++--- .../aslan/core/code/service/branch.go | 4 +-- .../aslan/core/code/service/merge_request.go | 11 ++++++-- .../aslan/core/code/service/tag.go | 10 +++++-- 8 files changed, 63 insertions(+), 38 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/codehub/codehub.go b/pkg/microservice/aslan/core/code/client/codehub/codehub.go index 349091c4..d3cd531b 100644 --- a/pkg/microservice/aslan/core/code/client/codehub/codehub.go +++ b/pkg/microservice/aslan/core/code/client/codehub/codehub.go @@ -33,8 +33,8 @@ func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, return &Client{Client: codehubClient}, nil } -func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { - bList, err := c.Client.BranchList(projectName) +func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { + bList, err := c.Client.BranchList(opt.ProjectName) if err != nil { return nil, err } @@ -49,8 +49,8 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { - tags, err := c.Client.TagList(projectName) +func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { + tags, err := c.Client.TagList(opt.ProjectName) if err != nil { return nil, err } @@ -65,6 +65,6 @@ func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) return res, nil } -func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { +func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { return nil, nil } diff --git a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go index 7c77d846..335a64a3 100644 --- a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go @@ -33,8 +33,8 @@ func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, return &Client{Client: gerritClient}, nil } -func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { - bList, err := c.Client.ListBranches(projectName) +func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(opt.ProjectName) if err != nil { return nil, err } @@ -47,8 +47,8 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { - result, err := c.Client.ListTags(projectName) +func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { + result, err := c.Client.ListTags(opt.ProjectName) if err != nil { return nil, err } @@ -62,6 +62,6 @@ func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) return res, nil } -func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { +func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { return nil, nil } diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index 9478e592..6fe479eb 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -44,8 +44,8 @@ func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, }, nil } -func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { - bList, err := c.Client.ListBranches(context.TODO(), namespace, projectName, nil) +func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(context.TODO(), opt.Namespace, opt.ProjectName, nil) if err != nil { return nil, err } @@ -59,9 +59,9 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { +func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { - tags, err := c.Client.ListTags(context.TODO(), namespace, projectName, nil) + tags, err := c.Client.ListTags(context.TODO(), opt.Namespace, opt.ProjectName, nil) if err != nil { return nil, err } @@ -76,8 +76,8 @@ func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) return res, nil } -func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { - prs, err := c.Client.ListPullRequests(context.TODO(), namespace, projectName, &github2.PullRequestListOptions{ +func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { + prs, err := c.Client.ListPullRequests(context.TODO(), opt.Namespace, opt.ProjectName, &github2.PullRequestListOptions{ ListOptions: github2.ListOptions{PerPage: 100}, }) if err != nil { diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index 1088917f..12d32eba 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -37,10 +37,10 @@ func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, return &Client{Client: client}, nil } -func (c *Client) ListBranches(namespace, projectName, key string, page, perPage int) ([]*client.Branch, error) { - bList, err := c.Client.ListBranches(namespace, projectName, key, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, +func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(opt.Namespace, opt.ProjectName, opt.Key, &gitlab.ListOptions{ + Page: opt.Page, + PerPage: opt.PerPage, NoPaginated: true, }) if err != nil { @@ -57,12 +57,12 @@ func (c *Client) ListBranches(namespace, projectName, key string, page, perPage return res, nil } -func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) ([]*client.Tag, error) { - tags, err := c.Client.ListTags(namespace, projectName, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, +func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { + tags, err := c.Client.ListTags(opt.Namespace, opt.ProjectName, &gitlab.ListOptions{ + Page: opt.Page, + PerPage: opt.PerPage, NoPaginated: true, - }, key) + }, opt.Key) if err != nil { return nil, err } @@ -76,10 +76,10 @@ func (c *Client) ListTags(namespace, projectName, key string, page, perPage int) return res, nil } -func (c *Client) ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*client.PullRequest, error) { - prs, err := c.Client.ListOpenedProjectMergeRequests(namespace, projectName, targeBr, key, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, +func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { + prs, err := c.Client.ListOpenedProjectMergeRequests(opt.Namespace, opt.ProjectName, opt.TargeBr, opt.Key, &gitlab.ListOptions{ + Page: opt.Page, + PerPage: opt.PerPage, NoPaginated: true, }) if err != nil { diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go index 41b2eac4..8e6b6963 100644 --- a/pkg/microservice/aslan/core/code/client/interface.go +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -1,9 +1,21 @@ package client type CodeHostClient interface { - ListBranches(namespace, projectName, key string, page, perPage int) ([]*Branch, error) - ListTags(namespace, projectName, key string, page, perPage int) ([]*Tag, error) - ListPrs(namespace, projectName, key, targeBr string, page, perPage int) ([]*PullRequest, error) + ListBranches(opt ListOpt) ([]*Branch, error) + ListTags(opt ListOpt) ([]*Tag, error) + ListPrs(opt ListOpt) ([]*PullRequest, error) +} + +type ListOpt struct { + Namespace string + ProjectName string + Key string + Page int + PerPage int + TargeBr string +} + +type TagOpt struct { } type Branch struct { diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 3eb21b08..00a18d53 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -24,9 +24,9 @@ import ( ) func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.Branch, error) { - client, err := open.OpenClient(codeHostID, log) + codehostClient, err := open.OpenClient(codeHostID, log) if err != nil { return nil, err } - return client.ListBranches(namespace, projectName, key, page, perPage) + return codehostClient.ListBranches(client.ListOpt{Namespace: namespace, ProjectName: projectName, Key: key, Page: page, PerPage: perPage}) } diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 4d6597b1..6a69a9ec 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -24,9 +24,16 @@ import ( ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.PullRequest, error) { - client, err := open.OpenClient(codeHostID, log) + codehostClient, err := open.OpenClient(codeHostID, log) if err != nil { return nil, err } - return client.ListPrs(namespace, projectName, key, targetBr, page, perPage) + return codehostClient.ListPrs(client.ListOpt{ + Namespace: namespace, + ProjectName: projectName, + Key: key, + Page: page, + PerPage: perPage, + TargeBr: targetBr, + }) } diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 0d81a1cc..5804f6a8 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -32,9 +32,15 @@ func CodeHostListTags(codeHostID int, projectName string, namespace string, key return nil, e.ErrCodehostListTags.AddDesc("git client is nil") } - client, err := open.OpenClient(ch.ID, log) + codehostClient, err := open.OpenClient(ch.ID, log) if err != nil { return nil, err } - return client.ListTags(namespace, projectName, key, page, perPage) + return codehostClient.ListTags(client.ListOpt{ + Namespace: namespace, + ProjectName: projectName, + Key: key, + Page: page, + PerPage: page, + }) } -- Gitee From 45ed1e40327898d03d6f9572c27fbd52a6aefe4a Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 16:44:54 +0800 Subject: [PATCH 1818/2647] remove blanc Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/client/interface.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go index 8e6b6963..eaa34ade 100644 --- a/pkg/microservice/aslan/core/code/client/interface.go +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -15,9 +15,6 @@ type ListOpt struct { TargeBr string } -type TagOpt struct { -} - type Branch struct { Name string `json:"name"` Protected bool `json:"protected"` -- Gitee From 85bd6e9acca864f4176744e9f6b81f41deb8c202 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Mar 2022 16:47:56 +0800 Subject: [PATCH 1819/2647] ingress logic support for kubernetes version 1.22 Signed-off-by: Min Min --- .../aslan/core/common/service/environment.go | 25 ++++++++--- .../environment/service/environment_group.go | 39 ++++++++++++---- .../aslan/core/environment/service/service.go | 45 +++++++++++++++++-- pkg/shared/kube/wrapper/ingress.go | 27 +++++++++++ pkg/tool/kube/getter/ingress.go | 26 +++++++---- pkg/tool/kube/getter/role.go | 42 ----------------- pkg/tool/kube/getter/rolebinding.go | 32 ------------- pkg/tool/kube/getter/serviceaccount.go | 32 ------------- 8 files changed, 137 insertions(+), 131 deletions(-) delete mode 100644 pkg/tool/kube/getter/role.go delete mode 100644 pkg/tool/kube/getter/rolebinding.go delete mode 100644 pkg/tool/kube/getter/serviceaccount.go diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 8b55e95e..cb10ce00 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -314,13 +314,28 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p } hostInfos := make([]resource.HostInfo, 0) - ingresses, err := getter.ListIngresses(nil, informer) - if err == nil { - for _, ingress := range ingresses { - hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) + version, err := cls.Discovery().ServerVersion() + if err != nil { + log.Errorf("Failed to get server version info for cluster: %s, the error is: %s", clusterID, err) + return 0, nil, err + } + if kubeclient.VersionLessThan122(version) { + ingresses, err := getter.ListExtensionsV1Beta1Ingresses(nil, informer) + if err == nil { + for _, ingress := range ingresses { + hostInfos = append(hostInfos, wrapper.Ingress(ingress).HostInfo()...) + } + } else { + log.Warnf("Failed to list ingresses, the error is: %s", err) } } else { - log.Warnf("Failed to list ingresses, the error is: %s", err) + ingresses, err := getter.ListNetworkingV1Ingress(nil, informer) + if err == nil { + for _, ingress := range ingresses { + hostInfos = append(hostInfos, wrapper.GetIngressHostInfo(ingress)...) + } + log.Warnf("Failed to list ingresses, the error is: %s", err) + } } // get all services diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index 9f84696b..1771fc33 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -167,19 +167,42 @@ func GetIngressInfo(product *commonmodels.Product, service *commonmodels.Service } switch u.GetKind() { case setting.Ingress: - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) if err != nil { - log.Errorf("failed to init kubeClient, clusterID: %s", product.ClusterID) + log.Errorf("failed to init clientset, clusterID: %s", product.ClusterID) return nil } - // need to get ingress from k8s - // serializer.NewDecoder()YamlToIngress() only supports ingress resource with apiVersion: apiVersion: extensions/v1beta1 - ing, found, err := getter.GetIngress(product.Namespace, u.GetName(), kubeClient) - if err != nil || !found { - log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, product.Namespace, err) + + inf, err := informer.NewInformer(product.ClusterID, product.Namespace, clientset) + if err != nil { + log.Errorf("failed to create informer from clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return nil + } + + version, err := clientset.Discovery().ServerVersion() + if err != nil { + log.Warnf("Failed to determine server version, error is: %s", err) continue } - hostInfos = append(hostInfos, wrapper.Ingress(ing).HostInfo()...) + + if kubeclient.VersionLessThan122(version) { + // get the ingress info from kubernetes. For cluster version 1.22- we only search for + // extensions/v1beta1. + // FIXME: add networking.k8s.io/v1beta1 & networking.k8s.io/v1 support for cluster 1.22- + ing, found, err := getter.GetExtensionsV1Beta1Ingress(product.Namespace, u.GetName(), inf) + if err != nil || !found { + log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, product.Namespace, err) + continue + } + hostInfos = append(hostInfos, wrapper.Ingress(ing).HostInfo()...) + } else { + ing, err := getter.GetNetworkingV1Ingress(product.Namespace, u.GetName(), inf) + if err != nil { + log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, product.Namespace, err) + continue + } + hostInfos = append(hostInfos, wrapper.GetIngressHostInfo(ing)...) + } } } ingressInfo.HostInfo = hostInfos diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index b5565cba..86a3b72c 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/util" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" @@ -201,6 +202,18 @@ func GetService(envName, productName, serviceName string, workLoadType string, l return nil, e.ErrGetService.AddErr(err) } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), env.ClusterID) + if err != nil { + log.Errorf("Failed to create kubernetes clientset for cluster id: %s, the error is: %s", env.ClusterID, err) + return nil, e.ErrGetService.AddErr(err) + } + + inf, err := informer.NewInformer(env.ClusterID, env.Namespace, clientset) + if err != nil { + log.Errorf("Failed to create informer for namespace [%s] in cluster [%s], the error is: %s", env.Namespace, env.ClusterID, err) + return nil, e.ErrGetService.AddErr(err) + } + namespace := env.Namespace switch env.Source { case setting.SourceFromExternal, setting.SourceFromHelm: @@ -318,13 +331,37 @@ func GetService(envName, productName, serviceName string, workLoadType string, l ret.Scales = append(ret.Scales, getStatefulSetWorkloadResource(sts, kubeClient, log)) case setting.Ingress: - ing, found, err := getter.GetIngress(namespace, u.GetName(), kubeClient) - if err != nil || !found { - log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, namespace, err) + + version, err := clientset.Discovery().ServerVersion() + if err != nil { + log.Warnf("Failed to determine server version, error is: %s", err) continue } + if kubeclient.VersionLessThan122(version) { + ing, found, err := getter.GetExtensionsV1Beta1Ingress(namespace, u.GetName(), inf) + if err != nil || !found { + log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, namespace, err) + continue + } - ret.Ingress = append(ret.Ingress, wrapper.Ingress(ing).Resource()) + ret.Ingress = append(ret.Ingress, wrapper.Ingress(ing).Resource()) + } else { + ing, err := getter.GetNetworkingV1Ingress(namespace, u.GetName(), inf) + if err != nil { + log.Warnf("no ingress %s found in %s:%s %v", u.GetName(), service.ServiceName, namespace, err) + continue + } + + ingress := &internalresource.Ingress{ + Name: ing.Name, + Labels: ing.Labels, + HostInfo: wrapper.GetIngressHostInfo(ing), + IPs: []string{}, + Age: util.Age(ing.CreationTimestamp.Unix()), + } + + ret.Ingress = append(ret.Ingress, ingress) + } case setting.Service: svc, found, err := getter.GetService(namespace, u.GetName(), kubeClient) diff --git a/pkg/shared/kube/wrapper/ingress.go b/pkg/shared/kube/wrapper/ingress.go index e6e250a6..5b9bac2d 100644 --- a/pkg/shared/kube/wrapper/ingress.go +++ b/pkg/shared/kube/wrapper/ingress.go @@ -18,6 +18,7 @@ package wrapper import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + v1 "k8s.io/api/networking/v1" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/util" @@ -43,6 +44,32 @@ func (ing *ingress) Unwrap() *extensionsv1beta1.Ingress { return ing.Ingress } +func GetIngressHostInfo(ing *v1.Ingress) []resource.HostInfo { + var res []resource.HostInfo + for _, rule := range ing.Spec.Rules { + info := resource.HostInfo{ + Host: rule.Host, + Backends: []resource.Backend{}, + } + + if rule.HTTP == nil { + continue + } + + for _, path := range rule.HTTP.Paths { + backend := resource.Backend{ + ServiceName: path.Backend.Service.Name, + ServicePort: string(path.Backend.Service.Port.Number), + } + info.Backends = append(info.Backends, backend) + } + + res = append(res, info) + } + + return res +} + func (ing *ingress) HostInfo() []resource.HostInfo { var res []resource.HostInfo for _, rule := range ing.Spec.Rules { diff --git a/pkg/tool/kube/getter/ingress.go b/pkg/tool/kube/getter/ingress.go index ca47d918..620a72d9 100644 --- a/pkg/tool/kube/getter/ingress.go +++ b/pkg/tool/kube/getter/ingress.go @@ -18,30 +18,40 @@ package getter import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + v1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" ) -func GetIngress(ns, name string, cl client.Client) (*extensionsv1beta1.Ingress, bool, error) { - g := &extensionsv1beta1.Ingress{} - found, err := GetResourceInCache(ns, name, g, cl) - if err != nil || !found { - g = nil +func GetExtensionsV1Beta1Ingress(namespace, name string, lister informers.SharedInformerFactory) (*extensionsv1beta1.Ingress, bool, error) { + ret, err := lister.Extensions().V1beta1().Ingresses().Lister().Ingresses(namespace).Get(name) + if err == nil { + return ret, true, nil } + return nil, false, err +} - return g, found, err +func GetNetworkingV1Ingress(namespace, name string, lister informers.SharedInformerFactory) (*v1.Ingress, error) { + return lister.Networking().V1().Ingresses().Lister().Ingresses(namespace).Get(name) } -// ListIngresses gets the ingress (extensions/v1beta1) from the informer -func ListIngresses(selector labels.Selector, lister informers.SharedInformerFactory) ([]*extensionsv1beta1.Ingress, error) { +// ListExtensionsV1Beta1Ingresses gets the ingress (extensions/v1beta1) from the informer +func ListExtensionsV1Beta1Ingresses(selector labels.Selector, lister informers.SharedInformerFactory) ([]*extensionsv1beta1.Ingress, error) { if selector == nil { selector = labels.NewSelector() } return lister.Extensions().V1beta1().Ingresses().Lister().List(selector) } +func ListNetworkingV1Ingress(selector labels.Selector, lister informers.SharedInformerFactory) ([]*v1.Ingress, error) { + if selector == nil { + selector = labels.NewSelector() + } + return lister.Networking().V1().Ingresses().Lister().List(selector) +} + func ListIngressesYaml(ns string, selector labels.Selector, cl client.Client) ([][]byte, error) { gvk := schema.GroupVersionKind{ Group: "extensions", diff --git a/pkg/tool/kube/getter/role.go b/pkg/tool/kube/getter/role.go deleted file mode 100644 index f1b10664..00000000 --- a/pkg/tool/kube/getter/role.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package getter - -import ( - rbacv1beta1 "k8s.io/api/rbac/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func GetRole(ns, name string, cl client.Client) (*rbacv1beta1.Role, bool, error) { - g := &rbacv1beta1.Role{} - found, err := GetResourceInCache(ns, name, g, cl) - if err != nil || !found { - g = nil - } - - return g, found, err -} - -func GetClusterRole(name string, cl client.Client) (*rbacv1beta1.ClusterRole, bool, error) { - g := &rbacv1beta1.ClusterRole{} - found, err := GetResourceInCache("", name, g, cl) - if err != nil || !found { - g = nil - } - - return g, found, err -} diff --git a/pkg/tool/kube/getter/rolebinding.go b/pkg/tool/kube/getter/rolebinding.go deleted file mode 100644 index 1314a663..00000000 --- a/pkg/tool/kube/getter/rolebinding.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package getter - -import ( - rbacv1beta1 "k8s.io/api/rbac/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func GetRoleBinding(ns, name string, cl client.Client) (*rbacv1beta1.RoleBinding, bool, error) { - g := &rbacv1beta1.RoleBinding{} - found, err := GetResourceInCache(ns, name, g, cl) - if err != nil || !found { - g = nil - } - - return g, found, err -} diff --git a/pkg/tool/kube/getter/serviceaccount.go b/pkg/tool/kube/getter/serviceaccount.go deleted file mode 100644 index 825ed246..00000000 --- a/pkg/tool/kube/getter/serviceaccount.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package getter - -import ( - corev1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func GetServiceAccount(ns, name string, cl client.Client) (*corev1.ServiceAccount, bool, error) { - svc := &corev1.ServiceAccount{} - found, err := GetResourceInCache(ns, name, svc, cl) - if err != nil || !found { - svc = nil - } - - return svc, found, err -} -- Gitee From be9ada5350f7f6ecde79a7c20722a877dfd65e3c Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 16:59:10 +0800 Subject: [PATCH 1820/2647] info Signed-off-by: mouuii --- .../aslan/core/code/service/repo.go | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 2e175bab..a8811811 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -25,6 +25,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" ) type RepoInfoList struct { @@ -48,17 +49,23 @@ type GitRepoInfo struct { // ListRepoInfos ... func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo, error) { - var err error var wg sync.WaitGroup var errList *multierror.Error for _, info := range infos { + codehostClient, err := open.OpenClient(info.CodehostID, log) + if err != nil { + return nil, err + } wg.Add(1) go func(info *GitRepoInfo) { defer func() { wg.Done() }() - info.PRs, err = CodeHostListPRs(info.CodehostID, info.Repo, strings.Replace(info.Owner, "%2F", "/", -1), "", "", 0, 0, log) + info.PRs, err = codehostClient.ListPrs(client.ListOpt{ + Namespace: strings.Replace(info.Owner, "%2F", "/", -1), + ProjectName: info.Repo, + }) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() @@ -76,7 +83,11 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo if info.Source == CodeHostCodeHub { projectName = info.RepoUUID } - info.Branches, err = CodeHostListBranches(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "", 0, 0, log) + + info.Branches, err = codehostClient.ListBranches(client.ListOpt{ + Namespace: strings.Replace(info.Owner, "%2F", "/", -1), + ProjectName: projectName, + }) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() @@ -95,7 +106,11 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo if info.Source == CodeHostCodeHub { projectName = info.RepoID } - info.Tags, err = CodeHostListTags(info.CodehostID, projectName, strings.Replace(info.Owner, "%2F", "/", -1), "", 0, 0, log) + + info.Tags, err = codehostClient.ListTags(client.ListOpt{ + Namespace: strings.Replace(info.Owner, "%2F", "/", -1), + ProjectName: projectName, + }) if err != nil { errList = multierror.Append(errList, err) info.ErrorMsg = err.Error() -- Gitee From 7adfb17ef0f6ef59635502def15a3c6101bbf723 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 17:01:28 +0800 Subject: [PATCH 1821/2647] add image_name field Signed-off-by: liu deyi --- .../core/common/repository/models/workflow.go | 24 ++++++++--------- .../aslan/core/common/service/service.go | 1 + pkg/microservice/cron/core/service/types.go | 26 +++++++++---------- .../core/service/types/task/model.go | 23 ++++++++-------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 5275895d..7b15e761 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -339,18 +339,18 @@ type HookPayload struct { } type TargetArgs struct { - Name string `bson:"name" json:"name"` - ImageName string `bson:"image_name" json:"image_name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - Build *BuildArgs `bson:"build" json:"build"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` - Image string `bson:"image" json:"image"` - BinFile string `bson:"bin_file" json:"bin_file"` - Envs []*KeyVal `bson:"envs" json:"envs"` - HasBuild bool `bson:"has_build" json:"has_build"` - JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` + Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name" json:"image_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Build *BuildArgs `bson:"build" json:"build"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + BinFile string `bson:"bin_file" json:"bin_file"` + Envs []*KeyVal `bson:"envs" json:"envs"` + HasBuild bool `bson:"has_build" json:"has_build"` + JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args,omitempty" json:"jenkins_build_args,omitempty"` } type JenkinsBuildArgs struct { diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 9cc723c8..edcf503d 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -758,6 +758,7 @@ func GetPresetRules() []*templatemodels.ImageSearchingRule { type Variable struct { SERVICE string + IMAGE_NAME string TIMESTAMP string TASK_ID string REPO_COMMIT_ID string diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 6b1401fc..50a81cbf 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -286,21 +286,19 @@ type WorkflowTaskArgs struct { IsParallel bool `json:"is_parallel" bson:"is_parallel"` } -// TargetArgs ... type TargetArgs struct { - // 服务组件名称 - Name string `bson:"name" json:"name"` - // 服务名称 - ServiceName string `bson:"service_name" json:"service_name"` - ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` - Build *BuildArgs `bson:"build" json:"build"` - Version string `bson:"version" json:"version"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` - Image string `bson:"image" json:"image"` - BinFile string `bson:"bin_file" json:"bin_file"` - Envs []*KeyVal `bson:"envs" json:"envs"` - HasBuild bool `bson:"has_build" json:"has_build"` - JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` + Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name" json:"image_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Build *BuildArgs `bson:"build" json:"build"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + BinFile string `bson:"bin_file" json:"bin_file"` + Envs []*KeyVal `bson:"envs" json:"envs"` + HasBuild bool `bson:"has_build" json:"has_build"` + JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args,omitempty" json:"jenkins_build_args,omitempty"` } type JenkinsBuildArgs struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 2535b31a..28f3c3ef 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -212,17 +212,18 @@ type HookPayload struct { } type TargetArgs struct { - Name string `bson:"name" json:"name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` - Build *BuildArgs `bson:"build" json:"build"` - Version string `bson:"version" json:"version"` - Deploy []DeployEnv `bson:"deloy" json:"deploy"` - Image string `bson:"image" json:"image"` - BinFile string `bson:"bin_file" json:"bin_file"` - Envs []*KeyVal `bson:"envs" json:"envs"` - HasBuild bool `bson:"has_build" json:"has_build"` - JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args" json:"jenkins_build_args"` + Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name" json:"image_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceType string `bson:"service_type,omitempty" json:"service_type,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Build *BuildArgs `bson:"build" json:"build"` + Deploy []DeployEnv `bson:"deploy" json:"deploy"` + Image string `bson:"image,omitempty" json:"image,omitempty"` + BinFile string `bson:"bin_file" json:"bin_file"` + Envs []*KeyVal `bson:"envs" json:"envs"` + HasBuild bool `bson:"has_build" json:"has_build"` + JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args,omitempty" json:"jenkins_build_args,omitempty"` } type TestTaskArgs struct { -- Gitee From 1983316cba9b0c3e656fd4be8f0349c588e16303 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 17:06:50 +0800 Subject: [PATCH 1822/2647] add license Signed-off-by: mouuii --- .../aslan/core/code/client/codehub/codehub.go | 16 ++++++++++++++++ .../aslan/core/code/client/gerrit/gerrit.go | 16 ++++++++++++++++ .../aslan/core/code/client/github/github.go | 16 ++++++++++++++++ .../aslan/core/code/client/gitlab/gitlab.go | 16 ++++++++++++++++ .../aslan/core/code/client/interface.go | 16 ++++++++++++++++ .../aslan/core/code/client/open/open.go | 16 ++++++++++++++++ .../service/collaboration_instance.go | 16 ++++++++++++++++ .../service/collaboration/collaboration_mode.go | 16 ++++++++++++++++ .../policy/core/repository/models/common.go | 16 ++++++++++++++++ pkg/microservice/policy/core/service/common.go | 16 ++++++++++++++++ pkg/middleware/gin/collaboration.go | 16 ++++++++++++++++ 11 files changed, 176 insertions(+) diff --git a/pkg/microservice/aslan/core/code/client/codehub/codehub.go b/pkg/microservice/aslan/core/code/client/codehub/codehub.go index d3cd531b..d1e30a34 100644 --- a/pkg/microservice/aslan/core/code/client/codehub/codehub.go +++ b/pkg/microservice/aslan/core/code/client/codehub/codehub.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package codehub import ( diff --git a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go index 335a64a3..d3bfd25a 100644 --- a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gerrit import ( diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index 6fe479eb..73c6fd27 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package github import ( diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index 12d32eba..91687c0d 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitlab import ( diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go index eaa34ade..8ab4f20e 100644 --- a/pkg/microservice/aslan/core/code/client/interface.go +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client type CodeHostClient interface { diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index ba912720..6b3d57d6 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package open import ( diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index f3e9ec80..aa8e70d5 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go index 08ed50a5..1d127a60 100644 --- a/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go +++ b/pkg/microservice/aslan/core/common/service/collaboration/collaboration_mode.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package collaboration import ( diff --git a/pkg/microservice/policy/core/repository/models/common.go b/pkg/microservice/policy/core/repository/models/common.go index 9851d229..72881dba 100644 --- a/pkg/microservice/policy/core/repository/models/common.go +++ b/pkg/microservice/policy/core/repository/models/common.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models type SubjectKind string diff --git a/pkg/microservice/policy/core/service/common.go b/pkg/microservice/policy/core/service/common.go index 8381d540..fbc17552 100644 --- a/pkg/microservice/policy/core/service/common.go +++ b/pkg/microservice/policy/core/service/common.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index 91a531f7..903d042d 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gin import ( -- Gitee From 57d2a6ce11081482dfe5a34b38d261fe469f6b37 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Mar 2022 17:19:26 +0800 Subject: [PATCH 1823/2647] import orders Signed-off-by: Min Min --- .../aslan/core/common/service/product.go | 11 ++++++----- pkg/shared/kube/client/version.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index edcba23c..a5c067cf 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -22,6 +22,12 @@ import ( "time" "github.com/hashicorp/go-multierror" + helmclient "github.com/mittwald/go-helm-client" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -34,11 +40,6 @@ import ( helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/updater" - helmclient "github.com/mittwald/go-helm-client" - "go.uber.org/zap" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/sets" ) const ( diff --git a/pkg/shared/kube/client/version.go b/pkg/shared/kube/client/version.go index 89fcfbe7..107f475a 100644 --- a/pkg/shared/kube/client/version.go +++ b/pkg/shared/kube/client/version.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client import ( -- Gitee From 9eee6f3dd01230d6107e71a7f4b39593debac00f Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Mar 2022 17:21:29 +0800 Subject: [PATCH 1824/2647] minor bugfix Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index cb10ce00..65b236aa 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -334,6 +334,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p for _, ingress := range ingresses { hostInfos = append(hostInfos, wrapper.GetIngressHostInfo(ingress)...) } + } else { log.Warnf("Failed to list ingresses, the error is: %s", err) } } -- Gitee From 174656e0ff8ae831951fce636710dc1c134e5ce4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 14 Mar 2022 17:29:31 +0800 Subject: [PATCH 1825/2647] update copyright Signed-off-by: Min Min --- pkg/shared/kube/client/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/kube/client/version.go b/pkg/shared/kube/client/version.go index 107f475a..e4a5639a 100644 --- a/pkg/shared/kube/client/version.go +++ b/pkg/shared/kube/client/version.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 34186ce57fe7b7fd43e301e8a61416d5d92b657e Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 14 Mar 2022 17:35:24 +0800 Subject: [PATCH 1826/2647] supports the deployment of partially defined services in the environment Signed-off-by: panxunying --- .../aslan/core/common/service/render.go | 16 ++++---- .../core/environment/handler/environment.go | 39 ++++++++----------- .../core/environment/service/environment.go | 15 +++++-- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 37c02115..12659fc3 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -183,10 +183,10 @@ func ValidateRenderSet(productName, renderName string, serviceInfo *templatemode return resp, fmt.Errorf("renderset[%s] not match product[%s]", renderName, productName) } if serviceInfo == nil { - if err := IsAllKeyCovered(resp, log); err != nil { - log.Errorf("[%s]cover all key [%s] error: %v", productName, renderName, err) - return resp, err - } + //if err := IsAllKeyCovered(resp, log); err != nil { + // log.Errorf("[%s]cover all key [%s] error: %v", productName, renderName, err) + // return resp, err + //} } else { // 单个服务是否全覆盖判断 if err := IsAllKeyCoveredService(serviceInfo.Owner, serviceInfo.Name, resp, log); err != nil { @@ -532,10 +532,10 @@ func ensureRenderSetArgs(args *commonmodels.RenderSet) error { if len(args.Name) == 0 { return errors.New("empty render set name") } - log := log.SugaredLogger() - if err := IsAllKeyCovered(args, log); err != nil { - return fmt.Errorf("[RenderSet.Create] %s error: %v", args.Name, err) - } + //log := log.SugaredLogger() + //if err := IsAllKeyCovered(args, log); err != nil { + // return fmt.Errorf("[RenderSet.Create] %s error: %v", args.Name, err) + //} // 设置新的版本号 rev, err := commonrepo.NewCounterColl().GetNextSeq("renderset:" + args.Name) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 4f354683..ce0cb653 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -40,13 +40,6 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) -type UpdateEnvs struct { - EnvNames []string `json:"env_names"` - ServiceNames []string `json:"service_names"` - UpdateType string `json:"update_type"` - Vars []*template.RenderKV `json:"vars,omitempty"` -} - type DeleteProductServicesRequest struct { ServiceNames []string `json:"service_names"` } @@ -90,37 +83,32 @@ func UpdateMultiProducts(c *gin.Context) { updateMultiHelmEnv(c, ctx) return } - - args := new(UpdateEnvs) + args := make([]*service.UpdateEnv, 0) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateMultiProducts c.GetRawData() err : %v", err) } - if err = json.Unmarshal(data, args); err != nil { + if err = json.Unmarshal(data, &args); err != nil { log.Errorf("UpdateMultiProducts json.Unmarshal err : %v", err) } - + var envNames []string + for _, arg := range args { + envNames = append(envNames, arg.EnvName) + } allowedEnvs, found := internalhandler.GetResourcesInHeader(c) if found { allowedSet := sets.NewString(allowedEnvs...) - currentSet := sets.NewString(args.EnvNames...) + currentSet := sets.NewString(envNames...) if !allowedSet.IsSuperset(currentSet) { c.String(http.StatusForbidden, "not all input envs are allowed, allowed envs are %v", allowedEnvs) return } } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.BindJSON(args); err != nil { - ctx.Logger.Error(err) - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "环境", strings.Join(envNames, ","), string(data), ctx.Logger) force, _ := strconv.ParseBool(c.Query("force")) - ctx.Resp, ctx.Err = service.AutoUpdateProduct(args.EnvNames, args.ServiceNames, c.Query("projectName"), ctx.RequestID, force, args.Vars, ctx.Logger) + ctx.Resp, ctx.Err = service.AutoUpdateProduct(args, envNames, c.Query("projectName"), ctx.RequestID, force, ctx.Logger) } func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { @@ -248,6 +236,11 @@ func CreateProduct(c *gin.Context) { ) } +type UpdateProductParams struct { + ServiceNames []string `json:"service_names"` + commonmodels.Product +} + func UpdateProduct(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -255,7 +248,7 @@ func UpdateProduct(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") - args := new(commonmodels.Product) + args := new(UpdateProductParams) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateProduct c.GetRawData() err : %v", err) @@ -275,7 +268,7 @@ func UpdateProduct(c *gin.Context) { force, _ := strconv.ParseBool(c.Query("force")) // update product asynchronously - ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, []string{}, force, args.Vars, ctx.Logger) + ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, args.ServiceNames, force, args.Vars, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product %s %s: %v", envName, projectName, ctx.Err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 98814b77..0526d082 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,6 +46,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" @@ -296,7 +298,14 @@ func AutoCreateProduct(productName, envType, requestID string, log *zap.SugaredL var mutexAutoUpdate sync.RWMutex -func AutoUpdateProduct(envNames, serviceNames []string, productName, requestID string, force bool, kvs []*templatemodels.RenderKV, log *zap.SugaredLogger) ([]*EnvStatus, error) { +type UpdateEnv struct { + EnvName string `json:"env_name"` + ServiceNames []string `json:"service_names"` + UpdateType string `json:"update_type,omitempty"` + Vars []*template.RenderKV `json:"vars,omitempty"` +} + +func AutoUpdateProduct(args []*UpdateEnv, envNames []string, productName, requestID string, force bool, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexAutoUpdate.Lock() defer func() { mutexAutoUpdate.Unlock() @@ -353,8 +362,8 @@ func AutoUpdateProduct(envNames, serviceNames []string, productName, requestID s } } - for envName := range productMap { - err = UpdateProductV2(envName, productName, setting.SystemUser, requestID, serviceNames, false, kvs, log) + for _, arg := range args { + err = UpdateProductV2(arg.EnvName, productName, setting.SystemUser, requestID, arg.ServiceNames, false, arg.Vars, log) if err != nil { log.Errorf("AutoUpdateProduct UpdateProductV2 err:%v", err) return envStatuses, err -- Gitee From 3b38e1150e0533a71d9464ecdbf080e93c9c094b Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 17:43:16 +0800 Subject: [PATCH 1827/2647] useless type Signed-off-by: mouuii --- .../aslan/core/code/service/types.go | 113 ------------------ 1 file changed, 113 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/types.go b/pkg/microservice/aslan/core/code/service/types.go index 81528aaa..f925d129 100644 --- a/pkg/microservice/aslan/core/code/service/types.go +++ b/pkg/microservice/aslan/core/code/service/types.go @@ -70,82 +70,6 @@ type Tag struct { Message string `json:"message"` } -func ToBranches(obj interface{}) []*Branch { - var res []*Branch - - switch os := obj.(type) { - case []*github.Branch: - for _, o := range os { - res = append(res, &Branch{ - Name: o.GetName(), - Protected: o.GetProtected(), - }) - } - case []*gitlab.Branch: - for _, o := range os { - res = append(res, &Branch{ - Name: o.Name, - Protected: o.Protected, - Merged: o.Merged, - }) - } - case []string: - for _, o := range os { - res = append(res, &Branch{ - Name: o, - }) - } - case []*codehub.Branch: - for _, o := range os { - res = append(res, &Branch{ - Name: o.Name, - Protected: o.Protected, - Merged: o.Merged, - }) - } - } - - return res -} - -func ToPullRequests(obj interface{}) []*PullRequest { - var res []*PullRequest - - switch os := obj.(type) { - case []*github.PullRequest: - for _, o := range os { - res = append(res, &PullRequest{ - ID: o.GetNumber(), - CreatedAt: o.GetCreatedAt().Unix(), - UpdatedAt: o.GetUpdatedAt().Unix(), - State: o.GetState(), - User: o.GetUser().GetLogin(), - Number: o.GetNumber(), - AuthorUsername: o.GetUser().GetLogin(), - Title: o.GetTitle(), - SourceBranch: o.GetHead().GetRef(), - TargetBranch: o.GetBase().GetRef(), - }) - } - case []*gitlab.MergeRequest: - for _, o := range os { - res = append(res, &PullRequest{ - ID: o.IID, - TargetBranch: o.TargetBranch, - SourceBranch: o.SourceBranch, - ProjectID: o.ProjectID, - Title: o.Title, - State: o.State, - CreatedAt: o.CreatedAt.Unix(), - UpdatedAt: o.UpdatedAt.Unix(), - AuthorUsername: o.Author.Username, - }) - } - } - - return res -} - func ToNamespaces(obj interface{}) []*Namespace { var res []*Namespace @@ -242,40 +166,3 @@ func ToProjects(obj interface{}) []*Project { return res } - -func ToTags(obj interface{}) []*Tag { - var res []*Tag - - switch os := obj.(type) { - case []*github.RepositoryTag: - for _, o := range os { - res = append(res, &Tag{ - Name: o.GetName(), - ZipballURL: o.GetZipballURL(), - TarballURL: o.GetTarballURL(), - }) - } - case []*gitlab.Tag: - for _, o := range os { - res = append(res, &Tag{ - Name: o.Name, - Message: o.Message, - }) - } - case []*gerrit.TagInfo: - for _, o := range os { - res = append(res, &Tag{ - Name: o.Ref, - Message: o.Message, - }) - } - case []*codehub.Tag: - for _, o := range os { - res = append(res, &Tag{ - Name: o.Name, - }) - } - } - - return res -} -- Gitee From c2922a16ab45bb8fe369e0f8c40ab5a8c33f79a4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 17:54:55 +0800 Subject: [PATCH 1828/2647] optimize workflow logic Signed-off-by: liu deyi --- .../core/common/repository/models/task/model.go | 1 + .../service/workflow/pipeline_validation.go | 11 ++++++++--- .../workflow/service/workflow/workflow_task.go | 16 ++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index ccdb9c16..76609100 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -170,4 +170,5 @@ type TaskOpt struct { ServiceName string `json:"service_name"` ServiceInfos *[]*ServiceInfo `json:"service_infos"` IsWorkflowTask bool `json:"is_workflow_task"` + ImageName string `json:"image_name"` } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 5013af75..0b41dc7a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -490,14 +490,18 @@ func setManunalBuilds(builds []*types.Repository, buildArgs []*types.Repository, // releaseCandidate 根据 TaskID 生成编译镜像Tag或者二进制包后缀 // TODO: max length of a tag is 128 -func releaseCandidate(b *task.Build, taskID int64, productName, envName, deliveryType string) string { +func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageName, deliveryType string) string { timeStamp := time.Now().Format("20060102150405") + + if imageName == "" { + imageName = b.ServiceName + } if len(b.JobCtx.Builds) == 0 { switch deliveryType { case config.TarResourceType: return fmt.Sprintf("%s-%s", b.ServiceName, timeStamp) default: - return fmt.Sprintf("%s:%s", b.ServiceName, timeStamp) + return fmt.Sprintf("%s:%s", imageName, timeStamp) } } @@ -541,6 +545,7 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, deliver } return newTarRule default: + candidate.ServiceName = imageName return replaceVariable(customImageRule, candidate) } } @@ -587,7 +592,7 @@ func replaceVariable(customRule *template.CustomRule, candidate *candidate) stri } currentRule = commonservice.ReplaceRuleVariable(currentRule, &commonservice.Variable{ - candidate.ServiceName, candidate.Timestamp, strconv.FormatInt(candidate.TaskID, 10), candidate.CommitID, candidate.ProductName, candidate.EnvName, + candidate.ServiceName, candidate.ServiceName, candidate.Timestamp, strconv.FormatInt(candidate.TaskID, 10), candidate.CommitID, candidate.ProductName, candidate.EnvName, candidate.Tag, candidate.Branch, strconv.Itoa(candidate.PR), }) return currentRule diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f9db6802..dc9352ed 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -75,7 +75,7 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr return resp, e.ErrListBuildModule.AddDesc(err.Error()) } - targetMap,_ := getProductTargetMap(product) + targetMap, _ := getProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) targets := make([]*commonmodels.TargetArgs, 0) for _, container := range projectTargets { @@ -364,7 +364,7 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) return resp, e.ErrListTestModule.AddDesc(err.Error()) } - targetMap,imageNameM := getProductTargetMap(product) + targetMap, imageNameM := getProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) hideServiceModules := getHideServiceModules(workflow) targets := make([]*commonmodels.TargetArgs, 0) @@ -386,9 +386,9 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) ServiceName: containerArr[1], ProductName: containerArr[0], Deploy: targetMap[container], - ImageName: imageNameM[container], - Build: &commonmodels.BuildArgs{}, - HasBuild: true, + ImageName: imageNameM[container], + Build: &commonmodels.BuildArgs{}, + HasBuild: true, } moBuild := findModuleByTargetAndVersion(allModules, container) if moBuild == nil { @@ -699,6 +699,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, ServiceName: target.ServiceName, ServiceInfos: &serviceInfos, IsWorkflowTask: true, + ImageName: target.ImageName, }, log); err != nil { log.Errorf("workflow_task ensurePipelineTask taskID:[%d] pipelineName:[%s] err:%v", task.ID, task.PipelineName, err) if err, ok := err.(*ContainerNotFound); ok { @@ -2204,8 +2205,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err return e.ErrFindRegistry.AddDesc(err.Error()) } } - - t.JobCtx.Image = GetImage(reg, releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "image")) + t.JobCtx.Image = GetImage(reg, releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, taskOpt.ImageName, "image")) taskOpt.Task.TaskArgs.Deploy.Image = t.JobCtx.Image if taskOpt.ServiceName != "" { @@ -2226,7 +2226,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err // 二进制文件名称 // 编译任务使用 t.JobCtx.PackageFile // 注意: 其他任务从 pt.TaskArgs.Deploy.PackageFile 获取, 必须要有编译任务 - t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, "tar")) + t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, taskOpt.ImageName, "tar")) taskOpt.Task.TaskArgs.Deploy.PackageFile = t.JobCtx.PackageFile // 注入编译模块中用户定义环境变量 -- Gitee From 19743e48093eeb21dd20152eda9b69eabc8407ec Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 18:00:01 +0800 Subject: [PATCH 1829/2647] optimize workflow logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/project/service/product.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index f343b6f6..371c4251 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -323,7 +323,7 @@ func validateCommonRule(currentRule, ruleType, deliveryType string) error { } currentRule = commonservice.ReplaceRuleVariable(currentRule, &commonservice.Variable{ - "ss", "ss", "ss", "ss", "ss", "ss", "ss", "ss", "ss", + "ss", "ss", "ss", "ss", "ss", "ss", "ss", "ss", "ss", "ss", }) switch deliveryType { case config.ImageResourceType: -- Gitee From 8bfb8bc14ec75ae8bb274d3781d46692827b6833 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 14 Mar 2022 18:36:25 +0800 Subject: [PATCH 1830/2647] chinese Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/service/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index a8811811..7c77a7b2 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -38,7 +38,7 @@ type GitRepoInfo struct { CodehostID int `json:"codehost_id"` Source string `json:"source"` DefaultBranch string `json:"default_branch"` - ErrorMsg string `json:"error_msg"` // repo信息是否拉取成功 + ErrorMsg string `json:"error_msg"` // get repo message fail message Branches []*client.Branch `json:"branches"` Tags []*client.Tag `json:"tags"` PRs []*client.PullRequest `json:"prs"` -- Gitee From 9c7dbf9938536ca6b76e464ee12139faead5dfce Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 20:01:23 +0800 Subject: [PATCH 1831/2647] Modify the update logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/environment/service/revision.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index b73e8791..1c282fb0 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -239,8 +239,9 @@ func compareServicesRev(serviceTmplNames []string, services []*commonmodels.Prod serviceRev.Containers = make([]*commonmodels.Container, 0) for _, container := range serviceTmpl.Containers { serviceRev.Containers = append(serviceRev.Containers, &commonmodels.Container{ - Image: container.Image, - Name: container.Name, + Image: container.Image, + Name: container.Name, + ImageName: container.ImageName, }) } } -- Gitee From 5a1d3755082ff31830e50ea307ebdf4e3777aa2a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 20:10:12 +0800 Subject: [PATCH 1832/2647] Modify the update logic Signed-off-by: liu deyi --- pkg/microservice/aslan/core/environment/service/revision.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 1c282fb0..48e38310 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -298,8 +298,9 @@ func compareServicesRev(serviceTmplNames []string, services []*commonmodels.Prod for _, container := range maxServiceTmpl.Containers { c := &commonmodels.Container{ - Image: container.Image, - Name: container.Name, + Image: container.Image, + Name: container.Name, + ImageName: container.ImageName, } // reuse existed container image -- Gitee From bcf77ca5b5e7aa8f652bde2f2cd5fc24a58f33f6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 21:06:51 +0800 Subject: [PATCH 1833/2647] Modify the ops workflow Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/repository/models/workflow.go | 1 + pkg/microservice/cron/core/service/types.go | 1 + pkg/microservice/warpdrive/core/service/types/task/model.go | 1 + 3 files changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 7b15e761..b7693af7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -370,6 +370,7 @@ type DeployEnv struct { type ArtifactArgs struct { Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name,omitempty" json:"image_name,omitempty"` ServiceName string `bson:"service_name" json:"service_name"` Image string `bson:"image,omitempty" json:"image,omitempty"` Deploy []DeployEnv `bson:"deploy" json:"deploy"` diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 50a81cbf..85a3369c 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -317,6 +317,7 @@ type BuildArgs struct { type ArtifactArgs struct { Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name,omitempty" json:"image_name,omitempty"` ServiceName string `bson:"service_name" json:"service_name"` Image string `bson:"image" json:"image"` Deploy []DeployEnv `bson:"deloy" json:"deploy"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 28f3c3ef..c8e7e894 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -179,6 +179,7 @@ type TestArgs struct { type ArtifactArgs struct { Name string `bson:"name" json:"name"` + ImageName string `bson:"image_name,omitempty" json:"image_name,omitempty"` ServiceName string `bson:"service_name" json:"service_name"` Image string `bson:"image" json:"image"` Deploy []DeployEnv `bson:"deploy" json:"deploy"` -- Gitee From c3ed60d91e4a3122b8829a4ae9e4b8eed58798b4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 14 Mar 2022 21:27:05 +0800 Subject: [PATCH 1834/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/service/service/service.go | 14 ++------------ pkg/shared/kube/wrapper/deployment.go | 15 ++------------- pkg/util/yaml.go | 10 ++++++++++ 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 988987b4..b7f2fcf0 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1316,7 +1316,7 @@ func getContainers(data string) ([]*commonmodels.Container, error) { container := &commonmodels.Container{ Name: nameStr, Image: imageStr, - ImageName: getImageName(imageStr), + ImageName: util.GetImageName(imageStr), } containers = append(containers, container) @@ -1356,7 +1356,7 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { container := &commonmodels.Container{ Name: nameStr, Image: imageStr, - ImageName: getImageName(imageStr), + ImageName: util.GetImageName(imageStr), } containers = append(containers, container) @@ -1365,16 +1365,6 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { return containers, nil } -func getImageName(image string) string { - imageNameStr := "" - imageArr := strings.Split(image, ":") - if len(imageArr) > 0 { - imageNameArr := strings.Split(imageArr[0], "/") - imageNameStr = imageNameArr[len(imageNameArr)-1] - } - return imageNameStr -} - func updateGerritWebhookByService(lastService, currentService *commonmodels.Service) error { var codehostDetail *systemconfig.CodeHost var err error diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 5da26c11..7892251c 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -17,13 +17,12 @@ limitations under the License. package wrapper import ( - "strings" - appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/kube/resource" + "github.com/koderover/zadig/pkg/util" ) // deployment is the wrapper for appsv1.Deployment type. @@ -59,7 +58,7 @@ func (w *deployment) WorkloadResource(pods []*corev1.Pod) *resource.Workload { } for _, c := range w.Spec.Template.Spec.Containers { - wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image, ImageName: getImageName(c.Image)}) + wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image, ImageName: util.GetImageName(c.Image)}) } for _, p := range pods { @@ -94,13 +93,3 @@ func (w *deployment) GetContainers() []*resource.ContainerImage { } return containers } - -func getImageName(image string) string { - imageNameStr := "" - imageArr := strings.Split(image, ":") - if len(imageArr) > 0 { - imageNameArr := strings.Split(imageArr[0], "/") - imageNameStr = imageNameArr[len(imageNameArr)-1] - } - return imageNameStr -} diff --git a/pkg/util/yaml.go b/pkg/util/yaml.go index 99c1f684..8a4d66a0 100644 --- a/pkg/util/yaml.go +++ b/pkg/util/yaml.go @@ -42,3 +42,13 @@ func SplitManifests(content string) []string { return res } + +func GetImageName(image string) string { + imageNameStr := "" + imageArr := strings.Split(image, ":") + if len(imageArr) > 0 { + imageNameArr := strings.Split(imageArr[0], "/") + imageNameStr = imageNameArr[len(imageNameArr)-1] + } + return imageNameStr +} -- Gitee From e30fe0a6f04cfb8e70e8830b335b77a78c8186a1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Mar 2022 10:13:32 +0800 Subject: [PATCH 1835/2647] add function annotation Signed-off-by: liu deyi --- pkg/util/yaml.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/util/yaml.go b/pkg/util/yaml.go index 8a4d66a0..c56afc76 100644 --- a/pkg/util/yaml.go +++ b/pkg/util/yaml.go @@ -43,6 +43,8 @@ func SplitManifests(content string) []string { return res } +// image ccr.ccs.tencentyun.com/shuhe/nginx:stable +// return nginx func GetImageName(image string) string { imageNameStr := "" imageArr := strings.Split(image, ":") -- Gitee From 8e17f6649cdd2ed13ad1ea32a4d787f507d87179 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Mar 2022 10:16:25 +0800 Subject: [PATCH 1836/2647] modify function annotation Signed-off-by: liu deyi --- pkg/util/yaml.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/yaml.go b/pkg/util/yaml.go index c56afc76..f9689385 100644 --- a/pkg/util/yaml.go +++ b/pkg/util/yaml.go @@ -43,7 +43,7 @@ func SplitManifests(content string) []string { return res } -// image ccr.ccs.tencentyun.com/shuhe/nginx:stable +// image ccr.ccs.tencentyun.com/koderover/nginx:stable // return nginx func GetImageName(image string) string { imageNameStr := "" -- Gitee From d4f4d0a8a72e48949e92f06fc398c0e23f59f07a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Mar 2022 10:56:57 +0800 Subject: [PATCH 1837/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/environment/service/product.go | 7 ++----- .../service/workflow/pipeline_validation.go | 1 + .../service/workflow/workflow_task.go | 18 +++--------------- pkg/util/strings.go | 19 +++++++++++++++++++ pkg/util/yaml.go | 12 ------------ 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 650ad3ce..2de44565 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -34,6 +34,7 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" ) var DefaultCleanWhiteList = []string{"spockadmin"} @@ -144,15 +145,11 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode if serviceTmpl.Type == setting.K8SDeployType || serviceTmpl.Type == setting.HelmDeployType { serviceResp.Containers = make([]*commonmodels.Container, 0) for _, c := range serviceTmpl.Containers { - imageName := c.ImageName - if imageName == "" { - imageName = c.Name - } container := &commonmodels.Container{ Name: c.Name, Image: c.Image, ImagePath: c.ImagePath, - ImageName: imageName, + ImageName: util.GetImageNameFromContainerInfo(c.ImageName, c.Name), } serviceResp.Containers = append(serviceResp.Containers, container) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 0b41dc7a..8db28da0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -545,6 +545,7 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageNa } return newTarRule default: + // If image, replace service name with imageName candidate.ServiceName = imageName return replaceVariable(customImageRule, candidate) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index dc9352ed..6ca25993 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -162,11 +162,7 @@ func getProductTargetMap(prod *commonmodels.Product) (map[string][]commonmodels. target := strings.Join([]string{service.ProductName, service.ServiceName, container.Name}, SplitSymbol) resp[target] = append(resp[target], deployEnv) - imageName := container.ImageName - if imageName == "" { - imageName = container.Name - } - imageNameM[target] = imageName + imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) } } return resp, imageNameM @@ -181,11 +177,7 @@ func getProductTargetMap(prod *commonmodels.Product) (map[string][]commonmodels. target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) resp[target] = append(resp[target], deployEnv) - imageName := container.ImageName - if imageName == "" { - imageName = container.Name - } - imageNameM[target] = imageName + imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) } case setting.PMDeployType: deployEnv := commonmodels.DeployEnv{Type: setting.PMDeployType, Env: serviceObj.ServiceName} @@ -198,11 +190,7 @@ func getProductTargetMap(prod *commonmodels.Product) (map[string][]commonmodels. target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) resp[target] = append(resp[target], deployEnv) - imageName := container.ImageName - if imageName == "" { - imageName = container.Name - } - imageNameM[target] = imageName + imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) } } } diff --git a/pkg/util/strings.go b/pkg/util/strings.go index 91438a43..e1495602 100644 --- a/pkg/util/strings.go +++ b/pkg/util/strings.go @@ -36,3 +36,22 @@ func ReplaceWrapLine(script string) string { -1, ), "\r", "\n", -1) } + +// image ccr.ccs.tencentyun.com/koderover/nginx:stable +// return nginx +func GetImageName(image string) string { + imageNameStr := "" + imageArr := strings.Split(image, ":") + if len(imageArr) > 0 { + imageNameArr := strings.Split(imageArr[0], "/") + imageNameStr = imageNameArr[len(imageNameArr)-1] + } + return imageNameStr +} + +func GetImageNameFromContainerInfo(imageName, containerName string) string { + if imageName == "" { + return containerName + } + return imageName +} diff --git a/pkg/util/yaml.go b/pkg/util/yaml.go index f9689385..99c1f684 100644 --- a/pkg/util/yaml.go +++ b/pkg/util/yaml.go @@ -42,15 +42,3 @@ func SplitManifests(content string) []string { return res } - -// image ccr.ccs.tencentyun.com/koderover/nginx:stable -// return nginx -func GetImageName(image string) string { - imageNameStr := "" - imageArr := strings.Split(image, ":") - if len(imageArr) > 0 { - imageNameArr := strings.Split(imageArr[0], "/") - imageNameStr = imageNameArr[len(imageNameArr)-1] - } - return imageNameStr -} -- Gitee From a36ea7e4d6b8500bda15bb715d21bccd52434feb Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 15 Mar 2022 11:19:13 +0800 Subject: [PATCH 1838/2647] remove log Signed-off-by: ddh27 --- .../aslan/core/common/service/instantmessage/service.go | 2 +- pkg/microservice/reaper/core/service/reaper/ginkgo.go | 1 - pkg/microservice/reaper/core/service/reaper/jmeter.go | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index db3f5778..92514dcb 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -327,7 +327,7 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if buildSt.BuildStatus.Status == "" { buildSt.BuildStatus.Status = config.StatusNotRun } - buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**服务名称**:%s \n", buildSt.ServiceName) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**服务名称**:%s \n", buildSt.Service) buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.JobCtx.Image) buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:[%s-%s %s](%s) \n", branchTagType, branchTag, commitID, gitCommitURL) buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**提交信息**:%s \n", commitMsg) diff --git a/pkg/microservice/reaper/core/service/reaper/ginkgo.go b/pkg/microservice/reaper/core/service/reaper/ginkgo.go index 30c34682..bff8b026 100644 --- a/pkg/microservice/reaper/core/service/reaper/ginkgo.go +++ b/pkg/microservice/reaper/core/service/reaper/ginkgo.go @@ -49,7 +49,6 @@ func mergeGinkgoTestResults(testResultFile, testResultPath, testUploadPath strin ) if len(testResultPath) == 0 { - log.Warning("merge test result step skipped: no test result path found") return nil } diff --git a/pkg/microservice/reaper/core/service/reaper/jmeter.go b/pkg/microservice/reaper/core/service/reaper/jmeter.go index d7b4649b..54511c87 100644 --- a/pkg/microservice/reaper/core/service/reaper/jmeter.go +++ b/pkg/microservice/reaper/core/service/reaper/jmeter.go @@ -31,7 +31,6 @@ import ( func JmeterTestResults(testResultFile, testResultPath, testUploadPath string) error { var err error if len(testResultPath) == 0 { - log.Warning("jmeter test result step skipped: no test result path found") return nil } -- Gitee From 13b4253fe0f890e366e3c722b062f67cc739020f Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 15 Mar 2022 11:46:55 +0800 Subject: [PATCH 1839/2647] supports the deployment of partially defined services in the environment Signed-off-by: panxunying --- .../common/repository/mongodb/render_set.go | 1 + .../aslan/core/common/service/render.go | 25 +++++++++++ .../core/environment/handler/environment.go | 16 ++++++- .../core/environment/service/environment.go | 45 ++++++++++++++----- pkg/util/array.go | 2 +- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go index e52d02ed..3f683efd 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go @@ -233,6 +233,7 @@ func (c *RenderSetColl) Update(args *models.RenderSet) error { "chart_infos": args.ChartInfos, "update_time": time.Now().Unix(), "update_by": args.UpdateBy, + "kvs": args.KVs, }} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 12659fc3..b993c67e 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -197,6 +197,29 @@ func ValidateRenderSet(productName, renderName string, serviceInfo *templatemode return resp, nil } +func mergeKVs(newKVs []*templatemodels.RenderKV, oldKVs []*templatemodels.RenderKV) []*templatemodels.RenderKV { + var result []*templatemodels.RenderKV + newKVsMap := make(map[string]*templatemodels.RenderKV) + for _, v := range newKVs { + newKVsMap[v.Key] = v + } + oldKVsMap := make(map[string]*templatemodels.RenderKV) + for _, v := range oldKVs { + oldKVsMap[v.Key] = v + if newKV, ok := newKVsMap[v.Key]; ok { + result = append(result, newKV) + continue + } + result = append(result, v) + } + for _, v := range newKVs { + if _, ok := oldKVsMap[v.Key]; !ok { + result = append(result, v) + } + } + return result +} + func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.RenderSetFindOption{Name: args.Name} rs, err := commonrepo.NewRenderSetColl().Find(opt) @@ -208,6 +231,8 @@ func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error } else { return nil } + mergedKVs := mergeKVs(args.KVs, rs.KVs) + args.KVs = mergedKVs } if err := ensureRenderSetArgs(args); err != nil { log.Error(err) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index ce0cb653..c47b6e9e 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -87,9 +87,13 @@ func UpdateMultiProducts(c *gin.Context) { data, err := c.GetRawData() if err != nil { log.Errorf("UpdateMultiProducts c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } if err = json.Unmarshal(data, &args); err != nil { log.Errorf("UpdateMultiProducts json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } var envNames []string for _, arg := range args { @@ -267,8 +271,14 @@ func UpdateProduct(c *gin.Context) { } force, _ := strconv.ParseBool(c.Query("force")) + serviceNames := sets.String{} + for _, kv := range args.Vars { + for _, s := range kv.Services { + serviceNames.Insert(s) + } + } // update product asynchronously - ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, args.ServiceNames, force, args.Vars, ctx.Logger) + ctx.Err = service.UpdateProductV2(envName, projectName, ctx.UserName, ctx.RequestID, serviceNames.List(), force, args.Vars, ctx.Logger) if ctx.Err != nil { ctx.Logger.Errorf("failed to update product %s %s: %v", envName, projectName, ctx.Err) } @@ -497,9 +507,13 @@ func DeleteProductServices(c *gin.Context) { data, err := c.GetRawData() if err != nil { log.Errorf("DeleteProductServices c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } if err = json.Unmarshal(data, args); err != nil { log.Errorf("DeleteProductServices json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddErr(err) + return } projectName := c.Query("projectName") envName := c.Param("name") diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 0526d082..f7ad80b7 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -491,7 +491,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. // 1. 如果服务待删除:将产品模板中已经不存在,产品环境中待删除的服务进行删除。 for _, serviceRev := range prodRevs.ServiceRevisions { - if serviceRev.Updatable && serviceRev.Deleted { + if serviceRev.Updatable && serviceRev.Deleted && util.InStringArray(serviceRev.ServiceName, serviceNames) { log.Infof("[%s][P:%s][S:%s] start to delete service", envName, productName, serviceRev.ServiceName) //根据namespace: EnvName, selector: productName + serviceName来删除属于该服务的所有资源 selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceRev.ServiceName}.AsSelector() @@ -605,21 +605,20 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. err = e.ErrUpdateEnv.AddDesc(err.Error()) return } + //merge new and old services var updateGroup []*commonmodels.ProductService newServiceMap := make(map[string]*commonmodels.ProductService) for _, service := range groupServices { newServiceMap[service.ServiceName] = service } oldServiceMap := make(map[string]*commonmodels.ProductService) - for _, serviceGroup := range existedProd.Services { - for _, service := range serviceGroup { - oldServiceMap[service.ServiceName] = service - if newService, ok := newServiceMap[service.ServiceName]; ok { - updateGroup = append(updateGroup, newService) - continue - } - updateGroup = append(updateGroup, service) + for _, service := range existedProd.Services[groupIndex] { + oldServiceMap[service.ServiceName] = service + if newService, ok := newServiceMap[service.ServiceName]; ok { + updateGroup = append(updateGroup, newService) + continue } + updateGroup = append(updateGroup, service) } for _, newService := range groupServices { if _, ok := oldServiceMap[newService.ServiceName]; !ok { @@ -1724,7 +1723,7 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l for serviceGroupIndex, serviceGroup := range productInfo.Services { var group []*commonmodels.ProductService for _, service := range serviceGroup { - if !util.InArray(service.ServiceName, serviceNames) { + if !util.InStringArray(service.ServiceName, serviceNames) { group = append(group, service) } } @@ -1734,6 +1733,30 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l return err } } + rs, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + Name: productInfo.Namespace, + }) + if err != nil { + log.Errorf("get renderSet error: %v", err) + return err + } + var updatedKVs []*templatemodels.RenderKV + for _, v := range rs.KVs { + var updatedServices []string + for _, service := range v.Services { + if util.InStringArray(service, serviceNames) { + updatedServices = append(updatedServices, service) + } + } + v.Services = updatedServices + updatedKVs = append(updatedKVs, v) + } + rs.KVs = updatedKVs + err = commonrepo.NewRenderSetColl().Update(rs) + if err != nil { + log.Errorf("update renderSet error: %v", err) + return err + } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) @@ -2667,7 +2690,7 @@ func getUpdatedProductServices(serviceNames []string, updateProduct *commonmodel updatedGroups := make([]*commonmodels.ProductService, 0) for _, service := range group { serviceRevision, ok := serviceRevisionMap[service.ServiceName+service.Type] - if !ok || (len(serviceNames) > 0 && !util.InArray(service.ServiceName, serviceNames)) { + if !ok || (len(serviceNames) > 0 && !util.InStringArray(service.ServiceName, serviceNames)) { //找不到 service revision continue } diff --git a/pkg/util/array.go b/pkg/util/array.go index 18224706..dba05564 100644 --- a/pkg/util/array.go +++ b/pkg/util/array.go @@ -16,7 +16,7 @@ limitations under the License. package util -func InArray(target string, str_array []string) bool { +func InStringArray(target string, str_array []string) bool { for _, element := range str_array { if target == element { return true -- Gitee From f012cc727b502009eeaf750c8ea980efe24baf4b Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 15 Mar 2022 13:37:02 +0800 Subject: [PATCH 1840/2647] fix delete service but not update product Signed-off-by: panxunying --- .../aslan/core/environment/service/environment.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f7ad80b7..ce687a44 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -488,7 +488,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. // 遍历产品环境和产品模板交叉对比的结果 // 四个状态:待删除,待添加,待更新,无需更新 - + var deletedServices []string // 1. 如果服务待删除:将产品模板中已经不存在,产品环境中待删除的服务进行删除。 for _, serviceRev := range prodRevs.ServiceRevisions { if serviceRev.Updatable && serviceRev.Deleted && util.InStringArray(serviceRev.ServiceName, serviceNames) { @@ -500,7 +500,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. //删除失败仅记录失败日志 log.Errorf("delete resource of service %s error:%v", serviceRev.ServiceName, err) } - + deletedServices = append(deletedServices, serviceRev.ServiceName) clusterSelector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceRev.ServiceName, setting.EnvNameLabel: envName}.AsSelector() err = commonservice.DeleteClusterResourceAsync(clusterSelector, kubeClient, log) if err != nil { @@ -613,6 +613,9 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. } oldServiceMap := make(map[string]*commonmodels.ProductService) for _, service := range existedProd.Services[groupIndex] { + if util.InStringArray(service.ServiceName, deletedServices) { + continue + } oldServiceMap[service.ServiceName] = service if newService, ok := newServiceMap[service.ServiceName]; ok { updateGroup = append(updateGroup, newService) @@ -621,7 +624,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. updateGroup = append(updateGroup, service) } for _, newService := range groupServices { - if _, ok := oldServiceMap[newService.ServiceName]; !ok { + if _, ok := oldServiceMap[newService.ServiceName]; !ok && !util.InStringArray(newService.ServiceName, deletedServices) { updateGroup = append(updateGroup, newService) } } -- Gitee From f06a4769f8c600b2b30c3229a4ee4a70066527d3 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 15 Mar 2022 13:53:20 +0800 Subject: [PATCH 1841/2647] fix merge conflict Signed-off-by: panxunying --- .../aslan/core/environment/service/environment.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 134e4ee3..4e142939 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1760,13 +1760,9 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l log.Errorf("update renderSet error: %v", err) return err } - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) - if err != nil { - return e.ErrUpdateEnv.AddErr(err) - } for _, name := range serviceNames { selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: name}.AsSelector() - err = commonservice.DeleteResourcesAsync(productInfo.Namespace, selector, kubeClient, log) + err = commonservice.DeleteNamespacedResource(productInfo.Namespace, selector, productInfo.ClusterID, log) if err != nil { //删除失败仅记录失败日志 log.Errorf("delete resource of service %s error:%v", name, err) -- Gitee From 5b2fedfe695602274916d1da30326d0f5bd5b8ca Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Mar 2022 15:25:16 +0800 Subject: [PATCH 1842/2647] adding image_name field in helm scene Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/service.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index edcf503d..ec1f2105 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -683,8 +683,9 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s return nil, err } ret = append(ret, &commonmodels.Container{ - Name: ExtractImageName(imageUrl), - Image: imageUrl, + Name: ExtractImageName(imageUrl), + ImageName: ExtractImageName(imageUrl), + Image: imageUrl, ImagePath: &commonmodels.ImagePathSpec{ Repo: searchResult[setting.PathSearchComponentRepo], Image: searchResult[setting.PathSearchComponentImage], -- Gitee From 3fc03105e8bb77ed30c7006adea74ad5d7084798 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 15 Mar 2022 16:47:11 +0800 Subject: [PATCH 1843/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/service.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index ec1f2105..74fca01e 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -682,9 +682,10 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s if err != nil { return nil, err } + name := ExtractImageName(imageUrl) ret = append(ret, &commonmodels.Container{ - Name: ExtractImageName(imageUrl), - ImageName: ExtractImageName(imageUrl), + Name: name, + ImageName: name, Image: imageUrl, ImagePath: &commonmodels.ImagePathSpec{ Repo: searchResult[setting.PathSearchComponentRepo], -- Gitee From 86900fc42883b0efecbc600caa2934bff13ba273 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Mar 2022 17:30:17 +0800 Subject: [PATCH 1844/2647] field Signed-off-by: mouuii --- .../aslan/core/code/client/codehub/codehub.go | 15 +++------------ .../aslan/core/code/client/gerrit/gerrit.go | 17 ++++------------- .../aslan/core/code/client/github/github.go | 13 +------------ .../aslan/core/code/client/gitlab/gitlab.go | 9 --------- 4 files changed, 8 insertions(+), 46 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/codehub/codehub.go b/pkg/microservice/aslan/core/code/client/codehub/codehub.go index d1e30a34..26f0b889 100644 --- a/pkg/microservice/aslan/core/code/client/codehub/codehub.go +++ b/pkg/microservice/aslan/core/code/client/codehub/codehub.go @@ -25,19 +25,10 @@ import ( ) type Config struct { - ID int `json:"id"` - Address string `json:"address"` - Type string `json:"type"` - AccessToken string `json:"access_token"` - Namespace string `json:"namespace"` Region string `json:"region"` - // the field and tag not consistent because of db field - AccessKey string `json:"application_id"` - SecretKey string `json:"client_secret"` - Username string `json:"username"` - Password string `json:"password"` - // the field determine whether the proxy is enabled - EnableProxy bool `json:"enable_proxy"` + AccessKey string `json:"application_id"` + SecretKey string `json:"client_secret"` + EnableProxy bool `json:"enable_proxy"` } type Client struct { diff --git a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go index d3bfd25a..47a9f9ed 100644 --- a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go @@ -18,26 +18,17 @@ package gerrit import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" - "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/tool/gerrit" + + "go.uber.org/zap" ) type Config struct { - ID int `json:"id"` Address string `json:"address"` - Type string `json:"type"` AccessToken string `json:"access_token"` - Namespace string `json:"namespace"` - Region string `json:"region"` - // the field and tag not consistent because of db field - AccessKey string `json:"application_id"` - SecretKey string `json:"client_secret"` - Username string `json:"username"` - Password string `json:"password"` - // the field determine whether the proxy is enabled - EnableProxy bool `json:"enable_proxy"` + AccessKey string `json:"application_id"` + EnableProxy bool `json:"enable_proxy"` } type Client struct { diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index 73c6fd27..f1b3cb81 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -28,19 +28,8 @@ import ( ) type Config struct { - ID int `json:"id"` - Address string `json:"address"` - Type string `json:"type"` AccessToken string `json:"access_token"` - Namespace string `json:"namespace"` - Region string `json:"region"` - // the field and tag not consistent because of db field - AccessKey string `json:"application_id"` - SecretKey string `json:"client_secret"` - Username string `json:"username"` - Password string `json:"password"` - // the field determine whether the proxy is enabled - EnableProxy bool `json:"enable_proxy"` + EnableProxy bool `json:"enable_proxy"` } type Client struct { diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index 91687c0d..889dee85 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -25,17 +25,8 @@ import ( ) type Config struct { - ID int `json:"id"` Address string `json:"address"` - Type string `json:"type"` AccessToken string `json:"access_token"` - Namespace string `json:"namespace"` - Region string `json:"region"` - // the field and tag not consistent because of db field - AccessKey string `json:"application_id"` - SecretKey string `json:"client_secret"` - Username string `json:"username"` - Password string `json:"password"` // the field determine whether the proxy is enabled EnableProxy bool `json:"enable_proxy"` } -- Gitee From 66ef6c2c755b723eedf5e9f83ea1ab169fd36ced Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Mar 2022 17:37:03 +0800 Subject: [PATCH 1845/2647] log Signed-off-by: mouuii --- .../core/collaboration/service/collaboration_instance.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 387d7139..f0c94554 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -863,14 +863,14 @@ func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projec for _, product := range deleteResp.Products { err := commonservice.DeleteProduct(username, product, projectName, requestID, log) if err != nil && err != mongo.ErrNoDocuments { - log.Infof("%v", err != mongo.ErrNoDocuments) + log.Errorf("delete product err:%v", err) return err } } for _, workflow := range deleteResp.Workflows { err := commonservice.DeleteWorkflow(workflow, requestID, false, log) - log.Infof("%v", err != mongo.ErrNoDocuments) if err != nil && err != mongo.ErrNoDocuments { + log.Errorf("delete workflow err:%v", err) return err } } -- Gitee From 1cbf15067e9c4f656dc135b9645d92af9367d1c2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Mar 2022 17:55:26 +0800 Subject: [PATCH 1846/2647] log Signed-off-by: mouuii --- .../aslan/core/code/client/github/github.go | 1 - pkg/microservice/aslan/core/code/client/open/open.go | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index f1b3cb81..4a53668e 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -65,7 +65,6 @@ func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { } func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { - tags, err := c.Client.ListTags(context.TODO(), opt.Namespace, opt.ProjectName, nil) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 6b3d57d6..9165acd3 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -27,8 +27,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gerrit" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" ) type ClientConfig interface { @@ -36,16 +36,16 @@ type ClientConfig interface { } var ClientsConfig = map[string]func() ClientConfig{ - "gitlab": func() ClientConfig { return new(gitlab.Config) }, - "github": func() ClientConfig { return new(github.Config) }, - "gerrit": func() ClientConfig { return new(gerrit.Config) }, - "codehub": func() ClientConfig { return new(codehub.Config) }, + setting.SourceFromGitlab: func() ClientConfig { return new(gitlab.Config) }, + setting.SourceFromGithub: func() ClientConfig { return new(github.Config) }, + setting.SourceFromGerrit: func() ClientConfig { return new(gerrit.Config) }, + setting.SourceFromCodeHub: func() ClientConfig { return new(codehub.Config) }, } func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { - return nil, e.ErrCodehostListBranches.AddDesc("git client is nil") + return nil, err } var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] -- Gitee From 9355eb298de0e4a87d1cc19e6b2bf12b1e824043 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 15 Mar 2022 20:38:20 +0800 Subject: [PATCH 1847/2647] move repoOwner to query paramter Signed-off-by: allenshen --- .../aslan/core/code/handler/codehost.go | 14 +++++++------- pkg/microservice/aslan/core/code/handler/router.go | 12 +++++------- .../aslan/core/code/handler/workspace.go | 8 +------- .../aslan/core/service/handler/loader.go | 1 - .../aslan/core/service/handler/router.go | 3 --- 5 files changed, 13 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index b472adc7..0d021373 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -74,7 +74,7 @@ func CodeHostGetProjectsList(c *gin.Context) { namespaceType := c.DefaultQuery("type", "group") codehostID := c.Param("codehostId") - namespace := c.Param("namespace") + namespace := c.Query("namespace") if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") @@ -121,8 +121,8 @@ func CodeHostGetBranchList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Param("namespace") - projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id + namespace := c.Query("namespace") + projectName := c.Query("projectName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) @@ -158,8 +158,8 @@ func CodeHostGetTagList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Param("namespace") - projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id + namespace := c.Query("namespace") + projectName := c.Query("projectName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) @@ -187,8 +187,8 @@ func CodeHostGetPRList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Param("namespace") - projectName := c.Param("projectName") // pro Name, id/name -> gitlab = id + namespace := c.Query("namespace") + projectName := c.Query("projectName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 0fa6ecea..6522fe13 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -29,10 +29,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehost.GET("", GetCodeHostList) codehost.GET("/:codehostId/namespaces", CodeHostGetNamespaceList) - codehost.GET("/:codehostId/namespaces/:namespace/projects", CodeHostGetProjectsList) - codehost.GET("/:codehostId/namespaces/:namespace/projects/:projectName/branches", CodeHostGetBranchList) - codehost.GET("/:codehostId/namespaces/:namespace/projects/:projectName/tags", CodeHostGetTagList) - codehost.GET("/:codehostId/namespaces/:namespace/projects/:projectName/prs", CodeHostGetPRList) + codehost.GET("/:codehostId/namespaces/projects", CodeHostGetProjectsList) + codehost.GET("/:codehostId/namespaces/projects/branches", CodeHostGetBranchList) + codehost.GET("/:codehostId/namespaces/projects/tags", CodeHostGetTagList) + codehost.GET("/:codehostId/namespaces/projects/prs", CodeHostGetPRList) codehost.PUT("/infos", ListRepoInfos) codehost.POST("/branches/regular/check", MatchBranchesList) } @@ -44,11 +44,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { workspace.DELETE("", GetProductNameByWorkspacePipeline, gin2.UpdateOperationLogStatus, CleanWorkspace) workspace.GET("/file", GetWorkspaceFile) - //workspace.GET("/git/:codehostId/:repoName/:branchName/:remoteName", GetGitRepoInfo) - workspace.GET("/git/:codehostId/:repoName", GetGitRepoInfo) + workspace.GET("/git/:codehostId", GetGitRepoInfo) workspace.GET("/tree", GetRepoTree) - //workspace.GET("/codehub/:codehostId/:repoUUID/:branchName", GetCodehubRepoInfo) workspace.GET("/codehub/:codehostId/:repoUUID", GetCodehubRepoInfo) } diff --git a/pkg/microservice/aslan/core/code/handler/workspace.go b/pkg/microservice/aslan/core/code/handler/workspace.go index 4009a41d..f7c84558 100644 --- a/pkg/microservice/aslan/core/code/handler/workspace.go +++ b/pkg/microservice/aslan/core/code/handler/workspace.go @@ -17,7 +17,6 @@ limitations under the License. package handler import ( - "net/url" "strconv" "github.com/gin-gonic/gin" @@ -94,16 +93,11 @@ func GetGitRepoInfo(c *gin.Context) { return } - repoName := c.Param("repoName") + repoName := c.Query("repoName") if repoName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty repo name") return } - repoName, err = url.QueryUnescape(repoName) - if err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc("repoName decode error") - return - } branchName := c.Query("branchName") if branchName == "" { diff --git a/pkg/microservice/aslan/core/service/handler/loader.go b/pkg/microservice/aslan/core/service/handler/loader.go index 607b9099..812f5b70 100644 --- a/pkg/microservice/aslan/core/service/handler/loader.go +++ b/pkg/microservice/aslan/core/service/handler/loader.go @@ -108,7 +108,6 @@ func ValidateServiceUpdate(c *gin.Context) { return } - //branchName := c.Param("branchName") branchName := c.Query("branchName") path := c.Query("path") diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index bbfcad44..10009f08 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -70,11 +70,8 @@ func (*Router) Inject(router *gin.RouterGroup) { loader := router.Group("loader") { - //loader.GET("/preload/:codehostId/:branchName", PreloadServiceTemplate) loader.GET("/preload/:codehostId", PreloadServiceTemplate) - //loader.POST("/load/:codehostId/:branchName", LoadServiceTemplate) loader.POST("/load/:codehostId", LoadServiceTemplate) - //loader.GET("/validateUpdate/:codehostId/:branchName", ValidateServiceUpdate) loader.GET("/validateUpdate/:codehostId", ValidateServiceUpdate) } -- Gitee From f2a61b8814fb0c92ecf959aa2aed23125d664272 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 15 Mar 2022 20:40:03 +0800 Subject: [PATCH 1848/2647] namespace & project Signed-off-by: mouuii --- .../aslan/core/code/client/codehub/codehub.go | 37 ++++++ .../aslan/core/code/client/gerrit/gerrit.go | 28 +++++ .../aslan/core/code/client/github/github.go | 45 ++++++++ .../aslan/core/code/client/gitlab/gitlab.go | 55 +++++++++ .../aslan/core/code/client/interface.go | 38 ++++++- .../aslan/core/code/service/namespace.go | 63 +---------- .../aslan/core/code/service/project.go | 86 ++------------ .../aslan/core/code/service/types.go | 106 ------------------ 8 files changed, 215 insertions(+), 243 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/codehub/codehub.go b/pkg/microservice/aslan/core/code/client/codehub/codehub.go index 26f0b889..008b42d4 100644 --- a/pkg/microservice/aslan/core/code/client/codehub/codehub.go +++ b/pkg/microservice/aslan/core/code/client/codehub/codehub.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/tool/codehub" + e "github.com/koderover/zadig/pkg/tool/errors" ) type Config struct { @@ -75,3 +76,39 @@ func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { return nil, nil } + +func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { + nsList, err := c.Client.NamespaceList() + if err != nil { + return nil, err + } + var res []*client.Namespace + for _, o := range nsList { + res = append(res, &client.Namespace{ + Name: o.Name, + Path: o.Path, + Kind: o.Kind, + ProjectUUID: o.ProjectUUID, + }) + } + return res, nil +} + +func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { + projects, err := c.Client.RepoList(opt.Namespace, opt.Key, opt.PerPage) + if err != nil { + return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) + } + var res []*client.Project + for _, project := range projects { + res = append(res, &client.Project{ + Name: project.Name, + Description: project.Description, + DefaultBranch: project.DefaultBranch, + Namespace: project.Namespace, + RepoUUID: project.RepoUUID, + RepoID: project.RepoID, + }) + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go index 47a9f9ed..723643b8 100644 --- a/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go +++ b/pkg/microservice/aslan/core/code/client/gerrit/gerrit.go @@ -19,7 +19,9 @@ package gerrit import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" + gerrittool "github.com/koderover/zadig/pkg/tool/gerrit" "go.uber.org/zap" ) @@ -72,3 +74,29 @@ func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { return nil, nil } + +func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { + return []*client.Namespace{{ + Name: gerrit.DefaultNamespace, + Path: gerrit.DefaultNamespace, + Kind: client.OrgKind, + }}, nil +} + +func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { + projects, err := c.Client.ListProjectsByKey(opt.Key) + if err != nil { + return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) + } + var res []*client.Project + for ind, o := range projects { + res = append(res, &client.Project{ + ID: ind, // fake id + Name: gerrittool.Unescape(o.ID), // id could have %2F + Description: o.Description, + DefaultBranch: "master", + Namespace: gerrittool.DefaultNamespace, + }) + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index 4a53668e..b88f502e 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -104,3 +104,48 @@ func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { } return res, nil } + +func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { + user, err := c.Client.GetAuthenticatedUser(context.TODO()) + if err != nil { + return nil, err + } + namespaceUser := client.Namespace{ + Name: user.GetLogin(), + Path: user.GetLogin(), + Kind: client.UserKind, + } + + organizations, err := c.Client.ListOrganizationsForAuthenticatedUser(context.TODO(), nil) + if err != nil { + return nil, err + } + + var res []*client.Namespace + res = append(res, &namespaceUser) + for _, o := range organizations { + res = append(res, &client.Namespace{ + Name: o.GetLogin(), + Path: o.GetLogin(), + Kind: client.OrgKind, + }) + } + return res, nil +} + +func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { + repos, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO(), nil) + if err != nil { + return nil, err + } + var res []*client.Project + for _, o := range repos { + res = append(res, &client.Project{ + ID: int(o.GetID()), + Name: o.GetName(), + DefaultBranch: o.GetDefaultBranch(), + Namespace: o.GetOwner().GetLogin(), + }) + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index 889dee85..9cff88e3 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -21,7 +21,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/git/gitlab" + gogitlab "github.com/xanzy/go-gitlab" ) type Config struct { @@ -108,3 +110,56 @@ func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { } return res, nil } + +func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { + nsList, err := c.Client.ListNamespaces(keyword, nil) + if err != nil { + return nil, err + } + var res []*client.Namespace + for _, o := range nsList { + res = append(res, &client.Namespace{ + Name: o.Path, + Path: o.FullPath, + Kind: o.Kind, + }) + } + return res, nil +} + +func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { + var projects []*gogitlab.Project + var err error + switch opt.NamespaceType { + case client.GroupKind: + projects, err = c.Client.ListGroupProjects(opt.Namespace, opt.Key, &gitlab.ListOptions{ + Page: opt.Page, + PerPage: opt.PerPage, + NoPaginated: true, + }) + if err != nil { + return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) + } + default: + projects, err = c.Client.ListUserProjects(opt.Namespace, opt.Key, &gitlab.ListOptions{ + Page: opt.Page, + PerPage: opt.PerPage, + NoPaginated: true, + }) + if err != nil { + return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) + } + + } + var res []*client.Project + for _, o := range projects { + res = append(res, &client.Project{ + ID: o.ID, + Name: o.Path, + Namespace: o.Namespace.FullPath, + Description: o.Description, + DefaultBranch: o.DefaultBranch, + }) + } + return res, nil +} diff --git a/pkg/microservice/aslan/core/code/client/interface.go b/pkg/microservice/aslan/core/code/client/interface.go index 8ab4f20e..78363305 100644 --- a/pkg/microservice/aslan/core/code/client/interface.go +++ b/pkg/microservice/aslan/core/code/client/interface.go @@ -16,19 +16,28 @@ limitations under the License. package client +const ( + OrgKind = "org" + GroupKind = "group" + UserKind = "user" +) + type CodeHostClient interface { ListBranches(opt ListOpt) ([]*Branch, error) ListTags(opt ListOpt) ([]*Tag, error) ListPrs(opt ListOpt) ([]*PullRequest, error) + ListNamespaces(keyword string) ([]*Namespace, error) + ListProjects(opt ListOpt) ([]*Project, error) } type ListOpt struct { - Namespace string - ProjectName string - Key string - Page int - PerPage int - TargeBr string + Namespace string + NamespaceType string + ProjectName string + Key string + Page int + PerPage int + TargeBr string } type Branch struct { @@ -58,3 +67,20 @@ type PullRequest struct { User string `json:"user"` Base string `json:"base,omitempty"` } + +type Namespace struct { + Name string `json:"name"` + Path string `json:"path"` + Kind string `json:"kind"` + ProjectUUID string `json:"project_uuid,omitempty"` +} + +type Project struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + DefaultBranch string `json:"defaultBranch"` + Namespace string `json:"namespace"` + RepoUUID string `json:"repo_uuid,omitempty"` + RepoID string `json:"repo_id,omitempty"` +} diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 6ee5b24a..671d0491 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -17,17 +17,10 @@ limitations under the License. package service import ( - "context" - "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/tool/codehub" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/git/gitlab" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" ) const ( @@ -38,54 +31,10 @@ const ( CodeHostCodeHub = "codehub" ) -func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*Namespace, error) { - ch, err := systemconfig.New().GetCodeHost(codeHostID) +func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*client.Namespace, error) { + codehostClient, err := open.OpenClient(codeHostID, log) if err != nil { - return nil, e.ErrCodehostListNamespaces.AddDesc("git client is nil") - } - - if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListNamespaces.AddDesc(err.Error()) - } - - nsList, err := client.ListNamespaces(keyword, nil) - if err != nil { - return nil, err - } - return ToNamespaces(nsList), nil - } else if ch.Type == gerrit.CodehostTypeGerrit { - return []*Namespace{{ - Name: gerrit.DefaultNamespace, - Path: gerrit.DefaultNamespace, - Kind: OrgKind, - }}, nil - } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) - codeHubNamespace, err := codeHubClient.NamespaceList() - if err != nil { - return nil, err - } - return ToNamespaces(codeHubNamespace), nil - } else { - // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - namespaces := make([]*Namespace, 0) - - user, err := gh.GetAuthenticatedUser(context.TODO()) - if err != nil { - return nil, err - } - namespaces = append(namespaces, ToNamespaces(user)...) - - organizations, err := gh.ListOrganizationsForAuthenticatedUser(context.TODO(), nil) - if err != nil { - return nil, err - } - namespaces = append(namespaces, ToNamespaces(organizations)...) - - return namespaces, nil + return nil, err } + return codehostClient.ListNamespaces(keyword) } diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 689efb13..287a963a 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -17,84 +17,22 @@ limitations under the License. package service import ( - "context" - "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/tool/codehub" - e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/gerrit" - "github.com/koderover/zadig/pkg/tool/git/gitlab" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" ) -func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, perPage int, keyword string, log *zap.SugaredLogger) ([]*Project, error) { - ch, err := systemconfig.New().GetCodeHost(codeHostID) +func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, perPage int, keyword string, log *zap.SugaredLogger) ([]*client.Project, error) { + codehostClient, err := open.OpenClient(codeHostID, log) if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") - } - if ch.Type == codeHostGitlab { - client, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - - if namespaceType == GroupKind { - projects, err := client.ListGroupProjects(namespace, keyword, nil) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - return ToProjects(projects), nil - } - - // user - projects, err := client.ListUserProjects(namespace, keyword, &gitlab.ListOptions{ - Page: page, - PerPage: perPage, - NoPaginated: true, - }) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - return ToProjects(projects), nil - - } else if ch.Type == gerrit.CodehostTypeGerrit { - cli := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - projects, err := cli.ListProjectsByKey(keyword) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - return ToProjects(projects), nil - } else if ch.Type == CodeHostCodeHub { - codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) - projects, err := codeHubClient.RepoList(namespace, keyword, 100) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) - } - return ToProjects(projects), nil - } else { - // github - gh := git.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) - repos, err := gh.ListRepositoriesForAuthenticatedUser(context.TODO(), nil) - if err != nil { - return nil, err - } - var projects []*Project - for _, p := range ToProjects(repos) { - if p.Namespace != namespace { - continue - } - projects = append(projects, p) - } - - return projects, nil + return nil, err } + return codehostClient.ListProjects(client.ListOpt{ + Namespace: namespace, + NamespaceType: namespaceType, + Key: keyword, + Page: page, + PerPage: perPage, + }) } diff --git a/pkg/microservice/aslan/core/code/service/types.go b/pkg/microservice/aslan/core/code/service/types.go index f925d129..952d49c0 100644 --- a/pkg/microservice/aslan/core/code/service/types.go +++ b/pkg/microservice/aslan/core/code/service/types.go @@ -16,15 +16,6 @@ limitations under the License. package service -import ( - "github.com/andygrunwald/go-gerrit" - "github.com/google/go-github/v35/github" - "github.com/xanzy/go-gitlab" - - "github.com/koderover/zadig/pkg/tool/codehub" - gerrittool "github.com/koderover/zadig/pkg/tool/gerrit" -) - type Branch struct { Name string `json:"name"` Protected bool `json:"protected"` @@ -69,100 +60,3 @@ type Tag struct { TarballURL string `json:"tarball_url"` Message string `json:"message"` } - -func ToNamespaces(obj interface{}) []*Namespace { - var res []*Namespace - - switch os := obj.(type) { - case *github.User: - res = append(res, &Namespace{ - Name: os.GetLogin(), - Path: os.GetLogin(), - Kind: UserKind, - }) - case []*github.User: - for _, o := range os { - res = append(res, &Namespace{ - Name: o.GetLogin(), - Path: o.GetLogin(), - Kind: UserKind, - }) - } - case []*github.Organization: - for _, o := range os { - res = append(res, &Namespace{ - Name: o.GetLogin(), - Path: o.GetLogin(), - Kind: OrgKind, - }) - } - case []*gitlab.Namespace: - for _, o := range os { - res = append(res, &Namespace{ - Name: o.Path, - Path: o.FullPath, - Kind: o.Kind, - }) - } - case []*codehub.Namespace: - for _, o := range os { - res = append(res, &Namespace{ - Name: o.Name, - Path: o.Path, - Kind: o.Kind, - ProjectUUID: o.ProjectUUID, - }) - } - } - - return res -} - -func ToProjects(obj interface{}) []*Project { - var res []*Project - - switch os := obj.(type) { - case []*github.Repository: - for _, o := range os { - res = append(res, &Project{ - ID: int(o.GetID()), - Name: o.GetName(), - DefaultBranch: o.GetDefaultBranch(), - Namespace: o.GetOwner().GetLogin(), - }) - } - case []*gitlab.Project: - for _, o := range os { - res = append(res, &Project{ - ID: o.ID, - Name: o.Path, - Namespace: o.Namespace.FullPath, - Description: o.Description, - DefaultBranch: o.DefaultBranch, - }) - } - case []*gerrit.ProjectInfo: - for ind, o := range os { - res = append(res, &Project{ - ID: ind, // fake id - Name: gerrittool.Unescape(o.ID), // id could have %2F - Description: o.Description, - DefaultBranch: "master", - Namespace: gerrittool.DefaultNamespace, - }) - } - case []*codehub.Project: - for _, project := range os { - res = append(res, &Project{ - Name: project.Name, - Description: project.Description, - DefaultBranch: project.DefaultBranch, - Namespace: project.Namespace, - RepoUUID: project.RepoUUID, - RepoID: project.RepoID, - }) - } - } - - return res -} -- Gitee From 5513bee0d7b897676fb304a3558c13b6229ddae0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 15 Mar 2022 20:47:54 +0800 Subject: [PATCH 1849/2647] move repoName to query paramter Signed-off-by: allenshen --- pkg/microservice/aslan/core/code/handler/router.go | 2 +- pkg/microservice/aslan/core/code/handler/workspace.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 6522fe13..8824db04 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -47,7 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workspace.GET("/git/:codehostId", GetGitRepoInfo) workspace.GET("/tree", GetRepoTree) - workspace.GET("/codehub/:codehostId/:repoUUID", GetCodehubRepoInfo) + workspace.GET("/codehub/:codehostId", GetCodehubRepoInfo) } } diff --git a/pkg/microservice/aslan/core/code/handler/workspace.go b/pkg/microservice/aslan/core/code/handler/workspace.go index f7c84558..43edf436 100644 --- a/pkg/microservice/aslan/core/code/handler/workspace.go +++ b/pkg/microservice/aslan/core/code/handler/workspace.go @@ -159,7 +159,7 @@ func GetCodehubRepoInfo(c *gin.Context) { return } - repoUUID := c.Param("repoUUID") + repoUUID := c.Query("repoUUID") if repoUUID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty repo uuid") return -- Gitee From 37be9479cc93e7d40dd421f5737b5c88580fe5b5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 16 Mar 2022 10:25:09 +0800 Subject: [PATCH 1850/2647] optimize query parameter name Signed-off-by: allenshen --- .../aslan/core/code/handler/codehost.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 0d021373..34e0c9d5 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -74,7 +74,7 @@ func CodeHostGetProjectsList(c *gin.Context) { namespaceType := c.DefaultQuery("type", "group") codehostID := c.Param("codehostId") - namespace := c.Query("namespace") + namespace := c.Query("repoOwner") if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") @@ -121,8 +121,8 @@ func CodeHostGetBranchList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Query("namespace") - projectName := c.Query("projectName") // pro Name, id/name -> gitlab = id + namespace := c.Query("repoOwner") + projectName := c.Query("repoName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) @@ -158,8 +158,8 @@ func CodeHostGetTagList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Query("namespace") - projectName := c.Query("projectName") // pro Name, id/name -> gitlab = id + namespace := c.Query("repoOwner") + projectName := c.Query("repoName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) @@ -187,8 +187,8 @@ func CodeHostGetPRList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Query("namespace") - projectName := c.Query("projectName") // pro Name, id/name -> gitlab = id + namespace := c.Query("repoOwner") + projectName := c.Query("repoName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { -- Gitee From 827844d997ec458895fdeaf2b27d361971618c7e Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 16 Mar 2022 11:36:08 +0800 Subject: [PATCH 1851/2647] delete type file Signed-off-by: mouuii --- .../aslan/core/code/service/types.go | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 pkg/microservice/aslan/core/code/service/types.go diff --git a/pkg/microservice/aslan/core/code/service/types.go b/pkg/microservice/aslan/core/code/service/types.go deleted file mode 100644 index 952d49c0..00000000 --- a/pkg/microservice/aslan/core/code/service/types.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -type Branch struct { - Name string `json:"name"` - Protected bool `json:"protected"` - Merged bool `json:"merged"` -} - -type PullRequest struct { - ID int `json:"id"` - TargetBranch string `json:"targetBranch"` - SourceBranch string `json:"sourceBranch"` - ProjectID int `json:"projectId"` - Title string `json:"title"` - State string `json:"state"` - CreatedAt int64 `json:"createdAt"` - UpdatedAt int64 `json:"updatedAt"` - AuthorUsername string `json:"authorUsername"` - Number int `json:"number"` - User string `json:"user"` - Base string `json:"base,omitempty"` -} - -type Namespace struct { - Name string `json:"name"` - Path string `json:"path"` - Kind string `json:"kind"` - ProjectUUID string `json:"project_uuid,omitempty"` -} - -type Project struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - DefaultBranch string `json:"defaultBranch"` - Namespace string `json:"namespace"` - RepoUUID string `json:"repo_uuid,omitempty"` - RepoID string `json:"repo_id,omitempty"` -} - -type Tag struct { - Name string `json:"name"` - ZipballURL string `json:"zipball_url"` - TarballURL string `json:"tarball_url"` - Message string `json:"message"` -} -- Gitee From 5ae24bf5592f1d973279c131cdba35537b967237 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 16 Mar 2022 14:07:31 +0800 Subject: [PATCH 1852/2647] update api url Signed-off-by: allenshen --- pkg/microservice/aslan/core/code/handler/router.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 8824db04..764dc04c 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -29,10 +29,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehost.GET("", GetCodeHostList) codehost.GET("/:codehostId/namespaces", CodeHostGetNamespaceList) - codehost.GET("/:codehostId/namespaces/projects", CodeHostGetProjectsList) - codehost.GET("/:codehostId/namespaces/projects/branches", CodeHostGetBranchList) - codehost.GET("/:codehostId/namespaces/projects/tags", CodeHostGetTagList) - codehost.GET("/:codehostId/namespaces/projects/prs", CodeHostGetPRList) + codehost.GET("/:codehostId/projects", CodeHostGetProjectsList) + codehost.GET("/:codehostId/branches", CodeHostGetBranchList) + codehost.GET("/:codehostId/tags", CodeHostGetTagList) + codehost.GET("/:codehostId/prs", CodeHostGetPRList) codehost.PUT("/infos", ListRepoInfos) codehost.POST("/branches/regular/check", MatchBranchesList) } -- Gitee From 7b925ebdc4ad9770ddd78ff4cda588361a97ab9c Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 16 Mar 2022 15:07:24 +0800 Subject: [PATCH 1853/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/code/handler/codehost.go | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 34e0c9d5..fd32b2ae 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -74,14 +74,14 @@ func CodeHostGetProjectsList(c *gin.Context) { namespaceType := c.DefaultQuery("type", "group") codehostID := c.Param("codehostId") - namespace := c.Query("repoOwner") + repoOwner := c.Query("repoOwner") if codehostID == "" { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") return } - if namespace == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty namespace") + if repoOwner == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoOwner") return } if namespaceType == "" { @@ -102,7 +102,7 @@ func CodeHostGetProjectsList(c *gin.Context) { chID, _ := strconv.Atoi(codehostID) ctx.Resp, ctx.Err = service.CodeHostListProjects( chID, - strings.Replace(namespace, "%2F", "/", -1), + strings.Replace(repoOwner, "%2F", "/", -1), namespaceType, args.Page, args.PerPage, @@ -121,8 +121,8 @@ func CodeHostGetBranchList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Query("repoOwner") - projectName := c.Query("repoName") // pro Name, id/name -> gitlab = id + repoOwner := c.Query("repoOwner") + repoName := c.Query("repoName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) @@ -133,20 +133,20 @@ func CodeHostGetBranchList(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") return } - if namespace == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty namespace") + if repoOwner == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoOwner") return } - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty projectName") + if repoName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoName") return } chID, _ := strconv.Atoi(codehostID) ctx.Resp, ctx.Err = service.CodeHostListBranches( chID, - projectName, - strings.Replace(namespace, "%2F", "/", -1), + repoName, + strings.Replace(repoOwner, "%2F", "/", -1), args.Key, args.Page, args.PerPage, @@ -158,8 +158,8 @@ func CodeHostGetTagList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Query("repoOwner") - projectName := c.Query("repoName") // pro Name, id/name -> gitlab = id + repoOwner := c.Query("repoOwner") + repoName := c.Query("repoName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) @@ -169,17 +169,17 @@ func CodeHostGetTagList(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") return } - if namespace == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty namespace") + if repoOwner == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoOwner") return } - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty projectName") + if repoName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoName") return } chID, _ := strconv.Atoi(codehostID) - ctx.Resp, ctx.Err = service.CodeHostListTags(chID, projectName, strings.Replace(namespace, "%2F", "/", -1), args.Key, args.Page, args.PerPage, ctx.Logger) + ctx.Resp, ctx.Err = service.CodeHostListTags(chID, repoName, strings.Replace(repoOwner, "%2F", "/", -1), args.Key, args.Page, args.PerPage, ctx.Logger) } func CodeHostGetPRList(c *gin.Context) { @@ -187,8 +187,8 @@ func CodeHostGetPRList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codehostID := c.Param("codehostId") - namespace := c.Query("repoOwner") - projectName := c.Query("repoName") // pro Name, id/name -> gitlab = id + repoOwner := c.Query("repoOwner") + repoName := c.Query("repoName") // pro Name, id/name -> gitlab = id args := new(CodeHostGetPageNateListArgs) if err := c.ShouldBindQuery(args); err != nil { @@ -200,19 +200,19 @@ func CodeHostGetPRList(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("empty codehostId") return } - if namespace == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty namespace") + if repoOwner == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoOwner") return } - if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("empty projectName") + if repoName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("empty repoName") return } targetBr := c.Query("targetBranch") chID, _ := strconv.Atoi(codehostID) - ctx.Resp, ctx.Err = service.CodeHostListPRs(chID, projectName, strings.Replace(namespace, "%2F", "/", -1), targetBr, args.Key, args.Page, args.PerPage, ctx.Logger) + ctx.Resp, ctx.Err = service.CodeHostListPRs(chID, repoName, strings.Replace(repoOwner, "%2F", "/", -1), targetBr, args.Key, args.Page, args.PerPage, ctx.Logger) } func ListRepoInfos(c *gin.Context) { -- Gitee From 59d0e18ebc37b7dbc6be1e385de0491fd0d02a4f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 15:11:51 +0800 Subject: [PATCH 1854/2647] debug code Signed-off-by: liu deyi --- pkg/tool/httpclient/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index a9698ce3..493f0bc5 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -18,6 +18,7 @@ package httpclient import ( "bytes" + "fmt" "io" "os" "time" @@ -148,6 +149,10 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons for _, rf := range rfs { rf(r) } + for key, value := range r.Header { + fmt.Println(fmt.Sprintf("key:%s,value:%s", key, value)) + } + return c.wrapError(r.Execute(method, url)) } -- Gitee From e81f61773952a3c22c3892abf06bbcc908d4e813 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 15:34:20 +0800 Subject: [PATCH 1855/2647] debug code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/extension.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 97db628d..9947845a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -124,10 +124,6 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, httpclient.SetHostURL(p.Task.URL), ) url := p.Task.Path - for _, header := range p.Task.Headers { - httpclient.SetHeader(header.Key, header.Value) - } - webhookPayload := &task.WebhookPayload{ EventName: "workflow", ProjectName: pipelineTask.ProductName, -- Gitee From 90f0f71d85a8144808304d610599136a92bef6a6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 15:51:57 +0800 Subject: [PATCH 1856/2647] fix header problem Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/extension.go | 6 ++++-- pkg/tool/httpclient/request_options.go | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 9947845a..f2b99e91 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -136,10 +136,12 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, if err != nil { return } + headers := make(map[string]string) for _, header := range p.Task.Headers { - httpclient.SetHeader(header.Key, header.Value) + headers[header.Key] = header.Value } - _, err = httpClient.Post(url, httpclient.SetHeader(ZadigEvent, EventName), httpclient.SetBody(body)) + headers[ZadigEvent] = EventName + _, err = httpClient.Post(url, httpclient.SetHeaders(headers), httpclient.SetBody(body)) if err != nil { return } diff --git a/pkg/tool/httpclient/request_options.go b/pkg/tool/httpclient/request_options.go index afc19279..c3b3c1bf 100644 --- a/pkg/tool/httpclient/request_options.go +++ b/pkg/tool/httpclient/request_options.go @@ -43,6 +43,12 @@ func SetHeader(header, value string) RequestFunc { } } +func SetHeaders(headers map[string]string) RequestFunc { + return func(r *resty.Request) { + r.SetHeaders(headers) + } +} + func SetHeadersFromHTTPHeader(header http.Header) RequestFunc { return func(r *resty.Request) { r.Header = header -- Gitee From c6a9a8f1b57c81952a2e46d2b4b1e09a947d279e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 15:57:05 +0800 Subject: [PATCH 1857/2647] remove debug code Signed-off-by: liu deyi --- .../warpdrive/core/service/taskplugin/trigger.go | 7 +++++-- pkg/tool/httpclient/client.go | 4 ---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go index d23f8c51..da50663e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/trigger.go @@ -145,10 +145,13 @@ func (p *TriggerTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi TaskEnvs: pipelineTask.TaskArgs.BuildArgs, } body, err = json.Marshal(webhookPayload) + + headers := make(map[string]string) for _, header := range p.Task.Headers { - httpclient.SetHeader(header.Key, header.Value) + headers[header.Key] = header.Value } - _, err = httpClient.Post(url, httpclient.SetHeader(ZadigEvent, EventName), httpclient.SetBody(body)) + headers[ZadigEvent] = EventName + _, err = httpClient.Post(url, httpclient.SetHeaders(headers), httpclient.SetBody(body)) if err != nil { return } diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index 493f0bc5..be6499fd 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -18,7 +18,6 @@ package httpclient import ( "bytes" - "fmt" "io" "os" "time" @@ -149,9 +148,6 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons for _, rf := range rfs { rf(r) } - for key, value := range r.Header { - fmt.Println(fmt.Sprintf("key:%s,value:%s", key, value)) - } return c.wrapError(r.Execute(method, url)) } -- Gitee From fe189348c9324f366324242d31cd87499401c969 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 16:27:58 +0800 Subject: [PATCH 1858/2647] debug code Signed-off-by: liu deyi --- pkg/tool/httpclient/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index be6499fd..b107056c 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -18,6 +18,7 @@ package httpclient import ( "bytes" + "fmt" "io" "os" "time" @@ -149,6 +150,7 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons rf(r) } + fmt.Println(fmt.Sprintf("request.body:%+v", r.Body)) return c.wrapError(r.Execute(method, url)) } -- Gitee From ecc6db2655ae896a9895576fe4ebfb6b46c516b2 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 16:32:43 +0800 Subject: [PATCH 1859/2647] debug code Signed-off-by: liu deyi --- pkg/tool/httpclient/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index b107056c..09d660f8 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -150,7 +150,7 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons rf(r) } - fmt.Println(fmt.Sprintf("request.body:%+v", r.Body)) + fmt.Println(fmt.Sprintf("request.body:%s", r.Body.(string))) return c.wrapError(r.Execute(method, url)) } -- Gitee From 4dc81704c71e47836698d3bb71be83ae60c93081 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 16:37:19 +0800 Subject: [PATCH 1860/2647] debug code Signed-off-by: liu deyi --- pkg/tool/httpclient/client.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index 09d660f8..c114f3e2 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -150,7 +150,10 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons rf(r) } - fmt.Println(fmt.Sprintf("request.body:%s", r.Body.(string))) + if value, ok := r.Body.(string); ok { + fmt.Println(fmt.Sprintf("request.body:%s", value)) + } + return c.wrapError(r.Execute(method, url)) } -- Gitee From 554ca9dfcae45f278c933ec10bd1b0676727ebd0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 16:52:43 +0800 Subject: [PATCH 1861/2647] store post payload Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/task/extension.go | 1 + .../warpdrive/core/service/taskplugin/extension.go | 1 + .../warpdrive/core/service/types/task/extension.go | 1 + pkg/tool/httpclient/client.go | 5 ----- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index cc8549c3..3f95e661 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -31,6 +31,7 @@ type Extension struct { Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + Payload string `bson:"payload" json:"payload"` ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index f2b99e91..030e8c66 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -136,6 +136,7 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, if err != nil { return } + p.Task.Payload = string(body) headers := make(map[string]string) for _, header := range p.Task.Headers { headers[header.Key] = header.Value diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 540b46ed..71b428b6 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -31,6 +31,7 @@ type Extension struct { Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + Payload string `bson:"payload" json:"payload"` ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` diff --git a/pkg/tool/httpclient/client.go b/pkg/tool/httpclient/client.go index c114f3e2..be6499fd 100644 --- a/pkg/tool/httpclient/client.go +++ b/pkg/tool/httpclient/client.go @@ -18,7 +18,6 @@ package httpclient import ( "bytes" - "fmt" "io" "os" "time" @@ -150,10 +149,6 @@ func (c *Client) Request(method, url string, rfs ...RequestFunc) (*resty.Respons rf(r) } - if value, ok := r.Body.(string); ok { - fmt.Println(fmt.Sprintf("request.body:%s", value)) - } - return c.wrapError(r.Execute(method, url)) } -- Gitee From 12057fc31dfa7315712d263d0186aa78e19cced1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 17:18:19 +0800 Subject: [PATCH 1862/2647] add omitempty attribute Signed-off-by: liu deyi --- .../warpdrive/core/service/types/task/trigger.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index bce09fb1..f9dd3e7e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,14 +54,14 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - EventName string `json:"event_name"` - ProjectName string `json:"project_name"` - TaskName string `json:"task_name"` - TaskID int64 `json:"task_id"` - TaskOutput []*TaskOutput `json:"task_output"` - TaskEnvs []*KeyVal `json:"task_envs"` - ServiceInfos []*ServiceInfo `json:"service_infos"` - Creator string `json:"creator"` + EventName string `bson:"event_name" json:"event_name"` + ProjectName string `bson:"project_name" json:"project_name"` + TaskName string `bson:"task_name" json:"task_name"` + TaskID int64 `bson:"task_id" json:"task_id"` + TaskOutput []*TaskOutput `bson:"task_output,omitempty" json:"task_output,omitempty"` + TaskEnvs []*KeyVal `bson:"task_envs,omitempty" json:"task_envs,omitempty"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Creator string `bson:"creator" json:"creator"` } type TaskOutput struct { -- Gitee From 629359390f442d230fdb99d10bec820dd5569999 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 17:26:03 +0800 Subject: [PATCH 1863/2647] modify field attribute Signed-off-by: liu deyi --- .../core/service/types/task/trigger.go | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index f9dd3e7e..c255c361 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,14 +54,23 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - EventName string `bson:"event_name" json:"event_name"` - ProjectName string `bson:"project_name" json:"project_name"` - TaskName string `bson:"task_name" json:"task_name"` - TaskID int64 `bson:"task_id" json:"task_id"` - TaskOutput []*TaskOutput `bson:"task_output,omitempty" json:"task_output,omitempty"` - TaskEnvs []*KeyVal `bson:"task_envs,omitempty" json:"task_envs,omitempty"` - ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` - Creator string `bson:"creator" json:"creator"` + //EventName string `bson:"event_name" json:"event_name"` + //ProjectName string `bson:"project_name" json:"project_name"` + //TaskName string `bson:"task_name" json:"task_name"` + //TaskID int64 `bson:"task_id" json:"task_id"` + //TaskOutput []*TaskOutput `bson:"task_output,omitempty" json:"task_output,omitempty"` + //TaskEnvs []*KeyVal `bson:"task_envs,omitempty" json:"task_envs,omitempty"` + //ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + //Creator string `bson:"creator" json:"creator"` + + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output,omitempty"` + TaskEnvs []*KeyVal `json:"task_envs,omitempty"` + ServiceInfos []*ServiceInfo `json:"service_infos"` + Creator string `json:"creator"` } type TaskOutput struct { -- Gitee From 5cd1472fe1dc865086951d50d0a944e623741dd7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 16 Mar 2022 17:50:58 +0800 Subject: [PATCH 1864/2647] optimize code Signed-off-by: liu deyi --- .../warpdrive/core/service/types/task/trigger.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index c255c361..8a069611 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,15 +54,6 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - //EventName string `bson:"event_name" json:"event_name"` - //ProjectName string `bson:"project_name" json:"project_name"` - //TaskName string `bson:"task_name" json:"task_name"` - //TaskID int64 `bson:"task_id" json:"task_id"` - //TaskOutput []*TaskOutput `bson:"task_output,omitempty" json:"task_output,omitempty"` - //TaskEnvs []*KeyVal `bson:"task_envs,omitempty" json:"task_envs,omitempty"` - //ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` - //Creator string `bson:"creator" json:"creator"` - EventName string `json:"event_name"` ProjectName string `json:"project_name"` TaskName string `json:"task_name"` -- Gitee From 8869dfe40fc1774063d0d48ad0d42ad6728aa242 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 16 Mar 2022 18:11:11 +0800 Subject: [PATCH 1865/2647] Fixed webhook branch being replaced Signed-off-by: ddh27 --- .../aslan/core/workflow/service/webhook/gitlab_workflow_task.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index f144feb5..fda376e1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -553,7 +553,6 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, branref = mergeEvent.ObjectAttributes.SourceBranch } prID = evt.ObjectAttributes.IID - item.MainRepo.Branch = getBranchFromRef(mergeEvent.ObjectAttributes.TargetBranch) case *gitlab.TagEvent: tagEvent = evt if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != tagEvent.Project.PathWithNamespace { -- Gitee From b0c64b9a40de4d12d127eabc84c5ef24a85905eb Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 11:08:44 +0800 Subject: [PATCH 1866/2647] Handle the problem that the service module name is too long and the job creation fails Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 3 ++- pkg/microservice/warpdrive/core/service/common/types.go | 2 +- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 42d34b5b..9af7cc14 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -266,7 +266,8 @@ func getPipelineSelector(options *GetContainerOptions) labels.Selector { ret[setting.TypeLabel] = options.SubTask if options.ServiceName != "" { - ret[setting.ServiceLabel] = strings.ToLower(options.ServiceName) + serviceKey := strings.TrimRight(options.ServiceName[:63], "_") + ret[setting.ServiceLabel] = strings.ToLower(serviceKey) } if options.PipelineType != "" { diff --git a/pkg/microservice/warpdrive/core/service/common/types.go b/pkg/microservice/warpdrive/core/service/common/types.go index b57d7c59..953c10c0 100644 --- a/pkg/microservice/warpdrive/core/service/common/types.go +++ b/pkg/microservice/warpdrive/core/service/common/types.go @@ -25,5 +25,5 @@ type Stage struct { RunParallel bool `bson:"run_parallel" json:"run_parallel"` Desc string `bson:"desc,omitempty" json:"desc,omitempty"` SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - AfterAll bool `json:"after_all" bson:"after_all"` + AfterAll bool `bson:"after_all" json:"after_all"` } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index d409809d..46c7b3ef 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -376,9 +376,10 @@ const ( // getJobLabels get labels k-v map from JobLabel struct func getJobLabels(jobLabel *JobLabel) map[string]string { + serviceKey := strings.TrimRight(jobLabel.ServiceName[:63], "_") retMap := map[string]string{ jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.PipelineName), jobLabel.TaskID), - jobLabelServiceKey: strings.ToLower(jobLabel.ServiceName), + jobLabelServiceKey: strings.ToLower(serviceKey), jobLabelSTypeKey: strings.Replace(jobLabel.TaskType, "_", "-", -1), jobLabelPTypeKey: jobLabel.PipelineType, } -- Gitee From 4781d71d741c5d7791a8c9f361249ef2e939c0a0 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 17 Mar 2022 11:33:46 +0800 Subject: [PATCH 1867/2647] fix yaml webhook test repo strategy Signed-off-by: ddh27 --- .../aslan/core/workflow/service/webhook/gitlab_workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index fda376e1..70cb3f3d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -432,7 +432,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if test.Repo.Strategy == TestRepoStrategyCurrentRepo { for _, repo := range moduleTest.Repos { if repo.RepoName == item.MainRepo.RepoName && repo.RepoOwner == item.MainRepo.RepoOwner { - repo.Branch = item.MainRepo.Branch + repo.Branch = branref repo.PR = prId } } -- Gitee From 6b58259d6bd1041afcedb60134f7be658d07e82d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 11:49:57 +0800 Subject: [PATCH 1868/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 5 ++++- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 9af7cc14..f8f1d516 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -266,7 +266,10 @@ func getPipelineSelector(options *GetContainerOptions) labels.Selector { ret[setting.TypeLabel] = options.SubTask if options.ServiceName != "" { - serviceKey := strings.TrimRight(options.ServiceName[:63], "_") + serviceKey := options.ServiceName + if len(serviceKey) > 63 { + serviceKey = serviceKey[:63] + } ret[setting.ServiceLabel] = strings.ToLower(serviceKey) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 46c7b3ef..97e9a010 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -376,7 +376,11 @@ const ( // getJobLabels get labels k-v map from JobLabel struct func getJobLabels(jobLabel *JobLabel) map[string]string { - serviceKey := strings.TrimRight(jobLabel.ServiceName[:63], "_") + serviceKey := jobLabel.ServiceName + if len(serviceKey) > 63 { + serviceKey = serviceKey[:63] + } + retMap := map[string]string{ jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.PipelineName), jobLabel.TaskID), jobLabelServiceKey: strings.ToLower(serviceKey), -- Gitee From 242e64c981728adc6a6944acf6b6d2c52f097838 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 17 Mar 2022 14:12:43 +0800 Subject: [PATCH 1869/2647] log Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/pipeline_task.go | 4 ++-- pkg/microservice/aslan/core/workflow/handler/workflow_task.go | 4 ++-- .../aslan/core/workflow/handler/workflow_v3_task.go | 2 +- pkg/shared/handler/base.go | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go index 37b7b2c0..dd6f4f68 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go @@ -125,7 +125,7 @@ func GetPipelineTask(c *gin.Context) { func RestartPipelineTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "重启", "单服务-工作流task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "单服务-工作流task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -139,7 +139,7 @@ func RestartPipelineTask(c *gin.Context) { func CancelTaskV2(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "取消", "单服务-工作流task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "取消", "单服务-工作流task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index cc4bcc1e..159150b5 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -168,7 +168,7 @@ func GetWorkflowTask(c *gin.Context) { func RestartWorkflowTask(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "重启", "工作流-task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "重启", "工作流-task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { @@ -182,7 +182,7 @@ func RestartWorkflowTask(c *gin.Context) { func CancelWorkflowTaskV2(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "取消", "工作流-task", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "取消", "工作流-task", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go index 721ad9e4..13d2e495 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v3_task.go @@ -73,7 +73,7 @@ func RestartWorkflowTaskV3(c *gin.Context) { func CancelWorkflowTaskV3(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, "", "取消", "工作流taskV3", c.Param("name"), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "取消", "工作流taskV3", c.Param("name"), "", ctx.Logger) taskID, err := strconv.ParseInt(c.Param("id"), 10, 64) if err != nil { diff --git a/pkg/shared/handler/base.go b/pkg/shared/handler/base.go index 504361b6..e1727c72 100644 --- a/pkg/shared/handler/base.go +++ b/pkg/shared/handler/base.go @@ -72,6 +72,8 @@ func NewContext(c *gin.Context) *Context { if err != nil { logger.Warnf("Failed to get user from token, err: %s", err) } + } else { + claims.Name = "system" } return &Context{ -- Gitee From 250030cf2970943cd9ae781111ccbc447e7b4ad0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 17 Mar 2022 17:08:47 +0800 Subject: [PATCH 1870/2647] update service create and update info Signed-off-by: allenshen --- .../core/environment/handler/environment.go | 1 - .../core/environment/service/environment.go | 309 +++++++++--------- .../service/environment_creator.go | 10 +- 3 files changed, 159 insertions(+), 161 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index c47b6e9e..d8c95d3e 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -142,7 +142,6 @@ func createHelmProduct(c *gin.Context, ctx *internalhandler.Context) { arg.ProductName = projectName envNameList = append(envNameList, arg.EnvName) } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "环境", strings.Join(envNameList, "-"), string(data), ctx.Logger) ctx.Err = service.CreateHelmProduct( diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4e142939..713389ba 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -166,10 +166,11 @@ type CreateHelmProductArg struct { } type UpdateMultiHelmProductArg struct { - ProductName string `json:"productName"` - EnvNames []string `json:"envNames"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` - ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented + ProductName string `json:"productName"` + EnvNames []string `json:"envNames"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + DeletedServices []string `json:"deleted_services"` + ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented } type RawYamlResp struct { @@ -788,78 +789,45 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames return nil } -func CreateHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { - templateProduct, err := templaterepo.NewProductColl().Find(productName) - if err != nil || templateProduct == nil { - if err != nil { - log.Errorf("failed to query product %s, err %s ", productName, err.Error()) - } - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", productName)) - } - - err = commonservice.FillProductTemplateValuesYamls(templateProduct, log) - if err != nil { - return e.ErrCreateEnv.AddDesc(err.Error()) - } - - errList := new(multierror.Error) - for _, arg := range args { - err = createSingleHelmProduct(templateProduct, requestID, userName, arg.RegistryID, arg, log) - if err != nil { - errList = multierror.Append(errList, err) - } - } - return errList.ErrorOrNil() -} - -func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, userName, registryID string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { - productObj := &commonmodels.Product{ - ProductName: templateProduct.ProductName, - Revision: 1, - Enabled: false, - EnvName: arg.EnvName, - UpdateBy: userName, - IsPublic: true, - ClusterID: arg.ClusterID, - Namespace: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), - Source: setting.SourceFromHelm, - IsOpenSource: templateProduct.IsOpensource, - ChartInfos: templateProduct.ChartInfos, - IsForkedProduct: false, - RegistryID: registryID, - IsExisted: arg.IsExisted, +// fill product services and chart infos and insert renderset data +func prepareHelmProductCreation(templateProduct *templatemodels.Product, productObj *commonmodels.Product, arg *CreateHelmProductArg, serviceTmplMap map[string]*commonmodels.Service, log *zap.SugaredLogger) error { + productObj.ChartInfos = make([]*templatemodels.RenderChart, 0) + // chart infos in template product + templateChartInfoMap := make(map[string]*templatemodels.RenderChart) + for _, tc := range templateProduct.ChartInfos { + templateChartInfoMap[tc.ServiceName] = tc } - customChartValueMap := make(map[string]*commonservice.RenderChartArg) + // user custom chart values + cvMap := make(map[string]*templatemodels.RenderChart) for _, singleCV := range arg.ChartValues { - customChartValueMap[singleCV.ServiceName] = singleCV - } - - for _, latestChart := range productObj.ChartInfos { - if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { - singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) + if tc, ok := templateChartInfoMap[singleCV.ServiceName]; !ok { + return fmt.Errorf("failed to find service info in template_product, serviceName: %s", singleCV.ServiceName) + } else { + chartInfo := &templatemodels.RenderChart{} + singleCV.FillRenderChartModel(chartInfo, tc.ChartVersion) + productObj.ChartInfos = append(productObj.ChartInfos, chartInfo) + cvMap[singleCV.ServiceName] = chartInfo } } + // default values defaultValuesYaml := arg.DefaultValues // generate service group data - allServiceInfoMap := templateProduct.AllServiceInfoMap() var serviceGroup [][]*commonmodels.ProductService for _, names := range templateProduct.Services { servicesResp := make([]*commonmodels.ProductService, 0) for _, serviceName := range names { - opt := &commonrepo.ServiceFindOption{ - ServiceName: serviceName, - ProductName: allServiceInfoMap[serviceName].Owner, - ExcludeStatus: setting.ProductStatusDeleting, + // only the services chosen by use can be applied into product + rc, ok := cvMap[serviceName] + if !ok { + continue } - serviceTmpl, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - errMsg := fmt.Sprintf("Can not find service with option %+v, error: %v", opt, err) - log.Error(errMsg) - return e.ErrCreateEnv.AddDesc(errMsg) + serviceTmpl, ok := serviceTmplMap[serviceName] + if !ok { + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to find service info in template_product, serviceName: %s", serviceName)) } serviceResp := &commonmodels.ProductService{ @@ -868,46 +836,27 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, Type: serviceTmpl.Type, Revision: serviceTmpl.Revision, } - if serviceTmpl.Type == setting.K8SDeployType { - serviceResp.Containers = make([]*commonmodels.Container, 0) - for _, c := range serviceTmpl.Containers { - container := &commonmodels.Container{ - Name: c.Name, - Image: c.Image, - ImagePath: c.ImagePath, - } - serviceResp.Containers = append(serviceResp.Containers, container) + serviceResp.Containers = make([]*commonmodels.Container, 0) + var err error + for _, c := range serviceTmpl.Containers { + image := c.Image + image, err = genImageFromYaml(c, rc.ValuesYaml, defaultValuesYaml, rc.GetOverrideYaml(), rc.OverrideValues) + if err != nil { + errMsg := fmt.Sprintf("genImageFromYaml product template %s,service name:%s,error:%s", productObj.ProductName, rc.ServiceName, err) + log.Error(errMsg) + return e.ErrCreateEnv.AddDesc(errMsg) } - } else if serviceTmpl.Type == setting.HelmDeployType { - serviceResp.Containers = make([]*commonmodels.Container, 0) - var err error - for _, c := range serviceTmpl.Containers { - image := c.Image - for _, rc := range productObj.ChartInfos { - if rc.ServiceName != serviceTmpl.ServiceName { - continue - } - image, err = genImageFromYaml(c, rc.ValuesYaml, defaultValuesYaml, rc.GetOverrideYaml(), rc.OverrideValues) - if err != nil { - errMsg := fmt.Sprintf("genImageFromYaml product template %s,service name:%s,error:%s", productObj.ProductName, rc.ServiceName, err) - log.Error(errMsg) - return e.ErrCreateEnv.AddDesc(errMsg) - } - } - - container := &commonmodels.Container{ - Name: c.Name, - Image: image, - ImagePath: c.ImagePath, - } - serviceResp.Containers = append(serviceResp.Containers, container) + container := &commonmodels.Container{ + Name: c.Name, + Image: image, + ImagePath: c.ImagePath, } + serviceResp.Containers = append(serviceResp.Containers, container) } servicesResp = append(servicesResp, serviceResp) } serviceGroup = append(serviceGroup, servicesResp) } - productObj.Services = serviceGroup // insert renderset info into db @@ -916,16 +865,86 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), EnvName: arg.EnvName, ProductTmpl: arg.ProductName, - UpdateBy: userName, + UpdateBy: productObj.UpdateBy, IsDefault: false, - DefaultValues: defaultValuesYaml, + DefaultValues: arg.DefaultValues, ChartInfos: productObj.ChartInfos, }, log) if err != nil { - log.Errorf("rennderset create fail when creating helm product, productName: %s", arg.ProductName) - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s", arg.ProductName)) + log.Errorf("rennderset create fail when copy creating helm product, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err)) + } + } + return nil +} + +func CreateHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { + templateProduct, err := templaterepo.NewProductColl().Find(productName) + if err != nil || templateProduct == nil { + if err != nil { + log.Errorf("failed to query product %s, err %s ", productName, err.Error()) + } + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", productName)) + } + + // fill all chart infos from product renderset + err = commonservice.FillProductTemplateValuesYamls(templateProduct, log) + if err != nil { + return e.ErrCreateEnv.AddDesc(err.Error()) + } + + // prepare data + serviceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + templateServiceMap := make(map[string]*commonmodels.Service) + for _, svc := range serviceTmpls { + templateServiceMap[svc.ServiceName] = svc + } + + errList := new(multierror.Error) + for _, arg := range args { + dataValid := true + for _, cv := range arg.ChartValues { + if _, ok := templateServiceMap[cv.ServiceName]; !ok { + dataValid = false + errList = multierror.Append(errList, fmt.Errorf("failed to find service tempalte, serviceName: %s", cv.ServiceName)) + } + } + if !dataValid { + continue + } + err = createSingleHelmProduct(templateProduct, requestID, userName, arg.RegistryID, arg, templateServiceMap, log) + if err != nil { + errList = multierror.Append(errList, err) } } + return errList.ErrorOrNil() +} + +func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, userName, registryID string, arg *CreateHelmProductArg, serviceTmplMap map[string]*commonmodels.Service, log *zap.SugaredLogger) error { + productObj := &commonmodels.Product{ + ProductName: templateProduct.ProductName, + Revision: 1, + Enabled: false, + EnvName: arg.EnvName, + UpdateBy: userName, + IsPublic: true, + ClusterID: arg.ClusterID, + Namespace: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + Source: setting.SourceFromHelm, + IsOpenSource: templateProduct.IsOpensource, + IsForkedProduct: false, + RegistryID: registryID, + IsExisted: arg.IsExisted, + } + + // fill services and chart infos of product + err := prepareHelmProductCreation(templateProduct, productObj, arg, serviceTmplMap, log) + if err != nil { + return err + } return CreateProduct(userName, requestID, productObj, log) } @@ -1041,10 +1060,25 @@ func CreateProduct(user, requestID string, args *commonmodels.Product, log *zap. } func CopyHelmProduct(productName, userName, requestID string, args []*CreateHelmProductArg, log *zap.SugaredLogger) error { - errList := new(multierror.Error) + templateProduct, err := templaterepo.NewProductColl().Find(productName) + if err != nil || templateProduct == nil { + if err != nil { + log.Errorf("failed to query product %s, err %s ", productName, err.Error()) + } + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to query product %s ", productName)) + } + serviceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + templateServiceMap := make(map[string]*commonmodels.Service) + for _, svc := range serviceTmpls { + templateServiceMap[svc.ServiceName] = svc + } + for _, arg := range args { - err := copySingleHelmProduct(productName, requestID, userName, arg, log) + err := copySingleHelmProduct(templateProduct, productName, requestID, userName, arg, templateServiceMap, log) if err != nil { errList = multierror.Append(errList, err) } @@ -1052,8 +1086,7 @@ func CopyHelmProduct(productName, userName, requestID string, args []*CreateHelm return errList.ErrorOrNil() } -func copySingleHelmProduct(productName, requestID, userName string, arg *CreateHelmProductArg, log *zap.SugaredLogger) error { - +func copySingleHelmProduct(templateProduct *templatemodels.Product, productName, requestID, userName string, arg *CreateHelmProductArg, serviceTmplMap map[string]*commonmodels.Service, log *zap.SugaredLogger) error { productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, EnvName: arg.BaseEnvName, @@ -1069,55 +1102,15 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH productInfo.BaseName = arg.BaseName productInfo.Namespace = commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace) - opt := &commonrepo.RenderSetFindOption{ - Name: productInfo.Render.Name, - Revision: productInfo.Render.Revision, - } - renderSetInfo, err := commonrepo.NewRenderSetColl().Find(opt) + // fill services and chart infos of product + err = prepareHelmProductCreation(templateProduct, productInfo, arg, serviceTmplMap, log) if err != nil { return err } - productInfo.ChartInfos = renderSetInfo.ChartInfos - - customChartValueMap := make(map[string]*commonservice.RenderChartArg) - for _, singleCV := range arg.ChartValues { - customChartValueMap[singleCV.ServiceName] = singleCV - } - - for _, latestChart := range productInfo.ChartInfos { - if singleCV, ok := customChartValueMap[latestChart.ServiceName]; ok { - singleCV.FillRenderChartModel(latestChart, latestChart.ChartVersion) - } - } + // clear render info productInfo.Render = nil - for idxm, svrInfo := range productInfo.Services { - for idxn, svr := range svrInfo { - if svr.Render != nil { - productInfo.Services[idxm][idxn].Render = nil - } - } - } - - // default values - defaultValuesYaml := arg.DefaultValues - - // insert renderset info into db - if len(productInfo.ChartInfos) > 0 { - err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ - Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), - EnvName: arg.EnvName, - ProductTmpl: arg.ProductName, - UpdateBy: userName, - IsDefault: false, - DefaultValues: defaultValuesYaml, - ChartInfos: productInfo.ChartInfos, - }, log) - if err != nil { - log.Errorf("rennderset create fail when copy creating helm product, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err) - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err)) - } - } + setServiceRender(productInfo) return CreateProduct(userName, requestID, productInfo, log) } @@ -1126,7 +1119,7 @@ func UpdateProductRecycleDay(envName, productName string, recycleDay int) error return commonrepo.NewProductColl().UpdateProductRecycleDay(envName, productName, recycleDay) } -func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, deletedServices []string, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -1141,13 +1134,21 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } + // use service definition from service template, but keep the image info + deletedSvcSet := sets.NewString(deletedServices...) productRespMap := productResp.GetServiceMap() + allServices := make([][]*commonmodels.ProductService, 0) for _, svrs := range updateProd.Services { + svcGroup := make([]*commonmodels.ProductService, 0) for _, svr := range svrs { ps, ok := productRespMap[svr.ServiceName] if !ok { continue } + if deletedSvcSet.Has(svr.ServiceName) { + continue + } + svcGroup = append(svcGroup, svr) for _, svrc := range svr.Containers { for _, psc := range ps.Containers { if svrc.Name == psc.Name { @@ -1157,10 +1158,11 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str } } } + allServices = append(allServices, svcGroup) } - productResp.Services = updateProd.Services + productResp.Services = allServices - // 设置产品状态为更新中 + // set status to updating if err := commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUpdating); err != nil { log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.UpdateEnvStatusErrMsg) @@ -1548,7 +1550,7 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for env: %s", envName)) } - err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, log) + err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, args.DeletedServices, log) if err != nil { log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) return envStatuses, e.ErrUpdateEnv.AddDesc(err.Error()) @@ -2656,7 +2658,6 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel func setServiceRender(args *commonmodels.Product) { for _, serviceGroup := range args.Services { for _, service := range serviceGroup { - // 当service type是k8s时才需要渲染信息 if service.Type == setting.K8SDeployType || service.Type == setting.HelmDeployType { service.Render = args.Render } @@ -2765,17 +2766,19 @@ func updateProductGroup(username, productName, envName, updateType string, produ return e.ErrUpdateEnv.AddErr(err) } + // current service applied in product for _, serviceGroup := range currentProductServices { for _, service := range serviceGroup { productServiceMap[service.ServiceName] = service } } + // services need to keep in product for _, service := range productResp.GetServiceMap() { productTemplServiceMap[service.ServiceName] = service } - // 找到环境里面还存在但是服务编排里面已经删除的服务卸载掉 + // uninstall releases which has been removed from services for serviceName := range productServiceMap { if _, isExist := productTemplServiceMap[serviceName]; !isExist { go func(namespace, serviceName string) { @@ -2784,8 +2787,6 @@ func updateProductGroup(username, productName, envName, updateType string, produ ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), Namespace: namespace, Wait: true, - //Force: true, - //Timeout: Timeout * time.Second * 10, }); err != nil { log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 64701c1c..a8b0fe79 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -197,12 +197,10 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } - if renderSet == nil { - renderSet, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) - if err != nil { - log.Errorf("[%s][P:%s] find product renderset error: %v", args.EnvName, args.ProductName, err) - return e.ErrCreateEnv.AddDesc(err.Error()) - } + renderSet, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) + if err != nil { + log.Errorf("[%s][P:%s] find product renderset error: %v", args.EnvName, args.ProductName, err) + return e.ErrCreateEnv.AddDesc(err.Error()) } // 设置产品render revision -- Gitee From 06c73929fab70167f2da4d47881852ded6caa58f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 17:26:06 +0800 Subject: [PATCH 1871/2647] fix cvm ops workflow bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 076b0fd6..8fdef6bb 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -128,7 +128,8 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) } for _, serviceName := range serviceNames { - newServiceName := serviceName + // Change the service name to underscore splicing + newServiceName := serviceName + "_" + serviceName wg.Start(func() { deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ Name: newServiceName, -- Gitee From 3116480e0133e869ba3513d7188072583f63b09a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 17:55:15 +0800 Subject: [PATCH 1872/2647] fix cvm ops workflow bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 8fdef6bb..9a710a72 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -129,10 +129,10 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) for _, serviceName := range serviceNames { // Change the service name to underscore splicing - newServiceName := serviceName + "_" + serviceName + newServiceName := serviceName wg.Start(func() { deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ - Name: newServiceName, + Name: serviceName + "_" + serviceName, Type: kind, Source: string(config.WorkflowType), PackageStorageURI: store.Endpoint + "/" + store.Bucket, -- Gitee From 5527a37bb262987d005a1c4f58f7153b29a17fc6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 17:57:45 +0800 Subject: [PATCH 1873/2647] fix cvm ops workflow bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 9a710a72..0e03f221 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -132,7 +132,7 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) newServiceName := serviceName wg.Start(func() { deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ - Name: serviceName + "_" + serviceName, + Name: newServiceName + "_" + newServiceName, Type: kind, Source: string(config.WorkflowType), PackageStorageURI: store.Endpoint + "/" + store.Bucket, -- Gitee From fab79149122f154c52a51208233f75a0dd7b1daa Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 19:27:39 +0800 Subject: [PATCH 1874/2647] fix cvm ops workflow bug Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/s3.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 0e03f221..4cd5ae2f 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -131,16 +131,24 @@ func ListTars(id, kind string, serviceNames []string, logger *zap.SugaredLogger) // Change the service name to underscore splicing newServiceName := serviceName wg.Start(func() { - deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(&commonrepo.DeliveryArtifactArgs{ - Name: newServiceName + "_" + newServiceName, + deliveryArtifactArgs := &commonrepo.DeliveryArtifactArgs{ + Name: newServiceName, Type: kind, Source: string(config.WorkflowType), PackageStorageURI: store.Endpoint + "/" + store.Bucket, - }) + } + deliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(deliveryArtifactArgs) + if err != nil { + logger.Errorf("ListTars err:%s", err) + return + } + deliveryArtifactArgs.Name = newServiceName + "_" + newServiceName + newDeliveryArtifacts, err := commonrepo.NewDeliveryArtifactColl().ListTars(deliveryArtifactArgs) if err != nil { logger.Errorf("ListTars err:%s", err) return } + deliveryArtifacts = append(deliveryArtifacts, newDeliveryArtifacts...) for _, deliveryArtifact := range deliveryArtifacts { activities, _, err := commonrepo.NewDeliveryActivityColl().List(&commonrepo.DeliveryActivityArgs{ArtifactID: deliveryArtifact.ID.Hex()}) if err != nil { -- Gitee From e1cc67e869f6d3754b2e6dd9d2e55179f317d8fd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 22:13:55 +0800 Subject: [PATCH 1875/2647] cvm add env hostnames variable Signed-off-by: liu deyi --- .../core/common/repository/models/task/build.go | 1 + .../core/workflow/service/workflow/workflow_task.go | 13 +++++++++---- .../core/service/taskplugin/artifact_deploy.go | 6 ++++++ .../warpdrive/core/service/taskplugin/build.go | 6 ++++++ .../warpdrive/core/service/types/task/build.go | 1 + 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index d5353efc..6132a2bb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -59,6 +59,7 @@ type Build struct { IsRestart bool `bson:"is_restart" json:"is_restart"` // Get the host bound to the environment of the cloud host service configuration EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + EnvHostNames map[string][]string `bson:"env_host_names,omitempty" json:"env_host_names,omitempty"` ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 6ca25993..416d1586 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1959,6 +1959,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if serviceTmpl != nil { build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) + envHostNames := make(map[string][]string) for _, envConfig := range serviceTmpl.EnvConfigs { privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) if err != nil { @@ -1966,16 +1967,19 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL continue } ips := sets.NewString() - ips = extractHostIPs(privateKeys, ips) + names := sets.NewString() + ips, names = extractHost(privateKeys, ips, names) privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) if err != nil { log.Errorf("ListNameByArgs labels err:%s", err) continue } - ips = extractHostIPs(privateKeys, ips) + ips, names = extractHost(privateKeys, ips, names) envHost[envConfig.EnvName] = ips.List() + envHostNames[envConfig.EnvName] = names.List() } build.EnvHostInfo = envHost + build.EnvHostNames = envHostNames } if args.Env != nil { @@ -2097,11 +2101,12 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL return subTasks, nil } -func extractHostIPs(privateKeys []*commonmodels.PrivateKey, ips sets.String) sets.String { +func extractHost(privateKeys []*commonmodels.PrivateKey, ips, names sets.String) (sets.String, sets.String) { for _, privateKey := range privateKeys { ips.Insert(privateKey.IP) + names.Insert(privateKey.Name) } - return ips + return ips, names } func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index a21b0fd9..fcbc24e4 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -126,6 +126,12 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) } + // env host names + for envName, names := range p.Task.EnvHostNames { + envHostKeysVar := &task.KeyVal{Key: envName + "_HOST_NAMEs", Value: strings.Join(names, ","), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) + } + // ARTIFACT if p.Task.ArtifactInfo != nil { var workspace = "/workspace" diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 9f7ce925..b6d8426c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -183,6 +183,12 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) } + // env host names + for envName, names := range p.Task.EnvHostNames { + envHostKeysVar := &task.KeyVal{Key: envName + "_HOST_NAMEs", Value: strings.Join(names, ","), IsCredential: false} + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envHostKeysVar) + } + // ARTIFACT if p.Task.JobCtx.FileArchiveCtx != nil { var workspace = "/workspace" diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 8c685a8f..0305efb0 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -58,6 +58,7 @@ type Build struct { IsRestart bool `bson:"is_restart" json:"is_restart"` // Get the host bound to the environment of the cloud host service configuration EnvHostInfo map[string][]string `bson:"env_host_info,omitempty" json:"env_host_info,omitempty"` + EnvHostNames map[string][]string `bson:"env_host_names,omitempty" json:"env_host_names,omitempty"` ArtifactInfo *ArtifactInfo `bson:"artifact_info,omitempty" json:"artifact_info,omitempty"` ClusterID string `bson:"cluster_id,omitempty" json:"cluster_id,omitempty"` -- Gitee From cca0db4da64b9dce9be1d44713f303c6ba9bdb4f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 23:01:24 +0800 Subject: [PATCH 1876/2647] cvm add env hostnames variable Signed-off-by: liu deyi --- .../aslan/core/common/repository/mongodb/private_key.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index c2f97eb7..e4ffea7a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -195,6 +195,7 @@ func (c *PrivateKeyColl) ListHostIPByArgs(args *ListHostIPArgs) ([]*models.Priva opt := options.Find() selector := bson.D{ {"ip", 1}, + {"name", 1}, } opt.SetProjection(selector) cursor, err := c.Collection.Find(ctx, query, opt) -- Gitee From b75158f03b5238335ecaf6b3675f3f4bb05c5e87 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 23:29:54 +0800 Subject: [PATCH 1877/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 416d1586..9f350898 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2105,6 +2105,8 @@ func extractHost(privateKeys []*commonmodels.PrivateKey, ips, names sets.String) for _, privateKey := range privateKeys { ips.Insert(privateKey.IP) names.Insert(privateKey.Name) + fmt.Println(fmt.Sprintf("privateKey.ip:%s", privateKey.IP)) + fmt.Println(fmt.Sprintf("privateKey.Name:%s", privateKey.Name)) } return ips, names } -- Gitee From fc82c85c9a86bbeccaccd386291f10e502c38eaa Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 17 Mar 2022 23:55:02 +0800 Subject: [PATCH 1878/2647] remove debug code Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 9f350898..416d1586 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2105,8 +2105,6 @@ func extractHost(privateKeys []*commonmodels.PrivateKey, ips, names sets.String) for _, privateKey := range privateKeys { ips.Insert(privateKey.IP) names.Insert(privateKey.Name) - fmt.Println(fmt.Sprintf("privateKey.ip:%s", privateKey.IP)) - fmt.Println(fmt.Sprintf("privateKey.Name:%s", privateKey.Name)) } return ips, names } -- Gitee From d8e4df025217a7016d5641303e3878a9541b3d37 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 18 Mar 2022 16:09:49 +0800 Subject: [PATCH 1879/2647] add delete service logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 713389ba..61f76feb 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1724,7 +1724,75 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l log.Errorf("find product error: %v", err) return err } + if getProjectType(productName) == setting.HelmDeployType { + return deleteHelmProductServices(productInfo, serviceNames, log) + } else { + return deleteK8sProductServices(productInfo, serviceNames, log) + } +} + +func deleteHelmProductServices(productInfo *commonmodels.Product, serviceNames []string, log *zap.SugaredLogger) error { + // init helm client + restConfig, err := kube.GetRESTConfig(productInfo.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + helmClient, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + + deleteServiceSet := sets.NewString(serviceNames...) + for serviceGroupIndex, serviceGroup := range productInfo.Services { + var group []*commonmodels.ProductService + for _, service := range serviceGroup { + if !deleteServiceSet.Has(service.ServiceName) { + group = append(group, service) + } + } + err := commonrepo.NewProductColl().UpdateGroup(productInfo.EnvName, productInfo.ProductName, serviceGroupIndex, group) + if err != nil { + log.Errorf("update product error: %v", err) + return err + } + } + renderset, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + Name: productInfo.Namespace, + }) + if err != nil { + log.Errorf("get renderSet error: %v", err) + return err + } + rcs := make([]*template.RenderChart, 0) + for _, v := range renderset.ChartInfos { + if !deleteServiceSet.Has(v.ServiceName) { + rcs = append(rcs, v) + } + } + renderset.ChartInfos = rcs + err = commonrepo.NewRenderSetColl().Update(renderset) + if err != nil { + log.Errorf("failed to update renderset, name: %s, err: %s", renderset.Name, err) + return err + } + + // uninstall services + for _, service := range deleteServiceSet.List() { + go func(namespace, serviceName string) { + log.Infof("uninstall release:%s", util.GeneHelmReleaseName(namespace, serviceName)) + if err = helmClient.UninstallRelease(&helmclient.ChartSpec{ + ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), + Namespace: namespace, + Wait: true, + }); err != nil { + log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) + } + }(productInfo.Namespace, service) + } + return nil +} +func deleteK8sProductServices(productInfo *commonmodels.Product, serviceNames []string, log *zap.SugaredLogger) error { for serviceGroupIndex, serviceGroup := range productInfo.Services { var group []*commonmodels.ProductService for _, service := range serviceGroup { @@ -1732,7 +1800,7 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l group = append(group, service) } } - err := commonrepo.NewProductColl().UpdateGroup(envName, productName, serviceGroupIndex, group) + err := commonrepo.NewProductColl().UpdateGroup(productInfo.EnvName, productInfo.ProductName, serviceGroupIndex, group) if err != nil { log.Errorf("update product error: %v", err) return err @@ -1763,7 +1831,7 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l return err } for _, name := range serviceNames { - selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: name}.AsSelector() + selector := labels.Set{setting.ProductLabel: productInfo.ProductName, setting.ServiceLabel: name}.AsSelector() err = commonservice.DeleteNamespacedResource(productInfo.Namespace, selector, productInfo.ClusterID, log) if err != nil { //删除失败仅记录失败日志 @@ -1883,6 +1951,10 @@ func getProjectType(productName string) string { return projectType } + if projectInfo.ProductFeature.DeployType == setting.HelmDeployType { + return setting.HelmDeployType + } + if projectInfo.ProductFeature.DeployType == setting.K8SDeployType && projectInfo.ProductFeature.BasicFacility == setting.BasicFacilityK8S { return projectType } -- Gitee From a6bf0b52d14579ec1c117c88a077f5907497e012 Mon Sep 17 00:00:00 2001 From: lxm Date: Fri, 18 Mar 2022 16:26:45 +0800 Subject: [PATCH 1880/2647] * fix: not-200 response code won't throw a error when download chart * fix: chart download failed when url not matches with zadig hard code Signed-off-by: lxm --- pkg/tool/helmclient/chartclient.go | 56 ++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go index 1de925ec..03fdfe8d 100644 --- a/pkg/tool/helmclient/chartclient.go +++ b/pkg/tool/helmclient/chartclient.go @@ -22,16 +22,19 @@ import ( "io/ioutil" "net/http" "path/filepath" + "strings" cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" "github.com/chartmuseum/helm-push/pkg/helm" "github.com/pkg/errors" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/repo" "github.com/koderover/zadig/pkg/tool/log" ) type ChartRepoClient struct { + RepoURL string *cm.Client } @@ -45,7 +48,10 @@ func NewHelmChartRepoClient(url, userName, password string) (*ChartRepoClient, e if err != nil { return nil, err } - return &ChartRepoClient{client}, nil + return &ChartRepoClient{ + RepoURL: url, + Client: client, + }, nil } // FetchIndexYaml fetch index.yaml from remote chart repo @@ -77,13 +83,20 @@ func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath s chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) chartTGZFilePath := filepath.Join(basePath, chartTGZName) - response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) + entry, err := client.GetChartFromIndex(chartName, chartVersion) + if err != nil { + return errors.Wrapf(err, "failed to get chart [%s]-[%s] info", chartName, chartVersion) + } + if len(entry.URLs) == 0 { + return errors.Wrapf(err, "failed to get chart [%s]-[%s] url", chartName, chartVersion) + } + response, err := client.downloadFileWithFullURL(entry.URLs[0]) if err != nil { return errors.Wrapf(err, "failed to download file") } if response.StatusCode != http.StatusOK { - return errors.Wrapf(err, "download file failed") + return fmt.Errorf("download file failed with status code:%d", response.StatusCode) } defer response.Body.Close() @@ -96,14 +109,20 @@ func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath s // DownloadAndExpand downloads chart from repo and expand chart files from tarball func (client *ChartRepoClient) DownloadAndExpand(chartName, chartVersion, localPath string) error { - chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) - response, err := client.DownloadFile(fmt.Sprintf("charts/%s", chartTGZName)) + entry, err := client.GetChartFromIndex(chartName, chartVersion) + if err != nil { + return errors.Wrapf(err, "failed to get chart [%s]-[%s] info", chartName, chartVersion) + } + if len(entry.URLs) == 0 { + return errors.Wrapf(err, "failed to get chart [%s]-[%s] url", chartName, chartVersion) + } + response, err := client.downloadFileWithFullURL(entry.URLs[0]) if err != nil { return errors.Wrapf(err, "failed to download file") } - if response.StatusCode != 200 { - return errors.Wrapf(err, "download file failed") + if response.StatusCode != http.StatusOK { + return fmt.Errorf("download file failed with status code:%d", response.StatusCode) } defer func() { _ = response.Body.Close() }() @@ -148,3 +167,26 @@ func handlePushResponse(resp *http.Response) error { log.Info("push chart to chart repo done") return nil } + +func (client *ChartRepoClient) GetChartFromIndex(chartName, chartVersion string) (*repo.ChartVersion, error) { + index, err := client.FetchIndexYaml() + if err != nil { + return nil, errors.Wrapf(err, "fetch index failed") + } + for name, entries := range index.Entries { + if strings.Compare(name, chartName) == 0 { + for _, entry := range entries { + if strings.Compare(entry.Version, chartVersion) == 0 { + return entry, nil + } + } + } + } + return nil, fmt.Errorf("failed to find chart [%s]-[%s]", chartName, chartVersion) +} + +func (client *ChartRepoClient) downloadFileWithFullURL(url string) (*http.Response, error) { + client.Option(cm.URL(url)) + defer client.Option(cm.URL(client.RepoURL)) + return client.DownloadFile("") +} -- Gitee From 306de24716594f566f418380134471488d9e896c Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 18 Mar 2022 15:48:36 +0800 Subject: [PATCH 1881/2647] fix bug of executing in pod Signed-off-by: zhangyifei --- .../service/workflow/workflow_task.go | 1 + .../service/taskplugin/artifact_deploy.go | 50 +++++++++++- .../core/service/taskplugin/build.go | 30 ++++++- .../core/service/taskplugin/build_v3.go | 9 ++- .../core/service/taskplugin/docker_build.go | 9 ++- .../core/service/taskplugin/jenkins_plugin.go | 8 +- .../warpdrive/core/service/taskplugin/job.go | 79 ++++++++++++------- .../core/service/taskplugin/release_image.go | 8 +- .../core/service/taskplugin/testing.go | 28 ++++++- pkg/tool/kube/podexec/exec.go | 10 ++- 10 files changed, 193 insertions(+), 39 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 6ca25993..9f6cc99f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1540,6 +1540,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, } envs = append(envs, &commonmodels.KeyVal{Key: "TEST_URL", Value: GetLink(pt, configbase.SystemAddress(), config.WorkflowType)}) envs = append(envs, &commonmodels.KeyVal{Key: "SERVICES", Value: services}) + envs = append(envs, &commonmodels.KeyVal{Key: "WORKSPACE", Value: "/workspace"}) testTask.JobCtx.EnvVars = envs testTask.ImageID = testModule.PreTest.ImageID diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index a21b0fd9..42b3caaa 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -27,11 +27,15 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -46,6 +50,8 @@ func InitializeArtifactTaskPlugin(taskType config.TaskType) TaskPlugin { return &ArtifactDeployTaskPlugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } @@ -56,6 +62,8 @@ type ArtifactDeployTaskPlugin struct { JobName string FileName string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config Task *task.Build Log *zap.SugaredLogger @@ -105,6 +113,46 @@ func (p *ArtifactDeployTaskPlugin) SetBuildStatusCompleted(status config.Status) } func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + switch p.Task.ClusterID { + case setting.LocalClusterID: + p.KubeNamespace = zadigconfig.Namespace() + default: + p.KubeNamespace = setting.AttachedClusterNamespace + + kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get kube client: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.kubeClient = kubeClient + + clientset, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get clientset: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.clientset = clientset + + restConfig, err := kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get clientset: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.restConfig = restConfig + } + envName := pipelineTask.WorkflowArgs.Namespace envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameVar) @@ -258,7 +306,7 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T // Wait ... func (p *ArtifactDeployTaskPlugin) Wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetBuildStatusCompleted(status) if status == config.StatusPassed { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 9f7ce925..9bf76aa0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -28,6 +28,8 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" zadigconfig "github.com/koderover/zadig/pkg/config" @@ -49,6 +51,8 @@ func InitializeBuildTaskPlugin(taskType config.TaskType) TaskPlugin { return &BuildTaskPlugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } @@ -59,6 +63,8 @@ type BuildTaskPlugin struct { JobName string FileName string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config Task *task.Build Log *zap.SugaredLogger @@ -132,6 +138,28 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe return } p.kubeClient = kubeClient + + clientset, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get clientset: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.clientset = clientset + + restConfig, err := kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get clientset: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + p.restConfig = restConfig } // not local cluster @@ -329,7 +357,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } func (p *BuildTaskPlugin) Wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetBuildStatusCompleted(status) if status == config.StatusPassed { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go index 47cfe978..884dbc9a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -26,6 +26,8 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" @@ -45,9 +47,12 @@ func InitializeBuildTaskV3Plugin(taskType config.TaskType) TaskPlugin { return &BuildTaskV3Plugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } +// Note: Deprecated. // BuildTaskV3Plugin is Plugin, name should be compatible with task type type BuildTaskV3Plugin struct { Name config.TaskType @@ -55,6 +60,8 @@ type BuildTaskV3Plugin struct { JobName string FileName string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config Task *task.Build Log *zap.SugaredLogger @@ -237,7 +244,7 @@ func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pi // Wait ... func (p *BuildTaskV3Plugin) Wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetBuildStatusCompleted(status) if status == config.StatusPassed { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go index cb9d526d..350c9578 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/docker_build.go @@ -24,6 +24,8 @@ import ( "go.uber.org/zap" yaml "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" @@ -41,6 +43,8 @@ type DockerBuildPlugin struct { JobName string FileName string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config Task *task.DockerBuild Log *zap.SugaredLogger } @@ -53,11 +57,14 @@ const ( DockerBuildTimeout = 60 * 60 // 60 minutes ) +// Note: Deprecated. // InitializeDockerBuildTaskPlugin init docker build plugin func InitializeDockerBuildTaskPlugin(taskType config.TaskType) TaskPlugin { return &DockerBuildPlugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } @@ -217,7 +224,7 @@ func (p *DockerBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, pi // Wait ... func (p *DockerBuildPlugin) Wait(ctx context.Context) { - status := waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.Log) + status := waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetStatus(status) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 945171b7..141074c6 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -24,6 +24,8 @@ import ( "github.com/bndr/gojenkins" "go.uber.org/zap" yaml "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/jenkinsplugin/core/service" @@ -44,6 +46,8 @@ func InitializeJenkinsBuildPlugin(taskType config.TaskType) TaskPlugin { return &JenkinsBuildPlugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } @@ -52,6 +56,8 @@ type JenkinsBuildPlugin struct { Name config.TaskType KubeNamespace string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config JobName string FileName string Task *task.JenkinsBuild @@ -195,7 +201,7 @@ func (j *JenkinsBuildPlugin) Run(ctx context.Context, pipelineTask *task.Task, p // Wait ... func (j *JenkinsBuildPlugin) Wait(ctx context.Context) { - jobStatus := waitJobEnd(ctx, j.TaskTimeout(), j.KubeNamespace, j.JobName, j.kubeClient, j.Log) + jobStatus := waitJobEnd(ctx, j.TaskTimeout(), j.KubeNamespace, j.JobName, j.kubeClient, j.clientset, j.restConfig, j.Log) jenkinsClient, err := gojenkins.CreateJenkins(nil, j.Task.JenkinsIntegration.URL, j.Task.JenkinsIntegration.Username, j.Task.JenkinsIntegration.Password).Init(ctx) if err != nil { j.SetStatus(config.StatusFailed) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 97e9a010..1fe635bb 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -37,6 +37,8 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" @@ -559,29 +561,47 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa job.Spec.Template.Spec.Affinity = affinity } - if linkedNs != "" && execNs != "" && pipelineTask.ConfigPayload.CustomDNSSupported { - job.Spec.Template.Spec.DNSConfig = &corev1.PodDNSConfig{ - Searches: []string{ - linkedNs + ".svc.cluster.local", - execNs + ".svc.cluster.local", - "svc.cluster.local", - "cluster.local", - }, - } - - if addresses, lookupErr := lookupKubeDNSServerHost(); lookupErr == nil { - job.Spec.Template.Spec.DNSPolicy = corev1.DNSNone - // https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-config - // There can be at most 3 IP addresses specified - job.Spec.Template.Spec.DNSConfig.Nameservers = addresses[:Min(3, len(addresses))] - value := "5" - job.Spec.Template.Spec.DNSConfig.Options = []corev1.PodDNSConfigOption{ - {Name: "ndots", Value: &value}, - } - } else { - log.SugaredLogger().Errorf("failed to find ip of kube dns %v", lookupErr) - } - } + // Note: + // The following logic is valid for the testing task and configures dNSconfig as follows: + // ``` + // dnsConfig: + // nameservers: + // - 192.168.0.157 + // options: + // - name: ndots + // value: "5" + // searches: + // - piggymetrics-env-dev.svc.cluster.local + // - koderover-agent.svc.cluster.local + // - svc.cluster.local + // - cluster.local + // ``` + // + // These are intra-cluster domain names that can be satisfied by default `ClusterFirst` DNSPolicy. + // No one knows why there is such logic, comment it out, run it for a while and then delete it. + // + // if linkedNs != "" && execNs != "" && pipelineTask.ConfigPayload.CustomDNSSupported { + // job.Spec.Template.Spec.DNSConfig = &corev1.PodDNSConfig{ + // Searches: []string{ + // linkedNs + ".svc.cluster.local", + // execNs + ".svc.cluster.local", + // "svc.cluster.local", + // "cluster.local", + // }, + + // if addresses, lookupErr := lookupKubeDNSServerHost(); lookupErr == nil { + // job.Spec.Template.Spec.DNSPolicy = corev1.DNSNone + // // https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-config + // // There can be at most 3 IP addresses specified + // job.Spec.Template.Spec.DNSConfig.Nameservers = addresses[:Min(3, len(addresses))] + // value := "5" + // job.Spec.Template.Spec.DNSConfig.Options = []corev1.PodDNSConfigOption{ + // {Name: "ndots", Value: &value}, + // } + // } else { + // log.SugaredLogger().Errorf("failed to find ip of kube dns %v", lookupErr) + // } + // } return job, nil } @@ -758,8 +778,8 @@ func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSp //waitJobEnd //Returns job status -func waitJobEnd(ctx context.Context, taskTimeout int, namspace, jobName string, kubeClient client.Client, xl *zap.SugaredLogger) (status config.Status) { - return waitJobEndWithFile(ctx, taskTimeout, namspace, jobName, false, kubeClient, xl) +func waitJobEnd(ctx context.Context, taskTimeout int, namspace, jobName string, kubeClient client.Client, clientset kubernetes.Interface, restConfig *rest.Config, xl *zap.SugaredLogger) (status config.Status) { + return waitJobEndWithFile(ctx, taskTimeout, namspace, jobName, false, kubeClient, clientset, restConfig, xl) } func waitJobReady(ctx context.Context, namespace, jobName string, kubeClient client.Client, xl *zap.SugaredLogger) (status config.Status) { @@ -808,7 +828,7 @@ func waitJobReady(ctx context.Context, namespace, jobName string, kubeClient cli return config.StatusRunning } -func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, checkFile bool, kubeClient client.Client, xl *zap.SugaredLogger) (status config.Status) { +func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, checkFile bool, kubeClient client.Client, clientset kubernetes.Interface, restConfig *rest.Config, xl *zap.SugaredLogger) (status config.Status) { xl.Infof("wait job to start: %s/%s", namespace, jobName) timeout := time.After(time.Duration(taskTimeout) * time.Second) podTimeout := time.After(120 * time.Second) @@ -876,7 +896,7 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName } if !ipod.Finished() { - jobStatus, exists, err = checkDogFoodExistsInContainer(namespace, ipod.Name, ipod.ContainerNames()[0]) + jobStatus, exists, err = checkDogFoodExistsInContainer(clientset, restConfig, namespace, ipod.Name, ipod.ContainerNames()[0]) if err != nil { xl.Errorf("Failed to check dog food file %s: %s.", pods[0].Name, err) break @@ -907,11 +927,10 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName time.Sleep(time.Second * 1) } - } -func checkDogFoodExistsInContainer(namespace string, pod string, container string) (commontypes.JobStatus, bool, error) { - stdout, _, success, err := podexec.ExecWithOptions(podexec.ExecOptions{ +func checkDogFoodExistsInContainer(clientset kubernetes.Interface, restConfig *rest.Config, namespace, pod, container string) (commontypes.JobStatus, bool, error) { + stdout, _, success, err := podexec.KubeExec(clientset, restConfig, podexec.ExecOptions{ Command: []string{"/bin/sh", "-c", fmt.Sprintf("test -f %[1]s && cat %[1]s", setting.DogFood)}, Namespace: namespace, PodName: pod, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index fd34f27b..eab1c706 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -24,6 +24,8 @@ import ( "go.uber.org/zap" yaml "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" @@ -39,6 +41,8 @@ func InitializeReleaseImagePlugin(taskType config.TaskType) TaskPlugin { return &ReleaseImagePlugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } @@ -49,6 +53,8 @@ type ReleaseImagePlugin struct { JobName string FileName string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config Task *task.ReleaseImage Log *zap.SugaredLogger } @@ -206,7 +212,7 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p // Wait ... func (p *ReleaseImagePlugin) Wait(ctx context.Context) { - status := waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.Log) + status := waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetStatus(status) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 856530a9..2cb4d48d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -27,6 +27,8 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" zadigconfig "github.com/koderover/zadig/pkg/config" @@ -47,6 +49,8 @@ func InitializeTestTaskPlugin(taskType config.TaskType) TaskPlugin { return &TestPlugin{ Name: taskType, kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), } } @@ -57,6 +61,8 @@ type TestPlugin struct { JobName string FileName string kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config Task *task.Testing Log *zap.SugaredLogger } @@ -124,6 +130,26 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC return } p.kubeClient = kubeClient + + clientset, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get clientset: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.clientset = clientset + + restConfig, err := kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + msg := fmt.Sprintf("failed to get clientset: %s", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + return + } + p.restConfig = restConfig } // not local cluster @@ -287,7 +313,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } func (p *TestPlugin) Wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.Log) + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetStatus(status) } diff --git a/pkg/tool/kube/podexec/exec.go b/pkg/tool/kube/podexec/exec.go index 5adcb830..d00edc54 100644 --- a/pkg/tool/kube/podexec/exec.go +++ b/pkg/tool/kube/podexec/exec.go @@ -23,7 +23,9 @@ import ( "strings" corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" "k8s.io/utils/exec" @@ -45,9 +47,13 @@ type ExecOptions struct { // returning stdout, stderr and error. `options` allowed for // additional parameters to be passed. func ExecWithOptions(options ExecOptions) (string, string, bool, error) { + return KubeExec(krkubeclient.Clientset(), krkubeclient.RESTConfig(), options) +} + +func KubeExec(kclient kubernetes.Interface, restConfig *rest.Config, options ExecOptions) (string, string, bool, error) { const tty = false - req := krkubeclient.Clientset().CoreV1().RESTClient().Post(). + req := kclient.CoreV1().RESTClient().Post(). Resource("pods"). Name(options.PodName). Namespace(options.Namespace). @@ -62,7 +68,7 @@ func ExecWithOptions(options ExecOptions) (string, string, bool, error) { TTY: tty, }, scheme.ParameterCodec) - executor, err := remotecommand.NewSPDYExecutor(krkubeclient.RESTConfig(), http.MethodPost, req.URL()) + executor, err := remotecommand.NewSPDYExecutor(restConfig, http.MethodPost, req.URL()) if err != nil { return "", "", false, err } -- Gitee From 3aaf85724d6d609f556c39e763ace6a4176321ee Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 18 Mar 2022 18:50:53 +0800 Subject: [PATCH 1882/2647] use a public method to init controller-runtime client, clientset and restConfig Signed-off-by: zhangyifei --- .../service/taskplugin/artifact_deploy.go | 29 +++---------------- .../core/service/taskplugin/build.go | 29 +++---------------- .../core/service/taskplugin/testing.go | 27 +++-------------- .../core/service/taskplugin/utils.go | 23 +++++++++++++++ 4 files changed, 35 insertions(+), 73 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index 42b3caaa..25859a5c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -35,7 +35,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -119,37 +118,17 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T default: p.KubeNamespace = setting.AttachedClusterNamespace - kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + crClient, clientset, restConfig, err := GetK8sClients(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { - msg := fmt.Sprintf("failed to get kube client: %s", err) - p.Log.Error(msg) + p.Log.Error(err) p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg + p.Task.Error = err.Error() p.SetBuildStatusCompleted(config.StatusFailed) return } - p.kubeClient = kubeClient - clientset, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) - if err != nil { - msg := fmt.Sprintf("failed to get clientset: %s", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return - } + p.kubeClient = crClient p.clientset = clientset - - restConfig, err := kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) - if err != nil { - msg := fmt.Sprintf("failed to get clientset: %s", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return - } p.restConfig = restConfig } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 9bf76aa0..79884fd7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -36,7 +36,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/types" @@ -128,37 +127,17 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe default: p.KubeNamespace = setting.AttachedClusterNamespace - kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + crClient, clientset, restConfig, err := GetK8sClients(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { - msg := fmt.Sprintf("failed to get kube client: %s", err) - p.Log.Error(msg) + p.Log.Error(err) p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg + p.Task.Error = err.Error() p.SetBuildStatusCompleted(config.StatusFailed) return } - p.kubeClient = kubeClient - clientset, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) - if err != nil { - msg := fmt.Sprintf("failed to get clientset: %s", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return - } + p.kubeClient = crClient p.clientset = clientset - - restConfig, err := kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) - if err != nil { - msg := fmt.Sprintf("failed to get clientset: %s", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return - } p.restConfig = restConfig } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 2cb4d48d..ca850aae 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -37,7 +37,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" - kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" s3tool "github.com/koderover/zadig/pkg/tool/s3" @@ -121,34 +120,16 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC default: p.KubeNamespace = setting.AttachedClusterNamespace - kubeClient, err := kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + crClient, clientset, restConfig, err := GetK8sClients(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) if err != nil { - msg := fmt.Sprintf("failed to get kube client: %s", err) - p.Log.Error(msg) + p.Log.Error(err) p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg + p.Task.Error = err.Error() return } - p.kubeClient = kubeClient - clientset, err := kubeclient.GetClientset(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) - if err != nil { - msg := fmt.Sprintf("failed to get clientset: %s", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - return - } + p.kubeClient = crClient p.clientset = clientset - - restConfig, err := kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) - if err != nil { - msg := fmt.Sprintf("failed to get clientset: %s", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - return - } p.restConfig = restConfig } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index ff69bd93..4630574a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -27,9 +27,13 @@ import ( t "github.com/docker/docker/api/types" "github.com/docker/docker/client" "go.uber.org/zap" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + crClient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kodo" ) @@ -219,3 +223,22 @@ func ToExtensionTask(sb map[string]interface{}) (*task.Extension, error) { } return extension, nil } + +func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes.Interface, *rest.Config, error) { + controllerRuntimeClient, err := kubeclient.GetKubeClient(hubServerAddr, clusterID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get controller runtime client: %s", err) + } + + clientset, err := kubeclient.GetKubeClientSet(hubServerAddr, clusterID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get clientset: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(hubServerAddr, clusterID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get rest config: %s", err) + } + + return controllerRuntimeClient, clientset, restConfig, nil +} -- Gitee From f0c0b19e2fae76cafe298798ee122d61f379d5f0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 20 Mar 2022 14:47:25 +0800 Subject: [PATCH 1883/2647] modify stage afterall Signed-off-by: liu deyi --- .../aslan/core/common/repository/models/pipeline.go | 2 +- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/pipeline.go b/pkg/microservice/aslan/core/common/repository/models/pipeline.go index 8be7b23f..aa1c122b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/pipeline.go +++ b/pkg/microservice/aslan/core/common/repository/models/pipeline.go @@ -65,7 +65,7 @@ type Stage struct { RunParallel bool `bson:"run_parallel" json:"run_parallel"` Desc string `bson:"desc,omitempty" json:"desc,omitempty"` SubTasks map[string]map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` - AfterAll bool `json:"after_all" bson:"after_all"` + AfterAll bool `bson:"after_all" json:"after_all"` } type Hook struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 02f86a8b..85d08e12 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2639,7 +2639,7 @@ func AddSubtaskToStage(stages *[]*commonmodels.Stage, subTask map[string]interfa RunParallel: true, } - if subTaskPre.TaskType == config.TaskResetImage { + if subTaskPre.TaskType == config.TaskResetImage || subTaskPre.TaskType == config.TaskExtension { stage.AfterAll = true } // 除了测试模块,其他的都可以并行跑 -- Gitee From 106dc48e6bb3a32e81f69fe6ea09f87eefe0fa43 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 20 Mar 2022 16:49:18 +0800 Subject: [PATCH 1884/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/workflow/workflow_task.go | 2 +- .../core/service/taskcontroller/task_handler.go | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 85d08e12..02f86a8b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2639,7 +2639,7 @@ func AddSubtaskToStage(stages *[]*commonmodels.Stage, subTask map[string]interfa RunParallel: true, } - if subTaskPre.TaskType == config.TaskResetImage || subTaskPre.TaskType == config.TaskExtension { + if subTaskPre.TaskType == config.TaskResetImage { stage.AfterAll = true } // 除了测试模块,其他的都可以并行跑 diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 84b583d3..bd3c395c 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -346,13 +346,20 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe } // Stage之间仅支持串行 + isBreak := false for stagePosition, stage := range pipelineTask.Stages { - if !stage.AfterAll { + if stage.AfterAll { + continue + } + + if !isBreak || stage.TaskType == config.TaskExtension { h.runStage(stagePosition, stage, pipelineTask.ConfigPayload.BuildConcurrency) - // 如果一个Stage执行失败了,跳出执行循环,并且更新pipelinetask状态为失败,发送ACK,并返回 - if stage.Status == config.StatusFailed || stage.Status == config.StatusCancelled || stage.Status == config.StatusTimeout { - break - } + } + + // 如果一个Stage执行失败了,跳出执行循环,并且更新pipelinetask状态为失败,发送ACK,并返回 + if stage.Status == config.StatusFailed || stage.Status == config.StatusCancelled || stage.Status == config.StatusTimeout { + isBreak = true + continue } } -- Gitee From 2fab758f9ac9cfe475bc3ec18a5877460062aed8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 20 Mar 2022 17:38:15 +0800 Subject: [PATCH 1885/2647] add logic to ensure helm chart upgrade Signed-off-by: allenshen --- .../core/environment/service/environment.go | 15 +---- .../service/environment_creator.go | 13 +--- .../aslan/core/environment/service/image.go | 8 +-- .../core/service/taskplugin/deploy.go | 2 +- pkg/tool/helmclient/helmclient.go | 62 ++++++++++++++++++- 5 files changed, 67 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4e142939..dfbc5f07 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2575,7 +2575,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha return err } -func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, kubecli client.Client, log *zap.SugaredLogger) { +func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { var ( err error errList = &multierror.Error{} @@ -2755,12 +2755,8 @@ func updateProductGroup(username, productName, envName, updateType string, produ productServiceMap = make(map[string]*commonmodels.ProductService) productTemplServiceMap = make(map[string]*commonmodels.ProductService) ) - restConfig, err := kube.GetRESTConfig(productResp.ClusterID) - if err != nil { - return e.ErrUpdateEnv.AddErr(err) - } - helmClient, err := helmtool.NewClientFromRestConf(restConfig, productResp.Namespace) + helmClient, err := helmtool.NewClientFromNamespace(productResp.ClusterID, productResp.Namespace) if err != nil { return e.ErrUpdateEnv.AddErr(err) } @@ -3078,12 +3074,7 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey } func updateProductVariable(productName, envName string, productResp *commonmodels.Product, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { - restConfig, err := kube.GetRESTConfig(productResp.ClusterID) - if err != nil { - return e.ErrUpdateEnv.AddErr(err) - } - - helmClient, err := helmtool.NewClientFromRestConf(restConfig, productResp.Namespace) + helmClient, err := helmtool.NewClientFromNamespace(productResp.ClusterID, productResp.Namespace) if err != nil { return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 64701c1c..1522b150 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -27,11 +27,10 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" - "github.com/koderover/zadig/pkg/tool/helmclient" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/informer" ) @@ -147,14 +146,8 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P args.Namespace = namespace } - restConfig, err := kube.GetRESTConfig(args.ClusterID) + helmClient, err := helmtool.NewClientFromNamespace(args.ClusterID, args.Namespace) if err != nil { - log.Errorf("GetRESTConfig error: %v", err) - return e.ErrCreateEnv.AddDesc(err.Error()) - } - helmClient, err := helmclient.NewClientFromRestConf(restConfig, args.Namespace) - if err != nil { - log.Errorf("[%s][%s] NewClientFromRestConf error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) } @@ -224,7 +217,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P eventStart := time.Now().Unix() - go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, kubeClient, log) + go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 938f40ff..ec7199dd 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -22,7 +22,6 @@ import ( helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" - "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -127,7 +126,6 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri replacedValuesYaml string mergedValuesYaml string replacedMergedValuesYaml string - restConfig *rest.Config helmClient helmclient.Client namespace string = product.Namespace ) @@ -171,11 +169,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri return err } - restConfig, err = kube.GetRESTConfig(product.ClusterID) - if err != nil { - return err - } - helmClient, err = helmtool.NewClientFromRestConf(restConfig, namespace) + helmClient, err = helmtool.NewClientFromNamespace(product.ClusterID, namespace) if err != nil { return err } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 32778651..a8d03400 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -507,7 +507,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * p.Log.Infof("final replaced merged values: \n%s", replacedMergedValuesYaml) - helmClient, err = helmtool.NewClientFromRestConf(p.restConfig, p.Task.Namespace) + helmClient, err = helmtool.NewClientFromNamespace(pipelineTask.ConfigPayload.DeployClusterID, p.Task.Namespace) if err != nil { err = errors.WithMessagef( err, diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index a375ec4d..d92e211a 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -31,6 +31,7 @@ import ( "helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/strvals" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -38,14 +39,51 @@ import ( "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type HelmClient struct { *hc.HelmClient + kubeClient client.Client + Namespace string +} + +func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { + restConfig, err := kube.GetRESTConfig(clusterID) + if err != nil { + return nil, err + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, err + } + + hcClient, err := hc.NewClientFromRestConf(&hc.RestConfClientOptions{ + Options: &hc.Options{ + Namespace: namespace, + DebugLog: log.Debugf, + }, + RestConfig: restConfig, + }) + if err != nil { + return nil, err + } + + helmClient := hcClient.(*hc.HelmClient) + return &HelmClient{ + helmClient, + kubeClient, + namespace, + }, nil } // NewClientFromRestConf returns a new Helm client constructed with the provided REST config options @@ -64,6 +102,8 @@ func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error helmClient := hcClient.(*hc.HelmClient) return &HelmClient{ helmClient, + nil, + ns, }, nil } @@ -265,8 +305,12 @@ func (hClient *HelmClient) upgradeCRD(ctx context.Context, k8sClient *clientset. // check weather to install or upgrade chart by current status // return error if neither install nor upgrade action is legal func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) { + historyReleaseCount := 10 + if spec.MaxHistory > 0 { + historyReleaseCount = spec.MaxHistory + } // find history of particular release - releases, err := hClient.ListReleaseHistory(spec.ReleaseName, 10) + releases, err := hClient.ListReleaseHistory(spec.ReleaseName, historyReleaseCount) if err != nil && err != driver.ErrReleaseNotFound { return false, err } @@ -285,13 +329,13 @@ func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) // release with deployed/failed/superseded status: normal upgrade operation if lastRelease.Info.Status == release.StatusDeployed || lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded { - return false, nil + return false, hClient.ensureUpgrade(historyReleaseCount, spec.ReleaseName, releases) } // find deployed revision with status deployed from history, would be upgrade operation for _, rel := range releases { if rel.Info.Status == release.StatusDeployed { - return false, nil + return false, hClient.ensureUpgrade(historyReleaseCount, spec.ReleaseName, releases) } } @@ -303,6 +347,18 @@ func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) return false, fmt.Errorf("can't install or upgrade chart with status: %s", lastRelease.Info.Status) } +// ensure new release revision can be saved +func (hClient *HelmClient) ensureUpgrade(maxHistoryCount int, releaseName string, releases []*release.Release) error { + if maxHistoryCount <= 0 || len(releases) < maxHistoryCount { + return nil + } + secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, releaseName, releases[len(releases)-1].Version) + if hClient.kubeClient == nil { + return errors.New("kubeClient is nil") + } + return updater.DeleteSecretWithName(hClient.Namespace, secretName, hClient.kubeClient) +} + // getChart returns a chart matching the provided chart name and options. func (hClient *HelmClient) getChart(chartName string, chartPathOptions *action.ChartPathOptions) (*chart.Chart, string, error) { chartPath, err := chartPathOptions.LocateChart(chartName, hClient.HelmClient.Settings) -- Gitee From 8335e9a228494b2fe86977b929b897d0533729c7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sun, 20 Mar 2022 17:42:30 +0800 Subject: [PATCH 1886/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/fs/file.go | 33 +++++++++++++++---- .../aslan/core/common/service/fs/s3.go | 2 +- .../aslan/core/common/service/helm.go | 8 +++-- .../aslan/core/service/service/helm.go | 2 +- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index 666cc404..e3fd1c54 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -82,14 +82,33 @@ func SaveAndUploadFiles(fileTree fs.FS, names []string, localBase, s3Base string return err } -// CopyFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. -func CopyFiles(localBase, currentChartPath string, logger *zap.SugaredLogger) error { - copyErr := copy.Copy(currentChartPath, localBase) - if copyErr != nil { - logger.Errorf("failed to copy chart info, err %s", copyErr) - } +// CopyAndUploadFiles copy a tree of files to other dir, at the same time, archives them and uploads to object storage. +func CopyAndUploadFiles(names []string, localBase, s3Base, currentChartPath string, logger *zap.SugaredLogger) error { + var wg wait.Group + var err error + + wg.Start(func() { + copyErr := copy.Copy(currentChartPath, localBase) + if copyErr != nil { + logger.Errorf("failed to copy chart info, err %s", copyErr) + err = copyErr + } + }) + + wg.Start(func() { + fileTree := os.DirFS(currentChartPath) + if s3Base == "" { + return + } + err2 := ArchiveAndUploadFilesToS3(fileTree, names, s3Base, logger) + if err2 != nil { + logger.Errorf("Failed to upload files to s3, err: %s", err2) + err = err2 + } + }) - return copyErr + wg.Wait() + return err } func saveInMemoryFilesToDisk(fileTree fs.FS, root string, logger *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index b69d6cc8..c0607d45 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -188,7 +188,7 @@ func DownloadAndCopyFilesFromGerrit(name, localBase string, logger *zap.SugaredL return err } - if err := CopyFiles(path.Join(localBase, path.Base(chartTemplate.Path)), path.Join(base, chartTemplate.Path), logger); err != nil { + if err := CopyAndUploadFiles([]string{}, path.Join(localBase, path.Base(chartTemplate.Path)), "", path.Join(base, chartTemplate.Path), logger); err != nil { log.Errorf("Failed to copy files for helm chart template %s, error: %s", name, err) return err } diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 45fb5407..eb59628d 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -97,10 +97,12 @@ func SaveAndUploadService(projectName, serviceName string, copies []string, file return fsservice.SaveAndUploadFiles(fileTree, names, localBase, s3Base, log.SugaredLogger()) } -func CopyFiles(projectName, serviceName, currentChartPath string, copies []string) error { +func CopyAndUploadService(projectName, serviceName, currentChartPath string, copies []string) error { localBase := config.LocalServicePath(projectName, serviceName) + s3Base := config.ObjectStorageServicePath(projectName, serviceName) + names := append([]string{serviceName}, copies...) - return fsservice.CopyFiles(path.Join(localBase, serviceName), currentChartPath, log.SugaredLogger()) + return fsservice.CopyAndUploadFiles(names, path.Join(localBase, serviceName), s3Base, currentChartPath, log.SugaredLogger()) } func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { @@ -138,7 +140,7 @@ func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { return err } // save files to disk and upload them to s3 - if err := CopyFiles(svc.ProductName, svc.ServiceName, svc.GerritPath, nil); err != nil { + if err := CopyAndUploadService(svc.ProductName, svc.ServiceName, svc.GerritPath, nil); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", svc.ServiceName, svc.ProductName, err) return err } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 9a30498e..d8d437a2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -665,7 +665,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr }() // copy to latest dir and upload to s3 - if err = commonservice.CopyFiles(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}); err != nil { + if err = commonservice.CopyAndUploadService(projectName, serviceName, currentFilePath, []string{fmt.Sprintf("%s-%d", serviceName, rev)}); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", serviceName, projectName, err) finalErr = e.ErrCreateTemplate.AddErr(err) return -- Gitee From 6cb2181440cccb5e3c1302da35d7ac71a6753a21 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 21 Mar 2022 10:29:54 +0800 Subject: [PATCH 1887/2647] modify function Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/template/chart.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 74547cee..04cad498 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -452,7 +452,7 @@ func processChartFromGerrit(name string, args *fs.DownloadFromSourceArgs, logger logger.Debug("Start to save and upload chart") localBase := configbase.LocalChartTemplatePath(name) - err1 := fs.CopyFiles(path.Join(localBase, path.Base(args.Path)), currentChartPath, logger) + err1 := fs.CopyAndUploadFiles([]string{}, path.Join(localBase, path.Base(args.Path)), "", currentChartPath, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) err = err1 -- Gitee From c98fdbc11e3f39d4b016d8f7aaaf4639a0894c02 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 21 Mar 2022 10:46:56 +0800 Subject: [PATCH 1888/2647] optimize code Signed-off-by: allenshen --- pkg/tool/helmclient/helmclient.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index d92e211a..2e8eac57 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -327,18 +327,18 @@ func (hClient *HelmClient) isInstallOperation(spec *hc.ChartSpec) (bool, error) return false, errors.New("another operation (install/upgrade/rollback) is in progress, please try later") } - // release with deployed/failed/superseded status: normal upgrade operation - if lastRelease.Info.Status == release.StatusDeployed || lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded { - return false, hClient.ensureUpgrade(historyReleaseCount, spec.ReleaseName, releases) - } - // find deployed revision with status deployed from history, would be upgrade operation for _, rel := range releases { if rel.Info.Status == release.StatusDeployed { - return false, hClient.ensureUpgrade(historyReleaseCount, spec.ReleaseName, releases) + return false, nil } } + // release with failed/superseded status: legal upgrade operation + if lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded { + return false, hClient.ensureUpgrade(historyReleaseCount, spec.ReleaseName, releases) + } + // if replace set to true, install will be a legal operation if st := lastRelease.Info.Status; spec.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) { return true, nil @@ -352,10 +352,15 @@ func (hClient *HelmClient) ensureUpgrade(maxHistoryCount int, releaseName string if maxHistoryCount <= 0 || len(releases) < maxHistoryCount { return nil } - secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, releaseName, releases[len(releases)-1].Version) + for _, re := range releases { + if re.Info.Status == release.StatusDeployed { + return nil + } + } if hClient.kubeClient == nil { return errors.New("kubeClient is nil") } + secretName := fmt.Sprintf("%s.%s.v%d", storage.HelmStorageType, releaseName, releases[len(releases)-1].Version) return updater.DeleteSecretWithName(hClient.Namespace, secretName, hClient.kubeClient) } -- Gitee From 944bcb83d8154a36d0499143993b2aa18e582bf4 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 17 Mar 2022 09:52:26 +0800 Subject: [PATCH 1889/2647] support subpath Signed-off-by: zhangyifei --- .../workflow/service/workflow/workflow_task.go | 15 +++++++++++++++ .../warpdrive/core/service/taskplugin/build.go | 15 +++++++++++++++ .../warpdrive/core/service/taskplugin/job.go | 6 +++++- .../warpdrive/core/service/taskplugin/testing.go | 16 ++++++++-------- pkg/types/cache.go | 3 +++ 5 files changed, 46 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 02f86a8b..1a2fbc47 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1516,6 +1516,21 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, testTask.JobCtx.TestResultPath = testModule.TestResultPath testTask.JobCtx.TestReportPath = testModule.TestReportPath + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(testModule.PreTest.ClusterID) + if err != nil { + return nil, err + } + testTask.Cache = clusterInfo.Cache + + // If the cluster is not configured with a cache medium, the cache cannot be used, so don't enable cache explicitly. + if testTask.Cache.MediumType == "" { + testTask.CacheEnable = false + } else { + testTask.CacheEnable = testModule.CacheEnable + testTask.CacheDirType = testModule.CacheDirType + testTask.CacheUserDir = testModule.CacheUserDir + } + if testTask.Registries == nil { testTask.Registries = registries } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 99125d07..cd9f2ffa 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -173,6 +173,9 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameVar) } + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "WORKFLOW", Value: pipelineTask.WorkflowArgs.WorkflowName}) + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "PROJECT", Value: pipelineTask.WorkflowArgs.ProductTmplName}) + taskIDVar := &task.KeyVal{Key: "TASK_ID", Value: strconv.FormatInt(pipelineTask.TaskID, 10), IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) @@ -233,10 +236,22 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } } + // Note: Currently, `SERVICE` in the environment variable represents a service module. + // Since variable rendering is required next, the `SERVICE_MODULE` environment variable is added to accurately + // characterize the service module. + // Do not use 'p.task. ServiceName' as the value because it is in the 'ServiceModule_ServiceName' format. + for _, env := range p.Task.JobCtx.EnvVars { + if env.Key == "SERVICE" { + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "SERVICE_MODULE", Value: env.Value}) + break + } + } + // Since we allow users to use custom environment variables, variable resolution is required. if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == types.NFSMedium && pipelineCtx.CacheDirType == types.UserDefinedCacheDir { pipelineCtx.CacheUserDir = p.renderEnv(pipelineCtx.CacheUserDir) + pipelineCtx.Cache.NFSProperties.Subpath = p.renderEnv(pipelineCtx.Cache.NFSProperties.Subpath) } jobCtx := JobCtxBuilder{ diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 1fe635bb..929b0535 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -549,6 +549,7 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ Name: volumeName, MountPath: mountPath, + SubPath: ctx.Cache.NFSProperties.Subpath, }) } @@ -898,7 +899,10 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName if !ipod.Finished() { jobStatus, exists, err = checkDogFoodExistsInContainer(clientset, restConfig, namespace, ipod.Name, ipod.ContainerNames()[0]) if err != nil { - xl.Errorf("Failed to check dog food file %s: %s.", pods[0].Name, err) + // Note: + // Currently, this error indicates "the target Pod cannot be accessed" or "the target Pod can be accessed, but the dog food file does not exist". + // In these two scenarios, `Info` is used to print logs because they are not business semantic exceptions. + xl.Infof("Result of checking dog food file %s: %s", pods[0].Name, err) break } if !exists { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index ca850aae..e13012b9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -157,21 +157,18 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.Task.Error = "" var linkedNamespace string var envName string + + workflowName := pipelineTask.PipelineName if pipelineTask.Type == config.SingleType { linkedNamespace = pipelineTask.TaskArgs.Test.Namespace } else if pipelineTask.Type == config.WorkflowType { product := &types.Product{EnvName: pipelineTask.WorkflowArgs.Namespace, ProductName: pipelineTask.WorkflowArgs.ProductTmplName} linkedNamespace = product.ProductName + "-env-" + product.EnvName envName = pipelineTask.WorkflowArgs.Namespace - } - //if linkedNamespace == "" { - // msg := "namespace for testing is empty" - // p.Log.Error(msg) - // p.Task.TaskStatus = task.StatusFailed - // p.Task.Error = msg - // return - //} + workflowName = pipelineTask.WorkflowArgs.WorkflowName + } + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "WORKFLOW", Value: workflowName}) namespaceEnvVar := &task.KeyVal{Key: "DEPLOY_ENV", Value: p.KubeNamespace, IsCredential: false} linkedNamespaceEnvVar := &task.KeyVal{Key: "LINKED_ENV", Value: linkedNamespace, IsCredential: false} @@ -186,6 +183,8 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, namespaceEnvVar) p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, linkedNamespaceEnvVar) p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameEnvVar) + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "SERVICE", Value: pipelineTask.ServiceName}) + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "PROJECT", Value: pipelineTask.ProductName}) var testReportFile string // html 测试报告 fileName := fmt.Sprintf("%s-%s-%d-%s-%s", config.SingleType, pipelineTask.PipelineName, pipelineTask.TaskID, config.TaskTestingV2, pipelineTask.ServiceName) @@ -205,6 +204,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == commontypes.NFSMedium && pipelineCtx.CacheDirType == commontypes.UserDefinedCacheDir { pipelineCtx.CacheUserDir = p.renderEnv(pipelineCtx.CacheUserDir) + pipelineCtx.Cache.NFSProperties.Subpath = p.renderEnv(pipelineCtx.Cache.NFSProperties.Subpath) } jobCtx := JobCtxBuilder{ diff --git a/pkg/types/cache.go b/pkg/types/cache.go index 5343e5e6..a32b5d9e 100644 --- a/pkg/types/cache.go +++ b/pkg/types/cache.go @@ -44,6 +44,7 @@ type NFSProperties struct { StorageClass string `json:"storage_class" bson:"storage_class"` StorageSizeInGiB int64 `json:"storage_size_in_gib" bson:"storage_size_in_gib"` PVC string `json:"pvc" bson:"pvc"` + Subpath string `json:"subpath" bson:"subpath"` } type Cache struct { @@ -58,3 +59,5 @@ const ( WorkspaceCacheDir CacheDirType = "workspace" UserDefinedCacheDir CacheDirType = "user_defined" ) + +const DefaultSubpath = "$PROJECT/$WORKFLOW/$SERVICE" -- Gitee From b182b63c31edadaf29e98dc150e5a90896f30f6f Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 21 Mar 2022 11:32:06 +0800 Subject: [PATCH 1890/2647] add new contributor @lxm Signed-off-by: allenshen --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 94d3f1cc..175715c6 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -69,3 +69,4 @@ The council will make a decision for the nomination. The candidate has to have d - [xrfinbupt](https://github.com/xrfinbupt) - [mhatvan](https://github.com/mhatvan) - [tbbrave](https://github.com/tbbrave) +- [lxm](https://github.com/lxm) -- Gitee From c6107c3d11ad8990bbc7ce3e2dbe8e40ee6f998f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 21 Mar 2022 14:43:31 +0800 Subject: [PATCH 1891/2647] modify annotation Signed-off-by: liu deyi --- .../core/service/taskcontroller/task_handler.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index bd3c395c..00906d8e 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -345,20 +345,20 @@ func (h *ExecHandler) execute(ctx context.Context, pipelineTask *task.Task, pipe } } - // Stage之间仅支持串行 - isBreak := false + // Only serial is supported between stages + // If the stage status is StatusFailed/StatusCancelled/StatusTimeout, other than the extension stage will not be executed + isSkip := false for stagePosition, stage := range pipelineTask.Stages { if stage.AfterAll { continue } - if !isBreak || stage.TaskType == config.TaskExtension { + if !isSkip || stage.TaskType == config.TaskExtension { h.runStage(stagePosition, stage, pipelineTask.ConfigPayload.BuildConcurrency) } - // 如果一个Stage执行失败了,跳出执行循环,并且更新pipelinetask状态为失败,发送ACK,并返回 if stage.Status == config.StatusFailed || stage.Status == config.StatusCancelled || stage.Status == config.StatusTimeout { - isBreak = true + isSkip = true continue } } -- Gitee From 8bf739c595fbd7eac1f1e60e44ac74ced974fd5b Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 21 Mar 2022 14:48:25 +0800 Subject: [PATCH 1892/2647] fix bug of download chart from harbor repo chart Signed-off-by: allenshen --- pkg/tool/helmclient/chartclient.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go index 03fdfe8d..d81345be 100644 --- a/pkg/tool/helmclient/chartclient.go +++ b/pkg/tool/helmclient/chartclient.go @@ -21,6 +21,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" "path/filepath" "strings" @@ -90,15 +91,16 @@ func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath s if len(entry.URLs) == 0 { return errors.Wrapf(err, "failed to get chart [%s]-[%s] url", chartName, chartVersion) } - response, err := client.downloadFileWithFullURL(entry.URLs[0]) + + response, err := client.downloadFileWithURL(entry.URLs[0]) if err != nil { return errors.Wrapf(err, "failed to download file") } + defer response.Body.Close() if response.StatusCode != http.StatusOK { return fmt.Errorf("download file failed with status code:%d", response.StatusCode) } - defer response.Body.Close() b, err := ioutil.ReadAll(response.Body) if err != nil { @@ -116,7 +118,7 @@ func (client *ChartRepoClient) DownloadAndExpand(chartName, chartVersion, localP if len(entry.URLs) == 0 { return errors.Wrapf(err, "failed to get chart [%s]-[%s] url", chartName, chartVersion) } - response, err := client.downloadFileWithFullURL(entry.URLs[0]) + response, err := client.downloadFileWithURL(entry.URLs[0]) if err != nil { return errors.Wrapf(err, "failed to download file") } @@ -185,8 +187,14 @@ func (client *ChartRepoClient) GetChartFromIndex(chartName, chartVersion string) return nil, fmt.Errorf("failed to find chart [%s]-[%s]", chartName, chartVersion) } -func (client *ChartRepoClient) downloadFileWithFullURL(url string) (*http.Response, error) { - client.Option(cm.URL(url)) - defer client.Option(cm.URL(client.RepoURL)) - return client.DownloadFile("") +func (client *ChartRepoClient) downloadFileWithURL(chartUrl string) (*http.Response, error) { + u, _ := url.Parse(chartUrl) + // chart url is absolute path + if u.Scheme == "http" || u.Scheme == "https" { + client.Option(cm.URL(chartUrl)) + defer client.Option(cm.URL(client.RepoURL)) + return client.DownloadFile("") + } else { + return client.DownloadFile(chartUrl) + } } -- Gitee From d526fd9701bd71b8db63eef2c6328bae1061da3e Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 21 Mar 2022 15:22:43 +0800 Subject: [PATCH 1893/2647] add comments Signed-off-by: allenshen --- pkg/tool/helmclient/chartclient.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go index d81345be..8389d041 100644 --- a/pkg/tool/helmclient/chartclient.go +++ b/pkg/tool/helmclient/chartclient.go @@ -187,14 +187,18 @@ func (client *ChartRepoClient) GetChartFromIndex(chartName, chartVersion string) return nil, fmt.Errorf("failed to find chart [%s]-[%s]", chartName, chartVersion) } +// TODO this function should be deprecated +// we should use standard `helm pull` to provide better compatibility func (client *ChartRepoClient) downloadFileWithURL(chartUrl string) (*http.Response, error) { u, _ := url.Parse(chartUrl) - // chart url is absolute path + // chart url is absolute path in index.yaml, set url of option to full chart url and download directly + // eg: chart repo hosted by gitee if u.Scheme == "http" || u.Scheme == "https" { client.Option(cm.URL(chartUrl)) defer client.Option(cm.URL(client.RepoURL)) return client.DownloadFile("") - } else { - return client.DownloadFile(chartUrl) } + // chart url in index.yaml is relative path, set url of option to the repoUrl and download by fileName + // eg: chart repo hosted by harbor + return client.DownloadFile(chartUrl) } -- Gitee From 4dc1241ca694e7c5cfd312b33786586d97fc04dd Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 21 Mar 2022 15:52:15 +0800 Subject: [PATCH 1894/2647] fix incomplete collection of policybinding Signed-off-by: panxunying --- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 17d79c6c..7c8fb0cc 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -399,7 +399,9 @@ func generateOPABindings(rbs []*models.RoleBinding, pbs []*models.PolicyBinding) if _, ok := userPolicyMap[s.UID]; !ok { userPolicyMap[s.UID] = make(map[string][]*roleRef) } - userPolicyMap[s.UID][rb.Namespace] = append(userRoleMap[s.UID][rb.Namespace], &roleRef{Name: rb.PolicyRef.Name, Namespace: rb.PolicyRef.Namespace}) + policyBindingRefs := userPolicyMap[s.UID][rb.Namespace] + policyBindingRefs = append(policyBindingRefs, &roleRef{Name: rb.PolicyRef.Name, Namespace: rb.PolicyRef.Namespace}) + userPolicyMap[s.UID][rb.Namespace] = policyBindingRefs } } } -- Gitee From 754191660189c9586fccb4951a014b18ae09f744 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 21 Mar 2022 16:09:06 +0800 Subject: [PATCH 1895/2647] optimize Signed-off-by: panxunying --- pkg/microservice/policy/core/service/bundle/opa_bundle.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 7c8fb0cc..5baac302 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -399,9 +399,7 @@ func generateOPABindings(rbs []*models.RoleBinding, pbs []*models.PolicyBinding) if _, ok := userPolicyMap[s.UID]; !ok { userPolicyMap[s.UID] = make(map[string][]*roleRef) } - policyBindingRefs := userPolicyMap[s.UID][rb.Namespace] - policyBindingRefs = append(policyBindingRefs, &roleRef{Name: rb.PolicyRef.Name, Namespace: rb.PolicyRef.Namespace}) - userPolicyMap[s.UID][rb.Namespace] = policyBindingRefs + userPolicyMap[s.UID][rb.Namespace] = append(userPolicyMap[s.UID][rb.Namespace], &roleRef{Name: rb.PolicyRef.Name, Namespace: rb.PolicyRef.Namespace}) } } } -- Gitee From 5d68452e1278dc8c367471fb0fa7f5591fbec16c Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 21 Mar 2022 17:32:37 +0800 Subject: [PATCH 1896/2647] optimize code Signed-off-by: allenshen --- pkg/tool/helmclient/helmclient.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 2e8eac57..d074b01f 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -43,7 +43,6 @@ import ( "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" @@ -56,8 +55,10 @@ type HelmClient struct { Namespace string } +// NewClientFromNamespace returns a new Helm client constructed with the provided clusterID and namespace +// a kubeClient will be initialized to support necessary k8s operations when install/upgrade helm charts func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { - restConfig, err := kube.GetRESTConfig(clusterID) + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) if err != nil { return nil, err } @@ -87,6 +88,7 @@ func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { } // NewClientFromRestConf returns a new Helm client constructed with the provided REST config options +// used to list/uninstall helm release func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error) { hcClient, err := hc.NewClientFromRestConf(&hc.RestConfClientOptions{ Options: &hc.Options{ -- Gitee From c85ce94ee2baf851da0241c8da94742aef67ac01 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 21 Mar 2022 17:34:24 +0800 Subject: [PATCH 1897/2647] optimize code Signed-off-by: allenshen --- pkg/tool/helmclient/helmclient.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index d074b01f..6362060c 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -354,11 +354,6 @@ func (hClient *HelmClient) ensureUpgrade(maxHistoryCount int, releaseName string if maxHistoryCount <= 0 || len(releases) < maxHistoryCount { return nil } - for _, re := range releases { - if re.Info.Status == release.StatusDeployed { - return nil - } - } if hClient.kubeClient == nil { return errors.New("kubeClient is nil") } -- Gitee From a1b0ca775861c77de71319212585e14b5140120a Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 21 Mar 2022 19:08:40 +0800 Subject: [PATCH 1898/2647] change env SERVICE to SERVICE_MODULE in testing Signed-off-by: zhangyifei --- pkg/microservice/warpdrive/core/service/taskplugin/testing.go | 2 +- pkg/types/cache.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index e13012b9..1ad24c66 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -183,7 +183,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, namespaceEnvVar) p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, linkedNamespaceEnvVar) p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameEnvVar) - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "SERVICE", Value: pipelineTask.ServiceName}) + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "SERVICE_MODULE", Value: pipelineTask.ServiceName}) p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "PROJECT", Value: pipelineTask.ProductName}) var testReportFile string // html 测试报告 diff --git a/pkg/types/cache.go b/pkg/types/cache.go index a32b5d9e..90a8ba2f 100644 --- a/pkg/types/cache.go +++ b/pkg/types/cache.go @@ -59,5 +59,3 @@ const ( WorkspaceCacheDir CacheDirType = "workspace" UserDefinedCacheDir CacheDirType = "user_defined" ) - -const DefaultSubpath = "$PROJECT/$WORKFLOW/$SERVICE" -- Gitee From 994945560c2aa16aedb1d099915399db48510b78 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Mar 2022 10:32:47 +0800 Subject: [PATCH 1899/2647] The number of replicas of the service is temporarily adjusted to 1000 Signed-off-by: liu deyi --- .../aslan/core/environment/service/service.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 86a3b72c..8ddd28b7 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -69,9 +69,9 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap return e.ErrScaleService.AddErr(err) } - // TODO: 目前强制定死扩容数量为0-20, 以后可以改成配置 - if number > 20 || number < 0 { - return e.ErrInvalidParam.AddDesc("伸缩数量范围[0..20]") + // TODO: At present, the number of replicas is forced to be 0-1000, which can be changed to configuration in the future + if number > 1000 || number < 0 { + return e.ErrInvalidParam.AddDesc("伸缩数量范围[0..1000]") } selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceName}.AsSelector() @@ -105,8 +105,9 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap } func Scale(args *ScaleArgs, logger *zap.SugaredLogger) error { - if args.Number > 20 || args.Number < 0 { - return e.ErrInvalidParam.AddDesc("伸缩数量范围[0..20]") + // TODO: At present, the number of replicas is forced to be 0-1000, which can be changed to configuration in the future + if args.Number > 1000 || args.Number < 0 { + return e.ErrInvalidParam.AddDesc("伸缩数量范围[0..1000]") } opt := &commonrepo.ProductFindOptions{Name: args.ProductName, EnvName: args.EnvName} -- Gitee From 6e18e99c0d944b4db651d6bea3747986966b7ba0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Mar 2022 10:41:10 +0800 Subject: [PATCH 1900/2647] Remove the limit on the number of service replicas Signed-off-by: liu deyi --- .../aslan/core/environment/service/service.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 8ddd28b7..26c4a750 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -69,11 +69,6 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap return e.ErrScaleService.AddErr(err) } - // TODO: At present, the number of replicas is forced to be 0-1000, which can be changed to configuration in the future - if number > 1000 || number < 0 { - return e.ErrInvalidParam.AddDesc("伸缩数量范围[0..1000]") - } - selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceName}.AsSelector() ds, err := getter.ListDeployments(prod.Namespace, selector, kubeClient) if err != nil { @@ -105,11 +100,6 @@ func ScaleService(envName, productName, serviceName string, number int, log *zap } func Scale(args *ScaleArgs, logger *zap.SugaredLogger) error { - // TODO: At present, the number of replicas is forced to be 0-1000, which can be changed to configuration in the future - if args.Number > 1000 || args.Number < 0 { - return e.ErrInvalidParam.AddDesc("伸缩数量范围[0..1000]") - } - opt := &commonrepo.ProductFindOptions{Name: args.ProductName, EnvName: args.EnvName} prod, err := commonrepo.NewProductColl().Find(opt) if err != nil { -- Gitee From 8cfc868dba61adad4a4c213dfae1d2d2981f3c92 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 22 Mar 2022 11:59:51 +0800 Subject: [PATCH 1901/2647] fix env update bug Signed-off-by: allenshen --- .../core/environment/service/environment.go | 113 ++++++++++++------ 1 file changed, 77 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 4aab8103..43659e7e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -169,7 +169,7 @@ type UpdateMultiHelmProductArg struct { ProductName string `json:"productName"` EnvNames []string `json:"envNames"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` - DeletedServices []string `json:"deleted_services"` + DeletedServices []string `json:"deletedServices"` ReplacePolicy string `json:"replacePolicy"` // TODO logic not implemented } @@ -801,14 +801,15 @@ func prepareHelmProductCreation(templateProduct *templatemodels.Product, product // user custom chart values cvMap := make(map[string]*templatemodels.RenderChart) for _, singleCV := range arg.ChartValues { - if tc, ok := templateChartInfoMap[singleCV.ServiceName]; !ok { - return fmt.Errorf("failed to find service info in template_product, serviceName: %s", singleCV.ServiceName) - } else { - chartInfo := &templatemodels.RenderChart{} - singleCV.FillRenderChartModel(chartInfo, tc.ChartVersion) - productObj.ChartInfos = append(productObj.ChartInfos, chartInfo) - cvMap[singleCV.ServiceName] = chartInfo + tc, ok := templateChartInfoMap[singleCV.ServiceName] + if !ok { + return fmt.Errorf("failed to find chart info in product, serviceName: %s productName: %s", singleCV.ServiceName, templateProduct.ProjectName) } + chartInfo := &templatemodels.RenderChart{} + singleCV.FillRenderChartModel(chartInfo, tc.ChartVersion) + chartInfo.ValuesYaml = tc.ValuesYaml + productObj.ChartInfos = append(productObj.ChartInfos, chartInfo) + cvMap[singleCV.ServiceName] = chartInfo } // default values @@ -827,7 +828,7 @@ func prepareHelmProductCreation(templateProduct *templatemodels.Product, product serviceTmpl, ok := serviceTmplMap[serviceName] if !ok { - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to find service info in template_product, serviceName: %s", serviceName)) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to find service info in template_service, serviceName: %s", serviceName)) } serviceResp := &commonmodels.ProductService{ @@ -860,20 +861,18 @@ func prepareHelmProductCreation(templateProduct *templatemodels.Product, product productObj.Services = serviceGroup // insert renderset info into db - if len(productObj.ChartInfos) > 0 { - err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ - Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), - EnvName: arg.EnvName, - ProductTmpl: arg.ProductName, - UpdateBy: productObj.UpdateBy, - IsDefault: false, - DefaultValues: arg.DefaultValues, - ChartInfos: productObj.ChartInfos, - }, log) - if err != nil { - log.Errorf("rennderset create fail when copy creating helm product, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err) - return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err)) - } + err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ + Name: commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace), + EnvName: arg.EnvName, + ProductTmpl: arg.ProductName, + UpdateBy: productObj.UpdateBy, + IsDefault: false, + DefaultValues: arg.DefaultValues, + ChartInfos: productObj.ChartInfos, + }, log) + if err != nil { + log.Errorf("rennderset create fail when copy creating helm product, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err) + return e.ErrCreateEnv.AddDesc(fmt.Sprintf("failed to save chart values, productName: %s,envname:%s,err:%s", arg.ProductName, arg.EnvName, err)) } return nil } @@ -1077,6 +1076,12 @@ func CopyHelmProduct(productName, userName, requestID string, args []*CreateHelm templateServiceMap[svc.ServiceName] = svc } + // fill all chart infos from product renderset + err = commonservice.FillProductTemplateValuesYamls(templateProduct, log) + if err != nil { + return e.ErrCreateEnv.AddDesc(err.Error()) + } + for _, arg := range args { err := copySingleHelmProduct(templateProduct, productName, requestID, userName, arg, templateServiceMap, log) if err != nil { @@ -1094,6 +1099,8 @@ func copySingleHelmProduct(templateProduct *templatemodels.Product, productName, if err != nil { return fmt.Errorf("failed to query base product info name :%s,envname:%s", productName, arg.BaseEnvName) } + + sourceRendersetName := productInfo.Namespace productInfo.ID = primitive.NilObjectID productInfo.Revision = 1 productInfo.EnvName = arg.EnvName @@ -1102,6 +1109,25 @@ func copySingleHelmProduct(templateProduct *templatemodels.Product, productName, productInfo.BaseName = arg.BaseName productInfo.Namespace = commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace) + // merge chart infos, use chart info in product to override charts in template_project + sourceRenderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{Name: sourceRendersetName}) + if err != nil { + return errors.Wrapf(err, "failed to find source renderset: %s", productInfo.Namespace) + } + sourceChartMap := make(map[string]*templatemodels.RenderChart) + for _, singleChart := range sourceRenderSet.ChartInfos { + sourceChartMap[singleChart.ServiceName] = singleChart + } + templateCharts := templateProduct.ChartInfos + templateProduct.ChartInfos = make([]*templatemodels.RenderChart, 0) + for _, chart := range templateCharts { + if chartFromSource, ok := sourceChartMap[chart.ServiceName]; ok { + templateProduct.ChartInfos = append(templateProduct.ChartInfos, chartFromSource) + } else { + templateProduct.ChartInfos = append(templateProduct.ChartInfos, chart) + } + } + // fill services and chart infos of product err = prepareHelmProductCreation(templateProduct, productInfo, arg, serviceTmplMap, log) if err != nil { @@ -1134,26 +1160,33 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } - // use service definition from service template, but keep the image info deletedSvcSet := sets.NewString(deletedServices...) - productRespMap := productResp.GetServiceMap() + // services need to be created or updated + serviceNeedUpdateOrCreate := sets.NewString() + for _, chart := range overrideCharts { + serviceNeedUpdateOrCreate.Insert(chart.ServiceName) + } + // use service definition from service template, but keep the image info + productServiceMap := productResp.GetServiceMap() allServices := make([][]*commonmodels.ProductService, 0) for _, svrs := range updateProd.Services { svcGroup := make([]*commonmodels.ProductService, 0) for _, svr := range svrs { - ps, ok := productRespMap[svr.ServiceName] - if !ok { + if deletedSvcSet.Has(svr.ServiceName) { continue } - if deletedSvcSet.Has(svr.ServiceName) { + ps, ok := productServiceMap[svr.ServiceName] + if !ok && !serviceNeedUpdateOrCreate.Has(svr.ServiceName) { continue } svcGroup = append(svcGroup, svr) - for _, svrc := range svr.Containers { - for _, psc := range ps.Containers { - if svrc.Name == psc.Name { - svrc.Image = psc.Image - break + if ps != nil { + for _, svrc := range svr.Containers { + for _, psc := range ps.Containers { + if svrc.Name == psc.Name { + svrc.Image = psc.Image + break + } } } } @@ -1770,10 +1803,18 @@ func deleteHelmProductServices(productInfo *commonmodels.Product, serviceNames [ } } renderset.ChartInfos = rcs - err = commonrepo.NewRenderSetColl().Update(renderset) + + // create new renderset + if err := commonservice.CreateHelmRenderSet(renderset, log); err != nil { + log.Error("failed to create renderset, name %s, err: %s", renderset.Name, err) + return e.ErrUpdateEnv.AddErr(err) + } + + productInfo.Render.Revision = renderset.Revision + err = commonrepo.NewProductColl().UpdateRender(renderset.EnvName, productInfo.ProductName, productInfo.Render) if err != nil { - log.Errorf("failed to update renderset, name: %s, err: %s", renderset.Name, err) - return err + log.Errorf("failed to update product render info, renderName: %s, err: %s", productInfo.Render.Name, err) + return e.ErrUpdateEnv.AddErr(err) } // uninstall services -- Gitee From ea7d012319a50f094ea5aa8cbc89786712bec7d0 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 22 Mar 2022 17:52:14 +0800 Subject: [PATCH 1902/2647] Fix test task unable to get build logs due to case Signed-off-by: ddh27 --- pkg/microservice/aslan/core/log/handler/log.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 25f83244..68d4e5eb 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -70,7 +70,7 @@ func GetTestJobContainerLogs(c *gin.Context) { } // Use all lowercase job names to avoid subdomain errors - ctx.Resp, ctx.Err = logservice.GetTestJobContainerLogs(c.Param("pipelineName"), c.Param("testName"), taskID, ctx.Logger) + ctx.Resp, ctx.Err = logservice.GetTestJobContainerLogs(strings.ToLower(c.Param("pipelineName")), c.Param("testName"), taskID, ctx.Logger) } func GetWorkflowTestJobContainerLogs(c *gin.Context) { @@ -84,7 +84,7 @@ func GetWorkflowTestJobContainerLogs(c *gin.Context) { } // Use all lowercase job names to avoid subdomain errors - ctx.Resp, ctx.Err = logservice.GetWorkflowTestJobContainerLogs(c.Param("pipelineName"), c.Param("serviceName"), c.Query("workflowType"), taskID, ctx.Logger) + ctx.Resp, ctx.Err = logservice.GetWorkflowTestJobContainerLogs(strings.ToLower(c.Param("pipelineName")), c.Param("serviceName"), c.Query("workflowType"), taskID, ctx.Logger) } func GetContainerLogs(c *gin.Context) { -- Gitee From 13b2b474c8a6a9c5a68c81216f74974ce0e1a549 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 22 Mar 2022 17:55:04 +0800 Subject: [PATCH 1903/2647] fix create env from existed ns failed bug Signed-off-by: allenshen --- .../core/common/repository/mongodb/render_set.go | 14 ++++++++++---- .../aslan/core/common/service/render.go | 11 ++++------- .../aslan/core/environment/service/environment.go | 11 ++++------- .../aslan/core/service/service/helm.go | 2 +- .../aslan/core/service/service/service.go | 2 +- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go index 3f683efd..ed80aa4e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go @@ -42,8 +42,9 @@ type RenderSetListOption struct { // RenderSetFindOption ... type RenderSetFindOption struct { // if Revision == 0 then search max revision of RenderSet - Revision int64 - Name string + ProductTmpl string + Revision int64 + Name string } type RenderSetPipeResp struct { @@ -168,8 +169,9 @@ func (c *RenderSetColl) List(opt *RenderSetListOption) ([]*models.RenderSet, err var resp []*models.RenderSet for _, pipe := range pipeResp { optRender := &RenderSetFindOption{ - Revision: pipe.Revision, - Name: pipe.RenderSet.Name, + Revision: pipe.Revision, + Name: pipe.RenderSet.Name, + ProductTmpl: pipe.RenderSet.ProductTmpl, } if pipe.Revision == 0 && pipe.RenderSet.Name == "" { continue @@ -208,6 +210,10 @@ func (c *RenderSetColl) Find(opt *RenderSetFindOption) (*models.RenderSet, error opts.SetSort(bson.D{{"revision", -1}}) } + if len(opt.ProductTmpl) > 0 { + query["product_tmpl"] = opt.ProductTmpl + } + rs := &models.RenderSet{} err := c.FindOne(context.TODO(), query, opts).Decode(&rs) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index b993c67e..8466be0b 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -153,6 +153,7 @@ func GetRenderSet(renderName string, revision int64, log *zap.SugaredLogger) (*c return resp, nil } +// GetRenderSetInfo func GetRenderSetInfo(renderName string, revision int64) (*commonmodels.RenderSet, error) { opt := &commonrepo.RenderSetFindOption{ Name: renderName, @@ -171,17 +172,13 @@ func ValidateRenderSet(productName, renderName string, serviceInfo *templatemode resp := &commonmodels.RenderSet{ProductTmpl: productName} var err error if renderName != "" { - opt := &commonrepo.RenderSetFindOption{Name: renderName} + opt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName} resp, err = commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("find renderset[%s] error: %v", renderName, err) return resp, err } } - if renderName != "" && resp.ProductTmpl != productName { - log.Errorf("renderset[%s] not match product[%s]", renderName, productName) - return resp, fmt.Errorf("renderset[%s] not match product[%s]", renderName, productName) - } if serviceInfo == nil { //if err := IsAllKeyCovered(resp, log); err != nil { // log.Errorf("[%s]cover all key [%s] error: %v", productName, renderName, err) @@ -221,7 +218,7 @@ func mergeKVs(newKVs []*templatemodels.RenderKV, oldKVs []*templatemodels.Render } func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { - opt := &commonrepo.RenderSetFindOption{Name: args.Name} + opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} rs, err := commonrepo.NewRenderSetColl().Find(opt) if rs != nil && err == nil { // 已经存在渲染配置集 @@ -248,7 +245,7 @@ func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error // CreateHelmRenderSet 添加renderSet func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { - opt := &commonrepo.RenderSetFindOption{Name: args.Name} + opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} rs, err := commonrepo.NewRenderSetColl().Find(opt) if rs != nil && err == nil { // 已经存在渲染配置集 diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index dfbc5f07..9481f1c1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1737,7 +1737,8 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l } } rs, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Name: productInfo.Namespace, + Name: productInfo.Namespace, + ProductTmpl: productName, }) if err != nil { log.Errorf("get renderSet error: %v", err) @@ -2509,17 +2510,13 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( resp := &commonmodels.RenderSet{ProductTmpl: productName} var err error if renderName != "" { - opt := &commonrepo.RenderSetFindOption{Name: renderName} + opt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName} resp, err = commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("find helm renderset[%s] error: %v", renderName, err) return resp, err } } - if renderName != "" && resp.ProductTmpl != productName { - log.Errorf("helm renderset[%s] not match product[%s]", renderName, productName) - return resp, fmt.Errorf("helm renderset[%s] not match product[%s]", renderName, productName) - } return resp, nil } @@ -2875,7 +2872,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return nil, err } // 系统默认的变量 - latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName}) + latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName, ProductTmpl: productName}) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d8d437a2..850f0e6f 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1444,7 +1444,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, createdBy string, log *zap.SugaredLogger) { // 对比上个版本的renderset,新增一个版本 latestChartInfos := make([]*templatemodels.RenderChart, 0) - renderOpt := &commonrepo.RenderSetFindOption{Name: productName} + renderOpt := &commonrepo.RenderSetFindOption{Name: productName, ProductTmpl: productName} if latestDefaultRenderSet, err := commonrepo.NewRenderSetColl().Find(renderOpt); err == nil { latestChartInfos = latestDefaultRenderSet.ChartInfos } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index b7f2fcf0..c674de15 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1036,7 +1036,7 @@ func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, // remove specific services from rendersets.chartinfos func removeServiceFromRenderset(productName, renderName, serviceName string) error { - renderOpt := &commonrepo.RenderSetFindOption{Name: renderName} + renderOpt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName} if rs, err := commonrepo.NewRenderSetColl().Find(renderOpt); err == nil { chartInfos := make([]*templatemodels.RenderChart, 0) for _, chartInfo := range rs.ChartInfos { -- Gitee From d7ec5187ab9b724a85ee5440b7ec1d5677f90ae3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 22 Mar 2022 18:07:36 +0800 Subject: [PATCH 1904/2647] update data Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/render.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 8466be0b..4ba74b44 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -153,7 +153,6 @@ func GetRenderSet(renderName string, revision int64, log *zap.SugaredLogger) (*c return resp, nil } -// GetRenderSetInfo func GetRenderSetInfo(renderName string, revision int64) (*commonmodels.RenderSet, error) { opt := &commonrepo.RenderSetFindOption{ Name: renderName, -- Gitee From 610cb9f5d009c5ea974309b55c7122020db42431 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 22 Mar 2022 20:23:17 +0800 Subject: [PATCH 1905/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- pkg/microservice/aslan/core/service/service/helm.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 9481f1c1..587db829 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2872,7 +2872,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return nil, err } // 系统默认的变量 - latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName, ProductTmpl: productName}) + latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName}) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 850f0e6f..d8d437a2 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1444,7 +1444,7 @@ func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, createdBy string, log *zap.SugaredLogger) { // 对比上个版本的renderset,新增一个版本 latestChartInfos := make([]*templatemodels.RenderChart, 0) - renderOpt := &commonrepo.RenderSetFindOption{Name: productName, ProductTmpl: productName} + renderOpt := &commonrepo.RenderSetFindOption{Name: productName} if latestDefaultRenderSet, err := commonrepo.NewRenderSetColl().Find(renderOpt); err == nil { latestChartInfos = latestDefaultRenderSet.ChartInfos } -- Gitee From c5307b5e4b1e0eb736f0259c0d33c6637781e668 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Mar 2022 20:26:38 +0800 Subject: [PATCH 1906/2647] A bug in handling workflow history task query Signed-off-by: liu deyi --- .../core/workflow/service/workflow/pipeline_task.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 3911fc3b..fd29fed6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -317,7 +317,8 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Detail: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} for _, svc := range filters { - serviceNameFiltersMap[svc] = nil + containerName := strings.Split(svc, "_")[0] + serviceNameFiltersMap[containerName] = nil } case "taskStatus": listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuses: filters, Detail: true, Type: typeString} @@ -346,7 +347,8 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer if buildStage != nil { for serviceName := range buildStage.SubTasks { if queryType == "serviceName" { - if _, ok := serviceNameFiltersMap[serviceName]; ok { + containerName := strings.Split(serviceName, "_")[0] + if _, ok := serviceNameFiltersMap[containerName]; ok { existSvc = true } } @@ -355,7 +357,8 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer } else if deployStage != nil { for serviceName := range deployStage.SubTasks { if queryType == "serviceName" { - if _, ok := serviceNameFiltersMap[serviceName]; ok { + containerName := strings.Split(serviceName, "_")[0] + if _, ok := serviceNameFiltersMap[containerName]; ok { existSvc = true } } -- Gitee From a7d0afbf39154da7d913acb2a59c6fc1a25eab54 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 22 Mar 2022 21:20:56 +0800 Subject: [PATCH 1907/2647] optimize query Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index fd29fed6..10df9609 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -441,11 +441,13 @@ func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeS } if buildStage != nil { for serviceName := range buildStage.SubTasks { - svcSets.Insert(serviceName) + containerName := strings.Split(serviceName, "_")[0] + svcSets.Insert(containerName) } } else if deployStage != nil { for serviceName := range deployStage.SubTasks { - svcSets.Insert(serviceName) + containerName := strings.Split(serviceName, "_")[0] + svcSets.Insert(containerName) } } buildStage, deployStage = nil, nil -- Gitee From b48f7ecdfbd8451192e60c0ccd9279a6ab2b31f7 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Wed, 23 Mar 2022 15:35:43 +0800 Subject: [PATCH 1908/2647] update registry secret for K8s Helm Chart demo (#1206) Signed-off-by: fansi Co-authored-by: fansi --- .../general-chart/templates/deployment.yaml | 4 +--- .../general-chart/templates/secret.yaml | 10 ---------- examples/multi-service-demo/general-chart/values.yaml | 5 ++--- examples/multi-service-demo/values/service1.yaml | 3 --- examples/multi-service-demo/values/service2.yaml | 3 --- examples/multi-service-demo/values/service3.yaml | 3 --- 6 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 examples/multi-service-demo/general-chart/templates/secret.yaml diff --git a/examples/multi-service-demo/general-chart/templates/deployment.yaml b/examples/multi-service-demo/general-chart/templates/deployment.yaml index 7f2e70d3..e441d6b8 100644 --- a/examples/multi-service-demo/general-chart/templates/deployment.yaml +++ b/examples/multi-service-demo/general-chart/templates/deployment.yaml @@ -33,7 +33,5 @@ spec: requests: cpu: {{ .Values.resources.requests.cpu }} memory: {{ .Values.resources.requests.mem }} - {{- if .Values.registryConf }} imagePullSecrets: - - name: {{ .Values.imagePullSecretsName }} - {{- end }} \ No newline at end of file + - name: {{ .Values.imagePullSecretsName }} \ No newline at end of file diff --git a/examples/multi-service-demo/general-chart/templates/secret.yaml b/examples/multi-service-demo/general-chart/templates/secret.yaml deleted file mode 100644 index 60417294..00000000 --- a/examples/multi-service-demo/general-chart/templates/secret.yaml +++ /dev/null @@ -1,10 +0,0 @@ -{{- if .Values.registryConf -}} -apiVersion: v1 -data: - .dockercfg: {{ .Values.registryConf }} -kind: Secret -metadata: - name: {{ .Values.imagePullSecretsName }} -type: kubernetes.io/dockercfg - -{{- end -}} \ No newline at end of file diff --git a/examples/multi-service-demo/general-chart/values.yaml b/examples/multi-service-demo/general-chart/values.yaml index 74d04023..4de0f35b 100644 --- a/examples/multi-service-demo/general-chart/values.yaml +++ b/examples/multi-service-demo/general-chart/values.yaml @@ -1,9 +1,8 @@ fullnameOverride: $T-Service$ replicaCount: 1 -port: {{.port}} +port: 20200 -registryConf: "" -imagePullSecretsName: "$T-Service$-registry-secret" +imagePullSecretsName: "default-registry-secret" image: repository: "ccr.ccs.tencentyun.com/koderover-public/$T-Service$" diff --git a/examples/multi-service-demo/values/service1.yaml b/examples/multi-service-demo/values/service1.yaml index cad204c4..985938cc 100644 --- a/examples/multi-service-demo/values/service1.yaml +++ b/examples/multi-service-demo/values/service1.yaml @@ -2,9 +2,6 @@ fullnameOverride: "service1" replicaCount: 1 port: 20221 -imagePullSecretsName: "secret-service1" -registryConf: "" - image: repository: "ccr.ccs.tencentyun.com/koderover-public/service1" tag: "latest" diff --git a/examples/multi-service-demo/values/service2.yaml b/examples/multi-service-demo/values/service2.yaml index 651c75dd..d6218882 100644 --- a/examples/multi-service-demo/values/service2.yaml +++ b/examples/multi-service-demo/values/service2.yaml @@ -2,9 +2,6 @@ fullnameOverride: "service2" replicaCount: 1 port: 20222 -imagePullSecretsName: "secret-service2" -registryConf: "" - image: repository: "ccr.ccs.tencentyun.com/koderover-public/service2" tag: "latest" diff --git a/examples/multi-service-demo/values/service3.yaml b/examples/multi-service-demo/values/service3.yaml index 13de1091..6fd1ddc9 100644 --- a/examples/multi-service-demo/values/service3.yaml +++ b/examples/multi-service-demo/values/service3.yaml @@ -2,9 +2,6 @@ fullnameOverride: "service3" replicaCount: 1 port: 20223 -imagePullSecretsName: "secret-service3" -registryConf: "" - image: repository: "ccr.ccs.tencentyun.com/koderover-public/service3" tag: "latest" -- Gitee From 8331bb1cf5f64442cddc9c546856a6b55cee3b04 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Wed, 23 Mar 2022 16:14:19 +0800 Subject: [PATCH 1909/2647] add base-chart for K8s Helm Chart demo (#1207) Signed-off-by: fansi Co-authored-by: fansi --- .../multi-service-demo/base-chart/Chart.yaml | 24 +++++++ .../base-chart/templates/_helpers.tpl | 63 +++++++++++++++++++ .../base-chart/templates/deployment.yaml | 37 +++++++++++ .../base-chart/templates/service.yaml | 16 +++++ .../multi-service-demo/base-chart/values.yaml | 18 ++++++ .../general-chart/values.yaml | 2 +- .../multi-service-demo/values/service1.yaml | 2 + .../multi-service-demo/values/service2.yaml | 2 + .../multi-service-demo/values/service3.yaml | 2 + 9 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 examples/multi-service-demo/base-chart/Chart.yaml create mode 100644 examples/multi-service-demo/base-chart/templates/_helpers.tpl create mode 100644 examples/multi-service-demo/base-chart/templates/deployment.yaml create mode 100644 examples/multi-service-demo/base-chart/templates/service.yaml create mode 100644 examples/multi-service-demo/base-chart/values.yaml diff --git a/examples/multi-service-demo/base-chart/Chart.yaml b/examples/multi-service-demo/base-chart/Chart.yaml new file mode 100644 index 00000000..08e787e5 --- /dev/null +++ b/examples/multi-service-demo/base-chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: general-chart +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/examples/multi-service-demo/base-chart/templates/_helpers.tpl b/examples/multi-service-demo/base-chart/templates/_helpers.tpl new file mode 100644 index 00000000..2668feb5 --- /dev/null +++ b/examples/multi-service-demo/base-chart/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +helm.sh/chart: {{ include "chart.chart" . }} +{{ include "chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/examples/multi-service-demo/base-chart/templates/deployment.yaml b/examples/multi-service-demo/base-chart/templates/deployment.yaml new file mode 100644 index 00000000..e441d6b8 --- /dev/null +++ b/examples/multi-service-demo/base-chart/templates/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + spec: + containers: + - name: {{ .Values.fullnameOverride }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: Always + command: + - /workspace/{{ .Values.fullnameOverride }} + ports: + - protocol: TCP + containerPort: {{ .Values.port }} + resources: + limits: + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.mem }} + requests: + cpu: {{ .Values.resources.requests.cpu }} + memory: {{ .Values.resources.requests.mem }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} \ No newline at end of file diff --git a/examples/multi-service-demo/base-chart/templates/service.yaml b/examples/multi-service-demo/base-chart/templates/service.yaml new file mode 100644 index 00000000..d21ce064 --- /dev/null +++ b/examples/multi-service-demo/base-chart/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + type: NodePort + ports: + - protocol: TCP + port: {{ .Values.port }} + targetPort: {{ .Values.port }} \ No newline at end of file diff --git a/examples/multi-service-demo/base-chart/values.yaml b/examples/multi-service-demo/base-chart/values.yaml new file mode 100644 index 00000000..005f6ebf --- /dev/null +++ b/examples/multi-service-demo/base-chart/values.yaml @@ -0,0 +1,18 @@ +fullnameOverride: service +replicaCount: 1 +port: 20221 + +imagePullSecretsName: "service-registry-secret" + +image: + repository: "ccr.ccs.tencentyun.com/koderover-public/service" + tag: "latest" + +resources: + requests: + cpu: 10m + mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi diff --git a/examples/multi-service-demo/general-chart/values.yaml b/examples/multi-service-demo/general-chart/values.yaml index 4de0f35b..f6b6773f 100644 --- a/examples/multi-service-demo/general-chart/values.yaml +++ b/examples/multi-service-demo/general-chart/values.yaml @@ -1,6 +1,6 @@ fullnameOverride: $T-Service$ replicaCount: 1 -port: 20200 +port: {{.port}} imagePullSecretsName: "default-registry-secret" diff --git a/examples/multi-service-demo/values/service1.yaml b/examples/multi-service-demo/values/service1.yaml index 985938cc..72d49c5a 100644 --- a/examples/multi-service-demo/values/service1.yaml +++ b/examples/multi-service-demo/values/service1.yaml @@ -2,6 +2,8 @@ fullnameOverride: "service1" replicaCount: 1 port: 20221 +imagePullSecretsName: "default-registry-secret" + image: repository: "ccr.ccs.tencentyun.com/koderover-public/service1" tag: "latest" diff --git a/examples/multi-service-demo/values/service2.yaml b/examples/multi-service-demo/values/service2.yaml index d6218882..d4299151 100644 --- a/examples/multi-service-demo/values/service2.yaml +++ b/examples/multi-service-demo/values/service2.yaml @@ -2,6 +2,8 @@ fullnameOverride: "service2" replicaCount: 1 port: 20222 +imagePullSecretsName: "default-registry-secret" + image: repository: "ccr.ccs.tencentyun.com/koderover-public/service2" tag: "latest" diff --git a/examples/multi-service-demo/values/service3.yaml b/examples/multi-service-demo/values/service3.yaml index 6fd1ddc9..3aba4e0c 100644 --- a/examples/multi-service-demo/values/service3.yaml +++ b/examples/multi-service-demo/values/service3.yaml @@ -2,6 +2,8 @@ fullnameOverride: "service3" replicaCount: 1 port: 20223 +imagePullSecretsName: "default-registry-secret" + image: repository: "ccr.ccs.tencentyun.com/koderover-public/service3" tag: "latest" -- Gitee From 8d17a7cd09204272273df4aa265134b837748338 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 23 Mar 2022 18:53:28 +0800 Subject: [PATCH 1910/2647] info add search key Signed-off-by: mouuii --- pkg/microservice/aslan/core/code/service/repo.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 7c77a7b2..de0e5191 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -45,6 +45,7 @@ type GitRepoInfo struct { ProjectUUID string `json:"project_uuid,omitempty"` RepoUUID string `json:"repo_uuid,omitempty"` RepoID string `json:"repo_id,omitempty"` + Key string `json:"key"` } // ListRepoInfos ... @@ -87,6 +88,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo info.Branches, err = codehostClient.ListBranches(client.ListOpt{ Namespace: strings.Replace(info.Owner, "%2F", "/", -1), ProjectName: projectName, + Key: info.Key, }) if err != nil { errList = multierror.Append(errList, err) @@ -110,6 +112,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo info.Tags, err = codehostClient.ListTags(client.ListOpt{ Namespace: strings.Replace(info.Owner, "%2F", "/", -1), ProjectName: projectName, + Key: info.Key, }) if err != nil { errList = multierror.Append(errList, err) -- Gitee From 36d24764b7c0fecfa0a16588931acf8192490b77 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Mar 2022 11:55:59 +0800 Subject: [PATCH 1911/2647] gitee integration Signed-off-by: liu deyi --- go.mod | 2 + go.sum | 2 + .../aslan/core/code/client/gitee/gitee.go | 1 + .../core/codehost/service/codehost.go | 5 +++ pkg/shared/client/systemconfig/codehost.go | 1 + pkg/tool/gitee/client.go | 43 +++++++++++++++++++ 6 files changed, 54 insertions(+) create mode 100644 pkg/microservice/aslan/core/code/client/gitee/gitee.go create mode 100644 pkg/tool/gitee/client.go diff --git a/go.mod b/go.mod index 22f6cda8..f5197db0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/koderover/zadig go 1.16 require ( + gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc // indirect github.com/27149chen/afero v1.6.2 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 @@ -87,6 +88,7 @@ require ( k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.1 sigs.k8s.io/yaml v1.3.0 + gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc ) replace ( diff --git a/go.sum b/go.sum index 61dce814..cb0107d8 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc h1:xjPHn/Qey+IMUZYo8goKsWmXHjn04RbaLCVinOh4UlM= +gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc/go.mod h1:qGJhn1KxC5UE4BUmxCE/hTpFfuKbd3U3V9fNROrspfE= github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go new file mode 100644 index 00000000..56733645 --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -0,0 +1 @@ +package gitee diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 97e65966..504aa769 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -173,6 +173,11 @@ func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*o AuthURL: address + "/oauth/authorize", TokenURL: address + "/oauth/token", }), nil + case systemconfig.GiteeProvider: + return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "groups", "hook", "enterprises"}, oauth2.Endpoint{ + AuthURL: address + "/oauth/authorize", + TokenURL: address + "/oauth/token", + }), nil } return nil, errors.New("illegal provider") } diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 3bf6030d..61151323 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -27,6 +27,7 @@ const ( GitHubProvider = "github" GerritProvider = "gerrit" CodeHubProvider = "codehub" + GiteeProvider = "gitee" ) type CodeHost struct { diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go new file mode 100644 index 00000000..2d2dc31e --- /dev/null +++ b/pkg/tool/gitee/client.go @@ -0,0 +1,43 @@ +package gitee + +import ( + "context" + "net/http" + "net/url" + + "gitee.com/openeuler/go-gitee/gitee" + "golang.org/x/oauth2" +) + +type Client struct { + *gitee.APIClient +} + +func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) { + var client *gitee.APIClient + + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: accessToken}, + ) + + conf := gitee.NewConfiguration() + conf.HTTPClient = oauth2.NewClient(context.Background(), ts) + + if enableProxy { + proxyURL, err := url.Parse(proxyAddr) + if err != nil { + return nil, err + } + transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} + conf.HTTPClient = &http.Client{ + Transport: &oauth2.Transport{ + Base: transport, + Source: oauth2.ReuseTokenSource(nil, ts), + }, + } + } + + client = gitee.NewAPIClient(conf) + + return &Client{client}, nil +} -- Gitee From 7fa374bc226be35b5830201c9cc1d583a4638ec0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 24 Mar 2022 11:55:59 +0800 Subject: [PATCH 1912/2647] gitee integration Signed-off-by: liu deyi --- go.mod | 2 + go.sum | 2 + .../aslan/core/code/client/gitee/gitee.go | 1 + .../core/codehost/service/codehost.go | 5 +++ pkg/shared/client/systemconfig/codehost.go | 1 + pkg/tool/gitee/client.go | 43 +++++++++++++++++++ 6 files changed, 54 insertions(+) create mode 100644 pkg/microservice/aslan/core/code/client/gitee/gitee.go create mode 100644 pkg/tool/gitee/client.go diff --git a/go.mod b/go.mod index 22f6cda8..f5197db0 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/koderover/zadig go 1.16 require ( + gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc // indirect github.com/27149chen/afero v1.6.2 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 @@ -87,6 +88,7 @@ require ( k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.1 sigs.k8s.io/yaml v1.3.0 + gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc ) replace ( diff --git a/go.sum b/go.sum index 61dce814..cb0107d8 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc h1:xjPHn/Qey+IMUZYo8goKsWmXHjn04RbaLCVinOh4UlM= +gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc/go.mod h1:qGJhn1KxC5UE4BUmxCE/hTpFfuKbd3U3V9fNROrspfE= github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go new file mode 100644 index 00000000..56733645 --- /dev/null +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -0,0 +1 @@ +package gitee diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 97e65966..504aa769 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -173,6 +173,11 @@ func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*o AuthURL: address + "/oauth/authorize", TokenURL: address + "/oauth/token", }), nil + case systemconfig.GiteeProvider: + return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "groups", "hook", "enterprises"}, oauth2.Endpoint{ + AuthURL: address + "/oauth/authorize", + TokenURL: address + "/oauth/token", + }), nil } return nil, errors.New("illegal provider") } diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 3bf6030d..61151323 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -27,6 +27,7 @@ const ( GitHubProvider = "github" GerritProvider = "gerrit" CodeHubProvider = "codehub" + GiteeProvider = "gitee" ) type CodeHost struct { diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go new file mode 100644 index 00000000..2d2dc31e --- /dev/null +++ b/pkg/tool/gitee/client.go @@ -0,0 +1,43 @@ +package gitee + +import ( + "context" + "net/http" + "net/url" + + "gitee.com/openeuler/go-gitee/gitee" + "golang.org/x/oauth2" +) + +type Client struct { + *gitee.APIClient +} + +func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) { + var client *gitee.APIClient + + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: accessToken}, + ) + + conf := gitee.NewConfiguration() + conf.HTTPClient = oauth2.NewClient(context.Background(), ts) + + if enableProxy { + proxyURL, err := url.Parse(proxyAddr) + if err != nil { + return nil, err + } + transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} + conf.HTTPClient = &http.Client{ + Transport: &oauth2.Transport{ + Base: transport, + Source: oauth2.ReuseTokenSource(nil, ts), + }, + } + } + + client = gitee.NewAPIClient(conf) + + return &Client{client}, nil +} -- Gitee From 02629c3f0977e8637a87b4d811984102ecabfa03 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 24 Mar 2022 23:05:09 +0800 Subject: [PATCH 1913/2647] fix bug of not clearing resources due to job startup timeout Signed-off-by: zhangyifei --- .../warpdrive/core/service/taskcontroller/task_handler.go | 3 +++ pkg/tool/kube/containerlog/log.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 00906d8e..2f189a30 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -500,6 +500,9 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl // 如果 SubTask 执行失败, 则不继续执行, 发送 Task 失败执行结果 // Failed, Timeout, Cancelled if plugin.IsTaskFailed() { + plugin.Complete(ctx, pipelineTask, servicename) + xl.Infof("task status: %s", plugin.Status()) + plugin.SetEndTime() updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) return plugin.Status(), fmt.Errorf("pipeline task failed: task_handler:308") diff --git a/pkg/tool/kube/containerlog/log.go b/pkg/tool/kube/containerlog/log.go index 0e9d320b..17733820 100644 --- a/pkg/tool/kube/containerlog/log.go +++ b/pkg/tool/kube/containerlog/log.go @@ -40,8 +40,11 @@ func GetContainerLogs(namespace, podName, containerName string, follow bool, tai return fmt.Errorf("failed to get pod %s in %s: %s", podName, namespace, err) } - _, err = out.Write([]byte(pod.Status.ContainerStatuses[0].State.Terminated.Message)) + if pod.Status.Phase == corev1.PodPending || pod.Status.Phase == corev1.PodUnknown { + return fmt.Errorf("phase of Pod %s in ns %s is %s", pod.Name, pod.Namespace, pod.Status.Phase) + } + _, err = out.Write([]byte(pod.Status.ContainerStatuses[0].State.Terminated.Message)) return err } -- Gitee From 744a54b91df6383f2526a025f4eb3c8a085dce04 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 11:22:33 +0800 Subject: [PATCH 1914/2647] add some related interface implementations Signed-off-by: liu deyi --- go.mod | 3 +- pkg/tool/gitee/branch.go | 15 +++++++ pkg/tool/gitee/client.go | 39 +++++++++-------- pkg/tool/gitee/pull_requests.go | 42 ++++++++++++++++++ pkg/tool/gitee/repositories.go | 75 +++++++++++++++++++++++++++++++++ pkg/tool/gitee/tag.go | 15 +++++++ pkg/tool/gitee/users.go | 16 +++++++ 7 files changed, 186 insertions(+), 19 deletions(-) create mode 100644 pkg/tool/gitee/branch.go create mode 100644 pkg/tool/gitee/pull_requests.go create mode 100644 pkg/tool/gitee/repositories.go create mode 100644 pkg/tool/gitee/tag.go create mode 100644 pkg/tool/gitee/users.go diff --git a/go.mod b/go.mod index f5197db0..88e415e0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/koderover/zadig go 1.16 require ( - gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc // indirect + gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc github.com/27149chen/afero v1.6.2 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 @@ -88,7 +88,6 @@ require ( k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.1 sigs.k8s.io/yaml v1.3.0 - gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc ) replace ( diff --git a/pkg/tool/gitee/branch.go b/pkg/tool/gitee/branch.go new file mode 100644 index 00000000..94084074 --- /dev/null +++ b/pkg/tool/gitee/branch.go @@ -0,0 +1,15 @@ +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) ListBranches(ctx context.Context, owner, repo string, opts *gitee.GetV5ReposOwnerRepoBranchesOpts) ([]gitee.Branch, error) { + bs, _, err := c.RepositoriesApi.GetV5ReposOwnerRepoBranches(ctx, owner, repo, opts) + if err != nil { + return nil, err + } + return bs, nil +} diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index 2d2dc31e..e38f68a4 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -14,29 +14,34 @@ type Client struct { } func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) { - var client *gitee.APIClient - - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: accessToken}, + var ( + client *gitee.APIClient + HttpClient *http.Client ) conf := gitee.NewConfiguration() - conf.HTTPClient = oauth2.NewClient(context.Background(), ts) - - if enableProxy { - proxyURL, err := url.Parse(proxyAddr) - if err != nil { - return nil, err - } - transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} - conf.HTTPClient = &http.Client{ - Transport: &oauth2.Transport{ - Base: transport, - Source: oauth2.ReuseTokenSource(nil, ts), - }, + dc := http.DefaultClient + if proxyAddr != "" { + p, err := url.Parse(proxyAddr) + if err == nil { + proxy := http.ProxyURL(p) + trans := &http.Transport{ + Proxy: proxy, + } + dc = &http.Client{Transport: trans} } } + if accessToken != "" { + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: accessToken}, + ) + HttpClient = oauth2.NewClient(ctx, ts) + } else { + HttpClient = dc + } + conf.HTTPClient = HttpClient client = gitee.NewAPIClient(conf) return &Client{client}, nil diff --git a/pkg/tool/gitee/pull_requests.go b/pkg/tool/gitee/pull_requests.go new file mode 100644 index 00000000..703905cd --- /dev/null +++ b/pkg/tool/gitee/pull_requests.go @@ -0,0 +1,42 @@ +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) GetPullRequest(ctx context.Context, owner string, repo string, number int) (gitee.PullRequest, error) { + pr, _, err := c.PullRequestsApi.GetV5ReposOwnerRepoPullsNumber(ctx, owner, repo, int32(number), nil) + if err != nil { + return gitee.PullRequest{}, err + } + + return pr, err +} + +func (c *Client) ListPullRequests(ctx context.Context, owner string, repo string, opts *gitee.GetV5ReposOwnerRepoPullsOpts) ([]gitee.PullRequest, error) { + prs, _, err := c.PullRequestsApi.GetV5ReposOwnerRepoPulls(ctx, owner, repo, opts) + if err != nil { + return nil, err + } + + return prs, err +} + +func (c *Client) ListCommits(ctx context.Context, owner string, repo string, number int, opts *gitee.GetV5ReposOwnerRepoPullsNumberCommitsOpts) ([]gitee.PullRequestCommits, error) { + cs, _, err := c.PullRequestsApi.GetV5ReposOwnerRepoPullsNumberCommits(ctx, owner, repo, int32(number), opts) + if err != nil { + return nil, err + } + + return cs, err +} + +func (c *Client) ListFiles(ctx context.Context, owner string, repo string, number int, opts *gitee.GetV5ReposOwnerRepoPullsNumberFilesOpts) ([]gitee.PullRequestFiles, error) { + is, _, err := c.PullRequestsApi.GetV5ReposOwnerRepoPullsNumberFiles(ctx, owner, repo, int32(number), opts) + if err != nil { + return nil, err + } + return is, err +} diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go new file mode 100644 index 00000000..38c79747 --- /dev/null +++ b/pkg/tool/gitee/repositories.go @@ -0,0 +1,75 @@ +package gitee + +import ( + "context" + + "github.com/antihax/optional" + + "github.com/koderover/zadig/pkg/tool/git" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) ListHooks(ctx context.Context, owner, repo string, opts *gitee.GetV5ReposOwnerRepoHooksOpts) ([]gitee.Hook, error) { + hs, _, err := c.WebhooksApi.GetV5ReposOwnerRepoHooks(ctx, owner, repo, opts) + if err != nil { + return nil, err + } + return hs, nil +} + +func (c *Client) DeleteHook(ctx context.Context, owner, repo string, id int64) error { + _, err := c.WebhooksApi.DeleteV5ReposOwnerRepoHooksId(ctx, owner, repo, int32(id), nil) + if err != nil { + return err + } + return nil +} + +func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { + resp, _, err := c.WebhooksApi.PostV5ReposOwnerRepoHooks(ctx, owner, repo, hook.URL, &gitee.PostV5ReposOwnerRepoHooksOpts{ + Password: optional.NewString(hook.Secret), + PushEvents: optional.NewBool(true), + TagPushEvents: optional.NewBool(true), + MergeRequestsEvents: optional.NewBool(true), + }) + if err != nil { + return gitee.Hook{}, err + } + + return resp, nil +} + +func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, hook *git.Hook) (gitee.Hook, error) { + resp, _, err := c.WebhooksApi.PatchV5ReposOwnerRepoHooksId(ctx, owner, repo, int32(id), hook.URL, &gitee.PatchV5ReposOwnerRepoHooksIdOpts{ + Password: optional.NewString(hook.Secret), + PushEvents: optional.NewBool(true), + TagPushEvents: optional.NewBool(true), + MergeRequestsEvents: optional.NewBool(true), + }) + if err != nil { + return gitee.Hook{}, err + } + + return resp, nil +} + +func (c *Client) GetContents(ctx context.Context, owner, repo, sha string) (gitee.Blob, error) { + fileContent, _, err := c.GitDataApi.GetV5ReposOwnerRepoGitBlobsSha(ctx, owner, repo, sha, &gitee.GetV5ReposOwnerRepoGitBlobsShaOpts{}) + if err != nil { + return gitee.Blob{}, err + } + return fileContent, nil +} + +// "Recursive" Assign a value of 1 to get the directory recursively +// sha Can be a branch name (such as master), Commit, or the SHA value of the directory Tree +func (c *Client) GetTrees(ctx context.Context, owner, repo, sha string, level int) (gitee.Tree, error) { + tree, _, err := c.GitDataApi.GetV5ReposOwnerRepoGitTreesSha(ctx, owner, repo, sha, &gitee.GetV5ReposOwnerRepoGitTreesShaOpts{ + Recursive: optional.NewInt32(int32(level)), + }) + if err != nil { + return gitee.Tree{}, err + } + return tree, nil +} diff --git a/pkg/tool/gitee/tag.go b/pkg/tool/gitee/tag.go new file mode 100644 index 00000000..36bf1f34 --- /dev/null +++ b/pkg/tool/gitee/tag.go @@ -0,0 +1,15 @@ +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) ListTags(ctx context.Context, owner string, repo string, opts *gitee.GetV5ReposOwnerRepoTagsOpts) (gitee.Tag, error) { + tags, _, err := c.RepositoriesApi.GetV5ReposOwnerRepoTags(ctx, owner, repo, opts) + if err != nil { + return gitee.Tag{}, err + } + return tags, err +} diff --git a/pkg/tool/gitee/users.go b/pkg/tool/gitee/users.go new file mode 100644 index 00000000..c5a8566f --- /dev/null +++ b/pkg/tool/gitee/users.go @@ -0,0 +1,16 @@ +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) GetAuthenticatedUser(ctx context.Context) (gitee.User, error) { + ur, _, err := c.UsersApi.GetV5User(ctx, &gitee.GetV5UserOpts{}) + if err != nil { + return gitee.User{}, err + } + + return ur, err +} -- Gitee From 292926cc1ad24a921cafce21ec20fb5d53c8130f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 12:27:02 +0800 Subject: [PATCH 1915/2647] add pk variable Signed-off-by: liu deyi --- .../common/repository/mongodb/private_key.go | 3 +++ .../service/workflow/workflow_task.go | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index e4ffea7a..bb429b79 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -194,8 +194,11 @@ func (c *PrivateKeyColl) ListHostIPByArgs(args *ListHostIPArgs) ([]*models.Priva opt := options.Find() selector := bson.D{ + {"_id", 1}, {"ip", 1}, {"name", 1}, + {"user_name", 1}, + {"private_key", 1}, } opt.SetProjection(selector) cursor, err := c.Collection.Find(ctx, query, opt) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 1a2fbc47..3762bd8c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1972,10 +1972,12 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.ImageFrom = commonmodels.ImageFromKoderover } + envHostInfos := make(map[string]*commonmodels.PrivateKey) if serviceTmpl != nil { build.ServiceType = setting.PMDeployType envHost := make(map[string][]string) envHostNames := make(map[string][]string) + for _, envConfig := range serviceTmpl.EnvConfigs { privateKeys, err := commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) if err != nil { @@ -1984,13 +1986,13 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } ips := sets.NewString() names := sets.NewString() - ips, names = extractHost(privateKeys, ips, names) + ips, names = extractHost(privateKeys, ips, names, envHostInfos) privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{Labels: envConfig.Labels}) if err != nil { log.Errorf("ListNameByArgs labels err:%s", err) continue } - ips, names = extractHost(privateKeys, ips, names) + ips, names = extractHost(privateKeys, ips, names, envHostInfos) envHost[envConfig.EnvName] = ips.List() envHostNames[envConfig.EnvName] = names.List() } @@ -2044,9 +2046,19 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL ssh.PrivateKey = latestKeyInfo.PrivateKey privateKeys = append(privateKeys, ssh) + delete(envHostInfos, sshID) } build.JobCtx.SSHs = privateKeys } + // Sync from the configuration in the environment + for _, privateKey := range envHostInfos { + build.JobCtx.SSHs = append(build.JobCtx.SSHs, &taskmodels.SSH{ + Name: privateKey.Name, + UserName: privateKey.UserName, + IP: privateKey.IP, + PrivateKey: privateKey.PrivateKey, + }) + } build.JobCtx.EnvVars = module.PreBuild.Envs @@ -2117,10 +2129,11 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL return subTasks, nil } -func extractHost(privateKeys []*commonmodels.PrivateKey, ips, names sets.String) (sets.String, sets.String) { +func extractHost(privateKeys []*commonmodels.PrivateKey, ips, names sets.String, envHostInfos map[string]*commonmodels.PrivateKey) (sets.String, sets.String) { for _, privateKey := range privateKeys { ips.Insert(privateKey.IP) names.Insert(privateKey.Name) + envHostInfos[privateKey.ID.Hex()] = privateKey } return ips, names } -- Gitee From 09cb6241d86711e95fffa372783c59ae2137f346 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 13:11:40 +0800 Subject: [PATCH 1916/2647] remove unused file Signed-off-by: liu deyi --- go.mod | 2 - .../aslan/core/code/client/gitee/gitee.go | 1 - pkg/tool/gitee/client.go | 43 ------------------- 3 files changed, 46 deletions(-) delete mode 100644 pkg/microservice/aslan/core/code/client/gitee/gitee.go delete mode 100644 pkg/tool/gitee/client.go diff --git a/go.mod b/go.mod index f5197db0..22f6cda8 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/koderover/zadig go 1.16 require ( - gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc // indirect github.com/27149chen/afero v1.6.2 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 @@ -88,7 +87,6 @@ require ( k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.1 sigs.k8s.io/yaml v1.3.0 - gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc ) replace ( diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go deleted file mode 100644 index 56733645..00000000 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ /dev/null @@ -1 +0,0 @@ -package gitee diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go deleted file mode 100644 index 2d2dc31e..00000000 --- a/pkg/tool/gitee/client.go +++ /dev/null @@ -1,43 +0,0 @@ -package gitee - -import ( - "context" - "net/http" - "net/url" - - "gitee.com/openeuler/go-gitee/gitee" - "golang.org/x/oauth2" -) - -type Client struct { - *gitee.APIClient -} - -func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) { - var client *gitee.APIClient - - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: accessToken}, - ) - - conf := gitee.NewConfiguration() - conf.HTTPClient = oauth2.NewClient(context.Background(), ts) - - if enableProxy { - proxyURL, err := url.Parse(proxyAddr) - if err != nil { - return nil, err - } - transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} - conf.HTTPClient = &http.Client{ - Transport: &oauth2.Transport{ - Base: transport, - Source: oauth2.ReuseTokenSource(nil, ts), - }, - } - } - - client = gitee.NewAPIClient(conf) - - return &Client{client}, nil -} -- Gitee From 21749824c4b1cfefbe6629808586bd04a4c2eef1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 13:12:41 +0800 Subject: [PATCH 1917/2647] remove unused file Signed-off-by: liu deyi --- go.sum | 2 -- pkg/shared/client/systemconfig/codehost.go | 1 - 2 files changed, 3 deletions(-) diff --git a/go.sum b/go.sum index cb0107d8..61dce814 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,6 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 entgo.io/ent v0.8.0/go.mod h1:KNjsukat/NJi6zJh1utwRadsbGOZsBbAZNDxkW7tMCc= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= -gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc h1:xjPHn/Qey+IMUZYo8goKsWmXHjn04RbaLCVinOh4UlM= -gitee.com/openeuler/go-gitee v0.0.0-20220323055843-285220630cbc/go.mod h1:qGJhn1KxC5UE4BUmxCE/hTpFfuKbd3U3V9fNROrspfE= github.com/27149chen/afero v1.6.2 h1:WRPUOreB90qGRQs8fxXcvi0srBPHy2octiJme7C7XCA= github.com/27149chen/afero v1.6.2/go.mod h1:68wFWsZwQPvguVWn5di+KCOLI0KQtSRve2mPFVg6VWY= github.com/AkihiroSuda/containerd-fuse-overlayfs v1.0.0/go.mod h1:0mMDvQFeLbbn1Wy8P2j3hwFhqBq+FKn8OZPno8WLmp8= diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 61151323..3bf6030d 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -27,7 +27,6 @@ const ( GitHubProvider = "github" GerritProvider = "gerrit" CodeHubProvider = "codehub" - GiteeProvider = "gitee" ) type CodeHost struct { -- Gitee From a54b698cb5c3fbd6453f5dd2777493e79c992355 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 13:13:42 +0800 Subject: [PATCH 1918/2647] remove unused code Signed-off-by: liu deyi --- .../systemconfig/core/codehost/service/codehost.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 504aa769..97e65966 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -173,11 +173,6 @@ func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*o AuthURL: address + "/oauth/authorize", TokenURL: address + "/oauth/token", }), nil - case systemconfig.GiteeProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "groups", "hook", "enterprises"}, oauth2.Endpoint{ - AuthURL: address + "/oauth/authorize", - TokenURL: address + "/oauth/token", - }), nil } return nil, errors.New("illegal provider") } -- Gitee From 894d3b0f6318bceb208304545eb4dac998d18514 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 15 Mar 2022 10:09:25 +0800 Subject: [PATCH 1919/2647] add enable/disable share-env API Signed-off-by: zhangyifei --- Makefile | 4 +- pkg/handler/cache/cache.go | 65 ++++++++++++++ pkg/handler/cache/consts.go | 21 +++++ pkg/handler/cache/handler.go | 84 +++++++++++++++++++ pkg/handler/cache/interface.go | 53 ++++++++++++ pkg/handler/cache/router.go | 39 +++++++++ .../aslan/core/environment/handler/router.go | 3 + .../core/environment/handler/share_env.go | 39 +++++++++ .../aslan/core/multicluster/handler/istio.go | 30 +++++++ .../aslan/core/multicluster/handler/router.go | 5 ++ .../core/multicluster/service/constants.go | 3 + .../aslan/core/multicluster/service/istio.go | 61 ++++++++++++++ pkg/microservice/aslan/server/rest/router.go | 2 + 13 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 pkg/handler/cache/cache.go create mode 100644 pkg/handler/cache/consts.go create mode 100644 pkg/handler/cache/handler.go create mode 100644 pkg/handler/cache/interface.go create mode 100644 pkg/handler/cache/router.go create mode 100644 pkg/microservice/aslan/core/environment/handler/share_env.go create mode 100644 pkg/microservice/aslan/core/multicluster/handler/istio.go create mode 100644 pkg/microservice/aslan/core/multicluster/service/istio.go diff --git a/Makefile b/Makefile index 98a7c92a..5550e2aa 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go - @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . + @docker buildx build --platform=linux/amd64 -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.arm64: %.image.amd64 @@ -83,7 +83,7 @@ resource-server.build.amd64: @cp docker/service/resource-server.Dockerfile docker/dist/amd64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/resource-server.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go - @docker build -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . + @docker buildx build --platform=linux/amd64 -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 resource-server.upload.amd64: resource-server.build.amd64 diff --git a/pkg/handler/cache/cache.go b/pkg/handler/cache/cache.go new file mode 100644 index 00000000..cca8ba07 --- /dev/null +++ b/pkg/handler/cache/cache.go @@ -0,0 +1,65 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cache + +import ( + "fmt" + "sync" +) + +type cacheStore struct { + store *sync.Map +} + +func NewCache() Cacher { + return &cacheStore{ + store: &sync.Map{}, + } +} + +func (c *cacheStore) Get(key string) (value interface{}, exists bool) { + return c.store.Load(key) +} + +func (c *cacheStore) Set(key string, value interface{}) { + c.store.Store(key, value) +} + +func (c *cacheStore) Delete(key string) { + c.store.Delete(key) +} + +func (c *cacheStore) List() map[string]interface{} { + data := map[string]interface{}{} + c.store.Range(func(key, value interface{}) bool { + data[fmt.Sprint(key)] = value + + return true + }) + + return data +} + +func (c *cacheStore) Purge() { + // Note: + // Don't use `c.store = &sync.Map{}` because it's dangerous when there's a race condition on `c.store.` + c.store.Range(func(key, value interface{}) bool { + c.store.Delete(key) + + return true + }) +} diff --git a/pkg/handler/cache/consts.go b/pkg/handler/cache/consts.go new file mode 100644 index 00000000..d8d46140 --- /dev/null +++ b/pkg/handler/cache/consts.go @@ -0,0 +1,21 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cache + +const MsgSuccess = "success" + +const HeaderXData = "x-data" diff --git a/pkg/handler/cache/handler.go b/pkg/handler/cache/handler.go new file mode 100644 index 00000000..8aae7899 --- /dev/null +++ b/pkg/handler/cache/handler.go @@ -0,0 +1,84 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cache + +import ( + "fmt" + + "github.com/gin-gonic/gin" + + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +type handlers struct { + cache Cacher +} + +func NewHandlers() *handlers { + return &handlers{ + cache: NewCache(), + } +} + +func (h *handlers) Get(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + data, _ := h.cache.Get(c.Param("key")) + if data == nil { + data = "" + } + + c.Header(HeaderXData, fmt.Sprint(data)) + ctx.Resp, ctx.Err = data, nil +} + +func (h *handlers) Set(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + h.cache.Set(c.Param("key"), c.Param("value")) + + ctx.Resp, ctx.Err = MsgSuccess, nil +} + +func (h *handlers) Delete(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + h.cache.Delete(c.Param("key")) + + ctx.Resp, ctx.Err = MsgSuccess, nil +} + +func (h *handlers) List(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + cacheData := h.cache.List() + + ctx.Resp, ctx.Err = cacheData, nil +} + +func (h *handlers) Purge(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + h.cache.Purge() + + ctx.Resp, ctx.Err = MsgSuccess, nil +} diff --git a/pkg/handler/cache/interface.go b/pkg/handler/cache/interface.go new file mode 100644 index 00000000..4a08d107 --- /dev/null +++ b/pkg/handler/cache/interface.go @@ -0,0 +1,53 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cache + +import "github.com/gin-gonic/gin" + +type Cacher interface { + // Get gets data based on `key`. + Get(key string) (value interface{}, exists bool) + + // Set sets data `key:values`. + Set(key string, value interface{}) + + // Delete deletes data based on `key`. + Delete(key string) + + // List lists all of data. + List() map[string]interface{} + + // Purge purges all of data. + Purge() +} + +type CacheHandler interface { + // Get is a gin handler that gets cache data. + Get(c *gin.Context) + + // Set is a gin handler that sets cache data. + Set(c *gin.Context) + + // Delete is a gin handler that deletes cache data. + Delete(c *gin.Context) + + // List is a gin handler that lists cache data. + List(c *gin.Context) + + // Purge is a gin handler that purges cache data. + Purge(c *gin.Context) +} diff --git a/pkg/handler/cache/router.go b/pkg/handler/cache/router.go new file mode 100644 index 00000000..5f091f62 --- /dev/null +++ b/pkg/handler/cache/router.go @@ -0,0 +1,39 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package cache + +import "github.com/gin-gonic/gin" + +type Router struct { + handlers CacheHandler +} + +func NewRouter() *Router { + return &Router{ + handlers: NewHandlers(), + } +} + +func (r *Router) Inject(router *gin.RouterGroup) { + router.GET("/:key", r.handlers.Get) + router.GET("/", r.handlers.List) + + router.POST("/:key/:value", r.handlers.Set) + + router.DELETE("/:key", r.handlers.Delete) + router.DELETE("/", r.handlers.Purge) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 1dca57ca..e0ee810e 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -122,6 +122,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/services/:serviceName/containers/:container", GetServiceContainer) environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) + + environments.POST("/:name/share/enable", EnvEnableShare) + environments.DELETE("/:name/share/enable", EnvDisableShare) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go new file mode 100644 index 00000000..f3bae2ae --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -0,0 +1,39 @@ +/* + Copyright 2021 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +// TODO: Implement. +func EnvEnableShare(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = nil, nil +} + +// TODO: Implement. +func EnvDisableShare(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = nil, nil +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/istio.go b/pkg/microservice/aslan/core/multicluster/handler/istio.go new file mode 100644 index 00000000..43c77ab1 --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/handler/istio.go @@ -0,0 +1,30 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func CheckIstiod(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.CheckIstiod(c, c.Param("id")) +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 4f02a1e8..61101eb2 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -46,6 +46,11 @@ func (*Router) Inject(router *gin.RouterGroup) { bundles.GET("", GetBundleResources) } + istio := router.Group("istio") + { + istio.GET("/check/:id", CheckIstiod) + } + router.GET("/:id/storageclasses", ListStorageClasses) router.GET("/:id/:namespace/pvcs", ListPVCs) } diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go index 114444b0..5c3e0489 100644 --- a/pkg/microservice/aslan/core/multicluster/service/constants.go +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -60,3 +60,6 @@ func (s StorageProvisioner) IsNFS() bool { // ZadigMinioSVC is the service name of minio. const ZadigMinioSVC = "zadig-minio" + +// IstiodDeployment is the Deployment name of istiod. +const IstiodDeployment = "istiod" diff --git a/pkg/microservice/aslan/core/multicluster/service/istio.go b/pkg/microservice/aslan/core/multicluster/service/istio.go new file mode 100644 index 00000000..725f4ef7 --- /dev/null +++ b/pkg/microservice/aslan/core/multicluster/service/istio.go @@ -0,0 +1,61 @@ +/* + Copyright 2022 The KodeRover Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package service + +import ( + "context" + "fmt" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" +) + +func CheckIstiod(ctx context.Context, clusterID string) (bool, error) { + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return false, fmt.Errorf("failed to get kube client: %s", err) + + } + + namespaces := &corev1.NamespaceList{} + err = kclient.List(ctx, namespaces, client.InNamespace("")) + if err != nil { + return false, fmt.Errorf("failed to list namespaces: %s", err) + } + + deployment := &appsv1.Deployment{} + for _, ns := range namespaces.Items { + err = kclient.Get(ctx, client.ObjectKey{Name: IstiodDeployment, Namespace: ns.Name}, deployment) + if err == nil { + return true, nil + } + + if apierrors.IsNotFound(err) { + err = nil + continue + } + + err = fmt.Errorf("failed to search %s: %s", IstiodDeployment, err) + } + + return false, err +} diff --git a/pkg/microservice/aslan/server/rest/router.go b/pkg/microservice/aslan/server/rest/router.go index ad2f2aec..8b1d62c3 100644 --- a/pkg/microservice/aslan/server/rest/router.go +++ b/pkg/microservice/aslan/server/rest/router.go @@ -21,6 +21,7 @@ import ( swaggerfiles "github.com/swaggo/files" ginswagger "github.com/swaggo/gin-swagger" + cachehandler "github.com/koderover/zadig/pkg/handler/cache" buildhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/build/handler" codehosthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/code/handler" collaborationhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/handler" @@ -88,6 +89,7 @@ func (s *engine) injectRouterGroup(router *gin.RouterGroup) { "/api/collaboration": new(collaborationhandler.Router), "/api/label": new(labelhandler.Router), "/api/stat": new(stathandler.Router), + "/api/cache": cachehandler.NewRouter(), } { r.Inject(router.Group(name)) } -- Gitee From 4b27e0596dc525a97baab86cf88c4a26b34fd235 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 22 Mar 2022 14:21:47 +0800 Subject: [PATCH 1920/2647] add API for checking whether all workloads have svc Signed-off-by: zhangyifei --- .../aslan/core/environment/handler/router.go | 4 +- .../core/environment/handler/share_env.go | 11 +- .../core/environment/service/share_env.go | 31 +++++ .../aslan/core/multicluster/handler/router.go | 1 + .../handler/{istio.go => share-env.go} | 7 + .../core/multicluster/service/constants.go | 4 +- .../service/{istio.go => share-env.go} | 124 +++++++++++++++++- 7 files changed, 172 insertions(+), 10 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/service/share_env.go rename pkg/microservice/aslan/core/multicluster/handler/{istio.go => share-env.go} (80%) rename pkg/microservice/aslan/core/multicluster/service/{istio.go => share-env.go} (30%) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index e0ee810e..a1f710d6 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -123,8 +123,8 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) - environments.POST("/:name/share/enable", EnvEnableShare) - environments.DELETE("/:name/share/enable", EnvDisableShare) + environments.POST("/:name/share/enable", EnableBaseEnv) + environments.DELETE("/:name/share/enable", DisableBaseEnv) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index f3bae2ae..98c8898c 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -19,21 +19,20 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -// TODO: Implement. -func EnvEnableShare(c *gin.Context) { +func EnableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = nil, nil + ctx.Resp, ctx.Err = service.EnableBaseEnv(c.Param("name")) } -// TODO: Implement. -func EnvDisableShare(c *gin.Context) { +func DisableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = nil, nil + ctx.Resp, ctx.Err = service.DisableBaseEnv(c.Param("name")) } diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go new file mode 100644 index 00000000..652d4654 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import "github.com/pkg/errors" + +var ErrNotImplemented = errors.New("not implemented") + +func EnableBaseEnv(envName string) (string, error) { + + return "", ErrNotImplemented +} + +func DisableBaseEnv(envName string) (string, error) { + + return "", ErrNotImplemented +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 61101eb2..12291e28 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -53,4 +53,5 @@ func (*Router) Inject(router *gin.RouterGroup) { router.GET("/:id/storageclasses", ListStorageClasses) router.GET("/:id/:namespace/pvcs", ListPVCs) + router.GET("/:id/:namespace/check/workloads/k8services", CheckWorkloadsK8sServices) } diff --git a/pkg/microservice/aslan/core/multicluster/handler/istio.go b/pkg/microservice/aslan/core/multicluster/handler/share-env.go similarity index 80% rename from pkg/microservice/aslan/core/multicluster/handler/istio.go rename to pkg/microservice/aslan/core/multicluster/handler/share-env.go index 43c77ab1..3274c934 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/istio.go +++ b/pkg/microservice/aslan/core/multicluster/handler/share-env.go @@ -28,3 +28,10 @@ func CheckIstiod(c *gin.Context) { ctx.Resp, ctx.Err = service.CheckIstiod(c, c.Param("id")) } + +func CheckWorkloadsK8sServices(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.CheckWorkloadsK8sServices(c, c.Param("id"), c.Param("namespace")) +} diff --git a/pkg/microservice/aslan/core/multicluster/service/constants.go b/pkg/microservice/aslan/core/multicluster/service/constants.go index 5c3e0489..74a2cd45 100644 --- a/pkg/microservice/aslan/core/multicluster/service/constants.go +++ b/pkg/microservice/aslan/core/multicluster/service/constants.go @@ -16,7 +16,9 @@ limitations under the License. package service -import "strings" +import ( + "strings" +) // StorageProvisioner is a storage type type StorageProvisioner string diff --git a/pkg/microservice/aslan/core/multicluster/service/istio.go b/pkg/microservice/aslan/core/multicluster/service/share-env.go similarity index 30% rename from pkg/microservice/aslan/core/multicluster/service/istio.go rename to pkg/microservice/aslan/core/multicluster/service/share-env.go index 725f4ef7..7f0e7222 100644 --- a/pkg/microservice/aslan/core/multicluster/service/istio.go +++ b/pkg/microservice/aslan/core/multicluster/service/share-env.go @@ -19,6 +19,7 @@ package service import ( "context" "fmt" + "time" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -27,13 +28,13 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/log" ) func CheckIstiod(ctx context.Context, clusterID string) (bool, error) { kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { return false, fmt.Errorf("failed to get kube client: %s", err) - } namespaces := &corev1.NamespaceList{} @@ -59,3 +60,124 @@ func CheckIstiod(ctx context.Context, clusterID string) (bool, error) { return false, err } + +// Slice of `.` and error are returned. +func CheckWorkloadsK8sServices(ctx context.Context, clusterID, namespace string) ([]string, error) { + timeStart := time.Now() + defer func() { + log.Infof("[CheckWorkloadsK8sServices]Time consumed: %s", time.Since(timeStart)) + }() + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + timeStartGetWorkloads := time.Now() + workloads, err := getWorkloads(ctx, kclient, namespace) + if err != nil { + return nil, fmt.Errorf("failed to get workloads: %s", err) + } + log.Infof("[CheckWorkloadsK8sServices][getWorkloads]Time consumed: %s", time.Since(timeStartGetWorkloads)) + + timeStartGetSvcs := time.Now() + svcs, err := getSvcs(ctx, kclient, namespace) + if err != nil { + return nil, fmt.Errorf("failed to get svcs: %s", err) + } + log.Infof("[CheckWorkloadsK8sServices][getSvcs]Time consumed: %s", time.Since(timeStartGetSvcs)) + + timeStartCheck := time.Now() + defer func() { + log.Infof("[CheckWorkloadsK8sServices][checkWorkloadsNoSvcs]Time consumed: %s", time.Since(timeStartCheck)) + }() + + return checkWorkloadsNoSvcs(svcs, workloads) +} + +// map structure: +// - key: `.`, e.g.: zadig.deployment +// - value: a map[string]string, which is workload' labels +// +// Note: +// Currently we only support Deployment and StatefulSet workloads, if more workloads need to be supported, +// they need to be added here. +func getWorkloads(ctx context.Context, kclient client.Client, namespace string) (map[string]map[string]string, error) { + deployments := &appsv1.DeploymentList{} + err := kclient.List(ctx, deployments, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list deployments in %s: %s", namespace, err) + } + + statefulsets := &appsv1.StatefulSetList{} + err = kclient.List(ctx, statefulsets, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list statefulsets in %s: %s", namespace, err) + } + + res := map[string]map[string]string{} + for _, d := range deployments.Items { + res[fmt.Sprintf("%s.%s", d.Name, d.Kind)] = d.Spec.Template.GetLabels() + } + for _, s := range statefulsets.Items { + res[fmt.Sprintf("%s.%s", s.Name, s.Kind)] = s.Spec.Template.GetLabels() + } + + return res, nil +} + +// map structure: +// - key: ``, e.g.: zadig +// - value: a map[string]string, which is Service's selector +func getSvcs(ctx context.Context, kclient client.Client, namespace string) (map[string]map[string]string, error) { + svcs := &corev1.ServiceList{} + err := kclient.List(ctx, svcs, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list svcs in %s: %s", namespace, err) + } + + res := map[string]map[string]string{} + for _, s := range svcs.Items { + res[s.Name] = s.Spec.Selector + } + + return res, nil +} + +// Structure of `svcs` and `workloads` is same as the above. +// Slice of `.` and error are returned. +func checkWorkloadsNoSvcs(svcs map[string]map[string]string, workloads map[string]map[string]string) ([]string, error) { + for _, svcSelector := range svcs { + for wName, wLabels := range workloads { + if !isMapSubset(svcSelector, wLabels) { + continue + } + + delete(workloads, wName) + + // Note: Generally, a workload does not correspond to multiple services in a namespace. + break + } + } + + workloadsName := make([]string, 0, len(workloads)) + for wname := range workloads { + workloadsName = append(workloadsName, wname) + } + + return workloadsName, nil +} + +func isMapSubset(mapSubSet, mapSet map[string]string) bool { + if len(mapSubSet) > len(mapSet) { + return false + } + + for k, v := range mapSubSet { + if vv, found := mapSet[k]; !found || vv != v { + return false + } + } + + return true +} -- Gitee From 2a95817c49c5a253532d21b0e5a0eb030ef3f22f Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 23 Mar 2022 10:38:36 +0800 Subject: [PATCH 1921/2647] support API that enables base env Signed-off-by: zhangyifei --- go.mod | 2 + go.sum | 6 + .../aslan/core/environment/handler/router.go | 1 + .../core/environment/handler/share_env.go | 11 +- .../core/environment/service/share_env.go | 320 +++++++++++++++++- .../aslan/core/multicluster/handler/router.go | 1 - .../handler/{share-env.go => share_env.go} | 7 - .../core/multicluster/service/share-env.go | 123 ------- 8 files changed, 333 insertions(+), 138 deletions(-) rename pkg/microservice/aslan/core/multicluster/handler/{share-env.go => share_env.go} (80%) diff --git a/go.mod b/go.mod index 22f6cda8..7fb93c52 100644 --- a/go.mod +++ b/go.mod @@ -79,6 +79,8 @@ require ( gorm.io/driver/mysql v1.1.2 gorm.io/gorm v1.21.16 helm.sh/helm/v3 v3.7.2 + istio.io/api v0.0.0-20220304035241-8c47cbbea144 // indirect + istio.io/client-go v0.0.0-20220304040955-30b642d5ba34 // indirect k8s.io/api v0.22.4 k8s.io/apiextensions-apiserver v0.22.4 k8s.io/apimachinery v0.22.4 diff --git a/go.sum b/go.sum index 61dce814..c69131ab 100644 --- a/go.sum +++ b/go.sum @@ -2318,6 +2318,12 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +istio.io/api v0.0.0-20220304035241-8c47cbbea144 h1:JIRhhxQ3LZKOmx2PXHO9xfcnT3mlHLmcewZtpTaAtoo= +istio.io/api v0.0.0-20220304035241-8c47cbbea144/go.mod h1:lavaUNsnT7RGyMFNOGgV5XvOgP3fkTSZkxP/0H/ISt4= +istio.io/client-go v0.0.0-20220304040955-30b642d5ba34 h1:MCFkv6odq6KRBmTM5+NlyNnt7fbYN+yMfOOhin745bQ= +istio.io/client-go v0.0.0-20220304040955-30b642d5ba34/go.mod h1:rKTRGbzRHY/lb3VCCIF4wwTwvkW000acWEcseSy3V9M= +istio.io/gogo-genproto v0.0.0-20210113155706-4daf5697332f h1:9710FpGLvIJ1GGEbpuTh1smVBv+r8cJfR3G82ouSxIQ= +istio.io/gogo-genproto v0.0.0-20210113155706-4daf5697332f/go.mod h1:6BwTZRNbWS570wHX/uR1Wqk5e0157TofTAUMzT7N4+s= k8s.io/api v0.21.0 h1:gu5iGF4V6tfVCQ/R+8Hc0h7H1JuEhzyEi9S4R5LM8+Y= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= k8s.io/apiextensions-apiserver v0.21.0 h1:Nd4uBuweg6ImzbxkC1W7xUNZcCV/8Vt10iTdTIVF3hw= diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index a1f710d6..b81e57ea 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -123,6 +123,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) + environments.GET("/:name/check/workloads/k8services", CheckWorkloadsK8sServices) environments.POST("/:name/share/enable", EnableBaseEnv) environments.DELETE("/:name/share/enable", DisableBaseEnv) } diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index 98c8898c..1e53beae 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -23,16 +23,23 @@ import ( internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) +func CheckWorkloadsK8sServices(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.CheckWorkloadsK8sServices(c, c.Param("name"), c.Query("projectName")) +} + func EnableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.EnableBaseEnv(c.Param("name")) + ctx.Err = service.EnableBaseEnv(c, c.Param("name"), c.Query("projectName")) } func DisableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.DisableBaseEnv(c.Param("name")) + ctx.Err = service.DisableBaseEnv(c.Param("name"), c.Query("projectName")) } diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 652d4654..a839ad18 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -16,16 +16,326 @@ limitations under the License. package service -import "github.com/pkg/errors" +import ( + "context" + "errors" + "fmt" + "time" + + networkingv1alpha3 "istio.io/api/networking/v1alpha3" + versionedclient "istio.io/client-go/pkg/clientset/versioned" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/log" +) var ErrNotImplemented = errors.New("not implemented") -func EnableBaseEnv(envName string) (string, error) { +const istioNamespace = "istio-system" +const istioProxyName = "istio-proxy" +const zadigEnvoyFilter = "zadig-share-env" + +// Slice of `.` and error are returned. +func CheckWorkloadsK8sServices(ctx context.Context, envName, productName string) ([]string, error) { + timeStart := time.Now() + defer func() { + log.Infof("[CheckWorkloadsK8sServices]Time consumed: %s", time.Since(timeStart)) + }() + + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, fmt.Errorf("failed to query env `%s` in project `%s`: %s", envName, productName, err) + } + + ns := prod.Namespace + clusterID := prod.ClusterID + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + timeStartGetWorkloads := time.Now() + workloads, err := getWorkloads(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to get workloads: %s", err) + } + log.Infof("[CheckWorkloadsK8sServices][getWorkloads]Time consumed: %s", time.Since(timeStartGetWorkloads)) + + timeStartGetSvcs := time.Now() + svcs, err := getSvcs(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to get svcs: %s", err) + } + log.Infof("[CheckWorkloadsK8sServices][getSvcs]Time consumed: %s", time.Since(timeStartGetSvcs)) - return "", ErrNotImplemented + timeStartCheck := time.Now() + defer func() { + log.Infof("[CheckWorkloadsK8sServices][checkWorkloadsNoSvcs]Time consumed: %s", time.Since(timeStartCheck)) + }() + + return checkWorkloadsNoSvcs(svcs, workloads) } -func DisableBaseEnv(envName string) (string, error) { +func EnableBaseEnv(ctx context.Context, envName, productName string) error { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return fmt.Errorf("failed to query env `%s` in project `%s`: %s", envName, productName, err) + } + + ns := prod.Namespace + clusterID := prod.ClusterID + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to new istio client: %s", err) + } + + // 1. Set `istio-injection=enabled` label on the namespace. + err = ensureIstioLabel(ctx, kclient, ns) + if err != nil { + return fmt.Errorf("failed to ensure istio label on namespace `%s`: %s", ns, err) + } + + // 2. Restart Pods that are not injected with `istio-proxy`. + err = ensurePodsWithIsitoProxy(ctx, kclient, ns) + if err != nil { + return fmt.Errorf("failed to ensure pods with istio-proxy in namespace `%s`: %s", ns, err) + } + + // 3. Deliver `VirtualService` (subsets are not required) in current namespace. + err = ensureVirtualServices(ctx, kclient, istioClient, ns) + if err != nil { + return fmt.Errorf("failed to ensure VirtualServices in namespace `%s`: %s", ns, err) + } + + // 4. Deliver `EnvoyFilter` in istio namespace. + err = ensureEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) + if err != nil { + return fmt.Errorf("failed to ensure EnvoyFilter in namespace `%s`: %s", istioNamespace, err) + } + + return nil +} + +func DisableBaseEnv(envName, product string) error { + + return ErrNotImplemented +} + +// map structure: +// - key: `.`, e.g.: zadig.deployment +// - value: a map[string]string, which is workload' labels +// +// Note: +// Currently we only support Deployment and StatefulSet workloads, if more workloads need to be supported, +// they need to be added here. +func getWorkloads(ctx context.Context, kclient client.Client, namespace string) (map[string]map[string]string, error) { + deployments := &appsv1.DeploymentList{} + err := kclient.List(ctx, deployments, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list deployments in %s: %s", namespace, err) + } + + statefulsets := &appsv1.StatefulSetList{} + err = kclient.List(ctx, statefulsets, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list statefulsets in %s: %s", namespace, err) + } + + res := map[string]map[string]string{} + for _, d := range deployments.Items { + res[fmt.Sprintf("%s.%s", d.Name, d.Kind)] = d.Spec.Template.GetLabels() + } + for _, s := range statefulsets.Items { + res[fmt.Sprintf("%s.%s", s.Name, s.Kind)] = s.Spec.Template.GetLabels() + } + + return res, nil +} + +// map structure: +// - key: ``, e.g.: zadig +// - value: a map[string]string, which is Service's selector +func getSvcs(ctx context.Context, kclient client.Client, namespace string) (map[string]map[string]string, error) { + svcs := &corev1.ServiceList{} + err := kclient.List(ctx, svcs, client.InNamespace(namespace)) + if err != nil { + return nil, fmt.Errorf("failed to list svcs in %s: %s", namespace, err) + } + + res := map[string]map[string]string{} + for _, s := range svcs.Items { + res[s.Name] = s.Spec.Selector + } + + return res, nil +} + +// Structure of `svcs` and `workloads` is same as the above. +// Slice of `.` and error are returned. +func checkWorkloadsNoSvcs(svcs map[string]map[string]string, workloads map[string]map[string]string) ([]string, error) { + for _, svcSelector := range svcs { + for wName, wLabels := range workloads { + if !isMapSubset(svcSelector, wLabels) { + continue + } + + delete(workloads, wName) + + // Note: Generally, a workload does not correspond to multiple services in a namespace. + break + } + } + + workloadsName := make([]string, 0, len(workloads)) + for wname := range workloads { + workloadsName = append(workloadsName, wname) + } + + return workloadsName, nil +} + +func isMapSubset(mapSubSet, mapSet map[string]string) bool { + if len(mapSubSet) > len(mapSet) { + return false + } + + for k, v := range mapSubSet { + if vv, found := mapSet[k]; !found || vv != v { + return false + } + } + + return true +} + +func ensureIstioLabel(ctx context.Context, kclient client.Client, ns string) error { + nsObj := &corev1.Namespace{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: ns, + }, nsObj) + if err != nil { + return fmt.Errorf("failed to query ns `%s`: %s", ns, err) + } + + nsObj.Labels["istio-injection"] = "enabled" + return kclient.Update(ctx, nsObj) +} + +func ensurePodsWithIsitoProxy(ctx context.Context, kclient client.Client, ns string) error { + pods := &corev1.PodList{} + err := kclient.List(ctx, pods, client.InNamespace(ns)) + if err != nil { + return fmt.Errorf("failed to query pods in ns `%s`: %s", ns, err) + } + + deleteOption := metav1.DeletePropagationBackground + for _, pod := range pods.Items { + hasIstioProxy := false + for _, container := range pod.Spec.Containers { + if container.Name == istioProxyName { + hasIstioProxy = true + break + } + } + + if !hasIstioProxy { + err := kclient.Delete(ctx, &pod, &client.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) + + if err != nil { + return fmt.Errorf("failed to ensure Pod `%s` in ns `%s` to inject istio-proxy: %s", pod.Name, ns, err) + } + } + } + + return nil +} + +func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) error { + svcs := &corev1.ServiceList{} + err := kclient.List(ctx, svcs, client.InNamespace(ns)) + if err != nil { + return fmt.Errorf("failed to list svcs in %s: %s", ns, err) + } + + for _, svc := range svcs.Items { + err := ensureVirtualService(ctx, istioClient, ns, svc.Name) + if err != nil { + return fmt.Errorf("failed to ensure VirtualService `%s` in ns `%s`: %s", svc.Name, ns, err) + } + } + + return nil +} + +func ensureVirtualService(ctx context.Context, istioClient versionedclient.Interface, ns, name string) error { + vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, name, metav1.GetOptions{}) + if err == nil { + log.Infof("Has found VirtualService `%s` in ns `%s` and don't recreate.", name, ns) + return nil + } + + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to query VirtualService `%s` in `%s`: %s", name, ns, err) + } + + vsObj.Name = name + vsObj.Spec = networkingv1alpha3.VirtualService{ + Hosts: []string{name}, + Http: []*networkingv1alpha3.HTTPRoute{ + &networkingv1alpha3.HTTPRoute{ + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", name, ns), + }, + }, + }, + }, + }, + } + _, err = istioClient.NetworkingV1alpha3().VirtualServices(ns).Create(ctx, vsObj, metav1.CreateOptions{}) + return err +} + +func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, ns, name string) error { + envoyFilterObj, err := istioClient.NetworkingV1alpha3().EnvoyFilters(ns).Get(ctx, name, metav1.GetOptions{}) + if err == nil { + log.Infof("Has found EnvoyFilter `%s` in ns `%s` and don't recreate.", name, istioNamespace) + return nil + } + + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to query EnvoyFilter `%s` in ns `%s`: %s", name, istioNamespace, err) + } + + envoyFilterObj.Name = name + envoyFilterObj.Spec = networkingv1alpha3.EnvoyFilter{ + ConfigPatches: []*networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{}, + } - return "", ErrNotImplemented + _, err = istioClient.NetworkingV1alpha3().EnvoyFilters(ns).Create(ctx, envoyFilterObj, metav1.CreateOptions{}) + return err } diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 12291e28..61101eb2 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -53,5 +53,4 @@ func (*Router) Inject(router *gin.RouterGroup) { router.GET("/:id/storageclasses", ListStorageClasses) router.GET("/:id/:namespace/pvcs", ListPVCs) - router.GET("/:id/:namespace/check/workloads/k8services", CheckWorkloadsK8sServices) } diff --git a/pkg/microservice/aslan/core/multicluster/handler/share-env.go b/pkg/microservice/aslan/core/multicluster/handler/share_env.go similarity index 80% rename from pkg/microservice/aslan/core/multicluster/handler/share-env.go rename to pkg/microservice/aslan/core/multicluster/handler/share_env.go index 3274c934..43c77ab1 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/share-env.go +++ b/pkg/microservice/aslan/core/multicluster/handler/share_env.go @@ -28,10 +28,3 @@ func CheckIstiod(c *gin.Context) { ctx.Resp, ctx.Err = service.CheckIstiod(c, c.Param("id")) } - -func CheckWorkloadsK8sServices(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = service.CheckWorkloadsK8sServices(c, c.Param("id"), c.Param("namespace")) -} diff --git a/pkg/microservice/aslan/core/multicluster/service/share-env.go b/pkg/microservice/aslan/core/multicluster/service/share-env.go index 7f0e7222..391ed98b 100644 --- a/pkg/microservice/aslan/core/multicluster/service/share-env.go +++ b/pkg/microservice/aslan/core/multicluster/service/share-env.go @@ -19,7 +19,6 @@ package service import ( "context" "fmt" - "time" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -28,7 +27,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - "github.com/koderover/zadig/pkg/tool/log" ) func CheckIstiod(ctx context.Context, clusterID string) (bool, error) { @@ -60,124 +58,3 @@ func CheckIstiod(ctx context.Context, clusterID string) (bool, error) { return false, err } - -// Slice of `.` and error are returned. -func CheckWorkloadsK8sServices(ctx context.Context, clusterID, namespace string) ([]string, error) { - timeStart := time.Now() - defer func() { - log.Infof("[CheckWorkloadsK8sServices]Time consumed: %s", time.Since(timeStart)) - }() - - kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) - if err != nil { - return nil, fmt.Errorf("failed to get kube client: %s", err) - } - - timeStartGetWorkloads := time.Now() - workloads, err := getWorkloads(ctx, kclient, namespace) - if err != nil { - return nil, fmt.Errorf("failed to get workloads: %s", err) - } - log.Infof("[CheckWorkloadsK8sServices][getWorkloads]Time consumed: %s", time.Since(timeStartGetWorkloads)) - - timeStartGetSvcs := time.Now() - svcs, err := getSvcs(ctx, kclient, namespace) - if err != nil { - return nil, fmt.Errorf("failed to get svcs: %s", err) - } - log.Infof("[CheckWorkloadsK8sServices][getSvcs]Time consumed: %s", time.Since(timeStartGetSvcs)) - - timeStartCheck := time.Now() - defer func() { - log.Infof("[CheckWorkloadsK8sServices][checkWorkloadsNoSvcs]Time consumed: %s", time.Since(timeStartCheck)) - }() - - return checkWorkloadsNoSvcs(svcs, workloads) -} - -// map structure: -// - key: `.`, e.g.: zadig.deployment -// - value: a map[string]string, which is workload' labels -// -// Note: -// Currently we only support Deployment and StatefulSet workloads, if more workloads need to be supported, -// they need to be added here. -func getWorkloads(ctx context.Context, kclient client.Client, namespace string) (map[string]map[string]string, error) { - deployments := &appsv1.DeploymentList{} - err := kclient.List(ctx, deployments, client.InNamespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list deployments in %s: %s", namespace, err) - } - - statefulsets := &appsv1.StatefulSetList{} - err = kclient.List(ctx, statefulsets, client.InNamespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list statefulsets in %s: %s", namespace, err) - } - - res := map[string]map[string]string{} - for _, d := range deployments.Items { - res[fmt.Sprintf("%s.%s", d.Name, d.Kind)] = d.Spec.Template.GetLabels() - } - for _, s := range statefulsets.Items { - res[fmt.Sprintf("%s.%s", s.Name, s.Kind)] = s.Spec.Template.GetLabels() - } - - return res, nil -} - -// map structure: -// - key: ``, e.g.: zadig -// - value: a map[string]string, which is Service's selector -func getSvcs(ctx context.Context, kclient client.Client, namespace string) (map[string]map[string]string, error) { - svcs := &corev1.ServiceList{} - err := kclient.List(ctx, svcs, client.InNamespace(namespace)) - if err != nil { - return nil, fmt.Errorf("failed to list svcs in %s: %s", namespace, err) - } - - res := map[string]map[string]string{} - for _, s := range svcs.Items { - res[s.Name] = s.Spec.Selector - } - - return res, nil -} - -// Structure of `svcs` and `workloads` is same as the above. -// Slice of `.` and error are returned. -func checkWorkloadsNoSvcs(svcs map[string]map[string]string, workloads map[string]map[string]string) ([]string, error) { - for _, svcSelector := range svcs { - for wName, wLabels := range workloads { - if !isMapSubset(svcSelector, wLabels) { - continue - } - - delete(workloads, wName) - - // Note: Generally, a workload does not correspond to multiple services in a namespace. - break - } - } - - workloadsName := make([]string, 0, len(workloads)) - for wname := range workloads { - workloadsName = append(workloadsName, wname) - } - - return workloadsName, nil -} - -func isMapSubset(mapSubSet, mapSet map[string]string) bool { - if len(mapSubSet) > len(mapSet) { - return false - } - - for k, v := range mapSubSet { - if vv, found := mapSet[k]; !found || vv != v { - return false - } - } - - return true -} -- Gitee From 5f34ea01c5f40ebc451e64caf7d9335d854a33ea Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 23 Mar 2022 14:14:30 +0800 Subject: [PATCH 1922/2647] support API that checks whether share env has been ready Signed-off-by: zhangyifei --- .../aslan/core/environment/handler/router.go | 1 + .../core/environment/handler/share_env.go | 9 +- .../core/environment/service/share_env.go | 172 +++++++++++++++--- .../aslan/core/environment/service/types.go | 21 +++ 4 files changed, 175 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index b81e57ea..5d121584 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -126,6 +126,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/check/workloads/k8services", CheckWorkloadsK8sServices) environments.POST("/:name/share/enable", EnableBaseEnv) environments.DELETE("/:name/share/enable", DisableBaseEnv) + environments.GET("/:name/check/sharenv/ready", CheckShareEnvReady) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index 1e53beae..fe319ed1 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -41,5 +41,12 @@ func DisableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = service.DisableBaseEnv(c.Param("name"), c.Query("projectName")) + ctx.Err = service.DisableBaseEnv(c, c.Param("name"), c.Query("projectName")) +} + +func CheckShareEnvReady(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.CheckShareEnvReady(c, c.Param("name"), c.Query("projectName")) } diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index a839ad18..425deba7 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -63,26 +63,7 @@ func CheckWorkloadsK8sServices(ctx context.Context, envName, productName string) return nil, fmt.Errorf("failed to get kube client: %s", err) } - timeStartGetWorkloads := time.Now() - workloads, err := getWorkloads(ctx, kclient, ns) - if err != nil { - return nil, fmt.Errorf("failed to get workloads: %s", err) - } - log.Infof("[CheckWorkloadsK8sServices][getWorkloads]Time consumed: %s", time.Since(timeStartGetWorkloads)) - - timeStartGetSvcs := time.Now() - svcs, err := getSvcs(ctx, kclient, ns) - if err != nil { - return nil, fmt.Errorf("failed to get svcs: %s", err) - } - log.Infof("[CheckWorkloadsK8sServices][getSvcs]Time consumed: %s", time.Since(timeStartGetSvcs)) - - timeStartCheck := time.Now() - defer func() { - log.Infof("[CheckWorkloadsK8sServices][checkWorkloadsNoSvcs]Time consumed: %s", time.Since(timeStartCheck)) - }() - - return checkWorkloadsNoSvcs(svcs, workloads) + return checkWorkloadsHaveK8sService(ctx, kclient, ns) } func EnableBaseEnv(ctx context.Context, envName, productName string) error { @@ -110,25 +91,25 @@ func EnableBaseEnv(ctx context.Context, envName, productName string) error { return fmt.Errorf("failed to new istio client: %s", err) } - // 1. Set `istio-injection=enabled` label on the namespace. + // 1. Ensure `istio-injection=enabled` label on the namespace. err = ensureIstioLabel(ctx, kclient, ns) if err != nil { return fmt.Errorf("failed to ensure istio label on namespace `%s`: %s", ns, err) } - // 2. Restart Pods that are not injected with `istio-proxy`. + // 2. Ensure Pods that are not injected with `istio-proxy`. err = ensurePodsWithIsitoProxy(ctx, kclient, ns) if err != nil { return fmt.Errorf("failed to ensure pods with istio-proxy in namespace `%s`: %s", ns, err) } - // 3. Deliver `VirtualService` (subsets are not required) in current namespace. + // 3. Ensure `VirtualService` (subsets are not required) in current namespace. err = ensureVirtualServices(ctx, kclient, istioClient, ns) if err != nil { return fmt.Errorf("failed to ensure VirtualServices in namespace `%s`: %s", ns, err) } - // 4. Deliver `EnvoyFilter` in istio namespace. + // 4. Ensure `EnvoyFilter` in istio namespace. err = ensureEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) if err != nil { return fmt.Errorf("failed to ensure EnvoyFilter in namespace `%s`: %s", istioNamespace, err) @@ -137,11 +118,92 @@ func EnableBaseEnv(ctx context.Context, envName, productName string) error { return nil } -func DisableBaseEnv(envName, product string) error { +func DisableBaseEnv(ctx context.Context, envName, productName string) error { return ErrNotImplemented } +func CheckShareEnvReady(ctx context.Context, envName, productName string) (*ShareEnvReady, error) { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, fmt.Errorf("failed to query env `%s` in project `%s`: %s", envName, productName, err) + } + + ns := prod.Namespace + clusterID := prod.ClusterID + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return nil, fmt.Errorf("failed to new istio client: %s", err) + } + + // 1. Check whether namespace has labeled `istio-injection=enabled`. + isNamespaceHasIstioLabel, err := checkIstioLabel(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to check whether namespace `%s` has labeled `istio-injection=enabled`: %s", ns, err) + } + + // 2. Check whether all workloads have K8s Service. + workloadsHaveNoK8sService, err := checkWorkloadsHaveK8sService(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to check whether all workloads in ns `%s` have K8s Service: %s", ns, err) + } + + isWorkloadsHaveNoK8sService := true + if len(workloadsHaveNoK8sService) > 0 { + isWorkloadsHaveNoK8sService = false + } + + // 3. Check whether all VirtualServices have been deployed. + isVirtualServicesDeployed, err := checkVirtualServicesDeployed(ctx, kclient, istioClient, ns) + if err != nil { + return nil, fmt.Errorf("failed to check whether all VirtualServices in ns `%s` have been deployed: %s", ns, err) + } + + // 4. Check whether all Pods have istio-proxy and are ready. + isPodsHaveIstioProxyAndReady, err := checkPodsWithIstioProxyAndReady(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to check whether all pods in ns `%s` have istio-proxy and are ready: %s", ns, err) + } + + res := &ShareEnvReady{ + Checks: ShareEnvReadyChecks{ + NamespaceHasIstioLabel: isNamespaceHasIstioLabel, + WorkloadsHaveK8sService: isWorkloadsHaveNoK8sService, + VirtualServicesDeployed: isVirtualServicesDeployed, + PodsHaveIstioProxyAndReady: isPodsHaveIstioProxyAndReady, + }, + } + res.CheckAndSetReady() + + return res, nil +} + +func checkWorkloadsHaveK8sService(ctx context.Context, kclient client.Client, ns string) ([]string, error) { + workloads, err := getWorkloads(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to get workloads: %s", err) + } + + svcs, err := getSvcs(ctx, kclient, ns) + if err != nil { + return nil, fmt.Errorf("failed to get svcs: %s", err) + } + + return checkWorkloadsNoSvcs(svcs, workloads) +} + // map structure: // - key: `.`, e.g.: zadig.deployment // - value: a map[string]string, which is workload' labels @@ -242,6 +304,18 @@ func ensureIstioLabel(ctx context.Context, kclient client.Client, ns string) err return kclient.Update(ctx, nsObj) } +func checkIstioLabel(ctx context.Context, kclient client.Client, ns string) (bool, error) { + nsObj := &corev1.Namespace{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: ns, + }, nsObj) + if err != nil { + return false, fmt.Errorf("failed to query ns `%s`: %s", ns, err) + } + + return nsObj.Labels["istio-injection"] == "enabled", nil +} + func ensurePodsWithIsitoProxy(ctx context.Context, kclient client.Client, ns string) error { pods := &corev1.PodList{} err := kclient.List(ctx, pods, client.InNamespace(ns)) @@ -273,11 +347,38 @@ func ensurePodsWithIsitoProxy(ctx context.Context, kclient client.Client, ns str return nil } +func checkPodsWithIstioProxyAndReady(ctx context.Context, kclient client.Client, ns string) (bool, error) { + pods := &corev1.PodList{} + err := kclient.List(ctx, pods, client.InNamespace(ns)) + if err != nil { + return false, fmt.Errorf("failed to query pods in ns `%s`: %s", ns, err) + } + + for _, pod := range pods.Items { + if !(pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodSucceeded) { + return false, nil + } + + hasIstioProxy := false + for _, container := range pod.Spec.Containers { + if container.Name == istioProxyName { + hasIstioProxy = true + break + } + } + if !hasIstioProxy { + return false, nil + } + } + + return true, nil +} + func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) error { svcs := &corev1.ServiceList{} err := kclient.List(ctx, svcs, client.InNamespace(ns)) if err != nil { - return fmt.Errorf("failed to list svcs in %s: %s", ns, err) + return fmt.Errorf("failed to list svcs in ns `%s`: %s", ns, err) } for _, svc := range svcs.Items { @@ -298,7 +399,7 @@ func ensureVirtualService(ctx context.Context, istioClient versionedclient.Inter } if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to query VirtualService `%s` in `%s`: %s", name, ns, err) + return fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", name, ns, err) } vsObj.Name = name @@ -320,6 +421,23 @@ func ensureVirtualService(ctx context.Context, istioClient versionedclient.Inter return err } +func checkVirtualServicesDeployed(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) (bool, error) { + svcs := &corev1.ServiceList{} + err := kclient.List(ctx, svcs, client.InNamespace(ns)) + if err != nil { + return false, fmt.Errorf("failed to list svcs in ns `%s`: %s", ns, err) + } + + for _, svc := range svcs.Items { + _, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, svc.Name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", svc.Name, ns, err) + } + } + + return true, nil +} + func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, ns, name string) error { envoyFilterObj, err := istioClient.NetworkingV1alpha3().EnvoyFilters(ns).Get(ctx, name, metav1.GetOptions{}) if err == nil { diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index f3c0af94..0880f304 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -120,3 +120,24 @@ type NodeResp struct { Nodes []*internalresource.Node `json:"data"` Labels []string `json:"labels"` } + +type ShareEnvReady struct { + IsReady bool `json:"is_ready"` + Checks ShareEnvReadyChecks `json:"checks"` +} + +type ShareEnvReadyChecks struct { + NamespaceHasIstioLabel bool `json:"namespace_has_istio_label"` + WorkloadsHaveK8sService bool `json:"workloads_have_k8s_service"` + VirtualServicesDeployed bool `json:"virtualservice_deployed"` + PodsHaveIstioProxyAndReady bool `json:"pods_have_istio_proxy_and_ready"` +} + +func (s *ShareEnvReady) CheckAndSetReady() { + if !s.Checks.NamespaceHasIstioLabel || !s.Checks.WorkloadsHaveK8sService || !s.Checks.VirtualServicesDeployed || !s.Checks.PodsHaveIstioProxyAndReady { + s.IsReady = false + return + } + + s.IsReady = true +} -- Gitee From 2c92419122763eca619fcb2e7bf2e378a9b4bed2 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 23 Mar 2022 22:13:40 +0800 Subject: [PATCH 1923/2647] add EnvoyFilter content Signed-off-by: zhangyifei --- go.mod | 5 +- .../core/environment/service/share_env.go | 191 +++++++++++++++++- .../aslan/core/environment/service/types.go | 27 +++ 3 files changed, 218 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7fb93c52..f59f4f9c 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,7 @@ require ( github.com/go-resty/resty/v2 v2.6.0 github.com/go-sql-driver/mysql v1.6.0 github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 @@ -79,8 +80,8 @@ require ( gorm.io/driver/mysql v1.1.2 gorm.io/gorm v1.21.16 helm.sh/helm/v3 v3.7.2 - istio.io/api v0.0.0-20220304035241-8c47cbbea144 // indirect - istio.io/client-go v0.0.0-20220304040955-30b642d5ba34 // indirect + istio.io/api v0.0.0-20220304035241-8c47cbbea144 + istio.io/client-go v0.0.0-20220304040955-30b642d5ba34 k8s.io/api v0.22.4 k8s.io/apiextensions-apiserver v0.22.4 k8s.io/apimachinery v0.22.4 diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 425deba7..ca4e0045 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -17,11 +17,15 @@ limitations under the License. package service import ( + "bytes" "context" + "encoding/json" "errors" "fmt" "time" + "github.com/gogo/protobuf/jsonpb" + types "github.com/gogo/protobuf/types" networkingv1alpha3 "istio.io/api/networking/v1alpha3" versionedclient "istio.io/client-go/pkg/clientset/versioned" appsv1 "k8s.io/api/apps/v1" @@ -32,6 +36,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" ) @@ -41,6 +46,9 @@ var ErrNotImplemented = errors.New("not implemented") const istioNamespace = "istio-system" const istioProxyName = "istio-proxy" const zadigEnvoyFilter = "zadig-share-env" +const envoyFilterNetworkHttpConnectionManager = "envoy.filters.network.http_connection_manager" +const envoyFilterHttpRouter = "envoy.filters.http.router" +const envoyFilterLua = "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" // Slice of `.` and error are returned. func CheckWorkloadsK8sServices(ctx context.Context, envName, productName string) ([]string, error) { @@ -110,7 +118,7 @@ func EnableBaseEnv(ctx context.Context, envName, productName string) error { } // 4. Ensure `EnvoyFilter` in istio namespace. - err = ensureEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) + err = ensureEnvoyFilter(ctx, istioClient, clusterID, istioNamespace, zadigEnvoyFilter) if err != nil { return fmt.Errorf("failed to ensure EnvoyFilter in namespace `%s`: %s", istioNamespace, err) } @@ -438,7 +446,7 @@ func checkVirtualServicesDeployed(ctx context.Context, kclient client.Client, is return true, nil } -func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, ns, name string) error { +func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, clusterID, ns, name string) error { envoyFilterObj, err := istioClient.NetworkingV1alpha3().EnvoyFilters(ns).Get(ctx, name, metav1.GetOptions{}) if err == nil { log.Infof("Has found EnvoyFilter `%s` in ns `%s` and don't recreate.", name, istioNamespace) @@ -449,11 +457,188 @@ func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interfac return fmt.Errorf("failed to query EnvoyFilter `%s` in ns `%s`: %s", name, istioNamespace, err) } + storeCacheOperation, err := buildEnvoyStoreCacheOperation() + if err != nil { + return fmt.Errorf("failed to build envoy operation of storing cache: %s", err) + } + + getCacheOperation, err := buildEnvoyGetCacheOperation() + if err != nil { + return fmt.Errorf("failed to build envoy operation of getting cache: %s", err) + } + + var cacheServerAddr string + var cacheServerPort int + + switch clusterID { + case setting.LocalClusterID: + cacheServerAddr = "aslan.zadig.svc.cluster.local" + cacheServerPort = 25000 + default: + cacheServerAddr = "hub-agent.koderover-agent.svc.cluster.local" + cacheServerPort = 80 + } + + clusterConfig, err := buildEnvoyClusterConfig(cacheServerAddr, cacheServerPort) + if err != nil { + return fmt.Errorf("failed to build envoy cluster config for `%s:%d`: %s", cacheServerAddr, cacheServerPort, err) + } + envoyFilterObj.Name = name envoyFilterObj.Spec = networkingv1alpha3.EnvoyFilter{ - ConfigPatches: []*networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{}, + ConfigPatches: []*networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ + &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ + ApplyTo: networkingv1alpha3.EnvoyFilter_HTTP_FILTER, + Match: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networkingv1alpha3.EnvoyFilter_SIDECAR_INBOUND, + ObjectTypes: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networkingv1alpha3.EnvoyFilter_ListenerMatch{ + FilterChain: &networkingv1alpha3.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networkingv1alpha3.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: envoyFilterNetworkHttpConnectionManager, + SubFilter: &networkingv1alpha3.EnvoyFilter_ListenerMatch_SubFilterMatch{ + Name: envoyFilterHttpRouter, + }, + }, + }, + }, + }, + }, + Patch: &networkingv1alpha3.EnvoyFilter_Patch{ + Operation: networkingv1alpha3.EnvoyFilter_Patch_INSERT_BEFORE, + Value: storeCacheOperation, + }, + }, + &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ + ApplyTo: networkingv1alpha3.EnvoyFilter_HTTP_FILTER, + Match: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networkingv1alpha3.EnvoyFilter_SIDECAR_OUTBOUND, + ObjectTypes: &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networkingv1alpha3.EnvoyFilter_ListenerMatch{ + FilterChain: &networkingv1alpha3.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networkingv1alpha3.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: envoyFilterNetworkHttpConnectionManager, + SubFilter: &networkingv1alpha3.EnvoyFilter_ListenerMatch_SubFilterMatch{ + Name: envoyFilterHttpRouter, + }, + }, + }, + }, + }, + }, + Patch: &networkingv1alpha3.EnvoyFilter_Patch{ + Operation: networkingv1alpha3.EnvoyFilter_Patch_INSERT_BEFORE, + Value: getCacheOperation, + }, + }, + &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ + ApplyTo: networkingv1alpha3.EnvoyFilter_CLUSTER, + Patch: &networkingv1alpha3.EnvoyFilter_Patch{ + Operation: networkingv1alpha3.EnvoyFilter_Patch_ADD, + Value: clusterConfig, + }, + }, + }, } _, err = istioClient.NetworkingV1alpha3().EnvoyFilters(ns).Create(ctx, envoyFilterObj, metav1.CreateOptions{}) return err } + +func buildEnvoyStoreCacheOperation() (*types.Struct, error) { + inlineCode := `function envoy_on_request(request_handle) + local requestID = request_handle:headers():get("x-request-id") + local env = request_handle:headers():get("x-env") + local headers, body = request_handle:httpCall( + "cache", + { + [":method"] = "POST", + [":path"] = string.format("/api/v1/%s/%s", requestID, env), + [":authority"] = "cache", + }, + "", + 5000 + ) +end +` + data := map[string]interface{}{ + "name": "envoy.lua", + "typed_config": map[string]string{ + "@type": envoyFilterLua, + "inlineCode": inlineCode, + }, + } + + return buildEnvoyPatchValue(data) +} + +func buildEnvoyGetCacheOperation() (*types.Struct, error) { + inlineCode := `function envoy_on_request(request_handle) + local requestID = request_handle:headers():get("x-request-id") + local headers, body = request_handle:httpCall( + "cache", + { + [":method"] = "GET", + [":path"] = string.format("/api/v1/%s", requestID), + [":authority"] = "cache", + }, + "", + 5000 + ) + + request_handle:headers():add("x-env", headers["x-data"]); +end +` + data := map[string]interface{}{ + "name": "envoy.lua", + "typed_config": map[string]string{ + "@type": envoyFilterLua, + "inlineCode": inlineCode, + }, + } + + return buildEnvoyPatchValue(data) +} + +func buildEnvoyClusterConfig(cacheAddr string, port int) (*types.Struct, error) { + data := map[string]interface{}{ + "name": "cache", + "type": "STRICT_DNS", + "connect_timeout": "3.0s", + "lb_policy": "ROUND_ROBIN", + "load_assignment": EnvoyClusterConfigLoadAssignment{ + ClusterName: "cache", + Endpoints: []EnvoyLBEndpoints{ + EnvoyLBEndpoints{ + LBEndpoints: []EnvoyEndpoints{ + EnvoyEndpoints{ + Endpoint: EnvoyEndpoint{ + Address: EnvoyAddress{ + SocketAddress: EnvoySocketAddress{ + Protocol: "TCP", + Address: cacheAddr, + PortValue: port, + }, + }, + }, + }, + }, + }, + }, + }, + } + + return buildEnvoyPatchValue(data) +} + +func buildEnvoyPatchValue(data map[string]interface{}) (*types.Struct, error) { + dataBytes, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("failed to marshal data: %s", err) + } + + reader := bytes.NewReader(dataBytes) + val := &types.Struct{} + err = jsonpb.Unmarshal(reader, val) + return val, err +} diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index 0880f304..c5877b3c 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -141,3 +141,30 @@ func (s *ShareEnvReady) CheckAndSetReady() { s.IsReady = true } + +type EnvoyClusterConfigLoadAssignment struct { + ClusterName string `json:"cluster_name"` + Endpoints []EnvoyLBEndpoints `json:"endpoints"` +} + +type EnvoyLBEndpoints struct { + LBEndpoints []EnvoyEndpoints `json:"lb_endpoints"` +} + +type EnvoyEndpoints struct { + Endpoint EnvoyEndpoint `json:"endpoint"` +} + +type EnvoyEndpoint struct { + Address EnvoyAddress `json:"address"` +} + +type EnvoyAddress struct { + SocketAddress EnvoySocketAddress `json:"socket_address"` +} + +type EnvoySocketAddress struct { + Protocol string `json:"protocol"` + Address string `json:"address"` + PortValue int `json:"port_value"` +} -- Gitee From 9e149ee3e0d75113981636172be6c003ba279b87 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 24 Mar 2022 21:34:03 +0800 Subject: [PATCH 1924/2647] support APIs that disables share-env mode and check whether env is ready Signed-off-by: zhangyifei --- .../aslan/core/environment/handler/router.go | 2 +- .../core/environment/handler/share_env.go | 2 +- .../core/environment/service/share_env.go | 245 ++++++++++++++---- .../aslan/core/environment/service/types.go | 36 ++- pkg/types/labels.go | 27 ++ 5 files changed, 255 insertions(+), 57 deletions(-) create mode 100644 pkg/types/labels.go diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 5d121584..ad4ffaba 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -126,7 +126,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/check/workloads/k8services", CheckWorkloadsK8sServices) environments.POST("/:name/share/enable", EnableBaseEnv) environments.DELETE("/:name/share/enable", DisableBaseEnv) - environments.GET("/:name/check/sharenv/ready", CheckShareEnvReady) + environments.GET("/:name/check/sharenv/:op/ready", CheckShareEnvReady) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index fe319ed1..0adfe82a 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -48,5 +48,5 @@ func CheckShareEnvReady(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.CheckShareEnvReady(c, c.Param("name"), c.Query("projectName")) + ctx.Resp, ctx.Err = service.CheckShareEnvReady(c, c.Param("name"), c.Param("op"), c.Query("projectName")) } diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index ca4e0045..cbeac85b 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -32,6 +32,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -39,6 +40,7 @@ import ( "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" + zadigtypes "github.com/koderover/zadig/pkg/types" ) var ErrNotImplemented = errors.New("not implemented") @@ -50,6 +52,8 @@ const envoyFilterNetworkHttpConnectionManager = "envoy.filters.network.http_conn const envoyFilterHttpRouter = "envoy.filters.http.router" const envoyFilterLua = "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" +const zadigNamePrefix = "zadig" + // Slice of `.` and error are returned. func CheckWorkloadsK8sServices(ctx context.Context, envName, productName string) ([]string, error) { timeStart := time.Now() @@ -127,11 +131,59 @@ func EnableBaseEnv(ctx context.Context, envName, productName string) error { } func DisableBaseEnv(ctx context.Context, envName, productName string) error { + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return fmt.Errorf("failed to query env `%s` in project `%s`: %s", envName, productName, err) + } - return ErrNotImplemented + ns := prod.Namespace + clusterID := prod.ClusterID + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to new istio client: %s", err) + } + + // // 1. Delete all associated subenvironments. + // err = deleteSubEnvs(ctx, kclient, ns) + // if err != nil { + // return fmt.Errorf("failed to delete associated subenvironments of base ns `%s`: %s", ns, err) + // } + + // 2. Delete EnvoyFilter in the namespace of Istio installation. + err = deleteEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) + if err != nil { + return fmt.Errorf("failed to delete EnvoyFilter: %s", err) + } + + // 3. Delete all VirtualServices delivered by the Zadig. + err = deleteVirtualServices(ctx, kclient, istioClient, ns) + if err != nil { + return fmt.Errorf("failed to delete VirtualServices that Zadig created in ns `%s`: %s", ns, err) + } + + // 4. Remove the `istio-injection=enabled` label of the namespace. + err = removeIstioLabel(ctx, kclient, ns) + if err != nil { + return fmt.Errorf("failed to remove istio label on ns `%s`: %s", ns, err) + } + + // 5. Restart the istio-Proxy injected Pods. + return removePodsIstioProxy(ctx, kclient, ns) } -func CheckShareEnvReady(ctx context.Context, envName, productName string) (*ShareEnvReady, error) { +func CheckShareEnvReady(ctx context.Context, envName, op, productName string) (*ShareEnvReady, error) { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -156,6 +208,8 @@ func CheckShareEnvReady(ctx context.Context, envName, productName string) (*Shar return nil, fmt.Errorf("failed to new istio client: %s", err) } + shareEnvOp := ShareEnvOp(op) + // 1. Check whether namespace has labeled `istio-injection=enabled`. isNamespaceHasIstioLabel, err := checkIstioLabel(ctx, kclient, ns) if err != nil { @@ -180,20 +234,21 @@ func CheckShareEnvReady(ctx context.Context, envName, productName string) (*Shar } // 4. Check whether all Pods have istio-proxy and are ready. - isPodsHaveIstioProxyAndReady, err := checkPodsWithIstioProxyAndReady(ctx, kclient, ns) + allHaveIstioProxy, allPodsReady, err := checkPodsWithIstioProxyAndReady(ctx, kclient, ns) if err != nil { return nil, fmt.Errorf("failed to check whether all pods in ns `%s` have istio-proxy and are ready: %s", ns, err) } res := &ShareEnvReady{ Checks: ShareEnvReadyChecks{ - NamespaceHasIstioLabel: isNamespaceHasIstioLabel, - WorkloadsHaveK8sService: isWorkloadsHaveNoK8sService, - VirtualServicesDeployed: isVirtualServicesDeployed, - PodsHaveIstioProxyAndReady: isPodsHaveIstioProxyAndReady, + NamespaceHasIstioLabel: isNamespaceHasIstioLabel, + WorkloadsHaveK8sService: isWorkloadsHaveNoK8sService, + VirtualServicesDeployed: isVirtualServicesDeployed, + PodsHaveIstioProxy: allHaveIstioProxy, + WorkloadsReady: allPodsReady, }, } - res.CheckAndSetReady() + res.CheckAndSetReady(shareEnvOp) return res, nil } @@ -308,7 +363,7 @@ func ensureIstioLabel(ctx context.Context, kclient client.Client, ns string) err return fmt.Errorf("failed to query ns `%s`: %s", ns, err) } - nsObj.Labels["istio-injection"] = "enabled" + nsObj.Labels[zadigtypes.IstioLabelKeyInjection] = zadigtypes.IstioLabelValueInjection return kclient.Update(ctx, nsObj) } @@ -321,50 +376,25 @@ func checkIstioLabel(ctx context.Context, kclient client.Client, ns string) (boo return false, fmt.Errorf("failed to query ns `%s`: %s", ns, err) } - return nsObj.Labels["istio-injection"] == "enabled", nil + return nsObj.Labels[zadigtypes.IstioLabelKeyInjection] == zadigtypes.IstioLabelValueInjection, nil } func ensurePodsWithIsitoProxy(ctx context.Context, kclient client.Client, ns string) error { - pods := &corev1.PodList{} - err := kclient.List(ctx, pods, client.InNamespace(ns)) - if err != nil { - return fmt.Errorf("failed to query pods in ns `%s`: %s", ns, err) - } - - deleteOption := metav1.DeletePropagationBackground - for _, pod := range pods.Items { - hasIstioProxy := false - for _, container := range pod.Spec.Containers { - if container.Name == istioProxyName { - hasIstioProxy = true - break - } - } - - if !hasIstioProxy { - err := kclient.Delete(ctx, &pod, &client.DeleteOptions{ - PropagationPolicy: &deleteOption, - }) - - if err != nil { - return fmt.Errorf("failed to ensure Pod `%s` in ns `%s` to inject istio-proxy: %s", pod.Name, ns, err) - } - } - } - - return nil + return restartPodsWithIstioProxy(ctx, kclient, ns, false) } -func checkPodsWithIstioProxyAndReady(ctx context.Context, kclient client.Client, ns string) (bool, error) { +func checkPodsWithIstioProxyAndReady(ctx context.Context, kclient client.Client, ns string) (allHaveIstioProxy, allPodsReady bool, err error) { pods := &corev1.PodList{} - err := kclient.List(ctx, pods, client.InNamespace(ns)) + err = kclient.List(ctx, pods, client.InNamespace(ns)) if err != nil { - return false, fmt.Errorf("failed to query pods in ns `%s`: %s", ns, err) + return false, false, fmt.Errorf("failed to query pods in ns `%s`: %s", ns, err) } + allHaveIstioProxy = true + allPodsReady = true for _, pod := range pods.Items { if !(pod.Status.Phase == corev1.PodRunning || pod.Status.Phase == corev1.PodSucceeded) { - return false, nil + allPodsReady = false } hasIstioProxy := false @@ -375,11 +405,15 @@ func checkPodsWithIstioProxyAndReady(ctx context.Context, kclient client.Client, } } if !hasIstioProxy { - return false, nil + allHaveIstioProxy = false + } + + if !allPodsReady && !allHaveIstioProxy { + return false, false, nil } } - return true, nil + return allHaveIstioProxy, allPodsReady, nil } func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) error { @@ -390,9 +424,10 @@ func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClie } for _, svc := range svcs.Items { - err := ensureVirtualService(ctx, istioClient, ns, svc.Name) + vsName := genVirtualServiceName(&svc) + err := ensureVirtualService(ctx, istioClient, ns, vsName) if err != nil { - return fmt.Errorf("failed to ensure VirtualService `%s` in ns `%s`: %s", svc.Name, ns, err) + return fmt.Errorf("failed to ensure VirtualService `%s` in ns `%s`: %s", vsName, ns, err) } } @@ -411,6 +446,12 @@ func ensureVirtualService(ctx context.Context, istioClient versionedclient.Inter } vsObj.Name = name + + if vsObj.Labels == nil { + vsObj.Labels = map[string]string{} + } + vsObj.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig + vsObj.Spec = networkingv1alpha3.VirtualService{ Hosts: []string{name}, Http: []*networkingv1alpha3.HTTPRoute{ @@ -437,9 +478,13 @@ func checkVirtualServicesDeployed(ctx context.Context, kclient client.Client, is } for _, svc := range svcs.Items { - _, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, svc.Name, metav1.GetOptions{}) + vsName := genVirtualServiceName(&svc) + _, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, vsName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return false, nil + } if err != nil { - return false, fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", svc.Name, ns, err) + return false, fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", vsName, ns, err) } } @@ -485,6 +530,12 @@ func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interfac } envoyFilterObj.Name = name + + if envoyFilterObj.Labels == nil { + envoyFilterObj.Labels = map[string]string{} + } + envoyFilterObj.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig + envoyFilterObj.Spec = networkingv1alpha3.EnvoyFilter{ ConfigPatches: []*networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ &networkingv1alpha3.EnvoyFilter_EnvoyConfigObjectPatch{ @@ -642,3 +693,101 @@ func buildEnvoyPatchValue(data map[string]interface{}) (*types.Struct, error) { err = jsonpb.Unmarshal(reader, val) return val, err } + +func deleteSubEnvs(ctx context.Context, kclient client.Client, baseNS string) error { + + return ErrNotImplemented +} + +func deleteEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, istioNamespace, name string) error { + _, err := istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Get(ctx, name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + log.Infof("EnvoyFilter %s is not found in ns `%s`. Skip.", name, istioNamespace) + return nil + } + if err != nil { + return fmt.Errorf("failed to query EnvoyFilter %s in ns `%s`: %s", name, istioNamespace, err) + } + + deleteOption := metav1.DeletePropagationBackground + return istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Delete(ctx, name, metav1.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) +} + +func deleteVirtualServices(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) error { + zadigLabels := map[string]string{ + zadigtypes.ZadigLabelKeyGlobalOwner: zadigtypes.Zadig, + } + + vsObjs, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).List(ctx, metav1.ListOptions{ + LabelSelector: labels.FormatLabels(zadigLabels), + }) + if err != nil { + return fmt.Errorf("failed to list VirtualServices in ns `%s`: %s", ns, err) + } + + deleteOption := metav1.DeletePropagationBackground + for _, vsObj := range vsObjs.Items { + err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Delete(ctx, vsObj.Name, metav1.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) + if err != nil { + return fmt.Errorf("failed to delete VirtualService %s in ns `%s`: %s", vsObj.Name, ns, err) + } + } + + return nil +} + +func removeIstioLabel(ctx context.Context, kclient client.Client, ns string) error { + nsObj := &corev1.Namespace{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: ns, + }, nsObj) + if err != nil { + return fmt.Errorf("failed to query ns `%s`: %s", ns, err) + } + + delete(nsObj.Labels, zadigtypes.IstioLabelKeyInjection) + return kclient.Update(ctx, nsObj) +} + +func removePodsIstioProxy(ctx context.Context, kclient client.Client, ns string) error { + return restartPodsWithIstioProxy(ctx, kclient, ns, true) +} + +func genVirtualServiceName(svc *corev1.Service) string { + return fmt.Sprintf("%s-%s", zadigNamePrefix, svc.Name) +} + +func restartPodsWithIstioProxy(ctx context.Context, kclient client.Client, ns string, restartConditionHasIstioProxy bool) error { + pods := &corev1.PodList{} + err := kclient.List(ctx, pods, client.InNamespace(ns)) + if err != nil { + return fmt.Errorf("failed to query pods in ns `%s`: %s", ns, err) + } + + deleteOption := metav1.DeletePropagationBackground + for _, pod := range pods.Items { + hasIstioProxy := false + for _, container := range pod.Spec.Containers { + if container.Name == istioProxyName { + hasIstioProxy = true + break + } + } + + if hasIstioProxy == restartConditionHasIstioProxy { + err := kclient.Delete(ctx, &pod, &client.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) + + if err != nil { + return fmt.Errorf("failed to delete Pod `%s` in ns `%s`: %s", pod.Name, ns, err) + } + } + } + + return nil +} diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index c5877b3c..00cc6423 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -127,19 +127,34 @@ type ShareEnvReady struct { } type ShareEnvReadyChecks struct { - NamespaceHasIstioLabel bool `json:"namespace_has_istio_label"` - WorkloadsHaveK8sService bool `json:"workloads_have_k8s_service"` - VirtualServicesDeployed bool `json:"virtualservice_deployed"` - PodsHaveIstioProxyAndReady bool `json:"pods_have_istio_proxy_and_ready"` + NamespaceHasIstioLabel bool `json:"namespace_has_istio_label"` + VirtualServicesDeployed bool `json:"virtualservice_deployed"` + PodsHaveIstioProxy bool `json:"pods_have_istio_proxy"` + WorkloadsReady bool `json:"workloads_ready"` + WorkloadsHaveK8sService bool `json:"workloads_have_k8s_service"` } -func (s *ShareEnvReady) CheckAndSetReady() { - if !s.Checks.NamespaceHasIstioLabel || !s.Checks.WorkloadsHaveK8sService || !s.Checks.VirtualServicesDeployed || !s.Checks.PodsHaveIstioProxyAndReady { +// Note: `WorkloadsHaveK8sService` is an optional condition. +func (s *ShareEnvReady) CheckAndSetReady(state ShareEnvOp) { + if !s.Checks.WorkloadsReady { s.IsReady = false return } - s.IsReady = true + switch state { + case ShareEnvEnable: + if !s.Checks.NamespaceHasIstioLabel || !s.Checks.VirtualServicesDeployed || !s.Checks.PodsHaveIstioProxy { + s.IsReady = false + } else { + s.IsReady = true + } + default: + if !s.Checks.NamespaceHasIstioLabel && !s.Checks.VirtualServicesDeployed && !s.Checks.PodsHaveIstioProxy { + s.IsReady = true + } else { + s.IsReady = false + } + } } type EnvoyClusterConfigLoadAssignment struct { @@ -168,3 +183,10 @@ type EnvoySocketAddress struct { Address string `json:"address"` PortValue int `json:"port_value"` } + +type ShareEnvOp string + +const ( + ShareEnvEnable ShareEnvOp = "enable" + ShareEnvDisable ShareEnvOp = "disable" +) diff --git a/pkg/types/labels.go b/pkg/types/labels.go new file mode 100644 index 00000000..58598663 --- /dev/null +++ b/pkg/types/labels.go @@ -0,0 +1,27 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import "fmt" + +const ZadigDomain = "zadig.koderover.com" +const Zadig = "zadig" + +var ZadigLabelKeyGlobalOwner = fmt.Sprintf("%s/owner", ZadigDomain) + +const IstioLabelKeyInjection = "istio-injection" +const IstioLabelValueInjection = "enabled" -- Gitee From 853ad67f93cf3691cef905120b7215fe216e802f Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 25 Mar 2022 11:39:30 +0800 Subject: [PATCH 1925/2647] refine license Signed-off-by: zhangyifei --- pkg/handler/cache/cache.go | 20 +++++++++---------- pkg/handler/cache/consts.go | 18 ++++++++--------- pkg/handler/cache/handler.go | 18 ++++++++--------- pkg/handler/cache/interface.go | 20 +++++++++---------- pkg/handler/cache/router.go | 20 +++++++++---------- .../core/environment/handler/share_env.go | 20 +++++++++---------- .../core/multicluster/handler/share_env.go | 20 +++++++++---------- .../service/{share-env.go => share_env.go} | 20 +++++++++---------- 8 files changed, 78 insertions(+), 78 deletions(-) rename pkg/microservice/aslan/core/multicluster/service/{share-env.go => share_env.go} (65%) diff --git a/pkg/handler/cache/cache.go b/pkg/handler/cache/cache.go index cca8ba07..82cd8052 100644 --- a/pkg/handler/cache/cache.go +++ b/pkg/handler/cache/cache.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package cache diff --git a/pkg/handler/cache/consts.go b/pkg/handler/cache/consts.go index d8d46140..98a8f2c3 100644 --- a/pkg/handler/cache/consts.go +++ b/pkg/handler/cache/consts.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package cache diff --git a/pkg/handler/cache/handler.go b/pkg/handler/cache/handler.go index 8aae7899..cfc2ac8f 100644 --- a/pkg/handler/cache/handler.go +++ b/pkg/handler/cache/handler.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package cache diff --git a/pkg/handler/cache/interface.go b/pkg/handler/cache/interface.go index 4a08d107..ab2abede 100644 --- a/pkg/handler/cache/interface.go +++ b/pkg/handler/cache/interface.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package cache diff --git a/pkg/handler/cache/router.go b/pkg/handler/cache/router.go index 5f091f62..e83bad03 100644 --- a/pkg/handler/cache/router.go +++ b/pkg/handler/cache/router.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package cache diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index 0adfe82a..bd22a4ce 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -1,17 +1,17 @@ /* - Copyright 2021 The KodeRover Authors. +Copyright 2021 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package handler diff --git a/pkg/microservice/aslan/core/multicluster/handler/share_env.go b/pkg/microservice/aslan/core/multicluster/handler/share_env.go index 43c77ab1..8a187bab 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/share_env.go +++ b/pkg/microservice/aslan/core/multicluster/handler/share_env.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package handler diff --git a/pkg/microservice/aslan/core/multicluster/service/share-env.go b/pkg/microservice/aslan/core/multicluster/service/share_env.go similarity index 65% rename from pkg/microservice/aslan/core/multicluster/service/share-env.go rename to pkg/microservice/aslan/core/multicluster/service/share_env.go index 391ed98b..2ca6820a 100644 --- a/pkg/microservice/aslan/core/multicluster/service/share-env.go +++ b/pkg/microservice/aslan/core/multicluster/service/share_env.go @@ -1,17 +1,17 @@ /* - Copyright 2022 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ package service -- Gitee From 92b7813454fdaddceae9f9a357b6043672fbcf20 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 17:18:06 +0800 Subject: [PATCH 1926/2647] add code related interfaces Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 122 ++++++++++++++++++ .../aslan/core/code/client/open/open.go | 2 + pkg/setting/consts.go | 2 + pkg/tool/gitee/organizations.go | 18 +++ pkg/tool/gitee/repositories.go | 11 +- 5 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 pkg/tool/gitee/organizations.go diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 56733645..41791ee8 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -1 +1,123 @@ package gitee + +import ( + "context" + + giteeClient "gitee.com/openeuler/go-gitee/gitee" + "github.com/antihax/optional" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" + "github.com/koderover/zadig/pkg/tool/gitee" +) + +type Config struct { + AccessToken string `json:"access_token"` + EnableProxy bool `json:"enable_proxy"` +} + +type Client struct { + Client *gitee.Client +} + +func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { + client, _ := gitee.NewClient(c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) + return &Client{ + Client: client, + }, nil +} + +func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { + bList, err := c.Client.ListBranches(context.TODO(), opt.Namespace, opt.ProjectName, nil) + if err != nil { + return nil, err + } + var res []*client.Branch + for _, o := range bList { + res = append(res, &client.Branch{ + Name: o.Name, + Protected: o.Protected, + }) + } + return res, nil +} + +func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { + tag, err := c.Client.ListTags(context.TODO(), opt.Namespace, opt.ProjectName, nil) + if err != nil { + return nil, err + } + var tags []*client.Tag + tags = append(tags, &client.Tag{ + Name: tag.Name, + }) + + return tags, nil +} + +func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { + prs, err := c.Client.ListPullRequests(context.TODO(), opt.Namespace, opt.ProjectName, &giteeClient.GetV5ReposOwnerRepoPullsOpts{ + PerPage: optional.NewInt32(100), + }) + if err != nil { + return nil, err + } + var res []*client.PullRequest + for _, o := range prs { + res = append(res, &client.PullRequest{ + ID: int(o.Number), + State: o.State, + User: o.User.Login, + Number: int(o.Number), + AuthorUsername: o.User.Login, + Title: o.Title, + SourceBranch: o.Base.Ref, + TargetBranch: o.Base.Ref, + }) + } + return res, nil +} + +func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { + user, err := c.Client.GetAuthenticatedUser(context.TODO()) + if err != nil { + return nil, err + } + namespaceUser := client.Namespace{ + Name: user.Login, + Path: user.Login, + Kind: client.UserKind, + } + + organizations, err := c.Client.ListOrganizationsForAuthenticatedUser(context.TODO()) + if err != nil { + return nil, err + } + + var res []*client.Namespace + res = append(res, &namespaceUser) + for _, o := range organizations { + res = append(res, &client.Namespace{ + Name: o.Login, + Path: o.Login, + Kind: client.OrgKind, + }) + } + return res, nil +} + +func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { + repo, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO()) + if err != nil { + return nil, err + } + var res []*client.Project + res = append(res, &client.Project{ + ID: int(repo.Id), + Name: repo.Name, + DefaultBranch: repo.DefaultBranch, + Namespace: repo.Owner.Login, + }) + return res, nil +} diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 9165acd3..f1a37900 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/codehub" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gerrit" + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitee" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" "github.com/koderover/zadig/pkg/setting" @@ -40,6 +41,7 @@ var ClientsConfig = map[string]func() ClientConfig{ setting.SourceFromGithub: func() ClientConfig { return new(github.Config) }, setting.SourceFromGerrit: func() ClientConfig { return new(gerrit.Config) }, setting.SourceFromCodeHub: func() ClientConfig { return new(codehub.Config) }, + setting.SourceFromGitee: func() ClientConfig { return new(gitee.Config) }, } func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index d5a91f91..071c8ddb 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -237,6 +237,8 @@ const ( SourceFromGerrit = "gerrit" // SourceFromCodeHub 配置来源为codehub SourceFromCodeHub = "codehub" + // SourceFromGitee 配置来源为gitee + SourceFromGitee = "gitee" // SourceFromChartTemplate 配置来源为helmTemplate SourceFromChartTemplate = "chartTemplate" // SourceFromPublicRepo 配置来源为publicRepo diff --git a/pkg/tool/gitee/organizations.go b/pkg/tool/gitee/organizations.go new file mode 100644 index 00000000..e40b6031 --- /dev/null +++ b/pkg/tool/gitee/organizations.go @@ -0,0 +1,18 @@ +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" + "github.com/antihax/optional" +) + +func (c *Client) ListOrganizationsForAuthenticatedUser(ctx context.Context) ([]gitee.Group, error) { + resp, _, err := c.OrganizationsApi.GetV5UserOrgs(ctx, &gitee.GetV5UserOrgsOpts{ + PerPage: optional.NewInt32(100), + }) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 38c79747..cb4e77b1 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -3,13 +3,20 @@ package gitee import ( "context" + "gitee.com/openeuler/go-gitee/gitee" "github.com/antihax/optional" "github.com/koderover/zadig/pkg/tool/git" - - "gitee.com/openeuler/go-gitee/gitee" ) +func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context) (gitee.Project, error) { + resp, _, err := c.RepositoriesApi.GetV5UserRepos(ctx, &gitee.GetV5UserReposOpts{}) + if err != nil { + return gitee.Project{}, err + } + return resp, nil +} + func (c *Client) ListHooks(ctx context.Context, owner, repo string, opts *gitee.GetV5ReposOwnerRepoHooksOpts) ([]gitee.Hook, error) { hs, _, err := c.WebhooksApi.GetV5ReposOwnerRepoHooks(ctx, owner, repo, opts) if err != nil { -- Gitee From d000d11b82b39187132ce0612bc73438aeff041b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 17:25:57 +0800 Subject: [PATCH 1927/2647] add third-party package dependencies Signed-off-by: liu deyi --- go.mod | 1 + go.sum | 1 + 2 files changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 88e415e0..b55001a5 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 + github.com/antihax/optional v1.0.0 // indirect github.com/aws/aws-sdk-go v1.34.28 github.com/blang/semver/v4 v4.0.0 github.com/bndr/gojenkins v1.1.0 diff --git a/go.sum b/go.sum index cb0107d8..af21fdde 100644 --- a/go.sum +++ b/go.sum @@ -206,6 +206,7 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 h1:2Qp3BQZ7r1z+oMUnFhD6FQ86Hh1PO+59yoUNNJ5Dn0M= github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1/go.mod h1:0iuRQp6WJ44ts+iihy5E/WlPqfg5RNeQxOmzRkxCdtk= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ= -- Gitee From 5877e71ce5f8a802034b5fd4c0f6551b7aca1aae Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 17:56:35 +0800 Subject: [PATCH 1928/2647] debug cdoe Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/client/gitee/gitee.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 41791ee8..bb2b8095 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -18,13 +18,16 @@ type Config struct { } type Client struct { - Client *gitee.Client + Client *gitee.Client + AccessToken string `json:"access_token"` } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { client, _ := gitee.NewClient(c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) + logger.Infof("c.AccessToken:%s", c.AccessToken) return &Client{ - Client: client, + Client: client, + AccessToken: c.AccessToken, }, nil } -- Gitee From e9af7e2151c8adb763a87f7a2980fdadc890ccb9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 25 Mar 2022 17:57:46 +0800 Subject: [PATCH 1929/2647] debug Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index fe6b433c..508cc691 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -176,8 +176,6 @@ rules: endpoint: "/api/aslan/environment/configmaps" - method: POST endpoint: "/api/aslan/environment/configmaps" - - method: GET - endpoint: "/api/podexec/?*/?*/?*/?*/podExec" - method: POST endpoint: "/api/aslan/workflow/servicetask" - action: delete_environment @@ -191,3 +189,9 @@ rules: matchAttributes: - key: "production" value: "false" + - action: debug_pod + alias: "服务调试" + description: "" + rules: + - method: GET + endpoint: "/api/podexec/?*/?*/?*/?*/podExec" \ No newline at end of file -- Gitee From 939a14148cefc6089e66f720a382716dd2f59cbd Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 25 Mar 2022 18:13:57 +0800 Subject: [PATCH 1930/2647] debug cdoe Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index bb2b8095..64d19143 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -18,16 +18,13 @@ type Config struct { } type Client struct { - Client *gitee.Client - AccessToken string `json:"access_token"` + Client *gitee.Client } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { client, _ := gitee.NewClient(c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) - logger.Infof("c.AccessToken:%s", c.AccessToken) return &Client{ - Client: client, - AccessToken: c.AccessToken, + Client: client, }, nil } @@ -116,11 +113,14 @@ func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { return nil, err } var res []*client.Project - res = append(res, &client.Project{ + project := &client.Project{ ID: int(repo.Id), Name: repo.Name, DefaultBranch: repo.DefaultBranch, - Namespace: repo.Owner.Login, - }) + } + if repo.Owner != nil { + project.Namespace = repo.Owner.Login + } + res = append(res, project) return res, nil } -- Gitee From 1ded17a9b661bd33e6710f86ef45759bc4042a0f Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sun, 27 Mar 2022 22:13:15 +0800 Subject: [PATCH 1931/2647] support creating gray env Signed-off-by: zhangyifei --- .../core/common/repository/models/product.go | 9 + .../aslan/core/common/service/kube/actions.go | 12 +- .../core/environment/handler/environment.go | 7 + .../core/environment/handler/share_env.go | 2 +- .../core/environment/service/environment.go | 65 ++--- .../service/environment_creator.go | 15 +- .../core/environment/service/share_env.go | 269 +++++++++++++++++- 7 files changed, 323 insertions(+), 56 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index f9df7679..2d2a0300 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -56,6 +56,9 @@ type Product struct { IsExisted bool `bson:"is_existed" json:"is_existed"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` + + // New Since v1.11.0. + ShareEnv ProductShareEnv `bson:"share_env" json:"share_env"` } type RenderInfo struct { @@ -86,6 +89,12 @@ type ServiceConfig struct { Revision int64 `bson:"revision" json:"revision"` } +type ProductShareEnv struct { + Enable bool `bson:"enable" json:"enable"` + IsBase bool `bson:"is_base" json:"is_base"` + BaseEnv string `bson:"base_env" json:"base_env"` +} + func (Product) TableName() string { return "product" } diff --git a/pkg/microservice/aslan/core/common/service/kube/actions.go b/pkg/microservice/aslan/core/common/service/kube/actions.go index 51ff6a74..2058d3ce 100644 --- a/pkg/microservice/aslan/core/common/service/kube/actions.go +++ b/pkg/microservice/aslan/core/common/service/kube/actions.go @@ -32,10 +32,18 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" + zadigtypes "github.com/koderover/zadig/pkg/types" ) -func CreateNamespace(namespace string, kubeClient client.Client) error { - err := updater.CreateNamespaceByName(namespace, map[string]string{setting.EnvCreatedBy: setting.EnvCreator}, kubeClient) +func CreateNamespace(namespace string, enableShare bool, kubeClient client.Client) error { + labels := map[string]string{ + setting.EnvCreatedBy: setting.EnvCreator, + } + if enableShare { + labels[zadigtypes.IstioLabelKeyInjection] = zadigtypes.IstioLabelValueInjection + } + + err := updater.CreateNamespaceByName(namespace, labels, kubeClient) if err != nil && !apierrors.IsAlreadyExists(err) { return err } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index c47b6e9e..103cebac 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -234,6 +234,13 @@ func CreateProduct(c *gin.Context) { return } + // !!! [ZADIG] FOR DEBUG. + args.ShareEnv = commonmodels.ProductShareEnv{ + Enable: true, + IsBase: false, + BaseEnv: "dev", + } + args.UpdateBy = ctx.UserName ctx.Err = service.CreateProduct( ctx.UserName, ctx.RequestID, args, ctx.Logger, diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index bd22a4ce..be12f755 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 587db829..d9a3dd9f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -34,6 +34,7 @@ import ( "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/strvals" + versionedclient "istio.io/client-go/pkg/clientset/versioned" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -46,10 +47,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -655,7 +655,7 @@ func UpdateProductRegistry(envName, productName, registryID string, log *zap.Sug if err != nil { return e.ErrUpdateEnv.AddErr(err) } - err = ensureKubeEnv(exitedProd.Namespace, registryID, kubeClient, log) + err = ensureKubeEnv(exitedProd.Namespace, registryID, false, kubeClient, log) if err != nil { log.Errorf("UpdateProductRegistry ensureKubeEnv by envName:%s,error: %v", envName, err) @@ -694,7 +694,7 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames } if project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { - err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, kubeClient, log) + err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, false, kubeClient, log) if err != nil { log.Errorf("[%s][P:%s] service.UpdateProductV2 create kubeEnv error: %v", envName, productName, err) @@ -1403,7 +1403,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) return err } - return ensureKubeEnv(product.Namespace, product.RegistryID, kubeClient, log) + return ensureKubeEnv(product.Namespace, product.RegistryID, false, kubeClient, log) } func UpdateHelmProductVariable(productName, envName, username, requestID string, updatedRcs []*templatemodels.RenderChart, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { @@ -1839,7 +1839,7 @@ func GetEstimatedRenderCharts(productName, envName, serviceNameListStr string, l return ret, nil } -func createGroups(envName, user, requestID string, args *commonmodels.Product, eventStart int64, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger) { +func createGroups(envName, user, requestID string, args *commonmodels.Product, eventStart int64, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, istioClient versionedclient.Interface, log *zap.SugaredLogger) { var err error defer func() { status := setting.ProductStatusSuccess @@ -1873,6 +1873,19 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e return } } + + // If the user does not enable environment sharing, end. Otherwise, continue to perform environment sharing operations. + if !args.ShareEnv.Enable { + return + } + + // Note: Currently, only sub-environments can be created, but baseline environments cannot be created. + err = ensureGrayEnvConfig(context.TODO(), args, kubeClient, istioClient) + if err != nil { + args.Status = setting.ProductStatusFailed + log.Errorf("Failed to ensure environment sharing in env %s of product %s: %s", args.EnvName, args.ProductName, err) + return + } } func getProjectType(productName string) string { @@ -2407,7 +2420,7 @@ func preCreateProduct(envName string, args *commonmodels.Product, kubeClient cli args.Render = tmpRenderInfo if preCreateNSAndSecret(productTmpl.ProductFeature) { - return ensureKubeEnv(args.Namespace, args.RegistryID, kubeClient, log) + return ensureKubeEnv(args.Namespace, args.RegistryID, args.ShareEnv.Enable, kubeClient, log) } return nil } @@ -2435,40 +2448,6 @@ func getPredefinedClusterLabels(product, service, envName string) map[string]str return labels } -func applySystemIngressTimeouts(labels map[string]string) map[string]string { - if labels == nil { - labels = make(map[string]string) - } - - if _, ok := labels[setting.IngressProxyConnectTimeoutLabel]; !ok { - labels[setting.IngressProxyConnectTimeoutLabel] = "300" - } - - if _, ok := labels[setting.IngressProxySendTimeoutLabel]; !ok { - labels[setting.IngressProxySendTimeoutLabel] = "300" - } - - if _, ok := labels[setting.IngressProxyReadTimeoutLabel]; !ok { - labels[setting.IngressProxyReadTimeoutLabel] = "300" - } - - return labels -} - -func applySystemIngressClass(labels map[string]string) map[string]string { - if labels == nil { - labels = make(map[string]string) - } - - if config.DefaultIngressClass() != "" { - if _, ok := labels[setting.IngressClassLabel]; !ok { - labels[setting.IngressClassLabel] = config.DefaultIngressClass() - } - } - - return labels -} - func applyUpdatedAnnotations(annotations map[string]string) map[string]string { if annotations == nil { annotations = make(map[string]string) @@ -2490,8 +2469,8 @@ func applySystemImagePullSecrets(podSpec *corev1.PodSpec) { }) } -func ensureKubeEnv(namespace string, registryId string, kubeClient client.Client, log *zap.SugaredLogger) error { - err := kube.CreateNamespace(namespace, kubeClient) +func ensureKubeEnv(namespace, registryId string, enableShare bool, kubeClient client.Client, log *zap.SugaredLogger) error { + err := kube.CreateNamespace(namespace, enableShare, kubeClient) if err != nil { log.Errorf("[%s] get or create namespace error: %v", namespace, err) return e.ErrCreateNamspace.AddDesc(e.SetNamespaceErrMsg) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 1522b150..920f4149 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -18,9 +18,11 @@ package service import ( "errors" + "fmt" "time" "go.uber.org/zap" + versionedclient "istio.io/client-go/pkg/clientset/versioned" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -265,7 +267,7 @@ func (creator *PMProductCreator) Create(user, requestID string, args *models.Pro return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, nil, log) + go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, nil, nil, log) return nil } @@ -304,6 +306,15 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddErr(err) } + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to new istio client: %s", err) + } + //判断namespace是否存在 namespace := args.GetNamespace() if args.Namespace == "" { @@ -353,6 +364,6 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, inf, kubeClient, log) + go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, inf, kubeClient, istioClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index cbeac85b..148df6a0 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -36,6 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" @@ -53,6 +54,7 @@ const envoyFilterHttpRouter = "envoy.filters.http.router" const envoyFilterLua = "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" const zadigNamePrefix = "zadig" +const zadigMatchXEnv = "x-env" // Slice of `.` and error are returned. func CheckWorkloadsK8sServices(ctx context.Context, envName, productName string) ([]string, error) { @@ -425,7 +427,7 @@ func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClie for _, svc := range svcs.Items { vsName := genVirtualServiceName(&svc) - err := ensureVirtualService(ctx, istioClient, ns, vsName) + err := ensureVirtualService(ctx, istioClient, ns, vsName, svc.Name) if err != nil { return fmt.Errorf("failed to ensure VirtualService `%s` in ns `%s`: %s", vsName, ns, err) } @@ -434,18 +436,18 @@ func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClie return nil } -func ensureVirtualService(ctx context.Context, istioClient versionedclient.Interface, ns, name string) error { - vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, name, metav1.GetOptions{}) +func ensureVirtualService(ctx context.Context, istioClient versionedclient.Interface, ns, vsName, svcName string) error { + vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, vsName, metav1.GetOptions{}) if err == nil { - log.Infof("Has found VirtualService `%s` in ns `%s` and don't recreate.", name, ns) + log.Infof("Has found VirtualService `%s` in ns `%s` and don't recreate.", vsName, ns) return nil } if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", name, ns, err) + return fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", vsName, ns, err) } - vsObj.Name = name + vsObj.Name = vsName if vsObj.Labels == nil { vsObj.Labels = map[string]string{} @@ -453,13 +455,13 @@ func ensureVirtualService(ctx context.Context, istioClient versionedclient.Inter vsObj.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig vsObj.Spec = networkingv1alpha3.VirtualService{ - Hosts: []string{name}, + Hosts: []string{svcName}, Http: []*networkingv1alpha3.HTTPRoute{ &networkingv1alpha3.HTTPRoute{ Route: []*networkingv1alpha3.HTTPRouteDestination{ &networkingv1alpha3.HTTPRouteDestination{ Destination: &networkingv1alpha3.Destination{ - Host: fmt.Sprintf("%s.%s.svc.cluster.local", name, ns), + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, ns), }, }, }, @@ -791,3 +793,254 @@ func restartPodsWithIstioProxy(ctx context.Context, kclient client.Client, ns st return nil } + +func ensureGrayEnvConfig(ctx context.Context, env *commonmodels.Product, kclient client.Client, istioClient versionedclient.Interface) error { + opt := &commonrepo.ProductFindOptions{Name: env.ProductName, EnvName: env.ShareEnv.BaseEnv} + baseEnv, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return fmt.Errorf("failed to find base env %s of product %s: %s", env.EnvName, env.ProductName, err) + } + + baseNS := baseEnv.Namespace + + // 1. Deploy VirtualServices of the workloads in gray environment and update them in the base environment. + err = ensureWorkloadsVirtualServiceInGrayAndBase(ctx, env, baseNS, kclient, istioClient) + if err != nil { + return fmt.Errorf("failed to ensure workloads VirtualService: %s", err) + } + + // 2. Deploy K8s Services and VirtualServices of all workloads in the base environment to the gray environment. + err = ensureDefaultK8sServiceAndVirtualServicesInGray(ctx, env, baseNS, kclient, istioClient) + if err != nil { + return fmt.Errorf("failed to ensure K8s Services and VirtualServices: %s", err) + } + + return nil +} + +func ensureDefaultK8sServiceAndVirtualServicesInGray(ctx context.Context, env *commonmodels.Product, baseNS string, kclient client.Client, istioClient versionedclient.Interface) error { + svcsInBase := &corev1.ServiceList{} + err := kclient.List(ctx, svcsInBase, client.InNamespace(baseNS)) + if err != nil { + return fmt.Errorf("failed to list svcs in %s: %s", baseNS, err) + } + + grayNS := env.Namespace + for _, svcInBase := range svcsInBase.Items { + err = ensureDefaultK8sServiceInGray(ctx, &svcInBase, grayNS, kclient) + if err != nil { + return err + } + + err = ensureDefaultVirtualServiceInGray(ctx, &svcInBase, grayNS, baseNS, istioClient) + if err != nil { + return err + } + } + + return nil +} + +func ensureDefaultK8sServiceInGray(ctx context.Context, baseSvc *corev1.Service, grayNS string, kclient client.Client) error { + svcInGray := &corev1.Service{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: baseSvc.Name, + Namespace: grayNS, + }, svcInGray) + if err == nil { + return nil + } + if !apierrors.IsNotFound(err) { + return err + } + + svcInGray.Name = baseSvc.Name + svcInGray.Namespace = grayNS + svcInGray.Labels = baseSvc.Labels + svcInGray.Annotations = baseSvc.Annotations + svcInGray.Spec.Selector = baseSvc.Spec.Selector + svcInGray.Spec.Ports = baseSvc.Spec.Ports + + if svcInGray.Labels != nil { + svcInGray.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig + } + + return kclient.Create(ctx, svcInGray) +} + +func ensureDefaultVirtualServiceInGray(ctx context.Context, baseSvc *corev1.Service, grayNS, baseNS string, istioClient versionedclient.Interface) error { + vsName := genVirtualServiceName(baseSvc) + svcName := baseSvc.Name + vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Get(ctx, vsName, metav1.GetOptions{}) + if err == nil { + log.Infof("Has found VirtualService `%s` in ns `%s` and don't recreate.", vsName, grayNS) + return nil + } + + if !apierrors.IsNotFound(err) { + return err + } + + vsObj.Name = vsName + + if vsObj.Labels == nil { + vsObj.Labels = map[string]string{} + } + vsObj.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig + + vsObj.Spec = networkingv1alpha3.VirtualService{ + Hosts: []string{svcName}, + Http: []*networkingv1alpha3.HTTPRoute{ + &networkingv1alpha3.HTTPRoute{ + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, baseNS), + }, + }, + }, + }, + }, + } + _, err = istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Create(ctx, vsObj, metav1.CreateOptions{}) + return err +} + +// Note: Currently we have made an assumption that all the necessary K8s services exist in the current environment. +func ensureWorkloadsVirtualServiceInGrayAndBase(ctx context.Context, env *commonmodels.Product, baseNS string, kclient client.Client, istioClient versionedclient.Interface) error { + svcs := &corev1.ServiceList{} + grayNS := env.Namespace + err := kclient.List(ctx, svcs, client.InNamespace(grayNS)) + if err != nil { + return err + } + + for _, svc := range svcs.Items { + vsName := genVirtualServiceName(&svc) + + err = ensureVirtualServiceInGray(ctx, env.EnvName, vsName, svc.Name, grayNS, baseNS, istioClient) + if err != nil { + return err + } + + err = ensureUpdateVirtualServiceInBase(ctx, env.EnvName, vsName, svc.Name, grayNS, baseNS, istioClient) + if err != nil { + return err + } + } + + return nil +} + +func ensureVirtualServiceInGray(ctx context.Context, envName, vsName, svcName, grayNS, baseNS string, istioClient versionedclient.Interface) error { + vsObjInGray, err := istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Get(ctx, vsName, metav1.GetOptions{}) + if err == nil { + return nil + } + + if !apierrors.IsNotFound(err) { + return err + } + + vsObjInGray.Name = vsName + vsObjInGray.Namespace = grayNS + + if vsObjInGray.Labels == nil { + vsObjInGray.Labels = map[string]string{} + } + vsObjInGray.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig + + vsObjInGray.Spec = networkingv1alpha3.VirtualService{ + Hosts: []string{svcName}, + Http: []*networkingv1alpha3.HTTPRoute{ + &networkingv1alpha3.HTTPRoute{ + Match: []*networkingv1alpha3.HTTPMatchRequest{ + &networkingv1alpha3.HTTPMatchRequest{ + Headers: map[string]*networkingv1alpha3.StringMatch{ + zadigMatchXEnv: &networkingv1alpha3.StringMatch{ + MatchType: &networkingv1alpha3.StringMatch_Exact{ + Exact: envName, + }, + }, + }, + }, + }, + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, grayNS), + }, + }, + }, + }, + &networkingv1alpha3.HTTPRoute{ + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, baseNS), + }, + }, + }, + }, + }, + } + + _, err = istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Create(ctx, vsObjInGray, metav1.CreateOptions{}) + return err +} + +func ensureUpdateVirtualServiceInBase(ctx context.Context, envName, vsName, svcName, grayNS, baseNS string, istioClient versionedclient.Interface) error { + vsObjInBase, err := istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Get(ctx, vsName, metav1.GetOptions{}) + if err != nil { + return err + } + + if vsObjInBase.Spec.Http == nil { + vsObjInBase.Spec.Http = []*networkingv1alpha3.HTTPRoute{} + } + + for _, vsHttp := range vsObjInBase.Spec.Http { + if len(vsHttp.Match) == 0 { + continue + } + + for _, vsMatch := range vsHttp.Match { + if len(vsMatch.Headers) == 0 { + continue + } + + matchValue, found := vsMatch.Headers[zadigMatchXEnv] + if !found { + continue + } + + if matchValue.GetExact() == envName { + return nil + } + } + } + + vsObjInBase.Spec.Http = append(vsObjInBase.Spec.Http, &networkingv1alpha3.HTTPRoute{ + Match: []*networkingv1alpha3.HTTPMatchRequest{ + &networkingv1alpha3.HTTPMatchRequest{ + Headers: map[string]*networkingv1alpha3.StringMatch{ + zadigMatchXEnv: &networkingv1alpha3.StringMatch{ + MatchType: &networkingv1alpha3.StringMatch_Exact{ + Exact: envName, + }, + }, + }, + }, + }, + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, grayNS), + }, + }, + }, + }) + + _, err = istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Update(ctx, vsObjInBase, metav1.UpdateOptions{}) + return err +} -- Gitee From a4d2d6e87231d0b11ecc49c2d9d5f126191192f0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 09:28:02 +0800 Subject: [PATCH 1932/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 19 +++++++++++-------- pkg/tool/gitee/repositories.go | 4 +++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 64d19143..d669a920 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -113,14 +113,17 @@ func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { return nil, err } var res []*client.Project - project := &client.Project{ - ID: int(repo.Id), - Name: repo.Name, - DefaultBranch: repo.DefaultBranch, + if repo.Id > 0 { + project := &client.Project{ + ID: int(repo.Id), + Name: repo.Name, + DefaultBranch: repo.DefaultBranch, + } + if repo.Owner != nil { + project.Namespace = repo.Owner.Login + } + res = append(res, project) } - if repo.Owner != nil { - project.Namespace = repo.Owner.Login - } - res = append(res, project) + return res, nil } diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index cb4e77b1..15e62416 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -10,7 +10,9 @@ import ( ) func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context) (gitee.Project, error) { - resp, _, err := c.RepositoriesApi.GetV5UserRepos(ctx, &gitee.GetV5UserReposOpts{}) + resp, _, err := c.RepositoriesApi.GetV5UserRepos(ctx, &gitee.GetV5UserReposOpts{ + Type_: optional.NewString("all"), + }) if err != nil { return gitee.Project{}, err } -- Gitee From a2cbfd7f0de605d2af7c0610c4bd2743445ec5ea Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 10:07:24 +0800 Subject: [PATCH 1933/2647] debug code Signed-off-by: liu deyi --- pkg/tool/gitee/repositories.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 15e62416..ed81549e 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -11,7 +11,8 @@ import ( func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context) (gitee.Project, error) { resp, _, err := c.RepositoriesApi.GetV5UserRepos(ctx, &gitee.GetV5UserReposOpts{ - Type_: optional.NewString("all"), + Visibility: optional.NewString("all"), + Affiliation: optional.NewString("admin"), }) if err != nil { return gitee.Project{}, err -- Gitee From a4d7ad7cdd1c3b314dd093a0aae7b485099aa5ef Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 11:06:00 +0800 Subject: [PATCH 1934/2647] debug code Signed-off-by: liu deyi --- pkg/tool/gitee/repositories.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index ed81549e..0840203a 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -2,6 +2,7 @@ package gitee import ( "context" + "fmt" "gitee.com/openeuler/go-gitee/gitee" "github.com/antihax/optional" @@ -13,10 +14,12 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context) (gite resp, _, err := c.RepositoriesApi.GetV5UserRepos(ctx, &gitee.GetV5UserReposOpts{ Visibility: optional.NewString("all"), Affiliation: optional.NewString("admin"), + PerPage: optional.NewInt32(100), }) if err != nil { return gitee.Project{}, err } + fmt.Println(fmt.Sprintf("resp:%+v", resp)) return resp, nil } -- Gitee From f1217b798b727209ebd7f2d572dc62ffbf3d1ba5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 11:57:26 +0800 Subject: [PATCH 1935/2647] modify tag and project interface Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 38 +++++++++---------- pkg/tool/gitee/repositories.go | 31 ++++++++++----- pkg/tool/gitee/tag.go | 16 ++++++-- 3 files changed, 52 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index d669a920..0436c67d 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -18,13 +18,15 @@ type Config struct { } type Client struct { - Client *gitee.Client + Client *gitee.Client + AccessToken string } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { client, _ := gitee.NewClient(c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) return &Client{ - Client: client, + Client: client, + AccessToken: c.AccessToken, }, nil } @@ -44,16 +46,18 @@ func (c *Client) ListBranches(opt client.ListOpt) ([]*client.Branch, error) { } func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { - tag, err := c.Client.ListTags(context.TODO(), opt.Namespace, opt.ProjectName, nil) + tags, err := c.Client.ListTags(context.TODO(), c.AccessToken, opt.Namespace, opt.ProjectName) if err != nil { return nil, err } - var tags []*client.Tag - tags = append(tags, &client.Tag{ - Name: tag.Name, - }) + var resp []*client.Tag + for _, tag := range tags { + resp = append(resp, &client.Tag{ + Name: tag.Name, + }) + } - return tags, nil + return resp, nil } func (c *Client) ListPrs(opt client.ListOpt) ([]*client.PullRequest, error) { @@ -108,21 +112,17 @@ func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { } func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { - repo, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO()) + projects, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO(), c.AccessToken) if err != nil { return nil, err } var res []*client.Project - if repo.Id > 0 { - project := &client.Project{ - ID: int(repo.Id), - Name: repo.Name, - DefaultBranch: repo.DefaultBranch, - } - if repo.Owner != nil { - project.Namespace = repo.Owner.Login - } - res = append(res, project) + for _, project := range projects { + res = append(res, &client.Project{ + ID: int(project.Id), + Name: project.Name, + DefaultBranch: project.DefaultBranch, + }) } return res, nil diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 0840203a..93fd5be5 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -2,25 +2,36 @@ package gitee import ( "context" - "fmt" "gitee.com/openeuler/go-gitee/gitee" "github.com/antihax/optional" "github.com/koderover/zadig/pkg/tool/git" + "github.com/koderover/zadig/pkg/tool/httpclient" ) -func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context) (gitee.Project, error) { - resp, _, err := c.RepositoriesApi.GetV5UserRepos(ctx, &gitee.GetV5UserReposOpts{ - Visibility: optional.NewString("all"), - Affiliation: optional.NewString("admin"), - PerPage: optional.NewInt32(100), - }) +const ( + GiteeHOSTURL = "https://gitee.com/api" +) + +func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, accessToken string) ([]gitee.Project, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := "/v5/user/repos" + queryParams := make(map[string]string) + queryParams["access_token"] = accessToken + queryParams["visibility"] = "all" + queryParams["affiliation"] = "admin" + queryParams["per_page"] = "100" + + var projects []gitee.Project + _, err := httpClient.Get(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&projects)) if err != nil { - return gitee.Project{}, err + return nil, err } - fmt.Println(fmt.Sprintf("resp:%+v", resp)) - return resp, nil + + return projects, nil } func (c *Client) ListHooks(ctx context.Context, owner, repo string, opts *gitee.GetV5ReposOwnerRepoHooksOpts) ([]gitee.Hook, error) { diff --git a/pkg/tool/gitee/tag.go b/pkg/tool/gitee/tag.go index 36bf1f34..73693073 100644 --- a/pkg/tool/gitee/tag.go +++ b/pkg/tool/gitee/tag.go @@ -2,14 +2,22 @@ package gitee import ( "context" + "fmt" "gitee.com/openeuler/go-gitee/gitee" + + "github.com/koderover/zadig/pkg/tool/httpclient" ) -func (c *Client) ListTags(ctx context.Context, owner string, repo string, opts *gitee.GetV5ReposOwnerRepoTagsOpts) (gitee.Tag, error) { - tags, _, err := c.RepositoriesApi.GetV5ReposOwnerRepoTags(ctx, owner, repo, opts) +func (c *Client) ListTags(ctx context.Context, accessToken, owner string, repo string) ([]gitee.Tag, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := fmt.Sprintf("/v5/repos/%s/%s/tags", owner, repo) + var tags []gitee.Tag + _, err := httpClient.Get(url, httpclient.SetQueryParam("access_token", accessToken), httpclient.SetResult(&tags)) if err != nil { - return gitee.Tag{}, err + return nil, err } - return tags, err + return tags, nil } -- Gitee From 2707c0fb00b2ea222851142f2591109c37fe746f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 12:13:53 +0800 Subject: [PATCH 1936/2647] modify tag and project model Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 5 +++-- pkg/tool/gitee/repositories.go | 10 ++++++++-- pkg/tool/gitee/tag.go | 11 +++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 0436c67d..fea7a956 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -53,7 +53,8 @@ func (c *Client) ListTags(opt client.ListOpt) ([]*client.Tag, error) { var resp []*client.Tag for _, tag := range tags { resp = append(resp, &client.Tag{ - Name: tag.Name, + Name: tag.Name, + Message: tag.Message, }) } @@ -119,7 +120,7 @@ func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { var res []*client.Project for _, project := range projects { res = append(res, &client.Project{ - ID: int(project.Id), + ID: project.ID, Name: project.Name, DefaultBranch: project.DefaultBranch, }) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 93fd5be5..01182eae 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -14,7 +14,13 @@ const ( GiteeHOSTURL = "https://gitee.com/api" ) -func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, accessToken string) ([]gitee.Project, error) { +type Project struct { + ID int `json:"id"` + Name string `json:"name"` + DefaultBranch string `json:"default_branch,omitempty"` +} + +func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, accessToken string) ([]Project, error) { httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) @@ -25,7 +31,7 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, acces queryParams["affiliation"] = "admin" queryParams["per_page"] = "100" - var projects []gitee.Project + var projects []Project _, err := httpClient.Get(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&projects)) if err != nil { return nil, err diff --git a/pkg/tool/gitee/tag.go b/pkg/tool/gitee/tag.go index 73693073..e12e0a77 100644 --- a/pkg/tool/gitee/tag.go +++ b/pkg/tool/gitee/tag.go @@ -4,17 +4,20 @@ import ( "context" "fmt" - "gitee.com/openeuler/go-gitee/gitee" - "github.com/koderover/zadig/pkg/tool/httpclient" ) -func (c *Client) ListTags(ctx context.Context, accessToken, owner string, repo string) ([]gitee.Tag, error) { +type Tag struct { + Name string `json:"name"` + Message string `json:"message"` +} + +func (c *Client) ListTags(ctx context.Context, accessToken, owner string, repo string) ([]Tag, error) { httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) url := fmt.Sprintf("/v5/repos/%s/%s/tags", owner, repo) - var tags []gitee.Tag + var tags []Tag _, err := httpClient.Get(url, httpclient.SetQueryParam("access_token", accessToken), httpclient.SetResult(&tags)) if err != nil { return nil, err -- Gitee From ba66866f839a0cd59a7cb0a999295e222bc1c31b Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 28 Mar 2022 14:47:28 +0800 Subject: [PATCH 1937/2647] add support of ali acr Signed-off-by: allenshen --- Makefile | 2 + docker/base/amd64/alpine-curl.Dockerfile | 8 + docker/base/arm64/alpine-curl.Dockerfile | 11 + docker/service/aslan.Dockerfile | 8 +- .../aslan/core/common/service/helm.go | 10 + .../aslan/core/delivery/service/version.go | 46 ++-- .../aslan/core/service/service/helm.go | 32 +-- .../aslan/core/system/service/helm.go | 6 +- pkg/tool/helmclient/chartclient.go | 204 ---------------- pkg/tool/helmclient/helmclient.go | 218 ++++++++++++++++-- 10 files changed, 274 insertions(+), 271 deletions(-) create mode 100644 docker/base/amd64/alpine-curl.Dockerfile create mode 100644 docker/base/arm64/alpine-curl.Dockerfile delete mode 100644 pkg/tool/helmclient/chartclient.go diff --git a/Makefile b/Makefile index 98a7c92a..952a04f0 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @mkdir -p docker/dist/amd64 @cp docker/service/$*.Dockerfile docker/dist/amd64/$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/amd64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile + @sed -i -e '/#alpine-curl.Dockerfile/ {' -e 'r docker/base/amd64/alpine-curl.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go @@ -71,6 +72,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @mkdir -p docker/dist/arm64 @cp docker/service/$*.Dockerfile docker/dist/arm64/$*.Dockerfile @sed -i -e '/#alpine-git.Dockerfile/ {' -e 'r docker/base/arm64/alpine-git.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile + @sed -i -e '/#alpine-curl.Dockerfile/ {' -e 'r docker/base/arm64/alpine-curl.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/arm64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/$*.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -o docker/dist/$* cmd/$*/main.go diff --git a/docker/base/amd64/alpine-curl.Dockerfile b/docker/base/amd64/alpine-curl.Dockerfile new file mode 100644 index 00000000..f08056e8 --- /dev/null +++ b/docker/base/amd64/alpine-curl.Dockerfile @@ -0,0 +1,8 @@ +FROM alpine/curl:3.14 + +# https://wiki.alpinelinux.org/wiki/Setting_the_timezone +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add tzdata && \ + cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo Asia/Shanghai > /etc/timezone && \ + apk del tzdata diff --git a/docker/base/arm64/alpine-curl.Dockerfile b/docker/base/arm64/alpine-curl.Dockerfile new file mode 100644 index 00000000..9465bd02 --- /dev/null +++ b/docker/base/arm64/alpine-curl.Dockerfile @@ -0,0 +1,11 @@ +FROM arm64v8/alpine + +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add curl + +# https://wiki.alpinelinux.org/wiki/Setting_the_timezone +RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ + apk add tzdata && \ + cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo Asia/Shanghai > /etc/timezone && \ + apk del tzdata diff --git a/docker/service/aslan.Dockerfile b/docker/service/aslan.Dockerfile index e55afc58..7921a7ff 100644 --- a/docker/service/aslan.Dockerfile +++ b/docker/service/aslan.Dockerfile @@ -1,4 +1,10 @@ -#alpine-git.Dockerfile +#alpine-curl.Dockerfile + +# install docker client +RUN curl -fsSL "https://resources.koderover.com/helm-acr_0.8.2_linux_amd64.tar.gz" -o helm-acr.tar.gz &&\ + mkdir -p /app/.helm/helmplugin/helm-acr &&\ + tar -xvzf helm-acr.tar.gz -C /app/.helm/helmplugin/helm-acr &&\ + rm -rf helm-acr* WORKDIR /app diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index eb59628d..3b39a3cc 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -23,6 +23,7 @@ import ( "github.com/27149chen/afero" "go.uber.org/zap" + "helm.sh/helm/v3/pkg/repo" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -146,3 +147,12 @@ func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { } return nil } + +func GeneHelmRepo(chartRepo *commonmodels.HelmRepo) *repo.Entry { + return &repo.Entry{ + Name: chartRepo.RepoName, + URL: chartRepo.URL, + Username: chartRepo.Username, + Password: chartRepo.Password, + } +} diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7fec33d6..274792be 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -37,6 +37,7 @@ import ( "go.uber.org/zap" chartloader "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/repo" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" @@ -520,14 +521,6 @@ func getProductEnvInfo(productName, envName string) (*commonmodels.Product, erro return productInfo, nil } -func createChartRepoClient(repoName string) (*helmtool.ChartRepoClient, error) { - chartRepo, err := getChartRepoData(repoName) - if err != nil { - return nil, err - } - return helmtool.NewHelmChartRepoClient(chartRepo.URL, chartRepo.Username, chartRepo.Password) -} - func getChartRepoData(repoName string) (*commonmodels.HelmRepo, error) { return commonrepo.NewHelmRepoColl().Find(&commonrepo.HelmRepoFindOption{RepoName: repoName}) } @@ -732,15 +725,15 @@ func handleSingleChart(chartData *DeliveryChartData, product *commonmodels.Produ return nil, err } - client, err := createChartRepoClient(chartRepo.RepoName) + client, err := helmtool.NewClient() if err != nil { return nil, errors.Wrapf(err, "failed to create chart repo client, repoName: %s", chartRepo.RepoName) } log.Infof("pushing chart %s to %s...", filepath.Base(chartPackagePath), chartRepo.URL) - err = client.PushChart(chartPackagePath, false) + err = client.PushChart(commonservice.GeneHelmRepo(chartRepo), chartPackagePath) if err != nil { - return nil, errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) + return nil, errors.Wrapf(err, "failed to push chart: %s", chartPackagePath) } return imageDetail, nil } @@ -1380,20 +1373,33 @@ func ListDeliveryServiceNames(productName string, log *zap.SugaredLogger) ([]str func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *commonmodels.DeliveryDistribute) (string, error) { productName, versionName := deliveryVersion.ProductName, deliveryVersion.Version chartTGZName := fmt.Sprintf("%s-%s.tgz", chartInfo.ChartName, chartInfo.ChartVersion) - chartTGZFileParent := getChartTGZDir(productName, versionName) + chartTGZFileParent, err := makeChartTGZFileDir(productName, versionName) + if err != nil { + return "", err + } + chartTGZFilePath := filepath.Join(chartTGZFileParent, chartTGZName) if _, err := os.Stat(chartTGZFilePath); err == nil { // local cache exists - log.Infof("local cache exists, path %s", chartTGZFilePath) return chartTGZFilePath, nil } - client, err := createChartRepoClient(chartInfo.ChartRepoName) + chartRepo, err := getChartRepoData(chartInfo.ChartRepoName) + if err != nil { + return "", err + } + hClient, err := helmtool.NewClient() if err != nil { return "", err } - return chartTGZFilePath, client.DownloadChart(chartTGZFileParent, chartInfo.ChartName, chartInfo.ChartVersion) + _, err = hClient.UpdateChartRepo(commonservice.GeneHelmRepo(chartRepo)) + if err != nil { + return "", errors.Wrapf(err, "failed to update helm repo") + } + + chartRef := fmt.Sprintf("%s/%s", chartRepo.RepoName, chartInfo.ChartName) + return chartTGZFilePath, hClient.DownloadChart(commonservice.GeneHelmRepo(chartRepo), chartRef, chartInfo.ChartVersion, chartTGZFileParent, false) } func getChartDistributeInfo(releaseID, chartName string, log *zap.SugaredLogger) (*commonmodels.DeliveryDistribute, error) { @@ -1448,12 +1454,16 @@ func preDownloadChart(projectName, versionName, chartName string, log *zap.Sugar return filePath, err } -func getIndexInfoFromChartRepo(chartRepoName string) (*helm.Index, error) { - client, err := createChartRepoClient(chartRepoName) +func getIndexInfoFromChartRepo(chartRepoName string) (*repo.IndexFile, error) { + chartRepo, err := getChartRepoData(chartRepoName) + if err != nil { + return nil, err + } + hClient, err := helmtool.NewClient() if err != nil { return nil, errors.Wrapf(err, "failed to create chart repo client") } - return client.FetchIndexYaml() + return hClient.FetchIndexYaml(commonservice.GeneHelmRepo(chartRepo)) } func fillChartUrl(charts []*DeliveryVersionPayloadChart, chartRepoName string) error { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d8d437a2..8111b38b 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -359,38 +359,18 @@ func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServic return nil, e.ErrCreateTemplate.AddDesc(fmt.Sprintf("failed to query chart-repo info, productName: %s, repoName: %s", projectName, chartRepoArgs.ChartRepoName)) } - chartClient, err := helmclient.NewHelmChartRepoClient(chartRepo.URL, chartRepo.Username, chartRepo.Password) + hClient, err := helmclient.NewClient() if err != nil { return nil, e.ErrCreateTemplate.AddErr(errors.Wrapf(err, "failed to init chart client for repo: %s", chartRepo.RepoName)) } - index, err := chartClient.FetchIndexYaml() - if err != nil { - return nil, e.ErrCreateTemplate.AddErr(err) - } - - // validate chart with specific version exists in repo - foundChart := false - for name, entries := range index.Entries { - if name != chartRepoArgs.ChartName { - continue - } - for _, entry := range entries { - if entry.Version == chartRepoArgs.ChartVersion { - foundChart = true - break - } - } - break - } - if !foundChart { - return nil, e.ErrCreateTemplate.AddDesc(fmt.Sprintf("failed to find chart: %s-%s from chart repo", chartRepoArgs.ChartName, chartRepoArgs.ChartVersion)) - } - + chartRef := fmt.Sprintf("%s/%s", chartRepo.RepoName, chartRepoArgs.ChartName) localPath := config.LocalServicePath(projectName, chartRepoArgs.ChartName) - err = chartClient.DownloadAndExpand(chartRepoArgs.ChartName, chartRepoArgs.ChartVersion, localPath) + // remove local file to untar + _ = os.RemoveAll(localPath) + err = hClient.DownloadChart(commonservice.GeneHelmRepo(chartRepo), chartRef, chartRepoArgs.ChartVersion, localPath, true) if err != nil { - return nil, e.ErrCreateTemplate.AddErr(err) + return nil, e.ErrCreateTemplate.AddErr(errors.Wrapf(err, "failed to download chart %s/%s-%s", chartRepo.RepoName, chartRepoArgs.ChartName, chartRepoArgs.ChartVersion)) } serviceName := chartRepoArgs.ChartName diff --git a/pkg/microservice/aslan/core/system/service/helm.go b/pkg/microservice/aslan/core/system/service/helm.go index 7ef62753..cdcf12bc 100644 --- a/pkg/microservice/aslan/core/system/service/helm.go +++ b/pkg/microservice/aslan/core/system/service/helm.go @@ -21,6 +21,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/tool/helmclient" ) @@ -73,12 +74,12 @@ func ListCharts(name string, log *zap.SugaredLogger) (*IndexFileResp, error) { return nil, err } - client, err := helmclient.NewHelmChartRepoClient(chartRepo.URL, chartRepo.Username, chartRepo.Password) + client, err := helmclient.NewClient() if err != nil { return nil, err } - indexInfo, err := client.FetchIndexYaml() + indexInfo, err := client.FetchIndexYaml(service.GeneHelmRepo(chartRepo)) if err != nil { return nil, err } @@ -95,6 +96,5 @@ func ListCharts(name string, log *zap.SugaredLogger) (*IndexFileResp, error) { }) } } - return indexResp, nil } diff --git a/pkg/tool/helmclient/chartclient.go b/pkg/tool/helmclient/chartclient.go deleted file mode 100644 index 8389d041..00000000 --- a/pkg/tool/helmclient/chartclient.go +++ /dev/null @@ -1,204 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helmclient - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "path/filepath" - "strings" - - cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" - "github.com/chartmuseum/helm-push/pkg/helm" - "github.com/pkg/errors" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/repo" - - "github.com/koderover/zadig/pkg/tool/log" -) - -type ChartRepoClient struct { - RepoURL string - *cm.Client -} - -func NewHelmChartRepoClient(url, userName, password string) (*ChartRepoClient, error) { - client, err := cm.NewClient( - cm.URL(url), - cm.Username(userName), - cm.Password(password), - // TODO need support more auth types - ) - if err != nil { - return nil, err - } - return &ChartRepoClient{ - RepoURL: url, - Client: client, - }, nil -} - -// FetchIndexYaml fetch index.yaml from remote chart repo -func (client *ChartRepoClient) FetchIndexYaml() (*helm.Index, error) { - resp, err := client.DownloadFile("index.yaml") - if err != nil { - return nil, errors.Wrapf(err, "failed to download index.yaml") - } - - defer resp.Body.Close() - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - if resp.StatusCode != http.StatusOK { - return nil, errors.Wrapf(getChartmuseumError(b, resp.StatusCode), "failed to download index.yaml") - } - - index, err := helm.LoadIndex(b) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse index.yaml") - } - return index, nil -} - -func (client *ChartRepoClient) DownloadChart(chartName, chartVersion, basePath string) error { - chartTGZName := fmt.Sprintf("%s-%s.tgz", chartName, chartVersion) - chartTGZFilePath := filepath.Join(basePath, chartTGZName) - - entry, err := client.GetChartFromIndex(chartName, chartVersion) - if err != nil { - return errors.Wrapf(err, "failed to get chart [%s]-[%s] info", chartName, chartVersion) - } - if len(entry.URLs) == 0 { - return errors.Wrapf(err, "failed to get chart [%s]-[%s] url", chartName, chartVersion) - } - - response, err := client.downloadFileWithURL(entry.URLs[0]) - if err != nil { - return errors.Wrapf(err, "failed to download file") - } - - defer response.Body.Close() - if response.StatusCode != http.StatusOK { - return fmt.Errorf("download file failed with status code:%d", response.StatusCode) - } - - b, err := ioutil.ReadAll(response.Body) - if err != nil { - return errors.Wrapf(err, "failed to read response data") - } - return ioutil.WriteFile(chartTGZFilePath, b, 0644) -} - -// DownloadAndExpand downloads chart from repo and expand chart files from tarball -func (client *ChartRepoClient) DownloadAndExpand(chartName, chartVersion, localPath string) error { - entry, err := client.GetChartFromIndex(chartName, chartVersion) - if err != nil { - return errors.Wrapf(err, "failed to get chart [%s]-[%s] info", chartName, chartVersion) - } - if len(entry.URLs) == 0 { - return errors.Wrapf(err, "failed to get chart [%s]-[%s] url", chartName, chartVersion) - } - response, err := client.downloadFileWithURL(entry.URLs[0]) - if err != nil { - return errors.Wrapf(err, "failed to download file") - } - - if response.StatusCode != http.StatusOK { - return fmt.Errorf("download file failed with status code:%d", response.StatusCode) - } - defer func() { _ = response.Body.Close() }() - - return chartutil.Expand(localPath, response.Body) -} - -// PushChart push chart to remote repo -func (client *ChartRepoClient) PushChart(chartPackagePath string, force bool) error { - log.Infof("pushing chart %s", filepath.Base(chartPackagePath)) - resp, err := client.UploadChartPackage(chartPackagePath, false) - if err != nil { - return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPackagePath) - } - - defer resp.Body.Close() - err = handlePushResponse(resp) - if err != nil { - return errors.Wrapf(err, "failed to push chart: %s ", chartPackagePath) - } - return nil -} - -func getChartmuseumError(b []byte, code int) error { - var er struct { - Error string `json:"error"` - } - err := json.Unmarshal(b, &er) - if err != nil || er.Error == "" { - return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) - } - return errors.Errorf("%d: %s", code, er.Error) -} - -func handlePushResponse(resp *http.Response) error { - if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted { - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - return getChartmuseumError(b, resp.StatusCode) - } - log.Info("push chart to chart repo done") - return nil -} - -func (client *ChartRepoClient) GetChartFromIndex(chartName, chartVersion string) (*repo.ChartVersion, error) { - index, err := client.FetchIndexYaml() - if err != nil { - return nil, errors.Wrapf(err, "fetch index failed") - } - for name, entries := range index.Entries { - if strings.Compare(name, chartName) == 0 { - for _, entry := range entries { - if strings.Compare(entry.Version, chartVersion) == 0 { - return entry, nil - } - } - } - } - return nil, fmt.Errorf("failed to find chart [%s]-[%s]", chartName, chartVersion) -} - -// TODO this function should be deprecated -// we should use standard `helm pull` to provide better compatibility -func (client *ChartRepoClient) downloadFileWithURL(chartUrl string) (*http.Response, error) { - u, _ := url.Parse(chartUrl) - // chart url is absolute path in index.yaml, set url of option to full chart url and download directly - // eg: chart repo hosted by gitee - if u.Scheme == "http" || u.Scheme == "https" { - client.Option(cm.URL(chartUrl)) - defer client.Option(cm.URL(client.RepoURL)) - return client.DownloadFile("") - } - // chart url in index.yaml is relative path, set url of option to the repoUrl and download by fileName - // eg: chart repo hosted by harbor - return client.DownloadFile(chartUrl) -} diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 6362060c..3f3b5049 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -17,20 +17,31 @@ limitations under the License. package helmclient import ( + "bytes" "context" "encoding/json" - "errors" "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" "reflect" "strings" + cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" hc "github.com/mittwald/go-helm-client" + "github.com/pkg/errors" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/plugin" "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" + "helm.sh/helm/v3/pkg/repo" "helm.sh/helm/v3/pkg/storage" "helm.sh/helm/v3/pkg/storage/driver" "helm.sh/helm/v3/pkg/strvals" @@ -49,12 +60,48 @@ import ( yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) +const ( + HelmPluginsDirectory = "/app/.helm/helmplugin" +) + +var repoInfo *repo.File +var generalSettings *cli.EnvSettings + +// enable support of oci registry +func init() { + os.Setenv("HELM_EXPERIMENTAL_OCI", "1") + os.Setenv("HELM_PLUGINS", HelmPluginsDirectory) + repoInfo = &repo.File{} + generalSettings = cli.New() + generalSettings.PluginsDirectory = HelmPluginsDirectory +} + type HelmClient struct { *hc.HelmClient kubeClient client.Client Namespace string } +// NewClient returns a new Helm client with no construct parameters +// used to update helm repo data and download index.yaml or helm charts +func NewClient() (*HelmClient, error) { + hcClient, err := hc.New(&hc.Options{ + RepositoryConfig: generalSettings.RepositoryConfig, + RepositoryCache: generalSettings.RepositoryCache, + }) + + if err != nil { + return nil, err + } + helmClient := hcClient.(*hc.HelmClient) + helmClient.Settings = generalSettings + return &HelmClient{ + helmClient, + nil, + "", + }, nil +} + // NewClientFromNamespace returns a new Helm client constructed with the provided clusterID and namespace // a kubeClient will be initialized to support necessary k8s operations when install/upgrade helm charts func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { @@ -382,14 +429,14 @@ func (hClient *HelmClient) getChart(chartName string, chartPathOptions *action.C func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { c := hClient.HelmClient - client := action.NewInstall(c.ActionConfig) - mergeInstallOptions(spec, client) + install := action.NewInstall(c.ActionConfig) + mergeInstallOptions(spec, install) - if client.Version == "" { - client.Version = ">0.0.0-0" + if install.Version == "" { + install.Version = ">0.0.0-0" } - helmChart, chartPath, err := hClient.getChart(spec.ChartName, &client.ChartPathOptions) + helmChart, chartPath, err := hClient.getChart(spec.ChartName, &install.ChartPathOptions) if err != nil { return nil, err } @@ -404,16 +451,16 @@ func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) if req := helmChart.Metadata.Dependencies; req != nil { if err := action.CheckDependencies(helmChart, req); err != nil { - if !client.DependencyUpdate { + if !install.DependencyUpdate { return nil, err } man := &downloader.Manager{ ChartPath: chartPath, - Keyring: client.ChartPathOptions.Keyring, + Keyring: install.ChartPathOptions.Keyring, SkipUpdate: false, Getters: c.Providers, - RepositoryConfig: c.Settings.RepositoryConfig, - RepositoryCache: c.Settings.RepositoryCache, + RepositoryConfig: generalSettings.RepositoryConfig, + RepositoryCache: generalSettings.RepositoryCache, } if err := man.Update(); err != nil { return nil, err @@ -426,7 +473,7 @@ func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) return nil, err } - rel, err := client.RunWithContext(ctx, helmChart, values) + rel, err := install.RunWithContext(ctx, helmChart, values) if err != nil { return rel, err } @@ -438,14 +485,14 @@ func (hClient *HelmClient) installChart(ctx context.Context, spec *hc.ChartSpec) func (hClient *HelmClient) upgradeChart(ctx context.Context, spec *hc.ChartSpec) (*release.Release, error) { c := hClient.HelmClient - client := action.NewUpgrade(c.ActionConfig) - mergeUpgradeOptions(spec, client) + upgrade := action.NewUpgrade(c.ActionConfig) + mergeUpgradeOptions(spec, upgrade) - if client.Version == "" { - client.Version = ">0.0.0-0" + if upgrade.Version == "" { + upgrade.Version = ">0.0.0-0" } - helmChart, _, err := hClient.getChart(spec.ChartName, &client.ChartPathOptions) + helmChart, _, err := hClient.getChart(spec.ChartName, &upgrade.ChartPathOptions) if err != nil { return nil, err } @@ -469,13 +516,11 @@ func (hClient *HelmClient) upgradeChart(ctx context.Context, spec *hc.ChartSpec) } } - rel, err := client.RunWithContext(ctx, spec.ReleaseName, helmChart, values) + rel, err := upgrade.RunWithContext(ctx, spec.ReleaseName, helmChart, values) if err != nil { return rel, err } - c.DebugLog("release upgraded successfully: %s/%s-%s", rel.Name, rel.Chart.Metadata.Name, rel.Chart.Metadata.Version) - return rel, nil } @@ -493,6 +538,141 @@ func (hClient *HelmClient) InstallOrUpgradeChart(ctx context.Context, spec *hc.C } } +func (hClient *HelmClient) UpdateChartRepo(repoEntry *repo.Entry) (string, error) { + chartRepo, err := repo.NewChartRepository(repoEntry, hClient.Providers) + if err != nil { + return "", err + } + chartRepo.CachePath = hClient.Settings.RepositoryCache + + // export envionment-variables for ali acr chart repo + os.Setenv("HELM_REPO_USERNAME", repoEntry.Username) + os.Setenv("HELM_REPO_PASSWORD", repoEntry.Password) + + // update repo info + repoInfo.Update(repoEntry) + + // download index.yaml + indexFilePath, err := chartRepo.DownloadIndexFile() + if err != nil { + return "", err + } + + err = repoInfo.WriteFile(hClient.Settings.RepositoryConfig, 0o644) + if err != nil { + return "", err + } + return indexFilePath, err +} + +// FetchIndexYaml fetch index.yaml from remote chart repo +// `helm repo add` and `helm repo update` will be executed +func (hClient *HelmClient) FetchIndexYaml(repoEntry *repo.Entry) (*repo.IndexFile, error) { + indexFilePath, err := hClient.UpdateChartRepo(repoEntry) + if err != nil { + return nil, err + } + // Read the index file for the repository to get chart information and return chart URL + repoIndex, err := repo.LoadIndexFile(indexFilePath) + return repoIndex, err +} + +func (hClient *HelmClient) DownloadChart(repoEntry *repo.Entry, chartRef string, chartVersion string, destDir string, unTar bool) error { + _, err := hClient.UpdateChartRepo(repoEntry) + if err != nil { + return nil + } + pull := action.NewPull() + pull.Password = repoEntry.Username + pull.Username = repoEntry.Password + pull.Version = chartVersion + pull.Settings = generalSettings + pull.DestDir = destDir + pull.UntarDir = destDir + pull.Untar = unTar + _, err = pull.Run(chartRef) + return err +} + +func (hClient *HelmClient) pushAcrChart(repoEntry *repo.Entry, chartPath string) error { + base := filepath.Join(hClient.Settings.PluginsDirectory, "helm-acr") + prog := exec.Command(filepath.Join(base, "bin/helm-cm-push"), chartPath, repoEntry.Name) + plugin.SetupPluginEnv(hClient.Settings, "cm-push", base) + prog.Env = os.Environ() + buf := bytes.NewBuffer(nil) + prog.Stdout = buf + prog.Stderr = os.Stderr + if err := prog.Run(); err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + os.Stderr.Write(eerr.Stderr) + return fmt.Errorf("plugin exited with error") + } + return err + } + return nil +} + +func (hClient *HelmClient) pushChartMuseum(repoEntry *repo.Entry, chartPath string) error { + chartClient, err := cm.NewClient( + cm.URL(repoEntry.URL), + cm.Username(repoEntry.Username), + cm.Password(repoEntry.Password), + ) + if err != nil { + return err + } + resp, err := chartClient.UploadChartPackage(chartPath, false) + if err != nil { + return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPath) + } + + defer resp.Body.Close() + err = handlePushResponse(resp) + if err != nil { + return errors.Wrapf(err, "failed to push chart: %s ", chartPath) + } + return nil +} + +func getChartmuseumError(b []byte, code int) error { + var er struct { + Error string `json:"error"` + } + err := json.Unmarshal(b, &er) + if err != nil || er.Error == "" { + return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) + } + return errors.Errorf("%d: %s", code, er.Error) +} + +func handlePushResponse(resp *http.Response) error { + if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted { + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + return getChartmuseumError(b, resp.StatusCode) + } + log.Info("push chart to chart repo done") + return nil +} + +func (hClient *HelmClient) PushChart(repoEntry *repo.Entry, chartPath string) error { + _, err := hClient.UpdateChartRepo(repoEntry) + if err != nil { + return nil + } + repoUrl, err := url.Parse(repoEntry.URL) + if err != nil { + return errors.Wrapf(err, "failed to parse repo url: %s", repoEntry.URL) + } + if repoUrl.Scheme == "acr" { + return hClient.pushAcrChart(repoEntry, chartPath) + } else { + return hClient.pushChartMuseum(repoEntry, chartPath) + } +} + // mergeInstallOptions merges values of the provided chart to helm install options used by the client. func mergeInstallOptions(chartSpec *hc.ChartSpec, installOptions *action.Install) { installOptions.CreateNamespace = chartSpec.CreateNamespace -- Gitee From 4f9be7c2b590bb02382bde52ac2bf9a59b1b7c30 Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 28 Mar 2022 15:15:46 +0800 Subject: [PATCH 1938/2647] rego improve Signed-off-by: mouuii --- .../core/service/bundle/rego/authz.rego | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 43fc1f50..f9268e17 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -114,7 +114,7 @@ access_is_granted { rule.method == http_request.method glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) - all_attributes_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) + any_attribute_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) } access_is_granted { @@ -124,7 +124,7 @@ access_is_granted { rule.method == http_request.method glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) - all_attributes_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) + any_attribute_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) } rule_is_matched_for_filtering { @@ -215,7 +215,7 @@ user_matched_role_rule_for_filtering[rule] { } -all_attributes_match(attributes, resourceType, resourceID) { +any_attribute_match(attributes, resourceType, resourceID) { res := data.resources[resourceType][_] res.resourceID == resourceID project_name_is_match(res) @@ -228,19 +228,6 @@ attributes_match(attributes, res) { attribute_match(attribute, res) } -attributes_mismatch(attributes, res) { - attribute := attributes[_] - attribute_mismatch(attribute, res) -} - -attribute_mismatch(attribute, res) { - res.spec[attribute.key] != attribute.value -} - -attribute_mismatch(attribute, res) { - not res.spec[attribute.key] -} - get_resource_id(idRegex) = id { output := regex.find_all_string_submatch_n(trim(idRegex, "/"), concat("/", input.parsed_path), -1) count(output) == 1 -- Gitee From b09858bc7b48c49d96fed161aa31c27b226f9d5a Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 18 Mar 2022 13:09:49 +0800 Subject: [PATCH 1939/2647] change distribute attributes to enable deployment in distribute stage Signed-off-by: Min Min --- .../core/common/repository/models/workflow.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index b7693af7..09a4c7af 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -274,12 +274,14 @@ type ExtensionStage struct { } type RepoImage struct { - RepoID string `json:"repo_id" bson:"repo_id"` - Name string `json:"name" bson:"name" yaml:"name"` - Username string `json:"-" yaml:"username"` - Password string `json:"-" yaml:"password"` - Host string `json:"host" yaml:"host"` - Namespace string `json:"namespace" yaml:"namespace"` + RepoID string `json:"repo_id" bson:"repo_id"` + Name string `json:"name" bson:"name" yaml:"name"` + Username string `json:"-" yaml:"username"` + Password string `json:"-" yaml:"password"` + Host string `json:"host" yaml:"host"` + Namespace string `json:"namespace" yaml:"namespace"` + DeployEnabled bool `json:"deploy_enabled" yaml:"deploy_enabled"` + DeployEnv string `json:"deploy_env" yaml:"deploy_env"` } type ProductDistribute struct { -- Gitee From 2a7bf0e025c2bc8c29c758565379f4ec63ec8902 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Mar 2022 07:53:04 +0800 Subject: [PATCH 1940/2647] distribute part in release_image rewritten Signed-off-by: Min Min --- .../repository/models/task/release_image.go | 20 ++++++++ .../service/workflow/workflow_task.go | 50 +++++++++++++++++-- .../predator/core/service/predator.go | 28 +++++++++-- .../predator/core/service/service.go | 13 +++-- .../core/service/taskplugin/release_image.go | 34 ++++++++++++- .../warpdrive/core/service/types/predator.go | 11 ++-- .../core/service/types/task/release_image.go | 24 +++++++++ 7 files changed, 159 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go index e113a715..0ec2ec8b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go @@ -44,12 +44,32 @@ type ReleaseImage struct { // destinations to distribute images Releases []models.RepoImage `bson:"releases" json:"releases"` + + // New Version field + ProductName string `bson:"product_name" json:"product_name"` + SourceImage string `bson:"source_image" json:"source_image"` + DistributeInfo []*DistributeInfo `bson:"distribute_info" json:"distribute_info"` +} + +type DistributeInfo struct { + Image string `bson:"image" json:"image"` + DistributeStartTime int64 `bson:"distribute_start_time" json:"distribute_start_time"` + DistributeEndTime int64 `bson:"distribute_end_time" json:"distribute_end_time"` + DistributeStatus string `bson:"distribute_status" json:"distribute_status"` + DeployEnabled bool `bson:"deploy_enabled" json:"deploy_enabled"` + DeployEnv string `bson:"deploy_env" json:"deploy_env"` + DeployNamespace string `bson:"deploy_namespace" json:"deploy_namespace"` + DeployStartTime int64 `bson:"deploy_start_time" json:"deploy_start_time"` + DeployEndTime int64 `bson:"deploy_end_time" json:"deploy_end_time"` + DeployStatus string `bson:"deploy_status" json:"deploy_status"` + RepoID string `bson:"repo_id" json:"repo_id"` } // SetImage ... func (ri *ReleaseImage) SetImage(image string) { if image != "" { ri.ImageTest = image + ri.SourceImage = image } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 3762bd8c..8a9c7e6a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -629,6 +629,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } if reflect.DeepEqual(distribute.Target, serviceModule) { distributeTasks, err = formatDistributeSubtasks( + args.ProductTmplName, workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, workflow.DistributeStage.JumpBoxHost, @@ -1056,6 +1057,7 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } if distribute.Target.ServiceModule == imageInfo.ServiceModule && distribute.Target.ServiceName == imageInfo.ServiceName { distributeTasks, err = formatDistributeSubtasks( + args.ProductTmplName, workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, workflow.DistributeStage.JumpBoxHost, @@ -1362,16 +1364,35 @@ func artifactToSubTasks(name, image string) (map[string]interface{}, error) { return artifactTask.ToSubTask() } -func formatDistributeSubtasks(releaseImages []commonmodels.RepoImage, imageRepo, jumpboxHost, destStorageURL string, distribute *commonmodels.ProductDistribute) ([]map[string]interface{}, error) { +func formatDistributeSubtasks(productName string, releaseImages []commonmodels.RepoImage, imageRepo, jumpboxHost, destStorageURL string, distribute *commonmodels.ProductDistribute) ([]map[string]interface{}, error) { var resp []map[string]interface{} if distribute.ImageDistribute { t := taskmodels.ReleaseImage{ - TaskType: config.TaskReleaseImage, - Enabled: true, - ImageRepo: imageRepo, - Releases: releaseImages, + TaskType: config.TaskReleaseImage, + Enabled: true, + ProductName: productName, + } + distributeInfo := make([]*taskmodels.DistributeInfo, 0) + // now we add distributeInfo in + for _, repoInfo := range releaseImages { + envInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + EnvName: repoInfo.DeployEnv, + Name: productName, + }) + if err != nil { + return nil, err + } + distributeInfo = append(distributeInfo, &taskmodels.DistributeInfo{ + DeployEnabled: repoInfo.DeployEnabled, + DeployEnv: repoInfo.DeployEnv, + DeployNamespace: envInfo.Namespace, + RepoID: repoInfo.RepoID, + }) } + t.DistributeInfo = distributeInfo + + // convert to subtask subtask, err := t.ToSubTask() if err != nil { return resp, err @@ -1722,6 +1743,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } if reflect.DeepEqual(distribute.Target, serviceModule) { distributeTasks, err = formatDistributeSubtasks( + args.ProductTmplName, workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, workflow.DistributeStage.JumpBoxHost, @@ -2561,13 +2583,31 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } } + var distributes []*taskmodels.DistributeInfo + for _, distribute := range t.DistributeInfo { + d := &taskmodels.DistributeInfo{ + DeployEnabled: distribute.DeployEnabled, + DeployEnv: distribute.DeployEnv, + DeployNamespace: distribute.DeployNamespace, + } + if v, ok := taskOpt.Task.ConfigPayload.RepoConfigs[distribute.RepoID]; ok { + d.Image = util.ReplaceRepo(taskOpt.Task.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) + } + distributes = append(distributes, d) + } + t.Releases = repos + t.DistributeInfo = distributes if len(t.Releases) == 0 { t.Enabled = false } else { t.ImageRelease = t.Releases[0].Name } + if len(t.DistributeInfo) == 0 { + t.Enabled = false + } + taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err diff --git a/pkg/microservice/predator/core/service/predator.go b/pkg/microservice/predator/core/service/predator.go index d1ad23fc..2659a4b4 100644 --- a/pkg/microservice/predator/core/service/predator.go +++ b/pkg/microservice/predator/core/service/predator.go @@ -18,11 +18,12 @@ package service // Context ... type Context struct { - JobType string `yaml:"job_type"` - DockerRegistry *DockerRegistry `yaml:"docker_registry"` - DockerBuildCtx *DockerBuildCtx `yaml:"build_ctx"` - OnSetup string `yaml:"setup,omitempty"` - ReleaseImages []RepoImage `yaml:"release_images"` + JobType string `yaml:"job_type"` + DockerRegistry *DockerRegistry `yaml:"docker_registry"` + DockerBuildCtx *DockerBuildCtx `yaml:"build_ctx"` + OnSetup string `yaml:"setup,omitempty"` + ReleaseImages []RepoImage `yaml:"release_images"` + DistributeList []*DistributeInfo `yaml:"distribute_info"` } type RepoImage struct { @@ -34,6 +35,23 @@ type RepoImage struct { Namespace string `yaml:"namespace"` } +// DistributeInfo will be convert into yaml, adding yaml +type DistributeInfo struct { + Image string `yaml:"image"` + DistributeStartTime int64 `yaml:"distribute_start_time"` + DistributeEndTime int64 `yaml:"distribute_end_time"` + DistributeStatus string `yaml:"distribute_status"` + DeployEnabled bool `yaml:"deploy_enabled"` + DeployEnv string `yaml:"deploy_env"` + DeployNamespace string `yaml:"deploy_namespace"` + DeployStartTime int64 `yaml:"deploy_start_time"` + DeployEndTime int64 `yaml:"deploy_end_time"` + DeployStatus string `yaml:"deploy_status"` + RepoID string `yaml:"repo_id"` + RepoAK string `yaml:"repo_ak"` + RepoSK string `yaml:"repo_sk"` +} + // DockerBuildCtx ... // Docker build参数 // WorkDir: docker build执行路径 diff --git a/pkg/microservice/predator/core/service/service.go b/pkg/microservice/predator/core/service/service.go index a728fbbd..35a3a4eb 100644 --- a/pkg/microservice/predator/core/service/service.go +++ b/pkg/microservice/predator/core/service/service.go @@ -102,13 +102,15 @@ func (p *Predator) Exec() error { // AfterExec ... func (p *Predator) AfterExec() error { - for _, image := range p.Ctx.ReleaseImages { - err := writeDockerConfig(image.Host, image.Username, image.Password) + for _, distribute := range p.Ctx.DistributeList { + // get the docker host info from the image + splitString := strings.Split(distribute.Image, "/") + err := writeDockerConfig(splitString[0], distribute.RepoAK, distribute.RepoSK) if err != nil { return fmt.Errorf("failed to write docker config file %v", err) } - cmd := dockerPush(image.Name) + cmd := dockerPush(distribute.Image) cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Dir = p.Ctx.DockerBuildCtx.WorkDir @@ -161,8 +163,9 @@ func (p *Predator) dockerCommands() []*exec.Cmd { }) } else if p.Ctx.JobType == setting.ReleaseImageJob { cmds = append(cmds, dockerPull(p.Ctx.DockerBuildCtx.ImageName)) - for _, image := range p.Ctx.ReleaseImages { - cmds = append(cmds, dockerTag(p.Ctx.DockerBuildCtx.ImageName, image.Name)) + // no more ReleaseImages in release_image job type + for _, distribute := range p.Ctx.DistributeList { + cmds = append(cmds, dockerTag(p.Ctx.DockerBuildCtx.ImageName, distribute.Image)) } } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index eab1c706..ebcdc4ac 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -123,9 +123,21 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p } } + distributes := make([]*task.DistributeInfo, 0) + for _, distribute := range p.Task.DistributeInfo { + if cfg, ok := pipelineTask.ConfigPayload.RepoConfigs[distribute.RepoID]; ok { + distribute.RepoAK = cfg.AccessKey + distribute.RepoSK = cfg.SecretKey + distributes = append(distributes, distribute) + } + } + if len(releases) == 0 { return } + if len(distributes) == 0 { + return + } jobCtx := &types.PredatorContext{ JobType: setting.ReleaseImageJob, @@ -141,7 +153,8 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p Password: pipelineTask.ConfigPayload.Registry.SecretKey, }, - ReleaseImages: releases, + ReleaseImages: releases, + DistributeInfo: distributes, } jobCtxBytes, err := yaml.Marshal(jobCtx) @@ -200,6 +213,10 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p } job.Namespace = p.KubeNamespace + startTime := time.Now().Unix() + for _, distribute := range p.Task.DistributeInfo { + distribute.DistributeStartTime = startTime + } if err := updater.CreateJob(job, p.kubeClient); err != nil { msg := fmt.Sprintf("create release image job error: %v", err) p.Log.Error(msg) @@ -214,6 +231,21 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p func (p *ReleaseImagePlugin) Wait(ctx context.Context) { status := waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.clientset, p.restConfig, p.Log) p.SetStatus(status) + distributeEndtime := time.Now().Unix() + for _, distribute := range p.Task.DistributeInfo { + distribute.DistributeEndTime = distributeEndtime + distribute.DistributeStatus = string(status) + } + // if the distribution stage failed, then deploy part won't run + if status != config.StatusPassed { + return + } + // otherwise, run any deploy subtasks + for _, distribute := range p.Task.DistributeInfo { + if !distribute.DeployEnabled { + break + } + } } // Complete ... diff --git a/pkg/microservice/warpdrive/core/service/types/predator.go b/pkg/microservice/warpdrive/core/service/types/predator.go index ccb37a18..c866e19c 100644 --- a/pkg/microservice/warpdrive/core/service/types/predator.go +++ b/pkg/microservice/warpdrive/core/service/types/predator.go @@ -19,9 +19,10 @@ package types import "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" type PredatorContext struct { - JobType string `yaml:"job_type"` - DockerRegistry *DockerRegistry `yaml:"docker_registry"` - DockerBuildCtx *task.DockerBuildCtx `yaml:"build_ctx"` - OnSetup string `yaml:"setup,omitempty"` - ReleaseImages []task.RepoImage `yaml:"release_images"` + JobType string `yaml:"job_type"` + DockerRegistry *DockerRegistry `yaml:"docker_registry"` + DockerBuildCtx *task.DockerBuildCtx `yaml:"build_ctx"` + OnSetup string `yaml:"setup,omitempty"` + ReleaseImages []task.RepoImage `yaml:"release_images"` + DistributeInfo []*task.DistributeInfo `yaml:"distribute_info"` } diff --git a/pkg/microservice/warpdrive/core/service/types/task/release_image.go b/pkg/microservice/warpdrive/core/service/types/task/release_image.go index cb2a7e4d..392a00c3 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/release_image.go +++ b/pkg/microservice/warpdrive/core/service/types/task/release_image.go @@ -43,6 +43,30 @@ type ReleaseImage struct { // destinations to distribute images Releases []RepoImage `bson:"releases" json:"releases"` + + // New Version field + ProductName string `json:"product_name"` + SourceImage string `json:"source_image"` + DistributeInfo []*DistributeInfo `json:"distribute_info"` +} + +// DistributeInfo will be convert into yaml, adding yaml +type DistributeInfo struct { + Image string `json:"image" yaml:"image"` + DistributeStartTime int64 `json:"distribute_start_time" yaml:"distribute_start_time"` + DistributeEndTime int64 `json:"distribute_end_time" yaml:"distribute_end_time"` + DistributeStatus string `json:"distribute_status" yaml:"distribute_status"` + DeployEnabled bool `json:"deploy_enabled" yaml:"deploy_enabled"` + DeployEnv string `json:"deploy_env" yaml:"deploy_env"` + DeployNamespace string `json:"deploy_namespace" yaml:"deploy_namespace"` + DeployStartTime int64 `json:"deploy_start_time" yaml:"deploy_start_time"` + DeployEndTime int64 `json:"deploy_end_time" yaml:"deploy_end_time"` + DeployStatus string `json:"deploy_status" yaml:"deploy_status"` + RepoID string `json:"repo_id" yaml:"repo_id"` + + //RepoAK and RepoSK is used for docker login, it is determined runtime, not passed by message so no json is required + RepoAK string `json:"-" yaml:"repo_ak"` + RepoSK string `json:"-" yaml:"repo_sk"` } type RepoImage struct { -- Gitee From e923198b25d952c352b2c55d238edf54dd58c6f5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Mar 2022 14:23:38 +0800 Subject: [PATCH 1941/2647] debug logs added Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 8a9c7e6a..e89eccee 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2610,6 +2610,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { + log.Errorf("release task to subtask error: %s", err) return err } } -- Gitee From 827f4dcbc68a47edf570e255576dc4326b3621a6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Mar 2022 14:34:34 +0800 Subject: [PATCH 1942/2647] debugged Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e89eccee..fee1d14e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1391,6 +1391,7 @@ func formatDistributeSubtasks(productName string, releaseImages []commonmodels.R }) } t.DistributeInfo = distributeInfo + t.Releases = releaseImages // convert to subtask subtask, err := t.ToSubTask() -- Gitee From 91f3c0f0e73fd14f942fc39e9d1b5347ffbf4028 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Mar 2022 16:55:26 +0800 Subject: [PATCH 1943/2647] added debug logs Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index ebcdc4ac..09db587d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -204,6 +204,7 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p return } + fmt.Printf(">>>>>>>>>>>>>>> ensure deleteing job <<<<<<<<<<<<<<<<<\n") if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { msg := fmt.Sprintf("delete release image job error: %v", err) p.Log.Error(msg) @@ -217,7 +218,10 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p for _, distribute := range p.Task.DistributeInfo { distribute.DistributeStartTime = startTime } + + fmt.Printf(">>>>>>>>>>>>>>>>>>>>>>> creating job <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n") if err := updater.CreateJob(job, p.kubeClient); err != nil { + fmt.Printf(">>>>>>>>>>>>>>>> failed to create job, the error is: %s <<<<<<<<<<<<<<<<", err) msg := fmt.Sprintf("create release image job error: %v", err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed -- Gitee From 54e931f63719dcda8231d1b5dc7450c78f23565f Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 24 Mar 2022 20:37:36 +0800 Subject: [PATCH 1944/2647] debug Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 09db587d..8c12a4d2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -111,6 +111,7 @@ func (p *ReleaseImagePlugin) TaskTimeout() int { // Run ... func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + fmt.Println("Starting to run release image plugin") p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace // 设置本次运行需要配置 //t.Workspace = fmt.Sprintf("%s/%s", pipelineTask.ConfigPayload.NFS.Path, pipelineTask.PipelineName) @@ -132,10 +133,8 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p } } - if len(releases) == 0 { - return - } if len(distributes) == 0 { + fmt.Println("distribute is 0") return } -- Gitee From a1949e60c1de6919521f87cf13d34255638c1c57 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 25 Mar 2022 08:19:39 +0800 Subject: [PATCH 1945/2647] k8s deployment type for distribution stage Signed-off-by: Min Min --- .../repository/models/task/release_image.go | 4 + .../service/workflow/workflow_task.go | 26 ++- .../core/service/taskplugin/release_image.go | 211 +++++++++++++++++- .../core/service/types/task/release_image.go | 4 + 4 files changed, 231 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go index 0ec2ec8b..bd1f06c9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go @@ -58,6 +58,10 @@ type DistributeInfo struct { DistributeStatus string `bson:"distribute_status" json:"distribute_status"` DeployEnabled bool `bson:"deploy_enabled" json:"deploy_enabled"` DeployEnv string `bson:"deploy_env" json:"deploy_env"` + DeployServiceType string `json:"deploy_service_type" yaml:"deploy_service_type"` + DeployContainerName string `json:"deploy_container_name" yaml:"deploy_container_name"` + DeployServiceName string `json:"deploy_service_name" yaml:"deploy_service_name"` + DeployClusterID string `json:"deploy_cluster_id" yaml:"deploy_cluster_id"` DeployNamespace string `bson:"deploy_namespace" json:"deploy_namespace"` DeployStartTime int64 `bson:"deploy_start_time" json:"deploy_start_time"` DeployEndTime int64 `bson:"deploy_end_time" json:"deploy_end_time"` diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index fee1d14e..e9baa67d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1373,6 +1373,11 @@ func formatDistributeSubtasks(productName string, releaseImages []commonmodels.R Enabled: true, ProductName: productName, } + // get product Info for further use + productInfo, err := template.NewProductColl().Find(productName) + if err != nil { + return nil, err + } distributeInfo := make([]*taskmodels.DistributeInfo, 0) // now we add distributeInfo in for _, repoInfo := range releaseImages { @@ -1384,10 +1389,12 @@ func formatDistributeSubtasks(productName string, releaseImages []commonmodels.R return nil, err } distributeInfo = append(distributeInfo, &taskmodels.DistributeInfo{ - DeployEnabled: repoInfo.DeployEnabled, - DeployEnv: repoInfo.DeployEnv, - DeployNamespace: envInfo.Namespace, - RepoID: repoInfo.RepoID, + DeployEnabled: repoInfo.DeployEnabled, + DeployEnv: repoInfo.DeployEnv, + DeployServiceType: productInfo.ProductFeature.DeployType, + DeployClusterID: envInfo.ClusterID, + DeployNamespace: envInfo.Namespace, + RepoID: repoInfo.RepoID, }) } t.DistributeInfo = distributeInfo @@ -2587,9 +2594,14 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err var distributes []*taskmodels.DistributeInfo for _, distribute := range t.DistributeInfo { d := &taskmodels.DistributeInfo{ - DeployEnabled: distribute.DeployEnabled, - DeployEnv: distribute.DeployEnv, - DeployNamespace: distribute.DeployNamespace, + DeployEnabled: distribute.DeployEnabled, + DeployEnv: distribute.DeployEnv, + DeployNamespace: distribute.DeployNamespace, + DeployContainerName: taskOpt.Task.ServiceName, // This is a match for target.Name + DeployServiceName: taskOpt.ServiceName, // this is a match for target.ServiceName + DeployServiceType: distribute.DeployServiceType, + DeployClusterID: distribute.DeployClusterID, + RepoID: distribute.RepoID, } if v, ok := taskOpt.Task.ConfigPayload.RepoConfigs[distribute.RepoID]; ok { d.Image = util.ReplaceRepo(taskOpt.Task.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 8c12a4d2..13d0fd60 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -19,6 +19,14 @@ package taskplugin import ( "context" "fmt" + "github.com/hashicorp/go-multierror" + configbase "github.com/koderover/zadig/pkg/config" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/pkg/errors" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/labels" "strconv" "time" @@ -43,12 +51,16 @@ func InitializeReleaseImagePlugin(taskType config.TaskType) TaskPlugin { kubeClient: krkubeclient.Client(), clientset: krkubeclient.Clientset(), restConfig: krkubeclient.RESTConfig(), + httpClient: httpclient.New( + httpclient.SetHostURL(configbase.AslanServiceAddress()), + ), } } // ReleaseImagePlugin Plugin name should be compatible with task type type ReleaseImagePlugin struct { Name config.TaskType + HubServerAddr string KubeNamespace string JobName string FileName string @@ -57,6 +69,8 @@ type ReleaseImagePlugin struct { restConfig *rest.Config Task *task.ReleaseImage Log *zap.SugaredLogger + httpClient *httpclient.Client + DeployError *multierror.Error } func (p *ReleaseImagePlugin) SetAckFunc(func()) { @@ -111,8 +125,8 @@ func (p *ReleaseImagePlugin) TaskTimeout() int { // Run ... func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { - fmt.Println("Starting to run release image plugin") p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace + p.HubServerAddr = pipelineTask.ConfigPayload.HubServerAddr // 设置本次运行需要配置 //t.Workspace = fmt.Sprintf("%s/%s", pipelineTask.ConfigPayload.NFS.Path, pipelineTask.PipelineName) releases := make([]task.RepoImage, 0) @@ -203,7 +217,6 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p return } - fmt.Printf(">>>>>>>>>>>>>>> ensure deleteing job <<<<<<<<<<<<<<<<<\n") if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { msg := fmt.Sprintf("delete release image job error: %v", err) p.Log.Error(msg) @@ -218,9 +231,7 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p distribute.DistributeStartTime = startTime } - fmt.Printf(">>>>>>>>>>>>>>>>>>>>>>> creating job <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n") if err := updater.CreateJob(job, p.kubeClient); err != nil { - fmt.Printf(">>>>>>>>>>>>>>>> failed to create job, the error is: %s <<<<<<<<<<<<<<<<", err) msg := fmt.Sprintf("create release image job error: %v", err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed @@ -232,8 +243,17 @@ func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, p // Wait ... func (p *ReleaseImagePlugin) Wait(ctx context.Context) { - status := waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.clientset, p.restConfig, p.Log) - p.SetStatus(status) + var err error + var status config.Status + defer func() { + if err != nil { + p.Log.Error(err) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = err.Error() + return + } + }() + status = waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.clientset, p.restConfig, p.Log) distributeEndtime := time.Now().Unix() for _, distribute := range p.Task.DistributeInfo { distribute.DistributeEndTime = distributeEndtime @@ -241,12 +261,175 @@ func (p *ReleaseImagePlugin) Wait(ctx context.Context) { } // if the distribution stage failed, then deploy part won't run if status != config.StatusPassed { + err = errors.New("failed to distribute images to the repository") return } // otherwise, run any deploy subtasks +DistributeLoop: for _, distribute := range p.Task.DistributeInfo { if !distribute.DeployEnabled { - break + continue + } + // set the start time on deployment start. + distribute.DeployStartTime = time.Now().Unix() + if distribute.DeployClusterID != "" { + p.restConfig, err = kubeclient.GetRESTConfig(p.HubServerAddr, distribute.DeployClusterID) + if err != nil { + err = errors.WithMessage(err, "can't get k8s rest config") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + p.kubeClient, err = kubeclient.GetKubeClient(p.HubServerAddr, distribute.DeployClusterID) + if err != nil { + err = errors.WithMessage(err, "can't init k8s client") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + } + // k8s deploy type service goes here + if distribute.DeployServiceType != setting.HelmDeployType { + replaced := false + // get servcie info + var ( + serviceInfo *types.ServiceTmpl + selector labels.Selector + ) + serviceInfo, err = p.getService(ctx, distribute.DeployServiceName, distribute.DeployServiceType, p.Task.ProductName, 0) + if err != nil { + // Maybe it is a share service, the entity is not under the project + serviceInfo, err = p.getService(ctx, distribute.DeployServiceName, distribute.DeployServiceType, "", 0) + if err != nil { + err = errors.WithMessage(err, "failed to get service info") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + } + if serviceInfo.WorkloadType == "" { + selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: distribute.DeployServiceName}.AsSelector() + + var deployments []*appsv1.Deployment + deployments, err = getter.ListDeployments(distribute.DeployNamespace, selector, p.kubeClient) + if err != nil { + err = errors.WithMessage(err, "failed to get deployment") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + + var statefulSets []*appsv1.StatefulSet + statefulSets, err = getter.ListStatefulSets(distribute.DeployNamespace, selector, p.kubeClient) + if err != nil { + err = errors.WithMessage(err, "failed to get statefulset") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + + DeploymentLoop: + for _, deploy := range deployments { + for _, container := range deploy.Spec.Template.Spec.Containers { + if container.Name == distribute.DeployContainerName { + err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, distribute.DeployContainerName, distribute.Image, p.kubeClient) + if err != nil { + p.DeployError = multierror.Append(p.DeployError, errors.WithMessagef( + err, + "failed to update container image in %s/deployments/%s/%s", + distribute.DeployNamespace, deploy.Name, container.Name)) + distribute.DeployEndTime = time.Now().Unix() + distribute.DeployStatus = string(config.StatusFailed) + p.SetStatus(config.StatusFailed) + continue DistributeLoop + } + replaced = true + break DeploymentLoop + } + } + } + StatefulSetLoop: + for _, sts := range statefulSets { + for _, container := range sts.Spec.Template.Spec.Containers { + if container.Name == distribute.DeployContainerName { + err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, distribute.DeployContainerName, distribute.Image, p.kubeClient) + if err != nil { + p.DeployError = multierror.Append(p.DeployError, errors.WithMessagef( + err, + "failed to update container image in %s/statefulset/%s/%s", + distribute.DeployNamespace, sts.Name, container.Name)) + distribute.DeployEndTime = time.Now().Unix() + distribute.DeployStatus = string(config.StatusFailed) + p.SetStatus(config.StatusFailed) + continue DistributeLoop + } + replaced = true + break StatefulSetLoop + } + } + } + } else { + switch serviceInfo.WorkloadType { + case setting.StatefulSet: + var statefulSet *appsv1.StatefulSet + statefulSet, _, err = getter.GetStatefulSet(distribute.DeployNamespace, distribute.DeployServiceName, p.kubeClient) + if err != nil { + err = errors.WithMessage(err, "failed to get statefulset") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + for _, container := range statefulSet.Spec.Template.Spec.Containers { + if container.Name == distribute.DeployContainerName { + err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, distribute.DeployContainerName, distribute.Image, p.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/statefulsets/%s/%s", + distribute.DeployNamespace, statefulSet.Name, container.Name) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + replaced = true + break + } + } + case setting.Deployment: + var deployment *appsv1.Deployment + deployment, _, err = getter.GetDeployment(distribute.DeployNamespace, distribute.DeployServiceName, p.kubeClient) + if err != nil { + err = errors.WithMessage(err, "failed to get deployment") + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue + } + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == distribute.DeployContainerName { + err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, distribute.DeployContainerName, distribute.Image, p.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/deployment/%s/%s", + distribute.DeployNamespace, deployment.Name, container.Name) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + replaced = true + break + } + } + } + } + if !replaced { + err = errors.Errorf( + "container %s is not found in resources with label %s", distribute.DeployContainerName, selector) + return + } + // if all is done in one deployment, update its status to success and endtime + distribute.DeployStatus = string(config.StatusPassed) + distribute.DeployEndTime = time.Now().Unix() } } } @@ -333,3 +516,17 @@ func (p *ReleaseImagePlugin) IsTaskEnabled() bool { func (p *ReleaseImagePlugin) ResetError() { p.Task.Error = "" } + +func (p *ReleaseImagePlugin) getService(ctx context.Context, name, serviceType, productName string, revision int64) (*types.ServiceTmpl, error) { + url := fmt.Sprintf("/api/service/services/%s/%s", name, serviceType) + + s := &types.ServiceTmpl{} + _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ + "projectName": productName, + "revision": fmt.Sprintf("%d", revision), + })) + if err != nil { + return nil, err + } + return s, nil +} diff --git a/pkg/microservice/warpdrive/core/service/types/task/release_image.go b/pkg/microservice/warpdrive/core/service/types/task/release_image.go index 392a00c3..7c43cf67 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/release_image.go +++ b/pkg/microservice/warpdrive/core/service/types/task/release_image.go @@ -58,6 +58,10 @@ type DistributeInfo struct { DistributeStatus string `json:"distribute_status" yaml:"distribute_status"` DeployEnabled bool `json:"deploy_enabled" yaml:"deploy_enabled"` DeployEnv string `json:"deploy_env" yaml:"deploy_env"` + DeployServiceType string `json:"deploy_service_type" yaml:"deploy_service_type"` + DeployContainerName string `json:"deploy_container_name" yaml:"deploy_container_name"` + DeployServiceName string `json:"deploy_service_name" yaml:"deploy_service_name"` + DeployClusterID string `json:"deploy_cluster_id" yaml:"deploy_cluster_id"` DeployNamespace string `json:"deploy_namespace" yaml:"deploy_namespace"` DeployStartTime int64 `json:"deploy_start_time" yaml:"deploy_start_time"` DeployEndTime int64 `json:"deploy_end_time" yaml:"deploy_end_time"` -- Gitee From 970e0f47c2c71275a8e6c70e5294731208b1c725 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 25 Mar 2022 08:32:27 +0800 Subject: [PATCH 1946/2647] minor bugfix to update release image stage's final status Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 13d0fd60..6537a556 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -251,6 +251,8 @@ func (p *ReleaseImagePlugin) Wait(ctx context.Context) { p.Task.TaskStatus = config.StatusFailed p.Task.Error = err.Error() return + } else { + p.SetStatus(config.StatusPassed) } }() status = waitJobEnd(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, p.kubeClient, p.clientset, p.restConfig, p.Log) -- Gitee From aa085b9fc65e34e62ef35c2372fd7ef7f14b47af Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 25 Mar 2022 09:45:14 +0800 Subject: [PATCH 1947/2647] deployment module for helm services in distribution stage Signed-off-by: Min Min --- .../core/service/taskplugin/release_image.go | 408 +++++++++++++++++- 1 file changed, 402 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 6537a556..8dd19cc0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -19,14 +19,21 @@ package taskplugin import ( "context" "fmt" - "github.com/hashicorp/go-multierror" configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/kube/getter" + s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util" + fsutil "github.com/koderover/zadig/pkg/util/fs" + helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" + "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" + "path/filepath" "strconv" "time" @@ -70,7 +77,7 @@ type ReleaseImagePlugin struct { Task *task.ReleaseImage Log *zap.SugaredLogger httpClient *httpclient.Client - DeployError *multierror.Error + StorageURI string } func (p *ReleaseImagePlugin) SetAckFunc(func()) { @@ -127,6 +134,7 @@ func (p *ReleaseImagePlugin) TaskTimeout() int { func (p *ReleaseImagePlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace p.HubServerAddr = pipelineTask.ConfigPayload.HubServerAddr + p.StorageURI = pipelineTask.StorageURI // 设置本次运行需要配置 //t.Workspace = fmt.Sprintf("%s/%s", pipelineTask.ConfigPayload.NFS.Path, pipelineTask.PipelineName) releases := make([]task.RepoImage, 0) @@ -336,10 +344,10 @@ DistributeLoop: if container.Name == distribute.DeployContainerName { err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, distribute.DeployContainerName, distribute.Image, p.kubeClient) if err != nil { - p.DeployError = multierror.Append(p.DeployError, errors.WithMessagef( + err = errors.WithMessagef( err, "failed to update container image in %s/deployments/%s/%s", - distribute.DeployNamespace, deploy.Name, container.Name)) + distribute.DeployNamespace, deploy.Name, container.Name) distribute.DeployEndTime = time.Now().Unix() distribute.DeployStatus = string(config.StatusFailed) p.SetStatus(config.StatusFailed) @@ -356,10 +364,10 @@ DistributeLoop: if container.Name == distribute.DeployContainerName { err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, distribute.DeployContainerName, distribute.Image, p.kubeClient) if err != nil { - p.DeployError = multierror.Append(p.DeployError, errors.WithMessagef( + err = errors.WithMessagef( err, "failed to update container image in %s/statefulset/%s/%s", - distribute.DeployNamespace, sts.Name, container.Name)) + distribute.DeployNamespace, sts.Name, container.Name) distribute.DeployEndTime = time.Now().Unix() distribute.DeployStatus = string(config.StatusFailed) p.SetStatus(config.StatusFailed) @@ -432,6 +440,313 @@ DistributeLoop: // if all is done in one deployment, update its status to success and endtime distribute.DeployStatus = string(config.StatusPassed) distribute.DeployEndTime = time.Now().Unix() + } else { + // helm deployment type logic goes here + var ( + productInfo *types.Product + renderChart *types.RenderChart + replacedValuesYaml string + mergedValuesYaml string + replacedMergedValuesYaml string + servicePath string + chartPath string + replaceValuesMap map[string]interface{} + renderInfo *types.RenderSet + helmClient helmclient.Client + ) + + p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", + p.Task.ProductName, + distribute.DeployServiceName, + distribute.DeployContainerName, + distribute.DeployNamespace) + + productInfo, err = p.getProductInfo(ctx, &EnvArgs{ + EnvName: distribute.DeployEnv, + ProductName: p.Task.ProductName, + }) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get product %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + renderInfo, err = p.getRenderSet(ctx, productInfo.Render.Name, productInfo.Render.Revision) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get getRenderSet %s/%d", + productInfo.Render.Name, productInfo.Render.Revision) + return + } + + serviceRevisionInProduct := int64(0) + var targetContainer *types.Container + for _, service := range productInfo.GetServiceMap() { + if service.ServiceName == distribute.DeployServiceName { + serviceRevisionInProduct = service.Revision + for _, container := range service.Containers { + if container.Name == distribute.DeployContainerName { + targetContainer = container + break + } + } + break + } + } + + if targetContainer == nil { + err = errors.Errorf("failed to find target container %s from service %s", distribute.DeployContainerName, distribute.DeployServiceName) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + if targetContainer.ImagePath == nil { + err = errors.Errorf("failed to get image path of %s from service %s", distribute.DeployContainerName, distribute.DeployServiceName) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + for _, chartInfo := range renderInfo.ChartInfos { + if chartInfo.ServiceName == distribute.DeployServiceName { + renderChart = chartInfo + break + } + } + + if renderChart == nil { + err = errors.Errorf("failed to update container image in %s/%s,chart not found", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + // use revision of service currently applied in environment instead of the latest revision + path, errDownload := p.downloadService(p.Task.ProductName, distribute.DeployServiceName, + p.StorageURI, serviceRevisionInProduct) + if errDownload != nil { + p.Log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + serviceRevisionInProduct, distribute.DeployServiceName) + path, errDownload = p.downloadService(p.Task.ProductName, distribute.DeployServiceName, + p.StorageURI, 0) + if errDownload != nil { + err = errors.WithMessagef( + errDownload, + "failed to download service %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + } + + chartPath, err = fsutil.RelativeToCurrentPath(path) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get relative path %s", + servicePath, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + serviceValuesYaml := renderChart.ValuesYaml + + // prepare image replace info + validMatchData := getValidMatchData(targetContainer.ImagePath) + + replaceValuesMap, err = assignImageData(distribute.Image, validMatchData) + if err != nil { + err = errors.WithMessagef( + err, + "failed to pase image uri %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + // replace image into service's values.yaml + replacedValuesYaml, err = replaceImage(serviceValuesYaml, replaceValuesMap) + if err != nil { + err = errors.WithMessagef( + err, + "failed to replace image uri %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + if replacedValuesYaml == "" { + err = errors.Errorf("failed to set new image uri into service's values.yaml %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + // merge override values and kvs into service's yaml + mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + if err != nil { + err = errors.WithMessagef( + err, + "failed to merge override values %s", + renderChart.OverrideValues, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + // replace image into final merged values.yaml + replacedMergedValuesYaml, err = replaceImage(mergedValuesYaml, replaceValuesMap) + if err != nil { + err = errors.WithMessagef( + err, + "failed to replace image uri into helm values %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + if replacedMergedValuesYaml == "" { + err = errors.Errorf("failed to set image uri into mreged values.yaml in %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + p.Log.Infof("final replaced merged values: \n%s", replacedMergedValuesYaml) + + helmClient, err = helmtool.NewClientFromNamespace(distribute.DeployClusterID, distribute.DeployNamespace) + if err != nil { + err = errors.WithMessagef( + err, + "failed to create helm client %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop + } + + releaseName := util.GeneHelmReleaseName(distribute.DeployNamespace, distribute.DeployServiceName) + + ensureUpgrade := func() error { + hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) + if errHistory != nil { + // list history should not block deploy operation, error will be logged instead of returned + p.Log.Errorf("failed to list release history, release: %s, err: %s", releaseName, errHistory) + return nil + } + if len(hrs) == 0 { + return nil + } + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + + if rel.Info.Status.IsPending() { + return fmt.Errorf("failed to upgrade release: %s with exceptional status: %s", releaseName, rel.Info.Status) + } + return nil + } + + err = ensureUpgrade() + if err != nil { + return + } + + timeOut := p.TaskTimeout() + chartSpec := helmclient.ChartSpec{ + ReleaseName: releaseName, + ChartName: chartPath, + Namespace: distribute.DeployNamespace, + ReuseValues: true, + Version: renderChart.ChartVersion, + ValuesYaml: replacedMergedValuesYaml, + SkipCRDs: false, + UpgradeCRDs: true, + Timeout: time.Second * time.Duration(timeOut), + Wait: true, + Replace: true, + MaxHistory: 10, + } + + done := make(chan bool) + go func(chan bool) { + if _, err = helmClient.InstallOrUpgradeChart(ctx, &chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to upgrade helm chart %s/%s", + distribute.DeployNamespace, + distribute.DeployServiceName, + ) + done <- false + } else { + done <- true + } + }(done) + + select { + case <-done: + break + case <-time.After(chartSpec.Timeout + time.Minute): + err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) + } + if err != nil { + return + } + + //替换环境变量中的chartInfos + for _, chartInfo := range renderInfo.ChartInfos { + if chartInfo.ServiceName == distribute.DeployServiceName { + chartInfo.ValuesYaml = replacedValuesYaml + break + } + } + + // TODO too dangerous to override entire renderset! + err = p.updateRenderSet(ctx, &types.RenderSet{ + Name: renderInfo.Name, + Revision: renderInfo.Revision, + DefaultValues: renderInfo.DefaultValues, + ChartInfos: renderInfo.ChartInfos, + }) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update renderset info %s/%s, renderset %s", + distribute.DeployNamespace, + distribute.DeployServiceName, + renderInfo.Name, + ) + } } } } @@ -532,3 +847,84 @@ func (p *ReleaseImagePlugin) getService(ctx context.Context, name, serviceType, } return s, nil } + +func (p *ReleaseImagePlugin) getProductInfo(ctx context.Context, args *EnvArgs) (*types.Product, error) { + url := fmt.Sprintf("/api/environment/environments/%s/productInfo", args.EnvName) + + prod := &types.Product{} + _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("projectName", args.ProductName)) + if err != nil { + return nil, err + } + return prod, nil +} + +func (p *ReleaseImagePlugin) getRenderSet(ctx context.Context, name string, revision int64) (*types.RenderSet, error) { + url := fmt.Sprintf("/api/project/renders/render/%s/revision/%d", name, revision) + + rs := &types.RenderSet{} + _, err := p.httpClient.Get(url, httpclient.SetResult(rs)) + if err != nil { + return nil, err + } + + return rs, nil +} + +func (p *ReleaseImagePlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { + logger := p.Log + + fileName := serviceName + if revision > 0 { + fileName = fmt.Sprintf("%s-%d", serviceName, revision) + } + tarball := fmt.Sprintf("%s.tar.gz", fileName) + localBase := configbase.LocalServicePath(productName, serviceName) + tarFilePath := filepath.Join(localBase, tarball) + + exists, err := fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if exists { + return tarFilePath, nil + } + + s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) + if err != nil { + return "", err + } + + s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, configbase.ObjectStorageServicePath(productName, serviceName)) + forcedPathStyle := true + if s3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3Client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err != nil { + p.Log.Errorf("failed to create s3 client, err: %s", err) + return "", err + } + if err = s3Client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { + logger.Errorf("failed to download file from s3, err: %s", err) + return "", err + } + + exists, err = fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if !exists { + return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) + } + + return tarFilePath, nil +} + +func (p *ReleaseImagePlugin) updateRenderSet(ctx context.Context, args *types.RenderSet) error { + url := "/api/project/renders" + + _, err := p.httpClient.Put(url, httpclient.SetBody(args)) + + return err +} -- Gitee From f52ed14ab38e81f145aee0c7f86d68fb902c1bad Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 17:14:54 +0800 Subject: [PATCH 1948/2647] enhance build function Signed-off-by: liu deyi --- .../aslan/core/common/service/gitee/gitee.go | 15 ++++++ .../service/workflow/pipeline_validation.go | 46 +++++++++++++++++++ .../reaper/core/service/meta/const.go | 3 ++ .../reaper/core/service/reaper/git.go | 2 + pkg/tool/gitee/branch.go | 43 +++++++++++++++++ pkg/tool/gitee/repositories.go | 34 ++++++++++++++ pkg/tool/gitee/tag.go | 7 +++ 7 files changed, 150 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/service/gitee/gitee.go diff --git a/pkg/microservice/aslan/core/common/service/gitee/gitee.go b/pkg/microservice/aslan/core/common/service/gitee/gitee.go new file mode 100644 index 00000000..8678a3cf --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/gitee/gitee.go @@ -0,0 +1,15 @@ +package gitee + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitee" + + giteeClient "github.com/koderover/zadig/pkg/tool/gitee" +) + +func NewClient(accessToken, proxyAddress string, enableProxy bool) gitee.Client { + client, _ := giteeClient.NewClient(accessToken, proxyAddress, enableProxy) + return gitee.Client{ + Client: client, + AccessToken: accessToken, + } +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 8db28da0..a1d04988 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -48,6 +48,7 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/gitee" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" @@ -355,6 +356,51 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } } + } else if codeHostInfo.Type == systemconfig.GiteeProvider { + gitCli, _ := gitee.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) + if build.CommitID == "" { + if build.Tag != "" && build.PR == 0 { + tags, err := gitCli.ListTags(context.Background(), codeHostInfo.AccessToken, build.RepoOwner, build.RepoName) + if err != nil { + log.Errorf("failed to gitee ListTags err:%s", err) + return + } + + for _, tag := range tags { + if tag.Name == build.Tag { + build.CommitID = tag.Commit.Sha + commitInfo, err := gitCli.GetSingleCommitOfProject(context.Background(), codeHostInfo.AccessToken, build.RepoOwner, build.RepoName, build.CommitID) + if err != nil { + log.Errorf("failed to gitee GetCommit %s err:%s", tag.Commit.Sha, err) + return + } + build.CommitMessage = commitInfo.Commit.Message + build.AuthorName = commitInfo.Commit.Author.Name + return + } + } + } else if build.Branch != "" && build.PR == 0 { + branch, err := gitCli.GetSingleBranch(codeHostInfo.AccessToken, build.RepoOwner, build.RepoName, build.Branch) + if err == nil { + build.CommitID = branch.Commit.Sha + build.CommitMessage = branch.Commit.Commit.Message + build.AuthorName = branch.Commit.Commit.Author.Name + } + } else if build.PR > 0 { + prCommits, err := gitCli.ListCommits(context.Background(), build.RepoOwner, build.RepoName, build.PR, nil) + sort.SliceStable(prCommits, func(i, j int) bool { + return prCommits[i].Commit.Committer.Date.Unix() > prCommits[j].Commit.Committer.Date.Unix() + }) + if err == nil && len(prCommits) > 0 { + for _, commit := range prCommits { + build.CommitID = commit.Sha + build.CommitMessage = commit.Commit.Message + build.AuthorName = commit.Commit.Author.Name + return + } + } + } + } } else { gitCli := git.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) //// 需要后端自动获取Branch当前Commit,并填写到build中 diff --git a/pkg/microservice/reaper/core/service/meta/const.go b/pkg/microservice/reaper/core/service/meta/const.go index d0385d72..97cfb964 100644 --- a/pkg/microservice/reaper/core/service/meta/const.go +++ b/pkg/microservice/reaper/core/service/meta/const.go @@ -28,6 +28,9 @@ const ( // ProviderCodehub ProviderCodehub = "codehub" + // ProviderGitee + ProviderGitee = "gitee" + // Oauth prefix OauthTokenPrefix = "oauth2" diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index a7b5590a..419ddc7a 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -202,6 +202,8 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { Cmd: c.RemoteAdd(repo.RemoteName, fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", u.Scheme, user, repo.Password, u.Host, repo.Owner, repo.Name)), DisableTrace: true, }) + } else if repo.Source == meta.ProviderGitee { + cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) } else { // github cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) diff --git a/pkg/tool/gitee/branch.go b/pkg/tool/gitee/branch.go index 94084074..de6b46db 100644 --- a/pkg/tool/gitee/branch.go +++ b/pkg/tool/gitee/branch.go @@ -2,8 +2,12 @@ package gitee import ( "context" + "fmt" + "time" "gitee.com/openeuler/go-gitee/gitee" + + "github.com/koderover/zadig/pkg/tool/httpclient" ) func (c *Client) ListBranches(ctx context.Context, owner, repo string, opts *gitee.GetV5ReposOwnerRepoBranchesOpts) ([]gitee.Branch, error) { @@ -13,3 +17,42 @@ func (c *Client) ListBranches(ctx context.Context, owner, repo string, opts *git } return bs, nil } + +type Branch struct { + Name string `json:"name"` + Commit struct { + Sha string `json:"sha"` + URL string `json:"url"` + Commit struct { + Author struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` + } `json:"author"` + URL string `json:"url"` + Message string `json:"message"` + Tree struct { + Sha string `json:"sha"` + URL string `json:"url"` + } `json:"tree"` + Committer struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` + } `json:"committer"` + } `json:"commit"` + } `json:"commit"` +} + +func (c *Client) GetSingleBranch(accessToken, owner, repo, branch string) (*Branch, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := fmt.Sprintf("/v5/repos/%s/%s/branches/%", owner, repo, branch) + var branchInfo *Branch + _, err := httpClient.Get(url, httpclient.SetQueryParam("access_token", accessToken), httpclient.SetResult(&branchInfo)) + if err != nil { + return nil, err + } + return branchInfo, nil +} diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 01182eae..6d15f0d6 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -2,6 +2,8 @@ package gitee import ( "context" + "fmt" + "time" "gitee.com/openeuler/go-gitee/gitee" "github.com/antihax/optional" @@ -103,3 +105,35 @@ func (c *Client) GetTrees(ctx context.Context, owner, repo, sha string, level in } return tree, nil } + +type RepoCommit struct { + URL string `json:"url"` + Sha string `json:"sha"` + Commit struct { + Author struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` + } `json:"author"` + Committer struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` + } `json:"committer"` + Message string `json:"message"` + } `json:"commit"` +} + +func (c *Client) GetSingleCommitOfProject(ctx context.Context, accessToken, owner, repo, commitSha string) (*RepoCommit, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := fmt.Sprintf("/v5/repos/%s/%s/commits/%s", owner, repo, commitSha) + + var commit *RepoCommit + _, err := httpClient.Get(url, httpclient.SetQueryParam("access_token", accessToken), httpclient.SetResult(&commit)) + if err != nil { + return nil, err + } + return commit, nil +} diff --git a/pkg/tool/gitee/tag.go b/pkg/tool/gitee/tag.go index e12e0a77..5d2c6f85 100644 --- a/pkg/tool/gitee/tag.go +++ b/pkg/tool/gitee/tag.go @@ -3,6 +3,7 @@ package gitee import ( "context" "fmt" + "time" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -10,6 +11,12 @@ import ( type Tag struct { Name string `json:"name"` Message string `json:"message"` + Commit Commit `json:"commit"` +} + +type Commit struct { + Sha string `json:"sha"` + Date time.Time `json:"date"` } func (c *Client) ListTags(ctx context.Context, accessToken, owner string, repo string) ([]Tag, error) { -- Gitee From 8d738b85bd572d5480ef6ec0915e8f727f72e9f0 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 18:00:38 +0800 Subject: [PATCH 1949/2647] modify reaper code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/meta/types.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index bed14d73..2c8ef078 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -398,6 +398,8 @@ func (g *Git) SSHCloneURL(source, owner, name string) string { func (g *Git) HTTPSCloneURL(source, token, owner, name string) string { if strings.ToLower(source) == ProviderGitlab { return fmt.Sprintf("https://%s/%s/%s.git", g.GetGitlabHost(), owner, name) + } else if strings.ToLower(source) == ProviderGitee { + return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", token, "gitee.com", owner, name) } //return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", g.GetInstallationToken(owner), g.GetGithubHost(), owner, name) return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", token, g.GetGithubHost(), owner, name) -- Gitee From 634df61c300a74f27bad08e623cff83b69dc83a1 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Mon, 28 Mar 2022 19:22:05 +0800 Subject: [PATCH 1950/2647] support deleting subenv Signed-off-by: zhangyifei --- .../service/collaboration_instance.go | 4 +- .../core/common/repository/mongodb/product.go | 15 ++ .../aslan/core/common/service/product.go | 14 +- .../core/environment/handler/environment.go | 3 +- .../core/environment/service/environment.go | 199 ++++++++++++++++++ .../aslan/core/environment/service/product.go | 6 +- .../core/environment/service/share_env.go | 54 ++--- .../environment/service/share_env_utils.go | 199 ++++++++++++++++++ .../aslan/core/project/service/product.go | 4 +- .../service/webhook/gitlab_workflow_task.go | 2 +- pkg/tool/errors/const.go | 4 + 11 files changed, 465 insertions(+), 39 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/service/share_env_utils.go diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 2b4375c6..5166c72a 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -877,7 +877,7 @@ func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projec log *zap.SugaredLogger) (err error) { deleteResp := getCollaborationDelete(updateResp) for _, product := range deleteResp.Products { - err := commonservice.DeleteProduct(username, product, projectName, requestID, log) + err := service2.DeleteProduct(username, product, projectName, requestID, log) if err != nil && err != mongo.ErrNoDocuments { log.Errorf("delete product err:%v", err) return err @@ -1291,7 +1291,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn } for _, product := range ci.Products { if product.CollaborationType == config.CollaborationNew { - err = commonservice.DeleteProduct(userName, product.Name, ci.ProjectName, requestID, logger) + err = service2.DeleteProduct(userName, product.Name, ci.ProjectName, requestID, logger) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index b57963f2..91625c7a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -54,6 +54,11 @@ type ProductListOptions struct { InProjects []string InEnvs []string InIDs []string + + // New Since v1.11.0 + ShareEnvEnable *bool + ShareEnvIsBase *bool + ShareEnvBaseEnv *string } type projectEnvs struct { @@ -234,6 +239,15 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { } query["_id"] = bson.M{"$in": oids} } + if opt.ShareEnvEnable != nil { + query["share_env.enable"] = *opt.ShareEnvEnable + } + if opt.ShareEnvIsBase != nil { + query["share_env.is_base"] = *opt.ShareEnvIsBase + } + if opt.ShareEnvBaseEnv != nil { + query["share_env.base_env"] = *opt.ShareEnvBaseEnv + } ctx := context.Background() opts := options.Find() @@ -344,6 +358,7 @@ func (c *ProductColl) Update(args *models.Product) error { "revision": args.Revision, "render": args.Render, "error": args.Error, + "share_env": args.ShareEnv, }} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 40b96bd4..466c1b0b 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -46,6 +46,7 @@ const ( timeout = 60 ) +// TODO: Deprecated. func DeleteProduct(username, envName, productName, requestID string, log *zap.SugaredLogger) (err error) { eventStart := time.Now().Unix() productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) @@ -145,7 +146,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("workflowStat not found error:%s", err) } if workloadStat != nil { - workloadStat.Workloads = filterWorkloadsByEnv(workloadStat.Workloads, productInfo.EnvName) + workloadStat.Workloads = FilterWorkloadsByEnv(workloadStat.Workloads, productInfo.EnvName) if err := commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat); err != nil { log.Errorf("update workloads fail error:%s", err) } @@ -210,12 +211,18 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su // Delete Cluster level resources err = DeleteClusterResource(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), productInfo.ClusterID, log) - if err != nil { err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) return } + // // Handles environment sharing related operations. + // err = envservice.EnsureDeleteShareEnvConfig(context.TODO(), productInfo, istioClient) + // if err != nil { + // err = e.ErrDeleteProduct.AddDesc(e.DeleteVirtualServiceErrMsg + ": " + err.Error()) + // return + // } + s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() if err1 := DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) @@ -228,10 +235,11 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } }() } + return nil } -func filterWorkloadsByEnv(exist []commonmodels.Workload, env string) []commonmodels.Workload { +func FilterWorkloadsByEnv(exist []commonmodels.Workload, env string) []commonmodels.Workload { result := make([]commonmodels.Workload, 0) for _, v := range exist { if v.EnvName != env { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 103cebac..fa138857 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -503,7 +503,8 @@ func DeleteProduct(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境", envName, "", ctx.Logger) - ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) + // ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) + ctx.Err = service.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) } func DeleteProductServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d9a3dd9f..3638b606 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -52,6 +52,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + mongotemplate "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" @@ -1716,6 +1717,204 @@ func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ( return helmVersions, nil } +func DeleteProduct(username, envName, productName, requestID string, log *zap.SugaredLogger) (err error) { + eventStart := time.Now().Unix() + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) + if err != nil { + log.Errorf("find product error: %v", err) + return err + } + + // delete informer's cache + informer.DeleteInformer(productInfo.ClusterID, productInfo.Namespace) + + envCMMap, err := collaboration.GetEnvCMMap([]string{productName}, log) + if err != nil { + return err + } + if cmSets, ok := envCMMap[collaboration.BuildEnvCMMapKey(productName, envName)]; ok { + return fmt.Errorf("this is a base environment, collaborations:%v is related", cmSets.List()) + } + + restConfig, err := kube.GetRESTConfig(productInfo.ClusterID) + if err != nil { + return e.ErrDeleteEnv.AddErr(err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return e.ErrDeleteEnv.AddErr(err) + } + + // 设置产品状态 + err = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusDeleting) + if err != nil { + log.Errorf("[%s][%s] update product status error: %v", username, productInfo.Namespace, err) + return e.ErrDeleteEnv.AddDesc("更新环境状态失败: " + err.Error()) + } + + log.Infof("[%s] delete product %s", username, productInfo.Namespace) + commonservice.LogProductStats(username, setting.DeleteProductEvent, productName, requestID, eventStart, log) + + switch productInfo.Source { + case setting.SourceFromHelm: + err = commonrepo.NewProductColl().Delete(envName, productName) + if err != nil { + log.Errorf("Product.Delete error: %v", err) + } + + go func() { + var err error + defer func() { + if err != nil { + // 发送删除产品失败消息给用户 + title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) + commonservice.SendErrorMessage(username, title, requestID, err, log) + _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) + } else { + title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) + content := fmt.Sprintf("namespace:%s", productInfo.Namespace) + commonservice.SendMessage(username, title, content, requestID, log) + } + }() + + //卸载helm release资源 + if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { + for _, services := range productInfo.Services { + for _, service := range services { + if err = hc.UninstallRelease(&helmclient.ChartSpec{ + ReleaseName: fmt.Sprintf("%s-%s", productInfo.Namespace, service.ServiceName), + Namespace: productInfo.Namespace, + Wait: true, + Force: true, + Timeout: Timeout * time.Second * 10, + }); err != nil { + log.Errorf("UninstallRelease err:%v", err) + } + } + } + } else { + log.Errorf("获取helmClient err:%v", err) + } + + //删除namespace + s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() + if err1 := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { + err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) + return + } + }() + case setting.SourceFromExternal: + err = commonrepo.NewProductColl().Delete(envName, productName) + if err != nil { + log.Errorf("Product.Delete error: %v", err) + } + + // 删除workload数据 + tempProduct, err := mongotemplate.NewProductColl().Find(productName) + if err != nil { + log.Errorf("project not found error:%s", err) + } + if tempProduct.ProductFeature != nil && tempProduct.ProductFeature.CreateEnvType == setting.SourceFromExternal { + workloadStat, err := commonrepo.NewWorkLoadsStatColl().Find(productInfo.ClusterID, productInfo.Namespace) + if err != nil { + log.Errorf("workflowStat not found error:%s", err) + } + if workloadStat != nil { + workloadStat.Workloads = commonservice.FilterWorkloadsByEnv(workloadStat.Workloads, productInfo.EnvName) + if err := commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat); err != nil { + log.Errorf("update workloads fail error:%s", err) + } + } + // 获取所有external的服务 + currentEnvServices, err := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, envName) + if err != nil { + log.Errorf("failed to list external workload, error:%s", err) + } + + externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + ExcludeEnvName: envName, + }) + if err != nil { + log.Errorf("failed to list external service, error:%s", err) + } + + externalEnvServiceM := make(map[string]bool) + for _, externalEnvService := range externalEnvServices { + externalEnvServiceM[externalEnvService.ServiceName] = true + } + + deleteServices := sets.NewString() + for _, currentEnvService := range currentEnvServices { + if _, isExist := externalEnvServiceM[currentEnvService.ServiceName]; !isExist { + deleteServices.Insert(currentEnvService.ServiceName) + } + } + err = commonrepo.NewServiceColl().BatchUpdateExternalServicesStatus(productName, "", setting.ProductStatusDeleting, deleteServices.List()) + if err != nil { + log.Errorf("UpdateStatus external services error:%s", err) + } + // delete services_in_external_env data + if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: productName, + EnvName: envName, + }); err != nil { + log.Errorf("remove services in external env error:%s", err) + } + } + case setting.SourceFromPM: + err = commonrepo.NewProductColl().Delete(envName, productName) + if err != nil { + log.Errorf("Product.Delete error: %v", err) + } + default: + go func() { + var err error + defer func() { + if err != nil { + // 发送删除产品失败消息给用户 + title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) + commonservice.SendErrorMessage(username, title, requestID, err, log) + _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) + } else { + title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) + content := fmt.Sprintf("namespace:%s", productInfo.Namespace) + commonservice.SendMessage(username, title, content, requestID, log) + } + }() + + // Delete Cluster level resources + err = commonservice.DeleteClusterResource(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), productInfo.ClusterID, log) + if err != nil { + err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) + return + } + + // Handles environment sharing related operations. + err = EnsureDeleteShareEnvConfig(context.TODO(), productInfo, istioClient) + if err != nil { + log.Errorf("Failed to delete share env config: %s", err) + err = e.ErrDeleteProduct.AddDesc(e.DeleteVirtualServiceErrMsg + ": " + err.Error()) + return + } + + s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() + if err1 := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { + err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) + return + } + + err = commonrepo.NewProductColl().Delete(envName, productName) + if err != nil { + log.Errorf("Product.Delete error: %v", err) + } + }() + } + + return nil +} + func DeleteProductServices(envName, productName string, serviceNames []string, log *zap.SugaredLogger) (err error) { productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 2de44565..24b91e4d 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -70,11 +70,11 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { //title := "系统清理产品信息" //content := fmt.Sprintf("环境 [%s] 已经连续%d天没有使用, 系统已自动删除该环境, 如有需要请重新创建。", product.EnvName, product.RecycleDay) - if err := commonservice.DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { + if err := DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { log.Errorf("[%s][P:%s] delete product error: %v", product.EnvName, product.ProductName, err) // 如果有错误,重试删除 - if err := commonservice.DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { + if err := DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { //content = fmt.Sprintf("系统自动清理环境 [%s] 失败,请手动删除环境。", product.ProductName) log.Errorf("[%s][P:%s] retry delete product error: %v", product.EnvName, product.ProductName, err) } @@ -306,7 +306,7 @@ func CleanProducts() { _, err := templaterepo.NewProductColl().Find(prod.ProductName) if err != nil && err.Error() == "not found" { logger.Errorf("环境所属的项目不存在,准备删除此环境, namespace:%s, 项目:%s\n", prod.Namespace, prod.ProductName) - err = commonservice.DeleteProduct("CleanProducts", prod.EnvName, prod.ProductName, "", logger) + err = DeleteProduct("CleanProducts", prod.EnvName, prod.ProductName, "", logger) if err != nil { logger.Errorf("delete product failed, namespace:%s, err:%v\n", prod.Namespace, err) continue diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 148df6a0..be245bfb 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -129,7 +129,8 @@ func EnableBaseEnv(ctx context.Context, envName, productName string) error { return fmt.Errorf("failed to ensure EnvoyFilter in namespace `%s`: %s", istioNamespace, err) } - return nil + // 5. Update the environment configuration. + return ensureBaseEnvConfig(ctx, prod) } func DisableBaseEnv(ctx context.Context, envName, productName string) error { @@ -157,11 +158,11 @@ func DisableBaseEnv(ctx context.Context, envName, productName string) error { return fmt.Errorf("failed to new istio client: %s", err) } - // // 1. Delete all associated subenvironments. - // err = deleteSubEnvs(ctx, kclient, ns) - // if err != nil { - // return fmt.Errorf("failed to delete associated subenvironments of base ns `%s`: %s", ns, err) - // } + // 1. Delete all associated subenvironments. + err = ensureDeleteAssociatedEnvs(ctx, prod.EnvName) + if err != nil { + return fmt.Errorf("failed to delete associated subenvironments of base ns `%s`: %s", ns, err) + } // 2. Delete EnvoyFilter in the namespace of Istio installation. err = deleteEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) @@ -696,27 +697,6 @@ func buildEnvoyPatchValue(data map[string]interface{}) (*types.Struct, error) { return val, err } -func deleteSubEnvs(ctx context.Context, kclient client.Client, baseNS string) error { - - return ErrNotImplemented -} - -func deleteEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, istioNamespace, name string) error { - _, err := istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Get(ctx, name, metav1.GetOptions{}) - if apierrors.IsNotFound(err) { - log.Infof("EnvoyFilter %s is not found in ns `%s`. Skip.", name, istioNamespace) - return nil - } - if err != nil { - return fmt.Errorf("failed to query EnvoyFilter %s in ns `%s`: %s", name, istioNamespace, err) - } - - deleteOption := metav1.DeletePropagationBackground - return istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Delete(ctx, name, metav1.DeleteOptions{ - PropagationPolicy: &deleteOption, - }) -} - func deleteVirtualServices(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) error { zadigLabels := map[string]string{ zadigtypes.ZadigLabelKeyGlobalOwner: zadigtypes.Zadig, @@ -1044,3 +1024,23 @@ func ensureUpdateVirtualServiceInBase(ctx context.Context, envName, vsName, svcN _, err = istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Update(ctx, vsObjInBase, metav1.UpdateOptions{}) return err } + +func EnsureDeleteShareEnvConfig(ctx context.Context, env *commonmodels.Product, istioClient versionedclient.Interface) error { + if !env.ShareEnv.Enable { + return nil + } + + if env.ShareEnv.IsBase { + // 1. Delete all associated subenvironments. + err := ensureDeleteAssociatedEnvs(ctx, env.EnvName) + if err != nil { + return err + } + + // 2. If no environment is shared, delete EnvoyFilter. + return ensureDeleteEnvoyFilter(ctx, env, istioClient) + } + + // Updated the VirtualService configuration in the base environment. + return ensureCleanRoutesInBase(ctx, env, istioClient) +} diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go new file mode 100644 index 00000000..803ab145 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -0,0 +1,199 @@ +/* +Copyright 2022 The KodeRover Authors. + +.Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "fmt" + + "go.uber.org/zap" + networkingv1alpha3 "istio.io/api/networking/v1alpha3" + versionedclient "istio.io/client-go/pkg/clientset/versioned" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/log" +) + +func ensureBaseEnvConfig(ctx context.Context, baseEnv *commonmodels.Product) error { + if baseEnv.ShareEnv.Enable && baseEnv.ShareEnv.IsBase { + return nil + } + + baseEnv.ShareEnv = commonmodels.ProductShareEnv{ + Enable: true, + IsBase: true, + } + + return commonrepo.NewProductColl().Update(baseEnv) +} + +func ensureDeleteAssociatedEnvs(ctx context.Context, baseEnvName string) error { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + ShareEnvEnable: getBoolPointer(true), + ShareEnvIsBase: getBoolPointer(false), + ShareEnvBaseEnv: getStrPointer(baseEnvName), + }) + if err != nil { + return err + } + + logger, err := zap.NewProduction() + if err != nil { + return err + } + + for _, env := range envs { + err := DeleteProduct("system", env.EnvName, env.ProductName, "", logger.Sugar()) + if err != nil { + return err + } + } + + return nil +} + +func ensureDeleteEnvoyFilter(ctx context.Context, baseEnv *commonmodels.Product, istioClient versionedclient.Interface) error { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + ShareEnvEnable: getBoolPointer(true), + }) + if err != nil { + return fmt.Errorf("failed to list products which enable env sharing: %s", err) + } + + needDeleteEnvoyFilter := true + for _, env := range envs { + if env.ProductName == baseEnv.ProductName && env.EnvName == baseEnv.EnvName { + continue + } + + needDeleteEnvoyFilter = false + } + + if !needDeleteEnvoyFilter { + return nil + } + + return deleteEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) +} + +func ensureCleanRoutesInBase(ctx context.Context, grayEnv *commonmodels.Product, istioClient versionedclient.Interface) error { + baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: grayEnv.ProductName, + EnvName: grayEnv.ShareEnv.BaseEnv, + }) + if err != nil { + return err + } + + grayNS := grayEnv.Namespace + vsList, err := istioClient.NetworkingV1alpha3().VirtualServices(grayNS).List(ctx, metav1.ListOptions{}) + if err != nil { + return fmt.Errorf("failed to list VirtualServices for env %s of product %s: %s", grayEnv.EnvName, grayEnv.ProductName, err) + } + + for _, vsInGray := range vsList.Items { + err = ensureCleanRouteInBase(ctx, grayEnv.EnvName, baseEnv.Namespace, vsInGray.Name, istioClient) + if err != nil { + return fmt.Errorf("failed to clean route in base env: %s", err) + } + } + + return nil +} + +func getBoolPointer(data bool) *bool { + return &data +} + +func getStrPointer(data string) *string { + return &data +} + +func deleteEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, istioNamespace, name string) error { + _, err := istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Get(ctx, name, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + log.Infof("EnvoyFilter %s is not found in ns `%s`. Skip.", name, istioNamespace) + return nil + } + if err != nil { + return fmt.Errorf("failed to query EnvoyFilter %s in ns `%s`: %s", name, istioNamespace, err) + } + + deleteOption := metav1.DeletePropagationBackground + return istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Delete(ctx, name, metav1.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) +} + +func ensureCleanRouteInBase(ctx context.Context, envName, baseNS, vsName string, istioClient versionedclient.Interface) error { + baseVS, err := istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Get(ctx, vsName, metav1.GetOptions{}) + if err != nil { + return err + } + + if len(baseVS.Spec.Http) == 0 { + return nil + } + + var needClean bool + var location int + for i, vsHttp := range baseVS.Spec.Http { + if len(vsHttp.Match) == 0 { + continue + } + + for _, vsMatch := range vsHttp.Match { + if len(vsMatch.Headers) == 0 { + continue + } + + matchValue, found := vsMatch.Headers[zadigMatchXEnv] + if !found { + continue + } + + if matchValue.GetExact() != envName { + continue + } + + needClean = true + break + } + + if !needClean { + continue + } + + location = i + break + } + + if !needClean { + return nil + } + + httpRoutes := make([]*networkingv1alpha3.HTTPRoute, 0, len(baseVS.Spec.Http)-1) + httpRoutes = append(httpRoutes, baseVS.Spec.Http[:location]...) + httpRoutes = append(httpRoutes, baseVS.Spec.Http[location+1:]...) + baseVS.Spec.Http = httpRoutes + + _, err = istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Update(ctx, baseVS, metav1.UpdateOptions{}) + return err +} diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 371c4251..ea2f180a 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -622,7 +622,7 @@ func UnForkProduct(userID string, username, productName, workflowName, envName, log.Errorf("Failed to delete roleBinding, err: %s", err) return e.ErrForkProduct } - if err := commonservice.DeleteProduct(username, envName, productName, requestID, log); err != nil { + if err := environmentservice.DeleteProduct(username, envName, productName, requestID, log); err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { if description != "not found" { @@ -715,7 +715,7 @@ func DeleteProductsAsync(userName, productName, requestID string, log *zap.Sugar } errList := new(multierror.Error) for _, env := range envs { - err = commonservice.DeleteProduct(userName, env.EnvName, productName, requestID, log) + err = environmentservice.DeleteProduct(userName, env.EnvName, productName, requestID, log) if err != nil { errList = multierror.Append(errList, err) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 70cb3f3d..3aa1aaf2 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -745,7 +745,7 @@ func CreateEnvAndTaskByPR(workflowArgs *commonmodels.WorkflowTaskArgs, prID int, } //按照用户设置的环境回收策略进行环境回收 if workflowArgs.EnvRecyclePolicy == setting.EnvRecyclePolicyAlways || (workflowArgs.EnvRecyclePolicy == setting.EnvRecyclePolicyTaskStatus && taskStatus == string(config.StatusPassed)) { - err = commonservice.DeleteProduct(setting.SystemUser, envName, workflowArgs.ProductTmplName, requestID, log) + err = environmentservice.DeleteProduct(setting.SystemUser, envName, workflowArgs.ProductTmplName, requestID, log) if err != nil { log.Errorf("CreateEnvAndTaskByPR DeleteProduct err:%v ", err) return err diff --git a/pkg/tool/errors/const.go b/pkg/tool/errors/const.go index c4ed8c26..744d01f8 100644 --- a/pkg/tool/errors/const.go +++ b/pkg/tool/errors/const.go @@ -117,3 +117,7 @@ const ( DuplicateClusterNameFound = "已经有相同名称的集群存在" StartPodTimeout = "这些服务中可能存在未正常运行的实例" ) + +const ( + DeleteVirtualServiceErrMsg = "删除 VirtualService 失败" +) -- Gitee From 4873083b6f89bb143aa8cc505b4f3c96abaf12ac Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 28 Mar 2022 19:46:16 +0800 Subject: [PATCH 1951/2647] update docker file Signed-off-by: allenshen --- docker/base/arm64/alpine-curl.Dockerfile | 2 +- docker/base/arm64/alpine-git.Dockerfile | 2 +- docker/base/arm64/alpine.Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/base/arm64/alpine-curl.Dockerfile b/docker/base/arm64/alpine-curl.Dockerfile index 9465bd02..3bda0ad8 100644 --- a/docker/base/arm64/alpine-curl.Dockerfile +++ b/docker/base/arm64/alpine-curl.Dockerfile @@ -1,4 +1,4 @@ -FROM arm64v8/alpine +FROM arm64v8/alpine:3.15.2 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk add curl diff --git a/docker/base/arm64/alpine-git.Dockerfile b/docker/base/arm64/alpine-git.Dockerfile index a72261a4..51fb7450 100644 --- a/docker/base/arm64/alpine-git.Dockerfile +++ b/docker/base/arm64/alpine-git.Dockerfile @@ -1,4 +1,4 @@ -FROM arm64v8/alpine +FROM arm64v8/alpine:3.15.2 RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ apk add git less openssh diff --git a/docker/base/arm64/alpine.Dockerfile b/docker/base/arm64/alpine.Dockerfile index 1acc539a..3e2d556d 100644 --- a/docker/base/arm64/alpine.Dockerfile +++ b/docker/base/arm64/alpine.Dockerfile @@ -1,4 +1,4 @@ -FROM arm64v8/alpine +FROM arm64v8/alpine:3.15.2 # https://wiki.alpinelinux.org/wiki/Setting_the_timezone RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \ -- Gitee From c795eca80d3509957071a5fd8ed2ec61a30b7d0b Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 28 Mar 2022 20:20:20 +0800 Subject: [PATCH 1952/2647] host environments can use registry Signed-off-by: panxunying --- pkg/microservice/aslan/core/service/handler/service.go | 3 ++- pkg/microservice/aslan/core/service/service/service.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 6fd1443d..33fba076 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -172,6 +172,7 @@ type K8sWorkloadsArgs struct { ClusterID string `bson:"cluster_id" json:"cluster_id"` Namespace string `bson:"namespace" json:"namespace"` ProductName string `bson:"product_name" json:"product_name"` + RegistryID string `json:"registry_id"` } func UpdateWorkloads(c *gin.Context) { @@ -202,7 +203,7 @@ func CreateK8sWorkloads(c *gin.Context) { return } - ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) + ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args.RegistryID, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) } func ListAvailablePublicServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c674de15..c2a7e9b2 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -255,7 +255,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv return serviceOption, nil } -func CreateK8sWorkLoads(ctx context.Context, requestID, username string, productName string, workLoads []commonmodels.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { +func CreateK8sWorkLoads(ctx context.Context, requestID, username, registryID string, productName string, workLoads []commonmodels.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { log.Errorf("[%s] error: %v", namespace, err) @@ -348,6 +348,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username string, product ProductName: productName, Source: setting.SourceFromExternal, ClusterID: clusterID, + RegistryID: registryID, EnvName: envName, Namespace: namespace, UpdateBy: username, -- Gitee From 6cdcc3f976f365780abc22ab1355b4d9503135ad Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 28 Mar 2022 21:20:11 +0800 Subject: [PATCH 1953/2647] debug logs Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_task.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e9baa67d..0ed52296 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -640,6 +640,9 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, log.Errorf("distrbiute stages to subtasks error: %v", err) return nil, e.ErrCreateTask.AddErr(err) } + for _, dtask := range distributeTasks { + fmt.Printf(">>>>>>>>>>>>>>>>>>>>>> task is: %+v <<<<<<<<<<<<<<<<<\n ", dtask) + } } } subTasks = append(subTasks, distributeTasks...) -- Gitee From 10405c07934ff6bcb181efa805a2667cf4f7e770 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Mon, 28 Mar 2022 21:30:08 +0800 Subject: [PATCH 1954/2647] refresh accesstoken Signed-off-by: liu deyi --- .../aslan/core/code/client/open/open.go | 16 +++++++++++ .../aslan/core/common/service/gitee/gitee.go | 15 ----------- .../service/workflow/pipeline_validation.go | 13 +++++++++ .../codehost/repository/mongodb/codehost.go | 4 +++ pkg/shared/client/systemconfig/codehost.go | 27 ++++++++++++++----- pkg/tool/gitee/client.go | 2 +- pkg/tool/gitee/repositories.go | 27 +++++++++++++++++++ 7 files changed, 81 insertions(+), 23 deletions(-) delete mode 100644 pkg/microservice/aslan/core/common/service/gitee/gitee.go diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index f1a37900..5d7a50b0 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -19,6 +19,7 @@ package open import ( "encoding/json" "fmt" + "time" "go.uber.org/zap" @@ -30,6 +31,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + giteeClient "github.com/koderover/zadig/pkg/tool/gitee" ) type ClientConfig interface { @@ -49,6 +51,20 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, if err != nil { return nil, err } + + // The normal expiration time is 86400 + if ch.Type == setting.SourceFromGitee && (time.Now().Unix()-ch.UpdatedAt) >= 86000 { + accessToken, _ := giteeClient.RefreshAccessToken(ch.RefreshToken) + if accessToken != nil { + ch.AccessToken = accessToken.AccessToken + ch.RefreshToken = accessToken.RefreshToken + ch.UpdatedAt = int64(accessToken.CreatedAt) + if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { + return nil, fmt.Errorf("failed to update codehost,err:%s", err) + } + } + } + var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] if !ok { diff --git a/pkg/microservice/aslan/core/common/service/gitee/gitee.go b/pkg/microservice/aslan/core/common/service/gitee/gitee.go deleted file mode 100644 index 8678a3cf..00000000 --- a/pkg/microservice/aslan/core/common/service/gitee/gitee.go +++ /dev/null @@ -1,15 +0,0 @@ -package gitee - -import ( - "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitee" - - giteeClient "github.com/koderover/zadig/pkg/tool/gitee" -) - -func NewClient(accessToken, proxyAddress string, enableProxy bool) gitee.Client { - client, _ := giteeClient.NewClient(accessToken, proxyAddress, enableProxy) - return gitee.Client{ - Client: client, - AccessToken: accessToken, - } -} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index a1d04988..afdf2464 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -357,6 +357,19 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } } else if codeHostInfo.Type == systemconfig.GiteeProvider { + // The normal expiration time is 86400 + if codeHostInfo.Type == setting.SourceFromGitee && (time.Now().Unix()-codeHostInfo.UpdatedAt) >= 86000 { + accessToken, _ := gitee.RefreshAccessToken(codeHostInfo.RefreshToken) + if accessToken != nil { + codeHostInfo.AccessToken = accessToken.AccessToken + codeHostInfo.RefreshToken = accessToken.RefreshToken + codeHostInfo.UpdatedAt = int64(accessToken.CreatedAt) + if err = systemconfig.New().UpdateCodeHost(codeHostInfo.ID, codeHostInfo); err != nil { + log.Errorf("failed to update codehost err:%s", err) + return + } + } + } gitCli, _ := gitee.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) if build.CommitID == "" { if build.Tag != "" && build.PR == 0 { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index f4f45ed0..66508ff6 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -161,6 +161,10 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, } if host.Type == setting.SourceFromGerrit { modifyValue["access_token"] = host.AccessToken + } else if host.Type == setting.SourceFromGitee { + modifyValue["access_token"] = host.AccessToken + modifyValue["refresh_token"] = host.RefreshToken + modifyValue["updated_at"] = host.UpdatedAt } change := bson.M{"$set": modifyValue} diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 61151323..25cefb33 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -31,19 +31,21 @@ const ( ) type CodeHost struct { - ID int `json:"id"` - Address string `json:"address"` - Type string `json:"type"` - AccessToken string `json:"access_token"` - Namespace string `json:"namespace"` - Region string `json:"region"` + ID int `json:"id"` + Address string `json:"address"` + Type string `json:"type"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + Namespace string `json:"namespace"` + Region string `json:"region"` // the field and tag not consistent because of db field AccessKey string `json:"application_id"` SecretKey string `json:"client_secret"` Username string `json:"username"` Password string `json:"password"` // the field determine whether the proxy is enabled - EnableProxy bool `json:"enable_proxy"` + EnableProxy bool `json:"enable_proxy"` + UpdatedAt int64 `json:"updated_at"` } type Option struct { @@ -80,6 +82,17 @@ func (c *Client) ListCodeHosts() ([]*CodeHost, error) { return res, nil } +func (c *Client) UpdateCodeHost(id int, codehost *CodeHost) error { + url := fmt.Sprintf("/codehosts/%d", id) + + _, err := c.Patch(url, httpclient.SetBody(codehost)) + if err != nil { + return err + } + + return nil +} + func (c *Client) GetCodeHostByAddressAndOwner(address, owner, source string) (*CodeHost, error) { url := "/codehosts" diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index e38f68a4..cd35b4c0 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -21,7 +21,7 @@ func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) conf := gitee.NewConfiguration() dc := http.DefaultClient - if proxyAddr != "" { + if enableProxy { p, err := url.Parse(proxyAddr) if err == nil { proxy := http.ProxyURL(p) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 6d15f0d6..3dd02986 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -137,3 +137,30 @@ func (c *Client) GetSingleCommitOfProject(ctx context.Context, accessToken, owne } return commit, nil } + +type AccessToken struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + CreatedAt int `json:"created_at"` +} + +func RefreshAccessToken(refreshToken string) (*AccessToken, error) { + httpClient := httpclient.New( + httpclient.SetHostURL("https://gitee.com"), + ) + url := "/oauth/token" + queryParams := make(map[string]string) + queryParams["grant_type"] = "refresh_token" + queryParams["refresh_token"] = refreshToken + + var accessToken *AccessToken + _, err := httpClient.Post(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&accessToken)) + if err != nil { + return nil, err + } + + return accessToken, nil +} -- Gitee From 2ded76b86101048d36f14f7a6abfbd6981c4a827 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 28 Mar 2022 21:31:30 +0800 Subject: [PATCH 1955/2647] debug logs Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 0ed52296..ec001d0d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -627,6 +627,8 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, if distribute.Target != nil { serviceModule.ProductName = distribute.Target.ProductName } + fmt.Printf(">>>>>>>>>>>>>>>> distribute.Target is: %s <<<<<<<<<<<<<<<<<<<< \n", distribute.Target) + fmt.Printf(">>>>>>>>>>>>>>>> distribute.Target is: %s <<<<<<<<<<<<<<<<<<<< \n", serviceModule) if reflect.DeepEqual(distribute.Target, serviceModule) { distributeTasks, err = formatDistributeSubtasks( args.ProductTmplName, -- Gitee From eedc2a86eb314fee28d347070c24d18b07f70006 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 09:31:24 +0800 Subject: [PATCH 1956/2647] record error message Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/client/open/open.go | 6 ++++-- .../core/workflow/service/workflow/pipeline_validation.go | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 5d7a50b0..1f7de459 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -54,14 +54,16 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, // The normal expiration time is 86400 if ch.Type == setting.SourceFromGitee && (time.Now().Unix()-ch.UpdatedAt) >= 86000 { - accessToken, _ := giteeClient.RefreshAccessToken(ch.RefreshToken) - if accessToken != nil { + accessToken, err := giteeClient.RefreshAccessToken(ch.RefreshToken) + if err == nil { ch.AccessToken = accessToken.AccessToken ch.RefreshToken = accessToken.RefreshToken ch.UpdatedAt = int64(accessToken.CreatedAt) if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { return nil, fmt.Errorf("failed to update codehost,err:%s", err) } + } else { + log.Errorf("failed to refreshAccessToken,err:%s", err) } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index afdf2464..5caf3676 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -360,7 +360,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { // The normal expiration time is 86400 if codeHostInfo.Type == setting.SourceFromGitee && (time.Now().Unix()-codeHostInfo.UpdatedAt) >= 86000 { accessToken, _ := gitee.RefreshAccessToken(codeHostInfo.RefreshToken) - if accessToken != nil { + if err == nil { codeHostInfo.AccessToken = accessToken.AccessToken codeHostInfo.RefreshToken = accessToken.RefreshToken codeHostInfo.UpdatedAt = int64(accessToken.CreatedAt) @@ -368,6 +368,8 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { log.Errorf("failed to update codehost err:%s", err) return } + } else { + log.Errorf("failed to refreshAccessToken,err:%s", err) } } gitCli, _ := gitee.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) -- Gitee From 2912b1244892c94db1f20374b99160546fb6bf33 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 29 Mar 2022 10:07:30 +0800 Subject: [PATCH 1957/2647] removed debug logs Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_task.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index ec001d0d..e9baa67d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -627,8 +627,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, if distribute.Target != nil { serviceModule.ProductName = distribute.Target.ProductName } - fmt.Printf(">>>>>>>>>>>>>>>> distribute.Target is: %s <<<<<<<<<<<<<<<<<<<< \n", distribute.Target) - fmt.Printf(">>>>>>>>>>>>>>>> distribute.Target is: %s <<<<<<<<<<<<<<<<<<<< \n", serviceModule) if reflect.DeepEqual(distribute.Target, serviceModule) { distributeTasks, err = formatDistributeSubtasks( args.ProductTmplName, @@ -642,9 +640,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, log.Errorf("distrbiute stages to subtasks error: %v", err) return nil, e.ErrCreateTask.AddErr(err) } - for _, dtask := range distributeTasks { - fmt.Printf(">>>>>>>>>>>>>>>>>>>>>> task is: %+v <<<<<<<<<<<<<<<<<\n ", dtask) - } } } subTasks = append(subTasks, distributeTasks...) -- Gitee From fc618ec705449d4bcc9b4f815f55fa30ca257a7f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 29 Mar 2022 11:12:36 +0800 Subject: [PATCH 1958/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 4 ++-- pkg/microservice/aslan/core/workflow/handler/router.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 267e7f49..9fb739e9 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -95,9 +95,9 @@ rules: description: "" rules: - method: POST - endpoint: "/api/aslan/workflow/workflowtask" + endpoint: "/api/aslan/workflow/workflowtask/?*" - method: PUT - endpoint: "/api/aslan/workflow/workflowtask" + endpoint: "/api/aslan/workflow/workflowtask/?*" - method: POST endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" - method: DELETE diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index dfe98e90..96781d42 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -122,8 +122,8 @@ func (*Router) Inject(router *gin.RouterGroup) { //todo 修改权限的uuid workflowtask.GET("/targets/:productName/:namespace", GetWorkflowArgs) workflowtask.GET("/preset/:namespace/:workflowName", PresetWorkflowArgs) - workflowtask.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTask) - workflowtask.PUT("", gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) + workflowtask.POST("/:id", gin2.UpdateOperationLogStatus, CreateWorkflowTask) + workflowtask.PUT("/:id", gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) workflowtask.GET("/max/:max/start/:start/pipelines/:name", ListWorkflowTasksResult) workflowtask.GET("/filters/pipelines/:name", GetFiltersPipeline) workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) -- Gitee From 6d10c97a2f9c6b0f75872d32a61f24dc35fd3758 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 29 Mar 2022 11:51:34 +0800 Subject: [PATCH 1959/2647] support updating service components Signed-off-by: zhangyifei --- .../core/environment/service/environment.go | 23 +++++- .../aslan/core/environment/service/k8s.go | 25 +++++- .../core/environment/service/revision.go | 3 + .../aslan/core/environment/service/service.go | 13 +++- .../core/environment/service/share_env.go | 77 +++++++++++++++++-- .../environment/service/share_env_utils.go | 45 +++++++++++ 6 files changed, 175 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 3638b606..16c12116 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -475,6 +475,16 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. return e.ErrUpdateEnv.AddErr(err) } + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), existedProd.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), existedProd.ClusterID) if err != nil { log.Errorf("[%s][%s] error: %v", envName, namespace, err) @@ -521,6 +531,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. updateProd.Status = setting.ProductStatusUpdating updateProd.Services = updatedServices + updateProd.ShareEnv = existedProd.ShareEnv log.Infof("[Namespace:%s][Product:%s]: update service orchestration in product. Status: %s", envName, productName, updateProd.Status) if err = commonrepo.NewProductColl().Update(updateProd); err != nil { @@ -579,7 +590,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. updateProd, service, existedServices[service.ServiceName], - renderSet, inf, kubeClient, log) + renderSet, inf, kubeClient, istioClient, log) if err != nil { lock.Lock() switch e := err.(type) { @@ -695,7 +706,7 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames } if project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { - err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, false, kubeClient, log) + err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, exitedProd.ShareEnv.Enable, kubeClient, log) if err != nil { log.Errorf("[%s][P:%s] service.UpdateProductV2 create kubeEnv error: %v", envName, productName, err) @@ -2107,7 +2118,7 @@ func getProjectType(productName string) string { // upsertService 创建或者更新服务, 更新服务之前先创建服务需要的配置 func upsertService(isUpdate bool, env *commonmodels.Product, service *commonmodels.ProductService, prevSvc *commonmodels.ProductService, - renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger, + renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, istioClient versionedclient.Interface, log *zap.SugaredLogger, ) ([]*unstructured.Unstructured, error) { errList := &multierror.Error{ ErrorFormat: func(es []error) string { @@ -2209,6 +2220,12 @@ func upsertService(isUpdate bool, env *commonmodels.Product, continue } + err = EnsureUpdateZadigService(context.TODO(), env, u.GetName(), kubeClient, istioClient) + if err != nil { + log.Errorf("Failed to update Zadig service %s for env %s of product %s: %s", u.GetName(), env.EnvName, env.ProductName, err) + errList = multierror.Append(errList, err) + continue + } case setting.Deployment, setting.StatefulSet: // compatibility flag, We add a match label in spec.selector field pre 1.10. needSelectorLabel := false diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 8b02e2a9..69931361 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -25,6 +25,7 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" + versionedclient "istio.io/client-go/pkg/clientset/versioned" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" @@ -90,6 +91,16 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { return e.ErrUpdateEnv.AddErr(err) } + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), exitedProd.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), exitedProd.ClusterID) if err != nil { return e.ErrUpdateEnv.AddDesc(err.Error()) @@ -122,7 +133,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { exitedProd, svc, exitedProd.GetServiceMap()[svc.ServiceName], - newRender, inf, kubeClient, k.log) + newRender, inf, kubeClient, istioClient, k.log) // 如果创建依赖服务组有返回错误, 停止等待 if err != nil { @@ -221,6 +232,16 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] errList = multierror.Append(errList, err) } + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), prod.ClusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to new istio client: %s", err) + } + var wg sync.WaitGroup var lock sync.Mutex var resources []*unstructured.Unstructured @@ -232,7 +253,7 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] updatableServiceNameList = append(updatableServiceNameList, group[i].ServiceName) go func(svc *commonmodels.ProductService) { defer wg.Done() - items, err := upsertService(false, prod, svc, nil, renderSet, informer, kubeClient, k.log) + items, err := upsertService(false, prod, svc, nil, renderSet, informer, kubeClient, istioClient, k.log) if err != nil { lock.Lock() switch e := err.(type) { diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 48e38310..3c7ba34b 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -32,6 +32,9 @@ import ( func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) (prodRevs []*ProductRevision, err error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName, IsSortByProductName: true, EnvName: envName, ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusUnknown}}) + if err != nil { + return nil, e.ErrListProducts.AddDesc(err.Error()) + } // list services with max revision of project allServiceTmpls, err := getServicesWithMaxRevision(productName) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 26c4a750..0f04557d 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -24,6 +24,7 @@ import ( "github.com/koderover/zadig/pkg/util" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" + versionedclient "istio.io/client-go/pkg/clientset/versioned" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/informers" @@ -383,6 +384,16 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e return err } + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), productObj.ClusterID) + if err != nil { + return err + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return err + } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), productObj.ClusterID) if err != nil { return e.ErrCreateEnv.AddErr(err) @@ -470,7 +481,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e productObj, productService, productService, - newRender, inf, kubeClient, log) + newRender, inf, kubeClient, istioClient, log) // 如果创建依赖服务组有返回错误, 停止等待 if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index be245bfb..0722dbb9 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -183,7 +183,13 @@ func DisableBaseEnv(ctx context.Context, envName, productName string) error { } // 5. Restart the istio-Proxy injected Pods. - return removePodsIstioProxy(ctx, kclient, ns) + err = removePodsIstioProxy(ctx, kclient, ns) + if err != nil { + return fmt.Errorf("failed to remove istio-proxy from pods in ns `%s`: %s", ns, err) + } + + // 6. Update the environment configuration. + return ensureDisableBaseEnvConfig(ctx, prod) } func CheckShareEnvReady(ctx context.Context, envName, op, productName string) (*ShareEnvReady, error) { @@ -913,12 +919,13 @@ func ensureWorkloadsVirtualServiceInGrayAndBase(ctx context.Context, env *common } func ensureVirtualServiceInGray(ctx context.Context, envName, vsName, svcName, grayNS, baseNS string, istioClient versionedclient.Interface) error { + var isExisted bool + vsObjInGray, err := istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Get(ctx, vsName, metav1.GetOptions{}) if err == nil { - return nil + isExisted = true } - - if !apierrors.IsNotFound(err) { + if err != nil && !apierrors.IsNotFound(err) { return err } @@ -965,7 +972,12 @@ func ensureVirtualServiceInGray(ctx context.Context, envName, vsName, svcName, g }, } - _, err = istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Create(ctx, vsObjInGray, metav1.CreateOptions{}) + if isExisted { + _, err = istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Update(ctx, vsObjInGray, metav1.UpdateOptions{}) + } else { + _, err = istioClient.NetworkingV1alpha3().VirtualServices(grayNS).Create(ctx, vsObjInGray, metav1.CreateOptions{}) + } + return err } @@ -1044,3 +1056,58 @@ func EnsureDeleteShareEnvConfig(ctx context.Context, env *commonmodels.Product, // Updated the VirtualService configuration in the base environment. return ensureCleanRoutesInBase(ctx, env, istioClient) } + +func EnsureUpdateZadigService(ctx context.Context, env *commonmodels.Product, svcName string, kclient client.Client, istioClient versionedclient.Interface) error { + if !env.ShareEnv.Enable { + return nil + } + + // Note: A Service may not be queried immediately after it is created. + var err error + svc := &corev1.Service{} + for i := 0; i < 3; i++ { + err = kclient.Get(ctx, client.ObjectKey{ + Name: svcName, + Namespace: env.Namespace, + }, svc) + if err == nil { + break + } + + log.Warnf("Failed to query Service %s in ns %s: %s", svcName, env.Namespace, err) + time.Sleep(1 * time.Second) + } + if err != nil { + return fmt.Errorf("failed to query Service %s in ns %s: %s", svcName, env.Namespace, err) + } + + vsName := genVirtualServiceName(svc) + + if env.ShareEnv.IsBase { + // 1. Create VirtualService in the base environment. + err = ensureVirtualService(ctx, istioClient, env.Namespace, vsName, svcName) + if err != nil { + return err + } + + // 2. Create VirtualService in all of the sub environments. + return ensureServicesInAllSubEnvs(ctx, env, svc, kclient, istioClient) + } + + baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: env.ProductName, + EnvName: env.ShareEnv.BaseEnv, + }) + if err != nil { + return err + } + + // 1. Create VirtualService in the sub environment. + err = ensureVirtualServiceInGray(ctx, env.EnvName, vsName, svcName, env.Namespace, baseEnv.Namespace, istioClient) + if err != nil { + return err + } + + // 2. Updated the VirtualService configuration in the base environment. + return ensureUpdateVirtualServiceInBase(ctx, env.EnvName, vsName, svcName, env.Namespace, baseEnv.Namespace, istioClient) +} diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 803ab145..92fb63a5 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -23,8 +23,10 @@ import ( "go.uber.org/zap" networkingv1alpha3 "istio.io/api/networking/v1alpha3" versionedclient "istio.io/client-go/pkg/clientset/versioned" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -44,6 +46,19 @@ func ensureBaseEnvConfig(ctx context.Context, baseEnv *commonmodels.Product) err return commonrepo.NewProductColl().Update(baseEnv) } +func ensureDisableBaseEnvConfig(ctx context.Context, baseEnv *commonmodels.Product) error { + if !baseEnv.ShareEnv.Enable && !baseEnv.ShareEnv.IsBase { + return nil + } + + baseEnv.ShareEnv = commonmodels.ProductShareEnv{ + Enable: false, + IsBase: false, + } + + return commonrepo.NewProductColl().Update(baseEnv) +} + func ensureDeleteAssociatedEnvs(ctx context.Context, baseEnvName string) error { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ ShareEnvEnable: getBoolPointer(true), @@ -197,3 +212,33 @@ func ensureCleanRouteInBase(ctx context.Context, envName, baseNS, vsName string, _, err = istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Update(ctx, baseVS, metav1.UpdateOptions{}) return err } + +func ensureServicesInAllSubEnvs(ctx context.Context, env *commonmodels.Product, svc *corev1.Service, kclient client.Client, istioClient versionedclient.Interface) error { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: env.ProductName, + ClusterID: env.ClusterID, + ShareEnvEnable: getBoolPointer(true), + ShareEnvIsBase: getBoolPointer(false), + ShareEnvBaseEnv: getStrPointer(env.EnvName), + }) + if err != nil { + return err + } + + vsName := genVirtualServiceName(svc) + for _, env := range envs { + log.Infof("Begin to ensure Services in subenv %s of prouduct %s.", env.EnvName, env.ProductName) + + err = ensureVirtualService(ctx, istioClient, env.Namespace, vsName, svc.Name) + if err != nil { + return err + } + + err = ensureDefaultK8sServiceInGray(ctx, svc, env.Namespace, kclient) + if err != nil { + return err + } + } + + return nil +} -- Gitee From ae7baff1d0384bbdf8c71d6a37179e80a675da13 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 12:14:44 +0800 Subject: [PATCH 1960/2647] create gitee webhook Signed-off-by: liu deyi --- pkg/microservice/aslan/core/common/service/workflow.go | 4 ++-- .../systemconfig/core/codehost/service/codehost.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 60fd9ded..99583112 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -182,7 +182,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger } switch ch.Type { - case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: + case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromGitee: err = webhook.NewClient().RemoveWebHook(&webhook.TaskOption{ Name: wh.name, Owner: wh.owner, @@ -217,7 +217,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger } switch ch.Type { - case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub: + case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromGitee: err = webhook.NewClient().AddWebHook(&webhook.TaskOption{ Name: wh.name, Owner: wh.owner, diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 504aa769..654c5453 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -174,7 +174,7 @@ func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*o TokenURL: address + "/oauth/token", }), nil case systemconfig.GiteeProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "groups", "hook", "enterprises"}, oauth2.Endpoint{ + return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "hook"}, oauth2.Endpoint{ AuthURL: address + "/oauth/authorize", TokenURL: address + "/oauth/token", }), nil -- Gitee From 00b7a9b331445440eeca75f098b263978f572176 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 29 Mar 2022 12:22:04 +0800 Subject: [PATCH 1961/2647] bug Signed-off-by: mouuii --- .../aslan/core/workflow/handler/policy.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 9fb739e9..933f1dc1 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -96,8 +96,18 @@ rules: rules: - method: POST endpoint: "/api/aslan/workflow/workflowtask/?*" + idRegex: "/api/aslan/workflow/workflowtask/([\\w\\W]+?)$" + resourceType: "Workflow" + matchAttributes: + - key: "placeholder" + value: "placeholder" - method: PUT endpoint: "/api/aslan/workflow/workflowtask/?*" + idRegex: "/api/aslan/workflow/workflowtask/([\\w\\W]+?)$" + resourceType: "Workflow" + matchAttributes: + - key: "placeholder" + value: "placeholder" - method: POST endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" - method: DELETE -- Gitee From 57a8bdbe37fa75b55bded8d3fc1dbb6616d5345d Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 29 Mar 2022 14:17:22 +0800 Subject: [PATCH 1962/2647] optimize Signed-off-by: panxunying --- .../aslan/core/service/handler/service.go | 13 +--- .../aslan/core/service/service/service.go | 73 +++++++++++-------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 33fba076..06a2fa0d 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -166,15 +166,6 @@ func ListServicePort(c *gin.Context) { ctx.Resp, ctx.Err = svcservice.ListServicePort(c.Param("name"), c.Param("type"), c.Query("projectName"), setting.ProductStatusDeleting, revision, ctx.Logger) } -type K8sWorkloadsArgs struct { - WorkLoads []commonmodels.Workload `bson:"workLoads" json:"workLoads"` - EnvName string `bson:"env_name" json:"env_name"` - ClusterID string `bson:"cluster_id" json:"cluster_id"` - Namespace string `bson:"namespace" json:"namespace"` - ProductName string `bson:"product_name" json:"product_name"` - RegistryID string `json:"registry_id"` -} - func UpdateWorkloads(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -196,14 +187,14 @@ func UpdateWorkloads(c *gin.Context) { func CreateK8sWorkloads(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(K8sWorkloadsArgs) + args := new(svcservice.K8sWorkloadsArgs) err := c.BindJSON(args) if err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid K8sWorkloadsArgs args") return } - ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args.RegistryID, args.ProductName, args.WorkLoads, args.ClusterID, args.Namespace, args.EnvName, ctx.Logger) + ctx.Err = svcservice.CreateK8sWorkLoads(c, ctx.RequestID, ctx.UserName, args, ctx.Logger) } func ListAvailablePublicServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c2a7e9b2..5c28ba85 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -255,16 +255,25 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv return serviceOption, nil } -func CreateK8sWorkLoads(ctx context.Context, requestID, username, registryID string, productName string, workLoads []commonmodels.Workload, clusterID, namespace string, envName string, log *zap.SugaredLogger) error { - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) +type K8sWorkloadsArgs struct { + WorkLoads []commonmodels.Workload `json:"workLoads"` + EnvName string `json:"env_name"` + ClusterID string `json:"cluster_id"` + Namespace string `json:"namespace"` + ProductName string `json:"product_name"` + RegistryID string `json:"registry_id"` +} + +func CreateK8sWorkLoads(ctx context.Context, requestID, userName string, args *K8sWorkloadsArgs, log *zap.SugaredLogger) error { + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), args.ClusterID) if err != nil { - log.Errorf("[%s] error: %v", namespace, err) + log.Errorf("[%s] error: %v", args.Namespace, err) return err } // 检查环境是否存在,envName和productName唯一 - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + opt := &commonrepo.ProductFindOptions{Name: args.ProductName, EnvName: args.EnvName} if _, err := commonrepo.NewProductColl().Find(opt); err == nil { - log.Errorf("[%s][P:%s] duplicate envName in the same project", envName, productName) + log.Errorf("[%s][P:%s] duplicate envName in the same project", args.EnvName, args.ProductName) return e.ErrCreateEnv.AddDesc(e.DuplicateEnvErrMsg) } @@ -275,7 +284,7 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username, registryID str ) serviceString := sets.NewString() - services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, "") + services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(args.ProductName, "") for _, v := range services { serviceString.Insert(v.ServiceName) } @@ -286,26 +295,26 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username, registryID str //} g := new(errgroup.Group) - for _, workload := range workLoads { + for _, workload := range args.WorkLoads { tempWorkload := workload g.Go(func() error { // If the service is already included in the database service template, add it to the new association table if serviceString.Has(tempWorkload.Name) { return commonrepo.NewServicesInExternalEnvColl().Create(&commonmodels.ServicesInExternalEnv{ - ProductName: productName, + ProductName: args.ProductName, ServiceName: tempWorkload.Name, - EnvName: envName, - Namespace: namespace, - ClusterID: clusterID, + EnvName: args.EnvName, + Namespace: args.Namespace, + ClusterID: args.ClusterID, }) } var bs []byte switch tempWorkload.Type { case setting.Deployment: - bs, _, err = getter.GetDeploymentYaml(namespace, tempWorkload.Name, kubeClient) + bs, _, err = getter.GetDeploymentYaml(args.Namespace, tempWorkload.Name, kubeClient) case setting.StatefulSet: - bs, _, err = getter.GetStatefulSetYaml(namespace, tempWorkload.Name, kubeClient) + bs, _, err = getter.GetStatefulSetYaml(args.Namespace, tempWorkload.Name, kubeClient) } if len(bs) == 0 || err != nil { @@ -316,21 +325,21 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username, registryID str mu.Lock() defer mu.Unlock() workloadsTmp = append(workloadsTmp, commonmodels.Workload{ - EnvName: envName, + EnvName: args.EnvName, Name: tempWorkload.Name, Type: tempWorkload.Type, - ProductName: productName, + ProductName: args.ProductName, }) - return CreateWorkloadTemplate(username, &commonmodels.Service{ + return CreateWorkloadTemplate(userName, &commonmodels.Service{ ServiceName: tempWorkload.Name, Yaml: string(bs), - ProductName: productName, - CreateBy: username, + ProductName: args.ProductName, + CreateBy: userName, Type: setting.K8SDeployType, WorkloadType: tempWorkload.Type, Source: setting.SourceFromExternal, - EnvName: envName, + EnvName: args.EnvName, Revision: 1, }, log) }) @@ -341,33 +350,33 @@ func CreateK8sWorkLoads(ctx context.Context, requestID, username, registryID str // 没有环境,创建环境 if _, err = commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: productName, - EnvName: envName, + Name: args.ProductName, + EnvName: args.EnvName, }); err != nil { - if err := service.CreateProduct(username, requestID, &commonmodels.Product{ - ProductName: productName, + if err := service.CreateProduct(userName, requestID, &commonmodels.Product{ + ProductName: args.ProductName, Source: setting.SourceFromExternal, - ClusterID: clusterID, - RegistryID: registryID, - EnvName: envName, - Namespace: namespace, - UpdateBy: username, + ClusterID: args.ClusterID, + RegistryID: args.RegistryID, + EnvName: args.EnvName, + Namespace: args.Namespace, + UpdateBy: userName, }, log); err != nil { return e.ErrCreateProduct.AddDesc("create product Error for unknown reason") } } - workLoadStat, err := commonrepo.NewWorkLoadsStatColl().Find(clusterID, namespace) + workLoadStat, err := commonrepo.NewWorkLoadsStatColl().Find(args.ClusterID, args.Namespace) if err != nil { workLoadStat = &commonmodels.WorkloadStat{ - ClusterID: clusterID, - Namespace: namespace, + ClusterID: args.ClusterID, + Namespace: args.Namespace, Workloads: workloadsTmp, } return commonrepo.NewWorkLoadsStatColl().Create(workLoadStat) } - workLoadStat.Workloads = replaceWorkloads(workLoadStat.Workloads, workloadsTmp, envName) + workLoadStat.Workloads = replaceWorkloads(workLoadStat.Workloads, workloadsTmp, args.EnvName) return commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workLoadStat) } -- Gitee From bd804762eb56e892369b1a261583bd1b099d633c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 15:04:59 +0800 Subject: [PATCH 1963/2647] optimize code Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 2 +- .../aslan/core/code/client/open/open.go | 28 ++++++++--------- .../aslan/core/common/service/gitee/client.go | 14 +++++++++ .../core/common/service/gitee/webhook.go | 30 +++++++++++++++++++ .../core/common/service/webhook/client.go | 4 +++ .../core/common/service/webhook/controller.go | 5 ++++ .../aslan/core/common/service/workflow.go | 2 ++ .../service/workflow/pipeline_validation.go | 30 +++++++++---------- pkg/tool/gitee/client.go | 21 ++++++++++++- 9 files changed, 104 insertions(+), 32 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/gitee/client.go create mode 100644 pkg/microservice/aslan/core/common/service/gitee/webhook.go diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index fea7a956..4adeaec1 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -23,7 +23,7 @@ type Client struct { } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - client, _ := gitee.NewClient(c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) + client, _ := gitee.NewClient(id, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) return &Client{ Client: client, AccessToken: c.AccessToken, diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 1f7de459..72263c83 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -19,7 +19,6 @@ package open import ( "encoding/json" "fmt" - "time" "go.uber.org/zap" @@ -31,7 +30,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/gitlab" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - giteeClient "github.com/koderover/zadig/pkg/tool/gitee" ) type ClientConfig interface { @@ -53,19 +51,19 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, } // The normal expiration time is 86400 - if ch.Type == setting.SourceFromGitee && (time.Now().Unix()-ch.UpdatedAt) >= 86000 { - accessToken, err := giteeClient.RefreshAccessToken(ch.RefreshToken) - if err == nil { - ch.AccessToken = accessToken.AccessToken - ch.RefreshToken = accessToken.RefreshToken - ch.UpdatedAt = int64(accessToken.CreatedAt) - if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - return nil, fmt.Errorf("failed to update codehost,err:%s", err) - } - } else { - log.Errorf("failed to refreshAccessToken,err:%s", err) - } - } + //if ch.Type == setting.SourceFromGitee && (time.Now().Unix()-ch.UpdatedAt) >= 86000 { + // accessToken, err := giteeClient.RefreshAccessToken(ch.RefreshToken) + // if err == nil { + // ch.AccessToken = accessToken.AccessToken + // ch.RefreshToken = accessToken.RefreshToken + // ch.UpdatedAt = int64(accessToken.CreatedAt) + // if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { + // return nil, fmt.Errorf("failed to update codehost,err:%s", err) + // } + // } else { + // log.Errorf("failed to refreshAccessToken,err:%s", err) + // } + //} var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] diff --git a/pkg/microservice/aslan/core/common/service/gitee/client.go b/pkg/microservice/aslan/core/common/service/gitee/client.go new file mode 100644 index 00000000..e39482a5 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/gitee/client.go @@ -0,0 +1,14 @@ +package gitee + +import "github.com/koderover/zadig/pkg/tool/gitee" + +type Client struct { + *gitee.Client +} + +func NewClient(id int, accessToken, proxyAddress string, enableProxy bool) *Client { + client, _ := gitee.NewClient(id, accessToken, proxyAddress, enableProxy) + return &Client{ + Client: client, + } +} diff --git a/pkg/microservice/aslan/core/common/service/gitee/webhook.go b/pkg/microservice/aslan/core/common/service/gitee/webhook.go new file mode 100644 index 00000000..61f34ad6 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/gitee/webhook.go @@ -0,0 +1,30 @@ +package gitee + +import ( + "context" + "strconv" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" + "github.com/koderover/zadig/pkg/tool/git" +) + +func (c *Client) CreateWebHook(owner, repo string) (string, error) { + hook, err := c.CreateHook(context.TODO(), owner, repo, &git.Hook{ + URL: config.WebHookURL(), + Secret: gitservice.GetHookSecret(), + }) + if err != nil { + return "", err + } + + return hook.Id, nil +} + +func (c *Client) DeleteWebHook(owner, repo, hookID string) error { + hookIDInt, err := strconv.ParseInt(hookID, 10, 64) + if err != nil { + return err + } + return c.DeleteHook(context.TODO(), owner, repo, hookIDInt) +} diff --git a/pkg/microservice/aslan/core/common/service/webhook/client.go b/pkg/microservice/aslan/core/common/service/webhook/client.go index e5558ba8..185c27c7 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/client.go +++ b/pkg/microservice/aslan/core/common/service/webhook/client.go @@ -42,6 +42,7 @@ func NewClient() *client { } type task struct { + ID int owner, repo, address, token, ref, ak, sk, region string from string add, enableProxy bool @@ -50,6 +51,7 @@ type task struct { } type TaskOption struct { + ID int Name string Owner string Repo string @@ -69,6 +71,7 @@ func (c *client) AddWebHook(taskOption *TaskOption) error { } t := &task{ + ID: taskOption.ID, owner: taskOption.Owner, repo: taskOption.Repo, address: taskOption.Address, @@ -104,6 +107,7 @@ func (c *client) RemoveWebHook(taskOption *TaskOption) error { } t := &task{ + ID: taskOption.ID, owner: taskOption.Owner, repo: taskOption.Repo, address: taskOption.Address, diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index a41d03cb..ab0d3d1b 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -28,6 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitee" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitlab" codehostdb "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" @@ -165,6 +166,8 @@ func removeWebhook(t *task, logger *zap.Logger) { } case setting.SourceFromCodeHub: cl = codehub.NewClient(t.ak, t.sk, t.region, config.ProxyHTTPSAddr(), t.enableProxy) + case setting.SourceFromGitee: + cl = gitee.NewClient(t.ID, t.token, config.ProxyHTTPSAddr(), t.enableProxy) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} @@ -237,6 +240,8 @@ func addWebhook(t *task, logger *zap.Logger) { case setting.SourceFromCodeHub: cl = codehub.NewClient(t.ak, t.sk, t.region, config.ProxyHTTPSAddr(), t.enableProxy) + case setting.SourceFromGitee: + cl = gitee.NewClient(t.ID, t.token, config.ProxyHTTPSAddr(), t.enableProxy) default: t.err = fmt.Errorf("invaild source: %s", t.from) t.doneCh <- struct{}{} diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 99583112..5a5a2197 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -184,6 +184,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger switch ch.Type { case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromGitee: err = webhook.NewClient().RemoveWebHook(&webhook.TaskOption{ + ID: ch.ID, Name: wh.name, Owner: wh.owner, Repo: wh.repo, @@ -219,6 +220,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger switch ch.Type { case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromGitee: err = webhook.NewClient().AddWebHook(&webhook.TaskOption{ + ID: ch.ID, Name: wh.name, Owner: wh.owner, Repo: wh.repo, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 5caf3676..ebe7c314 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -358,21 +358,21 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } else if codeHostInfo.Type == systemconfig.GiteeProvider { // The normal expiration time is 86400 - if codeHostInfo.Type == setting.SourceFromGitee && (time.Now().Unix()-codeHostInfo.UpdatedAt) >= 86000 { - accessToken, _ := gitee.RefreshAccessToken(codeHostInfo.RefreshToken) - if err == nil { - codeHostInfo.AccessToken = accessToken.AccessToken - codeHostInfo.RefreshToken = accessToken.RefreshToken - codeHostInfo.UpdatedAt = int64(accessToken.CreatedAt) - if err = systemconfig.New().UpdateCodeHost(codeHostInfo.ID, codeHostInfo); err != nil { - log.Errorf("failed to update codehost err:%s", err) - return - } - } else { - log.Errorf("failed to refreshAccessToken,err:%s", err) - } - } - gitCli, _ := gitee.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) + //if codeHostInfo.Type == setting.SourceFromGitee && (time.Now().Unix()-codeHostInfo.UpdatedAt) >= 86000 { + // accessToken, _ := gitee.RefreshAccessToken(codeHostInfo.RefreshToken) + // if err == nil { + // codeHostInfo.AccessToken = accessToken.AccessToken + // codeHostInfo.RefreshToken = accessToken.RefreshToken + // codeHostInfo.UpdatedAt = int64(accessToken.CreatedAt) + // if err = systemconfig.New().UpdateCodeHost(codeHostInfo.ID, codeHostInfo); err != nil { + // log.Errorf("failed to update codehost err:%s", err) + // return + // } + // } else { + // log.Errorf("failed to refreshAccessToken,err:%s", err) + // } + //} + gitCli, _ := gitee.NewClient(codeHostInfo.ID, codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) if build.CommitID == "" { if build.Tag != "" && build.PR == 0 { tags, err := gitCli.ListTags(context.Background(), codeHostInfo.AccessToken, build.RepoOwner, build.RepoName) diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index cd35b4c0..cb654d38 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -2,18 +2,22 @@ package gitee import ( "context" + "fmt" "net/http" "net/url" + "time" "gitee.com/openeuler/go-gitee/gitee" "golang.org/x/oauth2" + + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) type Client struct { *gitee.APIClient } -func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) { +func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { var ( client *gitee.APIClient HttpClient *http.Client @@ -33,6 +37,21 @@ func NewClient(accessToken, proxyAddr string, enableProxy bool) (*Client, error) } if accessToken != "" { + ch, err := systemconfig.New().GetCodeHost(id) + // The normal expiration time is 86400 + if err == nil && (time.Now().Unix()-ch.UpdatedAt) >= 86000 { + token, err := RefreshAccessToken(ch.RefreshToken) + if err == nil { + accessToken = token.AccessToken + ch.AccessToken = token.AccessToken + ch.RefreshToken = token.RefreshToken + ch.UpdatedAt = int64(token.CreatedAt) + if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { + return nil, fmt.Errorf("failed to update codehost,err:%s", err) + } + } + } + ctx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: accessToken}, -- Gitee From 58cd10aa787297a373339d9f41e7a181317462cf Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 29 Mar 2022 15:17:32 +0800 Subject: [PATCH 1964/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 933f1dc1..1b0621ee 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -98,16 +98,10 @@ rules: endpoint: "/api/aslan/workflow/workflowtask/?*" idRegex: "/api/aslan/workflow/workflowtask/([\\w\\W]+?)$" resourceType: "Workflow" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: PUT endpoint: "/api/aslan/workflow/workflowtask/?*" idRegex: "/api/aslan/workflow/workflowtask/([\\w\\W]+?)$" resourceType: "Workflow" - matchAttributes: - - key: "placeholder" - value: "placeholder" - method: POST endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" - method: DELETE -- Gitee From fc7585c44ebf510323787fc17fcb9c727f432116 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 16:14:29 +0800 Subject: [PATCH 1965/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/code/client/open/open.go | 15 --------------- .../service/workflow/pipeline_validation.go | 15 --------------- pkg/tool/gitee/repositories.go | 5 +++++ 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 72263c83..27f6ec3a 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -50,21 +50,6 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, return nil, err } - // The normal expiration time is 86400 - //if ch.Type == setting.SourceFromGitee && (time.Now().Unix()-ch.UpdatedAt) >= 86000 { - // accessToken, err := giteeClient.RefreshAccessToken(ch.RefreshToken) - // if err == nil { - // ch.AccessToken = accessToken.AccessToken - // ch.RefreshToken = accessToken.RefreshToken - // ch.UpdatedAt = int64(accessToken.CreatedAt) - // if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - // return nil, fmt.Errorf("failed to update codehost,err:%s", err) - // } - // } else { - // log.Errorf("failed to refreshAccessToken,err:%s", err) - // } - //} - var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] if !ok { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index ebe7c314..239d8614 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -357,21 +357,6 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } } else if codeHostInfo.Type == systemconfig.GiteeProvider { - // The normal expiration time is 86400 - //if codeHostInfo.Type == setting.SourceFromGitee && (time.Now().Unix()-codeHostInfo.UpdatedAt) >= 86000 { - // accessToken, _ := gitee.RefreshAccessToken(codeHostInfo.RefreshToken) - // if err == nil { - // codeHostInfo.AccessToken = accessToken.AccessToken - // codeHostInfo.RefreshToken = accessToken.RefreshToken - // codeHostInfo.UpdatedAt = int64(accessToken.CreatedAt) - // if err = systemconfig.New().UpdateCodeHost(codeHostInfo.ID, codeHostInfo); err != nil { - // log.Errorf("failed to update codehost err:%s", err) - // return - // } - // } else { - // log.Errorf("failed to refreshAccessToken,err:%s", err) - // } - //} gitCli, _ := gitee.NewClient(codeHostInfo.ID, codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) if build.CommitID == "" { if build.Tag != "" && build.PR == 0 { diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 3dd02986..d3e767d0 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -59,6 +59,10 @@ func (c *Client) DeleteHook(ctx context.Context, owner, repo string, id int64) e } func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { + fmt.Println(fmt.Sprintf("owner:%s", owner)) + fmt.Println(fmt.Sprintf("repo:%s", repo)) + fmt.Println(fmt.Sprintf("hook.secret:%s", hook.Secret)) + fmt.Println(fmt.Sprintf("hook.url:%s", hook.URL)) resp, _, err := c.WebhooksApi.PostV5ReposOwnerRepoHooks(ctx, owner, repo, hook.URL, &gitee.PostV5ReposOwnerRepoHooksOpts{ Password: optional.NewString(hook.Secret), PushEvents: optional.NewBool(true), @@ -66,6 +70,7 @@ func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.H MergeRequestsEvents: optional.NewBool(true), }) if err != nil { + fmt.Println(fmt.Sprintf("PostV5ReposOwnerRepoHooks err:%+v", err)) return gitee.Hook{}, err } -- Gitee From c7eadfa3969529fea1e844ce6379916810801f32 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 29 Mar 2022 16:47:23 +0800 Subject: [PATCH 1966/2647] support share env info for listing envs Signed-off-by: zhangyifei --- .../core/environment/service/environment.go | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 16c12116..bf55b802 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -106,6 +106,11 @@ type EnvResp struct { BaseRefs []string `json:"base_refs"` BaseName string `json:"base_name"` IsExisted bool `json:"is_existed"` + + // New Since v1.10.0 + ShareEnvEnable bool `json:"share_env_enable"` + ShareEnvIsBase bool `json:"share_env_is_base"` + ShareEnvBaseEnv string `json:"share_env_base_env"` } type ProductResp struct { @@ -222,26 +227,27 @@ func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) } var baseRefs []string if cmSet, ok := envCMMap[collaboration.BuildEnvCMMapKey(env.ProductName, env.EnvName)]; ok { - for _, cm := range cmSet.List() { - baseRefs = append(baseRefs, cm) - } + baseRefs = append(baseRefs, cmSet.List()...) } res = append(res, &EnvResp{ - ProjectName: projectName, - Name: env.EnvName, - IsPublic: env.IsPublic, - IsExisted: env.IsExisted, - ClusterName: clusterName, - Source: env.Source, - Production: production, - Status: env.Status, - Error: env.Error, - UpdateTime: env.UpdateTime, - UpdateBy: env.UpdateBy, - RegistryID: env.RegistryID, - ClusterID: env.ClusterID, - BaseRefs: baseRefs, - BaseName: env.BaseName, + ProjectName: projectName, + Name: env.EnvName, + IsPublic: env.IsPublic, + IsExisted: env.IsExisted, + ClusterName: clusterName, + Source: env.Source, + Production: production, + Status: env.Status, + Error: env.Error, + UpdateTime: env.UpdateTime, + UpdateBy: env.UpdateBy, + RegistryID: env.RegistryID, + ClusterID: env.ClusterID, + BaseRefs: baseRefs, + BaseName: env.BaseName, + ShareEnvEnable: env.ShareEnv.Enable, + ShareEnvIsBase: env.ShareEnv.IsBase, + ShareEnvBaseEnv: env.ShareEnv.BaseEnv, }) } -- Gitee From fb1be0efaf5b469f7a390a7ae7130b293fd5f6e5 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 16:55:29 +0800 Subject: [PATCH 1967/2647] debug code Signed-off-by: liu deyi --- .../aslan/core/common/service/gitee/client.go | 4 +- .../core/common/service/gitee/webhook.go | 2 +- pkg/tool/gitee/repositories.go | 46 +++++++++++++------ 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/gitee/client.go b/pkg/microservice/aslan/core/common/service/gitee/client.go index e39482a5..e29f94c4 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/client.go +++ b/pkg/microservice/aslan/core/common/service/gitee/client.go @@ -4,11 +4,13 @@ import "github.com/koderover/zadig/pkg/tool/gitee" type Client struct { *gitee.Client + AccessToken string } func NewClient(id int, accessToken, proxyAddress string, enableProxy bool) *Client { client, _ := gitee.NewClient(id, accessToken, proxyAddress, enableProxy) return &Client{ - Client: client, + Client: client, + AccessToken: accessToken, } } diff --git a/pkg/microservice/aslan/core/common/service/gitee/webhook.go b/pkg/microservice/aslan/core/common/service/gitee/webhook.go index 61f34ad6..1b0c179e 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitee/webhook.go @@ -10,7 +10,7 @@ import ( ) func (c *Client) CreateWebHook(owner, repo string) (string, error) { - hook, err := c.CreateHook(context.TODO(), owner, repo, &git.Hook{ + hook, err := c.CreateHook(c.AccessToken, owner, repo, &git.Hook{ URL: config.WebHookURL(), Secret: gitservice.GetHookSecret(), }) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index d3e767d0..fed9a38e 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -58,23 +58,43 @@ func (c *Client) DeleteHook(ctx context.Context, owner, repo string, id int64) e return nil } -func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { - fmt.Println(fmt.Sprintf("owner:%s", owner)) - fmt.Println(fmt.Sprintf("repo:%s", repo)) - fmt.Println(fmt.Sprintf("hook.secret:%s", hook.Secret)) - fmt.Println(fmt.Sprintf("hook.url:%s", hook.URL)) - resp, _, err := c.WebhooksApi.PostV5ReposOwnerRepoHooks(ctx, owner, repo, hook.URL, &gitee.PostV5ReposOwnerRepoHooksOpts{ - Password: optional.NewString(hook.Secret), - PushEvents: optional.NewBool(true), - TagPushEvents: optional.NewBool(true), - MergeRequestsEvents: optional.NewBool(true), - }) +//func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { +func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (gitee.Hook, error) { + //fmt.Println(fmt.Sprintf("owner:%s", owner)) + //fmt.Println(fmt.Sprintf("repo:%s", repo)) + //fmt.Println(fmt.Sprintf("hook.secret:%s", hook.Secret)) + //fmt.Println(fmt.Sprintf("hook.url:%s", hook.URL)) + //resp, _, err := c.WebhooksApi.PostV5ReposOwnerRepoHooks(ctx, owner, repo, hook.URL, &gitee.PostV5ReposOwnerRepoHooksOpts{ + // Password: optional.NewString(hook.Secret), + // PushEvents: optional.NewBool(true), + // TagPushEvents: optional.NewBool(true), + // MergeRequestsEvents: optional.NewBool(true), + //}) + //if err != nil { + // fmt.Println(fmt.Sprintf("PostV5ReposOwnerRepoHooks err:%+v", err)) + // return gitee.Hook{}, err + //} + // + //return resp, nil + + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := fmt.Sprintf("/v5/repos/%s/%s/hooks", owner, repo) + var hookInfo gitee.Hook + _, err := httpClient.Post(url, httpclient.SetBody(struct { + AccessToken string `json:"access_token"` + URL string `json:"url"` + Password string `json:"password"` + PushEvents string `json:"push_events"` + TagPushEvents string `json:"tag_push_events"` + MergeRequestsEvents string `json:"merge_requests_events"` + }{accessToken, hook.URL, hook.Secret, "true", "true", "true"}), httpclient.SetResult(&hookInfo)) if err != nil { fmt.Println(fmt.Sprintf("PostV5ReposOwnerRepoHooks err:%+v", err)) return gitee.Hook{}, err } - - return resp, nil + return hookInfo, nil } func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, hook *git.Hook) (gitee.Hook, error) { -- Gitee From a8bc5a6222849b185bb2be08a1d71add5bb0f0df Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 17:04:01 +0800 Subject: [PATCH 1968/2647] replace hook model Signed-off-by: liu deyi --- .../core/common/service/gitee/webhook.go | 2 +- pkg/tool/gitee/repositories.go | 38 +++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/gitee/webhook.go b/pkg/microservice/aslan/core/common/service/gitee/webhook.go index 1b0c179e..e9e635e0 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitee/webhook.go @@ -18,7 +18,7 @@ func (c *Client) CreateWebHook(owner, repo string) (string, error) { return "", err } - return hook.Id, nil + return strconv.Itoa(int(hook.ID)), nil } func (c *Client) DeleteWebHook(owner, repo, hookID string) error { diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index fed9a38e..c60ef0ba 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -58,30 +58,28 @@ func (c *Client) DeleteHook(ctx context.Context, owner, repo string, id int64) e return nil } -//func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { -func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (gitee.Hook, error) { - //fmt.Println(fmt.Sprintf("owner:%s", owner)) - //fmt.Println(fmt.Sprintf("repo:%s", repo)) - //fmt.Println(fmt.Sprintf("hook.secret:%s", hook.Secret)) - //fmt.Println(fmt.Sprintf("hook.url:%s", hook.URL)) - //resp, _, err := c.WebhooksApi.PostV5ReposOwnerRepoHooks(ctx, owner, repo, hook.URL, &gitee.PostV5ReposOwnerRepoHooksOpts{ - // Password: optional.NewString(hook.Secret), - // PushEvents: optional.NewBool(true), - // TagPushEvents: optional.NewBool(true), - // MergeRequestsEvents: optional.NewBool(true), - //}) - //if err != nil { - // fmt.Println(fmt.Sprintf("PostV5ReposOwnerRepoHooks err:%+v", err)) - // return gitee.Hook{}, err - //} - // - //return resp, nil +type Hook struct { + ID int `json:"id"` + URL string `json:"url"` + CreatedAt time.Time `json:"created_at"` + Password string `json:"password"` + ProjectID int `json:"project_id"` + Result string `json:"result"` + ResultCode int `json:"result_code"` + PushEvents bool `json:"push_events"` + TagPushEvents bool `json:"tag_push_events"` + IssuesEvents bool `json:"issues_events"` + NoteEvents bool `json:"note_events"` + MergeRequestsEvents bool `json:"merge_requests_events"` +} +//func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { +func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (*Hook, error) { httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) url := fmt.Sprintf("/v5/repos/%s/%s/hooks", owner, repo) - var hookInfo gitee.Hook + var hookInfo *Hook _, err := httpClient.Post(url, httpclient.SetBody(struct { AccessToken string `json:"access_token"` URL string `json:"url"` @@ -92,7 +90,7 @@ func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (gi }{accessToken, hook.URL, hook.Secret, "true", "true", "true"}), httpclient.SetResult(&hookInfo)) if err != nil { fmt.Println(fmt.Sprintf("PostV5ReposOwnerRepoHooks err:%+v", err)) - return gitee.Hook{}, err + return nil, err } return hookInfo, nil } -- Gitee From da7a79f588f0e8b0c107b267fdfbb2351a39e535 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 29 Mar 2022 17:40:44 +0800 Subject: [PATCH 1969/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 5 --- pkg/tool/helmclient/helmclient.go | 41 +++++++++++++------ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 274792be..456c2fb8 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -1393,11 +1393,6 @@ func downloadChart(deliveryVersion *commonmodels.DeliveryVersion, chartInfo *com return "", err } - _, err = hClient.UpdateChartRepo(commonservice.GeneHelmRepo(chartRepo)) - if err != nil { - return "", errors.Wrapf(err, "failed to update helm repo") - } - chartRef := fmt.Sprintf("%s/%s", chartRepo.RepoName, chartInfo.ChartName) return chartTGZFilePath, hClient.DownloadChart(commonservice.GeneHelmRepo(chartRepo), chartRef, chartInfo.ChartVersion, chartTGZFileParent, false) } diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 3f3b5049..b8c1ccf5 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -29,10 +30,10 @@ import ( "path/filepath" "reflect" "strings" + "sync" cm "github.com/chartmuseum/helm-push/pkg/chartmuseum" hc "github.com/mittwald/go-helm-client" - "github.com/pkg/errors" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" @@ -80,6 +81,7 @@ type HelmClient struct { *hc.HelmClient kubeClient client.Client Namespace string + lock *sync.Mutex } // NewClient returns a new Helm client with no construct parameters @@ -99,6 +101,7 @@ func NewClient() (*HelmClient, error) { helmClient, nil, "", + &sync.Mutex{}, }, nil } @@ -131,6 +134,7 @@ func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { helmClient, kubeClient, namespace, + &sync.Mutex{}, }, nil } @@ -153,6 +157,7 @@ func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error helmClient, nil, ns, + &sync.Mutex{}, }, nil } @@ -545,9 +550,15 @@ func (hClient *HelmClient) UpdateChartRepo(repoEntry *repo.Entry) (string, error } chartRepo.CachePath = hClient.Settings.RepositoryCache - // export envionment-variables for ali acr chart repo - os.Setenv("HELM_REPO_USERNAME", repoEntry.Username) - os.Setenv("HELM_REPO_PASSWORD", repoEntry.Password) + repoUrl, err := url.Parse(repoEntry.URL) + if err != nil { + return "", fmt.Errorf("failed to parse repo url: %s, err: %w", repoEntry.URL, err) + } + if repoUrl.Scheme == "acr" { + // export envionment-variables for ali acr chart repo + os.Setenv("HELM_REPO_USERNAME", repoEntry.Username) + os.Setenv("HELM_REPO_PASSWORD", repoEntry.Password) + } // update repo info repoInfo.Update(repoEntry) @@ -568,6 +579,8 @@ func (hClient *HelmClient) UpdateChartRepo(repoEntry *repo.Entry) (string, error // FetchIndexYaml fetch index.yaml from remote chart repo // `helm repo add` and `helm repo update` will be executed func (hClient *HelmClient) FetchIndexYaml(repoEntry *repo.Entry) (*repo.IndexFile, error) { + hClient.lock.Lock() + defer hClient.lock.Unlock() indexFilePath, err := hClient.UpdateChartRepo(repoEntry) if err != nil { return nil, err @@ -578,6 +591,8 @@ func (hClient *HelmClient) FetchIndexYaml(repoEntry *repo.Entry) (*repo.IndexFil } func (hClient *HelmClient) DownloadChart(repoEntry *repo.Entry, chartRef string, chartVersion string, destDir string, unTar bool) error { + hClient.lock.Lock() + defer hClient.lock.Unlock() _, err := hClient.UpdateChartRepo(repoEntry) if err != nil { return nil @@ -603,9 +618,8 @@ func (hClient *HelmClient) pushAcrChart(repoEntry *repo.Entry, chartPath string) prog.Stdout = buf prog.Stderr = os.Stderr if err := prog.Run(); err != nil { - if eerr, ok := err.(*exec.ExitError); ok { - os.Stderr.Write(eerr.Stderr) - return fmt.Errorf("plugin exited with error") + if eErr, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("plugin exited with error: %s", string(eErr.Stderr)) } return err } @@ -623,13 +637,14 @@ func (hClient *HelmClient) pushChartMuseum(repoEntry *repo.Entry, chartPath stri } resp, err := chartClient.UploadChartPackage(chartPath, false) if err != nil { - return errors.Wrapf(err, "failed to prepare pushing chart: %s", chartPath) + return fmt.Errorf("failed to prepare pushing chart: %s, error: %w", chartPath, err) + } defer resp.Body.Close() err = handlePushResponse(resp) if err != nil { - return errors.Wrapf(err, "failed to push chart: %s ", chartPath) + return fmt.Errorf("failed to push chart: %s, error: %w", chartPath, err) } return nil } @@ -640,9 +655,9 @@ func getChartmuseumError(b []byte, code int) error { } err := json.Unmarshal(b, &er) if err != nil || er.Error == "" { - return errors.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) + return fmt.Errorf("%d: could not properly parse response JSON: %s", code, string(b)) } - return errors.Errorf("%d: %s", code, er.Error) + return fmt.Errorf("chart museum errCode: %d, err: %s", code, er.Error) } func handlePushResponse(resp *http.Response) error { @@ -658,13 +673,15 @@ func handlePushResponse(resp *http.Response) error { } func (hClient *HelmClient) PushChart(repoEntry *repo.Entry, chartPath string) error { + hClient.lock.Lock() + defer hClient.lock.Unlock() _, err := hClient.UpdateChartRepo(repoEntry) if err != nil { return nil } repoUrl, err := url.Parse(repoEntry.URL) if err != nil { - return errors.Wrapf(err, "failed to parse repo url: %s", repoEntry.URL) + return fmt.Errorf("failed to parse repo url: %s, err: %w", repoEntry.URL, err) } if repoUrl.Scheme == "acr" { return hClient.pushAcrChart(repoEntry, chartPath) -- Gitee From cdc34fa2124f9c1b888054a8542c15c9bb778d74 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 29 Mar 2022 18:03:46 +0800 Subject: [PATCH 1970/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 1b0621ee..a241450f 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -104,8 +104,12 @@ rules: resourceType: "Workflow" - method: POST endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" + idRegex: "/pipelines/([\\w\\W]+?)$/restart" + resourceType: "Workflow" - method: DELETE endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" + idRegex: "/pipelines/([\\w\\W]+?)$" + resourceType: "Workflow" - method: POST endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" - method: POST -- Gitee From f0b903422f64030600405858dfea0243b9bf106e Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 29 Mar 2022 21:50:22 +0800 Subject: [PATCH 1971/2647] support deleting service components Signed-off-by: zhangyifei --- .../core/environment/service/environment.go | 26 ++++++- .../core/environment/service/share_env.go | 44 +++++++++++ .../environment/service/share_env_utils.go | 78 +++++++++++++++++++ pkg/tool/kube/updater/clusterrole.go | 18 +++-- pkg/tool/kube/updater/configmap.go | 6 +- pkg/tool/kube/updater/cronjob.go | 6 +- pkg/tool/kube/updater/deployment.go | 6 +- pkg/tool/kube/updater/ingress.go | 24 +++--- pkg/tool/kube/updater/job.go | 6 +- pkg/tool/kube/updater/pod.go | 6 +- pkg/tool/kube/updater/pv.go | 6 +- pkg/tool/kube/updater/pvc.go | 6 +- pkg/tool/kube/updater/replicaset.go | 6 +- pkg/tool/kube/updater/role.go | 25 +++--- pkg/tool/kube/updater/rolebinding.go | 6 +- pkg/tool/kube/updater/secret.go | 6 +- pkg/tool/kube/updater/serviceaccount.go | 6 +- pkg/tool/kube/util/utils.go | 29 +++++++ 18 files changed, 272 insertions(+), 38 deletions(-) create mode 100644 pkg/tool/kube/util/utils.go diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index bf55b802..e1853fe8 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1977,11 +1977,35 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l log.Errorf("update renderSet error: %v", err) return err } + + ctx := context.TODO() + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), productInfo.ClusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to new istio client: %s", err) + } + for _, name := range serviceNames { selector := labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: name}.AsSelector() + + err = EnsureDeleteZadigService(ctx, productInfo, selector, kclient, istioClient) + if err != nil { + // Only record and do not block subsequent traversals. + log.Errorf("Failed to delete Zadig service: %s", err) + } + err = commonservice.DeleteNamespacedResource(productInfo.Namespace, selector, productInfo.ClusterID, log) if err != nil { - //删除失败仅记录失败日志 + // Only record and do not block subsequent traversals. log.Errorf("delete resource of service %s error:%v", name, err) } } diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 0722dbb9..61ae295f 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -40,6 +40,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" zadigtypes "github.com/koderover/zadig/pkg/types" ) @@ -1111,3 +1112,46 @@ func EnsureUpdateZadigService(ctx context.Context, env *commonmodels.Product, sv // 2. Updated the VirtualService configuration in the base environment. return ensureUpdateVirtualServiceInBase(ctx, env.EnvName, vsName, svcName, env.Namespace, baseEnv.Namespace, istioClient) } + +func EnsureDeleteZadigService(ctx context.Context, env *commonmodels.Product, svcSelector labels.Selector, kclient client.Client, istioClient versionedclient.Interface) error { + if !env.ShareEnv.Enable { + return nil + } + + svcList := &corev1.ServiceList{} + err := kclient.List(ctx, svcList, &client.ListOptions{ + Namespace: env.Namespace, + LabelSelector: svcSelector, + }) + if err != nil { + return util.IgnoreNotFoundError(err) + } + + if len(svcList.Items) != 1 { + return fmt.Errorf("Length of svc list is not expected for env %s of product %s with selector %s. Expected 1 but got %d.", env.EnvName, env.ProductName, svcSelector.String(), len(svcList.Items)) + } + svc := &svcList.Items[0] + vsName := genVirtualServiceName(svc) + + // Delete VirtualService in the current environment. + err = ensureDeleteVirtualService(ctx, env, vsName, istioClient) + if err != nil { + return err + } + + if env.ShareEnv.IsBase { + // Delete VirtualService and K8s Service in all of the sub environments if there're no specific workloads. + return ensureDeleteServiceInAllSubEnvs(ctx, env, svc, kclient, istioClient) + } + + baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: env.ProductName, + EnvName: env.ShareEnv.BaseEnv, + }) + if err != nil { + return err + } + + // Update VirtualService Routes in the base environment. + return ensureCleanRouteInBase(ctx, env.EnvName, baseEnv.Namespace, vsName, istioClient) +} diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 92fb63a5..a8313a27 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -26,6 +26,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "sigs.k8s.io/controller-runtime/pkg/client" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -242,3 +243,80 @@ func ensureServicesInAllSubEnvs(ctx context.Context, env *commonmodels.Product, return nil } + +func ensureDeleteVirtualService(ctx context.Context, env *commonmodels.Product, vsName string, istioClient versionedclient.Interface) error { + _, err := istioClient.NetworkingV1alpha3().VirtualServices(env.Namespace).Get(ctx, vsName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + log.Warnf("Failed to find VirtualService %s for env %s of product %s. Don't try to delete it.", vsName, env.EnvName, env.ProductName) + return nil + } + if err != nil { + return err + } + + deleteOption := metav1.DeletePropagationBackground + return istioClient.NetworkingV1alpha3().VirtualServices(env.Namespace).Delete(ctx, vsName, metav1.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) +} + +func ensureDeleteServiceInAllSubEnvs(ctx context.Context, baseEnv *commonmodels.Product, svc *corev1.Service, kclient client.Client, istioClient versionedclient.Interface) error { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: baseEnv.ProductName, + ClusterID: baseEnv.ClusterID, + ShareEnvEnable: getBoolPointer(true), + ShareEnvIsBase: getBoolPointer(false), + ShareEnvBaseEnv: getStrPointer(baseEnv.EnvName), + }) + if err != nil { + return err + } + + // Note: Don't delete VirtualService and K8s Service if there's selected pods. + vsName := genVirtualServiceName(svc) + workloadSelector := labels.SelectorFromSet(labels.Set(svc.Spec.Selector)) + for _, env := range envs { + podList := &corev1.PodList{} + err = kclient.List(ctx, podList, &client.ListOptions{ + Namespace: env.Namespace, + LabelSelector: workloadSelector, + }) + if err == nil && len(podList.Items) > 0 { + continue + } + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + err = ensureDeleteVirtualService(ctx, env, vsName, istioClient) + if err != nil { + return fmt.Errorf("failed to delete VirtualService %s in env %s of product %s: %s", vsName, env.EnvName, env.ProductName, err) + } + + err = ensureDeleteK8sService(ctx, env.Namespace, svc.Name, kclient) + if err != nil { + return fmt.Errorf("failed to delete K8s Service %s in env %s of product: %s: %s", svc.Name, env.EnvName, env.ProductName, err) + } + } + + return nil +} + +func ensureDeleteK8sService(ctx context.Context, ns, svcName string, kclient client.Client) error { + svc := &corev1.Service{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: svcName, + Namespace: ns, + }, svc) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + + deleteOption := metav1.DeletePropagationBackground + return kclient.Delete(ctx, svc, &client.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) +} diff --git a/pkg/tool/kube/updater/clusterrole.go b/pkg/tool/kube/updater/clusterrole.go index bb6f6852..84a90144 100644 --- a/pkg/tool/kube/updater/clusterrole.go +++ b/pkg/tool/kube/updater/clusterrole.go @@ -23,6 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteClusterRoles(selector labels.Selector, clientset *kubernetes.Clientset) error { @@ -30,17 +32,21 @@ func DeleteClusterRoles(selector labels.Selector, clientset *kubernetes.Clientse if err != nil { return err } + deletePolicy := metav1.DeletePropagationForeground if kubeclient.VersionLessThan122(version) { - return clientset.RbacV1beta1().ClusterRoles().DeleteCollection(context.TODO(), metav1.DeleteOptions{ + err = clientset.RbacV1beta1().ClusterRoles().DeleteCollection(context.TODO(), metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, metav1.ListOptions{ + LabelSelector: selector.String(), + }) + } else { + err = clientset.RbacV1().ClusterRoles().DeleteCollection(context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, }, metav1.ListOptions{ LabelSelector: selector.String(), }) } - return clientset.RbacV1().ClusterRoles().DeleteCollection(context.TODO(), metav1.DeleteOptions{ - PropagationPolicy: &deletePolicy, - }, metav1.ListOptions{ - LabelSelector: selector.String(), - }) + + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/configmap.go b/pkg/tool/kube/updater/configmap.go index fd9c61af..f04ebc5e 100644 --- a/pkg/tool/kube/updater/configmap.go +++ b/pkg/tool/kube/updater/configmap.go @@ -25,11 +25,13 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteConfigMaps(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.CoreV1().ConfigMaps(namespace).DeleteCollection( + err := clientset.CoreV1().ConfigMaps(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -38,6 +40,8 @@ func DeleteConfigMaps(namespace string, selector labels.Selector, clientset *kub LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func UpdateConfigMap(namespace string, cm *corev1.ConfigMap, clientset *kubernetes.Clientset) error { diff --git a/pkg/tool/kube/updater/cronjob.go b/pkg/tool/kube/updater/cronjob.go index 9d3f2e65..9705698f 100644 --- a/pkg/tool/kube/updater/cronjob.go +++ b/pkg/tool/kube/updater/cronjob.go @@ -24,11 +24,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteCronJobs(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.BatchV1().CronJobs(namespace).DeleteCollection( + err := clientset.BatchV1().CronJobs(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -37,6 +39,8 @@ func DeleteCronJobs(namespace string, selector labels.Selector, clientset *kuber LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func CreateOrPatchCronJob(cj *batchv1beta1.CronJob, cl client.Client) error { diff --git a/pkg/tool/kube/updater/deployment.go b/pkg/tool/kube/updater/deployment.go index 7964ae8c..922bc897 100644 --- a/pkg/tool/kube/updater/deployment.go +++ b/pkg/tool/kube/updater/deployment.go @@ -28,6 +28,8 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) var restartPatchTemplate = template.Must(template.New("restart-patch-template").Parse(`{ @@ -69,7 +71,7 @@ func RestartDeployment(ns, name string, cl client.Client) error { func DeleteDeployments(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.AppsV1().Deployments(namespace).DeleteCollection( + err := clientset.AppsV1().Deployments(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -78,6 +80,8 @@ func DeleteDeployments(namespace string, selector labels.Selector, clientset *ku LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func UpdateDeploymentImage(ns, name, container, image string, cl client.Client) error { diff --git a/pkg/tool/kube/updater/ingress.go b/pkg/tool/kube/updater/ingress.go index c3f4dcc1..4f5d614e 100644 --- a/pkg/tool/kube/updater/ingress.go +++ b/pkg/tool/kube/updater/ingress.go @@ -23,6 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteIngresses(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { @@ -34,22 +36,24 @@ func DeleteIngresses(namespace string, selector labels.Selector, clientset *kube deletePolicy := metav1.DeletePropagationForeground if kubeclient.VersionLessThan122(version) { - return clientset.ExtensionsV1beta1().Ingresses(namespace).DeleteCollection( + err = clientset.ExtensionsV1beta1().Ingresses(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, }, metav1.ListOptions{ LabelSelector: selector.String(), }) + } else { + err = clientset.NetworkingV1().Ingresses(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } - return clientset.NetworkingV1().Ingresses(namespace).DeleteCollection( - context.TODO(), - metav1.DeleteOptions{ - PropagationPolicy: &deletePolicy, - }, - metav1.ListOptions{ - LabelSelector: selector.String(), - }, - ) + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/job.go b/pkg/tool/kube/updater/job.go index 4dfeee52..1ad6612f 100644 --- a/pkg/tool/kube/updater/job.go +++ b/pkg/tool/kube/updater/job.go @@ -25,11 +25,13 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteJobs(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.BatchV1().Jobs(namespace).DeleteCollection( + err := clientset.BatchV1().Jobs(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -38,6 +40,8 @@ func DeleteJobs(namespace string, selector labels.Selector, clientset *kubernete LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func CreateJob(job *batchv1.Job, cl client.Client) error { diff --git a/pkg/tool/kube/updater/pod.go b/pkg/tool/kube/updater/pod.go index a0422e51..8b480f3f 100644 --- a/pkg/tool/kube/updater/pod.go +++ b/pkg/tool/kube/updater/pod.go @@ -24,11 +24,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeletePods(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.CoreV1().Pods(namespace).DeleteCollection( + err := clientset.CoreV1().Pods(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -37,6 +39,8 @@ func DeletePods(namespace string, selector labels.Selector, clientset *kubernete LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func DeletePod(ns, name string, cl client.Client) error { diff --git a/pkg/tool/kube/updater/pv.go b/pkg/tool/kube/updater/pv.go index 56a98b16..e9f890fc 100644 --- a/pkg/tool/kube/updater/pv.go +++ b/pkg/tool/kube/updater/pv.go @@ -22,11 +22,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeletePersistentVolumes(selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.CoreV1().PersistentVolumes().DeleteCollection( + err := clientset.CoreV1().PersistentVolumes().DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -35,4 +37,6 @@ func DeletePersistentVolumes(selector labels.Selector, clientset *kubernetes.Cli LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/pvc.go b/pkg/tool/kube/updater/pvc.go index e75b0015..e91a6b37 100644 --- a/pkg/tool/kube/updater/pvc.go +++ b/pkg/tool/kube/updater/pvc.go @@ -22,11 +22,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeletePersistentVolumeClaims(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.CoreV1().PersistentVolumeClaims(namespace).DeleteCollection( + err := clientset.CoreV1().PersistentVolumeClaims(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -35,4 +37,6 @@ func DeletePersistentVolumeClaims(namespace string, selector labels.Selector, cl LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/replicaset.go b/pkg/tool/kube/updater/replicaset.go index 412d4bd0..ff546ccf 100644 --- a/pkg/tool/kube/updater/replicaset.go +++ b/pkg/tool/kube/updater/replicaset.go @@ -22,11 +22,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteReplicaSets(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.AppsV1().ReplicaSets(namespace).DeleteCollection( + err := clientset.AppsV1().ReplicaSets(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -35,4 +37,6 @@ func DeleteReplicaSets(namespace string, selector labels.Selector, clientset *ku LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/role.go b/pkg/tool/kube/updater/role.go index 6c026d0e..6c0511c4 100644 --- a/pkg/tool/kube/updater/role.go +++ b/pkg/tool/kube/updater/role.go @@ -23,6 +23,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteRoles(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { @@ -30,9 +32,10 @@ func DeleteRoles(namespace string, selector labels.Selector, clientset *kubernet if err != nil { return err } + deletePolicy := metav1.DeletePropagationForeground if kubeclient.VersionLessThan122(version) { - return clientset.RbacV1beta1().Roles(namespace).DeleteCollection( + err = clientset.RbacV1beta1().Roles(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -40,15 +43,17 @@ func DeleteRoles(namespace string, selector labels.Selector, clientset *kubernet LabelSelector: selector.String(), }, ) + } else { + err = clientset.RbacV1().Roles(namespace).DeleteCollection( + context.TODO(), + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + metav1.ListOptions{ + LabelSelector: selector.String(), + }, + ) } - return clientset.RbacV1().Roles(namespace).DeleteCollection( - context.TODO(), - metav1.DeleteOptions{ - PropagationPolicy: &deletePolicy, - }, - metav1.ListOptions{ - LabelSelector: selector.String(), - }, - ) + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/rolebinding.go b/pkg/tool/kube/updater/rolebinding.go index ed5d1d24..cc9cceb9 100644 --- a/pkg/tool/kube/updater/rolebinding.go +++ b/pkg/tool/kube/updater/rolebinding.go @@ -22,11 +22,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteRoleBindings(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.RbacV1().RoleBindings(namespace).DeleteCollection( + err := clientset.RbacV1().RoleBindings(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -35,4 +37,6 @@ func DeleteRoleBindings(namespace string, selector labels.Selector, clientset *k LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } diff --git a/pkg/tool/kube/updater/secret.go b/pkg/tool/kube/updater/secret.go index f6da831c..5a541121 100644 --- a/pkg/tool/kube/updater/secret.go +++ b/pkg/tool/kube/updater/secret.go @@ -24,11 +24,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteSecrets(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.CoreV1().Secrets(namespace).DeleteCollection( + err := clientset.CoreV1().Secrets(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -37,6 +39,8 @@ func DeleteSecrets(namespace string, selector labels.Selector, clientset *kubern LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func UpdateOrCreateSecret(s *corev1.Secret, cl client.Client) error { diff --git a/pkg/tool/kube/updater/serviceaccount.go b/pkg/tool/kube/updater/serviceaccount.go index 857c0233..23e1fc23 100644 --- a/pkg/tool/kube/updater/serviceaccount.go +++ b/pkg/tool/kube/updater/serviceaccount.go @@ -24,11 +24,13 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/tool/kube/util" ) func DeleteServiceAccounts(namespace string, selector labels.Selector, clientset *kubernetes.Clientset) error { deletePolicy := metav1.DeletePropagationForeground - return clientset.CoreV1().ServiceAccounts(namespace).DeleteCollection( + err := clientset.CoreV1().ServiceAccounts(namespace).DeleteCollection( context.TODO(), metav1.DeleteOptions{ PropagationPolicy: &deletePolicy, @@ -37,6 +39,8 @@ func DeleteServiceAccounts(namespace string, selector labels.Selector, clientset LabelSelector: selector.String(), }, ) + + return util.IgnoreNotFoundError(err) } func CreateServiceAccount(sa *corev1.ServiceAccount, cl client.Client) error { diff --git a/pkg/tool/kube/util/utils.go b/pkg/tool/kube/util/utils.go new file mode 100644 index 00000000..caa8cb8e --- /dev/null +++ b/pkg/tool/kube/util/utils.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" +) + +func IgnoreNotFoundError(err error) error { + if apierrors.IsNotFound(err) { + return nil + } + + return err +} -- Gitee From 294e8c819316e3e2ea69bf91565160e311551f0b Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 29 Mar 2022 22:05:27 +0800 Subject: [PATCH 1972/2647] fix a display bug Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 8dd19cc0..3933f498 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -816,7 +816,11 @@ func (p *ReleaseImagePlugin) IsTaskFailed() bool { // SetStartTime ... func (p *ReleaseImagePlugin) SetStartTime() { - p.Task.StartTime = time.Now().Unix() + startTime := time.Now().Unix() + p.Task.StartTime = startTime + for _, distribute := range p.Task.DistributeInfo { + distribute.DistributeStartTime = startTime + } } // SetEndTime ... -- Gitee From 74aca0678a7daa6353b111483799456ebbcac92f Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 29 Mar 2022 22:35:27 +0800 Subject: [PATCH 1973/2647] make workflow runnable even if the target container is not running Signed-off-by: Min Min --- .../service/workflow/pipeline_validation.go | 84 ++++++++----------- .../service/workflow/workflow_task.go | 8 +- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 8db28da0..9fd314dd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -28,10 +28,6 @@ import ( "time" "github.com/google/go-github/v35/github" - "go.uber.org/zap" - "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -52,6 +48,8 @@ import ( "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" + "go.uber.org/zap" + "k8s.io/apimachinery/pkg/labels" ) const ( @@ -696,8 +694,8 @@ func SetCandidateRegistry(payload *commonmodels.ConfigPayload, log *zap.SugaredL return nil } -// validateServiceContainer validate container with envName like dev -func validateServiceContainer(envName, productName, serviceName, container string) (string, error) { +// getImageInfoFromWorkload find the current image info from the cluster +func getImageInfoFromWorkload(envName, productName, serviceName, container string) (string, error) { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, EnvName: envName, @@ -711,9 +709,39 @@ func validateServiceContainer(envName, productName, serviceName, container strin return "", err } - return validateServiceContainer2( - product, serviceName, container, kubeClient, - ) + if product.Source == setting.SourceFromHelm { + return findCurrentlyUsingImage(product, serviceName, container) + } + + var selector labels.Selector + //helm和托管类型的服务查询所有标签的pod + if product.Source != setting.SourceFromHelm && product.Source != setting.SourceFromExternal { + selector = labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceName}.AsSelector() + //builder := &SelectorBuilder{ProductName: productName, ServiceName: serviceName} + //selector = builder.BuildSelector() + } + + pods, err := getter.ListPods(product.Namespace, selector, kubeClient) + if err != nil { + return "", fmt.Errorf("[%s] ListPods %s/%s error: %v", product.Namespace, productName, serviceName, err) + } + + for _, p := range pods { + pod := wrapper.Pod(p).Resource() + for _, c := range pod.ContainerStatuses { + if c.Name == container || commonservice.ExtractImageName(c.Image) == container { + return c.Image, nil + } + } + } + log.Errorf("[%s]container %s not found", product.Namespace, container) + + return "", &ContainerNotFound{ + ServiceName: serviceName, + Container: container, + EnvName: envName, + ProductName: productName, + } } type ContainerNotFound struct { @@ -748,44 +776,6 @@ func findCurrentlyUsingImage(productInfo *commonmodels.Product, serviceName, con return "", fmt.Errorf("failed to find image url") } -// validateServiceContainer2 validate container with raw namespace like dev-product -func validateServiceContainer2(productInfo *commonmodels.Product, serviceName, container string, kubeClient client.Client) (string, error) { - namespace, productName, envName, source := productInfo.Namespace, productInfo.ProductName, productInfo.EnvName, productInfo.Source - if source == setting.SourceFromHelm { - return findCurrentlyUsingImage(productInfo, serviceName, container) - } - - var selector labels.Selector - //helm和托管类型的服务查询所有标签的pod - if source != setting.SourceFromHelm && source != setting.SourceFromExternal { - selector = labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceName}.AsSelector() - //builder := &SelectorBuilder{ProductName: productName, ServiceName: serviceName} - //selector = builder.BuildSelector() - } - - pods, err := getter.ListPods(namespace, selector, kubeClient) - if err != nil { - return "", fmt.Errorf("[%s] ListPods %s/%s error: %v", namespace, productName, serviceName, err) - } - - for _, p := range pods { - pod := wrapper.Pod(p).Resource() - for _, c := range pod.ContainerStatuses { - if c.Name == container || commonservice.ExtractImageName(c.Image) == container { - return c.Image, nil - } - } - } - log.Errorf("[%s]container %s not found", namespace, container) - - return "", &ContainerNotFound{ - ServiceName: serviceName, - Container: container, - EnvName: envName, - ProductName: productName, - } -} - // IsProductAuthed 查询指定产品是否授权给用户, 或者用户所在的组 // TODO: REVERT Auth is diabled func IsProductAuthed(username, productOwner, productName string, perm config.ProductPermission, log *zap.SugaredLogger) bool { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 3762bd8c..7595a0af 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2429,7 +2429,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err if t.Enabled { t.SetNamespace(taskOpt.Task.TaskArgs.Deploy.Namespace) - image, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) + image, err := getImageInfoFromWorkload(t.EnvName, t.ProductName, t.ServiceName, t.ContainerName) if err != nil { log.Error(err) return err @@ -2460,12 +2460,6 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err containerName = strings.TrimSuffix(containerName, "_"+t.ServiceName) } - _, err := validateServiceContainer(t.EnvName, t.ProductName, t.ServiceName, containerName) - if err != nil { - log.Error(err) - return err - } - // Task creator can be webhook trigger or cronjob trigger or validated user // Validated user includes both that user is granted write permission or user is the owner of this product if taskOpt.Task.TaskCreator == setting.WebhookTaskCreator || -- Gitee From 1414f17350c1e239aabd2a7d90793e360b9ed369 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 29 Mar 2022 22:36:17 +0800 Subject: [PATCH 1974/2647] add gitee webhook Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 2 +- .../aslan/core/common/service/gitee/client.go | 2 +- .../aslan/core/workflow/handler/webhook.go | 3 + .../core/workflow/service/webhook/gitee.go | 59 ++ .../service/webhook/gitee_workflow_task.go | 325 +++++++++ .../service/workflow/pipeline_validation.go | 2 +- pkg/tool/gitee/client.go | 7 +- pkg/tool/gitee/event_parsing.go | 652 ++++++++++++++++++ pkg/tool/gitee/repositories.go | 94 +++ 9 files changed, 1140 insertions(+), 6 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/gitee.go create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go create mode 100644 pkg/tool/gitee/event_parsing.go diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 4adeaec1..4b7f0f1d 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -23,7 +23,7 @@ type Client struct { } func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - client, _ := gitee.NewClient(id, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) + client := gitee.NewClient(id, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) return &Client{ Client: client, AccessToken: c.AccessToken, diff --git a/pkg/microservice/aslan/core/common/service/gitee/client.go b/pkg/microservice/aslan/core/common/service/gitee/client.go index e29f94c4..2a5598ef 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/client.go +++ b/pkg/microservice/aslan/core/common/service/gitee/client.go @@ -8,7 +8,7 @@ type Client struct { } func NewClient(id int, accessToken, proxyAddress string, enableProxy bool) *Client { - client, _ := gitee.NewClient(id, accessToken, proxyAddress, enableProxy) + client := gitee.NewClient(id, accessToken, proxyAddress, enableProxy) return &Client{ Client: client, AccessToken: accessToken, diff --git a/pkg/microservice/aslan/core/workflow/handler/webhook.go b/pkg/microservice/aslan/core/workflow/handler/webhook.go index 0a6dcea6..32af948c 100644 --- a/pkg/microservice/aslan/core/workflow/handler/webhook.go +++ b/pkg/microservice/aslan/core/workflow/handler/webhook.go @@ -28,6 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/webhook" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/codehub" + "github.com/koderover/zadig/pkg/tool/gitee" ) // @Router /workflow/webhook [POST] @@ -50,6 +51,8 @@ func ProcessWebHook(c *gin.Context) { ctx.Err = webhook.ProcessGitlabHook(payload, c.Request, ctx.RequestID, ctx.Logger) } else if codehub.HookEventType(c.Request) != "" { ctx.Err = webhook.ProcessCodehubHook(payload, c.Request, ctx.RequestID, ctx.Logger) + } else if gitee.HookEventType(c.Request) != "" { + ctx.Err = webhook.ProcessGiteeHook(payload, c.Request, ctx.RequestID, ctx.Logger) } else { ctx.Err = webhook.ProcessGerritHook(payload, c.Request, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go new file mode 100644 index 00000000..ef23eb49 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -0,0 +1,59 @@ +package webhook + +import ( + "errors" + "net/http" + "time" + + "github.com/hashicorp/go-multierror" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/gitee" +) + +func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { + token := req.Header.Get("X-Gitee-Event") + secret := gitservice.GetHookSecret() + + if secret != "" && token != secret { + return errors.New("token is illegal") + } + + eventType := gitee.HookEventType(req) + event, err := gitee.ParseHook(eventType, payload) + if err != nil { + return err + } + + baseURI := config.SystemAddress() + var errorList = &multierror.Error{} + + switch event := event.(type) { + case *gitee.PushEvent: + //add webhook user + if len(event.Commits) > 0 { + webhookUser := &commonmodels.WebHookUser{ + Domain: req.Header.Get("X-Forwarded-Host"), + UserName: event.Commits[0].Author.Name, + Email: event.Commits[0].Author.Email, + Source: setting.SourceFromGitee, + CreatedAt: time.Now().Unix(), + } + commonrepo.NewWebHookUserColl().Upsert(webhookUser) + } + if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + case *gitee.PullRequestEvent, *gitee.TagPushEvent: + if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + } + + return errorList.ErrorOrNil() +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go new file mode 100644 index 00000000..803d9628 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go @@ -0,0 +1,325 @@ +package webhook + +import ( + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/types" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/gitee" +) + +type giteePullRequestDiffFunc func(event *gitee.PullRequestEvent, id int) ([]string, error) + +type giteePushEventMatcher struct { + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *gitee.PushEvent +} + +func (gpem *giteePushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gpem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Repository.FullName { + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } + if isRegular { + // Do not use regexp.MustCompile to avoid panic + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil + } + } + hookRepo.Branch = getBranchFromRef(ev.Ref) + hookRepo.Committer = ev.Pusher.Name + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + return MatchChanges(hookRepo, changedFiles), nil + } + + return false, nil +} + +func (gpem *giteePushEventMatcher) UpdateTaskArgs( + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, +) *commonmodels.WorkflowTaskArgs { + factory := &workflowArgsFactory{ + workflow: gpem.workflow, + reqID: requestID, + } + + factory.Update(product, args, &types.Repository{ + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + Branch: hookRepo.Branch, + }) + + return args +} + +type giteeMergeEventMatcher struct { + diffFunc giteePullRequestDiffFunc + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *gitee.PullRequestEvent +} + +func (gmem *giteeMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gmem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.PullRequest.Base.Repo.FullName { + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.PullRequest.Base.Ref { + return false, nil + } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.PullRequest.Base.Ref); !matched { + return false, nil + } + } + hookRepo.Branch = ev.PullRequest.Base.Ref + hookRepo.Committer = ev.PullRequest.User.Login + if ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err + } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(hookRepo, changedFiles), nil + } + } + return false, nil +} + +func (gmem *giteeMergeEventMatcher) UpdateTaskArgs( + product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string, +) *commonmodels.WorkflowTaskArgs { + factory := &workflowArgsFactory{ + workflow: gmem.workflow, + reqID: requestID, + } + + args = factory.Update(product, args, &types.Repository{ + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + Branch: hookRepo.Branch, + PR: gmem.event.PullRequest.Number, + }) + + return args +} + +type giteeTagEventMatcher struct { + log *zap.SugaredLogger + workflow *commonmodels.Workflow + event *gitee.TagPushEvent +} + +func (gtem giteeTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Repository.FullName { + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.Repository.DefaultBranch { + return false, nil + } + if isRegular { + // Do not use regexp.MustCompile to avoid panic + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Repository.DefaultBranch); !matched { + return false, nil + } + } + hookRepo.Tag = getTagFromRef(ev.Ref) + hookRepo.Committer = ev.Sender.Name + + return true, nil + } + + return false, nil +} + +func (gtem giteeTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { + factory := &workflowArgsFactory{ + workflow: gtem.workflow, + reqID: requestID, + } + + factory.Update(product, args, &types.Repository{ + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + Branch: hookRepo.Branch, + Tag: hookRepo.Tag, + }) + + return args +} + +func createGiteeEventMatcher( + event interface{}, diffSrv giteePullRequestDiffFunc, workflow *commonmodels.Workflow, log *zap.SugaredLogger, +) gitEventMatcher { + switch evt := event.(type) { + case *gitee.PushEvent: + return &giteePushEventMatcher{ + workflow: workflow, + log: log, + event: evt, + } + case *gitee.PullRequestEvent: + return &giteeMergeEventMatcher{ + diffFunc: diffSrv, + log: log, + event: evt, + workflow: workflow, + } + case *gitee.TagPushEvent: + return &giteeTagEventMatcher{ + workflow: workflow, + log: log, + event: evt, + } + } + + return nil +} + +func TriggerWorkflowByGiteeEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { + workflowList, err := commonrepo.NewWorkflowColl().List(&commonrepo.ListWorkflowOption{}) + if err != nil { + log.Errorf("failed to list workflow %v", err) + return err + } + + mErr := &multierror.Error{} + diffSrv := func(pullRequestEvent *gitee.PullRequestEvent, codehostId int) ([]string, error) { + return findChangedFilesOfPullRequestEvent(pullRequestEvent, codehostId) + } + + for _, workflow := range workflowList { + if workflow.HookCtl != nil && workflow.HookCtl.Enabled { + log.Debugf("find %d hooks in workflow %s", len(workflow.HookCtl.Items), workflow.Name) + for _, item := range workflow.HookCtl.Items { + if item.WorkflowArgs == nil { + continue + } + + matcher := createGiteeEventMatcher(event, diffSrv, workflow, log) + if matcher == nil { + continue + } + + if matches, err := matcher.Match(item.MainRepo); err != nil { + mErr = multierror.Append(mErr, err) + } else if matches { + log.Infof("event match hook %v of %s", item.MainRepo, workflow.Name) + namespace := strings.Split(item.WorkflowArgs.Namespace, ",")[0] + opt := &commonrepo.ProductFindOptions{Name: workflow.ProductTmplName, EnvName: namespace} + var prod *commonmodels.Product + if prod, err = commonrepo.NewProductColl().Find(opt); err != nil { + log.Warnf("can't find environment %s-%s", item.WorkflowArgs.Namespace, workflow.ProductTmplName) + continue + } + + var mergeRequestID, commitID string + var hookPayload *commonmodels.HookPayload + if ev, isPr := event.(*gitee.PullRequestEvent); isPr { + if ev.PullRequest != nil && ev.PullRequest.Number != 0 && ev.PullRequest.Head != nil && ev.PullRequest.Head.Sha != "" { + mergeRequestID = strconv.Itoa(ev.PullRequest.Number) + commitID = ev.PullRequest.Head.Sha + autoCancelOpt := &AutoCancelOpt{ + MergeRequestID: mergeRequestID, + CommitID: commitID, + TaskType: config.WorkflowType, + MainRepo: item.MainRepo, + WorkflowArgs: item.WorkflowArgs, + } + err := AutoCancelTask(autoCancelOpt, log) + if err != nil { + log.Errorf("failed to auto cancel workflow task when receive event due to %v ", err) + mErr = multierror.Append(mErr, err) + } + } + + hookPayload = &commonmodels.HookPayload{ + Owner: ev.Repository.Owner.Login, + Repo: ev.Repository.Name, + Branch: ev.PullRequest.Base.Ref, + Ref: ev.PullRequest.Head.Sha, + IsPr: true, + } + } + + args := matcher.UpdateTaskArgs(prod, item.WorkflowArgs, item.MainRepo, requestID) + args.MergeRequestID = mergeRequestID + args.CommitID = commitID + args.Source = setting.SourceFromGitee + args.CodehostID = item.MainRepo.CodehostID + args.RepoOwner = item.MainRepo.RepoOwner + args.RepoName = item.MainRepo.RepoName + args.Committer = item.MainRepo.Committer + args.HookPayload = hookPayload + + // 3. create task with args + if resp, err := workflowservice.CreateWorkflowTask(args, setting.WebhookTaskCreator, log); err != nil { + log.Errorf("failed to create workflow task when receive push event due to %v ", err) + mErr = multierror.Append(mErr, err) + } else { + log.Infof("succeed to create task %v", resp) + } + } else { + log.Debugf("event not matches %v", item.MainRepo) + } + } + } + } + + return mErr.ErrorOrNil() +} + +func findChangedFilesOfPullRequestEvent(event *gitee.PullRequestEvent, codehostID int) ([]string, error) { + detail, err := systemconfig.New().GetCodeHost(codehostID) + if err != nil { + return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) + } + + giteeCli := gitee.NewClient(detail.ID, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) + commitComparison, err := giteeCli.GetReposOwnerRepoCompareBaseHead(detail.AccessToken, event.PullRequest.Base.Repo.Owner.Login, event.PullRequest.Base.Repo.Name, event.PullRequest.Base.Sha, event.PullRequest.Head.Sha) + if err != nil { + return nil, fmt.Errorf("failed to get changes from gitee, err: %v", err) + } + + changeFiles := make([]string, 0) + for _, commitFile := range commitComparison.Files { + changeFiles = append(changeFiles, commitFile.Filename) + } + return changeFiles, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 239d8614..60522564 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -357,7 +357,7 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } } else if codeHostInfo.Type == systemconfig.GiteeProvider { - gitCli, _ := gitee.NewClient(codeHostInfo.ID, codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) + gitCli := gitee.NewClient(codeHostInfo.ID, codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) if build.CommitID == "" { if build.Tag != "" && build.PR == 0 { tags, err := gitCli.ListTags(context.Background(), codeHostInfo.AccessToken, build.RepoOwner, build.RepoName) diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index cb654d38..9cbf4d55 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -17,7 +17,7 @@ type Client struct { *gitee.APIClient } -func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { +func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) *Client { var ( client *gitee.APIClient HttpClient *http.Client @@ -46,8 +46,9 @@ func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) (*Client ch.AccessToken = token.AccessToken ch.RefreshToken = token.RefreshToken ch.UpdatedAt = int64(token.CreatedAt) + if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - return nil, fmt.Errorf("failed to update codehost,err:%s", err) + fmt.Println(fmt.Sprintf("failed to UpdateCodeHost err:%s", err)) } } } @@ -63,5 +64,5 @@ func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) (*Client conf.HTTPClient = HttpClient client = gitee.NewAPIClient(conf) - return &Client{client}, nil + return &Client{client} } diff --git a/pkg/tool/gitee/event_parsing.go b/pkg/tool/gitee/event_parsing.go new file mode 100644 index 00000000..82578335 --- /dev/null +++ b/pkg/tool/gitee/event_parsing.go @@ -0,0 +1,652 @@ +package gitee + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +// EventType represents a gitee event type. +type EventType string + +// List of available event types. +const ( + EventTypeMergeRequest EventType = "Merge Request Hook" + EventTypePush EventType = "Push Hook" + EventTypeTagPush EventType = "Tag Push Hook" +) + +const eventTypeHeader = "X-Gitee-Event" + +// HookEventType returns the event type for the given request. +func HookEventType(r *http.Request) EventType { + return EventType(r.Header.Get(eventTypeHeader)) +} + +func ParseHook(eventType EventType, payload []byte) (event interface{}, err error) { + return parseWebhook(eventType, payload) +} + +func parseWebhook(eventType EventType, payload []byte) (event interface{}, err error) { + switch eventType { + case EventTypeMergeRequest: + event = &PullRequestEvent{} + case EventTypePush: + event = &PushEvent{} + case EventTypeTagPush: + event = &TagPushEvent{} + default: + return nil, fmt.Errorf("unexpected event type: %s", eventType) + } + + if err := json.Unmarshal(payload, event); err != nil { + return nil, err + } + + return event, nil +} + +type TagPushEvent struct { + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + Created bool `json:"created"` + Deleted bool `json:"deleted"` + Compare string `json:"compare"` + Commits []struct { + ID string `json:"id"` + TreeID string `json:"tree_id"` + ParentIds []string `json:"parent_ids"` + Distinct bool `json:"distinct"` + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Time time.Time `json:"time"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"author"` + Committer struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"committer"` + Added interface{} `json:"added"` + Removed interface{} `json:"removed"` + Modified []string `json:"modified"` + } `json:"commits"` + HeadCommit struct { + ID string `json:"id"` + TreeID string `json:"tree_id"` + ParentIds []string `json:"parent_ids"` + Distinct bool `json:"distinct"` + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Time time.Time `json:"time"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"author"` + Committer struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"committer"` + Added interface{} `json:"added"` + Removed interface{} `json:"removed"` + Modified []string `json:"modified"` + } `json:"head_commit"` + TotalCommitsCount int `json:"total_commits_count"` + CommitsMoreThanTen bool `json:"commits_more_than_ten"` + Repository struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"repository"` + Project struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"project"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + User struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"user"` + Pusher struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"pusher"` + Sender struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"sender"` + Enterprise struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"enterprise"` + HookName string `json:"hook_name"` + HookID int `json:"hook_id"` + HookURL string `json:"hook_url"` + Password string `json:"password"` + Timestamp interface{} `json:"timestamp"` + Sign string `json:"sign"` +} + +type PushEvent struct { + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + Created bool `json:"created"` + Deleted bool `json:"deleted"` + Compare string `json:"compare"` + Commits []struct { + ID string `json:"id"` + TreeID string `json:"tree_id"` + ParentIds []string `json:"parent_ids"` + Distinct bool `json:"distinct"` + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + URL string `json:"url"` + Author struct { + Time time.Time `json:"time"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"author"` + Committer struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"committer"` + Added []string `json:"added,omitempty"` + Removed []string `json:"removed,omitempty"` + Modified []string `json:"modified,omitempty"` + } `json:"commits"` + TotalCommitsCount int `json:"total_commits_count"` + CommitsMoreThanTen bool `json:"commits_more_than_ten"` + Repository struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"repository"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + Pusher struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"pusher"` + Enterprise struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"enterprise"` + HookName string `json:"hook_name"` + HookID int `json:"hook_id"` + HookURL string `json:"hook_url"` + Password string `json:"password"` + Timestamp interface{} `json:"timestamp"` + Sign string `json:"sign"` +} + +type PullRequestEvent struct { + Action string `json:"action"` + ActionDesc string `json:"action_desc"` + PullRequest *struct { + ID int `json:"id"` + Number int `json:"number"` + State string `json:"state"` + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + Title string `json:"title"` + Body interface{} `json:"body"` + Languages []string `json:"languages"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ClosedAt interface{} `json:"closed_at"` + MergedAt interface{} `json:"merged_at"` + MergeCommitSha string `json:"merge_commit_sha"` + User struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"user"` + Head *struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + User struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"user"` + Repo struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"repo"` + } `json:"head"` + Base struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + User struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"user"` + Repo struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"repo"` + } `json:"base"` + Merged bool `json:"merged"` + Mergeable interface{} `json:"mergeable"` + MergeStatus string `json:"merge_status"` + Comments int `json:"comments"` + Commits int `json:"commits"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + ChangedFiles int `json:"changed_files"` + } `json:"pull_request"` + Number int `json:"number"` + Iid int `json:"iid"` + Title string `json:"title"` + Body interface{} `json:"body"` + Languages []string `json:"languages"` + State string `json:"state"` + MergeStatus string `json:"merge_status"` + MergeCommitSha string `json:"merge_commit_sha"` + URL string `json:"url"` + SourceBranch string `json:"source_branch"` + TargetBranch string `json:"target_branch"` + Repository struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` + } `json:"repository"` + Author struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"author"` + UpdatedBy struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"updated_by"` + Sender struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"sender"` + TargetUser struct { + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` + } `json:"target_user"` + Enterprise struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"enterprise"` + HookName string `json:"hook_name"` + HookID int `json:"hook_id"` + HookURL string `json:"hook_url"` + Password string `json:"password"` + Timestamp interface{} `json:"timestamp"` + Sign string `json:"sign"` +} diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index c60ef0ba..c0ef084c 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -187,3 +187,97 @@ func RefreshAccessToken(refreshToken string) (*AccessToken, error) { return accessToken, nil } + +type Compare struct { + Commits []struct { + URL string `json:"url"` + Sha string `json:"sha"` + HTMLURL string `json:"html_url"` + CommentsURL string `json:"comments_url"` + Commit struct { + Author struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` + } `json:"author"` + Committer struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` + } `json:"committer"` + Message string `json:"message"` + Tree struct { + Sha string `json:"sha"` + URL string `json:"url"` + } `json:"tree"` + } `json:"commit"` + Author struct { + ID int `json:"id"` + Login string `json:"login"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Remark string `json:"remark"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + } `json:"author"` + Committer struct { + ID int `json:"id"` + Login string `json:"login"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Remark string `json:"remark"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` + } `json:"committer"` + Parents []struct { + Sha string `json:"sha"` + URL string `json:"url"` + } `json:"parents"` + } `json:"commits"` + Files []struct { + Sha string `json:"sha"` + Filename string `json:"filename"` + Status string `json:"status"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + BlobURL string `json:"blob_url"` + RawURL string `json:"raw_url"` + ContentURL string `json:"content_url"` + Patch string `json:"patch"` + } `json:"files"` +} + +func (c *Client) GetReposOwnerRepoCompareBaseHead(accessToken, owner string, repo string, base string, head string) (*Compare, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := fmt.Sprintf("/v5/repos/%s/%s/compare/%s...%s", owner, repo, base, head) + + var compare *Compare + _, err := httpClient.Get(url, httpclient.SetQueryParam("access_token", accessToken), httpclient.SetResult(&compare)) + if err != nil { + return nil, err + } + return compare, nil +} -- Gitee From 45e029daadb16ed93fe7fcc80aa74815bcae1500 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 29 Mar 2022 22:50:40 +0800 Subject: [PATCH 1975/2647] dont block deleting subenv if cant' find VirtualService in base env Signed-off-by: zhangyifei --- .../aslan/core/environment/service/share_env_utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index a8313a27..6c445a2a 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -161,7 +161,8 @@ func deleteEnvoyFilter(ctx context.Context, istioClient versionedclient.Interfac func ensureCleanRouteInBase(ctx context.Context, envName, baseNS, vsName string, istioClient versionedclient.Interface) error { baseVS, err := istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Get(ctx, vsName, metav1.GetOptions{}) if err != nil { - return err + log.Warnf("Failed to query VirtualService %s releated to env %s in namesapce %s: %s. It may be not an exception and skip.", vsName, envName, baseNS, err) + return nil } if len(baseVS.Spec.Http) == 0 { -- Gitee From bc829c18a767fd072c4342242c6929012f730ad4 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 30 Mar 2022 10:29:28 +0800 Subject: [PATCH 1976/2647] support pm login and probe Signed-off-by: ddh27 --- .../common/repository/models/private_key.go | 26 +-- .../common/repository/models/task/build.go | 1 + .../common/repository/mongodb/private_key.go | 31 ++-- .../aslan/core/environment/handler/pm_exec.go | 18 +++ .../aslan/core/environment/handler/router.go | 1 + .../aslan/core/environment/service/pm_exec.go | 66 ++++++++ .../service/workflow/workflow_task.go | 5 + .../cron/core/service/client/pm.go | 67 ++++++++ .../core/service/scheduler/schedule_pm.go | 44 ++++++ .../service/scheduler/schedule_workflow.go | 2 +- .../cron/core/service/scheduler/scheduler.go | 13 ++ pkg/microservice/cron/core/service/service.go | 13 ++ .../reaper/core/service/meta/types.go | 1 + .../reaper/core/service/reaper/script.go | 1 + .../core/service/types/task/build.go | 1 + pkg/setting/consts.go | 9 ++ pkg/tool/errors/http_errors.go | 2 + pkg/tool/ssh/ssh.go | 35 +++++ pkg/tool/wsconn/wsconn.go | 148 ++++++++++++++++++ 19 files changed, 461 insertions(+), 23 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/handler/pm_exec.go create mode 100644 pkg/microservice/aslan/core/environment/service/pm_exec.go create mode 100644 pkg/microservice/cron/core/service/client/pm.go create mode 100644 pkg/microservice/cron/core/service/scheduler/schedule_pm.go create mode 100644 pkg/tool/ssh/ssh.go create mode 100644 pkg/tool/wsconn/wsconn.go diff --git a/pkg/microservice/aslan/core/common/repository/models/private_key.go b/pkg/microservice/aslan/core/common/repository/models/private_key.go index c54b9c26..54925662 100644 --- a/pkg/microservice/aslan/core/common/repository/models/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/models/private_key.go @@ -17,21 +17,25 @@ limitations under the License. package models import ( + "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" ) type PrivateKey struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - UserName string `bson:"user_name" json:"user_name"` - IP string `bson:"ip" json:"ip"` - Label string `bson:"label" json:"label"` - IsProd bool `bson:"is_prod" json:"is_prod"` - PrivateKey string `bson:"private_key" json:"private_key"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateTime int64 `bson:"update_time" json:"update_time"` - UpdateBy string `bson:"update_by" json:"update_by"` - Provider int8 `bson:"provider" json:"provider"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + UserName string `bson:"user_name" json:"user_name"` + IP string `bson:"ip" json:"ip"` + Port int64 `bson:"port" json:"port"` + Status setting.PMHostStatus `bson:"status" json:"status"` + Label string `bson:"label" json:"label"` + IsProd bool `bson:"is_prod" json:"is_prod"` + PrivateKey string `bson:"private_key" json:"private_key"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + Provider int8 `bson:"provider" json:"provider"` + UpdateStatus bool `bson:"-" json:"update_status"` } func (PrivateKey) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 6132a2bb..83144e88 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -181,6 +181,7 @@ type SSH struct { Name string `json:"name"` UserName string `json:"user_name"` IP string `json:"ip"` + Port int64 `json:"port"` IsProd bool `json:"is_prod"` Label string `json:"label"` PrivateKey string `json:"private_key"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index bb429b79..da3ef2e5 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -131,17 +131,25 @@ func (c *PrivateKeyColl) Update(id string, args *models.PrivateKey) error { } query := bson.M{"_id": oid} - change := bson.M{"$set": bson.M{ - "name": args.Name, - "user_name": args.UserName, - "ip": args.IP, - "label": args.Label, - "is_prod": args.IsProd, - "private_key": args.PrivateKey, - "provider": args.Provider, - "update_by": args.UpdateBy, - "update_time": time.Now().Unix(), - }} + change := bson.M{} + if args.UpdateStatus { + change = bson.M{"$set": bson.M{ + "status": args.Status, + }} + } else { + change = bson.M{"$set": bson.M{ + "name": args.Name, + "user_name": args.UserName, + "ip": args.IP, + "port": args.Port, + "label": args.Label, + "is_prod": args.IsProd, + "private_key": args.PrivateKey, + "provider": args.Provider, + "update_by": args.UpdateBy, + "update_time": time.Now().Unix(), + }} + } _, err = c.UpdateOne(context.TODO(), query, change) return err @@ -196,6 +204,7 @@ func (c *PrivateKeyColl) ListHostIPByArgs(args *ListHostIPArgs) ([]*models.Priva selector := bson.D{ {"_id", 1}, {"ip", 1}, + {"port", 1}, {"name", 1}, {"user_name", 1}, {"private_key", 1}, diff --git a/pkg/microservice/aslan/core/environment/handler/pm_exec.go b/pkg/microservice/aslan/core/environment/handler/pm_exec.go new file mode 100644 index 00000000..08e333e8 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/pm_exec.go @@ -0,0 +1,18 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func ConnectSshPmExec(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + ip := c.Query("ip") + name := c.Param("name") + + ctx.Err = service.ConnectSshPmExec(c, ctx.UserName, name, projectName, ip, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 1dca57ca..aa190b09 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -122,6 +122,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/services/:serviceName/containers/:container", GetServiceContainer) environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) + environments.GET("/:name/services/:serviceName/pmexec", ConnectSshPmExec) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/pm_exec.go b/pkg/microservice/aslan/core/environment/service/pm_exec.go new file mode 100644 index 00000000..254172ea --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/pm_exec.go @@ -0,0 +1,66 @@ +package service + +import ( + "fmt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "go.uber.org/zap" + + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" + toolssh "github.com/koderover/zadig/pkg/tool/ssh" + "github.com/koderover/zadig/pkg/tool/wsconn" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, log *zap.SugaredLogger) error { + resp, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ + Address: ip, + }) + if err != nil { + log.Errorf("PrivateKey.Find ip %s error: %s", ip, err) + return e.ErrGetPrivateKey + } + if resp.Status != setting.PMHostStatusNormal { + return e.ErrLoginPm.AddDesc(fmt.Sprintf("host %s status %s,is not normal", ip, resp.Status)) + } + + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Error(err) + return e.ErrLoginPm.AddErr(err) + } + if resp.Port == 0 { + resp.Port = setting.PMHostDefaultPort + } + sshCli, err := toolssh.NewSshCli([]byte(resp.PrivateKey), resp.UserName, resp.IP, resp.Port) + if err != nil { + conn.WriteMessage(1, []byte(err.Error())) + conn.Close() + return e.ErrLoginPm.AddErr(err) + } + sshClient := &wsconn.SshClient{ + // Username: resp.UserName, + // PrivateKey: resp.PrivateKey, + // Ip: resp.IP, + // Port: resp.Port, + SshCli: sshCli, + } + + if err := sshClient.GenerateWebTerminal(150, 40); err != nil { + conn.WriteMessage(1, []byte(err.Error())) + conn.Close() + } + sshClient.ConnectWs(conn) + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 3762bd8c..d09f56bd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2043,6 +2043,10 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL ssh.Name = latestKeyInfo.Name ssh.UserName = latestKeyInfo.UserName ssh.IP = latestKeyInfo.IP + ssh.Port = latestKeyInfo.Port + if ssh.Port == 0 { + ssh.Port = setting.PMHostDefaultPort + } ssh.PrivateKey = latestKeyInfo.PrivateKey privateKeys = append(privateKeys, ssh) @@ -2056,6 +2060,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL Name: privateKey.Name, UserName: privateKey.UserName, IP: privateKey.IP, + Port: privateKey.Port, PrivateKey: privateKey.PrivateKey, }) } diff --git a/pkg/microservice/cron/core/service/client/pm.go b/pkg/microservice/cron/core/service/client/pm.go new file mode 100644 index 00000000..d0e2072b --- /dev/null +++ b/pkg/microservice/cron/core/service/client/pm.go @@ -0,0 +1,67 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/koderover/zadig/pkg/microservice/cron/core/service" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func (c *Client) ListPmHosts(log *zap.SugaredLogger) ([]*service.PrivateKeyHosts, error) { + var ( + err error + resp = make([]*service.PrivateKeyHosts, 0) + ) + + url := fmt.Sprintf("%s/system/privateKey", c.APIBase) + request, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Errorf("ListPmHosts new http request error: %v", err) + return nil, err + } + + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + var body []byte + body, err = ioutil.ReadAll(ret.Body) + if err == nil { + if err = json.Unmarshal(body, &resp); err == nil { + return resp, nil + } + } + } + return resp, errors.WithMessage(err, "failed to list PmHosts") +} + +func (c *Client) UpdatePmHost(args *service.PrivateKeyHosts, log *zap.SugaredLogger) error { + args.UpdateStatus = true + body, err := json.Marshal(args) + if err != nil { + log.Errorf("marshal json args error: %v", err) + return err + } + + url := fmt.Sprintf("%s/system/privateKey/%s", c.APIBase, args.ID.Hex()) + request, err := http.NewRequest("PUT", url, bytes.NewBuffer(body)) + if err != nil { + log.Errorf("UpdatePmHost new http request error: %v", err) + return err + } + + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + _, err := ioutil.ReadAll(ret.Body) + if err != nil { + return errors.WithMessage(err, "failed to get PmHost") + } + } + + return nil +} diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_pm.go b/pkg/microservice/cron/core/service/scheduler/schedule_pm.go new file mode 100644 index 00000000..8139eff3 --- /dev/null +++ b/pkg/microservice/cron/core/service/scheduler/schedule_pm.go @@ -0,0 +1,44 @@ +package scheduler + +import ( + "time" + + "github.com/koderover/zadig/pkg/microservice/cron/core/service" + "github.com/koderover/zadig/pkg/setting" + "go.uber.org/zap" +) + +func (c *CronClient) UpdatePmHostStatusScheduler(log *zap.SugaredLogger) { + hosts, err := c.AslanCli.ListPmHosts(log) + if err != nil { + log.Error(err) + return + } + + log.Info("start init pm host status scheduler..") + for _, hostElem := range hosts { + go func(hostPm *service.PrivateKeyHosts, log *zap.SugaredLogger) { + if hostPm.Port == 0 { + hostPm.Port = setting.PMHostDefaultPort + } + newStatus := setting.PMHostStatusAbnormal + msg, err := doTCPProbe(hostPm.IP, int(hostPm.Port), 3*time.Second, log) + if err != nil { + log.Errorf("doTCPProbe TCP %s:%d err: %s)", hostPm.IP, hostPm.Port, err) + } + if msg == Success { + newStatus = setting.PMHostStatusNormal + } + + if hostPm.Status == newStatus { + return + } + hostPm.Status = newStatus + + err = c.AslanCli.UpdatePmHost(hostPm, log) + if err != nil { + log.Error(err) + } + }(hostElem, log) + } +} diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go index a3444de7..561935a3 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go @@ -123,7 +123,7 @@ func (c *CronClient) UpsertWorkflowScheduler(log *zap.SugaredLogger) { ScheduleNames := sets.NewString( CleanJobScheduler, UpsertWorkflowScheduler, UpsertTestScheduler, InitStatScheduler, InitOperationStatScheduler, - CleanProductScheduler, InitHealthCheckScheduler, UpsertColliePipelineScheduler) + CleanProductScheduler, InitHealthCheckScheduler, InitHealthCheckPmHostScheduler, UpsertColliePipelineScheduler) // 停掉已被删除的pipeline对应的scheduler for name := range c.Schedulers { diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 8b1fc7c2..69a254d5 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -72,6 +72,8 @@ const ( SystemCapacityGC = "SystemCapacityGC" InitHealthCheckScheduler = "InitHealthCheckScheduler" + + InitHealthCheckPmHostScheduler = "InitHealthCheckPmHostScheduler" ) // NewCronClient ... @@ -155,6 +157,8 @@ func (c *CronClient) Init() { c.InitPullSonarStatScheduler() // 定时初始化健康检查 c.InitHealthCheckScheduler() + // Timing probe host status + c.InitHealthCheckPmHostScheduler() } func (c *CronClient) InitCleanJobScheduler() { @@ -254,3 +258,12 @@ func (c *CronClient) InitHealthCheckScheduler() { c.Schedulers[InitHealthCheckScheduler].Start() } + +func (c *CronClient) InitHealthCheckPmHostScheduler() { + + c.Schedulers[InitHealthCheckPmHostScheduler] = gocron.NewScheduler() + + c.Schedulers[InitHealthCheckPmHostScheduler].Every(10).Seconds().Do(c.UpdatePmHostStatusScheduler, c.log) + + c.Schedulers[InitHealthCheckPmHostScheduler].Start() +} diff --git a/pkg/microservice/cron/core/service/service.go b/pkg/microservice/cron/core/service/service.go index 00998f49..ee4ad151 100644 --- a/pkg/microservice/cron/core/service/service.go +++ b/pkg/microservice/cron/core/service/service.go @@ -16,6 +16,11 @@ limitations under the License. package service +import ( + "github.com/koderover/zadig/pkg/setting" + "go.mongodb.org/mongo-driver/bson/primitive" +) + type SvcRevision struct { ServiceName string `json:"service_name"` Type string `json:"type"` @@ -99,6 +104,14 @@ type PmHealthCheck struct { CurrentUnhealthyNum int `json:"current_unhealthy_num,omitempty"` } +type PrivateKeyHosts struct { + ID primitive.ObjectID `json:"id,omitempty"` + IP string `json:"ip"` + Port int64 `json:"port"` + Status setting.PMHostStatus `json:"status"` + UpdateStatus bool `json:"update_status"` +} + type EnvStatus struct { HostID string `json:"host_id"` EnvName string `json:"env_name"` diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index bed14d73..e468baad 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -171,6 +171,7 @@ type SSH struct { Name string `json:"name"` UserName string `json:"user_name"` IP string `json:"ip"` + Port int64 `json:"port"` IsProd bool `json:"is_prod"` Label string `json:"label"` PrivateKey string `json:"private_key"` diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index d1321fa5..b60b1d2d 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -347,6 +347,7 @@ func (r *Reaper) RunPMDeployScripts() error { cmd.Env = append(cmd.Env, fmt.Sprintf("%s_PK=%s", ssh.Name, filepath.Join(os.TempDir(), ssh.Name+"_PK"))) cmd.Env = append(cmd.Env, fmt.Sprintf("%s_IP=%s", ssh.Name, ssh.IP)) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s_PORT=%d", ssh.Name, ssh.Port)) cmd.Env = append(cmd.Env, fmt.Sprintf("%s_USERNAME=%s", ssh.Name, ssh.UserName)) r.Ctx.SecretEnvs = append(r.Ctx.SecretEnvs, fmt.Sprintf("%s_PK=%s", ssh.Name, filepath.Join(os.TempDir(), ssh.Name+"_PK"))) diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 0305efb0..ae6e4310 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -205,6 +205,7 @@ type SSH struct { Name string `json:"name"` UserName string `json:"user_name"` IP string `json:"ip"` + Port int64 `json:"port"` IsProd bool `json:"is_prod"` Label string `json:"label"` PrivateKey string `json:"private_key"` diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index d5a91f91..a075d31a 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -590,6 +590,15 @@ const ( Abnormal K8SClusterStatus = "abnormal" ) +type PMHostStatus string + +const ( + PMHostStatusNormal PMHostStatus = "normal" + PMHostStatusAbnormal PMHostStatus = "abnormal" +) + +const PMHostDefaultPort int64 = 22 + type ResetImagePolicyType string const ( diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 174815c7..ae1d3737 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -193,6 +193,8 @@ var ( ErrUpdateService = NewHTTPError(6092, "更新服务失败") // ErrListPodEvents ... ErrListPodEvents = NewHTTPError(6093, "列出服务事件失败") + // ErrLoginPm ... + ErrLoginPm = NewHTTPError(6099, "登陆主机失败") //----------------------------------------------------------------------------------------------- // it report APIs Range: 6100 - 6199 diff --git a/pkg/tool/ssh/ssh.go b/pkg/tool/ssh/ssh.go new file mode 100644 index 00000000..13c2a5e3 --- /dev/null +++ b/pkg/tool/ssh/ssh.go @@ -0,0 +1,35 @@ +package ssh + +import ( + "fmt" + "net" + "time" + + "golang.org/x/crypto/ssh" +) + +func NewSshCli(privateKey []byte, user, host string, port int64) (*ssh.Client, error) { + var ( + clientConfig *ssh.ClientConfig + client *ssh.Client + err error + ) + + sign, err := ssh.ParsePrivateKey(privateKey) + if err != nil { + return nil, err + } + auth := []ssh.AuthMethod{ssh.PublicKeys(sign)} + clientConfig = &ssh.ClientConfig{ + User: user, + Auth: auth, + Timeout: 20 * time.Second, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + } + + addr := fmt.Sprintf("%s:%d", host, port) + client, err = ssh.Dial("tcp", addr, clientConfig) + return client, err +} diff --git a/pkg/tool/wsconn/wsconn.go b/pkg/tool/wsconn/wsconn.go new file mode 100644 index 00000000..4ab99e02 --- /dev/null +++ b/pkg/tool/wsconn/wsconn.go @@ -0,0 +1,148 @@ +package wsconn + +import ( + "bufio" + "time" + "unicode/utf8" + + "github.com/gorilla/websocket" + "golang.org/x/crypto/ssh" + + "github.com/koderover/zadig/pkg/tool/log" +) + +type ptyReqParam struct { + Term string + Cols uint32 + Rows uint32 + Width uint32 + Height uint32 + Modelist string +} + +type SshClient struct { + SshSession *ssh.Session + SshCli *ssh.Client + sshChan ssh.Channel +} + +func (sshCli *SshClient) GenerateWebTerminal(cols, rows uint32) error { + session, err := sshCli.SshCli.NewSession() + if err != nil { + return err + } + sshCli.SshSession = session + channel, inRequests, err := sshCli.SshCli.OpenChannel("session", nil) + if err != nil { + return err + } + sshCli.sshChan = channel + go func() { + for req := range inRequests { + if req.WantReply { + req.Reply(false, nil) + } + } + }() + termModes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + var termModesList []byte + for k, v := range termModes { + kv := struct { + Key byte + Val uint32 + }{k, v} + termModesList = append(termModesList, ssh.Marshal(&kv)...) + } + termModesList = append(termModesList, 0) + reqParam := ptyReqParam{ + Term: "xterm", + Cols: cols, + Rows: rows, + Width: uint32(cols * 8), + Height: uint32(cols * 8), + Modelist: string(termModesList), + } + ok, err := channel.SendRequest("pty-req", true, ssh.Marshal(&reqParam)) + if !ok || err != nil { + return err + } + ok, err = channel.SendRequest("shell", true, nil) + if !ok || err != nil { + return err + } + return nil +} + +func (sshCli *SshClient) ConnectWs(ws *websocket.Conn) { + go func() { + for { + _, p, err := ws.ReadMessage() + if err != nil { + return + } + _, err = sshCli.sshChan.Write(p) + if err != nil { + return + } + } + }() + + go func() { + bufferReader := bufio.NewReader(sshCli.sshChan) + buffer := []byte{} + ticker := time.NewTimer(time.Microsecond * 100) + defer ticker.Stop() + + rChan := make(chan rune) + go func() { + defer sshCli.SshCli.Close() + defer sshCli.sshChan.Close() + + for { + r, size, err := bufferReader.ReadRune() + if err != nil { + log.Error(err) + ws.WriteMessage(1, []byte("\033[31m websocket is closed\033[0m")) + ws.Close() + return + } + if size > 0 { + rChan <- r + } + } + }() + + for { + select { + case <-ticker.C: + if len(buffer) != 0 { + err := ws.WriteMessage(websocket.TextMessage, buffer) + buffer = []byte{} + if err != nil { + log.Error(err) + return + } + } + ticker.Reset(time.Microsecond * 100) + case dRune := <-rChan: + if dRune != utf8.RuneError { + byt := make([]byte, utf8.RuneLen(dRune)) + utf8.EncodeRune(byt, dRune) + buffer = append(buffer, byt...) + } else { + buffer = append(buffer, []byte("@")...) + } + } + } + }() + + defer func() { + if err := recover(); err != nil { + log.Error(err) + } + }() +} -- Gitee From d9539d92f6ba39673b1c7bf75ab2e8c5dc41646d Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 10:46:07 +0800 Subject: [PATCH 1977/2647] optimize helm deploy logic Signed-off-by: allenshen --- .../service/taskcontroller/task_handler.go | 74 +- .../service/taskcontroller/task_helper.go | 11 + .../core/service/taskcontroller/worker.go | 8 + .../core/service/taskplugin/deploy.go | 709 +++--------------- .../core/service/taskplugin/deploy_helm.go | 655 ++++++++++++++++ 5 files changed, 842 insertions(+), 615 deletions(-) create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 2f189a30..3fbcf8ab 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -26,6 +26,7 @@ import ( "github.com/nsqio/go-nsq" uuid "github.com/satori/go.uuid" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" @@ -295,30 +296,69 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren // Task is struct for worker var tasks []*Task + //tasks been preprocessed, map[serviceName]=[]Tasks + preProcessedTasks := make(map[string]*Task) + // helm deploy plugins map[fullServiceName]=>HelmDeployPlugin + helmDeployPlugins := make(map[string]*plugins.HelmDeployTaskPlugin, 0) + + // preprocess subTasks, make batchTask with multiple subTasks + // eg: multiple deploys of same helm chart + if stage.TaskType == config.TaskDeploy { + for fullServiceName, subTask := range stage.SubTasks { + deployTask, err := plugins.ToDeployTask(subTask) + if err != nil { + xl.Infof("failed to get deplot task, err: %s", err) + continue + } + if deployTask.ServiceType != setting.HelmDeployType { + continue + } + workerConcurrency = 1 + pluginInstance := plugins.InitializeHelmDeployTaskPlugin(config.TaskDeploy) + pluginInstance.Task = deployTask + batchTask := NewTask(ctx, h.executeTask, pluginInstance, subTask, stagePosition, fullServiceName, xl) + if _, ok := preProcessedTasks[deployTask.ServiceName]; !ok { + xl.Infof("new batch sub task of service name: %s, type: %s", fullServiceName, stage.TaskType) + preProcessedTasks[deployTask.ServiceName] = batchTask + } + helmDeployPlugins[fullServiceName] = pluginInstance + } + } // 每个SubTask会initiate一个plugin instance来执行 + preProcessedServices := sets.NewString() for serviceName, subTask := range stage.SubTasks { - var pluginInstance plugins.TaskPlugin xl.Infof("new sub task of service name: %s, type: %s", serviceName, stage.TaskType) - pluginInstance = pluginInitiator(stage.TaskType) - //xl.Errorf("%v", ctx.Value(CtxKeyBuildInfos)) - tasks = append(tasks, NewTask(ctx, h.executeTask, pluginInstance, subTask, stagePosition, serviceName, xl)) - } - // 判断subTask是否是deploy,如果是的话判断是否是helm类型的服务, - //todo helm类型的服务的部署暂时只支持串行执行 - for _, subTask := range stage.SubTasks { - if deploy, err := plugins.ToDeployTask(subTask); err == nil { - if deploy.ServiceType == "helm" { - workerConcurrency = 1 - break + if deployPlugin, ok := helmDeployPlugins[serviceName]; ok { + svcName := deployPlugin.Task.ServiceName + preProcessedTasks[svcName].AddRelatedTasks(helmDeployPlugins[serviceName]) + if !preProcessedServices.Has(svcName) { + tasks = append(tasks, preProcessedTasks[svcName]) } + preProcessedServices.Insert(svcName) + continue } + + var pluginInstance plugins.TaskPlugin + xl.Infof("new sub task of service name: %s, type: %s", serviceName, stage.TaskType) + pluginInstance = pluginInitiator(stage.TaskType) + taskObj := NewTask(ctx, h.executeTask, pluginInstance, subTask, stagePosition, serviceName, xl) + tasks = append(tasks, taskObj) } // 设置WorkPool来控制最大并发数和并发执行 workerPool := NewPool(tasks, workerConcurrency) // 发起workerConcurrency个并发执行,等待所有Task执行完成并返回 workerPool.Run() + + // set related task status + for _, helmDeployPlugin := range helmDeployPlugins { + if len(helmDeployPlugin.ContentPlugins) == 0 { + continue + } + updatePluginSubTask(helmDeployPlugin, pipelineTask, stagePosition, helmDeployPlugin.Task.ContainerName, xl) + } + // Worker is completed xl.Info("execution completed of subtasks in stage") stageStatus := getStageStatus(workerPool.Tasks, xl) @@ -480,11 +520,11 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl // 清除上一次错误信息 plugin.ResetError() - updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) + updatePluginSubTask(plugin, pipelineTask, pos, servicename, xl) h.SendAck() plugin.SetAckFunc(func() { - updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) + updatePluginSubTask(plugin, pipelineTask, pos, servicename, xl) h.SendAck() }) @@ -504,14 +544,14 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl xl.Infof("task status: %s", plugin.Status()) plugin.SetEndTime() - updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) + updatePluginSubTask(plugin, pipelineTask, pos, servicename, xl) return plugin.Status(), fmt.Errorf("pipeline task failed: task_handler:308") } xl.Infof("task status: %s", plugin.Status()) // 等待完成前, 更新 SubTask 执行结果到 PipelineTask - updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) + updatePluginSubTask(plugin, pipelineTask, pos, servicename, xl) h.SendAck() // 等待 SubTask 结束 @@ -528,7 +568,7 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } // 更新 SubTask 执行结果到 PipelineTask plugin.SetEndTime() - updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) + updatePluginSubTask(plugin, pipelineTask, pos, servicename, xl) h.SendAck() xl.Infof("end sub task [%s:%s]", plugin.Type(), plugin.Status()) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 939d55b1..3453133c 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -92,6 +92,17 @@ func initPipelineTask(pipelineTask *task.Task, xl *zap.SugaredLogger) { } } +func updatePluginSubTask(plugin plugins.TaskPlugin, pipelineTask *task.Task, pos int, servicename string, xl *zap.SugaredLogger) { + helmDeployPlugin, ok := plugin.(*plugins.HelmDeployTaskPlugin) + if ok && helmDeployPlugin != nil { + for _, p := range helmDeployPlugin.ContentPlugins { + updatePipelineSubTask(p.Task, pipelineTask, pos, p.Task.ContainerName, xl) + } + } else { + updatePipelineSubTask(plugin.GetTask(), pipelineTask, pos, servicename, xl) + } +} + // Notes: // 1.0中pos代表subtasks位置 // 2.0中pos代表stages位置 diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go b/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go index 538b482b..46cd99ec 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go @@ -105,6 +105,14 @@ func NewTask(ctx context.Context, taskExecutorFunc taskOperater, plugin plugins. } } +func (t *Task) AddRelatedTasks(helmDeployPlugin *plugins.HelmDeployTaskPlugin) { + plugin, ok := t.plugin.(*plugins.HelmDeployTaskPlugin) + if !ok || plugin == nil { + return + } + plugin.ContentPlugins = append(plugin.ContentPlugins, helmDeployPlugin) +} + // Run runs a Task and does appropriate accounting via a // given sync.WorkGroup. func (t *Task) Run(wg *sync.WaitGroup) { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index a8d03400..4d3ee991 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -19,40 +19,29 @@ package taskplugin import ( "context" "fmt" - "path/filepath" "regexp" "strings" "time" - helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.uber.org/zap" - "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/yaml" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" - "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" "github.com/koderover/zadig/pkg/shared/kube/wrapper" - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" - s3tool "github.com/koderover/zadig/pkg/tool/s3" - "github.com/koderover/zadig/pkg/util" - "github.com/koderover/zadig/pkg/util/converter" - fsutil "github.com/koderover/zadig/pkg/util/fs" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) // InitializeDeployTaskPlugin to initiate deploy task plugin and return ref @@ -138,52 +127,6 @@ type ResourceComponentSet interface { GetKind() string } -func RcsListFromDeployments(source []*appsv1.Deployment) []ResourceComponentSet { - rcsList := make([]ResourceComponentSet, 0, len(source)) - for _, deploy := range source { - rcsList = append(rcsList, wrapper.Deployment(deploy)) - } - return rcsList -} - -func RcsListFromStatefulSets(source []*appsv1.StatefulSet) []ResourceComponentSet { - rcsList := make([]ResourceComponentSet, 0, len(source)) - for _, sfs := range source { - rcsList = append(rcsList, wrapper.StatefulSet(sfs)) - } - return rcsList -} - -// find affected resources(deployment+statefulSet) for helm install or upgrade -// resource type: deployment statefulSet -func (p *DeployTaskPlugin) findHelmAffectedResources(namespace, serviceName string, resList []ResourceComponentSet) { - for _, res := range resList { - annotation := res.GetAnnotations() - if len(annotation) == 0 { - continue - } - // filter by services - if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { - extractedServiceName := util.ExtraServiceName(chartRelease, namespace) - if extractedServiceName != serviceName { - continue - } - } - for _, container := range res.GetContainers() { - resolvedImageUrl := resolveImageUrl(container.Image) - if resolvedImageUrl[setting.PathSearchComponentImage] == p.Task.ContainerName { - p.Log.Infof("%s find match container.name:%s container.image:%s", res.GetKind(), container.Name, container.Image) - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: res.GetKind(), - Container: container.Name, - Origin: container.Image, - Name: res.GetName(), - }) - } - } - } -} - func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ *task.PipelineCtx, _ string) { var ( err error @@ -214,406 +157,142 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } containerName := p.Task.ContainerName containerName = strings.TrimSuffix(containerName, "_"+p.Task.ServiceName) - if p.Task.ServiceType != setting.HelmDeployType { - // get servcie info - var ( - serviceInfo *types.ServiceTmpl - selector labels.Selector - ) - serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, p.Task.ProductName, 0) - if err != nil { - // Maybe it is a share service, the entity is not under the project - serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, "", 0) - if err != nil { - return - } - } - if serviceInfo.WorkloadType == "" { - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() - var deployments []*appsv1.Deployment - deployments, err = getter.ListDeployments(p.Task.Namespace, selector, p.kubeClient) - if err != nil { - return - } - - var statefulSets []*appsv1.StatefulSet - statefulSets, err = getter.ListStatefulSets(p.Task.Namespace, selector, p.kubeClient) - if err != nil { - return - } - - L: - for _, deploy := range deployments { - for _, container := range deploy.Spec.Template.Spec.Containers { - if container.Name == containerName { - err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, containerName, p.Task.Image, p.kubeClient) - if err != nil { - err = errors.WithMessagef( - err, - "failed to update container image in %s/deployments/%s/%s", - p.Task.Namespace, deploy.Name, container.Name) - return - } - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: setting.Deployment, - Container: container.Name, - Origin: container.Image, - Name: deploy.Name, - }) - replaced = true - break L - } - } - } - Loop: - for _, sts := range statefulSets { - for _, container := range sts.Spec.Template.Spec.Containers { - if container.Name == containerName { - err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, containerName, p.Task.Image, p.kubeClient) - if err != nil { - err = errors.WithMessagef( - err, - "failed to update container image in %s/statefulsets/%s/%s", - p.Task.Namespace, sts.Name, container.Name) - return - } - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: setting.StatefulSet, - Container: container.Name, - Origin: container.Image, - Name: sts.Name, - }) - replaced = true - break Loop - } - } - } - } else { - switch serviceInfo.WorkloadType { - case setting.StatefulSet: - var statefulSet *appsv1.StatefulSet - statefulSet, _, err = getter.GetStatefulSet(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) - if err != nil { - return - } - for _, container := range statefulSet.Spec.Template.Spec.Containers { - if container.Name == containerName { - err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, containerName, p.Task.Image, p.kubeClient) - if err != nil { - err = errors.WithMessagef( - err, - "failed to update container image in %s/statefulsets/%s/%s", - p.Task.Namespace, statefulSet.Name, container.Name) - return - } - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: setting.StatefulSet, - Container: container.Name, - Origin: container.Image, - Name: statefulSet.Name, - }) - replaced = true - break - } - } - case setting.Deployment: - var deployment *appsv1.Deployment - deployment, _, err = getter.GetDeployment(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) - if err != nil { - return - } - for _, container := range deployment.Spec.Template.Spec.Containers { - if container.Name == containerName { - err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, containerName, p.Task.Image, p.kubeClient) - if err != nil { - err = errors.WithMessagef( - err, - "failed to update container image in %s/deployments/%s/%s", - p.Task.Namespace, deployment.Name, container.Name) - return - } - p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ - Kind: setting.Deployment, - Container: container.Name, - Origin: container.Image, - Name: deployment.Name, - }) - replaced = true - break - } - } - } - } - if !replaced { - err = errors.Errorf( - "container %s is not found in resources with label %s", containerName, selector) + // get servcie info + var ( + serviceInfo *types.ServiceTmpl + selector labels.Selector + ) + serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, p.Task.ProductName, 0) + if err != nil { + // Maybe it is a share service, the entity is not under the project + serviceInfo, err = p.getService(ctx, p.Task.ServiceName, p.Task.ServiceType, "", 0) + if err != nil { return } - } else if p.Task.ServiceType == setting.HelmDeployType { - var ( - productInfo *types.Product - renderChart *types.RenderChart - replacedValuesYaml string - mergedValuesYaml string - replacedMergedValuesYaml string - servicePath string - chartPath string - replaceValuesMap map[string]interface{} - renderInfo *types.RenderSet - helmClient helmclient.Client - ) - - p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %s namespace %s", p.Task.ProductName, - p.Task.ServiceName, containerName, p.Task.Namespace) - - productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) + } + if serviceInfo.WorkloadType == "" { + selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() + + var deployments []*appsv1.Deployment + deployments, err = getter.ListDeployments(p.Task.Namespace, selector, p.kubeClient) if err != nil { - err = errors.WithMessagef( - err, - "failed to get product %s/%s", - p.Task.Namespace, p.Task.ServiceName) return } - renderInfo, err = p.getRenderSet(ctx, productInfo.Render.Name, productInfo.Render.Revision) + var statefulSets []*appsv1.StatefulSet + statefulSets, err = getter.ListStatefulSets(p.Task.Namespace, selector, p.kubeClient) if err != nil { - err = errors.WithMessagef( - err, - "failed to get getRenderSet %s/%d", - productInfo.Render.Name, productInfo.Render.Revision) return } - serviceRevisionInProduct := int64(0) - var targetContainer *types.Container - for _, service := range productInfo.GetServiceMap() { - if service.ServiceName == p.Task.ServiceName { - serviceRevisionInProduct = service.Revision - for _, container := range service.Containers { - if container.Name == containerName { - targetContainer = container - break + L: + for _, deploy := range deployments { + for _, container := range deploy.Spec.Template.Spec.Containers { + if container.Name == containerName { + err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, containerName, p.Task.Image, p.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/deployments/%s/%s", + p.Task.Namespace, deploy.Name, container.Name) + return } + p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ + Kind: setting.Deployment, + Container: container.Name, + Origin: container.Image, + Name: deploy.Name, + }) + replaced = true + break L } - break } } - - if targetContainer == nil { - err = errors.Errorf("failed to find target container %s from service %s", containerName, p.Task.ServiceName) - return - } - - if targetContainer.ImagePath == nil { - err = errors.Errorf("failed to get image path of %s from service %s", containerName, p.Task.ServiceName) - return - } - - for _, chartInfo := range renderInfo.ChartInfos { - if chartInfo.ServiceName == p.Task.ServiceName { - renderChart = chartInfo - break + Loop: + for _, sts := range statefulSets { + for _, container := range sts.Spec.Template.Spec.Containers { + if container.Name == containerName { + err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, containerName, p.Task.Image, p.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/statefulsets/%s/%s", + p.Task.Namespace, sts.Name, container.Name) + return + } + p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ + Kind: setting.StatefulSet, + Container: container.Name, + Origin: container.Image, + Name: sts.Name, + }) + replaced = true + break Loop + } } } - - if renderChart == nil { - err = errors.Errorf("failed to update container image in %s/%s,chart not found", - p.Task.Namespace, p.Task.ServiceName) - return - } - - // use revision of service currently applied in environment instead of the latest revision - path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, - pipelineTask.StorageURI, serviceRevisionInProduct) - if errDownload != nil { - p.Log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", - serviceRevisionInProduct, p.Task.ServiceName) - path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, - pipelineTask.StorageURI, 0) - if errDownload != nil { - err = errors.WithMessagef( - errDownload, - "failed to download service %s/%s", - p.Task.Namespace, p.Task.ServiceName) + } else { + switch serviceInfo.WorkloadType { + case setting.StatefulSet: + var statefulSet *appsv1.StatefulSet + statefulSet, _, err = getter.GetStatefulSet(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) + if err != nil { return } - } - - chartPath, err = fsutil.RelativeToCurrentPath(path) - if err != nil { - err = errors.WithMessagef( - err, - "failed to get relative path %s", - servicePath, - ) - return - } - - serviceValuesYaml := renderChart.ValuesYaml - - // prepare image replace info - validMatchData := getValidMatchData(targetContainer.ImagePath) - - replaceValuesMap, err = assignImageData(p.Task.Image, validMatchData) - if err != nil { - err = errors.WithMessagef( - err, - "failed to pase image uri %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - - // replace image into service's values.yaml - replacedValuesYaml, err = replaceImage(serviceValuesYaml, replaceValuesMap) - if err != nil { - err = errors.WithMessagef( - err, - "failed to replace image uri %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - if replacedValuesYaml == "" { - err = errors.Errorf("failed to set new image uri into service's values.yaml %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - - // merge override values and kvs into service's yaml - mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) - if err != nil { - err = errors.WithMessagef( - err, - "failed to merge override values %s", - renderChart.OverrideValues, - ) - return - } - - // replace image into final merged values.yaml - replacedMergedValuesYaml, err = replaceImage(mergedValuesYaml, replaceValuesMap) - if err != nil { - err = errors.WithMessagef( - err, - "failed to replace image uri into helm values %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - if replacedMergedValuesYaml == "" { - err = errors.Errorf("failed to set image uri into mreged values.yaml in %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - - p.Log.Infof("final replaced merged values: \n%s", replacedMergedValuesYaml) - - helmClient, err = helmtool.NewClientFromNamespace(pipelineTask.ConfigPayload.DeployClusterID, p.Task.Namespace) - if err != nil { - err = errors.WithMessagef( - err, - "failed to create helm client %s/%s", - p.Task.Namespace, p.Task.ServiceName) - return - } - - releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) - - ensureUpgrade := func() error { - hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) - if errHistory != nil { - // list history should not block deploy operation, error will be logged instead of returned - p.Log.Errorf("failed to list release history, release: %s, err: %s", releaseName, errHistory) - return nil - } - if len(hrs) == 0 { - return nil - } - releaseutil.Reverse(hrs, releaseutil.SortByRevision) - rel := hrs[0] - - if rel.Info.Status.IsPending() { - return fmt.Errorf("failed to upgrade release: %s with exceptional status: %s", releaseName, rel.Info.Status) + for _, container := range statefulSet.Spec.Template.Spec.Containers { + if container.Name == containerName { + err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, containerName, p.Task.Image, p.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/statefulsets/%s/%s", + p.Task.Namespace, statefulSet.Name, container.Name) + return + } + p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ + Kind: setting.StatefulSet, + Container: container.Name, + Origin: container.Image, + Name: statefulSet.Name, + }) + replaced = true + break + } } - return nil - } - - err = ensureUpgrade() - if err != nil { - return - } - - timeOut := p.TaskTimeout() - chartSpec := helmclient.ChartSpec{ - ReleaseName: releaseName, - ChartName: chartPath, - Namespace: p.Task.Namespace, - ReuseValues: true, - Version: renderChart.ChartVersion, - ValuesYaml: replacedMergedValuesYaml, - SkipCRDs: false, - UpgradeCRDs: true, - Timeout: time.Second * time.Duration(timeOut), - Wait: true, - Replace: true, - MaxHistory: 10, - } - - done := make(chan bool) - go func(chan bool) { - if _, err = helmClient.InstallOrUpgradeChart(ctx, &chartSpec); err != nil { - err = errors.WithMessagef( - err, - "failed to upgrade helm chart %s/%s", - p.Task.Namespace, p.Task.ServiceName) - done <- false - } else { - done <- true + case setting.Deployment: + var deployment *appsv1.Deployment + deployment, _, err = getter.GetDeployment(p.Task.Namespace, p.Task.ServiceName, p.kubeClient) + if err != nil { + return } - }(done) - - select { - case <-done: - break - case <-time.After(chartSpec.Timeout + time.Minute): - err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) - } - if err != nil { - return - } - - //替换环境变量中的chartInfos - for _, chartInfo := range renderInfo.ChartInfos { - if chartInfo.ServiceName == p.Task.ServiceName { - chartInfo.ValuesYaml = replacedValuesYaml - break + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == containerName { + err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, containerName, p.Task.Image, p.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/deployments/%s/%s", + p.Task.Namespace, deployment.Name, container.Name) + return + } + p.Task.ReplaceResources = append(p.Task.ReplaceResources, task.Resource{ + Kind: setting.Deployment, + Container: container.Name, + Origin: container.Image, + Name: deployment.Name, + }) + replaced = true + break + } } } - - // TODO too dangerous to override entire renderset! - err = p.updateRenderSet(ctx, &types.RenderSet{ - Name: renderInfo.Name, - Revision: renderInfo.Revision, - DefaultValues: renderInfo.DefaultValues, - ChartInfos: renderInfo.ChartInfos, - }) - if err != nil { - err = errors.WithMessagef( - err, - "failed to update renderset info %s/%s, renderset %s", - p.Task.Namespace, p.Task.ServiceName, renderInfo.Name) - } } -} - -func (p *DeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs) (*types.Product, error) { - url := fmt.Sprintf("/api/environment/environments/%s/productInfo", args.EnvName) - - prod := &types.Product{} - _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("projectName", args.ProductName)) - if err != nil { - return nil, err + if !replaced { + err = errors.Errorf( + "container %s is not found in resources with label %s", containerName, selector) + return } - return prod, nil } func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, productName string, revision int64) (*types.ServiceTmpl, error) { @@ -630,166 +309,6 @@ func (p *DeployTaskPlugin) getService(ctx context.Context, name, serviceType, pr return s, nil } -// download chart info of specific version, use the latest version if fails -func (p *DeployTaskPlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { - logger := p.Log - - fileName := serviceName - if revision > 0 { - fileName = fmt.Sprintf("%s-%d", serviceName, revision) - } - tarball := fmt.Sprintf("%s.tar.gz", fileName) - localBase := configbase.LocalServicePath(productName, serviceName) - tarFilePath := filepath.Join(localBase, tarball) - - exists, err := fsutil.FileExists(tarFilePath) - if err != nil { - return "", err - } - if exists { - return tarFilePath, nil - } - - s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) - if err != nil { - return "", err - } - - s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, configbase.ObjectStorageServicePath(productName, serviceName)) - forcedPathStyle := true - if s3Storage.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - s3Client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) - if err != nil { - p.Log.Errorf("failed to create s3 client, err: %s", err) - return "", err - } - if err = s3Client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { - logger.Errorf("failed to download file from s3, err: %s", err) - return "", err - } - - exists, err = fsutil.FileExists(tarFilePath) - if err != nil { - return "", err - } - if !exists { - return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) - } - - return tarFilePath, nil -} - -func (p *DeployTaskPlugin) getRenderSet(ctx context.Context, name string, revision int64) (*types.RenderSet, error) { - url := fmt.Sprintf("/api/project/renders/render/%s/revision/%d", name, revision) - - rs := &types.RenderSet{} - _, err := p.httpClient.Get(url, httpclient.SetResult(rs)) - if err != nil { - return nil, err - } - - return rs, nil -} - -func (p *DeployTaskPlugin) updateRenderSet(ctx context.Context, args *types.RenderSet) error { - url := "/api/project/renders" - - _, err := p.httpClient.Put(url, httpclient.SetBody(args)) - - return err -} - -func getValidMatchData(spec *types.ImagePathSpec) map[string]string { - ret := make(map[string]string) - if spec.Repo != "" { - ret[setting.PathSearchComponentRepo] = spec.Repo - } - if spec.Image != "" { - ret[setting.PathSearchComponentImage] = spec.Image - } - if spec.Tag != "" { - ret[setting.PathSearchComponentTag] = spec.Tag - } - return ret -} - -// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx -func resolveImageUrl(imageUrl string) map[string]string { - subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) - result := make(map[string]string) - exNames := imageParseRegex.SubexpNames() - for i, matchedStr := range subMatchAll { - if i != 0 && matchedStr != "" && matchedStr != ":" { - result[exNames[i]] = matchedStr - } - } - return result -} - -// replace image defines in yaml by new version -func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { - nestedMap, err := converter.Expand(imageValuesMap) - if err != nil { - return "", err - } - bs, err := yaml.Marshal(nestedMap) - if err != nil { - return "", err - } - mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) - if err != nil { - return "", err - } - return string(mergedBs), nil -} - -// assignImageData assign image url data into match data -// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path -// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value -func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { - ret := make(map[string]interface{}) - // total image url assigned into one single value - if len(matchData) == 1 { - for _, v := range matchData { - ret[v] = imageUrl - } - return ret, nil - } - - resolvedImageUrl := resolveImageUrl(imageUrl) - - // image url assigned into repo/image+tag - if len(matchData) == 3 { - ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") - ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] - ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] - return ret, nil - } - - if len(matchData) == 2 { - // image url assigned into repo/image + tag - if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { - ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] - for k, imagePath := range matchData { - if k == setting.PathSearchComponentTag { - continue - } - ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) - break - } - return ret, nil - } - // image url assigned into repo + image(tag) - ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") - ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) - return ret, nil - } - - return nil, errors.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) -} - // Wait ... func (p *DeployTaskPlugin) Wait(ctx context.Context) { // skip waiting for reset image task @@ -798,12 +317,6 @@ func (p *DeployTaskPlugin) Wait(ctx context.Context) { return } - // for services deployed by helm, use --wait option to ensure related resources are updated - if p.Task.ServiceType == setting.HelmDeployType { - p.Task.TaskStatus = config.StatusPassed - return - } - timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go new file mode 100644 index 00000000..b992a707 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -0,0 +1,655 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "path/filepath" + "strings" + "time" + + helmclient "github.com/mittwald/go-helm-client" + "github.com/pkg/errors" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/releaseutil" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/httpclient" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util" + "github.com/koderover/zadig/pkg/util/converter" + fsutil "github.com/koderover/zadig/pkg/util/fs" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" +) + +// InitializeHelmDeployTaskPlugin initiates a plugin to deploy helm charts and return ref +func InitializeHelmDeployTaskPlugin(taskType config.TaskType) *HelmDeployTaskPlugin { + return &HelmDeployTaskPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + restConfig: krkubeclient.RESTConfig(), + httpClient: httpclient.New( + httpclient.SetHostURL(configbase.AslanServiceAddress()), + ), + } +} + +// HelmDeployTaskPlugin Plugin name should be compatible with task type +type HelmDeployTaskPlugin struct { + Name config.TaskType + JobName string + kubeClient client.Client + restConfig *rest.Config + Task *task.Deploy + Log *zap.SugaredLogger + ReplaceImage string + + ContentPlugins []*HelmDeployTaskPlugin + httpClient *httpclient.Client +} + +func (p *HelmDeployTaskPlugin) SetAckFunc(func()) {} + +func (p *HelmDeployTaskPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.Log = xl +} + +func (p *HelmDeployTaskPlugin) Type() config.TaskType { + return p.Name +} + +func (p *HelmDeployTaskPlugin) Status() config.Status { + return p.Task.TaskStatus +} + +func (p *HelmDeployTaskPlugin) SetStatus(status config.Status) { + p.Task.TaskStatus = status + for _, subPlugin := range p.ContentPlugins { + subPlugin.Task.TaskStatus = status + } +} + +func (p *HelmDeployTaskPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = setting.DeployTimeout + } else { + if !p.Task.IsRestart { + p.Task.Timeout = p.Task.Timeout * 60 + } + } + return p.Task.Timeout +} + +func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ *task.PipelineCtx, _ string) { + var ( + err error + ) + + defer func() { + if err != nil { + p.Log.Error(err) + p.SetStatus(config.StatusFailed) + p.Task.Error = err.Error() + return + } + }() + + if pipelineTask.ConfigPayload.DeployClusterID != "" { + p.restConfig, err = kubeclient.GetRESTConfig(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) + if err != nil { + err = errors.WithMessage(err, "can't get k8s rest config") + return + } + + p.kubeClient, err = kubeclient.GetKubeClient(pipelineTask.ConfigPayload.HubServerAddr, pipelineTask.ConfigPayload.DeployClusterID) + if err != nil { + err = errors.WithMessage(err, "can't init k8s client") + return + } + } + // all involved containers + containerNameSet := sets.NewString() + for _, sPlugin := range p.ContentPlugins { + singleContainerName := strings.TrimSuffix(sPlugin.Task.ContainerName, "_"+p.Task.ServiceName) + containerNameSet.Insert(singleContainerName) + } + + var ( + productInfo *types.Product + renderChart *types.RenderChart + replacedValuesYaml string + mergedValuesYaml string + replacedMergedValuesYaml string + servicePath string + chartPath string + replaceValuesMap map[string]interface{} + renderInfo *types.RenderSet + helmClient helmclient.Client + ) + + p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %v namespace %s", p.Task.ProductName, + p.Task.ServiceName, containerNameSet.List(), p.Task.Namespace) + + productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get product %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + + renderInfo, err = p.getRenderSet(ctx, productInfo.Render.Name, productInfo.Render.Revision) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get getRenderSet %s/%d", + productInfo.Render.Name, productInfo.Render.Revision) + return + } + + serviceRevisionInProduct := int64(0) + involvedImagePaths := make(map[string]*types.ImagePathSpec) + for _, service := range productInfo.GetServiceMap() { + if service.ServiceName != p.Task.ServiceName { + continue + } + serviceRevisionInProduct = service.Revision + for _, container := range service.Containers { + if !containerNameSet.Has(container.Name) { + continue + } + if container.ImagePath == nil { + err = errors.WithMessagef(err, "image path of %s/%s is nil", service.ServiceName, container.Name) + return + } + involvedImagePaths[container.Name] = container.ImagePath + } + break + } + + if len(involvedImagePaths) == 0 { + err = errors.Errorf("failed to find containers from service %s", p.Task.ServiceName) + return + } + + for _, chartInfo := range renderInfo.ChartInfos { + if chartInfo.ServiceName == p.Task.ServiceName { + renderChart = chartInfo + break + } + } + + if renderChart == nil { + err = errors.Errorf("failed to update container image in %s/%s,chart not found", + p.Task.Namespace, p.Task.ServiceName) + return + } + + // use revision of service currently applied in environment instead of the latest revision + path, errDownload := p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, pipelineTask.StorageURI, serviceRevisionInProduct) + if errDownload != nil { + p.Log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + serviceRevisionInProduct, p.Task.ServiceName) + path, errDownload = p.downloadService(pipelineTask.ProductName, p.Task.ServiceName, + pipelineTask.StorageURI, 0) + if errDownload != nil { + err = errors.WithMessagef( + errDownload, + "failed to download service %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + } + + chartPath, err = fsutil.RelativeToCurrentPath(path) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get relative path %s", + servicePath, + ) + return + } + + serviceValuesYaml := renderChart.ValuesYaml + replaceValuesMap = make(map[string]interface{}) + + for _, sPlugin := range p.ContentPlugins { + containerName := strings.TrimSuffix(sPlugin.Task.ContainerName, "_"+p.Task.ServiceName) + if imagePath, ok := involvedImagePaths[containerName]; ok { + validMatchData := getValidMatchData(imagePath) + singleReplaceValuesMap, errAssign := assignImageData(sPlugin.Task.Image, validMatchData) + if errAssign != nil { + err = errors.WithMessagef( + errAssign, + "failed to pase image uri %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + for k, v := range singleReplaceValuesMap { + replaceValuesMap[k] = v + } + } + } + + // replace image into service's values.yaml + replacedValuesYaml, err = replaceImage(serviceValuesYaml, replaceValuesMap) + if err != nil { + err = errors.WithMessagef( + err, + "failed to replace image uri %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + if replacedValuesYaml == "" { + err = errors.Errorf("failed to set new image uri into service's values.yaml %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + + // merge override values and kvs into service's yaml + mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + if err != nil { + err = errors.WithMessagef( + err, + "failed to merge override values %s", + renderChart.OverrideValues, + ) + return + } + + // replace image into final merged values.yaml + replacedMergedValuesYaml, err = replaceImage(mergedValuesYaml, replaceValuesMap) + if err != nil { + err = errors.WithMessagef( + err, + "failed to replace image uri into helm values %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + if replacedMergedValuesYaml == "" { + err = errors.Errorf("failed to set image uri into mreged values.yaml in %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + + p.Log.Infof("final replaced merged values: \n%s", replacedMergedValuesYaml) + + helmClient, err = helmtool.NewClientFromNamespace(pipelineTask.ConfigPayload.DeployClusterID, p.Task.Namespace) + if err != nil { + err = errors.WithMessagef( + err, + "failed to create helm client %s/%s", + p.Task.Namespace, p.Task.ServiceName) + return + } + + releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) + + ensureUpgrade := func() error { + hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) + if errHistory != nil { + // list history should not block deploy operation, error will be logged instead of returned + p.Log.Errorf("failed to list release history, release: %s, err: %s", releaseName, errHistory) + return nil + } + if len(hrs) == 0 { + return nil + } + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + + if rel.Info.Status.IsPending() { + return fmt.Errorf("failed to upgrade release: %s with exceptional status: %s", releaseName, rel.Info.Status) + } + return nil + } + + err = ensureUpgrade() + if err != nil { + return + } + + timeOut := p.TaskTimeout() + chartSpec := helmclient.ChartSpec{ + ReleaseName: releaseName, + ChartName: chartPath, + Namespace: p.Task.Namespace, + ReuseValues: true, + Version: renderChart.ChartVersion, + ValuesYaml: replacedMergedValuesYaml, + SkipCRDs: false, + UpgradeCRDs: true, + Timeout: time.Second * time.Duration(timeOut), + Wait: true, + Replace: true, + MaxHistory: 10, + } + + done := make(chan bool) + go func(chan bool) { + if _, err = helmClient.InstallOrUpgradeChart(ctx, &chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to upgrade helm chart %s/%s", + p.Task.Namespace, p.Task.ServiceName) + done <- false + } else { + done <- true + } + }(done) + + select { + case <-done: + break + case <-time.After(chartSpec.Timeout + time.Minute): + err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) + } + if err != nil { + return + } + + //替换环境变量中的chartInfos + for _, chartInfo := range renderInfo.ChartInfos { + if chartInfo.ServiceName == p.Task.ServiceName { + chartInfo.ValuesYaml = replacedValuesYaml + break + } + } + + // TODO too dangerous to override entire renderset! + err = p.updateRenderSet(ctx, &types.RenderSet{ + Name: renderInfo.Name, + Revision: renderInfo.Revision, + DefaultValues: renderInfo.DefaultValues, + ChartInfos: renderInfo.ChartInfos, + }) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update renderset info %s/%s, renderset %s", + p.Task.Namespace, p.Task.ServiceName, renderInfo.Name) + } +} + +func (p *HelmDeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs) (*types.Product, error) { + url := fmt.Sprintf("/api/environment/environments/%s/productInfo", args.EnvName) + prod := &types.Product{} + _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("projectName", args.ProductName)) + if err != nil { + return nil, err + } + return prod, nil +} + +func (p *HelmDeployTaskPlugin) getService(ctx context.Context, name, serviceType, productName string, revision int64) (*types.ServiceTmpl, error) { + url := fmt.Sprintf("/api/service/services/%s/%s", name, serviceType) + s := &types.ServiceTmpl{} + _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ + "projectName": productName, + "revision": fmt.Sprintf("%d", revision), + })) + if err != nil { + return nil, err + } + return s, nil +} + +// download chart info of specific version, use the latest version if fails +func (p *HelmDeployTaskPlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { + logger := p.Log + + fileName := serviceName + if revision > 0 { + fileName = fmt.Sprintf("%s-%d", serviceName, revision) + } + tarball := fmt.Sprintf("%s.tar.gz", fileName) + localBase := configbase.LocalServicePath(productName, serviceName) + tarFilePath := filepath.Join(localBase, tarball) + + exists, err := fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if exists { + return tarFilePath, nil + } + + s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) + if err != nil { + return "", err + } + + s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, configbase.ObjectStorageServicePath(productName, serviceName)) + forcedPathStyle := true + if s3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3Client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err != nil { + p.Log.Errorf("failed to create s3 client, err: %s", err) + return "", err + } + if err = s3Client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { + logger.Errorf("failed to download file from s3, err: %s", err) + return "", err + } + + exists, err = fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if !exists { + return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) + } + return tarFilePath, nil +} + +func (p *HelmDeployTaskPlugin) getRenderSet(ctx context.Context, name string, revision int64) (*types.RenderSet, error) { + url := fmt.Sprintf("/api/project/renders/render/%s/revision/%d", name, revision) + rs := &types.RenderSet{} + _, err := p.httpClient.Get(url, httpclient.SetResult(rs)) + if err != nil { + return nil, err + } + return rs, nil +} + +func getValidMatchData(spec *types.ImagePathSpec) map[string]string { + ret := make(map[string]string) + if spec.Repo != "" { + ret[setting.PathSearchComponentRepo] = spec.Repo + } + if spec.Image != "" { + ret[setting.PathSearchComponentImage] = spec.Image + } + if spec.Tag != "" { + ret[setting.PathSearchComponentTag] = spec.Tag + } + return ret +} + +// replace image defines in yaml by new version +func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { + nestedMap, err := converter.Expand(imageValuesMap) + if err != nil { + return "", err + } + bs, err := yaml.Marshal(nestedMap) + if err != nil { + return "", err + } + mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) + if err != nil { + return "", err + } + return string(mergedBs), nil +} + +// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx +func resolveImageUrl(imageUrl string) map[string]string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + result := make(map[string]string) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + result[exNames[i]] = matchedStr + } + } + return result +} + +// assignImageData assign image url data into match data +// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path +// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value +func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { + ret := make(map[string]interface{}) + // total image url assigned into one single value + if len(matchData) == 1 { + for _, v := range matchData { + ret[v] = imageUrl + } + return ret, nil + } + + resolvedImageUrl := resolveImageUrl(imageUrl) + + // image url assigned into repo/image+tag + if len(matchData) == 3 { + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] + ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] + return ret, nil + } + + if len(matchData) == 2 { + // image url assigned into repo/image + tag + if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { + ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] + for k, imagePath := range matchData { + if k == setting.PathSearchComponentTag { + continue + } + ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) + break + } + return ret, nil + } + // image url assigned into repo + image(tag) + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) + return ret, nil + } + + return nil, errors.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) +} + +func (p *HelmDeployTaskPlugin) updateRenderSet(ctx context.Context, args *types.RenderSet) error { + url := "/api/project/renders" + _, err := p.httpClient.Put(url, httpclient.SetBody(args)) + return err +} + +// Wait ... +func (p *HelmDeployTaskPlugin) Wait(ctx context.Context) { + // skip waiting for reset image task + if p.Task.SkipWaiting { + p.SetStatus(config.StatusPassed) + return + } + + // for services deployed by helm, use --wait option to ensure related resources are updated + p.SetStatus(config.StatusPassed) + return +} + +func (p *HelmDeployTaskPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { +} + +func (p *HelmDeployTaskPlugin) SetTask(t map[string]interface{}) error { + task, err := ToDeployTask(t) + if err != nil { + return err + } + p.Task = task + return nil +} + +// GetTask ... +func (p *HelmDeployTaskPlugin) GetTask() interface{} { + return p.Task +} + +// IsTaskDone ... +func (p *HelmDeployTaskPlugin) IsTaskDone() bool { + if p.Task.TaskStatus != config.StatusCreated && p.Task.TaskStatus != config.StatusRunning { + return true + } + return false +} + +// IsTaskFailed ... +func (p *HelmDeployTaskPlugin) IsTaskFailed() bool { + if p.Task.TaskStatus == config.StatusFailed || p.Task.TaskStatus == config.StatusTimeout || p.Task.TaskStatus == config.StatusCancelled { + return true + } + return false +} + +// SetStartTime ... +func (p *HelmDeployTaskPlugin) SetStartTime() { + p.Task.StartTime = time.Now().Unix() + for _, subPlugin := range p.ContentPlugins { + subPlugin.Task.StartTime = p.Task.StartTime + } +} + +// SetEndTime ... +func (p *HelmDeployTaskPlugin) SetEndTime() { + p.Task.EndTime = time.Now().Unix() + for _, subPlugin := range p.ContentPlugins { + subPlugin.Task.EndTime = p.Task.EndTime + } +} + +// IsTaskEnabled ... +func (p *HelmDeployTaskPlugin) IsTaskEnabled() bool { + return p.Task.Enabled +} + +// ResetError ... +func (p *HelmDeployTaskPlugin) ResetError() { + p.Task.Error = "" + for _, subPlugin := range p.ContentPlugins { + subPlugin.Task.Error = "" + } +} -- Gitee From 4f89fe430dd5b0d16946f999206aac0015f60628 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 30 Mar 2022 10:53:37 +0800 Subject: [PATCH 1978/2647] changed the logic of getting workload info Signed-off-by: Min Min --- .../service/workflow/pipeline_validation.go | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 9fd314dd..cf2e8a99 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + appsv1 "k8s.io/api/apps/v1" "regexp" "sort" "strconv" @@ -42,14 +43,12 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/labels" ) const ( @@ -704,6 +703,11 @@ func getImageInfoFromWorkload(envName, productName, serviceName, container strin return "", err } + serviceInfo, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + }) + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { return "", err @@ -713,34 +717,34 @@ func getImageInfoFromWorkload(envName, productName, serviceName, container strin return findCurrentlyUsingImage(product, serviceName, container) } - var selector labels.Selector - //helm和托管类型的服务查询所有标签的pod - if product.Source != setting.SourceFromHelm && product.Source != setting.SourceFromExternal { - selector = labels.Set{setting.ProductLabel: productName, setting.ServiceLabel: serviceName}.AsSelector() - //builder := &SelectorBuilder{ProductName: productName, ServiceName: serviceName} - //selector = builder.BuildSelector() - } - - pods, err := getter.ListPods(product.Namespace, selector, kubeClient) - if err != nil { - return "", fmt.Errorf("[%s] ListPods %s/%s error: %v", product.Namespace, productName, serviceName, err) - } - - for _, p := range pods { - pod := wrapper.Pod(p).Resource() - for _, c := range pod.ContainerStatuses { - if c.Name == container || commonservice.ExtractImageName(c.Image) == container { + switch serviceInfo.WorkloadType { + case setting.StatefulSet: + var statefulSet *appsv1.StatefulSet + statefulSet, _, err = getter.GetStatefulSet(product.Namespace, serviceName, kubeClient) + if err != nil { + return "", err + } + for _, c := range statefulSet.Spec.Template.Spec.Containers { + if c.Name == container { return c.Image, nil } } - } - log.Errorf("[%s]container %s not found", product.Namespace, container) - - return "", &ContainerNotFound{ - ServiceName: serviceName, - Container: container, - EnvName: envName, - ProductName: productName, + return "", errors.New("no container in statefulset found") + case setting.Deployment: + var deployment *appsv1.Deployment + deployment, _, err = getter.GetDeployment(product.Namespace, serviceName, kubeClient) + if err != nil { + return "", err + } + for _, c := range deployment.Spec.Template.Spec.Containers { + if c.Name == container { + return c.Image, nil + } + } + return "", errors.New("no container in deployment found") + default: + log.Errorf("The type of workload is not supported: %s", serviceInfo.WorkloadType) + return "", errors.New("the type of workload is not supported") } } -- Gitee From 28b9a5adec20e1102a6d5cb20a5cac0a12bad7c5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 11:07:20 +0800 Subject: [PATCH 1979/2647] optimize code Signed-off-by: allenshen --- .../core/service/taskcontroller/task_handler.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 3fbcf8ab..05ad9092 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -297,7 +297,7 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren // Task is struct for worker var tasks []*Task //tasks been preprocessed, map[serviceName]=[]Tasks - preProcessedTasks := make(map[string]*Task) + pluginsByService := make(map[string]*plugins.HelmDeployTaskPlugin) // helm deploy plugins map[fullServiceName]=>HelmDeployPlugin helmDeployPlugins := make(map[string]*plugins.HelmDeployTaskPlugin, 0) @@ -316,10 +316,8 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren workerConcurrency = 1 pluginInstance := plugins.InitializeHelmDeployTaskPlugin(config.TaskDeploy) pluginInstance.Task = deployTask - batchTask := NewTask(ctx, h.executeTask, pluginInstance, subTask, stagePosition, fullServiceName, xl) - if _, ok := preProcessedTasks[deployTask.ServiceName]; !ok { - xl.Infof("new batch sub task of service name: %s, type: %s", fullServiceName, stage.TaskType) - preProcessedTasks[deployTask.ServiceName] = batchTask + if _, ok := pluginsByService[deployTask.ServiceName]; !ok { + pluginsByService[deployTask.ServiceName] = pluginInstance } helmDeployPlugins[fullServiceName] = pluginInstance } @@ -331,9 +329,11 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren xl.Infof("new sub task of service name: %s, type: %s", serviceName, stage.TaskType) if deployPlugin, ok := helmDeployPlugins[serviceName]; ok { svcName := deployPlugin.Task.ServiceName - preProcessedTasks[svcName].AddRelatedTasks(helmDeployPlugins[serviceName]) + pluginsByService[svcName].ContentPlugins = append(pluginsByService[svcName].ContentPlugins, helmDeployPlugins[serviceName]) if !preProcessedServices.Has(svcName) { - tasks = append(tasks, preProcessedTasks[svcName]) + xl.Infof("new batch sub task of service name: %s, type: %s", serviceName, stage.TaskType) + batchTask := NewTask(ctx, h.executeTask, pluginsByService[svcName], subTask, stagePosition, serviceName, xl) + tasks = append(tasks, batchTask) } preProcessedServices.Insert(svcName) continue -- Gitee From 8e2f95cfba7bcc8391c0d5ac1051b8529ba596d3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 11:10:39 +0800 Subject: [PATCH 1980/2647] remove useless code Signed-off-by: allenshen --- .../warpdrive/core/service/taskcontroller/worker.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go b/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go index 46cd99ec..538b482b 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/worker.go @@ -105,14 +105,6 @@ func NewTask(ctx context.Context, taskExecutorFunc taskOperater, plugin plugins. } } -func (t *Task) AddRelatedTasks(helmDeployPlugin *plugins.HelmDeployTaskPlugin) { - plugin, ok := t.plugin.(*plugins.HelmDeployTaskPlugin) - if !ok || plugin == nil { - return - } - plugin.ContentPlugins = append(plugin.ContentPlugins, helmDeployPlugin) -} - // Run runs a Task and does appropriate accounting via a // given sync.WorkGroup. func (t *Task) Run(wg *sync.WaitGroup) { -- Gitee From 25a041985d62c81bc24505faa75d1f0c0910c3c1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 11:24:57 +0800 Subject: [PATCH 1981/2647] add necessary code comments Signed-off-by: allenshen --- pkg/tool/helmclient/helmclient.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index b8c1ccf5..58d2e6e5 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -543,6 +543,8 @@ func (hClient *HelmClient) InstallOrUpgradeChart(ctx context.Context, spec *hc.C } } +// UpdateChartRepo works like executing `helm repo update` +// environment `HELM_REPO_USERNAME` and `HELM_REPO_PASSWORD` are only required for ali acr repos func (hClient *HelmClient) UpdateChartRepo(repoEntry *repo.Entry) (string, error) { chartRepo, err := repo.NewChartRepository(repoEntry, hClient.Providers) if err != nil { @@ -590,6 +592,10 @@ func (hClient *HelmClient) FetchIndexYaml(repoEntry *repo.Entry) (*repo.IndexFil return repoIndex, err } +// DownloadChart works like executing `helm pull repoName/chartName --version=version' +// since pulling from OCI Registry is still considered as an EXPERIMENTAL feature +// we DO NOT support pulling charts by pulling OCI Artifacts from OCI Registry +// NOTE consider using os.execCommand('helm pull') to reduce code complexity of offering compatibility since third-party plugins CANNOT be used as SDK func (hClient *HelmClient) DownloadChart(repoEntry *repo.Entry, chartRef string, chartVersion string, destDir string, unTar bool) error { hClient.lock.Lock() defer hClient.lock.Unlock() -- Gitee From 7295448e74e1717938c8c760ab493c596828bc33 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 30 Mar 2022 14:10:07 +0800 Subject: [PATCH 1982/2647] error handling Signed-off-by: Min Min --- .../core/workflow/service/workflow/pipeline_validation.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index cf2e8a99..1b1e8b5f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -707,6 +707,9 @@ func getImageInfoFromWorkload(envName, productName, serviceName, container strin ServiceName: serviceName, ProductName: productName, }) + if err != nil { + return "", err + } kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) if err != nil { -- Gitee From 6d57f5b81b22d7d3f72b8b2200e1915cf8f79cc7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 30 Mar 2022 14:24:09 +0800 Subject: [PATCH 1983/2647] debug code Signed-off-by: liu deyi --- pkg/tool/gitee/repositories.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index c0ef084c..a90df815 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -73,7 +73,6 @@ type Hook struct { MergeRequestsEvents bool `json:"merge_requests_events"` } -//func (c *Client) CreateHook(ctx context.Context, owner, repo string, hook *git.Hook) (gitee.Hook, error) { func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (*Hook, error) { httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), @@ -88,8 +87,8 @@ func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (*H TagPushEvents string `json:"tag_push_events"` MergeRequestsEvents string `json:"merge_requests_events"` }{accessToken, hook.URL, hook.Secret, "true", "true", "true"}), httpclient.SetResult(&hookInfo)) + fmt.Println(fmt.Sprintf("hook.Secret:%s", hook.Secret)) if err != nil { - fmt.Println(fmt.Sprintf("PostV5ReposOwnerRepoHooks err:%+v", err)) return nil, err } return hookInfo, nil -- Gitee From 2dd84b89f6efe4cb01d4f9a2dc9ca98f62448753 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 30 Mar 2022 14:26:27 +0800 Subject: [PATCH 1984/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/service/webhook/gitee.go | 2 +- pkg/tool/gitee/repositories.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index ef23eb49..b1cbb5a0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -17,7 +17,7 @@ import ( ) func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { - token := req.Header.Get("X-Gitee-Event") + token := req.Header.Get("X-Gitee-Token") secret := gitservice.GetHookSecret() if secret != "" && token != secret { diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index a90df815..f9daabcd 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -87,7 +87,6 @@ func (c *Client) CreateHook(accessToken, owner, repo string, hook *git.Hook) (*H TagPushEvents string `json:"tag_push_events"` MergeRequestsEvents string `json:"merge_requests_events"` }{accessToken, hook.URL, hook.Secret, "true", "true", "true"}), httpclient.SetResult(&hookInfo)) - fmt.Println(fmt.Sprintf("hook.Secret:%s", hook.Secret)) if err != nil { return nil, err } -- Gitee From ce39f3e895535eca80e7c63f3d560545bd8f732f Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 14:47:22 +0800 Subject: [PATCH 1985/2647] fix reset image not work bug Signed-off-by: allenshen --- .../warpdrive/core/service/taskcontroller/task_handler.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 05ad9092..5100310e 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -303,7 +303,7 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren // preprocess subTasks, make batchTask with multiple subTasks // eg: multiple deploys of same helm chart - if stage.TaskType == config.TaskDeploy { + if stage.TaskType == config.TaskDeploy || stage.TaskType == config.TaskResetImage { for fullServiceName, subTask := range stage.SubTasks { deployTask, err := plugins.ToDeployTask(subTask) if err != nil { @@ -326,7 +326,6 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren // 每个SubTask会initiate一个plugin instance来执行 preProcessedServices := sets.NewString() for serviceName, subTask := range stage.SubTasks { - xl.Infof("new sub task of service name: %s, type: %s", serviceName, stage.TaskType) if deployPlugin, ok := helmDeployPlugins[serviceName]; ok { svcName := deployPlugin.Task.ServiceName pluginsByService[svcName].ContentPlugins = append(pluginsByService[svcName].ContentPlugins, helmDeployPlugins[serviceName]) -- Gitee From ad89bbfd38d310b35d1cb987979c6271c5719dff Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 30 Mar 2022 17:37:02 +0800 Subject: [PATCH 1986/2647] fix jenkins plugin problem Signed-off-by: liu deyi --- .../aslan/core/workflow/service/workflow/workflow_task.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 3762bd8c..9361db2e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2324,6 +2324,9 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err taskOpt.Task.TaskArgs.Deploy.Image = image t.Image = image + if taskOpt.IsWorkflowTask { + t.ServiceName = t.ServiceName + "_" + t.Service + } taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err -- Gitee From d4e024b7738d7c31fb9d9dd1a24fc8c740482145 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 17:40:15 +0800 Subject: [PATCH 1987/2647] update log Signed-off-by: allenshen --- .../warpdrive/core/service/taskcontroller/task_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 5100310e..b2c06723 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -307,7 +307,7 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren for fullServiceName, subTask := range stage.SubTasks { deployTask, err := plugins.ToDeployTask(subTask) if err != nil { - xl.Infof("failed to get deplot task, err: %s", err) + xl.Errorf("failed to get deplot task, err: %s", err) continue } if deployTask.ServiceType != setting.HelmDeployType { -- Gitee From 6e404b1ea0c9e0eb00249251066ddf007ad00355 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 30 Mar 2022 17:43:30 +0800 Subject: [PATCH 1988/2647] ua for role pod_debug Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 112 ++++++++++++++++++ .../internal/repository/models/oldpolicy.go | 10 +- 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/1110.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go new file mode 100644 index 00000000..bc55566a --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -0,0 +1,112 @@ +package migrate + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/tool/log" +) + +func init() { + upgradepath.RegisterHandler("1.10.0", "1.11.0", V1100ToV1110) + upgradepath.RegisterHandler("1.11.0", "1.10.0", V1110ToV1100) +} + +func V1100ToV1110() error { + // iterate all roles , if the role contains manage env , add debug_pod + if err := roleAddPodDebug(); err != nil { + return err + } + + return nil +} + +func V1110ToV1100() error { + if err := roleDeletePodDebug(); err != nil { + return err + } + return nil +} + +func roleAddPodDebug() error { + ctx := context.Background() + var res []*models.Role + cursor, err := newRoleColl().Find(ctx, bson.M{}) + if err != nil { + log.Errorf("Failed to Find Policies, err: %s", err) + return err + } + err = cursor.All(ctx, &res) + if err != nil { + return err + } + for _, role := range res { + toUpdate := false + for i, rule := range role.Rules { + verbsSet := sets.NewString(rule.Verbs...) + if verbsSet.Has("manage_environment") { + verbsSet.Insert("debug_pod") + role.Rules[i].Verbs = verbsSet.List() + toUpdate = true + } + } + if toUpdate { + // start to update + fmt.Printf("find role:%s has verb:manage_environment ,start to update the role\n", role.Name) + query := bson.M{"name": role.Name} + change := bson.M{"$set": bson.M{ + "rules": role.Rules, + }} + _, err := newRoleColl().UpdateOne(ctx, query, change) + if err != nil { + fmt.Printf("err update%s\n", err) + return err + } + } + } + return nil +} + +func roleDeletePodDebug() error { + ctx := context.Background() + var res []*models.Role + cursor, err := newRoleColl().Find(ctx, bson.M{}) + if err != nil { + log.Errorf("Failed to Find Policies, err: %s", err) + return err + } + err = cursor.All(ctx, &res) + if err != nil { + return err + } + for _, role := range res { + toUpdate := false + for i, rule := range role.Rules { + verbsSet := sets.NewString(rule.Verbs...) + if verbsSet.Has("debug_pod") { + verbsSet.Delete("debug_pod") + role.Rules[i].Verbs = verbsSet.List() + toUpdate = true + } + } + if toUpdate { + // start to update + fmt.Printf("find role:%s has verb:debug_pod ,start to update the role\n", role.Name) + query := bson.M{"name": role.Name} + change := bson.M{"$set": bson.M{ + "rules": role.Rules, + }} + _, err := newRoleColl().UpdateOne(ctx, query, change) + if err != nil { + fmt.Printf("err update%s\n", err) + return err + } + } + } + return nil +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 29b0a4c0..1cca4fa7 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -28,8 +28,14 @@ type PolicyMeta struct { } type Role struct { - Name string `bson:"name" json:"name"` - Namespace string `bson:"namespace" json:"namespace"` + Name string `bson:"name" json:"name"` + Namespace string `bson:"namespace" json:"namespace"` + Rules []*Rule `bson:"rules" json:"rules"` +} + +type Rule struct { + // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. + Verbs []string `bson:"verbs" json:"verbs"` } type RoleBinding struct { -- Gitee From 14e24494f4946a539c0c34487290fb6218dc00d6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 30 Mar 2022 18:44:17 +0800 Subject: [PATCH 1989/2647] intercept the first eight display of the commit id Signed-off-by: liu deyi --- .../workflow/service/workflow/pipeline_validation.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 1b1e8b5f..055f30e2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - appsv1 "k8s.io/api/apps/v1" "regexp" "sort" "strconv" @@ -28,6 +27,8 @@ import ( "sync" "time" + appsv1 "k8s.io/api/apps/v1" + "github.com/google/go-github/v35/github" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -514,6 +515,7 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageNa reg = regexp.MustCompile(`[^\w.-]`) customImageRule *template.CustomRule customTarRule *template.CustomRule + commitID = first.CommitID ) if project, err := templaterepo.NewProductColl().Find(productName); err != nil { @@ -523,9 +525,13 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageNa customTarRule = project.CustomTarRule } + if len(commitID) > 8 { + commitID = commitID[0:8] + } + candidate := &candidate{ Branch: string(reg.ReplaceAll([]byte(first.Branch), []byte("-"))), - CommitID: first.CommitID, + CommitID: commitID, PR: first.PR, Tag: string(reg.ReplaceAll([]byte(first.Tag), []byte("-"))), EnvName: envName, -- Gitee From 796d1036ae949844c50186eefe50aad07e323419 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 30 Mar 2022 20:00:27 +0800 Subject: [PATCH 1990/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/workflow/pipeline_validation.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 055f30e2..cf68922e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -27,9 +27,10 @@ import ( "sync" "time" + "github.com/google/go-github/v35/github" + "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" - "github.com/google/go-github/v35/github" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -49,12 +50,12 @@ import ( "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" - "go.uber.org/zap" ) const ( NameSpaceRegexString = "[^a-z0-9.-]" defaultNameRegexString = "^[a-zA-Z0-9-_]{1,50}$" + InterceptCommitID = 8 ) var ( @@ -525,8 +526,8 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageNa customTarRule = project.CustomTarRule } - if len(commitID) > 8 { - commitID = commitID[0:8] + if len(commitID) > InterceptCommitID { + commitID = commitID[0:InterceptCommitID] } candidate := &candidate{ -- Gitee From 3e25d5747caa917bff694e8b233af9656859e142 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 30 Mar 2022 21:39:54 +0800 Subject: [PATCH 1991/2647] fix host terminal login Signed-off-by: ddh27 --- .../aslan/core/environment/service/pm_exec.go | 13 ++++++++----- pkg/tool/wsconn/wsconn.go | 2 ++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm_exec.go b/pkg/microservice/aslan/core/environment/service/pm_exec.go index 254172ea..91179e7c 100644 --- a/pkg/microservice/aslan/core/environment/service/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/service/pm_exec.go @@ -1,6 +1,7 @@ package service import ( + "encoding/base64" "fmt" "net/http" @@ -34,6 +35,10 @@ func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, if resp.Status != setting.PMHostStatusNormal { return e.ErrLoginPm.AddDesc(fmt.Sprintf("host %s status %s,is not normal", ip, resp.Status)) } + sDec, err := base64.StdEncoding.DecodeString(resp.PrivateKey) + if err != nil { + log.Errorf("base64 decode failure ip:%s, error:%s", ip, err) + } conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { @@ -43,21 +48,19 @@ func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, if resp.Port == 0 { resp.Port = setting.PMHostDefaultPort } - sshCli, err := toolssh.NewSshCli([]byte(resp.PrivateKey), resp.UserName, resp.IP, resp.Port) + sshCli, err := toolssh.NewSshCli(sDec, resp.UserName, resp.IP, resp.Port) if err != nil { + log.Error(err) conn.WriteMessage(1, []byte(err.Error())) conn.Close() return e.ErrLoginPm.AddErr(err) } sshClient := &wsconn.SshClient{ - // Username: resp.UserName, - // PrivateKey: resp.PrivateKey, - // Ip: resp.IP, - // Port: resp.Port, SshCli: sshCli, } if err := sshClient.GenerateWebTerminal(150, 40); err != nil { + log.Error(err) conn.WriteMessage(1, []byte(err.Error())) conn.Close() } diff --git a/pkg/tool/wsconn/wsconn.go b/pkg/tool/wsconn/wsconn.go index 4ab99e02..433220f3 100644 --- a/pkg/tool/wsconn/wsconn.go +++ b/pkg/tool/wsconn/wsconn.go @@ -82,10 +82,12 @@ func (sshCli *SshClient) ConnectWs(ws *websocket.Conn) { for { _, p, err := ws.ReadMessage() if err != nil { + log.Error(err) return } _, err = sshCli.sshChan.Write(p) if err != nil { + log.Error(err) return } } -- Gitee From 57117f586ad5e4dcf3b7b85c0ac7ec1349eaf8c1 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 21:56:31 +0800 Subject: [PATCH 1992/2647] save values yaml ource data Signed-off-by: allenshen --- .../common/repository/models/render_set.go | 5 ++- .../repository/models/template/product.go | 4 +- .../aslan/core/common/service/environment.go | 1 + .../aslan/core/common/service/render.go | 38 ++++++++++++++++--- .../core/environment/service/environment.go | 32 ++++++++++++++++ .../core/environment/service/renderset.go | 5 ++- .../aslan/core/service/service/helm.go | 25 ++++++++++-- .../aslan/core/service/service/types.go | 18 ++++----- pkg/setting/consts.go | 3 -- 9 files changed, 104 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/render_set.go b/pkg/microservice/aslan/core/common/repository/models/render_set.go index f114671b..6cc19df7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/models/render_set.go @@ -33,8 +33,9 @@ type RenderSet struct { Team string `bson:"team,omitempty" json:"team,omitempty"` UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` - IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` KVs []*templatemodels.RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` Description string `bson:"description,omitempty" json:"description,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 29d72c43..b34bd0ff 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -103,7 +103,9 @@ type GitRepoConfig struct { } type CustomYaml struct { - YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` + YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` + Source string `bson:"source" json:"source"` + SourceDetail interface{} `bson:"source_detail" json:"source_detail"` } // RenderChart ... diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 65b236aa..fbb4d45c 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -133,6 +133,7 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL rcaObj := new(RenderChartArg) rcaObj.LoadFromRenderChartModel(singleChart) rcaObj.EnvName = envName + rcaObj.YamlData = singleChart.OverrideYaml ret = append(ret, rcaObj) } return ret, nil diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 4ba74b44..2a25dff8 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -26,6 +26,7 @@ import ( "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -48,12 +49,19 @@ type KVPair struct { Value interface{} `json:"value"` } +type ValuesDataArgs struct { + YamlSource string `json:"yamlSource,omitempty"` + GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` +} + type RenderChartArg struct { - EnvName string `json:"envName,omitempty"` - ServiceName string `json:"serviceName,omitempty"` - ChartVersion string `json:"chartVersion,omitempty"` - OverrideValues []*KVPair `json:"overrideValues,omitempty"` - OverrideYaml string `json:"overrideYaml,omitempty"` + EnvName string `json:"envName,omitempty"` + ServiceName string `json:"serviceName,omitempty"` + ChartVersion string `json:"chartVersion,omitempty"` + OverrideValues []*KVPair `json:"overrideValues,omitempty"` + OverrideYaml string `json:"overrideYaml,omitempty"` + ValuesData *ValuesDataArgs `json:"valuesData,omitempty"` + YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` } func (args *RenderChartArg) ToOverrideValueString() string { @@ -83,9 +91,27 @@ func (args *RenderChartArg) fromOverrideValueString(valueStr string) { func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.CustomYaml { if len(args.OverrideYaml) > 0 { - return &templatemodels.CustomYaml{ + ret := &templatemodels.CustomYaml{ YamlContent: args.OverrideYaml, } + if args.ValuesData != nil { + ret.Source = args.ValuesData.YamlSource + if args.ValuesData.GitRepoConfig != nil { + repoData := &models.CreateFromRepo{ + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: args.ValuesData.GitRepoConfig.CodehostID, + Owner: args.ValuesData.GitRepoConfig.Owner, + Repo: args.ValuesData.GitRepoConfig.Repo, + Branch: args.ValuesData.GitRepoConfig.Branch, + }, + } + if len(args.ValuesData.GitRepoConfig.ValuesPaths) > 0 { + repoData.LoadPath = args.ValuesData.GitRepoConfig.ValuesPaths[0] + } + ret.SourceDetail = repoData + } + } + return ret } return nil } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 587db829..abafbb53 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,6 +46,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -149,6 +150,7 @@ type EnvRenderChartArg struct { type EnvRendersetArg struct { DefaultValues string `json:"defaultValues"` + ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` } @@ -158,6 +160,7 @@ type CreateHelmProductArg struct { Namespace string `json:"namespace"` ClusterID string `json:"clusterID"` DefaultValues string `json:"defaultValues"` + ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` RegistryID string `json:"registry_id"` ChartValues []*commonservice.RenderChartArg `json:"chartValues"` BaseEnvName string `json:"baseEnvName"` @@ -919,6 +922,7 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, UpdateBy: userName, IsDefault: false, DefaultValues: defaultValuesYaml, + YamlData: geneYamlData(arg.ValuesData), ChartInfos: productObj.ChartInfos, }, log) if err != nil { @@ -1345,6 +1349,31 @@ func checkOverrideValuesChange(source *templatemodels.RenderChart, args *commons return false } +func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml { + if args == nil { + return nil + } + var repoData *models.CreateFromRepo = nil + if args.GitRepoConfig != nil { + repoData = &models.CreateFromRepo{ + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: args.GitRepoConfig.CodehostID, + Owner: args.GitRepoConfig.Owner, + Repo: args.GitRepoConfig.Repo, + Branch: args.GitRepoConfig.Branch, + }, + } + if len(args.GitRepoConfig.ValuesPaths) > 0 { + repoData.LoadPath = args.GitRepoConfig.ValuesPaths[0] + } + } + ret := &templatemodels.CustomYaml{ + Source: args.YamlSource, + SourceDetail: repoData, + } + return ret +} + func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, @@ -1374,6 +1403,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string } productRenderset.DefaultValues = args.DefaultValues } + productRenderset.YamlData = geneYamlData(args.ValuesData) for _, requestRenderChart := range args.ChartValues { // update renderset info @@ -1382,6 +1412,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string continue } if !checkOverrideValuesChange(curRenderChart, requestRenderChart) { + requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) continue } requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) @@ -1426,6 +1457,7 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, ProductTmpl: productName, UpdateBy: username, DefaultValues: renderset.DefaultValues, + YamlData: renderset.YamlData, ChartInfos: renderset.ChartInfos, }, log, diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 2609a82e..e0885c3a 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -24,13 +24,15 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type DefaultValuesResp struct { - DefaultValues string `json:"defaultValues"` + DefaultValues string `json:"defaultValues"` + YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` } type YamlContentRequestArg struct { @@ -74,6 +76,7 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def return ret, nil } ret.DefaultValues = rendersetObj.DefaultValues + ret.YamlData = rendersetObj.YamlData return ret, nil } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 8111b38b..d1e4d764 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -111,6 +111,7 @@ type helmServiceCreationArgs struct { GerritPath string GerritCodeHostID int ChartRepoName string + ValuesSource *commonservice.ValuesDataArgs } type ChartTemplateData struct { @@ -539,6 +540,7 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe HelmTemplateName: templateArgs.TemplateName, ValuesYaml: templateArgs.ValuesYAML, Variables: templateArgs.Variables, + ValuesSource: args.ValuesData, }, logger, ) @@ -550,7 +552,8 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe } compareHelmVariable([]*templatemodels.RenderChart{ - {ServiceName: args.Name, + { + ServiceName: args.Name, ChartVersion: svc.HelmChart.Version, ValuesYaml: svc.HelmChart.ValuesYaml, }, @@ -886,7 +889,7 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel wg.Add(1) go func(repoConfig *commonservice.RepoConfig, path string) { defer wg.Done() - renderChart, err := handleSingleService(projectName, repoConfig, path, from, args.CreatedBy, templateChartData, logger) + renderChart, err := handleSingleService(projectName, repoConfig, path, from, args.CreatedBy, templateChartData, args.ValuesData, logger) if err != nil { failedServiceMap.Store(path, err.Error()) } else { @@ -924,7 +927,7 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel } func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, path, fromPath, createBy string, - templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { + templateChartData *ChartTemplateData, valuesData *commonservice.ValuesDataArgs, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, @@ -1011,6 +1014,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi HelmTemplateName: templateChartData.TemplateName, ValuePaths: []string{path}, ValuesYaml: string(valuesYAML), + ValuesSource: valuesData, }, logger, ) @@ -1105,6 +1109,21 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { Value: variable.Value, }) } + if args.ValuesSource != nil && args.ValuesSource.GitRepoConfig != nil { + yamlData.Source = args.ValuesSource.YamlSource + repoData := &models.CreateFromRepo{ + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: args.ValuesSource.GitRepoConfig.CodehostID, + Owner: args.ValuesSource.GitRepoConfig.Owner, + Repo: args.ValuesSource.GitRepoConfig.Repo, + Branch: args.ValuesSource.GitRepoConfig.Branch, + }, + } + if len(args.ValuesSource.GitRepoConfig.ValuesPaths) > 0 { + repoData.LoadPath = args.ValuesSource.GitRepoConfig.ValuesPaths[0] + } + yamlData.SourceDetail = repoData + } return &models.CreateFromChartTemplate{ YamlData: yamlData, TemplateName: args.HelmTemplateName, diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index 79167c2a..7061c21b 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -40,21 +40,17 @@ type HelmLoadSource struct { type HelmServiceCreationArgs struct { HelmLoadSource - Name string `json:"name"` - CreatedBy string `json:"createdBy"` - CreateFrom interface{} `json:"createFrom"` -} - -type ValuesDataArgs struct { - YamlSource string `json:"yamlSource,omitempty"` - GitRepoConfig *service.RepoConfig `json:"gitRepoConfig,omitempty"` + Name string `json:"name"` + CreatedBy string `json:"createdBy"` + CreateFrom interface{} `json:"createFrom"` + ValuesData *service.ValuesDataArgs `json:"valuesData"` } type BulkHelmServiceCreationArgs struct { HelmLoadSource - CreateFrom interface{} `json:"createFrom"` - CreatedBy string `json:"createdBy"` - ValuesData *ValuesDataArgs `json:"valuesData"` + CreateFrom interface{} `json:"createFrom"` + CreatedBy string `json:"createdBy"` + ValuesData *service.ValuesDataArgs `json:"valuesData"` } type FailedService struct { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index d5a91f91..f9f72320 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -508,9 +508,6 @@ const ( // helm related const ( - ValuesYamlSourceFreeEdit = "freeEdit" - ValuesYamlSourceGitRepo = "gitRepo" - // components used to search image paths from yaml PathSearchComponentRepo = "repo" PathSearchComponentImage = "image" -- Gitee From b475deb82eda3241e911e2c8d9cb79201ad2f935 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 22:10:48 +0800 Subject: [PATCH 1993/2647] optimize code Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/render.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 2a25dff8..eb0a9361 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -61,7 +61,7 @@ type RenderChartArg struct { OverrideValues []*KVPair `json:"overrideValues,omitempty"` OverrideYaml string `json:"overrideYaml,omitempty"` ValuesData *ValuesDataArgs `json:"valuesData,omitempty"` - YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` + YamlData *templatemodels.CustomYaml `json:"yaml_data,omitempty"` } func (args *RenderChartArg) ToOverrideValueString() string { -- Gitee From d036e2873e1b84eb2b75b5f5d0b87ff30102a459 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 30 Mar 2022 22:18:47 +0800 Subject: [PATCH 1994/2647] save git info when copy environment Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index abafbb53..c35805f1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1115,6 +1115,7 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH UpdateBy: userName, IsDefault: false, DefaultValues: defaultValuesYaml, + YamlData: geneYamlData(arg.ValuesData), ChartInfos: productInfo.ChartInfos, }, log) if err != nil { -- Gitee From 38d84c3012f23273f029055ed0243a755970297a Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 30 Mar 2022 22:54:26 +0800 Subject: [PATCH 1995/2647] fix partial service configmap modified since last update Signed-off-by: panxunying --- .../core/environment/service/environment.go | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 587db829..272e4f11 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -320,10 +320,10 @@ func AutoUpdateProduct(args []*UpdateEnv, envNames []string, productName, reques if !force && project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { modifiedByENV := make(map[string][]*serviceInfo) - for _, env := range envNames { - p, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: env}) + for _, arg := range args { + p, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: arg.EnvName}) if err != nil { - log.Errorf("Failed to get product %s in %s, error: %v", productName, env, err) + log.Errorf("Failed to get product %s in %s, error: %v", productName, arg.EnvName, err) continue } @@ -334,8 +334,14 @@ func AutoUpdateProduct(args []*UpdateEnv, envNames []string, productName, reques } modifiedServices := getModifiedServiceFromObjectMetaList(kube.GetDirtyResources(p.Namespace, kubeClient)) - if len(modifiedServices) > 0 { - modifiedByENV[env] = modifiedServices + var specifyModifiedServices []*serviceInfo + for _, modifiedService := range modifiedServices { + if util.InStringArray(modifiedService.Name, arg.ServiceNames) { + specifyModifiedServices = append(specifyModifiedServices, modifiedService) + } + } + if len(specifyModifiedServices) > 0 { + modifiedByENV[arg.EnvName] = specifyModifiedServices } } if len(modifiedByENV) > 0 { @@ -363,7 +369,7 @@ func AutoUpdateProduct(args []*UpdateEnv, envNames []string, productName, reques } for _, arg := range args { - err = UpdateProductV2(arg.EnvName, productName, setting.SystemUser, requestID, arg.ServiceNames, false, arg.Vars, log) + err = UpdateProductV2(arg.EnvName, productName, setting.SystemUser, requestID, arg.ServiceNames, force, arg.Vars, log) if err != nil { log.Errorf("AutoUpdateProduct UpdateProductV2 err:%v", err) return envStatuses, err @@ -683,8 +689,14 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames } if !force && project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { modifiedServices := getModifiedServiceFromObjectMetaList(kube.GetDirtyResources(exitedProd.Namespace, kubeClient)) - if len(modifiedServices) > 0 { - data, err := json.Marshal(modifiedServices) + var specifyModifiedServices []*serviceInfo + for _, modifiedService := range modifiedServices { + if util.InStringArray(modifiedService.Name, serviceNames) { + specifyModifiedServices = append(specifyModifiedServices, modifiedService) + } + } + if len(specifyModifiedServices) > 0 { + data, err := json.Marshal(specifyModifiedServices) if err != nil { log.Errorf("Marshal failure: %v", err) } -- Gitee From ca1df9fee477f6aa31a0f3469f7bb946682148d2 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 30 Mar 2022 23:29:13 +0800 Subject: [PATCH 1996/2647] fix bugs Signed-off-by: zhangyifei --- .../core/environment/handler/environment.go | 7 -- .../core/environment/service/environment.go | 16 ++- .../aslan/core/environment/service/product.go | 37 +++--- .../core/environment/service/share_env.go | 117 ++++++++++++++---- .../environment/service/share_env_utils.go | 2 +- .../aslan/core/environment/service/types.go | 5 + .../core/service/bundle/exemption_urls.go | 17 +++ 7 files changed, 151 insertions(+), 50 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index fa138857..dcc2bbfa 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -234,13 +234,6 @@ func CreateProduct(c *gin.Context) { return } - // !!! [ZADIG] FOR DEBUG. - args.ShareEnv = commonmodels.ProductShareEnv{ - Enable: true, - IsBase: false, - BaseEnv: "dev", - } - args.UpdateBy = ctx.UserName ctx.Err = service.CreateProduct( ctx.UserName, ctx.RequestID, args, ctx.Logger, diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index e1853fe8..450ff984 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -107,7 +107,7 @@ type EnvResp struct { BaseName string `json:"base_name"` IsExisted bool `json:"is_existed"` - // New Since v1.10.0 + // New Since v1.11.0 ShareEnvEnable bool `json:"share_env_enable"` ShareEnvIsBase bool `json:"share_env_is_base"` ShareEnvBaseEnv string `json:"share_env_base_env"` @@ -134,6 +134,11 @@ type ProductResp struct { IsExisted bool `json:"is_existed"` Source string `json:"source"` RegisterID string `json:"registry_id"` + + // New Since v1.11.0 + ShareEnvEnable bool `json:"share_env_enable"` + ShareEnvIsBase bool `json:"share_env_is_base"` + ShareEnvBaseEnv string `json:"share_env_base_env"` } type ProductParams struct { @@ -2009,6 +2014,15 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l log.Errorf("delete resource of service %s error:%v", name, err) } } + + if productInfo.ShareEnv.Enable && !productInfo.ShareEnv.IsBase { + err = ensureGrayEnvConfig(ctx, productInfo, kclient, istioClient) + if err != nil { + log.Errorf("Failed to ensure gray env config: %s", err) + return fmt.Errorf("failed to ensure gray env config: %s", err) + } + } + return nil } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 24b91e4d..60cd185d 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -191,23 +191,26 @@ func GetProduct(username, envName, productName string, log *zap.SugaredLogger) ( func buildProductResp(envName string, prod *commonmodels.Product, log *zap.SugaredLogger) *ProductResp { prodResp := &ProductResp{ - ID: prod.ID.Hex(), - ProductName: prod.ProductName, - Namespace: prod.Namespace, - Services: [][]string{}, - Status: setting.PodUnstable, - EnvName: prod.EnvName, - UpdateTime: prod.UpdateTime, - UpdateBy: prod.UpdateBy, - Render: prod.Render, - Error: prod.Error, - Vars: prod.Vars[:], - IsPublic: prod.IsPublic, - IsExisted: prod.IsExisted, - ClusterID: prod.ClusterID, - RecycleDay: prod.RecycleDay, - Source: prod.Source, - RegisterID: prod.RegistryID, + ID: prod.ID.Hex(), + ProductName: prod.ProductName, + Namespace: prod.Namespace, + Services: [][]string{}, + Status: setting.PodUnstable, + EnvName: prod.EnvName, + UpdateTime: prod.UpdateTime, + UpdateBy: prod.UpdateBy, + Render: prod.Render, + Error: prod.Error, + Vars: prod.Vars[:], + IsPublic: prod.IsPublic, + IsExisted: prod.IsExisted, + ClusterID: prod.ClusterID, + RecycleDay: prod.RecycleDay, + Source: prod.Source, + RegisterID: prod.RegistryID, + ShareEnvEnable: prod.ShareEnv.Enable, + ShareEnvIsBase: prod.ShareEnv.IsBase, + ShareEnvBaseEnv: prod.ShareEnv.BaseEnv, } if prod.ClusterID != "" { diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 61ae295f..63803c7f 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -119,7 +119,7 @@ func EnableBaseEnv(ctx context.Context, envName, productName string) error { } // 3. Ensure `VirtualService` (subsets are not required) in current namespace. - err = ensureVirtualServices(ctx, kclient, istioClient, ns) + err = ensureVirtualServices(ctx, prod, kclient, istioClient) if err != nil { return fmt.Errorf("failed to ensure VirtualServices in namespace `%s`: %s", ns, err) } @@ -426,33 +426,68 @@ func checkPodsWithIstioProxyAndReady(ctx context.Context, kclient client.Client, return allHaveIstioProxy, allPodsReady, nil } -func ensureVirtualServices(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, ns string) error { +func ensureVirtualServices(ctx context.Context, env *commonmodels.Product, kclient client.Client, istioClient versionedclient.Interface) error { svcs := &corev1.ServiceList{} - err := kclient.List(ctx, svcs, client.InNamespace(ns)) + err := kclient.List(ctx, svcs, client.InNamespace(env.Namespace)) if err != nil { - return fmt.Errorf("failed to list svcs in ns `%s`: %s", ns, err) + return fmt.Errorf("failed to list svcs in ns `%s`: %s", env.Namespace, err) } for _, svc := range svcs.Items { vsName := genVirtualServiceName(&svc) - err := ensureVirtualService(ctx, istioClient, ns, vsName, svc.Name) + err := ensureVirtualService(ctx, kclient, istioClient, env, &svc, vsName) if err != nil { - return fmt.Errorf("failed to ensure VirtualService `%s` in ns `%s`: %s", vsName, ns, err) + return fmt.Errorf("failed to ensure VirtualService `%s` in ns `%s`: %s", vsName, env.Namespace, err) } } return nil } -func ensureVirtualService(ctx context.Context, istioClient versionedclient.Interface, ns, vsName, svcName string) error { - vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(ns).Get(ctx, vsName, metav1.GetOptions{}) +func ensureVirtualService(ctx context.Context, kclient client.Client, istioClient versionedclient.Interface, env *commonmodels.Product, svc *corev1.Service, vsName string) error { + vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(env.Namespace).Get(ctx, vsName, metav1.GetOptions{}) if err == nil { - log.Infof("Has found VirtualService `%s` in ns `%s` and don't recreate.", vsName, ns) + log.Infof("Has found VirtualService `%s` in ns `%s` and don't recreate.", vsName, env.Namespace) return nil } if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", vsName, ns, err) + return fmt.Errorf("failed to query VirtualService `%s` in ns `%s`: %s", vsName, env.Namespace, err) + } + + matchedEnvs := []MatchedEnv{} + if env.ShareEnv.Enable && env.ShareEnv.IsBase { + subEnvs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + ShareEnvEnable: getBoolPointer(true), + ShareEnvIsBase: getBoolPointer(false), + ShareEnvBaseEnv: getStrPointer(env.EnvName), + }) + if err != nil { + return err + } + + for _, subEnv := range subEnvs { + pods := &corev1.PodList{} + err = kclient.List(ctx, pods, &client.ListOptions{ + Namespace: subEnv.Namespace, + LabelSelector: labels.SelectorFromSet(labels.Set(svc.Spec.Selector)), + }) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + + if len(pods.Items) == 0 { + continue + } + + matchedEnvs = append(matchedEnvs, MatchedEnv{ + EnvName: subEnv.EnvName, + Namespace: subEnv.Namespace, + }) + } } vsObj.Name = vsName @@ -462,21 +497,45 @@ func ensureVirtualService(ctx context.Context, istioClient versionedclient.Inter } vsObj.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig - vsObj.Spec = networkingv1alpha3.VirtualService{ - Hosts: []string{svcName}, - Http: []*networkingv1alpha3.HTTPRoute{ - &networkingv1alpha3.HTTPRoute{ - Route: []*networkingv1alpha3.HTTPRouteDestination{ - &networkingv1alpha3.HTTPRouteDestination{ - Destination: &networkingv1alpha3.Destination{ - Host: fmt.Sprintf("%s.%s.svc.cluster.local", svcName, ns), + routes := []*networkingv1alpha3.HTTPRoute{} + for _, matchedEnv := range matchedEnvs { + grayRoute := &networkingv1alpha3.HTTPRoute{ + Match: []*networkingv1alpha3.HTTPMatchRequest{ + &networkingv1alpha3.HTTPMatchRequest{ + Headers: map[string]*networkingv1alpha3.StringMatch{ + zadigMatchXEnv: &networkingv1alpha3.StringMatch{ + MatchType: &networkingv1alpha3.StringMatch_Exact{ + Exact: matchedEnv.EnvName, + }, }, }, }, }, + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svc.Name, matchedEnv.Namespace), + }, + }, + }, + } + routes = append(routes, grayRoute) + } + routes = append(routes, &networkingv1alpha3.HTTPRoute{ + Route: []*networkingv1alpha3.HTTPRouteDestination{ + &networkingv1alpha3.HTTPRouteDestination{ + Destination: &networkingv1alpha3.Destination{ + Host: fmt.Sprintf("%s.%s.svc.cluster.local", svc.Name, env.Namespace), + }, + }, }, + }) + + vsObj.Spec = networkingv1alpha3.VirtualService{ + Hosts: []string{svc.Name}, + Http: routes, } - _, err = istioClient.NetworkingV1alpha3().VirtualServices(ns).Create(ctx, vsObj, metav1.CreateOptions{}) + _, err = istioClient.NetworkingV1alpha3().VirtualServices(env.Namespace).Create(ctx, vsObj, metav1.CreateOptions{}) return err } @@ -614,7 +673,7 @@ func buildEnvoyStoreCacheOperation() (*types.Struct, error) { "cache", { [":method"] = "POST", - [":path"] = string.format("/api/v1/%s/%s", requestID, env), + [":path"] = string.format("/api/cache/%s/%s", requestID, env), [":authority"] = "cache", }, "", @@ -640,7 +699,7 @@ func buildEnvoyGetCacheOperation() (*types.Struct, error) { "cache", { [":method"] = "GET", - [":path"] = string.format("/api/v1/%s", requestID), + [":path"] = string.format("/api/cache/%s", requestID), [":authority"] = "cache", }, "", @@ -1013,7 +1072,7 @@ func ensureUpdateVirtualServiceInBase(ctx context.Context, envName, vsName, svcN } } - vsObjInBase.Spec.Http = append(vsObjInBase.Spec.Http, &networkingv1alpha3.HTTPRoute{ + grayRoute := &networkingv1alpha3.HTTPRoute{ Match: []*networkingv1alpha3.HTTPMatchRequest{ &networkingv1alpha3.HTTPMatchRequest{ Headers: map[string]*networkingv1alpha3.StringMatch{ @@ -1032,7 +1091,17 @@ func ensureUpdateVirtualServiceInBase(ctx context.Context, envName, vsName, svcN }, }, }, - }) + } + + numRoutes := len(vsObjInBase.Spec.Http) + if numRoutes == 0 { + vsObjInBase.Spec.Http = append(vsObjInBase.Spec.Http, grayRoute) + } else { + routes := make([]*networkingv1alpha3.HTTPRoute, 1, numRoutes+1) + routes[0] = grayRoute + routes = append(routes, vsObjInBase.Spec.Http...) + vsObjInBase.Spec.Http = routes + } _, err = istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Update(ctx, vsObjInBase, metav1.UpdateOptions{}) return err @@ -1086,7 +1155,7 @@ func EnsureUpdateZadigService(ctx context.Context, env *commonmodels.Product, sv if env.ShareEnv.IsBase { // 1. Create VirtualService in the base environment. - err = ensureVirtualService(ctx, istioClient, env.Namespace, vsName, svcName) + err = ensureVirtualService(ctx, kclient, istioClient, env, svc, vsName) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 6c445a2a..4de989f3 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -231,7 +231,7 @@ func ensureServicesInAllSubEnvs(ctx context.Context, env *commonmodels.Product, for _, env := range envs { log.Infof("Begin to ensure Services in subenv %s of prouduct %s.", env.EnvName, env.ProductName) - err = ensureVirtualService(ctx, istioClient, env.Namespace, vsName, svc.Name) + err = ensureVirtualService(ctx, kclient, istioClient, env, svc, vsName) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index 00cc6423..48d0b3d1 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -190,3 +190,8 @@ const ( ShareEnvEnable ShareEnvOp = "enable" ShareEnvDisable ShareEnvOp = "disable" ) + +type MatchedEnv struct { + EnvName string + Namespace string +} diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index aee0d2a5..24e019b5 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -92,6 +92,23 @@ var publicURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/callback"}, }, + // !!! THE FOLLOWING IS FOR DEBUG. + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/environment/environments/?*/check/workloads/k8services"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/environment/environments/?*/check/sharenv/enable/ready"}, + }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/environment/environments/?*/check/sharenv/disable/ready"}, + }, + { + Methods: []string{"POST", "DELETE"}, + Endpoints: []string{"api/aslan/environment/environments/?*/share/enable"}, + }, } // actions which are allowed for system admins only. -- Gitee From f613c618e10caa30fdda02f17f22a7f08e74e5f7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 05:57:49 +0800 Subject: [PATCH 1997/2647] bugfix for case of service with no workloadType Signed-off-by: Min Min --- .../service/workflow/pipeline_validation.go | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index cf68922e..629f598e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "k8s.io/apimachinery/pkg/labels" "regexp" "sort" "strconv" @@ -753,8 +754,35 @@ func getImageInfoFromWorkload(envName, productName, serviceName, container strin } return "", errors.New("no container in deployment found") default: - log.Errorf("The type of workload is not supported: %s", serviceInfo.WorkloadType) - return "", errors.New("the type of workload is not supported") + // since in some version of the service, there are no workload type, we need to do something with this + selector := labels.Set{setting.ProductLabel: product.ProductName, setting.ServiceLabel: serviceName}.AsSelector() + var deployments []*appsv1.Deployment + deployments, err = getter.ListDeployments(product.Namespace, selector, kubeClient) + if err != nil { + return "", err + } + var statefulSets []*appsv1.StatefulSet + statefulSets, err = getter.ListStatefulSets(product.Namespace, selector, kubeClient) + if err != nil { + return "", err + } + for _, deploy := range deployments { + for _, c := range deploy.Spec.Template.Spec.Containers { + if c.Name == container { + return c.Image, nil + } + } + } + for _, sts := range statefulSets { + for _, c := range sts.Spec.Template.Spec.Containers { + if c.Name == container { + return c.Image, nil + } + } + } + + log.Errorf("No workload found with container: %s", container) + return "", errors.New("no workload found with container") } } -- Gitee From 5c4aab521241fff7f51d4d8eb6bd624ad2b8c67e Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 08:40:44 +0800 Subject: [PATCH 1998/2647] attempt fix for wrong service List in workflow Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/service.go | 2 ++ .../aslan/core/workflow/service/workflow/workflow.go | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index b802a7bb..dd298c3f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -142,6 +142,8 @@ type serviceID struct { ProductName string `bson:"product_name"` } +func (c *ServiceColl) ListMaxRe + func (c *ServiceColl) ListMaxRevisionsForServices(services []*templatemodels.ServiceInfo, serviceType string) ([]*models.Service, error) { var srs []bson.D for _, s := range services { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 4037a825..6a4126b5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -304,12 +304,12 @@ type DeployEnv struct { func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, error) { resp := make([]*PreSetResp, 0) targets := make(map[string][]DeployEnv) - productTmpl, err := template.NewProductColl().Find(productName) - if err != nil { - log.Errorf("[%s] ProductTmpl.Find error: %v", productName, err) - return resp, e.ErrGetTemplate.AddDesc(err.Error()) - } - services, err := commonrepo.NewServiceColl().ListMaxRevisionsForServices(productTmpl.AllServiceInfos(), "") + //productTmpl, err := template.NewProductColl().Find(productName) + //if err != nil { + // log.Errorf("[%s] ProductTmpl.Find error: %v", productName, err) + // return resp, e.ErrGetTemplate.AddDesc(err.Error()) + //} + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct("") if err != nil { log.Errorf("ServiceTmpl.ListMaxRevisions error: %v", err) return resp, e.ErrListTemplate.AddDesc(err.Error()) -- Gitee From 6060c39d80323c19756a9f0451f2ca0ed58c01ae Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 08:41:00 +0800 Subject: [PATCH 1999/2647] remove unused code Signed-off-by: Min Min --- .../aslan/core/common/repository/mongodb/service.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index dd298c3f..b802a7bb 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -142,8 +142,6 @@ type serviceID struct { ProductName string `bson:"product_name"` } -func (c *ServiceColl) ListMaxRe - func (c *ServiceColl) ListMaxRevisionsForServices(services []*templatemodels.ServiceInfo, serviceType string) ([]*models.Service, error) { var srs []bson.D for _, s := range services { -- Gitee From 054d74eb61dba5095272efc893adac53c96efef6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 09:22:19 +0800 Subject: [PATCH 2000/2647] fix a stupid bug Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 6a4126b5..7b632ccd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -304,12 +304,12 @@ type DeployEnv struct { func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, error) { resp := make([]*PreSetResp, 0) targets := make(map[string][]DeployEnv) - //productTmpl, err := template.NewProductColl().Find(productName) - //if err != nil { - // log.Errorf("[%s] ProductTmpl.Find error: %v", productName, err) - // return resp, e.ErrGetTemplate.AddDesc(err.Error()) - //} - services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct("") + productTmpl, err := template.NewProductColl().Find(productName) + if err != nil { + log.Errorf("[%s] ProductTmpl.Find error: %v", productName, err) + return resp, e.ErrGetTemplate.AddDesc(err.Error()) + } + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productTmpl.ProductName) if err != nil { log.Errorf("ServiceTmpl.ListMaxRevisions error: %v", err) return resp, e.ErrListTemplate.AddDesc(err.Error()) -- Gitee From f74ac05c7d3460931a9cd953fb140fcb0d56d3e2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 10:20:57 +0800 Subject: [PATCH 2001/2647] change get resource order to maximize performance Signed-off-by: Min Min --- .../workflow/service/workflow/pipeline_validation.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 629f598e..1d21db97 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -761,11 +761,6 @@ func getImageInfoFromWorkload(envName, productName, serviceName, container strin if err != nil { return "", err } - var statefulSets []*appsv1.StatefulSet - statefulSets, err = getter.ListStatefulSets(product.Namespace, selector, kubeClient) - if err != nil { - return "", err - } for _, deploy := range deployments { for _, c := range deploy.Spec.Template.Spec.Containers { if c.Name == container { @@ -773,6 +768,11 @@ func getImageInfoFromWorkload(envName, productName, serviceName, container strin } } } + var statefulSets []*appsv1.StatefulSet + statefulSets, err = getter.ListStatefulSets(product.Namespace, selector, kubeClient) + if err != nil { + return "", err + } for _, sts := range statefulSets { for _, c := range sts.Spec.Template.Spec.Containers { if c.Name == container { -- Gitee From e31f949707aae98f346f8b4463a435707e74e8ba Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 31 Mar 2022 10:26:05 +0800 Subject: [PATCH 2002/2647] remove unused code Signed-off-by: zhangyifei --- .../aslan/core/common/service/product.go | 207 ------------------ 1 file changed, 207 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index 466c1b0b..ca6952c1 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -19,226 +19,19 @@ package service import ( "context" "fmt" - "time" "github.com/hashicorp/go-multierror" - helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" - "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - e "github.com/koderover/zadig/pkg/tool/errors" - helmtool "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/kube/informer" "github.com/koderover/zadig/pkg/tool/kube/updater" ) -const ( - timeout = 60 -) - -// TODO: Deprecated. -func DeleteProduct(username, envName, productName, requestID string, log *zap.SugaredLogger) (err error) { - eventStart := time.Now().Unix() - productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) - if err != nil { - log.Errorf("find product error: %v", err) - return err - } - - // delete informer's cache - informer.DeleteInformer(productInfo.ClusterID, productInfo.Namespace) - - envCMMap, err := collaboration.GetEnvCMMap([]string{productName}, log) - if err != nil { - return err - } - if cmSets, ok := envCMMap[collaboration.BuildEnvCMMapKey(productName, envName)]; ok { - return fmt.Errorf("this is a base environment, collaborations:%v is related", cmSets.List()) - } - - restConfig, err := kube.GetRESTConfig(productInfo.ClusterID) - if err != nil { - return e.ErrDeleteEnv.AddErr(err) - } - - // 设置产品状态 - err = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusDeleting) - if err != nil { - log.Errorf("[%s][%s] update product status error: %v", username, productInfo.Namespace, err) - return e.ErrDeleteEnv.AddDesc("更新环境状态失败: " + err.Error()) - } - - log.Infof("[%s] delete product %s", username, productInfo.Namespace) - LogProductStats(username, setting.DeleteProductEvent, productName, requestID, eventStart, log) - - switch productInfo.Source { - case setting.SourceFromHelm: - err = commonrepo.NewProductColl().Delete(envName, productName) - if err != nil { - log.Errorf("Product.Delete error: %v", err) - } - - go func() { - var err error - defer func() { - if err != nil { - // 发送删除产品失败消息给用户 - title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) - SendErrorMessage(username, title, requestID, err, log) - _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) - } else { - title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) - content := fmt.Sprintf("namespace:%s", productInfo.Namespace) - SendMessage(username, title, content, requestID, log) - } - }() - - //卸载helm release资源 - if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { - for _, services := range productInfo.Services { - for _, service := range services { - if err = hc.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: fmt.Sprintf("%s-%s", productInfo.Namespace, service.ServiceName), - Namespace: productInfo.Namespace, - Wait: true, - Force: true, - Timeout: timeout * time.Second * 10, - }); err != nil { - log.Errorf("UninstallRelease err:%v", err) - } - } - } - } else { - log.Errorf("获取helmClient err:%v", err) - } - - //删除namespace - s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() - if err1 := DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { - err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) - return - } - }() - case setting.SourceFromExternal: - err = commonrepo.NewProductColl().Delete(envName, productName) - if err != nil { - log.Errorf("Product.Delete error: %v", err) - } - - // 删除workload数据 - tempProduct, err := template.NewProductColl().Find(productName) - if err != nil { - log.Errorf("project not found error:%s", err) - } - if tempProduct.ProductFeature != nil && tempProduct.ProductFeature.CreateEnvType == setting.SourceFromExternal { - workloadStat, err := commonrepo.NewWorkLoadsStatColl().Find(productInfo.ClusterID, productInfo.Namespace) - if err != nil { - log.Errorf("workflowStat not found error:%s", err) - } - if workloadStat != nil { - workloadStat.Workloads = FilterWorkloadsByEnv(workloadStat.Workloads, productInfo.EnvName) - if err := commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(workloadStat); err != nil { - log.Errorf("update workloads fail error:%s", err) - } - } - // 获取所有external的服务 - currentEnvServices, err := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, envName) - if err != nil { - log.Errorf("failed to list external workload, error:%s", err) - } - - externalEnvServices, err := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, - ExcludeEnvName: envName, - }) - if err != nil { - log.Errorf("failed to list external service, error:%s", err) - } - - externalEnvServiceM := make(map[string]bool) - for _, externalEnvService := range externalEnvServices { - externalEnvServiceM[externalEnvService.ServiceName] = true - } - - deleteServices := sets.NewString() - for _, currentEnvService := range currentEnvServices { - if _, isExist := externalEnvServiceM[currentEnvService.ServiceName]; !isExist { - deleteServices.Insert(currentEnvService.ServiceName) - } - } - err = commonrepo.NewServiceColl().BatchUpdateExternalServicesStatus(productName, "", setting.ProductStatusDeleting, deleteServices.List()) - if err != nil { - log.Errorf("UpdateStatus external services error:%s", err) - } - // delete services_in_external_env data - if err = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: productName, - EnvName: envName, - }); err != nil { - log.Errorf("remove services in external env error:%s", err) - } - } - case setting.SourceFromPM: - err = commonrepo.NewProductColl().Delete(envName, productName) - if err != nil { - log.Errorf("Product.Delete error: %v", err) - } - default: - go func() { - var err error - defer func() { - if err != nil { - // 发送删除产品失败消息给用户 - title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) - SendErrorMessage(username, title, requestID, err, log) - _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) - } else { - title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) - content := fmt.Sprintf("namespace:%s", productInfo.Namespace) - SendMessage(username, title, content, requestID, log) - } - }() - - // Delete Cluster level resources - err = DeleteClusterResource(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), productInfo.ClusterID, log) - if err != nil { - err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) - return - } - - // // Handles environment sharing related operations. - // err = envservice.EnsureDeleteShareEnvConfig(context.TODO(), productInfo, istioClient) - // if err != nil { - // err = e.ErrDeleteProduct.AddDesc(e.DeleteVirtualServiceErrMsg + ": " + err.Error()) - // return - // } - - s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() - if err1 := DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { - err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) - return - } - - err = commonrepo.NewProductColl().Delete(envName, productName) - if err != nil { - log.Errorf("Product.Delete error: %v", err) - } - }() - } - - return nil -} - func FilterWorkloadsByEnv(exist []commonmodels.Workload, env string) []commonmodels.Workload { result := make([]commonmodels.Workload, 0) for _, v := range exist { -- Gitee From f626cf3944dae896afba13e30067e1aac121d395 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 10:27:09 +0800 Subject: [PATCH 2003/2647] ua for role pod_debug Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 2 ++ .../internal/repository/models/oldpolicy.go | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index bc55566a..b23196de 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -34,6 +34,7 @@ func V1110ToV1100() error { } func roleAddPodDebug() error { + log.Info("start to scan role") ctx := context.Background() var res []*models.Role cursor, err := newRoleColl().Find(ctx, bson.M{}) @@ -46,6 +47,7 @@ func roleAddPodDebug() error { return err } for _, role := range res { + log.Infof("start to check role:%s\n", role.Name) toUpdate := false for i, rule := range role.Rules { verbsSet := sets.NewString(rule.Verbs...) diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index 1cca4fa7..f86cc210 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -36,6 +36,15 @@ type Role struct { type Rule struct { // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. Verbs []string `bson:"verbs" json:"verbs"` + // Resources is a list of resources this rule applies to. '*' represents all resources. + Resources []string `bson:"resources" json:"resources"` + Kind string `bson:"kind" json:"kind"` + MatchAttributes []MatchAttribute `bson:"match_attributes" json:"match_attributes"` +} + +type MatchAttribute struct { + Key string `json:"key"` + Value string `json:"value"` } type RoleBinding struct { -- Gitee From 105f0f58ddca0abd232dcbd2b1a5bb8881e83bcf Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 31 Mar 2022 10:27:29 +0800 Subject: [PATCH 2004/2647] remove buildx in makefile Signed-off-by: zhangyifei --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5550e2aa..98a7c92a 100644 --- a/Makefile +++ b/Makefile @@ -60,7 +60,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go - @docker buildx build --platform=linux/amd64 -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . + @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.arm64: %.image.amd64 @@ -83,7 +83,7 @@ resource-server.build.amd64: @cp docker/service/resource-server.Dockerfile docker/dist/amd64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/resource-server.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go - @docker buildx build --platform=linux/amd64 -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . + @docker build -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 resource-server.upload.amd64: resource-server.build.amd64 -- Gitee From 2947e6cafcd4b358560e049ac98683155fab6978 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 10:28:53 +0800 Subject: [PATCH 2005/2647] ua for role pod_debug Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index b23196de..7cce0acd 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -2,7 +2,6 @@ package migrate import ( "context" - "fmt" "go.mongodb.org/mongo-driver/bson" "k8s.io/apimachinery/pkg/util/sets" @@ -59,14 +58,14 @@ func roleAddPodDebug() error { } if toUpdate { // start to update - fmt.Printf("find role:%s has verb:manage_environment ,start to update the role\n", role.Name) + log.Infof("find role:%s has verb:manage_environment ,start to update the role\n", role.Name) query := bson.M{"name": role.Name} change := bson.M{"$set": bson.M{ "rules": role.Rules, }} _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { - fmt.Printf("err update%s\n", err) + log.Errorf("err update%s\n", err) return err } } @@ -98,14 +97,14 @@ func roleDeletePodDebug() error { } if toUpdate { // start to update - fmt.Printf("find role:%s has verb:debug_pod ,start to update the role\n", role.Name) + log.Infof("find role:%s has verb:debug_pod ,start to update the role\n", role.Name) query := bson.M{"name": role.Name} change := bson.M{"$set": bson.M{ "rules": role.Rules, }} _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { - fmt.Printf("err update%s\n", err) + log.Errorf("err update%s\n", err) return err } } -- Gitee From ee3835e1865862aed00a37c160816861b04e69f7 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 31 Mar 2022 10:36:26 +0800 Subject: [PATCH 2006/2647] clean code Signed-off-by: zhangyifei --- .../aslan/core/environment/handler/environment.go | 1 - .../aslan/core/environment/service/environment.go | 8 +------- .../aslan/core/environment/service/share_env.go | 3 --- .../aslan/core/environment/service/share_env_utils.go | 2 +- 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index dcc2bbfa..626a9e2b 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -496,7 +496,6 @@ func DeleteProduct(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境", envName, "", ctx.Logger) - // ctx.Err = commonservice.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) ctx.Err = service.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 450ff984..651690d9 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1768,7 +1768,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return e.ErrDeleteEnv.AddErr(err) } - // 设置产品状态 err = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusDeleting) if err != nil { log.Errorf("[%s][%s] update product status error: %v", username, productInfo.Namespace, err) @@ -1789,7 +1788,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su var err error defer func() { if err != nil { - // 发送删除产品失败消息给用户 title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) commonservice.SendErrorMessage(username, title, requestID, err, log) _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) @@ -1800,7 +1798,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } }() - //卸载helm release资源 if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { for _, services := range productInfo.Services { for _, service := range services { @@ -1819,7 +1816,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("获取helmClient err:%v", err) } - //删除namespace s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() if err1 := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) @@ -1832,7 +1828,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("Product.Delete error: %v", err) } - // 删除workload数据 tempProduct, err := mongotemplate.NewProductColl().Find(productName) if err != nil { log.Errorf("project not found error:%s", err) @@ -1848,7 +1843,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Errorf("update workloads fail error:%s", err) } } - // 获取所有external的服务 + currentEnvServices, err := commonrepo.NewServiceColl().ListExternalWorkloadsBy(productName, envName) if err != nil { log.Errorf("failed to list external workload, error:%s", err) @@ -1895,7 +1890,6 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su var err error defer func() { if err != nil { - // 发送删除产品失败消息给用户 title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) commonservice.SendErrorMessage(username, title, requestID, err, log) _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 63803c7f..9723d630 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -20,7 +20,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "time" @@ -45,8 +44,6 @@ import ( zadigtypes "github.com/koderover/zadig/pkg/types" ) -var ErrNotImplemented = errors.New("not implemented") - const istioNamespace = "istio-system" const istioProxyName = "istio-proxy" const zadigEnvoyFilter = "zadig-share-env" diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 4de989f3..63249634 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -1,7 +1,7 @@ /* Copyright 2022 The KodeRover Authors. -.Licensed under the Apache License, Version 2.0 (the "License"); +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at -- Gitee From 5db2b36ef883d3e60aa32cd67b11e8d336d38a8e Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 10:58:01 +0800 Subject: [PATCH 2007/2647] import fix Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/pipeline_validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 1d21db97..b199f39f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - "k8s.io/apimachinery/pkg/labels" "regexp" "sort" "strconv" @@ -31,6 +30,7 @@ import ( "github.com/google/go-github/v35/github" "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/labels" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" -- Gitee From 3da3c1038b768f03a216dceb4e075929eb94cfd2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 11:02:25 +0800 Subject: [PATCH 2008/2647] ua for role pod_debug Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index 7cce0acd..8a3ea496 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -43,6 +43,7 @@ func roleAddPodDebug() error { } err = cursor.All(ctx, &res) if err != nil { + log.Errorf("get roles err:%s", err) return err } for _, role := range res { @@ -83,6 +84,7 @@ func roleDeletePodDebug() error { } err = cursor.All(ctx, &res) if err != nil { + log.Errorf("get roles err:%s", err) return err } for _, role := range res { -- Gitee From 337d2d772d1f2ca5f6c36cfd5bbcbf63a64b1f50 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 31 Mar 2022 11:28:10 +0800 Subject: [PATCH 2009/2647] fix host login ws error Signed-off-by: ddh27 --- .../aslan/core/environment/handler/pm_exec.go | 18 +- .../aslan/core/environment/service/pm_exec.go | 47 ++-- pkg/tool/ssh/ssh.go | 2 +- pkg/tool/wsconn/wsconn.go | 243 ++++++++++-------- 4 files changed, 180 insertions(+), 130 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/pm_exec.go b/pkg/microservice/aslan/core/environment/handler/pm_exec.go index 08e333e8..5b28fa32 100644 --- a/pkg/microservice/aslan/core/environment/handler/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/handler/pm_exec.go @@ -1,9 +1,12 @@ package handler import ( + "strconv" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func ConnectSshPmExec(c *gin.Context) { @@ -13,6 +16,19 @@ func ConnectSshPmExec(c *gin.Context) { projectName := c.Query("projectName") ip := c.Query("ip") name := c.Param("name") + if projectName == "" || ip == "" || name == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("param projectName or ip or name is empty") + } + colsStr := c.DefaultQuery("cols", "135") + cols, err := strconv.Atoi(colsStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + } + rowsStr := c.DefaultQuery("rows", "40") + rows, err := strconv.Atoi(rowsStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + } - ctx.Err = service.ConnectSshPmExec(c, ctx.UserName, name, projectName, ip, ctx.Logger) + ctx.Err = service.ConnectSshPmExec(c, ctx.UserName, name, projectName, ip, cols, rows, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/pm_exec.go b/pkg/microservice/aslan/core/environment/service/pm_exec.go index 91179e7c..8c198cab 100644 --- a/pkg/microservice/aslan/core/environment/service/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/service/pm_exec.go @@ -1,6 +1,7 @@ package service import ( + "bytes" "encoding/base64" "fmt" "net/http" @@ -24,7 +25,7 @@ var upgrader = websocket.Upgrader{ }, } -func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, log *zap.SugaredLogger) error { +func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, cols, rows int, log *zap.SugaredLogger) error { resp, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ Address: ip, }) @@ -35,35 +36,43 @@ func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, if resp.Status != setting.PMHostStatusNormal { return e.ErrLoginPm.AddDesc(fmt.Sprintf("host %s status %s,is not normal", ip, resp.Status)) } + if resp.Port == 0 { + resp.Port = setting.PMHostDefaultPort + } + sDec, err := base64.StdEncoding.DecodeString(resp.PrivateKey) if err != nil { - log.Errorf("base64 decode failure ip:%s, error:%s", ip, err) + log.Errorf("base64 decode failed ip:%s, error:%s", ip, err) + return e.ErrLoginPm.AddDesc(fmt.Sprintf("base64 decode failed ip:%s, error:%s", ip, err)) } - conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + sshCli, err := toolssh.NewSshCli(sDec, resp.UserName, resp.IP, resp.Port) if err != nil { - log.Error(err) + log.Errorf("NewSshCli err:%s", err) return e.ErrLoginPm.AddErr(err) } - if resp.Port == 0 { - resp.Port = setting.PMHostDefaultPort - } - sshCli, err := toolssh.NewSshCli(sDec, resp.UserName, resp.IP, resp.Port) + + sshConn, err := wsconn.NewSshConn(cols, rows, sshCli) if err != nil { - log.Error(err) - conn.WriteMessage(1, []byte(err.Error())) - conn.Close() + log.Errorf("NewSshConn err:%s", err) return e.ErrLoginPm.AddErr(err) } - sshClient := &wsconn.SshClient{ - SshCli: sshCli, - } + defer sshConn.Close() - if err := sshClient.GenerateWebTerminal(150, 40); err != nil { - log.Error(err) - conn.WriteMessage(1, []byte(err.Error())) - conn.Close() + ws, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + log.Errorf("ws upgrade err:%s", err) + return e.ErrLoginPm.AddErr(err) } - sshClient.ConnectWs(conn) + defer ws.Close() + + stopChan := make(chan bool, 3) + var logBuff = new(bytes.Buffer) + + go sshConn.ReadWsMessage(ws, logBuff, stopChan) + go sshConn.SendWsWriteMessage(ws, stopChan) + go sshConn.SessionWait(stopChan) + + <-stopChan return nil } diff --git a/pkg/tool/ssh/ssh.go b/pkg/tool/ssh/ssh.go index 13c2a5e3..cfc71461 100644 --- a/pkg/tool/ssh/ssh.go +++ b/pkg/tool/ssh/ssh.go @@ -23,7 +23,7 @@ func NewSshCli(privateKey []byte, user, host string, port int64) (*ssh.Client, e clientConfig = &ssh.ClientConfig{ User: user, Auth: auth, - Timeout: 20 * time.Second, + Timeout: 5 * time.Second, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, diff --git a/pkg/tool/wsconn/wsconn.go b/pkg/tool/wsconn/wsconn.go index 433220f3..dae2062f 100644 --- a/pkg/tool/wsconn/wsconn.go +++ b/pkg/tool/wsconn/wsconn.go @@ -1,9 +1,11 @@ package wsconn import ( - "bufio" + "bytes" + "encoding/json" + "io" + "sync" "time" - "unicode/utf8" "github.com/gorilla/websocket" "golang.org/x/crypto/ssh" @@ -11,140 +13,163 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) -type ptyReqParam struct { - Term string - Cols uint32 - Rows uint32 - Width uint32 - Height uint32 - Modelist string +type wsOperationType string + +const ( + wsMsgStdout wsOperationType = "stdout" + wsMsgStdin wsOperationType = "stdin" + wsMsgResize wsOperationType = "resize" +) + +type wsMessage struct { + Operation wsOperationType `json:"operation"` + Data string `json:"data"` + Rows int `json:"rows"` + Cols int `json:"cols"` } -type SshClient struct { +type wsBufferWriter struct { + buffer bytes.Buffer + mu sync.Mutex +} + +func (w *wsBufferWriter) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Write(p) +} + +type SshConn struct { + Stdin io.WriteCloser + WsWriter *wsBufferWriter SshSession *ssh.Session - SshCli *ssh.Client - sshChan ssh.Channel } -func (sshCli *SshClient) GenerateWebTerminal(cols, rows uint32) error { - session, err := sshCli.SshCli.NewSession() +func NewSshConn(cols, rows int, sshClient *ssh.Client) (*SshConn, error) { + sshSession, err := sshClient.NewSession() if err != nil { - return err + return nil, err } - sshCli.SshSession = session - channel, inRequests, err := sshCli.SshCli.OpenChannel("session", nil) + + stdin, err := sshSession.StdinPipe() if err != nil { - return err + return nil, err } - sshCli.sshChan = channel - go func() { - for req := range inRequests { - if req.WantReply { - req.Reply(false, nil) - } - } - }() - termModes := ssh.TerminalModes{ + + wsWriter := new(wsBufferWriter) + sshSession.Stdout = wsWriter + sshSession.Stderr = wsWriter + + modes := ssh.TerminalModes{ ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400, } - var termModesList []byte - for k, v := range termModes { - kv := struct { - Key byte - Val uint32 - }{k, v} - termModesList = append(termModesList, ssh.Marshal(&kv)...) - } - termModesList = append(termModesList, 0) - reqParam := ptyReqParam{ - Term: "xterm", - Cols: cols, - Rows: rows, - Width: uint32(cols * 8), - Height: uint32(cols * 8), - Modelist: string(termModesList), - } - ok, err := channel.SendRequest("pty-req", true, ssh.Marshal(&reqParam)) - if !ok || err != nil { - return err + + if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { + return nil, err } - ok, err = channel.SendRequest("shell", true, nil) - if !ok || err != nil { - return err + if err := sshSession.Shell(); err != nil { + return nil, err } - return nil + return &SshConn{Stdin: stdin, WsWriter: wsWriter, SshSession: sshSession}, nil } -func (sshCli *SshClient) ConnectWs(ws *websocket.Conn) { - go func() { - for { - _, p, err := ws.ReadMessage() +func (ssConn *SshConn) ReadWsMessage(wsConn *websocket.Conn, logBuff *bytes.Buffer, stopCh chan bool) { + defer setStop(stopCh) + for { + select { + case <-stopCh: + return + default: + _, wsData, err := wsConn.ReadMessage() if err != nil { - log.Error(err) + log.Errorf("read ws message err:%s", err) return } - _, err = sshCli.sshChan.Write(p) - if err != nil { - log.Error(err) + var wsMsgObj wsMessage + if err := json.Unmarshal(wsData, &wsMsgObj); err != nil { + log.Error("ReceiveWsMessage Unmarshal err:", string(wsData), err) return } - } - }() - - go func() { - bufferReader := bufio.NewReader(sshCli.sshChan) - buffer := []byte{} - ticker := time.NewTimer(time.Microsecond * 100) - defer ticker.Stop() - - rChan := make(chan rune) - go func() { - defer sshCli.SshCli.Close() - defer sshCli.sshChan.Close() - - for { - r, size, err := bufferReader.ReadRune() - if err != nil { - log.Error(err) - ws.WriteMessage(1, []byte("\033[31m websocket is closed\033[0m")) - ws.Close() - return - } - if size > 0 { - rChan <- r - } - } - }() - - for { - select { - case <-ticker.C: - if len(buffer) != 0 { - err := ws.WriteMessage(websocket.TextMessage, buffer) - buffer = []byte{} - if err != nil { - log.Error(err) - return + + switch wsMsgObj.Operation { + case wsMsgResize: + if wsMsgObj.Cols > 0 && wsMsgObj.Rows > 0 { + if err := ssConn.SshSession.WindowChange(wsMsgObj.Rows, wsMsgObj.Cols); err != nil { + log.Error("resize windows err:", err) } } - ticker.Reset(time.Microsecond * 100) - case dRune := <-rChan: - if dRune != utf8.RuneError { - byt := make([]byte, utf8.RuneLen(dRune)) - utf8.EncodeRune(byt, dRune) - buffer = append(buffer, byt...) - } else { - buffer = append(buffer, []byte("@")...) + case wsMsgStdin: + decodeBytes := []byte(wsMsgObj.Data) + if _, err := ssConn.Stdin.Write(decodeBytes); err != nil { + log.Error("ws stdin write to ssh.stdin err:", err) + setStop(stopCh) } + // if _, err := logBuff.Write(decodeBytes); err != nil { + // log.Error("write received cmd into log buffer err:", err) + // } } } - }() + } +} + +func (ssConn *SshConn) SendWsWriteMessage(wsConn *websocket.Conn, stopCh chan bool) { + defer setStop(stopCh) + tick := time.NewTicker(time.Millisecond * time.Duration(12)) + pingTick := time.NewTimer(10 * time.Second) + defer tick.Stop() + + for { + select { + case <-tick.C: + if err := flushWsWriter(ssConn.WsWriter, wsConn); err != nil { + log.Error("ssh sending output to webSocket err:", err) + return + } + case <-pingTick.C: + if err := wsConn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + log.Error("ssh sending ping to webSocket err:", err) + return + } + case <-stopCh: + return + } + } +} - defer func() { - if err := recover(); err != nil { - log.Error(err) +func (ssConn *SshConn) SessionWait(stopChan chan bool) { + if err := ssConn.SshSession.Wait(); err != nil { + log.Error("ssh session wait err:", err) + setStop(stopChan) + } +} + +func (s *SshConn) Close() { + if s.SshSession != nil { + s.SshSession.Close() + } +} + +func setStop(ch chan bool) { + ch <- true +} + +func flushWsWriter(w *wsBufferWriter, wsConn *websocket.Conn) error { + if w.buffer.Len() != 0 { + msg, err := json.Marshal(wsMessage{ + Operation: wsMsgStdout, + Data: string(w.buffer.Bytes()), + }) + if err != nil { + log.Errorf("write parse message err: %s", err) + return err + } + err = wsConn.WriteMessage(websocket.TextMessage, msg) + if err != nil { + return err } - }() + w.buffer.Reset() + } + return nil } -- Gitee From a5b96244649bb72d3b808281b2dba41f9e8b8cb8 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 31 Mar 2022 11:30:51 +0800 Subject: [PATCH 2010/2647] fix bug of constructing envs when creating pm env Signed-off-by: zhangyifei --- .../warpdrive/core/service/taskplugin/build.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index cd9f2ffa..517971f0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -168,13 +168,20 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameVar) } else if pipelineTask.Type == config.ServiceType { + // Note: + // In cloud host scenarios, this type of task is performed when creating the environment. + // This type of task does not occur in other scenarios. + envName := pipelineTask.ServiceTaskArgs.Namespace envNameVar := &task.KeyVal{Key: "ENV_NAME", Value: envName, IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, envNameVar) } - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "WORKFLOW", Value: pipelineTask.WorkflowArgs.WorkflowName}) - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "PROJECT", Value: pipelineTask.WorkflowArgs.ProductTmplName}) + // Note: When 'pipelinetask.type == config.ServiceType', it may be `nil`. + if pipelineTask.WorkflowArgs != nil { + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "WORKFLOW", Value: pipelineTask.WorkflowArgs.WorkflowName}) + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, &task.KeyVal{Key: "PROJECT", Value: pipelineTask.WorkflowArgs.ProductTmplName}) + } taskIDVar := &task.KeyVal{Key: "TASK_ID", Value: strconv.FormatInt(pipelineTask.TaskID, 10), IsCredential: false} p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) -- Gitee From c3709085590535b9650342f82e53248950d2546c Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 31 Mar 2022 11:37:03 +0800 Subject: [PATCH 2011/2647] fix host login ws error Signed-off-by: ddh27 --- .../aslan/core/environment/handler/pm_exec.go | 16 ++++++++++++++ .../aslan/core/environment/service/pm_exec.go | 21 +++++++++++++++---- .../cron/core/service/client/pm.go | 16 ++++++++++++++ .../core/service/scheduler/schedule_pm.go | 16 ++++++++++++++ pkg/tool/ssh/ssh.go | 16 ++++++++++++++ pkg/tool/wsconn/wsconn.go | 21 +++++++++++++++---- 6 files changed, 98 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/pm_exec.go b/pkg/microservice/aslan/core/environment/handler/pm_exec.go index 5b28fa32..615256df 100644 --- a/pkg/microservice/aslan/core/environment/handler/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/handler/pm_exec.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/environment/service/pm_exec.go b/pkg/microservice/aslan/core/environment/service/pm_exec.go index 8c198cab..41a61aef 100644 --- a/pkg/microservice/aslan/core/environment/service/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/service/pm_exec.go @@ -1,7 +1,22 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package service import ( - "bytes" "encoding/base64" "fmt" "net/http" @@ -67,9 +82,7 @@ func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, defer ws.Close() stopChan := make(chan bool, 3) - var logBuff = new(bytes.Buffer) - - go sshConn.ReadWsMessage(ws, logBuff, stopChan) + go sshConn.ReadWsMessage(ws, stopChan) go sshConn.SendWsWriteMessage(ws, stopChan) go sshConn.SessionWait(stopChan) diff --git a/pkg/microservice/cron/core/service/client/pm.go b/pkg/microservice/cron/core/service/client/pm.go index d0e2072b..c3b3b9ee 100644 --- a/pkg/microservice/cron/core/service/client/pm.go +++ b/pkg/microservice/cron/core/service/client/pm.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package client import ( diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_pm.go b/pkg/microservice/cron/core/service/scheduler/schedule_pm.go index 8139eff3..2ec5a5ec 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_pm.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_pm.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scheduler import ( diff --git a/pkg/tool/ssh/ssh.go b/pkg/tool/ssh/ssh.go index cfc71461..da38b4b1 100644 --- a/pkg/tool/ssh/ssh.go +++ b/pkg/tool/ssh/ssh.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package ssh import ( diff --git a/pkg/tool/wsconn/wsconn.go b/pkg/tool/wsconn/wsconn.go index dae2062f..c80cd40c 100644 --- a/pkg/tool/wsconn/wsconn.go +++ b/pkg/tool/wsconn/wsconn.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package wsconn import ( @@ -75,7 +91,7 @@ func NewSshConn(cols, rows int, sshClient *ssh.Client) (*SshConn, error) { return &SshConn{Stdin: stdin, WsWriter: wsWriter, SshSession: sshSession}, nil } -func (ssConn *SshConn) ReadWsMessage(wsConn *websocket.Conn, logBuff *bytes.Buffer, stopCh chan bool) { +func (ssConn *SshConn) ReadWsMessage(wsConn *websocket.Conn, stopCh chan bool) { defer setStop(stopCh) for { select { @@ -106,9 +122,6 @@ func (ssConn *SshConn) ReadWsMessage(wsConn *websocket.Conn, logBuff *bytes.Buff log.Error("ws stdin write to ssh.stdin err:", err) setStop(stopCh) } - // if _, err := logBuff.Write(decodeBytes); err != nil { - // log.Error("write received cmd into log buffer err:", err) - // } } } } -- Gitee From a666b7b1070ce35cb2ecde1c60af36688dcd973e Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 11:40:46 +0800 Subject: [PATCH 2012/2647] review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 10 +++++----- .../internal/repository/models/oldpolicy.go | 9 ++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index 8a3ea496..8a2a76f6 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -38,7 +38,7 @@ func roleAddPodDebug() error { var res []*models.Role cursor, err := newRoleColl().Find(ctx, bson.M{}) if err != nil { - log.Errorf("Failed to Find Policies, err: %s", err) + log.Errorf("Failed to find roles, err: %s", err) return err } err = cursor.All(ctx, &res) @@ -47,7 +47,7 @@ func roleAddPodDebug() error { return err } for _, role := range res { - log.Infof("start to check role:%s\n", role.Name) + log.Infof("start to check role:%s", role.Name) toUpdate := false for i, rule := range role.Rules { verbsSet := sets.NewString(rule.Verbs...) @@ -66,7 +66,7 @@ func roleAddPodDebug() error { }} _, err := newRoleColl().UpdateOne(ctx, query, change) if err != nil { - log.Errorf("err update%s\n", err) + log.Errorf("fail to update,err:%s", err) return err } } @@ -79,7 +79,7 @@ func roleDeletePodDebug() error { var res []*models.Role cursor, err := newRoleColl().Find(ctx, bson.M{}) if err != nil { - log.Errorf("Failed to Find Policies, err: %s", err) + log.Errorf("Fail to find roles, err: %s", err) return err } err = cursor.All(ctx, &res) @@ -99,7 +99,7 @@ func roleDeletePodDebug() error { } if toUpdate { // start to update - log.Infof("find role:%s has verb:debug_pod ,start to update the role\n", role.Name) + log.Infof("find role:%s has verb:debug_pod ,start to update the role", role.Name) query := bson.M{"name": role.Name} change := bson.M{"$set": bson.M{ "rules": role.Rules, diff --git a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go index f86cc210..8ddeb087 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/oldpolicy.go @@ -35,11 +35,10 @@ type Role struct { type Rule struct { // Verbs is a list of http methods or resource actions that apply to ALL the Resources contained in this rule. '*' represents all methods. - Verbs []string `bson:"verbs" json:"verbs"` - // Resources is a list of resources this rule applies to. '*' represents all resources. - Resources []string `bson:"resources" json:"resources"` - Kind string `bson:"kind" json:"kind"` - MatchAttributes []MatchAttribute `bson:"match_attributes" json:"match_attributes"` + Verbs []string `bson:"verbs" json:"verbs"` + Resources []string `bson:"resources" json:"resources"` + Kind string `bson:"kind" json:"kind"` + MatchAttributes []MatchAttribute `bson:"match_attributes" json:"match_attributes"` } type MatchAttribute struct { -- Gitee From c70cbb05c6281f41ab4f945c3399190d94aed958 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 11:41:58 +0800 Subject: [PATCH 2013/2647] review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index 8a2a76f6..cf25611b 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -59,7 +59,7 @@ func roleAddPodDebug() error { } if toUpdate { // start to update - log.Infof("find role:%s has verb:manage_environment ,start to update the role\n", role.Name) + log.Infof("find role:%s has verb:manage_environment ,start to update the role", role.Name) query := bson.M{"name": role.Name} change := bson.M{"$set": bson.M{ "rules": role.Rules, -- Gitee From d2783551d14d30a4bb844029641a3c1cf2e10467 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 12:01:26 +0800 Subject: [PATCH 2014/2647] review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index cf25611b..9f3f37ae 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -17,7 +17,7 @@ func init() { } func V1100ToV1110() error { - // iterate all roles , if the role contains manage env , add debug_pod + // iterate all roles , if the role contains manage env , add debug_pod if err := roleAddPodDebug(); err != nil { return err } @@ -38,7 +38,7 @@ func roleAddPodDebug() error { var res []*models.Role cursor, err := newRoleColl().Find(ctx, bson.M{}) if err != nil { - log.Errorf("Failed to find roles, err: %s", err) + log.Errorf("fail to find roles, err: %s", err) return err } err = cursor.All(ctx, &res) @@ -79,7 +79,7 @@ func roleDeletePodDebug() error { var res []*models.Role cursor, err := newRoleColl().Find(ctx, bson.M{}) if err != nil { - log.Errorf("Fail to find roles, err: %s", err) + log.Errorf("fail to find roles, err: %s", err) return err } err = cursor.All(ctx, &res) -- Gitee From 6d697e32f58f69ea27c155ee4f35698d34443a4d Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 12:02:23 +0800 Subject: [PATCH 2015/2647] review Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index 9f3f37ae..3b29e2c2 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -19,6 +19,7 @@ func init() { func V1100ToV1110() error { // iterate all roles , if the role contains manage env , add debug_pod if err := roleAddPodDebug(); err != nil { + log.Errorf("roleAddPodDebug err:%s", err) return err } @@ -27,6 +28,7 @@ func V1100ToV1110() error { func V1110ToV1100() error { if err := roleDeletePodDebug(); err != nil { + log.Errorf("roleDeletePodDebug err:%s", err) return err } return nil -- Gitee From 4e9272861a745ee352eb806ffa95296a763c98a7 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 31 Mar 2022 12:10:56 +0800 Subject: [PATCH 2016/2647] add space trim logic for service Signed-off-by: allenshen --- pkg/microservice/aslan/core/service/service/helm.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 8111b38b..9b42dc78 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -457,6 +457,9 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil, err } + // NOTE we may need a better way to handle service name with spaces + args.Name = strings.TrimSpace(args.Name) + var values [][]byte if len(templateChartInfo.DefaultValuesYAML) > 0 { //render variables @@ -950,6 +953,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi serviceName := filepath.Base(path) serviceName = strings.TrimSuffix(serviceName, filepath.Ext(serviceName)) + serviceName = strings.TrimSpace(serviceName) to := filepath.Join(config.LocalServicePath(projectName, serviceName), serviceName) // remove old files -- Gitee From aca5abec58c9f4369f3ba687fa29b6301881d544 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 31 Mar 2022 14:04:32 +0800 Subject: [PATCH 2017/2647] add license Signed-off-by: mouuii --- pkg/cli/upgradeassistant/cmd/migrate/1110.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1110.go b/pkg/cli/upgradeassistant/cmd/migrate/1110.go index 3b29e2c2..cc46bd0a 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1110.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1110.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package migrate import ( -- Gitee From 59f018f48be39c8710e5cb1a2671b5139bd11428 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 31 Mar 2022 14:07:13 +0800 Subject: [PATCH 2018/2647] debugs Signed-off-by: Min Min --- .../workflow/service/workflow/workflow.go | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 7b632ccd..b596fa22 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -285,6 +285,45 @@ func FindWorkflow(workflowName string, log *zap.SugaredLogger) (*commonmodels.Wo } resp.Schedules = &schedule } + if resp.BuildStage.Enabled { + // make a map of current target modules + buildMap := map[string]bool{} + for _, build := range resp.BuildStage.Modules { + key := fmt.Sprintf("%s-%s-%s", build.Target.ProductName, build.Target.ServiceName, build.Target.ServiceModule) + buildMap[key] = true + } + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(resp.ProductTmplName) + if err != nil { + log.Errorf("ServiceTmpl.ListMaxRevisions error: %v", err) + return resp, e.ErrListTemplate.AddDesc(err.Error()) + } + for _, serviceTmpl := range services { + switch serviceTmpl.Type { + case setting.PMDeployType: + // PM service does not have such logic + break + + case setting.K8SDeployType, setting.HelmDeployType: + for _, container := range serviceTmpl.Containers { + key := fmt.Sprintf("%s-%s-%s", serviceTmpl.ProductName, serviceTmpl.ServiceName, container.Name) + // if no target info is found for this container, meaning that this is a new service for that workflow + // then we need to add it to the response + if _, ok := buildMap[key]; !ok { + resp.BuildStage.Modules = append(resp.BuildStage.Modules, &commonmodels.BuildModule{ + HideServiceModule: false, + BuildModuleVer: "stable", + Target: &commonmodels.ServiceModuleTarget{ + ProductName: serviceTmpl.ProductName, + ServiceName: serviceTmpl.ServiceName, + ServiceModule: container.Name, + }, + }) + } + } + } + } + } + return resp, nil } -- Gitee From a3070dbfa1d50934540fb0008d57f04b44c5a443 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 31 Mar 2022 14:11:36 +0800 Subject: [PATCH 2019/2647] fix overview bug Signed-off-by: liu deyi --- .../core/common/repository/mongodb/delivery_artifact.go | 6 ++++++ pkg/microservice/aslan/core/stat/service/overview.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go index a7fb5ebc..d39407a5 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/delivery_artifact.go @@ -45,6 +45,7 @@ type DeliveryArtifactArgs struct { PerPage int `json:"per_page"` Page int `json:"page"` IsFuzzyQuery bool `json:"is_fuzzy_query"` + OnlyCount bool `json:"only_count"` PackageStorageURI string `json:"package_storage_uri"` } @@ -120,6 +121,11 @@ func (c *DeliveryArtifactColl) List(args *DeliveryArtifactArgs) ([]*models.Deliv if err != nil { return nil, 0, fmt.Errorf("find artifact err:%v", err) } + + if args.OnlyCount { + return nil, int(count), nil + } + opt := options.Find(). SetSort(bson.D{{"created_time", -1}}). SetSkip(int64(args.PerPage * (args.Page - 1))). diff --git a/pkg/microservice/aslan/core/stat/service/overview.go b/pkg/microservice/aslan/core/stat/service/overview.go index 1d08e0fd..cee8f273 100644 --- a/pkg/microservice/aslan/core/stat/service/overview.go +++ b/pkg/microservice/aslan/core/stat/service/overview.go @@ -62,7 +62,7 @@ func GetOverviewStat(log *zap.SugaredLogger) (*Overview, error) { } overview.EnvCount = int(envCount) - _, artifactCount, err := commonrepo.NewDeliveryArtifactColl().List(&commonrepo.DeliveryArtifactArgs{}) + _, artifactCount, err := commonrepo.NewDeliveryArtifactColl().List(&commonrepo.DeliveryArtifactArgs{OnlyCount: true}) if err != nil { log.Errorf("Failed to get artifact count err:%s", err) return nil, err -- Gitee From c9c0ba7c08a027beda2b26a03e5911f7cd1e31cc Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 31 Mar 2022 14:27:33 +0800 Subject: [PATCH 2020/2647] fix job lable problem Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 2 ++ pkg/microservice/warpdrive/core/service/taskplugin/job.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index f8f1d516..2613bf2e 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -269,6 +269,8 @@ func getPipelineSelector(options *GetContainerOptions) labels.Selector { serviceKey := options.ServiceName if len(serviceKey) > 63 { serviceKey = serviceKey[:63] + serviceKey = strings.TrimRight(serviceKey, "_") + serviceKey = strings.TrimRight(serviceKey, "-") } ret[setting.ServiceLabel] = strings.ToLower(serviceKey) } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 929b0535..f50bb2ea 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -381,6 +381,8 @@ func getJobLabels(jobLabel *JobLabel) map[string]string { serviceKey := jobLabel.ServiceName if len(serviceKey) > 63 { serviceKey = serviceKey[:63] + serviceKey = strings.TrimRight(serviceKey, "_") + serviceKey = strings.TrimRight(serviceKey, "-") } retMap := map[string]string{ -- Gitee From 452f52c407022cd8357e42d6d658c93869d08cf7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 31 Mar 2022 15:08:37 +0800 Subject: [PATCH 2021/2647] optimize code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/log/service/sse.go | 9 ++------- .../warpdrive/core/service/taskplugin/job.go | 9 +-------- pkg/util/yaml.go | 11 +++++++++++ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 2613bf2e..a215f026 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -36,6 +36,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/watcher" + "github.com/koderover/zadig/pkg/util" ) const ( @@ -266,13 +267,7 @@ func getPipelineSelector(options *GetContainerOptions) labels.Selector { ret[setting.TypeLabel] = options.SubTask if options.ServiceName != "" { - serviceKey := options.ServiceName - if len(serviceKey) > 63 { - serviceKey = serviceKey[:63] - serviceKey = strings.TrimRight(serviceKey, "_") - serviceKey = strings.TrimRight(serviceKey, "-") - } - ret[setting.ServiceLabel] = strings.ToLower(serviceKey) + ret[setting.ServiceLabel] = strings.ToLower(util.ReturnValidLabelValue(options.ServiceName)) } if options.PipelineType != "" { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index f50bb2ea..642bb94d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -378,16 +378,9 @@ const ( // getJobLabels get labels k-v map from JobLabel struct func getJobLabels(jobLabel *JobLabel) map[string]string { - serviceKey := jobLabel.ServiceName - if len(serviceKey) > 63 { - serviceKey = serviceKey[:63] - serviceKey = strings.TrimRight(serviceKey, "_") - serviceKey = strings.TrimRight(serviceKey, "-") - } - retMap := map[string]string{ jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.PipelineName), jobLabel.TaskID), - jobLabelServiceKey: strings.ToLower(serviceKey), + jobLabelServiceKey: strings.ToLower(util.ReturnValidLabelValue(jobLabel.ServiceName)), jobLabelSTypeKey: strings.Replace(jobLabel.TaskType, "_", "-", -1), jobLabelPTypeKey: jobLabel.PipelineType, } diff --git a/pkg/util/yaml.go b/pkg/util/yaml.go index 99c1f684..330c3dd7 100644 --- a/pkg/util/yaml.go +++ b/pkg/util/yaml.go @@ -42,3 +42,14 @@ func SplitManifests(content string) []string { return res } + +// value may only contain underscores or underscores +func ReturnValidLabelValue(value string) string { + value = strings.Replace(value, "-", "", -1) + value = strings.Replace(value, "_", "", -1) + if len(value) > 63 { + value = value[:63] + } + + return value +} -- Gitee From b35a24d4cd37b2f822a5c01c56aede5a7bc4d6d3 Mon Sep 17 00:00:00 2001 From: Xudong Zhang Date: Thu, 31 Mar 2022 00:08:43 +0800 Subject: [PATCH 2022/2647] gerrit: support search with multiple keywords Signed-off-by: Xudong Zhang --- pkg/tool/gerrit/client.go | 23 ++++++++++++++++++- pkg/tool/gerrit/client_test.go | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 pkg/tool/gerrit/client_test.go diff --git a/pkg/tool/gerrit/client.go b/pkg/tool/gerrit/client.go index ff6c257c..03337e30 100644 --- a/pkg/tool/gerrit/client.go +++ b/pkg/tool/gerrit/client.go @@ -50,6 +50,22 @@ func (c *Client) ListProjects() ([]*gerrit.ProjectInfo, error) { return c.ListProjectsByKey("") } +// convert keyword like `hello world` to `.*hello.*world.*` +func keywordToRegexp(keyword string) string { + splits := strings.Split(keyword, " ") + regSlices := make([]string, 0, len(splits)) + for _, split := range splits { + s := strings.TrimSpace(split) + if s != "" { + regSlices = append(regSlices, regexp.QuoteMeta(s)) + } + } + if len(regSlices) == 0 { + return ".*" + } + return ".*" + strings.Join(regSlices, ".*") + ".*" +} + func (c *Client) ListProjectsByKey(keyword string) ([]*gerrit.ProjectInfo, error) { opts := &gerrit.ProjectOptions{ Tree: false, @@ -59,8 +75,13 @@ func (c *Client) ListProjectsByKey(keyword string) ([]*gerrit.ProjectInfo, error }, } + // query with substring if no space included, otherwise use regexp if keyword != "" { - opts.Substring = keyword + if !strings.Contains(keyword, " ") { + opts.Substring = keyword + } else { + opts.Regex = keywordToRegexp(keyword) + } } resp, _, err := c.cli.Projects.ListProjects(opts) diff --git a/pkg/tool/gerrit/client_test.go b/pkg/tool/gerrit/client_test.go new file mode 100644 index 00000000..7c3c23fc --- /dev/null +++ b/pkg/tool/gerrit/client_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gerrit + +import "testing" + +func TestKeywordToRegexp(t *testing.T) { + type testcase struct { + keyword string + result string + } + testcases := []testcase{ + {"", ".*"}, + {" ", ".*"}, + {" ", ".*"}, + {"ab", ".*ab.*"}, + {" a b ", ".*a.*b.*"}, + {" a b c d ", ".*a.*b.*c.*d.*"}, + {" a/b c-d", ".*a/b.*c-d.*"}, + } + + for _, ts := range testcases { + regstr := keywordToRegexp(ts.keyword) + if regstr != ts.result { + t.Errorf("Expected result for keyword <%s> to be <%s> but got <%s>", ts.keyword, ts.result, regstr) + } + } +} -- Gitee From 535485ac1c5c38152a4f666effdfacfa4e12b342 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 31 Mar 2022 15:23:31 +0800 Subject: [PATCH 2023/2647] add new contributor @zxdvd Signed-off-by: zhangyifei --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 175715c6..cdfacc9a 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -70,3 +70,4 @@ The council will make a decision for the nomination. The candidate has to have d - [mhatvan](https://github.com/mhatvan) - [tbbrave](https://github.com/tbbrave) - [lxm](https://github.com/lxm) +- [zxdvd](https://github.com/zxdvd) -- Gitee From 756800f11a1ff593ca4953061f0d1779e1289237 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 31 Mar 2022 15:40:51 +0800 Subject: [PATCH 2024/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/common/repository/models/render_set.go | 4 ++-- pkg/microservice/aslan/core/environment/service/renderset.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/render_set.go b/pkg/microservice/aslan/core/common/repository/models/render_set.go index 6cc19df7..15814670 100644 --- a/pkg/microservice/aslan/core/common/repository/models/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/models/render_set.go @@ -33,8 +33,8 @@ type RenderSet struct { Team string `bson:"team,omitempty" json:"team,omitempty"` UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` - IsDefault bool `bson:"is_default" json:"is_default"` // 是否是默认配置 - DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` //环境默认变量 yaml content + IsDefault bool `bson:"is_default" json:"is_default"` + DefaultValues string `bson:"default_values,omitempty" json:"default_values,omitempty"` YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` KVs []*templatemodels.RenderKV `bson:"kvs,omitempty" json:"kvs,omitempty"` ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index e0885c3a..8b665023 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -32,7 +32,7 @@ import ( type DefaultValuesResp struct { DefaultValues string `json:"defaultValues"` - YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` + YamlData *templatemodels.CustomYaml `json:"yaml_data,omitempty"` } type YamlContentRequestArg struct { -- Gitee From b1d32fde9274d780dec4b463d32f9154c611ab00 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 31 Mar 2022 15:52:31 +0800 Subject: [PATCH 2025/2647] Notification does not display mirror information Signed-off-by: ddh27 --- .../aslan/core/common/service/instantmessage/service.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 92514dcb..669f209e 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -328,7 +328,10 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific buildSt.BuildStatus.Status = config.StatusNotRun } buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**服务名称**:%s \n", buildSt.Service) - buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.JobCtx.Image) + if !(buildSt.ServiceType == setting.PMDeployType && + (buildSt.JobCtx.FileArchiveCtx != nil || buildSt.JobCtx.DockerBuildCtx != nil)) { + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.JobCtx.Image) + } buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**代码信息**:[%s-%s %s](%s) \n", branchTagType, branchTag, commitID, gitCommitURL) buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**提交信息**:%s \n", commitMsg) build = append(build, buildElemTemp) -- Gitee From ad76b8d11ba2a1cef6c83651473a5d9395bd17b8 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 31 Mar 2022 19:17:49 +0800 Subject: [PATCH 2026/2647] fix git interface problem Signed-off-by: liu deyi --- pkg/tool/git/github/organizations.go | 4 ++++ pkg/tool/git/github/pull_requests.go | 8 ++++++++ pkg/tool/git/github/repositories.go | 24 ++++++++++++++++++++++++ pkg/tool/git/gitlab/branch.go | 4 ++++ pkg/tool/git/gitlab/group_project.go | 4 ++++ pkg/tool/git/gitlab/merge_request.go | 4 ++++ pkg/tool/git/gitlab/namespace.go | 4 ++++ pkg/tool/git/gitlab/project.go | 8 ++++++++ pkg/tool/git/gitlab/repo.go | 4 ++++ pkg/tool/git/gitlab/tag.go | 4 ++++ 10 files changed, 68 insertions(+) diff --git a/pkg/tool/git/github/organizations.go b/pkg/tool/git/github/organizations.go index 414785d1..2caaaa2a 100644 --- a/pkg/tool/git/github/organizations.go +++ b/pkg/tool/git/github/organizations.go @@ -32,6 +32,10 @@ func (c *Client) ListOrganizationsForAuthenticatedUser(ctx context.Context, opts return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.Organization os, ok := organizations.([]interface{}) if !ok { diff --git a/pkg/tool/git/github/pull_requests.go b/pkg/tool/git/github/pull_requests.go index 5170b737..39c0adea 100644 --- a/pkg/tool/git/github/pull_requests.go +++ b/pkg/tool/git/github/pull_requests.go @@ -50,6 +50,10 @@ func (c *Client) ListCommits(ctx context.Context, owner string, repo string, num return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.RepositoryCommit cs, ok := commits.([]interface{}) if !ok { @@ -72,6 +76,10 @@ func (c *Client) ListFiles(ctx context.Context, owner string, repo string, numbe return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.CommitFile fs, ok := files.([]interface{}) if !ok { diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 5bba3419..803d7281 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -39,6 +39,10 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, opts return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.Repository rs, ok := repositories.([]interface{}) if !ok { @@ -61,6 +65,10 @@ func (c *Client) ListBranches(ctx context.Context, owner, repo string, opts *Lis return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.Branch bs, ok := branches.([]interface{}) if !ok { @@ -83,6 +91,10 @@ func (c *Client) ListTags(ctx context.Context, owner, repo string, opts *ListOpt return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.RepositoryTag ts, ok := tags.([]interface{}) if !ok { @@ -105,6 +117,10 @@ func (c *Client) ListHooks(ctx context.Context, owner, repo string, opts *ListOp return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.Hook hs, ok := hooks.([]interface{}) if !ok { @@ -127,6 +143,10 @@ func (c *Client) ListReleases(ctx context.Context, owner, repo string, opts *Lis return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.RepositoryRelease hs, ok := releases.([]interface{}) if !ok { @@ -149,6 +169,10 @@ func (c *Client) ListRepositoryCommits(ctx context.Context, owner, repo, path, b return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.RepositoryCommit hs, ok := releases.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/branch.go b/pkg/tool/git/gitlab/branch.go index 8ddef945..1c3ca13b 100644 --- a/pkg/tool/git/gitlab/branch.go +++ b/pkg/tool/git/gitlab/branch.go @@ -29,6 +29,10 @@ func (c *Client) ListBranches(owner, repo, key string, opts *ListOptions) ([]*gi return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.Branch bs, ok := branches.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/group_project.go b/pkg/tool/git/gitlab/group_project.go index 17ebe83b..e3ad3590 100644 --- a/pkg/tool/git/gitlab/group_project.go +++ b/pkg/tool/git/gitlab/group_project.go @@ -43,6 +43,10 @@ func (c *Client) ListGroupProjects(namespace, keyword string, opts *ListOptions) return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.Project ps, ok := projects.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/merge_request.go b/pkg/tool/git/gitlab/merge_request.go index ed7624ce..2b3ad7f4 100644 --- a/pkg/tool/git/gitlab/merge_request.go +++ b/pkg/tool/git/gitlab/merge_request.go @@ -38,6 +38,10 @@ func (c *Client) ListOpenedProjectMergeRequests(owner, repo, targetBranch, key s return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.MergeRequest mrs, ok := mergeRequests.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/namespace.go b/pkg/tool/git/gitlab/namespace.go index cb7f5946..9a148efb 100644 --- a/pkg/tool/git/gitlab/namespace.go +++ b/pkg/tool/git/gitlab/namespace.go @@ -37,6 +37,10 @@ func (c *Client) ListNamespaces(keyword string, opts *ListOptions) ([]*gitlab.Na return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.Namespace ns, ok := namespaces.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/project.go b/pkg/tool/git/gitlab/project.go index 36f29e0d..5e81a204 100644 --- a/pkg/tool/git/gitlab/project.go +++ b/pkg/tool/git/gitlab/project.go @@ -45,6 +45,10 @@ func (c *Client) ListUserProjects(owner, keyword string, opts *ListOptions) ([]* return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.Project ps, ok := projects.([]interface{}) if !ok { @@ -116,6 +120,10 @@ func (c *Client) ListProjectHooks(owner, repo string, opts *ListOptions) ([]*git return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.ProjectHook hs, ok := hooks.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/repo.go b/pkg/tool/git/gitlab/repo.go index afb13dd2..ace765fa 100644 --- a/pkg/tool/git/gitlab/repo.go +++ b/pkg/tool/git/gitlab/repo.go @@ -44,6 +44,10 @@ func (c *Client) ListTree(owner, repo, path, branch string, recursive bool, opts return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.TreeNode ns, ok := nodes.([]interface{}) if !ok { diff --git a/pkg/tool/git/gitlab/tag.go b/pkg/tool/git/gitlab/tag.go index a840b84d..070013f6 100644 --- a/pkg/tool/git/gitlab/tag.go +++ b/pkg/tool/git/gitlab/tag.go @@ -29,6 +29,10 @@ func (c *Client) ListTags(owner, repo string, opts *ListOptions, key string) ([] return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*gitlab.Tag ts, ok := tags.([]interface{}) if !ok { -- Gitee From a6848b04d4da1828c70d15a3bf35aa803d16e54c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 31 Mar 2022 19:29:14 +0800 Subject: [PATCH 2027/2647] fix git interface problem Signed-off-by: liu deyi --- pkg/tool/git/github/apps.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/tool/git/github/apps.go b/pkg/tool/git/github/apps.go index 5aaea100..2b5af5e7 100644 --- a/pkg/tool/git/github/apps.go +++ b/pkg/tool/git/github/apps.go @@ -32,6 +32,10 @@ func (c *Client) ListInstallations(ctx context.Context, opts *ListOptions) ([]*g return res, r, err }, opts)) + if err != nil { + return nil, err + } + var res []*github.Installation is, ok := installations.([]interface{}) if !ok { -- Gitee From 097bb0de69564510fa00dac519065aec3e2a4b5e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 31 Mar 2022 22:20:02 +0800 Subject: [PATCH 2028/2647] fix host login bug Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/handler/pm_exec.go | 7 ++++--- pkg/microservice/aslan/core/environment/service/pm_exec.go | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/pm_exec.go b/pkg/microservice/aslan/core/environment/handler/pm_exec.go index 615256df..c10edaa1 100644 --- a/pkg/microservice/aslan/core/environment/handler/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/handler/pm_exec.go @@ -31,9 +31,10 @@ func ConnectSshPmExec(c *gin.Context) { projectName := c.Query("projectName") ip := c.Query("ip") + hostId := c.Query("hostId") name := c.Param("name") - if projectName == "" || ip == "" || name == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("param projectName or ip or name is empty") + if projectName == "" || ip == "" || name == "" || hostId == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("param projectName or ip or name or hostId is empty") } colsStr := c.DefaultQuery("cols", "135") cols, err := strconv.Atoi(colsStr) @@ -46,5 +47,5 @@ func ConnectSshPmExec(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) } - ctx.Err = service.ConnectSshPmExec(c, ctx.UserName, name, projectName, ip, cols, rows, ctx.Logger) + ctx.Err = service.ConnectSshPmExec(c, ctx.UserName, name, projectName, ip, hostId, cols, rows, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/pm_exec.go b/pkg/microservice/aslan/core/environment/service/pm_exec.go index 41a61aef..bff1b908 100644 --- a/pkg/microservice/aslan/core/environment/service/pm_exec.go +++ b/pkg/microservice/aslan/core/environment/service/pm_exec.go @@ -40,12 +40,12 @@ var upgrader = websocket.Upgrader{ }, } -func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip string, cols, rows int, log *zap.SugaredLogger) error { +func ConnectSshPmExec(c *gin.Context, username, envName, productName, ip, hostId string, cols, rows int, log *zap.SugaredLogger) error { resp, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ - Address: ip, + ID: hostId, }) if err != nil { - log.Errorf("PrivateKey.Find ip %s error: %s", ip, err) + log.Errorf("PrivateKey.Find ip %s id %s error: %s", ip, hostId, err) return e.ErrGetPrivateKey } if resp.Status != setting.PMHostStatusNormal { -- Gitee From 12c0eedb4565a8e94d8a8550a4ea1f2e96fc84c1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 31 Mar 2022 23:05:12 +0800 Subject: [PATCH 2029/2647] ensure entension stage Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 31 ++++++++++--------- .../core/service/taskplugin/extension.go | 15 ++++++++- .../core/service/types/task/extension.go | 31 ++++++++++--------- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 3f95e661..10c9c384 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -24,20 +24,23 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` - Payload string `bson:"payload" json:"payload"` - ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + WorkflowStatus config.Status `bson:"workflow_status" json:"workflow_status"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + Payload string `bson:"payload" json:"payload"` + Response string `bson:"response,omitempty" json:"response,omitempty"` + ResponseStatus int `bson:"response_status" json:"response_status"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } type ServiceInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 030e8c66..40ba7e6c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -118,6 +118,7 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, }() p.pipelineName = pipelineTask.PipelineName p.taskId = pipelineTask.TaskID + p.Task.WorkflowStatus = getPipelineStatus(pipelineTask) p.Log.Infof("succeed to create extension task %s", p.JobName) _, p.cancel = context.WithCancel(context.Background()) httpClient := httpclient.New( @@ -136,21 +137,33 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, if err != nil { return } + p.Task.Payload = string(body) headers := make(map[string]string) for _, header := range p.Task.Headers { headers[header.Key] = header.Value } headers[ZadigEvent] = EventName - _, err = httpClient.Post(url, httpclient.SetHeaders(headers), httpclient.SetBody(body)) + response, err := httpClient.Post(url, httpclient.SetHeaders(headers), httpclient.SetBody(body)) if err != nil { return } + p.Task.Response = string(response.Body()) + p.Task.ResponseStatus = response.StatusCode() if !p.Task.IsCallback { p.SetExtensionStatusCompleted(config.StatusPassed) } } +func getPipelineStatus(pipelineTask *task.Task) config.Status { + for _, stage := range pipelineTask.Stages { + if stage.Status == config.StatusFailed || stage.Status == config.StatusCancelled || stage.Status == config.StatusTimeout { + return stage.Status + } + } + return config.StatusPassed +} + // Wait ... func (p *ExtensionTaskPlugin) Wait(ctx context.Context) { timeout := time.After(time.Duration(p.TaskTimeout()) * time.Second) diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index 71b428b6..cb9c5b89 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -24,20 +24,23 @@ import ( ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` - Payload string `bson:"payload" json:"payload"` - ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + WorkflowStatus config.Status `bson:"workflow_status" json:"workflow_status"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + Payload string `bson:"payload" json:"payload"` + Response string `bson:"response,omitempty" json:"response,omitempty"` + ResponseStatus int `bson:"response_status" json:"response_status"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } type ServiceInfo struct { -- Gitee From b4b7e7e65e8182c50c9389788ce13cae673490f6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 1 Apr 2022 12:04:40 +0800 Subject: [PATCH 2030/2647] optimize code Signed-off-by: liu deyi --- .../common/repository/models/task/extension.go | 6 +++--- .../core/service/taskplugin/extension.go | 18 +++++++++--------- .../core/service/types/task/extension.go | 6 +++--- .../core/service/types/task/trigger.go | 17 +++++++++-------- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index 10c9c384..c482b7b0 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -18,6 +18,7 @@ package task import ( "fmt" + "net/http" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -30,11 +31,10 @@ type Extension struct { URL string `bson:"url" json:"url"` Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` - WorkflowStatus config.Status `bson:"workflow_status" json:"workflow_status"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` Payload string `bson:"payload" json:"payload"` - Response string `bson:"response,omitempty" json:"response,omitempty"` - ResponseStatus int `bson:"response_status" json:"response_status"` + ResponseBody string `bson:"response_body,omitempty" json:"response_body,omitempty"` + ResponseHeader http.Header `bson:"response_header,omitempty" json:"response_header,omitempty"` ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 40ba7e6c..3c3a323b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -118,7 +118,6 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, }() p.pipelineName = pipelineTask.PipelineName p.taskId = pipelineTask.TaskID - p.Task.WorkflowStatus = getPipelineStatus(pipelineTask) p.Log.Infof("succeed to create extension task %s", p.JobName) _, p.cancel = context.WithCancel(context.Background()) httpClient := httpclient.New( @@ -126,12 +125,13 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, ) url := p.Task.Path webhookPayload := &task.WebhookPayload{ - EventName: "workflow", - ProjectName: pipelineTask.ProductName, - TaskName: pipelineTask.PipelineName, - TaskID: pipelineTask.TaskID, - ServiceInfos: p.Task.ServiceInfos, - Creator: pipelineTask.TaskCreator, + EventName: "workflow", + ProjectName: pipelineTask.ProductName, + TaskName: pipelineTask.PipelineName, + TaskID: pipelineTask.TaskID, + ServiceInfos: p.Task.ServiceInfos, + Creator: pipelineTask.TaskCreator, + WorkflowStatus: getPipelineStatus(pipelineTask), } body, err = json.Marshal(webhookPayload) if err != nil { @@ -148,8 +148,8 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, if err != nil { return } - p.Task.Response = string(response.Body()) - p.Task.ResponseStatus = response.StatusCode() + p.Task.ResponseBody = string(response.Body()) + p.Task.ResponseHeader = response.Header() if !p.Task.IsCallback { p.SetExtensionStatusCompleted(config.StatusPassed) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index cb9c5b89..de78fcac 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -18,6 +18,7 @@ package task import ( "fmt" + "net/http" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" @@ -30,11 +31,10 @@ type Extension struct { URL string `bson:"url" json:"url"` Path string `bson:"path,omitempty" json:"path,omitempty"` IsCallback bool `bson:"is_callback" json:"is_callback"` - WorkflowStatus config.Status `bson:"workflow_status" json:"workflow_status"` Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` Payload string `bson:"payload" json:"payload"` - Response string `bson:"response,omitempty" json:"response,omitempty"` - ResponseStatus int `bson:"response_status" json:"response_status"` + ResponseBody string `bson:"response_body,omitempty" json:"response_body,omitempty"` + ResponseHeader http.Header `bson:"response_header,omitempty" json:"response_header,omitempty"` ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` Timeout int `bson:"timeout" json:"timeout,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/trigger.go b/pkg/microservice/warpdrive/core/service/types/task/trigger.go index 8a069611..427b7226 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/trigger.go +++ b/pkg/microservice/warpdrive/core/service/types/task/trigger.go @@ -54,14 +54,15 @@ func (t *Trigger) ToSubTask() (map[string]interface{}, error) { } type WebhookPayload struct { - EventName string `json:"event_name"` - ProjectName string `json:"project_name"` - TaskName string `json:"task_name"` - TaskID int64 `json:"task_id"` - TaskOutput []*TaskOutput `json:"task_output,omitempty"` - TaskEnvs []*KeyVal `json:"task_envs,omitempty"` - ServiceInfos []*ServiceInfo `json:"service_infos"` - Creator string `json:"creator"` + EventName string `json:"event_name"` + ProjectName string `json:"project_name"` + TaskName string `json:"task_name"` + TaskID int64 `json:"task_id"` + TaskOutput []*TaskOutput `json:"task_output,omitempty"` + TaskEnvs []*KeyVal `json:"task_envs,omitempty"` + ServiceInfos []*ServiceInfo `json:"service_infos"` + Creator string `json:"creator"` + WorkflowStatus config.Status `json:"workflow_status"` } type TaskOutput struct { -- Gitee From cecb99cb9c2b6261799dccfecbed582689209b5b Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 1 Apr 2022 16:17:58 +0800 Subject: [PATCH 2031/2647] optimize code Signed-off-by: liu deyi --- .../repository/models/task/extension.go | 33 +++++++++---------- .../core/service/taskplugin/extension.go | 6 ++-- .../core/service/types/task/extension.go | 33 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/extension.go b/pkg/microservice/aslan/core/common/repository/models/task/extension.go index c482b7b0..97e3ee9a 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/extension.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/extension.go @@ -18,29 +18,28 @@ package task import ( "fmt" - "net/http" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` - Payload string `bson:"payload" json:"payload"` - ResponseBody string `bson:"response_body,omitempty" json:"response_body,omitempty"` - ResponseHeader http.Header `bson:"response_header,omitempty" json:"response_header,omitempty"` - ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + Payload string `bson:"payload" json:"payload"` + ResponseBody *string `bson:"response_body" json:"response_body"` + ResponseCode *int `bson:"response_code" json:"response_code"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } type ServiceInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go index 3c3a323b..9ebeb665 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/extension.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/extension.go @@ -148,8 +148,10 @@ func (p *ExtensionTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, if err != nil { return } - p.Task.ResponseBody = string(response.Body()) - p.Task.ResponseHeader = response.Header() + responseBody := string(response.Body()) + responseCode := response.StatusCode() + p.Task.ResponseBody = &responseBody + p.Task.ResponseCode = &responseCode if !p.Task.IsCallback { p.SetExtensionStatusCompleted(config.StatusPassed) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/extension.go b/pkg/microservice/warpdrive/core/service/types/task/extension.go index de78fcac..f4eaa179 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/extension.go +++ b/pkg/microservice/warpdrive/core/service/types/task/extension.go @@ -18,29 +18,28 @@ package task import ( "fmt" - "net/http" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" ) type Extension struct { - TaskType config.TaskType `bson:"type" json:"type"` - Enabled bool `bson:"enabled" json:"enabled"` - TaskStatus config.Status `bson:"status" json:"status"` - URL string `bson:"url" json:"url"` - Path string `bson:"path,omitempty" json:"path,omitempty"` - IsCallback bool `bson:"is_callback" json:"is_callback"` - Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` - Payload string `bson:"payload" json:"payload"` - ResponseBody string `bson:"response_body,omitempty" json:"response_body,omitempty"` - ResponseHeader http.Header `bson:"response_header,omitempty" json:"response_header,omitempty"` - ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` - Timeout int `bson:"timeout" json:"timeout,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + TaskType config.TaskType `bson:"type" json:"type"` + Enabled bool `bson:"enabled" json:"enabled"` + TaskStatus config.Status `bson:"status" json:"status"` + URL string `bson:"url" json:"url"` + Path string `bson:"path,omitempty" json:"path,omitempty"` + IsCallback bool `bson:"is_callback" json:"is_callback"` + Headers []*models.KeyVal `bson:"headers,omitempty" json:"headers"` + Payload string `bson:"payload" json:"payload"` + ResponseBody *string `bson:"response_body" json:"response_body"` + ResponseCode *int `bson:"response_code" json:"response_code"` + ServiceInfos []*ServiceInfo `bson:"service_infos" json:"service_infos"` + Timeout int `bson:"timeout" json:"timeout,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` } type ServiceInfo struct { -- Gitee From e8c4582d4197aacc70a323b47ace6483601c237d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 1 Apr 2022 17:47:13 +0800 Subject: [PATCH 2032/2647] replace product variable with productName Signed-off-by: liu deyi --- pkg/microservice/aslan/config/consts.go | 1 + pkg/microservice/aslan/core/service/service/service.go | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index d2b37642..e2ec5ec3 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -26,6 +26,7 @@ var ( // RenderTemplateAlias ... RenderTemplateAlias = regexp.MustCompile(`{{\s?\.\w+\s?}}`) ServiceNameAlias = regexp.MustCompile(`\$Service\$`) + ProductNameAlias = regexp.MustCompile(`\$Product\$`) NameSpaceRegex = regexp.MustCompile(NameSpaceRegexString) ) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 5c28ba85..eabe0004 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1261,6 +1261,8 @@ func setCurrentContainerImages(args *commonmodels.Service) error { yamlData = config.RenderTemplateAlias.ReplaceAllLiteralString(yamlData, "ssssssss") // replace $Service$ with service name yamlData = config.ServiceNameAlias.ReplaceAllLiteralString(yamlData, args.ServiceName) + // replace $Product$ with product name + yamlData = config.ProductNameAlias.ReplaceAllLiteralString(yamlData, args.ProductName) if err := yaml.Unmarshal([]byte(yamlData), &resKind); err != nil { return fmt.Errorf("unmarshal ResourceKind error: %v", err) -- Gitee From 1160db15a6482a7641e1d4e7ab3c633a586f4b12 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 1 Apr 2022 18:24:56 +0800 Subject: [PATCH 2033/2647] fix bug of deleting EnvoyFilter when disable share-env Signed-off-by: zhangyifei --- pkg/microservice/aslan/core/environment/service/share_env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 9723d630..32367ca7 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -163,7 +163,7 @@ func DisableBaseEnv(ctx context.Context, envName, productName string) error { } // 2. Delete EnvoyFilter in the namespace of Istio installation. - err = deleteEnvoyFilter(ctx, istioClient, istioNamespace, zadigEnvoyFilter) + err = ensureDeleteEnvoyFilter(ctx, prod, istioClient) if err != nil { return fmt.Errorf("failed to delete EnvoyFilter: %s", err) } -- Gitee From 7457917de9ad34e977aa599a520398012693b054 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 1 Apr 2022 21:25:19 +0800 Subject: [PATCH 2034/2647] restrict deletion of registry, if used in the environment Signed-off-by: liu deyi --- .../aslan/core/system/service/registry.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 92a3f2db..e1b38557 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -26,6 +26,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util" ) @@ -139,6 +140,16 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { isDefault = false registryNamespaces []*commonmodels.RegistryNamespace ) + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + ExcludeStatus: []string{setting.ProductStatusDeleting}, + }) + + for _, env := range envs { + if env.RegistryID == id { + return fmt.Errorf("The registry cannot be deleted, the environment [%s] in project [%s] is in use", env.EnvName, env.ProductName) + } + } + // whether it is the default registry for _, registry := range registries { if registry.ID.Hex() == id && registry.IsDefault { -- Gitee From 435801837454dcbdc9cec1ad6374e3885ac0da62 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Fri, 1 Apr 2022 21:38:17 +0800 Subject: [PATCH 2035/2647] modify errro message Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index e1b38557..03b4913f 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -146,7 +146,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { for _, env := range envs { if env.RegistryID == id { - return fmt.Errorf("The registry cannot be deleted, the environment [%s] in project [%s] is in use", env.EnvName, env.ProductName) + return fmt.Errorf("The registry cannot be deleted, it's being used by environment [%s] of project [%s]", env.EnvName, env.ProductName) } } -- Gitee From 87365ccf5418509e176aa3398d8c8ceb957ba919 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 1 Apr 2022 22:52:42 +0800 Subject: [PATCH 2036/2647] fix bug of updating VirtualService in base env after deleting service in sub-env Signed-off-by: zhangyifei --- .../core/environment/service/share_env.go | 22 +++++++----- .../environment/service/share_env_utils.go | 34 ++++++++++++++----- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 32367ca7..e7a1d42d 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -463,20 +463,14 @@ func ensureVirtualService(ctx context.Context, kclient client.Client, istioClien return err } + svcSelector := labels.SelectorFromSet(labels.Set(svc.Spec.Selector)) for _, subEnv := range subEnvs { - pods := &corev1.PodList{} - err = kclient.List(ctx, pods, &client.ListOptions{ - Namespace: subEnv.Namespace, - LabelSelector: labels.SelectorFromSet(labels.Set(svc.Spec.Selector)), - }) - if apierrors.IsNotFound(err) { - continue - } + hasWorkload, err := doesSvcHasWorkload(ctx, subEnv.Namespace, svcSelector, kclient) if err != nil { return err } - if len(pods.Items) == 0 { + if !hasWorkload { continue } @@ -959,6 +953,16 @@ func ensureWorkloadsVirtualServiceInGrayAndBase(ctx context.Context, env *common } for _, svc := range svcs.Items { + // If there is no workloads in the sub-environment, the service is not updated. + hasWorkload, err := doesSvcHasWorkload(ctx, grayNS, labels.SelectorFromSet(labels.Set(svc.Spec.Selector)), kclient) + if err != nil { + return err + } + + if !hasWorkload { + continue + } + vsName := genVirtualServiceName(&svc) err = ensureVirtualServiceInGray(ctx, env.EnvName, vsName, svc.Name, grayNS, baseNS, istioClient) diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 63249634..666de84f 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -31,6 +31,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" ) @@ -206,6 +207,8 @@ func ensureCleanRouteInBase(ctx context.Context, envName, baseNS, vsName string, return nil } + log.Infof("Begin to clean route env=%s in base ns %s for VirtualService %s.", envName, baseNS, vsName) + httpRoutes := make([]*networkingv1alpha3.HTTPRoute, 0, len(baseVS.Spec.Http)-1) httpRoutes = append(httpRoutes, baseVS.Spec.Http[:location]...) httpRoutes = append(httpRoutes, baseVS.Spec.Http[location+1:]...) @@ -277,17 +280,13 @@ func ensureDeleteServiceInAllSubEnvs(ctx context.Context, baseEnv *commonmodels. vsName := genVirtualServiceName(svc) workloadSelector := labels.SelectorFromSet(labels.Set(svc.Spec.Selector)) for _, env := range envs { - podList := &corev1.PodList{} - err = kclient.List(ctx, podList, &client.ListOptions{ - Namespace: env.Namespace, - LabelSelector: workloadSelector, - }) - if err == nil && len(podList.Items) > 0 { - continue - } - if err != nil && !apierrors.IsNotFound(err) { + hasWorkload, err := doesSvcHasWorkload(ctx, env.Namespace, workloadSelector, kclient) + if err != nil { return err } + if hasWorkload { + continue + } err = ensureDeleteVirtualService(ctx, env, vsName, istioClient) if err != nil { @@ -321,3 +320,20 @@ func ensureDeleteK8sService(ctx context.Context, ns, svcName string, kclient cli PropagationPolicy: &deleteOption, }) } + +func doesSvcHasWorkload(ctx context.Context, ns string, svcSelector labels.Selector, kclient client.Client) (bool, error) { + podList := &corev1.PodList{} + err := kclient.List(ctx, podList, &client.ListOptions{ + Namespace: ns, + LabelSelector: svcSelector, + }) + if err != nil { + return false, util.IgnoreNotFoundError(err) + } + + if len(podList.Items) == 0 { + return false, nil + } + + return true, nil +} -- Gitee From 3a10fb1da784b0adb9469fa01314256ad859e922 Mon Sep 17 00:00:00 2001 From: mouuii Date: Sat, 2 Apr 2022 10:28:23 +0800 Subject: [PATCH 2037/2647] bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 4 +++- pkg/microservice/aslan/core/workflow/handler/router.go | 2 +- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 5 +---- .../policy/core/service/bundle/resource_endpoint_mapping.go | 3 +++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index a241450f..16fa9eba 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -49,7 +49,9 @@ rules: description: "" rules: - method: PUT - endpoint: "api/aslan/workflow/workflow" + endpoint: "api/aslan/workflow/workflow/?*" + resourceType: "Workflow" + idRegex: "api/aslan/workflow/workflow/([\\w\\W]+?)$" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 96781d42..8c79d02a 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -104,7 +104,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflow.POST("/:productName/auto", AutoCreateWorkflow) workflow.POST("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateWorkflow) - workflow.PUT("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) + workflow.PUT("/:workflow", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index f9268e17..d783685c 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -223,10 +223,6 @@ any_attribute_match(attributes, resourceType, resourceID) { attributes_match(attributes, res) } -attributes_match(attributes, res) { - attribute := attributes[_] - attribute_match(attribute, res) -} get_resource_id(idRegex) = id { output := regex.find_all_string_submatch_n(trim(idRegex, "/"), concat("/", input.parsed_path), -1) @@ -414,6 +410,7 @@ allowed_role_attributive_rules[rule] { rule := allowed_role_rules[_] rule.matchAttributes } + allowed_policy_attributive_rules[rule] { rule := allowed_policy_rules[_] rule.matchExpressions diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index 86dadcb2..bcc17fad 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -120,6 +120,9 @@ func getResourceActionMappings(policies []*models.PolicyMeta) resourceActionMapp for _, ar := range r.Rules { var as []*Attribute for _, a := range ar.MatchAttributes { + if a.Key == "production" { + continue + } as = append(as, &Attribute{Key: a.Key, Value: a.Value}) } -- Gitee From 5b9fef8cd029e1505b3473549b187e3e83154fe0 Mon Sep 17 00:00:00 2001 From: mouuii Date: Sat, 2 Apr 2022 13:53:48 +0800 Subject: [PATCH 2038/2647] bug Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 12 +++++++++--- .../aslan/core/environment/handler/router.go | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 508cc691..0433860a 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -167,11 +167,17 @@ rules: - key: "production" value: "false" - method: POST - endpoint: "/api/aslan/environment/image/deployment" + endpoint: "/api/aslan/environment/image/deployment/?*" + resourceType: "Environment" + idRegex: "/api/aslan/environment/image/deployment/([\\w\\W]+?)" - method: POST - endpoint: "/api/aslan/environment/image/statefulset" + resourceType: "Environment" + idRegex: "/api/aslan/environment/image/statefulset/([\\w\\W]+?)" + endpoint: "/api/aslan/environment/image/statefulset/?*" - method: DELETE - endpoint: "/api/aslan/environment/kube/pods/?*" + endpoint: "/api/aslan/environment/kube/?*/pods/?*" + resourceType: "Environment" + idRegex: "/api/aslan/environment/kube/([\\w\\W]+?)/pods" - method: PUT endpoint: "/api/aslan/environment/configmaps" - method: POST diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 1dca57ca..8b72950b 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -66,8 +66,8 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- image := router.Group("image") { - image.POST("/deployment", gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) - image.POST("/statefulset", gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) + image.POST("/deployment/:env", gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) + image.POST("/statefulset/:env", gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) } // 查询环境创建时的服务和变量信息 @@ -83,7 +83,7 @@ func (*Router) Inject(router *gin.RouterGroup) { kube.GET("/events", ListKubeEvents) kube.POST("/pods", ListServicePods) - kube.DELETE("/pods/:podName", gin2.UpdateOperationLogStatus, DeletePod) + kube.DELETE("/:env/pods/:podName", gin2.UpdateOperationLogStatus, DeletePod) kube.GET("/pods/:podName/events", ListPodEvents) kube.GET("/workloads", ListWorkloads) kube.GET("/nodes", ListNodes) -- Gitee From c624fa5243e7b07dbde38ea735cb151afca2dfd7 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 13:54:48 +0800 Subject: [PATCH 2039/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/workflow/pipeline_validation.go | 10 ++++++---- pkg/tool/gitee/branch.go | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 60522564..61000044 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -381,11 +381,13 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } else if build.Branch != "" && build.PR == 0 { branch, err := gitCli.GetSingleBranch(codeHostInfo.AccessToken, build.RepoOwner, build.RepoName, build.Branch) - if err == nil { - build.CommitID = branch.Commit.Sha - build.CommitMessage = branch.Commit.Commit.Message - build.AuthorName = branch.Commit.Commit.Author.Name + if err != nil { + log.Errorf("failed to gitee GetSingleBranch repoOwner:%s,repoName:%s,repoBranch:%s err:%s", build.RepoOwner, build.RepoName, build.Branch, err) + return } + build.CommitID = branch.Commit.Sha + build.CommitMessage = branch.Commit.Commit.Message + build.AuthorName = branch.Commit.Commit.Author.Name } else if build.PR > 0 { prCommits, err := gitCli.ListCommits(context.Background(), build.RepoOwner, build.RepoName, build.PR, nil) sort.SliceStable(prCommits, func(i, j int) bool { diff --git a/pkg/tool/gitee/branch.go b/pkg/tool/gitee/branch.go index de6b46db..c0246b00 100644 --- a/pkg/tool/gitee/branch.go +++ b/pkg/tool/gitee/branch.go @@ -48,7 +48,7 @@ func (c *Client) GetSingleBranch(accessToken, owner, repo, branch string) (*Bran httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) - url := fmt.Sprintf("/v5/repos/%s/%s/branches/%", owner, repo, branch) + url := fmt.Sprintf("/v5/repos/%s/%s/branches/%s", owner, repo, branch) var branchInfo *Branch _, err := httpClient.Get(url, httpclient.SetQueryParam("access_token", accessToken), httpclient.SetResult(&branchInfo)) if err != nil { -- Gitee From 09174c9d7f9492dac0dc0fde2a9d372a53bd5102 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Sat, 2 Apr 2022 14:09:46 +0800 Subject: [PATCH 2040/2647] optimize information indicating that a namespace may be in the terminating state during environment creation Signed-off-by: zhangyifei --- .../aslan/core/common/service/kube/actions.go | 23 +++++++++++++++++++ .../core/environment/service/environment.go | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/actions.go b/pkg/microservice/aslan/core/common/service/kube/actions.go index 51ff6a74..4df9c4c7 100644 --- a/pkg/microservice/aslan/core/common/service/kube/actions.go +++ b/pkg/microservice/aslan/core/common/service/kube/actions.go @@ -17,7 +17,9 @@ limitations under the License. package kube import ( + "context" "fmt" + "time" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -40,6 +42,27 @@ func CreateNamespace(namespace string, kubeClient client.Client) error { return err } + nsObj := &corev1.Namespace{} + // It may fail to obtain the namespace immediately after it is created due to synchronization delay. + // Try twice. + for i := 0; i < 2; i++ { + err = kubeClient.Get(context.TODO(), client.ObjectKey{ + Name: namespace, + }, nsObj) + if err == nil { + break + } + + time.Sleep(time.Second) + } + if err != nil { + return err + } + + if nsObj.Status.Phase == corev1.NamespaceTerminating { + return fmt.Errorf("namespace `%s` is in terminating state, please wait for a whilie and try again.", namespace) + } + return nil } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 272e4f11..989ec93e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2506,7 +2506,7 @@ func ensureKubeEnv(namespace string, registryId string, kubeClient client.Client err := kube.CreateNamespace(namespace, kubeClient) if err != nil { log.Errorf("[%s] get or create namespace error: %v", namespace, err) - return e.ErrCreateNamspace.AddDesc(e.SetNamespaceErrMsg) + return e.ErrCreateNamspace.AddDesc(err.Error()) } // 创建默认的镜像仓库secret -- Gitee From 070b2db56500244a21f4080fbd49db9b907ec0e3 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 14:20:31 +0800 Subject: [PATCH 2041/2647] restrict deletion of registry optimize Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 03b4913f..ee36d906 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -146,7 +146,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { for _, env := range envs { if env.RegistryID == id { - return fmt.Errorf("The registry cannot be deleted, it's being used by environment [%s] of project [%s]", env.EnvName, env.ProductName) + return fmt.Errorf("The registry cannot be deleted, it's being used by environment") } } -- Gitee From e6b2f69ad25be68d42d87bcc3a16a6db8d529b43 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 14:28:20 +0800 Subject: [PATCH 2042/2647] modify error message Signed-off-by: liu deyi --- pkg/microservice/aslan/core/system/service/registry.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index ee36d906..e2a33d85 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "errors" "fmt" "strings" @@ -146,7 +147,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { for _, env := range envs { if env.RegistryID == id { - return fmt.Errorf("The registry cannot be deleted, it's being used by environment") + return errors.New("The registry cannot be deleted, it's being used by environment") } } -- Gitee From 15b32ce2ad9df055124be88bc92ac0f2583750fb Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 14:43:36 +0800 Subject: [PATCH 2043/2647] debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/client/open/open.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 27f6ec3a..34048448 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -50,6 +50,9 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, return nil, err } + log.Infof("codehostID:%d", codehostID) + log.Infof("ch.AccessToken:%s", ch.AccessToken) + var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] if !ok { -- Gitee From 9ba6d662078e1d280eb24c8fdaa71f38c7149584 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 15:10:42 +0800 Subject: [PATCH 2044/2647] remove debug code Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/client/open/open.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 34048448..27f6ec3a 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -50,9 +50,6 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, return nil, err } - log.Infof("codehostID:%d", codehostID) - log.Infof("ch.AccessToken:%s", ch.AccessToken) - var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] if !ok { -- Gitee From 8294c1cc4c18d4f1c150b5762780d3c59003873f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 15:53:00 +0800 Subject: [PATCH 2045/2647] modify repo query permission Signed-off-by: liu deyi --- pkg/tool/gitee/repositories.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index f9daabcd..09282a56 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -30,7 +30,7 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, acces queryParams := make(map[string]string) queryParams["access_token"] = accessToken queryParams["visibility"] = "all" - queryParams["affiliation"] = "admin" + queryParams["affiliation"] = "owner" queryParams["per_page"] = "100" var projects []Project -- Gitee From 3156453abac5580e09fc323ef024de639c89c133 Mon Sep 17 00:00:00 2001 From: allenshen Date: Sat, 2 Apr 2022 16:37:07 +0800 Subject: [PATCH 2046/2647] add custom tag logic when build helm versions Signed-off-by: allenshen --- .../core/common/repository/models/queue.go | 1 + .../aslan/core/delivery/service/version.go | 51 ++++++---- .../aslan/core/environment/handler/helm.go | 9 ++ .../aslan/core/environment/handler/router.go | 1 + .../aslan/core/environment/service/helm.go | 94 +++++++++++++++++++ .../packager/core/service/packager.go | 1 + .../packager/core/service/service.go | 3 +- .../core/service/types/task/model.go | 1 + pkg/util/url.go | 9 ++ 9 files changed, 153 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index 93d630ef..dd8931e9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -101,6 +101,7 @@ type ImageData struct { ImageUrl string `bson:"image_url" json:"image_url"` ImageName string `bson:"image_name" json:"image_name"` ImageTag string `bson:"image_tag" json:"image_tag"` + CustomTag string `bson:"custom_tag" json:"custom_tag"` RegistryID string `bson:"registry_id" json:"registry_id"` } diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 456c2fb8..0900f6d0 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -76,10 +76,16 @@ type CreateHelmDeliveryVersionOption struct { S3StorageID string `json:"s3StorageID"` } +type ImageData struct { + ImageName string `json:"imageName"` + ImageTag string `json:"imageTag"` +} + type CreateHelmDeliveryVersionChartData struct { - ServiceName string `json:"serviceName"` - Version string `json:"version,omitempty"` - ValuesYamlContent string `json:"valuesYamlContent"` + ServiceName string `json:"serviceName"` + Version string `json:"version,omitempty"` + ValuesYamlContent string `json:"valuesYamlContent"` + ImageData []*ImageData `json:"imageData"` } type CreateHelmDeliveryVersionArgs struct { @@ -171,10 +177,11 @@ type DeliverySecurityStats struct { } type ImageUrlDetail struct { - ImageUrl string - Name string - Registry string - Tag string + ImageUrl string + Name string + Registry string + Tag string + CustomTag string } type ServiceImageDetails struct { @@ -584,13 +591,19 @@ func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) } // handleImageRegistry update image registry to target registry -func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, targetRegistry *commonmodels.RegistryNamespace, registryMap map[string]*commonmodels.RegistryNamespace) ([]byte, *ServiceImageDetails, error) { +func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, targetRegistry *commonmodels.RegistryNamespace, + registryMap map[string]*commonmodels.RegistryNamespace, imageData []*ImageData) ([]byte, *ServiceImageDetails, error) { flatMap, err := converter.YamlToFlatMap(valuesYaml) if err != nil { return nil, nil, err } + imageTagMap := make(map[string]string) + for _, it := range imageData { + imageTagMap[it.ImageName] = it.ImageTag + } + retValuesYaml := string(valuesYaml) serviceObj := chartData.ServiceObj @@ -617,8 +630,6 @@ func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, target return nil, nil, err } - targetImageUrl := util.ReplaceRepo(imageUrl, targetRegistry.RegAddr, targetRegistry.Namespace) - registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) if err != nil { return nil, nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) @@ -627,6 +638,10 @@ func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, target imageName := commonservice.ExtractImageName(imageUrl) imageTag := commonservice.ExtractImageTag(imageUrl) + customTag := imageTag + if ct, ok := imageTagMap[imageName]; ok { + customTag = ct + } registryID := "" // used source registry @@ -636,13 +651,16 @@ func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, target } imageDetail.Images = append(imageDetail.Images, &ImageUrlDetail{ - ImageUrl: imageUrl, - Name: imageName, - Tag: imageTag, - Registry: registryID, + ImageUrl: imageUrl, + Name: imageName, + Tag: imageTag, + Registry: registryID, + CustomTag: customTag, }) // assign image to values.yaml + targetImageUrl := util.ReplaceRepo(imageUrl, targetRegistry.RegAddr, targetRegistry.Namespace) + targetImageUrl = util.ReplaceTag(targetImageUrl, customTag) replaceValuesMap, err := commonservice.AssignImageData(targetImageUrl, spec) if err != nil { return nil, nil, fmt.Errorf("failed to pase image uri %s, err %s", targetImageUrl, err) @@ -689,8 +707,8 @@ func handleSingleChart(chartData *DeliveryChartData, product *commonmodels.Produ } } - // replace image url - valueYamlContent, imageDetail, err := handleImageRegistry(valueYamlContent, chartData, targetRegistry, registryMap) + // replace image url(registryUrl and imageTag) + valueYamlContent, imageDetail, err := handleImageRegistry(valueYamlContent, chartData, targetRegistry, registryMap, chartData.ChartData.ImageData) if err != nil { return nil, errors.Wrapf(err, "failed to handle image registry for service: %s", serviceObj.ServiceName) } @@ -839,6 +857,7 @@ func buildArtifactTaskArgs(projectName, envName string, imagesMap *sync.Map) *co ImageUrl: image.ImageUrl, ImageName: image.Name, ImageTag: image.Tag, + CustomTag: image.CustomTag, RegistryID: image.Registry, }) } diff --git a/pkg/microservice/aslan/core/environment/handler/helm.go b/pkg/microservice/aslan/core/environment/handler/helm.go index 9e097cd9..966e1ddc 100644 --- a/pkg/microservice/aslan/core/environment/handler/helm.go +++ b/pkg/microservice/aslan/core/environment/handler/helm.go @@ -38,3 +38,12 @@ func GetChartInfos(c *gin.Context) { projectName := c.Query("projectName") ctx.Resp, ctx.Err = service.GetChartInfos(projectName, envName, servicesName, ctx.Logger) } + +func GetImageInfos(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Param("name") + projectName := c.Query("projectName") + servicesName := c.Query("serviceName") + ctx.Resp, ctx.Err = service.GetImageInfos(projectName, envName, servicesName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index aa190b09..3a580625 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -112,6 +112,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/helm/releases", ListReleases) environments.GET("/:name/helm/charts", GetChartInfos) + environments.GET("/:name/helm/images", GetImageInfos) environments.GET("/:name/services/:serviceName", GetService) environments.PUT("/:name/services/:serviceName", gin2.UpdateOperationLogStatus, UpdateService) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 743a7594..3ea793d4 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -41,6 +41,7 @@ import ( "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" + "github.com/koderover/zadig/pkg/util/converter" ) type HelmReleaseResp struct { @@ -61,6 +62,20 @@ type HelmChartsResp struct { FileInfos []*types.FileInfo `json:"fileInfos"` } +type ImageData struct { + ImageName string `json:"imageName"` + ImageTag string `json:"imageTag"` +} + +type ServiceImages struct { + ServiceName string `json:"serviceName"` + Images []*ImageData `json:"imageData"` +} + +type ChartImagesResp struct { + ServiceImages []*ServiceImages `json:"serviceImages"` +} + func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmReleaseResp, error) { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) @@ -289,3 +304,82 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog return ret, nil } + +func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLogger) (*ChartImagesResp, error) { + + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + } + + restConfig, err := kube.GetRESTConfig(prod.ClusterID) + if err != nil { + log.Errorf("GetRESTConfig error: %v", err) + return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + } + helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + if err != nil { + log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) + return nil, e.ErrCreateDeliveryVersion.AddErr(err) + } + + // filter releases, only list releases deployed by zadig + serviceMap := prod.GetServiceMap() + serviceSet := sets.NewString() + for serviceName := range serviceMap { + serviceSet.Insert(serviceName) + } + + services := strings.Split(serviceNames, ",") + //svcSet := sets.NewString(serviceName...) + + ret := &ChartImagesResp{} + + for _, svcName := range services { + prodSvc, ok := serviceMap[svcName] + if !ok || prodSvc == nil { + return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to find service: %s in product", svcName)) + } + + releaseName := util.GeneHelmReleaseName(prod.Namespace, svcName) + valuesYaml, err := helmClient.GetReleaseValues(releaseName, true) + if err != nil { + return nil, e.ErrCreateDeliveryVersion.AddErr(err) + } + + flatMap, err := converter.Flatten(valuesYaml) + if err != nil { + return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to get flat map url for release :%s", releaseName)) + } + + svcImage := &ServiceImages{ + ServiceName: svcName, + Images: nil, + } + + for _, container := range prodSvc.Containers { + if container.ImagePath == nil { + return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to parse image for container:%s", container.Image)) + } + imageSearchRule := &template.ImageSearchingRule{ + Repo: container.ImagePath.Repo, + Image: container.ImagePath.Image, + Tag: container.ImagePath.Tag, + } + pattern := imageSearchRule.GetSearchingPattern() + imageUrl, err := commonservice.GeneImageURI(pattern, flatMap) + if err != nil { + return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to get image url for container:%s", container.Image)) + } + + svcImage.Images = append(svcImage.Images, &ImageData{ + container.ImageName, + commonservice.ExtractImageTag(imageUrl), + }) + } + ret.ServiceImages = append(ret.ServiceImages, svcImage) + + } + return ret, nil +} diff --git a/pkg/microservice/packager/core/service/packager.go b/pkg/microservice/packager/core/service/packager.go index 8f9e41da..7ee5ff44 100644 --- a/pkg/microservice/packager/core/service/packager.go +++ b/pkg/microservice/packager/core/service/packager.go @@ -20,6 +20,7 @@ type ImageData struct { ImageUrl string `yaml:"image_url" json:"image_url"` ImageName string `yaml:"image_name" json:"image_name"` ImageTag string `yaml:"image_tag" json:"image_tag"` + CustomTag string `yaml:"custom_tag" json:"custom_tag"` RegistryID string `yaml:"registry_id" json:"registry_id"` } diff --git a/pkg/microservice/packager/core/service/service.go b/pkg/microservice/packager/core/service/service.go index 158f7b9c..fb9c958e 100644 --- a/pkg/microservice/packager/core/service/service.go +++ b/pkg/microservice/packager/core/service/service.go @@ -166,7 +166,7 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri // tag image for _, registry := range targetRegistries { - targetImage := buildTargetImage(singleImage.ImageName, singleImage.ImageTag, registry.Host, registry.Namespace) + targetImage := buildTargetImage(singleImage.ImageName, singleImage.CustomTag, registry.Host, registry.Namespace) err = dockerClient.ImageTag(context.TODO(), singleImage.ImageUrl, targetImage) if err != nil { return nil, errors.Wrapf(err, "failed to tag image from: %s to: %s", singleImage.ImageUrl, targetImage) @@ -176,6 +176,7 @@ func handleSingleService(imageByService *ImagesByService, allRegistries map[stri ImageUrl: targetImage, ImageName: singleImage.ImageName, ImageTag: singleImage.ImageTag, + CustomTag: singleImage.CustomTag, RegistryID: singleImage.RegistryID, }) } diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index c8e7e894..7b210489 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -262,6 +262,7 @@ type ImageData struct { ImageUrl string `bson:"image_url" json:"image_url" yaml:"image_url"` ImageName string `bson:"image_name" json:"image_name" yaml:"image_name"` ImageTag string `bson:"image_tag" json:"image_tag" yaml:"image_tag"` + CustomTag string `bson:"custom_tag" json:"custom_tag" yaml:"custom_tag"` RegistryID string `bson:"registry_id" json:"registry_id" yaml:"registry_id"` } diff --git a/pkg/util/url.go b/pkg/util/url.go index 6a1e1252..fbefbd67 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -55,6 +55,15 @@ func ReplaceRepo(origin, addr, namespace string) string { }, "/") } +func ReplaceTag(origin, newTag string) string { + parts := strings.Split(origin, ":") + if len(parts) < 2 { + return origin + } + parts[len(parts)-1] = newTag + return strings.Join(parts, ":") +} + func GetAddress(uri string) (string, error) { u, err := url.Parse(uri) if err != nil { -- Gitee From ef88ae38686c90b9eda7965f2522f10edd2b2c2d Mon Sep 17 00:00:00 2001 From: allenshen Date: Sat, 2 Apr 2022 16:48:57 +0800 Subject: [PATCH 2047/2647] remove useless code Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/helm.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 3ea793d4..eab1ea0b 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -332,7 +332,6 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo } services := strings.Split(serviceNames, ",") - //svcSet := sets.NewString(serviceName...) ret := &ChartImagesResp{} -- Gitee From 39579376288188c86190bb487468266de6de55be Mon Sep 17 00:00:00 2001 From: liu deyi Date: Sat, 2 Apr 2022 16:57:08 +0800 Subject: [PATCH 2048/2647] repo support search Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/client/gitee/gitee.go | 2 +- .../systemconfig/core/codehost/service/codehost.go | 2 +- pkg/tool/gitee/client.go | 2 +- pkg/tool/gitee/repositories.go | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 4b7f0f1d..718c9c21 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -113,7 +113,7 @@ func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { } func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { - projects, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO(), c.AccessToken) + projects, err := c.Client.ListRepositoriesForAuthenticatedUser(c.AccessToken, opt.Key, opt.Page, opt.PerPage) if err != nil { return nil, err } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 654c5453..775ef4b5 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -174,7 +174,7 @@ func newOAuth(provider, callbackURL, clientID, clientSecret, address string) (*o TokenURL: address + "/oauth/token", }), nil case systemconfig.GiteeProvider: - return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "hook"}, oauth2.Endpoint{ + return oauth.New(callbackURL, clientID, clientSecret, []string{"projects", "pull_requests", "hook", "groups"}, oauth2.Endpoint{ AuthURL: address + "/oauth/authorize", TokenURL: address + "/oauth/token", }), nil diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index 9cbf4d55..65b78d9f 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -48,7 +48,7 @@ func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) *Client ch.UpdatedAt = int64(token.CreatedAt) if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - fmt.Println(fmt.Sprintf("failed to UpdateCodeHost err:%s", err)) + fmt.Println(fmt.Sprintf("failed to updateCodeHost err:%s", err)) } } } diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 09282a56..b5a2566a 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -3,6 +3,7 @@ package gitee import ( "context" "fmt" + "strconv" "time" "gitee.com/openeuler/go-gitee/gitee" @@ -22,7 +23,7 @@ type Project struct { DefaultBranch string `json:"default_branch,omitempty"` } -func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, accessToken string) ([]Project, error) { +func (c *Client) ListRepositoriesForAuthenticatedUser(accessToken, keyword string, page, perPage int) ([]Project, error) { httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) @@ -31,7 +32,9 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, acces queryParams["access_token"] = accessToken queryParams["visibility"] = "all" queryParams["affiliation"] = "owner" - queryParams["per_page"] = "100" + queryParams["q"] = keyword + queryParams["page"] = strconv.Itoa(page) + queryParams["per_page"] = strconv.Itoa(perPage) var projects []Project _, err := httpClient.Get(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&projects)) -- Gitee From 54a0a69b61043ddcd79b959c89c7e1fdfc851f1a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 5 Apr 2022 20:35:55 +0800 Subject: [PATCH 2049/2647] add group or user judgment Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 17 +++++++++++++--- pkg/tool/gitee/repositories.go | 20 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 718c9c21..53d702e3 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -113,10 +113,21 @@ func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { } func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { - projects, err := c.Client.ListRepositoriesForAuthenticatedUser(c.AccessToken, opt.Key, opt.Page, opt.PerPage) - if err != nil { - return nil, err + var projects []gitee.Project + var err error + switch opt.NamespaceType { + case client.GroupKind: + projects, err = c.Client.ListRepositoriesForOrg(c.AccessToken, opt.Namespace, opt.Page, opt.PerPage) + if err != nil { + return nil, err + } + default: + projects, err = c.Client.ListRepositoriesForAuthenticatedUser(c.AccessToken, opt.Key, opt.Page, opt.PerPage) + if err != nil { + return nil, err + } } + var res []*client.Project for _, project := range projects { res = append(res, &client.Project{ diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index b5a2566a..fdbd1f80 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -45,6 +45,26 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(accessToken, keyword strin return projects, nil } +func (c *Client) ListRepositoriesForOrg(accessToken, org string, page, perPage int) ([]Project, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(GiteeHOSTURL), + ) + url := fmt.Sprintf("/api/v5/orgs/%s/repos", org) + queryParams := make(map[string]string) + queryParams["access_token"] = accessToken + queryParams["type"] = "all" + queryParams["page"] = strconv.Itoa(page) + queryParams["per_page"] = strconv.Itoa(perPage) + + var projects []Project + _, err := httpClient.Get(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&projects)) + if err != nil { + return nil, err + } + + return projects, nil +} + func (c *Client) ListHooks(ctx context.Context, owner, repo string, opts *gitee.GetV5ReposOwnerRepoHooksOpts) ([]gitee.Hook, error) { hs, _, err := c.WebhooksApi.GetV5ReposOwnerRepoHooks(ctx, owner, repo, opts) if err != nil { -- Gitee From d17f3e03257b3d356b436891f97af270c7d16aa1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 5 Apr 2022 20:43:45 +0800 Subject: [PATCH 2050/2647] add group or user judgment Signed-off-by: liu deyi --- pkg/microservice/aslan/core/code/client/gitee/gitee.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index 53d702e3..ea78e1ca 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -116,7 +116,7 @@ func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { var projects []gitee.Project var err error switch opt.NamespaceType { - case client.GroupKind: + case client.OrgKind: projects, err = c.Client.ListRepositoriesForOrg(c.AccessToken, opt.Namespace, opt.Page, opt.PerPage) if err != nil { return nil, err -- Gitee From 42e101627350451f351b2d49e3ca3b2944c60197 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 5 Apr 2022 20:52:22 +0800 Subject: [PATCH 2051/2647] add group or user judgment Signed-off-by: liu deyi --- pkg/tool/gitee/repositories.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index fdbd1f80..096400b5 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -49,7 +49,7 @@ func (c *Client) ListRepositoriesForOrg(accessToken, org string, page, perPage i httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) - url := fmt.Sprintf("/api/v5/orgs/%s/repos", org) + url := fmt.Sprintf("/v5/orgs/%s/repos", org) queryParams := make(map[string]string) queryParams["access_token"] = accessToken queryParams["type"] = "all" -- Gitee From d43332db726ed0b2b2aebd74b9224b49b2316f84 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Tue, 5 Apr 2022 21:05:19 +0800 Subject: [PATCH 2052/2647] modify repo permission Signed-off-by: liu deyi --- pkg/tool/gitee/repositories.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 096400b5..9d1b284e 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -30,8 +30,9 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(accessToken, keyword strin url := "/v5/user/repos" queryParams := make(map[string]string) queryParams["access_token"] = accessToken - queryParams["visibility"] = "all" - queryParams["affiliation"] = "owner" + //queryParams["visibility"] = "all" + //queryParams["affiliation"] = "owner" + queryParams["type"] = "owner" queryParams["q"] = keyword queryParams["page"] = strconv.Itoa(page) queryParams["per_page"] = strconv.Itoa(perPage) -- Gitee From a908499ad4274a1fdaa2d59a8f9b4abc29581dcc Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 5 Apr 2022 22:00:48 +0800 Subject: [PATCH 2053/2647] restrict service add/update/delete operations Signed-off-by: zhangyifei --- .../core/environment/handler/environment.go | 19 +++ .../aslan/core/environment/handler/product.go | 21 ++- .../core/environment/service/environment.go | 5 +- .../service/environment_creator.go | 3 +- .../aslan/core/environment/service/product.go | 15 ++- .../core/environment/service/share_env.go | 73 ++++++++++- .../environment/service/share_env_utils.go | 54 ++++---- .../aslan/core/project/handler/product.go | 19 ++- .../aslan/core/project/service/product.go | 13 +- .../aslan/core/service/handler/environment.go | 31 +++++ .../aslan/core/service/handler/router.go | 2 + .../aslan/core/service/service/environment.go | 124 ++++++++++++++++++ .../core/service/bundle/exemption_urls.go | 4 + pkg/tool/errors/http_errors.go | 2 + pkg/types/env.go | 24 ++++ pkg/util/pointer.go | 25 ++++ 16 files changed, 395 insertions(+), 39 deletions(-) create mode 100644 pkg/microservice/aslan/core/service/handler/environment.go create mode 100644 pkg/microservice/aslan/core/service/service/environment.go create mode 100644 pkg/types/env.go create mode 100644 pkg/util/pointer.go diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 626a9e2b..2d564470 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -517,6 +517,25 @@ func DeleteProductServices(c *gin.Context) { } projectName := c.Query("projectName") envName := c.Param("name") + + // For environment sharing, if the environment is the base environment and the service to be deleted has been deployed in the subenvironment, + // we should prompt the user that `Delete the service in the subenvironment before deleting the service in the base environment`. + // servicesInSubEnvs, err := service.CheckServicesDeployedInSubEnvs(c) + svcsInSubEnvs, err := service.CheckServicesDeployedInSubEnvs(c, projectName, envName, args.ServiceNames) + if err != nil { + ctx.Err = err + return + } + if len(svcsInSubEnvs) > 0 { + data := make(map[string]interface{}, len(svcsInSubEnvs)) + for k, v := range svcsInSubEnvs { + data[k] = v + } + + ctx.Err = e.NewWithExtras(e.ErrDeleteSvcHasSvcsInSubEnv, "", data) + return + } + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境的服务", envName, "", ctx.Logger) ctx.Err = service.DeleteProductServices(envName, projectName, args.ServiceNames, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/product.go b/pkg/microservice/aslan/core/environment/handler/product.go index 7ba604f0..3878369a 100644 --- a/pkg/microservice/aslan/core/environment/handler/product.go +++ b/pkg/microservice/aslan/core/environment/handler/product.go @@ -17,10 +17,14 @@ limitations under the License. package handler import ( + "strconv" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) func CleanProductCronJob(c *gin.Context) { @@ -35,5 +39,20 @@ func GetInitProduct(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() productTemplateName := c.Param("name") - ctx.Resp, ctx.Err = service.GetInitProduct(productTemplateName, ctx.Logger) + + envType := types.EnvType(c.Query("envType")) + isBaseEnvStr := c.Query("isBaseEnv") + baseEnvName := c.Query("baseEnv") + + // Note: Compatible with previous behavior. + isBaseEnv, err := strconv.ParseBool(isBaseEnvStr) + if err != nil { + log.Warnf("Failed to parse %s to bool: %s.", isBaseEnvStr, err) + } + + if envType == "" { + envType = types.GeneralEnv + } + + ctx.Resp, ctx.Err = service.GetInitProduct(productTemplateName, envType, isBaseEnv, baseEnvName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 30e61207..91463889 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -67,6 +67,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" @@ -766,7 +767,7 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames log.Infof("[%s][P:%s] UpdateProduct", envName, productName) // 查找产品模板 - updateProd, err := GetInitProduct(productName, log) + updateProd, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) if err != nil { log.Errorf("[%s][P:%s] GetProductTemplate error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) @@ -1170,7 +1171,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str } currentProductService := productResp.Services // 查找产品模板 - updateProd, err := GetInitProduct(productName, log) + updateProd, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) if err != nil { log.Errorf("[%s][P:%s] GetProductTemplate error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 920f4149..a45e3234 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -34,6 +34,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/informer" + "github.com/koderover/zadig/pkg/types" ) type CreateProductParam struct { @@ -76,7 +77,7 @@ func (autoCreator *AutoCreator) Create(envName string) (string, error) { return productResp.Status, nil } - productObject, err := GetInitProduct(productName, log) + productObject, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) if err != nil { log.Errorf("AutoCreateProduct err:%v", err) return "", err diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 60cd185d..56c63187 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "fmt" "time" @@ -34,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" ) @@ -85,7 +87,7 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { } } -func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmodels.Product, error) { +func GetInitProduct(productTmplName string, envType types.EnvType, isBaseEnv bool, baseEnvName string, log *zap.SugaredLogger) (*commonmodels.Product, error) { ret := &commonmodels.Product{} prodTmpl, err := templaterepo.NewProductColl().Find(productTmplName) @@ -118,8 +120,17 @@ func GetInitProduct(productTmplName string, log *zap.SugaredLogger) (*commonmode ret.Source = setting.PMDeployType } + svcGroupNames := prodTmpl.Services + if envType == types.ShareEnv && !isBaseEnv { + // At this point the request is from the environment share. + svcGroupNames, err = GetEnvServiceList(context.TODO(), productTmplName, baseEnvName) + if err != nil { + return nil, fmt.Errorf("failed to get service list from env %s of product %s: %s", baseEnvName, productTmplName, err) + } + } + allServiceInfoMap := prodTmpl.AllServiceInfoMap() - for _, names := range prodTmpl.Services { + for _, names := range svcGroupNames { servicesResp := make([]*commonmodels.ProductService, 0) for _, serviceName := range names { diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index e7a1d42d..a8dd4628 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -42,6 +42,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" zadigtypes "github.com/koderover/zadig/pkg/types" + zadigutil "github.com/koderover/zadig/pkg/util" ) const istioNamespace = "istio-system" @@ -61,8 +62,10 @@ func CheckWorkloadsK8sServices(ctx context.Context, envName, productName string) log.Infof("[CheckWorkloadsK8sServices]Time consumed: %s", time.Since(timeStart)) }() - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - prod, err := commonrepo.NewProductColl().Find(opt) + prod, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) if err != nil { return nil, fmt.Errorf("failed to query env `%s` in project `%s`: %s", envName, productName, err) } @@ -455,9 +458,9 @@ func ensureVirtualService(ctx context.Context, kclient client.Client, istioClien matchedEnvs := []MatchedEnv{} if env.ShareEnv.Enable && env.ShareEnv.IsBase { subEnvs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ - ShareEnvEnable: getBoolPointer(true), - ShareEnvIsBase: getBoolPointer(false), - ShareEnvBaseEnv: getStrPointer(env.EnvName), + ShareEnvEnable: zadigutil.GetBoolPointer(true), + ShareEnvIsBase: zadigutil.GetBoolPointer(false), + ShareEnvBaseEnv: zadigutil.GetStrPointer(env.EnvName), }) if err != nil { return err @@ -1225,3 +1228,63 @@ func EnsureDeleteZadigService(ctx context.Context, env *commonmodels.Product, sv // Update VirtualService Routes in the base environment. return ensureCleanRouteInBase(ctx, env.EnvName, baseEnv.Namespace, vsName, istioClient) } + +func GetEnvServiceList(ctx context.Context, productName, baseEnvName string) ([][]string, error) { + env, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: baseEnvName, + }) + if err != nil { + return nil, fmt.Errorf("failed to query env `%s` in project `%s`: %s", baseEnvName, productName, err) + } + + svcGroupNames := make([][]string, len(env.Services)) + for i, svcArr := range env.Services { + svcGroupNames[i] = []string{} + for _, svc := range svcArr { + svcGroupNames[i] = append(svcGroupNames[i], svc.ServiceName) + } + } + + return svcGroupNames, nil +} + +// Map of map[ServiceName][]string{EnvName} is returned. +func CheckServicesDeployedInSubEnvs(ctx context.Context, productName, envName string, services []string) (map[string][]string, error) { + env, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return nil, fmt.Errorf("failed to query env %s of product %s: %s", envName, productName, err) + } + if !env.ShareEnv.Enable || !env.ShareEnv.IsBase { + return nil, nil + } + + envs, err := fetchSubEnvs(ctx, productName, env.ClusterID, envName) + if err != nil { + return nil, err + } + + envsSvcs := make(map[string][]string, len(envs)) + for _, env := range envs { + envsSvcs[env.EnvName] = getSvcInEnv(env) + } + + svcsInSubEnvs := map[string][]string{} + for _, svcName := range services { + for envName, svcNames := range envsSvcs { + if !zadigutil.InStringArray(svcName, svcNames) { + continue + } + + if _, found := svcsInSubEnvs[svcName]; !found { + svcsInSubEnvs[svcName] = []string{} + } + svcsInSubEnvs[svcName] = append(svcsInSubEnvs[svcName], envName) + } + } + + return svcsInSubEnvs, nil +} diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 666de84f..73c00cd0 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -33,6 +33,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" + zadigutil "github.com/koderover/zadig/pkg/util" ) func ensureBaseEnvConfig(ctx context.Context, baseEnv *commonmodels.Product) error { @@ -63,9 +64,9 @@ func ensureDisableBaseEnvConfig(ctx context.Context, baseEnv *commonmodels.Produ func ensureDeleteAssociatedEnvs(ctx context.Context, baseEnvName string) error { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ - ShareEnvEnable: getBoolPointer(true), - ShareEnvIsBase: getBoolPointer(false), - ShareEnvBaseEnv: getStrPointer(baseEnvName), + ShareEnvEnable: zadigutil.GetBoolPointer(true), + ShareEnvIsBase: zadigutil.GetBoolPointer(false), + ShareEnvBaseEnv: zadigutil.GetStrPointer(baseEnvName), }) if err != nil { return err @@ -88,7 +89,7 @@ func ensureDeleteAssociatedEnvs(ctx context.Context, baseEnvName string) error { func ensureDeleteEnvoyFilter(ctx context.Context, baseEnv *commonmodels.Product, istioClient versionedclient.Interface) error { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ - ShareEnvEnable: getBoolPointer(true), + ShareEnvEnable: zadigutil.GetBoolPointer(true), }) if err != nil { return fmt.Errorf("failed to list products which enable env sharing: %s", err) @@ -135,14 +136,6 @@ func ensureCleanRoutesInBase(ctx context.Context, grayEnv *commonmodels.Product, return nil } -func getBoolPointer(data bool) *bool { - return &data -} - -func getStrPointer(data string) *string { - return &data -} - func deleteEnvoyFilter(ctx context.Context, istioClient versionedclient.Interface, istioNamespace, name string) error { _, err := istioClient.NetworkingV1alpha3().EnvoyFilters(istioNamespace).Get(ctx, name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { @@ -219,13 +212,7 @@ func ensureCleanRouteInBase(ctx context.Context, envName, baseNS, vsName string, } func ensureServicesInAllSubEnvs(ctx context.Context, env *commonmodels.Product, svc *corev1.Service, kclient client.Client, istioClient versionedclient.Interface) error { - envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ - Name: env.ProductName, - ClusterID: env.ClusterID, - ShareEnvEnable: getBoolPointer(true), - ShareEnvIsBase: getBoolPointer(false), - ShareEnvBaseEnv: getStrPointer(env.EnvName), - }) + envs, err := fetchSubEnvs(ctx, env.ProductName, env.ClusterID, env.EnvName) if err != nil { return err } @@ -265,13 +252,7 @@ func ensureDeleteVirtualService(ctx context.Context, env *commonmodels.Product, } func ensureDeleteServiceInAllSubEnvs(ctx context.Context, baseEnv *commonmodels.Product, svc *corev1.Service, kclient client.Client, istioClient versionedclient.Interface) error { - envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ - Name: baseEnv.ProductName, - ClusterID: baseEnv.ClusterID, - ShareEnvEnable: getBoolPointer(true), - ShareEnvIsBase: getBoolPointer(false), - ShareEnvBaseEnv: getStrPointer(baseEnv.EnvName), - }) + envs, err := fetchSubEnvs(ctx, baseEnv.ProductName, baseEnv.ClusterID, baseEnv.EnvName) if err != nil { return err } @@ -337,3 +318,24 @@ func doesSvcHasWorkload(ctx context.Context, ns string, svcSelector labels.Selec return true, nil } + +func fetchSubEnvs(ctx context.Context, productName, clusterID, baseEnvName string) ([]*commonmodels.Product, error) { + return commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + ClusterID: clusterID, + ShareEnvEnable: zadigutil.GetBoolPointer(true), + ShareEnvIsBase: zadigutil.GetBoolPointer(false), + ShareEnvBaseEnv: zadigutil.GetStrPointer(baseEnvName), + }) +} + +func getSvcInEnv(env *commonmodels.Product) []string { + svcs := []string{} + for _, svcGroup := range env.Services { + for _, svc := range svcGroup { + svcs = append(svcs, svc.ServiceName) + } + } + + return svcs +} diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 0d96803f..10ffc225 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "io/ioutil" + "strconv" "github.com/gin-gonic/gin" @@ -29,6 +30,7 @@ import ( internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) func GetProductTemplate(c *gin.Context) { @@ -44,7 +46,22 @@ func GetProductTemplateServices(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() productTemplatName := c.Param("name") - ctx.Resp, ctx.Err = projectservice.GetProductTemplateServices(productTemplatName, ctx.Logger) + + envType := types.EnvType(c.Query("envType")) + isBaseEnvStr := c.Query("isBaseEnv") + baseEnvName := c.Query("baseEnv") + + // Note: Compatible with previous behavior. + isBaseEnv, err := strconv.ParseBool(isBaseEnvStr) + if err != nil { + log.Warnf("Failed to parse %s to bool: %s.", isBaseEnvStr, err) + } + + if envType == "" { + envType = types.GeneralEnv + } + + ctx.Resp, ctx.Err = projectservice.GetProductTemplateServices(productTemplatName, envType, isBaseEnv, baseEnvName, ctx.Logger) } func CreateProductTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index ea2f180a..3b1dec19 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "encoding/json" "fmt" "regexp" @@ -48,6 +49,7 @@ import ( configclient "github.com/koderover/zadig/pkg/shared/config" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) type CustomParseDataArgs struct { @@ -62,7 +64,7 @@ type ImageParseData struct { PresetId int `json:"presetId,omitempty"` } -func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*template.Product, error) { +func GetProductTemplateServices(productName string, envType types.EnvType, isBaseEnv bool, baseEnvName string, log *zap.SugaredLogger) (*template.Product, error) { resp, err := templaterepo.NewProductColl().Find(productName) if err != nil { log.Errorf("GetProductTemplate error: %v", err) @@ -77,6 +79,15 @@ func GetProductTemplateServices(productName string, log *zap.SugaredLogger) (*te if resp.Services == nil { resp.Services = make([][]string, 0) } + + if envType == types.ShareEnv && !isBaseEnv { + // At this point the request is from the environment share. + resp.Services, err = environmentservice.GetEnvServiceList(context.TODO(), productName, baseEnvName) + if err != nil { + return nil, fmt.Errorf("failed to get service list from env %s of product %s: %s", baseEnvName, productName, err) + } + } + return resp, nil } diff --git a/pkg/microservice/aslan/core/service/handler/environment.go b/pkg/microservice/aslan/core/service/handler/environment.go new file mode 100644 index 00000000..c44eead1 --- /dev/null +++ b/pkg/microservice/aslan/core/service/handler/environment.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetDeployableEnvs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetDeployableEnvs(c.Param("name"), c.Query("projectName")) +} diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 10009f08..47776600 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -54,6 +54,8 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.PUT("/yaml/validator", YamlValidator) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) + + k8s.GET("/:name/environments/deployable", GetDeployableEnvs) } workload := router.Group("workloads") diff --git a/pkg/microservice/aslan/core/service/service/environment.go b/pkg/microservice/aslan/core/service/service/environment.go new file mode 100644 index 00000000..cd675f69 --- /dev/null +++ b/pkg/microservice/aslan/core/service/service/environment.go @@ -0,0 +1,124 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/util" +) + +// The service can be deployed only in the following situations: +// 1. All general environments are deployable. +// 2. All base environments are deployable. +// 3. If the service has been deployed in the baseline environment, all sub-environments of the baseline environment +// can deploy the service. +// Otherwise, all sub-environments of the baseline environment cannot deploy the service. +func GetDeployableEnvs(svcName, projectName string) ([]string, error) { + // 1. Get all general environments. + envs0, err := getAllGeneralEnvs(projectName) + if err != nil { + return nil, err + } + + // 2. Get all deployable environments in the context of environment sharing.. + envs1, err := getDeployableShareEnvs(svcName, projectName) + if err != nil { + return nil, err + } + + envs0 = append(envs0, envs1...) + + return envs0, nil +} + +func getAllGeneralEnvs(projectName string) ([]string, error) { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: projectName, + ShareEnvEnable: util.GetBoolPointer(false), + }) + if err != nil { + return nil, err + } + + envNames := make([]string, len(envs)) + for i, env := range envs { + envNames[i] = env.EnvName + } + + return envNames, nil +} + +func getDeployableShareEnvs(svcName, projectName string) ([]string, error) { + baseEnvs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: projectName, + ShareEnvEnable: util.GetBoolPointer(true), + ShareEnvIsBase: util.GetBoolPointer(true), + }) + if err != nil { + return nil, err + } + + ret := []string{} + for _, baseEnv := range baseEnvs { + ret = append(ret, baseEnv.EnvName) + + if !hasSvcInEnv(svcName, baseEnv) { + continue + } + + subEnvs, err := getSubEnvs(baseEnv.EnvName, projectName) + if err != nil { + return nil, err + } + + ret = append(ret, subEnvs...) + } + + return ret, nil +} + +func getSubEnvs(baseEnvName, projectName string) ([]string, error) { + envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: projectName, + ShareEnvEnable: util.GetBoolPointer(true), + ShareEnvIsBase: util.GetBoolPointer(false), + ShareEnvBaseEnv: util.GetStrPointer(baseEnvName), + }) + if err != nil { + return nil, err + } + + envNames := make([]string, len(envs)) + for i, env := range envs { + envNames[i] = env.EnvName + } + + return envNames, nil +} + +func hasSvcInEnv(svcName string, env *commonmodels.Product) bool { + for _, svcGroup := range env.Services { + for _, svc := range svcGroup { + if svc.ServiceName == svcName { + return true + } + } + } + + return false +} diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 24e019b5..04253c2b 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -109,6 +109,10 @@ var publicURLs = []*policyRule{ Methods: []string{"POST", "DELETE"}, Endpoints: []string{"api/aslan/environment/environments/?*/share/enable"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/service/services/?*/environments/deployable"}, + }, } // actions which are allowed for system admins only. diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index ae1d3737..f39a0d2a 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -196,6 +196,8 @@ var ( // ErrLoginPm ... ErrLoginPm = NewHTTPError(6099, "登陆主机失败") + ErrDeleteSvcHasSvcsInSubEnv = NewHTTPError(6094, "删除服务失败,待删除服务存在于子环境中") + //----------------------------------------------------------------------------------------------- // it report APIs Range: 6100 - 6199 //----------------------------------------------------------------------------------------------- diff --git a/pkg/types/env.go b/pkg/types/env.go new file mode 100644 index 00000000..c741e87b --- /dev/null +++ b/pkg/types/env.go @@ -0,0 +1,24 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type EnvType string + +const ( + GeneralEnv EnvType = "general" + ShareEnv EnvType = "share" +) diff --git a/pkg/util/pointer.go b/pkg/util/pointer.go new file mode 100644 index 00000000..1e9ad350 --- /dev/null +++ b/pkg/util/pointer.go @@ -0,0 +1,25 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +func GetBoolPointer(data bool) *bool { + return &data +} + +func GetStrPointer(data string) *string { + return &data +} -- Gitee From cef990b5b4ed357fdb2cb4940481215a3220a99a Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 5 Apr 2022 23:04:01 +0800 Subject: [PATCH 2054/2647] add cache handler in hub-agent Signed-off-by: zhangyifei --- Makefile | 2 +- cmd/hub-agent/main.go | 11 ++- .../aslan/core/common/service/kube/service.go | 38 ++++++++++ .../hubagent/core/service/client.go | 8 -- .../hubagent/server/rest/router.go | 42 ++++++++++ .../hubagent/server/rest/server.go | 76 +++++++++++++++++++ pkg/microservice/hubagent/server/server.go | 43 ++++++++++- 7 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 pkg/microservice/hubagent/server/rest/router.go create mode 100644 pkg/microservice/hubagent/server/rest/server.go diff --git a/Makefile b/Makefile index 952a04f0..272928b0 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go - @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . + @docker buildx build --platform=linux/amd64 -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.arm64: %.image.amd64 diff --git a/cmd/hub-agent/main.go b/cmd/hub-agent/main.go index 8e7a30db..c42fe3a8 100644 --- a/cmd/hub-agent/main.go +++ b/cmd/hub-agent/main.go @@ -17,13 +17,22 @@ limitations under the License. package main import ( + "context" "log" + "os/signal" + "syscall" "github.com/koderover/zadig/pkg/microservice/hubagent/server" ) func main() { - if err := server.Serve(); err != nil { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + go func() { + <-ctx.Done() + stop() + }() + + if err := server.Serve(ctx); err != nil { log.Fatal(err) } } diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 381235d1..354dcd51 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -337,6 +337,25 @@ rules: - '*' --- + +apiVersion: v1 +kind: Service +metadata: + name: hub-agent + namespace: koderover-agent + labels: + app: koderover-agent-agent +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: koderover-agent-agent + +--- + apiVersion: apps/v1 {{- if .UseDeployment }} kind: Deployment @@ -567,6 +586,25 @@ rules: - '*' --- + +apiVersion: v1 +kind: Service +metadata: + name: hub-agent + namespace: {{.Namespace}} + labels: + app: koderover-agent-agent +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: koderover-agent-agent + +--- + apiVersion: apps/v1 {{- if .UseDeployment }} kind: Deployment diff --git a/pkg/microservice/hubagent/core/service/client.go b/pkg/microservice/hubagent/core/service/client.go index 5aa65b4c..5a7edcd6 100644 --- a/pkg/microservice/hubagent/core/service/client.go +++ b/pkg/microservice/hubagent/core/service/client.go @@ -31,7 +31,6 @@ import ( "github.com/pkg/errors" "go.uber.org/zap" - commonconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/hubagent/config" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" @@ -98,13 +97,6 @@ func (c *Client) getParams() (*input, error) { } func Init() error { - log.Init(&log.Config{ - Level: commonconfig.LogLevel(), - Filename: commonconfig.LogFile(), - SendToFile: commonconfig.SendLogToFile(), - Development: commonconfig.Mode() != setting.ReleaseMode, - }) - token := config.HubAgentToken() if token == "" { return fmt.Errorf("token must be configured") diff --git a/pkg/microservice/hubagent/server/rest/router.go b/pkg/microservice/hubagent/server/rest/router.go new file mode 100644 index 00000000..3d1c68dd --- /dev/null +++ b/pkg/microservice/hubagent/server/rest/router.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "github.com/gin-gonic/gin" + + cachehandler "github.com/koderover/zadig/pkg/handler/cache" + "github.com/koderover/zadig/pkg/microservice/user/core/handler" +) + +func (s *engine) injectRouterGroup(router *gin.RouterGroup) { + for _, r := range []injector{ + new(handler.Router), + } { + r.Inject(router.Group("/api/v1")) + } + + for name, r := range map[string]injector{ + "/api/cache": cachehandler.NewRouter(), + } { + r.Inject(router.Group(name)) + } +} + +type injector interface { + Inject(router *gin.RouterGroup) +} diff --git a/pkg/microservice/hubagent/server/rest/server.go b/pkg/microservice/hubagent/server/rest/server.go new file mode 100644 index 00000000..24584cbf --- /dev/null +++ b/pkg/microservice/hubagent/server/rest/server.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rest + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/config" + ginmiddleware "github.com/koderover/zadig/pkg/middleware/gin" + "github.com/koderover/zadig/pkg/tool/log" +) + +type engine struct { + *gin.Engine + + mode string +} + +func NewEngine() *engine { + s := &engine{mode: config.Mode()} + + gin.SetMode(s.mode) + + s.injectMiddlewares() + s.injectRouters() + + return s +} + +func (s *engine) injectMiddlewares() { + g := gin.New() + defer func() { + s.Engine = g + }() + + if s.mode == gin.TestMode { + return + } + g.Use(ginmiddleware.Response()) + g.Use(ginmiddleware.RequestID()) + g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) + g.Use(gin.Recovery()) +} + +func (s *engine) injectRouters() { + g := s.Engine + + g.NoRoute(func(c *gin.Context) { + c.String(http.StatusNotFound, "Invalid path: %s", c.Request.URL.Path) + }) + g.HandleMethodNotAllowed = true + g.NoMethod(func(c *gin.Context) { + c.String(http.StatusMethodNotAllowed, "Method not allowed: %s %s", c.Request.Method, c.Request.URL.Path) + }) + + apiRouters := g.Group("") + s.injectRouterGroup(apiRouters) + + s.Engine = g +} diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index e76babad..f59b44d9 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -17,10 +17,51 @@ limitations under the License. package server import ( + "context" + "net/http" + "time" + + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/hubagent/core/service" + "github.com/koderover/zadig/pkg/microservice/hubagent/server/rest" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" ) -func Serve() error { +func init() { + log.Init(&log.Config{ + Level: config.LogLevel(), + Filename: config.LogFile(), + SendToFile: config.SendLogToFile(), + Development: config.Mode() != setting.ReleaseMode, + }) +} + +func Serve(ctx context.Context) error { + log.Info("Start Hub-Agent service.") + + engine := rest.NewEngine() + server := &http.Server{Addr: ":80", Handler: engine} + + go func() { + <-ctx.Done() + + ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + log.Errorf("Failed to stop server, error: %s", err) + } + }() + + go func() { + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + log.Errorf("Failed to start http server, error: %s", err) + return + } + }() + if err := service.Init(); err != nil { return err } -- Gitee From 7dc82d19620e6798fd11f0563ddc67a4808b38ab Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 5 Apr 2022 23:05:31 +0800 Subject: [PATCH 2055/2647] remove buildx in Makefile Signed-off-by: zhangyifei --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 272928b0..952a04f0 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.up @sed -i -e '/#alpine.Dockerfile/ {' -e 'r docker/base/amd64/alpine.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/$*.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/$* cmd/$*/main.go - @docker buildx build --platform=linux/amd64 -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . + @docker build -f docker/dist/amd64/$*.Dockerfile --tag ${MAKE_IMAGE} . %.push.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.arm64: %.image.amd64 -- Gitee From bc0f89892bd2741aeb449490ba3735e0a3f38766 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 6 Apr 2022 10:17:53 +0800 Subject: [PATCH 2056/2647] fix import order Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index c35805f1..f3701248 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -46,11 +46,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" -- Gitee From 17c330a4f38eac6c9256803e5161c5d95d076f23 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 6 Apr 2022 10:28:14 +0800 Subject: [PATCH 2057/2647] Modify the way to pull code Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/meta/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index b4251acd..6d90698a 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -400,7 +400,7 @@ func (g *Git) HTTPSCloneURL(source, token, owner, name string) string { if strings.ToLower(source) == ProviderGitlab { return fmt.Sprintf("https://%s/%s/%s.git", g.GetGitlabHost(), owner, name) } else if strings.ToLower(source) == ProviderGitee { - return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", token, "gitee.com", owner, name) + return fmt.Sprintf("https://%s:%s@%s/%s/%s.git", OauthTokenPrefix, token, "gitee.com", owner, name) } //return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", g.GetInstallationToken(owner), g.GetGithubHost(), owner, name) return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", token, g.GetGithubHost(), owner, name) -- Gitee From 5c1abd0ea01e4a84d5167c05b75895d68e3889e5 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 6 Apr 2022 11:47:19 +0800 Subject: [PATCH 2058/2647] export all files in the test result export path Signed-off-by: ddh27 --- .../core/workflow/handler/pipeline_task.go | 6 ++- .../service/workflow/pipeline_task.go | 33 +++++++++--- .../workflow/testing/service/workspace.go | 35 ++++++++++--- .../reaper/core/service/reaper/artifact.go | 50 +++++++++---------- pkg/setting/consts.go | 2 + pkg/tool/s3/client.go | 29 +++++++++++ 6 files changed, 112 insertions(+), 43 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go index dd6f4f68..0efde264 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go @@ -191,8 +191,12 @@ func GetArtifactFile(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } + notHistoryFileFlag := false + if c.DefaultQuery("notHistoryFileFlag", "false") == "true" { + notHistoryFileFlag = true + } - resp, err := workflow.GetArtifactFileContent(c.Param("pipelineName"), taskID, ctx.Logger) + resp, err := workflow.GetArtifactFileContent(c.Param("pipelineName"), taskID, notHistoryFileFlag, ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 10df9609..57a31cbd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -1069,11 +1069,14 @@ func GePackageFileContent(pipelineName string, taskID int64, log *zap.SugaredLog return fileBytes, packageFile, err } -func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredLogger) ([]byte, error) { - s3Storage, client, artifactFiles, err := GetArtifactAndS3Info(pipelineName, "", taskID, log) +func GetArtifactFileContent(pipelineName string, taskID int64, notHistoryFileFlag bool, log *zap.SugaredLogger) ([]byte, error) { + s3Storage, client, artifactFiles, artifactResultOutByts, err := GetArtifactAndS3Info(pipelineName, "", taskID, notHistoryFileFlag, log) if err != nil { return nil, fmt.Errorf("download artifact err: %s", err) } + if notHistoryFileFlag { + return artifactResultOutByts, nil + } tempDir, _ := ioutil.TempDir("", "") sourcePath := path.Join(tempDir, "artifact") if _, err := os.Stat(sourcePath); os.IsNotExist(err) { @@ -1114,13 +1117,13 @@ func GetArtifactFileContent(pipelineName string, taskID int64, log *zap.SugaredL return fileBytes, err } -func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, error) { +func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, notHistoryFileFlag bool, log *zap.SugaredLogger) (*s3.S3, *s3tool.Client, []string, []byte, error) { fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return nil, nil, fis, err + return nil, nil, fis, nil, err } if storage.Subfolder != "" { @@ -1135,13 +1138,29 @@ func GetArtifactAndS3Info(pipelineName, dir string, taskID int64, log *zap.Sugar client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo Create S3 client err:%+v", err) - return nil, nil, fis, err + return nil, nil, fis, nil, err + } + + if notHistoryFileFlag { + objectKey := storage.GetObjectPath(fmt.Sprintf("%s/%s/%s", dir, "workspace", setting.ArtifactResultOut)) + object, err := client.GetFile(storage.Bucket, objectKey, &s3tool.DownloadOption{RetryNum: 2}) + if err != nil { + log.Errorf("GetTestArtifactInfo GetFile err:%s", err) + return nil, nil, fis, nil, err + } + fileByts, err := ioutil.ReadAll(object.Body) + if err != nil { + log.Errorf("GetTestArtifactInfo ioutil.ReadAll err:%s", err) + return nil, nil, fis, nil, err + } + return storage, client, fis, fileByts, nil } + prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return nil, nil, fis, err + return nil, nil, fis, nil, err } - return storage, client, files, nil + return storage, client, files, nil, nil } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index da56a35e..f10aea59 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -27,13 +27,18 @@ import ( s3tool "github.com/koderover/zadig/pkg/tool/s3" ) -func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) ([]string, error) { - fis := make([]string, 0) +type GetTestArtifactInfoResp struct { + FileNames []string `json:"file_names"` + NotHistoryFileFlag bool `json:"not_history_file_flag"` +} + +func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*GetTestArtifactInfoResp, error) { + resp := new(GetTestArtifactInfoResp) storage, err := s3.FindDefaultS3() if err != nil { log.Errorf("GetTestArtifactInfo FindDefaultS3 err:%v", err) - return fis, err + return resp, err } if storage.Subfolder != "" { storage.Subfolder = fmt.Sprintf("%s/%s/%d/%s", storage.Subfolder, pipelineName, taskID, "artifact") @@ -47,18 +52,32 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare client, err := s3tool.NewClient(storage.Endpoint, storage.Ak, storage.Sk, storage.Insecure, forcedPathStyle) if err != nil { log.Errorf("GetTestArtifactInfo create s3 client err:%v", err) - return fis, err + return resp, err } + + objectKey := storage.GetObjectPath(fmt.Sprintf("%s/%s/%s", dir, "workspace", setting.ArtifactResultOut)) + object, err := client.GetFile(storage.Bucket, objectKey, &s3tool.DownloadOption{IgnoreNotExistError: true, RetryNum: 2}) + if err != nil { + log.Errorf("GetTestArtifactInfo GetFile err:%s", err) + return resp, err + } + if object != nil && *object.ContentLength > 0 { + resp.FileNames = []string{setting.ArtifactResultOut} + resp.NotHistoryFileFlag = true + return resp, nil + } + prefix := storage.GetObjectPath(dir) files, err := client.ListFiles(storage.Bucket, prefix, true) if err != nil || len(files) <= 0 { - log.Errorf("GetTestArtifactInfo ListFiles err:%v", err) - return fis, err + log.Errorf("GetTestArtifactInfo ListFiles err:%s", err) + return resp, err } - resp := make([]string, 0) + fis := make([]string, 0) for _, file := range files { _, fileName := path.Split(file) - resp = append(resp, fileName) + fis = append(fis, fileName) } + resp.FileNames = fis return resp, nil } diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 47e3768b..dd8f469c 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -18,10 +18,8 @@ package reaper import ( "fmt" - "io/ioutil" "os" "os/exec" - "path" "path/filepath" "strings" @@ -52,36 +50,34 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] } if len(pluginType) == 0 { + tarName := setting.ArtifactResultOut + temp, err := os.Create(tarName) + if err != nil { + log.Errorf("failed to create %s err: %s", tarName, err) + return err + } + _ = temp.Close() + + cmdAndArtifactFullPaths := make([]string, 0) + cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, "-czf") + cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, tarName) for _, artifactPath := range artifactPaths { if len(artifactPath) == 0 { continue } - artifactPath = strings.TrimPrefix(artifactPath, "/") - - artifactPath = filepath.Join(activeWorkspace, artifactPath) - - artifactFiles, err := ioutil.ReadDir(artifactPath) - if err != nil || len(artifactFiles) == 0 { - continue - } - - for _, artifactFile := range artifactFiles { - if artifactFile.IsDir() { - continue - } - filePath := path.Join(artifactPath, artifactFile.Name()) - - if _, err := os.Stat(filePath); os.IsNotExist(err) { - continue - } - - if store != nil { - objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s", artifactPath, artifactFile.Name())) - if err = s3FileUpload(store, filePath, objectKey); err != nil { - return err - } - } + cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, "./"+artifactPath) + } + cmd := exec.Command("tar", cmdAndArtifactFullPaths...) + cmd.Stderr = os.Stderr + if err = cmd.Run(); err != nil { + log.Errorf("failed to compress %s err:%s", tarName, err) + return err + } + if store != nil { + objectKey := store.GetObjectPath(tarName) + if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { + return err } } return nil diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a075d31a..1f6346bc 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -635,3 +635,5 @@ const ( // AttachedClusterNamespace is the namespace Zadig uses in attached cluster. // Note: **Restricted because of product design since v1.9.0**. const AttachedClusterNamespace = "koderover-agent" + +const ArtifactResultOut = "artifactResultOut.tar.gz" diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index 7681b133..7064102d 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -207,6 +207,35 @@ func (c *Client) Upload(bucketName, src string, objectKey string) error { return err } +func (c *Client) GetFile(bucketName, objectKey string, option *DownloadOption) (*s3.GetObjectOutput, error) { + retry := 0 + var err error + + for retry < option.RetryNum { + opt := &s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + } + obj, err1 := c.GetObject(opt) + if err1 != nil { + if e, ok := err1.(awserr.Error); ok && e.Code() == s3.ErrCodeNoSuchKey { + if option.IgnoreNotExistError { + return nil, nil + } + return nil, err1 + } + + log.Warnf("GetFile Failed to get object %s from s3, try again, err: %s", objectKey, err1) + err = err1 + retry++ + continue + } + + return obj, nil + } + return nil, err +} + // ListFiles with given prefix func (c *Client) ListFiles(bucketName, prefix string, recursive bool) ([]string, error) { ret := make([]string, 0) -- Gitee From c8c86fa06c55e87c7e20debdd44233b8cbf4a847 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 13:54:00 +0800 Subject: [PATCH 2059/2647] jenkins Signed-off-by: mouuii --- .../core/common/repository/models/build.go | 7 ++- .../common/repository/models/task/jenkins.go | 7 ++- .../service/workflow/workflow_task.go | 43 +++++++++++++++++-- pkg/microservice/cron/core/service/types.go | 6 ++- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index dcfb1698..55381f04 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -125,8 +125,11 @@ type JenkinsBuild struct { } type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` + Name string `json:"name"` + Value interface{} `json:"value"` + Type string `bson:"type,omitempty" json:"type,omitempty"` + AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` } type Parameter struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 35e945ef..d0681f03 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -36,8 +36,11 @@ type JenkinsBuildArgs struct { } type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` + Name string `json:"name"` + Value interface{} `json:"value"` + Type string `bson:"type,omitempty" json:"type,omitempty"` + AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` } // JenkinsBuild ... diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 6ca25993..a5436816 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -26,6 +26,7 @@ import ( "strconv" "strings" "sync" + "time" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" @@ -109,8 +110,11 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr jenkinsBuildParams := make([]*commonmodels.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range moBuild.JenkinsBuild.JenkinsBuildParam { jenkinsBuildParams = append(jenkinsBuildParams, &commonmodels.JenkinsBuildParam{ - Name: jenkinsBuildParam.Name, - Value: jenkinsBuildParam.Value, + Name: jenkinsBuildParam.Name, + Value: jenkinsBuildParam.Value, + Type: jenkinsBuildParam.Type, + ChoiceOption: jenkinsBuildParam.ChoiceOption, + AutoGenerate: jenkinsBuildParam.AutoGenerate, }) } target.JenkinsBuildArgs = &commonmodels.JenkinsBuildArgs{ @@ -399,8 +403,11 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) jenkinsBuildParams := make([]*commonmodels.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range moBuild.JenkinsBuild.JenkinsBuildParam { jenkinsBuildParams = append(jenkinsBuildParams, &commonmodels.JenkinsBuildParam{ - Name: jenkinsBuildParam.Name, - Value: jenkinsBuildParam.Value, + Name: jenkinsBuildParam.Name, + Value: jenkinsBuildParam.Value, + Type: jenkinsBuildParam.Type, + ChoiceOption: jenkinsBuildParam.ChoiceOption, + AutoGenerate: jenkinsBuildParam.AutoGenerate, }) } target.JenkinsBuildArgs = &commonmodels.JenkinsBuildArgs{ @@ -2278,6 +2285,34 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err if jenkinsBuildParams.Name != "IMAGE" { continue } + + if jenkinsBuildParams.AutoGenerate { + log.Infof("********************") + opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} + exitedProd, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + log.Errorf("can't find product by envName:%s error msg: %v", taskOpt.EnvName, err) + return e.ErrFindRegistry.AddDesc(err.Error()) + } + + var reg *commonmodels.RegistryNamespace + if len(exitedProd.RegistryID) > 0 { + reg, _, err = commonservice.FindRegistryById(exitedProd.RegistryID, true, log) + if err != nil { + log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", + exitedProd.RegistryID, err) + return e.ErrFindRegistry.AddDesc(err.Error()) + } + } else { + reg, _, err = commonservice.FindDefaultRegistry(true, log) + if err != nil { + log.Errorf("can't find default candidate registry: %v", err) + return e.ErrFindRegistry.AddDesc(err.Error()) + } + } + image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Unix())) + } + if value, ok := jenkinsBuildParams.Value.(string); ok { image = value break diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 85a3369c..532ef7bc 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -307,8 +307,10 @@ type JenkinsBuildArgs struct { } type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` + Name string `json:"name"` + Value interface{} `json:"value"` + Type string `bson:"type,omitempty" json:"type,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` } type BuildArgs struct { -- Gitee From 8684a1c1af1edf0804161dcb2a7a2312c78d1ba8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 15:17:55 +0800 Subject: [PATCH 2060/2647] jenkins Signed-off-by: mouuii --- .../core/workflow/service/workflow/convert.go | 5 +++-- .../service/workflow/workflow_task.go | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 51e259d2..55f07dc9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -164,8 +164,9 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z jenkinsBuildParams := make([]*task.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range jenkinsBuildOption.JenkinsBuildArgs.JenkinsBuildParams { jenkinsBuildParams = append(jenkinsBuildParams, &task.JenkinsBuildParam{ - Name: jenkinsBuildParam.Name, - Value: jenkinsBuildParam.Value, + Name: jenkinsBuildParam.Name, + Value: jenkinsBuildParam.Value, + AutoGenerate: jenkinsBuildParam.AutoGenerate, }) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 3e78ee35..db108139 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -713,7 +713,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } return nil, e.ErrCreateTask.AddDesc(err.Error()) } - + log.Infof("*********task :%+v", task) for _, stask := range task.SubTasks { AddSubtaskToStage(&stages, stask, target.Name+"_"+target.ServiceName) } @@ -2315,11 +2315,11 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err if t.Enabled { // 分析镜像名称 image := "" - for _, jenkinsBuildParams := range t.JenkinsBuildArgs.JenkinsBuildParams { + for i, jenkinsBuildParams := range t.JenkinsBuildArgs.JenkinsBuildParams { if jenkinsBuildParams.Name != "IMAGE" { continue } - + log.Infof("******************** : %+v", jenkinsBuildParams) if jenkinsBuildParams.AutoGenerate { log.Infof("********************") opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} @@ -2345,14 +2345,17 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } } image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Unix())) - } - - if value, ok := jenkinsBuildParams.Value.(string); ok { - image = value + log.Infof("******************** image:%v", image) + t.JenkinsBuildArgs.JenkinsBuildParams[i].Value = image break + } else { + if value, ok := jenkinsBuildParams.Value.(string); ok { + image = value + break + } } } - + log.Infof("******************** image:%v", image) if image == "" || !strings.Contains(image, ":") { return &ImageIllegal{} } -- Gitee From 987d94bcd7f2585ac295c4d081136b61a97df34d Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 15:32:58 +0800 Subject: [PATCH 2061/2647] remove log Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index db108139..2fc29e1b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -713,7 +713,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } return nil, e.ErrCreateTask.AddDesc(err.Error()) } - log.Infof("*********task :%+v", task) for _, stask := range task.SubTasks { AddSubtaskToStage(&stages, stask, target.Name+"_"+target.ServiceName) } @@ -2319,9 +2318,8 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err if jenkinsBuildParams.Name != "IMAGE" { continue } - log.Infof("******************** : %+v", jenkinsBuildParams) + if jenkinsBuildParams.AutoGenerate { - log.Infof("********************") opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -2345,7 +2343,6 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } } image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Unix())) - log.Infof("******************** image:%v", image) t.JenkinsBuildArgs.JenkinsBuildParams[i].Value = image break } else { @@ -2355,7 +2352,6 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } } } - log.Infof("******************** image:%v", image) if image == "" || !strings.Contains(image, ":") { return &ImageIllegal{} } -- Gitee From 345858ce478f01dc55a0ca68acc29be438de633e Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 6 Apr 2022 16:16:33 +0800 Subject: [PATCH 2062/2647] Modify pull request model Signed-off-by: liu deyi --- .../service/webhook/gitee_workflow_task.go | 2 +- pkg/tool/gitee/event_parsing.go | 23 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go index 803d9628..08acecc1 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go @@ -312,7 +312,7 @@ func findChangedFilesOfPullRequestEvent(event *gitee.PullRequestEvent, codehostI } giteeCli := gitee.NewClient(detail.ID, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) - commitComparison, err := giteeCli.GetReposOwnerRepoCompareBaseHead(detail.AccessToken, event.PullRequest.Base.Repo.Owner.Login, event.PullRequest.Base.Repo.Name, event.PullRequest.Base.Sha, event.PullRequest.Head.Sha) + commitComparison, err := giteeCli.GetReposOwnerRepoCompareBaseHead(detail.AccessToken, event.Project.Namespace, event.Project.Name, event.PullRequest.Base.Sha, event.PullRequest.Head.Sha) if err != nil { return nil, fmt.Errorf("failed to get changes from gitee, err: %v", err) } diff --git a/pkg/tool/gitee/event_parsing.go b/pkg/tool/gitee/event_parsing.go index 82578335..d94bfd38 100644 --- a/pkg/tool/gitee/event_parsing.go +++ b/pkg/tool/gitee/event_parsing.go @@ -362,8 +362,27 @@ type PushEvent struct { } type PullRequestEvent struct { - Action string `json:"action"` - ActionDesc string `json:"action_desc"` + Action string `json:"action"` + ActionDesc string `json:"action_desc"` + Project struct { + ID int `json:"id"` + Language interface{} `json:"language"` + License interface{} `json:"license"` + Name string `json:"name"` + NameWithNamespace string `json:"name_with_namespace"` + Namespace string `json:"namespace"` + OpenIssuesCount int `json:"open_issues_count"` + Path string `json:"path"` + PathWithNamespace string `json:"path_with_namespace"` + Private bool `json:"private"` + PushedAt time.Time `json:"pushed_at"` + SSHURL string `json:"ssh_url"` + StargazersCount int `json:"stargazers_count"` + SvnURL string `json:"svn_url"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` + WatchersCount int `json:"watchers_count"` + } `json:"project"` PullRequest *struct { ID int `json:"id"` Number int `json:"number"` -- Gitee From b5d9f4646ee24d88ef6d3384b51a7448c8931248 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 6 Apr 2022 16:36:58 +0800 Subject: [PATCH 2063/2647] fix export all files in the test result export path Signed-off-by: ddh27 --- .../aslan/core/workflow/handler/pipeline_task.go | 8 +++++++- .../reaper/core/service/reaper/artifact.go | 11 +++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go index 0efde264..537d3cc0 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go @@ -29,6 +29,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/setting" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -201,6 +202,11 @@ func GetArtifactFile(c *gin.Context) { ctx.Err = err return } - c.Writer.Header().Set("Content-Disposition", `attachment; filename="artifact.tar.gz"`) + if notHistoryFileFlag { + c.Writer.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, setting.ArtifactResultOut)) + } else { + c.Writer.Header().Set("Content-Disposition", `attachment; filename="artifact.tar.gz"`) + } + c.Data(200, "application/octet-stream", resp) } diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index dd8f469c..51151c4b 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -66,8 +66,15 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] continue } artifactPath = strings.TrimPrefix(artifactPath, "/") - cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, "./"+artifactPath) + artifactPath := filepath.Join(activeWorkspace, artifactPath) + isDir, err := fs.IsDir(artifactPath) + if err != nil || !isDir { + log.Errorf("artifactPath is not exist %s err: %s", artifactPath, err) + continue + } + cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, artifactPath) } + cmd := exec.Command("tar", cmdAndArtifactFullPaths...) cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { @@ -75,7 +82,7 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] return err } if store != nil { - objectKey := store.GetObjectPath(tarName) + objectKey := store.GetObjectPath(fmt.Sprintf("%s/%s", activeWorkspace, tarName)) if err = s3FileUpload(store, temp.Name(), objectKey); err != nil { return err } -- Gitee From d8be2ced23334867c531e833c7a6c4b2c70b7706 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 6 Apr 2022 16:53:08 +0800 Subject: [PATCH 2064/2647] fix export all files in the test result export path Signed-off-by: ddh27 --- .../aslan/core/workflow/handler/pipeline_task.go | 7 ++++--- pkg/tool/s3/client.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go index 537d3cc0..38e94b09 100644 --- a/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/pipeline_task.go @@ -192,9 +192,10 @@ func GetArtifactFile(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - notHistoryFileFlag := false - if c.DefaultQuery("notHistoryFileFlag", "false") == "true" { - notHistoryFileFlag = true + notHistoryFileFlag, err := strconv.ParseBool(c.DefaultQuery("notHistoryFileFlag", "false")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid notHistoryFileFlag") + return } resp, err := workflow.GetArtifactFileContent(c.Param("pipelineName"), taskID, notHistoryFileFlag, ctx.Logger) diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index 7064102d..6cb342e5 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -225,7 +225,7 @@ func (c *Client) GetFile(bucketName, objectKey string, option *DownloadOption) ( return nil, err1 } - log.Warnf("GetFile Failed to get object %s from s3, try again, err: %s", objectKey, err1) + log.Warnf("Failed to get object %s from s3, try again, err: %s", objectKey, err1) err = err1 retry++ continue -- Gitee From cf8a2744657dfb4a7f99a3fd352d5b75192d8ded Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 16:59:00 +0800 Subject: [PATCH 2065/2647] rules Signed-off-by: mouuii --- .../repository/models/template/product.go | 1 + .../service/workflow/workflow_task.go | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 29d72c43..ffbefa95 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -150,6 +150,7 @@ type CustomRule struct { BranchRule string `bson:"branch_rule,omitempty" json:"branch_rule,omitempty"` PRAndBranchRule string `bson:"pr_and_branch_rule,omitempty" json:"pr_and_branch_rule,omitempty"` TagRule string `bson:"tag_rule,omitempty" json:"tag_rule,omitempty"` + JenkinsRule string `bson:"jenkins_rule,omitempty" json:"jenkins_rule,omitempty"` } type DeliveryVersionHook struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 2fc29e1b..4c4a222c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -17,6 +17,7 @@ limitations under the License. package workflow import ( + "bytes" "errors" "fmt" "net/url" @@ -26,6 +27,7 @@ import ( "strconv" "strings" "sync" + gotempl "text/template" "time" "go.mongodb.org/mongo-driver/mongo" @@ -39,6 +41,7 @@ import ( taskmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" @@ -2342,7 +2345,26 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err return e.ErrFindRegistry.AddDesc(err.Error()) } } - image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Unix())) + project, err := templaterepo.NewProductColl().Find(taskOpt.Task.ProductName) + if err != nil { + log.Errorf("find project err:%s", err) + return err + } + rule := project.CustomImageRule.JenkinsRule + if rule != "" { + va := commonservice.Variable{ + SERVICE: t.ServiceName, + TIMESTAMP: time.Now().Format("20060102150405"), + } + tm, _ := gotempl.New("jenkins").Parse(rule) + var replaceRuleVariable = gotempl.Must(tm, err) + payload := bytes.NewBufferString("") + _ = replaceRuleVariable.Execute(payload, va) + image = GetImage(reg, payload.String()) + } else { + image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Format("20060102150405"))) + } + t.JenkinsBuildArgs.JenkinsBuildParams[i].Value = image break } else { -- Gitee From fb1e57d63332334ea7b07a19359c40478cab3de4 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 6 Apr 2022 19:04:48 +0800 Subject: [PATCH 2066/2647] optimize code Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gitee.go | 10 +- pkg/setting/consts.go | 30 +- pkg/tool/gitee/branch.go | 56 +- pkg/tool/gitee/event_parsing.go | 916 ++++++------------ pkg/tool/gitee/repositories.go | 190 ++-- 5 files changed, 457 insertions(+), 745 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index b1cbb5a0..7536158d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -2,6 +2,7 @@ package webhook import ( "errors" + "fmt" "net/http" "time" @@ -49,7 +50,14 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { errorList = multierror.Append(errorList, err) } - case *gitee.PullRequestEvent, *gitee.TagPushEvent: + case *gitee.PullRequestEvent: + if event.Action != "open" { + return fmt.Errorf("action %s is skipped", event.Action) + } + if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + case *gitee.TagPushEvent: if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { errorList = multierror.Append(errorList, err) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 4d2b31cc..eab69954 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -215,37 +215,37 @@ const ( DefaultIngressClass = "koderover-nginx" - // K8SDeployType 容器化部署方式 + // K8SDeployType Containerized Deployment K8SDeployType = "k8s" - // helm 部署 + // helm deployment HelmDeployType = "helm" - // PMDeployType physical machine deploy 脚本物理机部署方式 + // PMDeployType physical machine deploy method PMDeployType = "pm" - // 基础设施 k8s类型 + // Infrastructure k8s type BasicFacilityK8S = "kubernetes" - // 基础设施 云主机 + // Infrastructure Cloud Hosting BasicFacilityCVM = "cloud_host" - // SourceFromZadig 配置来源由平台管理 + // SourceFromZadig Configuration sources are managed by the platform SourceFromZadig = "spock" - // SourceFromGitlab 配置来源为gitlab + // SourceFromGitlab The configuration source is gitlab SourceFromGitlab = "gitlab" - // SourceFromGithub 配置来源为github + // SourceFromGithub The configuration source is github SourceFromGithub = "github" - // SourceFromGitlab 配置来源为gerrit + // SourceFromGitlab The configuration source is gerrit SourceFromGerrit = "gerrit" - // SourceFromCodeHub 配置来源为codehub + // SourceFromCodeHub The configuration source is codehub SourceFromCodeHub = "codehub" - // SourceFromGitee 配置来源为gitee + // SourceFromGitee Configure the source as gitee SourceFromGitee = "gitee" - // SourceFromChartTemplate 配置来源为helmTemplate + // SourceFromChartTemplate The configuration source is helmTemplate SourceFromChartTemplate = "chartTemplate" - // SourceFromPublicRepo 配置来源为publicRepo + // SourceFromPublicRepo The configuration source is publicRepo SourceFromPublicRepo = "publicRepo" SourceFromChartRepo = "chartRepo" - // SourceFromGUI 配置来源为gui + // SourceFromGUI The configuration source is gui SourceFromGUI = "gui" //SourceFromHelm SourceFromHelm = "helm" @@ -265,7 +265,7 @@ const ( PublicService = "public" - // onboarding流程第二步 + // The second step of the onboarding process OnboardingStatusSecond = 2 Unset = "UNSET" diff --git a/pkg/tool/gitee/branch.go b/pkg/tool/gitee/branch.go index c0246b00..fe7773fc 100644 --- a/pkg/tool/gitee/branch.go +++ b/pkg/tool/gitee/branch.go @@ -19,29 +19,39 @@ func (c *Client) ListBranches(ctx context.Context, owner, repo string, opts *git } type Branch struct { - Name string `json:"name"` - Commit struct { - Sha string `json:"sha"` - URL string `json:"url"` - Commit struct { - Author struct { - Name string `json:"name"` - Date time.Time `json:"date"` - Email string `json:"email"` - } `json:"author"` - URL string `json:"url"` - Message string `json:"message"` - Tree struct { - Sha string `json:"sha"` - URL string `json:"url"` - } `json:"tree"` - Committer struct { - Name string `json:"name"` - Date time.Time `json:"date"` - Email string `json:"email"` - } `json:"committer"` - } `json:"commit"` - } `json:"commit"` + Name string `json:"name"` + Commit BranchCommit `json:"commit"` +} + +type BranchCommit struct { + Sha string `json:"sha"` + URL string `json:"url"` + Commit BranchCommmitInternalCommit `json:"commit"` +} + +type BranchCommmitInternalCommit struct { + Author BranchAuthor `json:"author"` + URL string `json:"url"` + Message string `json:"message"` + Tree BranchTree `json:"tree"` + Committer BranchCommitter `json:"committer"` +} + +type BranchAuthor struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` +} + +type BranchTree struct { + Sha string `json:"sha"` + URL string `json:"url"` +} + +type BranchCommitter struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` } func (c *Client) GetSingleBranch(accessToken, owner, repo, branch string) (*Branch, error) { diff --git a/pkg/tool/gitee/event_parsing.go b/pkg/tool/gitee/event_parsing.go index d94bfd38..e9a3b076 100644 --- a/pkg/tool/gitee/event_parsing.go +++ b/pkg/tool/gitee/event_parsing.go @@ -48,624 +48,312 @@ func parseWebhook(eventType EventType, payload []byte) (event interface{}, err e } type TagPushEvent struct { - Ref string `json:"ref"` - Before string `json:"before"` - After string `json:"after"` - Created bool `json:"created"` - Deleted bool `json:"deleted"` - Compare string `json:"compare"` - Commits []struct { - ID string `json:"id"` - TreeID string `json:"tree_id"` - ParentIds []string `json:"parent_ids"` - Distinct bool `json:"distinct"` - Message string `json:"message"` - Timestamp time.Time `json:"timestamp"` - URL string `json:"url"` - Author struct { - Time time.Time `json:"time"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"author"` - Committer struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"committer"` - Added interface{} `json:"added"` - Removed interface{} `json:"removed"` - Modified []string `json:"modified"` - } `json:"commits"` - HeadCommit struct { - ID string `json:"id"` - TreeID string `json:"tree_id"` - ParentIds []string `json:"parent_ids"` - Distinct bool `json:"distinct"` - Message string `json:"message"` - Timestamp time.Time `json:"timestamp"` - URL string `json:"url"` - Author struct { - Time time.Time `json:"time"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"author"` - Committer struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"committer"` - Added interface{} `json:"added"` - Removed interface{} `json:"removed"` - Modified []string `json:"modified"` - } `json:"head_commit"` - TotalCommitsCount int `json:"total_commits_count"` - CommitsMoreThanTen bool `json:"commits_more_than_ten"` - Repository struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - Description string `json:"description"` - Fork bool `json:"fork"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - PushedAt time.Time `json:"pushed_at"` - GitURL string `json:"git_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - SvnURL string `json:"svn_url"` - GitHTTPURL string `json:"git_http_url"` - GitSSHURL string `json:"git_ssh_url"` - GitSvnURL string `json:"git_svn_url"` - Homepage interface{} `json:"homepage"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - ForksCount int `json:"forks_count"` - Language string `json:"language"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - License interface{} `json:"license"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - Namespace string `json:"namespace"` - NameWithNamespace string `json:"name_with_namespace"` - PathWithNamespace string `json:"path_with_namespace"` - } `json:"repository"` - Project struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - Description string `json:"description"` - Fork bool `json:"fork"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - PushedAt time.Time `json:"pushed_at"` - GitURL string `json:"git_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - SvnURL string `json:"svn_url"` - GitHTTPURL string `json:"git_http_url"` - GitSSHURL string `json:"git_ssh_url"` - GitSvnURL string `json:"git_svn_url"` - Homepage interface{} `json:"homepage"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - ForksCount int `json:"forks_count"` - Language string `json:"language"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - License interface{} `json:"license"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - Namespace string `json:"namespace"` - NameWithNamespace string `json:"name_with_namespace"` - PathWithNamespace string `json:"path_with_namespace"` - } `json:"project"` - UserID int `json:"user_id"` - UserName string `json:"user_name"` - User struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"user"` - Pusher struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"pusher"` - Sender struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"sender"` - Enterprise struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"enterprise"` - HookName string `json:"hook_name"` - HookID int `json:"hook_id"` - HookURL string `json:"hook_url"` - Password string `json:"password"` - Timestamp interface{} `json:"timestamp"` - Sign string `json:"sign"` + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + Created bool `json:"created"` + Deleted bool `json:"deleted"` + Compare string `json:"compare"` + Commits []TagPushEventCommit `json:"commits"` + HeadCommit TagPushEventHeadCommit `json:"head_commit"` + TotalCommitsCount int `json:"total_commits_count"` + CommitsMoreThanTen bool `json:"commits_more_than_ten"` + Repository EventRepo `json:"repository"` + Project TagPushEventProject `json:"project"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + User EventUser `json:"user"` + Pusher EventUser `json:"pusher"` + Sender EventUser `json:"sender"` + Enterprise EventEnterprise `json:"enterprise"` + HookName string `json:"hook_name"` + HookID int `json:"hook_id"` + HookURL string `json:"hook_url"` + Password string `json:"password"` + Timestamp interface{} `json:"timestamp"` + Sign string `json:"sign"` +} + +type TagPushEventCommit struct { + ID string `json:"id"` + TreeID string `json:"tree_id"` + ParentIds []string `json:"parent_ids"` + Distinct bool `json:"distinct"` + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + URL string `json:"url"` + Author EventUser `json:"author"` + Committer EventUser `json:"committer"` + Added interface{} `json:"added"` + Removed interface{} `json:"removed"` + Modified []string `json:"modified"` +} + +type TagPushEventHeadCommit struct { + ID string `json:"id"` + TreeID string `json:"tree_id"` + ParentIds []string `json:"parent_ids"` + Distinct bool `json:"distinct"` + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + URL string `json:"url"` + Author EventUser `json:"author"` + Committer EventUser `json:"committer"` + Added interface{} `json:"added"` + Removed interface{} `json:"removed"` + Modified []string `json:"modified"` +} + +type TagPushEventProject struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner EventUser `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` } type PushEvent struct { - Ref string `json:"ref"` - Before string `json:"before"` - After string `json:"after"` - Created bool `json:"created"` - Deleted bool `json:"deleted"` - Compare string `json:"compare"` - Commits []struct { - ID string `json:"id"` - TreeID string `json:"tree_id"` - ParentIds []string `json:"parent_ids"` - Distinct bool `json:"distinct"` - Message string `json:"message"` - Timestamp time.Time `json:"timestamp"` - URL string `json:"url"` - Author struct { - Time time.Time `json:"time"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"author"` - Committer struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"committer"` - Added []string `json:"added,omitempty"` - Removed []string `json:"removed,omitempty"` - Modified []string `json:"modified,omitempty"` - } `json:"commits"` - TotalCommitsCount int `json:"total_commits_count"` - CommitsMoreThanTen bool `json:"commits_more_than_ten"` - Repository struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - Description string `json:"description"` - Fork bool `json:"fork"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - PushedAt time.Time `json:"pushed_at"` - GitURL string `json:"git_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - SvnURL string `json:"svn_url"` - GitHTTPURL string `json:"git_http_url"` - GitSSHURL string `json:"git_ssh_url"` - GitSvnURL string `json:"git_svn_url"` - Homepage interface{} `json:"homepage"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - ForksCount int `json:"forks_count"` - Language string `json:"language"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - License interface{} `json:"license"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - Namespace string `json:"namespace"` - NameWithNamespace string `json:"name_with_namespace"` - PathWithNamespace string `json:"path_with_namespace"` - } `json:"repository"` - UserID int `json:"user_id"` - UserName string `json:"user_name"` - Pusher struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"pusher"` - Enterprise struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"enterprise"` - HookName string `json:"hook_name"` - HookID int `json:"hook_id"` - HookURL string `json:"hook_url"` - Password string `json:"password"` - Timestamp interface{} `json:"timestamp"` - Sign string `json:"sign"` + Ref string `json:"ref"` + Before string `json:"before"` + After string `json:"after"` + Created bool `json:"created"` + Deleted bool `json:"deleted"` + Compare string `json:"compare"` + Commits []PushEventCommit `json:"commits"` + TotalCommitsCount int `json:"total_commits_count"` + CommitsMoreThanTen bool `json:"commits_more_than_ten"` + Repository EventRepo `json:"repository"` + UserID int `json:"user_id"` + UserName string `json:"user_name"` + Pusher EventUser `json:"pusher"` + Enterprise EventEnterprise `json:"enterprise"` + HookName string `json:"hook_name"` + HookID int `json:"hook_id"` + HookURL string `json:"hook_url"` + Password string `json:"password"` + Timestamp interface{} `json:"timestamp"` + Sign string `json:"sign"` +} + +type PushEventCommit struct { + ID string `json:"id"` + TreeID string `json:"tree_id"` + ParentIds []string `json:"parent_ids"` + Distinct bool `json:"distinct"` + Message string `json:"message"` + Timestamp time.Time `json:"timestamp"` + URL string `json:"url"` + Author EventUser `json:"author"` + Committer EventUser `json:"committer"` + Added []string `json:"added,omitempty"` + Removed []string `json:"removed,omitempty"` + Modified []string `json:"modified,omitempty"` } type PullRequestEvent struct { - Action string `json:"action"` - ActionDesc string `json:"action_desc"` - Project struct { - ID int `json:"id"` - Language interface{} `json:"language"` - License interface{} `json:"license"` - Name string `json:"name"` - NameWithNamespace string `json:"name_with_namespace"` - Namespace string `json:"namespace"` - OpenIssuesCount int `json:"open_issues_count"` - Path string `json:"path"` - PathWithNamespace string `json:"path_with_namespace"` - Private bool `json:"private"` - PushedAt time.Time `json:"pushed_at"` - SSHURL string `json:"ssh_url"` - StargazersCount int `json:"stargazers_count"` - SvnURL string `json:"svn_url"` - UpdatedAt time.Time `json:"updated_at"` - URL string `json:"url"` - WatchersCount int `json:"watchers_count"` - } `json:"project"` - PullRequest *struct { - ID int `json:"id"` - Number int `json:"number"` - State string `json:"state"` - HTMLURL string `json:"html_url"` - DiffURL string `json:"diff_url"` - PatchURL string `json:"patch_url"` - Title string `json:"title"` - Body interface{} `json:"body"` - Languages []string `json:"languages"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - ClosedAt interface{} `json:"closed_at"` - MergedAt interface{} `json:"merged_at"` - MergeCommitSha string `json:"merge_commit_sha"` - User struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"user"` - Head *struct { - Label string `json:"label"` - Ref string `json:"ref"` - Sha string `json:"sha"` - User struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"user"` - Repo struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - Description string `json:"description"` - Fork bool `json:"fork"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - PushedAt time.Time `json:"pushed_at"` - GitURL string `json:"git_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - SvnURL string `json:"svn_url"` - GitHTTPURL string `json:"git_http_url"` - GitSSHURL string `json:"git_ssh_url"` - GitSvnURL string `json:"git_svn_url"` - Homepage interface{} `json:"homepage"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - ForksCount int `json:"forks_count"` - Language string `json:"language"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - License interface{} `json:"license"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - Namespace string `json:"namespace"` - NameWithNamespace string `json:"name_with_namespace"` - PathWithNamespace string `json:"path_with_namespace"` - } `json:"repo"` - } `json:"head"` - Base struct { - Label string `json:"label"` - Ref string `json:"ref"` - Sha string `json:"sha"` - User struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"user"` - Repo struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - Description string `json:"description"` - Fork bool `json:"fork"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - PushedAt time.Time `json:"pushed_at"` - GitURL string `json:"git_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - SvnURL string `json:"svn_url"` - GitHTTPURL string `json:"git_http_url"` - GitSSHURL string `json:"git_ssh_url"` - GitSvnURL string `json:"git_svn_url"` - Homepage interface{} `json:"homepage"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - ForksCount int `json:"forks_count"` - Language string `json:"language"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - License interface{} `json:"license"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - Namespace string `json:"namespace"` - NameWithNamespace string `json:"name_with_namespace"` - PathWithNamespace string `json:"path_with_namespace"` - } `json:"repo"` - } `json:"base"` - Merged bool `json:"merged"` - Mergeable interface{} `json:"mergeable"` - MergeStatus string `json:"merge_status"` - Comments int `json:"comments"` - Commits int `json:"commits"` - Additions int `json:"additions"` - Deletions int `json:"deletions"` - ChangedFiles int `json:"changed_files"` - } `json:"pull_request"` - Number int `json:"number"` - Iid int `json:"iid"` - Title string `json:"title"` - Body interface{} `json:"body"` - Languages []string `json:"languages"` - State string `json:"state"` - MergeStatus string `json:"merge_status"` - MergeCommitSha string `json:"merge_commit_sha"` - URL string `json:"url"` - SourceBranch string `json:"source_branch"` - TargetBranch string `json:"target_branch"` - Repository struct { - ID int `json:"id"` - Name string `json:"name"` - Path string `json:"path"` - FullName string `json:"full_name"` - Owner struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"owner"` - Private bool `json:"private"` - HTMLURL string `json:"html_url"` - URL string `json:"url"` - Description string `json:"description"` - Fork bool `json:"fork"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - PushedAt time.Time `json:"pushed_at"` - GitURL string `json:"git_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - SvnURL string `json:"svn_url"` - GitHTTPURL string `json:"git_http_url"` - GitSSHURL string `json:"git_ssh_url"` - GitSvnURL string `json:"git_svn_url"` - Homepage interface{} `json:"homepage"` - StargazersCount int `json:"stargazers_count"` - WatchersCount int `json:"watchers_count"` - ForksCount int `json:"forks_count"` - Language string `json:"language"` - HasIssues bool `json:"has_issues"` - HasWiki bool `json:"has_wiki"` - HasPages bool `json:"has_pages"` - License interface{} `json:"license"` - OpenIssuesCount int `json:"open_issues_count"` - DefaultBranch string `json:"default_branch"` - Namespace string `json:"namespace"` - NameWithNamespace string `json:"name_with_namespace"` - PathWithNamespace string `json:"path_with_namespace"` - } `json:"repository"` - Author struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"author"` - UpdatedBy struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"updated_by"` - Sender struct { - Login string `json:"login"` - AvatarURL string `json:"avatar_url"` - HTMLURL string `json:"html_url"` - Type string `json:"type"` - SiteAdmin bool `json:"site_admin"` - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"sender"` - TargetUser struct { - ID int `json:"id"` - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - UserName string `json:"user_name"` - URL string `json:"url"` - } `json:"target_user"` - Enterprise struct { - Name string `json:"name"` - URL string `json:"url"` - } `json:"enterprise"` - HookName string `json:"hook_name"` - HookID int `json:"hook_id"` - HookURL string `json:"hook_url"` - Password string `json:"password"` - Timestamp interface{} `json:"timestamp"` - Sign string `json:"sign"` + Action string `json:"action"` + ActionDesc string `json:"action_desc"` + Project PullRequestEventProject `json:"project"` + PullRequest *PullRequestEventPullRequest `json:"pull_request"` + Number int `json:"number"` + Iid int `json:"iid"` + Title string `json:"title"` + Body interface{} `json:"body"` + Languages []string `json:"languages"` + State string `json:"state"` + MergeStatus string `json:"merge_status"` + MergeCommitSha string `json:"merge_commit_sha"` + URL string `json:"url"` + SourceBranch string `json:"source_branch"` + TargetBranch string `json:"target_branch"` + Repository PullRequestEventRepository `json:"repository"` + Author EventUser `json:"author"` + UpdatedBy EventUser `json:"updated_by"` + Sender EventUser `json:"sender"` + TargetUser EventUser `json:"target_user"` + Enterprise EventEnterprise `json:"enterprise"` + HookName string `json:"hook_name"` + HookID int `json:"hook_id"` + HookURL string `json:"hook_url"` + Password string `json:"password"` + Timestamp interface{} `json:"timestamp"` + Sign string `json:"sign"` +} + +type PullRequestEventProject struct { + ID int `json:"id"` + Language interface{} `json:"language"` + License interface{} `json:"license"` + Name string `json:"name"` + NameWithNamespace string `json:"name_with_namespace"` + Namespace string `json:"namespace"` + OpenIssuesCount int `json:"open_issues_count"` + Path string `json:"path"` + PathWithNamespace string `json:"path_with_namespace"` + Private bool `json:"private"` + PushedAt time.Time `json:"pushed_at"` + SSHURL string `json:"ssh_url"` + StargazersCount int `json:"stargazers_count"` + SvnURL string `json:"svn_url"` + UpdatedAt time.Time `json:"updated_at"` + URL string `json:"url"` + WatchersCount int `json:"watchers_count"` +} + +type PullRequestEventPullRequest struct { + ID int `json:"id"` + Number int `json:"number"` + State string `json:"state"` + HTMLURL string `json:"html_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + Title string `json:"title"` + Body interface{} `json:"body"` + Languages []string `json:"languages"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ClosedAt interface{} `json:"closed_at"` + MergedAt interface{} `json:"merged_at"` + MergeCommitSha string `json:"merge_commit_sha"` + User EventUser `json:"user"` + Head *PullRequestEventBase `json:"head"` + Base PullRequestEventBase `json:"base"` + Merged bool `json:"merged"` + Mergeable interface{} `json:"mergeable"` + MergeStatus string `json:"merge_status"` + Comments int `json:"comments"` + Commits int `json:"commits"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + ChangedFiles int `json:"changed_files"` +} + +type PullRequestEventBase struct { + Label string `json:"label"` + Ref string `json:"ref"` + Sha string `json:"sha"` + User EventUser `json:"user"` + Repo EventRepo `json:"repo"` +} + +type EventRepo struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner EventUser `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` +} + +type PullRequestEventRepository struct { + ID int `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FullName string `json:"full_name"` + Owner EventUser `json:"owner"` + Private bool `json:"private"` + HTMLURL string `json:"html_url"` + URL string `json:"url"` + Description string `json:"description"` + Fork bool `json:"fork"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + PushedAt time.Time `json:"pushed_at"` + GitURL string `json:"git_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + SvnURL string `json:"svn_url"` + GitHTTPURL string `json:"git_http_url"` + GitSSHURL string `json:"git_ssh_url"` + GitSvnURL string `json:"git_svn_url"` + Homepage interface{} `json:"homepage"` + StargazersCount int `json:"stargazers_count"` + WatchersCount int `json:"watchers_count"` + ForksCount int `json:"forks_count"` + Language string `json:"language"` + HasIssues bool `json:"has_issues"` + HasWiki bool `json:"has_wiki"` + HasPages bool `json:"has_pages"` + License interface{} `json:"license"` + OpenIssuesCount int `json:"open_issues_count"` + DefaultBranch string `json:"default_branch"` + Namespace string `json:"namespace"` + NameWithNamespace string `json:"name_with_namespace"` + PathWithNamespace string `json:"path_with_namespace"` +} + +type EventEnterprise struct { + Name string `json:"name"` + URL string `json:"url"` +} + +type EventUser struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + HTMLURL string `json:"html_url"` + Type string `json:"type"` + SiteAdmin bool `json:"site_admin"` + ID int `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + UserName string `json:"user_name"` + URL string `json:"url"` } diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index 9d1b284e..b9919afb 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -152,21 +152,21 @@ func (c *Client) GetTrees(ctx context.Context, owner, repo, sha string, level in } type RepoCommit struct { - URL string `json:"url"` - Sha string `json:"sha"` - Commit struct { - Author struct { - Name string `json:"name"` - Date time.Time `json:"date"` - Email string `json:"email"` - } `json:"author"` - Committer struct { - Name string `json:"name"` - Date time.Time `json:"date"` - Email string `json:"email"` - } `json:"committer"` - Message string `json:"message"` - } `json:"commit"` + URL string `json:"url"` + Sha string `json:"sha"` + Commit RepoCommitInnerCommit `json:"commit"` +} + +type RepoCommitInnerCommit struct { + Author RepoCommitAuthor `json:"author"` + Committer RepoCommitAuthor `json:"committer"` + Message string `json:"message"` +} + +type RepoCommitAuthor struct { + Name string `json:"name"` + Date time.Time `json:"date"` + Email string `json:"email"` } func (c *Client) GetSingleCommitOfProject(ctx context.Context, accessToken, owner, repo, commitSha string) (*RepoCommit, error) { @@ -211,83 +211,89 @@ func RefreshAccessToken(refreshToken string) (*AccessToken, error) { } type Compare struct { - Commits []struct { - URL string `json:"url"` - Sha string `json:"sha"` - HTMLURL string `json:"html_url"` - CommentsURL string `json:"comments_url"` - Commit struct { - Author struct { - Name string `json:"name"` - Date time.Time `json:"date"` - Email string `json:"email"` - } `json:"author"` - Committer struct { - Name string `json:"name"` - Date time.Time `json:"date"` - Email string `json:"email"` - } `json:"committer"` - Message string `json:"message"` - Tree struct { - Sha string `json:"sha"` - URL string `json:"url"` - } `json:"tree"` - } `json:"commit"` - Author struct { - ID int `json:"id"` - Login string `json:"login"` - Name string `json:"name"` - AvatarURL string `json:"avatar_url"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - Remark string `json:"remark"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - } `json:"author"` - Committer struct { - ID int `json:"id"` - Login string `json:"login"` - Name string `json:"name"` - AvatarURL string `json:"avatar_url"` - URL string `json:"url"` - HTMLURL string `json:"html_url"` - Remark string `json:"remark"` - FollowersURL string `json:"followers_url"` - FollowingURL string `json:"following_url"` - GistsURL string `json:"gists_url"` - StarredURL string `json:"starred_url"` - SubscriptionsURL string `json:"subscriptions_url"` - OrganizationsURL string `json:"organizations_url"` - ReposURL string `json:"repos_url"` - EventsURL string `json:"events_url"` - ReceivedEventsURL string `json:"received_events_url"` - Type string `json:"type"` - } `json:"committer"` - Parents []struct { - Sha string `json:"sha"` - URL string `json:"url"` - } `json:"parents"` - } `json:"commits"` - Files []struct { - Sha string `json:"sha"` - Filename string `json:"filename"` - Status string `json:"status"` - Additions int `json:"additions"` - Deletions int `json:"deletions"` - Changes int `json:"changes"` - BlobURL string `json:"blob_url"` - RawURL string `json:"raw_url"` - ContentURL string `json:"content_url"` - Patch string `json:"patch"` - } `json:"files"` + Commits []CompareCommit `json:"commits"` + Files []CompareFiles `json:"files"` +} + +type CompareCommit struct { + URL string `json:"url"` + Sha string `json:"sha"` + HTMLURL string `json:"html_url"` + CommentsURL string `json:"comments_url"` + Commit CompareInnerCommit `json:"commit"` + Author CompareAuthor `json:"author"` + Committer CompareCommitter `json:"committer"` + Parents []CompareParents `json:"parents"` +} + +type CompareParents struct { + Sha string `json:"sha"` + URL string `json:"url"` +} + +type CompareInnerCommit struct { + Author RepoCommitAuthor `json:"author"` + Committer RepoCommitAuthor `json:"committer"` + Message string `json:"message"` + Tree CompareTree `json:"tree"` +} + +type CompareTree struct { + Sha string `json:"sha"` + URL string `json:"url"` +} + +type CompareFiles struct { + Sha string `json:"sha"` + Filename string `json:"filename"` + Status string `json:"status"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + BlobURL string `json:"blob_url"` + RawURL string `json:"raw_url"` + ContentURL string `json:"content_url"` + Patch string `json:"patch"` +} + +type CompareCommitter struct { + ID int `json:"id"` + Login string `json:"login"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Remark string `json:"remark"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` +} + +type CompareAuthor struct { + ID int `json:"id"` + Login string `json:"login"` + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` + URL string `json:"url"` + HTMLURL string `json:"html_url"` + Remark string `json:"remark"` + FollowersURL string `json:"followers_url"` + FollowingURL string `json:"following_url"` + GistsURL string `json:"gists_url"` + StarredURL string `json:"starred_url"` + SubscriptionsURL string `json:"subscriptions_url"` + OrganizationsURL string `json:"organizations_url"` + ReposURL string `json:"repos_url"` + EventsURL string `json:"events_url"` + ReceivedEventsURL string `json:"received_events_url"` + Type string `json:"type"` } func (c *Client) GetReposOwnerRepoCompareBaseHead(accessToken, owner string, repo string, base string, head string) (*Compare, error) { -- Gitee From ab5eedefc155a5e895d85337d373568435fe731a Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 20:14:29 +0800 Subject: [PATCH 2067/2647] $ Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 0433860a..64fb34d0 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -169,10 +169,10 @@ rules: - method: POST endpoint: "/api/aslan/environment/image/deployment/?*" resourceType: "Environment" - idRegex: "/api/aslan/environment/image/deployment/([\\w\\W]+?)" + idRegex: "/api/aslan/environment/image/deployment/([\\w\\W]+?)$" - method: POST resourceType: "Environment" - idRegex: "/api/aslan/environment/image/statefulset/([\\w\\W]+?)" + idRegex: "/api/aslan/environment/image/statefulset/([\\w\\W]+?)$" endpoint: "/api/aslan/environment/image/statefulset/?*" - method: DELETE endpoint: "/api/aslan/environment/kube/?*/pods/?*" -- Gitee From cec97d9609e353e5127b11f4c4bb50d4f1732cb5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 20:30:43 +0800 Subject: [PATCH 2068/2647] exec Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 4 +++- pkg/microservice/podexec/server/server.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 64fb34d0..41cebdcf 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -200,4 +200,6 @@ rules: description: "" rules: - method: GET - endpoint: "/api/podexec/?*/?*/?*/?*/podExec" \ No newline at end of file + endpoint: "/api/podexec/?*/?*/?*/?*/?*/podExec" + idRegex: "api/podexec/([\\w\\W]+?)" + resourceType: "Environment" \ No newline at end of file diff --git a/pkg/microservice/podexec/server/server.go b/pkg/microservice/podexec/server/server.go index 09eb0fda..c57407bc 100644 --- a/pkg/microservice/podexec/server/server.go +++ b/pkg/microservice/podexec/server/server.go @@ -42,7 +42,7 @@ func Serve(ctx context.Context) error { router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]string{"message": "success"}) }) - router.HandleFunc("/api/{productName}/{namespace}/{podName}/{containerName}/podExec", service.ServeWs) + router.HandleFunc("/api/{env}/{productName}/{namespace}/{podName}/{containerName}/podExec", service.ServeWs) server := &http.Server{ Addr: "0.0.0.0:27000", -- Gitee From 0b8cd73cc496aa0fc7dd8399171b4d88dadb3aeb Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 6 Apr 2022 20:35:19 +0800 Subject: [PATCH 2069/2647] support export all files Signed-off-by: ddh27 --- .../workflow/testing/service/workspace.go | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index f10aea59..51c2b23d 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -17,14 +17,20 @@ limitations under the License. package service import ( + "bytes" "fmt" + "io/ioutil" + "os" + "os/exec" "path" + "strings" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/util/fs" ) type GetTestArtifactInfoResp struct { @@ -34,6 +40,7 @@ type GetTestArtifactInfoResp struct { func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.SugaredLogger) (*GetTestArtifactInfoResp, error) { resp := new(GetTestArtifactInfoResp) + fis := make([]string, 0) storage, err := s3.FindDefaultS3() if err != nil { @@ -62,7 +69,42 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare return resp, err } if object != nil && *object.ContentLength > 0 { - resp.FileNames = []string{setting.ArtifactResultOut} + tempDir, _ := ioutil.TempDir("", "") + sourcePath := path.Join(tempDir, "artifact") + if _, err := os.Stat(sourcePath); os.IsNotExist(err) { + _ = os.MkdirAll(sourcePath, 0777) + } + + file, err := os.Create(path.Join(sourcePath, setting.ArtifactResultOut)) + if err != nil { + return nil, fmt.Errorf("failed to create file %s %s", setting.ArtifactResultOut, err) + } + defer func() { + _ = file.Close() + _ = os.Remove(tempDir) + }() + + err = fs.SaveFile(object.Body, file.Name()) + if err != nil { + log.Errorf("Failed to save file to %s, err: %s", file.Name(), err) + return resp, err + } + + cmdtf := exec.Command("tar", "-tf", file.Name()) + var stdoutBuf bytes.Buffer + cmdtf.Stdout = &stdoutBuf + cmdtf.Stderr = os.Stderr + if err = cmdtf.Run(); err != nil { + fmt.Printf("failed to tar -tf err:%s", err) + return resp, err + } + for _, tarFile := range strings.Split(string(stdoutBuf.Bytes()), "\n") { + if strings.HasSuffix(tarFile, "/") { + continue + } + fis = append(fis, tarFile) + } + resp.FileNames = fis resp.NotHistoryFileFlag = true return resp, nil } @@ -73,7 +115,7 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare log.Errorf("GetTestArtifactInfo ListFiles err:%s", err) return resp, err } - fis := make([]string, 0) + for _, file := range files { _, fileName := path.Split(file) fis = append(fis, fileName) -- Gitee From 3c305c0abcdb78bb15700493e43a53875690d34a Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 6 Apr 2022 20:46:44 +0800 Subject: [PATCH 2070/2647] add +Copyright Signed-off-by: liu deyi --- .../aslan/core/code/client/gitee/gitee.go | 16 ++++++++++++ .../aslan/core/common/service/gitee/client.go | 16 ++++++++++++ .../core/common/service/gitee/webhook.go | 16 ++++++++++++ .../core/workflow/service/webhook/gitee.go | 16 ++++++++++++ .../service/webhook/gitee_workflow_task.go | 25 ++++++++++++++++--- pkg/shared/client/systemconfig/codehost.go | 6 +---- pkg/tool/gitee/branch.go | 16 ++++++++++++ pkg/tool/gitee/client.go | 16 ++++++++++++ pkg/tool/gitee/event_parsing.go | 16 ++++++++++++ pkg/tool/gitee/organizations.go | 16 ++++++++++++ pkg/tool/gitee/pull_requests.go | 16 ++++++++++++ pkg/tool/gitee/repositories.go | 16 ++++++++++++ pkg/tool/gitee/tag.go | 16 ++++++++++++ pkg/tool/gitee/users.go | 16 ++++++++++++ 14 files changed, 215 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitee/gitee.go b/pkg/microservice/aslan/core/code/client/gitee/gitee.go index ea78e1ca..6e5b99b2 100644 --- a/pkg/microservice/aslan/core/code/client/gitee/gitee.go +++ b/pkg/microservice/aslan/core/code/client/gitee/gitee.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/microservice/aslan/core/common/service/gitee/client.go b/pkg/microservice/aslan/core/common/service/gitee/client.go index 2a5598ef..c78ed56b 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/client.go +++ b/pkg/microservice/aslan/core/common/service/gitee/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import "github.com/koderover/zadig/pkg/tool/gitee" diff --git a/pkg/microservice/aslan/core/common/service/gitee/webhook.go b/pkg/microservice/aslan/core/common/service/gitee/webhook.go index e9e635e0..fe90ad6c 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/webhook.go +++ b/pkg/microservice/aslan/core/common/service/gitee/webhook.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index 7536158d..cb766e38 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package webhook import ( diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go index 08acecc1..4c68b726 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package webhook import ( @@ -40,7 +56,8 @@ func (gpem *giteePushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (b } if isRegular { // Do not use regexp.MustCompile to avoid panic - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + matched, err := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)) + if err != nil || !matched { return false, nil } } @@ -95,7 +112,8 @@ func (gmem *giteeMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( return false, nil } if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.PullRequest.Base.Ref); !matched { + matched, err := regexp.MatchString(hookRepo.Branch, ev.PullRequest.Base.Ref) + if err != nil || !matched { return false, nil } } @@ -154,7 +172,8 @@ func (gtem giteeTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (boo } if isRegular { // Do not use regexp.MustCompile to avoid panic - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Repository.DefaultBranch); !matched { + matched, err := regexp.MatchString(hookRepo.Branch, ev.Repository.DefaultBranch) + if err != nil || !matched { return false, nil } } diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 25cefb33..1d3ca376 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -86,11 +86,7 @@ func (c *Client) UpdateCodeHost(id int, codehost *CodeHost) error { url := fmt.Sprintf("/codehosts/%d", id) _, err := c.Patch(url, httpclient.SetBody(codehost)) - if err != nil { - return err - } - - return nil + return err } func (c *Client) GetCodeHostByAddressAndOwner(address, owner, source string) (*CodeHost, error) { diff --git a/pkg/tool/gitee/branch.go b/pkg/tool/gitee/branch.go index fe7773fc..2fb67b27 100644 --- a/pkg/tool/gitee/branch.go +++ b/pkg/tool/gitee/branch.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index 65b78d9f..b1487f41 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/event_parsing.go b/pkg/tool/gitee/event_parsing.go index e9a3b076..fbeef30a 100644 --- a/pkg/tool/gitee/event_parsing.go +++ b/pkg/tool/gitee/event_parsing.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/organizations.go b/pkg/tool/gitee/organizations.go index e40b6031..a9f88be9 100644 --- a/pkg/tool/gitee/organizations.go +++ b/pkg/tool/gitee/organizations.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/pull_requests.go b/pkg/tool/gitee/pull_requests.go index 703905cd..16da107f 100644 --- a/pkg/tool/gitee/pull_requests.go +++ b/pkg/tool/gitee/pull_requests.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index b9919afb..ebd8cb23 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/tag.go b/pkg/tool/gitee/tag.go index 5d2c6f85..45117317 100644 --- a/pkg/tool/gitee/tag.go +++ b/pkg/tool/gitee/tag.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( diff --git a/pkg/tool/gitee/users.go b/pkg/tool/gitee/users.go index c5a8566f..f46fa999 100644 --- a/pkg/tool/gitee/users.go +++ b/pkg/tool/gitee/users.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package gitee import ( -- Gitee From c5373264cc5d376741736807b109d7cd4c630fb6 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Wed, 6 Apr 2022 21:11:52 +0800 Subject: [PATCH 2071/2647] support update event Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/service/webhook/gitee.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index cb766e38..a172312e 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -67,7 +67,7 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * errorList = multierror.Append(errorList, err) } case *gitee.PullRequestEvent: - if event.Action != "open" { + if event.Action != "open" && event.Action != "update" { return fmt.Errorf("action %s is skipped", event.Action) } if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { -- Gitee From d88ab722ca92a7f459f1384de126825d0ab20c03 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 6 Apr 2022 22:00:18 +0800 Subject: [PATCH 2072/2647] exec Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 4 ++-- pkg/microservice/podexec/server/server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 41cebdcf..e15694a4 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -200,6 +200,6 @@ rules: description: "" rules: - method: GET - endpoint: "/api/podexec/?*/?*/?*/?*/?*/podExec" - idRegex: "api/podexec/([\\w\\W]+?)" + endpoint: "/api/podexec/?*/?*/?*/?*/podExec/?*" + idRegex: "/podExec/([\\w\\W]+?)$" resourceType: "Environment" \ No newline at end of file diff --git a/pkg/microservice/podexec/server/server.go b/pkg/microservice/podexec/server/server.go index c57407bc..3efc7bd8 100644 --- a/pkg/microservice/podexec/server/server.go +++ b/pkg/microservice/podexec/server/server.go @@ -42,7 +42,7 @@ func Serve(ctx context.Context) error { router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]string{"message": "success"}) }) - router.HandleFunc("/api/{env}/{productName}/{namespace}/{podName}/{containerName}/podExec", service.ServeWs) + router.HandleFunc("/api/{productName}/{namespace}/{podName}/{containerName}/podExec/{env}", service.ServeWs) server := &http.Server{ Addr: "0.0.0.0:27000", -- Gitee From f6510f5b58e91072444358e1fed59590ce1ff18b Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 6 Apr 2022 15:17:18 +0800 Subject: [PATCH 2073/2647] data structure done Signed-off-by: Min Min --- .../core/common/repository/models/build.go | 18 +++++++++++++++--- .../common/repository/models/task/build.go | 7 +++++++ .../workflow/service/workflow/workflow_task.go | 15 +++++++++++++++ .../reaper/core/service/meta/types.go | 12 ++++++++++++ .../warpdrive/core/service/taskplugin/job.go | 5 +++++ .../warpdrive/core/service/types/reaper.go | 7 +++++++ .../warpdrive/core/service/types/task/build.go | 13 ++++++++++++- 7 files changed, 73 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index dcfb1698..e5506845 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -95,15 +95,27 @@ type BuildObj struct { } type PostBuild struct { - DockerBuild *DockerBuild `bson:"docker_build,omitempty" json:"docker_build"` - FileArchive *FileArchive `bson:"file_archive,omitempty" json:"file_archive,omitempty"` - Scripts string `bson:"scripts" json:"scripts"` + DockerBuild *DockerBuild `bson:"docker_build,omitempty" json:"docker_build"` + ObjectStorageUpload *ObjectStorageUpload `bson:"object_storage_upload" json:"object_storage_upload"` + FileArchive *FileArchive `bson:"file_archive,omitempty" json:"file_archive,omitempty"` + Scripts string `bson:"scripts" json:"scripts"` } type FileArchive struct { FileLocation string `bson:"file_location" json:"file_location"` } +type ObjectStorageUpload struct { + Enabled bool `bson:"enabled" json:"enabled"` + ObjectStorageID string `bson:"object_storage_id" json:"object_storage_id"` + UploadDetail []*ObjectStoragePathDetail `bson:"upload_detail" json:"upload_detail"` +} + +type ObjectStoragePathDetail struct { + FilePath string `bson:"file_path" json:"file_path"` + DestinationPath string `bson:"dest_path" json:"dest_path"` +} + type DockerBuild struct { // WorkDir docker run path WorkDir string `bson:"work_dir" json:"work_dir"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 83144e88..04d42cec 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -168,6 +168,13 @@ type JobCtx struct { ClassicBuild bool `bson:"classic_build" json:"classic_build"` PostScripts string `bson:"post_scripts,omitempty" json:"post_scripts"` PMDeployScripts string `bson:"pm_deploy_scripts,omitempty" json:"pm_deploy_scripts"` + + // Upload To S3 related context + UploadEnabled bool `bson:"upload_enabled" json:"upload_enabled"` + OSSEndpoint string `bson:"oss_endpoint" json:"oss_endpoint"` + OSSAK string `bson:"oss_AK" json:"oss_AK"` + OSSSK string `bson:"oss_SK" json:"oss_SK"` + UploadInfo []*models.ObjectStoragePathDetail `bson:"upload_info" json:"upload_info"` } type BuildStep struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c56d4ef8..f0766b2c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2143,6 +2143,21 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.PostScripts = module.PostBuild.Scripts } + if module.PostBuild != nil && module.PostBuild.ObjectStorageUpload != nil { + build.JobCtx.UploadEnabled = module.PostBuild.ObjectStorageUpload.Enabled + if module.PostBuild.ObjectStorageUpload.Enabled { + storageInfo, err := commonrepo.NewS3StorageColl().Find(module.PostBuild.ObjectStorageUpload.ObjectStorageID) + if err != nil { + log.Infof("Failed to get basic storage info for uploading, the error is %s", err) + return nil, err + } + build.JobCtx.OSSEndpoint = storageInfo.Endpoint + build.JobCtx.OSSAK = storageInfo.Ak + build.JobCtx.OSSSK = storageInfo.Sk + build.JobCtx.UploadInfo = module.PostBuild.ObjectStorageUpload.UploadDetail + } + } + build.JobCtx.Caches = module.Caches if args.FileName != "" { diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 6d90698a..531c555c 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -125,6 +125,18 @@ type Context struct { Cache types.Cache `yaml:"cache"` CacheDirType types.CacheDirType `yaml:"cache_dir_type"` CacheUserDir string `yaml:"cache_user_dir"` + + // Upload To S3 related context + UploadEnabled bool `yaml:"upload_enabled"` + OSSEndpoint string `yaml:"oss_endpoint"` + OSSAK string `yaml:"oss_AK"` + OSSSK string `yaml:"oss_SK"` + UploadInfo []*ObjectStoragePathDetail `yaml:"upload_info"` +} + +type ObjectStoragePathDetail struct { + FilePath string `yaml:"file_path"` + DestinationPath string `yaml:"dest_path"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 642bb94d..0f1134ab 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -226,6 +226,11 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ServiceName: serviceName, StorageEndpoint: pipelineTask.StorageEndpoint, AesKey: pipelineTask.ConfigPayload.AesKey, + UploadEnabled: b.JobCtx.UploadEnabled, + OSSEndpoint: b.JobCtx.OSSEndpoint, + OSSAK: b.JobCtx.OSSAK, + OSSSK: b.JobCtx.OSSSK, + UploadInfo: b.JobCtx.UploadInfo, } if b.PipelineCtx.CacheEnable && !pipelineTask.ConfigPayload.ResetCache { diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 9ec9e96e..6d96c5f0 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -123,6 +123,13 @@ type Context struct { Cache types.Cache `yaml:"cache"` CacheDirType types.CacheDirType `yaml:"cache_dir_type"` CacheUserDir string `yaml:"cache_user_dir"` + + // Upload To S3 related context + UploadEnabled bool `yaml:"upload_enabled"` + OSSEndpoint string `yaml:"oss_endpoint"` + OSSAK string `yaml:"oss_AK"` + OSSSK string `yaml:"oss_SK"` + UploadInfo []*task.ObjectStoragePathDetail `yaml:"upload_info"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index ae6e4310..b4b9998a 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -18,7 +18,6 @@ package task import ( "fmt" - "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" @@ -198,6 +197,18 @@ type JobCtx struct { ClassicBuild bool `bson:"classic_build" json:"classic_build"` PostScripts string `bson:"post_scripts,omitempty" json:"post_scripts"` PMDeployScripts string `bson:"pm_deploy_scripts,omitempty" json:"pm_deploy_scripts"` + + // Upload To S3 related context + UploadEnabled bool `json:"upload_enabled"` + OSSEndpoint string `json:"oss_endpoint"` + OSSAK string `json:"oss_AK"` + OSSSK string `json:"oss_SK"` + UploadInfo []*ObjectStoragePathDetail `json:"upload_info"` +} + +type ObjectStoragePathDetail struct { + FilePath string `json:"file_path" yaml:"file_path"` + DestinationPath string `json:"dest_path" yaml:"dest_path"` } type SSH struct { -- Gitee From e11b0115403ec5d61542dc8c812f656a6d19755a Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 00:21:56 +0800 Subject: [PATCH 2074/2647] logic implementation Signed-off-by: Min Min --- .../common/repository/models/task/build.go | 17 ++-- .../service/workflow/workflow_task.go | 11 ++- .../reaper/core/service/meta/types.go | 17 ++-- .../reaper/core/service/reaper/reaper.go | 78 ++++++++++--------- .../warpdrive/core/service/taskplugin/job.go | 13 +++- .../warpdrive/core/service/types/reaper.go | 8 +- .../core/service/types/task/build.go | 21 +++-- 7 files changed, 98 insertions(+), 67 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 04d42cec..f3609ab7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -170,11 +170,18 @@ type JobCtx struct { PMDeployScripts string `bson:"pm_deploy_scripts,omitempty" json:"pm_deploy_scripts"` // Upload To S3 related context - UploadEnabled bool `bson:"upload_enabled" json:"upload_enabled"` - OSSEndpoint string `bson:"oss_endpoint" json:"oss_endpoint"` - OSSAK string `bson:"oss_AK" json:"oss_AK"` - OSSSK string `bson:"oss_SK" json:"oss_SK"` - UploadInfo []*models.ObjectStoragePathDetail `bson:"upload_info" json:"upload_info"` + UploadEnabled bool `bson:"upload_enabled" json:"upload_enabled"` + UploadStorageInfo *ObjectStorageInfo `bson:"upload_storage_info" json:"upload_storage_info"` + UploadInfo []*models.ObjectStoragePathDetail `bson:"upload_info" json:"upload_info"` +} + +type ObjectStorageInfo struct { + Endpoint string `json:"endpoint" bson:"endpoint"` + AK string `json:"AK" bson:"AK"` + SK string `json:"SK" bson:"SK"` + Bucket string `json:"bucket" bson:"bucket"` + Insecure bool `json:"insecure" bson:"insecure"` + Provider int8 `json:"provider" bson:"provider"` } type BuildStep struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index f0766b2c..76d60663 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2151,9 +2151,14 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL log.Infof("Failed to get basic storage info for uploading, the error is %s", err) return nil, err } - build.JobCtx.OSSEndpoint = storageInfo.Endpoint - build.JobCtx.OSSAK = storageInfo.Ak - build.JobCtx.OSSSK = storageInfo.Sk + build.JobCtx.UploadStorageInfo = &taskmodels.ObjectStorageInfo{ + Endpoint: storageInfo.Endpoint, + AK: storageInfo.Ak, + SK: storageInfo.Sk, + Bucket: storageInfo.Bucket, + Insecure: storageInfo.Insecure, + Provider: storageInfo.Provider, + } build.JobCtx.UploadInfo = module.PostBuild.ObjectStorageUpload.UploadDetail } } diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 531c555c..62a8fa16 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -127,11 +127,18 @@ type Context struct { CacheUserDir string `yaml:"cache_user_dir"` // Upload To S3 related context - UploadEnabled bool `yaml:"upload_enabled"` - OSSEndpoint string `yaml:"oss_endpoint"` - OSSAK string `yaml:"oss_AK"` - OSSSK string `yaml:"oss_SK"` - UploadInfo []*ObjectStoragePathDetail `yaml:"upload_info"` + UploadEnabled bool `yaml:"upload_enabled"` + UploadStorageInfo *ObjectStorageInfo `yaml:"upload_storage_info"` + UploadInfo []*ObjectStoragePathDetail `yaml:"upload_info"` +} + +type ObjectStorageInfo struct { + Endpoint string `yaml:"endpoint"` + AK string `yaml:"AK"` + SK string `yaml:"SK"` + Bucket string `yaml:"bucket"` + Insecure bool `yaml:"insecure"` + Provider int8 `yaml:"provider"` } type ObjectStoragePathDetail struct { diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 1aa1b34e..4706cb75 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -19,6 +19,7 @@ package reaper import ( "bytes" "fmt" + "github.com/koderover/zadig/pkg/tool/s3" "io" "io/ioutil" "os" @@ -377,44 +378,6 @@ func (r *Reaper) Exec() error { } func (r *Reaper) AfterExec() error { - if r.Ctx.GinkgoTest != nil { - resultPath := r.Ctx.GinkgoTest.ResultPath - if resultPath != "" && !strings.HasPrefix(resultPath, "/") { - resultPath = filepath.Join(r.ActiveWorkspace, resultPath) - } - - if r.Ctx.TestType == "" { - r.Ctx.TestType = setting.FunctionTest - } - - switch r.Ctx.TestType { - case setting.FunctionTest: - err := mergeGinkgoTestResults(r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir, r.StartTime) - if err != nil { - return fmt.Errorf("failed to merge test result: %s", err) - } - case setting.PerformanceTest: - err := JmeterTestResults(r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir) - if err != nil { - return fmt.Errorf("failed to archive performance test result: %s", err) - } - } - - if len(r.Ctx.GinkgoTest.ArtifactPaths) > 0 { - if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, r.Ctx.GinkgoTest.ArtifactPaths); err != nil { - return fmt.Errorf("failed to upload artifacts: %s", err) - } - } - - if err := r.archiveTestFiles(); err != nil { - return fmt.Errorf("failed to archive test files: %s", err) - } - - if err := r.archiveHTMLTestReportFile(); err != nil { - return fmt.Errorf("failed to archive HTML test report: %s", err) - } - } - if r.Ctx.ArtifactInfo == nil { if err := r.archiveS3Files(); err != nil { return fmt.Errorf("failed to archive S3 files: %s", err) @@ -429,6 +392,45 @@ func (r *Reaper) AfterExec() error { } } + if r.Ctx.UploadEnabled { + forcedPathStyle := true + if r.Ctx.UploadStorageInfo.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + client, err := s3.NewClient(r.Ctx.UploadStorageInfo.Endpoint, r.Ctx.UploadStorageInfo.AK, r.Ctx.UploadStorageInfo.SK, r.Ctx.UploadStorageInfo.Insecure, forcedPathStyle) + if err != nil { + return fmt.Errorf("failed to create s3 client to upload file, err: %s", err) + } + for _, upload := range r.Ctx.UploadInfo { + info, err := os.Stat(upload.FilePath) + if err != nil { + return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", upload.FilePath, upload.DestinationPath, err) + } + // if the given path is a directory + if info.IsDir() { + // we get ALL files in this directory and upload it to the object storage + files, err := ioutil.ReadDir(upload.FilePath) + if err != nil { + return fmt.Errorf("failed to read file information in directory: [%s], the error is: %s", upload.FilePath, err) + } + for _, file := range files { + key := filepath.Join(upload.DestinationPath, file.Name()) + originalFilePath := filepath.Join(upload.FilePath, file.Name()) + err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, originalFilePath, key) + if err != nil { + fmt.Printf("Failed to upload [%s] to key [%s] on s3, the error is: %s", originalFilePath, key, err) + } + } + } else { + key := filepath.Join(upload.DestinationPath, info.Name()) + err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, upload.FilePath, key) + if err != nil { + fmt.Printf("Failed to upload [%s] to key [%s] on s3, the error is: %s", upload.FilePath, key, err) + } + } + } + } + if r.Ctx.ArtifactPath != "" { if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, []string{r.Ctx.ArtifactPath}, "buildv3"); err != nil { return fmt.Errorf("failed to upload artifacts: %s", err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 0f1134ab..a15ef4b2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -227,10 +227,15 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName StorageEndpoint: pipelineTask.StorageEndpoint, AesKey: pipelineTask.ConfigPayload.AesKey, UploadEnabled: b.JobCtx.UploadEnabled, - OSSEndpoint: b.JobCtx.OSSEndpoint, - OSSAK: b.JobCtx.OSSAK, - OSSSK: b.JobCtx.OSSSK, - UploadInfo: b.JobCtx.UploadInfo, + UploadStorageInfo: &task.ObjectStorageInfo{ + Endpoint: b.JobCtx.UploadStorageInfo.Endpoint, + AK: b.JobCtx.UploadStorageInfo.AK, + SK: b.JobCtx.UploadStorageInfo.SK, + Bucket: b.JobCtx.UploadStorageInfo.Bucket, + Insecure: b.JobCtx.UploadStorageInfo.Insecure, + Provider: b.JobCtx.UploadStorageInfo.Provider, + }, + UploadInfo: b.JobCtx.UploadInfo, } if b.PipelineCtx.CacheEnable && !pipelineTask.ConfigPayload.ResetCache { diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 6d96c5f0..253612a0 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -125,11 +125,9 @@ type Context struct { CacheUserDir string `yaml:"cache_user_dir"` // Upload To S3 related context - UploadEnabled bool `yaml:"upload_enabled"` - OSSEndpoint string `yaml:"oss_endpoint"` - OSSAK string `yaml:"oss_AK"` - OSSSK string `yaml:"oss_SK"` - UploadInfo []*task.ObjectStoragePathDetail `yaml:"upload_info"` + UploadEnabled bool `yaml:"upload_enabled"` + UploadStorageInfo *task.ObjectStorageInfo `yaml:"upload_storage_info"` + UploadInfo []*task.ObjectStoragePathDetail `yaml:"upload_info"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index b4b9998a..2b00e18e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -199,16 +199,23 @@ type JobCtx struct { PMDeployScripts string `bson:"pm_deploy_scripts,omitempty" json:"pm_deploy_scripts"` // Upload To S3 related context - UploadEnabled bool `json:"upload_enabled"` - OSSEndpoint string `json:"oss_endpoint"` - OSSAK string `json:"oss_AK"` - OSSSK string `json:"oss_SK"` - UploadInfo []*ObjectStoragePathDetail `json:"upload_info"` + UploadEnabled bool `json:"upload_enabled"` + UploadStorageInfo *ObjectStorageInfo `json:"upload_storage_info"` + UploadInfo []*ObjectStoragePathDetail `json:"upload_info"` } type ObjectStoragePathDetail struct { - FilePath string `json:"file_path" yaml:"file_path"` - DestinationPath string `json:"dest_path" yaml:"dest_path"` + FilePath string `json:"file_path"` + DestinationPath string `json:"dest_path"` +} + +type ObjectStorageInfo struct { + Endpoint string `json:"endpoint" yaml:"endpoint"` + AK string `json:"AK" yaml:"AK"` + SK string `json:"SK" yaml:"SK"` + Bucket string `json:"bucket" yaml:"bucket"` + Insecure bool `json:"insecure" yaml:"insecure"` + Provider int8 `json:"provider" yaml:"provider"` } type SSH struct { -- Gitee From b638dbae4231eb6c2fb276b1292238dd7ec0cd50 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 10:20:19 +0800 Subject: [PATCH 2075/2647] reverse deletion Signed-off-by: Min Min --- .../reaper/core/service/reaper/reaper.go | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 4706cb75..27e8f0a8 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -378,6 +378,44 @@ func (r *Reaper) Exec() error { } func (r *Reaper) AfterExec() error { + if r.Ctx.GinkgoTest != nil { + resultPath := r.Ctx.GinkgoTest.ResultPath + if resultPath != "" && !strings.HasPrefix(resultPath, "/") { + resultPath = filepath.Join(r.ActiveWorkspace, resultPath) + } + + if r.Ctx.TestType == "" { + r.Ctx.TestType = setting.FunctionTest + } + + switch r.Ctx.TestType { + case setting.FunctionTest: + err := mergeGinkgoTestResults(r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir, r.StartTime) + if err != nil { + return fmt.Errorf("failed to merge test result: %s", err) + } + case setting.PerformanceTest: + err := JmeterTestResults(r.Ctx.Archive.File, resultPath, r.Ctx.Archive.Dir) + if err != nil { + return fmt.Errorf("failed to archive performance test result: %s", err) + } + } + + if len(r.Ctx.GinkgoTest.ArtifactPaths) > 0 { + if err := artifactsUpload(r.Ctx, r.ActiveWorkspace, r.Ctx.GinkgoTest.ArtifactPaths); err != nil { + return fmt.Errorf("failed to upload artifacts: %s", err) + } + } + + if err := r.archiveTestFiles(); err != nil { + return fmt.Errorf("failed to archive test files: %s", err) + } + + if err := r.archiveHTMLTestReportFile(); err != nil { + return fmt.Errorf("failed to archive HTML test report: %s", err) + } + } + if r.Ctx.ArtifactInfo == nil { if err := r.archiveS3Files(); err != nil { return fmt.Errorf("failed to archive S3 files: %s", err) -- Gitee From 7546f71e790567e9755d7b971f8a4297896091d7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 10:37:13 +0800 Subject: [PATCH 2076/2647] log Signed-off-by: mouuii --- .../aslan/core/common/repository/models/build.go | 6 +++--- .../core/common/repository/models/task/jenkins.go | 10 +++++----- .../workflow/service/workflow/pipeline_validation.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 8 ++++---- pkg/microservice/cron/core/service/types.go | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 55381f04..7ee52ebd 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -125,10 +125,10 @@ type JenkinsBuild struct { } type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` + Name string `bson:"name,omitempty" json:"name,omitempty"` + Value interface{} `bson:"value,omitempty" json:"value,omitempty"` Type string `bson:"type,omitempty" json:"type,omitempty"` - AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` + AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index d0681f03..5effae18 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -36,11 +36,11 @@ type JenkinsBuildArgs struct { } type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` - Type string `bson:"type,omitempty" json:"type,omitempty"` - AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + Name string `bson:"name,omitempt" json:"name,omitempt"` + Value interface{} `bson:"value,omitempt" json:"value,omitempt"` + Type string `bson:"type,omitempty" json:"type,omitempty"` + AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` } // JenkinsBuild ... diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 8db28da0..6f9b732b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -685,7 +685,7 @@ func SetCandidateRegistry(payload *commonmodels.ConfigPayload, log *zap.SugaredL reg, _, err := commonservice.FindDefaultRegistry(true, log) if err != nil { - log.Errorf("can't find default candidate registry: %v", err) + log.Errorf("can't find default candidate registry: %s", err) return e.ErrFindRegistry.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4c4a222c..22ac82f5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2211,7 +2211,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("can't find product by envName:%s error msg: %v", taskOpt.EnvName, err) + log.Errorf("can't find product by envName:%s error msg: %s", taskOpt.EnvName, err) return e.ErrFindRegistry.AddDesc(err.Error()) } @@ -2232,7 +2232,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } else { reg, _, err = commonservice.FindDefaultRegistry(true, log) if err != nil { - log.Errorf("can't find default candidate registry: %v", err) + log.Errorf("can't find default candidate registry: %s", err) return e.ErrFindRegistry.AddDesc(err.Error()) } } @@ -2326,7 +2326,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err opt := &commonrepo.ProductFindOptions{EnvName: taskOpt.EnvName, Name: taskOpt.Task.ProductName} exitedProd, err := commonrepo.NewProductColl().Find(opt) if err != nil { - log.Errorf("can't find product by envName:%s error msg: %v", taskOpt.EnvName, err) + log.Errorf("can't find product by envName:%s error msg: %s", taskOpt.EnvName, err) return e.ErrFindRegistry.AddDesc(err.Error()) } @@ -2341,7 +2341,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } else { reg, _, err = commonservice.FindDefaultRegistry(true, log) if err != nil { - log.Errorf("can't find default candidate registry: %v", err) + log.Errorf("can't find default candidate registry: %s", err) return e.ErrFindRegistry.AddDesc(err.Error()) } } diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 532ef7bc..d7de8e7c 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -307,8 +307,8 @@ type JenkinsBuildArgs struct { } type JenkinsBuildParam struct { - Name string `json:"name"` - Value interface{} `json:"value"` + Name string `bson:"name,omitempty" json:"name,omitempty"` + Value interface{} `bson:"value,omitempty" json:"value,omitempty"` Type string `bson:"type,omitempty" json:"type,omitempty"` ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` } -- Gitee From b683198e3c2321be6aa14636cf648593a53821e2 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 10:38:08 +0800 Subject: [PATCH 2077/2647] log Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 22ac82f5..c772375a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2225,7 +2225,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err if len(exitedProd.RegistryID) > 0 { reg, _, err = commonservice.FindRegistryById(exitedProd.RegistryID, true, log) if err != nil { - log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", + log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%s", exitedProd.RegistryID, err) return e.ErrFindRegistry.AddDesc(err.Error()) } @@ -2334,7 +2334,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err if len(exitedProd.RegistryID) > 0 { reg, _, err = commonservice.FindRegistryById(exitedProd.RegistryID, true, log) if err != nil { - log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%v", + log.Errorf("service.EnsureRegistrySecret: failed to find registry: %s error msg:%s", exitedProd.RegistryID, err) return e.ErrFindRegistry.AddDesc(err.Error()) } -- Gitee From f844bcfe62d773f136c111325cd231ccc83068ea Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 10:58:52 +0800 Subject: [PATCH 2078/2647] added replace env function Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/job.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index a15ef4b2..0636da75 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -282,6 +282,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ctx.Repos = append(ctx.Repos, repo) } + envmaps := make(map[string]string) for _, ev := range b.JobCtx.EnvVars { val := fmt.Sprintf("%s=%s", ev.Key, ev.Value) if ev.IsCredential { @@ -289,6 +290,14 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName } else { ctx.Envs = append(ctx.Envs, val) } + envmaps[ev.Key] = ev.Value + } + + if b.JobCtx.UploadEnabled { + for _, upload := range b.JobCtx.UploadInfo { + upload.FilePath = replaceEnvWithValue(upload.FilePath, envmaps) + upload.DestinationPath = replaceEnvWithValue(upload.DestinationPath, envmaps) + } } //Support multi build steps @@ -1045,3 +1054,12 @@ func genRegistrySecretName(reg *task.RegistryNamespace) (string, error) { return secretName, nil } + +func replaceEnvWithValue(str string, envs map[string]string) string { + ret := str + for key, value := range envs { + strKey := fmt.Sprintf("$%s", key) + ret = strings.ReplaceAll(str, strKey, value) + } + return ret +} -- Gitee From 30a19278dd3abed59b84aa488320aa5aa95808fd Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 12:09:20 +0800 Subject: [PATCH 2079/2647] bug Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 14 +++++++++++++- .../policy/core/service/bundle/opa_bundle.go | 6 +++--- .../service/bundle/resource_endpoint_mapping.go | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index e15694a4..b54c5273 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -170,14 +170,23 @@ rules: endpoint: "/api/aslan/environment/image/deployment/?*" resourceType: "Environment" idRegex: "/api/aslan/environment/image/deployment/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" - method: POST resourceType: "Environment" idRegex: "/api/aslan/environment/image/statefulset/([\\w\\W]+?)$" endpoint: "/api/aslan/environment/image/statefulset/?*" + matchAttributes: + - key: "production" + value: "false" - method: DELETE endpoint: "/api/aslan/environment/kube/?*/pods/?*" resourceType: "Environment" idRegex: "/api/aslan/environment/kube/([\\w\\W]+?)/pods" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/configmaps" - method: POST @@ -202,4 +211,7 @@ rules: - method: GET endpoint: "/api/podexec/?*/?*/?*/?*/podExec/?*" idRegex: "/podExec/([\\w\\W]+?)$" - resourceType: "Environment" \ No newline at end of file + resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" \ No newline at end of file diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 5baac302..62539c41 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -243,7 +243,7 @@ func (o policyBindings) Less(i, j int) bool { func generateOPARoles(roles []*models.Role, policyMetas []*models.PolicyMeta) *opaRoles { data := &opaRoles{} - resourceMappings := getResourceActionMappings(policyMetas) + resourceMappings := getResourceActionMappings(false, policyMetas) for _, ro := range roles { verbAttrMap := make(map[string]sets.String) @@ -304,7 +304,7 @@ func generateOPARoles(roles []*models.Role, policyMetas []*models.PolicyMeta) *o func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.PolicyMeta) *opaPolicies { data := &opaPolicies{} - resourceMappings := getResourceActionMappings(policyMetas) + resourceMappings := getResourceActionMappings(true, policyMetas) for _, policy := range policies { verbAttrMap := make(map[string]sets.String) @@ -446,7 +446,7 @@ func generateOPAExemptionURLs(policies []*models.PolicyMeta) *exemptionURLs { } sort.Sort(data.Privileged) - resourceMappings := getResourceActionMappings(policies) + resourceMappings := getResourceActionMappings(false, policies) for _, resourceMappings := range resourceMappings { for _, rs := range resourceMappings { for _, r := range rs { diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index bcc17fad..e5d43f2b 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -109,7 +109,7 @@ func (m resourceActionMappings) GetPolicyRules(resource string, actions []string return res } -func getResourceActionMappings(policies []*models.PolicyMeta) resourceActionMappings { +func getResourceActionMappings(isPolicy bool, policies []*models.PolicyMeta) resourceActionMappings { data := make(resourceActionMappings) for _, p := range policies { if _, ok := data[p.Resource]; !ok { @@ -120,7 +120,7 @@ func getResourceActionMappings(policies []*models.PolicyMeta) resourceActionMapp for _, ar := range r.Rules { var as []*Attribute for _, a := range ar.MatchAttributes { - if a.Key == "production" { + if a.Key == "production" && isPolicy { continue } as = append(as, &Attribute{Key: a.Key, Value: a.Value}) -- Gitee From 2147755bf6b98fa44c95cf6695d0b60687ffac8c Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 12:29:33 +0800 Subject: [PATCH 2080/2647] bug Signed-off-by: mouuii --- .../aslan/core/system/service/jenkins.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/jenkins.go b/pkg/microservice/aslan/core/system/service/jenkins.go index 65bae1a8..320dd1f4 100644 --- a/pkg/microservice/aslan/core/system/service/jenkins.go +++ b/pkg/microservice/aslan/core/system/service/jenkins.go @@ -37,6 +37,7 @@ type JenkinsArgs struct { type JenkinsBuildArgs struct { Name string `json:"name"` Value interface{} `json:"value"` + Type string `json:"type"` } func CreateJenkinsIntegration(args *commonmodels.JenkinsIntegration, log *zap.SugaredLogger) error { @@ -130,10 +131,17 @@ func ListJobBuildArgs(jobName string, log *zap.SugaredLogger) ([]*JenkinsBuildAr } jenkinsBuildArgsResp := make([]*JenkinsBuildArgs, 0) for _, paramDefinition := range paramDefinitions { - jenkinsBuildArgsResp = append(jenkinsBuildArgsResp, &JenkinsBuildArgs{ + arg := &JenkinsBuildArgs{ Name: paramDefinition.DefaultParameterValue.Name, Value: paramDefinition.DefaultParameterValue.Value, - }) + Type: paramDefinition.Type, + } + if paramDefinition.Type == "ChoiceParameterDefinition" { + arg.Type = "choice" + } else { + arg.Type = "string" + } + jenkinsBuildArgsResp = append(jenkinsBuildArgsResp, arg) } return jenkinsBuildArgsResp, nil } -- Gitee From 2b79445c8c0c151e8279b403d625ca7a20ae1969 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 12:33:32 +0800 Subject: [PATCH 2081/2647] debug Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/types/task/build.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 2b00e18e..d04c38f9 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -205,8 +205,8 @@ type JobCtx struct { } type ObjectStoragePathDetail struct { - FilePath string `json:"file_path"` - DestinationPath string `json:"dest_path"` + FilePath string `json:"file_path" yaml:"file_path"` + DestinationPath string `json:"dest_path" yaml:"dest_path"` } type ObjectStorageInfo struct { -- Gitee From ffe60c11db08deb5414110df0e5c1176504a5477 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 13:53:19 +0800 Subject: [PATCH 2082/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 0636da75..693b7417 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -296,6 +296,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.UploadEnabled { for _, upload := range b.JobCtx.UploadInfo { upload.FilePath = replaceEnvWithValue(upload.FilePath, envmaps) + fmt.Println("replaced filepath is: ", upload.FilePath) upload.DestinationPath = replaceEnvWithValue(upload.DestinationPath, envmaps) } } -- Gitee From 61c4e72b4cffbffe5fbb97bd900805aa32b4b486 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 14:15:07 +0800 Subject: [PATCH 2083/2647] more debug logs Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 693b7417..701c7a42 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -1060,6 +1060,7 @@ func replaceEnvWithValue(str string, envs map[string]string) string { ret := str for key, value := range envs { strKey := fmt.Sprintf("$%s", key) + fmt.Printf("replacing %s with %s\n", strKey, value) ret = strings.ReplaceAll(str, strKey, value) } return ret -- Gitee From bfe55a41b28ab7f3217772ee245c54881b136ebf Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 14:21:43 +0800 Subject: [PATCH 2084/2647] even more debug logs Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 701c7a42..f12f6c16 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -1060,7 +1060,7 @@ func replaceEnvWithValue(str string, envs map[string]string) string { ret := str for key, value := range envs { strKey := fmt.Sprintf("$%s", key) - fmt.Printf("replacing %s with %s\n", strKey, value) + fmt.Printf("replacing %s in string [%s] with %s\n", strKey, ret, value) ret = strings.ReplaceAll(str, strKey, value) } return ret -- Gitee From d00259fa43b8961c850fec1b064f89f7f816e362 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 14:31:17 +0800 Subject: [PATCH 2085/2647] delete debug logs Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index f12f6c16..116e96b3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -296,7 +296,6 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.UploadEnabled { for _, upload := range b.JobCtx.UploadInfo { upload.FilePath = replaceEnvWithValue(upload.FilePath, envmaps) - fmt.Println("replaced filepath is: ", upload.FilePath) upload.DestinationPath = replaceEnvWithValue(upload.DestinationPath, envmaps) } } @@ -1060,8 +1059,7 @@ func replaceEnvWithValue(str string, envs map[string]string) string { ret := str for key, value := range envs { strKey := fmt.Sprintf("$%s", key) - fmt.Printf("replacing %s in string [%s] with %s\n", strKey, ret, value) - ret = strings.ReplaceAll(str, strKey, value) + ret = strings.ReplaceAll(ret, strKey, value) } return ret } -- Gitee From 76be6b052569f9ee306e02285ebca4ff04c515d6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 14:39:40 +0800 Subject: [PATCH 2086/2647] final tweak Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 116e96b3..adf28c10 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -295,6 +295,9 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.UploadEnabled { for _, upload := range b.JobCtx.UploadInfo { + // since the frontend won't change the file path, the backend will have to add it + upload.FilePath = fmt.Sprintf("$WORKSPACE/%s", upload.FilePath) + // then we replace it with our env variables upload.FilePath = replaceEnvWithValue(upload.FilePath, envmaps) upload.DestinationPath = replaceEnvWithValue(upload.DestinationPath, envmaps) } -- Gitee From cfb7d1724612eb355ce8c0561e67540b1e674bbc Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 16:04:34 +0800 Subject: [PATCH 2087/2647] name Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/router.go | 4 ++-- pkg/microservice/aslan/core/workflow/handler/router.go | 2 +- pkg/microservice/podexec/server/server.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 95042aec..587c7271 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -66,8 +66,8 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- image := router.Group("image") { - image.POST("/deployment/:env", gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) - image.POST("/statefulset/:env", gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) + image.POST("/deployment/:envName", gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) + image.POST("/statefulset/:envName", gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) } // 查询环境创建时的服务和变量信息 diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 8c79d02a..e7c2db2f 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -104,7 +104,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflow.POST("/:productName/auto", AutoCreateWorkflow) workflow.POST("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateWorkflow) - workflow.PUT("/:workflow", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) + workflow.PUT("/:workflowName", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) workflow.GET("", ListWorkflows) workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) diff --git a/pkg/microservice/podexec/server/server.go b/pkg/microservice/podexec/server/server.go index 3efc7bd8..c326fe3b 100644 --- a/pkg/microservice/podexec/server/server.go +++ b/pkg/microservice/podexec/server/server.go @@ -42,7 +42,7 @@ func Serve(ctx context.Context) error { router.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(map[string]string{"message": "success"}) }) - router.HandleFunc("/api/{productName}/{namespace}/{podName}/{containerName}/podExec/{env}", service.ServeWs) + router.HandleFunc("/api/{productName}/{namespace}/{podName}/{containerName}/podExec/{envName}", service.ServeWs) server := &http.Server{ Addr: "0.0.0.0:27000", -- Gitee From 106360a170b4ba72bd8c12dc8ba132fabc3b4ae6 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 16:18:55 +0800 Subject: [PATCH 2088/2647] image name Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index c772375a..505e0919 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2353,8 +2353,9 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err rule := project.CustomImageRule.JenkinsRule if rule != "" { va := commonservice.Variable{ - SERVICE: t.ServiceName, - TIMESTAMP: time.Now().Format("20060102150405"), + SERVICE: t.ServiceName, + TIMESTAMP: time.Now().Format("20060102150405"), + IMAGE_NAME: t.Image, } tm, _ := gotempl.New("jenkins").Parse(rule) var replaceRuleVariable = gotempl.Must(tm, err) -- Gitee From d0524454b0164e8c4c558ffc9b8bfab38f978502 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 7 Apr 2022 17:30:09 +0800 Subject: [PATCH 2089/2647] Test workflow supports gitee code source to trigger webhook Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gitee.go | 65 ++++- .../service/webhook/gitee_testing_task.go | 274 ++++++++++++++++++ 2 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index a172312e..0f8ff4ee 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "net/http" + "sync" "time" "github.com/hashicorp/go-multierror" @@ -49,6 +50,7 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * baseURI := config.SystemAddress() var errorList = &multierror.Error{} + var wg sync.WaitGroup switch event := event.(type) { case *gitee.PushEvent: @@ -63,21 +65,64 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * } commonrepo.NewWebHookUserColl().Upsert(webhookUser) } - if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { - errorList = multierror.Append(errorList, err) - } + // build webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() + + //test webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerTestByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() case *gitee.PullRequestEvent: if event.Action != "open" && event.Action != "update" { return fmt.Errorf("action %s is skipped", event.Action) } - if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { - errorList = multierror.Append(errorList, err) - } + + // build webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() + + //test webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerTestByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() case *gitee.TagPushEvent: - if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { - errorList = multierror.Append(errorList, err) - } - } + // build webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerWorkflowByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() + //test webhook + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerTestByGiteeEvent(event, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() + } + wg.Wait() return errorList.ErrorOrNil() } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go new file mode 100644 index 00000000..cf6a61de --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go @@ -0,0 +1,274 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "regexp" + "strconv" + + "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/setting" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + testingservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" + "github.com/koderover/zadig/pkg/tool/gitee" +) + +type giteePushEventMatcherForTesting struct { + log *zap.SugaredLogger + testing *commonmodels.Testing + event *gitee.PushEvent +} + +func (gpem *giteePushEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gpem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Repository.FullName { + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } + + if isRegular { + matched, err := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)) + if err != nil || !matched { + return false, nil + } + } + hookRepo.Branch = getBranchFromRef(ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + + return MatchChanges(hookRepo, changedFiles), nil + } + + return false, nil +} + +func (gpem *giteePushEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gpem.testing, + reqID: requestID, + } + + factory.Update(args) + return args +} + +type giteeTagEventMatcherForTesting struct { + log *zap.SugaredLogger + testing *commonmodels.Testing + event *gitee.TagPushEvent +} + +func (gtem giteeTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { + return false, nil + } + } + hookRepo.Branch = ev.Project.DefaultBranch + return true, nil + } + + return false, nil +} + +func (gtem giteeTagEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gtem.testing, + reqID: requestID, + } + + factory.Update(args) + return args +} + +type giteeMergeEventMatcherForTesting struct { + diffFunc giteePullRequestDiffFunc + log *zap.SugaredLogger + testing *commonmodels.Testing + event *gitee.PullRequestEvent +} + +func (gmem *giteeMergeEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { + ev := gmem.event + // TODO: match codehost + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.PullRequest.Base.Repo.FullName { + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.PullRequest.Base.Ref { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.PullRequest.Base.Ref); !matched { + return false, nil + } + } + hookRepo.Branch = ev.PullRequest.Base.Ref + if ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err + } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(hookRepo, changedFiles), nil + } + + } + return false, nil +} + +func (gmem *giteeMergeEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { + factory := &testArgsFactory{ + testing: gmem.testing, + reqID: requestID, + } + + args = factory.Update(args) + + return args +} + +func createGiteeEventMatcherForTesting( + event interface{}, diffSrv giteePullRequestDiffFunc, testing *commonmodels.Testing, log *zap.SugaredLogger, +) gitEventMatcherForTesting { + switch evt := event.(type) { + case *gitee.PushEvent: + return &giteePushEventMatcherForTesting{ + testing: testing, + log: log, + event: evt, + } + case *gitee.PullRequestEvent: + return &giteeMergeEventMatcherForTesting{ + diffFunc: diffSrv, + log: log, + event: evt, + testing: testing, + } + case *gitee.TagPushEvent: + return &giteeTagEventMatcherForTesting{ + testing: testing, + log: log, + event: evt, + } + } + + return nil +} + +func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { + testingList, err := commonrepo.NewTestingColl().List(&commonrepo.ListTestOption{}) + if err != nil { + log.Errorf("failed to list testing %v", err) + return err + } + + mErr := &multierror.Error{} + diffSrv := func(PullRequestEvent *gitee.PullRequestEvent, codehostId int) ([]string, error) { + return findChangedFilesOfPullRequestEvent(PullRequestEvent, codehostId) + } + + for _, testing := range testingList { + if testing.HookCtl != nil && testing.HookCtl.Enabled { + log.Infof("find %d hooks in testing %s", len(testing.HookCtl.Items), testing.Name) + for _, item := range testing.HookCtl.Items { + if item.TestArgs == nil { + continue + } + + // 2. match webhook + matcher := createGiteeEventMatcherForTesting(event, diffSrv, testing, log) + if matcher == nil { + continue + } + + if matches, err := matcher.Match(item.MainRepo); err != nil { + mErr = multierror.Append(mErr, err) + } else if matches { + log.Infof("event match hook %v of %s", item.MainRepo, testing.Name) + var mergeRequestID, commitID string + if ev, isPr := event.(*gitee.PullRequestEvent); isPr { + // 如果是merge request,且该webhook触发器配置了自动取消, + // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 + mergeRequestID = strconv.Itoa(ev.PullRequest.Number) + commitID = ev.PullRequest.Head.Sha + autoCancelOpt := &AutoCancelOpt{ + MergeRequestID: mergeRequestID, + CommitID: commitID, + TaskType: config.TestType, + MainRepo: item.MainRepo, + TestArgs: item.TestArgs, + } + err := AutoCancelTask(autoCancelOpt, log) + if err != nil { + log.Errorf("failed to auto cancel testing task when receive event %v due to %v ", event, err) + mErr = multierror.Append(mErr, err) + } + } + + args := matcher.UpdateTaskArgs(item.TestArgs, requestID) + args.MergeRequestID = mergeRequestID + args.CommitID = commitID + args.Source = setting.SourceFromGitee + args.CodehostID = item.MainRepo.CodehostID + args.RepoOwner = item.MainRepo.RepoOwner + args.RepoName = item.MainRepo.RepoName + + // 3. create task with args + if resp, err := testingservice.CreateTestTask(args, log); err != nil { + log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) + mErr = multierror.Append(mErr, err) + } else { + log.Infof("succeed to create task %v", resp) + } + } else { + log.Debugf("event not matches %v", item.MainRepo) + } + } + } + } + + return mErr.ErrorOrNil() +} -- Gitee From dd68b6744a5e01a83725b52215c8ef6f98c8b96f Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 17:41:37 +0800 Subject: [PATCH 2090/2647] more varable Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 505e0919..5d2b87c1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2355,7 +2355,10 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err va := commonservice.Variable{ SERVICE: t.ServiceName, TIMESTAMP: time.Now().Format("20060102150405"), - IMAGE_NAME: t.Image, + IMAGE_NAME: taskOpt.ImageName, + PROJECT: taskOpt.Task.ProductName, + ENV_NAME: taskOpt.EnvName, + TASK_ID: strconv.FormatInt(taskOpt.Task.TaskID, 10), } tm, _ := gotempl.New("jenkins").Parse(rule) var replaceRuleVariable = gotempl.Must(tm, err) -- Gitee From 94ea05738de9e372b2bd42d9330c21a1e1f3bd34 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 18:01:13 +0800 Subject: [PATCH 2091/2647] code review fix Signed-off-by: Min Min --- .../workflow/service/workflow/workflow_task.go | 2 +- .../reaper/core/service/reaper/reaper.go | 14 ++++++++------ .../warpdrive/core/service/types/task/build.go | 1 + 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 76d60663..87544b79 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2148,7 +2148,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL if module.PostBuild.ObjectStorageUpload.Enabled { storageInfo, err := commonrepo.NewS3StorageColl().Find(module.PostBuild.ObjectStorageUpload.ObjectStorageID) if err != nil { - log.Infof("Failed to get basic storage info for uploading, the error is %s", err) + log.Errorf("Failed to get basic storage info for uploading, the error is %s", err) return nil, err } build.JobCtx.UploadStorageInfo = &taskmodels.ObjectStorageInfo{ diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 27e8f0a8..32620acc 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -19,7 +19,6 @@ package reaper import ( "bytes" "fmt" - "github.com/koderover/zadig/pkg/tool/s3" "io" "io/ioutil" "os" @@ -29,6 +28,7 @@ import ( "strings" "time" + "github.com/koderover/zadig/pkg/tool/s3" "gopkg.in/yaml.v3" "github.com/koderover/zadig/pkg/microservice/reaper/config" @@ -452,11 +452,13 @@ func (r *Reaper) AfterExec() error { return fmt.Errorf("failed to read file information in directory: [%s], the error is: %s", upload.FilePath, err) } for _, file := range files { - key := filepath.Join(upload.DestinationPath, file.Name()) - originalFilePath := filepath.Join(upload.FilePath, file.Name()) - err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, originalFilePath, key) - if err != nil { - fmt.Printf("Failed to upload [%s] to key [%s] on s3, the error is: %s", originalFilePath, key, err) + if !file.IsDir() { + key := filepath.Join(upload.DestinationPath, file.Name()) + originalFilePath := filepath.Join(upload.FilePath, file.Name()) + err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, originalFilePath, key) + if err != nil { + fmt.Printf("Failed to upload [%s] to key [%s] on s3, the error is: %s", originalFilePath, key, err) + } } } } else { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index d04c38f9..f524faa4 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -18,6 +18,7 @@ package task import ( "fmt" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" -- Gitee From 456db0dfa606cfcdaa98eef8a4dd43a655542556 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 7 Apr 2022 18:48:58 +0800 Subject: [PATCH 2092/2647] add vauels sync logic Signed-off-by: allenshen --- .../repository/models/template/product.go | 8 +- .../repository/mongodb/template/product.go | 4 + .../core/environment/handler/environment.go | 18 +++ .../core/environment/handler/revision.go | 5 +- .../aslan/core/environment/handler/router.go | 1 + .../core/environment/service/environment.go | 66 +++++++++++ .../core/environment/service/renderset.go | 61 ++++++++++ .../core/environment/service/revision.go | 9 +- .../cron/core/service/client/env.go | 55 ++++++++- .../core/service/scheduler/schedule_env.go | 53 +++++++-- .../service/scheduler/schedule_env_update.go | 105 ++++++++++++++++++ .../service/scheduler/schedule_workflow.go | 10 +- .../cron/core/service/scheduler/scheduler.go | 34 ++++-- pkg/microservice/cron/core/service/service.go | 13 ++- pkg/setting/consts.go | 1 + pkg/util/yaml/compare.go | 44 ++++++++ 16 files changed, 452 insertions(+), 35 deletions(-) create mode 100644 pkg/microservice/cron/core/service/scheduler/schedule_env_update.go create mode 100644 pkg/util/yaml/compare.go diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index b34bd0ff..09a16310 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -96,10 +96,10 @@ type EnvRenderKV struct { } type GitRepoConfig struct { - CodehostID int `bson:"codehost_id,omitempty"` - Owner string `bson:"owner,omitempty"` - Repo string `bson:"repo,omitempty"` - Branch string `bson:"branch,omitempty"` + CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id"` + Owner string `bson:"owner,omitempty" json:"owner"` + Repo string `bson:"repo,omitempty" json:"repo"` + Branch string `bson:"branch,omitempty" json:"branch"` } type CustomYaml struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 19d8f858..553452ff 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -174,6 +174,7 @@ type ProductListOpt struct { IsOpensource string ContainSharedServices []*template.ServiceInfo BasicFacility string + DeployType string } // ListWithOption ... @@ -190,6 +191,9 @@ func (c *ProductColl) ListWithOption(opt *ProductListOpt) ([]*template.Product, if opt.BasicFacility != "" { query["product_feature.basic_facility"] = opt.BasicFacility } + if opt.DeployType != "" { + query["product_feature.deploy_type"] = opt.DeployType + } cursor, err := c.Collection.Find(context.TODO(), query) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index c47b6e9e..ca87be9b 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -367,6 +367,24 @@ func EstimatedValues(c *gin.Context) { ctx.Resp, ctx.Err = service.GeneEstimatedValues(projectName, envName, serviceName, c.Query("scene"), c.Query("format"), arg, ctx.Logger) } +func SyncHelmProductRenderset(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be empty!") + return + } + envName := c.Param("name") + if envName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") + return + } + + ctx.Err = service.SyncHelmProductEnvironment(projectName, envName) +} + func UpdateHelmProductRenderset(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/environment/handler/revision.go b/pkg/microservice/aslan/core/environment/handler/revision.go index fa35d689..c8cba110 100644 --- a/pkg/microservice/aslan/core/environment/handler/revision.go +++ b/pkg/microservice/aslan/core/environment/handler/revision.go @@ -28,8 +28,9 @@ func ListProductsRevision(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() // trigger by cron service trigger := c.Query("basicFacility") - if trigger != "" { - ctx.Resp, ctx.Err = service.ListProductsRevisionByFacility(c.Query("basicFacility"), ctx.Logger) + deployType := c.Query("deployType") + if trigger != "" || deployType != "" { + ctx.Resp, ctx.Err = service.ListProductsRevisionByOption(trigger, deployType, ctx.Logger) return } ctx.Resp, ctx.Err = service.ListProductsRevision(c.Query("projectName"), c.Query("envName"), ctx.Logger) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index aa190b09..98e02022 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -103,6 +103,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.PUT("/:name/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) environments.POST("/:name/estimated-values", EstimatedValues) environments.PUT("/:name/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) + environments.PUT("/:name/syncVariables", gin2.UpdateOperationLogStatus, SyncHelmProductRenderset) environments.GET("/:name/helmChartVersions", GetHelmChartVersions) environments.GET("/:name/productInfo", GetProductInfo) environments.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProduct) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index a1d2ea36..86a3548b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1386,6 +1386,72 @@ func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml return ret } +func SyncHelmProductEnvironment(productName, envName string) error { + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) + return err + } + opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil || productRenderset == nil { + if err != nil { + log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) + } + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) + } + + updatedRCMap := make(map[string]*templatemodels.RenderChart) + + changed, defaultValues, err := SyncYamlFromSource(productRenderset.YamlData, productRenderset.DefaultValues) + if err != nil { + return err + } + if changed { + productRenderset.DefaultValues = defaultValues + for _, curRenderChart := range productRenderset.ChartInfos { + updatedRCMap[curRenderChart.ServiceName] = curRenderChart + } + } + for _, chartInfo := range productRenderset.ChartInfos { + if chartInfo.OverrideYaml == nil { + continue + } + changed, values, err := SyncYamlFromSource(chartInfo.OverrideYaml, chartInfo.OverrideYaml.YamlContent) + if err != nil { + return err + } + if changed { + chartInfo.OverrideYaml.YamlContent = values + updatedRCMap[chartInfo.ServiceName] = chartInfo + } + } + if len(updatedRCMap) == 0 { + return nil + } + + // content of values.yaml changed, environment will be updated + updatedRcList := make([]*templatemodels.RenderChart, 0) + for _, updatedRc := range updatedRCMap { + updatedRcList = append(updatedRcList, updatedRc) + } + + logger := log.SugaredLogger() + err = UpdateHelmProductVariable(productName, envName, "timer", "000000000000000000", updatedRcList, productRenderset, logger) + if err != nil { + return err + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) + return err + } + return ensureKubeEnv(product.Namespace, product.RegistryID, kubeClient, logger) +} + func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 8b665023..b486290c 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "encoding/json" "fmt" "sync" @@ -24,9 +25,12 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) @@ -44,6 +48,63 @@ type YamlContentRequestArg struct { ValuesPaths string `json:"valuesPaths" form:"valuesPaths"` } +func fromGitRepo(source string) bool { + if source == "" { + return true + } + if source == setting.SourceFromGitRepo { + return true + } + return false +} + +func unMarshalJson(source interface{}) (*models.CreateFromRepo, error) { + bs, err := json.Marshal(source) + if err != nil { + return nil, err + } + ret := &models.CreateFromRepo{} + err = json.Unmarshal(bs, ret) + return ret, err +} + +// SyncYamlFromSource sync values.yaml from source +// NOTE currently only support gitHub and gitlab +func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (bool, string, error) { + if yamlData == nil { + return false, "", nil + } + if !fromGitRepo(yamlData.Source) { + return false, "", nil + } + + sourceDetail, err := unMarshalJson(yamlData.SourceDetail) + if err != nil { + return false, "", err + } + if sourceDetail.GitRepoConfig == nil { + log.Warnf("git repo config is nil") + return false, "", nil + } + repoConfig := sourceDetail.GitRepoConfig + + valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ + CodehostID: repoConfig.CodehostID, + Owner: repoConfig.Owner, + Repo: repoConfig.Repo, + Path: sourceDetail.LoadPath, + Branch: repoConfig.Branch, + }) + if err != nil { + return false, "", err + } + equal, err := yamlutil.CheckEqual(valuesYAML, []byte(curValue)) + if err != nil || equal { + return false, "", err + } + return true, string(valuesYAML), nil +} + func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*DefaultValuesResp, error) { ret := &DefaultValuesResp{} diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 48e38310..70065f26 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -51,8 +51,8 @@ func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) ( return prodRevs, nil } -// ListProductsRevisionByFacility called by service cron -func ListProductsRevisionByFacility(basicFacility string, log *zap.SugaredLogger) ([]*ProductRevision, error) { +// ListProductsRevisionByOption called by service cron +func ListProductsRevisionByOption(basicFacility string, deployType string, log *zap.SugaredLogger) ([]*ProductRevision, error) { var ( err error prodRevs = make([]*ProductRevision, 0) @@ -60,7 +60,10 @@ func ListProductsRevisionByFacility(basicFacility string, log *zap.SugaredLogger projectNames = make([]string, 0) ) - temProducts, err := templaterepo.NewProductColl().ListWithOption(&templaterepo.ProductListOpt{BasicFacility: basicFacility}) + temProducts, err := templaterepo.NewProductColl().ListWithOption(&templaterepo.ProductListOpt{ + BasicFacility: basicFacility, + DeployType: deployType, + }) if err != nil { log.Errorf("Collection.TemplateProduct.List error: %s", err) return prodRevs, e.ErrListProducts.AddDesc(err.Error()) diff --git a/pkg/microservice/cron/core/service/client/env.go b/pkg/microservice/cron/core/service/client/env.go index 43fdbc85..2e7400c6 100644 --- a/pkg/microservice/cron/core/service/client/env.go +++ b/pkg/microservice/cron/core/service/client/env.go @@ -29,14 +29,18 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" ) -func (c *Client) ListEnvs(log *zap.SugaredLogger) ([]*service.ProductRevision, error) { +type EvnListOption struct { + BasicFacility string + DeployType string +} +func (c *Client) ListEnvs(log *zap.SugaredLogger, option *EvnListOption) ([]*service.ProductRevision, error) { var ( err error resp = make([]*service.ProductRevision, 0) ) - url := fmt.Sprintf("%s/environment/revision/products?basicFacility=%s", c.APIBase, "cloud_host") + url := fmt.Sprintf("%s/environment/revision/products?basicFacility=%s&deployType=%s", c.APIBase, option.BasicFacility, option.DeployType) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("ListEnvs new http request error: %v", err) @@ -80,10 +84,36 @@ func (c *Client) GetEnvService(productName, envName string, log *zap.SugaredLogg } } } - return envObj, errors.WithMessage(err, "failed to get service") } +func (c *Client) GetRenderset(name string, revision int64, log *zap.SugaredLogger) (*service.ProductRenderset, error) { + var ( + err error + rendersetObj = new(service.ProductRenderset) + ) + url := fmt.Sprintf("%s/project/renders/render/%s/revision/%d", c.APIBase, name, revision) + request, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Errorf("GetService new http request error: %v", err) + return nil, err + } + + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + var body []byte + body, err = ioutil.ReadAll(ret.Body) + if err == nil { + if err = json.Unmarshal(body, &rendersetObj); err == nil { + return rendersetObj, nil + } + } + } + + return rendersetObj, errors.WithMessage(err, "failed to get renderset") +} + func (c *Client) GetService(serviceName, productName, serviceType string, revision int64, log *zap.SugaredLogger) (*service.Service, error) { var ( err error @@ -137,6 +167,25 @@ func (c *Client) UpdateService(args *service.ServiceTmplObject, log *zap.Sugared return nil } +func (c *Client) SyncEnvVariables(productName, envName string, log *zap.SugaredLogger) error { + url := fmt.Sprintf("%s/environment/environments/%s/syncVariables?projectName=%s", c.APIBase, envName, productName) + request, err := http.NewRequest("PUT", url, nil) + if err != nil { + log.Errorf("SyncEnvVariables new http request error: %v", err) + return err + } + + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + _, err := ioutil.ReadAll(ret.Body) + if err != nil { + return errors.WithMessage(err, "failed to sync product variables ") + } + } + return nil +} + func (c *Client) GetHostInfo(hostID string, log *zap.SugaredLogger) (*service.PrivateKey, error) { var ( err error diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index 92f36ceb..56e3c6c6 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -33,18 +33,19 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/cron/core/service" + "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" ) // UpsertEnvServiceScheduler ... func (c *CronClient) UpsertEnvServiceScheduler(log *zap.SugaredLogger) { - envs, err := c.AslanCli.ListEnvs(log) + envs, err := c.AslanCli.ListEnvs(log, &client.EvnListOption{BasicFacility: setting.BasicFacilityCVM}) if err != nil { log.Error(err) return } //当前的环境数据和上次做比较,如果环境有删除或者环境中的服务有删除,要清理掉定时器 - c.compareProductRevision(envs, log) + c.comparePMProductRevision(envs, log) log.Info("start init env scheduler..") taskMap := make(map[string]bool) @@ -195,6 +196,7 @@ func BuildScheduledEnvJob(scheduler *gocron.Scheduler, healthCheck *service.PmHe } return scheduler.Every(interval).Seconds() } + func runProbe(healthCheck *service.PmHealthCheck, address string, log *zap.SugaredLogger) (string, error) { var ( message string @@ -309,28 +311,59 @@ func formatURL(protocol, address, path string, port int) (string, error) { return fmt.Sprintf("%s://%s:%d/%s", protocol, address, port, path), nil } -// 先比较环境,再比较服务 -func (c *CronClient) compareProductRevision(currentProductRevisions []*service.ProductRevision, log *zap.SugaredLogger) { - if len(c.lastProductRevisions) == 0 { - c.lastProductRevisions = currentProductRevisions +func buildEnvNameKey(productRevision *service.ProductRevision) string { + return "helm-values-sync-" + productRevision.ProductName + "-" + productRevision.EnvName +} + +func (c *CronClient) compareHelmProductEnvRevision(currentProductRevisions []*service.ProductRevision, log *zap.SugaredLogger) { + if len(c.lastHelmProductRevisions) == 0 { + c.lastHelmProductRevisions = currentProductRevisions + return + } + deleteProductRevisions := make([]*service.ProductRevision, 0) + curEnvSet := sets.NewString() + for _, r := range currentProductRevisions { + curEnvSet.Insert(buildEnvNameKey(r)) + } + for _, lastProductRevision := range c.lastHelmProductRevisions { + if !curEnvSet.Has(buildEnvNameKey(lastProductRevision)) { + deleteProductRevisions = append(deleteProductRevisions, lastProductRevision) + } + } + // delete related schedulers when env is deleted + for _, env := range deleteProductRevisions { + envKey := buildEnvNameKey(env) + if _, ok := c.SchedulerController[envKey]; ok { + c.SchedulerController[envKey] <- true + } + if _, ok := c.Schedulers[envKey]; ok { + c.Schedulers[envKey].Clear() + delete(c.Schedulers, envKey) + } + } + c.lastHelmProductRevisions = currentProductRevisions +} + +// compare environments and then services +func (c *CronClient) comparePMProductRevision(currentProductRevisions []*service.ProductRevision, log *zap.SugaredLogger) { + if len(c.lastPMProductRevisions) == 0 { + c.lastPMProductRevisions = currentProductRevisions return } deleteProductRevisions := make([]*service.ProductRevision, 0) lastProductSvcRevisionMap := make(map[string][]*service.SvcRevision) currentProductSvcRevisionMap := make(map[string][]*service.SvcRevision) - for _, lastProductRevision := range c.lastProductRevisions { + for _, lastProductRevision := range c.lastPMProductRevisions { isContain := false for _, currentProductRevision := range currentProductRevisions { if currentProductRevision.ProductName == lastProductRevision.ProductName && currentProductRevision.EnvName == lastProductRevision.EnvName { - currentProductSvcRevisionMap[currentProductRevision.ProductName+"-"+currentProductRevision.EnvName] = currentProductRevision.ServiceRevisions lastProductSvcRevisionMap[lastProductRevision.ProductName+"-"+lastProductRevision.EnvName] = lastProductRevision.ServiceRevisions isContain = true break } } - if !isContain { deleteProductRevisions = append(deleteProductRevisions, lastProductRevision) } @@ -451,7 +484,7 @@ func (c *CronClient) compareProductRevision(currentProductRevisions []*service.P } } - c.lastProductRevisions = currentProductRevisions + c.lastPMProductRevisions = currentProductRevisions } const ( diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go b/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go new file mode 100644 index 00000000..92590dcd --- /dev/null +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go @@ -0,0 +1,105 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scheduler + +import ( + "github.com/jasonlvhit/gocron" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/cron/core/service" + "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" + "github.com/koderover/zadig/pkg/setting" +) + +// EnvUpdateInterval TODO interval will be set on product in future +const EnvUpdateInterval = 30 + +func needCreateSchedule(rendersetObj *service.ProductRenderset) bool { + if rendersetObj.YamlData != nil && rendersetObj.YamlData.Source == setting.SourceFromGitRepo { + return true + } + for _, chartData := range rendersetObj.ChartInfos { + if chartData.OverrideYaml != nil && chartData.OverrideYaml.Source == setting.SourceFromGitRepo { + return true + } + } + return false +} + +func (c *CronClient) UpsertEnvValueSyncScheduler(log *zap.SugaredLogger) { + envs, err := c.AslanCli.ListEnvs(log, &client.EvnListOption{DeployType: setting.HelmDeployType}) + if err != nil { + log.Error(err) + return + } + + //compare to last revision, delete related schedulers when env is deleted + c.compareHelmProductEnvRevision(envs, log) + + log.Info("start init env values sync scheduler..") + for _, env := range envs { + envObj, err := c.AslanCli.GetEnvService(env.ProductName, env.EnvName, log) + if err != nil { + log.Errorf("failed to get env data, productName:%s envName:%s err:%v", env.ProductName, env.EnvName, err) + continue + } + if envObj.Render == nil { + log.Errorf("render info of product: %s:%s is nil", env.ProductName, env.EnvName) + continue + } + + rendersetObj, err := c.AslanCli.GetRenderset(envObj.Render.Name, envObj.Render.Revision, log) + if err != nil { + log.Errorf("failed to get renderset data: %s, err:%s", envObj.Render.Name, err) + continue + } + + envKey := buildEnvNameKey(env) + if lastEnvData, ok := c.lastEnvSchedulerData[envKey]; ok { + // render not changed, no need to update scheduler + if lastEnvData.Render.Revision == rendersetObj.Revision { + continue + } + } + c.lastEnvSchedulerData[envKey] = envObj + if _, ok := c.SchedulerController[envKey]; ok { + c.SchedulerController[envKey] <- true + } + if _, ok := c.Schedulers[envKey]; ok { + c.Schedulers[envKey].Clear() + delete(c.Schedulers, envKey) + } + + if !needCreateSchedule(rendersetObj) { + continue + } + + newScheduler := gocron.NewScheduler() + newScheduler.Every(EnvUpdateInterval).Seconds().Do(c.RunScheduledEnvUpdate, env.ProductName, env.EnvName, log) + c.Schedulers[envKey] = newScheduler + log.Infof("[%s] add schedulers..", envKey) + c.SchedulerController[envKey] = c.Schedulers[envKey].Start() + } +} + +func (c *CronClient) RunScheduledEnvUpdate(productName, envName string, log *zap.SugaredLogger) { + log.Infof("start to Run ScheduledEnvUpdate, productName: %s, envName: %s", productName, envName) + err := c.AslanCli.SyncEnvVariables(productName, envName, log) + if err != nil { + log.Errorf("failed to sync variables for env: %s:%s", productName, envName) + } +} diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go index 561935a3..f7998a25 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go @@ -128,18 +128,22 @@ func (c *CronClient) UpsertWorkflowScheduler(log *zap.SugaredLogger) { // 停掉已被删除的pipeline对应的scheduler for name := range c.Schedulers { if _, ok := taskMap[name]; !ok && !ScheduleNames.Has(name) { - //排除非容器部署服务健康检查定时器 + // exclude service heath check timers if strings.HasPrefix(name, "service-") && strings.Contains(name, "pm") { continue } - // 排除测试定时任务定时器 + // exclude test timers if strings.HasPrefix(name, "test-timer-") { continue } - // 排除自由编排工作流定时器 + // exclude collie pipeline timers if strings.HasPrefix(name, "collie-pipeline-timer-") { continue } + // exclude helm env values sync timers + if strings.HasPrefix(name, "helm-values-sync-") { + continue + } log.Warnf("[%s]deleted workflow detached", name) if _, ok := c.SchedulerController[name]; ok { c.SchedulerController[name] <- true diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 69a254d5..02f18a20 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -38,15 +38,17 @@ import ( // CronClient ... type CronClient struct { - AslanCli *client.Client - CollieCli *client.CollieClient - Schedulers map[string]*gocron.Scheduler - SchedulerController map[string]chan bool - lastSchedulers map[string][]*service.Schedule - lastServiceSchedulers map[string]*service.SvcRevision - enabledMap map[string]bool - lastProductRevisions []*service.ProductRevision - log *zap.SugaredLogger + AslanCli *client.Client + CollieCli *client.CollieClient + Schedulers map[string]*gocron.Scheduler + SchedulerController map[string]chan bool + lastSchedulers map[string][]*service.Schedule + lastServiceSchedulers map[string]*service.SvcRevision + lastEnvSchedulerData map[string]*service.ProductResp + enabledMap map[string]bool + lastPMProductRevisions []*service.ProductRevision + lastHelmProductRevisions []*service.ProductRevision + log *zap.SugaredLogger } const ( @@ -74,6 +76,8 @@ const ( InitHealthCheckScheduler = "InitHealthCheckScheduler" InitHealthCheckPmHostScheduler = "InitHealthCheckPmHostScheduler" + + InitHelmEnvSyncValuesScheduler = "InitHelmEnvSyncValuesScheduler" ) // NewCronClient ... @@ -122,6 +126,7 @@ func NewCronClient() *CronClient { Schedulers: make(map[string]*gocron.Scheduler), lastSchedulers: make(map[string][]*service.Schedule), lastServiceSchedulers: make(map[string]*service.SvcRevision), + lastEnvSchedulerData: make(map[string]*service.ProductResp), SchedulerController: make(map[string]chan bool), enabledMap: make(map[string]bool), log: log.SugaredLogger(), @@ -159,6 +164,8 @@ func (c *CronClient) Init() { c.InitHealthCheckScheduler() // Timing probe host status c.InitHealthCheckPmHostScheduler() + // sync values from remote for helm envs at regular intervals + c.InitHelmEnvSyncValuesScheduler() } func (c *CronClient) InitCleanJobScheduler() { @@ -267,3 +274,12 @@ func (c *CronClient) InitHealthCheckPmHostScheduler() { c.Schedulers[InitHealthCheckPmHostScheduler].Start() } + +func (c *CronClient) InitHelmEnvSyncValuesScheduler() { + + c.Schedulers[InitHelmEnvSyncValuesScheduler] = gocron.NewScheduler() + + c.Schedulers[InitHelmEnvSyncValuesScheduler].Every(20).Seconds().Do(c.UpsertEnvValueSyncScheduler, c.log) + + c.Schedulers[InitHelmEnvSyncValuesScheduler].Start() +} diff --git a/pkg/microservice/cron/core/service/service.go b/pkg/microservice/cron/core/service/service.go index ee4ad151..e120ab5f 100644 --- a/pkg/microservice/cron/core/service/service.go +++ b/pkg/microservice/cron/core/service/service.go @@ -17,8 +17,10 @@ limitations under the License. package service import ( - "github.com/koderover/zadig/pkg/setting" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/setting" ) type SvcRevision struct { @@ -77,6 +79,15 @@ type ProductResp struct { Source string `json:"source"` } +type ProductRenderset struct { + Name string `bson:"name" json:"name"` + Revision int64 `bson:"revision" json:"revision"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + ProductTmpl string `bson:"product_tmpl" json:"product_tmpl"` + YamlData *templatemodels.CustomYaml `bson:"yaml_data,omitempty" json:"yaml_data,omitempty"` + ChartInfos []*templatemodels.RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` +} + type EnvConfig struct { EnvName string `json:"env_name"` HostIDs []string `json:"host_ids"` diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0f83e7be..04123ede 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -252,6 +252,7 @@ const ( // service from yaml template ServiceSourceTemplate = "template" SourceFromPM = "pm" + SourceFromGitRepo = "repo" ProdENV = "prod" TestENV = "test" diff --git a/pkg/util/yaml/compare.go b/pkg/util/yaml/compare.go new file mode 100644 index 00000000..cc4cb34e --- /dev/null +++ b/pkg/util/yaml/compare.go @@ -0,0 +1,44 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + "reflect" + + "sigs.k8s.io/yaml" +) + +func CheckEqual(source, target []byte) (bool, error) { + if source == nil && target == nil { + return true, nil + } + if source == nil && target != nil { + return false, nil + } + if source != nil && target == nil { + return false, nil + } + sourceMap := make(map[string]interface{}) + if err := yaml.Unmarshal(source, &sourceMap); err != nil { + return false, err + } + targetMap := make(map[string]interface{}) + if err := yaml.Unmarshal(target, &targetMap); err != nil { + return false, err + } + return reflect.DeepEqual(sourceMap, targetMap), nil +} -- Gitee From 6d16c811ca10e43879370fea2a6cf2c64b3a6b4f Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 19:38:45 +0800 Subject: [PATCH 2093/2647] moved general structure Signed-off-by: Min Min --- .../reaper/core/service/meta/types.go | 20 ++---------- .../warpdrive/core/service/taskplugin/job.go | 2 +- .../warpdrive/core/service/types/reaper.go | 6 ++-- .../core/service/types/task/build.go | 20 ++---------- pkg/types/object_storage.go | 31 +++++++++++++++++++ 5 files changed, 41 insertions(+), 38 deletions(-) create mode 100644 pkg/types/object_storage.go diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 62a8fa16..6c914ada 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -127,23 +127,9 @@ type Context struct { CacheUserDir string `yaml:"cache_user_dir"` // Upload To S3 related context - UploadEnabled bool `yaml:"upload_enabled"` - UploadStorageInfo *ObjectStorageInfo `yaml:"upload_storage_info"` - UploadInfo []*ObjectStoragePathDetail `yaml:"upload_info"` -} - -type ObjectStorageInfo struct { - Endpoint string `yaml:"endpoint"` - AK string `yaml:"AK"` - SK string `yaml:"SK"` - Bucket string `yaml:"bucket"` - Insecure bool `yaml:"insecure"` - Provider int8 `yaml:"provider"` -} - -type ObjectStoragePathDetail struct { - FilePath string `yaml:"file_path"` - DestinationPath string `yaml:"dest_path"` + UploadEnabled bool `yaml:"upload_enabled"` + UploadStorageInfo *types.ObjectStorageInfo `yaml:"upload_storage_info"` + UploadInfo []*types.ObjectStoragePathDetail `yaml:"upload_info"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index adf28c10..cc796999 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -227,7 +227,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName StorageEndpoint: pipelineTask.StorageEndpoint, AesKey: pipelineTask.ConfigPayload.AesKey, UploadEnabled: b.JobCtx.UploadEnabled, - UploadStorageInfo: &task.ObjectStorageInfo{ + UploadStorageInfo: &commontypes.ObjectStorageInfo{ Endpoint: b.JobCtx.UploadStorageInfo.Endpoint, AK: b.JobCtx.UploadStorageInfo.AK, SK: b.JobCtx.UploadStorageInfo.SK, diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 253612a0..96cf51c0 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -125,9 +125,9 @@ type Context struct { CacheUserDir string `yaml:"cache_user_dir"` // Upload To S3 related context - UploadEnabled bool `yaml:"upload_enabled"` - UploadStorageInfo *task.ObjectStorageInfo `yaml:"upload_storage_info"` - UploadInfo []*task.ObjectStoragePathDetail `yaml:"upload_info"` + UploadEnabled bool `yaml:"upload_enabled"` + UploadStorageInfo *types.ObjectStorageInfo `yaml:"upload_storage_info"` + UploadInfo []*types.ObjectStoragePathDetail `yaml:"upload_info"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index f524faa4..65f9009e 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -200,23 +200,9 @@ type JobCtx struct { PMDeployScripts string `bson:"pm_deploy_scripts,omitempty" json:"pm_deploy_scripts"` // Upload To S3 related context - UploadEnabled bool `json:"upload_enabled"` - UploadStorageInfo *ObjectStorageInfo `json:"upload_storage_info"` - UploadInfo []*ObjectStoragePathDetail `json:"upload_info"` -} - -type ObjectStoragePathDetail struct { - FilePath string `json:"file_path" yaml:"file_path"` - DestinationPath string `json:"dest_path" yaml:"dest_path"` -} - -type ObjectStorageInfo struct { - Endpoint string `json:"endpoint" yaml:"endpoint"` - AK string `json:"AK" yaml:"AK"` - SK string `json:"SK" yaml:"SK"` - Bucket string `json:"bucket" yaml:"bucket"` - Insecure bool `json:"insecure" yaml:"insecure"` - Provider int8 `json:"provider" yaml:"provider"` + UploadEnabled bool `json:"upload_enabled"` + UploadStorageInfo *types.ObjectStorageInfo `json:"upload_storage_info"` + UploadInfo []*types.ObjectStoragePathDetail `json:"upload_info"` } type SSH struct { diff --git a/pkg/types/object_storage.go b/pkg/types/object_storage.go new file mode 100644 index 00000000..ba6f88eb --- /dev/null +++ b/pkg/types/object_storage.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type ObjectStorageInfo struct { + Endpoint string `bson:"endpoint" json:"endpoint" yaml:"endpoint"` + AK string `bson:"AK" json:"AK" yaml:"AK"` + SK string `bson:"SK" json:"SK" yaml:"SK"` + Bucket string `bson:"bucket" json:"bucket" yaml:"bucket"` + Insecure bool `bson:"insecure" json:"insecure" yaml:"insecure"` + Provider int8 `bson:"provider" json:"provider" yaml:"provider"` +} + +type ObjectStoragePathDetail struct { + FilePath string `bson:"file_path" json:"file_path" yaml:"file_path"` + DestinationPath string `bson:"dest_path" json:"dest_path" yaml:"dest_path"` +} -- Gitee From 1bb3decdb22ed337e98ea86f334f4493172cab9d Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 19:45:03 +0800 Subject: [PATCH 2094/2647] stuff Signed-off-by: Min Min --- .../aslan/core/common/repository/models/build.go | 11 +++-------- .../aslan/core/common/repository/models/task/build.go | 6 +++--- .../core/workflow/service/workflow/workflow_task.go | 2 +- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index e5506845..2e7b1707 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -106,14 +106,9 @@ type FileArchive struct { } type ObjectStorageUpload struct { - Enabled bool `bson:"enabled" json:"enabled"` - ObjectStorageID string `bson:"object_storage_id" json:"object_storage_id"` - UploadDetail []*ObjectStoragePathDetail `bson:"upload_detail" json:"upload_detail"` -} - -type ObjectStoragePathDetail struct { - FilePath string `bson:"file_path" json:"file_path"` - DestinationPath string `bson:"dest_path" json:"dest_path"` + Enabled bool `bson:"enabled" json:"enabled"` + ObjectStorageID string `bson:"object_storage_id" json:"object_storage_id"` + UploadDetail []*types.ObjectStoragePathDetail `bson:"upload_detail" json:"upload_detail"` } type DockerBuild struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index f3609ab7..11a1ab1c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -170,9 +170,9 @@ type JobCtx struct { PMDeployScripts string `bson:"pm_deploy_scripts,omitempty" json:"pm_deploy_scripts"` // Upload To S3 related context - UploadEnabled bool `bson:"upload_enabled" json:"upload_enabled"` - UploadStorageInfo *ObjectStorageInfo `bson:"upload_storage_info" json:"upload_storage_info"` - UploadInfo []*models.ObjectStoragePathDetail `bson:"upload_info" json:"upload_info"` + UploadEnabled bool `bson:"upload_enabled" json:"upload_enabled"` + UploadStorageInfo *types.ObjectStorageInfo `bson:"upload_storage_info" json:"upload_storage_info"` + UploadInfo []*types.ObjectStoragePathDetail `bson:"upload_info" json:"upload_info"` } type ObjectStorageInfo struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 87544b79..dfe3eeb0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2151,7 +2151,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL log.Errorf("Failed to get basic storage info for uploading, the error is %s", err) return nil, err } - build.JobCtx.UploadStorageInfo = &taskmodels.ObjectStorageInfo{ + build.JobCtx.UploadStorageInfo = &types.ObjectStorageInfo{ Endpoint: storageInfo.Endpoint, AK: storageInfo.Ak, SK: storageInfo.Sk, -- Gitee From f3921adee13754e515ee736e06444eb9cd9889f2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 7 Apr 2022 19:47:50 +0800 Subject: [PATCH 2095/2647] remove unused struct Signed-off-by: Min Min --- .../aslan/core/common/repository/models/task/build.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/build.go b/pkg/microservice/aslan/core/common/repository/models/task/build.go index 11a1ab1c..96b4dff8 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/build.go @@ -175,15 +175,6 @@ type JobCtx struct { UploadInfo []*types.ObjectStoragePathDetail `bson:"upload_info" json:"upload_info"` } -type ObjectStorageInfo struct { - Endpoint string `json:"endpoint" bson:"endpoint"` - AK string `json:"AK" bson:"AK"` - SK string `json:"SK" bson:"SK"` - Bucket string `json:"bucket" bson:"bucket"` - Insecure bool `json:"insecure" bson:"insecure"` - Provider int8 `json:"provider" bson:"provider"` -} - type BuildStep struct { BuildType string `bson:"type" json:"type"` Scripts string `bson:"scripts" json:"scripts"` -- Gitee From 39becd26b079f8c792e2279c84e5cb86cf968ea8 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 20:20:28 +0800 Subject: [PATCH 2096/2647] review Signed-off-by: mouuii --- .../core/common/repository/models/build.go | 12 ++------ .../common/repository/models/task/jenkins.go | 13 ++------- .../core/common/repository/models/workflow.go | 4 +-- .../aslan/core/system/service/jenkins.go | 5 ++-- .../core/workflow/service/workflow/convert.go | 5 ++-- .../service/workflow/workflow_task.go | 28 +++++++++++-------- pkg/microservice/cron/core/service/types.go | 12 ++------ pkg/types/build.go | 16 +++++++++++ 8 files changed, 49 insertions(+), 46 deletions(-) create mode 100644 pkg/types/build.go diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 7ee52ebd..678dd993 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -120,16 +120,8 @@ type DockerBuild struct { } type JenkinsBuild struct { - JobName string `bson:"job_name" json:"job_name"` - JenkinsBuildParam []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` -} - -type JenkinsBuildParam struct { - Name string `bson:"name,omitempty" json:"name,omitempty"` - Value interface{} `bson:"value,omitempty" json:"value,omitempty"` - Type string `bson:"type,omitempty" json:"type,omitempty"` - AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParam []*types.JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` } type Parameter struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go index 5effae18..a329a3d5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/jenkins.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type JenkinsIntegration struct { @@ -31,16 +32,8 @@ type JenkinsIntegration struct { } type JenkinsBuildArgs struct { - JobName string `bson:"job_name" json:"job_name"` - JenkinsBuildParams []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` -} - -type JenkinsBuildParam struct { - Name string `bson:"name,omitempt" json:"name,omitempt"` - Value interface{} `bson:"value,omitempt" json:"value,omitempt"` - Type string `bson:"type,omitempty" json:"type,omitempty"` - AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParams []*types.JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` } // JenkinsBuild ... diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index b7693af7..90614218 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -354,8 +354,8 @@ type TargetArgs struct { } type JenkinsBuildArgs struct { - JobName string `bson:"job_name" json:"job_name"` - JenkinsBuildParams []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParams []*types.JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` } type BuildArgs struct { diff --git a/pkg/microservice/aslan/core/system/service/jenkins.go b/pkg/microservice/aslan/core/system/service/jenkins.go index 320dd1f4..36984329 100644 --- a/pkg/microservice/aslan/core/system/service/jenkins.go +++ b/pkg/microservice/aslan/core/system/service/jenkins.go @@ -26,6 +26,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) type JenkinsArgs struct { @@ -137,9 +138,9 @@ func ListJobBuildArgs(jobName string, log *zap.SugaredLogger) ([]*JenkinsBuildAr Type: paramDefinition.Type, } if paramDefinition.Type == "ChoiceParameterDefinition" { - arg.Type = "choice" + arg.Type = string(types.Choice) } else { - arg.Type = "string" + arg.Type = string(types.Str) } jenkinsBuildArgsResp = append(jenkinsBuildArgsResp, arg) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 55f07dc9..ebee40b9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -28,6 +28,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) func ConvertQueueToTask(queueTask *commonmodels.Queue) *task.Task { @@ -161,9 +162,9 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z return nil, e.ErrConvertSubTasks.AddErr(err) } - jenkinsBuildParams := make([]*task.JenkinsBuildParam, 0) + jenkinsBuildParams := make([]*types.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range jenkinsBuildOption.JenkinsBuildArgs.JenkinsBuildParams { - jenkinsBuildParams = append(jenkinsBuildParams, &task.JenkinsBuildParam{ + jenkinsBuildParams = append(jenkinsBuildParams, &types.JenkinsBuildParam{ Name: jenkinsBuildParam.Name, Value: jenkinsBuildParam.Value, AutoGenerate: jenkinsBuildParam.AutoGenerate, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 5d2b87c1..1bd881d3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -110,9 +110,9 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr } if moBuild.JenkinsBuild != nil { - jenkinsBuildParams := make([]*commonmodels.JenkinsBuildParam, 0) + jenkinsBuildParams := make([]*types.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range moBuild.JenkinsBuild.JenkinsBuildParam { - jenkinsBuildParams = append(jenkinsBuildParams, &commonmodels.JenkinsBuildParam{ + jenkinsBuildParams = append(jenkinsBuildParams, &types.JenkinsBuildParam{ Name: jenkinsBuildParam.Name, Value: jenkinsBuildParam.Value, Type: jenkinsBuildParam.Type, @@ -403,9 +403,9 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) } if moBuild.JenkinsBuild != nil { - jenkinsBuildParams := make([]*commonmodels.JenkinsBuildParam, 0) + jenkinsBuildParams := make([]*types.JenkinsBuildParam, 0) for _, jenkinsBuildParam := range moBuild.JenkinsBuild.JenkinsBuildParam { - jenkinsBuildParams = append(jenkinsBuildParams, &commonmodels.JenkinsBuildParam{ + jenkinsBuildParams = append(jenkinsBuildParams, &types.JenkinsBuildParam{ Name: jenkinsBuildParam.Name, Value: jenkinsBuildParam.Value, Type: jenkinsBuildParam.Type, @@ -2360,10 +2360,18 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err ENV_NAME: taskOpt.EnvName, TASK_ID: strconv.FormatInt(taskOpt.Task.TaskID, 10), } - tm, _ := gotempl.New("jenkins").Parse(rule) + tm, err := gotempl.New("jenkins").Parse(rule) + if err != nil { + log.Errorf("Parse template err:%s", err) + return err + } var replaceRuleVariable = gotempl.Must(tm, err) payload := bytes.NewBufferString("") - _ = replaceRuleVariable.Execute(payload, va) + err = replaceRuleVariable.Execute(payload, va) + if err != nil { + log.Errorf("Execute template err:%s", err) + return err + } image = GetImage(reg, payload.String()) } else { image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Format("20060102150405"))) @@ -2371,11 +2379,9 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err t.JenkinsBuildArgs.JenkinsBuildParams[i].Value = image break - } else { - if value, ok := jenkinsBuildParams.Value.(string); ok { - image = value - break - } + } else if value, ok := jenkinsBuildParams.Value.(string); ok { + image = value + break } } if image == "" || !strings.Contains(image, ":") { diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index d7de8e7c..0e19ba1a 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -24,6 +24,7 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) // ScheduleType 触发模式 @@ -302,15 +303,8 @@ type TargetArgs struct { } type JenkinsBuildArgs struct { - JobName string `bson:"job_name" json:"job_name"` - JenkinsBuildParams []*JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` -} - -type JenkinsBuildParam struct { - Name string `bson:"name,omitempty" json:"name,omitempty"` - Value interface{} `bson:"value,omitempty" json:"value,omitempty"` - Type string `bson:"type,omitempty" json:"type,omitempty"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParams []*types.JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` } type BuildArgs struct { diff --git a/pkg/types/build.go b/pkg/types/build.go new file mode 100644 index 00000000..2aad3d39 --- /dev/null +++ b/pkg/types/build.go @@ -0,0 +1,16 @@ +package types + +type JenkinsBuildParam struct { + Name string `bson:"name,omitempty" json:"name,omitempty"` + Value interface{} `bson:"value,omitempty" json:"value,omitempty"` + Type JenkinsParamType `bson:"type,omitempty" json:"type,omitempty"` + AutoGenerate bool `bson:"auto_generate,omitempty" json:"auto_generate,omitempty"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` +} + +type JenkinsParamType string + +const ( + Str JenkinsParamType = "string" + Choice JenkinsParamType = "choice" +) -- Gitee From 9e911fe69afa7135892fee15376918376d9b29fe Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 7 Apr 2022 20:27:05 +0800 Subject: [PATCH 2097/2647] copy Signed-off-by: mouuii --- pkg/types/build.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/types/build.go b/pkg/types/build.go index 2aad3d39..cdccd0e5 100644 --- a/pkg/types/build.go +++ b/pkg/types/build.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package types type JenkinsBuildParam struct { -- Gitee From 4501e93017ff421361f24f3e6833ea65e6c95e16 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 7 Apr 2022 20:43:44 +0800 Subject: [PATCH 2098/2647] fix artifact image miss bug Signed-off-by: allenshen --- .../core/workflow/service/workflow/nsq_handlers.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 8d205c56..1238f83b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -312,7 +312,7 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifact.Name = imageName deliveryArtifact.ImageTag = imageTag //获取镜像详细信息 - imageInfo, _ := getImageInfo(imageName, imageTag, h.log) + imageInfo, _ := getImageInfo(buildInfo.ProductName, buildInfo.EnvName, imageName, imageTag, h.log) if imageInfo != nil { deliveryArtifact.ImageSize = imageInfo.ImageSize deliveryArtifact.ImageDigest = imageInfo.ImageDigest @@ -911,8 +911,15 @@ func upsertWorkflowStat(args *commonmodels.WorkflowStat, log *zap.SugaredLogger) return nil } -func getImageInfo(repoName, tag string, log *zap.SugaredLogger) (*commonmodels.DeliveryImage, error) { - registryInfo, _, err := commonservice.FindDefaultRegistry(false, log) +func getImageInfo(productName, evnName, repoName, tag string, log *zap.SugaredLogger) (*commonmodels.DeliveryImage, error) { + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: evnName, + }) + if err != nil { + return nil, err + } + registryInfo, _, err := commonservice.FindRegistryById(productInfo.RegistryID, false, log) if err != nil { log.Errorf("RegistryNamespace.get error: %v", err) return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) -- Gitee From 269266f4fd7e3a6251e43780769e18f85342a95a Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 7 Apr 2022 20:50:29 +0800 Subject: [PATCH 2099/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/nsq_handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 1238f83b..56392fec 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -311,7 +311,7 @@ func (h *TaskAckHandler) uploadTaskData(pt *task.Task) error { deliveryArtifact.Type = string(config.Image) deliveryArtifact.Name = imageName deliveryArtifact.ImageTag = imageTag - //获取镜像详细信息 + //get image detail info imageInfo, _ := getImageInfo(buildInfo.ProductName, buildInfo.EnvName, imageName, imageTag, h.log) if imageInfo != nil { deliveryArtifact.ImageSize = imageInfo.ImageSize -- Gitee From e9831bd04fc85edd19fe434e75bddff9772c3a3d Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 7 Apr 2022 21:16:56 +0800 Subject: [PATCH 2100/2647] optimize code Signed-off-by: liu deyi --- .../workflow/service/webhook/gitee_testing_task.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go index cf6a61de..cb174fe5 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go @@ -96,7 +96,8 @@ func (gtem giteeTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHook } if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { + matched, err := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch) + if err != nil || !matched { return false, nil } } @@ -138,7 +139,8 @@ func (gmem *giteeMergeEventMatcherForTesting) Match(hookRepo *commonmodels.MainH } if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.PullRequest.Base.Ref); !matched { + matched, err := regexp.MatchString(hookRepo.Branch, ev.PullRequest.Base.Ref) + if err != nil || !matched { return false, nil } } @@ -201,7 +203,7 @@ func createGiteeEventMatcherForTesting( func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { testingList, err := commonrepo.NewTestingColl().List(&commonrepo.ListTestOption{}) if err != nil { - log.Errorf("failed to list testing %v", err) + log.Errorf("failed to list testing,err: %s", err) return err } @@ -243,7 +245,7 @@ func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log * } err := AutoCancelTask(autoCancelOpt, log) if err != nil { - log.Errorf("failed to auto cancel testing task when receive event %v due to %v ", event, err) + log.Errorf("failed to auto cancel testing task when receive event %v due to %s ", event, err) mErr = multierror.Append(mErr, err) } } @@ -258,7 +260,7 @@ func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log * // 3. create task with args if resp, err := testingservice.CreateTestTask(args, log); err != nil { - log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) + log.Errorf("failed to create testing task when receive event %v due to %s ", event, err) mErr = multierror.Append(mErr, err) } else { log.Infof("succeed to create task %v", resp) -- Gitee From 75f55b22d4c6ef9bf218e281c1a2b9f2c5ba142f Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 7 Apr 2022 21:27:26 +0800 Subject: [PATCH 2101/2647] Compatible with the problem that the name of the old data imageName is empty Signed-off-by: liu deyi --- pkg/microservice/aslan/core/service/service/service.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index eabe0004..4cef72d5 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -184,6 +184,10 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv serviceModule := new(ServiceModule) serviceModule.Container = container serviceModule.ImageName = container.ImageName + //Compatible with the problem that the name of the old data imageName is empty + if serviceModule.ImageName == "" { + serviceModule.ImageName = container.Name + } buildObj, _ := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ProductName: args.ProductName, ServiceName: args.ServiceName, Targets: []string{container.Name}}) if buildObj != nil { serviceModule.BuildName = buildObj.Name -- Gitee From 0d5dd7454cf79308887567492c94e29aa8586697 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 7 Apr 2022 21:31:56 +0800 Subject: [PATCH 2102/2647] optimize cdoe Signed-off-by: liu deyi --- .../core/workflow/service/webhook/gitee_testing_task.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go index cb174fe5..f3ae3548 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go @@ -172,9 +172,7 @@ func (gmem *giteeMergeEventMatcherForTesting) UpdateTaskArgs(args *commonmodels. return args } -func createGiteeEventMatcherForTesting( - event interface{}, diffSrv giteePullRequestDiffFunc, testing *commonmodels.Testing, log *zap.SugaredLogger, -) gitEventMatcherForTesting { +func createGiteeEventMatcherForTesting(event interface{}, diffSrv giteePullRequestDiffFunc, testing *commonmodels.Testing, log *zap.SugaredLogger) gitEventMatcherForTesting { switch evt := event.(type) { case *gitee.PushEvent: return &giteePushEventMatcherForTesting{ @@ -232,8 +230,8 @@ func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log * log.Infof("event match hook %v of %s", item.MainRepo, testing.Name) var mergeRequestID, commitID string if ev, isPr := event.(*gitee.PullRequestEvent); isPr { - // 如果是merge request,且该webhook触发器配置了自动取消, - // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 + // If it is a merge request, and the webhook trigger is configured with automatic cancellation, + // It is necessary to confirm whether the task triggered by the commit of the merge request before this commit has been processed, and it will be cancelled if it is not processed. mergeRequestID = strconv.Itoa(ev.PullRequest.Number) commitID = ev.PullRequest.Head.Sha autoCancelOpt := &AutoCancelOpt{ -- Gitee From 247799f4e19765a0873fbb080cd3f082128bf3f1 Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 7 Apr 2022 21:33:13 +0800 Subject: [PATCH 2103/2647] add comment Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/gitee_testing_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go index f3ae3548..614c9609 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go @@ -199,6 +199,7 @@ func createGiteeEventMatcherForTesting(event interface{}, diffSrv giteePullReque } func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { + // 1. find configured testing testingList, err := commonrepo.NewTestingColl().List(&commonrepo.ListTestOption{}) if err != nil { log.Errorf("failed to list testing,err: %s", err) -- Gitee From 2db504bfec367154758dd705d3217fe8e91a7dba Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 7 Apr 2022 22:29:32 +0800 Subject: [PATCH 2104/2647] add auto sync option Signed-off-by: allenshen --- .../aslan/core/common/repository/models/template/product.go | 1 + pkg/microservice/aslan/core/common/service/render.go | 2 ++ .../aslan/core/environment/service/environment.go | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index b34bd0ff..e649c14b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -105,6 +105,7 @@ type GitRepoConfig struct { type CustomYaml struct { YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` Source string `bson:"source" json:"source"` + AutoSync bool `bson:"auto_sync" json:"auto_sync"` SourceDetail interface{} `bson:"source_detail" json:"source_detail"` } diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index eb0a9361..f4225a4f 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -51,6 +51,7 @@ type KVPair struct { type ValuesDataArgs struct { YamlSource string `json:"yamlSource,omitempty"` + AutoSync bool `json:"autoSync,omitempty"` GitRepoConfig *RepoConfig `json:"gitRepoConfig,omitempty"` } @@ -96,6 +97,7 @@ func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.CustomYaml { } if args.ValuesData != nil { ret.Source = args.ValuesData.YamlSource + ret.AutoSync = args.ValuesData.AutoSync if args.ValuesData.GitRepoConfig != nil { repoData := &models.CreateFromRepo{ GitRepoConfig: &templatemodels.GitRepoConfig{ diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index a1d2ea36..71c2bd03 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1381,6 +1381,7 @@ func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml } ret := &templatemodels.CustomYaml{ Source: args.YamlSource, + AutoSync: args.AutoSync, SourceDetail: repoData, } return ret @@ -2937,6 +2938,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes newChartInfos := make([]*templatemodels.RenderChart, 0) defaultValues := "" + var yamlData *templatemodels.CustomYaml switch updateType { case UpdateTypeSystem: for _, serviceNameGroup := range productTemp.Services { @@ -2954,6 +2956,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes return nil, err } defaultValues = currentEnvRenderSet.DefaultValues + yamlData = currentEnvRenderSet.YamlData // 环境里面的变量 currentEnvRenderSetMap := make(map[string]*templatemodels.RenderChart) @@ -3039,6 +3042,7 @@ func diffRenderSet(username, productName, envName, updateType string, productRes ProductTmpl: productName, ChartInfos: newChartInfos, DefaultValues: defaultValues, + YamlData: yamlData, UpdateBy: username, }, log, -- Gitee From 81bf02ddec76de9d1b203dacb8606c8d7390971e Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 7 Apr 2022 23:06:20 +0800 Subject: [PATCH 2105/2647] fix env copy error Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 71c2bd03..ccfa9afa 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1114,9 +1114,6 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH } } - // default values - defaultValuesYaml := arg.DefaultValues - // insert renderset info into db if len(productInfo.ChartInfos) > 0 { err := commonservice.CreateHelmRenderSet(&commonmodels.RenderSet{ @@ -1125,7 +1122,7 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH ProductTmpl: arg.ProductName, UpdateBy: userName, IsDefault: false, - DefaultValues: defaultValuesYaml, + DefaultValues: arg.DefaultValues, YamlData: geneYamlData(arg.ValuesData), ChartInfos: productInfo.ChartInfos, }, log) @@ -2672,11 +2669,10 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel // 获取服务详情 opt := &commonrepo.ServiceFindOption{ - ServiceName: svc.ServiceName, - Type: svc.Type, - Revision: svc.Revision, - ProductName: args.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, + ServiceName: svc.ServiceName, + Type: svc.Type, + Revision: svc.Revision, + ProductName: args.ProductName, } serviceObj, err := commonrepo.NewServiceColl().Find(opt) if err != nil { -- Gitee From 7b860fd0da65854cf2ad1eafad9241f51882183b Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 7 Apr 2022 23:12:18 +0800 Subject: [PATCH 2106/2647] add auto sync check Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/renderset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index b486290c..43e59065 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -71,7 +71,7 @@ func unMarshalJson(source interface{}) (*models.CreateFromRepo, error) { // SyncYamlFromSource sync values.yaml from source // NOTE currently only support gitHub and gitlab func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (bool, string, error) { - if yamlData == nil { + if yamlData == nil || !yamlData.AutoSync { return false, "", nil } if !fromGitRepo(yamlData.Source) { -- Gitee From 1ed360a3781ff2431924029f3755d312727b53f4 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 8 Apr 2022 10:07:11 +0800 Subject: [PATCH 2107/2647] remove debug comment Signed-off-by: zhangyifei --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 04253c2b..c246afcb 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -92,7 +92,6 @@ var publicURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/callback"}, }, - // !!! THE FOLLOWING IS FOR DEBUG. { Methods: []string{"GET"}, Endpoints: []string{"api/aslan/environment/environments/?*/check/workloads/k8services"}, -- Gitee From 999444094147e8775ce6fc0ec1b9f64cd28b6ab2 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 8 Apr 2022 10:17:27 +0800 Subject: [PATCH 2108/2647] remove API of enable/disable env sharing from public API Signed-off-by: zhangyifei --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index c246afcb..c4ef0fad 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -104,10 +104,6 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/aslan/environment/environments/?*/check/sharenv/disable/ready"}, }, - { - Methods: []string{"POST", "DELETE"}, - Endpoints: []string{"api/aslan/environment/environments/?*/share/enable"}, - }, { Methods: []string{"GET"}, Endpoints: []string{"api/aslan/service/services/?*/environments/deployable"}, -- Gitee From a69f12cc7882fe661bd56f5ece8f6f5d9cdcff74 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 10:31:12 +0800 Subject: [PATCH 2109/2647] share env cofig permission Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 508cc691..631f3af9 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -89,6 +89,14 @@ rules: alias: "配置" description: "" rules: + - method: POST + endpoint: "/api/environment/environments/?*/share/enable" + resourceType: "Environment" + idRegex: "/api/environment/environments/([\\w\\W]+?)/share/enable" + - method: DELETE + endpoint: "/api/environment/environments/?*/share/enable" + resourceType: "Environment" + idRegex: "/api/environment/environments/([\\w\\W]+?)/share/enable" - method: PUT endpoint: "/api/aslan/environment/environments" resourceType: "Environment" -- Gitee From cae1338f12e234454d661cb2e933a1d031a048b0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 8 Apr 2022 10:53:07 +0800 Subject: [PATCH 2110/2647] fix import issue Signed-off-by: allenshen --- pkg/microservice/cron/core/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/cron/core/service/service.go b/pkg/microservice/cron/core/service/service.go index e120ab5f..8fbb0d5f 100644 --- a/pkg/microservice/cron/core/service/service.go +++ b/pkg/microservice/cron/core/service/service.go @@ -17,9 +17,9 @@ limitations under the License. package service import ( - templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "go.mongodb.org/mongo-driver/bson/primitive" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/koderover/zadig/pkg/setting" ) -- Gitee From 4b4cfe837c21d9b5a2a8ac22968e6975f9c51180 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 10:54:19 +0800 Subject: [PATCH 2111/2647] share env cofig permission Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 631f3af9..314dd6e8 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -93,10 +93,16 @@ rules: endpoint: "/api/environment/environments/?*/share/enable" resourceType: "Environment" idRegex: "/api/environment/environments/([\\w\\W]+?)/share/enable" + matchAttributes: + - key: "production" + value: "false" - method: DELETE endpoint: "/api/environment/environments/?*/share/enable" resourceType: "Environment" idRegex: "/api/environment/environments/([\\w\\W]+?)/share/enable" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments" resourceType: "Environment" -- Gitee From 20208730b491b371ff3b09a95ac25f5ee2c7fef6 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 8 Apr 2022 11:49:51 +0800 Subject: [PATCH 2112/2647] remove comment Signed-off-by: zhangyifei --- pkg/microservice/aslan/core/environment/handler/environment.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 2d564470..bd2c6dcd 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -520,7 +520,6 @@ func DeleteProductServices(c *gin.Context) { // For environment sharing, if the environment is the base environment and the service to be deleted has been deployed in the subenvironment, // we should prompt the user that `Delete the service in the subenvironment before deleting the service in the base environment`. - // servicesInSubEnvs, err := service.CheckServicesDeployedInSubEnvs(c) svcsInSubEnvs, err := service.CheckServicesDeployedInSubEnvs(c, projectName, envName, args.ServiceNames) if err != nil { ctx.Err = err -- Gitee From 8415c488ac09ab0658821e3cc793dd43dcc05d68 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 8 Apr 2022 11:55:56 +0800 Subject: [PATCH 2113/2647] change year of license Signed-off-by: zhangyifei --- pkg/microservice/hubagent/server/rest/router.go | 2 +- pkg/microservice/hubagent/server/rest/server.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/hubagent/server/rest/router.go b/pkg/microservice/hubagent/server/rest/router.go index 3d1c68dd..70133963 100644 --- a/pkg/microservice/hubagent/server/rest/router.go +++ b/pkg/microservice/hubagent/server/rest/router.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/hubagent/server/rest/server.go b/pkg/microservice/hubagent/server/rest/server.go index 24584cbf..c2ef16b2 100644 --- a/pkg/microservice/hubagent/server/rest/server.go +++ b/pkg/microservice/hubagent/server/rest/server.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 70cc4ccf799fbc1b580254082c1b51682895f1ce Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 8 Apr 2022 14:42:34 +0800 Subject: [PATCH 2114/2647] fix compile error Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 648e41d9..57408555 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1463,16 +1463,11 @@ func SyncHelmProductEnvironment(productName, envName string) error { } logger := log.SugaredLogger() - err = UpdateHelmProductVariable(productName, envName, "timer", "000000000000000000", updatedRcList, productRenderset, logger) + err = UpdateHelmProductVariable(productName, envName, "cron", "000000000000000000", updatedRcList, productRenderset, logger) if err != nil { return err } - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) - if err != nil { - log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) - return err - } - return ensureKubeEnv(product.Namespace, product.RegistryID, kubeClient, logger) + return err } func UpdateHelmProductRenderset(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { -- Gitee From 381b8710c545893438f9c3eb0a9eea4fc756f8e9 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 8 Apr 2022 14:44:10 +0800 Subject: [PATCH 2115/2647] fix compile error Signed-off-by: allenshen --- .../aslan/core/environment/handler/environment.go | 2 +- .../aslan/core/environment/service/environment.go | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index a7739e61..2b4f032d 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -382,7 +382,7 @@ func SyncHelmProductRenderset(c *gin.Context) { return } - ctx.Err = service.SyncHelmProductEnvironment(projectName, envName) + ctx.Err = service.SyncHelmProductEnvironment(projectName, envName, ctx.RequestID, ctx.Logger) } func UpdateHelmProductRenderset(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 57408555..7ab23b05 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1409,7 +1409,7 @@ func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml return ret } -func SyncHelmProductEnvironment(productName, envName string) error { +func SyncHelmProductEnvironment(productName, envName, requestID string, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, EnvName: envName, @@ -1462,8 +1462,7 @@ func SyncHelmProductEnvironment(productName, envName string) error { updatedRcList = append(updatedRcList, updatedRc) } - logger := log.SugaredLogger() - err = UpdateHelmProductVariable(productName, envName, "cron", "000000000000000000", updatedRcList, productRenderset, logger) + err = UpdateHelmProductVariable(productName, envName, "cron", requestID, updatedRcList, productRenderset, log) if err != nil { return err } -- Gitee From 35cc96e736243f66ba288e8968adb9bc0f3c339d Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 8 Apr 2022 14:49:12 +0800 Subject: [PATCH 2116/2647] add encryption Signed-off-by: panxunying --- .../aslan/core/common/service/kube/actions.go | 18 ++++ .../aslan/core/common/service/rsa_key.go | 87 +++++++++++++++++++ pkg/microservice/aslan/core/service.go | 35 ++++++++ .../aslan/core/system/handler/router.go | 6 ++ .../aslan/core/system/handler/rsa_key.go | 36 ++++++++ .../systemconfig/core/email/handler/email.go | 3 +- .../systemconfig/core/email/service/email.go | 22 ++++- pkg/setting/consts.go | 2 + pkg/shared/client/aslan/rsa_key.go | 39 +++++++++ pkg/tool/crypto/aes.go | 12 +++ pkg/tool/rsa/rsa.go | 83 ++++++++++++++++++ 11 files changed, 340 insertions(+), 3 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/rsa_key.go create mode 100644 pkg/microservice/aslan/core/system/handler/rsa_key.go create mode 100644 pkg/shared/client/aslan/rsa_key.go diff --git a/pkg/microservice/aslan/core/common/service/kube/actions.go b/pkg/microservice/aslan/core/common/service/kube/actions.go index 51ff6a74..a8865162 100644 --- a/pkg/microservice/aslan/core/common/service/kube/actions.go +++ b/pkg/microservice/aslan/core/common/service/kube/actions.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/selection" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/kube/getter" @@ -43,6 +44,23 @@ func CreateNamespace(namespace string, kubeClient client.Client) error { return nil } +func CreateOrUpdateRSASecret(publicKey, privateKey []byte, kubeClient client.Client) error { + data := make(map[string][]byte) + + data["publicKey"] = publicKey + data["privateKey"] = privateKey + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: config.Namespace(), + Name: setting.RSASecretName, + }, + Data: data, + Type: corev1.SecretTypeOpaque, + } + return updater.UpdateOrCreateSecret(secret, kubeClient) +} + func CreateOrUpdateRegistrySecret(namespace string, reg *commonmodels.RegistryNamespace, kubeClient client.Client) error { data := make(map[string][]byte) diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go new file mode 100644 index 00000000..607aa2c3 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -0,0 +1,87 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "encoding/hex" + + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/tool/rsa" + + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" +) + +func GetRSAKey(log *zap.SugaredLogger) ([]byte, []byte, error) { + log.Infof("start get rsa key") + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + return nil, nil, err + } + res, err := clientset.CoreV1().Secrets(config.Namespace()).Get(context.TODO(), setting.RSASecretName, metav1.GetOptions{}) + if err != nil { + return nil, nil, err + } + return res.Data["publicKey"], res.Data["privateKey"], nil +} + +type GetRSAPublicKeyRes struct { + PublicKey string `json:"publicKey"` +} + +func GetRSAPublicKey(log *zap.SugaredLogger) (*GetRSAPublicKeyRes, error) { + publicKey, _, err := GetRSAKey(log) + if err != nil { + return nil, err + } + return &GetRSAPublicKeyRes{ + PublicKey: string(publicKey), + }, nil +} + +type GetAesKeyFromEncryptedKeyResp struct { + PlainText string `json:"plain_text"` +} + +func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*GetAesKeyFromEncryptedKeyResp, error) { + log.Infof("start GetAesKeyFromEncryptedKey") + _, privateKey, err := GetRSAKey(log) + if err != nil { + log.Errorf("getAesKeyFromEncryptedKey getRSAKey error msg:%s", err) + return nil, err + } + log.Infof("privateKey:%s", privateKey) + byteKey, err := hex.DecodeString(encryptedKey) + if err != nil { + log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) + return nil, err + } + log.Infof("byteKey:%s", byteKey) + aesKey, err := rsa.DecryptByPrivateKey(byteKey, privateKey) + if err != nil { + log.Errorf("getAesKeyFromEncryptedKey decryptByPrivateKey error msg:%s", err) + return nil, err + } + log.Infof("aesKey:%s", aesKey) + return &GetAesKeyFromEncryptedKeyResp{ + PlainText: string(aesKey), + }, nil +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index e69a8ccb..d5fcfd10 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -23,11 +23,15 @@ import ( "time" "github.com/hashicorp/go-multierror" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + commonconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" modeMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/collaboration/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" @@ -42,8 +46,10 @@ import ( testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "github.com/koderover/zadig/pkg/tool/rsa" ) const ( @@ -100,6 +106,33 @@ func registerPolicies() { } } +func initRsaKey() { + client, err := kubeclient.GetKubeClient(commonconfig.HubServerServiceAddress(), setting.LocalClusterID) + if err != nil { + log.DPanic(err) + } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + log.DPanic(err) + } + _, err = clientset.CoreV1().Secrets(commonconfig.Namespace()).Get(context.TODO(), setting.RSASecretName, metav1.GetOptions{}) + + if err != nil { + if apierrors.IsNotFound(err) { + err, publicKey, privateKey := rsa.GetRsaKey() + if err != nil { + log.DPanic(err) + } + err = kube.CreateOrUpdateRSASecret(publicKey, privateKey, client) + if err != nil { + log.DPanic(err) + } + } else { + log.DPanic(err) + } + } +} + func Start(ctx context.Context) { log.Init(&log.Config{ Level: commonconfig.LogLevel(), @@ -123,6 +156,8 @@ func Start(ctx context.Context) { registerPolicies() go StartControllers(ctx.Done()) + + initRsaKey() } func Stop(ctx context.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 7e6fb08b..20de6bc1 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -169,6 +169,12 @@ func (*Router) Inject(router *gin.RouterGroup) { privateKey.DELETE("/:id", gin2.UpdateOperationLogStatus, DeletePrivateKey) } + rsaKey := router.Group("rsaKey") + { + rsaKey.GET("publicKey", GetRSAPublicKey) + rsaKey.GET("decryptedText", GetTextFromEncryptedKey) + } + notification := router.Group("notification") { notification.GET("", PullNotify) diff --git a/pkg/microservice/aslan/core/system/handler/rsa_key.go b/pkg/microservice/aslan/core/system/handler/rsa_key.go new file mode 100644 index 00000000..108a7d14 --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/rsa_key.go @@ -0,0 +1,36 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetRSAPublicKey(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GetRSAPublicKey(ctx.Logger) +} + +func GetTextFromEncryptedKey(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GetAesKeyFromEncryptedKey(c.Query("encryptedKey"), ctx.Logger) +} diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index d90af53a..399bd293 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -33,7 +33,8 @@ func GetEmailHost(c *gin.Context) { func InternalGetEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.InternalGetEmailHost(ctx.Logger) + + ctx.Resp, ctx.Err = service.InternalGetEmailHost(c.Query("encryptedKey"), ctx.Logger) } func CreateEmailHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 1127081a..ac8629c1 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -21,8 +21,11 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/mongodb" + "github.com/koderover/zadig/pkg/shared/client/aslan" + "github.com/koderover/zadig/pkg/tool/crypto" ) func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { @@ -33,8 +36,23 @@ func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { return host, err } -func InternalGetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { - return mongodb.NewEmailHostColl().Find() +func InternalGetEmailHost(encryptedKey string, log *zap.SugaredLogger) (*models.EmailHost, error) { + aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) + if err != nil { + return nil, err + } + result, err := mongodb.NewEmailHostColl().Find() + if err != nil { + return nil, err + } + password, err := crypto.AesEncryptByKey(result.Password, aesKey.PlainText) + if err != nil { + return nil, err + } + log.Infof("pre password:%s", result.Password) + result.Password = password + log.Infof("password:%s", password) + return result, nil } func CreateEmailHost(emailHost *models.EmailHost, _ *zap.SugaredLogger) (*models.EmailHost, error) { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a075d31a..c17401c3 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -202,6 +202,8 @@ const ( APIVersionAppsV1 = "apps/v1" + RSASecretName = "zadig-rsa-key" + DefaultImagePullSecret = "default-registry-secret" ) diff --git a/pkg/shared/client/aslan/rsa_key.go b/pkg/shared/client/aslan/rsa_key.go new file mode 100644 index 00000000..5211c9d8 --- /dev/null +++ b/pkg/shared/client/aslan/rsa_key.go @@ -0,0 +1,39 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aslan + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type GetAesKeyFromEncryptedKeyResp struct { + PlainText string `json:"plain_text"` +} + +func (c *Client) GetTextFromEncryptedKey(encryptedKey string) (*GetAesKeyFromEncryptedKeyResp, error) { + url := fmt.Sprintf("/system/rsaKey/decryptedText?encryptedKey=%s", encryptedKey) + + res := &GetAesKeyFromEncryptedKeyResp{} + _, err := c.Get(url, httpclient.SetQueryParam("encryptedKey", encryptedKey), httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/pkg/tool/crypto/aes.go b/pkg/tool/crypto/aes.go index 9889f609..1084521a 100644 --- a/pkg/tool/crypto/aes.go +++ b/pkg/tool/crypto/aes.go @@ -67,6 +67,18 @@ func AesEncrypt(src string) (string, error) { return dest, nil } +func AesEncryptByKey(src, aesKey string) (string, error) { + client, err := NewAes(aesKey) + if err != nil { + return "", err + } + dest, err := client.Encrypt(src) + if err != nil { + return "", err + } + return dest, nil +} + func AesDecrypt(src string, aesKey ...string) (string, error) { var ( err error diff --git a/pkg/tool/rsa/rsa.go b/pkg/tool/rsa/rsa.go index fcae3b45..1128341e 100644 --- a/pkg/tool/rsa/rsa.go +++ b/pkg/tool/rsa/rsa.go @@ -23,6 +23,8 @@ import ( "encoding/pem" "errors" "io/ioutil" + "log" + "runtime" ) //var defaultPrivateKey = []byte(` @@ -154,6 +156,87 @@ func LoadPubKey(filename string) (err error) { return } +const ( + privateKeyPrefix = "RSA PRIVATE KEY" + publicKeyPrefix = "RSA PUBLIC KEY" +) + +func EncryptByPublicKey(plainText, key []byte) (cryptText []byte, err error) { + block, _ := pem.Decode(key) + defer func() { + if err := recover(); err != nil { + switch err.(type) { + case runtime.Error: + log.Println("runtime err:", err, "Check that the key is correct") + default: + log.Println("error:", err) + } + } + }() + publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + publicKey := publicKeyInterface.(*rsa.PublicKey) + + cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText) + if err != nil { + return nil, err + } + return cipherText, nil +} + +func DecryptByPrivateKey(cryptText, key []byte) (plainText []byte, err error) { + block, _ := pem.Decode(key) + + defer func() { + if err := recover(); err != nil { + switch err.(type) { + case runtime.Error: + log.Println("runtime err:", err, "Check that the key is correct") + default: + log.Println("error:", err) + } + } + }() + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return []byte{}, err + } + plainText, err = rsa.DecryptPKCS1v15(rand.Reader, privateKey, cryptText) + if err != nil { + return []byte{}, err + } + return plainText, nil +} + +func GetRsaKey() (error, []byte, []byte) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err, nil, nil + } + x509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey) + privateBlock := pem.Block{ + Type: privateKeyPrefix, + Bytes: x509PrivateKey, + } + privateKeyRes := pem.EncodeToMemory(&privateBlock) + + publicKey := privateKey.PublicKey + x509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey) + if err != nil { + return err, nil, nil + } + + publicBlock := pem.Block{ + Type: publicKeyPrefix, + Bytes: x509PublicKey, + } + + publicKeyRes := pem.EncodeToMemory(&publicBlock) + return nil, publicKeyRes, privateKeyRes +} + //RSA解密 // cipherText 需要解密的byte数据 func Decrypt(cipherText []byte) ([]byte, error) { -- Gitee From 2a45e9f774b5d3401b3ca94937e0adf9b0fe9bf3 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 8 Apr 2022 14:29:12 +0800 Subject: [PATCH 2117/2647] environment Configuration Management Signed-off-by: ddh27 --- pkg/microservice/aslan/config/consts.go | 9 + .../aslan/core/code/handler/router.go | 1 + .../aslan/core/code/handler/workspace.go | 28 ++ .../aslan/core/code/service/workspace.go | 16 + .../common/repository/models/env_configmap.go | 34 ++ .../common/repository/models/env_ingress.go | 35 ++ .../core/common/repository/models/env_pvc.go | 35 ++ .../common/repository/models/env_secret.go | 35 ++ .../repository/models/env_svc_depend.go | 37 ++ .../core/common/repository/models/product.go | 5 +- .../repository/mongodb/env_configmap.go | 175 +++++++ .../common/repository/mongodb/env_ingress.go | 101 ++++ .../core/common/repository/mongodb/env_pvc.go | 157 ++++++ .../common/repository/mongodb/env_secret.go | 157 ++++++ .../repository/mongodb/env_svc_depend.go | 254 ++++++++++ .../core/common/repository/mongodb/product.go | 4 + .../aslan/core/common/service/fs/git.go | 1 + .../environment/handler/common_env_cfg.go | 125 +++++ .../core/environment/handler/configmap.go | 44 +- .../aslan/core/environment/handler/ingress.go | 38 ++ .../aslan/core/environment/handler/pvc.go | 38 ++ .../aslan/core/environment/handler/router.go | 22 +- .../aslan/core/environment/handler/secret.go | 38 ++ .../environment/service/common_env_cfg.go | 461 ++++++++++++++++++ .../service/common_env_cfg_informer.go | 379 ++++++++++++++ .../core/environment/service/configmap.go | 305 ++++++++---- .../aslan/core/environment/service/env.go | 1 + .../core/environment/service/environment.go | 38 +- .../service/environment_creator.go | 20 +- .../aslan/core/environment/service/helm.go | 116 +++++ .../aslan/core/environment/service/ingress.go | 223 +++++++++ .../aslan/core/environment/service/k8s.go | 4 + .../aslan/core/environment/service/pm.go | 4 + .../aslan/core/environment/service/pvc.go | 203 ++++++++ .../aslan/core/environment/service/secret.go | 170 +++++++ pkg/microservice/aslan/core/service.go | 8 + pkg/setting/consts.go | 31 +- pkg/tool/errors/http_errors.go | 6 + pkg/tool/kube/getter/ingress.go | 24 + pkg/tool/kube/getter/pvc.go | 37 ++ pkg/tool/kube/getter/secrets.go | 14 + pkg/tool/kube/updater/base.go | 31 ++ pkg/tool/kube/updater/configmap.go | 2 +- pkg/tool/kube/updater/ingress.go | 32 ++ pkg/tool/kube/updater/pvc.go | 23 + pkg/tool/kube/updater/unstructured.go | 8 + 46 files changed, 3368 insertions(+), 161 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/env_configmap.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/env_ingress.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/env_pvc.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/env_secret.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/env_svc_depend.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_svc_depend.go create mode 100644 pkg/microservice/aslan/core/environment/handler/common_env_cfg.go create mode 100644 pkg/microservice/aslan/core/environment/handler/ingress.go create mode 100644 pkg/microservice/aslan/core/environment/handler/pvc.go create mode 100644 pkg/microservice/aslan/core/environment/handler/secret.go create mode 100644 pkg/microservice/aslan/core/environment/service/common_env_cfg.go create mode 100644 pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go create mode 100644 pkg/microservice/aslan/core/environment/service/ingress.go create mode 100644 pkg/microservice/aslan/core/environment/service/pvc.go create mode 100644 pkg/microservice/aslan/core/environment/service/secret.go create mode 100644 pkg/tool/kube/getter/pvc.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index e2ec5ec3..eb58e0b1 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -221,3 +221,12 @@ const ( RoleBindingNameEdit = setting.ProductName + "-edit" RoleBindingNameView = setting.ProductName + "-view" ) + +type CommonEnvCfgType string + +const ( + CommonEnvCfgTypeIngress CommonEnvCfgType = "Ingress" + CommonEnvCfgTypeConfigMap CommonEnvCfgType = "ConfigMap" + CommonEnvCfgTypeSecret CommonEnvCfgType = "Secret" + CommonEnvCfgTypePvc CommonEnvCfgType = "PVC" +) diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 764dc04c..509c2be9 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -47,6 +47,7 @@ func (*Router) Inject(router *gin.RouterGroup) { workspace.GET("/git/:codehostId", GetGitRepoInfo) workspace.GET("/tree", GetRepoTree) + workspace.GET("/getcontents/:codehostId", GetContents) workspace.GET("/codehub/:codehostId", GetCodehubRepoInfo) } diff --git a/pkg/microservice/aslan/core/code/handler/workspace.go b/pkg/microservice/aslan/core/code/handler/workspace.go index 43edf436..ffda5253 100644 --- a/pkg/microservice/aslan/core/code/handler/workspace.go +++ b/pkg/microservice/aslan/core/code/handler/workspace.go @@ -175,3 +175,31 @@ func GetCodehubRepoInfo(c *gin.Context) { ctx.Resp, ctx.Err = service.GetCodehubRepoInfo(codehostID, repoUUID, branchName, path, ctx.Logger) } + +func GetContents(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + codehostIDStr := c.Param("codehostId") + codehostID, err := strconv.Atoi(codehostIDStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("cannot convert codehost id to int") + return + } + + repoName := c.Query("repoName") + if repoName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("repoName cannot be empty") + return + } + branchName := c.Query("branchName") + path := c.Query("path") + isDir, err := strconv.ParseBool(c.Query("isDir")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalidParam isDir") + return + } + repoOwner := c.Query("repoOwner") + + ctx.Resp, ctx.Err = service.GetContents(codehostID, repoOwner, repoName, path, branchName, isDir, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index cb77ea5c..b1578a5f 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -31,6 +31,7 @@ import ( "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/util" ) func GetPublicRepoTree(repoLink, path string, logger *zap.SugaredLogger) ([]*git.TreeNode, error) { @@ -211,3 +212,18 @@ func GetCodehubRepoInfo(codehostID int, repoUUID, branchName, path string, log * } return fileInfos, nil } + +func GetContents(codeHostID int, owner, repo, path, branch string, isDir bool, logger *zap.SugaredLogger) (string, error) { + getter, err := fs.GetTreeGetter(codeHostID) + if err != nil { + logger.Errorf("Failed to get tree getter, err: %s", err) + return "", e.ErrListWorkspace.AddDesc(err.Error()) + } + + yamlInfos, err := getter.GetYAMLContents(owner, repo, path, branch, isDir, false) + if err != nil { + return "", e.ErrListWorkspace.AddDesc(err.Error()) + } + + return util.CombineManifests(yamlInfos), nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_configmap.go b/pkg/microservice/aslan/core/common/repository/models/env_configmap.go new file mode 100644 index 00000000..48ab7779 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/env_configmap.go @@ -0,0 +1,34 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type EnvConfigMap struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvConfigMap) TableName() string { + return "env_configmap" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_ingress.go b/pkg/microservice/aslan/core/common/repository/models/env_ingress.go new file mode 100644 index 00000000..b532d9ec --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/env_ingress.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type EnvIngress struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvIngress) TableName() string { + return "env_ingress" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_pvc.go b/pkg/microservice/aslan/core/common/repository/models/env_pvc.go new file mode 100644 index 00000000..f5a962c8 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/env_pvc.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type EnvPvc struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvPvc) TableName() string { + return "env_pvc" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_secret.go b/pkg/microservice/aslan/core/common/repository/models/env_secret.go new file mode 100644 index 00000000..a91eedfc --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/env_secret.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type EnvSecret struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvSecret) TableName() string { + return "env_secret" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_svc_depend.go b/pkg/microservice/aslan/core/common/repository/models/env_svc_depend.go new file mode 100644 index 00000000..e95836d4 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/env_svc_depend.go @@ -0,0 +1,37 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type EnvSvcDepend struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + EnvName string `bson:"env_name" json:"env_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + WorkloadType string `bson:"workload_type" json:"workload_type"` + Pvcs []string `bson:"pvcs" json:"pvcs"` + ConfigMaps []string `bson:"configmaps" json:"configmaps"` + Secrets []string `bson:"secrets" json:"secrets"` + CreateTime int64 `bson:"create_time" json:"create_time"` +} + +func (EnvSvcDepend) TableName() string { + return "env_svc_depend" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 2d2a0300..170b4150 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -51,14 +51,15 @@ type Product struct { Source string `bson:"source" json:"source"` IsOpenSource bool `bson:"is_opensource" json:"is_opensource"` RegistryID string `bson:"registry_id" json:"registry_id"` - BaseName string `bson:"base_name" json:"base_name"` + BaseName string `bson:"base_name" json:"base_name"` // IsExisted is true if this environment is created from an existing one IsExisted bool `bson:"is_existed" json:"is_existed"` // TODO: temp flag IsForkedProduct bool `bson:"-" json:"-"` // New Since v1.11.0. - ShareEnv ProductShareEnv `bson:"share_env" json:"share_env"` + ShareEnv ProductShareEnv `bson:"share_env" json:"share_env"` + EnvConfigYamls []string `bson:"env_config_yamls,omitempty" json:"env_config_yamls,omitempty"` } type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go new file mode 100644 index 00000000..e2f547a6 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go @@ -0,0 +1,175 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type ConfigMapColl struct { + *mongo.Collection + + coll string +} + +func NewConfigMapColl() *ConfigMapColl { + name := models.EnvConfigMap{}.TableName() + return &ConfigMapColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *ConfigMapColl) GetCollectionName() string { + return c.coll +} + +func (c *ConfigMapColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "create_time", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *ConfigMapColl) Create(args *models.EnvConfigMap, isCreateTime bool) error { + if isCreateTime { + args.CreateTime = time.Now().Unix() + } + _, err := c.InsertOne(context.TODO(), args) + return err +} + +type ListEnvCfgOption struct { + IsSort bool + ProductName string + Namespace string + EnvName string + Name string +} + +func (c *ConfigMapColl) List(opt *ListEnvCfgOption) ([]*models.EnvConfigMap, error) { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + + var resp []*models.EnvConfigMap + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"create_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *ConfigMapColl) Update(id string, services []string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + change := bson.M{"$set": bson.M{ + "services": services, + }} + + _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} + +type FindEnvCfgOption struct { + Id string + CreateTime string + ProductName string + Namespace string + EnvName string + Name string +} + +func (c *ConfigMapColl) Find(opt *FindEnvCfgOption) (*models.EnvConfigMap, error) { + if opt == nil { + return nil, errors.New("FindEnvCfgOption cannot be nil") + } + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return nil, err + } + query["_id"] = oid + } + opts := options.FindOne() + if len(opt.CreateTime) > 0 { + query["create_time"] = opt.CreateTime + } else { + opts.SetSort(bson.D{{"create_time", -1}}) + } + + rs := &models.EnvConfigMap{} + err := c.FindOne(context.TODO(), query, opts).Decode(&rs) + if err != nil { + return nil, err + } + return rs, err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go new file mode 100644 index 00000000..9707d5d7 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go @@ -0,0 +1,101 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type IngressColl struct { + *mongo.Collection + + coll string +} + +func NewIngressColl() *IngressColl { + name := models.EnvIngress{}.TableName() + return &IngressColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *IngressColl) GetCollectionName() string { + return c.coll +} + +func (c *IngressColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "create_time", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *IngressColl) Create(args *models.EnvIngress, isCreateTime bool) error { + if isCreateTime { + args.CreateTime = time.Now().Unix() + } + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *IngressColl) List(opt *ListEnvCfgOption) ([]*models.EnvIngress, error) { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + + var resp []*models.EnvIngress + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"create_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go new file mode 100644 index 00000000..15409272 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go @@ -0,0 +1,157 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type PvcColl struct { + *mongo.Collection + + coll string +} + +func NewPvcColl() *PvcColl { + name := models.EnvPvc{}.TableName() + return &PvcColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *PvcColl) GetCollectionName() string { + return c.coll +} + +func (c *PvcColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "create_time", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *PvcColl) Create(args *models.EnvPvc, isCreateTime bool) error { + if isCreateTime { + args.CreateTime = time.Now().Unix() + } + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *PvcColl) List(opt *ListEnvCfgOption) ([]*models.EnvPvc, error) { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + + var resp []*models.EnvPvc + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"create_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *PvcColl) Update(id string, services []string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + change := bson.M{"$set": bson.M{ + "services": services, + }} + + _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} + +func (c *PvcColl) Find(opt *FindEnvCfgOption) (*models.EnvPvc, error) { + if opt == nil { + return nil, errors.New("FindEnvCfgOption cannot be nil") + } + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return nil, err + } + query["_id"] = oid + } + opts := options.FindOne() + if len(opt.CreateTime) > 0 { + query["create_time"] = opt.CreateTime + } else { + opts.SetSort(bson.D{{"create_time", -1}}) + } + + rs := &models.EnvPvc{} + err := c.FindOne(context.TODO(), query, opts).Decode(&rs) + if err != nil { + return nil, err + } + return rs, err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go new file mode 100644 index 00000000..d22e0656 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go @@ -0,0 +1,157 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type SecretColl struct { + *mongo.Collection + + coll string +} + +func NewSecretColl() *SecretColl { + name := models.EnvSecret{}.TableName() + return &SecretColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *SecretColl) GetCollectionName() string { + return c.coll +} + +func (c *SecretColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "create_time", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *SecretColl) Create(args *models.EnvSecret, isCreateTime bool) error { + if isCreateTime { + args.CreateTime = time.Now().Unix() + } + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *SecretColl) List(opt *ListEnvCfgOption) ([]*models.EnvSecret, error) { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + + var resp []*models.EnvSecret + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"create_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *SecretColl) Update(id string, services []string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + change := bson.M{"$set": bson.M{ + "services": services, + }} + + _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} + +func (c *SecretColl) Find(opt *FindEnvCfgOption) (*models.EnvSecret, error) { + if opt == nil { + return nil, errors.New("FindEnvCfgOption cannot be nil") + } + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return nil, err + } + query["_id"] = oid + } + opts := options.FindOne() + if len(opt.CreateTime) > 0 { + query["create_time"] = opt.CreateTime + } else { + opts.SetSort(bson.D{{"create_time", -1}}) + } + + rs := &models.EnvSecret{} + err := c.FindOne(context.TODO(), query, opts).Decode(&rs) + if err != nil { + return nil, err + } + return rs, err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_svc_depend.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_svc_depend.go new file mode 100644 index 00000000..d845bbe8 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_svc_depend.go @@ -0,0 +1,254 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type EnvSvcDependColl struct { + *mongo.Collection + + coll string +} + +func NewEnvSvcDependColl() *EnvSvcDependColl { + name := models.EnvSvcDepend{}.TableName() + return &EnvSvcDependColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *EnvSvcDependColl) GetCollectionName() string { + return c.coll +} + +func (c *EnvSvcDependColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + bson.E{Key: "service_name", Value: 1}, + bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "product_name", Value: 1}, + bson.E{Key: "service_module", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *EnvSvcDependColl) Create(args *models.EnvSvcDepend) error { + + args.CreateTime = time.Now().Unix() + _, err := c.InsertOne(context.TODO(), args) + return err +} + +type ListEnvSvcDependOption struct { + IsSort bool + ProductName string + Namespace string + EnvName string + ServiceName string + ServiceModule string +} + +func (c *EnvSvcDependColl) List(opt *ListEnvSvcDependOption) ([]*models.EnvSvcDepend, error) { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.ServiceName) > 0 { + query["service_name"] = opt.ServiceName + } + if len(opt.ServiceModule) > 0 { + query["service_module"] = opt.ServiceModule + } + + var resp []*models.EnvSvcDepend + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"create_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *EnvSvcDependColl) Update(id string, envSvcDepend *models.EnvSvcDepend) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + change := bson.M{"$set": bson.M{ + "configmaps": envSvcDepend.ConfigMaps, + "pvcs": envSvcDepend.Pvcs, + "secrets": envSvcDepend.Secrets, + }} + + _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} + +func (c *EnvSvcDependColl) CreateOrUpdate(envSvcDepend *models.EnvSvcDepend) error { + opt := &FindEnvSvcDependOption{ + ProductName: envSvcDepend.ProductName, + EnvName: envSvcDepend.EnvName, + ServiceModule: envSvcDepend.ServiceModule, + } + envSvcDependOlder, err := NewEnvSvcDependColl().Find(opt) + if err != nil { + if IsErrNoDocuments(err) { + return NewEnvSvcDependColl().Create(envSvcDepend) + } + return err + } + + oid, err := primitive.ObjectIDFromHex(envSvcDependOlder.ID.Hex()) + if err != nil { + return err + } + + query := bson.M{"_id": oid} + change := bson.M{"$set": bson.M{ + "configmaps": envSvcDepend.ConfigMaps, + "pvcs": envSvcDepend.Pvcs, + "secrets": envSvcDepend.Secrets, + }} + + _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} + +type FindEnvSvcDependOption struct { + Id string + CreateTime string + ProductName string + Namespace string + EnvName string + ServiceName string + ServiceModule string +} + +func (c *EnvSvcDependColl) Find(opt *FindEnvSvcDependOption) (*models.EnvSvcDepend, error) { + if opt == nil { + return nil, errors.New("FindEnvSvcDependOption cannot be nil") + } + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.ServiceName) > 0 { + query["service_name"] = opt.ServiceName + } + if len(opt.ServiceModule) > 0 { + query["service_module"] = opt.ServiceModule + } + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return nil, err + } + query["_id"] = oid + } + opts := options.FindOne() + if len(opt.CreateTime) > 0 { + query["create_time"] = opt.CreateTime + } else { + opts.SetSort(bson.D{{"create_time", -1}}) + } + + rs := &models.EnvSvcDepend{} + err := c.FindOne(context.TODO(), query, opts).Decode(&rs) + if err != nil { + return nil, err + } + return rs, err +} + +type DeleteEnvSvcDependOption struct { + Id string + CreateTime string + ProductName string + Namespace string + EnvName string + ServiceName string + ServiceModule string +} + +func (c *EnvSvcDependColl) Delete(opt *DeleteEnvSvcDependOption) error { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.ServiceName) > 0 { + query["service_name"] = opt.ServiceName + } + if len(opt.ServiceModule) > 0 { + query["service_module"] = opt.ServiceModule + } + if len(opt.Id) > 0 { + oid, err := primitive.ObjectIDFromHex(opt.Id) + if err != nil { + return err + } + query["_id"] = oid + } + + _, err := c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 91625c7a..7cadd03f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -44,6 +44,7 @@ type ProductFindOptions struct { type ProductListOptions struct { EnvName string Name string + Namespace string IsPublic bool ClusterID string IsSortByUpdateTime bool @@ -210,6 +211,9 @@ func (c *ProductColl) List(opt *ProductListOptions) ([]*models.Product, error) { if opt.Name != "" { query["product_name"] = opt.Name } + if opt.Namespace != "" { + query["namespace"] = opt.Namespace + } if opt.IsPublic { query["is_public"] = opt.IsPublic } diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index baf05358..5f97fdb3 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -94,6 +94,7 @@ type TreeGetter interface { GetTreeContents(owner, repo, path, branch string) (afero.Fs, error) GetFileContent(owner, repo, path, branch string) ([]byte, error) GetTree(owner, repo, path, branch string) ([]*git.TreeNode, error) + GetYAMLContents(owner, repo, path, branch string, isDir, split bool) ([]string, error) } func GetPublicTreeGetter(repoLink string) (TreeGetter, error) { diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go new file mode 100644 index 00000000..a52c07a9 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -0,0 +1,125 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +func DeleteCommonEnvCfg(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Query("envName") + productName := c.Query("projectName") + commonEnvCfgType := c.Query("commonEnvCfgType") + objectName := c.Param("objectName") + if envName == "" || productName == "" || objectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName or objectName is invalid") + return + } + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "删除", "环境-对象", fmt.Sprintf("环境名称:%s,%s名称:%s", envName, commonEnvCfgType, objectName), "", ctx.Logger) + + ctx.Err = service.DeleteCommonEnvCfg(envName, productName, objectName, config.CommonEnvCfgType(commonEnvCfgType), ctx.Logger) +} + +func CreateCommonEnvCfg(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.CreateCommonEnvCfgArgs) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateCommonEnvCfg c.GetRawData() err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateCommonEnvCfg json.Unmarshal err : %v", err) + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新建", "环境-对象", fmt.Sprintf("环境名称:%s,%s:", args.EnvName, args.CommonEnvCfgType), string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + if args.YamlData == "" { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Err = service.CreateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) +} + +func UpdateCommonEnvCfg(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.UpdateCommonEnvCfgArgs) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateCommonEnvCfg c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("UpdateCommonEnvCfg json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", fmt.Sprintf("环境-%s", args.CommonEnvCfgType), fmt.Sprintf("环境名称:%s,%s名称:%s", args.EnvName, args.CommonEnvCfgType, args.Name), string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + if len(args.YamlData) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + + // TODO: REVERT AUTH is disabled + //authed := service.CtrlMgr.IsProductAuthed(ctx.UserIDStr, args.ProductOwner, args.ProductName, collection.ProductWritePermission, ctx.Logger) + //if !authed { + // ctx.Err = e.ErrUpdateConfigMap.AddDesc(e.ProductAccessDeniedErrMsg) + // return + //} + + ctx.Err = service.UpdateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) +} + +func ListCommonEnvCfgHistory(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.ListCommonEnvCfgHistoryArgs) + args.EnvName = c.Query("envName") + args.ProductName = c.Query("projectName") + args.CommonEnvCfgType = config.CommonEnvCfgType(c.Query("commonEnvCfgType")) + args.Name = c.Param("objectName") + + ctx.Resp, ctx.Err = service.ListCommonEnvCfgHistory(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/configmap.go b/pkg/microservice/aslan/core/environment/handler/configmap.go index b9e41164..d7771910 100644 --- a/pkg/microservice/aslan/core/environment/handler/configmap.go +++ b/pkg/microservice/aslan/core/environment/handler/configmap.go @@ -43,40 +43,6 @@ func ListConfigMaps(c *gin.Context) { ctx.Resp, ctx.Err = service.ListConfigMaps(args, ctx.Logger) } -func UpdateConfigMap(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := new(service.UpdateConfigMapArgs) - data, err := c.GetRawData() - if err != nil { - log.Errorf("UpdateConfigMap c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, args); err != nil { - log.Errorf("UpdateConfigMap json.Unmarshal err : %v", err) - } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "环境-服务-configMap", fmt.Sprintf("环境名称:%s,服务名称:%s", args.EnvName, args.ServiceName), string(data), ctx.Logger) - c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) - - if err := c.BindJSON(args); err != nil { - ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) - return - } - if len(args.Data) == 0 { - ctx.Err = e.ErrInvalidParam - return - } - - // TODO: REVERT AUTH is disabled - //authed := service.CtrlMgr.IsProductAuthed(ctx.UserIDStr, args.ProductOwner, args.ProductName, collection.ProductWritePermission, ctx.Logger) - //if !authed { - // ctx.Err = e.ErrUpdateConfigMap.AddDesc(e.ProductAccessDeniedErrMsg) - // return - //} - - ctx.Err = service.UpdateConfigMap(args.EnvName, args, ctx.UserName, ctx.UserID, ctx.Logger) -} - func RollBackConfigMap(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -104,3 +70,13 @@ func RollBackConfigMap(c *gin.Context) { ctx.Err = service.RollBackConfigMap(args.EnvName, args, ctx.UserName, ctx.UserID, ctx.Logger) } + +func MigrateHistoryConfigMaps(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Query("envName") + productName := c.Query("projectName") + + ctx.Resp, ctx.Err = service.MigrateHistoryConfigMaps(envName, productName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/ingress.go b/pkg/microservice/aslan/core/environment/handler/ingress.go new file mode 100644 index 00000000..e722be1d --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/ingress.go @@ -0,0 +1,38 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func ListIngresses(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Query("envName") + productName := c.Query("projectName") + if envName == "" || productName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName is invalid") + return + } + + ctx.Resp, ctx.Err = service.ListIngresses(envName, productName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/pvc.go b/pkg/microservice/aslan/core/environment/handler/pvc.go new file mode 100644 index 00000000..bc5d3392 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/pvc.go @@ -0,0 +1,38 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func ListPvcs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Query("envName") + productName := c.Query("projectName") + if envName == "" || productName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName is invalid") + return + } + + ctx.Resp, ctx.Err = service.ListPvcs(envName, productName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index f2b828a1..4cc48c9c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -31,8 +31,28 @@ func (*Router) Inject(router *gin.RouterGroup) { configmaps := router.Group("configmaps") { configmaps.GET("", ListConfigMaps) - configmaps.PUT("", gin2.UpdateOperationLogStatus, UpdateConfigMap) configmaps.POST("", gin2.UpdateOperationLogStatus, RollBackConfigMap) + configmaps.GET("/migrate", MigrateHistoryConfigMaps) + } + + secrets := router.Group("secrets") + { + secrets.GET("", ListSecrets) + } + ingresses := router.Group("ingresses") + { + ingresses.GET("", ListIngresses) + } + pvcs := router.Group("pvcs") + { + pvcs.GET("", ListPvcs) + } + commonEnvCfgs := router.Group("envcfgs") + { + commonEnvCfgs.GET("/:objectName", ListCommonEnvCfgHistory) + commonEnvCfgs.PUT("", gin2.UpdateOperationLogStatus, UpdateCommonEnvCfg) + commonEnvCfgs.POST("", gin2.UpdateOperationLogStatus, CreateCommonEnvCfg) + commonEnvCfgs.DELETE("/:objectName", gin2.UpdateOperationLogStatus, DeleteCommonEnvCfg) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/secret.go b/pkg/microservice/aslan/core/environment/handler/secret.go new file mode 100644 index 00000000..2c980716 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/secret.go @@ -0,0 +1,38 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func ListSecrets(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Query("envName") + productName := c.Query("projectName") + if envName == "" || productName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName is invalid") + return + } + + ctx.Resp, ctx.Err = service.ListSecrets(envName, productName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go new file mode 100644 index 00000000..ae0d74c1 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -0,0 +1,461 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + "fmt" + + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/serializer" + "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" +) + +func DeleteCommonEnvCfg(envName, productName, objectName string, commonEnvCfgType config.CommonEnvCfgType, log *zap.SugaredLogger) error { + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return e.ErrDeleteResource.AddErr(err) + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return e.ErrDeleteResource.AddErr(err) + } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrDeleteResource.AddErr(err) + } + + switch commonEnvCfgType { + case config.CommonEnvCfgTypeConfigMap: + err = updater.DeleteConfigMap(product.Namespace, objectName, kubeClient) + case config.CommonEnvCfgTypeSecret: + err = updater.DeleteSecretWithName(product.Namespace, objectName, kubeClient) + case config.CommonEnvCfgTypeIngress: + err = updater.DeleteIngresseWithName(product.Namespace, objectName, clientset) + case config.CommonEnvCfgTypePvc: + err = updater.DeletePvcWithName(product.Namespace, objectName, clientset) + default: + return e.ErrDeleteResource.AddDesc(fmt.Sprintf("%s is not support delete", commonEnvCfgType)) + } + if err != nil { + log.Error(err) + return e.ErrDeleteResource.AddDesc(err.Error()) + } + return nil +} + +type CreateCommonEnvCfgArgs struct { + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + YamlData string `json:"yaml_data"` + CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type"` +} + +func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { + js, err := yaml.YAMLToJSON([]byte(args.YamlData)) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrUpdateResource.AddErr(err) + } + + u, err := serializer.NewDecoder().YamlToUnstructured(js) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + if args.CommonEnvCfgType == config.CommonEnvCfgTypePvc { + if u.GetKind() != setting.PersistentVolumeClaim { + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) + } + } else if u.GetKind() != string(args.CommonEnvCfgType) { + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) + } + + switch args.CommonEnvCfgType { + case config.CommonEnvCfgTypeConfigMap: + cm := &corev1.ConfigMap{} + err = json.Unmarshal(js, cm) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + cm.Namespace = product.Namespace + addLables := cm.GetLabels() + if addLables == nil { + addLables = make(map[string]string) + } + addLables[string(setting.ProductLabel)] = args.ProductName + cm.SetLabels(addLables) + if err := updater.CreateConfigMap(cm, kubeClient); err != nil { + log.Error(err) + return e.ErrUpdateResource.AddErr(err) + } + envcm := &models.EnvConfigMap{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: cm.Name, + YamlData: args.YamlData, + } + if err = commonrepo.NewConfigMapColl().Create(envcm, true); err != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + case config.CommonEnvCfgTypeSecret: + secret := &corev1.Secret{} + err = json.Unmarshal(js, secret) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + secret.Namespace = product.Namespace + addLables := secret.GetLabels() + if addLables == nil { + addLables = make(map[string]string) + } + addLables[string(setting.ProductLabel)] = args.ProductName + secret.SetLabels(addLables) + if err := updater.UpdateOrCreateSecret(secret, kubeClient); err != nil { + log.Error(err) + return e.ErrUpdateResource.AddDesc(err.Error()) + } + envSecret := &models.EnvSecret{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: secret.Name, + YamlData: args.YamlData, + } + if err = commonrepo.NewSecretColl().Create(envSecret, true); err != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + case config.CommonEnvCfgTypeIngress: + uia := &UpdateCommonEnvCfgArgs{ + EnvName: args.EnvName, + ProductName: args.ProductName, + YamlData: args.YamlData, + } + if err = UpdateOrCreateIngress(uia, userName, userID, true, log); err != nil { + log.Error(err) + return e.ErrUpdateResource.AddErr(err) + } + case config.CommonEnvCfgTypePvc: + pvc := &corev1.PersistentVolumeClaim{} + err = json.Unmarshal(js, pvc) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + pvc.Namespace = product.Namespace + if err := updater.CreatePvc(product.Namespace, pvc, clientset); err != nil { + log.Error(err) + return e.ErrUpdateResource.AddErr(err) + } + envPvc := &models.EnvPvc{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: pvc.Name, + YamlData: args.YamlData, + } + if err = commonrepo.NewPvcColl().Create(envPvc, true); err != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + default: + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("%s is not support create", args.CommonEnvCfgType)) + } + return nil +} + +type UpdateCommonEnvCfgArgs struct { + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + ServiceName string `json:"service_name"` + Name string `json:"name"` + YamlData string `json:"yaml_data"` + RestartAssociatedSvc bool `json:"restart_associated_svc"` + CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type"` + Services []string `json:"services"` +} + +func UpdateCommonEnvCfg(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { + var err error + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(args.YamlData)) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + if args.CommonEnvCfgType == config.CommonEnvCfgTypePvc { + if u.GetKind() != setting.PersistentVolumeClaim { + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) + } + } else if u.GetKind() != string(args.CommonEnvCfgType) { + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) + } + + switch args.CommonEnvCfgType { + case config.CommonEnvCfgTypeConfigMap: + err = UpdateConfigMap(args, userName, userID, log) + case config.CommonEnvCfgTypeSecret: + err = UpdateSecret(args, userName, userID, log) + case config.CommonEnvCfgTypeIngress: + err = UpdateOrCreateIngress(args, userName, userID, false, log) + case config.CommonEnvCfgTypePvc: + err = UpdatePvc(args, userName, userID, log) + default: + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("%s is not support update", args.CommonEnvCfgType)) + } + if err != nil { + log.Error(err) + return err + } + return nil +} + +type ListCommonEnvCfgHistoryArgs struct { + EnvName string `json:"envName"` + ProductName string `json:"productName"` + Name string + CommonEnvCfgType config.CommonEnvCfgType `json:"commonEnvCfgType"` +} +type ListCommonEnvCfgHistoryRes struct { + ID primitive.ObjectID `json:"id,omitempty"` + ProductName string `json:"product_name"` + CreateTime int64 `json:"create_time"` + UpdateUserName string `json:"update_user_name"` + Namespace string `json:"namespace,omitempty"` + EnvName string `json:"env_name"` + Name string `json:"name"` + YamlData string `json:"yaml_data"` +} + +func ListCommonEnvCfgHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredLogger) ([]*ListCommonEnvCfgHistoryRes, error) { + opts := &commonrepo.ListEnvCfgOption{ + IsSort: true, + ProductName: args.ProductName, + EnvName: args.EnvName, + Name: args.Name, + } + switch args.CommonEnvCfgType { + case config.CommonEnvCfgTypeConfigMap: + res, err := commonrepo.NewConfigMapColl().List(opts) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + var listRes []*ListCommonEnvCfgHistoryRes + for _, resElem := range res { + listElem := &ListCommonEnvCfgHistoryRes{ + ID: resElem.ID, + ProductName: resElem.ProductName, + CreateTime: resElem.CreateTime, + UpdateUserName: resElem.UpdateUserName, + Namespace: resElem.Namespace, + EnvName: resElem.EnvName, + Name: resElem.Name, + YamlData: resElem.YamlData, + } + listRes = append(listRes, listElem) + } + return listRes, nil + case config.CommonEnvCfgTypeSecret: + res, err := commonrepo.NewSecretColl().List(opts) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + var listRes []*ListCommonEnvCfgHistoryRes + for _, resElem := range res { + listElem := &ListCommonEnvCfgHistoryRes{ + ID: resElem.ID, + ProductName: resElem.ProductName, + CreateTime: resElem.CreateTime, + UpdateUserName: resElem.UpdateUserName, + Namespace: resElem.Namespace, + EnvName: resElem.EnvName, + Name: resElem.Name, + YamlData: resElem.YamlData, + } + listRes = append(listRes, listElem) + } + return listRes, nil + case config.CommonEnvCfgTypeIngress: + res, err := commonrepo.NewIngressColl().List(opts) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + var listRes []*ListCommonEnvCfgHistoryRes + for _, resElem := range res { + listElem := &ListCommonEnvCfgHistoryRes{ + ID: resElem.ID, + ProductName: resElem.ProductName, + CreateTime: resElem.CreateTime, + UpdateUserName: resElem.UpdateUserName, + Namespace: resElem.Namespace, + EnvName: resElem.EnvName, + Name: resElem.Name, + YamlData: resElem.YamlData, + } + listRes = append(listRes, listElem) + } + return listRes, nil + case config.CommonEnvCfgTypePvc: + res, err := commonrepo.NewPvcColl().List(opts) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + var listRes []*ListCommonEnvCfgHistoryRes + for _, resElem := range res { + listElem := &ListCommonEnvCfgHistoryRes{ + ID: resElem.ID, + ProductName: resElem.ProductName, + CreateTime: resElem.CreateTime, + UpdateUserName: resElem.UpdateUserName, + Namespace: resElem.Namespace, + EnvName: resElem.EnvName, + Name: resElem.Name, + YamlData: resElem.YamlData, + } + listRes = append(listRes, listElem) + } + return listRes, nil + default: + return nil, e.ErrUpdateResource.AddDesc(fmt.Sprintf("%s is not support update", args.CommonEnvCfgType)) + } +} + +func restartPod(name, productName, envName, namespace string, commonEnvCfgType config.CommonEnvCfgType, clientset *kubernetes.Clientset, kubcli client.Client) error { + opts := &commonrepo.ListEnvSvcDependOption{ + ProductName: productName, + EnvName: envName, + } + envSvcProducts, err := commonrepo.NewEnvSvcDependColl().List(opts) + if err != nil { + return err + } + tplProduct, err := template.NewProductColl().Find(productName) + if err != nil { + return err + } + + for _, esp := range envSvcProducts { + switch commonEnvCfgType { + case config.CommonEnvCfgTypeConfigMap: + if !checkExistInList(name, esp.ConfigMaps) { + continue + } + case config.CommonEnvCfgTypeSecret: + if !checkExistInList(name, esp.Secrets) { + continue + } + case config.CommonEnvCfgTypePvc: + if !checkExistInList(name, esp.Pvcs) { + continue + } + default: + return fmt.Errorf("unsupport type %s to restart service", commonEnvCfgType) + } + + restartArgs := &SvcOptArgs{ + EnvName: envName, + ProductName: productName, + ServiceName: esp.ServiceName, + } + if tplProduct.ProductFeature.DeployType == "k8s" { + if err := restartK8sPod(restartArgs, namespace, clientset); err != nil { + log.Error(err) + return err + } + continue + } + + if err := restartPodHelmByWorkload(restartArgs, namespace, clientset, kubcli); err != nil { + log.Error(err) + return err + } + } + return nil +} + +func restartK8sPod(args *SvcOptArgs, ns string, clientset *kubernetes.Clientset) error { + selector := labels.Set{setting.ProductLabel: args.ProductName, setting.ServiceLabel: args.ServiceName}.AsSelector() + log.Infof("deleting pod from %s where %s", ns, selector) + return updater.DeletePods(ns, selector, clientset) +} + +func restartPodHelmByWorkload(args *SvcOptArgs, ns string, clientset *kubernetes.Clientset, kucli client.Client) error { + deployment, found, err := getter.GetDeployment(ns, args.ServiceName, kucli) + if err != nil { + return err + } + if found { + selector := labels.Set(deployment.GetLabels()).AsSelector() + log.Infof("deleting deploy %s pod from %s where %s", args.ServiceName, ns, selector) + return updater.DeletePods(ns, selector, clientset) + } + + sts, found, err := getter.GetStatefulSet(ns, args.ServiceName, kucli) + if err != nil { + return err + } + if found { + selector := labels.Set(sts.GetLabels()).AsSelector() + log.Infof("deleting sts %s pod from %s where %s", args.ServiceName, ns, selector) + return updater.DeletePods(ns, selector, clientset) + } + + return nil +} + +func checkExistInList(target string, arrList []string) bool { + for _, arr := range arrList { + if target == arr { + return true + } + } + return false +} diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go new file mode 100644 index 00000000..01296d32 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go @@ -0,0 +1,379 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "sync" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" +) + +var ClusterInformersMap sync.Map +var ClusterStopChanMap sync.Map + +func NewClusterInformerFactory(clusterID string, cls *kubernetes.Clientset) (informers.SharedInformerFactory, error) { + if clusterID == "" { + clusterID = setting.LocalClusterID + } + if informer, ok := ClusterInformersMap.Load(clusterID); ok { + return informer.(informers.SharedInformerFactory), nil + } + + informerFactory := informers.NewSharedInformerFactoryWithOptions(cls, time.Minute) + informerFactory.Apps().V1().Deployments().Lister() + informerFactory.Apps().V1().Deployments().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + go onDeploymentAddAndUpdate(obj) + }, + UpdateFunc: func(oldObj interface{}, newObj interface{}) { + newDeploy := newObj.(*appsv1.Deployment) + oldDeploy := oldObj.(*appsv1.Deployment) + if newDeploy.ResourceVersion == oldDeploy.ResourceVersion { + return + } + go onDeploymentAddAndUpdate(newObj) + }, + DeleteFunc: onDeploymentDelete, + }) + + informerFactory.Apps().V1().StatefulSets().Lister() + informerFactory.Apps().V1().StatefulSets().Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + go onStatefulSetAddAndUpdate(obj) + }, + UpdateFunc: func(oldObj interface{}, newObj interface{}) { + newDeploy := newObj.(*appsv1.StatefulSet) + oldDeploy := oldObj.(*appsv1.StatefulSet) + if newDeploy.ResourceVersion == oldDeploy.ResourceVersion { + return + } + go onStatefulSetAddAndUpdate(newObj) + }, + DeleteFunc: onStatefulSetDelete, + }) + + stopchan := make(chan struct{}) + informerFactory.Start(stopchan) + informerFactory.WaitForCacheSync(make(chan struct{})) + if _, ok := ClusterInformersMap.Load(clusterID); ok { + if stopchan, ok := ClusterStopChanMap.Load(clusterID); ok { + close(stopchan.(chan struct{})) + ClusterStopChanMap.Delete(clusterID) + } + } + ClusterInformersMap.Store(clusterID, informerFactory) + ClusterStopChanMap.Store(clusterID, stopchan) + return informerFactory, nil +} + +func DeleteClusterInformer(clusterID string) { + if _, ok := ClusterInformersMap.Load(clusterID); ok { + if stopchan, ok := ClusterStopChanMap.Load(clusterID); ok { + close(stopchan.(chan struct{})) + ClusterStopChanMap.Delete(clusterID) + } + } + ClusterInformersMap.Delete(clusterID) +} + +func onDeploymentAddAndUpdate(obj interface{}) { + deploy := obj.(*appsv1.Deployment) + product, continueFlag := GetProductAndFilterNs(deploy.Namespace, deploy.Name) + if !continueFlag { + return + } + labels := deploy.GetLabels() + label := labels[setting.ServiceLabel] + + configMaps, secrets, pvcs := VisitDeployment(deploy) + envSvcDepend := &models.EnvSvcDepend{ + ProductName: product.ProductName, + EnvName: product.EnvName, + Namespace: deploy.Namespace, + ServiceName: label, + ServiceModule: deploy.Name, + WorkloadType: setting.Deployment, + ConfigMaps: configMaps.List(), + Pvcs: pvcs.List(), + Secrets: secrets.List(), + } + if err := commonrepo.NewEnvSvcDependColl().CreateOrUpdate(envSvcDepend); err != nil { + log.Error("onDeploymentAdd EnvSvcDepend CreateOrUpdate error:", err) + } +} + +func onDeploymentDelete(obj interface{}) { + deploy := obj.(*appsv1.Deployment) + product, continueFlag := GetProductAndFilterNs(deploy.Namespace, deploy.Name) + if !continueFlag { + return + } + opts := &commonrepo.DeleteEnvSvcDependOption{ + ProductName: product.ProductName, + EnvName: product.EnvName, + ServiceModule: deploy.Name, + } + if err := commonrepo.NewEnvSvcDependColl().Delete(opts); err != nil { + log.Error("onDeploymentDelete EnvSvcDepend Delete error:", err) + } +} + +func VisitDeployment(deployment *appsv1.Deployment) (sets.String, sets.String, sets.String) { + cfgSets := sets.NewString() + secretSets := sets.NewString() + pvcSets := sets.NewString() + for _, initCon := range deployment.Spec.Template.Spec.InitContainers { + cfg, sec := visitContainerConfigmapAndSecretNames(initCon) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + } + for _, con := range deployment.Spec.Template.Spec.Containers { + cfg, sec := visitContainerConfigmapAndSecretNames(con) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + } + for _, ephemeralCon := range deployment.Spec.Template.Spec.EphemeralContainers { + cfg, sec := visitContainerConfigmapAndSecretNames(corev1.Container(ephemeralCon.DeepCopy().EphemeralContainerCommon)) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + } + for _, volume := range deployment.Spec.Template.Spec.Volumes { + cfg, sec, pvc := visitVolumeConfigmapAndSecretAndPvcNames(volume) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + pvcSets = pvcSets.Union(pvc) + } + for _, sec := range deployment.Spec.Template.Spec.ImagePullSecrets { + secretSets.Insert(sec.Name) + } + return cfgSets, secretSets, pvcSets +} + +func onStatefulSetAddAndUpdate(obj interface{}) { + sts := obj.(*appsv1.StatefulSet) + product, continueFlag := GetProductAndFilterNs(sts.Namespace, sts.Name) + if !continueFlag { + return + } + labels := sts.GetLabels() + label := labels[setting.ServiceLabel] + configMaps, secrets, pvcs := VisitStatefulSet(sts) + envSvcDepend := &models.EnvSvcDepend{ + ProductName: product.ProductName, + EnvName: product.EnvName, + Namespace: sts.Namespace, + ServiceName: label, + ServiceModule: sts.Name, + WorkloadType: setting.StatefulSet, + ConfigMaps: configMaps.List(), + Pvcs: pvcs.List(), + Secrets: secrets.List(), + } + + if err := commonrepo.NewEnvSvcDependColl().CreateOrUpdate(envSvcDepend); err != nil { + log.Error("onStatefulSetAdd EnvSvcDepend CreateOrUpdate error:", err) + } +} + +func onStatefulSetDelete(obj interface{}) { + sts := obj.(*appsv1.StatefulSet) + product, continueFlag := GetProductAndFilterNs(sts.Namespace, sts.Name) + if !continueFlag { + return + } + opts := &commonrepo.DeleteEnvSvcDependOption{ + ProductName: product.ProductName, + EnvName: product.EnvName, + ServiceModule: sts.Name, + } + if err := commonrepo.NewEnvSvcDependColl().Delete(opts); err != nil { + log.Error("onStatefulSetDelete EnvSvcDepend Delete error:", err) + } +} + +func VisitStatefulSet(sts *appsv1.StatefulSet) (sets.String, sets.String, sets.String) { + cfgSets := sets.NewString() + secretSets := sets.NewString() + pvcSets := sets.NewString() + for _, initCon := range sts.Spec.Template.Spec.InitContainers { + cfg, sec := visitContainerConfigmapAndSecretNames(initCon) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + } + for _, con := range sts.Spec.Template.Spec.Containers { + cfg, sec := visitContainerConfigmapAndSecretNames(con) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + } + for _, ephemeralCon := range sts.Spec.Template.Spec.EphemeralContainers { + cfg, sec := visitContainerConfigmapAndSecretNames(corev1.Container(ephemeralCon.DeepCopy().EphemeralContainerCommon)) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + } + for _, volume := range sts.Spec.Template.Spec.Volumes { + cfg, sec, pvc := visitVolumeConfigmapAndSecretAndPvcNames(volume) + cfgSets = cfgSets.Union(cfg) + secretSets = secretSets.Union(sec) + pvcSets = pvcSets.Union(pvc) + } + for _, sec := range sts.Spec.Template.Spec.ImagePullSecrets { + secretSets.Insert(sec.Name) + } + return cfgSets, secretSets, pvcSets +} + +func GetProductAndFilterNs(namespace, workloadName string) (*models.Product, bool) { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Namespace: namespace}) + if err != nil { + if !commonrepo.IsErrNoDocuments(err) { + log.Error("GetProductAndFilterNs err:", err) + } + return nil, false + } + + for _, product := range products { + if !product.IsExisted { + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return nil, false + } + ns, err := clientset.CoreV1().Namespaces().Get(context.Background(), namespace, metav1.GetOptions{}) + if err != nil { + return nil, false + } + labels := ns.GetLabels() + if val, ok := labels[setting.EnvCreatedBy]; !ok || val != setting.EnvCreator { + return nil, false + } + } + for _, psvc := range product.GetServiceMap() { + //TODO: helm not support restart service + if psvc.Type == setting.HelmDeployType && psvc.ServiceName == util.ExtraServiceName(workloadName, namespace) { + return product, false + } + if psvc.Type == setting.K8SDeployType && psvc.ServiceName == workloadName { + return product, true + } + } + } + return nil, false +} + +func visitContainerConfigmapAndSecretNames(container corev1.Container) (sets.String, sets.String) { + cfgSets := sets.NewString() + secretSets := sets.NewString() + for _, env := range container.EnvFrom { + if env.ConfigMapRef != nil { + cfgSets.Insert(env.ConfigMapRef.Name) + } + if env.SecretRef != nil { + secretSets.Insert(env.SecretRef.Name) + } + } + for _, envVar := range container.Env { + if envVar.ValueFrom != nil && envVar.ValueFrom.ConfigMapKeyRef != nil { + cfgSets.Insert(envVar.ValueFrom.ConfigMapKeyRef.Name) + } + if envVar.ValueFrom != nil && envVar.ValueFrom.SecretKeyRef != nil { + secretSets.Insert(envVar.ValueFrom.SecretKeyRef.Name) + } + } + return cfgSets, secretSets +} + +func visitVolumeConfigmapAndSecretAndPvcNames(volume corev1.Volume) (sets.String, sets.String, sets.String) { + cfgSets := sets.NewString() + secretSets := sets.NewString() + pvcSets := sets.NewString() + source := &volume.VolumeSource + switch { + case source.Projected != nil: + for j := range source.Projected.Sources { + if source.Projected.Sources[j].ConfigMap != nil { + cfgSets.Insert(source.Projected.Sources[j].ConfigMap.Name) + } + if source.Projected.Sources[j].Secret != nil { + secretSets.Insert(source.Projected.Sources[j].Secret.Name) + } + } + case source.ConfigMap != nil: + cfgSets.Insert(source.ConfigMap.Name) + case source.Secret != nil: + secretSets.Insert(source.Secret.SecretName) + case source.PersistentVolumeClaim != nil: + pvcSets.Insert(source.PersistentVolumeClaim.ClaimName) + } + return cfgSets, secretSets, pvcSets +} + +func StartClusterInformer() { + for { + k8sClusters, err := mongodb.NewK8SClusterColl().List(nil) + if err != nil && !mongodb.IsErrNoDocuments(err) { + log.Error("StartClusterInformer list cluster error:%s", err) + return + } + + for _, cluster := range k8sClusters { + if cluster.Status != setting.Normal { + DeleteClusterInformer(cluster.ID.Hex()) + continue + } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), cluster.ID.Hex()) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", cluster.ID.Hex(), err) + continue + } + + _, err = NewClusterInformerFactory(cluster.ID.Hex(), clientset) + if err != nil { + log.Errorf("failed to NewClusterInformerFactory clusterID: %s, the error is: %s", cluster.ID.Hex(), err) + } + } + + deleteClustInf := func(key, value interface{}) bool { + for _, cluster := range k8sClusters { + if key.(string) == cluster.ID.Hex() { + return true + } + } + DeleteClusterInformer(key.(string)) + return true + } + ClusterInformersMap.Range(deleteClustInf) + + time.Sleep(5 * time.Second) + } +} diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 94a7c56e..30108bcf 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -17,20 +17,25 @@ limitations under the License. package service import ( + "encoding/json" "fmt" "sort" "strings" + "sync" "time" + "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commontpl "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" @@ -57,11 +62,13 @@ type RollBackConfigMapArgs struct { } type UpdateConfigMapArgs struct { - EnvName string `json:"env_name"` - ProductName string `json:"product_name"` - ServiceName string `json:"service_name"` - ConfigName string `json:"config_name"` - Data map[string]string `json:"data"` + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + ServiceName string `json:"service_name"` + ConfigName string `json:"config_name"` + YamlData string `json:"yaml_data"` + RestartAssociatedSvc bool `json:"restart_associated_svc"` + Services []string `json:"services"` } type configMap struct { @@ -78,6 +85,16 @@ type configMapWithHistory struct { HistoricalRevisions []*configMap `json:"historicalRevisions"` } +type ListConfigMapRes struct { + CmName string `json:"cm_name"` + Immutable bool `json:"immutable"` + CmData map[string]string `json:"cm_data"` + YamlData string `json:"yaml_data"` + UpdateUserName string `json:"update_username"` + CreateTime time.Time `json:"create_time"` + Services []string `json:"services"` +} + func generateConfigMapResponse(cm *corev1.ConfigMap) *configMap { icm := wrapper.ConfigMap(cm) u, _ := icm.LastUpdateTime() @@ -91,8 +108,11 @@ func generateConfigMapResponse(cm *corev1.ConfigMap) *configMap { } } -func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*configMapWithHistory, error) { - selector := labels.Set{setting.ProductLabel: args.ProductName, setting.ServiceLabel: args.ServiceName}.AsSelector() +func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListConfigMapRes, error) { + selector := labels.Set{setting.ProductLabel: args.ProductName}.AsSelector() + if args.ServiceName != "" { + selector = labels.Set{setting.ProductLabel: args.ProductName, setting.ServiceLabel: args.ServiceName}.AsSelector() + } product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: args.ProductName, @@ -112,122 +132,136 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*configM return nil, e.ErrListConfigMaps.AddDesc(err.Error()) } - // 为了兼容老数据,需要找到当前使用的cm,即labels中没有config-backup=true的cm - // 当前使用的cm需要放在数组的首位,备份的cm则按照创建时间倒序 - cmWithHistory := make(map[string][]*configMap) - cmWithName := make(map[string]*configMap) - for _, cm := range cms { - icm := wrapper.ConfigMap(cm) - res := generateConfigMapResponse(cm) - if icm.Active() { - cmWithName[res.Name] = res - } else { - owner := icm.Owner() - if owner == "" { - continue + envSvcDepends, err := commonrepo.NewEnvSvcDependColl().List(&commonrepo.ListEnvSvcDependOption{ProductName: args.ProductName, EnvName: args.EnvName}) + if err != nil && commonrepo.IsErrNoDocuments(err) { + return nil, e.ErrListResources.AddDesc(err.Error()) + } + + var res []*ListConfigMapRes + var mutex sync.Mutex + var wg sync.WaitGroup + cmProcess := func(cm *corev1.ConfigMap) error { + for labelKey, labelVal := range cm.GetLabels() { + if labelKey == setting.ConfigBackupLabel && labelVal == setting.LabelValueTrue { + return nil } - cmWithHistory[owner] = append(cmWithHistory[owner], res) } - } + cm.SetManagedFields(nil) + cm.SetResourceVersion("") + yamlData, err := yaml.Marshal(cm) + if err != nil { + return err + } - res := make([]*configMapWithHistory, 0, len(cmWithName)) - for name, cm := range cmWithName { - history := cmWithHistory[name] - if len(history) > 1 { - sort.SliceStable(history, func(i, j int) bool { - return history[i].lastUpdateTime.After(history[j].lastUpdateTime) - }) + var tempSvcs []string + LB: + for _, svcDepend := range envSvcDepends { + for _, ccm := range svcDepend.ConfigMaps { + if ccm == cm.Name { + tempSvcs = append(tempSvcs, svcDepend.ServiceModule) + continue LB + } + } } - res = append(res, &configMapWithHistory{ - Current: cm, - HistoricalRevisions: history, - }) + immutable := false + if cm.Immutable != nil { + immutable = *cm.Immutable + } + resElem := &ListConfigMapRes{ + CmName: cm.Name, + Immutable: immutable, + CmData: cm.Data, + YamlData: string(yamlData), + Services: tempSvcs, + CreateTime: cm.GetCreationTimestamp().Time, + } + mutex.Lock() + res = append(res, resElem) + mutex.Unlock() + return nil + } + + for _, cmElem := range cms { + wg.Add(1) + go func(cm *corev1.ConfigMap) { + defer wg.Done() + err := cmProcess(cm) + if err != nil { + log.Errorf("ListConfigMaps ns:%s name:%s cmProcess err:%s", cm.Namespace, cm.Name, err) + } + }(cmElem) } - + wg.Wait() return res, nil } -func UpdateConfigMap(envName string, args *UpdateConfigMapArgs, userName, userID string, log *zap.SugaredLogger) error { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: args.ProductName, - EnvName: args.EnvName, - }) +func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { + js, err := yaml.YAMLToJSON([]byte(args.YamlData)) + cm := &corev1.ConfigMap{} + err = json.Unmarshal(js, cm) if err != nil { return e.ErrUpdateConfigMap.AddErr(err) } - kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) - if err != nil { - return e.ErrUpdateConfigMap.AddErr(err) + if cm.Name != args.Name { + return e.ErrUpdateConfigMap.AddDesc("configMap Yaml Name is incorrect") } - - clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) if err != nil { - log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) return e.ErrUpdateConfigMap.AddErr(err) } namespace := product.Namespace - cfg, found, err := getter.GetConfigMap(namespace, args.ConfigName, kubeClient) - if err != nil { - log.Error(err) - return e.ErrGetConfigMap.AddDesc(err.Error()) - } else if !found { - return e.ErrGetConfigMap.AddDesc("configMap not found") - } - - if err := archiveConfigMap(namespace, cfg, kubeClient, log); err != nil { - return err - } - - // 将configMap中的变量进行渲染 renderSet, err := commonservice.GetRenderSet(namespace, 0, log) if err != nil { log.Errorf("Failed to find render set for product template %s, err: %v", product.ProductName, err) return err } - - // 渲染变量 - for key, value := range args.Data { + for key, value := range cm.Data { for _, kv := range renderSet.KVs { value = strings.Replace(value, kv.Alias, kv.Value, -1) } value = kube.ParseSysKeys(product.Namespace, product.EnvName, product.ProductName, args.ServiceName, value) - args.Data[key] = value + cm.Data[key] = value } - cfg.Data = args.Data - // 记录修改configmap的用户 - cfg.Labels[setting.UpdateBy] = kube.MakeSafeLabelValue(userName) - cfg.Labels[setting.UpdateByID] = userID - cfg.Labels[setting.UpdateTime] = time.Now().Format("20060102150405") - cfg.Labels[setting.DirtyLabel] = setting.LabelValueTrue - - as := cfg.GetAnnotations() - if as == nil { - as = make(map[string]string) + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrUpdateConfigMap.AddErr(err) } - as[setting.ModifiedByAnnotation] = userName - as[setting.EditorIDAnnotation] = userID - as[setting.LastUpdateTimeAnnotation] = util.FormatTime(time.Now()) - cfg.SetAnnotations(as) - - if err := updater.UpdateConfigMap(namespace, cfg, clientset); err != nil { + if err := updater.UpdateConfigMap(namespace, cm, clientset); err != nil { log.Error(err) return e.ErrUpdateConfigMap.AddDesc(err.Error()) } - - restartArgs := &SvcOptArgs{ - EnvName: envName, - ProductName: args.ProductName, - ServiceName: args.ServiceName, + envcm := &models.EnvConfigMap{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: cm.Name, + YamlData: args.YamlData, } - - if err := restartPod(restartArgs, namespace, clientset, log); err != nil { - log.Error(err) + if err := commonrepo.NewConfigMapColl().Create(envcm, true); err != nil { + return e.ErrUpdateConfigMap.AddDesc(err.Error()) + } + tplProduct, err := commontpl.NewProductColl().Find(args.ProductName) + if err != nil { + return e.ErrUpdateConfigMap.AddDesc(err.Error()) + } + //TODO: helm not support restart service + if !args.RestartAssociatedSvc || tplProduct.ProductFeature.DeployType != setting.K8SDeployType { + return nil + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return e.ErrUpdateConfigMap.AddErr(err) + } + if err := restartPod(cm.Name, args.ProductName, args.EnvName, namespace, config.CommonEnvCfgTypeConfigMap, clientset, kubeClient); err != nil { return e.ErrRestartService.AddDesc(err.Error()) } - return nil } @@ -291,7 +325,7 @@ func RollBackConfigMap(envName string, args *RollBackConfigMapArgs, userName, us ServiceName: args.ServiceName, } - if err := restartPod(restartArgs, namespace, clientset, log); err != nil { + if err := restartK8sPod(restartArgs, namespace, clientset); err != nil { log.Error(err) return e.ErrRestartService.AddDesc(err.Error()) } @@ -359,9 +393,94 @@ func cleanArchiveConfigMap(namespace string, ls map[string]string, kubeClient cl } } -func restartPod(args *SvcOptArgs, ns string, clientset *kubernetes.Clientset, log *zap.SugaredLogger) error { - selector := labels.Set{setting.ProductLabel: args.ProductName, setting.ServiceLabel: args.ServiceName}.AsSelector() +type MigrateHistoryConfigMapsRes struct { +} + +func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogger) (*MigrateHistoryConfigMapsRes, error) { - log.Infof("deleting pod from %s where %s", ns, selector) - return updater.DeletePods(ns, selector, clientset) + products := make([]*models.Product, 0) + var err error + if productName != "" { + products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + EnvName: envName, + ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusCreating}, + }) + } else { + products, err = commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + ExcludeStatus: []string{setting.ProductStatusDeleting, setting.ProductStatusCreating}, + }) + } + if err != nil && err != mongo.ErrNoDocuments { + return nil, err + } + + for _, product := range products { + if product.IsExisted { + continue + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + cms, err := getter.ListConfigMaps(product.Namespace, labels.Set{setting.ProductLabel: product.ProductName, setting.ConfigBackupLabel: setting.LabelValueTrue}.AsSelector(), kubeClient) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + LB: + for _, cm := range cms { + cmNames := strings.Split(cm.Name, "-bak-") + cmName := cmNames[0] + envCm := &models.EnvConfigMap{ + ProductName: product.ProductName, + EnvName: product.EnvName, + Namespace: product.Namespace, + Name: cmName, + } + cmLables := cm.GetLabels() + if _, ok := cmLables[setting.UpdateTime]; !ok { + tm, err := time.Parse("20060102150405", cmLables[setting.UpdateTime]) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + envCm.CreateTime = tm.Unix() + } + + delete(cmLables, setting.UpdateTime) + delete(cmLables, setting.ConfigBackupLabel) + cm.SetLabels(cmLables) + cm.SetManagedFields(nil) + cm.SetAnnotations(make(map[string]string)) + cm.SetName(cmName) + + yamlData, err := yaml.Marshal(cm) + if err != nil { + log.Error(err) + return nil, e.ErrListResources.AddDesc(err.Error()) + } + envCm.YamlData = string(yamlData) + + opt := &commonrepo.ListEnvCfgOption{ + ProductName: product.ProductName, + EnvName: product.EnvName, + Namespace: product.Namespace, + Name: cmName, + } + envCfgsOlder, err := commonrepo.NewConfigMapColl().List(opt) + if err != nil && commonrepo.IsErrNoDocuments(err) { + return nil, e.ErrListResources.AddErr(err) + } + for _, cfg := range envCfgsOlder { + if envCm.CreateTime == cfg.CreateTime { + continue LB + } + } + + err = commonrepo.NewConfigMapColl().Create(envCm, false) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + } + } + return nil, nil } diff --git a/pkg/microservice/aslan/core/environment/service/env.go b/pkg/microservice/aslan/core/environment/service/env.go index f56c2a54..ba146709 100644 --- a/pkg/microservice/aslan/core/environment/service/env.go +++ b/pkg/microservice/aslan/core/environment/service/env.go @@ -31,6 +31,7 @@ type envHandle interface { listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp updateService(args *SvcOptArgs) error queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient informers.SharedInformerFactory) (string, string, []string) + initEnvConfigSet(envName, productName, username string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error } func envHandleFunc(projectType string, log *zap.SugaredLogger) envHandle { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7ab23b05..3a7d1a5e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -167,17 +167,18 @@ type EnvRendersetArg struct { } type CreateHelmProductArg struct { - ProductName string `json:"productName"` - EnvName string `json:"envName"` - Namespace string `json:"namespace"` - ClusterID string `json:"clusterID"` - DefaultValues string `json:"defaultValues"` - ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` - RegistryID string `json:"registry_id"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` - BaseEnvName string `json:"baseEnvName"` - BaseName string `json:"base_name,omitempty"` - IsExisted bool `json:"is_existed"` + ProductName string `json:"productName"` + EnvName string `json:"envName"` + Namespace string `json:"namespace"` + ClusterID string `json:"clusterID"` + DefaultValues string `json:"defaultValues"` + ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` + RegistryID string `json:"registry_id"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + BaseEnvName string `json:"baseEnvName"` + BaseName string `json:"base_name,omitempty"` + IsExisted bool `json:"is_existed"` + EnvConfigYamls []string `json:"env_config_yamls,omitempty"` } type UpdateMultiHelmProductArg struct { @@ -867,6 +868,7 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, IsForkedProduct: false, RegistryID: registryID, IsExisted: arg.IsExisted, + EnvConfigYamls: arg.EnvConfigYamls, } customChartValueMap := make(map[string]*commonservice.RenderChartArg) @@ -1108,6 +1110,7 @@ func copySingleHelmProduct(productName, requestID, userName string, arg *CreateH productInfo.ClusterID = arg.ClusterID productInfo.BaseName = arg.BaseName productInfo.Namespace = commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace) + productInfo.EnvConfigYamls = arg.EnvConfigYamls opt := &commonrepo.RenderSetFindOption{ Name: productInfo.Render.Name, @@ -2201,22 +2204,29 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e errorMsg = err.Error() // 发送创建产品失败消息给用户 - title := fmt.Sprintf("创建 [%s] 的 [%s] 环境失败", args.ProductName, args.EnvName) + title := fmt.Sprintf("创建 [%s] 的 [%s] 环境失败:%s", args.ProductName, args.EnvName, errorMsg) commonservice.SendErrorMessage(user, title, requestID, err, log) } commonservice.LogProductStats(envName, setting.CreateProductEvent, args.ProductName, requestID, eventStart, log) if err := commonrepo.NewProductColl().UpdateStatus(envName, args.ProductName, status); err != nil { - log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, args.ProductName, err) + log.Errorf("[%s][P:%s] Product.UpdateStatus error: %s", envName, args.ProductName, err) return } if err := commonrepo.NewProductColl().UpdateErrors(envName, args.ProductName, errorMsg); err != nil { - log.Errorf("[%s][P:%s] Product.UpdateErrors error: %v", envName, args.ProductName, err) + log.Errorf("[%s][P:%s] Product.UpdateErrors error: %s", envName, args.ProductName, err) return } }() + err = envHandleFunc(getProjectType(args.ProductName), log).initEnvConfigSet(envName, args.ProductName, user, args.EnvConfigYamls, informer, kubeClient) + if err != nil { + args.Status = setting.ProductStatusFailed + log.Errorf("initEnvConfigSet error :%s", err) + return + } + for _, group := range args.Services { err = envHandleFunc(getProjectType(args.ProductName), log).createGroup(envName, args.ProductName, user, group, renderSet, informer, kubeClient) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index a45e3234..5e9f50f0 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -142,6 +142,10 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P log.Errorf("[%s][%s] GetKubeClient error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) } + cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + return e.ErrCreateEnv.AddDesc(err.Error()) + } //判断namespace是否存在 namespace := args.GetNamespace() @@ -219,7 +223,21 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P } eventStart := time.Now().Unix() - + inf, err := informer.NewInformer(clusterID, args.Namespace, cls) + if err != nil { + log.Errorf("failed to create informer from clientset for clusterID: %s, the error is: %s", clusterID, err) + return nil + } + err = helmInitEnvConfigSet(args.EnvName, args.ProductName, user, args.EnvConfigYamls, inf, kubeClient) + if err != nil { + log.Errorf("failed to helmInitEnvConfigSet [%s][P:%s]: %s, the error is: %s", args.EnvName, args.ProductName, err) + if err := commonrepo.NewProductColl().UpdateStatus(args.EnvName, args.ProductName, setting.ProductStatusFailed); err != nil { + log.Errorf("helmInitEnvConfigSet [%s][P:%s] Product.UpdateStatus error: %s", args.EnvName, args.ProductName, err) + } + if err := commonrepo.NewProductColl().UpdateErrors(args.EnvName, args.ProductName, err.Error()); err != nil { + log.Errorf("helmInitEnvConfigSet [%s][P:%s] Product.UpdateErrors error: %s", args.EnvName, args.ProductName, err) + } + } go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 743a7594..5ba37919 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -26,7 +26,11 @@ import ( "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" "go.uber.org/zap" + "helm.sh/helm/v3/pkg/releaseutil" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/informers" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -38,6 +42,8 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/kube/serializer" + "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" @@ -289,3 +295,113 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog return ret, nil } + +func helmInitEnvConfigSet(envName, productName, userName string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { + return initEnvConfigSetAction(envName, productName, userName, envConfigYamls, inf, kubeClient) +} + +func initEnvConfigSetAction(envName, productName, userName string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { + errList := &multierror.Error{ + ErrorFormat: func(es []error) string { + format := "创建环境配置" + if len(es) == 1 { + return fmt.Sprintf(format+" %s 失败:%s", envName, es[0]) + } + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %s", err) + } + return fmt.Sprintf(format+" %s 失败:\n%s", envName, strings.Join(points, "\n")) + }, + } + + clusterLabels := getPredefinedClusterLabels(productName, "", envName) + delete(clusterLabels, "s-service") + for _, envConfigYaml := range envConfigYamls { + manifests := releaseutil.SplitManifests(envConfigYaml) + resources := make([]*unstructured.Unstructured, 0, len(manifests)) + for _, item := range manifests { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(item)) + if err != nil { + log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %s", item, err) + errList = multierror.Append(errList, err) + continue + } + resources = append(resources, u) + } + for _, u := range resources { + switch u.GetKind() { + case setting.ConfigMap, setting.Ingress, setting.Secret, setting.PersistentVolumeClaim: + ls := kube.MergeLabels(clusterLabels, u.GetLabels()) + u.SetNamespace(productName + "-env-" + envName) + u.SetLabels(ls) + + err := updater.CreateOrPatchUnstructuredNeverAnnotation(u, kubeClient) + if err != nil { + log.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + u.SetManagedFields(nil) + yamlData, err := yaml.Marshal(u.UnstructuredContent()) + if err != nil { + log.Errorf("Failed to initEnvConfigSet yaml.Marshal %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + switch u.GetKind() { + case setting.ConfigMap: + envCm := &models.EnvConfigMap{ + ProductName: productName, + UpdateUserName: userName, + EnvName: envName, + Name: u.GetName(), + YamlData: string(yamlData), + } + if err := commonrepo.NewConfigMapColl().Create(envCm, true); err != nil { + errList = multierror.Append(errList, err) + } + case setting.Ingress: + envIngress := &models.EnvIngress{ + ProductName: productName, + UpdateUserName: userName, + EnvName: envName, + Name: u.GetName(), + YamlData: string(yamlData), + } + if err := commonrepo.NewIngressColl().Create(envIngress, true); err != nil { + errList = multierror.Append(errList, err) + } + case setting.Secret: + envSecret := &models.EnvSecret{ + ProductName: productName, + UpdateUserName: userName, + EnvName: envName, + Name: u.GetName(), + YamlData: string(yamlData), + } + if err := commonrepo.NewSecretColl().Create(envSecret, true); err != nil { + errList = multierror.Append(errList, err) + } + case setting.PersistentVolumeClaim: + envPvc := &models.EnvPvc{ + ProductName: productName, + UpdateUserName: userName, + EnvName: envName, + Name: u.GetName(), + YamlData: string(yamlData), + } + if err := commonrepo.NewPvcColl().Create(envPvc, true); err != nil { + errList = multierror.Append(errList, err) + } + } + default: + errList = multierror.Append(errList, fmt.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, "kind not support")) + } + } + } + if len(errList.Errors) == 0 { + return nil + } + return errList +} diff --git a/pkg/microservice/aslan/core/environment/service/ingress.go b/pkg/microservice/aslan/core/environment/service/ingress.go new file mode 100644 index 00000000..ff421de1 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/ingress.go @@ -0,0 +1,223 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "go.uber.org/zap" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + v1 "k8s.io/api/networking/v1" + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/serializer" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +type ListIngressesResponse struct { + IngressName string `json:"ingress_name"` + HostInfo string `json:"host_info"` + Address string `json:"address"` + Ports string `json:"ports"` + ErrorReason string `json:"error_reason"` + YamlData string `json:"yaml_data"` + UpdateUserName string `json:"update_username"` + CreateTime time.Time `json:"create_time"` +} + +func ListIngresses(envName, productName string, log *zap.SugaredLogger) ([]*ListIngressesResponse, error) { + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + kubeCli, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + cliSet, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + version, err := cliSet.Discovery().ServerVersion() + if err != nil { + log.Errorf("Failed to get server version info for cluster: %s, the error is: %s", product.ClusterID, err) + return nil, err + } + ingresss, err := getter.ListIngresses(product.Namespace, kubeCli, kubeclient.VersionLessThan122(version)) + if err != nil { + log.Error(err) + return nil, e.ErrListResources.AddDesc(err.Error()) + } + + var res []*ListIngressesResponse + for _, ingress := range ingresss.Items { + ingress.SetManagedFields(nil) + ingress.SetResourceVersion("") + yamlData, err := yaml.Marshal(ingress) + if err != nil { + log.Error(err) + return nil, e.ErrListResources.AddDesc(err.Error()) + } + + if ingress.GetAPIVersion() == v1.SchemeGroupVersion.String() { + itemJson, err := ingress.MarshalJSON() + if err != nil { + return nil, err + } + itemIngress := &v1.Ingress{} + err = json.Unmarshal(itemJson, itemIngress) + if err != nil { + return nil, err + } + hostInfo := "" + for _, rule := range itemIngress.Spec.Rules { + hostInfo += rule.Host + "," + } + hostInfo = strings.TrimSuffix(hostInfo, ",") + address, ports, errorReason := "", "", "" + for _, addr := range itemIngress.Status.LoadBalancer.Ingress { + address += addr.IP + "," + for _, portStatus := range addr.Ports { + if portStatus.Error != nil { + errorReason += *portStatus.Error + ";" + } else { + ports += string(portStatus.Port) + "," + } + } + } + address = strings.TrimSuffix(address, ",") + errorReason = strings.TrimSuffix(errorReason, ";") + ports = strings.TrimSuffix(ports, ",") + + resElem := &ListIngressesResponse{ + IngressName: ingress.GetName(), + HostInfo: hostInfo, + Ports: ports, + ErrorReason: errorReason, + YamlData: string(yamlData), + Address: address, + } + res = append(res, resElem) + } + + if ingress.GetAPIVersion() == extensionsv1beta1.SchemeGroupVersion.String() { + itemJson, err := ingress.MarshalJSON() + if err != nil { + return nil, err + } + itemIngress := &extensionsv1beta1.Ingress{} + err = json.Unmarshal(itemJson, itemIngress) + if err != nil { + return nil, err + } + hostInfo, ports := "", "" + for _, rule := range itemIngress.Spec.Rules { + hostInfo += rule.Host + "," + if rule.HTTP != nil { + for _, path := range rule.HTTP.Paths { + ports += path.Backend.ServicePort.String() + "," + } + } + } + hostInfo = strings.TrimSuffix(hostInfo, ",") + address, errorReason := "", "" + for _, addr := range itemIngress.Status.LoadBalancer.Ingress { + address += addr.IP + "," + for _, portStatus := range addr.Ports { + if portStatus.Error != nil { + errorReason += *portStatus.Error + ";" + } + } + } + address = strings.TrimSuffix(address, ",") + errorReason = strings.TrimSuffix(errorReason, ";") + ports = strings.TrimSuffix(ports, ",") + + resElem := &ListIngressesResponse{ + IngressName: ingress.GetName(), + HostInfo: hostInfo, + Ports: ports, + ErrorReason: errorReason, + YamlData: string(yamlData), + Address: address, + } + res = append(res, resElem) + } + } + return res, nil +} + +type UpdateIngressArgs struct { + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + IngressName string `json:"ingress_name"` + YamlData string `json:"yaml_data"` + RestartAssociatedSvc bool `json:"restart_associated_svc"` +} + +func UpdateOrCreateIngress(args *UpdateCommonEnvCfgArgs, userName, userID string, isCreate bool, log *zap.SugaredLogger) error { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(args.YamlData)) + if err != nil { + log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %v", args.YamlData, err) + return e.ErrUpdateResource.AddDesc("ingress Yaml Name is incorrect") + } + if !isCreate && u.GetName() != args.Name { + return e.ErrUpdateResource.AddDesc("ingress Yaml Name is incorrect") + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + u.SetNamespace(product.Namespace) + err = updater.UpdateOrCreateUnstructured(u, kubeClient) + if err != nil { + log.Errorf("Failed to UpdateOrCreateIngress %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err) + return e.ErrUpdateResource.AddErr(fmt.Errorf("Failed to UpdateOrCreateIngress %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err)) + } + envIngress := &models.EnvIngress{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: u.GetName(), + YamlData: args.YamlData, + } + if commonrepo.NewIngressColl().Create(envIngress, true) != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + return err +} diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 69931361..bef8587b 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -291,3 +291,7 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] return nil } + +func (k *K8sService) initEnvConfigSet(envName, productName, userName string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { + return initEnvConfigSetAction(envName, productName, userName, envConfigYamls, inf, kubeClient) +} diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 6e1c8cd3..d2de3a07 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -261,3 +261,7 @@ func (p *PMService) createGroup(envName, productName, username string, group []* return nil } + +func (p *PMService) initEnvConfigSet(envName, productName, username string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { + return nil +} diff --git a/pkg/microservice/aslan/core/environment/service/pvc.go b/pkg/microservice/aslan/core/environment/service/pvc.go new file mode 100644 index 00000000..cdb81ee1 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/pvc.go @@ -0,0 +1,203 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + "sync" + "time" + + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +type ListPvcsResponse struct { + PvcName string `json:"pvc_name"` + Status string `json:"status"` + Volume string `json:"volume"` + AccessModes string `json:"access_modes"` + StorageClass string `json:"storageclass"` + Capacity string `json:"capacity"` + YamlData string `json:"yaml_data"` + UpdateUserName string `json:"update_username"` + CreateTime time.Time `json:"create_time"` + Services []string `json:"services"` +} + +func ListPvcs(envName, productName string, log *zap.SugaredLogger) ([]*ListPvcsResponse, error) { + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + + Pvcs, err := getter.ListPvcs(product.Namespace, nil, kubeClient) + if err != nil { + log.Error(err) + return nil, e.ErrListResources.AddDesc(err.Error()) + } + envSvcDepends, err := commonrepo.NewEnvSvcDependColl().List(&commonrepo.ListEnvSvcDependOption{ProductName: productName, EnvName: envName}) + if err != nil && commonrepo.IsErrNoDocuments(err) { + return nil, e.ErrListResources.AddDesc(err.Error()) + } + + var res []*ListPvcsResponse + var mutex sync.Mutex + var wg sync.WaitGroup + pvcProcess := func(pvc *v1.PersistentVolumeClaim) error { + pvc.SetManagedFields(nil) + pvc.SetResourceVersion("") + yamlData, err := yaml.Marshal(pvc) + if err != nil { + return err + } + accessModes := "" + lenAm := len(pvc.Spec.AccessModes) + for i, am := range pvc.Spec.AccessModes { + accessModes += string(am) + if i < lenAm-1 { + accessModes += "," + } + } + var tempSvcs []string + LB: + for _, svcDepend := range envSvcDepends { + for _, pvcc := range svcDepend.Pvcs { + if pvcc == pvc.Name { + tempSvcs = append(tempSvcs, svcDepend.ServiceModule) + continue LB + } + } + } + + storageClassName := "" + if pvc.Spec.StorageClassName != nil { + storageClassName = *pvc.Spec.StorageClassName + } + capacity := "" + resourceList := pvc.Status.Capacity[v1.ResourceStorage] + if &resourceList != nil { + capacity = resourceList.String() + } + resElem := &ListPvcsResponse{ + PvcName: pvc.Name, + Status: string(pvc.Status.Phase), + Volume: pvc.Spec.VolumeName, + AccessModes: accessModes, + StorageClass: storageClassName, + Capacity: capacity, + YamlData: string(yamlData), + Services: tempSvcs, + CreateTime: pvc.GetCreationTimestamp().Time, + } + mutex.Lock() + res = append(res, resElem) + mutex.Unlock() + return nil + } + for _, pvcElem := range Pvcs { + wg.Add(1) + go func(pvc *v1.PersistentVolumeClaim) { + defer wg.Done() + err := pvcProcess(pvc) + if err != nil { + log.Errorf("ListPvcs ns:%s name:%s pvcProcess err:%s", pvc.Namespace, pvc.Name, err) + } + }(pvcElem) + } + wg.Wait() + return res, nil +} + +type UpdatePvcArgs struct { + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + PvcName string `json:"pvc_name"` + YamlData string `json:"yaml_data"` + RestartAssociatedSvc bool `json:"restart_associated_svc"` +} + +func UpdatePvc(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { + js, err := yaml.YAMLToJSON([]byte(args.YamlData)) + pvc := &corev1.PersistentVolumeClaim{} + err = json.Unmarshal(js, pvc) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + if pvc.Name != args.Name { + return e.ErrUpdateResource.AddDesc("pvc Yaml Name is incorrect") + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrUpdateResource.AddErr(err) + } + pvc.Namespace = product.Namespace + err = updater.UpdatePvc(product.Namespace, pvc, clientset) + if err != nil { + log.Error(err) + return e.ErrUpdateResource.AddDesc(err.Error()) + } + envPvc := &models.EnvPvc{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: pvc.Name, + YamlData: args.YamlData, + } + if commonrepo.NewPvcColl().Create(envPvc, true) != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + + if !args.RestartAssociatedSvc { + return nil + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + if err := restartPod(pvc.Name, args.ProductName, args.EnvName, product.Namespace, config.CommonEnvCfgTypeSecret, clientset, kubeClient); err != nil { + return e.ErrRestartService.AddDesc(err.Error()) + } + return nil +} diff --git a/pkg/microservice/aslan/core/environment/service/secret.go b/pkg/microservice/aslan/core/environment/service/secret.go new file mode 100644 index 00000000..75612491 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/secret.go @@ -0,0 +1,170 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + "sync" + "time" + + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +type ListSecretsResponse struct { + SecretName string `json:"secret_name"` + SecretType string `json:"secret_type"` + YamlData string `json:"yaml_data"` + UpdateUserName string `json:"update_username"` + CreateTime time.Time `json:"create_time"` + Services []string `json:"services"` +} + +func ListSecrets(envName, productName string, log *zap.SugaredLogger) ([]*ListSecretsResponse, error) { + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + + envSvcDepends, err := commonrepo.NewEnvSvcDependColl().List(&commonrepo.ListEnvSvcDependOption{ProductName: productName, EnvName: envName}) + if err != nil && commonrepo.IsErrNoDocuments(err) { + return nil, e.ErrListResources.AddDesc(err.Error()) + } + + secrets, err := getter.ListSecrets(product.Namespace, kubeClient) + if err != nil { + log.Error(err) + return nil, e.ErrListResources.AddDesc(err.Error()) + } + + var res []*ListSecretsResponse + var mutex sync.Mutex + var wg sync.WaitGroup + secretProcess := func(secret *corev1.Secret) error { + secret.SetManagedFields(nil) + secret.SetResourceVersion("") + yamlData, err := yaml.Marshal(secret) + if err != nil { + return err + } + var tempSvcs []string + LB: + for _, svcDepend := range envSvcDepends { + for _, secc := range svcDepend.Secrets { + if secc == secret.Name { + tempSvcs = append(tempSvcs, svcDepend.ServiceModule) + continue LB + } + } + } + resElem := &ListSecretsResponse{ + SecretName: secret.Name, + SecretType: string(secret.Type), + YamlData: string(yamlData), + Services: tempSvcs, + CreateTime: secret.GetCreationTimestamp().Time, + } + mutex.Lock() + res = append(res, resElem) + mutex.Unlock() + return nil + } + + for _, secret := range secrets { + wg.Add(1) + go func(sec *corev1.Secret) { + defer wg.Done() + err := secretProcess(sec) + if err != nil { + log.Errorf("ListSecrets ns:%s name:%s secretProcess err:%s", sec.Namespace, sec.Name, err) + } + }(secret) + } + wg.Wait() + return res, nil +} + +func UpdateSecret(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { + js, err := yaml.YAMLToJSON([]byte(args.YamlData)) + secret := &corev1.Secret{} + err = json.Unmarshal(js, secret) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + if secret.Name != args.Name { + return e.ErrUpdateResource.AddDesc("secret Yaml Name is incorrect") + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + secret.Namespace = product.Namespace + err = updater.UpdateOrCreateSecret(secret, kubeClient) + if err != nil { + log.Error(err) + return e.ErrUpdateResource.AddDesc(err.Error()) + } + envSecret := &models.EnvSecret{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Name: secret.Name, + YamlData: args.YamlData, + } + if commonrepo.NewSecretColl().Create(envSecret, true) != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + + if !args.RestartAssociatedSvc { + return nil + } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) + return e.ErrUpdateConfigMap.AddErr(err) + } + + if err := restartPod(secret.Name, args.ProductName, args.EnvName, product.Namespace, config.CommonEnvCfgTypeSecret, clientset, kubeClient); err != nil { + return e.ErrRestartService.AddDesc(err.Error()) + } + return nil +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index e69a8ccb..cd9520b0 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -122,6 +122,9 @@ func Start(ctx context.Context) { registerPolicies() + //Parse the workload dependencies configMap, PVC, ingress, secret + go environmentservice.StartClusterInformer() + go StartControllers(ctx.Done()) } @@ -210,6 +213,11 @@ func initDatabase() { commonrepo.NewChartColl(), commonrepo.NewDockerfileTemplateColl(), commonrepo.NewProjectClusterRelationColl(), + commonrepo.NewConfigMapColl(), + commonrepo.NewIngressColl(), + commonrepo.NewSecretColl(), + commonrepo.NewPvcColl(), + commonrepo.NewEnvSvcDependColl(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a4cfe7dc..8b96bb34 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -126,21 +126,22 @@ const ( // k8s concepts const ( - Secret = "Secret" - ConfigMap = "ConfigMap" - Ingress = "Ingress" - Service = "Service" - Deployment = "Deployment" - StatefulSet = "StatefulSet" - Pod = "Pod" - ReplicaSet = "ReplicaSet" - Job = "Job" - CronJob = "CronJob" - ClusterRoleBinding = "ClusterRoleBinding" - ServiceAccount = "ServiceAccount" - ClusterRole = "ClusterRole" - Role = "Role" - RoleBinding = "RoleBinding" + Secret = "Secret" + ConfigMap = "ConfigMap" + Ingress = "Ingress" + PersistentVolumeClaim = "PersistentVolumeClaim" + Service = "Service" + Deployment = "Deployment" + StatefulSet = "StatefulSet" + Pod = "Pod" + ReplicaSet = "ReplicaSet" + Job = "Job" + CronJob = "CronJob" + ClusterRoleBinding = "ClusterRoleBinding" + ServiceAccount = "ServiceAccount" + ClusterRole = "ClusterRole" + Role = "Role" + RoleBinding = "RoleBinding" // labels TaskLabel = "s-task" diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index f39a0d2a..77f42ec6 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -193,6 +193,12 @@ var ( ErrUpdateService = NewHTTPError(6092, "更新服务失败") // ErrListPodEvents ... ErrListPodEvents = NewHTTPError(6093, "列出服务事件失败") + //ErrListResources ... + ErrListResources = NewHTTPError(6094, "列出对象资源失败") + //ErrUpdateResource ... + ErrUpdateResource = NewHTTPError(6095, "更新对象资源失败") + //ErrDeleteResource ... + ErrDeleteResource = NewHTTPError(6096, "删除对象资源失败") // ErrLoginPm ... ErrLoginPm = NewHTTPError(6099, "登陆主机失败") diff --git a/pkg/tool/kube/getter/ingress.go b/pkg/tool/kube/getter/ingress.go index 620a72d9..946fe9c2 100644 --- a/pkg/tool/kube/getter/ingress.go +++ b/pkg/tool/kube/getter/ingress.go @@ -19,6 +19,7 @@ package getter import ( extensionsv1beta1 "k8s.io/api/extensions/v1beta1" v1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" @@ -60,3 +61,26 @@ func ListIngressesYaml(ns string, selector labels.Selector, cl client.Client) ([ } return ListResourceYamlInCache(ns, selector, nil, gvk, cl) } + +func ListIngresses(namespace string, cl client.Client, lessThan122 bool) (*unstructured.UnstructuredList, error) { + gvk := schema.GroupVersionKind{ + Group: "extensions", + Kind: "Ingress", + Version: "v1beta1", + } + if !lessThan122 { + gvk = schema.GroupVersionKind{ + Group: "networking.k8s.io", + Kind: "Ingress", + Version: "v1", + } + } + u := &unstructured.UnstructuredList{} + u.SetGroupVersionKind(gvk) + + err := ListResourceInCache(namespace, labels.Everything(), nil, u, cl) + if err != nil { + return u, err + } + return u, err +} diff --git a/pkg/tool/kube/getter/pvc.go b/pkg/tool/kube/getter/pvc.go new file mode 100644 index 00000000..c6e37eac --- /dev/null +++ b/pkg/tool/kube/getter/pvc.go @@ -0,0 +1,37 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func ListPvcs(ns string, selector fields.Selector, cl client.Reader) ([]*corev1.PersistentVolumeClaim, error) { + pvcList := &corev1.PersistentVolumeClaimList{} + err := ListResourceInCache(ns, nil, selector, pvcList, cl) + if err != nil { + return nil, err + } + + var res []*corev1.PersistentVolumeClaim + for i := range pvcList.Items { + res = append(res, &pvcList.Items[i]) + } + return res, err +} diff --git a/pkg/tool/kube/getter/secrets.go b/pkg/tool/kube/getter/secrets.go index 1eaee5ad..8bfdf783 100644 --- a/pkg/tool/kube/getter/secrets.go +++ b/pkg/tool/kube/getter/secrets.go @@ -30,3 +30,17 @@ func GetSecret(ns, name string, cl client.Client) (*corev1.Secret, bool, error) return svc, found, err } + +func ListSecrets(ns string, cl client.Client) ([]*corev1.Secret, error) { + l := &corev1.SecretList{} + err := ListResourceInCache(ns, nil, nil, l, cl) + if err != nil { + return nil, err + } + + var res []*corev1.Secret + for i := range l.Items { + res = append(res, &l.Items[i]) + } + return res, err +} diff --git a/pkg/tool/kube/updater/base.go b/pkg/tool/kube/updater/base.go index 0c138e62..a2ddd979 100644 --- a/pkg/tool/kube/updater/base.go +++ b/pkg/tool/kube/updater/base.go @@ -79,6 +79,10 @@ func createObject(obj client.Object, cl client.Client) error { return cl.Create(context.TODO(), obj) } +func createObjectNeverAnnotation(obj client.Object, cl client.Client) error { + return cl.Create(context.TODO(), obj) +} + func updateOrCreateObject(obj client.Object, cl client.Client) error { err := updateObject(obj, cl) if err != nil { @@ -122,6 +126,33 @@ func createOrPatchObject(modified client.Object, cl client.Client) error { return patchObjectWithType(current.(client.Object), patchBytes, patchType, cl) } +func createOrPatchObjectNeverAnnotation(modified client.Object, cl client.Client) error { + c := modified.DeepCopyObject().(client.Object) + found, err := getter.GetResourceInCache(modified.GetNamespace(), modified.GetName(), c, cl) + if err != nil { + return err + } else if !found { + return createObjectNeverAnnotation(modified, cl) + } + + gvk := modified.GetObjectKind().GroupVersionKind() + current, err := util.SetGroupVersionKind(c, &gvk) + if err != nil { + return err + } + + patchBytes, patchType, err := patcher.GeneratePatchBytes(current, modified) + if err != nil { + return err + } + + if len(patchBytes) == 0 || patchType == "" || string(patchBytes) == "{}" { + return nil + } + + return patchObjectWithType(current.(client.Object), patchBytes, patchType, cl) +} + func patchObjectWithType(obj client.Object, patchBytes []byte, patchType types.PatchType, cl client.Client) error { return cl.Patch(context.TODO(), obj, client.RawPatch(patchType, patchBytes)) } diff --git a/pkg/tool/kube/updater/configmap.go b/pkg/tool/kube/updater/configmap.go index f04ebc5e..cf570256 100644 --- a/pkg/tool/kube/updater/configmap.go +++ b/pkg/tool/kube/updater/configmap.go @@ -63,7 +63,7 @@ func DeleteConfigMap(ns, name string, cl client.Client) error { } func CreateConfigMap(cm *corev1.ConfigMap, cl client.Client) error { - return createObject(cm, cl) + return createObjectNeverAnnotation(cm, cl) } func DeleteConfigMapsAndWait(ns string, selector labels.Selector, cl client.Client) error { diff --git a/pkg/tool/kube/updater/ingress.go b/pkg/tool/kube/updater/ingress.go index 4f5d614e..646a92a5 100644 --- a/pkg/tool/kube/updater/ingress.go +++ b/pkg/tool/kube/updater/ingress.go @@ -20,6 +20,7 @@ import ( "context" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + v1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" @@ -57,3 +58,34 @@ func DeleteIngresses(namespace string, selector labels.Selector, clientset *kube return util.IgnoreNotFoundError(err) } + +func DeleteIngresseWithName(namespace, name string, clientset *kubernetes.Clientset) error { + version, err := clientset.Discovery().ServerVersion() + if err != nil { + return err + } + + deletePolicy := metav1.DeletePropagationForeground + if kubeclient.VersionLessThan122(version) { + return clientset.ExtensionsV1beta1().Ingresses(namespace).Delete( + context.TODO(), name, + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }) + } + + return clientset.NetworkingV1().Ingresses(namespace).Delete( + context.TODO(), name, + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + ) +} + +func CreateIngress(namespace string, ingress *v1.Ingress, clientset *kubernetes.Clientset) error { + _, err := clientset.NetworkingV1().Ingresses(namespace).Create( + context.TODO(), ingress, + metav1.CreateOptions{}, + ) + return err +} diff --git a/pkg/tool/kube/updater/pvc.go b/pkg/tool/kube/updater/pvc.go index e91a6b37..f28e765d 100644 --- a/pkg/tool/kube/updater/pvc.go +++ b/pkg/tool/kube/updater/pvc.go @@ -19,6 +19,7 @@ package updater import ( "context" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes" @@ -40,3 +41,25 @@ func DeletePersistentVolumeClaims(namespace string, selector labels.Selector, cl return util.IgnoreNotFoundError(err) } + +func DeletePvcWithName(namespace, name string, clientset *kubernetes.Clientset) error { + deletePolicy := metav1.DeletePropagationForeground + return clientset.CoreV1().PersistentVolumeClaims(namespace).Delete( + context.TODO(), name, + metav1.DeleteOptions{ + PropagationPolicy: &deletePolicy, + }, + ) +} + +func CreatePvc(namespace string, pvc *corev1.PersistentVolumeClaim, clientset *kubernetes.Clientset) error { + _, err := clientset.CoreV1().PersistentVolumeClaims(namespace).Create(context.TODO(), pvc, + metav1.CreateOptions{}) + return err +} + +func UpdatePvc(namespace string, pvc *corev1.PersistentVolumeClaim, clientset *kubernetes.Clientset) error { + _, err := clientset.CoreV1().PersistentVolumeClaims(namespace).Update(context.TODO(), pvc, + metav1.UpdateOptions{}) + return err +} diff --git a/pkg/tool/kube/updater/unstructured.go b/pkg/tool/kube/updater/unstructured.go index d3fc2ef5..a9421b1a 100644 --- a/pkg/tool/kube/updater/unstructured.go +++ b/pkg/tool/kube/updater/unstructured.go @@ -25,6 +25,14 @@ func CreateOrPatchUnstructured(u *unstructured.Unstructured, cl client.Client) e return createOrPatchObject(u, cl) } +func CreateOrPatchUnstructuredNeverAnnotation(u *unstructured.Unstructured, cl client.Client) error { + return createOrPatchObjectNeverAnnotation(u, cl) +} + +func UpdateOrCreateUnstructured(u *unstructured.Unstructured, cl client.Client) error { + return updateOrCreateObject(u, cl) +} + func DeleteUnstructured(u *unstructured.Unstructured, cl client.Client) error { return deleteObjectWithDefaultOptions(u, cl) } -- Gitee From a305e713c4cbc7eaa365561073988c61630b7c1e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 8 Apr 2022 15:05:06 +0800 Subject: [PATCH 2118/2647] fix environment Configuration Management Signed-off-by: ddh27 --- .../core/environment/service/configmap.go | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 30108bcf..6a8e8e50 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -396,8 +396,9 @@ func cleanArchiveConfigMap(namespace string, ls map[string]string, kubeClient cl type MigrateHistoryConfigMapsRes struct { } -func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogger) (*MigrateHistoryConfigMapsRes, error) { +func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogger) ([]*models.EnvConfigMap, error) { + res := make([]*models.EnvConfigMap, 0) products := make([]*models.Product, 0) var err error if productName != "" { @@ -427,7 +428,7 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge if err != nil { return nil, e.ErrListResources.AddErr(err) } - LB: + for _, cm := range cms { cmNames := strings.Split(cm.Name, "-bak-") cmName := cmNames[0] @@ -438,7 +439,7 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge Name: cmName, } cmLables := cm.GetLabels() - if _, ok := cmLables[setting.UpdateTime]; !ok { + if _, ok := cmLables[setting.UpdateTime]; ok { tm, err := time.Parse("20060102150405", cmLables[setting.UpdateTime]) if err != nil { return nil, e.ErrListResources.AddErr(err) @@ -450,6 +451,7 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge delete(cmLables, setting.ConfigBackupLabel) cm.SetLabels(cmLables) cm.SetManagedFields(nil) + cm.SetResourceVersion("") cm.SetAnnotations(make(map[string]string)) cm.SetName(cmName) @@ -459,28 +461,12 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge return nil, e.ErrListResources.AddDesc(err.Error()) } envCm.YamlData = string(yamlData) - - opt := &commonrepo.ListEnvCfgOption{ - ProductName: product.ProductName, - EnvName: product.EnvName, - Namespace: product.Namespace, - Name: cmName, - } - envCfgsOlder, err := commonrepo.NewConfigMapColl().List(opt) - if err != nil && commonrepo.IsErrNoDocuments(err) { - return nil, e.ErrListResources.AddErr(err) - } - for _, cfg := range envCfgsOlder { - if envCm.CreateTime == cfg.CreateTime { - continue LB - } - } - err = commonrepo.NewConfigMapColl().Create(envCm, false) if err != nil { return nil, e.ErrListResources.AddErr(err) } + res = append(res, envCm) } } - return nil, nil + return res, nil } -- Gitee From 1bedeecfe114c58cdb6c023764fca6cc073c0f5d Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 8 Apr 2022 15:46:47 +0800 Subject: [PATCH 2119/2647] fix environment Configuration Management Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/ingress.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/ingress.go b/pkg/microservice/aslan/core/environment/service/ingress.go index ff421de1..3750e326 100644 --- a/pkg/microservice/aslan/core/environment/service/ingress.go +++ b/pkg/microservice/aslan/core/environment/service/ingress.go @@ -79,7 +79,7 @@ func ListIngresses(envName, productName string, log *zap.SugaredLogger) ([]*List for _, ingress := range ingresss.Items { ingress.SetManagedFields(nil) ingress.SetResourceVersion("") - yamlData, err := yaml.Marshal(ingress) + yamlData, err := yaml.Marshal(ingress.Object) if err != nil { log.Error(err) return nil, e.ErrListResources.AddDesc(err.Error()) -- Gitee From d1c5d78e151edaa3d32f0309543cb4e4dcda9879 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 8 Apr 2022 16:59:29 +0800 Subject: [PATCH 2120/2647] debug for stages other than build Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/job.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index cc796999..006e8c14 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -227,15 +227,19 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName StorageEndpoint: pipelineTask.StorageEndpoint, AesKey: pipelineTask.ConfigPayload.AesKey, UploadEnabled: b.JobCtx.UploadEnabled, - UploadStorageInfo: &commontypes.ObjectStorageInfo{ + UploadInfo: b.JobCtx.UploadInfo, + } + + // Currently only the build stage will have this info. For the rest we will skip this context + if b.JobCtx.UploadStorageInfo != nil { + ctx.UploadStorageInfo = &commontypes.ObjectStorageInfo{ Endpoint: b.JobCtx.UploadStorageInfo.Endpoint, AK: b.JobCtx.UploadStorageInfo.AK, SK: b.JobCtx.UploadStorageInfo.SK, Bucket: b.JobCtx.UploadStorageInfo.Bucket, Insecure: b.JobCtx.UploadStorageInfo.Insecure, Provider: b.JobCtx.UploadStorageInfo.Provider, - }, - UploadInfo: b.JobCtx.UploadInfo, + } } if b.PipelineCtx.CacheEnable && !pipelineTask.ConfigPayload.ResetCache { -- Gitee From 4309e1e4c7ecb01e3dd8be9f2e1659db4daa9d56 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 8 Apr 2022 17:20:47 +0800 Subject: [PATCH 2121/2647] check length of .spec.status.containerStatuses to avoid panic Signed-off-by: zhangyifei --- pkg/tool/kube/containerlog/log.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/tool/kube/containerlog/log.go b/pkg/tool/kube/containerlog/log.go index 17733820..36361070 100644 --- a/pkg/tool/kube/containerlog/log.go +++ b/pkg/tool/kube/containerlog/log.go @@ -40,9 +40,13 @@ func GetContainerLogs(namespace, podName, containerName string, follow bool, tai return fmt.Errorf("failed to get pod %s in %s: %s", podName, namespace, err) } + // Note: Do two protections to avoid panic. if pod.Status.Phase == corev1.PodPending || pod.Status.Phase == corev1.PodUnknown { return fmt.Errorf("phase of Pod %s in ns %s is %s", pod.Name, pod.Namespace, pod.Status.Phase) } + if len(pod.Status.ContainerStatuses) == 0 { + return fmt.Errorf("length of container statuses is 0 for pod %s in ns %s", podName, namespace) + } _, err = out.Write([]byte(pod.Status.ContainerStatuses[0].State.Terminated.Message)) return err -- Gitee From e89ab5eb0a5d0a4872f631af9d9917cfa2499ee9 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 8 Apr 2022 18:20:48 +0800 Subject: [PATCH 2122/2647] dont copy .nodePort in port item to avoice exception when update NodePort Service Signed-off-by: zhangyifei --- .../aslan/core/environment/service/share_env.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index a8dd4628..a98e47cf 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -899,7 +899,17 @@ func ensureDefaultK8sServiceInGray(ctx context.Context, baseSvc *corev1.Service, svcInGray.Labels = baseSvc.Labels svcInGray.Annotations = baseSvc.Annotations svcInGray.Spec.Selector = baseSvc.Spec.Selector - svcInGray.Spec.Ports = baseSvc.Spec.Ports + + ports := make([]corev1.ServicePort, len(baseSvc.Spec.Ports)) + for i, port := range baseSvc.Spec.Ports { + ports[i] = corev1.ServicePort{ + Name: port.Name, + Protocol: port.Protocol, + Port: port.Port, + TargetPort: port.TargetPort, + } + } + svcInGray.Spec.Ports = ports if svcInGray.Labels != nil { svcInGray.Labels[zadigtypes.ZadigLabelKeyGlobalOwner] = zadigtypes.Zadig -- Gitee From 0bb10b6f978e7395a5c7c58eee8f51701ac6f8fe Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 18:32:16 +0800 Subject: [PATCH 2123/2647] jenkins nil pointer bug Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 02499a7f..beae6ab1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2405,8 +2405,8 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err log.Errorf("find project err:%s", err) return err } - rule := project.CustomImageRule.JenkinsRule - if rule != "" { + + if project.CustomImageRule != nil && project.CustomImageRule.JenkinsRule != "" { va := commonservice.Variable{ SERVICE: t.ServiceName, TIMESTAMP: time.Now().Format("20060102150405"), @@ -2415,7 +2415,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err ENV_NAME: taskOpt.EnvName, TASK_ID: strconv.FormatInt(taskOpt.Task.TaskID, 10), } - tm, err := gotempl.New("jenkins").Parse(rule) + tm, err := gotempl.New("jenkins").Parse(project.CustomImageRule.JenkinsRule) if err != nil { log.Errorf("Parse template err:%s", err) return err -- Gitee From 082e56c0509c0353d2102f686ebf4accb6bb6bfb Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 18:35:30 +0800 Subject: [PATCH 2124/2647] fmt Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index beae6ab1..448966e5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2429,7 +2429,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } image = GetImage(reg, payload.String()) } else { - image = GetImage(reg, fmt.Sprintf("%s:%d", t.ServiceName, time.Now().Format("20060102150405"))) + image = GetImage(reg, fmt.Sprintf("%s:%s", t.ServiceName, time.Now().Format("20060102150405"))) } t.JenkinsBuildArgs.JenkinsBuildParams[i].Value = image -- Gitee From 2ad0dab02bf95e9bfa760e76778e5fbe4ce0e1bc Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 19:05:25 +0800 Subject: [PATCH 2125/2647] jenkins panic Signed-off-by: mouuii --- pkg/microservice/jenkinsplugin/core/service/jenkins.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/jenkinsplugin/core/service/jenkins.go b/pkg/microservice/jenkinsplugin/core/service/jenkins.go index bbd4bc66..dd4dc36a 100644 --- a/pkg/microservice/jenkinsplugin/core/service/jenkins.go +++ b/pkg/microservice/jenkinsplugin/core/service/jenkins.go @@ -27,7 +27,7 @@ import ( "time" "github.com/bndr/gojenkins" - yaml "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" "github.com/koderover/zadig/pkg/microservice/jenkinsplugin/config" "github.com/koderover/zadig/pkg/tool/log" @@ -147,6 +147,10 @@ func (p *JenkinsPlugin) afterExec(ctx context.Context, jenkinsClient *gojenkins. log.Errorf("get jenkins build detail err:%v", err) return err } + if jobBuild == nil { + log.Errorf("get job fail") + return fmt.Errorf("get job fail") + } p.outputLog(ctx, jobBuild) return nil } -- Gitee From 510984513dc9755c11a07793a37ee8371e1bd75a Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 19:14:31 +0800 Subject: [PATCH 2126/2647] jenkins panic Signed-off-by: mouuii --- pkg/microservice/jenkinsplugin/core/service/jenkins.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/jenkinsplugin/core/service/jenkins.go b/pkg/microservice/jenkinsplugin/core/service/jenkins.go index dd4dc36a..41828da4 100644 --- a/pkg/microservice/jenkinsplugin/core/service/jenkins.go +++ b/pkg/microservice/jenkinsplugin/core/service/jenkins.go @@ -148,8 +148,8 @@ func (p *JenkinsPlugin) afterExec(ctx context.Context, jenkinsClient *gojenkins. return err } if jobBuild == nil { - log.Errorf("get job fail") - return fmt.Errorf("get job fail") + log.Errorf("jobBuild is nil") + return fmt.Errorf("jobBuild is nil") } p.outputLog(ctx, jobBuild) return nil -- Gitee From 49b3dffd47c58448880eacab3f3695423ddd306e Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 8 Apr 2022 19:20:57 +0800 Subject: [PATCH 2127/2647] fix delete collaboration instance by mistake when exec workflow Signed-off-by: panxunying --- .../repository/mongodb/collaboration_instance.go | 10 +++++----- .../collaboration/service/collaboration_instance.go | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 14d9a41a..69ccfd79 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -33,7 +33,7 @@ import ( type CollaborationInstanceFindOptions struct { ProjectName string Name string - UserUID string + UserUID []string } type CollaborationInstanceListOptions struct { @@ -74,8 +74,8 @@ func (c *CollaborationInstanceColl) EnsureIndex(ctx context.Context) error { func (c *CollaborationInstanceColl) Find(opt *CollaborationInstanceFindOptions) (*models.CollaborationInstance, error) { res := &models.CollaborationInstance{} query := bson.M{} - if opt.UserUID != "" { - query["user_uid"] = opt.UserUID + if len(opt.UserUID) > 0 { + query["user_uid"] = bson.M{"$in": opt.UserUID} } if opt.ProjectName != "" { query["project_name"] = opt.ProjectName @@ -163,8 +163,8 @@ func (c *CollaborationInstanceColl) DeleteByProject(projectName string) error { func (c *CollaborationInstanceColl) List(opt *CollaborationInstanceFindOptions) ([]*models.CollaborationInstance, error) { var ret []*models.CollaborationInstance query := bson.M{} - if opt.UserUID != "" { - query["user_uid"] = opt.UserUID + if len(opt.UserUID) > 0 { + query["user_uid"] = bson.M{"$in": opt.UserUID} } if opt.ProjectName != "" { query["project_name"] = opt.ProjectName diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 5166c72a..cd90ea65 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -304,7 +304,7 @@ func GetCollaborationUpdate(projectName, uid, identityType, userName string, log } collaborationInstances, err := mongodb.NewCollaborationInstanceColl().List(&mongodb.CollaborationInstanceFindOptions{ ProjectName: projectName, - UserUID: uid, + UserUID: []string{uid}, }) if err != nil { logger.Errorf("GetCollaborationInstance error, err msg:%s", err) @@ -359,7 +359,7 @@ func syncInstance(updateResp *GetCollaborationUpdateResp, projectName, identityT findOpts = append(findOpts, mongodb.CollaborationInstanceFindOptions{ Name: instance.CollaborationName, ProjectName: instance.ProjectName, - UserUID: instance.UserUID, + UserUID: []string{instance.UserUID}, }) } return mongodb.NewCollaborationInstanceColl().BulkDelete(mongodb.CollaborationInstanceListOptions{ @@ -1227,7 +1227,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn findOpts = append(findOpts, mongodb.CollaborationInstanceFindOptions{ ProjectName: ci.ProjectName, Name: ci.CollaborationName, - UserUID: ci.UserUID, + UserUID: []string{ci.UserUID}, }) policyNames = append(policyNames, ci.PolicyName) } -- Gitee From 34084c7356a785c2bacf795d9556cc1da700c2e7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 8 Apr 2022 19:56:12 +0800 Subject: [PATCH 2128/2647] jenkins panic Signed-off-by: mouuii --- pkg/microservice/jenkinsplugin/core/service/jenkins.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/microservice/jenkinsplugin/core/service/jenkins.go b/pkg/microservice/jenkinsplugin/core/service/jenkins.go index 41828da4..a633cf75 100644 --- a/pkg/microservice/jenkinsplugin/core/service/jenkins.go +++ b/pkg/microservice/jenkinsplugin/core/service/jenkins.go @@ -147,10 +147,6 @@ func (p *JenkinsPlugin) afterExec(ctx context.Context, jenkinsClient *gojenkins. log.Errorf("get jenkins build detail err:%v", err) return err } - if jobBuild == nil { - log.Errorf("jobBuild is nil") - return fmt.Errorf("jobBuild is nil") - } p.outputLog(ctx, jobBuild) return nil } @@ -198,5 +194,5 @@ func getBuild(ctx context.Context, buildID int64, jenkins *gojenkins.Jenkins, jo if status == 200 { return &build, nil } - return nil, nil + return nil, fmt.Errorf("fail to get build") } -- Gitee From 268a29f0704319d1b47ce697f605d7199442e012 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 11 Apr 2022 11:04:45 +0800 Subject: [PATCH 2129/2647] fix collaboration copy helm params error Signed-off-by: panxunying --- .../core/collaboration/service/collaboration_instance.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 5166c72a..1977ca12 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -935,8 +935,8 @@ func syncNewResource(products *SyncCollaborationInstanceArgs, updateResp *GetCol OldName: product.BaseName, NewName: product.Name, BaseName: product.BaseName, - DefaultValues: product.DefaultValues, - ChartValues: product.ChartValues, + DefaultValues: productArg.DefaultValues, + ChartValues: productArg.ChartValues, }) } if productArg.DeployType == string(setting.K8SDeployType) { -- Gitee From 0d5f9edda33a38ea96d043629106745efa72bc76 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Mon, 11 Apr 2022 16:16:51 +0800 Subject: [PATCH 2130/2647] fix test report directory is empty Signed-off-by: ddh27 --- .../reaper/core/service/reaper/artifact.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/artifact.go b/pkg/microservice/reaper/core/service/reaper/artifact.go index 51151c4b..e046682e 100644 --- a/pkg/microservice/reaper/core/service/reaper/artifact.go +++ b/pkg/microservice/reaper/core/service/reaper/artifact.go @@ -51,13 +51,6 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] if len(pluginType) == 0 { tarName := setting.ArtifactResultOut - temp, err := os.Create(tarName) - if err != nil { - log.Errorf("failed to create %s err: %s", tarName, err) - return err - } - _ = temp.Close() - cmdAndArtifactFullPaths := make([]string, 0) cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, "-czf") cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, tarName) @@ -75,6 +68,16 @@ func artifactsUpload(ctx *meta.Context, activeWorkspace string, artifactPaths [] cmdAndArtifactFullPaths = append(cmdAndArtifactFullPaths, artifactPath) } + if len(cmdAndArtifactFullPaths) < 3 { + return nil + } + + temp, err := os.Create(tarName) + if err != nil { + log.Errorf("failed to create %s err: %s", tarName, err) + return err + } + _ = temp.Close() cmd := exec.Command("tar", cmdAndArtifactFullPaths...) cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { -- Gitee From 2b800689ff233b375b6526fef0dbf2379ae8aa7a Mon Sep 17 00:00:00 2001 From: mouuii Date: Mon, 11 Apr 2022 16:21:53 +0800 Subject: [PATCH 2131/2647] remove collie workflow schedule Signed-off-by: mouuii --- .../scheduler/schedule_collie_pipeline.go | 111 ------------------ .../cron/core/service/scheduler/scheduler.go | 27 ----- 2 files changed, 138 deletions(-) delete mode 100644 pkg/microservice/cron/core/service/scheduler/schedule_collie_pipeline.go diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_collie_pipeline.go b/pkg/microservice/cron/core/service/scheduler/schedule_collie_pipeline.go deleted file mode 100644 index ad35b2c8..00000000 --- a/pkg/microservice/cron/core/service/scheduler/schedule_collie_pipeline.go +++ /dev/null @@ -1,111 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package scheduler - -import ( - "reflect" - - "github.com/jasonlvhit/gocron" - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/microservice/cron/core/service" - "github.com/koderover/zadig/pkg/setting" -) - -// UpsertColliePipelineScheduler ... -func (c *CronClient) UpsertColliePipelineScheduler(log *zap.SugaredLogger) { - pipelines, err := c.CollieCli.ListColliePipelines(log) - if err != nil { - log.Error(err) - return - } - - log.Info("start init collie pipeline task scheduler..") - for _, pipeline := range pipelines { - log.Infof("deal collie pipeline data, name:%s, schedule:%v", pipeline.Metadata.Name, pipeline.Spec.Schedules) - key := "collie-pipeline-timer-" + pipeline.Metadata.Name - - schedules := pipeline.Spec.Schedules - // 如果该pipeline先配置了定时器,然后将定时器全部删除了,这时需要把原来的schedule暂停并删除 - if len(schedules) == 0 { - // 暂停 - if _, ok := c.SchedulerController[key]; ok { - c.SchedulerController[key] <- true - delete(c.SchedulerController, key) - } - // 删除 - if _, ok := c.Schedulers[key]; ok { - log.Warnf("delete expired test from Schedulers, key:%s", key) - delete(c.Schedulers, key) - } - if _, ok := c.lastSchedulers[key]; ok { - log.Warnf("delete expired test from lastSchedulers, key:%s", key) - delete(c.lastSchedulers, key) - } - continue - } - - // 判断该测试的定时器配置是否有更新,没有更新则跳过 - if _, ok := c.lastSchedulers[key]; ok && reflect.DeepEqual(schedules, c.lastSchedulers[key]) { - continue - } else { - c.lastSchedulers[key] = schedules - } - - // 定时器有更新,则重新设置该schedule - newScheduler := gocron.NewScheduler() - for _, schedule := range schedules { - if schedule == nil || !schedule.Enabled { - continue - } - log.Infof("deal collie pipeline schedule, name:%s, item:%v", pipeline.Metadata.Name, schedule) - if err := schedule.Validate(); err != nil { - log.Errorf("[%s] invalid schedule: %v", key, err) - continue - } - BuildScheduledJob(newScheduler, schedule).Do(c.RunColliePipelineScheduledTask, pipeline, log) - } - - c.Schedulers[key] = newScheduler - log.Infof("[%s] building schedulers..", key) - // 停掉旧的scheduler - if _, ok := c.SchedulerController[key]; ok { - c.SchedulerController[key] <- true - } - log.Infof("[%s] lens of scheduler: %d", key, c.Schedulers[key].Len()) - c.SchedulerController[key] = c.Schedulers[key].Start() - } -} - -// RunTestScheduledTask ... -func (c *CronClient) RunColliePipelineScheduledTask(pipeline *service.PipelineResource, log *zap.SugaredLogger) { - log.Infof("start collie pipeline cron job: %s ...", pipeline.Metadata.Name) - - args := &service.CreateBuildRequest{ - UserID: setting.CronTaskCreator, - UserName: setting.CronTaskCreator, - NoCache: false, - ResetVolume: false, - Variables: pipeline.Spec.Variables, - PipelineName: pipeline.Metadata.Name, - ProductName: pipeline.Metadata.Project, - } - - if err := c.CollieCli.RunColliePipelineTask(args, log); err != nil { - log.Errorf("[%s]RunTestScheduledTask err: %v", pipeline.Metadata.Name, err) - } -} diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 02f18a20..75895e0c 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -32,14 +32,12 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" - configclient "github.com/koderover/zadig/pkg/shared/config" "github.com/koderover/zadig/pkg/tool/log" ) // CronClient ... type CronClient struct { AslanCli *client.Client - CollieCli *client.CollieClient Schedulers map[string]*gocron.Scheduler SchedulerController map[string]chan bool lastSchedulers map[string][]*service.Schedule @@ -86,7 +84,6 @@ func NewCronClient() *CronClient { nsqLookupAddrs := config.NsqLookupAddrs() aslanCli := client.NewAslanClient(fmt.Sprintf("%s/api", configbase.AslanServiceAddress())) - collieCli := client.NewCollieClient(config.CollieAPI()) //初始化nsq config := nsq.NewConfig() // 注意 WD_POD_NAME 必须使用 Downward API 配置环境变量 @@ -94,14 +91,6 @@ func NewCronClient() *CronClient { config.MaxAttempts = 50 config.LookupdPollInterval = 1 * time.Second - //nsqClient := nsqcli.NewNsqClient(nsqLookupAddrs, "127.0.0.1:4151") - //// 初始化nsq topic - //err := nsqClient.EnsureNsqdTopics([]string{setting.TopicAck, setting.TopicItReport, setting.TopicNotification}) - //if err != nil { - // //FIXME - // log.Fatalf("cannot ensure nsq topic, the error is %v", err) - //} - //Cronjob Client cronjobClient, err := nsq.NewConsumer(setting.TopicCronjob, "cronjob", config) if err != nil { @@ -122,7 +111,6 @@ func NewCronClient() *CronClient { return &CronClient{ AslanCli: aslanCli, - CollieCli: collieCli, Schedulers: make(map[string]*gocron.Scheduler), lastSchedulers: make(map[string][]*service.Schedule), lastServiceSchedulers: make(map[string]*service.SvcRevision), @@ -144,12 +132,6 @@ func (c *CronClient) Init() { // 测试管理的定时任务触发 c.InitTestScheduler() - // 自由编排工作流定时任务触发 - cl := configclient.New(configbase.ConfigServiceAddress()) - if enable, err := cl.CheckFeature(setting.ModernWorkflowType); err == nil && enable { - c.InitColliePipelineScheduler() - } - // 定时清理环境 c.InitCleanProductScheduler() // clean collaboration instance resource every 5 minutes @@ -213,15 +195,6 @@ func (c *CronClient) InitTestScheduler() { c.Schedulers[UpsertTestScheduler].Start() } -func (c *CronClient) InitColliePipelineScheduler() { - - c.Schedulers[UpsertColliePipelineScheduler] = gocron.NewScheduler() - - c.Schedulers[UpsertColliePipelineScheduler].Every(1).Minutes().Do(c.UpsertColliePipelineScheduler, c.log) - - c.Schedulers[UpsertColliePipelineScheduler].Start() -} - func (c *CronClient) InitBuildStatScheduler() { c.Schedulers[InitStatScheduler] = gocron.NewScheduler() -- Gitee From 281b1f36100e9981005fd975ea5b53032e9d1ce5 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 11 Apr 2022 17:00:17 +0800 Subject: [PATCH 2132/2647] changed contributor guide Signed-off-by: Min Min --- community/dev/contributor-workflow.md | 28 +++++++++++++++---------- community/dev/edit_variable_button.png | Bin 0 -> 25957 bytes community/dev/fork_var1.png | Bin 66812 -> 67181 bytes community/dev/fork_variables_1.png | Bin 0 -> 67181 bytes community/dev/fork_variables_2.png | Bin 0 -> 16861 bytes community/dev/github_login.png | Bin 0 -> 20730 bytes 6 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 community/dev/edit_variable_button.png create mode 100644 community/dev/fork_variables_1.png create mode 100644 community/dev/fork_variables_2.png create mode 100644 community/dev/github_login.png diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index dd7b0374..1784aaee 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -45,20 +45,26 @@ Zadig 为您提供云上测试环境 [https://os.koderover.com](https://os.koder 第 1 步:GitHub OAuth 登录系统 -第 2 步:Fork Zadig 项目 +![Login through Github](./github_login.png) -![Fork project](./fork-zadig-option.png) +第 2 步:在 Zadig 项目中创建自己的环境 + +步骤如下: +1. 点击 Zadig 项目 +2. 在页面中点击 环境变量 按钮,如下图所示 + +![Edit variable button](./edit_variable_button.png) +3. 在弹出的窗口中,如图修改如下字段: 在弹出的窗口中,点击 zadig 选项,如图修改如下字段: -1. endpoint.FQDN=${github_id}.ko.coderover.cn -2. global.extensions.extAuth.extauthzServerRef.namespace=zadig-env-${github_id} -3. dex.fullnameOverride=zadig-${github_id}-dex -4. dex.config.issuer=http://zadig-${github_id}-dex -5. dex.config.staticClients[0].redirectURISs[0]=http:// ${github_id}.ko.coderover.cn/api/v1/callback - -![Fork_var1](./fork_var1.png) -![Fork_var2](./fork_var2.png) -![Fork_var3](./fork_var3.png) +1. dex.config.issuer=http://${github_id}-dex:5556/dex +2. dex.config.staticClients[0].redirectURISs[0]=http://${github_id}.ko.coderover.cn/api/v1/callback +3. dex.fullnameOverride=${github_id}-dex +4. endpoint.FQDN=${github_id}.ko.coderover.cn +5. global.extensions.extAuth.extauthzServerRef.namespace=zadig-env-${环境名称} + +![Fork_varibales_1](./fork_variables_1.png) +![Fork_varibales_2](./fork_variables_2.png) 请注意,如果没有按照以上规则修改参数,可能会导致环境无法创建/创建环境后无法正常访问! diff --git a/community/dev/edit_variable_button.png b/community/dev/edit_variable_button.png new file mode 100644 index 0000000000000000000000000000000000000000..3d31e367d14f7bb6837f921bae740c40969676c1 GIT binary patch literal 25957 zcmeFY_gj-o)HW(Aq97pB1Ox;Gq(-`Qq=?dy5_$xr_udIuDbl1#mo6pL(5uor(gFbz ziu4j%D1nfJdyD&=bDe+Seb4txt}FA*JTtRqm3!T5o=^=nMPfo)!Yfy<5WjdX|MJR} zYZ6zk;7#7VdeK6F@V$QJ%7ZH}o4M_WoB<==o1S>sFf0gy%%Y$1B zk6Us3X{i3kj|CpL-tNC$>T?{8@{bzYJyY2|+?VVu+T64FPK0;;j?9(cKdn{xjm8Z= zP?EnbGGq^+yQ2R-z34I{6EwVhTZ?A^NQn zW5XTnZpCnOQ>n0>b5(WY5Ag@B_7SrkqMltNGi_Fl*=As>F}?EoJ>_w{w6kB1a90Q@ zsG_xq^=kdzO9rE2Lmm?6(7RzK4{-eABj#upt}=%lUf9MznGseb98_j_R(V<+FZzz2qy(#U}A zZ}*Up&UD%;WI*MX%<6zk(}0&$vqwilA>Wbu+Gw}CDdHSWbFxf8oA}nOlkJG~abw?f z&H2~yri1CGdiI$wcN`@&&bbi{woAckBK_$jUFxZ#Q1gZ;aCRo-tigjZ0756_2cP4z z$taP4yTOY$e)KB|@+Me1O#4*K91k9k7WuWThAXZ^NY_Q!>Pk0*&~l$X(tT8hd$=6FIDH`-lS}Jhg{|G@-`wF7V!XcE5pI z0lxN%VIl$;l%qeozUNDpOk_@wN z7${mBZe!j@T^wL3xY9gbJv+UEpxEHC^~sG3`bV%6S0hOrR;rJikP1f&$4Ww}FX%ED zbb9cbtrYF@6?Kxn`Ik(bpGP%=8poN;Jv*X1SEj>8NU7Zk*LMagI#v{5>88OIsg77? zH}#t>Uk@nznoB0GduoW^J=v$pK07Mud&2Hcy*H?he{_3`Vz|i8X7Ig+&`bXs&)xH3 znid)kaHSQv+!`50DSHnb0g?z)Tdjy+VNN;c3hwbpQ^oq}_et#(oq(9T(`)`*S`6+0SpQ>w>BI zJr94KcfKebsc=Q{ns&BZvOd){c@hhB86RSPqmn@=XMxug+tqnPXo8__kScS3b>5sZ zhUEjN{<_LDU6=NqOx?RFGm{oOV@bgUMZy5wH|O;*B$uZRQum76m_WI~SRoJ#18~edy_pSpuTvS+L7}t-Mx9YE>3X9Pjm17RG z1nM+YHt-1i-+nXrkXPdfrV}o7cJ5b<*+s(GgqZ{z5>L;cto0r+WIco(tO&QRqev5 zr$HO{8@*h;awQqr>aDIRBeD>+siKeP`*}mX*=$_;`XNuu zYX_&R&$Mk?ei?qI>otjKV^d6@sz2Bd`)Ru=Rc+d1x#i)7eiud*S<-72P(#Hp0kx1Z0t)=K#Q z`otk8YoFmxa?f|sgFV3k=`ZN(3Od2#OsGQ_BGBAh-yjMN6!}hrjTEA6FK%U79o~Nw za?j()qa#DmJ3;ti;HHOd8owOQ&m(ZCye0KM^#j7@6K8Y}!mGb=@QaP%Scw<`qqsX% zLV~Z{(M+Jfzb7gqT2#CR33!HHd*~}~yja7t@@{q7FvaBw4B$x^3WAgc2qRvSM=pg~GM$q{Zh+f&Mme>+v!qc*Ux4GJ&2Gt8wgJ&-LeDP~)- zd+_bV5r*JDGT1*N=H}93u}mTIA`66VOH2Khfk>c1-)u1h!}u5Zm$Zh9}tD9-BvnPx7n(* zs4PT>Cjb|9OrEe8ULz?n#2L>9M8I0djf=qfJl!*AOX;J`z6ZtArzu|Tz0Ld_qzpoj z-3HF(Is!hv4{JZK8LC{|0r$Xn;+}SfV9A!e*lSfdZ}n8 zirzDI`6a3AaMIC%v1JZNFVb#`HH%BSzc$OUf%gjv)XXX--Ab^Eg>3+IHWAsJe%N|@ zCb5ItT+)q>SW_S3*65zL0co2ESP#R^2NyQRz&$rPOl7!r)SM@w$;OG+8AmPmDfb`L zM-+dl?7)>8zFW(x*>n9lVB60xu9Ut~G@JmE^shcohKSMGA|Jt=(ndH@<93Da)W7b> zyLLW)l;^i)GN?DQOl`bJRL(y{O_6h;GU|8MKgIl&TM}3>)1Ztp*&Exce^c_dQ=V}4 zg+*E(YI6Q#LcqDjPy@R>2g;#w-Iy!ST_Zq&ZLBBy6NA(rm9gfT5Me+QVYW2 zF?7Eqrt&@!`b_8F-(GLrq9ZxIot`%{qG^r&8r^QQNSf=2c~yX{BF}q>VVpf%5)p-e z4Os}FgBf#(`ps7iyRYWl`X*K7FfL*oXDBzO!RoXlS!=n*L54tm;CM?(&idXEB12Ez zroZ;t?zTvA_p`uw9bN$ojW5fqaP>m_!CyUYJ3!@|U$#SwV;jTHHJ%bUl{teo) z-peZOV%DeBhHtV5;dM2-W(6F7DfoT;2#oprDIw=-&I$Fv*8%D2tl(4H((58yMH%Mg3DlJ-o;T~+>XieN z&vjLOer)6urNF4Uz7Me%ew6x^bhsZSZ1SAy2p4Ke5GO#=2#%!jd-as<$ zPXEM5*XU>F7@83vHtDuG5W8qQ?F7W@Rg>&OiUZG&9U*6Z(){xu#8ybL4v6hLVs|~r8Rlrrj3Dd*Mvv`m%! zGI~Oo+z(#Vjc)czsdiV1M81fA;qYVDZA{#BLs+AEpQ!B1y~;qA_pW^kBH?X@6|zzj zdihdxdVY`h-+7h3h>+wQYUH;-*j4kRmga~!2R6#;i?M-=`T1s!DPLy@X!Us5A6oQ} z_hwWP<+Txd=B(TxrC-QM&#d$O@eQTxU$;Hi4L=bdb)29g^n@S+h3)&(w0NjXOHQ+(fmVl-^8qbmA2md~Ywqb#(O8 z&;Wl6)QADr`u>DC!FEP3{RI7ep=_Rm4?~HV@5x?FaC$zZmU`FFJyZLbV8VnkZ&W@a zxmXmuIUG7SiO8H!R<%0^o_-T>_!+vn&^p;XUv+91u8x{@ln^R3YPRGXwX}abrOq1k zY2PH$v1C`+RIcvndQ(zw!+PT^ zGW8COtykQeu0Y!)vihVfBty8^E!j0&S!O0DMJPXji=R$Z#j#|BD)k$!;09s?vdM|s zX{tf2_l{_N^!(*@51}sweV0`2`=n9fH(^tD=R#mbN@pGx6C%m+F7ZR86kRPE=~WHB zfddIOg6~^kk;d~if-qODY$zCOU=_T6c{+Z?#TO+CJv`Ac2FFf%9^XQ zm1F618u&9+Y{&bkPrzoPmO=Q8dQCc&CHv5)nuhghh*7hyErQQO-_UtaRs&FZ%GcCZ ztj5flugDYS2v6n`gFG5CXgVkZ*F0s(Kd-Lg?*XQxeQb3_xLK>Qm`XDj8$8Pyol|yGJf6GpDlY*{X zrmCTDxojN6nY)kL@sQK%?BtO~Pic{ux4atImNb{(w$fQ0Xy}}@GmJb1f%taX@m3lT zXPzeLRliGahagD!*Av$SEy|_bX|AsYc}JY(FPGvD3d4&0T^dUs&Vr;U%*XIb{j6*~ z`mhWM?17v`3~4^OYb%@f{m`WJQYHc|p!?aY!=o;>)PkFB8&{bZ@EBUz-aR(rD;KwyIH>*2lKDNcO zMvep_(uehi9g5l59RSjei*xUH+6{gE!WUTbl^PB!4}WsOPv*fMJ#M~U z8{oF%nE4NVG#i55o5Q_Xo0;ck^nV*Q+?7jOg2|;$h%#SvwOK<>?gsUbCzc{g` z5m{PV271sxYAMYUR$AUd>N6DKI*At5&g6e!o&S-AJk7-Hw+r`&GCi zQJp@VCKil3=An##foC?Uu|*%Cauh3;3}WFmrhq+EZDQ(O_*{CUaQ(CQX;UbCMC){7 zYbJ%=SG6|49~m?WaVAh5jl-yqhqjpxgwYmF`C_x zoA^d{Jtrrmoko`zW_O}u*gb4$NkaTM5YV88cu{5(^_1gzDE&Na;EOIAHJ(vQnk-c3 zv?Xe?kARy_g=fK5He%r=hq2=$=ek6~A9^DMxIis=rN?gSt5RXF)lshT4Oj z$bMx>MdD)mK6pi;e7qWv#g{Fz&=}~|^RMXFL@fuK%-6SJa}81=2s_W+tU#axLTw3o zi+tg2dk5THS>4J~+hn@tut>^Qr2O4KfsFw_l@d;NW_R0LsUn0f*@E>?f7K^MUD^Wb^1po{aj?%4$2=eN^@?et*vz8$Ah zAg>&wlA;OYg4>K@50xA&Y!qHa1-EE0go9=Q=T<&6bW&m#fkss&g`?4Y0atzBsjoXv zjqQl#pNx%WXOhk1A?f*MFr4AM4bE&isG-slZ1r(;y|I=Z@cb}_R7Erk_u~e-K`O$K zmwxsHO@{RW8W-?BIIumrcbg7?)lxo~=G_FLdzuTo$ws#$MNrL+=}u;{=O0)Ul}>wq z{mcQPKr>SZ_b1|Lp>?7PPum<-ZvIT4gkndj$CeuAsxfk(6_Wx^?U8hr3WVUrXLQm+u9%5VHjk?9v(SK3TwA}gpNY{DB*QqEzCy#3>{a%$-MkdkmfIv@G@B9yeZR<*|>hT}Zk!504mPNmoA*DyH zG0T(-N{T%Vs(>}YswfSY-0CU^!)O~ru&v^6zF(>JBy#}r#G3nefc}RUO}?5(lxbY) z10}p9`DOmM76~FY$j8RUY6Dqpm@c>f0Kc)ZG4&uFG0cFx#zf~n(4U&RXwste*U5}Hr0{y?5mA8#Y{Ns z5q3N0=5hP6cW%D0=dmaM$nuY}{{ES~TgC;AnY7<6XZSl6Jzj&X;Wbs8CqqKc4^sYa z_4+2YwFFe?P9N>$YcSCy`DK5ZubRP~aw!mIZt|2RTEni2zi*OhRl^f96S@oE95Ovb z*;t)U68{|}LmBv-r0~{X3a;x{O@rfG$N*O*3M8x$159e`}z< zWBmv!rr<#+{N0DY#W!aOzjaZ7g>= z#eJ>^ZxaTFE!9=83;D%O68xRHPG(d#1rldz7tlAYcT6xe{%)$0wEe$E z60$M|crld&S8mco3RN+~pwgx8UJ16m`HigTRwjhT!};c=t_KClT~DTgtHjzS5RuqW zz5lCbH@{qeyfg>XTsQB)4e*jUFLxiDK&W@iAx|KE*&?M>5G0KJXz_PG zJoZ}`&kkX)m7MbMPopf<|9f(ZZ$JUxEd3yPscCrE4VdS;hdccZm7l#a|Jz!0)wbRB zwRNQd@n4<1=*c@5G~9XJuuq^7{Hm;XQq>uUhz*f*RHi#$23nSEcl2OVIz9=q_Z**}Z$Y;y*p6A~~T34)OF2 z5N~Mh#A^tQKiHXYB%^0hxt;i`WHrI0|FdRU6?#J$H`A)*uYtdFOf!m>KCsxpm zm%m~haH3gh2%cfx-9W6RL~`SCC}=3{imwWVpwV z1Yx%|ClDazw72aAldSb9=yaf>m7Qvq9EE)4qT^!-@^PGAvI`qZE14}VEYA6+@@_0N zbGMReP!*bV#V*R?)*l;@F!);BXmmIPv4p4cGhi*b^~JIsd*0JAG@zuhptHG>Ko1xU zeAEU;PK|;w%1UPBIzBk#`0WkK#$&`ZQB|<%_vU4Bf=Iv3@q*3*p^R>qC|-IixKYB% zpd5fw4Rn0?<*{ZikAzESjhA~!l#Kw<-<>nhpY{h{-UX1cwmV$;`WU@6?!)I6`y>8t zm{$|dMyI#ZX@;_5tNfHi#TQ-hnoH}o0c2-1LlFY0w8!}Ya=T{s7rs5VNK;>`D^2&C zk8Mbhh%dhW)a36Znyr{ zH^Nc_7F+HCq9V@%*W3V3y<|^2N+mzD$y@Z`J2+Eg4~(o^<|5QvhWEGF1AjUP4{k89 z{xPqhzFV~}*3*YWen%;@k_P&By+`eootkY|q?b{E`xy)EV|lYJ-hs9|4*QHZ4Wv*P zcNjKRU^54aerRBI!D0&fyzx|;pKiU?WuFQ`n_Ya9E)Z6v{4sa?Gy98cr8F)pm9eLa zCanI~u3^_r+I2lwL14WyZ`fD%QugX3(Tb3NqPC15mGk1T@L`nl0E&TU=3sKvsVQkC z#fN`6_*Fhh0kVu{i02O9&v?2x+cPuA0MEc#%))j>plV7zP{Y4BqW9&iv$6#+@-*;M zqk=Yt6Rt~IDW9VzueU3?(MWhJ*DnE5Xvl{8`m`Ml@y`~eXJc^{;ZUGuxar4 z_MH>=nk`cfqBy%bHCwFzhRMLPY|~ecKgu?zbk*@bd>!28`*fWss`T9CdgSTi8~wMR zZWX8&)79reykP`IeVr0XS*f6bz8=C)D&;@5xkx?iVh1%i{w%KcT~Nb^>OdAple6Bo z(uhrDPq&`8HTYl=CFz2mXyKXEf6|@g6-8@j->JTpIgS%!01pN1D!uGOVBYiY0%9h@ zIcD|_8uu%K{kFf9X^I*94gNUWohNu+#14VGUQJjv{iJiwKo5;Qu>f3YX|>6y%SKE{ z%2u=A*fMq02mG8S&lZ1w11vC&JPj=(eAv9+T_{rXeik~1(O~M0^xyHc{RN<&I?Ecd zb$HHB7wa4`-sX8Zi^T6)dY>QntfY)bK5tngVw9}k@2>MpREZKcG;VlX#mWVeuyqjW zD&`W0T3CIIYni#nld*NUhD=v$u)IhOK6LfqpL27FYB2sRAq*dD#+^ypEGW$ls6W{? zb&FiPoBJos%GllVjyG<$Hvy`uMw}RWO!?>9)FIwrgdaa>-E&qq&gSXv=U%5`WVB2t zgOos-H8k_k5S)rJS3~(Fn7@c#`aX4n7oqNy^y-z%Q-oi&wch`>0<9B%0{M$Dk^vVE zs+nH#pOobH3LtD34MMh@vX|lF|Md8O%j2ufc_zd!r?k@{8D9T*?86_t&&#AyFe+L_ z-wzdyPyF#0uXJ6&qx*}`FO4ZENDKd_gW6K=Uj)(0clDic*OQlom$+a~8&5Vx%ZKCs zCERoVbR~0uFEZ}UWoGe2=feAGn-KoRqW4*3tld7bT*B?wG-A@VU0icj?(-=HuQO?s{eYeK3dT?!5@5Aahv9N)24HNb!aliTE$Ijn zHvQ~$5(X9x8#Ar@+;D?X?p*7>&x+~(TXh~?OKfz+l@cLBmxl2QFXC(<45YAT7jRhdYOL}%4i3Ch=)szNd@`gG8I9uI zG~Ayrff9*nLPFCd+|BXHrpnhmAa~D^w~O^04RKZwcZx}*k%e&@5MI+YEH}%uq*WPCApI<6fa=K z?tW)suf~olKQEpT898t6)M8;&s@A*Q#+BX|uG%|W1(6Y1t(Hb+s^#^d69sg+0r&j8 zl-{9g?9$a{4f3WB4rEByJY1U)TBK|0}Dz!Cx zCS>Cq66X_Vl5aiwS(R+@_Rkfi{CCK68{6nR<101I<&MZZ0nI0Y=q&QLj49IcH;VVL zTnyyK`4i?mC$>}1a7d~B6%A3D4--C11sUZ)w&;1El&QwBk!u z%>$p$pC^BDy_yV~$Kda`@NQ$Jjz3VamiGXEML!(99)F_tVQDUhjPFC%T>=6eU@%Nm-f%K1c(9*{!7_&R0WVP6$Mge!@_lq)5NcM#=ja+gFO&%yOT#1vLkKZMrn!Q zM}eWj(Mx&FvGjhpjP2-Hhj@IOzKH~(w4t+*!O&4gH9^pP|B@63skBl4SqLK8K{}*T zTD$!dI_l;jGC_yT?J2cDu_K!R1KIF*j4$7-gZ|)o+0nJSfx25FiZos|EBWA{=598j zwshC2%QRj)*wf7yDhBWfckIo1R2dAAVibNA?dv-xBEhBvc4%IuC389VE}1f+3fGhr z>T*lo<0h?ow|?fIQ6f3E#HACTX9khBZY!%(tSzf4`SyHQ&UYgId9GrBd3*WWuAvjf zf?HLFz?L(At@rVw_)9>pQF~nzDNF&S(d{-{nhE=5S z@a@rU?n@{#iVs(|Zm1PUkL_@{^y-}C0Tq!y1gw_ukN0%yokktoEKFS5SdJWKfh8bN zGj6&mZ#IGm@7if1YSLFX!q|t#W*vRn3Go0Y8gW0UTLD<}%G))Ooih@c;e|xT z7~nQRhYHP|v$qzMc|v*hg2}_RcrL&cVLe{?j_d1@N2EZJgEhnp_pqO!Ty~(lDrOdK zcObORc*3XZbMyII0CGOt9CWgN{LO~1`ene*s%Y#4E&3OW^xX&#q2W(Aie9r^x|2t9 zI-VyLCnQ2bLd#C$7W?seOTI+w)2%`9qBEX8n;u$FgxzxA8wenPa(C68YpR5`Y3Y*f zbQTF#N*hN}xyp$2_Qs>?ph7VVL4z@w`7_7b>2#qmmP?7ZaE zKK{W+t@IaYX#0sRl;%$Wg{NJ~gv=6)(m(qHK)om)KdLu z$t{5XB|e>NuLwAhu;waP}>WRJAWj4$GPzWaPH>C{hJhD z9NrV73mH4VB>bnG_ZyPkzsMWh8aY`1<_#nl%$jIw+|~bZFVO|=by5obs|#%xC;fkx z@yE3W{r^rH$xx+~Be~3ppSdp?{wN;Q4KIQY-QqmDzn_2tRI=YW!6!*++6fIc9o#fF zUC%yZcz$w3-g2{8OL^&h930p*8k;dg53&;a_cG}Pr4gKPvsT?&JOcx#{<#m7qS-Q6 z1Q)sLN|ikpGJUE9``{&2v3LGne84j_!vok&fJ!Oln|?$s_wu}AZh5tSBlY@+IpBN*&Ziq@N_lA_uZ&v*WT%rx72lNHf~HTpN(H=9ZV|~} zlH6D8&OkLE40SA|`^mnE{FO$Q6*D~H)cHAAKz>%VvOR}}t!VSeiuY0KDF$>`PNk?A zaBzwqsZm6o&2b^)KQzHzBfE*(Tgm;z zi(7auQPQF%F~zzcTMy|RFw5F4F&uJR{l>sk6TM2;6;fLt3#8+*HP|F~>&MjJd{OXe zYk(#Eb-t^#FXnt`Ytv{7KgNEgq}uazzJ;shP&JSPQF>1DA-1q0taphNc@(;vT7YYP z*WMu>MID~;K4bw3K^`hM4;4_4k2_JJE`9e~{25Lt`gkdOC`Yg7sg8w(7_yaQdH#YM zO!^pZDCpSHAf=ba?jL}|PB!9If|?*1`lie5I@FvZ%&)=jNbLH1=V0ce@>AW7qb=rU zZwvzP%WUpg5mXl+4)4FB|Hs(`vk}$mSnm_xzD-!{SDH66Ur`G3gP3iHlMmEfzkwkX zmo?m-05Y5&yn8&D)PY3zUJJ#Tg|(P89)E1TlSzNvCtjtTp1x8!H(pTf2HZ?@0@PLZ z9Nz!0w!|~1n>jF6RM=>281_-^6KsXcVL5oT%`qO97H{DQXXKX>9l;C`=Di)DmhF_R za8I3ob{(6VRA2r8&8Q5>kqAgU+y#fK?RrUj!9cjOOnBqhN1K1OXXpVBL;Rc)J9cp(LEK zP0ruL18tHvyex&_HQ>*1L*p%4q)}Br;#zoUVQrHg*{7rAA7&w7YS-CoPVx@eu0>MD zlhL=jya56XKU>welE$bRd~hSG`zz|74g|zoXrHX6Q-l5Xnt@WP=ZRZIqk~&UCxb^G zgn(WOGrcsS#=jMYWY#&$%z=t1#q$~gzFpov^nl?ab-Mpi{ME{jwd0FtUMqm+#KRYxj#+_t-F^68MBr$Y}HIa9Dg z(kaV_+~cAq?!Hr-Ineo34jtLM>L)8W;eZ2$k_^zwLNpy?gNKZqmJM6~LMnC*lpDz8 z1lZE0@!Zf-_WgJ(BXIA=$PW)vWDR&EeCSi3p>Ln`8DpRymDhtW{d#|@PQg^SYE7+U zvFOmoz$|`7-NS&qd%lT$R`{Lj_6z&&Mf|{~o%i?B_Ex9WpG`OOL8Y8^maBDa7BJ$V zR7!fYzF|pSgWeKPv_*^gh4u5 zTIT1v#=;;QgOZxB&BV8FmPyQ*+9~bxlUi5?g6oT8?p|X#CH=%m!ALXK9T*8O=HVd^ z4h~$-SEAPuT)NC<8mJ#}0TORGQo`ZbFL;;Mzoj8V>=2Q5X7};2`y~Xu<1BfBhc+T^ z@%&Xs?hE>f>S6KU`k;~e1s?J>mks}GVMG^0@NaSb{SPC*fUSrx!~fkAkPRBPeENg% z@+ex-7w;?)VT0bhL|tWf9zgY@%dTGj{~xprnMGgK3IsDKVp?$Amq72IuNp5z%)s*x zB?|suM2R-PpDk#li2Ns^37RJAH)hq%3D4l1X=WjIpW_7c321VsBU*VKT6%t$KlgPn z?w)*U(McQtCMRt0d~>>X?J75^S2rz)_+*4Bn&RIGEkl0&wOK=1R&Sd;u_KYJe zE2BL-BQ!FQr`I7~EG<^dMAw8@dHLX5tTzYMzxttmC)&-PA!G8_lb+|C4IJVdy#U!1 zt|$_Rf4Tjy5%|CKHHXs81x|V$>yJ5J$)?zpUAh{)>qfu}1+F)j>-(#)u@{hE{=d5Z zT>V9o0Hdcfu+;iGqF=KzSsX+Buh~PZE;2NTVrw}%!>GYpIoLcrE;r5Kba28A^D?~I zpqg2(ZmX6Dv{J)s6hAIT{0L(^uSf&YqOAJI5(T+0t|!fJrMJX?%q?uNSG?#vLL4yWO&6cbPxG zY`^%Z;zt9sBG`Rbfyux9i>=Fw#80d(*+Oz(`CfnQB7V-`v6nrCA1kb@=bVr@)wuUZRJR9&*rnO*R3&g=R#CA--5u&f$cZx$j-U2z zqFpUrlwA{vJu5{7Iw4&|r|(njPy0EBj^f5$@4HJ20b^^vM+-fm+t)>%F`+tlE(Vv0KNwPUbuBX*0GNsuqwsm{qgPTt#3eKVj& zzMw&8+qk*d784M9t{|QWfcu&p8f4ab$OIkz%PkwspC0rD&fikU213uIgQ|p6NOgDG zPf-k-P|Wy{E1-8DhD&uRoYBBpg8)$6@3Yd_I-A z$;`_8ku&J=H`U49d_(Ind9VE+EE`eLZIhoT@DSZ~vkX-{e_%!(0!ky3^;LJgybB;(nYFC!pqR zUrRq}O?@p_c|9Xs8k=*@!NC&`4g;9PfuZs7Y8aLlwj$P2^CHCY&agV>ZXjm6siwL6 zCxFU66*u{j0I;6eN9z9s*M{Oe=A8&C z<0zwwlO}cb)uXZH`!ik!E!yTq^`u(+ zq#>Mzwe6vmlYO_Q7eM@A{>>0^-+D5*U6hjPx4wfm17nPOw-=jw8+&FR z+o`mAlUM8TW6}E=OOJB3w*tIEF`68R%3rAc@pvIo5cibcqZ$KxUGc8mPAQ#Svc}&Y zlz=G2FIj7D|7fhS=-f3<5%ev|4>T~tBa|4Ao8AD`36(Yi1=iA&X`Ch+_tXYb@r7D^ zq07Lb@qV9r%YIONjk-MyrK_{Q4T`@e?OSh&5b34W-%KH}O$Sfz=(fbN;A~B-RC%0V zUjN)t$k8|99`Uhb9Cbw5u5$mI+KGD@UBE!*0g701Q~ZKJ{?V`)o$|~C4wlz94txSQ zz`VOMo_D%hJ}%mI+wUW(7SS$AWO#EN1hg^zG>)Q=hK0#L`v&poP}8mHahl%pQ?9t({p!F`a_*OnSkr9C@(ZTQ)QAH-Qt6{WY|IW=dcq$ET*4Jv9jG zigzK$1;qyXG>;!Vl|~jIzi$Gj9U@g|^B7%_cx^xA`*GnTjAKHMLE9JH`EFIX@cO#= zgTc8e>Ad!x7f%9r{P%L0*6(q&#Z(>Da(9WdsijJ>QWr)sq8>lVd}vq>7!akqPtRFQ zT2$s_w5bExB2vDHdot6vx<}LcIvALw3|ZnvHDq%Fn5lr_*9w?@%O4vtLoy=hnA*Qk z@S(gGfTm{h(tKGTLn7|RVnQYZ7cQ|Qr>_w&CPcF(B62IsEMS`-S z@w;?EC+$Yue2EjhOUOYXKgxrorA_derev1X5P%vx4yw-^Pb=My^Q)G*eS1hUzDB^I z#|6}uP*Jh7mY4R4D6PTcuI-n5qO7LlVTmM)s!0-3yxJ%mkVog|18H&h#}jOW6sz^5 z#g&>N`%1A?fGHazW2#|roRL&Qy|Zyi1+RN|YEmdh89&Y|4`}T*8|VYe|8$%BFyIVP zVy;8`lZk)cHgkf~ZU15ab))>hh5;K$=F>7&B$Fh z9sQqfVMcy|$gpv7^e&%gFP0X`YJ{7)UG zT5T}T4qMjpQc0Zu=hA0+Eht03FuKWa#yveLib@O{W+ar?{yZ}dFN2Ho3TvwpzxsVa)tME~ zr{1mlQFOi{`Uvlr{hbyuHp_P*+ah5Eo*}gz%yy30*c=+y6~%bBf?h5;?*QgNv@#iY zd$hQG^_}mCEE$t{@+|xNpC6qq0Z`7nKa2K2`@JIH`n1aTQf($=_ylDrGROdN;lN)` zG(MTrd@+*p_R5+GlFJrnOY3~}6_qUK^y718=0|GgU^j4>7}~--3O??-y*_wK5Wl>A zJe2ibGG9`{B+b8YwuD>grSWVUq$NBRGu;0a{aG<1?jvJ$v)61@T>Bf(pkBqdXD%Bozciot>pUYEj!C3nr8y#MJNWIjo=&tY))+8zI zu#i1U9BbkcX6u;3bFlfXNUg)t>1^p;k-x&hg#GE$RBR@*WieR33|Czo_Jyeai>WT} z{4k>(uJ)!~p(VQVH;r7IBlF#o&#cKiFnzUF_Wll;00Wi2dI<0+oc+VN)ApgZW|ms+ zs`~UjVd@YIyAH0Yh0b~AYQ4tflHx%SbEnB&zjVqMmM!E&gbJQPwcJB~@!^}Y7Bvf8 zy-0>b{*CgF=>A6W%-L*0qOtvVz3Vdqb>F`yWI`Hqm^o#W(4p%EJyLnnZ>>$9xAi^M z%ntPO8aV=S43SnE8&6rZwU^zgSHjAtSrkbA5IS-7Egmeg(;nKNQrsx6SW(q%&THUj z()O3?_okZsr#3e-?C!rBT|laOYqC-qBbdLK=Q{TOqNL0qnJ10XcY0e@gZNICZ6y`| zrD4s`!|wR&_6q5e)n=*oP((7x&vzt^OY_KM)QsjU0}BIZnG{tE4kF@lijVQkR$k8`Dj^m-@+WVBT#R3h*dm$O+ zB*sl%>u)Ru6=FAieetmny<{!wO*dshyzra;u0vWM`3DPmhSZJS%5lyskYdjAQmJMz6eaA+epdj84)PwZ-e}pg9&d-aB&@%8FHG^T9cx}Wu zMZ&Oce@CZ4=iel124aFAI9ydQ<&E$b3W#lKO&3$v0mzabhPgD)C;AsxD{2mm5>E) z+V9R^XOYfBnu>j#5^vm-E|qlE&6g@;Q6pZaCkMN&U}`b~PKs}nKaCLpi%UYY2RQu~ zKfEh3*zG>$PkR&3aSqdoxGdIE$*hNMYPUqx8(5&*Eg!~M z#BSeXsJHz&YG1`;J(>!?0{+#-n5Rf{6cTo8j2`7wt5m9f zq?V=ii7Ci{Mz^6$K?$C1Yk&Rs$K1iy6w$b@NV1H9JM+~m^=GAIq%(j7wAR&Be@cH= zDqvX0H3hZ8=6Ph8^-1H5m4bU+wl1r3k!Vv&n%v!LI)p7M0p zE(zJULA~AWc=7EQf`d@e91X&M*jMZ3AXrnDrOiGmFly$wzPnaUf^Q}(Re7PuN}+S8 zJ+>x1=W$HAfy4=9mDfq?ImJ-a-O@qoj=ho*JhSd9Ogmdt3595vg29I;(OjH2LPZE3 zk?uLHQt~e62{N@+l~u-zq2;yYcx(~X6|PI06qy4%-zJxtT;ty2la%Ud^DljW?K5-- z@K3^>c*oi_(k1l&D(%X{p?tf4wn&S8`6j!pQ4-mWY*_}8u}z557zWvQL&{Did^52U*6hEM=D^+1_W&@Lumfzkhz$HUG_XpZnbBocr9*ea`uuM|o&0g4Jta zIJ477^2ONxS`p_yf6dPo-mN05_bx8wTc!f%1kvwTPW2M6=|Z%!Q>ocZmJ9P)wXx|^ zZMcUqao(hZ2uC#9pgZ`W<5`%vGKMg8J%-rID zfRS9ut0-b9Oo+C>8{VqyXOIem{>ZBUVw+trbD&3-K&hzN2!Tj>xb_Q|X7I%SENb@( z>zOTAQ~N}e49qS>vpfauIW8j7su3hlNaw3Vqln3Rpgv%+0}daAlT*}b#kzfVuDORA z6U4}^=$ef=cfhU@ne3u}ZVp}?7ngAcT1<$SRm5?){P4!4-4!40L;x^}-L|RYh_y+E z6{MoZKuyf0Iy1A_$vqWV#N(t_N4QBOl7rUg;RBz#HO=1_t;VWKO&fwb6_T=3L>oVO zqC{H{h(vsn8sBP52k$#d)i#UMbcQ*0-4Dag=0841abw zO!>IGXA@JM4i6TG-2mK2`*{%(;_4q^k&&H%cw5Q3}uXM-pDQZ=Qit{o{Q7a1XgbJfnj2zh5yae@ z6GAdH42w0)(X56u{k1Y=X(vk{U3|MLf=vA$WbL8NKDozX|p((*j#gc2UI-cO7I zgE-C%y%_qzq}0rPz#|BL2`*yzxaYtFOm2nfU-hyQ&n!+0J~gjQkRC{ZsZ0vpR{A{I znqj#gbfI^ z|HL<*w4V?XHU_o{bn8BoWc3vFou9v!lRcc7al0&qNV-nK(KJ?2F6ug2#j7lvk8NSb z>G6sP^`CS^3-F{|yzR99p3CYlhsMICFC1)yRKO6j=Ms_qOTs;7wo<{EaEihl;YUDq zaB8N`YocjYBaupxo$H!+*kH2YGSNnUm7?-8MxxgPRDA_)4h%zi<10|zdZq zy|85REdd$J_$^qnjWf>S7RvG*Q!Fug+UOjj$;1)ARW{O_<0XK3-|yPPP71Oq`fp$#_i;00+62aC#6nrI?=CEDE&>lABN`GIo*7qpCAZ#+ySDPqZ*3Gwd3A z4MrEg7X#?Q&Fm*LK@Ui+vqSn$iug}YsMC99o1W+n9+6SJlQBhDp5o(zoqd|G4vw6$PYQDBXm&&q$0A*3u__l449wGm zC6{3YW`~X3cSyhk`uM1yL)6C*Zx4t-%IlBskc8s;r%$I}EfZ-)A!CndJw&2L>Fn_- zCxutC_cB5V|D=|zWUu1^$gUElM3OHCS;Zb zGclWiCyV+1(5k)UC4ZFCn=~RHAS}_ffY$SdF+H4TVk+|?^WHEtX{95=&_~01? z#N6ztLkkjANMi;DbeL_jI99@V3pH~7pYuoO!YVTv2VoMQucs8Mxx-|rpBP;tZa%Lw zX1WBeOVn)DFl83k#VPI|jex0vi^dSzB<-$wp3SfL^t9!+WLSZV?BTPPXx-Ex89p?UX zD)aG?e7X2+#0m1H`)73E*XhjS%C`kT6=;gsYUi2K;t795+m^ifyonL7he@aS7rD<4 z0qSK9^DGc4Wvo5)FlE7SP&SWC$MX?!38TKUBSlMA50kH=Os$OVW`b}254;XdfO28f zCpVYIOIBFxA2c`#pYhjWOgPE8&d~47#ai_m0z=>~c*3XpA74Q4jVJ z>@eWy%E@a&;2-{xnmPbS9 zIV{4)mn%}tZ+I2mrse;9$URpnoqD zgm-($%R0#q%)#~JTi1xscX@pSID0x~*OQ`K#60C$?5*OZfNhEi(<~!^K{7j=JSf%25N@vMzqg4n~5S zc+kGh`85_o|2nu=GGr>z0HvHe;wI|~bSWDt+s}QJp^As)K2xuTA1k#y+igh?Z;lw$ zh#Q8ZqHjhOQR0-K5wEj9$7R-HQhH`3_%#$5R?WzE zz^mZDRyglcHaY*6Li^;>C0{kCTGbU};h-whnCkKIgts_5^$nI6ftnwD{9|UXSM7si%az-yCr{^fEiZGBv!DMq-T2wFCTQ*=Ltp}L)l9$@w-Q$O1*fVVd#f1wdfQ%vZ3oK> zh^Q#71{BNq1)?l=AwMBls(I!n@YCqznD(`kM5iTYr)s zm8EyL;GSd^&AFq(I<}6R46rvWjClN9b)O?8^fsuk*=1I7HJjZ}D{ZUv_2ueLJ`YXu5ZnkI@Izdy)JQ(s%$px*eNuVGAa5eYt-QHgJ<;XXZZQ` zP)qk<@JCDH$@>=IMn!@kHb^Da(wcy^x>ar!>^X;Co^`>nsM1O01>Qby(qbT4IC8yK!f6n+;JA-egV;I=EL+0K%y=l0+RKT4# z_hYv3x0c-t>Pu3-vhOIcoAgF%Vm#0kYKhxq0_fni9 z1Z#DK^y7<>g+o5ndqc}c<7dA=UGrVJI?VeViL4ixoUBa|fW$!Qi)fu`tM6Or~B~9x`I|rX`H6)fn^b(dRbikTn*c#peE* zhE{LiClY{cWcO{2Rn`+1;&XG)%%?B{ zY?>RnK?PRt%d{>0tYTHJZZk$z-G^Onh5{w(13ar!Rk3z&^dw|vxC6&j;VOSU==MwU zgMK5s(K%SpBDN4omdU9ayBf(WK=*t-o8{5w(Y@vdUAO(*8=nu{_x>im>qI?hZ%v#- zuX)gosFPFD&s!t2ii6ecfq>wBntA_{*bQyP}yRcUWY zmAX9AWKr0Dub53zX)#C~uo3wCC4u7^3(go~a!%#nGpp`>CXCxs7OFBwqI@jezy*B0ig4 zC2`zu1PC%|M8B6r@OcUdijlq;+izrZJO>hVK>ip9Ns&y1ikyulJ%?=Uq4X2QKf_*> zAjWJ6S9U|uYh_unwU2Y^Z=BRl@1KQbn!JZqOm~DH3^tuR9)5~^j7pRZF_!oJzddOh z{b&p^#o?mvCxaO8iVAL)kooCFACB9SbXISY|p z`1_J&Qwt`TVXAD`CT1#7{g6 z^JKQ%`x|i#_y|#?jFL9!x0N<>D3kEUZ|F=Nm`l>k4>Z;uyAk&%?o^E?b$ z>Y7ERmA6>N9u~q9C6ktgNiFv}yO&ag3w4FiNzdQNd=qjE@S*>r&2v{(ncoqe;e*}l z&d#COvx(K+HOr=|&y)GP<@#=j{a%FHf7uN&{QYX+7Oz6^SKG6qqYiyPJo$QJZ(Bo19f5E>W+q0eQ>gw){et|d6qG5@6w3{MAE8L#dq_`&(LL8 z+u>&Oexu{!-$^KJNgkR5>l?4@H_>6;yut;4Gkq5a*Vz@Q;l2VKFGR)-Lu@OJABb#4T5Px`W);g8 zRL)Q4(?nZ*YzcVN!)U*{B$Ic#KHf#>MBHwS^LKwUkEpdD_AK0!mq(cM0iRDfrLCc_ JUUJJO^gm-#Cj$Tg literal 0 HcmV?d00001 diff --git a/community/dev/fork_var1.png b/community/dev/fork_var1.png index 666c36488666000cc5d05b9ec6c57b28491e34c4..1097b45d9048117a29431f09106a068ff0e56b78 100644 GIT binary patch literal 67181 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

^Uc!L#U+gMoPZf5-AaL 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 literal 0 HcmV?d00001 diff --git a/community/dev/fork_variables_2.png b/community/dev/fork_variables_2.png new file mode 100644 index 0000000000000000000000000000000000000000..16d5be36b8e4679ca5c192c39b8bc9cb734b6c91 GIT binary patch literal 16861 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⪚4zMG36xX-(&b*5N1E7fOwBhAg_4` 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 literal 0 HcmV?d00001 diff --git a/community/dev/github_login.png b/community/dev/github_login.png new file mode 100644 index 0000000000000000000000000000000000000000..ab1c59170744146eef9c01d4879b92524ed4040e GIT binary patch literal 20730 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 literal 0 HcmV?d00001 -- Gitee From 4fcb106e51b9f279f8ade4421143e48b0573c450 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 11 Apr 2022 17:01:50 +0800 Subject: [PATCH 2133/2647] text changes & removed unused picture Signed-off-by: Min Min --- community/dev/contributor-workflow.md | 11 +++++------ community/dev/fork-zadig-option.png | Bin 36777 -> 0 bytes community/dev/fork-zadig-vars.png | Bin 169440 -> 0 bytes community/dev/fork_var1.png | Bin 67181 -> 0 bytes community/dev/fork_var2.png | Bin 34962 -> 0 bytes community/dev/fork_var3.png | Bin 32941 -> 0 bytes 6 files changed, 5 insertions(+), 6 deletions(-) delete mode 100644 community/dev/fork-zadig-option.png delete mode 100644 community/dev/fork-zadig-vars.png delete mode 100644 community/dev/fork_var1.png delete mode 100644 community/dev/fork_var2.png delete mode 100644 community/dev/fork_var3.png diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index 1784aaee..f6896854 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -56,12 +56,11 @@ Zadig 为您提供云上测试环境 [https://os.koderover.com](https://os.koder ![Edit variable button](./edit_variable_button.png) 3. 在弹出的窗口中,如图修改如下字段: -在弹出的窗口中,点击 zadig 选项,如图修改如下字段: -1. dex.config.issuer=http://${github_id}-dex:5556/dex -2. dex.config.staticClients[0].redirectURISs[0]=http://${github_id}.ko.coderover.cn/api/v1/callback -3. dex.fullnameOverride=${github_id}-dex -4. endpoint.FQDN=${github_id}.ko.coderover.cn -5. global.extensions.extAuth.extauthzServerRef.namespace=zadig-env-${环境名称} + 1. dex.config.issuer=http://${github_id}-dex:5556/dex + 2. dex.config.staticClients[0].redirectURISs[0]=http://${github_id}.ko.coderover.cn/api/v1/callback + 3. dex.fullnameOverride=${github_id}-dex + 4. endpoint.FQDN=${github_id}.ko.coderover.cn + 5. global.extensions.extAuth.extauthzServerRef.namespace=zadig-env-${环境名称} ![Fork_varibales_1](./fork_variables_1.png) ![Fork_varibales_2](./fork_variables_2.png) diff --git a/community/dev/fork-zadig-option.png b/community/dev/fork-zadig-option.png deleted file mode 100644 index 5b4125cc3c5ece845394c6ca38ab34ed32b85236..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36777 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 mndWt;Q4<1$03pz35cvP_F24R5w?u{j0000 diff --git a/community/dev/fork-zadig-vars.png b/community/dev/fork-zadig-vars.png deleted file mode 100644 index 979ea70779d63331a6e90cd51b91c25214e8fc74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169440 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 z 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

  • -(q)(CZc4F7`N8W*62I(a8kg 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

    I<2N@qMHnjC93B9 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 g-PLhz7^yiJ30234Z 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

    ^Uc!L#U+gMoPZf5-AaL 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 diff --git a/community/dev/fork_var2.png b/community/dev/fork_var2.png deleted file mode 100644 index e9a8c83fe05d246ccf483a1d65a8f7d8185281fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34962 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 KaFL*%&;J3i-}&|c diff --git a/community/dev/fork_var3.png b/community/dev/fork_var3.png deleted file mode 100644 index ff872da6ae610b8cdcf14ebf87f9d5d82685a6a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32941 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 Jmb}sT{eOm{=)M2| -- Gitee From 8ec50788cc427e18aa6a0a26678949f98d904758 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 11 Apr 2022 18:24:07 +0800 Subject: [PATCH 2134/2647] add basic log to support custom release name Signed-off-by: allenshen --- .../core/common/repository/models/service.go | 87 +++++----- .../common/repository/models/task/deploy.go | 1 + .../repository/models/task/release_image.go | 1 + .../core/common/repository/mongodb/service.go | 10 ++ .../aslan/core/delivery/service/version.go | 2 +- .../core/environment/service/environment.go | 84 ++++++---- .../environment/service/environment_update.go | 154 ++++++++++++++++++ .../aslan/core/environment/service/helm.go | 2 +- .../aslan/core/environment/service/image.go | 15 +- .../aslan/core/service/handler/router.go | 1 + .../aslan/core/service/handler/service.go | 19 +++ .../aslan/core/service/service/service.go | 40 ++++- .../service/workflow/workflow_task.go | 23 ++- .../core/service/taskplugin/deploy_helm.go | 20 +-- .../core/service/taskplugin/release_image.go | 11 +- .../core/service/types/task/deploy.go | 1 + .../core/service/types/task/release_image.go | 7 +- pkg/setting/consts.go | 5 + pkg/util/helm.go | 8 + 19 files changed, 384 insertions(+), 107 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/service/environment_update.go diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 4ceb814b..611a981d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -18,6 +18,7 @@ package models import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + "github.com/koderover/zadig/pkg/setting" ) // TODO: move Revision out of Service. @@ -27,45 +28,46 @@ import ( // 1. Kubernetes service, and yaml+config is held in aslan: type == "k8s"; source == "spock"; yaml != "" // 2. Kubernetes service, and yaml+config is held in gitlab: type == "k8s"; source == "gitlab"; src_path != "" type Service struct { - ServiceName string `bson:"service_name" json:"service_name"` - Type string `bson:"type" json:"type"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - Revision int64 `bson:"revision" json:"revision"` - Source string `bson:"source,omitempty" json:"source,omitempty"` - GUIConfig *GUIConfig `bson:"gui_config,omitempty" json:"gui_config,omitempty"` - Yaml string `bson:"yaml,omitempty" json:"yaml"` - SrcPath string `bson:"src_path,omitempty" json:"src_path,omitempty"` - Commit *Commit `bson:"commit,omitempty" json:"commit,omitempty"` - KubeYamls []string `bson:"-" json:"-"` - Hash string `bson:"hash256,omitempty" json:"hash256,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time"` - CreateBy string `bson:"create_by" json:"create_by"` - Containers []*Container `bson:"containers,omitempty" json:"containers,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - Visibility string `bson:"visibility,omitempty" json:"visibility,omitempty"` - Status string `bson:"status,omitempty" json:"status,omitempty"` - GerritRepoName string `bson:"gerrit_repo_name,omitempty" json:"gerrit_repo_name,omitempty"` - GerritBranchName string `bson:"gerrit_branch_name,omitempty" json:"gerrit_branch_name,omitempty"` - GerritRemoteName string `bson:"gerrit_remote_name,omitempty" json:"gerrit_remote_name,omitempty"` - GerritPath string `bson:"gerrit_path,omitempty" json:"gerrit_path,omitempty"` - GerritCodeHostID int `bson:"gerrit_codeHost_id,omitempty" json:"gerrit_codeHost_id,omitempty"` - BuildName string `bson:"build_name,omitempty" json:"build_name,omitempty"` - HelmChart *HelmChart `bson:"helm_chart,omitempty" json:"helm_chart,omitempty"` - EnvConfigs []*EnvConfig `bson:"env_configs,omitempty" json:"env_configs,omitempty"` - EnvStatuses []*EnvStatus `bson:"env_statuses,omitempty" json:"env_statuses,omitempty"` - CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` - RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` - RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` - RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` - BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` - LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` - LoadFromDir bool `bson:"is_dir,omitempty" json:"is_dir,omitempty"` - CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` - HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` - WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` - EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` - TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` + ServiceName string `bson:"service_name" json:"service_name"` + Type string `bson:"type" json:"type"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Revision int64 `bson:"revision" json:"revision"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + GUIConfig *GUIConfig `bson:"gui_config,omitempty" json:"gui_config,omitempty"` + Yaml string `bson:"yaml,omitempty" json:"yaml"` + SrcPath string `bson:"src_path,omitempty" json:"src_path,omitempty"` + Commit *Commit `bson:"commit,omitempty" json:"commit,omitempty"` + KubeYamls []string `bson:"-" json:"-"` + Hash string `bson:"hash256,omitempty" json:"hash256,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time"` + CreateBy string `bson:"create_by" json:"create_by"` + Containers []*Container `bson:"containers,omitempty" json:"containers,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + Visibility string `bson:"visibility,omitempty" json:"visibility,omitempty"` + Status string `bson:"status,omitempty" json:"status,omitempty"` + GerritRepoName string `bson:"gerrit_repo_name,omitempty" json:"gerrit_repo_name,omitempty"` + GerritBranchName string `bson:"gerrit_branch_name,omitempty" json:"gerrit_branch_name,omitempty"` + GerritRemoteName string `bson:"gerrit_remote_name,omitempty" json:"gerrit_remote_name,omitempty"` + GerritPath string `bson:"gerrit_path,omitempty" json:"gerrit_path,omitempty"` + GerritCodeHostID int `bson:"gerrit_codeHost_id,omitempty" json:"gerrit_codeHost_id,omitempty"` + BuildName string `bson:"build_name,omitempty" json:"build_name,omitempty"` + HelmChart *HelmChart `bson:"helm_chart,omitempty" json:"helm_chart,omitempty"` + EnvConfigs []*EnvConfig `bson:"env_configs,omitempty" json:"env_configs,omitempty"` + EnvStatuses []*EnvStatus `bson:"env_statuses,omitempty" json:"env_statuses,omitempty"` + ReleaseNamingRule string `bson:"release_naming_rule" json:"release_naming_rule"` + CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` + RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` + RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` + RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` + BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` + LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` + LoadFromDir bool `bson:"is_dir,omitempty" json:"is_dir,omitempty"` + CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` + HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` + WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` } type CreateFromRepo struct { @@ -245,6 +247,13 @@ type PmHealthCheck struct { CurrentUnhealthyNum int `bson:"current_unhealthy_num,omitempty" json:"current_unhealthy_num,omitempty"` } +func (s *Service) GetReleaseNamingRule() string { + if len(s.ReleaseNamingRule) > 0 { + return s.ReleaseNamingRule + } + return setting.ReleaseNamingRulePlaceholder +} + func (Service) TableName() string { return "template_service" } diff --git a/pkg/microservice/aslan/core/common/repository/models/task/deploy.go b/pkg/microservice/aslan/core/common/repository/models/task/deploy.go index c00f36af..62f24c1c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/deploy.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/deploy.go @@ -41,6 +41,7 @@ type Deploy struct { ServiceName string `bson:"service_name" json:"service_name"` ServiceType string `bson:"service_type" json:"service_type"` ServiceRevision int64 `bson:"service_revision,omitempty" json:"service_revision,omitempty"` + ReleaseName string `bson:"release_name" json:"release_name"` ContainerName string `bson:"container_name" json:"container_name"` Image string `bson:"image" json:"image"` Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go index bd1f06c9..a9776223 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go @@ -47,6 +47,7 @@ type ReleaseImage struct { // New Version field ProductName string `bson:"product_name" json:"product_name"` + ReleaseName string `bson:"release_name" json:"release_name"` SourceImage string `bson:"source_image" json:"source_image"` DistributeInfo []*DistributeInfo `bson:"distribute_info" json:"distribute_info"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index b802a7bb..bd2ecf91 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -282,6 +282,16 @@ func (c *ServiceColl) UpdateServiceHealthCheckStatus(args *models.Service) error return err } +func (c *ServiceColl) UpdateServiceReleaseNamingRule(args *models.Service) error { + query := bson.M{"product_name": args.ProductName, "service_name": args.ServiceName, "revision": args.Revision} + changeMap := bson.M{ + "release_naming_rule": args.ReleaseNamingRule, + } + change := bson.M{"$set": changeMap} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *ServiceColl) Update(args *models.Service) error { // avoid panic issue if args == nil { diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 456c2fb8..7de61763 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -563,7 +563,7 @@ func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) return "", err } - releaseName := util.GeneHelmReleaseName(prod.Namespace, serviceObj.ServiceName) + releaseName := util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), prod.ProductName, prod.Namespace, prod.EnvName, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { log.Errorf("failed to get values map data, err: %s", err) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7ab23b05..00000133 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -191,6 +191,18 @@ type RawYamlResp struct { YamlContent string `json:"yamlContent"` } +type ReleaseInstallParam struct { + ProductName string + Namespace string + EnvName string + ServiceName string + ReleaseName string + DefaultValues string + MergedValues string + RenderChart *templatemodels.RenderChart + serviceObj *commonmodels.Service +} + type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { @@ -1905,13 +1917,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { for _, services := range productInfo.Services { for _, service := range services { - if err = hc.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: fmt.Sprintf("%s-%s", productInfo.Namespace, service.ServiceName), - Namespace: productInfo.Namespace, - Wait: true, - Force: true, - Timeout: Timeout * time.Second * 10, - }); err != nil { + if err = UninstallService(hc, productInfo.ProductName, productInfo.Namespace, service.ServiceName, service.Revision, true); err != nil { log.Errorf("UninstallRelease err:%v", err) } } @@ -2857,16 +2863,27 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func installOrUpgradeHelmChart(namespace string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client) error { - mergedValuesYaml, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) +func buildInstallParam(namespace string, envName string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service) (*ReleaseInstallParam, error) { + mergedValues, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { - err = errors.WithMessagef(err, "failed to merge override yaml %s and values %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues) - return err + return nil, fmt.Errorf("failed to merge override yaml %s and values %s, err: %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues, err) } - return installOrUpgradeHelmChartWithValues(namespace, mergedValuesYaml, renderChart, serviceObj, isRetry, helmClient) + ret := &ReleaseInstallParam{ + ProductName: serviceObj.ProductName, + Namespace: namespace, + EnvName: envName, + ServiceName: serviceObj.ServiceName, + ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), serviceObj.ProductName, namespace, envName, serviceObj.ServiceName), + DefaultValues: defaultValues, + MergedValues: mergedValues, + RenderChart: renderChart, + serviceObj: serviceObj, + } + return ret, nil } -func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service, isRetry bool, helmClient helmclient.Client) error { +func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry bool, helmClient helmclient.Client) error { + namespace, valuesYaml, renderChart, serviceObj := param.Namespace, param.MergedValues, param.RenderChart, param.serviceObj base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", @@ -2887,7 +2904,7 @@ func installOrUpgradeHelmChartWithValues(namespace, valuesYaml string, renderCha } chartSpec := &helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(namespace, serviceObj.ServiceName), + ReleaseName: param.ReleaseName, ChartName: chartPath, Namespace: namespace, Version: renderChart.ChartVersion, @@ -2945,8 +2962,11 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel } handler := func(serviceObj *commonmodels.Service, isRetry bool, logger *zap.SugaredLogger) error { - renderChart := chartInfoMap[serviceObj.ServiceName] - err = installOrUpgradeHelmChart(args.Namespace, renderChart, renderset.DefaultValues, serviceObj, isRetry, helmClient) + param, err := buildInstallParam(args.Namespace, renderset.EnvName, chartInfoMap[serviceObj.ServiceName], renderset.DefaultValues, serviceObj) + if err != nil { + return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, args.Namespace, err) + } + err = installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) if err != nil { log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) @@ -3104,18 +3124,12 @@ func updateProductGroup(username, productName, envName, updateType string, produ productTemplServiceMap[service.ServiceName] = service } - // 找到环境里面还存在但是服务编排里面已经删除的服务卸载掉 - for serviceName := range productServiceMap { + // remove services not existed in services templates + for serviceName, productSvc := range productServiceMap { if _, isExist := productTemplServiceMap[serviceName]; !isExist { go func(namespace, serviceName string) { log.Infof("ready to uninstall release:%s", fmt.Sprintf("%s-%s", namespace, serviceName)) - if err = helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), - Namespace: namespace, - Wait: true, - //Force: true, - //Timeout: Timeout * time.Second * 10, - }); err != nil { + if err = UninstallService(helmClient, productName, namespace, productSvc.ServiceName, productSvc.Revision, false); err != nil { log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) } }(productResp.Namespace, serviceName) @@ -3141,8 +3155,11 @@ func updateProductGroup(username, productName, envName, updateType string, produ } handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { - renderChart := renderChartMap[serviceObj.ServiceName] - errInstall := installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderSet.DefaultValues, serviceObj, isRetry, helmClient) + param, err := buildInstallParam(productResp.Namespace, renderSet.EnvName, renderChartMap[serviceObj.ServiceName], renderSet.DefaultValues, serviceObj) + if err != nil { + return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, err) + } + errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) if errInstall != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) return errors.Wrapf(errInstall, "failed to install or upgrade service %s", serviceObj.ServiceName) @@ -3420,12 +3437,15 @@ func updateProductVariable(productName, envName string, productResp *commonmodel renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(service *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { - renderChart := renderChartMap[service.ServiceName] - errInstall := installOrUpgradeHelmChart(productResp.Namespace, renderChart, renderset.DefaultValues, service, isRetry, helmClient) + handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { + param, err := buildInstallParam(productResp.Namespace, renderset.EnvName, renderChartMap[serviceObj.ServiceName], renderset.DefaultValues, serviceObj) + if err != nil { + return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, err) + } + errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) if errInstall != nil { - log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", service.ServiceName, productResp.Namespace, isRetry, errInstall) - return errors.Wrapf(errInstall, "failed to upgrade service %s", service.ServiceName) + log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, errInstall) + return errors.Wrapf(errInstall, "failed to upgrade service %s", serviceObj.ServiceName) } return nil } diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go new file mode 100644 index 00000000..bbcde8fe --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -0,0 +1,154 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + "time" + + "github.com/hashicorp/go-multierror" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" + helmclient "github.com/mittwald/go-helm-client" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func UninstallService(helmClient helmclient.Client, productName, namespace, serviceName string, revision int64, force bool) error { + revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Revision: revision, + ProductName: productName, + }) + if err != nil { + return fmt.Errorf("failed to find service: %s with revision: %d, err: %s", serviceName, revision, err) + } + return helmClient.UninstallRelease(&helmclient.ChartSpec{ + ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNamingRule(), productName, namespace, revisionSvc.EnvName, serviceName), + Namespace: namespace, + Wait: true, + Force: force, + Timeout: time.Second * 10, + }) +} + +func InstallService(helmClient helmclient.Client, param *ReleaseInstallParam) error { + handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { + errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) + if errInstall != nil { + log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, param.Namespace, isRetry, errInstall) + return errors.Wrapf(errInstall, "failed to install or upgrade service %s", serviceObj.ServiceName) + } + return nil + } + errList := batchExecutorWithRetry(3, time.Millisecond*2500, []*commonmodels.Service{param.serviceObj}, handler, log.SugaredLogger()) + if len(errList) > 0 { + return errList[0] + } + return nil +} + +// ReInstallServiceInEnv uninstall the service and reinstall to update releaseNaming rule +func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string, templateSvc *commonmodels.Service) error { + productSvcMap := productInfo.GetServiceMap() + productSvc, ok := productSvcMap[serviceName] + // service not applied in this environment + if !ok { + return nil + } + + log.Infof("###### reInstall service: %s in env: %s", serviceName, productInfo.EnvName) + renderInfo, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + Name: productSvc.Render.Name, + Revision: productSvc.Render.Revision}) + if err != nil { + return fmt.Errorf("failed to find renderset, %s:%d", productSvc.Render.Name, productSvc.Render.Revision) + } + + var renderChart *templatemodels.RenderChart + for _, _renderChart := range renderInfo.ChartInfos { + if _renderChart.ServiceName == serviceName { + renderChart = _renderChart + } + } + if renderChart == nil { + return fmt.Errorf("failed to find renderchart: %s", renderChart.ServiceName) + } + + helmClient, err := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) + if err != nil { + return err + } + + err = UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productSvc.ServiceName, productSvc.Revision, false) + if err != nil { + return fmt.Errorf("failed to uninstall release for service: %s, err: %s", productSvc.ServiceName, err) + } + + param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderChart, renderInfo.DefaultValues, templateSvc) + if err != nil { + return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", templateSvc.ServiceName, productInfo.Namespace, err) + } + + err = InstallService(helmClient, param) + if err != nil { + return fmt.Errorf("fauled to install release for service: %s, err: %s", templateSvc.ServiceName, err) + } + + foundSvc := false + // update service revision data in product + for groupIndex, svcGroup := range productInfo.Services { + for _, svc := range svcGroup { + if svc.ServiceName == serviceName { + svc.Revision = templateSvc.Revision + foundSvc = true + break + } + } + if foundSvc { + if err = commonrepo.NewProductColl().UpdateGroup(productInfo.EnvName, productInfo.ProductName, groupIndex, svcGroup); err != nil { + return fmt.Errorf("failed to update service group: %s:%s, err: %s ", productInfo.ProductName, productInfo.EnvName, err) + } + break + } + } + + return nil +} + +// UpdateSvcInAllEnvs updates svc in all envs in which the svc is already installed +func UpdateSvcInAllEnvs(productName, serviceName string, templateSvc *commonmodels.Service) error { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + }) + if err != nil { + return fmt.Errorf("failed to list envs for product: %s, err: %s", productName, err) + } + retErr := &multierror.Error{} + for _, product := range products { + err := ReInstallServiceInEnv(product, serviceName, templateSvc) + if err != nil { + retErr = multierror.Append(retErr, err) + } + } + return retErr.ErrorOrNil() +} diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 743a7594..37c1119a 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -174,7 +174,7 @@ func prepareChartVersionData(prod *models.Product, serviceObj *models.Service, r return err } - releaseName := util.GeneHelmReleaseName(prod.Namespace, serviceObj.ServiceName) + releaseName := util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), prod.ProductName, prod.Namespace, prod.EnvName, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { log.Errorf("failed to get values map data, err: %s", err) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index ec7199dd..7ed4f492 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -36,6 +36,7 @@ import ( helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" ) type UpdateContainerImageArgs struct { @@ -174,8 +175,20 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri return err } + param := &ReleaseInstallParam{ + ProductName: serviceObj.ProductName, + Namespace: namespace, + EnvName: product.EnvName, + ServiceName: serviceObj.ServiceName, + ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), serviceObj.ProductName, namespace, product.EnvName, serviceObj.ServiceName), + DefaultValues: renderSet.DefaultValues, + MergedValues: replacedMergedValuesYaml, + RenderChart: targetChart, + serviceObj: serviceObj, + } + // when replace image, should not wait - err = installOrUpgradeHelmChartWithValues(namespace, replacedMergedValuesYaml, targetChart, serviceObj, false, helmClient) + err = installOrUpgradeHelmChartWithValues(param, false, helmClient) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 47776600..514d2514 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -52,6 +52,7 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) k8s.PUT("/yaml/validator", YamlValidator) + k8s.GET("/:name/releaseNaming", HelmReleaseNaming) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 06a2fa0d..8c2778a7 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -146,6 +146,25 @@ func YamlValidator(c *gin.Context) { ctx.Resp = resp } +func HelmReleaseNaming(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + svcName := c.Query("name") + + args := new(svcservice.ReleaseNamingRule) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") + return + } + err := svcservice.UpdateReleaseNamingRule(projectName, svcName, args) + if err != nil { + ctx.Err = err + return + } +} + func DeleteServiceTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 4cef72d5..fe0cddf2 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -134,6 +134,10 @@ type YamlValidatorReq struct { Yaml string `json:"yaml,omitempty"` } +type ReleaseNamingRule struct { + NamingRule string `json:"naming_rule"` +} + func ListServicesInExtenalEnv(tmpResp *commonservice.ServiceTmplResp, log *zap.SugaredLogger) { opt := &commonrepo.ProductListOptions{ Source: setting.SourceFromExternal, @@ -923,7 +927,7 @@ func YamlValidator(args *YamlValidatorReq) []string { yamlData = config.ServiceNameAlias.ReplaceAllLiteralString(yamlData, args.ServiceName) if err := yaml.Unmarshal([]byte(yamlData), &resKind); err != nil { - log.Errorf("yaml unmarsh err: %s", err) + log.Errorf("yaml unmarshal err: %s", err) errorDetails = append(errorDetails, "Invalid yaml format. The content must be a series of valid Kubernetes resources") } } @@ -931,6 +935,40 @@ func YamlValidator(args *YamlValidatorReq) []string { return errorDetails } +func UpdateReleaseNamingRule(projectName, svcName string, args *ReleaseNamingRule) error { + serviceTemplate, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: svcName, + Revision: 0, + Type: setting.HelmDeployType, + ProductName: projectName, + ExcludeStatus: setting.ProductStatusDeleting, + }) + if err != nil { + return err + } + + oldReleaseNamingRule := serviceTemplate.GetReleaseNamingRule() + // nothing would happen if naming rule keeps the same + if oldReleaseNamingRule == args.NamingRule { + return nil + } + + rev, err := getNextServiceRevision(projectName, svcName) + if err != nil { + return fmt.Errorf("failed to get service next revision, service %s, err: %s", svcName, err) + } + serviceTemplate.Revision = rev + serviceTemplate.ReleaseNamingRule = args.NamingRule + + err = commonrepo.NewServiceColl().Create(serviceTemplate) + if err != nil { + return err + } + + // reinstall all services + return service.UpdateSvcInAllEnvs(projectName, svcName, serviceTemplate) +} + func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, visibility string, log *zap.SugaredLogger) error { openEnvTemplate, _ := strconv.ParseBool(isEnvTemplate) if openEnvTemplate && visibility == "public" { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 02499a7f..2829358e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -543,7 +543,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } } - // 获取全局configpayload + // get global configPayload configPayload := commonservice.GetConfigPayload(args.CodehostID) if len(env.RegistryID) == 0 { reg, _, err := commonservice.FindDefaultRegistry(false, log) @@ -597,7 +597,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } if env != nil { - // 生成部署的subtask + // generate subtasks to deploy for _, deployEnv := range target.Deploy { if deployEnv.Type == setting.PMDeployType { continue @@ -1314,14 +1314,19 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ return deployTask.ToSubTask() case setting.HelmDeployType: deployTask.ServiceType = setting.HelmDeployType - for _, services := range prodEnv.Services { - for _, service := range services { - if service.ServiceName == deployTask.ServiceName { - deployTask.ServiceRevision = service.Revision - return deployTask.ToSubTask() - } - } + if pSvc, ok := prodEnv.GetServiceMap()[deployTask.ServiceName]; ok { + deployTask.ServiceRevision = pSvc.Revision + } + revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: deployTask.ServiceName, + Revision: deployTask.ServiceRevision, + ProductName: prodEnv.ProductName, + }) + if err != nil { + return nil, fmt.Errorf("failed to find service: %s with revision: %s, err: %s", deployTask.ServiceName, deployTask.ServiceRevision, err) } + deployTask.ReleaseName = util.GeneReleaseName(revisionSvc.GetReleaseNamingRule(), prodEnv.ProductName, prodEnv.Namespace, prodEnv.EnvName, deployTask.ServiceName) + log.Infof("############ the release name is %s", deployTask.ReleaseName) return deployTask.ToSubTask() } return resp, fmt.Errorf("env type not match") diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go index b992a707..6ca9ad91 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -23,6 +23,8 @@ import ( "strings" "time" + "github.com/koderover/zadig/pkg/tool/log" + helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.uber.org/zap" @@ -43,7 +45,6 @@ import ( "github.com/koderover/zadig/pkg/tool/httpclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" s3tool "github.com/koderover/zadig/pkg/tool/s3" - "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" fsutil "github.com/koderover/zadig/pkg/util/fs" yamlutil "github.com/koderover/zadig/pkg/util/yaml" @@ -313,7 +314,9 @@ func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, return } - releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) + //releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) + releaseName := p.Task.ReleaseName + log.Info("####### the release name is %s", releaseName) ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) @@ -411,19 +414,6 @@ func (p *HelmDeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs return prod, nil } -func (p *HelmDeployTaskPlugin) getService(ctx context.Context, name, serviceType, productName string, revision int64) (*types.ServiceTmpl, error) { - url := fmt.Sprintf("/api/service/services/%s/%s", name, serviceType) - s := &types.ServiceTmpl{} - _, err := p.httpClient.Get(url, httpclient.SetResult(s), httpclient.SetQueryParams(map[string]string{ - "projectName": productName, - "revision": fmt.Sprintf("%d", revision), - })) - if err != nil { - return nil, err - } - return s, nil -} - // download chart info of specific version, use the latest version if fails func (p *HelmDeployTaskPlugin) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { logger := p.Log diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 3933f498..d31aebd3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -19,6 +19,10 @@ package taskplugin import ( "context" "fmt" + "path/filepath" + "strconv" + "time" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" @@ -26,16 +30,12 @@ import ( "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/kube/getter" s3tool "github.com/koderover/zadig/pkg/tool/s3" - "github.com/koderover/zadig/pkg/util" fsutil "github.com/koderover/zadig/pkg/util/fs" helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "helm.sh/helm/v3/pkg/releaseutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/labels" - "path/filepath" - "strconv" - "time" "go.uber.org/zap" yaml "gopkg.in/yaml.v3" @@ -656,7 +656,8 @@ DistributeLoop: continue DistributeLoop } - releaseName := util.GeneHelmReleaseName(distribute.DeployNamespace, distribute.DeployServiceName) + //releaseName := util.GeneHelmReleaseName(distribute.DeployNamespace, distribute.DeployServiceName) + releaseName := p.Task.ReleaseName ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) diff --git a/pkg/microservice/warpdrive/core/service/types/task/deploy.go b/pkg/microservice/warpdrive/core/service/types/task/deploy.go index c706557a..12842720 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/deploy.go +++ b/pkg/microservice/warpdrive/core/service/types/task/deploy.go @@ -41,6 +41,7 @@ type Deploy struct { ServiceName string `bson:"service_name" json:"service_name"` ServiceType string `bson:"service_type" json:"service_type"` ServiceRevision int64 `bson:"service_revision,omitempty" json:"service_revision,omitempty"` + ReleaseName string `bson:"release_name" json:"release_name"` ContainerName string `bson:"container_name" json:"container_name"` Image string `bson:"image" json:"image"` Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/release_image.go b/pkg/microservice/warpdrive/core/service/types/task/release_image.go index 7c43cf67..ff86ed40 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/release_image.go +++ b/pkg/microservice/warpdrive/core/service/types/task/release_image.go @@ -45,9 +45,10 @@ type ReleaseImage struct { Releases []RepoImage `bson:"releases" json:"releases"` // New Version field - ProductName string `json:"product_name"` - SourceImage string `json:"source_image"` - DistributeInfo []*DistributeInfo `json:"distribute_info"` + ProductName string `bson:"product_name" json:"product_name"` + ReleaseName string `bson:"release_name" json:"release_name"` + SourceImage string `bson:"source_image" json:"source_image"` + DistributeInfo []*DistributeInfo `bson:"distribute_info" json:"distribute_info"` } // DistributeInfo will be convert into yaml, adding yaml diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index a4cfe7dc..155d0a59 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -637,3 +637,8 @@ const ( const AttachedClusterNamespace = "koderover-agent" const ArtifactResultOut = "artifactResultOut.tar.gz" + +const ( + DefaultReleaseNamingRule = "$Service$" + ReleaseNamingRulePlaceholder = "$Namespace$-$Service$" +) diff --git a/pkg/util/helm.go b/pkg/util/helm.go index 6dfd7636..fa20049c 100644 --- a/pkg/util/helm.go +++ b/pkg/util/helm.go @@ -28,3 +28,11 @@ func GeneHelmReleaseName(namespace, serviceName string) string { func ExtraServiceName(releaseName, namespace string) string { return strings.TrimPrefix(releaseName, fmt.Sprintf("%s-", namespace)) } + +func GeneReleaseName(namingRule, projectName, namespace, envName, service string) string { + ret := strings.ReplaceAll(namingRule, "$Product$", projectName) + ret = strings.ReplaceAll(namingRule, "$Namespace$", namespace) + ret = strings.ReplaceAll(namingRule, "$EnvName", envName) + ret = strings.ReplaceAll(namingRule, "$Service$", service) + return ret +} -- Gitee From cafad7567503589c20e0de44ed56ad80e3145b8d Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 11 Apr 2022 20:49:51 +0800 Subject: [PATCH 2135/2647] word changes Signed-off-by: Min Min --- community/dev/contributor-workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community/dev/contributor-workflow.md b/community/dev/contributor-workflow.md index f6896854..868a58d3 100644 --- a/community/dev/contributor-workflow.md +++ b/community/dev/contributor-workflow.md @@ -47,7 +47,7 @@ Zadig 为您提供云上测试环境 [https://os.koderover.com](https://os.koder ![Login through Github](./github_login.png) -第 2 步:在 Zadig 项目中创建自己的环境 +第 2 步:在 Zadig 项目中获得开发者工作流和环境 步骤如下: 1. 点击 Zadig 项目 -- Gitee From 9f85d2f6561955eb8605a0025eb4758773340cef Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:41:23 +0800 Subject: [PATCH 2136/2647] update error message typo for better understanding (#1287) Signed-off-by: fansi Co-authored-by: fansi --- .../aslan/core/workflow/service/workflow/workflow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index b596fa22..43807519 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -423,7 +423,7 @@ func CreateWorkflow(workflow *commonmodels.Workflow, log *zap.SugaredLogger) err } if !checkWorkflowSubModule(workflow) { - return e.ErrUpsertWorkflow.AddDesc("workflow中没有子模块,请设置子模块") + return e.ErrUpsertWorkflow.AddDesc("未检测到构建部署或交付物部署,请配置一项") } if err := validateWorkflowHookNames(workflow); err != nil { @@ -485,7 +485,7 @@ func checkWorkflowSubModule(workflow *commonmodels.Workflow) bool { func UpdateWorkflow(workflow *commonmodels.Workflow, log *zap.SugaredLogger) error { if !checkWorkflowSubModule(workflow) { - return e.ErrUpsertWorkflow.AddDesc("workflow中没有子模块,请设置子模块") + return e.ErrUpsertWorkflow.AddDesc("未检测到构建部署或交付物部署,请配置一项") } currentWorkflow, err := commonrepo.NewWorkflowColl().Find(workflow.Name) -- Gitee From 02613373507790750a64f229a0d00f7dc7fe7465 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:57:05 +0800 Subject: [PATCH 2137/2647] add jmeter demo for test practice (#1285) Signed-off-by: fansi Co-authored-by: fansi --- examples/jMeter-demo/demo.jmx | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/jMeter-demo/demo.jmx diff --git a/examples/jMeter-demo/demo.jmx b/examples/jMeter-demo/demo.jmx new file mode 100644 index 00000000..b65b7173 --- /dev/null +++ b/examples/jMeter-demo/demo.jmx @@ -0,0 +1,51 @@ + + + + + + false + true + false + + + + + + + + continue + + false + 1 + + 10 + 1 + false + + + true + + + + + + + www.koderover.com + + https + + / + GET + true + false + true + false + + + + + + + + + -- Gitee From 5d977a3c0586e4a6bb67cea995c9e76e660fa836 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 12 Apr 2022 10:00:09 +0800 Subject: [PATCH 2138/2647] optimize code Signed-off-by: allenshen --- .../workflow/service/workflow/workflow_task.go | 18 +++++++++++------- .../core/service/taskplugin/deploy_helm.go | 2 +- .../core/service/taskplugin/release_image.go | 3 +++ pkg/util/helm.go | 10 +++------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 11029b0b..709ca726 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -639,7 +639,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, } if reflect.DeepEqual(distribute.Target, serviceModule) { distributeTasks, err = formatDistributeSubtasks( - args.ProductTmplName, + serviceModule, workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, workflow.DistributeStage.JumpBoxHost, @@ -1066,7 +1066,11 @@ func createReleaseImageTask(workflow *commonmodels.Workflow, args *commonmodels. } if distribute.Target.ServiceModule == imageInfo.ServiceModule && distribute.Target.ServiceName == imageInfo.ServiceName { distributeTasks, err = formatDistributeSubtasks( - args.ProductTmplName, + &commonmodels.ServiceModuleTarget{ + ProductName: args.ProductTmplName, + ServiceName: imageInfo.ServiceName, + ServiceModule: imageInfo.ServiceModule, + }, workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, workflow.DistributeStage.JumpBoxHost, @@ -1323,10 +1327,9 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ ProductName: prodEnv.ProductName, }) if err != nil { - return nil, fmt.Errorf("failed to find service: %s with revision: %s, err: %s", deployTask.ServiceName, deployTask.ServiceRevision, err) + return nil, fmt.Errorf("failed to find service: %s with revision: %d, err: %s", deployTask.ServiceName, deployTask.ServiceRevision, err) } deployTask.ReleaseName = util.GeneReleaseName(revisionSvc.GetReleaseNamingRule(), prodEnv.ProductName, prodEnv.Namespace, prodEnv.EnvName, deployTask.ServiceName) - log.Infof("############ the release name is %s", deployTask.ReleaseName) return deployTask.ToSubTask() } return resp, fmt.Errorf("env type not match") @@ -1378,9 +1381,9 @@ func artifactToSubTasks(name, image string) (map[string]interface{}, error) { return artifactTask.ToSubTask() } -func formatDistributeSubtasks(productName string, releaseImages []commonmodels.RepoImage, imageRepo, jumpboxHost, destStorageURL string, distribute *commonmodels.ProductDistribute) ([]map[string]interface{}, error) { +func formatDistributeSubtasks(serviceModule *commonmodels.ServiceModuleTarget, releaseImages []commonmodels.RepoImage, imageRepo, jumpboxHost, destStorageURL string, distribute *commonmodels.ProductDistribute) ([]map[string]interface{}, error) { var resp []map[string]interface{} - + productName := serviceModule.ProductName if distribute.ImageDistribute { t := taskmodels.ReleaseImage{ TaskType: config.TaskReleaseImage, @@ -1710,6 +1713,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator if env != nil { // 生成部署的subtask for _, deployEnv := range artifact.Deploy { + log.Infof("####### start deployEnvToSubTasks") deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) if err != nil { log.Errorf("deploy env to subtask error: %v", err) @@ -1765,7 +1769,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } if reflect.DeepEqual(distribute.Target, serviceModule) { distributeTasks, err = formatDistributeSubtasks( - args.ProductTmplName, + serviceModule, workflow.DistributeStage.Releases, workflow.DistributeStage.ImageRepo, workflow.DistributeStage.JumpBoxHost, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go index 6ca9ad91..fcfe624f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -316,7 +316,7 @@ func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, //releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) releaseName := p.Task.ReleaseName - log.Info("####### the release name is %s", releaseName) + log.Infof("####### the release name is %s", releaseName) ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index d31aebd3..764cc0dd 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -23,6 +23,8 @@ import ( "strconv" "time" + "github.com/koderover/zadig/pkg/tool/log" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" @@ -658,6 +660,7 @@ DistributeLoop: //releaseName := util.GeneHelmReleaseName(distribute.DeployNamespace, distribute.DeployServiceName) releaseName := p.Task.ReleaseName + log.Info("########## the release Name is %s", releaseName) ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) diff --git a/pkg/util/helm.go b/pkg/util/helm.go index fa20049c..2c148ae8 100644 --- a/pkg/util/helm.go +++ b/pkg/util/helm.go @@ -21,18 +21,14 @@ import ( "strings" ) -func GeneHelmReleaseName(namespace, serviceName string) string { - return fmt.Sprintf("%s-%s", namespace, serviceName) -} - func ExtraServiceName(releaseName, namespace string) string { return strings.TrimPrefix(releaseName, fmt.Sprintf("%s-", namespace)) } func GeneReleaseName(namingRule, projectName, namespace, envName, service string) string { ret := strings.ReplaceAll(namingRule, "$Product$", projectName) - ret = strings.ReplaceAll(namingRule, "$Namespace$", namespace) - ret = strings.ReplaceAll(namingRule, "$EnvName", envName) - ret = strings.ReplaceAll(namingRule, "$Service$", service) + ret = strings.ReplaceAll(ret, "$Namespace$", namespace) + ret = strings.ReplaceAll(ret, "$EnvName", envName) + ret = strings.ReplaceAll(ret, "$Service$", service) return ret } -- Gitee From a37157ffa512828bff113a722afd798b9693a471 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 12 Apr 2022 10:23:53 +0800 Subject: [PATCH 2139/2647] resolve comments Signed-off-by: allenshen --- .../core/environment/handler/policy.yaml | 7 +++++++ .../aslan/core/environment/service/helm.go | 20 ++++++++----------- pkg/util/url.go | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 314dd6e8..bf6bd59e 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -69,6 +69,13 @@ rules: matchAttributes: - key: "production" value: "false" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/helm/images" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/images" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - action: create_environment diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 4ee62977..f9eb2b52 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -312,22 +312,19 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog } func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLogger) (*ChartImagesResp, error) { - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + return nil, fmt.Errorf("failed to find product: %s:%s to get image infos, err: %s", productName, envName, err) } restConfig, err := kube.GetRESTConfig(prod.ClusterID) if err != nil { - log.Errorf("GetRESTConfig error: %v", err) - return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + return nil, fmt.Errorf("failed to get rest config: %s:%s to get image infos, err: %s", productName, envName, err) } helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) if err != nil { - log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) - return nil, e.ErrCreateDeliveryVersion.AddErr(err) + return nil, fmt.Errorf("failed to init kube client: %s:%s to get image infos, err: %s", productName, envName, err) } // filter releases, only list releases deployed by zadig @@ -344,18 +341,18 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo for _, svcName := range services { prodSvc, ok := serviceMap[svcName] if !ok || prodSvc == nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to find service: %s in product", svcName)) + return nil, fmt.Errorf("failed to find service: %s in product", svcName) } releaseName := util.GeneHelmReleaseName(prod.Namespace, svcName) valuesYaml, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddErr(err) + return nil, fmt.Errorf("failed to get values for relase: %s, err: %s", releaseName, err) } flatMap, err := converter.Flatten(valuesYaml) if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to get flat map url for release :%s", releaseName)) + return nil, fmt.Errorf("failed to get flat map url for release :%s", releaseName) } svcImage := &ServiceImages{ @@ -365,7 +362,7 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo for _, container := range prodSvc.Containers { if container.ImagePath == nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to parse image for container:%s", container.Image)) + return nil, fmt.Errorf("failed to parse image for container:%s", container.Image) } imageSearchRule := &template.ImageSearchingRule{ Repo: container.ImagePath.Repo, @@ -375,7 +372,7 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo pattern := imageSearchRule.GetSearchingPattern() imageUrl, err := commonservice.GeneImageURI(pattern, flatMap) if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(fmt.Sprintf("failed to get image url for container:%s", container.Image)) + return nil, fmt.Errorf("failed to get image url for container:%s", container.Image) } svcImage.Images = append(svcImage.Images, &ImageData{ @@ -384,7 +381,6 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo }) } ret.ServiceImages = append(ret.ServiceImages, svcImage) - } return ret, nil } diff --git a/pkg/util/url.go b/pkg/util/url.go index fbefbd67..69c0c744 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -57,7 +57,7 @@ func ReplaceRepo(origin, addr, namespace string) string { func ReplaceTag(origin, newTag string) string { parts := strings.Split(origin, ":") - if len(parts) < 2 { + if len(parts) != 2 { return origin } parts[len(parts)-1] = newTag -- Gitee From 6b9348aa846555db6d01fea170ac2e38c69bfd7b Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 12 Apr 2022 11:02:52 +0800 Subject: [PATCH 2140/2647] add services info to response Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/product.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 56c63187..c5b2e5f7 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -285,6 +285,10 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar servicesResp, _, errObj = ListGroups("", envName, prod.ProductName, -1, -1, log) } + if prod.Source == setting.SourceFromHelm { + prodResp.Services = prod.GetGroupServiceNames() + } + if errObj != nil { prodResp.Error = errObj.Error() } else { -- Gitee From a93668907ac9c01e496cc4110c5aa4b9bdaa9660 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 12 Apr 2022 13:52:55 +0800 Subject: [PATCH 2141/2647] optmize code Signed-off-by: allenshen --- .../core/common/repository/models/service.go | 88 +++++++++---------- .../core/common/repository/mongodb/service.go | 2 +- .../aslan/core/delivery/service/version.go | 2 +- .../core/environment/service/environment.go | 2 +- .../environment/service/environment_update.go | 2 +- .../aslan/core/environment/service/helm.go | 2 +- .../aslan/core/environment/service/image.go | 2 +- .../aslan/core/service/handler/router.go | 3 +- .../aslan/core/service/handler/service.go | 6 +- .../aslan/core/service/service/helm.go | 33 +++---- .../aslan/core/service/service/service.go | 17 ++-- .../service/workflow/workflow_task.go | 2 +- pkg/setting/consts.go | 4 +- 13 files changed, 84 insertions(+), 81 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 611a981d..43d9d8fb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -28,46 +28,46 @@ import ( // 1. Kubernetes service, and yaml+config is held in aslan: type == "k8s"; source == "spock"; yaml != "" // 2. Kubernetes service, and yaml+config is held in gitlab: type == "k8s"; source == "gitlab"; src_path != "" type Service struct { - ServiceName string `bson:"service_name" json:"service_name"` - Type string `bson:"type" json:"type"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - Revision int64 `bson:"revision" json:"revision"` - Source string `bson:"source,omitempty" json:"source,omitempty"` - GUIConfig *GUIConfig `bson:"gui_config,omitempty" json:"gui_config,omitempty"` - Yaml string `bson:"yaml,omitempty" json:"yaml"` - SrcPath string `bson:"src_path,omitempty" json:"src_path,omitempty"` - Commit *Commit `bson:"commit,omitempty" json:"commit,omitempty"` - KubeYamls []string `bson:"-" json:"-"` - Hash string `bson:"hash256,omitempty" json:"hash256,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time"` - CreateBy string `bson:"create_by" json:"create_by"` - Containers []*Container `bson:"containers,omitempty" json:"containers,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - Visibility string `bson:"visibility,omitempty" json:"visibility,omitempty"` - Status string `bson:"status,omitempty" json:"status,omitempty"` - GerritRepoName string `bson:"gerrit_repo_name,omitempty" json:"gerrit_repo_name,omitempty"` - GerritBranchName string `bson:"gerrit_branch_name,omitempty" json:"gerrit_branch_name,omitempty"` - GerritRemoteName string `bson:"gerrit_remote_name,omitempty" json:"gerrit_remote_name,omitempty"` - GerritPath string `bson:"gerrit_path,omitempty" json:"gerrit_path,omitempty"` - GerritCodeHostID int `bson:"gerrit_codeHost_id,omitempty" json:"gerrit_codeHost_id,omitempty"` - BuildName string `bson:"build_name,omitempty" json:"build_name,omitempty"` - HelmChart *HelmChart `bson:"helm_chart,omitempty" json:"helm_chart,omitempty"` - EnvConfigs []*EnvConfig `bson:"env_configs,omitempty" json:"env_configs,omitempty"` - EnvStatuses []*EnvStatus `bson:"env_statuses,omitempty" json:"env_statuses,omitempty"` - ReleaseNamingRule string `bson:"release_naming_rule" json:"release_naming_rule"` - CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` - RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` - RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` - RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` - BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` - LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` - LoadFromDir bool `bson:"is_dir,omitempty" json:"is_dir,omitempty"` - CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` - HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` - WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` - EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` - TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` + ServiceName string `bson:"service_name" json:"service_name"` + Type string `bson:"type" json:"type"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + Revision int64 `bson:"revision" json:"revision"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + GUIConfig *GUIConfig `bson:"gui_config,omitempty" json:"gui_config,omitempty"` + Yaml string `bson:"yaml,omitempty" json:"yaml"` + SrcPath string `bson:"src_path,omitempty" json:"src_path,omitempty"` + Commit *Commit `bson:"commit,omitempty" json:"commit,omitempty"` + KubeYamls []string `bson:"-" json:"-"` + Hash string `bson:"hash256,omitempty" json:"hash256,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time"` + CreateBy string `bson:"create_by" json:"create_by"` + Containers []*Container `bson:"containers,omitempty" json:"containers,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + Visibility string `bson:"visibility,omitempty" json:"visibility,omitempty"` + Status string `bson:"status,omitempty" json:"status,omitempty"` + GerritRepoName string `bson:"gerrit_repo_name,omitempty" json:"gerrit_repo_name,omitempty"` + GerritBranchName string `bson:"gerrit_branch_name,omitempty" json:"gerrit_branch_name,omitempty"` + GerritRemoteName string `bson:"gerrit_remote_name,omitempty" json:"gerrit_remote_name,omitempty"` + GerritPath string `bson:"gerrit_path,omitempty" json:"gerrit_path,omitempty"` + GerritCodeHostID int `bson:"gerrit_codeHost_id,omitempty" json:"gerrit_codeHost_id,omitempty"` + BuildName string `bson:"build_name,omitempty" json:"build_name,omitempty"` + HelmChart *HelmChart `bson:"helm_chart,omitempty" json:"helm_chart,omitempty"` + EnvConfigs []*EnvConfig `bson:"env_configs,omitempty" json:"env_configs,omitempty"` + EnvStatuses []*EnvStatus `bson:"env_statuses,omitempty" json:"env_statuses,omitempty"` + ReleaseNaming string `bson:"release_naming" json:"release_naming"` + CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` + RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` + RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` + RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` + BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` + LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` + LoadFromDir bool `bson:"is_dir,omitempty" json:"is_dir,omitempty"` + CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` + HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` + WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` + EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` + TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` } type CreateFromRepo struct { @@ -247,11 +247,11 @@ type PmHealthCheck struct { CurrentUnhealthyNum int `bson:"current_unhealthy_num,omitempty" json:"current_unhealthy_num,omitempty"` } -func (s *Service) GetReleaseNamingRule() string { - if len(s.ReleaseNamingRule) > 0 { - return s.ReleaseNamingRule +func (s *Service) GetReleaseNaming() string { + if len(s.ReleaseNaming) > 0 { + return s.ReleaseNaming } - return setting.ReleaseNamingRulePlaceholder + return setting.ReleaseNamingPlaceholder } func (Service) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index bd2ecf91..49d60e8b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -285,7 +285,7 @@ func (c *ServiceColl) UpdateServiceHealthCheckStatus(args *models.Service) error func (c *ServiceColl) UpdateServiceReleaseNamingRule(args *models.Service) error { query := bson.M{"product_name": args.ProductName, "service_name": args.ServiceName, "revision": args.Revision} changeMap := bson.M{ - "release_naming_rule": args.ReleaseNamingRule, + "release_naming_rule": args.ReleaseNaming, } change := bson.M{"$set": changeMap} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 7de61763..b1c52fbb 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -563,7 +563,7 @@ func ensureChartFiles(chartData *DeliveryChartData, prod *commonmodels.Product) return "", err } - releaseName := util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), prod.ProductName, prod.Namespace, prod.EnvName, serviceObj.ServiceName) + releaseName := util.GeneReleaseName(serviceObj.GetReleaseNaming(), prod.ProductName, prod.Namespace, prod.EnvName, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { log.Errorf("failed to get values map data, err: %s", err) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index b5a04658..efb194cf 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2883,7 +2883,7 @@ func buildInstallParam(namespace string, envName string, renderChart *templatemo Namespace: namespace, EnvName: envName, ServiceName: serviceObj.ServiceName, - ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), serviceObj.ProductName, namespace, envName, serviceObj.ServiceName), + ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, envName, serviceObj.ServiceName), DefaultValues: defaultValues, MergedValues: mergedValues, RenderChart: renderChart, diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index bbcde8fe..79f245c6 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -43,7 +43,7 @@ func UninstallService(helmClient helmclient.Client, productName, namespace, serv return fmt.Errorf("failed to find service: %s with revision: %d, err: %s", serviceName, revision, err) } return helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNamingRule(), productName, namespace, revisionSvc.EnvName, serviceName), + ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNaming(), productName, namespace, revisionSvc.EnvName, serviceName), Namespace: namespace, Wait: true, Force: force, diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 86264470..ca8eb8ee 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -180,7 +180,7 @@ func prepareChartVersionData(prod *models.Product, serviceObj *models.Service, r return err } - releaseName := util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), prod.ProductName, prod.Namespace, prod.EnvName, serviceObj.ServiceName) + releaseName := util.GeneReleaseName(serviceObj.GetReleaseNaming(), prod.ProductName, prod.Namespace, prod.EnvName, serviceObj.ServiceName) valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { log.Errorf("failed to get values map data, err: %s", err) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 7ed4f492..05a0cf8f 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -180,7 +180,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri Namespace: namespace, EnvName: product.EnvName, ServiceName: serviceObj.ServiceName, - ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNamingRule(), serviceObj.ProductName, namespace, product.EnvName, serviceObj.ServiceName), + ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, product.EnvName, serviceObj.ServiceName), DefaultValues: renderSet.DefaultValues, MergedValues: replacedMergedValuesYaml, RenderChart: targetChart, diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 514d2514..4715ddcd 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -42,6 +42,7 @@ func (*Router) Inject(router *gin.RouterGroup) { helm.POST("/services", CreateOrUpdateHelmService) helm.POST("/services/bulk", CreateOrUpdateBulkHelmServices) helm.PUT("/:productName", UpdateHelmService) + helm.PUT("/services/releaseNaming", HelmReleaseNaming) } k8s := router.Group("services") @@ -52,10 +53,8 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) k8s.PUT("/yaml/validator", YamlValidator) - k8s.GET("/:name/releaseNaming", HelmReleaseNaming) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) - k8s.GET("/:name/environments/deployable", GetDeployableEnvs) } diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 8c2778a7..71d03240 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -151,14 +151,16 @@ func HelmReleaseNaming(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() projectName := c.Query("projectName") - svcName := c.Query("name") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be nil") + } args := new(svcservice.ReleaseNamingRule) if err := c.BindJSON(args); err != nil { ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") return } - err := svcservice.UpdateReleaseNamingRule(projectName, svcName, args) + err := svcservice.UpdateReleaseNamingRule(projectName, args) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index f6498399..89249ad9 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -167,7 +167,6 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, } func GetHelmServiceModule(serviceName, productName string, revision int64, log *zap.SugaredLogger) (*HelmServiceModule, error) { - serviceTemplate, err := commonservice.GetServiceTemplate(serviceName, setting.HelmDeployType, productName, setting.ProductStatusDeleting, revision, log) if err != nil { return nil, err @@ -1202,21 +1201,22 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg } serviceObj := &commonmodels.Service{ - ServiceName: args.ServiceName, - Type: setting.HelmDeployType, - Revision: args.ServiceRevision, - ProductName: args.ProductName, - Visibility: setting.PrivateVisibility, - CreateTime: time.Now().Unix(), - CreateBy: args.CreateBy, - Containers: containerList, - CodehostID: args.CodehostID, - RepoOwner: args.Owner, - RepoName: args.Repo, - BranchName: args.Branch, - LoadPath: args.FilePath, - SrcPath: args.RepoLink, - Source: args.Source, + ServiceName: args.ServiceName, + Type: setting.HelmDeployType, + Revision: args.ServiceRevision, + ProductName: args.ProductName, + Visibility: setting.PrivateVisibility, + CreateTime: time.Now().Unix(), + CreateBy: args.CreateBy, + Containers: containerList, + CodehostID: args.CodehostID, + RepoOwner: args.Owner, + RepoName: args.Repo, + BranchName: args.Branch, + LoadPath: args.FilePath, + SrcPath: args.RepoLink, + Source: args.Source, + ReleaseNaming: setting.DefaultReleaseNaming, HelmChart: &commonmodels.HelmChart{ Name: chartName, Version: chartVersion, @@ -1253,6 +1253,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg log.Errorf("Failed to set status of current service templates, serviceName: %s, err: %s", args.ServiceName, err) return nil, err } + serviceObj.ReleaseNaming = currentSvcTmpl.GetReleaseNaming() } // create new service template diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index fe0cddf2..03bb50cf 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -135,7 +135,8 @@ type YamlValidatorReq struct { } type ReleaseNamingRule struct { - NamingRule string `json:"naming_rule"` + NamingRule string `json:"naming"` + ServiceName string `json:"service_name"` } func ListServicesInExtenalEnv(tmpResp *commonservice.ServiceTmplResp, log *zap.SugaredLogger) { @@ -935,9 +936,9 @@ func YamlValidator(args *YamlValidatorReq) []string { return errorDetails } -func UpdateReleaseNamingRule(projectName, svcName string, args *ReleaseNamingRule) error { +func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule) error { serviceTemplate, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: svcName, + ServiceName: args.ServiceName, Revision: 0, Type: setting.HelmDeployType, ProductName: projectName, @@ -947,18 +948,18 @@ func UpdateReleaseNamingRule(projectName, svcName string, args *ReleaseNamingRul return err } - oldReleaseNamingRule := serviceTemplate.GetReleaseNamingRule() + oldReleaseNamingRule := serviceTemplate.GetReleaseNaming() // nothing would happen if naming rule keeps the same if oldReleaseNamingRule == args.NamingRule { return nil } - rev, err := getNextServiceRevision(projectName, svcName) + rev, err := getNextServiceRevision(projectName, args.ServiceName) if err != nil { - return fmt.Errorf("failed to get service next revision, service %s, err: %s", svcName, err) + return fmt.Errorf("failed to get service next revision, service %s, err: %s", args.ServiceName, err) } serviceTemplate.Revision = rev - serviceTemplate.ReleaseNamingRule = args.NamingRule + serviceTemplate.ReleaseNaming = args.NamingRule err = commonrepo.NewServiceColl().Create(serviceTemplate) if err != nil { @@ -966,7 +967,7 @@ func UpdateReleaseNamingRule(projectName, svcName string, args *ReleaseNamingRul } // reinstall all services - return service.UpdateSvcInAllEnvs(projectName, svcName, serviceTemplate) + return service.UpdateSvcInAllEnvs(projectName, args.ServiceName, serviceTemplate) } func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, visibility string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 709ca726..746ac4a5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1329,7 +1329,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ if err != nil { return nil, fmt.Errorf("failed to find service: %s with revision: %d, err: %s", deployTask.ServiceName, deployTask.ServiceRevision, err) } - deployTask.ReleaseName = util.GeneReleaseName(revisionSvc.GetReleaseNamingRule(), prodEnv.ProductName, prodEnv.Namespace, prodEnv.EnvName, deployTask.ServiceName) + deployTask.ReleaseName = util.GeneReleaseName(revisionSvc.GetReleaseNaming(), prodEnv.ProductName, prodEnv.Namespace, prodEnv.EnvName, deployTask.ServiceName) return deployTask.ToSubTask() } return resp, fmt.Errorf("env type not match") diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 0a4b1450..b00a3067 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -640,6 +640,6 @@ const AttachedClusterNamespace = "koderover-agent" const ArtifactResultOut = "artifactResultOut.tar.gz" const ( - DefaultReleaseNamingRule = "$Service$" - ReleaseNamingRulePlaceholder = "$Namespace$-$Service$" + DefaultReleaseNaming = "$Service$" + ReleaseNamingPlaceholder = "$Namespace$-$Service$" ) -- Gitee From df832b26ea494302e999959bd5ea101bd3237945 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 12 Apr 2022 14:15:05 +0800 Subject: [PATCH 2142/2647] add cron job for docker clean Signed-off-by: mouuii --- go.mod | 1 + go.sum | 2 + .../common/repository/models/dind_clean.go | 2 + .../aslan/core/system/handler/router.go | 1 + .../aslan/core/system/handler/system_cache.go | 17 +++++ .../aslan/core/system/service/system_cache.go | 30 ++++++++ .../cron/core/service/client/cronjob.go | 35 +++++++++ .../cron/core/service/scheduler/scheduler.go | 74 +++++++++++++++++++ pkg/microservice/cron/server/server.go | 3 + pkg/shared/client/aslan/system.go | 23 ++++++ 10 files changed, 188 insertions(+) create mode 100644 pkg/shared/client/aslan/system.go diff --git a/go.mod b/go.mod index cd87aa67..bf097f9b 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/otiai10/copy v1.6.0 github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 + github.com/robfig/cron/v3 v3.0.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 diff --git a/go.sum b/go.sum index c01cabaf..46a359bb 100644 --- a/go.sum +++ b/go.sum @@ -1366,6 +1366,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= +github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= +github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= diff --git a/pkg/microservice/aslan/core/common/repository/models/dind_clean.go b/pkg/microservice/aslan/core/common/repository/models/dind_clean.go index 17c5b9b7..5ac3935f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/dind_clean.go +++ b/pkg/microservice/aslan/core/common/repository/models/dind_clean.go @@ -26,6 +26,8 @@ type DindClean struct { Status string `bson:"status" json:"status"` UpdateTime int64 `bson:"update_time" json:"update_time"` DindCleanInfos []*DindCleanInfo `bson:"dind_clean_infos" json:"dind_clean_infos"` + Cron string `bson:"cron" json:"cron"` + CronEnabled bool `bson:"cron_enabled" json:"cron_enabled"` } type DindCleanInfo struct { diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 7e6fb08b..8a67b040 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -87,6 +87,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { cleanCache.POST("/oneClick", CleanImageCache) cleanCache.GET("/state", CleanCacheState) + cleanCache.POST("/setCron", SetCron) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/system/handler/system_cache.go b/pkg/microservice/aslan/core/system/handler/system_cache.go index 00e87f17..ced5f6e7 100644 --- a/pkg/microservice/aslan/core/system/handler/system_cache.go +++ b/pkg/microservice/aslan/core/system/handler/system_cache.go @@ -30,6 +30,23 @@ func CleanImageCache(c *gin.Context) { ctx.Err = service.CleanImageCache(ctx.Logger) } +func SetCron(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + arg := new(SetCleanConfig) + if err := c.ShouldBindJSON(arg); err != nil { + ctx.Err = err + return + } + ctx.Err = service.SetCron(arg.Cron, arg.CronEnabled, ctx.Logger) +} + +type SetCleanConfig struct { + Cron string `json:"cron"` + CronEnabled bool `json:"cron_enabled"` +} + func CleanCacheState(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/system/service/system_cache.go b/pkg/microservice/aslan/core/system/service/system_cache.go index ca4d9ce5..5f337c05 100644 --- a/pkg/microservice/aslan/core/system/service/system_cache.go +++ b/pkg/microservice/aslan/core/system/service/system_cache.go @@ -43,6 +43,34 @@ const ( CleanStatusFailed = "failed" ) +//SetCron set the docker clean cron +func SetCron(cron string, cronEnabled bool, logger *zap.SugaredLogger) error { + dindCleans, _ := commonrepo.NewDindCleanColl().List() + switch len(dindCleans) { + case 0: + dindClean := &commonmodels.DindClean{ + Status: CleanStatusUnStart, + DindCleanInfos: []*commonmodels.DindCleanInfo{}, + Cron: cron, + CronEnabled: cronEnabled, + } + + if err := commonrepo.NewDindCleanColl().Upsert(dindClean); err != nil { + return e.ErrCreateDindClean.AddErr(err) + } + case 1: + dindClean := dindCleans[0] + dindClean.Status = dindCleans[0].Status + dindClean.DindCleanInfos = dindCleans[0].DindCleanInfos + dindClean.Cron = cron + dindClean.CronEnabled = cronEnabled + if err := commonrepo.NewDindCleanColl().Upsert(dindClean); err != nil { + return e.ErrUpdateDindClean.AddErr(err) + } + } + return nil +} + // CleanImageCache 清理镜像缓存 func CleanImageCache(logger *zap.SugaredLogger) error { //Get pod list by label and namespace @@ -157,6 +185,8 @@ func GetOrCreateCleanCacheState() (*commonmodels.DindClean, error) { Status: dindCleans[0].Status, UpdateTime: dindCleans[0].UpdateTime, DindCleanInfos: dindCleans[0].DindCleanInfos, + Cron: dindCleans[0].Cron, + CronEnabled: dindCleans[0].CronEnabled, } return dindClean, nil } diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 3d596ed3..517cc98b 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -174,6 +174,24 @@ func (c *Client) TriggerSystemGc(log *zap.SugaredLogger) error { return err } +//type CleanConfig struct { +// Cron string `json:"cron"` +// CronEnabled bool `json:"cron_enabled"` +//} +// +//func (c *Client) GetDockerCleanConfig() (*CleanConfig, error) { +// url := fmt.Sprintf("%s/system/cleanCache/state", c.APIBase) +// res, err := c.get(url) +// if err != nil { +// return nil, err +// } +// config := new(CleanConfig) +// if err := json.Unmarshal(res, config); err != nil { +// return nil, err +// } +// return config, nil +//} + func (c *Client) sendRequest(url string) error { request, err := http.NewRequest("GET", url, nil) if err != nil { @@ -187,6 +205,23 @@ func (c *Client) sendRequest(url string) error { return err } +//func (c *Client) get(url string) ([]byte, error) { +// request, err := http.NewRequest("GET", url, nil) +// if err != nil { +// return nil, err +// } +// +// resp, err := c.Conn.Do(request) +// if err == nil { +// defer func() { _ = resp.Body.Close() }() +// } +// result, err := ioutil.ReadAll(resp.Body) +// if err != nil { +// return nil, err +// } +// return result, err +//} + func (c *Client) sendPostRequest(url string, body io.Reader, log *zap.SugaredLogger) (string, error) { request, err := http.NewRequest("POST", url, body) if err != nil { diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 75895e0c..2b174fc7 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -20,6 +20,7 @@ import ( "fmt" stdlog "log" "os" + "reflect" "time" "github.com/jasonlvhit/gocron" @@ -32,7 +33,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/tool/log" + cronV3 "github.com/robfig/cron/v3" ) // CronClient ... @@ -49,6 +52,50 @@ type CronClient struct { log *zap.SugaredLogger } +type CronV3Client struct { + Cron *cronV3.Cron + AslanCli *aslan.Client +} + +func NewCronV3() *CronV3Client { + return &CronV3Client{ + Cron: cronV3.New(cronV3.WithSeconds()), + AslanCli: aslan.New(configbase.AslanServiceAddress()), + } +} + +func (c *CronV3Client) Start() { + var lastConfig *aslan.CleanConfig + var entryID cronV3.EntryID + c.Cron.AddFunc("*/3 * * * * *", func() { + // get the docker clean config + config, err := c.AslanCli.GetDockerCleanConfig() + if err != nil { + log.Errorf("get config err ,:%v", err) + return + } + if !reflect.DeepEqual(lastConfig, config) { + lastConfig = config + log.Infof("config changed to %v", config) + switch config.CronEnabled { + case true: + c.Cron.Run() + entryID, err = c.Cron.AddFunc(config.Cron, func() { + log.Infof("********add********* , %v\n", config) + }) + if err != nil { + log.Errorf("err get entryID:%v", entryID) + } + case false: + log.Infof("*******remove job********* , %v\n", config) + c.Cron.Remove(entryID) + } + } + }) + + c.Cron.Start() +} + const ( CleanJobScheduler = "CleanJobScheduler" @@ -76,6 +123,8 @@ const ( InitHealthCheckPmHostScheduler = "InitHealthCheckPmHostScheduler" InitHelmEnvSyncValuesScheduler = "InitHelmEnvSyncValuesScheduler" + + //InitDockerCleanScheduler = "InitDockerCleanScheduler" ) // NewCronClient ... @@ -148,8 +197,33 @@ func (c *CronClient) Init() { c.InitHealthCheckPmHostScheduler() // sync values from remote for helm envs at regular intervals c.InitHelmEnvSyncValuesScheduler() + // add docker clean + //c.InitDockerCleanScheduler() } +//func (c *CronClient) InitDockerCleanScheduler() { +// +// lastConfig := new(client.CleanConfig) +// c.Schedulers[InitDockerCleanScheduler] = gocron.NewScheduler() +// checkConfigfunc := func() { +// config, err := c.AslanCli.GetDockerCleanConfig() +// if err != nil { +// return +// } +// +// if !reflect.DeepEqual(config, lastConfig) { +// lastConfig = config +// // 配置发生变化 +// +// } +// +// } +// c.Schedulers[InitDockerCleanScheduler].Every(20).Seconds().Do( +// checkConfigfunc, c.log) +// +// c.Schedulers[InitDockerCleanScheduler].Start() +//} + func (c *CronClient) InitCleanJobScheduler() { c.Schedulers[CleanJobScheduler] = gocron.NewScheduler() diff --git a/pkg/microservice/cron/server/server.go b/pkg/microservice/cron/server/server.go index e35fc751..85e63505 100644 --- a/pkg/microservice/cron/server/server.go +++ b/pkg/microservice/cron/server/server.go @@ -39,6 +39,9 @@ func Serve(ctx context.Context) error { cronClient := scheduler.NewCronClient() cronClient.Init() + cronV3Client := scheduler.NewCronV3() + cronV3Client.Start() + http.HandleFunc("/ping", ping) server := &http.Server{Addr: ":8091", Handler: nil} diff --git a/pkg/shared/client/aslan/system.go b/pkg/shared/client/aslan/system.go new file mode 100644 index 00000000..2d8e012e --- /dev/null +++ b/pkg/shared/client/aslan/system.go @@ -0,0 +1,23 @@ +package aslan + +import ( + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type CleanConfig struct { + Cron string `json:"cron"` + CronEnabled bool `json:"cron_enabled"` +} + +func (c *Client) GetDockerCleanConfig() (*CleanConfig, error) { + url := "/system/cleanCache/state" + + res := new(CleanConfig) + + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, nil +} -- Gitee From 6e6aa97771242fee548c07dabcb9ac210a9f702e Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 12 Apr 2022 14:19:59 +0800 Subject: [PATCH 2143/2647] add cron job for docker clean Signed-off-by: mouuii --- pkg/microservice/cron/core/service/scheduler/scheduler.go | 4 ++++ pkg/shared/client/aslan/system.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 2b174fc7..8f298be4 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -82,6 +82,10 @@ func (c *CronV3Client) Start() { c.Cron.Run() entryID, err = c.Cron.AddFunc(config.Cron, func() { log.Infof("********add********* , %v\n", config) + // call docker clean + if err := c.AslanCli.DockerClean(); err != nil { + log.Errorf("clean docker cache fail , err:%s", err) + } }) if err != nil { log.Errorf("err get entryID:%v", entryID) diff --git a/pkg/shared/client/aslan/system.go b/pkg/shared/client/aslan/system.go index 2d8e012e..635b6846 100644 --- a/pkg/shared/client/aslan/system.go +++ b/pkg/shared/client/aslan/system.go @@ -21,3 +21,9 @@ func (c *Client) GetDockerCleanConfig() (*CleanConfig, error) { return res, nil } + +func (c *Client) DockerClean() error { + url := "/system/cleanCache/oneClick" + _, err := c.Post(url) + return err +} -- Gitee From ef74633aad9b9efdb0ef4bd571a833185f771b3c Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Apr 2022 17:51:54 +0800 Subject: [PATCH 2144/2647] api done Signed-off-by: Min Min --- go.mod | 2 +- .../repository/models/registry_namespace.go | 4 + .../aslan/core/system/handler/registry.go | 4 + .../aslan/core/system/handler/resp.go | 2 + .../aslan/core/system/service/registry.go | 113 ++++++++++++++++++ pkg/shared/kube/client/client.go | 9 ++ pkg/tool/kube/client/cluster.go | 9 ++ pkg/tool/kube/getter/statefulset.go | 7 ++ pkg/tool/kube/multicluster/service.go | 19 +++ 9 files changed, 168 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index cd87aa67..d6f156a6 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 - github.com/antihax/optional v1.0.0 // indirect + github.com/antihax/optional v1.0.0 github.com/aws/aws-sdk-go v1.34.28 github.com/blang/semver/v4 v4.0.0 github.com/bndr/gojenkins v1.1.0 diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index baf90d55..ee3d44e5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -38,6 +38,10 @@ type RegistryNamespace struct { Region string `bson:"region,omitempty" json:"region,omitempty"` UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` + + // New field since v1.11 to support self-signed TLS certificate + TLSCert string `bson:"tls_cert" json:"tls_cert"` + TLSKey string `bson:"tls_key" json:"tls_key"` } func (ns *RegistryNamespace) Validate() error { diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 9c6e950a..450b8b8d 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -49,6 +49,8 @@ func GetDefaultRegistryNamespace(c *gin.Context) { return } + // FIXME: a new feature in 1.11 added a tls certificate field for registry, but it is not added in this API temporarily + // since it is for Kodespace ONLY ctx.Resp = &Registry{ ID: reg.ID.Hex(), RegAddr: reg.RegAddr, @@ -74,6 +76,8 @@ func GetRegistryNamespace(c *gin.Context) { RegAddr: reg.RegAddr, IsDefault: reg.IsDefault, Namespace: reg.Namespace, + TLSCert: reg.TLSCert, + TLSKey: reg.TLSKey, } } diff --git a/pkg/microservice/aslan/core/system/handler/resp.go b/pkg/microservice/aslan/core/system/handler/resp.go index f36bb4da..b11a5006 100644 --- a/pkg/microservice/aslan/core/system/handler/resp.go +++ b/pkg/microservice/aslan/core/system/handler/resp.go @@ -23,4 +23,6 @@ type Registry struct { Namespace string `json:"namespace"` AccessKey string `json:"access_key"` SecretKey string `json:"secret_key"` + TLSCert string `json:"tls_cert"` + TLSKey string `json:"tls_key"` } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index e2a33d85..0d14b7e3 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -17,8 +17,15 @@ limitations under the License. package service import ( + "context" "errors" "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/util/retry" "strings" "go.uber.org/zap" @@ -96,6 +103,8 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa log.Errorf("RegistryNamespace.Create error: %v", err) return fmt.Errorf("RegistryNamespace.Create error: %v", err) } + + //return UpdateDinD(log) return nil } @@ -271,3 +280,107 @@ func UpdateRegistryNamespaceDefault(args *commonmodels.RegistryNamespace, log *z } return nil } + +func UpdateDinD(log *zap.SugaredLogger) error { + repoInfo, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + if err != nil { + log.Errorf("failed to list all registries to update dind, the error is: %s", err) + return err + } + + dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) + return err + } + + volumeMountList := make([]map[string]string, 0) + volumeList := make([]map[string]interface{}, 0) + for _, repo := range repoInfo { + if repo.TLSCert != "" { + mountName := fmt.Sprintf("%s-%s-cert", repo.RegAddr, repo.Namespace) + // create volumeMount info + volumeMountMap := map[string]string{ + "mountPath": "/etc/docker/certs.d", + "name": mountName, + } + volumeMountList = append(volumeMountList, volumeMountMap) + // create volume info + secretItemList := make([]map[string]string, 0) + secretItemList = append(secretItemList, map[string]string{ + "key": "cert.crt", + "path": fmt.Sprintf("%s/%s", repo.RegAddr, "cert.crt"), + }) + secretInfo := map[string]interface{}{ + "items": secretItemList, + "secretName": mountName, + } + volumeMap := map[string]interface{}{ + "name": mountName, + "secret": secretInfo, + } + volumeList = append(volumeList, volumeMap) + } + } + + stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulset"} + + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Retrieve the latest version of Deployment before attempting update + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver + result, getErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Get(context.TODO(), "dind", metav1.GetOptions{}) + if getErr != nil { + return err + } + + // extract spec containers + containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") + if err != nil || !found || containers == nil { + return err + } + + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { + return err + } + _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) + return updateErr + }) + + if retryErr != nil { + return fmt.Errorf("update dind statefulset failed: %v", retryErr) + } + + return nil +} + +//func CreateCertSecret(secretName, cert string, log *zap.SugaredLogger) error { +// dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) +// if err != nil { +// log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) +// return err +// } +// +// certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) +// +// secret := &unstructured.Unstructured{ +// Object: map[string]interface{}{ +// "apiVersion": "v1", +// "kind": "Secret", +// "metadata": map[string]interface{}{ +// "name": secretName, +// }, +// "type": "Opaque", +// "data": map[string]string{ +// "cert.crt": certificateString, +// }, +// }, +// } +// +// result, err := client.R +//} diff --git a/pkg/shared/kube/client/client.go b/pkg/shared/kube/client/client.go index 60966057..c7581c4b 100644 --- a/pkg/shared/kube/client/client.go +++ b/pkg/shared/kube/client/client.go @@ -17,6 +17,7 @@ limitations under the License. package client import ( + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -41,6 +42,14 @@ func GetKubeClientSet(hubServerAddr, clusterID string) (*kubernetes.Clientset, e return multicluster.GetKubeClientSet(hubServerAddr, clusterID) } +func GetDynamicKubeClient(hubserverAddr, clusterID string) (dynamic.Interface, error) { + if clusterID == setting.LocalClusterID { + clusterID = "" + } + + return multicluster.GetDynamicKubeclient(hubserverAddr, clusterID) +} + func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { if clusterID == setting.LocalClusterID { clusterID = "" diff --git a/pkg/tool/kube/client/cluster.go b/pkg/tool/kube/client/cluster.go index 6b575f94..d0d85c55 100644 --- a/pkg/tool/kube/client/cluster.go +++ b/pkg/tool/kube/client/cluster.go @@ -18,6 +18,7 @@ package client import ( "context" + "k8s.io/client-go/dynamic" "sync" "github.com/pkg/errors" @@ -57,6 +58,14 @@ func NewClientSet() (*kubernetes.Clientset, error) { return kubernetes.NewForConfig(config) } +func NewDynamicClient() (dynamic.Interface, error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, err + } + return dynamic.NewForConfig(config) +} + func Client() client.Client { return Cluster().GetClient() } diff --git a/pkg/tool/kube/getter/statefulset.go b/pkg/tool/kube/getter/statefulset.go index 7c7bbedc..1fda0a64 100644 --- a/pkg/tool/kube/getter/statefulset.go +++ b/pkg/tool/kube/getter/statefulset.go @@ -17,10 +17,13 @@ limitations under the License. package getter import ( + "context" appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -34,6 +37,10 @@ func GetStatefulSet(ns, name string, cl client.Client) (*appsv1.StatefulSet, boo return ss, found, err } +func GetStatefulSetNew(ns, name string, clientset *kubernetes.Clientset) (*appsv1.StatefulSet, error) { + return clientset.AppsV1().StatefulSets(ns).Get(context.TODO(), name, metav1.GetOptions{}) +} + func ListStatefulSets(ns string, selector labels.Selector, cl client.Client) ([]*appsv1.StatefulSet, error) { ss := &appsv1.StatefulSetList{} err := ListResourceInCache(ns, selector, nil, ss, cl) diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index 882f5ea6..649910a6 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -18,6 +18,7 @@ package multicluster import ( "fmt" + "k8s.io/client-go/dynamic" "net/http" "github.com/gin-gonic/gin" @@ -57,6 +58,19 @@ func GetKubeClientSet(hubServerAddr, clusterID string) (*kubernetes.Clientset, e return clusterService.GetClientGoKubeClient(clusterID) } +func GetDynamicKubeclient(hubServerAddr, clusterID string) (dynamic.Interface, error) { + if clusterID == "" { + return krkubeclient.NewDynamicClient() + } + + clusterService, err := NewAgent(hubServerAddr) + if err != nil { + return nil, fmt.Errorf("failed to get clusterService: %v", err) + } + + return clusterService.GetDynamicKubeClient(clusterID) +} + func GetKubeAPIReader(hubServerAddr, clusterID string) (client.Reader, error) { if clusterID == "" { return krkubeclient.APIReader(), nil @@ -144,6 +158,11 @@ func (s *Agent) GetClientGoKubeClient(clusterID string) (*kubernetes.Clientset, return kubernetes.NewForConfig(config) } +func (s *Agent) GetDynamicKubeClient(clusterID string) (dynamic.Interface, error) { + config := generateRestConfig(clusterID, s.hubServerAddr) + return dynamic.NewForConfig(config) +} + func (s *Agent) ClusterConnected(clusterID string) bool { if err := s.hubClient.HasSession(clusterID); err != nil { return false -- Gitee From 9d62c229778cd3739519e21ef5b0233063be250b Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Apr 2022 18:01:49 +0800 Subject: [PATCH 2145/2647] change data structure Signed-off-by: Min Min --- .../aslan/core/common/repository/models/registry_namespace.go | 4 ++++ pkg/microservice/aslan/core/system/service/registry.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index ee3d44e5..15bcc80c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -39,6 +39,10 @@ type RegistryNamespace struct { UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` + AdvancedSetting *RegistryAdvancedSetting `bson:"advanced_setting" json:"advanced_setting"` +} + +type RegistryAdvancedSetting struct { // New field since v1.11 to support self-signed TLS certificate TLSCert string `bson:"tls_cert" json:"tls_cert"` TLSKey string `bson:"tls_key" json:"tls_key"` diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 0d14b7e3..b03fbb6e 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -297,7 +297,7 @@ func UpdateDinD(log *zap.SugaredLogger) error { volumeMountList := make([]map[string]string, 0) volumeList := make([]map[string]interface{}, 0) for _, repo := range repoInfo { - if repo.TLSCert != "" { + if repo.AdvancedSetting != nil && repo.AdvancedSetting.TLSCert != "" { mountName := fmt.Sprintf("%s-%s-cert", repo.RegAddr, repo.Namespace) // create volumeMount info volumeMountMap := map[string]string{ -- Gitee From 685c21a49f9f76357bc8584917c164fc52d1f8a6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Apr 2022 18:04:56 +0800 Subject: [PATCH 2146/2647] minor fix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/handler/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 450b8b8d..524b87a3 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -76,8 +76,8 @@ func GetRegistryNamespace(c *gin.Context) { RegAddr: reg.RegAddr, IsDefault: reg.IsDefault, Namespace: reg.Namespace, - TLSCert: reg.TLSCert, - TLSKey: reg.TLSKey, + TLSCert: reg.AdvancedSetting.TLSCert, + TLSKey: reg.AdvancedSetting.TLSKey, } } -- Gitee From c7772b5a83dfb29682f82ae675f507ca3e6655c2 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 12 Apr 2022 18:42:17 +0800 Subject: [PATCH 2147/2647] add connector and codehost encrypte Signed-off-by: panxunying --- .../aslan/core/common/service/rsa_key.go | 14 +++++--- .../core/codehost/handler/codehost.go | 2 +- .../core/codehost/service/codehost.go | 35 +++++++++++++++++-- .../core/connector/handler/connector.go | 2 +- .../core/connector/service/connector.go | 21 ++++++++++- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index 607aa2c3..6019d1ad 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -18,7 +18,9 @@ package service import ( "context" - "encoding/hex" + "encoding/base64" + "net/url" + "strings" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -62,14 +64,18 @@ type GetAesKeyFromEncryptedKeyResp struct { } func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*GetAesKeyFromEncryptedKeyResp, error) { - log.Infof("start GetAesKeyFromEncryptedKey") _, privateKey, err := GetRSAKey(log) if err != nil { log.Errorf("getAesKeyFromEncryptedKey getRSAKey error msg:%s", err) return nil, err } - log.Infof("privateKey:%s", privateKey) - byteKey, err := hex.DecodeString(encryptedKey) + decodedKey, err := url.QueryUnescape(encryptedKey) + if err != nil { + return nil, err + } + decodedKey = strings.ReplaceAll(decodedKey, " ", "+") + log.Infof("decodedKey:%s", decodedKey) + byteKey, err := base64.StdEncoding.DecodeString(decodedKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) return nil, err diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 4715f4f0..7ccafc39 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -41,7 +41,7 @@ func CreateCodeHost(c *gin.Context) { func ListCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.List(c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) + ctx.Resp, ctx.Err = service.List(c.Query("encryptedKey"), c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } func DeleteCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 97e65966..edadd1c7 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -28,11 +28,14 @@ import ( "go.uber.org/zap" "golang.org/x/oauth2" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/internal/oauth" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/mongodb" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/crypto" ) const callback = "/api/directory/codehosts/callback" @@ -56,12 +59,40 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co return mongodb.NewCodehostColl().AddCodeHost(codehost) } -func List(address, owner, source string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { - return mongodb.NewCodehostColl().List(&mongodb.ListArgs{ +func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost) ([]*models.CodeHost, error) { + aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) + if err != nil { + return nil, err + } + var result []*models.CodeHost + for _, codeHost := range codeHosts { + if len(codeHost.Password) > 0 { + codeHost.Password, err = crypto.AesEncryptByKey(codeHost.Password, aesKey.PlainText) + if err != nil { + return nil, err + } + } + if len(codeHost.ClientSecret) > 0 { + codeHost.ClientSecret, err = crypto.AesEncryptByKey(codeHost.ClientSecret, aesKey.PlainText) + if err != nil { + return nil, err + } + } + result = append(result, codeHost) + } + return result, nil +} + +func List(encryptedKey, address, owner, source string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { + codeHosts, err := mongodb.NewCodehostColl().List(&mongodb.ListArgs{ Address: address, Owner: owner, Source: source, }) + if err != nil { + return nil, err + } + return encypteCodeHost(encryptedKey, codeHosts) } func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { diff --git a/pkg/microservice/systemconfig/core/connector/handler/connector.go b/pkg/microservice/systemconfig/core/connector/handler/connector.go index 288e95c4..1c60cc87 100644 --- a/pkg/microservice/systemconfig/core/connector/handler/connector.go +++ b/pkg/microservice/systemconfig/core/connector/handler/connector.go @@ -40,7 +40,7 @@ func ListConnectors(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListConnectors(ctx.Logger) + ctx.Resp, ctx.Err = service.ListConnectors(c.Query("encryptedKey"), ctx.Logger) } func DeleteConnector(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/connector/service/connector.go b/pkg/microservice/systemconfig/core/connector/service/connector.go index a60d39e0..d099ef7f 100644 --- a/pkg/microservice/systemconfig/core/connector/service/connector.go +++ b/pkg/microservice/systemconfig/core/connector/service/connector.go @@ -21,11 +21,18 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/repository/orm" + "github.com/koderover/zadig/pkg/shared/client/aslan" + "github.com/koderover/zadig/pkg/tool/crypto" ) -func ListConnectors(logger *zap.SugaredLogger) ([]*Connector, error) { +func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connector, error) { + aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) + if err != nil { + return nil, err + } cs, err := orm.NewConnectorColl().List() if err != nil { logger.Errorf("Failed to list connectors, err: %s", err) @@ -40,6 +47,18 @@ func ListConnectors(logger *zap.SugaredLogger) ([]*Connector, error) { logger.Warnf("Failed to unmarshal config, err: %s", err) continue } + if pw, ok := cf["bindPW"]; ok { + cf["bindPW"], err = crypto.AesEncryptByKey(pw.(string), aesKey.PlainText) + if err != nil { + return nil, err + } + } + if clientSecret, ok := cf["clientSecret"]; ok { + cf["clientSecret"], err = crypto.AesEncryptByKey(clientSecret.(string), aesKey.PlainText) + if err != nil { + return nil, err + } + } res = append(res, &Connector{ ConnectorBase: ConnectorBase{ Type: ConnectorType(c.Type), -- Gitee From 38b83663debb8cb0a007102f67350d42fecb1a88 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 12 Apr 2022 22:07:45 +0800 Subject: [PATCH 2148/2647] optmize code Signed-off-by: allenshen --- .../repository/models/task/release_image.go | 2 +- .../core/common/repository/mongodb/service.go | 2 +- .../environment/service/environment_update.go | 7 +-- .../aslan/core/service/handler/service.go | 2 +- .../aslan/core/service/service/helm.go | 1 + .../aslan/core/service/service/service.go | 27 +++++++-- .../service/workflow/workflow_task.go | 57 ++++++++++++++++++- .../predator/core/service/predator.go | 1 + .../core/service/taskplugin/deploy_helm.go | 3 - .../core/service/taskplugin/release_image.go | 5 +- .../core/service/types/task/release_image.go | 2 +- 11 files changed, 88 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go index a9776223..3dd921ad 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/release_image.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/release_image.go @@ -47,13 +47,13 @@ type ReleaseImage struct { // New Version field ProductName string `bson:"product_name" json:"product_name"` - ReleaseName string `bson:"release_name" json:"release_name"` SourceImage string `bson:"source_image" json:"source_image"` DistributeInfo []*DistributeInfo `bson:"distribute_info" json:"distribute_info"` } type DistributeInfo struct { Image string `bson:"image" json:"image"` + ReleaseName string `bson:"release_name" json:"release_name"` DistributeStartTime int64 `bson:"distribute_start_time" json:"distribute_start_time"` DistributeEndTime int64 `bson:"distribute_end_time" json:"distribute_end_time"` DistributeStatus string `bson:"distribute_status" json:"distribute_status"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 49d60e8b..4774bfd7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -285,7 +285,7 @@ func (c *ServiceColl) UpdateServiceHealthCheckStatus(args *models.Service) error func (c *ServiceColl) UpdateServiceReleaseNamingRule(args *models.Service) error { query := bson.M{"product_name": args.ProductName, "service_name": args.ServiceName, "revision": args.Revision} changeMap := bson.M{ - "release_naming_rule": args.ReleaseNaming, + "release_naming": args.ReleaseNaming, } change := bson.M{"$set": changeMap} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 79f245c6..11415e04 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -76,12 +76,11 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string return nil } - log.Infof("###### reInstall service: %s in env: %s", serviceName, productInfo.EnvName) renderInfo, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Name: productSvc.Render.Name, - Revision: productSvc.Render.Revision}) + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision}) if err != nil { - return fmt.Errorf("failed to find renderset, %s:%d", productSvc.Render.Name, productSvc.Render.Revision) + return fmt.Errorf("failed to find renderset, %s:%d", productInfo.Render.Name, productInfo.Render.Revision) } var renderChart *templatemodels.RenderChart diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 71d03240..264799ea 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -160,7 +160,7 @@ func HelmReleaseNaming(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") return } - err := svcservice.UpdateReleaseNamingRule(projectName, args) + err := svcservice.UpdateReleaseNamingRule(projectName, args, ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 89249ad9..e1d06cde 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -183,6 +183,7 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * serviceModules = append(serviceModules, serviceModule) } helmServiceModule.Service = serviceTemplate + serviceTemplate.ReleaseNaming = serviceTemplate.GetReleaseNaming() helmServiceModule.ServiceModules = serviceModules return helmServiceModule, err } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 03bb50cf..f2d9546a 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -42,6 +42,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -936,7 +937,7 @@ func YamlValidator(args *YamlValidatorReq) []string { return errorDetails } -func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule) error { +func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule, log *zap.SugaredLogger) error { serviceTemplate, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: args.ServiceName, Revision: 0, @@ -954,20 +955,36 @@ func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule) error return nil } + serviceTemplate.ReleaseNaming = args.NamingRule rev, err := getNextServiceRevision(projectName, args.ServiceName) if err != nil { return fmt.Errorf("failed to get service next revision, service %s, err: %s", args.ServiceName, err) } - serviceTemplate.Revision = rev - serviceTemplate.ReleaseNaming = args.NamingRule + basePath := config.LocalServicePath(serviceTemplate.ProductName, serviceTemplate.ServiceName) + if err = commonservice.PreLoadServiceManifests(basePath, serviceTemplate); err != nil { + return fmt.Errorf("failed to load chart info for service %s, err: %s", serviceTemplate.ServiceName, err) + } + + fsTree := os.DirFS(config.LocalServicePath(projectName, serviceTemplate.ServiceName)) + s3Base := config.ObjectStorageServicePath(projectName, serviceTemplate.ServiceName) + err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{fmt.Sprintf("%s-%d", serviceTemplate.ServiceName, rev)}, s3Base, log) + if err != nil { + return fmt.Errorf("failed to upload chart info for service %s, err: %s", serviceTemplate.ServiceName, err) + } + + serviceTemplate.Revision = rev err = commonrepo.NewServiceColl().Create(serviceTemplate) if err != nil { - return err + return fmt.Errorf("failed to update relase naming for service: %s, err: %s", args.ServiceName, err) } // reinstall all services - return service.UpdateSvcInAllEnvs(projectName, args.ServiceName, serviceTemplate) + err = service.UpdateSvcInAllEnvs(projectName, args.ServiceName, serviceTemplate) + if err != nil { + return fmt.Errorf("failed to re install service: %s, err: %s", args.ServiceName) + } + return nil } func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, visibility string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 746ac4a5..36444cda 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1377,7 +1377,6 @@ func artifactToSubTasks(name, image string) (map[string]interface{}, error) { artifactTask := taskmodels.Artifact{TaskType: config.TaskArtifact, Enabled: true} artifactTask.Name = name artifactTask.Image = image - return artifactTask.ToSubTask() } @@ -1405,12 +1404,33 @@ func formatDistributeSubtasks(serviceModule *commonmodels.ServiceModuleTarget, r if err != nil { return nil, err } + releaseName := "" + if repoInfo.DeployEnabled { + svcMap := envInfo.GetServiceMap() + pSvc, ok := svcMap[serviceModule.ServiceName] + if !ok { + return nil, fmt.Errorf("can't find service: %s:%s in product: %s", productName, repoInfo.DeployEnv, serviceModule.ServiceName) + } + if pSvc.Type == setting.HelmDeployType { + templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceModule.ServiceName, + Revision: pSvc.Revision, + Type: setting.HelmDeployType, + ProductName: productName, + }) + if err != nil { + return nil, err + } + releaseName = util.GeneReleaseName(templateSvc.GetReleaseNaming(), productName, envInfo.Namespace, repoInfo.DeployEnv, serviceModule.ServiceName) + } + } distributeInfo = append(distributeInfo, &taskmodels.DistributeInfo{ DeployEnabled: repoInfo.DeployEnabled, DeployEnv: repoInfo.DeployEnv, DeployServiceType: productInfo.ProductFeature.DeployType, DeployClusterID: envInfo.ClusterID, DeployNamespace: envInfo.Namespace, + ReleaseName: releaseName, RepoID: repoInfo.RepoID, }) } @@ -2211,6 +2231,34 @@ func extractHost(privateKeys []*commonmodels.PrivateKey, ips, names sets.String, return ips, names } +func getServiceNaming(projectName, envName, serviceName string) (string, error) { + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: projectName, + EnvName: envName, + }) + if err != nil { + return "", err + } + svcMap := productInfo.GetServiceMap() + pSvc, ok := svcMap[serviceName] + if !ok { + return "", fmt.Errorf("can't find service: %s:%s in product: %s", projectName, envName, serviceName) + } + if pSvc.Type != setting.HelmDeployType { + return "", nil + } + templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Revision: pSvc.Revision, + Type: setting.HelmDeployType, + ProductName: projectName, + }) + if err != nil { + return "", nil + } + return util.GeneReleaseName(templateSvc.GetReleaseNaming(), projectName, productInfo.Namespace, envName, serviceName), nil +} + func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) error { var ( buildEnvs []*commonmodels.KeyVal @@ -2700,6 +2748,13 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err DeployClusterID: distribute.DeployClusterID, RepoID: distribute.RepoID, } + if distribute.DeployEnabled { + releaseNaming, err := getServiceNaming(taskOpt.Task.ProductName, distribute.DeployEnv, taskOpt.ServiceName) + if err != nil { + return fmt.Errorf("failed to find release naming for service: %s, err: %s", taskOpt.ServiceName, err) + } + d.ReleaseName = releaseNaming + } if v, ok := taskOpt.Task.ConfigPayload.RepoConfigs[distribute.RepoID]; ok { d.Image = util.ReplaceRepo(taskOpt.Task.TaskArgs.Deploy.Image, v.RegAddr, v.Namespace) } diff --git a/pkg/microservice/predator/core/service/predator.go b/pkg/microservice/predator/core/service/predator.go index 2659a4b4..43252160 100644 --- a/pkg/microservice/predator/core/service/predator.go +++ b/pkg/microservice/predator/core/service/predator.go @@ -38,6 +38,7 @@ type RepoImage struct { // DistributeInfo will be convert into yaml, adding yaml type DistributeInfo struct { Image string `yaml:"image"` + ReleaseName string `yaml:"release_name"` DistributeStartTime int64 `yaml:"distribute_start_time"` DistributeEndTime int64 `yaml:"distribute_end_time"` DistributeStatus string `yaml:"distribute_status"` diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go index fcfe624f..0f8b0abf 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -23,8 +23,6 @@ import ( "strings" "time" - "github.com/koderover/zadig/pkg/tool/log" - helmclient "github.com/mittwald/go-helm-client" "github.com/pkg/errors" "go.uber.org/zap" @@ -316,7 +314,6 @@ func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, //releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) releaseName := p.Task.ReleaseName - log.Infof("####### the release name is %s", releaseName) ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 764cc0dd..e821b8b2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -23,8 +23,6 @@ import ( "strconv" "time" - "github.com/koderover/zadig/pkg/tool/log" - configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/taskplugin/s3" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" @@ -659,8 +657,7 @@ DistributeLoop: } //releaseName := util.GeneHelmReleaseName(distribute.DeployNamespace, distribute.DeployServiceName) - releaseName := p.Task.ReleaseName - log.Info("########## the release Name is %s", releaseName) + releaseName := distribute.ReleaseName ensureUpgrade := func() error { hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) diff --git a/pkg/microservice/warpdrive/core/service/types/task/release_image.go b/pkg/microservice/warpdrive/core/service/types/task/release_image.go index ff86ed40..2ae182d3 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/release_image.go +++ b/pkg/microservice/warpdrive/core/service/types/task/release_image.go @@ -46,7 +46,6 @@ type ReleaseImage struct { // New Version field ProductName string `bson:"product_name" json:"product_name"` - ReleaseName string `bson:"release_name" json:"release_name"` SourceImage string `bson:"source_image" json:"source_image"` DistributeInfo []*DistributeInfo `bson:"distribute_info" json:"distribute_info"` } @@ -54,6 +53,7 @@ type ReleaseImage struct { // DistributeInfo will be convert into yaml, adding yaml type DistributeInfo struct { Image string `json:"image" yaml:"image"` + ReleaseName string `json:"release_name" yaml:"release_name"` DistributeStartTime int64 `json:"distribute_start_time" yaml:"distribute_start_time"` DistributeEndTime int64 `json:"distribute_end_time" yaml:"distribute_end_time"` DistributeStatus string `json:"distribute_status" yaml:"distribute_status"` -- Gitee From 6f0957f39f495228f456925543a452404612efd3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 13 Apr 2022 10:31:47 +0800 Subject: [PATCH 2149/2647] remvoe useless code Signed-off-by: allenshen --- .../core/common/repository/mongodb/service.go | 10 ------- .../core/environment/service/environment.go | 30 ++++++++----------- .../aslan/core/environment/service/image.go | 15 ++++------ .../core/service/taskplugin/deploy_helm.go | 1 - .../core/service/taskplugin/release_image.go | 1 - 5 files changed, 18 insertions(+), 39 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 4774bfd7..b802a7bb 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -282,16 +282,6 @@ func (c *ServiceColl) UpdateServiceHealthCheckStatus(args *models.Service) error return err } -func (c *ServiceColl) UpdateServiceReleaseNamingRule(args *models.Service) error { - query := bson.M{"product_name": args.ProductName, "service_name": args.ServiceName, "revision": args.Revision} - changeMap := bson.M{ - "release_naming": args.ReleaseNaming, - } - change := bson.M{"$set": changeMap} - _, err := c.UpdateOne(context.TODO(), query, change) - return err -} - func (c *ServiceColl) Update(args *models.Service) error { // avoid panic issue if args == nil { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index efb194cf..615d3699 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -193,15 +193,12 @@ type RawYamlResp struct { } type ReleaseInstallParam struct { - ProductName string - Namespace string - EnvName string - ServiceName string - ReleaseName string - DefaultValues string - MergedValues string - RenderChart *templatemodels.RenderChart - serviceObj *commonmodels.Service + ProductName string + Namespace string + ReleaseName string + MergedValues string + RenderChart *templatemodels.RenderChart + serviceObj *commonmodels.Service } type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error @@ -2879,15 +2876,12 @@ func buildInstallParam(namespace string, envName string, renderChart *templatemo return nil, fmt.Errorf("failed to merge override yaml %s and values %s, err: %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues, err) } ret := &ReleaseInstallParam{ - ProductName: serviceObj.ProductName, - Namespace: namespace, - EnvName: envName, - ServiceName: serviceObj.ServiceName, - ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, envName, serviceObj.ServiceName), - DefaultValues: defaultValues, - MergedValues: mergedValues, - RenderChart: renderChart, - serviceObj: serviceObj, + ProductName: serviceObj.ProductName, + Namespace: namespace, + ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, envName, serviceObj.ServiceName), + MergedValues: mergedValues, + RenderChart: renderChart, + serviceObj: serviceObj, } return ret, nil } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 05a0cf8f..ec86c862 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -176,15 +176,12 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri } param := &ReleaseInstallParam{ - ProductName: serviceObj.ProductName, - Namespace: namespace, - EnvName: product.EnvName, - ServiceName: serviceObj.ServiceName, - ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, product.EnvName, serviceObj.ServiceName), - DefaultValues: renderSet.DefaultValues, - MergedValues: replacedMergedValuesYaml, - RenderChart: targetChart, - serviceObj: serviceObj, + ProductName: serviceObj.ProductName, + Namespace: namespace, + ReleaseName: util.GeneReleaseName(serviceObj.GetReleaseNaming(), serviceObj.ProductName, namespace, product.EnvName, serviceObj.ServiceName), + MergedValues: replacedMergedValuesYaml, + RenderChart: targetChart, + serviceObj: serviceObj, } // when replace image, should not wait diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go index 0f8b0abf..5bcf3331 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -312,7 +312,6 @@ func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, return } - //releaseName := util.GeneHelmReleaseName(p.Task.Namespace, p.Task.ServiceName) releaseName := p.Task.ReleaseName ensureUpgrade := func() error { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index e821b8b2..2e545f93 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -656,7 +656,6 @@ DistributeLoop: continue DistributeLoop } - //releaseName := util.GeneHelmReleaseName(distribute.DeployNamespace, distribute.DeployServiceName) releaseName := distribute.ReleaseName ensureUpgrade := func() error { -- Gitee From 378e45f8d5e2843d133c59f8337ff65c56d03d4b Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Apr 2022 11:27:47 +0800 Subject: [PATCH 2150/2647] delete service Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index b54c5273..55459e76 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -163,6 +163,10 @@ rules: endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" +# - method: PUT +# endpoint: "/api/aslan/environment/environments/?*/services" +# resourceType: "Environment" +# idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services" matchAttributes: - key: "production" value: "false" -- Gitee From dac252cf943ae59d28da5dd7e2864ac68e0a5a3b Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 13 Apr 2022 12:05:33 +0800 Subject: [PATCH 2151/2647] resolve comments Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 8 ++++---- .../aslan/core/environment/service/environment_update.go | 2 +- .../aslan/core/project/handler/service_policy.yaml | 2 ++ pkg/microservice/aslan/core/service/handler/service.go | 7 ++----- pkg/microservice/aslan/core/service/service/service.go | 3 +-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 615d3699..410795e1 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2870,7 +2870,7 @@ func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) ( return resp, nil } -func buildInstallParam(namespace string, envName string, renderChart *templatemodels.RenderChart, defaultValues string, serviceObj *commonmodels.Service) (*ReleaseInstallParam, error) { +func buildInstallParam(namespace, envName, defaultValues string, renderChart *templatemodels.RenderChart, serviceObj *commonmodels.Service) (*ReleaseInstallParam, error) { mergedValues, err := helmtool.MergeOverrideValues(renderChart.ValuesYaml, defaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) if err != nil { return nil, fmt.Errorf("failed to merge override yaml %s and values %s, err: %s", renderChart.GetOverrideYaml(), renderChart.OverrideValues, err) @@ -2966,7 +2966,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel } handler := func(serviceObj *commonmodels.Service, isRetry bool, logger *zap.SugaredLogger) error { - param, err := buildInstallParam(args.Namespace, renderset.EnvName, chartInfoMap[serviceObj.ServiceName], renderset.DefaultValues, serviceObj) + param, err := buildInstallParam(args.Namespace, renderset.EnvName, renderset.DefaultValues, chartInfoMap[serviceObj.ServiceName], serviceObj) if err != nil { return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, args.Namespace, err) } @@ -3159,7 +3159,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ } handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { - param, err := buildInstallParam(productResp.Namespace, renderSet.EnvName, renderChartMap[serviceObj.ServiceName], renderSet.DefaultValues, serviceObj) + param, err := buildInstallParam(productResp.Namespace, renderSet.EnvName, renderSet.DefaultValues, renderChartMap[serviceObj.ServiceName], serviceObj) if err != nil { return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, err) } @@ -3442,7 +3442,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { - param, err := buildInstallParam(productResp.Namespace, renderset.EnvName, renderChartMap[serviceObj.ServiceName], renderset.DefaultValues, serviceObj) + param, err := buildInstallParam(productResp.Namespace, renderset.EnvName, renderset.DefaultValues, renderChartMap[serviceObj.ServiceName], serviceObj) if err != nil { return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 11415e04..92ce6828 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -103,7 +103,7 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string return fmt.Errorf("failed to uninstall release for service: %s, err: %s", productSvc.ServiceName, err) } - param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderChart, renderInfo.DefaultValues, templateSvc) + param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) if err != nil { return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", templateSvc.ServiceName, productInfo.Namespace, err) } diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml index 3f04a647..853149a3 100644 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -42,6 +42,8 @@ rules: endpoint: "/api/aslan/project/products/?*" - method: PUT endpoint: "/api/aslan/project/products/?*/searching-rules" + - method: PUT + endpoint: "/api/aslan/service/helm/services/releaseNaming" - action: create_service alias: "新建" description: "" diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 264799ea..3cb99fb3 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -153,6 +153,7 @@ func HelmReleaseNaming(c *gin.Context) { projectName := c.Query("projectName") if projectName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be nil") + return } args := new(svcservice.ReleaseNamingRule) @@ -160,11 +161,7 @@ func HelmReleaseNaming(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") return } - err := svcservice.UpdateReleaseNamingRule(projectName, args, ctx.Logger) - if err != nil { - ctx.Err = err - return - } + ctx.Err = svcservice.UpdateReleaseNamingRule(projectName, args, ctx.Logger) } func DeleteServiceTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index f2d9546a..b342005f 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -42,7 +42,6 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -968,7 +967,7 @@ func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule, log *z fsTree := os.DirFS(config.LocalServicePath(projectName, serviceTemplate.ServiceName)) s3Base := config.ObjectStorageServicePath(projectName, serviceTemplate.ServiceName) - err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{fmt.Sprintf("%s-%d", serviceTemplate.ServiceName, rev)}, s3Base, log) + err = fs.ArchiveAndUploadFilesToS3(fsTree, []string{fmt.Sprintf("%s-%d", serviceTemplate.ServiceName, rev)}, s3Base, log) if err != nil { return fmt.Errorf("failed to upload chart info for service %s, err: %s", serviceTemplate.ServiceName, err) } -- Gitee From e4b9811e8048287cd6fde2d0df1e579384866042 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 13 Apr 2022 12:06:42 +0800 Subject: [PATCH 2152/2647] add jenkins and jira encrypte Signed-off-by: panxunying --- .../aslan/core/system/handler/jenkins.go | 4 ++-- .../aslan/core/system/service/jenkins.go | 21 +++++++++++++++++-- .../systemconfig/core/jira/handler/jira.go | 2 +- .../systemconfig/core/jira/service/jira.go | 19 +++++++++++++++-- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/system/handler/jenkins.go b/pkg/microservice/aslan/core/system/handler/jenkins.go index e1a8b99c..5424af5a 100644 --- a/pkg/microservice/aslan/core/system/handler/jenkins.go +++ b/pkg/microservice/aslan/core/system/handler/jenkins.go @@ -35,7 +35,7 @@ func CheckJenkinsIntegration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - resp, err := service.ListJenkinsIntegration(ctx.Logger) + resp, err := service.ListJenkinsIntegration("", ctx.Logger) if err != nil || len(resp) == 0 { ctx.Resp = &CheckJenkinsIntegrationResp{Exists: false} return @@ -64,7 +64,7 @@ func ListJenkinsIntegration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListJenkinsIntegration(ctx.Logger) + ctx.Resp, ctx.Err = service.ListJenkinsIntegration(c.Query("encryptedKey"), ctx.Logger) } func UpdateJenkinsIntegration(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/jenkins.go b/pkg/microservice/aslan/core/system/service/jenkins.go index 36984329..ad60e63a 100644 --- a/pkg/microservice/aslan/core/system/service/jenkins.go +++ b/pkg/microservice/aslan/core/system/service/jenkins.go @@ -25,6 +25,9 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + + "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types" ) @@ -49,13 +52,27 @@ func CreateJenkinsIntegration(args *commonmodels.JenkinsIntegration, log *zap.Su return nil } -func ListJenkinsIntegration(log *zap.SugaredLogger) ([]*commonmodels.JenkinsIntegration, error) { +func ListJenkinsIntegration(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels.JenkinsIntegration, error) { jenkinsIntegrations, err := commonrepo.NewJenkinsIntegrationColl().List() if err != nil { log.Errorf("ListJenkinsIntegration err:%v", err) return []*commonmodels.JenkinsIntegration{}, e.ErrListJenkinsIntegration.AddErr(err) } + if len(encryptedKey) == 0 { + return jenkinsIntegrations, nil + } + + aesKey, err := service.GetAesKeyFromEncryptedKey(encryptedKey, log) + if err != nil { + return nil, err + } + for _, integration := range jenkinsIntegrations { + integration.Password, err = crypto.AesEncryptByKey(integration.Password, aesKey.PlainText) + if err != nil { + return nil, err + } + } return jenkinsIntegrations, nil } @@ -87,7 +104,7 @@ func TestJenkinsConnection(args *JenkinsArgs, log *zap.SugaredLogger) error { func getJenkinsClient(log *zap.SugaredLogger) (*gojenkins.Jenkins, context.Context, error) { ctx := context.Background() - jenkinsIntegrations, err := ListJenkinsIntegration(log) + jenkinsIntegrations, err := ListJenkinsIntegration("", log) if err != nil { return nil, ctx, err } diff --git a/pkg/microservice/systemconfig/core/jira/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go index 2f9cf62a..5776bb82 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -33,7 +33,7 @@ func DeleteJira(c *gin.Context) { func GetJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GeJira(ctx.Logger) + ctx.Resp, ctx.Err = service.GeJira(c.Query("encryptedKey"), ctx.Logger) } func CreateJira(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 64851bd5..2db83768 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -21,12 +21,27 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/mongodb" + "github.com/koderover/zadig/pkg/shared/client/aslan" + "github.com/koderover/zadig/pkg/tool/crypto" ) -func GeJira(_ *zap.SugaredLogger) (*models.Jira, error) { - return mongodb.NewJiraColl().GetJira() +func GeJira(encryptedKey string, _ *zap.SugaredLogger) (*models.Jira, error) { + jira, err := mongodb.NewJiraColl().GetJira() + if err != nil { + return nil, err + } + aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) + if err != nil { + return nil, err + } + jira.AccessToken, err = crypto.AesEncryptByKey(jira.AccessToken, aesKey.PlainText) + if err != nil { + return nil, err + } + return jira, nil } func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { -- Gitee From a7f5d2998d834f10cc914593dc53f161fee84015 Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Apr 2022 13:28:05 +0800 Subject: [PATCH 2153/2647] fix bug Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 16 ++++++++-------- .../aslan/core/workflow/handler/policy.yaml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 32f03660..3c0a1d48 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -97,16 +97,16 @@ rules: description: "" rules: - method: POST - endpoint: "/api/environment/environments/?*/share/enable" + endpoint: "/api/aslan/environment/environments/?*/share/enable" resourceType: "Environment" - idRegex: "/api/environment/environments/([\\w\\W]+?)/share/enable" + idRegex: "/api/aslan/environment/environments/([\\w\\W]+?)/share/enable" matchAttributes: - key: "production" value: "false" - method: DELETE - endpoint: "/api/environment/environments/?*/share/enable" + endpoint: "/api/aslan/environment/environments/?*/share/enable" resourceType: "Environment" - idRegex: "/api/environment/environments/([\\w\\W]+?)/share/enable" + idRegex: "/api/aslan/environment/environments/([\\w\\W]+?)/share/enable" matchAttributes: - key: "production" value: "false" @@ -184,10 +184,10 @@ rules: endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" -# - method: PUT -# endpoint: "/api/aslan/environment/environments/?*/services" -# resourceType: "Environment" -# idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/services" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services" matchAttributes: - key: "production" value: "false" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 16fa9eba..aae2b443 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -106,7 +106,7 @@ rules: resourceType: "Workflow" - method: POST endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" - idRegex: "/pipelines/([\\w\\W]+?)$/restart" + idRegex: "/pipelines/([\\w\\W]+?)/restart" resourceType: "Workflow" - method: DELETE endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" -- Gitee From 3d1a26c64d954812fe683d8ef7ecc1206c63739d Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 13 Apr 2022 14:42:24 +0800 Subject: [PATCH 2154/2647] add external helm privateKey registry s3 encrypte Signed-off-by: panxunying --- .../aslan/core/common/service/helm.go | 14 ++++++++++++-- .../aslan/core/common/service/registry.go | 12 ++++++++++-- .../core/system/handler/external_system.go | 2 +- .../aslan/core/system/handler/helm.go | 2 +- .../aslan/core/system/handler/private_key.go | 2 +- .../aslan/core/system/handler/registry.go | 2 +- .../aslan/core/system/handler/s3.go | 2 +- .../core/system/service/external_system.go | 19 +++++++++++++++---- .../aslan/core/system/service/private_key.go | 13 ++++++++++++- .../aslan/core/system/service/s3.go | 15 +++++++++++++-- 10 files changed, 67 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 3b39a3cc..ce1fd8b4 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -32,17 +32,27 @@ import ( fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/crypto" "github.com/koderover/zadig/pkg/tool/log" fsutil "github.com/koderover/zadig/pkg/util/fs" ) -func ListHelmRepos(log *zap.SugaredLogger) ([]*commonmodels.HelmRepo, error) { +func ListHelmRepos(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels.HelmRepo, error) { + aesKey, err := GetAesKeyFromEncryptedKey(encryptedKey, log) + if err != nil { + return nil, err + } helmRepos, err := commonrepo.NewHelmRepoColl().List() if err != nil { log.Errorf("ListHelmRepos err:%v", err) return []*commonmodels.HelmRepo{}, nil } - + for _, helmRepo := range helmRepos { + helmRepo.Password, err = crypto.AesEncryptByKey(helmRepo.Password, aesKey.PlainText) + if err != nil { + return nil, err + } + } return helmRepos, nil } diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 501c5b36..3dc165a6 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" + "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util" ) @@ -97,7 +98,7 @@ func FindDefaultRegistry(getRealCredential bool, log *zap.SugaredLogger) (reg *m return findRegisty(&mongodb.FindRegOps{IsDefault: true}, getRealCredential, log) } -func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { +func ListRegistryNamespaces(encryptedKey string, getRealCredential bool, log *zap.SugaredLogger) ([]*models.RegistryNamespace, error) { resp, err := mongodb.NewRegistryNamespaceColl().FindAll(&mongodb.FindRegOps{}) if err != nil { log.Errorf("RegistryNamespace.List error: %s", err) @@ -107,7 +108,10 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* if !getRealCredential { return resp, nil } - + aesKey, err := GetAesKeyFromEncryptedKey(encryptedKey, log) + if err != nil { + return nil, err + } for _, reg := range resp { switch reg.RegProvider { case config.RegistryTypeSWR: @@ -122,6 +126,10 @@ func ListRegistryNamespaces(getRealCredential bool, log *zap.SugaredLogger) ([]* reg.AccessKey = realAK reg.SecretKey = realSK } + reg.SecretKey, err = crypto.AesEncryptByKey(reg.SecretKey, aesKey.PlainText) + if err != nil { + return nil, err + } } return resp, nil } diff --git a/pkg/microservice/aslan/core/system/handler/external_system.go b/pkg/microservice/aslan/core/system/handler/external_system.go index 785b1e2d..ba4db808 100644 --- a/pkg/microservice/aslan/core/system/handler/external_system.go +++ b/pkg/microservice/aslan/core/system/handler/external_system.go @@ -75,7 +75,7 @@ func ListExternalSystem(c *gin.Context) { return } - systemList, length, err := service.ListExternalSystem(args.PageNum, args.PageSize, ctx.Logger) + systemList, length, err := service.ListExternalSystem(c.Query("encryptedKey"), args.PageNum, args.PageSize, ctx.Logger) if err == nil { ctx.Resp = &listExternalResp{ SystemList: systemList, diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 6aa6ac52..31772051 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -32,7 +32,7 @@ func ListHelmRepos(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = commonservice.ListHelmRepos(ctx.Logger) + ctx.Resp, ctx.Err = commonservice.ListHelmRepos(c.Query("encryptedKey"), ctx.Logger) } func CreateHelmRepo(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 4ef782bb..78e75bf3 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -36,7 +36,7 @@ func ListPrivateKeys(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListPrivateKeys(ctx.Logger) + ctx.Resp, ctx.Err = service.ListPrivateKeys(c.Query("encryptedKey"), ctx.Logger) } func GetPrivateKey(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 9c6e950a..93434294 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -81,7 +81,7 @@ func ListRegistryNamespaces(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = commonservice.ListRegistryNamespaces(false, ctx.Logger) + ctx.Resp, ctx.Err = commonservice.ListRegistryNamespaces(c.Query("encryptedKey"), false, ctx.Logger) } func CreateRegistryNamespace(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index f8e534ad..af133fec 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -36,7 +36,7 @@ func ListS3Storage(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListS3Storage(ctx.Logger) + ctx.Resp, ctx.Err = service.ListS3Storage(c.Query("encryptedKey"), ctx.Logger) } func CreateS3Storage(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/external_system.go b/pkg/microservice/aslan/core/system/service/external_system.go index 76c2f97c..c95bff14 100644 --- a/pkg/microservice/aslan/core/system/service/external_system.go +++ b/pkg/microservice/aslan/core/system/service/external_system.go @@ -21,6 +21,8 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" ) @@ -37,18 +39,27 @@ func CreateExternalSystem(args *ExternalSystemDetail, log *zap.SugaredLogger) er return nil } -func ListExternalSystem(pageNum, pageSize int64, log *zap.SugaredLogger) ([]*ExternalSystemDetail, int64, error) { +func ListExternalSystem(encryptedKey string, pageNum, pageSize int64, log *zap.SugaredLogger) ([]*ExternalSystemDetail, int64, error) { resp, length, err := commonrepo.NewExternalSystemColl().List(pageNum, pageSize) if err != nil { log.Errorf("Failed to list external system from db, the error is: %s", err) return nil, 0, err } + aesKey, err := service.GetAesKeyFromEncryptedKey(encryptedKey, log) + if err != nil { + return nil, 0, err + } systemList := make([]*ExternalSystemDetail, 0) for _, item := range resp { + apiToken, err := crypto.AesEncryptByKey(item.APIToken, aesKey.PlainText) + if err != nil { + return nil, 0, err + } systemList = append(systemList, &ExternalSystemDetail{ - ID: item.ID.Hex(), - Name: item.Name, - Server: item.Server, + ID: item.ID.Hex(), + Name: item.Name, + Server: item.Server, + APIToken: apiToken, }) } return systemList, length, nil diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 847992c8..4efe7080 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -25,15 +25,26 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/pm" + "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" ) -func ListPrivateKeys(log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { +func ListPrivateKeys(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { resp, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{}) if err != nil { log.Errorf("PrivateKey.List error: %v", err) return resp, e.ErrListPrivateKeys } + aesKey, err := commonservice.GetAesKeyFromEncryptedKey(encryptedKey, log) + if err != nil { + return nil, err + } + for _, key := range resp { + key.PrivateKey, err = crypto.AesEncryptByKey(key.PrivateKey, aesKey.PlainText) + if err != nil { + return nil, err + } + } return resp, nil } diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index 4cd5ae2f..fb8d910c 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -27,8 +27,10 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/crypto" "github.com/koderover/zadig/pkg/tool/errors" s3tool "github.com/koderover/zadig/pkg/tool/s3" ) @@ -73,12 +75,21 @@ func CreateS3Storage(updateBy string, storage *commonmodels.S3Storage, logger *z return commonrepo.NewS3StorageColl().Create(storage) } -func ListS3Storage(logger *zap.SugaredLogger) ([]*commonmodels.S3Storage, error) { +func ListS3Storage(encryptedKey string, logger *zap.SugaredLogger) ([]*commonmodels.S3Storage, error) { stores, err := commonrepo.NewS3StorageColl().FindAll() if err == nil && len(stores) == 0 { stores = make([]*commonmodels.S3Storage, 0) } - + aesKey, err := service.GetAesKeyFromEncryptedKey(encryptedKey, logger) + if err != nil { + return nil, err + } + for _, store := range stores { + store.Sk, err = crypto.AesEncryptByKey(store.Sk, aesKey.PlainText) + if err != nil { + return nil, err + } + } return stores, err } -- Gitee From 4edc30edaea2422993a6aac580004f106451fc00 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 13 Apr 2022 17:23:30 +0800 Subject: [PATCH 2155/2647] fix image data nil bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/helm.go | 2 +- pkg/microservice/aslan/core/environment/service/revision.go | 5 +++-- pkg/microservice/aslan/core/service/service/service.go | 6 +----- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index f9eb2b52..b26b3b58 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -376,7 +376,7 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo } svcImage.Images = append(svcImage.Images, &ImageData{ - container.ImageName, + util.GetImageNameFromContainerInfo(container.ImageName, container.Name), commonservice.ExtractImageTag(imageUrl), }) } diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index a4e18b50..56138452 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -28,6 +28,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/util" ) func ListProductsRevision(productName, envName string, log *zap.SugaredLogger) (prodRevs []*ProductRevision, err error) { @@ -247,7 +248,7 @@ func compareServicesRev(serviceTmplNames []string, services []*commonmodels.Prod serviceRev.Containers = append(serviceRev.Containers, &commonmodels.Container{ Image: container.Image, Name: container.Name, - ImageName: container.ImageName, + ImageName: util.GetImageNameFromContainerInfo(container.ImageName, container.Name), }) } } @@ -306,7 +307,7 @@ func compareServicesRev(serviceTmplNames []string, services []*commonmodels.Prod c := &commonmodels.Container{ Image: container.Image, Name: container.Name, - ImageName: container.ImageName, + ImageName: util.GetImageNameFromContainerInfo(container.ImageName, container.Name), } // reuse existed container image diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 4cef72d5..28013b5b 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -183,11 +183,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv for _, container := range args.Containers { serviceModule := new(ServiceModule) serviceModule.Container = container - serviceModule.ImageName = container.ImageName - //Compatible with the problem that the name of the old data imageName is empty - if serviceModule.ImageName == "" { - serviceModule.ImageName = container.Name - } + serviceModule.ImageName = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) buildObj, _ := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ProductName: args.ProductName, ServiceName: args.ServiceName, Targets: []string{container.Name}}) if buildObj != nil { serviceModule.BuildName = buildObj.Name -- Gitee From 5ca10dc6ab2a3ae0a7dea944acca451c2fe62789 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 13 Apr 2022 18:28:30 +0800 Subject: [PATCH 2156/2647] update get service name from release name logic Signed-off-by: allenshen --- .../core/common/repository/mongodb/service.go | 44 +++++++++++++++++++ .../aslan/core/common/service/environment.go | 44 +++++++++++++++++-- .../service/common_env_cfg_informer.go | 5 --- .../aslan/core/environment/service/helm.go | 12 +++-- .../aslan/core/environment/service/image.go | 2 +- pkg/util/helm.go | 5 --- 6 files changed, 90 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index b802a7bb..2110fb4f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -61,6 +61,16 @@ type ServiceListOption struct { NotInServices []*templatemodels.ServiceInfo } +type ServiceRevision struct { + ServiceName string + Revision int64 +} + +type SvcRevisionListOption struct { + ProductName string + ServiceRevisions []*ServiceRevision +} + type ServiceAggregateResult struct { ServiceID primitive.ObjectID `bson:"service_id"` } @@ -437,6 +447,40 @@ func (c *ServiceColl) ListAllRevisions() ([]*models.Service, error) { return resp, err } +func (c *ServiceColl) ListServicesWithSRevision(opt *SvcRevisionListOption) ([]*models.Service, error) { + productMatch := bson.M{} + productMatch["product_name"] = opt.ProductName + + var serviceMatch bson.A + for _, sr := range opt.ServiceRevisions { + serviceMatch = append(serviceMatch, bson.M{ + "service_name": sr.ServiceName, + "revision": sr.Revision, + }) + } + + pipeline := []bson.M{ + { + "$match": productMatch, + }, + { + "$match": bson.M{ + "$or": serviceMatch, + }, + }, + } + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + + res := make([]*models.Service, 0) + if err := cursor.All(context.TODO(), &res); err != nil { + return nil, err + } + return res, err +} + func (c *ServiceColl) ListMaxRevisionsByProject(serviceName, serviceType string) ([]*models.Service, error) { pipeline := []bson.M{ diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index fbb4d45c..a21930b1 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -143,7 +143,7 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL func fillServiceDisplayName(svcList []*ServiceResp, productInfo *models.Product) { if productInfo.Source == setting.SourceFromHelm { for _, svc := range svcList { - svc.ServiceDisplayName = util.ExtraServiceName(svc.ServiceName, productInfo.Namespace) + svc.ServiceDisplayName = svc.ServiceName } } } @@ -408,10 +408,45 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work return resp } +// GetReleaseNameToServiceNameMap generates mapping relationship: releaseName=>serviceName +func GetReleaseNameToServiceNameMap(prod *models.Product) (map[string]string, error) { + // filter releases, only list releases deployed by zadig + productName, envName, serviceMap := prod.ProductName, prod.EnvName, prod.GetServiceMap() + listOpt := &commonrepo.SvcRevisionListOption{ + ProductName: prod.ProductName, + ServiceRevisions: make([]*commonrepo.ServiceRevision, 0), + } + for _, productSvc := range serviceMap { + listOpt.ServiceRevisions = append(listOpt.ServiceRevisions, &commonrepo.ServiceRevision{ + ServiceName: productSvc.ServiceName, + Revision: productSvc.Revision, + }) + } + templateServices, err := commonrepo.NewServiceColl().ListServicesWithSRevision(listOpt) + if err != nil { + return nil, fmt.Errorf("failed to list template services for pruduct: %s:%s", productName, envName) + } + + // map[ReleaseName] => serviceName + releaseNameMap := make(map[string]string) + for _, svcInfo := range templateServices { + releaseNameMap[util.GeneReleaseName(svcInfo.GetReleaseNaming(), productName, prod.Namespace, envName, svcInfo.ServiceName)] = svcInfo.ServiceName + } + return releaseNameMap, nil +} + // GetHelmServiceName get service name from annotations of resources deployed by helm // resType currently only support Deployment and StatefulSet -func GetHelmServiceName(namespace, resType, resName string, kubeClient client.Client) (string, error) { +// this function needs to be optimized +func GetHelmServiceName(prod *models.Product, resType, resName string, kubeClient client.Client) (string, error) { res := &unstructured.Unstructured{} + namespace := prod.Namespace + + nameMap, err := GetReleaseNameToServiceNameMap(prod) + if err != nil { + return "", err + } + res.SetGroupVersionKind(schema.GroupVersionKind{ Group: "apps", Version: "v1", @@ -426,8 +461,9 @@ func GetHelmServiceName(namespace, resType, resName string, kubeClient client.Cl } annotation := res.GetAnnotations() if len(annotation) > 0 { - if chartRelease, ok := annotation[setting.HelmReleaseNameAnnotation]; ok { - return util.ExtraServiceName(chartRelease, namespace), nil + releaseName := annotation[setting.HelmReleaseNameAnnotation] + if serviceName, ok := nameMap[releaseName]; ok { + return serviceName, nil } } return "", fmt.Errorf("failed to get annotation from resource %s, type %s", resName, resType) diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go index 01296d32..184f921c 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go @@ -36,7 +36,6 @@ import ( "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util" ) var ClusterInformersMap sync.Map @@ -279,10 +278,6 @@ func GetProductAndFilterNs(namespace, workloadName string) (*models.Product, boo } } for _, psvc := range product.GetServiceMap() { - //TODO: helm not support restart service - if psvc.Type == setting.HelmDeployType && psvc.ServiceName == util.ExtraServiceName(workloadName, namespace) { - return product, false - } if psvc.Type == setting.K8SDeployType && psvc.ServiceName == workloadName { return product, true } diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index ca8eb8ee..63704668 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -28,7 +28,6 @@ import ( "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -91,16 +90,15 @@ func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmR } // filter releases, only list releases deployed by zadig - serviceMap := prod.GetServiceMap() - serviceSet := sets.NewString() - for serviceName := range serviceMap { - serviceSet.Insert(serviceName) + releaseNameMap, err := commonservice.GetReleaseNameToServiceNameMap(prod) + if err != nil { + return nil, err } ret := make([]*HelmReleaseResp, 0, len(releases)) for _, release := range releases { - serviceName := util.ExtraServiceName(release.Name, prod.Namespace) - if !serviceSet.Has(serviceName) { + serviceName, ok := releaseNameMap[release.Name] + if !ok { continue } ret = append(ret, &HelmReleaseResp{ diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index ec86c862..1bcc503e 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -238,7 +238,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log // update service in helm way if product.Source == setting.HelmDeployType { - serviceName, err := commonservice.GetHelmServiceName(namespace, args.Type, args.Name, kubeClient) + serviceName, err := commonservice.GetHelmServiceName(product, args.Type, args.Name, kubeClient) if err != nil { return e.ErrUpdateConainterImage.AddErr(err) } diff --git a/pkg/util/helm.go b/pkg/util/helm.go index 2c148ae8..04ef5f63 100644 --- a/pkg/util/helm.go +++ b/pkg/util/helm.go @@ -17,14 +17,9 @@ limitations under the License. package util import ( - "fmt" "strings" ) -func ExtraServiceName(releaseName, namespace string) string { - return strings.TrimPrefix(releaseName, fmt.Sprintf("%s-", namespace)) -} - func GeneReleaseName(namingRule, projectName, namespace, envName, service string) string { ret := strings.ReplaceAll(namingRule, "$Product$", projectName) ret = strings.ReplaceAll(ret, "$Namespace$", namespace) -- Gitee From b0c764a9cbc787c51f78547c930dca946f6f669d Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Apr 2022 20:32:43 +0800 Subject: [PATCH 2157/2647] remove job before run Signed-off-by: mouuii --- pkg/microservice/aslan/core/system/service/system_cache.go | 6 ++++++ pkg/microservice/cron/core/service/scheduler/scheduler.go | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/system_cache.go b/pkg/microservice/aslan/core/system/service/system_cache.go index 5f337c05..167e9e6c 100644 --- a/pkg/microservice/aslan/core/system/service/system_cache.go +++ b/pkg/microservice/aslan/core/system/service/system_cache.go @@ -99,6 +99,8 @@ func CleanImageCache(logger *zap.SugaredLogger) error { } dindClean.Status = CleanStatusCleaning dindClean.DindCleanInfos = []*commonmodels.DindCleanInfo{} + dindClean.Cron = dindCleans[0].Cron + dindClean.CronEnabled = dindCleans[0].CronEnabled if err := commonrepo.NewDindCleanColl().Upsert(dindClean); err != nil { return e.ErrUpdateDindClean.AddErr(err) } @@ -118,6 +120,8 @@ func CleanImageCache(logger *zap.SugaredLogger) error { commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ Status: CleanStatusFailed, DindCleanInfos: []*commonmodels.DindCleanInfo{}, + Cron: dindCleans[0].Cron, + CronEnabled: dindCleans[0].CronEnabled, }) return } @@ -157,6 +161,8 @@ func CleanImageCache(logger *zap.SugaredLogger) error { commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ Status: status, DindCleanInfos: dindInfos, + Cron: dindCleans[0].Cron, + CronEnabled: dindCleans[0].CronEnabled, }) }() diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 8f298be4..f192e994 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -28,6 +28,8 @@ import ( "github.com/rfyiamcool/cronlib" "go.uber.org/zap" + cronV3 "github.com/robfig/cron/v3" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/cron/config" "github.com/koderover/zadig/pkg/microservice/cron/core/service" @@ -35,7 +37,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/tool/log" - cronV3 "github.com/robfig/cron/v3" ) // CronClient ... @@ -79,6 +80,7 @@ func (c *CronV3Client) Start() { log.Infof("config changed to %v", config) switch config.CronEnabled { case true: + c.Cron.Remove(entryID) c.Cron.Run() entryID, err = c.Cron.AddFunc(config.Cron, func() { log.Infof("********add********* , %v\n", config) -- Gitee From 3aa06be4023762effa4e7adff17b13a703d87efc Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 13 Apr 2022 20:51:44 +0800 Subject: [PATCH 2158/2647] environment permission Signed-off-by: mouuii --- .../aslan/core/environment/handler/policy.yaml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 3c0a1d48..1467b94f 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -96,6 +96,13 @@ rules: alias: "配置" description: "" rules: + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/services" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/environment/environments/?*/share/enable" resourceType: "Environment" @@ -112,7 +119,6 @@ rules: value: "false" - method: PUT endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - method: PUT endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" @@ -152,6 +158,8 @@ rules: alias: "管理服务实例" description: "" rules: + - method: PUT + endpoint: "/api/aslan/environment/environments" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" resourceType: "Environment" @@ -184,13 +192,6 @@ rules: endpoint: "/api/aslan/environment/environments/?*/services/?*" resourceType: "Environment" idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/services" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services" - matchAttributes: - - key: "production" - value: "false" - method: POST endpoint: "/api/aslan/environment/image/deployment/?*" resourceType: "Environment" -- Gitee From 85b46784010bae6a852d264dd4691e163163d1bb Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Wed, 13 Apr 2022 18:35:24 +0800 Subject: [PATCH 2159/2647] keep running task when wd restarts Signed-off-by: zhangyifei --- .../core/service/taskcontroller/interface.go | 27 ++++ .../service/taskcontroller/task_controller.go | 49 +++++-- .../service/taskcontroller/task_handler.go | 40 ++++-- .../core/service/taskplugin/build.go | 132 ++++++++++++------ .../warpdrive/core/service/taskplugin/job.go | 44 +++++- pkg/microservice/warpdrive/server/server.go | 9 +- 6 files changed, 225 insertions(+), 76 deletions(-) create mode 100644 pkg/microservice/warpdrive/core/service/taskcontroller/interface.go diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/interface.go b/pkg/microservice/warpdrive/core/service/taskcontroller/interface.go new file mode 100644 index 00000000..bfa6caaf --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/interface.go @@ -0,0 +1,27 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskcontroller + +import "context" + +type ControllerI interface { + // Init initializes the controller. + Init(ctx context.Context) error + + // Stop stops process logics. + Stop(ctx context.Context) error +} diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_controller.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_controller.go index 67ad5c1a..6a04745d 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_controller.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_controller.go @@ -31,47 +31,57 @@ import ( "github.com/koderover/zadig/pkg/tool/nsqcli" ) -func InitTaskController(ctx context.Context) error { +type controller struct { + consumers []*nsq.Consumer + producers []*nsq.Producer +} + +func NewController() ControllerI { + return &controller{ + consumers: []*nsq.Consumer{}, + producers: []*nsq.Producer{}, + } +} + +func (c *controller) Init(ctx context.Context) error { go func() { if err := client.Start(ctx); err != nil { panic(err) } }() - //初始化nsq cfg := nsq.NewConfig() - // 注意 WD_POD_NAME 必须使用 Downward API 配置环境变量 cfg.UserAgent = config.WarpDrivePodName() - // FIXME: Min: FIX MAGIC NUMBER cfg.MaxAttempts = 50 cfg.LookupdPollInterval = 1 * time.Second - //nsqd 和服务起在一起 FIXME: Min: FIX MAGIC ADDR + cfg.MsgTimeout = 1 * time.Minute nsqClient := nsqcli.NewNsqClient(config.NSQLookupAddrs(), "127.0.0.1:4151") - //Process topic processor, err := nsq.NewConsumer(setting.TopicProcess, "process", cfg) if err != nil { return fmt.Errorf("init nsq processor error: %v", err) } + processor.SetLogger(log.New(os.Stdout, "nsq consumer:", 0), nsq.LogLevelError) + c.consumers = append(c.consumers, processor) - //Cancel topic - //监听不同的channel,确保取消消息到每一个wd canceller, err := nsq.NewConsumer(setting.TopicCancel, cfg.UserAgent, cfg) if err != nil { return fmt.Errorf("init nsq canceller error: %v", err) } + canceller.SetLogger(log.New(os.Stdout, "nsq consumer:", 0), nsq.LogLevelError) + c.consumers = append(c.consumers, canceller) - //Sender nsqdAddr := "127.0.0.1:4150" sender, err := nsq.NewProducer(nsqdAddr, cfg) if err != nil { return fmt.Errorf("init nsq sender error: %v", err) } + sender.SetLogger(log.New(os.Stdout, "nsq producer:", 0), nsq.LogLevelError) + c.producers = append(c.producers, sender) - // 初始化nsq topic err = nsqClient.EnsureNsqdTopics([]string{setting.TopicAck, setting.TopicItReport, setting.TopicNotification}) if err != nil { return fmt.Errorf("ensure nsq topic error: %v", err) @@ -80,22 +90,31 @@ func InitTaskController(ctx context.Context) error { execHandler := &ExecHandler{ Sender: sender, } - processor.AddHandler(execHandler) - - //Add task plugin initiators to exec Handler + // Add task plugin initiators to exec Handler. initTaskPlugins(execHandler) cancelHandler := &CancelHandler{} - canceller.AddHandler(cancelHandler) if err := processor.ConnectToNSQLookupds(config.NSQLookupAddrs()); err != nil { return fmt.Errorf("processor could not connect to %v", config.NSQLookupAddrs()) } - if err := canceller.ConnectToNSQLookupds(config.NSQLookupAddrs()); err != nil { return fmt.Errorf("canceller could not connect to %v", config.NSQLookupAddrs()) } + + return nil +} + +func (c *controller) Stop(ctx context.Context) error { + for _, consumer := range c.consumers { + consumer.Stop() + } + + for _, producer := range c.producers { + producer.Stop() + } + return nil } diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index b2c06723..4d7fc382 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -47,6 +47,12 @@ var ( xl *zap.SugaredLogger ) +// TODO: Leave the logic here until we know why it exists. +var durationBeforeNextTask = 10 * time.Second + +// Note: `durationTouchMsg` is used to emit `TOUCH` cmd and it should smaller than `durationBeforeNextTask`. +var durationTouchMsg = 5 * time.Second + // Sender: sender to send ack/notification // TaskPlugins: registered task plugin initiators to initiate specific plugin to execute task type ExecHandler struct { @@ -60,33 +66,38 @@ type CancelHandler struct{} func (h *ExecHandler) HandleMessage(message *nsq.Message) error { defer func() { // 每次处理完消息, 等待一段时间不处理新消息 - time.Sleep(time.Second * 10) + time.Sleep(durationBeforeNextTask) }() xl = log.SugaredLogger() - // 如果存在运行中的 PipelineTask, 则重新requeue pipeline task - // task处理逻辑全部放在requeue之后,防止requeue影响正在运行的task - if pipelineTask != nil { - xl.Infof("warpdrive instance have one running pipeline task %s:%d", pipelineTask.PipelineName, pipelineTask.TaskID) - message.Requeue(time.Millisecond * 100) - return nil - } - // 获取 PipelineTask 内容 if err := json.Unmarshal(message.Body, &pipelineTask); err != nil { xl.Errorf("unmarshal PipelineTask error: %v", err) return nil } - xl.Infof("receiving pipeline task %s:%d message", pipelineTask.PipelineName, pipelineTask.TaskID) + taskName := fmt.Sprintf("%s:%d", pipelineTask.PipelineName, pipelineTask.TaskID) + xl.Infof("Receiving pipeline task %s message", taskName) - // xl - global logger xl = Logger(pipelineTask) - - // 初始化 Context, CancelFunc, PipelineTask ctx, cancel = context.WithCancel(context.Background()) - go h.runPipelineTask(ctx, cancel, xl) + go func(taskName string) { + for { + if pipelineTask == nil { + xl.Infof("Pipeline task %q has completed. Exit.", taskName) + break + } + + <-time.After(durationTouchMsg) + xl.Infof("After %s, touch message %q.", durationTouchMsg.String(), taskName) + message.Touch() + } + }(taskName) + + h.runPipelineTask(ctx, cancel, xl) + + // Note: If returning `nil`, we emit `FIN` cmd to nsq indicating that the messsage has been processed succefully. return nil } @@ -166,7 +177,6 @@ func (h *ExecHandler) runPipelineTask(ctx context.Context, cancel context.Cancel // Return 之前会执行defer内容,更新pipeline end time, 发送ACK,发送notification } -// HandleMessage ... func (h *CancelHandler) HandleMessage(message *nsq.Message) error { xl = Logger(pipelineTask) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 517971f0..b7aa1b4a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -19,6 +19,7 @@ package taskplugin import ( "context" "fmt" + "math" "math/rand" "os" "strconv" @@ -27,6 +28,7 @@ import ( "go.uber.org/zap" "gopkg.in/yaml.v3" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -92,7 +94,11 @@ func (p *BuildTaskPlugin) SetStatus(status config.Status) { p.Task.TaskStatus = status } +// Note: Unit of input `p.Task.Timeout` is `minutes` and convert it to `seconds` internally. +// TODO: Using time implicitly is easy to generate bugs. We need use `time.Duration` instead. func (p *BuildTaskPlugin) TaskTimeout() int { + p.Log.Infof("IsRestart: %t. TaskTimeout: %d", p.Task.IsRestart, p.Task.Timeout) + if p.Task.Timeout == 0 { p.Task.Timeout = BuildTaskV2Timeout } else { @@ -103,6 +109,13 @@ func (p *BuildTaskPlugin) TaskTimeout() int { return p.Task.Timeout } +// Note: This is a temporary function to be compatible with the `TaskTimeout()` method's process of time. +// TODO: Remove this function after `TaskTimeout` uses `time.Duration`. +func (p *BuildTaskPlugin) tmpSetTaskTimeout(durationInSeconds int) { + p.Task.Timeout = int(math.Ceil(float64(durationInSeconds) / 60.0)) + p.Task.IsRestart = false +} + func (p *BuildTaskPlugin) SetBuildStatusCompleted(status config.Status) { p.Task.BuildStatus.Status = status p.Task.BuildStatus.EndTime = time.Now().Unix() @@ -295,34 +308,41 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe PipelineType: string(pipelineTask.Type), } - if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - p.Log.Error(err) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = err.Error() - p.SetBuildStatusCompleted(config.StatusFailed) - return - } - - if err := createJobConfigMap( - p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient); err != nil { - msg := fmt.Sprintf("createJobConfigMap error: %v", err) + jobObj, jobExist, err := checkJobExists(ctx, p.KubeNamespace, jobLabel, p.kubeClient) + if err != nil { + msg := fmt.Sprintf("failed to check whether Job exist for %s:%d: %s", pipelineTask.PipelineName, pipelineTask.TaskID, err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed p.Task.Error = msg p.SetBuildStatusCompleted(config.StatusFailed) return } - p.Log.Infof("succeed to create cm for build job %s", p.JobName) + if jobExist { + p.Log.Infof("Job %s:%d eixsts.", pipelineTask.PipelineName, pipelineTask.TaskID) + + p.JobName = jobObj.Name + + // If the code is executed at this point, it indicates that the `wd` instance that executed the Job has been restarted and the + // Job timeout period needs to be corrected. + // + // Rule of reseting timeout: `timeout - (now - start_time_of_job) + compensate_duration` + // For now, `compensate_duration` is 2min. + taskTimeout := p.TaskTimeout() + elaspedTime := time.Now().Unix() - jobObj.Status.StartTime.Time.Unix() + timeout := taskTimeout + 120 - int(elaspedTime) + p.Log.Infof("Timeout before normalization: %d seconds", timeout) + if timeout < 0 { + // That shouldn't happen in theory, but a protection is needed. + timeout = 0 + } - jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) - if p.Task.ImageFrom == setting.ImageFromCustom { - jobImage = p.Task.BuildOS + p.Log.Infof("Timeout after normalization: %d seconds", timeout) + p.tmpSetTaskTimeout(timeout) } - //Resource request default value is LOW - job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) + _, cmExist, err := checkConfigMapExists(ctx, p.KubeNamespace, jobLabel, p.kubeClient) if err != nil { - msg := fmt.Sprintf("create build job context error: %v", err) + msg := fmt.Sprintf("failed to check whether ConfigMap exist for %s:%d: %s", pipelineTask.PipelineName, pipelineTask.TaskID, err) p.Log.Error(msg) p.Task.TaskStatus = config.StatusFailed p.Task.Error = msg @@ -330,33 +350,61 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe return } - job.Namespace = p.KubeNamespace + if !cmExist { + p.Log.Infof("ConfigMap for Job %s:%d does not exist. Create.", pipelineTask.PipelineName, pipelineTask.TaskID) - if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { - msg := fmt.Sprintf("delete build job error: %v", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return + err = createJobConfigMap(p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient) + if err != nil && !apierrors.IsAlreadyExists(err) { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } } + p.Log.Infof("succeed to create cm for build job %s", p.JobName) - // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 - if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { - msg := fmt.Sprintf("create secret error: %v", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return - } - if err := updater.CreateJob(job, p.kubeClient); err != nil { - msg := fmt.Sprintf("create build job error: %v", err) - p.Log.Error(msg) - p.Task.TaskStatus = config.StatusFailed - p.Task.Error = msg - p.SetBuildStatusCompleted(config.StatusFailed) - return + if !jobExist { + p.Log.Infof("Job %s:%d does not exist. Create.", pipelineTask.PipelineName, pipelineTask.TaskID) + + jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) + if p.Task.ImageFrom == setting.ImageFromCustom { + jobImage = p.Task.BuildOS + } + + //Resource request default value is LOW + job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) + if err != nil { + msg := fmt.Sprintf("create build job context error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + job.Namespace = p.KubeNamespace + + // Set imagePullSecrets of the private image registry integrated with KodeRover into the namespace. + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { + msg := fmt.Sprintf("create secret error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } + + err = updater.CreateJob(job, p.kubeClient) + if err != nil && !apierrors.IsAlreadyExists(err) { + msg := fmt.Sprintf("create build job error: %v", err) + p.Log.Error(msg) + p.Task.TaskStatus = config.StatusFailed + p.Task.Error = msg + p.SetBuildStatusCompleted(config.StatusFailed) + return + } } p.Log.Infof("succeed to create build job %s", p.JobName) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 006e8c14..48094528 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -52,6 +52,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" + kubeutil "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" commontypes "github.com/koderover/zadig/pkg/types" @@ -854,7 +855,8 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName xl.Infof("wait job to start: %s/%s", namespace, jobName) timeout := time.After(time.Duration(taskTimeout) * time.Second) podTimeout := time.After(120 * time.Second) - // 等待job运行 + + xl.Infof("Timeout of preparing Pod: %s. Timeout of running task: %s.", 120*time.Second, time.Duration(taskTimeout)*time.Second) var started bool for { @@ -1070,3 +1072,43 @@ func replaceEnvWithValue(str string, envs map[string]string) string { } return ret } + +func checkJobExists(ctx context.Context, ns string, jobLabels *JobLabel, kclient client.Client) (jobObj *batchv1.Job, exist bool, err error) { + labelsMap := getJobLabels(jobLabels) + labelSelector := labels.SelectorFromSet(labels.Set(labelsMap)) + + jobList := &batchv1.JobList{} + err = kclient.List(ctx, jobList, &client.ListOptions{ + Namespace: ns, + LabelSelector: labelSelector, + }) + if err != nil || len(jobList.Items) == 0 { + return nil, false, kubeutil.IgnoreNotFoundError(err) + } + + if len(jobList.Items) != 1 { + return nil, true, fmt.Errorf("more than 1 job with labels: %v", labelsMap) + } + + return &(jobList.Items[0]), true, nil +} + +func checkConfigMapExists(ctx context.Context, ns string, jobLabels *JobLabel, kclient client.Client) (cmObj *corev1.ConfigMap, exist bool, err error) { + labelsMap := getJobLabels(jobLabels) + labelSelector := labels.SelectorFromSet(labels.Set(labelsMap)) + + cmList := &corev1.ConfigMapList{} + err = kclient.List(ctx, cmList, &client.ListOptions{ + Namespace: ns, + LabelSelector: labelSelector, + }) + if err != nil || len(cmList.Items) == 0 { + return nil, false, kubeutil.IgnoreNotFoundError(err) + } + + if len(cmList.Items) != 1 { + return nil, true, fmt.Errorf("more than 1 ConfigMap with labels: %v", labelsMap) + } + + return &(cmList.Items[0]), true, nil +} diff --git a/pkg/microservice/warpdrive/server/server.go b/pkg/microservice/warpdrive/server/server.go index 329480f3..f2c1241a 100644 --- a/pkg/microservice/warpdrive/server/server.go +++ b/pkg/microservice/warpdrive/server/server.go @@ -18,6 +18,7 @@ package server import ( "context" + "fmt" "net/http" "time" @@ -37,8 +38,10 @@ func Serve(ctx context.Context) error { log.Info("Warpdrive service start ... ") - if err := taskcontroller.InitTaskController(ctx); err != nil { - log.Fatalf("NewTaskController error: %v", err) + controller := taskcontroller.NewController() + err := controller.Init(ctx) + if err != nil { + return fmt.Errorf("failed to init controller: %s", err) } http.HandleFunc("/ping", ping) @@ -65,7 +68,7 @@ func Serve(ctx context.Context) error { <-stopChan - return nil + return controller.Stop(ctx) } func ping(w http.ResponseWriter, r *http.Request) { -- Gitee From 9321b96a64f1b7cd1d8396d08a0295592b706f2a Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 14 Apr 2022 09:46:50 +0800 Subject: [PATCH 2160/2647] fixed status error for deploying after distribute Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 3933f498..4ed99d6e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -282,6 +282,7 @@ DistributeLoop: } // set the start time on deployment start. distribute.DeployStartTime = time.Now().Unix() + distribute.DeployStatus = setting.StatusRunning if distribute.DeployClusterID != "" { p.restConfig, err = kubeclient.GetRESTConfig(p.HubServerAddr, distribute.DeployClusterID) if err != nil { @@ -747,6 +748,8 @@ DistributeLoop: renderInfo.Name, ) } + distribute.DeployStatus = string(config.StatusPassed) + distribute.DeployEndTime = time.Now().Unix() } } } -- Gitee From ec53c547bdac46ea6e151a27a3e765b0aafcf738 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 14 Apr 2022 11:22:00 +0800 Subject: [PATCH 2161/2647] fix compile errors Signed-off-by: allenshen --- .../aslan/core/common/service/environment.go | 12 ++++++++++-- .../aslan/core/environment/service/helm.go | 18 +++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index a21930b1..623a90af 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -408,8 +408,7 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work return resp } -// GetReleaseNameToServiceNameMap generates mapping relationship: releaseName=>serviceName -func GetReleaseNameToServiceNameMap(prod *models.Product) (map[string]string, error) { +func GetProductUsedTemplateSvcs(prod *models.Product) ([]*models.Service, error) { // filter releases, only list releases deployed by zadig productName, envName, serviceMap := prod.ProductName, prod.EnvName, prod.GetServiceMap() listOpt := &commonrepo.SvcRevisionListOption{ @@ -426,7 +425,16 @@ func GetReleaseNameToServiceNameMap(prod *models.Product) (map[string]string, er if err != nil { return nil, fmt.Errorf("failed to list template services for pruduct: %s:%s", productName, envName) } + return templateServices, err +} +// GetReleaseNameToServiceNameMap generates mapping relationship: releaseName=>serviceName +func GetReleaseNameToServiceNameMap(prod *models.Product) (map[string]string, error) { + productName, envName := prod.ProductName, prod.EnvName + templateServices, err := GetProductUsedTemplateSvcs(prod) + if err != nil { + return nil, err + } // map[ReleaseName] => serviceName releaseNameMap := make(map[string]string) for _, svcInfo := range templateServices { diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 9af7784a..6c8e06e8 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -327,11 +327,14 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo // filter releases, only list releases deployed by zadig serviceMap := prod.GetServiceMap() - serviceSet := sets.NewString() - for serviceName := range serviceMap { - serviceSet.Insert(serviceName) + templateSvcs, err := commonservice.GetProductUsedTemplateSvcs(prod) + if err != nil { + return nil, fmt.Errorf("failed to get service tempaltes, err: %s", err) + } + templateSvcMap := make(map[string]*models.Service) + for _, ts := range templateSvcs { + templateSvcMap[ts.ServiceName] = ts } - services := strings.Split(serviceNames, ",") ret := &ChartImagesResp{} @@ -342,7 +345,12 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo return nil, fmt.Errorf("failed to find service: %s in product", svcName) } - releaseName := util.GeneHelmReleaseName(prod.Namespace, svcName) + ts, ok := templateSvcMap[svcName] + if !ok { + return nil, fmt.Errorf("failed to find template service: %s", svcName) + } + + releaseName := util.GeneReleaseName(ts.GetReleaseNaming(), productName, prod.Namespace, prod.Namespace, svcName) valuesYaml, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { return nil, fmt.Errorf("failed to get values for relase: %s, err: %s", releaseName, err) -- Gitee From c218c2ad1885845c4ad3d555c895bb5121b115a0 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 14 Apr 2022 11:24:00 +0800 Subject: [PATCH 2162/2647] bugfix Signed-off-by: panxunying --- .../aslan/core/delivery/service/version.go | 2 +- .../aslan/core/environment/service/image.go | 2 +- .../aslan/core/environment/service/service.go | 7 ++++--- pkg/microservice/aslan/core/system/service/registry.go | 4 ++-- .../core/workflow/service/workflow/artifact_task.go | 2 +- .../aslan/core/workflow/service/workflow/convert.go | 2 +- .../core/workflow/service/workflow/pipeline_task.go | 8 ++++---- .../core/workflow/service/workflow/workflow_task.go | 10 +++++----- 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 456c2fb8..7c6b43f1 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -808,7 +808,7 @@ func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productI } func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { - registries, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) + registries, err := commonservice.ListRegistryNamespaces("", true, log.SugaredLogger()) if err != nil { return nil, fmt.Errorf("failed to query registries") } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index ec7199dd..799e0c89 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -205,7 +205,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log return e.ErrUpdateConainterImage.AddErr(err) } // aws secrets needs to be refreshed - regs, err := commonservice.ListRegistryNamespaces(true, log) + regs, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("Failed to get registries to update container images, the error is: %s", err) return err diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 0f04557d..da98e7ea 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -21,7 +21,6 @@ import ( "strings" "github.com/hashicorp/go-multierror" - "github.com/koderover/zadig/pkg/util" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" versionedclient "istio.io/client-go/pkg/clientset/versioned" @@ -30,6 +29,8 @@ import ( "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/util" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -143,7 +144,7 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { } // aws secrets needs to be refreshed - regs, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) + regs, err := commonservice.ListRegistryNamespaces("", true, log.SugaredLogger()) if err != nil { log.Errorf("Failed to get registries to restart container, the error is: %s", err) return err @@ -404,7 +405,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e } // aws secrets needs to be refreshed - regs, err := commonservice.ListRegistryNamespaces(true, log) + regs, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("Failed to get registries to restart container, the error is: %s", err) return err diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index e2a33d85..f5f1edea 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -54,7 +54,7 @@ const ( // ListRegistries 为了抹掉ak和sk的数据 func ListRegistries(log *zap.SugaredLogger) ([]*commonmodels.RegistryNamespace, error) { - registryNamespaces, err := commonservice.ListRegistryNamespaces(false, log) + registryNamespaces, err := commonservice.ListRegistryNamespaces("", false, log) if err != nil { log.Errorf("RegistryNamespace.List error: %v", err) return registryNamespaces, fmt.Errorf("RegistryNamespace.List error: %v", err) @@ -177,7 +177,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { repoInfos := make([]*RepoInfo, 0) - resp, err := commonservice.ListRegistryNamespaces(false, log) + resp, err := commonservice.ListRegistryNamespaces("", false, log) if err != nil { log.Errorf("RegistryNamespace.List error: %v", err) return nil, fmt.Errorf("RegistryNamespace.List error: %v", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go index a957ee37..2be1fb38 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/artifact_task.go @@ -36,7 +36,7 @@ import ( // get global config payload func CreateArtifactPackageTask(args *commonmodels.ArtifactPackageTaskArgs, taskCreator string, log *zap.SugaredLogger) (int64, error) { configPayload := commonservice.GetConfigPayload(0) - repos, err := commonservice.ListRegistryNamespaces(true, log) + repos, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("CreateArtifactPackageTask query registries failed, err: %s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index ebee40b9..45d23de8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -157,7 +157,7 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z return nil, e.ErrConvertSubTasks.AddErr(err) } - registries, err := commonservice.ListRegistryNamespaces(true, log) + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 57a31cbd..b7577e00 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -114,7 +114,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } if build.Registries == nil { - registries, err := commonservice.ListRegistryNamespaces(true, log) + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } else { @@ -148,7 +148,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } if testing.Registries == nil { - registries, err := commonservice.ListRegistryNamespaces(true, log) + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } else { @@ -232,7 +232,7 @@ func CreatePipelineTask(args *commonmodels.TaskArgs, log *zap.SugaredLogger) (*C } } - repos, err := commonservice.ListRegistryNamespaces(false, log) + repos, err := commonservice.ListRegistryNamespaces("", false, log) if err != nil { return nil, e.ErrCreateTask.AddErr(err) } @@ -716,7 +716,7 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * testTask.JobCtx.Caches = testModule.Caches testTask.JobCtx.ArtifactPaths = testModule.ArtifactPaths if testTask.Registries == nil { - registries, err := commonservice.ListRegistryNamespaces(true, log) + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } else { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 448966e5..97b766c8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -830,7 +830,7 @@ func generateNextTaskID(workflowName string) (int64, error) { } func modifyConfigPayload(configPayload *commonmodels.ConfigPayload, ignoreCache, resetCache bool) { - repos, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) + repos, err := commonservice.ListRegistryNamespaces("", true, log.SugaredLogger()) if err == nil { configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) for _, repo := range repos { @@ -984,7 +984,7 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, } func buildRegistryMap() (map[string]*commonmodels.RegistryNamespace, error) { - registries, err := commonservice.ListRegistryNamespaces(true, log.SugaredLogger()) + registries, err := commonservice.ListRegistryNamespaces("", true, log.SugaredLogger()) if err != nil { return nil, fmt.Errorf("failed to query registries") } @@ -1513,7 +1513,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, testArgs := args.Tests testCreator := args.WorkflowTaskCreator - registries, err := commonservice.ListRegistryNamespaces(true, log) + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) } @@ -1674,7 +1674,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator // 获取全局configpayload configPayload := commonservice.GetConfigPayload(args.CodehostID) - repos, err := commonservice.ListRegistryNamespaces(true, log) + repos, err := commonservice.ListRegistryNamespaces("", true, log) if err == nil { configPayload.RepoConfigs = make(map[string]*commonmodels.RegistryNamespace) for _, repo := range repos { @@ -1946,7 +1946,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } } - registries, err := commonservice.ListRegistryNamespaces(true, log) + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) } -- Gitee From b2f9b11b81737b19cf5dd679339d5b569a3b6c2d Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Apr 2022 11:40:12 +0800 Subject: [PATCH 2163/2647] dead code Signed-off-by: mouuii --- .../cron/core/service/client/cronjob.go | 35 ----------------- .../cron/core/service/scheduler/scheduler.go | 39 +++---------------- pkg/shared/client/aslan/system.go | 16 ++++++++ 3 files changed, 22 insertions(+), 68 deletions(-) diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 517cc98b..3d596ed3 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -174,24 +174,6 @@ func (c *Client) TriggerSystemGc(log *zap.SugaredLogger) error { return err } -//type CleanConfig struct { -// Cron string `json:"cron"` -// CronEnabled bool `json:"cron_enabled"` -//} -// -//func (c *Client) GetDockerCleanConfig() (*CleanConfig, error) { -// url := fmt.Sprintf("%s/system/cleanCache/state", c.APIBase) -// res, err := c.get(url) -// if err != nil { -// return nil, err -// } -// config := new(CleanConfig) -// if err := json.Unmarshal(res, config); err != nil { -// return nil, err -// } -// return config, nil -//} - func (c *Client) sendRequest(url string) error { request, err := http.NewRequest("GET", url, nil) if err != nil { @@ -205,23 +187,6 @@ func (c *Client) sendRequest(url string) error { return err } -//func (c *Client) get(url string) ([]byte, error) { -// request, err := http.NewRequest("GET", url, nil) -// if err != nil { -// return nil, err -// } -// -// resp, err := c.Conn.Do(request) -// if err == nil { -// defer func() { _ = resp.Body.Close() }() -// } -// result, err := ioutil.ReadAll(resp.Body) -// if err != nil { -// return nil, err -// } -// return result, err -//} - func (c *Client) sendPostRequest(url string, body io.Reader, log *zap.SugaredLogger) (string, error) { request, err := http.NewRequest("POST", url, body) if err != nil { diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index f192e994..8f4b7cef 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -68,11 +68,11 @@ func NewCronV3() *CronV3Client { func (c *CronV3Client) Start() { var lastConfig *aslan.CleanConfig var entryID cronV3.EntryID - c.Cron.AddFunc("*/3 * * * * *", func() { + c.Cron.AddFunc("*/20 * * * * *", func() { // get the docker clean config config, err := c.AslanCli.GetDockerCleanConfig() if err != nil { - log.Errorf("get config err ,:%v", err) + log.Errorf("get config err :%s", err) return } if !reflect.DeepEqual(lastConfig, config) { @@ -83,17 +83,17 @@ func (c *CronV3Client) Start() { c.Cron.Remove(entryID) c.Cron.Run() entryID, err = c.Cron.AddFunc(config.Cron, func() { - log.Infof("********add********* , %v\n", config) + log.Infof("add docker_cache clean cron job,reg: %v", config.Cron) // call docker clean if err := c.AslanCli.DockerClean(); err != nil { - log.Errorf("clean docker cache fail , err:%s", err) + log.Errorf("fail to clean docker cache , err:%s", err) } }) if err != nil { - log.Errorf("err get entryID:%v", entryID) + log.Errorf("fail to add docker_cache clean cron job:reg: %v", config.Cron) } case false: - log.Infof("*******remove job********* , %v\n", config) + log.Infof("remove docker_cache clean job , entryID: %v", entryID) c.Cron.Remove(entryID) } } @@ -129,8 +129,6 @@ const ( InitHealthCheckPmHostScheduler = "InitHealthCheckPmHostScheduler" InitHelmEnvSyncValuesScheduler = "InitHelmEnvSyncValuesScheduler" - - //InitDockerCleanScheduler = "InitDockerCleanScheduler" ) // NewCronClient ... @@ -203,33 +201,8 @@ func (c *CronClient) Init() { c.InitHealthCheckPmHostScheduler() // sync values from remote for helm envs at regular intervals c.InitHelmEnvSyncValuesScheduler() - // add docker clean - //c.InitDockerCleanScheduler() } -//func (c *CronClient) InitDockerCleanScheduler() { -// -// lastConfig := new(client.CleanConfig) -// c.Schedulers[InitDockerCleanScheduler] = gocron.NewScheduler() -// checkConfigfunc := func() { -// config, err := c.AslanCli.GetDockerCleanConfig() -// if err != nil { -// return -// } -// -// if !reflect.DeepEqual(config, lastConfig) { -// lastConfig = config -// // 配置发生变化 -// -// } -// -// } -// c.Schedulers[InitDockerCleanScheduler].Every(20).Seconds().Do( -// checkConfigfunc, c.log) -// -// c.Schedulers[InitDockerCleanScheduler].Start() -//} - func (c *CronClient) InitCleanJobScheduler() { c.Schedulers[CleanJobScheduler] = gocron.NewScheduler() diff --git a/pkg/shared/client/aslan/system.go b/pkg/shared/client/aslan/system.go index 635b6846..2d3c5590 100644 --- a/pkg/shared/client/aslan/system.go +++ b/pkg/shared/client/aslan/system.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import ( -- Gitee From 2bde25592f9945574e63ec168743a8ba6859a7c9 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 14 Apr 2022 11:54:47 +0800 Subject: [PATCH 2164/2647] fix List API does not return kind and apiVersion Signed-off-by: ddh27 --- .../service/common_env_cfg_informer.go | 34 +++++++++++-------- pkg/tool/kube/getter/configmap.go | 6 ++++ pkg/tool/kube/getter/pvc.go | 7 ++++ pkg/tool/kube/getter/secrets.go | 7 ++++ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go index 01296d32..ed20c353 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg_informer.go @@ -36,7 +36,6 @@ import ( "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util" ) var ClusterInformersMap sync.Map @@ -109,19 +108,19 @@ func DeleteClusterInformer(clusterID string) { func onDeploymentAddAndUpdate(obj interface{}) { deploy := obj.(*appsv1.Deployment) - product, continueFlag := GetProductAndFilterNs(deploy.Namespace, deploy.Name) + labels := deploy.GetLabels() + serviceName := labels[setting.ServiceLabel] + product, continueFlag := GetProductAndFilterNs(deploy.Namespace, deploy.Name, serviceName) if !continueFlag { return } - labels := deploy.GetLabels() - label := labels[setting.ServiceLabel] configMaps, secrets, pvcs := VisitDeployment(deploy) envSvcDepend := &models.EnvSvcDepend{ ProductName: product.ProductName, EnvName: product.EnvName, Namespace: deploy.Namespace, - ServiceName: label, + ServiceName: serviceName, ServiceModule: deploy.Name, WorkloadType: setting.Deployment, ConfigMaps: configMaps.List(), @@ -135,13 +134,16 @@ func onDeploymentAddAndUpdate(obj interface{}) { func onDeploymentDelete(obj interface{}) { deploy := obj.(*appsv1.Deployment) - product, continueFlag := GetProductAndFilterNs(deploy.Namespace, deploy.Name) + labels := deploy.GetLabels() + serviceName := labels[setting.ServiceLabel] + product, continueFlag := GetProductAndFilterNs(deploy.Namespace, deploy.Name, serviceName) if !continueFlag { return } opts := &commonrepo.DeleteEnvSvcDependOption{ ProductName: product.ProductName, EnvName: product.EnvName, + ServiceName: serviceName, ServiceModule: deploy.Name, } if err := commonrepo.NewEnvSvcDependColl().Delete(opts); err != nil { @@ -182,18 +184,19 @@ func VisitDeployment(deployment *appsv1.Deployment) (sets.String, sets.String, s func onStatefulSetAddAndUpdate(obj interface{}) { sts := obj.(*appsv1.StatefulSet) - product, continueFlag := GetProductAndFilterNs(sts.Namespace, sts.Name) + labels := sts.GetLabels() + serviceName := labels[setting.ServiceLabel] + product, continueFlag := GetProductAndFilterNs(sts.Namespace, sts.Name, serviceName) if !continueFlag { return } - labels := sts.GetLabels() - label := labels[setting.ServiceLabel] + configMaps, secrets, pvcs := VisitStatefulSet(sts) envSvcDepend := &models.EnvSvcDepend{ ProductName: product.ProductName, EnvName: product.EnvName, Namespace: sts.Namespace, - ServiceName: label, + ServiceName: serviceName, ServiceModule: sts.Name, WorkloadType: setting.StatefulSet, ConfigMaps: configMaps.List(), @@ -208,13 +211,16 @@ func onStatefulSetAddAndUpdate(obj interface{}) { func onStatefulSetDelete(obj interface{}) { sts := obj.(*appsv1.StatefulSet) - product, continueFlag := GetProductAndFilterNs(sts.Namespace, sts.Name) + labels := sts.GetLabels() + serviceName := labels[setting.ServiceLabel] + product, continueFlag := GetProductAndFilterNs(sts.Namespace, sts.Name, serviceName) if !continueFlag { return } opts := &commonrepo.DeleteEnvSvcDependOption{ ProductName: product.ProductName, EnvName: product.EnvName, + ServiceName: serviceName, ServiceModule: sts.Name, } if err := commonrepo.NewEnvSvcDependColl().Delete(opts); err != nil { @@ -253,7 +259,7 @@ func VisitStatefulSet(sts *appsv1.StatefulSet) (sets.String, sets.String, sets.S return cfgSets, secretSets, pvcSets } -func GetProductAndFilterNs(namespace, workloadName string) (*models.Product, bool) { +func GetProductAndFilterNs(namespace, workloadName, svcName string) (*models.Product, bool) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Namespace: namespace}) if err != nil { if !commonrepo.IsErrNoDocuments(err) { @@ -280,10 +286,10 @@ func GetProductAndFilterNs(namespace, workloadName string) (*models.Product, boo } for _, psvc := range product.GetServiceMap() { //TODO: helm not support restart service - if psvc.Type == setting.HelmDeployType && psvc.ServiceName == util.ExtraServiceName(workloadName, namespace) { + if psvc.Type == setting.HelmDeployType { return product, false } - if psvc.Type == setting.K8SDeployType && psvc.ServiceName == workloadName { + if psvc.Type == setting.K8SDeployType && (psvc.ServiceName == workloadName || psvc.ServiceName == svcName) { return product, true } } diff --git a/pkg/tool/kube/getter/configmap.go b/pkg/tool/kube/getter/configmap.go index 61b846b9..2e71f22f 100644 --- a/pkg/tool/kube/getter/configmap.go +++ b/pkg/tool/kube/getter/configmap.go @@ -25,6 +25,12 @@ import ( func ListConfigMaps(ns string, selector labels.Selector, cl client.Client) ([]*corev1.ConfigMap, error) { l := &corev1.ConfigMapList{} + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "ConfigMap", + Version: "v1", + } + l.SetGroupVersionKind(gvk) err := ListResourceInCache(ns, selector, nil, l, cl) if err != nil { return nil, err diff --git a/pkg/tool/kube/getter/pvc.go b/pkg/tool/kube/getter/pvc.go index c6e37eac..4404c33f 100644 --- a/pkg/tool/kube/getter/pvc.go +++ b/pkg/tool/kube/getter/pvc.go @@ -19,11 +19,18 @@ package getter import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" ) func ListPvcs(ns string, selector fields.Selector, cl client.Reader) ([]*corev1.PersistentVolumeClaim, error) { pvcList := &corev1.PersistentVolumeClaimList{} + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "PersistentVolumeClaim", + Version: "v1", + } + pvcList.SetGroupVersionKind(gvk) err := ListResourceInCache(ns, nil, selector, pvcList, cl) if err != nil { return nil, err diff --git a/pkg/tool/kube/getter/secrets.go b/pkg/tool/kube/getter/secrets.go index 8bfdf783..d155ee40 100644 --- a/pkg/tool/kube/getter/secrets.go +++ b/pkg/tool/kube/getter/secrets.go @@ -18,6 +18,7 @@ package getter import ( corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -33,6 +34,12 @@ func GetSecret(ns, name string, cl client.Client) (*corev1.Secret, bool, error) func ListSecrets(ns string, cl client.Client) ([]*corev1.Secret, error) { l := &corev1.SecretList{} + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "Secret", + Version: "v1", + } + l.SetGroupVersionKind(gvk) err := ListResourceInCache(ns, nil, nil, l, cl) if err != nil { return nil, err -- Gitee From a20a585bc91dd6ff25ef3f0995fa1f4d4d91e4e9 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Apr 2022 14:03:30 +0800 Subject: [PATCH 2165/2647] use new cron lib Signed-off-by: mouuii --- go.mod | 4 +-- go.sum | 6 ++-- .../aslan/core/system/handler/router.go | 2 +- .../cron/core/service/scheduler/scheduler.go | 33 +++++++++---------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index bf097f9b..255ebd02 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/RyanCarrier/dijkstra v1.1.0 github.com/andygrunwald/go-gerrit v0.0.0-20171029143327-95b11af228a1 - github.com/antihax/optional v1.0.0 // indirect + github.com/antihax/optional v1.0.0 github.com/aws/aws-sdk-go v1.34.28 github.com/blang/semver/v4 v4.0.0 github.com/bndr/gojenkins v1.1.0 @@ -28,6 +28,7 @@ require ( github.com/garyburd/redigo v1.6.2 // indirect github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 + github.com/go-co-op/gocron v1.13.0 github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/go-sql-driver/mysql v1.6.0 @@ -54,7 +55,6 @@ require ( github.com/otiai10/copy v1.6.0 github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 - github.com/robfig/cron/v3 v3.0.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 diff --git a/go.sum b/go.sum index 46a359bb..ec128195 100644 --- a/go.sum +++ b/go.sum @@ -560,6 +560,8 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0 github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-bindata/go-bindata v1.0.1-0.20190711162640-ee3c2418e368/go.mod h1:7xCgX1lzlrXPHkfvn3EhumqHkmSlzt8at9q7v0ax19c= +github.com/go-co-op/gocron v1.13.0 h1:BjkuNImPy5NuIPEifhWItFG7pYyr27cyjS6BN9w/D4c= +github.com/go-co-op/gocron v1.13.0/go.mod h1:GD5EIEly1YNW+LovFVx5dzbYVcIc8544K99D8UVRpGo= github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -1366,8 +1368,8 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rfyiamcool/cronlib v1.0.0 h1:PNTF7pgtbL7TedrqnC6syhHwj4d1bCKxH3XmPqqgxvg= github.com/rfyiamcool/cronlib v1.0.0/go.mod h1:i7AVVUhM/kkNcC/Ayq0XOmyCJCcqa/FlflyYQMm5QWE= -github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= -github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 8a67b040..1e031d7a 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -87,7 +87,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { cleanCache.POST("/oneClick", CleanImageCache) cleanCache.GET("/state", CleanCacheState) - cleanCache.POST("/setCron", SetCron) + cleanCache.POST("/cron", SetCron) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 8f4b7cef..654e921a 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -28,7 +28,7 @@ import ( "github.com/rfyiamcool/cronlib" "go.uber.org/zap" - cronV3 "github.com/robfig/cron/v3" + newgoCron "github.com/go-co-op/gocron" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/cron/config" @@ -54,35 +54,34 @@ type CronClient struct { } type CronV3Client struct { - Cron *cronV3.Cron - AslanCli *aslan.Client + Scheduler *newgoCron.Scheduler + AslanCli *aslan.Client } func NewCronV3() *CronV3Client { return &CronV3Client{ - Cron: cronV3.New(cronV3.WithSeconds()), - AslanCli: aslan.New(configbase.AslanServiceAddress()), + Scheduler: newgoCron.NewScheduler(time.Local), + AslanCli: aslan.New(configbase.AslanServiceAddress()), } } func (c *CronV3Client) Start() { var lastConfig *aslan.CleanConfig - var entryID cronV3.EntryID - c.Cron.AddFunc("*/20 * * * * *", func() { + + c.Scheduler.Every(5).Seconds().Do(func() { // get the docker clean config config, err := c.AslanCli.GetDockerCleanConfig() if err != nil { log.Errorf("get config err :%s", err) return } + var job *newgoCron.Job if !reflect.DeepEqual(lastConfig, config) { lastConfig = config log.Infof("config changed to %v", config) - switch config.CronEnabled { - case true: - c.Cron.Remove(entryID) - c.Cron.Run() - entryID, err = c.Cron.AddFunc(config.Cron, func() { + if config.CronEnabled { + c.Scheduler.RemoveByReference(job) + job, err = c.Scheduler.Cron(config.Cron).Do(func() { log.Infof("add docker_cache clean cron job,reg: %v", config.Cron) // call docker clean if err := c.AslanCli.DockerClean(); err != nil { @@ -90,16 +89,16 @@ func (c *CronV3Client) Start() { } }) if err != nil { - log.Errorf("fail to add docker_cache clean cron job:reg: %v", config.Cron) + log.Errorf("fail to add docker_cache clean cron job:reg: %v,err:%s", config.Cron, err) } - case false: - log.Infof("remove docker_cache clean job , entryID: %v", entryID) - c.Cron.Remove(entryID) + } else { + log.Infof("remove docker_cache clean job , job: %v", job) + c.Scheduler.RemoveByReference(job) } } }) - c.Cron.Start() + c.Scheduler.StartAsync() } const ( -- Gitee From f3bec51467e307ad000f90eb6a22f9ac5ccb4d04 Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 14 Apr 2022 14:11:22 +0800 Subject: [PATCH 2166/2647] new data structure Signed-off-by: Min Min --- .../repository/models/registry_namespace.go | 7 +++++-- .../aslan/core/system/handler/registry.go | 13 +++++++------ .../aslan/core/system/handler/resp.go | 17 +++++++++-------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index 15bcc80c..c08c3c16 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -43,9 +43,12 @@ type RegistryNamespace struct { } type RegistryAdvancedSetting struct { + // indicator to make sure if the advanced has been modified + Modified bool `bson:"modified" json:"modified"` // New field since v1.11 to support self-signed TLS certificate - TLSCert string `bson:"tls_cert" json:"tls_cert"` - TLSKey string `bson:"tls_key" json:"tls_key"` + TLSEnabled bool `bson:"tls_enabled" json:"tls_enabled"` + TLSCert string `bson:"tls_cert" json:"tls_cert"` + TLSKey string `bson:"tls_key" json:"tls_key"` } func (ns *RegistryNamespace) Validate() error { diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 524b87a3..12a4b29a 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -72,12 +72,13 @@ func GetRegistryNamespace(c *gin.Context) { } ctx.Resp = &Registry{ - ID: reg.ID.Hex(), - RegAddr: reg.RegAddr, - IsDefault: reg.IsDefault, - Namespace: reg.Namespace, - TLSCert: reg.AdvancedSetting.TLSCert, - TLSKey: reg.AdvancedSetting.TLSKey, + ID: reg.ID.Hex(), + RegAddr: reg.RegAddr, + IsDefault: reg.IsDefault, + Namespace: reg.Namespace, + TLSEnabled: reg.AdvancedSetting.TLSEnabled, + TLSCert: reg.AdvancedSetting.TLSCert, + TLSKey: reg.AdvancedSetting.TLSKey, } } diff --git a/pkg/microservice/aslan/core/system/handler/resp.go b/pkg/microservice/aslan/core/system/handler/resp.go index b11a5006..91a05881 100644 --- a/pkg/microservice/aslan/core/system/handler/resp.go +++ b/pkg/microservice/aslan/core/system/handler/resp.go @@ -17,12 +17,13 @@ limitations under the License. package handler type Registry struct { - ID string `json:"id"` - RegAddr string `json:"reg_addr"` - IsDefault bool `json:"is_default"` - Namespace string `json:"namespace"` - AccessKey string `json:"access_key"` - SecretKey string `json:"secret_key"` - TLSCert string `json:"tls_cert"` - TLSKey string `json:"tls_key"` + ID string `json:"id"` + RegAddr string `json:"reg_addr"` + IsDefault bool `json:"is_default"` + Namespace string `json:"namespace"` + AccessKey string `json:"access_key"` + SecretKey string `json:"secret_key"` + TLSEnabled bool `json:"tls_enabled"` + TLSCert string `json:"tls_cert"` + TLSKey string `json:"tls_key"` } -- Gitee From 6f608b469c0caf626a2235935676fbb7329a9cbc Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 14 Apr 2022 14:23:25 +0800 Subject: [PATCH 2167/2647] minor bugfix Signed-off-by: Min Min --- .../aslan/core/common/repository/models/registry_namespace.go | 2 +- pkg/microservice/aslan/core/system/handler/resp.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index c08c3c16..55ca186f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -46,7 +46,7 @@ type RegistryAdvancedSetting struct { // indicator to make sure if the advanced has been modified Modified bool `bson:"modified" json:"modified"` // New field since v1.11 to support self-signed TLS certificate - TLSEnabled bool `bson:"tls_enabled" json:"tls_enabled"` + TLSEnabled bool `bson:"enable_tls" json:"enable_tls"` TLSCert string `bson:"tls_cert" json:"tls_cert"` TLSKey string `bson:"tls_key" json:"tls_key"` } diff --git a/pkg/microservice/aslan/core/system/handler/resp.go b/pkg/microservice/aslan/core/system/handler/resp.go index 91a05881..31621768 100644 --- a/pkg/microservice/aslan/core/system/handler/resp.go +++ b/pkg/microservice/aslan/core/system/handler/resp.go @@ -23,7 +23,7 @@ type Registry struct { Namespace string `json:"namespace"` AccessKey string `json:"access_key"` SecretKey string `json:"secret_key"` - TLSEnabled bool `json:"tls_enabled"` + TLSEnabled bool `json:"enable_tls"` TLSCert string `json:"tls_cert"` TLSKey string `json:"tls_key"` } -- Gitee From edd6cf1636f96811dbdb117bea7ca3e97c33b486 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Apr 2022 15:40:48 +0800 Subject: [PATCH 2168/2647] use new cron lib Signed-off-by: mouuii --- .../cron/core/service/scheduler/scheduler.go | 14 ++++++++------ pkg/types/cron.go | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 pkg/types/cron.go diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index 654e921a..c0f49085 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -37,6 +37,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/aslan" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) // CronClient ... @@ -75,14 +76,14 @@ func (c *CronV3Client) Start() { log.Errorf("get config err :%s", err) return } - var job *newgoCron.Job + if !reflect.DeepEqual(lastConfig, config) { lastConfig = config log.Infof("config changed to %v", config) if config.CronEnabled { - c.Scheduler.RemoveByReference(job) - job, err = c.Scheduler.Cron(config.Cron).Do(func() { - log.Infof("add docker_cache clean cron job,reg: %v", config.Cron) + c.Scheduler.RemoveByTag(string(types.CleanDockerTag)) + _, err = c.Scheduler.CronWithSeconds(config.Cron).Tag(string(types.CleanDockerTag)).Do(func() { + log.Infof("trigger aslan docker clean,reg: %v", config.Cron) // call docker clean if err := c.AslanCli.DockerClean(); err != nil { log.Errorf("fail to clean docker cache , err:%s", err) @@ -92,8 +93,9 @@ func (c *CronV3Client) Start() { log.Errorf("fail to add docker_cache clean cron job:reg: %v,err:%s", config.Cron, err) } } else { - log.Infof("remove docker_cache clean job , job: %v", job) - c.Scheduler.RemoveByReference(job) + log.Infof("remove docker_cache clean job , job tag: %v", types.CleanDockerTag) + c.Scheduler.RemoveByTag(string(types.CleanDockerTag)) + } } }) diff --git a/pkg/types/cron.go b/pkg/types/cron.go new file mode 100644 index 00000000..663d2982 --- /dev/null +++ b/pkg/types/cron.go @@ -0,0 +1,7 @@ +package types + +type CronTag string + +const ( + CleanDockerTag CronTag = "CleanDockerTag" +) -- Gitee From 7ec5b8ee78e4232b1949fa952654575ac0558615 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Apr 2022 15:44:33 +0800 Subject: [PATCH 2169/2647] use new cron lib Signed-off-by: mouuii --- pkg/microservice/aslan/core/system/service/system_cache.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/system_cache.go b/pkg/microservice/aslan/core/system/service/system_cache.go index 167e9e6c..79c15c4a 100644 --- a/pkg/microservice/aslan/core/system/service/system_cache.go +++ b/pkg/microservice/aslan/core/system/service/system_cache.go @@ -45,7 +45,11 @@ const ( //SetCron set the docker clean cron func SetCron(cron string, cronEnabled bool, logger *zap.SugaredLogger) error { - dindCleans, _ := commonrepo.NewDindCleanColl().List() + dindCleans, err := commonrepo.NewDindCleanColl().List() + if err != nil { + logger.Errorf("list dind cleans err:%s", err) + return err + } switch len(dindCleans) { case 0: dindClean := &commonmodels.DindClean{ -- Gitee From 525248ebcb5a390eb4b4f242a0f7c1d6a2af6407 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Apr 2022 16:19:38 +0800 Subject: [PATCH 2170/2647] copyright Signed-off-by: mouuii --- pkg/types/cron.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/types/cron.go b/pkg/types/cron.go index 663d2982..a1dd1bd5 100644 --- a/pkg/types/cron.go +++ b/pkg/types/cron.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package types type CronTag string -- Gitee From cd7125213b4c4409f04b7a24d9bea217547557a5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 14 Apr 2022 17:54:33 +0800 Subject: [PATCH 2171/2647] fix env update bug Signed-off-by: allenshen --- .../core/environment/service/environment.go | 219 ++++++++---------- .../aslan/core/environment/service/product.go | 9 +- 2 files changed, 102 insertions(+), 126 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 6d550de3..33ef1481 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1204,7 +1204,7 @@ func UpdateProductRecycleDay(envName, productName string, recycleDay int) error return commonrepo.NewProductColl().UpdateProductRecycleDay(envName, productName, recycleDay) } -func UpdateHelmProduct(productName, envName, updateType, username, requestID string, overrideCharts []*commonservice.RenderChartArg, deletedServices []string, log *zap.SugaredLogger) error { +func UpdateHelmProduct(productName, envName, username, requestID string, overrideCharts []*commonservice.RenderChartArg, deletedServices []string, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -1261,7 +1261,7 @@ func UpdateHelmProduct(productName, envName, updateType, username, requestID str } //对比当前环境中的环境变量和默认的环境变量 go func() { - err := updateProductGroup(username, productName, envName, updateType, productResp, currentProductService, overrideCharts, log) + err := updateProductGroup(username, productName, envName, productResp, currentProductService, overrideCharts, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -1719,6 +1719,22 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo } } + // ensure related services exist in template services + templateProduct, err := templaterepo.NewProductColl().Find(productName) + if err != nil { + log.Errorf("failed to find template pruduct: %s, err: %s", productName, err) + return envStatuses, err + } + serviceNameSet := sets.NewString() + for _, svcGroup := range templateProduct.Services { + serviceNameSet.Insert(svcGroup...) + } + for _, chartValue := range args.ChartValues { + if !serviceNameSet.Has(chartValue.ServiceName) { + return envStatuses, fmt.Errorf("failed to find service: %s in product template", chartValue.ServiceName) + } + } + // extract values.yaml and update renderset for envName := range productMap { renderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ @@ -1731,7 +1747,7 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for env: %s", envName)) } - err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, setting.SystemUser, requestID, args.ChartValues, args.DeletedServices, log) + err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, requestID, args.ChartValues, args.DeletedServices, log) if err != nil { log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) return envStatuses, e.ErrUpdateEnv.AddDesc(err.Error()) @@ -3224,7 +3240,7 @@ func batchExecutor(interval time.Duration, serviceList []*commonmodels.Service, return errList } -func updateProductGroup(username, productName, envName, updateType string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func updateProductGroup(username, productName, envName string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { var ( renderChartMap = make(map[string]*templatemodels.RenderChart) productServiceMap = make(map[string]*commonmodels.ProductService) @@ -3248,24 +3264,7 @@ func updateProductGroup(username, productName, envName, updateType string, produ productTemplServiceMap[service.ServiceName] = service } - // uninstall releases which has been removed from services - for serviceName := range productServiceMap { - if _, isExist := productTemplServiceMap[serviceName]; !isExist { - go func(namespace, serviceName string) { - log.Infof("ready to uninstall release:%s", fmt.Sprintf("%s-%s", namespace, serviceName)) - if err = helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), - Namespace: namespace, - Wait: true, - }); err != nil { - log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) - } - }(productResp.Namespace, serviceName) - } - } - - //比较当前环境中的变量和系统默认的最新变量 - renderSet, err := diffRenderSet(username, productName, envName, updateType, productResp, overrideCharts, log) + renderSet, err := diffRenderSet(username, productName, envName, productResp, overrideCharts, log) if err != nil { return e.ErrUpdateEnv.AddDesc("对比环境中的value.yaml和系统默认的value.yaml失败") } @@ -3342,130 +3341,108 @@ func updateProductGroup(username, productName, envName, updateType string, produ return errList.ErrorOrNil() } -// diffRenderSet 对比环境中的renderSet的值和服务的最新的renderSet的值 -func diffRenderSet(username, productName, envName, updateType string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { - productTemp, err := templaterepo.NewProductColl().Find(productName) - if err != nil { - log.Errorf("[ProductTmpl.find] err: %v", err) - return nil, err - } - // 系统默认的变量 +// diffRenderSet get diff between renderset in product and product template +// generate a new renderset and insert into db +func diffRenderSet(username, productName, envName string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { + // default renderset latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName}) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err } - latestRenderSetMap := make(map[string]*templatemodels.RenderChart) + // chart infos in template + latestChartInfoMap := make(map[string]*templatemodels.RenderChart) for _, renderInfo := range latestRenderSet.ChartInfos { - latestRenderSetMap[renderInfo.ServiceName] = renderInfo + latestChartInfoMap[renderInfo.ServiceName] = renderInfo } + // chart infos from client renderChartArgMap := make(map[string]*commonservice.RenderChartArg) for _, singleArg := range overrideCharts { - if singleArg.EnvName != envName { - continue + if singleArg.EnvName == envName { + renderChartArgMap[singleArg.ServiceName] = singleArg } - renderChartArgMap[singleArg.ServiceName] = singleArg } + renderSetOpt := &commonrepo.RenderSetFindOption{Name: productResp.Render.Name, Revision: productResp.Render.Revision} + currentEnvRenderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) + if err != nil { + log.Errorf("[RenderSet.find] err: %v", err) + return nil, err + } + defaultValues, yamlData := currentEnvRenderSet.DefaultValues, currentEnvRenderSet.YamlData + + // chart infos in product + currentChartInfoMap := make(map[string]*templatemodels.RenderChart) + for _, renderInfo := range currentEnvRenderSet.ChartInfos { + currentChartInfoMap[renderInfo.ServiceName] = renderInfo + } + + opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} + productCur, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + log.Errorf("GetProduct envName:%s, productName:%s, err:%s", envName, productName, err) + return nil, fmt.Errorf("GetProduct envName:%s, productName:%s, err:%s", envName, productName, err) + } + serviceMap := productCur.GetServiceMap() + serviceRespMap := productResp.GetServiceMap() newChartInfos := make([]*templatemodels.RenderChart, 0) - defaultValues := "" - var yamlData *templatemodels.CustomYaml - switch updateType { - case UpdateTypeSystem: - for _, serviceNameGroup := range productTemp.Services { - for _, serviceName := range serviceNameGroup { - if latestChartInfo, isExist := latestRenderSetMap[serviceName]; isExist { - newChartInfos = append(newChartInfos, latestChartInfo) - } - } - } - case UpdateTypeEnv: - renderSetOpt := &commonrepo.RenderSetFindOption{Name: productResp.Render.Name, Revision: productResp.Render.Revision} - currentEnvRenderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) - if err != nil { - log.Errorf("[RenderSet.find] err: %v", err) - return nil, err - } - defaultValues = currentEnvRenderSet.DefaultValues - yamlData = currentEnvRenderSet.YamlData - // 环境里面的变量 - currentEnvRenderSetMap := make(map[string]*templatemodels.RenderChart) - for _, renderInfo := range currentEnvRenderSet.ChartInfos { - currentEnvRenderSetMap[renderInfo.ServiceName] = renderInfo + for serviceName, latestChartInfo := range latestChartInfoMap { + currentChartInfo, okC := currentChartInfoMap[serviceName] + renderArg, okR := renderChartArgMap[serviceName] + if !okR && !okC { + continue } - tmpCurrentChartInfoMap := make(map[string]*templatemodels.RenderChart) - tmpLatestChartInfoMap := make(map[string]*templatemodels.RenderChart) - //过滤掉服务编排没有的服务,这部分不需要做diff - for _, serviceNameGroup := range productTemp.Services { - for _, serviceName := range serviceNameGroup { - if currentChartInfo, isExist := currentEnvRenderSetMap[serviceName]; isExist { - tmpCurrentChartInfoMap[serviceName] = currentChartInfo - } - if latestChartInfo, isExist := latestRenderSetMap[serviceName]; isExist { - tmpLatestChartInfoMap[serviceName] = latestChartInfo - } + serviceInfoResp := serviceRespMap[serviceName] + serviceInfoCur := serviceMap[serviceName] + imageRelatedKey := sets.NewString() + if serviceInfoResp != nil && serviceInfoCur != nil { + curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: productName, + Type: setting.HelmDeployType, + Revision: serviceInfoCur.Revision, + }) + if err != nil { + log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) + return nil, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) } - } - - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} - productCur, err := commonrepo.NewProductColl().Find(opt) - if err != nil { - log.Errorf("GetProduct envName:%s, productName:%s, err:%s", envName, productName, err) - return nil, fmt.Errorf("GetProduct envName:%s, productName:%s, err:%s", envName, productName, err) - } - serviceMap := productCur.GetServiceMap() - serviceRespMap := productResp.GetServiceMap() - for serviceName, latestChartInfo := range tmpLatestChartInfoMap { - if currentChartInfo, ok := tmpCurrentChartInfoMap[serviceName]; ok { - serviceInfoResp := serviceRespMap[serviceName] - serviceInfoCur := serviceMap[serviceName] - imageRelatedKey := sets.NewString() - if serviceInfoResp != nil && serviceInfoCur != nil { - curEnvService, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: serviceName, - ProductName: productName, - Type: setting.HelmDeployType, - Revision: serviceInfoCur.Revision, - }) - if err != nil { - log.Errorf("failed to query service, name %s, Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) - return nil, fmt.Errorf("failed to query service, name %s,Revision %d,err %s", serviceName, serviceInfoCur.Revision, err) - } - L: - for _, curSvcContainers := range curEnvService.Containers { - if checkServiceImageUpdated(curSvcContainers, serviceInfoCur) { - for _, container := range serviceInfoResp.Containers { - if curSvcContainers.Name == container.Name && container.ImagePath != nil { - imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) - continue L - } - } + L: + for _, curSvcContainers := range curEnvService.Containers { + if checkServiceImageUpdated(curSvcContainers, serviceInfoCur) { + for _, container := range serviceInfoResp.Containers { + if curSvcContainers.Name == container.Name && container.ImagePath != nil { + imageRelatedKey.Insert(container.ImagePath.Image, container.ImagePath.Repo, container.ImagePath.Tag) + continue L } } } - - //拿当前环境values.yaml的key的value去替换服务里面的values.yaml的相同的key的value - newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml), imageRelatedKey) - if err != nil { - log.Errorf("Failed to override values for service %s, err: %s", serviceName, err) - } else { - latestChartInfo.ValuesYaml = string(newValuesYaml) - } - - // user override value in cur environment - latestChartInfo.OverrideValues = currentChartInfo.OverrideValues - latestChartInfo.OverrideYaml = currentChartInfo.OverrideYaml } - // user override value form request - if renderArg, ok := renderChartArgMap[serviceName]; ok { - renderArg.FillRenderChartModel(latestChartInfo, latestChartInfo.ChartVersion) + } + + // use the variables in current product when updating services + if okC { + // use the value of the key of the current values.yaml to replace the value of the same key of the values.yaml in the service + newValuesYaml, err := overrideValues([]byte(currentChartInfo.ValuesYaml), []byte(latestChartInfo.ValuesYaml), imageRelatedKey) + if err != nil { + log.Errorf("Failed to override values for service %s, err: %s", serviceName, err) + } else { + latestChartInfo.ValuesYaml = string(newValuesYaml) } - newChartInfos = append(newChartInfos, latestChartInfo) + + // user override value in cur environment + latestChartInfo.OverrideValues = currentChartInfo.OverrideValues + latestChartInfo.OverrideYaml = currentChartInfo.OverrideYaml + } + + // user override value form request + if okR { + renderArg.FillRenderChartModel(latestChartInfo, latestChartInfo.ChartVersion) } + newChartInfos = append(newChartInfos, latestChartInfo) } if err = commonservice.CreateHelmRenderSet( diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index c5b2e5f7..e213875d 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -250,6 +250,10 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar prodResp.IsLocal = true } + if prod.Source != setting.SourceFromExternal { + prodResp.Services = prod.GetGroupServiceNames() + } + if prod.Status == setting.ProductStatusCreating { prodResp.Status = setting.PodCreating return prodResp @@ -281,14 +285,9 @@ func buildProductResp(envName string, prod *commonmodels.Product, log *zap.Sugar return prodResp } default: - prodResp.Services = prod.GetGroupServiceNames() servicesResp, _, errObj = ListGroups("", envName, prod.ProductName, -1, -1, log) } - if prod.Source == setting.SourceFromHelm { - prodResp.Services = prod.GetGroupServiceNames() - } - if errObj != nil { prodResp.Error = errObj.Error() } else { -- Gitee From e15aa89f330d72903a0f6aa927a8432e8853db32 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 14 Apr 2022 18:04:55 +0800 Subject: [PATCH 2172/2647] resolve comments Signed-off-by: allenshen --- .../core/environment/service/environment.go | 43 ++++--------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 33ef1481..d1d617a2 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -950,6 +950,7 @@ func CreateHelmProduct(productName, userName, requestID string, args []*CreateHe if _, ok := templateServiceMap[cv.ServiceName]; !ok { dataValid = false errList = multierror.Append(errList, fmt.Errorf("failed to find service tempalte, serviceName: %s", cv.ServiceName)) + break } } if !dataValid { @@ -1793,34 +1794,6 @@ func GetProductInfo(username, envName, productName string, log *zap.SugaredLogge return prod, nil } -func GetProductIngress(productName string, log *zap.SugaredLogger) ([]*ProductIngressInfo, error) { - productIngressInfos := make([]*ProductIngressInfo, 0) - products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) - if err != nil { - log.Errorf("Failed to list envs, err: %s", err) - return productIngressInfos, e.ErrListEnvs.AddDesc(err.Error()) - } - for _, prod := range products { - productIngressInfo := new(ProductIngressInfo) - productIngressInfo.EnvName = prod.EnvName - ingressInfos := make([]*commonservice.IngressInfo, 0) - - serviceGroups, _, err := ListGroups("", prod.EnvName, productName, 0, 0, log) - if err != nil { - log.Errorf("GetProductIngress GetProductAndKubeClient err:%v", err) - continue - } - for _, serviceGroup := range serviceGroups { - if serviceGroup.Ingress != nil && len(serviceGroup.Ingress.HostInfo) > 0 { - ingressInfos = append(ingressInfos, serviceGroup.Ingress) - } - } - productIngressInfo.IngressInfos = ingressInfos - productIngressInfos = append(productIngressInfos, productIngressInfo) - } - return productIngressInfos, nil -} - func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ([]*commonmodels.HelmVersions, error) { var ( helmVersions = make([]*commonmodels.HelmVersions, 0) @@ -2115,9 +2088,9 @@ func DeleteProductServices(envName, productName string, serviceNames []string, l } if getProjectType(productName) == setting.HelmDeployType { return deleteHelmProductServices(productInfo, serviceNames, log) - } else { - return deleteK8sProductServices(productInfo, serviceNames, log) } + return deleteK8sProductServices(productInfo, serviceNames, log) + } func deleteHelmProductServices(productInfo *commonmodels.Product, serviceNames []string, log *zap.SugaredLogger) error { @@ -2162,7 +2135,7 @@ func deleteHelmProductServices(productInfo *commonmodels.Product, serviceNames [ // create new renderset if err := commonservice.CreateHelmRenderSet(renderset, log); err != nil { - log.Error("failed to create renderset, name %s, err: %s", renderset.Name, err) + log.Errorf("failed to create renderset, name %s, err: %s", renderset.Name, err) return e.ErrUpdateEnv.AddErr(err) } @@ -3136,7 +3109,7 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel serviceList = append(serviceList, serviceObj) } - serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -3235,7 +3208,7 @@ func batchExecutor(interval time.Duration, serviceList []*commonmodels.Service, *failedServices = append(*failedServices, data) log.Errorf("service:%s apply failed, err %s", data.ServiceName, err) } - time.Sleep(time.Second) + time.Sleep(interval) } return errList } @@ -3321,7 +3294,7 @@ func updateProductGroup(username, productName, envName string, productResp *comm serviceList = append(serviceList, serviceObj) } - serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*500, serviceList, handler, log) if serviceGroupErr != nil { errList = multierror.Append(errList, serviceGroupErr...) } @@ -3571,7 +3544,7 @@ func updateProductVariable(productName, envName string, productResp *commonmodel } groupServices = append(groupServices, service) } - groupServiceErr := batchExecutorWithRetry(3, time.Millisecond*2500, serviceList, handler, log) + groupServiceErr := batchExecutorWithRetry(3, time.Millisecond*500, serviceList, handler, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } -- Gitee From 13965012a8b5e2b94a2bdbea05eb229e9332a349 Mon Sep 17 00:00:00 2001 From: mouuii Date: Thu, 14 Apr 2022 18:12:03 +0800 Subject: [PATCH 2173/2647] minute Signed-off-by: mouuii --- pkg/microservice/cron/core/service/scheduler/scheduler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index c0f49085..e6dccc40 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -82,7 +82,7 @@ func (c *CronV3Client) Start() { log.Infof("config changed to %v", config) if config.CronEnabled { c.Scheduler.RemoveByTag(string(types.CleanDockerTag)) - _, err = c.Scheduler.CronWithSeconds(config.Cron).Tag(string(types.CleanDockerTag)).Do(func() { + _, err = c.Scheduler.Cron(config.Cron).Tag(string(types.CleanDockerTag)).Do(func() { log.Infof("trigger aslan docker clean,reg: %v", config.Cron) // call docker clean if err := c.AslanCli.DockerClean(); err != nil { -- Gitee From e25f495663a5749bce62ff3a1c9e4921fe680cd9 Mon Sep 17 00:00:00 2001 From: panxunying Date: Thu, 14 Apr 2022 18:24:39 +0800 Subject: [PATCH 2174/2647] bugfix Signed-off-by: panxunying --- .../aslan/core/common/service/registry.go | 24 +++++++++++++++---- .../core/system/handler/external_system.go | 8 ++++++- .../aslan/core/system/handler/helm.go | 8 +++++-- .../aslan/core/system/handler/jenkins.go | 7 +++++- .../aslan/core/system/handler/private_key.go | 7 +++++- .../aslan/core/system/handler/registry.go | 7 +++++- .../aslan/core/system/handler/rsa_key.go | 8 ++++++- .../aslan/core/system/handler/s3.go | 7 +++++- .../core/codehost/handler/codehost.go | 8 ++++++- .../core/connector/handler/connector.go | 8 ++++++- .../core/connector/service/connector.go | 1 + .../systemconfig/core/email/handler/email.go | 8 ++++++- .../systemconfig/core/jira/handler/jira.go | 8 ++++++- .../systemconfig/core/jira/service/jira.go | 3 +++ 14 files changed, 95 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 3dc165a6..606663a9 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -104,14 +104,25 @@ func ListRegistryNamespaces(encryptedKey string, getRealCredential bool, log *za log.Errorf("RegistryNamespace.List error: %s", err) return resp, fmt.Errorf("RegistryNamespace.List error: %s", err) } - + var aesKey *GetAesKeyFromEncryptedKeyResp + if len(encryptedKey) > 0 { + aesKey, err = GetAesKeyFromEncryptedKey(encryptedKey, log) + if err != nil { + return nil, err + } + } if !getRealCredential { + if len(encryptedKey) > 0 { + for _, reg := range resp { + reg.SecretKey, err = crypto.AesEncryptByKey(reg.SecretKey, aesKey.PlainText) + if err != nil { + return nil, err + } + } + } return resp, nil } - aesKey, err := GetAesKeyFromEncryptedKey(encryptedKey, log) - if err != nil { - return nil, err - } + for _, reg := range resp { switch reg.RegProvider { case config.RegistryTypeSWR: @@ -126,6 +137,9 @@ func ListRegistryNamespaces(encryptedKey string, getRealCredential bool, log *za reg.AccessKey = realAK reg.SecretKey = realSK } + if len(encryptedKey) == 0 { + continue + } reg.SecretKey, err = crypto.AesEncryptByKey(reg.SecretKey, aesKey.PlainText) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/system/handler/external_system.go b/pkg/microservice/aslan/core/system/handler/external_system.go index ba4db808..97860c7f 100644 --- a/pkg/microservice/aslan/core/system/handler/external_system.go +++ b/pkg/microservice/aslan/core/system/handler/external_system.go @@ -27,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) @@ -75,7 +76,12 @@ func ListExternalSystem(c *gin.Context) { return } - systemList, length, err := service.ListExternalSystem(c.Query("encryptedKey"), args.PageNum, args.PageSize, ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + systemList, length, err := service.ListExternalSystem(encryptedKey, args.PageNum, args.PageSize, ctx.Logger) if err == nil { ctx.Resp = &listExternalResp{ SystemList: systemList, diff --git a/pkg/microservice/aslan/core/system/handler/helm.go b/pkg/microservice/aslan/core/system/handler/helm.go index 31772051..4f5b4f0b 100644 --- a/pkg/microservice/aslan/core/system/handler/helm.go +++ b/pkg/microservice/aslan/core/system/handler/helm.go @@ -31,8 +31,12 @@ import ( func ListHelmRepos(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Resp, ctx.Err = commonservice.ListHelmRepos(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = commonservice.ListHelmRepos(encryptedKey, ctx.Logger) } func CreateHelmRepo(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/jenkins.go b/pkg/microservice/aslan/core/system/handler/jenkins.go index 5424af5a..dfb66747 100644 --- a/pkg/microservice/aslan/core/system/handler/jenkins.go +++ b/pkg/microservice/aslan/core/system/handler/jenkins.go @@ -64,7 +64,12 @@ func ListJenkinsIntegration(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListJenkinsIntegration(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.ListJenkinsIntegration(encryptedKey, ctx.Logger) } func UpdateJenkinsIntegration(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 78e75bf3..71274731 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -36,7 +36,12 @@ func ListPrivateKeys(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListPrivateKeys(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.ListPrivateKeys(encryptedKey, ctx.Logger) } func GetPrivateKey(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 93434294..943d8fc2 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -81,7 +81,12 @@ func ListRegistryNamespaces(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = commonservice.ListRegistryNamespaces(c.Query("encryptedKey"), false, ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = commonservice.ListRegistryNamespaces(encryptedKey, false, ctx.Logger) } func CreateRegistryNamespace(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/rsa_key.go b/pkg/microservice/aslan/core/system/handler/rsa_key.go index 108a7d14..abfb20f9 100644 --- a/pkg/microservice/aslan/core/system/handler/rsa_key.go +++ b/pkg/microservice/aslan/core/system/handler/rsa_key.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func GetRSAPublicKey(c *gin.Context) { @@ -32,5 +33,10 @@ func GetRSAPublicKey(c *gin.Context) { func GetTextFromEncryptedKey(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetAesKeyFromEncryptedKey(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.GetAesKeyFromEncryptedKey(encryptedKey, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/s3.go b/pkg/microservice/aslan/core/system/handler/s3.go index af133fec..53ff2db1 100644 --- a/pkg/microservice/aslan/core/system/handler/s3.go +++ b/pkg/microservice/aslan/core/system/handler/s3.go @@ -36,7 +36,12 @@ func ListS3Storage(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListS3Storage(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.ListS3Storage(encryptedKey, ctx.Logger) } func CreateS3Storage(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 7ccafc39..1749016f 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func CreateCodeHost(c *gin.Context) { @@ -41,7 +42,12 @@ func CreateCodeHost(c *gin.Context) { func ListCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.List(c.Query("encryptedKey"), c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.List(encryptedKey, c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } func DeleteCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/connector/handler/connector.go b/pkg/microservice/systemconfig/core/connector/handler/connector.go index 1c60cc87..7fd5e232 100644 --- a/pkg/microservice/systemconfig/core/connector/handler/connector.go +++ b/pkg/microservice/systemconfig/core/connector/handler/connector.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/connector/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func CreateConnector(c *gin.Context) { @@ -40,7 +41,12 @@ func ListConnectors(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListConnectors(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.ListConnectors(encryptedKey, ctx.Logger) } func DeleteConnector(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/connector/service/connector.go b/pkg/microservice/systemconfig/core/connector/service/connector.go index d099ef7f..27b2b6c9 100644 --- a/pkg/microservice/systemconfig/core/connector/service/connector.go +++ b/pkg/microservice/systemconfig/core/connector/service/connector.go @@ -42,6 +42,7 @@ func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connecto var res []*Connector for _, c := range cs { cf := make(map[string]interface{}) + logger.Infof("config:%v", c.Config) err = json.Unmarshal([]byte(c.Config), &cf) if err != nil { logger.Warnf("Failed to unmarshal config, err: %s", err) diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index 399bd293..174854d3 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/email/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func GetEmailHost(c *gin.Context) { @@ -34,7 +35,12 @@ func InternalGetEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.InternalGetEmailHost(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.InternalGetEmailHost(encryptedKey, ctx.Logger) } func CreateEmailHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/jira/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go index 5776bb82..12bec496 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/repository/models" "github.com/koderover/zadig/pkg/microservice/systemconfig/core/jira/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func DeleteJira(c *gin.Context) { @@ -33,7 +34,12 @@ func DeleteJira(c *gin.Context) { func GetJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GeJira(c.Query("encryptedKey"), ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = service.GeJira(encryptedKey, ctx.Logger) } func CreateJira(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 2db83768..77b6c65f 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -33,6 +33,9 @@ func GeJira(encryptedKey string, _ *zap.SugaredLogger) (*models.Jira, error) { if err != nil { return nil, err } + if jira == nil { + return nil, nil + } aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { return nil, err -- Gitee From 61fa7b33b53d01eb52ed5301188a6cd090597deb Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Thu, 14 Apr 2022 21:50:49 +0800 Subject: [PATCH 2175/2647] use buildkit by default when docker build Signed-off-by: zhangyifei --- pkg/microservice/reaper/core/service/reaper/reaper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 32620acc..e5b292c3 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -543,6 +543,7 @@ func (r *Reaper) getUserEnvs() []string { fmt.Sprintf("WORKSPACE=%s", r.ActiveWorkspace), // TODO: readme文件可以使用别的方式代替 fmt.Sprintf("README=%s", ReadmeFile), + "DOCKER_BUILDKIT=1", } r.Ctx.Paths = strings.Replace(r.Ctx.Paths, "$HOME", config.Home(), -1) -- Gitee From fc40058f55cc8deb5a9777d7152d6d294014de9a Mon Sep 17 00:00:00 2001 From: fansi Date: Thu, 14 Apr 2022 21:27:32 +0800 Subject: [PATCH 2176/2647] add pytest case demo Signed-off-by: fansi --- examples/pytest-demo/num_test.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/pytest-demo/num_test.py diff --git a/examples/pytest-demo/num_test.py b/examples/pytest-demo/num_test.py new file mode 100644 index 00000000..30d8afe4 --- /dev/null +++ b/examples/pytest-demo/num_test.py @@ -0,0 +1,14 @@ +def increase(x): + return x + 1 + +def test_increase_1_to_2021_is_2022(): + assert increase(2021) == 2022 + +def square(x): + return x * x + +def test_the_square_of_5_is_25(): + assert square(5) == 25 + +# def test_the_square_of_negative_5_is_negative_25(): +# assert square(-5) == -25 -- Gitee From c621a4211a42c0f52ac6e3835ea27cc4a0a65e6b Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 00:20:03 +0800 Subject: [PATCH 2177/2647] update dind logic implemented Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 172 +++++++++++++++--- pkg/tool/kube/getter/base.go | 38 ++++ 2 files changed, 181 insertions(+), 29 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index b03fbb6e..ef632454 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -18,10 +18,12 @@ package service import ( "context" + "encoding/base64" "errors" "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/getter" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -104,8 +106,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa return fmt.Errorf("RegistryNamespace.Create error: %v", err) } - //return UpdateDinD(log) - return nil + return SyncDinDForRegistries(log) } func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { @@ -137,7 +138,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam log.Errorf("RegistryNamespace.Update error: %v", err) return fmt.Errorf("RegistryNamespace.Update error: %v", err) } - return nil + return SyncDinDForRegistries(log) } func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { @@ -181,7 +182,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { return err } } - return nil + return SyncDinDForRegistries(log) } func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { @@ -359,28 +360,141 @@ func UpdateDinD(log *zap.SugaredLogger) error { return nil } -//func CreateCertSecret(secretName, cert string, log *zap.SugaredLogger) error { -// dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) -// if err != nil { -// log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) -// return err -// } -// -// certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) -// -// secret := &unstructured.Unstructured{ -// Object: map[string]interface{}{ -// "apiVersion": "v1", -// "kind": "Secret", -// "metadata": map[string]interface{}{ -// "name": secretName, -// }, -// "type": "Opaque", -// "data": map[string]string{ -// "cert.crt": certificateString, -// }, -// }, -// } -// -// result, err := client.R -//} +func SyncDinDForRegistries(log *zap.SugaredLogger) error { + regList, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) + if err != nil { + log.Errorf("failed to list registry to update dind, the error is: %s", err) + return err + } + + stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulset"} + + dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) + return err + } + + volumeMountList := make([]map[string]string, 0) + volumeList := make([]map[string]interface{}, 0) + + insecureRegistryList := make([]string, 0) + for _, reg := range regList { + // compatibility changes before 1.11 + if reg.AdvancedSetting != nil { + // if a registry is marked as insecure, we add a record to insecure-registries + if !reg.AdvancedSetting.TLSEnabled { + insecureRegistryList = append(insecureRegistryList, reg.RegAddr) + } + // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon + if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { + mountName := fmt.Sprintf("%s-%s-cert", reg.RegAddr, reg.Namespace) + err := ensureCertificateSecret(mountName, config.Namespace(), reg.AdvancedSetting.TLSCert, log) + if err != nil { + log.Errorf("failed to ensure secret: %s, the error is: %s", mountName, err) + return err + } + + // create volumeMount info + volumeMountMap := map[string]string{ + "mountPath": "/etc/docker/certs.d", + "name": mountName, + } + volumeMountList = append(volumeMountList, volumeMountMap) + // create volume info + secretItemList := make([]map[string]string, 0) + secretItemList = append(secretItemList, map[string]string{ + "key": "cert.crt", + "path": fmt.Sprintf("%s/%s", reg.RegAddr, "cert.crt"), + }) + secretInfo := map[string]interface{}{ + "items": secretItemList, + "secretName": mountName, + } + volumeMap := map[string]interface{}{ + "name": mountName, + "secret": secretInfo, + } + volumeList = append(volumeList, volumeMap) + } + } + } + + retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Retrieve the latest version of Deployment before attempting update + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver + result, getErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Get(context.TODO(), "dind", metav1.GetOptions{}) + if getErr != nil { + return err + } + + // extract spec containers + containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") + if err != nil || !found || containers == nil { + return err + } + + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { + return err + } + _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) + return updateErr + }) + + if retryErr != nil { + log.Errorf("failed to update dind, the error is: %s", retryErr) + } + + return retryErr +} + +func ensureCertificateSecret(secretName, namespace, cert string, log *zap.SugaredLogger) error { + certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) + datamap := map[string]string{ + "cert.crt": certificateString, + } + + dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) + if err != nil { + log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) + return err + } + + secret, err := dynamicClient.Resource(getter.ResourceKindMap["Secret"]).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + // if there is an error, either because of not found or anything else, we try to create a secret with the given information + if err != nil { + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": secretName, + }, + "type": "Opaque", + "data": datamap, + }, + } + + _, err := dynamicClient.Resource(getter.ResourceKindMap["Secret"]).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) + } + return err + } else { + if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { + log.Errorf("failed to set data in secret object, the error is: %s", err) + return err + } + _, err := dynamicClient.Resource(getter.ResourceKindMap["Secret"]).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) + } + return err + } +} diff --git a/pkg/tool/kube/getter/base.go b/pkg/tool/kube/getter/base.go index d40e5a1a..15b3b05f 100644 --- a/pkg/tool/kube/getter/base.go +++ b/pkg/tool/kube/getter/base.go @@ -29,6 +29,44 @@ import ( "sigs.k8s.io/yaml" ) +var ResourceKindMap = map[string]schema.GroupVersionResource{ + "Deployment": { + Group: "apps", + Version: "v1", + Resource: "deployments", + }, + "ConfigMap": { + Group: "apps", + Version: "v1", + Resource: "configmaps", + }, + "Job": { + Group: "batch", + Version: "v1", + Resource: "jobs", + }, + "Secret": { + Group: "api", + Version: "v1", + Resource: "secrets", + }, + "Service": { + Group: "api", + Version: "v1", + Resource: "services", + }, + "StatefulSet": { + Group: "apps", + Version: "v1", + Resource: "statefulsets", + }, + "PersistentVolume": { + Group: "api", + Version: "v1", + Resource: "persistentvolumes", + }, +} + // GetResourceInCache gets a specific Kubernetes object in local cache, object can be any types // which are registered by "scheme.AddToScheme()". // Return true if object is found, false if not, or an error if something bad happened. -- Gitee From b539cf1adaa1fb3b7a13e3e42c16628d3c300237 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 00:20:43 +0800 Subject: [PATCH 2178/2647] update dind logic implemented Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 78 ------------------- 1 file changed, 78 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index ef632454..8e6d1426 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -282,84 +282,6 @@ func UpdateRegistryNamespaceDefault(args *commonmodels.RegistryNamespace, log *z return nil } -func UpdateDinD(log *zap.SugaredLogger) error { - repoInfo, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) - if err != nil { - log.Errorf("failed to list all registries to update dind, the error is: %s", err) - return err - } - - dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) - if err != nil { - log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) - return err - } - - volumeMountList := make([]map[string]string, 0) - volumeList := make([]map[string]interface{}, 0) - for _, repo := range repoInfo { - if repo.AdvancedSetting != nil && repo.AdvancedSetting.TLSCert != "" { - mountName := fmt.Sprintf("%s-%s-cert", repo.RegAddr, repo.Namespace) - // create volumeMount info - volumeMountMap := map[string]string{ - "mountPath": "/etc/docker/certs.d", - "name": mountName, - } - volumeMountList = append(volumeMountList, volumeMountMap) - // create volume info - secretItemList := make([]map[string]string, 0) - secretItemList = append(secretItemList, map[string]string{ - "key": "cert.crt", - "path": fmt.Sprintf("%s/%s", repo.RegAddr, "cert.crt"), - }) - secretInfo := map[string]interface{}{ - "items": secretItemList, - "secretName": mountName, - } - volumeMap := map[string]interface{}{ - "name": mountName, - "secret": secretInfo, - } - volumeList = append(volumeList, volumeMap) - } - } - - stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulset"} - - retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - // Retrieve the latest version of Deployment before attempting update - // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Get(context.TODO(), "dind", metav1.GetOptions{}) - if getErr != nil { - return err - } - - // extract spec containers - containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") - if err != nil || !found || containers == nil { - return err - } - - if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { - return err - } - if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { - return err - } - if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { - return err - } - _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) - return updateErr - }) - - if retryErr != nil { - return fmt.Errorf("update dind statefulset failed: %v", retryErr) - } - - return nil -} - func SyncDinDForRegistries(log *zap.SugaredLogger) error { regList, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err != nil { -- Gitee From 5318502a36c4920c727eb468cd4374603b4c3d1b Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 00:54:22 +0800 Subject: [PATCH 2179/2647] stuff Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 8e6d1426..5bf09baa 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -310,7 +310,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { } // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { - mountName := fmt.Sprintf("%s-%s-cert", reg.RegAddr, reg.Namespace) + mountName := fmt.Sprintf("%s-cert", reg.ID) err := ensureCertificateSecret(mountName, config.Namespace(), reg.AdvancedSetting.TLSCert, log) if err != nil { log.Errorf("failed to ensure secret: %s, the error is: %s", mountName, err) -- Gitee From 6e69625749cafb8d35156f14f6f43e7302cb1bca Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 01:04:03 +0800 Subject: [PATCH 2180/2647] GVR debug Signed-off-by: Min Min --- pkg/tool/kube/getter/base.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tool/kube/getter/base.go b/pkg/tool/kube/getter/base.go index 15b3b05f..e51caf80 100644 --- a/pkg/tool/kube/getter/base.go +++ b/pkg/tool/kube/getter/base.go @@ -46,7 +46,7 @@ var ResourceKindMap = map[string]schema.GroupVersionResource{ Resource: "jobs", }, "Secret": { - Group: "api", + Group: "core", Version: "v1", Resource: "secrets", }, -- Gitee From 681a150b9a4c6ac31fde5a8e323e876b932187fb Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 01:10:47 +0800 Subject: [PATCH 2181/2647] more debug stuff Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 5bf09baa..e408afa6 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -23,7 +23,6 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - "github.com/koderover/zadig/pkg/tool/kube/getter" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -382,13 +381,19 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare "cert.crt": certificateString, } + secretResourceKind := schema.GroupVersionResource{ + Group: "api", + Version: "v1", + Resource: "secrets", + } + dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) if err != nil { log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) return err } - secret, err := dynamicClient.Resource(getter.ResourceKindMap["Secret"]).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + secret, err := dynamicClient.Resource(secretResourceKind).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) // if there is an error, either because of not found or anything else, we try to create a secret with the given information if err != nil { secret := &unstructured.Unstructured{ @@ -403,7 +408,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare }, } - _, err := dynamicClient.Resource(getter.ResourceKindMap["Secret"]).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + _, err := dynamicClient.Resource(secretResourceKind).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) if err != nil { log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) } @@ -413,7 +418,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare log.Errorf("failed to set data in secret object, the error is: %s", err) return err } - _, err := dynamicClient.Resource(getter.ResourceKindMap["Secret"]).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + _, err := dynamicClient.Resource(secretResourceKind).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) if err != nil { log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) } -- Gitee From 4ebaff6a4afb18d7f4bf30c00358585a8b0bacab Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 01:26:14 +0800 Subject: [PATCH 2182/2647] debug info Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index e408afa6..7f7047d0 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -382,7 +382,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare } secretResourceKind := schema.GroupVersionResource{ - Group: "api", + Group: "core", Version: "v1", Resource: "secrets", } -- Gitee From e0db009ee1c6db098ed39ad33957ef5369b806ff Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 01:33:38 +0800 Subject: [PATCH 2183/2647] even more debugs Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 7f7047d0..29589111 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -382,7 +382,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare } secretResourceKind := schema.GroupVersionResource{ - Group: "core", + Group: "meta", Version: "v1", Resource: "secrets", } -- Gitee From c7405c6c7671f188d694162dbf633f6b4d2ea9bb Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 01:47:18 +0800 Subject: [PATCH 2184/2647] even more debugs Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 29589111..09c6dc30 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -23,6 +23,8 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -381,11 +383,9 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare "cert.crt": certificateString, } - secretResourceKind := schema.GroupVersionResource{ - Group: "meta", - Version: "v1", - Resource: "secrets", - } + secretType := &corev1.Secret{} + + gvr, _ := meta.UnsafeGuessKindToResource(secretType.GroupVersionKind()) dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) if err != nil { @@ -393,7 +393,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare return err } - secret, err := dynamicClient.Resource(secretResourceKind).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + secret, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) // if there is an error, either because of not found or anything else, we try to create a secret with the given information if err != nil { secret := &unstructured.Unstructured{ @@ -408,7 +408,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare }, } - _, err := dynamicClient.Resource(secretResourceKind).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + _, err := dynamicClient.Resource(gvr).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) if err != nil { log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) } @@ -418,7 +418,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare log.Errorf("failed to set data in secret object, the error is: %s", err) return err } - _, err := dynamicClient.Resource(secretResourceKind).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + _, err := dynamicClient.Resource(gvr).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) if err != nil { log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) } -- Gitee From 5a25d58f3b98da42ca6d936efd57aaa8430b520c Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 01:53:39 +0800 Subject: [PATCH 2185/2647] minor fix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 09c6dc30..fbe5533f 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -311,7 +311,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { } // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { - mountName := fmt.Sprintf("%s-cert", reg.ID) + mountName := fmt.Sprintf("%s-cert", reg.ID.Hex()) err := ensureCertificateSecret(mountName, config.Namespace(), reg.AdvancedSetting.TLSCert, log) if err != nil { log.Errorf("failed to ensure secret: %s, the error is: %s", mountName, err) -- Gitee From e1cd4e7d310440acb3d6067ff5a64a2cd4f3f164 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 02:00:28 +0800 Subject: [PATCH 2186/2647] minor fix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index fbe5533f..ba1047ea 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -393,7 +393,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare return err } - secret, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + secret, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), "21-registry-secret", metav1.GetOptions{}) // if there is an error, either because of not found or anything else, we try to create a secret with the given information if err != nil { secret := &unstructured.Unstructured{ -- Gitee From 5d637a884a8ca7c041c5c654e71fec1c55e28c55 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 02:09:52 +0800 Subject: [PATCH 2187/2647] debug codes Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index ba1047ea..e6b0a0c8 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -393,7 +393,11 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare return err } - secret, err := dynamicClient.Resource(gvr).Namespace(namespace).Get(context.TODO(), "21-registry-secret", metav1.GetOptions{}) + secret, err := dynamicClient.Resource(schema.GroupVersionResource{ + Group: "apps", + Version: "v1", + Resource: "deployments", + }).Namespace(namespace).Get(context.TODO(), "aslan", metav1.GetOptions{}) // if there is an error, either because of not found or anything else, we try to create a secret with the given information if err != nil { secret := &unstructured.Unstructured{ @@ -414,14 +418,16 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare } return err } else { - if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { - log.Errorf("failed to set data in secret object, the error is: %s", err) - return err - } - _, err := dynamicClient.Resource(gvr).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) - } - return err + //if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { + // log.Errorf("failed to set data in secret object, the error is: %s", err) + // return err + //} + //_, err := dynamicClient.Resource(gvr).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + //if err != nil { + // log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) + //} + //return err + log.Infof("got deployment: %+v", secret) + return nil } } -- Gitee From a516124bbdcd25040590ca5e3c1dc910001baf85 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 02:58:07 +0800 Subject: [PATCH 2188/2647] final debug Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index e6b0a0c8..3c1f6f90 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -23,8 +23,6 @@ import ( "fmt" "github.com/koderover/zadig/pkg/microservice/aslan/config" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -383,9 +381,10 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare "cert.crt": certificateString, } - secretType := &corev1.Secret{} - - gvr, _ := meta.UnsafeGuessKindToResource(secretType.GroupVersionKind()) + secretsGVR := schema.GroupVersionResource{ + Version: "v1", + Resource: "secrets", + } dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) if err != nil { @@ -393,11 +392,7 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare return err } - secret, err := dynamicClient.Resource(schema.GroupVersionResource{ - Group: "apps", - Version: "v1", - Resource: "deployments", - }).Namespace(namespace).Get(context.TODO(), "aslan", metav1.GetOptions{}) + secret, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) // if there is an error, either because of not found or anything else, we try to create a secret with the given information if err != nil { secret := &unstructured.Unstructured{ @@ -412,22 +407,20 @@ func ensureCertificateSecret(secretName, namespace, cert string, log *zap.Sugare }, } - _, err := dynamicClient.Resource(gvr).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) if err != nil { log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) } return err } else { - //if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { - // log.Errorf("failed to set data in secret object, the error is: %s", err) - // return err - //} - //_, err := dynamicClient.Resource(gvr).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - //if err != nil { - // log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) - //} - //return err - log.Infof("got deployment: %+v", secret) - return nil + if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { + log.Errorf("failed to set data in secret object, the error is: %s", err) + return err + } + _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) + } + return err } } -- Gitee From 3ccd662316f96121f228bca80e890a8986b7e3c0 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:08:41 +0800 Subject: [PATCH 2189/2647] changed the type of the map to avoid panic Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 3c1f6f90..029c5402 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -377,7 +377,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { func ensureCertificateSecret(secretName, namespace, cert string, log *zap.SugaredLogger) error { certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) - datamap := map[string]string{ + datamap := map[string]interface{}{ "cert.crt": certificateString, } -- Gitee From d345a8074fb66683d44090893d34485d0e90017e Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:18:01 +0800 Subject: [PATCH 2190/2647] more debug stuffs Signed-off-by: Min Min --- .../aslan/core/system/service/registry.go | 56 +++++++++---------- 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 029c5402..7b1f3184 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -26,7 +26,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/util/retry" "strings" "go.uber.org/zap" @@ -288,7 +287,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { return err } - stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulset"} + stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"} dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) if err != nil { @@ -341,38 +340,33 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { } } - retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error { - // Retrieve the latest version of Deployment before attempting update - // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Get(context.TODO(), "dind", metav1.GetOptions{}) - if getErr != nil { - return err - } - - // extract spec containers - containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") - if err != nil || !found || containers == nil { - return err - } - - if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { - return err - } - if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { - return err - } - if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { - return err - } - _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) - return updateErr - }) + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver + result, getErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Get(context.TODO(), "dind", metav1.GetOptions{}) + if getErr != nil { + log.Errorf("failed to get dind statefulset, the error is: %s", getErr) + return getErr + } - if retryErr != nil { - log.Errorf("failed to update dind, the error is: %s", retryErr) + // extract spec containers + containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") + if err != nil || !found || containers == nil { + return err } - return retryErr + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { + return err + } + _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) + if updateErr != nil { + log.Errorf("failed to update dind, the error is: %s", updateErr) + } + return updateErr } func ensureCertificateSecret(secretName, namespace, cert string, log *zap.SugaredLogger) error { -- Gitee From b64d1bf103f80254fcef9645ac912d9945dca589 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:26:34 +0800 Subject: [PATCH 2191/2647] even more failed map structure changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 7b1f3184..b91468e4 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -295,7 +295,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { return err } - volumeMountList := make([]map[string]string, 0) + volumeMountList := make([]map[string]interface{}, 0) volumeList := make([]map[string]interface{}, 0) insecureRegistryList := make([]string, 0) @@ -316,7 +316,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { } // create volumeMount info - volumeMountMap := map[string]string{ + volumeMountMap := map[string]interface{}{ "mountPath": "/etc/docker/certs.d", "name": mountName, } -- Gitee From e3ee085bcde8a46fbf6e7f5e718cb7753bbdc3ba Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:31:48 +0800 Subject: [PATCH 2192/2647] even more weird structure error Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index b91468e4..1207d9f8 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -295,8 +295,8 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { return err } - volumeMountList := make([]map[string]interface{}, 0) - volumeList := make([]map[string]interface{}, 0) + volumeMountList := make([]interface{}, 0) + volumeList := make([]interface{}, 0) insecureRegistryList := make([]string, 0) for _, reg := range regList { -- Gitee From 39b6ddf0c3bf47b9d272141220d53fc652a1807d Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:33:08 +0800 Subject: [PATCH 2193/2647] man this map is fucked Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 1207d9f8..198f3c44 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -322,8 +322,8 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { } volumeMountList = append(volumeMountList, volumeMountMap) // create volume info - secretItemList := make([]map[string]string, 0) - secretItemList = append(secretItemList, map[string]string{ + secretItemList := make([]map[string]interface{}, 0) + secretItemList = append(secretItemList, map[string]interface{}{ "key": "cert.crt", "path": fmt.Sprintf("%s/%s", reg.RegAddr, "cert.crt"), }) -- Gitee From 1fccaaaf1b73da96821843cb75c1a99536de26f8 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:37:14 +0800 Subject: [PATCH 2194/2647] bugfix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 198f3c44..5913085c 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -322,7 +322,7 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { } volumeMountList = append(volumeMountList, volumeMountMap) // create volume info - secretItemList := make([]map[string]interface{}, 0) + secretItemList := make([]interface{}, 0) secretItemList = append(secretItemList, map[string]interface{}{ "key": "cert.crt", "path": fmt.Sprintf("%s/%s", reg.RegAddr, "cert.crt"), -- Gitee From b6c9ad1b61cde90763a386663ac1ce09a1b7f31b Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:42:29 +0800 Subject: [PATCH 2195/2647] bugfix Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 5913085c..8395c8ce 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -323,9 +323,10 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { volumeMountList = append(volumeMountList, volumeMountMap) // create volume info secretItemList := make([]interface{}, 0) + addr := strings.Split(reg.RegAddr, "//") secretItemList = append(secretItemList, map[string]interface{}{ "key": "cert.crt", - "path": fmt.Sprintf("%s/%s", reg.RegAddr, "cert.crt"), + "path": fmt.Sprintf("%s/%s", addr[1], "cert.crt"), }) secretInfo := map[string]interface{}{ "items": secretItemList, -- Gitee From 7ac6540b9ed9b78bd120ce7b9bd63115f16c8e5c Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 03:57:08 +0800 Subject: [PATCH 2196/2647] attempt to fix the update dind problem Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 8395c8ce..7bf137bf 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -315,18 +315,17 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { return err } + addr := strings.Split(reg.RegAddr, "//") // create volumeMount info volumeMountMap := map[string]interface{}{ - "mountPath": "/etc/docker/certs.d", + "mountPath": fmt.Sprintf("/etc/docker/certs.d/%s", addr[1]), "name": mountName, } volumeMountList = append(volumeMountList, volumeMountMap) // create volume info secretItemList := make([]interface{}, 0) - addr := strings.Split(reg.RegAddr, "//") secretItemList = append(secretItemList, map[string]interface{}{ - "key": "cert.crt", - "path": fmt.Sprintf("%s/%s", addr[1], "cert.crt"), + "key": "cert.crt", }) secretInfo := map[string]interface{}{ "items": secretItemList, -- Gitee From 2e8c85bc875b863c743938036a6d9509f8a8e216 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 04:02:30 +0800 Subject: [PATCH 2197/2647] debug Signed-off-by: Min Min --- pkg/microservice/aslan/core/system/service/registry.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 7bf137bf..99959799 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -325,7 +325,8 @@ func SyncDinDForRegistries(log *zap.SugaredLogger) error { // create volume info secretItemList := make([]interface{}, 0) secretItemList = append(secretItemList, map[string]interface{}{ - "key": "cert.crt", + "key": "cert.crt", + "path": "cert.crt", }) secretInfo := map[string]interface{}{ "items": secretItemList, -- Gitee From 9cce250ddd25cf3b9c7a60d5260a178bdae3f564 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 04:33:26 +0800 Subject: [PATCH 2198/2647] hubagent related startup logic implemented Signed-off-by: Min Min --- pkg/microservice/aslan/core/service.go | 8 ++ pkg/microservice/hubagent/server/server.go | 150 +++++++++++++++++++++ pkg/shared/client/aslan/registries.go | 15 +++ pkg/shared/client/aslan/types.go | 17 +++ 4 files changed, 190 insertions(+) create mode 100644 pkg/shared/client/aslan/registries.go diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index cd9520b0..b5b8e2f2 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -111,6 +111,7 @@ func Start(ctx context.Context) { initDatabase() initService() + initDinD() systemservice.SetProxyConfig() @@ -149,6 +150,13 @@ func initService() { } } +func initDinD() { + err := systemservice.SyncDinDForRegistries(log.SugaredLogger()) + if err != nil { + log.Fatal(err) + } +} + func initDatabase() { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index f59b44d9..b590dac9 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -18,7 +18,17 @@ package server import ( "context" + "encoding/base64" + "fmt" + config2 "github.com/koderover/zadig/pkg/microservice/hubagent/config" + "github.com/koderover/zadig/pkg/shared/client/aslan" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "net/http" + "strings" "time" "github.com/koderover/zadig/pkg/config" @@ -43,6 +53,8 @@ func Serve(ctx context.Context) error { engine := rest.NewEngine() server := &http.Server{Addr: ":80", Handler: engine} + initDinD() + go func() { <-ctx.Done() @@ -68,3 +80,141 @@ func Serve(ctx context.Context) error { return nil } + +func initDinD() { + client := aslan.New(config2.HubServerBaseAddr()) + + ls, err := client.ListRegistries() + if err != nil { + log.Fatalf("failed to get information from zadig server to set DinD, err: %s", err) + } + + stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"} + + dynamicClient, err := kubeclient.GetDynamicKubeClient(config2.HubServerBaseAddr(), setting.LocalClusterID) + if err != nil { + log.Fatalf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) + } + + volumeMountList := make([]interface{}, 0) + volumeList := make([]interface{}, 0) + + insecureRegistryList := make([]string, 0) + for _, reg := range ls { + // compatibility changes before 1.11 + if reg.AdvancedSetting != nil { + // if a registry is marked as insecure, we add a record to insecure-registries + if !reg.AdvancedSetting.TLSEnabled { + insecureRegistryList = append(insecureRegistryList, reg.RegAddr) + } + // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon + if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { + mountName := fmt.Sprintf("%s-cert", reg.ID.Hex()) + err := ensureCertificateSecret(mountName, "koderover-agent", reg.AdvancedSetting.TLSCert, log.SugaredLogger()) + if err != nil { + log.Fatalf("failed to ensure secret: %s, the error is: %s", mountName, err) + } + + addr := strings.Split(reg.RegAddr, "//") + // create volumeMount info + volumeMountMap := map[string]interface{}{ + "mountPath": fmt.Sprintf("/etc/docker/certs.d/%s", addr[1]), + "name": mountName, + } + volumeMountList = append(volumeMountList, volumeMountMap) + // create volume info + secretItemList := make([]interface{}, 0) + secretItemList = append(secretItemList, map[string]interface{}{ + "key": "cert.crt", + "path": "cert.crt", + }) + secretInfo := map[string]interface{}{ + "items": secretItemList, + "secretName": mountName, + } + volumeMap := map[string]interface{}{ + "name": mountName, + "secret": secretInfo, + } + volumeList = append(volumeList, volumeMap) + } + } + } + + // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver + result, getErr := dynamicClient.Resource(stsResource).Namespace("koderover-agent").Get(context.TODO(), "dind", metav1.GetOptions{}) + if getErr != nil { + log.Fatalf("failed to get dind statefulset, the error is: %s", getErr) + } + + // extract spec containers + containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") + if err != nil || !found || containers == nil { + log.Fatal(err) + } + + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { + log.Fatal(err) + } + if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { + log.Fatal(err) + } + if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { + log.Fatal(err) + } + _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) + if updateErr != nil { + log.Errorf("failed to update dind, the error is: %s", updateErr) + } + log.Fatal(updateErr) +} + +func ensureCertificateSecret(secretName, namespace, cert string, log *zap.SugaredLogger) error { + certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) + datamap := map[string]interface{}{ + "cert.crt": certificateString, + } + + secretsGVR := schema.GroupVersionResource{ + Version: "v1", + Resource: "secrets", + } + + dynamicClient, err := kubeclient.GetDynamicKubeClient(config2.HubServerBaseAddr(), setting.LocalClusterID) + if err != nil { + log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) + return err + } + + secret, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + // if there is an error, either because of not found or anything else, we try to create a secret with the given information + if err != nil { + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": secretName, + }, + "type": "Opaque", + "data": datamap, + }, + } + + _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) + } + return err + } else { + if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { + log.Errorf("failed to set data in secret object, the error is: %s", err) + return err + } + _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) + } + return err + } +} diff --git a/pkg/shared/client/aslan/registries.go b/pkg/shared/client/aslan/registries.go new file mode 100644 index 00000000..8271a20b --- /dev/null +++ b/pkg/shared/client/aslan/registries.go @@ -0,0 +1,15 @@ +package aslan + +import "github.com/koderover/zadig/pkg/tool/httpclient" + +func (c *Client) ListRegistries() ([]*RegistryInfo, error) { + url := "/system/registry" + res := make([]*RegistryInfo, 0) + + _, err := c.Get(url, httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/pkg/shared/client/aslan/types.go b/pkg/shared/client/aslan/types.go index a3bcdc3c..47d04107 100644 --- a/pkg/shared/client/aslan/types.go +++ b/pkg/shared/client/aslan/types.go @@ -16,6 +16,8 @@ limitations under the License. package aslan +import "go.mongodb.org/mongo-driver/bson/primitive" + type Environment struct { ID string `json:"id"` ProductName string `json:"product_name"` @@ -111,3 +113,18 @@ type ServiceStatus struct { ServiceName string `json:"service_name"` Status string `json:"status"` } + +type RegistryInfo struct { + ID primitive.ObjectID `json:"id,omitempty"` + RegAddr string `json:"reg_addr"` + AdvancedSetting *RegistryAdvancedSetting `json:"advanced_setting"` +} + +type RegistryAdvancedSetting struct { + // indicator to make sure if the advanced has been modified + Modified bool `json:"modified"` + // New field since v1.11 to support self-signed TLS certificate + TLSEnabled bool `json:"enable_tls"` + TLSCert string `json:"tls_cert"` + TLSKey string `json:"tls_key"` +} -- Gitee From 0f23376f83e0660afb7858ec130231f8d32430bf Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 04:36:18 +0800 Subject: [PATCH 2199/2647] removed unused WRONG map Signed-off-by: Min Min --- pkg/tool/kube/getter/base.go | 38 ------------------------------------ 1 file changed, 38 deletions(-) diff --git a/pkg/tool/kube/getter/base.go b/pkg/tool/kube/getter/base.go index e51caf80..d40e5a1a 100644 --- a/pkg/tool/kube/getter/base.go +++ b/pkg/tool/kube/getter/base.go @@ -29,44 +29,6 @@ import ( "sigs.k8s.io/yaml" ) -var ResourceKindMap = map[string]schema.GroupVersionResource{ - "Deployment": { - Group: "apps", - Version: "v1", - Resource: "deployments", - }, - "ConfigMap": { - Group: "apps", - Version: "v1", - Resource: "configmaps", - }, - "Job": { - Group: "batch", - Version: "v1", - Resource: "jobs", - }, - "Secret": { - Group: "core", - Version: "v1", - Resource: "secrets", - }, - "Service": { - Group: "api", - Version: "v1", - Resource: "services", - }, - "StatefulSet": { - Group: "apps", - Version: "v1", - Resource: "statefulsets", - }, - "PersistentVolume": { - Group: "api", - Version: "v1", - Resource: "persistentvolumes", - }, -} - // GetResourceInCache gets a specific Kubernetes object in local cache, object can be any types // which are registered by "scheme.AddToScheme()". // Return true if object is found, false if not, or an error if something bad happened. -- Gitee From 7c7566a725cb4f5759dec9577cc088f720e30828 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 15 Apr 2022 10:38:58 +0800 Subject: [PATCH 2200/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/service/helm.go | 2 ++ .../aslan/core/common/service/registry.go | 3 +++ .../aslan/core/common/service/rsa_key.go | 15 +++++++-------- .../aslan/core/system/service/jenkins.go | 2 ++ pkg/microservice/aslan/core/system/service/s3.go | 2 ++ .../core/codehost/service/codehost.go | 10 +++++++--- .../core/connector/service/connector.go | 6 ++++-- .../systemconfig/core/email/service/email.go | 5 +++-- .../systemconfig/core/jira/service/jira.go | 5 ++++- 9 files changed, 34 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index ce1fd8b4..7ef0ba04 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -40,6 +40,7 @@ import ( func ListHelmRepos(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels.HelmRepo, error) { aesKey, err := GetAesKeyFromEncryptedKey(encryptedKey, log) if err != nil { + log.Errorf("ListHelmRepos GetAesKeyFromEncryptedKey err:%v", err) return nil, err } helmRepos, err := commonrepo.NewHelmRepoColl().List() @@ -50,6 +51,7 @@ func ListHelmRepos(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels for _, helmRepo := range helmRepos { helmRepo.Password, err = crypto.AesEncryptByKey(helmRepo.Password, aesKey.PlainText) if err != nil { + log.Errorf("ListHelmRepos AesEncryptByKey err:%v", err) return nil, err } } diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index 606663a9..dffcd9a5 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -108,6 +108,7 @@ func ListRegistryNamespaces(encryptedKey string, getRealCredential bool, log *za if len(encryptedKey) > 0 { aesKey, err = GetAesKeyFromEncryptedKey(encryptedKey, log) if err != nil { + log.Errorf("RegistryNamespace.List GetAesKeyFromEncryptedKey error: %s", err) return nil, err } } @@ -116,6 +117,7 @@ func ListRegistryNamespaces(encryptedKey string, getRealCredential bool, log *za for _, reg := range resp { reg.SecretKey, err = crypto.AesEncryptByKey(reg.SecretKey, aesKey.PlainText) if err != nil { + log.Errorf("RegistryNamespace.List AesEncryptByKey error: %s", err) return nil, err } } @@ -142,6 +144,7 @@ func ListRegistryNamespaces(encryptedKey string, getRealCredential bool, log *za } reg.SecretKey, err = crypto.AesEncryptByKey(reg.SecretKey, aesKey.PlainText) if err != nil { + log.Errorf("RegistryNamespace.List AesEncryptByKey error: %s", err) return nil, err } } diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index 6019d1ad..d78be3c9 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -32,8 +32,7 @@ import ( kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" ) -func GetRSAKey(log *zap.SugaredLogger) ([]byte, []byte, error) { - log.Infof("start get rsa key") +func GetRSAKey() ([]byte, []byte, error) { clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), setting.LocalClusterID) if err != nil { return nil, nil, err @@ -49,8 +48,8 @@ type GetRSAPublicKeyRes struct { PublicKey string `json:"publicKey"` } -func GetRSAPublicKey(log *zap.SugaredLogger) (*GetRSAPublicKeyRes, error) { - publicKey, _, err := GetRSAKey(log) +func GetRSAPublicKey() (*GetRSAPublicKeyRes, error) { + publicKey, _, err := GetRSAKey() if err != nil { return nil, err } @@ -64,7 +63,7 @@ type GetAesKeyFromEncryptedKeyResp struct { } func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*GetAesKeyFromEncryptedKeyResp, error) { - _, privateKey, err := GetRSAKey(log) + _, privateKey, err := GetRSAKey() if err != nil { log.Errorf("getAesKeyFromEncryptedKey getRSAKey error msg:%s", err) return nil, err @@ -74,19 +73,19 @@ func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*Ge return nil, err } decodedKey = strings.ReplaceAll(decodedKey, " ", "+") - log.Infof("decodedKey:%s", decodedKey) + byteKey, err := base64.StdEncoding.DecodeString(decodedKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) return nil, err } - log.Infof("byteKey:%s", byteKey) + aesKey, err := rsa.DecryptByPrivateKey(byteKey, privateKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decryptByPrivateKey error msg:%s", err) return nil, err } - log.Infof("aesKey:%s", aesKey) + return &GetAesKeyFromEncryptedKeyResp{ PlainText: string(aesKey), }, nil diff --git a/pkg/microservice/aslan/core/system/service/jenkins.go b/pkg/microservice/aslan/core/system/service/jenkins.go index ad60e63a..50d4da5a 100644 --- a/pkg/microservice/aslan/core/system/service/jenkins.go +++ b/pkg/microservice/aslan/core/system/service/jenkins.go @@ -65,11 +65,13 @@ func ListJenkinsIntegration(encryptedKey string, log *zap.SugaredLogger) ([]*com aesKey, err := service.GetAesKeyFromEncryptedKey(encryptedKey, log) if err != nil { + log.Errorf("ListJenkinsIntegration GetAesKeyFromEncryptedKey err:%v", err) return nil, err } for _, integration := range jenkinsIntegrations { integration.Password, err = crypto.AesEncryptByKey(integration.Password, aesKey.PlainText) if err != nil { + log.Errorf("ListJenkinsIntegration AesEncryptByKey err:%v", err) return nil, err } } diff --git a/pkg/microservice/aslan/core/system/service/s3.go b/pkg/microservice/aslan/core/system/service/s3.go index fb8d910c..cccd90f9 100644 --- a/pkg/microservice/aslan/core/system/service/s3.go +++ b/pkg/microservice/aslan/core/system/service/s3.go @@ -82,11 +82,13 @@ func ListS3Storage(encryptedKey string, logger *zap.SugaredLogger) ([]*commonmod } aesKey, err := service.GetAesKeyFromEncryptedKey(encryptedKey, logger) if err != nil { + logger.Errorf("ListS3Storage GetAesKeyFromEncryptedKey err:%s", err) return nil, err } for _, store := range stores { store.Sk, err = crypto.AesEncryptByKey(store.Sk, aesKey.PlainText) if err != nil { + logger.Errorf("ListS3Storage AesEncryptByKey err:%s", err) return nil, err } } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 46c164eb..f62a61d6 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -59,9 +59,10 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co return mongodb.NewCodehostColl().AddCodeHost(codehost) } -func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost) ([]*models.CodeHost, error) { +func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost, log *zap.SugaredLogger) ([]*models.CodeHost, error) { aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { + log.Errorf("ListCodeHost GetTextFromEncryptedKey error:%s", err) return nil, err } var result []*models.CodeHost @@ -69,12 +70,14 @@ func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost) ([]*mode if len(codeHost.Password) > 0 { codeHost.Password, err = crypto.AesEncryptByKey(codeHost.Password, aesKey.PlainText) if err != nil { + log.Errorf("ListCodeHost AesEncryptByKey error:%s", err) return nil, err } } if len(codeHost.ClientSecret) > 0 { codeHost.ClientSecret, err = crypto.AesEncryptByKey(codeHost.ClientSecret, aesKey.PlainText) if err != nil { + log.Errorf("ListCodeHost AesEncryptByKey error:%s", err) return nil, err } } @@ -83,16 +86,17 @@ func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost) ([]*mode return result, nil } -func List(encryptedKey, address, owner, source string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { +func List(encryptedKey, address, owner, source string, log *zap.SugaredLogger) ([]*models.CodeHost, error) { codeHosts, err := mongodb.NewCodehostColl().List(&mongodb.ListArgs{ Address: address, Owner: owner, Source: source, }) if err != nil { + log.Errorf("ListCodeHost error:%s", err) return nil, err } - return encypteCodeHost(encryptedKey, codeHosts) + return encypteCodeHost(encryptedKey, codeHosts, log) } func DeleteCodeHost(id int, _ *zap.SugaredLogger) error { diff --git a/pkg/microservice/systemconfig/core/connector/service/connector.go b/pkg/microservice/systemconfig/core/connector/service/connector.go index 27b2b6c9..838087e0 100644 --- a/pkg/microservice/systemconfig/core/connector/service/connector.go +++ b/pkg/microservice/systemconfig/core/connector/service/connector.go @@ -31,6 +31,7 @@ import ( func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connector, error) { aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { + logger.Errorf("ListConnectors GetTextFromEncryptedKey, err: %s", err) return nil, err } cs, err := orm.NewConnectorColl().List() @@ -42,21 +43,22 @@ func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connecto var res []*Connector for _, c := range cs { cf := make(map[string]interface{}) - logger.Infof("config:%v", c.Config) err = json.Unmarshal([]byte(c.Config), &cf) if err != nil { - logger.Warnf("Failed to unmarshal config, err: %s", err) + logger.Errorf("Failed to unmarshal config, err: %s", err) continue } if pw, ok := cf["bindPW"]; ok { cf["bindPW"], err = crypto.AesEncryptByKey(pw.(string), aesKey.PlainText) if err != nil { + logger.Errorf("ListConnectors AesEncryptByKey, err: %s", err) return nil, err } } if clientSecret, ok := cf["clientSecret"]; ok { cf["clientSecret"], err = crypto.AesEncryptByKey(clientSecret.(string), aesKey.PlainText) if err != nil { + logger.Errorf("ListConnectors AesEncryptByKey, err: %s", err) return nil, err } } diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index ac8629c1..62e6a4ac 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -39,19 +39,20 @@ func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { func InternalGetEmailHost(encryptedKey string, log *zap.SugaredLogger) (*models.EmailHost, error) { aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { + log.Errorf("InternalGetEmailHost GetTextFromEncryptedKey error:%s", err) return nil, err } result, err := mongodb.NewEmailHostColl().Find() if err != nil { + log.Errorf("InternalGetEmailHost find email host error:%s", err) return nil, err } password, err := crypto.AesEncryptByKey(result.Password, aesKey.PlainText) if err != nil { + log.Errorf("InternalGetEmailHost AesEncryptByKey error:%s", err) return nil, err } - log.Infof("pre password:%s", result.Password) result.Password = password - log.Infof("password:%s", password) return result, nil } diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 77b6c65f..0e3b525c 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -28,9 +28,10 @@ import ( "github.com/koderover/zadig/pkg/tool/crypto" ) -func GeJira(encryptedKey string, _ *zap.SugaredLogger) (*models.Jira, error) { +func GeJira(encryptedKey string, log *zap.SugaredLogger) (*models.Jira, error) { jira, err := mongodb.NewJiraColl().GetJira() if err != nil { + log.Errorf("GeJira error:%s", err) return nil, err } if jira == nil { @@ -38,10 +39,12 @@ func GeJira(encryptedKey string, _ *zap.SugaredLogger) (*models.Jira, error) { } aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { + log.Errorf("GeJira GetTextFromEncryptedKey erorr:%s", err) return nil, err } jira.AccessToken, err = crypto.AesEncryptByKey(jira.AccessToken, aesKey.PlainText) if err != nil { + log.Errorf("GeJira AesEncryptByKey erorr:%s", err) return nil, err } return jira, nil -- Gitee From c99e1e03323a334e2de770bb967dcd8edbc5c751 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 15 Apr 2022 11:22:30 +0800 Subject: [PATCH 2201/2647] fix vairable replace error Signed-off-by: allenshen --- pkg/util/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/util/helm.go b/pkg/util/helm.go index 04ef5f63..6cdf345a 100644 --- a/pkg/util/helm.go +++ b/pkg/util/helm.go @@ -23,7 +23,7 @@ import ( func GeneReleaseName(namingRule, projectName, namespace, envName, service string) string { ret := strings.ReplaceAll(namingRule, "$Product$", projectName) ret = strings.ReplaceAll(ret, "$Namespace$", namespace) - ret = strings.ReplaceAll(ret, "$EnvName", envName) + ret = strings.ReplaceAll(ret, "$EnvName$", envName) ret = strings.ReplaceAll(ret, "$Service$", service) return ret } -- Gitee From 0aa3f61e6fc9b69542bb024cc4b34fed39d245c0 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 15 Apr 2022 12:22:31 +0800 Subject: [PATCH 2202/2647] fix wrong image name Signed-off-by: allenshen --- .../service/workflow/pipeline_validation.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 4cddc215..122e92a0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -598,8 +598,6 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageNa } return newTarRule default: - // If image, replace service name with imageName - candidate.ServiceName = imageName return replaceVariable(customImageRule, candidate) } } @@ -646,8 +644,16 @@ func replaceVariable(customRule *template.CustomRule, candidate *candidate) stri } currentRule = commonservice.ReplaceRuleVariable(currentRule, &commonservice.Variable{ - candidate.ServiceName, candidate.ServiceName, candidate.Timestamp, strconv.FormatInt(candidate.TaskID, 10), candidate.CommitID, candidate.ProductName, candidate.EnvName, - candidate.Tag, candidate.Branch, strconv.Itoa(candidate.PR), + SERVICE: candidate.ServiceName, + IMAGE_NAME: candidate.ServiceName, + TIMESTAMP: candidate.Timestamp, + TASK_ID: strconv.FormatInt(candidate.TaskID, 10), + REPO_COMMIT_ID: candidate.CommitID, + PROJECT: candidate.ProductName, + ENV_NAME: candidate.EnvName, + REPO_TAG: candidate.Tag, + REPO_BRANCH: candidate.Branch, + REPO_PR: strconv.Itoa(candidate.PR), }) return currentRule } -- Gitee From 8840cbd9977677bb8382c70a114d07692b924910 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 15 Apr 2022 13:44:07 +0800 Subject: [PATCH 2203/2647] fix image name bug Signed-off-by: allenshen --- .../core/workflow/service/workflow/pipeline_validation.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 122e92a0..897505ac 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -589,6 +589,7 @@ func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageNa TaskID: taskID, ProductName: productName, ServiceName: b.ServiceName, + ImageName: imageName, } switch deliveryType { case config.TarResourceType: @@ -611,6 +612,7 @@ type candidate struct { Timestamp string ProductName string ServiceName string + ImageName string EnvName string } @@ -645,7 +647,7 @@ func replaceVariable(customRule *template.CustomRule, candidate *candidate) stri currentRule = commonservice.ReplaceRuleVariable(currentRule, &commonservice.Variable{ SERVICE: candidate.ServiceName, - IMAGE_NAME: candidate.ServiceName, + IMAGE_NAME: candidate.ImageName, TIMESTAMP: candidate.Timestamp, TASK_ID: strconv.FormatInt(candidate.TaskID, 10), REPO_COMMIT_ID: candidate.CommitID, -- Gitee From e1bf29fd4b4023ba9b45509a9c1e97dbe8c6d364 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 15 Apr 2022 11:41:00 +0800 Subject: [PATCH 2204/2647] Added the interface permission for environment configuration Signed-off-by: ddh27 --- .../environment/handler/common_env_cfg.go | 13 ++--- .../core/environment/handler/configmap.go | 2 +- .../aslan/core/environment/handler/ingress.go | 2 +- .../core/environment/handler/policy.yaml | 56 +++++++++++++++++++ .../aslan/core/environment/handler/pvc.go | 2 +- .../aslan/core/environment/handler/router.go | 16 +++--- .../aslan/core/environment/handler/secret.go | 2 +- 7 files changed, 72 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index a52c07a9..e2727e6d 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -34,7 +34,7 @@ func DeleteCommonEnvCfg(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") + envName := c.Param("envName") productName := c.Query("projectName") commonEnvCfgType := c.Query("commonEnvCfgType") objectName := c.Param("objectName") @@ -74,6 +74,7 @@ func CreateCommonEnvCfg(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } + args.EnvName = c.Param("envName") ctx.Err = service.CreateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) } @@ -100,13 +101,7 @@ func UpdateCommonEnvCfg(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - - // TODO: REVERT AUTH is disabled - //authed := service.CtrlMgr.IsProductAuthed(ctx.UserIDStr, args.ProductOwner, args.ProductName, collection.ProductWritePermission, ctx.Logger) - //if !authed { - // ctx.Err = e.ErrUpdateConfigMap.AddDesc(e.ProductAccessDeniedErrMsg) - // return - //} + args.EnvName = c.Param("envName") ctx.Err = service.UpdateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) } @@ -116,7 +111,7 @@ func ListCommonEnvCfgHistory(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := new(service.ListCommonEnvCfgHistoryArgs) - args.EnvName = c.Query("envName") + args.EnvName = c.Param("envName") args.ProductName = c.Query("projectName") args.CommonEnvCfgType = config.CommonEnvCfgType(c.Query("commonEnvCfgType")) args.Name = c.Param("objectName") diff --git a/pkg/microservice/aslan/core/environment/handler/configmap.go b/pkg/microservice/aslan/core/environment/handler/configmap.go index d7771910..1142e614 100644 --- a/pkg/microservice/aslan/core/environment/handler/configmap.go +++ b/pkg/microservice/aslan/core/environment/handler/configmap.go @@ -35,7 +35,7 @@ func ListConfigMaps(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := &service.ListConfigMapArgs{ - EnvName: c.Query("envName"), + EnvName: c.Param("envName"), ProductName: c.Query("projectName"), ServiceName: c.Query("serviceName"), } diff --git a/pkg/microservice/aslan/core/environment/handler/ingress.go b/pkg/microservice/aslan/core/environment/handler/ingress.go index e722be1d..3c16cc61 100644 --- a/pkg/microservice/aslan/core/environment/handler/ingress.go +++ b/pkg/microservice/aslan/core/environment/handler/ingress.go @@ -27,7 +27,7 @@ func ListIngresses(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") + envName := c.Param("envName") productName := c.Query("projectName") if envName == "" || productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName is invalid") diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 1467b94f..06f03011 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -78,6 +78,41 @@ rules: value: "false" - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" + - method: GET + endpoint: "/api/aslan/environment/configmaps/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/configmaps/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/envcfgs/?*/cfg/?*" + resourceType: "Environment" + idRegex: "/api/aslan/environment/envcfgs/([\\w\\W]+?)/cfg" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/secrets/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/secrets/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/ingresses/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/ingresses/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" + - method: GET + endpoint: "/api/aslan/environment/pvcs/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/pvcs/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" - action: create_environment alias: "创建" description: "" @@ -154,6 +189,27 @@ rules: matchAttributes: - key: "production" value: "false" + - method: PUT + endpoint: "/api/aslan/environment/envcfgs/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/envcfgs/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" + - method: POST + endpoint: "/api/aslan/environment/envcfgs/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/envcfgs/([\\w\\W]+?)$" + matchAttributes: + - key: "production" + value: "false" + - method: DELETE + endpoint: "/api/aslan/environment/envcfgs/?*/cfg/?*" + resourceType: "Environment" + idRegex: "api/aslan/environment/envcfgs/([\\w\\W]+?)/cfg" + matchAttributes: + - key: "production" + value: "false" - action: manage_environment alias: "管理服务实例" description: "" diff --git a/pkg/microservice/aslan/core/environment/handler/pvc.go b/pkg/microservice/aslan/core/environment/handler/pvc.go index bc5d3392..a3417f7b 100644 --- a/pkg/microservice/aslan/core/environment/handler/pvc.go +++ b/pkg/microservice/aslan/core/environment/handler/pvc.go @@ -27,7 +27,7 @@ func ListPvcs(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") + envName := c.Param("envName") productName := c.Query("projectName") if envName == "" || productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName is invalid") diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index fc1282ad..b11577b8 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -30,29 +30,29 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- configmaps := router.Group("configmaps") { - configmaps.GET("", ListConfigMaps) + configmaps.GET("/:envName", ListConfigMaps) configmaps.POST("", gin2.UpdateOperationLogStatus, RollBackConfigMap) configmaps.GET("/migrate", MigrateHistoryConfigMaps) } secrets := router.Group("secrets") { - secrets.GET("", ListSecrets) + secrets.GET("/:envName", ListSecrets) } ingresses := router.Group("ingresses") { - ingresses.GET("", ListIngresses) + ingresses.GET("/:envName", ListIngresses) } pvcs := router.Group("pvcs") { - pvcs.GET("", ListPvcs) + pvcs.GET("/:envName", ListPvcs) } commonEnvCfgs := router.Group("envcfgs") { - commonEnvCfgs.GET("/:objectName", ListCommonEnvCfgHistory) - commonEnvCfgs.PUT("", gin2.UpdateOperationLogStatus, UpdateCommonEnvCfg) - commonEnvCfgs.POST("", gin2.UpdateOperationLogStatus, CreateCommonEnvCfg) - commonEnvCfgs.DELETE("/:objectName", gin2.UpdateOperationLogStatus, DeleteCommonEnvCfg) + commonEnvCfgs.GET("/:envName/cfg/:objectName", ListCommonEnvCfgHistory) + commonEnvCfgs.PUT("/:envName", gin2.UpdateOperationLogStatus, UpdateCommonEnvCfg) + commonEnvCfgs.POST("/:envName", gin2.UpdateOperationLogStatus, CreateCommonEnvCfg) + commonEnvCfgs.DELETE("/:envName/cfg/:objectName", gin2.UpdateOperationLogStatus, DeleteCommonEnvCfg) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/handler/secret.go b/pkg/microservice/aslan/core/environment/handler/secret.go index 2c980716..3e97633a 100644 --- a/pkg/microservice/aslan/core/environment/handler/secret.go +++ b/pkg/microservice/aslan/core/environment/handler/secret.go @@ -27,7 +27,7 @@ func ListSecrets(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Query("envName") + envName := c.Param("envName") productName := c.Query("projectName") if envName == "" || productName == "" { ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName is invalid") -- Gitee From 14dea470d05e1d716e362d147ce6b3343029c82e Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 15 Apr 2022 14:52:02 +0800 Subject: [PATCH 2205/2647] Added the interface permission for environment configuration Signed-off-by: ddh27 --- .../aslan/core/environment/handler/common_env_cfg.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index e2727e6d..3e0af5ee 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -75,6 +75,7 @@ func CreateCommonEnvCfg(c *gin.Context) { return } args.EnvName = c.Param("envName") + args.ProductName = c.Query("projectName") ctx.Err = service.CreateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) } @@ -102,6 +103,7 @@ func UpdateCommonEnvCfg(c *gin.Context) { return } args.EnvName = c.Param("envName") + args.ProductName = c.Query("projectName") ctx.Err = service.UpdateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) } -- Gitee From 6cadb21b555a51a32d246f25792866b5ccdc80ea Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 15 Apr 2022 15:07:49 +0800 Subject: [PATCH 2206/2647] system permission Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/router.go | 1 + .../policy/core/service/bundle/exemption_urls.go | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index fc1282ad..d8f99e57 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -175,4 +175,5 @@ func (*Router) Inject(router *gin.RouterGroup) { { bundles.GET("", GetBundleResources) } + } diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index c4ef0fad..4afc3859 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -120,6 +120,14 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/aslan/project/products"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/system/rsaKey/publicKey"}, + }, + { + Methods: []string{"POST"}, + Endpoints: []string{"api/aslan/system/cleanCache/cron"}, + }, { Methods: []string{"POST"}, Endpoints: []string{"api/v1/picket/projects"}, -- Gitee From adec8994daf8e5b49cc7721c065b149e003e7240 Mon Sep 17 00:00:00 2001 From: mouuii Date: Fri, 15 Apr 2022 15:11:29 +0800 Subject: [PATCH 2207/2647] system permission Signed-off-by: mouuii --- pkg/microservice/aslan/core/environment/handler/router.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index d8f99e57..fc1282ad 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -175,5 +175,4 @@ func (*Router) Inject(router *gin.RouterGroup) { { bundles.GET("", GetBundleResources) } - } -- Gitee From 5700dec450082127746c18f1d01ad983b8e2d6b9 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 15:52:53 +0800 Subject: [PATCH 2208/2647] probably final functional changes Signed-off-by: Min Min --- .../repository/models/registry_namespace.go | 6 +- pkg/microservice/aslan/core/service.go | 2 +- .../aslan/core/system/handler/registry.go | 23 ++- .../aslan/core/system/handler/resp.go | 18 +- .../aslan/core/system/service/registry.go | 157 +++-------------- pkg/microservice/hubagent/server/server.go | 136 ++------------- .../core/service/bundle/exemption_urls.go | 4 + pkg/tool/registries/dind.go | 162 ++++++++++++++++++ pkg/tool/registries/types.go | 31 ++++ 9 files changed, 259 insertions(+), 280 deletions(-) create mode 100644 pkg/tool/registries/dind.go create mode 100644 pkg/tool/registries/types.go diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index 55ca186f..b502ea30 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -44,11 +44,11 @@ type RegistryNamespace struct { type RegistryAdvancedSetting struct { // indicator to make sure if the advanced has been modified + // This is solely a field for frontend display and not used anywhere else in the backend system Modified bool `bson:"modified" json:"modified"` // New field since v1.11 to support self-signed TLS certificate TLSEnabled bool `bson:"enable_tls" json:"enable_tls"` TLSCert string `bson:"tls_cert" json:"tls_cert"` - TLSKey string `bson:"tls_key" json:"tls_key"` } func (ns *RegistryNamespace) Validate() error { @@ -72,7 +72,3 @@ func (ns *RegistryNamespace) Validate() error { func (RegistryNamespace) TableName() string { return "registry_namespace" } - -//const CandidateRegType = "candidate" -// -//const DistRegType = "dist" diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index b5b8e2f2..cf0a9820 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -151,7 +151,7 @@ func initService() { } func initDinD() { - err := systemservice.SyncDinDForRegistries(log.SugaredLogger()) + err := systemservice.SyncDinDForRegistries() if err != nil { log.Fatal(err) } diff --git a/pkg/microservice/aslan/core/system/handler/registry.go b/pkg/microservice/aslan/core/system/handler/registry.go index 12a4b29a..aaa98498 100644 --- a/pkg/microservice/aslan/core/system/handler/registry.go +++ b/pkg/microservice/aslan/core/system/handler/registry.go @@ -71,15 +71,22 @@ func GetRegistryNamespace(c *gin.Context) { return } - ctx.Resp = &Registry{ - ID: reg.ID.Hex(), - RegAddr: reg.RegAddr, - IsDefault: reg.IsDefault, - Namespace: reg.Namespace, - TLSEnabled: reg.AdvancedSetting.TLSEnabled, - TLSCert: reg.AdvancedSetting.TLSCert, - TLSKey: reg.AdvancedSetting.TLSKey, + resp := &Registry{ + ID: reg.ID.Hex(), + RegAddr: reg.RegAddr, + IsDefault: reg.IsDefault, + Namespace: reg.Namespace, + } + + if reg.AdvancedSetting != nil { + resp.AdvancedSetting = &AdvancedRegistrySetting{ + Modified: reg.AdvancedSetting.Modified, + TLSEnabled: reg.AdvancedSetting.TLSEnabled, + TLSCert: reg.AdvancedSetting.TLSCert, + } } + + ctx.Resp = resp } func ListRegistryNamespaces(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/resp.go b/pkg/microservice/aslan/core/system/handler/resp.go index 31621768..f62ee99a 100644 --- a/pkg/microservice/aslan/core/system/handler/resp.go +++ b/pkg/microservice/aslan/core/system/handler/resp.go @@ -17,13 +17,17 @@ limitations under the License. package handler type Registry struct { - ID string `json:"id"` - RegAddr string `json:"reg_addr"` - IsDefault bool `json:"is_default"` - Namespace string `json:"namespace"` - AccessKey string `json:"access_key"` - SecretKey string `json:"secret_key"` + ID string `json:"id"` + RegAddr string `json:"reg_addr"` + IsDefault bool `json:"is_default"` + Namespace string `json:"namespace"` + AccessKey string `json:"access_key"` + SecretKey string `json:"secret_key"` + AdvancedSetting *AdvancedRegistrySetting `json:"advanced_setting"` +} + +type AdvancedRegistrySetting struct { + Modified bool `json:"modified"` TLSEnabled bool `json:"enable_tls"` TLSCert string `json:"tls_cert"` - TLSKey string `json:"tls_key"` } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index 99959799..4f8b2092 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -17,25 +17,21 @@ limitations under the License. package service import ( - "context" - "encoding/base64" "errors" "fmt" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "strings" "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/registry" "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" + registrytool "github.com/koderover/zadig/pkg/tool/registries" "github.com/koderover/zadig/pkg/util" ) @@ -104,7 +100,7 @@ func CreateRegistryNamespace(username string, args *commonmodels.RegistryNamespa return fmt.Errorf("RegistryNamespace.Create error: %v", err) } - return SyncDinDForRegistries(log) + return SyncDinDForRegistries() } func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNamespace, log *zap.SugaredLogger) error { @@ -136,7 +132,7 @@ func UpdateRegistryNamespace(username, id string, args *commonmodels.RegistryNam log.Errorf("RegistryNamespace.Update error: %v", err) return fmt.Errorf("RegistryNamespace.Update error: %v", err) } - return SyncDinDForRegistries(log) + return SyncDinDForRegistries() } func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { @@ -180,7 +176,7 @@ func DeleteRegistryNamespace(id string, log *zap.SugaredLogger) error { return err } } - return SyncDinDForRegistries(log) + return SyncDinDForRegistries() } func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { @@ -280,142 +276,31 @@ func UpdateRegistryNamespaceDefault(args *commonmodels.RegistryNamespace, log *z return nil } -func SyncDinDForRegistries(log *zap.SugaredLogger) error { - regList, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) - if err != nil { - log.Errorf("failed to list registry to update dind, the error is: %s", err) - return err - } - - stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"} - - dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) +func SyncDinDForRegistries() error { + registries, err := commonrepo.NewRegistryNamespaceColl().FindAll(&commonrepo.FindRegOps{}) if err != nil { - log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) - return err + return fmt.Errorf("failed to list registry to update dind, err: %s", err) } - volumeMountList := make([]interface{}, 0) - volumeList := make([]interface{}, 0) - - insecureRegistryList := make([]string, 0) - for _, reg := range regList { - // compatibility changes before 1.11 + regList := make([]*registrytool.RegistryInfoForDinDUpdate, 0) + for _, reg := range registries { + regItem := ®istrytool.RegistryInfoForDinDUpdate{ + ID: reg.ID, + RegAddr: reg.RegAddr, + } if reg.AdvancedSetting != nil { - // if a registry is marked as insecure, we add a record to insecure-registries - if !reg.AdvancedSetting.TLSEnabled { - insecureRegistryList = append(insecureRegistryList, reg.RegAddr) - } - // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon - if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { - mountName := fmt.Sprintf("%s-cert", reg.ID.Hex()) - err := ensureCertificateSecret(mountName, config.Namespace(), reg.AdvancedSetting.TLSCert, log) - if err != nil { - log.Errorf("failed to ensure secret: %s, the error is: %s", mountName, err) - return err - } - - addr := strings.Split(reg.RegAddr, "//") - // create volumeMount info - volumeMountMap := map[string]interface{}{ - "mountPath": fmt.Sprintf("/etc/docker/certs.d/%s", addr[1]), - "name": mountName, - } - volumeMountList = append(volumeMountList, volumeMountMap) - // create volume info - secretItemList := make([]interface{}, 0) - secretItemList = append(secretItemList, map[string]interface{}{ - "key": "cert.crt", - "path": "cert.crt", - }) - secretInfo := map[string]interface{}{ - "items": secretItemList, - "secretName": mountName, - } - volumeMap := map[string]interface{}{ - "name": mountName, - "secret": secretInfo, - } - volumeList = append(volumeList, volumeMap) + regItem.AdvancedSetting = ®istrytool.RegistryAdvancedSetting{ + TLSEnabled: reg.AdvancedSetting.TLSEnabled, + TLSCert: reg.AdvancedSetting.TLSCert, } } - } - - // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Get(context.TODO(), "dind", metav1.GetOptions{}) - if getErr != nil { - log.Errorf("failed to get dind statefulset, the error is: %s", getErr) - return getErr - } - - // extract spec containers - containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") - if err != nil || !found || containers == nil { - return err - } - - if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { - return err - } - if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { - return err - } - if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { - return err - } - _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) - if updateErr != nil { - log.Errorf("failed to update dind, the error is: %s", updateErr) - } - return updateErr -} - -func ensureCertificateSecret(secretName, namespace, cert string, log *zap.SugaredLogger) error { - certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) - datamap := map[string]interface{}{ - "cert.crt": certificateString, - } - - secretsGVR := schema.GroupVersionResource{ - Version: "v1", - Resource: "secrets", + regList = append(regList, regItem) } dynamicClient, err := kubeclient.GetDynamicKubeClient(config.HubServerAddress(), setting.LocalClusterID) if err != nil { - log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) - return err + return fmt.Errorf("failed to get dynamic client to update dind, err: %s", err) } - secret, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) - // if there is an error, either because of not found or anything else, we try to create a secret with the given information - if err != nil { - secret := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": secretName, - }, - "type": "Opaque", - "data": datamap, - }, - } - - _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) - if err != nil { - log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) - } - return err - } else { - if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { - log.Errorf("failed to set data in secret object, the error is: %s", err) - return err - } - _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) - } - return err - } + return registrytool.PrepareDinD(dynamicClient, config.Namespace(), regList) } diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index b590dac9..c0944631 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -18,17 +18,11 @@ package server import ( "context" - "encoding/base64" - "fmt" config2 "github.com/koderover/zadig/pkg/microservice/hubagent/config" "github.com/koderover/zadig/pkg/shared/client/aslan" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - "go.uber.org/zap" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" + registrytool "github.com/koderover/zadig/pkg/tool/registries" "net/http" - "strings" "time" "github.com/koderover/zadig/pkg/config" @@ -89,132 +83,28 @@ func initDinD() { log.Fatalf("failed to get information from zadig server to set DinD, err: %s", err) } - stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"} - - dynamicClient, err := kubeclient.GetDynamicKubeClient(config2.HubServerBaseAddr(), setting.LocalClusterID) - if err != nil { - log.Fatalf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) - } - - volumeMountList := make([]interface{}, 0) - volumeList := make([]interface{}, 0) - - insecureRegistryList := make([]string, 0) + regList := make([]*registrytool.RegistryInfoForDinDUpdate, 0) for _, reg := range ls { - // compatibility changes before 1.11 + regItem := ®istrytool.RegistryInfoForDinDUpdate{ + ID: reg.ID, + RegAddr: reg.RegAddr, + } if reg.AdvancedSetting != nil { - // if a registry is marked as insecure, we add a record to insecure-registries - if !reg.AdvancedSetting.TLSEnabled { - insecureRegistryList = append(insecureRegistryList, reg.RegAddr) - } - // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon - if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { - mountName := fmt.Sprintf("%s-cert", reg.ID.Hex()) - err := ensureCertificateSecret(mountName, "koderover-agent", reg.AdvancedSetting.TLSCert, log.SugaredLogger()) - if err != nil { - log.Fatalf("failed to ensure secret: %s, the error is: %s", mountName, err) - } - - addr := strings.Split(reg.RegAddr, "//") - // create volumeMount info - volumeMountMap := map[string]interface{}{ - "mountPath": fmt.Sprintf("/etc/docker/certs.d/%s", addr[1]), - "name": mountName, - } - volumeMountList = append(volumeMountList, volumeMountMap) - // create volume info - secretItemList := make([]interface{}, 0) - secretItemList = append(secretItemList, map[string]interface{}{ - "key": "cert.crt", - "path": "cert.crt", - }) - secretInfo := map[string]interface{}{ - "items": secretItemList, - "secretName": mountName, - } - volumeMap := map[string]interface{}{ - "name": mountName, - "secret": secretInfo, - } - volumeList = append(volumeList, volumeMap) + regItem.AdvancedSetting = ®istrytool.RegistryAdvancedSetting{ + TLSEnabled: reg.AdvancedSetting.TLSEnabled, + TLSCert: reg.AdvancedSetting.TLSCert, } } - } - - // RetryOnConflict uses exponential backoff to avoid exhausting the apiserver - result, getErr := dynamicClient.Resource(stsResource).Namespace("koderover-agent").Get(context.TODO(), "dind", metav1.GetOptions{}) - if getErr != nil { - log.Fatalf("failed to get dind statefulset, the error is: %s", getErr) - } - - // extract spec containers - containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") - if err != nil || !found || containers == nil { - log.Fatal(err) - } - - if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { - log.Fatal(err) - } - if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { - log.Fatal(err) - } - if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { - log.Fatal(err) - } - _, updateErr := dynamicClient.Resource(stsResource).Namespace(config.Namespace()).Update(context.TODO(), result, metav1.UpdateOptions{}) - if updateErr != nil { - log.Errorf("failed to update dind, the error is: %s", updateErr) - } - log.Fatal(updateErr) -} - -func ensureCertificateSecret(secretName, namespace, cert string, log *zap.SugaredLogger) error { - certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) - datamap := map[string]interface{}{ - "cert.crt": certificateString, - } - - secretsGVR := schema.GroupVersionResource{ - Version: "v1", - Resource: "secrets", + regList = append(regList, regItem) } dynamicClient, err := kubeclient.GetDynamicKubeClient(config2.HubServerBaseAddr(), setting.LocalClusterID) if err != nil { - log.Errorf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) - return err + log.Fatalf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) } - secret, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) - // if there is an error, either because of not found or anything else, we try to create a secret with the given information + err = registrytool.PrepareDinD(dynamicClient, "koderover-agent", regList) if err != nil { - secret := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": map[string]interface{}{ - "name": secretName, - }, - "type": "Opaque", - "data": datamap, - }, - } - - _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) - if err != nil { - log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) - } - return err - } else { - if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { - log.Errorf("failed to set data in secret object, the error is: %s", err) - return err - } - _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) - } - return err + log.Fatalf("failed to update dind, the error is: %s", err) } } diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index c4ef0fad..01db6ea5 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -44,6 +44,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/hub/connect"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/aslan/system/registry"}, + }, { Methods: []string{"GET"}, Endpoints: []string{"api/aslan/testing/report"}, diff --git a/pkg/tool/registries/dind.go b/pkg/tool/registries/dind.go new file mode 100644 index 00000000..c7a644d8 --- /dev/null +++ b/pkg/tool/registries/dind.go @@ -0,0 +1,162 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registries + +import ( + "context" + "encoding/base64" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "strings" + + "github.com/koderover/zadig/pkg/tool/log" +) + +func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*RegistryInfoForDinDUpdate) error { + // set statefulset GVR + stsResource := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"} + + volumeMountList := make([]interface{}, 0) + volumeList := make([]interface{}, 0) + insecureRegistryList := make([]interface{}, 0) + + for _, reg := range regList { + // compatibility changes before 1.11 + if reg.AdvancedSetting != nil { + // remove the http:// https:// prefix + addr := strings.Split(reg.RegAddr, "//") + // if a registry is marked as insecure, we add a record to insecure-registries + if !reg.AdvancedSetting.TLSEnabled { + insecureRegistryList = append(insecureRegistryList, fmt.Sprintf("--insecure-registry=%s", addr[1])) + } + // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon + if reg.AdvancedSetting.TLSEnabled && reg.AdvancedSetting.TLSCert != "" { + mountName := fmt.Sprintf("%s-cert", reg.ID.Hex()) + err := ensureCertificateSecret(dynamicClient, mountName, namespace, reg.AdvancedSetting.TLSCert) + if err != nil { + log.Errorf("failed to ensure secret: %s, the error is: %s", mountName, err) + return err + } + + // create volumeMount info + volumeMountMap := map[string]interface{}{ + "mountPath": fmt.Sprintf("/etc/docker/certs.d/%s", addr[1]), + "name": mountName, + } + volumeMountList = append(volumeMountList, volumeMountMap) + // create volume info + secretItemList := make([]interface{}, 0) + secretItemList = append(secretItemList, map[string]interface{}{ + "key": "cert.crt", + "path": "cert.crt", + }) + secretInfo := map[string]interface{}{ + "items": secretItemList, + "secretName": mountName, + } + volumeMap := map[string]interface{}{ + "name": mountName, + "secret": secretInfo, + } + volumeList = append(volumeList, volumeMap) + } + } + } + + result, getErr := dynamicClient.Resource(stsResource).Namespace(namespace).Get(context.TODO(), "dind", metav1.GetOptions{}) + if getErr != nil { + log.Errorf("failed to get dind statefulset, the error is: %s", getErr) + return getErr + } + + // extract spec containers + containers, found, err := unstructured.NestedSlice(result.Object, "spec", "template", "spec", "containers") + if err != nil || !found || containers == nil { + return err + } + + // update spec.template.spec.containers[0].volumeMounts + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { + return err + } + + // update spec.template.spec.containers[0].args + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), insecureRegistryList, "args"); err != nil { + return err + } + if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { + return err + } + + if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { + return err + } + _, updateErr := dynamicClient.Resource(stsResource).Namespace(namespace).Update(context.TODO(), result, metav1.UpdateOptions{}) + if updateErr != nil { + log.Errorf("failed to update dind, the error is: %s", updateErr) + } + return updateErr +} + +func ensureCertificateSecret(dynamicClient dynamic.Interface, secretName, namespace, cert string) error { + certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) + datamap := map[string]interface{}{ + "cert.crt": certificateString, + } + + // setup secret GVR for future use + secretsGVR := schema.GroupVersionResource{ + Version: "v1", + Resource: "secrets", + } + + secret, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + // if there is an error, either because of not found or anything else, we try to create a secret with the given information + if err != nil { + secret := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Secret", + "metadata": map[string]interface{}{ + "name": secretName, + }, + "type": "Opaque", + "data": datamap, + }, + } + + _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + if err != nil { + log.Errorf("failed to create secret: %s, the error is: %s", secretName, err) + } + return err + } else { + if err := unstructured.SetNestedField(secret.Object, datamap, "data"); err != nil { + log.Errorf("failed to set data in secret object, the error is: %s", err) + return err + } + _, err := dynamicClient.Resource(secretsGVR).Namespace(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) + if err != nil { + log.Errorf("failed to update secret: %s, the error is: %s", secretName, err) + } + return err + } +} diff --git a/pkg/tool/registries/types.go b/pkg/tool/registries/types.go new file mode 100644 index 00000000..53908947 --- /dev/null +++ b/pkg/tool/registries/types.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package registries + +import "go.mongodb.org/mongo-driver/bson/primitive" + +// RegistryInfoForDinDUpdate is the structure used to update dind, several unused field is omitted to avoid complication +type RegistryInfoForDinDUpdate struct { + ID primitive.ObjectID + RegAddr string + AdvancedSetting *RegistryAdvancedSetting +} + +type RegistryAdvancedSetting struct { + TLSEnabled bool + TLSCert string +} -- Gitee From 115929e7c81ebce4c5b979b78d15ff904749d80a Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 15:56:33 +0800 Subject: [PATCH 2209/2647] self-check code changes Signed-off-by: Min Min --- pkg/tool/kube/client/cluster.go | 2 +- pkg/tool/kube/getter/statefulset.go | 7 ------- pkg/tool/kube/multicluster/service.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/pkg/tool/kube/client/cluster.go b/pkg/tool/kube/client/cluster.go index d0d85c55..bba45635 100644 --- a/pkg/tool/kube/client/cluster.go +++ b/pkg/tool/kube/client/cluster.go @@ -18,11 +18,11 @@ package client import ( "context" - "k8s.io/client-go/dynamic" "sync" "github.com/pkg/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" diff --git a/pkg/tool/kube/getter/statefulset.go b/pkg/tool/kube/getter/statefulset.go index 1fda0a64..7c7bbedc 100644 --- a/pkg/tool/kube/getter/statefulset.go +++ b/pkg/tool/kube/getter/statefulset.go @@ -17,13 +17,10 @@ limitations under the License. package getter import ( - "context" appsv1 "k8s.io/api/apps/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -37,10 +34,6 @@ func GetStatefulSet(ns, name string, cl client.Client) (*appsv1.StatefulSet, boo return ss, found, err } -func GetStatefulSetNew(ns, name string, clientset *kubernetes.Clientset) (*appsv1.StatefulSet, error) { - return clientset.AppsV1().StatefulSets(ns).Get(context.TODO(), name, metav1.GetOptions{}) -} - func ListStatefulSets(ns string, selector labels.Selector, cl client.Client) ([]*appsv1.StatefulSet, error) { ss := &appsv1.StatefulSetList{} err := ListResourceInCache(ns, selector, nil, ss, cl) diff --git a/pkg/tool/kube/multicluster/service.go b/pkg/tool/kube/multicluster/service.go index 649910a6..ff0028b4 100644 --- a/pkg/tool/kube/multicluster/service.go +++ b/pkg/tool/kube/multicluster/service.go @@ -18,12 +18,12 @@ package multicluster import ( "fmt" - "k8s.io/client-go/dynamic" "net/http" "github.com/gin-gonic/gin" "github.com/pkg/errors" "go.uber.org/zap" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd/api" -- Gitee From 4c227cebf601867b762f2257afa1a03c44401070 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 15:59:44 +0800 Subject: [PATCH 2210/2647] more comments Signed-off-by: Min Min --- .../aslan/core/common/repository/models/registry_namespace.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go index b502ea30..388f0b34 100644 --- a/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go +++ b/pkg/microservice/aslan/core/common/repository/models/registry_namespace.go @@ -45,6 +45,7 @@ type RegistryNamespace struct { type RegistryAdvancedSetting struct { // indicator to make sure if the advanced has been modified // This is solely a field for frontend display and not used anywhere else in the backend system + // TODO: whether this field should exist needs a discussion Modified bool `bson:"modified" json:"modified"` // New field since v1.11 to support self-signed TLS certificate TLSEnabled bool `bson:"enable_tls" json:"enable_tls"` -- Gitee From 415ddf02cd70c348da0b78fb54ccc2ee8425189c Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 15 Apr 2022 16:04:25 +0800 Subject: [PATCH 2211/2647] import order fix Signed-off-by: Min Min --- pkg/microservice/hubagent/server/server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index c0944631..810ad080 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -18,18 +18,18 @@ package server import ( "context" - config2 "github.com/koderover/zadig/pkg/microservice/hubagent/config" - "github.com/koderover/zadig/pkg/shared/client/aslan" - kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - registrytool "github.com/koderover/zadig/pkg/tool/registries" "net/http" "time" "github.com/koderover/zadig/pkg/config" + config2 "github.com/koderover/zadig/pkg/microservice/hubagent/config" "github.com/koderover/zadig/pkg/microservice/hubagent/core/service" "github.com/koderover/zadig/pkg/microservice/hubagent/server/rest" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/aslan" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" + registrytool "github.com/koderover/zadig/pkg/tool/registries" ) func init() { -- Gitee From babb7f11fbcda1ad72ee14c2361a920d5229e476 Mon Sep 17 00:00:00 2001 From: panxunying Date: Fri, 15 Apr 2022 17:05:53 +0800 Subject: [PATCH 2212/2647] optimize Signed-off-by: panxunying --- pkg/microservice/aslan/core/system/handler/rsa_key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/handler/rsa_key.go b/pkg/microservice/aslan/core/system/handler/rsa_key.go index abfb20f9..74b9f01d 100644 --- a/pkg/microservice/aslan/core/system/handler/rsa_key.go +++ b/pkg/microservice/aslan/core/system/handler/rsa_key.go @@ -27,7 +27,7 @@ import ( func GetRSAPublicKey(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetRSAPublicKey(ctx.Logger) + ctx.Resp, ctx.Err = service.GetRSAPublicKey() } func GetTextFromEncryptedKey(c *gin.Context) { -- Gitee From 8138593121bc02d598ed2b2c8ed5d53f52a32d63 Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 15 Apr 2022 17:10:30 +0800 Subject: [PATCH 2213/2647] revolve comments Signed-off-by: allenshen --- .../core/environment/handler/environment.go | 4 +- .../core/environment/service/environment.go | 89 ++++++++++++------- 2 files changed, 59 insertions(+), 34 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index aaeaab96..f66e1efe 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -447,7 +447,7 @@ func updateMultiHelmEnv(c *gin.Context, ctx *internalhandler.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境", strings.Join(args.EnvNames, ","), string(data), ctx.Logger) ctx.Resp, ctx.Err = service.UpdateMultipleHelmEnv( - ctx.RequestID, args, ctx.Logger, + ctx.RequestID, ctx.UserName, args, ctx.Logger, ) } @@ -553,7 +553,7 @@ func DeleteProductServices(c *gin.Context) { } internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境的服务", envName, "", ctx.Logger) - ctx.Err = service.DeleteProductServices(envName, projectName, args.ServiceNames, ctx.Logger) + ctx.Err = service.DeleteProductServices(ctx.UserName, ctx.RequestID, envName, projectName, args.ServiceNames, ctx.Logger) } func ListGroups(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index d1d617a2..d3179d8a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -75,9 +75,7 @@ import ( ) const ( - Timeout = 60 - UpdateTypeSystem = "systemVar" - UpdateTypeEnv = "envVar" + Timeout = 60 ) const ( @@ -1205,6 +1203,14 @@ func UpdateProductRecycleDay(envName, productName string, recycleDay int) error return commonrepo.NewProductColl().UpdateProductRecycleDay(envName, productName, recycleDay) } +func buildContainerMap(cs []*models.Container) map[string]*models.Container { + containerMap := make(map[string]*models.Container) + for _, c := range cs { + containerMap[c.Name] = c + } + return containerMap +} + func UpdateHelmProduct(productName, envName, username, requestID string, overrideCharts []*commonservice.RenderChartArg, deletedServices []string, log *zap.SugaredLogger) error { opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productResp, err := commonrepo.NewProductColl().Find(opt) @@ -1214,7 +1220,7 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid } currentProductService := productResp.Services // 查找产品模板 - updateProd, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) + templateProd, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) if err != nil { log.Errorf("[%s][P:%s] GetProductTemplate error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) @@ -1229,25 +1235,27 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid // use service definition from service template, but keep the image info productServiceMap := productResp.GetServiceMap() allServices := make([][]*commonmodels.ProductService, 0) - for _, svrs := range updateProd.Services { + for _, svrs := range templateProd.Services { svcGroup := make([]*commonmodels.ProductService, 0) for _, svr := range svrs { if deletedSvcSet.Has(svr.ServiceName) { continue } ps, ok := productServiceMap[svr.ServiceName] + // only update or insert services if !ok && !serviceNeedUpdateOrCreate.Has(svr.ServiceName) { continue } svcGroup = append(svcGroup, svr) - if ps != nil { - for _, svrc := range svr.Containers { - for _, psc := range ps.Containers { - if svrc.Name == psc.Name { - svrc.Image = psc.Image - break - } - } + if ps == nil { + continue + } + + templateContainMap := buildContainerMap(svr.Containers) + prodContainMap := buildContainerMap(ps.Containers) + for name, container := range templateContainMap { + if pc, ok := prodContainMap[name]; ok { + container.Image = pc.Image } } } @@ -1690,7 +1698,7 @@ func updateHelmProductVariable(productResp *commonmodels.Product, renderset *com var mutexUpdateMultiHelm sync.RWMutex -func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, log *zap.SugaredLogger) ([]*EnvStatus, error) { +func UpdateMultipleHelmEnv(requestID, userName string, args *UpdateMultiHelmProductArg, log *zap.SugaredLogger) ([]*EnvStatus, error) { mutexUpdateMultiHelm.Lock() defer func() { mutexUpdateMultiHelm.Unlock() @@ -1748,7 +1756,7 @@ func UpdateMultipleHelmEnv(requestID string, args *UpdateMultiHelmProductArg, lo return envStatuses, e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for env: %s", envName)) } - err = UpdateHelmProduct(productName, envName, UpdateTypeEnv, requestID, args.ChartValues, args.DeletedServices, log) + err = UpdateHelmProduct(productName, envName, userName, requestID, args.ChartValues, args.DeletedServices, log) if err != nil { log.Errorf("UpdateMultiHelmProduct UpdateProductV2 err:%v", err) return envStatuses, e.ErrUpdateEnv.AddDesc(err.Error()) @@ -2080,21 +2088,20 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su return nil } -func DeleteProductServices(envName, productName string, serviceNames []string, log *zap.SugaredLogger) (err error) { +func DeleteProductServices(userName, requestID, envName, productName string, serviceNames []string, log *zap.SugaredLogger) (err error) { productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) if err != nil { log.Errorf("find product error: %v", err) return err } if getProjectType(productName) == setting.HelmDeployType { - return deleteHelmProductServices(productInfo, serviceNames, log) + return deleteHelmProductServices(userName, requestID, productInfo, serviceNames, log) } return deleteK8sProductServices(productInfo, serviceNames, log) } -func deleteHelmProductServices(productInfo *commonmodels.Product, serviceNames []string, log *zap.SugaredLogger) error { - // init helm client +func deleteHelmProductServices(userName, requestID string, productInfo *commonmodels.Product, serviceNames []string, log *zap.SugaredLogger) error { restConfig, err := kube.GetRESTConfig(productInfo.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) @@ -2146,19 +2153,37 @@ func deleteHelmProductServices(productInfo *commonmodels.Product, serviceNames [ return e.ErrUpdateEnv.AddErr(err) } - // uninstall services - for _, service := range deleteServiceSet.List() { - go func(namespace, serviceName string) { - log.Infof("uninstall release:%s", util.GeneHelmReleaseName(namespace, serviceName)) - if err = helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), - Namespace: namespace, - Wait: true, - }); err != nil { - log.Errorf("helm uninstall release %s err:%v", fmt.Sprintf("%s-%s", namespace, serviceName), err) - } - }(productInfo.Namespace, service) - } + go func() { + failedServices := sync.Map{} + wg := sync.WaitGroup{} + for _, service := range deleteServiceSet.List() { + wg.Add(1) + go func(namespace, serviceName string) { + defer wg.Done() + log.Infof("uninstall release:%s", util.GeneHelmReleaseName(namespace, serviceName)) + if errUninstall := helmClient.UninstallRelease(&helmclient.ChartSpec{ + ReleaseName: util.GeneHelmReleaseName(namespace, serviceName), + Namespace: namespace, + Wait: true, + }); errUninstall != nil { + errStr := fmt.Errorf("helm uninstall service %s err: %s", serviceName, errUninstall) + failedServices.Store(serviceName, errStr) + log.Error(errStr) + } + }(productInfo.Namespace, service) + } + wg.Wait() + errList := make([]string, 0) + failedServices.Range(func(key, value interface{}) bool { + errList = append(errList, value.(string)) + return true + }) + if len(errList) > 0 { + title := fmt.Sprintf("[%s] 的 [%s] 环境服务删除失败", productInfo.ProductName, productInfo.EnvName) + commonservice.SendErrorMessage(userName, title, requestID, errors.New(strings.Join(errList, "\n")), log) + } + }() + return nil } -- Gitee From 57109d040cca4dd736e015bf8c2c34125ff11db0 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Fri, 15 Apr 2022 18:48:17 +0800 Subject: [PATCH 2214/2647] fix the cache address in the local cluster Signed-off-by: zhangyifei --- pkg/microservice/aslan/core/environment/service/share_env.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index a98e47cf..16b26abd 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -580,7 +580,7 @@ func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interfac switch clusterID { case setting.LocalClusterID: - cacheServerAddr = "aslan.zadig.svc.cluster.local" + cacheServerAddr = fmt.Sprintf("aslan.%s.svc.cluster.local", config.Namespace()) cacheServerPort = 25000 default: cacheServerAddr = "hub-agent.koderover-agent.svc.cluster.local" -- Gitee From 9c0eb20291d6024b5778ca10c8d3872e5d6e902c Mon Sep 17 00:00:00 2001 From: allenshen Date: Fri, 15 Apr 2022 21:18:48 +0800 Subject: [PATCH 2215/2647] fix uninstall bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 763dbf9a..be3d2954 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2179,7 +2179,7 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo Namespace: product.Namespace, Wait: true, }); errUninstall != nil { - errStr := fmt.Errorf("helm uninstall service %s err: %s", serviceName, errUninstall) + errStr := fmt.Sprintf("helm uninstall service %s err: %s", serviceName, errUninstall) failedServices.Store(serviceName, errStr) log.Error(errStr) } -- Gitee From 3e1db0ab3f55afb0a7064ff26b4be6dcf89edf5e Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 09:56:19 +0800 Subject: [PATCH 2216/2647] optimize Signed-off-by: panxunying --- pkg/microservice/aslan/core/common/service/rsa_key.go | 3 +-- pkg/microservice/aslan/core/environment/service/service.go | 3 +-- pkg/microservice/aslan/core/system/service/jenkins.go | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index d78be3c9..c542f40c 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -26,10 +26,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/tool/rsa" - "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/rsa" ) func GetRSAKey() ([]byte, []byte, error) { diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index da98e7ea..b432c6a6 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -29,8 +29,6 @@ import ( "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/koderover/zadig/pkg/util" - "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -46,6 +44,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" ) func GetServiceContainer(envName, productName, serviceName, container string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/system/service/jenkins.go b/pkg/microservice/aslan/core/system/service/jenkins.go index 50d4da5a..011feb22 100644 --- a/pkg/microservice/aslan/core/system/service/jenkins.go +++ b/pkg/microservice/aslan/core/system/service/jenkins.go @@ -26,7 +26,6 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" - "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types" -- Gitee From c05db676d0cce7a7a3e642547d478d78f9742f25 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 10:58:43 +0800 Subject: [PATCH 2217/2647] optimize log Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow_task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 36444cda..4d3af951 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1409,7 +1409,7 @@ func formatDistributeSubtasks(serviceModule *commonmodels.ServiceModuleTarget, r svcMap := envInfo.GetServiceMap() pSvc, ok := svcMap[serviceModule.ServiceName] if !ok { - return nil, fmt.Errorf("can't find service: %s:%s in product: %s", productName, repoInfo.DeployEnv, serviceModule.ServiceName) + return nil, fmt.Errorf("can't find service: %s in product: %s:%s", serviceModule.ServiceName, productName, repoInfo.DeployEnv) } if pSvc.Type == setting.HelmDeployType { templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ @@ -2242,7 +2242,7 @@ func getServiceNaming(projectName, envName, serviceName string) (string, error) svcMap := productInfo.GetServiceMap() pSvc, ok := svcMap[serviceName] if !ok { - return "", fmt.Errorf("can't find service: %s:%s in product: %s", projectName, envName, serviceName) + return "", fmt.Errorf("can't find service: %s in product: %s:%s", serviceName, projectName, envName) } if pSvc.Type != setting.HelmDeployType { return "", nil -- Gitee From b3066d4ae5304afd1caaa56c7347e3f002b9deb1 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Apr 2022 11:09:36 +0800 Subject: [PATCH 2218/2647] final update Signed-off-by: Min Min --- .../aslan/core/common/service/kube/service.go | 8 ++++++++ pkg/microservice/hubagent/config/config.go | 4 ++++ pkg/microservice/hubagent/server/server.go | 4 ++-- pkg/setting/consts.go | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 354dcd51..254e0c89 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -19,6 +19,7 @@ package kube import ( "bytes" "fmt" + config2 "github.com/koderover/zadig/pkg/config" "net/url" "strings" "text/template" @@ -257,6 +258,7 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD ResourceServerImage: rsImage, ClientToken: token, HubServerBaseAddr: hubBase.String(), + AslanBaseAddr: config2.SystemAddress(), UseDeployment: useDeployment, }) } else { @@ -265,6 +267,7 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD ResourceServerImage: rsImage, ClientToken: token, HubServerBaseAddr: hubBase.String(), + AslanBaseAddr: config2.SystemAddress(), UseDeployment: useDeployment, Namespace: cluster.Namespace, }) @@ -284,6 +287,7 @@ type TemplateSchema struct { HubServerBaseAddr string Namespace string UseDeployment bool + AslanBaseAddr string } var YamlTemplate = template.Must(template.New("agentYaml").Parse(` @@ -405,6 +409,8 @@ spec: value: "{{.ClientToken}}" - name: HUB_SERVER_BASE_ADDR value: "{{.HubServerBaseAddr}}" + - name: ASLAN_BASE_ADDR + value: "{{.AslanBaseAddr}}" resources: limits: cpu: 1000m @@ -654,6 +660,8 @@ spec: value: "{{.ClientToken}}" - name: HUB_SERVER_BASE_ADDR value: "{{.HubServerBaseAddr}}" + - name: ASLAN_BASE_ADDR + value: "{{.AslanBaseAddr}}" resources: limits: cpu: 1000m diff --git a/pkg/microservice/hubagent/config/config.go b/pkg/microservice/hubagent/config/config.go index f46e9ccc..76968ace 100644 --- a/pkg/microservice/hubagent/config/config.go +++ b/pkg/microservice/hubagent/config/config.go @@ -39,3 +39,7 @@ func KubernetesServiceHost() string { func KubernetesServicePort() string { return viper.GetString(setting.KubernetesServicePort) } + +func AslanBaseAddr() string { + return viper.GetString(setting.AslanBaseAddr) +} diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index 810ad080..c86c996e 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -76,7 +76,7 @@ func Serve(ctx context.Context) error { } func initDinD() { - client := aslan.New(config2.HubServerBaseAddr()) + client := aslan.New(config2.AslanBaseAddr()) ls, err := client.ListRegistries() if err != nil { @@ -98,7 +98,7 @@ func initDinD() { regList = append(regList, regItem) } - dynamicClient, err := kubeclient.GetDynamicKubeClient(config2.HubServerBaseAddr(), setting.LocalClusterID) + dynamicClient, err := kubeclient.GetDynamicKubeClient(config2.AslanBaseAddr(), setting.LocalClusterID) if err != nil { log.Fatalf("failed to create dynamic kubernetes clientset for clusterID: %s, the error is: %s", setting.LocalClusterID, err) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 8b96bb34..1ffdd1db 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -83,6 +83,7 @@ const ( KubernetesServicePort = "KUBERNETES_SERVICE_PORT" Token = "X-API-Tunnel-Token" Params = "X-API-Tunnel-Params" + AslanBaseAddr = "ASLAN_BASE_ADDR" // warpdrive WarpDrivePodName = "WD_POD_NAME" -- Gitee From f02073855194d9ea765d97404cb8349fd26bc914 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 11:58:39 +0800 Subject: [PATCH 2219/2647] fix service rmeove bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index be3d2954..27b25136 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2167,7 +2167,7 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo wg.Add(1) go func(product *models.Product, serviceName string, revision int64) { defer wg.Done() - templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ServiceName: serviceName, Revision: revision}) + templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ServiceName: serviceName, Revision: revision, ProductName: product.ProductName}) if err != nil { failedServices.Store(serviceName, err.Error()) return -- Gitee From 1450b6331ffac6b090e31d04dfe38b7b4829e6e2 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Apr 2022 12:51:05 +0800 Subject: [PATCH 2220/2647] bugfix for registry with port Signed-off-by: Min Min --- pkg/tool/registries/dind.go | 79 +++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/pkg/tool/registries/dind.go b/pkg/tool/registries/dind.go index c7a644d8..35373ddd 100644 --- a/pkg/tool/registries/dind.go +++ b/pkg/tool/registries/dind.go @@ -38,6 +38,10 @@ func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*R volumeList := make([]interface{}, 0) insecureRegistryList := make([]interface{}, 0) + mountFlag := false + insecureFlag := false + sourceList := make([]interface{}, 0) + for _, reg := range regList { // compatibility changes before 1.11 if reg.AdvancedSetting != nil { @@ -45,6 +49,7 @@ func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*R addr := strings.Split(reg.RegAddr, "//") // if a registry is marked as insecure, we add a record to insecure-registries if !reg.AdvancedSetting.TLSEnabled { + insecureFlag = true insecureRegistryList = append(insecureRegistryList, fmt.Sprintf("--insecure-registry=%s", addr[1])) } // if a registry is marked as secure and a TLS cert is given, we mount this certificate to dind daemon @@ -55,32 +60,42 @@ func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*R log.Errorf("failed to ensure secret: %s, the error is: %s", mountName, err) return err } - - // create volumeMount info - volumeMountMap := map[string]interface{}{ - "mountPath": fmt.Sprintf("/etc/docker/certs.d/%s", addr[1]), - "name": mountName, + // create secret mount info + secretMount := map[string]interface{}{ + "name": mountName, + "items": []interface{}{ + map[string]interface{}{ + "key": "cert.crt", + "path": fmt.Sprintf("%s/%s", addr[1], "cert.crt"), + }, + }, } - volumeMountList = append(volumeMountList, volumeMountMap) - // create volume info - secretItemList := make([]interface{}, 0) - secretItemList = append(secretItemList, map[string]interface{}{ - "key": "cert.crt", - "path": "cert.crt", + + // create projected volumes sources list + sourceList = append(sourceList, map[string]interface{}{ + "secret": secretMount, }) - secretInfo := map[string]interface{}{ - "items": secretItemList, - "secretName": mountName, - } - volumeMap := map[string]interface{}{ - "name": mountName, - "secret": secretInfo, - } - volumeList = append(volumeList, volumeMap) + + // set mountFlag to add mounting volume to dind + mountFlag = true } } } + if mountFlag { + volumeMountMap := map[string]interface{}{ + "mountPath": "/etc/docker/certs.d", + "name": "cert-mount", + } + volumeMountList = append(volumeMountList, volumeMountMap) + volumeList = append(volumeList, map[string]interface{}{ + "name": "cert-mount", + "projected": map[string]interface{}{ + "sources": sourceList, + }, + }) + } + result, getErr := dynamicClient.Resource(stsResource).Namespace(namespace).Get(context.TODO(), "dind", metav1.GetOptions{}) if getErr != nil { log.Errorf("failed to get dind statefulset, the error is: %s", getErr) @@ -93,22 +108,28 @@ func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*R return err } - // update spec.template.spec.containers[0].volumeMounts - if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { - return err + if mountFlag { + // update spec.template.spec.containers[0].volumeMounts + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), volumeMountList, "volumeMounts"); err != nil { + return err + } + // update spec.template.spec.volumes + if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { + return err + } } - // update spec.template.spec.containers[0].args - if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), insecureRegistryList, "args"); err != nil { - return err + if insecureFlag { + // update spec.template.spec.containers[0].args + if err := unstructured.SetNestedField(containers[0].(map[string]interface{}), insecureRegistryList, "args"); err != nil { + return err + } } + if err := unstructured.SetNestedField(result.Object, containers, "spec", "template", "spec", "containers"); err != nil { return err } - if err := unstructured.SetNestedField(result.Object, volumeList, "spec", "template", "spec", "volumes"); err != nil { - return err - } _, updateErr := dynamicClient.Resource(stsResource).Namespace(namespace).Update(context.TODO(), result, metav1.UpdateOptions{}) if updateErr != nil { log.Errorf("failed to update dind, the error is: %s", updateErr) -- Gitee From b250c1f459efc933c96aa4bb22a35adea1bf2e56 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 18 Apr 2022 13:30:02 +0800 Subject: [PATCH 2221/2647] minor bugfix Signed-off-by: Min Min --- pkg/tool/registries/dind.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tool/registries/dind.go b/pkg/tool/registries/dind.go index 35373ddd..4fd001c2 100644 --- a/pkg/tool/registries/dind.go +++ b/pkg/tool/registries/dind.go @@ -65,8 +65,8 @@ func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*R "name": mountName, "items": []interface{}{ map[string]interface{}{ - "key": "cert.crt", - "path": fmt.Sprintf("%s/%s", addr[1], "cert.crt"), + "key": "ca.crt", + "path": fmt.Sprintf("%s/%s", addr[1], "ca.crt"), }, }, } @@ -140,7 +140,7 @@ func PrepareDinD(dynamicClient dynamic.Interface, namespace string, regList []*R func ensureCertificateSecret(dynamicClient dynamic.Interface, secretName, namespace, cert string) error { certificateString := base64.StdEncoding.EncodeToString([]byte(cert)) datamap := map[string]interface{}{ - "cert.crt": certificateString, + "ca.crt": certificateString, } // setup secret GVR for future use -- Gitee From 84dccd25e447f8034b66c92599448e70dff5c73b Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 14:16:44 +0800 Subject: [PATCH 2222/2647] fix release image not working bug Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow_task.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 4d3af951..22807e3e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -615,6 +615,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, return nil, e.ErrCreateTask.AddErr(err) } if resetImageTask != nil { + resetImageTask["release_name"] = deployTask["release_name"] subTasks = append(subTasks, resetImageTask) } } @@ -1733,7 +1734,6 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator if env != nil { // 生成部署的subtask for _, deployEnv := range artifact.Deploy { - log.Infof("####### start deployEnvToSubTasks") deployTask, err := deployEnvToSubTasks(deployEnv, env, productTempl.Timeout) if err != nil { log.Errorf("deploy env to subtask error: %v", err) @@ -1747,6 +1747,7 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator return nil, err } if resetImageTask != nil { + resetImageTask["release_name"] = deployTask["release_name"] subTasks = append(subTasks, resetImageTask) } } -- Gitee From 3378b6ed75f94e2dd8e60c8bf3c2073afc448161 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Wed, 13 Apr 2022 10:00:24 +0800 Subject: [PATCH 2223/2647] support host services display host information Signed-off-by: ddh27 --- .../core/common/repository/models/service.go | 21 ++++++++-- .../aslan/core/common/service/service.go | 38 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 43d9d8fb..e05ccf4c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -19,6 +19,7 @@ package models import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/koderover/zadig/pkg/setting" + "go.mongodb.org/mongo-driver/bson/primitive" ) // TODO: move Revision out of Service. @@ -223,10 +224,22 @@ type HelmServiceRespArgs struct { } type EnvStatus struct { - HostID string `bson:"host_id,omitempty" json:"host_id"` - EnvName string `bson:"env_name,omitempty" json:"env_name"` - Address string `bson:"address,omitempty" json:"address"` - Status string `bson:"status,omitempty" json:"status"` + HostID string `bson:"host_id,omitempty" json:"host_id"` + EnvName string `bson:"env_name,omitempty" json:"env_name"` + Address string `bson:"address,omitempty" json:"address"` + Status string `bson:"status,omitempty" json:"status"` + PmInfo *PmInfo `bson:"-" json:"pm_info"` +} + +type PmInfo struct { + ID primitive.ObjectID `json:"id,omitempty"` + Name string `json:"name"` + IP string `json:"ip"` + Port int64 `json:"port"` + Status setting.PMHostStatus `json:"status"` + Label string `json:"label"` + IsProd bool `json:"is_prod"` + Provider int8 `json:"provider"` } type EnvConfig struct { diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 74fca01e..5d417a9f 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -315,6 +315,15 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str return resp, e.ErrGetTemplate.AddDesc(errMsg) } + if resp.Type == setting.PMDeployType { + err = fillPmInfo(resp) + if err != nil { + errMsg := fmt.Sprintf("[ServiceTmpl.Find] %s fillPmInfo error: %v", serviceName, err) + log.Error(errMsg) + return resp, e.ErrGetTemplate.AddDesc(errMsg) + } + } + if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { if gitlabAddress, err := GetGitlabAddress(resp.SrcPath); err == nil { if details, err := systemconfig.New().ListCodeHosts(); err == nil { @@ -409,6 +418,35 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str return resp, nil } +func fillPmInfo(svc *commonmodels.Service) error { + pms, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{}) + if err != nil { + if commonrepo.IsErrNoDocuments(err) { + return nil + } + return err + } + pmMaps := make(map[string]*commonmodels.PrivateKey, len(pms)) + for _, pm := range pms { + pmMaps[pm.ID.Hex()] = pm + } + for _, envStatus := range svc.EnvStatuses { + if pm, ok := pmMaps[envStatus.HostID]; ok { + envStatus.PmInfo = &commonmodels.PmInfo{ + ID: pm.ID, + Name: pm.Name, + IP: pm.IP, + Port: pm.Port, + Status: pm.Status, + Label: pm.Label, + IsProd: pm.IsProd, + Provider: pm.Provider, + } + } + } + return nil +} + func UpdatePmServiceTemplate(username string, args *ServiceTmplBuildObject, log *zap.SugaredLogger) error { //该请求来自环境中的服务更新时,from=createEnv if args.ServiceTmplObject.From == "" { -- Gitee From e63cf73e4fce68f71e1edb06e1230e72d8ddb1e3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 16:11:22 +0800 Subject: [PATCH 2224/2647] fix service delete bug Signed-off-by: allenshen --- .../core/environment/service/environment.go | 28 ++++++++++++++++--- .../environment/service/environment_update.go | 6 ++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 27b25136..5c178e6f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1236,11 +1236,22 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid } deletedSvcSet := sets.NewString(deletedServices...) + deletedSvcRevision := make(map[string]int64) // services need to be created or updated serviceNeedUpdateOrCreate := sets.NewString() for _, chart := range overrideCharts { serviceNeedUpdateOrCreate.Insert(chart.ServiceName) } + + // get deleted services map[serviceName]=>serviceRevision + for _, svcGroup := range currentProductService { + for _, svc := range svcGroup { + if deletedSvcSet.Has(svc.ServiceName) { + deletedSvcRevision[svc.ServiceName] = svc.Revision + } + } + } + // use service definition from service template, but keep the image info productServiceMap := productResp.GetServiceMap() allServices := make([][]*commonmodels.ProductService, 0) @@ -1279,7 +1290,7 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid } //对比当前环境中的环境变量和默认的环境变量 go func() { - err := updateProductGroup(username, productName, envName, productResp, currentProductService, overrideCharts, log) + err := updateProductGroup(username, productName, envName, productResp, currentProductService, overrideCharts, deletedSvcRevision, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -1967,7 +1978,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { for _, services := range productInfo.Services { for _, service := range services { - if err = UninstallService(hc, productInfo.ProductName, productInfo.Namespace, service.ServiceName, service.Revision, true); err != nil { + if err = UninstallService(hc, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, service.ServiceName, service.Revision, true); err != nil { log.Errorf("UninstallRelease err:%v", err) } } @@ -3263,7 +3274,8 @@ func batchExecutor(interval time.Duration, serviceList []*commonmodels.Service, return errList } -func updateProductGroup(username, productName, envName string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) error { +func updateProductGroup(username, productName, envName string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, + overrideCharts []*commonservice.RenderChartArg, deletedSvcRevision map[string]int64, log *zap.SugaredLogger) error { var ( renderChartMap = make(map[string]*templatemodels.RenderChart) productServiceMap = make(map[string]*commonmodels.ProductService) @@ -3275,6 +3287,14 @@ func updateProductGroup(username, productName, envName string, productResp *comm return e.ErrUpdateEnv.AddErr(err) } + // uninstall services + for serviceName, serviceRevision := range deletedSvcRevision { + if err = UninstallService(helmClient, productResp.ProductName, productResp.Namespace, productResp.EnvName, serviceName, serviceRevision, true); err != nil { + log.Errorf("UninstallRelease err:%v", err) + return e.ErrUpdateEnv.AddErr(err) + } + } + // current service applied in product for _, serviceGroup := range currentProductServices { for _, service := range serviceGroup { @@ -3311,7 +3331,7 @@ func updateProductGroup(username, productName, envName string, productResp *comm } errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) if errInstall != nil { - log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, err) + log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, errInstall) return errors.Wrapf(errInstall, "failed to install or upgrade service %s", serviceObj.ServiceName) } return nil diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 92ce6828..fbe11cf4 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -33,7 +33,7 @@ import ( "go.uber.org/zap" ) -func UninstallService(helmClient helmclient.Client, productName, namespace, serviceName string, revision int64, force bool) error { +func UninstallService(helmClient helmclient.Client, productName, namespace, envName, serviceName string, revision int64, force bool) error { revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, Revision: revision, @@ -43,7 +43,7 @@ func UninstallService(helmClient helmclient.Client, productName, namespace, serv return fmt.Errorf("failed to find service: %s with revision: %d, err: %s", serviceName, revision, err) } return helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNaming(), productName, namespace, revisionSvc.EnvName, serviceName), + ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNaming(), productName, namespace, envName, serviceName), Namespace: namespace, Wait: true, Force: force, @@ -98,7 +98,7 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string return err } - err = UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productSvc.ServiceName, productSvc.Revision, false) + err = UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, productSvc.ServiceName, productSvc.Revision, true) if err != nil { return fmt.Errorf("failed to uninstall release for service: %s, err: %s", productSvc.ServiceName, err) } -- Gitee From 64963ec25111bd32f77c570129947b727571f098 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 18:25:10 +0800 Subject: [PATCH 2225/2647] fix resource restart fail error Signed-off-by: allenshen --- .../aslan/core/common/service/environment.go | 46 +++++++++++++++++-- .../aslan/core/environment/service/service.go | 17 ++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 623a90af..798bea92 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -171,7 +171,7 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, filterArray := []FilterFunc{ func(workloads []*Workload) []*Workload { - if projectInfo.ProductFeature == nil || projectInfo.ProductFeature.CreateEnvType != setting.SourceFromExternal { + if projectInfo.ProductFeature == nil || projectInfo.ProductFeature.DeployType != setting.SourceFromExternal { return workloads } @@ -202,6 +202,31 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, return res }, } + + // for helm service, only show deploys/stss created by zadig + if projectInfo.ProductFeature != nil && projectInfo.ProductFeature.DeployType == setting.SourceFromHelm { + filterArray = append(filterArray, func(workloads []*Workload) []*Workload { + releaseNameMap, err := GetReleaseNameToServiceNameMap(productInfo) + if err != nil { + log.Errorf("failed to generate relase map for product: %s:%s", productInfo.ProductName, productInfo.EnvName) + return workloads + } + + var res []*Workload + for _, workload := range workloads { + if len(workload.Annotation) == 0 { + continue + } + releaseName := workload.Annotation[setting.HelmReleaseNameAnnotation] + log.Infof("###### the relaseName is %s", releaseName) + if _, ok := releaseNameMap[releaseName]; ok { + res = append(res, workload) + } + } + return res + }) + } + if filter != "" { filterArray = append(filterArray, func(workloads []*Workload) []*Workload { data, err := jsonutil.ToJSON(filter) @@ -254,6 +279,7 @@ type Workload struct { Images []string `json:"-"` Ready bool `json:"ready"` ServiceName string `json:"service_name"` + Annotation map[string]string `json:"-"` } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -277,7 +303,14 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range listDeployments { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.Deployment, Images: wrapper.Deployment(v).ImageInfos(), Ready: wrapper.Deployment(v).Ready()}) + workLoads = append(workLoads, &Workload{ + Name: v.Name, + Spec: v.Spec.Template, + Type: setting.Deployment, + Images: wrapper.Deployment(v).ImageInfos(), + Ready: wrapper.Deployment(v).Ready(), + Annotation: v.Annotations, + }) } statefulSets, err := getter.ListStatefulSetsWithCache(nil, informer) if err != nil { @@ -285,7 +318,14 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } for _, v := range statefulSets { - workLoads = append(workLoads, &Workload{Name: v.Name, Spec: v.Spec.Template, Type: setting.StatefulSet, Images: wrapper.StatefulSet(v).ImageInfos(), Ready: wrapper.StatefulSet(v).Ready()}) + workLoads = append(workLoads, &Workload{ + Name: v.Name, + Spec: v.Spec.Template, + Type: setting.StatefulSet, + Images: wrapper.StatefulSet(v).ImageInfos(), + Ready: wrapper.StatefulSet(v).Ready(), + Annotation: v.Annotations, + }) } log.Debugf("Found %d workloads in total", len(workLoads)) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index b432c6a6..0fecb95c 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -420,7 +420,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e } switch productObj.Source { - case setting.SourceFromHelm, setting.SourceFromExternal: + case setting.SourceFromExternal: errList := new(multierror.Error) serviceObj, found, err := getter.GetService(productObj.Namespace, args.ServiceName, kubeClient) if err != nil || !found { @@ -446,7 +446,22 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e } } } + case setting.SourceFromHelm: + deploy, found, err := getter.GetDeployment(productObj.Namespace, args.ServiceName, kubeClient) + if err != nil { + return fmt.Errorf("failed to find resource %s, type %s, err %s", args.ServiceName, setting.Deployment, err.Error()) + } + if found { + return updater.RestartDeployment(productObj.Namespace, deploy.Name, kubeClient) + } + sts, found, err := getter.GetStatefulSet(productObj.Namespace, args.ServiceName, kubeClient) + if err != nil { + return fmt.Errorf("failed to find resource %s, type %s, err %s", args.ServiceName, setting.StatefulSet, err.Error()) + } + if found { + return updater.RestartStatefulSet(productObj.Namespace, sts.Name, kubeClient) + } default: var serviceTmpl *commonmodels.Service var newRender *commonmodels.RenderSet -- Gitee From cf3bdb6c43376faccd6877ac8798f78e37b18403 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 18:28:45 +0800 Subject: [PATCH 2226/2647] fix error Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 798bea92..2cdb5771 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -171,7 +171,7 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, filterArray := []FilterFunc{ func(workloads []*Workload) []*Workload { - if projectInfo.ProductFeature == nil || projectInfo.ProductFeature.DeployType != setting.SourceFromExternal { + if projectInfo.ProductFeature == nil || projectInfo.ProductFeature.CreateEnvType != setting.SourceFromExternal { return workloads } -- Gitee From dbb4320ab1d0448ed5f2e8e1ccb7c593c92d9ee8 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 18 Apr 2022 18:29:52 +0800 Subject: [PATCH 2227/2647] remove useless code Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/environment.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 2cdb5771..8e41ce40 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -218,7 +218,6 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, continue } releaseName := workload.Annotation[setting.HelmReleaseNameAnnotation] - log.Infof("###### the relaseName is %s", releaseName) if _, ok := releaseNameMap[releaseName]; ok { res = append(res, workload) } -- Gitee From 678c47492cc7f94ca723eee7537e16c4a2238a5c Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 20:07:20 +0800 Subject: [PATCH 2228/2647] fix encrypted Signed-off-by: panxunying --- .../aslan/core/code/handler/codehost.go | 2 +- .../aslan/core/common/service/rsa_key.go | 3 ++- .../aslan/core/common/service/service.go | 4 ++-- .../picket/core/filter/handler/codehost.go | 9 ++++++++- .../picket/core/filter/service/codehost.go | 9 ++------- .../core/codehost/handler/codehost.go | 7 +++++++ .../core/codehost/handler/router.go | 1 + .../core/codehost/service/codehost.go | 8 ++++++++ pkg/shared/client/systemconfig/codehost.go | 17 +++++++++++++++-- 9 files changed, 46 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 99f5bd1d..86681cf6 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -34,7 +34,7 @@ func GetCodeHostList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codeHostSlice := make([]*systemconfig.CodeHost, 0) - codeHosts, err := systemconfig.New().ListCodeHosts() + codeHosts, err := systemconfig.New().ListCodeHostsInternal() ctx.Err = err for _, codeHost := range codeHosts { codeHost.AccessToken = setting.MaskValue diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index c542f40c..9f5218e8 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -69,10 +69,11 @@ func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*Ge } decodedKey, err := url.QueryUnescape(encryptedKey) if err != nil { + log.Errorf("GetAesKeyFromEncryptedKey QueryUnescape error:%s", err) return nil, err } decodedKey = strings.ReplaceAll(decodedKey, " ", "+") - + log.Infof("decodedKey:%s", decodedKey) byteKey, err := base64.StdEncoding.DecodeString(decodedKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 5d417a9f..cd31c7e4 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -130,7 +130,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm return nil, e.ErrListTemplate.AddDesc(err.Error()) } - details, err := systemconfig.New().ListCodeHosts() + details, err := systemconfig.New().ListCodeHostsInternal() if err != nil { log.Errorf("无法从原有数据中恢复加载信息, listCodehostDetail failed err: %+v", err) return nil, e.ErrListTemplate.AddDesc(err.Error()) @@ -326,7 +326,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { if gitlabAddress, err := GetGitlabAddress(resp.SrcPath); err == nil { - if details, err := systemconfig.New().ListCodeHosts(); err == nil { + if details, err := systemconfig.New().ListCodeHostsInternal(); err == nil { for _, detail := range details { if strings.Contains(detail.Address, gitlabAddress) { resp.GerritCodeHostID = detail.ID diff --git a/pkg/microservice/picket/core/filter/handler/codehost.go b/pkg/microservice/picket/core/filter/handler/codehost.go index 0cfa7d00..1d8a6ee1 100644 --- a/pkg/microservice/picket/core/filter/handler/codehost.go +++ b/pkg/microservice/picket/core/filter/handler/codehost.go @@ -21,11 +21,18 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func ListCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListCodeHost(ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Logger.Infof("encryptedKey:%s", encryptedKey) + ctx.Resp, ctx.Err = service.ListCodeHost(encryptedKey, ctx.Logger) } diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go index 55a4e172..cf84514a 100644 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ b/pkg/microservice/picket/core/filter/service/codehost.go @@ -22,15 +22,10 @@ import ( "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) -func ListCodeHost(_ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { - list, err := systemconfig.New().ListCodeHosts() +func ListCodeHost(encryptedKey string, _ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { + list, err := systemconfig.New().ListCodeHosts(encryptedKey) if err != nil { return nil, err } - for k := range list { - list[k].AccessKey = "***" - list[k].SecretKey = "***" - list[k].AccessToken = "***" - } return list, nil } diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 1749016f..4934fcbc 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -47,9 +47,16 @@ func ListCodeHost(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } + ctx.Logger.Infof("encryptedKey:%s", encryptedKey) ctx.Resp, ctx.Err = service.List(encryptedKey, c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } +func ListCodeHostInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.ListInternal(c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) +} + func DeleteCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index 857fae2d..128169ab 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -27,6 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehost.GET("/callback", Callback) codehost.GET("", ListCodeHost) + codehost.GET("/internal", ListCodeHostInternal) codehost.DELETE("/:id", DeleteCodeHost) codehost.POST("", CreateCodeHost) codehost.PATCH("/:id", UpdateCodeHost) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index f62a61d6..977d2d6b 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -86,6 +86,14 @@ func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost, log *zap return result, nil } +func ListInternal(address, owner, source string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { + return mongodb.NewCodehostColl().List(&mongodb.ListArgs{ + Address: address, + Owner: owner, + Source: source, + }) +} + func List(encryptedKey, address, owner, source string, log *zap.SugaredLogger) ([]*models.CodeHost, error) { codeHosts, err := mongodb.NewCodehostColl().List(&mongodb.ListArgs{ Address: address, diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 1d3ca376..cfa12b0b 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -18,6 +18,7 @@ package systemconfig import ( "fmt" + "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -70,8 +71,20 @@ func (c *Client) GetCodeHost(id int) (*CodeHost, error) { return res, nil } -func (c *Client) ListCodeHosts() ([]*CodeHost, error) { - url := "/codehosts" +func (c *Client) ListCodeHosts(encryptedKey string) ([]*CodeHost, error) { + url := "/codehosts?encryptedKey=" + url.QueryEscape(encryptedKey) + + res := make([]*CodeHost, 0) + _, err := c.Get(url, httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *Client) ListCodeHostsInternal() ([]*CodeHost, error) { + url := "/codehosts/internal" res := make([]*CodeHost, 0) _, err := c.Get(url, httpclient.SetResult(&res)) -- Gitee From 6845c1bdc69e61a3380d64be15e8eb99b0e5ef21 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 20:31:27 +0800 Subject: [PATCH 2229/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/service/rsa_key.go | 1 - .../picket/core/filter/handler/codehost.go | 38 ------------------- .../picket/core/filter/service/codehost.go | 31 --------------- .../core/codehost/handler/codehost.go | 1 - pkg/shared/client/systemconfig/codehost.go | 13 ------- 5 files changed, 84 deletions(-) delete mode 100644 pkg/microservice/picket/core/filter/handler/codehost.go delete mode 100644 pkg/microservice/picket/core/filter/service/codehost.go diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index 9f5218e8..97a1ee43 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -73,7 +73,6 @@ func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*Ge return nil, err } decodedKey = strings.ReplaceAll(decodedKey, " ", "+") - log.Infof("decodedKey:%s", decodedKey) byteKey, err := base64.StdEncoding.DecodeString(decodedKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) diff --git a/pkg/microservice/picket/core/filter/handler/codehost.go b/pkg/microservice/picket/core/filter/handler/codehost.go deleted file mode 100644 index 1d8a6ee1..00000000 --- a/pkg/microservice/picket/core/filter/handler/codehost.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" - internalhandler "github.com/koderover/zadig/pkg/shared/handler" - e "github.com/koderover/zadig/pkg/tool/errors" -) - -func ListCodeHost(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - encryptedKey := c.Query("encryptedKey") - if len(encryptedKey) == 0 { - ctx.Err = e.ErrInvalidParam - return - } - ctx.Logger.Infof("encryptedKey:%s", encryptedKey) - ctx.Resp, ctx.Err = service.ListCodeHost(encryptedKey, ctx.Logger) -} diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go deleted file mode 100644 index cf84514a..00000000 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/shared/client/systemconfig" -) - -func ListCodeHost(encryptedKey string, _ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { - list, err := systemconfig.New().ListCodeHosts(encryptedKey) - if err != nil { - return nil, err - } - return list, nil -} diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 4934fcbc..84887453 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -47,7 +47,6 @@ func ListCodeHost(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Logger.Infof("encryptedKey:%s", encryptedKey) ctx.Resp, ctx.Err = service.List(encryptedKey, c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index cfa12b0b..e465a50d 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -18,7 +18,6 @@ package systemconfig import ( "fmt" - "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -71,18 +70,6 @@ func (c *Client) GetCodeHost(id int) (*CodeHost, error) { return res, nil } -func (c *Client) ListCodeHosts(encryptedKey string) ([]*CodeHost, error) { - url := "/codehosts?encryptedKey=" + url.QueryEscape(encryptedKey) - - res := make([]*CodeHost, 0) - _, err := c.Get(url, httpclient.SetResult(&res)) - if err != nil { - return nil, err - } - - return res, nil -} - func (c *Client) ListCodeHostsInternal() ([]*CodeHost, error) { url := "/codehosts/internal" -- Gitee From 85e123168999a4ff36676142115e343c16228ba5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 20:07:20 +0800 Subject: [PATCH 2230/2647] fix encrypted Signed-off-by: panxunying --- .../aslan/core/code/handler/codehost.go | 2 +- .../aslan/core/common/service/rsa_key.go | 3 ++- .../aslan/core/common/service/service.go | 4 ++-- .../picket/core/filter/handler/codehost.go | 9 ++++++++- .../picket/core/filter/service/codehost.go | 9 ++------- .../core/codehost/handler/codehost.go | 7 +++++++ .../core/codehost/handler/router.go | 1 + .../core/codehost/service/codehost.go | 8 ++++++++ pkg/shared/client/systemconfig/codehost.go | 17 +++++++++++++++-- 9 files changed, 46 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 99f5bd1d..86681cf6 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -34,7 +34,7 @@ func GetCodeHostList(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() codeHostSlice := make([]*systemconfig.CodeHost, 0) - codeHosts, err := systemconfig.New().ListCodeHosts() + codeHosts, err := systemconfig.New().ListCodeHostsInternal() ctx.Err = err for _, codeHost := range codeHosts { codeHost.AccessToken = setting.MaskValue diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index c542f40c..9f5218e8 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -69,10 +69,11 @@ func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*Ge } decodedKey, err := url.QueryUnescape(encryptedKey) if err != nil { + log.Errorf("GetAesKeyFromEncryptedKey QueryUnescape error:%s", err) return nil, err } decodedKey = strings.ReplaceAll(decodedKey, " ", "+") - + log.Infof("decodedKey:%s", decodedKey) byteKey, err := base64.StdEncoding.DecodeString(decodedKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 5d417a9f..cd31c7e4 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -130,7 +130,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm return nil, e.ErrListTemplate.AddDesc(err.Error()) } - details, err := systemconfig.New().ListCodeHosts() + details, err := systemconfig.New().ListCodeHostsInternal() if err != nil { log.Errorf("无法从原有数据中恢复加载信息, listCodehostDetail failed err: %+v", err) return nil, e.ErrListTemplate.AddDesc(err.Error()) @@ -326,7 +326,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { if gitlabAddress, err := GetGitlabAddress(resp.SrcPath); err == nil { - if details, err := systemconfig.New().ListCodeHosts(); err == nil { + if details, err := systemconfig.New().ListCodeHostsInternal(); err == nil { for _, detail := range details { if strings.Contains(detail.Address, gitlabAddress) { resp.GerritCodeHostID = detail.ID diff --git a/pkg/microservice/picket/core/filter/handler/codehost.go b/pkg/microservice/picket/core/filter/handler/codehost.go index 0cfa7d00..1d8a6ee1 100644 --- a/pkg/microservice/picket/core/filter/handler/codehost.go +++ b/pkg/microservice/picket/core/filter/handler/codehost.go @@ -21,11 +21,18 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func ListCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListCodeHost(ctx.Logger) + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Logger.Infof("encryptedKey:%s", encryptedKey) + ctx.Resp, ctx.Err = service.ListCodeHost(encryptedKey, ctx.Logger) } diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go index 55a4e172..cf84514a 100644 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ b/pkg/microservice/picket/core/filter/service/codehost.go @@ -22,15 +22,10 @@ import ( "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) -func ListCodeHost(_ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { - list, err := systemconfig.New().ListCodeHosts() +func ListCodeHost(encryptedKey string, _ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { + list, err := systemconfig.New().ListCodeHosts(encryptedKey) if err != nil { return nil, err } - for k := range list { - list[k].AccessKey = "***" - list[k].SecretKey = "***" - list[k].AccessToken = "***" - } return list, nil } diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 1749016f..4934fcbc 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -47,9 +47,16 @@ func ListCodeHost(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } + ctx.Logger.Infof("encryptedKey:%s", encryptedKey) ctx.Resp, ctx.Err = service.List(encryptedKey, c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } +func ListCodeHostInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.ListInternal(c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) +} + func DeleteCodeHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/codehost/handler/router.go b/pkg/microservice/systemconfig/core/codehost/handler/router.go index 857fae2d..128169ab 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/router.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/router.go @@ -27,6 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { codehost.GET("/callback", Callback) codehost.GET("", ListCodeHost) + codehost.GET("/internal", ListCodeHostInternal) codehost.DELETE("/:id", DeleteCodeHost) codehost.POST("", CreateCodeHost) codehost.PATCH("/:id", UpdateCodeHost) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index f62a61d6..977d2d6b 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -86,6 +86,14 @@ func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost, log *zap return result, nil } +func ListInternal(address, owner, source string, _ *zap.SugaredLogger) ([]*models.CodeHost, error) { + return mongodb.NewCodehostColl().List(&mongodb.ListArgs{ + Address: address, + Owner: owner, + Source: source, + }) +} + func List(encryptedKey, address, owner, source string, log *zap.SugaredLogger) ([]*models.CodeHost, error) { codeHosts, err := mongodb.NewCodehostColl().List(&mongodb.ListArgs{ Address: address, diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 1d3ca376..cfa12b0b 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -18,6 +18,7 @@ package systemconfig import ( "fmt" + "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -70,8 +71,20 @@ func (c *Client) GetCodeHost(id int) (*CodeHost, error) { return res, nil } -func (c *Client) ListCodeHosts() ([]*CodeHost, error) { - url := "/codehosts" +func (c *Client) ListCodeHosts(encryptedKey string) ([]*CodeHost, error) { + url := "/codehosts?encryptedKey=" + url.QueryEscape(encryptedKey) + + res := make([]*CodeHost, 0) + _, err := c.Get(url, httpclient.SetResult(&res)) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *Client) ListCodeHostsInternal() ([]*CodeHost, error) { + url := "/codehosts/internal" res := make([]*CodeHost, 0) _, err := c.Get(url, httpclient.SetResult(&res)) -- Gitee From 68fad6918e6298db6003cf3438e0ac795820c7a8 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 20:31:27 +0800 Subject: [PATCH 2231/2647] optimize Signed-off-by: panxunying --- .../aslan/core/common/service/rsa_key.go | 1 - .../picket/core/filter/handler/codehost.go | 38 ------------------- .../picket/core/filter/service/codehost.go | 31 --------------- .../core/codehost/handler/codehost.go | 1 - pkg/shared/client/systemconfig/codehost.go | 13 ------- 5 files changed, 84 deletions(-) delete mode 100644 pkg/microservice/picket/core/filter/handler/codehost.go delete mode 100644 pkg/microservice/picket/core/filter/service/codehost.go diff --git a/pkg/microservice/aslan/core/common/service/rsa_key.go b/pkg/microservice/aslan/core/common/service/rsa_key.go index 9f5218e8..97a1ee43 100644 --- a/pkg/microservice/aslan/core/common/service/rsa_key.go +++ b/pkg/microservice/aslan/core/common/service/rsa_key.go @@ -73,7 +73,6 @@ func GetAesKeyFromEncryptedKey(encryptedKey string, log *zap.SugaredLogger) (*Ge return nil, err } decodedKey = strings.ReplaceAll(decodedKey, " ", "+") - log.Infof("decodedKey:%s", decodedKey) byteKey, err := base64.StdEncoding.DecodeString(decodedKey) if err != nil { log.Errorf("getAesKeyFromEncryptedKey decodeString error msg:%s", err) diff --git a/pkg/microservice/picket/core/filter/handler/codehost.go b/pkg/microservice/picket/core/filter/handler/codehost.go deleted file mode 100644 index 1d8a6ee1..00000000 --- a/pkg/microservice/picket/core/filter/handler/codehost.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - "github.com/gin-gonic/gin" - - "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" - internalhandler "github.com/koderover/zadig/pkg/shared/handler" - e "github.com/koderover/zadig/pkg/tool/errors" -) - -func ListCodeHost(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - encryptedKey := c.Query("encryptedKey") - if len(encryptedKey) == 0 { - ctx.Err = e.ErrInvalidParam - return - } - ctx.Logger.Infof("encryptedKey:%s", encryptedKey) - ctx.Resp, ctx.Err = service.ListCodeHost(encryptedKey, ctx.Logger) -} diff --git a/pkg/microservice/picket/core/filter/service/codehost.go b/pkg/microservice/picket/core/filter/service/codehost.go deleted file mode 100644 index cf84514a..00000000 --- a/pkg/microservice/picket/core/filter/service/codehost.go +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "go.uber.org/zap" - - "github.com/koderover/zadig/pkg/shared/client/systemconfig" -) - -func ListCodeHost(encryptedKey string, _ *zap.SugaredLogger) ([]*systemconfig.CodeHost, error) { - list, err := systemconfig.New().ListCodeHosts(encryptedKey) - if err != nil { - return nil, err - } - return list, nil -} diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 4934fcbc..84887453 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -47,7 +47,6 @@ func ListCodeHost(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Logger.Infof("encryptedKey:%s", encryptedKey) ctx.Resp, ctx.Err = service.List(encryptedKey, c.Query("address"), c.Query("owner"), c.Query("source"), ctx.Logger) } diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index cfa12b0b..e465a50d 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -18,7 +18,6 @@ package systemconfig import ( "fmt" - "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -71,18 +70,6 @@ func (c *Client) GetCodeHost(id int) (*CodeHost, error) { return res, nil } -func (c *Client) ListCodeHosts(encryptedKey string) ([]*CodeHost, error) { - url := "/codehosts?encryptedKey=" + url.QueryEscape(encryptedKey) - - res := make([]*CodeHost, 0) - _, err := c.Get(url, httpclient.SetResult(&res)) - if err != nil { - return nil, err - } - - return res, nil -} - func (c *Client) ListCodeHostsInternal() ([]*CodeHost, error) { url := "/codehosts/internal" -- Gitee From ef9af7f3781be9cb0783062694404ffba6b5f411 Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 21:12:31 +0800 Subject: [PATCH 2232/2647] remove picket codehost Signed-off-by: panxunying --- pkg/microservice/picket/core/filter/handler/router.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 5851f946..96364fb7 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -42,10 +42,6 @@ func (*Router) Inject(router *gin.RouterGroup) { { rolebindings.GET("", ListBindings) } - codehosts := router.Group("codehosts") - { - codehosts.GET("", ListCodeHost) - } users := router.Group("users") { users.DELETE("/:id", DeleteUser) -- Gitee From 7c3176e24bd9c35c638c2a04f3b5c9eab079541f Mon Sep 17 00:00:00 2001 From: panxunying Date: Mon, 18 Apr 2022 21:12:31 +0800 Subject: [PATCH 2233/2647] remove picket codehost Signed-off-by: panxunying --- pkg/microservice/picket/core/filter/handler/router.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 5851f946..96364fb7 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -42,10 +42,6 @@ func (*Router) Inject(router *gin.RouterGroup) { { rolebindings.GET("", ListBindings) } - codehosts := router.Group("codehosts") - { - codehosts.GET("", ListCodeHost) - } users := router.Group("users") { users.DELETE("/:id", DeleteUser) -- Gitee From 0979f4b18095225edc56fd02ae10befd41043616 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 19 Apr 2022 11:16:04 +0800 Subject: [PATCH 2234/2647] fix ListPrivateKey api do not encrypted Signed-off-by: ddh27 --- .../aslan/core/system/handler/private_key.go | 7 +++++++ pkg/microservice/aslan/core/system/handler/router.go | 1 + .../aslan/core/system/service/private_key.go | 12 ++++++++++++ pkg/microservice/cron/core/service/client/pm.go | 2 +- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 71274731..d922aab9 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -32,6 +32,13 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +func ListPrivateKeysInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListPrivateKeysInternal(ctx.Logger) +} + func ListPrivateKeys(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 8d8df2ad..db7f1397 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -162,6 +162,7 @@ func (*Router) Inject(router *gin.RouterGroup) { privateKey := router.Group("privateKey") { privateKey.GET("", ListPrivateKeys) + privateKey.GET("/internal", ListPrivateKeysInternal) privateKey.GET("/:id", GetPrivateKey) privateKey.GET("/labels", ListLabels) privateKey.POST("", gin2.UpdateOperationLogStatus, CreatePrivateKey) diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 4efe7080..b85f500e 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -48,6 +48,18 @@ func ListPrivateKeys(encryptedKey string, log *zap.SugaredLogger) ([]*commonmode return resp, nil } +func ListPrivateKeysInternal(log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { + resp, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{}) + if err != nil { + if commonrepo.IsErrNoDocuments(err) { + return []*commonmodels.PrivateKey{}, nil + } + log.Errorf("PrivateKey.List error: %v", err) + return resp, e.ErrListPrivateKeys + } + return resp, nil +} + func GetPrivateKey(id string, log *zap.SugaredLogger) (*commonmodels.PrivateKey, error) { resp, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ ID: id, diff --git a/pkg/microservice/cron/core/service/client/pm.go b/pkg/microservice/cron/core/service/client/pm.go index c3b3b9ee..7d7dc5b2 100644 --- a/pkg/microservice/cron/core/service/client/pm.go +++ b/pkg/microservice/cron/core/service/client/pm.go @@ -34,7 +34,7 @@ func (c *Client) ListPmHosts(log *zap.SugaredLogger) ([]*service.PrivateKeyHosts resp = make([]*service.PrivateKeyHosts, 0) ) - url := fmt.Sprintf("%s/system/privateKey", c.APIBase) + url := fmt.Sprintf("%s/system/privateKey/internal", c.APIBase) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("ListPmHosts new http request error: %v", err) -- Gitee From 92a1008fde5d30b60ba53c1db4a7723a78e3f424 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Tue, 19 Apr 2022 11:16:04 +0800 Subject: [PATCH 2235/2647] fix ListPrivateKey api do not encrypted Signed-off-by: ddh27 --- .../aslan/core/system/handler/private_key.go | 7 +++++++ pkg/microservice/aslan/core/system/handler/router.go | 1 + .../aslan/core/system/service/private_key.go | 12 ++++++++++++ pkg/microservice/cron/core/service/client/pm.go | 2 +- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 71274731..d922aab9 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -32,6 +32,13 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +func ListPrivateKeysInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListPrivateKeysInternal(ctx.Logger) +} + func ListPrivateKeys(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index 8d8df2ad..db7f1397 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -162,6 +162,7 @@ func (*Router) Inject(router *gin.RouterGroup) { privateKey := router.Group("privateKey") { privateKey.GET("", ListPrivateKeys) + privateKey.GET("/internal", ListPrivateKeysInternal) privateKey.GET("/:id", GetPrivateKey) privateKey.GET("/labels", ListLabels) privateKey.POST("", gin2.UpdateOperationLogStatus, CreatePrivateKey) diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 4efe7080..b85f500e 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -48,6 +48,18 @@ func ListPrivateKeys(encryptedKey string, log *zap.SugaredLogger) ([]*commonmode return resp, nil } +func ListPrivateKeysInternal(log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { + resp, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{}) + if err != nil { + if commonrepo.IsErrNoDocuments(err) { + return []*commonmodels.PrivateKey{}, nil + } + log.Errorf("PrivateKey.List error: %v", err) + return resp, e.ErrListPrivateKeys + } + return resp, nil +} + func GetPrivateKey(id string, log *zap.SugaredLogger) (*commonmodels.PrivateKey, error) { resp, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ ID: id, diff --git a/pkg/microservice/cron/core/service/client/pm.go b/pkg/microservice/cron/core/service/client/pm.go index c3b3b9ee..7d7dc5b2 100644 --- a/pkg/microservice/cron/core/service/client/pm.go +++ b/pkg/microservice/cron/core/service/client/pm.go @@ -34,7 +34,7 @@ func (c *Client) ListPmHosts(log *zap.SugaredLogger) ([]*service.PrivateKeyHosts resp = make([]*service.PrivateKeyHosts, 0) ) - url := fmt.Sprintf("%s/system/privateKey", c.APIBase) + url := fmt.Sprintf("%s/system/privateKey/internal", c.APIBase) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("ListPmHosts new http request error: %v", err) -- Gitee From d833724118af51c4497398514350ad8efe1d22e5 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Apr 2022 11:27:28 +0800 Subject: [PATCH 2236/2647] client workflow task url not correct Signed-off-by: mouuii --- pkg/microservice/cron/core/service/client/cronjob.go | 3 ++- .../cron/core/service/scheduler/cronjob_handler.go | 5 +++-- pkg/microservice/picket/client/aslan/workflow.go | 5 +++-- pkg/microservice/picket/core/public/handler/public.go | 2 +- pkg/microservice/picket/core/public/service/public.go | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 3d596ed3..42bb62a4 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -24,6 +24,7 @@ import ( "io" "io/ioutil" "net/http" + "path" "time" "go.uber.org/zap" @@ -94,7 +95,7 @@ func (c *Client) RunPipelineTask(args *service.TaskArgs, log *zap.SugaredLogger) } func (c *Client) RunWorkflowTask(args *service.WorkflowTaskArgs, log *zap.SugaredLogger) error { - url := fmt.Sprintf("%s/workflow/workflowtask", c.APIBase) + url := path.Join(c.APIBase, "workflow/workflowtask", args.WorkflowName) log.Info("start run scheduled task..") body, err := json.Marshal(args) if err != nil { diff --git a/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go b/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go index 28448974..a16cd2af 100644 --- a/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go +++ b/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net/http" + "path" "strings" "time" @@ -250,7 +251,7 @@ func (h *CronjobHandler) registerWorkFlowJob(name, schedule string, job *service args.DistributeEnabled = job.WorkflowArgs.DistributeEnabled } scheduleJob, err := cronlib.NewJobModel(schedule, func() { - if err := h.aslanCli.ScheduleCall("workflow/workflowtask", args, log.SugaredLogger()); err != nil { + if err := h.aslanCli.ScheduleCall(path.Join("workflow/workflowtask", args.WorkflowName), args, log.SugaredLogger()); err != nil { log.Errorf("[%s]RunScheduledTask err: %v", name, err) } }) @@ -354,7 +355,7 @@ func registerCronjob(job *service.Cronjob, client *client.Client, scheduler *cro cron, _ = convertCronString(job.JobType, job.Time, job.Frequency, job.Number) } scheduleJob, err := cronlib.NewJobModel(cron, func() { - if err := client.ScheduleCall("workflow/workflowtask", args, log.SugaredLogger()); err != nil { + if err := client.ScheduleCall(path.Join("workflow/workflowtask", job.WorkflowArgs.WorkflowName), args, log.SugaredLogger()); err != nil { log.Errorf("[%s]RunScheduledTask err: %v", job.Name, err) } }) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 35d0601b..7ae400f5 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" "net/url" + "path" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" @@ -58,8 +59,8 @@ func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.V return res.Body(), nil } -func (c *Client) CreateWorkflowTask(header http.Header, qs url.Values, body []byte) ([]byte, error) { - url := "/workflow/workflowtask" +func (c *Client) CreateWorkflowTask(header http.Header, qs url.Values, body []byte, workflowName string) ([]byte, error) { + url := path.Join("/workflow/workflowtask", workflowName) res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody(body)) if err != nil { diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 5a42387a..b0406897 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -46,7 +46,7 @@ func CreateWorkflowTask(c *gin.Context) { ctx.Logger.Errorf("Marshal err:%s", err) return } - res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) + res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), req.WorkflowName, body, ctx.Logger) if err != nil { ctx.Err = err ctx.Logger.Errorf("CreateWorkflowTask err:%s", err) diff --git a/pkg/microservice/picket/core/public/service/public.go b/pkg/microservice/picket/core/public/service/public.go index 3a29ebaa..e8e8b1c6 100644 --- a/pkg/microservice/picket/core/public/service/public.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -77,8 +77,8 @@ type WorkflowTaskDetail struct { Status string `json:"status"` } -func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.SugaredLogger) ([]byte, error) { - return aslan.New().CreateWorkflowTask(header, qs, body) +func CreateWorkflowTask(header http.Header, qs url.Values, workflowName string, body []byte, _ *zap.SugaredLogger) ([]byte, error) { + return aslan.New().CreateWorkflowTask(header, qs, body, workflowName) } func CancelWorkflowTask(header http.Header, qs url.Values, id string, name string, _ *zap.SugaredLogger) (int, error) { -- Gitee From 1b8f98a7ca277cf2503479df887d44cd4044f0b8 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 19 Apr 2022 11:49:21 +0800 Subject: [PATCH 2237/2647] fix retrieve password error Signed-off-by: panxunying --- .../systemconfig/core/email/handler/email.go | 10 ++++++++-- .../systemconfig/core/email/handler/router.go | 3 ++- .../systemconfig/core/email/service/email.go | 15 +++++++++++---- pkg/shared/client/systemconfig/email.go | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index 174854d3..1f3d714d 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -31,7 +31,13 @@ func GetEmailHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) } -func InternalGetEmailHost(c *gin.Context) { +func GetEmailHostInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GetEmailHostInternal(ctx.Logger) +} + +func GetEncryptedEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -40,7 +46,7 @@ func InternalGetEmailHost(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.InternalGetEmailHost(encryptedKey, ctx.Logger) + ctx.Resp, ctx.Err = service.GetEncryptedEmailHost(encryptedKey, ctx.Logger) } func CreateEmailHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/email/handler/router.go b/pkg/microservice/systemconfig/core/email/handler/router.go index 0a1cf8f8..aea6d9b2 100644 --- a/pkg/microservice/systemconfig/core/email/handler/router.go +++ b/pkg/microservice/systemconfig/core/email/handler/router.go @@ -30,7 +30,8 @@ func (*Router) Inject(router *gin.RouterGroup) { emails.PATCH("/host", UpdateEmailHost) emails.DELETE("/host", DeleteEmailHost) - emails.GET("/internal/host", InternalGetEmailHost) + emails.GET("/internal/host", GetEncryptedEmailHost) + emails.GET("/host/internal", GetEmailHostInternal) emails.GET("/service", GetEmailService) emails.POST("/service", CreateEmailService) diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 62e6a4ac..f191e765 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -36,20 +36,27 @@ func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { return host, err } -func InternalGetEmailHost(encryptedKey string, log *zap.SugaredLogger) (*models.EmailHost, error) { +func GetEmailHostInternal(_ *zap.SugaredLogger) (*models.EmailHost, error) { + return mongodb.NewEmailHostColl().Find() +} + +func GetEncryptedEmailHost(encryptedKey string, log *zap.SugaredLogger) (*models.EmailHost, error) { aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { - log.Errorf("InternalGetEmailHost GetTextFromEncryptedKey error:%s", err) + log.Errorf("GetEncryptedEmailHost GetTextFromEncryptedKey error:%s", err) return nil, err } result, err := mongodb.NewEmailHostColl().Find() if err != nil { - log.Errorf("InternalGetEmailHost find email host error:%s", err) + log.Errorf("GetEncryptedEmailHost find email host error:%s", err) return nil, err } + if result == nil { + return nil, nil + } password, err := crypto.AesEncryptByKey(result.Password, aesKey.PlainText) if err != nil { - log.Errorf("InternalGetEmailHost AesEncryptByKey error:%s", err) + log.Errorf("GetEncryptedEmailHost AesEncryptByKey error:%s", err) return nil, err } result.Password = password diff --git a/pkg/shared/client/systemconfig/email.go b/pkg/shared/client/systemconfig/email.go index 0aecd6bc..3e94ffc9 100644 --- a/pkg/shared/client/systemconfig/email.go +++ b/pkg/shared/client/systemconfig/email.go @@ -28,7 +28,7 @@ type Email struct { } func (c *Client) GetEmailHost() (*Email, error) { - url := "/emails/internal/host/" + url := "/emails/host/internal/" res := &Email{} _, err := c.Get(url, httpclient.SetResult(res)) -- Gitee From 49183405b8ee00bb19cb35d97d1e8630c653d7c4 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 19 Apr 2022 11:49:21 +0800 Subject: [PATCH 2238/2647] fix retrieve password error Signed-off-by: panxunying --- .../systemconfig/core/email/handler/email.go | 10 ++++++++-- .../systemconfig/core/email/handler/router.go | 3 ++- .../systemconfig/core/email/service/email.go | 15 +++++++++++---- pkg/shared/client/systemconfig/email.go | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/systemconfig/core/email/handler/email.go b/pkg/microservice/systemconfig/core/email/handler/email.go index 174854d3..1f3d714d 100644 --- a/pkg/microservice/systemconfig/core/email/handler/email.go +++ b/pkg/microservice/systemconfig/core/email/handler/email.go @@ -31,7 +31,13 @@ func GetEmailHost(c *gin.Context) { ctx.Resp, ctx.Err = service.GetEmailHost(ctx.Logger) } -func InternalGetEmailHost(c *gin.Context) { +func GetEmailHostInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GetEmailHostInternal(ctx.Logger) +} + +func GetEncryptedEmailHost(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -40,7 +46,7 @@ func InternalGetEmailHost(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.InternalGetEmailHost(encryptedKey, ctx.Logger) + ctx.Resp, ctx.Err = service.GetEncryptedEmailHost(encryptedKey, ctx.Logger) } func CreateEmailHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/email/handler/router.go b/pkg/microservice/systemconfig/core/email/handler/router.go index 0a1cf8f8..aea6d9b2 100644 --- a/pkg/microservice/systemconfig/core/email/handler/router.go +++ b/pkg/microservice/systemconfig/core/email/handler/router.go @@ -30,7 +30,8 @@ func (*Router) Inject(router *gin.RouterGroup) { emails.PATCH("/host", UpdateEmailHost) emails.DELETE("/host", DeleteEmailHost) - emails.GET("/internal/host", InternalGetEmailHost) + emails.GET("/internal/host", GetEncryptedEmailHost) + emails.GET("/host/internal", GetEmailHostInternal) emails.GET("/service", GetEmailService) emails.POST("/service", CreateEmailService) diff --git a/pkg/microservice/systemconfig/core/email/service/email.go b/pkg/microservice/systemconfig/core/email/service/email.go index 62e6a4ac..f191e765 100644 --- a/pkg/microservice/systemconfig/core/email/service/email.go +++ b/pkg/microservice/systemconfig/core/email/service/email.go @@ -36,20 +36,27 @@ func GetEmailHost(_ *zap.SugaredLogger) (*models.EmailHost, error) { return host, err } -func InternalGetEmailHost(encryptedKey string, log *zap.SugaredLogger) (*models.EmailHost, error) { +func GetEmailHostInternal(_ *zap.SugaredLogger) (*models.EmailHost, error) { + return mongodb.NewEmailHostColl().Find() +} + +func GetEncryptedEmailHost(encryptedKey string, log *zap.SugaredLogger) (*models.EmailHost, error) { aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { - log.Errorf("InternalGetEmailHost GetTextFromEncryptedKey error:%s", err) + log.Errorf("GetEncryptedEmailHost GetTextFromEncryptedKey error:%s", err) return nil, err } result, err := mongodb.NewEmailHostColl().Find() if err != nil { - log.Errorf("InternalGetEmailHost find email host error:%s", err) + log.Errorf("GetEncryptedEmailHost find email host error:%s", err) return nil, err } + if result == nil { + return nil, nil + } password, err := crypto.AesEncryptByKey(result.Password, aesKey.PlainText) if err != nil { - log.Errorf("InternalGetEmailHost AesEncryptByKey error:%s", err) + log.Errorf("GetEncryptedEmailHost AesEncryptByKey error:%s", err) return nil, err } result.Password = password diff --git a/pkg/shared/client/systemconfig/email.go b/pkg/shared/client/systemconfig/email.go index 0aecd6bc..3e94ffc9 100644 --- a/pkg/shared/client/systemconfig/email.go +++ b/pkg/shared/client/systemconfig/email.go @@ -28,7 +28,7 @@ type Email struct { } func (c *Client) GetEmailHost() (*Email, error) { - url := "/emails/internal/host/" + url := "/emails/host/internal/" res := &Email{} _, err := c.Get(url, httpclient.SetResult(res)) -- Gitee From bb3e5fa752b16ad518c482362f9bd107dec8aff7 Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Apr 2022 11:27:28 +0800 Subject: [PATCH 2239/2647] client workflow task url not correct Signed-off-by: mouuii --- pkg/microservice/cron/core/service/client/cronjob.go | 3 ++- .../cron/core/service/scheduler/cronjob_handler.go | 5 +++-- pkg/microservice/picket/client/aslan/workflow.go | 5 +++-- pkg/microservice/picket/core/public/handler/public.go | 2 +- pkg/microservice/picket/core/public/service/public.go | 4 ++-- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/cron/core/service/client/cronjob.go b/pkg/microservice/cron/core/service/client/cronjob.go index 3d596ed3..42bb62a4 100644 --- a/pkg/microservice/cron/core/service/client/cronjob.go +++ b/pkg/microservice/cron/core/service/client/cronjob.go @@ -24,6 +24,7 @@ import ( "io" "io/ioutil" "net/http" + "path" "time" "go.uber.org/zap" @@ -94,7 +95,7 @@ func (c *Client) RunPipelineTask(args *service.TaskArgs, log *zap.SugaredLogger) } func (c *Client) RunWorkflowTask(args *service.WorkflowTaskArgs, log *zap.SugaredLogger) error { - url := fmt.Sprintf("%s/workflow/workflowtask", c.APIBase) + url := path.Join(c.APIBase, "workflow/workflowtask", args.WorkflowName) log.Info("start run scheduled task..") body, err := json.Marshal(args) if err != nil { diff --git a/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go b/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go index 28448974..a16cd2af 100644 --- a/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go +++ b/pkg/microservice/cron/core/service/scheduler/cronjob_handler.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net/http" + "path" "strings" "time" @@ -250,7 +251,7 @@ func (h *CronjobHandler) registerWorkFlowJob(name, schedule string, job *service args.DistributeEnabled = job.WorkflowArgs.DistributeEnabled } scheduleJob, err := cronlib.NewJobModel(schedule, func() { - if err := h.aslanCli.ScheduleCall("workflow/workflowtask", args, log.SugaredLogger()); err != nil { + if err := h.aslanCli.ScheduleCall(path.Join("workflow/workflowtask", args.WorkflowName), args, log.SugaredLogger()); err != nil { log.Errorf("[%s]RunScheduledTask err: %v", name, err) } }) @@ -354,7 +355,7 @@ func registerCronjob(job *service.Cronjob, client *client.Client, scheduler *cro cron, _ = convertCronString(job.JobType, job.Time, job.Frequency, job.Number) } scheduleJob, err := cronlib.NewJobModel(cron, func() { - if err := client.ScheduleCall("workflow/workflowtask", args, log.SugaredLogger()); err != nil { + if err := client.ScheduleCall(path.Join("workflow/workflowtask", job.WorkflowArgs.WorkflowName), args, log.SugaredLogger()); err != nil { log.Errorf("[%s]RunScheduledTask err: %v", job.Name, err) } }) diff --git a/pkg/microservice/picket/client/aslan/workflow.go b/pkg/microservice/picket/client/aslan/workflow.go index 35d0601b..7ae400f5 100644 --- a/pkg/microservice/picket/client/aslan/workflow.go +++ b/pkg/microservice/picket/client/aslan/workflow.go @@ -20,6 +20,7 @@ import ( "fmt" "net/http" "net/url" + "path" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" @@ -58,8 +59,8 @@ func (c *Client) ListTestWorkflows(testName string, header http.Header, qs url.V return res.Body(), nil } -func (c *Client) CreateWorkflowTask(header http.Header, qs url.Values, body []byte) ([]byte, error) { - url := "/workflow/workflowtask" +func (c *Client) CreateWorkflowTask(header http.Header, qs url.Values, body []byte, workflowName string) ([]byte, error) { + url := path.Join("/workflow/workflowtask", workflowName) res, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody(body)) if err != nil { diff --git a/pkg/microservice/picket/core/public/handler/public.go b/pkg/microservice/picket/core/public/handler/public.go index 5a42387a..b0406897 100644 --- a/pkg/microservice/picket/core/public/handler/public.go +++ b/pkg/microservice/picket/core/public/handler/public.go @@ -46,7 +46,7 @@ func CreateWorkflowTask(c *gin.Context) { ctx.Logger.Errorf("Marshal err:%s", err) return } - res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), body, ctx.Logger) + res, err := service.CreateWorkflowTask(c.Request.Header, c.Request.URL.Query(), req.WorkflowName, body, ctx.Logger) if err != nil { ctx.Err = err ctx.Logger.Errorf("CreateWorkflowTask err:%s", err) diff --git a/pkg/microservice/picket/core/public/service/public.go b/pkg/microservice/picket/core/public/service/public.go index 3a29ebaa..e8e8b1c6 100644 --- a/pkg/microservice/picket/core/public/service/public.go +++ b/pkg/microservice/picket/core/public/service/public.go @@ -77,8 +77,8 @@ type WorkflowTaskDetail struct { Status string `json:"status"` } -func CreateWorkflowTask(header http.Header, qs url.Values, body []byte, _ *zap.SugaredLogger) ([]byte, error) { - return aslan.New().CreateWorkflowTask(header, qs, body) +func CreateWorkflowTask(header http.Header, qs url.Values, workflowName string, body []byte, _ *zap.SugaredLogger) ([]byte, error) { + return aslan.New().CreateWorkflowTask(header, qs, body, workflowName) } func CancelWorkflowTask(header http.Header, qs url.Values, id string, name string, _ *zap.SugaredLogger) (int, error) { -- Gitee From 6a7ad3a91334af0db98b10442edce5c6fcc310cd Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 13:52:24 +0800 Subject: [PATCH 2240/2647] add self signed certificate support for docker registry utilities in aslan Signed-off-by: Min Min --- .../core/common/service/registry/service.go | 23 +++++++++++++++++-- .../aslan/core/system/service/registry.go | 4 ++-- .../workflow/service/workflow/nsq_handlers.go | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 3e811869..77c0a848 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -18,6 +18,8 @@ package registry import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "net" @@ -79,18 +81,24 @@ type Service interface { GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (*commonmodels.DeliveryImage, error) } -func NewV2Service(provider string) Service { +func NewV2Service(provider string, tlsEnabled bool, tlsCert string) Service { + // since SWR & AWS services are provided by known provider, we assume it is signed by well known CA switch provider { case config.RegistryTypeSWR: return &swrService{} case config.RegistryTypeAWS: return &ecrService{} default: - return &v2RegistryService{} + return &v2RegistryService{ + EnableHTTPS: tlsEnabled, + CustomCert: tlsCert, + } } } type v2RegistryService struct { + EnableHTTPS bool + CustomCert string } type authClient struct { @@ -115,10 +123,21 @@ func (s *v2RegistryService) createClient(ep Endpoint, logger *zap.SugaredLogger) KeepAlive: 30 * time.Second, } + tlsConfig := &tls.Config{} + + if s.EnableHTTPS && s.CustomCert != "" { + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(s.CustomCert)) + tlsConfig.RootCAs = caCertPool + } else if !s.EnableHTTPS { + tlsConfig.InsecureSkipVerify = true + } + base := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: direct.DialContext, TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: tlsConfig, DisableKeepAlives: true, } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index d9b95e83..b6013924 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -200,7 +200,7 @@ func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { } func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, logger *zap.SugaredLogger) ([]*RepoImgResp, error) { - repos, err := registry.NewV2Service(registryInfo.RegProvider).ListRepoImages(registry.ListRepoImagesOption{ + repos, err := registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, @@ -233,7 +233,7 @@ func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, func GetRepoTags(registryInfo *commonmodels.RegistryNamespace, name string, log *zap.SugaredLogger) (*registry.ImagesResp, error) { var resp *registry.ImagesResp - repos, err := registry.NewV2Service(registryInfo.RegProvider).ListRepoImages(registry.ListRepoImagesOption{ + repos, err := registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 56392fec..d38e2df0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -925,7 +925,7 @@ func getImageInfo(productName, evnName, repoName, tag string, log *zap.SugaredLo return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) } - return registry.NewV2Service(registryInfo.RegProvider).GetImageInfo(registry.GetRepoImageDetailOption{ + return registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).GetImageInfo(registry.GetRepoImageDetailOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, -- Gitee From 09aca719f7bfe8f84e3ef732949a653475411b35 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 13:52:24 +0800 Subject: [PATCH 2241/2647] add self signed certificate support for docker registry utilities in aslan Signed-off-by: Min Min --- .../core/common/service/registry/service.go | 23 +++++++++++++++++-- .../aslan/core/system/service/registry.go | 4 ++-- .../workflow/service/workflow/nsq_handlers.go | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 3e811869..77c0a848 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -18,6 +18,8 @@ package registry import ( "context" + "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "net" @@ -79,18 +81,24 @@ type Service interface { GetImageInfo(option GetRepoImageDetailOption, log *zap.SugaredLogger) (*commonmodels.DeliveryImage, error) } -func NewV2Service(provider string) Service { +func NewV2Service(provider string, tlsEnabled bool, tlsCert string) Service { + // since SWR & AWS services are provided by known provider, we assume it is signed by well known CA switch provider { case config.RegistryTypeSWR: return &swrService{} case config.RegistryTypeAWS: return &ecrService{} default: - return &v2RegistryService{} + return &v2RegistryService{ + EnableHTTPS: tlsEnabled, + CustomCert: tlsCert, + } } } type v2RegistryService struct { + EnableHTTPS bool + CustomCert string } type authClient struct { @@ -115,10 +123,21 @@ func (s *v2RegistryService) createClient(ep Endpoint, logger *zap.SugaredLogger) KeepAlive: 30 * time.Second, } + tlsConfig := &tls.Config{} + + if s.EnableHTTPS && s.CustomCert != "" { + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(s.CustomCert)) + tlsConfig.RootCAs = caCertPool + } else if !s.EnableHTTPS { + tlsConfig.InsecureSkipVerify = true + } + base := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: direct.DialContext, TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: tlsConfig, DisableKeepAlives: true, } diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index d9b95e83..b6013924 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -200,7 +200,7 @@ func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { } func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, logger *zap.SugaredLogger) ([]*RepoImgResp, error) { - repos, err := registry.NewV2Service(registryInfo.RegProvider).ListRepoImages(registry.ListRepoImagesOption{ + repos, err := registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, @@ -233,7 +233,7 @@ func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, func GetRepoTags(registryInfo *commonmodels.RegistryNamespace, name string, log *zap.SugaredLogger) (*registry.ImagesResp, error) { var resp *registry.ImagesResp - repos, err := registry.NewV2Service(registryInfo.RegProvider).ListRepoImages(registry.ListRepoImagesOption{ + repos, err := registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 56392fec..d38e2df0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -925,7 +925,7 @@ func getImageInfo(productName, evnName, repoName, tag string, log *zap.SugaredLo return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) } - return registry.NewV2Service(registryInfo.RegProvider).GetImageInfo(registry.GetRepoImageDetailOption{ + return registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).GetImageInfo(registry.GetRepoImageDetailOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, -- Gitee From 8b87e26042fd0b58a56a155131039570175c608d Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 14:26:35 +0800 Subject: [PATCH 2242/2647] easier way to skip this Signed-off-by: Min Min --- .../aslan/core/common/service/registry/service.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 77c0a848..18b48fc4 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -19,7 +19,6 @@ package registry import ( "context" "crypto/tls" - "crypto/x509" "encoding/json" "fmt" "net" @@ -125,11 +124,7 @@ func (s *v2RegistryService) createClient(ep Endpoint, logger *zap.SugaredLogger) tlsConfig := &tls.Config{} - if s.EnableHTTPS && s.CustomCert != "" { - caCertPool := x509.NewCertPool() - caCertPool.AppendCertsFromPEM([]byte(s.CustomCert)) - tlsConfig.RootCAs = caCertPool - } else if !s.EnableHTTPS { + if (s.EnableHTTPS && s.CustomCert != "") || !s.EnableHTTPS { tlsConfig.InsecureSkipVerify = true } -- Gitee From fe57489773085be799b548a64e3f2398d427405d Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 14:36:55 +0800 Subject: [PATCH 2243/2647] debug logs Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/registry/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 18b48fc4..893aec3b 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -111,6 +111,7 @@ type authClient struct { } func (s *v2RegistryService) createClient(ep Endpoint, logger *zap.SugaredLogger) (cli *authClient, err error) { + fmt.Printf("creating registry client for ep: %s, cert: %s, enableTLS: %s", ep, s.CustomCert, s.EnableHTTPS) endpointURL, err := url.Parse(ep.Addr) if err != nil { return -- Gitee From 14bfe4ce1df8d372a0639ae04a1fa250088809f8 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 14:37:42 +0800 Subject: [PATCH 2244/2647] revert Signed-off-by: Min Min --- .../aslan/core/common/service/registry/service.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/registry/service.go b/pkg/microservice/aslan/core/common/service/registry/service.go index 893aec3b..77c0a848 100644 --- a/pkg/microservice/aslan/core/common/service/registry/service.go +++ b/pkg/microservice/aslan/core/common/service/registry/service.go @@ -19,6 +19,7 @@ package registry import ( "context" "crypto/tls" + "crypto/x509" "encoding/json" "fmt" "net" @@ -111,7 +112,6 @@ type authClient struct { } func (s *v2RegistryService) createClient(ep Endpoint, logger *zap.SugaredLogger) (cli *authClient, err error) { - fmt.Printf("creating registry client for ep: %s, cert: %s, enableTLS: %s", ep, s.CustomCert, s.EnableHTTPS) endpointURL, err := url.Parse(ep.Addr) if err != nil { return @@ -125,7 +125,11 @@ func (s *v2RegistryService) createClient(ep Endpoint, logger *zap.SugaredLogger) tlsConfig := &tls.Config{} - if (s.EnableHTTPS && s.CustomCert != "") || !s.EnableHTTPS { + if s.EnableHTTPS && s.CustomCert != "" { + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM([]byte(s.CustomCert)) + tlsConfig.RootCAs = caCertPool + } else if !s.EnableHTTPS { tlsConfig.InsecureSkipVerify = true } -- Gitee From 23e951c6c96051386cf32b92ce599dfe8da52b62 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 15:13:44 +0800 Subject: [PATCH 2245/2647] bugfix for hubagent connection problem Signed-off-by: Min Min --- pkg/microservice/hubagent/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index c86c996e..415bf01c 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -76,7 +76,7 @@ func Serve(ctx context.Context) error { } func initDinD() { - client := aslan.New(config2.AslanBaseAddr()) + client := aslan.NewExternal(config2.AslanBaseAddr(), "") ls, err := client.ListRegistries() if err != nil { -- Gitee From fb98b0430febade46cbf8b4e6092805b592140e0 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 15:13:44 +0800 Subject: [PATCH 2246/2647] bugfix for hubagent connection problem Signed-off-by: Min Min --- pkg/microservice/hubagent/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/hubagent/server/server.go b/pkg/microservice/hubagent/server/server.go index c86c996e..415bf01c 100644 --- a/pkg/microservice/hubagent/server/server.go +++ b/pkg/microservice/hubagent/server/server.go @@ -76,7 +76,7 @@ func Serve(ctx context.Context) error { } func initDinD() { - client := aslan.New(config2.AslanBaseAddr()) + client := aslan.NewExternal(config2.AslanBaseAddr(), "") ls, err := client.ListRegistries() if err != nil { -- Gitee From a03ed38aaa73cd17f54049894576216457606f35 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 19 Apr 2022 15:46:36 +0800 Subject: [PATCH 2247/2647] fix dind version in attached cluster Signed-off-by: zhangyifei --- pkg/microservice/aslan/core/common/service/kube/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 254e0c89..14305d01 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -19,7 +19,6 @@ package kube import ( "bytes" "fmt" - config2 "github.com/koderover/zadig/pkg/config" "net/url" "strings" "text/template" @@ -29,6 +28,7 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + config2 "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -510,7 +510,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: dind - image: ccr.ccs.tencentyun.com/koderover-public/library-docker:stable-dind + image: ccr.ccs.tencentyun.com/koderover-public/docker:20.10.14-dind args: - --mtu=1376 env: @@ -762,7 +762,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: dind - image: ccr.ccs.tencentyun.com/koderover-public/library-docker:stable-dind + image: ccr.ccs.tencentyun.com/koderover-public/docker:20.10.14-dind args: - --mtu=1376 env: -- Gitee From 851b52630838120c70071419952886d80ba5cbf2 Mon Sep 17 00:00:00 2001 From: zhangyifei Date: Tue, 19 Apr 2022 15:46:36 +0800 Subject: [PATCH 2248/2647] fix dind version in attached cluster Signed-off-by: zhangyifei --- pkg/microservice/aslan/core/common/service/kube/service.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 254e0c89..14305d01 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -19,7 +19,6 @@ package kube import ( "bytes" "fmt" - config2 "github.com/koderover/zadig/pkg/config" "net/url" "strings" "text/template" @@ -29,6 +28,7 @@ import ( "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + config2 "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -510,7 +510,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: dind - image: ccr.ccs.tencentyun.com/koderover-public/library-docker:stable-dind + image: ccr.ccs.tencentyun.com/koderover-public/docker:20.10.14-dind args: - --mtu=1376 env: @@ -762,7 +762,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: dind - image: ccr.ccs.tencentyun.com/koderover-public/library-docker:stable-dind + image: ccr.ccs.tencentyun.com/koderover-public/docker:20.10.14-dind args: - --mtu=1376 env: -- Gitee From 83f65674a43294edd4af244a358942a83a324712 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 19 Apr 2022 16:12:53 +0800 Subject: [PATCH 2249/2647] optimize relase naming rule update logic Signed-off-by: allenshen --- .../core/environment/service/environment.go | 13 ++----- .../environment/service/environment_update.go | 21 ++++++++-- .../aslan/core/service/handler/service.go | 2 +- .../aslan/core/service/service/service.go | 38 ++++++++++++++----- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 5c178e6f..bf358973 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1978,7 +1978,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { for _, services := range productInfo.Services { for _, service := range services { - if err = UninstallService(hc, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, service.ServiceName, service.Revision, true); err != nil { + if err = UninstallServiceByName(hc, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, service.ServiceName, service.Revision, true); err != nil { log.Errorf("UninstallRelease err:%v", err) } } @@ -2183,13 +2183,8 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo failedServices.Store(serviceName, err.Error()) return } - releaseName := util.GeneReleaseName(templateSvc.GetReleaseNaming(), product.ProductName, productInfo.Namespace, product.EnvName, serviceName) - log.Infof("uninstall release: %s", releaseName) - if errUninstall := helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: releaseName, - Namespace: product.Namespace, - Wait: true, - }); errUninstall != nil { + log.Infof("uninstall release for service: %s", serviceName) + if errUninstall := UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, templateSvc, false); errUninstall != nil { errStr := fmt.Sprintf("helm uninstall service %s err: %s", serviceName, errUninstall) failedServices.Store(serviceName, errStr) log.Error(errStr) @@ -3289,7 +3284,7 @@ func updateProductGroup(username, productName, envName string, productResp *comm // uninstall services for serviceName, serviceRevision := range deletedSvcRevision { - if err = UninstallService(helmClient, productResp.ProductName, productResp.Namespace, productResp.EnvName, serviceName, serviceRevision, true); err != nil { + if err = UninstallServiceByName(helmClient, productResp.ProductName, productResp.Namespace, productResp.EnvName, serviceName, serviceRevision, true); err != nil { log.Errorf("UninstallRelease err:%v", err) return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index fbe11cf4..38eed87e 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -33,7 +33,7 @@ import ( "go.uber.org/zap" ) -func UninstallService(helmClient helmclient.Client, productName, namespace, envName, serviceName string, revision int64, force bool) error { +func UninstallServiceByName(helmClient helmclient.Client, productName, namespace, envName, serviceName string, revision int64, force bool) error { revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, Revision: revision, @@ -42,8 +42,12 @@ func UninstallService(helmClient helmclient.Client, productName, namespace, envN if err != nil { return fmt.Errorf("failed to find service: %s with revision: %d, err: %s", serviceName, revision, err) } + return UninstallService(helmClient, productName, namespace, envName, revisionSvc, force) +} + +func UninstallService(helmClient helmclient.Client, productName, namespace, envName string, revisionSvc *commonmodels.Service, force bool) error { return helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNaming(), productName, namespace, envName, serviceName), + ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNaming(), productName, namespace, envName, revisionSvc.ServiceName), Namespace: namespace, Wait: true, Force: force, @@ -98,9 +102,18 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string return err } - err = UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, productSvc.ServiceName, productSvc.Revision, true) + prodSvcTemp, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ServiceName: serviceName, Revision: productSvc.Revision, ProductName: productInfo.ProductName}) if err != nil { - return fmt.Errorf("failed to uninstall release for service: %s, err: %s", productSvc.ServiceName, err) + return fmt.Errorf("failed to get service: %s with revision: %d, err: %s", serviceName, productSvc.Revision, productInfo.ProductName) + } + + // nothing would happen if release naming rule are same + if prodSvcTemp.GetReleaseNaming() == templateSvc.GetReleaseNaming() { + return nil + } + + if err := UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, prodSvcTemp, true); err != nil { + return fmt.Errorf("helm uninstall service: %s in namespace: %s, err: %s", serviceName, productInfo.Namespace, err) } param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 3cb99fb3..73e9c0cb 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -161,7 +161,7 @@ func HelmReleaseNaming(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") return } - ctx.Err = svcservice.UpdateReleaseNamingRule(projectName, args, ctx.Logger) + ctx.Err = svcservice.UpdateReleaseNamingRule(ctx.UserName, ctx.RequestID, projectName, args, ctx.Logger) } func DeleteServiceTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index fb71b70b..1bf62b2a 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -932,7 +932,7 @@ func YamlValidator(args *YamlValidatorReq) []string { return errorDetails } -func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule, log *zap.SugaredLogger) error { +func UpdateReleaseNamingRule(userName, requestID, projectName string, args *ReleaseNamingRule, log *zap.SugaredLogger) error { serviceTemplate, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: args.ServiceName, Revision: 0, @@ -944,10 +944,24 @@ func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule, log *z return err } - oldReleaseNamingRule := serviceTemplate.GetReleaseNaming() - // nothing would happen if naming rule keeps the same - if oldReleaseNamingRule == args.NamingRule { - return nil + // check if namings rule changes for services deployed in envs + if serviceTemplate.GetReleaseNaming() == args.NamingRule { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: projectName, + }) + if err != nil { + return fmt.Errorf("failed to list envs for product: %s, err: %s", projectName, err) + } + modified := false + for _, product := range products { + if pSvc, ok := product.GetServiceMap()[args.ServiceName]; ok && pSvc.Revision != serviceTemplate.Revision { + modified = true + break + } + } + if !modified { + return nil + } } serviceTemplate.ReleaseNaming = args.NamingRule @@ -974,11 +988,15 @@ func UpdateReleaseNamingRule(projectName string, args *ReleaseNamingRule, log *z return fmt.Errorf("failed to update relase naming for service: %s, err: %s", args.ServiceName, err) } - // reinstall all services - err = service.UpdateSvcInAllEnvs(projectName, args.ServiceName, serviceTemplate) - if err != nil { - return fmt.Errorf("failed to re install service: %s, err: %s", args.ServiceName) - } + go func() { + // reinstall services in envs + err = service.UpdateSvcInAllEnvs(projectName, args.ServiceName, serviceTemplate) + if err != nil { + title := fmt.Sprintf("服务 [%s] 重建失败", args.ServiceName) + commonservice.SendErrorMessage(userName, title, requestID, err, log) + } + }() + return nil } -- Gitee From cefcc8c74ab618921bc6b03cf6b8622ecb5048ee Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Apr 2022 16:18:28 +0800 Subject: [PATCH 2250/2647] config permission Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 17 ++++++++++ .../aslan/core/workflow/handler/policy.yaml | 33 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 06f03011..a3d0a002 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -32,22 +32,31 @@ rules: value: "false" - method: GET endpoint: "/api/aslan/environment/kube/workloads" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/service/workloads" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/export/service" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/configmaps" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/kube/pods/?*/events" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/kube/events" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/project/products/?*/services" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/revision/products" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/environments/?*/workloads" resourceType: "Environment" @@ -78,6 +87,7 @@ rules: value: "false" - method: GET endpoint: "/api/aslan/environment/diff/products/?*/service/?*" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/environment/configmaps/?*" resourceType: "Environment" @@ -154,6 +164,7 @@ rules: value: "false" - method: PUT endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" - method: PUT endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" @@ -180,8 +191,10 @@ rules: value: "false" - method: PUT endpoint: "/api/aslan/service/workloads" + resourceType: "Environment" - method: GET endpoint: "/api/aslan/project/products/?*/services" + resourceType: "Environment" - method: PUT endpoint: "/api/aslan/environment/environments/?*/registry" resourceType: "Environment" @@ -216,6 +229,7 @@ rules: rules: - method: PUT endpoint: "/api/aslan/environment/environments" + resourceType: "Environment" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" resourceType: "Environment" @@ -271,10 +285,13 @@ rules: value: "false" - method: PUT endpoint: "/api/aslan/environment/configmaps" + resourceType: "Environment" - method: POST endpoint: "/api/aslan/environment/configmaps" + resourceType: "Environment" - method: POST endpoint: "/api/aslan/workflow/servicetask" + resourceType: "Environment" - action: delete_environment alias: "删除" description: "" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index aae2b443..742f5a53 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -14,36 +14,52 @@ rules: value: "placeholder" - method: GET endpoint: "api/aslan/workflow/workflow" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/workflow/find/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/directory/workflowTask" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/v3" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/v3/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/v3/?*/args" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*" + resourceType: "Workflow" - action: edit_workflow alias: "编辑" description: "" @@ -60,20 +76,26 @@ rules: value: "placeholder" - method: GET endpoint: "/api/aslan/workflow/workflow/preset/?*" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/testing/testdetail" + resourceType: "Workflow" - method: PUT endpoint: "/api/aslan/workflow/v3/?*" + resourceType: "Workflow" - action: create_workflow alias: "新建" description: "" rules: - method: POST endpoint: "api/aslan/workflow/workflow" + resourceType: "Workflow" - method: PUT endpoint: "/api/aslan/workflow/workflow/old/?*/new/?*" + resourceType: "Workflow" - method: POST endpoint: "/api/directory/workflowTask/create" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" @@ -82,16 +104,20 @@ rules: value: "placeholder" - method: GET endpoint: "/api/aslan/testing/testdetail" + resourceType: "Workflow" - method: POST endpoint: "/api/aslan/workflow/v3" + resourceType: "Workflow" - action: delete_workflow alias: "删除" description: "" rules: - method: DELETE endpoint: "/api/aslan/workflow/workflow/?*" + resourceType: "Workflow" - method: DELETE endpoint: "/api/aslan/workflow/v3/?*" + resourceType: "Workflow" - action: run_workflow alias: "执行" description: "" @@ -114,8 +140,10 @@ rules: resourceType: "Workflow" - method: POST endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" + resourceType: "Workflow" - method: POST endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/environment/environments" resourceType: "Environment" @@ -124,11 +152,16 @@ rules: value: "placeholder" - method: GET endpoint: "/api/aslan/project/products/?*/services" + resourceType: "Workflow" - method: GET endpoint: "/api/aslan/workflow/v3/?*/args" + resourceType: "Workflow" - method: POST endpoint: "/api/aslan/workflow/v3/workflowtask" + resourceType: "Workflow" - method: POST endpoint: "/api/aslan/workflow/v3/workflowtask/id/?*/name/?*/restart" + resourceType: "Workflow" - method: DELETE endpoint: "/api/aslan/workflow/v3/workflowtask/id/?*/name/?*" + resourceType: "Workflow" -- Gitee From a2cc5d8fd6c37bc101088e71a569f2bd99c6f716 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 19 Apr 2022 16:26:43 +0800 Subject: [PATCH 2251/2647] fix deploy failed for host porjects Signed-off-by: allenshen --- .../aslan/core/common/service/environment.go | 2 +- .../core/workflow/service/workflow/workflow_task.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 8e41ce40..d95579af 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -204,7 +204,7 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, } // for helm service, only show deploys/stss created by zadig - if projectInfo.ProductFeature != nil && projectInfo.ProductFeature.DeployType == setting.SourceFromHelm { + if projectInfo.ProductFeature != nil && projectInfo.ProductFeature.DeployType == setting.HelmDeployType { filterArray = append(filterArray, func(workloads []*Workload) []*Workload { releaseNameMap, err := GetReleaseNameToServiceNameMap(productInfo) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 143fd9eb..e2936b7f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1407,12 +1407,12 @@ func formatDistributeSubtasks(serviceModule *commonmodels.ServiceModuleTarget, r } releaseName := "" if repoInfo.DeployEnabled { - svcMap := envInfo.GetServiceMap() - pSvc, ok := svcMap[serviceModule.ServiceName] - if !ok { - return nil, fmt.Errorf("can't find service: %s in product: %s:%s", serviceModule.ServiceName, productName, repoInfo.DeployEnv) - } - if pSvc.Type == setting.HelmDeployType { + if productInfo.ProductFeature != nil && productInfo.ProductFeature.DeployType == setting.HelmDeployType { + svcMap := envInfo.GetServiceMap() + pSvc, ok := svcMap[serviceModule.ServiceName] + if !ok { + return nil, fmt.Errorf("can't find service: %s in product: %s:%s", serviceModule.ServiceName, productName, repoInfo.DeployEnv) + } templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceModule.ServiceName, Revision: pSvc.Revision, -- Gitee From d36d22b5dbd55f9312d4999d185435ffd1fb40e1 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 19 Apr 2022 16:27:23 +0800 Subject: [PATCH 2252/2647] fix the third-party login entry is missing Signed-off-by: panxunying --- .../core/connector/handler/connector.go | 7 +++++ .../core/connector/handler/router.go | 1 + .../core/connector/service/connector.go | 28 +++++++++++++++++++ .../user/core/service/login/third_party.go | 2 +- pkg/shared/client/systemconfig/connector.go | 4 +-- 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/systemconfig/core/connector/handler/connector.go b/pkg/microservice/systemconfig/core/connector/handler/connector.go index 7fd5e232..6d5d69e6 100644 --- a/pkg/microservice/systemconfig/core/connector/handler/connector.go +++ b/pkg/microservice/systemconfig/core/connector/handler/connector.go @@ -49,6 +49,13 @@ func ListConnectors(c *gin.Context) { ctx.Resp, ctx.Err = service.ListConnectors(encryptedKey, ctx.Logger) } +func ListConnectorsInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListConnectorsInternal(ctx.Logger) +} + func DeleteConnector(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/connector/handler/router.go b/pkg/microservice/systemconfig/core/connector/handler/router.go index 91626d3b..1795bdd0 100644 --- a/pkg/microservice/systemconfig/core/connector/handler/router.go +++ b/pkg/microservice/systemconfig/core/connector/handler/router.go @@ -27,6 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { connector.POST("", CreateConnector) connector.GET("", ListConnectors) + connector.GET("/internal", ListConnectorsInternal) connector.GET("/:id", GetConnector) connector.PUT("/:id", UpdateConnector) connector.DELETE("/:id", DeleteConnector) diff --git a/pkg/microservice/systemconfig/core/connector/service/connector.go b/pkg/microservice/systemconfig/core/connector/service/connector.go index 838087e0..f5e4fb9d 100644 --- a/pkg/microservice/systemconfig/core/connector/service/connector.go +++ b/pkg/microservice/systemconfig/core/connector/service/connector.go @@ -28,6 +28,34 @@ import ( "github.com/koderover/zadig/pkg/tool/crypto" ) +func ListConnectorsInternal(logger *zap.SugaredLogger) ([]*Connector, error) { + cs, err := orm.NewConnectorColl().List() + if err != nil { + logger.Errorf("Failed to list connectors, err: %s", err) + return nil, err + } + + var res []*Connector + for _, c := range cs { + cf := make(map[string]interface{}) + err = json.Unmarshal([]byte(c.Config), &cf) + if err != nil { + logger.Errorf("Failed to unmarshal config, err: %s", err) + continue + } + res = append(res, &Connector{ + ConnectorBase: ConnectorBase{ + Type: ConnectorType(c.Type), + }, + ID: c.ID, + Name: c.Name, + Config: cf, + }) + } + + return res, nil +} + func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connector, error) { aesKey, err := aslan.New(config.AslanServiceAddress()).GetTextFromEncryptedKey(encryptedKey) if err != nil { diff --git a/pkg/microservice/user/core/service/login/third_party.go b/pkg/microservice/user/core/service/login/third_party.go index 061fc0dc..5d0919b4 100644 --- a/pkg/microservice/user/core/service/login/third_party.go +++ b/pkg/microservice/user/core/service/login/third_party.go @@ -26,7 +26,7 @@ type enabledStatus struct { } func ThirdPartyLoginEnabled() *enabledStatus { - connectors, err := systemconfig.New().ListConnectors() + connectors, err := systemconfig.New().ListConnectorsInternal() if err != nil { log.Warnf("Failed to list connectors, err: %s", err) } diff --git a/pkg/shared/client/systemconfig/connector.go b/pkg/shared/client/systemconfig/connector.go index 374102da..13bd369d 100644 --- a/pkg/shared/client/systemconfig/connector.go +++ b/pkg/shared/client/systemconfig/connector.go @@ -55,8 +55,8 @@ func (c *Client) GetLDAPConnector(id string) (*Connector, error) { return res, err } -func (c *Client) ListConnectors() ([]*Connector, error) { - url := "/connectors" +func (c *Client) ListConnectorsInternal() ([]*Connector, error) { + url := "/connectors/internal" res := make([]*Connector, 0) _, err := c.Get(url, httpclient.SetResult(&res)) -- Gitee From 181c13691ed75d68c51415a91766ef68f7950c63 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 16:30:01 +0800 Subject: [PATCH 2253/2647] add disable cronjob logic when deleting workflow Signed-off-by: Min Min --- .../aslan/core/common/service/workflow.go | 6 ++++++ .../aslan/core/workflow/service/workflow/cronjob.go | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 5a5a2197..952fcf10 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -18,6 +18,7 @@ package service import ( "fmt" + workflow2 "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "sync" "github.com/hashicorp/go-multierror" @@ -98,6 +99,11 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log.Errorf("Failed to process webhook, err: %s", err) } + err = workflow2.DisableCronjobForWorkflow(workflow) + if err != nil { + log.Errorf("Failed to stop cronjob for workflow: %s, error: %s", workflow.Name, err) + } + go gerrit.DeleteGerritWebhook(workflow, log) //删除所属的所有定时任务 diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go b/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go index f9ba13a4..7026bbfb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go @@ -64,6 +64,17 @@ func HandleCronjob(workflow *commonmodels.Workflow, log *zap.SugaredLogger) erro return nil } +func DisableCronjobForWorkflow(workflow *commonmodels.Workflow) error { + payload := &commonservice.CronjobPayload{ + Name: workflow.Name, + JobType: config.WorkflowCronjob, + Action: setting.TypeDisableCronjob, + } + + pl, _ := json.Marshal(payload) + return nsq.Publish(setting.TopicCronjob, pl) +} + func UpdateCronjob(parentName, parentType, productName string, schedule *commonmodels.ScheduleCtrl, log *zap.SugaredLogger) (deleteList []string, err error) { idMap := make(map[string]bool) deleteList = make([]string, 0) @@ -135,7 +146,7 @@ func UpdateCronjob(parentName, parentType, productName string, schedule *commonm return deleteList, nil } -func DeleteCronjob(parentName, parentType string) error { +func DeleteCronjob(parentName, parentType string, log *zap.SugaredLogger) error { return commonrepo.NewCronjobColl().Delete(&commonrepo.CronjobDeleteOption{ ParentName: parentName, ParentType: parentType, -- Gitee From 33d1864d7c6737fb80904374e68be4eef0ae1b43 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 19 Apr 2022 16:34:17 +0800 Subject: [PATCH 2254/2647] minor changes Signed-off-by: Min Min --- .../aslan/core/common/service/workflow.go | 16 ++++++++++++++-- .../core/workflow/service/workflow/cronjob.go | 13 +------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 952fcf10..e8a2e2ba 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -17,8 +17,8 @@ limitations under the License. package service import ( + "encoding/json" "fmt" - workflow2 "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "sync" "github.com/hashicorp/go-multierror" @@ -30,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/collaboration" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -99,7 +100,7 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, log.Errorf("Failed to process webhook, err: %s", err) } - err = workflow2.DisableCronjobForWorkflow(workflow) + err = DisableCronjobForWorkflow(workflow) if err != nil { log.Errorf("Failed to stop cronjob for workflow: %s, error: %s", workflow.Name, err) } @@ -160,6 +161,17 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, return nil } +func DisableCronjobForWorkflow(workflow *models.Workflow) error { + payload := &CronjobPayload{ + Name: workflow.Name, + JobType: config.WorkflowCronjob, + Action: setting.TypeDisableCronjob, + } + + pl, _ := json.Marshal(payload) + return nsq.Publish(setting.TopicCronjob, pl) +} + func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger *zap.SugaredLogger) error { currentSet := toHookSet(currentHooks) updatedSet := toHookSet(updatedHooks) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go b/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go index 7026bbfb..f9ba13a4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/cronjob.go @@ -64,17 +64,6 @@ func HandleCronjob(workflow *commonmodels.Workflow, log *zap.SugaredLogger) erro return nil } -func DisableCronjobForWorkflow(workflow *commonmodels.Workflow) error { - payload := &commonservice.CronjobPayload{ - Name: workflow.Name, - JobType: config.WorkflowCronjob, - Action: setting.TypeDisableCronjob, - } - - pl, _ := json.Marshal(payload) - return nsq.Publish(setting.TopicCronjob, pl) -} - func UpdateCronjob(parentName, parentType, productName string, schedule *commonmodels.ScheduleCtrl, log *zap.SugaredLogger) (deleteList []string, err error) { idMap := make(map[string]bool) deleteList = make([]string, 0) @@ -146,7 +135,7 @@ func UpdateCronjob(parentName, parentType, productName string, schedule *commonm return deleteList, nil } -func DeleteCronjob(parentName, parentType string, log *zap.SugaredLogger) error { +func DeleteCronjob(parentName, parentType string) error { return commonrepo.NewCronjobColl().Delete(&commonrepo.CronjobDeleteOption{ ParentName: parentName, ParentType: parentType, -- Gitee From 7a6c604598a03a240fc17766a3bf4db0a608eead Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Apr 2022 16:56:02 +0800 Subject: [PATCH 2255/2647] remove publicKey prilivige permission Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index e67be35e..a6048190 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -124,10 +124,6 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/aslan/project/products"}, }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/system/rsaKey/publicKey"}, - }, { Methods: []string{"POST"}, Endpoints: []string{"api/aslan/system/cleanCache/cron"}, -- Gitee From a88bb482a59ea2dc1146eff76d6f6574b1ba71b4 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 19 Apr 2022 17:19:23 +0800 Subject: [PATCH 2256/2647] fix load github yaml error Signed-off-by: panxunying --- pkg/shared/client/systemconfig/codehost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index e465a50d..30592747 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -90,7 +90,7 @@ func (c *Client) UpdateCodeHost(id int, codehost *CodeHost) error { } func (c *Client) GetCodeHostByAddressAndOwner(address, owner, source string) (*CodeHost, error) { - url := "/codehosts" + url := "/codehosts/internal" res := make([]*CodeHost, 0) -- Gitee From 8d9569a84f2035a7792bf872628763c2d13717b2 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 19 Apr 2022 18:32:22 +0800 Subject: [PATCH 2257/2647] add git binary file to aslan contianer Signed-off-by: allenshen --- docker/service/aslan.Dockerfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/service/aslan.Dockerfile b/docker/service/aslan.Dockerfile index 7921a7ff..f8d84fdb 100644 --- a/docker/service/aslan.Dockerfile +++ b/docker/service/aslan.Dockerfile @@ -1,6 +1,8 @@ -#alpine-curl.Dockerfile +#alpine-git.Dockerfile -# install docker client +RUN apk --no-cache add curl + +# install ali-acr plugin RUN curl -fsSL "https://resources.koderover.com/helm-acr_0.8.2_linux_amd64.tar.gz" -o helm-acr.tar.gz &&\ mkdir -p /app/.helm/helmplugin/helm-acr &&\ tar -xvzf helm-acr.tar.gz -C /app/.helm/helmplugin/helm-acr &&\ -- Gitee From 74eabd0b12448badce968987759cb7bdcbe2665c Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 19 Apr 2022 19:30:13 +0800 Subject: [PATCH 2258/2647] fix distribution error Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow_task.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 143fd9eb..efca3ab4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2240,14 +2240,14 @@ func getServiceNaming(projectName, envName, serviceName string) (string, error) if err != nil { return "", err } + if productInfo.Source != setting.SourceFromHelm { + return "", nil + } svcMap := productInfo.GetServiceMap() pSvc, ok := svcMap[serviceName] if !ok { return "", fmt.Errorf("can't find service: %s in product: %s:%s", serviceName, projectName, envName) } - if pSvc.Type != setting.HelmDeployType { - return "", nil - } templateSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, Revision: pSvc.Revision, -- Gitee From 3f419f0c12b71df0dfb5ff79b0c306cb1a22520f Mon Sep 17 00:00:00 2001 From: mouuii Date: Tue, 19 Apr 2022 20:35:41 +0800 Subject: [PATCH 2259/2647] load template service bug Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/handler/service_policy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml index 853149a3..5e2d3862 100644 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -54,6 +54,8 @@ rules: endpoint: "/api/aslan/service/services" - method: POST endpoint: "/api/aslan/service/loader/load/?*/?*" + - method: POST + endpoint: "api/aslan/service/template/load" - method: POST endpoint: "/api/aslan/service/helm/services" - method: POST -- Gitee From 839c7926f2c488216b06a893d309244a6e9f2f1e Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 19 Apr 2022 20:39:57 +0800 Subject: [PATCH 2260/2647] fix get jira info error Signed-off-by: panxunying --- pkg/microservice/systemconfig/core/jira/handler/jira.go | 6 ++++++ pkg/microservice/systemconfig/core/jira/handler/router.go | 1 + pkg/microservice/systemconfig/core/jira/service/jira.go | 4 ++++ pkg/shared/client/systemconfig/jira.go | 2 +- 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/systemconfig/core/jira/handler/jira.go b/pkg/microservice/systemconfig/core/jira/handler/jira.go index 12bec496..b97b26d4 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/jira.go +++ b/pkg/microservice/systemconfig/core/jira/handler/jira.go @@ -42,6 +42,12 @@ func GetJira(c *gin.Context) { ctx.Resp, ctx.Err = service.GeJira(encryptedKey, ctx.Logger) } +func GetJiraInternal(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + ctx.Resp, ctx.Err = service.GeJiraInternal(ctx.Logger) +} + func CreateJira(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/systemconfig/core/jira/handler/router.go b/pkg/microservice/systemconfig/core/jira/handler/router.go index dc18be77..6086766e 100644 --- a/pkg/microservice/systemconfig/core/jira/handler/router.go +++ b/pkg/microservice/systemconfig/core/jira/handler/router.go @@ -26,6 +26,7 @@ func (*Router) Inject(router *gin.RouterGroup) { jira := router.Group("jira") { jira.GET("", GetJira) + jira.GET("/internal", GetJiraInternal) jira.POST("", CreateJira) jira.PATCH("", UpdateJira) jira.DELETE("", DeleteJira) diff --git a/pkg/microservice/systemconfig/core/jira/service/jira.go b/pkg/microservice/systemconfig/core/jira/service/jira.go index 0e3b525c..2b999406 100644 --- a/pkg/microservice/systemconfig/core/jira/service/jira.go +++ b/pkg/microservice/systemconfig/core/jira/service/jira.go @@ -50,6 +50,10 @@ func GeJira(encryptedKey string, log *zap.SugaredLogger) (*models.Jira, error) { return jira, nil } +func GeJiraInternal(_ *zap.SugaredLogger) (*models.Jira, error) { + return mongodb.NewJiraColl().GetJira() +} + func CreateJira(jira *models.Jira, _ *zap.SugaredLogger) (*models.Jira, error) { jira.CreatedAt = time.Now().Unix() jira.UpdatedAt = time.Now().Unix() diff --git a/pkg/shared/client/systemconfig/jira.go b/pkg/shared/client/systemconfig/jira.go index fb2698b8..806d6204 100644 --- a/pkg/shared/client/systemconfig/jira.go +++ b/pkg/shared/client/systemconfig/jira.go @@ -31,7 +31,7 @@ type JiraInfo struct { } func (c *Client) GetJiraInfo() (*JiraInfo, error) { - url := "/jira" + url := "/jira/internal" jira := &JiraInfo{} _, err := c.Get(url, httpclient.SetResult(jira)) -- Gitee From d72018bfe7fbe77472d92192d713d16b52eab0e5 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 19 Apr 2022 23:40:03 +0800 Subject: [PATCH 2261/2647] fix delete collaboration instance error Signed-off-by: panxunying --- .../collaboration/repository/mongodb/collaboration_instance.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 69ccfd79..5a7ba63e 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -144,7 +144,7 @@ func (c *CollaborationInstanceColl) BulkDelete(opt CollaborationInstanceListOpti for _, findOpt := range opt.FindOpts { condition = append(condition, bson.M{ "project_name": findOpt.ProjectName, - "user_uid": findOpt.UserUID, + "user_uid": bson.M{"$in": findOpt.UserUID}, "collaboration_name": findOpt.Name, }) } -- Gitee From 1766fa814563cac2e5c048d2937bf11f1c635113 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 19 Apr 2022 23:44:12 +0800 Subject: [PATCH 2262/2647] fix helm multiple modules deploy bug Signed-off-by: allenshen --- .../core/service/taskcontroller/task_handler.go | 4 ++-- .../warpdrive/core/service/taskplugin/deploy_helm.go | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 4d7fc382..6ac2be48 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -309,7 +309,7 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren //tasks been preprocessed, map[serviceName]=[]Tasks pluginsByService := make(map[string]*plugins.HelmDeployTaskPlugin) // helm deploy plugins map[fullServiceName]=>HelmDeployPlugin - helmDeployPlugins := make(map[string]*plugins.HelmDeployTaskPlugin, 0) + helmDeployPlugins := make(map[string]*plugins.HelmDeployTaskPlugin) // preprocess subTasks, make batchTask with multiple subTasks // eg: multiple deploys of same helm chart @@ -338,7 +338,7 @@ func (h *ExecHandler) runStage(stagePosition int, stage *common.Stage, concurren for serviceName, subTask := range stage.SubTasks { if deployPlugin, ok := helmDeployPlugins[serviceName]; ok { svcName := deployPlugin.Task.ServiceName - pluginsByService[svcName].ContentPlugins = append(pluginsByService[svcName].ContentPlugins, helmDeployPlugins[serviceName]) + pluginsByService[svcName].ContentPlugins = append(pluginsByService[svcName].ContentPlugins, deployPlugin) if !preProcessedServices.Has(svcName) { xl.Infof("new batch sub task of service name: %s, type: %s", serviceName, stage.TaskType) batchTask := NewTask(ctx, h.executeTask, pluginsByService[svcName], subTask, stagePosition, serviceName, xl) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go index 5bcf3331..0cbc3dc2 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -582,11 +582,13 @@ func (p *HelmDeployTaskPlugin) Complete(ctx context.Context, pipelineTask *task. } func (p *HelmDeployTaskPlugin) SetTask(t map[string]interface{}) error { - task, err := ToDeployTask(t) - if err != nil { - return err + if p.Task == nil { + task, err := ToDeployTask(t) + if err != nil { + return err + } + p.Task = task } - p.Task = task return nil } -- Gitee From 66806bff9a2977fdf4174fad88341634a08cb086 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Apr 2022 09:40:14 +0800 Subject: [PATCH 2263/2647] 2 bugfix for 1.11.0 Signed-off-by: Min Min --- .../aslan/core/common/service/workflow.go | 9 ++++++++- .../aslan/core/system/service/registry.go | 16 ++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index e8a2e2ba..31b08da1 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -162,10 +162,17 @@ func DeleteWorkflow(workflowName, requestID string, isDeletingProductTmpl bool, } func DisableCronjobForWorkflow(workflow *models.Workflow) error { + disableIDList := make([]string, 0) payload := &CronjobPayload{ Name: workflow.Name, JobType: config.WorkflowCronjob, - Action: setting.TypeDisableCronjob, + Action: setting.TypeEnableCronjob, + } + if workflow.Schedules.Enabled { + for _, timer := range workflow.Schedules.Items { + disableIDList = append(disableIDList, timer.ID.Hex()) + } + payload.DeleteList = disableIDList } pl, _ := json.Marshal(payload) diff --git a/pkg/microservice/aslan/core/system/service/registry.go b/pkg/microservice/aslan/core/system/service/registry.go index b6013924..32a94005 100644 --- a/pkg/microservice/aslan/core/system/service/registry.go +++ b/pkg/microservice/aslan/core/system/service/registry.go @@ -200,7 +200,13 @@ func ListAllRepos(log *zap.SugaredLogger) ([]*RepoInfo, error) { } func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, logger *zap.SugaredLogger) ([]*RepoImgResp, error) { - repos, err := registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).ListRepoImages(registry.ListRepoImagesOption{ + var regService registry.Service + if registryInfo.AdvancedSetting != nil { + regService = registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert) + } else { + regService = registry.NewV2Service(registryInfo.RegProvider, true, "") + } + repos, err := regService.ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, @@ -233,7 +239,13 @@ func ListReposTags(registryInfo *commonmodels.RegistryNamespace, names []string, func GetRepoTags(registryInfo *commonmodels.RegistryNamespace, name string, log *zap.SugaredLogger) (*registry.ImagesResp, error) { var resp *registry.ImagesResp - repos, err := registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).ListRepoImages(registry.ListRepoImagesOption{ + var regService registry.Service + if registryInfo.AdvancedSetting != nil { + regService = registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert) + } else { + regService = registry.NewV2Service(registryInfo.RegProvider, true, "") + } + repos, err := regService.ListRepoImages(registry.ListRepoImagesOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, -- Gitee From 2d45fcb49dabccf14c31b8e4b1944f3b9ee3dc29 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Apr 2022 11:06:53 +0800 Subject: [PATCH 2264/2647] stuff Signed-off-by: Min Min --- .../aslan/core/common/service/workflow.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 31b08da1..330ad882 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -168,9 +168,17 @@ func DisableCronjobForWorkflow(workflow *models.Workflow) error { JobType: config.WorkflowCronjob, Action: setting.TypeEnableCronjob, } - if workflow.Schedules.Enabled { - for _, timer := range workflow.Schedules.Items { - disableIDList = append(disableIDList, timer.ID.Hex()) + if workflow.ScheduleEnabled { + jobList, err := mongodb.NewCronjobColl().List(&mongodb.ListCronjobParam{ + ParentName: workflow.Name, + ParentType: config.WorkflowCronjob, + }) + if err != nil { + return err + } + + for _, job := range jobList { + disableIDList = append(disableIDList, job.ID.Hex()) } payload.DeleteList = disableIDList } -- Gitee From 7e4415cb8fa9327573c7126485b37ebe6fc92a7b Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 20 Apr 2022 11:34:22 +0800 Subject: [PATCH 2265/2647] add debug log Signed-off-by: panxunying --- .../aslan/core/collaboration/service/collaboration_instance.go | 1 + pkg/microservice/aslan/core/label/handler/label_binding.go | 1 + pkg/microservice/aslan/core/label/service/labelsbinding.go | 1 + 3 files changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 2f07272d..36a7d3fd 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -849,6 +849,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } } if len(deleteBindings) > 0 { + logger.Infof("start syncLabel DeleteLabelBindings:%v user:%s", deleteBindings, userName) err = service.DeleteLabelBindings(&service.DeleteLabelBindingsArgs{ LabelBindings: deleteBindings, }, userName, logger) diff --git a/pkg/microservice/aslan/core/label/handler/label_binding.go b/pkg/microservice/aslan/core/label/handler/label_binding.go index c6003154..a699b73b 100644 --- a/pkg/microservice/aslan/core/label/handler/label_binding.go +++ b/pkg/microservice/aslan/core/label/handler/label_binding.go @@ -63,5 +63,6 @@ func DeleteLabelBindings(c *gin.Context) { ctx.Err = err return } + ctx.Logger.Infof("DeleteLabelBindings user:%s", ctx.UserName) ctx.Err = service.DeleteLabelBindings(deleteLabelBindingsArgs, ctx.UserName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index ae9d85a3..7401ca25 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -107,6 +107,7 @@ type DeleteLabelBindingsArgs struct { } func DeleteLabelBindings(dr *DeleteLabelBindingsArgs, userName string, logger *zap.SugaredLogger) error { + logger.Infof("DeleteLabelBindings:%v userName:%s", dr, userName) return mongodb.NewLabelBindingColl().BulkDelete(dr.LabelBindings) } -- Gitee From 63ed60f5b1b2aa67bf5c134149429db3a83ba4be Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Apr 2022 11:44:20 +0800 Subject: [PATCH 2266/2647] change default to imagename Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index bba3b2df..8984916d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2487,7 +2487,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } image = GetImage(reg, payload.String()) } else { - image = GetImage(reg, fmt.Sprintf("%s:%s", t.ServiceName, time.Now().Format("20060102150405"))) + image = GetImage(reg, fmt.Sprintf("%s:%s", taskOpt.ImageName, time.Now().Format("20060102150405"))) } t.JenkinsBuildArgs.JenkinsBuildParams[i].Value = image -- Gitee From 41cb7cc1e809112fb639f863afd2729eecb54351 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Apr 2022 15:02:28 +0800 Subject: [PATCH 2267/2647] fixed helm deploy bug Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index dd4cdeb2..325fb1f3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -680,7 +680,9 @@ DistributeLoop: err = ensureUpgrade() if err != nil { - return + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop } timeOut := p.TaskTimeout() @@ -721,7 +723,9 @@ DistributeLoop: err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) } if err != nil { - return + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + continue DistributeLoop } //替换环境变量中的chartInfos -- Gitee From 7fe15d78fd9f5b4e0d0e580e782f845c4e8874ba Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Apr 2022 15:48:36 +0800 Subject: [PATCH 2268/2647] fixed get Image info panic problem Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/nsq_handlers.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index d38e2df0..08b27c96 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -925,7 +925,14 @@ func getImageInfo(productName, evnName, repoName, tag string, log *zap.SugaredLo return nil, fmt.Errorf("RegistryNamespace.get error: %v", err) } - return registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert).GetImageInfo(registry.GetRepoImageDetailOption{ + var regService registry.Service + if registryInfo.AdvancedSetting != nil { + regService = registry.NewV2Service(registryInfo.RegProvider, registryInfo.AdvancedSetting.TLSEnabled, registryInfo.AdvancedSetting.TLSCert) + } else { + regService = registry.NewV2Service(registryInfo.RegProvider, true, "") + } + + return regService.GetImageInfo(registry.GetRepoImageDetailOption{ Endpoint: registry.Endpoint{ Addr: registryInfo.RegAddr, Ak: registryInfo.AccessKey, -- Gitee From 13b3d8318e3b39d542e62eccda53b9a3dfb87c1f Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 20 Apr 2022 15:52:03 +0800 Subject: [PATCH 2269/2647] fix release name error Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/helm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 6c8e06e8..6dd0e83e 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -350,7 +350,7 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo return nil, fmt.Errorf("failed to find template service: %s", svcName) } - releaseName := util.GeneReleaseName(ts.GetReleaseNaming(), productName, prod.Namespace, prod.Namespace, svcName) + releaseName := util.GeneReleaseName(ts.GetReleaseNaming(), productName, prod.Namespace, prod.EnvName, svcName) valuesYaml, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { return nil, fmt.Errorf("failed to get values for relase: %s, err: %s", releaseName, err) -- Gitee From 8d4d8a10c33e0c42013f672864e4d892ae4a7c9b Mon Sep 17 00:00:00 2001 From: mouuii Date: Wed, 20 Apr 2022 20:26:49 +0800 Subject: [PATCH 2270/2647] pm show bug Signed-off-by: mouuii --- .../aslan/core/environment/service/pm.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index d2de3a07..8f5d2338 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" @@ -125,11 +126,20 @@ func (p *PMService) listGroupServices(allServices []*commonmodels.ProductService gp.ProductName = serviceTmpl.ProductName if len(serviceTmpl.EnvStatuses) > 0 { envStatuses := make([]*commonmodels.EnvStatus, 0) + filterEnvStatuses, err := pm.GenerateEnvStatus(serviceTmpl.EnvConfigs, log.NopSugaredLogger()) + if err != nil { + return + } + filterEnvStatusSet := sets.NewString() + for _, v := range filterEnvStatuses { + filterEnvStatusSet.Insert(v.Address) + } for _, envStatus := range serviceTmpl.EnvStatuses { - if envStatus.EnvName == envName { + if envStatus.EnvName == envName && filterEnvStatusSet.Has(envStatus.Address) { envStatuses = append(envStatuses, envStatus) } } + if len(envStatuses) > 0 { gp.EnvStatuses = envStatuses mutex.Lock() -- Gitee From 1ad09e492e8550eb3e015d932546b3626c5d4095 Mon Sep 17 00:00:00 2001 From: Min Min Date: Wed, 20 Apr 2022 21:59:09 +0800 Subject: [PATCH 2271/2647] fixed stage status error in distribute Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 325fb1f3..082cac81 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -436,7 +436,9 @@ DistributeLoop: if !replaced { err = errors.Errorf( "container %s is not found in resources with label %s", distribute.DeployContainerName, selector) - return + distribute.DeployStatus = string(config.StatusFailed) + distribute.DeployEndTime = time.Now().Unix() + break } // if all is done in one deployment, update its status to success and endtime distribute.DeployStatus = string(config.StatusPassed) -- Gitee From c13eca1de3812ce50d41da3fbb7b8c1ba8731de3 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Thu, 21 Apr 2022 10:17:54 +0800 Subject: [PATCH 2272/2647] Fixed an issue where the host failed to remove deleted services when updating the environment Signed-off-by: ddh27 --- .../core/common/repository/mongodb/service.go | 7 +++++++ .../core/environment/service/environment.go | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index 2110fb4f..ac09023e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -184,6 +184,13 @@ func (c *ServiceColl) ListMaxRevisionsByProduct(productName string) ([]*models.S return c.listMaxRevisions(m, nil) } +func (c *ServiceColl) ListMaxRevisionsAllSvcByProduct(productName string) ([]*models.Service, error) { + m := bson.M{ + "product_name": productName, + } + return c.listMaxRevisions(m, nil) +} + // Find 根据service_name和type查询特定版本的配置模板 // 如果 Revision == 0 查询最大版本的配置模板 func (c *ServiceColl) Find(opt *ServiceFindOption) (*models.Service, error) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index bf358973..de838a8a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -741,6 +741,22 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames } } + //TODO:The host update environment cannot remove deleted services + if !force && project.ProductFeature != nil && project.ProductFeature.BasicFacility == setting.BasicFacilityCVM { + services, err := commonrepo.NewServiceColl().ListMaxRevisionsAllSvcByProduct(productName) + if err != nil && !commonrepo.IsErrNoDocuments(err) { + log.Errorf("ListMaxRevisionsAllSvcByProduct: %s", err) + return fmt.Errorf("ListMaxRevisionsAllSvcByProduct: %s", err) + } + if services != nil { + svcNames := make([]string, len(services)) + for _, svc := range services { + svcNames = append(svcNames, svc.ServiceName) + } + serviceNames = svcNames + } + } + if project.ProductFeature != nil && project.ProductFeature.BasicFacility != setting.BasicFacilityCVM { err = ensureKubeEnv(exitedProd.Namespace, exitedProd.RegistryID, exitedProd.ShareEnv.Enable, kubeClient, log) -- Gitee From 662e1cdfefe5e830853d235dd8f4aa5911c4ba12 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 21 Apr 2022 11:08:16 +0800 Subject: [PATCH 2273/2647] fix service module duplicate bug Signed-off-by: allenshen --- .../aslan/core/common/service/service.go | 17 +++++++++++++++++ .../aslan/core/environment/service/revision.go | 18 +++--------------- .../aslan/core/environment/service/types.go | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index cd31c7e4..77795753 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -705,6 +705,17 @@ func ExtractRegistryNamespace(imageURI string) string { return strings.Join(nsComponent, "/") } +func generateUniquePath(pathData map[string]string) string { + keys := []string{setting.PathSearchComponentRepo, setting.PathSearchComponentImage, setting.PathSearchComponentTag} + values := make([]string, 0) + for _, key := range keys { + if value := pathData[key]; value != "" { + values = append(values, value) + } + } + return strings.Join(values, "-") +} + func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]string) ([]*commonmodels.Container, error) { flatMap, err := converter.Flatten(nested) if err != nil { @@ -715,7 +726,13 @@ func parseImagesByPattern(nested map[string]interface{}, patterns []map[string]s return nil, err } ret := make([]*commonmodels.Container, 0) + usedImagePath := sets.NewString() for _, searchResult := range matchedPath { + uniquePath := generateUniquePath(searchResult) + if usedImagePath.Has(uniquePath) { + continue + } + usedImagePath.Insert(uniquePath) imageUrl, err := GeneImageURI(searchResult, flatMap) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 56138452..c0fc519e 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -103,12 +103,10 @@ func ListProductsRevisionByOption(basicFacility string, deployType string, log * } func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*commonmodels.Service, log *zap.SugaredLogger) (*ProductRevision, error) { - prodRev := new(ProductRevision) - // 查询当前产品的最新模板信息 - productTemplatName := product.ProductName - prodTmpl, err := templaterepo.NewProductColl().Find(productTemplatName) + productTemplateName := product.ProductName + prodTmpl, err := templaterepo.NewProductColl().Find(productTemplateName) if err != nil { log.Errorf("[ProductTmpl.Find] %s error: %v", product.ProductName, err) return prodRev, e.ErrFindProductTmpl @@ -126,11 +124,6 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common return prodRev, nil } - // 如果当前产品版本比产品模板小, 则需要更新 - if prodRev.NextRevision > prodRev.CurrentRevision { - prodRev.Updatable = true - } - var allRenders []*commonmodels.RenderSet var newRender *commonmodels.RenderSet if prodTmpl.ProductFeature == nil || prodTmpl.ProductFeature.DeployType == setting.K8SDeployType { @@ -152,7 +145,7 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common } allRenders, err = commonrepo.NewRenderSetColl().ListRendersets(&commonrepo.RenderSetListOption{ Revisions: renderRevision.List(), - ProductTmpl: productTemplatName, + ProductTmpl: productTemplateName, RendersetName: rendersetName, }) if err != nil { @@ -168,11 +161,6 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common return nil, e.ErrGetProductRevision.AddDesc(err.Error()) } - // 如果任一服务组有更新, 则认为产品需要更新 - if !prodRev.Updatable { - prodRev.Updatable = prodRev.GroupsUpdated() - } - return prodRev, nil } diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index 48d0b3d1..1b1a7f01 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -98,7 +98,7 @@ func (pr *ProductRevision) GroupsUpdated() bool { return false } for _, serviceRev := range pr.ServiceRevisions { - if serviceRev.Updatable { + if serviceRev.Updatable && !serviceRev.New && !serviceRev.Deleted { return true } } -- Gitee From c69f79456e7d9f6560693ed8f8339bfb1d07e9bf Mon Sep 17 00:00:00 2001 From: Min Min Date: Thu, 21 Apr 2022 11:54:57 +0800 Subject: [PATCH 2274/2647] changed the status of the stage Signed-off-by: Min Min --- .../warpdrive/core/service/taskplugin/release_image.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 082cac81..43bb3889 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -282,7 +282,7 @@ DistributeLoop: } // set the start time on deployment start. distribute.DeployStartTime = time.Now().Unix() - distribute.DeployStatus = setting.StatusRunning + distribute.DeployStatus = "running" if distribute.DeployClusterID != "" { p.restConfig, err = kubeclient.GetRESTConfig(p.HubServerAddr, distribute.DeployClusterID) if err != nil { -- Gitee From ce0f9173f02d729a36135e55e303ed0393d24a14 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 21 Apr 2022 12:06:57 +0800 Subject: [PATCH 2275/2647] set product updatable logic Signed-off-by: allenshen --- .../aslan/core/environment/service/revision.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index c0fc519e..409a5855 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -124,6 +124,10 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common return prodRev, nil } + if prodRev.NextRevision > prodRev.CurrentRevision { + prodRev.Updatable = true + } + var allRenders []*commonmodels.RenderSet var newRender *commonmodels.RenderSet if prodTmpl.ProductFeature == nil || prodTmpl.ProductFeature.DeployType == setting.K8SDeployType { @@ -161,6 +165,10 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common return nil, e.ErrGetProductRevision.AddDesc(err.Error()) } + if !prodRev.Updatable { + prodRev.Updatable = prodRev.GroupsUpdated() + } + return prodRev, nil } -- Gitee From 5d1c1f46d004a7a9eb36544b9ca8d5bd7879f033 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 21 Apr 2022 13:58:15 +0800 Subject: [PATCH 2276/2647] fix bug Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/types.go b/pkg/microservice/aslan/core/environment/service/types.go index 1b1a7f01..48d0b3d1 100644 --- a/pkg/microservice/aslan/core/environment/service/types.go +++ b/pkg/microservice/aslan/core/environment/service/types.go @@ -98,7 +98,7 @@ func (pr *ProductRevision) GroupsUpdated() bool { return false } for _, serviceRev := range pr.ServiceRevisions { - if serviceRev.Updatable && !serviceRev.New && !serviceRev.Deleted { + if serviceRev.Updatable { return true } } -- Gitee From 4b70c9dadb1cf51455c6a09dbea78e87c2a7437c Mon Sep 17 00:00:00 2001 From: liu deyi Date: Thu, 21 Apr 2022 20:16:36 +0800 Subject: [PATCH 2277/2647] optimize gitee pull request webhook Signed-off-by: liu deyi --- pkg/microservice/aslan/core/workflow/service/webhook/gitee.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index 0f8ff4ee..f9e77db7 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -87,6 +87,10 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * return fmt.Errorf("action %s is skipped", event.Action) } + if event.Action == "update" && event.ActionDesc == "target_branch_changed" { + return fmt.Errorf("action %s is skipped", event.Action) + } + // build webhook wg.Add(1) go func() { -- Gitee From 00352703ea3888fd81e10100bfbd8e9294d61497 Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Thu, 21 Apr 2022 20:38:14 +0800 Subject: [PATCH 2278/2647] fix(typo):fix typo of err msg. Signed-off-by: leozhang2018 --- pkg/microservice/aslan/core/environment/service/environment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index de838a8a..22a4ae84 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2644,7 +2644,7 @@ func upsertService(isUpdate bool, env *commonmodels.Product, continue } default: - errList = multierror.Append(errList, fmt.Errorf("object is not a appsv1.Deployment ort appsv1.StatefulSet")) + errList = multierror.Append(errList, fmt.Errorf("object is not a appsv1.Deployment or appsv1.StatefulSet")) continue } -- Gitee From 31dedfa1489667431839ff63136798afa350ed75 Mon Sep 17 00:00:00 2001 From: ddh27 Date: Fri, 22 Apr 2022 14:03:23 +0800 Subject: [PATCH 2279/2647] Optimize yamL exports Signed-off-by: ddh27 --- pkg/tool/kube/getter/base.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/tool/kube/getter/base.go b/pkg/tool/kube/getter/base.go index d40e5a1a..1529cf70 100644 --- a/pkg/tool/kube/getter/base.go +++ b/pkg/tool/kube/getter/base.go @@ -145,6 +145,7 @@ func ListResourceYamlInCache(ns string, selector labels.Selector, fieldSelector var res [][]byte for _, item := range u.Items { + item.SetManagedFields(nil) data, err := item.MarshalJSON() if err != nil { return nil, err -- Gitee From e38d978fc72f699a3ad256f06ab3a7273381bfa1 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Fri, 22 Apr 2022 15:43:31 +0800 Subject: [PATCH 2280/2647] fix depracated api version (#1392) Signed-off-by: fansi Co-authored-by: fansi --- .../freestyle-k8s-specifications/db/db-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml index c832029b..0ad914b0 100644 --- a/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: apps/v1 kind: Deployment metadata: labels: -- Gitee From 3a3efdb55f8904446c65cf210d8fac03edba6f4d Mon Sep 17 00:00:00 2001 From: allenshen Date: Sun, 24 Apr 2022 11:20:02 +0800 Subject: [PATCH 2281/2647] fix k8s yaml reload from template bug Signed-off-by: allenshen --- .../aslan/core/service/handler/yaml_template.go | 2 +- .../aslan/core/service/service/yaml_template.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go index 051653f4..e6a400f5 100644 --- a/pkg/microservice/aslan/core/service/handler/yaml_template.go +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -54,5 +54,5 @@ func ReloadServiceFromYamlTemplate(c *gin.Context) { return } - ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.UserName, req.ProjectName, req.ServiceName, req.Variables, ctx.Logger) + ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.UserName, req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go index 81a40a00..2190eace 100644 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ b/pkg/microservice/aslan/core/service/service/yaml_template.go @@ -51,7 +51,7 @@ func LoadServiceFromYamlTemplate(username, projectName, serviceName, templateID return err } -func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, variables []*Variable, logger *zap.SugaredLogger) error { +func ReloadServiceFromYamlTemplate(username, projectName, serviceName, templateID string, variables []*Variable, logger *zap.SugaredLogger) error { service, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, ProductName: projectName, @@ -63,12 +63,12 @@ func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, va if service.Source != setting.ServiceSourceTemplate { return errors.New("service is not created from template") } - if service.TemplateID == "" { - return errors.New("failed to find template id for service:" + serviceName) + if templateID == "" { + return errors.New("template id can't be nil") } - template, err := commonrepo.NewYamlTemplateColl().GetById(service.TemplateID) + template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) if err != nil { - logger.Errorf("Failed to find template of ID: %s, the error is: %s", service.TemplateID, err) + logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) return err } renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) @@ -79,11 +79,11 @@ func ReloadServiceFromYamlTemplate(username, projectName, serviceName string, va Source: setting.ServiceSourceTemplate, Yaml: renderedYaml, Visibility: setting.PrivateVisibility, - TemplateID: service.TemplateID, + TemplateID: templateID, } _, err = CreateServiceTemplate(username, svc, logger) if err != nil { - logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", service.TemplateID, err) + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) } return err } -- Gitee From 5be1c9780dbe4c0b70566f16a90bd98e28387248 Mon Sep 17 00:00:00 2001 From: panxunying Date: Sun, 24 Apr 2022 16:56:51 +0800 Subject: [PATCH 2282/2647] fix policy data collection error Signed-off-by: panxunying --- .../aslan/core/environment/service/bundle.go | 11 +++++------ .../aslan/core/multicluster/service/bundle.go | 10 +++++----- .../aslan/core/workflow/service/workflow/bundle.go | 9 ++++----- .../policy/core/service/bundle/rego/authz.rego | 7 ++++++- .../policy/core/service/bundle/resource_bundle.go | 6 +++--- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index c0b109a4..a2eb1f0d 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -31,9 +31,9 @@ import ( ) type resourceSpec struct { - ResourceID string `json:"resourceID"` - ProjectName string `json:"projectName"` - Spec map[string]interface{} `json:"spec"` + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec []string `json:"spec"` } func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { @@ -78,11 +78,10 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec := &resourceSpec{ ResourceID: env.EnvName, ProjectName: env.ProductName, - Spec: map[string]interface{}{}, } if labels, ok := labelsResp.Labels[resourceKey]; ok { for _, v := range labels { - resourceSpec.Spec[v.Key] = v.Value + resourceSpec.Spec = append(resourceSpec.Spec, v.Key+":"+v.Value) } } @@ -92,7 +91,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { if ok { production = cluster.Production } - resourceSpec.Spec["production"] = strconv.FormatBool(production) + resourceSpec.Spec = append(resourceSpec.Spec, strconv.FormatBool(production)) res = append(res, resourceSpec) } diff --git a/pkg/microservice/aslan/core/multicluster/service/bundle.go b/pkg/microservice/aslan/core/multicluster/service/bundle.go index 5eb49f16..dea11a3a 100644 --- a/pkg/microservice/aslan/core/multicluster/service/bundle.go +++ b/pkg/microservice/aslan/core/multicluster/service/bundle.go @@ -24,9 +24,9 @@ import ( ) type resourceSpec struct { - ResourceID string `json:"resourceID"` - ProjectName string `json:"projectName"` - Spec map[string]interface{} `json:"spec"` + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec []string `json:"spec"` } func GetBundleResources() ([]*resourceSpec, error) { @@ -40,8 +40,8 @@ func GetBundleResources() ([]*resourceSpec, error) { for _, cluster := range clusters { res = append(res, &resourceSpec{ ResourceID: cluster.ID.Hex(), - Spec: map[string]interface{}{ - "production": strconv.FormatBool(cluster.Production), + Spec: []string{ + "production:" + strconv.FormatBool(cluster.Production), }, }) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go index 3a9c0f41..c85e45cd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/bundle.go @@ -28,9 +28,9 @@ import ( ) type resourceSpec struct { - ResourceID string `json:"resourceID"` - ProjectName string `json:"projectName"` - Spec map[string]interface{} `json:"spec"` + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec []string `json:"spec"` } func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { @@ -61,11 +61,10 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resourceSpec := &resourceSpec{ ResourceID: workflow.Name, ProjectName: workflow.ProductTmplName, - Spec: make(map[string]interface{}), } if labels, ok := labelsResp.Labels[resourceKey]; ok { for _, v := range labels { - resourceSpec.Spec[v.Key] = v.Value + resourceSpec.Spec = append(resourceSpec.Spec, v.Key+":"+v.Value) } } res = append(res, resourceSpec) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index d783685c..b2282335 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -166,7 +166,12 @@ attributes_match(attributes, res) { } attribute_match(attribute, res) { - res.spec[attribute.key] == attribute.value + resValue := res.spec[_] + value_match(resValue, sprintf("%s:%s",[attribute.key,attribute.value])) +} + +value_match(resValue, value) { + resValue == value } project_name_is_match(res) { diff --git a/pkg/microservice/policy/core/service/bundle/resource_bundle.go b/pkg/microservice/policy/core/service/bundle/resource_bundle.go index 542fadd5..3ca2c0b5 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/resource_bundle.go @@ -41,9 +41,9 @@ type Config struct { } type ResourceSpec struct { - ResourceID string `json:"resourceID"` - ProjectName string `json:"projectName"` - Spec map[string]interface{} `json:"spec"` + ResourceID string `json:"resourceID"` + ProjectName string `json:"projectName"` + Spec []string `json:"spec"` } type ResourceBundle map[string]resources -- Gitee From 53735e8874303dbe163738b624bd2e044d966ed8 Mon Sep 17 00:00:00 2001 From: panxunying Date: Sun, 24 Apr 2022 17:12:19 +0800 Subject: [PATCH 2283/2647] fix policy data collection error Signed-off-by: panxunying --- pkg/microservice/aslan/core/environment/service/bundle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index a2eb1f0d..278fc6b7 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -91,7 +91,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { if ok { production = cluster.Production } - resourceSpec.Spec = append(resourceSpec.Spec, strconv.FormatBool(production)) + resourceSpec.Spec = append(resourceSpec.Spec, "production:"+strconv.FormatBool(production)) res = append(res, resourceSpec) } -- Gitee From eda4b34c45482a85573084b4713012db02c51f69 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Sun, 24 Apr 2022 20:21:38 +0800 Subject: [PATCH 2284/2647] add registry secret for hosting project (#1400) Signed-off-by: fansi Co-authored-by: fansi --- examples/microservice-demo/k8s-yaml/backend/deployment.yaml | 5 +++-- examples/microservice-demo/k8s-yaml/frontend/deployment.yaml | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/microservice-demo/k8s-yaml/backend/deployment.yaml b/examples/microservice-demo/k8s-yaml/backend/deployment.yaml index b5e0fc13..c0d617e8 100644 --- a/examples/microservice-demo/k8s-yaml/backend/deployment.yaml +++ b/examples/microservice-demo/k8s-yaml/backend/deployment.yaml @@ -20,10 +20,11 @@ spec: containers: - name: backend image: ccr.ccs.tencentyun.com/koderover-public/backend:latest - imagePullPolicy: Always + imagePullPolicy: Always command: - /workspace/backend ports: - protocol: TCP containerPort: 20219 - \ No newline at end of file + imagePullSecrets: + - name: default-registry-secret diff --git a/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml b/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml index ee894c2c..639e8a3f 100644 --- a/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml +++ b/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml @@ -36,6 +36,5 @@ spec: requests: cpu: 100m memory: 100M - - - + imagePullSecrets: + - name: default-registry-secret -- Gitee From bf88484869265aed2767ab7096afc7bd63de101a Mon Sep 17 00:00:00 2001 From: dyliu Date: Mon, 25 Apr 2022 10:24:17 +0800 Subject: [PATCH 2285/2647] fix the bug of extracting image name (#1401) * fix the bug of extracting imagename Signed-off-by: liu deyi * modify annotation Signed-off-by: liu deyi * fix bug in extracting repo name from image Signed-off-by: liu deyi * modify annotation Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * handler error msg Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- go.mod | 1 + go.sum | 2 ++ .../aslan/core/service/service/service.go | 4 ++-- pkg/shared/kube/wrapper/deployment.go | 2 +- pkg/util/strings.go | 18 ++++++++++++------ 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 255ebd02..848cbb78 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/bugsnag/panicwrap v1.3.1 // indirect github.com/cenkalti/backoff/v4 v4.1.1 github.com/chartmuseum/helm-push v0.10.1 + github.com/containers/image v3.0.2+incompatible github.com/coocood/freecache v1.1.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b diff --git a/go.sum b/go.sum index ec128195..f9e56ee3 100644 --- a/go.sum +++ b/go.sum @@ -405,6 +405,8 @@ github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/image v3.0.2+incompatible h1:B1lqAE8MUPCrsBLE86J0gnXleeRq8zJnQryhiiGQNyE= +github.com/containers/image v3.0.2+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 1bf62b2a..d80986cb 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1401,7 +1401,7 @@ func getContainers(data string) ([]*commonmodels.Container, error) { container := &commonmodels.Container{ Name: nameStr, Image: imageStr, - ImageName: util.GetImageName(imageStr), + ImageName: util.ExtractImageName(imageStr), } containers = append(containers, container) @@ -1441,7 +1441,7 @@ func getCronJobContainers(data string) ([]*commonmodels.Container, error) { container := &commonmodels.Container{ Name: nameStr, Image: imageStr, - ImageName: util.GetImageName(imageStr), + ImageName: util.ExtractImageName(imageStr), } containers = append(containers, container) diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 7892251c..1c0ec71a 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -58,7 +58,7 @@ func (w *deployment) WorkloadResource(pods []*corev1.Pod) *resource.Workload { } for _, c := range w.Spec.Template.Spec.Containers { - wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image, ImageName: util.GetImageName(c.Image)}) + wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image, ImageName: util.ExtractImageName(c.Image)}) } for _, p := range pods { diff --git a/pkg/util/strings.go b/pkg/util/strings.go index e1495602..6e3ec112 100644 --- a/pkg/util/strings.go +++ b/pkg/util/strings.go @@ -19,6 +19,8 @@ package util import ( "regexp" "strings" + + ref "github.com/containers/image/docker/reference" ) func GetJiraKeys(title string) (keys []string) { @@ -37,15 +39,19 @@ func ReplaceWrapLine(script string) string { ), "\r", "\n", -1) } -// image ccr.ccs.tencentyun.com/koderover/nginx:stable -// return nginx -func GetImageName(image string) string { +// Test case reference https://github.com/containers/image/blob/main/docker/reference/reference_test.go +func ExtractImageName(image string) string { imageNameStr := "" - imageArr := strings.Split(image, ":") - if len(imageArr) > 0 { - imageNameArr := strings.Split(imageArr[0], "/") + + reference, err := ref.Parse(image) + if err != nil { + return imageNameStr + } + if named, ok := reference.(ref.Named); ok { + imageNameArr := strings.Split(named.Name(), "/") imageNameStr = imageNameArr[len(imageNameArr)-1] } + return imageNameStr } -- Gitee From a75d851494fecb2b2b23327c10fdab3d9540f1f7 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:02:50 +0800 Subject: [PATCH 2286/2647] add system roles (#1222) * picket service bff get user infos Signed-off-by: mouuii * system role rego && url Signed-off-by: mouuii * resourceScope Signed-off-by: mouuii * policyMeta Signed-off-by: mouuii * users list Signed-off-by: mouuii * system role Signed-off-by: mouuii * remove fmt Signed-off-by: mouuii * add rule url Signed-off-by: mouuii * cluster role -> system role Signed-off-by: mouuii * system role Signed-off-by: mouuii * system role Signed-off-by: mouuii * system role Signed-off-by: mouuii * xx Signed-off-by: mouuii * xx Signed-off-by: mouuii * xx Signed-off-by: mouuii * remove useless fmt Signed-off-by: mouuii * add some urls Signed-off-by: mouuii * add some urls Signed-off-by: mouuii * delete release delete Signed-off-by: mouuii * template permission Signed-off-by: mouuii * template permission Signed-off-by: mouuii * rename policy define Signed-off-by: mouuii * rename policy define Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * code review Signed-off-by: mouuii * bug Signed-off-by: mouuii * review Signed-off-by: mouuii * review Signed-off-by: mouuii * review Signed-off-by: mouuii * license Signed-off-by: mouuii --- .../{policy_definitions.go => policy_meta.go} | 4 +- .../{policy_definitions.go => policy_meta.go} | 6 +- .../{policy_definitions.go => policy_meta.go} | 8 +- .../{policy_definitions.go => policy_meta.go} | 17 +- .../workflow/handler/system-policies.yaml | 166 ++++++++++++++++++ .../{policy_definitions.go => policy_meta.go} | 4 +- .../picket/client/policy/rolebinding.go | 18 ++ pkg/microservice/picket/client/user/user.go | 23 +++ .../picket/core/filter/handler/router.go | 2 +- .../picket/core/filter/handler/users.go | 15 ++ .../picket/core/filter/service/users.go | 49 ++++++ .../core/handler/policy_meta_registration.go | 3 +- pkg/microservice/policy/core/handler/role.go | 1 - .../policy/core/handler/role_binding.go | 54 ++++++ .../policy/core/handler/router.go | 2 + .../policy/core/repository/models/role.go | 1 + .../core/repository/mongodb/role_binding.go | 22 +++ .../core/service/bundle/exemption_urls.go | 24 --- .../core/service/bundle/rego/authz.rego | 35 ++++ .../core/service/policy_meta_registration.go | 36 +++- pkg/microservice/policy/core/service/role.go | 7 +- .../policy/core/service/role_binding.go | 23 +++ .../user/core/service/user/user.go | 41 ++--- pkg/shared/client/aslan/registries.go | 16 ++ pkg/types/policy.go | 25 +++ pkg/types/user.go | 47 +++++ 26 files changed, 569 insertions(+), 80 deletions(-) rename pkg/microservice/aslan/core/delivery/handler/{policy_definitions.go => policy_meta.go} (92%) rename pkg/microservice/aslan/core/environment/handler/{policy_definitions.go => policy_meta.go} (92%) rename pkg/microservice/aslan/core/project/handler/{policy_definitions.go => policy_meta.go} (85%) rename pkg/microservice/aslan/core/workflow/handler/{policy_definitions.go => policy_meta.go} (71%) create mode 100644 pkg/microservice/aslan/core/workflow/handler/system-policies.yaml rename pkg/microservice/aslan/core/workflow/testing/handler/{policy_definitions.go => policy_meta.go} (92%) create mode 100644 pkg/types/policy.go create mode 100644 pkg/types/user.go diff --git a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go b/pkg/microservice/aslan/core/delivery/handler/policy_meta.go similarity index 92% rename from pkg/microservice/aslan/core/delivery/handler/policy_definitions.go rename to pkg/microservice/aslan/core/delivery/handler/policy_meta.go index dfe7b02b..695ba3c4 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/delivery/handler/policy_meta.go @@ -26,11 +26,11 @@ import ( ) //go:embed policy.yaml -var policyDefinitions []byte +var policyMeta []byte func (*Router) Policies() []*policy.PolicyMeta { res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyDefinitions, res) + err := yaml.Unmarshal(policyMeta, res) if err != nil { // should not have happened here log.DPanic(err) diff --git a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go b/pkg/microservice/aslan/core/environment/handler/policy_meta.go similarity index 92% rename from pkg/microservice/aslan/core/environment/handler/policy_definitions.go rename to pkg/microservice/aslan/core/environment/handler/policy_meta.go index 01369276..492526fa 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/environment/handler/policy_meta.go @@ -33,18 +33,18 @@ const ( ) //go:embed policy.yaml -var policyDefinitions []byte +var policyMeta []byte func (*Router) Policies() []*policy.PolicyMeta { res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyDefinitions, res) + err := yaml.Unmarshal(policyMeta, res) if err != nil { // should not have happened here log.DPanic(err) } productionPolicy := &policy.PolicyMeta{} - _ = yaml.Unmarshal(policyDefinitions, productionPolicy) + _ = yaml.Unmarshal(policyMeta, productionPolicy) productionPolicy.Resource = productionPolicyResource productionPolicy.Alias = productionPolicyAlias //productionPolicy.Description = "" diff --git a/pkg/microservice/aslan/core/project/handler/policy_definitions.go b/pkg/microservice/aslan/core/project/handler/policy_meta.go similarity index 85% rename from pkg/microservice/aslan/core/project/handler/policy_definitions.go rename to pkg/microservice/aslan/core/project/handler/policy_meta.go index 63a37d48..2bd1f1f3 100644 --- a/pkg/microservice/aslan/core/project/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/project/handler/policy_meta.go @@ -26,20 +26,20 @@ import ( ) //go:embed service_policy.yaml -var servicePolicyDefinitions []byte +var servicePolicyMeta []byte //go:embed build_policy.yaml -var buildPolicyDefinitions []byte +var buildPolicymeta []byte func (*Router) Policies() []*policy.PolicyMeta { serviceRes := &policy.PolicyMeta{} - err := yaml.Unmarshal(servicePolicyDefinitions, serviceRes) + err := yaml.Unmarshal(servicePolicyMeta, serviceRes) if err != nil { // should not have happened here log.DPanic(err) } buildRes := &policy.PolicyMeta{} - err = yaml.Unmarshal(buildPolicyDefinitions, buildRes) + err = yaml.Unmarshal(buildPolicymeta, buildRes) if err != nil { // should not have happened here log.DPanic(err) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/handler/policy_meta.go similarity index 71% rename from pkg/microservice/aslan/core/workflow/handler/policy_definitions.go rename to pkg/microservice/aslan/core/workflow/handler/policy_meta.go index dfe7b02b..b2f49de2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/handler/policy_meta.go @@ -26,15 +26,26 @@ import ( ) //go:embed policy.yaml -var policyDefinitions []byte +var workflowPolicyMeta []byte + +//go:embed system-policies.yaml +var systemPolicyMeta []byte func (*Router) Policies() []*policy.PolicyMeta { res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyDefinitions, res) + err := yaml.Unmarshal(workflowPolicyMeta, res) + if err != nil { + // should not have happened here + log.DPanic(err) + } + + systemMetas := []*policy.PolicyMeta{} + err = yaml.Unmarshal(systemPolicyMeta, &systemMetas) if err != nil { // should not have happened here log.DPanic(err) } + systemMetas = append(systemMetas, res) - return []*policy.PolicyMeta{res} + return systemMetas } diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml new file mode 100644 index 00000000..85edac88 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -0,0 +1,166 @@ +- resource: TestCenter + alias: "测试中心" + description: "" + rules: + - action: get_test + alias: "查看" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/testing/test" + # get single test info + - method: GET + endpoint: "/api/aslan/testing/test/?*" + - method: GET + endpoint: "/api/aslan/testing/testdetail" + - method: GET + endpoint: "/api/aslan/workflow/workflow/testName/?*" + # get test workflow related + - method: GET + endpoint: "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*" + - method: GET + endpoint: "/api/aslan/testing/workspace/workflow/?*/taskId/?*" + - action: edit_test + alias: "编辑" + description: "" + rules: + - method: PUT + endpoint: "/api/aslan/testing/test" + - action: delete_test + alias: "删除" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/testing/test/?*" + - action: create_test + alias: "新建" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/testing/test" + - action: run_test + alias: "执行" + description: "" + rules: + - method: POST + endpoint: "/api/aslan/testing/testtask" +- resource: DataCenter + alias: "数据视图" + description: "" + rules: + - action: data_over + alias: "数据概览|查看" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/stat/dashboard/overview" + - action: efficiency_over + alias: "数据洞察|查看" + description: "" + rules: + # 9 build efficiency + - method: GET + endpoint: "/api/aslan/stat/dashboard/build" + - method: GET + endpoint: "/api/aslan/stat/dashboard/deploy" + - method: GET + endpoint: "/api/aslan/stat/dashboard/test" + - method: POST + endpoint: "/api/aslan/stat/quality/buildHealthMeasure" + - method: POST + endpoint: "/api/aslan/stat/quality/buildLatestTenMeasure" + - method: POST + endpoint: "/api/aslan/stat/quality/buildDailyMeasure" + - method: POST + endpoint: "/api/aslan/stat/quality/buildTenDurationMeasure" + - method: POST + endpoint: "/api/aslan/stat/quality/buildAverageMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/buildTrend" + # 4 deploy efficiency + - method: POST + endpoint: "api/aslan/stat/quality/deployTopFiveFailureMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/deployTopFiveHigherMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/deployWeeklyMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/deployHealthMeasure" + # 5 test efficiency + - method: POST + endpoint: "api/aslan/stat/quality/testDeliveryDeploy" + - method: POST + endpoint: "api/aslan/stat/quality/testAverageMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/testCaseMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/testHealthMeasure" + - method: POST + endpoint: "api/aslan/stat/quality/testTrend" +- resource: Template + alias: "模版库" + description: "" + rules: + - action: create_template + alias: "新建" + description: "新建" + rules: + - method: POST + endpoint: "/api/aslan/template/dockerfile" + - method: POST + endpoint: "/api/aslan/template/charts" + - method: POST + endpoint: "/api/aslan/template/yaml" + - action: get_template + alias: "查看" + description: "查看" + rules: + - method: GET + endpoint: "/api/aslan/template/yaml" + - method: GET + endpoint: "/api/aslan/template/charts" + - method: GET + endpoint: "/api/aslan/template/charts/?*" + - method: GET + endpoint: "/api/aslan/template/dockerfile" + - action: edit_template + alias: "编辑" + description: "编辑" + rules: + - method: PUT + endpoint: "/api/aslan/template/yaml/?*" + - method: PUT + endpoint: "/api/aslan/template/charts/?*" + # 修改 chart 模版变量 + - method: PUT + endpoint: "api/aslan/template/charts/?*/variables" + - method: PUT + endpoint: "/api/aslan/template/dockerfile/?*" + - action: delete_template + alias: "删除" + description: "删除" + rules: + - method: DELETE + endpoint: "/api/aslan/template/yaml/?*" + - method: DELETE + endpoint: "/api/aslan/template/charts/?*" + - method: DELETE + endpoint: "/api/aslan/template/dockerfile/?*" +- resource: DeliveryCenter + alias: "交付中心" + description: "" + rules: + - action: get + alias: "版本管理|查看" + description: "查看" + rules: + - method: GET + endpoint: "/api/aslan/delivery/releases" + - method: GET + endpoint: "/api/aslan/delivery/releases/?*" + - action: get + alias: "交付物追踪|查看" + description: "查看" + rules: + - method: GET + endpoint: "/api/aslan/delivery/artifacts" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go b/pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go similarity index 92% rename from pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go rename to pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go index dfe7b02b..695ba3c4 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy_definitions.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go @@ -26,11 +26,11 @@ import ( ) //go:embed policy.yaml -var policyDefinitions []byte +var policyMeta []byte func (*Router) Policies() []*policy.PolicyMeta { res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyDefinitions, res) + err := yaml.Unmarshal(policyMeta, res) if err != nil { // should not have happened here log.DPanic(err) diff --git a/pkg/microservice/picket/client/policy/rolebinding.go b/pkg/microservice/picket/client/policy/rolebinding.go index 50a7a365..bb8bbe39 100644 --- a/pkg/microservice/picket/client/policy/rolebinding.go +++ b/pkg/microservice/picket/client/policy/rolebinding.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/types" ) type RoleBinding struct { @@ -74,3 +75,20 @@ func (c *Client) DeleteRoleBindings(userID string, header http.Header, qs url.Va } return res.Body(), err } + +type SearchSystemRoleBindingArgs struct { + Uids []string `json:"uids"` +} + +func (c *Client) SearchSystemRoleBindings(uids []string) (map[string][]*types.RoleBinding, error) { + url := "system-rolebindings/search" + args := SearchSystemRoleBindingArgs{ + Uids: uids, + } + result := map[string][]*types.RoleBinding{} + _, err := c.Post(url, httpclient.SetBody(args), httpclient.SetResult(&result)) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/pkg/microservice/picket/client/user/user.go b/pkg/microservice/picket/client/user/user.go index eb0d93a3..7a5d623f 100644 --- a/pkg/microservice/picket/client/user/user.go +++ b/pkg/microservice/picket/client/user/user.go @@ -21,6 +21,7 @@ import ( "net/url" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/types" ) func (c *Client) DeleteUser(userId string, header http.Header, qs url.Values) ([]byte, error) { @@ -33,3 +34,25 @@ func (c *Client) DeleteUser(userId string, header http.Header, qs url.Values) ([ return res.Body(), nil } + +func (c *Client) SearchUsers(header http.Header, qs url.Values, body interface{}) (*types.UsersResp, error) { + url := "/users/search" + result := &types.UsersResp{} + _, err := c.Post(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs), httpclient.SetBody(body), httpclient.SetResult(result)) + if err != nil { + return nil, err + } + return result, nil +} + +type SearchArgs struct { + Name string `json:"name,omitempty"` + Account string `json:"account,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + UIDs []string `json:"uids,omitempty"` + PerPage int `json:"per_page,omitempty"` + Page int `json:"page,omitempty"` +} + +type SearchRes struct { +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index 96364fb7..dcfed5a2 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -44,11 +44,11 @@ func (*Router) Inject(router *gin.RouterGroup) { } users := router.Group("users") { + users.POST("", SearchUsers) users.DELETE("/:id", DeleteUser) } downloads := router.Group("kubeconfig") { downloads.GET("", GetKubeConfig) } - } diff --git a/pkg/microservice/picket/core/filter/handler/users.go b/pkg/microservice/picket/core/filter/handler/users.go index e0ee375e..f6f85dc5 100644 --- a/pkg/microservice/picket/core/filter/handler/users.go +++ b/pkg/microservice/picket/core/filter/handler/users.go @@ -19,6 +19,7 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/picket/client/user" "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -30,3 +31,17 @@ func DeleteUser(c *gin.Context) { uid := c.Param("id") ctx.Resp, ctx.Err = service.DeleteUser(uid, c.Request.Header, c.Request.URL.Query(), ctx.Logger) } + +func SearchUsers(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + req := &user.SearchArgs{} + + if err := c.ShouldBindJSON(req); err != nil { + ctx.Logger.Errorf("bindjson err :%s", err) + ctx.Err = err + return + } + ctx.Resp, ctx.Err = service.SearchUsers(c.Request.Header, c.Request.URL.Query(), req, ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/users.go b/pkg/microservice/picket/core/filter/service/users.go index af86762f..faa6b0fb 100644 --- a/pkg/microservice/picket/core/filter/service/users.go +++ b/pkg/microservice/picket/core/filter/service/users.go @@ -24,6 +24,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/picket/client/policy" "github.com/koderover/zadig/pkg/microservice/picket/client/user" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type DeleteUserResp struct { @@ -37,3 +39,50 @@ func DeleteUser(userID string, header http.Header, qs url.Values, _ *zap.Sugared } return policy.New().DeleteRoleBindings(userID, header, qs) } + +func SearchUsers(header http.Header, qs url.Values, args *user.SearchArgs, log *zap.SugaredLogger) (*types.UsersResp, error) { + users, err := user.New().SearchUsers(header, qs, args) + if err != nil { + log.Errorf("search users err :%s", err) + return nil, err + } + tmpUids := []string{} + for _, user := range users.Users { + tmpUids = append(tmpUids, user.Uid) + } + + systemMap, err := policy.New().SearchSystemRoleBindings(tmpUids) + if err != nil { + log.Errorf("search system user bindings err :%s", err) + return nil, err + } + + res := &types.UsersResp{ + Users: make([]types.UserInfo, 0), + TotalCount: users.TotalCount, + } + for _, user := range users.Users { + userInfo := types.UserInfo{ + LastLoginTime: user.LastLoginTime, + Uid: user.Uid, + Name: user.Name, + IdentityType: user.IdentityType, + Email: user.Email, + Phone: user.Phone, + Account: user.Account, + APIToken: user.APIToken, + } + if rb, ok := systemMap[user.Uid]; ok { + userInfo.SystemRoleBindings = rb + for _, binding := range rb { + if binding.Role == string(setting.SystemAdmin) { + userInfo.Admin = true + break + } + } + } + res.Users = append(res.Users, userInfo) + } + + return res, nil +} diff --git a/pkg/microservice/policy/core/handler/policy_meta_registration.go b/pkg/microservice/policy/core/handler/policy_meta_registration.go index 6d0a8417..99a1fcdc 100644 --- a/pkg/microservice/policy/core/handler/policy_meta_registration.go +++ b/pkg/microservice/policy/core/handler/policy_meta_registration.go @@ -40,6 +40,7 @@ func CreateOrUpdatePolicyRegistration(c *gin.Context) { func GetPolicyRegistrationDefinitions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + scope := c.Query("scope") - ctx.Resp, ctx.Err = service.GetPolicyRegistrationDefinitions(ctx.Logger) + ctx.Resp, ctx.Err = service.GetPolicyRegistrationDefinitions(scope, ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/role.go b/pkg/microservice/policy/core/handler/role.go index 62338402..5c657a2a 100644 --- a/pkg/microservice/policy/core/handler/role.go +++ b/pkg/microservice/policy/core/handler/role.go @@ -221,7 +221,6 @@ func CreateSystemRole(c *gin.Context) { ctx.Err = err return } - args.Type = setting.ResourceTypeSystem ctx.Err = service.CreateRole(service.SystemScope, args, ctx.Logger) } diff --git a/pkg/microservice/policy/core/handler/role_binding.go b/pkg/microservice/policy/core/handler/role_binding.go index fc445ba1..cb9fb51b 100644 --- a/pkg/microservice/policy/core/handler/role_binding.go +++ b/pkg/microservice/policy/core/handler/role_binding.go @@ -137,6 +137,43 @@ func UpdateRoleBindings(c *gin.Context) { ctx.Err = service.UpdateRoleBindings(projectName, args, c.Query("userID"), ctx.Logger) } +func UpdateSystemRoleBindings(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + userID := c.Query("userID") + if userID == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("userID is empty") + return + } + + args := make([]*service.RoleBinding, 0) + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = err + return + } + var userId string + for i, v := range args { + if i == 0 { + userId = v.UID + } + if v.UID == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("some roleBinding's uid is empty") + return + } + + if v.UID != userId { + ctx.Err = e.ErrInvalidParam.AddDesc("roleBindings' uid is not consistent") + return + } + } + // TODO: mouuii args can be empty slice , this will delete admin binding , try to improve + if userId == "" { + userId = userID + } + ctx.Err = service.UpdateRoleBindings(service.SystemScope, args, userId, ctx.Logger) +} + func DeleteSystemRoleBinding(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -182,6 +219,23 @@ func ListSystemRoleBindings(c *gin.Context) { ctx.Resp, ctx.Err = service.ListRoleBindings(service.SystemScope, "", ctx.Logger) } +type SearchSystemRoleBindingArgs struct { + Uids []string `json:"uids"` +} + +func SearchSystemRoleBinding(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(SearchSystemRoleBindingArgs) + if err := c.ShouldBindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.SearchSystemRoleBindings(args.Uids, ctx.Logger) +} + func ListUserBindings(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index d31dc684..3bb60242 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -86,6 +86,8 @@ func (*Router) Inject(router *gin.RouterGroup) { systemRoleBindings := router.Group("system-rolebindings") { systemRoleBindings.POST("", CreateSystemRoleBinding) + systemRoleBindings.POST("/search", SearchSystemRoleBinding) + systemRoleBindings.POST("/update", UpdateSystemRoleBindings) systemRoleBindings.GET("", ListSystemRoleBindings) systemRoleBindings.DELETE("/:name", DeleteSystemRoleBinding) systemRoleBindings.PUT("/:name", CreateOrUpdateSystemRoleBinding) diff --git a/pkg/microservice/policy/core/repository/models/role.go b/pkg/microservice/policy/core/repository/models/role.go index 525f8336..f2a65f53 100644 --- a/pkg/microservice/policy/core/repository/models/role.go +++ b/pkg/microservice/policy/core/repository/models/role.go @@ -24,6 +24,7 @@ import ( // for a cluster scoped Role, namespace is empty. type Role struct { Name string `bson:"name" json:"name"` + Desc string `bson:"desc" json:"desc"` Namespace string `bson:"namespace" json:"namespace"` Rules []*Rule `bson:"rules" json:"rules"` Type setting.ResourceType `bson:"type" json:"type"` diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index cd9a0216..1c57b353 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -115,6 +115,28 @@ func (c *RoleBindingColl) ListBy(projectName, uid string) ([]*models.RoleBinding return res, nil } +func (c *RoleBindingColl) ListSystemRoleBindingsByUIDs(uids []string) ([]*models.RoleBinding, error) { + var res []*models.RoleBinding + + ctx := context.Background() + query := bson.M{"namespace": "*"} + if len(uids) > 0 { + query["subjects.uid"] = bson.M{"$in": uids} + } + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + + return res, nil +} + func (c *RoleBindingColl) Delete(name string, projectName string) error { query := bson.M{"name": name, "namespace": projectName} _, err := c.DeleteOne(context.TODO(), query) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index a6048190..af4d09ce 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -316,30 +316,6 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"DELETE"}, Endpoints: []string{"api/v1/system-policybindings/?*"}, }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/template/yaml"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/template/yaml/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/template/dockerfile"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/template/dockerfile/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/template/charts"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/template/charts/?*"}, - }, } // actions which are allowed for project admins. diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index b2282335..d0bdc354 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -117,6 +117,17 @@ access_is_granted { any_attribute_match(rule.matchAttributes, rule.resourceType, get_resource_id(rule.idRegex)) } +access_is_granted { + not url_is_privileged + + some rule + + allowed_system_role_plain_rules[rule] + rule.method == http_request.method + glob.match(trim(rule.endpoint, "/"), ["/"], concat("/", input.parsed_path)) +} + + access_is_granted { some rule @@ -349,6 +360,14 @@ allowed_roles[role_ref] { role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] } +allowed_system_roles[role_ref]{ + some i + some j + data.bindings.role_bindings[i].uid == claims.uid + data.bindings.role_bindings[i].bindings[j].namespace == "*" + role_ref := data.bindings.role_bindings[i].bindings[j].role_refs[_] +} + # if the proejct is visible by all users (the user name is "*"), the bound roles are also allowed allowed_roles[role_ref] { some i @@ -384,6 +403,16 @@ allowed_role_rules[rule] { rule := data.roles.roles[i].rules[_] } +allowed_system_role_rules[rule] { + some role_ref + allowed_system_roles[role_ref] + + some i + data.roles.roles[i].name == role_ref.name + role_ref.namespace == "*" + rule := data.roles.roles[i].rules[_] +} + allowed_policy_rules[rule] { some policy_ref allowed_policies[policy_ref] @@ -406,6 +435,12 @@ allowed_role_plain_rules[rule] { not rule.matchExpressions } +allowed_system_role_plain_rules[rule] { + rule := allowed_system_role_rules[_] + not rule.matchAttributes + not rule.matchExpressions +} + allowed_policy_attributive_rules[rule] { rule := allowed_policy_rules[_] rule.matchAttributes diff --git a/pkg/microservice/policy/core/service/policy_meta_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go index 8131a51d..bff62a7d 100644 --- a/pkg/microservice/policy/core/service/policy_meta_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -18,9 +18,11 @@ package service import ( "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/types" ) type PolicyMeta struct { @@ -96,29 +98,45 @@ func CreateOrUpdatePolicyRegistration(p *PolicyMeta, _ *zap.SugaredLogger) error return mongodb.NewPolicyMetaColl().UpdateOrCreate(obj) } -func GetPolicyRegistrationDefinitions(_ *zap.SugaredLogger) ([]*PolicyDefinition, error) { - policies, err := mongodb.NewPolicyMetaColl().List() +func GetPolicyRegistrationDefinitions(scope string, _ *zap.SugaredLogger) ([]*PolicyDefinition, error) { + policieMetas, err := mongodb.NewPolicyMetaColl().List() if err != nil { return nil, err } + systemScopeSet := sets.NewString("TestCenter", "DataCenter", "Template", "DeliveryCenter") + projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build") + systemPolicyMetas, projectPolicyMetas, filteredPolicyMetas := []*models.PolicyMeta{}, []*models.PolicyMeta{}, []*models.PolicyMeta{} + for _, v := range policieMetas { + if systemScopeSet.Has(v.Resource) { + systemPolicyMetas = append(systemPolicyMetas, v) + } else if projectScopeSet.Has(v.Resource) { + projectPolicyMetas = append(projectPolicyMetas, v) + } + } + switch scope { + case string(types.SystemScope): + filteredPolicyMetas = systemPolicyMetas + case string(types.ProjectScope): + filteredPolicyMetas = projectPolicyMetas + default: + filteredPolicyMetas = policieMetas + } var res []*PolicyDefinition - for _, p := range policies { + for _, meta := range filteredPolicyMetas { pd := &PolicyDefinition{ - Resource: p.Resource, - Alias: p.Alias, - Description: p.Description, + Resource: meta.Resource, + Alias: meta.Alias, + Description: meta.Description, } - for _, r := range p.Rules { + for _, r := range meta.Rules { pd.Rules = append(pd.Rules, &PolicyRuleDefinition{ Action: r.Action, Alias: r.Alias, Description: r.Description, }) } - res = append(res, pd) } - return res, nil } diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index cc902958..e5aa2d8d 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -33,7 +33,9 @@ type Role struct { // if the role is created by system , set the type to "system" Type setting.ResourceType `json:"type,omitempty"` // frontend default select flag - Select bool `json:"select,omitempty"` + Select bool `json:"select,omitempty"` + Namespace string `json:"namespace"` + Desc string `json:"desc,omitempty"` } func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { @@ -41,6 +43,7 @@ func CreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { Name: role.Name, Namespace: ns, Type: role.Type, + Desc: role.Desc, } for _, r := range role.Rules { @@ -100,7 +103,7 @@ func ListRoles(projectName string, _ *zap.SugaredLogger) ([]*Role, error) { if v.Name == string(setting.Contributor) { continue } - tmpRole := Role{Select: false, Name: v.Name, Type: v.Type} + tmpRole := Role{Select: false, Name: v.Name, Type: v.Type, Desc: v.Desc, Namespace: v.Namespace} if v.Name == string(setting.ReadProjectOnly) { tmpRole.Select = true } diff --git a/pkg/microservice/policy/core/service/role_binding.go b/pkg/microservice/policy/core/service/role_binding.go index 68f58916..c5063032 100644 --- a/pkg/microservice/policy/core/service/role_binding.go +++ b/pkg/microservice/policy/core/service/role_binding.go @@ -99,6 +99,29 @@ func ListRoleBindings(ns, uid string, _ *zap.SugaredLogger) ([]*RoleBinding, err return roleBindings, nil } +func SearchSystemRoleBindings(uids []string, _ *zap.SugaredLogger) (map[string][]*RoleBinding, error) { + var roleBindings []*RoleBinding + modelRoleBindings, err := mongodb.NewRoleBindingColl().ListSystemRoleBindingsByUIDs(uids) + if err != nil { + return nil, err + } + + for _, v := range modelRoleBindings { + roleBindings = append(roleBindings, &RoleBinding{ + Name: v.Name, + Role: v.RoleRef.Name, + UID: v.Subjects[0].UID, + Preset: v.RoleRef.Namespace == "", + }) + } + resMap := make(map[string][]*RoleBinding) + for _, rb := range roleBindings { + resMap[rb.UID] = append(resMap[rb.UID], rb) + } + + return resMap, nil +} + func DeleteRoleBinding(name string, projectName string, _ *zap.SugaredLogger) error { return mongodb.NewRoleBindingColl().Delete(name, projectName) } diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 55dfbd88..ef2bbe40 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -38,6 +38,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/mail" + "github.com/koderover/zadig/pkg/types" ) type User struct { @@ -63,17 +64,6 @@ type QueryArgs struct { Page int `json:"page,omitempty"` } -type UserInfo struct { - LastLoginTime int64 `json:"lastLoginTime"` - Uid string `json:"uid"` - Name string `json:"name"` - IdentityType string `gorm:"default:'unknown'" json:"identity_type"` - Email string `json:"email"` - Phone string `json:"phone"` - Account string `json:"account"` - APIToken string `json:"token"` -} - type Password struct { Uid string `json:"uid"` OldPassword string `json:"oldPassword"` @@ -85,11 +75,6 @@ type ResetParams struct { Password string `json:"password"` } -type UsersResp struct { - Users []UserInfo `json:"users"` - TotalCount int64 `json:"totalCount"` -} - type SyncUserInfo struct { Account string `json:"account"` IdentityType string `json:"identityType"` @@ -161,7 +146,7 @@ func SearchAndSyncUser(ldapId string, logger *zap.SugaredLogger) error { return nil } -func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { +func GetUser(uid string, logger *zap.SugaredLogger) (*types.UserInfo, error) { user, err := orm.GetUserByUid(uid, core.DB) if err != nil { logger.Errorf("GetUser getUserByUid:%s error, error msg:%s", uid, err.Error()) @@ -212,14 +197,14 @@ func GetUser(uid string, logger *zap.SugaredLogger) (*UserInfo, error) { return userInfoRes, nil } -func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { +func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*types.UsersResp, error) { user, err := orm.GetUser(args.Account, args.IdentityType, core.DB) if err != nil { logger.Errorf("SearchUserByAccount GetUser By account:%s error, error msg:%s", args.Account, err.Error()) return nil, err } if user == nil { - return &UsersResp{ + return &types.UsersResp{ Users: nil, TotalCount: 0, }, nil @@ -230,20 +215,20 @@ func SearchUserByAccount(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp return nil, err } usersInfo := mergeUserLogin([]models.User{*user}, *userLogins, logger) - return &UsersResp{ + return &types.UsersResp{ Users: usersInfo, TotalCount: int64(len(usersInfo)), }, nil } -func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) { +func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*types.UsersResp, error) { count, err := orm.GetUsersCount(args.Name) if err != nil { logger.Errorf("SeachUsers GetUsersCount By name:%s error, error msg:%s", args.Name, err.Error()) return nil, err } if count == 0 { - return &UsersResp{ + return &types.UsersResp{ TotalCount: 0, }, nil } @@ -263,21 +248,21 @@ func SearchUsers(args *QueryArgs, logger *zap.SugaredLogger) (*UsersResp, error) return nil, err } usersInfo := mergeUserLogin(users, *userLogins, logger) - return &UsersResp{ + return &types.UsersResp{ Users: usersInfo, TotalCount: count, }, nil } -func mergeUserLogin(users []models.User, userLogins []models.UserLogin, logger *zap.SugaredLogger) []UserInfo { +func mergeUserLogin(users []models.User, userLogins []models.UserLogin, logger *zap.SugaredLogger) []types.UserInfo { userLoginMap := make(map[string]models.UserLogin) for _, userLogin := range userLogins { userLoginMap[userLogin.UID] = userLogin } - var usersInfo []UserInfo + var usersInfo []types.UserInfo for _, user := range users { if userLogin, ok := userLoginMap[user.UID]; ok { - usersInfo = append(usersInfo, UserInfo{ + usersInfo = append(usersInfo, types.UserInfo{ LastLoginTime: userLogin.LastLoginTime, Uid: user.UID, Phone: user.Phone, @@ -293,7 +278,7 @@ func mergeUserLogin(users []models.User, userLogins []models.UserLogin, logger * return usersInfo } -func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*UsersResp, error) { +func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*types.UsersResp, error) { users, err := orm.ListUsersByUIDs(uids, core.DB) if err != nil { logger.Errorf("SearchUsersByUIDs SeachUsers By uids:%s error, error msg:%s", uids, err.Error()) @@ -305,7 +290,7 @@ func SearchUsersByUIDs(uids []string, logger *zap.SugaredLogger) (*UsersResp, er return nil, err } usersInfo := mergeUserLogin(users, *userLogins, logger) - return &UsersResp{ + return &types.UsersResp{ Users: usersInfo, TotalCount: int64(len(usersInfo)), }, nil diff --git a/pkg/shared/client/aslan/registries.go b/pkg/shared/client/aslan/registries.go index 8271a20b..24b1add5 100644 --- a/pkg/shared/client/aslan/registries.go +++ b/pkg/shared/client/aslan/registries.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package aslan import "github.com/koderover/zadig/pkg/tool/httpclient" diff --git a/pkg/types/policy.go b/pkg/types/policy.go new file mode 100644 index 00000000..51995e6d --- /dev/null +++ b/pkg/types/policy.go @@ -0,0 +1,25 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +//PolicyMetaScope resource scope for permission +type PolicyMetaScope string + +const ( + SystemScope PolicyMetaScope = "system" + ProjectScope PolicyMetaScope = "project" +) diff --git a/pkg/types/user.go b/pkg/types/user.go new file mode 100644 index 00000000..d046a54d --- /dev/null +++ b/pkg/types/user.go @@ -0,0 +1,47 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "github.com/koderover/zadig/pkg/setting" +) + +type UserInfo struct { + LastLoginTime int64 `json:"last_login_time"` + Uid string `json:"uid"` + Name string `json:"name"` + IdentityType string `gorm:"default:'unknown'" json:"identity_type"` + Email string `json:"email"` + Phone string `json:"phone"` + Account string `json:"account"` + APIToken string `json:"token"` + SystemRoleBindings []*RoleBinding `json:"system_role_bindings"` + Admin bool `json:"admin"` +} + +type UsersResp struct { + Users []UserInfo `json:"users"` + TotalCount int64 `json:"total_count"` +} + +type RoleBinding struct { + Name string `json:"name"` + UID string `json:"uid"` + Role string `json:"role"` + Preset bool `json:"preset"` + Type setting.ResourceType `json:"type"` +} -- Gitee From 44487c695e74727220c73f8e0ef3bb2643d83b2f Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:28:50 +0800 Subject: [PATCH 2287/2647] add some log to codehost (#1409) * add some log Signed-off-by: mouuii * add log to code host Signed-off-by: mouuii * add log to code host Signed-off-by: mouuii * build err Signed-off-by: mouuii * log err Signed-off-by: mouuii --- .../aslan/core/code/client/gitlab/gitlab.go | 3 ++- .../aslan/core/code/client/open/open.go | 3 +++ .../aslan/core/code/service/branch.go | 10 ++++++++-- .../aslan/core/code/service/merge_request.go | 10 ++++++++-- .../aslan/core/code/service/namespace.go | 10 ++++++++-- .../aslan/core/code/service/project.go | 10 ++++++++-- pkg/microservice/aslan/core/code/service/tag.go | 17 ++++++++--------- 7 files changed, 45 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index 9cff88e3..d4a9a0d0 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -19,11 +19,12 @@ package gitlab import ( "go.uber.org/zap" + gogitlab "github.com/xanzy/go-gitlab" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/git/gitlab" - gogitlab "github.com/xanzy/go-gitlab" ) type Config struct { diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 27f6ec3a..0793042f 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -47,12 +47,14 @@ var ClientsConfig = map[string]func() ClientConfig{ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { + log.Errorf("get code host info err:%s", err) return nil, err } var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] if !ok { + log.Error("unknow codehost type") return c, fmt.Errorf("unknow codehost type") } clientConfig := f() @@ -61,6 +63,7 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, return nil, err } if err := json.Unmarshal(bs, clientConfig); err != nil { + log.Errorf("marsh err:%s", err) return nil, err } return clientConfig.Open(codehostID, log) diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 00a18d53..7f3ba790 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -24,9 +24,15 @@ import ( ) func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.Branch, error) { - codehostClient, err := open.OpenClient(codeHostID, log) + cli, err := open.OpenClient(codeHostID, log) if err != nil { + log.Errorf("open client err:%s", err) return nil, err } - return codehostClient.ListBranches(client.ListOpt{Namespace: namespace, ProjectName: projectName, Key: key, Page: page, PerPage: perPage}) + br, err := cli.ListBranches(client.ListOpt{Namespace: namespace, ProjectName: projectName, Key: key, Page: page, PerPage: perPage}) + if err != nil { + log.Errorf("list branch err:%s", err) + return nil, err + } + return br, nil } diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 6a69a9ec..31f1ea41 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -24,11 +24,12 @@ import ( ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.PullRequest, error) { - codehostClient, err := open.OpenClient(codeHostID, log) + cli, err := open.OpenClient(codeHostID, log) if err != nil { + log.Errorf("open client err:%s", err) return nil, err } - return codehostClient.ListPrs(client.ListOpt{ + prs, err := cli.ListPrs(client.ListOpt{ Namespace: namespace, ProjectName: projectName, Key: key, @@ -36,4 +37,9 @@ func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, ke PerPage: perPage, TargeBr: targetBr, }) + if err != nil { + log.Errorf("list prs err:%s", err) + return nil, err + } + return prs, nil } diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 671d0491..6b8ad4b7 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -32,9 +32,15 @@ const ( ) func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*client.Namespace, error) { - codehostClient, err := open.OpenClient(codeHostID, log) + cli, err := open.OpenClient(codeHostID, log) if err != nil { + log.Errorf("open client err:%s", err) return nil, err } - return codehostClient.ListNamespaces(keyword) + ns, err := cli.ListNamespaces(keyword) + if err != nil { + log.Errorf("list namespace err:%s", err) + return nil, err + } + return ns, nil } diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index 287a963a..d3590a41 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -24,15 +24,21 @@ import ( ) func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, perPage int, keyword string, log *zap.SugaredLogger) ([]*client.Project, error) { - codehostClient, err := open.OpenClient(codeHostID, log) + cli, err := open.OpenClient(codeHostID, log) if err != nil { + log.Errorf("open client err:%s", err) return nil, err } - return codehostClient.ListProjects(client.ListOpt{ + projects, err := cli.ListProjects(client.ListOpt{ Namespace: namespace, NamespaceType: namespaceType, Key: keyword, Page: page, PerPage: perPage, }) + if err != nil { + log.Errorf("list projects err:%s", err) + return nil, err + } + return projects, nil } diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 5804f6a8..057f8246 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -21,26 +21,25 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" - e "github.com/koderover/zadig/pkg/tool/errors" ) func CodeHostListTags(codeHostID int, projectName string, namespace string, key string, page int, perPage int, log *zap.SugaredLogger) ([]*client.Tag, error) { - ch, err := systemconfig.New().GetCodeHost(codeHostID) - if err != nil { - log.Error(err) - return nil, e.ErrCodehostListTags.AddDesc("git client is nil") - } - codehostClient, err := open.OpenClient(ch.ID, log) + cli, err := open.OpenClient(codeHostID, log) if err != nil { + log.Errorf("open client err:%s", err) return nil, err } - return codehostClient.ListTags(client.ListOpt{ + tags, err := cli.ListTags(client.ListOpt{ Namespace: namespace, ProjectName: projectName, Key: key, Page: page, PerPage: page, }) + if err != nil { + log.Errorf("list tags err:%s", err) + return nil, err + } + return tags, nil } -- Gitee From 38c8f4ae313f6bf792a75f14ea367208a55de040 Mon Sep 17 00:00:00 2001 From: Xudong Zhang Date: Tue, 26 Apr 2022 11:19:15 +0800 Subject: [PATCH 2288/2647] tool/s3: detect and add mimetype before uploading to s3 (#1412) Signed-off-by: Xudong Zhang --- pkg/tool/s3/client.go | 14 +++++++++++++ pkg/tool/s3/client_test.go | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 pkg/tool/s3/client_test.go diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index 6cb342e5..b2273260 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -18,7 +18,9 @@ package s3 import ( "fmt" + "mime" "os" + "path/filepath" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -191,6 +193,14 @@ func (c *Client) RemoveFiles(bucketName string, prefixList []string) { } } +func detectMimetype(path string) string { + fileext := filepath.Ext(path) + if fileext == "" { + return "" + } + return mime.TypeByExtension(fileext) +} + // Upload uploads a file from src to the bucket with the specified objectKey func (c *Client) Upload(bucketName, src string, objectKey string) error { file, err := os.OpenFile(src, os.O_RDONLY, 0600) @@ -203,6 +213,10 @@ func (c *Client) Upload(bucketName, src string, objectKey string) error { Bucket: aws.String(bucketName), Key: aws.String(objectKey), } + mimetype := detectMimetype(src) + if mimetype != "" { + input.ContentType = &mimetype + } _, err = c.PutObject(input) return err } diff --git a/pkg/tool/s3/client_test.go b/pkg/tool/s3/client_test.go new file mode 100644 index 00000000..9c40a6c2 --- /dev/null +++ b/pkg/tool/s3/client_test.go @@ -0,0 +1,43 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package s3 + +import ( + "testing" +) + +func TestDetectMimetype(t *testing.T) { + type testcase struct { + file string + mimetype string + } + testcases := []testcase{ + {"", ""}, + {"/a/abc.", ""}, + {"abc/ab", ""}, + {".abc.a", ""}, + {"./abc.js", "application/javascript"}, + {"./abc.css", "text/css; charset=utf-8"}, + {"./abc.html", "text/html; charset=utf-8"}, + } + for _, ts := range testcases { + m := detectMimetype(ts.file) + if m != ts.mimetype { + t.Errorf("Expected result for path <%s> to be <%s> but got <%s>", ts.file, ts.mimetype, m) + } + } +} -- Gitee From a523ea1dc896e8f9afe65e938da39e38443f4187 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:45:17 +0800 Subject: [PATCH 2289/2647] add message (#1414) Signed-off-by: mouuii --- pkg/microservice/user/core/service/user/user.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index ef2bbe40..1e532988 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -18,12 +18,14 @@ package user import ( _ "embed" + "errors" "fmt" "net/url" "time" "github.com/dexidp/dex/connector/ldap" ldapv3 "github.com/go-ldap/ldap/v3" + "github.com/go-sql-driver/mysql" "github.com/golang-jwt/jwt" "github.com/google/uuid" "go.uber.org/zap" @@ -37,6 +39,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core/service/login" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/mail" "github.com/koderover/zadig/pkg/types" ) @@ -415,7 +418,11 @@ func CreateUser(args *User, logger *zap.SugaredLogger) (*models.User, error) { if err != nil { tx.Rollback() logger.Errorf("CreateUser CreateUser :%v error, error msg:%s", user, err.Error()) - return nil, err + var mysqlErr *mysql.MySQLError + if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 { + return nil, e.ErrCreateUser.AddErr(err).AddDesc("存在相同用户名") + } + return nil, e.ErrCreateUser.AddErr(err) } hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(args.Password), bcrypt.DefaultCost) userLogin := &models.UserLogin{ -- Gitee From 78dbebc429bc318fc9f2e5bc4bbf7a251774d40f Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Tue, 26 Apr 2022 14:02:05 +0800 Subject: [PATCH 2290/2647] support for multiple notifications (#1399) * aslan: surpport for multiple notifications Signed-off-by: guoyu * add upgrade and rollback code Signed-off-by: guoyu * add line break Signed-off-by: guoyu * modify dingding notification line break Signed-off-by: guoyu * upgrade idempotency Signed-off-by: guoyu * fix log typo Signed-off-by: guoyu * fix log typo Signed-off-by: guoyu Co-authored-by: guoyu --- pkg/cli/upgradeassistant/cmd/migrate/1120.go | 210 ++++++++++++++++++ .../internal/repository/models/testing.go | 14 +- .../internal/repository/models/workflow.go | 51 +++++ .../internal/repository/mongodb/workflow.go | 91 ++++++++ .../core/common/repository/models/pipeline.go | 7 +- .../core/common/repository/models/testing.go | 23 +- .../core/common/repository/models/workflow.go | 3 + .../common/service/instantmessage/service.go | 122 +++++----- 8 files changed, 452 insertions(+), 69 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/1120.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/workflow.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/workflow.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1120.go b/pkg/cli/upgradeassistant/cmd/migrate/1120.go new file mode 100644 index 00000000..28cf65b9 --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/1120.go @@ -0,0 +1,210 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrate + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/tool/log" +) + +func init() { + upgradepath.RegisterHandler("1.11.0", "1.12.0", V1110ToV1120) + upgradepath.RegisterHandler("1.12.0", "1.11.0", V1120ToV1110) +} + +func V1110ToV1120() error { + // iterate all testings , if contains notifyctl , add notifyctls + if err := moduleTestingAddNotifyCtls(); err != nil { + log.Errorf("moduleTestingAddNotifyCtls err:%s", err) + return err + } + // iterate all workflows , if contains notifyctl , add notifyctls + if err := workflowAddNotifyCtls(); err != nil { + log.Errorf("workflowAddNotifyCtls err:%s", err) + return err + } + + return nil +} + +func V1120ToV1110() error { + // iterate all testings , if contains notifyctl , add notifyctls + if err := moduleTestingRollBackNotifyCtls(); err != nil { + log.Errorf("moduleTestingRollBackNotifyCtls err:%s", err) + return err + } + // iterate all workflows , if contains notifyctl , add notifyctls + if err := workflowRollBackNotifyCtls(); err != nil { + log.Errorf("workflowRollBackNotifyCtls err:%s", err) + return err + } + return nil +} + +func moduleTestingAddNotifyCtls() error { + testingCol := internalmongodb.NewTestingColl() + + testings, err := testingCol.List(&internalmongodb.ListTestOption{}) + if err != nil { + return fmt.Errorf("failed to list all data in `zadig.module_testing`,err: %s", err) + } + + if len(testings) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, testing := range testings { + // 如果已经执行过数据迁移,不要重复迁移 + if testing.NotifyCtls != nil { + continue + } + if testing.NotifyCtl != nil { + testing.NotifyCtls = []*models.NotifyCtl{testing.NotifyCtl} + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", testing.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"notify_ctls", testing.NotifyCtls}, + }}, + }), + ) + } + _, err = testingCol.BulkWrite(context.TODO(), ms) + + return err +} + +func moduleTestingRollBackNotifyCtls() error { + testingCol := internalmongodb.NewTestingColl() + + testings, err := testingCol.List(&internalmongodb.ListTestOption{}) + if err != nil { + return fmt.Errorf("failed to list all data in `zadig.module_testing`,err: %s", err) + } + + if len(testings) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, testing := range testings { + for _, notifyctl := range testing.NotifyCtls { + if notifyctl != nil && notifyctl.Enabled { + testing.NotifyCtl = notifyctl + break + } + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", testing.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"notify_ctl", testing.NotifyCtl}, + }}, + }), + ) + } + _, err = testingCol.BulkWrite(context.TODO(), ms) + + return err +} + +func workflowAddNotifyCtls() error { + workflowColl := internalmongodb.NewWorkflowColl() + + workflows, err := workflowColl.List(&internalmongodb.ListWorkflowOption{}) + if err != nil { + return fmt.Errorf("failed to list all data in `zadig.workflow`,err: %s", err) + } + + if len(workflows) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, workflow := range workflows { + // 如果已经执行过数据迁移,不要重复迁移 + if workflow.NotifyCtls != nil { + continue + } + if workflow.NotifyCtl != nil { + workflow.NotifyCtls = []*models.NotifyCtl{workflow.NotifyCtl} + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", workflow.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"notify_ctls", workflow.NotifyCtls}, + }}, + }), + ) + } + _, err = workflowColl.BulkWrite(context.TODO(), ms) + + return err +} + +func workflowRollBackNotifyCtls() error { + workflowColl := internalmongodb.NewWorkflowColl() + + workflows, err := workflowColl.List(&internalmongodb.ListWorkflowOption{}) + if err != nil { + return fmt.Errorf("failed to list all data in `zadig.workflow`,err: %s", err) + } + + if len(workflows) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, workflow := range workflows { + for _, notifyctl := range workflow.NotifyCtls { + if notifyctl != nil && notifyctl.Enabled { + workflow.NotifyCtl = notifyctl + break + } + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", workflow.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"notify_ctl", workflow.NotifyCtl}, + }}, + }), + ) + } + _, err = workflowColl.BulkWrite(context.TODO(), ms) + + return err +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/testing.go b/pkg/cli/upgradeassistant/internal/repository/models/testing.go index dc9d041c..e6629acf 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/testing.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/testing.go @@ -25,12 +25,14 @@ import ( ) type Testing struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - PreTest *PreTest `bson:"pre_test" json:"pre_test"` - Timeout int `bson:"timeout" json:"timeout"` - HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` - NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` - Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + PreTest *PreTest `bson:"pre_test" json:"pre_test"` + Timeout int `bson:"timeout" json:"timeout"` + HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` + + Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` diff --git a/pkg/cli/upgradeassistant/internal/repository/models/workflow.go b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go new file mode 100644 index 00000000..1402098f --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Workflow struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Enabled bool `bson:"enabled" json:"enabled"` + ProductTmplName string `bson:"product_tmpl_name" json:"product_tmpl_name"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + EnvName string `bson:"env_name" json:"env_name,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + UpdateBy string `bson:"update_by" json:"update_by,omitempty"` + CreateBy string `bson:"create_by" json:"create_by,omitempty"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + CreateTime int64 `bson:"create_time" json:"create_time"` + Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` + ScheduleEnabled bool `bson:"schedule_enabled" json:"schedule_enabled"` + // TODO: Deprecated. + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + // New since V1.12.0. + NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` + BaseName string `bson:"base_name" json:"base_name"` + + // ResetImage indicate whether reset image to original version after completion + ResetImage bool `bson:"reset_image" json:"reset_image"` + // IsParallel 控制单一工作流的任务是否支持并行处理 + IsParallel bool `json:"is_parallel" bson:"is_parallel"` +} + +func (Workflow) TableName() string { + return "workflow" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/workflow.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/workflow.go new file mode 100644 index 00000000..04c810bf --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/workflow.go @@ -0,0 +1,91 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ListWorkflowOption struct { + IsSort bool + Projects []string + Names []string + Ids []string +} + +type WorkflowColl struct { + *mongo.Collection + + coll string +} + +func NewWorkflowColl() *WorkflowColl { + name := models.Workflow{}.TableName() + return &WorkflowColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *WorkflowColl) GetCollectionName() string { + return c.coll +} + +func (c *WorkflowColl) List(opt *ListWorkflowOption) ([]*models.Workflow, error) { + query := bson.M{} + if len(opt.Projects) > 0 { + query["product_tmpl_name"] = bson.M{"$in": opt.Projects} + } + if len(opt.Names) > 0 { + query["name"] = bson.M{"$in": opt.Names} + } + if len(opt.Ids) > 0 { + var oids []primitive.ObjectID + for _, id := range opt.Ids { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + oids = append(oids, oid) + } + query["_id"] = bson.M{"$in": oids} + } + + resp := make([]*models.Workflow, 0) + ctx := context.Background() + opts := options.Find() + if opt.IsSort { + opts.SetSort(bson.D{{"update_time", -1}}) + } + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/models/pipeline.go b/pkg/microservice/aslan/core/common/repository/models/pipeline.go index aa1c122b..960193e2 100644 --- a/pkg/microservice/aslan/core/common/repository/models/pipeline.go +++ b/pkg/microservice/aslan/core/common/repository/models/pipeline.go @@ -51,8 +51,11 @@ type Pipeline struct { PassRate float32 `bson:"pass_rate,omitempty" json:"pass_rate,omitempty"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` Slack *Slack `bson:"slack" json:"slack"` - NotifyCtl *NotifyCtl `bson:"notify_ctl" json:"notify_ctl"` - IsFavorite bool `bson:"-" json:"is_favorite"` + // TODO: Deprecated. + NotifyCtl *NotifyCtl `bson:"notify_ctl" json:"notify_ctl"` + // New since V1.12.0. + NotifyCtls []*NotifyCtl `bson:"notify_ctls" json:"notify_ctls"` + IsFavorite bool `bson:"-" json:"is_favorite"` // 是否允许同时运行多次 MultiRun bool `bson:"multi_run" json:"multi_run"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index 386c8e42..871583bd 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -45,16 +45,19 @@ type Testing struct { // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` - ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` - TestCaseNum int `bson:"-" json:"test_case_num,omitempty"` - ExecuteNum int `bson:"-" json:"execute_num,omitempty"` - PassRate float64 `bson:"-" json:"pass_rate,omitempty"` - AvgDuration float64 `bson:"-" json:"avg_duration,omitempty"` - Workflows []*Workflow `bson:"-" json:"workflows,omitempty"` - Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` - HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` - NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` - ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` + ArtifactPaths []string `bson:"artifact_paths,omitempty" json:"artifact_paths,omitempty"` + TestCaseNum int `bson:"-" json:"test_case_num,omitempty"` + ExecuteNum int `bson:"-" json:"execute_num,omitempty"` + PassRate float64 `bson:"-" json:"pass_rate,omitempty"` + AvgDuration float64 `bson:"-" json:"avg_duration,omitempty"` + Workflows []*Workflow `bson:"-" json:"workflows,omitempty"` + Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` + HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + // TODO: Deprecated. + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + // New since V1.12.0. + NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` + ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` // New since V1.10.0. CacheEnable bool `bson:"cache_enable" json:"cache_enable"` diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 10ea963b..59f325ac 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -48,7 +48,10 @@ type Workflow struct { SecurityStage *SecurityStage `bson:"security_stage" json:"security_stage"` DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` ExtensionStage *ExtensionStage `bson:"extension_stage" json:"extension_stage"` + // TODO: Deprecated. NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + // New since V1.12.0. + NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` BaseName string `bson:"base_name" json:"base_name"` diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 669f209e..2185c3a2 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -28,6 +28,7 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" @@ -97,6 +98,51 @@ func (w *Service) SendMessageRequest(uri string, message interface{}) ([]byte, e } func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool) error { + var notifyCtls []*models.NotifyCtl + var desc string + switch task.Type { + case config.SingleType: + resp, err := w.pipelineColl.Find(&mongodb.PipelineFindOption{Name: task.PipelineName}) + if err != nil { + log.Errorf("failed to find Pipeline,err: %s", err) + return err + } + notifyCtls = resp.NotifyCtls + + case config.WorkflowType: + resp, err := w.workflowColl.Find(task.PipelineName) + if err != nil { + log.Errorf("failed to find Workflow,err: %s", err) + return err + } + notifyCtls = resp.NotifyCtls + + case config.TestType: + resp, err := w.testingColl.Find(strings.TrimSuffix(task.PipelineName, "-job"), task.ProductName) + if err != nil { + log.Errorf("failed to find Testing,err: %s", err) + return err + } + notifyCtls = resp.NotifyCtls + + default: + log.Errorf("task type is not supported!") + return nil + } + + for _, notifyCtl := range notifyCtls { + if err := w.sendMessage(task, notifyCtl, testTaskStatusChanged, desc); err != nil { + log.Errorf("send %s message err: %s", notifyCtl.WebHookType, err) + continue + } + } + return nil +} + +func (w *Service) sendMessage(task *task.Task, notifyCtl *models.NotifyCtl, testTaskStatusChanged bool, desc string) error { + if notifyCtl == nil { + return nil + } var ( uri = "" content = "" @@ -105,27 +151,19 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool isAtAll bool title = "" larkCard *LarkCard + err error ) if task.Type == config.SingleType { - resp, err := w.pipelineColl.Find(&mongodb.PipelineFindOption{Name: task.PipelineName}) - if err != nil { - log.Errorf("Pipeline find err :%s", err) - return err - } - if resp.NotifyCtl == nil { - log.Infof("pipeline notifyCtl is not set!") - return nil - } - if resp.NotifyCtl.Enabled && sets.NewString(resp.NotifyCtl.NotifyTypes...).Has(string(task.Status)) { - webHookType = resp.NotifyCtl.WebHookType + if notifyCtl.Enabled && sets.NewString(notifyCtl.NotifyTypes...).Has(string(task.Status)) { + webHookType = notifyCtl.WebHookType if webHookType == dingDingType { - uri = resp.NotifyCtl.DingDingWebHook - atMobiles = resp.NotifyCtl.AtMobiles - isAtAll = resp.NotifyCtl.IsAtAll + uri = notifyCtl.DingDingWebHook + atMobiles = notifyCtl.AtMobiles + isAtAll = notifyCtl.IsAtAll } else if webHookType == feiShuType { - uri = resp.NotifyCtl.FeiShuWebHook + uri = notifyCtl.FeiShuWebHook } else { - uri = resp.NotifyCtl.WeChatWebHook + uri = notifyCtl.WeChatWebHook } content, err = w.createNotifyBody(&wechatNotification{ Task: task, @@ -142,25 +180,16 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } } } else if task.Type == config.WorkflowType { - resp, err := w.workflowColl.Find(task.PipelineName) - if err != nil { - log.Errorf("Workflow find err :%s", err) - return err - } - if resp.NotifyCtl == nil { - log.Infof("Workflow notifyCtl is not set!") - return nil - } - if resp.NotifyCtl.Enabled && sets.NewString(resp.NotifyCtl.NotifyTypes...).Has(string(task.Status)) { - webHookType = resp.NotifyCtl.WebHookType + if notifyCtl.Enabled && sets.NewString(notifyCtl.NotifyTypes...).Has(string(task.Status)) { + webHookType = notifyCtl.WebHookType if webHookType == dingDingType { - uri = resp.NotifyCtl.DingDingWebHook - atMobiles = resp.NotifyCtl.AtMobiles - isAtAll = resp.NotifyCtl.IsAtAll + uri = notifyCtl.DingDingWebHook + atMobiles = notifyCtl.AtMobiles + isAtAll = notifyCtl.IsAtAll } else if webHookType == feiShuType { - uri = resp.NotifyCtl.FeiShuWebHook + uri = notifyCtl.FeiShuWebHook } else { - uri = resp.NotifyCtl.WeChatWebHook + uri = notifyCtl.WeChatWebHook } title, content, larkCard, err = w.createNotifyBodyOfWorkflowIM(&wechatNotification{ Task: task, @@ -177,28 +206,19 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool } } } else if task.Type == config.TestType { - resp, err := w.testingColl.Find(strings.TrimSuffix(task.PipelineName, "-job"), task.ProductName) - if err != nil { - log.Errorf("testing find err :%s", err) - return err - } - if resp.NotifyCtl == nil { - log.Infof("testing notifyCtl is not set!") - return nil - } - statusSets := sets.NewString(resp.NotifyCtl.NotifyTypes...) - if resp.NotifyCtl.Enabled && (statusSets.Has(string(task.Status)) || (testTaskStatusChanged && statusSets.Has(string(config.StatusChanged)))) { - webHookType = resp.NotifyCtl.WebHookType + statusSets := sets.NewString(notifyCtl.NotifyTypes...) + if notifyCtl.Enabled && (statusSets.Has(string(task.Status)) || (testTaskStatusChanged && statusSets.Has(string(config.StatusChanged)))) { + webHookType = notifyCtl.WebHookType if webHookType == dingDingType { - uri = resp.NotifyCtl.DingDingWebHook - atMobiles = resp.NotifyCtl.AtMobiles - isAtAll = resp.NotifyCtl.IsAtAll + uri = notifyCtl.DingDingWebHook + atMobiles = notifyCtl.AtMobiles + isAtAll = notifyCtl.IsAtAll } else if webHookType == feiShuType { - uri = resp.NotifyCtl.FeiShuWebHook + uri = notifyCtl.FeiShuWebHook } else { - uri = resp.NotifyCtl.WeChatWebHook + uri = notifyCtl.WeChatWebHook } - title, content, larkCard, err = w.createNotifyBodyOfTestIM(resp.Desc, &wechatNotification{ + title, content, larkCard, err = w.createNotifyBodyOfTestIM(desc, &wechatNotification{ Task: task, BaseURI: configbase.SystemAddress(), IsSingle: false, @@ -327,7 +347,7 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific if buildSt.BuildStatus.Status == "" { buildSt.BuildStatus.Status = config.StatusNotRun } - buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**服务名称**:%s \n", buildSt.Service) + buildElemTemp += fmt.Sprintf("\n\n{{if eq .WebHookType \"dingding\"}}---\n\n##### {{end}}**服务名称**:%s \n", buildSt.Service) if !(buildSt.ServiceType == setting.PMDeployType && (buildSt.JobCtx.FileArchiveCtx != nil || buildSt.JobCtx.DockerBuildCtx != nil)) { buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.JobCtx.Image) -- Gitee From 46dc1b1d95cec6d7962b9c82b16d10319aaf6ddd Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 26 Apr 2022 17:11:12 +0800 Subject: [PATCH 2291/2647] admin add desc (#1415) Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/admin.yaml | 1 + pkg/cli/initconfig/cmd/init.go | 6 +++--- pkg/microservice/policy/core/service/role.go | 1 + pkg/shared/client/policy/role.go | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml index fe11e12a..03bf5c61 100644 --- a/pkg/cli/initconfig/cmd/admin.yaml +++ b/pkg/cli/initconfig/cmd/admin.yaml @@ -1,5 +1,6 @@ name: admin type: system +desc: "拥有系统中任何操作的权限" rules: - verbs: - "*" diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 535f78d7..891db925 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -203,11 +203,11 @@ func presetRoleBinding(uid string) error { func presetRole() error { g := new(errgroup.Group) g.Go(func() error { - systemRole := &policy.Role{} - if err := yaml.Unmarshal(admin, systemRole); err != nil { + systemAdminRole := &policy.Role{} + if err := yaml.Unmarshal(admin, systemAdminRole); err != nil { log.DPanic(err) } - return policy.NewDefault().CreateSystemRole(systemRole.Name, systemRole) + return policy.NewDefault().CreateSystemRole(systemAdminRole.Name, systemAdminRole) }) rolesArray := [][]byte{readOnly, readProjectOnly, contributor, projectAdmin} diff --git a/pkg/microservice/policy/core/service/role.go b/pkg/microservice/policy/core/service/role.go index e5aa2d8d..ef5f1d77 100644 --- a/pkg/microservice/policy/core/service/role.go +++ b/pkg/microservice/policy/core/service/role.go @@ -77,6 +77,7 @@ func UpdateRole(ns string, role *Role, _ *zap.SugaredLogger) error { func UpdateOrCreateRole(ns string, role *Role, _ *zap.SugaredLogger) error { obj := &models.Role{ Name: role.Name, + Desc: role.Desc, Namespace: ns, Type: role.Type, } diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index 247b56d4..809e4fc3 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -132,6 +132,7 @@ func (c *Client) CreatePresetRole(name string, role *Role) error { type Role struct { Name string `json:"name"` Type setting.ResourceType `json:"type"` + Desc string `json:"desc"` Rules []*struct { Verbs []string `json:"verbs"` Resources []string `json:"resources"` -- Gitee From 7808b787119beaf2c665068c4fd914fa9302ac42 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Tue, 26 Apr 2022 17:37:45 +0800 Subject: [PATCH 2292/2647] Sonar Integration (#1393) * added Sonar integration function Signed-off-by: Min Min * stuff Signed-off-by: Min Min * sonar related function done Signed-off-by: Min Min * debugg Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * code review changed Signed-off-by: Min Min * even more code review changes Signed-off-by: Min Min * added liscense Signed-off-by: Min Min --- .../core/common/repository/models/sonar.go | 29 +++ .../core/common/repository/mongodb/sonar.go | 134 +++++++++++++ .../aslan/core/system/handler/router.go | 13 ++ .../aslan/core/system/handler/sonar.go | 179 ++++++++++++++++++ .../aslan/core/system/service/sonar.go | 140 ++++++++++++++ .../aslan/core/system/service/types.go | 6 + .../core/service/bundle/exemption_urls.go | 4 + 7 files changed, 505 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/repository/models/sonar.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/sonar.go create mode 100644 pkg/microservice/aslan/core/system/handler/sonar.go create mode 100644 pkg/microservice/aslan/core/system/service/sonar.go diff --git a/pkg/microservice/aslan/core/common/repository/models/sonar.go b/pkg/microservice/aslan/core/common/repository/models/sonar.go new file mode 100644 index 00000000..e81499c1 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/sonar.go @@ -0,0 +1,29 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type SonarIntegration struct { + ID primitive.ObjectID `bson:"_id,omitempty"` + ServerAddress string `bson:"server_address"` + Token string `bson:"token"` +} + +func (SonarIntegration) TableName() string { + return "sonar_integration" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go b/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go new file mode 100644 index 00000000..278261c3 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go @@ -0,0 +1,134 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type SonarIntegrationColl struct { + *mongo.Collection + + coll string +} + +func NewSonarIntegrationColl() *SonarIntegrationColl { + name := models.SonarIntegration{}.TableName() + return &SonarIntegrationColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *SonarIntegrationColl) GetCollectionName() string { + return c.coll +} + +func (c *SonarIntegrationColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *SonarIntegrationColl) Create(ctx context.Context, args *models.SonarIntegration) error { + if args == nil { + return errors.New("sonar integration is nil") + } + + _, err := c.InsertOne(ctx, args) + + return err +} + +func (c *SonarIntegrationColl) List(ctx context.Context, pageNum, pageSize int64) ([]*models.SonarIntegration, int64, error) { + query := bson.M{} + resp := make([]*models.SonarIntegration, 0) + + opt := options.Find() + + if pageNum != 0 && pageSize != 0 { + opt = opt. + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + } + + cursor, err := c.Collection.Find(ctx, query, opt) + if err != nil { + return nil, 0, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, 0, err + } + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return nil, 0, err + } + + return resp, count, nil +} + +func (c *SonarIntegrationColl) GetByID(ctx context.Context, idstring string) (*models.SonarIntegration, error) { + resp := new(models.SonarIntegration) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(ctx, query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *SonarIntegrationColl) Update(ctx context.Context, idString string, obj *models.SonarIntegration) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(ctx, filter, update) + return err +} + +func (c *SonarIntegrationColl) DeleteByID(ctx context.Context, idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(ctx, query) + return err +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index db7f1397..b1c64954 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -226,4 +226,17 @@ func (*Router) Inject(router *gin.RouterGroup) { externalSystem.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateExternalSystem) externalSystem.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteExternalSystem) } + + // --------------------------------------------------------------------------------------- + // sonar integration API + // --------------------------------------------------------------------------------------- + sonar := router.Group("sonar") + { + sonar.POST("/integration", gin2.UpdateOperationLogStatus, CreateSonarIntegration) + sonar.PUT("/integration/:id", gin2.UpdateOperationLogStatus, UpdateSonarIntegration) + sonar.GET("/integration", ListSonarIntegration) + sonar.GET("/integration/:id", GetSonarIntegration) + sonar.DELETE("/integration/:id", gin2.UpdateOperationLogStatus, DeleteSonarIntegration) + sonar.POST("/validate", ValidateSonarInformation) + } } diff --git a/pkg/microservice/aslan/core/system/handler/sonar.go b/pkg/microservice/aslan/core/system/handler/sonar.go new file mode 100644 index 00000000..f9889661 --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/sonar.go @@ -0,0 +1,179 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/tool/crypto" + + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +var SonarIntegrationValidationError = errors.New("name and server must be provided") + +func CreateSonarIntegration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.SonarIntegration) + + data, err := c.GetRawData() + if err != nil { + log.Errorf("Create sonar integration GetRawData err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("Create sonar integration Unmarshal err : %s", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "系统配置-Sonar集成", fmt.Sprintf("server: %s, token: %s", args.ServerAddress, args.Token), string(data), ctx.Logger) + + if err != nil { + ctx.Err = fmt.Errorf("failed to update sonar integration: %s", err) + return + } + + if args.ServerAddress == "" || args.Token == "" { + ctx.Err = SonarIntegrationValidationError + return + } + ctx.Err = service.CreateSonarIntegration(args, ctx.Logger) +} + +func UpdateSonarIntegration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.SonarIntegration) + + data, err := c.GetRawData() + if err != nil { + log.Errorf("Update sonar integration GetRawData err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("Update sonar integration Unmarshal err : %s", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "系统配置-Sonar集成", fmt.Sprintf("server: %s, token: %s", args.ServerAddress, args.Token), string(data), ctx.Logger) + + if err != nil { + ctx.Err = fmt.Errorf("failed to update sonar integration: %s", err) + return + } + + if args.ServerAddress == "" || args.Token == "" { + ctx.Err = SonarIntegrationValidationError + return + } + ctx.Err = service.UpdateSonarIntegration(c.Param("id"), args, ctx.Logger) +} + +func ListSonarIntegration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + + aesKey, err := commonservice.GetAesKeyFromEncryptedKey(encryptedKey, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + sonarList, _, err := service.ListSonarIntegration(ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + for _, sonar := range sonarList { + encryptedSonarToken, err := crypto.AesEncryptByKey(sonar.Token, aesKey.PlainText) + if err != nil { + ctx.Err = fmt.Errorf("failed to encrypt sonar token, err: %s", err) + return + } + sonar.Token = encryptedSonarToken + } + ctx.Resp = sonarList +} + +func GetSonarIntegration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + + aesKey, err := commonservice.GetAesKeyFromEncryptedKey(encryptedKey, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + resp, err := service.GetSonarIntegration(c.Param("id"), ctx.Logger) + if err != nil { + ctx.Err = err + return + } + encryptedSonarToken, err := crypto.AesEncryptByKey(resp.Token, aesKey.PlainText) + if err != nil { + ctx.Err = fmt.Errorf("failed to encrypt sonar token, err: %s", err) + return + } + resp.Token = encryptedSonarToken + ctx.Resp = resp +} + +func DeleteSonarIntegration(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "系统配置-Sonar集成", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + ctx.Err = service.DeleteSonarIntegration(c.Param("id"), ctx.Logger) +} + +func ValidateSonarInformation(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.SonarIntegration) + + data, err := c.GetRawData() + if err != nil { + log.Errorf("Validate sonar integration GetRawData err : %s", err) + ctx.Err = fmt.Errorf("validate sonar integration GetRawData err : %s", err) + return + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("Validate sonar integration Unmarshal err : %s", err) + ctx.Err = fmt.Errorf("validate sonar integration Unmarshal err : %s", err) + return + } + + ctx.Err = service.ValidateSonarIntegration(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/service/sonar.go b/pkg/microservice/aslan/core/system/service/sonar.go new file mode 100644 index 00000000..a3aae4a0 --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/sonar.go @@ -0,0 +1,140 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func CreateSonarIntegration(args *SonarIntegration, log *zap.SugaredLogger) error { + err := commonrepo.NewSonarIntegrationColl().Create(context.TODO(), &commonmodels.SonarIntegration{ + ServerAddress: args.ServerAddress, + Token: args.Token, + }) + if err != nil { + log.Errorf("Create external system error: %s", err) + return e.ErrCreateExternalLink.AddErr(err) + } + return nil +} + +func UpdateSonarIntegration(id string, integration *SonarIntegration, log *zap.SugaredLogger) error { + err := commonrepo.NewSonarIntegrationColl().Update( + context.TODO(), + id, + &commonmodels.SonarIntegration{ + ServerAddress: integration.ServerAddress, + Token: integration.Token, + }, + ) + if err != nil { + log.Errorf("update external system error: %s", err) + } + return err +} + +func ListSonarIntegration(log *zap.SugaredLogger) ([]*SonarIntegration, int64, error) { + // for now paging is not supported + sonarList, length, err := commonrepo.NewSonarIntegrationColl().List(context.TODO(), 0, 0) + if err != nil { + log.Errorf("Failed to list sonar integration from db, the error is: %s", err) + return nil, 0, err + } + resp := make([]*SonarIntegration, 0) + for _, sonar := range sonarList { + resp = append(resp, &SonarIntegration{ + ID: sonar.ID.Hex(), + ServerAddress: sonar.ServerAddress, + Token: sonar.Token, + }) + } + return resp, length, nil +} + +func GetSonarIntegration(id string, log *zap.SugaredLogger) (*SonarIntegration, error) { + resp := new(SonarIntegration) + sonarIntegration, err := commonrepo.NewSonarIntegrationColl().GetByID(context.TODO(), id) + if err != nil { + log.Errorf("Failed to get sonar integration detail from id %s, the error is: %s", id, err) + return nil, err + } + resp.ID = sonarIntegration.ID.Hex() + resp.ServerAddress = sonarIntegration.ServerAddress + resp.Token = sonarIntegration.Token + return resp, nil +} + +func DeleteSonarIntegration(id string, log *zap.SugaredLogger) error { + err := commonrepo.NewSonarIntegrationColl().DeleteByID(context.TODO(), id) + if err != nil { + log.Errorf("Failed to delete sonar integration of id: %s, the error is: %s", id, err) + } + return err +} + +type sonarValidationResponse struct { + Valid bool `json:"valid"` +} + +func ValidateSonarIntegration(arg *SonarIntegration, log *zap.SugaredLogger) error { + validateAPI := fmt.Sprintf("%s/%s", arg.ServerAddress, "api/authentication/validate") + + req, err := http.NewRequest("GET", validateAPI, nil) + if err != nil { + log.Errorf("failed to create http request to API: %s with error: %s", validateAPI, err) + return err + } + + req.SetBasicAuth(arg.Token, "") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Errorf("failed to request the sonar validate API, the error is: %s", err) + return err + } + defer resp.Body.Close() + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + log.Errorf("failed to read response body, the error is: %s", err) + return err + } + if resp.StatusCode == http.StatusOK { + res := &sonarValidationResponse{} + err = json.Unmarshal(bodyBytes, res) + if err != nil { + log.Errorf("failed to ready response body, the error is: %s", err) + return err + } + if res.Valid { + return nil + } + return errors.New("the token validation failed.") + } + + return fmt.Errorf("the server responded with code: %d and message: %s", resp.StatusCode, string(bodyBytes)) +} diff --git a/pkg/microservice/aslan/core/system/service/types.go b/pkg/microservice/aslan/core/system/service/types.go index 917fe8db..f5a17053 100644 --- a/pkg/microservice/aslan/core/system/service/types.go +++ b/pkg/microservice/aslan/core/system/service/types.go @@ -27,3 +27,9 @@ type WorkflowConcurrencySettings struct { WorkflowConcurrency int64 `json:"workflow_concurrency"` BuildConcurrency int64 `json:"build_concurrency"` } + +type SonarIntegration struct { + ID string `json:"id"` + ServerAddress string `json:"server_address"` + Token string `json:"token"` +} diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index af4d09ce..f9dd7f7e 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -128,6 +128,10 @@ var systemAdminURLs = []*policyRule{ Methods: []string{"POST"}, Endpoints: []string{"api/aslan/system/cleanCache/cron"}, }, + { + Methods: []string{"GET", "POST", "PUT", "DELETE"}, + Endpoints: []string{"api/aslan/system/sonar/?*"}, + }, { Methods: []string{"POST"}, Endpoints: []string{"api/v1/picket/projects"}, -- Gitee From 8c6b5881c43953776618cf41934710f7fce042c7 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 27 Apr 2022 11:04:59 +0800 Subject: [PATCH 2293/2647] fix for when frontend pass an empty list (#1420) Signed-off-by: dianqihanwangzi --- pkg/cli/upgradeassistant/cmd/migrate/1120.go | 4 ++-- .../internal/repository/models/testing.go | 2 +- .../internal/repository/models/workflow.go | 2 +- .../aslan/core/common/repository/models/testing.go | 2 +- .../aslan/core/common/repository/models/workflow.go | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1120.go b/pkg/cli/upgradeassistant/cmd/migrate/1120.go index 28cf65b9..dd9b48e0 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1120.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1120.go @@ -81,7 +81,7 @@ func moduleTestingAddNotifyCtls() error { if testing.NotifyCtls != nil { continue } - if testing.NotifyCtl != nil { + if testing.NotifyCtl != nil && testing.NotifyCtl.Enabled { testing.NotifyCtls = []*models.NotifyCtl{testing.NotifyCtl} } @@ -154,7 +154,7 @@ func workflowAddNotifyCtls() error { if workflow.NotifyCtls != nil { continue } - if workflow.NotifyCtl != nil { + if workflow.NotifyCtl != nil && workflow.NotifyCtl.Enabled { workflow.NotifyCtls = []*models.NotifyCtl{workflow.NotifyCtl} } diff --git a/pkg/cli/upgradeassistant/internal/repository/models/testing.go b/pkg/cli/upgradeassistant/internal/repository/models/testing.go index e6629acf..204f8f70 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/testing.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/testing.go @@ -30,7 +30,7 @@ type Testing struct { Timeout int `bson:"timeout" json:"timeout"` HookCtl *TestingHookCtrl `bson:"hook_ctl" json:"hook_ctl"` NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` - NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` + NotifyCtls []*NotifyCtl `bson:"notify_ctls" json:"notify_ctls"` Schedules *ScheduleCtrl `bson:"schedules,omitempty" json:"schedules,omitempty"` diff --git a/pkg/cli/upgradeassistant/internal/repository/models/workflow.go b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go index 1402098f..c543538f 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/workflow.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/workflow.go @@ -37,7 +37,7 @@ type Workflow struct { // TODO: Deprecated. NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` // New since V1.12.0. - NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` + NotifyCtls []*NotifyCtl `bson:"notify_ctls" json:"notify_ctls"` BaseName string `bson:"base_name" json:"base_name"` // ResetImage indicate whether reset image to original version after completion diff --git a/pkg/microservice/aslan/core/common/repository/models/testing.go b/pkg/microservice/aslan/core/common/repository/models/testing.go index 871583bd..44038666 100644 --- a/pkg/microservice/aslan/core/common/repository/models/testing.go +++ b/pkg/microservice/aslan/core/common/repository/models/testing.go @@ -56,7 +56,7 @@ type Testing struct { // TODO: Deprecated. NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` // New since V1.12.0. - NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` + NotifyCtls []*NotifyCtl `bson:"notify_ctls" json:"notify_ctls"` ScheduleEnabled bool `bson:"schedule_enabled" json:"-"` // New since V1.10.0. diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 59f325ac..191460cc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -49,11 +49,11 @@ type Workflow struct { DistributeStage *DistributeStage `bson:"distribute_stage" json:"distribute_stage"` ExtensionStage *ExtensionStage `bson:"extension_stage" json:"extension_stage"` // TODO: Deprecated. - NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` + NotifyCtl *NotifyCtl `bson:"notify_ctl,omitempty" json:"notify_ctl,omitempty"` // New since V1.12.0. - NotifyCtls []*NotifyCtl `bson:"notify_ctls,omitempty" json:"notify_ctls,omitempty"` - HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` - BaseName string `bson:"base_name" json:"base_name"` + NotifyCtls []*NotifyCtl `bson:"notify_ctls" json:"notify_ctls"` + HookCtl *WorkflowHookCtrl `bson:"hook_ctl" json:"hook_ctl"` + BaseName string `bson:"base_name" json:"base_name"` // ResetImage indicate whether reset image to original version after completion ResetImage bool `bson:"reset_image" json:"reset_image"` -- Gitee From e6df401150556b7714bd775cb483d6ef73b8eebd Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 27 Apr 2022 11:47:43 +0800 Subject: [PATCH 2294/2647] support send jenkins build notifications (#1421) Signed-off-by: dianqihanwangzi --- .../core/common/service/instantmessage/service.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index 2185c3a2..a5cbf00f 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -357,6 +357,18 @@ func (w *Service) createNotifyBodyOfWorkflowIM(weChatNotification *wechatNotific build = append(build, buildElemTemp) } + case config.TaskJenkinsBuild: + for _, sb := range subStage.SubTasks { + buildElemTemp := "" + buildSt, err := base.ToJenkinsBuildTask(sb) + if err != nil { + return "", "", nil, err + } + buildElemTemp += fmt.Sprintf("\n\n{{if eq .WebHookType \"dingding\"}}---\n\n##### {{end}}**服务名称**:%s \n", buildSt.Service) + buildElemTemp += fmt.Sprintf("{{if eq .WebHookType \"dingding\"}}##### {{end}}**镜像信息**:%s \n", buildSt.Image) + build = append(build, buildElemTemp) + } + case config.TaskTestingV2: test = "{{if eq .WebHookType \"dingding\"}}##### {{end}}**测试结果** \n" for _, sb := range subStage.SubTasks { -- Gitee From 0ada8a891d45df33aa9b4b4727c30ad7231ffe2e Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Wed, 27 Apr 2022 13:38:36 +0800 Subject: [PATCH 2295/2647] fix List API does not return kind and apiVersion (#1422) Signed-off-by: ddh27 --- .../aslan/core/environment/service/configmap.go | 6 ++++++ pkg/microservice/aslan/core/environment/service/pvc.go | 6 ++++++ pkg/microservice/aslan/core/environment/service/secret.go | 6 ++++++ pkg/tool/kube/getter/configmap.go | 2 +- pkg/tool/kube/getter/pvc.go | 2 +- pkg/tool/kube/getter/secrets.go | 2 +- 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 6a8e8e50..e23984e9 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -148,6 +148,12 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListCon } cm.SetManagedFields(nil) cm.SetResourceVersion("") + if cm.APIVersion == "" { + cm.APIVersion = "v1" + } + if cm.Kind == "" { + cm.Kind = "ConfigMap" + } yamlData, err := yaml.Marshal(cm) if err != nil { return err diff --git a/pkg/microservice/aslan/core/environment/service/pvc.go b/pkg/microservice/aslan/core/environment/service/pvc.go index cdb81ee1..c166d3af 100644 --- a/pkg/microservice/aslan/core/environment/service/pvc.go +++ b/pkg/microservice/aslan/core/environment/service/pvc.go @@ -77,6 +77,12 @@ func ListPvcs(envName, productName string, log *zap.SugaredLogger) ([]*ListPvcsR pvcProcess := func(pvc *v1.PersistentVolumeClaim) error { pvc.SetManagedFields(nil) pvc.SetResourceVersion("") + if pvc.APIVersion == "" { + pvc.APIVersion = "v1" + } + if pvc.Kind == "" { + pvc.Kind = "PersistentVolumeClaim" + } yamlData, err := yaml.Marshal(pvc) if err != nil { return err diff --git a/pkg/microservice/aslan/core/environment/service/secret.go b/pkg/microservice/aslan/core/environment/service/secret.go index 75612491..9f1c7dfd 100644 --- a/pkg/microservice/aslan/core/environment/service/secret.go +++ b/pkg/microservice/aslan/core/environment/service/secret.go @@ -73,6 +73,12 @@ func ListSecrets(envName, productName string, log *zap.SugaredLogger) ([]*ListSe secretProcess := func(secret *corev1.Secret) error { secret.SetManagedFields(nil) secret.SetResourceVersion("") + if secret.APIVersion == "" { + secret.APIVersion = "v1" + } + if secret.Kind == "" { + secret.Kind = "Secret" + } yamlData, err := yaml.Marshal(secret) if err != nil { return err diff --git a/pkg/tool/kube/getter/configmap.go b/pkg/tool/kube/getter/configmap.go index 2e71f22f..fc429e8d 100644 --- a/pkg/tool/kube/getter/configmap.go +++ b/pkg/tool/kube/getter/configmap.go @@ -26,7 +26,7 @@ import ( func ListConfigMaps(ns string, selector labels.Selector, cl client.Client) ([]*corev1.ConfigMap, error) { l := &corev1.ConfigMapList{} gvk := schema.GroupVersionKind{ - Group: "", + Group: "core", Kind: "ConfigMap", Version: "v1", } diff --git a/pkg/tool/kube/getter/pvc.go b/pkg/tool/kube/getter/pvc.go index 4404c33f..935ff3f9 100644 --- a/pkg/tool/kube/getter/pvc.go +++ b/pkg/tool/kube/getter/pvc.go @@ -26,7 +26,7 @@ import ( func ListPvcs(ns string, selector fields.Selector, cl client.Reader) ([]*corev1.PersistentVolumeClaim, error) { pvcList := &corev1.PersistentVolumeClaimList{} gvk := schema.GroupVersionKind{ - Group: "", + Group: "core", Kind: "PersistentVolumeClaim", Version: "v1", } diff --git a/pkg/tool/kube/getter/secrets.go b/pkg/tool/kube/getter/secrets.go index d155ee40..08e9443c 100644 --- a/pkg/tool/kube/getter/secrets.go +++ b/pkg/tool/kube/getter/secrets.go @@ -35,7 +35,7 @@ func GetSecret(ns, name string, cl client.Client) (*corev1.Secret, bool, error) func ListSecrets(ns string, cl client.Client) ([]*corev1.Secret, error) { l := &corev1.SecretList{} gvk := schema.GroupVersionKind{ - Group: "", + Group: "core", Kind: "Secret", Version: "v1", } -- Gitee From 658543d3f488323c461625e1af82ab401bc59a7d Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 27 Apr 2022 14:45:46 +0800 Subject: [PATCH 2296/2647] fixed the failure to display the number of services due to some service deployment failures (#1427) Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/environment/service/environment.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 22a4ae84..68672f3c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -563,11 +563,9 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. updateProd.Services = updatedServices updateProd.ShareEnv = existedProd.ShareEnv - log.Infof("[Namespace:%s][Product:%s]: update service orchestration in product. Status: %s", envName, productName, updateProd.Status) - if err = commonrepo.NewProductColl().Update(updateProd); err != nil { - log.Errorf("[Namespace:%s][Product:%s] Product.Update error: %v", envName, productName, err) - err = e.ErrUpdateEnv.AddErr(err) - return + if err := commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUpdating); err != nil { + log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, productName, err) + return e.ErrUpdateEnv.AddDesc(e.UpdateEnvStatusErrMsg) } existedServices := existedProd.GetServiceMap() -- Gitee From 3424e02e1b8141d00b20f10e01c34e25adbbfec1 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Wed, 27 Apr 2022 15:55:08 +0800 Subject: [PATCH 2297/2647] fixed a bug where update aws secret will accidently update default registry secret (#1429) Signed-off-by: Min Min --- .../aslan/core/common/service/kube/actions.go | 65 ++++++++++++++++++- .../aslan/core/common/service/registry.go | 2 +- .../aslan/core/environment/service/image.go | 2 +- .../aslan/core/environment/service/service.go | 4 +- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/actions.go b/pkg/microservice/aslan/core/common/service/kube/actions.go index 43afde73..eeacfa75 100644 --- a/pkg/microservice/aslan/core/common/service/kube/actions.go +++ b/pkg/microservice/aslan/core/common/service/kube/actions.go @@ -19,6 +19,8 @@ package kube import ( "context" "fmt" + "regexp" + "strings" "time" corev1 "k8s.io/api/core/v1" @@ -38,6 +40,8 @@ import ( zadigtypes "github.com/koderover/zadig/pkg/types" ) +var registrySecretSuffix = "-registry-secret" + func CreateNamespace(namespace string, enableShare bool, kubeClient client.Client) error { labels := map[string]string{ setting.EnvCreatedBy: setting.EnvCreator, @@ -92,7 +96,22 @@ func CreateOrUpdateRSASecret(publicKey, privateKey []byte, kubeClient client.Cli return updater.UpdateOrCreateSecret(secret, kubeClient) } -func CreateOrUpdateRegistrySecret(namespace string, reg *commonmodels.RegistryNamespace, kubeClient client.Client) error { +func CreateOrUpdateDefaultRegistrySecret(namespace string, reg *commonmodels.RegistryNamespace, kubeClient client.Client) error { + return CreateOrUpdateRegistrySecret(namespace, reg, true, kubeClient) +} + +func CreateOrUpdateRegistrySecret(namespace string, reg *commonmodels.RegistryNamespace, isDefault bool, kubeClient client.Client) error { + var secretName string + var err error + if !isDefault { + secretName, err = genRegistrySecretName(reg) + if err != nil { + return fmt.Errorf("failed to generate registry secret name: %s", err) + } + } else { + secretName = setting.DefaultImagePullSecret + } + data := make(map[string][]byte) dockerConfig := fmt.Sprintf( @@ -107,7 +126,7 @@ func CreateOrUpdateRegistrySecret(namespace string, reg *commonmodels.RegistryNa secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, - Name: setting.DefaultImagePullSecret, + Name: secretName, }, Data: data, Type: corev1.SecretTypeDockercfg, @@ -115,6 +134,48 @@ func CreateOrUpdateRegistrySecret(namespace string, reg *commonmodels.RegistryNa return updater.UpdateOrCreateSecret(secret, kubeClient) } +func genRegistrySecretName(reg *commonmodels.RegistryNamespace) (string, error) { + if reg.IsDefault { + return setting.DefaultImagePullSecret, nil + } + + arr := strings.Split(reg.Namespace, "/") + namespaceInRegistry := arr[len(arr)-1] + + // for AWS ECR, there are no namespace, thus we need to find the NS from the URI + if namespaceInRegistry == "" { + uriDecipher := strings.Split(reg.RegAddr, ".") + namespaceInRegistry = uriDecipher[0] + } + + filteredName, err := formatRegistryName(namespaceInRegistry) + if err != nil { + return "", err + } + + secretName := filteredName + registrySecretSuffix + if reg.RegType != "" { + secretName = filteredName + "-" + reg.RegType + registrySecretSuffix + } + + return secretName, nil +} + +// Note: The name of a Secret object must be a valid DNS subdomain name: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names +func formatRegistryName(namespaceInRegistry string) (string, error) { + reg, err := regexp.Compile("[^a-zA-Z0-9\\.-]+") + if err != nil { + return "", err + } + processedName := reg.ReplaceAllString(namespaceInRegistry, "") + processedName = strings.ToLower(processedName) + if len(processedName) > 237 { + processedName = processedName[:237] + } + return processedName, nil +} + // GetDirtyResources searches for dirty active resources in the given namespace, and return their metadata. func GetDirtyResources(ns string, kubeClient client.Client) []metav1.Object { var oms []metav1.Object diff --git a/pkg/microservice/aslan/core/common/service/registry.go b/pkg/microservice/aslan/core/common/service/registry.go index dffcd9a5..9b7b0798 100644 --- a/pkg/microservice/aslan/core/common/service/registry.go +++ b/pkg/microservice/aslan/core/common/service/registry.go @@ -174,7 +174,7 @@ func EnsureDefaultRegistrySecret(namespace string, registryId string, kubeClient } } - err = kube.CreateOrUpdateRegistrySecret(namespace, reg, kubeClient) + err = kube.CreateOrUpdateDefaultRegistrySecret(namespace, reg, kubeClient) if err != nil { log.Errorf("[%s] CreateDockerSecret error: %s", namespace, err) return e.ErrUpdateSecret.AddDesc(e.CreateDefaultRegistryErrMsg) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index d3e2fada..fd4c3a61 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -222,7 +222,7 @@ func UpdateContainerImage(requestID string, args *UpdateContainerImageArgs, log } for _, reg := range regs { if reg.RegProvider == config.RegistryTypeAWS { - if err := kube.CreateOrUpdateRegistrySecret(namespace, reg, kubeClient); err != nil { + if err := kube.CreateOrUpdateRegistrySecret(namespace, reg, false, kubeClient); err != nil { retErr := fmt.Errorf("failed to update pull secret for registry: %s, the error is: %s", reg.ID.Hex(), err) log.Errorf("%s\n", retErr.Error()) return retErr diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index 0fecb95c..cd364d8f 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -150,7 +150,7 @@ func RestartScale(args *RestartScaleArgs, _ *zap.SugaredLogger) error { } for _, reg := range regs { if reg.RegProvider == config.RegistryTypeAWS { - if err := kube.CreateOrUpdateRegistrySecret(prod.Namespace, reg, kubeClient); err != nil { + if err := kube.CreateOrUpdateRegistrySecret(prod.Namespace, reg, false, kubeClient); err != nil { retErr := fmt.Errorf("failed to update pull secret for registry: %s, the error is: %s", reg.ID.Hex(), err) log.Errorf("%s\n", retErr.Error()) return retErr @@ -411,7 +411,7 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e } for _, reg := range regs { if reg.RegProvider == config.RegistryTypeAWS { - if err := kube.CreateOrUpdateRegistrySecret(productObj.Namespace, reg, kubeClient); err != nil { + if err := kube.CreateOrUpdateRegistrySecret(productObj.Namespace, reg, false, kubeClient); err != nil { retErr := fmt.Errorf("failed to update pull secret for registry: %s, the error is: %s", reg.ID.Hex(), err) log.Errorf("%s\n", retErr.Error()) return retErr -- Gitee From 5553e6b8ee9c28f64d78e13d73670ff5ff823b37 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 27 Apr 2022 16:15:19 +0800 Subject: [PATCH 2298/2647] action replicate (#1426) Signed-off-by: mouuii --- .../aslan/core/workflow/handler/system-policies.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index 85edac88..5366c456 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -150,7 +150,7 @@ alias: "交付中心" description: "" rules: - - action: get + - action: realase_get alias: "版本管理|查看" description: "查看" rules: @@ -158,7 +158,7 @@ endpoint: "/api/aslan/delivery/releases" - method: GET endpoint: "/api/aslan/delivery/releases/?*" - - action: get + - action: delivery_get alias: "交付物追踪|查看" description: "查看" rules: -- Gitee From 26eb86ff53727e9cb101194bee22ecbf937cb385 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 27 Apr 2022 16:19:18 +0800 Subject: [PATCH 2299/2647] add miss urls (#1419) Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 27 +++++++++++++++++++ .../core/project/handler/service_policy.yaml | 2 ++ .../aslan/core/service/handler/service.go | 11 ++++++++ .../core/workflow/testing/handler/policy.yaml | 2 ++ 4 files changed, 42 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index a3d0a002..682c2edf 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -130,8 +130,15 @@ rules: - method: POST endpoint: "/api/aslan/environment/environments" resourceType: "Cluster" + matchAttributes: + - key: "production" + value: "false" - method: POST endpoint: "/api/aslan/service/workloads" + resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/project/products/?*/services" - method: GET @@ -141,6 +148,17 @@ rules: alias: "配置" description: "" rules: + # ----托管场景下配置服务需要接口------ + - method: GET + endpoint: "/api/aslan/build/targets" + resourceType: "Environment" + - method: GET + endpoint: "/api/aslan/build/build" + resourceType: "Environment" + - method: GET + endpoint: "/api/aslan/service/services/?*" + resourceType: "Environment" + # ---------- - method: PUT endpoint: "/api/aslan/environment/environments/?*/services" resourceType: "Environment" @@ -165,6 +183,9 @@ rules: - method: PUT endpoint: "/api/aslan/environment/environments" resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/environment/environments/?*" resourceType: "Environment" @@ -191,6 +212,9 @@ rules: value: "false" - method: PUT endpoint: "/api/aslan/service/workloads" + matchAttributes: + - key: "production" + value: "false" resourceType: "Environment" - method: GET endpoint: "/api/aslan/project/products/?*/services" @@ -230,6 +254,9 @@ rules: - method: PUT endpoint: "/api/aslan/environment/environments" resourceType: "Environment" + matchAttributes: + - key: "production" + - value: "false" - method: POST endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" resourceType: "Environment" diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml index 5e2d3862..7df8fecd 100644 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -34,6 +34,8 @@ rules: endpoint: "/api/aslan/service/helm/?*" - method: PUT endpoint: "/api/aslan/service/services" + - method: POST + endpoint: "/api/aslan/service/services" - method: PUT endpoint: "/api/aslan/service/pm/?*" - method: PUT diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 73e9c0cb..9aeea81f 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -21,9 +21,11 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/http" "strconv" "github.com/gin-gonic/gin" + "k8s.io/apimachinery/pkg/util/sets" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" @@ -199,6 +201,15 @@ func UpdateWorkloads(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } + allowedEnvs, found := internalhandler.GetResourcesInHeader(c) + if found { + allowedSet := sets.NewString(allowedEnvs...) + if !allowedSet.Has(env) { + c.String(http.StatusForbidden, "not all input envs are allowed, allowed envs are %v", allowedEnvs) + return + } + } + ctx.Err = svcservice.UpdateWorkloads(c, ctx.RequestID, ctx.UserName, product, env, *args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml index a876ef3a..87622c4d 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml @@ -18,6 +18,8 @@ rules: endpoint: "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*" - method: GET endpoint: "/api/aslan/testing/workspace/workflow/?*/taskId/?*" + - method: GET + endpoint: "/api/aslan/cluster/clusters" - action: edit_test alias: "编辑" description: "" -- Gitee From 24e74414b388416f262d1f3a5c95dba4b78f0064 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 27 Apr 2022 16:59:50 +0800 Subject: [PATCH 2300/2647] add default login setting (#1417) * add default login Signed-off-by: panxunying * add log Signed-off-by: panxunying * add log Signed-off-by: panxunying * optimize Signed-off-by: panxunying * optimize Signed-off-by: panxunying Co-authored-by: panxunying --- .../core/common/repository/models/settings.go | 1 + .../common/repository/mongodb/settings.go | 11 ++++ .../aslan/core/system/handler/login.go | 44 ++++++++++++++++ .../aslan/core/system/handler/router.go | 7 +++ .../aslan/core/system/service/login.go | 51 +++++++++++++++++++ .../core/connector/service/connector.go | 17 +++++-- .../core/connector/service/types.go | 8 +-- .../user/core/handler/login/third_party.go | 20 +++++++- pkg/setting/consts.go | 2 + pkg/shared/client/aslan/login.go | 39 ++++++++++++++ 10 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 pkg/microservice/aslan/core/system/handler/login.go create mode 100644 pkg/microservice/aslan/core/system/service/login.go create mode 100644 pkg/shared/client/aslan/login.go diff --git a/pkg/microservice/aslan/core/common/repository/models/settings.go b/pkg/microservice/aslan/core/common/repository/models/settings.go index d51d4cdc..2a490c95 100644 --- a/pkg/microservice/aslan/core/common/repository/models/settings.go +++ b/pkg/microservice/aslan/core/common/repository/models/settings.go @@ -22,6 +22,7 @@ type SystemSetting struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` WorkflowConcurrency int64 `bson:"workflow_concurrency" json:"workflow_concurrency"` BuildConcurrency int64 `bson:"build_concurrency" json:"build_concurrency"` + DefaultLogin string `bson:"default_login" json:"default_login"` UpdateTime int64 `bson:"update_time" json:"update_time"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go index 21634eb8..8b849a89 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/settings.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/settings.go @@ -58,6 +58,16 @@ func (c *SystemSettingColl) Get() (*models.SystemSetting, error) { return resp, err } +func (c *SystemSettingColl) UpdateDefaultLoginSetting(defaultLogin string) error { + id, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) + change := bson.M{"$set": bson.M{ + "default_login": defaultLogin, + }} + query := bson.M{"_id": id} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *SystemSettingColl) UpdateConcurrencySetting(workflowConcurrency, buildConcurrency int64) error { id, _ := primitive.ObjectIDFromHex(setting.LocalClusterID) change := bson.M{"$set": bson.M{ @@ -76,6 +86,7 @@ func (c *SystemSettingColl) InitSystemSettings() error { return c.CreateOrUpdate(setting.LocalClusterID, &models.SystemSetting{ WorkflowConcurrency: 2, BuildConcurrency: 5, + DefaultLogin: setting.DefaultLoginLocal, }) } return nil diff --git a/pkg/microservice/aslan/core/system/handler/login.go b/pkg/microservice/aslan/core/system/handler/login.go new file mode 100644 index 00000000..4925e6ab --- /dev/null +++ b/pkg/microservice/aslan/core/system/handler/login.go @@ -0,0 +1,44 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func GetDefaultLogin(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetDefaultLogin(ctx.Logger) +} + +func UpdateDefaultLogin(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.UpdateDefaultLoginParams) + if err := c.BindJSON(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = service.UpdateDefaultLogin(args.DefaultLogin, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index b1c64954..e132722d 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -132,6 +132,13 @@ func (*Router) Inject(router *gin.RouterGroup) { concurrency.POST("/workflow", UpdateWorkflowConcurrency) } + // default login default login home page settings + login := router.Group("login") + { + login.GET("/default", GetDefaultLogin) + login.POST("/default", UpdateDefaultLogin) + } + // --------------------------------------------------------------------------------------- // 自定义镜像管理接口 // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/system/service/login.go b/pkg/microservice/aslan/core/system/service/login.go new file mode 100644 index 00000000..fd8de30e --- /dev/null +++ b/pkg/microservice/aslan/core/system/service/login.go @@ -0,0 +1,51 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "go.uber.org/zap" + + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" +) + +func GetDefaultLogin(logger *zap.SugaredLogger) (*GetDefaultLoginResponse, error) { + configuration, err := commonrepo.NewSystemSettingColl().Get() + if err != nil { + logger.Errorf("GetDefaultLogin error:%s", err) + return nil, err + } + defaultLogin := setting.DefaultLoginLocal + if len(configuration.DefaultLogin) > 0 { + defaultLogin = configuration.DefaultLogin + } + return &GetDefaultLoginResponse{ + DefaultLogin: defaultLogin, + }, nil +} + +type GetDefaultLoginResponse struct { + DefaultLogin string `json:"default_login"` +} + +type UpdateDefaultLoginParams struct { + DefaultLogin string `json:"default_login"` +} + +func UpdateDefaultLogin(defaultLogin string, _ *zap.SugaredLogger) error { + return commonrepo.NewSystemSettingColl().UpdateDefaultLoginSetting(defaultLogin) +} diff --git a/pkg/microservice/systemconfig/core/connector/service/connector.go b/pkg/microservice/systemconfig/core/connector/service/connector.go index f5e4fb9d..7c7fa8d5 100644 --- a/pkg/microservice/systemconfig/core/connector/service/connector.go +++ b/pkg/microservice/systemconfig/core/connector/service/connector.go @@ -67,7 +67,11 @@ func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connecto logger.Errorf("Failed to list connectors, err: %s", err) return nil, err } - + config, err := aslan.New(config.AslanServiceAddress()).GetDefaultLogin() + if err != nil { + logger.Errorf("Failed to list connectors, err: %s", err) + return nil, err + } var res []*Connector for _, c := range cs { cf := make(map[string]interface{}) @@ -90,13 +94,18 @@ func ListConnectors(encryptedKey string, logger *zap.SugaredLogger) ([]*Connecto return nil, err } } + isDefault := false + if config.DefaultLogin == c.ID { + isDefault = true + } res = append(res, &Connector{ ConnectorBase: ConnectorBase{ Type: ConnectorType(c.Type), }, - ID: c.ID, - Name: c.Name, - Config: cf, + ID: c.ID, + Name: c.Name, + Config: cf, + IsDefault: isDefault, }) } diff --git a/pkg/microservice/systemconfig/core/connector/service/types.go b/pkg/microservice/systemconfig/core/connector/service/types.go index 90c22df8..2696bc12 100644 --- a/pkg/microservice/systemconfig/core/connector/service/types.go +++ b/pkg/microservice/systemconfig/core/connector/service/types.go @@ -44,10 +44,10 @@ const ( type Connector struct { ConnectorBase - - ID string `json:"id"` - Name string `json:"name"` - Config interface{} `json:"config"` + IsDefault bool `json:"is_default"` + ID string `json:"id"` + Name string `json:"name"` + Config interface{} `json:"config"` } type ConnectorBase struct { diff --git a/pkg/microservice/user/core/handler/login/third_party.go b/pkg/microservice/user/core/handler/login/third_party.go index 40240bae..3f155b9d 100644 --- a/pkg/microservice/user/core/handler/login/third_party.go +++ b/pkg/microservice/user/core/handler/login/third_party.go @@ -34,6 +34,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/config" "github.com/koderover/zadig/pkg/microservice/user/core/service/login" "github.com/koderover/zadig/pkg/microservice/user/core/service/user" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/aslan" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" @@ -49,6 +51,9 @@ func provider() *oidc.Provider { } func Login(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + oauth2Config := &oauth2.Config{ ClientID: config.ClientID(), ClientSecret: config.ClientSecret(), @@ -56,8 +61,21 @@ func Login(c *gin.Context) { Scopes: config.Scopes(), RedirectURL: config.RedirectURI(), } + authCodeURL := oauth2Config.AuthCodeURL(config.AppState, oauth2.AccessTypeOffline) - authCodeURL = strings.Replace(authCodeURL, config.IssuerURL(), configbase.SystemAddress()+"/dex", -1) + systemConfig, err := aslan.New(configbase.AslanServiceAddress()).GetDefaultLogin() + if err != nil { + ctx.Err = err + return + } + defaultLogin := "" + replaceURL := configbase.SystemAddress() + "/dex/auth" + if systemConfig.DefaultLogin != setting.DefaultLoginLocal { + defaultLogin = systemConfig.DefaultLogin + replaceURL = replaceURL + "/" + defaultLogin + } + authCodeURL = strings.Replace(authCodeURL, config.IssuerURL()+"/auth", replaceURL, -1) + c.Redirect(http.StatusSeeOther, authCodeURL) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 5eda749d..b26fb9bd 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -614,6 +614,8 @@ const ( const LocalClusterID = "0123456789abcdef12345678" +const DefaultLoginLocal = "local" + const RequestModeOpenAPI = "openAPI" const DeployTimeout = 60 * 10 // 10 minutes diff --git a/pkg/shared/client/aslan/login.go b/pkg/shared/client/aslan/login.go new file mode 100644 index 00000000..7f2cc5ea --- /dev/null +++ b/pkg/shared/client/aslan/login.go @@ -0,0 +1,39 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aslan + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type GetDefaultLoginResp struct { + DefaultLogin string `json:"default_login"` +} + +func (c *Client) GetDefaultLogin() (*GetDefaultLoginResp, error) { + url := fmt.Sprintf("/system/login/default") + + res := &GetDefaultLoginResp{} + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, nil +} -- Gitee From eed916ddec0a7fb2578cbf60052895788d8611f0 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 27 Apr 2022 18:15:39 +0800 Subject: [PATCH 2301/2647] template user (#1433) Signed-off-by: mouuii --- pkg/microservice/aslan/core/project/handler/service_policy.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml index 7df8fecd..9b009e63 100644 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ b/pkg/microservice/aslan/core/project/handler/service_policy.yaml @@ -24,6 +24,8 @@ rules: endpoint: "/api/aslan/service/helm/?*/?*/serviceModule" - method: GET endpoint: "/api/aslan/service/services/?*/pm" + - method: GET + endpoint: "/api/aslan/template/yaml" - method: GET endpoint: "/api/aslan/service/helm/?*/?*/fileContent" - action: edit_service -- Gitee From 857a7fbeb3ca47b2a8690a4fe97aa78411763dcf Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 27 Apr 2022 18:37:03 +0800 Subject: [PATCH 2302/2647] build template (#1398) * add curd ability Signed-off-by: allenshen * add build template render logic Signed-off-by: allenshen * add debug Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * update service repo config Signed-off-by: allenshen * add target repos logic Signed-off-by: allenshen * update build repo read logic Signed-off-by: allenshen * add policy config Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * resolve comments Signed-off-by: allenshen --- .../aslan/core/build/service/build.go | 16 ++ .../core/common/repository/models/build.go | 37 ++++- .../repository/models/build_template.go | 47 ++++++ .../repository/mongodb/build_template.go | 143 ++++++++++++++++++ .../aslan/core/common/service/build.go | 32 +++- .../core/common/service/template/build.go | 97 ++++++++++++ .../core/common/service/template/chart.go | 2 +- .../common/service/template/dockerfile.go | 2 +- .../core/common/service/template/types.go | 2 +- .../core/common/service/template/yaml.go | 2 +- pkg/microservice/aslan/core/service.go | 1 + .../aslan/core/templatestore/handler/build.go | 85 +++++++++++ .../core/templatestore/handler/router.go | 9 ++ .../workflow/handler/system-policies.yaml | 10 ++ .../service/webhook/gitlab_workflow_task.go | 6 +- .../workflow/service/workflow/service_task.go | 6 +- .../workflow/service/workflow/workflow.go | 6 +- .../service/workflow/workflow_task.go | 76 +++++++--- .../service/taskplugin/artifact_deploy.go | 18 +-- .../core/service/taskplugin/build.go | 28 +--- .../core/service/taskplugin/build_v3.go | 28 +--- .../core/service/taskplugin/utils.go | 29 ++++ 22 files changed, 568 insertions(+), 114 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/build_template.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/build_template.go create mode 100644 pkg/microservice/aslan/core/common/service/template/build.go create mode 100644 pkg/microservice/aslan/core/templatestore/handler/build.go diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index d74ac8d5..c671597a 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -108,6 +108,21 @@ func ListBuild(name, targets, productName string, log *zap.SugaredLogger) ([]*Bu return resp, nil } +func fillBuildRepoData(build *commonmodels.Build) { + if build.TemplateID == "" { + return + } + build.Targets = make([]*commonmodels.ServiceModuleTarget, 0, len(build.TargetRepos)) + for _, target := range build.TargetRepos { + build.Targets = append(build.Targets, &commonmodels.ServiceModuleTarget{ + ProductName: target.Service.ProductName, + ServiceName: target.Service.ServiceName, + ServiceModule: target.Service.ServiceModule, + Repos: target.Repos, + }) + } +} + func CreateBuild(username string, build *commonmodels.Build, log *zap.SugaredLogger) error { if len(build.Name) == 0 { return e.ErrCreateBuildModule.AddDesc("empty name") @@ -319,6 +334,7 @@ func UpdateBuildTargets(name, productName string, targets []*commonmodels.Servic } func correctFields(build *commonmodels.Build) { + fillBuildRepoData(build) // make sure cache has no empty field caches := make([]string, 0) for _, cache := range build.Caches { diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 59c7aca5..8d3bcd5d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -24,15 +24,17 @@ import ( ) type Build struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Team string `bson:"team,omitempty" json:"team,omitempty"` - Source string `bson:"source,omitempty" json:"source,omitempty"` - Timeout int `bson:"timeout" json:"timeout"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + TemplateID string `bson:"template_id" json:"template_id"` + Timeout int `bson:"timeout" json:"timeout"` // 在任一编译配置模板中只能出现一次 // 对于k8s部署是传入容器名称 // 对于物理机部署是服务名称 Targets []*ServiceModuleTarget `bson:"targets" json:"targets"` + TargetRepos []*TargetRepo `bson:"-" json:"target_repos"` Description string `bson:"desc,omitempty" json:"desc"` UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` @@ -144,9 +146,21 @@ type ParamVal struct { } type ServiceModuleTarget struct { - ProductName string `bson:"product_name" json:"product_name"` - ServiceName string `bson:"service_name" json:"service_name"` - ServiceModule string `bson:"service_module" json:"service_module"` + ProductName string `bson:"product_name" json:"product_name"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Repos []*types.Repository `bson:"repos,omitempty" json:"repos,omitempty"` +} + +type ServiceModuleTargetBase struct { + ProductName string `json:"product_name"` + ServiceName string `json:"service_name"` + ServiceModule string `json:"service_module"` +} + +type TargetRepo struct { + Service *ServiceModuleTargetBase `json:"service"` + Repos []*types.Repository `json:"repos"` } type KeyVal struct { @@ -162,6 +176,13 @@ type Item struct { Version string `bson:"version" json:"version"` } +func (build *Build) SafeRepos() []*types.Repository { + if len(build.Repos) == 0 { + return []*types.Repository{} + } + return build.Repos +} + func (Build) TableName() string { return "module_build" } diff --git a/pkg/microservice/aslan/core/common/repository/models/build_template.go b/pkg/microservice/aslan/core/common/repository/models/build_template.go new file mode 100644 index 00000000..8dc3f1e6 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/build_template.go @@ -0,0 +1,47 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/types" +) + +type BuildTemplate struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Team string `bson:"team,omitempty" json:"team,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + Timeout int `bson:"timeout" json:"timeout"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` + JenkinsBuild *JenkinsBuild `bson:"jenkins_build,omitempty" json:"jenkins_build,omitempty"` + Scripts string `bson:"scripts" json:"scripts"` + PostBuild *PostBuild `bson:"post_build,omitempty" json:"post_build"` + SSHs []string `bson:"sshs" json:"sshs"` + PMDeployScripts string `bson:"pm_deploy_scripts" json:"pm_deploy_scripts"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` +} + +func (BuildTemplate) TableName() string { + return "build_template" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go b/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go new file mode 100644 index 00000000..312497ae --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go @@ -0,0 +1,143 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type BuildTemplateQueryOption struct { + ID string + Name string +} + +type BuildTemplateColl struct { + *mongo.Collection + coll string +} + +func NewBuildTemplateColl() *BuildTemplateColl { + name := models.BuildTemplate{}.TableName() + return &BuildTemplateColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *BuildTemplateColl) GetCollectionName() string { + return c.coll +} + +func (c *BuildTemplateColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + _, err := c.Indexes().CreateOne(ctx, mod) + return err +} + +func (c *BuildTemplateColl) Create(obj *models.BuildTemplate) error { + if obj == nil { + return fmt.Errorf("nil object") + } + _, err := c.InsertOne(context.TODO(), obj) + return err +} + +func (c *BuildTemplateColl) Update(idStr string, obj *models.BuildTemplate) error { + id, err := primitive.ObjectIDFromHex(idStr) + if err != nil { + return err + } + obj.ID = id + query := bson.M{"_id": id} + change := bson.M{"$set": obj} + _, err = c.UpdateOne(context.TODO(), query, change) + return err +} + +func (c *BuildTemplateColl) Find(opt *BuildTemplateQueryOption) (*models.BuildTemplate, error) { + if opt == nil { + return nil, errors.New("nil FindOption") + } + query := bson.M{} + if len(opt.ID) > 0 { + id, err := primitive.ObjectIDFromHex(opt.ID) + if err != nil { + return nil, err + } + query["_id"] = id + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + resp := new(models.BuildTemplate) + err := c.Collection.FindOne(context.TODO(), query).Decode(resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *BuildTemplateColl) List(pageNum, pageSize int) ([]*models.BuildTemplate, int, error) { + resp := make([]*models.BuildTemplate, 0) + query := bson.M{} + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + opt := options.Find(). + SetSkip(int64((pageNum - 1) * pageSize)). + SetLimit(int64(pageSize)) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, int(count), nil +} + +func (c *BuildTemplateColl) DeleteByName(name string) error { + query := bson.M{"name": name} + _, err := c.DeleteOne(context.TODO(), query) + return err +} + +func (c *BuildTemplateColl) DeleteByID(idStr string) error { + id, err := primitive.ObjectIDFromHex(idStr) + if err != nil { + return err + } + query := bson.M{"_id": id} + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/build.go b/pkg/microservice/aslan/core/common/service/build.go index e5a46bd2..968400fc 100644 --- a/pkg/microservice/aslan/core/common/service/build.go +++ b/pkg/microservice/aslan/core/common/service/build.go @@ -116,10 +116,7 @@ func EnsureResp(build *commonmodels.Build) { if len(build.Targets) == 0 { build.Targets = make([]*commonmodels.ServiceModuleTarget, 0) } - - if len(build.Repos) == 0 { - build.Repos = make([]*types.Repository, 0) - } + build.Repos = build.SafeRepos() if build.PreBuild != nil { if len(build.PreBuild.Installs) == 0 { @@ -141,4 +138,31 @@ func EnsureResp(build *commonmodels.Build) { build.PreBuild.Parameters = make([]*commonmodels.Parameter, 0) } } + + if build.TemplateID != "" { + build.TargetRepos = make([]*commonmodels.TargetRepo, 0, len(build.Targets)) + for _, target := range build.Targets { + targetRepo := &commonmodels.TargetRepo{ + Service: &commonmodels.ServiceModuleTargetBase{ + ProductName: target.ProductName, + ServiceName: target.ServiceName, + ServiceModule: target.ServiceModule, + }, + Repos: target.Repos, + } + build.TargetRepos = append(build.TargetRepos, targetRepo) + } + } +} + +func FindReposByTarget(projectName, serviceName, serviceModule string, build *commonmodels.Build) []*types.Repository { + if build.TemplateID == "" { + return build.SafeRepos() + } + for _, target := range build.Targets { + if target.ServiceName == serviceName && target.ProductName == projectName && target.ServiceModule == serviceModule { + return target.Repos + } + } + return build.SafeRepos() } diff --git a/pkg/microservice/aslan/core/common/service/template/build.go b/pkg/microservice/aslan/core/common/service/template/build.go new file mode 100644 index 00000000..1d29595d --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/template/build.go @@ -0,0 +1,97 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package template + +import ( + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +type BuildTemplateBrief struct { + Id string `json:"id"` + Name string `json:"name"` +} + +type BuildTemplateListResp struct { + BuildTemplates []*BuildTemplateBrief `json:"build_templates"` + Total int `json:"total,omitempty"` +} + +func AddBuildTemplate(userName string, build *commonmodels.BuildTemplate, logger *zap.SugaredLogger) error { + if len(build.Name) == 0 { + return e.ErrCreateBuildModule.AddDesc("empty name") + } + if err := commonutil.CheckDefineResourceParam(build.PreBuild.ResReq, build.PreBuild.ResReqSpec); err != nil { + return e.ErrCreateBuildModule.AddDesc(err.Error()) + } + build.UpdateBy = userName + if err := commonrepo.NewBuildTemplateColl().Create(build); err != nil { + log.Errorf("[Build.Upsert] %s error: %s", build.Name, err) + return e.ErrCreateBuildModule.AddErr(err) + } + return nil +} + +func GetBuildTemplateByName(name string) (*commonmodels.BuildTemplate, error) { + return mongodb.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{Name: name}) +} + +func GetBuildTemplateByID(idStr string) (*commonmodels.BuildTemplate, error) { + return mongodb.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ID: idStr}) +} + +func ListBuildTemplates(pageNum, pageSize int) (*BuildTemplateListResp, error) { + buildTemplates, count, err := mongodb.NewBuildTemplateColl().List(pageNum, pageSize) + if err != nil { + return nil, err + } + ret := &BuildTemplateListResp{ + Total: count, + } + for _, bt := range buildTemplates { + ret.BuildTemplates = append(ret.BuildTemplates, &BuildTemplateBrief{ + Id: bt.ID.Hex(), + Name: bt.Name, + }) + } + return ret, nil +} + +func RemoveBuildTemplate(name string, logger *zap.SugaredLogger) error { + err := mongodb.NewBuildTemplateColl().DeleteByID(name) + if err != nil { + logger.Errorf("Failed to delete build template %s, err: %s", name, err) + return err + } + return nil +} + +func UpdateBuildTemplate(id string, buildTemplate *commonmodels.BuildTemplate, logger *zap.SugaredLogger) error { + _, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: id, + }) + if err != nil { + return err + } + return commonrepo.NewBuildTemplateColl().Update(id, buildTemplate) +} diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/common/service/template/chart.go index 04cad498..dda6f6f2 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/common/service/template/chart.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package dockerfile +package template import ( "fmt" diff --git a/pkg/microservice/aslan/core/common/service/template/dockerfile.go b/pkg/microservice/aslan/core/common/service/template/dockerfile.go index 9f00b28c..a7414953 100644 --- a/pkg/microservice/aslan/core/common/service/template/dockerfile.go +++ b/pkg/microservice/aslan/core/common/service/template/dockerfile.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package dockerfile +package template import ( "errors" diff --git a/pkg/microservice/aslan/core/common/service/template/types.go b/pkg/microservice/aslan/core/common/service/template/types.go index 285f698b..561c36e2 100644 --- a/pkg/microservice/aslan/core/common/service/template/types.go +++ b/pkg/microservice/aslan/core/common/service/template/types.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package dockerfile +package template import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" diff --git a/pkg/microservice/aslan/core/common/service/template/yaml.go b/pkg/microservice/aslan/core/common/service/template/yaml.go index 455ccec3..006496b8 100644 --- a/pkg/microservice/aslan/core/common/service/template/yaml.go +++ b/pkg/microservice/aslan/core/common/service/template/yaml.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package dockerfile +package template import ( "errors" diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index eaf7bc10..e58c8944 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -261,6 +261,7 @@ func initDatabase() { commonrepo.NewSecretColl(), commonrepo.NewPvcColl(), commonrepo.NewEnvSvcDependColl(), + commonrepo.NewBuildTemplateColl(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), diff --git a/pkg/microservice/aslan/core/templatestore/handler/build.go b/pkg/microservice/aslan/core/templatestore/handler/build.go new file mode 100644 index 00000000..085c9f52 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/handler/build.go @@ -0,0 +1,85 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "fmt" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +func GetBuildTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = templateservice.GetBuildTemplateByID(c.Param("id")) +} + +func ListBuildTemplates(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := &listYamlQuery{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + ctx.Resp, ctx.Err = templateservice.ListBuildTemplates(args.PageNum, args.PageSize) +} + +func AddBuildTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.BuildTemplate) + err := c.BindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid Build args") + return + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "模板-构建", args.Name, fmt.Sprintf("%+v", args), ctx.Logger) + ctx.Err = templateservice.AddBuildTemplate(ctx.UserName, args, ctx.Logger) +} + +func UpdateBuildTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.BuildTemplate) + err := c.BindJSON(args) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid Build args") + return + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "编辑", "模板-构建", args.Name, fmt.Sprintf("%+v", args), ctx.Logger) + + ctx.Err = templateservice.UpdateBuildTemplate(c.Param("id"), args, ctx.Logger) +} + +func RemoveBuildTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "模板-构建", c.Param("id"), "", ctx.Logger) + ctx.Err = templateservice.RemoveBuildTemplate(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index f00fd506..0b632774 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -59,4 +59,13 @@ func (*Router) Inject(router *gin.RouterGroup) { yaml.GET("/:id/reference", GetYamlTemplateReference) yaml.POST("/getVariables", GetYamlTemplateVariables) } + + build := router.Group("build") + { + build.POST("", AddBuildTemplate) + build.PUT("/:id", UpdateBuildTemplate) + build.GET("", ListBuildTemplates) + build.GET("/:id", GetBuildTemplate) + build.DELETE("/:id", RemoveBuildTemplate) + } } diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index 5366c456..e13e234d 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -111,6 +111,8 @@ endpoint: "/api/aslan/template/charts" - method: POST endpoint: "/api/aslan/template/yaml" + - method: POST + endpoint: "/api/aslan/template/build" - action: get_template alias: "查看" description: "查看" @@ -123,6 +125,10 @@ endpoint: "/api/aslan/template/charts/?*" - method: GET endpoint: "/api/aslan/template/dockerfile" + - method: GET + endpoint: "/api/aslan/template/build" + - method: GET + endpoint: "/api/aslan/template/build/?*" - action: edit_template alias: "编辑" description: "编辑" @@ -136,6 +142,8 @@ endpoint: "api/aslan/template/charts/?*/variables" - method: PUT endpoint: "/api/aslan/template/dockerfile/?*" + - method: PUT + endpoint: "/api/aslan/template/build/?*" - action: delete_template alias: "删除" description: "删除" @@ -146,6 +154,8 @@ endpoint: "/api/aslan/template/charts/?*" - method: DELETE endpoint: "/api/aslan/template/dockerfile/?*" + - method: DELETE + endpoint: "/api/aslan/template/build/?*" - resource: DeliveryCenter alias: "交付中心" description: "" diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 3aa1aaf2..3ebd85e0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -469,13 +469,15 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor } return fmt.Errorf("[Build.Find] serviceName: %s productName:%s serviceModule:%s error: %s", svr.Name, workflow.ProductTmplName, svr.ServiceModule, err) } - for _, repo := range resp.Repos { + + repos := commonservice.FindReposByTarget(targetElem.ProductName, targetElem.ServiceName, targetElem.Name, resp) + for _, repo := range repos { if repo.RepoName == item.MainRepo.RepoName && repo.RepoOwner == item.MainRepo.RepoOwner { repo.Branch = branref repo.PR = prId } } - targetElem.Build = &commonmodels.BuildArgs{Repos: resp.Repos} + targetElem.Build = &commonmodels.BuildArgs{Repos: repos} targetElem.Deploy = make([]commonmodels.DeployEnv, 0) if deployed { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go index baffa417..87384df1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/service_task.go @@ -156,7 +156,7 @@ func ListServiceWorkflows(productName, envName, serviceName, serviceType string, ServiceModule: container.Name, } serviceModuleTarget := fmt.Sprintf("%s%s%s%s%s", service.ProductName, SplitSymbol, service.ServiceName, SplitSymbol, container.Name) - moBuild := findModuleByTargetAndVersion(allModules, serviceModuleTarget) + moBuild, _ := findModuleByTargetAndVersion(allModules, serviceModuleTarget) if moBuild == nil { continue } @@ -169,7 +169,7 @@ func ListServiceWorkflows(productName, envName, serviceName, serviceType string, ServiceModule: service.ServiceName, } serviceModuleTarget := fmt.Sprintf("%s%s%s%s%s", service.ProductName, SplitSymbol, service.ServiceName, SplitSymbol, service.ServiceName) - moBuild := findModuleByTargetAndVersion(allModules, serviceModuleTarget) + moBuild, _ := findModuleByTargetAndVersion(allModules, serviceModuleTarget) if moBuild != nil { resp.Targets = append(resp.Targets, target) } @@ -303,6 +303,6 @@ func serviceTaskArgsToTaskArgs(serviceTaskArgs *commonmodels.ServiceTaskArgs) *c resp.Builds = make([]*types.Repository, 0) return resp } - resp.Builds = buildObj.Repos + resp.Builds = commonservice.FindReposByTarget(serviceTaskArgs.ProductName, serviceTaskArgs.ServiceName, serviceTaskArgs.ServiceName, buildObj) return resp } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 43807519..1521b0d5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -402,10 +402,10 @@ func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, for _, moTarget := range mo.Targets { moduleTargetStr := fmt.Sprintf("%s%s%s%s%s", moTarget.ProductName, SplitSymbol, moTarget.ServiceName, SplitSymbol, moTarget.ServiceModule) if moduleTargetStr == k { - if len(mo.Repos) == 0 { - preSet.Repos = make([]*types.Repository, 0) + if mo.TemplateID != "" { + preSet.Repos = moTarget.Repos } else { - preSet.Repos = mo.Repos + preSet.Repos = mo.SafeRepos() } } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 8984916d..71989371 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -92,17 +92,13 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr } target := &commonmodels.TargetArgs{Name: containerArr[2], ServiceName: containerArr[1], Deploy: targetMap[container], Build: &commonmodels.BuildArgs{}, HasBuild: true} - moBuild := findModuleByTargetAndVersion(allModules, container) + moBuild, _ := findModuleByTargetAndVersion(allModules, container) if moBuild == nil { moBuild = &commonmodels.Build{} target.HasBuild = false } - if len(moBuild.Repos) == 0 { - target.Build.Repos = make([]*types.Repository, 0) - } else { - target.Build.Repos = moBuild.Repos - } + target.Build.Repos = moBuild.SafeRepos() if moBuild.PreBuild != nil { EnsureBuildResp(moBuild) @@ -253,10 +249,10 @@ func getProjectTargets(productName string) []string { return targets } -func findModuleByTargetAndVersion(allModules []*commonmodels.Build, serviceModuleTarget string) *commonmodels.Build { +func findModuleByTargetAndVersion(allModules []*commonmodels.Build, serviceModuleTarget string) (*commonmodels.Build, *commonmodels.ServiceModuleTarget) { containerArr := strings.Split(serviceModuleTarget, SplitSymbol) if len(containerArr) != 3 { - return nil + return nil, nil } opt := &commonrepo.ServiceFindOption{ @@ -272,11 +268,11 @@ func findModuleByTargetAndVersion(allModules []*commonmodels.Build, serviceModul for _, target := range mo.Targets { targetStr := fmt.Sprintf("%s%s%s%s%s", target.ProductName, SplitSymbol, target.ServiceName, SplitSymbol, target.ServiceModule) if targetStr == strings.Join(containerArr, SplitSymbol) { - return mo + return mo, target } } } - return nil + return nil, nil } func EnsureBuildResp(mb *commonmodels.Build) { @@ -284,9 +280,7 @@ func EnsureBuildResp(mb *commonmodels.Build) { mb.Targets = make([]*commonmodels.ServiceModuleTarget, 0) } - if len(mb.Repos) == 0 { - mb.Repos = make([]*types.Repository, 0) - } + mb.Repos = mb.SafeRepos() if mb.PreBuild != nil { if len(mb.PreBuild.Installs) == 0 { @@ -385,16 +379,16 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) Build: &commonmodels.BuildArgs{}, HasBuild: true, } - moBuild := findModuleByTargetAndVersion(allModules, container) + moBuild, targetInfo := findModuleByTargetAndVersion(allModules, container) if moBuild == nil { moBuild = &commonmodels.Build{} target.HasBuild = false } - if len(moBuild.Repos) == 0 { - target.Build.Repos = make([]*types.Repository, 0) + if moBuild.TemplateID != "" { + target.Build.Repos = targetInfo.Repos } else { - target.Build.Repos = moBuild.Repos + target.Build.Repos = moBuild.SafeRepos() } if moBuild.PreBuild != nil { @@ -909,6 +903,7 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, } // If the service component owned by the build contains any service component of the service, the match is considered successful match := false + repos := build.Repos for _, container := range serviceTmpl.Containers { serviceModuleTarget := &commonmodels.ServiceModuleTarget{ ProductName: serviceTmpl.ProductName, @@ -920,6 +915,7 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, target.Name = container.Name target.ServiceName = serviceTmpl.ServiceName match = true + repos = buildTarget.Repos break } } @@ -931,7 +927,8 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, if target.Build == nil { continue } - for _, buildRepo := range build.Repos { + + for _, buildRepo := range repos { for _, targetRepo := range target.Build.Repos { if targetRepo.RepoName == buildRepo.RepoName { // openAPI only pass repoName, branch, pr @@ -1942,6 +1939,40 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator return resp, nil } +// fillBuildDetail fill the contents for builds created from build templates +func fillBuildDetail(moduleBuild *commonmodels.Build, serviceName, serviceModule string) error { + if moduleBuild.TemplateID == "" { + return nil + } + buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: moduleBuild.TemplateID, + }) + if err != nil { + return fmt.Errorf("failed to find build template with id: %s, err: %s", moduleBuild.TemplateID, err) + } + + moduleBuild.Timeout = buildTemplate.Timeout + moduleBuild.PreBuild = buildTemplate.PreBuild + moduleBuild.JenkinsBuild = buildTemplate.JenkinsBuild + moduleBuild.Scripts = buildTemplate.Scripts + moduleBuild.PostBuild = buildTemplate.PostBuild + moduleBuild.SSHs = buildTemplate.SSHs + moduleBuild.PMDeployScripts = buildTemplate.PMDeployScripts + moduleBuild.CacheEnable = buildTemplate.CacheEnable + moduleBuild.CacheDirType = buildTemplate.CacheDirType + moduleBuild.CacheUserDir = buildTemplate.CacheUserDir + moduleBuild.AdvancedSettingsModified = buildTemplate.AdvancedSettingsModified + + // repos are configured by service modules + for _, serviceConfig := range moduleBuild.Targets { + if serviceConfig.ServiceName == serviceName && serviceConfig.ServiceModule == serviceModule { + moduleBuild.Repos = serviceConfig.Repos + break + } + } + return nil +} + func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredLogger) ([]map[string]interface{}, error) { var ( subTasks = make([]map[string]interface{}, 0) @@ -1982,6 +2013,10 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } for _, module := range modules { + err = fillBuildDetail(module, args.ServiceName, args.Target) + if err != nil { + return nil, e.ErrConvertSubTasks.AddErr(err) + } build := &taskmodels.Build{ TaskType: config.TaskBuild, Enabled: true, @@ -2077,7 +2112,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.InstallItems = make([]*commonmodels.Item, 0) } - build.JobCtx.Builds = module.Repos + build.JobCtx.Builds = module.SafeRepos() for _, repo := range build.JobCtx.Builds { repoInfo, err := systemconfig.New().GetCodeHost(repo.CodehostID) if err != nil { @@ -2086,9 +2121,6 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL } repo.EnableProxy = repoInfo.EnableProxy } - if len(build.JobCtx.Builds) == 0 { - build.JobCtx.Builds = make([]*types.Repository, 0) - } build.JobCtx.BuildSteps = []*taskmodels.BuildStep{} if module.Scripts != "" { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index 400d27df..15f20476 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -171,23 +171,9 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T } p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace - for _, repo := range p.Task.JobCtx.Builds { - repoName := strings.Replace(repo.RepoName, "-", "_", -1) - if len(repo.Branch) > 0 { - branchVar := &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, branchVar) - } - - if len(repo.Tag) > 0 { - tagVar := &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, tagVar) - } - if repo.PR > 0 { - prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) - } - } + //instantiates variables like ${_BRANCH} ${${REPO_index}_BRANCH} .. + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, InstantiateBuildSysVariables(&p.Task.JobCtx)...) jobCtx := JobCtxBuilder{ JobName: p.JobName, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index b7aa1b4a..e486a42e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -229,32 +229,8 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, artifactKeysVar) } - for _, repo := range p.Task.JobCtx.Builds { - repoName := strings.Replace(repo.RepoName, "-", "_", -1) - if len(repo.Branch) > 0 { - branchVar := &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, branchVar) - } - - if len(repo.Tag) > 0 { - tagVar := &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, tagVar) - } - - if repo.PR > 0 { - prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) - } - - if len(repo.CommitID) > 0 { - commitVar := &task.KeyVal{ - Key: fmt.Sprintf("%s_COMMIT_ID", repoName), - Value: repo.CommitID, - IsCredential: false, - } - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, commitVar) - } - } + //instantiates variables like ${_BRANCH} ${${REPO_index}_BRANCH} .. + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, InstantiateBuildSysVariables(&p.Task.JobCtx)...) // Note: Currently, `SERVICE` in the environment variable represents a service module. // Since variable rendering is required next, the `SERVICE_MODULE` environment variable is added to accurately diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go index 884dbc9a..94cd049a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -21,7 +21,6 @@ import ( "fmt" "math/rand" "strconv" - "strings" "time" "go.uber.org/zap" @@ -115,32 +114,9 @@ func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pi p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, taskIDVar) p.KubeNamespace = pipelineTask.ConfigPayload.Build.KubeNamespace - for _, repo := range p.Task.JobCtx.Builds { - repoName := strings.Replace(repo.RepoName, "-", "_", -1) - if len(repo.Branch) > 0 { - branchVar := &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, branchVar) - } - - if len(repo.Tag) > 0 { - tagVar := &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, tagVar) - } - - if repo.PR > 0 { - prVar := &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false} - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, prVar) - } - if len(repo.CommitID) > 0 { - commitVar := &task.KeyVal{ - Key: fmt.Sprintf("%s_COMMIT_ID", repoName), - Value: repo.CommitID, - IsCredential: false, - } - p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, commitVar) - } - } + //instantiates variables like ${_BRANCH} ${${REPO_index}_BRANCH} ... + p.Task.JobCtx.EnvVars = append(p.Task.JobCtx.EnvVars, InstantiateBuildSysVariables(&p.Task.JobCtx)...) jobCtx := JobCtxBuilder{ JobName: p.JobName, diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 4630574a..093a1091 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "math/rand" + "strconv" "strings" "time" @@ -242,3 +243,31 @@ func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes return controllerRuntimeClient, clientset, restConfig, nil } + +// InstantiateBuildSysVariables instantiate system variables for build module +func InstantiateBuildSysVariables(jobCtx *task.JobCtx) []*task.KeyVal { + ret := make([]*task.KeyVal, 0) + for index, repo := range jobCtx.Builds { + repoName := strings.Replace(repo.RepoName, "-", "_", -1) + + repoIndex := fmt.Sprintf("REPO_%d", index) + ret = append(ret, &task.KeyVal{Key: fmt.Sprintf(repoIndex), Value: repoName, IsCredential: false}) + + if len(repo.Branch) > 0 { + ret = append(ret, &task.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false}) + } + + if len(repo.Tag) > 0 { + ret = append(ret, &task.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false}) + } + + if repo.PR > 0 { + ret = append(ret, &task.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false}) + } + + if len(repo.CommitID) > 0 { + ret = append(ret, &task.KeyVal{Key: fmt.Sprintf("%s_COMMIT_ID", repoName), Value: repo.CommitID, IsCredential: false}) + } + } + return ret +} -- Gitee From ea46887dc788463274a7c501f8c4165069b2fc50 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 27 Apr 2022 20:05:26 +0800 Subject: [PATCH 2303/2647] update config (#1437) Signed-off-by: allenshen --- .../aslan/core/workflow/handler/system-policies.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index e13e234d..ca90bbef 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -142,7 +142,7 @@ endpoint: "api/aslan/template/charts/?*/variables" - method: PUT endpoint: "/api/aslan/template/dockerfile/?*" - - method: PUT + - method: PUT endpoint: "/api/aslan/template/build/?*" - action: delete_template alias: "删除" -- Gitee From ea614e31a324166b761a8c05665ba15fc71c982b Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 27 Apr 2022 20:47:18 +0800 Subject: [PATCH 2304/2647] Hotfix/yaml format (#1438) * update config Signed-off-by: allenshen * update config Signed-off-by: allenshen --- .../aslan/core/workflow/handler/system-policies.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index ca90bbef..63595e28 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -155,7 +155,7 @@ - method: DELETE endpoint: "/api/aslan/template/dockerfile/?*" - method: DELETE - endpoint: "/api/aslan/template/build/?*" + endpoint: "/api/aslan/template/build/?*" - resource: DeliveryCenter alias: "交付中心" description: "" -- Gitee From 49019d6d0fe2f0fd515d1fd5b118e27dba5e7db6 Mon Sep 17 00:00:00 2001 From: flyer Date: Thu, 28 Apr 2022 11:28:52 +0800 Subject: [PATCH 2305/2647] support env sharing in helm project (#1425) * support env sharing in helm project Signed-off-by: zhangyifei * refine comments Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- .../aslan/core/environment/handler/product.go | 18 +- .../core/environment/service/environment.go | 80 +++++++- .../service/environment_creator.go | 12 +- .../environment/service/environment_update.go | 24 ++- .../aslan/core/environment/service/image.go | 3 +- .../aslan/core/environment/service/product.go | 20 ++ .../core/environment/service/revision.go | 30 ++- .../core/environment/service/share_env.go | 191 +++++++++++++----- .../environment/service/share_env_utils.go | 72 ++++++- .../aslan/core/project/handler/product.go | 17 +- pkg/tool/helmclient/helmclient.go | 13 +- pkg/tool/kube/util/utils.go | 67 ++++++ 12 files changed, 453 insertions(+), 94 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/product.go b/pkg/microservice/aslan/core/environment/handler/product.go index 3878369a..e8b187d0 100644 --- a/pkg/microservice/aslan/core/environment/handler/product.go +++ b/pkg/microservice/aslan/core/environment/handler/product.go @@ -17,13 +17,13 @@ limitations under the License. package handler import ( + "fmt" "strconv" "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" - "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" ) @@ -44,15 +44,19 @@ func GetInitProduct(c *gin.Context) { isBaseEnvStr := c.Query("isBaseEnv") baseEnvName := c.Query("baseEnv") - // Note: Compatible with previous behavior. - isBaseEnv, err := strconv.ParseBool(isBaseEnvStr) - if err != nil { - log.Warnf("Failed to parse %s to bool: %s.", isBaseEnvStr, err) - } - if envType == "" { envType = types.GeneralEnv } + var isBaseEnv bool + var err error + if envType == types.ShareEnv { + isBaseEnv, err = strconv.ParseBool(isBaseEnvStr) + if err != nil { + ctx.Err = fmt.Errorf("failed to parse %s to bool: %s", isBaseEnvStr, err) + return + } + } + ctx.Resp, ctx.Err = service.GetInitProduct(productTemplateName, envType, isBaseEnv, baseEnvName, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 68672f3c..316da6a8 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -32,6 +32,7 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" + "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/strvals" versionedclient "istio.io/client-go/pkg/clientset/versioned" @@ -177,6 +178,9 @@ type CreateHelmProductArg struct { BaseName string `json:"base_name,omitempty"` IsExisted bool `json:"is_existed"` EnvConfigYamls []string `json:"env_config_yamls,omitempty"` + + // New Since v1.12.0 + ShareEnv commonmodels.ProductShareEnv `json:"share_env"` } type UpdateMultiHelmProductArg struct { @@ -1001,6 +1005,7 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, RegistryID: registryID, IsExisted: arg.IsExisted, EnvConfigYamls: arg.EnvConfigYamls, + ShareEnv: arg.ShareEnv, } // fill services and chart infos of product @@ -1968,8 +1973,15 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su log.Infof("[%s] delete product %s", username, productInfo.Namespace) commonservice.LogProductStats(username, setting.DeleteProductEvent, productName, requestID, eventStart, log) + ctx := context.TODO() switch productInfo.Source { case setting.SourceFromHelm: + // Handles environment sharing related operations. + err = EnsureDeleteShareEnvConfig(ctx, productInfo, istioClient) + if err != nil { + log.Errorf("Failed to delete share env config for env %s of product %s: %s", productInfo.EnvName, productInfo.ProductName, err) + } + err = commonrepo.NewProductColl().Delete(envName, productName) if err != nil { log.Errorf("Product.Delete error: %v", err) @@ -1992,7 +2004,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { for _, services := range productInfo.Services { for _, service := range services { - if err = UninstallServiceByName(hc, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, service.ServiceName, service.Revision, true); err != nil { + if err = UninstallServiceByName(hc, service.ServiceName, productInfo, service.Revision, true); err != nil { log.Errorf("UninstallRelease err:%v", err) } } @@ -2093,7 +2105,7 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su } // Handles environment sharing related operations. - err = EnsureDeleteShareEnvConfig(context.TODO(), productInfo, istioClient) + err = EnsureDeleteShareEnvConfig(ctx, productInfo, istioClient) if err != nil { log.Errorf("Failed to delete share env config: %s", err) err = e.ErrDeleteProduct.AddDesc(e.DeleteVirtualServiceErrMsg + ": " + err.Error()) @@ -2126,11 +2138,10 @@ func DeleteProductServices(userName, requestID, envName, productName string, ser return deleteHelmProductServices(userName, requestID, productInfo, serviceNames, log) } return deleteK8sProductServices(productInfo, serviceNames, log) - } func deleteHelmProductServices(userName, requestID string, productInfo *commonmodels.Product, serviceNames []string, log *zap.SugaredLogger) error { - restConfig, err := kube.GetRESTConfig(productInfo.ClusterID) + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), productInfo.ClusterID) if err != nil { return e.ErrUpdateEnv.AddErr(err) } @@ -2139,6 +2150,17 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo return e.ErrUpdateEnv.AddErr(err) } + ctx := context.TODO() + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), productInfo.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + deleteServiceSet := sets.NewString(serviceNames...) deletedSvcRevision := make(map[string]int64) @@ -2198,7 +2220,7 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo return } log.Infof("uninstall release for service: %s", serviceName) - if errUninstall := UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, templateSvc, false); errUninstall != nil { + if errUninstall := UninstallService(helmClient, productInfo, templateSvc, false); errUninstall != nil { errStr := fmt.Sprintf("helm uninstall service %s err: %s", serviceName, errUninstall) failedServices.Store(serviceName, errStr) log.Error(errStr) @@ -2216,6 +2238,13 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo title := fmt.Sprintf("[%s] 的 [%s] 环境服务删除失败", productInfo.ProductName, productInfo.EnvName) commonservice.SendErrorMessage(userName, title, requestID, errors.New(strings.Join(errList, "\n")), log) } + + if productInfo.ShareEnv.Enable && !productInfo.ShareEnv.IsBase { + err = EnsureGrayEnvConfig(ctx, productInfo, kclient, istioClient) + if err != nil { + log.Errorf("Failed to ensure gray env config: %s", err) + } + } }() return nil @@ -2294,7 +2323,7 @@ func deleteK8sProductServices(productInfo *commonmodels.Product, serviceNames [] } if productInfo.ShareEnv.Enable && !productInfo.ShareEnv.IsBase { - err = ensureGrayEnvConfig(ctx, productInfo, kclient, istioClient) + err = EnsureGrayEnvConfig(ctx, productInfo, kclient, istioClient) if err != nil { log.Errorf("Failed to ensure gray env config: %s", err) return fmt.Errorf("failed to ensure gray env config: %s", err) @@ -2419,7 +2448,7 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e } // Note: Currently, only sub-environments can be created, but baseline environments cannot be created. - err = ensureGrayEnvConfig(context.TODO(), args, kubeClient, istioClient) + err = EnsureGrayEnvConfig(context.TODO(), args, kubeClient, istioClient) if err != nil { args.Status = setting.ProductStatusFailed log.Errorf("Failed to ensure environment sharing in env %s of product %s: %s", args.EnvName, args.ProductName, err) @@ -3064,7 +3093,7 @@ func buildInstallParam(namespace, envName, defaultValues string, renderChart *te return ret, nil } -func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry bool, helmClient helmclient.Client) error { +func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry bool, helmClient *helmtool.HelmClient) error { namespace, valuesYaml, renderChart, serviceObj := param.Namespace, param.MergedValues, param.RenderChart, param.serviceObj base := config.LocalServicePathWithRevision(serviceObj.ProductName, serviceObj.ServiceName, serviceObj.Revision) if err := commonservice.PreloadServiceManifestsByRevision(base, serviceObj); err != nil { @@ -3099,16 +3128,37 @@ func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry boo chartSpec.Replace = true } - if _, err = helmClient.InstallOrUpgradeChart(context.TODO(), chartSpec); err != nil { + // If the target environment is a shared environment and a sub env, we need to clear the deployed K8s Service. + ctx := context.TODO() + err = EnsureDeletePreCreatedServices(ctx, param.ProductName, param.Namespace, chartSpec, helmClient) + if err != nil { + return fmt.Errorf("failed to ensure deleting pre-created K8s Services for product %q in namespace %q: %s", param.ProductName, param.Namespace, err) + } + + helmClient, err = helmClient.Clone() + if err != nil { + return fmt.Errorf("failed to clone helm client: %s", err) + } + + var release *release.Release + release, err = helmClient.InstallOrUpgradeChart(ctx, chartSpec) + if err != nil { err = errors.WithMessagef( err, "failed to Install helm chart %s/%s", namespace, serviceObj.ServiceName) + } else { + err = EnsureZadigServiceByManifest(ctx, param.ProductName, param.Namespace, release.Manifest) + if err != nil { + err = errors.WithMessagef(err, "failed to ensure Zadig Service %s", err) + } } + return err } -func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient helmclient.Client, log *zap.SugaredLogger) { +func installProductHelmCharts(user, envName, requestID string, args *commonmodels.Product, renderset *commonmodels.RenderSet, eventStart int64, helmClient *helmtool.HelmClient, + kclient client.Client, istioClient versionedclient.Interface, log *zap.SugaredLogger) { var ( err error errList = &multierror.Error{} @@ -3185,6 +3235,14 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel } } + // Note: For the sub env, try to supplement information relevant to the base env. + if args.ShareEnv.Enable && !args.ShareEnv.IsBase { + shareEnvErr := EnsureGrayEnvConfig(context.TODO(), args, kclient, istioClient) + if shareEnvErr != nil { + errList = multierror.Append(errList, shareEnvErr) + } + } + err = errList.ErrorOrNil() } @@ -3298,7 +3356,7 @@ func updateProductGroup(username, productName, envName string, productResp *comm // uninstall services for serviceName, serviceRevision := range deletedSvcRevision { - if err = UninstallServiceByName(helmClient, productResp.ProductName, productResp.Namespace, productResp.EnvName, serviceName, serviceRevision, true); err != nil { + if err = UninstallServiceByName(helmClient, serviceName, productResp, serviceRevision, true); err != nil { log.Errorf("UninstallRelease err:%v", err) return e.ErrUpdateEnv.AddErr(err) } diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index bf68f388..b503fd49 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -147,6 +147,16 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + //判断namespace是否存在 namespace := args.GetNamespace() if args.Namespace == "" { @@ -236,7 +246,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P log.Errorf("helmInitEnvConfigSet [%s][P:%s] Product.UpdateErrors error: %s", args.EnvName, args.ProductName, err) } } - go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, log) + go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, kubeClient, istioClient, log) return nil } diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 38eed87e..eb1ca037 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "fmt" "time" @@ -33,29 +34,36 @@ import ( "go.uber.org/zap" ) -func UninstallServiceByName(helmClient helmclient.Client, productName, namespace, envName, serviceName string, revision int64, force bool) error { +func UninstallServiceByName(helmClient helmclient.Client, serviceName string, env *commonmodels.Product, revision int64, force bool) error { revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: serviceName, Revision: revision, - ProductName: productName, + ProductName: env.ProductName, }) if err != nil { return fmt.Errorf("failed to find service: %s with revision: %d, err: %s", serviceName, revision, err) } - return UninstallService(helmClient, productName, namespace, envName, revisionSvc, force) + return UninstallService(helmClient, env, revisionSvc, force) } -func UninstallService(helmClient helmclient.Client, productName, namespace, envName string, revisionSvc *commonmodels.Service, force bool) error { +func UninstallService(helmClient helmclient.Client, env *commonmodels.Product, revisionSvc *commonmodels.Service, force bool) error { + releaseName := util.GeneReleaseName(revisionSvc.GetReleaseNaming(), env.ProductName, env.Namespace, env.EnvName, revisionSvc.ServiceName) + + err := EnsureDeleteZadigServiceByHelmRelease(context.TODO(), env, releaseName, helmClient) + if err != nil { + return fmt.Errorf("failed to ensure delete Zadig Service by helm release: %s", err) + } + return helmClient.UninstallRelease(&helmclient.ChartSpec{ - ReleaseName: util.GeneReleaseName(revisionSvc.GetReleaseNaming(), productName, namespace, envName, revisionSvc.ServiceName), - Namespace: namespace, + ReleaseName: releaseName, + Namespace: env.Namespace, Wait: true, Force: force, Timeout: time.Second * 10, }) } -func InstallService(helmClient helmclient.Client, param *ReleaseInstallParam) error { +func InstallService(helmClient *helmtool.HelmClient, param *ReleaseInstallParam) error { handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) if errInstall != nil { @@ -112,7 +120,7 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string return nil } - if err := UninstallService(helmClient, productInfo.ProductName, productInfo.Namespace, productInfo.EnvName, prodSvcTemp, true); err != nil { + if err := UninstallService(helmClient, productInfo, prodSvcTemp, true); err != nil { return fmt.Errorf("helm uninstall service: %s in namespace: %s, err: %s", serviceName, productInfo.Namespace, err) } diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index fd4c3a61..a67fd515 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -20,7 +20,6 @@ import ( "fmt" "time" - helmclient "github.com/mittwald/go-helm-client" "go.uber.org/zap" "sigs.k8s.io/controller-runtime/pkg/client" @@ -127,7 +126,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri replacedValuesYaml string mergedValuesYaml string replacedMergedValuesYaml string - helmClient helmclient.Client + helmClient *helmtool.HelmClient namespace string = product.Namespace ) diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index e213875d..7f5ef6b4 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -127,6 +127,26 @@ func GetInitProduct(productTmplName string, envType types.EnvType, isBaseEnv boo if err != nil { return nil, fmt.Errorf("failed to get service list from env %s of product %s: %s", baseEnvName, productTmplName, err) } + + // Note: In the Helm scenario, filter `chart_infos` which is used by front-end. + if prodTmpl.ProductFeature != nil && prodTmpl.ProductFeature.DeployType == setting.HelmDeployType { + chartInfos := []*templatemodels.RenderChart{} + for _, chartInfo := range ret.ChartInfos { + found := false + for _, svcGroupName := range svcGroupNames { + if util.InStringArray(chartInfo.ServiceName, svcGroupName) { + found = true + break + } + } + + if found { + chartInfos = append(chartInfos, chartInfo) + } + } + + ret.ChartInfos = chartInfos + } } allServiceInfoMap := prodTmpl.AllServiceInfoMap() diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 409a5855..04dc1823 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "fmt" "go.uber.org/zap" @@ -183,15 +184,40 @@ func compareGroupServicesRev(servicesTmpl [][]string, productInfo *commonmodels. var serviceRev []*SvcRevision svcList := make([]*commonmodels.ProductService, 0) svcTmplNameList := make([]string, 0) - // 拍平服务组 + for _, services := range productInfo.Services { svcList = append(svcList, services...) } + for _, svcsTmpl := range servicesTmpl { svcTmplNameList = append(svcTmplNameList, svcsTmpl...) } - var err error + // Note: For sub env, only the services in the base env are displayed. + if productInfo.ShareEnv.Enable && !productInfo.ShareEnv.IsBase { + svcGroupsInBase, err := GetEnvServiceList(context.TODO(), productInfo.ProductName, productInfo.ShareEnv.BaseEnv) + if err != nil { + return nil, fmt.Errorf("failed to get service list in base env %q of product %q: %s", productInfo.EnvName, productInfo.ProductName, err) + } + + svcMap := map[string]struct{}{} + for _, svcGroup := range svcGroupsInBase { + for _, svcName := range svcGroup { + svcMap[svcName] = struct{}{} + } + } + + tmplNameList := []string{} + for _, svcTmplName := range svcTmplNameList { + if _, found := svcMap[svcTmplName]; found { + tmplNameList = append(tmplNameList, svcTmplName) + } + } + + svcTmplNameList = tmplNameList + } + + var err error serviceRev, err = compareServicesRev(svcTmplNameList, svcList, allServiceTmpls, allRender, newRender, log) if err != nil { log.Errorf("Failed to compare service revision. Error: %v", err) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 16b26abd..1e00e350 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -25,6 +25,7 @@ import ( "github.com/gogo/protobuf/jsonpb" types "github.com/gogo/protobuf/types" + helmclient "github.com/mittwald/go-helm-client" networkingv1alpha3 "istio.io/api/networking/v1alpha3" versionedclient "istio.io/client-go/pkg/clientset/versioned" appsv1 "k8s.io/api/apps/v1" @@ -39,6 +40,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" zadigtypes "github.com/koderover/zadig/pkg/types" @@ -834,7 +836,7 @@ func restartPodsWithIstioProxy(ctx context.Context, kclient client.Client, ns st return nil } -func ensureGrayEnvConfig(ctx context.Context, env *commonmodels.Product, kclient client.Client, istioClient versionedclient.Interface) error { +func EnsureGrayEnvConfig(ctx context.Context, env *commonmodels.Product, kclient client.Client, istioClient versionedclient.Interface) error { opt := &commonrepo.ProductFindOptions{Name: env.ProductName, EnvName: env.ShareEnv.BaseEnv} baseEnv, err := commonrepo.NewProductColl().Find(opt) if err != nil { @@ -1165,35 +1167,7 @@ func EnsureUpdateZadigService(ctx context.Context, env *commonmodels.Product, sv return fmt.Errorf("failed to query Service %s in ns %s: %s", svcName, env.Namespace, err) } - vsName := genVirtualServiceName(svc) - - if env.ShareEnv.IsBase { - // 1. Create VirtualService in the base environment. - err = ensureVirtualService(ctx, kclient, istioClient, env, svc, vsName) - if err != nil { - return err - } - - // 2. Create VirtualService in all of the sub environments. - return ensureServicesInAllSubEnvs(ctx, env, svc, kclient, istioClient) - } - - baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: env.ProductName, - EnvName: env.ShareEnv.BaseEnv, - }) - if err != nil { - return err - } - - // 1. Create VirtualService in the sub environment. - err = ensureVirtualServiceInGray(ctx, env.EnvName, vsName, svcName, env.Namespace, baseEnv.Namespace, istioClient) - if err != nil { - return err - } - - // 2. Updated the VirtualService configuration in the base environment. - return ensureUpdateVirtualServiceInBase(ctx, env.EnvName, vsName, svcName, env.Namespace, baseEnv.Namespace, istioClient) + return ensureUpdateZadigSerivce(ctx, env, svc, kclient, istioClient) } func EnsureDeleteZadigService(ctx context.Context, env *commonmodels.Product, svcSelector labels.Selector, kclient client.Client, istioClient versionedclient.Interface) error { @@ -1214,29 +1188,8 @@ func EnsureDeleteZadigService(ctx context.Context, env *commonmodels.Product, sv return fmt.Errorf("Length of svc list is not expected for env %s of product %s with selector %s. Expected 1 but got %d.", env.EnvName, env.ProductName, svcSelector.String(), len(svcList.Items)) } svc := &svcList.Items[0] - vsName := genVirtualServiceName(svc) - - // Delete VirtualService in the current environment. - err = ensureDeleteVirtualService(ctx, env, vsName, istioClient) - if err != nil { - return err - } - - if env.ShareEnv.IsBase { - // Delete VirtualService and K8s Service in all of the sub environments if there're no specific workloads. - return ensureDeleteServiceInAllSubEnvs(ctx, env, svc, kclient, istioClient) - } - baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: env.ProductName, - EnvName: env.ShareEnv.BaseEnv, - }) - if err != nil { - return err - } - - // Update VirtualService Routes in the base environment. - return ensureCleanRouteInBase(ctx, env.EnvName, baseEnv.Namespace, vsName, istioClient) + return ensureDeleteZadigService(ctx, env, svc, kclient, istioClient) } func GetEnvServiceList(ctx context.Context, productName, baseEnvName string) ([][]string, error) { @@ -1298,3 +1251,137 @@ func CheckServicesDeployedInSubEnvs(ctx context.Context, productName, envName st return svcsInSubEnvs, nil } + +func EnsureZadigServiceByManifest(ctx context.Context, productName, namespace, manifest string) error { + env, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + Namespace: namespace, + }) + if err != nil { + return fmt.Errorf("failed to query namespace %q in project %q: %s", namespace, productName, err) + } + + if !env.ShareEnv.Enable { + return nil + } + + svcNames, err := util.GetSvcNamesFromManifest(manifest) + if err != nil { + return fmt.Errorf("failed to get Service names from manifest: %s", err) + } + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), env.ClusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), env.ClusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to get istio client: %s", err) + } + + for _, svcName := range svcNames { + err := EnsureUpdateZadigService(ctx, env, svcName, kclient, istioClient) + if err != nil { + return fmt.Errorf("failed to ensure Zadig Service for K8s Service %q in env %q of product %q: %s", svcName, env.EnvName, env.ProductName, err) + } + } + + return nil +} + +func EnsureDeleteZadigServiceByHelmRelease(ctx context.Context, env *commonmodels.Product, releaseName string, helmClient helmclient.Client) error { + if !env.ShareEnv.Enable { + return nil + } + + release, err := helmClient.GetRelease(releaseName) + if err != nil { + return fmt.Errorf("failed to get release %q in namespace %q: %s", releaseName, env.Namespace, err) + } + + svcNames, err := util.GetSvcNamesFromManifest(release.Manifest) + if err != nil { + return fmt.Errorf("failed to get Service names from manifest: %s", err) + } + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), env.ClusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), env.ClusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + istioClient, err := versionedclient.NewForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to get istio client: %s", err) + } + + for _, svcName := range svcNames { + err = ensureDeleteZadigServiceBySvcName(ctx, env, svcName, kclient, istioClient) + if err != nil { + return fmt.Errorf("failed to ensure deleting Zadig service by service name %q for env %q of product %q: %s", svcName, env.EnvName, env.ProductName, err) + } + } + + return nil +} + +func ensureDeleteZadigServiceBySvcName(ctx context.Context, env *commonmodels.Product, svcName string, kclient client.Client, istioClient versionedclient.Interface) error { + svc := &corev1.Service{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: svcName, + Namespace: env.Namespace, + }, svc) + if err != nil { + return fmt.Errorf("failed to find Service %q in namespace %q: %s", svcName, env.Namespace, err) + } + + return ensureDeleteZadigService(ctx, env, svc, kclient, istioClient) +} + +func EnsureDeletePreCreatedServices(ctx context.Context, productName, namespace string, chartSpec *helmclient.ChartSpec, helmClient *helmtool.HelmClient) error { + env, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + Namespace: namespace, + }) + if err != nil { + return fmt.Errorf("failed to query namespace %q in project %q: %s", namespace, productName, err) + } + + if !(env.ShareEnv.Enable && !env.ShareEnv.IsBase) { + return nil + } + + manifestBytes, err := helmClient.TemplateChart(chartSpec) + if err != nil { + return fmt.Errorf("failed template chart %q for release %q in namespace %q: %s", chartSpec.ChartName, chartSpec.ReleaseName, chartSpec.Namespace, err) + } + + svcNames, err := util.GetSvcNamesFromManifest(string(manifestBytes)) + if err != nil { + return fmt.Errorf("failed to get Service names from manifest: %s", err) + } + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), env.ClusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + for _, svcName := range svcNames { + err := ensureDeleteK8sService(ctx, namespace, svcName, kclient, true) + if err != nil { + return fmt.Errorf("failed to ensure delete existing K8s Service %q in namespace %q: %s", svcName, namespace, err) + } + } + + return nil +} diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 73c00cd0..9a963103 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -33,6 +33,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/tool/kube/util" "github.com/koderover/zadig/pkg/tool/log" + zadiglabels "github.com/koderover/zadig/pkg/types" zadigutil "github.com/koderover/zadig/pkg/util" ) @@ -153,12 +154,15 @@ func deleteEnvoyFilter(ctx context.Context, istioClient versionedclient.Interfac } func ensureCleanRouteInBase(ctx context.Context, envName, baseNS, vsName string, istioClient versionedclient.Interface) error { - baseVS, err := istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Get(ctx, vsName, metav1.GetOptions{}) + vsObj, err := istioClient.NetworkingV1alpha3().VirtualServices(baseNS).Get(ctx, vsName, metav1.GetOptions{}) if err != nil { log.Warnf("Failed to query VirtualService %s releated to env %s in namesapce %s: %s. It may be not an exception and skip.", vsName, envName, baseNS, err) return nil } + // Note: DeepCopy is used to avoid unpredictable results in concurrent operations. + baseVS := vsObj.DeepCopy() + if len(baseVS.Spec.Http) == 0 { return nil } @@ -274,7 +278,7 @@ func ensureDeleteServiceInAllSubEnvs(ctx context.Context, baseEnv *commonmodels. return fmt.Errorf("failed to delete VirtualService %s in env %s of product %s: %s", vsName, env.EnvName, env.ProductName, err) } - err = ensureDeleteK8sService(ctx, env.Namespace, svc.Name, kclient) + err = ensureDeleteK8sService(ctx, env.Namespace, svc.Name, kclient, true) if err != nil { return fmt.Errorf("failed to delete K8s Service %s in env %s of product: %s: %s", svc.Name, env.EnvName, env.ProductName, err) } @@ -283,7 +287,7 @@ func ensureDeleteServiceInAllSubEnvs(ctx context.Context, baseEnv *commonmodels. return nil } -func ensureDeleteK8sService(ctx context.Context, ns, svcName string, kclient client.Client) error { +func ensureDeleteK8sService(ctx context.Context, ns, svcName string, kclient client.Client, systemCreatedOnly bool) error { svc := &corev1.Service{} err := kclient.Get(ctx, client.ObjectKey{ Name: svcName, @@ -296,6 +300,10 @@ func ensureDeleteK8sService(ctx context.Context, ns, svcName string, kclient cli return err } + if systemCreatedOnly && !(svc.Labels != nil && svc.Labels[zadiglabels.ZadigLabelKeyGlobalOwner] == zadiglabels.Zadig) { + return nil + } + deleteOption := metav1.DeletePropagationBackground return kclient.Delete(ctx, svc, &client.DeleteOptions{ PropagationPolicy: &deleteOption, @@ -339,3 +347,61 @@ func getSvcInEnv(env *commonmodels.Product) []string { return svcs } + +func ensureUpdateZadigSerivce(ctx context.Context, env *commonmodels.Product, svc *corev1.Service, kclient client.Client, istioClient versionedclient.Interface) error { + vsName := genVirtualServiceName(svc) + + if env.ShareEnv.IsBase { + // 1. Create VirtualService in the base environment. + err := ensureVirtualService(ctx, kclient, istioClient, env, svc, vsName) + if err != nil { + return err + } + + // 2. Create VirtualService in all of the sub environments. + return ensureServicesInAllSubEnvs(ctx, env, svc, kclient, istioClient) + } + + baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: env.ProductName, + EnvName: env.ShareEnv.BaseEnv, + }) + if err != nil { + return err + } + + // 1. Create VirtualService in the sub environment. + err = ensureVirtualServiceInGray(ctx, env.EnvName, vsName, svc.Name, env.Namespace, baseEnv.Namespace, istioClient) + if err != nil { + return err + } + + // 2. Updated the VirtualService configuration in the base environment. + return ensureUpdateVirtualServiceInBase(ctx, env.EnvName, vsName, svc.Name, env.Namespace, baseEnv.Namespace, istioClient) +} + +func ensureDeleteZadigService(ctx context.Context, env *commonmodels.Product, svc *corev1.Service, kclient client.Client, istioClient versionedclient.Interface) error { + vsName := genVirtualServiceName(svc) + + // Delete VirtualService in the current environment. + err := ensureDeleteVirtualService(ctx, env, vsName, istioClient) + if err != nil { + return err + } + + if env.ShareEnv.IsBase { + // Delete VirtualService and K8s Service in all of the sub environments if there're no specific workloads. + return ensureDeleteServiceInAllSubEnvs(ctx, env, svc, kclient, istioClient) + } + + baseEnv, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: env.ProductName, + EnvName: env.ShareEnv.BaseEnv, + }) + if err != nil { + return err + } + + // Update VirtualService Routes in the base environment. + return ensureCleanRouteInBase(ctx, env.EnvName, baseEnv.Namespace, vsName, istioClient) +} diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 10ffc225..93871a8f 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -19,6 +19,7 @@ package handler import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "strconv" @@ -51,16 +52,20 @@ func GetProductTemplateServices(c *gin.Context) { isBaseEnvStr := c.Query("isBaseEnv") baseEnvName := c.Query("baseEnv") - // Note: Compatible with previous behavior. - isBaseEnv, err := strconv.ParseBool(isBaseEnvStr) - if err != nil { - log.Warnf("Failed to parse %s to bool: %s.", isBaseEnvStr, err) - } - if envType == "" { envType = types.GeneralEnv } + var isBaseEnv bool + var err error + if envType == types.ShareEnv { + isBaseEnv, err = strconv.ParseBool(isBaseEnvStr) + if err != nil { + ctx.Err = fmt.Errorf("failed to parse %s to bool: %s", isBaseEnvStr, err) + return + } + } + ctx.Resp, ctx.Err = projectservice.GetProductTemplateServices(productTemplatName, envType, isBaseEnv, baseEnvName, ctx.Logger) } diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 58d2e6e5..4b5ef009 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -82,6 +82,7 @@ type HelmClient struct { kubeClient client.Client Namespace string lock *sync.Mutex + RestConfig *rest.Config } // NewClient returns a new Helm client with no construct parameters @@ -102,12 +103,13 @@ func NewClient() (*HelmClient, error) { nil, "", &sync.Mutex{}, + nil, }, nil } // NewClientFromNamespace returns a new Helm client constructed with the provided clusterID and namespace // a kubeClient will be initialized to support necessary k8s operations when install/upgrade helm charts -func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { +func NewClientFromNamespace(clusterID, namespace string) (*HelmClient, error) { restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) if err != nil { return nil, err @@ -135,12 +137,13 @@ func NewClientFromNamespace(clusterID, namespace string) (hc.Client, error) { kubeClient, namespace, &sync.Mutex{}, + restConfig, }, nil } // NewClientFromRestConf returns a new Helm client constructed with the provided REST config options // used to list/uninstall helm release -func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error) { +func NewClientFromRestConf(restConfig *rest.Config, ns string) (*HelmClient, error) { hcClient, err := hc.NewClientFromRestConf(&hc.RestConfClientOptions{ Options: &hc.Options{ Namespace: ns, @@ -158,6 +161,7 @@ func NewClientFromRestConf(restConfig *rest.Config, ns string) (hc.Client, error nil, ns, &sync.Mutex{}, + restConfig, }, nil } @@ -696,6 +700,11 @@ func (hClient *HelmClient) PushChart(repoEntry *repo.Entry, chartPath string) er } } +// NOTE: When using this method, pay attention to whether restConfig is present in the original client. +func (hClient *HelmClient) Clone() (*HelmClient, error) { + return NewClientFromRestConf(hClient.RestConfig, hClient.Namespace) +} + // mergeInstallOptions merges values of the provided chart to helm install options used by the client. func mergeInstallOptions(chartSpec *hc.ChartSpec, installOptions *action.Install) { installOptions.CreateNamespace = chartSpec.CreateNamespace diff --git a/pkg/tool/kube/util/utils.go b/pkg/tool/kube/util/utils.go index caa8cb8e..3a9c0732 100644 --- a/pkg/tool/kube/util/utils.go +++ b/pkg/tool/kube/util/utils.go @@ -17,9 +17,24 @@ limitations under the License. package util import ( + "bytes" + "fmt" + "io" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + yamlutil "k8s.io/apimachinery/pkg/util/yaml" ) +type ObjGVK struct { + Object runtime.Object + GVK *schema.GroupVersionKind +} + func IgnoreNotFoundError(err error) error { if apierrors.IsNotFound(err) { return nil @@ -27,3 +42,55 @@ func IgnoreNotFoundError(err error) error { return err } + +func ParseManifest(manifest string) ([]ObjGVK, error) { + decoder := yamlutil.NewYAMLOrJSONDecoder(bytes.NewReader([]byte(manifest)), 5*1024*1024) + + res := []ObjGVK{} + for { + var rawObj runtime.RawExtension + + err := decoder.Decode(&rawObj) + if err == io.EOF { + break + } + if err != nil { + return res, fmt.Errorf("failed to decode raw extension: %s", err) + } + + obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(rawObj.Raw, nil, nil) + if err != nil { + return res, fmt.Errorf("failed to decode RawExtension to runtime.Object and GVK: %s", err) + } + + res = append(res, ObjGVK{ + Object: obj, + GVK: gvk, + }) + } + + return res, nil +} + +func GetSvcNamesFromManifest(manifest string) ([]string, error) { + objgvks, err := ParseManifest(manifest) + if err != nil { + return nil, err + } + + svcNames := []string{} + for _, objgvk := range objgvks { + if objgvk.GVK.GroupKind().String() != "Service" { + continue + } + + objMeta, err := meta.Accessor(objgvk.Object) + if err != nil { + return nil, fmt.Errorf("failed to construct object meta: %s", err) + } + + svcNames = append(svcNames, objMeta.GetName()) + } + + return svcNames, nil +} -- Gitee From 80c1898d8d793c52c7cb0842fc11f14690ccc2ed Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 28 Apr 2022 11:31:53 +0800 Subject: [PATCH 2306/2647] jenkins supports integrating multiple instances (#1428) * jenkins support multi integration Signed-off-by: liu deyi * jenkins support multi integration Signed-off-by: liu deyi * modify error msg Signed-off-by: liu deyi * remove unused annotation Signed-off-by: liu deyi --- .../core/common/repository/models/build.go | 1 + .../core/common/repository/mongodb/jenkins.go | 13 ++++++++++ .../aslan/core/system/handler/jenkins.go | 4 +-- .../aslan/core/system/handler/router.go | 4 +-- .../aslan/core/system/service/jenkins.go | 18 ++++++------- .../core/workflow/service/workflow/convert.go | 26 +++++++++++++------ 6 files changed, 44 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 8d3bcd5d..546a4e24 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -129,6 +129,7 @@ type DockerBuild struct { } type JenkinsBuild struct { + JenkinsID string `bson:"jenkins_id" json:"jenkins_id"` JobName string `bson:"job_name" json:"job_name"` JenkinsBuildParam []*types.JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/jenkins.go b/pkg/microservice/aslan/core/common/repository/mongodb/jenkins.go index f1bbeaa9..1cd17bcc 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/jenkins.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/jenkins.go @@ -52,6 +52,19 @@ func (c *JenkinsIntegrationColl) EnsureIndex(ctx context.Context) error { return nil } +func (c *JenkinsIntegrationColl) Get(id string) (*models.JenkinsIntegration, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + + query := bson.M{"_id": oid} + res := &models.JenkinsIntegration{} + err = c.FindOne(context.TODO(), query).Decode(res) + + return res, err +} + func (c *JenkinsIntegrationColl) Create(args *models.JenkinsIntegration) error { if args == nil { return errors.New("nil jenkins integration args") diff --git a/pkg/microservice/aslan/core/system/handler/jenkins.go b/pkg/microservice/aslan/core/system/handler/jenkins.go index dfb66747..e333673d 100644 --- a/pkg/microservice/aslan/core/system/handler/jenkins.go +++ b/pkg/microservice/aslan/core/system/handler/jenkins.go @@ -109,12 +109,12 @@ func ListJobNames(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListJobNames(ctx.Logger) + ctx.Resp, ctx.Err = service.ListJobNames(c.Param("id"), ctx.Logger) } func ListJobBuildArgs(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListJobBuildArgs(c.Param("jobName"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListJobBuildArgs(c.Param("id"), c.Param("jobName"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index e132722d..eecc30f4 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -111,8 +111,8 @@ func (*Router) Inject(router *gin.RouterGroup) { jenkins.PUT("/integration/:id", UpdateJenkinsIntegration) jenkins.DELETE("/integration/:id", DeleteJenkinsIntegration) jenkins.POST("/user/connection", TestJenkinsConnection) - jenkins.GET("/jobNames", ListJobNames) - jenkins.GET("/buildArgs/:jobName", ListJobBuildArgs) + jenkins.GET("/jobNames/:id", ListJobNames) + jenkins.GET("/buildArgs/:id/:jobName", ListJobBuildArgs) } //系统配额 diff --git a/pkg/microservice/aslan/core/system/service/jenkins.go b/pkg/microservice/aslan/core/system/service/jenkins.go index 011feb22..8e7815c5 100644 --- a/pkg/microservice/aslan/core/system/service/jenkins.go +++ b/pkg/microservice/aslan/core/system/service/jenkins.go @@ -103,24 +103,22 @@ func TestJenkinsConnection(args *JenkinsArgs, log *zap.SugaredLogger) error { return nil } -func getJenkinsClient(log *zap.SugaredLogger) (*gojenkins.Jenkins, context.Context, error) { +func getJenkinsClient(id string, log *zap.SugaredLogger) (*gojenkins.Jenkins, context.Context, error) { ctx := context.Background() - jenkinsIntegrations, err := ListJenkinsIntegration("", log) + jenkinsIntegration, err := commonrepo.NewJenkinsIntegrationColl().Get(id) if err != nil { - return nil, ctx, err - } - if len(jenkinsIntegrations) == 0 { return nil, ctx, fmt.Errorf("未找到jenkins集成数据") } - jenkinsClient, err := gojenkins.CreateJenkins(nil, jenkinsIntegrations[0].URL, jenkinsIntegrations[0].Username, jenkinsIntegrations[0].Password).Init(ctx) + + jenkinsClient, err := gojenkins.CreateJenkins(nil, jenkinsIntegration.URL, jenkinsIntegration.Username, jenkinsIntegration.Password).Init(ctx) if err != nil { return nil, ctx, err } return jenkinsClient, ctx, nil } -func ListJobNames(log *zap.SugaredLogger) ([]string, error) { - jenkinsClient, ctx, err := getJenkinsClient(log) +func ListJobNames(id string, log *zap.SugaredLogger) ([]string, error) { + jenkinsClient, ctx, err := getJenkinsClient(id, log) if err != nil { return []string{}, e.ErrListJobNames.AddErr(err) } @@ -135,8 +133,8 @@ func ListJobNames(log *zap.SugaredLogger) ([]string, error) { return jobNames, nil } -func ListJobBuildArgs(jobName string, log *zap.SugaredLogger) ([]*JenkinsBuildArgs, error) { - jenkinsClient, ctx, err := getJenkinsClient(log) +func ListJobBuildArgs(id, jobName string, log *zap.SugaredLogger) ([]*JenkinsBuildArgs, error) { + jenkinsClient, ctx, err := getJenkinsClient(id, log) if err != nil { return []*JenkinsBuildArgs{}, e.ErrListJobBuildArgs.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 45d23de8..1dbeecb5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -152,11 +152,6 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z return nil, e.ErrConvertSubTasks.AddErr(err) } - jenkinsIntegrations, err := commonrepo.NewJenkinsIntegrationColl().List() - if err != nil { - return nil, e.ErrConvertSubTasks.AddErr(err) - } - registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { return nil, e.ErrConvertSubTasks.AddErr(err) @@ -172,6 +167,10 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z } for _, module := range modules { + jenkinsIntegration := getJenkinsIntegration(module.JenkinsBuild) + if jenkinsIntegration == nil { + return nil, e.ErrConvertSubTasks.AddDesc("not found jenkins client") + } build := &task.JenkinsBuild{ TaskType: config.TaskJenkinsBuild, Enabled: true, @@ -185,9 +184,9 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z JenkinsBuildParams: jenkinsBuildParams, }, JenkinsIntegration: &task.JenkinsIntegration{ - URL: jenkinsIntegrations[0].URL, - Username: jenkinsIntegrations[0].Username, - Password: jenkinsIntegrations[0].Password, + URL: jenkinsIntegration.URL, + Username: jenkinsIntegration.Username, + Password: jenkinsIntegration.Password, }, Registries: registries, } @@ -202,6 +201,17 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z return subTasks, nil } +func getJenkinsIntegration(jenkinsBuild *commonmodels.JenkinsBuild) *commonmodels.JenkinsIntegration { + if jenkinsBuild == nil { + return nil + } + jenkinsIntegration, err := commonrepo.NewJenkinsIntegrationColl().Get(jenkinsBuild.JenkinsID) + if err != nil { + return nil + } + return jenkinsIntegration +} + func AddPipelineJiraSubTask(pipeline *commonmodels.Pipeline, log *zap.SugaredLogger) (map[string]interface{}, error) { jira := &task.Jira{ TaskType: config.TaskJira, -- Gitee From 651de8f4539db3f251841926e5554ea60b69caa0 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 28 Apr 2022 11:35:17 +0800 Subject: [PATCH 2307/2647] create test need get cluster (#1440) Signed-off-by: mouuii --- .../aslan/core/workflow/handler/system-policies.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index 63595e28..85cf57fd 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -38,6 +38,8 @@ rules: - method: POST endpoint: "/api/aslan/testing/test" + - method: GET + endpoint: "/api/aslan/cluster/clusters" - action: run_test alias: "执行" description: "" -- Gitee From dfb1a280a7641bd2d9e4fc662138049db074a416 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 28 Apr 2022 13:53:55 +0800 Subject: [PATCH 2308/2647] permission miss (#1442) * create test need get cluster Signed-off-by: mouuii * run test need get job permission Signed-off-by: mouuii --- .../aslan/core/workflow/handler/system-policies.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index 85cf57fd..d4a42514 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -46,6 +46,8 @@ rules: - method: POST endpoint: "/api/aslan/testing/testtask" + - method: GET + endpoint: "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*" - resource: DataCenter alias: "数据视图" description: "" -- Gitee From 3953c6c7337c047afb9cb09b8aa8263685b13e95 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 28 Apr 2022 13:55:57 +0800 Subject: [PATCH 2309/2647] permission bug (#1444) Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/rego/authz.rego | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index d0bdc354..04d14018 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -297,11 +297,13 @@ user_projects[project] { some i data.bindings.role_bindings[i].uid == claims.uid project := data.bindings.role_bindings[i].bindings[_].namespace + project !="*" } user_projects[project] { some i data.bindings.policy_bindings[i].uid == claims.uid project := data.bindings.policy_bindings[i].bindings[_].namespace + project !="*" } # get all projects which are visible by all users (the user name is "*") -- Gitee From ca9cd6098cd67d4fad4e8a6d480196b779e94721 Mon Sep 17 00:00:00 2001 From: flyer Date: Thu, 28 Apr 2022 14:31:46 +0800 Subject: [PATCH 2310/2647] remove scripts that are added for node project (#1445) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/reaper/core/service/reaper/script.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index b60b1d2d..03aea043 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -206,11 +206,6 @@ func (r *Reaper) runScripts() error { // avoid non-blocking IO for stdout to workaround "stdout: write error" for _, script := range r.Ctx.Scripts { scripts = append(scripts, script) - // TODO: This may cause nodejs compilation problems, but it is not completely determined, so keep it for now. - if strings.Contains(script, "yarn ") || strings.Contains(script, "npm ") || strings.Contains(script, "bower ") { - scripts = append(scripts, "echo 'turn off O_NONBLOCK after using node'") - scripts = append(scripts, "python -c 'import os,sys,fcntl; flags = fcntl.fcntl(sys.stdout, fcntl.F_GETFL); fcntl.fcntl(sys.stdout, fcntl.F_SETFL, flags&~os.O_NONBLOCK);'") - } } userScriptFile := "user_script.sh" -- Gitee From 9e9a7646fc5a7edd4364f6a4a06282cb53dce151 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 28 Apr 2022 15:58:50 +0800 Subject: [PATCH 2311/2647] add repo name parameter (#1443) * add repo name parameter Signed-off-by: allenshen * fix module get bug Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow_task.go | 8 ++++++++ .../warpdrive/core/service/taskplugin/utils.go | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 71989371..fc081a89 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -97,6 +97,10 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr moBuild = &commonmodels.Build{} target.HasBuild = false } + err = fillBuildDetail(moBuild, containerArr[1], containerArr[2]) + if err != nil { + return resp, e.ErrListBuildModule.AddErr(err) + } target.Build.Repos = moBuild.SafeRepos() @@ -384,6 +388,10 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) moBuild = &commonmodels.Build{} target.HasBuild = false } + err = fillBuildDetail(moBuild, containerArr[1], containerArr[2]) + if err != nil { + return resp, e.ErrListBuildModule.AddErr(err) + } if moBuild.TemplateID != "" { target.Build.Repos = targetInfo.Repos diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 093a1091..18ed9fad 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -248,6 +248,10 @@ func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes func InstantiateBuildSysVariables(jobCtx *task.JobCtx) []*task.KeyVal { ret := make([]*task.KeyVal, 0) for index, repo := range jobCtx.Builds { + + repoNameIndex := fmt.Sprintf("REPONAME_%d", index) + ret = append(ret, &task.KeyVal{Key: fmt.Sprintf(repoNameIndex), Value: repo.RepoName, IsCredential: false}) + repoName := strings.Replace(repo.RepoName, "-", "_", -1) repoIndex := fmt.Sprintf("REPO_%d", index) -- Gitee From 5292a889ba919c1d02a75d54d4005a4f813e9c2d Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 28 Apr 2022 16:13:29 +0800 Subject: [PATCH 2312/2647] data over permission miss (#1446) Signed-off-by: mouuii --- .../aslan/core/workflow/handler/system-policies.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index d4a42514..c2b2cbbc 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -58,6 +58,12 @@ rules: - method: GET endpoint: "/api/aslan/stat/dashboard/overview" + - method: GET + endpoint: "/api/aslan/stat/dashboard/test" + - method: GET + endpoint: "api/aslan/stat/dashboard/deploy" + - method: GET + endpoint: "/api/aslan/stat/dashboard/build" - action: efficiency_over alias: "数据洞察|查看" description: "" -- Gitee From 15c7dd7ac4481622b3ca4b369b7b6165d51c326e Mon Sep 17 00:00:00 2001 From: flyer Date: Thu, 28 Apr 2022 17:30:07 +0800 Subject: [PATCH 2313/2647] return error if failed to fetch reaper (#1448) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 48094528..43d92b3c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -473,9 +473,9 @@ func buildJobWithLinkedNs(taskType config.TaskType, jobImage, jobName, serviceNa } if !strings.Contains(jobImage, PredatorPlugin) && !strings.Contains(jobImage, JenkinsPlugin) && !strings.Contains(jobImage, PackagerPlugin) { - reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", reaperBinaryFile) + reaperBootingScript = fmt.Sprintf("curl -m 10 --retry-delay 3 --retry 3 -sSL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", reaperBinaryFile) if pipelineTask.ConfigPayload.Proxy.EnableApplicationProxy && pipelineTask.ConfigPayload.Proxy.Type == "http" { - reaperBootingScript = fmt.Sprintf("curl -m 60 --retry-delay 5 --retry 3 -sL --proxy %s %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", + reaperBootingScript = fmt.Sprintf("curl -m 10 --retry-delay 3 --retry 3 -sSL --proxy %s %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", pipelineTask.ConfigPayload.Proxy.GetProxyURL(), reaperBinaryFile, ) -- Gitee From d46144113bed23393e7a1653affc592f387c4a1f Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:47:48 +0800 Subject: [PATCH 2314/2647] system test miss url (#1447) Signed-off-by: mouuii --- .../aslan/core/workflow/handler/system-policies.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index c2b2cbbc..a13df1e2 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -26,6 +26,8 @@ rules: - method: PUT endpoint: "/api/aslan/testing/test" + - method: GET + endpoint: "/api/aslan/cluster/clusters" - action: delete_test alias: "删除" description: "" -- Gitee From 9102194f32a0acf24923c229d63d76f9e354d7d6 Mon Sep 17 00:00:00 2001 From: flyer Date: Thu, 28 Apr 2022 21:22:52 +0800 Subject: [PATCH 2315/2647] get images info from pods when listing workloads in helm (#1450) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- .../aslan/core/common/service/environment.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index d95579af..5a8e05dd 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -35,6 +35,7 @@ import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/resource" @@ -148,7 +149,7 @@ func fillServiceDisplayName(svcList []*ServiceResp, productInfo *models.Product) } } -// ListWorkloadsInEnv returns all workloads in the given env which meat the filter. +// ListWorkloadsInEnv returns all workloads in the given env which meet the filter. // A filter is in this format: a=b,c=d, and it is a fuzzy matching. Which means it will return all records with a field called // a and the value contain character b. func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, log *zap.SugaredLogger) (int, []*ServiceResp, error) { @@ -327,6 +328,13 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p }) } + // Note: In some scenarios, such as environment sharing, there may be more containers in Pod than workload. + for _, workload := range workLoads { + selector := labels.SelectorFromSet(labels.Set(workload.Spec.Labels)) + _, _, images := kube.GetSelectedPodsInfo(selector, informer, log) + workload.Images = images + } + log.Debugf("Found %d workloads in total", len(workLoads)) // 对于workload过滤 -- Gitee From 388f9eb5a270fc1b0369ceb8bf30213095b454a7 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Fri, 29 Apr 2022 11:07:14 +0800 Subject: [PATCH 2316/2647] data overview move to picket (#1449) * proxy Signed-off-by: mouuii * proxy Signed-off-by: mouuii * add license Signed-off-by: mouuii --- .../workflow/handler/system-policies.yaml | 8 +-- pkg/microservice/picket/client/aslan/stat.go | 61 +++++++++++++++++++ .../picket/core/filter/handler/router.go | 7 +++ .../picket/core/filter/handler/stat.go | 49 +++++++++++++++ .../picket/core/filter/service/stat.go | 42 +++++++++++++ 5 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 pkg/microservice/picket/client/aslan/stat.go create mode 100644 pkg/microservice/picket/core/filter/handler/stat.go create mode 100644 pkg/microservice/picket/core/filter/service/stat.go diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index a13df1e2..0db27c5a 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -59,13 +59,13 @@ description: "" rules: - method: GET - endpoint: "/api/aslan/stat/dashboard/overview" + endpoint: "/api/v1/picket/stat/dashboard/overview" - method: GET - endpoint: "/api/aslan/stat/dashboard/test" + endpoint: "/api/v1/picket/stat/dashboard/test" - method: GET - endpoint: "api/aslan/stat/dashboard/deploy" + endpoint: "api/v1/picket/stat/dashboard/deploy" - method: GET - endpoint: "/api/aslan/stat/dashboard/build" + endpoint: "/api/v1/picket/stat/dashboard/build" - action: efficiency_over alias: "数据洞察|查看" description: "" diff --git a/pkg/microservice/picket/client/aslan/stat.go b/pkg/microservice/picket/client/aslan/stat.go new file mode 100644 index 00000000..8e90c2cd --- /dev/null +++ b/pkg/microservice/picket/client/aslan/stat.go @@ -0,0 +1,61 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package aslan + +import ( + "net/http" + "net/url" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +func (c *Client) Overview(header http.Header, qs url.Values) ([]byte, error) { + url := "/stat/dashboard/overview" + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} +func (c *Client) Test(header http.Header, qs url.Values) ([]byte, error) { + url := "/stat/dashboard/test" + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} +func (c *Client) Deploy(header http.Header, qs url.Values) ([]byte, error) { + url := "/stat/dashboard/deploy" + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} +func (c *Client) Build(header http.Header, qs url.Values) ([]byte, error) { + url := "/stat/dashboard/build" + res, err := c.Get(url, httpclient.SetHeadersFromHTTPHeader(header), httpclient.SetQueryParamsFromValues(qs)) + if err != nil { + return nil, err + } + + return res.Body(), nil +} diff --git a/pkg/microservice/picket/core/filter/handler/router.go b/pkg/microservice/picket/core/filter/handler/router.go index dcfed5a2..58274a7d 100644 --- a/pkg/microservice/picket/core/filter/handler/router.go +++ b/pkg/microservice/picket/core/filter/handler/router.go @@ -51,4 +51,11 @@ func (*Router) Inject(router *gin.RouterGroup) { { downloads.GET("", GetKubeConfig) } + stat := router.Group("stat") + { + stat.GET("dashboard/overview", Overview) + stat.GET("dashboard/deploy", Deploy) + stat.GET("dashboard/test", Test) + stat.GET("dashboard/build", Build) + } } diff --git a/pkg/microservice/picket/core/filter/handler/stat.go b/pkg/microservice/picket/core/filter/handler/stat.go new file mode 100644 index 00000000..a9cae8d1 --- /dev/null +++ b/pkg/microservice/picket/core/filter/handler/stat.go @@ -0,0 +1,49 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/picket/core/filter/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func Overview(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.Overview(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} +func Test(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.Test(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} +func Deploy(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.Deploy(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} +func Build(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.Build(c.Request.Header, c.Request.URL.Query(), ctx.Logger) +} diff --git a/pkg/microservice/picket/core/filter/service/stat.go b/pkg/microservice/picket/core/filter/service/stat.go new file mode 100644 index 00000000..bc709fc1 --- /dev/null +++ b/pkg/microservice/picket/core/filter/service/stat.go @@ -0,0 +1,42 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "net/http" + "net/url" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/picket/client/aslan" +) + +func Overview(header http.Header, qs url.Values, log *zap.SugaredLogger) ([]byte, error) { + return aslan.New().Overview(header, qs) +} + +func Test(header http.Header, qs url.Values, log *zap.SugaredLogger) ([]byte, error) { + return aslan.New().Test(header, qs) +} + +func Deploy(header http.Header, qs url.Values, log *zap.SugaredLogger) ([]byte, error) { + return aslan.New().Deploy(header, qs) +} + +func Build(header http.Header, qs url.Values, log *zap.SugaredLogger) ([]byte, error) { + return aslan.New().Build(header, qs) +} -- Gitee From bfe0019c0952e513e2a361d4b50edd581efe0f20 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 29 Apr 2022 15:54:03 +0800 Subject: [PATCH 2317/2647] fix can't delete no use params when update product template (#1455) * fix can't delete no use params when update product template Signed-off-by: panxunying * optimize Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/common/service/render.go | 26 ++++++++++++++++--- .../core/environment/service/environment.go | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index f4225a4f..8cef3131 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -244,12 +244,10 @@ func mergeKVs(newKVs []*templatemodels.RenderKV, oldKVs []*templatemodels.Render return result } -func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { +func CreateRenderSetByMerge(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} rs, err := commonrepo.NewRenderSetColl().Find(opt) if rs != nil && err == nil { - // 已经存在渲染配置集 - // 判断是否有修改 if rs.Diff(args) { args.IsDefault = rs.IsDefault } else { @@ -270,6 +268,28 @@ func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error return nil } +func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { + opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} + rs, err := commonrepo.NewRenderSetColl().Find(opt) + if rs != nil && err == nil { + if rs.Diff(args) { + args.IsDefault = rs.IsDefault + } else { + return nil + } + } + if err := ensureRenderSetArgs(args); err != nil { + log.Error(err) + return e.ErrCreateRenderSet.AddDesc(err.Error()) + } + if err := commonrepo.NewRenderSetColl().Create(args); err != nil { + errMsg := fmt.Sprintf("[RenderSet.Create] %s error: %v", args.Name, err) + log.Error(errMsg) + return e.ErrCreateRenderSet.AddDesc(errMsg) + } + return nil +} + // CreateHelmRenderSet 添加renderSet func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 316da6a8..9020e4eb 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -767,7 +767,7 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames return err } - err = commonservice.CreateRenderSet( + err = commonservice.CreateRenderSetByMerge( &commonmodels.RenderSet{ Name: exitedProd.Namespace, EnvName: envName, -- Gitee From e59657aa485f8d8f8a6f9321b3ea009e0ca5c945 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 29 Apr 2022 16:05:43 +0800 Subject: [PATCH 2318/2647] fix for migrate failure when upgrade more than onece (#1456) Signed-off-by: guoyu Co-authored-by: guoyu --- pkg/cli/upgradeassistant/cmd/migrate/1120.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1120.go b/pkg/cli/upgradeassistant/cmd/migrate/1120.go index dd9b48e0..9206d845 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1120.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1120.go @@ -95,7 +95,9 @@ func moduleTestingAddNotifyCtls() error { }), ) } - _, err = testingCol.BulkWrite(context.TODO(), ms) + if len(ms) > 0 { + _, err = testingCol.BulkWrite(context.TODO(), ms) + } return err } @@ -131,7 +133,9 @@ func moduleTestingRollBackNotifyCtls() error { }), ) } - _, err = testingCol.BulkWrite(context.TODO(), ms) + if len(ms) > 0 { + _, err = testingCol.BulkWrite(context.TODO(), ms) + } return err } @@ -168,7 +172,9 @@ func workflowAddNotifyCtls() error { }), ) } - _, err = workflowColl.BulkWrite(context.TODO(), ms) + if len(ms) > 0 { + _, err = workflowColl.BulkWrite(context.TODO(), ms) + } return err } @@ -204,7 +210,9 @@ func workflowRollBackNotifyCtls() error { }), ) } - _, err = workflowColl.BulkWrite(context.TODO(), ms) + if len(ms) > 0 { + _, err = workflowColl.BulkWrite(context.TODO(), ms) + } return err } -- Gitee From f25228ab78f6e0fa42b69bcd6ce4f4be46f26d95 Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 29 Apr 2022 16:37:31 +0800 Subject: [PATCH 2319/2647] add integrated jenkins id in build (#1453) * add integrated jenkins id in build Signed-off-by: liu deyi * modify annotation Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/1120.go | 93 +++++++++++++++++++ .../internal/repository/models/build.go | 18 +++- .../internal/repository/models/jenkins.go | 32 +++++++ .../internal/repository/mongodb/build.go | 16 ++++ .../internal/repository/mongodb/jenkins.go | 62 +++++++++++++ 5 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/jenkins.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/jenkins.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1120.go b/pkg/cli/upgradeassistant/cmd/migrate/1120.go index 9206d845..b957f10b 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1120.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1120.go @@ -46,6 +46,11 @@ func V1110ToV1120() error { return err } + if err := buildAddJenkinsID(); err != nil { + log.Errorf("buildAddJenkinsID err:%s", err) + return err + } + return nil } @@ -60,6 +65,11 @@ func V1120ToV1110() error { log.Errorf("workflowRollBackNotifyCtls err:%s", err) return err } + + if err := buildAddJenkinsIDRollBack(); err != nil { + log.Errorf("buildAddJenkinsIDRollBack err:%s", err) + return err + } return nil } @@ -216,3 +226,86 @@ func workflowRollBackNotifyCtls() error { return err } + +// If the data is not cleaned, executing the jenkins build will report an error +func buildAddJenkinsID() error { + jenkins, err := internalmongodb.NewJenkinsIntegrationColl().List() + if err != nil { + return fmt.Errorf("failed to list jenkins integration ,err: %s", err) + } + + if len(jenkins) == 0 { + return nil + } + + builds, err := internalmongodb.NewBuildColl().List(&internalmongodb.BuildListOption{}) + if err != nil { + return fmt.Errorf("failed to list all builds ,err: %s", err) + } + + if len(builds) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, build := range builds { + if build.JenkinsBuild == nil { + continue + } + + if build.JenkinsBuild.JenkinsID != "" { + continue + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", build.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"jenkins_build.jenkins_id", jenkins[0].ID.Hex()}, + }}, + }), + ) + } + if len(ms) > 0 { + _, err = internalmongodb.NewBuildColl().BulkWrite(context.TODO(), ms) + } + + return err +} + +func buildAddJenkinsIDRollBack() error { + builds, err := internalmongodb.NewBuildColl().List(&internalmongodb.BuildListOption{}) + if err != nil { + return fmt.Errorf("failed to list all builds ,err: %s", err) + } + + if len(builds) == 0 { + return nil + } + + var ms []mongo.WriteModel + for _, build := range builds { + if build.JenkinsBuild == nil { + continue + } + + if build.JenkinsBuild.JenkinsID == "" { + continue + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"_id", build.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"jenkins_build.jenkins_id", ""}, + }}, + }), + ) + } + if len(ms) > 0 { + _, err = internalmongodb.NewBuildColl().BulkWrite(context.TODO(), ms) + } + return err +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/build.go b/pkg/cli/upgradeassistant/internal/repository/models/build.go index 8ff7b639..780edeae 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/build.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/build.go @@ -24,10 +24,11 @@ import ( ) type Build struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` - Timeout int `bson:"timeout" json:"timeout"` - + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + ProductName string `bson:"product_name" json:"product_name"` + PreBuild *PreBuild `bson:"pre_build" json:"pre_build"` + Timeout int `bson:"timeout" json:"timeout"` // TODO: Deprecated. Caches []string `bson:"caches" json:"caches"` @@ -36,7 +37,14 @@ type Build struct { CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` // New since V1.10.0. Only to tell the webpage should the advanced settings be displayed - AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` + AdvancedSettingsModified bool `bson:"advanced_setting_modified" json:"advanced_setting_modified"` + JenkinsBuild *JenkinsBuild `bson:"jenkins_build,omitempty" json:"jenkins_build,omitempty"` +} + +type JenkinsBuild struct { + JenkinsID string `bson:"jenkins_id" json:"jenkins_id"` + JobName string `bson:"job_name" json:"job_name"` + JenkinsBuildParam []*types.JenkinsBuildParam `bson:"jenkins_build_param" json:"jenkins_build_params"` } // PreBuild prepares an environment for a job diff --git a/pkg/cli/upgradeassistant/internal/repository/models/jenkins.go b/pkg/cli/upgradeassistant/internal/repository/models/jenkins.go new file mode 100644 index 00000000..96910307 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/jenkins.go @@ -0,0 +1,32 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type JenkinsIntegration struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + URL string `bson:"url" json:"url"` + Username string `bson:"username" json:"username"` + Password string `bson:"password" json:"password"` + UpdateBy string `bson:"update_by" json:"update_by"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` +} + +func (j JenkinsIntegration) TableName() string { + return "jenkins_integration" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go index b181749d..89acb248 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/build.go @@ -65,3 +65,19 @@ func (c *BuildColl) List(opt *BuildListOption) ([]*models.Build, error) { return resp, nil } + +func (c *BuildColl) Update(build *models.Build) error { + if build == nil { + return errors.New("nil Module args") + } + + query := bson.M{"name": build.Name} + if build.ProductName != "" { + query["product_name"] = build.ProductName + } + + updateBuild := bson.M{"$set": build} + + _, err := c.Collection.UpdateOne(context.TODO(), query, updateBuild) + return err +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/jenkins.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/jenkins.go new file mode 100644 index 00000000..3932f314 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/jenkins.go @@ -0,0 +1,62 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type JenkinsIntegrationColl struct { + *mongo.Collection + + coll string +} + +func NewJenkinsIntegrationColl() *JenkinsIntegrationColl { + name := models.JenkinsIntegration{}.TableName() + coll := &JenkinsIntegrationColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *JenkinsIntegrationColl) List() ([]*models.JenkinsIntegration, error) { + resp := make([]*models.JenkinsIntegration, 0) + query := bson.M{} + + ctx := context.Background() + opts := options.Find().SetSort(bson.D{{"updated_at", -1}}) + + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} -- Gitee From 362559b6e4ada28ed6866c116a19c08b1109e853 Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 29 Apr 2022 16:57:16 +0800 Subject: [PATCH 2320/2647] Service module can be associated with multiple builds (#1423) * service associates multiple builds Signed-off-by: liu deyi * modify helm logic Signed-off-by: liu deyi * modify router path Signed-off-by: liu deyi * modify interface Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * modify productName to projectName Signed-off-by: liu deyi * add buildName Signed-off-by: liu deyi * Compatible with old data Signed-off-by: liu deyi * modify workflow preset interface Signed-off-by: liu deyi * jenkins add buildname Signed-off-by: liu deyi * the find workflow interface is compatible with old data Signed-off-by: liu deyi * Modify the string concatenation method Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove debug ode Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * Handle the build acquisition method in the cloud host scenario Signed-off-by: liu deyi * modify function name Signed-off-by: liu deyi * Modify the way to get the service list Signed-off-by: liu deyi * add shared service Signed-off-by: liu deyi * remove unused code Signed-off-by: liu deyi * handler error Signed-off-by: liu deyi * handle error Signed-off-by: liu deyi * Dealing with Workflow Task Selection Environment Issues Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * Handle display of build names in workflows Signed-off-by: liu deyi --- .../aslan/core/build/handler/build.go | 7 ++ .../aslan/core/build/handler/router.go | 1 + .../aslan/core/build/service/build.go | 44 ++++++++++ .../aslan/core/build/service/target.go | 32 ++------ .../core/common/repository/models/build.go | 3 +- .../core/common/repository/models/workflow.go | 1 + .../aslan/core/service/service/helm.go | 13 ++- .../aslan/core/service/service/service.go | 14 +++- .../core/workflow/service/workflow/convert.go | 2 + .../workflow/service/workflow/workflow.go | 26 +++++- .../service/workflow/workflow_task.go | 81 ++++++++++++++++++- 11 files changed, 189 insertions(+), 35 deletions(-) diff --git a/pkg/microservice/aslan/core/build/handler/build.go b/pkg/microservice/aslan/core/build/handler/build.go index b335d85f..c7f36201 100644 --- a/pkg/microservice/aslan/core/build/handler/build.go +++ b/pkg/microservice/aslan/core/build/handler/build.go @@ -44,6 +44,13 @@ func ListBuildModules(c *gin.Context) { ctx.Resp, ctx.Err = buildservice.ListBuild(c.Query("name"), c.Query("targets"), c.Query("projectName"), ctx.Logger) } +func ListBuildModulesByServiceModule(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = buildservice.ListBuildModulesByServiceModule(c.Query("projectName"), ctx.Logger) +} + func CreateBuildModule(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/build/handler/router.go b/pkg/microservice/aslan/core/build/handler/router.go index 44811e7d..837d084a 100644 --- a/pkg/microservice/aslan/core/build/handler/router.go +++ b/pkg/microservice/aslan/core/build/handler/router.go @@ -29,6 +29,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { build.GET("/:name", FindBuildModule) build.GET("", ListBuildModules) + build.GET("/serviceModule", ListBuildModulesByServiceModule) build.POST("", gin2.UpdateOperationLogStatus, CreateBuildModule) build.PUT("", gin2.UpdateOperationLogStatus, UpdateBuildModule) build.DELETE("", gin2.UpdateOperationLogStatus, DeleteBuildModule) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index c671597a..ce72aac5 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -43,6 +43,12 @@ type BuildResp struct { ProductName string `json:"productName"` } +type ServiceModuleAndBuildResp struct { + ServiceName string `json:"service_name"` + ServiceModule string `json:"service_module"` + ModuleBuilds []*BuildResp `json:"module_builds"` +} + func FindBuild(name, productName string, log *zap.SugaredLogger) (*commonmodels.Build, error) { opt := &commonrepo.BuildFindOption{ Name: name, @@ -108,6 +114,44 @@ func ListBuild(name, targets, productName string, log *zap.SugaredLogger) ([]*Bu return resp, nil } +func ListBuildModulesByServiceModule(productName string, log *zap.SugaredLogger) ([]*ServiceModuleAndBuildResp, error) { + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) + if err != nil { + return nil, e.ErrListBuildModule.AddErr(err) + } + + serviceModuleAndBuildResp := make([]*ServiceModuleAndBuildResp, 0) + for _, serviceTmpl := range services { + for _, container := range serviceTmpl.Containers { + opt := &commonrepo.BuildListOption{ + ServiceName: serviceTmpl.ServiceName, + Targets: []string{container.Name}, + } + if serviceTmpl.Visibility != setting.PublicService { + opt.ProductName = productName + } + + buildModules, err := commonrepo.NewBuildColl().List(opt) + if err != nil { + return nil, e.ErrListBuildModule.AddErr(err) + } + var resp []*BuildResp + for _, build := range buildModules { + resp = append(resp, &BuildResp{ + ID: build.ID.Hex(), + Name: build.Name, + }) + } + serviceModuleAndBuildResp = append(serviceModuleAndBuildResp, &ServiceModuleAndBuildResp{ + ServiceName: serviceTmpl.ServiceName, + ServiceModule: container.Name, + ModuleBuilds: resp, + }) + } + } + return serviceModuleAndBuildResp, nil +} + func fillBuildRepoData(build *commonmodels.Build) { if build.TemplateID == "" { return diff --git a/pkg/microservice/aslan/core/build/service/target.go b/pkg/microservice/aslan/core/build/service/target.go index 016c0ee7..28052d26 100644 --- a/pkg/microservice/aslan/core/build/service/target.go +++ b/pkg/microservice/aslan/core/build/service/target.go @@ -20,7 +20,6 @@ import ( "fmt" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/util/sets" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -37,39 +36,22 @@ func ListDeployTarget(productName string, log *zap.SugaredLogger) ([]*commonmode return serviceObjects, e.ErrListTemplate.AddDesc(errMsg) } - //获取该项目下的的build的targets - buildTargets := sets.NewString() - builds, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{ProductName: productName}) - if err != nil { - log.Errorf("[Build.List] %s error: %v", productName, err) - return nil, e.ErrListBuildModule.AddErr(err) - } - for _, build := range builds { - for _, serviceModuleTarget := range build.Targets { - buildTargets.Insert(fmt.Sprintf("%s-%s", serviceModuleTarget.ServiceName, serviceModuleTarget.ServiceModule)) - } - } - for _, svc := range services { switch svc.Type { case setting.K8SDeployType, setting.HelmDeployType: for _, container := range svc.Containers { - if !buildTargets.Has(fmt.Sprintf("%s-%s", svc.ServiceName, container.Name)) { - serviceObjects = append(serviceObjects, &commonmodels.ServiceModuleTarget{ - ProductName: svc.ProductName, - ServiceName: svc.ServiceName, - ServiceModule: container.Name, - }) - } - } - case setting.PMDeployType: - if !buildTargets.Has(fmt.Sprintf("%s-%s", svc.ServiceName, svc.ServiceName)) { serviceObjects = append(serviceObjects, &commonmodels.ServiceModuleTarget{ ProductName: svc.ProductName, ServiceName: svc.ServiceName, - ServiceModule: svc.ServiceName, + ServiceModule: container.Name, }) } + case setting.PMDeployType: + serviceObjects = append(serviceObjects, &commonmodels.ServiceModuleTarget{ + ProductName: svc.ProductName, + ServiceName: svc.ServiceName, + ServiceModule: svc.ServiceName, + }) } } diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 546a4e24..df1c1d1c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -150,7 +150,8 @@ type ServiceModuleTarget struct { ProductName string `bson:"product_name" json:"product_name"` ServiceName string `bson:"service_name" json:"service_name"` ServiceModule string `bson:"service_module" json:"service_module"` - Repos []*types.Repository `bson:"repos,omitempty" json:"repos,omitempty"` + BuildName string `bson:"build_name" json:"build_name"` + Repos []*types.Repository `bson:"repos,omitempty" json:"repos,omitempty"` } type ServiceModuleTargetBase struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 191460cc..49ad3478 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -356,6 +356,7 @@ type TargetArgs struct { Envs []*KeyVal `bson:"envs" json:"envs"` HasBuild bool `bson:"has_build" json:"has_build"` JenkinsBuildArgs *JenkinsBuildArgs `bson:"jenkins_build_args,omitempty" json:"jenkins_build_args,omitempty"` + BuildName string `bson:"build_name,omitempty" json:"build_name"` } type JenkinsBuildArgs struct { diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index e1d06cde..d5d7a05c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -33,6 +33,7 @@ import ( "github.com/otiai10/copy" "github.com/pkg/errors" "go.uber.org/zap" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "sigs.k8s.io/yaml" @@ -176,10 +177,16 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * for _, container := range serviceTemplate.Containers { serviceModule := new(ServiceModule) serviceModule.Container = container - buildObj, _ := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ProductName: productName, ServiceName: serviceName, Targets: []string{container.Name}}) - if buildObj != nil { - serviceModule.BuildName = buildObj.Name + + buildObjs, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{ProductName: productName, ServiceName: serviceName, Targets: []string{container.Name}}) + if err != nil { + return nil, err + } + buildNames := sets.NewString() + for _, buildObj := range buildObjs { + buildNames.Insert(buildObj.Name) } + serviceModule.BuildNames = buildNames.List() serviceModules = append(serviceModules, serviceModule) } helmServiceModule.Service = serviceTemplate diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index d80986cb..ddd48456 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -64,7 +64,7 @@ type ServiceOption struct { type ServiceModule struct { *commonmodels.Container - BuildName string `json:"build_name"` + BuildNames []string `json:"build_names"` } type Variable struct { @@ -189,10 +189,16 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv serviceModule := new(ServiceModule) serviceModule.Container = container serviceModule.ImageName = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) - buildObj, _ := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{ProductName: args.ProductName, ServiceName: args.ServiceName, Targets: []string{container.Name}}) - if buildObj != nil { - serviceModule.BuildName = buildObj.Name + buildObjs, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{ProductName: args.ProductName, ServiceName: args.ServiceName, Targets: []string{container.Name}}) + if err != nil { + return nil, err + } + + buildNames := sets.NewString() + for _, buildObj := range buildObjs { + buildNames.Insert(buildObj.Name) } + serviceModule.BuildNames = buildNames.List() serviceModules = append(serviceModules, serviceModule) } serviceOption.ServiceModules = serviceModules diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go index 1dbeecb5..457e5d51 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/convert.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/convert.go @@ -126,6 +126,7 @@ func ConvertTaskToQueue(task *task.Task) *commonmodels.Queue { } type JenkinsBuildOption struct { + BuildName string Version string Target string ServiceName string @@ -139,6 +140,7 @@ func JenkinsBuildModuleToSubTasks(jenkinsBuildOption *JenkinsBuildOption, log *z ) opt := &commonrepo.BuildListOption{ + Name: jenkinsBuildOption.BuildName, ServiceName: jenkinsBuildOption.ServiceName, ProductName: jenkinsBuildOption.ProductName, } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 1521b0d5..6cf257e3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -288,15 +288,25 @@ func FindWorkflow(workflowName string, log *zap.SugaredLogger) (*commonmodels.Wo if resp.BuildStage.Enabled { // make a map of current target modules buildMap := map[string]bool{} + + moList, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{}) + if err != nil { + return resp, e.ErrListTemplate.AddDesc(err.Error()) + } for _, build := range resp.BuildStage.Modules { key := fmt.Sprintf("%s-%s-%s", build.Target.ProductName, build.Target.ServiceName, build.Target.ServiceModule) buildMap[key] = true + if build.Target.BuildName == "" { + build.Target.BuildName = findBuildName(key, moList) + } } + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(resp.ProductTmplName) if err != nil { log.Errorf("ServiceTmpl.ListMaxRevisions error: %v", err) return resp, e.ErrListTemplate.AddDesc(err.Error()) } + for _, serviceTmpl := range services { switch serviceTmpl.Type { case setting.PMDeployType: @@ -316,6 +326,7 @@ func FindWorkflow(workflowName string, log *zap.SugaredLogger) (*commonmodels.Wo ProductName: serviceTmpl.ProductName, ServiceName: serviceTmpl.ServiceName, ServiceModule: container.Name, + BuildName: findBuildName(key, moList), }, }) } @@ -327,6 +338,18 @@ func FindWorkflow(workflowName string, log *zap.SugaredLogger) (*commonmodels.Wo return resp, nil } +func findBuildName(key string, moList []*commonmodels.Build) string { + for _, mo := range moList { + for _, moTarget := range mo.Targets { + moduleTargetStr := fmt.Sprintf("%s-%s-%s", moTarget.ProductName, moTarget.ServiceName, moTarget.ServiceModule) + if key == moduleTargetStr { + return mo.Name + } + } + } + return "" +} + type PreSetResp struct { Target *commonmodels.ServiceModuleTarget `json:"target"` BuildModuleVers []string `json:"build_module_vers"` @@ -348,7 +371,7 @@ func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, log.Errorf("[%s] ProductTmpl.Find error: %v", productName, err) return resp, e.ErrGetTemplate.AddDesc(err.Error()) } - services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productTmpl.ProductName) + services, err := commonrepo.NewServiceColl().ListMaxRevisionsForServices(productTmpl.AllServiceInfos(), "") if err != nil { log.Errorf("ServiceTmpl.ListMaxRevisions error: %v", err) return resp, e.ErrListTemplate.AddDesc(err.Error()) @@ -407,6 +430,7 @@ func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, } else { preSet.Repos = mo.SafeRepos() } + preSet.Target.BuildName = mo.Name } } } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index fc081a89..01c2b42e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -253,6 +253,81 @@ func getProjectTargets(productName string) []string { return targets } +func findModuleByContainer(productName, serviceModuleTarget string, buildStageModules []*commonmodels.BuildModule, allModules []*commonmodels.Build, log *zap.SugaredLogger) (*commonmodels.Build, *commonmodels.ServiceModuleTarget) { + // find build name + var buildName string + for _, buildStageModule := range buildStageModules { + if buildStageModule.Target == nil { + return nil, nil + } + targetStr := fmt.Sprintf("%s%s%s%s%s", buildStageModule.Target.ProductName, SplitSymbol, buildStageModule.Target.ServiceName, SplitSymbol, buildStageModule.Target.ServiceModule) + if serviceModuleTarget == targetStr { + buildName = buildStageModule.Target.BuildName + break + } + } + if buildName == "" { + // Compatible with old data if buildName is empty + productTmpl, err := template.NewProductColl().Find(productName) + if err != nil { + log.Errorf("failed to find project,err:%s", err) + return nil, nil + } + services, err := commonrepo.NewServiceColl().ListMaxRevisionsForServices(productTmpl.AllServiceInfos(), "") + if err != nil { + log.Errorf("failed to list services,err:%s", err) + return nil, nil + } + + for _, serviceTmpl := range services { + if serviceTmpl.Type != setting.PMDeployType { + for _, container := range serviceTmpl.Containers { + targetStr := fmt.Sprintf("%s%s%s%s%s", serviceTmpl.ProductName, SplitSymbol, serviceTmpl.ServiceName, SplitSymbol, container.Name) + if serviceModuleTarget == targetStr { + buildName = findBuildNameByContainerName(container.Name, serviceTmpl) + } + } + } else if serviceTmpl.Type == setting.PMDeployType { + targetStr := fmt.Sprintf("%s%s%s%s%s", serviceTmpl.ProductName, SplitSymbol, serviceTmpl.ServiceName, SplitSymbol, serviceTmpl.ServiceName) + if serviceModuleTarget == targetStr { + buildName = findBuildNameByContainerName(serviceTmpl.ServiceName, serviceTmpl) + } + } + } + } + if buildName != "" { + for _, module := range allModules { + if module.Name == buildName { + for _, target := range module.Targets { + targetStr := fmt.Sprintf("%s%s%s%s%s", target.ProductName, SplitSymbol, target.ServiceName, SplitSymbol, target.ServiceModule) + if targetStr == serviceModuleTarget { + return module, target + } + } + } + } + } + + return nil, nil +} + +func findBuildNameByContainerName(containerName string, serviceTmpl *commonmodels.Service) string { + opt := &commonrepo.BuildListOption{ + ServiceName: serviceTmpl.ServiceName, + Targets: []string{containerName}, + } + if serviceTmpl.Visibility != setting.PublicService { + opt.ProductName = serviceTmpl.ProductName + } + + buildModules, err := commonrepo.NewBuildColl().List(opt) + if err != nil || len(buildModules) == 0 { + return "" + } + buildName := buildModules[0].Name + return buildName +} + func findModuleByTargetAndVersion(allModules []*commonmodels.Build, serviceModuleTarget string) (*commonmodels.Build, *commonmodels.ServiceModuleTarget) { containerArr := strings.Split(serviceModuleTarget, SplitSymbol) if len(containerArr) != 3 { @@ -383,11 +458,13 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) Build: &commonmodels.BuildArgs{}, HasBuild: true, } - moBuild, targetInfo := findModuleByTargetAndVersion(allModules, container) + + moBuild, targetInfo := findModuleByContainer(workflow.ProductTmplName, container, workflow.BuildStage.Modules, allModules, log) if moBuild == nil { moBuild = &commonmodels.Build{} target.HasBuild = false } + target.BuildName = moBuild.Name err = fillBuildDetail(moBuild, containerArr[1], containerArr[2]) if err != nil { return resp, e.ErrListBuildModule.AddErr(err) @@ -583,6 +660,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, ProductName: args.ProductTmplName, Variables: target.Envs, Env: env, + BuildName: target.BuildName, } subTasks, err = BuildModuleToSubTasks(buildModuleArgs, log) } else { @@ -591,6 +669,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, ServiceName: target.ServiceName, ProductName: args.ProductTmplName, JenkinsBuildArgs: target.JenkinsBuildArgs, + BuildName: target.BuildName, }, log) } if err != nil { -- Gitee From 1ae1be8fb43667c72d4253ce5dac886bd7ffc1e3 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Fri, 29 Apr 2022 17:07:53 +0800 Subject: [PATCH 2321/2647] permission (#1451) Signed-off-by: mouuii --- pkg/microservice/aslan/core/workflow/handler/policy.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 742f5a53..d4774829 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -83,10 +83,18 @@ rules: - method: PUT endpoint: "/api/aslan/workflow/v3/?*" resourceType: "Workflow" + #一个服务关联多个构建 + - method: GET + endpoint: "/api/aslan/build/build/serviceModule" + resourceType: "Workflow" - action: create_workflow alias: "新建" description: "" rules: + #一个服务关联多个构建 + - method: GET + endpoint: "/api/aslan/build/build/serviceModule" + resourceType: "Workflow" - method: POST endpoint: "api/aslan/workflow/workflow" resourceType: "Workflow" -- Gitee From b9ee309dc17d636843fce5c0d5285bd0418c7879 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 5 May 2022 14:29:42 +0800 Subject: [PATCH 2322/2647] pm add permission (#1457) Signed-off-by: mouuii --- .../core/environment/handler/policy.yaml | 11 ++++++++++ .../core/handler/policy_meta_registration.go | 4 ++-- .../core/service/policy_meta_registration.go | 20 +++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 682c2edf..9ec31699 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -338,6 +338,17 @@ rules: endpoint: "/api/podexec/?*/?*/?*/?*/podExec/?*" idRegex: "/podExec/([\\w\\W]+?)$" resourceType: "Environment" + matchAttributes: + - key: "production" + value: "false" + - action: ssh_pm + alias: "主机登陆" + description: "" + rules: + - method: GET + endpoint: "/api/aslan/environment/environments/?*/services/?*/pmexec" + idRegex: "/api/aslan/environment/environments/([\\w\\W]+?)/services" + resourceType: "Environment" matchAttributes: - key: "production" value: "false" \ No newline at end of file diff --git a/pkg/microservice/policy/core/handler/policy_meta_registration.go b/pkg/microservice/policy/core/handler/policy_meta_registration.go index 99a1fcdc..4fc3e1a0 100644 --- a/pkg/microservice/policy/core/handler/policy_meta_registration.go +++ b/pkg/microservice/policy/core/handler/policy_meta_registration.go @@ -41,6 +41,6 @@ func GetPolicyRegistrationDefinitions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() scope := c.Query("scope") - - ctx.Resp, ctx.Err = service.GetPolicyRegistrationDefinitions(scope, ctx.Logger) + envType := c.Query("env_type") + ctx.Resp, ctx.Err = service.GetPolicyRegistrationDefinitions(scope, envType, ctx.Logger) } diff --git a/pkg/microservice/policy/core/service/policy_meta_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go index bff62a7d..00a18e9f 100644 --- a/pkg/microservice/policy/core/service/policy_meta_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -22,6 +22,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" ) @@ -98,13 +99,13 @@ func CreateOrUpdatePolicyRegistration(p *PolicyMeta, _ *zap.SugaredLogger) error return mongodb.NewPolicyMetaColl().UpdateOrCreate(obj) } -func GetPolicyRegistrationDefinitions(scope string, _ *zap.SugaredLogger) ([]*PolicyDefinition, error) { +func GetPolicyRegistrationDefinitions(scope, envType string, _ *zap.SugaredLogger) ([]*PolicyDefinition, error) { policieMetas, err := mongodb.NewPolicyMetaColl().List() if err != nil { return nil, err } systemScopeSet := sets.NewString("TestCenter", "DataCenter", "Template", "DeliveryCenter") - projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build") + projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build", "Service") systemPolicyMetas, projectPolicyMetas, filteredPolicyMetas := []*models.PolicyMeta{}, []*models.PolicyMeta{}, []*models.PolicyMeta{} for _, v := range policieMetas { if systemScopeSet.Has(v.Resource) { @@ -118,6 +119,21 @@ func GetPolicyRegistrationDefinitions(scope string, _ *zap.SugaredLogger) ([]*Po case string(types.SystemScope): filteredPolicyMetas = systemPolicyMetas case string(types.ProjectScope): + for i, meta := range projectPolicyMetas { + if meta.Resource == "Environment" || meta.Resource == "ProductionEnvironment" { + tmpRules := []*models.PolicyMetaRule{} + for _, rule := range meta.Rules { + if rule.Action == "debug_pod" && envType == setting.PMDeployType { + continue + } + if rule.Action == "ssh_pm" && envType != setting.PMDeployType { + continue + } + tmpRules = append(tmpRules, rule) + } + projectPolicyMetas[i].Rules = tmpRules + } + } filteredPolicyMetas = projectPolicyMetas default: filteredPolicyMetas = policieMetas -- Gitee From 658a713425846b6b3420de78a9ddb16cfd9a26cd Mon Sep 17 00:00:00 2001 From: flyer Date: Thu, 5 May 2022 16:08:38 +0800 Subject: [PATCH 2323/2647] dont enable buildkit feature by default (#1458) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/reaper/core/service/reaper/reaper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index e5b292c3..32620acc 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -543,7 +543,6 @@ func (r *Reaper) getUserEnvs() []string { fmt.Sprintf("WORKSPACE=%s", r.ActiveWorkspace), // TODO: readme文件可以使用别的方式代替 fmt.Sprintf("README=%s", ReadmeFile), - "DOCKER_BUILDKIT=1", } r.Ctx.Paths = strings.Replace(r.Ctx.Paths, "$HOME", config.Home(), -1) -- Gitee From 1adc525c3d72296a46dd492f018e478f8157ac67 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 5 May 2022 16:53:02 +0800 Subject: [PATCH 2324/2647] get workflow add release interface (#1462) Signed-off-by: mouuii --- pkg/microservice/aslan/core/delivery/handler/policy.yaml | 2 ++ pkg/microservice/aslan/core/workflow/handler/policy.yaml | 3 +++ 2 files changed, 5 insertions(+) diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml index 758739d4..441aa414 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ b/pkg/microservice/aslan/core/delivery/handler/policy.yaml @@ -8,6 +8,8 @@ rules: rules: - method: GET endpoint: "/api/aslan/delivery/releases/helm/charts" + - method: GET + endpoint: "/api/aslan/delivery/releases" - action: delete_delivery alias: "删除" description: "" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index d4774829..1b8b7494 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -6,6 +6,9 @@ rules: alias: "查看" description: "" rules: + - method: GET + endpoint: "/api/aslan/delivery/releases" + resourceType: "Workflow" - method: GET endpoint: "api/aslan/workflow/workflow" resourceType: "Workflow" -- Gitee From 4e7a7a5785bd546cd0fc8996a96ed53e138945bd Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 6 May 2022 10:23:59 +0800 Subject: [PATCH 2325/2647] gitee pull request add comment (#1465) * gitee pull request add comment Signed-off-by: liu deyi * modify the comment update logic Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- .../common/repository/models/notification.go | 4 +- .../core/common/service/gitee/comment.go | 31 +++++++++++++++ .../core/common/service/scmnotify/client.go | 35 ++++++++++++++++- .../common/service/scmnotify/scmnotify.go | 2 + .../service/webhook/gitee_testing_task.go | 16 ++++++++ .../service/webhook/gitee_workflow_task.go | 16 ++++++++ pkg/tool/gitee/comment.go | 38 +++++++++++++++++++ 7 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/gitee/comment.go create mode 100644 pkg/tool/gitee/comment.go diff --git a/pkg/microservice/aslan/core/common/repository/models/notification.go b/pkg/microservice/aslan/core/common/repository/models/notification.go index 7b33ee8d..6ddd63e5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/notification.go +++ b/pkg/microservice/aslan/core/common/repository/models/notification.go @@ -27,7 +27,7 @@ import ( ) type Notification struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` CodehostID int `bson:"codehost_id" json:"codehost_id"` Tasks []*NotificationTask `bson:"tasks,omitempty" json:"tasks,omitempty"` PrID int `bson:"pr_id" json:"pr_id"` @@ -41,6 +41,8 @@ type Notification struct { PrTask *PrTaskInfo `bson:"pr_task_info,omitempty" json:"pr_task_info,omitempty"` Label string `bson:"label" json:"label" ` Revision string `bson:"revision" json:"revision"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` } type PrTaskInfo struct { diff --git a/pkg/microservice/aslan/core/common/service/gitee/comment.go b/pkg/microservice/aslan/core/common/service/gitee/comment.go new file mode 100644 index 00000000..5d9e5b82 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/gitee/comment.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) CreateMergeRequestComment(ctx context.Context, owner, repo string, number int32, pullRequestCommentPostParam gitee.PullRequestCommentPostParam) (gitee.PullRequestComments, error) { + return c.Client.CreateMergeRequestComment(ctx, owner, repo, number, pullRequestCommentPostParam) +} + +func (c *Client) UpdateMergeRequestComment(ctx context.Context, owner, repo string, id int32, pullRequestCommentPatchParam gitee.PullRequestCommentPatchParam) error { + return c.Client.UpdateMergeRequestComment(ctx, owner, repo, id, pullRequestCommentPatchParam) +} diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index feadce50..10662073 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -17,16 +17,20 @@ limitations under the License. package scmnotify import ( + "context" "fmt" "strconv" "strings" + giteeClient "gitee.com/openeuler/go-gitee/gitee" "github.com/pkg/errors" "github.com/xanzy/go-gitlab" "go.uber.org/zap" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gitee" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/gerrit" gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" @@ -59,7 +63,7 @@ func (c *Client) Comment(notify *models.Notification) error { if err != nil { return errors.Wrapf(err, "codehost %d not found to comment", notify.CodehostID) } - if strings.ToLower(codeHostDetail.Type) == "gitlab" { + if strings.ToLower(codeHostDetail.Type) == setting.SourceFromGitlab { var note *gitlab.Note cli, err := gitlabtool.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr(), codeHostDetail.EnableProxy) if err != nil { @@ -154,6 +158,35 @@ func (c *Client) Comment(notify *models.Notification) error { } } } + } else if strings.ToLower(codeHostDetail.Type) == setting.SourceFromGitee { + cli := gitee.NewClient(codeHostDetail.ID, codeHostDetail.AccessToken, config.ProxyHTTPSAddr(), codeHostDetail.EnableProxy) + var pullRequestComments giteeClient.PullRequestComments + if notify.CommentID == "" { + // create comment + pullRequestComments, err = cli.CreateMergeRequestComment(context.Background(), + notify.RepoOwner, notify.RepoName, int32(notify.PrID), giteeClient.PullRequestCommentPostParam{ + Body: comment, + }, + ) + + if err == nil { + notify.CommentID = strconv.Itoa(int(pullRequestComments.Id)) + } + } else { + // update comment + commentID, err := strconv.Atoi(notify.CommentID) + if err != nil { + return fmt.Errorf("failed to atoi commentID %v,err: %s", notify.CommentID, err) + } + err = cli.UpdateMergeRequestComment(context.Background(), + notify.RepoOwner, notify.RepoName, int32(commentID), giteeClient.PullRequestCommentPatchParam{ + Body: comment, + }) + } + + if err != nil { + return fmt.Errorf("failed to comment gitee due to %s/%d %v", notify.ProjectID, notify.PrID, err) + } } else { return fmt.Errorf("non gitlab source not supported to comment") } diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index a265cfe6..adad1b1e 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -62,6 +62,8 @@ func (s *Service) SendInitWebhookComment( IsTest: isTest, Label: mainRepo.GetLabelValue(), Revision: mainRepo.Revision, + RepoOwner: mainRepo.RepoOwner, + RepoName: mainRepo.RepoName, } if err := s.Client.Comment(notification); err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go index 614c9609..4bdf8f68 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go @@ -27,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" testingservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" "github.com/koderover/zadig/pkg/tool/gitee" ) @@ -211,6 +212,7 @@ func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log * return findChangedFilesOfPullRequestEvent(PullRequestEvent, codehostId) } + var notification *commonmodels.Notification for _, testing := range testingList { if testing.HookCtl != nil && testing.HookCtl.Enabled { log.Infof("find %d hooks in testing %s", len(testing.HookCtl.Items), testing.Name) @@ -247,6 +249,20 @@ func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log * log.Errorf("failed to auto cancel testing task when receive event %v due to %s ", event, err) mErr = multierror.Append(mErr, err) } + + if notification == nil { + notification, err = scmnotify.NewService().SendInitWebhookComment( + item.MainRepo, ev.PullRequest.Number, baseURI, false, true, log, + ) + if err != nil { + log.Errorf("failed to init webhook comment due to %s", err) + mErr = multierror.Append(mErr, err) + } + } + } + + if notification != nil { + item.TestArgs.NotificationID = notification.ID.Hex() } args := matcher.UpdateTaskArgs(item.TestArgs, requestID) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go index 4c68b726..98ee9b82 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go @@ -29,6 +29,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -243,6 +244,7 @@ func TriggerWorkflowByGiteeEvent(event interface{}, baseURI, requestID string, l return findChangedFilesOfPullRequestEvent(pullRequestEvent, codehostId) } + var notification *commonmodels.Notification for _, workflow := range workflowList { if workflow.HookCtl != nil && workflow.HookCtl.Enabled { log.Debugf("find %d hooks in workflow %s", len(workflow.HookCtl.Items), workflow.Name) @@ -286,6 +288,20 @@ func TriggerWorkflowByGiteeEvent(event interface{}, baseURI, requestID string, l log.Errorf("failed to auto cancel workflow task when receive event due to %v ", err) mErr = multierror.Append(mErr, err) } + + if notification == nil { + notification, err = scmnotify.NewService().SendInitWebhookComment( + item.MainRepo, ev.PullRequest.Number, baseURI, false, false, log, + ) + if err != nil { + log.Errorf("failed to init webhook comment due to %s", err) + mErr = multierror.Append(mErr, err) + } + } + } + + if notification != nil { + item.WorkflowArgs.NotificationID = notification.ID.Hex() } hookPayload = &commonmodels.HookPayload{ diff --git a/pkg/tool/gitee/comment.go b/pkg/tool/gitee/comment.go new file mode 100644 index 00000000..2292f53f --- /dev/null +++ b/pkg/tool/gitee/comment.go @@ -0,0 +1,38 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package gitee + +import ( + "context" + + "gitee.com/openeuler/go-gitee/gitee" +) + +func (c *Client) CreateMergeRequestComment(ctx context.Context, owner, repo string, number int32, pullRequestCommentPostParam gitee.PullRequestCommentPostParam) (gitee.PullRequestComments, error) { + pullRequestComments, _, err := c.PullRequestsApi.PostV5ReposOwnerRepoPullsNumberComments(ctx, owner, repo, number, pullRequestCommentPostParam) + if err != nil { + return pullRequestComments, err + } + return pullRequestComments, nil +} + +func (c *Client) UpdateMergeRequestComment(ctx context.Context, owner, repo string, id int32, pullRequestCommentPatchParam gitee.PullRequestCommentPatchParam) error { + _, _, err := c.PullRequestsApi.PatchV5ReposOwnerRepoPullsCommentsId(ctx, owner, repo, id, pullRequestCommentPatchParam) + if err != nil { + return err + } + return nil +} -- Gitee From b8073ec2609a6faf4c5163cf6cd6c7e46d48535f Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 6 May 2022 17:51:54 +0800 Subject: [PATCH 2326/2647] service reload from template (#1452) * add service reload logic Signed-off-by: allenshen * update service reload logic Signed-off-by: allenshen * update policy file Signed-off-by: allenshen * fix variable display error Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * fix comments Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * resolve comment Signed-off-by: allenshen * resolve comment Signed-off-by: allenshen --- .../core/common/repository/models/chart.go | 23 +- .../core/common/repository/models/service.go | 8 +- .../core/common/repository/mongodb/service.go | 18 + .../common/service/template/dockerfile.go | 93 +---- .../core/service/handler/yaml_template.go | 15 +- .../aslan/core/service/service/helm.go | 74 +++- .../aslan/core/service/service/service.go | 30 +- .../aslan/core/service/service/template.go | 355 ++++++++++++++++++ .../aslan/core/service/service/types.go | 11 +- .../core/service/service/yaml_template.go | 102 ----- .../aslan/core/templatestore/handler/build.go | 2 +- .../aslan/core/templatestore/handler/chart.go | 9 +- .../core/templatestore/handler/dockerfile.go | 13 +- .../core/templatestore/handler/router.go | 20 +- .../aslan/core/templatestore/handler/yaml.go | 22 +- .../service}/build.go | 2 +- .../service}/chart.go | 22 +- .../core/templatestore/service/dockerfile.go | 117 ++++++ .../service}/yaml.go | 28 +- .../workflow/handler/system-policies.yaml | 4 + 20 files changed, 695 insertions(+), 273 deletions(-) create mode 100644 pkg/microservice/aslan/core/service/service/template.go delete mode 100644 pkg/microservice/aslan/core/service/service/yaml_template.go rename pkg/microservice/aslan/core/{common/service/template => templatestore/service}/build.go (99%) rename pkg/microservice/aslan/core/{common/service/template => templatestore/service}/chart.go (95%) create mode 100644 pkg/microservice/aslan/core/templatestore/service/dockerfile.go rename pkg/microservice/aslan/core/{common/service/template => templatestore/service}/yaml.go (84%) diff --git a/pkg/microservice/aslan/core/common/repository/models/chart.go b/pkg/microservice/aslan/core/common/repository/models/chart.go index 3f7135e1..4c7e3fd5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/chart.go +++ b/pkg/microservice/aslan/core/common/repository/models/chart.go @@ -16,17 +16,20 @@ limitations under the License. package models +import "go.mongodb.org/mongo-driver/bson/primitive" + type Chart struct { - Name string `bson:"name" json:"name"` - Source string `bson:"source" json:"source"` - Owner string `bson:"owner" json:"owner"` - Repo string `bson:"repo" json:"repo"` - Path string `bson:"path" json:"path"` - Branch string `bson:"branch" json:"branch"` - CodeHostID int `bson:"codehost_id" json:"codeHostID"` - Revision int64 `bson:"revision" json:"revision"` - ChartVariables []*ChartVariable `bson:"variables" json:"variables"` - Sha1 string `bson:"sha1" json:"sha1"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Source string `bson:"source" json:"source"` + Owner string `bson:"owner" json:"owner"` + Repo string `bson:"repo" json:"repo"` + Path string `bson:"path" json:"path"` + Branch string `bson:"branch" json:"branch"` + CodeHostID int `bson:"codehost_id" json:"codeHostID"` + Revision int64 `bson:"revision" json:"revision"` + ChartVariables []*ChartVariable `bson:"variables" json:"variables"` + Sha1 string `bson:"sha1" json:"sha1"` } type ChartVariable struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index e05ccf4c..10efb512 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -64,11 +64,12 @@ type Service struct { BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` LoadFromDir bool `bson:"is_dir,omitempty" json:"is_dir,omitempty"` - CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` + CreateFrom interface{} `bson:"create_from,omitempty" json:"create_from,omitempty"` HealthChecks []*PmHealthCheck `bson:"health_checks,omitempty" json:"health_checks,omitempty"` WorkloadType string `bson:"workload_type,omitempty" json:"workload_type,omitempty"` EnvName string `bson:"env_name,omitempty" json:"env_name,omitempty"` TemplateID string `bson:"template_id,omitempty" json:"template_id,omitempty"` + AutoSync bool `bson:"auto_sync" json:"auto_sync"` } type CreateFromRepo struct { @@ -94,6 +95,11 @@ type CreateFromChartRepo struct { ChartVersion string `json:"chart_version" bson:"chart_version"` } +type CreateFromYamlTemplate struct { + TemplateID string `bson:"template_id" json:"template_id"` + Variables []*Variable `bson:"variables" json:"variables"` +} + type GUIConfig struct { Deployment interface{} `bson:"deployment,omitempty" json:"deployment,omitempty"` Ingress interface{} `bson:"ingress,omitempty" json:"ingress,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index ac09023e..e503bf68 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -184,6 +184,24 @@ func (c *ServiceColl) ListMaxRevisionsByProduct(productName string) ([]*models.S return c.listMaxRevisions(m, nil) } +func (c *ServiceColl) ListMaxRevisionServicesByYamlTemplate(templateId string) ([]*models.Service, error) { + m := bson.M{ + "template_id": templateId, + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + "source": setting.ServiceSourceTemplate, + } + return c.listMaxRevisions(m, nil) +} + +func (c *ServiceColl) ListMaxRevisionServicesByChartTemplate(templateName string) ([]*models.Service, error) { + m := bson.M{ + "create_from.template_name": templateName, + "status": bson.M{"$ne": setting.ProductStatusDeleting}, + "source": setting.SourceFromChartTemplate, + } + return c.listMaxRevisions(m, nil) +} + func (c *ServiceColl) ListMaxRevisionsAllSvcByProduct(productName string) ([]*models.Service, error) { m := bson.M{ "product_name": productName, diff --git a/pkg/microservice/aslan/core/common/service/template/dockerfile.go b/pkg/microservice/aslan/core/common/service/template/dockerfile.go index a7414953..b246736b 100644 --- a/pkg/microservice/aslan/core/common/service/template/dockerfile.go +++ b/pkg/microservice/aslan/core/common/service/template/dockerfile.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,56 +21,15 @@ import ( "fmt" "strings" - dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" - dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" "go.uber.org/zap" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" + dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" + dockerfileparser "github.com/moby/buildkit/frontend/dockerfile/parser" ) -func CreateDockerfileTemplate(template *DockerfileTemplate, logger *zap.SugaredLogger) error { - err := commonrepo.NewDockerfileTemplateColl().Create(&commonmodels.DockerfileTemplate{ - Name: template.Name, - Content: template.Content, - }) - if err != nil { - logger.Errorf("create dockerfile template error: %s", err) - } - return err -} - -func UpdateDockerfileTemplate(id string, template *DockerfileTemplate, logger *zap.SugaredLogger) error { - err := commonrepo.NewDockerfileTemplateColl().Update( - id, - &commonmodels.DockerfileTemplate{ - Name: template.Name, - Content: template.Content, - }, - ) - if err != nil { - logger.Errorf("update dockerfile template error: %s", err) - } - return err -} - -func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*DockerfileListObject, int, error) { - resp := make([]*DockerfileListObject, 0) - templateList, total, err := commonrepo.NewDockerfileTemplateColl().List(pageNum, pageSize) - if err != nil { - logger.Errorf("list dockerfile template error: %s", err) - return resp, 0, err - } - for _, obj := range templateList { - resp = append(resp, &DockerfileListObject{ - ID: obj.ID.Hex(), - Name: obj.Name, - }) - } - return resp, total, err -} - func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*DockerfileDetail, error) { resp := new(DockerfileDetail) dockerfileTemplate, err := commonrepo.NewDockerfileTemplateColl().GetById(id) @@ -89,52 +48,6 @@ func GetDockerfileTemplateDetail(id string, logger *zap.SugaredLogger) (*Dockerf return resp, nil } -func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { - ref, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) - if err != nil { - logger.Errorf("Failed to get build reference for template id: %s, the error is: %s", id, err) - return err - } - if len(ref) > 0 { - return errors.New("this template is in use") - } - err = commonrepo.NewDockerfileTemplateColl().DeleteByID(id) - if err != nil { - logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) - } - return err -} - -func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*BuildReference, error) { - ret := make([]*BuildReference, 0) - referenceList, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) - if err != nil { - logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) - return ret, err - } - for _, reference := range referenceList { - ret = append(ret, &BuildReference{ - BuildName: reference.Name, - ProjectName: reference.ProductName, - }) - } - return ret, nil -} - -func ValidateDockerfileTemplate(template string, logger *zap.SugaredLogger) error { - // some dockerfile validation stuff - reader := strings.NewReader(template) - result, err := dockerfileparser.Parse(reader) - if err != nil { - return err - } - _, _, err = dockerfileinstructions.Parse(result.AST) - if err != nil { - return err - } - return nil -} - func getVariables(s string, logger *zap.SugaredLogger) ([]*commonmodels.ChartVariable, error) { ret := make([]*commonmodels.ChartVariable, 0) reader := strings.NewReader(s) diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go index e6a400f5..3b048dd7 100644 --- a/pkg/microservice/aslan/core/service/handler/yaml_template.go +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -23,36 +23,29 @@ import ( internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -type LoadServiceFromYamlTemplateReq struct { - ServiceName string `json:"service_name"` - ProjectName string `json:"project_name"` - TemplateID string `json:"template_id"` - Variables []*svcservice.Variable `json:"variables"` -} - func LoadServiceFromYamlTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(LoadServiceFromYamlTemplateReq) + req := new(svcservice.LoadServiceFromYamlTemplateReq) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.UserName, req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, ctx.Logger) + ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.UserName, req, ctx.Logger) } func ReloadServiceFromYamlTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := new(LoadServiceFromYamlTemplateReq) + req := new(svcservice.LoadServiceFromYamlTemplateReq) if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err return } - ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.UserName, req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, ctx.Logger) + ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.UserName, req, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index d5d7a05c..3381f447 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -113,6 +113,8 @@ type helmServiceCreationArgs struct { GerritCodeHostID int ChartRepoName string ValuesSource *commonservice.ValuesDataArgs + CreationDetail interface{} + AutoSync bool } type ChartTemplateData struct { @@ -167,6 +169,47 @@ func ListHelmServices(productName string, log *zap.SugaredLogger) (*HelmService, return helmService, nil } +func getCreateFromChartTemplate(createFrom interface{}) (*models.CreateFromChartTemplate, error) { + bs, err := json.Marshal(createFrom) + if err != nil { + return nil, err + } + ret := &models.CreateFromChartTemplate{} + err = json.Unmarshal(bs, ret) + return ret, err +} + +func fillServiceTemplateVariables(serviceTemplate *models.Service) error { + if serviceTemplate.Source != setting.SourceFromChartTemplate { + return nil + } + creation, err := getCreateFromChartTemplate(serviceTemplate.CreateFrom) + if err != nil { + return fmt.Errorf("failed to get creation detail: %s", err) + } + + templateChart, err := commonrepo.NewChartColl().Get(creation.TemplateName) + if err != nil { + return err + } + variables := make([]*models.Variable, 0) + curValueMap := make(map[string]string) + for _, kv := range creation.Variables { + curValueMap[kv.Key] = kv.Value + } + + for _, v := range templateChart.ChartVariables { + value := v.Value + if cv, ok := curValueMap[v.Key]; ok { + value = cv + } + variables = append(variables, &models.Variable{Key: v.Key, Value: value}) + } + creation.Variables = variables + serviceTemplate.CreateFrom = creation + return nil +} + func GetHelmServiceModule(serviceName, productName string, revision int64, log *zap.SugaredLogger) (*HelmServiceModule, error) { serviceTemplate, err := commonservice.GetServiceTemplate(serviceName, setting.HelmDeployType, productName, setting.ProductStatusDeleting, revision, log) if err != nil { @@ -189,6 +232,11 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * serviceModule.BuildNames = buildNames.List() serviceModules = append(serviceModules, serviceModule) } + err = fillServiceTemplateVariables(serviceTemplate) + if err != nil { + // NOTE source template may be deleted, error should not block the following logic + log.Warnf("failed to fill service template variables for service: %s, err: %s", serviceTemplate.ServiceName, err) + } helmServiceModule.Service = serviceTemplate serviceTemplate.ReleaseNaming = serviceTemplate.GetReleaseNaming() helmServiceModule.ServiceModules = serviceModules @@ -465,6 +513,11 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil, err } + return createOrUpdateHelmServiceFromChartTemplate(templateArgs, templateChartInfo, projectName, args, logger) +} + +func createOrUpdateHelmServiceFromChartTemplate(templateArgs *CreateFromChartTemplate, templateChartInfo *ChartTemplateData, projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { + // NOTE we may need a better way to handle service name with spaces args.Name = strings.TrimSpace(args.Name) @@ -489,11 +542,11 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe from := filepath.Join(localBase, base) to := filepath.Join(config.LocalServicePath(projectName, args.Name), args.Name) // remove old files - if err = os.RemoveAll(to); err != nil { + if err := os.RemoveAll(to); err != nil { logger.Errorf("Failed to remove dir %s, err: %s", to, err) return nil, err } - if err = copy.Copy(from, to); err != nil { + if err := copy.Copy(from, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", from, to, err) return nil, err } @@ -551,6 +604,8 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe ValuesYaml: templateArgs.ValuesYAML, Variables: templateArgs.Variables, ValuesSource: args.ValuesData, + CreationDetail: args.CreationDetail, + AutoSync: args.AutoSync, }, logger, ) @@ -899,7 +954,7 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel wg.Add(1) go func(repoConfig *commonservice.RepoConfig, path string) { defer wg.Done() - renderChart, err := handleSingleService(projectName, repoConfig, path, from, args.CreatedBy, templateChartData, args.ValuesData, logger) + renderChart, err := handleSingleService(projectName, repoConfig, args, path, from, templateChartData, logger) if err != nil { failedServiceMap.Store(path, err.Error()) } else { @@ -936,9 +991,8 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel return resp, nil } -func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, path, fromPath, createBy string, - templateChartData *ChartTemplateData, valuesData *commonservice.ValuesDataArgs, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { - +func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, args *BulkHelmServiceCreationArgs, path, fromPath string, templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { + createBy, valuesData := args.CreatedBy, args.ValuesData valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, Owner: repoConfig.Owner, @@ -1026,6 +1080,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi ValuePaths: []string{path}, ValuesYaml: string(valuesYAML), ValuesSource: valuesData, + AutoSync: args.AutoSync, }, logger, ) @@ -1225,12 +1280,17 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg SrcPath: args.RepoLink, Source: args.Source, ReleaseNaming: setting.DefaultReleaseNaming, + AutoSync: args.AutoSync, HelmChart: &commonmodels.HelmChart{ Name: chartName, Version: chartVersion, ValuesYaml: valuesYaml, }, - CreateFrom: geneCreationDetail(args), + } + if args.CreationDetail != nil { + serviceObj.CreateFrom = args.CreationDetail + } else { + serviceObj.CreateFrom = geneCreationDetail(args) } switch args.Source { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index ddd48456..ffc791ec 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -55,11 +55,12 @@ import ( ) type ServiceOption struct { - ServiceModules []*ServiceModule `json:"service_module"` - SystemVariable []*Variable `json:"system_variable"` - CustomVariable []*templatemodels.RenderKV `json:"custom_variable"` - Yaml string `json:"yaml"` - Service *commonmodels.Service `json:"service,omitempty"` + ServiceModules []*ServiceModule `json:"service_module"` + SystemVariable []*Variable `json:"system_variable"` + CustomVariable []*templatemodels.RenderKV `json:"custom_variable"` + TemplateVariable []*Variable `json:"template_variable"` + Yaml string `json:"yaml"` + Service *commonmodels.Service `json:"service,omitempty"` } type ServiceModule struct { @@ -181,6 +182,24 @@ func GetServiceTemplateOption(serviceName, productName string, revision int64, l return serviceOption, err } +func GetTemplateVariables(args *commonmodels.Service) []*Variable { + if args.TemplateID == "" { + return nil + } + templateInfo, err := commonrepo.NewYamlTemplateColl().GetById(args.TemplateID) + if err != nil { + log.Errorf("failed to find template with id: %s for service: %s, err: %s", args.TemplateID, args.ServiceName, err) + return nil + } + + variables, err := buildYamlTemplateVariables(args, templateInfo) + if err != nil { + log.Errorf("failed to extract template variables for service: %s, err: %s", args.ServiceName, err) + return nil + } + return variables +} + func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*ServiceOption, error) { serviceOption := new(ServiceOption) @@ -216,6 +235,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv Key: "$EnvName$", Value: ""}, } + serviceOption.TemplateVariable = GetTemplateVariables(args) renderKVs, err := commonservice.ListServicesRenderKeys([]*templatemodels.ServiceInfo{{Name: args.ServiceName, Owner: args.ProductName}}, log) if err != nil { log.Errorf("ListServicesRenderKeys %s error: %v", args.ServiceName, err) diff --git a/pkg/microservice/aslan/core/service/service/template.go b/pkg/microservice/aslan/core/service/service/template.go new file mode 100644 index 00000000..32e69f78 --- /dev/null +++ b/pkg/microservice/aslan/core/service/service/template.go @@ -0,0 +1,355 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" +) + +type LoadServiceFromYamlTemplateReq struct { + ServiceName string `json:"service_name"` + ProjectName string `json:"project_name"` + TemplateID string `json:"template_id"` + AutoSync bool `json:"auto_sync"` + Variables []*Variable `json:"variables"` +} + +func geneCreateFromDetail(templateId string, variables []*Variable) *commonmodels.CreateFromYamlTemplate { + vs := make([]*commonmodels.Variable, 0, len(variables)) + for _, kv := range variables { + vs = append(vs, &commonmodels.Variable{ + Key: kv.Key, + Value: kv.Value, + }) + } + return &commonmodels.CreateFromYamlTemplate{ + TemplateID: templateId, + Variables: vs, + } +} + +func LoadServiceFromYamlTemplate(username string, req *LoadServiceFromYamlTemplateReq, logger *zap.SugaredLogger) error { + projectName, serviceName, templateID, variables, autoSync := req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, req.AutoSync + template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) + if err != nil { + logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) + return err + } + renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) + service := &commonmodels.Service{ + ServiceName: serviceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: templateID, + AutoSync: autoSync, + CreateFrom: geneCreateFromDetail(templateID, variables), + } + _, err = CreateServiceTemplate(username, service, logger) + if err != nil { + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) + } + return err +} + +func ReloadServiceFromYamlTemplate(username string, req *LoadServiceFromYamlTemplateReq, logger *zap.SugaredLogger) error { + projectName, serviceName, templateID, variables, autoSync := req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, req.AutoSync + service, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + ProductName: projectName, + }) + if err != nil { + logger.Errorf("Cannot find service of name [%s] from project [%s], the error is: %s", serviceName, projectName, err) + return err + } + if service.Source != setting.ServiceSourceTemplate { + return errors.New("service is not created from template") + } + if service.TemplateID == "" { + return fmt.Errorf("failed to find template id for service: %s", serviceName) + } + template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) + if err != nil { + logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) + return err + } + renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) + svc := &commonmodels.Service{ + ServiceName: serviceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: templateID, + AutoSync: autoSync, + CreateFrom: geneCreateFromDetail(templateID, variables), + } + _, err = CreateServiceTemplate(username, svc, logger) + if err != nil { + logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", service.TemplateID, err) + } + return err +} + +func renderYamlFromTemplate(yaml, productName, serviceName string, variables []*Variable) string { + for _, variable := range variables { + yaml = strings.ReplaceAll(yaml, buildVariable(variable.Key), variable.Value) + } + yaml = strings.ReplaceAll(yaml, setting.TemplateVariableProduct, productName) + yaml = strings.ReplaceAll(yaml, setting.TemplateVariableService, serviceName) + return yaml +} + +func buildVariable(key string) string { + return fmt.Sprintf("{{.%s}}", key) +} + +// SyncServiceFromTemplate syncs services from (yaml|chart)template +func SyncServiceFromTemplate(source, templateId, templateName string, logger *zap.SugaredLogger) error { + if source == setting.ServiceSourceTemplate { + return syncServicesFromYamlTemplate(templateId, logger) + } else { + return syncServicesFromChartTemplate(templateName, logger) + } +} + +func syncServicesFromYamlTemplate(templateId string, logger *zap.SugaredLogger) error { + serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionServicesByYamlTemplate(templateId) + if err != nil { + return err + } + servicesByProject := make(map[string][]*commonmodels.Service) + for _, service := range serviceList { + if !service.AutoSync { + continue + } + servicesByProject[service.ProductName] = append(servicesByProject[service.ProductName], service) + } + yamlTemplate, err := commonrepo.NewYamlTemplateColl().GetById(templateId) + if err != nil { + return fmt.Errorf("failed to find yaml template: %s, err: %s", templateId, err) + } + for _, services := range servicesByProject { + go func(pServices []*commonmodels.Service) { + for _, service := range pServices { + err := reloadServiceFromYamlTemplate(service.ProductName, yamlTemplate, service) + if err != nil { + logger.Errorf("failed to reload service: %s from template: %s, err: %s", service.ServiceName, yamlTemplate.Name, err) + title := fmt.Sprintf("从模板更新 [%s] 的 [%s] 服务失败", service.ProductName, service.ServiceName) + commonservice.SendErrorMessage("system", title, "", err, logger) + } + } + }(services) + } + return nil +} + +func syncServicesFromChartTemplate(templateName string, logger *zap.SugaredLogger) error { + chartTemplate, err := prepareChartTemplateData(templateName, log.SugaredLogger()) + if err != nil { + return err + } + + serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionServicesByChartTemplate(templateName) + if err != nil { + return err + } + servicesByProject := make(map[string][]*commonmodels.Service) + for _, service := range serviceList { + if !service.AutoSync { + continue + } + servicesByProject[service.ProductName] = append(servicesByProject[service.ProductName], service) + } + + for _, services := range servicesByProject { + go func(pService []*commonmodels.Service) { + for _, service := range pService { + err := reloadServiceFromChartTemplate(service, chartTemplate) + if err != nil { + logger.Errorf("failed to reload service %s/%s from chart template, err: %s", service.ProductName, service.ServiceName, err) + title := fmt.Sprintf("从模板更新 [%s] 的 [%s] 服务失败", service.ProductName, service.ServiceName) + commonservice.SendErrorMessage("system", title, "", err, logger) + } + } + }(services) + } + return nil +} + +func reloadServiceFromChartTemplate(service *commonmodels.Service, chartTemplate *ChartTemplateData) error { + variable, customYaml, err := buildChartTemplateVariables(service, chartTemplate.TemplateData) + if err != nil { + return err + } + + templateArgs := &CreateFromChartTemplate{ + TemplateName: chartTemplate.TemplateName, + ValuesYAML: customYaml, + Variables: variable, + } + args := &HelmServiceCreationArgs{ + HelmLoadSource: HelmLoadSource{}, + Name: service.ServiceName, + CreatedBy: "system", + ValuesData: nil, + CreationDetail: service.CreateFrom, + AutoSync: service.AutoSync, + } + ret, err := createOrUpdateHelmServiceFromChartTemplate(templateArgs, chartTemplate, service.ProductName, args, log.SugaredLogger()) + if err != nil { + return err + } + if len(ret.FailedServices) == 1 { + return errors.New(ret.FailedServices[0].Error) + } + return nil +} + +func buildYamlTemplateVariables(service *commonmodels.Service, template *commonmodels.YamlTemplate) ([]*Variable, error) { + variables := make([]*Variable, 0) + variableMap := make(map[string]*Variable) + for _, v := range template.Variables { + kv := &Variable{ + Key: v.Key, + Value: v.Value, + } + variableMap[v.Key] = kv + variables = append(variables, kv) + } + + creation := &commonmodels.CreateFromYamlTemplate{} + vbs := make([]*commonmodels.Variable, 0) + if service.CreateFrom != nil { + bs, err := json.Marshal(service.CreateFrom) + if err != nil { + log.Errorf("failed to marshal creation data: %s", err) + return variables, err + } + + err = json.Unmarshal(bs, creation) + if err != nil { + log.Errorf("failed to unmarshal creation data: %s", err) + return variables, err + } + for _, kv := range creation.Variables { + if tkv, ok := variableMap[kv.Key]; ok { + tkv.Value = kv.Value + } + } + } else { + creation.TemplateID = template.ID.Hex() + } + + for _, kv := range variables { + vbs = append(vbs, &commonmodels.Variable{ + Key: kv.Key, + Value: kv.Value, + }) + } + creation.Variables = vbs + service.CreateFrom = creation + + return variables, nil +} + +func buildChartTemplateVariables(service *commonmodels.Service, template *commonmodels.Chart) ([]*Variable, string, error) { + variables := make([]*Variable, 0) + variableMap := make(map[string]*Variable) + + for _, v := range template.ChartVariables { + kv := &Variable{ + Key: v.Key, + Value: v.Value, + } + variableMap[v.Key] = kv + variables = append(variables, kv) + } + + customYaml := "" + if service.CreateFrom != nil { + bs, err := json.Marshal(service.CreateFrom) + if err != nil { + log.Errorf("failed to marshal creation data: %s", err) + return variables, "", err + } + creation := &commonmodels.CreateFromChartTemplate{} + err = json.Unmarshal(bs, creation) + if err != nil { + log.Errorf("failed to unmarshal creation data: %s", err) + return variables, "", err + } + for _, kv := range creation.Variables { + if tkv, ok := variableMap[kv.Key]; ok { + tkv.Value = kv.Value + } + } + if creation.YamlData != nil { + customYaml = creation.YamlData.YamlContent + } + vbs := make([]*commonmodels.Variable, 0) + for _, kv := range variables { + vbs = append(vbs, &commonmodels.Variable{ + Key: kv.Key, + Value: kv.Value, + }) + } + creation.Variables = vbs + service.CreateFrom = creation + } + return variables, customYaml, nil +} + +func reloadServiceFromYamlTemplate(projectName string, template *commonmodels.YamlTemplate, service *commonmodels.Service) error { + //extract variables from current service + variables, err := buildYamlTemplateVariables(service, template) + if err != nil { + return err + } + + renderedYaml := renderYamlFromTemplate(template.Content, projectName, service.ServiceName, variables) + svc := &commonmodels.Service{ + ServiceName: service.ServiceName, + Type: setting.K8SDeployType, + ProductName: projectName, + Source: setting.ServiceSourceTemplate, + Yaml: renderedYaml, + Visibility: setting.PrivateVisibility, + TemplateID: service.TemplateID, + CreateFrom: service.CreateFrom, + AutoSync: service.AutoSync, + } + _, err = CreateServiceTemplate("system", svc, log.SugaredLogger()) + if err != nil { + return fmt.Errorf("failed to reload service template from template ID: %s, error : %s", service.TemplateID, err) + } + return nil +} diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index 7061c21b..ef96e2ae 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -40,10 +40,12 @@ type HelmLoadSource struct { type HelmServiceCreationArgs struct { HelmLoadSource - Name string `json:"name"` - CreatedBy string `json:"createdBy"` - CreateFrom interface{} `json:"createFrom"` - ValuesData *service.ValuesDataArgs `json:"valuesData"` + Name string `json:"name"` + CreatedBy string `json:"createdBy"` + AutoSync bool `json:"auto_sync"` + CreateFrom interface{} `json:"createFrom"` + ValuesData *service.ValuesDataArgs `json:"valuesData"` + CreationDetail interface{} `json:"-"` } type BulkHelmServiceCreationArgs struct { @@ -51,6 +53,7 @@ type BulkHelmServiceCreationArgs struct { CreateFrom interface{} `json:"createFrom"` CreatedBy string `json:"createdBy"` ValuesData *service.ValuesDataArgs `json:"valuesData"` + AutoSync bool `json:"auto_sync"` } type FailedService struct { diff --git a/pkg/microservice/aslan/core/service/service/yaml_template.go b/pkg/microservice/aslan/core/service/service/yaml_template.go deleted file mode 100644 index 2190eace..00000000 --- a/pkg/microservice/aslan/core/service/service/yaml_template.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package service - -import ( - "errors" - "fmt" - "strings" - - "go.uber.org/zap" - - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/setting" -) - -func LoadServiceFromYamlTemplate(username, projectName, serviceName, templateID string, variables []*Variable, logger *zap.SugaredLogger) error { - template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) - if err != nil { - logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) - return err - } - renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) - service := &commonmodels.Service{ - ServiceName: serviceName, - Type: setting.K8SDeployType, - ProductName: projectName, - Source: setting.ServiceSourceTemplate, - Yaml: renderedYaml, - Visibility: setting.PrivateVisibility, - TemplateID: templateID, - } - _, err = CreateServiceTemplate(username, service, logger) - if err != nil { - logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) - } - return err -} - -func ReloadServiceFromYamlTemplate(username, projectName, serviceName, templateID string, variables []*Variable, logger *zap.SugaredLogger) error { - service, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ - ServiceName: serviceName, - ProductName: projectName, - }) - if err != nil { - logger.Errorf("Cannot find service of name [%s] from project [%s], the error is: %s", serviceName, projectName, err) - return err - } - if service.Source != setting.ServiceSourceTemplate { - return errors.New("service is not created from template") - } - if templateID == "" { - return errors.New("template id can't be nil") - } - template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) - if err != nil { - logger.Errorf("Failed to find template of ID: %s, the error is: %s", templateID, err) - return err - } - renderedYaml := renderYamlFromTemplate(template.Content, projectName, serviceName, variables) - svc := &commonmodels.Service{ - ServiceName: serviceName, - Type: setting.K8SDeployType, - ProductName: projectName, - Source: setting.ServiceSourceTemplate, - Yaml: renderedYaml, - Visibility: setting.PrivateVisibility, - TemplateID: templateID, - } - _, err = CreateServiceTemplate(username, svc, logger) - if err != nil { - logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) - } - return err -} - -func renderYamlFromTemplate(yaml, productName, serviceName string, variables []*Variable) string { - for _, variable := range variables { - yaml = strings.Replace(yaml, buildVariable(variable.Key), variable.Value, -1) - } - yaml = strings.Replace(yaml, setting.TemplateVariableProduct, productName, -1) - yaml = strings.Replace(yaml, setting.TemplateVariableService, serviceName, -1) - return yaml -} - -func buildVariable(key string) string { - return fmt.Sprintf("{{.%s}}", key) -} diff --git a/pkg/microservice/aslan/core/templatestore/handler/build.go b/pkg/microservice/aslan/core/templatestore/handler/build.go index 085c9f52..03c26228 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/build.go +++ b/pkg/microservice/aslan/core/templatestore/handler/build.go @@ -22,7 +22,7 @@ import ( "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" ) diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index fa466cb5..0ee1923d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -21,7 +21,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" "github.com/koderover/zadig/pkg/tool/errors" ) @@ -100,6 +100,13 @@ func UpdateChartTemplateVariables(c *gin.Context) { ctx.Err = templateservice.UpdateChartTemplateVariables(c.Param("name"), args, ctx.Logger) } +func SyncChartTemplateReference(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = templateservice.SyncHelmTemplateReference(c.Param("name"), ctx.Logger) +} + func RemoveChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index bf37f6be..455ee803 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -21,7 +21,8 @@ import ( "github.com/gin-gonic/gin" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -29,7 +30,7 @@ func CreateDockerfileTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &templateservice.DockerfileTemplate{} + req := &template.DockerfileTemplate{} if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err @@ -50,7 +51,7 @@ func UpdateDockerfileTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &templateservice.DockerfileTemplate{} + req := &template.DockerfileTemplate{} if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err @@ -73,8 +74,8 @@ type listDockerfileQuery struct { } type ListDockefileResp struct { - DockerfileTemplates []*templateservice.DockerfileListObject `json:"dockerfile_template"` - Total int `json:"total"` + DockerfileTemplates []*template.DockerfileListObject `json:"dockerfile_template"` + Total int `json:"total"` } func ListDockerfileTemplate(c *gin.Context) { @@ -101,7 +102,7 @@ func GetDockerfileTemplateDetail(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = templateservice.GetDockerfileTemplateDetail(c.Param("id"), ctx.Logger) + ctx.Resp, ctx.Err = template.GetDockerfileTemplateDetail(c.Param("id"), ctx.Logger) } func DeleteDockerfileTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/templatestore/handler/router.go b/pkg/microservice/aslan/core/templatestore/handler/router.go index 0b632774..926e1b7d 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/router.go +++ b/pkg/microservice/aslan/core/templatestore/handler/router.go @@ -26,16 +26,17 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- // chart templates // --------------------------------------------------------------------------------------- - install := router.Group("charts") + chart := router.Group("charts") { - install.GET("", ListChartTemplates) - install.GET("/:name", GetChartTemplate) - install.GET("/:name/files", ListFiles) - install.GET("/:name/variables", GetTemplateVariables) - install.POST("", AddChartTemplate) - install.PUT("/:name", UpdateChartTemplate) - install.PUT("/:name/variables", UpdateChartTemplateVariables) - install.DELETE("/:name", RemoveChartTemplate) + chart.GET("", ListChartTemplates) + chart.GET("/:name", GetChartTemplate) + chart.GET("/:name/files", ListFiles) + chart.GET("/:name/variables", GetTemplateVariables) + chart.POST("", AddChartTemplate) + chart.PUT("/:name", UpdateChartTemplate) + chart.POST("/:name/reference", SyncChartTemplateReference) + chart.PUT("/:name/variables", UpdateChartTemplateVariables) + chart.DELETE("/:name", RemoveChartTemplate) } dockerfile := router.Group("dockerfile") @@ -57,6 +58,7 @@ func (*Router) Inject(router *gin.RouterGroup) { yaml.GET("/:id", GetYamlTemplateDetail) yaml.DELETE("/:id", DeleteYamlTemplate) yaml.GET("/:id/reference", GetYamlTemplateReference) + yaml.POST("/:id/reference", SyncYamlTemplateReference) yaml.POST("/getVariables", GetYamlTemplateVariables) } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 9836f148..3be8dd68 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -18,9 +18,10 @@ package handler import ( "github.com/gin-gonic/gin" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + templateservice "github.com/koderover/zadig/pkg/microservice/aslan/core/templatestore/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) @@ -28,7 +29,7 @@ func CreateYamlTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &templateservice.YamlTemplate{} + req := &template.YamlTemplate{} if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err @@ -42,7 +43,7 @@ func UpdateYamlTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - req := &templateservice.YamlTemplate{} + req := &template.YamlTemplate{} if err := c.ShouldBindJSON(req); err != nil { ctx.Err = err @@ -58,9 +59,9 @@ type listYamlQuery struct { } type ListYamlResp struct { - SystemVariables []*commonmodels.ChartVariable `json:"system_variables"` - YamlTemplates []*templateservice.YamlListObject `json:"yaml_template"` - Total int `json:"total"` + SystemVariables []*commonmodels.ChartVariable `json:"system_variables"` + YamlTemplates []*template.YamlListObject `json:"yaml_template"` + Total int `json:"total"` } func ListYamlTemplate(c *gin.Context) { @@ -106,6 +107,13 @@ func GetYamlTemplateReference(c *gin.Context) { ctx.Resp, ctx.Err = templateservice.GetYamlTemplateReference(c.Param("id"), ctx.Logger) } +func SyncYamlTemplateReference(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = templateservice.SyncYamlTemplateReference(c.Param("id"), ctx.Logger) +} + type getYamlTemplateVariablesReq struct { Content string `json:"content"` } diff --git a/pkg/microservice/aslan/core/common/service/template/build.go b/pkg/microservice/aslan/core/templatestore/service/build.go similarity index 99% rename from pkg/microservice/aslan/core/common/service/template/build.go rename to pkg/microservice/aslan/core/templatestore/service/build.go index 1d29595d..eac282fb 100644 --- a/pkg/microservice/aslan/core/common/service/template/build.go +++ b/pkg/microservice/aslan/core/templatestore/service/build.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package template +package service import ( "go.uber.org/zap" diff --git a/pkg/microservice/aslan/core/common/service/template/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go similarity index 95% rename from pkg/microservice/aslan/core/common/service/template/chart.go rename to pkg/microservice/aslan/core/templatestore/service/chart.go index dda6f6f2..59c426fc 100644 --- a/pkg/microservice/aslan/core/common/service/template/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package template +package service import ( "fmt" @@ -37,6 +37,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/log" @@ -53,10 +55,10 @@ var ( type ChartTemplateListResp struct { SystemVariables []*commonmodels.ChartVariable `json:"systemVariables"` - ChartTemplates []*Chart `json:"chartTemplates"` + ChartTemplates []*template.Chart `json:"chartTemplates"` } -func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { +func GetChartTemplate(name string, logger *zap.SugaredLogger) (*template.Chart, error) { chart, err := commonrepo.NewChartColl().Get(name) if err != nil { logger.Errorf("Failed to get chart template %s, err: %s", name, err) @@ -85,7 +87,7 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*Chart, error) { }) } - return &Chart{ + return &template.Chart{ Name: name, CodehostID: chart.CodeHostID, Owner: chart.Owner, @@ -132,9 +134,9 @@ func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, erro return nil, err } - res := make([]*Chart, 0, len(cs)) + res := make([]*template.Chart, 0, len(cs)) for _, c := range cs { - res = append(res, &Chart{ + res = append(res, &template.Chart{ Name: c.Name, CodehostID: c.CodeHostID, Owner: c.Owner, @@ -297,7 +299,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z variables = append(variables, variable) } - return mongodb.NewChartColl().Update(&models.Chart{ + err = mongodb.NewChartColl().Update(&models.Chart{ Name: name, Owner: args.Owner, Repo: args.Repo, @@ -308,6 +310,12 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z ChartVariables: variables, Source: ch.Type, }) + + return err +} + +func SyncHelmTemplateReference(name string, logger *zap.SugaredLogger) error { + return service.SyncServiceFromTemplate(setting.SourceFromChartTemplate, "", name, logger) } func UpdateChartTemplateVariables(name string, args []*commonmodels.Variable, logger *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/templatestore/service/dockerfile.go b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go new file mode 100644 index 00000000..6b0282e0 --- /dev/null +++ b/pkg/microservice/aslan/core/templatestore/service/dockerfile.go @@ -0,0 +1,117 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "errors" + "strings" + + dockerfileinstructions "github.com/moby/buildkit/frontend/dockerfile/instructions" + "github.com/moby/buildkit/frontend/dockerfile/parser" + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" +) + +func CreateDockerfileTemplate(template *template.DockerfileTemplate, logger *zap.SugaredLogger) error { + err := commonrepo.NewDockerfileTemplateColl().Create(&commonmodels.DockerfileTemplate{ + Name: template.Name, + Content: template.Content, + }) + if err != nil { + logger.Errorf("create dockerfile template error: %s", err) + } + return err +} + +func UpdateDockerfileTemplate(id string, template *template.DockerfileTemplate, logger *zap.SugaredLogger) error { + err := commonrepo.NewDockerfileTemplateColl().Update( + id, + &commonmodels.DockerfileTemplate{ + Name: template.Name, + Content: template.Content, + }, + ) + if err != nil { + logger.Errorf("update dockerfile template error: %s", err) + } + return err +} + +func ListDockerfileTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*template.DockerfileListObject, int, error) { + resp := make([]*template.DockerfileListObject, 0) + templateList, total, err := commonrepo.NewDockerfileTemplateColl().List(pageNum, pageSize) + if err != nil { + logger.Errorf("list dockerfile template error: %s", err) + return resp, 0, err + } + for _, obj := range templateList { + resp = append(resp, &template.DockerfileListObject{ + ID: obj.ID.Hex(), + Name: obj.Name, + }) + } + return resp, total, err +} + +func DeleteDockerfileTemplate(id string, logger *zap.SugaredLogger) error { + ref, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for template id: %s, the error is: %s", id, err) + return err + } + if len(ref) > 0 { + return errors.New("this template is in use") + } + err = commonrepo.NewDockerfileTemplateColl().DeleteByID(id) + if err != nil { + logger.Errorf("Failed to delete dockerfile template of id: %s, the error is: %s", id, err) + } + return err +} + +func GetDockerfileTemplateReference(id string, logger *zap.SugaredLogger) ([]*template.BuildReference, error) { + ret := make([]*template.BuildReference, 0) + referenceList, err := commonrepo.NewBuildColl().GetDockerfileTemplateReference(id) + if err != nil { + logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) + return ret, err + } + for _, reference := range referenceList { + ret = append(ret, &template.BuildReference{ + BuildName: reference.Name, + ProjectName: reference.ProductName, + }) + } + return ret, nil +} + +func ValidateDockerfileTemplate(template string, _ *zap.SugaredLogger) error { + // some dockerfile validation stuff + reader := strings.NewReader(template) + result, err := parser.Parse(reader) + if err != nil { + return err + } + _, _, err = dockerfileinstructions.Parse(result.AST) + if err != nil { + return err + } + return nil +} diff --git a/pkg/microservice/aslan/core/common/service/template/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go similarity index 84% rename from pkg/microservice/aslan/core/common/service/template/yaml.go rename to pkg/microservice/aslan/core/templatestore/service/yaml.go index 006496b8..06c9e5ab 100644 --- a/pkg/microservice/aslan/core/common/service/template/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package template +package service import ( "errors" @@ -25,6 +25,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" ) @@ -33,7 +35,7 @@ var DefaultSystemVariable = map[string]string{ setting.TemplateVariableService: setting.TemplateVariableServiceDescription, } -func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error { +func CreateYamlTemplate(template *template.YamlTemplate, logger *zap.SugaredLogger) error { vars := make([]*models.Variable, 0) for _, variable := range template.Variable { vars = append(vars, &models.Variable{ @@ -52,7 +54,7 @@ func CreateYamlTemplate(template *YamlTemplate, logger *zap.SugaredLogger) error return err } -func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLogger) error { +func UpdateYamlTemplate(id string, template *template.YamlTemplate, logger *zap.SugaredLogger) error { vars := make([]*models.Variable, 0) for _, variable := range template.Variable { vars = append(vars, &models.Variable{ @@ -74,15 +76,15 @@ func UpdateYamlTemplate(id string, template *YamlTemplate, logger *zap.SugaredLo return err } -func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*YamlListObject, int, error) { - resp := make([]*YamlListObject, 0) +func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*template.YamlListObject, int, error) { + resp := make([]*template.YamlListObject, 0) templateList, total, err := commonrepo.NewYamlTemplateColl().List(pageNum, pageSize) if err != nil { logger.Errorf("list dockerfile template error: %s", err) return resp, 0, err } for _, obj := range templateList { - resp = append(resp, &YamlListObject{ + resp = append(resp, &template.YamlListObject{ ID: obj.ID.Hex(), Name: obj.Name, }) @@ -90,8 +92,8 @@ func ListYamlTemplate(pageNum, pageSize int, logger *zap.SugaredLogger) ([]*Yaml return resp, total, err } -func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*YamlDetail, error) { - resp := new(YamlDetail) +func GetYamlTemplateDetail(id string, logger *zap.SugaredLogger) (*template.YamlDetail, error) { + resp := new(template.YamlDetail) yamlTemplate, err := commonrepo.NewYamlTemplateColl().GetById(id) if err != nil { logger.Errorf("Failed to get dockerfile template from id: %s, the error is: %s", id, err) @@ -127,15 +129,19 @@ func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { return err } -func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*ServiceReference, error) { - ret := make([]*ServiceReference, 0) +func SyncYamlTemplateReference(id string, logger *zap.SugaredLogger) error { + return service.SyncServiceFromTemplate(setting.ServiceSourceTemplate, id, "", logger) +} + +func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*template.ServiceReference, error) { + ret := make([]*template.ServiceReference, 0) referenceList, err := commonrepo.NewServiceColl().GetTemplateReference(id) if err != nil { logger.Errorf("Failed to get build reference for dockerfile template id: %s, the error is: %s", id, err) return ret, err } for _, reference := range referenceList { - ret = append(ret, &ServiceReference{ + ret = append(ret, &template.ServiceReference{ ServiceName: reference.ServiceName, ProjectName: reference.ProductName, }) diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml index 0db27c5a..f3c098d0 100644 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml @@ -152,6 +152,10 @@ # 修改 chart 模版变量 - method: PUT endpoint: "api/aslan/template/charts/?*/variables" + - method: POST + endpoint: "api/aslan/template/charts/?*/reference" + - method: POST + endpoint: "api/aslan/template/yaml/?*/reference" - method: PUT endpoint: "/api/aslan/template/dockerfile/?*" - method: PUT -- Gitee From 7aea1c304a1718f45ce653b3e8fb8ce59b579f45 Mon Sep 17 00:00:00 2001 From: flyer Date: Fri, 6 May 2022 21:36:07 +0800 Subject: [PATCH 2327/2647] support trace id of SkyWalking, Jaeger and Zipkin (#1468) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- .../core/environment/service/share_env.go | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 1e00e350..07447780 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -663,13 +663,32 @@ func ensureEnvoyFilter(ctx context.Context, istioClient versionedclient.Interfac func buildEnvoyStoreCacheOperation() (*types.Struct, error) { inlineCode := `function envoy_on_request(request_handle) - local requestID = request_handle:headers():get("x-request-id") + function split_str(s, delimiter) + res = {} + for match in (s..delimiter):gmatch("(.-)"..delimiter) do + table.insert(res, match) + end + + return res + end + + local traceid = request_handle:headers():get("sw8") + if traceid then + arr = split_str(traceid, "-") + traceid = arr[2] + else + traceid = request_handle:headers():get("x-request-id") + if not traceid then + traceid = request_handle:headers():get("x-b3-traceid") + end + end + local env = request_handle:headers():get("x-env") local headers, body = request_handle:httpCall( "cache", { [":method"] = "POST", - [":path"] = string.format("/api/cache/%s/%s", requestID, env), + [":path"] = string.format("/api/cache/%s/%s", traceid, env), [":authority"] = "cache", }, "", @@ -690,12 +709,32 @@ end func buildEnvoyGetCacheOperation() (*types.Struct, error) { inlineCode := `function envoy_on_request(request_handle) - local requestID = request_handle:headers():get("x-request-id") + function split_str(s, delimiter) + res = {} + for match in (s..delimiter):gmatch("(.-)"..delimiter) do + table.insert(res, match) + end + + return res + end + + local traceid = request_handle:headers():get("sw8") + request_handle:logWarn(string.format("[inbound]sw8: %s", traceid)) + if traceid then + arr = split_str(traceid, "-") + traceid = arr[2] + else + traceid = request_handle:headers():get("x-request-id") + if not traceid then + traceid = request_handle:headers():get("x-b3-traceid") + end + end + local headers, body = request_handle:httpCall( "cache", { [":method"] = "GET", - [":path"] = string.format("/api/cache/%s", requestID), + [":path"] = string.format("/api/cache/%s", traceid), [":authority"] = "cache", }, "", -- Gitee From b26d23e972d16b84977edcf5aa199375e3948954 Mon Sep 17 00:00:00 2001 From: flyer Date: Fri, 6 May 2022 21:47:28 +0800 Subject: [PATCH 2328/2647] remove useless log in EnvoyFilter (#1471) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/aslan/core/environment/service/share_env.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 07447780..1dee5270 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -719,7 +719,6 @@ func buildEnvoyGetCacheOperation() (*types.Struct, error) { end local traceid = request_handle:headers():get("sw8") - request_handle:logWarn(string.format("[inbound]sw8: %s", traceid)) if traceid then arr = split_str(traceid, "-") traceid = arr[2] -- Gitee From 8016bf4f2d62a509dd86f0c53b80bbf3845b56a7 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Sat, 7 May 2022 15:25:20 +0800 Subject: [PATCH 2329/2647] service global variable enhancement (#1408) * Service global variable enhancement Signed-off-by: ddh27 * Service global variable enhancement Signed-off-by: ddh27 --- .../aslan/core/service/handler/router.go | 1 + .../aslan/core/service/handler/service.go | 18 +++++ .../aslan/core/service/service/service.go | 67 +++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 4715ddcd..b3527136 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -53,6 +53,7 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) k8s.PUT("/yaml/validator", YamlValidator) + k8s.PUT("/:name/yaml/view", YamlViewServiceTemplate) k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) k8s.GET("/:name/environments/deployable", GetDeployableEnvs) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 9aeea81f..21007682 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -148,6 +148,24 @@ func YamlValidator(c *gin.Context) { ctx.Resp = resp } +func YamlViewServiceTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(svcservice.YamlViewServiceTemplateReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") + return + } + args.ProjectName = c.Query("projectName") + args.ServiceName = c.Param("name") + if args.ProjectName == "" || args.ServiceName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName or serviceName can't be nil") + return + } + ctx.Resp, ctx.Err = svcservice.YamlViewServiceTemplate(args) +} + func HelmReleaseNaming(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index ffc791ec..c6725e0c 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "os" "path" + "regexp" "strconv" "strings" "sync" @@ -42,6 +43,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -135,6 +137,13 @@ type YamlValidatorReq struct { Yaml string `json:"yaml,omitempty"` } +type YamlViewServiceTemplateReq struct { + ServiceName string `json:"service_name"` + ProjectName string `json:"project_name"` + EnvName string `json:"env_name"` + Variables []*templatemodels.RenderKV `json:"variables"` +} + type ReleaseNamingRule struct { NamingRule string `json:"naming"` ServiceName string `json:"service_name"` @@ -958,6 +967,64 @@ func YamlValidator(args *YamlValidatorReq) []string { return errorDetails } +func YamlViewServiceTemplate(args *YamlViewServiceTemplateReq) (string, error) { + opt := &commonrepo.ServiceFindOption{ + ServiceName: args.ServiceName, + ProductName: args.ProjectName, + ExcludeStatus: setting.ProductStatusDeleting, + } + svcTmpl, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + return "", err + } + + renderSet := new(commonmodels.RenderSet) + renderSet.KVs = args.Variables + parsedYaml := commonservice.RenderValueForString(svcTmpl.Yaml, renderSet) + if args.EnvName != "" { + prod, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProjectName, + EnvName: args.EnvName, + }) + if err != nil { + return "", err + } + + cerSvc := prod.GetServiceMap() + svcInfo, found := cerSvc[args.ServiceName] + if !found { + return "", fmt.Errorf("services %s is not exist in env %s", args.ServiceName, prod.Namespace) + } + parsedYaml = kube.ParseSysKeys(prod.Namespace, prod.EnvName, prod.ProductName, args.ServiceName, parsedYaml) + parsedYaml, err = replaceContainerImages(parsedYaml, svcTmpl.Containers, svcInfo.Containers) + if err != nil { + return "", err + } + } + + return parsedYaml, nil +} + +func replaceContainerImages(tmpl string, ori []*commonmodels.Container, replace []*commonmodels.Container) (string, error) { + replaceMap := make(map[string]string) + for _, container := range replace { + replaceMap[container.Name] = container.Image + } + + for _, container := range ori { + imageRex, err := regexp.Compile("image:\\s*" + container.Image) + if err != nil { + return "", err + } + if _, ok := replaceMap[container.Name]; !ok { + continue + } + tmpl = imageRex.ReplaceAllLiteralString(tmpl, fmt.Sprintf("image: %s", replaceMap[container.Name])) + } + + return tmpl, nil +} + func UpdateReleaseNamingRule(userName, requestID, projectName string, args *ReleaseNamingRule, log *zap.SugaredLogger) error { serviceTemplate, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ ServiceName: args.ServiceName, -- Gitee From 26553962612a40b67b77452d2b9f9de1fd2557b8 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Sat, 7 May 2022 15:25:45 +0800 Subject: [PATCH 2330/2647] fix typo (#1475) Signed-off-by: fansi Co-authored-by: fansi --- pkg/microservice/aslan/core/environment/handler/policy.yaml | 2 +- pkg/tool/errors/http_errors.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 9ec31699..72ed925f 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -342,7 +342,7 @@ rules: - key: "production" value: "false" - action: ssh_pm - alias: "主机登陆" + alias: "主机登录" description: "" rules: - method: GET diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 77f42ec6..f5a63149 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -200,7 +200,7 @@ var ( //ErrDeleteResource ... ErrDeleteResource = NewHTTPError(6096, "删除对象资源失败") // ErrLoginPm ... - ErrLoginPm = NewHTTPError(6099, "登陆主机失败") + ErrLoginPm = NewHTTPError(6099, "登录主机失败") ErrDeleteSvcHasSvcsInSubEnv = NewHTTPError(6094, "删除服务失败,待删除服务存在于子环境中") -- Gitee From c7d9afe5104d2ac9eeb68a3881bce84e7e89b337 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Sat, 7 May 2022 16:09:05 +0800 Subject: [PATCH 2331/2647] service auto deploy into envs (#1470) * add basic logic to auto deploy Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * resolve comments Signed-off-by: allenshen --- .../repository/models/template/product.go | 5 + .../repository/mongodb/template/product.go | 1 + .../environment/service/environment_update.go | 173 +++++++++++++++--- .../aslan/core/project/handler/product.go | 2 +- .../aslan/core/project/service/product.go | 10 +- .../aslan/core/service/handler/helm.go | 4 +- .../aslan/core/service/service/helm.go | 21 ++- .../aslan/core/service/service/service.go | 9 +- .../aslan/core/service/service/types.go | 2 + .../core/workflow/service/webhook/gerrit.go | 6 +- .../core/workflow/service/webhook/utils.go | 3 +- 11 files changed, 195 insertions(+), 41 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index ee53ac5b..be499350 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -33,6 +33,7 @@ type Product struct { UpdateBy string `bson:"update_by" json:"update_by"` Enabled bool `bson:"enabled" json:"enabled"` Visibility string `bson:"visibility" json:"visibility"` + AutoDeploy *AutoDeployPolicy `bson:"auto_deploy" json:"auto_deploy"` Timeout int `bson:"timeout,omitempty" json:"timeout,omitempty"` Services [][]string `bson:"services" json:"services"` SharedServices []*ServiceInfo `bson:"shared_services,omitempty" json:"shared_services,omitempty"` @@ -162,6 +163,10 @@ type DeliveryVersionHook struct { Path string `bson:"path" json:"path"` } +type AutoDeployPolicy struct { + Enable bool `bson:"enable" json:"enable"` +} + func (Product) TableName() string { return "template_product" } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 553452ff..f9e5525b 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -274,6 +274,7 @@ func (c *ProductColl) Update(productName string, args *template.Product) error { "enabled": args.Enabled, "description": args.Description, "timeout": args.Timeout, + "auto_deploy": args.AutoDeploy, "shared_services": args.SharedServices, "image_searching_rules": args.ImageSearchingRules, "custom_tar_rule": args.CustomTarRule, diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index eb1ca037..3ccdad3d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -26,6 +26,8 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -79,9 +81,30 @@ func InstallService(helmClient *helmtool.HelmClient, param *ReleaseInstallParam) return nil } -// ReInstallServiceInEnv uninstall the service and reinstall to update releaseNaming rule -func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string, templateSvc *commonmodels.Service) error { - productSvcMap := productInfo.GetServiceMap() +func updateServiceRevisionInProduct(productInfo *commonmodels.Product, serviceName string, serviceRevision int64) error { + foundSvc := false + // update service revision data in product + for groupIndex, svcGroup := range productInfo.Services { + for _, svc := range svcGroup { + if svc.ServiceName == serviceName { + svc.Revision = serviceRevision + foundSvc = true + break + } + } + if foundSvc { + if err := commonrepo.NewProductColl().UpdateGroup(productInfo.EnvName, productInfo.ProductName, groupIndex, svcGroup); err != nil { + return fmt.Errorf("failed to update service group: %s:%s, err: %s ", productInfo.ProductName, productInfo.EnvName, err) + } + break + } + } + return nil +} + +// reInstallHelmServiceInEnv uninstall the service and reinstall to update releaseNaming rule +func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { + productSvcMap, serviceName := productInfo.GetServiceMap(), templateSvc.ServiceName productSvc, ok := productSvcMap[serviceName] // service not applied in this environment if !ok { @@ -92,7 +115,7 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string Name: productInfo.Render.Name, Revision: productInfo.Render.Revision}) if err != nil { - return fmt.Errorf("failed to find renderset, %s:%d", productInfo.Render.Name, productInfo.Render.Revision) + return fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, err) } var renderChart *templatemodels.RenderChart @@ -134,29 +157,101 @@ func ReInstallServiceInEnv(productInfo *commonmodels.Product, serviceName string return fmt.Errorf("fauled to install release for service: %s, err: %s", templateSvc.ServiceName, err) } - foundSvc := false - // update service revision data in product - for groupIndex, svcGroup := range productInfo.Services { - for _, svc := range svcGroup { - if svc.ServiceName == serviceName { - svc.Revision = templateSvc.Revision - foundSvc = true - break - } + return updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) +} + +func updateHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { + productSvcMap, serviceName := productInfo.GetServiceMap(), templateSvc.ServiceName + // service not applied in this environment + if _, ok := productSvcMap[serviceName]; !ok { + return nil + } + + renderInfo, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision}) + if err != nil { + return fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, err) + } + + var renderChart *templatemodels.RenderChart + for _, _renderChart := range renderInfo.ChartInfos { + if _renderChart.ServiceName == serviceName { + renderChart = _renderChart } - if foundSvc { - if err = commonrepo.NewProductColl().UpdateGroup(productInfo.EnvName, productInfo.ProductName, groupIndex, svcGroup); err != nil { - return fmt.Errorf("failed to update service group: %s:%s, err: %s ", productInfo.ProductName, productInfo.EnvName, err) - } - break + } + if renderChart == nil { + return fmt.Errorf("failed to find renderchart for service: %s", renderChart.ServiceName) + } + + helmClient, err := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) + if err != nil { + return err + } + + param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) + if err != nil { + return fmt.Errorf("failed to generate install param, service: %s err: %s", templateSvc.ServiceName, err) + } + + err = InstallService(helmClient, param) + if err != nil { + return fmt.Errorf("fauled to install release for service: %s, err: %s", templateSvc.ServiceName, err) + } + + return updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) +} + +func updateK8sServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { + productSvcMap, serviceName := productInfo.GetServiceMap(), templateSvc.ServiceName + // service not applied in this environment + if _, ok := productSvcMap[serviceName]; !ok { + return nil + } + return UpdateProductV2(productInfo.EnvName, productInfo.ProductName, "", "", []string{templateSvc.ServiceName}, false, nil, log.SugaredLogger()) +} + +// ReInstallHelmSvcInAllEnvs reinstall svc in all envs in which the svc is already installed +func ReInstallHelmSvcInAllEnvs(productName string, templateSvc *commonmodels.Service) error { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + }) + if err != nil { + return fmt.Errorf("failed to list envs for product: %s, err: %s", productName, err) + } + retErr := &multierror.Error{} + for _, product := range products { + err := reInstallHelmServiceInEnv(product, templateSvc) + if err != nil { + retErr = multierror.Append(retErr, fmt.Errorf("failed to update service: %s/%s, err: %s", product.EnvName, templateSvc.ServiceName, err)) + } } + return retErr.ErrorOrNil() +} - return nil +// updateHelmSvcInAllEnvs updates helm svc in all envs in which the svc is already installed +func updateHelmSvcInAllEnvs(productName string, templateSvcs []*commonmodels.Service) error { + products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: productName, + }) + if err != nil { + return fmt.Errorf("failed to list envs for product: %s, err: %s", productName, err) + } + retErr := &multierror.Error{} + for _, product := range products { + for _, templateSvc := range templateSvcs { + err := updateHelmServiceInEnv(product, templateSvc) + if err != nil { + retErr = multierror.Append(retErr, fmt.Errorf("failed to update service: %s/%s, err: %s", product.EnvName, templateSvc.ServiceName, err)) + } + } + } + return retErr.ErrorOrNil() } -// UpdateSvcInAllEnvs updates svc in all envs in which the svc is already installed -func UpdateSvcInAllEnvs(productName, serviceName string, templateSvc *commonmodels.Service) error { +// updateK8sSvcInAllEnvs updates k8s svc in all envs in which the svc is already installed +func updateK8sSvcInAllEnvs(productName string, templateSvc *commonmodels.Service) error { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ Name: productName, }) @@ -165,10 +260,44 @@ func UpdateSvcInAllEnvs(productName, serviceName string, templateSvc *commonmode } retErr := &multierror.Error{} for _, product := range products { - err := ReInstallServiceInEnv(product, serviceName, templateSvc) + err := updateK8sServiceInEnv(product, templateSvc) if err != nil { retErr = multierror.Append(retErr, err) } } return retErr.ErrorOrNil() } + +func AutoDeployHelmServiceToEnvs(userName, requestID, projectName string, serviceTemplates []*commonmodels.Service, log *zap.SugaredLogger) error { + templateProduct, err := templaterepo.NewProductColl().Find(projectName) + if err != nil { + return fmt.Errorf("failed to find template product when depolying services: %s, err: %s", projectName, err) + } + if templateProduct.AutoDeploy == nil || !templateProduct.AutoDeploy.Enable { + return nil + } + go func() { + err = updateHelmSvcInAllEnvs(projectName, serviceTemplates) + if err != nil { + commonservice.SendErrorMessage(userName, "服务自动部署失败", requestID, err, log) + } + }() + return nil +} + +func AutoDeployYamlServiceToEnvs(userName, requestID string, serviceTemplate *commonmodels.Service, log *zap.SugaredLogger) error { + templateProduct, err := templaterepo.NewProductColl().Find(serviceTemplate.ProductName) + if err != nil { + return fmt.Errorf("failed to find template product when depolying services: %s, err: %s", serviceTemplate.ServiceName, err) + } + if templateProduct.AutoDeploy == nil || !templateProduct.AutoDeploy.Enable { + return nil + } + go func() { + err = updateK8sSvcInAllEnvs(serviceTemplate.ProductName, serviceTemplate) + if err != nil { + commonservice.SendErrorMessage(userName, "服务自动部署失败", requestID, err, log) + } + }() + return nil +} diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 93871a8f..82202b23 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -250,5 +250,5 @@ func CreateOrUpdateMatchRules(c *gin.Context) { return } - ctx.Err = projectservice.UpdateCustomMatchRules(c.Param("name"), ctx.UserName, args.Rules) + ctx.Err = projectservice.UpdateCustomMatchRules(c.Param("name"), ctx.UserName, ctx.RequestID, args.Rules) } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 3b1dec19..cbc5d3fa 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -815,7 +815,7 @@ func GetCustomMatchRules(productName string, log *zap.SugaredLogger) ([]*ImagePa return ret, nil } -func UpdateCustomMatchRules(productName string, userName string, matchRules []*ImageParseData) error { +func UpdateCustomMatchRules(productName, userName, requestID string, matchRules []*ImageParseData) error { productInfo, err := templaterepo.NewProductColl().Find(productName) if err != nil { log.Errorf("query product:%s fail, err:%s", productName, err.Error()) @@ -857,7 +857,7 @@ func UpdateCustomMatchRules(productName string, userName string, matchRules []*I if err != nil { return err } - err = reParseServices(userName, services, imageRulesToSave) + err = reParseServices(userName, requestID, services, imageRulesToSave) if err != nil { return err } @@ -872,15 +872,17 @@ func UpdateCustomMatchRules(productName string, userName string, matchRules []*I } // reparse values.yaml for each service -func reParseServices(userName string, serviceList []*commonmodels.Service, matchRules []*template.ImageSearchingRule) error { +func reParseServices(userName, requestID string, serviceList []*commonmodels.Service, matchRules []*template.ImageSearchingRule) error { updatedServiceTmpls := make([]*commonmodels.Service, 0) var err error + var projectName string for _, serviceTmpl := range serviceList { if serviceTmpl.Type != setting.HelmDeployType || serviceTmpl.HelmChart == nil { continue } valuesYaml := serviceTmpl.HelmChart.ValuesYaml + projectName = serviceTmpl.ProductName valuesMap := make(map[string]interface{}) err = yaml.Unmarshal([]byte(valuesYaml), &valuesMap) @@ -931,7 +933,7 @@ func reParseServices(userName string, serviceList []*commonmodels.Service, match return err } - return nil + return environmentservice.AutoDeployHelmServiceToEnvs(userName, requestID, projectName, updatedServiceTmpls, log.SugaredLogger()) } func DeleteCollabrationMode(productName string, userName string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index ae188c28..4042cd21 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -87,7 +87,7 @@ func CreateOrUpdateHelmService(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") return } - args.CreatedBy = ctx.UserName + args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, ctx.Logger) } @@ -107,7 +107,7 @@ func CreateOrUpdateBulkHelmServices(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") return } - args.CreatedBy = ctx.UserName + args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(projectName, args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 3381f447..6f96b6ec 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -46,6 +46,7 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" @@ -96,6 +97,7 @@ type helmServiceCreationArgs struct { FilePath string ProductName string CreateBy string + RequestID string CodehostID int Owner string Repo string @@ -479,6 +481,7 @@ func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServic ServiceName: serviceName, ProductName: projectName, CreateBy: args.CreatedBy, + RequestID: args.RequestID, Source: setting.SourceFromChartRepo, }, log, @@ -599,6 +602,7 @@ func createOrUpdateHelmServiceFromChartTemplate(templateArgs *CreateFromChartTem FilePath: to, ProductName: projectName, CreateBy: args.CreatedBy, + RequestID: args.RequestID, Source: setting.SourceFromChartTemplate, HelmTemplateName: templateArgs.TemplateName, ValuesYaml: templateArgs.ValuesYAML, @@ -737,6 +741,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr FilePath: filePath, ProductName: projectName, CreateBy: args.CreatedBy, + RequestID: args.RequestID, CodehostID: createFromRepo.CodehostID, Owner: createFromRepo.Owner, Repo: createFromRepo.Repo, @@ -886,6 +891,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC FilePath: filePath, ProductName: projectName, CreateBy: args.CreatedBy, + RequestID: args.RequestID, CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, @@ -954,7 +960,7 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel wg.Add(1) go func(repoConfig *commonservice.RepoConfig, path string) { defer wg.Done() - renderChart, err := handleSingleService(projectName, repoConfig, args, path, from, templateChartData, logger) + renderChart, err := handleSingleService(projectName, repoConfig, path, from, args, templateChartData, logger) if err != nil { failedServiceMap.Store(path, err.Error()) } else { @@ -991,8 +997,8 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel return resp, nil } -func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, args *BulkHelmServiceCreationArgs, path, fromPath string, templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { - createBy, valuesData := args.CreatedBy, args.ValuesData +func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, path, fromPath string, args *BulkHelmServiceCreationArgs, + templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, Owner: repoConfig.Owner, @@ -1073,14 +1079,15 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi ServiceName: serviceName, FilePath: to, ProductName: projectName, - CreateBy: createBy, + CreateBy: args.CreatedBy, + RequestID: args.RequestID, CodehostID: repoConfig.CodehostID, Source: setting.SourceFromChartTemplate, HelmTemplateName: templateChartData.TemplateName, ValuePaths: []string{path}, ValuesYaml: string(valuesYAML), - ValuesSource: valuesData, AutoSync: args.AutoSync, + ValuesSource: args.ValuesData, }, logger, ) @@ -1345,7 +1352,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - return serviceObj, nil + return serviceObj, service.AutoDeployHelmServiceToEnvs(args.CreateBy, args.RequestID, serviceObj.ProductName, []*models.Service{serviceObj}, logger) } func loadServiceFileInfos(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { @@ -1404,7 +1411,7 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s return fis, nil } -// UpdateHelmService TODO need to be deprecated +// UpdateHelmService deprecated func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { serviceMap := make(map[string]int64) for _, helmServiceInfo := range args.HelmServiceInfos { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index c6725e0c..2c87bc14 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -791,6 +791,11 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap } commonservice.ProcessServiceWebhook(args, serviceTmpl, args.ServiceName, log) + err = service.AutoDeployYamlServiceToEnvs(userName, "", args, log) + if err != nil { + return nil, e.ErrCreateTemplate.AddErr(err) + } + return GetServiceOption(args, log) } @@ -1083,14 +1088,14 @@ func UpdateReleaseNamingRule(userName, requestID, projectName string, args *Rele go func() { // reinstall services in envs - err = service.UpdateSvcInAllEnvs(projectName, args.ServiceName, serviceTemplate) + err = service.ReInstallHelmSvcInAllEnvs(projectName, serviceTemplate) if err != nil { title := fmt.Sprintf("服务 [%s] 重建失败", args.ServiceName) commonservice.SendErrorMessage(userName, title, requestID, err, log) } }() - return nil + return service.AutoDeployHelmServiceToEnvs(userName, requestID, serviceTemplate.ProductName, []*commonmodels.Service{serviceTemplate}, log) } func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, visibility string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index ef96e2ae..7dc605b5 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -42,6 +42,7 @@ type HelmServiceCreationArgs struct { HelmLoadSource Name string `json:"name"` CreatedBy string `json:"createdBy"` + RequestID string `json:"-"` AutoSync bool `json:"auto_sync"` CreateFrom interface{} `json:"createFrom"` ValuesData *service.ValuesDataArgs `json:"valuesData"` @@ -52,6 +53,7 @@ type BulkHelmServiceCreationArgs struct { HelmLoadSource CreateFrom interface{} `json:"createFrom"` CreatedBy string `json:"createdBy"` + RequestID string `json:"-"` ValuesData *service.ValuesDataArgs `json:"valuesData"` AutoSync bool `json:"auto_sync"` } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index a6d51c3b..70a82815 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -38,6 +38,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" gerritservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/gerrit" + environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -185,7 +186,7 @@ func GetGerritServiceTemplates() ([]*commonmodels.Service, error) { return commonrepo.NewServiceColl().ListMaxRevisions(opt) } -// SyncServiceTemplateFromGerrit Force to sync Service Template to latest commit and content, +// SyncServiceTemplateFromGerrit Force to sync Service Template to the latest commit and content, // Notes: if remains the same, quit sync; if updates, revision +1 func SyncServiceTemplateFromGerrit(service *commonmodels.Service, log *zap.SugaredLogger) error { if service.Source != setting.SourceFromGerrit { @@ -209,7 +210,8 @@ func SyncServiceTemplateFromGerrit(service *commonmodels.Service, log *zap.Sugar log.Errorf("SyncServiceTemplateFromGerrit Failed to sync service %s from gerrit path %s error: %v", service.ServiceName, service.SrcPath, err) return e.ErrCreateTemplate.AddDesc(err.Error()) } - return nil + + return environmentservice.AutoDeployYamlServiceToEnvs(service.CreateBy, "", service, log) } // remove old repo dir oldRepoDir := filepath.Join(config.S3StoragePath(), service.GerritRepoName) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index c845528c..46ddddcf 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -38,6 +38,7 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" + environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -159,13 +160,13 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar if err != nil { return fmt.Errorf("get next service template revision error: %v", err) } - args.Revision = rev // update service template if err := commonrepo.NewServiceColl().Create(args); err != nil { log.Errorf("Failed to sync service %s from github path %s error: %v", args.ServiceName, args.SrcPath, err) return e.ErrCreateTemplate.AddDesc(err.Error()) } + return environmentservice.AutoDeployYamlServiceToEnvs(args.CreateBy, "", args, log) } else if args.Type == setting.HelmDeployType { if args.Source == setting.SourceFromGitlab { // Set args.Commit -- Gitee From b32fd727b484be087bc344f915e4cdb71092a247 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Mon, 9 May 2022 11:15:05 +0800 Subject: [PATCH 2332/2647] added the Spring Boot service in self-test co-commissioning mode (#1477) * Added the Spring Boot service in self-test co-commissioning mode Signed-off-by: ddh27 * Added the Spring Boot service in self-test co-commissioning mode Signed-off-by: ddh27 --- .../acme-financial-account.yaml | 78 +++++++++++++++++++ .../acme-financial-customer.yaml | 77 ++++++++++++++++++ .../acme-financial-office.yaml | 77 ++++++++++++++++++ .../acme-financial-ui.yaml | 77 ++++++++++++++++++ 4 files changed, 309 insertions(+) create mode 100644 examples/spring-boot-acme-financial/acme-financial-account.yaml create mode 100644 examples/spring-boot-acme-financial/acme-financial-customer.yaml create mode 100644 examples/spring-boot-acme-financial/acme-financial-office.yaml create mode 100644 examples/spring-boot-acme-financial/acme-financial-ui.yaml diff --git a/examples/spring-boot-acme-financial/acme-financial-account.yaml b/examples/spring-boot-acme-financial/acme-financial-account.yaml new file mode 100644 index 00000000..c7180608 --- /dev/null +++ b/examples/spring-boot-acme-financial/acme-financial-account.yaml @@ -0,0 +1,78 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: acme-financial-account + name: acme-financial-account +spec: + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: acme-financial-account + template: + metadata: + labels: + app: acme-financial-account + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 100m + memory: 256Mi + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar" + - name: SW_AGENT_NAME + value: "acme-financial-account" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-account:v0.1 + imagePullPolicy: Always + name: account + ports: + - containerPort: 8083 + protocol: TCP + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 100m + memory: 512Mi + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + volumes: + - name: skywalking-agent + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: acme-financial-account + name: acme-financial-account +spec: + ports: + - name: http + port: 8083 + protocol: TCP + targetPort: 8083 + selector: + app: acme-financial-account + sessionAffinity: None + type: ClusterIP \ No newline at end of file diff --git a/examples/spring-boot-acme-financial/acme-financial-customer.yaml b/examples/spring-boot-acme-financial/acme-financial-customer.yaml new file mode 100644 index 00000000..11e93bf7 --- /dev/null +++ b/examples/spring-boot-acme-financial/acme-financial-customer.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: acme-financial-customer + name: acme-financial-customer +spec: + replicas: 1 + selector: + matchLabels: + app: acme-financial-customer + template: + metadata: + labels: + app: acme-financial-customer + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 100m + memory: 256Mi + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar" + - name: SW_AGENT_NAME + value: "acme-financial-customer" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-customer:v0.1 + imagePullPolicy: Always + name: customer + ports: + - containerPort: 8084 + protocol: TCP + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 100m + memory: 512Mi + volumes: + - name: skywalking-agent + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: acme-financial-customer + name: acme-financial-customer +spec: + ports: + - name: http + port: 8084 + protocol: TCP + targetPort: 8084 + selector: + app: acme-financial-customer + sessionAffinity: None + type: ClusterIP \ No newline at end of file diff --git a/examples/spring-boot-acme-financial/acme-financial-office.yaml b/examples/spring-boot-acme-financial/acme-financial-office.yaml new file mode 100644 index 00000000..f7d97bdd --- /dev/null +++ b/examples/spring-boot-acme-financial/acme-financial-office.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: acme-financial-office + name: acme-financial-office +spec: + replicas: 1 + selector: + matchLabels: + app: acme-financial-office + template: + metadata: + labels: + app: acme-financial-office + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar" + - name: SW_AGENT_NAME + value: "acme-financial-office" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-office:v0.1 + imagePullPolicy: Always + name: office + ports: + - containerPort: 8082 + protocol: TCP + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 100m + memory: 512Mi + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + volumes: + - name: skywalking-agent + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: acme-financial-office + name: acme-financial-back-office +spec: + ports: + - name: http + port: 8082 + protocol: TCP + targetPort: 8082 + selector: + app: acme-financial-office + sessionAffinity: None + type: ClusterIP \ No newline at end of file diff --git a/examples/spring-boot-acme-financial/acme-financial-ui.yaml b/examples/spring-boot-acme-financial/acme-financial-ui.yaml new file mode 100644 index 00000000..c5508061 --- /dev/null +++ b/examples/spring-boot-acme-financial/acme-financial-ui.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: acme-financial-ui + name: acme-financial-ui +spec: + replicas: 1 + selector: + matchLabels: + app: acme-financial-ui + template: + metadata: + labels: + app: acme-financial-ui + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + resources: + limits: + cpu: 100m + memory: 256Mi + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar" + - name: SW_AGENT_NAME + value: "acme-financial-ui" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-ui:v0.1 + imagePullPolicy: Always + name: ui + ports: + - containerPort: 8081 + protocol: TCP + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 100m + memory: 512Mi + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + volumes: + - name: skywalking-agent + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: acme-financial-ui + name: acme-financial-ui +spec: + ports: + - name: http + port: 8081 + protocol: TCP + targetPort: 8081 + selector: + app: acme-financial-ui + sessionAffinity: None + type: NodePort \ No newline at end of file -- Gitee From e9528be806b68f316fabfff773d9a6beb17fbedd Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 9 May 2022 16:11:34 +0800 Subject: [PATCH 2333/2647] fix service auto deploy bug (#1479) Signed-off-by: allenshen --- .../environment/service/environment_update.go | 22 +++++-- .../aslan/core/service/service/helm.go | 59 +++++++++++++------ .../aslan/core/service/service/service.go | 2 +- .../aslan/core/service/service/template.go | 22 +++---- .../aslan/core/templatestore/handler/chart.go | 2 +- .../aslan/core/templatestore/handler/yaml.go | 2 +- .../aslan/core/templatestore/service/chart.go | 4 +- .../aslan/core/templatestore/service/yaml.go | 4 +- 8 files changed, 76 insertions(+), 41 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 3ccdad3d..4a0a5a30 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -160,7 +160,7 @@ func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *c return updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) } -func updateHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { +func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { productSvcMap, serviceName := productInfo.GetServiceMap(), templateSvc.ServiceName // service not applied in this environment if _, ok := productSvcMap[serviceName]; !ok { @@ -184,6 +184,20 @@ func updateHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *comm return fmt.Errorf("failed to find renderchart for service: %s", renderChart.ServiceName) } + chartArg := &commonservice.RenderChartArg{} + chartArg.FillRenderChartModel(renderChart, renderChart.ChartVersion) + newRenderSet, err := diffRenderSet(userName, productInfo.ProductName, productInfo.EnvName, productInfo, []*commonservice.RenderChartArg{chartArg}, log.SugaredLogger()) + if err != nil { + return fmt.Errorf("failed to build renderset for service: %s, err: %s", renderChart.ServiceName, err) + } + + // update renderChart + for _, _renderChart := range newRenderSet.ChartInfos { + if _renderChart.ServiceName == serviceName { + renderChart = _renderChart + } + } + helmClient, err := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) if err != nil { return err @@ -231,7 +245,7 @@ func ReInstallHelmSvcInAllEnvs(productName string, templateSvc *commonmodels.Ser } // updateHelmSvcInAllEnvs updates helm svc in all envs in which the svc is already installed -func updateHelmSvcInAllEnvs(productName string, templateSvcs []*commonmodels.Service) error { +func updateHelmSvcInAllEnvs(userName, productName string, templateSvcs []*commonmodels.Service) error { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ Name: productName, }) @@ -241,7 +255,7 @@ func updateHelmSvcInAllEnvs(productName string, templateSvcs []*commonmodels.Ser retErr := &multierror.Error{} for _, product := range products { for _, templateSvc := range templateSvcs { - err := updateHelmServiceInEnv(product, templateSvc) + err := updateHelmServiceInEnv(userName, product, templateSvc) if err != nil { retErr = multierror.Append(retErr, fmt.Errorf("failed to update service: %s/%s, err: %s", product.EnvName, templateSvc.ServiceName, err)) } @@ -277,7 +291,7 @@ func AutoDeployHelmServiceToEnvs(userName, requestID, projectName string, servic return nil } go func() { - err = updateHelmSvcInAllEnvs(projectName, serviceTemplates) + err = updateHelmSvcInAllEnvs(userName, projectName, serviceTemplates) if err != nil { commonservice.SendErrorMessage(userName, "服务自动部署失败", requestID, err, log) } diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 6f96b6ec..30dbab78 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -500,6 +500,12 @@ func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServic }, }, projectName, args.CreatedBy, log) + err = service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, svc.ProductName, []*models.Service{svc}, log) + if err != nil { + finalErr = e.ErrCreateTemplate.AddErr(err) + return nil, finalErr + } + return &BulkHelmServiceCreationResponse{ SuccessServices: []string{serviceName}, }, nil @@ -628,6 +634,11 @@ func createOrUpdateHelmServiceFromChartTemplate(templateArgs *CreateFromChartTem }, }, projectName, args.CreatedBy, logger) + err = service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, svc.ProductName, []*models.Service{svc}, logger) + if err != nil { + return nil, err + } + return &BulkHelmServiceCreationResponse{ SuccessServices: []string{args.Name}, }, nil @@ -668,6 +679,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr helmRenderCharts := make([]*templatemodels.RenderChart, 0, len(filePaths)) var wg wait.Group var mux sync.RWMutex + var serviceList []*commonmodels.Service for _, p := range filePaths { filePath := strings.TrimLeft(p, "/") wg.Start(func() { @@ -760,6 +772,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr finalErr = e.ErrCreateTemplate.AddErr(err) return } + serviceList = append(serviceList, svc) helmRenderCharts = append(helmRenderCharts, &templatemodels.RenderChart{ ServiceName: serviceName, @@ -772,7 +785,7 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr wg.Wait() compareHelmVariable(helmRenderCharts, projectName, args.CreatedBy, log) - return response, nil + return response, service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, projectName, serviceList, log) } func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { @@ -805,6 +818,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC var wg wait.Group var mux sync.RWMutex + serviceList := make([]*commonmodels.Service, 0) for _, p := range repoArgs.Paths { filePath := strings.TrimLeft(p, "/") wg.Start(func() { @@ -906,6 +920,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC finalErr = e.ErrCreateTemplate.AddErr(err) return } + serviceList = append(serviceList, svc) helmRenderCharts = append(helmRenderCharts, &templatemodels.RenderChart{ ServiceName: serviceName, @@ -918,7 +933,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC wg.Wait() compareHelmVariable(helmRenderCharts, projectName, args.CreatedBy, log) - return response, nil + return response, service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, projectName, serviceList, log) } func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { @@ -953,6 +968,8 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel //record errors for every service failedServiceMap := &sync.Map{} renderChartMap := &sync.Map{} + svcMap := &sync.Map{} + serviceList := make([]*commonmodels.Service, 0) wg := sync.WaitGroup{} // run goroutines to speed up @@ -960,10 +977,11 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel wg.Add(1) go func(repoConfig *commonservice.RepoConfig, path string) { defer wg.Done() - renderChart, err := handleSingleService(projectName, repoConfig, path, from, args, templateChartData, logger) + renderChart, svcInfo, err := handleSingleService(projectName, repoConfig, path, from, args, templateChartData, logger) if err != nil { failedServiceMap.Store(path, err.Error()) } else { + svcMap.Store(renderChart.ServiceName, svcInfo) renderChartMap.Store(renderChart.ServiceName, renderChart) } }(args.ValuesData.GitRepoConfig, singlePath) @@ -983,6 +1001,10 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel renderChars = append(renderChars, value.(*templatemodels.RenderChart)) return true }) + svcMap.Range(func(key, value interface{}) bool { + serviceList = append(serviceList, value.(*commonmodels.Service)) + return true + }) failedServiceMap.Range(func(key, value interface{}) bool { resp.FailedServices = append(resp.FailedServices, &FailedService{ @@ -993,12 +1015,11 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel }) compareHelmVariable(renderChars, projectName, args.CreatedBy, logger) - - return resp, nil + return resp, service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, projectName, serviceList, logger) } func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, path, fromPath string, args *BulkHelmServiceCreationArgs, - templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, error) { + templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, *commonmodels.Service, error) { valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, Owner: repoConfig.Owner, @@ -1007,18 +1028,18 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi Branch: repoConfig.Branch, }) if err != nil { - return nil, err + return nil, nil, err } if len(valuesYAML) == 0 { - return nil, fmt.Errorf("values.yaml is empty") + return nil, nil, fmt.Errorf("values.yaml is empty") } values := [][]byte{templateChartData.DefaultValuesYAML, valuesYAML} mergedValues, err := yamlutil.Merge(values) if err != nil { logger.Errorf("Failed to merge values, err: %s", err) - return nil, err + return nil, nil, err } serviceName := filepath.Base(path) @@ -1029,29 +1050,29 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi // remove old files if err = os.RemoveAll(to); err != nil { logger.Errorf("Failed to remove dir %s, err: %s", to, err) - return nil, err + return nil, nil, err } if err = copy.Copy(fromPath, to); err != nil { logger.Errorf("Failed to copy file from %s to %s, err: %s", fromPath, to, err) - return nil, err + return nil, nil, err } // write values.yaml file if err = os.WriteFile(filepath.Join(to, setting.ValuesYaml), mergedValues, 0644); err != nil { logger.Errorf("Failed to write values, err: %s", err) - return nil, err + return nil, nil, err } rev, err := getNextServiceRevision(projectName, serviceName) if err != nil { log.Errorf("Failed to get next revision for service %s, err: %s", serviceName, err) - return nil, errors.Wrapf(err, "Failed to get service next revision, service %s", serviceName) + return nil, nil, errors.Wrapf(err, "Failed to get service next revision, service %s", serviceName) } err = copyChartRevision(projectName, serviceName, rev) if err != nil { log.Errorf("Failed to copy file %s, err: %s", serviceName, err) - return nil, errors.Wrapf(err, "Failed to copy chart info, service %s", serviceName) + return nil, nil, errors.Wrapf(err, "Failed to copy chart info, service %s", serviceName) } fsTree := os.DirFS(config.LocalServicePath(projectName, serviceName)) @@ -1066,10 +1087,10 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi if err = fsservice.ArchiveAndUploadFilesToS3(fsTree, []string{serviceName, fmt.Sprintf("%s-%d", serviceName, rev)}, serviceS3Base, logger); err != nil { logger.Errorf("Failed to upload files for service %s in project %s, err: %s", serviceName, projectName, err) - return nil, err + return nil, nil, err } - _, err = createOrUpdateHelmService( + svc, err := createOrUpdateHelmService( fsTree, &helmServiceCreationArgs{ ChartName: templateChartData.ChartName, @@ -1093,14 +1114,14 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi ) if err != nil { logger.Errorf("Failed to create service %s in project %s, error: %s", serviceName, projectName, err) - return nil, err + return nil, nil, err } return &templatemodels.RenderChart{ ServiceName: serviceName, ChartVersion: templateChartData.ChartVersion, ValuesYaml: string(mergedValues), - }, nil + }, svc, nil } func readChartYAML(chartTree fs.FS, base string, logger *zap.SugaredLogger) (string, string, error) { @@ -1352,7 +1373,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg return nil, err } - return serviceObj, service.AutoDeployHelmServiceToEnvs(args.CreateBy, args.RequestID, serviceObj.ProductName, []*models.Service{serviceObj}, logger) + return serviceObj, nil } func loadServiceFileInfos(productName, serviceName string, revision int64, dir string) ([]*types.FileInfo, error) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 2c87bc14..63e7ac99 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1095,7 +1095,7 @@ func UpdateReleaseNamingRule(userName, requestID, projectName string, args *Rele } }() - return service.AutoDeployHelmServiceToEnvs(userName, requestID, serviceTemplate.ProductName, []*commonmodels.Service{serviceTemplate}, log) + return nil } func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, visibility string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/service/service/template.go b/pkg/microservice/aslan/core/service/service/template.go index 32e69f78..9853b983 100644 --- a/pkg/microservice/aslan/core/service/service/template.go +++ b/pkg/microservice/aslan/core/service/service/template.go @@ -133,15 +133,15 @@ func buildVariable(key string) string { } // SyncServiceFromTemplate syncs services from (yaml|chart)template -func SyncServiceFromTemplate(source, templateId, templateName string, logger *zap.SugaredLogger) error { +func SyncServiceFromTemplate(userName, source, templateId, templateName string, logger *zap.SugaredLogger) error { if source == setting.ServiceSourceTemplate { - return syncServicesFromYamlTemplate(templateId, logger) + return syncServicesFromYamlTemplate(userName, templateId, logger) } else { - return syncServicesFromChartTemplate(templateName, logger) + return syncServicesFromChartTemplate(userName, templateName, logger) } } -func syncServicesFromYamlTemplate(templateId string, logger *zap.SugaredLogger) error { +func syncServicesFromYamlTemplate(userName, templateId string, logger *zap.SugaredLogger) error { serviceList, err := commonrepo.NewServiceColl().ListMaxRevisionServicesByYamlTemplate(templateId) if err != nil { return err @@ -160,11 +160,11 @@ func syncServicesFromYamlTemplate(templateId string, logger *zap.SugaredLogger) for _, services := range servicesByProject { go func(pServices []*commonmodels.Service) { for _, service := range pServices { - err := reloadServiceFromYamlTemplate(service.ProductName, yamlTemplate, service) + err := reloadServiceFromYamlTemplate(userName, service.ProductName, yamlTemplate, service) if err != nil { - logger.Errorf("failed to reload service: %s from template: %s, err: %s", service.ServiceName, yamlTemplate.Name, err) + logger.Error(err) title := fmt.Sprintf("从模板更新 [%s] 的 [%s] 服务失败", service.ProductName, service.ServiceName) - commonservice.SendErrorMessage("system", title, "", err, logger) + commonservice.SendErrorMessage(userName, title, "", err, logger) } } }(services) @@ -172,7 +172,7 @@ func syncServicesFromYamlTemplate(templateId string, logger *zap.SugaredLogger) return nil } -func syncServicesFromChartTemplate(templateName string, logger *zap.SugaredLogger) error { +func syncServicesFromChartTemplate(userName, templateName string, logger *zap.SugaredLogger) error { chartTemplate, err := prepareChartTemplateData(templateName, log.SugaredLogger()) if err != nil { return err @@ -197,7 +197,7 @@ func syncServicesFromChartTemplate(templateName string, logger *zap.SugaredLogge if err != nil { logger.Errorf("failed to reload service %s/%s from chart template, err: %s", service.ProductName, service.ServiceName, err) title := fmt.Sprintf("从模板更新 [%s] 的 [%s] 服务失败", service.ProductName, service.ServiceName) - commonservice.SendErrorMessage("system", title, "", err, logger) + commonservice.SendErrorMessage(userName, title, "", err, logger) } } }(services) @@ -328,7 +328,7 @@ func buildChartTemplateVariables(service *commonmodels.Service, template *common return variables, customYaml, nil } -func reloadServiceFromYamlTemplate(projectName string, template *commonmodels.YamlTemplate, service *commonmodels.Service) error { +func reloadServiceFromYamlTemplate(userName, projectName string, template *commonmodels.YamlTemplate, service *commonmodels.Service) error { //extract variables from current service variables, err := buildYamlTemplateVariables(service, template) if err != nil { @@ -347,7 +347,7 @@ func reloadServiceFromYamlTemplate(projectName string, template *commonmodels.Ya CreateFrom: service.CreateFrom, AutoSync: service.AutoSync, } - _, err = CreateServiceTemplate("system", svc, log.SugaredLogger()) + _, err = CreateServiceTemplate(userName, svc, log.SugaredLogger()) if err != nil { return fmt.Errorf("failed to reload service template from template ID: %s, error : %s", service.TemplateID, err) } diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 0ee1923d..9bf3da25 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -104,7 +104,7 @@ func SyncChartTemplateReference(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = templateservice.SyncHelmTemplateReference(c.Param("name"), ctx.Logger) + ctx.Err = templateservice.SyncHelmTemplateReference(ctx.UserName, c.Param("name"), ctx.Logger) } func RemoveChartTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 3be8dd68..600936eb 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -111,7 +111,7 @@ func SyncYamlTemplateReference(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Err = templateservice.SyncYamlTemplateReference(c.Param("id"), ctx.Logger) + ctx.Err = templateservice.SyncYamlTemplateReference(ctx.UserName, c.Param("id"), ctx.Logger) } type getYamlTemplateVariablesReq struct { diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 59c426fc..ca1bcec9 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -314,8 +314,8 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z return err } -func SyncHelmTemplateReference(name string, logger *zap.SugaredLogger) error { - return service.SyncServiceFromTemplate(setting.SourceFromChartTemplate, "", name, logger) +func SyncHelmTemplateReference(userName, name string, logger *zap.SugaredLogger) error { + return service.SyncServiceFromTemplate(userName, setting.SourceFromChartTemplate, "", name, logger) } func UpdateChartTemplateVariables(name string, args []*commonmodels.Variable, logger *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/templatestore/service/yaml.go b/pkg/microservice/aslan/core/templatestore/service/yaml.go index 06c9e5ab..0716647d 100644 --- a/pkg/microservice/aslan/core/templatestore/service/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/service/yaml.go @@ -129,8 +129,8 @@ func DeleteYamlTemplate(id string, logger *zap.SugaredLogger) error { return err } -func SyncYamlTemplateReference(id string, logger *zap.SugaredLogger) error { - return service.SyncServiceFromTemplate(setting.ServiceSourceTemplate, id, "", logger) +func SyncYamlTemplateReference(userName, id string, logger *zap.SugaredLogger) error { + return service.SyncServiceFromTemplate(userName, setting.ServiceSourceTemplate, id, "", logger) } func GetYamlTemplateReference(id string, logger *zap.SugaredLogger) ([]*template.ServiceReference, error) { -- Gitee From 860f72d94b41d35defcfe7e482f23be48701b9d9 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Mon, 9 May 2022 17:33:57 +0800 Subject: [PATCH 2334/2647] Optimized YAML verification error message (#1480) Signed-off-by: ddh27 --- pkg/microservice/aslan/core/service/service/service.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 63e7ac99..6d367c98 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -964,8 +964,7 @@ func YamlValidator(args *YamlValidatorReq) []string { yamlData = config.ServiceNameAlias.ReplaceAllLiteralString(yamlData, args.ServiceName) if err := yaml.Unmarshal([]byte(yamlData), &resKind); err != nil { - log.Errorf("yaml unmarshal err: %s", err) - errorDetails = append(errorDetails, "Invalid yaml format. The content must be a series of valid Kubernetes resources") + errorDetails = append(errorDetails, fmt.Sprintf("Invalid yaml format. The content must be a series of valid Kubernetes resources. err: %s", err)) } } } -- Gitee From 1eeb733d47d2b8ba335ff144b6178850d3bf93c6 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Mon, 9 May 2022 18:04:16 +0800 Subject: [PATCH 2335/2647] Enable filter for branch and tags (#1478) * changed datastructure to enable filter for repository branches Signed-off-by: Min Min * minor fix Signed-off-by: Min Min * bugfix Signed-off-by: Min Min * change preset API to make it work Signed-off-by: Min Min * filter logic implementation Signed-off-by: Min Min * code review changes Signed-off-by: Min Min --- .../aslan/core/code/service/repo.go | 30 +++++++++++++++++++ .../core/common/repository/models/workflow.go | 7 +++-- .../service/workflow/workflow_task.go | 20 +++++++++++++ pkg/types/repo.go | 12 ++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index de0e5191..5a53ecd7 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -46,6 +46,8 @@ type GitRepoInfo struct { RepoUUID string `json:"repo_uuid,omitempty"` RepoID string `json:"repo_id,omitempty"` Key string `json:"key"` + // FilterRegexp is the regular expression filter for the branches and tags + FilterRegexp string `json:"filter_regexp,omitempty"` } // ListRepoInfos ... @@ -124,6 +126,34 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo } wg.Wait() + for _, info := range infos { + if info.FilterRegexp != "" { + newBranchList := make([]*client.Branch, 0) + for _, branch := range info.Branches { + match, err := regexp.MatchString(info.FilterRegexp, branch.Name) + if err != nil { + log.Errorf("failed to match regular expression: %s on branch name :%s, error: %s", info.FilterRegexp, branch.Name, err) + continue + } + if match { + newBranchList = append(newBranchList, branch) + } + } + newTagList := make([]*client.Tag, 0) + for _, tag := range info.Tags { + match, err := regexp.MatchString(info.FilterRegexp, tag.Name) + if err != nil { + log.Errorf("failed to match regular expression: %s on tag name :%s, error: %s", info.FilterRegexp, tag.Name, err) + continue + } + if match { + newTagList = append(newTagList, tag) + } + } + info.Branches = newBranchList + info.Tags = newTagList + } + } if err := errList.ErrorOrNil(); err != nil { log.Errorf("list repo info error: %v", err) } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 49ad3478..6088211c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -224,9 +224,10 @@ type BuildStage struct { // BuildModule ... type BuildModule struct { - Target *ServiceModuleTarget `bson:"target" json:"target"` - HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` - BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` + Target *ServiceModuleTarget `bson:"target" json:"target"` + HideServiceModule bool `bson:"hide_service_module" json:"hide_service_module"` + BuildModuleVer string `bson:"build_module_ver" json:"build_module_ver"` + BranchFilter []*types.BranchFilterInfo `bson:"branch_filter" json:"branch_filter"` } type ArtifactStage struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 01c2b42e..e67d8233 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -409,6 +409,13 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) log.Errorf("Workflow.Find error: %v", err) return resp, e.ErrFindWorkflow.AddDesc(err.Error()) } + filtermap := make(map[string][]*types.BranchFilterInfo) + if workflow.BuildStage.Enabled { + for _, buildModule := range workflow.BuildStage.Modules { + buildKey := fmt.Sprintf("%s/%s", buildModule.Target.ServiceModule, buildModule.Target.ServiceName) + filtermap[buildKey] = buildModule.BranchFilter + } + } if workflow.DistributeStage != nil { resp.DistributeEnabled = workflow.DistributeStage.Enabled } @@ -476,6 +483,19 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) target.Build.Repos = moBuild.SafeRepos() } + key := fmt.Sprintf("%s/%s", containerArr[2], containerArr[1]) + for _, repoInfo := range target.Build.Repos { + if filterList, ok := filtermap[key]; ok { + for _, filter := range filterList { + // make sure they are the same repository + if filter.CodehostID == repoInfo.CodehostID && filter.RepoOwner == repoInfo.RepoOwner && filter.RepoName == repoInfo.RepoName { + repoInfo.FilterRegexp = filter.FilterRegExp + break + } + } + } + } + if moBuild.PreBuild != nil { EnsureBuildResp(moBuild) target.Envs = moBuild.PreBuild.Envs diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 9c6890ae..3f143a44 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -54,6 +54,18 @@ type Repository struct { Password string `bson:"password,omitempty" json:"password,omitempty"` // Now EnableProxy is not something we store. We decide this on runtime EnableProxy bool `bson:"-" json:"enable_proxy,omitempty"` + // FilterRegexp is the regular expression filter for the branches and tags + FilterRegexp string `bson:"-" json:"filter_regexp,omitempty"` +} + +type BranchFilterInfo struct { + // repository identifier + CodehostID int `bson:"codehost_id" json:"codehost_id"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` + + // actual regular expression filter + FilterRegExp string `bson:"filter_regexp" json:"filter_regexp"` } // GetReleaseCandidateTag 返回待发布对象Tag -- Gitee From 4c06ce88a3b9357c87d662505f8d3aad3e0214e0 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Mon, 9 May 2022 21:26:33 +0800 Subject: [PATCH 2336/2647] Fixed yamL preview when adding services to the environment (#1481) Signed-off-by: ddh27 --- .../aslan/core/service/service/service.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 6d367c98..d7635837 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -994,15 +994,14 @@ func YamlViewServiceTemplate(args *YamlViewServiceTemplateReq) (string, error) { return "", err } + parsedYaml = kube.ParseSysKeys(prod.Namespace, prod.EnvName, prod.ProductName, args.ServiceName, parsedYaml) cerSvc := prod.GetServiceMap() svcInfo, found := cerSvc[args.ServiceName] - if !found { - return "", fmt.Errorf("services %s is not exist in env %s", args.ServiceName, prod.Namespace) - } - parsedYaml = kube.ParseSysKeys(prod.Namespace, prod.EnvName, prod.ProductName, args.ServiceName, parsedYaml) - parsedYaml, err = replaceContainerImages(parsedYaml, svcTmpl.Containers, svcInfo.Containers) - if err != nil { - return "", err + if found { + parsedYaml, err = replaceContainerImages(parsedYaml, svcTmpl.Containers, svcInfo.Containers) + if err != nil { + return "", err + } } } -- Gitee From 71017f65613289b17654751cba0becdeb5a98b42 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Tue, 10 May 2022 15:42:00 +0800 Subject: [PATCH 2337/2647] added filter for default branch (#1482) * added filter for default branch Signed-off-by: Min Min * change error handling logic Signed-off-by: Min Min * even more error handling Signed-off-by: Min Min * error handling 3 Signed-off-by: Min Min --- pkg/microservice/aslan/core/code/service/repo.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 5a53ecd7..8ed2e3bf 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -133,7 +133,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo match, err := regexp.MatchString(info.FilterRegexp, branch.Name) if err != nil { log.Errorf("failed to match regular expression: %s on branch name :%s, error: %s", info.FilterRegexp, branch.Name, err) - continue + return nil, err } if match { newBranchList = append(newBranchList, branch) @@ -144,7 +144,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo match, err := regexp.MatchString(info.FilterRegexp, tag.Name) if err != nil { log.Errorf("failed to match regular expression: %s on tag name :%s, error: %s", info.FilterRegexp, tag.Name, err) - continue + return nil, err } if match { newTagList = append(newTagList, tag) @@ -152,10 +152,20 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo } info.Branches = newBranchList info.Tags = newTagList + + match, err := regexp.MatchString(info.FilterRegexp, info.DefaultBranch) + if err != nil { + log.Errorf("failed to compile regular expression: %s, the error is: %s", info.FilterRegexp, err) + return nil, err + } + if !match { + info.DefaultBranch = "" + } } } if err := errList.ErrorOrNil(); err != nil { log.Errorf("list repo info error: %v", err) + return nil, err } return infos, nil } -- Gitee From a8d40bb26df97c126f8836a5514724ed297b4e11 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 11 May 2022 16:18:41 +0800 Subject: [PATCH 2338/2647] add release view (#1474) * add relese view page api Signed-off-by: allenshen * add serivce filter logic when querying workloads Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * resolve compile errors Signed-off-by: allenshen * fix workload filter fetch bug Signed-off-by: allenshen * fix service template update error Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * resolve comments Signed-off-by: allenshen * fix service update bug Signed-off-by: allenshen * fix service update error Signed-off-by: allenshen * resolve comments Signed-off-by: allenshen * fix service order Signed-off-by: allenshen --- .../core/common/repository/models/product.go | 1 + .../core/common/repository/mongodb/product.go | 10 + .../aslan/core/common/service/environment.go | 102 +++- .../aslan/core/common/service/render.go | 47 +- .../core/environment/handler/environment.go | 95 +++- .../aslan/core/environment/handler/helm.go | 18 +- .../core/environment/handler/policy.yaml | 21 + .../aslan/core/environment/handler/router.go | 3 + .../core/environment/service/environment.go | 486 ++++++++++-------- .../environment/service/environment_update.go | 112 ++-- .../aslan/core/environment/service/helm.go | 276 ++++++++-- .../core/environment/service/renderset.go | 2 +- .../core/environment/service/revision.go | 2 +- pkg/setting/consts.go | 12 +- pkg/util/yaml/compare.go | 25 +- pkg/util/yaml/compare_test.go | 75 +++ pkg/util/yaml/path_search_test.go | 30 +- 17 files changed, 973 insertions(+), 344 deletions(-) create mode 100644 pkg/util/yaml/compare_test.go diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 170b4150..5519b316 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -82,6 +82,7 @@ type ProductService struct { Revision int64 `bson:"revision" json:"revision"` Containers []*Container `bson:"containers" json:"containers,omitempty"` Render *RenderInfo `bson:"render,omitempty" json:"render,omitempty"` // 记录每个服务render信息 便于更新单个服务 + Error string `bson:"error,omitempty" json:"error,omitempty"` EnvConfigs []*EnvConfig `bson:"-" json:"env_configs,omitempty"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/product.go index 7cadd03f..9fc86e1d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/product.go @@ -305,6 +305,16 @@ func (c *ProductColl) ListProjectsInNames(names []string) ([]*projectEnvs, error return res, nil } +func (c *ProductColl) UpdateStatusAndError(envName, projectName, status, errorMsg string) error { + query := bson.M{"env_name": envName, "product_name": projectName} + change := bson.M{"$set": bson.M{ + "status": status, + "error": errorMsg, + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} + func (c *ProductColl) UpdateStatus(owner, productName, status string) error { query := bson.M{"env_name": owner, "product_name": productName} change := bson.M{"$set": bson.M{ diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 5a8e05dd..cbb10b08 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -141,11 +141,12 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL } // fill service display name if necessary -func fillServiceDisplayName(svcList []*ServiceResp, productInfo *models.Product) { - if productInfo.Source == setting.SourceFromHelm { - for _, svc := range svcList { - svc.ServiceDisplayName = svc.ServiceName - } +func fillServiceInfo(svcList []*ServiceResp, productInfo *models.Product) { + if productInfo.Source != setting.SourceFromHelm { + return + } + for _, svc := range svcList { + svc.ServiceDisplayName = svc.ServiceName } } @@ -231,13 +232,13 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, filterArray = append(filterArray, func(workloads []*Workload) []*Workload { data, err := jsonutil.ToJSON(filter) if err != nil { - log.Warnf("Invalid filter, err: %s", err) + log.Errorf("Invalid filter, err: %s", err) return workloads } f := &workloadFilter{} if err = json.Unmarshal(data, f); err != nil { - log.Warnf("Invalid filter, err: %s", err) + log.Errorf("Invalid filter, err: %s", err) return workloads } @@ -256,18 +257,47 @@ func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, if err != nil { return count, resp, err } - fillServiceDisplayName(resp, productInfo) + + fillServiceInfo(resp, productInfo) return count, resp, nil } type FilterFunc func(services []*Workload) []*Workload type workloadFilter struct { - Name string `json:"name"` + Name string `json:"name"` + ServiceName string `json:"serviceName"` + ServiceNameList sets.String `json:"-"` +} + +type wfAlias workloadFilter + +func (f *workloadFilter) UnmarshalJSON(data []byte) error { + aliasData := &wfAlias{} + if err := json.Unmarshal(data, aliasData); err != nil { + return err + } + f.Name = aliasData.Name + f.ServiceName = aliasData.ServiceName + if f.ServiceName != "*" { + serviceNames := strings.Split(f.ServiceName, "|") + f.ServiceNameList = sets.NewString(serviceNames...) + } + return nil } func (f *workloadFilter) Match(workload *Workload) bool { - return strings.Contains(workload.Name, f.Name) + if len(f.Name) > 0 { + if !strings.Contains(workload.Name, f.Name) { + return false + } + } + if len(f.ServiceNameList) > 0 { + if !f.ServiceNameList.Has(workload.ServiceName) { + return false + } + } + return true } type Workload struct { @@ -278,8 +308,36 @@ type Workload struct { Spec corev1.PodTemplateSpec `json:"-"` Images []string `json:"-"` Ready bool `json:"ready"` - ServiceName string `json:"service_name"` Annotation map[string]string `json:"-"` + ServiceName string `json:"service_name"` //serviceName refers to the service defines in zadig +} + +// fillServiceName set service name defined in zadig to workloads, this would be helpful for helm release view +// TODO optimize this function, use release.template to filter related resources +func fillServiceName(envName, productName string, workloads []*Workload) error { + if len(envName) == 0 { + return nil + } + productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + return err + } + if productInfo.Source != setting.SourceFromHelm { + return nil + } + releaseNameMap, err := GetReleaseNameToServiceNameMap(productInfo) + if err != nil { + return err + } + for _, wl := range workloads { + if chartRelease, ok := wl.Annotation[setting.HelmReleaseNameAnnotation]; ok { + wl.ServiceName = releaseNameMap[chartRelease] + } + } + return nil } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { @@ -302,6 +360,7 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Errorf("[%s][%s] create product record error: %v", envName, namespace, err) return 0, resp, e.ErrListGroups.AddDesc(err.Error()) } + for _, v := range listDeployments { workLoads = append(workLoads, &Workload{ Name: v.Name, @@ -337,6 +396,12 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p log.Debugf("Found %d workloads in total", len(workLoads)) + err = fillServiceName(envName, productName, workLoads) + // err of getting service name should not block the return of workloads + if err != nil { + log.Warnf("failed to set service name for workloads, error: %s", err) + } + // 对于workload过滤 for _, f := range filter { workLoads = f(workLoads) @@ -490,6 +555,21 @@ func GetReleaseNameToServiceNameMap(prod *models.Product) (map[string]string, er return releaseNameMap, nil } +// GetServiceNameToReleaseNameMap generates mapping relationship: serviceName=>releaseName +func GetServiceNameToReleaseNameMap(prod *models.Product) (map[string]string, error) { + productName, envName := prod.ProductName, prod.EnvName + templateServices, err := GetProductUsedTemplateSvcs(prod) + if err != nil { + return nil, err + } + // map[serviceName] => ReleaseName + releaseNameMap := make(map[string]string) + for _, svcInfo := range templateServices { + releaseNameMap[svcInfo.ServiceName] = util.GeneReleaseName(svcInfo.GetReleaseNaming(), productName, prod.Namespace, envName, svcInfo.ServiceName) + } + return releaseNameMap, nil +} + // GetHelmServiceName get service name from annotations of resources deployed by helm // resType currently only support Deployment and StatefulSet // this function needs to be optimized diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 8cef3131..ce68f41c 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "sort" "strings" @@ -34,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type RepoConfig struct { @@ -65,6 +67,14 @@ type RenderChartArg struct { YamlData *templatemodels.CustomYaml `json:"yaml_data,omitempty"` } +type RenderChartDiffResult string + +const ( + Different RenderChartDiffResult = "different" + Same RenderChartDiffResult = "same" + LogicSame RenderChartDiffResult = "logicSame" +) + func (args *RenderChartArg) ToOverrideValueString() string { if len(args.OverrideValues) == 0 { return "" @@ -86,7 +96,7 @@ func (args *RenderChartArg) fromOverrideValueString(valueStr string) { args.OverrideValues = make([]*KVPair, 0) err := json.Unmarshal([]byte(valueStr), &args.OverrideValues) if err != nil { - log.Errorf("decode override value fail, ") + log.Errorf("decode override value fail, err: %s", err) } } @@ -145,6 +155,36 @@ func (args *RenderChartArg) LoadFromRenderChartModel(chart *templatemodels.Rende args.fromCustomValueYaml(chart.OverrideYaml) } +func (args *RenderChartArg) GetUniqueKvMap() map[string]interface{} { + uniqueKvs := make(map[string]interface{}) + for index := range args.OverrideValues { + kv := args.OverrideValues[len(args.OverrideValues)-index-1] + if _, ok := uniqueKvs[kv.Key]; ok { + continue + } + uniqueKvs[kv.Key] = kv.Value + } + return uniqueKvs +} + +// DiffValues generate diff values to override from two chart args +func (args *RenderChartArg) DiffValues(target *RenderChartArg) RenderChartDiffResult { + argsUniqueKvs := args.GetUniqueKvMap() + targetUniqueKvs := target.GetUniqueKvMap() + if len(argsUniqueKvs) != len(targetUniqueKvs) || !reflect.DeepEqual(argsUniqueKvs, targetUniqueKvs) { + return Different + } + // override yamls have the same text content + if args.OverrideYaml == target.OverrideYaml { + return Same + } + equal, _ := yamlutil.Equal(args.OverrideYaml, target.OverrideYaml) + if equal { + return LogicSame + } + return Different +} + func listTmplRenderKeys(productTmplName string, log *zap.SugaredLogger) ([]*templatemodels.RenderKV, map[string]*templatemodels.ServiceInfo, error) { //如果没找到对应产品,则kv为空 prodTmpl, err := template.NewProductColl().Find(productTmplName) @@ -213,7 +253,7 @@ func ValidateRenderSet(productName, renderName string, serviceInfo *templatemode //} } else { // 单个服务是否全覆盖判断 - if err := IsAllKeyCoveredService(serviceInfo.Owner, serviceInfo.Name, resp, log); err != nil { + if err := IsAllKeyCoveredService(serviceInfo.Owner, serviceInfo.Name, resp); err != nil { log.Errorf("[%s]cover all key [%s] error: %v", productName, renderName, err) return resp, err } @@ -567,7 +607,7 @@ func IsAllKeyCovered(arg *commonmodels.RenderSet, log *zap.SugaredLogger) error } // IsAllKeyCoveredService 检查是否覆盖所有服务key -func IsAllKeyCoveredService(productName, serviceName string, arg *commonmodels.RenderSet, log *zap.SugaredLogger) error { +func IsAllKeyCoveredService(productName, serviceName string, arg *commonmodels.RenderSet) error { opt := &commonrepo.ServiceFindOption{ ServiceName: serviceName, ProductName: productName, @@ -601,6 +641,7 @@ func ensureRenderSetArgs(args *commonmodels.RenderSet) error { if len(args.Name) == 0 { return errors.New("empty render set name") } + //log := log.SugaredLogger() //if err := IsAllKeyCovered(args, log); err != nil { // return fmt.Errorf("[RenderSet.Create] %s error: %v", args.Name, err) diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index f66e1efe..d048a906 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -19,6 +19,7 @@ package handler import ( "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -384,32 +385,45 @@ func SyncHelmProductRenderset(c *gin.Context) { ctx.Err = service.SyncHelmProductEnvironment(projectName, envName, ctx.RequestID, ctx.Logger) } -func UpdateHelmProductRenderset(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - +func generalRequestValidate(c *gin.Context) (string, string, error) { projectName := c.Query("projectName") if projectName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be empty!") - return + return "", "", errors.New("projectName can't be empty") } envName := c.Param("name") if envName == "" { - ctx.Err = e.ErrInvalidParam.AddDesc("envName can't be empty!") + return "", "", errors.New("envName can't be empty") + } + return projectName, envName, nil +} + +func UpdateHelmProductRenderset(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName, envName, err := generalRequestValidate(c) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) return } arg := new(service.EnvRendersetArg) data, err := c.GetRawData() if err != nil { - log.Errorf("UpdateHelmProductVariable c.GetRawData() err : %v", err) + log.Errorf("UpdateHelmProductRenderset c.GetRawData() err : %v", err) } if err = json.Unmarshal(data, arg); err != nil { - log.Errorf("UpdateHelmProductVariable json.Unmarshal err : %v", err) + log.Errorf("UpdateHelmProductRenderset json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "更新环境变量", "", string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + err = c.BindJSON(arg) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } ctx.Err = service.UpdateHelmProductRenderset(projectName, envName, ctx.UserName, ctx.RequestID, arg, ctx.Logger) if ctx.Err != nil { @@ -417,6 +431,67 @@ func UpdateHelmProductRenderset(c *gin.Context) { } } +func UpdateHelmProductDefaultValues(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName, envName, err := generalRequestValidate(c) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + arg := new(service.EnvRendersetArg) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateHelmProductDefaultValues c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, arg); err != nil { + log.Errorf("UpdateHelmProductDefaultValues json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "更新全局变量", envName, string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + err = c.BindJSON(arg) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Err = service.UpdateHelmProductDefaultValues(projectName, envName, ctx.UserName, ctx.RequestID, arg, ctx.Logger) +} + +func UpdateHelmProductCharts(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName, envName, err := generalRequestValidate(c) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + arg := new(service.EnvRendersetArg) + + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateHelmProductCharts c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, arg); err != nil { + log.Errorf("UpdateHelmProductCharts json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "更新服务", "", string(data), ctx.Logger) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + err = c.BindJSON(arg) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Err = service.UpdateHelmProductCharts(projectName, envName, ctx.UserName, ctx.RequestID, arg, ctx.Logger) +} + func updateMultiHelmEnv(c *gin.Context, ctx *internalhandler.Context) { projectName := c.Query("projectName") if projectName == "" { diff --git a/pkg/microservice/aslan/core/environment/handler/helm.go b/pkg/microservice/aslan/core/environment/handler/helm.go index 966e1ddc..5280d01f 100644 --- a/pkg/microservice/aslan/core/environment/handler/helm.go +++ b/pkg/microservice/aslan/core/environment/handler/helm.go @@ -26,8 +26,24 @@ func ListReleases(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() envName := c.Param("name") + + args := &service.HelmReleaseQueryArgs{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + ctx.Resp, ctx.Err = service.ListReleases(args, envName, ctx.Logger) +} + +func GetChartValues(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + envName := c.Param("name") projectName := c.Query("projectName") - ctx.Resp, ctx.Err = service.ListReleases(projectName, envName, ctx.Logger) + serviceName := c.Query("serviceName") + + ctx.Resp, ctx.Err = service.GetChartValues(projectName, envName, serviceName) } func GetChartInfos(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml index 72ed925f..f5a84008 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ b/pkg/microservice/aslan/core/environment/handler/policy.yaml @@ -71,6 +71,13 @@ rules: matchAttributes: - key: "production" value: "false" + - method: GET + endpoint: "/api/aslan/environment/environments/?*/helm/values" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/values$" + matchAttributes: + - key: "production" + value: "false" - method: GET endpoint: "/api/aslan/environment/environments/?*/helm/charts" resourceType: "Environment" @@ -210,6 +217,20 @@ rules: matchAttributes: - key: "production" value: "false" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/helm/default-values" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/default-values$" + matchAttributes: + - key: "production" + value: "false" + - method: PUT + endpoint: "/api/aslan/environment/environments/?*/helm/charts" + resourceType: "Environment" + idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" + matchAttributes: + - key: "production" + value: "false" - method: PUT endpoint: "/api/aslan/service/workloads" matchAttributes: diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index b11577b8..f0bce70c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -123,6 +123,8 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.PUT("/:name/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) environments.POST("/:name/estimated-values", EstimatedValues) environments.PUT("/:name/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) + environments.PUT("/:name/helm/default-values", gin2.UpdateOperationLogStatus, UpdateHelmProductDefaultValues) + environments.PUT("/:name/helm/charts", gin2.UpdateOperationLogStatus, UpdateHelmProductCharts) environments.PUT("/:name/syncVariables", gin2.UpdateOperationLogStatus, SyncHelmProductRenderset) environments.GET("/:name/helmChartVersions", GetHelmChartVersions) environments.GET("/:name/productInfo", GetProductInfo) @@ -132,6 +134,7 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/workloads", ListWorkloadsInEnv) environments.GET("/:name/helm/releases", ListReleases) + environments.GET("/:name/helm/values", GetChartValues) environments.GET("/:name/helm/charts", GetChartInfos) environments.GET("/:name/helm/images", GetImageInfos) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 9020e4eb..598f33b3 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -73,6 +73,7 @@ import ( "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" "github.com/koderover/zadig/pkg/util/fs" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) const ( @@ -160,9 +161,10 @@ type EnvRenderChartArg struct { } type EnvRendersetArg struct { - DefaultValues string `json:"defaultValues"` - ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + DefaultValues string `json:"defaultValues"` + ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + UpdateServiceTmpl bool `json:"updateServiceTmpl"` } type CreateHelmProductArg struct { @@ -205,6 +207,7 @@ type ReleaseInstallParam struct { } type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error +type svcUpgradeFilter func(svc *commonmodels.ProductService) bool func ListProducts(projectName string, envNames []string, log *zap.SugaredLogger) ([]*EnvResp, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: projectName, InEnvs: envNames, IsSortByProductName: true}) @@ -1181,7 +1184,7 @@ func copySingleHelmProduct(templateProduct *templatemodels.Product, productName, // merge chart infos, use chart info in product to override charts in template_project sourceRenderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{Name: sourceRendersetName}) if err != nil { - return errors.Wrapf(err, "failed to find source renderset: %s", productInfo.Namespace) + return fmt.Errorf("failed to find source renderset: %s, err: %s", productInfo.Namespace, err) } sourceChartMap := make(map[string]*templatemodels.RenderChart) for _, singleChart := range sourceRenderSet.ChartInfos { @@ -1247,13 +1250,14 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid return e.ErrUpdateEnv.AddDesc(err.Error()) } currentProductService := productResp.Services - // 查找产品模板 + // create product data from product template templateProd, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) if err != nil { log.Errorf("[%s][P:%s] GetProductTemplate error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } + // set image and render to the value used on current environment deletedSvcSet := sets.NewString(deletedServices...) deletedSvcRevision := make(map[string]int64) // services need to be created or updated @@ -1316,30 +1320,18 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid title := fmt.Sprintf("更新 [%s] 的 [%s] 环境失败", productName, envName) commonservice.SendErrorMessage(username, title, requestID, err, log) - // 设置产品状态 - log.Infof("[%s][P:%s] update status to => %s", envName, productName, setting.ProductStatusFailed) - if err2 := commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusFailed); err2 != nil { - log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, productName, err2) - return - } - log.Infof("[%s][P:%s] update error to => %s", envName, productName, err) - if err2 := commonrepo.NewProductColl().UpdateErrors(envName, productName, err.Error()); err2 != nil { - log.Errorf("[%s][P:%s] Product.UpdateErrors error: %v", envName, productName, err2) + productResp.Status = setting.ProductStatusFailed + if err = commonrepo.NewProductColl().UpdateStatusAndError(envName, productName, productResp.Status, err.Error()); err != nil { + log.Errorf("[%s][%s] Product.Update error: %v", envName, productName, err) return } } else { productResp.Status = setting.ProductStatusSuccess - - if err = commonrepo.NewProductColl().UpdateStatus(envName, productName, productResp.Status); err != nil { + if err = commonrepo.NewProductColl().UpdateStatusAndError(envName, productName, productResp.Status, ""); err != nil { log.Errorf("[%s][%s] Product.Update error: %v", envName, productName, err) return } - - if err = commonrepo.NewProductColl().UpdateErrors(envName, productName, ""); err != nil { - log.Errorf("[%s][P:%s] Product.UpdateErrors error: %v", envName, productName, err) - return - } } }() return nil @@ -1480,11 +1472,142 @@ func GeneEstimatedValues(productName, envName, serviceName, scene, format string } // check if override values or yaml content changes -func checkOverrideValuesChange(source *templatemodels.RenderChart, args *commonservice.RenderChartArg) bool { - if source.OverrideValues != args.ToOverrideValueString() || source.GetOverrideYaml() != args.OverrideYaml { - return true +// return [need-Redeploy] and [need-SaveToDB] +func checkOverrideValuesChange(source *templatemodels.RenderChart, args *commonservice.RenderChartArg) (bool, bool) { + sourceArg := &commonservice.RenderChartArg{} + sourceArg.LoadFromRenderChartModel(source) + + same := sourceArg.DiffValues(args) + switch same { + case commonservice.Different: + return true, true + case commonservice.LogicSame: + return false, true + case commonservice.Same: + return false, false } - return false + return false, false +} + +func UpdateHelmProductDefaultValues(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { + // validate if yaml content is legal + err := yaml.Unmarshal([]byte(args.DefaultValues), map[string]interface{}{}) + if err != nil { + return err + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) + return err + } + opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil || productRenderset == nil { + if err != nil { + log.Errorf("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) + } + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) + } + + if productRenderset.DefaultValues == args.DefaultValues { + return nil + } + + equal, err := yamlutil.Equal(productRenderset.DefaultValues, args.DefaultValues) + if err != nil { + return e.ErrUpdateEnv.AddErr(fmt.Errorf("failed to unmarshal default values in renderset, err: %s", err)) + } + + productRenderset.DefaultValues = args.DefaultValues + updatedRcList := make([]*templatemodels.RenderChart, 0) + if !equal { + for _, curRenderChart := range productRenderset.ChartInfos { + updatedRcList = append(updatedRcList, curRenderChart) + } + } + + err = UpdateHelmProductVariable(productName, envName, userName, requestID, updatedRcList, productRenderset, log) + if err != nil { + return err + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetKubeClient error, error msg:%s", err) + return err + } + return ensureKubeEnv(product.Namespace, product.RegistryID, false, kubeClient, log) +} + +func UpdateHelmProductCharts(productName, envName, userName, requestID string, args *EnvRendersetArg, log *zap.SugaredLogger) error { + if len(args.ChartValues) == 0 { + return nil + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: productName, + EnvName: envName, + }) + if err != nil { + log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) + return err + } + opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) + if err != nil || productRenderset == nil { + if err != nil { + log.Errorf("query renderset fail when updating helm product:%s render charts, err %s", productName, err) + } + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) + } + + requestValueMap := make(map[string]*commonservice.RenderChartArg) + for _, arg := range args.ChartValues { + requestValueMap[arg.ServiceName] = arg + } + + valuesInRenderset := make(map[string]*templatemodels.RenderChart) + for _, rc := range productRenderset.ChartInfos { + valuesInRenderset[rc.ServiceName] = rc + } + + updatedRcMap := make(map[string]*templatemodels.RenderChart) + changedCharts := make([]*commonservice.RenderChartArg, 0) + + // update override values + for serviceName, arg := range requestValueMap { + arg.EnvName = envName + rcValues, ok := valuesInRenderset[serviceName] + if !ok { + log.Errorf("failed to find current chart values for service: %s", serviceName) + return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to find current chart values for service: %s", serviceName)) + } + + arg.FillRenderChartModel(rcValues, rcValues.ChartVersion) + changedCharts = append(changedCharts, arg) + updatedRcMap[serviceName] = rcValues + } + + // update service to latest revision acts like update service templates + if args.UpdateServiceTmpl { + updateEnvArg := &UpdateMultiHelmProductArg{ + ProductName: productName, + EnvNames: []string{envName}, + ChartValues: changedCharts, + } + _, err = UpdateMultipleHelmEnv(requestID, userName, updateEnvArg, log) + return err + } + + rcList := make([]*templatemodels.RenderChart, 0) + for _, rc := range updatedRcMap { + rcList = append(rcList, rc) + } + + return UpdateHelmProductVariable(productName, envName, userName, requestID, rcList, productRenderset, log) } func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml { @@ -1526,7 +1649,7 @@ func SyncHelmProductEnvironment(productName, envName, requestID string, log *zap productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { - log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) + log.Errorf("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) } return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) } @@ -1586,7 +1709,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { - log.Infof("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) + log.Errorf("query renderset fail when updating helm product:%s render charts, err %s", productName, err.Error()) } return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) } @@ -1610,8 +1733,7 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string if curRenderChart.ServiceName != requestRenderChart.ServiceName { continue } - if !checkOverrideValuesChange(curRenderChart, requestRenderChart) { - requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) + if _, needSaveData := checkOverrideValuesChange(curRenderChart, requestRenderChart); !needSaveData { continue } requestRenderChart.FillRenderChartModel(curRenderChart, curRenderChart.ChartVersion) @@ -1665,72 +1787,64 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, return e.ErrUpdateEnv.AddDesc(e.FindProductTmplErrMsg) } + // update render info of product if productResp.Render == nil { productResp.Render = &commonmodels.RenderInfo{ProductTmpl: productResp.ProductName} } - renderSet, err := FindHelmRenderSet(productResp.ProductName, productResp.Namespace, log) if err != nil { - log.Errorf("[%s][P:%s] find product renderset error: %v", productResp.EnvName, productResp.ProductName, err) - return e.ErrCreateEnv.AddDesc(err.Error()) + log.Errorf("[%s][P:%s] find product renderset error: %s", productResp.EnvName, productResp.ProductName, err) + return e.ErrUpdateEnv.AddDesc(err.Error()) } productResp.Render.Revision = renderSet.Revision + productResp.Render.Name = renderSet.Name + + // update render used in product + err = commonrepo.NewProductColl().UpdateRender(envName, productResp.ProductName, productResp.Render) + if err != nil { + log.Errorf("[%s][P:%s] failed to update product renderset: %s", productResp.EnvName, productResp.ProductName, err) + return e.ErrUpdateEnv.AddErr(err) + } + + // only update renderset value to db, no need to upgrade chart release + if len(updatedRcs) == 0 { + return nil + } return updateHelmProductVariable(productResp, renderSet, oldRenderVersion, username, requestID, log) } func updateHelmProductVariable(productResp *commonmodels.Product, renderset *commonmodels.RenderSet, oldRenderVersion int64, userName, requestID string, log *zap.SugaredLogger) error { - envName, productName := productResp.EnvName, productResp.ProductName + restConfig, err := kube.GetRESTConfig(productResp.ClusterID) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } - // 设置产品状态为更新中 + helmClient, err := helmtool.NewClientFromRestConf(restConfig, productResp.Namespace) + if err != nil { + return e.ErrUpdateEnv.AddErr(err) + } + + // set product status to updating if err := commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUpdating); err != nil { log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.UpdateEnvStatusErrMsg) } go func() { - err := updateProductVariable(productName, envName, productResp, renderset, log) + err := proceedHelmRelease(productName, envName, productResp, renderset, helmClient, nil, log) if err != nil { - log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) + log.Errorf("error occurred when upgrading services in env: %s/%s, err: %s ", productName, envName, err) // 发送更新产品失败消息给用户 title := fmt.Sprintf("更新 [%s] 的 [%s] 环境失败", productName, envName) commonservice.SendErrorMessage(userName, title, requestID, err, log) - - // 设置产品状态 - log.Infof("[%s][P:%s] update status to => %s", envName, productName, setting.ProductStatusFailed) - if err2 := commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusFailed); err2 != nil { - log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, productName, err2) - return - } - - log.Infof("[%s][P:%s] update error to => %s", envName, productName, err) - if err2 := commonrepo.NewProductColl().UpdateErrors(envName, productName, err.Error()); err2 != nil { - log.Errorf("[%s][P:%s] Product.UpdateErrors error: %v", envName, productName, err2) - return - } - - // If the update environment failed, Roll back to the previous render version and delete new revision - log.Infof("[%s][P:%s] roll back to previous render version: %d", envName, productName, oldRenderVersion) - if deleteRenderSetErr := commonrepo.NewRenderSetColl().DeleteRenderSet(productName, productResp.Render.Name, productResp.Render.Revision); deleteRenderSetErr != nil { - log.Errorf("[%s][P:%s] Product delete renderSet error: %v", envName, productName, deleteRenderSetErr) - } - productResp.Render.Revision = oldRenderVersion - if updateRendErr := commonrepo.NewProductColl().UpdateRender(envName, productName, productResp.Render); updateRendErr != nil { - log.Errorf("[%s][P:%s] Product update render error: %v", envName, productName, updateRendErr) - } - return } productResp.Status = setting.ProductStatusSuccess - if err = commonrepo.NewProductColl().UpdateStatus(envName, productName, productResp.Status); err != nil { + if err = commonrepo.NewProductColl().UpdateStatusAndError(envName, productName, productResp.Status, ""); err != nil { log.Errorf("[%s][%s] Product.Update error: %v", envName, productName, err) return } - - if err = commonrepo.NewProductColl().UpdateErrors(envName, productName, ""); err != nil { - log.Errorf("[%s][P:%s] Product.UpdateErrors error: %v", envName, productName, err) - return - } }() return nil } @@ -3145,7 +3259,7 @@ func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry boo if err != nil { err = errors.WithMessagef( err, - "failed to Install helm chart %s/%s", + "failed to install or upgrade helm chart %s/%s", namespace, serviceObj.ServiceName) } else { err = EnsureZadigServiceByManifest(ctx, param.ProductName, param.Namespace, release.Manifest) @@ -3165,25 +3279,16 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel ) defer func() { - status := setting.ProductStatusSuccess - errorMsg := "" if err != nil { - status = setting.ProductStatusFailed - errorMsg = err.Error() - - // 发送创建产品失败消息给用户 title := fmt.Sprintf("创建 [%s] 的 [%s] 环境失败", args.ProductName, args.EnvName) commonservice.SendErrorMessage(user, title, requestID, err, log) } commonservice.LogProductStats(envName, setting.CreateProductEvent, args.ProductName, requestID, eventStart, log) - if err := commonrepo.NewProductColl().UpdateStatus(envName, args.ProductName, status); err != nil { - log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, args.ProductName, err) - return - } - if err := commonrepo.NewProductColl().UpdateErrors(envName, args.ProductName, errorMsg); err != nil { - log.Errorf("[%s][P:%s] Product.UpdateErrors error: %v", envName, args.ProductName, err) + status := setting.ProductStatusSuccess + if err = commonrepo.NewProductColl().UpdateStatusAndError(envName, args.ProductName, status, ""); err != nil { + log.Errorf("[%s][P:%s] Product.UpdateStatusAndError error: %v", envName, args.ProductName, err) return } }() @@ -3193,46 +3298,10 @@ func installProductHelmCharts(user, envName, requestID string, args *commonmodel chartInfoMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, logger *zap.SugaredLogger) error { - param, err := buildInstallParam(args.Namespace, renderset.EnvName, renderset.DefaultValues, chartInfoMap[serviceObj.ServiceName], serviceObj) - if err != nil { - return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, args.Namespace, err) - } - err = installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) - if err != nil { - log.Errorf("failed to install service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, args.Namespace, isRetry, err) - return errors.Wrapf(err, "failed to install service: %s, namespace: %s", serviceObj.ServiceName, args.Namespace) - } - return nil - } - - for _, serviceGroups := range args.Services { - serviceList := make([]*commonmodels.Service, 0) - for _, svc := range serviceGroups { - _, ok := chartInfoMap[svc.ServiceName] - if !ok { - continue - } - - // 获取服务详情 - opt := &commonrepo.ServiceFindOption{ - ServiceName: svc.ServiceName, - Type: svc.Type, - Revision: svc.Revision, - ProductName: args.ProductName, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - errList = multierror.Append(errList, errors.Wrapf(err, "failed to find template servce, serviceName %s", svc.ServiceName)) - continue - } - - serviceList = append(serviceList, serviceObj) - } - serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*500, serviceList, handler, log) - if serviceGroupErr != nil { - errList = multierror.Append(errList, serviceGroupErr...) - } + err = proceedHelmRelease(args.ProductName, args.EnvName, args, renderset, helmClient, nil, log) + if err != nil { + log.Errorf("error occurred when installing services in env: %s/%s, err: %s ", args.ProductName, envName, err) + errList = multierror.Append(errList, err) } // Note: For the sub env, try to supplement information relevant to the base env. @@ -3344,7 +3413,6 @@ func batchExecutor(interval time.Duration, serviceList []*commonmodels.Service, func updateProductGroup(username, productName, envName string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, overrideCharts []*commonservice.RenderChartArg, deletedSvcRevision map[string]int64, log *zap.SugaredLogger) error { var ( - renderChartMap = make(map[string]*templatemodels.RenderChart) productServiceMap = make(map[string]*commonmodels.ProductService) productTemplServiceMap = make(map[string]*commonmodels.ProductService) ) @@ -3379,6 +3447,7 @@ func updateProductGroup(username, productName, envName string, productResp *comm return e.ErrUpdateEnv.AddDesc("对比环境中的value.yaml和系统默认的value.yaml失败") } + productResp.ChartInfos = renderSet.ChartInfos svcNameSet := sets.NewString() for _, singleChart := range overrideCharts { if singleChart.EnvName != envName { @@ -3387,71 +3456,22 @@ func updateProductGroup(username, productName, envName string, productResp *comm svcNameSet.Insert(singleChart.ServiceName) } - for _, renderChart := range renderSet.ChartInfos { - renderChartMap[renderChart.ServiceName] = renderChart - } - - handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { - param, err := buildInstallParam(productResp.Namespace, renderSet.EnvName, renderSet.DefaultValues, renderChartMap[serviceObj.ServiceName], serviceObj) - if err != nil { - return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, err) - } - errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) - if errInstall != nil { - log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, errInstall) - return errors.Wrapf(errInstall, "failed to install or upgrade service %s", serviceObj.ServiceName) - } - return nil + filter := func(svc *commonmodels.ProductService) bool { + return svcNameSet.Has(svc.ServiceName) } - errList := new(multierror.Error) - for groupIndex, services := range productResp.Services { - serviceList := make([]*commonmodels.Service, 0) - for _, svc := range services { - _, ok := renderChartMap[svc.ServiceName] - if !ok { - continue - } - // service is not in update list - if !svcNameSet.Has(svc.ServiceName) { - continue - } - - opt := &commonrepo.ServiceFindOption{ - ServiceName: svc.ServiceName, - Type: svc.Type, - Revision: svc.Revision, - ProductName: svc.ProductName, - ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("failed to find service with opt %+v, err: %s", opt, err) - errList = multierror.Append(errList, errors.Wrapf(err, "failed to find template servce, serviceName %s", svc.ServiceName)) - continue - } - - serviceList = append(serviceList, serviceObj) - } - - serviceGroupErr := batchExecutorWithRetry(3, time.Millisecond*500, serviceList, handler, log) - if serviceGroupErr != nil { - errList = multierror.Append(errList, serviceGroupErr...) - } - - if err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, services); err != nil { - log.Errorf("Failed to update service group %d, err: %s", groupIndex, err) - errList = multierror.Append(errList, err) - } + err = proceedHelmRelease(productName, envName, productResp, renderSet, helmClient, filter, log) + if err != nil { + log.Errorf("error occurred when upgrading services in env: %s/%s, err: %s ", productName, envName, err) + return err } productResp.Render.Revision = renderSet.Revision if err = commonrepo.NewProductColl().Update(productResp); err != nil { log.Errorf("Failed to update env, err: %s", err) - errList = multierror.Append(errList, err) + return err } - - return errList.ErrorOrNil() + return nil } // diffRenderSet get diff between renderset in product and product template @@ -3592,7 +3612,36 @@ func checkServiceImageUpdated(curContainer *commonmodels.Container, serviceInfo return true } -// for keys exist in both yaml, current values will override latest values +// find related keys of service modules when container image changed in env since imported +func getImageChangeInvolvedKeys(prodService *commonmodels.ProductService, revisionSvc, latestSvc *commonmodels.Service) sets.String { + imageSet := sets.NewString() + latestContainer := make(map[string]*commonmodels.Container) + revisionSvcMap := make(map[string]*commonmodels.Container) + for _, c := range latestSvc.Containers { + latestContainer[c.Name] = c + } + for _, c := range revisionSvc.Containers { + revisionSvcMap[c.Name] = c + } + + for _, prodContainer := range prodService.Containers { + revisionSvcContainer, ok := revisionSvcMap[prodContainer.Name] + if !ok { + continue + } + if prodContainer.Image == revisionSvcContainer.Image { + continue + } + lc, ok := latestContainer[prodContainer.Name] + if !ok || lc.ImagePath == nil { + continue + } + imageSet.Insert(lc.ImagePath.Image, lc.ImagePath.Tag, lc.ImagePath.Repo) + } + return imageSet +} + +// for keys exist in both yaml, current values will override the latest values // only for images func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey sets.String) ([]byte, error) { currentValuesMap := map[string]interface{}{} @@ -3641,66 +3690,73 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey return yaml.Marshal(latestValuesMap) } -func updateProductVariable(productName, envName string, productResp *commonmodels.Product, renderset *commonmodels.RenderSet, log *zap.SugaredLogger) error { - helmClient, err := helmtool.NewClientFromNamespace(productResp.ClusterID, productResp.Namespace) - if err != nil { - return e.ErrUpdateEnv.AddErr(err) - } - +func proceedHelmRelease(productName, envName string, productResp *commonmodels.Product, renderset *commonmodels.RenderSet, helmClient *helmtool.HelmClient, filter svcUpgradeFilter, log *zap.SugaredLogger) error { renderChartMap := make(map[string]*templatemodels.RenderChart) for _, renderChart := range productResp.ChartInfos { renderChartMap[renderChart.ServiceName] = renderChart } - handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error { - param, err := buildInstallParam(productResp.Namespace, renderset.EnvName, renderset.DefaultValues, renderChartMap[serviceObj.ServiceName], serviceObj) - if err != nil { - return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, err) + prodServiceMap := productResp.GetServiceMap() + handler := func(serviceObj *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) (err error) { + defer func() { + if prodSvc, ok := prodServiceMap[serviceObj.ServiceName]; ok { + if err != nil { + prodSvc.Error = err.Error() + } else { + prodSvc.Error = "" + } + } + }() + param, errBuildParam := buildInstallParam(productResp.Namespace, renderset.EnvName, renderset.DefaultValues, renderChartMap[serviceObj.ServiceName], serviceObj) + if errBuildParam != nil { + err = fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", serviceObj.ServiceName, productResp.Namespace, errBuildParam) + return } errInstall := installOrUpgradeHelmChartWithValues(param, isRetry, helmClient) if errInstall != nil { log.Errorf("failed to upgrade service: %s, namespace: %s, isRetry: %v, err: %s", serviceObj.ServiceName, productResp.Namespace, isRetry, errInstall) - return errors.Wrapf(errInstall, "failed to upgrade service %s", serviceObj.ServiceName) + err = fmt.Errorf("failed to upgrade service %s, err: %s", serviceObj.ServiceName, errInstall) } - return nil + return } errList := new(multierror.Error) - for groupIndex, services := range productResp.Services { + for groupIndex, groupServices := range productResp.Services { serviceList := make([]*commonmodels.Service, 0) - groupServices := make([]*commonmodels.ProductService, 0) - for _, service := range services { - if _, isExist := renderChartMap[service.ServiceName]; isExist { - opt := &commonrepo.ServiceFindOption{ - ServiceName: service.ServiceName, - Type: service.Type, - Revision: service.Revision, - ProductName: productName, - //ExcludeStatus: setting.ProductStatusDeleting, - } - serviceObj, err := commonrepo.NewServiceColl().Find(opt) - if err != nil { - log.Errorf("failed to find service %s, err %s", service.ServiceName, err.Error()) - continue - } - serviceList = append(serviceList, serviceObj) + for _, service := range groupServices { + if _, ok := renderChartMap[service.ServiceName]; !ok { + continue + } + if filter != nil && !filter(service) { + continue } - groupServices = append(groupServices, service) + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: productName, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + log.Errorf("failed to find service %s, err %s", service.ServiceName, err.Error()) + return err + } + serviceList = append(serviceList, serviceObj) } groupServiceErr := batchExecutorWithRetry(3, time.Millisecond*500, serviceList, handler, log) if groupServiceErr != nil { errList = multierror.Append(errList, groupServiceErr...) } - err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, groupServices) + err := commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, groupServices) if err != nil { - log.Errorf("Failed to update collection - service group %d. Error: %v", groupIndex, err) - return e.ErrUpdateEnv.AddDesc(err.Error()) + log.Errorf("Failed to update service group %d. Error: %v", groupIndex, err) + return err + } + if errList.ErrorOrNil() != nil { + return errList.ErrorOrNil() } } - if err = commonrepo.NewProductColl().Update(productResp); err != nil { - errList = multierror.Append(errList, err) - } - return errList.ErrorOrNil() + return nil } func setFieldValueIsNotExist(obj map[string]interface{}, value interface{}, fields ...string) map[string]interface{} { diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 4a0a5a30..9a19245b 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -81,6 +81,31 @@ func InstallService(helmClient *helmtool.HelmClient, param *ReleaseInstallParam) return nil } +func setProductServiceError(productInfo *commonmodels.Product, serviceName string, err error) error { + foundSvc := false + // update service revision data in product + for groupIndex, svcGroup := range productInfo.Services { + for _, svc := range svcGroup { + if svc.ServiceName == serviceName { + if err != nil { + svc.Error = err.Error() + } else { + svc.Error = "" + } + foundSvc = true + break + } + } + if foundSvc { + if err := commonrepo.NewProductColl().UpdateGroup(productInfo.EnvName, productInfo.ProductName, groupIndex, svcGroup); err != nil { + return fmt.Errorf("failed to update service group: %s:%s, err: %s ", productInfo.ProductName, productInfo.EnvName, err) + } + break + } + } + return nil +} + func updateServiceRevisionInProduct(productInfo *commonmodels.Product, serviceName string, serviceRevision int64) error { foundSvc := false // update service revision data in product @@ -103,7 +128,7 @@ func updateServiceRevisionInProduct(productInfo *commonmodels.Product, serviceNa } // reInstallHelmServiceInEnv uninstall the service and reinstall to update releaseNaming rule -func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { +func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) (finalError error) { productSvcMap, serviceName := productInfo.GetServiceMap(), templateSvc.ServiceName productSvc, ok := productSvcMap[serviceName] // service not applied in this environment @@ -111,11 +136,17 @@ func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *c return nil } - renderInfo, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + var err error + defer func() { + finalError = setProductServiceError(productInfo, templateSvc.ServiceName, err) + }() + + renderInfo, errRender := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ Name: productInfo.Render.Name, Revision: productInfo.Render.Revision}) - if err != nil { - return fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, err) + if errRender != nil { + err = fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, errRender) + return } var renderChart *templatemodels.RenderChart @@ -125,17 +156,20 @@ func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *c } } if renderChart == nil { - return fmt.Errorf("failed to find renderchart: %s", renderChart.ServiceName) + err = fmt.Errorf("failed to find renderchart: %s", renderChart.ServiceName) + return } - helmClient, err := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) - if err != nil { - return err + helmClient, errCreateClient := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) + if errCreateClient != nil { + err = fmt.Errorf("failed to create helm client, err: %s", errCreateClient) + return } - prodSvcTemp, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ServiceName: serviceName, Revision: productSvc.Revision, ProductName: productInfo.ProductName}) - if err != nil { - return fmt.Errorf("failed to get service: %s with revision: %d, err: %s", serviceName, productSvc.Revision, productInfo.ProductName) + prodSvcTemp, errFindProd := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ServiceName: serviceName, Revision: productSvc.Revision, ProductName: productInfo.ProductName}) + if errFindProd != nil { + err = fmt.Errorf("failed to get service: %s with revision: %d, err: %s", serviceName, productSvc.Revision, errFindProd) + return } // nothing would happen if release naming rule are same @@ -143,35 +177,44 @@ func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *c return nil } - if err := UninstallService(helmClient, productInfo, prodSvcTemp, true); err != nil { - return fmt.Errorf("helm uninstall service: %s in namespace: %s, err: %s", serviceName, productInfo.Namespace, err) + if err = UninstallService(helmClient, productInfo, prodSvcTemp, true); err != nil { + err = fmt.Errorf("helm uninstall service: %s in namespace: %s, err: %s", serviceName, productInfo.Namespace, err) + return } - param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) - if err != nil { - return fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", templateSvc.ServiceName, productInfo.Namespace, err) + param, errBuildParam := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) + if errBuildParam != nil { + err = fmt.Errorf("failed to generate install param, service: %s, namespace: %s, err: %s", templateSvc.ServiceName, productInfo.Namespace, errBuildParam) + return } err = InstallService(helmClient, param) if err != nil { - return fmt.Errorf("fauled to install release for service: %s, err: %s", templateSvc.ServiceName, err) + return } - return updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) + err = updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) + return } -func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { +func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, templateSvc *commonmodels.Service) (finalError error) { productSvcMap, serviceName := productInfo.GetServiceMap(), templateSvc.ServiceName // service not applied in this environment if _, ok := productSvcMap[serviceName]; !ok { return nil } - renderInfo, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + var err error + defer func() { + finalError = setProductServiceError(productInfo, templateSvc.ServiceName, err) + }() + + renderInfo, errFindRender := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ Name: productInfo.Render.Name, Revision: productInfo.Render.Revision}) - if err != nil { - return fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, err) + if errFindRender != nil { + err = fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, errFindRender) + return } var renderChart *templatemodels.RenderChart @@ -181,14 +224,16 @@ func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, } } if renderChart == nil { - return fmt.Errorf("failed to find renderchart for service: %s", renderChart.ServiceName) + err = fmt.Errorf("failed to find renderchart for service: %s", renderChart.ServiceName) + return } chartArg := &commonservice.RenderChartArg{} chartArg.FillRenderChartModel(renderChart, renderChart.ChartVersion) - newRenderSet, err := diffRenderSet(userName, productInfo.ProductName, productInfo.EnvName, productInfo, []*commonservice.RenderChartArg{chartArg}, log.SugaredLogger()) - if err != nil { - return fmt.Errorf("failed to build renderset for service: %s, err: %s", renderChart.ServiceName, err) + newRenderSet, errDiffRender := diffRenderSet(userName, productInfo.ProductName, productInfo.EnvName, productInfo, []*commonservice.RenderChartArg{chartArg}, log.SugaredLogger()) + if errDiffRender != nil { + err = fmt.Errorf("failed to build renderset for service: %s, err: %s", renderChart.ServiceName, errDiffRender) + return } // update renderChart @@ -198,14 +243,15 @@ func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, } } - helmClient, err := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) - if err != nil { - return err + helmClient, errCreateClient := helmtool.NewClientFromNamespace(productInfo.ClusterID, productInfo.Namespace) + if errCreateClient != nil { + err = fmt.Errorf("failed to create helm client, err: %s", errCreateClient) + return } - param, err := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) + param, errBuildParam := buildInstallParam(productInfo.Namespace, productInfo.EnvName, renderInfo.DefaultValues, renderChart, templateSvc) if err != nil { - return fmt.Errorf("failed to generate install param, service: %s err: %s", templateSvc.ServiceName, err) + return fmt.Errorf("failed to generate install param, service: %s err: %s", templateSvc.ServiceName, errBuildParam) } err = InstallService(helmClient, param) @@ -213,7 +259,8 @@ func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, return fmt.Errorf("fauled to install release for service: %s, err: %s", templateSvc.ServiceName, err) } - return updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) + err = updateServiceRevisionInProduct(productInfo, templateSvc.ServiceName, templateSvc.Revision) + return } func updateK8sServiceInEnv(productInfo *commonmodels.Product, templateSvc *commonmodels.Service) error { @@ -238,7 +285,6 @@ func ReInstallHelmSvcInAllEnvs(productName string, templateSvc *commonmodels.Ser err := reInstallHelmServiceInEnv(product, templateSvc) if err != nil { retErr = multierror.Append(retErr, fmt.Errorf("failed to update service: %s/%s, err: %s", product.EnvName, templateSvc.ServiceName, err)) - } } return retErr.ErrorOrNil() diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 6dd0e83e..eb4c8129 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -25,7 +26,10 @@ import ( "github.com/hashicorp/go-multierror" "github.com/otiai10/copy" + "github.com/pkg/errors" "go.uber.org/zap" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/informers" @@ -47,14 +51,28 @@ import ( "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/converter" + jsonutil "github.com/koderover/zadig/pkg/util/json" ) +type HelmReleaseQueryArgs struct { + ProjectName string `json:"projectName" form:"projectName"` + Filter string `json:"filter" form:"filter"` +} + +type ReleaseFilter struct { + Name string `json:"name"` + Status ReleaseStatus `json:"status"` +} + type HelmReleaseResp struct { - ReleaseName string `json:"releaseName"` - ServiceName string `json:"serviceName"` - Revision int `json:"revision"` - Chart string `json:"chart"` - AppVersion string `json:"appVersion"` + ReleaseName string `json:"releaseName"` + ServiceName string `json:"serviceName"` + Revision int `json:"revision"` + Chart string `json:"chart"` + AppVersion string `json:"appVersion"` + Status ReleaseStatus `json:"status"` + Updatable bool `json:"updatable"` + Error string `json:"error"` } type ChartInfo struct { @@ -67,6 +85,56 @@ type HelmChartsResp struct { FileInfos []*types.FileInfo `json:"fileInfos"` } +type ValuesResp struct { + ValuesYaml string `json:"valuesYaml"` +} + +type SvcDataSet struct { + ProdSvc *models.ProductService + TmplSvc *models.Service + SvcRelease *release.Release +} + +type ReleaseStatus string + +const ( + HelmReleaseStatusPending ReleaseStatus = "pending" + HelmReleaseStatusDeployed ReleaseStatus = "deployed" + HelmReleaseStatusFailed ReleaseStatus = "failed" + HelmReleaseStatusNotDeployed ReleaseStatus = "notDeployed" +) + +func listReleaseInNamespace(helmClient *helmtool.HelmClient, filter *ReleaseFilter) ([]*release.Release, error) { + listClient := action.NewList(helmClient.ActionConfig) + + switch filter.Status { + case HelmReleaseStatusDeployed: + listClient.StateMask = action.ListDeployed + case HelmReleaseStatusFailed: + listClient.StateMask = action.ListFailed | action.ListSuperseded + case HelmReleaseStatusPending: + listClient.StateMask = action.ListPendingRollback | action.ListPendingUpgrade | action.ListPendingInstall | action.ListUninstalling + default: + listClient.StateMask = action.ListAll + } + listClient.Filter = filter.Name + + return listClient.Run() +} + +func getReleaseStatus(re *release.Release) ReleaseStatus { + switch re.Info.Status { + case release.StatusDeployed: + return HelmReleaseStatusDeployed + case release.StatusFailed, release.StatusSuperseded: + return HelmReleaseStatusFailed + case release.StatusPendingRollback, release.StatusPendingUpgrade, release.StatusPendingInstall, release.StatusUninstalling: + return HelmReleaseStatusPending + default: + return HelmReleaseStatusFailed + } +} + type ImageData struct { ImageName string `json:"imageName"` ImageTag string `json:"imageTag"` @@ -81,49 +149,194 @@ type ChartImagesResp struct { ServiceImages []*ServiceImages `json:"serviceImages"` } -func ListReleases(productName, envName string, log *zap.SugaredLogger) ([]*HelmReleaseResp, error) { - opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} +func GetChartValues(projectName, envName, serviceName string) (*ValuesResp, error) { + opt := &commonrepo.ProductFindOptions{Name: projectName, EnvName: envName} prod, err := commonrepo.NewProductColl().Find(opt) if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + return nil, fmt.Errorf("failed to find project: %s, err: %s", projectName, err) } restConfig, err := kube.GetRESTConfig(prod.ClusterID) if err != nil { - log.Errorf("GetRESTConfig error: %v", err) - return nil, e.ErrCreateDeliveryVersion.AddDesc(err.Error()) + log.Errorf("GetRESTConfig error: %s", err) + return nil, fmt.Errorf("failed to get k8s rest config, err: %s", err) } helmClient, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) if err != nil { - log.Errorf("[%s][%s] NewClientFromRestConf error: %v", envName, productName, err) - return nil, e.ErrCreateDeliveryVersion.AddErr(err) + log.Errorf("[%s][%s] NewClientFromRestConf error: %s", envName, projectName, err) + return nil, fmt.Errorf("failed to init helm client, err: %s", err) + } + + serviceMap := prod.GetServiceMap() + prodSvc, ok := serviceMap[serviceName] + if !ok { + return nil, fmt.Errorf("failed to find sercice: %s in env: %s", serviceName, envName) } - releases, err := helmClient.ListDeployedReleases() + revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Revision: prodSvc.Revision, + ProductName: prodSvc.ProductName, + }) if err != nil { - return nil, e.ErrCreateDeliveryVersion.AddErr(err) + return nil, err } - // filter releases, only list releases deployed by zadig - releaseNameMap, err := commonservice.GetReleaseNameToServiceNameMap(prod) + releaseName := util.GeneReleaseName(revisionSvc.GetReleaseNaming(), prodSvc.ProductName, prod.Namespace, prod.EnvName, prodSvc.ServiceName) + valuesMap, err := helmClient.GetReleaseValues(releaseName, true) if err != nil { + log.Errorf("failed to get values map data, err: %s", err) return nil, err } - ret := make([]*HelmReleaseResp, 0, len(releases)) - for _, release := range releases { - serviceName, ok := releaseNameMap[release.Name] - if !ok { + currentValuesYaml, err := yaml.Marshal(valuesMap) + if err != nil { + return nil, err + } + + return &ValuesResp{ValuesYaml: string(currentValuesYaml)}, nil +} + +func ListReleases(args *HelmReleaseQueryArgs, envName string, log *zap.SugaredLogger) ([]*HelmReleaseResp, error) { + projectName, filterStr := args.ProjectName, args.Filter + opt := &commonrepo.ProductFindOptions{Name: projectName, EnvName: envName} + prod, err := commonrepo.NewProductColl().Find(opt) + if err != nil { + return nil, fmt.Errorf("failed to find project: %s, err: %s", projectName, err) + } + + restConfig, err := kube.GetRESTConfig(prod.ClusterID) + if err != nil { + log.Errorf("GetRESTConfig error: %s", err) + return nil, fmt.Errorf("failed to get k8s rest config, err: %s", err) + } + helmClientInterface, err := helmtool.NewClientFromRestConf(restConfig, prod.Namespace) + if err != nil { + log.Errorf("[%s][%s] NewClientFromRestConf error: %s", envName, projectName, err) + return nil, fmt.Errorf("failed to init helm client, err: %s", err) + } + + filter := &ReleaseFilter{} + if len(filterStr) > 0 { + data, err := jsonutil.ToJSON(filterStr) + if err != nil { + log.Warnf("invalid filter, err: %s", err) + return nil, fmt.Errorf("invalid filter, err: %s", err) + } + + if err = json.Unmarshal(data, filter); err != nil { + log.Warnf("Invalid filter, err: %s", err) + return nil, fmt.Errorf("invalid filter, err: %s", err) + } + } + + releases, err := listReleaseInNamespace(helmClientInterface, filter) + if err != nil { + return nil, fmt.Errorf("failed to list release, err: %s", err) + } + releaseMap := make(map[string]*release.Release) + for _, re := range releases { + releaseMap[re.Name] = re + } + + releaseNameMap, err := commonservice.GetServiceNameToReleaseNameMap(prod) + if err != nil { + return nil, fmt.Errorf("failed to build release-service map: %s", err) + } + + svcDatSetMap := make(map[string]*SvcDataSet) + svcDataList := make([]*SvcDataSet, 0) + + for _, svcGroup := range prod.Services { + for _, prodSvc := range svcGroup { + serviceName := prodSvc.ServiceName + releaseName := releaseNameMap[serviceName] + svcDataSet := &SvcDataSet{ + ProdSvc: prodSvc, + SvcRelease: releaseMap[releaseName], + } + svcDatSetMap[serviceName] = svcDataSet + svcDataList = append(svcDataList, svcDataSet) + } + } + + // set service template data + serviceTmpls, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(projectName) + if err != nil { + return nil, errors.Errorf("failed to list service templates for project: %s", projectName) + } + for _, tmplSvc := range serviceTmpls { + if svcDataSet, ok := svcDatSetMap[tmplSvc.ServiceName]; ok { + svcDataSet.TmplSvc = tmplSvc + } + } + + filterFunc := func(svcDataSet *SvcDataSet) bool { + // filter by name + if filter.Name != "" && !strings.Contains(svcDataSet.ProdSvc.ServiceName, filter.Name) { + return false + } + // filter by status + switch filter.Status { + case HelmReleaseStatusDeployed: + if svcDataSet.SvcRelease == nil || svcDataSet.SvcRelease.Info.Status != release.StatusDeployed { + return false + } + case HelmReleaseStatusFailed: + if svcDataSet.SvcRelease != nil && svcDataSet.SvcRelease.Info.Status != release.StatusFailed && svcDataSet.SvcRelease.Info.Status != release.StatusSuperseded { + return false + } + if svcDataSet.ProdSvc.Error == "" { + return false + } + case HelmReleaseStatusPending: + if svcDataSet.SvcRelease == nil || (!svcDataSet.SvcRelease.Info.Status.IsPending() && svcDataSet.SvcRelease.Info.Status != release.StatusUninstalling) { + return false + } + case HelmReleaseStatusNotDeployed: + if svcDataSet.SvcRelease != nil { + return false + } + } + return true + } + + ret := make([]*HelmReleaseResp, 0) + + for _, svcDataSet := range svcDataList { + if !filterFunc(svcDataSet) { continue } - ret = append(ret, &HelmReleaseResp{ - ReleaseName: release.Name, - ServiceName: serviceName, - Revision: release.Version, - Chart: release.Chart.Name(), - AppVersion: release.Chart.AppVersion(), - }) + + updatable := false + if svcDataSet.TmplSvc != nil { + if svcDataSet.ProdSvc.Revision != svcDataSet.TmplSvc.Revision { + updatable = true + } + } + + respObj := &HelmReleaseResp{ + ServiceName: svcDataSet.ProdSvc.ServiceName, + Status: HelmReleaseStatusNotDeployed, + Updatable: updatable, + Error: svcDataSet.ProdSvc.Error, + } + + if svcDataSet.SvcRelease != nil { + respObj.ReleaseName = svcDataSet.SvcRelease.Name + respObj.Revision = svcDataSet.SvcRelease.Version + respObj.Chart = svcDataSet.SvcRelease.Chart.Name() + respObj.AppVersion = svcDataSet.SvcRelease.Chart.AppVersion() + respObj.Status = getReleaseStatus(svcDataSet.SvcRelease) + } + + if svcDataSet.ProdSvc.Error != "" { + respObj.Status = HelmReleaseStatusFailed + } + + ret = append(ret, respObj) } + return ret, nil } @@ -161,7 +374,7 @@ func loadChartFilesInfo(productName, serviceName string, revision int64, dir str } //prepare chart version data -func prepareChartVersionData(prod *models.Product, serviceObj *models.Service, renderChart *template.RenderChart, renderset *models.RenderSet) error { +func prepareChartVersionData(prod *models.Product, serviceObj *models.Service) error { productName := prod.ProductName serviceName, revision := serviceObj.ServiceName, serviceObj.Revision base := config.LocalServicePathWithRevision(productName, serviceName, revision) @@ -279,12 +492,7 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog errList = multierror.Append(errList, fmt.Errorf("failed to query service, serviceName: %s, revision: %d", serviceName, revision)) return } - renderChart, ok := chartMap[serviceName] - if !ok { - errList = multierror.Append(errList, fmt.Errorf("failed to find render chart for service %s in target namespace", serviceName)) - return - } - err = prepareChartVersionData(prod, serviceObj, renderChart, renderSet) + err = prepareChartVersionData(prod, serviceObj) if err != nil { errList = multierror.Append(errList, fmt.Errorf("failed to prepare chart info for service %s", serviceObj.ServiceName)) return diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 43e59065..18c18db5 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -98,7 +98,7 @@ func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (b if err != nil { return false, "", err } - equal, err := yamlutil.CheckEqual(valuesYAML, []byte(curValue)) + equal, err := yamlutil.Equal(string(valuesYAML), curValue) if err != nil || equal { return false, "", err } diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 04dc1823..5afbaa28 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -220,7 +220,7 @@ func compareGroupServicesRev(servicesTmpl [][]string, productInfo *commonmodels. var err error serviceRev, err = compareServicesRev(svcTmplNameList, svcList, allServiceTmpls, allRender, newRender, log) if err != nil { - log.Errorf("Failed to compare service revision. Error: %v", err) + log.Errorf("Failed to compare service revision, %s:%s, Error: %v", productInfo.ProductName, productInfo.EnvName, err) return serviceRev, e.ErrListProductsRevision.AddDesc(err.Error()) } diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index b26fb9bd..8a1437c2 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -194,13 +194,11 @@ const ( // annotations HelmReleaseNameAnnotation = "meta.helm.sh/release-name" - EnvCreatedBy = "createdBy" - EnvCreator = "koderover" - PodReady = "ready" - JobReady = "Completed" - PodNotReady = "not ready" - HelmReleaseStatusPending = "Pending" - HelmReleaseStatusDeployed = "Deployed" + EnvCreatedBy = "createdBy" + EnvCreator = "koderover" + PodReady = "ready" + JobReady = "Completed" + PodNotReady = "not ready" APIVersionAppsV1 = "apps/v1" diff --git a/pkg/util/yaml/compare.go b/pkg/util/yaml/compare.go index cc4cb34e..230dc75c 100644 --- a/pkg/util/yaml/compare.go +++ b/pkg/util/yaml/compare.go @@ -22,23 +22,22 @@ import ( "sigs.k8s.io/yaml" ) -func CheckEqual(source, target []byte) (bool, error) { - if source == nil && target == nil { +func Equal(source, target string) (bool, error) { + if source == target { return true, nil } - if source == nil && target != nil { - return false, nil - } - if source != nil && target == nil { - return false, nil - } - sourceMap := make(map[string]interface{}) - if err := yaml.Unmarshal(source, &sourceMap); err != nil { + + sourceYamlObj, targetYamlObj := make(map[string]interface{}), make(map[string]interface{}) + + err := yaml.Unmarshal([]byte(source), &sourceYamlObj) + if err != nil { return false, err } - targetMap := make(map[string]interface{}) - if err := yaml.Unmarshal(target, &targetMap); err != nil { + + err = yaml.Unmarshal([]byte(target), &targetYamlObj) + if err != nil { return false, err } - return reflect.DeepEqual(sourceMap, targetMap), nil + + return reflect.DeepEqual(sourceYamlObj, targetYamlObj), nil } diff --git a/pkg/util/yaml/compare_test.go b/pkg/util/yaml/compare_test.go new file mode 100644 index 00000000..8c624e40 --- /dev/null +++ b/pkg/util/yaml/compare_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yaml + +import ( + . "github.com/onsi/ginkgo" + "github.com/onsi/gomega" +) + +var fromYaml = ` +env: dev +image: + repository: go-sample-site + tag: "0.2.1" +imagePullSecrets: + - name: default-secret +` + +var toYaml = ` +imagePullSecrets: + - name: default-secret +env: dev +image: + repository: go-sample-site + tag: "0.2.1" +` + +var toYaml2 = ` +# 这是一个注释 +# 这是一个注释2 +imagePullSecrets: + - name: default-secret +env: dev +image: + repository: go-sample-site + tag: "0.2.1" +` + +var _ = Describe("Testing yaml equal", func() { + + Context("yaml equal", func() { + It("should work as expected", func() { + equal, err := Equal(fromYaml, toYaml) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(equal).Should(gomega.BeTrue()) + }) + + It("should work as expected", func() { + equal, err := Equal(fromYaml, toYaml2) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(equal).Should(gomega.BeTrue()) + }) + + It("should work as expected", func() { + equal, err := Equal(toYaml, toYaml2) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + gomega.Expect(equal).Should(gomega.BeTrue()) + }) + + }) +}) diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 162e2f96..2a3bb342 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -18,7 +18,7 @@ package yaml import ( . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" + "github.com/onsi/gomega" "github.com/koderover/zadig/pkg/util/converter" ) @@ -128,8 +128,8 @@ var _ = Describe("Testing search", func() { } flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(lcpMatedPaths).Should(gomega.ConsistOf([]map[string]string{ {"image": "image.name", "repo": "image.repo", "tag": "image.tag"}, })) }) @@ -140,8 +140,8 @@ var _ = Describe("Testing search", func() { } flatMap, _ := converter.YamlToFlatMap([]byte(testYaml1)) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(lcpMatedPaths).Should(gomega.ConsistOf([]map[string]string{ {"image": "image.name", "repo": "image.repo", "tag": "image.tag"}, })) }) @@ -153,9 +153,9 @@ var _ = Describe("Testing search", func() { flatMap, _ := converter.YamlToFlatMap([]byte(testYaml2)) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) - Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + gomega.Expect(lcpMatedPaths).Should(gomega.ConsistOf([]map[string]string{ {"image": "svc1.image.name", "repo": "svc1.image.repo", "tag": "svc1.image.tag"}, {"image": "svc2.image.name", "repo": "svc2.image.repo", "tag": "svc2.image.tag"}, })) @@ -166,12 +166,12 @@ var _ = Describe("Testing search", func() { {"repo": "repo", "tag": "tag", "image": "name"}, } flatMap, err := converter.YamlToFlatMap([]byte(testYaml3)) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) - Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + gomega.Expect(lcpMatedPaths).Should(gomega.ConsistOf([]map[string]string{ {"image": "svc1.image.name", "repo": "svc1.image.repo", "tag": "svc1.image.tag"}, {"image": "svc2.image.detail.name", "repo": "svc2.image.repo", "tag": "svc2.image.detail.tag"}, })) @@ -182,12 +182,12 @@ var _ = Describe("Testing search", func() { {"image": "image", "tag": "tag", "repo": "global.hub"}, } flatMap, err := converter.YamlToFlatMap([]byte(testYaml8)) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) - Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + gomega.Expect(lcpMatedPaths).Should(gomega.ConsistOf([]map[string]string{ {"image": "testSpec.image.image", "repo": "repoData1.global.hub", "tag": "testSpec.image.tag"}, {"image": "svc1.image", "repo": "repoData1.global.hub", "tag": "svc1.tag"}, {"image": "svc2.image.image", "repo": "repoData1.global.hub", "tag": "svc2.image.tag"}, @@ -202,8 +202,8 @@ var _ = Describe("Testing search", func() { } flatMap, _ := converter.YamlToFlatMap([]byte(testYaml9)) lcpMatedPaths, err = SearchByPattern(flatMap, pattern) - Expect(err).NotTo(HaveOccurred()) - Expect(lcpMatedPaths).Should(ConsistOf([]map[string]string{ + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + gomega.Expect(lcpMatedPaths).Should(gomega.ConsistOf([]map[string]string{ {"image": "deploy.image.name", "repo": "global.hub", "tag": "deploy.image.tag"}, })) }) -- Gitee From 9273cc54471f6668b0c5e0395871a2f2b39d7016 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Thu, 12 May 2022 16:05:50 +0800 Subject: [PATCH 2339/2647] yaml variable substitution indent length Settings (#1490) Signed-off-by: ddh27 --- .../aslan/core/common/service/render.go | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index ce68f41c..775bd001 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -528,7 +528,31 @@ func RenderValueForString(origin string, rs *commonmodels.RenderSet) string { } rs.SetKVAlias() for _, v := range rs.KVs { - origin = strings.Replace(origin, v.Alias, v.Value, -1) + if v.State == "unused" { + continue + } + origin = replaceAliasValue(origin, v) + } + return origin +} + +func replaceAliasValue(origin string, v *templatemodels.RenderKV) string { + for { + idx := strings.Index(origin, v.Alias) + if idx < 0 { + break + } + spaces := "" + start := 0 + for i := idx - 1; i >= 0; i-- { + if string(origin[i]) != " " { + break + } + start++ + } + spaces = origin[idx-start : idx] + valueStr := strings.Replace(v.Value, "\n", fmt.Sprintf("%s%s", "\n", spaces), -1) + origin = strings.Replace(origin, v.Alias, valueStr, 1) } return origin } -- Gitee From cb4955d833d56835279ae24283bc4fe3f4160ba4 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 12 May 2022 16:06:25 +0800 Subject: [PATCH 2340/2647] add release field to workflowtask interface (#1487) * add release field to workflowtask interface Signed-off-by: mouuii * add release field to workflowtask interface Signed-off-by: mouuii * add release field to workflowtask interface Signed-off-by: mouuii * add release field to workflowtask interface Signed-off-by: mouuii * add release field to workflowtask interface Signed-off-by: mouuii * add release field to workflowtask interface Signed-off-by: mouuii * change to en Signed-off-by: mouuii --- go.mod | 1 + go.sum | 2 + .../aslan/core/workflow/handler/policy.yaml | 3 - .../aslan/core/workflow/handler/sse.go | 27 +++++- .../core/workflow/handler/workflow_task.go | 36 +++++++- .../core/service/types/task/model.go | 20 ++-- pkg/types/dto/workflowtask.go | 91 +++++++++++++++++++ 7 files changed, 163 insertions(+), 17 deletions(-) create mode 100644 pkg/types/dto/workflowtask.go diff --git a/go.mod b/go.mod index 848cbb78..355cf0ec 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 + github.com/jinzhu/copier v0.3.5 // indirect github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mittwald/go-helm-client v0.8.4 diff --git a/go.sum b/go.sum index f9e56ee3..aa9613aa 100644 --- a/go.sum +++ b/go.sum @@ -986,6 +986,8 @@ github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0 github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= +github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml index 1b8b7494..d4774829 100644 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ b/pkg/microservice/aslan/core/workflow/handler/policy.yaml @@ -6,9 +6,6 @@ rules: alias: "查看" description: "" rules: - - method: GET - endpoint: "/api/aslan/delivery/releases" - resourceType: "Workflow" - method: GET endpoint: "api/aslan/workflow/workflow" resourceType: "Workflow" diff --git a/pkg/microservice/aslan/core/workflow/handler/sse.go b/pkg/microservice/aslan/core/workflow/handler/sse.go index 1212adf1..d5f555b3 100644 --- a/pkg/microservice/aslan/core/workflow/handler/sse.go +++ b/pkg/microservice/aslan/core/workflow/handler/sse.go @@ -21,14 +21,16 @@ import ( "strconv" "time" - "k8s.io/apimachinery/pkg/util/wait" - "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "k8s.io/apimachinery/pkg/util/wait" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types/dto" ) func GetPipelineTaskSSE(c *gin.Context) { @@ -119,8 +121,27 @@ func GetWorkflowTaskSSE(c *gin.Context) { ctx.Logger.Errorf("[%s] GetPipelineTaskSSE error: %v", ctx.UserName, err) return false, err } + releases, err := service.ListDeliveryVersion(&service.ListDeliveryVersionArgs{ + TaskId: int(res.TaskID), + ServiceName: res.ServiceName, + ProjectName: res.ProductName, + WorkflowName: res.PipelineName, + }, ctx.Logger) + + re := []dto.Release{} + for _, v := range releases { + re = append(re, dto.Release{ + ID: v.VersionInfo.ID, + Version: v.VersionInfo.Version, + }) + } + var task dto.Task + if err := copier.Copy(&task, res); err != nil { + return false, err + } + task.Releases = re - msgChan <- res + msgChan <- task if time.Since(startTime).Minutes() == float64(60) { ctx.Logger.Warnf("[%s] Query GetPipelineTaskSSE API over 60 minutes", ctx.UserName) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go index 159150b5..0384ba4e 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task.go @@ -25,15 +25,18 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" + "github.com/jinzhu/copier" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types/dto" ) // GetWorkflowArgs find workflow args @@ -162,7 +165,38 @@ func GetWorkflowTask(c *gin.Context) { if workflowType == string(config.TestType) { workflowTypeString = config.TestType } - ctx.Resp, ctx.Err = workflow.GetPipelineTaskV2(taskID, c.Param("name"), workflowTypeString, ctx.Logger) + task, err := workflow.GetPipelineTaskV2(taskID, c.Param("name"), workflowTypeString, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + releases, err := service.ListDeliveryVersion(&service.ListDeliveryVersionArgs{ + TaskId: int(task.TaskID), + ServiceName: task.ServiceName, + ProjectName: task.ProductName, + WorkflowName: task.PipelineName, + }, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + var toReleases []dto.Release + for _, v := range releases { + toReleases = append(toReleases, dto.Release{ + ID: v.VersionInfo.ID, + Version: v.VersionInfo.Version, + }) + } + var toTask dto.Task + if err := copier.Copy(&toTask, task); err != nil { + ctx.Err = err + return + } + toTask.Releases = toReleases + ctx.Resp = toTask + ctx.Err = err + return } func RestartWorkflowTask(c *gin.Context) { diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 7b210489..15b283b6 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -49,26 +49,26 @@ type Task struct { IsDeleted bool `bson:"is_deleted" json:"is_deleted"` IsArchived bool `bson:"is_archived" json:"is_archived"` AgentID string `bson:"agent_id" json:"agent_id"` - // 是否允许同时运行多次 + // is allowed to run multiple times MultiRun bool `bson:"multi_run" json:"multi_run"` - // target 服务名称, k8s为容器名称, 物理机为服务名 + // target is container name when k8s, service name when pm Target string `bson:"target,omitempty" json:"target"` - // 使用预定义编译管理模块中的内容生成SubTasks, - // 查询条件为 服务模板名称: ServiceTmpl, 版本: BuildModuleVer - // 如果为空,则使用pipeline自定义SubTasks + // generate SubTasks with predefine build module, + // query filter param: ServiceTmpl, BuildModuleVer + // if nil,use pipeline self define SubTasks BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - // TaskArgs 单服务工作流任务参数 + // TaskArgs single workflow args TaskArgs *TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` - // WorkflowArgs 多服务工作流任务参数 + // WorkflowArgs multi workflow args WorkflowArgs *WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` - // TestArgs 测试任务参数 + // TestArgs test workflow args TestArgs *TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` - // ServiceTaskArgs 脚本部署工作流任务参数 + // ServiceTaskArgs sh deploy args ServiceTaskArgs *ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ArtifactPackageTaskArgs arguments for artifact-package type tasks ArtifactPackageTaskArgs *ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` - // ConfigPayload 系统配置信息 + // ConfigPayload system config info ConfigPayload *ConfigPayload `json:"config_payload,omitempty"` Error string `bson:"error,omitempty" json:"error,omitempty"` Services [][]*ProductService `bson:"services" json:"services"` diff --git a/pkg/types/dto/workflowtask.go b/pkg/types/dto/workflowtask.go new file mode 100644 index 00000000..21e998a6 --- /dev/null +++ b/pkg/types/dto/workflowtask.go @@ -0,0 +1,91 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dto + +import ( + "sync" + + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" +) + +type Task struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []map[string]interface{} `bson:"sub_tasks" json:"sub_tasks"` + Stages []*models.Stage `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + // is allowed to run multiple times + MultiRun bool `bson:"multi_run" json:"multi_run"` + // target is container name when k8s, service name when pm + Target string `bson:"target,omitempty" json:"target"` + // generate SubTasks with predefine build module, + // query filter param: ServiceTmpl, BuildModuleVer + // if nil,use pipeline self define SubTasks + BuildModuleVer string `bson:"build_module_ver,omitempty" json:"build_module_ver"` + ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` + // TaskArgs single workflow args + TaskArgs *models.TaskArgs `bson:"task_args,omitempty" json:"task_args,omitempty"` + // WorkflowArgs multi workflow args + WorkflowArgs *models.WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` + // TestArgs test workflow args + TestArgs *models.TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` + // ServiceTaskArgs sh deploy args + ServiceTaskArgs *models.ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` + // ArtifactPackageTaskArgs arguments for artifact-package type tasks + ArtifactPackageTaskArgs *models.ArtifactPackageTaskArgs `bson:"artifact_package_args,omitempty" json:"artifact_package_args,omitempty"` + // ConfigPayload system config info + ConfigPayload *models.ConfigPayload `bson:"configpayload" json:"config_payload,omitempty"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + Services [][]*models.ProductService `bson:"services" json:"services"` + Render *models.RenderInfo `bson:"render" json:"render"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + // interface{} 为types.TestReport + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + RwLock sync.Mutex `bson:"-" json:"-"` + ResetImage bool `bson:"resetImage" json:"resetImage"` + ResetImagePolicy setting.ResetImagePolicyType `bson:"reset_image_policy" json:"reset_image_policy"` + TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + Features []string `bson:"features" json:"features"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + Releases []Release `json:"releases"` +} + +type Release struct { + ID primitive.ObjectID `json:"id"` + Version string `json:"version"` +} -- Gitee From 69a3fc618a197aa85335c0fcce25b259ebd58617 Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 12 May 2022 17:06:13 +0800 Subject: [PATCH 2341/2647] fix wardrive panic issue (#1491) Signed-off-by: liu deyi --- .../core/service/taskplugin/jenkins_plugin.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go index 141074c6..b88b202d 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jenkins_plugin.go @@ -205,14 +205,26 @@ func (j *JenkinsBuildPlugin) Wait(ctx context.Context) { jenkinsClient, err := gojenkins.CreateJenkins(nil, j.Task.JenkinsIntegration.URL, j.Task.JenkinsIntegration.Username, j.Task.JenkinsIntegration.Password).Init(ctx) if err != nil { j.SetStatus(config.StatusFailed) + msg := fmt.Sprintf("failed to create jenkins client, error: %s", err) + j.Log.Error(msg) + j.Task.Error = msg + return } job, err := jenkinsClient.GetJob(ctx, j.Task.JenkinsBuildArgs.JobName) if err != nil { j.SetStatus(config.StatusFailed) + msg := fmt.Sprintf("failed to get jenkins job, error: %s", err) + j.Log.Error(msg) + j.Task.Error = msg + return } build, err := job.GetLastCompletedBuild(ctx) if err != nil { j.SetStatus(config.StatusFailed) + msg := fmt.Sprintf("failed to get last completed build, error: %s", err) + j.Log.Error(msg) + j.Task.Error = msg + return } jenkinsBuildStatus := j.matchStatus(jobStatus, build.GetResult()) j.SetStatus(jenkinsBuildStatus) -- Gitee From 0fe9999509cdb9e38fbede4e495d05712e56a1ef Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Thu, 12 May 2022 19:01:20 +0800 Subject: [PATCH 2342/2647] CodeScan Task implementation (#1416) * add code scan config apis Signed-off-by: Min Min * debug Signed-off-by: Min Min * debugs Signed-off-by: Min Min * debug Signed-off-by: Min Min * added image type for listing customimages Signed-off-by: Min Min * task related api implementation Signed-off-by: Min Min * logs API Signed-off-by: Min Min * debug Signed-off-by: Min Min * debug Signed-off-by: Min Min * warpdrive first change Signed-off-by: Min Min * warpdrive second change Signed-off-by: Min Min * minor fix Signed-off-by: Min Min * minor change of wd and reaper to make things work Signed-off-by: Min Min * changed the way sonar parameter works Signed-off-by: Min Min * stuff Signed-off-by: Min Min * comment changes Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * task format debug logs Signed-off-by: Min Min * even more debug logs Signed-off-by: Min Min * fixed sonarQube type check Signed-off-by: Min Min * sonar part seems to be done Signed-off-by: Min Min * minor debug for sonar scan Signed-off-by: Min Min * added debug functionality Signed-off-by: Min Min * changed the way to handle logs Signed-off-by: Min Min * sonar credential fix Signed-off-by: Min Min * debug for code scanner directory Signed-off-by: Min Min * debug sleep Signed-off-by: Min Min * debug Signed-off-by: Min Min * improved error log to make debug easier Signed-off-by: Min Min * debug for log functionality Signed-off-by: Min Min * removed test used logs Signed-off-by: Min Min * non-sse logs implementation changes Signed-off-by: Min Min * debugs Signed-off-by: Min Min * parameter naming change Signed-off-by: Min Min * added response for create scanning task API Signed-off-by: Min Min * stuff Signed-off-by: Min Min * stuff Signed-off-by: Min Min * ssse logs fix Signed-off-by: Min Min * improve debug logs to make things easier Signed-off-by: Min Min * debug for sse logs Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * stuff Signed-off-by: Min Min * repo info included Signed-off-by: Min Min * security changes Signed-off-by: Min Min * etc type scanner fix Signed-off-by: Min Min * sse logs tail lines stuff Signed-off-by: Min Min * added some statistics for List scan modules Signed-off-by: Min Min * average run time implementation Signed-off-by: Min Min * removed unused module Signed-off-by: Min Min * bugfix Signed-off-by: Min Min * bugfix & frontend changes Signed-off-by: Min Min * added github webhook support Signed-off-by: Min Min * debug scanning trigger for github Signed-off-by: Min Min * more debug logs Signed-off-by: Min Min * even more debug logs Signed-off-by: Min Min * even more debug logs Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * bugfix Signed-off-by: Min Min * gitlab webhook done Signed-off-by: Min Min * minor bug fix Signed-off-by: Min Min * multiple bug fix Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * stuff Signed-off-by: Min Min --- pkg/microservice/aslan/config/consts.go | 3 + .../common/repository/models/basic_images.go | 3 + .../core/common/repository/models/scanning.go | 47 ++ .../common/repository/models/task/scanning.go | 56 +++ .../common/repository/mongodb/basic_images.go | 5 + .../common/repository/mongodb/scanning.go | 160 +++++++ .../core/common/repository/mongodb/sonar.go | 4 +- .../aslan/core/common/service/base/convert.go | 12 +- .../core/common/service/webhook/client.go | 1 + .../aslan/core/common/service/workflow.go | 12 + .../aslan/core/log/handler/log.go | 26 ++ .../aslan/core/log/handler/router.go | 2 + .../aslan/core/log/handler/sse.go | 47 ++ .../aslan/core/log/service/log.go | 16 + .../aslan/core/log/service/sse.go | 2 +- pkg/microservice/aslan/core/service.go | 1 + .../aslan/core/system/handler/basic_images.go | 2 +- .../aslan/core/system/service/basic_images.go | 3 +- .../aslan/core/workflow/handler/webhook.go | 6 + .../core/workflow/service/webhook/github.go | 34 ++ .../service/webhook/github_scanning_task.go | 201 +++++++++ .../core/workflow/service/webhook/gitlab.go | 16 + .../service/webhook/gitlab_scanning_task.go | 192 +++++++++ .../core/workflow/service/webhook/utils.go | 13 + .../core/workflow/service/workflow/sort.go | 1 + .../core/workflow/testing/handler/router.go | 19 + .../core/workflow/testing/handler/scanning.go | 267 ++++++++++++ .../core/workflow/testing/service/scanning.go | 402 ++++++++++++++++++ .../core/workflow/testing/service/types.go | 122 ++++++ .../reaper/core/service/meta/types.go | 7 + .../reaper/core/service/reaper/reaper.go | 148 ++++--- .../reaper/core/service/reaper/script.go | 42 ++ pkg/microservice/warpdrive/config/const.go | 3 + .../service/taskcontroller/task_handler.go | 3 + .../service/taskcontroller/task_helper.go | 1 + .../warpdrive/core/service/taskplugin/job.go | 6 +- .../core/service/taskplugin/scanning.go | 354 +++++++++++++++ .../core/service/taskplugin/utils.go | 8 + .../warpdrive/core/service/types/reaper.go | 7 + .../core/service/types/task/scanning.go | 46 ++ pkg/setting/consts.go | 1 + pkg/tool/errors/http_errors.go | 4 + pkg/types/reaper.go | 9 +- pkg/types/scanning.go | 50 +++ 44 files changed, 2287 insertions(+), 77 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/scanning.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/task/scanning.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/scanning.go create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go create mode 100644 pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go create mode 100644 pkg/microservice/aslan/core/workflow/testing/handler/scanning.go create mode 100644 pkg/microservice/aslan/core/workflow/testing/service/scanning.go create mode 100644 pkg/microservice/aslan/core/workflow/testing/service/types.go create mode 100644 pkg/microservice/warpdrive/core/service/taskplugin/scanning.go create mode 100644 pkg/microservice/warpdrive/core/service/types/task/scanning.go create mode 100644 pkg/types/scanning.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index eb58e0b1..8e44f40e 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -88,6 +88,8 @@ const ( WorkflowTypeV3 PipelineType = "workflow_v3" // ArtifactPackageType package artifact ArtifactType PipelineType = "artifact" + // ScanningType is the type for scanning + ScanningType PipelineType = "scanning" ) type Status string @@ -133,6 +135,7 @@ const ( TaskArtifactDeploy TaskType = "artifact_deploy" TaskDeploy TaskType = "deploy" TaskTestingV2 TaskType = "testingv2" + TaskScanning TaskType = "scanning" TaskDistributeToS3 TaskType = "distribute2kodo" TaskReleaseImage TaskType = "release_image" TaskJira TaskType = "jira" diff --git a/pkg/microservice/aslan/core/common/repository/models/basic_images.go b/pkg/microservice/aslan/core/common/repository/models/basic_images.go index d250ddb4..eaf81186 100644 --- a/pkg/microservice/aslan/core/common/repository/models/basic_images.go +++ b/pkg/microservice/aslan/core/common/repository/models/basic_images.go @@ -34,6 +34,9 @@ type BasicImage struct { CreateTime int64 `bson:"create_time" json:"create_time"` UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` + // New field since 1.12 + // ImageType is the type of the image, currently only one type is supported called sonar + ImageType string `bson:"image_type" json:"image_type"` } func (BasicImage) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/scanning.go b/pkg/microservice/aslan/core/common/repository/models/scanning.go new file mode 100644 index 00000000..b18601e0 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/scanning.go @@ -0,0 +1,47 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/types" +) + +type Scanning struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + ProjectName string `bson:"project_name" json:"project_name"` + Description string `bson:"description" json:"description"` + ScannerType string `bson:"scanner_type" json:"scanner_type"` + ImageID string `bson:"image_id" json:"image_id"` + SonarID string `bson:"sonar_id" json:"sonar_id"` + Repos []*types.Repository `bson:"repos" json:"repos"` + // Parameter is for sonarQube type only + Parameter string `bson:"parameter" json:"parameter"` + // Script is for other type only + Script string `bson:"script" json:"script"` + AdvancedSetting *types.ScanningAdvancedSetting `bson:"advanced_setting" json:"advanced_setting"` + + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + UpdatedBy string `bson:"updated_by" json:"updated_by"` +} + +func (Scanning) TableName() string { + return "scanning" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/task/scanning.go b/pkg/microservice/aslan/core/common/repository/models/task/scanning.go new file mode 100644 index 00000000..f38c90f4 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/task/scanning.go @@ -0,0 +1,56 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" +) + +type Scanning struct { + TaskType config.TaskType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + ScanningID string `bson:"scanning_id" json:"scanning_id"` + Name string `bson:"name" json:"name"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + ImageInfo string `bson:"image_info" json:"image_info"` + SonarInfo *types.SonarInfo `bson:"sonar_info" json:"sonar_info"` + Repos []*types.Repository `bson:"repos" json:"repos"` + Proxy *models.Proxy `bson:"proxy" json:"proxy"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + // ResReq defines job requested resources + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + Timeout int64 `bson:"timeout" json:"timeout"` + Registries []*models.RegistryNamespace `bson:"-" json:"registries"` + // Parameter is for sonarQube type only + Parameter string `bson:"parameter" json:"parameter"` + // Script is for other type only + Script string `bson:"script" json:"script"` +} + +func (t *Scanning) ToSubTask() (map[string]interface{}, error) { + var task map[string]interface{} + if err := models.IToi(t, &task); err != nil { + return nil, fmt.Errorf("convert ScanningTask to interface error: %s", err) + } + return task, nil +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/basic_images.go b/pkg/microservice/aslan/core/common/repository/mongodb/basic_images.go index 2d194a86..b3a9c768 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/basic_images.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/basic_images.go @@ -36,6 +36,7 @@ type BasicImageOpt struct { Value string `bson:"value"` Label string `bson:"label"` ImageFrom string `bson:"image_from"` + ImageType string `bson:"image_type"` } type BasicImageColl struct { @@ -86,6 +87,9 @@ func (c *BasicImageColl) List(opt *BasicImageOpt) ([]*models.BasicImage, error) if opt != nil && opt.Value != "" { query["value"] = opt.Value } + if opt != nil && opt.ImageType != "" { + query["image_type"] = opt.ImageType + } ctx := context.Background() resp := make([]*models.BasicImage, 0) @@ -130,6 +134,7 @@ func (c *BasicImageColl) Update(id string, args *models.BasicImage) error { "label": args.Label, "value": args.Value, "image_from": args.ImageFrom, + "image_type": args.ImageType, "update_by": args.UpdateBy, "update_time": time.Now().Unix(), }} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/scanning.go b/pkg/microservice/aslan/core/common/repository/mongodb/scanning.go new file mode 100644 index 00000000..8f11cf09 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/scanning.go @@ -0,0 +1,160 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ScanningListOption struct { + ProjectName string +} + +type ScanningColl struct { + *mongo.Collection + + coll string +} + +func NewScanningColl() *ScanningColl { + name := models.Scanning{}.TableName() + return &ScanningColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *ScanningColl) GetCollectionName() string { + return c.coll +} + +func (c *ScanningColl) EnsureIndex(ctx context.Context) error { + mod := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "project_name", Value: 1}, + bson.E{Key: "name", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mod) + + return err +} + +func (c *ScanningColl) Create(scanning *models.Scanning) error { + if scanning == nil { + return errors.New("nil scanning args") + } + + scanning.CreatedAt = time.Now().Unix() + + _, err := c.InsertOne(context.TODO(), scanning) + return err +} + +func (c *ScanningColl) Update(idString string, scanning *models.Scanning) error { + if scanning == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + + scanning.UpdatedAt = time.Now().Unix() + + filter := bson.M{"_id": id} + update := bson.M{"$set": scanning} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *ScanningColl) List(listOption *ScanningListOption, pageNum, pageSize int64) ([]*models.Scanning, int64, error) { + query := bson.M{} + resp := make([]*models.Scanning, 0) + ctx := context.Background() + + opt := options.Find() + + if pageNum != 0 && pageSize != 0 { + opt. + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + } + + if listOption != nil { + query["project_name"] = listOption.ProjectName + } + + cursor, err := c.Collection.Find(ctx, query, opt) + if err != nil { + return nil, 0, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, 0, err + } + count, err := c.Collection.CountDocuments(ctx, query) + if err != nil { + return nil, 0, err + } + + return resp, count, nil +} + +func (c *ScanningColl) GetByID(idstring string) (*models.Scanning, error) { + resp := new(models.Scanning) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *ScanningColl) DeleteByID(idstring string) error { + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go b/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go index 278261c3..f0f3e0da 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/sonar.go @@ -70,9 +70,7 @@ func (c *SonarIntegrationColl) List(ctx context.Context, pageNum, pageSize int64 opt := options.Find() if pageNum != 0 && pageSize != 0 { - opt = opt. - SetSkip((pageNum - 1) * pageSize). - SetLimit(pageSize) + opt = opt.SetSkip((pageNum - 1) * pageSize).SetLimit(pageSize) } cursor, err := c.Collection.Find(ctx, query, opt) diff --git a/pkg/microservice/aslan/core/common/service/base/convert.go b/pkg/microservice/aslan/core/common/service/base/convert.go index 6a44a7bd..db6460a1 100644 --- a/pkg/microservice/aslan/core/common/service/base/convert.go +++ b/pkg/microservice/aslan/core/common/service/base/convert.go @@ -31,7 +31,15 @@ type Preview struct { func ToPreview(sb map[string]interface{}) (*Preview, error) { var pre *Preview if err := task.IToi(sb, &pre); err != nil { - return nil, fmt.Errorf("convert interface to SubTaskPreview error: %v", err) + return nil, fmt.Errorf("convert interface to SubTaskPreview error: %s", err) + } + return pre, nil +} + +func ToScanning(sb map[string]interface{}) (*task.Scanning, error) { + var pre *task.Scanning + if err := task.IToi(sb, &pre); err != nil { + return nil, fmt.Errorf("convert interface to Scanning error: %s", err) } return pre, nil } @@ -39,7 +47,7 @@ func ToPreview(sb map[string]interface{}) (*Preview, error) { func ToBuildTask(sb map[string]interface{}) (*task.Build, error) { var t *task.Build if err := task.IToi(sb, &t); err != nil { - return nil, fmt.Errorf("convert interface to BuildTaskV2 error: %v", err) + return nil, fmt.Errorf("convert interface to BuildTaskV2 error: %s", err) } return t, nil } diff --git a/pkg/microservice/aslan/core/common/service/webhook/client.go b/pkg/microservice/aslan/core/common/service/webhook/client.go index 185c27c7..8bd83b93 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/client.go +++ b/pkg/microservice/aslan/core/common/service/webhook/client.go @@ -27,6 +27,7 @@ const ( ColliePrefix = "collie-" ServicePrefix = "service-" TestingPrefix = "testing-" + ScannerPrefix = "scanning-" taskTimeoutSecond = 10 ) diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index 330ad882..ca8537dc 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) func DeleteWorkflows(productName, requestID string, log *zap.SugaredLogger) error { @@ -327,6 +328,17 @@ func toHookSet(hooks interface{}) HookSet { codeHostID: h.MainRepo.CodehostID, }) } + case []*types.ScanningHook: + for _, h := range hs { + res.Insert(hookItem{ + hookUniqueID: hookUniqueID{ + name: h.RepoName, + owner: h.RepoOwner, + repo: h.RepoName, + }, + codeHostID: h.CodehostID, + }) + } } return res diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 68d4e5eb..62ba19b0 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -18,6 +18,7 @@ package handler import ( "context" + "fmt" "strconv" "strings" @@ -128,3 +129,28 @@ func GetWorkflowBuildV3JobContainerLogs(c *gin.Context) { // Use all lowercase job names to avoid subdomain errors ctx.Resp, ctx.Err = logservice.GetWorkflowBuildV3JobContainerLogs(strings.ToLower(c.Param("workflowName")), c.Query("type"), taskID, ctx.Logger) } + +func GetScanningContainerLogs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = fmt.Errorf("id must be provided") + return + } + + taskIDStr := c.Param("scan_id") + if taskIDStr == "" { + ctx.Err = fmt.Errorf("scan_id must be provided") + return + } + + taskID, err := strconv.ParseInt(taskIDStr, 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Resp, ctx.Err = logservice.GetScanningContainerLogs(id, taskID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/log/handler/router.go b/pkg/microservice/aslan/core/log/handler/router.go index 11034f28..7b366080 100644 --- a/pkg/microservice/aslan/core/log/handler/router.go +++ b/pkg/microservice/aslan/core/log/handler/router.go @@ -34,6 +34,7 @@ func (*Router) Inject(router *gin.RouterGroup) { log.GET("/pipelines/:pipelineName/tasks/:taskId/tests/:testName", GetTestJobContainerLogs) log.GET("/workflow/:pipelineName/tasks/:taskId/tests/:testName/service/:serviceName", GetWorkflowTestJobContainerLogs) log.GET("/v3/workflow/:workflowName/tasks/:taskId", GetWorkflowBuildV3JobContainerLogs) + log.GET("/scanning/:id/task/:scan_id", GetScanningContainerLogs) } sse := router.Group("sse") @@ -45,5 +46,6 @@ func (*Router) Inject(router *gin.RouterGroup) { sse.GET("/workflow/test/:pipelineName/:taskId/:testName/:lines/:serviceName", GetWorkflowTestJobContainerLogsSSE) sse.GET("/service/build/:serviceName/:envName/:productName", GetServiceJobContainerLogsSSE) sse.GET("/v3/workflow/build/:workflowName/:taskId/:lines", GetWorkflowBuildV3JobContainerLogsSSE) + sse.GET("/scanning/:id/task/:scan_id", GetScanningContainerLogsSSE) } } diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index cd1dc7ea..c022d6ba 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -26,6 +26,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" logservice "github.com/koderover/zadig/pkg/microservice/aslan/core/log/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/util/ginzap" @@ -252,3 +253,49 @@ func GetWorkflowBuildV3JobContainerLogsSSE(c *gin.Context) { ctx.Logger) }, ctx.Logger) } + +func GetScanningContainerLogsSSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + + id := c.Param("id") + if id == "" { + ctx.Err = fmt.Errorf("id must be provided") + return + } + + taskIDStr := c.Param("scan_id") + if taskIDStr == "" { + ctx.Err = fmt.Errorf("scan_id must be provided") + return + } + + taskID, err := strconv.ParseInt(taskIDStr, 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + tails, err := strconv.ParseInt(c.Query("lines"), 10, 64) + if err != nil { + tails = int64(10) + } + + resp, err := service.GetScanningModuleByID(id, ctx.Logger) + scanningName := fmt.Sprintf("%s-%s-%s", resp.Name, id, "scanning-job") + options := &logservice.GetContainerOptions{ + Namespace: config.Namespace(), + PipelineName: scanningName, + SubTask: string(config.TaskScanning), + TailLines: tails, + TaskID: taskID, + PipelineType: string(config.ScanningType), + ServiceName: resp.Name, + } + + internalhandler.Stream(c, func(ctx1 context.Context, streamChan chan interface{}) { + logservice.TaskContainerLogStream( + ctx1, streamChan, + options, + ctx.Logger) + }, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index 7b2c94f3..cc1044ca 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -147,3 +147,19 @@ func GetWorkflowBuildV3JobContainerLogs(workflowName, buildType string, taskID i return buildLog, nil } + +func GetScanningContainerLogs(scanID string, taskID int64, log *zap.SugaredLogger) (string, error) { + scanning, err := commonrepo.NewScanningColl().GetByID(scanID) + if err != nil { + log.Errorf("failed to get scanning from db to get scanning detail, the error is: %s", err) + return "", err + } + scanningName := fmt.Sprintf("%s-%s-%s", scanning.Name, scanID, "scanning-job") + scanningLogFilePrefix := fmt.Sprintf("%s-%s-%d-%s", config.ScanningType, scanningName, taskID, config.ScanningType) + buildLog, err := getContainerLogFromS3(scanningName, scanningLogFilePrefix, taskID, log) + if err != nil { + return "", err + } + + return buildLog, nil +} diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index a215f026..76f05b98 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -216,7 +216,7 @@ func waitAndGetLog(ctx context.Context, streamChan chan interface{}, selector la PodCtx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - log.Debugf("Waiting until pod is running before establishing the stream.") + log.Debugf("Waiting until pod is running before establishing the stream. labelSelector: %+v", selector) clientSet, err := kubeclient.GetClientset(config.HubServerAddress(), options.ClusterID) if err != nil { log.Errorf("GetContainerLogs, get client set error: %s", err) diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index e58c8944..618fc25d 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -262,6 +262,7 @@ func initDatabase() { commonrepo.NewPvcColl(), commonrepo.NewEnvSvcDependColl(), commonrepo.NewBuildTemplateColl(), + commonrepo.NewScanningColl(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), diff --git a/pkg/microservice/aslan/core/system/handler/basic_images.go b/pkg/microservice/aslan/core/system/handler/basic_images.go index be11a670..157e668d 100644 --- a/pkg/microservice/aslan/core/system/handler/basic_images.go +++ b/pkg/microservice/aslan/core/system/handler/basic_images.go @@ -43,7 +43,7 @@ func ListBasicImages(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListBasicImages(c.Query("image_from"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListBasicImages(c.Query("image_from"), c.Query("image_type"), ctx.Logger) } func CreateBasicImage(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/basic_images.go b/pkg/microservice/aslan/core/system/service/basic_images.go index 1fe3c19d..5cde81df 100644 --- a/pkg/microservice/aslan/core/system/service/basic_images.go +++ b/pkg/microservice/aslan/core/system/service/basic_images.go @@ -69,9 +69,10 @@ func GetBasicImage(id string, log *zap.SugaredLogger) (*commonmodels.BasicImage, return resp, nil } -func ListBasicImages(imageFrom string, log *zap.SugaredLogger) ([]*commonmodels.BasicImage, error) { +func ListBasicImages(imageFrom string, imageType string, log *zap.SugaredLogger) ([]*commonmodels.BasicImage, error) { opt := &commonrepo.BasicImageOpt{ ImageFrom: imageFrom, + ImageType: imageType, } resp, err := commonrepo.NewBasicImageColl().List(opt) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/handler/webhook.go b/pkg/microservice/aslan/core/workflow/handler/webhook.go index 32af948c..2a2cdab9 100644 --- a/pkg/microservice/aslan/core/workflow/handler/webhook.go +++ b/pkg/microservice/aslan/core/workflow/handler/webhook.go @@ -81,5 +81,11 @@ func processGithub(payload []byte, req *http.Request, requestID string, log *zap log.Errorf("error happens to trigger ProcessGithubWebHookForTest %v", err) errs = multierror.Append(errs, err) } + // webhooks for scanning task + err = webhook.ProcessGithubWebhookForScanning(payload, req, requestID, log) + if err != nil { + log.Errorf("error happens to trigger Scanning for github %v", err) + errs = multierror.Append(errs, err) + } return errs.ErrorOrNil() } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index 4042b17b..b71cd36c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -409,6 +409,40 @@ func ProcessGithubWebHookForTest(payload []byte, req *http.Request, requestID st return nil } +func ProcessGithubWebhookForScanning(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { + hookType := github.WebHookType(req) + if hookType == "integration_installation" || hookType == "installation" || hookType == "ping" { + return nil + } + + err := validateSecret(payload, []byte(gitservice.GetHookSecret()), req) + if err != nil { + return err + } + + event, err := github.ParseWebHook(github.WebHookType(req), payload) + if err != nil { + log.Errorf("Failed to parse webhook, err: %s", err) + return err + } + + deliveryID := github.DeliveryID(req) + + log.Infof("[Webhook] event: %s delivery id: %s received for scanning trigger", hookType, deliveryID) + + switch et := event.(type) { + case *github.PullRequestEvent, *github.PushEvent, *github.CreateEvent: + if err = TriggerScanningByGithubEvent(et, requestID, log); err != nil { + log.Errorf("TriggerScanningByGithubEvent error: %s", err) + return e.ErrGithubWebHook.AddErr(err) + } + default: + log.Warn("Unsupported event type") + return nil + } + return nil +} + func ProcessGithubWebHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { forwardedProto := req.Header.Get("X-Forwarded-Proto") forwardedHost := req.Header.Get("X-Forwarded-Host") diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go new file mode 100644 index 00000000..753e96ca --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go @@ -0,0 +1,201 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "github.com/google/go-github/v35/github" + "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + scanningservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" + "github.com/koderover/zadig/pkg/types" + "go.uber.org/zap" + "strconv" +) + +type gitEventMatcherForScanning interface { + Match(repository *types.ScanningHook) (bool, error) +} + +func TriggerScanningByGithubEvent(event interface{}, requestID string, log *zap.SugaredLogger) error { + //1.find configured testing + scanningList, _, err := commonrepo.NewScanningColl().List(nil, 0, 0) + if err != nil { + log.Errorf("failed to list scanning %v", err) + return err + } + + mErr := &multierror.Error{} + diffSrv := func(pullRequestEvent *github.PullRequestEvent, codehostId int) ([]string, error) { + return findChangedFilesOfPullRequest(pullRequestEvent, codehostId) + } + + log.Infof("Matching scanning list to find matched task to run.") + for _, scanning := range scanningList { + log.Infof("scanning.AdvancedSetting.Hookctl is: [%s], scanning.AdvancedSetting.HookCtl.Enabled is: [%s], scanningName is: [%s]", scanning.AdvancedSetting.HookCtl, scanning.AdvancedSetting.HookCtl.Enabled, scanning.ID) + if scanning.AdvancedSetting.HookCtl != nil && scanning.AdvancedSetting.HookCtl.Enabled { + for _, item := range scanning.AdvancedSetting.HookCtl.Items { + matcher := createGithubEventMatcherForScanning(event, diffSrv, scanning, log) + if matcher == nil { + log.Infof("got a nil matcher for trigger: %s/%s, stopping...", item.RepoOwner, item.RepoName) + continue + } + if matches, err := matcher.Match(item); err != nil { + mErr = multierror.Append(err) + } else if matches { + log.Infof("event match hook %v of %s", item, scanning.Name) + var mergeRequestID string + if ev, isPr := event.(*github.PullRequestEvent); isPr { + mergeRequestID = strconv.Itoa(*ev.PullRequest.Number) + } + triggerRepoInfo := make([]*scanningservice.ScanningRepoInfo, 0) + // for now only one repo is supported + repoInfo := &scanningservice.ScanningRepoInfo{ + CodehostID: item.CodehostID, + Source: item.Source, + RepoOwner: item.RepoOwner, + RepoName: item.RepoName, + Branch: item.Branch, + } + if mergeRequestID != "" { + prID, err := strconv.Atoi(mergeRequestID) + if err != nil { + log.Errorf("failed to convert mergeRequestID: %s to int, error: %s", mergeRequestID, err) + mErr = multierror.Append(mErr, err) + continue + } + repoInfo.PR = prID + } + + triggerRepoInfo = append(triggerRepoInfo, repoInfo) + + if resp, err := scanningservice.CreateScanningTask(scanning.ID.Hex(), triggerRepoInfo, "webhook", log); err != nil { + log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) + mErr = multierror.Append(mErr, err) + } else { + log.Infof("succeed to create task %v", resp) + } + } else { + log.Debugf("event not matches %v", item) + } + } + } + } + return mErr.ErrorOrNil() +} + +type githubPushEventMatcherForScanning struct { + log *zap.SugaredLogger + scanning *commonmodels.Scanning + event *github.PushEvent +} + +func (gpem githubPushEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + ev := gpem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { + matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) + + if !EventConfigured(matchRepo, config.HookEventPush) { + return false, nil + } + + hookRepo.Branch = getBranchFromRef(*ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + + return MatchChanges(matchRepo, changedFiles), nil + } + + return false, nil +} + +type githubMergeEventMatcherForScanning struct { + diffFunc githubPullRequestDiffFunc + log *zap.SugaredLogger + scanning *commonmodels.Scanning + event *github.PullRequestEvent +} + +func (gmem githubMergeEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + ev := gmem.event + // TODO: match codehost + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { + matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) + if !EventConfigured(matchRepo, config.HookEventPr) { + return false, nil + } + + hookRepo.Branch = *ev.PullRequest.Base.Ref + + if *ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err + } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(matchRepo, changedFiles), nil + } + + } + return false, nil +} + +type githubTagEventMatcherForScanning struct { + log *zap.SugaredLogger + scanning *commonmodels.Scanning + event *github.CreateEvent +} + +func (gtem githubTagEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + return false, nil +} + +func createGithubEventMatcherForScanning( + event interface{}, diffSrv githubPullRequestDiffFunc, scanning *commonmodels.Scanning, log *zap.SugaredLogger, +) gitEventMatcherForScanning { + switch evt := event.(type) { + case *github.PushEvent: + return &githubPushEventMatcherForScanning{ + scanning: scanning, + log: log, + event: evt, + } + case *github.PullRequestEvent: + return &githubMergeEventMatcherForScanning{ + diffFunc: diffSrv, + log: log, + event: evt, + scanning: scanning, + } + case *github.CreateEvent: + return &githubTagEventMatcherForScanning{ + scanning: scanning, + log: log, + event: evt, + } + } + + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 9da84cff..ac8d47a6 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -141,6 +141,14 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log errorList = multierror.Append(errorList, err) } }() + + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerScanningByGitlabEvent(pushEvent, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() } if mergeEvent != nil { @@ -170,6 +178,14 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log errorList = multierror.Append(errorList, err) } }() + + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerScanningByGitlabEvent(pushEvent, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() } if tagEvent != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go new file mode 100644 index 00000000..67db7937 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go @@ -0,0 +1,192 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "github.com/hashicorp/go-multierror" + "github.com/xanzy/go-gitlab" + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + scanningservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" + "github.com/koderover/zadig/pkg/types" +) + +func TriggerScanningByGitlabEvent(event interface{}, baseURI, requestID string, log *zap.SugaredLogger) error { + // 1. find configured testing + scanningList, _, err := commonrepo.NewScanningColl().List(nil, 0, 0) + if err != nil { + log.Errorf("failed to list testing %v", err) + return err + } + + mErr := &multierror.Error{} + diffSrv := func(mergeEvent *gitlab.MergeEvent, codehostId int) ([]string, error) { + return findChangedFilesOfMergeRequest(mergeEvent, codehostId) + } + + for _, scanning := range scanningList { + if scanning.AdvancedSetting.HookCtl != nil && scanning.AdvancedSetting.HookCtl.Enabled { + log.Infof("find %d hooks in testing %s", len(scanning.AdvancedSetting.HookCtl.Items), scanning.Name) + for _, item := range scanning.AdvancedSetting.HookCtl.Items { + // 2. match webhook + matcher := createGitlabEventMatcherForScanning(event, diffSrv, scanning, log) + if matcher == nil { + continue + } + + if matches, err := matcher.Match(item); err != nil { + mErr = multierror.Append(mErr, err) + } else if matches { + log.Infof("event match hook %v of %s", item, scanning.Name) + var mergeRequestID int + if ev, isPr := event.(*gitlab.MergeEvent); isPr { + // 如果是merge request,且该webhook触发器配置了自动取消, + // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 + mergeRequestID = ev.ObjectAttributes.IID + } + + triggerRepoInfo := make([]*scanningservice.ScanningRepoInfo, 0) + // for now only one repo is supported + repoInfo := &scanningservice.ScanningRepoInfo{ + CodehostID: item.CodehostID, + Source: item.Source, + RepoOwner: item.RepoOwner, + RepoName: item.RepoName, + Branch: item.Branch, + } + + repoInfo.PR = mergeRequestID + + if resp, err := scanningservice.CreateScanningTask(scanning.ID.Hex(), triggerRepoInfo, "webhook", log); err != nil { + log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) + mErr = multierror.Append(mErr, err) + } else { + log.Infof("succeed to create task %v", resp) + } + } else { + log.Debugf("event not matches %v", item) + } + } + } + } + + return mErr.ErrorOrNil() +} + +func createGitlabEventMatcherForScanning( + event interface{}, diffSrv gitlabMergeRequestDiffFunc, scanning *commonmodels.Scanning, log *zap.SugaredLogger, +) gitEventMatcherForScanning { + switch evt := event.(type) { + case *gitlab.PushEvent: + return &gitlabPushEventMatcherForScanning{ + scanning: scanning, + log: log, + event: evt, + } + case *gitlab.MergeEvent: + return &gitlabMergeEventMatcherForScanning{ + diffFunc: diffSrv, + log: log, + event: evt, + scanning: scanning, + } + case *gitlab.TagEvent: + return &gitlabTagEventMatcherForScanning{ + scanning: scanning, + log: log, + event: evt, + } + } + + return nil +} + +type gitlabPushEventMatcherForScanning struct { + log *zap.SugaredLogger + scanning *commonmodels.Scanning + event *gitlab.PushEvent +} + +func (gpem gitlabPushEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + ev := gpem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) + + if !EventConfigured(matchRepo, config.HookEventPush) { + return false, nil + } + + hookRepo.Branch = getBranchFromRef(ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + + return MatchChanges(matchRepo, changedFiles), nil + } + + return false, nil +} + +type gitlabMergeEventMatcherForScanning struct { + diffFunc gitlabMergeRequestDiffFunc + log *zap.SugaredLogger + scanning *commonmodels.Scanning + event *gitlab.MergeEvent +} + +func (gmem gitlabMergeEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + ev := gmem.event + // TODO: match codehost + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { + matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) + if !EventConfigured(matchRepo, config.HookEventPr) { + return false, nil + } + + hookRepo.Branch = ev.ObjectAttributes.TargetBranch + + if ev.ObjectAttributes.State == "opened" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err + } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + + return MatchChanges(matchRepo, changedFiles), nil + } + + } + return false, nil +} + +type gitlabTagEventMatcherForScanning struct { + log *zap.SugaredLogger + scanning *commonmodels.Scanning + event *gitlab.TagEvent +} + +func (gtem gitlabTagEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + return false, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 46ddddcf..8ed4b450 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "github.com/koderover/zadig/pkg/types" "os" "path" "path/filepath" @@ -572,6 +573,18 @@ func MatchChanges(m *commonmodels.MainHookRepo, files []string) bool { return false } +func ConvertScanningHookToMainHookRepo(hook *types.ScanningHook) *commonmodels.MainHookRepo { + return &commonmodels.MainHookRepo{ + Source: hook.Source, + RepoOwner: hook.RepoOwner, + RepoName: hook.RepoName, + Branch: hook.Branch, + MatchFolders: hook.MatchFolders, + CodehostID: hook.CodehostID, + Events: hook.Events, + } +} + func EventConfigured(m *commonmodels.MainHookRepo, event config.HookEventType) bool { for _, ev := range m.Events { if ev == event { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go index 29c67d47..5e0adc35 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/sort.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/sort.go @@ -50,6 +50,7 @@ var SubtaskOrder = map[config.TaskType]int{ config.TaskType("reset_image"): 15, config.TaskType("trigger"): 16, config.TaskType("extension"): 17, + config.TaskType("scanning"): 18, } type ByStageKind []*commonmodels.Stage diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/router.go b/pkg/microservice/aslan/core/workflow/testing/handler/router.go index ccc94293..c87fd3c2 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/router.go @@ -53,6 +53,25 @@ func (*Router) Inject(router *gin.RouterGroup) { tester.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteTestModule) } + // --------------------------------------------------------------------------------------- + // Code scan APIs + // --------------------------------------------------------------------------------------- + scanner := router.Group("scanning") + { + // code scan config apis + scanner.POST("", GetScanningProductName, gin2.UpdateOperationLogStatus, CreateScanningModule) + scanner.PUT("/:id", GetScanningProductName, gin2.UpdateOperationLogStatus, UpdateScanningModule) + scanner.GET("", ListScanningModule) + scanner.GET("/:id", GetScanningModule) + scanner.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteScanningModule) + + // code scan tasks apis + scanner.POST("/:id/task", gin2.UpdateOperationLogStatus, CreateScanningTask) + scanner.GET("/:id/task", ListScanningTask) + scanner.GET("/:id/task/:scan_id", GetScanningTask) + scanner.GET("/:id/task/:scan_id/sse", GetScanningTaskSSE) + } + testStat := router.Group("teststat") { // 供aslanx的enterprise模块的数据统计调用 diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go new file mode 100644 index 00000000..16260abd --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go @@ -0,0 +1,267 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + e "github.com/koderover/zadig/pkg/tool/errors" + "io/ioutil" + "k8s.io/apimachinery/pkg/util/wait" + "strconv" + "time" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/tool/log" +) + +var MissingIDError = e.ErrInvalidParam.AddDesc("ID must be provided") + +func GetScanningProductName(c *gin.Context) { + args := new(commonmodels.Scanning) + data, err := c.GetRawData() + if err != nil { + log.Errorf("c.GetRawData() err : %s", err) + return + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("json.Unmarshal err : %s", err) + return + } + c.Set("productName", args.ProjectName) + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + c.Next() +} + +func CreateScanningModule(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.Scanning) + data, err := c.GetRawData() + if err != nil { + log.Errorf("Create scanning module c.GetRawData() err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("Create scanning module json.Unmarshal err : %s", err) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "新增", "项目管理-代码扫描", args.Name, string(data), ctx.Logger) + + if err != nil { + ctx.Err = fmt.Errorf("create scanning module err : %s", err) + return + } + + ctx.Err = service.CreateScanningModule(ctx.UserName, args, ctx.Logger) +} + +func UpdateScanningModule(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.Scanning) + data, err := c.GetRawData() + if err != nil { + log.Errorf("Update scanning module c.GetRawData() err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("Update scanning module json.Unmarshal err : %s", err) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProjectName, "修改", "项目管理-代码扫描", args.Name, string(data), ctx.Logger) + + if err != nil { + ctx.Err = fmt.Errorf("update scanning module err : %s", err) + return + } + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + ctx.Err = service.UpdateScanningModule(id, ctx.UserName, args, ctx.Logger) +} + +func ListScanningModule(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + resp, _, err := service.ListScanningModule(c.Query("projectName"), ctx.Logger) + ctx.Resp = resp + ctx.Err = err +} + +func GetScanningModule(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + ctx.Resp, ctx.Err = service.GetScanningModuleByID(id, ctx.Logger) +} + +func DeleteScanningModule(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "删除", "项目管理-测试", c.Param("name"), "", ctx.Logger) + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + ctx.Err = service.DeleteScanningModuleByID(id, ctx.Logger) +} + +type createScanningTaskResp struct { + TaskID int64 `json:"task_id"` +} + +func CreateScanningTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + req := make([]*service.ScanningRepoInfo, 0) + data, err := c.GetRawData() + if err != nil { + log.Errorf("Create scanning task c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, &req); err != nil { + log.Errorf("Create scanning task json.Unmarshal err : %v", err) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "代码扫描任务", id, string(data), ctx.Logger) + + resp, err := service.CreateScanningTask(id, req, ctx.UserName, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + ctx.Resp = &createScanningTaskResp{TaskID: resp} +} + +type listQuery struct { + PageSize int64 `json:"page_size" form:"page_size,default=100"` + PageNum int64 `json:"page_num" form:"page_num,default=1"` +} + +func ListScanningTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + // Query Verification + args := &listQuery{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + ctx.Resp, ctx.Err = service.ListScanningTask(id, args.PageNum, args.PageSize, ctx.Logger) +} + +func GetScanningTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + taskIDStr := c.Param("scan_id") + if taskIDStr == "" { + ctx.Err = fmt.Errorf("scan_id must be provided") + return + } + + taskID, err := strconv.ParseInt(taskIDStr, 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("invalid task id: %s", err)) + return + } + + ctx.Resp, ctx.Err = service.GetScanningTaskInfo(id, taskID, ctx.Logger) +} + +func GetScanningTaskSSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + taskIDStr := c.Param("scan_id") + if taskIDStr == "" { + ctx.Err = fmt.Errorf("scan_id must be provided") + return + } + + taskID, err := strconv.ParseInt(taskIDStr, 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("invalid task id: %s", err)) + return + } + + internalhandler.Stream(c, func(ctx1 context.Context, msgChan chan interface{}) { + err := wait.PollImmediateUntil(time.Second, func() (bool, error) { + res, err := service.GetScanningTaskInfo(id, taskID, ctx.Logger) + if err != nil { + ctx.Logger.Errorf("[%s] Get scanning task info error: %s", ctx.UserName, err) + return false, err + } + + msgChan <- res + + return false, nil + }, ctx1.Done()) + + if err != nil && err != wait.ErrWaitTimeout { + ctx.Logger.Error(err) + } + }, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go new file mode 100644 index 00000000..466ed0ef --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -0,0 +1,402 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "fmt" + "sort" + "time" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" + workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" +) + +func CreateScanningModule(username string, args *Scanning, log *zap.SugaredLogger) error { + if len(args.Name) == 0 { + return e.ErrCreateScanningModule.AddDesc("empty Name") + } + + err := commonservice.ProcessWebhook(args.AdvancedSetting.HookCtl.Items, nil, webhook.ScannerPrefix+args.Name, log) + if err != nil { + return e.ErrCreateScanningModule.AddErr(err) + } + + scanningModule := ConvertToDBScanningModule(args) + scanningModule.UpdatedBy = username + + err = commonrepo.NewScanningColl().Create(scanningModule) + + if err != nil { + log.Errorf("Create scanning module %s error: %s", args.Name, err) + return e.ErrCreateScanningModule.AddErr(err) + } + + return nil +} + +func UpdateScanningModule(id, username string, args *Scanning, log *zap.SugaredLogger) error { + if len(args.Name) == 0 { + return e.ErrCreateScanningModule.AddDesc("empty Name") + } + + scanning, err := commonrepo.NewScanningColl().GetByID(id) + if err != nil { + log.Errorf("failed to get scanning information to update webhook, err: %s", err) + return err + } + + if scanning.AdvancedSetting.HookCtl.Enabled { + err = commonservice.ProcessWebhook(args.AdvancedSetting.HookCtl.Items, scanning.AdvancedSetting.HookCtl.Items, webhook.ScannerPrefix+args.Name, log) + if err != nil { + log.Errorf("failed to process webhook for scanning: %s, the error is: %s", args.Name, err) + return e.ErrUpdateScanningModule.AddErr(err) + } + } else { + err = commonservice.ProcessWebhook(args.AdvancedSetting.HookCtl.Items, nil, webhook.ScannerPrefix+args.Name, log) + if err != nil { + log.Errorf("failed to process webhook for scanning: %s, the error is: %s", args.Name, err) + return e.ErrUpdateScanningModule.AddErr(err) + } + } + + scanningModule := ConvertToDBScanningModule(args) + scanningModule.UpdatedBy = username + + err = commonrepo.NewScanningColl().Update(id, scanningModule) + + if err != nil { + log.Errorf("update scanning module %s error: %s", args.Name, err) + return e.ErrUpdateScanningModule.AddErr(err) + } + + return nil +} + +func ListScanningModule(projectName string, log *zap.SugaredLogger) ([]*ListScanningRespItem, int64, error) { + scanningList, total, err := commonrepo.NewScanningColl().List(&commonrepo.ScanningListOption{ProjectName: projectName}, 0, 0) + if err != nil { + log.Errorf("failed to list scanning list from mongodb, the error is: %s", err) + return nil, 0, err + } + resp := make([]*ListScanningRespItem, 0) + for _, scanning := range scanningList { + res, err := ListScanningTask(scanning.ID.Hex(), 0, 0, log) + if err != nil { + log.Errorf("failed to get scanning task statistics, error is: %s", err) + return nil, 0, err + } + var timesTaken int64 + for _, scanTask := range res.ScanTasks { + timesTaken += scanTask.RunTime + } + var avgRuntime int64 + if len(res.ScanTasks) > 0 { + avgRuntime = timesTaken / int64(len(res.ScanTasks)) + } else { + avgRuntime = 0 + } + resp = append(resp, &ListScanningRespItem{ + ID: scanning.ID.Hex(), + Name: scanning.Name, + Statistics: &ScanningStatistic{ + TimesRun: int64(res.TotalTasks), + AverageRuntime: avgRuntime, + }, + CreatedAt: scanning.CreatedAt, + UpdatedAt: scanning.UpdatedAt, + }) + } + return resp, total, nil +} + +func GetScanningModuleByID(id string, log *zap.SugaredLogger) (*Scanning, error) { + scanning, err := commonrepo.NewScanningColl().GetByID(id) + if err != nil { + log.Errorf("failed to get scanning from mongodb, the error is: %s", err) + return nil, err + } + return ConvertDBScanningModule(scanning), nil +} + +func DeleteScanningModuleByID(id string, log *zap.SugaredLogger) error { + scanning, err := commonrepo.NewScanningColl().GetByID(id) + if err != nil { + log.Errorf("failed to get scanning from mongodb, the error is: %s", err) + return err + } + + err = commonservice.ProcessWebhook(nil, scanning.AdvancedSetting.HookCtl.Items, webhook.ScannerPrefix+scanning.Name, log) + if err != nil { + log.Errorf("failed to process webhook for scanning module: %s, the error is: %s", id, err) + return err + } + + err = commonrepo.NewScanningColl().DeleteByID(id) + if err != nil { + log.Errorf("failed to delete scanning from mongodb, the error is: %s", err) + } + return err +} + +func CreateScanningTask(id string, req []*ScanningRepoInfo, username string, log *zap.SugaredLogger) (int64, error) { + scanningInfo, err := commonrepo.NewScanningColl().GetByID(id) + if err != nil { + log.Errorf("failed to get scanning from mongodb, the error is: %s", err) + return 0, err + } + + scanningName := fmt.Sprintf("%s-%s-%s", scanningInfo.Name, id, "scanning-job") + + nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.ScanningTaskFmt, scanningName)) + if err != nil { + log.Errorf("failed to generated task id for scanning task, error: %s", err) + return 0, e.ErrGetCounter.AddDesc(err.Error()) + } + + imageInfo, err := commonrepo.NewBasicImageColl().Find(scanningInfo.ImageID) + if err != nil { + log.Errorf("failed to get image information to create scanning task, error: %s", err) + return 0, err + } + + registries, err := commonservice.ListRegistryNamespaces("", true, log) + if err != nil { + log.Errorf("ListRegistryNamespaces err:%v", err) + return 0, err + } + + repos := make([]*types.Repository, 0) + for _, arg := range req { + rep, err := systemconfig.New().GetCodeHost(arg.CodehostID) + if err != nil { + log.Errorf("failed to get codehost info from mongodb, the error is: %s", err) + return 0, err + } + repos = append(repos, &types.Repository{ + Source: rep.Type, + RepoOwner: arg.RepoOwner, + RepoName: arg.RepoName, + Branch: arg.Branch, + PR: arg.PR, + CodehostID: arg.CodehostID, + OauthToken: rep.AccessToken, + Address: rep.Address, + Username: rep.Username, + Password: rep.Password, + EnableProxy: rep.EnableProxy, + }) + } + + scanningTask := &task.Scanning{ + TaskType: config.TaskScanning, + Status: config.StatusCreated, + ScanningID: scanningInfo.ID.Hex(), + Name: scanningInfo.Name, + ImageInfo: imageInfo.Value, + ResReq: scanningInfo.AdvancedSetting.ResReq, + ResReqSpec: scanningInfo.AdvancedSetting.ResReqSpec, + Registries: registries, + Parameter: scanningInfo.Parameter, + Script: scanningInfo.Script, + Timeout: DefaultScanningTimeout, + ClusterID: scanningInfo.AdvancedSetting.ClusterID, + Repos: repos, + } + + if scanningInfo.ScannerType == "sonarQube" { + sonarInfo, err := commonrepo.NewSonarIntegrationColl().GetByID(context.TODO(), scanningInfo.SonarID) + if err != nil { + log.Errorf("failed to get sonar integration information to create scanning task, error: %s", err) + return 0, err + } + + scanningTask.SonarInfo = &types.SonarInfo{ + Token: sonarInfo.Token, + ServerAddress: sonarInfo.ServerAddress, + } + } + + proxies, err := commonrepo.NewProxyColl().List(&commonrepo.ProxyArgs{}) + if err != nil { + log.Errorf("failed to get proxy info to create scanning task, error: %s", err) + return 0, err + } + if len(proxies) != 0 { + scanningTask.Proxy = proxies[0] + } + + scanningSubtask, err := scanningTask.ToSubTask() + if err != nil { + log.Errorf("failed to convert scanning subtask, error: %s", err) + return 0, e.ErrCreateTask.AddDesc(err.Error()) + } + + stages := make([]*commonmodels.Stage, 0) + workflowservice.AddSubtaskToStage(&stages, scanningSubtask, scanningInfo.Name) + sort.Sort(workflowservice.ByStageKind(stages)) + + configPayload := commonservice.GetConfigPayload(0) + + defaultS3, err := s3.FindDefaultS3() + if err != nil { + log.Errorf("cannot find the default s3 to store the logs, error: %s", err) + return 0, e.ErrFindDefaultS3Storage.AddDesc("default storage is required by distribute task") + } + + defaultURL, err := defaultS3.GetEncryptedURL() + if err != nil { + log.Errorf("cannot convert the s3 config to an encrypted URI, error: %s", err) + return 0, e.ErrS3Storage.AddErr(err) + } + + finalTask := &task.Task{ + TaskID: nextTaskID, + ProductName: scanningInfo.ProjectName, + PipelineName: scanningName, + Type: config.ScanningType, + Status: config.StatusCreated, + TaskCreator: username, + CreateTime: time.Now().Unix(), + Stages: stages, + ConfigPayload: configPayload, + StorageURI: defaultURL, + } + + if len(finalTask.Stages) <= 0 { + return 0, e.ErrCreateTask.AddDesc(e.PipelineSubTaskNotFoundErrMsg) + } + + if err := workflowservice.CreateTask(finalTask); err != nil { + log.Error(err) + return 0, e.ErrCreateTask + } + + return nextTaskID, nil +} + +func ListScanningTask(id string, pageNum, pageSize int64, log *zap.SugaredLogger) (*ListScanningTaskResp, error) { + scanningInfo, err := commonrepo.NewScanningColl().GetByID(id) + if err != nil { + log.Errorf("failed to get scanning from mongodb, the error is: %s", err) + return nil, err + } + + scanningName := fmt.Sprintf("%s-%s-%s", scanningInfo.Name, id, "scanning-job") + listTaskOpt := &commonrepo.ListTaskOption{ + PipelineName: scanningName, + Limit: int(pageSize), + Skip: int((pageNum - 1) * pageSize), + Detail: true, + Type: config.ScanningType, + } + countTaskOpt := &commonrepo.CountTaskOption{ + PipelineNames: []string{scanningName}, + Type: config.ScanningType, + } + resp, err := commonrepo.NewTaskColl().List(listTaskOpt) + if err != nil { + log.Errorf("failed to list scanning task for scanning: %s, the error is: %s", id, err) + return nil, err + } + cnt, err := commonrepo.NewTaskColl().Count(countTaskOpt) + if err != nil { + log.Errorf("failed to count scanning task for scanning: %s, the error is: %s", id, err) + return nil, err + } + + scanTasks := make([]*ScanningTaskResp, 0) + + for _, scanningTask := range resp { + taskInfo := &ScanningTaskResp{ + ScanID: scanningTask.TaskID, + Status: string(scanningTask.Status), + Creator: scanningTask.TaskCreator, + CreatedAt: scanningTask.CreateTime, + } + if scanningTask.Status == config.StatusPassed || scanningTask.Status == config.StatusCancelled || scanningTask.Status == config.StatusFailed { + taskInfo.RunTime = scanningTask.EndTime - scanningTask.StartTime + } + scanTasks = append(scanTasks, taskInfo) + } + + return &ListScanningTaskResp{ + ScanInfo: &ScanningInfo{ + Editor: scanningInfo.UpdatedBy, + UpdatedAt: scanningInfo.UpdatedAt, + }, + ScanTasks: scanTasks, + TotalTasks: cnt, + }, nil +} + +func GetScanningTaskInfo(scanningID string, taskID int64, log *zap.SugaredLogger) (*ScanningTaskDetail, error) { + scanningInfo, err := commonrepo.NewScanningColl().GetByID(scanningID) + if err != nil { + log.Errorf("failed to get scanning from mongodb, the error is: %s", err) + return nil, err + } + + scanningName := fmt.Sprintf("%s-%s-%s", scanningInfo.Name, scanningID, "scanning-job") + resp, err := commonrepo.NewTaskColl().Find(taskID, scanningName, config.ScanningType) + if err != nil { + log.Errorf("failed to get task information, error: %s", err) + return nil, err + } + + sonarInfo, err := commonrepo.NewSonarIntegrationColl().GetByID(context.TODO(), scanningInfo.SonarID) + if err != nil { + log.Errorf("failed to get sonar integration info, error: %s", err) + return nil, err + } + + repoInfo := resp.Stages[0].SubTasks[scanningInfo.Name] + scanningTaskInfo, err := base.ToScanning(repoInfo) + if err != nil { + log.Errorf("failed to convert the content into scanning subtask, the error is: %s", err) + return nil, fmt.Errorf("failed to convert the content into scanning subtask, the error is: %s", err) + } + + // for security reasons, we set all sensitive information to empty + for _, repo := range scanningTaskInfo.Repos { + repo.OauthToken = "" + repo.Password = "" + repo.Username = "" + } + + return &ScanningTaskDetail{ + Creator: resp.TaskCreator, + Status: string(resp.Status), + CreateTime: resp.CreateTime, + EndTime: resp.EndTime, + RepoInfo: scanningTaskInfo.Repos, + ResultLink: sonarInfo.ServerAddress, + }, nil +} diff --git a/pkg/microservice/aslan/core/workflow/testing/service/types.go b/pkg/microservice/aslan/core/workflow/testing/service/types.go new file mode 100644 index 00000000..f08604d5 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/testing/service/types.go @@ -0,0 +1,122 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/types" +) + +const DefaultScanningTimeout = 60 * 60 + +type Scanning struct { + ID string `json:"id"` + Name string `json:"name"` + ProjectName string `json:"project_name"` + Description string `json:"description"` + ScannerType string `json:"scanner_type"` + ImageID string `json:"image_id"` + SonarID string `json:"sonar_id"` + Repos []*types.Repository `json:"repos"` + // Parameter is for sonarQube type only + Parameter string `json:"parameter"` + // Script is for other type only + Script string `json:"script"` + AdvancedSetting *types.ScanningAdvancedSetting `json:"advanced_settings"` +} + +type ListScanningRespItem struct { + ID string `json:"id"` + Name string `json:"name"` + Statistics *ScanningStatistic `json:"statistics"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +type ScanningRepoInfo struct { + CodehostID int `json:"codehost_id"` + Source string `json:"source"` + RepoOwner string `json:"repo_owner"` + RepoName string `json:"repo_name"` + PR int `json:"pr"` + Branch string `json:"branch"` +} + +type ScanningStatistic struct { + TimesRun int64 `json:"times_run"` + AverageRuntime int64 `json:"run_time_average"` +} + +type ListScanningTaskResp struct { + ScanInfo *ScanningInfo `json:"scan_info"` + ScanTasks []*ScanningTaskResp `json:"scan_tasks"` + TotalTasks int `json:"total_tasks"` +} + +type ScanningInfo struct { + Editor string `json:"editor"` + UpdatedAt int64 `json:"updated_at"` +} + +type ScanningTaskResp struct { + ScanID int64 `json:"scan_id"` + Status string `json:"status"` + RunTime int64 `json:"run_time"` + Creator string `json:"creator"` + CreatedAt int64 `json:"created_at"` +} + +type ScanningTaskDetail struct { + Creator string `json:"creator"` + Status string `json:"status"` + CreateTime int64 `json:"create_time"` + EndTime int64 `json:"end_time"` + RepoInfo []*types.Repository `json:"repo_info"` + ResultLink string `json:"result_link,omitempty"` +} + +func ConvertToDBScanningModule(args *Scanning) *commonmodels.Scanning { + // ID is omitted since they are of different type and there will be no use of it + return &commonmodels.Scanning{ + Name: args.Name, + ProjectName: args.ProjectName, + Description: args.Description, + ScannerType: args.ScannerType, + ImageID: args.ImageID, + SonarID: args.SonarID, + Repos: args.Repos, + Parameter: args.Parameter, + Script: args.Script, + AdvancedSetting: args.AdvancedSetting, + } +} + +func ConvertDBScanningModule(scanning *commonmodels.Scanning) *Scanning { + return &Scanning{ + ID: scanning.ID.Hex(), + Name: scanning.Name, + ProjectName: scanning.ProjectName, + Description: scanning.Description, + ScannerType: scanning.ScannerType, + ImageID: scanning.ImageID, + SonarID: scanning.SonarID, + Repos: scanning.Repos, + Parameter: scanning.Parameter, + Script: scanning.Script, + AdvancedSetting: scanning.AdvancedSetting, + } +} diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 6c914ada..bda4c074 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -130,6 +130,13 @@ type Context struct { UploadEnabled bool `yaml:"upload_enabled"` UploadStorageInfo *types.ObjectStorageInfo `yaml:"upload_storage_info"` UploadInfo []*types.ObjectStoragePathDetail `yaml:"upload_info"` + + // scanner used flag + ScannerFlag bool `yaml:"scanner_flag"` + ScannerType string `yaml:"scanner_type"` + SonarParameter string `yaml:"sonar_parameter"` + SonarServer string `yaml:"sonar_server"` + SonarLogin string `yaml:"sonar_login"` } type ArtifactInfo struct { diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 32620acc..1785ee82 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -72,10 +72,12 @@ func NewReaper() (*Reaper, error) { cm: NewTarCacheManager(ctx.StorageURI, ctx.PipelineName, ctx.ServiceName, ctx.AesKey), } - if ctx.TestType == "" { - reaper.Type = types.BuildReaperType - } else { + if ctx.TestType != "" { reaper.Type = types.TestReaperType + } else if ctx.ScannerFlag { + reaper.Type = types.ScanningReaperType + } else { + reaper.Type = types.BuildReaperType } workspace := "/workspace" @@ -175,79 +177,82 @@ func (r *Reaper) EnsureDir(dir string) error { func (r *Reaper) BeforeExec() error { r.StartTime = time.Now() - log.Infof("Checking Docker Connectivity.") - startTimeCheckDocker := time.Now() - for i := 0; i < 15; i++ { - if err := dockerInfo().Run(); err == nil { - break - } - time.Sleep(time.Second * 1) - } - log.Infof("Check ended. Duration: %.2f seconds.", time.Since(startTimeCheckDocker).Seconds()) - - if r.Ctx.DockerRegistry != nil { - if r.Ctx.DockerRegistry.UserName != "" { - log.Infof("Logining Docker Registry: %s.", r.Ctx.DockerRegistry.Host) - startTimeDockerLogin := time.Now() - cmd := dockerLogin(r.Ctx.DockerRegistry.UserName, r.Ctx.DockerRegistry.Password, r.Ctx.DockerRegistry.Host) - var out bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &out - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to login docker registry: %s", err) + // the execution preparation are not required, so we skip it if the reaper type is scanning + if r.Type != types.ScanningReaperType { + log.Infof("Checking Docker Connectivity.") + startTimeCheckDocker := time.Now() + for i := 0; i < 15; i++ { + if err := dockerInfo().Run(); err == nil { + break } + time.Sleep(time.Second * 1) + } + log.Infof("Check ended. Duration: %.2f seconds.", time.Since(startTimeCheckDocker).Seconds()) + + if r.Ctx.DockerRegistry != nil { + if r.Ctx.DockerRegistry.UserName != "" { + log.Infof("Logining Docker Registry: %s.", r.Ctx.DockerRegistry.Host) + startTimeDockerLogin := time.Now() + cmd := dockerLogin(r.Ctx.DockerRegistry.UserName, r.Ctx.DockerRegistry.Password, r.Ctx.DockerRegistry.Host) + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to login docker registry: %s", err) + } - log.Infof("Login ended. Duration: %.2f seconds.", time.Since(startTimeDockerLogin).Seconds()) + log.Infof("Login ended. Duration: %.2f seconds.", time.Since(startTimeDockerLogin).Seconds()) + } } - } - if r.Ctx.CacheEnable && r.Ctx.Cache.MediumType == types.ObjectMedium { - log.Info("Pulling Cache.") - startTimePullCache := time.Now() - if err := r.DecompressCache(); err != nil { - // If the workflow runs for the first time, there may be no cache. - log.Infof("Failed to pull cache: %s. Duration: %.2f seconds.", err, time.Since(startTimePullCache).Seconds()) - } else { - log.Infof("Succeed to pull cache. Duration: %.2f seconds.", time.Since(startTimePullCache).Seconds()) + if r.Ctx.CacheEnable && r.Ctx.Cache.MediumType == types.ObjectMedium { + log.Info("Pulling Cache.") + startTimePullCache := time.Now() + if err := r.DecompressCache(); err != nil { + // If the workflow runs for the first time, there may be no cache. + log.Infof("Failed to pull cache: %s. Duration: %.2f seconds.", err, time.Since(startTimePullCache).Seconds()) + } else { + log.Infof("Succeed to pull cache. Duration: %.2f seconds.", time.Since(startTimePullCache).Seconds()) + } } - } - if err := os.MkdirAll(path.Join(os.Getenv("HOME"), "/.ssh"), os.ModePerm); err != nil { - return fmt.Errorf("create ssh folder error: %v", err) - } - - if r.Ctx.Archive != nil && len(r.Ctx.Archive.Dir) > 0 { - if err := os.MkdirAll(r.Ctx.Archive.Dir, os.ModePerm); err != nil { - return fmt.Errorf("create DistDir error: %v", err) + if err := os.MkdirAll(path.Join(os.Getenv("HOME"), "/.ssh"), os.ModePerm); err != nil { + return fmt.Errorf("create ssh folder error: %v", err) } - } - if r.Ctx.Git != nil { - if err := r.Ctx.Git.WriteGithubSSHFile(); err != nil { - return fmt.Errorf("write github ssh file error: %v", err) + if r.Ctx.Archive != nil && len(r.Ctx.Archive.Dir) > 0 { + if err := os.MkdirAll(r.Ctx.Archive.Dir, os.ModePerm); err != nil { + return fmt.Errorf("create DistDir error: %v", err) + } } - if err := r.Ctx.Git.WriteGitlabSSHFile(); err != nil { - return fmt.Errorf("write gitlab ssh file error: %v", err) - } + if r.Ctx.Git != nil { + if err := r.Ctx.Git.WriteGithubSSHFile(); err != nil { + return fmt.Errorf("write github ssh file error: %v", err) + } - if err := r.Ctx.Git.WriteKnownHostFile(); err != nil { - return fmt.Errorf("write known_host file error: %v", err) - } + if err := r.Ctx.Git.WriteGitlabSSHFile(); err != nil { + return fmt.Errorf("write gitlab ssh file error: %v", err) + } - if err := r.Ctx.Git.WriteSSHConfigFile(r.Ctx.Proxy); err != nil { - return fmt.Errorf("write ssh config error: %v", err) - } - } + if err := r.Ctx.Git.WriteKnownHostFile(); err != nil { + return fmt.Errorf("write known_host file error: %v", err) + } - if r.Ctx.GinkgoTest != nil && len(r.Ctx.GinkgoTest.ResultPath) > 0 { - r.Ctx.GinkgoTest.ResultPath = filepath.Join(r.ActiveWorkspace, r.Ctx.GinkgoTest.ResultPath) - if err := os.RemoveAll(r.Ctx.GinkgoTest.ResultPath); err != nil { - log.Warning(err.Error()) + if err := r.Ctx.Git.WriteSSHConfigFile(r.Ctx.Proxy); err != nil { + return fmt.Errorf("write ssh config error: %v", err) + } } - if err := os.MkdirAll(r.Ctx.GinkgoTest.ResultPath, os.ModePerm); err != nil { - return fmt.Errorf("create test result path error: %v", err) + if r.Ctx.GinkgoTest != nil && len(r.Ctx.GinkgoTest.ResultPath) > 0 { + r.Ctx.GinkgoTest.ResultPath = filepath.Join(r.ActiveWorkspace, r.Ctx.GinkgoTest.ResultPath) + if err := os.RemoveAll(r.Ctx.GinkgoTest.ResultPath); err != nil { + log.Warning(err.Error()) + } + + if err := os.MkdirAll(r.Ctx.GinkgoTest.ResultPath, os.ModePerm); err != nil { + return fmt.Errorf("create test result path error: %v", err) + } } } @@ -367,12 +372,23 @@ func (r *Reaper) Exec() error { log.Warningf("Failed to create README file: %s", err) } - log.Info("Executing User Build Script.") - startTimeRunBuildScript := time.Now() - if err := r.runScripts(); err != nil { - return fmt.Errorf("failed to execute user build script: %s", err) + // for build/test/scanner of type other than sonar, we run the script + if !r.Ctx.ScannerFlag || r.Ctx.ScannerType != types.ScanningTypeSonar { + log.Info("Executing User Build Script.") + startTimeRunBuildScript := time.Now() + if err := r.runScripts(); err != nil { + return fmt.Errorf("failed to execute user build script: %s", err) + } + log.Infof("Execution ended. Duration: %.2f seconds.", time.Since(startTimeRunBuildScript).Seconds()) + } else { + // for sonar type we write the sonar parameter into config file and go with sonar-scanner command + log.Info("Executing SonarQube Scanning process.") + startTimeRunSonar := time.Now() + if err := r.runSonarScanner(); err != nil { + return fmt.Errorf("failed to execute sonar scanning process, the error is: %s", err) + } + log.Infof("Sonar scan ended. Duration %.2f seconds.", time.Since(startTimeRunSonar).Seconds()) } - log.Infof("Execution ended. Duration: %.2f seconds.", time.Since(startTimeRunBuildScript).Seconds()) return r.runDockerBuild() } diff --git a/pkg/microservice/reaper/core/service/reaper/script.go b/pkg/microservice/reaper/core/service/reaper/script.go index 03aea043..45f8a1a4 100644 --- a/pkg/microservice/reaper/core/service/reaper/script.go +++ b/pkg/microservice/reaper/core/service/reaper/script.go @@ -21,6 +21,7 @@ import ( "encoding/base64" "fmt" "io" + "io/fs" "io/ioutil" "os" "os/exec" @@ -258,6 +259,47 @@ func (r *Reaper) runScripts() error { return cmd.Wait() } +func (r *Reaper) runSonarScanner() error { + // first we write the sonar scanner config to the config file + for _, repo := range r.Ctx.Repos { + log.Infof("Writing sonar-project.properties for repo: %s", repo.Name) + repoConfigPath := filepath.Join("/workspace", repo.Name, "sonar-project.properties") + configContent := fmt.Sprintf("sonar.login=%s\nsonar.host.url=%s\n%s", r.Ctx.SonarLogin, r.Ctx.SonarServer, r.Ctx.SonarParameter) + err := os.WriteFile(repoConfigPath, []byte(configContent), fs.ModeAppend) + if err != nil { + log.Errorf("failed to write sonar-project.properties for repo: %s, the error is: %s", repo.Name, err) + return err + } + } + // then we simply run the sonar-scanner command to commence the scan + cmd := exec.Command("sonar-scanner") + // since currently only one codehost is supported, we will just use the first repository + cmd.Dir = filepath.Join("/workspace", r.Ctx.Repos[0].Name) + fileName := filepath.Join(os.TempDir(), "sonar.log") + util.WriteFile(fileName, []byte{}, 0700) + var wg sync.WaitGroup + + cmdStdoutReader, err := cmd.StdoutPipe() + if err != nil { + return err + } + + wg.Add(1) + go func() { + defer wg.Done() + + r.handleCmdOutput(cmdStdoutReader, false, fileName) + }() + + if err := cmd.Start(); err != nil { + return err + } + + wg.Wait() + + return cmd.Wait() +} + func (r *Reaper) prepareScriptsEnv() []string { scripts := []string{} scripts = append(scripts, "eval $(ssh-agent -s) > /dev/null") diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 3db83f95..5ee27d05 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -37,6 +37,7 @@ const ( TaskTrigger TaskType = "trigger" TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" + TaskScanning TaskType = "scanning" ) type Status string @@ -75,6 +76,8 @@ const ( WorkflowTypeV3 PipelineType = "workflow_v3" // ArtifactType artifact build ArtifactType PipelineType = "artifact" + // ScanningType is the type for scanning + ScanningType PipelineType = "scanning" ) type NotifyType int diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 6ac2be48..52cc5802 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -492,6 +492,9 @@ func (h *ExecHandler) executeTask(taskCtx context.Context, plugin plugins.TaskPl } else if pipelineTask.Type == config.ArtifactType { fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.ArtifactType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), "_", "-", -1) + } else if pipelineTask.Type == config.ScanningType { + fileName = strings.Replace(strings.ToLower(fmt.Sprintf("%s-%s-%d-%s", config.ScanningType, pipelineTask.PipelineName, pipelineTask.TaskID, plugin.Type())), + "_", "-", -1) } plugin.Init(jobName, fileName, xl) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go index 3453133c..6076af28 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_helper.go @@ -571,6 +571,7 @@ func initTaskPlugins(execHandler *ExecHandler) { config.TaskTrigger: plugins.InitializeTriggerTaskPlugin, config.TaskArtifactPackage: plugins.InitializeArtifactPackagePlugin, config.TaskExtension: plugins.InitializeExtensionTaskPlugin, + config.TaskScanning: plugins.InitializeScanningTaskPlugin, } for name, pluginInitiator := range pluginConf { registerTaskPlugin(execHandler, name, pluginInitiator) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 43d92b3c..ba0527b9 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -124,7 +124,7 @@ func saveContainerLog(pipelineTask *task.Task, namespace, clusterID, fileName st if err = saveFile(buf, tempFileName); err == nil { var store *s3.S3 if store, err = s3.NewS3StorageFromEncryptedURI(pipelineTask.StorageURI); err != nil { - log.Errorf("failed to NewS3StorageFromEncryptedURI ") + log.Errorf("failed to Create S3 endpoint from Encrypted URI: %s, the error is: %s ", pipelineTask.StorageURI, err) return err } if store.Subfolder != "" { @@ -374,6 +374,10 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName ctx.ArtifactPath = b.JobCtx.ArtifactPath } + if pipelineTask.Type == config.ScanningType { + ctx.ScannerFlag = true + } + return ctx } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go b/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go new file mode 100644 index 00000000..1eae65dd --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go @@ -0,0 +1,354 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package taskplugin + +import ( + "context" + "fmt" + "github.com/koderover/zadig/pkg/tool/kube/updater" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "math" + "strings" + "time" + + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + + zadigconfig "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" +) + +func InitializeScanningTaskPlugin(taskType config.TaskType) TaskPlugin { + return &ScanPlugin{ + Name: taskType, + kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), + } +} + +type ScanPlugin struct { + Name config.TaskType + KubeNamespace string + JobName string + FileName string + kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config + Task *task.Scanning + Log *zap.SugaredLogger +} + +func (p *ScanPlugin) SetAckFunc(func()) { +} + +const ( + ScanningTaskTimeout = 60 * 60 // 60 minutes + ScanningTypeSonar = "sonarQube" + ScanningTypeOther = "other" +) + +func (p *ScanPlugin) Init(jobname, filename string, xl *zap.SugaredLogger) { + p.JobName = jobname + p.FileName = filename + p.Log = xl +} + +func (p *ScanPlugin) Type() config.TaskType { + return p.Name +} + +func (p *ScanPlugin) Status() config.Status { + return p.Task.Status +} + +func (p *ScanPlugin) SetStatus(status config.Status) { + p.Task.Status = status +} + +func (p *ScanPlugin) TaskTimeout() int { + if p.Task.Timeout == 0 { + p.Task.Timeout = ScanningTaskTimeout + } + return int(p.Task.Timeout) +} + +func (p *ScanPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineCtx *task.PipelineCtx, serviceName string) { + switch p.Task.ClusterID { + case setting.LocalClusterID: + p.KubeNamespace = zadigconfig.Namespace() + default: + p.KubeNamespace = setting.AttachedClusterNamespace + + crClient, clientset, restConfig, err := GetK8sClients(pipelineTask.ConfigPayload.HubServerAddr, p.Task.ClusterID) + if err != nil { + p.Log.Error(err) + p.Task.Status = config.StatusFailed + p.Task.Error = err.Error() + return + } + + p.kubeClient = crClient + p.clientset = clientset + p.restConfig = restConfig + } + + // Since only one repository is supported per scanning, we just hard code it + repo := &task.Repository{ + Source: p.Task.Repos[0].Source, + RepoOwner: p.Task.Repos[0].RepoOwner, + RepoName: p.Task.Repos[0].RepoName, + Branch: p.Task.Repos[0].Branch, + PR: p.Task.Repos[0].PR, + OauthToken: p.Task.Repos[0].OauthToken, + Address: p.Task.Repos[0].Address, + Username: p.Task.Repos[0].Username, + Password: p.Task.Repos[0].Password, + EnableProxy: p.Task.Repos[0].EnableProxy, + RemoteName: p.Task.Repos[0].RemoteName, + } + if repo.RemoteName == "" { + repo.RemoteName = "origin" + } + + jobCtx := JobCtxBuilder{ + JobName: p.JobName, + PipelineCtx: pipelineCtx, + JobCtx: task.JobCtx{ + Builds: []*task.Repository{repo}, + }, + } + + reaperContext := jobCtx.BuildReaperContext(pipelineTask, serviceName) + + // if the scanning task is of sonar type, then we add the sonar parameter to the context + if p.Task.SonarInfo != nil { + reaperContext.SonarParameter = p.Task.Parameter + reaperContext.SonarServer = p.Task.SonarInfo.ServerAddress + reaperContext.SonarLogin = p.Task.SonarInfo.Token + reaperContext.ScannerType = ScanningTypeSonar + } else { + reaperContext.ScannerType = ScanningTypeOther + reaperContext.Scripts = append(reaperContext.Scripts, strings.Split(replaceWrapLine(p.Task.Script), "\n")...) + } + + jobCtxBytes, err := yaml.Marshal(reaperContext) + if err != nil { + msg := fmt.Sprintf("cannot reaper.Context data: %v", err) + p.Log.Error(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + jobObj, jobExist, err := checkJobExists(ctx, p.KubeNamespace, jobLabel, p.kubeClient) + if err != nil { + msg := fmt.Sprintf("failed to check whether Job exist for %s:%d: %s", pipelineTask.PipelineName, pipelineTask.TaskID, err) + p.Log.Error(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + + if jobExist { + p.Log.Infof("Job %s:%d eixsts.", pipelineTask.PipelineName, pipelineTask.TaskID) + + p.JobName = jobObj.Name + + // If the code is executed at this point, it indicates that the `wd` instance that executed the Job has been restarted and the + // Job timeout period needs to be corrected. + // + // Rule of reseting timeout: `timeout - (now - start_time_of_job) + compensate_duration` + // For now, `compensate_duration` is 2min. + taskTimeout := p.TaskTimeout() + elaspedTime := time.Now().Unix() - jobObj.Status.StartTime.Time.Unix() + timeout := taskTimeout + 120 - int(elaspedTime) + p.Log.Infof("Timeout before normalization: %d seconds", timeout) + if timeout < 0 { + // That shouldn't happen in theory, but a protection is needed. + timeout = 0 + } + + p.Log.Infof("Timeout after normalization: %d seconds", timeout) + p.tmpSetTaskTimeout(timeout) + } + + _, cmExist, err := checkConfigMapExists(ctx, p.KubeNamespace, jobLabel, p.kubeClient) + if err != nil { + msg := fmt.Sprintf("failed to check whether ConfigMap exist for %s:%d: %s", pipelineTask.PipelineName, pipelineTask.TaskID, err) + p.Log.Error(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + + if !cmExist { + p.Log.Infof("ConfigMap for Job %s:%d does not exist. Create.", pipelineTask.PipelineName, pipelineTask.TaskID) + + err = createJobConfigMap(p.KubeNamespace, p.JobName, jobLabel, string(jobCtxBytes), p.kubeClient) + if err != nil && !apierrors.IsAlreadyExists(err) { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + p.Log.Error(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + } + p.Log.Infof("succeed to create cm for build job %s", p.JobName) + + if !jobExist { + p.Log.Infof("Job %s:%d does not exist. Create.", pipelineTask.PipelineName, pipelineTask.TaskID) + + // search namespace should also include desired namespace + job, err := buildJobWithLinkedNs( + p.Type(), p.Task.ImageInfo, p.JobName, serviceName, p.Task.ClusterID, pipelineTask.ConfigPayload.Test.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries, + p.KubeNamespace, + // this is a useless field, so we will just leave it empty + "", + ) + + job.Namespace = p.KubeNamespace + + if err != nil { + msg := fmt.Sprintf("create scanning job context error: %v", err) + p.Log.Error(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + + // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 + if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { + msg := fmt.Sprintf("create secret error: %v", err) + p.Log.Errorf(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + if err := updater.CreateJob(job, p.kubeClient); err != nil { + msg := fmt.Sprintf("create testing job error: %v", err) + p.Log.Error(msg) + p.Task.Status = config.StatusFailed + p.Task.Error = msg + return + } + } + + p.Log.Infof("succeed to create build job %s", p.JobName) + + p.Task.Status = waitJobReady(ctx, p.KubeNamespace, p.JobName, p.kubeClient, p.Log) +} + +func (p *ScanPlugin) Wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, p.TaskTimeout(), p.KubeNamespace, p.JobName, true, p.kubeClient, p.clientset, p.restConfig, p.Log) + p.SetStatus(status) +} + +func (p *ScanPlugin) tmpSetTaskTimeout(durationInSeconds int) { + p.Task.Timeout = int64(math.Ceil(float64(durationInSeconds) / 60.0)) +} + +func (p *ScanPlugin) Complete(ctx context.Context, pipelineTask *task.Task, serviceName string) { + jobLabel := &JobLabel{ + PipelineName: pipelineTask.PipelineName, + ServiceName: serviceName, + TaskID: pipelineTask.TaskID, + TaskType: string(p.Type()), + PipelineType: string(pipelineTask.Type), + } + + // Clean up tasks that user canceled or timed out. + defer func() { + go func() { + if err := ensureDeleteJob(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + } + + if err := ensureDeleteConfigMap(p.KubeNamespace, jobLabel, p.kubeClient); err != nil { + p.Log.Error(err) + } + }() + }() + + err := saveContainerLog(pipelineTask, p.KubeNamespace, p.Task.ClusterID, p.FileName, jobLabel, p.kubeClient) + if err != nil { + p.Log.Error(err) + p.Task.Error = err.Error() + return + } +} + +func (p *ScanPlugin) SetTask(t map[string]interface{}) error { + task, err := ToScanningTask(t) + if err != nil { + return err + } + p.Task = task + + return nil +} + +func (p *ScanPlugin) GetTask() interface{} { + return p.Task +} + +func (p *ScanPlugin) IsTaskDone() bool { + if p.Task.Status != config.StatusCreated && p.Task.Status != config.StatusRunning { + return true + } + return false +} + +func (p *ScanPlugin) IsTaskFailed() bool { + if p.Task.Status == config.StatusFailed || p.Task.Status == config.StatusTimeout || p.Task.Status == config.StatusCancelled { + return true + } + return false +} + +// since scan is a standalone job, a subtask should not have startTime and endtime + +func (p *ScanPlugin) SetStartTime() { + //p.Task.S = time.Now().Unix() +} + +func (p *ScanPlugin) SetEndTime() { + //p.Task.EndTime = time.Now().Unix() +} + +// scan job is a standalone job so it is always enabled +func (p *ScanPlugin) IsTaskEnabled() bool { + return true +} + +func (p *ScanPlugin) ResetError() { + p.Task.Error = "" +} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 18ed9fad..c1125478 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -116,6 +116,14 @@ func ToBuildTask(sb map[string]interface{}) (*task.Build, error) { return t, nil } +func ToScanningTask(sb map[string]interface{}) (*task.Scanning, error) { + var t *task.Scanning + if err := IToi(sb, &t); err != nil { + return nil, fmt.Errorf("convert interface to Scanning task error: %s", err) + } + return t, nil +} + func ToArtifactTask(sb map[string]interface{}) (*task.ArtifactPackage, error) { var t *task.ArtifactPackage if err := IToi(sb, &t); err != nil { diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index 96cf51c0..bf1e319c 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -128,6 +128,13 @@ type Context struct { UploadEnabled bool `yaml:"upload_enabled"` UploadStorageInfo *types.ObjectStorageInfo `yaml:"upload_storage_info"` UploadInfo []*types.ObjectStoragePathDetail `yaml:"upload_info"` + + // scanner used flag + ScannerFlag bool `yaml:"scanner_flag"` + ScannerType string `yaml:"scanner_type"` + SonarParameter string `yaml:"sonar_parameter"` + SonarServer string `yaml:"sonar_server"` + SonarLogin string `yaml:"sonar_login"` } type ArtifactInfo struct { diff --git a/pkg/microservice/warpdrive/core/service/types/task/scanning.go b/pkg/microservice/warpdrive/core/service/types/task/scanning.go new file mode 100644 index 00000000..02758cec --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/types/task/scanning.go @@ -0,0 +1,46 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package task + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" +) + +type Scanning struct { + TaskType config.TaskType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + ScanningID string `bson:"scanning_id" json:"scanning_id"` + Name string `bson:"name" json:"name"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + ImageInfo string `bson:"image_info" json:"image_info"` + SonarInfo *types.SonarInfo `bson:"sonar_info" json:"sonar_info"` + Repos []*types.Repository `bson:"repos" json:"repos"` + Proxy *models.Proxy `bson:"proxy" json:"proxy"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + // ResReq defines job requested resources + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + Timeout int64 `bson:"timeout" json:"timeout"` + Registries []*RegistryNamespace `bson:"-" json:"registries"` + // Parameter is for sonarQube type only + Parameter string `bson:"parameter" json:"parameter"` + // Script is for other type only + Script string `bson:"script" json:"script"` +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 8a1437c2..2cdb4f7b 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -336,6 +336,7 @@ const ( WorkflowTaskV3Fmt = "WorkflowTaskV3:%s" TestTaskFmt = "TestTask:%s" ServiceTaskFmt = "ServiceTask:%s" + ScanningTaskFmt = "ScanningTask:%s" ) // Product Status diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index f5a63149..237dff5b 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -465,6 +465,10 @@ var ( ErrDeleteTestModule = NewHTTPError(6533, "删除测试模块失败") // ErrGetTestReport ... ErrGetTestReport = NewHTTPError(6534, "获取html测试报告失败") + // ErrCreateScanningModule ... + ErrCreateScanningModule = NewHTTPError(6535, "新建扫描模块失败") + // ErrCreateScanningModule ... + ErrUpdateScanningModule = NewHTTPError(6536, "更新扫描模块失败") // Workflow APIs Range: 6540 - 6550 //----------------------------------------------------------------------------------------------- diff --git a/pkg/types/reaper.go b/pkg/types/reaper.go index 38282b43..7e3fed0a 100644 --- a/pkg/types/reaper.go +++ b/pkg/types/reaper.go @@ -19,6 +19,11 @@ package types type ReaperType string const ( - BuildReaperType ReaperType = "Build" - TestReaperType ReaperType = "Test" + BuildReaperType ReaperType = "Build" + TestReaperType ReaperType = "Test" + ScanningReaperType ReaperType = "Scanning" +) + +const ( + ScanningTypeSonar = "sonarQube" ) diff --git a/pkg/types/scanning.go b/pkg/types/scanning.go new file mode 100644 index 00000000..30f1a183 --- /dev/null +++ b/pkg/types/scanning.go @@ -0,0 +1,50 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" +) + +type ScanningAdvancedSetting struct { + ClusterID string `bson:"cluster_id" json:"cluster_id"` + Timeout int64 `bson:"timeout" json:"timeout"` + ResReq setting.Request `bson:"res_req" json:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec"` + HookCtl *ScanningHookCtl `bson:"hook_ctl" json:"hook_ctl"` +} + +type ScanningHookCtl struct { + Enabled bool `bson:"enabled" json:"enabled"` + Items []*ScanningHook `bson:"items" json:"items"` +} + +type ScanningHook struct { + CodehostID int `bson:"codehost_id" json:"codehost_id"` + Source string `bson:"source" json:"source"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` + Branch string `bson:"branch" json:"branch"` + Events []config.HookEventType `bson:"events" json:"events"` + MatchFolders []string `bson:"match_folders" json:"match_folders"` +} + +type SonarInfo struct { + ServerAddress string `bson:"server_address" json:"server_address"` + Token string `bson:"token" json:"token"` +} -- Gitee From ffb2ba4c8b9614bb6ab6cc3b220fd85d521659a8 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Thu, 12 May 2022 21:29:19 +0800 Subject: [PATCH 2343/2647] fix filter problem for multiple service modules (#1488) * added debug logs for filter problem Signed-off-by: Min Min * even more debug logs Signed-off-by: Min Min * more debug logs Signed-off-by: Min Min * stuff Signed-off-by: Min Min * idk what i am doing Signed-off-by: Min Min * changed implementation Signed-off-by: Min Min * debugs Signed-off-by: Min Min * debugs Signed-off-by: Min Min * stuff Signed-off-by: Min Min * stuff Signed-off-by: Min Min * debug Signed-off-by: Min Min * removed debug logs Signed-off-by: Min Min * removed useless log Signed-off-by: Min Min --- .../core/common/repository/models/build.go | 12 ++++++++ .../service/workflow/workflow_task.go | 30 ++++++++++--------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index df1c1d1c..33930de9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -185,6 +185,18 @@ func (build *Build) SafeRepos() []*types.Repository { return build.Repos } +func (build *Build) SafeReposDeepCopy() []*types.Repository { + if len(build.Repos) == 0 { + return []*types.Repository{} + } + resp := make([]*types.Repository, 0) + for _, repo := range build.Repos { + tmpRepo := *repo + resp = append(resp, &tmpRepo) + } + return resp +} + func (Build) TableName() string { return "module_build" } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index e67d8233..540fcf2f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -480,20 +480,7 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) if moBuild.TemplateID != "" { target.Build.Repos = targetInfo.Repos } else { - target.Build.Repos = moBuild.SafeRepos() - } - - key := fmt.Sprintf("%s/%s", containerArr[2], containerArr[1]) - for _, repoInfo := range target.Build.Repos { - if filterList, ok := filtermap[key]; ok { - for _, filter := range filterList { - // make sure they are the same repository - if filter.CodehostID == repoInfo.CodehostID && filter.RepoOwner == repoInfo.RepoOwner && filter.RepoName == repoInfo.RepoName { - repoInfo.FilterRegexp = filter.FilterRegExp - break - } - } - } + target.Build.Repos = moBuild.SafeReposDeepCopy() } if moBuild.PreBuild != nil { @@ -523,6 +510,21 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) } } + for _, target := range targets { + key := fmt.Sprintf("%s/%s", target.Name, target.ServiceName) + for _, repoInfo := range target.Build.Repos { + if filterList, ok := filtermap[key]; ok { + for _, filter := range filterList { + // make sure they are the same repository + if filter.CodehostID == repoInfo.CodehostID && filter.RepoOwner == repoInfo.RepoOwner && filter.RepoName == repoInfo.RepoName { + repoInfo.FilterRegexp = filter.FilterRegExp + break + } + } + } + } + } + resp.Target = targets testArgs := make([]*commonmodels.TestArgs, 0) testsMap := make(map[string]*commonmodels.Testing) -- Gitee From b969504116587aae8d9fdbed64789f8e57757963 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Thu, 12 May 2022 22:02:16 +0800 Subject: [PATCH 2344/2647] Bugfix for panic in wd when a scanning is triggered by gitlab (#1492) * debug infos Signed-off-by: Min Min * stuff Signed-off-by: Min Min * bugfix found Signed-off-by: Min Min --- .../aslan/core/workflow/service/webhook/gitlab_scanning_task.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go index 67db7937..fefc884e 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go @@ -71,6 +71,7 @@ func TriggerScanningByGitlabEvent(event interface{}, baseURI, requestID string, RepoName: item.RepoName, Branch: item.Branch, } + triggerRepoInfo = append(triggerRepoInfo, repoInfo) repoInfo.PR = mergeRequestID -- Gitee From 3ec26b1b52df8cc022a435882df8e0dea42a8cd2 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Fri, 13 May 2022 10:56:52 +0800 Subject: [PATCH 2345/2647] fixed when there are no hook ctl it will cause aslan to panic (#1493) * fixed when there are no hook ctl it will cause aslan to panic Signed-off-by: Min Min * stuff Signed-off-by: Min Min * even worse debug logs Signed-off-by: Min Min * debug logs Signed-off-by: Min Min * debug Signed-off-by: Min Min --- .../service/webhook/github_scanning_task.go | 6 ++++++ .../aslan/core/workflow/service/webhook/gitlab.go | 2 +- .../service/webhook/gitlab_scanning_task.go | 14 ++++++++++---- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go index 753e96ca..26cd9853 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go @@ -107,6 +107,9 @@ type githubPushEventMatcherForScanning struct { func (gpem githubPushEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { ev := gpem.event + if hookRepo == nil { + return false, nil + } if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) @@ -137,6 +140,9 @@ type githubMergeEventMatcherForScanning struct { func (gmem githubMergeEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { ev := gmem.event + if hookRepo == nil { + return false, nil + } // TODO: match codehost if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index ac8d47a6..e4b88c5b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -182,7 +182,7 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log wg.Add(1) go func() { defer wg.Done() - if err = TriggerScanningByGitlabEvent(pushEvent, baseURI, requestID, log); err != nil { + if err = TriggerScanningByGitlabEvent(mergeEvent, baseURI, requestID, log); err != nil { errorList = multierror.Append(errorList, err) } }() diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go index fefc884e..f5f6afe3 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go @@ -43,7 +43,7 @@ func TriggerScanningByGitlabEvent(event interface{}, baseURI, requestID string, for _, scanning := range scanningList { if scanning.AdvancedSetting.HookCtl != nil && scanning.AdvancedSetting.HookCtl.Enabled { - log.Infof("find %d hooks in testing %s", len(scanning.AdvancedSetting.HookCtl.Items), scanning.Name) + log.Infof("find %d hooks in scanning %s", len(scanning.AdvancedSetting.HookCtl.Items), scanning.Name) for _, item := range scanning.AdvancedSetting.HookCtl.Items { // 2. match webhook matcher := createGitlabEventMatcherForScanning(event, diffSrv, scanning, log) @@ -125,8 +125,11 @@ type gitlabPushEventMatcherForScanning struct { event *gitlab.PushEvent } -func (gpem gitlabPushEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { +func (gpem *gitlabPushEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { ev := gpem.event + if hookRepo == nil { + return false, nil + } if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) @@ -155,8 +158,11 @@ type gitlabMergeEventMatcherForScanning struct { event *gitlab.MergeEvent } -func (gmem gitlabMergeEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { +func (gmem *gitlabMergeEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { ev := gmem.event + if hookRepo == nil { + return false, nil + } // TODO: match codehost if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { matchRepo := ConvertScanningHookToMainHookRepo(hookRepo) @@ -188,6 +194,6 @@ type gitlabTagEventMatcherForScanning struct { event *gitlab.TagEvent } -func (gtem gitlabTagEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { +func (gtem *gitlabTagEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { return false, nil } -- Gitee From bbbbc2fa0418c0e968d9e32268fd9ae883855f53 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Fri, 13 May 2022 11:05:29 +0800 Subject: [PATCH 2346/2647] add service yaml for simple-service project (#1495) * add service yaml for simple-service project Signed-off-by: fansi * fix comments Signed-off-by: fansi Co-authored-by: fansi --- examples/simple-service/nginx-yaml/nginx.yaml | 37 ++++++++++++++ examples/simple-service/service-yaml/a.yaml | 49 +++++++++++++++++++ examples/simple-service/service-yaml/b.yaml | 49 +++++++++++++++++++ examples/simple-service/service-yaml/c.yaml | 43 ++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 examples/simple-service/nginx-yaml/nginx.yaml create mode 100644 examples/simple-service/service-yaml/a.yaml create mode 100644 examples/simple-service/service-yaml/b.yaml create mode 100644 examples/simple-service/service-yaml/c.yaml diff --git a/examples/simple-service/nginx-yaml/nginx.yaml b/examples/simple-service/nginx-yaml/nginx.yaml new file mode 100644 index 00000000..b0f96974 --- /dev/null +++ b/examples/simple-service/nginx-yaml/nginx.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: Service +metadata: + name: nginx +spec: + ports: + - name: http + port: 80 + targetPort: 80 + selector: + app: nginx + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx +spec: + selector: + matchLabels: + app: nginx + replicas: 1 + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: ccr.ccs.tencentyun.com/koderover-rc/nginx:v1 + ports: + - containerPort: 80 + resources: + limits: + cpu: 100m + memory: 100Mi diff --git a/examples/simple-service/service-yaml/a.yaml b/examples/simple-service/service-yaml/a.yaml new file mode 100644 index 00000000..5a555b3c --- /dev/null +++ b/examples/simple-service/service-yaml/a.yaml @@ -0,0 +1,49 @@ +apiVersion: v1 +kind: Service +metadata: + name: a + labels: + app: a +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app: a + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: a + labels: + app: a +spec: + selector: + matchLabels: + app: a + replicas: 1 + template: + metadata: + labels: + app: a + spec: + containers: + - name: myapp-1 + image: ccr.ccs.tencentyun.com/koderover-rc/myapp-1:v0.1__linux_amd64 + imagePullPolicy: Always + command: ["/myapp-1"] + args: ["--downstream-addr", "$(DOWNSTREAM_ADDR)", "--headers", "$(HEADERS)"] + env: + - name: DOWNSTREAM_ADDR + value: "b" + - name: HEADERS + value: "x-request-id" + ports: + - containerPort: 8080 + resources: + limits: + cpu: 100m + memory: 100Mi diff --git a/examples/simple-service/service-yaml/b.yaml b/examples/simple-service/service-yaml/b.yaml new file mode 100644 index 00000000..94e57b54 --- /dev/null +++ b/examples/simple-service/service-yaml/b.yaml @@ -0,0 +1,49 @@ +apiVersion: v1 +kind: Service +metadata: + name: b + labels: + app: b +spec: + ports: + - name: http + port: 80 + targetPort: 8081 + selector: + app: b + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: b + labels: + app: b +spec: + selector: + matchLabels: + app: b + replicas: 1 + template: + metadata: + labels: + app: b + spec: + containers: + - name: myapp-2 + image: ccr.ccs.tencentyun.com/koderover-rc/myapp-2:v0.1__linux_amd64 + imagePullPolicy: Always + command: ["/myapp-2"] + args: ["--downstream-addr", "$(DOWNSTREAM_ADDR)", "--headers", "$(HEADERS)"] + env: + - name: DOWNSTREAM_ADDR + value: "c" + - name: HEADERS + value: "x-request-id" + ports: + - containerPort: 8081 + resources: + limits: + cpu: 100m + memory: 100Mi diff --git a/examples/simple-service/service-yaml/c.yaml b/examples/simple-service/service-yaml/c.yaml new file mode 100644 index 00000000..b9c7e551 --- /dev/null +++ b/examples/simple-service/service-yaml/c.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: Service +metadata: + name: c + labels: + app: c +spec: + ports: + - name: http + port: 80 + targetPort: 8082 + selector: + app: c + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: c + labels: + app: c +spec: + selector: + matchLabels: + app: c + replicas: 1 + template: + metadata: + labels: + app: c + spec: + containers: + - name: myapp-3 + image: ccr.ccs.tencentyun.com/koderover-rc/myapp-3:v0.1__linux_amd64 + imagePullPolicy: Always + command: ["/myapp-3"] + ports: + - containerPort: 8082 + resources: + limits: + cpu: 100m + memory: 100Mi -- Gitee From 9e1656f0fe2051836594b6824e7a8e03e3d719d7 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Fri, 13 May 2022 12:50:16 +0800 Subject: [PATCH 2347/2647] sonar image initialization & upgrade (#1496) * added sonar image into system integration Signed-off-by: Min Min * added initialize logic Signed-off-by: Min Min --- pkg/cli/upgradeassistant/cmd/migrate/1120.go | 18 +++++ .../repository/models/basic_images.go | 44 ++++++++++ .../repository/mongodb/basic_imnages.go | 81 +++++++++++++++++++ .../aslan/core/system/service/basic_images.go | 8 ++ 4 files changed, 151 insertions(+) create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/basic_images.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/mongodb/basic_imnages.go diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1120.go b/pkg/cli/upgradeassistant/cmd/migrate/1120.go index b957f10b..018328e9 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1120.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1120.go @@ -51,6 +51,11 @@ func V1110ToV1120() error { return err } + if err := integrateSonar(); err != nil { + log.Errorf("add sonar integration err:%s", err) + return err + } + return nil } @@ -274,6 +279,19 @@ func buildAddJenkinsID() error { return err } +func integrateSonar() error { + sonarImageList, err := internalmongodb.NewBasicImageColl().GetSonarTypeImage() + if err != nil { + return fmt.Errorf("failed to list basic images of type sonar ,err: %s", err) + } + + // if no sonar image list is included, we add one + if len(sonarImageList) == 0 { + return internalmongodb.NewBasicImageColl().CreateZadigSonarImage() + } + return nil +} + func buildAddJenkinsIDRollBack() error { builds, err := internalmongodb.NewBuildColl().List(&internalmongodb.BuildListOption{}) if err != nil { diff --git a/pkg/cli/upgradeassistant/internal/repository/models/basic_images.go b/pkg/cli/upgradeassistant/internal/repository/models/basic_images.go new file mode 100644 index 00000000..0e624fa5 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/basic_images.go @@ -0,0 +1,44 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +const ( + ImageFromKoderover = "koderover" + ImageFromCustom = "custom" +) + +// BasicImage ... +type BasicImage struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Value string `bson:"value" json:"value"` + Label string `bson:"label" json:"label"` + ImageFrom string `bson:"image_from" json:"image_from"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateTime int64 `bson:"update_time" json:"update_time"` + UpdateBy string `bson:"update_by" json:"update_by"` + // New field since 1.12 + // ImageType is the type of the image, currently only one type is supported called sonar + ImageType string `bson:"image_type" json:"image_type"` +} + +func (BasicImage) TableName() string { + return "basic_image" +} diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/basic_imnages.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/basic_imnages.go new file mode 100644 index 00000000..2af43534 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/basic_imnages.go @@ -0,0 +1,81 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "time" +) + +type BasicImageOpt struct { + Value string `bson:"value"` + Label string `bson:"label"` + ImageFrom string `bson:"image_from"` + ImageType string `bson:"image_type"` +} + +type BasicImageColl struct { + *mongo.Collection + + coll string +} + +func NewBasicImageColl() *BasicImageColl { + name := models.BasicImage{}.TableName() + coll := &BasicImageColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} + + return coll +} + +func (c *BasicImageColl) GetSonarTypeImage() ([]*models.BasicImage, error) { + query := bson.M{} + query["image_type"] = "sonar" + + ctx := context.Background() + resp := make([]*models.BasicImage, 0) + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, err +} + +func (c *BasicImageColl) CreateZadigSonarImage() error { + args := &models.BasicImage{ + Value: "sonarsource/sonar-scanner-cli", + Label: "sonar:latest", + CreateTime: time.Now().Unix(), + UpdateTime: time.Now().Unix(), + UpdateBy: "system", + ImageType: "sonar", + } + + _, err := c.InsertOne(context.TODO(), args) + return err +} diff --git a/pkg/microservice/aslan/core/system/service/basic_images.go b/pkg/microservice/aslan/core/system/service/basic_images.go index 5cde81df..769b17b2 100644 --- a/pkg/microservice/aslan/core/system/service/basic_images.go +++ b/pkg/microservice/aslan/core/system/service/basic_images.go @@ -55,6 +55,14 @@ func InitbasicImageInfos() []*commonmodels.BasicImage { UpdateTime: time.Now().Unix(), UpdateBy: "system", }, + { + Value: "sonarsource/sonar-scanner-cli", + Label: "sonar:latest", + CreateTime: time.Now().Unix(), + UpdateTime: time.Now().Unix(), + UpdateBy: "system", + ImageType: "sonar", + }, } return basicImageInfos -- Gitee From 6b8c45c2d727740b64eea1e283ae26b85e104ff5 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Fri, 13 May 2022 16:04:08 +0800 Subject: [PATCH 2348/2647] login (#1497) Signed-off-by: mouuii --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index f9dd7f7e..5e5d8922 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -62,7 +62,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve", "api/v1/login-enabled"}, + Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve", "api/v1/login-enabled", "login/pure"}, }, { Methods: []string{"GET"}, -- Gitee From 042ec6776681747e8e17d6f9d844752375462988 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Fri, 13 May 2022 18:31:14 +0800 Subject: [PATCH 2349/2647] update pytest test case (#1498) Signed-off-by: fansi Co-authored-by: fansi --- examples/pytest-demo/num_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/pytest-demo/num_test.py b/examples/pytest-demo/num_test.py index 30d8afe4..25601928 100644 --- a/examples/pytest-demo/num_test.py +++ b/examples/pytest-demo/num_test.py @@ -10,5 +10,5 @@ def square(x): def test_the_square_of_5_is_25(): assert square(5) == 25 -# def test_the_square_of_negative_5_is_negative_25(): -# assert square(-5) == -25 +def test_the_square_of_negative_5_is_negative_25(): + assert square(-5) == -25 -- Gitee From 4cb52be72fa8391ef00d18c12a7363ce85951d41 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Mon, 16 May 2022 16:07:31 +0800 Subject: [PATCH 2350/2647] split python test case (#1501) Signed-off-by: fansi Co-authored-by: fansi --- examples/pytest-demo/fail_test.py | 5 +++++ examples/pytest-demo/{num_test.py => pass_test.py} | 3 --- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 examples/pytest-demo/fail_test.py rename examples/pytest-demo/{num_test.py => pass_test.py} (71%) diff --git a/examples/pytest-demo/fail_test.py b/examples/pytest-demo/fail_test.py new file mode 100644 index 00000000..dc494b1a --- /dev/null +++ b/examples/pytest-demo/fail_test.py @@ -0,0 +1,5 @@ +def square(x): + return x * x + +def test_the_square_of_negative_5_is_negative_25(): + assert square(-5) == -25 diff --git a/examples/pytest-demo/num_test.py b/examples/pytest-demo/pass_test.py similarity index 71% rename from examples/pytest-demo/num_test.py rename to examples/pytest-demo/pass_test.py index 25601928..64f0271c 100644 --- a/examples/pytest-demo/num_test.py +++ b/examples/pytest-demo/pass_test.py @@ -9,6 +9,3 @@ def square(x): def test_the_square_of_5_is_25(): assert square(5) == 25 - -def test_the_square_of_negative_5_is_negative_25(): - assert square(-5) == -25 -- Gitee From b9ae0bdb5f5edfd0f0798932fdf1a6a3d9b485a4 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Tue, 17 May 2022 10:08:11 +0800 Subject: [PATCH 2351/2647] add piggymetrics example (#1500) * add piggymetrics example Signed-off-by: ddh27 * add piggymetrics example Signed-off-by: ddh27 * add piggymetrics example: set resource Signed-off-by: ddh27 * add piggymetrics example: rm status Signed-off-by: ddh27 --- .../1-rabbitmq-service.yaml | 59 +++++++++ .../10-statistics-service.yaml | 97 ++++++++++++++ .../11-notification-mongodb-service.yaml | 61 +++++++++ .../12-notification-service.yaml | 117 +++++++++++++++++ .../13-monitoring-service.yaml | 89 +++++++++++++ .../14-turbine-stream-service.yaml | 91 ++++++++++++++ .../2-config-service.yaml | 119 ++++++++++++++++++ .../3-registry-service.yaml | 87 +++++++++++++ .../4-gateway-service.yaml | 96 ++++++++++++++ .../5-auth-mongodb-service.yaml | 61 +++++++++ .../6-auth-service.yaml | 107 ++++++++++++++++ .../7-account-mongodb-service.yaml | 63 ++++++++++ .../8-account-service.yaml | 97 ++++++++++++++ .../9-statistics-mongodb-service.yaml | 61 +++++++++ 14 files changed, 1205 insertions(+) create mode 100644 examples/spring-cloud-piggymetrics/1-rabbitmq-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/10-statistics-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/11-notification-mongodb-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/12-notification-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/13-monitoring-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/2-config-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/3-registry-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/4-gateway-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/5-auth-mongodb-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/6-auth-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/7-account-mongodb-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/8-account-service.yaml create mode 100644 examples/spring-cloud-piggymetrics/9-statistics-mongodb-service.yaml diff --git a/examples/spring-cloud-piggymetrics/1-rabbitmq-service.yaml b/examples/spring-cloud-piggymetrics/1-rabbitmq-service.yaml new file mode 100644 index 00000000..73c49f32 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/1-rabbitmq-service.yaml @@ -0,0 +1,59 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: rabbitmq + name: rabbitmq +spec: + ports: + - name: http + port: 15672 + targetPort: 15672 + - name: amqp + port: 5672 + targetPort: 5672 + selector: + project: piggymetrics + tier: infrastructure + app: rabbitmq +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: rabbitmq + name: rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: infrastructure + app: rabbitmq + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: infrastructure + app: rabbitmq + spec: + containers: + - image: rabbitmq:3-management + name: rabbitmq + ports: + - containerPort: 15672 + - containerPort: 5672 + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 256M + restartPolicy: Always \ No newline at end of file diff --git a/examples/spring-cloud-piggymetrics/10-statistics-service.yaml b/examples/spring-cloud-piggymetrics/10-statistics-service.yaml new file mode 100644 index 00000000..f10acc8d --- /dev/null +++ b/examples/spring-cloud-piggymetrics/10-statistics-service.yaml @@ -0,0 +1,97 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: backend + app: statistics-service + name: statistics-service +spec: + ports: + - name: http + port: 7000 + targetPort: 7000 + selector: + project: piggymetrics + tier: backend + app: statistics-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: backend + app: statistics-service + name: statistics-service +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: backend + app: statistics-service + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: backend + app: statistics-service + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "statistics-service" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: statistics_mongodb_password + - name: STATISTICS_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: statistics_service_password + envFrom: + - configMapRef: + name: special-config-env + ports: + - containerPort: 7000 + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-statistics-service:v0.3.0 + name: statistics-service + resources: + limits: + cpu: "1" + memory: 1G + requests: + cpu: 500m + memory: 512M + imagePullPolicy: Always + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/11-notification-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/11-notification-mongodb-service.yaml new file mode 100644 index 00000000..640dfa83 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/11-notification-mongodb-service.yaml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Service +metadata: + name: notification-mongodb + labels: + project: piggymetrics + tier: database + app: notification-mongodb +spec: + ports: + - name: mongo + port: 27017 + targetPort: 27017 + selector: + project: piggymetrics + tier: database + app: notification-mongodb +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: database + app: notification-mongodb + name: notification-mongodb +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: database + app: notification-mongodb + strategy: + type: Recreate + template: + metadata: + labels: + project: piggymetrics + tier: database + app: notification-mongodb + spec: + containers: + - env: + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_mongodb_password + ports: + - containerPort: 27017 + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + name: notification-mongodb + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 256M + restartPolicy: Always diff --git a/examples/spring-cloud-piggymetrics/12-notification-service.yaml b/examples/spring-cloud-piggymetrics/12-notification-service.yaml new file mode 100644 index 00000000..d6158694 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/12-notification-service.yaml @@ -0,0 +1,117 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: backend + app: notification-service + name: notification-service +spec: + ports: + - name: http + port: 8000 + targetPort: 8000 + selector: + project: piggymetrics + tier: backend + app: notification-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: backend + app: notification-service + name: notification-service +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: backend + app: notification-service + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: backend + app: notification-service + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "notification-service" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_mongodb_password + - name: NOTIFICATION_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_service_password + - name: NOTIFICATION_EMAIL_HOST + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_email_host + - name: NOTIFICATION_EMAIL_PORT + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_email_port + - name: NOTIFICATION_EMAIL_USER + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_email_user + - name: NOTIFICATION_EMAIL_PASS + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_email_pass + envFrom: + - configMapRef: + name: special-config-env + ports: + - containerPort: 8000 + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-notification-service:v0.3.0 + name: notification-service + resources: + limits: + cpu: "1" + memory: 1G + requests: + cpu: 500m + memory: 512M + imagePullPolicy: Always + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/13-monitoring-service.yaml b/examples/spring-cloud-piggymetrics/13-monitoring-service.yaml new file mode 100644 index 00000000..35e5d564 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/13-monitoring-service.yaml @@ -0,0 +1,89 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: monitoring + name: monitoring +spec: + type: NodePort + ports: + - name: http + port: 9000 + targetPort: 8080 + nodePort: 31090 + selector: + project: piggymetrics + tier: infrastructure + app: monitoring +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: monitoring + name: monitoring +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: infrastructure + app: monitoring + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: infrastructure + app: monitoring + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "monitoring" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + envFrom: + - configMapRef: + name: special-config-env + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-monitoring:v0.3.0 + name: monitoring + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 256M + ports: + - containerPort: 8080 + imagePullPolicy: Always + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml b/examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml new file mode 100644 index 00000000..a849f5b0 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml @@ -0,0 +1,91 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + trier: infrastructure + app: turbine-stream-service + name: turbine-stream-service +spec: + ports: + - name: exposed + port: 8989 + targetPort: 8989 + - name: http + port: 8080 + targetPort: 8080 + selector: + project: piggymetrics + trier: infrastructure + app: turbine-stream-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + trier: infrastructure + app: turbine-stream-service + name: turbine-stream-service +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + trier: infrastructure + app: turbine-stream-service + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + trier: infrastructure + app: turbine-stream-service + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "turbine-stream-service" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + envFrom: + - configMapRef: + name: special-config-env + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-statistics-service:v0.3.0 + name: turbine-stream-service + resources: + limits: + cpu: "1" + memory: 1G + requests: + cpu: 500m + memory: 512M + ports: + - containerPort: 8989 + - containerPort: 8080 + imagePullPolicy: Always + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/2-config-service.yaml b/examples/spring-cloud-piggymetrics/2-config-service.yaml new file mode 100644 index 00000000..5ab3faf2 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/2-config-service.yaml @@ -0,0 +1,119 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: config + name: config +spec: + ports: + - name: http + port: 8888 + targetPort: 8888 + selector: + project: piggymetrics + tier: infrastructure + app: config +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: config + name: config +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: infrastructure + app: config + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: infrastructure + app: config + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "config" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + envFrom: + - configMapRef: + name: special-config-env + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-config:v0.3.0 + name: config + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 200m + memory: 512M + imagePullPolicy: Always + ports: + - containerPort: 8888 + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} +--- +apiVersion: v1 +kind: Secret +metadata: + name: piggymetrics +type: Opaque +data: + config_service_password: Q09ORklHX1NFUlZJQ0VfUEFTU1dPUkQ= + auth_mongodb_password: QVVUSF9NT05HT0RCX1BBU1NXT1JE + notification_service_password: Tk9USUZJQ0FUSU9OX1NFUlZJQ0VfUEFTU1dPUkQ= + notification_mongodb_password: Tk9USUZJQ0FUSU9OX01PTkdPREJfUEFTU1dPUkQ= + statistics_service_password: U1RBVElTVElDU19TRVJWSUNFX1BBU1NXT1JE + statistics_mongodb_password: U1RBVElTVElDU19NT05HT0RCX1BBU1NXT1JE + account_service_password: QUNDT1VOVF9TRVJWSUNFX1BBU1NXT1JE + account_mongodb_password: QUNDT1VOVF9NT05HT0RCX1BBU1NXT1JE + notification_email_host: c210cC5nbWFpbC5jb20= + notification_email_port: NDY1 + notification_email_user: ZGV2LXVzZXI= + notification_email_pass: ZGV2LXBhc3N3b3Jk +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: special-config-env +data: + registry_service_host: registry + auth_service_host: auth-service + auth_mongodb_host: auth-mongodb + account_mongodb_host: account-mongodb + notification_mongodb_host: notification-mongodb + statistics_mongodb_host: statistics-mongodb + rabbitmq_host: rabbitmq \ No newline at end of file diff --git a/examples/spring-cloud-piggymetrics/3-registry-service.yaml b/examples/spring-cloud-piggymetrics/3-registry-service.yaml new file mode 100644 index 00000000..a8778f0b --- /dev/null +++ b/examples/spring-cloud-piggymetrics/3-registry-service.yaml @@ -0,0 +1,87 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: registry + name: registry +spec: + ports: + - name: http + port: 8761 + targetPort: 8761 + selector: + project: piggymetrics + tier: infrastructure + app: registry +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: registry + name: registry +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: infrastructure + app: registry + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: infrastructure + app: registry + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "registry" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + envFrom: + - configMapRef: + name: special-config-env + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-registry:v0.3.0 + name: registry + resources: + limits: + cpu: 800m + memory: 1G + requests: + cpu: 400m + memory: 768M + imagePullPolicy: Always + ports: + - containerPort: 8761 + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/4-gateway-service.yaml b/examples/spring-cloud-piggymetrics/4-gateway-service.yaml new file mode 100644 index 00000000..72cac37c --- /dev/null +++ b/examples/spring-cloud-piggymetrics/4-gateway-service.yaml @@ -0,0 +1,96 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: frontend + app: gateway + name: gateway +spec: + # comment or delete the following line if you want to use a LoadBalancer + type: NodePort + # if your cluster supports it, uncomment the following to automatically create + # an external load-balanced IP for the frontend service. + # type: LoadBalancer + ports: + - name: http + port: 80 + targetPort: 4000 + # comment or delete the following line if you want to use a LoadBalancer + nodePort: 30080 + selector: + project: piggymetrics + tier: frontend + app: gateway +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gateway + labels: + project: piggymetrics + tier: frontend + app: gateway +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: frontend + app: gateway + strategy: + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + project: piggymetrics + tier: frontend + app: gateway + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - name: gateway + env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "gateway" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + envFrom: + - configMapRef: + name: special-config-env + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-gateway:v0.3.0 + name: gateway + resources: + limits: + cpu: "1" + memory: 1G + requests: + cpu: 500m + memory: 512M + imagePullPolicy: Always + ports: + - containerPort: 4000 + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/5-auth-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/5-auth-mongodb-service.yaml new file mode 100644 index 00000000..8b346311 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/5-auth-mongodb-service.yaml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Service +metadata: + name: auth-mongodb + labels: + project: piggymetrics + tier: database + app: auth-mongodb +spec: + ports: + - name: mongo + port: 27017 + targetPort: 27017 + selector: + project: piggymetrics + tier: database + app: auth-mongodb +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: auth-mongodb + labels: + project: piggymetrics + tier: database + app: auth-mongodb +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: database + app: auth-mongodb + strategy: + type: Recreate + template: + metadata: + labels: + project: piggymetrics + tier: database + app: auth-mongodb + spec: + containers: + - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + name: auth-mongodb + env: + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: auth_mongodb_password + ports: + - containerPort: 27017 + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 256M + restartPolicy: Always diff --git a/examples/spring-cloud-piggymetrics/6-auth-service.yaml b/examples/spring-cloud-piggymetrics/6-auth-service.yaml new file mode 100644 index 00000000..34eaf6fb --- /dev/null +++ b/examples/spring-cloud-piggymetrics/6-auth-service.yaml @@ -0,0 +1,107 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: auth-service + name: auth-service +spec: + ports: + - name: http + port: 5000 + targetPort: 5000 + selector: + project: piggymetrics + tier: infrastructure + app: auth-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: infrastructure + app: auth-service + name: auth-service +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: infrastructure + app: auth-service + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: infrastructure + app: auth-service + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "auth-service" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: ACCOUNT_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: account_service_password + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: auth_mongodb_password + - name: NOTIFICATION_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: notification_service_password + - name: STATISTICS_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: statistics_service_password + envFrom: + - configMapRef: + name: special-config-env + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-auth-service:v0.3.0 + name: auth-service + resources: + limits: + cpu: "1" + memory: 1G + requests: + cpu: 500m + memory: 512M + imagePullPolicy: Always + ports: + - containerPort: 5000 + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/7-account-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/7-account-mongodb-service.yaml new file mode 100644 index 00000000..2e6fa315 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/7-account-mongodb-service.yaml @@ -0,0 +1,63 @@ +apiVersion: v1 +kind: Service +metadata: + name: account-mongodb + labels: + project: piggymetrics + tier: database + app: account-mongodb +spec: + ports: + - name: mongo + port: 27017 + targetPort: 27017 + selector: + project: piggymetrics + tier: database + app: account-mongodb +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: account-mongodb + labels: + project: piggymetrics + tier: database + app: account-mongodb +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: database + app: account-mongodb + strategy: + type: Recreate + template: + metadata: + labels: + project: piggymetrics + tier: database + app: account-mongodb + spec: + containers: + - name: account-mongodb + env: + - name: INIT_DUMP + value: account-service-dump.js + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: account_mongodb_password + ports: + - containerPort: 27017 + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 256M + restartPolicy: Always diff --git a/examples/spring-cloud-piggymetrics/8-account-service.yaml b/examples/spring-cloud-piggymetrics/8-account-service.yaml new file mode 100644 index 00000000..3a0a2a0b --- /dev/null +++ b/examples/spring-cloud-piggymetrics/8-account-service.yaml @@ -0,0 +1,97 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + project: piggymetrics + tier: backend + app: account-service + name: account-service +spec: + ports: + - name: http + port: 6000 + targetPort: 6000 + selector: + project: piggymetrics + tier: backend + app: account-service +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: backend + app: account-service + name: account-service +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: backend + app: account-service + strategy: + type: RollingUpdate + template: + metadata: + labels: + project: piggymetrics + tier: backend + app: account-service + spec: + initContainers: + - name: init-skywalking-agent + image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + command: + - 'sh' + - '-c' + - 'set -ex;mkdir -p /vmskywalking/agent;cp -r /usr/skywalking/agent/* /vmskywalking/agent;' + volumeMounts: + - mountPath: /vmskywalking/agent + name: skywalking-agent + containers: + - env: + - name: JAVA_OPTS + value: "-javaagent:/opt/skywalking/agent/skywalking-agent.jar -Duser.timezone=GMT+8" + - name: SW_AGENT_NAME + value: "account-service" + - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES + value: "skywalking-oap.skywalking.svc.cluster.local:11800" + - name: ACCOUNT_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: account_service_password + - name: CONFIG_SERVICE_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: config_service_password + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: account_mongodb_password + envFrom: + - configMapRef: + name: special-config-env + ports: + - containerPort: 6000 + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-account-service:v0.3.0 + name: account-service + resources: + limits: + cpu: "1" + memory: 1G + requests: + cpu: 500m + memory: 512M + imagePullPolicy: Always + volumeMounts: + - mountPath: /opt/skywalking/agent + name: skywalking-agent + restartPolicy: Always + volumes: + - name: skywalking-agent + emptyDir: {} diff --git a/examples/spring-cloud-piggymetrics/9-statistics-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/9-statistics-mongodb-service.yaml new file mode 100644 index 00000000..30731a60 --- /dev/null +++ b/examples/spring-cloud-piggymetrics/9-statistics-mongodb-service.yaml @@ -0,0 +1,61 @@ +apiVersion: v1 +kind: Service +metadata: + name: statistics-mongodb + labels: + project: piggymetrics + tier: database + app: statistics-mongodb +spec: + ports: + - name: mongo + port: 27017 + targetPort: 27017 + selector: + project: piggymetrics + tier: database + app: statistics-mongodb +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + project: piggymetrics + tier: database + app: statistics-mongodb + name: statistics-mongodb +spec: + replicas: 1 + selector: + matchLabels: + project: piggymetrics + tier: database + app: statistics-mongodb + strategy: + type: Recreate + template: + metadata: + labels: + project: piggymetrics + tier: database + app: statistics-mongodb + spec: + containers: + - env: + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: piggymetrics + key: statistics_mongodb_password + ports: + - containerPort: 27017 + image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + name: statistics-mongodb + resources: + limits: + cpu: 500m + memory: 1G + requests: + cpu: 100m + memory: 256M + restartPolicy: Always -- Gitee From cfaf07637f5f7d060ac8cc856263f638945825b8 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 17 May 2022 10:39:19 +0800 Subject: [PATCH 2352/2647] update public login url (#1503) Signed-off-by: fansi Co-authored-by: fansi --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index 5e5d8922..b97f43db 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -62,7 +62,7 @@ var publicURLs = []*policyRule{ }, { Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve", "api/v1/login-enabled", "login/pure"}, + Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve", "api/v1/login-enabled", "login/password"}, }, { Methods: []string{"GET"}, -- Gitee From b8f89e31574539d253f762304d1e0f2e66b9fec8 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Tue, 17 May 2022 15:02:54 +0800 Subject: [PATCH 2353/2647] delete get sonar integration auth settings (#1505) Signed-off-by: Min Min --- pkg/microservice/policy/core/service/bundle/exemption_urls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index b97f43db..dddba9a7 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -129,7 +129,7 @@ var systemAdminURLs = []*policyRule{ Endpoints: []string{"api/aslan/system/cleanCache/cron"}, }, { - Methods: []string{"GET", "POST", "PUT", "DELETE"}, + Methods: []string{"POST", "PUT", "DELETE"}, Endpoints: []string{"api/aslan/system/sonar/?*"}, }, { -- Gitee From cdc4e04b490e6db34fab8a2ca262442d27c97234 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Tue, 17 May 2022 21:06:38 +0800 Subject: [PATCH 2354/2647] add piggymetrics example: edit file path (#1506) Signed-off-by: ddh27 --- .../{ => account-mongodb-service}/7-account-mongodb-service.yaml | 0 .../{ => account-service}/8-account-service.yaml | 0 .../{ => auth-mongodb-service}/5-auth-mongodb-service.yaml | 0 .../{ => auth-service}/6-auth-service.yaml | 0 .../{ => config-service}/2-config-service.yaml | 0 .../{ => gateway-service}/4-gateway-service.yaml | 0 .../{ => monitoring-service}/13-monitoring-service.yaml | 0 .../11-notification-mongodb-service.yaml | 0 .../{ => notification-service}/12-notification-service.yaml | 0 .../{ => rabbitmq-service}/1-rabbitmq-service.yaml | 0 .../{ => registry-service}/3-registry-service.yaml | 0 .../9-statistics-mongodb-service.yaml | 0 .../{ => statistics-services}/10-statistics-service.yaml | 0 .../{ => turbine-stream-service}/14-turbine-stream-service.yaml | 1 + 14 files changed, 1 insertion(+) rename examples/spring-cloud-piggymetrics/{ => account-mongodb-service}/7-account-mongodb-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => account-service}/8-account-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => auth-mongodb-service}/5-auth-mongodb-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => auth-service}/6-auth-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => config-service}/2-config-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => gateway-service}/4-gateway-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => monitoring-service}/13-monitoring-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => notification-mongodb-service}/11-notification-mongodb-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => notification-service}/12-notification-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => rabbitmq-service}/1-rabbitmq-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => registry-service}/3-registry-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => statistics-mongodb-service}/9-statistics-mongodb-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => statistics-services}/10-statistics-service.yaml (100%) rename examples/spring-cloud-piggymetrics/{ => turbine-stream-service}/14-turbine-stream-service.yaml (99%) diff --git a/examples/spring-cloud-piggymetrics/7-account-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/account-mongodb-service/7-account-mongodb-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/7-account-mongodb-service.yaml rename to examples/spring-cloud-piggymetrics/account-mongodb-service/7-account-mongodb-service.yaml diff --git a/examples/spring-cloud-piggymetrics/8-account-service.yaml b/examples/spring-cloud-piggymetrics/account-service/8-account-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/8-account-service.yaml rename to examples/spring-cloud-piggymetrics/account-service/8-account-service.yaml diff --git a/examples/spring-cloud-piggymetrics/5-auth-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/auth-mongodb-service/5-auth-mongodb-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/5-auth-mongodb-service.yaml rename to examples/spring-cloud-piggymetrics/auth-mongodb-service/5-auth-mongodb-service.yaml diff --git a/examples/spring-cloud-piggymetrics/6-auth-service.yaml b/examples/spring-cloud-piggymetrics/auth-service/6-auth-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/6-auth-service.yaml rename to examples/spring-cloud-piggymetrics/auth-service/6-auth-service.yaml diff --git a/examples/spring-cloud-piggymetrics/2-config-service.yaml b/examples/spring-cloud-piggymetrics/config-service/2-config-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/2-config-service.yaml rename to examples/spring-cloud-piggymetrics/config-service/2-config-service.yaml diff --git a/examples/spring-cloud-piggymetrics/4-gateway-service.yaml b/examples/spring-cloud-piggymetrics/gateway-service/4-gateway-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/4-gateway-service.yaml rename to examples/spring-cloud-piggymetrics/gateway-service/4-gateway-service.yaml diff --git a/examples/spring-cloud-piggymetrics/13-monitoring-service.yaml b/examples/spring-cloud-piggymetrics/monitoring-service/13-monitoring-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/13-monitoring-service.yaml rename to examples/spring-cloud-piggymetrics/monitoring-service/13-monitoring-service.yaml diff --git a/examples/spring-cloud-piggymetrics/11-notification-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/notification-mongodb-service/11-notification-mongodb-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/11-notification-mongodb-service.yaml rename to examples/spring-cloud-piggymetrics/notification-mongodb-service/11-notification-mongodb-service.yaml diff --git a/examples/spring-cloud-piggymetrics/12-notification-service.yaml b/examples/spring-cloud-piggymetrics/notification-service/12-notification-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/12-notification-service.yaml rename to examples/spring-cloud-piggymetrics/notification-service/12-notification-service.yaml diff --git a/examples/spring-cloud-piggymetrics/1-rabbitmq-service.yaml b/examples/spring-cloud-piggymetrics/rabbitmq-service/1-rabbitmq-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/1-rabbitmq-service.yaml rename to examples/spring-cloud-piggymetrics/rabbitmq-service/1-rabbitmq-service.yaml diff --git a/examples/spring-cloud-piggymetrics/3-registry-service.yaml b/examples/spring-cloud-piggymetrics/registry-service/3-registry-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/3-registry-service.yaml rename to examples/spring-cloud-piggymetrics/registry-service/3-registry-service.yaml diff --git a/examples/spring-cloud-piggymetrics/9-statistics-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/statistics-mongodb-service/9-statistics-mongodb-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/9-statistics-mongodb-service.yaml rename to examples/spring-cloud-piggymetrics/statistics-mongodb-service/9-statistics-mongodb-service.yaml diff --git a/examples/spring-cloud-piggymetrics/10-statistics-service.yaml b/examples/spring-cloud-piggymetrics/statistics-services/10-statistics-service.yaml similarity index 100% rename from examples/spring-cloud-piggymetrics/10-statistics-service.yaml rename to examples/spring-cloud-piggymetrics/statistics-services/10-statistics-service.yaml diff --git a/examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml b/examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml similarity index 99% rename from examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml rename to examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml index a849f5b0..ffded7b4 100644 --- a/examples/spring-cloud-piggymetrics/14-turbine-stream-service.yaml +++ b/examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml @@ -89,3 +89,4 @@ spec: volumes: - name: skywalking-agent emptyDir: {} + -- Gitee From ec6b177aff9b05d15a2899c47e6dd1f64772101f Mon Sep 17 00:00:00 2001 From: flyer Date: Wed, 18 May 2022 14:41:45 +0800 Subject: [PATCH 2355/2647] fix bug of selecting dind for attached cluster (#1509) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- go.mod | 2 + go.sum | 2 + .../core/service/common/dockerhosts.go | 201 ++++++++++++++++++ .../core/service/common/dockerhosts_test.go | 50 +++++ .../service/taskcontroller/task_handler.go | 4 +- .../core/service/taskplugin/build.go | 4 + 6 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 pkg/microservice/warpdrive/core/service/common/dockerhosts.go create mode 100644 pkg/microservice/warpdrive/core/service/common/dockerhosts_test.go diff --git a/go.mod b/go.mod index 355cf0ec..3dcf720d 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,9 @@ require ( github.com/bradleyfalzon/ghinstallation v1.1.1 github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect + github.com/buraksezer/consistent v0.9.0 // indirect github.com/cenkalti/backoff/v4 v4.1.1 + github.com/cespare/xxhash v1.1.0 // indirect github.com/chartmuseum/helm-push v0.10.1 github.com/containers/image v3.0.2+incompatible github.com/coocood/freecache v1.1.0 diff --git a/go.sum b/go.sum index aa9613aa..963148a8 100644 --- a/go.sum +++ b/go.sum @@ -276,6 +276,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v1.3.1 h1:pmuhHlhbUV4OOrGDvoiMjHSZzwRcL+I9cIzYKiW4lII= github.com/bugsnag/panicwrap v1.3.1/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/buraksezer/consistent v0.9.0 h1:Zfs6bX62wbP3QlbPGKUhqDw7SmNkOzY5bHZIYXYpR5g= +github.com/buraksezer/consistent v0.9.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw= github.com/caarlos0/ctrlc v1.0.0/go.mod h1:CdXpj4rmq0q/1Eb44M9zi2nKB0QraNKuRGYGrrHhcQw= github.com/campoy/unique v0.0.0-20180121183637-88950e537e7e/go.mod h1:9IOqJGCPMSc6E5ydlp5NIonxObaeu/Iub/X03EKPVYo= github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= diff --git a/pkg/microservice/warpdrive/core/service/common/dockerhosts.go b/pkg/microservice/warpdrive/core/service/common/dockerhosts.go new file mode 100644 index 00000000..33200e39 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/common/dockerhosts.go @@ -0,0 +1,201 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/buraksezer/consistent" + "github.com/cespare/xxhash" + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" +) + +var once sync.Once + +type Member string + +func (m Member) String() string { + return string(m) +} + +type hasher struct{} + +func (h hasher) Sum64(data []byte) uint64 { + return xxhash.Sum64(data) +} + +type ClusterID string + +type DockerHostsI interface { + GetBestHost(ClusterID, string) string + + Sync() +} + +type dockerhosts struct { + rwLock *sync.RWMutex + store map[ClusterID]*consistent.Consistent + hubServerAddr string + syncInterval time.Duration + + logger *zap.SugaredLogger +} + +var dockerHosts DockerHostsI + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func NewDockerHosts(hubServerAddr string, logger *zap.SugaredLogger) DockerHostsI { + once.Do(func() { + dockerHosts = &dockerhosts{ + rwLock: &sync.RWMutex{}, + store: map[ClusterID]*consistent.Consistent{}, + hubServerAddr: hubServerAddr, + logger: logger, + } + + go dockerHosts.Sync() + }) + + return dockerHosts +} + +func (d *dockerhosts) GetBestHost(clusterID ClusterID, key string) string { + if d.store[clusterID] == nil { + d.initClusterInfo(clusterID) + } + + member := d.store[clusterID].LocateKey([]byte(key)) + + return member.String() +} + +func (d *dockerhosts) initClusterInfo(clusterID ClusterID) { + d.rwLock.Lock() + defer d.rwLock.Unlock() + + members := d.getDockerHostsSvc(clusterID) + + cfg := consistent.Config{ + PartitionCount: 271, + ReplicationFactor: 20, + Load: 1.25, + Hasher: hasher{}, + } + + d.store[clusterID] = consistent.New(members, cfg) +} + +func (d *dockerhosts) getDockerHostsSvc(clusterID ClusterID) []consistent.Member { + ns := config.Namespace() + if string(clusterID) != setting.LocalClusterID { + ns = setting.AttachedClusterNamespace + } + + kclient, err := kubeclient.GetKubeClient(d.hubServerAddr, string(clusterID)) + if err != nil { + d.logger.Warnf("Failed to get kubeclient for cluster %q: %s. Try to use default dockerhosts.", clusterID, err) + return d.getDefaultDockerHosts() + } + + dindSts := &appsv1.StatefulSet{} + err = kclient.Get(context.TODO(), client.ObjectKey{ + Name: "dind", + Namespace: ns, + }, dindSts) + if err != nil { + d.logger.Warnf("Failed to get dind statefuleset in namespace %q of cluster %q: %s", ns, clusterID, err) + return d.getDefaultDockerHosts() + } + + members := []consistent.Member{} + for i := 0; i < int(*dindSts.Spec.Replicas); i++ { + members = append(members, d.genDindAddr(i)) + } + + return members +} + +func (d *dockerhosts) getDefaultDockerHosts() []consistent.Member { + return []consistent.Member{d.genDindAddr(0)} +} + +func (d *dockerhosts) genDindAddr(idx int) Member { + return Member(fmt.Sprintf("tcp://dind-%d.dind:2375", idx)) +} + +func (d *dockerhosts) Sync() { + d.logger.Info("Begin to sync") + + wait.Forever(func() { + d.rwLock.Lock() + defer d.rwLock.Unlock() + + for clusterID, consistentHash := range d.store { + currentMembers := d.getDockerHostsSvc(clusterID) + oldMembers := consistentHash.GetMembers() + + d.logger.Infof("Cluster: %q. Current Members: %d. Old Members: %d", clusterID, len(currentMembers), len(oldMembers)) + + addedMembers, deletedMembers := d.diffMembers(oldMembers, currentMembers) + for _, member := range addedMembers { + consistentHash.Add(member) + } + for _, member := range deletedMembers { + consistentHash.Remove(member.String()) + } + } + }, 3*time.Minute) +} + +func (d *dockerhosts) diffMembers(old, current []consistent.Member) (added, deleted []consistent.Member) { + curMap := make(map[consistent.Member]struct{}, len(current)) + for _, member := range current { + curMap[member] = struct{}{} + } + + oldMap := make(map[consistent.Member]struct{}, len(old)) + for _, member := range old { + oldMap[member] = struct{}{} + } + + for _, member := range old { + if _, found := curMap[member]; !found { + deleted = append(deleted, member) + } + } + for _, member := range current { + if _, found := oldMap[member]; !found { + added = append(added, member) + } + } + + return added, deleted +} diff --git a/pkg/microservice/warpdrive/core/service/common/dockerhosts_test.go b/pkg/microservice/warpdrive/core/service/common/dockerhosts_test.go new file mode 100644 index 00000000..21cf2685 --- /dev/null +++ b/pkg/microservice/warpdrive/core/service/common/dockerhosts_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package common + +import ( + "math/rand" + "testing" + "time" + + "go.uber.org/zap" +) + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func TestNewDockerHosts(t *testing.T) { + hubServerAddr := "hub-server.zadig" + logger, _ := zap.NewProduction() + dockerhosts := NewDockerHosts(hubServerAddr, logger.Sugar()) + + clusters := []ClusterID{ClusterID("local"), ClusterID("worker0"), ClusterID("worker1")} + svcs := []string{"svc0", "svc1", "svc2", "svc3", "svc4"} + + for i := 0; i < len(clusters); i++ { + t.Logf("In Cluster %q\n", clusters[i]) + + for j := 0; j < 10; j++ { + svc := svcs[rand.Intn(len(svcs))] + host := dockerhosts.GetBestHost(clusters[i], svc) + t.Logf("\tHost for svc %q: %q\n", svc, host) + } + + t.Log("\n") + } +} diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index 52cc5802..a3886d6a 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -138,7 +138,9 @@ func (h *ExecHandler) runPipelineTask(ctx context.Context, cancel context.Cancel return } - // 选取当前最空闲的dockerhost + // Deprecated. + // Note: This logic is reserved for compatibility with plugins other than build. This logic can be removed if we have understood all of the plugins. + // For attached clusters, this logic is wrong because it still deals with dind in the local cluster. dockerHost, err := plugins.GetBestDockerHost(pipelineTask.ConfigPayload.Docker.HostList, string(pipelineTask.Type), pipelineTask.ConfigPayload.Build.KubeNamespace, xl) if err != nil { errMsg := fmt.Sprintf("[%s]Cannot find docker host: %v", pipelineTask.PipelineName, err) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index e486a42e..430821c1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -36,6 +36,7 @@ import ( zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/config" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/common" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" @@ -154,6 +155,9 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe p.restConfig = restConfig } + dockerhosts := common.NewDockerHosts(pipelineTask.ConfigPayload.HubServerAddr, p.Log) + pipelineTask.DockerHost = dockerhosts.GetBestHost(common.ClusterID(p.Task.ClusterID), serviceName) + // not local cluster var ( replaceDindServer = "." + DindServer -- Gitee From 99c650985a3c35597a39c3b9e21dfbcd9a8997f8 Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 19 May 2022 16:45:04 +0800 Subject: [PATCH 2356/2647] Support for general git integration and pulling code (#1485) * Support for general git integration and pulling code Signed-off-by: liu deyi * If the user chooses sshkey, write sshfile and config Signed-off-by: liu deyi * If tag is equal to empty, assign branch Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * remove tag assignment Signed-off-by: liu deyi * optmize code Signed-off-by: liu deyi * optmize code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * get repo name Signed-off-by: liu deyi * optmize code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * remove debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * add Copyright Signed-off-by: liu deyi * fix conflict Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * Handling branch assignment issues Signed-off-by: liu deyi --- go.mod | 2 +- .../aslan/core/code/client/open/open.go | 10 +-- .../aslan/core/code/service/branch.go | 12 ++- .../aslan/core/code/service/merge_request.go | 12 ++- .../aslan/core/code/service/namespace.go | 14 +++- .../aslan/core/code/service/project.go | 12 ++- .../aslan/core/code/service/repo.go | 12 ++- .../aslan/core/code/service/tag.go | 13 ++- .../core/common/repository/models/build.go | 4 + .../service/workflow/pipeline_validation.go | 40 ++++----- .../reaper/core/service/meta/const.go | 3 + .../reaper/core/service/meta/types.go | 36 ++++---- .../reaper/core/service/reaper/git.go | 83 ++++++++++++++++++- .../codehost/repository/models/codehost.go | 40 +++++---- .../codehost/repository/mongodb/codehost.go | 5 ++ .../core/codehost/service/codehost.go | 21 ++++- .../warpdrive/core/service/taskplugin/job.go | 34 ++++---- .../warpdrive/core/service/types/reaper.go | 34 ++++---- .../core/service/types/task/build.go | 5 ++ pkg/setting/consts.go | 2 + pkg/shared/client/systemconfig/codehost.go | 10 ++- pkg/types/git.go | 24 ++++++ pkg/types/repo.go | 6 +- pkg/util/url.go | 31 +++++++ 24 files changed, 354 insertions(+), 111 deletions(-) create mode 100644 pkg/types/git.go diff --git a/go.mod b/go.mod index 3dcf720d..6156701f 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.0.50 github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 - github.com/jinzhu/copier v0.3.5 // indirect + github.com/jinzhu/copier v0.3.5 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/mittwald/go-helm-client v0.8.4 diff --git a/pkg/microservice/aslan/core/code/client/open/open.go b/pkg/microservice/aslan/core/code/client/open/open.go index 0793042f..2e9ae976 100644 --- a/pkg/microservice/aslan/core/code/client/open/open.go +++ b/pkg/microservice/aslan/core/code/client/open/open.go @@ -44,13 +44,7 @@ var ClientsConfig = map[string]func() ClientConfig{ setting.SourceFromGitee: func() ClientConfig { return new(gitee.Config) }, } -func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, error) { - ch, err := systemconfig.New().GetCodeHost(codehostID) - if err != nil { - log.Errorf("get code host info err:%s", err) - return nil, err - } - +func OpenClient(ch *systemconfig.CodeHost, log *zap.SugaredLogger) (client.CodeHostClient, error) { var c client.CodeHostClient f, ok := ClientsConfig[ch.Type] if !ok { @@ -66,5 +60,5 @@ func OpenClient(codehostID int, log *zap.SugaredLogger) (client.CodeHostClient, log.Errorf("marsh err:%s", err) return nil, err } - return clientConfig.Open(codehostID, log) + return clientConfig.Open(ch.ID, log) } diff --git a/pkg/microservice/aslan/core/code/service/branch.go b/pkg/microservice/aslan/core/code/service/branch.go index 7f3ba790..4079c583 100644 --- a/pkg/microservice/aslan/core/code/service/branch.go +++ b/pkg/microservice/aslan/core/code/service/branch.go @@ -21,10 +21,20 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) func CodeHostListBranches(codeHostID int, projectName, namespace, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.Branch, error) { - cli, err := open.OpenClient(codeHostID, log) + ch, err := systemconfig.New().GetCodeHost(codeHostID) + if err != nil { + log.Errorf("get code host info err:%s", err) + return nil, err + } + if ch.Type == setting.SourceFromOther { + return []*client.Branch{}, nil + } + cli, err := open.OpenClient(ch, log) if err != nil { log.Errorf("open client err:%s", err) return nil, err diff --git a/pkg/microservice/aslan/core/code/service/merge_request.go b/pkg/microservice/aslan/core/code/service/merge_request.go index 31f1ea41..e65f3ec9 100644 --- a/pkg/microservice/aslan/core/code/service/merge_request.go +++ b/pkg/microservice/aslan/core/code/service/merge_request.go @@ -21,10 +21,20 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) func CodeHostListPRs(codeHostID int, projectName, namespace, targetBr string, key string, page, perPage int, log *zap.SugaredLogger) ([]*client.PullRequest, error) { - cli, err := open.OpenClient(codeHostID, log) + ch, err := systemconfig.New().GetCodeHost(codeHostID) + if err != nil { + log.Errorf("get code host info err:%s", err) + return nil, err + } + if ch.Type == setting.SourceFromOther { + return []*client.PullRequest{}, nil + } + cli, err := open.OpenClient(ch, log) if err != nil { log.Errorf("open client err:%s", err) return nil, err diff --git a/pkg/microservice/aslan/core/code/service/namespace.go b/pkg/microservice/aslan/core/code/service/namespace.go index 6b8ad4b7..4e610728 100644 --- a/pkg/microservice/aslan/core/code/service/namespace.go +++ b/pkg/microservice/aslan/core/code/service/namespace.go @@ -21,6 +21,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) const ( @@ -32,7 +34,17 @@ const ( ) func CodeHostListNamespaces(codeHostID int, keyword string, log *zap.SugaredLogger) ([]*client.Namespace, error) { - cli, err := open.OpenClient(codeHostID, log) + ch, err := systemconfig.New().GetCodeHost(codeHostID) + if err != nil { + log.Errorf("get code host info err:%s", err) + return nil, err + } + + if ch.Type == setting.SourceFromOther { + return []*client.Namespace{}, nil + } + + cli, err := open.OpenClient(ch, log) if err != nil { log.Errorf("open client err:%s", err) return nil, err diff --git a/pkg/microservice/aslan/core/code/service/project.go b/pkg/microservice/aslan/core/code/service/project.go index d3590a41..3596c46b 100644 --- a/pkg/microservice/aslan/core/code/service/project.go +++ b/pkg/microservice/aslan/core/code/service/project.go @@ -21,10 +21,20 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) func CodeHostListProjects(codeHostID int, namespace, namespaceType string, page, perPage int, keyword string, log *zap.SugaredLogger) ([]*client.Project, error) { - cli, err := open.OpenClient(codeHostID, log) + ch, err := systemconfig.New().GetCodeHost(codeHostID) + if err != nil { + log.Errorf("get code host info err:%s", err) + return nil, err + } + if ch.Type == setting.SourceFromOther { + return []*client.Project{}, nil + } + cli, err := open.OpenClient(ch, log) if err != nil { log.Errorf("open client err:%s", err) return nil, err diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index 8ed2e3bf..ce05138f 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -26,6 +26,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) type RepoInfoList struct { @@ -56,7 +58,15 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo var errList *multierror.Error for _, info := range infos { - codehostClient, err := open.OpenClient(info.CodehostID, log) + ch, err := systemconfig.New().GetCodeHost(info.CodehostID) + if err != nil { + log.Errorf("get code host info err:%s", err) + return nil, err + } + if ch.Type == setting.SourceFromOther { + return []*GitRepoInfo{}, nil + } + codehostClient, err := open.OpenClient(ch, log) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/code/service/tag.go b/pkg/microservice/aslan/core/code/service/tag.go index 057f8246..79039182 100644 --- a/pkg/microservice/aslan/core/code/service/tag.go +++ b/pkg/microservice/aslan/core/code/service/tag.go @@ -21,11 +21,20 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client" "github.com/koderover/zadig/pkg/microservice/aslan/core/code/client/open" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" ) func CodeHostListTags(codeHostID int, projectName string, namespace string, key string, page int, perPage int, log *zap.SugaredLogger) ([]*client.Tag, error) { - - cli, err := open.OpenClient(codeHostID, log) + ch, err := systemconfig.New().GetCodeHost(codeHostID) + if err != nil { + log.Errorf("get code host info err:%s", err) + return nil, err + } + if ch.Type == setting.SourceFromOther { + return []*client.Tag{}, nil + } + cli, err := open.OpenClient(ch, log) if err != nil { log.Errorf("open client err:%s", err) return nil, err diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 33930de9..611b29ba 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" ) type Build struct { @@ -192,6 +193,9 @@ func (build *Build) SafeReposDeepCopy() []*types.Repository { resp := make([]*types.Repository, 0) for _, repo := range build.Repos { tmpRepo := *repo + if tmpRepo.Source == setting.SourceFromOther { + tmpRepo.RepoOwner, tmpRepo.RepoName = util.ParseOwnerAndRepo(tmpRepo.OtherAddress, tmpRepo.AuthType) + } resp = append(resp, &tmpRepo) } return resp diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index 897505ac..db04debb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -295,14 +295,14 @@ func SetTriggerBuilds(builds []*types.Repository, buildArgs []*types.Repository, go func(build *types.Repository) { defer wg.Done() - setBuildInfo(build, log) + setBuildInfo(build, buildArgs, log) }(build) } wg.Wait() return nil } -func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { +func setBuildInfo(build *types.Repository, buildArgs []*types.Repository, log *zap.SugaredLogger) { codeHostInfo, err := systemconfig.New().GetCodeHost(build.CodehostID) if err != nil { log.Errorf("failed to get codehost detail %d %v", build.CodehostID, err) @@ -403,9 +403,8 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { } } } - } else { + } else if codeHostInfo.Type == systemconfig.GitHubProvider { gitCli := git.NewClient(codeHostInfo.AccessToken, config.ProxyHTTPSAddr(), codeHostInfo.EnableProxy) - //// 需要后端自动获取Branch当前Commit,并填写到build中 if build.CommitID == "" { if build.Tag != "" && build.PR == 0 { opt := &github.ListOptions{Page: 1, PerPage: 100} @@ -454,28 +453,19 @@ func setBuildInfo(build *types.Repository, log *zap.SugaredLogger) { log.Warnf("github setBuildInfo failed, use build %+v", build) return } - //if build.Branch != "" && build.PR > 0 { - // prCommits := make([]*github.RepositoryCommit, 0) - // opt := &github.ListOptions{Page: 1, PerPage: 100} - // for opt.Page > 0 { - // list, resp, err := gitCli.PullRequests.ListCommits(context.Background(), build.RepoOwner, build.RepoName, build.PR, opt) - // if err != nil { - // fmt.Printf("ListCommits error: %v\n", err) - // return - // } - // prCommits = append(prCommits, list...) - // opt.Page = resp.NextPage - // } - // - // if len(prCommits) > 0 { - // commit := prCommits[len(prCommits)-1] - // build.CommitID = *commit.SHA - // } - //} } - + } else if codeHostInfo.Type == systemconfig.OtherProvider { + build.SSHKey = codeHostInfo.SSHKey + build.PrivateAccessToken = codeHostInfo.PrivateAccessToken + build.RepoOwner, build.RepoName = util.ParseOwnerAndRepo(build.OtherAddress, build.AuthType) + for _, buildArg := range buildArgs { + if buildArg.RepoOwner == build.RepoOwner && buildArg.RepoName == build.RepoName && buildArg.CheckoutPath == build.CheckoutPath { + setBuildFromArg(build, buildArg) + break + } + } + return } - } // 根据传入的build arg设置build参数 @@ -529,7 +519,7 @@ func setManunalBuilds(builds []*types.Repository, buildArgs []*types.Repository, break } } - setBuildInfo(build, log) + setBuildInfo(build, buildArgs, log) }(build) } wg.Wait() diff --git a/pkg/microservice/reaper/core/service/meta/const.go b/pkg/microservice/reaper/core/service/meta/const.go index 97cfb964..fe306fd6 100644 --- a/pkg/microservice/reaper/core/service/meta/const.go +++ b/pkg/microservice/reaper/core/service/meta/const.go @@ -31,6 +31,9 @@ const ( // ProviderGitee ProviderGitee = "gitee" + // ProviderOther + ProviderOther = "other" + // Oauth prefix OauthTokenPrefix = "oauth2" diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index bda4c074..15259328 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -262,21 +262,25 @@ type Install struct { // Repo ... type Repo struct { - Source string `yaml:"source"` - Address string `yaml:"address"` - Owner string `yaml:"owner"` - Name string `yaml:"name"` - RemoteName string `yaml:"remote_name"` - Branch string `yaml:"branch"` - PR int `yaml:"pr"` - Tag string `yaml:"tag"` - CheckoutPath string `yaml:"checkout_path"` - SubModules bool `yaml:"submodules"` - OauthToken string `yaml:"oauthToken"` - User string `yaml:"username"` - Password string `yaml:"password"` - CheckoutRef string `yaml:"checkout_ref"` - EnableProxy bool `yaml:"enable_proxy"` + Source string `yaml:"source"` + Address string `yaml:"address"` + Owner string `yaml:"owner"` + Name string `yaml:"name"` + RemoteName string `yaml:"remote_name"` + Branch string `yaml:"branch"` + PR int `yaml:"pr"` + Tag string `yaml:"tag"` + CheckoutPath string `yaml:"checkout_path"` + SubModules bool `yaml:"submodules"` + OauthToken string `yaml:"oauthToken"` + User string `yaml:"username"` + Password string `yaml:"password"` + CheckoutRef string `yaml:"checkout_ref"` + EnableProxy bool `yaml:"enable_proxy"` + OtherAddress string `yaml:"other_address,omitempty"` + AuthType types.AuthType `yaml:"auth_type,omitempty"` + SSHKey string `yaml:"ssh_key,omitempty"` + PrivateAccessToken string `yaml:"private_access_token,omitempty"` } // PRRef returns refs format @@ -422,7 +426,7 @@ func (g *Git) HTTPSCloneURL(source, token, owner, name string) string { // e.g. //https://oauth2:ACCESS_TOKEN@somegitlab.com/owner/name.git func (g *Git) OAuthCloneURL(source, token, address, owner, name, scheme string) string { - if strings.ToLower(source) == ProviderGitlab { + if strings.ToLower(source) == ProviderGitlab || strings.ToLower(source) == ProviderOther { // address 需要传过来 return fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", scheme, OauthTokenPrefix, token, address, owner, name) } diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 419ddc7a..5ceb3efd 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -20,15 +20,20 @@ import ( "bufio" "encoding/base64" "fmt" + "io/ioutil" "net/url" "os" "path" "path/filepath" "strings" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/reaper/config" c "github.com/koderover/zadig/pkg/microservice/reaper/core/service/cmd" "github.com/koderover/zadig/pkg/microservice/reaper/core/service/meta" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) func (r *Reaper) RunGitGc(folder string) error { @@ -82,7 +87,7 @@ func (r *Reaper) runGitCmds() error { //cmds = append(cmds, &c.Command{Cmd: c.SetConfig("http.postBuffer", "524288000"), DisableTrace: true}) cmds = append(cmds, &c.Command{Cmd: c.SetConfig("http.postBuffer", "2097152000"), DisableTrace: true}) var tokens []string - + var hostNames = sets.NewString() for _, repo := range r.Ctx.Repos { if repo == nil || len(repo.Name) == 0 { continue @@ -103,9 +108,18 @@ func (r *Reaper) runGitCmds() error { } } else if repo.Source == meta.ProviderCodehub { tokens = append(tokens, repo.Password) + } else if repo.Source == meta.ProviderOther { + tokens = append(tokens, repo.PrivateAccessToken) + tokens = append(tokens, repo.SSHKey) } tokens = append(tokens, repo.OauthToken) - cmds = append(cmds, r.buildGitCommands(repo)...) + cmds = append(cmds, r.buildGitCommands(repo, hostNames)...) + } + // write ssh key + if len(hostNames.List()) > 0 { + if err := writeSSHConfigFile(hostNames, r.Ctx.Proxy); err != nil { + return err + } } for _, c := range cmds { @@ -147,7 +161,7 @@ func (r *Reaper) runGitCmds() error { return nil } -func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { +func (r *Reaper) buildGitCommands(repo *meta.Repo, hostNames sets.String) []*c.Command { cmds := make([]*c.Command, 0) @@ -204,6 +218,31 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { }) } else if repo.Source == meta.ProviderGitee { cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) + } else if repo.Source == meta.ProviderOther { + if repo.AuthType == types.SSHAuthType { + host := getHost(repo.OtherAddress) + if !hostNames.Has(host) { + if err := writeSSHFile(repo.SSHKey, host); err != nil { + log.Errorf("failed to write ssh file %s: %s", repo.SSHKey, err) + } + hostNames.Insert(host) + } + + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, repo.OtherAddress), + DisableTrace: true, + }) + } else if repo.AuthType == types.PrivateAccessTokenAuthType { + u, err := url.Parse(repo.OtherAddress) + if err != nil { + log.Errorf("failed to parse url,err:%s", err) + } else { + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.OAuthCloneURL(repo.Source, repo.PrivateAccessToken, u.Host, repo.Owner, repo.Name, u.Scheme)), + DisableTrace: true, + }) + } + } } else { // github cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) @@ -239,3 +278,41 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo) []*c.Command { return cmds } + +func writeSSHFile(sshKey, hostName string) error { + if sshKey == "" { + return fmt.Errorf("ssh cannot be empty") + } + + if hostName == "" { + return fmt.Errorf("hostName cannot be empty") + } + + pathName := fmt.Sprintf("/.ssh/id_rsa.%s", strings.Replace(hostName, ".", "", -1)) + file := path.Join(config.Home(), pathName) + return ioutil.WriteFile(file, []byte(sshKey), 0400) +} + +func writeSSHConfigFile(hostNames sets.String, proxy *meta.Proxy) error { + out := "\nHOST *\nStrictHostKeyChecking=no\nUserKnownHostsFile=/dev/null\n" + for _, hostName := range hostNames.List() { + out += fmt.Sprintf("\nHost %s\nIdentityFile ~/.ssh/id_rsa.%s\n", hostName, strings.Replace(hostName, ".", "", -1)) + if proxy.EnableRepoProxy && proxy.Type == "socks5" { + out = out + fmt.Sprintf("ProxyCommand nc -x %s %%h %%p\n", proxy.GetProxyURL()) + } + } + file := path.Join(config.Home(), "/.ssh/config") + return ioutil.WriteFile(file, []byte(out), 0600) +} + +// git@github.com:koderover/zadig.git +// return github.com +func getHost(address string) string { + hostArr := strings.Split(address, ":") + host := hostArr[0] + host = strings.TrimPrefix(host, "git@") + if len(hostArr) > 2 { + return fmt.Sprintf("%s:%s", host, hostArr[1]) + } + return host +} diff --git a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go index dfa8245b..80a8f23e 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/models/codehost.go @@ -16,23 +16,31 @@ limitations under the License. package models +import ( + "github.com/koderover/zadig/pkg/types" +) + type CodeHost struct { - ID int `bson:"id" json:"id"` - Type string `bson:"type" json:"type"` - Address string `bson:"address" json:"address"` - IsReady string `bson:"is_ready" json:"is_ready"` - AccessToken string `bson:"access_token" json:"access_token"` - RefreshToken string `bson:"refresh_token" json:"refresh_token"` - Namespace string `bson:"namespace" json:"namespace"` - ApplicationId string `bson:"application_id" json:"application_id"` - Region string `bson:"region" json:"region,omitempty"` - Username string `bson:"username" json:"username,omitempty"` - Password string `bson:"password" json:"password,omitempty"` - ClientSecret string `bson:"client_secret" json:"client_secret"` - CreatedAt int64 `bson:"created_at" json:"created_at"` - UpdatedAt int64 `bson:"updated_at" json:"updated_at"` - DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` - EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` + Address string `bson:"address" json:"address"` + IsReady string `bson:"is_ready" json:"is_ready"` + AccessToken string `bson:"access_token" json:"access_token"` + RefreshToken string `bson:"refresh_token" json:"refresh_token"` + Namespace string `bson:"namespace" json:"namespace"` + ApplicationId string `bson:"application_id" json:"application_id"` + Region string `bson:"region,omitempty" json:"region,omitempty"` + Username string `bson:"username,omitempty" json:"username,omitempty"` + Password string `bson:"password,omitempty" json:"password,omitempty"` + ClientSecret string `bson:"client_secret" json:"client_secret"` + Alias string `bson:"alias,omitempty" json:"alias,omitempty"` + AuthType types.AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty"` + SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty"` + PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty"` + CreatedAt int64 `bson:"created_at" json:"created_at"` + UpdatedAt int64 `bson:"updated_at" json:"updated_at"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 66508ff6..0813177e 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -157,6 +157,7 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, "username": host.Username, "password": host.Password, "enable_proxy": host.EnableProxy, + "alias": host.Alias, "updated_at": time.Now().Unix(), } if host.Type == setting.SourceFromGerrit { @@ -165,6 +166,10 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, modifyValue["access_token"] = host.AccessToken modifyValue["refresh_token"] = host.RefreshToken modifyValue["updated_at"] = host.UpdatedAt + } else if host.Type == setting.SourceFromOther { + modifyValue["auth_type"] = host.AuthType + modifyValue["ssh_key"] = host.SSHKey + modifyValue["private_access_token"] = host.PrivateAccessToken } change := bson.M{"$set": modifyValue} diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 977d2d6b..dd4859f2 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -41,13 +41,14 @@ import ( const callback = "/api/directory/codehosts/callback" func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHost, error) { - if codehost.Type == "codehub" { + if codehost.Type == setting.SourceFromCodeHub || codehost.Type == setting.SourceFromOther { codehost.IsReady = "2" } - if codehost.Type == "gerrit" { + if codehost.Type == setting.SourceFromGerrit { codehost.IsReady = "2" codehost.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", codehost.Username, codehost.Password))) } + codehost.CreatedAt = time.Now().Unix() codehost.UpdatedAt = time.Now().Unix() @@ -81,6 +82,22 @@ func encypteCodeHost(encryptedKey string, codeHosts []*models.CodeHost, log *zap return nil, err } } + if len(codeHost.SSHKey) > 0 { + codeHost.SSHKey, err = crypto.AesEncryptByKey(codeHost.SSHKey, aesKey.PlainText) + if err != nil { + log.Errorf("ListCodeHost AesEncryptByKey error:%s", err) + return nil, err + } + } + + if len(codeHost.PrivateAccessToken) > 0 { + codeHost.PrivateAccessToken, err = crypto.AesEncryptByKey(codeHost.PrivateAccessToken, aesKey.PlainText) + if err != nil { + log.Errorf("ListCodeHost AesEncryptByKey error:%s", err) + return nil, err + } + } + result = append(result, codeHost) } return result, nil diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index ba0527b9..9aab26a5 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -268,21 +268,25 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName for _, build := range b.JobCtx.Builds { repo := &types.Repo{ - Source: build.Source, - Owner: build.RepoOwner, - Name: build.RepoName, - RemoteName: build.RemoteName, - Branch: build.Branch, - PR: build.PR, - Tag: build.Tag, - CheckoutPath: build.CheckoutPath, - SubModules: build.SubModules, - OauthToken: build.OauthToken, - Address: build.Address, - CheckoutRef: build.CheckoutRef, - User: build.Username, - Password: build.Password, - EnableProxy: build.EnableProxy, + Source: build.Source, + Owner: build.RepoOwner, + Name: build.RepoName, + RemoteName: build.RemoteName, + Branch: build.Branch, + PR: build.PR, + Tag: build.Tag, + CheckoutPath: build.CheckoutPath, + SubModules: build.SubModules, + OauthToken: build.OauthToken, + Address: build.Address, + CheckoutRef: build.CheckoutRef, + User: build.Username, + Password: build.Password, + EnableProxy: build.EnableProxy, + OtherAddress: build.OtherAddress, + AuthType: build.AuthType, + SSHKey: build.SSHKey, + PrivateAccessToken: build.PrivateAccessToken, } ctx.Repos = append(ctx.Repos, repo) } diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index bf1e319c..c2636f2b 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -217,21 +217,25 @@ type Install struct { // Repo ... type Repo struct { - Source string `yaml:"source"` - Address string `yaml:"address"` - Owner string `yaml:"owner"` - Name string `yaml:"name"` - RemoteName string `yaml:"remote_name"` - Branch string `yaml:"branch"` - PR int `yaml:"pr"` - Tag string `yaml:"tag"` - CheckoutPath string `yaml:"checkout_path"` - SubModules bool `yaml:"submodules"` - OauthToken string `yaml:"oauthToken"` - User string `yaml:"username"` - Password string `yaml:"password"` - CheckoutRef string `yaml:"checkout_ref"` - EnableProxy bool `yaml:"enable_proxy"` + Source string `yaml:"source"` + Address string `yaml:"address"` + Owner string `yaml:"owner"` + Name string `yaml:"name"` + RemoteName string `yaml:"remote_name"` + Branch string `yaml:"branch"` + PR int `yaml:"pr"` + Tag string `yaml:"tag"` + CheckoutPath string `yaml:"checkout_path"` + SubModules bool `yaml:"submodules"` + OauthToken string `yaml:"oauthToken"` + User string `yaml:"username"` + Password string `yaml:"password"` + CheckoutRef string `yaml:"checkout_ref"` + EnableProxy bool `yaml:"enable_proxy"` + OtherAddress string `yaml:"other_address,omitempty"` + AuthType types.AuthType `yaml:"auth_type,omitempty"` + SSHKey string `yaml:"ssh_key,omitempty"` + PrivateAccessToken string `yaml:"private_access_token,omitempty"` } // PRRef returns refs format diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 65f9009e..be244c4f 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -289,6 +289,11 @@ type Repository struct { RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty"` EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy,omitempty"` + // The address of the code base input of the other type + OtherAddress string `bson:"other_address,omitempty" json:"other_address,omitempty"` + AuthType types.AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty"` + SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty"` + PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty"` } type BuildStep struct { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 2cdb4f7b..b1ecf056 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -241,6 +241,8 @@ const ( SourceFromCodeHub = "codehub" // SourceFromGitee Configure the source as gitee SourceFromGitee = "gitee" + // SourceFromGitee Configure the source as other + SourceFromOther = "other" // SourceFromChartTemplate The configuration source is helmTemplate SourceFromChartTemplate = "chartTemplate" // SourceFromPublicRepo The configuration source is publicRepo diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 30592747..430abe6e 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/types" ) const ( @@ -28,6 +29,7 @@ const ( GerritProvider = "gerrit" CodeHubProvider = "codehub" GiteeProvider = "gitee" + OtherProvider = "other" ) type CodeHost struct { @@ -44,8 +46,12 @@ type CodeHost struct { Username string `json:"username"` Password string `json:"password"` // the field determine whether the proxy is enabled - EnableProxy bool `json:"enable_proxy"` - UpdatedAt int64 `json:"updated_at"` + EnableProxy bool `json:"enable_proxy"` + UpdatedAt int64 `json:"updated_at"` + OtherAddress string `json:"other_address,omitempty"` + AuthType types.AuthType `json:"auth_type,omitempty"` + SSHKey string `json:"ssh_key,omitempty"` + PrivateAccessToken string `json:"private_access_token,omitempty"` } type Option struct { diff --git a/pkg/types/git.go b/pkg/types/git.go new file mode 100644 index 00000000..e19eda34 --- /dev/null +++ b/pkg/types/git.go @@ -0,0 +1,24 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type AuthType string + +const ( + SSHAuthType AuthType = "SSH" + PrivateAccessTokenAuthType AuthType = "PrivateAccessToken" +) diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 3f143a44..68e2caa0 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -24,7 +24,6 @@ import ( // Repository struct type Repository struct { - // Source is github, gitlab Source string `bson:"source,omitempty" json:"source,omitempty"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` @@ -56,6 +55,11 @@ type Repository struct { EnableProxy bool `bson:"-" json:"enable_proxy,omitempty"` // FilterRegexp is the regular expression filter for the branches and tags FilterRegexp string `bson:"-" json:"filter_regexp,omitempty"` + // The address of the code base input of the other type + OtherAddress string `bson:"other_address,omitempty" json:"other_address,omitempty"` + AuthType AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty"` + SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty"` + PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty"` } type BranchFilterInfo struct { diff --git a/pkg/util/url.go b/pkg/util/url.go index 69c0c744..8e054bfb 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -20,6 +20,9 @@ import ( "fmt" "net/url" "strings" + + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) func TrimURLScheme(urlAddr string) string { @@ -75,3 +78,31 @@ func GetAddress(uri string) (string, error) { return fmt.Sprintf("%s://%s", u.Scheme, u.Host), nil } + +// ParseOwnerAndRepo extracts the owner and repo info from the given link, +// the link must have to following format: http(s)://example.com/owner/repo +func ParseOwnerAndRepo(repoLink string, authType types.AuthType) (string, string) { + var ownerAndRepo []string + repoLink = strings.TrimSuffix(repoLink, ".git") + switch authType { + case types.SSHAuthType: + repoLinkArr := strings.Split(repoLink, ":") + currentRepoLink := repoLinkArr[len(repoLinkArr)-1] + uriPath := strings.Trim(currentRepoLink, "/") + ownerAndRepo = strings.Split(uriPath, "/") + case types.PrivateAccessTokenAuthType: + uri, err := url.Parse(repoLink) + if err != nil { + log.Errorf("failed to parse url, err:%s", err) + return "", "" + } + + uriPath := strings.Trim(uri.Path, "/") + ownerAndRepo = strings.Split(uriPath, "/") + } + if len(ownerAndRepo) != 2 { + return "", "" + } + + return ownerAndRepo[0], ownerAndRepo[1] +} -- Gitee From 9e546a676afd711481345b343ceb796745a3af7f Mon Sep 17 00:00:00 2001 From: leozhang2018 Date: Thu, 19 May 2022 18:06:02 +0800 Subject: [PATCH 2357/2647] add new contributor @rovast Signed-off-by: leozhang2018 --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index cdfacc9a..351efb48 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -71,3 +71,4 @@ The council will make a decision for the nomination. The candidate has to have d - [tbbrave](https://github.com/tbbrave) - [lxm](https://github.com/lxm) - [zxdvd](https://github.com/zxdvd) +- [rovast](https://github.com/rovast) -- Gitee From f8479ba60789c6fdcf0544aefee07a215277f71e Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 19 May 2022 18:26:07 +0800 Subject: [PATCH 2358/2647] frontend policy (#1499) * add GetUserRules Signed-off-by: panxunying * add GetUserRulesByProject Signed-off-by: panxunying * fix error Signed-off-by: panxunying * add production environment Signed-off-by: panxunying * remove no use code Signed-off-by: panxunying Co-authored-by: panxunying --- .../service/collaboration_instance.go | 38 +-- .../aslan/core/environment/service/bundle.go | 4 +- .../aslan/core/label/config/consts.go | 4 +- .../aslan/core/label/service/label.go | 2 +- .../aslan/core/label/service/labelsbinding.go | 2 +- .../policy/core/handler/permission.go | 11 +- .../policy/core/handler/router.go | 4 +- .../core/repository/mongodb/role_binding.go | 22 ++ .../core/service/bundle/exemption_urls.go | 4 + .../core/service/bundle/rego/authz.rego | 2 +- .../policy/core/service/permission.go | 236 ++++++++++++++++-- .../policy/core/service/policy.go | 12 + .../core/service/policy_meta_registration.go | 2 +- 13 files changed, 292 insertions(+), 51 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 36a7d3fd..03d20893 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -401,11 +401,11 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp rules = append(rules, &policy.Rule{ Verbs: product.Verbs, Kind: "resource", - Resources: []string{string(config2.ResourceTypeProduct)}, + Resources: []string{string(config2.ResourceTypeEnvironment)}, MatchAttributes: []policy.MatchAttribute{ { Key: "policy", - Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name), + Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeEnvironment), product.Name), }, }, }) @@ -460,11 +460,11 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp rules = append(rules, &policy.Rule{ Verbs: product.Verbs, Kind: "resource", - Resources: []string{string(config2.ResourceTypeProduct)}, + Resources: []string{string(config2.ResourceTypeEnvironment)}, MatchAttributes: []policy.MatchAttribute{ { Key: "policy", - Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeProduct), product.BaseName), + Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeEnvironment), product.BaseName), }, }, }) @@ -528,7 +528,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, - string(config2.ResourceTypeProduct), product.Name), + string(config2.ResourceTypeEnvironment), product.Name), Type: setting.ResourceTypeSystem, ProjectName: projectName, }) @@ -548,7 +548,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType labels = append(labels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, - string(config2.ResourceTypeProduct), product.Name), + string(config2.ResourceTypeEnvironment), product.Name), Type: setting.ResourceTypeSystem, ProjectName: projectName, }) @@ -566,7 +566,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType deleteLabels = append(deleteLabels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, - string(config2.ResourceTypeProduct), product.BaseName), + string(config2.ResourceTypeEnvironment), product.BaseName), Type: setting.ResourceTypeSystem, ProjectName: projectName, }) @@ -586,7 +586,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType deleteLabels = append(deleteLabels, mongodb2.Label{ Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, - string(config2.ResourceTypeProduct), product.BaseName), + string(config2.ResourceTypeEnvironment), product.BaseName), Type: setting.ResourceTypeSystem, }) } @@ -635,7 +635,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType for _, product := range item.UpdateSpec.Products { labels = append(labels, mongodb2.Label{ Key: "policy", - Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name), + Value: buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeEnvironment), product.New.Name), Type: setting.ResourceTypeSystem, }) } @@ -683,7 +683,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }) } for _, product := range mode.Products { - labelValue := buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeProduct), product.Name) + labelValue := buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeEnvironment), product.Name) labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) @@ -697,7 +697,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType Resource: mongodb2.Resource{ Name: name, ProjectName: projectName, - Type: string(config2.ResourceTypeProduct), + Type: string(config2.ResourceTypeEnvironment), }, }) } @@ -723,7 +723,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType }) } for _, product := range item.NewSpec.Products { - labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.Name) + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeEnvironment), product.Name) labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) @@ -736,7 +736,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType LabelID: labelId, Resource: mongodb2.Resource{ Name: name, - Type: string(config2.ResourceTypeProduct), + Type: string(config2.ResourceTypeEnvironment), ProjectName: projectName, }, }) @@ -791,7 +791,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType } for _, product := range item.UpdateSpec.Products { if product.Old.CollaborationType == config.CollaborationShare && product.New.CollaborationType == config.CollaborationNew { - labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name) + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeEnvironment), product.New.Name) labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) @@ -800,7 +800,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType LabelID: labelId, Resource: mongodb2.Resource{ Name: buildName(product.New.Name, item.CollaborationMode, identityType, userName), - Type: string(config2.ResourceTypeProduct), + Type: string(config2.ResourceTypeEnvironment), ProjectName: projectName, }, }) @@ -808,13 +808,13 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType LabelID: labelId, Resource: mongodb2.Resource{ Name: product.Old.BaseName, - Type: string(config2.ResourceTypeProduct), + Type: string(config2.ResourceTypeEnvironment), ProjectName: projectName, }, }) } if product.Old.CollaborationType == config.CollaborationNew && product.New.CollaborationType == config.CollaborationShare { - labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeProduct), product.New.Name) + labelValue := buildLabelValue(projectName, item.CollaborationMode, identityType, userName, string(config2.ResourceTypeEnvironment), product.New.Name) labelId, ok := labelIdMap[service.BuildLabelString("policy", labelValue)] if !ok { return fmt.Errorf("label:%s not exist", labelValue) @@ -823,7 +823,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType LabelID: labelId, Resource: mongodb2.Resource{ Name: product.New.Name, - Type: string(config2.ResourceTypeProduct), + Type: string(config2.ResourceTypeEnvironment), ProjectName: projectName, }, }) @@ -831,7 +831,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType LabelID: labelId, Resource: mongodb2.Resource{ Name: product.Old.Name, - Type: string(config2.ResourceTypeProduct), + Type: string(config2.ResourceTypeEnvironment), ProjectName: projectName, }, }) diff --git a/pkg/microservice/aslan/core/environment/service/bundle.go b/pkg/microservice/aslan/core/environment/service/bundle.go index 278fc6b7..405a50a3 100644 --- a/pkg/microservice/aslan/core/environment/service/bundle.go +++ b/pkg/microservice/aslan/core/environment/service/bundle.go @@ -51,7 +51,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { resource := labeldb.Resource{ Name: env.EnvName, ProjectName: env.ProductName, - Type: string(config.ResourceTypeProduct), + Type: string(config.ResourceTypeEnvironment), } resources = append(resources, resource) } @@ -74,7 +74,7 @@ func GetBundleResources(logger *zap.SugaredLogger) ([]*resourceSpec, error) { } for _, env := range envs { - resourceKey := commonConfig.BuildResourceKey(string(config.ResourceTypeProduct), env.ProductName, env.EnvName) + resourceKey := commonConfig.BuildResourceKey(string(config.ResourceTypeEnvironment), env.ProductName, env.EnvName) resourceSpec := &resourceSpec{ ResourceID: env.EnvName, ProjectName: env.ProductName, diff --git a/pkg/microservice/aslan/core/label/config/consts.go b/pkg/microservice/aslan/core/label/config/consts.go index 8bb25327..744761b1 100644 --- a/pkg/microservice/aslan/core/label/config/consts.go +++ b/pkg/microservice/aslan/core/label/config/consts.go @@ -19,6 +19,6 @@ package config type ResourceType string const ( - ResourceTypeWorkflow ResourceType = "Workflow" - ResourceTypeProduct ResourceType = "Environment" + ResourceTypeWorkflow ResourceType = "Workflow" + ResourceTypeEnvironment ResourceType = "Environment" ) diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index b31310d1..45d0a487 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -101,7 +101,7 @@ type ListResourcesByLabelsResp struct { } func BuildLabelString(key string, value string) string { - return fmt.Sprintf("%s-%s", key, value) + return fmt.Sprintf("%s:%s", key, value) } func ListResourcesByLabels(filters []mongodb.Label, logger *zap.SugaredLogger) (*ListResourcesByLabelsResp, error) { diff --git a/pkg/microservice/aslan/core/label/service/labelsbinding.go b/pkg/microservice/aslan/core/label/service/labelsbinding.go index 7401ca25..faf8c161 100644 --- a/pkg/microservice/aslan/core/label/service/labelsbinding.go +++ b/pkg/microservice/aslan/core/label/service/labelsbinding.go @@ -76,7 +76,7 @@ func CreateLabelBindings(cr *CreateLabelBindingsArgs, userName string, logger *z logger.Errorf("there're resources not exist") return e.ErrForbidden.AddDesc("there're resources not exist") } - case string(config.ResourceTypeProduct): + case string(config.ResourceTypeEnvironment): products := []commondb.Product{} resourceSet := sets.NewString() for _, vv := range v { diff --git a/pkg/microservice/policy/core/handler/permission.go b/pkg/microservice/policy/core/handler/permission.go index fdf51bbf..5646fafa 100644 --- a/pkg/microservice/policy/core/handler/permission.go +++ b/pkg/microservice/policy/core/handler/permission.go @@ -24,13 +24,18 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" ) -func GetUserPermission(c *gin.Context) { +func GetUserRules(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - projectName := c.Query("projectName") + ctx.Resp, ctx.Err = service.GetUserRules(ctx.UserID, ctx.Logger) +} + +func GetUserRulesByProject(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.GetPermission(projectName, c.Param("uid"), ctx.Logger) + ctx.Resp, ctx.Err = service.GetUserRulesByProject(ctx.UserID, c.Param("name"), ctx.Logger) } type GetUserResourcesPermissionReq struct { diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 3bb60242..071bc918 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -119,8 +119,8 @@ func (*Router) Inject(router *gin.RouterGroup) { } policyUserPermission := router.Group("permission") { - policyUserPermission.POST("resources", GetUserResourcesPermission) - policyUserPermission.GET("/:uid", GetUserPermission) + policyUserPermission.GET("project/:name", GetUserRulesByProject) + policyUserPermission.GET("", GetUserRules) } } diff --git a/pkg/microservice/policy/core/repository/mongodb/role_binding.go b/pkg/microservice/policy/core/repository/mongodb/role_binding.go index 1c57b353..ed86f765 100644 --- a/pkg/microservice/policy/core/repository/mongodb/role_binding.go +++ b/pkg/microservice/policy/core/repository/mongodb/role_binding.go @@ -115,6 +115,28 @@ func (c *RoleBindingColl) ListBy(projectName, uid string) ([]*models.RoleBinding return res, nil } +func (c *RoleBindingColl) ListRoleBindingsByUIDs(uids []string) ([]*models.RoleBinding, error) { + var res []*models.RoleBinding + + ctx := context.Background() + query := bson.M{} + if len(uids) > 0 { + query["subjects.uid"] = bson.M{"$in": uids} + } + + cursor, err := c.Collection.Find(ctx, query) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &res) + if err != nil { + return nil, err + } + + return res, nil +} + func (c *RoleBindingColl) ListSystemRoleBindingsByUIDs(uids []string) ([]*models.RoleBinding, error) { var res []*models.RoleBinding diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go index dddba9a7..940c80f6 100644 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ b/pkg/microservice/policy/core/service/bundle/exemption_urls.go @@ -108,6 +108,10 @@ var publicURLs = []*policyRule{ Methods: []string{"GET"}, Endpoints: []string{"api/aslan/environment/environments/?*/check/sharenv/disable/ready"}, }, + { + Methods: []string{"GET"}, + Endpoints: []string{"api/v1/permission/project/?*"}, + }, { Methods: []string{"GET"}, Endpoints: []string{"api/aslan/service/services/?*/environments/deployable"}, diff --git a/pkg/microservice/policy/core/service/bundle/rego/authz.rego b/pkg/microservice/policy/core/service/bundle/rego/authz.rego index 04d14018..60ae61ed 100644 --- a/pkg/microservice/policy/core/service/bundle/rego/authz.rego +++ b/pkg/microservice/policy/core/service/bundle/rego/authz.rego @@ -411,7 +411,7 @@ allowed_system_role_rules[rule] { some i data.roles.roles[i].name == role_ref.name - role_ref.namespace == "*" + data.roles.roles[i].namespace == "*" rule := data.roles.roles[i].rules[_] } diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index 8479c9c4..d2d4458a 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -18,46 +18,244 @@ package service import ( "fmt" + "strings" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/core/label/config" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" + "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/label" ) -// GetPermission user's permission for frontend -func GetPermission(ns, uid string, logger *zap.SugaredLogger) (map[string][]string, error) { - //1.get user's all role in some project - roleBindings, err := ListUserAllRoleBindings(ns, uid) +func GetUserRulesByProject(uid string, projectName string, log *zap.SugaredLogger) (*GetUserRulesByProjectResp, error) { + roleBindings, err := mongodb.NewRoleBindingColl().ListBy(projectName, uid) if err != nil { - logger.Errorf("ListUserAllRoleBindings err:%s", err) return nil, err } roles, err := ListUserAllRolesByRoleBindings(roleBindings) if err != nil { return nil, err } - //2.get user's all rules - rolesSet := map[string]sets.String{} - rolesResp := map[string][]string{} + roleMap := make(map[string]*models.Role) for _, role := range roles { + roleMap[role.Name] = role + } + isSystemAdmin := false + isProjectAdmin := false + projectVerbSet := sets.NewString() + for _, rolebinding := range roleBindings { + if rolebinding.RoleRef.Name == string(setting.SystemAdmin) { + isSystemAdmin = true + continue + } else if rolebinding.RoleRef.Name == string(setting.ProjectAdmin) { + isProjectAdmin = true + continue + } + var role *models.Role + if roleRef, ok := roleMap[rolebinding.RoleRef.Name]; ok { + role = roleRef + } else { + log.Errorf("roleMap has no role:%s", rolebinding.RoleRef.Name) + return nil, fmt.Errorf("roleMap has no role:%s", rolebinding.RoleRef.Name) + } for _, rule := range role.Rules { - for _, resource := range rule.Resources { - if verbsSet, ok := rolesSet[resource]; ok { + var ruleVerbs []string + if rule.Resources[0] == "ProductionEnvironment" { + for _, verb := range rule.Verbs { + ruleVerbs = append(ruleVerbs, "production:"+verb) + } + } else { + ruleVerbs = rule.Verbs + } + projectVerbSet.Insert(ruleVerbs...) + } + } + + policyBindings, err := mongodb.NewPolicyBindingColl().ListBy(projectName, uid) + if err != nil { + return nil, err + } + policies, err := ListUserAllPoliciesByPolicyBindings(policyBindings) + if err != nil { + return nil, err + } + policyMap := make(map[string]*models.Policy) + for _, policy := range policies { + policyMap[policy.Name] = policy + } + labelVerbMap := make(map[string][]string) + + for _, policyBinding := range policyBindings { + var policy *models.Policy + if policyRef, ok := policyMap[policyBinding.PolicyRef.Name]; ok { + policy = policyRef + } else { + log.Errorf("policyMap has no policy:%s", policyBinding.PolicyRef.Name) + return nil, fmt.Errorf("policyMap has no policy:%s", policyBinding.PolicyRef.Name) + } + + for _, rule := range policy.Rules { + for _, matchAttribute := range rule.MatchAttributes { + labelKeyKey := rule.Resources[0] + ":" + matchAttribute.Key + ":" + matchAttribute.Value + if verbs, ok := labelVerbMap[labelKeyKey]; ok { + verbsSet := sets.NewString(verbs...) verbsSet.Insert(rule.Verbs...) - rolesSet[resource] = verbsSet + labelVerbMap[labelKeyKey] = verbsSet.List() } else { - rolesSet[resource] = sets.NewString(rule.Verbs...) + labelVerbMap[labelKeyKey] = rule.Verbs } } } } - for k, v := range rolesSet { - rolesResp[k] = v.List() + var labels []label.Label + for labelKey, _ := range labelVerbMap { + keySplit := strings.Split(labelKey, ":") + labels = append(labels, label.Label{ + Type: keySplit[0], + Key: keySplit[1], + Value: keySplit[2], + }) + } + req := label.ListResourcesByLabelsReq{ + LabelFilters: labels, + } + resp, err := label.New().ListResourcesByLabels(req) + if err != nil { + return nil, err + } + environmentVerbMap := make(map[string][]string) + workflowVerbMap := make(map[string][]string) + for labelKey, resources := range resp.Resources { + for _, resource := range resources { + if verbs, ok := labelVerbMap[resource.Type+":"+labelKey]; ok { + if resource.Type == string(config.ResourceTypeEnvironment) { + if resourceVerbs, rOK := environmentVerbMap[resource.Name]; rOK { + verbSet := sets.NewString(resourceVerbs...) + verbSet.Insert(verbs...) + environmentVerbMap[resource.Name] = verbSet.List() + } else { + environmentVerbMap[resource.Name] = verbs + } + } + if resource.Type == string(config.ResourceTypeWorkflow) { + if resourceVerbs, rOK := workflowVerbMap[resource.Name]; rOK { + verbSet := sets.NewString(resourceVerbs...) + verbSet.Insert(verbs...) + workflowVerbMap[resource.Name] = verbSet.List() + } else { + workflowVerbMap[resource.Name] = verbs + } + } + } else { + log.Warnf("labelVerbMap key:%s not exist", resource.Type+":"+labelKey) + } + } + } + return &GetUserRulesByProjectResp{ + IsSystemAdmin: isSystemAdmin, + ProjectVerbs: projectVerbSet.List(), + IsProjectAdmin: isProjectAdmin, + EnvironmentVerbsMap: environmentVerbMap, + WorkflowVerbsMap: workflowVerbMap, + }, nil +} + +type GetUserRulesByProjectResp struct { + IsSystemAdmin bool `json:"is_system_admin"` + IsProjectAdmin bool `json:"is_project_admin"` + ProjectVerbs []string `json:"project_verbs"` + WorkflowVerbsMap map[string][]string `json:"workflow_verbs_map"` + EnvironmentVerbsMap map[string][]string `json:"environment_verbs_map"` +} + +type GetUserRulesResp struct { + IsSystemAdmin bool `json:"is_system_admin"` + ProjectAdminList []string `json:"project_admin_list"` + ProjectVerbMap map[string][]string `json:"project_verb_map"` + SystemVerbs []string `json:"system_verbs"` +} + +func GetUserRules(uid string, log *zap.SugaredLogger) (*GetUserRulesResp, error) { + roleBindings, err := mongodb.NewRoleBindingColl().ListRoleBindingsByUIDs([]string{uid}) + if err != nil { + return nil, err + } + if len(roleBindings) == 0 { + return nil, nil + } + roles, err := ListUserAllRolesByRoleBindings(roleBindings) + if err != nil { + return nil, err + } + roleMap := make(map[string]*models.Role) + for _, role := range roles { + roleMap[role.Name] = role + } + isSystemAdmin := false + projectAdminSet := sets.NewString() + projectVerbMap := make(map[string][]string) + systemVerbSet := sets.NewString() + for _, rolebinding := range roleBindings { + if rolebinding.RoleRef.Name == string(setting.SystemAdmin) { + isSystemAdmin = true + continue + } else if rolebinding.RoleRef.Name == string(setting.ProjectAdmin) { + projectAdminSet.Insert(rolebinding.Namespace) + continue + } + var role *models.Role + if roleRef, ok := roleMap[rolebinding.RoleRef.Name]; ok { + role = roleRef + } else { + log.Errorf("roleMap has no role:%s", rolebinding.RoleRef.Name) + return nil, fmt.Errorf("roleMap has no role:%s", rolebinding.RoleRef.Name) + } + if rolebinding.Namespace == "*" { + for _, rule := range role.Rules { + systemVerbSet.Insert(rule.Verbs...) + } + } else { + if verbs, ok := projectVerbMap[rolebinding.Namespace]; ok { + verbSet := sets.NewString(verbs...) + for _, rule := range role.Rules { + var ruleVerbs []string + if rule.Resources[0] == "ProductionEnvironment" { + for _, verb := range rule.Verbs { + ruleVerbs = append(ruleVerbs, "production:"+verb) + } + } else { + ruleVerbs = rule.Verbs + } + verbSet.Insert(ruleVerbs...) + } + projectVerbMap[rolebinding.Namespace] = verbSet.List() + + } else { + verbSet := sets.NewString() + for _, rule := range role.Rules { + var ruleVerbs []string + if rule.Resources[0] == "ProductionEnvironment" { + for _, verb := range rule.Verbs { + ruleVerbs = append(ruleVerbs, "production:"+verb) + } + } else { + ruleVerbs = rule.Verbs + } + verbSet.Insert(ruleVerbs...) + } + projectVerbMap[rolebinding.Namespace] = verbSet.List() + } + } } - return rolesResp, nil + return &GetUserRulesResp{ + IsSystemAdmin: isSystemAdmin, + ProjectVerbMap: projectVerbMap, + SystemVerbs: systemVerbSet.List(), + ProjectAdminList: projectAdminSet.List(), + }, nil } //GetResourcesPermission get resources action list for frontend to show icon @@ -114,18 +312,18 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, break } for _, rule := range role.Rules { - if rule.Resources[0] == resourceType && resourceType != string(config.ResourceTypeProduct) { + if rule.Resources[0] == resourceType && resourceType != string(config.ResourceTypeEnvironment) { for k, v := range resourceM { resourceM[k] = v.Insert(rule.Verbs...) } } - if (rule.Resources[0] == "Environment" || rule.Resources[0] == "ProductionEnvironment") && resourceType == string(config.ResourceTypeProduct) { + if (rule.Resources[0] == "Environment" || rule.Resources[0] == "ProductionEnvironment") && resourceType == string(config.ResourceTypeEnvironment) { var rs []label.Resource for _, v := range resources { r := label.Resource{ Name: v, ProjectName: projectName, - Type: string(config.ResourceTypeProduct), + Type: string(config.ResourceTypeEnvironment), } rs = append(rs, r) } @@ -135,7 +333,7 @@ func GetResourcesPermission(uid string, projectName string, resourceType string, continue } for _, resource := range resources { - resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeProduct, projectName, resource) + resourceKey := fmt.Sprintf("%s-%s-%s", config.ResourceTypeEnvironment, projectName, resource) if labels, ok := labelRes.Labels[resourceKey]; ok { for _, label := range labels { if label.Key == "production" { diff --git a/pkg/microservice/policy/core/service/policy.go b/pkg/microservice/policy/core/service/policy.go index 365f5f48..e1b5d22d 100644 --- a/pkg/microservice/policy/core/service/policy.go +++ b/pkg/microservice/policy/core/service/policy.go @@ -241,3 +241,15 @@ func DeletePolicies(names []string, projectName string, logger *zap.SugaredLogge return mongodb.NewPolicyBindingColl().DeleteByPolicies(names, projectName) } + +func ListUserAllPoliciesByPolicyBindings(policyBindings []*models.PolicyBinding) ([]*models.Policy, error) { + var policies []*models.Policy + for _, v := range policyBindings { + tmpRoles, err := mongodb.NewPolicyColl().ListBySpaceAndName(v.PolicyRef.Namespace, v.PolicyRef.Name) + if err != nil { + continue + } + policies = append(policies, tmpRoles...) + } + return policies, nil +} diff --git a/pkg/microservice/policy/core/service/policy_meta_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go index 00a18e9f..e5aa01b4 100644 --- a/pkg/microservice/policy/core/service/policy_meta_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -105,7 +105,7 @@ func GetPolicyRegistrationDefinitions(scope, envType string, _ *zap.SugaredLogge return nil, err } systemScopeSet := sets.NewString("TestCenter", "DataCenter", "Template", "DeliveryCenter") - projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build", "Service") + projectScopeSet := sets.NewString("Workflow", "Environment", "Test", "Delivery", "Build", "Service") systemPolicyMetas, projectPolicyMetas, filteredPolicyMetas := []*models.PolicyMeta{}, []*models.PolicyMeta{}, []*models.PolicyMeta{} for _, v := range policieMetas { if systemScopeSet.Has(v.Resource) { -- Gitee From c146cec1b95ed8e0b30d65a6bb65f3d745d3f95e Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 20 May 2022 10:00:11 +0800 Subject: [PATCH 2359/2647] fix gerrit url problem (#1515) Signed-off-by: liu deyi --- go.mod | 4 ++-- .../aslan/core/workflow/service/webhook/gerrit.go | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 6156701f..e2db34b3 100644 --- a/go.mod +++ b/go.mod @@ -15,9 +15,9 @@ require ( github.com/bradleyfalzon/ghinstallation v1.1.1 github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect github.com/bugsnag/panicwrap v1.3.1 // indirect - github.com/buraksezer/consistent v0.9.0 // indirect + github.com/buraksezer/consistent v0.9.0 github.com/cenkalti/backoff/v4 v4.1.1 - github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash v1.1.0 github.com/chartmuseum/helm-push v0.10.1 github.com/containers/image v3.0.2+incompatible github.com/coocood/freecache v1.1.0 diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index 70a82815..dba3fcad 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -32,6 +32,7 @@ import ( "github.com/otiai10/copy" "go.uber.org/zap" + systemConfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -58,12 +59,7 @@ type gerritTypeEvent struct { } func ProcessGerritHook(payload []byte, req *http.Request, requestID string, log *zap.SugaredLogger) error { - baseURI := fmt.Sprintf( - "%s://%s", - req.Header.Get("X-Forwarded-Proto"), - req.Header.Get("X-Forwarded-Host"), - ) - + baseURI := systemConfig.SystemAddress() gerritTypeEventObj := new(gerritTypeEvent) if err := json.Unmarshal(payload, gerritTypeEventObj); err != nil { log.Errorf("processGerritHook json.Unmarshal err : %v", err) -- Gitee From a5b5512788eca26cbd4428be103b055354e30bc3 Mon Sep 17 00:00:00 2001 From: dyliu Date: Fri, 20 May 2022 14:00:55 +0800 Subject: [PATCH 2360/2647] the service supports import from gitee and automatic synchronization of webhook (#1502) * Service supports importing from giteee Signed-off-by: liu deyi * the service supports import from gitee and automatic synchronization of webhook Signed-off-by: liu deyi * modify variable name Signed-off-by: liu deyi * add src path field Signed-off-by: liu deyi * add src path field Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * modify preload logic Signed-off-by: liu deyi * modify field name Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * save serviec Signed-off-by: liu deyi * remove unused code Signed-off-by: liu deyi * modify code after code review Signed-off-by: liu deyi * modify code after code review Signed-off-by: liu deyi * modify code after code review Signed-off-by: liu deyi * modify code after code review Signed-off-by: liu deyi --- go.mod | 1 + .../aslan/core/code/service/workspace.go | 9 +- .../core/common/repository/models/service.go | 1 + .../core/common/service/command/git_cmd.go | 2 +- .../aslan/core/common/service/fs/file.go | 5 + .../aslan/core/common/service/fs/s3.go | 37 ++- .../aslan/core/common/service/gitee/client.go | 4 +- .../aslan/core/common/service/helm.go | 29 +- .../aslan/core/service/service/helm.go | 72 +++-- .../aslan/core/service/service/loader.go | 273 +++++++++++++++++- .../aslan/core/service/service/service.go | 6 +- .../aslan/core/templatestore/service/chart.go | 10 +- .../core/workflow/service/webhook/gerrit.go | 23 +- .../core/workflow/service/webhook/gitee.go | 213 ++++++++++++++ pkg/tool/gitee/repositories.go | 2 +- pkg/util/file.go | 11 + 16 files changed, 619 insertions(+), 79 deletions(-) diff --git a/go.mod b/go.mod index e2db34b3..de83dfb6 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/go-github v17.0.0+incompatible github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index b1578a5f..cdb1712d 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -96,10 +96,9 @@ func getWorkspaceBasePath(pipelineName string) (string, error) { return "", err } - //base := path.Join(s.Config.NFS.Path, pipe.Name) base := path.Join(config.S3StoragePath(), pipe.Name) - if _, err := os.Stat(base); os.IsNotExist(err) { + if exsit, err := util.PathExists(base); !exsit { return "", err } @@ -144,16 +143,16 @@ func GetGitRepoInfo(codehostID int, repoOwner, repoName, branchName, remoteName, base := path.Join(config.S3StoragePath(), repoName) if err := os.RemoveAll(base); err != nil { - log.Errorf("dir remove err:%v", err) + log.Warnf("dir remove err:%s", err) } detail, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { - log.Errorf("GetGitRepoInfo GetCodehostDetail err:%v", err) + log.Errorf("GetGitRepoInfo GetCodehostDetail err:%s", err) return fis, e.ErrListRepoDir.AddDesc(err.Error()) } err = command.RunGitCmds(detail, repoOwner, repoName, branchName, remoteName) if err != nil { - log.Errorf("GetGitRepoInfo runGitCmds err:%v", err) + log.Errorf("GetGitRepoInfo runGitCmds err:%s", err) return fis, e.ErrListRepoDir.AddDesc(err.Error()) } files, err := ioutil.ReadDir(path.Join(base, dir)) diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 10efb512..54d5a986 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -52,6 +52,7 @@ type Service struct { GerritRemoteName string `bson:"gerrit_remote_name,omitempty" json:"gerrit_remote_name,omitempty"` GerritPath string `bson:"gerrit_path,omitempty" json:"gerrit_path,omitempty"` GerritCodeHostID int `bson:"gerrit_codeHost_id,omitempty" json:"gerrit_codeHost_id,omitempty"` + GiteePath string `bson:"gitee_path,omitempty" json:"gitee_path,omitempty"` BuildName string `bson:"build_name,omitempty" json:"build_name,omitempty"` HelmChart *HelmChart `bson:"helm_chart,omitempty" json:"helm_chart,omitempty"` EnvConfigs []*EnvConfig `bson:"env_configs,omitempty" json:"env_configs,omitempty"` diff --git a/pkg/microservice/aslan/core/common/service/command/git_cmd.go b/pkg/microservice/aslan/core/common/service/command/git_cmd.go index fc7ceef1..898461d6 100644 --- a/pkg/microservice/aslan/core/common/service/command/git_cmd.go +++ b/pkg/microservice/aslan/core/common/service/command/git_cmd.go @@ -176,7 +176,7 @@ func buildGitCommands(repo *Repo) []*Command { cmds = append(cmds, &Command{Cmd: RemoteRemove(repo.RemoteName), DisableTrace: true, IgnoreError: true}) } - if repo.Source == setting.SourceFromGitlab { + if repo.Source == setting.SourceFromGitlab || repo.Source == setting.SourceFromGitee { u, _ := url.Parse(repo.Address) url := OAuthCloneURL(repo.OauthToken, u.Host, repo.Owner, repo.Name, u.Scheme) cmds = append(cmds, &Command{ diff --git a/pkg/microservice/aslan/core/common/service/fs/file.go b/pkg/microservice/aslan/core/common/service/fs/file.go index e3fd1c54..7d513cd4 100644 --- a/pkg/microservice/aslan/core/common/service/fs/file.go +++ b/pkg/microservice/aslan/core/common/service/fs/file.go @@ -46,6 +46,11 @@ func PreloadFiles(name, localBase, s3Base, source string, logger *zap.SugaredLog logger.Errorf("Failed to download files from s3, err: %s", err) return err } + case setting.SourceFromGitee: + if err = DownloadAndCopyFilesFromGitee(name, localBase, logger); err != nil { + logger.Errorf("Failed to download files from s3, err: %s", err) + return err + } default: if err = DownloadAndExtractFilesFromS3(name, localBase, s3Base, logger); err != nil { logger.Errorf("Failed to download files from s3, err: %s", err) diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index c0607d45..14915808 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -31,7 +31,6 @@ import ( s3service "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/systemconfig" - "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" fsutil "github.com/koderover/zadig/pkg/util/fs" ) @@ -175,21 +174,49 @@ func DownloadAndCopyFilesFromGerrit(name, localBase string, logger *zap.SugaredL chartTemplate, err := mongodb.NewChartColl().Get(name) base := path.Join(config.S3StoragePath(), chartTemplate.Repo) if err := os.RemoveAll(base); err != nil { - logger.Errorf("Failed to remove dir, err:%s", err) + logger.Warnf("Failed to remove dir, err:%s", err) } detail, err := systemconfig.New().GetCodeHost(chartTemplate.CodeHostID) if err != nil { - log.Errorf("Failed to GetCodehostDetail, err:%s", err) + logger.Errorf("Failed to GetCodehostDetail, err:%s", err) return err } err = command.RunGitCmds(detail, "default", chartTemplate.Repo, chartTemplate.Branch, "origin") if err != nil { - log.Errorf("Failed to runGitCmds, err:%s", err) + logger.Errorf("Failed to runGitCmds, err:%s", err) return err } if err := CopyAndUploadFiles([]string{}, path.Join(localBase, path.Base(chartTemplate.Path)), "", path.Join(base, chartTemplate.Path), logger); err != nil { - log.Errorf("Failed to copy files for helm chart template %s, error: %s", name, err) + logger.Errorf("Failed to copy files for helm chart template %s, error: %s", name, err) + return err + } + return nil +} + +func DownloadAndCopyFilesFromGitee(name, localBase string, logger *zap.SugaredLogger) error { + chartTemplate, err := mongodb.NewChartColl().Get(name) + if err != nil { + logger.Errorf("Failed to get chart template, err:%s", err) + return err + } + base := path.Join(config.S3StoragePath(), chartTemplate.Repo) + if err := os.RemoveAll(base); err != nil { + logger.Warnf("Failed to remove dir, err:%s", err) + } + detail, err := systemconfig.New().GetCodeHost(chartTemplate.CodeHostID) + if err != nil { + logger.Errorf("Failed to get codehost detail, err:%s", err) + return err + } + err = command.RunGitCmds(detail, chartTemplate.Owner, chartTemplate.Repo, chartTemplate.Branch, "origin") + if err != nil { + logger.Errorf("Failed to runGitCmds, err:%s", err) + return err + } + + if err := CopyAndUploadFiles([]string{}, path.Join(localBase, path.Base(chartTemplate.Path)), "", path.Join(base, chartTemplate.Path), logger); err != nil { + logger.Errorf("Failed to copy files for helm chart template %s, error: %s", name, err) return err } return nil diff --git a/pkg/microservice/aslan/core/common/service/gitee/client.go b/pkg/microservice/aslan/core/common/service/gitee/client.go index c78ed56b..264a35cc 100644 --- a/pkg/microservice/aslan/core/common/service/gitee/client.go +++ b/pkg/microservice/aslan/core/common/service/gitee/client.go @@ -16,7 +16,9 @@ limitations under the License. package gitee -import "github.com/koderover/zadig/pkg/tool/gitee" +import ( + "github.com/koderover/zadig/pkg/tool/gitee" +) type Client struct { *gitee.Client diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 7ef0ba04..9f96059d 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -76,6 +76,8 @@ func PreLoadServiceManifests(base string, svc *commonmodels.Service) error { switch svc.Source { case setting.SourceFromGerrit: return preLoadServiceManifestsFromGerrit(svc) + case setting.SourceFromGitee: + return preLoadServiceManifestsFromGitee(svc) default: return preLoadServiceManifestsFromSource(svc) } @@ -140,7 +142,7 @@ func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { base := path.Join(config.S3StoragePath(), svc.GerritRepoName) if err := os.RemoveAll(base); err != nil { - log.Errorf("Failed to remove dir, err:%s", err) + log.Warnf("Failed to remove dir, err:%s", err) } detail, err := systemconfig.New().GetCodeHost(svc.GerritCodeHostID) if err != nil { @@ -152,7 +154,7 @@ func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { log.Errorf("Failed to runGitCmds, err:%s", err) return err } - // save files to disk and upload them to s3 + // copy files to disk and upload them to s3 if err := CopyAndUploadService(svc.ProductName, svc.ServiceName, svc.GerritPath, nil); err != nil { log.Errorf("Failed to save or upload files for service %s in project %s, error: %s", svc.ServiceName, svc.ProductName, err) return err @@ -168,3 +170,26 @@ func GeneHelmRepo(chartRepo *commonmodels.HelmRepo) *repo.Entry { Password: chartRepo.Password, } } + +func preLoadServiceManifestsFromGitee(svc *commonmodels.Service) error { + base := path.Join(config.S3StoragePath(), svc.RepoName) + if err := os.RemoveAll(base); err != nil { + log.Warnf("Failed to remove dir, err:%s", err) + } + detail, err := systemconfig.New().GetCodeHost(svc.CodehostID) + if err != nil { + log.Errorf("Failed to GetCodehostDetail, err:%s", err) + return err + } + err = command.RunGitCmds(detail, svc.RepoOwner, svc.RepoName, svc.BranchName, "origin") + if err != nil { + log.Errorf("Failed to runGitCmds, err:%s", err) + return err + } + // save files to disk and upload them to s3 + if err := CopyAndUploadService(svc.ProductName, svc.ServiceName, svc.GiteePath, nil); err != nil { + log.Errorf("Failed to copy or upload files for service %s in project %s, error: %s", svc.ServiceName, svc.ProductName, err) + return err + } + return nil +} diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 30dbab78..0e2e2405 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -108,6 +108,7 @@ type helmServiceCreationArgs struct { ValuePaths []string ValuesYaml string Variables []*Variable + GiteePath string GerritRepoName string GerritBranchName string GerritRemoteName string @@ -396,8 +397,8 @@ func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) case LoadFromChartTemplate: return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) - case LoadFromGerrit: - return CreateOrUpdateHelmServiceFromGerrit(projectName, args, logger) + case LoadFromGerrit, setting.SourceFromGitee: + return CreateOrUpdateHelmServiceFromRepo(projectName, args, logger) case LoadFromChartRepo: return CreateOrUpdateHelmServiceFromChartRepo(projectName, args, logger) default: @@ -656,7 +657,7 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *system return ch.Type, ch, nil } -func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { var ( filePaths []string response = &BulkHelmServiceCreationResponse{} @@ -742,29 +743,46 @@ func CreateOrUpdateHelmServiceFromGerrit(projectName string, args *HelmServiceCr return } + var repoLink string + if string(args.Source) == setting.SourceFromGitee { + codehostInfo, err := systemconfig.New().GetCodeHost(createFromRepo.CodehostID) + if err != nil { + finalErr = errors.Wrapf(err, "failed to get code host, id %d", createFromRepo.CodehostID) + return + } + repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, createFromRepo.Owner, createFromRepo.Repo, "tree", createFromRepo.Branch, filePath) + } + + helmServiceCreationArgs := &helmServiceCreationArgs{ + ChartName: serviceName, + ChartVersion: chartVersion, + ServiceRevision: rev, + MergedValues: string(valuesYAML), + ServiceName: serviceName, + FilePath: filePath, + ProductName: projectName, + CreateBy: args.CreatedBy, + RequestID: args.RequestID, + CodehostID: createFromRepo.CodehostID, + Owner: createFromRepo.Owner, + Repo: createFromRepo.Repo, + Branch: createFromRepo.Branch, + Source: string(args.Source), + RepoLink: repoLink, + GiteePath: currentFilePath, + } + + if string(args.Source) == setting.SourceFromGerrit { + helmServiceCreationArgs.GerritCodeHostID = createFromRepo.CodehostID + helmServiceCreationArgs.GerritPath = currentFilePath + helmServiceCreationArgs.GerritRepoName = createFromRepo.Repo + helmServiceCreationArgs.GerritBranchName = createFromRepo.Branch + helmServiceCreationArgs.GerritRemoteName = "origin" + } + svc, err := createOrUpdateHelmService( nil, - &helmServiceCreationArgs{ - ChartName: serviceName, - ChartVersion: chartVersion, - ServiceRevision: rev, - MergedValues: string(valuesYAML), - ServiceName: serviceName, - FilePath: filePath, - ProductName: projectName, - CreateBy: args.CreatedBy, - RequestID: args.RequestID, - CodehostID: createFromRepo.CodehostID, - Owner: createFromRepo.Owner, - Repo: createFromRepo.Repo, - Branch: createFromRepo.Branch, - Source: string(args.Source), - GerritCodeHostID: createFromRepo.CodehostID, - GerritPath: currentFilePath, - GerritRepoName: createFromRepo.Repo, - GerritBranchName: createFromRepo.Branch, - GerritRemoteName: "origin", - }, + helmServiceCreationArgs, log, ) if err != nil { @@ -1177,6 +1195,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { case setting.SourceFromGitlab, setting.SourceFromGithub, setting.SourceFromGerrit, + setting.SourceFromGitee, setting.SourceFromCodeHub: return &models.CreateFromRepo{ GitRepoConfig: &templatemodels.GitRepoConfig{ @@ -1269,6 +1288,9 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg case string(LoadFromGerrit): base := path.Join(config.S3StoragePath(), args.GerritRepoName) chartName, chartVersion, err = readChartYAMLFromLocal(filepath.Join(base, args.FilePath), logger) + case setting.SourceFromGitee: + base := path.Join(config.S3StoragePath(), args.Repo) + chartName, chartVersion, err = readChartYAMLFromLocal(filepath.Join(base, args.FilePath), logger) default: chartName, chartVersion, err = readChartYAML(fsTree, args.ServiceName, logger) } @@ -1328,6 +1350,8 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg serviceObj.GerritRepoName = args.GerritRepoName serviceObj.GerritBranchName = args.GerritBranchName serviceObj.GerritRemoteName = args.GerritRemoteName + case setting.SourceFromGitee: + serviceObj.GiteePath = args.GiteePath } log.Infof("Starting to create service %s with revision %d", args.ServiceName, args.ServiceRevision) diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 6605d752..8cde9f82 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -38,6 +38,7 @@ import ( "github.com/koderover/zadig/pkg/tool/codehub" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gerrit" + "github.com/koderover/zadig/pkg/tool/gitee" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) @@ -64,6 +65,8 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b ret, err = preloadGerritService(ch, repoName, branchName, remoteName, path, isDir) case setting.SourceFromCodeHub: ret, err = preloadCodehubService(ch, repoName, repoUUID, branchName, path, isDir) + case setting.SourceFromGitee: + ret, err = preloadGiteeService(ch, repoOwner, repoName, branchName, remoteName, path, isDir) default: return nil, e.ErrPreloadServiceTemplate.AddDesc("Not supported code source") } @@ -85,6 +88,8 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoNam return loadGerritService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) case setting.SourceFromCodeHub: return loadCodehubService(username, ch, repoOwner, repoName, repoUUID, branchName, args, log) + case setting.SourceFromGitee: + return loadGiteeService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) default: return e.ErrLoadServiceTemplate.AddDesc("unsupported code source") } @@ -106,6 +111,8 @@ func ValidateServiceUpdate(codehostID int, serviceName, repoOwner, repoName, rep return validateServiceUpdateGerrit(detail, serviceName, repoName, branchName, remoteName, path, isDir) case setting.SourceFromCodeHub: return validateServiceUpdateCodehub(detail, serviceName, repoName, repoUUID, branchName, path, isDir) + case setting.SourceFromGitee: + return validateServiceUpdateGitee(detail, serviceName, repoOwner, repoName, branchName, remoteName, path, isDir) default: return e.ErrValidateServiceUpdate.AddDesc("Not supported code source") } @@ -127,7 +134,7 @@ func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, r if !isDir { if !isYaml(loadPath) { - log.Errorf("trying to preload a non-yaml file") + log.Error("trying to preload a non-yaml file") return nil, e.ErrPreloadServiceTemplate.AddDesc("Non-yaml service loading is not supported") } pathSegment := strings.Split(loadPath, "/") @@ -136,10 +143,10 @@ func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, r } else { fileInfos, err := ioutil.ReadDir(filePath) if err != nil { - log.Errorf("Failed to read directory info of path: %s, the error is: %+v", filePath, err) + log.Error("Failed to read directory info of path: %s, the error is: %+v", filePath, err) return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) } - if isValidGerritServiceDir(fileInfos) { + if isValidServiceDir(fileInfos) { svcName := loadPath if loadPath == "" { svcName = repoName @@ -158,7 +165,7 @@ func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, r log.Errorf("Failed to get subdir content from gerrit with path: %s, the error is: %+v", subDirPath, err) return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) } - if isValidGerritServiceDir(subtree) { + if isValidServiceDir(subtree) { ret = append(ret, getFileName(file.Name())) isGrandParent = true } @@ -229,6 +236,68 @@ func preloadCodehubService(detail *systemconfig.CodeHost, repoName, repoUUID, br return ret, nil } +//Get a list of services that gitee can load based on repo information +func preloadGiteeService(detail *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName, loadPath string, isDir bool) ([]string, error) { + ret := make([]string, 0) + + base := path.Join(config.S3StoragePath(), repoName) + if exist, err := util.PathExists(base); !exist { + log.Warnf("path does not exist,err:%s", err) + err = command.RunGitCmds(detail, repoOwner, repoName, branchName, remoteName) + if err != nil { + return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) + } + } + + filePath := path.Join(base, loadPath) + + if !isDir { + if !isYaml(loadPath) { + log.Error("trying to preload a non-yaml file") + return nil, e.ErrPreloadServiceTemplate.AddDesc("Non-yaml service loading is not supported") + } + pathSegment := strings.Split(loadPath, "/") + fileName := pathSegment[len(pathSegment)-1] + ret = append(ret, getFileName(fileName)) + } else { + fileInfos, err := ioutil.ReadDir(filePath) + if err != nil { + log.Errorf("Failed to read directory info of path: %s, the error is: %s", filePath, err) + return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) + } + if isValidServiceDir(fileInfos) { + svcName := loadPath + if loadPath == "" { + svcName = repoName + } + pathList := strings.Split(svcName, "/") + folderName := pathList[len(pathList)-1] + ret = append(ret, folderName) + return ret, nil + } + isGrandParent := false + for _, file := range fileInfos { + if file.IsDir() { + subDirPath := fmt.Sprintf("%s/%s", filePath, file.Name()) + subtree, err := ioutil.ReadDir(subDirPath) + if err != nil { + log.Errorf("Failed to get subdir content from gitee with path: %s, the error is: %s", subDirPath, err) + return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) + } + if isValidServiceDir(subtree) { + ret = append(ret, getFileName(file.Name())) + isGrandParent = true + } + } + } + if !isGrandParent { + log.Errorf("invalid folder selected since no yaml is presented in path: %s", filePath) + return ret, e.ErrPreloadServiceTemplate.AddDesc("所选路径下没有yaml,请重新选择") + } + } + return ret, nil +} + // 根据repo信息从gerrit加载服务 func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { if remoteName == "" { @@ -302,7 +371,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re log.Errorf("Failed to read directory info of path: %s, the error is: %+v", filePath, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } - if isValidGerritServiceDir(fileInfos) { + if isValidServiceDir(fileInfos) { return loadServiceFromGerrit(fileInfos, ch.ID, username, branchName, args.LoadPath, filePath, repoOwner, remoteName, repoName, args, commitInfo, log) } for _, entry := range fileInfos { @@ -313,7 +382,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re log.Errorf("Failed to read subdir info from gerrit package of path: %s, the error is: %+v", subtreePath, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } - if isValidGerritServiceDir(subtreeInfo) { + if isValidServiceDir(subtreeInfo) { if err := loadServiceFromGerrit(subtreeInfo, ch.ID, username, branchName, subtreeLoadPath, subtreePath, repoOwner, remoteName, repoName, args, commitInfo, log); err != nil { return err } @@ -327,7 +396,7 @@ func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loa var splittedYaml []string fileName := pathList[len(pathList)-1] serviceName := getFileName(fileName) - yamlList, err := extractGerritYamls(path, tree) + yamlList, err := extractYamls(path, tree) if err != nil { log.Errorf("Failed to extract yamls from gerrit package, the error is: %+v", err) return err @@ -497,6 +566,142 @@ func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeN return err } +// Load services from gitee based on repo information +func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { + if remoteName == "" { + remoteName = "origin" + } + base := path.Join(config.S3StoragePath(), repoName) + if _, err := os.Stat(base); os.IsNotExist(err) { + err = command.RunGitCmds(ch, repoOwner, repoName, branchName, remoteName) + if err != nil { + return e.ErrLoadServiceTemplate.AddDesc(err.Error()) + } + } + + giteeCli := gitee.NewClient(ch.ID, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + branch, err := giteeCli.GetSingleBranch(ch.AccessToken, repoOwner, repoName, branchName) + if err != nil { + log.Errorf("Failed to get latest commit info from repo: %s, the error is: %s", repoName, err) + return e.ErrLoadServiceTemplate.AddDesc(err.Error()) + } + commitInfo := &models.Commit{ + SHA: branch.Commit.Sha, + Message: branch.Commit.Commit.Message, + } + + filePath := path.Join(base, args.LoadPath) + if !args.LoadFromDir { + contentBytes, err := ioutil.ReadFile(path.Join(base, args.LoadPath)) + if err != nil { + log.Errorf("Failed to read file of path: %s, the error is: %s", args.LoadPath, err) + return e.ErrLoadServiceTemplate.AddDesc(err.Error()) + } + pathSegments := strings.Split(args.LoadPath, "/") + fileName := pathSegments[len(pathSegments)-1] + svcName := getFileName(fileName) + splittedYaml := SplitYaml(string(contentBytes)) + srcPath := fmt.Sprintf("%s/%s/%s/blob/%s/%s", ch.Address, repoOwner, repoName, branchName, args.LoadPath) + createSvcArgs := &models.Service{ + CodehostID: ch.ID, + RepoName: repoName, + RepoOwner: repoOwner, + BranchName: branchName, + SrcPath: srcPath, + LoadPath: args.LoadPath, + LoadFromDir: args.LoadFromDir, + KubeYamls: splittedYaml, + CreateBy: username, + ServiceName: svcName, + Type: args.Type, + ProductName: args.ProductName, + Source: setting.SourceFromGitee, + Yaml: string(contentBytes), + Commit: commitInfo, + Visibility: args.Visibility, + } + _, err = CreateServiceTemplate(username, createSvcArgs, log) + if err != nil { + _, messageMap := e.ErrorMessage(err) + if description, ok := messageMap["description"]; ok { + return e.ErrLoadServiceTemplate.AddDesc(description.(string)) + } + return e.ErrLoadServiceTemplate.AddDesc("Load Service Error for unknown reason") + } + return nil + } + fileInfos, err := ioutil.ReadDir(filePath) + if err != nil { + log.Errorf("Failed to read directory info of path: %s, the error is: %s", filePath, err) + return e.ErrLoadServiceTemplate.AddDesc(err.Error()) + } + if isValidServiceDir(fileInfos) { + return loadServiceFromGitee(fileInfos, ch, username, branchName, args.LoadPath, filePath, repoOwner, remoteName, repoName, args, commitInfo, log) + } + for _, entry := range fileInfos { + subtreeLoadPath := fmt.Sprintf("%s/%s", args.LoadPath, entry.Name()) + subtreePath := fmt.Sprintf("%s/%s", filePath, entry.Name()) + subtreeInfo, err := ioutil.ReadDir(subtreePath) + if err != nil { + log.Errorf("Failed to read subdir info from gitee package of path: %s, the error is: %s", subtreePath, err) + return e.ErrLoadServiceTemplate.AddDesc(err.Error()) + } + if isValidServiceDir(subtreeInfo) { + if err := loadServiceFromGitee(subtreeInfo, ch, username, branchName, subtreeLoadPath, subtreePath, repoOwner, remoteName, repoName, args, commitInfo, log); err != nil { + return err + } + } + } + return nil +} + +func loadServiceFromGitee(tree []os.FileInfo, ch *systemconfig.CodeHost, username, branchName, loadPath, path, repoOwner, remoteName, repoName string, args *LoadServiceReq, commit *models.Commit, log *zap.SugaredLogger) error { + pathList := strings.Split(path, "/") + var splittedYaml []string + fileName := pathList[len(pathList)-1] + serviceName := getFileName(fileName) + repoInfo := fmt.Sprintf("%s/%s", repoOwner, repoName) + yamlList, err := extractYamls(path, tree) + if err != nil { + log.Errorf("Failed to extract yamls from gitee package, the error is: %+v", err) + return err + } + for _, yamlEntry := range yamlList { + splittedYaml = append(splittedYaml, SplitYaml(yamlEntry)...) + } + yml := joinYamls(yamlList) + srcPath := fmt.Sprintf("%s/%s/tree/%s/%s", ch.Address, repoInfo, branchName, loadPath) + createSvcArgs := &models.Service{ + CodehostID: ch.ID, + BranchName: branchName, + RepoName: repoName, + RepoOwner: repoOwner, + LoadPath: loadPath, + SrcPath: srcPath, + LoadFromDir: args.LoadFromDir, + KubeYamls: splittedYaml, + CreateBy: username, + ServiceName: serviceName, + Type: args.Type, + ProductName: args.ProductName, + Source: setting.SourceFromGitee, + Yaml: yml, + Commit: commit, + Visibility: args.Visibility, + } + + _, err = CreateServiceTemplate(username, createSvcArgs, log) + if err != nil { + _, messageMap := e.ErrorMessage(err) + if description, ok := messageMap["description"]; ok { + err = e.ErrLoadServiceTemplate.AddDesc(description.(string)) + } else { + err = e.ErrLoadServiceTemplate.AddDesc("Load Service Error for unknown reason") + } + } + return err +} + // 根据github repo决定服务是否可以更新这个repo地址 func validateServiceUpdateGithub(detail *systemconfig.CodeHost, serviceName, repoOwner, repoName, branchName, path string, isDir bool) error { ctx := context.Background() @@ -609,7 +814,7 @@ func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, rep filePath := path.Join(base, loadPath) if !isDir { if !isYaml(loadPath) { - log.Errorf("trying to preload a non-yaml file") + log.Error("trying to preload a non-yaml file") return e.ErrPreloadServiceTemplate.AddDesc("Non-yaml service loading is not supported") } pathSegment := strings.Split(loadPath, "/") @@ -625,7 +830,7 @@ func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, rep log.Errorf("Failed to read directory info of path: %s, the error is: %+v", filePath, err) return e.ErrValidateServiceUpdate.AddDesc(err.Error()) } - if isValidGerritServiceDir(fileInfos) { + if isValidServiceDir(fileInfos) { svcName := loadPath if loadPath == "" { svcName = repoName @@ -681,6 +886,52 @@ func validateServiceUpdateCodehub(detail *systemconfig.CodeHost, serviceName, re return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") } +// Determine whether the service can update the repo address according to the gitee repo +func validateServiceUpdateGitee(detail *systemconfig.CodeHost, serviceName, repoOwner, repoName, branchName, remoteName, loadPath string, isDir bool) error { + base := path.Join(config.S3StoragePath(), repoName) + if exist, err := util.PathExists(base); !exist { + log.Warnf("path does not exist,err:%s", err) + err := command.RunGitCmds(detail, repoOwner, repoName, branchName, remoteName) + if err != nil { + return e.ErrValidateServiceUpdate.AddDesc(err.Error()) + } + } + + filePath := path.Join(base, loadPath) + if !isDir { + if !isYaml(loadPath) { + log.Error("trying to preload a non-yaml file") + return e.ErrPreloadServiceTemplate.AddDesc("Non-yaml service loading is not supported") + } + pathSegment := strings.Split(loadPath, "/") + fileName := pathSegment[len(pathSegment)-1] + if getFileName(fileName) != serviceName { + log.Errorf("The loaded file name [%s] is the same as the service to be updated: [%s]", fileName, serviceName) + return e.ErrValidateServiceUpdate.AddDesc("文件名称和服务名称不一致") + } + return nil + } + fileInfos, err := ioutil.ReadDir(filePath) + if err != nil { + log.Errorf("Failed to read directory info of path: %s, the error is: %+v", filePath, err) + return e.ErrValidateServiceUpdate.AddDesc(err.Error()) + } + if isValidServiceDir(fileInfos) { + svcName := loadPath + if loadPath == "" { + svcName = repoName + } + pathList := strings.Split(svcName, "/") + folderName := pathList[len(pathList)-1] + if folderName != serviceName { + log.Errorf("The loaded file name [%s] is the same as the service to be updated: [%s]", folderName, serviceName) + return e.ErrValidateServiceUpdate.AddDesc("文件夹名称和服务名称不一致") + } + return nil + } + return e.ErrValidateServiceUpdate.AddDesc("所选路径中没有yaml,请重新选择") +} + func isValidGithubServiceDir(child []*github.RepositoryContent) bool { for _, entry := range child { if entry.GetType() == "file" && isYaml(entry.GetName()) { @@ -699,7 +950,7 @@ func isValidGitlabServiceDir(child []*gitlab.TreeNode) bool { return false } -func isValidGerritServiceDir(child []os.FileInfo) bool { +func isValidServiceDir(child []os.FileInfo) bool { for _, file := range child { if !file.IsDir() && isYaml(file.Name()) { return true @@ -737,7 +988,7 @@ func extractCodehubYamls(client *codehub.CodeHubClient, tree []*codehub.TreeNode return ret, nil } -func extractGerritYamls(basePath string, tree []os.FileInfo) ([]string, error) { +func extractYamls(basePath string, tree []os.FileInfo) ([]string, error) { var ret []string for _, entry := range tree { if !entry.IsDir() && isYaml(entry.Name()) { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index d7635837..3db878e1 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -289,7 +289,7 @@ func GetServiceOption(args *commonmodels.Service, log *zap.SugaredLogger) (*Serv } if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || - args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub { + args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromGitee { serviceOption.Yaml = args.Yaml } return serviceOption, nil @@ -713,7 +713,7 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap } } // 配置来源为Gitlab,对比配置的ChangeLog是否变化 - if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub { + if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromGitee { if args.Commit != nil && serviceTmpl.Commit != nil && args.Commit.SHA == serviceTmpl.Commit.SHA { log.Infof("%s change log remains the same, quit creation", args.Source) return GetServiceOption(serviceTmpl, log) @@ -1371,7 +1371,7 @@ func ensureServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sug args.Containers = make([]*commonmodels.Container, 0) } // Only the gerrit/spock/external type needs to be processed by yaml - if args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromZadig || args.Source == setting.SourceFromExternal || args.Source == setting.ServiceSourceTemplate { + if args.Source == setting.SourceFromGerrit || args.Source == setting.SourceFromZadig || args.Source == setting.SourceFromExternal || args.Source == setting.ServiceSourceTemplate || args.Source == setting.SourceFromGitee { // 拆分 all-in-one yaml文件 // 替换分隔符 args.Yaml = util.ReplaceWrapLine(args.Yaml) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index ca1bcec9..109c8838 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -213,8 +213,8 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. loadErr error ) switch ch.Type { - case setting.SourceFromGerrit: - sha1, loadErr = processChartFromGerrit(name, args, logger) + case setting.SourceFromGerrit, setting.SourceFromGitee: + sha1, loadErr = processChartFromGitRepo(name, args, logger) default: sha1, loadErr = processChartFromSource(name, args, logger) } @@ -265,8 +265,8 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z ) switch ch.Type { - case setting.SourceFromGerrit: - sha1, loadErr = processChartFromGerrit(name, args, logger) + case setting.SourceFromGerrit, setting.SourceFromGitee: + sha1, loadErr = processChartFromGitRepo(name, args, logger) default: sha1, loadErr = processChartFromSource(name, args, logger) } @@ -446,7 +446,7 @@ func processChartFromSource(name string, args *fs.DownloadFromSourceArgs, logger return sha1, nil } -func processChartFromGerrit(name string, args *fs.DownloadFromSourceArgs, logger *zap.SugaredLogger) (string, error) { +func processChartFromGitRepo(name string, args *fs.DownloadFromSourceArgs, logger *zap.SugaredLogger) (string, error) { var ( wg wait.Group sha1 string diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index dba3fcad..825df7af 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -285,26 +285,7 @@ func ensureServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sug if err := setCurrentContainerImages(args); err != nil { return err } - //判断该服务组件是否存在,如果存在不让保存 - //if args.Revision == 0 { - // currentServiceContainerNames := make([]string, 0) - // for _, container := range args.Containers { - // currentServiceContainerNames = append(currentServiceContainerNames, container.Name) - // } - // if serviceTmpls, err := s.coll.ServiceTmpl.ListMaxRevisions(); err == nil { - // for _, serviceTmpl := range serviceTmpls { - // switch serviceTmpl.Type { - // case template.K8SDeployType: - // for _, container := range serviceTmpl.Containers { - // target := container.Name - // if utils.Contains(target, currentServiceContainerNames) { - // return fmt.Errorf("服务组件不能重复,项目 [%s] 服务 [%s] 已存在同名的服务组件 [%s]", serviceTmpl.ProductName, serviceTmpl.ServiceName, target) - // } - // } - // } - // } - // } - //} + log.Infof("find %d containers in service %s", len(args.Containers), args.ServiceName) } @@ -321,7 +302,7 @@ func ensureServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sug } func reloadServiceTmplFromGerrit(svc *commonmodels.Service, log *zap.SugaredLogger) error { - _, err := service.CreateOrUpdateHelmServiceFromGerrit(svc.ProductName, &service.HelmServiceCreationArgs{ + _, err := service.CreateOrUpdateHelmServiceFromRepo(svc.ProductName, &service.HelmServiceCreationArgs{ HelmLoadSource: service.HelmLoadSource{ Source: service.LoadFromGerrit, }, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index f9e77db7..c2b49ddc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -19,18 +19,31 @@ package webhook import ( "errors" "fmt" + "io/ioutil" "net/http" + "os" + "path" + "path/filepath" + "strings" "sync" "time" "github.com/hashicorp/go-multierror" + "github.com/otiai10/copy" "go.uber.org/zap" "github.com/koderover/zadig/pkg/config" + microserviceConfig "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/command" gitservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/git" + environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/gitee" ) @@ -65,6 +78,10 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * } commonrepo.NewWebHookUserColl().Upsert(webhookUser) } + + if err := updateServiceTemplateByGiteeEvent(req.RequestURI, log); err != nil { + errorList = multierror.Append(errorList, err) + } // build webhook wg.Add(1) go func() { @@ -130,3 +147,199 @@ func ProcessGiteeHook(payload []byte, req *http.Request, requestID string, log * wg.Wait() return errorList.ErrorOrNil() } + +func updateServiceTemplateByGiteeEvent(uri string, log *zap.SugaredLogger) error { + serviceTmpls, err := GetGiteeServiceTemplates() + if err != nil { + log.Errorf("failed to get gitee service templates, err: %s", err) + return err + } + errs := &multierror.Error{} + for _, serviceTmpl := range serviceTmpls { + service, err := commonservice.GetServiceTemplate(serviceTmpl.ServiceName, serviceTmpl.Type, serviceTmpl.ProductName, setting.ProductStatusDeleting, serviceTmpl.Revision, log) + if err != nil { + log.Errorf("failed to get gitee service templates, err:%s", err) + errs = multierror.Append(errs, err) + } + detail, err := systemconfig.New().GetCodeHost(service.CodehostID) + if err != nil { + log.Errorf("failed to get codehost detail err:%s", err) + errs = multierror.Append(errs, err) + } + newRepoName := fmt.Sprintf("%s-new", service.RepoName) + err = command.RunGitCmds(detail, service.RepoOwner, newRepoName, service.BranchName, "origin") + if err != nil { + log.Errorf("failed to run git cmds err:%s", err) + errs = multierror.Append(errs, err) + } + + newBase, err := GetWorkspaceBasePath(newRepoName) + if err != nil { + errs = multierror.Append(errs, err) + } + + oldBase, err := GetWorkspaceBasePath(service.RepoName) + if err != nil { + errs = multierror.Append(errs, err) + err = command.RunGitCmds(detail, service.RepoOwner, service.RepoName, service.BranchName, "origin") + if err != nil { + errs = multierror.Append(errs, err) + } + } + + filePath, err := os.Stat(path.Join(newBase, service.LoadPath)) + if err != nil { + errs = multierror.Append(errs, err) + } + + var newYamlContent string + var oldYamlContent string + if filePath.IsDir() { + if newFileContents, err := readAllFileContentUnderDir(path.Join(newBase, service.LoadPath)); err == nil { + newYamlContent = strings.Join(newFileContents, setting.YamlFileSeperator) + } else { + errs = multierror.Append(errs, err) + } + + if oldFileContents, err := readAllFileContentUnderDir(path.Join(oldBase, service.LoadPath)); err == nil { + oldYamlContent = strings.Join(oldFileContents, setting.YamlFileSeperator) + } else { + errs = multierror.Append(errs, err) + } + } else { + if contentBytes, err := ioutil.ReadFile(path.Join(newBase, service.LoadPath)); err == nil { + newYamlContent = string(contentBytes) + } else { + errs = multierror.Append(errs, err) + } + + if contentBytes, err := ioutil.ReadFile(path.Join(oldBase, service.LoadPath)); err == nil { + oldYamlContent = string(contentBytes) + } else { + errs = multierror.Append(errs, err) + } + } + + if strings.Compare(newYamlContent, oldYamlContent) != 0 { + log.Infof("Started to sync service template %s from gitee %s", service.ServiceName, service.LoadPath) + service.CreateBy = "system" + service.Yaml = newYamlContent + if err := SyncServiceTemplateFromGitee(service, log); err != nil { + errs = multierror.Append(errs, err) + } + } else { + log.Infof("Service template %s from gitee %s is not affected, no sync", service.ServiceName, service.LoadPath) + } + } + + return errs.ErrorOrNil() +} + +// GetGiteeServiceTemplates Get all service templates maintained in gitee +func GetGiteeServiceTemplates() ([]*commonmodels.Service, error) { + opt := &commonrepo.ServiceListOption{ + Source: setting.SourceFromGitee, + } + return commonrepo.NewServiceColl().ListMaxRevisions(opt) +} + +func GetWorkspaceBasePath(repoName string) (string, error) { + if strings.Contains(repoName, "/") { + repoName = strings.Replace(repoName, "/", "-", -1) + } + base := path.Join(microserviceConfig.S3StoragePath(), repoName) + + if _, err := os.Stat(base); os.IsNotExist(err) { + return base, err + } + + return base, nil +} + +func SyncServiceTemplateFromGitee(service *commonmodels.Service, log *zap.SugaredLogger) error { + if service.Source != setting.SourceFromGitee { + return fmt.Errorf("SyncServiceTemplateFromGitee service template is not from gitee,source:%s", service.Source) + } + + if _, err := syncGiteeLatestCommit(service); err != nil { + log.Errorf("SyncServiceTemplateFromGitee sync change log from gitee failed service %s, error: %s", service.ServiceName, err) + return err + } + + if service.Type == setting.K8SDeployType { + if err := ensureServiceTmpl(setting.WebhookTaskCreator, service, log); err != nil { + log.Errorf("SyncServiceTemplateFromGitee ensureServiceTmpl error: %s", err) + return e.ErrValidateTemplate.AddDesc(err.Error()) + } + + if err := commonrepo.NewServiceColl().Create(service); err != nil { + log.Errorf("SyncServiceTemplateFromGitee Failed to sync service %s from gitee path %s error: %s", service.ServiceName, service.SrcPath, err) + return e.ErrCreateTemplate.AddDesc(err.Error()) + } + + return environmentservice.AutoDeployYamlServiceToEnvs(service.CreateBy, "", service, log) + } + // remove old repo dir + oldRepoDir := filepath.Join(microserviceConfig.S3StoragePath(), service.RepoName) + os.RemoveAll(oldRepoDir) + // copy new repo data to old dir data + if err := os.MkdirAll(oldRepoDir, 0775); err != nil { + return err + } + + newRepoDir := filepath.Join(microserviceConfig.S3StoragePath(), service.RepoName+"-new") + if copyErr := copy.Copy(newRepoDir, oldRepoDir); copyErr != nil { + return copyErr + } + + if err := reloadServiceTmplFromGitee(service, log); err != nil { + return err + } + log.Infof("End of sync service template %s from gitee path %s", service.ServiceName, service.SrcPath) + return nil +} + +func syncGiteeLatestCommit(service *commonmodels.Service) (*systemconfig.CodeHost, error) { + if service.CodehostID == 0 { + return nil, fmt.Errorf("codehostId cannot be empty") + } + if service.RepoName == "" { + return nil, fmt.Errorf("repoName cannot be empty") + } + if service.BranchName == "" { + return nil, fmt.Errorf("branchName cannot be empty") + } + detail, err := systemconfig.New().GetCodeHost(service.CodehostID) + if err != nil { + return nil, err + } + + giteeCli := gitee.NewClient(detail.ID, detail.AccessToken, microserviceConfig.ProxyHTTPSAddr(), detail.EnableProxy) + commit, err := giteeCli.GetSingleBranch(detail.AccessToken, service.RepoOwner, service.RepoName, service.BranchName) + if err != nil { + return detail, err + } + + service.Commit = &commonmodels.Commit{ + SHA: commit.Commit.Sha, + Message: commit.Commit.Commit.Message, + } + return detail, nil +} + +func reloadServiceTmplFromGitee(svc *commonmodels.Service, log *zap.SugaredLogger) error { + _, err := service.CreateOrUpdateHelmServiceFromRepo(svc.ProductName, &service.HelmServiceCreationArgs{ + HelmLoadSource: service.HelmLoadSource{ + Source: setting.SourceFromGitee, + }, + CreatedBy: svc.CreateBy, + CreateFrom: &service.CreateFromRepo{ + CodehostID: svc.CodehostID, + Owner: svc.RepoOwner, + Repo: svc.RepoName, + Branch: svc.BranchName, + Paths: []string{svc.LoadPath}, + }, + }, log) + return err +} diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index ebd8cb23..d59cd937 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -150,7 +150,7 @@ func (c *Client) UpdateHook(ctx context.Context, owner, repo string, id int64, h func (c *Client) GetContents(ctx context.Context, owner, repo, sha string) (gitee.Blob, error) { fileContent, _, err := c.GitDataApi.GetV5ReposOwnerRepoGitBlobsSha(ctx, owner, repo, sha, &gitee.GetV5ReposOwnerRepoGitBlobsShaOpts{}) if err != nil { - return gitee.Blob{}, err + return fileContent, err } return fileContent, nil } diff --git a/pkg/util/file.go b/pkg/util/file.go index 30144213..51b583d9 100644 --- a/pkg/util/file.go +++ b/pkg/util/file.go @@ -60,3 +60,14 @@ func ReadFile(filename string) ([]byte, error) { } return contentByte, nil } + +func PathExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} -- Gitee From 87109ed22a5481eceeffca6310d2a7b851c590cf Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Fri, 20 May 2022 15:00:11 +0800 Subject: [PATCH 2361/2647] Hosts support HTTP health check (#1514) * Hosts support HTTP health check Signed-off-by: ddh27 * Hosts support HTTP health check Signed-off-by: ddh27 * Hosts support HTTP health check Signed-off-by: ddh27 --- .../common/repository/models/private_key.go | 2 ++ .../common/repository/mongodb/private_key.go | 1 + .../aslan/core/system/service/private_key.go | 5 +++ .../core/service/scheduler/schedule_env.go | 22 ++++++++++-- .../core/service/scheduler/schedule_pm.go | 25 +++++++++++-- pkg/microservice/cron/core/service/service.go | 2 ++ pkg/types/private_key.go | 36 +++++++++++++++++++ 7 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 pkg/types/private_key.go diff --git a/pkg/microservice/aslan/core/common/repository/models/private_key.go b/pkg/microservice/aslan/core/common/repository/models/private_key.go index 54925662..2e0723b7 100644 --- a/pkg/microservice/aslan/core/common/repository/models/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/models/private_key.go @@ -18,6 +18,7 @@ package models import ( "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -35,6 +36,7 @@ type PrivateKey struct { UpdateTime int64 `bson:"update_time" json:"update_time"` UpdateBy string `bson:"update_by" json:"update_by"` Provider int8 `bson:"provider" json:"provider"` + Probe *types.Probe `bson:"probe" json:"probe"` UpdateStatus bool `bson:"-" json:"update_status"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index da3ef2e5..135ccce7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -146,6 +146,7 @@ func (c *PrivateKeyColl) Update(id string, args *models.PrivateKey) error { "is_prod": args.IsProd, "private_key": args.PrivateKey, "provider": args.Provider, + "probe": args.Probe, "update_by": args.UpdateBy, "update_time": time.Now().Unix(), }} diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index b85f500e..b7e79124 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -25,8 +25,10 @@ import ( commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/pm" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/types" ) func ListPrivateKeys(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { @@ -40,6 +42,9 @@ func ListPrivateKeys(encryptedKey string, log *zap.SugaredLogger) ([]*commonmode return nil, err } for _, key := range resp { + if key.Probe == nil { + key.Probe = &types.Probe{ProbeScheme: setting.ProtocolTCP} + } key.PrivateKey, err = crypto.AesEncryptByKey(key.PrivateKey, aesKey.PlainText) if err != nil { return nil, err diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env.go b/pkg/microservice/cron/core/service/scheduler/schedule_env.go index 56e3c6c6..b25e82cc 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env.go @@ -35,6 +35,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) // UpsertEnvServiceScheduler ... @@ -205,7 +206,7 @@ func runProbe(healthCheck *service.PmHealthCheck, address string, log *zap.Sugar timeout := time.Duration(healthCheck.TimeOut) * time.Second switch healthCheck.Protocol { case setting.ProtocolHTTP, setting.ProtocolHTTPS: - if message, err = doHTTPProbe(healthCheck.Protocol, address, healthCheck.Path, healthCheck.Port, timeout, log); err != nil { + if message, err = doHTTPProbe(healthCheck.Protocol, address, healthCheck.Path, healthCheck.Port, []*types.HTTPHeader{}, timeout, "", log); err != nil { log.Errorf("doHttpProbe err:%v", err) return Failure, err } @@ -240,7 +241,7 @@ func doTCPProbe(addr string, port int, timeout time.Duration, log *zap.SugaredLo return Success, nil } -func doHTTPProbe(protocol, address, path string, port int, timeout time.Duration, log *zap.SugaredLogger) (string, error) { +func doHTTPProbe(protocol, address, path string, port int, headerList []*types.HTTPHeader, timeout time.Duration, responseSuccessFlag string, log *zap.SugaredLogger) (string, error) { tlsConfig := &tls.Config{InsecureSkipVerify: true} transport := &http.Transport{ TLSClientConfig: tlsConfig, @@ -256,10 +257,14 @@ func doHTTPProbe(protocol, address, path string, port int, timeout time.Duration if err != nil { return Failure, err } + headers := buildHeader(headerList) req, err := http.NewRequest("GET", url, nil) if err != nil { return Failure, err } + req.Header = headers + req.Host = headers.Get("Host") + res, err := client.Do(req) if err != nil { return Failure, err @@ -271,10 +276,13 @@ func doHTTPProbe(protocol, address, path string, port int, timeout time.Duration } if res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusBadRequest { + if responseSuccessFlag != "" && !strings.Contains(string(body), responseSuccessFlag) { + return Failure, fmt.Errorf("HTTP probe failed with response success flag: %s", responseSuccessFlag) + } log.Infof("Probe succeeded for %s, Response: %v", url, *res) return Success, nil } - log.Infof("Probe failed for %s, response body: %v", url, string(body)) + log.Warnf("Probe failed for %s, response body: %v", url, string(body)) return Failure, fmt.Errorf("HTTP probe failed with statuscode: %d", res.StatusCode) } @@ -311,6 +319,14 @@ func formatURL(protocol, address, path string, port int) (string, error) { return fmt.Sprintf("%s://%s:%d/%s", protocol, address, port, path), nil } +func buildHeader(headerList []*types.HTTPHeader) http.Header { + headers := make(http.Header) + for _, header := range headerList { + headers[header.Name] = append(headers[header.Name], header.Value) + } + return headers +} + func buildEnvNameKey(productRevision *service.ProductRevision) string { return "helm-values-sync-" + productRevision.ProductName + "-" + productRevision.EnvName } diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_pm.go b/pkg/microservice/cron/core/service/scheduler/schedule_pm.go index 2ec5a5ec..3c090a80 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_pm.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_pm.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" "go.uber.org/zap" ) @@ -38,10 +39,28 @@ func (c *CronClient) UpdatePmHostStatusScheduler(log *zap.SugaredLogger) { hostPm.Port = setting.PMHostDefaultPort } newStatus := setting.PMHostStatusAbnormal - msg, err := doTCPProbe(hostPm.IP, int(hostPm.Port), 3*time.Second, log) - if err != nil { - log.Errorf("doTCPProbe TCP %s:%d err: %s)", hostPm.IP, hostPm.Port, err) + if hostPm.Probe == nil { + hostPm.Probe = &types.Probe{ProbeScheme: setting.ProtocolTCP} + } + var err error + msg := "" + + switch hostPm.Probe.ProbeScheme { + case setting.ProtocolHTTP, setting.ProtocolHTTPS: + if hostPm.Probe.HttpProbe == nil { + break + } + msg, err = doHTTPProbe(string(hostPm.Probe.ProbeScheme), hostPm.IP, hostPm.Probe.HttpProbe.Path, hostPm.Probe.HttpProbe.Port, hostPm.Probe.HttpProbe.HTTPHeaders, time.Duration(hostPm.Probe.HttpProbe.TimeOutSecond)*time.Second, hostPm.Probe.HttpProbe.ResponseSuccessFlag, log) + if err != nil { + log.Warnf("doHttpProbe err:%s", err) + } + case setting.ProtocolTCP: + msg, err = doTCPProbe(hostPm.IP, int(hostPm.Port), 3*time.Second, log) + if err != nil { + log.Warnf("doTCPProbe TCP %s:%d err: %s)", hostPm.IP, hostPm.Port, err) + } } + if msg == Success { newStatus = setting.PMHostStatusNormal } diff --git a/pkg/microservice/cron/core/service/service.go b/pkg/microservice/cron/core/service/service.go index 8fbb0d5f..5c0a023d 100644 --- a/pkg/microservice/cron/core/service/service.go +++ b/pkg/microservice/cron/core/service/service.go @@ -21,6 +21,7 @@ import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" ) type SvcRevision struct { @@ -120,6 +121,7 @@ type PrivateKeyHosts struct { IP string `json:"ip"` Port int64 `json:"port"` Status setting.PMHostStatus `json:"status"` + Probe *types.Probe `json:"probe"` UpdateStatus bool `json:"update_status"` } diff --git a/pkg/types/private_key.go b/pkg/types/private_key.go new file mode 100644 index 00000000..54e07563 --- /dev/null +++ b/pkg/types/private_key.go @@ -0,0 +1,36 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type Probe struct { + ProbeScheme string `bson:"probe_type" json:"probe_type"` + HttpProbe *HTTPGetAction `bson:"http_probe" json:"http_probe"` +} + +type HTTPHeader struct { + Name string `bson:"name" json:"name"` + Value string `bson:"value" json:"value"` +} + +type HTTPGetAction struct { + Path string `bson:"path" json:"path"` + Port int `bson:"port" json:"port"` + Host string `bson:"-" json:"host,omitempty"` + HTTPHeaders []*HTTPHeader `bson:"http_headers" json:"http_headers"` + TimeOutSecond int `bson:"timeout_second" json:"timeout_second"` + ResponseSuccessFlag string `bson:"response_success_flag" json:"response_success_flag"` +} -- Gitee From b72b815ea30c4314b50b87bf6f7bd95861f93d63 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 23 May 2022 09:35:28 +0800 Subject: [PATCH 2362/2647] remove reaper plugin (#1504) * update reaper plugin logic Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * optiomize code Signed-off-by: allenshen * remove reaper plugin dockerfile Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- Makefile | 38 ++----------------- .../service/reaper-plugin-bionic.Dockerfile | 5 --- docker/service/reaper-plugin-focal.Dockerfile | 5 --- .../service/reaper-plugin-xenial.Dockerfile | 5 --- .../service/taskplugin/artifact_deploy.go | 5 +-- .../core/service/taskplugin/build.go | 5 +-- .../core/service/taskplugin/build_v3.go | 6 +-- .../core/service/taskplugin/testing.go | 5 +-- .../core/service/taskplugin/utils.go | 15 ++++++++ 9 files changed, 23 insertions(+), 66 deletions(-) delete mode 100644 docker/service/reaper-plugin-bionic.Dockerfile delete mode 100644 docker/service/reaper-plugin-focal.Dockerfile delete mode 100644 docker/service/reaper-plugin-xenial.Dockerfile diff --git a/Makefile b/Makefile index 952a04f0..4d6926da 100644 --- a/Makefile +++ b/Makefile @@ -9,45 +9,15 @@ REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) ALL_PUSH=$(TARGETS:=.push) -ALL_REAPER=$(REAPER_OS:=.reaper.image) -ALL_REAPER_PUSH=$(REAPER_OS:=.reaper.push) -all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) $(ALL_REAPER:=.amd64) $(ALL_REAPER:=.arm64) resource-server.build.amd64 resource-server.build.arm64 -all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 +all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 +all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 -all.amd64: $(ALL_IMAGES:=.amd64) $(ALL_REAPER:=.amd64) resource-server.build.amd64 -all.arm64: $(ALL_IMAGES:=.amd64) $(ALL_REAPER:=.arm64) resource-server.build.arm64 +all.amd64: $(ALL_IMAGES:=.amd64) resource-server.build.amd64 +all.arm64: $(ALL_IMAGES:=.amd64) resource-server.build.arm64 allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) resource-server.upload.amd64 allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.upload.arm64 -%.reaper.push.arm64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-arm64-$* -%.reaper.push.arm64: %.reaper.image.arm64 - @docker push ${MAKE_IMAGE} - -%.reaper.image.arm64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-arm64-$* -%.reaper.image.arm64: - @mkdir -p docker/dist/arm64 - @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -o docker/dist/reaper cmd/reaper/main.go - @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#ubuntu-bionic.Dockerfile/ {' -e 'r docker/base/arm64/ubuntu-bionic.Dockerfile' -e 'd' -e '}' docker/dist/arm64/reaper-plugin-$*.Dockerfile - @docker build -f docker/dist/arm64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . - -%.reaper.push.amd64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-amd64-$* -%.reaper.push.amd64: %.reaper.image.amd64 - @docker push ${MAKE_IMAGE} - -%.reaper.image.amd64: MAKE_IMAGE = ${IMAGE_REPOSITORY}/reaper-plugin:${VERSION}-amd64-$* -%.reaper.image.amd64: - @mkdir -p docker/dist/amd64 - @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go - @cp docker/service/reaper-plugin-$*.Dockerfile docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#ubuntu-xenial.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-xenial.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#ubuntu-focal.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-focal.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @sed -i -e '/#ubuntu-bionic.Dockerfile/ {' -e 'r docker/base/amd64/ubuntu-bionic.Dockerfile' -e 'd' -e '}' docker/dist/amd64/reaper-plugin-$*.Dockerfile - @docker build -f docker/dist/amd64/reaper-plugin-$*.Dockerfile --tag ${MAKE_IMAGE} . - %.push.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 %.push.amd64: %.image.amd64 @docker push ${MAKE_IMAGE} diff --git a/docker/service/reaper-plugin-bionic.Dockerfile b/docker/service/reaper-plugin-bionic.Dockerfile deleted file mode 100644 index 1a80de58..00000000 --- a/docker/service/reaper-plugin-bionic.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -#ubuntu-bionic.Dockerfile - -COPY docker/dist/reaper /usr/local/bin - -ENTRYPOINT ["reaper"] diff --git a/docker/service/reaper-plugin-focal.Dockerfile b/docker/service/reaper-plugin-focal.Dockerfile deleted file mode 100644 index c94f758d..00000000 --- a/docker/service/reaper-plugin-focal.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -#ubuntu-focal.Dockerfile - -COPY docker/dist/reaper /usr/local/bin - -ENTRYPOINT ["reaper"] diff --git a/docker/service/reaper-plugin-xenial.Dockerfile b/docker/service/reaper-plugin-xenial.Dockerfile deleted file mode 100644 index 9257f0b8..00000000 --- a/docker/service/reaper-plugin-xenial.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -#ubuntu-xenial.Dockerfile - -COPY docker/dist/reaper /usr/local/bin - -ENTRYPOINT ["reaper"] diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go index 15f20476..581085f0 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/artifact_deploy.go @@ -228,10 +228,7 @@ func (p *ArtifactDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.T } p.Log.Infof("succeed to create cm for build job %s", p.JobName) - jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) - if p.Task.ImageFrom == setting.ImageFromCustom { - jobImage = p.Task.BuildOS - } + jobImage := getReaperImage(pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS, p.Task.ImageFrom) //Resource request default value is LOW job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 430821c1..9f3ccf46 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -348,10 +348,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe if !jobExist { p.Log.Infof("Job %s:%d does not exist. Create.", pipelineTask.PipelineName, pipelineTask.TaskID) - jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) - if p.Task.ImageFrom == setting.ImageFromCustom { - jobImage = p.Task.BuildOS - } + jobImage := getReaperImage(pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS, p.Task.ImageFrom) //Resource request default value is LOW job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, p.Task.ClusterID, pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go index 94cd049a..d3c3bb5c 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build_v3.go @@ -31,7 +31,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" - "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -171,10 +170,7 @@ func (p *BuildTaskV3Plugin) Run(ctx context.Context, pipelineTask *task.Task, pi } p.Log.Infof("succeed to create cm for build job %s", p.JobName) - jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) - if p.Task.ImageFrom == setting.ImageFromCustom { - jobImage = p.Task.BuildOS - } + jobImage := getReaperImage(pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS, p.Task.ImageFrom) //Resource request default value is LOW job, err := buildJob(p.Type(), jobImage, p.JobName, serviceName, "", pipelineTask.ConfigPayload.Build.KubeNamespace, p.Task.ResReq, p.Task.ResReqSpec, pipelineCtx, pipelineTask, p.Task.Registries) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go index 1ad24c66..b782727e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/testing.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/testing.go @@ -248,10 +248,7 @@ func (p *TestPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC return } - jobImage := fmt.Sprintf("%s-%s", pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS) - if p.Task.ImageFrom == config.ImageFromCustom { - jobImage = p.Task.BuildOS - } + jobImage := getReaperImage(pipelineTask.ConfigPayload.Release.ReaperImage, p.Task.BuildOS, p.Task.ImageFrom) // search namespace should also include desired namespace job, err := buildJobWithLinkedNs( diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index c1125478..5fb10ff8 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -34,6 +34,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/warpdrive/config" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kodo" ) @@ -283,3 +284,17 @@ func InstantiateBuildSysVariables(jobCtx *task.JobCtx) []*task.KeyVal { } return ret } + +// getReaperImage generates the image used to run reaper +// depends on the setting on page 'Build' +func getReaperImage(reaperImage, buildOS, imageFrom string) string { + // for built-in image, reaperImage and buildOs can generate a complete image + // reaperImage: ccr.ccs.tencentyun.com/koderover-public/build-base:${BuildOS}-amd64 + // buildOS: focal xenial bionic + jobImage := strings.ReplaceAll(reaperImage, "${BuildOS}", buildOS) + // for custom image, buildOS represents the exact custom image + if imageFrom == setting.ImageFromCustom { + jobImage = buildOS + } + return jobImage +} -- Gitee From c062b7a7670ef95cfc61e11bf7b3057dab1c674b Mon Sep 17 00:00:00 2001 From: Xudong Zhang Date: Mon, 23 May 2022 10:22:14 +0800 Subject: [PATCH 2363/2647] fix bug: should only get pr for github repo (#1516) Signed-off-by: Xudong Zhang --- pkg/microservice/warpdrive/core/service/taskplugin/jira.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go index d14c33c5..bb488802 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go @@ -175,7 +175,7 @@ func (p *JiraPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC } } } - } else { + } else if build.Source == setting.SourceFromGithub { httpsAddr := "" if pipelineTask.ConfigPayload.Proxy.EnableRepoProxy && pipelineTask.ConfigPayload.Proxy.Type == "http" { httpsAddr = pipelineTask.ConfigPayload.Proxy.GetProxyURL() -- Gitee From 54b6deab8faa0aa3c312cc6ff40861385a2c7ad9 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Mon, 23 May 2022 11:25:32 +0800 Subject: [PATCH 2364/2647] make permission easy to config (#1461) * remove resourceType Signed-off-by: mouuii * miss release Signed-off-by: mouuii * miss release Signed-off-by: mouuii * miss release Signed-off-by: mouuii * miss release Signed-off-by: mouuii * remove workflow idregex Signed-off-by: mouuii * move env to get Signed-off-by: mouuii * remove dead code Signed-off-by: mouuii * remove dead code Signed-off-by: mouuii * remxx Signed-off-by: mouuii * remxx Signed-off-by: mouuii * remxx Signed-off-by: mouuii * remxx Signed-off-by: mouuii * remove system Signed-off-by: mouuii * move workflow to all Signed-off-by: mouuii * move env to other Signed-off-by: mouuii * fix conflict Signed-off-by: mouuii * move to a single metas.yaml Signed-off-by: mouuii * move meta to policy service Signed-off-by: mouuii * move meta to policy service Signed-off-by: mouuii * watch configmap change Signed-off-by: mouuii * typo fix Signed-off-by: mouuii * add default namespace Signed-off-by: mouuii * fix typo Signed-off-by: mouuii * config bug Signed-off-by: mouuii * move urls to meta Signed-off-by: mouuii * fix bug Signed-off-by: mouuii * urls yaml Signed-off-by: mouuii * role Signed-off-by: mouuii * xx Signed-off-by: mouuii * fix meta yaml bug Signed-off-by: mouuii * meta yaml config Signed-off-by: mouuii * endpoints -> endpoint Signed-off-by: mouuii * preset to role Signed-off-by: mouuii * preset to role Signed-off-by: mouuii * system Signed-off-by: mouuii * change meta pkg to yamlconfig Signed-off-by: mouuii * url not correct Signed-off-by: mouuii * url not use configmap Signed-off-by: mouuii * match Signed-off-by: mouuii * add env Signed-off-by: mouuii * add empty Signed-off-by: mouuii * add empty endpoint Signed-off-by: mouuii * sona get Signed-off-by: mouuii * revert workflow Signed-off-by: mouuii * license Signed-off-by: mouuii * add some log Signed-off-by: mouuii * add some log & default return value Signed-off-by: mouuii * self deepcopy Signed-off-by: mouuii --- go.mod | 1 + go.sum | 6 +- pkg/cli/initconfig/cmd/admin.yaml | 8 - pkg/cli/initconfig/cmd/contributor.yaml | 32 - pkg/cli/initconfig/cmd/init.go | 33 - pkg/cli/initconfig/cmd/project-admin.yaml | 7 - pkg/cli/initconfig/cmd/read-only.yaml | 38 - pkg/cli/initconfig/cmd/read-project-only.yaml | 40 -- .../service/collaboration_instance.go | 29 +- .../aslan/core/delivery/handler/policy.yaml | 36 - .../core/environment/handler/policy.yaml | 375 ---------- .../core/project/handler/build_policy.yaml | 38 - .../aslan/core/project/handler/policy_meta.go | 49 -- .../core/project/handler/service_policy.yaml | 74 -- pkg/microservice/aslan/core/service.go | 33 +- .../aslan/core/workflow/handler/policy.yaml | 175 ----- .../core/workflow/handler/policy_meta.go | 51 -- .../workflow/handler/system-policies.yaml | 192 ----- .../core/workflow/testing/handler/policy.yaml | 46 -- .../workflow/testing/handler/policy_meta.go | 40 -- .../core/handler/policy_meta_registration.go | 14 - .../policy/core/handler/router.go | 5 - .../core/service/bundle/exemption_urls.go | 445 ------------ .../policy/core/service/bundle/opa_bundle.go | 47 +- .../core/service/bundle/resource_bundle.go | 3 +- .../bundle/resource_endpoint_mapping.go | 18 +- .../policy/core/service/permission.go | 9 +- .../core/service/policy_meta_registration.go | 31 +- .../policy/core/yamlconfig/meta.go | 169 +++++ .../policy/core/yamlconfig/metas.yaml | 662 ++++++++++++++++++ .../core/yamlconfig/role.go} | 33 +- .../policy/core/yamlconfig/role.yaml | 103 +++ .../core/yamlconfig/urls.go} | 73 +- .../policy/core/yamlconfig/urls.yaml | 387 ++++++++++ pkg/microservice/policy/server/server.go | 216 ++++++ pkg/setting/consts.go | 4 + pkg/shared/client/policy/client.go | 19 - pkg/shared/client/policy/policy.go | 28 +- pkg/shared/client/policy/role.go | 38 +- pkg/types/policy.go | 52 ++ pkg/util/deepcopy/deepcopy.go | 145 ++++ 41 files changed, 1874 insertions(+), 1930 deletions(-) delete mode 100644 pkg/cli/initconfig/cmd/admin.yaml delete mode 100644 pkg/cli/initconfig/cmd/contributor.yaml delete mode 100644 pkg/cli/initconfig/cmd/project-admin.yaml delete mode 100644 pkg/cli/initconfig/cmd/read-only.yaml delete mode 100644 pkg/cli/initconfig/cmd/read-project-only.yaml delete mode 100644 pkg/microservice/aslan/core/delivery/handler/policy.yaml delete mode 100644 pkg/microservice/aslan/core/environment/handler/policy.yaml delete mode 100644 pkg/microservice/aslan/core/project/handler/build_policy.yaml delete mode 100644 pkg/microservice/aslan/core/project/handler/policy_meta.go delete mode 100644 pkg/microservice/aslan/core/project/handler/service_policy.yaml delete mode 100644 pkg/microservice/aslan/core/workflow/handler/policy.yaml delete mode 100644 pkg/microservice/aslan/core/workflow/handler/policy_meta.go delete mode 100644 pkg/microservice/aslan/core/workflow/handler/system-policies.yaml delete mode 100644 pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml delete mode 100644 pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go delete mode 100644 pkg/microservice/policy/core/service/bundle/exemption_urls.go create mode 100644 pkg/microservice/policy/core/yamlconfig/meta.go create mode 100644 pkg/microservice/policy/core/yamlconfig/metas.yaml rename pkg/microservice/{aslan/core/delivery/handler/policy_meta.go => policy/core/yamlconfig/role.go} (53%) create mode 100644 pkg/microservice/policy/core/yamlconfig/role.yaml rename pkg/microservice/{aslan/core/environment/handler/policy_meta.go => policy/core/yamlconfig/urls.go} (34%) create mode 100644 pkg/microservice/policy/core/yamlconfig/urls.yaml create mode 100644 pkg/util/deepcopy/deepcopy.go diff --git a/go.mod b/go.mod index de83dfb6..da0e5aad 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/jinzhu/now v1.1.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mittwald/go-helm-client v0.8.4 github.com/moby/buildkit v0.9.1 github.com/nsqio/go-nsq v1.0.7 diff --git a/go.sum b/go.sum index 963148a8..b987b849 100644 --- a/go.sum +++ b/go.sum @@ -1154,8 +1154,9 @@ github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= @@ -1172,8 +1173,9 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mittwald/go-helm-client v0.8.4 h1:2T/MUkgUjeP+h2P0YYyuwXN8jB/l90oQ12JQ5+9h9tI= github.com/mittwald/go-helm-client v0.8.4/go.mod h1:GtL4+taCH4ADXO+aimCEQ5EtPRSGPSHcT6d/fB3coFk= github.com/moby/buildkit v0.8.1/go.mod h1:/kyU1hKy/aYCuP39GZA9MaKioovHku57N6cqlKZIaiQ= diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml deleted file mode 100644 index 03bf5c61..00000000 --- a/pkg/cli/initconfig/cmd/admin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: admin -type: system -desc: "拥有系统中任何操作的权限" -rules: - - verbs: - - "*" - resources: - - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml deleted file mode 100644 index c9cfd423..00000000 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: contributor -type: system -rules: - - verbs: - - get_workflow - - run_workflow - resources: - - Workflow - kind: resource - - verbs: - - get_environment - - config_environment - - manage_environment - - delete_environment - resources: - - Environment - kind: resource - - verbs: - - get_service - resources: - - Service - kind: resource - - verbs: - - get_build - resources: - - Build - kind: resource - - verbs: - - get_test - resources: - - Test - kind: resource diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 891db925..7b113ff0 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -23,8 +23,6 @@ import ( "time" "github.com/spf13/cobra" - "golang.org/x/sync/errgroup" - "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" @@ -96,10 +94,6 @@ func initSystemConfig() error { log.Errorf("presetSystemAdmin err:%s", err) return err } - if err := presetRole(); err != nil { - log.Errorf("presetRole err:%s", err) - return err - } if err := presetRoleBinding(uid); err != nil { log.Errorf("presetRoleBinding err:%s", err) @@ -200,33 +194,6 @@ func presetRoleBinding(uid string) error { } -func presetRole() error { - g := new(errgroup.Group) - g.Go(func() error { - systemAdminRole := &policy.Role{} - if err := yaml.Unmarshal(admin, systemAdminRole); err != nil { - log.DPanic(err) - } - return policy.NewDefault().CreateSystemRole(systemAdminRole.Name, systemAdminRole) - }) - - rolesArray := [][]byte{readOnly, readProjectOnly, contributor, projectAdmin} - - for _, v := range rolesArray { - role := &policy.Role{} - if err := yaml.Unmarshal(v, role); err != nil { - log.DPanic(err) - } - g.Go(func() error { - return policy.NewDefault().CreatePresetRole(role.Name, role) - }) - } - if err := g.Wait(); err != nil { - return err - } - return nil -} - func createLocalCluster() error { cluster, err := aslan.New(config.AslanServiceAddress()).GetLocalCluster() if err != nil { diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml deleted file mode 100644 index 39a2908d..00000000 --- a/pkg/cli/initconfig/cmd/project-admin.yaml +++ /dev/null @@ -1,7 +0,0 @@ -name: project-admin -type: system -rules: - - verbs: - - "*" - resources: - - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml deleted file mode 100644 index 0fe0f37b..00000000 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: read-only -type: system -rules: - - verbs: - - get_workflow - resources: - - Workflow - kind: resource - - verbs: - - get_environment - resources: - - Environment - kind: resource - - verbs: - - get_environment - resources: - - ProductionEnvironment - kind: resource - - verbs: - - get_service - resources: - - Service - kind: resource - - verbs: - - get_build - resources: - - Build - kind: resource - - verbs: - - get_test - resources: - - Test - kind: resource - - verbs: - - get_delivery - resources: - - Delivery - kind: resource \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml deleted file mode 100644 index 105bfd1f..00000000 --- a/pkg/cli/initconfig/cmd/read-project-only.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: read-project-only -type: system -namespace: '' -rules: - - verbs: - - get_workflow - resources: - - Workflow - kind: resource - match_attributes: - - key: placeholder - value: placeholder - - verbs: - - get_environment - resources: - - Environment - kind: resource - match_attributes: - - key: placeholder - value: placeholder - - verbs: - - get_service - resources: - - Service - kind: resource - - verbs: - - get_build - resources: - - Build - kind: resource - - verbs: - - get_test - resources: - - Test - kind: resource - - verbs: - - get_delivery - resources: - - Delivery - kind: resource \ No newline at end of file diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 03d20893..e6cbd88e 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -39,6 +39,7 @@ import ( workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/shared/client/policy" + "github.com/koderover/zadig/pkg/types" ) type GetCollaborationUpdateResp struct { @@ -377,19 +378,19 @@ func buildPolicybindingName(uid, policyName, projectName string) string { func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityType, userName, uid string, logger *zap.SugaredLogger) error { - var policies []*policy.Policy + var policies []*types.Policy var policyBindings []*policy.PolicyBinding for _, mode := range updateResp.New { - var rules []*policy.Rule + var rules []*types.Rule policyName := buildPolicyName(projectName, mode.Name, identityType, userName) for _, workflow := range mode.Workflows { - rules = append(rules, &policy.Rule{ + rules = append(rules, &types.Rule{ Verbs: workflow.Verbs, Kind: "resource", Resources: []string{string(config2.ResourceTypeWorkflow)}, - MatchAttributes: []policy.MatchAttribute{ + MatchAttributes: []types.MatchAttribute{ { Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.Name), @@ -398,11 +399,11 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp }) } for _, product := range mode.Products { - rules = append(rules, &policy.Rule{ + rules = append(rules, &types.Rule{ Verbs: product.Verbs, Kind: "resource", Resources: []string{string(config2.ResourceTypeEnvironment)}, - MatchAttributes: []policy.MatchAttribute{ + MatchAttributes: []types.MatchAttribute{ { Key: "policy", Value: buildLabelValue(projectName, mode.Name, identityType, userName, string(config2.ResourceTypeEnvironment), product.Name), @@ -410,7 +411,7 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp }, }) } - policies = append(policies, &policy.Policy{ + policies = append(policies, &types.Policy{ Name: policyName, UpdateTime: time.Now().Unix(), Description: buildPolicyDescription(mode.Name, userName), @@ -440,15 +441,15 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp return err } } - var updatePolicies []*policy.Policy + var updatePolicies []*types.Policy for _, instance := range updateResp.UpdateInstance { - var rules []*policy.Rule + var rules []*types.Rule for _, workflow := range instance.Workflows { - rules = append(rules, &policy.Rule{ + rules = append(rules, &types.Rule{ Verbs: workflow.Verbs, Kind: "resource", Resources: []string{string(config2.ResourceTypeWorkflow)}, - MatchAttributes: []policy.MatchAttribute{ + MatchAttributes: []types.MatchAttribute{ { Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeWorkflow), workflow.BaseName), @@ -457,11 +458,11 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp }) } for _, product := range instance.Products { - rules = append(rules, &policy.Rule{ + rules = append(rules, &types.Rule{ Verbs: product.Verbs, Kind: "resource", Resources: []string{string(config2.ResourceTypeEnvironment)}, - MatchAttributes: []policy.MatchAttribute{ + MatchAttributes: []types.MatchAttribute{ { Key: "policy", Value: buildLabelValue(projectName, instance.CollaborationName, identityType, userName, string(config2.ResourceTypeEnvironment), product.BaseName), @@ -469,7 +470,7 @@ func syncPolicy(updateResp *GetCollaborationUpdateResp, projectName, identityTyp }, }) } - updatePolicies = append(updatePolicies, &policy.Policy{ + updatePolicies = append(updatePolicies, &types.Policy{ Name: instance.PolicyName, Description: buildPolicyDescription(instance.CollaborationName, userName), UpdateTime: time.Now().Unix(), diff --git a/pkg/microservice/aslan/core/delivery/handler/policy.yaml b/pkg/microservice/aslan/core/delivery/handler/policy.yaml deleted file mode 100644 index 441aa414..00000000 --- a/pkg/microservice/aslan/core/delivery/handler/policy.yaml +++ /dev/null @@ -1,36 +0,0 @@ -resource: Delivery -alias: "版本管理" -description: "" -rules: - - action: get_delivery - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/delivery/releases/helm/charts" - - method: GET - endpoint: "/api/aslan/delivery/releases" - - action: delete_delivery - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/delivery/releases/?*" - - action: create_delivery - alias: "创建" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/delivery/releases/helm" - - method: GET - endpoint: "/api/aslan/environment/environments" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/helm/releases" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/helm/charts" - - method: GET - endpoint: "/api/aslan/service/helm/?*/?*/fileContent" - - method: POST - endpoint: "/api/aslan/delivery/releases/helm/global-variables" - - method: GET - endpoint: "/api/aslan/delivery/releases/helm/charts/version" diff --git a/pkg/microservice/aslan/core/environment/handler/policy.yaml b/pkg/microservice/aslan/core/environment/handler/policy.yaml deleted file mode 100644 index f5a84008..00000000 --- a/pkg/microservice/aslan/core/environment/handler/policy.yaml +++ /dev/null @@ -1,375 +0,0 @@ -resource: Environment -alias: "环境" -description: "" -rules: - - action: get_environment - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/environments/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/groups" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/groups$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/services/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/kube/workloads" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/service/workloads" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/export/service" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/configmaps" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/kube/pods/?*/events" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/kube/events" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/logs/sse/pods/?*/containers/?*" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/project/products/?*/services" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/revision/products" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/workloads" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/workloads$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/helm/releases" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/releases$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/helm/values" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/values$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/helm/charts" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/environments/?*/helm/images" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/images" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/diff/products/?*/service/?*" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/environment/configmaps/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/configmaps/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/envcfgs/?*/cfg/?*" - resourceType: "Environment" - idRegex: "/api/aslan/environment/envcfgs/([\\w\\W]+?)/cfg" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/secrets/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/secrets/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/ingresses/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/ingresses/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/environment/pvcs/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/pvcs/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - action: create_environment - alias: "创建" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/environment/environments" - resourceType: "Cluster" - matchAttributes: - - key: "production" - value: "false" - - method: POST - endpoint: "/api/aslan/service/workloads" - resourceType: "Environment" - matchAttributes: - - key: "production" - value: "false" - - method: GET - endpoint: "/api/aslan/project/products/?*/services" - - method: GET - endpoint: "api/aslan/cluster/clusters" - resourceType: "Cluster" - - action: config_environment - alias: "配置" - description: "" - rules: - # ----托管场景下配置服务需要接口------ - - method: GET - endpoint: "/api/aslan/build/targets" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/build/build" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/service/services/?*" - resourceType: "Environment" - # ---------- - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/services" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services" - matchAttributes: - - key: "production" - value: "false" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/share/enable" - resourceType: "Environment" - idRegex: "/api/aslan/environment/environments/([\\w\\W]+?)/share/enable" - matchAttributes: - - key: "production" - value: "false" - - method: DELETE - endpoint: "/api/aslan/environment/environments/?*/share/enable" - resourceType: "Environment" - idRegex: "/api/aslan/environment/environments/([\\w\\W]+?)/share/enable" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/service/pm/healthCheckUpdate" - resourceType: "Environment" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/envRecycle" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/envRecycle$" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/renderset" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/renderset$" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/helm/default-values" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/default-values$" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/helm/charts" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/helm/charts$" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/service/workloads" - matchAttributes: - - key: "production" - value: "false" - resourceType: "Environment" - - method: GET - endpoint: "/api/aslan/project/products/?*/services" - resourceType: "Environment" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/registry" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/registry$" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/envcfgs/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/envcfgs/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: POST - endpoint: "/api/aslan/environment/envcfgs/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/envcfgs/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: DELETE - endpoint: "/api/aslan/environment/envcfgs/?*/cfg/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/envcfgs/([\\w\\W]+?)/cfg" - matchAttributes: - - key: "production" - value: "false" - - action: manage_environment - alias: "管理服务实例" - description: "" - rules: - - method: PUT - endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - matchAttributes: - - key: "production" - - value: "false" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/restart" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "production" - value: "false" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/restartNew" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "production" - value: "false" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/scale" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "production" - value: "false" - - method: POST - endpoint: "/api/aslan/environment/environments/?*/services/?*/scaleNew" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/environments/?*/services/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)/services/" - - method: POST - endpoint: "/api/aslan/environment/image/deployment/?*" - resourceType: "Environment" - idRegex: "/api/aslan/environment/image/deployment/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - method: POST - resourceType: "Environment" - idRegex: "/api/aslan/environment/image/statefulset/([\\w\\W]+?)$" - endpoint: "/api/aslan/environment/image/statefulset/?*" - matchAttributes: - - key: "production" - value: "false" - - method: DELETE - endpoint: "/api/aslan/environment/kube/?*/pods/?*" - resourceType: "Environment" - idRegex: "/api/aslan/environment/kube/([\\w\\W]+?)/pods" - matchAttributes: - - key: "production" - value: "false" - - method: PUT - endpoint: "/api/aslan/environment/configmaps" - resourceType: "Environment" - - method: POST - endpoint: "/api/aslan/environment/configmaps" - resourceType: "Environment" - - method: POST - endpoint: "/api/aslan/workflow/servicetask" - resourceType: "Environment" - - action: delete_environment - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/environment/environments/?*" - resourceType: "Environment" - idRegex: "api/aslan/environment/environments/([\\w\\W]+?)$" - matchAttributes: - - key: "production" - value: "false" - - action: debug_pod - alias: "服务调试" - description: "" - rules: - - method: GET - endpoint: "/api/podexec/?*/?*/?*/?*/podExec/?*" - idRegex: "/podExec/([\\w\\W]+?)$" - resourceType: "Environment" - matchAttributes: - - key: "production" - value: "false" - - action: ssh_pm - alias: "主机登录" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/environment/environments/?*/services/?*/pmexec" - idRegex: "/api/aslan/environment/environments/([\\w\\W]+?)/services" - resourceType: "Environment" - matchAttributes: - - key: "production" - value: "false" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/project/handler/build_policy.yaml b/pkg/microservice/aslan/core/project/handler/build_policy.yaml deleted file mode 100644 index 29b07c52..00000000 --- a/pkg/microservice/aslan/core/project/handler/build_policy.yaml +++ /dev/null @@ -1,38 +0,0 @@ -resource: Build -alias: "构建" -description: "" -rules: - - action: get_build - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/build/build" - - method: GET - endpoint: "/api/aslan/build/build/?*" - - method: GET - endpoint: "/api/aslan/build/targets" - - method: GET - endpoint: "/api/aslan/cluster/clusters" - - action: create_build - alias: "新建" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/build/build" - - action: edit_build - alias: "编辑" - description: "" - rules: - - method: PUT - endpoint: "/api/aslan/build/build" - - method: POST - endpoint: "/api/aslan/build/targets" - - method: POST - endpoint: "/api/aslan/build/build/targets" - - action: delete_build - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/build/build" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/project/handler/policy_meta.go b/pkg/microservice/aslan/core/project/handler/policy_meta.go deleted file mode 100644 index 2bd1f1f3..00000000 --- a/pkg/microservice/aslan/core/project/handler/policy_meta.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - _ "embed" - - "sigs.k8s.io/yaml" - - "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/tool/log" -) - -//go:embed service_policy.yaml -var servicePolicyMeta []byte - -//go:embed build_policy.yaml -var buildPolicymeta []byte - -func (*Router) Policies() []*policy.PolicyMeta { - serviceRes := &policy.PolicyMeta{} - err := yaml.Unmarshal(servicePolicyMeta, serviceRes) - if err != nil { - // should not have happened here - log.DPanic(err) - } - buildRes := &policy.PolicyMeta{} - err = yaml.Unmarshal(buildPolicymeta, buildRes) - if err != nil { - // should not have happened here - log.DPanic(err) - } - - return []*policy.PolicyMeta{serviceRes, buildRes} -} diff --git a/pkg/microservice/aslan/core/project/handler/service_policy.yaml b/pkg/microservice/aslan/core/project/handler/service_policy.yaml deleted file mode 100644 index 9b009e63..00000000 --- a/pkg/microservice/aslan/core/project/handler/service_policy.yaml +++ /dev/null @@ -1,74 +0,0 @@ -resource: Service -alias: "服务" -description: "" -rules: - - action: get_service - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/service/services" - - method: GET - endpoint: "/api/aslan/service/services/?*/k8s" - - method: GET - endpoint: "/api/aslan/project/products/?*/services" - - method: GET - endpoint: "/api/aslan/service/services/?*" - - method: GET - endpoint: "/api/aslan/project/products/?*/searching-rules" - - method: GET - endpoint: "/api/aslan/service/helm/?*/?*/filePath" - - method: GET - endpoint: "/api/aslan/service/helm/?*/?*/fileContent" - - method: GET - endpoint: "/api/aslan/service/helm/?*/?*/serviceModule" - - method: GET - endpoint: "/api/aslan/service/services/?*/pm" - - method: GET - endpoint: "/api/aslan/template/yaml" - - method: GET - endpoint: "/api/aslan/service/helm/?*/?*/fileContent" - - action: edit_service - alias: "编辑" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/service/helm/?*" - - method: PUT - endpoint: "/api/aslan/service/services" - - method: POST - endpoint: "/api/aslan/service/services" - - method: PUT - endpoint: "/api/aslan/service/pm/?*" - - method: PUT - endpoint: "/api/aslan/project/products/?*" - - method: PATCH - endpoint: "/api/aslan/project/products/?*" - - method: PUT - endpoint: "/api/aslan/project/products/?*/searching-rules" - - method: PUT - endpoint: "/api/aslan/service/helm/services/releaseNaming" - - action: create_service - alias: "新建" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/service/pm/?*" - - method: POST - endpoint: "/api/aslan/service/services" - - method: POST - endpoint: "/api/aslan/service/loader/load/?*/?*" - - method: POST - endpoint: "api/aslan/service/template/load" - - method: POST - endpoint: "/api/aslan/service/helm/services" - - method: POST - endpoint: "/api/aslan/service/helm/services/bulk" - - action: delete_service - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/service/services/?*/?*" - - method: DELETE - endpoint: "/api/aslan/service/services/?*/pm" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 618fc25d..817c987e 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -34,22 +34,17 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" - deliveryhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/handler" - environmenthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/handler" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" - projecthandler "github.com/koderover/zadig/pkg/microservice/aslan/core/project/handler" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" - workflowhandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/handler" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - testinghandler "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/handler" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/policy" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" mongotool "github.com/koderover/zadig/pkg/tool/mongo" "github.com/koderover/zadig/pkg/tool/rsa" + "github.com/koderover/zadig/pkg/types" ) const ( @@ -57,7 +52,7 @@ const ( ) type policyGetter interface { - Policies() []*policy.PolicyMeta + Policies() []*types.PolicyMeta } type Controller interface { @@ -84,28 +79,6 @@ func StartControllers(stopCh <-chan struct{}) { wg.Wait() } -func registerPolicies() { - policyClient := policy.NewWithRetry() - var policies []*policy.PolicyMeta - for _, r := range []policyGetter{ - new(workflowhandler.Router), - new(environmenthandler.Router), - new(projecthandler.Router), - new(testinghandler.Router), - new(deliveryhandler.Router), - } { - policies = append(policies, r.Policies()...) - } - - for _, p := range policies { - err := policyClient.CreateOrUpdatePolicyRegistration(p) - if err != nil { - // should not have happened here - log.DPanic(err) - } - } -} - func initRsaKey() { client, err := kubeclient.GetKubeClient(commonconfig.HubServerServiceAddress(), setting.LocalClusterID) if err != nil { @@ -154,8 +127,6 @@ func Start(ctx context.Context) { environmentservice.ResetProductsStatus() - registerPolicies() - //Parse the workload dependencies configMap, PVC, ingress, secret go environmentservice.StartClusterInformer() diff --git a/pkg/microservice/aslan/core/workflow/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/handler/policy.yaml deleted file mode 100644 index d4774829..00000000 --- a/pkg/microservice/aslan/core/workflow/handler/policy.yaml +++ /dev/null @@ -1,175 +0,0 @@ -resource: Workflow -alias: "工作流" -description: "" -rules: - - action: get_workflow - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "api/aslan/workflow/workflow" - resourceType: "Workflow" - matchAttributes: - - key: "placeholder" - value: "placeholder" - - method: GET - endpoint: "api/aslan/workflow/workflow" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/workflow/find/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/logs/sse/workflow/build/?*/?*/?*/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/service/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/directory/workflowTask" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/v3" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/v3/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/v3/?*/args" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/servicetask/workflows/?*/?*/?*/?*" - resourceType: "Workflow" - - action: edit_workflow - alias: "编辑" - description: "" - rules: - - method: PUT - endpoint: "api/aslan/workflow/workflow/?*" - resourceType: "Workflow" - idRegex: "api/aslan/workflow/workflow/([\\w\\W]+?)$" - - method: GET - endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - matchAttributes: - - key: "placeholder" - value: "placeholder" - - method: GET - endpoint: "/api/aslan/workflow/workflow/preset/?*" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/testing/testdetail" - resourceType: "Workflow" - - method: PUT - endpoint: "/api/aslan/workflow/v3/?*" - resourceType: "Workflow" - #一个服务关联多个构建 - - method: GET - endpoint: "/api/aslan/build/build/serviceModule" - resourceType: "Workflow" - - action: create_workflow - alias: "新建" - description: "" - rules: - #一个服务关联多个构建 - - method: GET - endpoint: "/api/aslan/build/build/serviceModule" - resourceType: "Workflow" - - method: POST - endpoint: "api/aslan/workflow/workflow" - resourceType: "Workflow" - - method: PUT - endpoint: "/api/aslan/workflow/workflow/old/?*/new/?*" - resourceType: "Workflow" - - method: POST - endpoint: "/api/directory/workflowTask/create" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - matchAttributes: - - key: "placeholder" - value: "placeholder" - - method: GET - endpoint: "/api/aslan/testing/testdetail" - resourceType: "Workflow" - - method: POST - endpoint: "/api/aslan/workflow/v3" - resourceType: "Workflow" - - action: delete_workflow - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/workflow/workflow/?*" - resourceType: "Workflow" - - method: DELETE - endpoint: "/api/aslan/workflow/v3/?*" - resourceType: "Workflow" - - action: run_workflow - alias: "执行" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/workflow/workflowtask/?*" - idRegex: "/api/aslan/workflow/workflowtask/([\\w\\W]+?)$" - resourceType: "Workflow" - - method: PUT - endpoint: "/api/aslan/workflow/workflowtask/?*" - idRegex: "/api/aslan/workflow/workflowtask/([\\w\\W]+?)$" - resourceType: "Workflow" - - method: POST - endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*/restart" - idRegex: "/pipelines/([\\w\\W]+?)/restart" - resourceType: "Workflow" - - method: DELETE - endpoint: "/api/aslan/workflow/workflowtask/id/?*/pipelines/?*" - idRegex: "/pipelines/([\\w\\W]+?)$" - resourceType: "Workflow" - - method: POST - endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/restart" - resourceType: "Workflow" - - method: POST - endpoint: "/api/directory/workflowTask/id/?*/pipelines/?*/cancel" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/environment/environments" - resourceType: "Environment" - matchAttributes: - - key: "placeholder" - value: "placeholder" - - method: GET - endpoint: "/api/aslan/project/products/?*/services" - resourceType: "Workflow" - - method: GET - endpoint: "/api/aslan/workflow/v3/?*/args" - resourceType: "Workflow" - - method: POST - endpoint: "/api/aslan/workflow/v3/workflowtask" - resourceType: "Workflow" - - method: POST - endpoint: "/api/aslan/workflow/v3/workflowtask/id/?*/name/?*/restart" - resourceType: "Workflow" - - method: DELETE - endpoint: "/api/aslan/workflow/v3/workflowtask/id/?*/name/?*" - resourceType: "Workflow" diff --git a/pkg/microservice/aslan/core/workflow/handler/policy_meta.go b/pkg/microservice/aslan/core/workflow/handler/policy_meta.go deleted file mode 100644 index b2f49de2..00000000 --- a/pkg/microservice/aslan/core/workflow/handler/policy_meta.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - _ "embed" - - "sigs.k8s.io/yaml" - - "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/tool/log" -) - -//go:embed policy.yaml -var workflowPolicyMeta []byte - -//go:embed system-policies.yaml -var systemPolicyMeta []byte - -func (*Router) Policies() []*policy.PolicyMeta { - res := &policy.PolicyMeta{} - err := yaml.Unmarshal(workflowPolicyMeta, res) - if err != nil { - // should not have happened here - log.DPanic(err) - } - - systemMetas := []*policy.PolicyMeta{} - err = yaml.Unmarshal(systemPolicyMeta, &systemMetas) - if err != nil { - // should not have happened here - log.DPanic(err) - } - systemMetas = append(systemMetas, res) - - return systemMetas -} diff --git a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml b/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml deleted file mode 100644 index f3c098d0..00000000 --- a/pkg/microservice/aslan/core/workflow/handler/system-policies.yaml +++ /dev/null @@ -1,192 +0,0 @@ -- resource: TestCenter - alias: "测试中心" - description: "" - rules: - - action: get_test - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/testing/test" - # get single test info - - method: GET - endpoint: "/api/aslan/testing/test/?*" - - method: GET - endpoint: "/api/aslan/testing/testdetail" - - method: GET - endpoint: "/api/aslan/workflow/workflow/testName/?*" - # get test workflow related - - method: GET - endpoint: "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*" - - method: GET - endpoint: "/api/aslan/testing/workspace/workflow/?*/taskId/?*" - - action: edit_test - alias: "编辑" - description: "" - rules: - - method: PUT - endpoint: "/api/aslan/testing/test" - - method: GET - endpoint: "/api/aslan/cluster/clusters" - - action: delete_test - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/testing/test/?*" - - action: create_test - alias: "新建" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/testing/test" - - method: GET - endpoint: "/api/aslan/cluster/clusters" - - action: run_test - alias: "执行" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/testing/testtask" - - method: GET - endpoint: "/api/aslan/workflow/sse/workflows/id/?*/pipelines/?*" -- resource: DataCenter - alias: "数据视图" - description: "" - rules: - - action: data_over - alias: "数据概览|查看" - description: "" - rules: - - method: GET - endpoint: "/api/v1/picket/stat/dashboard/overview" - - method: GET - endpoint: "/api/v1/picket/stat/dashboard/test" - - method: GET - endpoint: "api/v1/picket/stat/dashboard/deploy" - - method: GET - endpoint: "/api/v1/picket/stat/dashboard/build" - - action: efficiency_over - alias: "数据洞察|查看" - description: "" - rules: - # 9 build efficiency - - method: GET - endpoint: "/api/aslan/stat/dashboard/build" - - method: GET - endpoint: "/api/aslan/stat/dashboard/deploy" - - method: GET - endpoint: "/api/aslan/stat/dashboard/test" - - method: POST - endpoint: "/api/aslan/stat/quality/buildHealthMeasure" - - method: POST - endpoint: "/api/aslan/stat/quality/buildLatestTenMeasure" - - method: POST - endpoint: "/api/aslan/stat/quality/buildDailyMeasure" - - method: POST - endpoint: "/api/aslan/stat/quality/buildTenDurationMeasure" - - method: POST - endpoint: "/api/aslan/stat/quality/buildAverageMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/buildTrend" - # 4 deploy efficiency - - method: POST - endpoint: "api/aslan/stat/quality/deployTopFiveFailureMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/deployTopFiveHigherMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/deployWeeklyMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/deployHealthMeasure" - # 5 test efficiency - - method: POST - endpoint: "api/aslan/stat/quality/testDeliveryDeploy" - - method: POST - endpoint: "api/aslan/stat/quality/testAverageMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/testCaseMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/testHealthMeasure" - - method: POST - endpoint: "api/aslan/stat/quality/testTrend" -- resource: Template - alias: "模版库" - description: "" - rules: - - action: create_template - alias: "新建" - description: "新建" - rules: - - method: POST - endpoint: "/api/aslan/template/dockerfile" - - method: POST - endpoint: "/api/aslan/template/charts" - - method: POST - endpoint: "/api/aslan/template/yaml" - - method: POST - endpoint: "/api/aslan/template/build" - - action: get_template - alias: "查看" - description: "查看" - rules: - - method: GET - endpoint: "/api/aslan/template/yaml" - - method: GET - endpoint: "/api/aslan/template/charts" - - method: GET - endpoint: "/api/aslan/template/charts/?*" - - method: GET - endpoint: "/api/aslan/template/dockerfile" - - method: GET - endpoint: "/api/aslan/template/build" - - method: GET - endpoint: "/api/aslan/template/build/?*" - - action: edit_template - alias: "编辑" - description: "编辑" - rules: - - method: PUT - endpoint: "/api/aslan/template/yaml/?*" - - method: PUT - endpoint: "/api/aslan/template/charts/?*" - # 修改 chart 模版变量 - - method: PUT - endpoint: "api/aslan/template/charts/?*/variables" - - method: POST - endpoint: "api/aslan/template/charts/?*/reference" - - method: POST - endpoint: "api/aslan/template/yaml/?*/reference" - - method: PUT - endpoint: "/api/aslan/template/dockerfile/?*" - - method: PUT - endpoint: "/api/aslan/template/build/?*" - - action: delete_template - alias: "删除" - description: "删除" - rules: - - method: DELETE - endpoint: "/api/aslan/template/yaml/?*" - - method: DELETE - endpoint: "/api/aslan/template/charts/?*" - - method: DELETE - endpoint: "/api/aslan/template/dockerfile/?*" - - method: DELETE - endpoint: "/api/aslan/template/build/?*" -- resource: DeliveryCenter - alias: "交付中心" - description: "" - rules: - - action: realase_get - alias: "版本管理|查看" - description: "查看" - rules: - - method: GET - endpoint: "/api/aslan/delivery/releases" - - method: GET - endpoint: "/api/aslan/delivery/releases/?*" - - action: delivery_get - alias: "交付物追踪|查看" - description: "查看" - rules: - - method: GET - endpoint: "/api/aslan/delivery/artifacts" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml b/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml deleted file mode 100644 index 87622c4d..00000000 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy.yaml +++ /dev/null @@ -1,46 +0,0 @@ -resource: Test -alias: "测试" -description: "" -rules: - - action: get_test - alias: "查看" - description: "" - rules: - - method: GET - endpoint: "/api/aslan/testing/test" - - method: GET - endpoint: "/api/aslan/testing/test/?*" - - method: GET - endpoint: "/api/aslan/testing/testdetail" - - method: GET - endpoint: "/api/aslan/workflow/workflow/testName/?*" - - method: GET - endpoint: "/api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?*" - - method: GET - endpoint: "/api/aslan/testing/workspace/workflow/?*/taskId/?*" - - method: GET - endpoint: "/api/aslan/cluster/clusters" - - action: edit_test - alias: "编辑" - description: "" - rules: - - method: PUT - endpoint: "/api/aslan/testing/test" - - action: delete_test - alias: "删除" - description: "" - rules: - - method: DELETE - endpoint: "/api/aslan/testing/test/?*" - - action: create_test - alias: "新建" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/testing/test" - - action: run_test - alias: "执行" - description: "" - rules: - - method: POST - endpoint: "/api/aslan/testing/testtask" diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go b/pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go deleted file mode 100644 index 695ba3c4..00000000 --- a/pkg/microservice/aslan/core/workflow/testing/handler/policy_meta.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package handler - -import ( - _ "embed" - - "sigs.k8s.io/yaml" - - "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/tool/log" -) - -//go:embed policy.yaml -var policyMeta []byte - -func (*Router) Policies() []*policy.PolicyMeta { - res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyMeta, res) - if err != nil { - // should not have happened here - log.DPanic(err) - } - - return []*policy.PolicyMeta{res} -} diff --git a/pkg/microservice/policy/core/handler/policy_meta_registration.go b/pkg/microservice/policy/core/handler/policy_meta_registration.go index 4fc3e1a0..8de9e877 100644 --- a/pkg/microservice/policy/core/handler/policy_meta_registration.go +++ b/pkg/microservice/policy/core/handler/policy_meta_registration.go @@ -23,20 +23,6 @@ import ( internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -func CreateOrUpdatePolicyRegistration(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - args := &service.PolicyMeta{} - if err := c.ShouldBindJSON(args); err != nil { - ctx.Err = err - return - } - args.Resource = c.Param("resourceName") - - ctx.Err = service.CreateOrUpdatePolicyRegistration(args, ctx.Logger) -} - func GetPolicyRegistrationDefinitions(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/policy/core/handler/router.go b/pkg/microservice/policy/core/handler/router.go index 071bc918..96a9efb4 100644 --- a/pkg/microservice/policy/core/handler/router.go +++ b/pkg/microservice/policy/core/handler/router.go @@ -103,11 +103,6 @@ func (*Router) Inject(router *gin.RouterGroup) { bundles.GET("/:name", DownloadBundle) } - policyRegistrations := router.Group("policymetas") - { - policyRegistrations.PUT("/:resourceName", CreateOrUpdatePolicyRegistration) - } - policyDefinitions := router.Group("policy-definitions") { policyDefinitions.GET("", GetPolicyRegistrationDefinitions) diff --git a/pkg/microservice/policy/core/service/bundle/exemption_urls.go b/pkg/microservice/policy/core/service/bundle/exemption_urls.go deleted file mode 100644 index 940c80f6..00000000 --- a/pkg/microservice/policy/core/service/bundle/exemption_urls.go +++ /dev/null @@ -1,445 +0,0 @@ -/* -Copyright 2021 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package bundle - -// TODO: Policy Service should not care about the policy details of a certain service, instead, a service which wants -// to be managed by the Policy Service should register its rules to the Policy Service so that the Policy Service knows -// how to make a decision without being aware of the detailed rules. - -type exemptionURLs struct { - Public rules `json:"public"` // public urls are not controlled by AuthN and AuthZ - Privileged rules `json:"privileged"` // privileged urls can only be visited by system admins - Registered rules `json:"registered"` // registered urls are the entire list of urls which are controlled by AuthZ, which means that if an url is not in this list, it is not controlled by AuthZ -} - -type policyRule struct { - Methods []string `json:"methods"` - Endpoints []string `json:"endpoints"` -} - -var publicURLs = []*policyRule{ - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/health"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/webhook"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/hub/connect"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/system/registry"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/testing/report"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/cluster/agent/?*/agent.yaml"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/podexec/health"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/login", "api/v1/signup", "api/v1/retrieve", "api/v1/login-enabled", "login/password"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/codehosts/?*/auth", "api/v1/codehosts/callback"}, - }, - { - Methods: []string{"*"}, - Endpoints: []string{"api/v1/callback"}, - }, - { - Methods: []string{"*"}, - Endpoints: []string{"dex/**"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"", "signin", "setup", "static/**", "v1/**", "mobile/**", "workflows/**", "favicon.ico"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/directory/codehosts/callback"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/features/?*"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/system/jenkins/exist"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/callback"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/environment/environments/?*/check/workloads/k8services"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/environment/environments/?*/check/sharenv/enable/ready"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/environment/environments/?*/check/sharenv/disable/ready"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/permission/project/?*"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/service/services/?*/environments/deployable"}, - }, -} - -// actions which are allowed for system admins only. -var systemAdminURLs = []*policyRule{ - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/v1/features/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/project/products"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/cleanCache/cron"}, - }, - { - Methods: []string{"POST", "PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/sonar/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/picket/projects"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/cluster/clusters"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/cluster/clusters/?*"}, - }, - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/aslan/cluster/clusters/?*/disconnect"}, - }, - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/aslan/cluster/clusters/?*/reconnect"}, - }, - { - Methods: []string{"GET", "POST", "PUT", "DELETE"}, - Endpoints: []string{"api/collaboration/collaborations"}, - }, - { - Methods: []string{"POST", "PUT"}, - Endpoints: []string{"api/aslan/system/install"}, - }, - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/aslan/system/install/delete"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/proxyManage"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/proxyManage/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/registry/namespaces"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/registry/namespaces/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/s3storage"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/s3storage/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/githubApp"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/aslan/system/githubApp/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/jenkins/integration"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/jenkins/integration/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/jenkins/integration/user/connection"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/basicImages"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/basicImages/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/helm"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/helm/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/privateKey"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/aslan/system/privateKey/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/aslan/system/announcement"}, - }, - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/aslan/system/announcement/update"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/system/announcement/all"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/aslan/system/announcement/?*"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/aslan/system/operation"}, - }, - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/aslan/system/operation/?*"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/aslan/system/proxy/config"}, - }, - { - Methods: []string{"GET", "PUT", "PATCH"}, - Endpoints: []string{"api/v1/users/?*"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/picket/users/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/users"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/public-roles"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/v1/public-roles/?*"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/system-roles"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/system-roles/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/public-policies"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/v1/public-policies/?*"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/system-policies"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/system-policies/?*"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/system-rolebindings"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/system-rolebindings/?*"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/system-policybindings"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/system-policybindings/?*"}, - }, -} - -// actions which are allowed for project admins. -var projectAdminURLs = []*policyRule{ - { - Methods: []string{"PUT"}, - Endpoints: []string{"api/aslan/project/products"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/v1/picket/projects/?*"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/aslan/project/products/?*"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/users"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/users/search"}, - }, - { - Methods: []string{"GET", "POST", "PUT", "DELETE"}, - Endpoints: []string{"api/collaboration/collaborations"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/roles"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/roles/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/roles"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/v1/roles/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/roles/bulk-delete"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/public-roles"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/public-roles/?*"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/policies"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/policies/?*"}, - }, - { - Methods: []string{"POST", "DELETE"}, - Endpoints: []string{"api/v1/policies"}, - }, - { - Methods: []string{"PUT", "DELETE"}, - Endpoints: []string{"api/v1/policies/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/policies/bulk-delete"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/public-policies"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/public-policies/?*"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/rolebindings"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/picket/rolebindings"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/rolebindings/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/rolebindings/bulk-delete"}, - }, - { - Methods: []string{"GET", "POST"}, - Endpoints: []string{"api/v1/policybindings"}, - }, - { - Methods: []string{"GET"}, - Endpoints: []string{"api/v1/picket/policybindings"}, - }, - { - Methods: []string{"DELETE"}, - Endpoints: []string{"api/v1/policybindings/?*"}, - }, - { - Methods: []string{"POST"}, - Endpoints: []string{"api/v1/policybindings/bulk-delete"}, - }, -} - -var adminURLs = append(systemAdminURLs, projectAdminURLs...) diff --git a/pkg/microservice/policy/core/service/bundle/opa_bundle.go b/pkg/microservice/policy/core/service/bundle/opa_bundle.go index 62539c41..9e6107df 100644 --- a/pkg/microservice/policy/core/service/bundle/opa_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/opa_bundle.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" "github.com/koderover/zadig/pkg/microservice/policy/core/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/policy/core/yamlconfig" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/tool/opa" ) @@ -84,10 +85,10 @@ type opaRoleBindings struct { type role struct { Name string `json:"name"` Namespace string `json:"namespace"` - Rules rules `json:"rules"` + Rules Rules `json:"rules"` } -type rule struct { +type Rule struct { Method string `json:"method"` Endpoint string `json:"endpoint"` ResourceType string `json:"resourceType,omitempty"` @@ -173,11 +174,11 @@ type roleRef struct { Namespace string `json:"namespace"` } -type rules []*rule +type Rules []*Rule -func (o rules) Len() int { return len(o) } -func (o rules) Swap(i, j int) { o[i], o[j] = o[j], o[i] } -func (o rules) Less(i, j int) bool { +func (o Rules) Len() int { return len(o) } +func (o Rules) Swap(i, j int) { o[i], o[j] = o[j], o[i] } +func (o Rules) Less(i, j int) bool { if o[i].Endpoint == o[j].Endpoint { if o[i].Method == o[j].Method { return o[i].MatchAttributes.LessOrEqual(o[j].MatchAttributes) @@ -290,7 +291,7 @@ func generateOPARoles(roles []*models.Role, policyMetas []*models.PolicyMeta) *o } for _, v := range r.Verbs { for _, endpoint := range r.Resources { - opaRole.Rules = append(opaRole.Rules, &rule{Method: v, Endpoint: endpoint}) + opaRole.Rules = append(opaRole.Rules, &Rule{Method: v, Endpoint: endpoint}) } } } @@ -351,7 +352,7 @@ func generateOPAPolicies(policies []*models.Policy, policyMetas []*models.Policy } for _, v := range r.Verbs { for _, endpoint := range r.Resources { - opaRole.Rules = append(opaRole.Rules, &rule{Method: v, Endpoint: endpoint}) + opaRole.Rules = append(opaRole.Rules, &Rule{Method: v, Endpoint: endpoint}) } } } @@ -419,29 +420,32 @@ func generateOPABindings(rbs []*models.RoleBinding, pbs []*models.PolicyBinding) return data } -func generateOPAExemptionURLs(policies []*models.PolicyMeta) *exemptionURLs { - data := &exemptionURLs{} +type ExemptionURLs struct { + Public Rules `json:"public"` // public urls are not controlled by AuthN and AuthZ + Privileged Rules `json:"privileged"` // privileged urls can only be visited by system admins + Registered Rules `json:"registered"` // registered urls are the entire list of urls which are controlled by AuthZ, which means that if an url is not in this list, it is not controlled by AuthZ +} + +func generateOPAExemptionURLs(policies []*models.PolicyMeta) *ExemptionURLs { + data := &ExemptionURLs{} - for _, r := range publicURLs { + for _, r := range yamlconfig.GetExemptionsUrls().Public { if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { r.Methods = AllMethods } for _, method := range r.Methods { - for _, endpoint := range r.Endpoints { - data.Public = append(data.Public, &rule{Method: method, Endpoint: endpoint}) - } + data.Public = append(data.Public, &Rule{Method: method, Endpoint: r.Endpoint}) + } } sort.Sort(data.Public) - for _, r := range systemAdminURLs { + for _, r := range yamlconfig.GetExemptionsUrls().SystemAdmin { if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { r.Methods = AllMethods } for _, method := range r.Methods { - for _, endpoint := range r.Endpoints { - data.Privileged = append(data.Privileged, &rule{Method: method, Endpoint: endpoint}) - } + data.Privileged = append(data.Privileged, &Rule{Method: method, Endpoint: r.Endpoint}) } } sort.Sort(data.Privileged) @@ -454,14 +458,15 @@ func generateOPAExemptionURLs(policies []*models.PolicyMeta) *exemptionURLs { } } } + + adminURLs := append(yamlconfig.GetExemptionsUrls().SystemAdmin, yamlconfig.GetExemptionsUrls().ProjectAdmin...) + for _, r := range adminURLs { if len(r.Methods) == 1 && r.Methods[0] == models.MethodAll { r.Methods = AllMethods } for _, method := range r.Methods { - for _, endpoint := range r.Endpoints { - data.Registered = append(data.Registered, &rule{Method: method, Endpoint: endpoint}) - } + data.Registered = append(data.Registered, &Rule{Method: method, Endpoint: r.Endpoint}) } } diff --git a/pkg/microservice/policy/core/service/bundle/resource_bundle.go b/pkg/microservice/policy/core/service/bundle/resource_bundle.go index 3ca2c0b5..9fb5de8b 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_bundle.go +++ b/pkg/microservice/policy/core/service/bundle/resource_bundle.go @@ -37,7 +37,8 @@ type ResourceBundleService struct { } type Config struct { - Bundles []*ResourceBundleService `json:"bundles"` + Bundles []*ResourceBundleService `json:"bundles"` + Namespace string `json:"namespace"` } type ResourceSpec struct { diff --git a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go index e5d43f2b..5518ca78 100644 --- a/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go +++ b/pkg/microservice/policy/core/service/bundle/resource_endpoint_mapping.go @@ -24,9 +24,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/policy/core/repository/models" ) -type resourceActionMappings map[string]map[string][]*rule +type resourceActionMappings map[string]map[string][]*Rule -func (m resourceActionMappings) GetRules(resource string, actions []string) []*rule { +func (m resourceActionMappings) GetRules(resource string, actions []string) []*Rule { mappings, ok := m[resource] if !ok { return nil @@ -37,11 +37,11 @@ func (m resourceActionMappings) GetRules(resource string, actions []string) []*r all = true } actionSet := sets.NewString(actions...) - var res []*rule + var res []*Rule for action, r := range mappings { if all || actionSet.Has(action) { for _, rr := range r { - rrr := &rule{ + rrr := &Rule{ Method: rr.Method, Endpoint: rr.Endpoint, ResourceType: rr.ResourceType, @@ -57,7 +57,7 @@ func (m resourceActionMappings) GetRules(resource string, actions []string) []*r return res } -func (m resourceActionMappings) GetPolicyRules(resource string, actions []string, verbAttrMap map[string]sets.String) []*rule { +func (m resourceActionMappings) GetPolicyRules(resource string, actions []string, verbAttrMap map[string]sets.String) []*Rule { mappings, ok := m[resource] if !ok { return nil @@ -68,7 +68,7 @@ func (m resourceActionMappings) GetPolicyRules(resource string, actions []string all = true } actionSet := sets.NewString(actions...) - var res []*rule + var res []*Rule for action, r := range mappings { var attr Attributes if attrs, ok := verbAttrMap[action]; ok { @@ -93,7 +93,7 @@ func (m resourceActionMappings) GetPolicyRules(resource string, actions []string attrSets.Insert(attribute.Key + "&&" + attribute.Value) } } - rrr := &rule{ + rrr := &Rule{ Method: rr.Method, Endpoint: rr.Endpoint, ResourceType: rr.ResourceType, @@ -113,7 +113,7 @@ func getResourceActionMappings(isPolicy bool, policies []*models.PolicyMeta) res data := make(resourceActionMappings) for _, p := range policies { if _, ok := data[p.Resource]; !ok { - data[p.Resource] = make(map[string][]*rule) + data[p.Resource] = make(map[string][]*Rule) } for _, r := range p.Rules { @@ -126,7 +126,7 @@ func getResourceActionMappings(isPolicy bool, policies []*models.PolicyMeta) res as = append(as, &Attribute{Key: a.Key, Value: a.Value}) } - data[p.Resource][r.Action] = append(data[p.Resource][r.Action], &rule{ + data[p.Resource][r.Action] = append(data[p.Resource][r.Action], &Rule{ Method: ar.Method, Endpoint: ar.Endpoint, ResourceType: ar.ResourceType, diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index d2d4458a..dbcd0511 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -181,14 +181,17 @@ type GetUserRulesResp struct { func GetUserRules(uid string, log *zap.SugaredLogger) (*GetUserRulesResp, error) { roleBindings, err := mongodb.NewRoleBindingColl().ListRoleBindingsByUIDs([]string{uid}) if err != nil { - return nil, err + log.Errorf("ListRoleBindingsByUIDs err:%s") + return &GetUserRulesResp{}, err } if len(roleBindings) == 0 { - return nil, nil + log.Info("rolebindings == 0") + return &GetUserRulesResp{}, nil } roles, err := ListUserAllRolesByRoleBindings(roleBindings) if err != nil { - return nil, err + log.Errorf("ListUserAllRolesByRoleBindings err:%s", err) + return &GetUserRulesResp{}, err } roleMap := make(map[string]*models.Role) for _, role := range roles { diff --git a/pkg/microservice/policy/core/service/policy_meta_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go index e5aa01b4..d0940c04 100644 --- a/pkg/microservice/policy/core/service/policy_meta_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -26,33 +26,6 @@ import ( "github.com/koderover/zadig/pkg/types" ) -type PolicyMeta struct { - Resource string `json:"resource"` - Alias string `json:"alias"` - Description string `json:"description"` - Rules []*PolicyRule `json:"rules"` -} - -type PolicyRule struct { - Action string `json:"action"` - Alias string `json:"alias"` - Description string `json:"description"` - Rules []*ActionRule `json:"rules"` -} - -type ActionRule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` - ResourceType string `json:"resourceType,omitempty"` - IDRegex string `json:"idRegex,omitempty"` - MatchAttributes []attribute `json:"matchAttributes,omitempty"` -} - -type attribute struct { - Key string `json:"key"` - Value string `json:"value"` -} - type PolicyDefinition struct { Resource string `json:"resource"` Alias string `json:"alias"` @@ -66,7 +39,7 @@ type PolicyRuleDefinition struct { Description string `json:"description"` } -func CreateOrUpdatePolicyRegistration(p *PolicyMeta, _ *zap.SugaredLogger) error { +func CreateOrUpdatePolicyRegistration(p *types.PolicyMeta, _ *zap.SugaredLogger) error { obj := &models.PolicyMeta{ Resource: p.Resource, Alias: p.Alias, @@ -105,7 +78,7 @@ func GetPolicyRegistrationDefinitions(scope, envType string, _ *zap.SugaredLogge return nil, err } systemScopeSet := sets.NewString("TestCenter", "DataCenter", "Template", "DeliveryCenter") - projectScopeSet := sets.NewString("Workflow", "Environment", "Test", "Delivery", "Build", "Service") + projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build", "Service") systemPolicyMetas, projectPolicyMetas, filteredPolicyMetas := []*models.PolicyMeta{}, []*models.PolicyMeta{}, []*models.PolicyMeta{} for _, v := range policieMetas { if systemScopeSet.Has(v.Resource) { diff --git a/pkg/microservice/policy/core/yamlconfig/meta.go b/pkg/microservice/policy/core/yamlconfig/meta.go new file mode 100644 index 00000000..13084ed4 --- /dev/null +++ b/pkg/microservice/policy/core/yamlconfig/meta.go @@ -0,0 +1,169 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package yamlconfig + +import ( + _ "embed" + "strings" + + "sigs.k8s.io/yaml" + + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util/deepcopy" +) + +//go:embed metas.yaml +var PolicyMetas []byte + +var defaultMetaConfig *MetaConfig + +func init() { + log.Init(&log.Config{ + Level: "debug", + NoCaller: true, + }) + defaultMetaConfig = newMetaConfigFromEmbed() +} + +func DefaultPolicyMetas() []*types.PolicyMeta { + return defaultMetaConfig.Policies() +} + +func DefaultPolicyMetasConfig() *MetaConfig { + return defaultMetaConfig +} + +func PolicyMetasFromBytes(bs []byte) []*types.PolicyMeta { + return newMetaConfigFromBytes(bs).Policies() +} + +type MetaGetter interface { + Policies() []*types.PolicyMeta +} + +type MetaConfig struct { + Description string `json:"description"` + Metas []*types.PolicyMeta `json:"metas"` +} + +func (m *MetaConfig) Policies() []*types.PolicyMeta { + m.Metas = processMetas(m.Metas) + return m.Metas +} + +func newMetaConfigFromBytes(b []byte) MetaGetter { + m := &MetaConfig{} + if err := yaml.Unmarshal(b, &m); err != nil { + log.Warnf("fail to new meta getter err:%s, use the default meta getter", err) + return defaultMetaConfig + } + return m +} + +func newMetaConfigFromEmbed() *MetaConfig { + config := &MetaConfig{} + if err := yaml.Unmarshal(PolicyMetas, &config); err != nil { + log.DPanic(err) + } + return config +} + +func processMetas(metas []*types.PolicyMeta) []*types.PolicyMeta { + proEnvMeta := &types.PolicyMeta{} + + for _, meta := range metas { + if meta.Resource == "Workflow" { + for _, ruleMeta := range meta.Rules { + tmpRules := []*types.ActionRule{} + for _, rule := range ruleMeta.Rules { + if rule.ResourceType == "" { + rule.ResourceType = "Workflow" + } + if strings.Contains(rule.Endpoint, ":name") { + idRegex := strings.ReplaceAll(rule.Endpoint, ":name", `([\w\W].*)`) + idRegex = strings.ReplaceAll(idRegex, "?*", `[\w\W].*`) + endpoint := strings.ReplaceAll(rule.Endpoint, ":name", "?*") + rule.Endpoint = endpoint + rule.IDRegex = idRegex + } + if rule.Filter { + rule.MatchAttributes = []*types.Attribute{ + { + Key: "placeholder", + Value: "placeholder", + }, + } + } + tmpRules = append(tmpRules, rule) + } + ruleMeta.Rules = tmpRules + } + } + if meta.Resource == "Environment" { + for _, ruleMeta := range meta.Rules { + tmpRules := []*types.ActionRule{} + for _, rule := range ruleMeta.Rules { + if rule.ResourceType == "" { + rule.ResourceType = "Environment" + } + if rule.Filter { + rule.MatchAttributes = []*types.Attribute{ + { + Key: "production", + Value: "false", + }, + } + } + if strings.Contains(rule.Endpoint, ":name") { + idRegex := strings.ReplaceAll(rule.Endpoint, ":name", `([\w\W].*)`) + idRegex = strings.ReplaceAll(idRegex, "?*", `[\w\W].*`) + endpoint := strings.ReplaceAll(rule.Endpoint, ":name", "?*") + rule.Endpoint = endpoint + rule.IDRegex = idRegex + rule.MatchAttributes = []*types.Attribute{ + { + Key: "production", + Value: "false", + }, + } + } + + tmpRules = append(tmpRules, rule) + } + ruleMeta.Rules = tmpRules + } + + if err := deepcopy.FromTo(meta, proEnvMeta); err != nil { + log.DPanic(err) + } + proEnvMeta.Resource = "ProductionEnvironment" + proEnvMeta.Alias = "环境(生产/预发布)" + for _, ru := range proEnvMeta.Rules { + for _, r := range ru.Rules { + for _, a := range r.MatchAttributes { + if a.Key == "production" { + a.Value = "true" + } + } + } + } + } + } + metas = append(metas, proEnvMeta) + return metas +} diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml new file mode 100644 index 00000000..31ac74b5 --- /dev/null +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -0,0 +1,662 @@ +description: " + public urls: url not need to authentication ; system_admin urls: urls that + only system admin has permission ; project_admin: urls that project admin has + permission" +metas: + - resource: Delivery + alias: 版本管理 + description: '' + rules: + - action: get_delivery + alias: 查看 + description: '' + rules: + - method: GET + endpoint: /api/aslan/delivery/releases/helm/charts + - method: GET + endpoint: /api/aslan/delivery/releases + - action: delete_delivery + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: /api/aslan/delivery/releases/?* + - action: create_delivery + alias: 创建 + description: '' + rules: + - method: POST + endpoint: /api/aslan/delivery/releases/helm + - method: GET + endpoint: /api/aslan/environment/environments + - method: GET + endpoint: /api/aslan/environment/environments/?*/helm/releases + - method: GET + endpoint: /api/aslan/environment/environments/?*/helm/charts + - method: GET + endpoint: /api/aslan/service/helm/?*/?*/fileContent + - method: POST + endpoint: /api/aslan/delivery/releases/helm/global-variables + - method: GET + endpoint: /api/aslan/delivery/releases/helm/charts/version + - resource: Test + alias: 测试 + description: '' + rules: + - action: get_test + alias: 查看 + description: '' + rules: + - method: GET + endpoint: /api/aslan/testing/test + - method: GET + endpoint: /api/aslan/testing/test/?* + - method: GET + endpoint: /api/aslan/testing/testdetail + - method: GET + endpoint: /api/aslan/workflow/workflow/testName/?* + - method: GET + endpoint: /api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?* + - method: GET + endpoint: /api/aslan/testing/workspace/workflow/?*/taskId/?* + - method: GET + endpoint: /api/aslan/cluster/clusters + - action: edit_test + alias: 编辑 + description: '' + rules: + - method: PUT + endpoint: /api/aslan/testing/test + - action: delete_test + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: /api/aslan/testing/test/?* + - action: create_test + alias: 新建 + description: '' + rules: + - method: POST + endpoint: /api/aslan/testing/test + - action: run_test + alias: 执行 + description: '' + rules: + - method: POST + endpoint: /api/aslan/testing/testtask + - resource: Service + alias: 服务 + description: '' + rules: + - action: get_service + alias: 查看 + description: '' + rules: + - method: GET + endpoint: /api/aslan/service/services + - method: GET + endpoint: /api/aslan/service/services/?*/k8s + - method: GET + endpoint: /api/aslan/project/products/?*/services + - method: GET + endpoint: /api/aslan/service/services/?* + - method: GET + endpoint: /api/aslan/project/products/?*/searching-rules + - method: GET + endpoint: /api/aslan/service/helm/?*/?*/filePath + - method: GET + endpoint: /api/aslan/service/helm/?*/?*/fileContent + - method: GET + endpoint: /api/aslan/service/helm/?*/?*/serviceModule + - method: GET + endpoint: /api/aslan/service/services/?*/pm + - method: GET + endpoint: /api/aslan/template/yaml + - method: GET + endpoint: /api/aslan/service/helm/?*/?*/fileContent + - action: edit_service + alias: 编辑 + description: '' + rules: + - method: POST + endpoint: /api/aslan/service/helm/?* + - method: PUT + endpoint: /api/aslan/service/services + - method: POST + endpoint: /api/aslan/service/services + - method: PUT + endpoint: /api/aslan/service/pm/?* + - method: PUT + endpoint: /api/aslan/project/products/?* + - method: PATCH + endpoint: /api/aslan/project/products/?* + - method: PUT + endpoint: /api/aslan/project/products/?*/searching-rules + - method: PUT + endpoint: /api/aslan/service/helm/services/releaseNaming + - action: create_service + alias: 新建 + description: '' + rules: + - method: POST + endpoint: /api/aslan/service/pm/?* + - method: POST + endpoint: /api/aslan/service/services + - method: POST + endpoint: /api/aslan/service/loader/load/?*/?* + - method: POST + endpoint: /api/aslan/service/template/load + - method: POST + endpoint: /api/aslan/service/helm/services + - method: POST + endpoint: /api/aslan/service/helm/services/bulk + - action: delete_service + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: /api/aslan/service/services/?*/?* + - method: DELETE + endpoint: /api/aslan/service/services/?*/pm + - resource: Build + alias: 构建 + description: '' + rules: + - action: get_build + alias: 查看 + description: '' + rules: + - method: GET + endpoint: /api/aslan/build/build + - method: GET + endpoint: /api/aslan/build/build/?* + - method: GET + endpoint: /api/aslan/build/targets + - method: GET + endpoint: /api/aslan/cluster/clusters + - action: create_build + alias: 新建 + description: '' + rules: + - method: POST + endpoint: /api/aslan/build/build + - action: edit_build + alias: 编辑 + description: '' + rules: + - method: PUT + endpoint: /api/aslan/build/build + - method: POST + endpoint: /api/aslan/build/targets + - method: POST + endpoint: /api/aslan/build/build/targets + - action: delete_build + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: /api/aslan/build/build + - resource: TestCenter + alias: 测试中心 + description: '' + rules: + - action: get_test + alias: 查看 + description: '' + rules: + - method: GET + endpoint: /api/aslan/testing/test + - method: GET + endpoint: /api/aslan/testing/test/?* + - method: GET + endpoint: /api/aslan/testing/testdetail + - method: GET + endpoint: /api/aslan/workflow/workflow/testName/?* + - method: GET + endpoint: /api/aslan/workflow/v2/tasks/workflow/workflow/?*/taskId/?* + - method: GET + endpoint: /api/aslan/testing/workspace/workflow/?*/taskId/?* + - action: edit_test + alias: 编辑 + description: '' + rules: + - method: PUT + endpoint: /api/aslan/testing/test + - method: GET + endpoint: /api/aslan/cluster/clusters + - action: delete_test + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: /api/aslan/testing/test/?* + - action: create_test + alias: 新建 + description: '' + rules: + - method: POST + endpoint: /api/aslan/testing/test + - method: GET + endpoint: /api/aslan/cluster/clusters + - action: run_test + alias: 执行 + description: '' + rules: + - method: POST + endpoint: /api/aslan/testing/testtask + - method: GET + endpoint: /api/aslan/workflow/sse/workflows/id/?*/pipelines/?* + - resource: DataCenter + alias: 数据视图 + description: '' + rules: + - action: data_over + alias: 数据概览|查看 + description: '' + rules: + - method: GET + endpoint: /api/v1/picket/stat/dashboard/overview + - method: GET + endpoint: /api/v1/picket/stat/dashboard/test + - method: GET + endpoint: api/v1/picket/stat/dashboard/deploy + - method: GET + endpoint: /api/v1/picket/stat/dashboard/build + - action: efficiency_over + alias: 数据洞察|查看 + description: '' + rules: + - method: GET + endpoint: /api/aslan/stat/dashboard/build + - method: GET + endpoint: /api/aslan/stat/dashboard/deploy + - method: GET + endpoint: /api/aslan/stat/dashboard/test + - method: POST + endpoint: /api/aslan/stat/quality/buildHealthMeasure + - method: POST + endpoint: /api/aslan/stat/quality/buildLatestTenMeasure + - method: POST + endpoint: /api/aslan/stat/quality/buildDailyMeasure + - method: POST + endpoint: /api/aslan/stat/quality/buildTenDurationMeasure + - method: POST + endpoint: /api/aslan/stat/quality/buildAverageMeasure + - method: POST + endpoint: api/aslan/stat/quality/buildTrend + - method: POST + endpoint: api/aslan/stat/quality/deployTopFiveFailureMeasure + - method: POST + endpoint: api/aslan/stat/quality/deployTopFiveHigherMeasure + - method: POST + endpoint: api/aslan/stat/quality/deployWeeklyMeasure + - method: POST + endpoint: api/aslan/stat/quality/deployHealthMeasure + - method: POST + endpoint: api/aslan/stat/quality/testDeliveryDeploy + - method: POST + endpoint: api/aslan/stat/quality/testAverageMeasure + - method: POST + endpoint: api/aslan/stat/quality/testCaseMeasure + - method: POST + endpoint: api/aslan/stat/quality/testHealthMeasure + - method: POST + endpoint: api/aslan/stat/quality/testTrend + - resource: Template + alias: 模版库 + description: '' + rules: + - action: create_template + alias: 新建 + description: 新建 + rules: + - method: POST + endpoint: /api/aslan/template/dockerfile + - method: POST + endpoint: /api/aslan/template/charts + - method: POST + endpoint: /api/aslan/template/yaml + - method: POST + endpoint: /api/aslan/template/build + - action: get_template + alias: 查看 + description: 查看 + rules: + - method: GET + endpoint: /api/aslan/template/yaml + - method: GET + endpoint: /api/aslan/template/charts + - method: GET + endpoint: /api/aslan/template/charts/?* + - method: GET + endpoint: /api/aslan/template/dockerfile + - method: GET + endpoint: /api/aslan/template/build + - method: GET + endpoint: /api/aslan/template/build/?* + - action: edit_template + alias: 编辑 + description: 编辑 + rules: + - method: PUT + endpoint: /api/aslan/template/yaml/?* + - method: PUT + endpoint: /api/aslan/template/charts/?* + - method: POST + endpoint: api/aslan/template/charts/?*/reference + - method: POST + endpoint: api/aslan/template/yaml/?*/reference + - method: PUT + endpoint: api/aslan/template/charts/?*/variables + - method: PUT + endpoint: /api/aslan/template/dockerfile/?* + - method: PUT + endpoint: /api/aslan/template/build/?* + - action: delete_template + alias: 删除 + description: 删除 + rules: + - method: DELETE + endpoint: /api/aslan/template/yaml/?* + - method: DELETE + endpoint: /api/aslan/template/charts/?* + - method: DELETE + endpoint: /api/aslan/template/dockerfile/?* + - method: DELETE + endpoint: /api/aslan/template/build/?* + - resource: DeliveryCenter + alias: 交付中心 + description: '' + rules: + - action: release_get + alias: 版本管理|查看 + description: 查看 + rules: + - method: GET + endpoint: /api/aslan/delivery/releases + - method: GET + endpoint: /api/aslan/delivery/releases/?* + - action: delivery_get + alias: 交付物追踪|查看 + description: 查看 + rules: + - method: GET + endpoint: /api/aslan/delivery/artifacts + - resource: Workflow + alias: 工作流 + description: '' + rules: + - action: get_workflow + alias: 查看 + description: '' + rules: + - method: GET + endpoint: api/aslan/workflow/workflow + filter: true + - method: GET + endpoint: api/aslan/workflow/workflow + - method: GET + endpoint: /api/aslan/workflow/workflow/find/?* + - method: GET + endpoint: /api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?* + - method: GET + endpoint: /api/aslan/workflow/workflowtask/id/?*/pipelines/?* + - method: GET + endpoint: /api/aslan/workflow/sse/workflows/id/?*/pipelines/?* + - method: GET + endpoint: /api/aslan/logs/sse/workflow/build/?*/?*/?*/?* + - method: GET + endpoint: /api/aslan/logs/log/workflow/?*/tasks/?*/service/?* + - method: GET + endpoint: /api/aslan/logs/sse/workflow/test/?*/?*/?*/?*/?* + - method: GET + endpoint: /api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?* + - method: GET + endpoint: /api/aslan/logs/log/workflow/?*/tasks/?*/tests/test/service/?* + - method: GET + endpoint: /api/aslan/testing/itreport/workflow/?*/id/?*/names/?*/service/?* + - method: GET + endpoint: /api/directory/workflowTask + - method: GET + endpoint: /api/aslan/workflow/v3 + - method: GET + endpoint: /api/aslan/workflow/v3/?* + - method: GET + endpoint: /api/aslan/workflow/v3/?*/args + - method: GET + endpoint: /api/aslan/workflow/servicetask/workflows/?*/?*/?*/?* + - action: edit_workflow + alias: 编辑 + description: '' + rules: + - method: PUT + endpoint: 'api/aslan/workflow/workflow/:name' + - method: GET + endpoint: /api/aslan/environment/environments + resourceType: Environment + filter: true + - method: GET + endpoint: /api/aslan/workflow/workflow/preset/?* + - method: GET + endpoint: /api/aslan/testing/testdetail + - method: PUT + endpoint: /api/aslan/workflow/v3/?* + - method: GET + endpoint: /api/aslan/build/build/serviceModule + - action: create_workflow + alias: 新建 + description: '' + rules: + - method: GET + endpoint: /api/aslan/build/build/serviceModule + - method: POST + endpoint: api/aslan/workflow/workflow + - method: PUT + endpoint: /api/aslan/workflow/workflow/old/?*/new/?* + - method: POST + endpoint: /api/directory/workflowTask/create + - method: GET + endpoint: /api/aslan/environment/environments + resourceType: Environment + filter: true + - method: GET + endpoint: /api/aslan/testing/testdetail + - method: POST + endpoint: /api/aslan/workflow/v3 + - action: delete_workflow + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: /api/aslan/workflow/workflow/?* + - method: DELETE + endpoint: /api/aslan/workflow/v3/?* + - action: run_workflow + alias: 执行 + description: '' + rules: + - method: POST + endpoint: '/api/aslan/workflow/workflowtask/:name' + - method: PUT + endpoint: '/api/aslan/workflow/workflowtask/:name' + - method: POST + endpoint: '/api/aslan/workflow/workflowtask/id/?*/pipelines/:name/restart' + - method: DELETE + endpoint: '/api/aslan/workflow/workflowtask/id/?*/pipelines/:name' + - method: POST + endpoint: /api/directory/workflowTask/id/?*/pipelines/?*/restart + - method: POST + endpoint: /api/directory/workflowTask/id/?*/pipelines/?*/cancel + - method: GET + endpoint: /api/aslan/environment/environments + resourceType: Environment + filter: true + - method: GET + endpoint: /api/aslan/project/products/?*/services + - method: GET + endpoint: /api/aslan/workflow/v3/?*/args + - method: POST + endpoint: /api/aslan/workflow/v3/workflowtask + - method: POST + endpoint: /api/aslan/workflow/v3/workflowtask/id/?*/name/?*/restart + - method: DELETE + endpoint: /api/aslan/workflow/v3/workflowtask/id/?*/name/?* + - resource: Environment + alias: 环境 + description: '' + rules: + - action: get_environment + alias: 查看 + description: '' + rules: + - method: GET + endpoint: '/api/aslan/environment/environments/:name/helm/values' + - method: GET + endpoint: /api/aslan/environment/environments + - method: GET + endpoint: '/api/aslan/environment/environments/:name' + - method: GET + endpoint: '/api/aslan/environment/environments/:name/groups' + - method: GET + endpoint: '/api/aslan/environment/environments/:name/services/?*' + - method: GET + endpoint: /api/aslan/environment/kube/workloads + - method: GET + endpoint: /api/aslan/service/workloads + - method: GET + endpoint: /api/aslan/environment/export/service + - method: GET + endpoint: /api/aslan/environment/configmaps + - method: GET + endpoint: /api/aslan/environment/kube/pods/?*/events + - method: GET + endpoint: /api/aslan/environment/kube/events + - method: GET + endpoint: /api/aslan/logs/sse/pods/?*/containers/?* + - method: GET + endpoint: /api/aslan/project/products/?*/services + - method: GET + endpoint: /api/aslan/environment/revision/products + - method: GET + endpoint: '/api/aslan/environment/environments/:name/workloads' + - method: GET + endpoint: '/api/aslan/environment/environments/:name/helm/releases' + - method: GET + endpoint: '/api/aslan/environment/environments/:name/helm/charts' + - method: GET + endpoint: '/api/aslan/environment/environments/:name/helm/images' + - method: GET + endpoint: /api/aslan/environment/diff/products/?*/service/?* + - method: GET + endpoint: '/api/aslan/environment/configmaps/:name' + - method: GET + endpoint: '/api/aslan/environment/envcfgs/:name/cfg/?*' + - method: GET + endpoint: '/api/aslan/environment/secrets/:name' + - method: GET + endpoint: '/api/aslan/environment/ingresses/:name' + - method: GET + endpoint: '/api/aslan/environment/pvcs/:name' + - action: create_environment + alias: 创建 + description: '' + rules: + - method: POST + endpoint: /api/aslan/environment/environments + resourceType: Cluster + filter: true + - method: POST + endpoint: /api/aslan/service/workloads + - method: GET + endpoint: /api/aslan/project/products/?*/services + - method: GET + endpoint: api/aslan/cluster/clusters + - action: config_environment + alias: 配置 + description: '' + rules: + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/helm/default-values' + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/helm/charts' + - method: GET + endpoint: /api/aslan/build/targets + - method: GET + endpoint: /api/aslan/build/build + - method: GET + endpoint: /api/aslan/service/services/?* + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/services' + - method: POST + endpoint: '/api/aslan/environment/environments/:name/share/enable' + - method: DELETE + endpoint: '/api/aslan/environment/environments/:name/share/enable' + - method: PUT + endpoint: /api/aslan/environment/environments + - method: PUT + endpoint: '/api/aslan/environment/environments/:name' + - method: PUT + endpoint: /api/aslan/service/pm/healthCheckUpdate + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/envRecycle' + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/renderset' + - method: PUT + endpoint: /api/aslan/service/workloads + - method: GET + endpoint: /api/aslan/project/products/?*/services + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/registry' + - method: PUT + endpoint: '/api/aslan/environment/envcfgs/:name' + - method: POST + endpoint: '/api/aslan/environment/envcfgs/:name' + - method: DELETE + endpoint: '/api/aslan/environment/envcfgs/:name/cfg/?*' + - action: manage_environment + alias: 管理服务实例 + description: '' + rules: + - method: PUT + endpoint: /api/aslan/environment/environments + - method: POST + endpoint: '/api/aslan/environment/environments/:name/services/?*/restart' + - method: POST + endpoint: '/api/aslan/environment/environments/:name/services/?*/restartNew' + - method: POST + endpoint: '/api/aslan/environment/environments/:name/services/?*/scale' + - method: POST + endpoint: '/api/aslan/environment/environments/:name/services/?*/scaleNew' + - method: PUT + endpoint: '/api/aslan/environment/environments/:name/services/?*' + - method: POST + endpoint: '/api/aslan/environment/image/deployment/:name' + - method: POST + endpoint: '/api/aslan/environment/image/statefulset/:name' + - method: DELETE + endpoint: '/api/aslan/environment/kube/:name/pods/?*' + - method: PUT + endpoint: /api/aslan/environment/configmaps + - method: POST + endpoint: /api/aslan/environment/configmaps + - method: POST + endpoint: /api/aslan/workflow/servicetask + - action: delete_environment + alias: 删除 + description: '' + rules: + - method: DELETE + endpoint: '/api/aslan/environment/environments/:name' + - action: debug_pod + alias: 服务调试 + description: '' + rules: + - method: GET + endpoint: '/api/podexec/?*/?*/?*/?*/podExec/:name' + - action: ssh_pm + alias: 主机登录 + description: '' + rules: + - method: GET + endpoint: '/api/aslan/environment/environments/:name/services/?*/pmexec' diff --git a/pkg/microservice/aslan/core/delivery/handler/policy_meta.go b/pkg/microservice/policy/core/yamlconfig/role.go similarity index 53% rename from pkg/microservice/aslan/core/delivery/handler/policy_meta.go rename to pkg/microservice/policy/core/yamlconfig/role.go index 695ba3c4..de3e927d 100644 --- a/pkg/microservice/aslan/core/delivery/handler/policy_meta.go +++ b/pkg/microservice/policy/core/yamlconfig/role.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,27 +14,24 @@ See the License for the specific language governing permissions and limitations under the License. */ -package handler +package yamlconfig -import ( - _ "embed" +import _ "embed" - "sigs.k8s.io/yaml" +//go:embed role.yaml +var roles []byte +var useConfigMapRoles bool - "github.com/koderover/zadig/pkg/shared/client/policy" - "github.com/koderover/zadig/pkg/tool/log" -) +var configMapRoles []byte -//go:embed policy.yaml -var policyMeta []byte - -func (*Router) Policies() []*policy.PolicyMeta { - res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyMeta, res) - if err != nil { - // should not have happened here - log.DPanic(err) +func PresetRolesBytes() []byte { + if useConfigMapRoles { + return configMapRoles } + return roles +} - return []*policy.PolicyMeta{res} +func RefreshRoles(b []byte) { + useConfigMapRoles = true + configMapRoles = b } diff --git a/pkg/microservice/policy/core/yamlconfig/role.yaml b/pkg/microservice/policy/core/yamlconfig/role.yaml new file mode 100644 index 00000000..7312f82b --- /dev/null +++ b/pkg/microservice/policy/core/yamlconfig/role.yaml @@ -0,0 +1,103 @@ +description: "we have 5 preset roles: you can change the role rules to update permission" +preset_roles: + - name: admin + desc: 拥有系统中任何操作的权限 + rules: + - verbs: + - '*' + resources: + - '*' + - name: contributor + rules: + - verbs: + - get_workflow + - run_workflow + resources: + - Workflow + - verbs: + - get_environment + - config_environment + - manage_environment + - delete_environment + resources: + - Environment + - verbs: + - get_service + resources: + - Service + - verbs: + - get_build + resources: + - Build + - verbs: + - get_test + resources: + - Test + - name: project-admin + rules: + - verbs: + - '*' + resources: + - '*' + - name: read-only + rules: + - verbs: + - get_workflow + resources: + - Workflow + - verbs: + - get_environment + resources: + - Environment + - verbs: + - get_environment + resources: + - ProductionEnvironment + - verbs: + - get_service + resources: + - Service + - verbs: + - get_build + resources: + - Build + - verbs: + - get_test + resources: + - Test + - verbs: + - get_delivery + resources: + - Delivery + - name: read-project-only + rules: + - verbs: + - get_workflow + resources: + - Workflow + match_attributes: + - key: placeholder + value: placeholder + - verbs: + - get_environment + resources: + - Environment + match_attributes: + - key: placeholder + value: placeholder + - verbs: + - get_service + resources: + - Service + - verbs: + - get_build + resources: + - Build + - verbs: + - get_test + resources: + - Test + - verbs: + - get_delivery + resources: + - Delivery diff --git a/pkg/microservice/aslan/core/environment/handler/policy_meta.go b/pkg/microservice/policy/core/yamlconfig/urls.go similarity index 34% rename from pkg/microservice/aslan/core/environment/handler/policy_meta.go rename to pkg/microservice/policy/core/yamlconfig/urls.go index 492526fa..99d22d0c 100644 --- a/pkg/microservice/aslan/core/environment/handler/policy_meta.go +++ b/pkg/microservice/policy/core/yamlconfig/urls.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,49 +14,62 @@ See the License for the specific language governing permissions and limitations under the License. */ -package handler +package yamlconfig import ( _ "embed" "sigs.k8s.io/yaml" - "github.com/koderover/zadig/pkg/shared/client/policy" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) -const ( - productionPolicyResource = "ProductionEnvironment" - productionPolicyAlias = "环境(生产/预发布)" - productionKey = "production" - productionValueTrue = "true" -) +//go:embed urls.yaml +var urls []byte +var defaultEmbedUrlConfig *ConfigYaml +var configMapUrlConfig *ConfigYaml +var useConfigMapUrls bool -//go:embed policy.yaml -var policyMeta []byte +type ConfigYaml struct { + ExemptionUrls *ExemptionURLs `json:"exemption_urls"` + Description string `json:"description"` +} -func (*Router) Policies() []*policy.PolicyMeta { - res := &policy.PolicyMeta{} - err := yaml.Unmarshal(policyMeta, res) - if err != nil { - // should not have happened here +type ExemptionURLs struct { + Public []*types.PolicyRule `json:"public"` + SystemAdmin []*types.PolicyRule `json:"system_admin"` + ProjectAdmin []*types.PolicyRule `json:"project_admin"` +} + +func init() { + log.Init(&log.Config{ + Level: "debug", + NoCaller: true, + }) + if err := yaml.Unmarshal(urls, &defaultEmbedUrlConfig); err != nil { log.DPanic(err) } +} - productionPolicy := &policy.PolicyMeta{} - _ = yaml.Unmarshal(policyMeta, productionPolicy) - productionPolicy.Resource = productionPolicyResource - productionPolicy.Alias = productionPolicyAlias - //productionPolicy.Description = "" - for _, ru := range productionPolicy.Rules { - for _, r := range ru.Rules { - for _, a := range r.MatchAttributes { - if a.Key == productionKey { - a.Value = productionValueTrue - } - } - } +func GetExemptionsUrls() *ExemptionURLs { + if !useConfigMapUrls || configMapUrlConfig == nil { + return defaultEmbedUrlConfig.ExemptionUrls } + return configMapUrlConfig.ExemptionUrls +} + +func GetDefaultEmbedUrlConfig() *ConfigYaml { + return defaultEmbedUrlConfig +} - return []*policy.PolicyMeta{res, productionPolicy} +func RefreshConfigMapByte(b []byte) error { + if err := yaml.Unmarshal(b, &configMapUrlConfig); err != nil { + log.Errorf("refresh err:%s", err) + useConfigMapUrls = false + return err + } + log.Infof("start to use configMap url") + useConfigMapUrls = true + return nil } diff --git a/pkg/microservice/policy/core/yamlconfig/urls.yaml b/pkg/microservice/policy/core/yamlconfig/urls.yaml new file mode 100644 index 00000000..a9df1331 --- /dev/null +++ b/pkg/microservice/policy/core/yamlconfig/urls.yaml @@ -0,0 +1,387 @@ +description: "public urls: url not need to authentication ; system_admin urls: urls that only system admin has permission ; project_admin: urls that project admin has permission" +exemption_urls: + public: + - endpoint: api/v1/permission/project/?* + methods: + - GET + - endpoint: api/aslan/health + methods: + - GET + - endpoint: api/aslan/webhook + methods: + - POST + - endpoint: api/hub/connect + methods: + - GET + - endpoint: api/aslan/system/registry + methods: + - GET + - endpoint: api/aslan/testing/report + methods: + - GET + - endpoint: api/aslan/cluster/agent/?*/agent.yaml + methods: + - GET + - endpoint: api/podexec/health + methods: + - GET + - endpoint: api/v1/login + methods: + - GET + - POST + - endpoint: api/v1/signup + methods: + - GET + - POST + - endpoint: api/v1/retrieve + methods: + - GET + - POST + - endpoint: api/v1/login-enabled + methods: + - GET + - POST + - endpoint: login/password + methods: + - GET + - POST + - endpoint: api/v1/codehosts/?*/auth + methods: + - GET + - endpoint: api/v1/codehosts/callback + methods: + - GET + - endpoint: api/v1/callback + methods: + - GET + - POST + - PUT + - PATCH + - DELETE + - endpoint: dex/** + methods: + - GET + - POST + - PUT + - PATCH + - DELETE +# endpoint = '' is p0 level + - endpoint: '' + methods: + - GET + - endpoint: signin + methods: + - GET + - endpoint: setup + methods: + - GET + - endpoint: static/** + methods: + - GET + - endpoint: v1/** + methods: + - GET + - endpoint: mobile/** + methods: + - GET + - endpoint: workflows/** + methods: + - GET + - endpoint: favicon.ico + methods: + - GET + - endpoint: api/directory/codehosts/callback + methods: + - GET + - endpoint: api/v1/features/?* + methods: + - GET + - endpoint: api/aslan/system/jenkins/exist + methods: + - GET + - endpoint: api/callback + methods: + - POST + - endpoint: api/aslan/environment/environments/?*/check/workloads/k8services + methods: + - GET + - endpoint: api/aslan/environment/environments/?*/check/sharenv/enable/ready + methods: + - GET + - endpoint: api/aslan/environment/environments/?*/check/sharenv/disable/ready + methods: + - GET + - endpoint: api/aslan/service/services/?*/environments/deployable + methods: + - GET + system_admin: + - endpoint: api/v1/features/?* + methods: + - PUT + - endpoint: api/aslan/project/products + methods: + - POST + - endpoint: api/aslan/system/cleanCache/cron + methods: + - POST + - endpoint: api/aslan/system/sonar/?* + methods: + - POST + - PUT + - DELETE + - endpoint: api/v1/picket/projects + methods: + - POST + - endpoint: api/aslan/cluster/clusters + methods: + - POST + - endpoint: api/aslan/cluster/clusters/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/cluster/clusters/?*/disconnect + methods: + - PUT + - endpoint: api/aslan/cluster/clusters/?*/reconnect + methods: + - PUT + - endpoint: api/collaboration/collaborations + methods: + - GET + - POST + - PUT + - DELETE + - endpoint: api/aslan/system/install + methods: + - POST + - PUT + - endpoint: api/aslan/system/install/delete + methods: + - PUT + - endpoint: api/aslan/system/proxyManage + methods: + - POST + - endpoint: api/aslan/system/proxyManage/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/registry/namespaces + methods: + - POST + - endpoint: api/aslan/system/registry/namespaces/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/s3storage + methods: + - POST + - endpoint: api/aslan/system/s3storage/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/githubApp + methods: + - POST + - endpoint: api/aslan/system/githubApp/?* + methods: + - DELETE + - endpoint: api/aslan/system/jenkins/integration + methods: + - POST + - endpoint: api/aslan/system/jenkins/integration/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/jenkins/integration/user/connection + methods: + - POST + - endpoint: api/aslan/system/basicImages + methods: + - POST + - endpoint: api/aslan/system/basicImages/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/helm + methods: + - POST + - endpoint: api/aslan/system/helm/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/privateKey + methods: + - POST + - endpoint: api/aslan/system/privateKey/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/system/announcement + methods: + - POST + - endpoint: api/aslan/system/announcement/update + methods: + - PUT + - endpoint: api/aslan/system/announcement/all + methods: + - GET + - endpoint: api/aslan/system/announcement/?* + methods: + - DELETE + - endpoint: api/aslan/system/operation + methods: + - GET + - POST + - endpoint: api/aslan/system/operation/?* + methods: + - PUT + - endpoint: api/aslan/system/proxy/config + methods: + - GET + - endpoint: api/v1/users/?* + methods: + - GET + - PUT + - PATCH + - endpoint: api/v1/picket/users/?* + methods: + - DELETE + - endpoint: api/v1/users + methods: + - POST + - endpoint: api/v1/public-roles + methods: + - POST + - endpoint: api/v1/public-roles/?* + methods: + - PUT + - DELETE + - endpoint: api/v1/system-roles + methods: + - GET + - POST + - endpoint: api/v1/system-roles/?* + methods: + - DELETE + - endpoint: api/v1/public-policies + methods: + - POST + - endpoint: api/v1/public-policies/?* + methods: + - PUT + - DELETE + - endpoint: api/v1/system-policies + methods: + - GET + - POST + - endpoint: api/v1/system-policies/?* + methods: + - DELETE + - endpoint: api/v1/system-rolebindings + methods: + - GET + - POST + - endpoint: api/v1/system-rolebindings/?* + methods: + - DELETE + - endpoint: api/v1/system-policybindings + methods: + - GET + - POST + - endpoint: api/v1/system-policybindings/?* + methods: + - DELETE + project_admin: + - endpoint: api/aslan/project/products + methods: + - PUT + - endpoint: api/v1/picket/projects/?* + methods: + - PUT + - DELETE + - endpoint: api/aslan/project/products/?* + methods: + - DELETE + - endpoint: api/v1/users + methods: + - GET + - endpoint: api/v1/users/search + methods: + - POST + - endpoint: api/collaboration/collaborations + methods: + - GET + - POST + - PUT + - DELETE + - endpoint: api/v1/roles + methods: + - GET + - endpoint: api/v1/roles/?* + methods: + - GET + - endpoint: api/v1/roles + methods: + - POST + - endpoint: api/v1/roles/?* + methods: + - PUT + - DELETE + - endpoint: api/v1/roles/bulk-delete + methods: + - POST + - endpoint: api/v1/public-roles + methods: + - GET + - endpoint: api/v1/public-roles/?* + methods: + - GET + - endpoint: api/v1/policies + methods: + - GET + - endpoint: api/v1/policies/?* + methods: + - GET + - endpoint: api/v1/policies + methods: + - POST + - DELETE + - endpoint: api/v1/policies/?* + methods: + - PUT + - DELETE + - endpoint: api/v1/policies/bulk-delete + methods: + - POST + - endpoint: api/v1/public-policies + methods: + - GET + - endpoint: api/v1/public-policies/?* + methods: + - GET + - endpoint: api/v1/rolebindings + methods: + - GET + - POST + - endpoint: api/v1/picket/rolebindings + methods: + - GET + - endpoint: api/v1/rolebindings/?* + methods: + - DELETE + - endpoint: api/v1/rolebindings/bulk-delete + methods: + - POST + - endpoint: api/v1/policybindings + methods: + - GET + - POST + - endpoint: api/v1/picket/policybindings + methods: + - GET + - endpoint: api/v1/policybindings/?* + methods: + - DELETE + - endpoint: api/v1/policybindings/bulk-delete + methods: + - POST diff --git a/pkg/microservice/policy/server/server.go b/pkg/microservice/policy/server/server.go index 9fb22f23..2b1440d9 100644 --- a/pkg/microservice/policy/server/server.go +++ b/pkg/microservice/policy/server/server.go @@ -21,8 +21,25 @@ import ( "net/http" "time" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/informers" + "k8s.io/client-go/tools/cache" + "sigs.k8s.io/yaml" + + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + commonconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/policy/core" + "github.com/koderover/zadig/pkg/microservice/policy/core/service" + "github.com/koderover/zadig/pkg/microservice/policy/core/yamlconfig" "github.com/koderover/zadig/pkg/microservice/policy/server/rest" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + toolClient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" ) @@ -48,6 +65,21 @@ func Serve(ctx context.Context) error { log.Errorf("Failed to stop server, error: %s", err) } }() + go func() { + if err := toolClient.Start(ctx); err != nil { + panic(err) + } + }() + + if err := migratePolicyMeta(); err != nil { + log.Errorf("fail to migrate policyMeta, err:%s", err) + return err + } + + if err := migrateRole(); err != nil { + log.Errorf("fail to migrate role , err:%s", err) + return err + } if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Errorf("Failed to start http server, error: %s", err) @@ -58,3 +90,187 @@ func Serve(ctx context.Context) error { return nil } + +type PresetRoleConfigYaml struct { + PresetRoles []*service.Role `json:"preset_roles"` + Description string `json:"description"` +} + +func migrateRole() error { + + bs := yamlconfig.PresetRolesBytes() + config := PresetRoleConfigYaml{} + + if err := yaml.Unmarshal(bs, &config); err != nil { + log.Errorf("yaml Unmarshal err:%s", err) + return err + } + + for _, role := range config.PresetRoles { + for _, rule := range role.Rules { + rule.Kind = "resource" + } + role.Type = "system" + } + + for _, role := range config.PresetRoles { + if err := service.UpdateOrCreateRole(role.Namespace, role, nil); err != nil { + log.Errorf("UpdateOrCreateRole err:%s", err) + return err + } + } + return nil + +} + +// migratePolicyMeta migrate the policy meta db date +func migratePolicyMeta() error { + client, err := kubeclient.GetKubeClient(commonconfig.HubServerServiceAddress(), setting.LocalClusterID) + if err != nil { + log.DPanic(err) + } + clientset, err := kubeclient.GetKubeClientSet(commonconfig.HubServerServiceAddress(), setting.LocalClusterID) + if err != nil { + log.DPanic(err) + } + + namespace := commonconfig.Namespace() + policyMetasConfig := yamlconfig.DefaultPolicyMetasConfig() + policyMetaConfigBytes, err := yaml.Marshal(policyMetasConfig) + if err != nil { + return err + } + urls := yamlconfig.GetDefaultEmbedUrlConfig() + urlsBytes, err := yaml.Marshal(urls) + if err != nil { + return err + } + + metaConfigMap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: setting.PolicyMetaConfigMapName, + Namespace: namespace, + }, + Data: map[string]string{ + "meta.yaml": string(policyMetaConfigBytes), + }, + } + + urlConfigMap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: setting.PolicyURLConfigMapName, + Namespace: namespace, + }, + Data: map[string]string{ + "urls.yaml": string(urlsBytes), + }, + } + roleConfigMap := &corev1.ConfigMap{ + TypeMeta: metav1.TypeMeta{ + Kind: "ConfigMap", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: setting.PolicyRoleConfigMapName, + Namespace: namespace, + }, + Data: map[string]string{ + "roles.yaml": string(yamlconfig.PresetRolesBytes()), + }, + } + + if err := initConfigMap(metaConfigMap, client, clientset); err != nil { + log.Errorf("init config map meta err:%s", err) + } + if err := initConfigMap(urlConfigMap, client, clientset); err != nil { + log.Errorf("init config map url err:%s", err) + } + + if err := initConfigMap(roleConfigMap, client, clientset); err != nil { + log.Errorf("init config map roles err:%s", err) + } + + for _, v := range yamlconfig.DefaultPolicyMetas() { + if err := service.CreateOrUpdatePolicyRegistration(v, nil); err != nil { + log.DPanic(err) + } + } + _, err = NewClusterInformerFactory("", clientset) + return err +} + +func initConfigMap(cm *corev1.ConfigMap, client client.Client, clientset *kubernetes.Clientset) error { + _, found, err := getter.GetConfigMap(cm.Namespace, cm.Name, client) + if err != nil { + log.Errorf("get config map err:%s", err) + } + if !found { + err := updater.CreateConfigMap(cm, client) + if err != nil { + log.Infof("create config map err:%s", err) + return err + } + } else { + if err := updater.UpdateConfigMap(cm.Namespace, cm, clientset); err != nil { + log.Infof("update config map err:%s", err) + return err + } + } + return nil +} + +func NewClusterInformerFactory(clusterId string, cls *kubernetes.Clientset) (informers.SharedInformerFactory, error) { + if clusterId == "" { + clusterId = setting.LocalClusterID + } + informerFactory := informers.NewSharedInformerFactoryWithOptions(cls, time.Hour*24, informers.WithNamespace(commonconfig.Namespace())) + configMapsInformer := informerFactory.Core().V1().ConfigMaps().Informer() + configMapsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(oldObj, newObj interface{}) { + configMap, ok := newObj.(*corev1.ConfigMap) + if !ok { + return + } + if configMap.Name == setting.PolicyMetaConfigMapName { + if b, ok := configMap.Data["meta.yaml"]; ok { + log.Infof("start to update the meta configMap") + for _, v := range yamlconfig.PolicyMetasFromBytes([]byte(b)) { + if err := service.CreateOrUpdatePolicyRegistration(v, nil); err != nil { + log.Errorf("fail to CreateOrUpdatePolicyRegistration,err:%s", err) + continue + } + } + } + } + if configMap.Name == setting.PolicyURLConfigMapName { + if b, ok := configMap.Data["urls.yaml"]; ok { + log.Infof("start to refresh url configMap data") + if err := yamlconfig.RefreshConfigMapByte([]byte(b)); err != nil { + log.Errorf("refresh urls err:%s", err) + } + } + } + if configMap.Name == setting.PolicyRoleConfigMapName { + if b, ok := configMap.Data["roles.yaml"]; ok { + log.Infof("start to refresh role configmap data") + yamlconfig.RefreshRoles([]byte(b)) + if err := migrateRole(); err != nil { + log.Errorf("refresh role err:%s", err) + } + } + } + + }, + }) + stop := make(chan struct{}) + informerFactory.Start(stop) + return informerFactory, nil +} diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index b1ecf056..4e6ae701 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -205,6 +205,10 @@ const ( RSASecretName = "zadig-rsa-key" DefaultImagePullSecret = "default-registry-secret" + + PolicyMetaConfigMapName = "zadig-policy-meta" + PolicyURLConfigMapName = "zadig-policy-url" + PolicyRoleConfigMapName = "zadig-policy-role" ) const ( diff --git a/pkg/shared/client/policy/client.go b/pkg/shared/client/policy/client.go index 3692ce00..34f60e29 100644 --- a/pkg/shared/client/policy/client.go +++ b/pkg/shared/client/policy/client.go @@ -17,35 +17,16 @@ limitations under the License. package policy import ( - "time" - "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/tool/httpclient" ) -const retryInterval = 200 * time.Millisecond - type Client struct { *httpclient.Client host string } -func NewWithRetry() *Client { - host := config.PolicyServiceAddress() - - c := httpclient.New( - httpclient.SetHostURL(host+"/api/v1"), - httpclient.SetRetryCount(100), - httpclient.SetRetryWaitTime(retryInterval), - ) - - return &Client{ - Client: c, - host: host, - } -} - func NewDefault() *Client { host := config.PolicyServiceAddress() diff --git a/pkg/shared/client/policy/policy.go b/pkg/shared/client/policy/policy.go index c7404043..a2747146 100644 --- a/pkg/shared/client/policy/policy.go +++ b/pkg/shared/client/policy/policy.go @@ -22,29 +22,11 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) -type Policy struct { - Name string `json:"name"` - Description string `json:"description"` - UpdateTime int64 `json:"update_time"` - Rules []*Rule `json:"rules"` -} - -type Rule struct { - Verbs []string `json:"verbs"` - Resources []string `json:"resources"` - Kind string `json:"kind"` - MatchAttributes []MatchAttribute `json:"match_attributes"` -} - -type MatchAttribute struct { - Key string `json:"key"` - Value string `json:"value"` -} - type CreatePoliciesArgs struct { - Policies []*Policy `json:"policies"` + Policies []*types.Policy `json:"policies"` } type DeletePoliciesArgs struct { @@ -99,7 +81,7 @@ func (c *Client) DeletePolicyBindings(names []string, projectName string) error return err } -func (c *Client) UpdatePolicy(ns string, policy *Policy) error { +func (c *Client) UpdatePolicy(ns string, policy *types.Policy) error { url := fmt.Sprintf("/policies/%s?projectName=%s", policy.Name, ns) _, err := c.Put(url, httpclient.SetBody(policy)) return err @@ -123,9 +105,9 @@ func (c *Client) GetResourcePermission(req *ResourcePermissionReq) (map[string][ return result, nil } -func (c *Client) GetPolicies(names string) ([]*Policy, error) { +func (c *Client) GetPolicies(names string) ([]*types.Policy, error) { url := fmt.Sprintf("/policies/bulk") - res := make([]*Policy, 0) + res := make([]*types.Policy, 0) _, err := c.Get(url, httpclient.SetQueryParam("names", names), httpclient.SetResult(&res)) if err != nil { log.Errorf("Failed to getPolicies,err: %s", err) diff --git a/pkg/shared/client/policy/role.go b/pkg/shared/client/policy/role.go index 809e4fc3..27345de5 100644 --- a/pkg/shared/client/policy/role.go +++ b/pkg/shared/client/policy/role.go @@ -21,35 +21,9 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/types" ) -type PolicyMeta struct { - Resource string `json:"resource"` - Alias string `json:"alias"` - Description string `json:"description"` - Rules []*RuleMeta `json:"rules"` -} - -type RuleMeta struct { - Action string `json:"action"` - Alias string `json:"alias"` - Description string `json:"description"` - Rules []*ActionRule `json:"rules"` -} - -type ActionRule struct { - Method string `json:"method"` - Endpoint string `json:"endpoint"` - ResourceType string `json:"resourceType,omitempty"` - IDRegex string `json:"idRegex,omitempty"` - MatchAttributes []*Attribute `json:"matchAttributes,omitempty"` -} - -type Attribute struct { - Key string `json:"key"` - Value string `json:"value"` -} - type RoleBinding struct { Name string `json:"name"` UID string `json:"uid"` @@ -58,7 +32,7 @@ type RoleBinding struct { Type setting.ResourceType `json:"type"` } -func (c *Client) CreateOrUpdatePolicyRegistration(p *PolicyMeta) error { +func (c *Client) CreateOrUpdatePolicyRegistration(p *types.PolicyMeta) error { url := fmt.Sprintf("/policymetas/%s", p.Resource) _, err := c.Put(url, httpclient.SetBody(p)) @@ -134,10 +108,10 @@ type Role struct { Type setting.ResourceType `json:"type"` Desc string `json:"desc"` Rules []*struct { - Verbs []string `json:"verbs"` - Resources []string `json:"resources"` - Kind string `json:"kind"` - MatchAttributes []MatchAttribute `json:"match_attributes,omitempty"` + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + MatchAttributes []types.MatchAttribute `json:"match_attributes,omitempty"` } `json:"rules"` } diff --git a/pkg/types/policy.go b/pkg/types/policy.go index 51995e6d..bda3cd67 100644 --- a/pkg/types/policy.go +++ b/pkg/types/policy.go @@ -23,3 +23,55 @@ const ( SystemScope PolicyMetaScope = "system" ProjectScope PolicyMetaScope = "project" ) + +type Policy struct { + Name string `json:"name"` + Description string `json:"description"` + UpdateTime int64 `json:"update_time"` + Rules []*Rule `json:"rules"` +} + +type Rule struct { + Verbs []string `json:"verbs"` + Resources []string `json:"resources"` + Kind string `json:"kind"` + MatchAttributes []MatchAttribute `json:"match_attributes"` +} + +type MatchAttribute struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type PolicyMeta struct { + Resource string `json:"resource"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*RuleMeta `json:"rules"` +} + +type RuleMeta struct { + Action string `json:"action"` + Alias string `json:"alias"` + Description string `json:"description"` + Rules []*ActionRule `json:"rules"` +} + +type ActionRule struct { + Method string `json:"method"` + Endpoint string `json:"endpoint"` + ResourceType string `json:"resourceType,omitempty"` + IDRegex string `json:"idRegex,omitempty"` + MatchAttributes []*Attribute `json:"matchAttributes,omitempty"` + Filter bool `json:"filter,omitempty"` +} + +type Attribute struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type PolicyRule struct { + Methods []string `json:"methods"` + Endpoint string `json:"endpoint"` +} diff --git a/pkg/util/deepcopy/deepcopy.go b/pkg/util/deepcopy/deepcopy.go new file mode 100644 index 00000000..bc41cd1f --- /dev/null +++ b/pkg/util/deepcopy/deepcopy.go @@ -0,0 +1,145 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package deepcopy + +import ( + "fmt" + "reflect" + "unsafe" +) + +// FromTo deep copies original and assigns the copy to the copy argument (pointer). +func FromTo(original, copy interface{}) error { + if original == nil { + copy = nil + return nil + } else if copy == nil { // TODO try to initialize it here + return fmt.Errorf("FromTo: copy target is nil, it should be a valid pointer") + // copyValue := reflect.New(value.Type().Elem()).Elem() + // copy = copyValue.Interface() + } + copyValue := reflect.ValueOf(copy) + if copyValue.Kind() != reflect.Ptr { + return fmt.Errorf("FromTo: copy target type %T and not a pointer", copy) + } + value := reflect.ValueOf(original) + if value.Kind() == reflect.Ptr { + if value.IsNil() { + copy = nil // TODO return typed nil + return nil + } + value = value.Elem() + } + copyValue.Elem().Set(deepCopy(value)) + return nil +} + +func deepCopy(original reflect.Value) reflect.Value { + switch original.Kind() { + case reflect.Slice: + return deepCopySlice(original) + case reflect.Map: + return deepCopyMap(original) + case reflect.Ptr: + return deepCopyPointer(original) + case reflect.Struct: + return deepCopyStruct(original) + case reflect.Chan: + return deepCopyChan(original) + case reflect.Array: + return deepCopyArray(original) + default: + return forceCopyValue(original) + } +} + +func deepCopySlice(original reflect.Value) reflect.Value { + if original.IsNil() { + return original + } + copy := reflect.MakeSlice(original.Type(), 0, 0) + for i := 0; i < original.Len(); i++ { + elementCopy := deepCopy(original.Index(i)) + copy = reflect.Append(copy, elementCopy) + } + return copy +} + +func deepCopyArray(original reflect.Value) reflect.Value { + if original.Len() == 0 { + // it cannot be changed anyway, so we can return the original + return original + } + elementType := original.Index(0).Type() + arrayType := reflect.ArrayOf(original.Len(), elementType) + newPointer := reflect.New(arrayType) + copy := newPointer.Elem() + for i := 0; i < original.Len(); i++ { + subCopy := deepCopy(original.Index(i)) + copy.Index(i).Set(subCopy) + } + return copy +} + +func deepCopyMap(original reflect.Value) reflect.Value { + if original.IsNil() { + return original + } + keyType := original.Type().Key() + valueType := original.Type().Elem() + mapType := reflect.MapOf(keyType, valueType) + copy := reflect.MakeMap(mapType) + for _, key := range original.MapKeys() { + value := deepCopy(original.MapIndex(key)) + copy.SetMapIndex(key, value) + } + return copy +} + +func deepCopyPointer(original reflect.Value) reflect.Value { + if original.IsNil() { + return original + } + element := original.Elem() + copy := reflect.New(element.Type()) + copyElement := deepCopy(element) + copy.Elem().Set(copyElement) + return copy +} + +func deepCopyStruct(original reflect.Value) reflect.Value { + copy := reflect.New(original.Type()).Elem() + copy.Set(original) + for i := 0; i < original.NumField(); i++ { + fieldValue := copy.Field(i) + fieldValue = reflect.NewAt(fieldValue.Type(), unsafe.Pointer(fieldValue.UnsafeAddr())).Elem() + fieldValue.Set(deepCopy(fieldValue)) + } + return copy +} + +func deepCopyChan(original reflect.Value) reflect.Value { + return reflect.MakeChan(original.Type(), original.Cap()) +} + +// forceCopyValue simply creates a new pointer and sets its value to the original. +func forceCopyValue(original reflect.Value) reflect.Value { + originalType := original.Type() + newPointer := reflect.New(originalType) + newPointer.Elem().Set(original) + return newPointer.Elem() +} -- Gitee From 77cd69ec0bdeade0d56a4e5216fdfd81ca6efdb3 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 23 May 2022 11:30:04 +0800 Subject: [PATCH 2365/2647] fix panic when job not found (#1518) Signed-off-by: dianqihanwangzi --- pkg/microservice/warpdrive/core/service/taskplugin/job.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 9aab26a5..040ef70f 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -825,11 +825,15 @@ func waitJobReady(ctx context.Context, namespace, jobName string, kubeClient cli default: time.Sleep(time.Second) - job, _, err := getter.GetJob(namespace, jobName, kubeClient) + job, found, err := getter.GetJob(namespace, jobName, kubeClient) if err != nil { xl.Errorf("Failed to get job `%s` in namespace `%s`: %s", jobName, namespace, err) continue } + if !found { + xl.Errorf("Job `%s` not found in namespace `%s`, retry in a second", jobName, namespace) + continue + } podLabels := labels.Set{ jobLabelPTypeKey: job.Labels[jobLabelPTypeKey], -- Gitee From ff867af6f13ba0e2467f89b4e36c9527b08c179f Mon Sep 17 00:00:00 2001 From: Xudong Zhang Date: Mon, 23 May 2022 11:31:45 +0800 Subject: [PATCH 2366/2647] reaper: upload dir to s3 recursively (#1517) Signed-off-by: Xudong Zhang --- .../reaper/core/service/reaper/reaper.go | 19 +++++------------ pkg/tool/s3/client.go | 21 +++++++++++++++++-- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 1785ee82..230ce66a 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -462,26 +462,17 @@ func (r *Reaper) AfterExec() error { } // if the given path is a directory if info.IsDir() { - // we get ALL files in this directory and upload it to the object storage - files, err := ioutil.ReadDir(upload.FilePath) + err := client.UploadDir(r.Ctx.UploadStorageInfo.Bucket, upload.FilePath, upload.DestinationPath) if err != nil { - return fmt.Errorf("failed to read file information in directory: [%s], the error is: %s", upload.FilePath, err) - } - for _, file := range files { - if !file.IsDir() { - key := filepath.Join(upload.DestinationPath, file.Name()) - originalFilePath := filepath.Join(upload.FilePath, file.Name()) - err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, originalFilePath, key) - if err != nil { - fmt.Printf("Failed to upload [%s] to key [%s] on s3, the error is: %s", originalFilePath, key, err) - } - } + log.Errorf("Failed to upload dir [%s] to path [%s] on s3, the error is: %s", upload.FilePath, upload.DestinationPath, err) + return err } } else { key := filepath.Join(upload.DestinationPath, info.Name()) err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, upload.FilePath, key) if err != nil { - fmt.Printf("Failed to upload [%s] to key [%s] on s3, the error is: %s", upload.FilePath, key, err) + log.Errorf("Failed to upload [%s] to key [%s] on s3, the error is: %s", upload.FilePath, key, err) + return err } } } diff --git a/pkg/tool/s3/client.go b/pkg/tool/s3/client.go index b2273260..44b8da8e 100644 --- a/pkg/tool/s3/client.go +++ b/pkg/tool/s3/client.go @@ -18,6 +18,7 @@ package s3 import ( "fmt" + "io/fs" "mime" "os" "path/filepath" @@ -29,7 +30,7 @@ import ( "github.com/aws/aws-sdk-go/service/s3" "github.com/koderover/zadig/pkg/tool/log" - "github.com/koderover/zadig/pkg/util/fs" + fsutil "github.com/koderover/zadig/pkg/util/fs" ) const ( @@ -110,7 +111,7 @@ func (c *Client) download(bucketName, objectKey, dest string, option *DownloadOp retry++ continue } - err = fs.SaveFile(obj.Body, dest) + err = fsutil.SaveFile(obj.Body, dest) if err != nil { log.Errorf("Failed to save file to %s, err: %s", dest, err) } @@ -221,6 +222,22 @@ func (c *Client) Upload(bucketName, src string, objectKey string) error { return err } +// Upload upload all files in a directory to a S3 path recursively +func (c *Client) UploadDir(bucketName, srcdir string, s3dir string) error { + err := fs.WalkDir(os.DirFS(srcdir), ".", func(p string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + if d.IsDir() { + return nil + } + key := filepath.Join(s3dir, p) + originalFilePath := filepath.Join(srcdir, p) + return c.Upload(bucketName, originalFilePath, key) + }) + return err +} + func (c *Client) GetFile(bucketName, objectKey string, option *DownloadOption) (*s3.GetObjectOutput, error) { retry := 0 var err error -- Gitee From 35a5d0784429c5a89e596da45ce82834b33d4da5 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 23 May 2022 20:29:45 +0800 Subject: [PATCH 2367/2647] fix webhook remove fail bug (#1519) * fix webhook remove fail bug Signed-off-by: allenshen * optimize cide Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- .../aslan/core/common/service/webhook/controller.go | 2 +- .../aslan/core/common/service/workflow.go | 2 +- .../systemconfig/core/codehost/handler/codehost.go | 13 ++++++++++++- .../core/codehost/repository/mongodb/codehost.go | 7 +++++-- .../systemconfig/core/codehost/service/codehost.go | 8 ++++---- pkg/shared/client/systemconfig/codehost.go | 12 ++++++++++++ 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index ab0d3d1b..d4f451de 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -137,7 +137,7 @@ func ensureSafeDelete(ref, repoName, repoOwner, repoAddress string) (bool, error if service.RepoName != repoName || service.RepoOwner != repoOwner { continue } - codeHostInfo, err := codehostdb.NewCodehostColl().GetCodeHostByID(service.CodehostID) + codeHostInfo, err := codehostdb.NewCodehostColl().GetCodeHostByID(service.CodehostID, false) if err == nil { if codeHostInfo.Address != repoAddress { continue diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index ca8537dc..ecf88fbe 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -208,7 +208,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger wg.Add(1) go func(wh hookItem) { defer wg.Done() - ch, err := systemconfig.New().GetCodeHost(wh.codeHostID) + ch, err := systemconfig.New().GetRawCodeHost(wh.codeHostID) if err != nil { logger.Errorf("Failed to get codeHost by id %d, err: %s", wh.codeHostID, err) errs = multierror.Append(errs, err) diff --git a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go index 84887453..20cf7643 100644 --- a/pkg/microservice/systemconfig/core/codehost/handler/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/handler/codehost.go @@ -17,6 +17,7 @@ limitations under the License. package handler import ( + "fmt" "net/http" "strconv" @@ -77,7 +78,17 @@ func GetCodeHost(c *gin.Context) { ctx.Err = err return } - ctx.Resp, ctx.Err = service.GetCodeHost(id, ctx.Logger) + + ignoreDelete := false + if len(c.Query("ignoreDelete")) > 0 { + ignoreDelete, err = strconv.ParseBool(c.Query("ignoreDelete")) + if err != nil { + ctx.Err = fmt.Errorf("failed to parse param ignoreDelete, err: %s", err) + return + } + } + + ctx.Resp, ctx.Err = service.GetCodeHost(id, ignoreDelete, ctx.Logger) } func AuthCodeHost(c *gin.Context) { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 0813177e..2953d57b 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -80,10 +80,13 @@ func (c *CodehostColl) DeleteCodeHost() error { return nil } -func (c *CodehostColl) GetCodeHostByID(ID int) (*models.CodeHost, error) { +func (c *CodehostColl) GetCodeHostByID(ID int, ignoreDelete bool) (*models.CodeHost, error) { codehost := new(models.CodeHost) - query := bson.M{"id": ID, "deleted_at": 0} + query := bson.M{"id": ID} + if !ignoreDelete { + query["deleted_at"] = 0 + } if err := c.Collection.FindOne(context.TODO(), query).Decode(codehost); err != nil { return nil, err } diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index dd4859f2..5cba0dfb 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -139,8 +139,8 @@ func UpdateCodeHostByToken(host *models.CodeHost, _ *zap.SugaredLogger) (*models return mongodb.NewCodehostColl().UpdateCodeHostByToken(host) } -func GetCodeHost(id int, _ *zap.SugaredLogger) (*models.CodeHost, error) { - return mongodb.NewCodehostColl().GetCodeHostByID(id) +func GetCodeHost(id int, ignoreDelete bool, _ *zap.SugaredLogger) (*models.CodeHost, error) { + return mongodb.NewCodehostColl().GetCodeHostByID(id, ignoreDelete) } type state struct { @@ -149,7 +149,7 @@ type state struct { } func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (string, error) { - codeHost, err := GetCodeHost(codeHostID, logger) + codeHost, err := GetCodeHost(codeHostID, false, logger) if err != nil { logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) return "", err @@ -195,7 +195,7 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) logger.Errorf("ParseURL:%s err:%s", sta.RedirectURL, err) return "", err } - codehost, err := GetCodeHost(sta.CodeHostID, logger) + codehost, err := GetCodeHost(sta.CodeHostID, false, logger) if err != nil { return handle(redirectParsedURL, err) } diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 430abe6e..d51ea20b 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -76,6 +76,18 @@ func (c *Client) GetCodeHost(id int) (*CodeHost, error) { return res, nil } +func (c *Client) GetRawCodeHost(id int) (*CodeHost, error) { + url := fmt.Sprintf("/codehosts/%d?ignoreDelete=true", id) + + res := &CodeHost{} + _, err := c.Get(url, httpclient.SetResult(res)) + if err != nil { + return nil, err + } + + return res, nil +} + func (c *Client) ListCodeHostsInternal() ([]*CodeHost, error) { url := "/codehosts/internal" -- Gitee From 43ecb7f233082ad26518060c34829139a694c0a6 Mon Sep 17 00:00:00 2001 From: dyliu Date: Mon, 23 May 2022 21:20:38 +0800 Subject: [PATCH 2368/2647] Code integration to add aliases (#1520) Signed-off-by: liu deyi --- go.mod | 1 - pkg/cli/upgradeassistant/cmd/migrate/1120.go | 79 +++++++++++++++++++ .../internal/repository/models/codehost.go | 15 ++-- .../codehost/repository/mongodb/codehost.go | 11 ++- .../core/codehost/service/codehost.go | 18 +++++ 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index da0e5aad..090e0d56 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,6 @@ require ( github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/gogo/protobuf v1.3.2 github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/google/go-github v17.0.0+incompatible github.com/google/go-github/v35 v35.3.0 github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1120.go b/pkg/cli/upgradeassistant/cmd/migrate/1120.go index 018328e9..778bf20f 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/1120.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/1120.go @@ -56,6 +56,11 @@ func V1110ToV1120() error { return err } + if err := supplementCodehostAlias(); err != nil { + log.Errorf("supplement codehost alias, err:%s", err) + return err + } + return nil } @@ -75,6 +80,11 @@ func V1120ToV1110() error { log.Errorf("buildAddJenkinsIDRollBack err:%s", err) return err } + + if err := supplementCodehostAliasRollBack(); err != nil { + log.Errorf("supplement codehost alias rollback, err:%s", err) + return err + } return nil } @@ -326,4 +336,73 @@ func buildAddJenkinsIDRollBack() error { _, err = internalmongodb.NewBuildColl().BulkWrite(context.TODO(), ms) } return err + +} +func supplementCodehostAlias() error { + codeHosts, err := internalmongodb.NewCodehostColl().List() + if err != nil { + return fmt.Errorf("failed to list all codehosts ,err: %s", err) + } + var ms []mongo.WriteModel + for _, codehost := range codeHosts { + if codehost.Alias != "" { + continue + } + var alias string + switch codehost.Type { + case "gitlab": + alias = codehost.AccessKey[0:8] + case "github": + alias = codehost.Namespace + case "gerrit": + alias = codehost.Username + case "gitee": + alias = codehost.Namespace + } + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"id", codehost.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"alias", alias}, + }}, + }), + ) + } + + if len(ms) > 0 { + _, err = internalmongodb.NewCodehostColl().BulkWrite(context.TODO(), ms) + } + return err +} + +func supplementCodehostAliasRollBack() error { + codeHosts, err := internalmongodb.NewCodehostColl().List() + if err != nil { + return fmt.Errorf("failed to list all codehosts ,err: %s", err) + } + var ms []mongo.WriteModel + for _, codehost := range codeHosts { + if codehost.Alias == "" { + continue + } + if codehost.Type == "other" { + continue + } + + ms = append(ms, + mongo.NewUpdateOneModel(). + SetFilter(bson.D{{"id", codehost.ID}}). + SetUpdate(bson.D{{"$set", + bson.D{ + {"alias", ""}, + }}, + }), + ) + } + + if len(ms) > 0 { + _, err = internalmongodb.NewCodehostColl().BulkWrite(context.TODO(), ms) + } + return err } diff --git a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go index 58dafa2c..a712d145 100644 --- a/pkg/cli/upgradeassistant/internal/repository/models/codehost.go +++ b/pkg/cli/upgradeassistant/internal/repository/models/codehost.go @@ -17,12 +17,15 @@ limitations under the License. package models type CodeHost struct { - ID int `bson:"id" json:"id"` - Type string `bson:"type" json:"type"` - Address string `bson:"address" json:"address"` - Namespace string `bson:"namespace" json:"namespace"` - AccessToken string `bson:"access_token" json:"accessToken"` - EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + ID int `bson:"id" json:"id"` + Type string `bson:"type" json:"type"` + Address string `bson:"address" json:"address"` + Namespace string `bson:"namespace" json:"namespace"` + AccessToken string `bson:"access_token" json:"accessToken"` + EnableProxy bool `bson:"enable_proxy" json:"enable_proxy"` + Alias string `bson:"alias" json:"alias"` + Username string `bson:"username" json:"username"` + AccessKey string `bson:"application_id" json:"application_id"` } func (CodeHost) TableName() string { diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 2953d57b..3d5e3e1d 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -80,8 +80,16 @@ func (c *CodehostColl) DeleteCodeHost() error { return nil } -func (c *CodehostColl) GetCodeHostByID(ID int, ignoreDelete bool) (*models.CodeHost, error) { +func (c *CodehostColl) GetCodeHostByAlias(alias string) (*models.CodeHost, error) { + codehost := new(models.CodeHost) + query := bson.M{"alias": alias, "deleted_at": 0} + if err := c.Collection.FindOne(context.TODO(), query).Decode(codehost); err != nil { + return nil, err + } + return codehost, nil +} +func (c *CodehostColl) GetCodeHostByID(ID int, ignoreDelete bool) (*models.CodeHost, error) { codehost := new(models.CodeHost) query := bson.M{"id": ID} if !ignoreDelete { @@ -123,7 +131,6 @@ func (c *CodehostColl) List(args *ListArgs) ([]*models.CodeHost, error) { func (c *CodehostColl) CodeHostList() ([]*models.CodeHost, error) { codeHosts := make([]*models.CodeHost, 0) - cursor, err := c.Collection.Find(context.TODO(), bson.M{}) if err != nil { return nil, err diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index 5cba0dfb..d53b5dca 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -49,6 +49,12 @@ func CreateCodeHost(codehost *models.CodeHost, _ *zap.SugaredLogger) (*models.Co codehost.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", codehost.Username, codehost.Password))) } + if codehost.Alias != "" { + if _, err := mongodb.NewCodehostColl().GetCodeHostByAlias(codehost.Alias); err == nil { + return nil, fmt.Errorf("alias cannot have the same name") + } + } + codehost.CreatedAt = time.Now().Unix() codehost.UpdatedAt = time.Now().Unix() @@ -132,6 +138,18 @@ func UpdateCodeHost(host *models.CodeHost, _ *zap.SugaredLogger) (*models.CodeHo if host.Type == setting.SourceFromGerrit { host.AccessToken = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", host.Username, host.Password))) } + + var oldAlias string + oldCodeHost, err := mongodb.NewCodehostColl().GetCodeHostByID(host.ID, false) + if err == nil { + oldAlias = oldCodeHost.Alias + } + if host.Alias != "" && host.Alias != oldAlias { + if _, err := mongodb.NewCodehostColl().GetCodeHostByAlias(host.Alias); err == nil { + return nil, fmt.Errorf("alias cannot have the same name") + } + } + return mongodb.NewCodehostColl().UpdateCodeHost(host) } -- Gitee From fae275f9329a91fb523ce939598d38a464fff40b Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 24 May 2022 10:36:12 +0800 Subject: [PATCH 2369/2647] permission (#1522) * permission Signed-off-by: mouuii * permission Signed-off-by: mouuii --- .../core/service/policy_meta_registration.go | 13 ++++- .../policy/core/yamlconfig/metas.yaml | 56 ++++++++++++++++++- pkg/setting/consts.go | 3 +- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/policy/core/service/policy_meta_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go index d0940c04..77e2944f 100644 --- a/pkg/microservice/policy/core/service/policy_meta_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -78,7 +78,7 @@ func GetPolicyRegistrationDefinitions(scope, envType string, _ *zap.SugaredLogge return nil, err } systemScopeSet := sets.NewString("TestCenter", "DataCenter", "Template", "DeliveryCenter") - projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build", "Service") + projectScopeSet := sets.NewString("Workflow", "Environment", "ProductionEnvironment", "Test", "Delivery", "Build", "Service", "Scan") systemPolicyMetas, projectPolicyMetas, filteredPolicyMetas := []*models.PolicyMeta{}, []*models.PolicyMeta{}, []*models.PolicyMeta{} for _, v := range policieMetas { if systemScopeSet.Has(v.Resource) { @@ -92,6 +92,17 @@ func GetPolicyRegistrationDefinitions(scope, envType string, _ *zap.SugaredLogge case string(types.SystemScope): filteredPolicyMetas = systemPolicyMetas case string(types.ProjectScope): + tmp := []*models.PolicyMeta{} + for _, meta := range projectPolicyMetas { + if envType == setting.PMDeployType && (meta.Resource == "ProductionEnvironment" || meta.Resource == "Delivery") { + continue + } + if envType == setting.TrusteeshipDeployType && meta.Resource == "Delivery" { + continue + } + tmp = append(tmp, meta) + } + projectPolicyMetas = tmp for i, meta := range projectPolicyMetas { if meta.Resource == "Environment" || meta.Resource == "ProductionEnvironment" { tmpRules := []*models.PolicyMetaRule{} diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index 31ac74b5..483d2774 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -47,6 +47,12 @@ metas: alias: 查看 description: '' rules: + - method: GET + endpoint: /api/aslan/workflow/sse/workflows/id/?*/pipelines/?* + - method: GET + endpoint: /api/aslan/workflow/workflowtask/id/?*/pipelines/?* + - method: GET + endpoint: /api/aslan/workflow/workflowtask/max/?*/start/?*/pipelines/?* - method: GET endpoint: /api/aslan/testing/test - method: GET @@ -83,8 +89,12 @@ metas: alias: 执行 description: '' rules: + - method: DELETE + endpoint: api/aslan/testing/testtask/productName/?*/id/?*/pipelines/?* - method: POST endpoint: /api/aslan/testing/testtask + - method: POST + endpoint: /api/aslan/testing/testtask/productName/?*/id/?*/pipelines/?*/restart - resource: Service alias: 服务 description: '' @@ -179,6 +189,10 @@ metas: alias: 新建 description: '' rules: + - method: GET + endpoint: api/aslan/template/build + - method: GET + endpoint: api/aslan/template/dockerfile - method: POST endpoint: /api/aslan/build/build - action: edit_build @@ -205,6 +219,8 @@ metas: alias: 查看 description: '' rules: + - method: GET + endpoint: /api/aslan/workflow/sse/workflows/id/?*/pipelines/?* - method: GET endpoint: /api/aslan/testing/test - method: GET @@ -245,8 +261,6 @@ metas: rules: - method: POST endpoint: /api/aslan/testing/testtask - - method: GET - endpoint: /api/aslan/workflow/sse/workflows/id/?*/pipelines/?* - resource: DataCenter alias: 数据视图 description: '' @@ -562,6 +576,8 @@ metas: alias: 创建 description: '' rules: + - method: GET + endpoint: api/aslan/delivery/releases - method: POST endpoint: /api/aslan/environment/environments resourceType: Cluster @@ -660,3 +676,39 @@ metas: rules: - method: GET endpoint: '/api/aslan/environment/environments/:name/services/?*/pmexec' + - resource: Scan + alias: "代码扫描" + description: "" + rules: + - action: get_sacn + alias: 查看 + description: "" + rules: + - method: GET + endpoint: "api/aslan/testing/scanning" + - action: create_scan + alias: 新建 + description: "" + rules: + - method: POST + endpoint: "/api/aslan/testing/scanning" + - method: GET + endpoint: "/api/aslan/cluster/clusters" + - action: edit_scan + alias: 编辑 + description: "" + rules: + - method: PUT + endpoint: "/api/aslan/testing/scanning/?*" + - action: delete_scan + alias: "删除" + description: "" + rules: + - method: DELETE + endpoint: "/api/aslan/testing/scanning/?*" + - action: run_scan + alias: "执行" + description: '' + rules: + - method: POST + endpoint: "/api/aslan/testing/scanning/?*/task" \ No newline at end of file diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 4e6ae701..57c263d0 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -226,7 +226,8 @@ const ( // helm deployment HelmDeployType = "helm" // PMDeployType physical machine deploy method - PMDeployType = "pm" + PMDeployType = "pm" + TrusteeshipDeployType = "trusteeship" // Infrastructure k8s type BasicFacilityK8S = "kubernetes" -- Gitee From 4bc110591ce5cf5ff7e8e53f8fb9f7cf13476394 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 24 May 2022 15:02:27 +0800 Subject: [PATCH 2370/2647] fix scan typo & miss url (#1526) * fix scan typo Signed-off-by: mouuii * template need cluster url Signed-off-by: mouuii --- pkg/microservice/policy/core/yamlconfig/metas.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index 483d2774..f4dafc65 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -337,6 +337,8 @@ metas: alias: 查看 description: 查看 rules: + - method: GET + endpoint: /api/aslan/cluster/clusters - method: GET endpoint: /api/aslan/template/yaml - method: GET @@ -680,7 +682,7 @@ metas: alias: "代码扫描" description: "" rules: - - action: get_sacn + - action: get_scan alias: 查看 description: "" rules: -- Gitee From fd647e5e219fb39e4c8173ddbde34746aefaa8de Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Tue, 24 May 2022 16:43:51 +0800 Subject: [PATCH 2371/2647] support tag event for github & gitlab (#1510) * tag event support and comment Signed-off-by: Min Min * debug Signed-off-by: Min Min * debugs Signed-off-by: Min Min * stuff Signed-off-by: Min Min * stuff Signed-off-by: Min Min * debug Signed-off-by: Min Min * changed the implementation Signed-off-by: Min Min * fixed panic bug Signed-off-by: Min Min * code review changes Signed-off-by: Min Min * comments Signed-off-by: Min Min --- .../common/repository/models/notification.go | 11 +++ .../common/repository/models/task/model.go | 2 + .../core/common/repository/models/workflow.go | 8 +++ .../core/common/service/notify/client.go | 17 +++++ .../common/service/scmnotify/scmnotify.go | 68 ++++++++++++++++++- .../service/webhook/gerrit_workflow_task.go | 2 +- .../service/webhook/gitee_testing_task.go | 2 +- .../service/webhook/gitee_workflow_task.go | 2 +- .../service/webhook/github_scanning_task.go | 14 +++- .../core/workflow/service/webhook/gitlab.go | 8 +++ .../service/webhook/gitlab_pipeline_task.go | 2 +- .../service/webhook/gitlab_scanning_task.go | 30 +++++++- .../service/webhook/gitlab_testing_task.go | 2 +- .../service/webhook/gitlab_workflow_task.go | 2 +- .../core/workflow/testing/handler/scanning.go | 2 +- .../core/workflow/testing/service/scanning.go | 16 ++++- .../core/workflow/testing/service/types.go | 1 + .../core/service/taskplugin/scanning.go | 1 + 18 files changed, 178 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/notification.go b/pkg/microservice/aslan/core/common/repository/models/notification.go index 6ddd63e5..75f7a59c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/notification.go +++ b/pkg/microservice/aslan/core/common/repository/models/notification.go @@ -37,6 +37,7 @@ type Notification struct { BaseURI string `bson:"base_uri" json:"base_uri"` IsPipeline bool `bson:"is_pipeline" json:"is_pipeline"` IsTest bool `bson:"is_test" json:"is_test"` + IsScanning bool `bson:"is_scanning" json:"is_scanning"` ErrInfo string `bson:"err_info" json:"err_info"` PrTask *PrTaskInfo `bson:"pr_task_info,omitempty" json:"pr_task_info,omitempty"` Label string `bson:"label" json:"label" ` @@ -56,6 +57,8 @@ type NotificationTask struct { ProductName string `bson:"product_name" json:"product_name"` WorkflowName string `bson:"workflow_name" json:"workflow_name"` PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + ScanningName string `bson:"scanning_name" json:"scanningName"` + ScanningID string `bson:"scanning_id" json:"scanning_id"` TestName string `bson:"test_name" json:"test_name"` ID int64 `bson:"id" json:"id"` Status config.TaskStatus `bson:"status" json:"status"` @@ -123,6 +126,14 @@ func (n *Notification) CreateCommentBody() (comment string, err error) { tmplSource = "|触发的测试|状态|测试结果(成功数/总用例数量)| \n |---|---|---| \n {{range .Tasks}}|[{{.TestName}}#{{.ID}}]({{$.BaseURI}}/v1/projects/detail/{{.ProductName}}/test/detail/function/{{.TestName}}/{{.ID}}) | {{if eq .StatusVerbose $.Success}} {+ {{.StatusVerbose}} +}{{else}}{- {{.StatusVerbose}} -}{{end}} | {{range .TestReports}}{{.Name}}: {{.Successes}}/{{.Tests}}
    {{end}} | \n {{end}}" } + } else if n.IsScanning { + if len(n.Tasks) == 0 { + tmplSource = "触发的代码扫描:等待任务启动中" + } else { + tmplSource = + "|触发的代码扫描|状态| \n |---|---| \n {{range .Tasks}}|[{{.ScanningName}}#{{.ID}}]({{$.BaseURI}}/v1/projects/detail/{{.ProductName}}/scanner/detail/{{.ScanningName}}/task/{{.ID}}?id={{.ScanningID}}) | {{if eq .StatusVerbose $.Success}} {+ {{.StatusVerbose}} +}{{else}}{- {{.StatusVerbose}} -}{{end}} | \n {{end}}" + + } } else { if len(n.Tasks) == 0 { tmplSource = "触发的工作流:等待任务启动中" diff --git a/pkg/microservice/aslan/core/common/repository/models/task/model.go b/pkg/microservice/aslan/core/common/repository/models/task/model.go index 76609100..3131ba48 100644 --- a/pkg/microservice/aslan/core/common/repository/models/task/model.go +++ b/pkg/microservice/aslan/core/common/repository/models/task/model.go @@ -65,6 +65,8 @@ type Task struct { WorkflowArgs *models.WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args,omitempty"` // TestArgs 测试任务参数 TestArgs *models.TestTaskArgs `bson:"test_args,omitempty" json:"test_args,omitempty"` + // ScanningArgs argument for scanning tasks + ScanningArgs *models.ScanningArgs `bson:"scanning_args,omitempty" json:"scanning_args,omitempty"` // ServiceTaskArgs 脚本部署工作流任务参数 ServiceTaskArgs *models.ServiceTaskArgs `bson:"service_args,omitempty" json:"service_args,omitempty"` // ArtifactPackageTaskArgs arguments for artifact-package type tasks diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 6088211c..44136ac1 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -189,6 +189,14 @@ type WorkflowTaskArgs struct { ReleaseImages []*ReleaseImage `bson:"release_images,omitempty" json:"release_images,omitempty"` } +type ScanningArgs struct { + ScanningName string `json:"scanning_name" bson:"scanning_name"` + ScanningID string `json:"scanning_id" bson:"scanning_id"` + + // NotificationID is the id of scmnotify.Notification + NotificationID string `bson:"notification_id" json:"notification_id"` +} + type ReleaseImage struct { Image string `bson:"image" json:"image"` ServiceName string `bson:"service_name" json:"service_name"` diff --git a/pkg/microservice/aslan/core/common/service/notify/client.go b/pkg/microservice/aslan/core/common/service/notify/client.go index b07010b9..0f40a2d9 100644 --- a/pkg/microservice/aslan/core/common/service/notify/client.go +++ b/pkg/microservice/aslan/core/common/service/notify/client.go @@ -233,6 +233,23 @@ func (c *client) ProccessNotify(notify *models.Notify) error { } } task.Stages = ctx.Stages + } else if ctx.Type == config.ScanningType { + logger.Infof("scanning get task #%d notify, status: %s", ctx.TaskID, ctx.Status) + err = c.scmNotifyService.UpdateWebhookCommentForScanning(task, logger) + if err != nil { + // FIXME: This error will not be returned since the logic above didn't return. It is expected to return if we know what we are doing. + log.Errorf("Failed to update webhook comment for scanning: %s, err: %s", task.ScanningArgs.ScanningID, err) + } + if ctx.TaskID > 1 { + scanningPreTask, err := c.taskColl.Find(ctx.TaskID-1, ctx.PipelineName, ctx.Type) + if err != nil { + return fmt.Errorf("get test previous task #%d notify, status: %s,err:%s", ctx.TaskID-1, ctx.Status, err) + } + if scanningPreTask.Status != task.Status && task.Status != config.StatusRunning { + testTaskStatusChanged = true + } + } + task.Stages = ctx.Stages } err = c.InstantmessageService.SendInstantMessage(task, testTaskStatusChanged) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index adad1b1e..04cd2974 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -51,7 +51,7 @@ func NewService() *Service { } func (s *Service) SendInitWebhookComment( - mainRepo *models.MainHookRepo, prID int, baseURI string, isPipeline, isTest bool, logger *zap.SugaredLogger, + mainRepo *models.MainHookRepo, prID int, baseURI string, isPipeline, isTest, isScanning bool, logger *zap.SugaredLogger, ) (*models.Notification, error) { notification := &models.Notification{ CodehostID: mainRepo.CodehostID, @@ -60,6 +60,7 @@ func (s *Service) SendInitWebhookComment( BaseURI: baseURI, IsPipeline: isPipeline, IsTest: isTest, + IsScanning: isScanning, Label: mainRepo.GetLabelValue(), Revision: mainRepo.Revision, RepoOwner: mainRepo.RepoOwner, @@ -408,6 +409,71 @@ func (s *Service) UpdateWebhookCommentForTest(task *task.Task, logger *zap.Sugar return nil } +func (s *Service) UpdateWebhookCommentForScanning(task *task.Task, logger *zap.SugaredLogger) (err error) { + if task.ScanningArgs.NotificationID == "" { + return + } + + var notification *models.Notification + if notification, err = s.Coll.Find(task.ScanningArgs.NotificationID); err != nil { + logger.Errorf("can't find notification by id %s %s", task.TestArgs.NotificationID, err) + return err + } + + var tasks []*models.NotificationTask + var taskExist bool + var shouldComment bool + + status := convertTaskStatusToNotificationTaskStatus(task.Status) + for _, nTask := range notification.Tasks { + if nTask.ID == task.TaskID { + shouldComment = nTask.Status != status + scmTask := &models.NotificationTask{ + ProductName: task.ProductName, + TestName: task.PipelineName, + ID: task.TaskID, + ScanningName: task.ScanningArgs.ScanningName, + ScanningID: task.ScanningArgs.ScanningID, + Status: status, + } + + tasks = append(tasks, scmTask) + taskExist = true + } else { + tasks = append(tasks, nTask) + } + } + + if !taskExist { + tasks = append(tasks, &models.NotificationTask{ + ProductName: task.ProductName, + TestName: task.PipelineName, + ID: task.TaskID, + ScanningName: task.ScanningArgs.ScanningName, + ScanningID: task.ScanningArgs.ScanningID, + Status: status, + }) + shouldComment = true + } + + if shouldComment { + notification.Tasks = tasks + if err = s.Client.Comment(notification); err != nil { + // cannot return error here since the upsert operation is required for further use. + logger.Warnf("failed to comment %s, %v", notification.ToString(), err) + } + + if err = s.Coll.Upsert(notification); err != nil { + logger.Warnf("can't upsert notification by id %s", notification.ID) + return + } + } else { + logger.Infof("status not changed of task %s %d, skip to update comment", task.PipelineName, task.TaskID) + } + + return nil +} + func (s *Service) UpdatePipelineWebhookComment(task *task.Task, logger *zap.SugaredLogger) (err error) { if task.TaskArgs == nil { logger.Warnf("taskArgs of %s is nil", task.PipelineName) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index ff21e617..eb35f164 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -367,7 +367,7 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base mainRepo.RepoOwner = "" mainRepo.Revision = m.Event.PatchSet.Revision notification, _ = scmnotify.NewService().SendInitWebhookComment( - mainRepo, m.Event.Change.Number, baseURI, false, false, log, + mainRepo, m.Event.Change.Number, baseURI, false, false, false, log, ) } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go index 4bdf8f68..11d27568 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_testing_task.go @@ -252,7 +252,7 @@ func TriggerTestByGiteeEvent(event interface{}, baseURI, requestID string, log * if notification == nil { notification, err = scmnotify.NewService().SendInitWebhookComment( - item.MainRepo, ev.PullRequest.Number, baseURI, false, true, log, + item.MainRepo, ev.PullRequest.Number, baseURI, false, true, false, log, ) if err != nil { log.Errorf("failed to init webhook comment due to %s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go index 98ee9b82..ca073b73 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee_workflow_task.go @@ -291,7 +291,7 @@ func TriggerWorkflowByGiteeEvent(event interface{}, baseURI, requestID string, l if notification == nil { notification, err = scmnotify.NewService().SendInitWebhookComment( - item.MainRepo, ev.PullRequest.Number, baseURI, false, false, log, + item.MainRepo, ev.PullRequest.Number, baseURI, false, false, false, log, ) if err != nil { log.Errorf("failed to init webhook comment due to %s", err) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go index 26cd9853..7eb567ed 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go @@ -84,7 +84,7 @@ func TriggerScanningByGithubEvent(event interface{}, requestID string, log *zap. triggerRepoInfo = append(triggerRepoInfo, repoInfo) - if resp, err := scanningservice.CreateScanningTask(scanning.ID.Hex(), triggerRepoInfo, "webhook", log); err != nil { + if resp, err := scanningservice.CreateScanningTask(scanning.ID.Hex(), triggerRepoInfo, "", "webhook", log); err != nil { log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) mErr = multierror.Append(mErr, err) } else { @@ -175,6 +175,18 @@ type githubTagEventMatcherForScanning struct { } func (gtem githubTagEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { + hookInfo := ConvertScanningHookToMainHookRepo(hookRepo) + if !EventConfigured(hookInfo, config.HookEventTag) { + return false, nil + } + + hookRepo.Branch = *ev.Repo.DefaultBranch + + return true, nil + } + return false, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index e4b88c5b..09b7ee60 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -206,6 +206,14 @@ func ProcessGitlabHook(payload []byte, req *http.Request, requestID string, log errorList = multierror.Append(errorList, err) } }() + + wg.Add(1) + go func() { + defer wg.Done() + if err = TriggerScanningByGitlabEvent(tagEvent, baseURI, requestID, log); err != nil { + errorList = multierror.Append(errorList, err) + } + }() } wg.Wait() diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go index ec5ec801..1187f224 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go @@ -226,7 +226,7 @@ func TriggerPipelineByGitlabEvent(event interface{}, baseURI, requestID string, hookRepo.RepoOwner = taskargs.HookPayload.Owner if notification == nil { notification, _ = scmnotify.NewService().SendInitWebhookComment( - hookRepo, taskargs.Builds[0].PR, baseURI, true, false, log, + hookRepo, taskargs.Builds[0].PR, baseURI, true, false, false, log, ) } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go index f5f6afe3..bc38cd0d 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go @@ -17,13 +17,14 @@ limitations under the License. package webhook import ( - "github.com/hashicorp/go-multierror" "github.com/xanzy/go-gitlab" "go.uber.org/zap" + "github.com/hashicorp/go-multierror" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" scanningservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" "github.com/koderover/zadig/pkg/types" ) @@ -41,6 +42,8 @@ func TriggerScanningByGitlabEvent(event interface{}, baseURI, requestID string, return findChangedFilesOfMergeRequest(mergeEvent, codehostId) } + var notification *commonmodels.Notification + for _, scanning := range scanningList { if scanning.AdvancedSetting.HookCtl != nil && scanning.AdvancedSetting.HookCtl.Enabled { log.Infof("find %d hooks in scanning %s", len(scanning.AdvancedSetting.HookCtl.Items), scanning.Name) @@ -60,6 +63,13 @@ func TriggerScanningByGitlabEvent(event interface{}, baseURI, requestID string, // 如果是merge request,且该webhook触发器配置了自动取消, // 则需要确认该merge request在本次commit之前的commit触发的任务是否处理完,没有处理完则取消掉。 mergeRequestID = ev.ObjectAttributes.IID + + if notification == nil { + mainRepo := ConvertScanningHookToMainHookRepo(item) + notification, _ = scmnotify.NewService().SendInitWebhookComment( + mainRepo, ev.ObjectAttributes.IID, baseURI, false, false, true, log, + ) + } } triggerRepoInfo := make([]*scanningservice.ScanningRepoInfo, 0) @@ -75,7 +85,12 @@ func TriggerScanningByGitlabEvent(event interface{}, baseURI, requestID string, repoInfo.PR = mergeRequestID - if resp, err := scanningservice.CreateScanningTask(scanning.ID.Hex(), triggerRepoInfo, "webhook", log); err != nil { + notificationID := "" + if notification != nil { + notificationID = notification.ID.Hex() + } + + if resp, err := scanningservice.CreateScanningTask(scanning.ID.Hex(), triggerRepoInfo, notificationID, "webhook", log); err != nil { log.Errorf("failed to create testing task when receive event %v due to %v ", event, err) mErr = multierror.Append(mErr, err) } else { @@ -195,5 +210,16 @@ type gitlabTagEventMatcherForScanning struct { } func (gtem *gitlabTagEventMatcherForScanning) Match(hookRepo *types.ScanningHook) (bool, error) { + ev := gtem.event + if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + hookInfo := ConvertScanningHookToMainHookRepo(hookRepo) + if !EventConfigured(hookInfo, config.HookEventTag) { + return false, nil + } + + hookRepo.Branch = ev.Project.DefaultBranch + return true, nil + } + return false, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index daa8a368..e29b6322 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -191,7 +191,7 @@ func TriggerTestByGitlabEvent(event interface{}, baseURI, requestID string, log // 发送本次commit的通知 if notification == nil { notification, _ = scmnotify.NewService().SendInitWebhookComment( - item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, true, log, + item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, true, false, log, ) } } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 3ebd85e0..e9ea23aa 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -626,7 +626,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, } if notification == nil { notification, _ = scmnotify.NewService().SendInitWebhookComment( - item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, log, + item.MainRepo, ev.ObjectAttributes.IID, baseURI, false, false, false, log, ) } } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go index 16260abd..91dc1bac 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go @@ -167,7 +167,7 @@ func CreateScanningTask(c *gin.Context) { internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "代码扫描任务", id, string(data), ctx.Logger) - resp, err := service.CreateScanningTask(id, req, ctx.UserName, ctx.Logger) + resp, err := service.CreateScanningTask(id, req, "", ctx.UserName, ctx.Logger) if err != nil { ctx.Err = err return diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go index 466ed0ef..4dfbadb1 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -31,6 +31,7 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/scmnotify" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" @@ -166,7 +167,8 @@ func DeleteScanningModuleByID(id string, log *zap.SugaredLogger) error { return err } -func CreateScanningTask(id string, req []*ScanningRepoInfo, username string, log *zap.SugaredLogger) (int64, error) { +// CreateScanningTask uses notificationID if the task is triggered by webhook, otherwise it should be empty +func CreateScanningTask(id string, req []*ScanningRepoInfo, notificationID, username string, log *zap.SugaredLogger) (int64, error) { scanningInfo, err := commonrepo.NewScanningColl().GetByID(id) if err != nil { log.Errorf("failed to get scanning from mongodb, the error is: %s", err) @@ -206,6 +208,7 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, username string, log RepoName: arg.RepoName, Branch: arg.Branch, PR: arg.PR, + Tag: arg.Tag, CodehostID: arg.CodehostID, OauthToken: rep.AccessToken, Address: rep.Address, @@ -288,6 +291,11 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, username string, log Stages: stages, ConfigPayload: configPayload, StorageURI: defaultURL, + ScanningArgs: &commonmodels.ScanningArgs{ + ScanningName: scanningInfo.Name, + ScanningID: scanningInfo.ID.Hex(), + NotificationID: notificationID, + }, } if len(finalTask.Stages) <= 0 { @@ -299,6 +307,12 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, username string, log return 0, e.ErrCreateTask } + // Updating the comment in the git repository, this will not cause the function to return error if this function call fails + err = scmnotify.NewService().UpdateWebhookCommentForScanning(finalTask, log) + if err != nil { + log.Warnf("Failed to update comment for scanning: %s, the error is: %s", scanningInfo.ID.Hex(), err) + } + return nextTaskID, nil } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/types.go b/pkg/microservice/aslan/core/workflow/testing/service/types.go index f08604d5..3d464d91 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/types.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/types.go @@ -54,6 +54,7 @@ type ScanningRepoInfo struct { RepoName string `json:"repo_name"` PR int `json:"pr"` Branch string `json:"branch"` + Tag string `json:"tag"` } type ScanningStatistic struct { diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go b/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go index 1eae65dd..259b82c1 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go @@ -120,6 +120,7 @@ func (p *ScanPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC RepoName: p.Task.Repos[0].RepoName, Branch: p.Task.Repos[0].Branch, PR: p.Task.Repos[0].PR, + Tag: p.Task.Repos[0].Tag, OauthToken: p.Task.Repos[0].OauthToken, Address: p.Task.Repos[0].Address, Username: p.Task.Repos[0].Username, -- Gitee From 9bde40032b1f620b1b9edd4f47090ff48581f2fa Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 24 May 2022 17:42:50 +0800 Subject: [PATCH 2372/2647] remove embed file (#1528) Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/init.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 7b113ff0..e60ee395 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -42,21 +42,6 @@ func init() { }) } -//go:embed contributor.yaml -var contributor []byte - -//go:embed read-only.yaml -var readOnly []byte - -//go:embed read-project-only.yaml -var readProjectOnly []byte - -//go:embed admin.yaml -var admin []byte - -//go:embed project-admin.yaml -var projectAdmin []byte - var initCmd = &cobra.Command{ Use: "init", Short: "init system config", -- Gitee From 0eed4a0aa7ba3857fc9c85080963ab76494782f7 Mon Sep 17 00:00:00 2001 From: panxunying Date: Tue, 24 May 2022 19:06:33 +0800 Subject: [PATCH 2373/2647] rollback init config Signed-off-by: panxunying --- pkg/cli/initconfig/cmd/admin.yaml | 8 +++ pkg/cli/initconfig/cmd/contributor.yaml | 32 ++++++++++++ pkg/cli/initconfig/cmd/init.go | 50 +++++++++++++++++++ pkg/cli/initconfig/cmd/project-admin.yaml | 7 +++ pkg/cli/initconfig/cmd/read-only.yaml | 38 ++++++++++++++ pkg/cli/initconfig/cmd/read-project-only.yaml | 40 +++++++++++++++ 6 files changed, 175 insertions(+) create mode 100644 pkg/cli/initconfig/cmd/admin.yaml create mode 100644 pkg/cli/initconfig/cmd/contributor.yaml create mode 100644 pkg/cli/initconfig/cmd/project-admin.yaml create mode 100644 pkg/cli/initconfig/cmd/read-only.yaml create mode 100644 pkg/cli/initconfig/cmd/read-project-only.yaml diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml new file mode 100644 index 00000000..03bf5c61 --- /dev/null +++ b/pkg/cli/initconfig/cmd/admin.yaml @@ -0,0 +1,8 @@ +name: admin +type: system +desc: "拥有系统中任何操作的权限" +rules: + - verbs: + - "*" + resources: + - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml new file mode 100644 index 00000000..c9cfd423 --- /dev/null +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -0,0 +1,32 @@ +name: contributor +type: system +rules: + - verbs: + - get_workflow + - run_workflow + resources: + - Workflow + kind: resource + - verbs: + - get_environment + - config_environment + - manage_environment + - delete_environment + resources: + - Environment + kind: resource + - verbs: + - get_service + resources: + - Service + kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index e60ee395..89cd6842 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -23,6 +23,9 @@ import ( "time" "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" + + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" @@ -69,6 +72,48 @@ func run() error { return err } +//go:embed contributor.yaml +var contributor []byte + +//go:embed read-only.yaml +var readOnly []byte + +//go:embed read-project-only.yaml +var readProjectOnly []byte + +//go:embed admin.yaml +var admin []byte + +//go:embed project-admin.yaml +var projectAdmin []byte + +func presetRole() error { + g := new(errgroup.Group) + g.Go(func() error { + systemAdminRole := &policy.Role{} + if err := yaml.Unmarshal(admin, systemAdminRole); err != nil { + log.DPanic(err) + } + return policy.NewDefault().CreateSystemRole(systemAdminRole.Name, systemAdminRole) + }) + + rolesArray := [][]byte{readOnly, readProjectOnly, contributor, projectAdmin} + + for _, v := range rolesArray { + role := &policy.Role{} + if err := yaml.Unmarshal(v, role); err != nil { + log.DPanic(err) + } + g.Go(func() error { + return policy.NewDefault().CreatePresetRole(role.Name, role) + }) + } + if err := g.Wait(); err != nil { + return err + } + return nil +} + func initSystemConfig() error { email := config.AdminEmail() password := config.AdminPassword() @@ -80,6 +125,11 @@ func initSystemConfig() error { return err } + if err := presetRole(); err != nil { + log.Errorf("presetRole err:%s", err) + return err + } + if err := presetRoleBinding(uid); err != nil { log.Errorf("presetRoleBinding err:%s", err) return err diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml new file mode 100644 index 00000000..39a2908d --- /dev/null +++ b/pkg/cli/initconfig/cmd/project-admin.yaml @@ -0,0 +1,7 @@ +name: project-admin +type: system +rules: + - verbs: + - "*" + resources: + - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml new file mode 100644 index 00000000..0fe0f37b --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -0,0 +1,38 @@ +name: read-only +type: system +rules: + - verbs: + - get_workflow + resources: + - Workflow + kind: resource + - verbs: + - get_environment + resources: + - Environment + kind: resource + - verbs: + - get_environment + resources: + - ProductionEnvironment + kind: resource + - verbs: + - get_service + resources: + - Service + kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource + - verbs: + - get_delivery + resources: + - Delivery + kind: resource \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml new file mode 100644 index 00000000..105bfd1f --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -0,0 +1,40 @@ +name: read-project-only +type: system +namespace: '' +rules: + - verbs: + - get_workflow + resources: + - Workflow + kind: resource + match_attributes: + - key: placeholder + value: placeholder + - verbs: + - get_environment + resources: + - Environment + kind: resource + match_attributes: + - key: placeholder + value: placeholder + - verbs: + - get_service + resources: + - Service + kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource + - verbs: + - get_delivery + resources: + - Delivery + kind: resource \ No newline at end of file -- Gitee From f6b8ead54840263acea6aff50b149a46cdb68f50 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 24 May 2022 19:10:07 +0800 Subject: [PATCH 2374/2647] rollback init config (#1530) Signed-off-by: panxunying Co-authored-by: panxunying --- pkg/cli/initconfig/cmd/admin.yaml | 8 +++ pkg/cli/initconfig/cmd/contributor.yaml | 32 ++++++++++++ pkg/cli/initconfig/cmd/init.go | 50 +++++++++++++++++++ pkg/cli/initconfig/cmd/project-admin.yaml | 7 +++ pkg/cli/initconfig/cmd/read-only.yaml | 38 ++++++++++++++ pkg/cli/initconfig/cmd/read-project-only.yaml | 40 +++++++++++++++ 6 files changed, 175 insertions(+) create mode 100644 pkg/cli/initconfig/cmd/admin.yaml create mode 100644 pkg/cli/initconfig/cmd/contributor.yaml create mode 100644 pkg/cli/initconfig/cmd/project-admin.yaml create mode 100644 pkg/cli/initconfig/cmd/read-only.yaml create mode 100644 pkg/cli/initconfig/cmd/read-project-only.yaml diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml new file mode 100644 index 00000000..03bf5c61 --- /dev/null +++ b/pkg/cli/initconfig/cmd/admin.yaml @@ -0,0 +1,8 @@ +name: admin +type: system +desc: "拥有系统中任何操作的权限" +rules: + - verbs: + - "*" + resources: + - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml new file mode 100644 index 00000000..c9cfd423 --- /dev/null +++ b/pkg/cli/initconfig/cmd/contributor.yaml @@ -0,0 +1,32 @@ +name: contributor +type: system +rules: + - verbs: + - get_workflow + - run_workflow + resources: + - Workflow + kind: resource + - verbs: + - get_environment + - config_environment + - manage_environment + - delete_environment + resources: + - Environment + kind: resource + - verbs: + - get_service + resources: + - Service + kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index e60ee395..89cd6842 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -23,6 +23,9 @@ import ( "time" "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" + + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" @@ -69,6 +72,48 @@ func run() error { return err } +//go:embed contributor.yaml +var contributor []byte + +//go:embed read-only.yaml +var readOnly []byte + +//go:embed read-project-only.yaml +var readProjectOnly []byte + +//go:embed admin.yaml +var admin []byte + +//go:embed project-admin.yaml +var projectAdmin []byte + +func presetRole() error { + g := new(errgroup.Group) + g.Go(func() error { + systemAdminRole := &policy.Role{} + if err := yaml.Unmarshal(admin, systemAdminRole); err != nil { + log.DPanic(err) + } + return policy.NewDefault().CreateSystemRole(systemAdminRole.Name, systemAdminRole) + }) + + rolesArray := [][]byte{readOnly, readProjectOnly, contributor, projectAdmin} + + for _, v := range rolesArray { + role := &policy.Role{} + if err := yaml.Unmarshal(v, role); err != nil { + log.DPanic(err) + } + g.Go(func() error { + return policy.NewDefault().CreatePresetRole(role.Name, role) + }) + } + if err := g.Wait(); err != nil { + return err + } + return nil +} + func initSystemConfig() error { email := config.AdminEmail() password := config.AdminPassword() @@ -80,6 +125,11 @@ func initSystemConfig() error { return err } + if err := presetRole(); err != nil { + log.Errorf("presetRole err:%s", err) + return err + } + if err := presetRoleBinding(uid); err != nil { log.Errorf("presetRoleBinding err:%s", err) return err diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml new file mode 100644 index 00000000..39a2908d --- /dev/null +++ b/pkg/cli/initconfig/cmd/project-admin.yaml @@ -0,0 +1,7 @@ +name: project-admin +type: system +rules: + - verbs: + - "*" + resources: + - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml new file mode 100644 index 00000000..0fe0f37b --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-only.yaml @@ -0,0 +1,38 @@ +name: read-only +type: system +rules: + - verbs: + - get_workflow + resources: + - Workflow + kind: resource + - verbs: + - get_environment + resources: + - Environment + kind: resource + - verbs: + - get_environment + resources: + - ProductionEnvironment + kind: resource + - verbs: + - get_service + resources: + - Service + kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource + - verbs: + - get_delivery + resources: + - Delivery + kind: resource \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml new file mode 100644 index 00000000..105bfd1f --- /dev/null +++ b/pkg/cli/initconfig/cmd/read-project-only.yaml @@ -0,0 +1,40 @@ +name: read-project-only +type: system +namespace: '' +rules: + - verbs: + - get_workflow + resources: + - Workflow + kind: resource + match_attributes: + - key: placeholder + value: placeholder + - verbs: + - get_environment + resources: + - Environment + kind: resource + match_attributes: + - key: placeholder + value: placeholder + - verbs: + - get_service + resources: + - Service + kind: resource + - verbs: + - get_build + resources: + - Build + kind: resource + - verbs: + - get_test + resources: + - Test + kind: resource + - verbs: + - get_delivery + resources: + - Delivery + kind: resource \ No newline at end of file -- Gitee From 27ae79e85e9b85e0e4fb43bf6e5d1baaa026a14c Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 24 May 2022 19:37:28 +0800 Subject: [PATCH 2375/2647] admin add * (#1532) Signed-off-by: mouuii --- pkg/microservice/policy/server/server.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/policy/server/server.go b/pkg/microservice/policy/server/server.go index 2b1440d9..e3a671f3 100644 --- a/pkg/microservice/policy/server/server.go +++ b/pkg/microservice/policy/server/server.go @@ -107,6 +107,9 @@ func migrateRole() error { } for _, role := range config.PresetRoles { + if role.Name == "admin" { + role.Namespace = "*" + } for _, rule := range role.Rules { rule.Kind = "resource" } -- Gitee From 463a7fc20705786f8b38f67abf4280847ed028b6 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 25 May 2022 15:03:09 +0800 Subject: [PATCH 2376/2647] use proxy (#1525) Signed-off-by: mouuii --- .../systemconfig/core/codehost/internal/oauth/oauth.go | 7 +++++-- .../systemconfig/core/codehost/service/codehost.go | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go index d14227a5..f7b17fa4 100644 --- a/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go +++ b/pkg/microservice/systemconfig/core/codehost/internal/oauth/oauth.go @@ -25,6 +25,8 @@ import ( "golang.org/x/oauth2" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/systemconfig/core/codehost/repository/models" + "github.com/koderover/zadig/pkg/tool/log" ) type OAuth struct { @@ -59,7 +61,7 @@ func (o *OAuth) LoginURL(state string) string { return o.oauth2Config.AuthCodeURL(state) } -func (o *OAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { +func (o *OAuth) HandleCallback(r *http.Request, c *models.CodeHost) (*oauth2.Token, error) { q := r.URL.Query() if errType := q.Get("error"); errType != "" { return nil, &OAuth2Error{errType, q.Get("error_description")} @@ -68,7 +70,8 @@ func (o *OAuth) HandleCallback(r *http.Request) (*oauth2.Token, error) { httpClient := http.DefaultClient // if set http proxy proxies, err := commonrepo.NewProxyColl().List(&commonrepo.ProxyArgs{}) - if err == nil && len(proxies) != 0 && proxies[0].EnableRepoProxy { + if err == nil && len(proxies) != 0 && proxies[0].EnableRepoProxy && c.EnableProxy { + log.Info("use proxy") port := proxies[0].Port ip := proxies[0].Address proxyRawUrl := fmt.Sprintf("http://%s:%d", ip, port) diff --git a/pkg/microservice/systemconfig/core/codehost/service/codehost.go b/pkg/microservice/systemconfig/core/codehost/service/codehost.go index d53b5dca..df946195 100644 --- a/pkg/microservice/systemconfig/core/codehost/service/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/service/codehost.go @@ -169,7 +169,7 @@ type state struct { func AuthCodeHost(redirectURI string, codeHostID int, logger *zap.SugaredLogger) (string, error) { codeHost, err := GetCodeHost(codeHostID, false, logger) if err != nil { - logger.Errorf("GetCodeHost:%s err:%s", codeHostID, err) + logger.Errorf("GetCodeHost:%d err:%s", codeHostID, err) return "", err } redirectParsedURL, err := url.Parse(redirectURI) @@ -224,10 +224,12 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) } o, err := newOAuth(codehost.Type, callbackURL.String(), codehost.ApplicationId, codehost.ClientSecret, codehost.Address) if err != nil { + logger.Errorf("newOAuth err:%s", err) return handle(redirectParsedURL, err) } - token, err := o.HandleCallback(r) + token, err := o.HandleCallback(r, codehost) if err != nil { + logger.Errorf("HandleCallback err:%s", err) return handle(redirectParsedURL, err) } codehost.AccessToken = token.AccessToken @@ -236,6 +238,7 @@ func HandleCallback(stateStr string, r *http.Request, logger *zap.SugaredLogger) logger.Errorf("UpdateCodeHostByToken err:%s", err) return handle(redirectParsedURL, err) } + logger.Infof("success update codehost ready status") return handle(redirectParsedURL, nil) } -- Gitee From 369533cb692996ebcaba0e5874dd4f7e277501b0 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 26 May 2022 10:05:43 +0800 Subject: [PATCH 2377/2647] set cron config status success && add timeout (#1536) * status success Signed-off-by: mouuii * remove goroutine Signed-off-by: mouuii * refactor Signed-off-by: mouuii * timeout Signed-off-by: mouuii * timeout Signed-off-by: mouuii --- .../aslan/core/system/service/system_cache.go | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/system/service/system_cache.go b/pkg/microservice/aslan/core/system/service/system_cache.go index 79c15c4a..08c4473e 100644 --- a/pkg/microservice/aslan/core/system/service/system_cache.go +++ b/pkg/microservice/aslan/core/system/service/system_cache.go @@ -17,6 +17,7 @@ limitations under the License. package service import ( + "context" "sort" "strings" "sync" @@ -64,7 +65,7 @@ func SetCron(cron string, cronEnabled bool, logger *zap.SugaredLogger) error { } case 1: dindClean := dindCleans[0] - dindClean.Status = dindCleans[0].Status + dindClean.Status = CleanStatusSuccess dindClean.DindCleanInfos = dindCleans[0].DindCleanInfos dindClean.Cron = cron dindClean.CronEnabled = cronEnabled @@ -110,26 +111,27 @@ func CleanImageCache(logger *zap.SugaredLogger) error { } } - go func() { + namespace := config.Namespace() + selector := labels.Set{setting.ComponentLabel: "dind"}.AsSelector() + pods, err := getter.ListPods(namespace, selector, kubetool.Client()) + if err != nil { + logger.Errorf("[%s]list dind pods error: %v", namespace, err) + return commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ + Status: CleanStatusFailed, + DindCleanInfos: []*commonmodels.DindCleanInfo{}, + Cron: dindCleans[0].Cron, + CronEnabled: dindCleans[0].CronEnabled, + }) + } + + timeout, cancel := context.WithTimeout(context.Background(), 20*time.Minute) + defer cancel() + res := make(chan *commonmodels.DindClean) + go func(ch chan *commonmodels.DindClean) { var ( - namespace = config.Namespace() - status = CleanStatusSuccess + status = CleanStatusSuccess ) - selector := labels.Set{setting.ComponentLabel: "dind"}.AsSelector() - pods, err := getter.ListPods(namespace, selector, kubetool.Client()) - - if err != nil { - logger.Errorf("[%s]list dind pods error: %v", namespace, err) - commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ - Status: CleanStatusFailed, - DindCleanInfos: []*commonmodels.DindCleanInfo{}, - Cron: dindCleans[0].Cron, - CronEnabled: dindCleans[0].CronEnabled, - }) - return - } - dindInfos := make([]*commonmodels.DindCleanInfo, 0, len(pods)) var wg sync.WaitGroup for _, pod := range pods { @@ -162,13 +164,26 @@ func CleanImageCache(logger *zap.SugaredLogger) error { break } } - commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ + res <- &commonmodels.DindClean{ Status: status, DindCleanInfos: dindInfos, Cron: dindCleans[0].Cron, CronEnabled: dindCleans[0].CronEnabled, + } + + }(res) + + select { + case <-timeout.Done(): + commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ + Status: CleanStatusFailed, + DindCleanInfos: []*commonmodels.DindCleanInfo{}, + Cron: dindCleans[0].Cron, + CronEnabled: dindCleans[0].CronEnabled, }) - }() + case info := <-res: + commonrepo.NewDindCleanColl().Upsert(info) + } return nil } -- Gitee From d2183c620f49d5a0269cb097a619ff4cb9274592 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 26 May 2022 15:07:18 +0800 Subject: [PATCH 2378/2647] Import a service from an existing environment (#1521) * add insert service Signed-off-by: panxunying * add insert service Signed-off-by: panxunying * add insert service Signed-off-by: panxunying * add insert service Signed-off-by: panxunying * add insert service Signed-off-by: panxunying * insert service Signed-off-by: panxunying * insert service Signed-off-by: panxunying * insert service Signed-off-by: panxunying * insert service Signed-off-by: panxunying * rollback init config Signed-off-by: panxunying * add policy for insert service Signed-off-by: panxunying * keep annotations Signed-off-by: panxunying * set generation=0 Signed-off-by: panxunying * optimize Signed-off-by: panxunying Co-authored-by: panxunying --- go.mod | 1 + go.sum | 1 + .../service/collaboration_instance.go | 4 +- .../core/environment/handler/environment.go | 7 +- .../core/environment/service/environment.go | 41 ++-- .../aslan/core/environment/service/product.go | 6 +- .../environment/service/share_env_utils.go | 2 +- .../aslan/core/project/handler/product.go | 9 +- .../aslan/core/project/service/product.go | 36 +-- .../aslan/core/service/handler/environment.go | 28 +++ .../aslan/core/service/handler/router.go | 2 + .../aslan/core/service/service/environment.go | 221 ++++++++++++++++++ .../service/webhook/gitlab_workflow_task.go | 2 +- .../policy/core/yamlconfig/metas.yaml | 4 + pkg/tool/kube/getter/base.go | 40 ++++ pkg/tool/kube/getter/configmap.go | 18 ++ pkg/tool/kube/getter/deployment.go | 9 + pkg/tool/kube/getter/ingress.go | 47 ++++ pkg/tool/kube/getter/pvc.go | 18 ++ pkg/tool/kube/getter/secrets.go | 18 ++ pkg/tool/kube/getter/service.go | 18 ++ pkg/tool/kube/getter/statefulset.go | 9 + 22 files changed, 494 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index 090e0d56..fcf8d4b6 100644 --- a/go.mod +++ b/go.mod @@ -95,6 +95,7 @@ require ( k8s.io/apimachinery v0.22.4 k8s.io/client-go v0.22.4 k8s.io/kubectl v0.22.4 + k8s.io/kubernetes v1.13.0 k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.1 sigs.k8s.io/yaml v1.3.0 diff --git a/go.sum b/go.sum index b987b849..a682c89d 100644 --- a/go.sum +++ b/go.sum @@ -2397,6 +2397,7 @@ k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2R k8s.io/kubectl v0.21.0 h1:WZXlnG/yjcE4LWO2g6ULjFxtzK6H1TKzsfaBFuVIhNg= k8s.io/kubectl v0.21.0/go.mod h1:EU37NukZRXn1TpAkMUoy8Z/B2u6wjHDS4aInsDzVvks= k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/kubernetes v1.13.0 h1:qTfB+u5M92k2fCCCVP2iuhgwwSOv1EkAkvQY1tQODD8= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js= k8s.io/metrics v0.21.0/go.mod h1:L3Ji9EGPP1YBbfm9sPfEXSpnj8i24bfQbAFAsW0NueQ= diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index e6cbd88e..b7c4da1a 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -879,7 +879,7 @@ func syncDeleteResource(updateResp *GetCollaborationUpdateResp, username, projec log *zap.SugaredLogger) (err error) { deleteResp := getCollaborationDelete(updateResp) for _, product := range deleteResp.Products { - err := service2.DeleteProduct(username, product, projectName, requestID, log) + err := service2.DeleteProduct(username, product, projectName, requestID, true, log) if err != nil && err != mongo.ErrNoDocuments { log.Errorf("delete product err:%v", err) return err @@ -1293,7 +1293,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn } for _, product := range ci.Products { if product.CollaborationType == config.CollaborationNew { - err = service2.DeleteProduct(userName, product.Name, ci.ProjectName, requestID, logger) + err = service2.DeleteProduct(userName, product.Name, ci.ProjectName, requestID, true, logger) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index d048a906..98da11c0 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -587,8 +587,13 @@ func DeleteProduct(c *gin.Context) { envName := c.Param("name") projectName := c.Query("projectName") + is_delete, err := strconv.ParseBool(c.Query("is_delete")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalidParam is_delete") + return + } internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境", envName, "", ctx.Logger) - ctx.Err = service.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, ctx.Logger) + ctx.Err = service.DeleteProduct(ctx.UserName, envName, projectName, ctx.RequestID, is_delete, ctx.Logger) } func DeleteProductServices(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 598f33b3..1d0af6a2 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2049,7 +2049,7 @@ func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ( return helmVersions, nil } -func DeleteProduct(username, envName, productName, requestID string, log *zap.SugaredLogger) (err error) { +func DeleteProduct(username, envName, productName, requestID string, isDelete bool, log *zap.SugaredLogger) (err error) { eventStart := time.Now().Unix() productInfo, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: productName, EnvName: envName}) if err != nil { @@ -2210,28 +2210,28 @@ func DeleteProduct(username, envName, productName, requestID string, log *zap.Su commonservice.SendMessage(username, title, content, requestID, log) } }() + if isDelete { + // Delete Cluster level resources + err = commonservice.DeleteClusterResource(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), productInfo.ClusterID, log) + if err != nil { + err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) + return + } - // Delete Cluster level resources - err = commonservice.DeleteClusterResource(labels.Set{setting.ProductLabel: productName, setting.EnvNameLabel: envName}.AsSelector(), productInfo.ClusterID, log) - if err != nil { - err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) - return - } - - // Handles environment sharing related operations. - err = EnsureDeleteShareEnvConfig(ctx, productInfo, istioClient) - if err != nil { - log.Errorf("Failed to delete share env config: %s", err) - err = e.ErrDeleteProduct.AddDesc(e.DeleteVirtualServiceErrMsg + ": " + err.Error()) - return - } + // Handles environment sharing related operations. + err = EnsureDeleteShareEnvConfig(ctx, productInfo, istioClient) + if err != nil { + log.Errorf("Failed to delete share env config: %s", err) + err = e.ErrDeleteProduct.AddDesc(e.DeleteVirtualServiceErrMsg + ": " + err.Error()) + return + } - s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() - if err1 := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { - err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) - return + s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() + if err1 := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { + err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) + return + } } - err = commonrepo.NewProductColl().Delete(envName, productName) if err != nil { log.Errorf("Product.Delete error: %v", err) @@ -3145,7 +3145,6 @@ func applyUpdatedAnnotations(annotations map[string]string) map[string]string { annotations = make(map[string]string) } - annotations[setting.UpdatedByLabel] = fmt.Sprintf("%d", time.Now().Unix()) return annotations } diff --git a/pkg/microservice/aslan/core/environment/service/product.go b/pkg/microservice/aslan/core/environment/service/product.go index 7f5ef6b4..27d680ae 100644 --- a/pkg/microservice/aslan/core/environment/service/product.go +++ b/pkg/microservice/aslan/core/environment/service/product.go @@ -72,11 +72,11 @@ func CleanProductCronJob(requestID string, log *zap.SugaredLogger) { //title := "系统清理产品信息" //content := fmt.Sprintf("环境 [%s] 已经连续%d天没有使用, 系统已自动删除该环境, 如有需要请重新创建。", product.EnvName, product.RecycleDay) - if err := DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { + if err := DeleteProduct("robot", product.EnvName, product.ProductName, requestID, true, log); err != nil { log.Errorf("[%s][P:%s] delete product error: %v", product.EnvName, product.ProductName, err) // 如果有错误,重试删除 - if err := DeleteProduct("robot", product.EnvName, product.ProductName, requestID, log); err != nil { + if err := DeleteProduct("robot", product.EnvName, product.ProductName, requestID, true, log); err != nil { //content = fmt.Sprintf("系统自动清理环境 [%s] 失败,请手动删除环境。", product.ProductName) log.Errorf("[%s][P:%s] retry delete product error: %v", product.EnvName, product.ProductName, err) } @@ -343,7 +343,7 @@ func CleanProducts() { _, err := templaterepo.NewProductColl().Find(prod.ProductName) if err != nil && err.Error() == "not found" { logger.Errorf("环境所属的项目不存在,准备删除此环境, namespace:%s, 项目:%s\n", prod.Namespace, prod.ProductName) - err = DeleteProduct("CleanProducts", prod.EnvName, prod.ProductName, "", logger) + err = DeleteProduct("CleanProducts", prod.EnvName, prod.ProductName, "", true, logger) if err != nil { logger.Errorf("delete product failed, namespace:%s, err:%v\n", prod.Namespace, err) continue diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index 9a963103..a345e26e 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -79,7 +79,7 @@ func ensureDeleteAssociatedEnvs(ctx context.Context, baseEnvName string) error { } for _, env := range envs { - err := DeleteProduct("system", env.EnvName, env.ProductName, "", logger.Sugar()) + err := DeleteProduct("system", env.EnvName, env.ProductName, "", true, logger.Sugar()) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/project/handler/product.go b/pkg/microservice/aslan/core/project/handler/product.go index 82202b23..55ccaab9 100644 --- a/pkg/microservice/aslan/core/project/handler/product.go +++ b/pkg/microservice/aslan/core/project/handler/product.go @@ -184,7 +184,14 @@ func DeleteProductTemplate(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() internalhandler.InsertOperationLog(c, ctx.UserName, c.Param("name"), "删除", "项目管理-项目", c.Param("name"), "", ctx.Logger) - ctx.Err = projectservice.DeleteProductTemplate(ctx.UserName, c.Param("name"), ctx.RequestID, ctx.Logger) + isDelete, err := strconv.ParseBool(c.Query("is_delete")) + if err != nil { + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalidParam is_delete") + return + } + } + ctx.Err = projectservice.DeleteProductTemplate(ctx.UserName, c.Param("name"), ctx.RequestID, isDelete, ctx.Logger) } func ListTemplatesHierachy(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index cbc5d3fa..fdabfdb4 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -355,7 +355,7 @@ func validateCommonRule(currentRule, ruleType, deliveryType string) error { } // DeleteProductTemplate 删除产品模板 -func DeleteProductTemplate(userName, productName, requestID string, log *zap.SugaredLogger) (err error) { +func DeleteProductTemplate(userName, productName, requestID string, isDelete bool, log *zap.SugaredLogger) (err error) { publicServices, err := commonrepo.NewServiceColl().ListMaxRevisions(&commonrepo.ServiceListOption{ProductName: productName, Visibility: setting.PublicService}) if err != nil { log.Errorf("pre delete check failed, err: %s", err) @@ -459,7 +459,7 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug _ = commonrepo.NewBuildColl().Delete("", productName) _ = commonrepo.NewServiceColl().Delete("", "", productName, "", 0) _ = commonservice.DeleteDeliveryInfos(productName, log) - _ = DeleteProductsAsync(userName, productName, requestID, log) + _ = DeleteProductsAsync(userName, productName, requestID, isDelete, log) // delete service webhooks after services are deleted for _, s := range services { @@ -468,20 +468,22 @@ func DeleteProductTemplate(userName, productName, requestID string, log *zap.Sug }() // 删除workload - go func() { - workloads, _ := commonrepo.NewWorkLoadsStatColl().FindByProductName(productName) - for _, v := range workloads { - // update workloads - tmp := []commonmodels.Workload{} - for _, vv := range v.Workloads { - if vv.ProductName != productName { - tmp = append(tmp, vv) + if isDelete { + go func() { + workloads, _ := commonrepo.NewWorkLoadsStatColl().FindByProductName(productName) + for _, v := range workloads { + // update workloads + tmp := []commonmodels.Workload{} + for _, vv := range v.Workloads { + if vv.ProductName != productName { + tmp = append(tmp, vv) + } } + v.Workloads = tmp + commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(v) } - v.Workloads = tmp - commonrepo.NewWorkLoadsStatColl().UpdateWorkloads(v) - } - }() + }() + } // delete servicesInExternalEnv data go func() { _ = commonrepo.NewServicesInExternalEnvColl().Delete(&commonrepo.ServicesInExternalEnvArgs{ @@ -633,7 +635,7 @@ func UnForkProduct(userID string, username, productName, workflowName, envName, log.Errorf("Failed to delete roleBinding, err: %s", err) return e.ErrForkProduct } - if err := environmentservice.DeleteProduct(username, envName, productName, requestID, log); err != nil { + if err := environmentservice.DeleteProduct(username, envName, productName, requestID, true, log); err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { if description != "not found" { @@ -719,14 +721,14 @@ func ensureProductTmpl(args *template.Product) error { return nil } -func DeleteProductsAsync(userName, productName, requestID string, log *zap.SugaredLogger) error { +func DeleteProductsAsync(userName, productName, requestID string, isDelete bool, log *zap.SugaredLogger) error { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{Name: productName}) if err != nil { return e.ErrListProducts.AddDesc(err.Error()) } errList := new(multierror.Error) for _, env := range envs { - err = environmentservice.DeleteProduct(userName, env.EnvName, productName, requestID, log) + err = environmentservice.DeleteProduct(userName, env.EnvName, productName, requestID, isDelete, log) if err != nil { errList = multierror.Append(errList, err) } diff --git a/pkg/microservice/aslan/core/service/handler/environment.go b/pkg/microservice/aslan/core/service/handler/environment.go index c44eead1..bc64e81a 100644 --- a/pkg/microservice/aslan/core/service/handler/environment.go +++ b/pkg/microservice/aslan/core/service/handler/environment.go @@ -17,10 +17,13 @@ limitations under the License. package handler import ( + "encoding/json" + "github.com/gin-gonic/gin" "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" ) func GetDeployableEnvs(c *gin.Context) { @@ -29,3 +32,28 @@ func GetDeployableEnvs(c *gin.Context) { ctx.Resp, ctx.Err = service.GetDeployableEnvs(c.Param("name"), c.Query("projectName")) } + +func GetKubeWorkloads(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetKubeWorkloads(c.Query("namespace"), c.Query("cluster_id"), ctx.Logger) +} + +func LoadKubeWorkloadsYaml(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.LoadKubeWorkloadsYamlReq) + data, err := c.GetRawData() + if err != nil { + ctx.Logger.Errorf("copyHelmProduct c.GetRawData() err : %s", err) + } else if err = json.Unmarshal(data, &args); err != nil { + ctx.Logger.Errorf("copyHelmProduct json.Unmarshal err : %s", err) + } + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + ctx.Err = service.LoadKubeWorkloadsYaml(ctx.UserName, args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index b3527136..beffbeab 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -57,6 +57,8 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) k8s.GET("/:name/environments/deployable", GetDeployableEnvs) + k8s.GET("/kube/workloads", GetKubeWorkloads) + k8s.POST("/yaml", LoadKubeWorkloadsYaml) } workload := router.Group("workloads") diff --git a/pkg/microservice/aslan/core/service/service/environment.go b/pkg/microservice/aslan/core/service/service/environment.go index cd675f69..b8cd4347 100644 --- a/pkg/microservice/aslan/core/service/service/environment.go +++ b/pkg/microservice/aslan/core/service/service/environment.go @@ -17,8 +17,18 @@ limitations under the License. package service import ( + "fmt" + "strings" + + "go.uber.org/zap" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/util" ) @@ -46,6 +56,217 @@ func GetDeployableEnvs(svcName, projectName string) ([]string, error) { return envs0, nil } +type GetKubeWorkloadsResp struct { + WorkloadsMap map[string][]string `json:"workloads_map"` +} + +func GetKubeWorkloads(namespace, clusterID string, log *zap.SugaredLogger) (*GetKubeWorkloadsResp, error) { + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + log.Errorf("cluster is not connected [%s] err:%s", clusterID, err) + return nil, err + } + + deployments, err := getter.ListDeployments(namespace, nil, kubeClient) + if err != nil { + log.Errorf("GetKubeWorkloads ListDeployments error, error msg:%s", err) + return nil, err + } + workloadsMap := make(map[string][]string) + var deployNames []string + for _, deployment := range deployments { + deployNames = append(deployNames, deployment.Name) + } + workloadsMap["deployment"] = deployNames + configMaps, err := getter.ListConfigMaps(namespace, nil, kubeClient) + if err != nil { + log.Errorf("GetKubeWorkloads ListConfigMaps error, error msg:%s", err) + return nil, err + } + var configMapNames []string + for _, configmap := range configMaps { + configMapNames = append(configMapNames, configmap.Name) + } + workloadsMap["configmap"] = configMapNames + services, err := getter.ListServices(namespace, nil, kubeClient) + if err != nil { + log.Errorf("GetKubeWorkloads ListServices error, error msg:%s", err) + return nil, err + } + var serviceNames []string + for _, service := range services { + serviceNames = append(serviceNames, service.Name) + } + workloadsMap["service"] = serviceNames + ingresses, err := getter.ListIngresses(namespace, kubeClient, true) + if err != nil { + log.Errorf("GetKubeWorkloads ListIngresses error, error msg:%s", err) + return nil, err + } + var ingressNames []string + for _, ingress := range ingresses.Items { + ingressNames = append(ingressNames, ingress.GetName()) + } + workloadsMap["ingress"] = ingressNames + secrets, err := getter.ListSecrets(namespace, kubeClient) + if err != nil { + log.Errorf("GetKubeWorkloads ListSecrets error, error msg:%s", err) + return nil, err + } + var secretNames []string + for _, secret := range secrets { + secretNames = append(secretNames, secret.Name) + } + workloadsMap["secret"] = secretNames + statefulsets, err := getter.ListStatefulSets(namespace, nil, kubeClient) + if err != nil { + log.Errorf("GetKubeWorkloads ListStatefulSets error, error msg:%s", err) + return nil, err + } + var statefulsetNames []string + for _, statefulset := range statefulsets { + statefulsetNames = append(statefulsetNames, statefulset.Name) + } + workloadsMap["statefulset"] = statefulsetNames + pvcs, err := getter.ListPvcs(namespace, nil, kubeClient) + if err != nil { + log.Errorf("GetKubeWorkloads ListPvcs error, error msg:%s", err) + return nil, err + } + var pvcNames []string + for _, pvc := range pvcs { + pvcNames = append(pvcNames, pvc.Name) + } + workloadsMap["pvc"] = pvcNames + return &GetKubeWorkloadsResp{ + WorkloadsMap: workloadsMap, + }, nil +} + +type ServiceWorkloads struct { + Name string `json:"name"` + WorkloadsMap map[string][]string `json:"workloads_map"` +} + +type LoadKubeWorkloadsYamlReq struct { + ProductName string `json:"product_name"` + Visibility string `json:"visibility"` + Type string `json:"type"` + Namespace string `json:"namespace"` + ClusterID string `json:"cluster_id"` + Services []ServiceWorkloads `json:"services"` +} + +type ServiceYaml struct { + Name string `json:"name"` + Yaml string `json:"yaml"` +} + +type GetKubeWorkloadsYamlResp struct { + Services []ServiceYaml `json:"services"` +} + +func LoadKubeWorkloadsYaml(username string, params *LoadKubeWorkloadsYamlReq, log *zap.SugaredLogger) error { + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), params.ClusterID) + if err != nil { + log.Errorf("cluster is not connected [%s]", params.ClusterID) + return err + } + for _, service := range params.Services { + var yamls []string + for workloadType, workloads := range service.WorkloadsMap { + switch workloadType { + case "configmap": + for _, workload := range workloads { + bs, _, err := getter.GetConfigMapYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return e.ErrGetService.AddDesc(fmt.Sprintf("get deploy/configmap failed err:%s", err)) + } + + yamls = append(yamls, string(bs)) + } + case "deployment": + for _, workload := range workloads { + bs, _, err := getter.GetDeploymentYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return e.ErrGetService.AddDesc(fmt.Sprintf("get deploy/deployment failed err:%s", err)) + } + + yamls = append(yamls, string(bs)) + } + case "service": + for _, workload := range workloads { + bs, _, err := getter.GetServiceYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return e.ErrGetService.AddDesc(fmt.Sprintf("get deploy/service failed err:%s", err)) + } + + yamls = append(yamls, string(bs)) + } + case "secret": + for _, workload := range workloads { + bs, _, err := getter.GetSecretYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return e.ErrGetService.AddDesc(fmt.Sprintf("get deploy/secret failed err:%s", err)) + } + + yamls = append(yamls, string(bs)) + } + case "ingress": + for _, workload := range workloads { + bs, _, err := getter.GetIngressYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return e.ErrGetService.AddDesc(fmt.Sprintf("get deploy/ingress failed err:%s", err)) + } + + yamls = append(yamls, string(bs)) + } + case "statefulset": + for _, workload := range workloads { + bs, _, err := getter.GetStatefulSetYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return fmt.Errorf("get deploy/statefulset failed err:%s", err) + } + yamls = append(yamls, string(bs)) + } + case "pvc": + for _, workload := range workloads { + bs, _, err := getter.GetPVCYamlFormat(params.Namespace, workload, kubeClient) + if len(bs) == 0 || err != nil { + log.Errorf("not found yaml %v", err) + return fmt.Errorf("get deploy/pvc failed err:%s", err) + } + yamls = append(yamls, string(bs)) + } + default: + return fmt.Errorf("do not support workload kind:%s", workloadType) + } + } + yaml := strings.Join(yamls, setting.YamlFileSeperator) + serviceParam := &commonmodels.Service{ + ProductName: params.ProductName, + ServiceName: service.Name, + Visibility: params.Visibility, + Type: params.Type, + Yaml: yaml, + Source: "spock", + } + _, err := CreateServiceTemplate(username, serviceParam, log) + if err != nil { + log.Errorf("CreateServiceTemplate error, msg:%s", err) + return err + } + } + + return nil +} + func getAllGeneralEnvs(projectName string) ([]string, error) { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ Name: projectName, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index e9ea23aa..02881664 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -747,7 +747,7 @@ func CreateEnvAndTaskByPR(workflowArgs *commonmodels.WorkflowTaskArgs, prID int, } //按照用户设置的环境回收策略进行环境回收 if workflowArgs.EnvRecyclePolicy == setting.EnvRecyclePolicyAlways || (workflowArgs.EnvRecyclePolicy == setting.EnvRecyclePolicyTaskStatus && taskStatus == string(config.StatusPassed)) { - err = environmentservice.DeleteProduct(setting.SystemUser, envName, workflowArgs.ProductTmplName, requestID, log) + err = environmentservice.DeleteProduct(setting.SystemUser, envName, workflowArgs.ProductTmplName, requestID, true, log) if err != nil { log.Errorf("CreateEnvAndTaskByPR DeleteProduct err:%v ", err) return err diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index f4dafc65..d2011d3f 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -161,6 +161,10 @@ metas: endpoint: /api/aslan/service/helm/services - method: POST endpoint: /api/aslan/service/helm/services/bulk + - method: GET + endpoint: /api/aslan/service/services/kube/workloads + - method: POST + endpoint: /api/aslan/service/services/yaml - action: delete_service alias: 删除 description: '' diff --git a/pkg/tool/kube/getter/base.go b/pkg/tool/kube/getter/base.go index 1529cf70..eee6f183 100644 --- a/pkg/tool/kube/getter/base.go +++ b/pkg/tool/kube/getter/base.go @@ -20,6 +20,7 @@ import ( "context" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" @@ -111,6 +112,45 @@ func GetResourceYamlInCache(ns, name string, gvk schema.GroupVersionKind, cl cli return data, true, nil } +func GetResourceJSONInCacheFormat(ns, name string, gvk schema.GroupVersionKind, cl client.Reader) ([]byte, bool, error) { + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(gvk) + + found, err := GetResourceInCache(ns, name, u, cl) + if err != nil || !found { + return nil, false, err + } + u.SetManagedFields(nil) + u.SetUID("") + u.SetSelfLink("") + u.SetResourceVersion("") + u.SetCreationTimestamp(metav1.Time{}) + u.SetNamespace("") + u.SetGeneration(0) + content := u.UnstructuredContent() + delete(content, "status") + u.SetUnstructuredContent(content) + data, err := u.MarshalJSON() + if err != nil { + return nil, false, err + } + + return data, true, nil +} + +func GetResourceYamlInCacheFormat(ns, name string, gvk schema.GroupVersionKind, cl client.Reader) ([]byte, bool, error) { + d, found, err := GetResourceJSONInCacheFormat(ns, name, gvk, cl) + if err != nil || !found || len(d) == 0 { + return nil, false, err + } + + data, err := yaml.JSONToYAML(d) + if err != nil { + return nil, false, err + } + return data, true, nil +} + // ListResourceJSONInCache gets a set of specific Kubernetes object in local cache, and return a representation in json format. func ListResourceJSONInCache(ns string, selector labels.Selector, fieldSelector fields.Selector, gvk schema.GroupVersionKind, cl client.Reader) ([][]byte, error) { u := &unstructured.UnstructuredList{} diff --git a/pkg/tool/kube/getter/configmap.go b/pkg/tool/kube/getter/configmap.go index fc429e8d..dca102c1 100644 --- a/pkg/tool/kube/getter/configmap.go +++ b/pkg/tool/kube/getter/configmap.go @@ -61,3 +61,21 @@ func ListConfigMapsYaml(ns string, selector labels.Selector, cl client.Client) ( } return ListResourceYamlInCache(ns, selector, nil, gvk, cl) } + +func GetConfigMapYaml(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "ConfigMap", + Version: "v1", + } + return GetResourceYamlInCache(ns, name, gvk, cl) +} + +func GetConfigMapYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "ConfigMap", + Version: "v1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} diff --git a/pkg/tool/kube/getter/deployment.go b/pkg/tool/kube/getter/deployment.go index 6d3595c9..d2eead6d 100644 --- a/pkg/tool/kube/getter/deployment.go +++ b/pkg/tool/kube/getter/deployment.go @@ -72,3 +72,12 @@ func GetDeploymentYaml(ns string, name string, cl client.Client) ([]byte, bool, } return GetResourceYamlInCache(ns, name, gvk, cl) } + +func GetDeploymentYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "apps", + Kind: "Deployment", + Version: "v1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} diff --git a/pkg/tool/kube/getter/ingress.go b/pkg/tool/kube/getter/ingress.go index 946fe9c2..60423cd2 100644 --- a/pkg/tool/kube/getter/ingress.go +++ b/pkg/tool/kube/getter/ingress.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" + "k8s.io/kubernetes/pkg/apis/extensions" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -84,3 +85,49 @@ func ListIngresses(namespace string, cl client.Client, lessThan122 bool) (*unstr } return u, err } + +func ListIngressesFormat(namespace string, cl client.Client, lessThan122 bool) ([]*extensions.Ingress, error) { + l := &extensions.IngressList{} + gvk := schema.GroupVersionKind{ + Group: "extensions", + Kind: "Ingress", + Version: "v1beta1", + } + if !lessThan122 { + gvk = schema.GroupVersionKind{ + Group: "networking.k8s.io", + Kind: "Ingress", + Version: "v1", + } + } + u := &unstructured.UnstructuredList{} + u.SetGroupVersionKind(gvk) + + err := ListResourceInCache(namespace, labels.Everything(), nil, u, cl) + if err != nil { + return nil, err + } + var res []*extensions.Ingress + for i := range l.Items { + res = append(res, &l.Items[i]) + } + return res, err +} + +func GetIngressYaml(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "extensions", + Kind: "Ingress", + Version: "v1beta1", + } + return GetResourceYamlInCache(ns, name, gvk, cl) +} + +func GetIngressYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "extensions", + Kind: "Ingress", + Version: "v1beta1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} diff --git a/pkg/tool/kube/getter/pvc.go b/pkg/tool/kube/getter/pvc.go index 935ff3f9..2b4d024c 100644 --- a/pkg/tool/kube/getter/pvc.go +++ b/pkg/tool/kube/getter/pvc.go @@ -42,3 +42,21 @@ func ListPvcs(ns string, selector fields.Selector, cl client.Reader) ([]*corev1. } return res, err } + +func GetPVCYaml(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "PersistentVolumeClaim", + Version: "v1", + } + return GetResourceYamlInCache(ns, name, gvk, cl) +} + +func GetPVCYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "PersistentVolumeClaim", + Version: "v1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} diff --git a/pkg/tool/kube/getter/secrets.go b/pkg/tool/kube/getter/secrets.go index 08e9443c..14d060b3 100644 --- a/pkg/tool/kube/getter/secrets.go +++ b/pkg/tool/kube/getter/secrets.go @@ -51,3 +51,21 @@ func ListSecrets(ns string, cl client.Client) ([]*corev1.Secret, error) { } return res, err } + +func GetSecretYaml(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "Secret", + Version: "v1", + } + return GetResourceYamlInCache(ns, name, gvk, cl) +} + +func GetSecretYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "Secret", + Version: "v1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} diff --git a/pkg/tool/kube/getter/service.go b/pkg/tool/kube/getter/service.go index cbf0ca1f..593dd2ff 100644 --- a/pkg/tool/kube/getter/service.go +++ b/pkg/tool/kube/getter/service.go @@ -63,3 +63,21 @@ func ListServicesYaml(ns string, selector labels.Selector, cl client.Client) ([] } return ListResourceYamlInCache(ns, selector, nil, gvk, cl) } + +func GetServiceYaml(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "Service", + Version: "v1", + } + return GetResourceYamlInCache(ns, name, gvk, cl) +} + +func GetServiceYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "", + Kind: "Service", + Version: "v1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} diff --git a/pkg/tool/kube/getter/statefulset.go b/pkg/tool/kube/getter/statefulset.go index 7c7bbedc..8b96976d 100644 --- a/pkg/tool/kube/getter/statefulset.go +++ b/pkg/tool/kube/getter/statefulset.go @@ -72,3 +72,12 @@ func GetStatefulSetYaml(ns string, name string, cl client.Client) ([]byte, bool, } return GetResourceYamlInCache(ns, name, gvk, cl) } + +func GetStatefulSetYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { + gvk := schema.GroupVersionKind{ + Group: "apps", + Kind: "StatefulSet", + Version: "v1", + } + return GetResourceYamlInCacheFormat(ns, name, gvk, cl) +} -- Gitee From 136854baa234f41fd9dca5daa15eb923034c2a87 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 26 May 2022 15:15:51 +0800 Subject: [PATCH 2379/2647] optimize logic of fetch code from git repos (#1151) * add namespace config of git repos Signed-off-by: allenshen * update namespace parameter Signed-off-by: allenshen * add namespace parameter Signed-off-by: allenshen * make service webhook work Signed-off-by: allenshen * make webhook work Signed-off-by: allenshen * remove unnecessary change code Signed-off-by: allenshen * update ns Signed-off-by: allenshen * add namespace attribute Signed-off-by: allenshen * add namespace to chart detail Signed-off-by: allenshen * update namespace fill logic Signed-off-by: allenshen * add namespace value Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * fix bug Signed-off-by: allenshen * optimize yaml source Signed-off-by: allenshen * fix compile error Signed-off-by: allenshen * fix compile error Signed-off-by: allenshen * optmize code Signed-off-by: allenshen * fix compile error Signed-off-by: allenshen * fix auto sync config bug Signed-off-by: allenshen * fix comments Signed-off-by: allenshen --- .../aslan/core/code/handler/codehost.go | 13 +- .../aslan/core/code/handler/workspace.go | 14 +- .../aslan/core/code/service/repo.go | 15 +- .../aslan/core/code/service/workspace.go | 4 +- .../core/common/repository/models/chart.go | 26 +- .../core/common/repository/models/queue.go | 11 +- .../core/common/repository/models/service.go | 14 +- .../repository/models/template/product.go | 8 + .../core/common/repository/models/workflow.go | 38 ++- .../aslan/core/common/service/build.go | 7 + .../core/common/service/command/git_cmd.go | 4 +- .../aslan/core/common/service/environment.go | 31 ++ .../aslan/core/common/service/fs/git.go | 19 +- .../aslan/core/common/service/fs/s3.go | 4 +- .../aslan/core/common/service/helm.go | 6 +- .../aslan/core/common/service/render.go | 52 ++-- .../common/service/scmnotify/scmnotify.go | 4 +- .../aslan/core/common/service/service.go | 131 +++++---- .../aslan/core/common/service/sets.go | 2 +- .../core/common/service/template/types.go | 1 + .../aslan/core/common/service/utils.go | 26 +- .../core/common/service/webhook/client.go | 15 +- .../core/common/service/webhook/controller.go | 29 +- .../core/common/service/webhook/types.go | 4 +- .../aslan/core/common/service/workflow.go | 54 ++-- .../core/environment/service/environment.go | 8 +- .../core/environment/service/renderset.go | 23 +- .../aslan/core/service/handler/loader.go | 7 +- .../aslan/core/service/service/helm.go | 88 ++++-- .../aslan/core/service/service/loader.go | 52 ++-- .../aslan/core/service/service/new_loader.go | 47 +-- .../aslan/core/service/service/service.go | 2 +- .../aslan/core/service/service/types.go | 1 + .../aslan/core/templatestore/service/chart.go | 5 + .../service/webhook/codehub_workflow_task.go | 21 +- .../core/workflow/service/webhook/gerrit.go | 5 +- .../service/webhook/gerrit_workflow_task.go | 21 +- .../core/workflow/service/webhook/gitee.go | 4 +- .../core/workflow/service/webhook/github.go | 4 + .../service/webhook/github_testing_task.go | 122 ++++---- .../service/webhook/github_workflow_task.go | 162 ++++++----- .../core/workflow/service/webhook/gitlab.go | 3 +- .../service/webhook/gitlab_pipeline_task.go | 6 +- .../service/webhook/gitlab_testing_task.go | 125 ++++---- .../service/webhook/gitlab_workflow_task.go | 271 +++++++++--------- .../core/workflow/service/webhook/utils.go | 90 ++++-- .../service/workflow/workflow_task.go | 7 + .../core/workflow/testing/service/scanning.go | 25 +- .../core/workflow/testing/service/types.go | 18 +- pkg/microservice/cron/core/service/types.go | 1 + .../reaper/core/service/meta/types.go | 1 + .../reaper/core/service/reaper/git.go | 11 +- .../warpdrive/core/service/taskplugin/job.go | 1 + .../warpdrive/core/service/types/reaper.go | 1 + .../core/service/types/task/build.go | 1 + .../core/service/types/task/model.go | 1 + pkg/types/repo.go | 8 + 57 files changed, 1004 insertions(+), 670 deletions(-) diff --git a/pkg/microservice/aslan/core/code/handler/codehost.go b/pkg/microservice/aslan/core/code/handler/codehost.go index 86681cf6..1b6e7e00 100644 --- a/pkg/microservice/aslan/core/code/handler/codehost.go +++ b/pkg/microservice/aslan/core/code/handler/codehost.go @@ -100,7 +100,7 @@ func CodeHostGetProjectsList(c *gin.Context) { } chID, _ := strconv.Atoi(codehostID) - ctx.Resp, ctx.Err = service.CodeHostListProjects( + projects, err := service.CodeHostListProjects( chID, strings.Replace(repoOwner, "%2F", "/", -1), namespaceType, @@ -108,6 +108,17 @@ func CodeHostGetProjectsList(c *gin.Context) { args.PerPage, args.Key, ctx.Logger) + if err != nil { + ctx.Err = err + return + } + + for _, project := range projects { + if project.Namespace == "" { + project.Namespace = repoOwner + } + } + ctx.Resp = projects } type CodeHostGetPageNateListArgs struct { diff --git a/pkg/microservice/aslan/core/code/handler/workspace.go b/pkg/microservice/aslan/core/code/handler/workspace.go index ffda5253..848339c2 100644 --- a/pkg/microservice/aslan/core/code/handler/workspace.go +++ b/pkg/microservice/aslan/core/code/handler/workspace.go @@ -113,12 +113,17 @@ func GetGitRepoInfo(c *gin.Context) { dir := c.Query("dir") - ctx.Resp, ctx.Err = service.GetGitRepoInfo(codehostID, c.Query("repoOwner"), repoName, branchName, remoteName, dir, ctx.Logger) + namespace := c.Query("repoNamespace") + if namespace == "" { + namespace = c.Query("repoOwner") + } + ctx.Resp, ctx.Err = service.GetGitRepoInfo(codehostID, c.Query("repoOwner"), namespace, repoName, branchName, remoteName, dir, ctx.Logger) } type repoInfo struct { CodeHostID int `json:"codehost_id" form:"codehost_id"` Owner string `json:"owner" form:"owner"` + Namespace string `json:"namespace" form:"namespace"` Repo string `json:"repo" form:"repo"` Path string `json:"path" form:"path"` Branch string `json:"branch" form:"branch"` @@ -140,7 +145,12 @@ func GetRepoTree(c *gin.Context) { return } - ctx.Resp, ctx.Err = service.GetRepoTree(info.CodeHostID, info.Owner, info.Repo, info.Path, info.Branch, ctx.Logger) + owner := info.Owner + if len(info.Namespace) > 0 { + owner = info.Namespace + } + + ctx.Resp, ctx.Err = service.GetRepoTree(info.CodeHostID, owner, info.Repo, info.Path, info.Branch, ctx.Logger) } func GetCodehubRepoInfo(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index ce05138f..d2187a35 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -36,6 +36,7 @@ type RepoInfoList struct { type GitRepoInfo struct { Owner string `json:"repo_owner"` + Namespace string `json:"repo_namespace"` Repo string `json:"repo"` CodehostID int `json:"codehost_id"` Source string `json:"source"` @@ -52,6 +53,13 @@ type GitRepoInfo struct { FilterRegexp string `json:"filter_regexp,omitempty"` } +func (repo *GitRepoInfo) GetNamespace() string { + if repo.Namespace == "" { + return repo.Owner + } + return repo.Namespace +} + // ListRepoInfos ... func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo, error) { var wg sync.WaitGroup @@ -76,7 +84,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo wg.Done() }() info.PRs, err = codehostClient.ListPrs(client.ListOpt{ - Namespace: strings.Replace(info.Owner, "%2F", "/", -1), + Namespace: strings.Replace(info.GetNamespace(), "%2F", "/", -1), ProjectName: info.Repo, }) if err != nil { @@ -96,9 +104,8 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo if info.Source == CodeHostCodeHub { projectName = info.RepoUUID } - info.Branches, err = codehostClient.ListBranches(client.ListOpt{ - Namespace: strings.Replace(info.Owner, "%2F", "/", -1), + Namespace: strings.Replace(info.GetNamespace(), "%2F", "/", -1), ProjectName: projectName, Key: info.Key, }) @@ -122,7 +129,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo } info.Tags, err = codehostClient.ListTags(client.ListOpt{ - Namespace: strings.Replace(info.Owner, "%2F", "/", -1), + Namespace: strings.Replace(info.GetNamespace(), "%2F", "/", -1), ProjectName: projectName, Key: info.Key, }) diff --git a/pkg/microservice/aslan/core/code/service/workspace.go b/pkg/microservice/aslan/core/code/service/workspace.go index cdb1712d..0f2a7620 100644 --- a/pkg/microservice/aslan/core/code/service/workspace.go +++ b/pkg/microservice/aslan/core/code/service/workspace.go @@ -135,7 +135,7 @@ type FileInfo struct { IsDir bool `json:"is_dir"` } -func GetGitRepoInfo(codehostID int, repoOwner, repoName, branchName, remoteName, dir string, log *zap.SugaredLogger) ([]*FileInfo, error) { +func GetGitRepoInfo(codehostID int, repoOwner, repoNamespace, repoName, branchName, remoteName, dir string, log *zap.SugaredLogger) ([]*FileInfo, error) { fis := make([]*FileInfo, 0) if dir == "" { dir = "/" @@ -150,7 +150,7 @@ func GetGitRepoInfo(codehostID int, repoOwner, repoName, branchName, remoteName, log.Errorf("GetGitRepoInfo GetCodehostDetail err:%s", err) return fis, e.ErrListRepoDir.AddDesc(err.Error()) } - err = command.RunGitCmds(detail, repoOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(detail, repoOwner, repoNamespace, repoName, branchName, remoteName) if err != nil { log.Errorf("GetGitRepoInfo runGitCmds err:%s", err) return fis, e.ErrListRepoDir.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/common/repository/models/chart.go b/pkg/microservice/aslan/core/common/repository/models/chart.go index 4c7e3fd5..0f9434fa 100644 --- a/pkg/microservice/aslan/core/common/repository/models/chart.go +++ b/pkg/microservice/aslan/core/common/repository/models/chart.go @@ -20,16 +20,17 @@ import "go.mongodb.org/mongo-driver/bson/primitive" type Chart struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Source string `bson:"source" json:"source"` - Owner string `bson:"owner" json:"owner"` - Repo string `bson:"repo" json:"repo"` - Path string `bson:"path" json:"path"` - Branch string `bson:"branch" json:"branch"` - CodeHostID int `bson:"codehost_id" json:"codeHostID"` - Revision int64 `bson:"revision" json:"revision"` + Name string `bson:"name" json:"name"` + Source string `bson:"source" json:"source"` + Owner string `bson:"owner" json:"owner"` + Namespace string `bson:"namespace" json:"namespace"` + Repo string `bson:"repo" json:"repo"` + Path string `bson:"path" json:"path"` + Branch string `bson:"branch" json:"branch"` + CodeHostID int `bson:"codehost_id" json:"codeHostID"` + Revision int64 `bson:"revision" json:"revision"` ChartVariables []*ChartVariable `bson:"variables" json:"variables"` - Sha1 string `bson:"sha1" json:"sha1"` + Sha1 string `bson:"sha1" json:"sha1"` } type ChartVariable struct { @@ -38,6 +39,13 @@ type ChartVariable struct { Description string `json:"description,omitempty"` } +func (chart *Chart) GetNamespace() string { + if len(chart.Namespace) > 0 { + return chart.Namespace + } + return chart.Owner +} + func (chart *Chart) GetVariableMap() map[string]*ChartVariable { if len(chart.ChartVariables) == 0 { return nil diff --git a/pkg/microservice/aslan/core/common/repository/models/queue.go b/pkg/microservice/aslan/core/common/repository/models/queue.go index dd8931e9..826120ba 100644 --- a/pkg/microservice/aslan/core/common/repository/models/queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/queue.go @@ -75,14 +75,15 @@ type Queue struct { type TriggerBy struct { // 触发此次任务的代码库信息 - CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` - RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` - RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` - Source string `json:"source,omitempty" bson:"source,omitempty"` + CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` + RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` + RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` + Source string `json:"source,omitempty" bson:"source,omitempty"` // 触发此次任务的merge request id,用于判断多个任务对应的commit是否属于同一个merge request MergeRequestID string `json:"merge_request_id,omitempty" bson:"merge_request_id,omitempty"` // 触发此次任务的commit id - CommitID string `json:"commit_id,omitempty" bson:"commit_id,omitempty"` + CommitID string `bson:"commit_id,omitempty" json:"commit_id,omitempty"` } type ServiceTaskArgs struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 54d5a986..6bec2ed4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -60,6 +60,7 @@ type Service struct { ReleaseNaming string `bson:"release_naming" json:"release_naming"` CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id,omitempty"` RepoOwner string `bson:"repo_owner,omitempty" json:"repo_owner,omitempty"` + RepoNamespace string `bson:"repo_namespace,omitempty" json:"repo_namespace,omitempty"` RepoName string `bson:"repo_name,omitempty" json:"repo_name,omitempty"` RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` BranchName string `bson:"branch_name,omitempty" json:"branch_name,omitempty"` @@ -267,9 +268,16 @@ type PmHealthCheck struct { CurrentUnhealthyNum int `bson:"current_unhealthy_num,omitempty" json:"current_unhealthy_num,omitempty"` } -func (s *Service) GetReleaseNaming() string { - if len(s.ReleaseNaming) > 0 { - return s.ReleaseNaming +func (svc *Service) GetRepoNamespace() string { + if svc.RepoNamespace != "" { + return svc.RepoNamespace + } + return svc.RepoOwner +} + +func (svc *Service) GetReleaseNaming() string { + if len(svc.ReleaseNaming) > 0 { + return svc.ReleaseNaming } return setting.ReleaseNamingPlaceholder } diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index be499350..075a7694 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -101,6 +101,14 @@ type GitRepoConfig struct { Owner string `bson:"owner,omitempty" json:"owner"` Repo string `bson:"repo,omitempty" json:"repo"` Branch string `bson:"branch,omitempty" json:"branch"` + Namespace string `bson:"namespace,omitempty" json:"namespace"` // records the actual namespace of repo, used to generate correct project name +} + +func (grc *GitRepoConfig) GetNamespace() string { + if len(grc.Namespace) > 0 { + return grc.Namespace + } + return grc.Owner } type CustomYaml struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow.go b/pkg/microservice/aslan/core/common/repository/models/workflow.go index 44136ac1..bc8879d5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow.go @@ -77,20 +77,28 @@ type WorkflowHook struct { } type MainHookRepo struct { - Name string `bson:"name,omitempty" json:"name,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - Source string `bson:"source,omitempty" json:"source,omitempty"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoName string `bson:"repo_name" json:"repo_name"` - Branch string `bson:"branch" json:"branch"` - Tag string `bson:"tag" json:"tag"` - Committer string `bson:"committer" json:"committer"` - MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` - Events []config.HookEventType `bson:"events" json:"events"` - Label string `bson:"label" json:"label"` - Revision string `bson:"revision" json:"revision"` - IsRegular bool `bson:"is_regular" json:"is_regular"` + Name string `bson:"name,omitempty" json:"name,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` + RepoName string `bson:"repo_name" json:"repo_name"` + Branch string `bson:"branch" json:"branch"` + Tag string `bson:"tag" json:"tag"` + Committer string `bson:"committer" json:"committer"` + MatchFolders []string `bson:"match_folders" json:"match_folders,omitempty"` + CodehostID int `bson:"codehost_id" json:"codehost_id"` + Events []config.HookEventType `bson:"events" json:"events"` + Label string `bson:"label" json:"label"` + Revision string `bson:"revision" json:"revision"` + IsRegular bool `bson:"is_regular" json:"is_regular"` +} + +func (m *MainHookRepo) GetRepoNamespace() string { + if m.RepoNamespace != "" { + return m.RepoNamespace + } + return m.RepoOwner } func (m MainHookRepo) GetLabelValue() string { @@ -176,6 +184,7 @@ type WorkflowTaskArgs struct { Source string `bson:"source" json:"source"` CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` RepoName string `bson:"repo_name" json:"repo_name"` Committer string `bson:"committer,omitempty" json:"committer,omitempty"` //github check run @@ -215,6 +224,7 @@ type TestTaskArgs struct { Source string `bson:"source" json:"source"` CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` RepoName string `bson:"repo_name" json:"repo_name"` } diff --git a/pkg/microservice/aslan/core/common/service/build.go b/pkg/microservice/aslan/core/common/service/build.go index 968400fc..fcaf6949 100644 --- a/pkg/microservice/aslan/core/common/service/build.go +++ b/pkg/microservice/aslan/core/common/service/build.go @@ -118,6 +118,10 @@ func EnsureResp(build *commonmodels.Build) { } build.Repos = build.SafeRepos() + for _, repo := range build.Repos { + repo.RepoNamespace = repo.GetRepoNamespace() + } + if build.PreBuild != nil { if len(build.PreBuild.Installs) == 0 { build.PreBuild.Installs = make([]*commonmodels.Item, 0) @@ -142,6 +146,9 @@ func EnsureResp(build *commonmodels.Build) { if build.TemplateID != "" { build.TargetRepos = make([]*commonmodels.TargetRepo, 0, len(build.Targets)) for _, target := range build.Targets { + for _, repo := range target.Repos { + repo.RepoNamespace = repo.GetRepoNamespace() + } targetRepo := &commonmodels.TargetRepo{ Service: &commonmodels.ServiceModuleTargetBase{ ProductName: target.ProductName, diff --git a/pkg/microservice/aslan/core/common/service/command/git_cmd.go b/pkg/microservice/aslan/core/common/service/command/git_cmd.go index 898461d6..0fb75f0f 100644 --- a/pkg/microservice/aslan/core/common/service/command/git_cmd.go +++ b/pkg/microservice/aslan/core/common/service/command/git_cmd.go @@ -38,6 +38,7 @@ type Repo struct { Source string `yaml:"source"` Address string `yaml:"address"` Owner string `yaml:"owner"` + Namespace string `yaml:"namespace"` Name string `yaml:"name"` RemoteName string `yaml:"remote_name"` Branch string `yaml:"branch"` @@ -65,7 +66,7 @@ type Command struct { IgnoreError bool } -func RunGitCmds(codehostDetail *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string) error { +func RunGitCmds(codehostDetail *systemconfig.CodeHost, repoOwner, repoNamespace, repoName, branchName, remoteName string) error { var ( tokens []string repo *Repo @@ -76,6 +77,7 @@ func RunGitCmds(codehostDetail *systemconfig.CodeHost, repoOwner, repoName, bran Source: codehostDetail.Type, Address: codehostDetail.Address, Name: repoName, + Namespace: repoNamespace, Branch: branchName, OauthToken: codehostDetail.AccessToken, RemoteName: remoteName, diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index cbb10b08..8f44fa24 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -98,6 +98,32 @@ type IngressInfo struct { HostInfo []resource.HostInfo `json:"host_info"` } +func UnMarshalSourceDetail(source interface{}) (*models.CreateFromRepo, error) { + bs, err := json.Marshal(source) + if err != nil { + return nil, err + } + ret := &models.CreateFromRepo{} + err = json.Unmarshal(bs, ret) + return ret, err +} + +func FillGitNamespace(yamlData *templatemodels.CustomYaml) error { + if yamlData == nil || yamlData.Source != setting.SourceFromGitRepo { + return nil + } + sourceDetail, err := UnMarshalSourceDetail(yamlData.SourceDetail) + if err != nil { + return err + } + if sourceDetail.GitRepoConfig == nil { + return nil + } + sourceDetail.GitRepoConfig.Namespace = sourceDetail.GitRepoConfig.GetNamespace() + yamlData.SourceDetail = sourceDetail + return nil +} + func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredLogger) ([]*RenderChartArg, error) { renderSetName := GetProductEnvNamespace(envName, productName, "") @@ -134,6 +160,11 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL rcaObj := new(RenderChartArg) rcaObj.LoadFromRenderChartModel(singleChart) rcaObj.EnvName = envName + err = FillGitNamespace(rendersetObj.YamlData) + if err != nil { + // Note, since user can always reselect the git info, error should not block normal logic + log.Warnf("failed to fill git namespace data, err: %s", err) + } rcaObj.YamlData = singleChart.OverrideYaml ret = append(ret, rcaObj) } diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index 5f97fdb3..8329ddd7 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -34,20 +34,28 @@ import ( type DownloadFromSourceArgs struct { CodehostID int `json:"codeHostID"` Owner string `json:"owner"` + Namespace string `json:"namespace"` Repo string `json:"repo"` Path string `json:"path"` Branch string `json:"branch"` RepoLink string `json:"repoLink"` } +func (args *DownloadFromSourceArgs) GetNamespace() string { + if len(args.Namespace) > 0 { + return args.Namespace + } + return args.Owner +} + func DownloadFileFromSource(args *DownloadFromSourceArgs) ([]byte, error) { getter, err := treeGetter(args.RepoLink, args.CodehostID) if err != nil { log.Errorf("Failed to get tree getter, err: %s", err) return nil, err } - - return getter.GetFileContent(args.Owner, args.Repo, args.Path, args.Branch) + owner := args.GetNamespace() + return getter.GetFileContent(owner, args.Repo, args.Path, args.Branch) } func DownloadFilesFromSource(args *DownloadFromSourceArgs, rootNameGetter func(afero.Fs) (string, error)) (fs.FS, error) { @@ -56,8 +64,11 @@ func DownloadFilesFromSource(args *DownloadFromSourceArgs, rootNameGetter func(a log.Errorf("Failed to get tree getter, err: %s", err) return nil, err } - - chartTree, err := getter.GetTreeContents(args.Owner, args.Repo, args.Path, args.Branch) + owner := args.Namespace + if owner == "" { + owner = args.Owner + } + chartTree, err := getter.GetTreeContents(owner, args.Repo, args.Path, args.Branch) if err != nil { log.Errorf("Failed to get tree contents for service %+v, err: %s", args, err) return nil, err diff --git a/pkg/microservice/aslan/core/common/service/fs/s3.go b/pkg/microservice/aslan/core/common/service/fs/s3.go index 14915808..bdcba89e 100644 --- a/pkg/microservice/aslan/core/common/service/fs/s3.go +++ b/pkg/microservice/aslan/core/common/service/fs/s3.go @@ -181,7 +181,7 @@ func DownloadAndCopyFilesFromGerrit(name, localBase string, logger *zap.SugaredL logger.Errorf("Failed to GetCodehostDetail, err:%s", err) return err } - err = command.RunGitCmds(detail, "default", chartTemplate.Repo, chartTemplate.Branch, "origin") + err = command.RunGitCmds(detail, "default", "default", chartTemplate.Repo, chartTemplate.Branch, "origin") if err != nil { logger.Errorf("Failed to runGitCmds, err:%s", err) return err @@ -209,7 +209,7 @@ func DownloadAndCopyFilesFromGitee(name, localBase string, logger *zap.SugaredLo logger.Errorf("Failed to get codehost detail, err:%s", err) return err } - err = command.RunGitCmds(detail, chartTemplate.Owner, chartTemplate.Repo, chartTemplate.Branch, "origin") + err = command.RunGitCmds(detail, chartTemplate.Owner, chartTemplate.GetNamespace(), chartTemplate.Repo, chartTemplate.Branch, "origin") if err != nil { logger.Errorf("Failed to runGitCmds, err:%s", err) return err diff --git a/pkg/microservice/aslan/core/common/service/helm.go b/pkg/microservice/aslan/core/common/service/helm.go index 9f96059d..11639d14 100644 --- a/pkg/microservice/aslan/core/common/service/helm.go +++ b/pkg/microservice/aslan/core/common/service/helm.go @@ -122,7 +122,7 @@ func CopyAndUploadService(projectName, serviceName, currentChartPath string, cop func preLoadServiceManifestsFromSource(svc *commonmodels.Service) error { tree, err := fsservice.DownloadFilesFromSource( - &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName, RepoLink: svc.SrcPath}, + &fsservice.DownloadFromSourceArgs{CodehostID: svc.CodehostID, Owner: svc.RepoOwner, Namespace: svc.RepoNamespace, Repo: svc.RepoName, Path: svc.LoadPath, Branch: svc.BranchName, RepoLink: svc.SrcPath}, func(afero.Fs) (string, error) { return svc.ServiceName, nil }) @@ -149,7 +149,7 @@ func preLoadServiceManifestsFromGerrit(svc *commonmodels.Service) error { log.Errorf("Failed to GetCodehostDetail, err:%s", err) return err } - err = command.RunGitCmds(detail, "default", svc.GerritRepoName, svc.GerritBranchName, svc.GerritRemoteName) + err = command.RunGitCmds(detail, "default", "default", svc.GerritRepoName, svc.GerritBranchName, svc.GerritRemoteName) if err != nil { log.Errorf("Failed to runGitCmds, err:%s", err) return err @@ -181,7 +181,7 @@ func preLoadServiceManifestsFromGitee(svc *commonmodels.Service) error { log.Errorf("Failed to GetCodehostDetail, err:%s", err) return err } - err = command.RunGitCmds(detail, svc.RepoOwner, svc.RepoName, svc.BranchName, "origin") + err = command.RunGitCmds(detail, svc.RepoOwner, svc.GetRepoNamespace(), svc.RepoName, svc.BranchName, "origin") if err != nil { log.Errorf("Failed to runGitCmds, err:%s", err) return err diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 775bd001..2635997a 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -41,6 +41,7 @@ import ( type RepoConfig struct { CodehostID int `json:"codehostID,omitempty"` Owner string `json:"owner,omitempty"` + Namespace string `json:"namespace,omitempty"` Repo string `json:"repo,omitempty"` Branch string `json:"branch,omitempty"` ValuesPaths []string `json:"valuesPaths,omitempty"` @@ -101,31 +102,30 @@ func (args *RenderChartArg) fromOverrideValueString(valueStr string) { } func (args *RenderChartArg) toCustomValuesYaml() *templatemodels.CustomYaml { - if len(args.OverrideYaml) > 0 { - ret := &templatemodels.CustomYaml{ - YamlContent: args.OverrideYaml, - } - if args.ValuesData != nil { - ret.Source = args.ValuesData.YamlSource - ret.AutoSync = args.ValuesData.AutoSync - if args.ValuesData.GitRepoConfig != nil { - repoData := &models.CreateFromRepo{ - GitRepoConfig: &templatemodels.GitRepoConfig{ - CodehostID: args.ValuesData.GitRepoConfig.CodehostID, - Owner: args.ValuesData.GitRepoConfig.Owner, - Repo: args.ValuesData.GitRepoConfig.Repo, - Branch: args.ValuesData.GitRepoConfig.Branch, - }, - } - if len(args.ValuesData.GitRepoConfig.ValuesPaths) > 0 { - repoData.LoadPath = args.ValuesData.GitRepoConfig.ValuesPaths[0] - } - ret.SourceDetail = repoData + ret := &templatemodels.CustomYaml{ + YamlContent: args.OverrideYaml, + } + if args.ValuesData != nil { + ret.Source = args.ValuesData.YamlSource + ret.AutoSync = args.ValuesData.AutoSync + if args.ValuesData.GitRepoConfig != nil { + repoData := &models.CreateFromRepo{ + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: args.ValuesData.GitRepoConfig.CodehostID, + Owner: args.ValuesData.GitRepoConfig.Owner, + Namespace: args.ValuesData.GitRepoConfig.Namespace, + Repo: args.ValuesData.GitRepoConfig.Repo, + Branch: args.ValuesData.GitRepoConfig.Branch, + }, + } + if len(args.ValuesData.GitRepoConfig.ValuesPaths) > 0 { + repoData.LoadPath = args.ValuesData.GitRepoConfig.ValuesPaths[0] } + ret.SourceDetail = repoData + ret.Source = setting.SourceFromGitRepo } - return ret } - return nil + return ret } func (args *RenderChartArg) fromCustomValueYaml(customValuesYaml *templatemodels.CustomYaml) { @@ -140,11 +140,7 @@ func (args *RenderChartArg) FillRenderChartModel(chart *templatemodels.RenderCha chart.ServiceName = args.ServiceName chart.ChartVersion = version chart.OverrideValues = args.ToOverrideValueString() - if len(args.OverrideYaml) > 0 { - chart.OverrideYaml = args.toCustomValuesYaml() - } else { - chart.OverrideYaml = nil - } + chart.OverrideYaml = args.toCustomValuesYaml() } // LoadFromRenderChartModel load from render chart model @@ -337,7 +333,7 @@ func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) e if rs != nil && err == nil { // 已经存在渲染配置集 // 判断是否有修改 - if rs.DefaultValues != args.DefaultValues || rs.HelmRenderDiff(args) { + if rs.DefaultValues != args.DefaultValues || rs.HelmRenderDiff(args) || !reflect.DeepEqual(rs.YamlData, args.YamlData) { args.IsDefault = rs.IsDefault } else { return nil diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go index 04cd2974..38c98efe 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/scmnotify.go @@ -56,7 +56,7 @@ func (s *Service) SendInitWebhookComment( notification := &models.Notification{ CodehostID: mainRepo.CodehostID, PrID: prID, - ProjectID: strings.TrimLeft(mainRepo.RepoOwner+"/"+mainRepo.RepoName, "/"), + ProjectID: strings.TrimLeft(mainRepo.GetRepoNamespace()+"/"+mainRepo.RepoName, "/"), BaseURI: baseURI, IsPipeline: isPipeline, IsTest: isTest, @@ -97,7 +97,7 @@ func (s *Service) SendErrWebhookComment( notification := &models.Notification{ CodehostID: mainRepo.CodehostID, PrID: prID, - ProjectID: strings.TrimLeft(mainRepo.RepoOwner+"/"+mainRepo.RepoName, "/"), + ProjectID: strings.TrimLeft(mainRepo.GetRepoNamespace()+"/"+mainRepo.RepoName, "/"), BaseURI: baseURI, IsPipeline: isPipeline, IsTest: isTest, diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 77795753..c45f0fec 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -81,6 +81,7 @@ type ServiceProductMap struct { Visibility string `json:"visibility,omitempty"` CodehostID int `json:"codehost_id"` RepoOwner string `json:"repo_owner"` + RepoNamespace string `json:"repo_namespace"` RepoName string `json:"repo_name"` RepoUUID string `json:"repo_uuid"` BranchName string `json:"branch_name"` @@ -140,38 +141,33 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm serviceObject.CodehostID = detail.ID } } - _, owner, r, branch, loadPath, _, err := GetOwnerRepoBranchPath(serviceObject.SrcPath) - if err != nil { - log.Errorf("Failed to load info from url: %s, the error is: %+v", serviceObject.SrcPath, err) - return nil, e.ErrListTemplate.AddDesc(fmt.Sprintf("Failed to load info from url: %s, the error is: %+v", serviceObject.SrcPath, err)) - } - // 万一codehost被删了,找不到 if serviceObject.CodehostID == 0 { log.Errorf("Failed to find the old code host info") return nil, e.ErrListTemplate.AddDesc("无法找到原有的codehost信息,请确认codehost仍然存在") } - serviceObject.RepoOwner = owner - serviceObject.RepoName = r - serviceObject.BranchName = branch - serviceObject.LoadPath = loadPath + + err = fillServiceRepoInfo(serviceObject) + if err != nil { + log.Errorf("Failed to load info from url: %s, the error is: %s", serviceObject.SrcPath, err) + return nil, e.ErrListTemplate.AddDesc(fmt.Sprintf("Failed to load info from url: %s, the error is: %+v", serviceObject.SrcPath, err)) + } serviceObject.LoadFromDir = true } else if serviceObject.Source == setting.SourceFromGithub && serviceObject.RepoName == "" { - address, owner, r, branch, loadPath, _, err := GetOwnerRepoBranchPath(serviceObject.SrcPath) + err = fillServiceRepoInfo(serviceObject) + if err != nil { + return nil, err + } + address, err := GetGitlabAddress(serviceObject.SrcPath) if err != nil { return nil, err } - detail, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: serviceObject.RepoOwner}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err } serviceObject.CodehostID = detail.ID - serviceObject.RepoOwner = owner - serviceObject.RepoName = r - serviceObject.BranchName = branch - serviceObject.LoadPath = loadPath serviceObject.LoadFromDir = true } @@ -185,6 +181,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm Visibility: serviceObject.Visibility, CodehostID: serviceObject.CodehostID, RepoOwner: serviceObject.RepoOwner, + RepoNamespace: serviceObject.GetRepoNamespace(), RepoName: serviceObject.RepoName, RepoUUID: serviceObject.RepoUUID, BranchName: serviceObject.BranchName, @@ -325,56 +322,51 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str } if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { - if gitlabAddress, err := GetGitlabAddress(resp.SrcPath); err == nil { - if details, err := systemconfig.New().ListCodeHostsInternal(); err == nil { - for _, detail := range details { - if strings.Contains(detail.Address, gitlabAddress) { - resp.GerritCodeHostID = detail.ID - resp.CodehostID = detail.ID - } - } - _, owner, r, branch, loadPath, pathType, err := GetOwnerRepoBranchPath(resp.SrcPath) - if err != nil { - log.Errorf("Failed to load info from url: %s, the error is: %+v", resp.SrcPath, err) - return nil, e.ErrGetService.AddDesc(fmt.Sprintf("Failed to load info from url: %s, the error is: %+v", resp.SrcPath, err)) - } - // 万一codehost被删了,找不到 - if resp.CodehostID == 0 { - log.Errorf("Failed to find the old code host info") - return nil, e.ErrListTemplate.AddDesc("无法找到原有的codehost信息,请确认codehost仍然存在") - } - resp.RepoOwner = owner - resp.RepoName = r - resp.BranchName = branch - resp.LoadPath = loadPath - resp.LoadFromDir = pathType == "tree" - return resp, nil - } - errMsg := fmt.Sprintf("[ServiceTmpl.Find] ListCodehostDetail %s error: %v", serviceName, err) + gitlabAddress, err := GetGitlabAddress(resp.SrcPath) + if err != nil { + errMsg := fmt.Sprintf("[ServiceTmpl.Find] GetGitlabAddress %s error: %s", serviceName, err) log.Error(errMsg) - } else { - errMsg := fmt.Sprintf("[ServiceTmpl.Find] GetGitlabAddress %s error: %v", serviceName, err) + return resp, e.ErrGetTemplate.AddDesc(errMsg) + } + details, err := systemconfig.New().ListCodeHostsInternal() + if err != nil { + errMsg := fmt.Sprintf("[ServiceTmpl.Find] ListCodehostDetail %s error: %s", serviceName, err) log.Error(errMsg) + return resp, e.ErrGetTemplate.AddDesc(errMsg) } - + for _, detail := range details { + if strings.Contains(detail.Address, gitlabAddress) { + resp.GerritCodeHostID = detail.ID + resp.CodehostID = detail.ID + } + } + if resp.CodehostID == 0 { + log.Errorf("Failed to find the old code host info") + return nil, e.ErrGetTemplate.AddDesc("无法找到原有的codehost信息,请确认codehost仍然存在") + } + err = fillServiceRepoInfo(resp) + if err != nil { + log.Errorf("Failed to load info from url: %s, the error is: %s", resp.SrcPath, err) + return nil, e.ErrGetService.AddDesc(fmt.Sprintf("Failed to load info from url: %s, the error is: %+v", resp.SrcPath, err)) + } + return resp, nil } else if resp.Source == setting.SourceFromGithub && resp.GerritCodeHostID == 0 { - address, owner, r, branch, loadPath, _, err := GetOwnerRepoBranchPath(resp.SrcPath) + err = fillServiceRepoInfo(resp) + if err != nil { + return nil, err + } + address, err := GetGitlabAddress(resp.SrcPath) if err != nil { return nil, err } detail, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) + &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: resp.RepoOwner}) if err != nil { log.Errorf("get github codeHostInfo failed, err:%v", err) return nil, err } resp.CodehostID = detail.ID - resp.RepoOwner = owner - resp.RepoName = r - resp.BranchName = branch - resp.LoadPath = loadPath - resp.LoadFromDir = true return resp, nil } else if resp.Source == setting.SourceFromGUI { @@ -414,7 +406,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str } } } - + resp.RepoNamespace = resp.GetRepoNamespace() return resp, nil } @@ -553,22 +545,45 @@ func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName s return } action = "add" - address := getAddressFromPath(updated.SrcPath, updated.RepoOwner, updated.RepoName, logger.Desugar()) + address, err := GetGitlabAddress(updated.SrcPath) + if err != nil { + log.Errorf("failed to parse codehost address, err: %s", err) + return + } if address == "" { return } - updatedHooks = append(updatedHooks, &webhook.WebHook{Owner: updated.RepoOwner, Repo: updated.RepoName, Address: address, Name: "trigger", CodeHostID: updated.CodehostID}) + updatedHooks = append(updatedHooks, &webhook.WebHook{ + Owner: updated.RepoOwner, + Namespace: updated.GetRepoNamespace(), + Repo: updated.RepoName, + Address: address, + Name: "trigger", + CodeHostID: updated.CodehostID, + }) } if current != nil { if !needProcessWebhook(current.Source) { return } action = "remove" - address := getAddressFromPath(current.SrcPath, current.RepoOwner, current.RepoName, logger.Desugar()) + address, err := GetGitlabAddress(current.SrcPath) + if err != nil { + log.Errorf("failed to parse codehost address, err: %s", err) + return + } + //address := getAddressFromPath(current.SrcPath, current.GetRepoNamespace(), current.RepoName, logger.Desugar()) if address == "" { return } - currentHooks = append(currentHooks, &webhook.WebHook{Owner: current.RepoOwner, Repo: current.RepoName, Address: address, Name: "trigger", CodeHostID: current.CodehostID}) + currentHooks = append(currentHooks, &webhook.WebHook{ + Owner: current.RepoOwner, + Namespace: current.GetRepoNamespace(), + Repo: current.RepoName, + Address: address, + Name: "trigger", + CodeHostID: current.CodehostID, + }) } if updated != nil && current != nil { action = "update" diff --git a/pkg/microservice/aslan/core/common/service/sets.go b/pkg/microservice/aslan/core/common/service/sets.go index d6360681..719ae234 100644 --- a/pkg/microservice/aslan/core/common/service/sets.go +++ b/pkg/microservice/aslan/core/common/service/sets.go @@ -17,7 +17,7 @@ limitations under the License. package service type hookUniqueID struct { - owner, repo, source, name string + owner, namespace, repo, source, name string } type hookItem struct { diff --git a/pkg/microservice/aslan/core/common/service/template/types.go b/pkg/microservice/aslan/core/common/service/template/types.go index 561c36e2..d9dc9a4e 100644 --- a/pkg/microservice/aslan/core/common/service/template/types.go +++ b/pkg/microservice/aslan/core/common/service/template/types.go @@ -25,6 +25,7 @@ type Chart struct { Name string `json:"name"` CodehostID int `json:"codehostID"` Owner string `json:"owner"` + Namespace string `json:"namespace"` Repo string `json:"repo"` Branch string `json:"branch"` Path string `json:"path"` diff --git a/pkg/microservice/aslan/core/common/service/utils.go b/pkg/microservice/aslan/core/common/service/utils.go index a8940492..ed2ef03e 100644 --- a/pkg/microservice/aslan/core/common/service/utils.go +++ b/pkg/microservice/aslan/core/common/service/utils.go @@ -85,8 +85,8 @@ func GetGitlabAddress(URL string) (string, error) { return fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), nil } -// GetOwnerRepoBranchPath 获取gitlab路径中的owner、repo、branch和path -func GetOwnerRepoBranchPath(URL string) (string, string, string, string, string, string, error) { +// parseOwnerRepoBranchPath parse owner、repo、branch and path from gitlab repo path +func parseOwnerRepoBranchPath(URL string) (string, string, string, string, string, string, error) { if !strings.Contains(URL, "https") && !strings.Contains(URL, "http") { return "", "", "", "", "", "", fmt.Errorf("url is illegal:%s", URL) } @@ -116,3 +116,25 @@ func GetOwnerRepoBranchPath(URL string) (string, string, string, string, string, } return address, urlPathArray[3], urlPathArray[4], "", "", pathType, nil } + +// fillServiceRepoInfo ensures the value of repo is set +func fillServiceRepoInfo(svc *models.Service) error { + if svc.RepoOwner != "" && svc.RepoName != "" && svc.BranchName != "" && svc.LoadPath != "" { + svc.RepoNamespace = svc.GetRepoNamespace() + return nil + } + var err error + pathType := "" + _, svc.RepoOwner, svc.RepoNamespace, svc.BranchName, svc.LoadPath, pathType, err = parseOwnerRepoBranchPath(svc.SrcPath) + if err != nil { + return err + } + + if svc.Source == setting.SourceFromGitlab { + svc.LoadFromDir = pathType == "tree" + } else if svc.Source == setting.SourceFromGithub { + svc.LoadFromDir = true + } + svc.RepoNamespace = svc.GetRepoNamespace() + return nil +} diff --git a/pkg/microservice/aslan/core/common/service/webhook/client.go b/pkg/microservice/aslan/core/common/service/webhook/client.go index 8bd83b93..1604bbcd 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/client.go +++ b/pkg/microservice/aslan/core/common/service/webhook/client.go @@ -43,18 +43,19 @@ func NewClient() *client { } type task struct { - ID int - owner, repo, address, token, ref, ak, sk, region string - from string - add, enableProxy bool - err error - doneCh chan struct{} + ID int + owner, namespace, repo, address, token, ref, ak, sk, region string + from string + add, enableProxy bool + err error + doneCh chan struct{} } type TaskOption struct { ID int Name string Owner string + Namespace string Repo string Address string Token string @@ -74,6 +75,7 @@ func (c *client) AddWebHook(taskOption *TaskOption) error { t := &task{ ID: taskOption.ID, owner: taskOption.Owner, + namespace: taskOption.Namespace, repo: taskOption.Repo, address: taskOption.Address, token: taskOption.Token, @@ -110,6 +112,7 @@ func (c *client) RemoveWebHook(taskOption *TaskOption) error { t := &task{ ID: taskOption.ID, owner: taskOption.Owner, + namespace: taskOption.Namespace, repo: taskOption.Repo, address: taskOption.Address, token: taskOption.Token, diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index d4f451de..b049352d 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -98,6 +98,7 @@ func (c *controller) processNextWorkItem() bool { logger := c.logger.With( zap.String("owner", t.owner), + zap.String("namespace", t.namespace), zap.String("repo", t.repo), zap.String("address", t.address), zap.String("ref", t.ref), @@ -174,7 +175,12 @@ func removeWebhook(t *task, logger *zap.Logger) { return } - webhook, err := coll.Find(t.owner, t.repo, t.address) + repoNamespace := t.namespace + if repoNamespace == "" { + repoNamespace = t.owner + } + + webhook, err := coll.Find(repoNamespace, t.repo, t.address) if err != nil { t.err = err t.doneCh <- struct{}{} @@ -185,7 +191,7 @@ func removeWebhook(t *task, logger *zap.Logger) { logger.Info("Removing webhook") //ensure safe remove, same reference may be used by multiple services - safe, err := ensureSafeDelete(t.ref, t.repo, t.owner, t.address) + safe, err := ensureSafeDelete(t.ref, t.repo, repoNamespace, t.address) if err != nil { t.err = err t.doneCh <- struct{}{} @@ -193,7 +199,7 @@ func removeWebhook(t *task, logger *zap.Logger) { } if safe { - updated, err := coll.RemoveReference(t.owner, t.repo, t.address, t.ref) + updated, err := coll.RemoveReference(repoNamespace, t.repo, t.address, t.ref) if err != nil { t.err = err t.doneCh <- struct{}{} @@ -202,7 +208,7 @@ func removeWebhook(t *task, logger *zap.Logger) { if len(updated.References) == 0 { logger.Info("Deleting webhook") - err = cl.DeleteWebHook(t.owner, t.repo, webhook.HookID) + err = cl.DeleteWebHook(repoNamespace, t.repo, webhook.HookID) if err != nil { logger.Error("Failed to delete webhook", zap.Error(err)) t.err = err @@ -210,7 +216,7 @@ func removeWebhook(t *task, logger *zap.Logger) { return } - err = coll.Delete(t.owner, t.repo, t.address) + err = coll.Delete(repoNamespace, t.repo, t.address) if err != nil { logger.Error("Failed to delete webhook record in db", zap.Error(err)) t.err = err @@ -248,8 +254,13 @@ func addWebhook(t *task, logger *zap.Logger) { return } + repoNamespace := t.namespace + if repoNamespace == "" { + repoNamespace = t.owner + } + logger.Info("Adding webhook") - created, err := coll.AddReferenceOrCreate(t.owner, t.repo, t.address, t.ref) + created, err := coll.AddReferenceOrCreate(repoNamespace, t.repo, t.address, t.ref) if err != nil || !created { t.err = err t.doneCh <- struct{}{} @@ -257,16 +268,16 @@ func addWebhook(t *task, logger *zap.Logger) { } logger.Info("Creating webhook") - hookID, err = cl.CreateWebHook(t.owner, t.repo) + hookID, err = cl.CreateWebHook(repoNamespace, t.repo) if err != nil { t.err = err logger.Error("Failed to create webhook", zap.Error(err)) - if err = coll.Delete(t.owner, t.repo, t.address); err != nil { + if err = coll.Delete(repoNamespace, t.repo, t.address); err != nil { logger.Error("Failed to delete webhook record in db", zap.Error(err)) } } else { if hookID != "" { - if err = coll.Update(t.owner, t.repo, t.address, hookID); err != nil { + if err = coll.Update(repoNamespace, t.repo, t.address, hookID); err != nil { t.err = err logger.Error("Failed to update webhook", zap.Error(err)) } diff --git a/pkg/microservice/aslan/core/common/service/webhook/types.go b/pkg/microservice/aslan/core/common/service/webhook/types.go index 4bf88744..c89dde93 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/types.go +++ b/pkg/microservice/aslan/core/common/service/webhook/types.go @@ -17,6 +17,6 @@ limitations under the License. package webhook type WebHook struct { - Owner, Repo, Address, Name string - CodeHostID int + Owner, Namespace, Repo, Address, Name string + CodeHostID int } diff --git a/pkg/microservice/aslan/core/common/service/workflow.go b/pkg/microservice/aslan/core/common/service/workflow.go index ecf88fbe..20b4971d 100644 --- a/pkg/microservice/aslan/core/common/service/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflow.go @@ -221,6 +221,7 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger ID: ch.ID, Name: wh.name, Owner: wh.owner, + Namespace: wh.namespace, Repo: wh.repo, Address: ch.Address, Token: ch.AccessToken, @@ -254,17 +255,18 @@ func ProcessWebhook(updatedHooks, currentHooks interface{}, name string, logger switch ch.Type { case setting.SourceFromGithub, setting.SourceFromGitlab, setting.SourceFromCodeHub, setting.SourceFromGitee: err = webhook.NewClient().AddWebHook(&webhook.TaskOption{ - ID: ch.ID, - Name: wh.name, - Owner: wh.owner, - Repo: wh.repo, - Address: ch.Address, - Token: ch.AccessToken, - Ref: name, - AK: ch.AccessKey, - SK: ch.SecretKey, - Region: ch.Region, - From: ch.Type, + ID: ch.ID, + Name: wh.name, + Owner: wh.owner, + Namespace: wh.namespace, + Repo: wh.repo, + Address: ch.Address, + Token: ch.AccessToken, + Ref: name, + AK: ch.AccessKey, + SK: ch.SecretKey, + Region: ch.Region, + From: ch.Type, }) if err != nil { logger.Errorf("Failed to add %s webhook %+v, err: %s", ch.Type, wh, err) @@ -287,10 +289,11 @@ func toHookSet(hooks interface{}) HookSet { for _, h := range hs { res.Insert(hookItem{ hookUniqueID: hookUniqueID{ - name: h.MainRepo.Name, - owner: h.MainRepo.RepoOwner, - repo: h.MainRepo.RepoName, - source: h.MainRepo.Source, + name: h.MainRepo.Name, + owner: h.MainRepo.RepoOwner, + namespace: h.MainRepo.GetRepoNamespace(), + repo: h.MainRepo.RepoName, + source: h.MainRepo.Source, }, codeHostID: h.MainRepo.CodehostID, }) @@ -299,9 +302,10 @@ func toHookSet(hooks interface{}) HookSet { for _, h := range hs { res.Insert(hookItem{ hookUniqueID: hookUniqueID{ - name: h.Name, - owner: h.Owner, - repo: h.Repo, + name: h.Name, + owner: h.Owner, + namespace: h.Owner, // webhooks for pipelines, no need to handler anymore + repo: h.Repo, }, codeHostID: h.CodehostID, }) @@ -310,9 +314,10 @@ func toHookSet(hooks interface{}) HookSet { for _, h := range hs { res.Insert(hookItem{ hookUniqueID: hookUniqueID{ - name: h.Name, - owner: h.Owner, - repo: h.Repo, + name: h.Name, + owner: h.Owner, + namespace: h.Namespace, + repo: h.Repo, }, codeHostID: h.CodeHostID, }) @@ -321,9 +326,10 @@ func toHookSet(hooks interface{}) HookSet { for _, h := range hs { res.Insert(hookItem{ hookUniqueID: hookUniqueID{ - name: h.MainRepo.Name, - owner: h.MainRepo.RepoOwner, - repo: h.MainRepo.RepoName, + name: h.MainRepo.Name, + owner: h.MainRepo.RepoOwner, + namespace: h.MainRepo.GetRepoNamespace(), + repo: h.MainRepo.RepoName, }, codeHostID: h.MainRepo.CodehostID, }) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 1d0af6a2..c23a077f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1513,16 +1513,14 @@ func UpdateHelmProductDefaultValues(productName, envName, userName, requestID st return e.ErrUpdateEnv.AddDesc(fmt.Sprintf("failed to query renderset for envirionment: %s", envName)) } - if productRenderset.DefaultValues == args.DefaultValues { - return nil - } - equal, err := yamlutil.Equal(productRenderset.DefaultValues, args.DefaultValues) if err != nil { return e.ErrUpdateEnv.AddErr(fmt.Errorf("failed to unmarshal default values in renderset, err: %s", err)) } productRenderset.DefaultValues = args.DefaultValues + productRenderset.YamlData = geneYamlData(args.ValuesData) + updatedRcList := make([]*templatemodels.RenderChart, 0) if !equal { for _, curRenderChart := range productRenderset.ChartInfos { @@ -1620,6 +1618,7 @@ func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml GitRepoConfig: &templatemodels.GitRepoConfig{ CodehostID: args.GitRepoConfig.CodehostID, Owner: args.GitRepoConfig.Owner, + Namespace: args.GitRepoConfig.Namespace, Repo: args.GitRepoConfig.Repo, Branch: args.GitRepoConfig.Branch, }, @@ -1627,6 +1626,7 @@ func geneYamlData(args *commonservice.ValuesDataArgs) *templatemodels.CustomYaml if len(args.GitRepoConfig.ValuesPaths) > 0 { repoData.LoadPath = args.GitRepoConfig.ValuesPaths[0] } + args.YamlSource = setting.SourceFromGitRepo } ret := &templatemodels.CustomYaml{ Source: args.YamlSource, diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 18c18db5..6a26d09f 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -17,7 +17,6 @@ limitations under the License. package service import ( - "encoding/json" "fmt" "sync" @@ -25,9 +24,9 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" @@ -43,6 +42,7 @@ type YamlContentRequestArg struct { CodehostID int `json:"codehostID" form:"codehostID"` Owner string `json:"owner" form:"owner"` Repo string `json:"repo" form:"repo"` + Namespace string `json:"namespace" form:"namespace"` Branch string `json:"branch" form:"branch"` RepoLink string `json:"repoLink" form:"repoLink"` ValuesPaths string `json:"valuesPaths" form:"valuesPaths"` @@ -58,16 +58,6 @@ func fromGitRepo(source string) bool { return false } -func unMarshalJson(source interface{}) (*models.CreateFromRepo, error) { - bs, err := json.Marshal(source) - if err != nil { - return nil, err - } - ret := &models.CreateFromRepo{} - err = json.Unmarshal(bs, ret) - return ret, err -} - // SyncYamlFromSource sync values.yaml from source // NOTE currently only support gitHub and gitlab func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (bool, string, error) { @@ -78,7 +68,7 @@ func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (b return false, "", nil } - sourceDetail, err := unMarshalJson(yamlData.SourceDetail) + sourceDetail, err := service.UnMarshalSourceDetail(yamlData.SourceDetail) if err != nil { return false, "", err } @@ -90,6 +80,7 @@ func SyncYamlFromSource(yamlData *templatemodels.CustomYaml, curValue string) (b valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, + Namespace: repoConfig.Namespace, Owner: repoConfig.Owner, Repo: repoConfig.Repo, Path: sourceDetail.LoadPath, @@ -137,6 +128,11 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def return ret, nil } ret.DefaultValues = rendersetObj.DefaultValues + err = service.FillGitNamespace(rendersetObj.YamlData) + if err != nil { + // Note, since user can always reselect the git info, error should not block normal logic + log.Warnf("failed to fill git namespace data, err: %s", err) + } ret.YamlData = rendersetObj.YamlData return ret, nil } @@ -155,6 +151,7 @@ func GetMergedYamlContent(arg *YamlContentRequestArg, paths []string) (string, e &fsservice.DownloadFromSourceArgs{ CodehostID: arg.CodehostID, Owner: arg.Owner, + Namespace: arg.Namespace, Repo: arg.Repo, Path: path, Branch: arg.Branch, diff --git a/pkg/microservice/aslan/core/service/handler/loader.go b/pkg/microservice/aslan/core/service/handler/loader.go index 812f5b70..8bd5303e 100644 --- a/pkg/microservice/aslan/core/service/handler/loader.go +++ b/pkg/microservice/aslan/core/service/handler/loader.go @@ -84,8 +84,11 @@ func LoadServiceTemplate(c *gin.Context) { remoteName := c.Query("remoteName") repoOwner := c.Query("repoOwner") - - ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, repoName, repoUUID, branchName, remoteName, args, ctx.Logger) + namespace := c.Query("namespace") + if namespace == "" { + namespace = repoOwner + } + ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, namespace, repoName, repoUUID, branchName, remoteName, args, ctx.Logger) } // ValidateServiceUpdate ... diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 0e2e2405..8051df4e 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -100,6 +100,7 @@ type helmServiceCreationArgs struct { RequestID string CodehostID int Owner string + Namespace string Repo string Branch string RepoLink string @@ -213,6 +214,37 @@ func fillServiceTemplateVariables(serviceTemplate *models.Service) error { return nil } +func fillServiceCreationInfo(serviceTemplate *models.Service) error { + if serviceTemplate.Source != setting.SourceFromChartTemplate { + return nil + } + creation, err := getCreateFromChartTemplate(serviceTemplate.CreateFrom) + if err != nil { + return fmt.Errorf("failed to get creation detail: %s", err) + } + + if creation.YamlData == nil || creation.YamlData.Source != setting.SourceFromGitRepo { + return nil + } + + bs, err := json.Marshal(creation.YamlData.SourceDetail) + if err != nil { + return err + } + cfr := &models.CreateFromRepo{} + err = json.Unmarshal(bs, cfr) + if err != nil { + return err + } + if cfr.GitRepoConfig == nil { + return nil + } + cfr.GitRepoConfig.Namespace = cfr.GitRepoConfig.GetNamespace() + creation.YamlData.SourceDetail = cfr + serviceTemplate.CreateFrom = creation + return nil +} + func GetHelmServiceModule(serviceName, productName string, revision int64, log *zap.SugaredLogger) (*HelmServiceModule, error) { serviceTemplate, err := commonservice.GetServiceTemplate(serviceName, setting.HelmDeployType, productName, setting.ProductStatusDeleting, revision, log) if err != nil { @@ -240,6 +272,12 @@ func GetHelmServiceModule(serviceName, productName string, revision int64, log * // NOTE source template may be deleted, error should not block the following logic log.Warnf("failed to fill service template variables for service: %s, err: %s", serviceTemplate.ServiceName, err) } + err = fillServiceCreationInfo(serviceTemplate) + if err != nil { + // NOTE since the source of yaml can always be selected when reloading, error should not block the following logic + log.Warnf("failed to fill git namespace for yaml source : %s, err: %s", serviceTemplate.ServiceName, err) + } + helmServiceModule.Service = serviceTemplate serviceTemplate.ReleaseNaming = serviceTemplate.GetReleaseNaming() helmServiceModule.ServiceModules = serviceModules @@ -754,22 +792,28 @@ func CreateOrUpdateHelmServiceFromRepo(projectName string, args *HelmServiceCrea } helmServiceCreationArgs := &helmServiceCreationArgs{ - ChartName: serviceName, - ChartVersion: chartVersion, - ServiceRevision: rev, - MergedValues: string(valuesYAML), - ServiceName: serviceName, - FilePath: filePath, - ProductName: projectName, - CreateBy: args.CreatedBy, - RequestID: args.RequestID, - CodehostID: createFromRepo.CodehostID, - Owner: createFromRepo.Owner, - Repo: createFromRepo.Repo, - Branch: createFromRepo.Branch, - Source: string(args.Source), - RepoLink: repoLink, - GiteePath: currentFilePath, + ChartName: serviceName, + ChartVersion: chartVersion, + ServiceRevision: rev, + MergedValues: string(valuesYAML), + ServiceName: serviceName, + FilePath: filePath, + ProductName: projectName, + CreateBy: args.CreatedBy, + RequestID: args.RequestID, + CodehostID: createFromRepo.CodehostID, + Owner: createFromRepo.Owner, + Namespace: createFromRepo.Namespace, + Repo: createFromRepo.Repo, + Branch: createFromRepo.Branch, + Source: string(args.Source), + RepoLink: repoLink, + GiteePath: currentFilePath, + GerritCodeHostID: createFromRepo.CodehostID, + GerritPath: currentFilePath, + GerritRepoName: createFromRepo.Repo, + GerritBranchName: createFromRepo.Branch, + GerritRemoteName: "origin", } if string(args.Source) == setting.SourceFromGerrit { @@ -862,7 +906,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC log.Infof("Loading chart under path %s", filePath) fsTree, err := fsservice.DownloadFilesFromSource( - &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, + &fsservice.DownloadFromSourceArgs{CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, Namespace: repoArgs.Namespace, Repo: repoArgs.Repo, Path: filePath, Branch: repoArgs.Branch, RepoLink: repoLink}, func(chartTree afero.Fs) (string, error) { var err error serviceName, chartVersion, err = readChartYAML(afero.NewIOFS(chartTree), filepath.Base(filePath), log) @@ -909,7 +953,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC } if source != setting.SourceFromPublicRepo && codehostInfo != nil { - repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Owner, repoArgs.Repo, "tree", repoArgs.Branch, filePath) + repoLink = fmt.Sprintf("%s/%s/%s/%s/%s/%s", codehostInfo.Address, repoArgs.Namespace, repoArgs.Repo, "tree", repoArgs.Branch, filePath) } svc, err := createOrUpdateHelmService( @@ -926,6 +970,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC RequestID: args.RequestID, CodehostID: repoArgs.CodehostID, Owner: repoArgs.Owner, + Namespace: repoArgs.Namespace, Repo: repoArgs.Repo, Branch: repoArgs.Branch, RepoLink: repoLink, @@ -1042,6 +1087,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi CodehostID: repoConfig.CodehostID, Owner: repoConfig.Owner, Repo: repoConfig.Repo, + Namespace: repoConfig.Namespace, Path: path, Branch: repoConfig.Branch, }) @@ -1203,6 +1249,7 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { Owner: args.Owner, Repo: args.Repo, Branch: args.Branch, + Namespace: args.Namespace, }, LoadPath: args.FilePath, } @@ -1223,13 +1270,15 @@ func geneCreationDetail(args *helmServiceCreationArgs) interface{} { }) } if args.ValuesSource != nil && args.ValuesSource.GitRepoConfig != nil { - yamlData.Source = args.ValuesSource.YamlSource + //yamlData.Source = args.ValuesSource.YamlSource + yamlData.Source = setting.SourceFromGitRepo repoData := &models.CreateFromRepo{ GitRepoConfig: &templatemodels.GitRepoConfig{ CodehostID: args.ValuesSource.GitRepoConfig.CodehostID, Owner: args.ValuesSource.GitRepoConfig.Owner, Repo: args.ValuesSource.GitRepoConfig.Repo, Branch: args.ValuesSource.GitRepoConfig.Branch, + Namespace: args.ValuesSource.GitRepoConfig.Namespace, }, } if len(args.ValuesSource.GitRepoConfig.ValuesPaths) > 0 { @@ -1324,6 +1373,7 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg Containers: containerList, CodehostID: args.CodehostID, RepoOwner: args.Owner, + RepoNamespace: args.Namespace, RepoName: args.Repo, BranchName: args.Branch, LoadPath: args.FilePath, diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 8cde9f82..7c2d0422 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -75,7 +75,7 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b } // LoadServiceFromCodeHost 根据提供的codehost信息加载服务 -func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoName, repoUUID, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, namespace, repoName, repoUUID, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("Failed to load codehost for preload service list, the error is: %+v", err) @@ -83,7 +83,7 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, repoNam } switch ch.Type { case setting.SourceFromGithub, setting.SourceFromGitlab: - return loadService(username, ch, repoOwner, repoName, branchName, args, log) + return loadService(username, ch, repoOwner, namespace, repoName, branchName, args, log) case setting.SourceFromGerrit: return loadGerritService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) case setting.SourceFromCodeHub: @@ -124,7 +124,7 @@ func preloadGerritService(detail *systemconfig.CodeHost, repoName, branchName, r base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(detail, setting.GerritDefaultOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(detail, setting.GerritDefaultOwner, setting.GerritDefaultOwner, repoName, branchName, remoteName) if err != nil { return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) } @@ -243,7 +243,7 @@ func preloadGiteeService(detail *systemconfig.CodeHost, repoOwner, repoName, bra base := path.Join(config.S3StoragePath(), repoName) if exist, err := util.PathExists(base); !exist { log.Warnf("path does not exist,err:%s", err) - err = command.RunGitCmds(detail, repoOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(detail, repoOwner, repoOwner, repoName, branchName, remoteName) if err != nil { return nil, e.ErrPreloadServiceTemplate.AddDesc(err.Error()) } @@ -305,7 +305,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re } base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(ch, repoOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(ch, repoOwner, repoOwner, repoName, branchName, remoteName) if err != nil { return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } @@ -338,6 +338,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re CodehostID: ch.ID, RepoName: repoName, RepoOwner: repoOwner, + RepoNamespace: repoOwner, // TODO gerrit need set namespace? BranchName: branchName, LoadPath: args.LoadPath, LoadFromDir: args.LoadFromDir, @@ -459,23 +460,24 @@ func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, r srcPath := fmt.Sprintf("%s/%s/%s/blob/%s/%s", ch.Address, repoOwner, repoName, branchName, args.LoadPath) createSvcArgs := &models.Service{ - CodehostID: ch.ID, - RepoOwner: repoOwner, - RepoName: repoName, - RepoUUID: repoUUID, - BranchName: branchName, - SrcPath: srcPath, - LoadPath: args.LoadPath, - LoadFromDir: args.LoadFromDir, - KubeYamls: yamls, - CreateBy: username, - ServiceName: getFileName(args.LoadPath), - Type: args.Type, - ProductName: args.ProductName, - Source: ch.Type, - Yaml: util.CombineManifests(yamls), - Commit: &models.Commit{SHA: commit.ID, Message: commit.Message}, - Visibility: args.Visibility, + CodehostID: ch.ID, + RepoOwner: repoOwner, + RepoNamespace: repoOwner, // TODO codehub need fill namespace? + RepoName: repoName, + RepoUUID: repoUUID, + BranchName: branchName, + SrcPath: srcPath, + LoadPath: args.LoadPath, + LoadFromDir: args.LoadFromDir, + KubeYamls: yamls, + CreateBy: username, + ServiceName: getFileName(args.LoadPath), + Type: args.Type, + ProductName: args.ProductName, + Source: ch.Type, + Yaml: util.CombineManifests(yamls), + Commit: &models.Commit{SHA: commit.ID, Message: commit.Message}, + Visibility: args.Visibility, } if _, err = CreateServiceTemplate(username, createSvcArgs, log); err != nil { log.Errorf("Failed to create service template, serviceName:%s error: %s", createSvcArgs.ServiceName, err) @@ -573,7 +575,7 @@ func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, rep } base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(ch, repoOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(ch, repoOwner, repoName, repoName, branchName, remoteName) if err != nil { return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } @@ -805,7 +807,7 @@ func validateServiceUpdateGitlab(detail *systemconfig.CodeHost, serviceName, rep func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, repoName, branchName, remoteName, loadPath string, isDir bool) error { base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(detail, setting.GerritDefaultOwner, repoName, branchName, remoteName) + err = command.RunGitCmds(detail, setting.GerritDefaultOwner, setting.GerritDefaultOwner, repoName, branchName, remoteName) if err != nil { return e.ErrValidateServiceUpdate.AddDesc(err.Error()) } @@ -891,7 +893,7 @@ func validateServiceUpdateGitee(detail *systemconfig.CodeHost, serviceName, repo base := path.Join(config.S3StoragePath(), repoName) if exist, err := util.PathExists(base); !exist { log.Warnf("path does not exist,err:%s", err) - err := command.RunGitCmds(detail, repoOwner, repoName, branchName, remoteName) + err := command.RunGitCmds(detail, repoOwner, repoOwner, repoName, branchName, remoteName) if err != nil { return e.ErrValidateServiceUpdate.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/service/service/new_loader.go b/pkg/microservice/aslan/core/service/service/new_loader.go index 120dd7d1..3b2f3e10 100644 --- a/pkg/microservice/aslan/core/service/service/new_loader.go +++ b/pkg/microservice/aslan/core/service/service/new_loader.go @@ -95,8 +95,8 @@ type serviceInfo struct { yamls []string } -func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch string, args *LoadServiceReq, logger *zap.SugaredLogger) error { - logger.Infof("Loading service from %s with owner %s, repo %s, branch %s and path %s", ch.Type, owner, repo, branch, args.LoadPath) +func loadService(username string, ch *systemconfig.CodeHost, owner, namespace, repo, branch string, args *LoadServiceReq, logger *zap.SugaredLogger) error { + logger.Infof("Loading service from %s with owner %s, namespace %s, repo %s, branch %s and path %s", ch.Type, owner, namespace, repo, branch, args.LoadPath) project, err := templaterepo.NewProductColl().Find(args.ProductName) if err != nil { @@ -112,7 +112,7 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch var services []serviceInfo if !args.LoadFromDir { - yamls, err := loader.GetYAMLContents(owner, repo, args.LoadPath, branch, false, true) + yamls, err := loader.GetYAMLContents(namespace, repo, args.LoadPath, branch, false, true) if err != nil { logger.Errorf("Failed to get yamls under path %s, err: %s", args.LoadPath, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -120,7 +120,7 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch services = []serviceInfo{{path: args.LoadPath, isDir: false, yamls: yamls}} } else { - treeNodes, err := loader.GetTree(owner, repo, args.LoadPath, branch) + treeNodes, err := loader.GetTree(namespace, repo, args.LoadPath, branch) if err != nil { logger.Errorf("Failed to get tree under path %s, err: %s", args.LoadPath, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -133,7 +133,7 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch if len(files) > 0 { var yamls []string for _, f := range files { - res, err := loader.GetYAMLContents(owner, repo, f.FullPath, branch, false, true) + res, err := loader.GetYAMLContents(namespace, repo, f.FullPath, branch, false, true) if err != nil { logger.Errorf("Failed to get yamls under path %s, err: %s", f.FullPath, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -144,7 +144,7 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch services = []serviceInfo{{path: args.LoadPath, isDir: true, yamls: yamls}} } else if len(folders) > 0 { for _, f := range folders { - res, err := loader.GetYAMLContents(owner, repo, f.FullPath, branch, true, true) + res, err := loader.GetYAMLContents(namespace, repo, f.FullPath, branch, true, true) if err != nil { logger.Errorf("Failed to get yamls under path %s, err: %s", f.FullPath, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -165,7 +165,7 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch return e.ErrInvalidParam.AddDesc(fmt.Sprintf("A service with same name %s is already existing", serviceName)) } - commit, err := loader.GetLatestRepositoryCommit(owner, repo, info.path, branch) + commit, err := loader.GetLatestRepositoryCommit(namespace, repo, info.path, branch) if err != nil { logger.Errorf("Failed to get latest commit under path %s, error: %s", info.path, err) return e.ErrLoadServiceTemplate.AddDesc(err.Error()) @@ -176,22 +176,23 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, repo, branch pathType = "blob" } createSvcArgs := &models.Service{ - CodehostID: ch.ID, - RepoName: repo, - RepoOwner: owner, - BranchName: branch, - LoadPath: info.path, - LoadFromDir: info.isDir, - KubeYamls: info.yamls, - SrcPath: fmt.Sprintf("%s/%s/%s/%s/%s/%s", ch.Address, owner, repo, pathType, branch, info.path), - CreateBy: username, - ServiceName: serviceName, - Type: args.Type, - ProductName: args.ProductName, - Source: ch.Type, - Yaml: util.CombineManifests(info.yamls), - Commit: &models.Commit{SHA: commit.SHA, Message: commit.Message}, - Visibility: args.Visibility, + CodehostID: ch.ID, + RepoName: repo, + RepoOwner: owner, + RepoNamespace: namespace, + BranchName: branch, + LoadPath: info.path, + LoadFromDir: info.isDir, + KubeYamls: info.yamls, + SrcPath: fmt.Sprintf("%s/%s/%s/%s/%s/%s", ch.Address, namespace, repo, pathType, branch, info.path), + CreateBy: username, + ServiceName: serviceName, + Type: args.Type, + ProductName: args.ProductName, + Source: ch.Type, + Yaml: util.CombineManifests(info.yamls), + Commit: &models.Commit{SHA: commit.SHA, Message: commit.Message}, + Visibility: args.Visibility, } _, err = CreateServiceTemplate(username, createSvcArgs, logger) if err != nil { diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 3db878e1..56fe6582 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1323,7 +1323,7 @@ func GetGerritServiceYaml(args *commonmodels.Service, log *zap.SugaredLogger) er base := path.Join(config.S3StoragePath(), args.GerritRepoName) if _, err := os.Stat(base); os.IsNotExist(err) { - err = command.RunGitCmds(codehostDetail, setting.GerritDefaultOwner, args.GerritRepoName, args.GerritBranchName, args.GerritRemoteName) + err = command.RunGitCmds(codehostDetail, setting.GerritDefaultOwner, setting.GerritDefaultOwner, args.GerritRepoName, args.GerritBranchName, args.GerritRemoteName) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/service/service/types.go b/pkg/microservice/aslan/core/service/service/types.go index 7dc605b5..027b29b8 100644 --- a/pkg/microservice/aslan/core/service/service/types.go +++ b/pkg/microservice/aslan/core/service/service/types.go @@ -71,6 +71,7 @@ type BulkHelmServiceCreationResponse struct { type CreateFromRepo struct { CodehostID int `json:"codehostID"` Owner string `json:"owner"` + Namespace string `json:"namespace"` Repo string `json:"repo"` Branch string `json:"branch"` Paths []string `json:"paths"` diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 109c8838..03448762 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -94,6 +94,7 @@ func GetChartTemplate(name string, logger *zap.SugaredLogger) (*template.Chart, Repo: chart.Repo, Path: chart.Path, Branch: chart.Branch, + Namespace: chart.GetNamespace(), Files: fis, Variables: variables, }, nil @@ -140,6 +141,7 @@ func ListChartTemplates(logger *zap.SugaredLogger) (*ChartTemplateListResp, erro Name: c.Name, CodehostID: c.CodeHostID, Owner: c.Owner, + Namespace: c.GetNamespace(), Repo: c.Repo, Path: c.Path, Branch: c.Branch, @@ -238,6 +240,7 @@ func AddChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *zap. return mongodb.NewChartColl().Create(&models.Chart{ Name: name, Owner: args.Owner, + Namespace: args.Namespace, Repo: args.Repo, Path: args.Path, Branch: args.Branch, @@ -302,6 +305,7 @@ func UpdateChartTemplate(name string, args *fs.DownloadFromSourceArgs, logger *z err = mongodb.NewChartColl().Update(&models.Chart{ Name: name, Owner: args.Owner, + Namespace: args.Namespace, Repo: args.Repo, Path: args.Path, Branch: args.Branch, @@ -347,6 +351,7 @@ func UpdateChartTemplateVariables(name string, args []*commonmodels.Variable, lo err = mongodb.NewChartColl().Update(&models.Chart{ Name: name, Owner: chart.Owner, + Namespace: chart.Namespace, Repo: chart.Repo, Path: chart.Path, Branch: chart.Branch, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go index c88a216f..a1c2ed2c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/codehub_workflow_task.go @@ -60,11 +60,12 @@ func (cmem *codehubMergeEventMatcher) UpdateTaskArgs( } args = factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - PR: cmem.event.ObjectAttributes.IID, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, + PR: cmem.event.ObjectAttributes.IID, }) return args @@ -97,10 +98,11 @@ func (cpem *codehubPushEventMatcher) UpdateTaskArgs( } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, }) return args @@ -199,6 +201,7 @@ func TriggerWorkflowByCodehubEvent(event interface{}, baseURI, requestID string, args.Source = setting.SourceFromCodeHub args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner + args.RepoNamespace = item.MainRepo.GetRepoNamespace() args.RepoName = item.MainRepo.RepoName args.Committer = item.MainRepo.Committer // 3. create task with args diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index 825df7af..6b52a496 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -101,7 +101,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro errs = multierror.Append(errs, err) } newRepoName := fmt.Sprintf("%s-new", service.GerritRepoName) - err = command.RunGitCmds(detail, setting.GerritDefaultOwner, newRepoName, service.GerritBranchName, service.GerritRemoteName) + err = command.RunGitCmds(detail, setting.GerritDefaultOwner, setting.GerritDefaultOwner, newRepoName, service.GerritBranchName, service.GerritRemoteName) if err != nil { log.Errorf("updateServiceTemplateByGerritEvent runGitCmds err:%v", err) errs = multierror.Append(errs, err) @@ -115,7 +115,7 @@ func updateServiceTemplateByGerritEvent(uri string, log *zap.SugaredLogger) erro oldBase, err := GetGerritWorkspaceBasePath(service.GerritRepoName) if err != nil { errs = multierror.Append(errs, err) - err = command.RunGitCmds(detail, setting.GerritDefaultOwner, service.GerritRepoName, service.GerritBranchName, service.GerritRemoteName) + err = command.RunGitCmds(detail, setting.GerritDefaultOwner, setting.GerritDefaultOwner, service.GerritRepoName, service.GerritBranchName, service.GerritRemoteName) if err != nil { errs = multierror.Append(errs, err) } @@ -310,6 +310,7 @@ func reloadServiceTmplFromGerrit(svc *commonmodels.Service, log *zap.SugaredLogg CreateFrom: &service.CreateFromRepo{ CodehostID: svc.CodehostID, Owner: svc.RepoOwner, + Namespace: svc.GetRepoNamespace(), Repo: svc.RepoName, Branch: svc.BranchName, Paths: []string{svc.LoadPath}, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go index eb35f164..5261c714 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit_workflow_task.go @@ -195,10 +195,11 @@ func (gruem *gerritChangeMergedEventMatcher) UpdateTaskArgs( } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, }) return args @@ -237,11 +238,12 @@ func (gpcem *gerritPatchsetCreatedEventMatcher) UpdateTaskArgs(product *commonmo } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - PR: gpcem.Event.Change.Number, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, + PR: gpcem.Event.Change.Number, }) return args @@ -381,6 +383,7 @@ func TriggerWorkflowByGerritEvent(event *gerritTypeEvent, body []byte, uri, base workflowArgs.Source = setting.SourceFromGerrit workflowArgs.CodehostID = item.MainRepo.CodehostID workflowArgs.RepoOwner = item.MainRepo.RepoOwner + workflowArgs.RepoNamespace = item.MainRepo.GetRepoNamespace() workflowArgs.RepoName = item.MainRepo.RepoName workflowArgs.Committer = item.MainRepo.Committer if resp, err := workflowservice.CreateWorkflowTask(workflowArgs, setting.WebhookTaskCreator, log); err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index c2b49ddc..0ceaf90e 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -167,7 +167,7 @@ func updateServiceTemplateByGiteeEvent(uri string, log *zap.SugaredLogger) error errs = multierror.Append(errs, err) } newRepoName := fmt.Sprintf("%s-new", service.RepoName) - err = command.RunGitCmds(detail, service.RepoOwner, newRepoName, service.BranchName, "origin") + err = command.RunGitCmds(detail, service.RepoOwner, service.GetRepoNamespace(), newRepoName, service.BranchName, "origin") if err != nil { log.Errorf("failed to run git cmds err:%s", err) errs = multierror.Append(errs, err) @@ -181,7 +181,7 @@ func updateServiceTemplateByGiteeEvent(uri string, log *zap.SugaredLogger) error oldBase, err := GetWorkspaceBasePath(service.RepoName) if err != nil { errs = multierror.Append(errs, err) - err = command.RunGitCmds(detail, service.RepoOwner, service.RepoName, service.BranchName, "origin") + err = command.RunGitCmds(detail, service.RepoOwner, service.GetRepoNamespace(), service.RepoName, service.BranchName, "origin") if err != nil { errs = multierror.Append(errs, err) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index b71cd36c..f675b2b5 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -199,6 +199,7 @@ func prEventToPipelineTasks(event *github.PullRequestEvent, requestID string, lo eventRepo := &types.Repository{ RepoOwner: owner, + RepoNamespace: owner, RepoName: repo, Branch: branch, PR: prNum, @@ -331,6 +332,7 @@ func pushEventToPipelineTasks(event *github.PushEvent, requestID string, log *za eventRepo := &types.Repository{ RepoOwner: owner, + RepoNamespace: owner, RepoName: repo, Branch: branch, CommitID: commitID, @@ -631,6 +633,8 @@ func GetGithubServiceTemplates() ([]*commonmodels.Service, error) { } // GetOwnerRepoBranchPath 获取gitlab路径中的owner、repo、branch和path +// return address, owner, repo, branch, path, pathType +// Note this function needs be optimized badly! func GetOwnerRepoBranchPath(URL string) (string, string, string, string, string, string, error) { if !strings.Contains(URL, "https") && !strings.Contains(URL, "http") { return "", "", "", "", "", "", fmt.Errorf("url is illegal") diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go index f27b978c..8478e0a8 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_testing_task.go @@ -98,32 +98,31 @@ type githubMergeEventMatcherForTesting struct { func (gpem *githubPushEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { - if !EventConfigured(hookRepo, config.HookEventPush) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { - return false, nil - } + if !checkRepoNamespaceMatch(hookRepo, *ev.Repo.FullName) { + return false, nil + } + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { - return false, nil - } - } - hookRepo.Branch = getBranchFromRef(*ev.Ref) - var changedFiles []string - for _, commit := range ev.Commits { - changedFiles = append(changedFiles, commit.Added...) - changedFiles = append(changedFiles, commit.Removed...) - changedFiles = append(changedFiles, commit.Modified...) + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { + return false, nil } - - return MatchChanges(hookRepo, changedFiles), nil + } + hookRepo.Branch = getBranchFromRef(*ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) } - return false, nil + return MatchChanges(hookRepo, changedFiles), nil } func (gpem *githubPushEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { @@ -144,26 +143,25 @@ type githubTagEventMatcherForTesting struct { func (gtem githubTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gtem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { - if !EventConfigured(hookRepo, config.HookEventTag) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != *ev.Repo.DefaultBranch { - return false, nil - } + if !checkRepoNamespaceMatch(hookRepo, *ev.Repo.FullName) { + return false, nil + } + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != *ev.Repo.DefaultBranch { + return false, nil + } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.Repo.DefaultBranch); !matched { - return false, nil - } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.Repo.DefaultBranch); !matched { + return false, nil } - hookRepo.Branch = *ev.Repo.DefaultBranch - - return true, nil } + hookRepo.Branch = *ev.Repo.DefaultBranch - return false, nil + return true, nil } func (gtem githubTagEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { @@ -207,35 +205,37 @@ func createGithubEventMatcherForTesting( func (gmem *githubMergeEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event // TODO: match codehost - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { - if !EventConfigured(hookRepo, config.HookEventPr) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { - return false, nil - } + if !checkRepoNamespaceMatch(hookRepo, *ev.PullRequest.Base.Repo.FullName) { + return false, nil + } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.PullRequest.Base.Ref); !matched { - return false, nil - } + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.PullRequest.Base.Ref); !matched { + return false, nil } - hookRepo.Branch = *ev.PullRequest.Base.Ref - - if *ev.PullRequest.State == "open" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v", ev) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + } + hookRepo.Branch = *ev.PullRequest.Base.Ref - return MatchChanges(hookRepo, changedFiles), nil + if *ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + return MatchChanges(hookRepo, changedFiles), nil } return false, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index 7eb88708..bea847ad 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -54,33 +54,34 @@ type githubPushEventMatcher struct { func (gpem *githubPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { - if !EventConfigured(hookRepo, config.HookEventPush) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + if !checkRepoNamespaceMatch(hookRepo, *ev.Repo.FullName) { + return false, nil + } + + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } + if isRegular { + // Do not use regexp.MustCompile to avoid panic + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { return false, nil } - if isRegular { - // Do not use regexp.MustCompile to avoid panic - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(*ev.Ref)); !matched { - return false, nil - } - } - hookRepo.Branch = getBranchFromRef(*ev.Ref) - hookRepo.Committer = *ev.Pusher.Name - var changedFiles []string - for _, commit := range ev.Commits { - changedFiles = append(changedFiles, commit.Added...) - changedFiles = append(changedFiles, commit.Removed...) - changedFiles = append(changedFiles, commit.Modified...) - } - return MatchChanges(hookRepo, changedFiles), nil } - - return false, nil + hookRepo.Branch = getBranchFromRef(*ev.Ref) + hookRepo.Committer = *ev.Pusher.Name + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + return MatchChanges(hookRepo, changedFiles), nil } func getBranchFromRef(ref string) string { @@ -109,10 +110,11 @@ func (gpem *githubPushEventMatcher) UpdateTaskArgs( } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, }) return args @@ -127,34 +129,38 @@ type githubMergeEventMatcher struct { func (gmem *githubMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.PullRequest.Base.Repo.FullName { - if !EventConfigured(hookRepo, config.HookEventPr) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { + if !checkRepoNamespaceMatch(hookRepo, *ev.PullRequest.Base.Repo.FullName) { + return false, nil + } + + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != *ev.PullRequest.Base.Ref { + return false, nil + } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.PullRequest.Base.Ref); !matched { return false, nil } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.PullRequest.Base.Ref); !matched { - return false, nil - } + } + hookRepo.Branch = *ev.PullRequest.Base.Ref + hookRepo.Committer = *ev.PullRequest.User.Login + if *ev.PullRequest.State == "open" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err } - hookRepo.Branch = *ev.PullRequest.Base.Ref - hookRepo.Committer = *ev.PullRequest.User.Login - if *ev.PullRequest.State == "open" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v", ev) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) - return MatchChanges(hookRepo, changedFiles), nil - } + return MatchChanges(hookRepo, changedFiles), nil } + return false, nil } @@ -167,11 +173,12 @@ func (gmem *githubMergeEventMatcher) UpdateTaskArgs( } args = factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - PR: *gmem.event.PullRequest.Number, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, + PR: *gmem.event.PullRequest.Number, }) return args @@ -241,28 +248,29 @@ type githubTagEventMatcher struct { func (gtem githubTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gtem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == *ev.Repo.FullName { - if !EventConfigured(hookRepo, config.HookEventTag) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != *ev.Repo.DefaultBranch { + if !checkRepoNamespaceMatch(hookRepo, *ev.Repo.FullName) { + return false, nil + } + + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != *ev.Repo.DefaultBranch { + return false, nil + } + if isRegular { + // Do not use regexp.MustCompile to avoid panic + if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.Repo.DefaultBranch); !matched { return false, nil } - if isRegular { - // Do not use regexp.MustCompile to avoid panic - if matched, _ := regexp.MatchString(hookRepo.Branch, *ev.Repo.DefaultBranch); !matched { - return false, nil - } - } - hookRepo.Tag = getTagFromRef(*ev.Ref) - hookRepo.Committer = *ev.Sender.Name - - return true, nil } + hookRepo.Tag = getTagFromRef(*ev.Ref) + hookRepo.Committer = *ev.Sender.Name - return false, nil + return true, nil } func (gtem githubTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { @@ -272,11 +280,12 @@ func (gtem githubTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - Tag: hookRepo.Tag, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, + Tag: hookRepo.Tag, }) return args @@ -385,6 +394,7 @@ func TriggerWorkflowByGithubEvent(event interface{}, baseURI, deliveryID, reques args.Source = setting.SourceFromGithub args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner + args.RepoNamespace = item.MainRepo.GetRepoNamespace() args.RepoName = item.MainRepo.RepoName args.Committer = item.MainRepo.Committer args.HookPayload = hookPayload diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go index 09b7ee60..da9380f6 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab.go @@ -293,8 +293,7 @@ func updateServiceTemplateByPushEvent(diffs []string, pathWithNamespace string, errs := &multierror.Error{} for _, service := range serviceTmpls { - // TODO need optimize, use repo namespace - if service.RepoOwner+"/"+service.RepoName != pathWithNamespace { + if service.GetRepoNamespace()+"/"+service.RepoName != pathWithNamespace { continue } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go index 1187f224..d0154307 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_pipeline_task.go @@ -46,7 +46,7 @@ func (pgpem *pipelineGitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHo var ( changedFiles = make([]string, 0) ) - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { + if checkRepoNamespaceMatch(hookRepo, ev.Project.PathWithNamespace) { if hookRepo.Branch == getBranchFromRef(ev.Ref) && EventConfigured(hookRepo, config.HookEventPush) { for _, commit := range ev.Commits { changedFiles = append(changedFiles, commit.Added...) @@ -65,6 +65,7 @@ func (pgpem *pipelineGitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHo eventRepo := &types.Repository{ RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), RepoName: hookRepo.RepoName, Branch: branch, CommitID: commitID, @@ -107,7 +108,7 @@ func (pgmem *pipelineGitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainH err error changedFiles = make([]string, 0) ) - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { + if checkRepoNamespaceMatch(hookRepo, ev.ObjectAttributes.Target.PathWithNamespace) { if EventConfigured(hookRepo, config.HookEventPr) && (hookRepo.Branch == ev.ObjectAttributes.TargetBranch) { if ev.ObjectAttributes.State == "opened" { changedFiles, err = pgmem.diffFunc(ev, hookRepo.CodehostID) @@ -128,6 +129,7 @@ func (pgmem *pipelineGitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainH eventRepo := &types.Repository{ RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), RepoName: hookRepo.RepoName, PR: ev.ObjectAttributes.IID, Branch: branch, diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go index e29b6322..eb50b12c 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_testing_task.go @@ -59,32 +59,30 @@ type gitlabPushEventMatcherForTesting struct { func (gpem *gitlabPushEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if !EventConfigured(hookRepo, config.HookEventPush) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { - return false, nil - } + if !checkRepoNamespaceMatch(hookRepo, ev.Project.PathWithNamespace) { + return false, nil + } + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { - return false, nil - } - } - hookRepo.Branch = getBranchFromRef(ev.Ref) - var changedFiles []string - for _, commit := range ev.Commits { - changedFiles = append(changedFiles, commit.Added...) - changedFiles = append(changedFiles, commit.Removed...) - changedFiles = append(changedFiles, commit.Modified...) + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { + return false, nil } - - return MatchChanges(hookRepo, changedFiles), nil } - - return false, nil + hookRepo.Branch = getBranchFromRef(ev.Ref) + var changedFiles []string + for _, commit := range ev.Commits { + changedFiles = append(changedFiles, commit.Added...) + changedFiles = append(changedFiles, commit.Removed...) + changedFiles = append(changedFiles, commit.Modified...) + } + return MatchChanges(hookRepo, changedFiles), nil } func (gpem *gitlabPushEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { @@ -105,25 +103,26 @@ type gitlabTagEventMatcherForTesting struct { func (gtem gitlabTagEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gtem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if !EventConfigured(hookRepo, config.HookEventTag) { - return false, nil - } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { - return false, nil - } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { - return false, nil - } - } - hookRepo.Branch = ev.Project.DefaultBranch - return true, nil + if !checkRepoNamespaceMatch(hookRepo, ev.Project.PathWithNamespace) { + return false, nil } - return false, nil + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { + return false, nil + } + } + hookRepo.Branch = ev.Project.DefaultBranch + return true, nil } func (gtem gitlabTagEventMatcherForTesting) UpdateTaskArgs(args *commonmodels.TestTaskArgs, requestID string) *commonmodels.TestTaskArgs { @@ -206,6 +205,7 @@ func TriggerTestByGitlabEvent(event interface{}, baseURI, requestID string, log args.Source = setting.SourceFromGitlab args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner + args.RepoNamespace = item.MainRepo.GetRepoNamespace() args.RepoName = item.MainRepo.RepoName // 3. create task with args @@ -263,35 +263,36 @@ type gitlabMergeEventMatcherForTesting struct { func (gmem *gitlabMergeEventMatcherForTesting) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event // TODO: match codehost - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if !EventConfigured(hookRepo, config.HookEventPr) { - return false, nil - } + if !checkRepoNamespaceMatch(hookRepo, ev.ObjectAttributes.Target.PathWithNamespace) { + return false, nil + } - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { - return false, nil - } + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { - return false, nil - } + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { + return false, nil + } + + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { + return false, nil } - hookRepo.Branch = ev.ObjectAttributes.TargetBranch - - if ev.ObjectAttributes.State == "opened" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v", ev) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + } + hookRepo.Branch = ev.ObjectAttributes.TargetBranch - return MatchChanges(hookRepo, changedFiles), nil + if ev.ObjectAttributes.State == "opened" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v", ev) + return false, err } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + return MatchChanges(hookRepo, changedFiles), nil } return false, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 02881664..98fcdba4 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -61,49 +61,50 @@ type gitlabMergeEventMatcher struct { func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gmem.event // TODO: match codehost - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.ObjectAttributes.Target.PathWithNamespace { - if !EventConfigured(hookRepo, config.HookEventPr) { + if !checkRepoNamespaceMatch(hookRepo, ev.ObjectAttributes.Target.PathWithNamespace) { + return false, nil + } + if !EventConfigured(hookRepo, config.HookEventPr) { + return false, nil + } + if gmem.isYaml { + refFlag := false + for _, ref := range gmem.trigger.Rules.Branchs { + if matched, _ := regexp.MatchString(ref, getBranchFromRef(hookRepo.Branch)); matched { + refFlag = true + break + } + } + if !refFlag { return false, nil } - if gmem.isYaml { - refFlag := false - for _, ref := range gmem.trigger.Rules.Branchs { - if matched, _ := regexp.MatchString(ref, getBranchFromRef(hookRepo.Branch)); matched { - refFlag = true - break - } - } - if !refFlag { - return false, nil - } - } else { - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { + } else { + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.ObjectAttributes.TargetBranch { + return false, nil + } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { return false, nil } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.ObjectAttributes.TargetBranch); !matched { - return false, nil - } - } } - hookRepo.Branch = ev.ObjectAttributes.TargetBranch - hookRepo.Committer = ev.User.Username - if ev.ObjectAttributes.State == "opened" { - var changedFiles []string - changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) - if err != nil { - gmem.log.Warnf("failed to get changes of event %v, err:%s", ev, err) - return false, err - } - gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) - if gmem.isYaml { - serviceChangeds := ServicesMatchChangesFiles(gmem.trigger.Rules.MatchFolders, changedFiles) - gmem.yamlServiceChanged = serviceChangeds - return len(serviceChangeds) != 0, nil - } - return MatchChanges(hookRepo, changedFiles), nil + } + hookRepo.Branch = ev.ObjectAttributes.TargetBranch + hookRepo.Committer = ev.User.Username + if ev.ObjectAttributes.State == "opened" { + var changedFiles []string + changedFiles, err := gmem.diffFunc(ev, hookRepo.CodehostID) + if err != nil { + gmem.log.Warnf("failed to get changes of event %v, err:%s", ev, err) + return false, err } + gmem.log.Debugf("succeed to get %d changes in merge event", len(changedFiles)) + if gmem.isYaml { + serviceChangeds := ServicesMatchChangesFiles(gmem.trigger.Rules.MatchFolders, changedFiles) + gmem.yamlServiceChanged = serviceChangeds + return len(serviceChangeds) != 0, nil + } + return MatchChanges(hookRepo, changedFiles), nil } return false, nil } @@ -130,11 +131,12 @@ func (gmem *gitlabMergeEventMatcher) UpdateTaskArgs( } args = factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - PR: gmem.event.ObjectAttributes.IID, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, + PR: gmem.event.ObjectAttributes.IID, }) return args @@ -185,67 +187,66 @@ type gitlabPushEventMatcher struct { func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gpem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if !EventConfigured(hookRepo, config.HookEventPush) { + if !checkRepoNamespaceMatch(hookRepo, ev.Project.PathWithNamespace) { + return false, nil + } + if !EventConfigured(hookRepo, config.HookEventPush) { + return false, nil + } + if gpem.isYaml { + refFlag := false + for _, ref := range gpem.trigger.Rules.Branchs { + if matched, _ := regexp.MatchString(ref, getBranchFromRef(ev.Ref)); matched { + refFlag = true + break + } + } + if !refFlag { return false, nil } - if gpem.isYaml { - refFlag := false - for _, ref := range gpem.trigger.Rules.Branchs { - if matched, _ := regexp.MatchString(ref, getBranchFromRef(ev.Ref)); matched { - refFlag = true - break - } - } - if !refFlag { - return false, nil - } - } else { - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + } else { + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { return false, nil } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, getBranchFromRef(ev.Ref)); !matched { - return false, nil - } - } - } - - hookRepo.Branch = getBranchFromRef(ev.Ref) - hookRepo.Committer = ev.UserUsername - var changedFiles []string - detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) - if err != nil { - gpem.log.Errorf("GetCodehostDetail error: %s", err) - return false, err } + } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) - if err != nil { - gpem.log.Errorf("NewClient error: %s", err) - return false, err - } + hookRepo.Branch = getBranchFromRef(ev.Ref) + hookRepo.Committer = ev.UserUsername + var changedFiles []string + detail, err := systemconfig.New().GetCodeHost(hookRepo.CodehostID) + if err != nil { + gpem.log.Errorf("GetCodehostDetail error: %s", err) + return false, err + } - // compare接口获取两个commit之间的最终的改动 - diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) - if err != nil { - gpem.log.Errorf("Failed to get push event diffs, error: %s", err) - return false, err - } - for _, diff := range diffs { - changedFiles = append(changedFiles, diff.NewPath) - changedFiles = append(changedFiles, diff.OldPath) - } - if gpem.isYaml { - serviceChangeds := ServicesMatchChangesFiles(gpem.trigger.Rules.MatchFolders, changedFiles) - gpem.yamlServiceChanged = serviceChangeds - return len(serviceChangeds) != 0, nil - } - return MatchChanges(hookRepo, changedFiles), nil + client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) + if err != nil { + gpem.log.Errorf("NewClient error: %s", err) + return false, err } - return false, nil + // compare接口获取两个commit之间的最终的改动 + diffs, err := client.Compare(ev.ProjectID, ev.Before, ev.After) + if err != nil { + gpem.log.Errorf("Failed to get push event diffs, error: %s", err) + return false, err + } + for _, diff := range diffs { + changedFiles = append(changedFiles, diff.NewPath) + changedFiles = append(changedFiles, diff.OldPath) + } + if gpem.isYaml { + serviceChangeds := ServicesMatchChangesFiles(gpem.trigger.Rules.MatchFolders, changedFiles) + gpem.yamlServiceChanged = serviceChangeds + return len(serviceChangeds) != 0, nil + } + return MatchChanges(hookRepo, changedFiles), nil } func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( @@ -270,10 +271,11 @@ func (gpem *gitlabPushEventMatcher) UpdateTaskArgs( } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, }) return args @@ -290,40 +292,41 @@ type gitlabTagEventMatcher struct { func (gtem gitlabTagEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) (bool, error) { ev := gtem.event - if (hookRepo.RepoOwner + "/" + hookRepo.RepoName) == ev.Project.PathWithNamespace { - if !EventConfigured(hookRepo, config.HookEventTag) { + + if !checkRepoNamespaceMatch(hookRepo, ev.Project.PathWithNamespace) { + return false, nil + } + + if !EventConfigured(hookRepo, config.HookEventTag) { + return false, nil + } + if gtem.isYaml { + refFlag := false + for _, ref := range gtem.trigger.Rules.Branchs { + if matched, _ := regexp.MatchString(ref, ev.Project.DefaultBranch); matched { + refFlag = true + break + } + } + if !refFlag { return false, nil } - if gtem.isYaml { - refFlag := false - for _, ref := range gtem.trigger.Rules.Branchs { - if matched, _ := regexp.MatchString(ref, ev.Project.DefaultBranch); matched { - refFlag = true - break - } - } - if !refFlag { - return false, nil - } - } else { - isRegular := hookRepo.IsRegular - if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { + } else { + isRegular := hookRepo.IsRegular + if !isRegular && hookRepo.Branch != ev.Project.DefaultBranch { + return false, nil + } + if isRegular { + if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { return false, nil } - if isRegular { - if matched, _ := regexp.MatchString(hookRepo.Branch, ev.Project.DefaultBranch); !matched { - return false, nil - } - } } - - hookRepo.Committer = ev.UserName - hookRepo.Tag = getTagFromRef(ev.Ref) - - return true, nil } - return false, nil + hookRepo.Committer = ev.UserName + hookRepo.Tag = getTagFromRef(ev.Ref) + + return true, nil } func (gtem gitlabTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, args *commonmodels.WorkflowTaskArgs, hookRepo *commonmodels.MainHookRepo, requestID string) *commonmodels.WorkflowTaskArgs { @@ -341,11 +344,12 @@ func (gtem gitlabTagEventMatcher) UpdateTaskArgs(product *commonmodels.Product, } factory.Update(product, args, &types.Repository{ - CodehostID: hookRepo.CodehostID, - RepoName: hookRepo.RepoName, - RepoOwner: hookRepo.RepoOwner, - Branch: hookRepo.Branch, - Tag: hookRepo.Tag, + CodehostID: hookRepo.CodehostID, + RepoName: hookRepo.RepoName, + RepoOwner: hookRepo.RepoOwner, + RepoNamespace: hookRepo.GetRepoNamespace(), + Branch: hookRepo.Branch, + Tag: hookRepo.Tag, }) return args @@ -538,14 +542,14 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, switch evt := event.(type) { case *gitlab.PushEvent: pushEvent = evt - if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != pushEvent.Project.PathWithNamespace { + if !checkRepoNamespaceMatch(item.MainRepo, pushEvent.Project.PathWithNamespace) { log.Debugf("event not matches repo: %v", item.MainRepo) continue } branref = pushEvent.Ref case *gitlab.MergeEvent: mergeEvent = evt - if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != mergeEvent.ObjectAttributes.Target.PathWithNamespace { + if !checkRepoNamespaceMatch(item.MainRepo, mergeEvent.ObjectAttributes.Target.PathWithNamespace) { log.Debugf("event not matches repo: %v", item.MainRepo) continue } @@ -557,7 +561,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, prID = evt.ObjectAttributes.IID case *gitlab.TagEvent: tagEvent = evt - if (item.MainRepo.RepoOwner + "/" + item.MainRepo.RepoName) != tagEvent.Project.PathWithNamespace { + if !checkRepoNamespaceMatch(item.MainRepo, tagEvent.Project.PathWithNamespace) { log.Debugf("event not matches repo: %v", item.MainRepo) continue } @@ -641,6 +645,7 @@ func TriggerWorkflowByGitlabEvent(event interface{}, baseURI, requestID string, args.Source = setting.SourceFromGitlab args.CodehostID = item.MainRepo.CodehostID args.RepoOwner = item.MainRepo.RepoOwner + args.RepoNamespace = item.MainRepo.GetRepoNamespace() args.RepoName = item.MainRepo.RepoName args.Committer = item.MainRepo.Committer // 3. create task with args diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 8ed4b450..a953b5bc 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - "github.com/koderover/zadig/pkg/types" "os" "path" "path/filepath" @@ -48,34 +47,42 @@ import ( gitlabtool "github.com/koderover/zadig/pkg/tool/git/gitlab" "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" ) -func syncContent(args *commonmodels.Service, logger *zap.SugaredLogger) error { +func syncContentFromCodehub(args *commonmodels.Service, logger *zap.SugaredLogger) error { address, _, repo, branch, path, pathType, err := GetOwnerRepoBranchPath(args.SrcPath) if err != nil { logger.Errorf("Failed to parse url %s, err: %s", args.SrcPath, err) return fmt.Errorf("url parse failure, err: %s", err) } + if len(args.LoadPath) > 0 { + path = args.LoadPath + } + if len(args.BranchName) > 0 { + branch = args.BranchName + } + if len(args.RepoName) > 0 { + repo = args.RepoName + } + var yamls []string - switch args.Source { - case setting.SourceFromCodeHub: - client, err := getCodehubClientByAddress(address) - if err != nil { - logger.Errorf("Failed to get codehub client, error: %s", err) - return err - } - repoUUID, err := client.GetRepoUUID(repo) - if err != nil { - logger.Errorf("Failed to get repoUUID, error: %s", err) - return err - } - yamls, err = client.GetYAMLContents(repoUUID, branch, path, pathType == "tree", true) - if err != nil { - logger.Errorf("Failed to get yamls, error: %s", err) - return err - } + client, err := getCodehubClientByAddress(address) + if err != nil { + logger.Errorf("Failed to get codehub client, error: %s", err) + return err + } + repoUUID, err := client.GetRepoUUID(repo) + if err != nil { + logger.Errorf("Failed to get repoUUID, error: %s", err) + return err + } + yamls, err = client.GetYAMLContents(repoUUID, branch, path, pathType == "tree", true) + if err != nil { + logger.Errorf("Failed to get yamls, error: %s", err) + return err } args.KubeYamls = yamls @@ -93,6 +100,7 @@ func reloadServiceTmplFromGit(svc *commonmodels.Service, log *zap.SugaredLogger) CreateFrom: &service.CreateFromRepo{ CodehostID: svc.CodehostID, Owner: svc.RepoOwner, + Namespace: svc.GetRepoNamespace(), Repo: svc.RepoName, Branch: svc.BranchName, Paths: []string{svc.LoadPath}, @@ -136,7 +144,7 @@ func fillServiceTmpl(userName string, args *commonmodels.Service, log *zap.Sugar return err } } else if args.Source == setting.SourceFromCodeHub { - err := syncContent(args, log) + err := syncContentFromCodehub(args, log) if err != nil { log.Errorf("Sync content from codehub failed, error: %v", err) return err @@ -212,6 +220,12 @@ func syncLatestCommit(service *commonmodels.Service) error { if len(service.LoadPath) > 0 { path = service.LoadPath } + if len(service.GetRepoNamespace()) > 0 { + owner = service.GetRepoNamespace() + } + if len(service.RepoName) > 0 { + repo = service.RepoName + } commit, err := GitlabGetLatestCommit(client, owner, repo, branch, path) if err != nil { @@ -374,6 +388,19 @@ func syncContentFromGitlab(userName string, args *commonmodels.Service) error { return fmt.Errorf("url format failed") } + if len(args.LoadPath) > 0 { + path = args.LoadPath + } + if len(args.BranchName) > 0 { + branch = args.BranchName + } + if len(args.RepoName) > 0 { + repo = args.RepoName + } + if len(args.GetRepoNamespace()) > 0 { + owner = args.GetRepoNamespace() + } + client, err := getGitlabClientByCodehostId(args.CodehostID) if err != nil { return err @@ -403,11 +430,25 @@ func joinYamls(files []string) string { func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) error { // 根据pipeline中的filepath获取文件内容 address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(args.SrcPath) + if err != nil { - log.Errorf("GetOwnerRepoBranchPath failed, srcPath:%s, err:%v", args.SrcPath, err) + log.Errorf("parseOwnerRepoBranchPath failed, srcPath:%s, err:%v", args.SrcPath, err) return errors.New("invalid url " + args.SrcPath) } + if len(args.LoadPath) > 0 { + path = args.LoadPath + } + if len(args.BranchName) > 0 { + branch = args.BranchName + } + if len(args.RepoName) > 0 { + repo = args.RepoName + } + if len(args.GetRepoNamespace()) > 0 { + owner = args.GetRepoNamespace() + } + ch, err := systemconfig.GetCodeHostInfo( &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) if err != nil { @@ -417,6 +458,9 @@ func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) e gc := githubtool.NewClient(&githubtool.Config{AccessToken: ch.AccessToken, Proxy: config.ProxyHTTPSAddr()}) fileContent, directoryContent, err := gc.GetContents(context.TODO(), owner, repo, path, &github.RepositoryContentGetOptions{Ref: branch}) + if err != nil { + return err + } if fileContent != nil { svcContent, _ := fileContent.GetContent() splitYaml := SplitYaml(svcContent) @@ -737,6 +781,10 @@ func getServiceSrcPath(service *commonmodels.Service) (string, error) { return p, err } +func checkRepoNamespaceMatch(hookRepo *commonmodels.MainHookRepo, pathWithNamespace string) bool { + return (hookRepo.GetRepoNamespace() + "/" + hookRepo.RepoName) == pathWithNamespace +} + // check if sub path is a part of parent path // eg: parent: k1/k2 sub: k1/k2/k3 return true // parent k1/k2-2 sub: k1/k2/k3 return false diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 540fcf2f..fac30e95 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -867,6 +867,7 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, triggerBy := &commonmodels.TriggerBy{ CodehostID: args.CodehostID, RepoOwner: args.RepoOwner, + RepoNamespace: args.RepoNamespace, RepoName: args.RepoName, Source: args.Source, MergeRequestID: args.MergeRequestID, @@ -1043,6 +1044,7 @@ func AddDataToArgsOrCreateReleaseImageTask(args *commonmodels.WorkflowTaskArgs, // openAPI only pass repoName, branch, pr targetRepo.Source = buildRepo.Source targetRepo.RepoOwner = buildRepo.RepoOwner + targetRepo.RepoNamespace = buildRepo.RepoNamespace targetRepo.RemoteName = buildRepo.RemoteName targetRepo.CodehostID = buildRepo.CodehostID targetRepo.CheckoutPath = buildRepo.CheckoutPath @@ -1670,6 +1672,7 @@ func testArgsToSubtask(args *commonmodels.WorkflowTaskArgs, pt *taskmodels.Task, return nil, err } repo.EnableProxy = repoInfo.EnableProxy + repo.RepoNamespace = repo.GetRepoNamespace() } testTask := &taskmodels.Testing{ @@ -2229,6 +2232,10 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL return nil, err } repo.EnableProxy = repoInfo.EnableProxy + repo.RepoNamespace = repo.GetRepoNamespace() + } + if len(build.JobCtx.Builds) == 0 { + build.JobCtx.Builds = make([]*types.Repository, 0) } build.JobCtx.BuildSteps = []*taskmodels.BuildStep{} diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go index 4dfbadb1..dcffeef4 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -203,18 +203,19 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, notificationID, user return 0, err } repos = append(repos, &types.Repository{ - Source: rep.Type, - RepoOwner: arg.RepoOwner, - RepoName: arg.RepoName, - Branch: arg.Branch, - PR: arg.PR, - Tag: arg.Tag, - CodehostID: arg.CodehostID, - OauthToken: rep.AccessToken, - Address: rep.Address, - Username: rep.Username, - Password: rep.Password, - EnableProxy: rep.EnableProxy, + Source: rep.Type, + RepoOwner: arg.RepoOwner, + RepoName: arg.RepoName, + Branch: arg.Branch, + PR: arg.PR, + CodehostID: arg.CodehostID, + OauthToken: rep.AccessToken, + Address: rep.Address, + Username: rep.Username, + Password: rep.Password, + EnableProxy: rep.EnableProxy, + RepoNamespace: arg.RepoNamespace, + Tag: arg.Tag, }) } diff --git a/pkg/microservice/aslan/core/workflow/testing/service/types.go b/pkg/microservice/aslan/core/workflow/testing/service/types.go index 3d464d91..1cdb7312 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/types.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/types.go @@ -48,13 +48,14 @@ type ListScanningRespItem struct { } type ScanningRepoInfo struct { - CodehostID int `json:"codehost_id"` - Source string `json:"source"` - RepoOwner string `json:"repo_owner"` - RepoName string `json:"repo_name"` - PR int `json:"pr"` - Branch string `json:"branch"` - Tag string `json:"tag"` + CodehostID int `json:"codehost_id"` + Source string `json:"source"` + RepoOwner string `json:"repo_owner"` + RepoNamespace string `json:"repo_namespace"` + RepoName string `json:"repo_name"` + PR int `json:"pr"` + Branch string `json:"branch"` + Tag string `json:"tag"` } type ScanningStatistic struct { @@ -107,6 +108,9 @@ func ConvertToDBScanningModule(args *Scanning) *commonmodels.Scanning { } func ConvertDBScanningModule(scanning *commonmodels.Scanning) *Scanning { + for _, repo := range scanning.Repos { + repo.RepoNamespace = repo.GetRepoNamespace() + } return &Scanning{ ID: scanning.ID.Hex(), Name: scanning.Name, diff --git a/pkg/microservice/cron/core/service/types.go b/pkg/microservice/cron/core/service/types.go index 0e19ba1a..60923cf2 100644 --- a/pkg/microservice/cron/core/service/types.go +++ b/pkg/microservice/cron/core/service/types.go @@ -278,6 +278,7 @@ type WorkflowTaskArgs struct { Source string `bson:"source" json:"source"` CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` RepoName string `bson:"repo_name" json:"repo_name"` //github check run diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index 15259328..df4349d0 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -266,6 +266,7 @@ type Repo struct { Address string `yaml:"address"` Owner string `yaml:"owner"` Name string `yaml:"name"` + Namespace string `yaml:"namespace"` RemoteName string `yaml:"remote_name"` Branch string `yaml:"branch"` PR int `yaml:"pr"` diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index 5ceb3efd..c53ccc5a 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -194,10 +194,15 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo, hostNames sets.String) []*c.C cmds = append(cmds, &c.Command{Cmd: c.RemoteRemove(repo.RemoteName), DisableTrace: true, IgnoreError: true}) } + // namespace represents the real owner + owner := repo.Namespace + if len(owner) == 0 { + owner = repo.Owner + } if repo.Source == meta.ProviderGitlab { u, _ := url.Parse(repo.Address) cmds = append(cmds, &c.Command{ - Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, repo.Owner, repo.Name, u.Scheme)), + Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, owner, repo.Name, u.Scheme)), DisableTrace: true, }) } else if repo.Source == meta.ProviderGerrit { @@ -213,7 +218,7 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo, hostNames sets.String) []*c.C u, _ := url.Parse(repo.Address) user := url.QueryEscape(repo.User) cmds = append(cmds, &c.Command{ - Cmd: c.RemoteAdd(repo.RemoteName, fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", u.Scheme, user, repo.Password, u.Host, repo.Owner, repo.Name)), + Cmd: c.RemoteAdd(repo.RemoteName, fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", u.Scheme, user, repo.Password, u.Host, owner, repo.Name)), DisableTrace: true, }) } else if repo.Source == meta.ProviderGitee { @@ -245,7 +250,7 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo, hostNames sets.String) []*c.C } } else { // github - cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) + cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, owner, repo.Name)), DisableTrace: true}) } ref := repo.Ref() diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 040ef70f..95d79dc7 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -271,6 +271,7 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName Source: build.Source, Owner: build.RepoOwner, Name: build.RepoName, + Namespace: build.RepoNamespace, RemoteName: build.RemoteName, Branch: build.Branch, PR: build.PR, diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index c2636f2b..fe4353c1 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -220,6 +220,7 @@ type Repo struct { Source string `yaml:"source"` Address string `yaml:"address"` Owner string `yaml:"owner"` + Namespace string `yaml:"namespace"` Name string `yaml:"name"` RemoteName string `yaml:"remote_name"` Branch string `yaml:"branch"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index be244c4f..968abdbd 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -265,6 +265,7 @@ type Repository struct { Source string `bson:"source,omitempty" json:"source,omitempty"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` RemoteName string `bson:"remote_name,omitempty" json:"remote_name,omitempty"` Branch string `bson:"branch" json:"branch"` PR int `bson:"pr,omitempty" json:"pr,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/model.go b/pkg/microservice/warpdrive/core/service/types/task/model.go index 15b283b6..fbd48935 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/model.go +++ b/pkg/microservice/warpdrive/core/service/types/task/model.go @@ -159,6 +159,7 @@ type WorkflowTaskArgs struct { Source string `bson:"source" json:"source"` CodehostID int `bson:"codehost_id" json:"codehost_id"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` RepoName string `bson:"repo_name" json:"repo_name"` Committer string `bson:"committer,omitempty" json:"committer,omitempty"` //github check run diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 68e2caa0..06f6bad4 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -26,6 +26,7 @@ import ( type Repository struct { Source string `bson:"source,omitempty" json:"source,omitempty"` RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` RepoName string `bson:"repo_name" json:"repo_name"` RemoteName string `bson:"remote_name,omitempty" json:"remote_name,omitempty"` Branch string `bson:"branch" json:"branch"` @@ -104,3 +105,10 @@ func (repo *Repository) GetReleaseCandidateTag(taskID int64) string { } return tag } + +func (repo *Repository) GetRepoNamespace() string { + if repo.RepoNamespace != "" { + return repo.RepoNamespace + } + return repo.RepoOwner +} -- Gitee From fc2bebb805b3e1b49d8adc5fc81cf451edf95b97 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Thu, 26 May 2022 18:09:20 +0800 Subject: [PATCH 2380/2647] remove init job preset role && permission urls (#1527) * permission urls Signed-off-by: mouuii * pm&helm urls Signed-off-by: mouuii * pm&helm urls Signed-off-by: mouuii --- pkg/cli/initconfig/cmd/admin.yaml | 8 --- pkg/cli/initconfig/cmd/contributor.yaml | 32 ------------ pkg/cli/initconfig/cmd/init.go | 50 ------------------- pkg/cli/initconfig/cmd/project-admin.yaml | 7 --- pkg/cli/initconfig/cmd/read-only.yaml | 38 -------------- pkg/cli/initconfig/cmd/read-project-only.yaml | 40 --------------- .../policy/core/yamlconfig/metas.yaml | 16 +++++- .../policy/core/yamlconfig/role.yaml | 8 +++ 8 files changed, 22 insertions(+), 177 deletions(-) delete mode 100644 pkg/cli/initconfig/cmd/admin.yaml delete mode 100644 pkg/cli/initconfig/cmd/contributor.yaml delete mode 100644 pkg/cli/initconfig/cmd/project-admin.yaml delete mode 100644 pkg/cli/initconfig/cmd/read-only.yaml delete mode 100644 pkg/cli/initconfig/cmd/read-project-only.yaml diff --git a/pkg/cli/initconfig/cmd/admin.yaml b/pkg/cli/initconfig/cmd/admin.yaml deleted file mode 100644 index 03bf5c61..00000000 --- a/pkg/cli/initconfig/cmd/admin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: admin -type: system -desc: "拥有系统中任何操作的权限" -rules: - - verbs: - - "*" - resources: - - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/contributor.yaml b/pkg/cli/initconfig/cmd/contributor.yaml deleted file mode 100644 index c9cfd423..00000000 --- a/pkg/cli/initconfig/cmd/contributor.yaml +++ /dev/null @@ -1,32 +0,0 @@ -name: contributor -type: system -rules: - - verbs: - - get_workflow - - run_workflow - resources: - - Workflow - kind: resource - - verbs: - - get_environment - - config_environment - - manage_environment - - delete_environment - resources: - - Environment - kind: resource - - verbs: - - get_service - resources: - - Service - kind: resource - - verbs: - - get_build - resources: - - Build - kind: resource - - verbs: - - get_test - resources: - - Test - kind: resource diff --git a/pkg/cli/initconfig/cmd/init.go b/pkg/cli/initconfig/cmd/init.go index 89cd6842..e60ee395 100644 --- a/pkg/cli/initconfig/cmd/init.go +++ b/pkg/cli/initconfig/cmd/init.go @@ -23,9 +23,6 @@ import ( "time" "github.com/spf13/cobra" - "golang.org/x/sync/errgroup" - - "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/setting" @@ -72,48 +69,6 @@ func run() error { return err } -//go:embed contributor.yaml -var contributor []byte - -//go:embed read-only.yaml -var readOnly []byte - -//go:embed read-project-only.yaml -var readProjectOnly []byte - -//go:embed admin.yaml -var admin []byte - -//go:embed project-admin.yaml -var projectAdmin []byte - -func presetRole() error { - g := new(errgroup.Group) - g.Go(func() error { - systemAdminRole := &policy.Role{} - if err := yaml.Unmarshal(admin, systemAdminRole); err != nil { - log.DPanic(err) - } - return policy.NewDefault().CreateSystemRole(systemAdminRole.Name, systemAdminRole) - }) - - rolesArray := [][]byte{readOnly, readProjectOnly, contributor, projectAdmin} - - for _, v := range rolesArray { - role := &policy.Role{} - if err := yaml.Unmarshal(v, role); err != nil { - log.DPanic(err) - } - g.Go(func() error { - return policy.NewDefault().CreatePresetRole(role.Name, role) - }) - } - if err := g.Wait(); err != nil { - return err - } - return nil -} - func initSystemConfig() error { email := config.AdminEmail() password := config.AdminPassword() @@ -125,11 +80,6 @@ func initSystemConfig() error { return err } - if err := presetRole(); err != nil { - log.Errorf("presetRole err:%s", err) - return err - } - if err := presetRoleBinding(uid); err != nil { log.Errorf("presetRoleBinding err:%s", err) return err diff --git a/pkg/cli/initconfig/cmd/project-admin.yaml b/pkg/cli/initconfig/cmd/project-admin.yaml deleted file mode 100644 index 39a2908d..00000000 --- a/pkg/cli/initconfig/cmd/project-admin.yaml +++ /dev/null @@ -1,7 +0,0 @@ -name: project-admin -type: system -rules: - - verbs: - - "*" - resources: - - "*" \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-only.yaml b/pkg/cli/initconfig/cmd/read-only.yaml deleted file mode 100644 index 0fe0f37b..00000000 --- a/pkg/cli/initconfig/cmd/read-only.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: read-only -type: system -rules: - - verbs: - - get_workflow - resources: - - Workflow - kind: resource - - verbs: - - get_environment - resources: - - Environment - kind: resource - - verbs: - - get_environment - resources: - - ProductionEnvironment - kind: resource - - verbs: - - get_service - resources: - - Service - kind: resource - - verbs: - - get_build - resources: - - Build - kind: resource - - verbs: - - get_test - resources: - - Test - kind: resource - - verbs: - - get_delivery - resources: - - Delivery - kind: resource \ No newline at end of file diff --git a/pkg/cli/initconfig/cmd/read-project-only.yaml b/pkg/cli/initconfig/cmd/read-project-only.yaml deleted file mode 100644 index 105bfd1f..00000000 --- a/pkg/cli/initconfig/cmd/read-project-only.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: read-project-only -type: system -namespace: '' -rules: - - verbs: - - get_workflow - resources: - - Workflow - kind: resource - match_attributes: - - key: placeholder - value: placeholder - - verbs: - - get_environment - resources: - - Environment - kind: resource - match_attributes: - - key: placeholder - value: placeholder - - verbs: - - get_service - resources: - - Service - kind: resource - - verbs: - - get_build - resources: - - Build - kind: resource - - verbs: - - get_test - resources: - - Test - kind: resource - - verbs: - - get_delivery - resources: - - Delivery - kind: resource \ No newline at end of file diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index d2011d3f..f6dd712b 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -103,6 +103,18 @@ metas: alias: 查看 description: '' rules: + - method: GET + endpoint: api/aslan/build/targets + - method: GET + endpoint: api/aslan/cluster/clusters + - method: GET + endpoint: api/aslan/build/build/?* + - method: GET + endpoint: api/aslan/build/build + - method: GET + endpoint: /api/aslan/template/build + - method: GET + endpoint: api/aslan/template/dockerfile - method: GET endpoint: /api/aslan/service/services - method: GET @@ -411,6 +423,8 @@ metas: alias: 查看 description: '' rules: + - method: GET + endpoint: /api/aslan/workflow/workflow/preset/?* - method: GET endpoint: api/aslan/workflow/workflow filter: true @@ -456,8 +470,6 @@ metas: endpoint: /api/aslan/environment/environments resourceType: Environment filter: true - - method: GET - endpoint: /api/aslan/workflow/workflow/preset/?* - method: GET endpoint: /api/aslan/testing/testdetail - method: PUT diff --git a/pkg/microservice/policy/core/yamlconfig/role.yaml b/pkg/microservice/policy/core/yamlconfig/role.yaml index 7312f82b..3dcbf17b 100644 --- a/pkg/microservice/policy/core/yamlconfig/role.yaml +++ b/pkg/microservice/policy/core/yamlconfig/role.yaml @@ -41,6 +41,10 @@ preset_roles: - '*' - name: read-only rules: + - verbs: + - get_scan + resources: + - Scan - verbs: - get_workflow resources: @@ -101,3 +105,7 @@ preset_roles: - get_delivery resources: - Delivery + - verbs: + - get_scan + resources: + - Scan -- Gitee From c8fa381aece07c821f4dace4f3075fcd851901e3 Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 26 May 2022 18:56:22 +0800 Subject: [PATCH 2381/2647] Optimize the general git integration function (#1539) * Optimize the general git integration function Signed-off-by: liu deyi * debug code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- .../aslan/core/build/service/build.go | 38 +++++++++++++++++++ .../core/common/repository/models/build.go | 4 -- .../service/workflow/pipeline_validation.go | 3 +- .../reaper/core/service/meta/types.go | 1 - .../reaper/core/service/reaper/git.go | 27 +++++++------ .../warpdrive/core/service/taskplugin/job.go | 1 - .../warpdrive/core/service/types/reaper.go | 1 - .../core/service/types/task/build.go | 1 - pkg/shared/client/systemconfig/codehost.go | 1 - pkg/types/repo.go | 1 - 10 files changed, 52 insertions(+), 26 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index ce72aac5..0e70bd61 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -30,7 +30,10 @@ import ( commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) type BuildResp struct { @@ -394,6 +397,41 @@ func correctFields(build *commonmodels.Build) { build.PostBuild.DockerBuild.DockerFile = strings.Trim(build.PostBuild.DockerBuild.DockerFile, " ") build.PostBuild.DockerBuild.WorkDir = strings.Trim(build.PostBuild.DockerBuild.WorkDir, " ") } + if build.TemplateID == "" { + for _, repo := range build.Repos { + if repo.Source != setting.SourceFromOther { + continue + } + modifyAuthType(repo) + } + return + } + + for _, target := range build.Targets { + for _, repo := range target.Repos { + if repo.Source != setting.SourceFromOther { + continue + } + modifyAuthType(repo) + } + } +} + +func modifyAuthType(repo *types.Repository) { + repo.RepoOwner = strings.TrimPrefix(repo.RepoOwner, "/") + repo.RepoOwner = strings.TrimSuffix(repo.RepoOwner, "/") + repo.RepoName = strings.TrimPrefix(repo.RepoName, "/") + repo.RepoName = strings.TrimSuffix(repo.RepoName, "/") + codehosts, err := systemconfig.New().ListCodeHostsInternal() + if err != nil { + log.Errorf("failed to list codehost,err:%s", err) + } + for _, codehost := range codehosts { + if repo.CodehostID == codehost.ID { + repo.AuthType = codehost.AuthType + break + } + } } func verifyBuildTargets(name, productName string, targets []*commonmodels.ServiceModuleTarget, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 611b29ba..33930de9 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -21,7 +21,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" - "github.com/koderover/zadig/pkg/util" ) type Build struct { @@ -193,9 +192,6 @@ func (build *Build) SafeReposDeepCopy() []*types.Repository { resp := make([]*types.Repository, 0) for _, repo := range build.Repos { tmpRepo := *repo - if tmpRepo.Source == setting.SourceFromOther { - tmpRepo.RepoOwner, tmpRepo.RepoName = util.ParseOwnerAndRepo(tmpRepo.OtherAddress, tmpRepo.AuthType) - } resp = append(resp, &tmpRepo) } return resp diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index db04debb..d864dae1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -457,9 +457,8 @@ func setBuildInfo(build *types.Repository, buildArgs []*types.Repository, log *z } else if codeHostInfo.Type == systemconfig.OtherProvider { build.SSHKey = codeHostInfo.SSHKey build.PrivateAccessToken = codeHostInfo.PrivateAccessToken - build.RepoOwner, build.RepoName = util.ParseOwnerAndRepo(build.OtherAddress, build.AuthType) for _, buildArg := range buildArgs { - if buildArg.RepoOwner == build.RepoOwner && buildArg.RepoName == build.RepoName && buildArg.CheckoutPath == build.CheckoutPath { + if buildArg.RepoOwner == build.RepoOwner && buildArg.RepoName == build.RepoName { setBuildFromArg(build, buildArg) break } diff --git a/pkg/microservice/reaper/core/service/meta/types.go b/pkg/microservice/reaper/core/service/meta/types.go index df4349d0..e3612120 100644 --- a/pkg/microservice/reaper/core/service/meta/types.go +++ b/pkg/microservice/reaper/core/service/meta/types.go @@ -278,7 +278,6 @@ type Repo struct { Password string `yaml:"password"` CheckoutRef string `yaml:"checkout_ref"` EnableProxy bool `yaml:"enable_proxy"` - OtherAddress string `yaml:"other_address,omitempty"` AuthType types.AuthType `yaml:"auth_type,omitempty"` SSHKey string `yaml:"ssh_key,omitempty"` PrivateAccessToken string `yaml:"private_access_token,omitempty"` diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index c53ccc5a..e378bc3d 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -225,20 +225,20 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo, hostNames sets.String) []*c.C cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, r.Ctx.Git.HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) } else if repo.Source == meta.ProviderOther { if repo.AuthType == types.SSHAuthType { - host := getHost(repo.OtherAddress) + host := getHost(repo.Address) if !hostNames.Has(host) { if err := writeSSHFile(repo.SSHKey, host); err != nil { log.Errorf("failed to write ssh file %s: %s", repo.SSHKey, err) } hostNames.Insert(host) } - + remoteName := fmt.Sprintf("%s:%s/%s.git", repo.Address, repo.Owner, repo.Name) cmds = append(cmds, &c.Command{ - Cmd: c.RemoteAdd(repo.RemoteName, repo.OtherAddress), + Cmd: c.RemoteAdd(repo.RemoteName, remoteName), DisableTrace: true, }) } else if repo.AuthType == types.PrivateAccessTokenAuthType { - u, err := url.Parse(repo.OtherAddress) + u, err := url.Parse(repo.Address) if err != nil { log.Errorf("failed to parse url,err:%s", err) } else { @@ -293,7 +293,9 @@ func writeSSHFile(sshKey, hostName string) error { return fmt.Errorf("hostName cannot be empty") } - pathName := fmt.Sprintf("/.ssh/id_rsa.%s", strings.Replace(hostName, ".", "", -1)) + hostName = strings.Replace(hostName, ".", "", -1) + hostName = strings.Replace(hostName, ":", "", -1) + pathName := fmt.Sprintf("/.ssh/id_rsa.%s", hostName) file := path.Join(config.Home(), pathName) return ioutil.WriteFile(file, []byte(sshKey), 0400) } @@ -301,7 +303,9 @@ func writeSSHFile(sshKey, hostName string) error { func writeSSHConfigFile(hostNames sets.String, proxy *meta.Proxy) error { out := "\nHOST *\nStrictHostKeyChecking=no\nUserKnownHostsFile=/dev/null\n" for _, hostName := range hostNames.List() { - out += fmt.Sprintf("\nHost %s\nIdentityFile ~/.ssh/id_rsa.%s\n", hostName, strings.Replace(hostName, ".", "", -1)) + name := strings.Replace(hostName, ".", "", -1) + name = strings.Replace(name, ":", "", -1) + out += fmt.Sprintf("\nHost %s\nIdentityFile ~/.ssh/id_rsa.%s\n", hostName, name) if proxy.EnableRepoProxy && proxy.Type == "socks5" { out = out + fmt.Sprintf("ProxyCommand nc -x %s %%h %%p\n", proxy.GetProxyURL()) } @@ -310,14 +314,9 @@ func writeSSHConfigFile(hostNames sets.String, proxy *meta.Proxy) error { return ioutil.WriteFile(file, []byte(out), 0600) } -// git@github.com:koderover/zadig.git +// git@github.com or git@github.com:2000 // return github.com func getHost(address string) string { - hostArr := strings.Split(address, ":") - host := hostArr[0] - host = strings.TrimPrefix(host, "git@") - if len(hostArr) > 2 { - return fmt.Sprintf("%s:%s", host, hostArr[1]) - } - return host + address = strings.TrimPrefix(address, "git@") + return address } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 95d79dc7..5e8ac0a3 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -284,7 +284,6 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName User: build.Username, Password: build.Password, EnableProxy: build.EnableProxy, - OtherAddress: build.OtherAddress, AuthType: build.AuthType, SSHKey: build.SSHKey, PrivateAccessToken: build.PrivateAccessToken, diff --git a/pkg/microservice/warpdrive/core/service/types/reaper.go b/pkg/microservice/warpdrive/core/service/types/reaper.go index fe4353c1..240b2996 100644 --- a/pkg/microservice/warpdrive/core/service/types/reaper.go +++ b/pkg/microservice/warpdrive/core/service/types/reaper.go @@ -233,7 +233,6 @@ type Repo struct { Password string `yaml:"password"` CheckoutRef string `yaml:"checkout_ref"` EnableProxy bool `yaml:"enable_proxy"` - OtherAddress string `yaml:"other_address,omitempty"` AuthType types.AuthType `yaml:"auth_type,omitempty"` SSHKey string `yaml:"ssh_key,omitempty"` PrivateAccessToken string `yaml:"private_access_token,omitempty"` diff --git a/pkg/microservice/warpdrive/core/service/types/task/build.go b/pkg/microservice/warpdrive/core/service/types/task/build.go index 968abdbd..80130bc0 100644 --- a/pkg/microservice/warpdrive/core/service/types/task/build.go +++ b/pkg/microservice/warpdrive/core/service/types/task/build.go @@ -291,7 +291,6 @@ type Repository struct { RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty"` EnableProxy bool `bson:"enable_proxy,omitempty" json:"enable_proxy,omitempty"` // The address of the code base input of the other type - OtherAddress string `bson:"other_address,omitempty" json:"other_address,omitempty"` AuthType types.AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty"` SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty"` PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty"` diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index d51ea20b..1f0fb27d 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -48,7 +48,6 @@ type CodeHost struct { // the field determine whether the proxy is enabled EnableProxy bool `json:"enable_proxy"` UpdatedAt int64 `json:"updated_at"` - OtherAddress string `json:"other_address,omitempty"` AuthType types.AuthType `json:"auth_type,omitempty"` SSHKey string `json:"ssh_key,omitempty"` PrivateAccessToken string `json:"private_access_token,omitempty"` diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 06f6bad4..b46f2b87 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -57,7 +57,6 @@ type Repository struct { // FilterRegexp is the regular expression filter for the branches and tags FilterRegexp string `bson:"-" json:"filter_regexp,omitempty"` // The address of the code base input of the other type - OtherAddress string `bson:"other_address,omitempty" json:"other_address,omitempty"` AuthType AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty"` SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty"` PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty"` -- Gitee From 0885dd65cb7ef2e1827f129d603a1608eea00c5b Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Fri, 27 May 2022 14:06:34 +0800 Subject: [PATCH 2382/2647] debug (#1542) Signed-off-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow_task.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index fac30e95..581683ca 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -741,7 +741,8 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, if distribute.Target != nil { serviceModule.ProductName = distribute.Target.ProductName } - if reflect.DeepEqual(distribute.Target, serviceModule) { + // since more field is introduced into the serviceModuleTarget structure, we only use the field we need to determine if the target match + if serviceModule.ProductName == distribute.Target.ProductName && serviceModule.ServiceName == distribute.Target.ServiceName && serviceModule.ServiceModule == distribute.Target.ServiceModule { distributeTasks, err = formatDistributeSubtasks( serviceModule, workflow.DistributeStage.Releases, -- Gitee From 133ac83af397f340f257b587c94256be507a705e Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 27 May 2022 16:01:04 +0800 Subject: [PATCH 2383/2647] fix partial service index out of bounds (#1546) * rollback init config Signed-off-by: panxunying * fix partial service index out of bounds Signed-off-by: panxunying Co-authored-by: panxunying --- .../core/environment/service/environment.go | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index c23a077f..179b8d13 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -659,16 +659,20 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. newServiceMap[service.ServiceName] = service } oldServiceMap := make(map[string]*commonmodels.ProductService) - for _, service := range existedProd.Services[groupIndex] { - if util.InStringArray(service.ServiceName, deletedServices) { - continue - } - oldServiceMap[service.ServiceName] = service - if newService, ok := newServiceMap[service.ServiceName]; ok { - updateGroup = append(updateGroup, newService) - continue + for _, existedGroupServices := range existedProd.Services { + for _, service := range existedGroupServices { + if util.InStringArray(service.ServiceName, deletedServices) { + continue + } + oldServiceMap[service.ServiceName] = service + if newService, ok := newServiceMap[service.ServiceName]; ok { + if util.InStringArray(service.ServiceName, serviceNames) { + updateGroup = append(updateGroup, newService) + } else { + updateGroup = append(updateGroup, service) + } + } } - updateGroup = append(updateGroup, service) } for _, newService := range groupServices { if _, ok := oldServiceMap[newService.ServiceName]; !ok && !util.InStringArray(newService.ServiceName, deletedServices) { -- Gitee From a0bb11ba8902d6e84fa4bf00838ddde16e282d82 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 27 May 2022 16:09:51 +0800 Subject: [PATCH 2384/2647] fix testing notify message missing description (#1545) Signed-off-by: dianqihanwangzi --- .../aslan/core/common/service/instantmessage/service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/instantmessage/service.go b/pkg/microservice/aslan/core/common/service/instantmessage/service.go index a5cbf00f..5edffb7c 100644 --- a/pkg/microservice/aslan/core/common/service/instantmessage/service.go +++ b/pkg/microservice/aslan/core/common/service/instantmessage/service.go @@ -124,6 +124,7 @@ func (w *Service) SendInstantMessage(task *task.Task, testTaskStatusChanged bool return err } notifyCtls = resp.NotifyCtls + desc = resp.Desc default: log.Errorf("task type is not supported!") -- Gitee From d43e5e1ae2bca988f4ab3ab58700e8abc2b97061 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Fri, 27 May 2022 17:36:09 +0800 Subject: [PATCH 2385/2647] test center (#1551) Signed-off-by: mouuii --- .../policy/core/service/policy_meta_registration.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/microservice/policy/core/service/policy_meta_registration.go b/pkg/microservice/policy/core/service/policy_meta_registration.go index 77e2944f..30f20f04 100644 --- a/pkg/microservice/policy/core/service/policy_meta_registration.go +++ b/pkg/microservice/policy/core/service/policy_meta_registration.go @@ -90,6 +90,19 @@ func GetPolicyRegistrationDefinitions(scope, envType string, _ *zap.SugaredLogge switch scope { case string(types.SystemScope): + for _, meta := range systemPolicyMetas { + if meta.Resource == "TestCenter" { + tmpRules := []*models.PolicyMetaRule{} + for _, rule := range meta.Rules { + if rule.Action == "create_test" { + continue + } + tmpRules = append(tmpRules, rule) + } + + meta.Rules = tmpRules + } + } filteredPolicyMetas = systemPolicyMetas case string(types.ProjectScope): tmp := []*models.PolicyMeta{} -- Gitee From c54b543c5ca5ad3667eb375dd6444b5560968419 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Fri, 27 May 2022 18:49:32 +0800 Subject: [PATCH 2386/2647] fix partial service index out of bounds (#1553) * rollback init config Signed-off-by: panxunying * fix partial service index out of bounds Signed-off-by: panxunying * remove debug log Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/environment/service/environment.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 179b8d13..29633401 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -564,7 +564,7 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. // 首先更新一次数据库,将产品模板的最新编排更新到数据库 // 只更新编排,不更新服务revision等信息 - updatedServices := getUpdatedProductServices(serviceNames, updateProd, serviceRevisionMap, existedProd) + updatedServices := getUpdatedProductServices(updateProd, serviceRevisionMap, existedProd) updateProd.Status = setting.ProductStatusUpdating updateProd.Services = updatedServices @@ -675,10 +675,11 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. } } for _, newService := range groupServices { - if _, ok := oldServiceMap[newService.ServiceName]; !ok && !util.InStringArray(newService.ServiceName, deletedServices) { + if _, ok := oldServiceMap[newService.ServiceName]; !ok && !util.InStringArray(newService.ServiceName, deletedServices) && util.InStringArray(newService.ServiceName, serviceNames) { updateGroup = append(updateGroup, newService) } } + err = commonrepo.NewProductColl().UpdateGroup(envName, productName, groupIndex, updateGroup) if err != nil { log.Errorf("Failed to update collection - service group %d. Error: %v", groupIndex, err) @@ -3336,7 +3337,7 @@ func getServiceRevisionMap(serviceRevisionList []*SvcRevision) map[string]*SvcRe return serviceRevisionMap } -func getUpdatedProductServices(serviceNames []string, updateProduct *commonmodels.Product, serviceRevisionMap map[string]*SvcRevision, currentProduct *commonmodels.Product) [][]*commonmodels.ProductService { +func getUpdatedProductServices(updateProduct *commonmodels.Product, serviceRevisionMap map[string]*SvcRevision, currentProduct *commonmodels.Product) [][]*commonmodels.ProductService { currentServices := make(map[string]*commonmodels.ProductService) for _, group := range currentProduct.Services { for _, service := range group { @@ -3349,7 +3350,7 @@ func getUpdatedProductServices(serviceNames []string, updateProduct *commonmodel updatedGroups := make([]*commonmodels.ProductService, 0) for _, service := range group { serviceRevision, ok := serviceRevisionMap[service.ServiceName+service.Type] - if !ok || (len(serviceNames) > 0 && !util.InStringArray(service.ServiceName, serviceNames)) { + if !ok { //找不到 service revision continue } -- Gitee From 86a64ef2d73987ff4d38160adc42f1cf0fa78447 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Sat, 28 May 2022 20:37:26 +0800 Subject: [PATCH 2387/2647] fix render data miss bug after deploy (#1556) * fix render data miss bug after deploy Signed-off-by: allenshen * remove bson tag Signed-off-by: allenshen --- .../core/service/types/environment.go | 25 +++++++------------ .../warpdrive/core/service/types/renderset.go | 7 ------ 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/types/environment.go b/pkg/microservice/warpdrive/core/service/types/environment.go index be83e18c..bf9a386a 100644 --- a/pkg/microservice/warpdrive/core/service/types/environment.go +++ b/pkg/microservice/warpdrive/core/service/types/environment.go @@ -60,26 +60,19 @@ type RenderKV struct { Services []string `bson:"services" json:"services"` } -type GitRepoConfig struct { - CodehostID int `bson:"codehost_id,omitempty"` - Owner string `bson:"owner,omitempty"` - Repo string `bson:"repo,omitempty"` - Branch string `bson:"branch,omitempty"` -} - type CustomYaml struct { - YamlSource string `bson:"yaml_source,omitempty" json:"yaml_source,omitempty"` - YamlContent string `bson:"yaml_content,omitempty" json:"yaml_content,omitempty"` - GitRepoConfig *GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` - ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` + YamlContent string `json:"yaml_content,omitempty"` + Source string `json:"source"` + AutoSync bool `json:"auto_sync"` + SourceDetail interface{} `json:"source_detail"` } type RenderChart struct { - ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` - ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` - ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` - OverrideYaml *CustomYaml `bson:"override_yaml,omitempty" json:"override_yaml,omitempty"` - OverrideValues string `bson:"override_values,omitempty" json:"override_values,omitempty"` + ServiceName string `json:"service_name,omitempty"` + ChartVersion string `json:"chart_version,omitempty"` + ValuesYaml string `json:"values_yaml,omitempty"` + OverrideYaml *CustomYaml `json:"override_yaml,omitempty"` + OverrideValues string `json:"override_values,omitempty"` } type ProductAuth struct { diff --git a/pkg/microservice/warpdrive/core/service/types/renderset.go b/pkg/microservice/warpdrive/core/service/types/renderset.go index 3d469a78..250b89ab 100644 --- a/pkg/microservice/warpdrive/core/service/types/renderset.go +++ b/pkg/microservice/warpdrive/core/service/types/renderset.go @@ -33,10 +33,3 @@ type RenderSet struct { ChartInfos []*RenderChart `bson:"chart_infos,omitempty" json:"chart_infos,omitempty"` Description string `bson:"description,omitempty" json:"description,omitempty"` } - -//// RenderChart ... -//type RenderChart struct { -// ServiceName string `bson:"service_name,omitempty" json:"service_name,omitempty"` -// ChartVersion string `bson:"chart_version,omitempty" json:"chart_version,omitempty"` -// ValuesYaml string `bson:"values_yaml,omitempty" json:"values_yaml,omitempty"` -//} -- Gitee From 30032007545eb179ebdf4d945c4b907b15f25b4b Mon Sep 17 00:00:00 2001 From: dyliu Date: Sat, 28 May 2022 20:39:09 +0800 Subject: [PATCH 2388/2647] Fix ssh pull code issue (#1555) * optimize code Signed-off-by: liu deyi * cut out port Signed-off-by: liu deyi --- pkg/microservice/reaper/core/service/reaper/git.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/reaper/core/service/reaper/git.go b/pkg/microservice/reaper/core/service/reaper/git.go index e378bc3d..f7ae043b 100644 --- a/pkg/microservice/reaper/core/service/reaper/git.go +++ b/pkg/microservice/reaper/core/service/reaper/git.go @@ -233,6 +233,10 @@ func (r *Reaper) buildGitCommands(repo *meta.Repo, hostNames sets.String) []*c.C hostNames.Insert(host) } remoteName := fmt.Sprintf("%s:%s/%s.git", repo.Address, repo.Owner, repo.Name) + // Including the case of the port + if strings.Contains(repo.Address, ":") { + remoteName = fmt.Sprintf("%s/%s/%s.git", repo.Address, repo.Owner, repo.Name) + } cmds = append(cmds, &c.Command{ Cmd: c.RemoteAdd(repo.RemoteName, remoteName), DisableTrace: true, @@ -317,6 +321,8 @@ func writeSSHConfigFile(hostNames sets.String, proxy *meta.Proxy) error { // git@github.com or git@github.com:2000 // return github.com func getHost(address string) string { + address = strings.TrimPrefix(address, "ssh://") address = strings.TrimPrefix(address, "git@") - return address + hostArr := strings.Split(address, ":") + return hostArr[0] } -- Gitee From 2c77879122644454a9b2f5ee0eb4a23c5658e3cb Mon Sep 17 00:00:00 2001 From: Xudong Zhang Date: Wed, 25 May 2022 22:59:14 +0800 Subject: [PATCH 2389/2647] reaper: avoid change origin job info since it may be retried Changing user input leads to following problem: 1. The UploadPath became sth like `/workspace/static` at first 2. Then it became `/workspace/workspace/static` if retried Signed-off-by: Xudong Zhang --- .../reaper/core/service/reaper/reaper.go | 12 ++++++------ .../warpdrive/core/service/taskplugin/job.go | 5 +++-- pkg/types/object_storage.go | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index 230ce66a..f348490e 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -456,22 +456,22 @@ func (r *Reaper) AfterExec() error { return fmt.Errorf("failed to create s3 client to upload file, err: %s", err) } for _, upload := range r.Ctx.UploadInfo { - info, err := os.Stat(upload.FilePath) + info, err := os.Stat(upload.AbsFilePath) if err != nil { - return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", upload.FilePath, upload.DestinationPath, err) + return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", upload.AbsFilePath, upload.DestinationPath, err) } // if the given path is a directory if info.IsDir() { - err := client.UploadDir(r.Ctx.UploadStorageInfo.Bucket, upload.FilePath, upload.DestinationPath) + err := client.UploadDir(r.Ctx.UploadStorageInfo.Bucket, upload.AbsFilePath, upload.DestinationPath) if err != nil { - log.Errorf("Failed to upload dir [%s] to path [%s] on s3, the error is: %s", upload.FilePath, upload.DestinationPath, err) + log.Errorf("Failed to upload dir [%s] to path [%s] on s3, the error is: %s", upload.AbsFilePath, upload.DestinationPath, err) return err } } else { key := filepath.Join(upload.DestinationPath, info.Name()) - err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, upload.FilePath, key) + err := client.Upload(r.Ctx.UploadStorageInfo.Bucket, upload.AbsFilePath, key) if err != nil { - log.Errorf("Failed to upload [%s] to key [%s] on s3, the error is: %s", upload.FilePath, key, err) + log.Errorf("Failed to upload [%s] to key [%s] on s3, the error is: %s", upload.AbsFilePath, key, err) return err } } diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/job.go b/pkg/microservice/warpdrive/core/service/taskplugin/job.go index 5e8ac0a3..ed078f42 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/job.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/job.go @@ -305,9 +305,10 @@ func (b *JobCtxBuilder) BuildReaperContext(pipelineTask *task.Task, serviceName if b.JobCtx.UploadEnabled { for _, upload := range b.JobCtx.UploadInfo { // since the frontend won't change the file path, the backend will have to add it - upload.FilePath = fmt.Sprintf("$WORKSPACE/%s", upload.FilePath) + // use AbsFilePath to avoid change the original user input since a job may be retried + upload.AbsFilePath = fmt.Sprintf("$WORKSPACE/%s", upload.FilePath) // then we replace it with our env variables - upload.FilePath = replaceEnvWithValue(upload.FilePath, envmaps) + upload.AbsFilePath = replaceEnvWithValue(upload.AbsFilePath, envmaps) upload.DestinationPath = replaceEnvWithValue(upload.DestinationPath, envmaps) } } diff --git a/pkg/types/object_storage.go b/pkg/types/object_storage.go index ba6f88eb..b162ca71 100644 --- a/pkg/types/object_storage.go +++ b/pkg/types/object_storage.go @@ -27,5 +27,6 @@ type ObjectStorageInfo struct { type ObjectStoragePathDetail struct { FilePath string `bson:"file_path" json:"file_path" yaml:"file_path"` + AbsFilePath string `bson:"abs_file_path" json:"abs_file_path" yaml:"abs_file_path"` DestinationPath string `bson:"dest_path" json:"dest_path" yaml:"dest_path"` } -- Gitee From 1370a7f0f546e6b3c5de0a18ceb1d834f9eea113 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Mon, 30 May 2022 11:25:00 +0800 Subject: [PATCH 2390/2647] Fix the problem that the service cannot restart (#1563) Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 29633401..68fe0115 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -3150,6 +3150,7 @@ func applyUpdatedAnnotations(annotations map[string]string) map[string]string { annotations = make(map[string]string) } + annotations[setting.UpdatedByLabel] = fmt.Sprintf("%d", time.Now().Unix()) return annotations } -- Gitee From b4a2eb8543a826ef15ca59d445f00453bd5fb9e5 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 30 May 2022 11:43:37 +0800 Subject: [PATCH 2391/2647] add env setting ability (#1550) * add env setting ability Signed-off-by: allenshen * update env set logic Signed-off-by: allenshen * add template build delete safe ensure Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- .../aslan/core/build/service/build.go | 33 ++++++++++++++---- .../core/common/repository/models/build.go | 4 ++- .../core/common/repository/mongodb/build.go | 4 +++ .../aslan/core/common/service/build.go | 34 +++++++++++++++++++ .../aslan/core/templatestore/service/build.go | 24 +++++++++++-- .../service/workflow/workflow_task.go | 6 +++- 6 files changed, 93 insertions(+), 12 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 0e70bd61..00d5f545 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -155,9 +155,15 @@ func ListBuildModulesByServiceModule(productName string, log *zap.SugaredLogger) return serviceModuleAndBuildResp, nil } -func fillBuildRepoData(build *commonmodels.Build) { +func fillBuildTargetData(build *commonmodels.Build) error { if build.TemplateID == "" { - return + return nil + } + buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: build.TemplateID, + }) + if err != nil { + return fmt.Errorf("failed to find build template with id: %s, err: %s", build.TemplateID, err) } build.Targets = make([]*commonmodels.ServiceModuleTarget, 0, len(build.TargetRepos)) for _, target := range build.TargetRepos { @@ -166,8 +172,10 @@ func fillBuildRepoData(build *commonmodels.Build) { ServiceName: target.Service.ServiceName, ServiceModule: target.Service.ServiceModule, Repos: target.Repos, + Envs: commonservice.MergeBuildEnvs(buildTemplate.PreBuild.Envs, target.Envs), }) } + return nil } func CreateBuild(username string, build *commonmodels.Build, log *zap.SugaredLogger) error { @@ -179,7 +187,10 @@ func CreateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog } build.UpdateBy = username - correctFields(build) + err := correctFields(build) + if err != nil { + return err + } if err := commonrepo.NewBuildColl().Create(build); err != nil { log.Errorf("[Build.Upsert] %s error: %v", build.Name, err) @@ -202,7 +213,11 @@ func UpdateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog commonservice.EnsureSecretEnvs(existed.PreBuild.Envs, build.PreBuild.Envs) } - correctFields(build) + err = correctFields(build) + if err != nil { + return err + } + build.UpdateBy = username build.UpdateTime = time.Now().Unix() @@ -380,8 +395,11 @@ func UpdateBuildTargets(name, productName string, targets []*commonmodels.Servic return nil } -func correctFields(build *commonmodels.Build) { - fillBuildRepoData(build) +func correctFields(build *commonmodels.Build) error { + err := fillBuildTargetData(build) + if err != nil { + return err + } // make sure cache has no empty field caches := make([]string, 0) for _, cache := range build.Caches { @@ -404,7 +422,7 @@ func correctFields(build *commonmodels.Build) { } modifyAuthType(repo) } - return + return nil } for _, target := range build.Targets { @@ -415,6 +433,7 @@ func correctFields(build *commonmodels.Build) { modifyAuthType(repo) } } + return nil } func modifyAuthType(repo *types.Repository) { diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index 33930de9..f068d803 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -151,7 +151,8 @@ type ServiceModuleTarget struct { ServiceName string `bson:"service_name" json:"service_name"` ServiceModule string `bson:"service_module" json:"service_module"` BuildName string `bson:"build_name" json:"build_name"` - Repos []*types.Repository `bson:"repos,omitempty" json:"repos,omitempty"` + Repos []*types.Repository `bson:"repos,omitempty" json:"repos,omitempty"` + Envs []*KeyVal `bson:"envs,omitempty" json:"envs"` } type ServiceModuleTargetBase struct { @@ -163,6 +164,7 @@ type ServiceModuleTargetBase struct { type TargetRepo struct { Service *ServiceModuleTargetBase `json:"service"` Repos []*types.Repository `json:"repos"` + Envs []*KeyVal `json:"envs"` } type KeyVal struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/build.go b/pkg/microservice/aslan/core/common/repository/mongodb/build.go index 8dc74f0c..f19be738 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/build.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/build.go @@ -41,6 +41,7 @@ type BuildListOption struct { BuildOS string BasicImageID string PrivateKeyID string + TemplateID string } // FindOption ... @@ -142,6 +143,9 @@ func (c *BuildColl) List(opt *BuildListOption) ([]*models.Build, error) { if len(opt.PrivateKeyID) != 0 { query["ssh.id"] = opt.PrivateKeyID } + if len(opt.TemplateID) != 0 { + query["template_id"] = opt.TemplateID + } var resp []*models.Build ctx := context.Background() diff --git a/pkg/microservice/aslan/core/common/service/build.go b/pkg/microservice/aslan/core/common/service/build.go index fcaf6949..ea453e65 100644 --- a/pkg/microservice/aslan/core/common/service/build.go +++ b/pkg/microservice/aslan/core/common/service/build.go @@ -27,6 +27,7 @@ import ( commonutil "github.com/koderover/zadig/pkg/microservice/aslan/core/common/util" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" ) @@ -144,11 +145,22 @@ func EnsureResp(build *commonmodels.Build) { } if build.TemplateID != "" { + buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: build.TemplateID, + }) + //NOTE deleted template should not block the normal logic of build modules + if err != nil { + log.Warnf("failed to find build template with id: %s, err: %s", build.TemplateID, err) + } build.TargetRepos = make([]*commonmodels.TargetRepo, 0, len(build.Targets)) for _, target := range build.Targets { for _, repo := range target.Repos { repo.RepoNamespace = repo.GetRepoNamespace() } + envs := target.Envs + if buildTemplate != nil { + envs = MergeBuildEnvs(buildTemplate.PreBuild.Envs, envs) + } targetRepo := &commonmodels.TargetRepo{ Service: &commonmodels.ServiceModuleTargetBase{ ProductName: target.ProductName, @@ -156,6 +168,12 @@ func EnsureResp(build *commonmodels.Build) { ServiceModule: target.ServiceModule, }, Repos: target.Repos, + Envs: envs, + } + for _, v := range targetRepo.Envs { + if v.IsCredential { + v.Value = setting.MaskValue + } } build.TargetRepos = append(build.TargetRepos, targetRepo) } @@ -173,3 +191,19 @@ func FindReposByTarget(projectName, serviceName, serviceModule string, build *co } return build.SafeRepos() } + +func MergeBuildEnvs(templateEnvs []*commonmodels.KeyVal, customEnvs []*commonmodels.KeyVal) []*commonmodels.KeyVal { + customEnvMap := make(map[string]*commonmodels.KeyVal) + for _, v := range customEnvs { + customEnvMap[v.Key] = v + } + retEnvs := make([]*commonmodels.KeyVal, 0) + for _, v := range templateEnvs { + if cv, ok := customEnvMap[v.Key]; ok { + retEnvs = append(retEnvs, cv) + } else { + retEnvs = append(retEnvs, v) + } + } + return retEnvs +} diff --git a/pkg/microservice/aslan/core/templatestore/service/build.go b/pkg/microservice/aslan/core/templatestore/service/build.go index eac282fb..0ced21cf 100644 --- a/pkg/microservice/aslan/core/templatestore/service/build.go +++ b/pkg/microservice/aslan/core/templatestore/service/build.go @@ -17,6 +17,8 @@ limitations under the License. package service import ( + "fmt" + "go.uber.org/zap" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -77,10 +79,26 @@ func ListBuildTemplates(pageNum, pageSize int) (*BuildTemplateListResp, error) { return ret, nil } -func RemoveBuildTemplate(name string, logger *zap.SugaredLogger) error { - err := mongodb.NewBuildTemplateColl().DeleteByID(name) +func RemoveBuildTemplate(id string, logger *zap.SugaredLogger) error { + _, err := GetBuildTemplateByID(id) + if err != nil { + return fmt.Errorf("failed to find build template with id: %s, err: %s", id, err) + } + + // when template build is used by particular builds, this template can't be deleted + usedModules, err := mongodb.NewBuildColl().List(&commonrepo.BuildListOption{ + TemplateID: id, + }) + if err != nil { + return fmt.Errorf("failed to find builds with template id: %s, err: %s", id, err) + } + if len(usedModules) > 0 { + return fmt.Errorf("template build has beed used, can't be deleted") + } + + err = mongodb.NewBuildTemplateColl().DeleteByID(id) if err != nil { - logger.Errorf("Failed to delete build template %s, err: %s", name, err) + logger.Errorf("Failed to delete build template %s, err: %s", id, err) return err } return nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 581683ca..9b1c77a1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2080,6 +2080,10 @@ func fillBuildDetail(moduleBuild *commonmodels.Build, serviceName, serviceModule for _, serviceConfig := range moduleBuild.Targets { if serviceConfig.ServiceName == serviceName && serviceConfig.ServiceModule == serviceModule { moduleBuild.Repos = serviceConfig.Repos + if moduleBuild.PreBuild == nil { + moduleBuild.PreBuild = &commonmodels.PreBuild{} + } + moduleBuild.PreBuild.Envs = commonservice.MergeBuildEnvs(moduleBuild.PreBuild.Envs, serviceConfig.Envs) break } } @@ -2285,7 +2289,7 @@ func BuildModuleToSubTasks(args *commonmodels.BuildModuleArgs, log *zap.SugaredL build.JobCtx.EnvVars = module.PreBuild.Envs - if len(module.PreBuild.Envs) == 0 { + if len(build.JobCtx.EnvVars) == 0 { build.JobCtx.EnvVars = make([]*commonmodels.KeyVal, 0) } -- Gitee From a52b49e4efcf8153896938c262dd6b1a2af3e0f2 Mon Sep 17 00:00:00 2001 From: dyliu Date: Mon, 30 May 2022 12:04:00 +0800 Subject: [PATCH 2392/2647] The namespace field of codehost is no longer relied on in business logic (#1562) * fix namesapce is equal to empty case Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- .../aslan/core/common/service/service.go | 77 +++---------------- .../core/workflow/service/webhook/utils.go | 45 ++--------- 2 files changed, 16 insertions(+), 106 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index c45f0fec..5e0b501e 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -36,7 +36,6 @@ import ( templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util/converter" @@ -123,29 +122,10 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm } for _, serviceObject := range services { - // FIXME: 兼容老数据,想办法干掉这个 - if serviceObject.Source == setting.SourceFromGitlab && serviceObject.CodehostID == 0 { - gitlabAddress, err := GetGitlabAddress(serviceObject.SrcPath) - if err != nil { - log.Errorf("无法从原有数据中恢复加载信息, GetGitlabAddr failed err: %+v", err) - return nil, e.ErrListTemplate.AddDesc(err.Error()) - } - - details, err := systemconfig.New().ListCodeHostsInternal() - if err != nil { - log.Errorf("无法从原有数据中恢复加载信息, listCodehostDetail failed err: %+v", err) - return nil, e.ErrListTemplate.AddDesc(err.Error()) - } - for _, detail := range details { - if strings.Contains(detail.Address, gitlabAddress) { - serviceObject.CodehostID = detail.ID - } - } - if serviceObject.CodehostID == 0 { - log.Errorf("Failed to find the old code host info") - return nil, e.ErrListTemplate.AddDesc("无法找到原有的codehost信息,请确认codehost仍然存在") - } - + if serviceObject.CodehostID == 0 { + return nil, e.ErrListTemplate.AddDesc("codehost id is empty") + } + if serviceObject.Source == setting.SourceFromGitlab { err = fillServiceRepoInfo(serviceObject) if err != nil { log.Errorf("Failed to load info from url: %s, the error is: %s", serviceObject.SrcPath, err) @@ -157,17 +137,7 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm if err != nil { return nil, err } - address, err := GetGitlabAddress(serviceObject.SrcPath) - if err != nil { - return nil, err - } - detail, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: serviceObject.RepoOwner}) - if err != nil { - log.Errorf("get github codeHostInfo failed, err:%v", err) - return nil, err - } - serviceObject.CodehostID = detail.ID + serviceObject.LoadFromDir = true } @@ -321,29 +291,11 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str } } + if resp.CodehostID == 0 { + return nil, e.ErrGetTemplate.AddDesc("Please confirm if codehost exists") + } + if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { - gitlabAddress, err := GetGitlabAddress(resp.SrcPath) - if err != nil { - errMsg := fmt.Sprintf("[ServiceTmpl.Find] GetGitlabAddress %s error: %s", serviceName, err) - log.Error(errMsg) - return resp, e.ErrGetTemplate.AddDesc(errMsg) - } - details, err := systemconfig.New().ListCodeHostsInternal() - if err != nil { - errMsg := fmt.Sprintf("[ServiceTmpl.Find] ListCodehostDetail %s error: %s", serviceName, err) - log.Error(errMsg) - return resp, e.ErrGetTemplate.AddDesc(errMsg) - } - for _, detail := range details { - if strings.Contains(detail.Address, gitlabAddress) { - resp.GerritCodeHostID = detail.ID - resp.CodehostID = detail.ID - } - } - if resp.CodehostID == 0 { - log.Errorf("Failed to find the old code host info") - return nil, e.ErrGetTemplate.AddDesc("无法找到原有的codehost信息,请确认codehost仍然存在") - } err = fillServiceRepoInfo(resp) if err != nil { log.Errorf("Failed to load info from url: %s, the error is: %s", resp.SrcPath, err) @@ -355,18 +307,7 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str if err != nil { return nil, err } - address, err := GetGitlabAddress(resp.SrcPath) - if err != nil { - return nil, err - } - detail, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: resp.RepoOwner}) - if err != nil { - log.Errorf("get github codeHostInfo failed, err:%v", err) - return nil, err - } - resp.CodehostID = detail.ID return resp, nil } else if resp.Source == setting.SourceFromGUI { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index a953b5bc..a8766fd9 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -383,22 +383,10 @@ func syncContentFromGitlab(userName string, args *commonmodels.Service) error { return nil } - _, owner, repo, branch, path, pathType, err := GetOwnerRepoBranchPath(args.SrcPath) - if err != nil { - return fmt.Errorf("url format failed") - } - - if len(args.LoadPath) > 0 { - path = args.LoadPath - } - if len(args.BranchName) > 0 { - branch = args.BranchName - } - if len(args.RepoName) > 0 { - repo = args.RepoName - } - if len(args.GetRepoNamespace()) > 0 { - owner = args.GetRepoNamespace() + var owner, repo, branch, path string = args.GetRepoNamespace(), args.RepoName, args.BranchName, args.LoadPath + var pathType = "tree" + if strings.Contains(args.SrcPath, "blob") { + pathType = "blob" } client, err := getGitlabClientByCodehostId(args.CodehostID) @@ -429,30 +417,11 @@ func joinYamls(files []string) string { func syncContentFromGithub(args *commonmodels.Service, log *zap.SugaredLogger) error { // 根据pipeline中的filepath获取文件内容 - address, owner, repo, branch, path, _, err := GetOwnerRepoBranchPath(args.SrcPath) - - if err != nil { - log.Errorf("parseOwnerRepoBranchPath failed, srcPath:%s, err:%v", args.SrcPath, err) - return errors.New("invalid url " + args.SrcPath) - } - - if len(args.LoadPath) > 0 { - path = args.LoadPath - } - if len(args.BranchName) > 0 { - branch = args.BranchName - } - if len(args.RepoName) > 0 { - repo = args.RepoName - } - if len(args.GetRepoNamespace()) > 0 { - owner = args.GetRepoNamespace() - } + var owner, repo, branch, path = args.GetRepoNamespace(), args.RepoName, args.BranchName, args.LoadPath - ch, err := systemconfig.GetCodeHostInfo( - &systemconfig.Option{CodeHostType: systemconfig.GitHubProvider, Address: address, Namespace: owner}) + ch, err := systemconfig.New().GetCodeHost(args.CodehostID) if err != nil { - log.Errorf("GetCodeHostInfo failed, srcPath:%s, err:%v", args.SrcPath, err) + log.Errorf("failed to getCodeHostInfo, srcPath:%s, err:%s", args.SrcPath, err) return err } -- Gitee From c0061cc2e8ca3b4bb3da2e6e798757aa73a9f405 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 30 May 2022 14:07:26 +0800 Subject: [PATCH 2393/2647] cancel function implemented Signed-off-by: Min Min --- .../aslan/core/common/service/task.go | 4 ++ .../core/workflow/testing/handler/router.go | 1 + .../core/workflow/testing/handler/scanning.go | 37 +++++++++++++++++++ .../core/workflow/testing/service/scanning.go | 12 ++++++ 4 files changed, 54 insertions(+) diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index 4ad1c455..07cf1206 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -87,6 +87,8 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { _ = scmNotifyService.UpdatePipelineWebhookComment(t, log) + } else if typeString == config.ScanningType { + _ = scmNotifyService.UpdateWebhookCommentForScanning(t, log) } return nil @@ -134,6 +136,8 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P _ = scmNotifyService.UpdateWebhookCommentForTest(t, log) } else if typeString == config.SingleType { _ = scmNotifyService.UpdatePipelineWebhookComment(t, log) + } else if typeString == config.ScanningType { + _ = scmNotifyService.UpdateWebhookCommentForScanning(t, log) } return nil diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/router.go b/pkg/microservice/aslan/core/workflow/testing/handler/router.go index c87fd3c2..c1345ef2 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/router.go @@ -69,6 +69,7 @@ func (*Router) Inject(router *gin.RouterGroup) { scanner.POST("/:id/task", gin2.UpdateOperationLogStatus, CreateScanningTask) scanner.GET("/:id/task", ListScanningTask) scanner.GET("/:id/task/:scan_id", GetScanningTask) + scanner.DELETE("/:id/task/:scan_id", gin2.UpdateOperationLogStatus, CancelScanningTask) scanner.GET("/:id/task/:scan_id/sse", GetScanningTaskSSE) } diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go index 91dc1bac..bd6aa62f 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go @@ -21,6 +21,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/config" e "github.com/koderover/zadig/pkg/tool/errors" "io/ioutil" "k8s.io/apimachinery/pkg/util/wait" @@ -225,6 +226,42 @@ func GetScanningTask(c *gin.Context) { ctx.Resp, ctx.Err = service.GetScanningTaskInfo(id, taskID, ctx.Logger) } +func CancelScanningTask(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + id := c.Param("id") + if id == "" { + ctx.Err = MissingIDError + return + } + + taskIDStr := c.Param("scan_id") + if taskIDStr == "" { + ctx.Err = fmt.Errorf("scan_id must be provided") + return + } + + taskID, err := strconv.ParseInt(taskIDStr, 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(fmt.Sprintf("invalid task id: %s", err)) + return + } + + req := make([]*service.ScanningRepoInfo, 0) + data, err := c.GetRawData() + if err != nil { + log.Errorf("Create scanning task c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, &req); err != nil { + log.Errorf("Create scanning task json.Unmarshal err : %v", err) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "取消", "代码扫描任务", id, string(data), ctx.Logger) + + ctx.Err = service.CancelScanningTask(ctx.UserName, id, taskID, config.ScanningType, ctx.RequestID, ctx.Logger) +} + func GetScanningTaskSSE(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go index dcffeef4..3996adcd 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -415,3 +415,15 @@ func GetScanningTaskInfo(scanningID string, taskID int64, log *zap.SugaredLogger ResultLink: sonarInfo.ServerAddress, }, nil } + +func CancelScanningTask(userName, scanningID string, taskID int64, typeString config.PipelineType, requestID string, log *zap.SugaredLogger) error { + scanningInfo, err := commonrepo.NewScanningColl().GetByID(scanningID) + if err != nil { + log.Errorf("failed to get scanning from mongodb, the error is: %s", err) + return err + } + + scanningTaskName := fmt.Sprintf("%s-%s-%s", scanningInfo.Name, scanningID, "scanning-job") + + return commonservice.CancelTaskV2(userName, scanningTaskName, taskID, typeString, requestID, log) +} -- Gitee From fb73fd403d5d8a3264565d870d0cb7ee72d335ad Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 30 May 2022 14:51:50 +0800 Subject: [PATCH 2394/2647] fix collaboration bulk create workflow error (#1570) * rollback init config Signed-off-by: panxunying * fix collaboration bulk create workflow error Signed-off-by: panxunying Co-authored-by: panxunying --- .../workflow/service/workflow/workflow.go | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 6cf257e3..6177a826 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -744,13 +744,12 @@ type BulkCopyWorkflowArgs struct { func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.SugaredLogger) error { var workflows []commonrepo.Workflow - workflowMap := make(map[string]WorkflowCopyItem) + for _, item := range args.Items { workflows = append(workflows, commonrepo.Workflow{ ProjectName: item.ProjectName, Name: item.Old, }) - workflowMap[item.ProjectName+"-"+item.Old] = item } oldWorkflows, err := commonrepo.NewWorkflowColl().ListByWorkflows(commonrepo.ListWorkflowOpt{ Workflows: workflows, @@ -759,16 +758,22 @@ func BulkCopyWorkflow(args BulkCopyWorkflowArgs, username string, log *zap.Sugar log.Error(err) return e.ErrGetPipeline.AddErr(err) } - var newWorkflows []*commonmodels.Workflow + workflowMap := make(map[string]*commonmodels.Workflow) for _, workflow := range oldWorkflows { - if item, ok := workflowMap[workflow.ProductTmplName+"-"+workflow.Name]; ok { - workflow.UpdateBy = username - workflow.Name = item.New - workflow.BaseName = item.BaseName - workflow.ID = primitive.NewObjectID() - newWorkflows = append(newWorkflows, workflow) + workflowMap[workflow.ProductTmplName+"-"+workflow.Name] = workflow + } + var newWorkflows []*commonmodels.Workflow + for _, workflow := range args.Items { + if item, ok := workflowMap[workflow.ProjectName+"-"+workflow.Old]; ok { + newItem := *item + newItem.UpdateBy = username + newItem.Name = workflow.New + newItem.BaseName = workflow.BaseName + newItem.ID = primitive.NewObjectID() + + newWorkflows = append(newWorkflows, &newItem) } else { - return fmt.Errorf("workflow:%s not exist", workflow.ProductTmplName+"-"+workflow.Name) + return fmt.Errorf("workflow:%s not exist", item.ProductTmplName+"-"+item.Name) } } return commonrepo.NewWorkflowColl().BulkCreate(newWorkflows) -- Gitee From f56d97c80046a3d53a8069b4b9cc8d24290ed0e4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 30 May 2022 14:53:53 +0800 Subject: [PATCH 2395/2647] self code review changes Signed-off-by: Min Min --- pkg/microservice/aslan/core/common/service/task.go | 12 ++++++++++-- .../aslan/core/workflow/testing/handler/scanning.go | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/task.go b/pkg/microservice/aslan/core/common/service/task.go index 07cf1206..aa203da5 100644 --- a/pkg/microservice/aslan/core/common/service/task.go +++ b/pkg/microservice/aslan/core/common/service/task.go @@ -88,7 +88,11 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P } else if typeString == config.SingleType { _ = scmNotifyService.UpdatePipelineWebhookComment(t, log) } else if typeString == config.ScanningType { - _ = scmNotifyService.UpdateWebhookCommentForScanning(t, log) + err = scmNotifyService.UpdateWebhookCommentForScanning(t, log) + // this logic will not affect the result of cancellation, so we only print the error in logs. + if err != nil { + log.Errorf("Failed to update webhook comment for scanning in upon cancel, the error is: %s", err) + } } return nil @@ -137,7 +141,11 @@ func CancelTask(userName, pipelineName string, taskID int64, typeString config.P } else if typeString == config.SingleType { _ = scmNotifyService.UpdatePipelineWebhookComment(t, log) } else if typeString == config.ScanningType { - _ = scmNotifyService.UpdateWebhookCommentForScanning(t, log) + err = scmNotifyService.UpdateWebhookCommentForScanning(t, log) + // this logic will not affect the result of cancellation, so we only print the error in logs. + if err != nil { + log.Errorf("Failed to update webhook comment for scanning in upon cancel, the error is: %s", err) + } } return nil diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go index bd6aa62f..602aebee 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go @@ -21,7 +21,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/koderover/zadig/pkg/microservice/aslan/config" e "github.com/koderover/zadig/pkg/tool/errors" "io/ioutil" "k8s.io/apimachinery/pkg/util/wait" @@ -30,6 +29,7 @@ import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" -- Gitee From 35081d0fd7df64774bfb07ca04dcb12b35a3cc22 Mon Sep 17 00:00:00 2001 From: dyliu Date: Mon, 30 May 2022 14:58:44 +0800 Subject: [PATCH 2396/2647] fix codehost not found bug (#1568) Signed-off-by: liu deyi --- .../aslan/core/common/service/service.go | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index 5e0b501e..d1321fe5 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -122,10 +122,10 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm } for _, serviceObject := range services { - if serviceObject.CodehostID == 0 { - return nil, e.ErrListTemplate.AddDesc("codehost id is empty") - } if serviceObject.Source == setting.SourceFromGitlab { + if serviceObject.CodehostID == 0 { + return nil, e.ErrListTemplate.AddDesc("codehost id is empty") + } err = fillServiceRepoInfo(serviceObject) if err != nil { log.Errorf("Failed to load info from url: %s, the error is: %s", serviceObject.SrcPath, err) @@ -133,6 +133,9 @@ func ListServiceTemplate(productName string, log *zap.SugaredLogger) (*ServiceTm } serviceObject.LoadFromDir = true } else if serviceObject.Source == setting.SourceFromGithub && serviceObject.RepoName == "" { + if serviceObject.CodehostID == 0 { + return nil, e.ErrListTemplate.AddDesc("codehost id is empty") + } err = fillServiceRepoInfo(serviceObject) if err != nil { return nil, err @@ -291,18 +294,20 @@ func GetServiceTemplate(serviceName, serviceType, productName, excludeStatus str } } - if resp.CodehostID == 0 { - return nil, e.ErrGetTemplate.AddDesc("Please confirm if codehost exists") - } - if resp.Source == setting.SourceFromGitlab && resp.RepoName == "" { + if resp.CodehostID == 0 { + return nil, e.ErrGetTemplate.AddDesc("Please confirm if codehost exists") + } err = fillServiceRepoInfo(resp) if err != nil { log.Errorf("Failed to load info from url: %s, the error is: %s", resp.SrcPath, err) return nil, e.ErrGetService.AddDesc(fmt.Sprintf("Failed to load info from url: %s, the error is: %+v", resp.SrcPath, err)) } return resp, nil - } else if resp.Source == setting.SourceFromGithub && resp.GerritCodeHostID == 0 { + } else if resp.Source == setting.SourceFromGithub { + if resp.CodehostID == 0 { + return nil, e.ErrGetTemplate.AddDesc("Please confirm if codehost exists") + } err = fillServiceRepoInfo(resp) if err != nil { return nil, err -- Gitee From 9e0335489173f3a28fe7cd65a00875ab9a18b429 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 30 May 2022 15:23:55 +0800 Subject: [PATCH 2397/2647] code review changes Signed-off-by: Min Min --- .../aslan/core/workflow/testing/handler/scanning.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go index 602aebee..59afff72 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/scanning.go @@ -248,16 +248,7 @@ func CancelScanningTask(c *gin.Context) { return } - req := make([]*service.ScanningRepoInfo, 0) - data, err := c.GetRawData() - if err != nil { - log.Errorf("Create scanning task c.GetRawData() err : %v", err) - } - if err = json.Unmarshal(data, &req); err != nil { - log.Errorf("Create scanning task json.Unmarshal err : %v", err) - } - - internalhandler.InsertOperationLog(c, ctx.UserName, "", "取消", "代码扫描任务", id, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "取消", "代码扫描任务", id, taskIDStr, ctx.Logger) ctx.Err = service.CancelScanningTask(ctx.UserName, id, taskID, config.ScanningType, ctx.RequestID, ctx.Logger) } -- Gitee From ca64534fe0ada634f84cd3772c7942e3d6c0c309 Mon Sep 17 00:00:00 2001 From: Min Min Date: Mon, 30 May 2022 17:24:22 +0800 Subject: [PATCH 2398/2647] enable submodules and checkout Path related logic for code scanning Signed-off-by: Min Min --- .../core/workflow/testing/service/scanning.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go index 3996adcd..e19f9782 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -202,7 +202,8 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, notificationID, user log.Errorf("failed to get codehost info from mongodb, the error is: %s", err) return 0, err } - repos = append(repos, &types.Repository{ + + repoInfo := &types.Repository{ Source: rep.Type, RepoOwner: arg.RepoOwner, RepoName: arg.RepoName, @@ -216,7 +217,19 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, notificationID, user EnableProxy: rep.EnableProxy, RepoNamespace: arg.RepoNamespace, Tag: arg.Tag, - }) + } + + for _, repo := range scanningInfo.Repos { + // make sure we are using the same repo's configuration + if repo.CodehostID == arg.CodehostID && repo.RepoName == arg.RepoName { + repoInfo.SubModules = repo.SubModules + repoInfo.RemoteName = repo.RemoteName + repoInfo.CheckoutPath = repo.CheckoutPath + break + } + } + + repos = append(repos, repoInfo) } scanningTask := &task.Scanning{ -- Gitee From b981268ab3a135177e8f8f9d2d09dfa6e2300f14 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 30 May 2022 21:42:16 +0800 Subject: [PATCH 2399/2647] fix bug (#1574) Signed-off-by: allenshen --- pkg/microservice/aslan/core/code/client/github/github.go | 2 +- .../aslan/core/common/repository/mongodb/build_template.go | 1 + .../aslan/core/common/service/github/repository.go | 1 - pkg/tool/git/github/repositories.go | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index b88f502e..dee584d6 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -134,7 +134,7 @@ func (c *Client) ListNamespaces(keyword string) ([]*client.Namespace, error) { } func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { - repos, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO(), nil) + repos, err := c.Client.ListRepositoriesForAuthenticatedUser(context.TODO(), opt.Namespace, nil) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go b/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go index 312497ae..c6c888b6 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/build_template.go @@ -65,6 +65,7 @@ func (c *BuildTemplateColl) Create(obj *models.BuildTemplate) error { if obj == nil { return fmt.Errorf("nil object") } + obj.ID = primitive.NilObjectID _, err := c.InsertOne(context.TODO(), obj) return err } diff --git a/pkg/microservice/aslan/core/common/service/github/repository.go b/pkg/microservice/aslan/core/common/service/github/repository.go index 06134f34..8745b241 100644 --- a/pkg/microservice/aslan/core/common/service/github/repository.go +++ b/pkg/microservice/aslan/core/common/service/github/repository.go @@ -62,6 +62,5 @@ func (c *Client) GetFileContent(owner, repo, path, branch string) ([]byte, error } res, err := fileContent.GetContent() - return []byte(res), err } diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 803d7281..8b13cee4 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -29,9 +29,9 @@ import ( fsutil "github.com/koderover/zadig/pkg/util/fs" ) -func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, opts *ListOptions) ([]*github.Repository, error) { +func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, user string, opts *ListOptions) ([]*github.Repository, error) { repositories, err := wrap(paginated(func(o *github.ListOptions) ([]interface{}, *github.Response, error) { - rs, r, err := c.Repositories.List(ctx, "", &github.RepositoryListOptions{ListOptions: *o}) + rs, r, err := c.Repositories.List(ctx, user, &github.RepositoryListOptions{ListOptions: *o}) var res []interface{} for _, r := range rs { res = append(res, r) -- Gitee From e530a8fd78eaae03bcd4802dab74e2eba91ef245 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Tue, 31 May 2022 09:27:17 +0800 Subject: [PATCH 2400/2647] bugfix (#1578) Signed-off-by: mouuii --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index b7577e00..73d4b691 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -663,7 +663,13 @@ func TestArgsToTestSubtask(args *commonmodels.TestTaskArgs, pt *task.Task, log * } else { testArg.Builds = testing.Repos pr, _ := strconv.Atoi(args.MergeRequestID) - testArg.Builds[0].PR = pr + + for i, build := range testArg.Builds { + if build.Source == args.Source { + testArg.Builds[i].PR = pr + } + } + } if testing.PreTest != nil { -- Gitee From 67de7427996753cc1942d4d65b0eb28993c92ddf Mon Sep 17 00:00:00 2001 From: dyliu Date: Tue, 31 May 2022 10:20:57 +0800 Subject: [PATCH 2401/2647] fix gerrit webhook review and service update problem (#1579) Signed-off-by: liu deyi --- .../aslan/core/common/service/scmnotify/client.go | 2 +- pkg/microservice/aslan/core/service/service/loader.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index 10662073..5a0dd937 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -99,7 +99,7 @@ func (c *Client) Comment(notify *models.Notification) error { // create task created comment if !task.FirstCommented && task.Status == config.TaskStatusReady { if e := cli.SetReview( - notify.ProjectID, + notify.RepoName, notify.PrID, fmt.Sprintf(""+ "%s ⏱️ %s/v1/projects/detail/%s/pipelines/multi/%s/%d", diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index 7c2d0422..b8fcdf69 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -805,6 +805,9 @@ func validateServiceUpdateGitlab(detail *systemconfig.CodeHost, serviceName, rep // 根据gerrit repo决定服务是否可以更新这个repo地址 func validateServiceUpdateGerrit(detail *systemconfig.CodeHost, serviceName, repoName, branchName, remoteName, loadPath string, isDir bool) error { + if remoteName == "" { + remoteName = "origin" + } base := path.Join(config.S3StoragePath(), repoName) if _, err := os.Stat(base); os.IsNotExist(err) { err = command.RunGitCmds(detail, setting.GerritDefaultOwner, setting.GerritDefaultOwner, repoName, branchName, remoteName) @@ -890,6 +893,9 @@ func validateServiceUpdateCodehub(detail *systemconfig.CodeHost, serviceName, re // Determine whether the service can update the repo address according to the gitee repo func validateServiceUpdateGitee(detail *systemconfig.CodeHost, serviceName, repoOwner, repoName, branchName, remoteName, loadPath string, isDir bool) error { + if remoteName == "" { + remoteName = "origin" + } base := path.Join(config.S3StoragePath(), repoName) if exist, err := util.PathExists(base); !exist { log.Warnf("path does not exist,err:%s", err) -- Gitee From d1e521dcdf3707f6cafd6833f1144a7aff52da6c Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 17 May 2022 18:13:42 +0800 Subject: [PATCH 2402/2647] new workflow controller Signed-off-by: guoyu --- pkg/microservice/aslan/config/consts.go | 22 +++++ .../repository/models/wokflow_task_v4.go | 80 +++++++++++++++++++ .../common/repository/models/workflow_v4.go | 57 +++++++++++++ .../workflow/service/jobcontroller/job.go | 73 +++++++++++++++++ .../jobcontroller/stepcontroller/step.go | 66 +++++++++++++++ .../stepcontroller/step_shell.go | 65 +++++++++++++++ .../service/workflowcontroller/workflow.go | 67 ++++++++++++++++ 7 files changed, 430 insertions(+) create mode 100644 pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go create mode 100644 pkg/microservice/aslan/core/common/repository/models/workflow_v4.go create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 8e44f40e..99f099b1 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -148,6 +148,28 @@ const ( TaskArtifactPackage TaskType = "artifact_package" ) +type StepType string + +const ( + StepTools StepType = "tools" + StepShell StepType = "shell" + StepDockerBuild StepType = "docker_build" + StepDeploy StepType = "deploy" + StepHelmDeploy StepType = "helm_deploy" + StepImageDistribute StepType = "image_distribute" + StepArchive StepType = "archive" + StepArchiveDistribute StepType = "archive_distribute" + StepJunitReport StepType = "junit_report" + StepHtmlReport StepType = "html_report" +) + +type StageType string + +const ( + StageCustom StageType = "custom" + StepApproval StageType = "approval" +) + type DistributeType string const ( diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go new file mode 100644 index 00000000..493aa23f --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -0,0 +1,80 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" +) + +type WorkflowTask struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Stages []*StageTask `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + AgentID string `bson:"agent_id" json:"agent_id"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` + StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` +} + +func (WorkflowTask) TableName() string { + return "workflow_task" +} + +type StageTask struct { + Name string `bson:"name" json:"name"` + Status config.Status `bson:"status" json:"status"` + // default is custom defined by user. + StageType string `bson:"stage_type" json:"stage_type"` + Spec interface{} `bson:"spec" json:"spec"` + Jobs []*JobTask `bson:"jobs" json:"jobs"` +} + +type JobTask struct { + Name string `bson:"name" json:"name"` + Status config.Status `bson:"status" json:"status"` + Namepace string `bson:"namespace" json:"namespace"` + PodName string `bson:"pod_name" json:"pod_name"` + Properties *JobProperties `bson:"properties" json:"properties"` + Steps []*StepTask `bson:"steps" json:"steps"` +} + +type StepTask struct { + Name string `bson:"name" json:"name"` + JobName string `bson:"job_name" json:"job_name"` + Status config.Status `bson:"status" json:"status"` + StepType config.StepType `bson:"step_type" json:"step_type"` + Spec interface{} `bson:"Spec" json:"Spec"` + Outputs []*StepOutput `bson:"outputs" json:"outputs"` +} diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go new file mode 100644 index 00000000..fa0eb3f9 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -0,0 +1,57 @@ +package models + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type WorkflowV4 struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name" json:"name"` + Args []*KeyVal `bson:"args" json:"args"` + Stages []*WorkflowStage `bson:"stages" json:"stages"` + ProjectName string `bson:"project_name" json:"project_name"` + Description string `bson:"description" json:"description"` + CreatedBy string `bson:"created_by" json:"created_by"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdatedBy string `bson:"updated_by" json:"updated_by"` + UpdateTime int64 `bson:"update_time" json:"update_time"` +} + +type WorkflowStage struct { + Name string `bson:"name" json:"name"` + // default is custom defined by user. + StageType string `bson:"stage_type" json:"stage_type"` + Spec interface{} `bson:"spec" json:"spec"` + Jobs []*Job `bson:"jobs" json:"jobs"` +} + +type Job struct { + Name string `bson:"name" json:"name"` + Properties *JobProperties `bson:"properties" json:"properties"` + Steps []*Step `bson:"steps" json:"steps"` +} + +type JobProperties struct { + Timeout int64 `bson:"timeout" json:"timeout"` + Retry int64 `bson:"retry" json:"retry"` + ResourceRequest string `bson:"resource_request" json:"resource_request"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + Args []*KeyVal `bson:"args" json:"args"` +} + +type Step struct { + Name string `bson:"name" json:"name"` + StepType config.StepType `bson:"step_type" json:"step_type"` + Spec interface{} `bson:"Spec" json:"Spec"` + Outputs []*StepOutput `bson:"outputs" json:"outputs"` +} + +type StepOutput struct { + Name string `bson:"name" json:"name"` + Description string `bson:"description" json:"description"` +} + +func (WorkflowV4) TableName() string { + return "workflow_v4" +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go new file mode 100644 index 00000000..baab09e4 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -0,0 +1,73 @@ +package jobcontroller + +import ( + "context" + "sync" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller" + "go.uber.org/zap" +) + +type jobCtl struct { + job *commonmodels.JobTask + jobContext *sync.Map + globalContext *sync.Map + logger *zap.SugaredLogger + ack func() error +} + +func NewJobCtl(job *commonmodels.JobTask, globalContext *sync.Map, ack func() error, logger *zap.SugaredLogger) *jobCtl { + return &jobCtl{ + job: job, + globalContext: globalContext, + logger: logger, + ack: ack, + } +} + +func (c *jobCtl) Run(ctx context.Context) { + defer c.stop(ctx) + defer c.ack() + done := make(chan struct{}, 1) + go func() { + c.run(ctx) + done <- struct{}{} + }() + select { + case <-done: + return + case <-ctx.Done(): + c.job.Status = config.StatusCancelled + return + case <-time.After(time.Minute * time.Duration(c.job.Properties.Timeout)): + c.job.Status = config.StatusTimeout + return + } +} + +func (c *jobCtl) run(ctx context.Context) { + c.job.Status = config.StatusRunning + c.ack() + // TODO create pods + for _, step := range c.job.Steps { + done := make(chan struct{}, 1) + go func(stepTask *stepcontroller.StepTask) { + stepTask.Run(ctx, c.globalContext, c.jobContext, c.ack, c.job.Namepace, c.job.PodName, c.logger) + done <- struct{}{} + }((*stepcontroller.StepTask)(step)) + select { + case <-done: + return + case <-ctx.Done(): + step.Status = config.StatusCancelled + return + } + } +} + +func (c *jobCtl) stop(ctx context.Context) { + // TODO delete pods +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go new file mode 100644 index 00000000..01735212 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go @@ -0,0 +1,66 @@ +package stepcontroller + +import ( + "context" + "sync" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + "go.uber.org/zap" +) + +type Step interface { + // Type ... + stepType() config.StepType + + // Status ... + status() config.Status + + // outputVariables + outputVariables() map[string]string + + // Run ... + run(ctx context.Context) +} + +type StepTask commonmodels.StepTask + +// render context key value to step spec +func (s *StepTask) renderSpec(globalContext, jobContext *sync.Map) { +} + +// render context key value to step spec +func (s *StepTask) writeOutputToContext(globalContext, jobContext *sync.Map, outputMap map[string]string) { +} + +func (s *StepTask) Run(ctx context.Context, globalContext, jobContext *sync.Map, ack func() error, logger *zap.SugaredLogger) { + done := make(chan struct{}, 1) + go func() { + s.run(ctx, globalContext, jobContext, ack, logger) + done <- struct{}{} + }() + select { + case <-done: + return + case <-ctx.Done(): + s.Status = config.StatusCancelled + return + } +} + +func (s *StepTask) run(ctx context.Context, globalContext, jobContext *sync.Map, ack func() error, logger *zap.SugaredLogger) { + s.Status = config.StatusRunning + ack() + s.renderSpec(globalContext, jobContext) + var stepController Step + switch s.StepType { + case config.StepShell: + stepController = NewShellStepController(s, ack, logger) + default: + log.Errorf("no matched step type found: %s", s.StepType) + } + stepController.run(ctx) + output := stepController.outputVariables() + s.writeOutputToContext(globalContext, jobContext, output) +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go new file mode 100644 index 00000000..fd4bca99 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go @@ -0,0 +1,65 @@ +package stepcontroller + +import ( + "context" + "encoding/json" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "go.uber.org/zap" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type shellStep struct { + stepTask *StepTask + stepSpec *ShellSpec + kubeClient client.Client + clientset kubernetes.Interface + restConfig *rest.Config + logger *zap.SugaredLogger + + ack func() error +} + +type ShellSpec struct { + Script string `bson:"script" yaml:"script"` +} + +func NewShellStepController(stepTask *StepTask, ack func() error, logger *zap.SugaredLogger) *shellStep { + shellStep := &shellStep{ + stepTask: stepTask, + kubeClient: krkubeclient.Client(), + clientset: krkubeclient.Clientset(), + restConfig: krkubeclient.RESTConfig(), + ack: ack, + logger: logger, + } + b, err := json.Marshal(stepTask.Spec) + if err != nil { + logger.Errorf("marshal shell spec error: %s", err) + return shellStep + } + shellSpec := ShellSpec{} + if err := json.Unmarshal(b, &shellSpec); err != nil { + logger.Errorf("unmarshal shell spec error: %s", err) + } + shellStep.stepSpec = &shellSpec + return shellStep +} + +func (s *shellStep) stepType() config.StepType { + return config.StepShell +} + +func (s *shellStep) status() config.Status { + return s.stepTask.Status +} + +func (s *shellStep) outputVariables() map[string]string { + return map[string]string{} + +} + +func (s *shellStep) run(ctx context.Context) {} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go new file mode 100644 index 00000000..edbfbd7c --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -0,0 +1,67 @@ +package workflowcontroller + +import ( + "context" + "sync" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller" + "go.uber.org/zap" +) + +var approveChannelMap sync.Map +var cancelChannelMap sync.Map + +type workflowCtl struct { + workflow *commonmodels.WorkflowV4 + workflowTask *commonmodels.WorkflowTask + globalContext *sync.Map + logger *zap.SugaredLogger + ack func() error +} + +func NewWorkflowController(workflow *commonmodels.WorkflowV4, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) *workflowCtl { + ctl := &workflowCtl{ + workflow: workflow, + workflowTask: workflowTask, + logger: logger, + } + ctl.ack = ctl.updateWorkflowTask + return ctl +} + +func (c *workflowCtl) Run(ctx context.Context) error { + defer c.ack() + defer updateworkflowStatus(c.workflowTask) + ctx, cancel := context.WithCancel(ctx) + defer cancel() + cancelChannelMap.Store(c.workflowTask.TaskID, cancel) + for _, stage := range c.workflowTask.Stages { + c.runStage(ctx, stage) + } + return nil +} + +func (c *workflowCtl) runStage(ctx context.Context, stage *commonmodels.StageTask) { + defer c.ack() + defer updateStageStatus(stage) + switch stage.StageType { + case "approve": + //TODO wait for approve + default: + // TODO run job in parallel + for _, job := range stage.Jobs { + jobCtl := jobcontroller.NewJobCtl(job, c.globalContext, c.ack, c.logger) + jobCtl.Run(ctx) + } + } +} + +func updateStageStatus(stage *commonmodels.StageTask) {} + +func updateworkflowStatus(workflow *commonmodels.WorkflowTask) {} + +func (c *workflowCtl) updateWorkflowTask() error { + // TODO update workflow task + return nil +} -- Gitee From 05d592e8e9a8b3d3030025df7a9a51aa66bc2ed1 Mon Sep 17 00:00:00 2001 From: guoyu Date: Wed, 18 May 2022 22:20:20 +0800 Subject: [PATCH 2403/2647] update schedule logic Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 28 ++-- .../common/repository/models/workflow_v4.go | 3 +- .../workflow/service/jobcontroller/job.go | 56 ++++--- .../stepcontroller/step.go | 12 +- .../stepcontroller/step_shell.go | 8 +- .../service/workflowcontroller/queue.go | 1 + .../service/workflowcontroller/workflow.go | 156 ++++++++++++++++-- 7 files changed, 214 insertions(+), 50 deletions(-) rename pkg/microservice/aslan/core/workflow/service/{jobcontroller => }/stepcontroller/step.go (88%) rename pkg/microservice/aslan/core/workflow/service/{jobcontroller => }/stepcontroller/step_shell.go (89%) create mode 100644 pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 493aa23f..677df3a1 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -36,12 +36,10 @@ type WorkflowTask struct { EndTime int64 `bson:"end_time" json:"end_time,omitempty"` Stages []*StageTask `bson:"stages" json:"stages"` ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - AgentHost string `bson:"agent_host,omitempty" json:"agent_host,omitempty"` DockerHost string `bson:"-" json:"docker_host,omitempty"` TeamName string `bson:"team,omitempty" json:"team,omitempty"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` IsArchived bool `bson:"is_archived" json:"is_archived"` - AgentID string `bson:"agent_id" json:"agent_id"` Error string `bson:"error,omitempty" json:"error,omitempty"` StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` @@ -53,10 +51,13 @@ func (WorkflowTask) TableName() string { } type StageTask struct { - Name string `bson:"name" json:"name"` - Status config.Status `bson:"status" json:"status"` + Name string `bson:"name" json:"name"` + Status config.Status `bson:"status" json:"status"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` // default is custom defined by user. StageType string `bson:"stage_type" json:"stage_type"` + Parallel bool `bson:"parallel" json:"parallel"` Spec interface{} `bson:"spec" json:"spec"` Jobs []*JobTask `bson:"jobs" json:"jobs"` } @@ -64,17 +65,22 @@ type StageTask struct { type JobTask struct { Name string `bson:"name" json:"name"` Status config.Status `bson:"status" json:"status"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` Namepace string `bson:"namespace" json:"namespace"` - PodName string `bson:"pod_name" json:"pod_name"` + PodName string `bson:"pod_name" json:"pod_name"` Properties *JobProperties `bson:"properties" json:"properties"` Steps []*StepTask `bson:"steps" json:"steps"` } type StepTask struct { - Name string `bson:"name" json:"name"` - JobName string `bson:"job_name" json:"job_name"` - Status config.Status `bson:"status" json:"status"` - StepType config.StepType `bson:"step_type" json:"step_type"` - Spec interface{} `bson:"Spec" json:"Spec"` - Outputs []*StepOutput `bson:"outputs" json:"outputs"` + Name string `bson:"name" json:"name"` + JobName string `bson:"job_name" json:"job_name"` + Timeout int64 `bson:"timeout" json:"timeout"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Status config.Status `bson:"status" json:"status"` + StepType config.StepType `bson:"step_type" json:"step_type"` + Spec interface{} `bson:"Spec" json:"Spec"` + Outputs []*StepOutput `bson:"outputs" json:"outputs"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index fa0eb3f9..2ca0f40e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -42,6 +42,7 @@ type JobProperties struct { type Step struct { Name string `bson:"name" json:"name"` + Timeout int64 `bson:"timeout" json:"timeout"` StepType config.StepType `bson:"step_type" json:"step_type"` Spec interface{} `bson:"Spec" json:"Spec"` Outputs []*StepOutput `bson:"outputs" json:"outputs"` @@ -49,7 +50,7 @@ type Step struct { type StepOutput struct { Name string `bson:"name" json:"name"` - Description string `bson:"description" json:"description"` + Description string `bson:"description" json:"description"` } func (WorkflowV4) TableName() string { diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index baab09e4..76d83609 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -7,30 +7,41 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/stepcontroller" "go.uber.org/zap" ) -type jobCtl struct { +type JobCtl struct { job *commonmodels.JobTask jobContext *sync.Map globalContext *sync.Map logger *zap.SugaredLogger + Ctx context.Context ack func() error } -func NewJobCtl(job *commonmodels.JobTask, globalContext *sync.Map, ack func() error, logger *zap.SugaredLogger) *jobCtl { - return &jobCtl{ +func NewJobCtl(ctx context.Context, job *commonmodels.JobTask, globalContext *sync.Map, ack func() error, logger *zap.SugaredLogger) *JobCtl { + return &JobCtl{ job: job, globalContext: globalContext, logger: logger, ack: ack, + Ctx: ctx, } } -func (c *jobCtl) Run(ctx context.Context) { - defer c.stop(ctx) - defer c.ack() +func (c *JobCtl) Run(ctx context.Context) { + ctx, cancel := context.WithCancel(ctx) + c.job.Status = config.StatusRunning + c.job.StartTime = time.Now().Unix() + c.logger.Infof("start job: %s,status: %s", c.job.Name, c.job.Status) + defer func() { + c.job.EndTime = time.Now().Unix() + c.logger.Infof("finish job: %s,status: %s", c.job.Name, c.job.Status) + c.stop(ctx) + c.ack() + cancel() + }() done := make(chan struct{}, 1) go func() { c.run(ctx) @@ -48,26 +59,33 @@ func (c *jobCtl) Run(ctx context.Context) { } } -func (c *jobCtl) run(ctx context.Context) { +func (c *JobCtl) run(ctx context.Context) { c.job.Status = config.StatusRunning c.ack() // TODO create pods for _, step := range c.job.Steps { - done := make(chan struct{}, 1) - go func(stepTask *stepcontroller.StepTask) { - stepTask.Run(ctx, c.globalContext, c.jobContext, c.ack, c.job.Namepace, c.job.PodName, c.logger) - done <- struct{}{} - }((*stepcontroller.StepTask)(step)) - select { - case <-done: - return - case <-ctx.Done(): - step.Status = config.StatusCancelled + (*stepcontroller.StepTask)(step).Run(ctx, c.globalContext, c.jobContext, c.ack, c.logger) + if step.Status != config.StatusPassed { + c.job.Status = step.Status return } + + // done := make(chan struct{}, 1) + // go func(stepTask *stepcontroller.StepTask) { + // stepTask.Run(ctx, c.globalContext, c.jobContext, c.ack, c.logger) + // done <- struct{}{} + // }((*stepcontroller.StepTask)(step)) + // select { + // case <-done: + // return + // case <-ctx.Done(): + // step.Status = config.StatusCancelled + // return + // } } + c.job.Status = config.StatusPassed } -func (c *jobCtl) stop(ctx context.Context) { +func (c *JobCtl) stop(ctx context.Context) { // TODO delete pods } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go similarity index 88% rename from pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go rename to pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go index 01735212..88fe8546 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go @@ -3,6 +3,7 @@ package stepcontroller import ( "context" "sync" + "time" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -35,6 +36,14 @@ func (s *StepTask) writeOutputToContext(globalContext, jobContext *sync.Map, out } func (s *StepTask) Run(ctx context.Context, globalContext, jobContext *sync.Map, ack func() error, logger *zap.SugaredLogger) { + s.Status = config.StatusRunning + s.StartTime = time.Now().Unix() + ack() + logger.Infof("start step: %s,status: %s", s.Name, s.Status) + defer func() { + s.EndTime = time.Now().Unix() + logger.Infof("finish step: %s,status: %s", s.Name, s.Status) + }() done := make(chan struct{}, 1) go func() { s.run(ctx, globalContext, jobContext, ack, logger) @@ -50,8 +59,6 @@ func (s *StepTask) Run(ctx context.Context, globalContext, jobContext *sync.Map, } func (s *StepTask) run(ctx context.Context, globalContext, jobContext *sync.Map, ack func() error, logger *zap.SugaredLogger) { - s.Status = config.StatusRunning - ack() s.renderSpec(globalContext, jobContext) var stepController Step switch s.StepType { @@ -60,6 +67,7 @@ func (s *StepTask) run(ctx context.Context, globalContext, jobContext *sync.Map, default: log.Errorf("no matched step type found: %s", s.StepType) } + stepController.run(ctx) output := stepController.outputVariables() s.writeOutputToContext(globalContext, jobContext, output) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go similarity index 89% rename from pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go rename to pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go index fd4bca99..31b4d016 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/stepcontroller/step_shell.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go @@ -3,6 +3,8 @@ package stepcontroller import ( "context" "encoding/json" + "math/rand" + "time" "github.com/koderover/zadig/pkg/microservice/aslan/config" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" @@ -62,4 +64,8 @@ func (s *shellStep) outputVariables() map[string]string { } -func (s *shellStep) run(ctx context.Context) {} +func (s *shellStep) run(ctx context.Context) { + r := rand.Intn(10) + time.Sleep(time.Duration(r) * time.Second) + s.stepTask.Status = config.StatusPassed +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go new file mode 100644 index 00000000..082f0023 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go @@ -0,0 +1 @@ +package workflowcontroller diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index edbfbd7c..c4ba64e5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -2,8 +2,11 @@ package workflowcontroller import ( "context" + "fmt" "sync" + "time" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller" "go.uber.org/zap" @@ -13,16 +16,14 @@ var approveChannelMap sync.Map var cancelChannelMap sync.Map type workflowCtl struct { - workflow *commonmodels.WorkflowV4 workflowTask *commonmodels.WorkflowTask globalContext *sync.Map logger *zap.SugaredLogger ack func() error } -func NewWorkflowController(workflow *commonmodels.WorkflowV4, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) *workflowCtl { +func NewWorkflowController(workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) *workflowCtl { ctl := &workflowCtl{ - workflow: workflow, workflowTask: workflowTask, logger: logger, } @@ -30,38 +31,161 @@ func NewWorkflowController(workflow *commonmodels.WorkflowV4, workflowTask *comm return ctl } -func (c *workflowCtl) Run(ctx context.Context) error { - defer c.ack() - defer updateworkflowStatus(c.workflowTask) +func CancelWorkflowTask(workflowName string, id int64) error { + value, ok := cancelChannelMap.Load(fmt.Sprintf("%s-%d", workflowName, id)) + if !ok { + return fmt.Errorf("no mactched task found, id: %d, workflow name: %s", id, workflowName) + } + if f, ok := value.(context.CancelFunc); ok { + f() + return nil + } + return fmt.Errorf("cancel func type mismatched, id: %d, workflow name: %s", id, workflowName) +} + +func (c *workflowCtl) Run(ctx context.Context, concurrency int) { + c.workflowTask.Status = config.StatusRunning + c.workflowTask.StartTime = time.Now().Unix() + c.ack() + c.logger.Infof("start workflow: %s,status: %s", c.workflowTask.WorkflowName, c.workflowTask.Status) + defer func() { + c.workflowTask.EndTime = time.Now().Unix() + c.logger.Infof("finish workflow: %s,status: %s", c.workflowTask.WorkflowName, c.workflowTask.Status) + c.ack() + }() ctx, cancel := context.WithCancel(ctx) defer cancel() - cancelChannelMap.Store(c.workflowTask.TaskID, cancel) + cancelChannelMap.Store(fmt.Sprintf("%s-%d", c.workflowTask.WorkflowName, c.workflowTask.TaskID), cancel) for _, stage := range c.workflowTask.Stages { - c.runStage(ctx, stage) + c.runStage(ctx, stage, concurrency) + if stage.Status != config.StatusPassed { + c.workflowTask.Status = stage.Status + return + } } - return nil + c.workflowTask.Status = config.StatusPassed } -func (c *workflowCtl) runStage(ctx context.Context, stage *commonmodels.StageTask) { - defer c.ack() - defer updateStageStatus(stage) +func (c *workflowCtl) runStage(ctx context.Context, stage *commonmodels.StageTask, concurrency int) { + stage.Status = config.StatusRunning + stage.StartTime = time.Now().Unix() + c.ack() + c.logger.Infof("start stage: %s,status: %s", stage.Name, stage.Status) + defer func() { + updateStageStatus(stage) + stage.EndTime = time.Now().Unix() + c.logger.Infof("finish stage: %s,status: %s", stage.Name, stage.Status) + c.ack() + }() switch stage.StageType { case "approve": //TODO wait for approve default: // TODO run job in parallel + var workerConcurrency = 1 + if stage.Parallel { + if len(stage.Jobs) > int(concurrency) { + workerConcurrency = int(concurrency) + } else { + workerConcurrency = len(stage.Jobs) + } + + } + jobCtls := []*jobcontroller.JobCtl{} for _, job := range stage.Jobs { - jobCtl := jobcontroller.NewJobCtl(job, c.globalContext, c.ack, c.logger) - jobCtl.Run(ctx) + jobCtl := jobcontroller.NewJobCtl(ctx, job, c.globalContext, c.ack, c.logger) + jobCtls = append(jobCtls, jobCtl) } + NewPool(jobCtls, workerConcurrency).Run() } } -func updateStageStatus(stage *commonmodels.StageTask) {} +func updateStageStatus(stage *commonmodels.StageTask) { + statusMap := map[config.Status]int{ + config.StatusCancelled: 4, + config.StatusTimeout: 3, + config.StatusFailed: 2, + config.StatusPassed: 1, + config.StatusSkipped: 0, + } + + // 初始化stageStatus为创建状态 + stageStatus := config.StatusRunning + + jobStatus := make([]int, len(stage.Jobs)) + + for i, j := range stage.Jobs { + statusCode, ok := statusMap[j.Status] + if !ok { + statusCode = -1 + } + jobStatus[i] = statusCode + } + var stageStatusCode int + for i, code := range jobStatus { + if i == 0 || code > stageStatusCode { + stageStatusCode = code + } + } + + for taskstatus, code := range statusMap { + if stageStatusCode == code { + stageStatus = taskstatus + break + } + } + stage.Status = stageStatus +} -func updateworkflowStatus(workflow *commonmodels.WorkflowTask) {} +func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { +} func (c *workflowCtl) updateWorkflowTask() error { // TODO update workflow task return nil } + +// Pool is a worker group that runs a number of tasks at a +// configured concurrency. +type Pool struct { + Jobs []*jobcontroller.JobCtl + concurrency int + jobsChan chan *jobcontroller.JobCtl + wg sync.WaitGroup +} + +// NewPool initializes a new pool with the given tasks and +// at the given concurrency. +func NewPool(jobs []*jobcontroller.JobCtl, concurrency int) *Pool { + return &Pool{ + Jobs: jobs, + concurrency: concurrency, + jobsChan: make(chan *jobcontroller.JobCtl), + } +} + +// Run runs all job within the pool and blocks until it's +// finished. +func (p *Pool) Run() { + for i := 0; i < p.concurrency; i++ { + go p.work() + } + + p.wg.Add(len(p.Jobs)) + for _, task := range p.Jobs { + p.jobsChan <- task + } + + // all workers return + close(p.jobsChan) + + p.wg.Wait() +} + +// The work loop for any single goroutine. +func (p *Pool) work() { + for job := range p.jobsChan { + job.Run(job.Ctx) + p.wg.Done() + } +} -- Gitee From 417d3ddb7f400b936b9c6d6499ad39c9972f6aba Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 23 May 2022 22:08:09 +0800 Subject: [PATCH 2404/2647] update scheduler model Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 61 +++++---- .../common/repository/models/workflow_v4.go | 8 +- .../workflow/service/jobcontroller/job.go | 127 ++++++++++-------- .../service/jobcontroller/job_freestyle.go | 36 +++++ .../workflow/service/stepcontroller/step.go | 74 ---------- .../service/stepcontroller/step_shell.go | 71 ---------- .../service/workflowcontroller/stage.go | 79 +++++++++++ .../workflowcontroller/stage_custom.go | 39 ++++++ .../service/workflowcontroller/workflow.go | 124 +++-------------- 9 files changed, 278 insertions(+), 341 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go delete mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go delete mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 677df3a1..dd79b66e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -23,27 +23,25 @@ import ( ) type WorkflowTask struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - WorkflowName string `bson:"workflow_name" json:"workflow_name"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - Stages []*StageTask `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - StorageURI string `bson:"storage_uri,omitempty" json:"storage_uri,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` - StorageEndpoint string `bson:"storage_endpoint" json:"storage_endpoint"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + Type config.PipelineType `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Stages []*StageTask `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` } func (WorkflowTask) TableName() string { @@ -56,7 +54,7 @@ type StageTask struct { StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` // default is custom defined by user. - StageType string `bson:"stage_type" json:"stage_type"` + StageType string `bson:"type" json:"type"` Parallel bool `bson:"parallel" json:"parallel"` Spec interface{} `bson:"spec" json:"spec"` Jobs []*JobTask `bson:"jobs" json:"jobs"` @@ -64,9 +62,11 @@ type StageTask struct { type JobTask struct { Name string `bson:"name" json:"name"` + JobType string `bson:"type" json:"type"` Status config.Status `bson:"status" json:"status"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Error string `bson:"error" json:"error"` Namepace string `bson:"namespace" json:"namespace"` PodName string `bson:"pod_name" json:"pod_name"` Properties *JobProperties `bson:"properties" json:"properties"` @@ -74,13 +74,12 @@ type JobTask struct { } type StepTask struct { - Name string `bson:"name" json:"name"` - JobName string `bson:"job_name" json:"job_name"` - Timeout int64 `bson:"timeout" json:"timeout"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - Status config.Status `bson:"status" json:"status"` - StepType config.StepType `bson:"step_type" json:"step_type"` - Spec interface{} `bson:"Spec" json:"Spec"` - Outputs []*StepOutput `bson:"outputs" json:"outputs"` + Name string `bson:"name" json:"name"` + JobName string `bson:"job_name" json:"job_name"` + Error string `bson:"error" json:"error"` + StepType config.StepType `bson:"step_type" json:"step_type"` + // step input params,differ form steps + Spec interface{} `bson:"Spec" json:"Spec"` + // step output results,like testing results,differ form steps + Result interface{} `bson:"result" json:"result"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 2ca0f40e..7d9297bb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -21,7 +21,7 @@ type WorkflowV4 struct { type WorkflowStage struct { Name string `bson:"name" json:"name"` // default is custom defined by user. - StageType string `bson:"stage_type" json:"stage_type"` + StageType string `bson:"type" json:"type"` Spec interface{} `bson:"spec" json:"spec"` Jobs []*Job `bson:"jobs" json:"jobs"` } @@ -29,7 +29,10 @@ type WorkflowStage struct { type Job struct { Name string `bson:"name" json:"name"` Properties *JobProperties `bson:"properties" json:"properties"` + JobType string `bson:"type" json:"type"` + Spec interface{} `bson:"spec" json:"spec"` Steps []*Step `bson:"steps" json:"steps"` + Outputs []*Output `bson:"outputs" son:"outputs"` } type JobProperties struct { @@ -45,10 +48,9 @@ type Step struct { Timeout int64 `bson:"timeout" json:"timeout"` StepType config.StepType `bson:"step_type" json:"step_type"` Spec interface{} `bson:"Spec" json:"Spec"` - Outputs []*StepOutput `bson:"outputs" json:"outputs"` } -type StepOutput struct { +type Output struct { Name string `bson:"name" json:"name"` Description string `bson:"description" json:"description"` } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index 76d83609..8d79569c 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -7,85 +7,102 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/stepcontroller" "go.uber.org/zap" ) -type JobCtl struct { - job *commonmodels.JobTask - jobContext *sync.Map - globalContext *sync.Map - logger *zap.SugaredLogger - Ctx context.Context - ack func() error +type JobCtl interface { + Run(ctx context.Context) } -func NewJobCtl(ctx context.Context, job *commonmodels.JobTask, globalContext *sync.Map, ack func() error, logger *zap.SugaredLogger) *JobCtl { - return &JobCtl{ - job: job, - globalContext: globalContext, - logger: logger, - ack: ack, - Ctx: ctx, - } -} - -func (c *JobCtl) Run(ctx context.Context) { +func runJob(ctx context.Context, job *commonmodels.JobTask, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { ctx, cancel := context.WithCancel(ctx) - c.job.Status = config.StatusRunning - c.job.StartTime = time.Now().Unix() - c.logger.Infof("start job: %s,status: %s", c.job.Name, c.job.Status) + job.Status = config.StatusRunning + job.StartTime = time.Now().Unix() + logger.Infof("start job: %s,status: %s", job.Name, job.Status) defer func() { - c.job.EndTime = time.Now().Unix() - c.logger.Infof("finish job: %s,status: %s", c.job.Name, c.job.Status) - c.stop(ctx) - c.ack() + job.EndTime = time.Now().Unix() + logger.Infof("finish job: %s,status: %s", job.Name, job.Status) + ack() cancel() }() + var jobCtl JobCtl + switch job.JobType { + case "deploy": + // TODO + default: + jobCtl = NewFreestyleJobCtl(job, globalContext, ack, logger) + } done := make(chan struct{}, 1) go func() { - c.run(ctx) + jobCtl.Run(ctx) done <- struct{}{} }() select { case <-done: return case <-ctx.Done(): - c.job.Status = config.StatusCancelled + job.Status = config.StatusCancelled return - case <-time.After(time.Minute * time.Duration(c.job.Properties.Timeout)): - c.job.Status = config.StatusTimeout + case <-time.After(time.Second * time.Duration(job.Properties.Timeout)): + job.Status = config.StatusTimeout return } } -func (c *JobCtl) run(ctx context.Context) { - c.job.Status = config.StatusRunning - c.ack() - // TODO create pods - for _, step := range c.job.Steps { - (*stepcontroller.StepTask)(step).Run(ctx, c.globalContext, c.jobContext, c.ack, c.logger) - if step.Status != config.StatusPassed { - c.job.Status = step.Status - return - } +func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { + jobPool := NewPool(ctx, jobs, concurrency, globalContext, logger, ack) + jobPool.Run() +} - // done := make(chan struct{}, 1) - // go func(stepTask *stepcontroller.StepTask) { - // stepTask.Run(ctx, c.globalContext, c.jobContext, c.ack, c.logger) - // done <- struct{}{} - // }((*stepcontroller.StepTask)(step)) - // select { - // case <-done: - // return - // case <-ctx.Done(): - // step.Status = config.StatusCancelled - // return - // } +// Pool is a worker group that runs a number of tasks at a +// configured concurrency. +type Pool struct { + Jobs []*commonmodels.JobTask + concurrency int + jobsChan chan *commonmodels.JobTask + globalContext *sync.Map + logger *zap.SugaredLogger + ack func() + ctx context.Context + wg sync.WaitGroup +} + +// NewPool initializes a new pool with the given tasks and +// at the given concurrency. +func NewPool(ctx context.Context, jobs []*commonmodels.JobTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *Pool { + return &Pool{ + Jobs: jobs, + concurrency: concurrency, + jobsChan: make(chan *commonmodels.JobTask), + globalContext: globalContext, + logger: logger, + ack: ack, + ctx: ctx, + } +} + +// Run runs all job within the pool and blocks until it's +// finished. +func (p *Pool) Run() { + for i := 0; i < p.concurrency; i++ { + go p.work() + } + + p.wg.Add(len(p.Jobs)) + for _, task := range p.Jobs { + p.jobsChan <- task } - c.job.Status = config.StatusPassed + + // all workers return + close(p.jobsChan) + + p.wg.Wait() } -func (c *JobCtl) stop(ctx context.Context) { - // TODO delete pods +// The work loop for any single goroutine. +func (p *Pool) work() { + for job := range p.jobsChan { + runJob(p.ctx, job, p.globalContext, p.logger, p.ack) + p.wg.Done() + } } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go new file mode 100644 index 00000000..a07e21ba --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -0,0 +1,36 @@ +package jobcontroller + +import ( + "context" + "math/rand" + "sync" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "go.uber.org/zap" +) + +type FreestyleJobCtl struct { + job *commonmodels.JobTask + jobContext *sync.Map + globalContext *sync.Map + logger *zap.SugaredLogger + ack func() +} + +func NewFreestyleJobCtl(job *commonmodels.JobTask, globalContext *sync.Map, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { + return &FreestyleJobCtl{ + job: job, + globalContext: globalContext, + logger: logger, + ack: ack, + } +} + +func (c *FreestyleJobCtl) Run(ctx context.Context) { + r := rand.Intn(10) + time.Sleep(time.Duration(r) * time.Second) + c.logger.Infof("job: %s sleep %d seccod", c.job.Name, r) + c.job.Status = config.StatusPassed +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go deleted file mode 100644 index 88fe8546..00000000 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go +++ /dev/null @@ -1,74 +0,0 @@ -package stepcontroller - -import ( - "context" - "sync" - "time" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/tool/log" - "go.uber.org/zap" -) - -type Step interface { - // Type ... - stepType() config.StepType - - // Status ... - status() config.Status - - // outputVariables - outputVariables() map[string]string - - // Run ... - run(ctx context.Context) -} - -type StepTask commonmodels.StepTask - -// render context key value to step spec -func (s *StepTask) renderSpec(globalContext, jobContext *sync.Map) { -} - -// render context key value to step spec -func (s *StepTask) writeOutputToContext(globalContext, jobContext *sync.Map, outputMap map[string]string) { -} - -func (s *StepTask) Run(ctx context.Context, globalContext, jobContext *sync.Map, ack func() error, logger *zap.SugaredLogger) { - s.Status = config.StatusRunning - s.StartTime = time.Now().Unix() - ack() - logger.Infof("start step: %s,status: %s", s.Name, s.Status) - defer func() { - s.EndTime = time.Now().Unix() - logger.Infof("finish step: %s,status: %s", s.Name, s.Status) - }() - done := make(chan struct{}, 1) - go func() { - s.run(ctx, globalContext, jobContext, ack, logger) - done <- struct{}{} - }() - select { - case <-done: - return - case <-ctx.Done(): - s.Status = config.StatusCancelled - return - } -} - -func (s *StepTask) run(ctx context.Context, globalContext, jobContext *sync.Map, ack func() error, logger *zap.SugaredLogger) { - s.renderSpec(globalContext, jobContext) - var stepController Step - switch s.StepType { - case config.StepShell: - stepController = NewShellStepController(s, ack, logger) - default: - log.Errorf("no matched step type found: %s", s.StepType) - } - - stepController.run(ctx) - output := stepController.outputVariables() - s.writeOutputToContext(globalContext, jobContext, output) -} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go deleted file mode 100644 index 31b4d016..00000000 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go +++ /dev/null @@ -1,71 +0,0 @@ -package stepcontroller - -import ( - "context" - "encoding/json" - "math/rand" - "time" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" - "go.uber.org/zap" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type shellStep struct { - stepTask *StepTask - stepSpec *ShellSpec - kubeClient client.Client - clientset kubernetes.Interface - restConfig *rest.Config - logger *zap.SugaredLogger - - ack func() error -} - -type ShellSpec struct { - Script string `bson:"script" yaml:"script"` -} - -func NewShellStepController(stepTask *StepTask, ack func() error, logger *zap.SugaredLogger) *shellStep { - shellStep := &shellStep{ - stepTask: stepTask, - kubeClient: krkubeclient.Client(), - clientset: krkubeclient.Clientset(), - restConfig: krkubeclient.RESTConfig(), - ack: ack, - logger: logger, - } - b, err := json.Marshal(stepTask.Spec) - if err != nil { - logger.Errorf("marshal shell spec error: %s", err) - return shellStep - } - shellSpec := ShellSpec{} - if err := json.Unmarshal(b, &shellSpec); err != nil { - logger.Errorf("unmarshal shell spec error: %s", err) - } - shellStep.stepSpec = &shellSpec - return shellStep -} - -func (s *shellStep) stepType() config.StepType { - return config.StepShell -} - -func (s *shellStep) status() config.Status { - return s.stepTask.Status -} - -func (s *shellStep) outputVariables() map[string]string { - return map[string]string{} - -} - -func (s *shellStep) run(ctx context.Context) { - r := rand.Intn(10) - time.Sleep(time.Duration(r) * time.Second) - s.stepTask.Status = config.StatusPassed -} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go new file mode 100644 index 00000000..1b180a7e --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -0,0 +1,79 @@ +package workflowcontroller + +import ( + "context" + "sync" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "go.uber.org/zap" +) + +type StageCtl interface { + Run(ctx context.Context, concurrency int) +} + +func runStage(ctx context.Context, stage *commonmodels.StageTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { + stage.Status = config.StatusRunning + stage.StartTime = time.Now().Unix() + ack() + logger.Infof("start stage: %s,status: %s", stage.Name, stage.Status) + defer func() { + updateStageStatus(stage) + stage.EndTime = time.Now().Unix() + logger.Infof("finish stage: %s,status: %s", stage.Name, stage.Status) + ack() + }() + var stageCtl StageCtl + switch stage.StageType { + case "approve": + // TODO approval stage + default: + stageCtl = NewCustomStageCtl(stage, globalContext, logger, ack) + } + stageCtl.Run(ctx, concurrency) +} + +func RunStages(ctx context.Context, stages []*commonmodels.StageTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { + for _, stage := range stages { + runStage(ctx, stage, concurrency, globalContext, logger, ack) + } +} + +func updateStageStatus(stage *commonmodels.StageTask) { + statusMap := map[config.Status]int{ + config.StatusCancelled: 4, + config.StatusTimeout: 3, + config.StatusFailed: 2, + config.StatusPassed: 1, + config.StatusSkipped: 0, + } + + // 初始化stageStatus为创建状态 + stageStatus := config.StatusRunning + + jobStatus := make([]int, len(stage.Jobs)) + + for i, j := range stage.Jobs { + statusCode, ok := statusMap[j.Status] + if !ok { + statusCode = -1 + } + jobStatus[i] = statusCode + } + var stageStatusCode int + for i, code := range jobStatus { + if i == 0 || code > stageStatusCode { + stageStatusCode = code + } + } + + for taskstatus, code := range statusMap { + if stageStatusCode == code { + stageStatus = taskstatus + break + } + } + stage.Status = stageStatus +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go new file mode 100644 index 00000000..02d8b06c --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go @@ -0,0 +1,39 @@ +package workflowcontroller + +import ( + "context" + "sync" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller" + "go.uber.org/zap" +) + +type CustomStageCtl struct { + stage *commonmodels.StageTask + globalContext *sync.Map + logger *zap.SugaredLogger + ack func() +} + +func NewCustomStageCtl(stage *commonmodels.StageTask, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *CustomStageCtl { + return &CustomStageCtl{ + stage: stage, + globalContext: globalContext, + logger: logger, + ack: ack, + } +} + +func (c *CustomStageCtl) Run(ctx context.Context, concurrency int) { + var workerConcurrency = 1 + if c.stage.Parallel { + if len(c.stage.Jobs) > int(concurrency) { + workerConcurrency = int(concurrency) + } else { + workerConcurrency = len(c.stage.Jobs) + } + + } + jobcontroller.RunJobs(ctx, c.stage.Jobs, workerConcurrency, c.globalContext, c.logger, c.ack) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index c4ba64e5..3f8d985c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -8,7 +8,6 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller" "go.uber.org/zap" ) @@ -19,7 +18,7 @@ type workflowCtl struct { workflowTask *commonmodels.WorkflowTask globalContext *sync.Map logger *zap.SugaredLogger - ack func() error + ack func() } func NewWorkflowController(workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) *workflowCtl { @@ -56,51 +55,11 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { ctx, cancel := context.WithCancel(ctx) defer cancel() cancelChannelMap.Store(fmt.Sprintf("%s-%d", c.workflowTask.WorkflowName, c.workflowTask.TaskID), cancel) - for _, stage := range c.workflowTask.Stages { - c.runStage(ctx, stage, concurrency) - if stage.Status != config.StatusPassed { - c.workflowTask.Status = stage.Status - return - } - } - c.workflowTask.Status = config.StatusPassed -} - -func (c *workflowCtl) runStage(ctx context.Context, stage *commonmodels.StageTask, concurrency int) { - stage.Status = config.StatusRunning - stage.StartTime = time.Now().Unix() - c.ack() - c.logger.Infof("start stage: %s,status: %s", stage.Name, stage.Status) - defer func() { - updateStageStatus(stage) - stage.EndTime = time.Now().Unix() - c.logger.Infof("finish stage: %s,status: %s", stage.Name, stage.Status) - c.ack() - }() - switch stage.StageType { - case "approve": - //TODO wait for approve - default: - // TODO run job in parallel - var workerConcurrency = 1 - if stage.Parallel { - if len(stage.Jobs) > int(concurrency) { - workerConcurrency = int(concurrency) - } else { - workerConcurrency = len(stage.Jobs) - } - - } - jobCtls := []*jobcontroller.JobCtl{} - for _, job := range stage.Jobs { - jobCtl := jobcontroller.NewJobCtl(ctx, job, c.globalContext, c.ack, c.logger) - jobCtls = append(jobCtls, jobCtl) - } - NewPool(jobCtls, workerConcurrency).Run() - } + RunStages(ctx, c.workflowTask.Stages, concurrency, c.globalContext, c.logger, c.ack) + updateworkflowStatus(c.workflowTask) } -func updateStageStatus(stage *commonmodels.StageTask) { +func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { statusMap := map[config.Status]int{ config.StatusCancelled: 4, config.StatusTimeout: 3, @@ -109,83 +68,34 @@ func updateStageStatus(stage *commonmodels.StageTask) { config.StatusSkipped: 0, } - // 初始化stageStatus为创建状态 - stageStatus := config.StatusRunning + // 初始化workflowStatus为创建状态 + workflowStatus := config.StatusRunning - jobStatus := make([]int, len(stage.Jobs)) + stageStatus := make([]int, len(workflow.Stages)) - for i, j := range stage.Jobs { + for i, j := range workflow.Stages { statusCode, ok := statusMap[j.Status] if !ok { statusCode = -1 } - jobStatus[i] = statusCode + stageStatus[i] = statusCode } - var stageStatusCode int - for i, code := range jobStatus { - if i == 0 || code > stageStatusCode { - stageStatusCode = code + var workflowStatusCode int + for i, code := range stageStatus { + if i == 0 || code > workflowStatusCode { + workflowStatusCode = code } } for taskstatus, code := range statusMap { - if stageStatusCode == code { - stageStatus = taskstatus + if workflowStatusCode == code { + workflowStatus = taskstatus break } } - stage.Status = stageStatus -} - -func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { + workflow.Status = workflowStatus } -func (c *workflowCtl) updateWorkflowTask() error { +func (c *workflowCtl) updateWorkflowTask() { // TODO update workflow task - return nil -} - -// Pool is a worker group that runs a number of tasks at a -// configured concurrency. -type Pool struct { - Jobs []*jobcontroller.JobCtl - concurrency int - jobsChan chan *jobcontroller.JobCtl - wg sync.WaitGroup -} - -// NewPool initializes a new pool with the given tasks and -// at the given concurrency. -func NewPool(jobs []*jobcontroller.JobCtl, concurrency int) *Pool { - return &Pool{ - Jobs: jobs, - concurrency: concurrency, - jobsChan: make(chan *jobcontroller.JobCtl), - } -} - -// Run runs all job within the pool and blocks until it's -// finished. -func (p *Pool) Run() { - for i := 0; i < p.concurrency; i++ { - go p.work() - } - - p.wg.Add(len(p.Jobs)) - for _, task := range p.Jobs { - p.jobsChan <- task - } - - // all workers return - close(p.jobsChan) - - p.wg.Wait() -} - -// The work loop for any single goroutine. -func (p *Pool) work() { - for job := range p.jobsChan { - job.Run(job.Ctx) - p.wg.Done() - } } -- Gitee From 06a325fbf29574bf56a5ce0a957dbec67a7046b4 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 24 May 2022 11:07:40 +0800 Subject: [PATCH 2405/2647] fail stage if failed Signed-off-by: guoyu --- .../core/workflow/service/workflowcontroller/stage.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 1b180a7e..50d7fd52 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -38,7 +38,17 @@ func runStage(ctx context.Context, stage *commonmodels.StageTask, concurrency in func RunStages(ctx context.Context, stages []*commonmodels.StageTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { for _, stage := range stages { runStage(ctx, stage, concurrency, globalContext, logger, ack) + if statusFailed(stage.Status) { + return + } + } +} + +func statusFailed(status config.Status) bool { + if status == config.StatusCancelled || status == config.StatusFailed || status == config.StatusTimeout { + return true } + return false } func updateStageStatus(stage *commonmodels.StageTask) { -- Gitee From a9f13aedd3f86d9787f4abf24c437e616ad1211c Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 27 May 2022 23:14:43 +0800 Subject: [PATCH 2406/2647] add job excutor Signed-off-by: guoyu --- cmd/jobexcutor/main.go | 22 ++++ pkg/microservice/jobexcutor/config/config.go | 24 ++++ .../jobexcutor/core/service/job.go | 112 ++++++++++++++++++ .../jobexcutor/core/service/meta/types.go | 41 +++++++ .../jobexcutor/core/service/step/step.go | 81 +++++++++++++ .../core/service/step/step_shell.go | 88 ++++++++++++++ .../jobexcutor/excutor/excutor.go | 64 ++++++++++ pkg/types/step/step.go | 5 + 8 files changed, 437 insertions(+) create mode 100644 cmd/jobexcutor/main.go create mode 100644 pkg/microservice/jobexcutor/config/config.go create mode 100644 pkg/microservice/jobexcutor/core/service/job.go create mode 100644 pkg/microservice/jobexcutor/core/service/meta/types.go create mode 100644 pkg/microservice/jobexcutor/core/service/step/step.go create mode 100644 pkg/microservice/jobexcutor/core/service/step/step_shell.go create mode 100644 pkg/microservice/jobexcutor/excutor/excutor.go create mode 100644 pkg/types/step/step.go diff --git a/cmd/jobexcutor/main.go b/cmd/jobexcutor/main.go new file mode 100644 index 00000000..6f6e7928 --- /dev/null +++ b/cmd/jobexcutor/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "context" + "log" + "os/signal" + "syscall" + + "github.com/koderover/zadig/pkg/microservice/jobexcutor/excutor" +) + +func main() { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + go func() { + <-ctx.Done() + stop() + }() + + if err := excutor.Execute(ctx); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/microservice/jobexcutor/config/config.go b/pkg/microservice/jobexcutor/config/config.go new file mode 100644 index 00000000..faa54563 --- /dev/null +++ b/pkg/microservice/jobexcutor/config/config.go @@ -0,0 +1,24 @@ +package config + +import ( + "github.com/koderover/zadig/pkg/setting" + "github.com/spf13/viper" + + _ "github.com/koderover/zadig/pkg/config" +) + +func JobConfigFile() string { + return viper.GetString(setting.JobConfigFile) +} + +func Path() string { + return viper.GetString(setting.Path) +} + +func DockerHost() string { + return viper.GetString(setting.DockerHost) +} + +func Home() string { + return viper.GetString(setting.Home) +} diff --git a/pkg/microservice/jobexcutor/core/service/job.go b/pkg/microservice/jobexcutor/core/service/job.go new file mode 100644 index 00000000..5ab73a50 --- /dev/null +++ b/pkg/microservice/jobexcutor/core/service/job.go @@ -0,0 +1,112 @@ +package job + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + "github.com/koderover/zadig/pkg/microservice/jobexcutor/config" + "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service/meta" + "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service/step" + "gopkg.in/yaml.v3" +) + +type Job struct { + Ctx *meta.JobContext + StartTime time.Time + ActiveWorkspace string + UserEnvs map[string]string +} + +func NewJob() (*Job, error) { + context, err := ioutil.ReadFile(config.JobConfigFile()) + if err != nil { + return nil, fmt.Errorf("read job config file error: %v", err) + } + + var ctx *meta.JobContext + if err := yaml.Unmarshal(context, &ctx); err != nil { + return nil, fmt.Errorf("cannot unmarshal job data: %v", err) + } + + ctx.Paths = config.Path() + + job := &Job{ + Ctx: ctx, + } + + err = job.EnsureActiveWorkspace(ctx.Workspace) + if err != nil { + return nil, fmt.Errorf("failed to ensure active workspace `%s`: %s", ctx.Workspace, err) + } + + userEnvs := job.getUserEnvs() + job.UserEnvs = make(map[string]string, len(userEnvs)) + for _, env := range userEnvs { + items := strings.Split(env, "=") + if len(items) != 2 { + continue + } + + job.UserEnvs[items[0]] = items[1] + } + + return job, nil +} + +func (j *Job) EnsureActiveWorkspace(workspace string) error { + if workspace == "" { + tempWorkspace, err := ioutil.TempDir(os.TempDir(), "reaper") + if err != nil { + return fmt.Errorf("create workspace error: %v", err) + } + j.ActiveWorkspace = tempWorkspace + return os.Chdir(j.ActiveWorkspace) + } + + err := os.MkdirAll(workspace, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to create workspace: %v", err) + } + j.ActiveWorkspace = workspace + + return os.Chdir(j.ActiveWorkspace) +} + +func (j *Job) getUserEnvs() []string { + envs := []string{ + "CI=true", + "ZADIG=true", + fmt.Sprintf("HOME=%s", config.Home()), + fmt.Sprintf("WORKSPACE=%s", j.ActiveWorkspace), + } + + j.Ctx.Paths = strings.Replace(j.Ctx.Paths, "$HOME", config.Home(), -1) + envs = append(envs, fmt.Sprintf("PATH=%s", j.Ctx.Paths)) + envs = append(envs, fmt.Sprintf("DOCKER_HOST=%s", config.DockerHost())) + envs = append(envs, j.Ctx.Envs...) + envs = append(envs, j.Ctx.SecretEnvs...) + + return envs +} + +func (j *Job) Run(ctx context.Context) error { + for _, stepInfo := range j.Ctx.Steps { + var stepInstance step.Step + var err error + switch stepInfo.StepType { + case "shell": + stepInstance, err = step.NewShellStep(ctx, j.ActiveWorkspace, j.getUserEnvs(), j.Ctx.SecretEnvs) + if err != nil { + return err + } + } + if err := stepInstance.Run(ctx); err != nil { + return err + } + } + return nil +} diff --git a/pkg/microservice/jobexcutor/core/service/meta/types.go b/pkg/microservice/jobexcutor/core/service/meta/types.go new file mode 100644 index 00000000..a7c0c72f --- /dev/null +++ b/pkg/microservice/jobexcutor/core/service/meta/types.go @@ -0,0 +1,41 @@ +package meta + +type JobContext struct { + Name string `yaml:"name"` + // Workspace 容器工作目录 [必填] + Workspace string `yaml:"workspace"` + Proxy *Proxy `yaml:"proxy"` + // Envs 用户注入环境变量, 包括安装脚本环境变量 [optional] + Envs EnvVar `yaml:"envs"` + // SecretEnvs 用户注入敏感信息环境变量, value不能在stdout stderr中输出 [optional] + SecretEnvs EnvVar `yaml:"secret_envs"` + // WorkflowName + WorkflowName string `yaml:"workflow_name"` + // TaskID + TaskID int64 `yaml:"task_id"` + // Paths 执行脚本Path + Paths string `yaml:"-"` + + Steps []*Step `yaml:"steps"` + Outputs []string `yaml:"outputs"` +} + +type Step struct { + Name string `yaml:"name"` + StepType string `yaml:"type"` + Spec interface{} `yaml:"spec"` +} + +type EnvVar []string + +// Proxy 翻墙配置信息 +type Proxy struct { + Type string `yaml:"type"` + Address string `yaml:"address"` + Port int `yaml:"port"` + NeedPassword bool `yaml:"need_password"` + Username string `yaml:"username"` + Password string `yaml:"password"` + EnableRepoProxy bool `yaml:"enable_repo_proxy"` + EnableApplicationProxy bool `yaml:"enable_application_proxy"` +} diff --git a/pkg/microservice/jobexcutor/core/service/step/step.go b/pkg/microservice/jobexcutor/core/service/step/step.go new file mode 100644 index 00000000..bd097cc5 --- /dev/null +++ b/pkg/microservice/jobexcutor/core/service/step/step.go @@ -0,0 +1,81 @@ +package step + +import ( + "bufio" + "context" + "fmt" + "io" + "strings" + + "github.com/koderover/zadig/pkg/microservice/jobexcutor/config" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/util" +) + +type Step interface { + Run(ctx context.Context) error +} + +func prepareScriptsEnv() []string { + scripts := []string{} + scripts = append(scripts, "eval $(ssh-agent -s) > /dev/null") + // $HOME/.ssh/id_rsa 为 github 私钥 + scripts = append(scripts, fmt.Sprintf("ssh-add %s/.ssh/id_rsa.github &> /dev/null", config.Home())) + scripts = append(scripts, fmt.Sprintf("rm %s/.ssh/id_rsa.github &> /dev/null", config.Home())) + // $HOME/.ssh/gitlab 为 gitlab 私钥 + scripts = append(scripts, fmt.Sprintf("ssh-add %s/.ssh/id_rsa.gitlab &> /dev/null", config.Home())) + scripts = append(scripts, fmt.Sprintf("rm %s/.ssh/id_rsa.gitlab &> /dev/null", config.Home())) + + return scripts +} + +func handleCmdOutput(pipe io.ReadCloser, needPersistentLog bool, logFile string, secretEnvs []string) { + reader := bufio.NewReader(pipe) + + for { + lineBytes, err := reader.ReadBytes('\n') + if err != nil { + if err == io.EOF { + break + } + + log.Errorf("Failed to read log when processing cmd output: %s", err) + break + } + + fmt.Printf("%s", maskSecretEnvs(string(lineBytes), secretEnvs)) + + if needPersistentLog { + err := util.WriteFile(logFile, lineBytes, 0700) + if err != nil { + log.Warnf("Failed to write file when processing cmd output: %s", err) + } + } + } +} + +const ( + secretEnvMask = "********" +) + +func maskSecretEnvs(message string, secretEnvs []string) string { + out := message + + for _, val := range secretEnvs { + if len(val) == 0 { + continue + } + sl := strings.Split(val, "=") + + if len(sl) != 2 { + continue + } + + if len(sl[0]) == 0 || len(sl[1]) == 0 { + // invalid key value pair received + continue + } + out = strings.Replace(out, strings.Join(sl[1:], "="), secretEnvMask, -1) + } + return out +} diff --git a/pkg/microservice/jobexcutor/core/service/step/step_shell.go b/pkg/microservice/jobexcutor/core/service/step/step_shell.go new file mode 100644 index 00000000..f19bf80a --- /dev/null +++ b/pkg/microservice/jobexcutor/core/service/step/step_shell.go @@ -0,0 +1,88 @@ +package step + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + + "github.com/koderover/zadig/pkg/types/step" + "github.com/koderover/zadig/pkg/util" +) + +type ShellStep struct { + spec *step.StepShellSpec + envs []string + secretEnvs []string + workspace string +} + +func NewShellStep(spec interface{}, workspace string, envs, secretEnvs []string) (*ShellStep, error) { + specImpl, ok := (spec).(*step.StepShellSpec) + if !ok { + return nil, fmt.Errorf("convert %+v to shell spec failed", spec) + } + return &ShellStep{spec: specImpl, workspace: workspace, envs: envs, secretEnvs: secretEnvs}, nil +} + +func (s *ShellStep) Run(ctx context.Context) error { + if len(s.spec.Scripts) == 0 { + return nil + } + scripts := prepareScriptsEnv() + + scripts = append(scripts, s.spec.Scripts...) + + userScriptFile := "user_script.sh" + if err := ioutil.WriteFile(filepath.Join(os.TempDir(), userScriptFile), []byte(strings.Join(scripts, "\n")), 0700); err != nil { + return fmt.Errorf("write script file error: %v", err) + } + + cmd := exec.Command("/bin/bash", filepath.Join(os.TempDir(), userScriptFile)) + cmd.Dir = s.workspace + cmd.Env = s.envs + + fileName := filepath.Join(os.TempDir(), "user_script.log") + //如果文件不存在就创建文件,避免后面使用变量出错 + util.WriteFile(fileName, []byte{}, 0700) + + needPersistentLog := true + + var wg sync.WaitGroup + + cmdStdoutReader, err := cmd.StdoutPipe() + if err != nil { + return err + } + + wg.Add(1) + go func() { + defer wg.Done() + + handleCmdOutput(cmdStdoutReader, needPersistentLog, fileName, s.secretEnvs) + }() + + cmdStdErrReader, err := cmd.StderrPipe() + if err != nil { + return err + } + + wg.Add(1) + go func() { + defer wg.Done() + + handleCmdOutput(cmdStdErrReader, needPersistentLog, fileName, s.secretEnvs) + }() + + if err := cmd.Start(); err != nil { + return err + } + + wg.Wait() + + return cmd.Wait() +} diff --git a/pkg/microservice/jobexcutor/excutor/excutor.go b/pkg/microservice/jobexcutor/excutor/excutor.go new file mode 100644 index 00000000..ef599cfc --- /dev/null +++ b/pkg/microservice/jobexcutor/excutor/excutor.go @@ -0,0 +1,64 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package excutor + +import ( + "context" + "time" + + commonconfig "github.com/koderover/zadig/pkg/config" + job "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" +) + +func Execute(ctx context.Context) error { + log.Init(&log.Config{ + Level: commonconfig.LogLevel(), + NoCaller: true, + NoLogLevel: true, + Development: commonconfig.Mode() != setting.ReleaseMode, + }) + + start := time.Now() + + var err error + var reaperType types.ReaperType + defer func() { + resultMsg := types.JobSuccess + if err != nil { + resultMsg = types.JobFail + log.Errorf("Failed to run: %s.", err) + } + log.Infof("Job Status: %s", resultMsg) + + log.Infof("====================== %s End. Duration: %.2f seconds ======================", reaperType, time.Since(start).Seconds()) + }() + + var j *job.Job + j, err = job.NewJob() + if err != nil { + return err + } + + log.Infof("====================== %s Start ======================", reaperType) + if err := j.Run(ctx); err != nil { + return err + } + return nil +} diff --git a/pkg/types/step/step.go b/pkg/types/step/step.go new file mode 100644 index 00000000..d6d947a9 --- /dev/null +++ b/pkg/types/step/step.go @@ -0,0 +1,5 @@ +package step + +type StepShellSpec struct { + Scripts []string `yaml:"scripts"` +} -- Gitee From 3e1351dd69c1563a3d5da238b860cca8f610cfb0 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 31 May 2022 11:07:04 +0800 Subject: [PATCH 2407/2647] add workflow excutor detail Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 10 + .../common/repository/models/workflow_v4.go | 12 +- .../workflow/service/jobcontroller/job.go | 14 +- .../service/jobcontroller/job_excutor.go | 27 ++ .../service/jobcontroller/job_freestyle.go | 113 ++++++- .../service/jobcontroller/kubernetes.go | 296 ++++++++++++++++++ .../workflow/service/jobcontroller/types.go | 39 +++ .../service/workflowcontroller/stage.go | 8 +- .../workflowcontroller/stage_custom.go | 6 +- .../service/workflowcontroller/workflow.go | 12 +- 10 files changed, 518 insertions(+), 19 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go create mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index dd79b66e..fb7ef760 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -83,3 +83,13 @@ type StepTask struct { // step output results,like testing results,differ form steps Result interface{} `bson:"result" json:"result"` } + +type WorkflowTaskCtx struct { + WorkflowName string + TaskID int64 + DockerHost string + Workspace string + DistDir string + DockerMountDir string + ConfigMapMountDir string +} diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 7d9297bb..f1952a3e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -2,6 +2,7 @@ package models import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/setting" "go.mongodb.org/mongo-driver/bson/primitive" ) @@ -36,11 +37,12 @@ type Job struct { } type JobProperties struct { - Timeout int64 `bson:"timeout" json:"timeout"` - Retry int64 `bson:"retry" json:"retry"` - ResourceRequest string `bson:"resource_request" json:"resource_request"` - ClusterID string `bson:"cluster_id" json:"cluster_id"` - Args []*KeyVal `bson:"args" json:"args"` + Timeout int64 `bson:"timeout" json:"timeout"` + Retry int64 `bson:"retry" json:"retry"` + ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + BuildOS string `bson:"build_os" json:"build_os"` + Args []*KeyVal `bson:"args" json:"args"` } type Step struct { diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index 8d79569c..25286874 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -14,7 +14,7 @@ type JobCtl interface { Run(ctx context.Context) } -func runJob(ctx context.Context, job *commonmodels.JobTask, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { +func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { ctx, cancel := context.WithCancel(ctx) job.Status = config.StatusRunning job.StartTime = time.Now().Unix() @@ -30,7 +30,7 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, globalContext *sync. case "deploy": // TODO default: - jobCtl = NewFreestyleJobCtl(job, globalContext, ack, logger) + jobCtl = NewFreestyleJobCtl(job, workflowCtx, globalContext, ack, logger) } done := make(chan struct{}, 1) go func() { @@ -49,8 +49,8 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, globalContext *sync. } } -func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { - jobPool := NewPool(ctx, jobs, concurrency, globalContext, logger, ack) +func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { + jobPool := NewPool(ctx, jobs, workflowCtx, concurrency, globalContext, logger, ack) jobPool.Run() } @@ -58,6 +58,7 @@ func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, concurrency int, // configured concurrency. type Pool struct { Jobs []*commonmodels.JobTask + workflowCtx *commonmodels.WorkflowTaskCtx concurrency int jobsChan chan *commonmodels.JobTask globalContext *sync.Map @@ -69,10 +70,11 @@ type Pool struct { // NewPool initializes a new pool with the given tasks and // at the given concurrency. -func NewPool(ctx context.Context, jobs []*commonmodels.JobTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *Pool { +func NewPool(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *Pool { return &Pool{ Jobs: jobs, concurrency: concurrency, + workflowCtx: workflowCtx, jobsChan: make(chan *commonmodels.JobTask), globalContext: globalContext, logger: logger, @@ -102,7 +104,7 @@ func (p *Pool) Run() { // The work loop for any single goroutine. func (p *Pool) work() { for job := range p.jobsChan { - runJob(p.ctx, job, p.globalContext, p.logger, p.ack) + runJob(p.ctx, job, p.workflowCtx, p.globalContext, p.logger, p.ack) p.wg.Done() } } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go new file mode 100644 index 00000000..e4daed27 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go @@ -0,0 +1,27 @@ +package jobcontroller + +import ( + "strings" + "sync" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map) *JobContext { + var envVars, secretEnvVars []string + for _, env := range job.Properties.Args { + if env.IsCredential { + secretEnvVars = append(secretEnvVars, strings.Join([]string{env.Key, env.Value}, "=")) + continue + } + envVars = append(envVars, strings.Join([]string{env.Key, env.Value}, "=")) + } + return &JobContext{ + Name: job.Name, + Envs: envVars, + SecretEnvs: secretEnvVars, + WorkflowName: workflowCtx.WorkflowName, + TaskID: workflowCtx.TaskID, + Steps: job.Steps, + } +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index a07e21ba..27c3c718 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -2,26 +2,34 @@ package jobcontroller import ( "context" + "fmt" "math/rand" "sync" "time" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/kube/updater" "go.uber.org/zap" + "gopkg.in/yaml.v3" + crClient "sigs.k8s.io/controller-runtime/pkg/client" ) type FreestyleJobCtl struct { job *commonmodels.JobTask + workflowCtx *commonmodels.WorkflowTaskCtx jobContext *sync.Map globalContext *sync.Map logger *zap.SugaredLogger ack func() } -func NewFreestyleJobCtl(job *commonmodels.JobTask, globalContext *sync.Map, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { +func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { return &FreestyleJobCtl{ job: job, + workflowCtx: workflowCtx, globalContext: globalContext, logger: logger, ack: ack, @@ -34,3 +42,106 @@ func (c *FreestyleJobCtl) Run(ctx context.Context) { c.logger.Infof("job: %s sleep %d seccod", c.job.Name, r) c.job.Status = config.StatusPassed } + +func (c *FreestyleJobCtl) run(ctx context.Context) { + + // get kube client + var crClient crClient.Client + var err error + hubServerAddr := config.HubServerAddress() + switch c.job.Properties.ClusterID { + case setting.LocalClusterID: + c.job.Namepace = zadigconfig.Namespace() + default: + c.job.Namepace = setting.AttachedClusterNamespace + + crClient, _, _, err = GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) + if err != nil { + c.job.Status = config.StatusFailed + c.job.Error = err.Error() + c.job.EndTime = time.Now().Unix() + return + } + } + + // decide which docker host to use. + // TODO: do not use code in warpdrive moudule, should move to a public place + // dockerhosts := common.NewDockerHosts(hubServerAddr, c.logger) + // dockerHost := dockerhosts.GetBestHost(common.ClusterID(c.job.Properties.ClusterID), "") + + // TODO: inject vars like task_id and etc, passing them into c.job.Properties.Args + envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Namepace, IsCredential: false} + c.job.Properties.Args = append(c.job.Properties.Args, envNameVar) + + jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx, c.globalContext)) + if err != nil { + msg := fmt.Sprintf("cannot Jobexcutor.Context data: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + + jobLabel := &JobLabel{ + WorkflowName: c.workflowCtx.WorkflowName, + TaskID: c.workflowCtx.TaskID, + JobType: string(c.job.JobType), + } + if err := ensureDeleteConfigMap(c.job.Namepace, jobLabel, crClient); err != nil { + c.logger.Error(err) + c.job.Status = config.StatusFailed + c.job.Error = err.Error() + return + } + + if err := createJobConfigMap( + c.job.Namepace, c.job.Name, jobLabel, string(jobCtxBytes), crClient); err != nil { + msg := fmt.Sprintf("createJobConfigMap error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + + c.logger.Infof("succeed to create cm for build job %s", c.job.Name) + + jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) + + //Resource request default value is LOW + job, err := buildJob(c.job.JobType, jobImage, c.job.Name, c.job.Properties.ClusterID, c.job.Namepace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) + if err != nil { + msg := fmt.Sprintf("create build job context error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + + job.Namespace = c.job.Namepace + + if err := ensureDeleteJob(c.job.Namepace, jobLabel, crClient); err != nil { + msg := fmt.Sprintf("delete build job error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + + // 将集成到KodeRover的私有镜像仓库的访问权限设置到namespace中 + // if err := createOrUpdateRegistrySecrets(p.KubeNamespace, pipelineTask.ConfigPayload.RegistryID, p.Task.Registries, p.kubeClient); err != nil { + // msg := fmt.Sprintf("create secret error: %v", err) + // p.Log.Error(msg) + // p.Task.TaskStatus = config.StatusFailed + // p.Task.Error = msg + // p.SetBuildStatusCompleted(config.StatusFailed) + // return + // } + if err := updater.CreateJob(job, crClient); err != nil { + msg := fmt.Sprintf("create build job error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + c.logger.Infof("succeed to create build job %s", c.job.Name) +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go new file mode 100644 index 00000000..2b02eaf6 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -0,0 +1,296 @@ +package jobcontroller + +import ( + "fmt" + "path" + "strconv" + "strings" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + crClient "sigs.k8s.io/controller-runtime/pkg/client" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" +) + +func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes.Interface, *rest.Config, error) { + controllerRuntimeClient, err := kubeclient.GetKubeClient(hubServerAddr, clusterID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get controller runtime client: %s", err) + } + + clientset, err := kubeclient.GetKubeClientSet(hubServerAddr, clusterID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get clientset: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(hubServerAddr, clusterID) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get rest config: %s", err) + } + + return controllerRuntimeClient, clientset, restConfig, nil +} + +type JobLabel struct { + WorkflowName string + TaskID int64 + JobType string +} + +const ( + jobLabelTaskKey = "s-task" + jobLabelSTypeKey = "s-type" +) + +func ensureDeleteConfigMap(namespace string, jobLabel *JobLabel, kubeClient crClient.Client) error { + ls := getJobLabels(jobLabel) + return updater.DeleteConfigMapsAndWait(namespace, labels.Set(ls).AsSelector(), kubeClient) +} + +func ensureDeleteJob(namespace string, jobLabel *JobLabel, kubeClient crClient.Client) error { + ls := getJobLabels(jobLabel) + return updater.DeleteJobsAndWait(namespace, labels.Set(ls).AsSelector(), kubeClient) +} + +// getJobLabels get labels k-v map from JobLabel struct +func getJobLabels(jobLabel *JobLabel) map[string]string { + retMap := map[string]string{ + jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.WorkflowName), jobLabel.TaskID), + jobLabelSTypeKey: strings.Replace(jobLabel.JobType, "_", "-", -1), + } + // no need to add labels with empty value to a job + for k, v := range retMap { + if len(v) == 0 { + delete(retMap, k) + } + } + return retMap +} + +func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx string, kubeClient crClient.Client) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobName, + Namespace: namespace, + Labels: getJobLabels(jobLabel), + }, + Data: map[string]string{ + "job-config.xml": jobCtx, + }, + } + + return updater.CreateConfigMap(cm, kubeClient) +} + +// getReaperImage generates the image used to run reaper +// depends on the setting on page 'Build' +func getReaperImage(reaperImage, buildOS string) string { + // for built-in image, reaperImage and buildOs can generate a complete image + // reaperImage: ccr.ccs.tencentyun.com/koderover-public/build-base:${BuildOS}-amd64 + // buildOS: focal xenial bionic + jobImage := strings.ReplaceAll(reaperImage, "${BuildOS}", buildOS) + return jobImage +} + +func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, resReq setting.Request, resReqSpec setting.RequestSpec, jobTask *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, registries []*task.RegistryNamespace) (*batchv1.Job, error) { + + labels := getJobLabels(&JobLabel{ + WorkflowName: workflowCtx.WorkflowName, + TaskID: workflowCtx.TaskID, + JobType: string(jobType), + }) + + // 引用集成到系统中的私有镜像仓库的访问权限 + ImagePullSecrets := []corev1.LocalObjectReference{ + { + Name: setting.DefaultImagePullSecret, + }, + } + // for _, reg := range registries { + // secretName, err := genRegistrySecretName(reg) + // if err != nil { + // return nil, fmt.Errorf("failed to generate registry secret name: %s", err) + // } + + // secret := corev1.LocalObjectReference{ + // Name: secretName, + // } + // ImagePullSecrets = append(ImagePullSecrets, secret) + // } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobName, + Labels: labels, + }, + Spec: batchv1.JobSpec{ + Completions: int32Ptr(1), + Parallelism: int32Ptr(1), + BackoffLimit: int32Ptr(0), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + ImagePullSecrets: ImagePullSecrets, + Containers: []corev1.Container{ + { + ImagePullPolicy: corev1.PullAlways, + Name: labels["s-type"], + Image: jobImage, + Env: []corev1.EnvVar{ + { + Name: "JOB_CONFIG_FILE", + Value: path.Join(workflowCtx.ConfigMapMountDir, "job-config.xml"), + }, + // 连接对应wd上的dockerdeamon + { + Name: "DOCKER_HOST", + Value: workflowCtx.DockerHost, + }, + }, + VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), + Resources: getResourceRequirements(resReq, resReqSpec), + + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + }, + }, + Volumes: getVolumes(jobName), + }, + }, + }, + } + + // if ctx.CacheEnable && ctx.Cache.MediumType == commontypes.NFSMedium { + // volumeName := "build-cache" + // job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{ + // Name: volumeName, + // VolumeSource: corev1.VolumeSource{ + // PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + // ClaimName: ctx.Cache.NFSProperties.PVC, + // }, + // }, + // }) + + // mountPath := ctx.CacheUserDir + // if ctx.CacheDirType == commontypes.WorkspaceCacheDir { + // mountPath = "/workspace" + // } + + // job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + // Name: volumeName, + // MountPath: mountPath, + // SubPath: ctx.Cache.NFSProperties.Subpath, + // }) + // } + + // if affinity := addNodeAffinity(clusterID, pipelineTask.ConfigPayload.K8SClusters); affinity != nil { + // job.Spec.Template.Spec.Affinity = affinity + // } + + return job, nil +} + +func getVolumeMounts(configMapMountDir string) []corev1.VolumeMount { + resp := make([]corev1.VolumeMount, 0) + + resp = append(resp, corev1.VolumeMount{ + Name: "job-config", + MountPath: configMapMountDir, + }) + + return resp +} + +func getVolumes(jobName string) []corev1.Volume { + resp := make([]corev1.Volume, 0) + resp = append(resp, corev1.Volume{ + Name: "job-config", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: jobName, + }, + }, + }, + }) + return resp +} + +func getResourceRequirements(resReq setting.Request, resReqSpec setting.RequestSpec) corev1.ResourceRequirements { + + switch resReq { + case setting.HighRequest: + return generateResourceRequirements(setting.HighRequest, setting.HighRequestSpec) + + case setting.MediumRequest: + return generateResourceRequirements(setting.MediumRequest, setting.MediumRequestSpec) + + case setting.LowRequest: + return generateResourceRequirements(setting.LowRequest, setting.LowRequestSpec) + + case setting.MinRequest: + return generateResourceRequirements(setting.MinRequest, setting.MinRequestSpec) + + case setting.DefineRequest: + return generateResourceRequirements(resReq, resReqSpec) + + default: + return generateResourceRequirements(setting.DefaultRequest, setting.DefaultRequestSpec) + } +} + +//generateResourceRequirements +//cpu Request:Limit=1:4 +//memory default Request:Limit=1:4 ; if memoryLimit>= 8Gi,Request:Limit=1:8 +func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSpec) corev1.ResourceRequirements { + + if req != setting.DefineRequest { + return corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(reqSpec.CpuLimit) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(reqSpec.MemoryLimit) + setting.MemoryUintMi), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(reqSpec.CpuReq) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(reqSpec.MemoryReq) + setting.MemoryUintMi), + }, + } + } + + cpuReqInt := reqSpec.CpuLimit / 4 + if cpuReqInt < 1 { + cpuReqInt = 1 + } + memoryReqInt := reqSpec.MemoryLimit / 4 + if memoryReqInt >= 2*1024 { + memoryReqInt = memoryReqInt / 2 + } + if memoryReqInt < 1 { + memoryReqInt = 1 + } + + return corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(reqSpec.CpuLimit) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(reqSpec.MemoryLimit) + setting.MemoryUintMi), + }, + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(strconv.Itoa(cpuReqInt) + setting.CpuUintM), + corev1.ResourceMemory: resource.MustParse(strconv.Itoa(memoryReqInt) + setting.MemoryUintMi), + }, + } +} + +func int32Ptr(i int32) *int32 { return &i } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go new file mode 100644 index 00000000..cf05ea21 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go @@ -0,0 +1,39 @@ +package jobcontroller + +import ( + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type JobContext struct { + Name string `yaml:"name"` + // Workspace 容器工作目录 [必填] + Workspace string `yaml:"workspace"` + Proxy *Proxy `yaml:"proxy"` + // Envs 用户注入环境变量, 包括安装脚本环境变量 [optional] + Envs EnvVar `yaml:"envs"` + // SecretEnvs 用户注入敏感信息环境变量, value不能在stdout stderr中输出 [optional] + SecretEnvs EnvVar `yaml:"secret_envs"` + // WorkflowName + WorkflowName string `yaml:"workflow_name"` + // TaskID + TaskID int64 `yaml:"task_id"` + // Paths 执行脚本Path + Paths string `yaml:"-"` + + Steps []*commonmodels.StepTask `yaml:"steps"` + Outputs []string `yaml:"outputs"` +} + +type EnvVar []string + +// Proxy 翻墙配置信息 +type Proxy struct { + Type string `yaml:"type"` + Address string `yaml:"address"` + Port int `yaml:"port"` + NeedPassword bool `yaml:"need_password"` + Username string `yaml:"username"` + Password string `yaml:"password"` + EnableRepoProxy bool `yaml:"enable_repo_proxy"` + EnableApplicationProxy bool `yaml:"enable_application_proxy"` +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 50d7fd52..68ebf5a9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -14,7 +14,7 @@ type StageCtl interface { Run(ctx context.Context, concurrency int) } -func runStage(ctx context.Context, stage *commonmodels.StageTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { +func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { stage.Status = config.StatusRunning stage.StartTime = time.Now().Unix() ack() @@ -30,14 +30,14 @@ func runStage(ctx context.Context, stage *commonmodels.StageTask, concurrency in case "approve": // TODO approval stage default: - stageCtl = NewCustomStageCtl(stage, globalContext, logger, ack) + stageCtl = NewCustomStageCtl(stage, workflowCtx, globalContext, logger, ack) } stageCtl.Run(ctx, concurrency) } -func RunStages(ctx context.Context, stages []*commonmodels.StageTask, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { +func RunStages(ctx context.Context, stages []*commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { for _, stage := range stages { - runStage(ctx, stage, concurrency, globalContext, logger, ack) + runStage(ctx, stage, workflowCtx, concurrency, globalContext, logger, ack) if statusFailed(stage.Status) { return } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go index 02d8b06c..5c5ef2bb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go @@ -11,16 +11,18 @@ import ( type CustomStageCtl struct { stage *commonmodels.StageTask + workflowCtx *commonmodels.WorkflowTaskCtx globalContext *sync.Map logger *zap.SugaredLogger ack func() } -func NewCustomStageCtl(stage *commonmodels.StageTask, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *CustomStageCtl { +func NewCustomStageCtl(stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *CustomStageCtl { return &CustomStageCtl{ stage: stage, globalContext: globalContext, logger: logger, + workflowCtx: workflowCtx, ack: ack, } } @@ -35,5 +37,5 @@ func (c *CustomStageCtl) Run(ctx context.Context, concurrency int) { } } - jobcontroller.RunJobs(ctx, c.stage.Jobs, workerConcurrency, c.globalContext, c.logger, c.ack) + jobcontroller.RunJobs(ctx, c.stage.Jobs, c.workflowCtx, workerConcurrency, c.globalContext, c.logger, c.ack) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 3f8d985c..144b0071 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -8,6 +8,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + uuid "github.com/satori/go.uuid" "go.uber.org/zap" ) @@ -55,7 +56,16 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { ctx, cancel := context.WithCancel(ctx) defer cancel() cancelChannelMap.Store(fmt.Sprintf("%s-%d", c.workflowTask.WorkflowName, c.workflowTask.TaskID), cancel) - RunStages(ctx, c.workflowTask.Stages, concurrency, c.globalContext, c.logger, c.ack) + + workflowCtx := &commonmodels.WorkflowTaskCtx{ + WorkflowName: c.workflowTask.WorkflowName, + TaskID: c.workflowTask.TaskID, + DistDir: fmt.Sprintf("%s/%s/dist/%d", config.S3StoragePath(), c.workflowTask.WorkflowName, c.workflowTask.TaskID), + DockerMountDir: fmt.Sprintf("/tmp/%s/docker/%d", uuid.NewV4(), time.Now().Unix()), + ConfigMapMountDir: fmt.Sprintf("/tmp/%s/cm/%d", uuid.NewV4(), time.Now().Unix()), + } + + RunStages(ctx, c.workflowTask.Stages, workflowCtx, concurrency, c.globalContext, c.logger, c.ack) updateworkflowStatus(c.workflowTask) } -- Gitee From 7d24f18bb7e227e2bdaa8901df4feebd44038434 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 31 May 2022 13:58:14 +0800 Subject: [PATCH 2408/2647] fix collaboration bulk create product error and reset instance when remove mode (#1583) * rollback init config Signed-off-by: panxunying * fix collaboration bulk create product error Signed-off-by: panxunying Co-authored-by: panxunying --- .../mongodb/collaboration_instance.go | 18 ++++++++++++++- .../service/collaboration_mode.go | 2 +- .../core/environment/service/environment.go | 23 ++++++++++++------- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go index 5a7ba63e..15a8aee7 100644 --- a/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/repository/mongodb/collaboration_instance.go @@ -101,7 +101,7 @@ func (c *CollaborationInstanceColl) Create(args *models.CollaborationInstance) e func (c *CollaborationInstanceColl) Update(uid string, args *models.CollaborationInstance) error { if args == nil { - return errors.New("nil CollaborationMode") + return errors.New("nil CollaborationInstance") } query := bson.M{"collaboration_name": args.CollaborationName, "project_name": args.ProjectName, "user_uid": uid} @@ -118,6 +118,22 @@ func (c *CollaborationInstanceColl) Update(uid string, args *models.Collaboratio return err } +func (c *CollaborationInstanceColl) ResetRevision(modeName, projectName string) error { + if len(modeName) == 0 || len(projectName) == 0 { + return errors.New("nil modeName or projectName") + } + + query := bson.M{"collaboration_name": modeName, "project_name": projectName} + change := bson.M{"$set": bson.M{ + "update_time": time.Now().Unix(), + "revision": 0, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + + return err +} + func (c *CollaborationInstanceColl) BulkCreate(args []*models.CollaborationInstance) error { if len(args) == 0 { return nil diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go index 973308df..0771d79c 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_mode.go @@ -47,5 +47,5 @@ func DeleteCollaborationMode(username, projectName, name string, logger *zap.Sug logger.Errorf("UpdateCollaborationMode error, err msg:%s", err) return err } - return nil + return mongodb.NewCollaborationInstanceColl().ResetRevision(name, projectName) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 68fe0115..38426a20 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1067,6 +1067,9 @@ func BulkCopyHelmProduct(projectName, user, requestID string, arg CopyHelmProduc } var args []*CreateHelmProductArg for _, item := range arg.Items { + if item.OldName == item.NewName { + continue + } if product, ok := productMap[item.OldName]; ok { args = append(args, &CreateHelmProductArg{ ProductName: projectName, @@ -1108,15 +1111,19 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc } for _, item := range arg.Items { + if item.OldName == item.NewName { + continue + } if product, ok := productMap[item.OldName]; ok { - product.EnvName = item.NewName - product.Vars = item.Vars - product.Namespace = projectName + "-env-" + product.EnvName - product.Render.Name = product.Namespace - util.Clear(&product.ID) - product.Render.Revision = 0 - product.BaseName = item.BaseName - err = CreateProduct(user, requestID, product, log) + newProduct := *product + newProduct.EnvName = item.NewName + newProduct.Vars = item.Vars + newProduct.Namespace = projectName + "-env-" + product.EnvName + newProduct.Render.Name = product.Namespace + util.Clear(&newProduct.ID) + newProduct.Render.Revision = 0 + newProduct.BaseName = item.BaseName + err = CreateProduct(user, requestID, &newProduct, log) if err != nil { return err } -- Gitee From 68590a78f448cb89d02ae7a7606bbf67b4c1ecad Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 31 May 2022 14:59:45 +0800 Subject: [PATCH 2409/2647] update data struct Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 3 +-- .../common/repository/models/workflow_v4.go | 1 + .../service/jobcontroller/job_freestyle.go | 27 +++++++++---------- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index fb7ef760..e6a0b5c6 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -67,10 +67,9 @@ type JobTask struct { StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` Error string `bson:"error" json:"error"` - Namepace string `bson:"namespace" json:"namespace"` - PodName string `bson:"pod_name" json:"pod_name"` Properties *JobProperties `bson:"properties" json:"properties"` Steps []*StepTask `bson:"steps" json:"steps"` + Outputs []*Output `bson:"outputs" son:"outputs"` } type StepTask struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index f1952a3e..647a3f96 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -42,6 +42,7 @@ type JobProperties struct { ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` ClusterID string `bson:"cluster_id" json:"cluster_id"` BuildOS string `bson:"build_os" json:"build_os"` + Namepace string `bson:"namespace" json:"namespace"` Args []*KeyVal `bson:"args" json:"args"` } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 27c3c718..755559c6 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -44,16 +44,15 @@ func (c *FreestyleJobCtl) Run(ctx context.Context) { } func (c *FreestyleJobCtl) run(ctx context.Context) { - // get kube client var crClient crClient.Client var err error hubServerAddr := config.HubServerAddress() switch c.job.Properties.ClusterID { case setting.LocalClusterID: - c.job.Namepace = zadigconfig.Namespace() + c.job.Properties.Namepace = zadigconfig.Namespace() default: - c.job.Namepace = setting.AttachedClusterNamespace + c.job.Properties.Namepace = setting.AttachedClusterNamespace crClient, _, _, err = GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) if err != nil { @@ -70,7 +69,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { // dockerHost := dockerhosts.GetBestHost(common.ClusterID(c.job.Properties.ClusterID), "") // TODO: inject vars like task_id and etc, passing them into c.job.Properties.Args - envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Namepace, IsCredential: false} + envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namepace, IsCredential: false} c.job.Properties.Args = append(c.job.Properties.Args, envNameVar) jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx, c.globalContext)) @@ -87,7 +86,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { TaskID: c.workflowCtx.TaskID, JobType: string(c.job.JobType), } - if err := ensureDeleteConfigMap(c.job.Namepace, jobLabel, crClient); err != nil { + if err := ensureDeleteConfigMap(c.job.Properties.Namepace, jobLabel, crClient); err != nil { c.logger.Error(err) c.job.Status = config.StatusFailed c.job.Error = err.Error() @@ -95,7 +94,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { } if err := createJobConfigMap( - c.job.Namepace, c.job.Name, jobLabel, string(jobCtxBytes), crClient); err != nil { + c.job.Properties.Namepace, c.job.Name, jobLabel, string(jobCtxBytes), crClient); err != nil { msg := fmt.Sprintf("createJobConfigMap error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed @@ -103,24 +102,24 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { return } - c.logger.Infof("succeed to create cm for build job %s", c.job.Name) + c.logger.Infof("succeed to create cm for job %s", c.job.Name) jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW - job, err := buildJob(c.job.JobType, jobImage, c.job.Name, c.job.Properties.ClusterID, c.job.Namepace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) + job, err := buildJob(c.job.JobType, jobImage, c.job.Name, c.job.Properties.ClusterID, c.job.Properties.Namepace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) if err != nil { - msg := fmt.Sprintf("create build job context error: %v", err) + msg := fmt.Sprintf("create job context error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed c.job.Error = msg return } - job.Namespace = c.job.Namepace + job.Namespace = c.job.Properties.Namepace - if err := ensureDeleteJob(c.job.Namepace, jobLabel, crClient); err != nil { - msg := fmt.Sprintf("delete build job error: %v", err) + if err := ensureDeleteJob(c.job.Properties.Namepace, jobLabel, crClient); err != nil { + msg := fmt.Sprintf("delete job error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed c.job.Error = msg @@ -137,11 +136,11 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { // return // } if err := updater.CreateJob(job, crClient); err != nil { - msg := fmt.Sprintf("create build job error: %v", err) + msg := fmt.Sprintf("create job error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed c.job.Error = msg return } - c.logger.Infof("succeed to create build job %s", c.job.Name) + c.logger.Infof("succeed to create job %s", c.job.Name) } -- Gitee From 79bce280b87adb1113895660232df2893e77cfdb Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Tue, 31 May 2022 16:00:31 +0800 Subject: [PATCH 2410/2647] Clusters can be connected to HubAgent (#1576) Signed-off-by: ddh27 --- .../common/repository/models/k8s_cluster.go | 53 ++++-- .../common/repository/mongodb/k8s_cluster.go | 14 ++ .../aslan/core/common/service/kube/service.go | 71 +++++++- .../core/multicluster/handler/clusters.go | 7 + .../aslan/core/multicluster/handler/router.go | 1 + .../core/multicluster/service/clusters.go | 155 +++++++++++++++--- pkg/microservice/aslan/core/service.go | 3 + .../core/repository/models/models.go | 31 ++-- .../core/repository/mongodb/k8scluster.go | 5 +- .../hubserver/core/service/service.go | 2 + 10 files changed, 281 insertions(+), 61 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 4f2cbfc5..1b0c3dd4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -25,24 +25,27 @@ import ( ) type K8SCluster struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty" bson:"advanced_config,omitempty"` - Status setting.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Provider int8 `json:"provider" bson:"provider"` - Local bool `json:"local" bson:"local"` - Cache types.Cache `json:"cache" bson:"cache"` + ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty" bson:"advanced_config,omitempty"` + Status setting.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Provider int8 `json:"provider" bson:"provider"` + Local bool `json:"local" bson:"local"` + Cache types.Cache `json:"cache" bson:"cache"` + LastConnectionTime int64 `json:"last_connection_time" bson:"last_connection_time"` + UpdateHubagentErrorMsg string `json:"update_hubagent_error_msg" bson:"update_hubagent_error_msg"` + DindCfg *DindCfg `json:"dind_cfg" bson:"dind_cfg"` } type K8SClusterResp struct { @@ -70,6 +73,20 @@ type NodeSelectorRequirement struct { Operator corev1.NodeSelectorOperator `json:"operator" bson:"operator"` } +type Limits struct { + CPU int `json:"cpu" bson:"cpu"` + Memory int `json:"memory" bson:"memory"` +} + +type Resources struct { + Limits *Limits `json:"limits" bson:"limits"` +} + +type DindCfg struct { + Replicas int `json:"replicas" bson:"replicas"` + Resources *Resources `json:"resources" bson:"resources"` +} + func (K8SCluster) TableName() string { return "k8s_cluster" } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index c1670260..85e31388 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -211,6 +211,7 @@ func (c *K8SClusterColl) UpdateMutableFields(cluster *models.K8SCluster, id stri "production": cluster.Production, "advanced_config": cluster.AdvancedConfig, "cache": cluster.Cache, + "dind_cfg": cluster.DindCfg, }}, ) @@ -227,6 +228,19 @@ func (c *K8SClusterColl) UpdateStatus(cluster *models.K8SCluster) error { return err } +func (c *K8SClusterColl) UpdateUpgradeAgentInfo(id, updateHubagentErrorMsg string) error { + clusterID, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + _, err = c.UpdateOne(context.TODO(), + bson.M{"_id": clusterID}, bson.M{"$set": bson.M{ + "update_hubagent_error_msg": updateHubagentErrorMsg, + }}, + ) + return err +} + func (c *K8SClusterColl) FindConnectedClusters() ([]*models.K8SCluster, error) { var clusters []*models.K8SCluster cursor, err := c.Collection.Find(context.TODO(), bson.M{"disconnected": false}) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 14305d01..e05d6ae5 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "net/url" + "strconv" "strings" "text/template" @@ -110,6 +111,7 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, id string, logger *z cluster.AdvancedConfig = &commonmodels.AdvancedConfig{ Strategy: "normal", } + cluster.DindCfg = nil } err = s.coll.Create(cluster, id) if err != nil { @@ -191,6 +193,19 @@ func (s *Service) GetCluster(id string, logger *zap.SugaredLogger) (*models.K8SC if err != nil { return nil, err } + + if cluster.DindCfg == nil { + cluster.DindCfg = &commonmodels.DindCfg{ + Replicas: DefaultDindReplicas, + Resources: &commonmodels.Resources{ + Limits: &commonmodels.Limits{ + CPU: DefaultDindLimitsCPU, + Memory: DefaultDindLimitsMemory, + }, + }, + } + } + cluster.Token = token return cluster, nil } @@ -225,7 +240,7 @@ func (s *Service) ListConnectedClusters(logger *zap.SugaredLogger) ([]*models.K8 return clusters, nil } -func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { +func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useDeployment, upgradeAgent bool, logger *zap.SugaredLogger) ([]byte, error) { var ( cluster *models.K8SCluster err error @@ -252,6 +267,8 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD return nil, err } + dindReplicas, dindLimitsCPU, dindLimitsMemory := setDindCfg(upgradeAgent, cluster) + if cluster.Namespace == "" { err = YamlTemplate.Execute(buffer, TemplateSchema{ HubAgentImage: agentImage, @@ -260,6 +277,9 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD HubServerBaseAddr: hubBase.String(), AslanBaseAddr: config2.SystemAddress(), UseDeployment: useDeployment, + DindReplicas: dindReplicas, + DindLimitsCPU: dindLimitsCPU, + DindLimitsMemory: dindLimitsMemory, }) } else { err = YamlTemplateForNamespace.Execute(buffer, TemplateSchema{ @@ -270,6 +290,9 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD AslanBaseAddr: config2.SystemAddress(), UseDeployment: useDeployment, Namespace: cluster.Namespace, + DindReplicas: dindReplicas, + DindLimitsCPU: dindLimitsCPU, + DindLimitsMemory: dindLimitsMemory, }) } @@ -280,6 +303,37 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD return buffer.Bytes(), nil } +func (s *Service) UpdateUpgradeAgentInfo(id, updateHubagentErrorMsg string) error { + _, err := s.coll.Get(id) + if err != nil { + return err + } + err = s.coll.UpdateUpgradeAgentInfo(id, updateHubagentErrorMsg) + return err +} + +func setDindCfg(upgradeAgent bool, cluster *models.K8SCluster) (int, string, string) { + var dindReplicas int = DefaultDindReplicas + var dindLimitsCPU string = strconv.Itoa(DefaultDindLimitsCPU) + setting.CpuUintM + var dindLimitsMemory string = strconv.Itoa(DefaultDindLimitsMemory) + setting.MemoryUintMi + + if upgradeAgent && cluster.DindCfg != nil { + if cluster.DindCfg.Replicas > 0 { + dindReplicas = cluster.DindCfg.Replicas + } + + if cluster.DindCfg.Resources != nil && cluster.DindCfg.Resources.Limits != nil { + if cluster.DindCfg.Resources.Limits.CPU > 0 { + dindLimitsCPU = strconv.Itoa(cluster.DindCfg.Resources.Limits.CPU) + setting.CpuUintM + } + if cluster.DindCfg.Resources.Limits.Memory > 0 { + dindLimitsMemory = strconv.Itoa(cluster.DindCfg.Resources.Limits.Memory) + setting.MemoryUintMi + } + } + } + return dindReplicas, dindLimitsCPU, dindLimitsMemory +} + type TemplateSchema struct { HubAgentImage string ResourceServerImage string @@ -288,8 +342,17 @@ type TemplateSchema struct { Namespace string UseDeployment bool AslanBaseAddr string + DindReplicas int + DindLimitsCPU string + DindLimitsMemory string } +const ( + DefaultDindReplicas int = 1 + DefaultDindLimitsCPU int = 4000 + DefaultDindLimitsMemory int = 8192 +) + var YamlTemplate = template.Must(template.New("agentYaml").Parse(` --- @@ -491,7 +554,7 @@ metadata: app.kubernetes.io/name: zadig spec: serviceName: dind - replicas: 1 + replicas: {{.DindReplicas}} selector: matchLabels: app.kubernetes.io/component: dind @@ -523,8 +586,8 @@ spec: containerPort: 2375 resources: limits: - cpu: "4" - memory: 8Gi + cpu: {{.DindLimitsCPU}} + memory: {{.DindLimitsMemory}} requests: cpu: 100m memory: 128Mi diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index 1ae09c4c..c6078c7f 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -143,3 +143,10 @@ func GetClusterYaml(hubURI string) func(*gin.Context) { c.Abort() } } + +func UpgradeAgent(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = service.UpgradeAgent(c.Param("id"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 61101eb2..3eda6da8 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -27,6 +27,7 @@ func (*Router) Inject(router *gin.RouterGroup) { Agent := router.Group("agent") { Agent.GET("/:id/agent.yaml", GetClusterYaml("/api/hub")) + Agent.GET("/:id/upgrade", UpgradeAgent) } Cluster := router.Group("clusters") diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 35463273..ba08a410 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -22,13 +22,17 @@ import ( "net/http" "regexp" "strings" + "time" "github.com/gin-gonic/gin" + "github.com/hashicorp/go-multierror" "go.uber.org/zap" + "helm.sh/helm/v3/pkg/releaseutil" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/controller-runtime/pkg/client" @@ -40,23 +44,30 @@ import ( "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/kube/serializer" + "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util/ginzap" ) var namePattern = regexp.MustCompile(`^[0-9a-zA-Z_.-]{1,32}$`) type K8SCluster struct { - ID string `json:"id,omitempty"` - Name string `json:"name"` - Description string `json:"description"` - AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty"` - Status setting.K8SClusterStatus `json:"status"` - Production bool `json:"production"` - CreatedAt int64 `json:"createdAt"` - CreatedBy string `json:"createdBy"` - Provider int8 `json:"provider"` - Local bool `json:"local"` - Cache types.Cache `json:"cache"` + ID string `json:"id,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + AdvancedConfig *AdvancedConfig `json:"advanced_config,omitempty"` + Status setting.K8SClusterStatus `json:"status"` + Production bool `json:"production"` + CreatedAt int64 `json:"createdAt"` + CreatedBy string `json:"createdBy"` + Provider int8 `json:"provider"` + Local bool `json:"local"` + Cache types.Cache `json:"cache"` + LastConnectionTime int64 `json:"last_connection_time"` + UpdateHubagentErrorMsg string `json:"update_hubagent_error_msg"` + DindCfg *commonmodels.DindCfg `json:"dind_cfg"` } type AdvancedConfig struct { @@ -114,18 +125,34 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( ProjectNames: getProjectNames(c.ID.Hex(), logger), } } + + if c.DindCfg == nil { + c.DindCfg = &commonmodels.DindCfg{ + Replicas: kube.DefaultDindReplicas, + Resources: &commonmodels.Resources{ + Limits: &commonmodels.Limits{ + CPU: kube.DefaultDindLimitsCPU, + Memory: kube.DefaultDindLimitsMemory, + }, + }, + } + } + res = append(res, &K8SCluster{ - ID: c.ID.Hex(), - Name: c.Name, - Description: c.Description, - Status: c.Status, - Production: c.Production, - CreatedBy: c.CreatedBy, - CreatedAt: c.CreatedAt, - Provider: c.Provider, - Local: c.Local, - AdvancedConfig: advancedConfig, - Cache: c.Cache, + ID: c.ID.Hex(), + Name: c.Name, + Description: c.Description, + Status: c.Status, + Production: c.Production, + CreatedBy: c.CreatedBy, + CreatedAt: c.CreatedAt, + Provider: c.Provider, + Local: c.Local, + AdvancedConfig: advancedConfig, + Cache: c.Cache, + LastConnectionTime: c.LastConnectionTime, + UpdateHubagentErrorMsg: c.UpdateHubagentErrorMsg, + DindCfg: c.DindCfg, }) } @@ -209,6 +236,7 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K CreatedAt: args.CreatedAt, CreatedBy: args.CreatedBy, Cache: args.Cache, + DindCfg: args.DindCfg, } return s.CreateCluster(cluster, args.ID, logger) @@ -256,6 +284,7 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com switch id { case setting.LocalClusterID: namespace = config.Namespace() + args.DindCfg = nil default: namespace = setting.AttachedClusterNamespace } @@ -313,6 +342,7 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com AdvancedConfig: advancedConfig, Production: args.Production, Cache: args.Cache, + DindCfg: args.DindCfg, } return s.UpdateCluster(id, cluster, logger) } @@ -360,7 +390,59 @@ func ProxyAgent(writer gin.ResponseWriter, request *http.Request) { func GetYaml(id, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { s, _ := kube.NewService("") - return s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), hubURI, useDeployment, logger) + return s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), hubURI, useDeployment, false, logger) +} + +func UpgradeAgent(id string, logger *zap.SugaredLogger) error { + if id == setting.LocalClusterID { + return fmt.Errorf("local cluster %s not support Upgrade Agent", id) + } + s, _ := kube.NewService("") + clusterInfo, err := s.GetCluster(id, logger) + if err != nil { + return err + } + if clusterInfo.Status != setting.Normal { + return fmt.Errorf("cluster %s status %s not support Upgrade Agent", clusterInfo.Name, clusterInfo.Status) + } + + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), id) + if err != nil { + return fmt.Errorf(" failed to get kube client: %s cluster: %s", err, clusterInfo.Name) + } + + yamls, err := s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), "/api/hub", true, true, logger) + if err != nil { + return err + } + + errList := new(multierror.Error) + manifests := releaseutil.SplitManifests(string(yamls)) + resources := make([]*unstructured.Unstructured, 0, len(manifests)) + for _, item := range manifests { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(item)) + if err != nil { + log.Errorf("[UpgradeAgent] Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %v", item, err) + errList = multierror.Append(errList, err) + continue + } + resources = append(resources, u) + } + + for _, u := range resources { + err = updater.CreateOrPatchUnstructured(u, kubeClient) + if err != nil { + log.Errorf("[UpgradeAgent] Failed to create or update %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + } + updateHubagentErrorMsg := "" + if len(errList.Errors) > 0 { + updateHubagentErrorMsg = errList.Error() + } + + return s.UpdateUpgradeAgentInfo(id, updateHubagentErrorMsg) } func setClusterCache(args *K8SCluster) error { @@ -391,3 +473,30 @@ func setClusterCache(args *K8SCluster) error { return nil } + +func ClusterApplyUpgradeAgent() { + for i := 0; i < 3; i++ { + time.Sleep(10 * time.Second) + k8sClusters, err := commonrepo.NewK8SClusterColl().List(nil) + if err != nil && !commonrepo.IsErrNoDocuments(err) { + log.Errorf("[ClusterApplyUpgradeAgent] list cluster error:%s", err) + return + } + + for _, cluster := range k8sClusters { + if cluster.ID.Hex() == setting.LocalClusterID { + continue + } + if cluster.Status != setting.Normal { + log.Warnf("[ClusterApplyUpgradeAgent] not support apply. cluster %s status %s ", cluster.Name, cluster.Status) + continue + } + if err := UpgradeAgent(cluster.ID.Hex(), ginzap.WithContext(&gin.Context{}).Sugar()); err != nil { + log.Errorf("[ClusterApplyUpgradeAgent] UpgradeAgent cluster %s error:%s", cluster.Name, err) + } else { + log.Infof("[ClusterApplyUpgradeAgent] UpgradeAgent cluster %s successed", cluster.Name) + } + } + time.Sleep(20 * time.Second) + } +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 817c987e..8c868f7c 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -36,6 +36,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" + multiclusterservice "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" @@ -132,6 +133,8 @@ func Start(ctx context.Context) { go StartControllers(ctx.Done()) + go multiclusterservice.ClusterApplyUpgradeAgent() + initRsaKey() } diff --git a/pkg/microservice/hubserver/core/repository/models/models.go b/pkg/microservice/hubserver/core/repository/models/models.go index 04c47b98..50edf38f 100644 --- a/pkg/microservice/hubserver/core/repository/models/models.go +++ b/pkg/microservice/hubserver/core/repository/models/models.go @@ -30,21 +30,22 @@ type K8SClusterInfo struct { } type K8SCluster struct { - ID primitive.ObjectID `json:"id" bson:"_id"` - Name string `json:"name" bson:"name"` - Tags []string `json:"tags" bson:"tags"` - Description string `json:"description" bson:"description"` - Namespace string `json:"namespace" bson:"namespace"` - Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` - Status config.K8SClusterStatus `json:"status" bson:"status"` - Error string `json:"error" bson:"error"` - Yaml string `json:"yaml" bson:"yaml"` - Production bool `json:"production" bson:"production"` - CreatedAt int64 `json:"createdAt" bson:"createdAt"` - CreatedBy string `json:"createdBy" bson:"createdBy"` - Disconnected bool `json:"-" bson:"disconnected"` - Token string `json:"token" bson:"-"` - Local bool `json:"local" bson:"local"` + ID primitive.ObjectID `json:"id" bson:"_id"` + Name string `json:"name" bson:"name"` + Tags []string `json:"tags" bson:"tags"` + Description string `json:"description" bson:"description"` + Namespace string `json:"namespace" bson:"namespace"` + Info *K8SClusterInfo `json:"info,omitempty" bson:"info,omitempty"` + Status config.K8SClusterStatus `json:"status" bson:"status"` + Error string `json:"error" bson:"error"` + Yaml string `json:"yaml" bson:"yaml"` + Production bool `json:"production" bson:"production"` + CreatedAt int64 `json:"createdAt" bson:"createdAt"` + CreatedBy string `json:"createdBy" bson:"createdBy"` + Disconnected bool `json:"-" bson:"disconnected"` + Token string `json:"token" bson:"-"` + Local bool `json:"local" bson:"local"` + LastConnectionTime int64 `json:"last_connection_time" bson:"last_connection_time"` } func (K8SCluster) TableName() string { diff --git a/pkg/microservice/hubserver/core/repository/mongodb/k8scluster.go b/pkg/microservice/hubserver/core/repository/mongodb/k8scluster.go index 9993a2d4..eb153989 100644 --- a/pkg/microservice/hubserver/core/repository/mongodb/k8scluster.go +++ b/pkg/microservice/hubserver/core/repository/mongodb/k8scluster.go @@ -18,6 +18,7 @@ package mongodb import ( "context" + "time" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" @@ -68,7 +69,8 @@ func (c *K8sClusterColl) UpdateStatus(cluster *models.K8SCluster) error { query := bson.M{"_id": cluster.ID} update := bson.M{"$set": bson.M{ - "status": cluster.Status, + "last_connection_time": cluster.LastConnectionTime, + "status": cluster.Status, }} _, err := c.UpdateOne(context.TODO(), query, update) @@ -88,6 +90,7 @@ func (c *K8sClusterColl) UpdateConnectState(id string, disconnected bool) error if disconnected { change["status"] = config.Disconnected } else { + change["last_connection_time"] = time.Now().Unix() change["status"] = config.Pending } update := bson.M{"$set": change} diff --git a/pkg/microservice/hubserver/core/service/service.go b/pkg/microservice/hubserver/core/service/service.go index 9b84f2bc..36f0c5c2 100644 --- a/pkg/microservice/hubserver/core/service/service.go +++ b/pkg/microservice/hubserver/core/service/service.go @@ -85,6 +85,7 @@ func Authorize(req *http.Request) (clientKey string, authed bool, err error) { if cluster.Status != "normal" { cluster.Status = "normal" + cluster.LastConnectionTime = time.Now().Unix() err = mongodb.NewK8sClusterColl().UpdateStatus(cluster) if err != nil { log.Errorf("failed to update clusters status %s %v", cluster.Name, err) @@ -267,6 +268,7 @@ func Sync(server *remotedialer.Server, stopCh <-chan struct{}) { "cluster %s connected changed %s => %s", cluster.Name, cluster.Status, config.Normal, ) + cluster.LastConnectionTime = time.Now().Unix() cluster.Status = config.Normal statusChanged = true } -- Gitee From 2aa938f21a919d8ce781cf5838f8fb63583eceef Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Tue, 31 May 2022 16:57:37 +0800 Subject: [PATCH 2411/2647] Clusters can be connected to HubAgent:add api auth (#1592) Signed-off-by: ddh27 --- pkg/microservice/policy/core/yamlconfig/urls.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/policy/core/yamlconfig/urls.yaml b/pkg/microservice/policy/core/yamlconfig/urls.yaml index a9df1331..8c7807b2 100644 --- a/pkg/microservice/policy/core/yamlconfig/urls.yaml +++ b/pkg/microservice/policy/core/yamlconfig/urls.yaml @@ -135,6 +135,9 @@ exemption_urls: - endpoint: api/aslan/cluster/clusters methods: - POST + - endpoint: api/aslan/cluster/agent/?*/upgrade + methods: + - GET - endpoint: api/aslan/cluster/clusters/?* methods: - PUT -- Gitee From 927109ae9ca6ee25b005a758e4752f9050e2de93 Mon Sep 17 00:00:00 2001 From: dyliu Date: Tue, 31 May 2022 17:02:27 +0800 Subject: [PATCH 2412/2647] fix yaml trigger webhook problem (#1589) * fix yaml trigger webhook problem Signed-off-by: liu deyi * fix yaml trigger webhook problem Signed-off-by: liu deyi --- .../aslan/core/workflow/service/webhook/gitlab_workflow_task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 98fcdba4..1349a013 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -70,7 +70,7 @@ func (gmem *gitlabMergeEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) if gmem.isYaml { refFlag := false for _, ref := range gmem.trigger.Rules.Branchs { - if matched, _ := regexp.MatchString(ref, getBranchFromRef(hookRepo.Branch)); matched { + if matched, _ := regexp.MatchString(ref, getBranchFromRef(ev.ObjectAttributes.TargetBranch)); matched { refFlag = true break } -- Gitee From e968e5430eabfc9324db1df226b57ec08bd12eb7 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 31 May 2022 17:12:39 +0800 Subject: [PATCH 2413/2647] fix default image error for scanning Signed-off-by: Min Min --- .../aslan/core/workflow/testing/service/scanning.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go index e19f9782..3963669f 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "sort" + "strings" "time" "go.uber.org/zap" @@ -189,6 +190,12 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, notificationID, user return 0, err } + scanningImage := imageInfo.Value + + if imageInfo.ImageFrom == commonmodels.ImageFromKoderover { + scanningImage = strings.ReplaceAll(config.ReaperImage(), "${BuildOS}", imageInfo.Value) + } + registries, err := commonservice.ListRegistryNamespaces("", true, log) if err != nil { log.Errorf("ListRegistryNamespaces err:%v", err) @@ -237,7 +244,7 @@ func CreateScanningTask(id string, req []*ScanningRepoInfo, notificationID, user Status: config.StatusCreated, ScanningID: scanningInfo.ID.Hex(), Name: scanningInfo.Name, - ImageInfo: imageInfo.Value, + ImageInfo: scanningImage, ResReq: scanningInfo.AdvancedSetting.ResReq, ResReqSpec: scanningInfo.AdvancedSetting.ResReqSpec, Registries: registries, -- Gitee From 5ba510f930ad0a67d69e823f87c51e82719f6242 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Tue, 31 May 2022 18:14:10 +0800 Subject: [PATCH 2414/2647] fix Clusters can be connected to HubAgent (#1596) Signed-off-by: ddh27 --- .../aslan/core/common/service/kube/service.go | 8 ++++---- .../aslan/core/multicluster/service/clusters.go | 4 ++-- pkg/microservice/hubserver/core/service/service.go | 14 ++++++-------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index e05d6ae5..1ef11f4a 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -240,7 +240,7 @@ func (s *Service) ListConnectedClusters(logger *zap.SugaredLogger) ([]*models.K8 return clusters, nil } -func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useDeployment, upgradeAgent bool, logger *zap.SugaredLogger) ([]byte, error) { +func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { var ( cluster *models.K8SCluster err error @@ -267,7 +267,7 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD return nil, err } - dindReplicas, dindLimitsCPU, dindLimitsMemory := setDindCfg(upgradeAgent, cluster) + dindReplicas, dindLimitsCPU, dindLimitsMemory := setDindCfg(cluster) if cluster.Namespace == "" { err = YamlTemplate.Execute(buffer, TemplateSchema{ @@ -312,12 +312,12 @@ func (s *Service) UpdateUpgradeAgentInfo(id, updateHubagentErrorMsg string) erro return err } -func setDindCfg(upgradeAgent bool, cluster *models.K8SCluster) (int, string, string) { +func setDindCfg(cluster *models.K8SCluster) (int, string, string) { var dindReplicas int = DefaultDindReplicas var dindLimitsCPU string = strconv.Itoa(DefaultDindLimitsCPU) + setting.CpuUintM var dindLimitsMemory string = strconv.Itoa(DefaultDindLimitsMemory) + setting.MemoryUintMi - if upgradeAgent && cluster.DindCfg != nil { + if cluster.DindCfg != nil { if cluster.DindCfg.Replicas > 0 { dindReplicas = cluster.DindCfg.Replicas } diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index ba08a410..29b35746 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -390,7 +390,7 @@ func ProxyAgent(writer gin.ResponseWriter, request *http.Request) { func GetYaml(id, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ([]byte, error) { s, _ := kube.NewService("") - return s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), hubURI, useDeployment, false, logger) + return s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), hubURI, useDeployment, logger) } func UpgradeAgent(id string, logger *zap.SugaredLogger) error { @@ -411,7 +411,7 @@ func UpgradeAgent(id string, logger *zap.SugaredLogger) error { return fmt.Errorf(" failed to get kube client: %s cluster: %s", err, clusterInfo.Name) } - yamls, err := s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), "/api/hub", true, true, logger) + yamls, err := s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), "/api/hub", true, logger) if err != nil { return err } diff --git a/pkg/microservice/hubserver/core/service/service.go b/pkg/microservice/hubserver/core/service/service.go index 36f0c5c2..8b7c8ab6 100644 --- a/pkg/microservice/hubserver/core/service/service.go +++ b/pkg/microservice/hubserver/core/service/service.go @@ -83,14 +83,12 @@ func Authorize(req *http.Request) (clientKey string, authed bool, err error) { clusters.Store(cluster.ID.Hex(), input.Cluster) - if cluster.Status != "normal" { - cluster.Status = "normal" - cluster.LastConnectionTime = time.Now().Unix() - err = mongodb.NewK8sClusterColl().UpdateStatus(cluster) - if err != nil { - log.Errorf("failed to update clusters status %s %v", cluster.Name, err) - return - } + cluster.Status = "normal" + cluster.LastConnectionTime = time.Now().Unix() + err = mongodb.NewK8sClusterColl().UpdateStatus(cluster) + if err != nil { + log.Errorf("failed to update clusters status %s %v", cluster.Name, err) + return } log.Infof("cluster %s connected", cluster.Name) -- Gitee From 8ac4f42f56f9eb576f468967072550c2d46aab48 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 31 May 2022 18:20:08 +0800 Subject: [PATCH 2415/2647] fix collaboration nil pointer (#1598) * rollback init config Signed-off-by: panxunying * collaboration bugfix Signed-off-by: panxunying * fix collaboration nil pointer Signed-off-by: panxunying Co-authored-by: panxunying --- .../collaboration/service/collaboration_instance.go | 11 ++++++++--- pkg/middleware/gin/collaboration.go | 3 +++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index b7c4da1a..39e7c7a3 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1136,16 +1136,18 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id } renderSets, err := getRenderSet(projectName, newProductName.List()) if err != nil { + logger.Errorf("getRenderSet error:%s", err) return nil, err } if renderSets != nil { productRenderSetMap := make(map[string]models2.RenderSet) for _, set := range renderSets { - productRenderSetMap[set.EnvName] = *set + productRenderSetMap[set.EnvName] = set } for _, product := range newProduct { set, ok := productRenderSetMap[product.BaseName] if !ok { + logger.Errorf("product:%s not exist", product.BaseName) return nil, fmt.Errorf("product:%s not exist", product.BaseName) } @@ -1158,6 +1160,7 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id for _, product := range newProduct { chart, ok := envChartsMap[product.BaseName] if !ok { + logger.Errorf("product:%s not exist", product.BaseName) return nil, fmt.Errorf("product:%s not exist", product.BaseName) } @@ -1317,10 +1320,11 @@ func GetCollaborationNew(projectName, uid, identityType, userName string, logger logger.Errorf("GetCollaborationNew error, err msg:%s", err) return nil, err } + logger.Infof("updateresp:%v", updateResp) return getCollaborationNew(updateResp, projectName, identityType, userName, logger) } -func getRenderSet(projectName string, envs []string) ([]*models2.RenderSet, error) { +func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error) { products, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ InProjects: []string{projectName}, InEnvs: envs, @@ -1335,8 +1339,9 @@ func getRenderSet(projectName string, envs []string) ([]*models2.RenderSet, erro Name: product.Namespace, }) } - renderSets, err := commonrepo.NewRenderSetColl().List(&commonrepo.RenderSetListOption{ + renderSets, err := commonrepo.NewRenderSetColl().ListByFindOpts(&commonrepo.RenderSetListOption{ ProductTmpl: projectName, + FindOpts: findOpts, }) if err != nil { return nil, err diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index 903d042d..ef74db6c 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -45,6 +45,9 @@ func GetCollaborationNew() gin.HandlerFunc { ctx.Err = err c.Abort() } + if newResp == nil { + c.Next() + } if newResp.IfSync { err := service.SyncCollaborationInstance(nil, projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.RequestID, ctx.Logger) if err != nil { -- Gitee From 3839c4b3d00be46239ef8aaac7d0ebf435a47750 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 31 May 2022 19:36:58 +0800 Subject: [PATCH 2416/2647] fixed submodule not working problem Signed-off-by: Min Min --- pkg/microservice/warpdrive/core/service/taskplugin/scanning.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go b/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go index 259b82c1..f5addf0a 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/scanning.go @@ -127,6 +127,7 @@ func (p *ScanPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC Password: p.Task.Repos[0].Password, EnableProxy: p.Task.Repos[0].EnableProxy, RemoteName: p.Task.Repos[0].RemoteName, + SubModules: p.Task.Repos[0].SubModules, } if repo.RemoteName == "" { repo.RemoteName = "origin" -- Gitee From 4dbb5befb76054d8969701160d3db9100f211ae9 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Tue, 31 May 2022 20:44:40 +0800 Subject: [PATCH 2417/2647] fix collaboration create product with wrong namespace (#1602) * rollback init config Signed-off-by: panxunying * fix k8s new product namespace error Signed-off-by: panxunying Co-authored-by: panxunying --- .../core/collaboration/service/collaboration_instance.go | 1 - .../aslan/core/environment/service/environment.go | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 39e7c7a3..4927cd17 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1320,7 +1320,6 @@ func GetCollaborationNew(projectName, uid, identityType, userName string, logger logger.Errorf("GetCollaborationNew error, err msg:%s", err) return nil, err } - logger.Infof("updateresp:%v", updateResp) return getCollaborationNew(updateResp, projectName, identityType, userName, logger) } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 38426a20..ec02fc18 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1118,8 +1118,8 @@ func BulkCopyYamlProduct(projectName, user, requestID string, arg CopyYamlProduc newProduct := *product newProduct.EnvName = item.NewName newProduct.Vars = item.Vars - newProduct.Namespace = projectName + "-env-" + product.EnvName - newProduct.Render.Name = product.Namespace + newProduct.Namespace = projectName + "-env-" + newProduct.EnvName + newProduct.Render.Name = newProduct.Namespace util.Clear(&newProduct.ID) newProduct.Render.Revision = 0 newProduct.BaseName = item.BaseName -- Gitee From c82fb84a0462a5bd91ec7f14a9e7e5cf1c412976 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 31 May 2022 21:11:11 +0800 Subject: [PATCH 2418/2647] fixed get scanning task info error Signed-off-by: Min Min --- .../core/workflow/testing/service/scanning.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go index 3963669f..96d956cf 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/scanning.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/scanning.go @@ -406,10 +406,15 @@ func GetScanningTaskInfo(scanningID string, taskID int64, log *zap.SugaredLogger return nil, err } - sonarInfo, err := commonrepo.NewSonarIntegrationColl().GetByID(context.TODO(), scanningInfo.SonarID) - if err != nil { - log.Errorf("failed to get sonar integration info, error: %s", err) - return nil, err + resultAddr := "" + + if scanningInfo.ScannerType == "sonarQube" { + sonarInfo, err := commonrepo.NewSonarIntegrationColl().GetByID(context.TODO(), scanningInfo.SonarID) + if err != nil { + log.Errorf("failed to get sonar integration info, error: %s", err) + return nil, err + } + resultAddr = sonarInfo.ServerAddress } repoInfo := resp.Stages[0].SubTasks[scanningInfo.Name] @@ -432,7 +437,7 @@ func GetScanningTaskInfo(scanningID string, taskID int64, log *zap.SugaredLogger CreateTime: resp.CreateTime, EndTime: resp.EndTime, RepoInfo: scanningTaskInfo.Repos, - ResultLink: sonarInfo.ServerAddress, + ResultLink: resultAddr, }, nil } -- Gitee From 0d3de94aa2230ead75cb4350d9f33e251015c1cd Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 31 May 2022 21:27:10 +0800 Subject: [PATCH 2419/2647] add namespace return Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/workflow.go | 3 +++ .../aslan/core/workflow/service/workflow/workflow_task.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 6cf257e3..21fc5eb2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -434,6 +434,9 @@ func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, } } } + for _, repo := range preSet.Repos { + repo.RepoNamespace = repo.GetRepoNamespace() + } resp = append(resp, preSet) } return resp, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 581683ca..e27083b9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -360,6 +360,9 @@ func EnsureBuildResp(mb *commonmodels.Build) { } mb.Repos = mb.SafeRepos() + for _, repo := range mb.Repos { + repo.RepoNamespace = repo.GetRepoNamespace() + } if mb.PreBuild != nil { if len(mb.PreBuild.Installs) == 0 { -- Gitee From ad2b3f57e4f345683ecee71cf370ea32c723eede Mon Sep 17 00:00:00 2001 From: flyer Date: Wed, 1 Jun 2022 09:38:12 +0800 Subject: [PATCH 2420/2647] support IDE plugin (#1588) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- Makefile | 17 +- cmd/zgctl/cmd/root.go | 69 +++ cmd/zgctl/cmd/serve.go | 76 +++ cmd/zgctl/main.go | 25 + docker/service/syncthing.Dockerfile | 3 + go.mod | 2 + go.sum | 19 + .../aslan/core/environment/handler/ide.go | 55 ++ .../aslan/core/environment/handler/router.go | 3 + .../aslan/core/environment/service/ide.go | 536 ++++++++++++++++++ pkg/microservice/zgctl/handler.go | 111 ++++ pkg/microservice/zgctl/interface.go | 51 ++ pkg/microservice/zgctl/router.go | 54 ++ pkg/microservice/zgctl/template.go | 186 ++++++ pkg/microservice/zgctl/utils.go | 27 + pkg/microservice/zgctl/zgctl.go | 443 +++++++++++++++ pkg/shared/client/aslan/environment.go | 20 + pkg/types/ide.go | 33 ++ pkg/types/labels.go | 2 + pkg/types/workload.go | 24 + pkg/util/pointer.go | 4 + 21 files changed, 1759 insertions(+), 1 deletion(-) create mode 100644 cmd/zgctl/cmd/root.go create mode 100644 cmd/zgctl/cmd/serve.go create mode 100644 cmd/zgctl/main.go create mode 100644 docker/service/syncthing.Dockerfile create mode 100644 pkg/microservice/aslan/core/environment/handler/ide.go create mode 100644 pkg/microservice/aslan/core/environment/service/ide.go create mode 100644 pkg/microservice/zgctl/handler.go create mode 100644 pkg/microservice/zgctl/interface.go create mode 100644 pkg/microservice/zgctl/router.go create mode 100644 pkg/microservice/zgctl/template.go create mode 100644 pkg/microservice/zgctl/utils.go create mode 100644 pkg/microservice/zgctl/zgctl.go create mode 100644 pkg/types/ide.go create mode 100644 pkg/types/workload.go diff --git a/Makefile b/Makefile index 4d6926da..9129336a 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,10 @@ REAPER_OS= focal xenial bionic ALL_IMAGES=$(TARGETS:=.image) ALL_PUSH=$(TARGETS:=.push) -all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 +PLATFORMS=darwin linux windows +ARCHITECTURES=amd64 arm64 + +all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 build-zgctl-all-platforms zgctl-sidecar.amd64 all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 all.amd64: $(ALL_IMAGES:=.amd64) resource-server.build.amd64 @@ -73,6 +76,18 @@ resource-server.upload.arm64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server: resource-server.upload.arm64: resource-server.build.arm64 @docker push ${MAKE_IMAGE} +pre-build: + @mkdir -p bin/ + +build-zgctl-all-platforms: pre-build + $(foreach GOOS, $(PLATFORMS),\ + $(foreach GOARCH, $(ARCHITECTURES), $(shell export GOOS=$(GOOS); export GOARCH=$(GOARCH); CGO_ENABLED=0 go build -v -o bin/zgctl-$(GOOS)-$(GOARCH) cmd/zgctl/main.go))) + +zgctl-sidecar.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zgctl-sidecar:${VERSION}-amd64 +zgctl-sidecar.amd64: zgctl-sidecar.amd64 + @docker build -f docker/service/syncthing.Dockerfile --tag ${MAKE_IMAGE} . + .PHONY: clean clean: @rm -rf docker/dist + @rm -rf bin/ diff --git a/cmd/zgctl/cmd/root.go b/cmd/zgctl/cmd/root.go new file mode 100644 index 00000000..cc3e8447 --- /dev/null +++ b/cmd/zgctl/cmd/root.go @@ -0,0 +1,69 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" +) + +var zadigHost string +var zadigToken string +var homeDir string + +func init() { + rootCmd.PersistentFlags().StringVar(&zadigHost, "zadig-host", "", "host of zadig system") + rootCmd.PersistentFlags().StringVar(&zadigToken, "zadig-token", "", "token to access zadig system") + rootCmd.PersistentFlags().StringVar(&homeDir, "home-dir", "$HOME/.zadig", "home dir of plugin") + + logPath := filepath.Join(os.ExpandEnv(homeDir), "log") + if _, err := os.Stat(logPath); err == nil { + err := os.Remove(logPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to remove log %s: %s", logPath, err) + os.Exit(1) + } + } + + log.Init(&log.Config{ + Level: config.LogLevel(), + SendToFile: config.SendLogToFile(), + Filename: logPath, + Development: config.Mode() != setting.ReleaseMode, + MaxSize: 5, + }) +} + +var rootCmd = &cobra.Command{ + Use: "zgctl", + Short: "zgctl is a cli used for IDE plugin.", + RunE: func(cmd *cobra.Command, args []string) error { + + return nil + }, +} + +func Execute() error { + return rootCmd.Execute() +} diff --git a/cmd/zgctl/cmd/serve.go b/cmd/zgctl/cmd/serve.go new file mode 100644 index 00000000..54efa8e7 --- /dev/null +++ b/cmd/zgctl/cmd/serve.go @@ -0,0 +1,76 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/dgraph-io/badger/v3" + "github.com/gin-gonic/gin" + "github.com/spf13/cobra" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/zgctl" + ginmiddleware "github.com/koderover/zadig/pkg/middleware/gin" + "github.com/koderover/zadig/pkg/tool/log" +) + +var listenAddr string + +func init() { + serveCmd.Flags().StringVar(&listenAddr, "listen-addr", ":8080", "listen address of zgctl serve") + + rootCmd.AddCommand(serveCmd) +} + +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "provide APIs for IDE Plugin", + Long: "serve is a sub cmd used to provide APIs for IDE Plugin.", + RunE: func(cmd *cobra.Command, args []string) error { + r := gin.Default() + injectMiddlewares(r) + + homeDir := os.ExpandEnv(homeDir) + dbPath := filepath.Join(homeDir, "db") + db, err := badger.Open(badger.DefaultOptions(dbPath)) + if err != nil { + return fmt.Errorf("failed to open db %q: %s", dbPath, err) + } + defer db.Close() + + apiv1 := r.Group("/api/v1") + router := zgctl.NewRouter(&zgctl.RouterConfig{ + ZadigHost: zadigHost, + ZadigToken: zadigToken, + HomeDir: homeDir, + DB: db, + }) + router.Inject(apiv1) + + return r.Run(listenAddr) + }, +} + +func injectMiddlewares(g *gin.Engine) { + g.Use(ginmiddleware.Response()) + g.Use(ginmiddleware.RequestID()) + g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) + g.Use(gin.Recovery()) +} diff --git a/cmd/zgctl/main.go b/cmd/zgctl/main.go new file mode 100644 index 00000000..baeb50cc --- /dev/null +++ b/cmd/zgctl/main.go @@ -0,0 +1,25 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/koderover/zadig/cmd/zgctl/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/docker/service/syncthing.Dockerfile b/docker/service/syncthing.Dockerfile new file mode 100644 index 00000000..263aafca --- /dev/null +++ b/docker/service/syncthing.Dockerfile @@ -0,0 +1,3 @@ +FROM syncthing/syncthing:1.20.1 + +WORKDIR /home/syncthing diff --git a/go.mod b/go.mod index fcf8d4b6..04c57aaa 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/coocood/freecache v1.1.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b + github.com/dgraph-io/badger/v3 v3.2103.2 // indirect github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v20.10.11+incompatible github.com/docker/go-connections v0.4.0 @@ -61,6 +62,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 github.com/satori/go.uuid v1.2.0 + github.com/shirou/gopsutil/v3 v3.20.10 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index a682c89d..e3ad83eb 100644 --- a/go.sum +++ b/go.sum @@ -189,6 +189,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -460,9 +462,14 @@ github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mz github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b h1:ovHbNjGAQsGEs67tYU6C6ex2D2shDkeZ7pPprx58f2k= github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b/go.mod h1:g64CEwk9b4oLTREOu8mFkjTkDUvyxzWGqhnPwQlfrq8= github.com/dexidp/dex/api/v2 v2.0.0/go.mod h1:k5arBJT1QYvpsEY3sEd0NXJp3hKWKuUUfzJ3BlcqPdM= +github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= +github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= @@ -500,6 +507,7 @@ github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= @@ -588,6 +596,8 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -736,6 +746,7 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -806,6 +817,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/crfs v0.0.0-20191108021818-71d77da419c9/go.mod h1:etGhoOqfwPkooV6aqoX3eBGQOJblqdoc9XvWOeuxpPw= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1410,7 +1423,10 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada h1:WokF3GuxBeL+n4Lk4Fa8v9mbdjlrl7bHuneF4N1bk2I= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/gopsutil/v3 v3.20.10 h1:7zomV9HJv6UGk225YtvEa5+camNLpbua3MAz/GqiVJY= +github.com/shirou/gopsutil/v3 v3.20.10/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -1440,6 +1456,8 @@ github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34c github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= @@ -1940,6 +1958,7 @@ golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/pkg/microservice/aslan/core/environment/handler/ide.go b/pkg/microservice/aslan/core/environment/handler/ide.go new file mode 100644 index 00000000..c700e7cd --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/ide.go @@ -0,0 +1,55 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "errors" + + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/types" +) + +func PatchWorkload(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("name") + serviceName := c.Param("serviceName") + projectName := c.Query("projectName") + + var staretInfo types.StartDevmodeInfo + if err := c.BindJSON(&staretInfo); err != nil { + ctx.Err = errors.New("invalid request body") + return + } + + ctx.Resp, ctx.Err = service.PatchWorkload(c, projectName, envName, serviceName, staretInfo.DevImage) +} + +func RecoverWorkload(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("name") + serviceName := c.Param("serviceName") + projectName := c.Query("projectName") + + ctx.Err = service.RecoverWorkload(c, projectName, envName, serviceName) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index f0bce70c..2cfc7f49 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -154,6 +154,9 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/check/sharenv/:op/ready", CheckShareEnvReady) environments.GET("/:name/services/:serviceName/pmexec", ConnectSshPmExec) + + environments.POST("/:name/services/:serviceName/devmode/patch", PatchWorkload) + environments.POST("/:name/services/:serviceName/devmode/recover", RecoverWorkload) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/ide.go b/pkg/microservice/aslan/core/environment/service/ide.go new file mode 100644 index 00000000..be53b0e8 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/ide.go @@ -0,0 +1,536 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" +) + +var ErrUnsupportedService = errors.New("unsupported workload type (only support Deployment/StatefulSet) or workload number is not 1") + +func PatchWorkload(ctx context.Context, projectName, envName, serviceName, devImage string) (*types.WorkloadInfo, error) { + project, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: projectName, + EnvName: envName, + }) + if err != nil { + return nil, fmt.Errorf("failed to query env %q of project %q: %s", envName, projectName, err) + } + ns := project.Namespace + clusterID := project.ClusterID + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client: %s", err) + } + + selector := labels.Set{setting.ProductLabel: projectName, setting.ServiceLabel: serviceName}.AsSelector() + workloadType, err := getWorkloadType(ctx, kclient, selector, ns) + if err != nil { + return nil, fmt.Errorf("failed to get workload type: %s", err) + } + log.Infof("workloadType: %s", workloadType) + + podName, err := patchWorkload(ctx, kclient, selector, ns, devImage, workloadType) + + return &types.WorkloadInfo{ + PodName: podName, + PodNamespace: ns, + }, err +} + +func RecoverWorkload(ctx context.Context, projectName, envName, serviceName string) error { + project, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: projectName, + EnvName: envName, + }) + if err != nil { + return fmt.Errorf("failed to query env %q of project %q: %s", envName, projectName, err) + } + ns := project.Namespace + clusterID := project.ClusterID + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + selector := labels.Set{setting.ProductLabel: projectName, setting.ServiceLabel: serviceName}.AsSelector() + workloadType, err := getWorkloadType(ctx, kclient, selector, ns) + if err != nil { + return fmt.Errorf("failed to get workload type: %s", err) + } + log.Infof("workloadType: %s", workloadType) + + return recoverWorkload(ctx, kclient, selector, ns, workloadType) +} + +func scaleAndWaitService(ctx context.Context, envName, projectName, serviceName, ns string, workloadType types.WorkloadType, kclient client.Client, selector labels.Selector) error { + logger, err := zap.NewProduction() + if err != nil { + return err + } + + err = ScaleService(envName, projectName, serviceName, 1, logger.Sugar()) + if err != nil { + return err + } + + return wait.PollImmediate(2*time.Second, 3*time.Minute, func() (done bool, err error) { + switch workloadType { + case types.DeploymentWorkload: + objs := &appsv1.DeploymentList{} + err := kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + // Don't return error to continue polling. + log.Warnf("Failed to list deployments: %s", err) + return false, nil + } + + if len(objs.Items) != 1 { + return false, fmt.Errorf("invalid number of deployments: %d", len(objs.Items)) + } + + obj := objs.Items[0] + if obj.Status.Replicas == 1 && obj.Status.ReadyReplicas == 1 { + return true, nil + } + + return false, nil + case types.StatefulSetWorkload: + objs := &appsv1.StatefulSetList{} + err := kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + // Don't return error to continue polling. + log.Warnf("Failed to list statefulsets: %s", err) + return false, nil + } + + if len(objs.Items) != 1 { + return false, fmt.Errorf("invalid number of statefulsets: %d", len(objs.Items)) + } + + obj := objs.Items[0] + if obj.Status.Replicas == 1 && obj.Status.ReadyReplicas == 1 { + return true, nil + } + + return false, nil + default: + return false, fmt.Errorf("unsupported workload type: %s", workloadType) + } + }) +} + +func getWorkloadType(ctx context.Context, kclient client.Client, selector labels.Selector, ns string) (types.WorkloadType, error) { + res := []types.WorkloadType{} + + deploymentList := &appsv1.DeploymentList{} + err := kclient.List(ctx, deploymentList, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + return "", fmt.Errorf("failed to list deployment: %s", err) + } + if len(deploymentList.Items) == 1 { + res = append(res, types.DeploymentWorkload) + } + + stsList := &appsv1.StatefulSetList{} + err = kclient.List(ctx, stsList, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + return "", fmt.Errorf("failed to list statefulset: %s", err) + } + if len(stsList.Items) == 1 { + res = append(res, types.StatefulSetWorkload) + } + + if len(res) != 1 { + return "", ErrUnsupportedService + } + + return res[0], nil +} + +func patchWorkload(ctx context.Context, kclient client.Client, selector labels.Selector, ns, devImage string, workloadType types.WorkloadType) (string, error) { + switch workloadType { + case types.DeploymentWorkload: + err := patchDeployment(ctx, kclient, selector, ns, devImage) + if err != nil { + return "", err + } + case types.StatefulSetWorkload: + err := patchStatefulSet(ctx, kclient, selector, ns, devImage) + if err != nil { + return "", err + } + default: + return "", fmt.Errorf("unsupported workload type: %s", workloadType) + } + + err := waitPodReady(ctx, kclient, selector, ns) + if err != nil { + return "", err + } + + pod, err := getPod(ctx, kclient, selector, ns) + if err != nil { + return "", fmt.Errorf("failed to get pod: %s", err) + } + + return pod.Name, nil +} + +func patchDeployment(ctx context.Context, kclient client.Client, selector labels.Selector, ns, devImage string) error { + objs := &appsv1.DeploymentList{} + err := kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + return fmt.Errorf("failed to list deployments: %s", err) + } + if len(objs.Items) != 1 { + return fmt.Errorf("invalid number of deployments: %d", len(objs.Items)) + } + + deployment := objs.Items[0] + + spec, err := json.Marshal(deployment.Spec) + if err != nil { + return fmt.Errorf("failed to marshal deployment spec: %s", err) + } + if deployment.Annotations == nil { + deployment.Annotations = map[string]string{} + } + deployment.Annotations[types.OriginSpec] = string(spec) + + deployment.Spec.Replicas = util.GetInt32Pointer(1) + deployment.Spec.Template.Spec.Volumes = []corev1.Volume{ + corev1.Volume{ + Name: "syncthing", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + deployment.Spec.Template.Spec.Containers = []corev1.Container{ + corev1.Container{ + Name: types.IDEContainerNameDev, + Image: devImage, + ImagePullPolicy: corev1.PullAlways, + Command: []string{"/bin/sh", "-c", "tail -f /dev/null"}, + WorkingDir: types.DevmodeWorkDir, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + corev1.VolumeMount{ + Name: "syncthing", + MountPath: types.DevmodeWorkDir, + }, + }, + }, + corev1.Container{ + Name: types.IDEContainerNameSidecar, + Image: types.IDESidecarImage, + ImagePullPolicy: corev1.PullAlways, + Command: []string{"/bin/sh", "-c", "tail -f /dev/null"}, + WorkingDir: types.DevmodeWorkDir, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("300m"), + corev1.ResourceMemory: resource.MustParse("300Mi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + corev1.VolumeMount{ + Name: "syncthing", + MountPath: types.DevmodeWorkDir, + }, + }, + }, + } + + return kclient.Update(ctx, &deployment) +} + +func waitPodReady(ctx context.Context, kclient client.Client, selector labels.Selector, ns string) error { + return wait.PollImmediate(2*time.Second, 3*time.Minute, func() (done bool, err error) { + objs := &corev1.PodList{} + err = kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + log.Warnf("failed to list pod: %s", err) + return false, nil + } + + for _, pod := range objs.Items { + if len(pod.Spec.Containers) != 2 { + continue + } + + found := true + for _, container := range pod.Spec.Containers { + if container.Name == types.IDEContainerNameDev || container.Name == types.IDEContainerNameSidecar { + continue + } + + found = false + } + + if found && pod.Status.Phase == corev1.PodRunning { + return true, nil + } + } + + return false, nil + }) +} + +func patchStatefulSet(ctx context.Context, kclient client.Client, selector labels.Selector, ns, devImage string) error { + objs := &appsv1.StatefulSetList{} + err := kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + return fmt.Errorf("failed to list statefulsets: %s", err) + } + if len(objs.Items) != 1 { + return fmt.Errorf("invalid number of statefulsets: %d", len(objs.Items)) + } + + sts := objs.Items[0] + + spec, err := json.Marshal(sts.Spec) + if err != nil { + return fmt.Errorf("failed to marshal statefulset spec: %s", err) + } + if sts.Annotations == nil { + sts.Annotations = map[string]string{} + } + sts.Annotations[types.OriginSpec] = string(spec) + + sts.Spec.Replicas = util.GetInt32Pointer(1) + sts.Spec.Template.Spec.Volumes = []corev1.Volume{ + corev1.Volume{ + Name: "syncthing", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + sts.Spec.Template.Spec.Containers = []corev1.Container{ + corev1.Container{ + Name: types.IDEContainerNameDev, + Image: devImage, + ImagePullPolicy: corev1.PullAlways, + Command: []string{"/bin/sh", "-c", "tail -f /dev/null"}, + WorkingDir: types.DevmodeWorkDir, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("1Gi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + corev1.VolumeMount{ + Name: "syncthing", + MountPath: types.DevmodeWorkDir, + }, + }, + }, + corev1.Container{ + Name: types.IDEContainerNameSidecar, + Image: types.IDESidecarImage, + ImagePullPolicy: corev1.PullAlways, + Command: []string{"/bin/sh", "-c", "tail -f /dev/null"}, + WorkingDir: types.DevmodeWorkDir, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("300m"), + corev1.ResourceMemory: resource.MustParse("300Mi"), + }, + }, + VolumeMounts: []corev1.VolumeMount{ + corev1.VolumeMount{ + Name: "syncthing", + MountPath: types.DevmodeWorkDir, + }, + }, + }, + } + + return kclient.Update(ctx, &sts) +} + +func recoverWorkload(ctx context.Context, kclient client.Client, selector labels.Selector, ns string, workloadType types.WorkloadType) error { + switch workloadType { + case types.DeploymentWorkload: + err := recoverDeployment(ctx, kclient, selector, ns) + if err != nil { + return err + } + case types.StatefulSetWorkload: + log.Infof("[ZADIG]Begin to recover statefulset: %s", selector.String()) + err := recoverStatefulSet(ctx, kclient, selector, ns) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupported workload type: %s", workloadType) + } + + return waitPodReady(ctx, kclient, selector, ns) +} + +func recoverDeployment(ctx context.Context, kclient client.Client, selector labels.Selector, ns string) error { + objs := &appsv1.DeploymentList{} + err := kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + return fmt.Errorf("failed to list deployments: %s", err) + } + if len(objs.Items) != 1 { + return fmt.Errorf("invalid number of deployments: %d", len(objs.Items)) + } + + deployment := objs.Items[0] + if deployment.Annotations == nil || deployment.Annotations[types.OriginSpec] == "" { + return fmt.Errorf("cant' find original workload spec from annotation %q", types.OriginSpec) + } + + var spec appsv1.DeploymentSpec + if err := json.Unmarshal([]byte(deployment.Annotations[types.OriginSpec]), &spec); err != nil { + return fmt.Errorf("failed to unmarshal original workload spec from annotation: %s", err) + } + + deployment.Spec = spec + delete(deployment.Annotations, types.OriginSpec) + + return kclient.Update(ctx, &deployment) +} + +func recoverStatefulSet(ctx context.Context, kclient client.Client, selector labels.Selector, ns string) error { + objs := &appsv1.StatefulSetList{} + err := kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + return fmt.Errorf("failed to list statefulsets: %s", err) + } + if len(objs.Items) != 1 { + return fmt.Errorf("invalid number of statefulsets: %d", len(objs.Items)) + } + + sts := objs.Items[0] + if sts.Annotations == nil || sts.Annotations[types.OriginSpec] == "" { + return fmt.Errorf("cant' find original workload spec from annotation %q", types.OriginSpec) + } + + var spec appsv1.StatefulSetSpec + if err := json.Unmarshal([]byte(sts.Annotations[types.OriginSpec]), &spec); err != nil { + return fmt.Errorf("failed to unmarshal original workload spec from annotation: %s", err) + } + + sts.Spec = spec + delete(sts.Annotations, types.OriginSpec) + + return kclient.Update(ctx, &sts) +} + +func getPod(ctx context.Context, kclient client.Client, selector labels.Selector, ns string) (*corev1.Pod, error) { + var targetPod *corev1.Pod + err := wait.PollImmediate(2*time.Second, 6*time.Second, func() (done bool, err error) { + objs := &corev1.PodList{} + err = kclient.List(ctx, objs, &client.ListOptions{ + Namespace: ns, + LabelSelector: selector, + }) + if err != nil { + log.Warnf("failed to list pod: %s", err) + return false, nil + } + + for _, pod := range objs.Items { + if len(pod.Spec.Containers) != 2 { + continue + } + + found := true + for _, container := range pod.Spec.Containers { + if container.Name == types.IDEContainerNameDev || container.Name == types.IDEContainerNameSidecar { + continue + } + + found = false + } + + if found { + targetPod = &pod + return true, nil + } + } + + return false, nil + }) + + if targetPod == nil { + return nil, fmt.Errorf("failed to find target pod: %s", err) + } + + return targetPod, nil +} diff --git a/pkg/microservice/zgctl/handler.go b/pkg/microservice/zgctl/handler.go new file mode 100644 index 00000000..4940792c --- /dev/null +++ b/pkg/microservice/zgctl/handler.go @@ -0,0 +1,111 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package zgctl + +import ( + "errors" + + "github.com/dgraph-io/badger/v3" + "github.com/gin-gonic/gin" + + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/types" +) + +type HandlerConfig struct { + ZadigHost string + ZadigToken string + HomeDir string + DB *badger.DB +} + +type handlers struct { + zgctl ZgCtler +} + +type KubeconfigInfo struct { + KubeConfig KubeConfigData `json:"kubeconfig"` +} + +type KubeConfigData struct { + Path string `json:"path"` +} + +func NewHandlers(cfg *HandlerConfig) ZgCtlHandler { + zgctl := NewZgCtl(&ZgCtlConfig{ + ZadigHost: cfg.ZadigHost, + ZadigToken: cfg.ZadigToken, + HomeDir: cfg.HomeDir, + DB: cfg.DB, + }) + + return &handlers{ + zgctl: zgctl, + } +} + +func (h *handlers) StartDevMode(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("envName") + serviceName := c.Param("serviceName") + projectName := c.Query("projectName") + dataDir := c.Query("dataDir") + + var startInfo types.StartDevmodeInfo + if err := c.BindJSON(&startInfo); err != nil { + ctx.Err = errors.New("invalid request body") + return + } + + ctx.Err = h.zgctl.StartDevMode(c, projectName, envName, serviceName, dataDir, startInfo.DevImage) +} + +func (h *handlers) StopDevMode(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("envName") + serviceName := c.Param("serviceName") + projectName := c.Query("projectName") + + ctx.Err = h.zgctl.StopDevMode(c, projectName, envName, serviceName) +} + +func (h *handlers) DevImages(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp = h.zgctl.DevImages() +} + +func (h *handlers) ConfigKubeconfig(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("envName") + projectName := c.Query("projectName") + + var kubeconfig KubeconfigInfo + if err := c.BindJSON(&kubeconfig); err != nil { + ctx.Err = errors.New("invalid request body") + return + } + + ctx.Err = h.zgctl.ConfigKubeconfig(projectName, envName, kubeconfig.KubeConfig.Path) +} diff --git a/pkg/microservice/zgctl/interface.go b/pkg/microservice/zgctl/interface.go new file mode 100644 index 00000000..8444e3ea --- /dev/null +++ b/pkg/microservice/zgctl/interface.go @@ -0,0 +1,51 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package zgctl + +import ( + "context" + + "github.com/gin-gonic/gin" +) + +type ZgCtler interface { + // StartDevMode starts dev mode. + StartDevMode(ctx context.Context, projectName, envName, serviceName, dataDir, devImage string) error + + // StopDevMode stops dev mode. + StopDevMode(ctx context.Context, projectName, envName, serviceName string) error + + // DevImages provides image list for dev mode. + DevImages() []string + + // ConfigKubeconfig stores relations between env and kubeconfig. + ConfigKubeconfig(projectName, envName, kubeconfigPath string) error +} + +type ZgCtlHandler interface { + // StartDevMode is a gin handler that starts dev mode. + StartDevMode(*gin.Context) + + // StopDevMode is a gin handler that stops dev mode. + StopDevMode(*gin.Context) + + // DevImages is a gin handler that provides image list for dev mode. + DevImages(*gin.Context) + + // ConfigKubeconfig is a gin handler that stores relations between env and kubeconfig. + ConfigKubeconfig(*gin.Context) +} diff --git a/pkg/microservice/zgctl/router.go b/pkg/microservice/zgctl/router.go new file mode 100644 index 00000000..0f3ebfc5 --- /dev/null +++ b/pkg/microservice/zgctl/router.go @@ -0,0 +1,54 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package zgctl + +import ( + "github.com/dgraph-io/badger/v3" + "github.com/gin-gonic/gin" +) + +type RouterConfig struct { + ZadigHost string + ZadigToken string + HomeDir string + DB *badger.DB +} + +type Router struct { + handlers ZgCtlHandler +} + +func NewRouter(cfg *RouterConfig) *Router { + handlers := NewHandlers(&HandlerConfig{ + ZadigHost: cfg.ZadigHost, + ZadigToken: cfg.ZadigToken, + HomeDir: cfg.HomeDir, + DB: cfg.DB, + }) + + return &Router{ + handlers: handlers, + } +} + +func (r *Router) Inject(router *gin.RouterGroup) { + router.POST("/devmode/:envName/:serviceName", r.handlers.StartDevMode) + router.DELETE("/devmode/:envName/:serviceName", r.handlers.StopDevMode) + router.POST("/devmode/:envName/kubeconfig", r.handlers.ConfigKubeconfig) + + router.GET("/devmode/images", r.handlers.DevImages) +} diff --git a/pkg/microservice/zgctl/template.go b/pkg/microservice/zgctl/template.go new file mode 100644 index 00000000..003cc03f --- /dev/null +++ b/pkg/microservice/zgctl/template.go @@ -0,0 +1,186 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package zgctl + +var syncthingConfigTmpl = ` + + + basic + + + + + + + 1 + + 3600 + + basic + + 0 + 0 + 0 + random + false + 0 + 0 + 10 + false + false + false + 25 + .stfolder + false + 0 + 2 + false + standard + standard + false + false + + +

    dynamic
    + false + false + 0 + 0 + 0 + false + 0 + + +
    {{.RemoteAddr}}
    + false + false + 0 + 0 + 0 +
    + +
    127.0.0.1:8384
    + Tb4ahP7HEXxqDwf6UehnySgcb9mUozuM + default +
    + + + {{.ListenAddr}} + default + false + false + 21027 + [ff12::8384]:21027 + 0 + 0 + 60 + false + 10 + false + true + 60 + 30 + 10 + -1 + 3 + + https://data.syncthing.net/newdata + false + 1800 + true + 12 + false + 24 + false + 5 + false + 1 + https://upgrades.syncthing.net/meta.json + false + 10 + authenticationUserAndPassword + 0 + true + 0 + https://crash.syncthing.net/newcrash + true + 180 + 20 + default + auto + 0 + true + false + 0 + 0 + false + + + + basic + + + + 1 + + 3600 + + basic + + 0 + 0 + 0 + random + false + 0 + 0 + 10 + false + false + false + 25 + .stfolder + false + 0 + 2 + false + standard + standard + false + false + + +
    dynamic
    + false + false + 0 + 0 + 0 + false + 0 +
    + +
    + + +` + +var syncthingRunTmpl = ` +#!/bin/sh + +nohup syncthing serve --config={{.ConfigDir}} --data={{.DataDir}} & +` diff --git a/pkg/microservice/zgctl/utils.go b/pkg/microservice/zgctl/utils.go new file mode 100644 index 00000000..3fb8d564 --- /dev/null +++ b/pkg/microservice/zgctl/utils.go @@ -0,0 +1,27 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package zgctl + +var devImages = []string{ + "ccr.ccs.tencentyun.com/koderover-rc/golang:1.18.1", + "ccr.ccs.tencentyun.com/koderover-rc/node:18.2", + "ccr.ccs.tencentyun.com/koderover-rc/python:3.10.4-buster", + "ccr.ccs.tencentyun.com/koderover-rc/openjdk:19-jdk-buster", + "ccr.ccs.tencentyun.com/koderover-rc/php:8.1.6-fpm", + "ccr.ccs.tencentyun.com/koderover-rc/ruby:3.1.2", + "ccr.ccs.tencentyun.com/koderover-rc/rust:1.61.0", +} diff --git a/pkg/microservice/zgctl/zgctl.go b/pkg/microservice/zgctl/zgctl.go new file mode 100644 index 00000000..a56b135a --- /dev/null +++ b/pkg/microservice/zgctl/zgctl.go @@ -0,0 +1,443 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package zgctl + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + "time" + + "github.com/dgraph-io/badger/v3" + "github.com/shirou/gopsutil/v3/process" + + "github.com/koderover/zadig/pkg/shared/client/aslan" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" +) + +type ZgCtlConfig struct { + ZadigHost string + ZadigToken string + HomeDir string + DB *badger.DB +} + +type zgctl struct { + aslanClient *aslan.Client + homeDir string + syncthingBin string + db *badger.DB +} + +type SyncthingConfig struct { + SyncDirPath string + SyncType string + LocalDeviceID string + RemoteDeviceID string + RemoteAddr string + ListenAddr string +} + +type SyncthingScript struct { + ConfigDir string + DataDir string +} + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func NewZgCtl(cfg *ZgCtlConfig) ZgCtler { + aslanClient := aslan.NewExternal(cfg.ZadigHost, cfg.ZadigToken) + aslanClient.SetTimeout(3 * time.Minute) + + log.Infof("home dir: %s", cfg.HomeDir) + + return &zgctl{ + aslanClient: aslanClient, + homeDir: cfg.HomeDir, + syncthingBin: filepath.Join(cfg.HomeDir, "bin", "syncthing"), + db: cfg.DB, + } +} + +func (z *zgctl) StartDevMode(ctx context.Context, projectName, envName, serviceName, dataDir, devImage string) error { + // 1. Adjust replicas of the workload to 1 and patch Pod. + log.Info("Begin to patch worload.") + workloadInfo, err := z.aslanClient.PatchWorkload(projectName, envName, serviceName, devImage) + if err != nil { + return fmt.Errorf("failed to patch service %q in env %q of project %q: %s", envName, serviceName, projectName, err) + } + + log.Infof("workload info: %#v", workloadInfo) + err = z.setZadig2K8sData(projectName, envName, serviceName, workloadInfo) + if err != nil { + return fmt.Errorf("failed to set workload info: %s", err) + } + + // 2. Configure `Syncthing`. + log.Info("Begin to config Syncthing.") + localPort := z.genRandPort() + remotePort := z.genRandPort() + configDir := z.generateServiceConfigDir(projectName, envName, serviceName) + err = z.configSyncthing(ctx, configDir, dataDir, localPort, remotePort) + if err != nil { + return fmt.Errorf("failed to config syncthing for service %q in env %q: %s", envName, serviceName, err) + } + + // 3. Execute port-forward to implement one-way network communication. + log.Info("Begin to do port-forward.") + + kubeconfigPath, err := z.getEnvKubeconfigData(projectName, envName) + if err != nil { + return fmt.Errorf("failed to find kubeconfig path of env %q in project %q: %s", envName, projectName, err) + } + log.Infof("kubeconfig path for env %q in project %q: %s", envName, projectName, kubeconfigPath) + + err = z.podPortForward(ctx, workloadInfo.PodNamespace, workloadInfo.PodName, kubeconfigPath, remotePort) + if err != nil { + return fmt.Errorf("failed to port-forward service %q in env %q: %s", envName, serviceName, err) + } + + // 4. Start `Syncthing`. + log.Info("Begin to start Syncthing.") + return z.startSyncthing(ctx, workloadInfo.PodNamespace, workloadInfo.PodName, configDir, dataDir, kubeconfigPath) +} + +func (z *zgctl) StopDevMode(ctx context.Context, projectName, envName, serviceName string) error { + configDir := z.generateServiceConfigDir(projectName, envName, serviceName) + + // 1. Stop `Syncthing` locally and delete the corresponding configuration. + log.Info("Begin to stop Syncthing.") + + workloadInfo, err := z.getZadig2K8sData(projectName, envName, serviceName) + if err != nil { + return fmt.Errorf("failed to get find workload info for project %q, envName %q, serviceName %q: %s", projectName, envName, serviceName, err) + } + + err = z.stopSyncthing(ctx, configDir, workloadInfo) + if err != nil { + log.Warnf("Failed to stop syncthing for service %q in env %q: %s", envName, serviceName, err) + } + + // 2. Recover workload. + log.Info("Begin to recover workload.") + return z.aslanClient.RecoverWorkload(projectName, envName, serviceName) +} + +func (z *zgctl) DevImages() []string { + return devImages +} + +func (z *zgctl) ConfigKubeconfig(projectName, envName, kubeconfigPath string) error { + return z.setEnvKubeconfigData(projectName, envName, kubeconfigPath) +} + +func (z *zgctl) configSyncthing(ctx context.Context, configDir, dataDir string, localPort, remotePort int) error { + // 1. Generate local `Syncthing` config. + localDeviceID, err := z.configLocalSyncthing(configDir) + if err != nil { + return fmt.Errorf("failed to generate local Syncthing: %s", err) + } + log.Infof("local device id: %s", localDeviceID) + + // 2. Generate remote `Syncthing` config. + tmpRemoteConfigPath := filepath.Join(configDir, "remote") + tmpRemoteDeviceID, err := z.configLocalSyncthing(tmpRemoteConfigPath) + if err != nil { + return fmt.Errorf("failed to generate tmp remote Syncthing: %s", err) + } + log.Infof("tmp remote device id: %s", tmpRemoteDeviceID) + + // 3. Adjust configs of local and remote Syncthing. + return z.adjustSyncthingConfig(localDeviceID, tmpRemoteDeviceID, configDir, dataDir, localPort, remotePort) +} + +func (z *zgctl) startSyncthing(ctx context.Context, ns, podName, configDir, dataDir, kubeconfigPath string) error { + // 1. Upload config to remote Pod and start Syncthing in remote Pod. + err := z.startRemoteSyncthing(ctx, ns, podName, configDir, kubeconfigPath) + if err != nil { + return fmt.Errorf("failed to start remote syncthing: %s", err) + } + + // 2. Start local Syncthing. + return z.startLocalSyncthing(ctx, configDir, dataDir) +} + +func (z *zgctl) stopSyncthing(ctx context.Context, configDir string, workloadInfo *types.WorkloadInfo) error { + processes, err := process.Processes() + if err != nil { + return err + } + + for _, p := range processes { + // Note: Don't use `p.Name()`. See: https://github.com/shirou/gopsutil/issues/1043 + cmdline, err := p.Cmdline() + if err != nil { + log.Warnf("Failed to find command line: %s", err) + continue + } + + if (strings.Contains(cmdline, "syncthing serve") && strings.Contains(cmdline, configDir)) || + strings.Contains(cmdline, fmt.Sprintf("kubectl -n %s port-forward %s", workloadInfo.PodNamespace, workloadInfo.PodName)) { + log.Infof("Begin to kill process %s. Cmdline: %s.", p, cmdline) + err = p.Kill() + if err != nil { + log.Warnf("Failed to kill process %s: %s", p, err) + } + } + } + + return os.RemoveAll(configDir) +} + +func (z *zgctl) podPortForward(ctx context.Context, ns, podName, kubeconfigPath string, port int) error { + go func() { + portForward := fmt.Sprintf("kubectl -n %s port-forward %s %d:%d --kubeconfig %s", ns, podName, port, port, kubeconfigPath) + log.Infof("port-forward: %s", portForward) + + cmd := exec.Command("/bin/sh", "-c", portForward) + res, err := cmd.CombinedOutput() + if err != nil { + log.Errorf("Failed to do port-forward %q: %s. Msg: %s.", portForward, err, string(res)) + return + } + + log.Infof("Has done `%q`: %s.", portForward, string(res)) + }() + + return nil +} + +func (z *zgctl) configLocalSyncthing(configDir string) (deviceID string, err error) { + log.Infof("config dir: %s", configDir) + + var errBuffer bytes.Buffer + cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("%s generate --config=%s", z.syncthingBin, configDir)) + cmd.Stderr = &errBuffer + err = cmd.Run() + if err != nil { + return "", err + } + + items := strings.Split(errBuffer.String(), "Device ID:") + return strings.TrimSpace(items[1]), nil +} + +func (z *zgctl) generateServiceConfigDir(projectName, envName, serviceName string) string { + return filepath.Join(z.homeDir, "config", fmt.Sprintf("%s-%s-%s", projectName, envName, serviceName)) +} + +func (z *zgctl) adjustSyncthingConfig(localDeviceID, remoteDeviceID, configDir, dataDir string, localPort, remotePort int) error { + configTmpl, err := template.New("local").Parse(syncthingConfigTmpl) + if err != nil { + return fmt.Errorf("failed to parse config template: %s", err) + } + + localAddr := fmt.Sprintf("tcp://127.0.0.1:%d", localPort) + remoteAddr := fmt.Sprintf("tcp://127.0.0.1:%d", remotePort) + + localData := SyncthingConfig{ + SyncDirPath: dataDir, + SyncType: "sendonly", + LocalDeviceID: localDeviceID, + RemoteDeviceID: remoteDeviceID, + RemoteAddr: remoteAddr, + ListenAddr: localAddr, + } + + var localBuffer bytes.Buffer + err = configTmpl.Execute(&localBuffer, localData) + if err != nil { + return fmt.Errorf("failed to execute local config template: %s", err) + } + + err = ioutil.WriteFile(filepath.Join(configDir, "config.xml"), localBuffer.Bytes(), 0600) + if err != nil { + return fmt.Errorf("failed to overwrite local config: %s", err) + } + + scriptTmpl, err := template.New("local-script").Parse(syncthingRunTmpl) + if err != nil { + return fmt.Errorf("failed to parse script template: %s", err) + } + + localScriptData := SyncthingScript{ + ConfigDir: configDir, + DataDir: dataDir, + } + + var localScriptBuffer bytes.Buffer + err = scriptTmpl.Execute(&localScriptBuffer, localScriptData) + if err != nil { + return fmt.Errorf("failed to execute local script template: %s", err) + } + + err = ioutil.WriteFile(filepath.Join(configDir, "run.sh"), localScriptBuffer.Bytes(), 0755) + if err != nil { + return fmt.Errorf("failed to write local run.sh: %s", err) + } + + remoteData := SyncthingConfig{ + SyncDirPath: types.DevmodeWorkDir, + SyncType: "receiveonly", + LocalDeviceID: localDeviceID, + RemoteDeviceID: remoteDeviceID, + RemoteAddr: "dynamic", + ListenAddr: remoteAddr, + } + + var remoteBuffer bytes.Buffer + err = configTmpl.Execute(&remoteBuffer, remoteData) + if err != nil { + return fmt.Errorf("failed to execute remote config template: %s", err) + } + + err = ioutil.WriteFile(filepath.Join(configDir, "remote", "config.xml"), remoteBuffer.Bytes(), 0600) + if err != nil { + return fmt.Errorf("failed to overwrite remote config: %s", err) + } + + remoteScriptData := SyncthingScript{ + ConfigDir: filepath.Join(types.DevmodeWorkDir, "config"), + DataDir: types.DevmodeWorkDir, + } + + var remoteScriptBuffer bytes.Buffer + err = scriptTmpl.Execute(&remoteScriptBuffer, remoteScriptData) + if err != nil { + return fmt.Errorf("failed to execute remote script template: %s", err) + } + + return ioutil.WriteFile(filepath.Join(configDir, "remote", "run.sh"), remoteScriptBuffer.Bytes(), 0755) +} + +func (z *zgctl) genRandPort() int { + max := 60000 + min := 40000 + + return rand.Intn(max-min) + min +} + +func (z *zgctl) genSyncthingServeAddr() string { + return fmt.Sprintf("tcp://127.0.0.1:%d", z.genRandPort()) +} + +func (z *zgctl) startRemoteSyncthing(ctx context.Context, ns, podName, configDir, kubeconfigPath string) error { + cpCmd := fmt.Sprintf("kubectl --kubeconfig %s -n %s cp %s %s:%s -c %s", kubeconfigPath, ns, filepath.Join(configDir, "remote"), podName, filepath.Join(types.DevmodeWorkDir, "config"), types.IDEContainerNameSidecar) + log.Infof("cp cmd: %s", cpCmd) + + cmd := exec.Command("/bin/sh", "-c", cpCmd) + res, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to cp files to remote pod: %s. msg: %s.", err, string(res)) + } + + execCmd := fmt.Sprintf("kubectl --kubeconfig %s -n %s exec %s -c %s -- /bin/sh -c '%s >/dev/null 2>/dev/null &'", kubeconfigPath, ns, podName, types.IDEContainerNameSidecar, filepath.Join(types.DevmodeWorkDir, "config", "run.sh")) + log.Infof("exec cmd: %s", execCmd) + cmd = exec.Command("/bin/sh", "-c", execCmd) + res, err = cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run syncthing in remote pod: %s. msg: %s", err, string(res)) + } + + return nil +} + +func (z *zgctl) startLocalSyncthing(ctx context.Context, configDir, dataDir string) error { + script := fmt.Sprintf("%s >/dev/null 2>/dev/null &", filepath.Join(configDir, "run.sh")) + log.Infof("script: %s", script) + + cmd := exec.Command("/bin/sh", "-c", script) + res, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to run syncthing locally: %s. msg: %s.", err, string(res)) + } + + return nil +} + +func (z *zgctl) genZadig2K8sMapKey(projectName, envName, serviceName string) string { + return fmt.Sprintf("%s-%s-%s", projectName, envName, serviceName) +} + +func (z *zgctl) genEnvKubeconfigKey(projectName, envName string) string { + return fmt.Sprintf("%s-%s", projectName, envName) +} + +func (z *zgctl) setZadig2K8sData(projectName, envName, serviceName string, data *types.WorkloadInfo) error { + key := z.genZadig2K8sMapKey(projectName, envName, serviceName) + val, err := json.Marshal(data) + if err != nil { + return err + } + + return z.setData([]byte(key), val) +} + +func (z *zgctl) getZadig2K8sData(projectName, envName, serviceName string) (*types.WorkloadInfo, error) { + key := z.genZadig2K8sMapKey(projectName, envName, serviceName) + val, err := z.getData([]byte(key)) + if err != nil { + return nil, err + } + + var data types.WorkloadInfo + err = json.Unmarshal(val, &data) + return &data, err +} + +func (z *zgctl) setEnvKubeconfigData(projectName, envName, kubeconfigPath string) error { + key := z.genEnvKubeconfigKey(projectName, envName) + return z.setData([]byte(key), []byte(kubeconfigPath)) +} + +func (z *zgctl) getEnvKubeconfigData(projectName, envName string) (string, error) { + key := z.genEnvKubeconfigKey(projectName, envName) + val, err := z.getData([]byte(key)) + return string(val), err +} + +func (z *zgctl) setData(key, val []byte) error { + return z.db.Update(func(txn *badger.Txn) error { + return txn.Set(key, val) + }) +} + +func (z *zgctl) getData(key []byte) ([]byte, error) { + var val []byte + err := z.db.View(func(txn *badger.Txn) error { + item, err := txn.Get(key) + if err != nil { + return err + } + + val, err = item.ValueCopy(nil) + return err + }) + + return val, err +} diff --git a/pkg/shared/client/aslan/environment.go b/pkg/shared/client/aslan/environment.go index a1387054..24794dd2 100644 --- a/pkg/shared/client/aslan/environment.go +++ b/pkg/shared/client/aslan/environment.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/tool/httpclient" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) func (c *Client) ListEnvironments(projectName string) ([]*Environment, error) { @@ -120,3 +121,22 @@ func (c *Client) ListServicesStatusByEnvironment(envName, projectName string) ([ return res, nil } + +func (c *Client) PatchWorkload(projectName, envName, serviceName, devImage string) (*types.WorkloadInfo, error) { + res := &types.WorkloadInfo{} + + body := types.StartDevmodeInfo{ + DevImage: devImage, + } + url := fmt.Sprintf("/environment/environments/%s/services/%s/devmode/patch?projectName=%s", envName, serviceName, projectName) + _, err := c.Post(url, httpclient.SetBody(body), httpclient.SetResult(res)) + + return res, err +} + +func (c *Client) RecoverWorkload(projectName, envName, serviceName string) error { + url := fmt.Sprintf("/environment/environments/%s/services/%s/devmode/recover?projectName=%s", envName, serviceName, projectName) + _, err := c.Post(url) + + return err +} diff --git a/pkg/types/ide.go b/pkg/types/ide.go new file mode 100644 index 00000000..60b8fb25 --- /dev/null +++ b/pkg/types/ide.go @@ -0,0 +1,33 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type WorkloadInfo struct { + PodName string `json:"pod_name"` + PodNamespace string `json:"pod_namespace"` +} + +const IDEContainerNameDev = "dev" +const IDEContainerNameSidecar = "sidecar" + +type StartDevmodeInfo struct { + DevImage string `json:"dev_image"` +} + +const IDESidecarImage = "ccr.ccs.tencentyun.com/koderover-rc/zgctl-sidecar:20220526172433-amd64" + +const DevmodeWorkDir = "/home/zadig/" diff --git a/pkg/types/labels.go b/pkg/types/labels.go index 58598663..c9d81561 100644 --- a/pkg/types/labels.go +++ b/pkg/types/labels.go @@ -25,3 +25,5 @@ var ZadigLabelKeyGlobalOwner = fmt.Sprintf("%s/owner", ZadigDomain) const IstioLabelKeyInjection = "istio-injection" const IstioLabelValueInjection = "enabled" + +var OriginSpec = fmt.Sprintf("%s/origin", ZadigDomain) diff --git a/pkg/types/workload.go b/pkg/types/workload.go new file mode 100644 index 00000000..69d75e3b --- /dev/null +++ b/pkg/types/workload.go @@ -0,0 +1,24 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package types + +type WorkloadType string + +const ( + DeploymentWorkload WorkloadType = "deployment" + StatefulSetWorkload WorkloadType = "statefulset" +) diff --git a/pkg/util/pointer.go b/pkg/util/pointer.go index 1e9ad350..1c3ecd57 100644 --- a/pkg/util/pointer.go +++ b/pkg/util/pointer.go @@ -23,3 +23,7 @@ func GetBoolPointer(data bool) *bool { func GetStrPointer(data string) *string { return &data } + +func GetInt32Pointer(data int32) *int32 { + return &data +} -- Gitee From bf922312a8bbc01689dc863bbb8570ae65fe895d Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 1 Jun 2022 10:01:34 +0800 Subject: [PATCH 2421/2647] add public url (#1610) Signed-off-by: mouuii --- pkg/microservice/policy/core/yamlconfig/urls.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/microservice/policy/core/yamlconfig/urls.yaml b/pkg/microservice/policy/core/yamlconfig/urls.yaml index 8c7807b2..c6a88331 100644 --- a/pkg/microservice/policy/core/yamlconfig/urls.yaml +++ b/pkg/microservice/policy/core/yamlconfig/urls.yaml @@ -1,6 +1,12 @@ description: "public urls: url not need to authentication ; system_admin urls: urls that only system admin has permission ; project_admin: urls that project admin has permission" exemption_urls: public: + - endpoint: api/aslan/environment/environments/?*/services/?*/devmode/patch + methods: + - POST + - endpoint: api/aslan/environment/environments/?*/services/?*/devmode/recover + methods: + - POST - endpoint: api/v1/permission/project/?* methods: - GET -- Gitee From 8edfd4d45a2b3b578a7b5103fa4f5748626af75b Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 1 Jun 2022 14:33:13 +0800 Subject: [PATCH 2422/2647] add repo namespace resp (#1614) Signed-off-by: allenshen --- .../aslan/core/workflow/service/workflow/pipeline_task.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 73d4b691..152a29a5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -846,6 +846,9 @@ func EnsureTestingResp(mt *commonmodels.Testing) { if len(mt.Repos) == 0 { mt.Repos = make([]*types.Repository, 0) } + for _, repo := range mt.Repos { + repo.RepoNamespace = repo.GetRepoNamespace() + } if mt.PreTest != nil { if len(mt.PreTest.Installs) == 0 { -- Gitee From 8583657ab55a2c5221e3f248bb130ffdbd908bb5 Mon Sep 17 00:00:00 2001 From: flyer Date: Wed, 1 Jun 2022 15:31:54 +0800 Subject: [PATCH 2423/2647] support uploading ide sidecar image (#1616) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- Makefile | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 9129336a..a1a90631 100644 --- a/Makefile +++ b/Makefile @@ -13,12 +13,12 @@ ALL_PUSH=$(TARGETS:=.push) PLATFORMS=darwin linux windows ARCHITECTURES=amd64 arm64 -all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 build-zgctl-all-platforms zgctl-sidecar.amd64 -all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 +all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 build-zgctl-all-platforms zgctl-sidecar.build.amd64 +all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 zgctl-sidecar.upload.amd64 -all.amd64: $(ALL_IMAGES:=.amd64) resource-server.build.amd64 +all.amd64: $(ALL_IMAGES:=.amd64) resource-server.build.amd64 zgctl-sidecar.build.amd64 all.arm64: $(ALL_IMAGES:=.amd64) resource-server.build.arm64 -allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) resource-server.upload.amd64 +allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) resource-server.upload.amd64 zgctl-sidecar.upload.amd64 allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.upload.arm64 %.push.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 @@ -83,10 +83,14 @@ build-zgctl-all-platforms: pre-build $(foreach GOOS, $(PLATFORMS),\ $(foreach GOARCH, $(ARCHITECTURES), $(shell export GOOS=$(GOOS); export GOARCH=$(GOARCH); CGO_ENABLED=0 go build -v -o bin/zgctl-$(GOOS)-$(GOARCH) cmd/zgctl/main.go))) -zgctl-sidecar.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zgctl-sidecar:${VERSION}-amd64 -zgctl-sidecar.amd64: zgctl-sidecar.amd64 +zgctl-sidecar.build.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zgctl-sidecar:${VERSION}-amd64 +zgctl-sidecar.build.amd64: @docker build -f docker/service/syncthing.Dockerfile --tag ${MAKE_IMAGE} . +zgctl-sidecar.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zgctl-sidecar:${VERSION}-amd64 +zgctl-sidecar.upload.amd64: zgctl-sidecar.build.amd64 + @docker push ${MAKE_IMAGE} + .PHONY: clean clean: @rm -rf docker/dist -- Gitee From bb7283dc528c5e86b32a6488ca3f4666c933ebd1 Mon Sep 17 00:00:00 2001 From: ddh27 <30834299+ddh27@users.noreply.github.com> Date: Wed, 1 Jun 2022 16:45:59 +0800 Subject: [PATCH 2424/2647] Fixed failure to get service instance details due to RenderSet version error (#1619) Signed-off-by: ddh27 --- pkg/microservice/aslan/core/environment/service/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index cd364d8f..e406032c 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -283,7 +283,7 @@ func GetService(envName, productName, serviceName string, workLoadType string, l ) } - renderSetFindOpt := &commonrepo.RenderSetFindOption{Name: env.Render.Name, Revision: env.Render.Revision} + renderSetFindOpt := &commonrepo.RenderSetFindOption{Name: env.Render.Name, Revision: service.Render.Revision} rs, err := commonrepo.NewRenderSetColl().Find(renderSetFindOpt) if err != nil { log.Errorf("find renderset[%s] error: %v", service.Render.Name, err) -- Gitee From 869e57d29e09ee7bb55e2d509d9e8a2536155b1a Mon Sep 17 00:00:00 2001 From: guoyu Date: Wed, 1 Jun 2022 17:28:36 +0800 Subject: [PATCH 2425/2647] run first new workflow Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 67 +++++++-------- .../common/repository/models/workflow_v4.go | 14 +-- .../aslan/core/workflow/handler/router.go | 8 ++ .../core/workflow/handler/workflow_task_v4.go | 38 ++++++++ .../workflow/service/jobcontroller/job.go | 31 +++---- .../service/jobcontroller/job_freestyle.go | 72 ++++++++++++---- .../service/jobcontroller/kubernetes.go | 86 +++++++++++++++++++ .../workflow/service/workflow/workflow_v4.go | 17 ++++ .../jobexcutor/core/service/job.go | 15 +--- .../jobexcutor/core/service/step/step.go | 35 ++++++++ .../core/service/step/step_shell.go | 13 ++- .../jobexcutor/excutor/excutor.go | 8 +- 12 files changed, 306 insertions(+), 98 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index e6a0b5c6..b2070ec8 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -23,25 +23,24 @@ import ( ) type WorkflowTask struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - WorkflowName string `bson:"workflow_name" json:"workflow_name"` - Type config.PipelineType `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - Stages []*StageTask `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + Status config.Status `bson:"status" json:"status,omitempty"` + Description string `bson:"description,omitempty" json:"description,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Stages []*StageTask `bson:"stages" json:"stages"` + ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + TeamName string `bson:"team,omitempty" json:"team,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` } func (WorkflowTask) TableName() string { @@ -61,26 +60,26 @@ type StageTask struct { } type JobTask struct { - Name string `bson:"name" json:"name"` - JobType string `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - Error string `bson:"error" json:"error"` - Properties *JobProperties `bson:"properties" json:"properties"` - Steps []*StepTask `bson:"steps" json:"steps"` - Outputs []*Output `bson:"outputs" son:"outputs"` + Name string `bson:"name" json:"name"` + JobType string `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Error string `bson:"error" json:"error"` + Properties JobProperties `bson:"properties" json:"properties"` + Steps []*StepTask `bson:"steps" json:"steps"` + Outputs []*Output `bson:"outputs" son:"outputs"` } type StepTask struct { - Name string `bson:"name" json:"name"` - JobName string `bson:"job_name" json:"job_name"` - Error string `bson:"error" json:"error"` - StepType config.StepType `bson:"step_type" json:"step_type"` + Name string `bson:"name" json:"name" yaml:"name"` + JobName string `bson:"job_name" json:"job_name" yaml:"job_name"` + Error string `bson:"error" json:"error" yaml:"error"` + StepType config.StepType `bson:"type" json:"type" yaml:"type"` // step input params,differ form steps - Spec interface{} `bson:"Spec" json:"Spec"` + Spec interface{} `bson:"spec" json:"spec" yaml:"spec"` // step output results,like testing results,differ form steps - Result interface{} `bson:"result" json:"result"` + Result interface{} `bson:"result" json:"result" yaml:"result"` } type WorkflowTaskCtx struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 647a3f96..4a4c3f13 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -28,12 +28,12 @@ type WorkflowStage struct { } type Job struct { - Name string `bson:"name" json:"name"` - Properties *JobProperties `bson:"properties" json:"properties"` - JobType string `bson:"type" json:"type"` - Spec interface{} `bson:"spec" json:"spec"` - Steps []*Step `bson:"steps" json:"steps"` - Outputs []*Output `bson:"outputs" son:"outputs"` + Name string `bson:"name" json:"name"` + Properties JobProperties `bson:"properties" json:"properties"` + JobType string `bson:"type" json:"type"` + Spec interface{} `bson:"spec" json:"spec"` + Steps []*Step `bson:"steps" json:"steps"` + Outputs []*Output `bson:"outputs" son:"outputs"` } type JobProperties struct { @@ -42,7 +42,7 @@ type JobProperties struct { ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` ClusterID string `bson:"cluster_id" json:"cluster_id"` BuildOS string `bson:"build_os" json:"build_os"` - Namepace string `bson:"namespace" json:"namespace"` + Namespace string `bson:"namespace" json:"namespace"` Args []*KeyVal `bson:"args" json:"args"` } diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index e7c2db2f..ee014c44 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -163,6 +163,14 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) } + // --------------------------------------------------------------------------------------- + // workflow v4 任务接口 + // --------------------------------------------------------------------------------------- + taskV4 := router.Group("v4/workflowtask") + { + taskV4.POST("", CreateWorkflowTaskV4) + } + bundles := router.Group("bundle-resources") { bundles.GET("", GetBundleResources) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go new file mode 100644 index 00000000..5cd769c7 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -0,0 +1,38 @@ +package handler + +import ( + "bytes" + "encoding/json" + "io/ioutil" + + "github.com/gin-gonic/gin" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +func CreateWorkflowTaskV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.WorkflowTask) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowTaskv4 c.GetRawData() err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreateWorkflowTaskv4 json.Unmarshal err : %s", err) + } + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = workflow.CreateWorkflowV4(ctx.UserName, args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index 25286874..3f159686 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -7,6 +7,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" "go.uber.org/zap" ) @@ -15,15 +16,25 @@ type JobCtl interface { } func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { - ctx, cancel := context.WithCancel(ctx) job.Status = config.StatusRunning job.StartTime = time.Now().Unix() + // set default timeout + if job.Properties.Timeout <= 0 { + job.Properties.Timeout = 600 + } + // set default resource + if job.Properties.ResourceRequest == setting.Request("") { + job.Properties.ResourceRequest = setting.MinRequest + } + // set default resource + if job.Properties.ClusterID == "" { + job.Properties.ClusterID = setting.LocalClusterID + } logger.Infof("start job: %s,status: %s", job.Name, job.Status) defer func() { job.EndTime = time.Now().Unix() logger.Infof("finish job: %s,status: %s", job.Name, job.Status) ack() - cancel() }() var jobCtl JobCtl switch job.JobType { @@ -32,21 +43,7 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm default: jobCtl = NewFreestyleJobCtl(job, workflowCtx, globalContext, ack, logger) } - done := make(chan struct{}, 1) - go func() { - jobCtl.Run(ctx) - done <- struct{}{} - }() - select { - case <-done: - return - case <-ctx.Done(): - job.Status = config.StatusCancelled - return - case <-time.After(time.Second * time.Duration(job.Properties.Timeout)): - job.Status = config.StatusTimeout - return - } + jobCtl.Run(ctx) } func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 755559c6..a5266087 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -3,7 +3,6 @@ package jobcontroller import ( "context" "fmt" - "math/rand" "sync" "time" @@ -11,6 +10,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" "go.uber.org/zap" "gopkg.in/yaml.v3" @@ -23,6 +23,7 @@ type FreestyleJobCtl struct { jobContext *sync.Map globalContext *sync.Map logger *zap.SugaredLogger + kubeclient crClient.Client ack func() } @@ -37,30 +38,29 @@ func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.Wor } func (c *FreestyleJobCtl) Run(ctx context.Context) { - r := rand.Intn(10) - time.Sleep(time.Duration(r) * time.Second) - c.logger.Infof("job: %s sleep %d seccod", c.job.Name, r) - c.job.Status = config.StatusPassed + c.run(ctx) + c.wait(ctx) + c.complete(ctx) } func (c *FreestyleJobCtl) run(ctx context.Context) { // get kube client - var crClient crClient.Client - var err error hubServerAddr := config.HubServerAddress() switch c.job.Properties.ClusterID { case setting.LocalClusterID: - c.job.Properties.Namepace = zadigconfig.Namespace() + c.job.Properties.Namespace = zadigconfig.Namespace() + c.kubeclient = krkubeclient.Client() default: - c.job.Properties.Namepace = setting.AttachedClusterNamespace + c.job.Properties.Namespace = setting.AttachedClusterNamespace - crClient, _, _, err = GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) + crClient, _, _, err := GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) if err != nil { c.job.Status = config.StatusFailed c.job.Error = err.Error() c.job.EndTime = time.Now().Unix() return } + c.kubeclient = crClient } // decide which docker host to use. @@ -69,7 +69,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { // dockerHost := dockerhosts.GetBestHost(common.ClusterID(c.job.Properties.ClusterID), "") // TODO: inject vars like task_id and etc, passing them into c.job.Properties.Args - envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namepace, IsCredential: false} + envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namespace, IsCredential: false} c.job.Properties.Args = append(c.job.Properties.Args, envNameVar) jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx, c.globalContext)) @@ -86,7 +86,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { TaskID: c.workflowCtx.TaskID, JobType: string(c.job.JobType), } - if err := ensureDeleteConfigMap(c.job.Properties.Namepace, jobLabel, crClient); err != nil { + if err := ensureDeleteConfigMap(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { c.logger.Error(err) c.job.Status = config.StatusFailed c.job.Error = err.Error() @@ -94,7 +94,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { } if err := createJobConfigMap( - c.job.Properties.Namepace, c.job.Name, jobLabel, string(jobCtxBytes), crClient); err != nil { + c.job.Properties.Namespace, c.job.Name, jobLabel, string(jobCtxBytes), c.kubeclient); err != nil { msg := fmt.Sprintf("createJobConfigMap error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed @@ -104,10 +104,12 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { c.logger.Infof("succeed to create cm for job %s", c.job.Name) - jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) + // TODO: do not use default image + jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test1" + // jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW - job, err := buildJob(c.job.JobType, jobImage, c.job.Name, c.job.Properties.ClusterID, c.job.Properties.Namepace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) + job, err := buildJob(c.job.JobType, jobImage, c.job.Name, c.job.Properties.ClusterID, c.job.Properties.Namespace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) if err != nil { msg := fmt.Sprintf("create job context error: %v", err) c.logger.Error(msg) @@ -116,9 +118,9 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { return } - job.Namespace = c.job.Properties.Namepace + job.Namespace = c.job.Properties.Namespace - if err := ensureDeleteJob(c.job.Properties.Namepace, jobLabel, crClient); err != nil { + if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { msg := fmt.Sprintf("delete job error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed @@ -135,7 +137,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { // p.SetBuildStatusCompleted(config.StatusFailed) // return // } - if err := updater.CreateJob(job, crClient); err != nil { + if err := updater.CreateJob(job, c.kubeclient); err != nil { msg := fmt.Sprintf("create job error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed @@ -144,3 +146,37 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { } c.logger.Infof("succeed to create job %s", c.job.Name) } + +func (c *FreestyleJobCtl) wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, int(c.job.Properties.Timeout), c.job.Properties.Namespace, c.job.Name, c.kubeclient, c.logger) + c.job.Status = status +} + +func (c *FreestyleJobCtl) complete(ctx context.Context) { + jobLabel := &JobLabel{ + WorkflowName: c.workflowCtx.WorkflowName, + TaskID: c.workflowCtx.TaskID, + JobType: string(c.job.JobType), + } + + // 清理用户取消和超时的任务 + defer func() { + if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + c.job.Error = err.Error() + } + if err := ensureDeleteConfigMap(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + c.job.Error = err.Error() + } + }() + + // err := saveContainerLog(pipelineTask, p.KubeNamespace, "", c.FileName, jobLabel, c.kubeclient) + // if err != nil { + // p.Log.Error(err) + // p.Task.Error = err.Error() + // return + // } + + // p.Task.LogFile = p.FileName +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 2b02eaf6..e588b84e 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -1,11 +1,14 @@ package jobcontroller import ( + "context" "fmt" "path" "strconv" "strings" + "time" + "go.uber.org/zap" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -15,10 +18,13 @@ import ( "k8s.io/client-go/rest" crClient "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/shared/kube/wrapper" + "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" ) @@ -294,3 +300,83 @@ func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSp } func int32Ptr(i int32) *int32 { return &i } + +func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, kubeClient crClient.Client, xl *zap.SugaredLogger) (status config.Status) { + xl.Infof("wait job to start: %s/%s", namespace, jobName) + timeout := time.After(time.Duration(taskTimeout) * time.Second) + podTimeout := time.After(120 * time.Second) + + xl.Infof("Timeout of preparing Pod: %s. Timeout of running task: %s.", 120*time.Second, time.Duration(taskTimeout)*time.Second) + + var started bool + for { + select { + case <-ctx.Done(): + return config.StatusCancelled + + case <-podTimeout: + return config.StatusTimeout + default: + job, _, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil { + xl.Errorf("get job failed, namespace:%s, jobName:%s, err:%v", namespace, jobName, err) + } + if job != nil { + started = job.Status.Active > 0 + } + } + if started { + break + } + + time.Sleep(time.Second) + } + + // 等待job 运行结束 + xl.Infof("wait job to end: %s %s", namespace, jobName) + for { + select { + case <-ctx.Done(): + return config.StatusCancelled + + case <-timeout: + return config.StatusTimeout + + default: + job, found, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil || !found { + xl.Errorf("failed to get pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + // pod is still running + if job.Status.Active != 0 { + + pods, err := getter.ListPods(namespace, labels.Set{"job-name": jobName}.AsSelector(), kubeClient) + if err != nil { + xl.Errorf("failed to find pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + + for _, pod := range pods { + ipod := wrapper.Pod(pod) + if ipod.Pending() { + continue + } + if ipod.Failed() { + return config.StatusFailed + } + if ipod.Succeeded() { + return config.StatusPassed + } + } + + } else if job.Status.Succeeded != 0 { + return config.StatusPassed + } else { + return config.StatusFailed + } + } + + time.Sleep(time.Second * 1) + } +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go new file mode 100644 index 00000000..d85365ee --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -0,0 +1,17 @@ +package workflow + +import ( + "context" + + "go.uber.org/zap" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" +) + +func CreateWorkflowV4(user string, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { + ctl := workflowcontroller.NewWorkflowController(workflowTask, logger) + ctx := context.Background() + go ctl.Run(ctx, 2) + return nil +} diff --git a/pkg/microservice/jobexcutor/core/service/job.go b/pkg/microservice/jobexcutor/core/service/job.go index 5ab73a50..b17c0028 100644 --- a/pkg/microservice/jobexcutor/core/service/job.go +++ b/pkg/microservice/jobexcutor/core/service/job.go @@ -94,19 +94,6 @@ func (j *Job) getUserEnvs() []string { } func (j *Job) Run(ctx context.Context) error { - for _, stepInfo := range j.Ctx.Steps { - var stepInstance step.Step - var err error - switch stepInfo.StepType { - case "shell": - stepInstance, err = step.NewShellStep(ctx, j.ActiveWorkspace, j.getUserEnvs(), j.Ctx.SecretEnvs) - if err != nil { - return err - } - } - if err := stepInstance.Run(ctx); err != nil { - return err - } - } + step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.Ctx.Envs, j.Ctx.SecretEnvs) return nil } diff --git a/pkg/microservice/jobexcutor/core/service/step/step.go b/pkg/microservice/jobexcutor/core/service/step/step.go index bd097cc5..b2ccba6d 100644 --- a/pkg/microservice/jobexcutor/core/service/step/step.go +++ b/pkg/microservice/jobexcutor/core/service/step/step.go @@ -6,8 +6,10 @@ import ( "fmt" "io" "strings" + "time" "github.com/koderover/zadig/pkg/microservice/jobexcutor/config" + "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service/meta" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) @@ -16,6 +18,39 @@ type Step interface { Run(ctx context.Context) error } +func RunSteps(ctx context.Context, steps []*meta.Step, workspace string, envs, secretEnvs []string) error { + for _, stepInfo := range steps { + if err := runStep(ctx, stepInfo, workspace, envs, secretEnvs); err != nil { + return err + } + } + return nil +} +func runStep(ctx context.Context, step *meta.Step, workspace string, envs, secretEnvs []string) error { + start := time.Now() + var stepInstance Step + var err error + log.Infof("====================== %s Start ======================", step.Name) + defer func() { + log.Infof("====================== %s End. Duration: %.2f seconds ======================", step.Name, time.Since(start).Seconds()) + }() + switch step.StepType { + case "shell": + stepInstance, err = NewShellStep(step.Spec, workspace, envs, secretEnvs) + if err != nil { + return err + } + default: + err := fmt.Errorf("step type: %s does not match any known type", step.StepType) + log.Error(err) + return err + } + if err := stepInstance.Run(ctx); err != nil { + return err + } + return nil +} + func prepareScriptsEnv() []string { scripts := []string{} scripts = append(scripts, "eval $(ssh-agent -s) > /dev/null") diff --git a/pkg/microservice/jobexcutor/core/service/step/step_shell.go b/pkg/microservice/jobexcutor/core/service/step/step_shell.go index f19bf80a..85122b6a 100644 --- a/pkg/microservice/jobexcutor/core/service/step/step_shell.go +++ b/pkg/microservice/jobexcutor/core/service/step/step_shell.go @@ -12,6 +12,7 @@ import ( "github.com/koderover/zadig/pkg/types/step" "github.com/koderover/zadig/pkg/util" + "gopkg.in/yaml.v3" ) type ShellStep struct { @@ -22,11 +23,15 @@ type ShellStep struct { } func NewShellStep(spec interface{}, workspace string, envs, secretEnvs []string) (*ShellStep, error) { - specImpl, ok := (spec).(*step.StepShellSpec) - if !ok { - return nil, fmt.Errorf("convert %+v to shell spec failed", spec) + shellStep := &ShellStep{workspace: workspace, envs: envs, secretEnvs: secretEnvs} + yamlBytes, err := yaml.Marshal(spec) + if err != nil { + return shellStep, fmt.Errorf("marshal spec %+v failed", spec) + } + if err := yaml.Unmarshal(yamlBytes, &shellStep.spec); err != nil { + return shellStep, fmt.Errorf("unmarshal spec %s to shell spec failed", yamlBytes) } - return &ShellStep{spec: specImpl, workspace: workspace, envs: envs, secretEnvs: secretEnvs}, nil + return shellStep, nil } func (s *ShellStep) Run(ctx context.Context) error { diff --git a/pkg/microservice/jobexcutor/excutor/excutor.go b/pkg/microservice/jobexcutor/excutor/excutor.go index ef599cfc..3862a28d 100644 --- a/pkg/microservice/jobexcutor/excutor/excutor.go +++ b/pkg/microservice/jobexcutor/excutor/excutor.go @@ -37,8 +37,8 @@ func Execute(ctx context.Context) error { start := time.Now() + excutor := "job-excutor" var err error - var reaperType types.ReaperType defer func() { resultMsg := types.JobSuccess if err != nil { @@ -47,7 +47,7 @@ func Execute(ctx context.Context) error { } log.Infof("Job Status: %s", resultMsg) - log.Infof("====================== %s End. Duration: %.2f seconds ======================", reaperType, time.Since(start).Seconds()) + log.Infof("====================== %s End. Duration: %.2f seconds ======================", excutor, time.Since(start).Seconds()) }() var j *job.Job @@ -56,8 +56,8 @@ func Execute(ctx context.Context) error { return err } - log.Infof("====================== %s Start ======================", reaperType) - if err := j.Run(ctx); err != nil { + log.Infof("====================== %s Start ======================", excutor) + if err = j.Run(ctx); err != nil { return err } return nil -- Gitee From 8b7aa7ea95553c0b60156429dee9fdb2ffee12aa Mon Sep 17 00:00:00 2001 From: dyliu Date: Wed, 1 Jun 2022 17:58:41 +0800 Subject: [PATCH 2426/2647] Fix the bug of deleting cloud host builds (#1618) * Fix the bug of deleting cloud host builds Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi --- go.mod | 2 +- go.sum | 2 +- .../aslan/core/build/service/build.go | 52 ++++++++++++++++++- .../core/common/repository/models/service.go | 2 +- .../core/common/repository/mongodb/service.go | 9 ++-- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 04c57aaa..7b5df614 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/coocood/freecache v1.1.0 github.com/coreos/go-oidc/v3 v3.0.0 github.com/dexidp/dex v0.0.0-20210802203454-3fac2ab6bc3b - github.com/dgraph-io/badger/v3 v3.2103.2 // indirect + github.com/dgraph-io/badger/v3 v3.2103.2 github.com/docker/distribution v2.7.1+incompatible github.com/docker/docker v20.10.11+incompatible github.com/docker/go-connections v0.4.0 diff --git a/go.sum b/go.sum index e3ad83eb..f3d6d901 100644 --- a/go.sum +++ b/go.sum @@ -469,6 +469,7 @@ github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= @@ -1454,7 +1455,6 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 00d5f545..f595efef 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -218,9 +218,12 @@ func UpdateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog return err } + if err = updateCvmService(build, existed); err != nil { + log.Warnf("failed to update cvm service,err:%s", err) + } + build.UpdateBy = username build.UpdateTime = time.Now().Unix() - if err := commonrepo.NewBuildColl().Update(build); err != nil { log.Errorf("[Build.Upsert] %s error: %v", build.Name, err) return e.ErrUpdateBuildModule.AddErr(err) @@ -229,6 +232,53 @@ func UpdateBuild(username string, build *commonmodels.Build, log *zap.SugaredLog return nil } +func updateCvmService(currentBuild, oldBuild *commonmodels.Build) error { + deleteServices := sets.NewString() + currentServiceModuleKey := sets.NewString() + for _, currentServiceModule := range currentBuild.Targets { + currentServiceModuleKey.Insert(fmt.Sprintf("%s-%s-%s", currentServiceModule.ProductName, currentServiceModule.ServiceName, currentServiceModule.ServiceModule)) + } + + for _, oldServiceModule := range oldBuild.Targets { + if !currentServiceModuleKey.Has(fmt.Sprintf("%s-%s-%s", oldServiceModule.ProductName, oldServiceModule.ServiceName, oldServiceModule.ServiceModule)) { + deleteServices.Insert(oldServiceModule.ServiceName) + } + } + + for _, serviceName := range deleteServices.List() { + opt := &commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Type: setting.PMDeployType, + ProductName: currentBuild.ProductName, + ExcludeStatus: setting.ProductStatusDeleting, + } + + resp, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + continue + } + + serviceTemplate := fmt.Sprintf(setting.ServiceTemplateCounterName, resp.ServiceName, resp.ProductName) + rev, err := commonrepo.NewCounterColl().GetNextSeq(serviceTemplate) + if err != nil { + return err + } + resp.Revision = rev + + if err := commonrepo.NewServiceColl().Delete(resp.ServiceName, resp.Type, resp.ProductName, setting.ProductStatusDeleting, resp.Revision); err != nil { + log.Errorf("failed to delete service %s, error: %s", resp.ServiceName, err) + return err + } + resp.BuildName = "" + if err := commonrepo.NewServiceColl().Create(resp); err != nil { + log.Errorf("failed to delete service %s, error: %s", resp.ServiceName, err) + return err + } + } + + return nil +} + func DeleteBuild(name, productName string, log *zap.SugaredLogger) error { if len(name) == 0 { return e.ErrDeleteBuildModule.AddDesc("empty name") diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 6bec2ed4..0bcdd757 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -53,7 +53,7 @@ type Service struct { GerritPath string `bson:"gerrit_path,omitempty" json:"gerrit_path,omitempty"` GerritCodeHostID int `bson:"gerrit_codeHost_id,omitempty" json:"gerrit_codeHost_id,omitempty"` GiteePath string `bson:"gitee_path,omitempty" json:"gitee_path,omitempty"` - BuildName string `bson:"build_name,omitempty" json:"build_name,omitempty"` + BuildName string `bson:"build_name" json:"build_name"` HelmChart *HelmChart `bson:"helm_chart,omitempty" json:"helm_chart,omitempty"` EnvConfigs []*EnvConfig `bson:"env_configs,omitempty" json:"env_configs,omitempty"` EnvStatuses []*EnvStatus `bson:"env_statuses,omitempty" json:"env_statuses,omitempty"` diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/service.go b/pkg/microservice/aslan/core/common/repository/mongodb/service.go index e503bf68..6c48cd88 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/service.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/service.go @@ -572,9 +572,7 @@ func (c *ServiceColl) ListMaxRevisions(opt *ServiceListOption) ([]*models.Servic if opt.ServiceName != "" { preMatch["service_name"] = opt.ServiceName } - if opt.BuildName != "" { - preMatch["build_name"] = opt.BuildName - } + if opt.Source != "" { preMatch["source"] = opt.Source } @@ -586,6 +584,9 @@ func (c *ServiceColl) ListMaxRevisions(opt *ServiceListOption) ([]*models.Servic } // post options (anything that changes over revision should be added in post options) + if opt.BuildName != "" { + postMatch["build_name"] = opt.BuildName + } if opt.Visibility != "" { postMatch["visibility"] = opt.Visibility } @@ -611,6 +612,7 @@ func (c *ServiceColl) ListMaxRevisions(opt *ServiceListOption) ([]*models.Servic } postMatch["_id"] = bson.M{"$in": srs} } + } return c.listMaxRevisions(preMatch, postMatch) @@ -681,6 +683,7 @@ func (c *ServiceColl) listMaxRevisions(preMatch, postMatch bson.M) ([]*models.Se }, "service_id": bson.M{"$last": "$_id"}, "visibility": bson.M{"$last": "$visibility"}, + "build_name": bson.M{"$last": "$build_name"}, }, }, } -- Gitee From 6fdce8a8ddbde8d6d96abb86067a0cfe33971f05 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 1 Jun 2022 18:08:33 +0800 Subject: [PATCH 2427/2647] pod add nodename (#1622) Signed-off-by: mouuii --- pkg/shared/kube/resource/pod.go | 2 ++ pkg/shared/kube/wrapper/pod.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/shared/kube/resource/pod.go b/pkg/shared/kube/resource/pod.go index d74d6537..a62a7a8c 100644 --- a/pkg/shared/kube/resource/pod.go +++ b/pkg/shared/kube/resource/pod.go @@ -25,6 +25,8 @@ type Pod struct { IP string `json:"ip"` Labels map[string]string `json:"labels"` ContainerStatuses []Container `json:"containers"` + NodeName string `json:"node_name"` + HostIP string `json:"host_ip"` } type Container struct { diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index 32902ec2..be001989 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -126,6 +126,8 @@ func (w *pod) Resource() *resource.Pod { IP: w.Status.PodIP, Labels: w.Labels, ContainerStatuses: []resource.Container{}, + NodeName: w.Spec.NodeName, + HostIP: w.Status.HostIP, } if len(w.OwnerReferences) > 0 { p.Kind = w.OwnerReferences[0].Kind -- Gitee From 6d423895fe02cc8c6d933b1478ab68fb29b7da79 Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Wed, 1 Jun 2022 18:09:19 +0800 Subject: [PATCH 2428/2647] first time can be null,use event time (#1621) Signed-off-by: mouuii --- pkg/shared/kube/wrapper/event.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/shared/kube/wrapper/event.go b/pkg/shared/kube/wrapper/event.go index a2b758f5..1943e387 100644 --- a/pkg/shared/kube/wrapper/event.go +++ b/pkg/shared/kube/wrapper/event.go @@ -43,7 +43,7 @@ func (w *event) Unwrap() *corev1.Event { } func (w *event) Resource() *resource.Event { - return &resource.Event{ + e := &resource.Event{ Reason: w.Reason, Message: w.Message, FirstSeen: w.FirstTimestamp.Unix(), @@ -51,4 +51,9 @@ func (w *event) Resource() *resource.Event { Count: w.Count, Type: w.Type, } + if (w.FirstTimestamp.IsZero() || w.LastTimestamp.IsZero()) && !w.EventTime.IsZero() { + e.FirstSeen = w.EventTime.Unix() + e.LastSeen = w.EventTime.Unix() + } + return e } -- Gitee From 9954b98eeed322f77c52f7567be24eaaa6e87a1b Mon Sep 17 00:00:00 2001 From: guoyu Date: Wed, 1 Jun 2022 18:28:25 +0800 Subject: [PATCH 2429/2647] add copyright Signed-off-by: guoyu --- cmd/jobexcutor/main.go | 16 ++++++++++++++++ .../core/common/repository/models/workflow_v4.go | 16 ++++++++++++++++ .../core/workflow/handler/workflow_task_v4.go | 16 ++++++++++++++++ .../core/workflow/service/jobcontroller/job.go | 16 ++++++++++++++++ .../service/jobcontroller/job_excutor.go | 16 ++++++++++++++++ .../service/jobcontroller/job_freestyle.go | 16 ++++++++++++++++ .../workflow/service/jobcontroller/kubernetes.go | 16 ++++++++++++++++ .../core/workflow/service/jobcontroller/types.go | 16 ++++++++++++++++ .../workflow/service/workflow/workflow_v4.go | 16 ++++++++++++++++ .../workflow/service/workflowcontroller/queue.go | 16 ++++++++++++++++ .../workflow/service/workflowcontroller/stage.go | 16 ++++++++++++++++ .../service/workflowcontroller/stage_custom.go | 16 ++++++++++++++++ .../service/workflowcontroller/workflow.go | 16 ++++++++++++++++ pkg/microservice/jobexcutor/core/service/job.go | 16 ++++++++++++++++ .../jobexcutor/core/service/meta/types.go | 16 ++++++++++++++++ .../jobexcutor/core/service/step/step.go | 16 ++++++++++++++++ .../jobexcutor/core/service/step/step_shell.go | 16 ++++++++++++++++ pkg/types/step/step.go | 16 ++++++++++++++++ 18 files changed, 288 insertions(+) diff --git a/cmd/jobexcutor/main.go b/cmd/jobexcutor/main.go index 6f6e7928..0206d8d7 100644 --- a/cmd/jobexcutor/main.go +++ b/cmd/jobexcutor/main.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package main import ( diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 4a4c3f13..ecd0f34c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package models import ( diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index 5cd769c7..319bc405 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package handler import ( diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index 3f159686..a854d43f 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package jobcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go index e4daed27..0eef8557 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package jobcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index a5266087..00dd34e2 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package jobcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index e588b84e..6db5f803 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package jobcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go index cf05ea21..04f0373a 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package jobcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index d85365ee..87bb1279 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflow import ( diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go index 082f0023..90d6a0e8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go @@ -1 +1,17 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflowcontroller diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 68ebf5a9..2e505be2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflowcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go index 5c5ef2bb..97cc1ef2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflowcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 144b0071..2a1494a7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package workflowcontroller import ( diff --git a/pkg/microservice/jobexcutor/core/service/job.go b/pkg/microservice/jobexcutor/core/service/job.go index b17c0028..c7674bd4 100644 --- a/pkg/microservice/jobexcutor/core/service/job.go +++ b/pkg/microservice/jobexcutor/core/service/job.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package job import ( diff --git a/pkg/microservice/jobexcutor/core/service/meta/types.go b/pkg/microservice/jobexcutor/core/service/meta/types.go index a7c0c72f..b2e475c7 100644 --- a/pkg/microservice/jobexcutor/core/service/meta/types.go +++ b/pkg/microservice/jobexcutor/core/service/meta/types.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package meta type JobContext struct { diff --git a/pkg/microservice/jobexcutor/core/service/step/step.go b/pkg/microservice/jobexcutor/core/service/step/step.go index b2ccba6d..4c056a21 100644 --- a/pkg/microservice/jobexcutor/core/service/step/step.go +++ b/pkg/microservice/jobexcutor/core/service/step/step.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package step import ( diff --git a/pkg/microservice/jobexcutor/core/service/step/step_shell.go b/pkg/microservice/jobexcutor/core/service/step/step_shell.go index 85122b6a..f16c98d3 100644 --- a/pkg/microservice/jobexcutor/core/service/step/step_shell.go +++ b/pkg/microservice/jobexcutor/core/service/step/step_shell.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package step import ( diff --git a/pkg/types/step/step.go b/pkg/types/step/step.go index d6d947a9..04ee326d 100644 --- a/pkg/types/step/step.go +++ b/pkg/types/step/step.go @@ -1,3 +1,19 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package step type StepShellSpec struct { -- Gitee From ee87b7de7de0121c12248f85e7133af96eb8bcd2 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 2 Jun 2022 11:28:20 +0800 Subject: [PATCH 2430/2647] parameter adjustment Signed-off-by: guoyu --- .../core/common/repository/models/wokflow_task_v4.go | 12 ++++-------- .../core/common/repository/models/workflow_v4.go | 10 +++++----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index b2070ec8..028cd89f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -27,16 +27,13 @@ type WorkflowTask struct { TaskID int64 `bson:"task_id" json:"task_id"` WorkflowName string `bson:"workflow_name" json:"workflow_name"` Status config.Status `bson:"status" json:"status,omitempty"` - Description string `bson:"description,omitempty" json:"description,omitempty"` TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` Stages []*StageTask `bson:"stages" json:"stages"` - ReqID string `bson:"req_id,omitempty" json:"req_id,omitempty"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - TeamName string `bson:"team,omitempty" json:"team,omitempty"` + ProjectName string `bson:"project_name,omitempty" json:"project_name,omitempty"` IsDeleted bool `bson:"is_deleted" json:"is_deleted"` IsArchived bool `bson:"is_archived" json:"is_archived"` Error string `bson:"error,omitempty" json:"error,omitempty"` @@ -53,10 +50,9 @@ type StageTask struct { StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` // default is custom defined by user. - StageType string `bson:"type" json:"type"` - Parallel bool `bson:"parallel" json:"parallel"` - Spec interface{} `bson:"spec" json:"spec"` - Jobs []*JobTask `bson:"jobs" json:"jobs"` + StageType string `bson:"type" json:"type"` + Parallel bool `bson:"parallel" json:"parallel"` + Jobs []*JobTask `bson:"jobs" json:"jobs"` } type JobTask struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index ecd0f34c..a0a3595b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -38,9 +38,8 @@ type WorkflowV4 struct { type WorkflowStage struct { Name string `bson:"name" json:"name"` // default is custom defined by user. - StageType string `bson:"type" json:"type"` - Spec interface{} `bson:"spec" json:"spec"` - Jobs []*Job `bson:"jobs" json:"jobs"` + StageType string `bson:"type" json:"type"` + Jobs []*Job `bson:"jobs" json:"jobs"` } type Job struct { @@ -49,7 +48,7 @@ type Job struct { JobType string `bson:"type" json:"type"` Spec interface{} `bson:"spec" json:"spec"` Steps []*Step `bson:"steps" json:"steps"` - Outputs []*Output `bson:"outputs" son:"outputs"` + Outputs []*Output `bson:"outputs" json:"outputs"` } type JobProperties struct { @@ -60,13 +59,14 @@ type JobProperties struct { BuildOS string `bson:"build_os" json:"build_os"` Namespace string `bson:"namespace" json:"namespace"` Args []*KeyVal `bson:"args" json:"args"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` } type Step struct { Name string `bson:"name" json:"name"` Timeout int64 `bson:"timeout" json:"timeout"` StepType config.StepType `bson:"step_type" json:"step_type"` - Spec interface{} `bson:"Spec" json:"Spec"` + Spec interface{} `bson:"spec" json:"spec"` } type Output struct { -- Gitee From 8d50dbe05668809cc2890f7485e63333a12fdd8c Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 2 Jun 2022 11:36:59 +0800 Subject: [PATCH 2431/2647] excutor --> executor Signed-off-by: guoyu --- cmd/{jobexcutor => jobexecutor}/main.go | 4 ++-- .../{jobexcutor => jobexecutor}/config/config.go | 0 .../{jobexcutor => jobexecutor}/core/service/job.go | 6 +++--- .../{jobexcutor => jobexecutor}/core/service/meta/types.go | 0 .../{jobexcutor => jobexecutor}/core/service/step/step.go | 4 ++-- .../core/service/step/step_shell.go | 0 .../excutor/excutor.go => jobexecutor/executor/executor.go} | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) rename cmd/{jobexcutor => jobexecutor}/main.go (87%) rename pkg/microservice/{jobexcutor => jobexecutor}/config/config.go (100%) rename pkg/microservice/{jobexcutor => jobexecutor}/core/service/job.go (92%) rename pkg/microservice/{jobexcutor => jobexecutor}/core/service/meta/types.go (100%) rename pkg/microservice/{jobexcutor => jobexecutor}/core/service/step/step.go (96%) rename pkg/microservice/{jobexcutor => jobexecutor}/core/service/step/step_shell.go (100%) rename pkg/microservice/{jobexcutor/excutor/excutor.go => jobexecutor/executor/executor.go} (94%) diff --git a/cmd/jobexcutor/main.go b/cmd/jobexecutor/main.go similarity index 87% rename from cmd/jobexcutor/main.go rename to cmd/jobexecutor/main.go index 0206d8d7..370a2ff3 100644 --- a/cmd/jobexcutor/main.go +++ b/cmd/jobexecutor/main.go @@ -22,7 +22,7 @@ import ( "os/signal" "syscall" - "github.com/koderover/zadig/pkg/microservice/jobexcutor/excutor" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/executor" ) func main() { @@ -32,7 +32,7 @@ func main() { stop() }() - if err := excutor.Execute(ctx); err != nil { + if err := executor.Execute(ctx); err != nil { log.Fatal(err) } } diff --git a/pkg/microservice/jobexcutor/config/config.go b/pkg/microservice/jobexecutor/config/config.go similarity index 100% rename from pkg/microservice/jobexcutor/config/config.go rename to pkg/microservice/jobexecutor/config/config.go diff --git a/pkg/microservice/jobexcutor/core/service/job.go b/pkg/microservice/jobexecutor/core/service/job.go similarity index 92% rename from pkg/microservice/jobexcutor/core/service/job.go rename to pkg/microservice/jobexecutor/core/service/job.go index c7674bd4..502a3d41 100644 --- a/pkg/microservice/jobexcutor/core/service/job.go +++ b/pkg/microservice/jobexecutor/core/service/job.go @@ -24,9 +24,9 @@ import ( "strings" "time" - "github.com/koderover/zadig/pkg/microservice/jobexcutor/config" - "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service/meta" - "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service/step" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/config" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/meta" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/step" "gopkg.in/yaml.v3" ) diff --git a/pkg/microservice/jobexcutor/core/service/meta/types.go b/pkg/microservice/jobexecutor/core/service/meta/types.go similarity index 100% rename from pkg/microservice/jobexcutor/core/service/meta/types.go rename to pkg/microservice/jobexecutor/core/service/meta/types.go diff --git a/pkg/microservice/jobexcutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go similarity index 96% rename from pkg/microservice/jobexcutor/core/service/step/step.go rename to pkg/microservice/jobexecutor/core/service/step/step.go index 4c056a21..8da0ec26 100644 --- a/pkg/microservice/jobexcutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -24,8 +24,8 @@ import ( "strings" "time" - "github.com/koderover/zadig/pkg/microservice/jobexcutor/config" - "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service/meta" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/config" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/meta" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" ) diff --git a/pkg/microservice/jobexcutor/core/service/step/step_shell.go b/pkg/microservice/jobexecutor/core/service/step/step_shell.go similarity index 100% rename from pkg/microservice/jobexcutor/core/service/step/step_shell.go rename to pkg/microservice/jobexecutor/core/service/step/step_shell.go diff --git a/pkg/microservice/jobexcutor/excutor/excutor.go b/pkg/microservice/jobexecutor/executor/executor.go similarity index 94% rename from pkg/microservice/jobexcutor/excutor/excutor.go rename to pkg/microservice/jobexecutor/executor/executor.go index 3862a28d..a11283df 100644 --- a/pkg/microservice/jobexcutor/excutor/excutor.go +++ b/pkg/microservice/jobexecutor/executor/executor.go @@ -14,14 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package excutor +package executor import ( "context" "time" commonconfig "github.com/koderover/zadig/pkg/config" - job "github.com/koderover/zadig/pkg/microservice/jobexcutor/core/service" + job "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" -- Gitee From ce6aa3fa4f98a3595540aa6f6b4f95b5ecc421f3 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 2 Jun 2022 11:54:08 +0800 Subject: [PATCH 2432/2647] fix copyright Signed-off-by: guoyu --- cmd/jobexecutor/main.go | 2 +- .../common/repository/models/wokflow_task_v4.go | 2 +- .../core/common/repository/models/workflow_v4.go | 2 +- .../core/workflow/handler/workflow_task_v4.go | 2 +- .../core/workflow/service/jobcontroller/job.go | 2 +- .../service/jobcontroller/job_excutor.go | 2 +- .../service/jobcontroller/job_freestyle.go | 2 +- .../workflow/service/jobcontroller/kubernetes.go | 2 +- .../core/workflow/service/jobcontroller/types.go | 2 +- .../workflow/service/workflow/workflow_v4.go | 2 +- .../workflow/service/workflowcontroller/queue.go | 2 +- .../workflow/service/workflowcontroller/stage.go | 2 +- .../service/workflowcontroller/stage_custom.go | 2 +- .../service/workflowcontroller/workflow.go | 2 +- pkg/microservice/jobexecutor/config/config.go | 16 ++++++++++++++++ pkg/microservice/jobexecutor/core/service/job.go | 2 +- .../jobexecutor/core/service/meta/types.go | 2 +- .../jobexecutor/core/service/step/step.go | 2 +- .../jobexecutor/core/service/step/step_shell.go | 2 +- .../jobexecutor/executor/executor.go | 2 +- pkg/types/step/step.go | 2 +- 21 files changed, 36 insertions(+), 20 deletions(-) diff --git a/cmd/jobexecutor/main.go b/cmd/jobexecutor/main.go index 370a2ff3..d231bd90 100644 --- a/cmd/jobexecutor/main.go +++ b/cmd/jobexecutor/main.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 028cd89f..efae3696 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index a0a3595b..ca0dedc6 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index 319bc405..478cdc57 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index a854d43f..c938b19c 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go index 0eef8557..9f07fb00 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 00dd34e2..9bca63e7 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 6db5f803..5de7b17c 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go index 04f0373a..f397af26 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 87bb1279..0ced3700 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go index 90d6a0e8..6e11d7f8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 2e505be2..9afb1afb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go index 97cc1ef2..b3b7265f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 2a1494a7..7ae3bbe9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/jobexecutor/config/config.go b/pkg/microservice/jobexecutor/config/config.go index faa54563..d75f73a5 100644 --- a/pkg/microservice/jobexecutor/config/config.go +++ b/pkg/microservice/jobexecutor/config/config.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package config import ( diff --git a/pkg/microservice/jobexecutor/core/service/job.go b/pkg/microservice/jobexecutor/core/service/job.go index 502a3d41..b48262f2 100644 --- a/pkg/microservice/jobexecutor/core/service/job.go +++ b/pkg/microservice/jobexecutor/core/service/job.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/jobexecutor/core/service/meta/types.go b/pkg/microservice/jobexecutor/core/service/meta/types.go index b2e475c7..3b04d11b 100644 --- a/pkg/microservice/jobexecutor/core/service/meta/types.go +++ b/pkg/microservice/jobexecutor/core/service/meta/types.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/jobexecutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go index 8da0ec26..45adee4d 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/jobexecutor/core/service/step/step_shell.go b/pkg/microservice/jobexecutor/core/service/step/step_shell.go index f16c98d3..728225ec 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_shell.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_shell.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/microservice/jobexecutor/executor/executor.go b/pkg/microservice/jobexecutor/executor/executor.go index a11283df..e1e13f82 100644 --- a/pkg/microservice/jobexecutor/executor/executor.go +++ b/pkg/microservice/jobexecutor/executor/executor.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pkg/types/step/step.go b/pkg/types/step/step.go index 04ee326d..5799b987 100644 --- a/pkg/types/step/step.go +++ b/pkg/types/step/step.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The KodeRover Authors. +Copyright 2022 The KodeRover Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. -- Gitee From 001b6dcf204c14cf3aec8be96026f97c17a1efcb Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 2 Jun 2022 11:56:18 +0800 Subject: [PATCH 2433/2647] fix collaboration nil pointer (#1625) * rollback init config Signed-off-by: panxunying * fix collaboration nil pointer Signed-off-by: panxunying * optimize Signed-off-by: panxunying Co-authored-by: panxunying --- .../collaboration/service/collaboration_instance.go | 10 +++++++--- pkg/middleware/gin/collaboration.go | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 4927cd17..1e80ff1a 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1147,8 +1147,8 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id for _, product := range newProduct { set, ok := productRenderSetMap[product.BaseName] if !ok { - logger.Errorf("product:%s not exist", product.BaseName) - return nil, fmt.Errorf("product:%s not exist", product.BaseName) + logger.Warnf("product:%s renderSet not exist", product.BaseName) + continue } product.Vars = set.KVs @@ -1333,8 +1333,12 @@ func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error } var findOpts []commonrepo.RenderSetFindOption for _, product := range products { + var revision int64 + for _, productService := range product.GetServiceMap() { + revision = productService.Render.Revision + } findOpts = append(findOpts, commonrepo.RenderSetFindOption{ - Revision: product.Revision, + Revision: revision, Name: product.Namespace, }) } diff --git a/pkg/middleware/gin/collaboration.go b/pkg/middleware/gin/collaboration.go index ef74db6c..19800f0a 100644 --- a/pkg/middleware/gin/collaboration.go +++ b/pkg/middleware/gin/collaboration.go @@ -44,22 +44,27 @@ func GetCollaborationNew() gin.HandlerFunc { if err != nil { ctx.Err = err c.Abort() + return } if newResp == nil { c.Next() + return } if newResp.IfSync { err := service.SyncCollaborationInstance(nil, projectName, ctx.UserID, ctx.IdentityType, ctx.Account, ctx.RequestID, ctx.Logger) if err != nil { ctx.Err = err c.Abort() + return } } if len(newResp.Product) == 0 && len(newResp.Workflow) == 0 { c.Next() + return } else { ctx.Resp = newResp c.Abort() + return } } } -- Gitee From 6e86f2c632552522fbab1018ba8d381b13852488 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 2 Jun 2022 14:59:17 +0800 Subject: [PATCH 2434/2647] fix collaboration nil pointer when project type is pm (#1631) * rollback init config Signed-off-by: panxunying * fix collaboration nil pointer when project type is pm Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/collaboration/service/collaboration_instance.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 1e80ff1a..8a018188 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1320,6 +1320,9 @@ func GetCollaborationNew(projectName, uid, identityType, userName string, logger logger.Errorf("GetCollaborationNew error, err msg:%s", err) return nil, err } + if updateResp == nil || (updateResp.Update == nil && updateResp.New == nil && updateResp.UpdateInstance == nil && updateResp.Delete == nil) { + return nil, nil + } return getCollaborationNew(updateResp, projectName, identityType, userName, logger) } -- Gitee From 67bcffabbff609a24d36f89664679476fc6177a9 Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 2 Jun 2022 15:25:54 +0800 Subject: [PATCH 2435/2647] Optimize the problem that gitlab accesstoken expires in 2 hours (#1629) * Optimize the problem that gitalb accesstoken expires in 2 hours Signed-off-by: liu deyi * add log Signed-off-by: liu deyi * Increase the return of alias Signed-off-by: liu deyi * update accessToken and refreshToken Signed-off-by: liu deyi --- pkg/cli/upgradeassistant/cmd/migrate/170.go | 2 +- .../aslan/core/code/client/gitlab/gitlab.go | 2 +- .../aslan/core/common/service/fs/git.go | 2 +- .../core/common/service/gitlab/client.go | 4 +- .../core/common/service/scmnotify/client.go | 2 +- .../core/common/service/webhook/controller.go | 4 +- .../aslan/core/service/service/new_loader.go | 2 +- .../service/webhook/gitlab_workflow_task.go | 6 +- .../core/workflow/service/webhook/utils.go | 4 +- .../workflow/service/workflow/nsq_handlers.go | 2 +- .../codehost/repository/mongodb/codehost.go | 2 +- pkg/shared/client/systemconfig/codehost.go | 1 + pkg/tool/git/gitlab/client.go | 56 ++++++++++++++++++- pkg/tool/gitee/client.go | 8 ++- 14 files changed, 78 insertions(+), 19 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/170.go b/pkg/cli/upgradeassistant/cmd/migrate/170.go index ba6e60e0..ee721692 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate/170.go +++ b/pkg/cli/upgradeassistant/cmd/migrate/170.go @@ -118,7 +118,7 @@ func refreshWebHookSecret(secret string) error { case setting.SourceFromGithub: cl = github.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + cl, err = gitlab.NewClient(ch.ID, ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { log.Warnf("Failed to create gitlab client, err: %s", err) continue diff --git a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go index d4a9a0d0..46a7cc0e 100644 --- a/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go +++ b/pkg/microservice/aslan/core/code/client/gitlab/gitlab.go @@ -40,7 +40,7 @@ type Client struct { func (c *Config) Open(id int, logger *zap.SugaredLogger) (client.CodeHostClient, error) { - client, err := gitlab.NewClient(c.Address, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) + client, err := gitlab.NewClient(id, c.Address, c.AccessToken, config.ProxyHTTPSAddr(), c.EnableProxy) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/common/service/fs/git.go b/pkg/microservice/aslan/core/common/service/fs/git.go index 8329ddd7..2bcdbb0b 100644 --- a/pkg/microservice/aslan/core/common/service/fs/git.go +++ b/pkg/microservice/aslan/core/common/service/fs/git.go @@ -123,7 +123,7 @@ func GetTreeGetter(codeHostID int) (TreeGetter, error) { case setting.SourceFromGithub: return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy), nil case setting.SourceFromGitlab: - return gitlabservice.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + return gitlabservice.NewClient(ch.ID, ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) default: // should not have happened here log.DPanicf("invalid source: %s", ch.Type) diff --git a/pkg/microservice/aslan/core/common/service/gitlab/client.go b/pkg/microservice/aslan/core/common/service/gitlab/client.go index cf798e33..6f8326f7 100644 --- a/pkg/microservice/aslan/core/common/service/gitlab/client.go +++ b/pkg/microservice/aslan/core/common/service/gitlab/client.go @@ -24,8 +24,8 @@ type Client struct { *gitlab.Client } -func NewClient(address, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { - c, err := gitlab.NewClient(address, accessToken, proxyAddr, enableProxy) +func NewClient(id int, address, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { + c, err := gitlab.NewClient(id, address, accessToken, proxyAddr, enableProxy) if err != nil { return nil, err } diff --git a/pkg/microservice/aslan/core/common/service/scmnotify/client.go b/pkg/microservice/aslan/core/common/service/scmnotify/client.go index 5a0dd937..3eea8698 100644 --- a/pkg/microservice/aslan/core/common/service/scmnotify/client.go +++ b/pkg/microservice/aslan/core/common/service/scmnotify/client.go @@ -65,7 +65,7 @@ func (c *Client) Comment(notify *models.Notification) error { } if strings.ToLower(codeHostDetail.Type) == setting.SourceFromGitlab { var note *gitlab.Note - cli, err := gitlabtool.NewClient(codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr(), codeHostDetail.EnableProxy) + cli, err := gitlabtool.NewClient(codeHostDetail.ID, codeHostDetail.Address, codeHostDetail.AccessToken, config.ProxyHTTPSAddr(), codeHostDetail.EnableProxy) if err != nil { c.logger.Errorf("create gitlab client failed err: %v", err) return fmt.Errorf("create gitlab client failed err: %v", err) diff --git a/pkg/microservice/aslan/core/common/service/webhook/controller.go b/pkg/microservice/aslan/core/common/service/webhook/controller.go index b049352d..62ec122e 100644 --- a/pkg/microservice/aslan/core/common/service/webhook/controller.go +++ b/pkg/microservice/aslan/core/common/service/webhook/controller.go @@ -159,7 +159,7 @@ func removeWebhook(t *task, logger *zap.Logger) { case setting.SourceFromGithub: cl = github.NewClient(t.token, config.ProxyHTTPSAddr(), t.enableProxy) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(t.address, t.token, config.ProxyHTTPSAddr(), t.enableProxy) + cl, err = gitlab.NewClient(t.ID, t.address, t.token, config.ProxyHTTPSAddr(), t.enableProxy) if err != nil { t.err = err t.doneCh <- struct{}{} @@ -237,7 +237,7 @@ func addWebhook(t *task, logger *zap.Logger) { case setting.SourceFromGithub: cl = github.NewClient(t.token, config.ProxyHTTPSAddr(), t.enableProxy) case setting.SourceFromGitlab: - cl, err = gitlab.NewClient(t.address, t.token, config.ProxyHTTPSAddr(), t.enableProxy) + cl, err = gitlab.NewClient(t.ID, t.address, t.token, config.ProxyHTTPSAddr(), t.enableProxy) if err != nil { t.err = err t.doneCh <- struct{}{} diff --git a/pkg/microservice/aslan/core/service/service/new_loader.go b/pkg/microservice/aslan/core/service/service/new_loader.go index 3b2f3e10..893f3860 100644 --- a/pkg/microservice/aslan/core/service/service/new_loader.go +++ b/pkg/microservice/aslan/core/service/service/new_loader.go @@ -242,7 +242,7 @@ func getLoader(ch *systemconfig.CodeHost) (yamlLoader, error) { case setting.SourceFromGithub: return githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy), nil case setting.SourceFromGitlab: - return gitlabservice.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + return gitlabservice.NewClient(ch.ID, ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) default: // should not have happened here log.DPanicf("invalid source: %s", ch.Type) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index 1349a013..cd3ff3ed 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -225,7 +225,7 @@ func (gpem *gitlabPushEventMatcher) Match(hookRepo *commonmodels.MainHookRepo) ( return false, err } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) + client, err := gitlabtool.NewClient(detail.ID, detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) if err != nil { gpem.log.Errorf("NewClient error: %s", err) return false, err @@ -365,7 +365,7 @@ func UpdateWorkflowTaskArgs(triggerYaml *TriggerYaml, workflow *commonmodels.Wor if err != nil { return fmt.Errorf("GetCodeHost codehostId:%d err:%s", item.MainRepo.CodehostID, err) } - cli, err := gitlabtool.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + cli, err := gitlabtool.NewClient(ch.ID, ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { return fmt.Errorf("gitlabtool.NewClient codehostId:%d err:%s", item.MainRepo.CodehostID, err) } @@ -682,7 +682,7 @@ func findChangedFilesOfMergeRequest(event *gitlab.MergeEvent, codehostID int) ([ return nil, fmt.Errorf("failed to find codehost %d: %v", codehostID, err) } - client, err := gitlabtool.NewClient(detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) + client, err := gitlabtool.NewClient(detail.ID, detail.Address, detail.AccessToken, config.ProxyHTTPSAddr(), detail.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index a8766fd9..4295ed5b 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -289,7 +289,7 @@ func getGitlabClientByCodehostId(codehostId int) (*gitlabtool.Client, error) { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(fmt.Sprintf("failed to get codehost:%d, err: %s", codehost, err)) } - client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) + client, err := gitlabtool.NewClient(codehost.ID, codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) @@ -308,7 +308,7 @@ func getGitlabClientByAddress(address string) (*gitlabtool.Client, error) { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc("git client is nil") } - client, err := gitlabtool.NewClient(codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) + client, err := gitlabtool.NewClient(codehost.ID, codehost.Address, codehost.AccessToken, config.ProxyHTTPSAddr(), codehost.EnableProxy) if err != nil { log.Error(err) return nil, e.ErrCodehostListProjects.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 08b27c96..9b04a803 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -223,7 +223,7 @@ func getRawFileContent(codehostID int, repo, owner, branch, filePath string) ([] } switch ch.Type { case setting.SourceFromGitlab: - cli, err := gitlab.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) + cli, err := gitlab.NewClient(ch.ID, ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy) if err != nil { return nil, errors.Wrapf(err, "Failed to get gitlab client") } diff --git a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go index 3d5e3e1d..71759aa5 100644 --- a/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go +++ b/pkg/microservice/systemconfig/core/codehost/repository/mongodb/codehost.go @@ -172,7 +172,7 @@ func (c *CodehostColl) UpdateCodeHost(host *models.CodeHost) (*models.CodeHost, } if host.Type == setting.SourceFromGerrit { modifyValue["access_token"] = host.AccessToken - } else if host.Type == setting.SourceFromGitee { + } else if host.Type == setting.SourceFromGitee || host.Type == setting.SourceFromGitlab { modifyValue["access_token"] = host.AccessToken modifyValue["refresh_token"] = host.RefreshToken modifyValue["updated_at"] = host.UpdatedAt diff --git a/pkg/shared/client/systemconfig/codehost.go b/pkg/shared/client/systemconfig/codehost.go index 1f0fb27d..c4a557d0 100644 --- a/pkg/shared/client/systemconfig/codehost.go +++ b/pkg/shared/client/systemconfig/codehost.go @@ -48,6 +48,7 @@ type CodeHost struct { // the field determine whether the proxy is enabled EnableProxy bool `json:"enable_proxy"` UpdatedAt int64 `json:"updated_at"` + Alias string `json:"alias,omitempty"` AuthType types.AuthType `json:"auth_type,omitempty"` SSHKey string `json:"ssh_key,omitempty"` PrivateAccessToken string `json:"private_access_token,omitempty"` diff --git a/pkg/tool/git/gitlab/client.go b/pkg/tool/git/gitlab/client.go index 44fbf8d7..38f0edc3 100644 --- a/pkg/tool/git/gitlab/client.go +++ b/pkg/tool/git/gitlab/client.go @@ -21,9 +21,12 @@ import ( "io" "net/http" "net/url" + "time" + "github.com/koderover/zadig/pkg/tool/log" "github.com/xanzy/go-gitlab" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -45,7 +48,7 @@ type Client struct { *gitlab.Client } -func NewClient(address, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { +func NewClient(id int, address, accessToken, proxyAddr string, enableProxy bool) (*Client, error) { var client *http.Client if enableProxy { proxyURL, err := url.Parse(proxyAddr) @@ -58,6 +61,28 @@ func NewClient(address, accessToken, proxyAddr string, enableProxy bool) (*Clien client = http.DefaultClient } + if accessToken != "" { + ch, err := systemconfig.New().GetCodeHost(id) + // The normal expiration time is 7200 + if err == nil && (time.Now().Unix()-ch.UpdatedAt) >= 7000 { + token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.RefreshToken) + if err == nil { + accessToken = token.AccessToken + ch.AccessToken = token.AccessToken + ch.RefreshToken = token.RefreshToken + ch.UpdatedAt = int64(token.CreatedAt) + + if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { + log.Errorf("failed to update codeHost err:%s", err) + } + } else { + log.Errorf("failed to refresh accessToken, err:%s", err) + } + } else if err != nil { + log.Errorf("failed to get codeHost id: %d, err:%s", id, err) + } + } + cli, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(address), gitlab.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("failed to create gitlab client, err: %s", err) @@ -119,3 +144,32 @@ func wrapError(res *gitlab.Response, err error) error { } return nil } + +type AccessToken struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + CreatedAt int `json:"created_at"` +} + +func refreshAccessToken(address, clientID, refreshToken string) (*AccessToken, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(address), + ) + url := "/oauth/token" + queryParams := make(map[string]string) + queryParams["grant_type"] = "refresh_token" + queryParams["code_verifier"] = "CODE_VERIFIER" + queryParams["refresh_token"] = refreshToken + queryParams["client_id"] = clientID + + var accessToken *AccessToken + _, err := httpClient.Post(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&accessToken)) + if err != nil { + return nil, err + } + + return accessToken, nil +} diff --git a/pkg/tool/gitee/client.go b/pkg/tool/gitee/client.go index b1487f41..f643b1de 100644 --- a/pkg/tool/gitee/client.go +++ b/pkg/tool/gitee/client.go @@ -18,7 +18,6 @@ package gitee import ( "context" - "fmt" "net/http" "net/url" "time" @@ -27,6 +26,7 @@ import ( "golang.org/x/oauth2" "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" ) type Client struct { @@ -64,9 +64,13 @@ func NewClient(id int, accessToken, proxyAddr string, enableProxy bool) *Client ch.UpdatedAt = int64(token.CreatedAt) if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - fmt.Println(fmt.Sprintf("failed to updateCodeHost err:%s", err)) + log.Errorf("failed to updateCodeHost err:%s", err) } + } else { + log.Errorf("failed to refresh accessToken, err:%s", err) } + } else if err != nil { + log.Errorf("failed to get codeHost id: %d, err:%s", id, err) } ctx := context.WithValue(context.Background(), oauth2.HTTPClient, dc) -- Gitee From 4be64681d2a322b6bf0f9adf2f9245f7c7cece48 Mon Sep 17 00:00:00 2001 From: dyliu Date: Thu, 2 Jun 2022 17:40:02 +0800 Subject: [PATCH 2436/2647] Optimize the problem that gitlab accesstoken expires in 2 hours (#1637) Signed-off-by: liu deyi --- pkg/tool/git/gitlab/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tool/git/gitlab/client.go b/pkg/tool/git/gitlab/client.go index 38f0edc3..9f379d4e 100644 --- a/pkg/tool/git/gitlab/client.go +++ b/pkg/tool/git/gitlab/client.go @@ -65,7 +65,7 @@ func NewClient(id int, address, accessToken, proxyAddr string, enableProxy bool) ch, err := systemconfig.New().GetCodeHost(id) // The normal expiration time is 7200 if err == nil && (time.Now().Unix()-ch.UpdatedAt) >= 7000 { - token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.RefreshToken) + token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.SecretKey, ch.RefreshToken) if err == nil { accessToken = token.AccessToken ch.AccessToken = token.AccessToken @@ -154,16 +154,16 @@ type AccessToken struct { CreatedAt int `json:"created_at"` } -func refreshAccessToken(address, clientID, refreshToken string) (*AccessToken, error) { +func refreshAccessToken(address, clientID, clientSecret, refreshToken string) (*AccessToken, error) { httpClient := httpclient.New( httpclient.SetHostURL(address), ) url := "/oauth/token" queryParams := make(map[string]string) queryParams["grant_type"] = "refresh_token" - queryParams["code_verifier"] = "CODE_VERIFIER" queryParams["refresh_token"] = refreshToken queryParams["client_id"] = clientID + queryParams["client_secret"] = clientSecret var accessToken *AccessToken _, err := httpClient.Post(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&accessToken)) -- Gitee From 8e86ca03e5b80ab2ba4d98fe44fb8dfb5ea8632a Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 2 Jun 2022 18:25:21 +0800 Subject: [PATCH 2437/2647] fix add all services to env when user add only one service (#1640) * rollback init config Signed-off-by: panxunying * fix add all services to env when user add only one service Signed-off-by: panxunying * move log Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/environment/service/environment.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index ec02fc18..7bdc976f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -602,8 +602,6 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. // 服务需要更新,需要upsert // 所有服务全部upsert一遍,确保所有服务起来 if svcRev.Updatable { - log.Infof("[Namespace:%s][Product:%s][Service:%s][IsNew:%v] upsert service", - envName, productName, svcRev.ServiceName, svcRev.New) service := &commonmodels.ProductService{ ServiceName: svcRev.ServiceName, @@ -615,7 +613,9 @@ func UpdateProduct(serviceNames []string, existedProd, updateProd *commonmodels. service.Containers = svcRev.Containers service.Render = updateProd.Render - if svcRev.Type == setting.K8SDeployType { + if svcRev.Type == setting.K8SDeployType && util.InStringArray(service.ServiceName, serviceNames) { + log.Infof("[Namespace:%s][Product:%s][Service:%s][IsNew:%v] upsert service", + envName, productName, svcRev.ServiceName, svcRev.New) wg.Add(1) go func() { defer wg.Done() -- Gitee From 7bdb851f17b02b8f4b5b213bd1395c07db28f936 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 2 Jun 2022 20:00:43 +0800 Subject: [PATCH 2438/2647] fix product service update error (#1642) Signed-off-by: allenshen --- .../aslan/core/environment/service/environment.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 7bdc976f..30f1fa4f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1301,6 +1301,13 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid if !ok && !serviceNeedUpdateOrCreate.Has(svr.ServiceName) { continue } + + // existed service has nothing to update + if ok && !serviceNeedUpdateOrCreate.Has(svr.ServiceName) { + svcGroup = append(svcGroup, ps) + continue + } + svcGroup = append(svcGroup, svr) if ps == nil { continue @@ -3541,6 +3548,12 @@ func diffRenderSet(username, productName, envName string, productResp *commonmod continue } + // no need to update service revision in renderset.services + if !okR { + newChartInfos = append(newChartInfos, currentChartInfo) + continue + } + serviceInfoResp := serviceRespMap[serviceName] serviceInfoCur := serviceMap[serviceName] imageRelatedKey := sets.NewString() -- Gitee From 8f6462e44784e2dc5bad80266bf8173d3803dc40 Mon Sep 17 00:00:00 2001 From: flyer Date: Thu, 2 Jun 2022 20:03:13 +0800 Subject: [PATCH 2439/2647] fix bug of querying subenvs which are not related projects (#1644) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/aslan/core/environment/service/share_env.go | 5 +++-- .../aslan/core/environment/service/share_env_utils.go | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/share_env.go b/pkg/microservice/aslan/core/environment/service/share_env.go index 1dee5270..0047c04d 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env.go +++ b/pkg/microservice/aslan/core/environment/service/share_env.go @@ -162,7 +162,7 @@ func DisableBaseEnv(ctx context.Context, envName, productName string) error { } // 1. Delete all associated subenvironments. - err = ensureDeleteAssociatedEnvs(ctx, prod.EnvName) + err = ensureDeleteAssociatedEnvs(ctx, prod) if err != nil { return fmt.Errorf("failed to delete associated subenvironments of base ns `%s`: %s", ns, err) } @@ -460,6 +460,7 @@ func ensureVirtualService(ctx context.Context, kclient client.Client, istioClien matchedEnvs := []MatchedEnv{} if env.ShareEnv.Enable && env.ShareEnv.IsBase { subEnvs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: env.ProductName, ShareEnvEnable: zadigutil.GetBoolPointer(true), ShareEnvIsBase: zadigutil.GetBoolPointer(false), ShareEnvBaseEnv: zadigutil.GetStrPointer(env.EnvName), @@ -1168,7 +1169,7 @@ func EnsureDeleteShareEnvConfig(ctx context.Context, env *commonmodels.Product, if env.ShareEnv.IsBase { // 1. Delete all associated subenvironments. - err := ensureDeleteAssociatedEnvs(ctx, env.EnvName) + err := ensureDeleteAssociatedEnvs(ctx, env) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/environment/service/share_env_utils.go b/pkg/microservice/aslan/core/environment/service/share_env_utils.go index a345e26e..b1616d26 100644 --- a/pkg/microservice/aslan/core/environment/service/share_env_utils.go +++ b/pkg/microservice/aslan/core/environment/service/share_env_utils.go @@ -63,11 +63,12 @@ func ensureDisableBaseEnvConfig(ctx context.Context, baseEnv *commonmodels.Produ return commonrepo.NewProductColl().Update(baseEnv) } -func ensureDeleteAssociatedEnvs(ctx context.Context, baseEnvName string) error { +func ensureDeleteAssociatedEnvs(ctx context.Context, baseProduct *commonmodels.Product) error { envs, err := commonrepo.NewProductColl().List(&commonrepo.ProductListOptions{ + Name: baseProduct.ProductName, ShareEnvEnable: zadigutil.GetBoolPointer(true), ShareEnvIsBase: zadigutil.GetBoolPointer(false), - ShareEnvBaseEnv: zadigutil.GetStrPointer(baseEnvName), + ShareEnvBaseEnv: zadigutil.GetStrPointer(baseProduct.EnvName), }) if err != nil { return err -- Gitee From 14cba831d95a6886bf5cd7b02f19e08d4b31d7ee Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 6 Jun 2022 00:05:32 +0800 Subject: [PATCH 2440/2647] save global context Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 36 +++++++------ .../workflow/service/jobcontroller/job.go | 44 ++++++++-------- .../service/jobcontroller/job_excutor.go | 43 --------------- .../service/jobcontroller/job_freestyle.go | 52 +++++++++++++------ .../service/workflowcontroller/stage.go | 9 ++-- .../workflowcontroller/stage_custom.go | 23 ++++---- .../service/workflowcontroller/workflow.go | 40 ++++++++++++-- 7 files changed, 128 insertions(+), 119 deletions(-) delete mode 100644 pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index efae3696..e21b9df5 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -23,21 +23,23 @@ import ( ) type WorkflowTask struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TaskID int64 `bson:"task_id" json:"task_id"` - WorkflowName string `bson:"workflow_name" json:"workflow_name"` - Status config.Status `bson:"status" json:"status,omitempty"` - TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` - TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - Stages []*StageTask `bson:"stages" json:"stages"` - ProjectName string `bson:"project_name,omitempty" json:"project_name,omitempty"` - IsDeleted bool `bson:"is_deleted" json:"is_deleted"` - IsArchived bool `bson:"is_archived" json:"is_archived"` - Error string `bson:"error,omitempty" json:"error,omitempty"` - IsRestart bool `bson:"is_restart" json:"is_restart"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + Args []*KeyVal `bson:"args" json:"args"` + GlobalContext map[string]string `bson:"global_context" json:"global_context"` + Status config.Status `bson:"status" json:"status,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Stages []*StageTask `bson:"stages" json:"stages"` + ProjectName string `bson:"project_name,omitempty" json:"project_name,omitempty"` + IsDeleted bool `bson:"is_deleted" json:"is_deleted"` + IsArchived bool `bson:"is_archived" json:"is_archived"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` } func (WorkflowTask) TableName() string { @@ -86,4 +88,8 @@ type WorkflowTaskCtx struct { DistDir string DockerMountDir string ConfigMapMountDir string + WorkflowArgs []*KeyVal + GlobalContextGet func(key string) (string, bool) + GlobalContextSet func(key, value string) + GlobalContextEach func(f func(k, v string) bool) } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index c938b19c..1d7a4aad 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -31,7 +31,7 @@ type JobCtl interface { Run(ctx context.Context) } -func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { +func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger, ack func()) { job.Status = config.StatusRunning job.StartTime = time.Now().Unix() // set default timeout @@ -57,42 +57,40 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm case "deploy": // TODO default: - jobCtl = NewFreestyleJobCtl(job, workflowCtx, globalContext, ack, logger) + jobCtl = NewFreestyleJobCtl(job, workflowCtx, ack, logger) } jobCtl.Run(ctx) } -func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { - jobPool := NewPool(ctx, jobs, workflowCtx, concurrency, globalContext, logger, ack) +func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, logger *zap.SugaredLogger, ack func()) { + jobPool := NewPool(ctx, jobs, workflowCtx, concurrency, logger, ack) jobPool.Run() } // Pool is a worker group that runs a number of tasks at a // configured concurrency. type Pool struct { - Jobs []*commonmodels.JobTask - workflowCtx *commonmodels.WorkflowTaskCtx - concurrency int - jobsChan chan *commonmodels.JobTask - globalContext *sync.Map - logger *zap.SugaredLogger - ack func() - ctx context.Context - wg sync.WaitGroup + Jobs []*commonmodels.JobTask + workflowCtx *commonmodels.WorkflowTaskCtx + concurrency int + jobsChan chan *commonmodels.JobTask + logger *zap.SugaredLogger + ack func() + ctx context.Context + wg sync.WaitGroup } // NewPool initializes a new pool with the given tasks and // at the given concurrency. -func NewPool(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *Pool { +func NewPool(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, logger *zap.SugaredLogger, ack func()) *Pool { return &Pool{ - Jobs: jobs, - concurrency: concurrency, - workflowCtx: workflowCtx, - jobsChan: make(chan *commonmodels.JobTask), - globalContext: globalContext, - logger: logger, - ack: ack, - ctx: ctx, + Jobs: jobs, + concurrency: concurrency, + workflowCtx: workflowCtx, + jobsChan: make(chan *commonmodels.JobTask), + logger: logger, + ack: ack, + ctx: ctx, } } @@ -117,7 +115,7 @@ func (p *Pool) Run() { // The work loop for any single goroutine. func (p *Pool) work() { for job := range p.jobsChan { - runJob(p.ctx, job, p.workflowCtx, p.globalContext, p.logger, p.ack) + runJob(p.ctx, job, p.workflowCtx, p.logger, p.ack) p.wg.Done() } } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go deleted file mode 100644 index 9f07fb00..00000000 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_excutor.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package jobcontroller - -import ( - "strings" - "sync" - - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" -) - -func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map) *JobContext { - var envVars, secretEnvVars []string - for _, env := range job.Properties.Args { - if env.IsCredential { - secretEnvVars = append(secretEnvVars, strings.Join([]string{env.Key, env.Value}, "=")) - continue - } - envVars = append(envVars, strings.Join([]string{env.Key, env.Value}, "=")) - } - return &JobContext{ - Name: job.Name, - Envs: envVars, - SecretEnvs: secretEnvVars, - WorkflowName: workflowCtx.WorkflowName, - TaskID: workflowCtx.TaskID, - Steps: job.Steps, - } -} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 9bca63e7..c6cf5c0a 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -19,7 +19,7 @@ package jobcontroller import ( "context" "fmt" - "sync" + "strings" "time" zadigconfig "github.com/koderover/zadig/pkg/config" @@ -34,22 +34,19 @@ import ( ) type FreestyleJobCtl struct { - job *commonmodels.JobTask - workflowCtx *commonmodels.WorkflowTaskCtx - jobContext *sync.Map - globalContext *sync.Map - logger *zap.SugaredLogger - kubeclient crClient.Client - ack func() + job *commonmodels.JobTask + workflowCtx *commonmodels.WorkflowTaskCtx + logger *zap.SugaredLogger + kubeclient crClient.Client + ack func() } -func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { +func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { return &FreestyleJobCtl{ - job: job, - workflowCtx: workflowCtx, - globalContext: globalContext, - logger: logger, - ack: ack, + job: job, + workflowCtx: workflowCtx, + logger: logger, + ack: ack, } } @@ -88,7 +85,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namespace, IsCredential: false} c.job.Properties.Args = append(c.job.Properties.Args, envNameVar) - jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx, c.globalContext)) + jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx)) if err != nil { msg := fmt.Sprintf("cannot Jobexcutor.Context data: %v", err) c.logger.Error(msg) @@ -196,3 +193,28 @@ func (c *FreestyleJobCtl) complete(ctx context.Context) { // p.Task.LogFile = p.FileName } + +func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx) *JobContext { + var envVars, secretEnvVars []string + for _, env := range job.Properties.Args { + if env.IsCredential { + secretEnvVars = append(secretEnvVars, strings.Join([]string{env.Key, env.Value}, "=")) + continue + } + envVars = append(envVars, strings.Join([]string{env.Key, env.Value}, "=")) + } + + workflowCtx.GlobalContextEach(func(k, v string) bool { + envVars = append(envVars, strings.Join([]string{k, v}, "=")) + return true + }) + + return &JobContext{ + Name: job.Name, + Envs: envVars, + SecretEnvs: secretEnvVars, + WorkflowName: workflowCtx.WorkflowName, + TaskID: workflowCtx.TaskID, + Steps: job.Steps, + } +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 9afb1afb..b4ec442c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -18,7 +18,6 @@ package workflowcontroller import ( "context" - "sync" "time" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -30,7 +29,7 @@ type StageCtl interface { Run(ctx context.Context, concurrency int) } -func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { +func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, logger *zap.SugaredLogger, ack func()) { stage.Status = config.StatusRunning stage.StartTime = time.Now().Unix() ack() @@ -46,14 +45,14 @@ func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *c case "approve": // TODO approval stage default: - stageCtl = NewCustomStageCtl(stage, workflowCtx, globalContext, logger, ack) + stageCtl = NewCustomStageCtl(stage, workflowCtx, logger, ack) } stageCtl.Run(ctx, concurrency) } -func RunStages(ctx context.Context, stages []*commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) { +func RunStages(ctx context.Context, stages []*commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, logger *zap.SugaredLogger, ack func()) { for _, stage := range stages { - runStage(ctx, stage, workflowCtx, concurrency, globalContext, logger, ack) + runStage(ctx, stage, workflowCtx, concurrency, logger, ack) if statusFailed(stage.Status) { return } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go index b3b7265f..f96ba8c7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go @@ -18,7 +18,6 @@ package workflowcontroller import ( "context" - "sync" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller" @@ -26,20 +25,18 @@ import ( ) type CustomStageCtl struct { - stage *commonmodels.StageTask - workflowCtx *commonmodels.WorkflowTaskCtx - globalContext *sync.Map - logger *zap.SugaredLogger - ack func() + stage *commonmodels.StageTask + workflowCtx *commonmodels.WorkflowTaskCtx + logger *zap.SugaredLogger + ack func() } -func NewCustomStageCtl(stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, globalContext *sync.Map, logger *zap.SugaredLogger, ack func()) *CustomStageCtl { +func NewCustomStageCtl(stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger, ack func()) *CustomStageCtl { return &CustomStageCtl{ - stage: stage, - globalContext: globalContext, - logger: logger, - workflowCtx: workflowCtx, - ack: ack, + stage: stage, + logger: logger, + workflowCtx: workflowCtx, + ack: ack, } } @@ -53,5 +50,5 @@ func (c *CustomStageCtl) Run(ctx context.Context, concurrency int) { } } - jobcontroller.RunJobs(ctx, c.stage.Jobs, c.workflowCtx, workerConcurrency, c.globalContext, c.logger, c.ack) + jobcontroller.RunJobs(ctx, c.stage.Jobs, c.workflowCtx, workerConcurrency, c.logger, c.ack) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 7ae3bbe9..f4fd7617 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -32,10 +32,10 @@ var approveChannelMap sync.Map var cancelChannelMap sync.Map type workflowCtl struct { - workflowTask *commonmodels.WorkflowTask - globalContext *sync.Map - logger *zap.SugaredLogger - ack func() + workflowTask *commonmodels.WorkflowTask + globalContextMutex sync.RWMutex + logger *zap.SugaredLogger + ack func() } func NewWorkflowController(workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) *workflowCtl { @@ -60,6 +60,9 @@ func CancelWorkflowTask(workflowName string, id int64) error { } func (c *workflowCtl) Run(ctx context.Context, concurrency int) { + if c.workflowTask.GlobalContext == nil { + c.workflowTask.GlobalContext = make(map[string]string) + } c.workflowTask.Status = config.StatusRunning c.workflowTask.StartTime = time.Now().Unix() c.ack() @@ -79,9 +82,13 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { DistDir: fmt.Sprintf("%s/%s/dist/%d", config.S3StoragePath(), c.workflowTask.WorkflowName, c.workflowTask.TaskID), DockerMountDir: fmt.Sprintf("/tmp/%s/docker/%d", uuid.NewV4(), time.Now().Unix()), ConfigMapMountDir: fmt.Sprintf("/tmp/%s/cm/%d", uuid.NewV4(), time.Now().Unix()), + WorkflowArgs: c.workflowTask.Args, + GlobalContextGet: c.getGlobalContext, + GlobalContextSet: c.setGlobalContext, + GlobalContextEach: c.globalContextEach, } - RunStages(ctx, c.workflowTask.Stages, workflowCtx, concurrency, c.globalContext, c.logger, c.ack) + RunStages(ctx, c.workflowTask.Stages, workflowCtx, concurrency, c.logger, c.ack) updateworkflowStatus(c.workflowTask) } @@ -125,3 +132,26 @@ func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { func (c *workflowCtl) updateWorkflowTask() { // TODO update workflow task } + +func (c *workflowCtl) getGlobalContext(key string) (string, bool) { + c.globalContextMutex.RLock() + defer c.globalContextMutex.RUnlock() + v, existed := c.workflowTask.GlobalContext[key] + return v, existed +} + +func (c *workflowCtl) setGlobalContext(key, value string) { + c.globalContextMutex.Lock() + defer c.globalContextMutex.Unlock() + c.workflowTask.GlobalContext[key] = value +} + +func (c *workflowCtl) globalContextEach(f func(k, v string) bool) { + c.globalContextMutex.RLock() + defer c.globalContextMutex.RUnlock() + for k, v := range c.workflowTask.GlobalContext { + if !f(k, v) { + return + } + } +} -- Gitee From 34f9c7593d4470e53cec3b25d576b0208bd9e7bc Mon Sep 17 00:00:00 2001 From: mouuii <49775493+mouuii@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:35:51 +0800 Subject: [PATCH 2441/2647] all user permission (#1647) Signed-off-by: mouuii --- pkg/microservice/policy/core/service/permission.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/policy/core/service/permission.go b/pkg/microservice/policy/core/service/permission.go index dbcd0511..48efdc19 100644 --- a/pkg/microservice/policy/core/service/permission.go +++ b/pkg/microservice/policy/core/service/permission.go @@ -35,6 +35,11 @@ func GetUserRulesByProject(uid string, projectName string, log *zap.SugaredLogge if err != nil { return nil, err } + allUserRoleBingdins, err := mongodb.NewRoleBindingColl().ListBy(projectName, "*") + if err != nil { + return nil, err + } + roleBindings = append(roleBindings, allUserRoleBingdins...) roles, err := ListUserAllRolesByRoleBindings(roleBindings) if err != nil { return nil, err @@ -179,7 +184,7 @@ type GetUserRulesResp struct { } func GetUserRules(uid string, log *zap.SugaredLogger) (*GetUserRulesResp, error) { - roleBindings, err := mongodb.NewRoleBindingColl().ListRoleBindingsByUIDs([]string{uid}) + roleBindings, err := mongodb.NewRoleBindingColl().ListRoleBindingsByUIDs([]string{uid, "*"}) if err != nil { log.Errorf("ListRoleBindingsByUIDs err:%s") return &GetUserRulesResp{}, err -- Gitee From 6a1b23c5e49ced53058b568f86d08c7741bfcd36 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 6 Jun 2022 15:11:36 +0800 Subject: [PATCH 2442/2647] inject global context into step spec Signed-off-by: guoyu --- .../service/jobcontroller/job_freestyle.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index c6cf5c0a..9f362ca2 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -85,7 +85,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namespace, IsCredential: false} c.job.Properties.Args = append(c.job.Properties.Args, envNameVar) - jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx)) + jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx, c.logger)) if err != nil { msg := fmt.Sprintf("cannot Jobexcutor.Context data: %v", err) c.logger.Error(msg) @@ -194,7 +194,7 @@ func (c *FreestyleJobCtl) complete(ctx context.Context) { // p.Task.LogFile = p.FileName } -func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx) *JobContext { +func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger) *JobContext { var envVars, secretEnvVars []string for _, env := range job.Properties.Args { if env.IsCredential { @@ -204,8 +204,20 @@ func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels envVars = append(envVars, strings.Join([]string{env.Key, env.Value}, "=")) } + // inject global context into step spec. workflowCtx.GlobalContextEach(func(k, v string) bool { - envVars = append(envVars, strings.Join([]string{k, v}, "=")) + for _, step := range job.Steps { + yamlString, err := yaml.Marshal(step.Spec) + if err != nil { + logger.Errorf("marshal step spec error: %v", err) + return false + } + replacedString := strings.ReplaceAll(string(yamlString), fmt.Sprintf("$(%s)", k), v) + if err := yaml.Unmarshal([]byte(replacedString), &step.Spec); err != nil { + logger.Errorf("unmarshal step spec error: %v", err) + return false + } + } return true }) -- Gitee From cec2bcbf6d2d2b12c97558c9c02305402da0b460 Mon Sep 17 00:00:00 2001 From: flyer Date: Mon, 6 Jun 2022 16:54:17 +0800 Subject: [PATCH 2443/2647] fix bug of locating syncthing path (#1650) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- cmd/zgctl/cmd/root.go | 12 +----- go.mod | 4 +- go.sum | 31 ++++++++++----- pkg/microservice/zgctl/template.go | 2 +- pkg/microservice/zgctl/zgctl.go | 62 ++++++++++++++++++++++-------- 5 files changed, 71 insertions(+), 40 deletions(-) diff --git a/cmd/zgctl/cmd/root.go b/cmd/zgctl/cmd/root.go index cc3e8447..736613db 100644 --- a/cmd/zgctl/cmd/root.go +++ b/cmd/zgctl/cmd/root.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "fmt" "os" "path/filepath" @@ -37,19 +36,10 @@ func init() { rootCmd.PersistentFlags().StringVar(&zadigToken, "zadig-token", "", "token to access zadig system") rootCmd.PersistentFlags().StringVar(&homeDir, "home-dir", "$HOME/.zadig", "home dir of plugin") - logPath := filepath.Join(os.ExpandEnv(homeDir), "log") - if _, err := os.Stat(logPath); err == nil { - err := os.Remove(logPath) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove log %s: %s", logPath, err) - os.Exit(1) - } - } - log.Init(&log.Config{ Level: config.LogLevel(), SendToFile: config.SendLogToFile(), - Filename: logPath, + Filename: filepath.Join(os.ExpandEnv(homeDir), "log"), Development: config.Mode() != setting.ReleaseMode, MaxSize: 5, }) diff --git a/go.mod b/go.mod index 7b5df614..994f576c 100644 --- a/go.mod +++ b/go.mod @@ -62,13 +62,13 @@ require ( github.com/pkg/errors v0.9.1 github.com/rfyiamcool/cronlib v1.0.0 github.com/satori/go.uuid v1.2.0 - github.com/shirou/gopsutil/v3 v3.20.10 + github.com/shirou/gopsutil/v3 v3.22.5 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.8.1 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.3.0 github.com/swaggo/swag v1.5.1 diff --git a/go.sum b/go.sum index f3d6d901..5a9dc0a0 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,6 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= -github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -597,8 +595,8 @@ github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= @@ -831,8 +829,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= @@ -1100,6 +1099,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= @@ -1344,6 +1345,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -1426,8 +1429,8 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada h1:WokF3GuxBeL+n4Lk4Fa8v9mbdjlrl7bHuneF4N1bk2I= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= -github.com/shirou/gopsutil/v3 v3.20.10 h1:7zomV9HJv6UGk225YtvEa5+camNLpbua3MAz/GqiVJY= -github.com/shirou/gopsutil/v3 v3.20.10/go.mod h1:igHnfak0qnw1biGeI2qKQvu0ZkwvEkUcCLlYhZzdr/4= +github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA= +github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -1505,8 +1508,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= @@ -1532,6 +1536,10 @@ github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLD github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= +github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1606,6 +1614,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 h1:p7OofyZ509h8DmPLh8Hn+EIIZm/xYhdZHJ9GnXHdr6U= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= @@ -1958,12 +1968,12 @@ golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201013081832-0aaa2718063a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1986,8 +1996,9 @@ golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= diff --git a/pkg/microservice/zgctl/template.go b/pkg/microservice/zgctl/template.go index 003cc03f..ad56ca09 100644 --- a/pkg/microservice/zgctl/template.go +++ b/pkg/microservice/zgctl/template.go @@ -182,5 +182,5 @@ var syncthingConfigTmpl = ` var syncthingRunTmpl = ` #!/bin/sh -nohup syncthing serve --config={{.ConfigDir}} --data={{.DataDir}} & +nohup {{.SyncthingBin}} serve --config={{.ConfigDir}} --data={{.DataDir}} & ` diff --git a/pkg/microservice/zgctl/zgctl.go b/pkg/microservice/zgctl/zgctl.go index a56b135a..fe29745c 100644 --- a/pkg/microservice/zgctl/zgctl.go +++ b/pkg/microservice/zgctl/zgctl.go @@ -62,8 +62,9 @@ type SyncthingConfig struct { } type SyncthingScript struct { - ConfigDir string - DataDir string + SyncthingBin string + ConfigDir string + DataDir string } func init() { @@ -74,12 +75,13 @@ func NewZgCtl(cfg *ZgCtlConfig) ZgCtler { aslanClient := aslan.NewExternal(cfg.ZadigHost, cfg.ZadigToken) aslanClient.SetTimeout(3 * time.Minute) - log.Infof("home dir: %s", cfg.HomeDir) + syncthingBin := filepath.Join(cfg.HomeDir, "bin", "syncthing") + log.Infof("home dir: %s, syncthing bin: %s", cfg.HomeDir, syncthingBin) return &zgctl{ aslanClient: aslanClient, homeDir: cfg.HomeDir, - syncthingBin: filepath.Join(cfg.HomeDir, "bin", "syncthing"), + syncthingBin: syncthingBin, db: cfg.DB, } } @@ -89,13 +91,17 @@ func (z *zgctl) StartDevMode(ctx context.Context, projectName, envName, serviceN log.Info("Begin to patch worload.") workloadInfo, err := z.aslanClient.PatchWorkload(projectName, envName, serviceName, devImage) if err != nil { - return fmt.Errorf("failed to patch service %q in env %q of project %q: %s", envName, serviceName, projectName, err) + errRet := fmt.Errorf("failed to patch service %q in env %q of project %q: %s", envName, serviceName, projectName, err) + log.Error(errRet) + return errRet } log.Infof("workload info: %#v", workloadInfo) err = z.setZadig2K8sData(projectName, envName, serviceName, workloadInfo) if err != nil { - return fmt.Errorf("failed to set workload info: %s", err) + errRet := fmt.Errorf("failed to set workload info: %s", err) + log.Error(errRet) + return errRet } // 2. Configure `Syncthing`. @@ -105,7 +111,9 @@ func (z *zgctl) StartDevMode(ctx context.Context, projectName, envName, serviceN configDir := z.generateServiceConfigDir(projectName, envName, serviceName) err = z.configSyncthing(ctx, configDir, dataDir, localPort, remotePort) if err != nil { - return fmt.Errorf("failed to config syncthing for service %q in env %q: %s", envName, serviceName, err) + errRet := fmt.Errorf("failed to config syncthing for service %q in env %q: %s", envName, serviceName, err) + log.Error(errRet) + return errRet } // 3. Execute port-forward to implement one-way network communication. @@ -113,18 +121,29 @@ func (z *zgctl) StartDevMode(ctx context.Context, projectName, envName, serviceN kubeconfigPath, err := z.getEnvKubeconfigData(projectName, envName) if err != nil { - return fmt.Errorf("failed to find kubeconfig path of env %q in project %q: %s", envName, projectName, err) + errRet := fmt.Errorf("failed to find kubeconfig path of env %q in project %q: %s", envName, projectName, err) + log.Error(errRet) + return errRet } log.Infof("kubeconfig path for env %q in project %q: %s", envName, projectName, kubeconfigPath) err = z.podPortForward(ctx, workloadInfo.PodNamespace, workloadInfo.PodName, kubeconfigPath, remotePort) if err != nil { - return fmt.Errorf("failed to port-forward service %q in env %q: %s", envName, serviceName, err) + errRet := fmt.Errorf("failed to port-forward service %q in env %q: %s", envName, serviceName, err) + log.Error(errRet) + return errRet } // 4. Start `Syncthing`. log.Info("Begin to start Syncthing.") - return z.startSyncthing(ctx, workloadInfo.PodNamespace, workloadInfo.PodName, configDir, dataDir, kubeconfigPath) + err = z.startSyncthing(ctx, workloadInfo.PodNamespace, workloadInfo.PodName, configDir, dataDir, kubeconfigPath) + if err != nil { + errRet := fmt.Errorf("failed to start syncthing: %s", err) + log.Error(errRet) + return errRet + } + + return nil } func (z *zgctl) StopDevMode(ctx context.Context, projectName, envName, serviceName string) error { @@ -135,7 +154,9 @@ func (z *zgctl) StopDevMode(ctx context.Context, projectName, envName, serviceNa workloadInfo, err := z.getZadig2K8sData(projectName, envName, serviceName) if err != nil { - return fmt.Errorf("failed to get find workload info for project %q, envName %q, serviceName %q: %s", projectName, envName, serviceName, err) + errRet := fmt.Errorf("failed to get find workload info for project %q, envName %q, serviceName %q: %s", projectName, envName, serviceName, err) + log.Error(errRet) + return errRet } err = z.stopSyncthing(ctx, configDir, workloadInfo) @@ -145,7 +166,14 @@ func (z *zgctl) StopDevMode(ctx context.Context, projectName, envName, serviceNa // 2. Recover workload. log.Info("Begin to recover workload.") - return z.aslanClient.RecoverWorkload(projectName, envName, serviceName) + err = z.aslanClient.RecoverWorkload(projectName, envName, serviceName) + if err != nil { + errRet := fmt.Errorf("failed to recover workload: %s", err) + log.Error(errRet) + return errRet + } + + return nil } func (z *zgctl) DevImages() []string { @@ -286,8 +314,9 @@ func (z *zgctl) adjustSyncthingConfig(localDeviceID, remoteDeviceID, configDir, } localScriptData := SyncthingScript{ - ConfigDir: configDir, - DataDir: dataDir, + SyncthingBin: filepath.Join(z.homeDir, "bin", "syncthing"), + ConfigDir: configDir, + DataDir: dataDir, } var localScriptBuffer bytes.Buffer @@ -322,8 +351,9 @@ func (z *zgctl) adjustSyncthingConfig(localDeviceID, remoteDeviceID, configDir, } remoteScriptData := SyncthingScript{ - ConfigDir: filepath.Join(types.DevmodeWorkDir, "config"), - DataDir: types.DevmodeWorkDir, + SyncthingBin: "syncthing", + ConfigDir: filepath.Join(types.DevmodeWorkDir, "config"), + DataDir: types.DevmodeWorkDir, } var remoteScriptBuffer bytes.Buffer -- Gitee From f65b4e6de542a497282231b1017a0a6510a943a6 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 6 Jun 2022 22:04:35 +0800 Subject: [PATCH 2444/2647] pass job output to globalcontext Signed-off-by: guoyu --- .../service/jobcontroller/job_freestyle.go | 19 ++++++- .../service/jobcontroller/kubernetes.go | 43 +++++++++++++++- .../jobexecutor/core/service/job.go | 51 ++++++++++++++++++- .../jobexecutor/executor/executor.go | 7 ++- pkg/types/job/job.go | 27 ++++++++++ 5 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 pkg/types/job/job.go diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 9f362ca2..dd94c6c6 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -118,7 +118,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { c.logger.Infof("succeed to create cm for job %s", c.job.Name) // TODO: do not use default image - jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test1" + jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test2" // jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW @@ -184,6 +184,18 @@ func (c *FreestyleJobCtl) complete(ctx context.Context) { } }() + // get job outputs info from pod terminate message. + outputs, err := getJobOutput(c.job.Properties.Namespace, jobLabel, c.kubeclient) + if err != nil { + c.logger.Error(err) + c.job.Error = err.Error() + } + + // write jobs output info to globalcontext so other job can use like this $(jobName.outputName) + for _, output := range outputs { + c.workflowCtx.GlobalContextSet(strings.Join([]string{c.job.Name, output.Name}, "."), output.Value) + } + // err := saveContainerLog(pipelineTask, p.KubeNamespace, "", c.FileName, jobLabel, c.kubeclient) // if err != nil { // p.Log.Error(err) @@ -220,6 +232,10 @@ func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels } return true }) + outputs := []string{} + for _, output := range job.Outputs { + outputs = append(outputs, output.Name) + } return &JobContext{ Name: job.Name, @@ -227,6 +243,7 @@ func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels SecretEnvs: secretEnvVars, WorkflowName: workflowCtx.WorkflowName, TaskID: workflowCtx.TaskID, + Outputs: outputs, Steps: job.Steps, } } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 5de7b17c..003cfb94 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -18,6 +18,7 @@ package jobcontroller import ( "context" + "encoding/json" "fmt" "path" "strconv" @@ -42,6 +43,7 @@ import ( "github.com/koderover/zadig/pkg/shared/kube/wrapper" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/types/job" ) func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes.Interface, *rest.Config, error) { @@ -185,7 +187,8 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), Resources: getResourceRequirements(resReq, resReqSpec), - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + TerminationMessagePath: job.JobTerminationFile, }, }, Volumes: getVolumes(jobName), @@ -231,6 +234,10 @@ func getVolumeMounts(configMapMountDir string) []corev1.VolumeMount { Name: "job-config", MountPath: configMapMountDir, }) + resp = append(resp, corev1.VolumeMount{ + Name: "zadig-context", + MountPath: "/zadig/", + }) return resp } @@ -247,6 +254,12 @@ func getVolumes(jobName string) []corev1.Volume { }, }, }) + resp = append(resp, corev1.Volume{ + Name: "zadig-context", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) return resp } @@ -396,3 +409,31 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName time.Sleep(time.Second * 1) } } + +func getJobOutput(namespace string, jobLabel *JobLabel, kubeClient crClient.Client) ([]*job.JobOutput, error) { + resp := []*job.JobOutput{} + ls := getJobLabels(jobLabel) + pods, err := getter.ListPods(namespace, labels.Set(ls).AsSelector(), kubeClient) + if err != nil { + return resp, err + } + for _, pod := range pods { + ipod := wrapper.Pod(pod) + // only collect succeeed job outputs. + if !ipod.Succeeded() { + return resp, nil + } + for _, containerStatus := range pod.Status.ContainerStatuses { + if containerStatus.Name != ls["s-type"] { + continue + } + if containerStatus.State.Terminated != nil && len(containerStatus.State.Terminated.Message) != 0 { + if err := json.Unmarshal([]byte(containerStatus.State.Terminated.Message), &resp); err != nil { + return resp, err + } + return resp, nil + } + } + } + return resp, nil +} diff --git a/pkg/microservice/jobexecutor/core/service/job.go b/pkg/microservice/jobexecutor/core/service/job.go index b48262f2..19cb06d8 100644 --- a/pkg/microservice/jobexecutor/core/service/job.go +++ b/pkg/microservice/jobexecutor/core/service/job.go @@ -18,15 +18,18 @@ package job import ( "context" + "encoding/json" "fmt" "io/ioutil" "os" + "path/filepath" "strings" "time" "github.com/koderover/zadig/pkg/microservice/jobexecutor/config" "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/meta" "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/step" + "github.com/koderover/zadig/pkg/types/job" "gopkg.in/yaml.v3" ) @@ -37,6 +40,12 @@ type Job struct { UserEnvs map[string]string } +const ( + // MaxContainerTerminationMessageLength is the upper bound any one container may write to + // its termination message path. Contents above this length will cause a failure. + MaxContainerTerminationMessageLength = 1024 * 4 +) + func NewJob() (*Job, error) { context, err := ioutil.ReadFile(config.JobConfigFile()) if err != nil { @@ -110,6 +119,46 @@ func (j *Job) getUserEnvs() []string { } func (j *Job) Run(ctx context.Context) error { - step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.Ctx.Envs, j.Ctx.SecretEnvs) + if err := os.MkdirAll(job.JobOutputDir, os.ModePerm); err != nil { + return err + } + if err := step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.Ctx.Envs, j.Ctx.SecretEnvs); err != nil { + return err + } return nil } +func (j *Job) AfterRun(ctx context.Context) error { + return j.collectJobResult(ctx) +} + +func (j *Job) collectJobResult(ctx context.Context) error { + outputs := []*job.JobOutput{} + for _, outputName := range j.Ctx.Outputs { + fileContents, err := ioutil.ReadFile(filepath.Join(job.JobOutputDir, outputName)) + if os.IsNotExist(err) { + continue + } else if err != nil { + return err + } + outputs = append(outputs, &job.JobOutput{Name: outputName, Value: string(fileContents)}) + } + jsonOutput, err := json.Marshal(outputs) + if err != nil { + return err + } + + if len(jsonOutput) > MaxContainerTerminationMessageLength { + return fmt.Errorf("termination message is above max allowed size 4096, caused by large task result") + } + + f, err := os.OpenFile(job.JobTerminationFile, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + return err + } + defer f.Close() + + if _, err = f.Write(jsonOutput); err != nil { + return err + } + return f.Sync() +} diff --git a/pkg/microservice/jobexecutor/executor/executor.go b/pkg/microservice/jobexecutor/executor/executor.go index e1e13f82..be449865 100644 --- a/pkg/microservice/jobexecutor/executor/executor.go +++ b/pkg/microservice/jobexecutor/executor/executor.go @@ -18,6 +18,7 @@ package executor import ( "context" + "os" "time" commonconfig "github.com/koderover/zadig/pkg/config" @@ -44,6 +45,8 @@ func Execute(ctx context.Context) error { if err != nil { resultMsg = types.JobFail log.Errorf("Failed to run: %s.", err) + log.Infof("====================== %s End. Duration: %.2f seconds ======================", excutor, time.Since(start).Seconds()) + os.Exit(1) } log.Infof("Job Status: %s", resultMsg) @@ -55,10 +58,12 @@ func Execute(ctx context.Context) error { if err != nil { return err } - log.Infof("====================== %s Start ======================", excutor) if err = j.Run(ctx); err != nil { return err } + if err = j.AfterRun(ctx); err != nil { + return err + } return nil } diff --git a/pkg/types/job/job.go b/pkg/types/job/job.go new file mode 100644 index 00000000..d56c5786 --- /dev/null +++ b/pkg/types/job/job.go @@ -0,0 +1,27 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +const ( + JobOutputDir = "/zadig/results/" + JobTerminationFile = "/zadig/termination" +) + +type JobOutput struct { + Name string `json:"name"` + Value string `json:"value"` +} -- Gitee From 91e6102d320e146a3782c09d593c8212ed2efcd3 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 6 Jun 2022 23:08:03 +0800 Subject: [PATCH 2445/2647] make job name unique Signed-off-by: guoyu --- .../service/jobcontroller/job_freestyle.go | 14 +++++++++----- .../workflow/service/jobcontroller/kubernetes.go | 8 ++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index dd94c6c6..ae06fcba 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -35,6 +35,7 @@ import ( type FreestyleJobCtl struct { job *commonmodels.JobTask + jobName string workflowCtx *commonmodels.WorkflowTaskCtx logger *zap.SugaredLogger kubeclient crClient.Client @@ -47,6 +48,7 @@ func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.Wor workflowCtx: workflowCtx, logger: logger, ack: ack, + jobName: fmt.Sprintf("%s-%s-%d", job.Name, job.JobType, workflowCtx.TaskID), } } @@ -98,6 +100,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { WorkflowName: c.workflowCtx.WorkflowName, TaskID: c.workflowCtx.TaskID, JobType: string(c.job.JobType), + JobName: c.job.Name, } if err := ensureDeleteConfigMap(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { c.logger.Error(err) @@ -107,7 +110,7 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { } if err := createJobConfigMap( - c.job.Properties.Namespace, c.job.Name, jobLabel, string(jobCtxBytes), c.kubeclient); err != nil { + c.job.Properties.Namespace, c.jobName, jobLabel, string(jobCtxBytes), c.kubeclient); err != nil { msg := fmt.Sprintf("createJobConfigMap error: %v", err) c.logger.Error(msg) c.job.Status = config.StatusFailed @@ -115,14 +118,14 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { return } - c.logger.Infof("succeed to create cm for job %s", c.job.Name) + c.logger.Infof("succeed to create cm for job %s", c.jobName) // TODO: do not use default image jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test2" // jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW - job, err := buildJob(c.job.JobType, jobImage, c.job.Name, c.job.Properties.ClusterID, c.job.Properties.Namespace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) + job, err := buildJob(c.job.JobType, jobImage, c.jobName, c.job.Properties.ClusterID, c.job.Properties.Namespace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) if err != nil { msg := fmt.Sprintf("create job context error: %v", err) c.logger.Error(msg) @@ -157,11 +160,11 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { c.job.Error = msg return } - c.logger.Infof("succeed to create job %s", c.job.Name) + c.logger.Infof("succeed to create job %s", c.jobName) } func (c *FreestyleJobCtl) wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, int(c.job.Properties.Timeout), c.job.Properties.Namespace, c.job.Name, c.kubeclient, c.logger) + status := waitJobEndWithFile(ctx, int(c.job.Properties.Timeout), c.job.Properties.Namespace, c.jobName, c.kubeclient, c.logger) c.job.Status = status } @@ -170,6 +173,7 @@ func (c *FreestyleJobCtl) complete(ctx context.Context) { WorkflowName: c.workflowCtx.WorkflowName, TaskID: c.workflowCtx.TaskID, JobType: string(c.job.JobType), + JobName: c.job.Name, } // 清理用户取消和超时的任务 diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 003cfb94..6acafc43 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -68,11 +68,13 @@ func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes type JobLabel struct { WorkflowName string TaskID int64 + JobName string JobType string } const ( jobLabelTaskKey = "s-task" + jobLabelNameKey = "s-name" jobLabelSTypeKey = "s-type" ) @@ -90,6 +92,7 @@ func ensureDeleteJob(namespace string, jobLabel *JobLabel, kubeClient crClient.C func getJobLabels(jobLabel *JobLabel) map[string]string { retMap := map[string]string{ jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.WorkflowName), jobLabel.TaskID), + jobLabelNameKey: strings.Replace(jobLabel.JobName, "_", "-", -1), jobLabelSTypeKey: strings.Replace(jobLabel.JobType, "_", "-", -1), } // no need to add labels with empty value to a job @@ -132,6 +135,7 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re WorkflowName: workflowCtx.WorkflowName, TaskID: workflowCtx.TaskID, JobType: string(jobType), + JobName: jobTask.Name, }) // 引用集成到系统中的私有镜像仓库的访问权限 @@ -171,7 +175,7 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re Containers: []corev1.Container{ { ImagePullPolicy: corev1.PullAlways, - Name: labels["s-type"], + Name: labels[jobLabelNameKey], Image: jobImage, Env: []corev1.EnvVar{ { @@ -424,7 +428,7 @@ func getJobOutput(namespace string, jobLabel *JobLabel, kubeClient crClient.Clie return resp, nil } for _, containerStatus := range pod.Status.ContainerStatuses { - if containerStatus.Name != ls["s-type"] { + if containerStatus.Name != ls[jobLabelNameKey] { continue } if containerStatus.State.Terminated != nil && len(containerStatus.State.Terminated.Message) != 0 { -- Gitee From acebb5c3e807bd495c248afb46adb29e11e40979 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 8 Jun 2022 15:42:10 +0800 Subject: [PATCH 2446/2647] fix error return deleting all svcs from environment (#1657) * fix error return deleting all svcs from environment Signed-off-by: allenshen * fix spell issue Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/environment.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 8f44fa24..0cbbf85a 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -554,6 +554,9 @@ func findServiceFromIngress(hostInfos []resource.HostInfo, currentWorkload *Work func GetProductUsedTemplateSvcs(prod *models.Product) ([]*models.Service, error) { // filter releases, only list releases deployed by zadig productName, envName, serviceMap := prod.ProductName, prod.EnvName, prod.GetServiceMap() + if len(serviceMap) == 0 { + return nil, nil + } listOpt := &commonrepo.SvcRevisionListOption{ ProductName: prod.ProductName, ServiceRevisions: make([]*commonrepo.ServiceRevision, 0), @@ -566,7 +569,7 @@ func GetProductUsedTemplateSvcs(prod *models.Product) ([]*models.Service, error) } templateServices, err := commonrepo.NewServiceColl().ListServicesWithSRevision(listOpt) if err != nil { - return nil, fmt.Errorf("failed to list template services for pruduct: %s:%s", productName, envName) + return nil, fmt.Errorf("failed to list template services for pruduct: %s:%s, err: %s", productName, envName, err) } return templateServices, err } -- Gitee From 69cb61ad8f5966e3eca33c80247af196b0d10bfa Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 8 Jun 2022 18:28:55 +0800 Subject: [PATCH 2447/2647] fix github list project not found bug (#1658) * fix github list project not found bug Signed-off-by: allenshen * fix github fetch project bug Signed-off-by: allenshen --- pkg/tool/git/github/repositories.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/tool/git/github/repositories.go b/pkg/tool/git/github/repositories.go index 8b13cee4..84ce6aa3 100644 --- a/pkg/tool/git/github/repositories.go +++ b/pkg/tool/git/github/repositories.go @@ -31,7 +31,8 @@ import ( func (c *Client) ListRepositoriesForAuthenticatedUser(ctx context.Context, user string, opts *ListOptions) ([]*github.Repository, error) { repositories, err := wrap(paginated(func(o *github.ListOptions) ([]interface{}, *github.Response, error) { - rs, r, err := c.Repositories.List(ctx, user, &github.RepositoryListOptions{ListOptions: *o}) + // Note. parameter user is not used when list repositories because private repos will NOT be in response data from gitHub even user is the exact owner of these private repos + rs, r, err := c.Repositories.List(ctx, "", &github.RepositoryListOptions{ListOptions: *o}) var res []interface{} for _, r := range rs { res = append(res, r) -- Gitee From ed1a557e38144797bd50d2db3b9523297838f6b0 Mon Sep 17 00:00:00 2001 From: guoyu Date: Wed, 8 Jun 2022 21:25:11 +0800 Subject: [PATCH 2448/2647] add steps Signed-off-by: guoyu --- pkg/microservice/aslan/config/consts.go | 1 + .../workflow/service/jobcontroller/job.go | 18 + .../service/jobcontroller/job_freestyle.go | 49 ++- .../service/jobcontroller/kubernetes.go | 2 +- .../workflow/service/stepcontroller/step.go | 82 ++++ .../stepcontroller/step_docker_build.go | 59 +++ .../service/stepcontroller/step_git.go | 72 ++++ .../service/stepcontroller/step_shell.go | 37 ++ .../jobexecutor/core/service/cmd/cmd.go | 28 ++ .../jobexecutor/core/service/cmd/git.go | 154 ++++++++ .../jobexecutor/core/service/job.go | 4 +- .../jobexecutor/core/service/meta/types.go | 13 - .../jobexecutor/core/service/step/step.go | 41 ++ .../core/service/step/step_docker_build.go | 194 +++++++++ .../jobexecutor/core/service/step/step_git.go | 372 ++++++++++++++++++ pkg/tool/dockerhost/docker_host.go | 201 ++++++++++ pkg/tool/dockerhost/dockerhosts_test.go | 50 +++ pkg/types/step/step_docker_build.go | 55 +++ pkg/types/step/step_git.go | 150 +++++++ pkg/types/step/{step.go => step_shell.go} | 0 20 files changed, 1548 insertions(+), 34 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go create mode 100644 pkg/microservice/jobexecutor/core/service/cmd/cmd.go create mode 100644 pkg/microservice/jobexecutor/core/service/cmd/git.go create mode 100644 pkg/microservice/jobexecutor/core/service/step/step_docker_build.go create mode 100644 pkg/microservice/jobexecutor/core/service/step/step_git.go create mode 100644 pkg/tool/dockerhost/docker_host.go create mode 100644 pkg/tool/dockerhost/dockerhosts_test.go create mode 100644 pkg/types/step/step_docker_build.go create mode 100644 pkg/types/step/step_git.go rename pkg/types/step/{step.go => step_shell.go} (100%) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 99f099b1..c0dcf355 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -153,6 +153,7 @@ type StepType string const ( StepTools StepType = "tools" StepShell StepType = "shell" + StepGit StepType = "git" StepDockerBuild StepType = "docker_build" StepDeploy StepType = "deploy" StepHelmDeploy StepType = "helm_deploy" diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index 1d7a4aad..a4a51d8f 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -23,6 +23,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/stepcontroller" "github.com/koderover/zadig/pkg/setting" "go.uber.org/zap" ) @@ -46,12 +47,29 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm if job.Properties.ClusterID == "" { job.Properties.ClusterID = setting.LocalClusterID } + // init step configration. + if err := stepcontroller.PrepareSteps(ctx, workflowCtx, job.Steps, logger); err != nil { + logger.Error(err) + job.Error = err.Error() + job.Status = config.StatusFailed + job.EndTime = time.Now().Unix() + logger.Infof("finish job: %s,status: %s", job.Name, job.Status) + ack() + return + } + logger.Infof("start job: %s,status: %s", job.Name, job.Status) defer func() { + if err := stepcontroller.SummarizeSteps(ctx, workflowCtx, job.Steps, logger); err != nil { + logger.Error(err) + job.Error = err.Error() + job.Status = config.StatusFailed + } job.EndTime = time.Now().Unix() logger.Infof("finish job: %s,status: %s", job.Name, job.Status) ack() }() + var jobCtl JobCtl switch job.JobType { case "deploy": diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index ae06fcba..c77d4ed9 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -26,6 +26,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/dockerhost" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" "go.uber.org/zap" @@ -33,6 +34,11 @@ import ( crClient "sigs.k8s.io/controller-runtime/pkg/client" ) +const ( + DindServer = "dind" + KoderoverAgentNamespace = "koderover-agent" +) + type FreestyleJobCtl struct { job *commonmodels.JobTask jobName string @@ -80,8 +86,31 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { // decide which docker host to use. // TODO: do not use code in warpdrive moudule, should move to a public place - // dockerhosts := common.NewDockerHosts(hubServerAddr, c.logger) - // dockerHost := dockerhosts.GetBestHost(common.ClusterID(c.job.Properties.ClusterID), "") + dockerhosts := dockerhost.NewDockerHosts(hubServerAddr, c.logger) + c.job.Properties.DockerHost = dockerhosts.GetBestHost(dockerhost.ClusterID(c.job.Properties.ClusterID), "") + + // not local cluster + var ( + replaceDindServer = "." + DindServer + dockerHost = "" + ) + + if c.job.Properties.ClusterID != "" && c.job.Properties.ClusterID != setting.LocalClusterID { + if strings.Contains(c.job.Properties.DockerHost, config.Namespace()) { + // replace namespace only + dockerHost = strings.Replace(c.job.Properties.DockerHost, config.Namespace(), KoderoverAgentNamespace, 1) + } else { + // add namespace + dockerHost = strings.Replace(c.job.Properties.DockerHost, replaceDindServer, replaceDindServer+"."+KoderoverAgentNamespace, 1) + } + } else if c.job.Properties.ClusterID == "" || c.job.Properties.ClusterID == setting.LocalClusterID { + if !strings.Contains(c.job.Properties.DockerHost, config.Namespace()) { + // add namespace + dockerHost = strings.Replace(c.job.Properties.DockerHost, replaceDindServer, replaceDindServer+"."+config.Namespace(), 1) + } + } + + c.job.Properties.DockerHost = dockerHost // TODO: inject vars like task_id and etc, passing them into c.job.Properties.Args envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namespace, IsCredential: false} @@ -220,22 +249,6 @@ func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels envVars = append(envVars, strings.Join([]string{env.Key, env.Value}, "=")) } - // inject global context into step spec. - workflowCtx.GlobalContextEach(func(k, v string) bool { - for _, step := range job.Steps { - yamlString, err := yaml.Marshal(step.Spec) - if err != nil { - logger.Errorf("marshal step spec error: %v", err) - return false - } - replacedString := strings.ReplaceAll(string(yamlString), fmt.Sprintf("$(%s)", k), v) - if err := yaml.Unmarshal([]byte(replacedString), &step.Spec); err != nil { - logger.Errorf("unmarshal step spec error: %v", err) - return false - } - } - return true - }) outputs := []string{} for _, output := range job.Outputs { outputs = append(outputs, output.Name) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 6acafc43..a7b05c05 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -185,7 +185,7 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re // 连接对应wd上的dockerdeamon { Name: "DOCKER_HOST", - Value: workflowCtx.DockerHost, + Value: jobTask.Properties.DockerHost, }, }, VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go new file mode 100644 index 00000000..33c99be9 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go @@ -0,0 +1,82 @@ +package stepcontroller + +import ( + "context" + "fmt" + "strings" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type StepCtl interface { + // get required info from aslan before job run. + PreRun(ctx context.Context) + // collect required info after job run.(like test report) + AfterRun(ctx context.Context) +} + +func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { + // inject global context into step spec. + workflowCtx.GlobalContextEach(func(k, v string) bool { + for _, step := range steps { + yamlString, err := yaml.Marshal(step.Spec) + if err != nil { + logger.Errorf("marshal step spec error: %v", err) + return false + } + replacedString := strings.ReplaceAll(string(yamlString), fmt.Sprintf("$(%s)", k), v) + if err := yaml.Unmarshal([]byte(replacedString), &step.Spec); err != nil { + logger.Errorf("unmarshal step spec error: %v", err) + return false + } + } + return true + }) + + stepCtls := []StepCtl{} + for _, step := range steps { + stepCtl, err := instantiateStepCtl(step, logger) + if err != nil { + return err + } + stepCtls = append(stepCtls, stepCtl) + } + for _, stepCtl := range stepCtls { + stepCtl.PreRun(ctx) + } + return nil +} + +func SummarizeSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { + stepCtls := []StepCtl{} + for _, step := range steps { + stepCtl, err := instantiateStepCtl(step, logger) + if err != nil { + return err + } + stepCtls = append(stepCtls, stepCtl) + } + for _, stepCtl := range stepCtls { + stepCtl.AfterRun(ctx) + } + return nil +} + +func instantiateStepCtl(step *commonmodels.StepTask, logger *zap.SugaredLogger) (StepCtl, error) { + var stepCtl StepCtl + var err error + switch step.StepType { + case config.StepGit: + stepCtl, err = NewGitCtl(step, logger) + case config.StepShell: + stepCtl, err = NewShellCtl(step, logger) + case config.StepDockerBuild: + stepCtl, err = NewDockerBuildCtl(step, logger) + default: + logger.Infof("unknown step type: %s", step.StepType) + } + return stepCtl, err +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go new file mode 100644 index 00000000..6730b257 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go @@ -0,0 +1,59 @@ +package stepcontroller + +import ( + "context" + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/types/step" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type dockerBuildCtl struct { + step *commonmodels.StepTask + dockerBuildSpec *step.StepDockerBuildSpec + log *zap.SugaredLogger +} + +func NewDockerBuildCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*dockerBuildCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal git spec error: %v", err) + } + dockerBuildSpec := &step.StepDockerBuildSpec{} + if err := yaml.Unmarshal(yamlString, &dockerBuildSpec); err != nil { + return nil, fmt.Errorf("unmarshal git spec error: %v", err) + } + if dockerBuildSpec.Proxy == nil { + dockerBuildSpec.Proxy = &step.Proxy{} + } + return &dockerBuildCtl{dockerBuildSpec: dockerBuildSpec, log: log, step: stepTask}, nil +} + +func (s *dockerBuildCtl) PreRun(ctx context.Context) { + s.dockerBuildSpec.DockerRegistry = &step.DockerRegistry{ + UserName: config.RegistryAccessKey(), + Password: config.RegistrySecretKey(), + Namespace: config.RegistryNamespace(), + Host: config.RegistryAddress(), + } + proxies, _ := mongodb.NewProxyColl().List(&mongodb.ProxyArgs{}) + if len(proxies) != 0 { + s.dockerBuildSpec.Proxy.Address = proxies[0].Address + s.dockerBuildSpec.Proxy.EnableApplicationProxy = proxies[0].EnableApplicationProxy + s.dockerBuildSpec.Proxy.EnableRepoProxy = proxies[0].EnableRepoProxy + s.dockerBuildSpec.Proxy.NeedPassword = proxies[0].NeedPassword + s.dockerBuildSpec.Proxy.Password = proxies[0].Password + s.dockerBuildSpec.Proxy.Port = proxies[0].Port + s.dockerBuildSpec.Proxy.Type = proxies[0].Type + s.dockerBuildSpec.Proxy.Username = proxies[0].Username + } + s.step.Spec = s.dockerBuildSpec +} + +func (s *dockerBuildCtl) AfterRun(ctx context.Context) { + +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go new file mode 100644 index 00000000..06d64150 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go @@ -0,0 +1,72 @@ +package stepcontroller + +import ( + "context" + "fmt" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types/step" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type gitCtl struct { + step *commonmodels.StepTask + gitSpec *step.StepGitSpec + log *zap.SugaredLogger +} + +func NewGitCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*gitCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal git spec error: %v", err) + } + gitSpec := &step.StepGitSpec{} + if err := yaml.Unmarshal(yamlString, &gitSpec); err != nil { + return nil, fmt.Errorf("unmarshal git spec error: %v", err) + } + if gitSpec.Proxy == nil { + gitSpec.Proxy = &step.Proxy{} + } + return &gitCtl{gitSpec: gitSpec, log: log, step: stepTask}, nil +} + +func (s *gitCtl) PreRun(ctx context.Context) { + for _, repo := range s.gitSpec.Repos { + cID := repo.CodeHostID + if cID == 0 { + log.Error("codehostID can't be empty") + return + } + detail, err := systemconfig.New().GetCodeHost(cID) + if err != nil { + s.log.Error(err) + return + } + repo.Source = detail.Type + repo.OauthToken = detail.AccessToken + repo.Address = detail.Address + repo.User = detail.Username + repo.Password = detail.Password + repo.EnableProxy = detail.EnableProxy + } + proxies, _ := mongodb.NewProxyColl().List(&mongodb.ProxyArgs{}) + if len(proxies) != 0 { + s.gitSpec.Proxy.Address = proxies[0].Address + s.gitSpec.Proxy.EnableApplicationProxy = proxies[0].EnableApplicationProxy + s.gitSpec.Proxy.EnableRepoProxy = proxies[0].EnableRepoProxy + s.gitSpec.Proxy.NeedPassword = proxies[0].NeedPassword + s.gitSpec.Proxy.Password = proxies[0].Password + s.gitSpec.Proxy.Port = proxies[0].Port + s.gitSpec.Proxy.Type = proxies[0].Type + s.gitSpec.Proxy.Username = proxies[0].Username + } + s.step.Spec = s.gitSpec +} + +func (s *gitCtl) AfterRun(ctx context.Context) { + +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go new file mode 100644 index 00000000..3ba678df --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go @@ -0,0 +1,37 @@ +package stepcontroller + +import ( + "context" + "fmt" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/types/step" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type shellCtl struct { + step *commonmodels.StepTask + shellSpec *step.StepShellSpec + log *zap.SugaredLogger +} + +func NewShellCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*shellCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal shell spec error: %v", err) + } + shellSpec := &step.StepShellSpec{} + if err := yaml.Unmarshal(yamlString, &shellSpec); err != nil { + return nil, fmt.Errorf("unmarshal shell spec error: %v", err) + } + return &shellCtl{shellSpec: shellSpec, log: log, step: stepTask}, nil +} + +func (s *shellCtl) PreRun(ctx context.Context) { + +} + +func (s *shellCtl) AfterRun(ctx context.Context) { + +} diff --git a/pkg/microservice/jobexecutor/core/service/cmd/cmd.go b/pkg/microservice/jobexecutor/core/service/cmd/cmd.go new file mode 100644 index 00000000..cc983bad --- /dev/null +++ b/pkg/microservice/jobexecutor/core/service/cmd/cmd.go @@ -0,0 +1,28 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import "os/exec" + +// Command ... +type Command struct { + Cmd *exec.Cmd + // DisableTrace display command args + DisableTrace bool + // IgnoreError ingore command run error + IgnoreError bool +} diff --git a/pkg/microservice/jobexecutor/core/service/cmd/git.go b/pkg/microservice/jobexecutor/core/service/cmd/git.go new file mode 100644 index 00000000..243aa1dc --- /dev/null +++ b/pkg/microservice/jobexecutor/core/service/cmd/git.go @@ -0,0 +1,154 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import "os/exec" + +// InitGit creates an empty git repository. +// it returns command git init +func InitGit(dir string) *exec.Cmd { + cmd := exec.Command( + "git", + "init", + ) + cmd.Dir = dir + return cmd +} + +// RemoteAdd sets the remote origin for the repository. +func RemoteAdd(remoteName, remote string) *exec.Cmd { + return exec.Command( + "git", + "remote", + "add", + remoteName, + remote, + ) +} + +// RemoteRemove removes the remote origin for the repository. +func RemoteRemove(remoteName string) *exec.Cmd { + return exec.Command( + "git", + "remote", + "remove", + remoteName, + ) +} + +// CheckoutHead returns command git checkout -qf FETCH_HEAD +func CheckoutHead() *exec.Cmd { + return exec.Command( + "git", + "checkout", + "-qf", + "FETCH_HEAD", + ) +} + +// Fetch fetches changes by ref, ref can be a tag, branch or pr. --depth=1 is used to limit fetching +// to the last commit from the tip of each remote branch history. +// e.g. git fetch origin +refs/heads/onboarding --depth=1 +func Fetch(remoteName, ref string) *exec.Cmd { + return exec.Command( + "git", + "fetch", + remoteName, + "+"+ref, // "+" means overwrite + "--depth=1", + ) +} + +// DeepenedFetch deepens the fetch history. It is similar with Fetch but accepts 500 more commit history than +// last Fetch operation by --deepen=500 option. +// e.g. git fetch origin +refs/heads/onboarding --deepen=500 +func DeepenedFetch(remoteName, ref string) *exec.Cmd { + return exec.Command( + "git", + "fetch", + remoteName, + "+"+ref, // "+" means overwrite + "--deepen=500", + ) +} + +// ResetMerge reset last merge +// It return command git reset --merge +func ResetMerge() *exec.Cmd { + return exec.Command( + "git", + "reset", + "--merge", + ) + +} + +// Merge merge a branch +// e.g. git merge demo +func Merge(branch string) *exec.Cmd { + return exec.Command( + "git", + "merge", + branch, + "--allow-unrelated-histories", // add this option to avoid "fatal: refusing to merge unrelated histories" + ) +} + +// UpdateSubmodules returns command: git submodule update --init --recursive +func UpdateSubmodules() *exec.Cmd { + cmd := exec.Command( + "git", + "submodule", + "update", + "--init", + "--recursive", + ) + return cmd +} + +// SetConfig returns command: git config --global $KEY $VA +// e.g. git config --global user.name username +func SetConfig(key, value string) *exec.Cmd { + return exec.Command( + "git", + "config", + "--global", + key, + value, + ) +} + +// SetConfig returns command: git config --global $KEY $VA +// e.g. git config --global user.name username +func Gc() *exec.Cmd { + return exec.Command( + "git", + "gc", + ) +} + +// ShowLastLog returns command git --no-pager log --oneline -1 +// It shows last commit messge with sha +func ShowLastLog() *exec.Cmd { + return exec.Command( + "git", + "--no-pager", + "log", + "--oneline", + "-1", + ) +} diff --git a/pkg/microservice/jobexecutor/core/service/job.go b/pkg/microservice/jobexecutor/core/service/job.go index 19cb06d8..b6b2cf1e 100644 --- a/pkg/microservice/jobexecutor/core/service/job.go +++ b/pkg/microservice/jobexecutor/core/service/job.go @@ -84,7 +84,7 @@ func NewJob() (*Job, error) { func (j *Job) EnsureActiveWorkspace(workspace string) error { if workspace == "" { - tempWorkspace, err := ioutil.TempDir(os.TempDir(), "reaper") + tempWorkspace, err := ioutil.TempDir(os.TempDir(), "jobexecutor") if err != nil { return fmt.Errorf("create workspace error: %v", err) } @@ -122,7 +122,7 @@ func (j *Job) Run(ctx context.Context) error { if err := os.MkdirAll(job.JobOutputDir, os.ModePerm); err != nil { return err } - if err := step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.Ctx.Envs, j.Ctx.SecretEnvs); err != nil { + if err := step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.getUserEnvs(), j.Ctx.SecretEnvs); err != nil { return err } return nil diff --git a/pkg/microservice/jobexecutor/core/service/meta/types.go b/pkg/microservice/jobexecutor/core/service/meta/types.go index 3b04d11b..1a855ca6 100644 --- a/pkg/microservice/jobexecutor/core/service/meta/types.go +++ b/pkg/microservice/jobexecutor/core/service/meta/types.go @@ -20,7 +20,6 @@ type JobContext struct { Name string `yaml:"name"` // Workspace 容器工作目录 [必填] Workspace string `yaml:"workspace"` - Proxy *Proxy `yaml:"proxy"` // Envs 用户注入环境变量, 包括安装脚本环境变量 [optional] Envs EnvVar `yaml:"envs"` // SecretEnvs 用户注入敏感信息环境变量, value不能在stdout stderr中输出 [optional] @@ -43,15 +42,3 @@ type Step struct { } type EnvVar []string - -// Proxy 翻墙配置信息 -type Proxy struct { - Type string `yaml:"type"` - Address string `yaml:"address"` - Port int `yaml:"port"` - NeedPassword bool `yaml:"need_password"` - Username string `yaml:"username"` - Password string `yaml:"password"` - EnableRepoProxy bool `yaml:"enable_repo_proxy"` - EnableApplicationProxy bool `yaml:"enable_application_proxy"` -} diff --git a/pkg/microservice/jobexecutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go index 45adee4d..0bd79736 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -21,10 +21,12 @@ import ( "context" "fmt" "io" + "os" "strings" "time" "github.com/koderover/zadig/pkg/microservice/jobexecutor/config" + "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/cmd" "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/meta" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/util" @@ -56,6 +58,16 @@ func runStep(ctx context.Context, step *meta.Step, workspace string, envs, secre if err != nil { return err } + case "git": + stepInstance, err = NewGitStep(step.Spec, workspace, envs, secretEnvs) + if err != nil { + return err + } + case "docker_build": + stepInstance, err = NewDockerBuildStep(step.Spec, workspace, envs, secretEnvs) + if err != nil { + return err + } default: err := fmt.Errorf("step type: %s does not match any known type", step.StepType) log.Error(err) @@ -109,6 +121,18 @@ const ( secretEnvMask = "********" ) +func maskSecret(secrets []string, message string) string { + out := message + + for _, val := range secrets { + if len(val) == 0 { + continue + } + out = strings.Replace(out, val, "********", -1) + } + return out +} + func maskSecretEnvs(message string, secretEnvs []string) string { out := message @@ -130,3 +154,20 @@ func maskSecretEnvs(message string, secretEnvs []string) string { } return out } + +func isDirEmpty(dir string) bool { + f, err := os.Open(dir) + if err != nil { + return true + } + defer f.Close() + + _, err = f.Readdir(1) + return err == io.EOF +} + +func setCmdsWorkDir(dir string, cmds []*cmd.Command) { + for _, c := range cmds { + c.Cmd.Dir = dir + } +} diff --git a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go new file mode 100644 index 00000000..8ac4d90f --- /dev/null +++ b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go @@ -0,0 +1,194 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types/step" + "github.com/koderover/zadig/pkg/util/fs" + "gopkg.in/yaml.v3" +) + +const dockerExe = "/usr/local/bin/docker" + +type DockerBuildStep struct { + spec *step.StepDockerBuildSpec + envs []string + secretEnvs []string + workspace string +} + +func NewDockerBuildStep(spec interface{}, workspace string, envs, secretEnvs []string) (*DockerBuildStep, error) { + dockerBuildStep := &DockerBuildStep{workspace: workspace, envs: envs, secretEnvs: secretEnvs} + yamlBytes, err := yaml.Marshal(spec) + if err != nil { + return dockerBuildStep, fmt.Errorf("marshal spec %+v failed", spec) + } + if err := yaml.Unmarshal(yamlBytes, &dockerBuildStep.spec); err != nil { + return dockerBuildStep, fmt.Errorf("unmarshal spec %s to docker build spec failed", yamlBytes) + } + return dockerBuildStep, nil +} + +func (s *DockerBuildStep) Run(ctx context.Context) error { + if err := s.dockerLogin(); err != nil { + return err + } + return s.runDockerBuild() +} + +func (s DockerBuildStep) dockerLogin() error { + if s.spec.DockerRegistry == nil { + return nil + } + if s.spec.DockerRegistry.UserName != "" { + log.Infof("Logining Docker Registry: %s.", s.spec.DockerRegistry.Host) + startTimeDockerLogin := time.Now() + cmd := dockerLogin(s.spec.DockerRegistry.UserName, s.spec.DockerRegistry.Password, s.spec.DockerRegistry.Host) + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to login docker registry: %s", err) + } + + log.Infof("Login ended. Duration: %.2f seconds.", time.Since(startTimeDockerLogin).Seconds()) + } + return nil +} + +func (s *DockerBuildStep) runDockerBuild() error { + if s.spec == nil { + return nil + } + + log.Info("Preparing Dockerfile.") + startTimePrepareDockerfile := time.Now() + err := prepareDockerfile(s.spec.Source, s.spec.DockerTemplateContent) + if err != nil { + return fmt.Errorf("failed to prepare dockerfile: %s", err) + } + log.Infof("Preparation ended. Duration: %.2f seconds.", time.Since(startTimePrepareDockerfile).Seconds()) + + if s.spec.Proxy != nil { + setProxy(s.spec) + } + + log.Info("Runing Docker Build.") + startTimeDockerBuild := time.Now() + envs := s.envs + for _, c := range s.dockerCommands() { + c.Stdout = os.Stdout + c.Stderr = os.Stderr + c.Dir = s.workspace + c.Env = envs + if err := c.Run(); err != nil { + return fmt.Errorf("failed to run docker build: %s", err) + } + } + log.Infof("Docker build ended. Duration: %.2f seconds.", time.Since(startTimeDockerBuild).Seconds()) + + return nil +} + +func (s *DockerBuildStep) dockerCommands() []*exec.Cmd { + cmds := make([]*exec.Cmd, 0) + cmds = append( + cmds, + dockerBuildCmd( + s.spec.GetDockerFile(), + s.spec.ImageName, + s.spec.WorkDir, + s.spec.BuildArgs, + s.spec.IgnoreCache, + ), + dockerPush(s.spec.ImageName), + ) + return cmds +} + +func dockerBuildCmd(dockerfile, fullImage, ctx, buildArgs string, ignoreCache bool) *exec.Cmd { + args := []string{"-c"} + dockerCommand := "docker build --rm=true" + if ignoreCache { + dockerCommand += " --no-cache" + } + + if buildArgs != "" { + for _, val := range strings.Fields(buildArgs) { + if val != "" { + dockerCommand = dockerCommand + " " + val + } + } + + } + dockerCommand = dockerCommand + " -t " + fullImage + " -f " + dockerfile + " " + ctx + args = append(args, dockerCommand) + return exec.Command("sh", args...) +} + +func dockerPush(fullImage string) *exec.Cmd { + args := []string{ + "push", + fullImage, + } + return exec.Command(dockerExe, args...) +} + +func dockerLogin(user, password, registry string) *exec.Cmd { + return exec.Command( + dockerExe, + "login", + "-u", user, + "-p", password, + registry, + ) +} + +func prepareDockerfile(dockerfileSource, dockerfileContent string) error { + if dockerfileSource == setting.DockerfileSourceTemplate { + reader := strings.NewReader(dockerfileContent) + readCloser := io.NopCloser(reader) + path := fmt.Sprintf("/%s", setting.ZadigDockerfilePath) + err := fs.SaveFile(readCloser, path) + if err != nil { + return err + } + } + return nil +} + +func setProxy(ctx *step.StepDockerBuildSpec) { + if ctx.Proxy.EnableRepoProxy && ctx.Proxy.Type == "http" { + if !strings.Contains(strings.ToLower(ctx.BuildArgs), "--build-arg http_proxy=") { + ctx.BuildArgs = fmt.Sprintf("%s --build-arg http_proxy=%s", ctx.BuildArgs, ctx.Proxy.GetProxyURL()) + } + if !strings.Contains(strings.ToLower(ctx.BuildArgs), "--build-arg https_proxy=") { + ctx.BuildArgs = fmt.Sprintf("%s --build-arg https_proxy=%s", ctx.BuildArgs, ctx.Proxy.GetProxyURL()) + } + } +} diff --git a/pkg/microservice/jobexecutor/core/service/step/step_git.go b/pkg/microservice/jobexecutor/core/service/step/step_git.go new file mode 100644 index 00000000..9e1a4bb3 --- /dev/null +++ b/pkg/microservice/jobexecutor/core/service/step/step_git.go @@ -0,0 +1,372 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +import ( + "bufio" + "context" + "encoding/base64" + "fmt" + "io/ioutil" + "net/url" + "os" + "path" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/jobexecutor/config" + c "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/cmd" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/types/step" +) + +type GitStep struct { + spec *step.StepGitSpec + envs []string + secretEnvs []string + workspace string +} + +func NewGitStep(spec interface{}, workspace string, envs, secretEnvs []string) (*GitStep, error) { + gitStep := &GitStep{workspace: workspace, envs: envs, secretEnvs: secretEnvs} + yamlBytes, err := yaml.Marshal(spec) + if err != nil { + return gitStep, fmt.Errorf("marshal spec %+v failed", spec) + } + if err := yaml.Unmarshal(yamlBytes, &gitStep.spec); err != nil { + return gitStep, fmt.Errorf("unmarshal spec %s to shell spec failed", yamlBytes) + } + return gitStep, nil +} + +func (s *GitStep) Run(ctx context.Context) error { + return s.runGitCmds() +} + +func (s *GitStep) RunGitGc(folder string) error { + // envs := r.getUserEnvs() + cmd := c.Gc() + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + // cmd.Env = envs + cmd.Dir = path.Join(s.workspace, folder) + return cmd.Run() +} + +func (s *GitStep) runGitCmds() error { + + envs := s.envs + // 如果存在github代码库,则设置代理,同时保证非github库不走代理 + if s.spec.Proxy.EnableRepoProxy && s.spec.Proxy.Type == "http" { + noProxy := "" + proxyFlag := false + for _, repo := range s.spec.Repos { + if repo.EnableProxy { + if !proxyFlag { + envs = append(envs, fmt.Sprintf("http_proxy=%s", s.spec.Proxy.GetProxyURL())) + envs = append(envs, fmt.Sprintf("https_proxy=%s", s.spec.Proxy.GetProxyURL())) + proxyFlag = true + } + } else { + uri, err := url.Parse(repo.Address) + if err == nil { + if noProxy != "" { + noProxy += "," + } + noProxy += uri.Host + } + } + } + envs = append(envs, fmt.Sprintf("no_proxy=%s", noProxy)) + } + + // 获取git代码 + cmds := make([]*c.Command, 0) + + cmds = append(cmds, &c.Command{Cmd: c.SetConfig("user.email", "ci@koderover.com"), DisableTrace: true}) + cmds = append(cmds, &c.Command{Cmd: c.SetConfig("user.name", "koderover"), DisableTrace: true}) + + // https://stackoverflow.com/questions/24952683/git-push-error-rpc-failed-result-56-http-code-200-fatal-the-remote-end-hun/36843260 + //cmds = append(cmds, &c.Command{Cmd: c.SetConfig("http.postBuffer", "524288000"), DisableTrace: true}) + cmds = append(cmds, &c.Command{Cmd: c.SetConfig("http.postBuffer", "2097152000"), DisableTrace: true}) + var tokens []string + var hostNames = sets.NewString() + for _, repo := range s.spec.Repos { + if repo == nil || len(repo.Name) == 0 { + continue + } + + if repo.Source == step.ProviderGerrit { + userpass, _ := base64.StdEncoding.DecodeString(repo.OauthToken) + userpassPair := strings.Split(string(userpass), ":") + var user, password string + if len(userpassPair) > 1 { + password = userpassPair[1] + } + user = userpassPair[0] + repo.User = user + if password != "" { + repo.Password = password + tokens = append(tokens, repo.Password) + } + } else if repo.Source == step.ProviderCodehub { + tokens = append(tokens, repo.Password) + } else if repo.Source == step.ProviderOther { + tokens = append(tokens, repo.PrivateAccessToken) + tokens = append(tokens, repo.SSHKey) + } + tokens = append(tokens, repo.OauthToken) + cmds = append(cmds, s.buildGitCommands(repo, hostNames)...) + } + // write ssh key + if len(hostNames.List()) > 0 { + if err := writeSSHConfigFile(hostNames, s.spec.Proxy); err != nil { + return err + } + } + + for _, c := range cmds { + cmdOutReader, err := c.Cmd.StdoutPipe() + if err != nil { + return err + } + + outScanner := bufio.NewScanner(cmdOutReader) + go func() { + for outScanner.Scan() { + fmt.Printf("%s\n", maskSecret(tokens, outScanner.Text())) + } + }() + + cmdErrReader, err := c.Cmd.StderrPipe() + if err != nil { + return err + } + + errScanner := bufio.NewScanner(cmdErrReader) + go func() { + for errScanner.Scan() { + fmt.Printf("%s\n", maskSecret(tokens, errScanner.Text())) + } + }() + + c.Cmd.Env = envs + if !c.DisableTrace { + log.Infof("%s", strings.Join(c.Cmd.Args, " ")) + } + if err := c.Cmd.Run(); err != nil { + if c.IgnoreError { + continue + } + return err + } + } + return nil +} + +func (s *GitStep) buildGitCommands(repo *step.Repo, hostNames sets.String) []*c.Command { + + cmds := make([]*c.Command, 0) + + if len(repo.Name) == 0 { + return cmds + } + + workDir := filepath.Join(s.workspace, repo.Name) + if len(repo.CheckoutPath) != 0 { + workDir = filepath.Join(s.workspace, repo.CheckoutPath) + } + + if _, err := os.Stat(workDir); os.IsNotExist(err) { + os.MkdirAll(workDir, 0777) + } + + // 预防非正常退出导致git被锁住 + indexLockPath := path.Join(workDir, "/.git/index.lock") + if err := os.RemoveAll(indexLockPath); err != nil { + log.Errorf("Failed to remove %s: %s", indexLockPath, err) + } + shallowLockPath := path.Join(workDir, "/.git/shallow.lock") + if err := os.RemoveAll(shallowLockPath); err != nil { + log.Errorf("Failed to remove %s: %s", shallowLockPath, err) + } + + if isDirEmpty(filepath.Join(workDir, ".git")) { + cmds = append(cmds, &c.Command{Cmd: c.InitGit(workDir)}) + } else { + cmds = append(cmds, &c.Command{Cmd: c.RemoteRemove(repo.RemoteName), DisableTrace: true, IgnoreError: true}) + } + + // namespace represents the real owner + owner := repo.Namespace + if len(owner) == 0 { + owner = repo.Owner + } + if repo.Source == step.ProviderGitlab { + u, _ := url.Parse(repo.Address) + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, owner, repo.Name, u.Scheme)), + DisableTrace: true, + }) + } else if repo.Source == step.ProviderGerrit { + u, _ := url.Parse(repo.Address) + u.Path = fmt.Sprintf("/a/%s", repo.Name) + u.User = url.UserPassword(repo.User, repo.Password) + + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, u.String()), + DisableTrace: true, + }) + } else if repo.Source == step.ProviderCodehub { + u, _ := url.Parse(repo.Address) + user := url.QueryEscape(repo.User) + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", u.Scheme, user, repo.Password, u.Host, owner, repo.Name)), + DisableTrace: true, + }) + } else if repo.Source == step.ProviderGitee { + cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) + } else if repo.Source == step.ProviderOther { + if repo.AuthType == types.SSHAuthType { + host := getHost(repo.Address) + if !hostNames.Has(host) { + if err := writeSSHFile(repo.SSHKey, host); err != nil { + log.Errorf("failed to write ssh file %s: %s", repo.SSHKey, err) + } + hostNames.Insert(host) + } + remoteName := fmt.Sprintf("%s:%s/%s.git", repo.Address, repo.Owner, repo.Name) + // Including the case of the port + if strings.Contains(repo.Address, ":") { + remoteName = fmt.Sprintf("%s/%s/%s.git", repo.Address, repo.Owner, repo.Name) + } + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, remoteName), + DisableTrace: true, + }) + } else if repo.AuthType == types.PrivateAccessTokenAuthType { + u, err := url.Parse(repo.Address) + if err != nil { + log.Errorf("failed to parse url,err:%s", err) + } else { + cmds = append(cmds, &c.Command{ + Cmd: c.RemoteAdd(repo.RemoteName, OAuthCloneURL(repo.Source, repo.PrivateAccessToken, u.Host, repo.Owner, repo.Name, u.Scheme)), + DisableTrace: true, + }) + } + } + } else { + // github + cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, HTTPSCloneURL(repo.Source, repo.OauthToken, owner, repo.Name)), DisableTrace: true}) + } + + ref := repo.Ref() + if ref == "" { + return cmds + } + + cmds = append(cmds, &c.Command{Cmd: c.Fetch(repo.RemoteName, ref)}, &c.Command{Cmd: c.CheckoutHead()}) + + // PR rebase branch 请求 + if repo.PR > 0 && len(repo.Branch) > 0 { + newBranch := fmt.Sprintf("pr%d", repo.PR) + ref := fmt.Sprintf("%s:%s", repo.PRRef(), newBranch) + cmds = append( + cmds, + &c.Command{Cmd: c.DeepenedFetch(repo.RemoteName, repo.BranchRef())}, + &c.Command{Cmd: c.ResetMerge()}, + &c.Command{Cmd: c.DeepenedFetch(repo.RemoteName, ref)}, + &c.Command{Cmd: c.Merge(newBranch)}, + ) + } + + if repo.SubModules { + cmds = append(cmds, &c.Command{Cmd: c.UpdateSubmodules()}) + } + + cmds = append(cmds, &c.Command{Cmd: c.ShowLastLog()}) + + setCmdsWorkDir(workDir, cmds) + + return cmds +} + +func writeSSHFile(sshKey, hostName string) error { + if sshKey == "" { + return fmt.Errorf("ssh cannot be empty") + } + + if hostName == "" { + return fmt.Errorf("hostName cannot be empty") + } + + hostName = strings.Replace(hostName, ".", "", -1) + hostName = strings.Replace(hostName, ":", "", -1) + pathName := fmt.Sprintf("/.ssh/id_rsa.%s", hostName) + file := path.Join(config.Home(), pathName) + return ioutil.WriteFile(file, []byte(sshKey), 0400) +} + +func writeSSHConfigFile(hostNames sets.String, proxy *step.Proxy) error { + out := "\nHOST *\nStrictHostKeyChecking=no\nUserKnownHostsFile=/dev/null\n" + for _, hostName := range hostNames.List() { + name := strings.Replace(hostName, ".", "", -1) + name = strings.Replace(name, ":", "", -1) + out += fmt.Sprintf("\nHost %s\nIdentityFile ~/.ssh/id_rsa.%s\n", hostName, name) + if proxy.EnableRepoProxy && proxy.Type == "socks5" { + out = out + fmt.Sprintf("ProxyCommand nc -x %s %%h %%p\n", proxy.GetProxyURL()) + } + } + file := path.Join(config.Home(), "/.ssh/config") + return ioutil.WriteFile(file, []byte(out), 0600) +} + +// git@github.com or git@github.com:2000 +// return github.com +func getHost(address string) string { + address = strings.TrimPrefix(address, "ssh://") + address = strings.TrimPrefix(address, "git@") + hostArr := strings.Split(address, ":") + return hostArr[0] +} + +// SSHCloneURL returns Oauth clone url +// e.g. +//https://oauth2:ACCESS_TOKEN@somegitlab.com/owner/name.git +func OAuthCloneURL(source, token, address, owner, name, scheme string) string { + if strings.ToLower(source) == step.ProviderGitlab || strings.ToLower(source) == step.ProviderOther { + // address 需要传过来 + return fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", scheme, step.OauthTokenPrefix, token, address, owner, name) + } + // GITHUB + return "github" +} + +// HTTPSCloneURL returns HTTPS clone url +func HTTPSCloneURL(source, token, owner, name string) string { + if strings.ToLower(source) == step.ProviderGitlab { + return fmt.Sprintf("https://%s/%s/%s.git", "gitlab.koderover.com", owner, name) + } else if strings.ToLower(source) == step.ProviderGitee { + return fmt.Sprintf("https://%s:%s@%s/%s/%s.git", step.OauthTokenPrefix, token, "gitee.com", owner, name) + } + //return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", g.GetInstallationToken(owner), g.GetGithubHost(), owner, name) + return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", token, "github.com", owner, name) +} diff --git a/pkg/tool/dockerhost/docker_host.go b/pkg/tool/dockerhost/docker_host.go new file mode 100644 index 00000000..d6da5d43 --- /dev/null +++ b/pkg/tool/dockerhost/docker_host.go @@ -0,0 +1,201 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dockerhost + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/buraksezer/consistent" + "github.com/cespare/xxhash" + "go.uber.org/zap" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" +) + +var once sync.Once + +type Member string + +func (m Member) String() string { + return string(m) +} + +type hasher struct{} + +func (h hasher) Sum64(data []byte) uint64 { + return xxhash.Sum64(data) +} + +type ClusterID string + +type DockerHostsI interface { + GetBestHost(ClusterID, string) string + + Sync() +} + +type dockerhosts struct { + rwLock *sync.RWMutex + store map[ClusterID]*consistent.Consistent + hubServerAddr string + syncInterval time.Duration + + logger *zap.SugaredLogger +} + +var dockerHosts DockerHostsI + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func NewDockerHosts(hubServerAddr string, logger *zap.SugaredLogger) DockerHostsI { + once.Do(func() { + dockerHosts = &dockerhosts{ + rwLock: &sync.RWMutex{}, + store: map[ClusterID]*consistent.Consistent{}, + hubServerAddr: hubServerAddr, + logger: logger, + } + + go dockerHosts.Sync() + }) + + return dockerHosts +} + +func (d *dockerhosts) GetBestHost(clusterID ClusterID, key string) string { + if d.store[clusterID] == nil { + d.initClusterInfo(clusterID) + } + + member := d.store[clusterID].LocateKey([]byte(key)) + + return member.String() +} + +func (d *dockerhosts) initClusterInfo(clusterID ClusterID) { + d.rwLock.Lock() + defer d.rwLock.Unlock() + + members := d.getDockerHostsSvc(clusterID) + + cfg := consistent.Config{ + PartitionCount: 271, + ReplicationFactor: 20, + Load: 1.25, + Hasher: hasher{}, + } + + d.store[clusterID] = consistent.New(members, cfg) +} + +func (d *dockerhosts) getDockerHostsSvc(clusterID ClusterID) []consistent.Member { + ns := config.Namespace() + if string(clusterID) != setting.LocalClusterID { + ns = setting.AttachedClusterNamespace + } + + kclient, err := kubeclient.GetKubeClient(d.hubServerAddr, string(clusterID)) + if err != nil { + d.logger.Warnf("Failed to get kubeclient for cluster %q: %s. Try to use default dockerhosts.", clusterID, err) + return d.getDefaultDockerHosts() + } + + dindSts := &appsv1.StatefulSet{} + err = kclient.Get(context.TODO(), client.ObjectKey{ + Name: "dind", + Namespace: ns, + }, dindSts) + if err != nil { + d.logger.Warnf("Failed to get dind statefuleset in namespace %q of cluster %q: %s", ns, clusterID, err) + return d.getDefaultDockerHosts() + } + + members := []consistent.Member{} + for i := 0; i < int(*dindSts.Spec.Replicas); i++ { + members = append(members, d.genDindAddr(i)) + } + + return members +} + +func (d *dockerhosts) getDefaultDockerHosts() []consistent.Member { + return []consistent.Member{d.genDindAddr(0)} +} + +func (d *dockerhosts) genDindAddr(idx int) Member { + return Member(fmt.Sprintf("tcp://dind-%d.dind:2375", idx)) +} + +func (d *dockerhosts) Sync() { + d.logger.Info("Begin to sync") + + wait.Forever(func() { + d.rwLock.Lock() + defer d.rwLock.Unlock() + + for clusterID, consistentHash := range d.store { + currentMembers := d.getDockerHostsSvc(clusterID) + oldMembers := consistentHash.GetMembers() + + d.logger.Infof("Cluster: %q. Current Members: %d. Old Members: %d", clusterID, len(currentMembers), len(oldMembers)) + + addedMembers, deletedMembers := d.diffMembers(oldMembers, currentMembers) + for _, member := range addedMembers { + consistentHash.Add(member) + } + for _, member := range deletedMembers { + consistentHash.Remove(member.String()) + } + } + }, 3*time.Minute) +} + +func (d *dockerhosts) diffMembers(old, current []consistent.Member) (added, deleted []consistent.Member) { + curMap := make(map[consistent.Member]struct{}, len(current)) + for _, member := range current { + curMap[member] = struct{}{} + } + + oldMap := make(map[consistent.Member]struct{}, len(old)) + for _, member := range old { + oldMap[member] = struct{}{} + } + + for _, member := range old { + if _, found := curMap[member]; !found { + deleted = append(deleted, member) + } + } + for _, member := range current { + if _, found := oldMap[member]; !found { + added = append(added, member) + } + } + + return added, deleted +} diff --git a/pkg/tool/dockerhost/dockerhosts_test.go b/pkg/tool/dockerhost/dockerhosts_test.go new file mode 100644 index 00000000..6df1f4a3 --- /dev/null +++ b/pkg/tool/dockerhost/dockerhosts_test.go @@ -0,0 +1,50 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dockerhost + +import ( + "math/rand" + "testing" + "time" + + "go.uber.org/zap" +) + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) +} + +func TestNewDockerHosts(t *testing.T) { + hubServerAddr := "hub-server.zadig" + logger, _ := zap.NewProduction() + dockerhosts := NewDockerHosts(hubServerAddr, logger.Sugar()) + + clusters := []ClusterID{ClusterID("local"), ClusterID("worker0"), ClusterID("worker1")} + svcs := []string{"svc0", "svc1", "svc2", "svc3", "svc4"} + + for i := 0; i < len(clusters); i++ { + t.Logf("In Cluster %q\n", clusters[i]) + + for j := 0; j < 10; j++ { + svc := svcs[rand.Intn(len(svcs))] + host := dockerhosts.GetBestHost(clusters[i], svc) + t.Logf("\tHost for svc %q: %q\n", svc, host) + } + + t.Log("\n") + } +} diff --git a/pkg/types/step/step_docker_build.go b/pkg/types/step/step_docker_build.go new file mode 100644 index 00000000..16e6bc63 --- /dev/null +++ b/pkg/types/step/step_docker_build.go @@ -0,0 +1,55 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/setting" +) + +type StepDockerBuildSpec struct { + Source string `yaml:"source"` + WorkDir string `yaml:"work_dir"` + RegistryHost string `yaml:"registry_host"` + DockerFile string `yaml:"docker_file"` + ImageName string `yaml:"image_name"` + BuildArgs string `yaml:"build_args"` + ImageReleaseTag string `yaml:"image_release_tag"` + DockerTemplateContent string `yaml:"docker_template_content"` + Proxy *Proxy `yaml:"proxy"` + IgnoreCache bool `yaml:"ignore_cache"` + DockerRegistry *DockerRegistry `yaml:"docker_registry"` +} + +type DockerRegistry struct { + Host string `yaml:"host"` + Namespace string `yaml:"namespace"` + UserName string `yaml:"username"` + Password string `yaml:"password"` +} + +func (s *StepDockerBuildSpec) GetDockerFile() string { + // if the source of the dockerfile is from template, we write our own dockerfile + if s.Source == setting.DockerfileSourceTemplate { + return fmt.Sprintf("/%s", setting.ZadigDockerfilePath) + } + if s.DockerFile == "" { + return "Dockerfile" + } + return s.DockerFile +} diff --git a/pkg/types/step/step_git.go b/pkg/types/step/step_git.go new file mode 100644 index 00000000..0c577f18 --- /dev/null +++ b/pkg/types/step/step_git.go @@ -0,0 +1,150 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +import ( + "fmt" + "strings" + + "github.com/koderover/zadig/pkg/types" +) + +type StepGitSpec struct { + Repos []*Repo `yaml:"repos"` + Proxy *Proxy `yaml:"proxy"` +} + +type Repo struct { + Source string `yaml:"source"` + Address string `yaml:"address"` + CodeHostID int `yaml:"code_host_id"` + Owner string `yaml:"owner"` + Name string `yaml:"name"` + Namespace string `yaml:"namespace"` + RemoteName string `yaml:"remote_name"` + Branch string `yaml:"branch"` + PR int `yaml:"pr"` + Tag string `yaml:"tag"` + CheckoutPath string `yaml:"checkout_path"` + SubModules bool `yaml:"submodules"` + OauthToken string `yaml:"oauthToken"` + User string `yaml:"username"` + Password string `yaml:"password"` + CheckoutRef string `yaml:"checkout_ref"` + EnableProxy bool `yaml:"enable_proxy"` + AuthType types.AuthType `yaml:"auth_type,omitempty"` + SSHKey string `yaml:"ssh_key,omitempty"` + PrivateAccessToken string `yaml:"private_access_token,omitempty"` +} + +type Proxy struct { + Type string `yaml:"type"` + Address string `yaml:"address"` + Port int `yaml:"port"` + NeedPassword bool `yaml:"need_password"` + Username string `yaml:"username"` + Password string `yaml:"password"` + EnableRepoProxy bool `yaml:"enable_repo_proxy"` + EnableApplicationProxy bool `yaml:"enable_application_proxy"` +} + +const ( + // ProviderGithub ... + ProviderGithub = "github" + // ProviderGitlab ... + ProviderGitlab = "gitlab" + + // ProviderGerrit + ProviderGerrit = "gerrit" + + // ProviderCodehub + ProviderCodehub = "codehub" + + // ProviderGitee + ProviderGitee = "gitee" + + // ProviderOther + ProviderOther = "other" + + // Oauth prefix + OauthTokenPrefix = "oauth2" + + FileName = "reaper.tar.gz" +) + +func (p *Proxy) GetProxyURL() string { + var uri string + if p.NeedPassword { + uri = fmt.Sprintf("%s://%s:%s@%s:%d", + p.Type, + p.Username, + p.Password, + p.Address, + p.Port, + ) + return uri + } + + uri = fmt.Sprintf("%s://%s:%d", + p.Type, + p.Address, + p.Port, + ) + return uri +} + +// PRRef returns refs format +// It will check repo provider type, by default returns github refs format. +// +// e.g. github returns refs/pull/1/head +// e.g. gitlab returns merge-requests/1/head +func (r *Repo) PRRef() string { + if strings.ToLower(r.Source) == ProviderGitlab || strings.ToLower(r.Source) == ProviderCodehub { + return fmt.Sprintf("merge-requests/%d/head", r.PR) + } else if strings.ToLower(r.Source) == ProviderGerrit { + return r.CheckoutRef + } + return fmt.Sprintf("refs/pull/%d/head", r.PR) +} + +// BranchRef returns branch refs format +// e.g. refs/heads/master +func (r *Repo) BranchRef() string { + return fmt.Sprintf("refs/heads/%s", r.Branch) +} + +// TagRef returns the tag ref of current repo +// e.g. refs/tags/v1.0.0 +func (r *Repo) TagRef() string { + return fmt.Sprintf("refs/tags/%s", r.Tag) +} + +// Ref returns the changes ref of current repo in the following order: +// 1. tag ref +// 2. branch ref +// 3. pr ref +func (r *Repo) Ref() string { + if len(r.Tag) > 0 { + return r.TagRef() + } else if len(r.Branch) > 0 { + return r.BranchRef() + } else if r.PR > 0 { + return r.PRRef() + } + + return "" +} diff --git a/pkg/types/step/step.go b/pkg/types/step/step_shell.go similarity index 100% rename from pkg/types/step/step.go rename to pkg/types/step/step_shell.go -- Gitee From 42b4b33e4d1a549539d1cb56f33fef8eaca5e2bb Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 9 Jun 2022 11:16:25 +0800 Subject: [PATCH 2449/2647] add image filter logic when creating versions (#1656) * add image filter logic Signed-off-by: allenshen * add selected parameter Signed-off-by: allenshen --- .../aslan/core/delivery/service/version.go | 26 +++++++++++++------ .../aslan/core/environment/service/helm.go | 18 +++++++++++++ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 1e5fcaf6..52750a77 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -79,6 +79,7 @@ type CreateHelmDeliveryVersionOption struct { type ImageData struct { ImageName string `json:"imageName"` ImageTag string `json:"imageTag"` + Selected bool `json:"selected"` } type CreateHelmDeliveryVersionChartData struct { @@ -601,7 +602,9 @@ func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, target imageTagMap := make(map[string]string) for _, it := range imageData { - imageTagMap[it.ImageName] = it.ImageTag + if it.Selected { + imageTagMap[it.ImageName] = it.ImageTag + } } retValuesYaml := string(valuesYaml) @@ -630,19 +633,26 @@ func handleImageRegistry(valuesYaml []byte, chartData *DeliveryChartData, target return nil, nil, err } - registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) - if err != nil { - return nil, nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) - } - registryUrl = strings.TrimSuffix(registryUrl, "/") - imageName := commonservice.ExtractImageName(imageUrl) imageTag := commonservice.ExtractImageTag(imageUrl) - customTag := imageTag + + customTag := "" if ct, ok := imageTagMap[imageName]; ok { customTag = ct + } else { + continue + } + + if customTag == "" { + customTag = imageTag } + registryUrl, err := commonservice.ExtractImageRegistry(imageUrl) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to parse registry from image uri: %s", imageUrl) + } + registryUrl = strings.TrimSuffix(registryUrl, "/") + registryID := "" // used source registry if registry, ok := registryMap[registryUrl]; ok { diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index eb4c8129..b6e3617f 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -32,6 +32,7 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/releaseutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -138,6 +139,7 @@ func getReleaseStatus(re *release.Release) ReleaseStatus { type ImageData struct { ImageName string `json:"imageName"` ImageTag string `json:"imageTag"` + Selected bool `json:"selected"` } type ServiceImages struct { @@ -574,10 +576,25 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo Images: nil, } + allModules, err := commonrepo.NewBuildColl().List(&commonrepo.BuildListOption{ProductName: productName}) + if err != nil { + return nil, fmt.Errorf("failed to list builds for project: %s, err: %s", productName, err) + } + + containerNameSet := sets.NewString() + for _, build := range allModules { + for _, target := range build.Targets { + if target.ServiceName == svcName { + containerNameSet.Insert(target.ServiceModule) + } + } + } + for _, container := range prodSvc.Containers { if container.ImagePath == nil { return nil, fmt.Errorf("failed to parse image for container:%s", container.Image) } + imageSearchRule := &template.ImageSearchingRule{ Repo: container.ImagePath.Repo, Image: container.ImagePath.Image, @@ -592,6 +609,7 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo svcImage.Images = append(svcImage.Images, &ImageData{ util.GetImageNameFromContainerInfo(container.ImageName, container.Name), commonservice.ExtractImageTag(imageUrl), + containerNameSet.Has(container.ImageName), }) } ret.ServiceImages = append(ret.ServiceImages, svcImage) -- Gitee From 4044de7252badfe4349ab1d4109f2afe2c71d49a Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Thu, 9 Jun 2022 17:07:10 +0800 Subject: [PATCH 2450/2647] add resource limit for demos (#1662) Signed-off-by: fansi Co-authored-by: fansi --- examples/microservice-demo/chart/templates/backend.yaml | 4 ++++ examples/microservice-demo/chart/templates/frontend.yaml | 7 ++----- examples/microservice-demo/chart/values.yaml | 5 +++++ .../microservice-demo/k8s-yaml/backend/deployment.yaml | 4 ++++ .../microservice-demo/k8s-yaml/frontend/deployment.yaml | 3 --- .../multi-service-demo/k8s-yaml/service1/deployment.yaml | 4 ++++ .../multi-service-demo/k8s-yaml/service2/deployment.yaml | 4 ++++ .../multi-service-demo/k8s-yaml/service3/deployment.yaml | 4 ++++ examples/nginx/yaml/nginx.yaml | 4 ++++ examples/nginx/yaml/redis/redis.yaml | 2 +- .../freestyle-k8s-specifications/db/db-deployment.yaml | 5 +++++ .../redis/redis-deployment.yaml | 5 +++++ .../result/result-deployment.yaml | 4 ++++ .../freestyle-k8s-specifications/vote/vote-deployment.yaml | 4 ++++ .../worker/worker-deployment.yaml | 4 ++++ 15 files changed, 54 insertions(+), 9 deletions(-) diff --git a/examples/microservice-demo/chart/templates/backend.yaml b/examples/microservice-demo/chart/templates/backend.yaml index 0f40aa6a..fd2b2e98 100644 --- a/examples/microservice-demo/chart/templates/backend.yaml +++ b/examples/microservice-demo/chart/templates/backend.yaml @@ -26,6 +26,10 @@ spec: ports: - protocol: TCP containerPort: 20219 + resources: + limits: + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.memory }} imagePullSecrets: - name: registry-secret --- diff --git a/examples/microservice-demo/chart/templates/frontend.yaml b/examples/microservice-demo/chart/templates/frontend.yaml index 694148d7..e3194bdf 100644 --- a/examples/microservice-demo/chart/templates/frontend.yaml +++ b/examples/microservice-demo/chart/templates/frontend.yaml @@ -31,11 +31,8 @@ spec: containerPort: 80 resources: limits: - cpu: 1 - memory: 512Mi - requests: - cpu: 100m - memory: 100M + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.memory }} imagePullSecrets: - name: registry-secret --- diff --git a/examples/microservice-demo/chart/values.yaml b/examples/microservice-demo/chart/values.yaml index 9b7c7d26..58fc3cee 100644 --- a/examples/microservice-demo/chart/values.yaml +++ b/examples/microservice-demo/chart/values.yaml @@ -18,4 +18,9 @@ Frontend: domain: demo.domain.com port: 20219 +resources: + limits: + cpu: 50m + memory: 50Mi + registrySecret: "" diff --git a/examples/microservice-demo/k8s-yaml/backend/deployment.yaml b/examples/microservice-demo/k8s-yaml/backend/deployment.yaml index c0d617e8..63b62c65 100644 --- a/examples/microservice-demo/k8s-yaml/backend/deployment.yaml +++ b/examples/microservice-demo/k8s-yaml/backend/deployment.yaml @@ -26,5 +26,9 @@ spec: ports: - protocol: TCP containerPort: 20219 + resources: + limits: + memory: 50Mi + cpu: 50m imagePullSecrets: - name: default-registry-secret diff --git a/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml b/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml index 639e8a3f..f752fa25 100644 --- a/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml +++ b/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml @@ -31,9 +31,6 @@ spec: containerPort: 80 resources: limits: - cpu: 1 - memory: 512Mi - requests: cpu: 100m memory: 100M imagePullSecrets: diff --git a/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml index 8765d0d8..ae8d6e6d 100644 --- a/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml @@ -26,4 +26,8 @@ spec: ports: - protocol: TCP containerPort: 20221 + resources: + limits: + memory: 50Mi + cpu: 50m \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml index 8e643058..9c24eebf 100644 --- a/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml @@ -26,4 +26,8 @@ spec: ports: - protocol: TCP containerPort: 20222 + resources: + limits: + memory: 50Mi + cpu: 50m \ No newline at end of file diff --git a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml index 83466d5b..4248ddf7 100644 --- a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml @@ -26,4 +26,8 @@ spec: ports: - protocol: TCP containerPort: 20223 + resources: + limits: + memory: 50Mi + cpu: 50m \ No newline at end of file diff --git a/examples/nginx/yaml/nginx.yaml b/examples/nginx/yaml/nginx.yaml index b37915ec..2c16e8cf 100644 --- a/examples/nginx/yaml/nginx.yaml +++ b/examples/nginx/yaml/nginx.yaml @@ -41,6 +41,10 @@ spec: volumeMounts: - name: static-page mountPath: /usr/share/nginx/html + resources: + limits: + memory: 50Mi + cpu: 50m volumes: - name: static-page configMap: diff --git a/examples/nginx/yaml/redis/redis.yaml b/examples/nginx/yaml/redis/redis.yaml index f63f07cb..3d3f238d 100644 --- a/examples/nginx/yaml/redis/redis.yaml +++ b/examples/nginx/yaml/redis/redis.yaml @@ -22,7 +22,7 @@ spec: - name: master image: k8s.gcr.io/redis:e2e # or just image: redis resources: - requests: + limits: cpu: 100m memory: 100Mi ports: diff --git a/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml index 0ad914b0..6cd12f16 100644 --- a/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/db/db-deployment.yaml @@ -26,6 +26,11 @@ spec: env: - name: POSTGRES_HOST_AUTH_METHOD value: trust + resources: + limits: + memory: 50Mi + cpu: 50m volumes: - name: db-data emptyDir: {} + diff --git a/examples/voting-app/freestyle-k8s-specifications/redis/redis-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/redis/redis-deployment.yaml index 24aa5213..e341929d 100644 --- a/examples/voting-app/freestyle-k8s-specifications/redis/redis-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/redis/redis-deployment.yaml @@ -23,6 +23,11 @@ spec: volumeMounts: - mountPath: /data name: redis-data + resources: + limits: + memory: 50Mi + cpu: 50m volumes: - name: redis-data emptyDir: {} + diff --git a/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml index 585b8019..db31b845 100644 --- a/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml @@ -20,3 +20,7 @@ spec: ports: - containerPort: 80 name: result + resources: + limits: + memory: 50Mi + cpu: 50m diff --git a/examples/voting-app/freestyle-k8s-specifications/vote/vote-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/vote/vote-deployment.yaml index 4b28134b..5f7ca3b1 100644 --- a/examples/voting-app/freestyle-k8s-specifications/vote/vote-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/vote/vote-deployment.yaml @@ -22,3 +22,7 @@ spec: ports: - containerPort: 80 name: vote + resources: + limits: + memory: 50Mi + cpu: 50m diff --git a/examples/voting-app/freestyle-k8s-specifications/worker/worker-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/worker/worker-deployment.yaml index b48cd0b6..c2ac3d86 100644 --- a/examples/voting-app/freestyle-k8s-specifications/worker/worker-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/worker/worker-deployment.yaml @@ -17,3 +17,7 @@ spec: containers: - image: dockersamples/examplevotingapp_worker name: worker-e2e + resources: + limits: + memory: 50Mi + cpu: 50m -- Gitee From 7ac60c916acfe69fa3781fd545de1007dd19969a Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Mon, 13 Jun 2022 11:57:43 +0800 Subject: [PATCH 2451/2647] add resource limit for multi-service demo template (#1664) Signed-off-by: fansi Co-authored-by: fansi --- examples/multi-service-demo/k8s-yaml/template.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/multi-service-demo/k8s-yaml/template.yaml b/examples/multi-service-demo/k8s-yaml/template.yaml index b33b5163..d6f56181 100644 --- a/examples/multi-service-demo/k8s-yaml/template.yaml +++ b/examples/multi-service-demo/k8s-yaml/template.yaml @@ -26,6 +26,10 @@ spec: ports: - protocol: TCP containerPort: {{.port}} + resources: + limits: + memory: {{.memoryLimit}} + cpu: {{.cpuLimit}} --- apiVersion: v1 kind: Service -- Gitee From e05bcbb1e753d8a5961c1e26b922e53b307e1b30 Mon Sep 17 00:00:00 2001 From: flyer Date: Mon, 13 Jun 2022 15:44:51 +0800 Subject: [PATCH 2452/2647] support debug container based on EphemeralContainers (#1665) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- .../aslan/core/environment/handler/debug.go | 40 ++++ .../aslan/core/environment/handler/router.go | 2 + .../aslan/core/environment/service/debug.go | 182 ++++++++++++++++++ .../aslan/core/environment/service/ide.go | 1 - .../core/multicluster/handler/clusters.go | 7 + .../aslan/core/multicluster/handler/router.go | 2 + .../core/multicluster/service/clusters.go | 26 +++ .../policy/core/yamlconfig/metas.yaml | 4 +- pkg/shared/kube/wrapper/deployment.go | 1 + pkg/shared/kube/wrapper/pod.go | 24 ++- pkg/shared/kube/wrapper/statefulset.go | 1 + pkg/shared/kube/wrapper/utils.go | 33 ++++ pkg/types/env.go | 2 + 13 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 pkg/microservice/aslan/core/environment/handler/debug.go create mode 100644 pkg/microservice/aslan/core/environment/service/debug.go create mode 100644 pkg/shared/kube/wrapper/utils.go diff --git a/pkg/microservice/aslan/core/environment/handler/debug.go b/pkg/microservice/aslan/core/environment/handler/debug.go new file mode 100644 index 00000000..7bc446e1 --- /dev/null +++ b/pkg/microservice/aslan/core/environment/handler/debug.go @@ -0,0 +1,40 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/types" +) + +func PatchDebugContainer(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("env") + podName := c.Param("podName") + projectName := c.Query("projectName") + debugImage := c.Query("debugImage") + if debugImage == "" { + debugImage = types.DebugImage + } + + ctx.Err = service.PatchDebugContainer(c, projectName, envName, podName, debugImage) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 2cfc7f49..a54f440c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -107,6 +107,8 @@ func (*Router) Inject(router *gin.RouterGroup) { kube.GET("/pods/:podName/events", ListPodEvents) kube.GET("/workloads", ListWorkloads) kube.GET("/nodes", ListNodes) + + kube.POST("/:env/pods/:podName/debugcontainer", PatchDebugContainer) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/core/environment/service/debug.go b/pkg/microservice/aslan/core/environment/service/debug.go new file mode 100644 index 00000000..9bb10ebe --- /dev/null +++ b/pkg/microservice/aslan/core/environment/service/debug.go @@ -0,0 +1,182 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/discovery" + corev1client "k8s.io/client-go/kubernetes/typed/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" +) + +const ZadigDebugContainerName = "zadig-debug" +const K8sBetaVersionForEphemeralContainer = "v1.23" + +func PatchDebugContainer(ctx context.Context, projectName, envName, podName, debugImage string) error { + prod, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: projectName, + EnvName: envName, + }) + if err != nil { + return fmt.Errorf("failed to query env %q in project %q: %s", envName, projectName, err) + } + + clusterID := prod.ClusterID + ns := prod.Namespace + + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get kube client: %s", err) + } + + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get kube clientset: %s", err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return fmt.Errorf("failed to get rest config: %s", err) + } + + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + return fmt.Errorf("failed to get discovery client: %s", err) + } + + pod := &corev1.Pod{} + err = kclient.Get(ctx, client.ObjectKey{ + Name: podName, + Namespace: ns, + }, pod) + if err != nil { + return fmt.Errorf("failed to get pod %q in ns %q: %s", podName, ns, err) + } + + k8sVersion, err := checkK8sVersion(discoveryClient) + if err != nil { + return fmt.Errorf("failed to check K8s version: %s", err) + } + + debugContainer := genDebugContainer(debugImage) + if version.CompareKubeAwareVersionStrings(K8sBetaVersionForEphemeralContainer, k8sVersion) < 0 { + _, _, err = debugByEphemeralContainerLegacy(ctx, clientset.CoreV1(), pod, debugContainer) + } else { + _, _, err = debugByEphemeralContainer(ctx, clientset.CoreV1(), pod, debugContainer) + } + + return err +} + +func checkK8sVersion(client *discovery.DiscoveryClient) (string, error) { + serverInfo, err := client.ServerVersion() + if err != nil { + return "", err + } + + // Examples: v1.23.3, v1.20.6-tke.16 + items := strings.Split(serverInfo.GitVersion, ".") + if len(items) < 2 { + return "", fmt.Errorf("invalid server version format %q", serverInfo.GitVersion) + } + + return fmt.Sprintf("%s.%s", items[0], items[1]), nil +} + +func genDebugContainer(imageName string) *corev1.EphemeralContainer { + return &corev1.EphemeralContainer{ + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + Name: ZadigDebugContainerName, + Image: imageName, + Command: []string{"tail", "-f", "/dev/null"}, + ImagePullPolicy: corev1.PullAlways, + TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, + }, + } +} + +func debugByEphemeralContainerLegacy(ctx context.Context, podClient corev1client.CoreV1Interface, pod *corev1.Pod, + debugContainer *corev1.EphemeralContainer) (*corev1.Pod, string, error) { + + patch, err := json.Marshal([]map[string]interface{}{{ + "op": "add", + "path": "/ephemeralContainers/-", + "value": debugContainer, + }}) + if err != nil { + return nil, "", fmt.Errorf("error creating JSON 6902 patch for old /ephemeralcontainers API: %s", err) + } + + result := podClient.RESTClient().Patch(types.JSONPatchType). + Namespace(pod.Namespace). + Resource("pods"). + Name(pod.Name). + SubResource("ephemeralcontainers"). + Body(patch). + Do(ctx) + if err := result.Error(); err != nil { + return nil, "", err + } + + newPod, err := podClient.Pods(pod.Namespace).Get(ctx, pod.Name, metav1.GetOptions{}) + if err != nil { + return nil, "", err + } + + return newPod, debugContainer.Name, nil +} + +func debugByEphemeralContainer(ctx context.Context, podClient corev1client.CoreV1Interface, pod *corev1.Pod, + debugContainer *corev1.EphemeralContainer) (*corev1.Pod, string, error) { + podJS, err := json.Marshal(pod) + if err != nil { + return nil, "", fmt.Errorf("error creating JSON for pod: %v", err) + } + + debugPod := pod.DeepCopy() + debugPod.Spec.EphemeralContainers = append(debugPod.Spec.EphemeralContainers, *debugContainer) + debugJS, err := json.Marshal(debugPod) + if err != nil { + return nil, "", fmt.Errorf("error creating JSON for debug container: %v", err) + } + + patch, err := strategicpatch.CreateTwoWayMergePatch(podJS, debugJS, pod) + if err != nil { + return nil, "", fmt.Errorf("error creating patch to add debug container: %v", err) + } + + pods := podClient.Pods(pod.Namespace) + result, err := pods.Patch(ctx, pod.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}, "ephemeralcontainers") + if err != nil { + return nil, "", fmt.Errorf("failed to patch: %s", err) + } + + return result, debugContainer.Name, nil +} diff --git a/pkg/microservice/aslan/core/environment/service/ide.go b/pkg/microservice/aslan/core/environment/service/ide.go index be53b0e8..6cbfcc6d 100644 --- a/pkg/microservice/aslan/core/environment/service/ide.go +++ b/pkg/microservice/aslan/core/environment/service/ide.go @@ -422,7 +422,6 @@ func recoverWorkload(ctx context.Context, kclient client.Client, selector labels return err } case types.StatefulSetWorkload: - log.Infof("[ZADIG]Begin to recover statefulset: %s", selector.String()) err := recoverStatefulSet(ctx, kclient, selector, ns) if err != nil { return err diff --git a/pkg/microservice/aslan/core/multicluster/handler/clusters.go b/pkg/microservice/aslan/core/multicluster/handler/clusters.go index c6078c7f..38ed4baa 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/handler/clusters.go @@ -150,3 +150,10 @@ func UpgradeAgent(c *gin.Context) { ctx.Err = service.UpgradeAgent(c.Param("id"), ctx.Logger) } + +func CheckEphemeralContainers(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.CheckEphemeralContainers(c, c.Param("id")) +} diff --git a/pkg/microservice/aslan/core/multicluster/handler/router.go b/pkg/microservice/aslan/core/multicluster/handler/router.go index 3eda6da8..cf559894 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/router.go +++ b/pkg/microservice/aslan/core/multicluster/handler/router.go @@ -54,4 +54,6 @@ func (*Router) Inject(router *gin.RouterGroup) { router.GET("/:id/storageclasses", ListStorageClasses) router.GET("/:id/:namespace/pvcs", ListPVCs) + + router.GET("/:id/check/ephemeralcontainers", CheckEphemeralContainers) } diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index 29b35746..a1c04b32 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -34,6 +34,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/discovery" "sigs.k8s.io/controller-runtime/pkg/client" configbase "github.com/koderover/zadig/pkg/config" @@ -500,3 +501,28 @@ func ClusterApplyUpgradeAgent() { time.Sleep(20 * time.Second) } } + +func CheckEphemeralContainers(ctx context.Context, clusterID string) (bool, error) { + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return false, fmt.Errorf("failed to get rest config: %s", err) + } + + discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig) + if err != nil { + return false, fmt.Errorf("failed to new discovery client: %s", err) + } + + resource, err := discoveryClient.ServerResourcesForGroupVersion("v1") + if err != nil { + return false, fmt.Errorf("failed to get server resource: %s", err) + } + + for _, apiResource := range resource.APIResources { + if apiResource.Name == "pods/ephemeralcontainers" { + return true, nil + } + } + + return false, nil +} diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index f6dd712b..6ef88883 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -688,6 +688,8 @@ metas: rules: - method: GET endpoint: '/api/podexec/?*/?*/?*/?*/podExec/:name' + - method: POST + endpoint: '/api/aslan/environment/kube/:name/pods/?*/debugcontainer' - action: ssh_pm alias: 主机登录 description: '' @@ -729,4 +731,4 @@ metas: description: '' rules: - method: POST - endpoint: "/api/aslan/testing/scanning/?*/task" \ No newline at end of file + endpoint: "/api/aslan/testing/scanning/?*/task" diff --git a/pkg/shared/kube/wrapper/deployment.go b/pkg/shared/kube/wrapper/deployment.go index 1c0ec71a..5edfacf4 100644 --- a/pkg/shared/kube/wrapper/deployment.go +++ b/pkg/shared/kube/wrapper/deployment.go @@ -57,6 +57,7 @@ func (w *deployment) WorkloadResource(pods []*corev1.Pod) *resource.Workload { Pods: make([]*resource.Pod, 0, len(pods)), } + // Note: EphemeralContainers only belong to Pods and do not exist in Deployment Spec. for _, c := range w.Spec.Template.Spec.Containers { wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image, ImageName: util.ExtractImageName(c.Image)}) } diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index be001989..3b21f820 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -133,7 +133,13 @@ func (w *pod) Resource() *resource.Pod { p.Kind = w.OwnerReferences[0].Kind } - for _, container := range w.Status.ContainerStatuses { + containersStatus := []corev1.ContainerStatus{} + containersStatus = append(containersStatus, w.Status.ContainerStatuses...) + if checkEphemeralContainerStatusFieldExist(&w.Status) { + containersStatus = append(containersStatus, w.Status.EphemeralContainerStatuses...) + } + + for _, container := range containersStatus { cs := resource.Container{ Name: container.Name, RestartCount: container.RestartCount, @@ -175,6 +181,22 @@ func (w *pod) Resource() *resource.Pod { p.ContainerStatuses = append(p.ContainerStatuses, cs) } + // Note: Seems that in K8s versions [v1.16, v1.22], EphemeralContainerStatuses exist but are empty while in K8s versions + // [v1.23, ], EphemeralContainerStatuses exist and are not empty. + if checkEphemeralContainerStatusFieldExist(&w.Status) && len(w.Status.EphemeralContainerStatuses) == 0 && + checkEphemeralContainerFieldExist(&w.Spec) { + for _, container := range w.Spec.EphemeralContainers { + cs := resource.Container{ + Name: container.Name, + Image: container.Image, + RestartCount: 0, + Status: "running", + } + + p.ContainerStatuses = append(p.ContainerStatuses, cs) + } + } + if w.DeletionTimestamp != nil { p.Status = "Terminating" } diff --git a/pkg/shared/kube/wrapper/statefulset.go b/pkg/shared/kube/wrapper/statefulset.go index 7f4bb459..6f363282 100644 --- a/pkg/shared/kube/wrapper/statefulset.go +++ b/pkg/shared/kube/wrapper/statefulset.go @@ -56,6 +56,7 @@ func (w *statefulSet) WorkloadResource(pods []*corev1.Pod) *resource.Workload { Pods: make([]*resource.Pod, 0, len(pods)), } + // Note: EphemeralContainers only belong to Pods and do not exist in Deployment Spec. for _, c := range w.Spec.Template.Spec.Containers { wl.Images = append(wl.Images, resource.ContainerImage{Name: c.Name, Image: c.Image}) } diff --git a/pkg/shared/kube/wrapper/utils.go b/pkg/shared/kube/wrapper/utils.go new file mode 100644 index 00000000..ddb7ca4c --- /dev/null +++ b/pkg/shared/kube/wrapper/utils.go @@ -0,0 +1,33 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package wrapper + +import ( + "reflect" + + corev1 "k8s.io/api/core/v1" +) + +func checkEphemeralContainerFieldExist(podSpec *corev1.PodSpec) bool { + metaValue := reflect.ValueOf(podSpec).Elem() + return metaValue.FieldByName("EphemeralContainers") != (reflect.Value{}) +} + +func checkEphemeralContainerStatusFieldExist(podStatus *corev1.PodStatus) bool { + metaValue := reflect.ValueOf(podStatus).Elem() + return metaValue.FieldByName("EphemeralContainerStatuses") != (reflect.Value{}) +} diff --git a/pkg/types/env.go b/pkg/types/env.go index c741e87b..fceb394e 100644 --- a/pkg/types/env.go +++ b/pkg/types/env.go @@ -22,3 +22,5 @@ const ( GeneralEnv EnvType = "general" ShareEnv EnvType = "share" ) + +const DebugImage = "ccr.ccs.tencentyun.com/koderover-public/zadig-debug:v0.1.0" -- Gitee From 9014fa7f431b83af5f93faa0731d1acfea04336b Mon Sep 17 00:00:00 2001 From: flyer Date: Mon, 13 Jun 2022 16:38:04 +0800 Subject: [PATCH 2453/2647] add Dockerfile of debug image (#1668) Signed-off-by: zhangyifei add Dockerfile of debug image Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- Makefile | 16 ++++++++++++---- docker/service/zadig-debug.Dockerfile | 4 ++++ pkg/types/env.go | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 docker/service/zadig-debug.Dockerfile diff --git a/Makefile b/Makefile index a1a90631..efbc73fe 100644 --- a/Makefile +++ b/Makefile @@ -13,12 +13,12 @@ ALL_PUSH=$(TARGETS:=.push) PLATFORMS=darwin linux windows ARCHITECTURES=amd64 arm64 -all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 build-zgctl-all-platforms zgctl-sidecar.build.amd64 -all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 zgctl-sidecar.upload.amd64 +all: $(ALL_IMAGES:=.amd64) $(ALL_IMAGES:=.arm64) resource-server.build.amd64 resource-server.build.arm64 build-zgctl-all-platforms zgctl-sidecar.build.amd64 zadig-debug.build.amd64 +all.push: $(ALL_PUSH:=.amd64) $(ALL_PUSH:=.arm64) resource-server.upload.amd64 resource-server.upload.arm64 zgctl-sidecar.upload.amd64 zadig-debug.upload.amd64 -all.amd64: $(ALL_IMAGES:=.amd64) resource-server.build.amd64 zgctl-sidecar.build.amd64 +all.amd64: $(ALL_IMAGES:=.amd64) resource-server.build.amd64 zgctl-sidecar.build.amd64 zadig-debug.build.amd64 all.arm64: $(ALL_IMAGES:=.amd64) resource-server.build.arm64 -allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) resource-server.upload.amd64 zgctl-sidecar.upload.amd64 +allpush.amd64: $(ALL_PUSH:=.amd64) $(ALL_REAPER_PUSH:=.amd64) resource-server.upload.amd64 zgctl-sidecar.upload.amd64 zadig-debug.upload.amd64 allpush.arm64: $(ALL_PUSH:=.arm64) $(ALL_REAPER_PUSH:=.arm64) resource-server.upload.arm64 %.push.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/$*:${VERSION}-amd64 @@ -91,6 +91,14 @@ zgctl-sidecar.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zgctl-sidecar:${VE zgctl-sidecar.upload.amd64: zgctl-sidecar.build.amd64 @docker push ${MAKE_IMAGE} +zadig-debug.build.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zadig-debug:${VERSION}-amd64 +zadig-debug.build.amd64: + @docker build -f docker/service/zadig-debug.Dockerfile --tag ${MAKE_IMAGE} . + +zadig-debug.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/zadig-debug:${VERSION}-amd64 +zadig-debug.upload.amd64: zadig-debug.build.amd64 + @docker push ${MAKE_IMAGE} + .PHONY: clean clean: @rm -rf docker/dist diff --git a/docker/service/zadig-debug.Dockerfile b/docker/service/zadig-debug.Dockerfile new file mode 100644 index 00000000..192a4e6c --- /dev/null +++ b/docker/service/zadig-debug.Dockerfile @@ -0,0 +1,4 @@ +FROM alpine:3.16.0 + +RUN apk update && apk upgrade +RUN apk add bash curl wget iputils busybox-extras bind-tools tcpdump net-tools procps sysstat diff --git a/pkg/types/env.go b/pkg/types/env.go index fceb394e..1afe53a5 100644 --- a/pkg/types/env.go +++ b/pkg/types/env.go @@ -23,4 +23,4 @@ const ( ShareEnv EnvType = "share" ) -const DebugImage = "ccr.ccs.tencentyun.com/koderover-public/zadig-debug:v0.1.0" +const DebugImage = "ccr.ccs.tencentyun.com/koderover-rc/zadig-debug:v0.1.0" -- Gitee From e63cfdc602363fbd8cb7e79fe145a354b4b7729a Mon Sep 17 00:00:00 2001 From: flyer Date: Tue, 14 Jun 2022 14:21:52 +0800 Subject: [PATCH 2454/2647] support validating ephemeralcontainers in podexec (#1670) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/podexec/core/service/ws_terminal.go | 11 +++++++++++ pkg/shared/kube/wrapper/pod.go | 6 +++--- pkg/shared/kube/wrapper/utils.go | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/podexec/core/service/ws_terminal.go b/pkg/microservice/podexec/core/service/ws_terminal.go index 003ad1f5..69b4b523 100644 --- a/pkg/microservice/podexec/core/service/ws_terminal.go +++ b/pkg/microservice/podexec/core/service/ws_terminal.go @@ -36,6 +36,7 @@ import ( conf "github.com/koderover/zadig/pkg/microservice/podexec/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/kube/wrapper" "github.com/koderover/zadig/pkg/tool/log" ) @@ -157,11 +158,21 @@ func ValidatePod(kubeClient kubernetes.Interface, namespace, podName, containerN if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed { return false, fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase) } + for _, c := range pod.Spec.Containers { if containerName == c.Name { return true, nil } } + + if wrapper.CheckEphemeralContainerFieldExist(&pod.Spec) { + for _, c := range pod.Spec.EphemeralContainers { + if containerName == c.Name { + return true, nil + } + } + } + return false, fmt.Errorf("pod has no container '%s'", containerName) } diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index 3b21f820..5fbf906d 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -135,7 +135,7 @@ func (w *pod) Resource() *resource.Pod { containersStatus := []corev1.ContainerStatus{} containersStatus = append(containersStatus, w.Status.ContainerStatuses...) - if checkEphemeralContainerStatusFieldExist(&w.Status) { + if CheckEphemeralContainerStatusFieldExist(&w.Status) { containersStatus = append(containersStatus, w.Status.EphemeralContainerStatuses...) } @@ -183,8 +183,8 @@ func (w *pod) Resource() *resource.Pod { // Note: Seems that in K8s versions [v1.16, v1.22], EphemeralContainerStatuses exist but are empty while in K8s versions // [v1.23, ], EphemeralContainerStatuses exist and are not empty. - if checkEphemeralContainerStatusFieldExist(&w.Status) && len(w.Status.EphemeralContainerStatuses) == 0 && - checkEphemeralContainerFieldExist(&w.Spec) { + if CheckEphemeralContainerStatusFieldExist(&w.Status) && len(w.Status.EphemeralContainerStatuses) == 0 && + CheckEphemeralContainerFieldExist(&w.Spec) { for _, container := range w.Spec.EphemeralContainers { cs := resource.Container{ Name: container.Name, diff --git a/pkg/shared/kube/wrapper/utils.go b/pkg/shared/kube/wrapper/utils.go index ddb7ca4c..2ce244ed 100644 --- a/pkg/shared/kube/wrapper/utils.go +++ b/pkg/shared/kube/wrapper/utils.go @@ -22,12 +22,12 @@ import ( corev1 "k8s.io/api/core/v1" ) -func checkEphemeralContainerFieldExist(podSpec *corev1.PodSpec) bool { +func CheckEphemeralContainerFieldExist(podSpec *corev1.PodSpec) bool { metaValue := reflect.ValueOf(podSpec).Elem() return metaValue.FieldByName("EphemeralContainers") != (reflect.Value{}) } -func checkEphemeralContainerStatusFieldExist(podStatus *corev1.PodStatus) bool { +func CheckEphemeralContainerStatusFieldExist(podStatus *corev1.PodStatus) bool { metaValue := reflect.ValueOf(podStatus).Elem() return metaValue.FieldByName("EphemeralContainerStatuses") != (reflect.Value{}) } -- Gitee From 3e6378cb7647de20fc5b314ff846cd3b91146f0b Mon Sep 17 00:00:00 2001 From: flyer Date: Tue, 14 Jun 2022 14:48:37 +0800 Subject: [PATCH 2455/2647] add enable_debug_container field when listing workload info (#1671) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/shared/kube/resource/pod.go | 21 +++++++++++---------- pkg/shared/kube/wrapper/pod.go | 4 ++++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/pkg/shared/kube/resource/pod.go b/pkg/shared/kube/resource/pod.go index a62a7a8c..78737c40 100644 --- a/pkg/shared/kube/resource/pod.go +++ b/pkg/shared/kube/resource/pod.go @@ -17,16 +17,17 @@ limitations under the License. package resource type Pod struct { - Kind string `json:"kind"` - Name string `json:"name"` - Status string `json:"status"` - Age string `json:"age"` - CreateTime int64 `json:"createtime"` - IP string `json:"ip"` - Labels map[string]string `json:"labels"` - ContainerStatuses []Container `json:"containers"` - NodeName string `json:"node_name"` - HostIP string `json:"host_ip"` + Kind string `json:"kind"` + Name string `json:"name"` + Status string `json:"status"` + Age string `json:"age"` + CreateTime int64 `json:"createtime"` + IP string `json:"ip"` + Labels map[string]string `json:"labels"` + ContainerStatuses []Container `json:"containers"` + NodeName string `json:"node_name"` + HostIP string `json:"host_ip"` + EnableDebugContainer bool `json:"enable_debug_container"` } type Container struct { diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index 5fbf906d..8f8d73a8 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -210,5 +210,9 @@ func (w *pod) Resource() *resource.Pod { } } + if CheckEphemeralContainerFieldExist(&w.Spec) && len(w.Spec.EphemeralContainers) > 0 { + p.EnableDebugContainer = true + } + return p } -- Gitee From 76dead68f97c4598a1e51704d3a27f0acc952105 Mon Sep 17 00:00:00 2001 From: flyer Date: Tue, 14 Jun 2022 15:58:37 +0800 Subject: [PATCH 2456/2647] check whether user has canceled task when touching msg (#1673) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- .../service/taskcontroller/task_handler.go | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go index a3886d6a..c0ecadb8 100644 --- a/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go +++ b/pkg/microservice/warpdrive/core/service/taskcontroller/task_handler.go @@ -82,18 +82,23 @@ func (h *ExecHandler) HandleMessage(message *nsq.Message) error { xl = Logger(pipelineTask) ctx, cancel = context.WithCancel(context.Background()) - go func(taskName string) { + go func(ctx context.Context, taskName string) { for { - if pipelineTask == nil { - xl.Infof("Pipeline task %q has completed. Exit.", taskName) - break - } + select { + case <-ctx.Done(): + xl.Infof("Pipeline task %q has been canceled. Exit.", taskName) + return + case <-time.After(durationTouchMsg): + if pipelineTask == nil { + xl.Infof("Pipeline task %q has completed. Exit.", taskName) + return + } - <-time.After(durationTouchMsg) - xl.Infof("After %s, touch message %q.", durationTouchMsg.String(), taskName) - message.Touch() + xl.Infof("After %s, touch message %q.", durationTouchMsg.String(), taskName) + message.Touch() + } } - }(taskName) + }(ctx, taskName) h.runPipelineTask(ctx, cancel, xl) -- Gitee From 1b3b5fd2fbb15072baccf344b1ac4c4763781c0e Mon Sep 17 00:00:00 2001 From: flyer Date: Tue, 14 Jun 2022 16:25:52 +0800 Subject: [PATCH 2457/2647] add EphemeralContainer image info when listing workloads (#1674) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/shared/kube/wrapper/pod.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index 8f8d73a8..c33caea1 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -178,6 +178,16 @@ func (w *pod) Resource() *resource.Pod { break } } + + if CheckEphemeralContainerFieldExist(&w.Spec) { + for _, specContainer := range w.Spec.EphemeralContainers { + if specContainer.Name == container.Name { + cs.Image = specContainer.Image + break + } + } + } + p.ContainerStatuses = append(p.ContainerStatuses, cs) } -- Gitee From b9027ef6950597deb407beaa6909c426a8814e34 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 15 Jun 2022 10:58:03 +0800 Subject: [PATCH 2458/2647] fix registry error when running workflows (#1676) * fix registry error when running workflows Signed-off-by: allenshen * fix registry error when running workflows Signed-off-by: allenshen --- .../service/workflow/workflow_task.go | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 36267abf..b02e0125 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -658,6 +658,11 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, env.RegistryID = reg.ID.Hex() } configPayload.RegistryID = env.RegistryID + err = SetCandidateRegistry(configPayload, log) + if err != nil { + log.Errorf("workflow_task setCandidateRegistry configPayload:[%v] err:%v", configPayload, err) + return nil, err + } nextTaskID, err := generateNextTaskID(args.WorkflowName) if err != nil { @@ -858,12 +863,6 @@ func CreateWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator string, return nil, e.ErrCreateTask.AddDesc(err.Error()) } - err = SetCandidateRegistry(configPayload, log) - if err != nil { - log.Errorf("workflow_task setCandidateRegistry configPayload:[%v] err:%v", configPayload, err) - return nil, err - } - AddSubtaskToStage(&stages, testSubTask, testTask.TestModuleName) } @@ -1824,6 +1823,12 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator } } + err = SetCandidateRegistry(configPayload, log) + if err != nil { + log.Errorf("workflow_task setCandidateRegistry configPayload:[%v] err:%v", configPayload, err) + return nil, err + } + configPayload.IgnoreCache = args.IgnoreCache configPayload.ResetCache = args.ResetCache @@ -1991,13 +1996,6 @@ func CreateArtifactWorkflowTask(args *commonmodels.WorkflowTaskArgs, taskCreator log.Errorf("workflow_task ToSubTask err:%v", err) return nil, e.ErrCreateTask.AddDesc(err.Error()) } - - err = SetCandidateRegistry(configPayload, log) - if err != nil { - log.Errorf("workflow_task setCandidateRegistry configPayload:[%v] err:%v", configPayload, err) - return nil, err - } - AddSubtaskToStage(&stages, testSubTask, testTask.TestModuleName) } @@ -2709,17 +2707,11 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err } } - err = SetCandidateRegistry(taskOpt.Task.ConfigPayload, log) - if err != nil { - return err - } - taskOpt.Task.SubTasks[i], err = t.ToSubTask() if err != nil { return err } } - case config.TaskTestingV2: t, err := base.ToTestingTask(subTask) if err != nil { @@ -2740,11 +2732,6 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err setManunalBuilds(t.JobCtx.Builds, taskOpt.Task.TaskArgs.Test.Builds, log) } - err = SetCandidateRegistry(taskOpt.Task.ConfigPayload, log) - if err != nil { - return err - } - //use log path if taskOpt.Task.ServiceName == "" { taskOpt.Task.ServiceName = t.TestName -- Gitee From 90ff8c696b23b95970dd411c1370e570b76543f2 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 15 Jun 2022 11:20:24 +0800 Subject: [PATCH 2459/2647] fix resource label miss bug (#1672) * fix resource label miss bug Signed-off-by: allenshen * fix resource label miss bug Signed-off-by: allenshen * fix secret and cm config error Signed-off-by: allenshen --- .../environment/service/common_env_cfg.go | 52 ++++++++++++++----- .../core/environment/service/configmap.go | 12 ++++- .../aslan/core/environment/service/ingress.go | 4 ++ .../aslan/core/environment/service/pvc.go | 4 ++ .../aslan/core/environment/service/secret.go | 12 ++++- 5 files changed, 68 insertions(+), 16 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index ae0d74c1..a9101c64 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -41,6 +41,11 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +type ResourceWithLabel interface { + GetLabels() map[string]string + SetLabels(labels map[string]string) +} + func DeleteCommonEnvCfg(envName, productName, objectName string, commonEnvCfgType config.CommonEnvCfgType, log *zap.SugaredLogger) error { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: productName, @@ -86,6 +91,23 @@ type CreateCommonEnvCfgArgs struct { CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type"` } +// ensureLabel ensure label 's-product' exists in particular resource(secret/configmap) deployed by zadig +func ensureLabel(res ResourceWithLabel, projectName string) (string, error) { + resLabels := res.GetLabels() + if resLabels == nil { + resLabels = make(map[string]string) + } + resLabels[setting.ProductLabel] = projectName + res.SetLabels(resLabels) + + jsonBytes, err := json.Marshal(res) + if err != nil { + return "", err + } + yamlBytes, err := yaml.JSONToYAML(jsonBytes) + return string(yamlBytes), err +} + func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { js, err := yaml.YAMLToJSON([]byte(args.YamlData)) if err != nil { @@ -130,24 +152,25 @@ func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, l return e.ErrUpdateResource.AddErr(err) } cm.Namespace = product.Namespace - addLables := cm.GetLabels() - if addLables == nil { - addLables = make(map[string]string) + + yamlData, err := ensureLabel(cm, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) } - addLables[string(setting.ProductLabel)] = args.ProductName - cm.SetLabels(addLables) + if err := updater.CreateConfigMap(cm, kubeClient); err != nil { log.Error(err) return e.ErrUpdateResource.AddErr(err) } - envcm := &models.EnvConfigMap{ + + envCM := &models.EnvConfigMap{ ProductName: args.ProductName, UpdateUserName: userName, EnvName: args.EnvName, Name: cm.Name, - YamlData: args.YamlData, + YamlData: yamlData, } - if err = commonrepo.NewConfigMapColl().Create(envcm, true); err != nil { + if err = commonrepo.NewConfigMapColl().Create(envCM, true); err != nil { return e.ErrUpdateResource.AddDesc(err.Error()) } case config.CommonEnvCfgTypeSecret: @@ -157,22 +180,23 @@ func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, l return e.ErrUpdateResource.AddErr(err) } secret.Namespace = product.Namespace - addLables := secret.GetLabels() - if addLables == nil { - addLables = make(map[string]string) + + yamlData, err := ensureLabel(secret, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) } - addLables[string(setting.ProductLabel)] = args.ProductName - secret.SetLabels(addLables) + if err := updater.UpdateOrCreateSecret(secret, kubeClient); err != nil { log.Error(err) return e.ErrUpdateResource.AddDesc(err.Error()) } + envSecret := &models.EnvSecret{ ProductName: args.ProductName, UpdateUserName: userName, EnvName: args.EnvName, Name: secret.Name, - YamlData: args.YamlData, + YamlData: yamlData, } if err = commonrepo.NewSecretColl().Create(envSecret, true); err != nil { return e.ErrUpdateResource.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index e23984e9..12e40a2b 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -199,6 +199,10 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListCon }(cmElem) } wg.Wait() + + sort.SliceStable(res, func(i, j int) bool { + return res[i].CmName < res[j].CmName + }) return res, nil } @@ -239,6 +243,12 @@ func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) return e.ErrUpdateConfigMap.AddErr(err) } + + yamlData, err := ensureLabel(cm, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + if err := updater.UpdateConfigMap(namespace, cm, clientset); err != nil { log.Error(err) return e.ErrUpdateConfigMap.AddDesc(err.Error()) @@ -248,7 +258,7 @@ func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log UpdateUserName: userName, EnvName: args.EnvName, Name: cm.Name, - YamlData: args.YamlData, + YamlData: yamlData, } if err := commonrepo.NewConfigMapColl().Create(envcm, true); err != nil { return e.ErrUpdateConfigMap.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/ingress.go b/pkg/microservice/aslan/core/environment/service/ingress.go index 3750e326..435664fb 100644 --- a/pkg/microservice/aslan/core/environment/service/ingress.go +++ b/pkg/microservice/aslan/core/environment/service/ingress.go @@ -19,6 +19,7 @@ package service import ( "encoding/json" "fmt" + "sort" "strings" "time" @@ -170,6 +171,9 @@ func ListIngresses(envName, productName string, log *zap.SugaredLogger) ([]*List res = append(res, resElem) } } + sort.SliceStable(res, func(i, j int) bool { + return res[i].IngressName < res[j].IngressName + }) return res, nil } diff --git a/pkg/microservice/aslan/core/environment/service/pvc.go b/pkg/microservice/aslan/core/environment/service/pvc.go index c166d3af..3dfa07dd 100644 --- a/pkg/microservice/aslan/core/environment/service/pvc.go +++ b/pkg/microservice/aslan/core/environment/service/pvc.go @@ -18,6 +18,7 @@ package service import ( "encoding/json" + "sort" "sync" "time" @@ -142,6 +143,9 @@ func ListPvcs(envName, productName string, log *zap.SugaredLogger) ([]*ListPvcsR }(pvcElem) } wg.Wait() + sort.SliceStable(res, func(i, j int) bool { + return res[i].PvcName < res[j].PvcName + }) return res, nil } diff --git a/pkg/microservice/aslan/core/environment/service/secret.go b/pkg/microservice/aslan/core/environment/service/secret.go index 9f1c7dfd..d6d6a591 100644 --- a/pkg/microservice/aslan/core/environment/service/secret.go +++ b/pkg/microservice/aslan/core/environment/service/secret.go @@ -18,6 +18,7 @@ package service import ( "encoding/json" + "sort" "sync" "time" @@ -117,6 +118,9 @@ func ListSecrets(envName, productName string, log *zap.SugaredLogger) ([]*ListSe }(secret) } wg.Wait() + sort.SliceStable(res, func(i, j int) bool { + return res[i].SecretName < res[j].SecretName + }) return res, nil } @@ -144,6 +148,12 @@ func UpdateSecret(args *UpdateCommonEnvCfgArgs, userName, userID string, log *za return e.ErrUpdateResource.AddErr(err) } secret.Namespace = product.Namespace + + yamlData, err := ensureLabel(secret, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + err = updater.UpdateOrCreateSecret(secret, kubeClient) if err != nil { log.Error(err) @@ -154,7 +164,7 @@ func UpdateSecret(args *UpdateCommonEnvCfgArgs, userName, userID string, log *za UpdateUserName: userName, EnvName: args.EnvName, Name: secret.Name, - YamlData: args.YamlData, + YamlData: yamlData, } if commonrepo.NewSecretColl().Create(envSecret, true) != nil { return e.ErrUpdateResource.AddDesc(err.Error()) -- Gitee From 35272276d4a68d0114e2a5414ec12b2e863d544d Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 15 Jun 2022 23:47:59 +0800 Subject: [PATCH 2460/2647] cvm resource management (#1663) * supports project-level host resource management Signed-off-by: liu deyi * optimize query logic Signed-off-by: liu deyi * optimize update private key logic Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * optimize code Signed-off-by: liu deyi * update list private key internal query logic Signed-off-by: allenshen * update project host logic Signed-off-by: allenshen * update pm list query condition Signed-off-by: allenshen * update private key query condition Signed-off-by: allenshen * update url.yamle Signed-off-by: allenshen * update url auth config Signed-off-by: allenshen * add unique index for table private key Signed-off-by: allenshen * update file name Signed-off-by: allenshen Co-authored-by: liu deyi --- .../common/repository/models/private_key.go | 1 + .../common/repository/mongodb/private_key.go | 32 +++- .../aslan/core/project/handler/host.go | 146 ++++++++++++++++++ .../aslan/core/project/handler/router.go | 11 ++ .../aslan/core/project/service/product.go | 7 + .../aslan/core/service/service/service.go | 6 +- .../aslan/core/system/handler/private_key.go | 2 +- .../aslan/core/system/service/private_key.go | 59 ++++--- .../policy/core/yamlconfig/urls.yaml | 15 ++ 9 files changed, 246 insertions(+), 33 deletions(-) create mode 100644 pkg/microservice/aslan/core/project/handler/host.go diff --git a/pkg/microservice/aslan/core/common/repository/models/private_key.go b/pkg/microservice/aslan/core/common/repository/models/private_key.go index 2e0723b7..2cfc60eb 100644 --- a/pkg/microservice/aslan/core/common/repository/models/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/models/private_key.go @@ -37,6 +37,7 @@ type PrivateKey struct { UpdateBy string `bson:"update_by" json:"update_by"` Provider int8 `bson:"provider" json:"provider"` Probe *types.Probe `bson:"probe" json:"probe"` + ProjectName string `bson:"project_name,omitempty" json:"project_name"` UpdateStatus bool `bson:"-" json:"update_status"` } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index 135ccce7..cc7e47b8 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -32,7 +32,8 @@ import ( ) type PrivateKeyArgs struct { - Name string + Name string + ProjectName string } type PrivateKeyColl struct { @@ -54,12 +55,18 @@ func (c *PrivateKeyColl) GetCollectionName() string { } func (c *PrivateKeyColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.M{"label": 1}, - Options: options.Index().SetUnique(false), + mods := []mongo.IndexModel{ + { + Keys: bson.M{"label": 1}, + Options: options.Index().SetUnique(false), + }, + { + Keys: bson.M{"name": 1}, + Options: options.Index().SetUnique(true), + }, } - _, err := c.Indexes().CreateOne(ctx, mod) + _, err := c.Indexes().CreateMany(ctx, mods) return err } @@ -91,6 +98,13 @@ func (c *PrivateKeyColl) List(args *PrivateKeyArgs) ([]*models.PrivateKey, error if args.Name != "" { query["name"] = args.Name } + + if args.ProjectName != "" { + query["project_name"] = args.ProjectName + } else { + query["project_name"] = bson.M{"$exists": false} + } + resp := make([]*models.PrivateKey, 0) ctx := context.Background() @@ -168,6 +182,14 @@ func (c *PrivateKeyColl) Delete(id string) error { return err } +func (c *PrivateKeyColl) BulkDelete(projectName string) error { + query := bson.M{} + query["project_name"] = projectName + + _, err := c.DeleteMany(context.TODO(), query) + return err +} + func (c *PrivateKeyColl) DeleteAll() error { _, err := c.DeleteMany(context.TODO(), bson.M{}) return err diff --git a/pkg/microservice/aslan/core/project/handler/host.go b/pkg/microservice/aslan/core/project/handler/host.go new file mode 100644 index 00000000..b453a0c1 --- /dev/null +++ b/pkg/microservice/aslan/core/project/handler/host.go @@ -0,0 +1,146 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" +) + +type batchCreatePMArgs struct { + Option string `json:"option"` + Data []*commonmodels.PrivateKey `json:"data"` +} + +func ListPMHosts(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + encryptedKey := c.Query("encryptedKey") + if len(encryptedKey) == 0 { + ctx.Err = e.ErrInvalidParam + return + } + ctx.Resp, ctx.Err = systemservice.ListPrivateKeys(encryptedKey, c.Query("projectName"), c.Query("keyword"), ctx.Logger) +} + +func GetPMHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.GetPrivateKey(c.Param("id"), ctx.Logger) +} + +func ListLabels(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = service.ListLabels() +} + +func CreatePMHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.PrivateKey) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreatePMHost c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("CreatePMHost json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "项目资源-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + if err := c.ShouldBindWith(&args, binding.JSON); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid physical machine args") + return + } + args.UpdateBy = ctx.UserName + args.ProjectName = c.Query("projectName") + ctx.Err = systemservice.CreatePrivateKey(args, ctx.Logger) +} + +func BatchCreatePMHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(batchCreatePMArgs) + data, err := c.GetRawData() + if err != nil { + log.Errorf("BatchCreatePMHost c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("BatchCreatePMHost json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "批量新增", "项目资源-主机管理", "", string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid physical machine args") + return + } + for _, pmArg := range args.Data { + pmArg.ProjectName = c.Query("projectName") + } + ctx.Err = systemservice.BatchCreatePrivateKey(args.Data, args.Option, ctx.UserName, ctx.Logger) +} + +func UpdatePMHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.PrivateKey) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdatePhysicalHost c.GetRawData() err : %v", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("UpdatePhysicalHost json.Unmarshal err : %v", err) + } + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "项目资源-主机管理", fmt.Sprintf("hostName:%s ip:%s", args.Name, args.IP), string(data), ctx.Logger) + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindWith(&args, binding.JSON); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid physical machine args") + return + } + args.UpdateBy = ctx.UserName + + ctx.Err = systemservice.UpdatePrivateKey(c.Param("id"), args, ctx.Logger) +} + +func DeletePMHost(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "项目资源-主机管理", fmt.Sprintf("id:%s", c.Param("id")), "", ctx.Logger) + ctx.Err = systemservice.DeletePrivateKey(c.Param("id"), ctx.UserName, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index d8a5c4d1..5eeef72a 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -64,4 +64,15 @@ func (*Router) Inject(router *gin.RouterGroup) { { project.GET("", ListProjects) } + + pms := router.Group("pms") + { + pms.GET("", ListPMHosts) + pms.GET("/:id", GetPMHost) + pms.GET("/labels", ListLabels) + pms.POST("", gin2.UpdateOperationLogStatus, CreatePMHost) + pms.POST("/batch", gin2.UpdateOperationLogStatus, BatchCreatePMHost) + pms.PUT("/:id", gin2.UpdateOperationLogStatus, UpdatePMHost) + pms.DELETE("/:id", gin2.UpdateOperationLogStatus, DeletePMHost) + } } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index fdabfdb4..88967798 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -491,6 +491,13 @@ func DeleteProductTemplate(userName, productName, requestID string, isDelete boo }) }() + // delete privateKey data + go func() { + if err = commonrepo.NewPrivateKeyColl().BulkDelete(productName); err != nil { + log.Errorf("failed to bulk delete privateKey, error:%s", err) + } + }() + return nil } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 56fe6582..f227ce93 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -878,8 +878,8 @@ func UpdateServiceHealthCheckStatus(args *commonservice.ServiceTmplObject) error log.Errorf("Can not find service with option %+v. Error: %s", args, err) return err } - changeEnvStatus := []*commonmodels.EnvStatus{} - changeEnvConfigs := []*commonmodels.EnvConfig{} + var changeEnvStatus []*commonmodels.EnvStatus + var changeEnvConfigs []*commonmodels.EnvConfig changeEnvConfigs = append(changeEnvConfigs, args.EnvConfigs...) envConfigsSet := sets.String{} @@ -891,7 +891,7 @@ func UpdateServiceHealthCheckStatus(args *commonservice.ServiceTmplObject) error changeEnvConfigs = append(changeEnvConfigs, v) } } - privateKeys := []*commonmodels.PrivateKey{} + var privateKeys []*commonmodels.PrivateKey for _, envConfig := range args.EnvConfigs { privateKeys, err = commonrepo.NewPrivateKeyColl().ListHostIPByArgs(&commonrepo.ListHostIPArgs{IDs: envConfig.HostIDs}) if err != nil { diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index d922aab9..265c75ba 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -48,7 +48,7 @@ func ListPrivateKeys(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListPrivateKeys(encryptedKey, ctx.Logger) + ctx.Resp, ctx.Err = service.ListPrivateKeys(encryptedKey, "", c.Query("keyword"), ctx.Logger) } func GetPrivateKey(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index b7e79124..64247234 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -17,6 +17,9 @@ limitations under the License. package service import ( + "fmt" + "strings" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" @@ -31,12 +34,25 @@ import ( "github.com/koderover/zadig/pkg/types" ) -func ListPrivateKeys(encryptedKey string, log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { - resp, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{}) +func ListPrivateKeys(encryptedKey, projectName, keyword string, log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { + var resp []*commonmodels.PrivateKey + var err error + privateKeys, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{ProjectName: projectName}) if err != nil { - log.Errorf("PrivateKey.List error: %v", err) + log.Errorf("PrivateKey.List error: %s", err) return resp, e.ErrListPrivateKeys } + + if keyword == "" { + resp = privateKeys + } else { + for _, privateKey := range privateKeys { + if strings.Contains(privateKey.Name, keyword) || strings.Contains(privateKey.IP, keyword) { + resp = append(resp, privateKey) + } + } + } + aesKey, err := commonservice.GetAesKeyFromEncryptedKey(encryptedKey, log) if err != nil { return nil, err @@ -81,28 +97,38 @@ func CreatePrivateKey(args *commonmodels.PrivateKey, log *zap.SugaredLogger) err return e.ErrCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") } - if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{Name: args.Name}); len(privateKeys) > 0 { + privateKeyArgs := &commonrepo.PrivateKeyArgs{ + Name: args.Name, + } + + if privateKeys, _ := commonrepo.NewPrivateKeyColl().List(privateKeyArgs); len(privateKeys) > 0 { return e.ErrCreatePrivateKey.AddDesc("Name already exists") } err := commonrepo.NewPrivateKeyColl().Create(args) if err != nil { - log.Errorf("PrivateKey.Create error: %v", err) + log.Errorf("failed to create privateKey, error: %s", err) return e.ErrCreatePrivateKey } return nil } func UpdatePrivateKey(id string, args *commonmodels.PrivateKey, log *zap.SugaredLogger) error { - err := commonrepo.NewPrivateKeyColl().Update(id, args) + _, err := commonrepo.NewPrivateKeyColl().Find(commonrepo.FindPrivateKeyOption{ID: id}) if err != nil { - log.Errorf("PrivateKey.Update %s error: %v", id, err) - return e.ErrUpdatePrivateKey + log.Errorf("failed to find privateKey with id: %s, error: %s", id, err) + return e.ErrUpdatePrivateKey.AddErr(fmt.Errorf("failed to find privateKey with id: %s, err: %s", id, err)) + } + + err = commonrepo.NewPrivateKeyColl().Update(id, args) + if err != nil { + log.Errorf("failed to update privateKey, error: %s", err) + return e.ErrUpdatePrivateKey.AddErr(err) } return nil } -func DeletePrivateKey(id string, userName string, log *zap.SugaredLogger) error { +func DeletePrivateKey(id, userName string, log *zap.SugaredLogger) error { // 检查该私钥是否被引用 buildOpt := &commonrepo.BuildListOption{PrivateKeyID: id} builds, err := commonrepo.NewBuildColl().List(buildOpt) @@ -177,20 +203,6 @@ func ListLabels() ([]string, error) { // patch: Overwrite existing func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username string, log *zap.SugaredLogger) error { switch option { - //case "override": - // if err := commonrepo.NewPrivateKeyColl().DeleteAll(); err != nil { - // return e.ErrBulkCreatePrivateKey.AddDesc("delete all privateKeys failed") - // } - // for _, currentPrivateKey := range args { - // if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { - // return e.ErrBulkCreatePrivateKey.AddDesc("主机名称仅支持字母,数字和下划线且首个字符不以数字开头") - // } - // currentPrivateKey.UpdateBy = username - // if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { - // log.Errorf("PrivateKey.Create error: %s", err) - // return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") - // } - // } case "increment": for _, currentPrivateKey := range args { if !config.CVMNameRegex.MatchString(currentPrivateKey.Name) { @@ -221,7 +233,6 @@ func BatchCreatePrivateKey(args []*commonmodels.PrivateKey, option, username str } continue } - if err := commonrepo.NewPrivateKeyColl().Create(currentPrivateKey); err != nil { log.Errorf("PrivateKey.Create error: %s", err) return e.ErrBulkCreatePrivateKey.AddDesc("bulk add privateKey failed") diff --git a/pkg/microservice/policy/core/yamlconfig/urls.yaml b/pkg/microservice/policy/core/yamlconfig/urls.yaml index c6a88331..be8bd9a6 100644 --- a/pkg/microservice/policy/core/yamlconfig/urls.yaml +++ b/pkg/microservice/policy/core/yamlconfig/urls.yaml @@ -394,3 +394,18 @@ exemption_urls: - endpoint: api/v1/policybindings/bulk-delete methods: - POST + - endpoint: api/aslan/project/pms/?* + methods: + - GET + - PUT + - DELETE + - endpoint: api/aslan/project/pms/labels + methods: + - GET + - endpoint: api/aslan/project/pms/batch + methods: + - POST + - endpoint: api/aslan/project/pms + methods: + - GET + - POST -- Gitee From 7b4c9bd876aeeaf43582d9fa0adae02b946a2a13 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 16 Jun 2022 11:02:58 +0800 Subject: [PATCH 2461/2647] add tools install step Signed-off-by: guoyu --- .../common/repository/models/workflow_v4.go | 1 + .../workflow/service/jobcontroller/job.go | 4 +- .../service/jobcontroller/job_freestyle.go | 4 + .../workflow/service/jobcontroller/types.go | 2 +- .../workflow/service/stepcontroller/step.go | 12 +- .../stepcontroller/step_docker_build.go | 4 +- .../stepcontroller/step_tool_install.go | 107 ++++++++++++ .../jobexecutor/core/service/job.go | 8 +- .../jobexecutor/core/service/meta/types.go | 2 +- .../jobexecutor/core/service/step/step.go | 13 +- .../core/service/step/step_shell.go | 3 +- .../core/service/step/step_tool_install.go | 161 ++++++++++++++++++ pkg/types/step/step_tool_install.go | 22 +++ 13 files changed, 325 insertions(+), 18 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go create mode 100644 pkg/microservice/jobexecutor/core/service/step/step_tool_install.go create mode 100644 pkg/types/step/step_tool_install.go diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index ca0dedc6..c60fbc05 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -59,6 +59,7 @@ type JobProperties struct { BuildOS string `bson:"build_os" json:"build_os"` Namespace string `bson:"namespace" json:"namespace"` Args []*KeyVal `bson:"args" json:"args"` + Paths string `bson:"-" json:"-"` DockerHost string `bson:"-" json:"docker_host,omitempty"` } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index a4a51d8f..f1494d20 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -48,7 +48,7 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm job.Properties.ClusterID = setting.LocalClusterID } // init step configration. - if err := stepcontroller.PrepareSteps(ctx, workflowCtx, job.Steps, logger); err != nil { + if err := stepcontroller.PrepareSteps(ctx, workflowCtx, &job.Properties.Paths, job.Steps, logger); err != nil { logger.Error(err) job.Error = err.Error() job.Status = config.StatusFailed @@ -60,7 +60,7 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm logger.Infof("start job: %s,status: %s", job.Name, job.Status) defer func() { - if err := stepcontroller.SummarizeSteps(ctx, workflowCtx, job.Steps, logger); err != nil { + if err := stepcontroller.SummarizeSteps(ctx, workflowCtx, &job.Properties.Paths, job.Steps, logger); err != nil { logger.Error(err) job.Error = err.Error() job.Status = config.StatusFailed diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index c77d4ed9..5a5ce875 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -45,15 +45,18 @@ type FreestyleJobCtl struct { workflowCtx *commonmodels.WorkflowTaskCtx logger *zap.SugaredLogger kubeclient crClient.Client + paths *string ack func() } func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { + paths := "" return &FreestyleJobCtl{ job: job, workflowCtx: workflowCtx, logger: logger, ack: ack, + paths: &paths, jobName: fmt.Sprintf("%s-%s-%d", job.Name, job.JobType, workflowCtx.TaskID), } } @@ -262,5 +265,6 @@ func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels TaskID: workflowCtx.TaskID, Outputs: outputs, Steps: job.Steps, + Paths: job.Properties.Paths, } } diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go index f397af26..f5b584b5 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go @@ -34,7 +34,7 @@ type JobContext struct { // TaskID TaskID int64 `yaml:"task_id"` // Paths 执行脚本Path - Paths string `yaml:"-"` + Paths string `yaml:"paths"` Steps []*commonmodels.StepTask `yaml:"steps"` Outputs []string `yaml:"outputs"` diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go index 33c99be9..910bcf7a 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go @@ -18,7 +18,7 @@ type StepCtl interface { AfterRun(ctx context.Context) } -func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { +func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, jobPath *string, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { // inject global context into step spec. workflowCtx.GlobalContextEach(func(k, v string) bool { for _, step := range steps { @@ -38,7 +38,7 @@ func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx stepCtls := []StepCtl{} for _, step := range steps { - stepCtl, err := instantiateStepCtl(step, logger) + stepCtl, err := instantiateStepCtl(step, jobPath, logger) if err != nil { return err } @@ -50,10 +50,10 @@ func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx return nil } -func SummarizeSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { +func SummarizeSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, jobPath *string, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { stepCtls := []StepCtl{} for _, step := range steps { - stepCtl, err := instantiateStepCtl(step, logger) + stepCtl, err := instantiateStepCtl(step, jobPath, logger) if err != nil { return err } @@ -65,7 +65,7 @@ func SummarizeSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskC return nil } -func instantiateStepCtl(step *commonmodels.StepTask, logger *zap.SugaredLogger) (StepCtl, error) { +func instantiateStepCtl(step *commonmodels.StepTask, jobPath *string, logger *zap.SugaredLogger) (StepCtl, error) { var stepCtl StepCtl var err error switch step.StepType { @@ -75,6 +75,8 @@ func instantiateStepCtl(step *commonmodels.StepTask, logger *zap.SugaredLogger) stepCtl, err = NewShellCtl(step, logger) case config.StepDockerBuild: stepCtl, err = NewDockerBuildCtl(step, logger) + case config.StepTools: + stepCtl, err = NewToolInstallCtl(step, jobPath, logger) default: logger.Infof("unknown step type: %s", step.StepType) } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go index 6730b257..4d073759 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go @@ -21,11 +21,11 @@ type dockerBuildCtl struct { func NewDockerBuildCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*dockerBuildCtl, error) { yamlString, err := yaml.Marshal(stepTask.Spec) if err != nil { - return nil, fmt.Errorf("marshal git spec error: %v", err) + return nil, fmt.Errorf("marshal docker build spec error: %v", err) } dockerBuildSpec := &step.StepDockerBuildSpec{} if err := yaml.Unmarshal(yamlString, &dockerBuildSpec); err != nil { - return nil, fmt.Errorf("unmarshal git spec error: %v", err) + return nil, fmt.Errorf("unmarshal docker build spec error: %v", err) } if dockerBuildSpec.Proxy == nil { dockerBuildSpec.Proxy = &step.Proxy{} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go new file mode 100644 index 00000000..81e4d43c --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go @@ -0,0 +1,107 @@ +package stepcontroller + +import ( + "context" + "fmt" + "strings" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/types/step" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type toolInstallCtl struct { + step *commonmodels.StepTask + toolInstalldSpec *step.StepToolInstallSpec + jobPath *string + log *zap.SugaredLogger +} + +func NewToolInstallCtl(stepTask *commonmodels.StepTask, jobPath *string, log *zap.SugaredLogger) (*toolInstallCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal tool install spec error: %v", err) + } + toolInstallSpec := &step.StepToolInstallSpec{} + if err := yaml.Unmarshal(yamlString, &toolInstallSpec); err != nil { + return nil, fmt.Errorf("unmarshal git spec error: %v", err) + } + return &toolInstallCtl{toolInstalldSpec: toolInstallSpec, jobPath: jobPath, log: log, step: stepTask}, nil +} + +func (s *toolInstallCtl) PreRun(ctx context.Context) { + install, err := buildInstallCtx(s.toolInstalldSpec.Name, s.toolInstalldSpec.Version) + if err != nil { + s.log.Error(err) + } + spec := &step.StepToolInstallSpec{ + Name: s.toolInstalldSpec.Name, + Version: s.toolInstalldSpec.Version, + BinPath: install.BinPath, + Download: install.DownloadPath, + Envs: install.Envs, + Scripts: strings.Split(replaceWrapLine(install.Scripts), "\n"), + S3Storage: &step.S3{ + Ak: config.S3StorageAK(), + Sk: config.S3StorageSK(), + Endpoint: config.S3StorageEndpoint(), + Bucket: config.S3StorageBucket(), + Subfolder: config.S3StoragePath(), + Protocol: config.S3StorageProtocol(), + }, + } + objectStorage, _ := mongodb.NewS3StorageColl().FindDefault() + if objectStorage != nil { + spec.S3Storage.Endpoint = objectStorage.Endpoint + spec.S3Storage.Sk = objectStorage.Sk + spec.S3Storage.Ak = objectStorage.Ak + spec.S3Storage.Subfolder = objectStorage.Subfolder + spec.S3Storage.Bucket = objectStorage.Bucket + spec.S3Storage.Protocol = "https" + if objectStorage.Insecure { + spec.S3Storage.Protocol = "http" + } + } + + s.step.Spec = spec + if *s.jobPath != "" { + *s.jobPath = strings.Join([]string{*s.jobPath, install.BinPath}, ":") + } else { + *s.jobPath = install.BinPath + } + +} + +func (s *toolInstallCtl) AfterRun(ctx context.Context) { + +} + +//根据用户的配置和BuildStep中步骤的依赖,从系统配置的InstallItems中获取配置项,构建Install Context +func buildInstallCtx(name, version string) (*commonmodels.Install, error) { + resp := &commonmodels.Install{} + if name == "" || version == "" { + return resp, nil + } + install, err := commonrepo.NewInstallColl().Find(name, version) + if err != nil { + return resp, fmt.Errorf("%s:%s not found", name, version) + } + if !install.Enabled { + return resp, fmt.Errorf("%s:%s disabled", name, version) + } + resp = install + return resp, nil +} + +func replaceWrapLine(script string) string { + return strings.Replace(strings.Replace( + script, + "\r\n", + "\n", + -1, + ), "\r", "\n", -1) +} diff --git a/pkg/microservice/jobexecutor/core/service/job.go b/pkg/microservice/jobexecutor/core/service/job.go index b6b2cf1e..457935a5 100644 --- a/pkg/microservice/jobexecutor/core/service/job.go +++ b/pkg/microservice/jobexecutor/core/service/job.go @@ -57,7 +57,11 @@ func NewJob() (*Job, error) { return nil, fmt.Errorf("cannot unmarshal job data: %v", err) } - ctx.Paths = config.Path() + if ctx.Paths != "" { + ctx.Paths = fmt.Sprintf("%s:%s", config.Path(), ctx.Paths) + } else { + ctx.Paths = config.Path() + } job := &Job{ Ctx: ctx, @@ -122,7 +126,7 @@ func (j *Job) Run(ctx context.Context) error { if err := os.MkdirAll(job.JobOutputDir, os.ModePerm); err != nil { return err } - if err := step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.getUserEnvs(), j.Ctx.SecretEnvs); err != nil { + if err := step.RunSteps(ctx, j.Ctx.Steps, j.ActiveWorkspace, j.Ctx.Paths, j.getUserEnvs(), j.Ctx.SecretEnvs); err != nil { return err } return nil diff --git a/pkg/microservice/jobexecutor/core/service/meta/types.go b/pkg/microservice/jobexecutor/core/service/meta/types.go index 1a855ca6..f8644bb6 100644 --- a/pkg/microservice/jobexecutor/core/service/meta/types.go +++ b/pkg/microservice/jobexecutor/core/service/meta/types.go @@ -29,7 +29,7 @@ type JobContext struct { // TaskID TaskID int64 `yaml:"task_id"` // Paths 执行脚本Path - Paths string `yaml:"-"` + Paths string `yaml:"paths"` Steps []*Step `yaml:"steps"` Outputs []string `yaml:"outputs"` diff --git a/pkg/microservice/jobexecutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go index 0bd79736..c42d8526 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -36,15 +36,15 @@ type Step interface { Run(ctx context.Context) error } -func RunSteps(ctx context.Context, steps []*meta.Step, workspace string, envs, secretEnvs []string) error { +func RunSteps(ctx context.Context, steps []*meta.Step, workspace, paths string, envs, secretEnvs []string) error { for _, stepInfo := range steps { - if err := runStep(ctx, stepInfo, workspace, envs, secretEnvs); err != nil { + if err := runStep(ctx, stepInfo, workspace, paths, envs, secretEnvs); err != nil { return err } } return nil } -func runStep(ctx context.Context, step *meta.Step, workspace string, envs, secretEnvs []string) error { +func runStep(ctx context.Context, step *meta.Step, workspace, paths string, envs, secretEnvs []string) error { start := time.Now() var stepInstance Step var err error @@ -54,7 +54,7 @@ func runStep(ctx context.Context, step *meta.Step, workspace string, envs, secre }() switch step.StepType { case "shell": - stepInstance, err = NewShellStep(step.Spec, workspace, envs, secretEnvs) + stepInstance, err = NewShellStep(step.Spec, workspace, paths, envs, secretEnvs) if err != nil { return err } @@ -68,6 +68,11 @@ func runStep(ctx context.Context, step *meta.Step, workspace string, envs, secre if err != nil { return err } + case "tools": + stepInstance, err = NewToolInstallStep(step.Spec, workspace, envs, secretEnvs) + if err != nil { + return err + } default: err := fmt.Errorf("step type: %s does not match any known type", step.StepType) log.Error(err) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_shell.go b/pkg/microservice/jobexecutor/core/service/step/step_shell.go index 728225ec..0a563530 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_shell.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_shell.go @@ -36,9 +36,10 @@ type ShellStep struct { envs []string secretEnvs []string workspace string + Paths string } -func NewShellStep(spec interface{}, workspace string, envs, secretEnvs []string) (*ShellStep, error) { +func NewShellStep(spec interface{}, workspace, paths string, envs, secretEnvs []string) (*ShellStep, error) { shellStep := &ShellStep{workspace: workspace, envs: envs, secretEnvs: secretEnvs} yamlBytes, err := yaml.Marshal(spec) if err != nil { diff --git a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go new file mode 100644 index 00000000..068c846c --- /dev/null +++ b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go @@ -0,0 +1,161 @@ +package step + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/google/uuid" + "github.com/koderover/zadig/pkg/microservice/reaper/config" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/httpclient" + + "github.com/koderover/zadig/pkg/tool/log" + s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/types/step" + "gopkg.in/yaml.v3" +) + +type ToolInstallStep struct { + spec *step.StepToolInstallSpec + envs []string + secretEnvs []string + workspace string +} + +func NewToolInstallStep(spec interface{}, workspace string, envs, secretEnvs []string) (*ToolInstallStep, error) { + toolInstallStep := &ToolInstallStep{workspace: workspace, envs: envs, secretEnvs: secretEnvs} + yamlBytes, err := yaml.Marshal(spec) + if err != nil { + return toolInstallStep, fmt.Errorf("marshal spec %+v failed", spec) + } + if err := yaml.Unmarshal(yamlBytes, &toolInstallStep.spec); err != nil { + return toolInstallStep, fmt.Errorf("unmarshal spec %s to shell spec failed", yamlBytes) + } + return toolInstallStep, nil +} + +func (s *ToolInstallStep) Run(ctx context.Context) error { + return s.runIntallationScripts() +} + +func (s *ToolInstallStep) runIntallationScripts() error { + if s.spec == nil { + return nil + } + var ( + openProxy bool + proxyScript, disProxyScript string + ) + + var tmpPath string + scripts := []string{} + scripts = append(scripts, "set -ex") + + // 获取用户指定环境变量 + s.envs = append(s.envs, Environs(s.spec.Envs)...) + + if openProxy { + scripts = append(scripts, proxyScript) + } + + // 如果应用有配置下载路径 + if s.spec.Download != "" { + s.spec.S3Storage.Subfolder = fmt.Sprintf("%s/%s-v%s", config.ConstructCachePath, s.spec.Name, s.spec.Version) + filepath := strings.Split(s.spec.Download, "/") + fileName := filepath[len(filepath)-1] + forcedPathStyle := true + if s.spec.S3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + + tmpPath = path.Join(os.TempDir(), fileName) + s3client, err := s3tool.NewClient(s.spec.S3Storage.Endpoint, s.spec.S3Storage.Ak, s.spec.S3Storage.Sk, s.spec.S3Storage.Insecure, forcedPathStyle) + if err == nil { + objectKey := GetObjectPath(fileName, s.spec.S3Storage.Subfolder) + err = s3client.Download( + s.spec.S3Storage.Bucket, + objectKey, + tmpPath, + ) + + // 缓存不存在 + if err != nil { + err := httpclient.Download(s.spec.Download, tmpPath) + if err != nil { + return err + } + s3client.Upload( + s.spec.S3Storage.Bucket, + tmpPath, + objectKey, + ) + log.Infof("Package loaded from url: %s", s.spec.Download) + } + } else { + err := httpclient.Download(s.spec.Download, tmpPath) + if err != nil { + return err + } + } + } + + for j, command := range s.spec.Scripts { + realCommand := strings.ReplaceAll(command, config.FilepathParam, tmpPath) + s.spec.Scripts[j] = realCommand + } + + scripts = append(scripts, s.spec.Scripts...) + + if openProxy { + scripts = append(scripts, disProxyScript) + } + uid, _ := uuid.NewUUID() + file := filepath.Join(os.TempDir(), fmt.Sprintf("install_script_%d.sh", uid)) + if err := ioutil.WriteFile(file, []byte(strings.Join(scripts, "\n")), 0700); err != nil { + return fmt.Errorf("write script file error: %v", err) + } + + cmd := exec.Command("/bin/bash", file) + cmd.Dir = s.workspace + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = s.envs + + if err := cmd.Run(); err != nil { + return err + } + + return nil +} + +func Environs(envs []string) []string { + resp := []string{} + for _, val := range envs { + if val == "" { + continue + } + + if len(strings.Split(val, "=")) != 2 { + continue + } + + replaced := strings.Replace(val, "$HOME", config.Home(), -1) + resp = append(resp, replaced) + } + return resp +} + +func GetObjectPath(name, subFolder string) string { + // target should not be started with / + if subFolder != "" { + return strings.TrimLeft(filepath.Join(subFolder, name), "/") + } + + return strings.TrimLeft(name, "/") +} diff --git a/pkg/types/step/step_tool_install.go b/pkg/types/step/step_tool_install.go new file mode 100644 index 00000000..b79bde7a --- /dev/null +++ b/pkg/types/step/step_tool_install.go @@ -0,0 +1,22 @@ +package step + +type StepToolInstallSpec struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Scripts []string `yaml:"scripts"` + BinPath string `yaml:"bin_path"` + Envs []string `yaml:"envs"` + Download string `yaml:"download"` + S3Storage *S3 `yaml:"s3_storage"` +} + +type S3 struct { + Ak string `yaml:"ak"` + Sk string `yaml:"sk"` + Endpoint string `yaml:"endpoint"` + Bucket string `yaml:"bucket"` + Subfolder string `yaml:"subfolder"` + Insecure bool `yaml:"insecure"` + Provider int `yaml:"provider"` + Protocol string `yaml:"protocol"` +} -- Gitee From 98ea861ea590137a01a48b1c963abd0dcb222d9f Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 16 Jun 2022 15:02:43 +0800 Subject: [PATCH 2462/2647] a new service with the same name needs to be given a prompt (#1669) * rollback init config Signed-off-by: panxunying * yaml code host load service exist Signed-off-by: panxunying * optimize Signed-off-by: panxunying * a new service with the same name needs to be given a prompt Signed-off-by: panxunying * optimize Signed-off-by: panxunying * add policy Signed-off-by: panxunying * a new service with the same name needs to be given a prompt Signed-off-by: panxunying * add policy Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/service/handler/environment.go | 2 +- .../aslan/core/service/handler/helm.go | 28 +++++- .../aslan/core/service/handler/loader.go | 38 +++++++- .../aslan/core/service/handler/router.go | 4 +- .../aslan/core/service/handler/service.go | 2 +- .../core/service/handler/yaml_template.go | 2 +- .../aslan/core/service/service/environment.go | 4 +- .../aslan/core/service/service/helm.go | 49 +++++----- .../aslan/core/service/service/loader.go | 46 ++++----- .../aslan/core/service/service/new_loader.go | 4 +- .../aslan/core/service/service/service.go | 95 ++++++++++--------- .../aslan/core/service/service/template.go | 10 +- .../core/workflow/service/webhook/gerrit.go | 2 +- .../core/workflow/service/webhook/gitee.go | 2 +- .../core/workflow/service/webhook/utils.go | 2 +- .../policy/core/yamlconfig/metas.yaml | 4 + 16 files changed, 181 insertions(+), 113 deletions(-) diff --git a/pkg/microservice/aslan/core/service/handler/environment.go b/pkg/microservice/aslan/core/service/handler/environment.go index bc64e81a..b4af13f4 100644 --- a/pkg/microservice/aslan/core/service/handler/environment.go +++ b/pkg/microservice/aslan/core/service/handler/environment.go @@ -55,5 +55,5 @@ func LoadKubeWorkloadsYaml(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - ctx.Err = service.LoadKubeWorkloadsYaml(ctx.UserName, args, ctx.Logger) + ctx.Err = service.LoadKubeWorkloadsYaml(ctx.UserName, args, false, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index 4042cd21..b8601044 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -89,7 +89,27 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, false, ctx.Logger) +} + +func UpdateHelmService(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + projectName := c.Query("projectName") + if projectName == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("projectName can't be nil") + return + } + + args := new(svcservice.HelmServiceCreationArgs) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid HelmService json args") + return + } + args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID + + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, true, ctx.Logger) } func CreateOrUpdateBulkHelmServices(c *gin.Context) { @@ -109,10 +129,10 @@ func CreateOrUpdateBulkHelmServices(c *gin.Context) { } args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID - ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(projectName, args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(projectName, args, false, ctx.Logger) } -func UpdateHelmService(c *gin.Context) { +func EditHelmService(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -124,5 +144,5 @@ func UpdateHelmService(c *gin.Context) { args.CreateBy = ctx.UserName args.ProductName = c.Param("productName") - ctx.Err = svcservice.UpdateHelmService(args, ctx.Logger) + ctx.Err = svcservice.EditHelmService(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/handler/loader.go b/pkg/microservice/aslan/core/service/handler/loader.go index 8bd5303e..94b2d01b 100644 --- a/pkg/microservice/aslan/core/service/handler/loader.go +++ b/pkg/microservice/aslan/core/service/handler/loader.go @@ -88,7 +88,43 @@ func LoadServiceTemplate(c *gin.Context) { if namespace == "" { namespace = repoOwner } - ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, namespace, repoName, repoUUID, branchName, remoteName, args, ctx.Logger) + ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, namespace, repoName, repoUUID, branchName, remoteName, args, false, ctx.Logger) +} + +func SyncServiceTemplate(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + codehostIDStr := c.Param("codehostId") + + codehostID, err := strconv.Atoi(codehostIDStr) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("cannot convert codehost id to string") + return + } + + repoName := c.Query("repoName") + repoUUID := c.Query("repoUUID") + if repoName == "" && repoUUID == "" { + ctx.Err = e.ErrInvalidParam.AddDesc("repoName and repoUUID cannot be empty at the same time") + return + } + + branchName := c.Query("branchName") + + args := new(svcservice.LoadServiceReq) + if err := c.BindJSON(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid LoadServiceReq json args") + return + } + + remoteName := c.Query("remoteName") + repoOwner := c.Query("repoOwner") + namespace := c.Query("namespace") + if namespace == "" { + namespace = repoOwner + } + ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, namespace, repoName, repoUUID, branchName, remoteName, args, true, ctx.Logger) } // ValidateServiceUpdate ... diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index beffbeab..7f7ffaf5 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -40,8 +40,9 @@ func (*Router) Inject(router *gin.RouterGroup) { helm.GET("/:productName/:serviceName/filePath", GetFilePath) helm.GET("/:productName/:serviceName/fileContent", GetFileContent) helm.POST("/services", CreateOrUpdateHelmService) + helm.PUT("/services", UpdateHelmService) helm.POST("/services/bulk", CreateOrUpdateBulkHelmServices) - helm.PUT("/:productName", UpdateHelmService) + helm.PUT("/:productName", EditHelmService) helm.PUT("/services/releaseNaming", HelmReleaseNaming) } @@ -77,6 +78,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { loader.GET("/preload/:codehostId", PreloadServiceTemplate) loader.POST("/load/:codehostId", LoadServiceTemplate) + loader.PUT("/load/:codehostId", SyncServiceTemplate) loader.GET("/validateUpdate/:codehostId", ValidateServiceUpdate) } diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 21007682..d9052122 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -92,7 +92,7 @@ func CreateServiceTemplate(c *gin.Context) { } args.CreateBy = ctx.UserName - ctx.Resp, ctx.Err = svcservice.CreateServiceTemplate(ctx.UserName, args, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateServiceTemplate(ctx.UserName, args, false, ctx.Logger) } func UpdateServiceTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go index 3b048dd7..f96b9d81 100644 --- a/pkg/microservice/aslan/core/service/handler/yaml_template.go +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -34,7 +34,7 @@ func LoadServiceFromYamlTemplate(c *gin.Context) { return } - ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.UserName, req, ctx.Logger) + ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.UserName, req, false, ctx.Logger) } func ReloadServiceFromYamlTemplate(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/service/service/environment.go b/pkg/microservice/aslan/core/service/service/environment.go index b8cd4347..1a44ed19 100644 --- a/pkg/microservice/aslan/core/service/service/environment.go +++ b/pkg/microservice/aslan/core/service/service/environment.go @@ -166,7 +166,7 @@ type GetKubeWorkloadsYamlResp struct { Services []ServiceYaml `json:"services"` } -func LoadKubeWorkloadsYaml(username string, params *LoadKubeWorkloadsYamlReq, log *zap.SugaredLogger) error { +func LoadKubeWorkloadsYaml(username string, params *LoadKubeWorkloadsYamlReq, force bool, log *zap.SugaredLogger) error { kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), params.ClusterID) if err != nil { log.Errorf("cluster is not connected [%s]", params.ClusterID) @@ -257,7 +257,7 @@ func LoadKubeWorkloadsYaml(username string, params *LoadKubeWorkloadsYamlReq, lo Yaml: yaml, Source: "spock", } - _, err := CreateServiceTemplate(username, serviceParam, log) + _, err := CreateServiceTemplate(username, serviceParam, force, log) if err != nil { log.Errorf("CreateServiceTemplate error, msg:%s", err) return err diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index 8051df4e..c88f020c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -429,22 +429,22 @@ func clearLocalChartFiles(projectName, serviceName string, revision int64, logge } } -func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmService(projectName string, args *HelmServiceCreationArgs, force bool, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { switch args.Source { case LoadFromRepo, LoadFromPublicRepo: - return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, logger) + return CreateOrUpdateHelmServiceFromGitRepo(projectName, args, force, logger) case LoadFromChartTemplate: - return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, logger) + return CreateOrUpdateHelmServiceFromChartTemplate(projectName, args, force, logger) case LoadFromGerrit, setting.SourceFromGitee: - return CreateOrUpdateHelmServiceFromRepo(projectName, args, logger) + return CreateOrUpdateHelmServiceFromRepo(projectName, args, force, logger) case LoadFromChartRepo: - return CreateOrUpdateHelmServiceFromChartRepo(projectName, args, logger) + return CreateOrUpdateHelmServiceFromChartRepo(projectName, args, force, logger) default: return nil, fmt.Errorf("invalid source") } } -func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServiceCreationArgs, force bool, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { chartRepoArgs, ok := args.CreateFrom.(*CreateFromChartRepo) if !ok { return nil, e.ErrCreateTemplate.AddDesc("invalid argument") @@ -522,7 +522,7 @@ func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServic CreateBy: args.CreatedBy, RequestID: args.RequestID, Source: setting.SourceFromChartRepo, - }, + }, force, log, ) if err != nil { @@ -550,7 +550,7 @@ func CreateOrUpdateHelmServiceFromChartRepo(projectName string, args *HelmServic }, nil } -func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmServiceCreationArgs, force bool, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { return nil, fmt.Errorf("invalid argument") @@ -561,10 +561,10 @@ func CreateOrUpdateHelmServiceFromChartTemplate(projectName string, args *HelmSe return nil, err } - return createOrUpdateHelmServiceFromChartTemplate(templateArgs, templateChartInfo, projectName, args, logger) + return createOrUpdateHelmServiceFromChartTemplate(templateArgs, templateChartInfo, projectName, args, force, logger) } -func createOrUpdateHelmServiceFromChartTemplate(templateArgs *CreateFromChartTemplate, templateChartInfo *ChartTemplateData, projectName string, args *HelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func createOrUpdateHelmServiceFromChartTemplate(templateArgs *CreateFromChartTemplate, templateChartInfo *ChartTemplateData, projectName string, args *HelmServiceCreationArgs, force bool, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { // NOTE we may need a better way to handle service name with spaces args.Name = strings.TrimSpace(args.Name) @@ -655,7 +655,7 @@ func createOrUpdateHelmServiceFromChartTemplate(templateArgs *CreateFromChartTem ValuesSource: args.ValuesData, CreationDetail: args.CreationDetail, AutoSync: args.AutoSync, - }, + }, force, logger, ) @@ -695,7 +695,7 @@ func getCodehostType(repoArgs *CreateFromRepo, repoLink string) (string, *system return ch.Type, ch, nil } -func CreateOrUpdateHelmServiceFromRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromRepo(projectName string, args *HelmServiceCreationArgs, force bool, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { var ( filePaths []string response = &BulkHelmServiceCreationResponse{} @@ -827,6 +827,7 @@ func CreateOrUpdateHelmServiceFromRepo(projectName string, args *HelmServiceCrea svc, err := createOrUpdateHelmService( nil, helmServiceCreationArgs, + force, log, ) if err != nil { @@ -850,7 +851,7 @@ func CreateOrUpdateHelmServiceFromRepo(projectName string, args *HelmServiceCrea return response, service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, projectName, serviceList, log) } -func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceCreationArgs, force bool, log *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { var err error var repoLink string repoArgs, ok := args.CreateFrom.(*CreateFromRepo) @@ -975,7 +976,7 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC Branch: repoArgs.Branch, RepoLink: repoLink, Source: source, - }, + }, force, log, ) if err != nil { @@ -999,16 +1000,16 @@ func CreateOrUpdateHelmServiceFromGitRepo(projectName string, args *HelmServiceC return response, service.AutoDeployHelmServiceToEnvs(args.CreatedBy, args.RequestID, projectName, serviceList, log) } -func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateBulkHelmService(projectName string, args *BulkHelmServiceCreationArgs, force bool, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { switch args.Source { case LoadFromChartTemplate: - return CreateOrUpdateBulkHelmServiceFromTemplate(projectName, args, logger) + return CreateOrUpdateBulkHelmServiceFromTemplate(projectName, args, force, logger) default: return nil, fmt.Errorf("invalid source") } } -func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHelmServiceCreationArgs, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { +func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHelmServiceCreationArgs, force bool, logger *zap.SugaredLogger) (*BulkHelmServiceCreationResponse, error) { templateArgs, ok := args.CreateFrom.(*CreateFromChartTemplate) if !ok { return nil, fmt.Errorf("invalid argument") @@ -1040,7 +1041,7 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel wg.Add(1) go func(repoConfig *commonservice.RepoConfig, path string) { defer wg.Done() - renderChart, svcInfo, err := handleSingleService(projectName, repoConfig, path, from, args, templateChartData, logger) + renderChart, svcInfo, err := handleSingleService(projectName, repoConfig, path, from, args, templateChartData, force, logger) if err != nil { failedServiceMap.Store(path, err.Error()) } else { @@ -1082,7 +1083,7 @@ func CreateOrUpdateBulkHelmServiceFromTemplate(projectName string, args *BulkHel } func handleSingleService(projectName string, repoConfig *commonservice.RepoConfig, path, fromPath string, args *BulkHelmServiceCreationArgs, - templateChartData *ChartTemplateData, logger *zap.SugaredLogger) (*templatemodels.RenderChart, *commonmodels.Service, error) { + templateChartData *ChartTemplateData, force bool, logger *zap.SugaredLogger) (*templatemodels.RenderChart, *commonmodels.Service, error) { valuesYAML, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, Owner: repoConfig.Owner, @@ -1174,6 +1175,7 @@ func handleSingleService(projectName string, repoConfig *commonservice.RepoConfi AutoSync: args.AutoSync, ValuesSource: args.ValuesData, }, + force, logger, ) if err != nil { @@ -1328,7 +1330,7 @@ func renderVariablesToYaml(valuesYaml string, productName, serviceName string, v return valuesYaml, nil } -func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logger *zap.SugaredLogger) (*commonmodels.Service, error) { +func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, force bool, logger *zap.SugaredLogger) (*commonmodels.Service, error) { var ( chartName, chartVersion string err error @@ -1418,6 +1420,9 @@ func createOrUpdateHelmService(fsTree fs.FS, args *helmServiceCreationArgs, logg // update status of current service template to deleting if currentSvcTmpl != nil { + if !force { + return nil, fmt.Errorf("service:%s already exists", args.ServiceName) + } err = commonrepo.NewServiceColl().UpdateStatus(args.ServiceName, args.ProductName, setting.ProductStatusDeleting) if err != nil { log.Errorf("Failed to set status of current service templates, serviceName: %s, err: %s", args.ServiceName, err) @@ -1506,8 +1511,8 @@ func loadServiceFileInfos(productName, serviceName string, revision int64, dir s return fis, nil } -// UpdateHelmService deprecated -func UpdateHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { +// EditHelmService deprecated +func EditHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { serviceMap := make(map[string]int64) for _, helmServiceInfo := range args.HelmServiceInfos { diff --git a/pkg/microservice/aslan/core/service/service/loader.go b/pkg/microservice/aslan/core/service/service/loader.go index b8fcdf69..9dffa729 100644 --- a/pkg/microservice/aslan/core/service/service/loader.go +++ b/pkg/microservice/aslan/core/service/service/loader.go @@ -75,7 +75,7 @@ func PreloadServiceFromCodeHost(codehostID int, repoOwner, repoName, repoUUID, b } // LoadServiceFromCodeHost 根据提供的codehost信息加载服务 -func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, namespace, repoName, repoUUID, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, namespace, repoName, repoUUID, branchName, remoteName string, args *LoadServiceReq, force bool, log *zap.SugaredLogger) error { ch, err := systemconfig.New().GetCodeHost(codehostID) if err != nil { log.Errorf("Failed to load codehost for preload service list, the error is: %+v", err) @@ -83,13 +83,13 @@ func LoadServiceFromCodeHost(username string, codehostID int, repoOwner, namespa } switch ch.Type { case setting.SourceFromGithub, setting.SourceFromGitlab: - return loadService(username, ch, repoOwner, namespace, repoName, branchName, args, log) + return loadService(username, ch, repoOwner, namespace, repoName, branchName, args, force, log) case setting.SourceFromGerrit: - return loadGerritService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) + return loadGerritService(username, ch, repoOwner, repoName, branchName, remoteName, args, force, log) case setting.SourceFromCodeHub: - return loadCodehubService(username, ch, repoOwner, repoName, repoUUID, branchName, args, log) + return loadCodehubService(username, ch, repoOwner, repoName, repoUUID, branchName, args, force, log) case setting.SourceFromGitee: - return loadGiteeService(username, ch, repoOwner, repoName, branchName, remoteName, args, log) + return loadGiteeService(username, ch, repoOwner, repoName, branchName, remoteName, args, force, log) default: return e.ErrLoadServiceTemplate.AddDesc("unsupported code source") } @@ -299,7 +299,7 @@ func preloadGiteeService(detail *systemconfig.CodeHost, repoOwner, repoName, bra } // 根据repo信息从gerrit加载服务 -func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, force bool, log *zap.SugaredLogger) error { if remoteName == "" { remoteName = "origin" } @@ -357,7 +357,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re Commit: commitInfo, Visibility: args.Visibility, } - _, err = CreateServiceTemplate(username, createSvcArgs, log) + _, err = CreateServiceTemplate(username, createSvcArgs, force, log) if err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { @@ -373,7 +373,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } if isValidServiceDir(fileInfos) { - return loadServiceFromGerrit(fileInfos, ch.ID, username, branchName, args.LoadPath, filePath, repoOwner, remoteName, repoName, args, commitInfo, log) + return loadServiceFromGerrit(fileInfos, ch.ID, username, branchName, args.LoadPath, filePath, repoOwner, remoteName, repoName, args, commitInfo, force, log) } for _, entry := range fileInfos { subtreeLoadPath := fmt.Sprintf("%s/%s", args.LoadPath, entry.Name()) @@ -384,7 +384,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } if isValidServiceDir(subtreeInfo) { - if err := loadServiceFromGerrit(subtreeInfo, ch.ID, username, branchName, subtreeLoadPath, subtreePath, repoOwner, remoteName, repoName, args, commitInfo, log); err != nil { + if err := loadServiceFromGerrit(subtreeInfo, ch.ID, username, branchName, subtreeLoadPath, subtreePath, repoOwner, remoteName, repoName, args, commitInfo, force, log); err != nil { return err } } @@ -392,7 +392,7 @@ func loadGerritService(username string, ch *systemconfig.CodeHost, repoOwner, re return nil } -func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loadPath, path, repoOwner, remoteName, repoName string, args *LoadServiceReq, commit *models.Commit, log *zap.SugaredLogger) error { +func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loadPath, path, repoOwner, remoteName, repoName string, args *LoadServiceReq, commit *models.Commit, force bool, log *zap.SugaredLogger) error { pathList := strings.Split(path, "/") var splittedYaml []string fileName := pathList[len(pathList)-1] @@ -429,7 +429,7 @@ func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loa Visibility: args.Visibility, } - _, err = CreateServiceTemplate(username, createSvcArgs, log) + _, err = CreateServiceTemplate(username, createSvcArgs, force, log) if err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { @@ -442,7 +442,7 @@ func loadServiceFromGerrit(tree []os.FileInfo, id int, username, branchName, loa } // load codehub service -func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, repoUUID, branchName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, repoUUID, branchName string, args *LoadServiceReq, force bool, log *zap.SugaredLogger) error { codeHubClient := codehub.NewCodeHubClient(ch.AccessKey, ch.SecretKey, ch.Region, config.ProxyHTTPSAddr(), ch.EnableProxy) if !args.LoadFromDir { @@ -479,7 +479,7 @@ func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, r Commit: &models.Commit{SHA: commit.ID, Message: commit.Message}, Visibility: args.Visibility, } - if _, err = CreateServiceTemplate(username, createSvcArgs, log); err != nil { + if _, err = CreateServiceTemplate(username, createSvcArgs, force, log); err != nil { log.Errorf("Failed to create service template, serviceName:%s error: %s", createSvcArgs.ServiceName, err) _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { @@ -494,7 +494,7 @@ func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, r return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } if isValidCodehubServiceDir(treeNodes) { - return loadServiceFromCodehub(codeHubClient, treeNodes, ch, username, repoOwner, repoName, repoUUID, branchName, args.LoadPath, args, log) + return loadServiceFromCodehub(codeHubClient, treeNodes, ch, username, repoOwner, repoName, repoUUID, branchName, args.LoadPath, args, force, log) } for _, treeNode := range treeNodes { @@ -505,7 +505,7 @@ func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, r return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } if isValidCodehubServiceDir(subtree) { - if err := loadServiceFromCodehub(codeHubClient, subtree, ch, username, repoOwner, repoName, repoUUID, branchName, treeNode.Path, args, log); err != nil { + if err := loadServiceFromCodehub(codeHubClient, subtree, ch, username, repoOwner, repoName, repoUUID, branchName, treeNode.Path, args, force, log); err != nil { return err } } @@ -514,7 +514,7 @@ func loadCodehubService(username string, ch *systemconfig.CodeHost, repoOwner, r return nil } -func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeNode, ch *systemconfig.CodeHost, username, repoOwner, repoName, repoUUID, branchName, path string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeNode, ch *systemconfig.CodeHost, username, repoOwner, repoName, repoUUID, branchName, path string, args *LoadServiceReq, force bool, log *zap.SugaredLogger) error { pathList := strings.Split(path, "/") var splittedYaml []string serviceName := pathList[len(pathList)-1] @@ -557,7 +557,7 @@ func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeN Visibility: args.Visibility, } - if _, err = CreateServiceTemplate(username, createSvcArgs, log); err != nil { + if _, err = CreateServiceTemplate(username, createSvcArgs, force, log); err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { err = e.ErrLoadServiceTemplate.AddDesc(description.(string)) @@ -569,7 +569,7 @@ func loadServiceFromCodehub(client *codehub.CodeHubClient, tree []*codehub.TreeN } // Load services from gitee based on repo information -func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, log *zap.SugaredLogger) error { +func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, repoName, branchName, remoteName string, args *LoadServiceReq, force bool, log *zap.SugaredLogger) error { if remoteName == "" { remoteName = "origin" } @@ -622,7 +622,7 @@ func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, rep Commit: commitInfo, Visibility: args.Visibility, } - _, err = CreateServiceTemplate(username, createSvcArgs, log) + _, err = CreateServiceTemplate(username, createSvcArgs, force, log) if err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { @@ -638,7 +638,7 @@ func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, rep return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } if isValidServiceDir(fileInfos) { - return loadServiceFromGitee(fileInfos, ch, username, branchName, args.LoadPath, filePath, repoOwner, remoteName, repoName, args, commitInfo, log) + return loadServiceFromGitee(fileInfos, ch, username, branchName, args.LoadPath, filePath, repoOwner, remoteName, repoName, args, commitInfo, force, log) } for _, entry := range fileInfos { subtreeLoadPath := fmt.Sprintf("%s/%s", args.LoadPath, entry.Name()) @@ -649,7 +649,7 @@ func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, rep return e.ErrLoadServiceTemplate.AddDesc(err.Error()) } if isValidServiceDir(subtreeInfo) { - if err := loadServiceFromGitee(subtreeInfo, ch, username, branchName, subtreeLoadPath, subtreePath, repoOwner, remoteName, repoName, args, commitInfo, log); err != nil { + if err := loadServiceFromGitee(subtreeInfo, ch, username, branchName, subtreeLoadPath, subtreePath, repoOwner, remoteName, repoName, args, commitInfo, force, log); err != nil { return err } } @@ -657,7 +657,7 @@ func loadGiteeService(username string, ch *systemconfig.CodeHost, repoOwner, rep return nil } -func loadServiceFromGitee(tree []os.FileInfo, ch *systemconfig.CodeHost, username, branchName, loadPath, path, repoOwner, remoteName, repoName string, args *LoadServiceReq, commit *models.Commit, log *zap.SugaredLogger) error { +func loadServiceFromGitee(tree []os.FileInfo, ch *systemconfig.CodeHost, username, branchName, loadPath, path, repoOwner, remoteName, repoName string, args *LoadServiceReq, commit *models.Commit, force bool, log *zap.SugaredLogger) error { pathList := strings.Split(path, "/") var splittedYaml []string fileName := pathList[len(pathList)-1] @@ -692,7 +692,7 @@ func loadServiceFromGitee(tree []os.FileInfo, ch *systemconfig.CodeHost, usernam Visibility: args.Visibility, } - _, err = CreateServiceTemplate(username, createSvcArgs, log) + _, err = CreateServiceTemplate(username, createSvcArgs, force, log) if err != nil { _, messageMap := e.ErrorMessage(err) if description, ok := messageMap["description"]; ok { diff --git a/pkg/microservice/aslan/core/service/service/new_loader.go b/pkg/microservice/aslan/core/service/service/new_loader.go index 893f3860..58b71c09 100644 --- a/pkg/microservice/aslan/core/service/service/new_loader.go +++ b/pkg/microservice/aslan/core/service/service/new_loader.go @@ -95,7 +95,7 @@ type serviceInfo struct { yamls []string } -func loadService(username string, ch *systemconfig.CodeHost, owner, namespace, repo, branch string, args *LoadServiceReq, logger *zap.SugaredLogger) error { +func loadService(username string, ch *systemconfig.CodeHost, owner, namespace, repo, branch string, args *LoadServiceReq, force bool, logger *zap.SugaredLogger) error { logger.Infof("Loading service from %s with owner %s, namespace %s, repo %s, branch %s and path %s", ch.Type, owner, namespace, repo, branch, args.LoadPath) project, err := templaterepo.NewProductColl().Find(args.ProductName) @@ -194,7 +194,7 @@ func loadService(username string, ch *systemconfig.CodeHost, owner, namespace, r Commit: &models.Commit{SHA: commit.SHA, Message: commit.Message}, Visibility: args.Visibility, } - _, err = CreateServiceTemplate(username, createSvcArgs, logger) + _, err = CreateServiceTemplate(username, createSvcArgs, force, logger) if err != nil { logger.Errorf("Failed to create service template, err: %s", err) _, messageMap := e.ErrorMessage(err) diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index f227ce93..7dc2f745 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -684,7 +684,7 @@ func CreateWorkloadTemplate(userName string, args *commonmodels.Service, log *za return nil } -func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap.SugaredLogger) (*ServiceOption, error) { +func CreateServiceTemplate(userName string, args *commonmodels.Service, force bool, log *zap.SugaredLogger) (*ServiceOption, error) { opt := &commonrepo.ServiceFindOption{ ServiceName: args.ServiceName, ProductName: args.ProductName, @@ -702,52 +702,53 @@ func CreateServiceTemplate(userName string, args *commonmodels.Service, log *zap // 在更新数据库前检查是否有完全重复的Item,如果有,则退出。 serviceTmpl, notFoundErr := commonrepo.NewServiceColl().Find(opt) - if notFoundErr == nil { - if args.Type == setting.K8SDeployType && args.Source == serviceTmpl.Source { - // 配置来源为zadig,对比配置内容是否变化,需要对比Yaml内容 - // 如果Source没有设置,默认认为是zadig平台管理配置方式 - if args.Source == setting.SourceFromZadig || args.Source == "" { - if args.Yaml != "" && serviceTmpl.Yaml == args.Yaml { - log.Info("Yaml config remains the same, quit creation.") - return GetServiceOption(serviceTmpl, log) - } - } - // 配置来源为Gitlab,对比配置的ChangeLog是否变化 - if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromGitee { - if args.Commit != nil && serviceTmpl.Commit != nil && args.Commit.SHA == serviceTmpl.Commit.SHA { - log.Infof("%s change log remains the same, quit creation", args.Source) - return GetServiceOption(serviceTmpl, log) - } - if args.LoadPath != serviceTmpl.LoadPath && serviceTmpl.LoadPath != "" { - log.Errorf("Changing load path is not allowed") - return nil, e.ErrCreateTemplate.AddDesc("不允许更改加载路径") - } - } else if args.Source == setting.SourceFromGerrit { - if args.Yaml != "" && serviceTmpl.Yaml == args.Yaml { - log.Info("gerrit Yaml config remains the same, quit creation.") - return GetServiceOption(serviceTmpl, log) - } - if args.LoadPath != serviceTmpl.LoadPath && serviceTmpl.LoadPath != "" { - log.Errorf("Changing load path is not allowed") - return nil, e.ErrCreateTemplate.AddDesc("不允许更改加载路径") - } - if err := updateGerritWebhookByService(serviceTmpl, args); err != nil { - log.Infof("gerrit update webhook err :%v", err) - return nil, err - } - } - } else if args.Source == setting.SourceFromGerrit { - err := GetGerritServiceYaml(args, log) - if err != nil { - log.Errorf("GetGerritServiceYaml from gerrit failed, error: %v", err) - return nil, err - } - //创建gerrit webhook - if err = createGerritWebhookByService(args.GerritCodeHostID, args.ServiceName, args.GerritRepoName, args.GerritBranchName); err != nil { - log.Errorf("createGerritWebhookByService error: %v", err) - return nil, err - } - } + if notFoundErr == nil && !force { + return nil, fmt.Errorf("service:%s already exists", serviceTmpl.ServiceName) + //if args.Type == setting.K8SDeployType && args.Source == serviceTmpl.Source { + // // 配置来源为zadig,对比配置内容是否变化,需要对比Yaml内容 + // // 如果Source没有设置,默认认为是zadig平台管理配置方式 + // if args.Source == setting.SourceFromZadig || args.Source == "" { + // if args.Yaml != "" && serviceTmpl.Yaml == args.Yaml { + // log.Info("Yaml config remains the same, quit creation.") + // return GetServiceOption(serviceTmpl, log) + // } + // } + // // 配置来源为Gitlab,对比配置的ChangeLog是否变化 + // if args.Source == setting.SourceFromGitlab || args.Source == setting.SourceFromGithub || args.Source == setting.SourceFromCodeHub || args.Source == setting.SourceFromGitee { + // if args.Commit != nil && serviceTmpl.Commit != nil && args.Commit.SHA == serviceTmpl.Commit.SHA { + // log.Infof("%s change log remains the same, quit creation", args.Source) + // return GetServiceOption(serviceTmpl, log) + // } + // if args.LoadPath != serviceTmpl.LoadPath && serviceTmpl.LoadPath != "" { + // log.Errorf("Changing load path is not allowed") + // return nil, e.ErrCreateTemplate.AddDesc("不允许更改加载路径") + // } + // } else if args.Source == setting.SourceFromGerrit { + // if args.Yaml != "" && serviceTmpl.Yaml == args.Yaml { + // log.Info("gerrit Yaml config remains the same, quit creation.") + // return GetServiceOption(serviceTmpl, log) + // } + // if args.LoadPath != serviceTmpl.LoadPath && serviceTmpl.LoadPath != "" { + // log.Errorf("Changing load path is not allowed") + // return nil, e.ErrCreateTemplate.AddDesc("不允许更改加载路径") + // } + // if err := updateGerritWebhookByService(serviceTmpl, args); err != nil { + // log.Infof("gerrit update webhook err :%v", err) + // return nil, err + // } + // } + //} else if args.Source == setting.SourceFromGerrit { + // err := GetGerritServiceYaml(args, log) + // if err != nil { + // log.Errorf("GetGerritServiceYaml from gerrit failed, error: %v", err) + // return nil, err + // } + // //创建gerrit webhook + // if err = createGerritWebhookByService(args.GerritCodeHostID, args.ServiceName, args.GerritRepoName, args.GerritBranchName); err != nil { + // log.Errorf("createGerritWebhookByService error: %v", err) + // return nil, err + // } + //} } else { if args.Source == setting.SourceFromGerrit { //创建gerrit webhook diff --git a/pkg/microservice/aslan/core/service/service/template.go b/pkg/microservice/aslan/core/service/service/template.go index 9853b983..b2a27caf 100644 --- a/pkg/microservice/aslan/core/service/service/template.go +++ b/pkg/microservice/aslan/core/service/service/template.go @@ -53,7 +53,7 @@ func geneCreateFromDetail(templateId string, variables []*Variable) *commonmodel } } -func LoadServiceFromYamlTemplate(username string, req *LoadServiceFromYamlTemplateReq, logger *zap.SugaredLogger) error { +func LoadServiceFromYamlTemplate(username string, req *LoadServiceFromYamlTemplateReq, force bool, logger *zap.SugaredLogger) error { projectName, serviceName, templateID, variables, autoSync := req.ProjectName, req.ServiceName, req.TemplateID, req.Variables, req.AutoSync template, err := commonrepo.NewYamlTemplateColl().GetById(templateID) if err != nil { @@ -72,7 +72,7 @@ func LoadServiceFromYamlTemplate(username string, req *LoadServiceFromYamlTempla AutoSync: autoSync, CreateFrom: geneCreateFromDetail(templateID, variables), } - _, err = CreateServiceTemplate(username, service, logger) + _, err = CreateServiceTemplate(username, service, force, logger) if err != nil { logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", templateID, err) } @@ -112,7 +112,7 @@ func ReloadServiceFromYamlTemplate(username string, req *LoadServiceFromYamlTemp AutoSync: autoSync, CreateFrom: geneCreateFromDetail(templateID, variables), } - _, err = CreateServiceTemplate(username, svc, logger) + _, err = CreateServiceTemplate(username, svc, true, logger) if err != nil { logger.Errorf("Failed to create service template from template ID: %s, the error is: %s", service.TemplateID, err) } @@ -224,7 +224,7 @@ func reloadServiceFromChartTemplate(service *commonmodels.Service, chartTemplate CreationDetail: service.CreateFrom, AutoSync: service.AutoSync, } - ret, err := createOrUpdateHelmServiceFromChartTemplate(templateArgs, chartTemplate, service.ProductName, args, log.SugaredLogger()) + ret, err := createOrUpdateHelmServiceFromChartTemplate(templateArgs, chartTemplate, service.ProductName, args, true, log.SugaredLogger()) if err != nil { return err } @@ -347,7 +347,7 @@ func reloadServiceFromYamlTemplate(userName, projectName string, template *commo CreateFrom: service.CreateFrom, AutoSync: service.AutoSync, } - _, err = CreateServiceTemplate(userName, svc, log.SugaredLogger()) + _, err = CreateServiceTemplate(userName, svc, true, log.SugaredLogger()) if err != nil { return fmt.Errorf("failed to reload service template from template ID: %s, error : %s", service.TemplateID, err) } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go index 6b52a496..7486ee42 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gerrit.go @@ -315,7 +315,7 @@ func reloadServiceTmplFromGerrit(svc *commonmodels.Service, log *zap.SugaredLogg Branch: svc.BranchName, Paths: []string{svc.LoadPath}, }, - }, log) + }, true, log) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go index 0ceaf90e..fa6713b9 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitee.go @@ -340,6 +340,6 @@ func reloadServiceTmplFromGitee(svc *commonmodels.Service, log *zap.SugaredLogge Branch: svc.BranchName, Paths: []string{svc.LoadPath}, }, - }, log) + }, true, log) return err } diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go index 4295ed5b..90d51a48 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/utils.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/utils.go @@ -105,7 +105,7 @@ func reloadServiceTmplFromGit(svc *commonmodels.Service, log *zap.SugaredLogger) Branch: svc.BranchName, Paths: []string{svc.LoadPath}, }, - }, log) + }, true, log) return err } diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index 6ef88883..47d11fd6 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -167,10 +167,14 @@ metas: endpoint: /api/aslan/service/services - method: POST endpoint: /api/aslan/service/loader/load/?*/?* + - method: PUT + endpoint: /api/aslan/service/loader/load/?*/?* - method: POST endpoint: /api/aslan/service/template/load - method: POST endpoint: /api/aslan/service/helm/services + - method: PUT + endpoint: /api/aslan/service/helm/services - method: POST endpoint: /api/aslan/service/helm/services/bulk - method: GET -- Gitee From 7c3b9b98c72eba4a7668348401416856994da30d Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Thu, 16 Jun 2022 17:12:22 +0800 Subject: [PATCH 2463/2647] fix error message for duplicate workflow (#1677) Signed-off-by: Min Min Co-authored-by: Min Min --- .../aslan/core/workflow/service/workflow/workflow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 7afaa3f5..0f7f997f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -183,8 +183,8 @@ func AutoCreateWorkflow(productName string, log *zap.SugaredLogger) *EnvStatus { if workflowSlice.Has(workflowName) { continue } - if _, err := commonrepo.NewWorkflowColl().Find(workflowName); err == nil { - errList = multierror.Append(errList, fmt.Errorf("workflow [%s] 在项目 [%s] 中已经存在", workflowName, productName)) + if dupWorkflow, err := commonrepo.NewWorkflowColl().Find(workflowName); err == nil { + errList = multierror.Append(errList, fmt.Errorf("workflow [%s] 在项目 [%s] 中已经存在", workflowName, dupWorkflow.ProductTmplName)) } workflow := new(commonmodels.Workflow) workflow.Enabled = true -- Gitee From 02148dcc34bc7ee9840813f249b510d092d26660 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 16 Jun 2022 21:19:41 +0800 Subject: [PATCH 2464/2647] add archive step Signed-off-by: guoyu --- .../workflow/service/stepcontroller/step.go | 2 + .../service/stepcontroller/step_archive.go | 68 +++++++++++ .../jobexecutor/core/service/step/step.go | 5 + .../core/service/step/step_archive.go | 111 ++++++++++++++++++ pkg/types/step/common.go | 39 ++++++ pkg/types/step/step_archive.go | 25 ++++ pkg/types/step/step_git.go | 11 -- pkg/types/step/step_tool_install.go | 11 -- 8 files changed, 250 insertions(+), 22 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go create mode 100644 pkg/microservice/jobexecutor/core/service/step/step_archive.go create mode 100644 pkg/types/step/common.go create mode 100644 pkg/types/step/step_archive.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go index 910bcf7a..8ede472c 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go @@ -77,6 +77,8 @@ func instantiateStepCtl(step *commonmodels.StepTask, jobPath *string, logger *za stepCtl, err = NewDockerBuildCtl(step, logger) case config.StepTools: stepCtl, err = NewToolInstallCtl(step, jobPath, logger) + case config.StepArchive: + stepCtl, err = NewArchiveInstallCtl(step, logger) default: logger.Infof("unknown step type: %s", step.StepType) } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go new file mode 100644 index 00000000..bfb7c757 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go @@ -0,0 +1,68 @@ +package stepcontroller + +import ( + "context" + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/types/step" + "go.uber.org/zap" + "gopkg.in/yaml.v3" +) + +type archiveCtl struct { + step *commonmodels.StepTask + toolInstalldSpec *step.StepArchiveSpec + log *zap.SugaredLogger +} + +func NewArchiveInstallCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*archiveCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal tool install spec error: %v", err) + } + toolInstallSpec := &step.StepArchiveSpec{} + if err := yaml.Unmarshal(yamlString, &toolInstallSpec); err != nil { + return nil, fmt.Errorf("unmarshal git spec error: %v", err) + } + return &archiveCtl{toolInstalldSpec: toolInstallSpec, log: log, step: stepTask}, nil +} + +func (s *archiveCtl) PreRun(ctx context.Context) { + spec := &step.StepArchiveSpec{ + FilePath: s.toolInstalldSpec.FilePath, + AbsFilePath: s.toolInstalldSpec.AbsFilePath, + DestinationPath: s.toolInstalldSpec.DestinationPath, + S3: &step.S3{ + Ak: config.S3StorageAK(), + Sk: config.S3StorageSK(), + Endpoint: config.S3StorageEndpoint(), + Bucket: config.S3StorageBucket(), + Subfolder: config.S3StoragePath(), + Protocol: config.S3StorageProtocol(), + }, + } + if s.toolInstalldSpec.S3StorageID == "" { + s.toolInstalldSpec = spec + return + } + objectStorage, _ := mongodb.NewS3StorageColl().Find(s.toolInstalldSpec.S3StorageID) + if objectStorage != nil { + spec.S3.Endpoint = objectStorage.Endpoint + spec.S3.Sk = objectStorage.Sk + spec.S3.Ak = objectStorage.Ak + spec.S3.Subfolder = objectStorage.Subfolder + spec.S3.Bucket = objectStorage.Bucket + spec.S3.Protocol = "https" + if objectStorage.Insecure { + spec.S3.Protocol = "http" + } + } + s.step.Spec = spec +} + +func (s *archiveCtl) AfterRun(ctx context.Context) { + +} diff --git a/pkg/microservice/jobexecutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go index c42d8526..d25fecfc 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -73,6 +73,11 @@ func runStep(ctx context.Context, step *meta.Step, workspace, paths string, envs if err != nil { return err } + case "archive": + stepInstance, err = NewArchiveStep(step.Spec, workspace, envs, secretEnvs) + if err != nil { + return err + } default: err := fmt.Errorf("step type: %s does not match any known type", step.StepType) log.Error(err) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_archive.go b/pkg/microservice/jobexecutor/core/service/step/step_archive.go new file mode 100644 index 00000000..d758cf2f --- /dev/null +++ b/pkg/microservice/jobexecutor/core/service/step/step_archive.go @@ -0,0 +1,111 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/types/step" + "gopkg.in/yaml.v3" +) + +type ArchiveStep struct { + spec *step.StepArchiveSpec + envs []string + secretEnvs []string + workspace string +} + +func NewArchiveStep(spec interface{}, workspace string, envs, secretEnvs []string) (*ArchiveStep, error) { + archiveStep := &ArchiveStep{workspace: workspace, envs: envs, secretEnvs: secretEnvs} + yamlBytes, err := yaml.Marshal(spec) + if err != nil { + return archiveStep, fmt.Errorf("marshal spec %+v failed", spec) + } + if err := yaml.Unmarshal(yamlBytes, &archiveStep.spec); err != nil { + return archiveStep, fmt.Errorf("unmarshal spec %s to shell spec failed", yamlBytes) + } + return archiveStep, nil +} + +func (s *ArchiveStep) Run(ctx context.Context) error { + if s.spec.DestinationPath == "" || s.spec.FilePath == "" { + return nil + } + forcedPathStyle := true + if s.spec.S3.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + client, err := s3.NewClient(s.spec.S3.Endpoint, s.spec.S3.Ak, s.spec.S3.Sk, s.spec.S3.Insecure, forcedPathStyle) + if err != nil { + return fmt.Errorf("failed to create s3 client to upload file, err: %s", err) + } + + envmaps := make(map[string]string) + for _, env := range s.envs { + kv := strings.Split(env, "=") + if len(kv) != 2 { + continue + } + envmaps[kv[0]] = kv[1] + } + for _, secretEnv := range s.secretEnvs { + kv := strings.Split(secretEnv, "=") + if len(kv) != 2 { + continue + } + envmaps[kv[0]] = kv[1] + } + + s.spec.AbsFilePath = fmt.Sprintf("$WORKSPACE/%s", s.spec.FilePath) + s.spec.AbsFilePath = replaceEnvWithValue(s.spec.AbsFilePath, envmaps) + s.spec.DestinationPath = replaceEnvWithValue(s.spec.DestinationPath, envmaps) + + info, err := os.Stat(s.spec.AbsFilePath) + if err != nil { + return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", s.spec.AbsFilePath, s.spec.DestinationPath, err) + } + // if the given path is a directory + if info.IsDir() { + err := client.UploadDir(s.spec.S3.Bucket, s.spec.AbsFilePath, s.spec.DestinationPath) + if err != nil { + return err + } + } else { + key := filepath.Join(s.spec.DestinationPath, info.Name()) + err := client.Upload(s.spec.S3.Bucket, s.spec.AbsFilePath, key) + if err != nil { + return err + } + } + return nil +} + +func replaceEnvWithValue(str string, envs map[string]string) string { + ret := str + for key, value := range envs { + strKey := fmt.Sprintf("$%s", key) + ret = strings.ReplaceAll(ret, strKey, value) + } + return ret +} diff --git a/pkg/types/step/common.go b/pkg/types/step/common.go new file mode 100644 index 00000000..f759222b --- /dev/null +++ b/pkg/types/step/common.go @@ -0,0 +1,39 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +type Proxy struct { + Type string `yaml:"type"` + Address string `yaml:"address"` + Port int `yaml:"port"` + NeedPassword bool `yaml:"need_password"` + Username string `yaml:"username"` + Password string `yaml:"password"` + EnableRepoProxy bool `yaml:"enable_repo_proxy"` + EnableApplicationProxy bool `yaml:"enable_application_proxy"` +} + +type S3 struct { + Ak string `yaml:"ak"` + Sk string `yaml:"sk"` + Endpoint string `yaml:"endpoint"` + Bucket string `yaml:"bucket"` + Subfolder string `yaml:"subfolder"` + Insecure bool `yaml:"insecure"` + Provider int `yaml:"provider"` + Protocol string `yaml:"protocol"` +} diff --git a/pkg/types/step/step_archive.go b/pkg/types/step/step_archive.go new file mode 100644 index 00000000..b6d012b9 --- /dev/null +++ b/pkg/types/step/step_archive.go @@ -0,0 +1,25 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +type StepArchiveSpec struct { + FilePath string `yaml:"file_path"` + AbsFilePath string `yaml:"abs_file_path"` + DestinationPath string `yaml:"dest_path"` + S3StorageID string `yaml:"s3_storage_id"` + S3 *S3 `yaml:"s3_storage"` +} diff --git a/pkg/types/step/step_git.go b/pkg/types/step/step_git.go index 0c577f18..2ed4aa95 100644 --- a/pkg/types/step/step_git.go +++ b/pkg/types/step/step_git.go @@ -51,17 +51,6 @@ type Repo struct { PrivateAccessToken string `yaml:"private_access_token,omitempty"` } -type Proxy struct { - Type string `yaml:"type"` - Address string `yaml:"address"` - Port int `yaml:"port"` - NeedPassword bool `yaml:"need_password"` - Username string `yaml:"username"` - Password string `yaml:"password"` - EnableRepoProxy bool `yaml:"enable_repo_proxy"` - EnableApplicationProxy bool `yaml:"enable_application_proxy"` -} - const ( // ProviderGithub ... ProviderGithub = "github" diff --git a/pkg/types/step/step_tool_install.go b/pkg/types/step/step_tool_install.go index b79bde7a..9ad31e01 100644 --- a/pkg/types/step/step_tool_install.go +++ b/pkg/types/step/step_tool_install.go @@ -9,14 +9,3 @@ type StepToolInstallSpec struct { Download string `yaml:"download"` S3Storage *S3 `yaml:"s3_storage"` } - -type S3 struct { - Ak string `yaml:"ak"` - Sk string `yaml:"sk"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` - Subfolder string `yaml:"subfolder"` - Insecure bool `yaml:"insecure"` - Provider int `yaml:"provider"` - Protocol string `yaml:"protocol"` -} -- Gitee From 6449f7cde2b90b7ea6b646205f2978e04eadff59 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 17 Jun 2022 09:46:30 +0800 Subject: [PATCH 2465/2647] fix host query filter (#1678) Signed-off-by: allenshen --- .../aslan/core/common/repository/mongodb/private_key.go | 4 +++- pkg/microservice/aslan/core/project/handler/host.go | 2 +- pkg/microservice/aslan/core/system/handler/private_key.go | 2 +- pkg/microservice/aslan/core/system/service/private_key.go | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go index cc7e47b8..cf87ff60 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/private_key.go @@ -34,6 +34,7 @@ import ( type PrivateKeyArgs struct { Name string ProjectName string + SystemOnly bool } type PrivateKeyColl struct { @@ -101,7 +102,8 @@ func (c *PrivateKeyColl) List(args *PrivateKeyArgs) ([]*models.PrivateKey, error if args.ProjectName != "" { query["project_name"] = args.ProjectName - } else { + } + if args.SystemOnly { query["project_name"] = bson.M{"$exists": false} } diff --git a/pkg/microservice/aslan/core/project/handler/host.go b/pkg/microservice/aslan/core/project/handler/host.go index b453a0c1..5ae80af0 100644 --- a/pkg/microservice/aslan/core/project/handler/host.go +++ b/pkg/microservice/aslan/core/project/handler/host.go @@ -46,7 +46,7 @@ func ListPMHosts(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = systemservice.ListPrivateKeys(encryptedKey, c.Query("projectName"), c.Query("keyword"), ctx.Logger) + ctx.Resp, ctx.Err = systemservice.ListPrivateKeys(encryptedKey, c.Query("projectName"), c.Query("keyword"), false, ctx.Logger) } func GetPMHost(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/handler/private_key.go b/pkg/microservice/aslan/core/system/handler/private_key.go index 265c75ba..2043e7a2 100644 --- a/pkg/microservice/aslan/core/system/handler/private_key.go +++ b/pkg/microservice/aslan/core/system/handler/private_key.go @@ -48,7 +48,7 @@ func ListPrivateKeys(c *gin.Context) { ctx.Err = e.ErrInvalidParam return } - ctx.Resp, ctx.Err = service.ListPrivateKeys(encryptedKey, "", c.Query("keyword"), ctx.Logger) + ctx.Resp, ctx.Err = service.ListPrivateKeys(encryptedKey, "", c.Query("keyword"), true, ctx.Logger) } func GetPrivateKey(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/system/service/private_key.go b/pkg/microservice/aslan/core/system/service/private_key.go index 64247234..b9bc0b84 100644 --- a/pkg/microservice/aslan/core/system/service/private_key.go +++ b/pkg/microservice/aslan/core/system/service/private_key.go @@ -34,10 +34,10 @@ import ( "github.com/koderover/zadig/pkg/types" ) -func ListPrivateKeys(encryptedKey, projectName, keyword string, log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { +func ListPrivateKeys(encryptedKey, projectName, keyword string, systemOnly bool, log *zap.SugaredLogger) ([]*commonmodels.PrivateKey, error) { var resp []*commonmodels.PrivateKey var err error - privateKeys, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{ProjectName: projectName}) + privateKeys, err := commonrepo.NewPrivateKeyColl().List(&commonrepo.PrivateKeyArgs{ProjectName: projectName, SystemOnly: systemOnly}) if err != nil { log.Errorf("PrivateKey.List error: %s", err) return resp, e.ErrListPrivateKeys -- Gitee From b863935419497879c868685a8680a732d2c22a9f Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 17 Jun 2022 14:26:46 +0800 Subject: [PATCH 2466/2647] fix helm dev delete ns bug (#1682) * fix helm dev delete ns bug Signed-off-by: allenshen * fix helm dev delete ns bug Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- .../core/environment/service/environment.go | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 30f1fa4f..c7600973 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2121,11 +2121,11 @@ func DeleteProduct(username, envName, productName, requestID string, isDelete bo } go func() { - var err error + errList := &multierror.Error{} defer func() { - if err != nil { + if errList.ErrorOrNil() != nil { title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 失败!", productName, envName) - commonservice.SendErrorMessage(username, title, requestID, err, log) + commonservice.SendErrorMessage(username, title, requestID, errList.ErrorOrNil(), log) _ = commonrepo.NewProductColl().UpdateStatus(envName, productName, setting.ProductStatusUnknown) } else { title := fmt.Sprintf("删除项目:[%s] 环境:[%s] 成功!", productName, envName) @@ -2134,22 +2134,25 @@ func DeleteProduct(username, envName, productName, requestID string, isDelete bo } }() - if hc, err := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); err == nil { - for _, services := range productInfo.Services { - for _, service := range services { + if isDelete { + if hc, errHelmClient := helmtool.NewClientFromRestConf(restConfig, productInfo.Namespace); errHelmClient == nil { + for _, service := range productInfo.GetServiceMap() { if err = UninstallServiceByName(hc, service.ServiceName, productInfo, service.Revision, true); err != nil { - log.Errorf("UninstallRelease err:%v", err) + log.Warnf("UninstallRelease for service %s err:%s", service.ServiceName, err) + errList = multierror.Append(errList, err) } } + } else { + log.Errorf("failed to get helmClient, err: %s", errHelmClient) + errList = multierror.Append(errList, e.ErrDeleteEnv.AddErr(errHelmClient)) + return } - } else { - log.Errorf("获取helmClient err:%v", err) - } - s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() - if err1 := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err1 != nil { - err = e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg + ": " + err1.Error()) - return + s := labels.Set{setting.EnvCreatedBy: setting.EnvCreator}.AsSelector() + if err := commonservice.DeleteNamespaceIfMatch(productInfo.Namespace, s, productInfo.ClusterID, log); err != nil { + errList = multierror.Append(errList, e.ErrDeleteEnv.AddDesc(e.DeleteNamespaceErrMsg+": "+err.Error())) + return + } } }() case setting.SourceFromExternal: -- Gitee From 0a30702896f02d54328848fc68f6984d3df35971 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 17 Jun 2022 17:26:59 +0800 Subject: [PATCH 2467/2647] fix helm workload change image tag fail bug (#1684) Signed-off-by: allenshen --- .../aslan/core/environment/service/image.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index a67fd515..296bcb3c 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -63,9 +63,11 @@ func getValidMatchData(spec *models.ImagePathSpec) map[string]string { } // prepare necessary data from db -func prepareData(namespace, serviceName string, containerName string, product *models.Product) (targetContainer *models.Container, +func prepareData(namespace, serviceName, containerName, image string, product *models.Product) (targetContainer *models.Container, targetChart *templatemodels.RenderChart, renderSet *models.RenderSet, serviceObj *models.Service, err error) { + imageName := commonservice.ExtractImageName(image) + var targetProductService *models.ProductService for _, productService := range product.GetServiceMap() { if productService.ServiceName != serviceName { @@ -73,16 +75,15 @@ func prepareData(namespace, serviceName string, containerName string, product *m } targetProductService = productService for _, container := range productService.Containers { - if container.Name != containerName { - continue + if container.ImageName == imageName { + targetContainer = container + break } - targetContainer = container - break } break } if targetContainer == nil { - err = fmt.Errorf("failed to find container %s", containerName) + err = fmt.Errorf("failed to find image config: %s for container %s", imageName, containerName) return } @@ -130,7 +131,7 @@ func updateContainerForHelmChart(serviceName, resType, image, containerName stri namespace string = product.Namespace ) - targetContainer, targetChart, renderSet, serviceObj, err := prepareData(namespace, serviceName, containerName, product) + targetContainer, targetChart, renderSet, serviceObj, err := prepareData(namespace, serviceName, containerName, image, product) if err != nil { return err } -- Gitee From d41258726d8071b79e3f40e0fafd3f68d7094a36 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 20 Jun 2022 14:35:04 +0800 Subject: [PATCH 2468/2647] fixed repeated failure of update service template (#1686) * rollback init config Signed-off-by: panxunying * fix update service template error Signed-off-by: panxunying * format Signed-off-by: panxunying Co-authored-by: panxunying --- pkg/microservice/aslan/core/service/handler/service.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index d9052122..8bfc29dd 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -90,9 +90,16 @@ func CreateServiceTemplate(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid ServiceTmpl json args") return } + + force, err := strconv.ParseBool(c.Query("force")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("force params error") + return + } + args.CreateBy = ctx.UserName - ctx.Resp, ctx.Err = svcservice.CreateServiceTemplate(ctx.UserName, args, false, ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateServiceTemplate(ctx.UserName, args, force, ctx.Logger) } func UpdateServiceTemplate(c *gin.Context) { -- Gitee From 26eb8cbc7146dbae8dda1a9d5bf365ab4a9f3992 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 20 Jun 2022 16:45:24 +0800 Subject: [PATCH 2469/2647] add deploy and helm-deloy step Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 1 + .../workflow/service/jobcontroller/job.go | 9 +- .../workflow/service/stepcontroller/step.go | 45 +- .../service/stepcontroller/step_archive.go | 19 +- .../service/stepcontroller/step_deploy.go | 347 ++++++++++++ .../stepcontroller/step_docker_build.go | 9 +- .../service/stepcontroller/step_git.go | 14 +- .../stepcontroller/step_helm_deploy.go | 533 ++++++++++++++++++ .../service/stepcontroller/step_shell.go | 11 +- .../stepcontroller/step_tool_install.go | 12 +- .../service/workflowcontroller/workflow.go | 1 + pkg/types/step/step_deploy.go | 35 ++ pkg/types/step/step_helm_deploy.go | 33 ++ 13 files changed, 1040 insertions(+), 29 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go create mode 100644 pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go create mode 100644 pkg/types/step/step_deploy.go create mode 100644 pkg/types/step/step_helm_deploy.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index e21b9df5..d280be42 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -82,6 +82,7 @@ type StepTask struct { type WorkflowTaskCtx struct { WorkflowName string + ProjectName string TaskID int64 DockerHost string Workspace string diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index f1494d20..04e3ab4b 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -73,7 +73,14 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm var jobCtl JobCtl switch job.JobType { case "deploy": - // TODO + // do deploy inside aslan instead of jobexecutor. + status, err := stepcontroller.RunSteps(ctx, workflowCtx, &job.Properties.Paths, job.Steps, logger) + job.Status = status + if err != nil { + logger.Error(err) + job.Error = err.Error() + } + return default: jobCtl = NewFreestyleJobCtl(job, workflowCtx, ack, logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go index 8ede472c..b5c234b3 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go @@ -13,9 +13,12 @@ import ( type StepCtl interface { // get required info from aslan before job run. - PreRun(ctx context.Context) + PreRun(ctx context.Context) error + // run specific step, now only use for deploy job (freestyle job merge step together) + Run(ctx context.Context) (config.Status, error) + // collect required info after job run.(like test report) - AfterRun(ctx context.Context) + AfterRun(ctx context.Context) error } func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, jobPath *string, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { @@ -38,34 +41,56 @@ func PrepareSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx stepCtls := []StepCtl{} for _, step := range steps { - stepCtl, err := instantiateStepCtl(step, jobPath, logger) + stepCtl, err := instantiateStepCtl(step, workflowCtx, jobPath, logger) if err != nil { return err } stepCtls = append(stepCtls, stepCtl) } for _, stepCtl := range stepCtls { - stepCtl.PreRun(ctx) + if err := stepCtl.PreRun(ctx); err != nil { + return err + } } return nil } +func RunSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, jobPath *string, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) (config.Status, error) { + stepCtls := []StepCtl{} + for _, step := range steps { + stepCtl, err := instantiateStepCtl(step, workflowCtx, jobPath, logger) + if err != nil { + return config.StatusFailed, err + } + stepCtls = append(stepCtls, stepCtl) + } + for _, stepCtl := range stepCtls { + status, err := stepCtl.Run(ctx) + if err != nil || status != config.StatusPassed { + return status, err + } + } + return config.StatusPassed, nil +} + func SummarizeSteps(ctx context.Context, workflowCtx *commonmodels.WorkflowTaskCtx, jobPath *string, steps []*commonmodels.StepTask, logger *zap.SugaredLogger) error { stepCtls := []StepCtl{} for _, step := range steps { - stepCtl, err := instantiateStepCtl(step, jobPath, logger) + stepCtl, err := instantiateStepCtl(step, workflowCtx, jobPath, logger) if err != nil { return err } stepCtls = append(stepCtls, stepCtl) } for _, stepCtl := range stepCtls { - stepCtl.AfterRun(ctx) + if err := stepCtl.AfterRun(ctx); err != nil { + return nil + } } return nil } -func instantiateStepCtl(step *commonmodels.StepTask, jobPath *string, logger *zap.SugaredLogger) (StepCtl, error) { +func instantiateStepCtl(step *commonmodels.StepTask, workflowCtx *commonmodels.WorkflowTaskCtx, jobPath *string, logger *zap.SugaredLogger) (StepCtl, error) { var stepCtl StepCtl var err error switch step.StepType { @@ -78,7 +103,11 @@ func instantiateStepCtl(step *commonmodels.StepTask, jobPath *string, logger *za case config.StepTools: stepCtl, err = NewToolInstallCtl(step, jobPath, logger) case config.StepArchive: - stepCtl, err = NewArchiveInstallCtl(step, logger) + stepCtl, err = NewArchiveCtl(step, logger) + case config.StepDeploy: + stepCtl, err = NewDeployCtl(step, workflowCtx, logger) + case config.StepHelmDeploy: + stepCtl, err = NewHelmDeployCtl(step, workflowCtx, logger) default: logger.Infof("unknown step type: %s", step.StepType) } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go index bfb7c757..a170cbdd 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go @@ -18,19 +18,19 @@ type archiveCtl struct { log *zap.SugaredLogger } -func NewArchiveInstallCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*archiveCtl, error) { +func NewArchiveCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*archiveCtl, error) { yamlString, err := yaml.Marshal(stepTask.Spec) if err != nil { return nil, fmt.Errorf("marshal tool install spec error: %v", err) } - toolInstallSpec := &step.StepArchiveSpec{} - if err := yaml.Unmarshal(yamlString, &toolInstallSpec); err != nil { + archiveSpec := &step.StepArchiveSpec{} + if err := yaml.Unmarshal(yamlString, &archiveSpec); err != nil { return nil, fmt.Errorf("unmarshal git spec error: %v", err) } - return &archiveCtl{toolInstalldSpec: toolInstallSpec, log: log, step: stepTask}, nil + return &archiveCtl{toolInstalldSpec: archiveSpec, log: log, step: stepTask}, nil } -func (s *archiveCtl) PreRun(ctx context.Context) { +func (s *archiveCtl) PreRun(ctx context.Context) error { spec := &step.StepArchiveSpec{ FilePath: s.toolInstalldSpec.FilePath, AbsFilePath: s.toolInstalldSpec.AbsFilePath, @@ -46,7 +46,7 @@ func (s *archiveCtl) PreRun(ctx context.Context) { } if s.toolInstalldSpec.S3StorageID == "" { s.toolInstalldSpec = spec - return + return nil } objectStorage, _ := mongodb.NewS3StorageColl().Find(s.toolInstalldSpec.S3StorageID) if objectStorage != nil { @@ -61,8 +61,13 @@ func (s *archiveCtl) PreRun(ctx context.Context) { } } s.step.Spec = spec + return nil } -func (s *archiveCtl) AfterRun(ctx context.Context) { +func (s *archiveCtl) Run(ctx context.Context) (config.Status, error) { + return config.StatusPassed, nil +} +func (s *archiveCtl) AfterRun(ctx context.Context) error { + return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go new file mode 100644 index 00000000..fdc76111 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go @@ -0,0 +1,347 @@ +package stepcontroller + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + "github.com/koderover/zadig/pkg/shared/kube/wrapper" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/types/step" + "github.com/pkg/errors" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type deployCtl struct { + step *commonmodels.StepTask + deploySpec *step.StepDeploySpec + workflowCtx *commonmodels.WorkflowTaskCtx + namespace string + log *zap.SugaredLogger + kubeClient client.Client + restConfig *rest.Config +} + +func NewDeployCtl(stepTask *commonmodels.StepTask, workflowCtx *commonmodels.WorkflowTaskCtx, log *zap.SugaredLogger) (*deployCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal deploy spec error: %v", err) + } + deploySpec := &step.StepDeploySpec{} + if err := yaml.Unmarshal(yamlString, &deploySpec); err != nil { + return nil, fmt.Errorf("unmarshal deploy spec error: %v", err) + } + return &deployCtl{deploySpec: deploySpec, workflowCtx: workflowCtx, log: log, step: stepTask}, nil +} + +func (s *deployCtl) PreRun(ctx context.Context) error { + return nil +} + +func (s *deployCtl) Run(ctx context.Context) (config.Status, error) { + if err := s.run(ctx); err != nil { + return config.StatusFailed, err + } + return s.wait(ctx) +} + +func (s *deployCtl) AfterRun(ctx context.Context) error { + return nil +} + +func (s *deployCtl) run(ctx context.Context) error { + var ( + err error + replaced = false + ) + env, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: s.workflowCtx.ProjectName, + EnvName: s.deploySpec.Env, + }) + if err != nil { + return err + } + s.namespace = env.Namespace + s.deploySpec.ClusterID = env.ClusterID + + if s.deploySpec.ClusterID != "" { + s.restConfig, err = kubeclient.GetRESTConfig(config.HubServerAddress(), s.deploySpec.ClusterID) + if err != nil { + err = errors.WithMessage(err, "can't get k8s rest config") + return err + } + + s.kubeClient, err = kubeclient.GetKubeClient(config.HubServerAddress(), s.deploySpec.ClusterID) + if err != nil { + err = errors.WithMessage(err, "can't init k8s client") + return err + } + } else { + s.kubeClient = krkubeclient.Client() + s.restConfig = krkubeclient.RESTConfig() + } + + // get servcie info + var ( + serviceInfo *commonmodels.Service + selector labels.Selector + ) + serviceInfo, err = commonrepo.NewServiceColl().Find( + &commonrepo.ServiceFindOption{ + ServiceName: s.deploySpec.ServiceName, + ProductName: s.workflowCtx.ProjectName, + ExcludeStatus: setting.ProductStatusDeleting, + Type: s.deploySpec.ServiceType, + }) + if err != nil { + // Maybe it is a share service, the entity is not under the project + serviceInfo, err = commonrepo.NewServiceColl().Find( + &commonrepo.ServiceFindOption{ + ServiceName: s.deploySpec.ServiceName, + ExcludeStatus: setting.ProductStatusDeleting, + Type: s.deploySpec.ServiceType, + }) + if err != nil { + return err + } + } + + if serviceInfo.WorkloadType == "" { + selector = labels.Set{setting.ProductLabel: s.workflowCtx.ProjectName, setting.ServiceLabel: s.deploySpec.ServiceName}.AsSelector() + + var deployments []*appsv1.Deployment + deployments, err = getter.ListDeployments(env.Namespace, selector, s.kubeClient) + if err != nil { + return err + } + + var statefulSets []*appsv1.StatefulSet + statefulSets, err = getter.ListStatefulSets(env.Namespace, selector, s.kubeClient) + if err != nil { + return err + } + + L: + for _, deploy := range deployments { + for _, container := range deploy.Spec.Template.Spec.Containers { + if container.Name == s.deploySpec.ServiceModule { + err = updater.UpdateDeploymentImage(deploy.Namespace, deploy.Name, s.deploySpec.ServiceModule, s.deploySpec.Image, s.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/deployments/%s/%s", + env.Namespace, deploy.Name, container.Name) + return err + } + s.deploySpec.ReplaceResources = append(s.deploySpec.ReplaceResources, step.Resource{ + Kind: setting.Deployment, + Container: container.Name, + Origin: container.Image, + Name: deploy.Name, + }) + replaced = true + break L + } + } + } + Loop: + for _, sts := range statefulSets { + for _, container := range sts.Spec.Template.Spec.Containers { + if container.Name == s.deploySpec.ServiceModule { + err = updater.UpdateStatefulSetImage(sts.Namespace, sts.Name, s.deploySpec.ServiceModule, s.deploySpec.Image, s.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/statefulsets/%s/%s", + env.Namespace, sts.Name, container.Name) + return err + } + s.deploySpec.ReplaceResources = append(s.deploySpec.ReplaceResources, step.Resource{ + Kind: setting.StatefulSet, + Container: container.Name, + Origin: container.Image, + Name: sts.Name, + }) + replaced = true + break Loop + } + } + } + } else { + switch serviceInfo.WorkloadType { + case setting.StatefulSet: + var statefulSet *appsv1.StatefulSet + statefulSet, _, err = getter.GetStatefulSet(env.Namespace, s.deploySpec.ServiceName, s.kubeClient) + if err != nil { + return err + } + for _, container := range statefulSet.Spec.Template.Spec.Containers { + if container.Name == s.deploySpec.ServiceModule { + err = updater.UpdateStatefulSetImage(statefulSet.Namespace, statefulSet.Name, s.deploySpec.ServiceModule, s.deploySpec.Image, s.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/statefulsets/%s/%s", + env.Namespace, statefulSet.Name, container.Name) + return err + } + s.deploySpec.ReplaceResources = append(s.deploySpec.ReplaceResources, step.Resource{ + Kind: setting.StatefulSet, + Container: container.Name, + Origin: container.Image, + Name: statefulSet.Name, + }) + replaced = true + break + } + } + case setting.Deployment: + var deployment *appsv1.Deployment + deployment, _, err = getter.GetDeployment(env.Namespace, s.deploySpec.ServiceName, s.kubeClient) + if err != nil { + return err + } + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == s.deploySpec.ServiceModule { + err = updater.UpdateDeploymentImage(deployment.Namespace, deployment.Name, s.deploySpec.ServiceModule, s.deploySpec.Image, s.kubeClient) + if err != nil { + err = errors.WithMessagef( + err, + "failed to update container image in %s/deployments/%s/%s", + env.Namespace, deployment.Name, container.Name) + return err + } + s.deploySpec.ReplaceResources = append(s.deploySpec.ReplaceResources, step.Resource{ + Kind: setting.Deployment, + Container: container.Name, + Origin: container.Image, + Name: deployment.Name, + }) + replaced = true + break + } + } + } + } + if !replaced { + err = errors.Errorf( + "container %s is not found in resources with label %s", s.deploySpec.ServiceModule, selector) + return err + } + s.step.Spec = s.deploySpec + return nil +} + +func (s *deployCtl) wait(ctx context.Context) (config.Status, error) { + timeout := time.After(time.Duration(s.timeout()) * time.Second) + + selector := labels.Set{setting.ProductLabel: s.workflowCtx.ProjectName, setting.ServiceLabel: s.deploySpec.ServiceName}.AsSelector() + + for { + select { + case <-ctx.Done(): + return config.StatusCancelled, nil + + case <-timeout: + pods, err := getter.ListPods(s.namespace, selector, s.kubeClient) + if err != nil { + return config.StatusFailed, err + } + + var msg []string + for _, pod := range pods { + podResource := wrapper.Pod(pod).Resource() + if podResource.Status != setting.StatusRunning && podResource.Status != setting.StatusSucceeded { + for _, cs := range podResource.ContainerStatuses { + // message为空不认为是错误状态,有可能还在waiting + if cs.Message != "" { + msg = append(msg, fmt.Sprintf("Status: %s, Reason: %s, Message: %s", cs.Status, cs.Reason, cs.Message)) + } + } + } + } + + if len(msg) != 0 { + err = errors.New(strings.Join(msg, "\n")) + } + + return config.StatusTimeout, err + + default: + time.Sleep(time.Second * 2) + ready := true + var err error + L: + for _, resource := range s.deploySpec.ReplaceResources { + switch resource.Kind { + case setting.Deployment: + d, found, e := getter.GetDeployment(s.namespace, resource.Name, s.kubeClient) + if e != nil { + err = e + } + if e != nil || !found { + s.log.Errorf( + "failed to check deployment ready status %s/%s/%s - %v", + s.namespace, + resource.Kind, + resource.Name, + e, + ) + ready = false + } else { + ready = wrapper.Deployment(d).Ready() + } + + if !ready { + break L + } + case setting.StatefulSet: + st, found, e := getter.GetStatefulSet(s.namespace, resource.Name, s.kubeClient) + if e != nil { + err = e + } + if err != nil || !found { + s.log.Errorf( + "failed to check statefulSet ready status %s/%s/%s", + s.namespace, + resource.Kind, + resource.Name, + e, + ) + ready = false + } else { + ready = wrapper.StatefulSet(st).Ready() + } + + if !ready { + break L + } + } + } + + if ready { + return config.StatusPassed, nil + } + } + } +} + +func (s *deployCtl) timeout() int { + if s.deploySpec.Timeout == 0 { + s.deploySpec.Timeout = setting.DeployTimeout + } + return s.deploySpec.Timeout +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go index 4d073759..7a5a8b5b 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go @@ -33,7 +33,7 @@ func NewDockerBuildCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) return &dockerBuildCtl{dockerBuildSpec: dockerBuildSpec, log: log, step: stepTask}, nil } -func (s *dockerBuildCtl) PreRun(ctx context.Context) { +func (s *dockerBuildCtl) PreRun(ctx context.Context) error { s.dockerBuildSpec.DockerRegistry = &step.DockerRegistry{ UserName: config.RegistryAccessKey(), Password: config.RegistrySecretKey(), @@ -52,8 +52,13 @@ func (s *dockerBuildCtl) PreRun(ctx context.Context) { s.dockerBuildSpec.Proxy.Username = proxies[0].Username } s.step.Spec = s.dockerBuildSpec + return nil } -func (s *dockerBuildCtl) AfterRun(ctx context.Context) { +func (s *dockerBuildCtl) Run(ctx context.Context) (config.Status, error) { + return config.StatusPassed, nil +} +func (s *dockerBuildCtl) AfterRun(ctx context.Context) error { + return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go index 06d64150..b903eb2a 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" @@ -34,17 +35,17 @@ func NewGitCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*gitCtl return &gitCtl{gitSpec: gitSpec, log: log, step: stepTask}, nil } -func (s *gitCtl) PreRun(ctx context.Context) { +func (s *gitCtl) PreRun(ctx context.Context) error { for _, repo := range s.gitSpec.Repos { cID := repo.CodeHostID if cID == 0 { log.Error("codehostID can't be empty") - return + return nil } detail, err := systemconfig.New().GetCodeHost(cID) if err != nil { s.log.Error(err) - return + return nil } repo.Source = detail.Type repo.OauthToken = detail.AccessToken @@ -65,8 +66,13 @@ func (s *gitCtl) PreRun(ctx context.Context) { s.gitSpec.Proxy.Username = proxies[0].Username } s.step.Spec = s.gitSpec + return nil } -func (s *gitCtl) AfterRun(ctx context.Context) { +func (s *gitCtl) Run(ctx context.Context) (config.Status, error) { + return config.StatusPassed, nil +} +func (s *gitCtl) AfterRun(ctx context.Context) error { + return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go new file mode 100644 index 00000000..d8ba81c5 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go @@ -0,0 +1,533 @@ +package stepcontroller + +import ( + "context" + "fmt" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" + helmtool "github.com/koderover/zadig/pkg/tool/helmclient" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + s3tool "github.com/koderover/zadig/pkg/tool/s3" + "github.com/koderover/zadig/pkg/types/step" + "github.com/koderover/zadig/pkg/util/converter" + fsutil "github.com/koderover/zadig/pkg/util/fs" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" + helmclient "github.com/mittwald/go-helm-client" + "github.com/pkg/errors" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/releaseutil" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + imageUrlParseRegexString = `(?P.+/)?(?P[^:]+){1}(:)?(?P.+)?` +) + +var ( + imageParseRegex = regexp.MustCompile(imageUrlParseRegexString) +) + +type helmDeployCtl struct { + step *commonmodels.StepTask + helmDeploySpec *step.StepHelmDeploySpec + workflowCtx *commonmodels.WorkflowTaskCtx + namespace string + log *zap.SugaredLogger + kubeClient client.Client + restConfig *rest.Config +} + +func NewHelmDeployCtl(stepTask *commonmodels.StepTask, workflowCtx *commonmodels.WorkflowTaskCtx, log *zap.SugaredLogger) (*helmDeployCtl, error) { + yamlString, err := yaml.Marshal(stepTask.Spec) + if err != nil { + return nil, fmt.Errorf("marshal helm deploy spec error: %v", err) + } + helmDeploySpec := &step.StepHelmDeploySpec{} + if err := yaml.Unmarshal(yamlString, &helmDeploySpec); err != nil { + return nil, fmt.Errorf("unmarshal helm deploy spec error: %v", err) + } + return &helmDeployCtl{helmDeploySpec: helmDeploySpec, workflowCtx: workflowCtx, log: log, step: stepTask}, nil +} + +func (s *helmDeployCtl) PreRun(ctx context.Context) error { + return nil +} + +func (s *helmDeployCtl) Run(ctx context.Context) (config.Status, error) { + if err := s.run(ctx); err != nil { + return config.StatusFailed, err + } + return config.StatusPassed, nil +} + +func (s *helmDeployCtl) AfterRun(ctx context.Context) error { + return nil +} + +func (s *helmDeployCtl) run(ctx context.Context) error { + var ( + err error + ) + + env, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: s.workflowCtx.ProjectName, + EnvName: s.helmDeploySpec.Env, + }) + if err != nil { + return err + } + s.namespace = env.Namespace + s.helmDeploySpec.ClusterID = env.ClusterID + + if s.helmDeploySpec.ClusterID != "" { + s.restConfig, err = kubeclient.GetRESTConfig(config.HubServerAddress(), s.helmDeploySpec.ClusterID) + if err != nil { + err = errors.WithMessage(err, "can't get k8s rest config") + return err + } + + s.kubeClient, err = kubeclient.GetKubeClient(config.HubServerAddress(), s.helmDeploySpec.ClusterID) + if err != nil { + err = errors.WithMessage(err, "can't init k8s client") + return err + } + } else { + s.kubeClient = krkubeclient.Client() + s.restConfig = krkubeclient.RESTConfig() + } + + // all involved containers + containerNameSet := sets.NewString() + for _, svcAndContainer := range s.helmDeploySpec.ImageAndModules { + singleContainerName := strings.TrimSuffix(svcAndContainer.ServiceModule, "_"+s.helmDeploySpec.ServiceName) + containerNameSet.Insert(singleContainerName) + } + + var ( + productInfo *commonmodels.Product + renderChart *templatemodels.RenderChart + replacedValuesYaml string + mergedValuesYaml string + replacedMergedValuesYaml string + servicePath string + chartPath string + replaceValuesMap map[string]interface{} + renderInfo *commonmodels.RenderSet + helmClient helmclient.Client + ) + + s.log.Infof("start helm deploy, productName %s serviceName %s containerName %v namespace %s", s.workflowCtx.ProjectName, + s.helmDeploySpec.ServiceName, containerNameSet.List(), s.namespace) + + productInfo, err = commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: s.workflowCtx.ProjectName, EnvName: s.helmDeploySpec.Env}) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get product %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + + renderInfo, err = commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision}) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get getRenderSet %s/%d", + productInfo.Render.Name, productInfo.Render.Revision) + return err + } + + serviceRevisionInProduct := int64(0) + involvedImagePaths := make(map[string]*commonmodels.ImagePathSpec) + for _, service := range productInfo.GetServiceMap() { + if service.ServiceName != s.helmDeploySpec.ServiceName { + continue + } + serviceRevisionInProduct = service.Revision + for _, container := range service.Containers { + if !containerNameSet.Has(container.Name) { + continue + } + if container.ImagePath == nil { + err = errors.WithMessagef(err, "image path of %s/%s is nil", service.ServiceName, container.Name) + return err + } + involvedImagePaths[container.Name] = container.ImagePath + } + break + } + + if len(involvedImagePaths) == 0 { + err = errors.Errorf("failed to find containers from service %s", s.helmDeploySpec.ServiceName) + return err + } + + for _, chartInfo := range renderInfo.ChartInfos { + if chartInfo.ServiceName == s.helmDeploySpec.ServiceName { + renderChart = chartInfo + break + } + } + + if renderChart == nil { + err = errors.Errorf("failed to update container image in %s/%s,chart not found", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + + defaultS3, err := s3.FindDefaultS3() + if err != nil { + return err + } + + defaultURL, err := defaultS3.GetEncryptedURL() + if err != nil { + return err + } + + // use revision of service currently applied in environment instead of the latest revision + path, errDownload := s.downloadService(s.workflowCtx.ProjectName, s.helmDeploySpec.ServiceName, defaultURL, serviceRevisionInProduct) + if errDownload != nil { + s.log.Warnf("failed to get chart of revision: %d for service: %s, use latest version", + serviceRevisionInProduct, s.helmDeploySpec.ServiceName) + path, errDownload = s.downloadService(s.workflowCtx.ProjectName, s.helmDeploySpec.ServiceName, + defaultURL, 0) + if errDownload != nil { + err = errors.WithMessagef( + errDownload, + "failed to download service %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + } + + chartPath, err = fsutil.RelativeToCurrentPath(path) + if err != nil { + err = errors.WithMessagef( + err, + "failed to get relative path %s", + servicePath, + ) + return err + } + + serviceValuesYaml := renderChart.ValuesYaml + replaceValuesMap = make(map[string]interface{}) + + for _, svcAndContainer := range s.helmDeploySpec.ImageAndModules { + containerName := strings.TrimSuffix(svcAndContainer.ServiceModule, "_"+s.helmDeploySpec.ServiceName) + if imagePath, ok := involvedImagePaths[containerName]; ok { + validMatchData := getValidMatchData(imagePath) + singleReplaceValuesMap, errAssign := assignImageData(svcAndContainer.Image, validMatchData) + if errAssign != nil { + err = errors.WithMessagef( + errAssign, + "failed to pase image uri %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + for k, v := range singleReplaceValuesMap { + replaceValuesMap[k] = v + } + } + } + + // replace image into service's values.yaml + replacedValuesYaml, err = replaceImage(serviceValuesYaml, replaceValuesMap) + if err != nil { + err = errors.WithMessagef( + err, + "failed to replace image uri %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + if replacedValuesYaml == "" { + err = errors.Errorf("failed to set new image uri into service's values.yaml %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + + // merge override values and kvs into service's yaml + mergedValuesYaml, err = helmtool.MergeOverrideValues(serviceValuesYaml, renderInfo.DefaultValues, renderChart.GetOverrideYaml(), renderChart.OverrideValues) + if err != nil { + err = errors.WithMessagef( + err, + "failed to merge override values %s", + renderChart.OverrideValues, + ) + return err + } + + // replace image into final merged values.yaml + replacedMergedValuesYaml, err = replaceImage(mergedValuesYaml, replaceValuesMap) + if err != nil { + err = errors.WithMessagef( + err, + "failed to replace image uri into helm values %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return nil + } + if replacedMergedValuesYaml == "" { + err = errors.Errorf("failed to set image uri into mreged values.yaml in %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + + s.log.Infof("final replaced merged values: \n%s", replacedMergedValuesYaml) + + helmClient, err = helmtool.NewClientFromNamespace(s.helmDeploySpec.ClusterID, s.namespace) + if err != nil { + err = errors.WithMessagef( + err, + "failed to create helm client %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + return err + } + + releaseName := s.helmDeploySpec.ReleaseName + + ensureUpgrade := func() error { + hrs, errHistory := helmClient.ListReleaseHistory(releaseName, 10) + if errHistory != nil { + // list history should not block deploy operation, error will be logged instead of returned + s.log.Errorf("failed to list release history, release: %s, err: %s", releaseName, errHistory) + return nil + } + if len(hrs) == 0 { + return nil + } + releaseutil.Reverse(hrs, releaseutil.SortByRevision) + rel := hrs[0] + + if rel.Info.Status.IsPending() { + return fmt.Errorf("failed to upgrade release: %s with exceptional status: %s", releaseName, rel.Info.Status) + } + return nil + } + + err = ensureUpgrade() + if err != nil { + return err + } + + timeOut := s.timeout() + chartSpec := helmclient.ChartSpec{ + ReleaseName: releaseName, + ChartName: chartPath, + Namespace: s.namespace, + ReuseValues: true, + Version: renderChart.ChartVersion, + ValuesYaml: replacedMergedValuesYaml, + SkipCRDs: false, + UpgradeCRDs: true, + Timeout: time.Second * time.Duration(timeOut), + Wait: true, + Replace: true, + MaxHistory: 10, + } + + done := make(chan bool) + go func(chan bool) { + if _, err = helmClient.InstallOrUpgradeChart(ctx, &chartSpec); err != nil { + err = errors.WithMessagef( + err, + "failed to upgrade helm chart %s/%s", + s.namespace, s.helmDeploySpec.ServiceName) + done <- false + } else { + done <- true + } + }(done) + + select { + case <-done: + break + case <-time.After(chartSpec.Timeout + time.Minute): + err = fmt.Errorf("failed to upgrade relase: %s, timeout", chartSpec.ReleaseName) + } + if err != nil { + return nil + } + + //替换环境变量中的chartInfos + for _, chartInfo := range renderInfo.ChartInfos { + if chartInfo.ServiceName == s.helmDeploySpec.ServiceName { + chartInfo.ValuesYaml = replacedValuesYaml + break + } + } + + // TODO too dangerous to override entire renderset! + if err := commonrepo.NewRenderSetColl().Update(&commonmodels.RenderSet{ + Name: renderInfo.Name, + Revision: renderInfo.Revision, + DefaultValues: renderInfo.DefaultValues, + ChartInfos: renderInfo.ChartInfos, + }); err != nil { + err = errors.WithMessagef( + err, + "failed to update renderset info %s/%s, renderset %s", + s.namespace, s.helmDeploySpec.ServiceName, renderInfo.Name) + return err + } + return nil +} + +func (s *helmDeployCtl) timeout() int { + if s.helmDeploySpec.Timeout == 0 { + s.helmDeploySpec.Timeout = setting.DeployTimeout + } + return s.helmDeploySpec.Timeout +} + +// download chart info of specific version, use the latest version if fails +func (s *helmDeployCtl) downloadService(productName, serviceName, storageURI string, revision int64) (string, error) { + logger := s.log + + fileName := serviceName + if revision > 0 { + fileName = fmt.Sprintf("%s-%d", serviceName, revision) + } + tarball := fmt.Sprintf("%s.tar.gz", fileName) + localBase := config.LocalServicePath(productName, serviceName) + tarFilePath := filepath.Join(localBase, tarball) + + exists, err := fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if exists { + return tarFilePath, nil + } + + s3Storage, err := s3.NewS3StorageFromEncryptedURI(storageURI) + if err != nil { + return "", err + } + + s3Storage.Subfolder = filepath.Join(s3Storage.Subfolder, config.ObjectStorageServicePath(productName, serviceName)) + forcedPathStyle := true + if s3Storage.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3Client, err := s3tool.NewClient(s3Storage.Endpoint, s3Storage.Ak, s3Storage.Sk, s3Storage.Insecure, forcedPathStyle) + if err != nil { + s.log.Errorf("failed to create s3 client, err: %s", err) + return "", err + } + if err = s3Client.Download(s3Storage.Bucket, s3Storage.GetObjectPath(tarball), tarFilePath); err != nil { + logger.Errorf("failed to download file from s3, err: %s", err) + return "", err + } + + exists, err = fsutil.FileExists(tarFilePath) + if err != nil { + return "", err + } + if !exists { + return "", fmt.Errorf("file %s on s3 not found", s3Storage.GetObjectPath(tarball)) + } + return tarFilePath, nil +} + +func getValidMatchData(spec *commonmodels.ImagePathSpec) map[string]string { + ret := make(map[string]string) + if spec.Repo != "" { + ret[setting.PathSearchComponentRepo] = spec.Repo + } + if spec.Image != "" { + ret[setting.PathSearchComponentImage] = spec.Image + } + if spec.Tag != "" { + ret[setting.PathSearchComponentTag] = spec.Tag + } + return ret +} + +// assignImageData assign image url data into match data +// matchData: image=>absolute-path repo=>absolute-path tag=>absolute-path +// return: absolute-image-path=>image-value absolute-repo-path=>repo-value absolute-tag-path=>tag-value +func assignImageData(imageUrl string, matchData map[string]string) (map[string]interface{}, error) { + ret := make(map[string]interface{}) + // total image url assigned into one single value + if len(matchData) == 1 { + for _, v := range matchData { + ret[v] = imageUrl + } + return ret, nil + } + + resolvedImageUrl := resolveImageUrl(imageUrl) + + // image url assigned into repo/image+tag + if len(matchData) == 3 { + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = resolvedImageUrl[setting.PathSearchComponentImage] + ret[matchData[setting.PathSearchComponentTag]] = resolvedImageUrl[setting.PathSearchComponentTag] + return ret, nil + } + + if len(matchData) == 2 { + // image url assigned into repo/image + tag + if tagPath, ok := matchData[setting.PathSearchComponentTag]; ok { + ret[tagPath] = resolvedImageUrl[setting.PathSearchComponentTag] + for k, imagePath := range matchData { + if k == setting.PathSearchComponentTag { + continue + } + ret[imagePath] = fmt.Sprintf("%s%s", resolvedImageUrl[setting.PathSearchComponentRepo], resolvedImageUrl[setting.PathSearchComponentImage]) + break + } + return ret, nil + } + // image url assigned into repo + image(tag) + ret[matchData[setting.PathSearchComponentRepo]] = strings.TrimSuffix(resolvedImageUrl[setting.PathSearchComponentRepo], "/") + ret[matchData[setting.PathSearchComponentImage]] = fmt.Sprintf("%s:%s", resolvedImageUrl[setting.PathSearchComponentImage], resolvedImageUrl[setting.PathSearchComponentTag]) + return ret, nil + } + + return nil, errors.Errorf("match data illegal, expect length: 1-3, actual length: %d", len(matchData)) +} + +// parse image url to map: repo=>xxx/xx/xx image=>xx tag=>xxx +func resolveImageUrl(imageUrl string) map[string]string { + subMatchAll := imageParseRegex.FindStringSubmatch(imageUrl) + result := make(map[string]string) + exNames := imageParseRegex.SubexpNames() + for i, matchedStr := range subMatchAll { + if i != 0 && matchedStr != "" && matchedStr != ":" { + result[exNames[i]] = matchedStr + } + } + return result +} + +// replace image defines in yaml by new version +func replaceImage(sourceYaml string, imageValuesMap map[string]interface{}) (string, error) { + nestedMap, err := converter.Expand(imageValuesMap) + if err != nil { + return "", err + } + bs, err := yaml.Marshal(nestedMap) + if err != nil { + return "", err + } + mergedBs, err := yamlutil.Merge([][]byte{[]byte(sourceYaml), bs}) + if err != nil { + return "", err + } + return string(mergedBs), nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go index 3ba678df..2a3a8424 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/types/step" "go.uber.org/zap" @@ -28,10 +29,14 @@ func NewShellCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*shel return &shellCtl{shellSpec: shellSpec, log: log, step: stepTask}, nil } -func (s *shellCtl) PreRun(ctx context.Context) { - +func (s *shellCtl) PreRun(ctx context.Context) error { + return nil } -func (s *shellCtl) AfterRun(ctx context.Context) { +func (s *shellCtl) Run(ctx context.Context) (config.Status, error) { + return config.StatusPassed, nil +} +func (s *shellCtl) AfterRun(ctx context.Context) error { + return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go index 81e4d43c..bb796b32 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go @@ -28,12 +28,12 @@ func NewToolInstallCtl(stepTask *commonmodels.StepTask, jobPath *string, log *za } toolInstallSpec := &step.StepToolInstallSpec{} if err := yaml.Unmarshal(yamlString, &toolInstallSpec); err != nil { - return nil, fmt.Errorf("unmarshal git spec error: %v", err) + return nil, fmt.Errorf("unmarshal tool spec error: %v", err) } return &toolInstallCtl{toolInstalldSpec: toolInstallSpec, jobPath: jobPath, log: log, step: stepTask}, nil } -func (s *toolInstallCtl) PreRun(ctx context.Context) { +func (s *toolInstallCtl) PreRun(ctx context.Context) error { install, err := buildInstallCtx(s.toolInstalldSpec.Name, s.toolInstalldSpec.Version) if err != nil { s.log.Error(err) @@ -73,11 +73,15 @@ func (s *toolInstallCtl) PreRun(ctx context.Context) { } else { *s.jobPath = install.BinPath } - + return nil } -func (s *toolInstallCtl) AfterRun(ctx context.Context) { +func (s *toolInstallCtl) Run(ctx context.Context) (config.Status, error) { + return config.StatusPassed, nil +} +func (s *toolInstallCtl) AfterRun(ctx context.Context) error { + return nil } //根据用户的配置和BuildStep中步骤的依赖,从系统配置的InstallItems中获取配置项,构建Install Context diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index f4fd7617..7920fbd8 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -78,6 +78,7 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { workflowCtx := &commonmodels.WorkflowTaskCtx{ WorkflowName: c.workflowTask.WorkflowName, + ProjectName: c.workflowTask.ProjectName, TaskID: c.workflowTask.TaskID, DistDir: fmt.Sprintf("%s/%s/dist/%d", config.S3StoragePath(), c.workflowTask.WorkflowName, c.workflowTask.TaskID), DockerMountDir: fmt.Sprintf("/tmp/%s/docker/%d", uuid.NewV4(), time.Now().Unix()), diff --git a/pkg/types/step/step_deploy.go b/pkg/types/step/step_deploy.go new file mode 100644 index 00000000..214c26de --- /dev/null +++ b/pkg/types/step/step_deploy.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +type StepDeploySpec struct { + Env string `yaml:"env"` + ServiceName string `yaml:"service_name"` + ServiceType string `yaml:"service_type"` + ServiceModule string `yaml:"service_module"` + Image string `yaml:"image"` + ClusterID string `yaml:"cluster_id"` + Timeout int `yaml:"timeout"` + ReplaceResources []Resource `yaml:"replace_resources"` +} + +type Resource struct { + Name string `yaml:"name"` + Kind string `yaml:"kind"` + Container string `yaml:"container"` + Origin string `yaml:"origin"` +} diff --git a/pkg/types/step/step_helm_deploy.go b/pkg/types/step/step_helm_deploy.go new file mode 100644 index 00000000..97ab7c58 --- /dev/null +++ b/pkg/types/step/step_helm_deploy.go @@ -0,0 +1,33 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package step + +type StepHelmDeploySpec struct { + Env string `yaml:"env"` + ServiceName string `yaml:"service_name"` + ServiceType string `yaml:"service_type"` + ImageAndModules []*ImageAndServiceModule `yaml:"image_and_service_modules"` + ClusterID string `yaml:"cluster_id"` + ReleaseName string `yaml:"release_name"` + Timeout int `yaml:"timeout"` + ReplaceResources []Resource `yaml:"replace_resources"` +} + +type ImageAndServiceModule struct { + ServiceModule string `yaml:"service_module"` + Image string `yaml:"image"` +} -- Gitee From faf248e1dc5f873443d82b5cde5288008f2443f5 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Mon, 20 Jun 2022 20:28:00 +0800 Subject: [PATCH 2470/2647] add helm chart for voting-app (#1690) Signed-off-by: fansi Co-authored-by: fansi --- examples/voting-app/chart/Chart.yaml | 23 ++++++++ examples/voting-app/chart/templates/db.yaml | 53 +++++++++++++++++++ .../voting-app/chart/templates/redis.yaml | 50 +++++++++++++++++ .../voting-app/chart/templates/result.yaml | 46 ++++++++++++++++ examples/voting-app/chart/templates/vote.yaml | 48 +++++++++++++++++ .../voting-app/chart/templates/worker.yaml | 26 +++++++++ examples/voting-app/chart/values.yaml | 36 +++++++++++++ 7 files changed, 282 insertions(+) create mode 100644 examples/voting-app/chart/Chart.yaml create mode 100644 examples/voting-app/chart/templates/db.yaml create mode 100644 examples/voting-app/chart/templates/redis.yaml create mode 100644 examples/voting-app/chart/templates/result.yaml create mode 100644 examples/voting-app/chart/templates/vote.yaml create mode 100644 examples/voting-app/chart/templates/worker.yaml create mode 100644 examples/voting-app/chart/values.yaml diff --git a/examples/voting-app/chart/Chart.yaml b/examples/voting-app/chart/Chart.yaml new file mode 100644 index 00000000..fcc9eb19 --- /dev/null +++ b/examples/voting-app/chart/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: voting-app +description: multi microservice project + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.7 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: 1.16.0 diff --git a/examples/voting-app/chart/templates/db.yaml b/examples/voting-app/chart/templates/db.yaml new file mode 100644 index 00000000..58baacd0 --- /dev/null +++ b/examples/voting-app/chart/templates/db.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: db + name: db +spec: + replicas: {{ .Values.services.db.replicas }} + selector: + matchLabels: + app: db + template: + metadata: + labels: + app: db + spec: + containers: + - image: {{ .Values.services.db.image }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + name: postgres + ports: + - containerPort: {{ .Values.services.db.port }} + name: postgres + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: db-data + env: + - name: POSTGRES_HOST_AUTH_METHOD + value: trust + resources: + limits: + memory: {{ .Values.resources.limits.memory }} + cpu: {{ .Values.resources.limits.cpu }} + volumes: + - name: db-data + emptyDir: {} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: db + name: db +spec: + type: ClusterIP + ports: + - name: "db-service" + port: {{ .Values.services.db.port }} + targetPort: {{ .Values.services.db.port }} + selector: + app: db diff --git a/examples/voting-app/chart/templates/redis.yaml b/examples/voting-app/chart/templates/redis.yaml new file mode 100644 index 00000000..667723e6 --- /dev/null +++ b/examples/voting-app/chart/templates/redis.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: redis + name: redis +spec: + replicas: {{ .Values.services.redis.replicas }} + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + containers: + - image: {{ .Values.services.redis.image }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + name: redis + ports: + - containerPort: {{ .Values.services.redis.port }} + name: redis + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: {{ .Values.resources.limits.memory }} + cpu: {{ .Values.resources.limits.cpu }} + volumes: + - name: redis-data + emptyDir: {} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: redis + name: redis +spec: + type: ClusterIP + ports: + - name: "redis-service" + port: {{ .Values.services.redis.port }} + targetPort: {{ .Values.services.redis.port }} + selector: + app: redis diff --git a/examples/voting-app/chart/templates/result.yaml b/examples/voting-app/chart/templates/result.yaml new file mode 100644 index 00000000..c069cfde --- /dev/null +++ b/examples/voting-app/chart/templates/result.yaml @@ -0,0 +1,46 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: result + name: result-rc-origin +spec: + replicas: {{ .Values.services.vote.replicas }} + selector: + matchLabels: + app: result + template: + metadata: + labels: + app: result + spec: + containers: + - image: {{ .Values.services.result.image }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + name: result-e2e + ports: + - containerPort: 80 + name: result + resources: + limits: + memory: {{ .Values.resources.limits.memory }} + cpu: {{ .Values.resources.limits.cpu }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: result + version: rc-origin + name: result +spec: + type: NodePort + ports: + - name: "result-service" + port: {{ .Values.services.result.port }} + targetPort: 80 + selector: + app: result + diff --git a/examples/voting-app/chart/templates/vote.yaml b/examples/voting-app/chart/templates/vote.yaml new file mode 100644 index 00000000..f1145555 --- /dev/null +++ b/examples/voting-app/chart/templates/vote.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vote + name: vote-rc-origin +spec: + replicas: {{ .Values.services.vote.replicas }} + selector: + matchLabels: + app: vote + version: rc-origin + template: + metadata: + labels: + app: vote + version: rc-origin + spec: + containers: + - image: {{ .Values.services.vote.image }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + name: vote-e2e + ports: + - containerPort: 80 + name: vote + resources: + limits: + memory: {{ .Values.resources.limits.memory }} + cpu: {{ .Values.resources.limits.cpu }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: vote + name: vote +spec: + type: NodePort + ports: + - name: "vote-service" + port: {{ .Values.services.vote.port }} + targetPort: 80 + selector: + app: vote + version: rc-origin + diff --git a/examples/voting-app/chart/templates/worker.yaml b/examples/voting-app/chart/templates/worker.yaml new file mode 100644 index 00000000..83c89e4b --- /dev/null +++ b/examples/voting-app/chart/templates/worker.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: worker + name: worker +spec: + replicas: {{ .Values.services.worker.replicas }} + selector: + matchLabels: + app: worker + template: + metadata: + labels: + app: worker + spec: + containers: + - image: {{ .Values.services.worker.image }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + name: worker-e2e + resources: + limits: + memory: {{ .Values.resources.limits.memory }} + cpu: {{ .Values.resources.limits.cpu }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} diff --git a/examples/voting-app/chart/values.yaml b/examples/voting-app/chart/values.yaml new file mode 100644 index 00000000..50b49ecc --- /dev/null +++ b/examples/voting-app/chart/values.yaml @@ -0,0 +1,36 @@ +# Default values for chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +imagePullPolicy: Always +imagePullSecretsName: default-registry-secret + +services: + vote: + port: 5000 + image: dockersamples/examplevotingapp_vote:before + replicas: 1 + + result: + port: 5001 + image: dockersamples/examplevotingapp_result:before + replicas: 1 + + worker: + image: dockersamples/examplevotingapp_worker + replicas: 1 + + redis: + port: 6379 + image: redis:alpine + replicas: 1 + + db: + port: 5432 + image: postgres:9.4 + replicas: 1 + +resources: + limits: + cpu: 50m + memory: 50Mi \ No newline at end of file -- Gitee From b2b813088a4f8e4b0c8722615d1d68f19243a5d7 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Tue, 21 Jun 2022 10:14:24 +0800 Subject: [PATCH 2471/2647] fix panic when workflow args were nil (#1691) * fix panic when workflow args were nil Signed-off-by: dianqihanwangzi * avoid panic Signed-off-by: dianqihanwangzi --- .../core/workflow/service/workflow/pipeline_controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go index 73fb95b8..d6536ddc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_controller.go @@ -415,7 +415,7 @@ func ParallelRunningAndQueuedTasks(currentTask *task.Task) bool { return false } //如果是产品工作流判断是否设置了并行 - if currentTask.Type == config.WorkflowType && !currentTask.WorkflowArgs.IsParallel { + if currentTask.Type == config.WorkflowType && currentTask.WorkflowArgs != nil && !currentTask.WorkflowArgs.IsParallel { return false } //优先处理webhook @@ -474,11 +474,11 @@ func checkWebhookTaskNamespace(currentTask *task.Task) bool { continue } - if !runningNamespaces.Has(t.WorkflowArgs.Namespace) { + if t.WorkflowArgs != nil && !runningNamespaces.Has(t.WorkflowArgs.Namespace) { runningNamespaces.Insert(t.WorkflowArgs.Namespace) } } - if !runningNamespaces.Has(currentTask.WorkflowArgs.Namespace) && currentTask.WorkflowArgs.IsParallel { + if currentTask.WorkflowArgs != nil && !runningNamespaces.Has(currentTask.WorkflowArgs.Namespace) && currentTask.WorkflowArgs.IsParallel { return true } -- Gitee From e64fe1b55f1ccddb086253fe11badeb922745289 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 21 Jun 2022 10:22:18 +0800 Subject: [PATCH 2472/2647] fix bug occcurs when image name with under line (#1692) * fix bug occcurs when image name with under line Signed-off-by: allenshen * fix bug when image name contains underline Signed-off-by: allenshen --- .../core/common/repository/mongodb/task.go | 10 +++++-- .../service/workflow/pipeline_task.go | 26 ++++++++++++++++--- .../service/workflow/workflow_task.go | 5 +--- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index fca649a0..9d4ba725 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -59,6 +59,11 @@ type FindTaskOption struct { Type config.PipelineType } +type ServiceModule struct { + ServiceModule string `json:"service_module"` + ServiceName string `json:"service_name"` +} + type TaskPreview struct { TaskID int64 `bson:"task_id" json:"task_id"` TaskCreator string `bson:"task_creator" json:"task_creator"` @@ -77,8 +82,9 @@ type TaskPreview struct { Type config.PipelineType `bson:"type" json:"type"` Stages []*models.Stage `bson:"stages" json:"stages,omitempty"` // 服务名称,用于任务列表的展示 - BuildServices []string `bson:"-" json:"build_services"` - TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + BuildServices []string `bson:"-" json:"build_services"` + ServiceModules []*ServiceModule `bson:"-" json:"service_modules"` + TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` } type ListAllTaskOption struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 152a29a5..eb15e4ef 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -345,24 +345,42 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer } existSvc := false if buildStage != nil { - for serviceName := range buildStage.SubTasks { + for serviceName, subTask := range buildStage.SubTasks { + buildInfo, err := base.ToBuildTask(subTask) + containerName := buildInfo.Service + if err != nil { + log.Errorf("get buildInfo failed ! err: %s", err) + return ret, e.ErrListTasks.AddDesc(fmt.Sprintf("failed to get build info for task: %d, err: %s", t.TaskID, err)) + } if queryType == "serviceName" { - containerName := strings.Split(serviceName, "_")[0] if _, ok := serviceNameFiltersMap[containerName]; ok { existSvc = true } } t.BuildServices = append(t.BuildServices, serviceName) + t.ServiceModules = append(t.ServiceModules, &commonrepo.ServiceModule{ + ServiceName: containerName, + ServiceModule: strings.TrimSuffix(serviceName, "_"+buildInfo.Service), + }) } } else if deployStage != nil { - for serviceName := range deployStage.SubTasks { + for serviceName, subTask := range deployStage.SubTasks { + deployInfo, err := base.ToDeployTask(subTask) + containerName := deployInfo.ServiceName + if err != nil { + log.Errorf("get deployInfo failed ! err: %s", err) + return ret, e.ErrListTasks.AddDesc(fmt.Sprintf("failed to get deploy info for task: %d, err: %s", t.TaskID, err)) + } if queryType == "serviceName" { - containerName := strings.Split(serviceName, "_")[0] if _, ok := serviceNameFiltersMap[containerName]; ok { existSvc = true } } t.BuildServices = append(t.BuildServices, serviceName) + t.ServiceModules = append(t.ServiceModules, &commonrepo.ServiceModule{ + ServiceName: containerName, + ServiceModule: strings.TrimSuffix(serviceName, "_"+deployInfo.ServiceName), + }) } } buildStage, deployStage = nil, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index b02e0125..65574943 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -1419,10 +1419,7 @@ func deployEnvToSubTasks(env commonmodels.DeployEnv, prodEnv *commonmodels.Produ return nil, err } deployTask.ServiceName = envList[0] - deployTask.ContainerName = envList[1] - if !strings.Contains(envList[1], "_") { - deployTask.ContainerName = envList[1] + "_" + envList[0] - } + deployTask.ContainerName = envList[1] + "_" + envList[0] switch env.Type { case setting.K8SDeployType: -- Gitee From e2716c00181be8b96422e7cf915a6979a67f8653 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Tue, 21 Jun 2022 11:26:02 +0800 Subject: [PATCH 2473/2647] added dind custom image support for external clusters (#1666) Signed-off-by: Min Min Co-authored-by: Min Min --- pkg/microservice/aslan/config/config.go | 4 ++++ pkg/microservice/aslan/core/common/service/kube/service.go | 7 +++++-- pkg/setting/consts.go | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/config/config.go b/pkg/microservice/aslan/config/config.go index 57917c6a..c2b14a77 100644 --- a/pkg/microservice/aslan/config/config.go +++ b/pkg/microservice/aslan/config/config.go @@ -249,3 +249,7 @@ func ServiceNameWithRevision(serviceName string, revision int64) string { func ServiceAccountNameForUser(userID string) string { return fmt.Sprintf("%s-sa", userID) } + +func DindImage() string { + return viper.GetString(setting.DindImage) +} diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index 1ef11f4a..f0bd158e 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -280,6 +280,7 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD DindReplicas: dindReplicas, DindLimitsCPU: dindLimitsCPU, DindLimitsMemory: dindLimitsMemory, + DindImage: config.DindImage(), }) } else { err = YamlTemplateForNamespace.Execute(buffer, TemplateSchema{ @@ -293,6 +294,7 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD DindReplicas: dindReplicas, DindLimitsCPU: dindLimitsCPU, DindLimitsMemory: dindLimitsMemory, + DindImage: config.DindImage(), }) } @@ -345,6 +347,7 @@ type TemplateSchema struct { DindReplicas int DindLimitsCPU string DindLimitsMemory string + DindImage string } const ( @@ -573,7 +576,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: dind - image: ccr.ccs.tencentyun.com/koderover-public/docker:20.10.14-dind + image: {{.DindImage}} args: - --mtu=1376 env: @@ -825,7 +828,7 @@ spec: topologyKey: kubernetes.io/hostname containers: - name: dind - image: ccr.ccs.tencentyun.com/koderover-public/docker:20.10.14-dind + image: {{.DindImage}} args: - --mtu=1376 env: diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 57c263d0..66fd61ae 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -104,6 +104,9 @@ const ( // jenkins JenkinsBuildImage = "JENKINS_BUILD_IMAGE" + // dind + DindImage = "DIND_IMAGE" + DebugMode = "debug" ReleaseMode = "release" TestMode = "test" -- Gitee From 9d3b116f7e2547a42d3c1c7cf1028c835eb4b80a Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 21 Jun 2022 14:31:05 +0800 Subject: [PATCH 2474/2647] fix bug (#1699) Signed-off-by: allenshen --- .../core/environment/service/environment.go | 1 + .../aslan/core/log/handler/sse.go | 19 ++++---- .../aslan/core/log/service/sse.go | 47 ++++++++++++------- .../aslan/core/project/service/product.go | 2 + 4 files changed, 42 insertions(+), 27 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index c7600973..9c3376e3 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -922,6 +922,7 @@ func prepareHelmProductCreation(templateProduct *templatemodels.Product, product } container := &commonmodels.Container{ Name: c.Name, + ImageName: util.GetImageNameFromContainerInfo(c.ImageName, c.Name), Image: image, ImagePath: c.ImagePath, } diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index c022d6ba..36cc909b 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -96,15 +96,16 @@ func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { subTask := c.Query("subTask") options := &logservice.GetContainerOptions{ - Namespace: config.Namespace(), - PipelineName: c.Param("pipelineName"), - SubTask: subTask, - TailLines: tails, - TaskID: taskID, - ServiceName: c.Param("serviceName"), - PipelineType: string(config.WorkflowType), - EnvName: c.Query("envName"), - ProductName: c.Query("projectName"), + Namespace: config.Namespace(), + PipelineName: c.Param("pipelineName"), + SubTask: subTask, + TailLines: tails, + TaskID: taskID, + ServiceName: c.Param("serviceName"), + ServiceModule: c.Query("serviceModule"), + PipelineType: string(config.WorkflowType), + EnvName: c.Query("envName"), + ProductName: c.Query("projectName"), } internalhandler.Stream(c, func(ctx1 context.Context, streamChan chan interface{}) { diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 76f05b98..98574759 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -44,17 +44,18 @@ const ( ) type GetContainerOptions struct { - Namespace string - PipelineName string - SubTask string - TailLines int64 - TaskID int64 - PipelineType string - ServiceName string - TestName string - EnvName string - ProductName string - ClusterID string + Namespace string + PipelineName string + SubTask string + TailLines int64 + TaskID int64 + PipelineType string + ServiceName string + ServiceModule string + TestName string + EnvName string + ProductName string + ClusterID string } func ContainerLogStream(ctx context.Context, streamChan chan interface{}, envName, productName, podName, containerName string, follow bool, tailLines int64, log *zap.SugaredLogger) { @@ -116,14 +117,14 @@ func containerLogStream(ctx context.Context, streamChan chan interface{}, namesp } } -func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, options *GetContainerOptions, log *zap.SugaredLogger) { - if options == nil { - return +func parseServiceName(fullServiceName, serviceModule string) (string, string) { + // when service module is passed, use the passed value + // otherwise we fall back to the old logic + if len(serviceModule) > 0 { + return strings.TrimPrefix(fullServiceName, serviceModule+"_"), serviceModule } - log.Debugf("Start to get task container log.") - - var serviceName, serviceModule string - serviceNames := strings.Split(options.ServiceName, "_") + var serviceName string + serviceNames := strings.Split(fullServiceName, "_") switch len(serviceNames) { case 1: serviceModule = serviceNames[0] @@ -132,6 +133,16 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op serviceModule = serviceNames[0] serviceName = serviceNames[1] } + return serviceName, serviceModule +} + +func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, options *GetContainerOptions, log *zap.SugaredLogger) { + if options == nil { + return + } + log.Debugf("Start to get task container log.") + + serviceName, serviceModule := parseServiceName(options.ServiceName, options.ServiceModule) // Cloud host scenario reads real-time logs from the environment, so pipelineName is empty. if options.EnvName != "" && options.ProductName != "" && options.PipelineName == "" { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 88967798..9ee4279b 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -50,6 +50,7 @@ import ( e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" ) type CustomParseDataArgs struct { @@ -543,6 +544,7 @@ func ForkProduct(username, uid, requestID string, args *template.ForkProject, lo Name: c.Name, Image: c.Image, ImagePath: c.ImagePath, + ImageName: util.GetImageNameFromContainerInfo(c.ImageName, c.Name), } serviceResp.Containers = append(serviceResp.Containers, container) } -- Gitee From 1683d723c0fb866c5b1e2d7ed036c417e36746aa Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 21 Jun 2022 15:54:56 +0800 Subject: [PATCH 2475/2647] fix bug of filter workload by service module (#1701) * fix bug Signed-off-by: allenshen * fix workflow query bug Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- .../workflow/service/workflow/pipeline_task.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index eb15e4ef..6e64545d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -347,39 +347,39 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer if buildStage != nil { for serviceName, subTask := range buildStage.SubTasks { buildInfo, err := base.ToBuildTask(subTask) - containerName := buildInfo.Service if err != nil { log.Errorf("get buildInfo failed ! err: %s", err) return ret, e.ErrListTasks.AddDesc(fmt.Sprintf("failed to get build info for task: %d, err: %s", t.TaskID, err)) } + serviceModule := strings.TrimSuffix(serviceName, "_"+buildInfo.Service) if queryType == "serviceName" { - if _, ok := serviceNameFiltersMap[containerName]; ok { + if _, ok := serviceNameFiltersMap[serviceModule]; ok { existSvc = true } } t.BuildServices = append(t.BuildServices, serviceName) t.ServiceModules = append(t.ServiceModules, &commonrepo.ServiceModule{ - ServiceName: containerName, - ServiceModule: strings.TrimSuffix(serviceName, "_"+buildInfo.Service), + ServiceName: buildInfo.Service, + ServiceModule: serviceModule, }) } } else if deployStage != nil { for serviceName, subTask := range deployStage.SubTasks { deployInfo, err := base.ToDeployTask(subTask) - containerName := deployInfo.ServiceName if err != nil { log.Errorf("get deployInfo failed ! err: %s", err) return ret, e.ErrListTasks.AddDesc(fmt.Sprintf("failed to get deploy info for task: %d, err: %s", t.TaskID, err)) } + serviceModule := strings.TrimSuffix(serviceName, "_"+deployInfo.ServiceName) if queryType == "serviceName" { - if _, ok := serviceNameFiltersMap[containerName]; ok { + if _, ok := serviceNameFiltersMap[serviceModule]; ok { existSvc = true } } t.BuildServices = append(t.BuildServices, serviceName) t.ServiceModules = append(t.ServiceModules, &commonrepo.ServiceModule{ - ServiceName: containerName, - ServiceModule: strings.TrimSuffix(serviceName, "_"+deployInfo.ServiceName), + ServiceName: deployInfo.ServiceName, + ServiceModule: serviceModule, }) } } -- Gitee From 430b130d688726e7e208026f00609daaab06c941 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 21 Jun 2022 20:39:17 +0800 Subject: [PATCH 2476/2647] add directory description for demo (#1705) * add chart for service3 Signed-off-by: fansi * add directory description for multi-service-demo Signed-off-by: fansi Co-authored-by: fansi --- examples/multi-service-demo/README.md | 16 +++++ .../full-charts/service3/Chart.yaml | 24 +++++++ .../service3/templates/_helpers.tpl | 63 +++++++++++++++++++ .../service3/templates/deployment.yaml | 37 +++++++++++ .../service3/templates/service.yaml | 16 +++++ .../full-charts/service3/values.yaml | 18 ++++++ 6 files changed, 174 insertions(+) create mode 100644 examples/multi-service-demo/README.md create mode 100644 examples/multi-service-demo/full-charts/service3/Chart.yaml create mode 100644 examples/multi-service-demo/full-charts/service3/templates/_helpers.tpl create mode 100644 examples/multi-service-demo/full-charts/service3/templates/deployment.yaml create mode 100644 examples/multi-service-demo/full-charts/service3/templates/service.yaml create mode 100644 examples/multi-service-demo/full-charts/service3/values.yaml diff --git a/examples/multi-service-demo/README.md b/examples/multi-service-demo/README.md new file mode 100644 index 00000000..90968f16 --- /dev/null +++ b/examples/multi-service-demo/README.md @@ -0,0 +1,16 @@ +`multi-service-demo` 目录结构说明: + +``` bash +├── Dockerfile # Dockerfile,也可以作为 Dockerfile 模板,构建镜像时需要通过 --build-arg service=<值> 指定 service 参数的值 +├── Makefile # 构建工程文件,构建时使用 make build-service* 即可 +├── base-chart # Helm Chart 模板,可用于使用模板批量创建服务 +├── full-charts # 多个 Helm Chart,service1、service2、service3 分别有完整独立的 Helm Chart 配置 +├── general-chart # Helm Chart 模板,不可用于使用模板批量创建服务 +├── k8s-yaml # K8s YAML 配置文件,用于 K8s YAML 项目 +│   ├── service1 # service1 完整的配置 +│   ├── service2 # service2 完整的配置 +│   ├── service3 # service3 完整的配置 +│   └── template.yaml # service1/service2/service3 的 K8s YAML 服务模板 +├── src # 服务源代码 +└── values # 使用 base-chart 模板批量创建服务时,多个服务的 values 文件 +``` diff --git a/examples/multi-service-demo/full-charts/service3/Chart.yaml b/examples/multi-service-demo/full-charts/service3/Chart.yaml new file mode 100644 index 00000000..4059fc12 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service3/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: service3 +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/examples/multi-service-demo/full-charts/service3/templates/_helpers.tpl b/examples/multi-service-demo/full-charts/service3/templates/_helpers.tpl new file mode 100644 index 00000000..2668feb5 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service3/templates/_helpers.tpl @@ -0,0 +1,63 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "chart.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "chart.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "chart.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "chart.labels" -}} +helm.sh/chart: {{ include "chart.chart" . }} +{{ include "chart.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "chart.selectorLabels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "chart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "chart.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/examples/multi-service-demo/full-charts/service3/templates/deployment.yaml b/examples/multi-service-demo/full-charts/service3/templates/deployment.yaml new file mode 100644 index 00000000..e441d6b8 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service3/templates/deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + replicas: {{ .Values.replicaCount }} + template: + metadata: + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + spec: + containers: + - name: {{ .Values.fullnameOverride }} + image: {{ .Values.image.repository }}:{{ .Values.image.tag }} + imagePullPolicy: Always + command: + - /workspace/{{ .Values.fullnameOverride }} + ports: + - protocol: TCP + containerPort: {{ .Values.port }} + resources: + limits: + cpu: {{ .Values.resources.limits.cpu }} + memory: {{ .Values.resources.limits.mem }} + requests: + cpu: {{ .Values.resources.requests.cpu }} + memory: {{ .Values.resources.requests.mem }} + imagePullSecrets: + - name: {{ .Values.imagePullSecretsName }} \ No newline at end of file diff --git a/examples/multi-service-demo/full-charts/service3/templates/service.yaml b/examples/multi-service-demo/full-charts/service3/templates/service.yaml new file mode 100644 index 00000000..d21ce064 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service3/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.fullnameOverride }} + labels: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} +spec: + selector: + app.kubernetes.io/name: demo + app.kubernetes.io/instance: {{ .Values.fullnameOverride }} + type: NodePort + ports: + - protocol: TCP + port: {{ .Values.port }} + targetPort: {{ .Values.port }} \ No newline at end of file diff --git a/examples/multi-service-demo/full-charts/service3/values.yaml b/examples/multi-service-demo/full-charts/service3/values.yaml new file mode 100644 index 00000000..57a6f941 --- /dev/null +++ b/examples/multi-service-demo/full-charts/service3/values.yaml @@ -0,0 +1,18 @@ +fullnameOverride: service1 +replicaCount: 1 +port: 20223 + +imagePullSecretsName: "default-registry-secret" + +image: + repository: "ccr.ccs.tencentyun.com/koderover-public/service3" + tag: "latest" + +resources: + requests: + cpu: 10m + mem: 10Mi + + limits: + cpu: 20m + mem: 20Mi -- Gitee From f54d381dd53deb1d1640c4071141b65419605251 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Tue, 21 Jun 2022 20:39:41 +0800 Subject: [PATCH 2477/2647] update deployment name for voting-app demo (#1693) Signed-off-by: fansi Co-authored-by: fansi --- examples/voting-app/chart/templates/result.yaml | 2 +- examples/voting-app/chart/templates/vote.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/voting-app/chart/templates/result.yaml b/examples/voting-app/chart/templates/result.yaml index c069cfde..e514cfcb 100644 --- a/examples/voting-app/chart/templates/result.yaml +++ b/examples/voting-app/chart/templates/result.yaml @@ -3,7 +3,7 @@ kind: Deployment metadata: labels: app: result - name: result-rc-origin + name: result spec: replicas: {{ .Values.services.vote.replicas }} selector: diff --git a/examples/voting-app/chart/templates/vote.yaml b/examples/voting-app/chart/templates/vote.yaml index f1145555..8bb6a492 100644 --- a/examples/voting-app/chart/templates/vote.yaml +++ b/examples/voting-app/chart/templates/vote.yaml @@ -3,7 +3,7 @@ kind: Deployment metadata: labels: app: vote - name: vote-rc-origin + name: vote spec: replicas: {{ .Values.services.vote.replicas }} selector: -- Gitee From 66fcc3d0ce7bb9ce36f4723654d2bff58dc89b7f Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 22 Jun 2022 15:36:30 +0800 Subject: [PATCH 2478/2647] fix ua execute error bug (#1708) Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate.go | 20 ++++++++++++------- .../internal/upgradepath/versions.go | 9 ++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/cli/upgradeassistant/cmd/migrate.go b/pkg/cli/upgradeassistant/cmd/migrate.go index c139fb76..5d698086 100644 --- a/pkg/cli/upgradeassistant/cmd/migrate.go +++ b/pkg/cli/upgradeassistant/cmd/migrate.go @@ -19,9 +19,9 @@ package cmd import ( "context" "fmt" - "sort" "time" + "github.com/blang/semver/v4" "github.com/spf13/cobra" "github.com/spf13/viper" "k8s.io/apimachinery/pkg/util/sets" @@ -70,22 +70,28 @@ func run() error { log.Infof("Migrating from %s to %s", from, to) versionSets := sets.NewString() - for _, rh := range upgradepath.RegisteredHandlers { versionSets.Insert(rh.FromVersion, rh.ToVersion) } - upgradepath.VersionDatas = versionSets.List() - sort.Strings(upgradepath.VersionDatas) if len(from) == 0 { from = oldestVersion } if len(to) == 0 { - to = upgradepath.VersionDatas[len(upgradepath.VersionDatas)-1] + return fmt.Errorf("target version not assigned") } versionSets.Insert(from, to) - upgradepath.VersionDatas = versionSets.List() - sort.Strings(upgradepath.VersionDatas) + + for _, verStr := range versionSets.List() { + semVersion, err := semver.Make(verStr) + if err != nil { + return fmt.Errorf("failed to parse version: %s, err: %s", verStr, err) + } + upgradepath.VersionDatas = append(upgradepath.VersionDatas, semVersion) + } + + // sort versions by semver orders + semver.Sort(upgradepath.VersionDatas) // add default handlers for i := 0; i < len(upgradepath.VersionDatas)-1; i++ { diff --git a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go index 5451d8e1..ec960cac 100644 --- a/pkg/cli/upgradeassistant/internal/upgradepath/versions.go +++ b/pkg/cli/upgradeassistant/internal/upgradepath/versions.go @@ -23,16 +23,11 @@ import ( var VersionDatas versionList -type versionList []string +type versionList semver.Versions func (v versionList) VersionIndex(version string) int { - t, err := semver.Make(version) - if err != nil { - return 0 - } - for index, _version := range VersionDatas { - if _version == t.FinalizeVersion() { + if _version.String() == version { return index } } -- Gitee From 1453036785dcf7a3225c28071d2fa39efa164e47 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 22 Jun 2022 18:05:48 +0800 Subject: [PATCH 2479/2647] add new contributor @pokixing Signed-off-by: panxunying --- GOVERNANCE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 351efb48..cac629d6 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -72,3 +72,4 @@ The council will make a decision for the nomination. The candidate has to have d - [lxm](https://github.com/lxm) - [zxdvd](https://github.com/zxdvd) - [rovast](https://github.com/rovast) +- [pokixing](https://github.com/pokixing) \ No newline at end of file -- Gitee From 3474f5d33f4280392e9227acb65de65232e6d02a Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 23 Jun 2022 14:20:59 +0800 Subject: [PATCH 2480/2647] optimize service status (#1711) * update service status Signed-off-by: panxunying * optimize Signed-off-by: panxunying Co-authored-by: panxunying --- .../aslan/core/common/service/environment.go | 27 ++++++++++--------- pkg/shared/kube/resource/pod.go | 4 +++ pkg/shared/kube/wrapper/pod.go | 17 +++++++++--- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 0cbbf85a..bd8e2011 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -79,19 +79,20 @@ func FillProductTemplateValuesYamls(tmpl *templatemodels.Product, log *zap.Sugar // 产品列表页服务Response type ServiceResp struct { - ServiceName string `json:"service_name"` - ServiceDisplayName string `json:"service_display_name"` - Type string `json:"type"` - Status string `json:"status"` - Images []string `json:"images,omitempty"` - ProductName string `json:"product_name"` - EnvName string `json:"env_name"` - Ingress *IngressInfo `json:"ingress"` - Ready string `json:"ready"` - EnvStatuses []*models.EnvStatus `json:"env_statuses,omitempty"` - WorkLoadType string `json:"workLoadType"` - Revision int64 `json:"revision"` - EnvConfigs []*models.EnvConfig `json:"env_configs"` + ServiceName string `json:"service_name"` + ServiceDisplayName string `json:"service_display_name"` + Type string `json:"type"` + Status string `json:"status"` + Images []string `json:"images,omitempty"` + ProductName string `json:"product_name"` + EnvName string `json:"env_name"` + Ingress *IngressInfo `json:"ingress"` + //deprecated + Ready string `json:"ready"` + EnvStatuses []*models.EnvStatus `json:"env_statuses,omitempty"` + WorkLoadType string `json:"workLoadType"` + Revision int64 `json:"revision"` + EnvConfigs []*models.EnvConfig `json:"env_configs"` } type IngressInfo struct { diff --git a/pkg/shared/kube/resource/pod.go b/pkg/shared/kube/resource/pod.go index 78737c40..2c1c04f8 100644 --- a/pkg/shared/kube/resource/pod.go +++ b/pkg/shared/kube/resource/pod.go @@ -28,6 +28,9 @@ type Pod struct { NodeName string `json:"node_name"` HostIP string `json:"host_ip"` EnableDebugContainer bool `json:"enable_debug_container"` + PodReady bool `json:"pod_ready"` + ContainersReady bool `json:"containers_ready"` + ContainersMessage string `json:"containers_message"` } type Container struct { @@ -35,6 +38,7 @@ type Container struct { Image string `json:"image"` RestartCount int32 `json:"restart_count"` Status string `json:"status"` + Ready bool `json:"ready"` // Message regarding the last termination of the container Message string `json:"message"` // reason from the last termination of the container diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index c33caea1..9e51916d 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -89,14 +89,19 @@ func (w *pod) Ready() bool { // All conditions specified in readinessGates are True. // When a Pod's containers are Ready but at least one custom condition is missing or False, the kubelet sets the Pod's condition to ContainersReady. // https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-readiness-status -func (w *pod) ContainersReady() bool { +func (w *pod) ContainersReady() (bool, string) { cs := w.Status.Conditions + for _, c := range cs { if c.Type == corev1.ContainersReady { - return c.Status == corev1.ConditionTrue + if c.Status == corev1.ConditionTrue { + return true, "" + } else { + return false, c.Message + } } } - return false + return false, "" } func (w *pod) Containers() []string { @@ -118,6 +123,7 @@ func (w *pod) ContainerNames() []string { } func (w *pod) Resource() *resource.Pod { + containersReady, containersMessage := w.ContainersReady() p := &resource.Pod{ Name: w.Name, Status: string(w.Status.Phase), @@ -125,6 +131,9 @@ func (w *pod) Resource() *resource.Pod { CreateTime: w.CreationTimestamp.Unix(), IP: w.Status.PodIP, Labels: w.Labels, + PodReady: w.Ready(), + ContainersReady: containersReady, + ContainersMessage: containersMessage, ContainerStatuses: []resource.Container{}, NodeName: w.Spec.NodeName, HostIP: w.Status.HostIP, @@ -143,6 +152,7 @@ func (w *pod) Resource() *resource.Pod { cs := resource.Container{ Name: container.Name, RestartCount: container.RestartCount, + Ready: container.Ready, } if container.State.Running != nil { @@ -187,7 +197,6 @@ func (w *pod) Resource() *resource.Pod { } } } - p.ContainerStatuses = append(p.ContainerStatuses, cs) } -- Gitee From 6c9ebd6334d87970fcf341c8d742303d1b32fc4d Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 23 Jun 2022 18:15:35 +0800 Subject: [PATCH 2481/2647] fix collaboration mode helm scene null pointer (#1703) * rollback init config Signed-off-by: panxunying * fix collaboration mode helm scene nil pointer Signed-off-by: panxunying * add log Signed-off-by: panxunying Co-authored-by: panxunying --- .../service/collaboration_instance.go | 38 ++++++++++--------- .../aslan/core/label/handler/labels.go | 2 +- .../aslan/core/label/service/label.go | 7 ++-- .../aslan/core/project/service/product.go | 6 +-- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 8a018188..617d43c5 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -618,7 +618,7 @@ func syncLabel(updateResp *GetCollaborationUpdateResp, projectName, identityType for _, label := range resp.Labels { ids = append(ids, label.ID.Hex()) } - err = service.DeleteLabels(ids, true, logger) + err = service.DeleteLabels(ids, true, userName, logger) if err != nil { logger.Errorf("delete labels error, error msg:%s", err) return err @@ -1134,25 +1134,27 @@ func getCollaborationNew(updateResp *GetCollaborationUpdateResp, projectName, id } } } - renderSets, err := getRenderSet(projectName, newProductName.List()) - if err != nil { - logger.Errorf("getRenderSet error:%s", err) - return nil, err - } - if renderSets != nil { - productRenderSetMap := make(map[string]models2.RenderSet) - for _, set := range renderSets { - productRenderSetMap[set.EnvName] = set + if len(newProduct) > 0 && newProduct[0].DeployType == setting.K8SDeployType { + renderSets, err := getRenderSet(projectName, newProductName.List()) + if err != nil { + logger.Errorf("getRenderSet error:%s", err) + return nil, err } - for _, product := range newProduct { - set, ok := productRenderSetMap[product.BaseName] - if !ok { - logger.Warnf("product:%s renderSet not exist", product.BaseName) - continue + if renderSets != nil { + productRenderSetMap := make(map[string]models2.RenderSet) + for _, set := range renderSets { + productRenderSetMap[set.EnvName] = set } + for _, product := range newProduct { + set, ok := productRenderSetMap[product.BaseName] + if !ok { + logger.Warnf("product:%s renderSet not exist", product.BaseName) + continue + } - product.Vars = set.KVs - product.DefaultValues = set.DefaultValues + product.Vars = set.KVs + product.DefaultValues = set.DefaultValues + } } } if len(newProduct) > 0 && newProduct[0].DeployType == setting.HelmDeployType { @@ -1281,7 +1283,7 @@ func DeleteCIResources(userName, requestID string, cis []*models.CollaborationIn labelIds = append(labelIds, l.ID.Hex()) } - err = service.DeleteLabels(labelIds, true, logger) + err = service.DeleteLabels(labelIds, true, "system", logger) if err != nil { return err } diff --git a/pkg/microservice/aslan/core/label/handler/labels.go b/pkg/microservice/aslan/core/label/handler/labels.go index b182d706..b8274818 100644 --- a/pkg/microservice/aslan/core/label/handler/labels.go +++ b/pkg/microservice/aslan/core/label/handler/labels.go @@ -79,7 +79,7 @@ func DeleteLabels(c *gin.Context) { ctx.Err = err return } - ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.Logger) + ctx.Err = service.DeleteLabels(deleteLabelsArgs.IDs, forceBool, ctx.UserName, ctx.Logger) } func ListResourcesByLabels(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index 45d0a487..eff028ad 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -200,12 +200,13 @@ type DeleteLabelsArgs struct { IDs []string } -func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) error { +func DeleteLabels(ids []string, forceDelete bool, userName string, logger *zap.SugaredLogger) error { if len(ids) == 0 { return nil } if forceDelete { + logger.Infof("user:%s force deleteLabels:%s", userName, ids) if err := mongodb.NewLabelBindingColl().BulkDeleteByLabelIds(ids); err != nil { logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, ids) return err @@ -215,7 +216,7 @@ func DeleteLabels(ids []string, forceDelete bool, logger *zap.SugaredLogger) err return mongodb.NewLabelColl().BulkDelete(ids) } -func DeleteLabelsAndBindingsByProject(projectName string, logger *zap.SugaredLogger) error { +func DeleteLabelsAndBindingsByProject(projectName, userName string, logger *zap.SugaredLogger) error { labels, err := mongodb.NewLabelColl().ListByProjectName(projectName) if err != nil { @@ -237,7 +238,7 @@ func DeleteLabelsAndBindingsByProject(projectName string, logger *zap.SugaredLog for _, labelBinding := range labelBindings { labelBindingIDs = append(labelBindingIDs, labelBinding.ID.Hex()) } - + logger.Infof("user:%s DeleteLabelsAndBindingsByProject labelbindingIDs:%s", userName, labelBindingIDs) if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, labelBindingIDs) return err diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 9ee4279b..b3e4fba9 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -403,7 +403,7 @@ func DeleteProductTemplate(userName, productName, requestID string, isDelete boo return err } - if err = DeleteLabels(productName, log); err != nil { + if err = DeleteLabels(productName, userName, log); err != nil { log.Errorf("DeleteLabels productName %s err: %s", productName, err) return err } @@ -980,8 +980,8 @@ func DeletePolicy(productName string, log *zap.SugaredLogger) error { return nil } -func DeleteLabels(productName string, log *zap.SugaredLogger) error { - if err := service2.DeleteLabelsAndBindingsByProject(productName, log); err != nil { +func DeleteLabels(productName, userName string, log *zap.SugaredLogger) error { + if err := service2.DeleteLabelsAndBindingsByProject(productName, userName, log); err != nil { log.Errorf("delete labels and bindings by project fail , err :%s", err) return err } -- Gitee From 34abddf6d779cd10df704e35d155fa22794e2e36 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 24 Jun 2022 15:10:21 +0800 Subject: [PATCH 2482/2647] update helm service update logic (#1688) Signed-off-by: allenshen --- .../core/environment/service/environment.go | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 9c3376e3..9406519a 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -1262,7 +1262,7 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid log.Errorf("GetProduct envName:%s, productName:%s, err:%+v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(err.Error()) } - currentProductService := productResp.Services + // create product data from product template templateProd, err := GetInitProduct(productName, types.GeneralEnv, false, "", log) if err != nil { @@ -1279,17 +1279,16 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid serviceNeedUpdateOrCreate.Insert(chart.ServiceName) } + productServiceMap := productResp.GetServiceMap() + // get deleted services map[serviceName]=>serviceRevision - for _, svcGroup := range currentProductService { - for _, svc := range svcGroup { - if deletedSvcSet.Has(svc.ServiceName) { - deletedSvcRevision[svc.ServiceName] = svc.Revision - } + for _, svc := range productServiceMap { + if deletedSvcSet.Has(svc.ServiceName) { + deletedSvcRevision[svc.ServiceName] = svc.Revision } } // use service definition from service template, but keep the image info - productServiceMap := productResp.GetServiceMap() allServices := make([][]*commonmodels.ProductService, 0) for _, svrs := range templateProd.Services { svcGroup := make([]*commonmodels.ProductService, 0) @@ -1331,9 +1330,10 @@ func UpdateHelmProduct(productName, envName, username, requestID string, overrid log.Errorf("[%s][P:%s] Product.UpdateStatus error: %v", envName, productName, err) return e.ErrUpdateEnv.AddDesc(e.UpdateEnvStatusErrMsg) } + //对比当前环境中的环境变量和默认的环境变量 go func() { - err := updateProductGroup(username, productName, envName, productResp, currentProductService, overrideCharts, deletedSvcRevision, log) + err := updateProductGroup(username, productName, envName, productResp, overrideCharts, deletedSvcRevision, log) if err != nil { log.Errorf("[%s][P:%s] failed to update product %#v", envName, productName, err) // 发送更新产品失败消息给用户 @@ -1892,9 +1892,7 @@ func UpdateMultipleHelmEnv(requestID, userName string, args *UpdateMultiHelmProd if productRevision.ProductName != productName || !envNameSet.Has(productRevision.EnvName) { continue } - if !productRevision.Updatable { - continue - } + // NOTE. there is no need to check if product is updatable anymore productMap[productRevision.EnvName] = productRevision if len(productMap) == len(envNames) { break @@ -3433,12 +3431,8 @@ func batchExecutor(interval time.Duration, serviceList []*commonmodels.Service, return errList } -func updateProductGroup(username, productName, envName string, productResp *commonmodels.Product, currentProductServices [][]*commonmodels.ProductService, +func updateProductGroup(username, productName, envName string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, deletedSvcRevision map[string]int64, log *zap.SugaredLogger) error { - var ( - productServiceMap = make(map[string]*commonmodels.ProductService) - productTemplServiceMap = make(map[string]*commonmodels.ProductService) - ) helmClient, err := helmtool.NewClientFromNamespace(productResp.ClusterID, productResp.Namespace) if err != nil { @@ -3453,18 +3447,6 @@ func updateProductGroup(username, productName, envName string, productResp *comm } } - // current service applied in product - for _, serviceGroup := range currentProductServices { - for _, service := range serviceGroup { - productServiceMap[service.ServiceName] = service - } - } - - // services need to keep in product - for _, service := range productResp.GetServiceMap() { - productTemplServiceMap[service.ServiceName] = service - } - renderSet, err := diffRenderSet(username, productName, envName, productResp, overrideCharts, log) if err != nil { return e.ErrUpdateEnv.AddDesc("对比环境中的value.yaml和系统默认的value.yaml失败") -- Gitee From bacc35897da278ba945fe51f9799315d6d5218aa Mon Sep 17 00:00:00 2001 From: guoyu Date: Sun, 26 Jun 2022 11:28:37 +0800 Subject: [PATCH 2483/2647] add workflow related apis Signed-off-by: guoyu --- pkg/microservice/aslan/config/consts.go | 17 ++ .../core/common/repository/models/build.go | 10 +- .../repository/models/wokflow_task_v4.go | 7 +- .../common/repository/models/workflow_v4.go | 103 +++++-- .../repository/mongodb/workflow_task_v4.go | 177 ++++++++++++ .../common/repository/mongodb/workflow_v4.go | 163 +++++++++++ pkg/microservice/aslan/core/service.go | 2 + .../aslan/core/workflow/handler/router.go | 14 + .../core/workflow/handler/workflow_task_v4.go | 33 ++- .../core/workflow/handler/workflow_v4.go | 130 +++++++++ .../service/stepcontroller/step_archive.go | 14 +- .../service/stepcontroller/step_git.go | 8 +- .../core/workflow/service/workflow/job/job.go | 68 +++++ .../service/workflow/job/job_build.go | 265 ++++++++++++++++++ .../service/workflow/job/job_deploy.go | 159 +++++++++++ .../service/workflow/workflow_task_v4.go | 135 +++++++++ .../workflow/service/workflow/workflow_v4.go | 126 ++++++++- .../service/workflowcontroller/stage.go | 10 +- .../service/workflowcontroller/workflow.go | 4 + pkg/setting/consts.go | 1 + pkg/types/repo.go | 62 ++++ pkg/types/step/step_git.go | 87 +----- 22 files changed, 1461 insertions(+), 134 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go create mode 100644 pkg/microservice/aslan/core/workflow/handler/workflow_v4.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/job/job.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index c0dcf355..05e7e0f0 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -164,6 +164,23 @@ const ( StepHtmlReport StepType = "html_report" ) +type JobType string + +const ( + JobBuild JobType = "build" + JobDeploy JobType = "deploy" + JobZadigBuild JobType = "zadig-build" + JobZadigDeploy JobType = "zadig-deploy" + JobFreestyle JobType = "freestyle" +) + +type DeploySourceType string + +const ( + SourceRuntime DeploySourceType = "runtime" + SourceFromJob DeploySourceType = "fromjob" +) + type StageType string const ( diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index f068d803..e6296e0c 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -168,11 +168,11 @@ type TargetRepo struct { } type KeyVal struct { - Key string `bson:"key" json:"key"` - Value string `bson:"value" json:"value"` - Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty"` - IsCredential bool `bson:"is_credential" json:"is_credential"` + Key string `bson:"key" json:"key" yaml:"key"` + Value string `bson:"value" json:"value" yaml:"value"` + Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty" yaml:"type"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty" yaml:"choiceOption,omitempty"` + IsCredential bool `bson:"is_credential" json:"is_credential" yaml:"isCredential"` } type Item struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index d280be42..01e99739 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -51,10 +51,9 @@ type StageTask struct { Status config.Status `bson:"status" json:"status"` StartTime int64 `bson:"start_time" json:"start_time,omitempty"` EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - // default is custom defined by user. - StageType string `bson:"type" json:"type"` - Parallel bool `bson:"parallel" json:"parallel"` - Jobs []*JobTask `bson:"jobs" json:"jobs"` + Parallel bool `bson:"parallel" json:"parallel"` + Approval *Approval `bson:"approval" json:"approval"` + Jobs []*JobTask `bson:"jobs" json:"jobs"` } type JobTask struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index c60fbc05..85f3701b 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -17,38 +17,89 @@ limitations under the License. package models import ( + "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types" "go.mongodb.org/mongo-driver/bson/primitive" + "gopkg.in/yaml.v3" ) type WorkflowV4 struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Name string `bson:"name" json:"name"` - Args []*KeyVal `bson:"args" json:"args"` - Stages []*WorkflowStage `bson:"stages" json:"stages"` - ProjectName string `bson:"project_name" json:"project_name"` - Description string `bson:"description" json:"description"` - CreatedBy string `bson:"created_by" json:"created_by"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdatedBy string `bson:"updated_by" json:"updated_by"` - UpdateTime int64 `bson:"update_time" json:"update_time"` + ID primitive.ObjectID `bson:"_id,omitempty" yaml:"-" json:"id"` + Name string `bson:"name" yaml:"name" json:"name"` + KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` + Stages []*WorkflowStage `bson:"stages" yaml:"stages" json:"stages"` + Project string `bson:"project" yaml:"project" json:"project"` + Description string `bson:"description" yaml:"description" json:"description"` + CreatedBy string `bson:"created_by" yaml:"created_by" json:"created_by"` + CreateTime int64 `bson:"create_time" yaml:"create_time" json:"create_time"` + UpdatedBy string `bson:"updated_by" yaml:"updated_by" json:"updated_by"` + UpdateTime int64 `bson:"update_time" yaml:"update_time" json:"update_time"` } type WorkflowStage struct { - Name string `bson:"name" json:"name"` - // default is custom defined by user. - StageType string `bson:"type" json:"type"` - Jobs []*Job `bson:"jobs" json:"jobs"` + Name string `bson:"name" yaml:"name" json:"name"` + Parallel bool `bson:"parallel" yaml:"parallel" json:"parallel"` + Approval *Approval `bson:"approval" yaml:"approval" json:"approval"` + Jobs []*Job `bson:"jobs" yaml:"jobs" json:"jobs"` +} + +type Approval struct { + Enabled bool `bson:"enabled" yaml:"enabled" json:"enabled"` + ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` + Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"` + NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` + Description string `bson:"description" yaml:"description" json:"description"` +} + +type User struct { + UserID string `bson:"user_id" yaml:"user_id" json:"user_id"` + UserName string `bson:"user_name" yaml:"user_name" json:"user_name"` } type Job struct { - Name string `bson:"name" json:"name"` - Properties JobProperties `bson:"properties" json:"properties"` - JobType string `bson:"type" json:"type"` - Spec interface{} `bson:"spec" json:"spec"` - Steps []*Step `bson:"steps" json:"steps"` - Outputs []*Output `bson:"outputs" json:"outputs"` + Name string `bson:"name" yaml:"name" json:"name"` + JobType config.JobType `bson:"type" yaml:"type" json:"type"` + Spec interface{} `bson:"spec" yaml:"spec" json:"spec"` +} + +type FreestyleJobSpec struct { + Properties *JobProperties `bson:"properties" yaml:"properties" json:"properties"` + Steps []*Step `bson:"steps" yaml:"steps" json:"steps"` + Outputs []*Output `bson:"outputs" yaml:"outputs" json:"outputs"` +} + +type ZadigBuildJobSpec struct { + DockerRegistryID string `bson:"docker_registry_id" yaml:"docker_registry_id" json:"docker_registry_id"` + ServiceAndBuilds []*ServiceAndBuild `bson:"service_and_builds" yaml:"service_and_builds" json:"service_and_builds"` +} + +type ServiceAndBuild struct { + ServiceName string `bson:"service_name" yaml:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" yaml:"service_module" json:"service_module"` + BuildName string `bson:"build_name" yaml:"build_name" json:"build_name"` + Image string `bson:"-" yaml:"-" json:"image"` + Package string `bson:"-" yaml:"-" json:"package"` + KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` + Repos []*types.Repository `bson:"-" yaml:"-" json:"repos"` +} + +type ZadigDeployJobSpec struct { + Env string `bson:"env" yaml:"env" json:"env"` + DeployType string `bson:"-" yaml:"-" json:"deploy_type"` + // fromjob/runtime, runtime 表示运行时输入,fromjob 表示从上游构建任务中获取 + Source config.DeploySourceType `bson:"source" yaml:"source" json:"source"` + // 当 source 为 fromjob 时需要,指定部署镜像来源是上游哪一个构建任务 + JobName string `bson:"job_name" yaml:"job_name" json:"job_name"` + ServiceAndImages []*ServiceAndImage `bson:"-" yaml:"-" json:"service_and_images"` +} + +type ServiceAndImage struct { + ServiceName string `bson:"service_name" yaml:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" yaml:"service_module" json:"service_module"` + Image string `bson:"image" yaml:"image" json:"image"` } type JobProperties struct { @@ -75,6 +126,18 @@ type Output struct { Description string `bson:"description" json:"description"` } +func IToiYaml(before interface{}, after interface{}) error { + b, err := yaml.Marshal(before) + if err != nil { + return fmt.Errorf("marshal task error: %v", err) + } + + if err := yaml.Unmarshal(b, after); err != nil { + return fmt.Errorf("unmarshal task error: %v", err) + } + return nil +} + func (WorkflowV4) TableName() string { return "workflow_v4" } diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go new file mode 100644 index 00000000..2c46d59d --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go @@ -0,0 +1,177 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type ListWorkflowTaskV4Option struct { + WorkflowName string +} + +type WorkflowTaskv4Coll struct { + *mongo.Collection + + coll string +} + +func NewworkflowTaskv4Coll() *WorkflowTaskv4Coll { + name := models.WorkflowTask{}.TableName() + return &WorkflowTaskv4Coll{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *WorkflowTaskv4Coll) GetCollectionName() string { + return c.coll +} + +func (c *WorkflowTaskv4Coll) EnsureIndex(ctx context.Context) error { + mod := []mongo.IndexModel{ + { + Keys: bson.D{ + bson.E{Key: "task_id", Value: 1}, + bson.E{Key: "workflow_name", Value: 1}, + bson.E{Key: "is_deleted", Value: 1}, + bson.E{Key: "status", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + { + Keys: bson.M{"create_time": 1}, + Options: options.Index().SetUnique(false), + }, + { + Keys: bson.D{ + bson.E{Key: "workflow_name", Value: 1}, + bson.E{Key: "is_deleted", Value: 1}, + }, + Options: options.Index().SetUnique(false), + }, + } + + _, err := c.Indexes().CreateMany(ctx, mod) + + return err +} + +func (c *WorkflowTaskv4Coll) Create(obj *models.WorkflowTask) (string, error) { + if obj == nil { + return "", fmt.Errorf("nil object") + } + + res, err := c.InsertOne(context.TODO(), obj) + if err != nil { + return "", err + } + ID, ok := res.InsertedID.(primitive.ObjectID) + if !ok { + return "", errors.New("failed to get object id from create") + } + return ID.Hex(), err +} + +func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option, pageNum, pageSize int64) ([]*models.WorkflowTask, int64, error) { + resp := make([]*models.WorkflowTask, 0) + query := bson.M{} + if opt.WorkflowName != "" { + query["workflow_name"] = opt.WorkflowName + } + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + var findOption *options.FindOptions + if pageNum == 0 && pageSize == 0 { + findOption = options.Find() + } else { + findOption = options.Find(). + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + } + + cursor, err := c.Collection.Find(context.TODO(), query, findOption) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, count, nil +} + +func (c *WorkflowTaskv4Coll) Find(name string) (*models.WorkflowTask, error) { + resp := new(models.WorkflowTask) + query := bson.M{"name": name} + + err := c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *WorkflowTaskv4Coll) GetByID(idstring string) (*models.WorkflowTask, error) { + resp := new(models.WorkflowTask) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *WorkflowTaskv4Coll) Update(idString string, obj *models.WorkflowTask) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *WorkflowTaskv4Coll) DeleteByWorkflowName(workflowName string) error { + query := bson.M{"workflow_name": workflowName} + change := bson.M{"$set": bson.M{ + "is_deleted": true, + "is_archived": true, + }} + + _, err := c.UpdateMany(context.TODO(), query, change) + return err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go new file mode 100644 index 00000000..8398cc89 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go @@ -0,0 +1,163 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + "fmt" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type WorkflowV4Coll struct { + *mongo.Collection + + coll string +} + +type ListWorkflowV4Option struct { + ProjectName string +} + +func NewWorkflowV4Coll() *WorkflowV4Coll { + name := models.WorkflowV4{}.TableName() + return &WorkflowV4Coll{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *WorkflowV4Coll) GetCollectionName() string { + return c.coll +} + +func (c *WorkflowV4Coll) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.M{"project": 1}, + Options: options.Index().SetUnique(false), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *WorkflowV4Coll) Create(obj *models.WorkflowV4) (string, error) { + if obj == nil { + return "", fmt.Errorf("nil object") + } + + res, err := c.InsertOne(context.TODO(), obj) + if err != nil { + return "", err + } + ID, ok := res.InsertedID.(primitive.ObjectID) + if !ok { + return "", errors.New("failed to get object id from create") + } + return ID.Hex(), err +} + +func (c *WorkflowV4Coll) List(opt *ListWorkflowV4Option, pageNum, pageSize int64) ([]*models.WorkflowV4, int64, error) { + resp := make([]*models.WorkflowV4, 0) + query := bson.M{} + if opt.ProjectName != "" { + query["project"] = opt.ProjectName + } + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return nil, 0, err + } + var findOption *options.FindOptions + if pageNum == 0 && pageSize == 0 { + findOption = options.Find() + } else { + findOption = options.Find(). + SetSkip((pageNum - 1) * pageSize). + SetLimit(pageSize) + } + + cursor, err := c.Collection.Find(context.TODO(), query, findOption) + if err != nil { + return nil, 0, err + } + err = cursor.All(context.TODO(), &resp) + if err != nil { + return nil, 0, err + } + return resp, count, nil +} + +func (c *WorkflowV4Coll) Find(name string) (*models.WorkflowV4, error) { + resp := new(models.WorkflowV4) + query := bson.M{"name": name} + + err := c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *WorkflowV4Coll) GetByID(idstring string) (*models.WorkflowV4, error) { + resp := new(models.WorkflowV4) + id, err := primitive.ObjectIDFromHex(idstring) + if err != nil { + return nil, err + } + query := bson.M{"_id": id} + + err = c.FindOne(context.TODO(), query).Decode(&resp) + if err != nil { + return nil, err + } + return resp, nil +} + +func (c *WorkflowV4Coll) Update(idString string, obj *models.WorkflowV4) error { + if obj == nil { + return fmt.Errorf("nil object") + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return fmt.Errorf("invalid id") + } + filter := bson.M{"_id": id} + update := bson.M{"$set": obj} + + _, err = c.UpdateOne(context.TODO(), filter, update) + return err +} + +func (c *WorkflowV4Coll) DeleteByID(idString string) error { + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + return err + } + query := bson.M{"_id": id} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 817c987e..60bdd7cb 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -234,6 +234,8 @@ func initDatabase() { commonrepo.NewEnvSvcDependColl(), commonrepo.NewBuildTemplateColl(), commonrepo.NewScanningColl(), + commonrepo.NewWorkflowV4Coll(), + commonrepo.NewworkflowTaskv4Coll(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index ee014c44..e006b99d 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -163,12 +163,26 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) } + // --------------------------------------------------------------------------------------- + // 新版本 通用工作流(暂命名) 接口 + // --------------------------------------------------------------------------------------- + workflowV4 := router.Group("v4") + { + workflowV4.POST("", CreateWorkflowV4) + workflowV4.GET("", ListWorkflowV4) + workflowV4.GET("/name/:name", FindWorkflowV4) + workflowV4.PUT("/:name", UpdateWorkflowV4) + workflowV4.DELETE("/:name", DeleteWorkflowV4) + workflowV4.GET("/preset/:name", GetWorkflowV4Preset) + } + // --------------------------------------------------------------------------------------- // workflow v4 任务接口 // --------------------------------------------------------------------------------------- taskV4 := router.Group("v4/workflowtask") { taskV4.POST("", CreateWorkflowTaskV4) + taskV4.GET("", ListWorkflowTaskV4) } bundles := router.Group("bundle-resources") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index 478cdc57..8658c9bf 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -30,11 +30,22 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) +type listWorkflowTaskV4Query struct { + PageSize int64 `json:"page_size" form:"page_size,default=20"` + PageNum int64 `json:"page_num" form:"page_num,default=1"` + WorkflowName string `json:"workflow_name" form:"workflow_name"` +} + +type listWorkflowTaskV4Resp struct { + WorkflowList []*commonmodels.WorkflowTask `json:"workflow_list"` + Total int64 `json:"total"` +} + func CreateWorkflowTaskV4(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(commonmodels.WorkflowTask) + args := new(commonmodels.WorkflowV4) data, err := c.GetRawData() if err != nil { log.Errorf("CreateWorkflowTaskv4 c.GetRawData() err : %s", err) @@ -50,5 +61,23 @@ func CreateWorkflowTaskV4(c *gin.Context) { return } - ctx.Err = workflow.CreateWorkflowV4(ctx.UserName, args, ctx.Logger) + ctx.Err = workflow.CreateWorkflowTaskV4(ctx.UserName, args, ctx.Logger) +} + +func ListWorkflowTaskV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &listWorkflowTaskV4Query{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + taskList, total, err := workflow.ListWorkflowTaskV4(args.WorkflowName, args.PageNum, args.PageSize, ctx.Logger) + resp := listWorkflowTaskV4Resp{ + WorkflowList: taskList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go new file mode 100644 index 00000000..b7cc0cbd --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -0,0 +1,130 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "bytes" + "io/ioutil" + + "github.com/gin-gonic/gin" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" + "gopkg.in/yaml.v3" +) + +type listWorkflowV4Query struct { + PageSize int64 `json:"page_size" form:"page_size,default=20"` + PageNum int64 `json:"page_num" form:"page_num,default=1"` + Project string `json:"project" form:"project"` +} + +type listWorkflowV4Resp struct { + WorkflowList []*workflow.Workflow `json:"workflow_list"` + Total int64 `json:"total"` +} + +func CreateWorkflowV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.WorkflowV4) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowv4 c.GetRawData() err : %s", err) + } + if err = yaml.Unmarshal(data, args); err != nil { + log.Errorf("CreateWorkflowv4 json.Unmarshal err : %s", err) + } + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindYAML(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = workflow.CreateWorkflowV4(ctx.UserName, args, ctx.Logger) +} + +func ListWorkflowV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &listWorkflowV4Query{} + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + workflowList, total, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, args.PageNum, args.PageSize, ctx.Logger) + resp := listWorkflowV4Resp{ + WorkflowList: workflowList, + Total: total, + } + ctx.Resp = resp + ctx.Err = err +} + +func UpdateWorkflowV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.WorkflowV4) + data, err := c.GetRawData() + if err != nil { + log.Errorf("UpdateWorkflowV4 c.GetRawData() err : %s", err) + } + if err = yaml.Unmarshal(data, args); err != nil { + log.Errorf("UpdateWorkflowV4 json.Unmarshal err : %s", err) + } + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindYAML(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + + ctx.Err = workflow.UpdateWorkflowV4(c.Param("name"), ctx.UserName, args, ctx.Logger) +} + +func DeleteWorkflowV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = workflow.DeleteWorkflowV4(c.Param("name"), ctx.Logger) +} + +func FindWorkflowV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + resp, err := workflow.FindWorkflowV4(c.Param("name"), ctx.Logger) + if err != nil { + c.JSON(e.ErrorMessage(err)) + c.Abort() + return + } + c.YAML(200, resp) +} + +func GetWorkflowV4Preset(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = workflow.GetWorkflowv4Preset(c.Param("name"), ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go index a170cbdd..d29caa06 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go @@ -45,7 +45,19 @@ func (s *archiveCtl) PreRun(ctx context.Context) error { }, } if s.toolInstalldSpec.S3StorageID == "" { - s.toolInstalldSpec = spec + objectStorage, _ := mongodb.NewS3StorageColl().FindDefault() + if objectStorage != nil { + spec.S3.Endpoint = objectStorage.Endpoint + spec.S3.Sk = objectStorage.Sk + spec.S3.Ak = objectStorage.Ak + spec.S3.Subfolder = objectStorage.Subfolder + spec.S3.Bucket = objectStorage.Bucket + spec.S3.Protocol = "https" + if objectStorage.Insecure { + spec.S3.Protocol = "http" + } + } + s.step.Spec = spec return nil } objectStorage, _ := mongodb.NewS3StorageColl().Find(s.toolInstalldSpec.S3StorageID) diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go index b903eb2a..abc25da7 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go @@ -37,20 +37,20 @@ func NewGitCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*gitCtl func (s *gitCtl) PreRun(ctx context.Context) error { for _, repo := range s.gitSpec.Repos { - cID := repo.CodeHostID + cID := repo.CodehostID if cID == 0 { log.Error("codehostID can't be empty") - return nil + return fmt.Errorf("codehostID can't be empty") } detail, err := systemconfig.New().GetCodeHost(cID) if err != nil { s.log.Error(err) - return nil + return err } repo.Source = detail.Type repo.OauthToken = detail.AccessToken repo.Address = detail.Address - repo.User = detail.Username + repo.Username = detail.Username repo.Password = detail.Password repo.EnableProxy = detail.EnableProxy } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go new file mode 100644 index 00000000..f9e5b852 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -0,0 +1,68 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type JobCtl interface { + Instantiate() error + SetPreset() error + ToJobs(taskID int64) ([]*commonmodels.JobTask, error) +} + +func initJobCtl(job *commonmodels.Job, workflow *commonmodels.WorkflowV4) (JobCtl, error) { + var resp JobCtl + switch job.JobType { + case config.JobZadigBuild: + resp = &BuildJob{job: job, workflow: workflow} + + case config.JobZadigDeploy: + resp = &DeployJob{job: job, workflow: workflow} + default: + return resp, fmt.Errorf("job type not found %s", job.JobType) + } + return resp, nil +} + +func Instantiate(job *commonmodels.Job, workflow *commonmodels.WorkflowV4) error { + ctl, err := initJobCtl(job, workflow) + if err != nil { + return err + } + return ctl.Instantiate() +} + +func SetPreset(job *commonmodels.Job, workflow *commonmodels.WorkflowV4) error { + jobCtl, err := initJobCtl(job, workflow) + if err != nil { + return err + } + return jobCtl.SetPreset() +} + +func ToJobs(job *commonmodels.Job, workflow *commonmodels.WorkflowV4, taskID int64) ([]*commonmodels.JobTask, error) { + jobCtl, err := initJobCtl(job, workflow) + if err != nil { + return []*commonmodels.JobTask{}, err + } + return jobCtl.ToJobs(taskID) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go new file mode 100644 index 00000000..7d79ed09 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -0,0 +1,265 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +import ( + "fmt" + "path" + "strconv" + "strings" + "time" + + configbase "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/types/step" +) + +type BuildJob struct { + job *commonmodels.Job + workflow *commonmodels.WorkflowV4 + spec *commonmodels.ZadigBuildJobSpec +} + +func (j *BuildJob) Instantiate() error { + j.spec = &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToiYaml(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + return nil +} + +func (j *BuildJob) SetPreset() error { + j.spec = &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + + for _, build := range j.spec.ServiceAndBuilds { + buildInfo, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.BuildName}) + if err != nil { + return err + } + for _, target := range buildInfo.Targets { + if target.ServiceName == build.ServiceName && target.ServiceModule == build.ServiceModule { + build.Repos = buildInfo.Repos + build.KeyVals = renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs) + break + } + } + } + j.job.Spec = j.spec + return nil +} + +func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { + resp := []*commonmodels.JobTask{} + + j.spec = &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return resp, err + } + j.job.Spec = j.spec + + registry, err := commonrepo.NewRegistryNamespaceColl().Find(&commonrepo.FindRegOps{ID: j.spec.DockerRegistryID}) + if err != nil { + return resp, err + } + + for _, build := range j.spec.ServiceAndBuilds { + imageTag := "" + for _, repo := range build.Repos { + imageTag = repo.GetReleaseCandidateTag(taskID) + if len(registry.Namespace) > 0 { + build.Image = fmt.Sprintf("%s/%s/%s:%s", registry.RegAddr, registry.Namespace, build.ServiceModule, imageTag) + } + build.Image = fmt.Sprintf("%s/%s:%s", registry.RegAddr, build.ServiceModule, imageTag) + build.Image = strings.TrimPrefix(build.Image, "http://") + build.Image = strings.TrimPrefix(build.Image, "https://") + break + } + build.Package = fmt.Sprintf("%s-%s-%d.tar.gz", build.ServiceModule, time.Now().Format("20060102150405"), taskID) + + jobTask := &commonmodels.JobTask{ + Name: j.job.Name + "-" + build.ServiceName, + JobType: string(config.JobZadigBuild), + } + buildInfo, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.BuildName}) + if err != nil { + return resp, err + } + jobTask.Properties = commonmodels.JobProperties{ + Timeout: int64(buildInfo.Timeout), + ResourceRequest: buildInfo.PreBuild.ResReq, + Args: buildInfo.PreBuild.Envs, + ClusterID: buildInfo.PreBuild.ClusterID, + } + jobTask.Properties.Args = append(jobTask.Properties.Args, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name)...) + + // init tools install step + for _, tool := range buildInfo.PreBuild.Installs { + toolInstallStep := &commonmodels.StepTask{ + Name: fmt.Sprintf("%s-%s-%s", build.ServiceName, tool.Name, tool.Version), + JobName: jobTask.Name, + StepType: config.StepTools, + Spec: step.StepToolInstallSpec{Name: tool.Name, Version: tool.Version}, + } + jobTask.Steps = append(jobTask.Steps, toolInstallStep) + } + // init git clone step + gitStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-git", + JobName: jobTask.Name, + StepType: config.StepGit, + Spec: step.StepGitSpec{Repos: build.Repos}, + } + jobTask.Steps = append(jobTask.Steps, gitStep) + + // init shell step + shellStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-shell", + JobName: jobTask.Name, + StepType: config.StepShell, + Spec: step.StepShellSpec{Scripts: strings.Split(replaceWrapLine(buildInfo.Scripts), "\n")}, + } + jobTask.Steps = append(jobTask.Steps, shellStep) + + // init docker build step + if buildInfo.PostBuild.DockerBuild != nil { + dockefileContent := "" + if buildInfo.PostBuild.DockerBuild.TemplateID != "" { + dockerfileTemplate, err := commonrepo.NewDockerfileTemplateColl().GetById(buildInfo.PostBuild.DockerBuild.TemplateID) + if err != nil { + return resp, nil + } + dockefileContent = dockerfileTemplate.Content + } + + dockerBuildStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-docker-build", + JobName: jobTask.Name, + StepType: config.StepDockerBuild, + Spec: step.StepDockerBuildSpec{ + Source: buildInfo.PostBuild.DockerBuild.Source, + WorkDir: buildInfo.PostBuild.DockerBuild.WorkDir, + DockerFile: buildInfo.PostBuild.DockerBuild.DockerFile, + ImageName: build.Image, + ImageReleaseTag: imageTag, + BuildArgs: buildInfo.PostBuild.DockerBuild.BuildArgs, + DockerTemplateContent: dockefileContent, + }, + } + jobTask.Steps = append(jobTask.Steps, dockerBuildStep) + } + + // init archive step + if buildInfo.PostBuild.FileArchive != nil && buildInfo.PostBuild.FileArchive.FileLocation != "" { + archiveStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-archive", + JobName: jobTask.Name, + StepType: config.StepArchive, + Spec: step.StepArchiveSpec{ + FilePath: path.Join(buildInfo.PostBuild.FileArchive.FileLocation, build.Package), + DestinationPath: path.Join(j.workflow.Name, fmt.Sprint(taskID), j.job.Name, "archive"), + }, + } + jobTask.Steps = append(jobTask.Steps, archiveStep) + } + + // init object storage step + if buildInfo.PostBuild.ObjectStorageUpload != nil && buildInfo.PostBuild.ObjectStorageUpload.Enabled { + for _, detail := range buildInfo.PostBuild.ObjectStorageUpload.UploadDetail { + archiveStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-archive", + JobName: jobTask.Name, + StepType: config.StepArchive, + Spec: step.StepArchiveSpec{ + FilePath: detail.FilePath, + DestinationPath: detail.DestinationPath, + S3StorageID: buildInfo.PostBuild.ObjectStorageUpload.ObjectStorageID, + }, + } + jobTask.Steps = append(jobTask.Steps, archiveStep) + } + } + resp = append(resp, jobTask) + } + j.job.Spec = j.spec + return resp, nil +} + +func renderKeyVals(input, origin []*commonmodels.KeyVal) []*commonmodels.KeyVal { + for i, originKV := range origin { + for _, inputKV := range input { + if originKV.Key == inputKV.Key { + origin[i] = inputKV + } + } + } + return origin +} + +func replaceWrapLine(script string) string { + return strings.Replace(strings.Replace( + script, + "\r\n", + "\n", + -1, + ), "\r", "\n", -1) +} + +func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, workflowName string) []*commonmodels.KeyVal { + ret := make([]*commonmodels.KeyVal, 0) + for index, repo := range build.Repos { + + repoNameIndex := fmt.Sprintf("REPONAME_%d", index) + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf(repoNameIndex), Value: repo.RepoName, IsCredential: false}) + + repoName := strings.Replace(repo.RepoName, "-", "_", -1) + + repoIndex := fmt.Sprintf("REPO_%d", index) + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf(repoIndex), Value: repoName, IsCredential: false}) + + if len(repo.Branch) > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false}) + } + + if len(repo.Tag) > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false}) + } + + if repo.PR > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false}) + } + + if len(repo.CommitID) > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_COMMIT_ID", repoName), Value: repo.CommitID, IsCredential: false}) + } + } + ret = append(ret, &commonmodels.KeyVal{Key: "TASK_ID", Value: fmt.Sprintf("%d", taskID), IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE", Value: build.ServiceName, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "IMAGE", Value: build.Image, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "CI", Value: "true", IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "ZADIG", Value: "true", IsCredential: false}) + buildURL := fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/%s/%d", configbase.SystemAddress(), project, workflowName, taskID) + ret = append(ret, &commonmodels.KeyVal{Key: "BUILD_URL", Value: buildURL, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "PKG_FILE", Value: build.Package, IsCredential: false}) + return ret +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go new file mode 100644 index 00000000..3fd206a1 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -0,0 +1,159 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +import ( + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/types/step" +) + +type DeployJob struct { + job *commonmodels.Job + workflow *commonmodels.WorkflowV4 + spec *commonmodels.ZadigDeployJobSpec +} + +func (j *DeployJob) Instantiate() error { + j.spec = &commonmodels.ZadigDeployJobSpec{} + if err := commonmodels.IToiYaml(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + return nil +} + +func (j *DeployJob) SetPreset() error { + j.spec = &commonmodels.ZadigDeployJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + + if j.spec.Source != config.SourceRuntime { + return nil + } + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(j.workflow.Project) + if err != nil { + return err + } + for _, service := range services { + j.spec.DeployType = service.Type + for _, container := range service.Containers { + j.spec.ServiceAndImages = append(j.spec.ServiceAndImages, &commonmodels.ServiceAndImage{ServiceName: service.ServiceName, ServiceModule: container.Name}) + } + } + j.job.Spec = j.spec + return nil +} + +func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { + resp := []*commonmodels.JobTask{} + + j.spec = &commonmodels.ZadigDeployJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return resp, err + } + j.job.Spec = j.spec + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: j.workflow.Project, EnvName: j.spec.Env}) + if err != nil { + return resp, err + } + // get deploy info from previous build job + if j.spec.Source == config.SourceFromJob { + for _, stage := range j.workflow.Stages { + for _, job := range stage.Jobs { + if job.JobType != config.JobZadigBuild || job.Name != j.spec.JobName { + continue + } + buildSpec := &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToi(job.Spec, buildSpec); err != nil { + return resp, err + } + for _, build := range buildSpec.ServiceAndBuilds { + j.spec.ServiceAndImages = append(j.spec.ServiceAndImages, &commonmodels.ServiceAndImage{ + ServiceName: build.ServiceName, + ServiceModule: build.ServiceModule, + Image: build.Image, + }) + } + } + } + } + if j.spec.DeployType == setting.K8SDeployType { + for _, deploy := range j.spec.ServiceAndImages { + jobTask := &commonmodels.JobTask{ + Name: j.job.Name + "-" + deploy.ServiceName + "-" + deploy.ServiceModule, + JobType: string(config.JobDeploy), + } + deployStep := &commonmodels.StepTask{ + Name: deploy.ServiceName + "-deploy", + JobName: jobTask.Name, + StepType: config.StepDeploy, + Spec: step.StepDeploySpec{ + Env: j.spec.Env, + ServiceName: deploy.ServiceName, + ServiceType: setting.K8SDeployType, + ServiceModule: deploy.ServiceModule, + ClusterID: product.ClusterID, + Image: deploy.Image, + }, + } + jobTask.Steps = append(jobTask.Steps, deployStep) + resp = append(resp, jobTask) + } + } + if j.spec.DeployType == setting.HelmDeployType { + deployServiceMap := map[string][]*commonmodels.ServiceAndImage{} + for _, deploy := range j.spec.ServiceAndImages { + deployServiceMap[deploy.ServiceName] = append(deployServiceMap[deploy.ServiceName], deploy) + } + for serviceName, deploys := range deployServiceMap { + jobTask := &commonmodels.JobTask{ + Name: j.job.Name + "-" + serviceName, + JobType: string(config.JobDeploy), + } + deployStep := &commonmodels.StepTask{ + Name: serviceName + "-deploy", + JobName: jobTask.Name, + StepType: config.StepHelmDeploy, + } + helmDeploySpec := step.StepHelmDeploySpec{ + Env: j.spec.Env, + ServiceName: serviceName, + ServiceType: setting.HelmDeployType, + ClusterID: product.ClusterID, + } + for _, deploy := range deploys { + helmDeploySpec.ImageAndModules = append(helmDeploySpec.ImageAndModules, &step.ImageAndServiceModule{ + ServiceModule: deploy.ServiceModule, + Image: deploy.Image, + }) + } + deployStep.Spec = helmDeploySpec + + jobTask.Steps = append(jobTask.Steps, deployStep) + resp = append(resp, jobTask) + } + } + + j.job.Spec = j.spec + return resp, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go new file mode 100644 index 00000000..00d70aa5 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -0,0 +1,135 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workflow + +import ( + "context" + "fmt" + "time" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" + "github.com/koderover/zadig/pkg/setting" + e "github.com/koderover/zadig/pkg/tool/errors" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/zap" +) + +func GetWorkflowv4Preset(workflowName string, log *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { + workflow, err := commonrepo.NewWorkflowV4Coll().Find(workflowName) + if err != nil { + log.Errorf("cannot find workflow %s, the error is: %v", workflowName, err) + return nil, e.ErrFindWorkflow.AddDesc(err.Error()) + } + for _, stage := range workflow.Stages { + for _, job := range stage.Jobs { + if err := jobctl.SetPreset(job, workflow); err != nil { + log.Errorf("cannot get workflow %s preset, the error is: %v", workflowName, err) + return nil, e.ErrFindWorkflow.AddDesc(err.Error()) + } + } + } + return workflow, nil +} + +func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *zap.SugaredLogger) error { + workflowTask := &commonmodels.WorkflowTask{} + nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.WorkflowTaskV4Fmt, workflow.Name)) + if err != nil { + log.Errorf("Counter.GetNextSeq error: %v", err) + return e.ErrGetCounter.AddDesc(err.Error()) + } + workflowTask.TaskID = nextTaskID + workflowTask.TaskCreator = user + workflowTask.TaskRevoker = user + workflowTask.CreateTime = time.Now().Unix() + workflowTask.WorkflowName = workflow.Name + workflowTask.ProjectName = workflow.Project + + for _, stage := range workflow.Stages { + stageTask := &commonmodels.StageTask{ + Name: stage.Name, + Parallel: stage.Parallel, + Approval: stage.Approval, + } + workflowTask.Stages = append(workflowTask.Stages, stageTask) + for _, job := range stage.Jobs { + if err := setZadigBuildRepos(job, log); err != nil { + log.Errorf("set build info error: %v", err) + return e.ErrCreateTask.AddDesc(err.Error()) + } + jobs, err := jobctl.ToJobs(job, workflow, nextTaskID) + if err != nil { + log.Errorf("cannot create workflow %s, the error is: %v", workflow.Name, err) + return e.ErrCreateTask.AddDesc(err.Error()) + } + stageTask.Jobs = append(stageTask.Jobs, jobs...) + } + + } + idString, err := commonrepo.NewworkflowTaskv4Coll().Create(workflowTask) + if err != nil { + log.Errorf("cannot create workflow task %s, the error is: %v", workflow.Name, err) + return e.ErrCreateTask.AddDesc(err.Error()) + } + id, err := primitive.ObjectIDFromHex(idString) + if err != nil { + log.Errorf("transform task id error: %v", err) + return e.ErrCreateTask.AddDesc(err.Error()) + } + ctx := context.Background() + workflowTask.ID = id + go workflowcontroller.NewWorkflowController(workflowTask, log).Run(ctx, 5) + return nil +} + +func UpdateWorkflowTaskV4(id string, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { + err := commonrepo.NewworkflowTaskv4Coll().Update( + id, + workflowTask, + ) + if err != nil { + logger.Errorf("update workflowTaskV4 error: %s", err) + return e.ErrCreateTask.AddErr(err) + } + return nil +} + +func ListWorkflowTaskV4(workflowName string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*commonmodels.WorkflowTask, int64, error) { + resp, total, err := commonrepo.NewworkflowTaskv4Coll().List(&commonrepo.ListWorkflowTaskV4Option{WorkflowName: workflowName}, pageNum, pageSize) + if err != nil { + logger.Errorf("list workflowTaskV4 error: %s", err) + return resp, total, err + } + return resp, total, nil +} + +func setZadigBuildRepos(job *commonmodels.Job, logger *zap.SugaredLogger) error { + spec := &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + return err + } + for _, build := range spec.ServiceAndBuilds { + if err := setManunalBuilds(build.Repos, build.Repos, logger); err != nil { + return err + } + } + job.Spec = spec + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 0ced3700..219957d2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -17,17 +17,131 @@ limitations under the License. package workflow import ( - "context" + "fmt" + "time" "go.uber.org/zap" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" ) -func CreateWorkflowV4(user string, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { - ctl := workflowcontroller.NewWorkflowController(workflowTask, logger) - ctx := context.Background() - go ctl.Run(ctx, 2) +func CreateWorkflowV4(user string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { + _, err := commonrepo.NewWorkflowV4Coll().Find(workflow.Name) + if err == nil { + errStr := fmt.Sprintf("workflow v4 [%s] 在项目 [%s] 中已经存在!", workflow.Name, workflow.Project) + return e.ErrUpsertWorkflow.AddDesc(errStr) + } + + workflow.CreatedBy = user + workflow.UpdatedBy = user + workflow.CreateTime = time.Now().Unix() + workflow.UpdateTime = time.Now().Unix() + + for _, stage := range workflow.Stages { + for _, job := range stage.Jobs { + if err := jobctl.Instantiate(job, workflow); err != nil { + logger.Errorf("Failed to instantiate workflow v4,error: %v", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + } + } + + if _, err := commonrepo.NewWorkflowV4Coll().Create(workflow); err != nil { + logger.Errorf("Failed to create workflow v4, the error is: %s", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + + return nil +} + +func UpdateWorkflowV4(name, user string, inputWorkflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { + workflow, err := commonrepo.NewWorkflowV4Coll().Find(name) + if err != nil { + logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) + return e.ErrFindWorkflow.AddErr(err) + } + + inputWorkflow.UpdatedBy = user + inputWorkflow.UpdateTime = time.Now().Unix() + inputWorkflow.ID = workflow.ID + + for _, stage := range inputWorkflow.Stages { + for _, job := range stage.Jobs { + if err := jobctl.Instantiate(job, workflow); err != nil { + logger.Errorf("Failed to instantiate workflow v4,error: %v", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + } + } + + if err := commonrepo.NewWorkflowV4Coll().Update( + workflow.ID.Hex(), + inputWorkflow, + ); err != nil { + logger.Errorf("update workflowV4 error: %s", err) + return e.ErrUpsertWorkflow.AddErr(err) + } return nil } + +func FindWorkflowV4(name string, logger *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { + workflow, err := commonrepo.NewWorkflowV4Coll().Find(name) + if err != nil { + logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) + return workflow, e.ErrFindWorkflow.AddErr(err) + } + return workflow, err +} + +func DeleteWorkflowV4(name string, logger *zap.SugaredLogger) error { + workflow, err := commonrepo.NewWorkflowV4Coll().Find(name) + if err != nil { + logger.Errorf("Failed to delete WorkflowV4: %s, the error is: %v", name, err) + return e.ErrDeleteWorkflow.AddErr(err) + } + if err := commonrepo.NewWorkflowV4Coll().DeleteByID(workflow.ID.Hex()); err != nil { + logger.Errorf("Failed to delete WorkflowV4: %s, the error is: %v", name, err) + return e.ErrDeleteWorkflow.AddErr(err) + } + + if err := commonrepo.NewCounterColl().Delete("WorkflowTaskV4:" + name); err != nil { + log.Errorf("Counter.Delete error: %s", err) + } + return nil +} + +func ListWorkflowV4(projectName, userID string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*Workflow, int64, error) { + resp := make([]*Workflow, 0) + workflowV4List, total, err := commonrepo.NewWorkflowV4Coll().List(&commonrepo.ListWorkflowV4Option{ + ProjectName: projectName, + }, pageNum, pageSize) + if err != nil { + logger.Errorf("Failed to list workflow v4, the error is: %s", err) + return resp, 0, err + } + + workflow, err := ListWorkflows([]string{projectName}, userID, []string{}, logger) + if err != nil { + return resp, 0, err + } + resp = append(resp, workflow...) + + for _, workflow := range workflowV4List { + stages := []string{} + for _, stage := range workflow.Stages { + stages = append(stages, stage.Name) + } + resp = append(resp, &Workflow{ + Name: workflow.Name, + ProjectName: workflow.Project, + EnabledStages: stages, + CreateTime: workflow.CreateTime, + WorkflowType: "common_workflow", + }) + } + return resp, total, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index b4ec442c..42196d19 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -40,13 +40,9 @@ func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *c logger.Infof("finish stage: %s,status: %s", stage.Name, stage.Status) ack() }() - var stageCtl StageCtl - switch stage.StageType { - case "approve": - // TODO approval stage - default: - stageCtl = NewCustomStageCtl(stage, workflowCtx, logger, ack) - } + + stageCtl := NewCustomStageCtl(stage, workflowCtx, logger, ack) + stageCtl.Run(ctx, concurrency) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 7920fbd8..5932abfd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -24,6 +24,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" uuid "github.com/satori/go.uuid" "go.uber.org/zap" ) @@ -132,6 +133,9 @@ func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { func (c *workflowCtl) updateWorkflowTask() { // TODO update workflow task + if err := commonrepo.NewworkflowTaskv4Coll().Update(c.workflowTask.ID.Hex(), c.workflowTask); err != nil { + c.logger.Errorf("update workflow task v4 failed,error: %v", err) + } } func (c *workflowCtl) getGlobalContext(key string) (string, bool) { diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 57c263d0..c1a8945a 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -341,6 +341,7 @@ const ( PipelineTaskFmt = "PipelineTask:%s" WorkflowTaskFmt = "WorkflowTask:%s" WorkflowTaskV3Fmt = "WorkflowTaskV3:%s" + WorkflowTaskV4Fmt = "WorkflowTaskV4:%s" TestTaskFmt = "TestTask:%s" ServiceTaskFmt = "ServiceTask:%s" ScanningTaskFmt = "ScanningTask:%s" diff --git a/pkg/types/repo.go b/pkg/types/repo.go index b46f2b87..c579ba5e 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -19,6 +19,7 @@ package types import ( "fmt" "regexp" + "strings" "time" ) @@ -111,3 +112,64 @@ func (repo *Repository) GetRepoNamespace() string { } return repo.RepoOwner } + +const ( + // ProviderGithub ... + ProviderGithub = "github" + // ProviderGitlab ... + ProviderGitlab = "gitlab" + + // ProviderGerrit + ProviderGerrit = "gerrit" + + // ProviderCodehub + ProviderCodehub = "codehub" + + // ProviderGitee + ProviderGitee = "gitee" + + // ProviderOther + ProviderOther = "other" +) + +// PRRef returns refs format +// It will check repo provider type, by default returns github refs format. +// +// e.g. github returns refs/pull/1/head +// e.g. gitlab returns merge-requests/1/head +func (r *Repository) PRRef() string { + if strings.ToLower(r.Source) == ProviderGitlab || strings.ToLower(r.Source) == ProviderCodehub { + return fmt.Sprintf("merge-requests/%d/head", r.PR) + } else if strings.ToLower(r.Source) == ProviderGerrit { + return r.CheckoutRef + } + return fmt.Sprintf("refs/pull/%d/head", r.PR) +} + +// BranchRef returns branch refs format +// e.g. refs/heads/master +func (r *Repository) BranchRef() string { + return fmt.Sprintf("refs/heads/%s", r.Branch) +} + +// TagRef returns the tag ref of current repo +// e.g. refs/tags/v1.0.0 +func (r *Repository) TagRef() string { + return fmt.Sprintf("refs/tags/%s", r.Tag) +} + +// Ref returns the changes ref of current repo in the following order: +// 1. tag ref +// 2. branch ref +// 3. pr ref +func (r *Repository) Ref() string { + if len(r.Tag) > 0 { + return r.TagRef() + } else if len(r.Branch) > 0 { + return r.BranchRef() + } else if r.PR > 0 { + return r.PRRef() + } + + return "" +} diff --git a/pkg/types/step/step_git.go b/pkg/types/step/step_git.go index 2ed4aa95..fdc13dca 100644 --- a/pkg/types/step/step_git.go +++ b/pkg/types/step/step_git.go @@ -18,57 +18,16 @@ package step import ( "fmt" - "strings" "github.com/koderover/zadig/pkg/types" ) type StepGitSpec struct { - Repos []*Repo `yaml:"repos"` - Proxy *Proxy `yaml:"proxy"` -} - -type Repo struct { - Source string `yaml:"source"` - Address string `yaml:"address"` - CodeHostID int `yaml:"code_host_id"` - Owner string `yaml:"owner"` - Name string `yaml:"name"` - Namespace string `yaml:"namespace"` - RemoteName string `yaml:"remote_name"` - Branch string `yaml:"branch"` - PR int `yaml:"pr"` - Tag string `yaml:"tag"` - CheckoutPath string `yaml:"checkout_path"` - SubModules bool `yaml:"submodules"` - OauthToken string `yaml:"oauthToken"` - User string `yaml:"username"` - Password string `yaml:"password"` - CheckoutRef string `yaml:"checkout_ref"` - EnableProxy bool `yaml:"enable_proxy"` - AuthType types.AuthType `yaml:"auth_type,omitempty"` - SSHKey string `yaml:"ssh_key,omitempty"` - PrivateAccessToken string `yaml:"private_access_token,omitempty"` + Repos []*types.Repository `yaml:"repos"` + Proxy *Proxy `yaml:"proxy"` } const ( - // ProviderGithub ... - ProviderGithub = "github" - // ProviderGitlab ... - ProviderGitlab = "gitlab" - - // ProviderGerrit - ProviderGerrit = "gerrit" - - // ProviderCodehub - ProviderCodehub = "codehub" - - // ProviderGitee - ProviderGitee = "gitee" - - // ProviderOther - ProviderOther = "other" - // Oauth prefix OauthTokenPrefix = "oauth2" @@ -95,45 +54,3 @@ func (p *Proxy) GetProxyURL() string { ) return uri } - -// PRRef returns refs format -// It will check repo provider type, by default returns github refs format. -// -// e.g. github returns refs/pull/1/head -// e.g. gitlab returns merge-requests/1/head -func (r *Repo) PRRef() string { - if strings.ToLower(r.Source) == ProviderGitlab || strings.ToLower(r.Source) == ProviderCodehub { - return fmt.Sprintf("merge-requests/%d/head", r.PR) - } else if strings.ToLower(r.Source) == ProviderGerrit { - return r.CheckoutRef - } - return fmt.Sprintf("refs/pull/%d/head", r.PR) -} - -// BranchRef returns branch refs format -// e.g. refs/heads/master -func (r *Repo) BranchRef() string { - return fmt.Sprintf("refs/heads/%s", r.Branch) -} - -// TagRef returns the tag ref of current repo -// e.g. refs/tags/v1.0.0 -func (r *Repo) TagRef() string { - return fmt.Sprintf("refs/tags/%s", r.Tag) -} - -// Ref returns the changes ref of current repo in the following order: -// 1. tag ref -// 2. branch ref -// 3. pr ref -func (r *Repo) Ref() string { - if len(r.Tag) > 0 { - return r.TagRef() - } else if len(r.Branch) > 0 { - return r.BranchRef() - } else if r.PR > 0 { - return r.PRRef() - } - - return "" -} -- Gitee From 9b88c63761a6d3b6afa4adcfce9c0d44f2c6e5ec Mon Sep 17 00:00:00 2001 From: guoyu Date: Sun, 26 Jun 2022 11:29:46 +0800 Subject: [PATCH 2484/2647] update workflow list api Signed-off-by: guoyu --- pkg/microservice/aslan/core/build/service/build.go | 6 ++++-- .../aslan/core/workflow/service/workflow/workflow.go | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 00d5f545..e7312096 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -40,6 +40,7 @@ type BuildResp struct { ID string `json:"id"` Name string `json:"name"` Targets []*commonmodels.ServiceModuleTarget `json:"targets"` + KeyVals []*commonmodels.KeyVal `json:"key_vals"` UpdateTime int64 `json:"update_time"` UpdateBy string `json:"update_by"` Pipelines []string `json:"pipelines"` @@ -141,8 +142,9 @@ func ListBuildModulesByServiceModule(productName string, log *zap.SugaredLogger) var resp []*BuildResp for _, build := range buildModules { resp = append(resp, &BuildResp{ - ID: build.ID.Hex(), - Name: build.Name, + ID: build.ID.Hex(), + Name: build.Name, + KeyVals: build.PreBuild.Envs, }) } serviceModuleAndBuildResp = append(serviceModuleAndBuildResp, &ServiceModuleAndBuildResp{ diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 6177a826..4f6f6f83 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -56,6 +56,7 @@ type Workflow struct { SchedulerEnabled bool `json:"schedulerEnabled"` EnabledStages []string `json:"enabledStages"` IsFavorite bool `json:"isFavorite"` + WorkflowType string `json:"workflow_type"` RecentTask *TaskInfo `json:"recentTask"` RecentSuccessfulTask *TaskInfo `json:"recentSuccessfulTask"` RecentFailedTask *TaskInfo `json:"recentFailedTask"` -- Gitee From ee7a0aae93c2a5046b9d6d971604ea3ccf64d70f Mon Sep 17 00:00:00 2001 From: guoyu Date: Sun, 26 Jun 2022 11:30:43 +0800 Subject: [PATCH 2485/2647] jobexecutor log to files so we can collect Signed-off-by: guoyu --- .../service/jobcontroller/kubernetes.go | 42 ++++++++++++- .../jobexecutor/core/service/step/step.go | 4 +- .../core/service/step/step_docker_build.go | 13 ++-- .../jobexecutor/core/service/step/step_git.go | 60 +++++++++---------- .../core/service/step/step_tool_install.go | 3 +- .../jobexecutor/executor/executor.go | 29 +++++++-- 6 files changed, 103 insertions(+), 48 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index a7b05c05..6c813a19 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -46,6 +46,13 @@ import ( "github.com/koderover/zadig/pkg/types/job" ) +const ( + BusyBoxImage = "ccr.ccs.tencentyun.com/koderover-public/busybox:latest" + ZadigContextDir = "/zadig/" + ZadigLogFile = ZadigContextDir + "zadig.log" + ZadigLifeCycleFile = ZadigContextDir + "lifecycle" +) + func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes.Interface, *rest.Config, error) { controllerRuntimeClient, err := kubeclient.GetKubeClient(hubServerAddr, clusterID) if err != nil { @@ -130,6 +137,13 @@ func getReaperImage(reaperImage, buildOS string) string { } func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, resReq setting.Request, resReqSpec setting.RequestSpec, jobTask *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, registries []*task.RegistryNamespace) (*batchv1.Job, error) { + tailLogCommandTemplate := `tail -f %s & +while [ -f %s ]; +do + sleep 1s; +done; +` + tailLogCommand := fmt.Sprintf(tailLogCommandTemplate, ZadigLogFile, ZadigLifeCycleFile) labels := getJobLabels(&JobLabel{ WorkflowName: workflowCtx.WorkflowName, @@ -172,11 +186,28 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, ImagePullSecrets: ImagePullSecrets, + InitContainers: []corev1.Container{ + { + ImagePullPolicy: corev1.PullIfNotPresent, + Name: "init-log-file", + Image: BusyBoxImage, + VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), + Command: []string{"/bin/sh", "-c", fmt.Sprintf("touch %s %s", ZadigLogFile, ZadigLifeCycleFile)}, + }, + }, Containers: []corev1.Container{ { ImagePullPolicy: corev1.PullAlways, Name: labels[jobLabelNameKey], Image: jobImage, + Command: []string{"/bin/sh", "-c", "jobexecutor"}, + Lifecycle: &corev1.Lifecycle{ + PreStop: &corev1.Handler{ + Exec: &corev1.ExecAction{ + Command: []string{"/bin/sh", "-c", fmt.Sprintf("rm %s", ZadigLifeCycleFile)}, + }, + }, + }, Env: []corev1.EnvVar{ { Name: "JOB_CONFIG_FILE", @@ -194,6 +225,15 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re TerminationMessagePolicy: corev1.TerminationMessageReadFile, TerminationMessagePath: job.JobTerminationFile, }, + { + ImagePullPolicy: corev1.PullIfNotPresent, + Name: "log", + Image: BusyBoxImage, + VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), + Command: []string{"/bin/sh", "-c"}, + Args: []string{tailLogCommand}, + Lifecycle: &corev1.Lifecycle{}, + }, }, Volumes: getVolumes(jobName), }, @@ -240,7 +280,7 @@ func getVolumeMounts(configMapMountDir string) []corev1.VolumeMount { }) resp = append(resp, corev1.VolumeMount{ Name: "zadig-context", - MountPath: "/zadig/", + MountPath: ZadigContextDir, }) return resp diff --git a/pkg/microservice/jobexecutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go index d25fecfc..815fda3d 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -48,9 +48,9 @@ func runStep(ctx context.Context, step *meta.Step, workspace, paths string, envs start := time.Now() var stepInstance Step var err error - log.Infof("====================== %s Start ======================", step.Name) + fmt.Printf("====================== %s Start ======================\n", step.Name) defer func() { - log.Infof("====================== %s End. Duration: %.2f seconds ======================", step.Name, time.Since(start).Seconds()) + fmt.Printf("====================== %s End. Duration: %.2f seconds ======================\n", step.Name, time.Since(start).Seconds()) }() switch step.StepType { case "shell": diff --git a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go index 8ac4d90f..85555ad9 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go @@ -27,7 +27,6 @@ import ( "time" "github.com/koderover/zadig/pkg/setting" - "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/step" "github.com/koderover/zadig/pkg/util/fs" "gopkg.in/yaml.v3" @@ -66,7 +65,7 @@ func (s DockerBuildStep) dockerLogin() error { return nil } if s.spec.DockerRegistry.UserName != "" { - log.Infof("Logining Docker Registry: %s.", s.spec.DockerRegistry.Host) + fmt.Printf("Logining Docker Registry: %s.\n", s.spec.DockerRegistry.Host) startTimeDockerLogin := time.Now() cmd := dockerLogin(s.spec.DockerRegistry.UserName, s.spec.DockerRegistry.Password, s.spec.DockerRegistry.Host) var out bytes.Buffer @@ -76,7 +75,7 @@ func (s DockerBuildStep) dockerLogin() error { return fmt.Errorf("failed to login docker registry: %s", err) } - log.Infof("Login ended. Duration: %.2f seconds.", time.Since(startTimeDockerLogin).Seconds()) + fmt.Printf("Login ended. Duration: %.2f seconds.\n", time.Since(startTimeDockerLogin).Seconds()) } return nil } @@ -86,19 +85,19 @@ func (s *DockerBuildStep) runDockerBuild() error { return nil } - log.Info("Preparing Dockerfile.") + fmt.Printf("Preparing Dockerfile.\n") startTimePrepareDockerfile := time.Now() err := prepareDockerfile(s.spec.Source, s.spec.DockerTemplateContent) if err != nil { return fmt.Errorf("failed to prepare dockerfile: %s", err) } - log.Infof("Preparation ended. Duration: %.2f seconds.", time.Since(startTimePrepareDockerfile).Seconds()) + fmt.Printf("Preparation ended. Duration: %.2f seconds.\n", time.Since(startTimePrepareDockerfile).Seconds()) if s.spec.Proxy != nil { setProxy(s.spec) } - log.Info("Runing Docker Build.") + fmt.Printf("Runing Docker Build.\n") startTimeDockerBuild := time.Now() envs := s.envs for _, c := range s.dockerCommands() { @@ -110,7 +109,7 @@ func (s *DockerBuildStep) runDockerBuild() error { return fmt.Errorf("failed to run docker build: %s", err) } } - log.Infof("Docker build ended. Duration: %.2f seconds.", time.Since(startTimeDockerBuild).Seconds()) + fmt.Printf("Docker build ended. Duration: %.2f seconds.\n", time.Since(startTimeDockerBuild).Seconds()) return nil } diff --git a/pkg/microservice/jobexecutor/core/service/step/step_git.go b/pkg/microservice/jobexecutor/core/service/step/step_git.go index 9e1a4bb3..75cb0c15 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_git.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_git.go @@ -75,7 +75,7 @@ func (s *GitStep) runGitCmds() error { envs := s.envs // 如果存在github代码库,则设置代理,同时保证非github库不走代理 - if s.spec.Proxy.EnableRepoProxy && s.spec.Proxy.Type == "http" { + if s.spec.Proxy != nil && s.spec.Proxy.EnableRepoProxy && s.spec.Proxy.Type == "http" { noProxy := "" proxyFlag := false for _, repo := range s.spec.Repos { @@ -110,11 +110,11 @@ func (s *GitStep) runGitCmds() error { var tokens []string var hostNames = sets.NewString() for _, repo := range s.spec.Repos { - if repo == nil || len(repo.Name) == 0 { + if repo == nil || len(repo.RepoName) == 0 { continue } - if repo.Source == step.ProviderGerrit { + if repo.Source == types.ProviderGerrit { userpass, _ := base64.StdEncoding.DecodeString(repo.OauthToken) userpassPair := strings.Split(string(userpass), ":") var user, password string @@ -122,14 +122,14 @@ func (s *GitStep) runGitCmds() error { password = userpassPair[1] } user = userpassPair[0] - repo.User = user + repo.Username = user if password != "" { repo.Password = password tokens = append(tokens, repo.Password) } - } else if repo.Source == step.ProviderCodehub { + } else if repo.Source == types.ProviderCodehub { tokens = append(tokens, repo.Password) - } else if repo.Source == step.ProviderOther { + } else if repo.Source == types.ProviderOther { tokens = append(tokens, repo.PrivateAccessToken) tokens = append(tokens, repo.SSHKey) } @@ -170,7 +170,7 @@ func (s *GitStep) runGitCmds() error { c.Cmd.Env = envs if !c.DisableTrace { - log.Infof("%s", strings.Join(c.Cmd.Args, " ")) + fmt.Printf("%s\n", strings.Join(c.Cmd.Args, " ")) } if err := c.Cmd.Run(); err != nil { if c.IgnoreError { @@ -182,15 +182,15 @@ func (s *GitStep) runGitCmds() error { return nil } -func (s *GitStep) buildGitCommands(repo *step.Repo, hostNames sets.String) []*c.Command { +func (s *GitStep) buildGitCommands(repo *types.Repository, hostNames sets.String) []*c.Command { cmds := make([]*c.Command, 0) - if len(repo.Name) == 0 { + if len(repo.RepoName) == 0 { return cmds } - workDir := filepath.Join(s.workspace, repo.Name) + workDir := filepath.Join(s.workspace, repo.RepoName) if len(repo.CheckoutPath) != 0 { workDir = filepath.Join(s.workspace, repo.CheckoutPath) } @@ -216,35 +216,35 @@ func (s *GitStep) buildGitCommands(repo *step.Repo, hostNames sets.String) []*c. } // namespace represents the real owner - owner := repo.Namespace + owner := repo.RepoNamespace if len(owner) == 0 { - owner = repo.Owner + owner = repo.RepoOwner } - if repo.Source == step.ProviderGitlab { + if repo.Source == types.ProviderGitlab { u, _ := url.Parse(repo.Address) cmds = append(cmds, &c.Command{ - Cmd: c.RemoteAdd(repo.RemoteName, OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, owner, repo.Name, u.Scheme)), + Cmd: c.RemoteAdd(repo.RemoteName, OAuthCloneURL(repo.Source, repo.OauthToken, u.Host, owner, repo.RepoName, u.Scheme)), DisableTrace: true, }) - } else if repo.Source == step.ProviderGerrit { + } else if repo.Source == types.ProviderGerrit { u, _ := url.Parse(repo.Address) - u.Path = fmt.Sprintf("/a/%s", repo.Name) - u.User = url.UserPassword(repo.User, repo.Password) + u.Path = fmt.Sprintf("/a/%s", repo.RepoName) + u.User = url.UserPassword(repo.Username, repo.Password) cmds = append(cmds, &c.Command{ Cmd: c.RemoteAdd(repo.RemoteName, u.String()), DisableTrace: true, }) - } else if repo.Source == step.ProviderCodehub { + } else if repo.Source == types.ProviderCodehub { u, _ := url.Parse(repo.Address) - user := url.QueryEscape(repo.User) + user := url.QueryEscape(repo.Username) cmds = append(cmds, &c.Command{ - Cmd: c.RemoteAdd(repo.RemoteName, fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", u.Scheme, user, repo.Password, u.Host, owner, repo.Name)), + Cmd: c.RemoteAdd(repo.RemoteName, fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", u.Scheme, user, repo.Password, u.Host, owner, repo.RepoName)), DisableTrace: true, }) - } else if repo.Source == step.ProviderGitee { - cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, HTTPSCloneURL(repo.Source, repo.OauthToken, repo.Owner, repo.Name)), DisableTrace: true}) - } else if repo.Source == step.ProviderOther { + } else if repo.Source == types.ProviderGitee { + cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, HTTPSCloneURL(repo.Source, repo.OauthToken, repo.RepoOwner, repo.RepoName)), DisableTrace: true}) + } else if repo.Source == types.ProviderOther { if repo.AuthType == types.SSHAuthType { host := getHost(repo.Address) if !hostNames.Has(host) { @@ -253,10 +253,10 @@ func (s *GitStep) buildGitCommands(repo *step.Repo, hostNames sets.String) []*c. } hostNames.Insert(host) } - remoteName := fmt.Sprintf("%s:%s/%s.git", repo.Address, repo.Owner, repo.Name) + remoteName := fmt.Sprintf("%s:%s/%s.git", repo.Address, repo.RepoOwner, repo.RepoName) // Including the case of the port if strings.Contains(repo.Address, ":") { - remoteName = fmt.Sprintf("%s/%s/%s.git", repo.Address, repo.Owner, repo.Name) + remoteName = fmt.Sprintf("%s/%s/%s.git", repo.Address, repo.RepoOwner, repo.RepoName) } cmds = append(cmds, &c.Command{ Cmd: c.RemoteAdd(repo.RemoteName, remoteName), @@ -268,14 +268,14 @@ func (s *GitStep) buildGitCommands(repo *step.Repo, hostNames sets.String) []*c. log.Errorf("failed to parse url,err:%s", err) } else { cmds = append(cmds, &c.Command{ - Cmd: c.RemoteAdd(repo.RemoteName, OAuthCloneURL(repo.Source, repo.PrivateAccessToken, u.Host, repo.Owner, repo.Name, u.Scheme)), + Cmd: c.RemoteAdd(repo.RemoteName, OAuthCloneURL(repo.Source, repo.PrivateAccessToken, u.Host, repo.RepoOwner, repo.RepoName, u.Scheme)), DisableTrace: true, }) } } } else { // github - cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, HTTPSCloneURL(repo.Source, repo.OauthToken, owner, repo.Name)), DisableTrace: true}) + cmds = append(cmds, &c.Command{Cmd: c.RemoteAdd(repo.RemoteName, HTTPSCloneURL(repo.Source, repo.OauthToken, owner, repo.RepoName)), DisableTrace: true}) } ref := repo.Ref() @@ -352,7 +352,7 @@ func getHost(address string) string { // e.g. //https://oauth2:ACCESS_TOKEN@somegitlab.com/owner/name.git func OAuthCloneURL(source, token, address, owner, name, scheme string) string { - if strings.ToLower(source) == step.ProviderGitlab || strings.ToLower(source) == step.ProviderOther { + if strings.ToLower(source) == types.ProviderGitlab || strings.ToLower(source) == types.ProviderOther { // address 需要传过来 return fmt.Sprintf("%s://%s:%s@%s/%s/%s.git", scheme, step.OauthTokenPrefix, token, address, owner, name) } @@ -362,9 +362,9 @@ func OAuthCloneURL(source, token, address, owner, name, scheme string) string { // HTTPSCloneURL returns HTTPS clone url func HTTPSCloneURL(source, token, owner, name string) string { - if strings.ToLower(source) == step.ProviderGitlab { + if strings.ToLower(source) == types.ProviderGitlab { return fmt.Sprintf("https://%s/%s/%s.git", "gitlab.koderover.com", owner, name) - } else if strings.ToLower(source) == step.ProviderGitee { + } else if strings.ToLower(source) == types.ProviderGitee { return fmt.Sprintf("https://%s:%s@%s/%s/%s.git", step.OauthTokenPrefix, token, "gitee.com", owner, name) } //return fmt.Sprintf("https://x-access-token:%s@%s/%s/%s.git", g.GetInstallationToken(owner), g.GetGithubHost(), owner, name) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go index 068c846c..d060afda 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go @@ -15,7 +15,6 @@ import ( "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" - "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/types/step" "gopkg.in/yaml.v3" @@ -95,7 +94,7 @@ func (s *ToolInstallStep) runIntallationScripts() error { tmpPath, objectKey, ) - log.Infof("Package loaded from url: %s", s.spec.Download) + fmt.Printf("Package loaded from url: %s\n", s.spec.Download) } } else { err := httpclient.Download(s.spec.Download, tmpPath) diff --git a/pkg/microservice/jobexecutor/executor/executor.go b/pkg/microservice/jobexecutor/executor/executor.go index be449865..f9804737 100644 --- a/pkg/microservice/jobexecutor/executor/executor.go +++ b/pkg/microservice/jobexecutor/executor/executor.go @@ -18,6 +18,7 @@ package executor import ( "context" + "fmt" "os" "time" @@ -28,29 +29,45 @@ import ( "github.com/koderover/zadig/pkg/types" ) +const ( + ZadigContextDir = "/zadig/" + ZadigLogFile = ZadigContextDir + "zadig.log" + ZadigLifeCycleFile = ZadigContextDir + "lifecycle" +) + func Execute(ctx context.Context) error { + f, err := os.OpenFile(ZadigLogFile, os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0755) + if err != nil { + fmt.Println("open log file error") + return err + } + os.Stdout = f + os.Stderr = f + log.Init(&log.Config{ Level: commonconfig.LogLevel(), NoCaller: true, NoLogLevel: true, Development: commonconfig.Mode() != setting.ReleaseMode, + SendToFile: true, + Filename: ZadigLogFile, }) start := time.Now() excutor := "job-excutor" - var err error defer func() { + os.Remove(ZadigLifeCycleFile) resultMsg := types.JobSuccess if err != nil { resultMsg = types.JobFail - log.Errorf("Failed to run: %s.", err) - log.Infof("====================== %s End. Duration: %.2f seconds ======================", excutor, time.Since(start).Seconds()) + fmt.Printf("Failed to run: %s.\n", err) + fmt.Printf("====================== %s End. Duration: %.2f seconds ======================\n", excutor, time.Since(start).Seconds()) os.Exit(1) } - log.Infof("Job Status: %s", resultMsg) + fmt.Printf("Job Status: %s\n", resultMsg) - log.Infof("====================== %s End. Duration: %.2f seconds ======================", excutor, time.Since(start).Seconds()) + fmt.Printf("====================== %s End. Duration: %.2f seconds ======================\n", excutor, time.Since(start).Seconds()) }() var j *job.Job @@ -58,7 +75,7 @@ func Execute(ctx context.Context) error { if err != nil { return err } - log.Infof("====================== %s Start ======================", excutor) + fmt.Printf("====================== %s Start ======================\n", excutor) if err = j.Run(ctx); err != nil { return err } -- Gitee From c091f48f5e0609df367030b7d8c0dd41c897e499 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Mon, 27 Jun 2022 17:09:32 +0800 Subject: [PATCH 2486/2647] update EphemeralContainer ready status (#1717) Signed-off-by: panxunying Co-authored-by: panxunying --- pkg/shared/kube/wrapper/pod.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/shared/kube/wrapper/pod.go b/pkg/shared/kube/wrapper/pod.go index 9e51916d..03b85f93 100644 --- a/pkg/shared/kube/wrapper/pod.go +++ b/pkg/shared/kube/wrapper/pod.go @@ -145,7 +145,10 @@ func (w *pod) Resource() *resource.Pod { containersStatus := []corev1.ContainerStatus{} containersStatus = append(containersStatus, w.Status.ContainerStatuses...) if CheckEphemeralContainerStatusFieldExist(&w.Status) { - containersStatus = append(containersStatus, w.Status.EphemeralContainerStatuses...) + for _, status := range w.Status.EphemeralContainerStatuses { + status.Ready = true + containersStatus = append(containersStatus, status) + } } for _, container := range containersStatus { @@ -210,8 +213,8 @@ func (w *pod) Resource() *resource.Pod { Image: container.Image, RestartCount: 0, Status: "running", + Ready: true, } - p.ContainerStatuses = append(p.ContainerStatuses, cs) } } -- Gitee From 4ea0c06cd72d1e372942321b568d6c1c2b9b6fc6 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 28 Jun 2022 10:00:54 +0800 Subject: [PATCH 2487/2647] fix render query condition not accurate bug (#1718) * fix render query condition not inaccurate bug Signed-off-by: allenshen * update query logic Signed-off-by: allenshen * add query conditions Signed-off-by: allenshen --- .../service/collaboration_instance.go | 6 +- .../common/repository/mongodb/render_set.go | 8 ++ .../aslan/core/common/service/delivery.go | 9 ++- .../aslan/core/common/service/environment.go | 6 +- .../aslan/core/common/service/render.go | 31 ++++--- .../core/common/service/template_product.go | 2 +- .../aslan/core/delivery/service/version.go | 6 +- .../core/environment/service/configmap.go | 2 +- .../aslan/core/environment/service/diff.go | 4 +- .../core/environment/service/environment.go | 81 ++++++++++++++----- .../service/environment_creator.go | 8 +- .../environment/service/environment_group.go | 2 +- .../environment/service/environment_update.go | 12 ++- .../aslan/core/environment/service/helm.go | 2 +- .../aslan/core/environment/service/image.go | 7 +- .../aslan/core/environment/service/k8s.go | 2 +- .../core/environment/service/renderset.go | 6 +- .../core/environment/service/revision.go | 2 +- .../aslan/core/environment/service/service.go | 14 +++- .../aslan/core/service/service/helm.go | 2 +- .../aslan/core/service/service/service.go | 8 +- .../service/webhook/gitlab_workflow_task.go | 2 +- 22 files changed, 159 insertions(+), 63 deletions(-) diff --git a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go index 617d43c5..20fa0859 100644 --- a/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go +++ b/pkg/microservice/aslan/core/collaboration/service/collaboration_instance.go @@ -1343,8 +1343,10 @@ func getRenderSet(projectName string, envs []string) ([]models2.RenderSet, error revision = productService.Render.Revision } findOpts = append(findOpts, commonrepo.RenderSetFindOption{ - Revision: revision, - Name: product.Namespace, + Revision: revision, + ProductTmpl: projectName, + EnvName: product.EnvName, + Name: product.Namespace, }) } renderSets, err := commonrepo.NewRenderSetColl().ListByFindOpts(&commonrepo.RenderSetListOption{ diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go index ed80aa4e..48742f2a 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go @@ -43,6 +43,8 @@ type RenderSetListOption struct { type RenderSetFindOption struct { // if Revision == 0 then search max revision of RenderSet ProductTmpl string + EnvName string + IsDefault bool Revision int64 Name string } @@ -213,6 +215,12 @@ func (c *RenderSetColl) Find(opt *RenderSetFindOption) (*models.RenderSet, error if len(opt.ProductTmpl) > 0 { query["product_tmpl"] = opt.ProductTmpl } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if opt.IsDefault { + query["is_default"] = opt.IsDefault + } rs := &models.RenderSet{} err := c.FindOne(context.TODO(), query, opts).Decode(&rs) diff --git a/pkg/microservice/aslan/core/common/service/delivery.go b/pkg/microservice/aslan/core/common/service/delivery.go index eeb4aa2d..19ddb029 100644 --- a/pkg/microservice/aslan/core/common/service/delivery.go +++ b/pkg/microservice/aslan/core/common/service/delivery.go @@ -374,7 +374,7 @@ func getProductEnvInfo(pipelineTask *taskmodels.Task, log *zap.SugaredLogger) (* product.Namespace = productInfo.Namespace } - if renderSet, err := GetRenderSet(product.Namespace, pipelineTask.Render.Revision, log); err == nil { + if renderSet, err := GetRenderSet(product.Namespace, pipelineTask.Render.Revision, false, product.Namespace, log); err == nil { product.Vars = renderSet.KVs } else { log.Errorf("GetProductEnvInfo GetRenderSet namespace:%s pipelineTask.Render.Revision:%d err:%v", product.GetNamespace(), pipelineTask.Render.Revision, err) @@ -450,7 +450,12 @@ func updateServiceImage(serviceName, image, containerName string, product *commo func getServiceRenderYAML(productInfo *commonmodels.Product, containers []*commonmodels.Container, serviceName, deployType string, log *zap.SugaredLogger) (string, error) { if deployType == setting.K8SDeployType { - opt := &commonrepo.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} + opt := &commonrepo.RenderSetFindOption{ + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, + EnvName: productInfo.EnvName, + ProductTmpl: productInfo.ProductName, + } newRender, err := commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("[%s][P:%s]renderset Find error: %v", productInfo.EnvName, productInfo.ProductName, err) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index bd8e2011..471e7080 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -49,7 +49,7 @@ import ( // FillProductTemplateValuesYamls 返回renderSet中的renderChart信息 func FillProductTemplateValuesYamls(tmpl *templatemodels.Product, log *zap.SugaredLogger) error { - renderSet, err := GetRenderSet(tmpl.ProductName, 0, log) + renderSet, err := GetRenderSet(tmpl.ProductName, 0, true, "", log) if err != nil { log.Errorf("Failed to find render set for product template %s", tmpl.ProductName) return err @@ -130,7 +130,9 @@ func GetRenderCharts(productName, envName, serviceName string, log *zap.SugaredL renderSetName := GetProductEnvNamespace(envName, productName, "") opt := &commonrepo.RenderSetFindOption{ - Name: renderSetName, + ProductTmpl: productName, + EnvName: envName, + Name: renderSetName, } rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/render.go b/pkg/microservice/aslan/core/common/service/render.go index 2635997a..3b4c2f9a 100644 --- a/pkg/microservice/aslan/core/common/service/render.go +++ b/pkg/microservice/aslan/core/common/service/render.go @@ -194,14 +194,16 @@ func listTmplRenderKeys(productTmplName string, log *zap.SugaredLogger) ([]*temp return kvs, prodTmpl.AllServiceInfoMap(), err } -func GetRenderSet(renderName string, revision int64, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { +func GetRenderSet(renderName string, revision int64, isDefault bool, envName string, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { // 未指定renderName返回空的renderSet if renderName == "" { return &commonmodels.RenderSet{KVs: []*templatemodels.RenderKV{}}, nil } opt := &commonrepo.RenderSetFindOption{ - Name: renderName, - Revision: revision, + Name: renderName, + Revision: revision, + IsDefault: isDefault, + EnvName: envName, } resp, found, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { @@ -231,11 +233,11 @@ func GetRenderSetInfo(renderName string, revision int64) (*commonmodels.RenderSe } // ValidateRenderSet 检查指定renderSet是否能覆盖产品所有需要渲染的值 -func ValidateRenderSet(productName, renderName string, serviceInfo *templatemodels.ServiceInfo, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { +func ValidateRenderSet(productName, renderName, envName string, serviceInfo *templatemodels.ServiceInfo, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { resp := &commonmodels.RenderSet{ProductTmpl: productName} var err error if renderName != "" { - opt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName} + opt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName, EnvName: envName} resp, err = commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("find renderset[%s] error: %v", renderName, err) @@ -281,7 +283,7 @@ func mergeKVs(newKVs []*templatemodels.RenderKV, oldKVs []*templatemodels.Render } func CreateRenderSetByMerge(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { - opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} + opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl, EnvName: args.EnvName} rs, err := commonrepo.NewRenderSetColl().Find(opt) if rs != nil && err == nil { if rs.Diff(args) { @@ -305,7 +307,11 @@ func CreateRenderSetByMerge(args *commonmodels.RenderSet, log *zap.SugaredLogger } func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { - opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} + opt := &commonrepo.RenderSetFindOption{ + Name: args.Name, + ProductTmpl: args.ProductTmpl, + EnvName: args.EnvName, + } rs, err := commonrepo.NewRenderSetColl().Find(opt) if rs != nil && err == nil { if rs.Diff(args) { @@ -328,7 +334,11 @@ func CreateRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error // CreateHelmRenderSet 添加renderSet func CreateHelmRenderSet(args *commonmodels.RenderSet, log *zap.SugaredLogger) error { - opt := &commonrepo.RenderSetFindOption{Name: args.Name, ProductTmpl: args.ProductTmpl} + opt := &commonrepo.RenderSetFindOption{ + Name: args.Name, + ProductTmpl: args.ProductTmpl, + EnvName: args.EnvName, + } rs, err := commonrepo.NewRenderSetColl().Find(opt) if rs != nil && err == nil { // 已经存在渲染配置集 @@ -568,8 +578,9 @@ func getValueFromSharedRenderSet(kv *templatemodels.RenderKV, productName string } renderSetOpt := &commonrepo.RenderSetFindOption{ - Name: targetProduct, - Revision: 0, + Name: targetProduct, + IsDefault: true, + Revision: 0, } renderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) if err != nil { diff --git a/pkg/microservice/aslan/core/common/service/template_product.go b/pkg/microservice/aslan/core/common/service/template_product.go index df2446df..5546a651 100644 --- a/pkg/microservice/aslan/core/common/service/template_product.go +++ b/pkg/microservice/aslan/core/common/service/template_product.go @@ -188,7 +188,7 @@ func FillProductTemplateVars(productTemplates []*template.Product, log *zap.Suga <-ch wg.Done() }() - renderSet, err := GetRenderSet(tmpl.ProductName, 0, log) + renderSet, err := GetRenderSet(tmpl.ProductName, 0, true, "", log) if err != nil { errStr += fmt.Sprintf("Failed to find render set for product template, productName:%s, err:%v\n", tmpl.ProductName, err) log.Errorf("Failed to find render set for product template %s", tmpl.ProductName) diff --git a/pkg/microservice/aslan/core/delivery/service/version.go b/pkg/microservice/aslan/core/delivery/service/version.go index 52750a77..1c8f97b7 100644 --- a/pkg/microservice/aslan/core/delivery/service/version.go +++ b/pkg/microservice/aslan/core/delivery/service/version.go @@ -793,8 +793,10 @@ func CreateHelmDeliveryVersion(args *CreateHelmDeliveryVersionArgs, logger *zap. func prepareChartData(chartDatas []*CreateHelmDeliveryVersionChartData, productInfo *commonmodels.Product) (map[string]*DeliveryChartData, error) { renderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Revision: productInfo.Render.Revision, - Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, + Name: productInfo.Render.Name, + EnvName: productInfo.EnvName, + ProductTmpl: productInfo.ProductName, }) if err != nil { return nil, fmt.Errorf("failed to find renderSet: %s, revision: %d", productInfo.Render.Name, productInfo.Render.Revision) diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 12e40a2b..298509d6 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -225,7 +225,7 @@ func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log } namespace := product.Namespace - renderSet, err := commonservice.GetRenderSet(namespace, 0, log) + renderSet, err := commonservice.GetRenderSet(namespace, 0, false, product.EnvName, log) if err != nil { log.Errorf("Failed to find render set for product template %s, err: %v", product.ProductName, err) return err diff --git a/pkg/microservice/aslan/core/environment/service/diff.go b/pkg/microservice/aslan/core/environment/service/diff.go index 0c493023..b1f87f6a 100644 --- a/pkg/microservice/aslan/core/environment/service/diff.go +++ b/pkg/microservice/aslan/core/environment/service/diff.go @@ -88,11 +88,11 @@ func GetServiceDiff(envName, productName, serviceName string, log *zap.SugaredLo oldRender := &commonmodels.RenderSet{} newRender := &commonmodels.RenderSet{} if productInfo.Render != nil { - oldRender, err = commonservice.GetRenderSet(productInfo.Render.Name, productInfo.Render.Revision, log) + oldRender, err = commonservice.GetRenderSet(productInfo.Render.Name, productInfo.Render.Revision, false, envName, log) if err != nil { return resp, err } - newRender, err = commonservice.GetRenderSet(productInfo.Render.Name, 0, log) + newRender, err = commonservice.GetRenderSet(productInfo.Render.Name, 0, false, envName, log) if err != nil { return resp, err } diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 9406519a..24e03d57 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -291,7 +291,7 @@ func FillProductVars(products []*commonmodels.Product, log *zap.SugaredLogger) e renderName = product.Render.Name revision = product.Render.Revision } - renderSet, err := commonservice.GetRenderSet(renderName, revision, log) + renderSet, err := commonservice.GetRenderSet(renderName, revision, false, product.EnvName, log) if err != nil { log.Errorf("Failed to find render set, productName: %s, namespace: %s, err: %s", product.ProductName, product.Namespace, err) return e.ErrGetRenderSet.AddDesc(err.Error()) @@ -796,7 +796,7 @@ func UpdateProductV2(envName, productName, user, requestID string, serviceNames } // 检查renderset是否覆盖产品所有key - renderSet, err := commonservice.ValidateRenderSet(exitedProd.ProductName, exitedProd.Render.Name, nil, log) + renderSet, err := commonservice.ValidateRenderSet(exitedProd.ProductName, exitedProd.Render.Name, exitedProd.EnvName, nil, log) if err != nil { log.Errorf("[%s][P:%s] validate product renderset error: %v", envName, exitedProd.ProductName, err) return e.ErrUpdateEnv.AddDesc(err.Error()) @@ -1195,7 +1195,11 @@ func copySingleHelmProduct(templateProduct *templatemodels.Product, productName, productInfo.EnvConfigYamls = arg.EnvConfigYamls // merge chart infos, use chart info in product to override charts in template_project - sourceRenderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{Name: sourceRendersetName}) + sourceRenderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ + Name: sourceRendersetName, + EnvName: arg.BaseName, + ProductTmpl: arg.ProductName, + }) if err != nil { return fmt.Errorf("failed to find source renderset: %s, err: %s", productInfo.Namespace, err) } @@ -1403,7 +1407,12 @@ func prepareEstimatedData(productName, envName, serviceName, usageScenario, defa } // find chart info from cur render set - opt := &commonrepo.RenderSetFindOption{Name: productInfo.Render.Name, Revision: productInfo.Render.Revision} + opt := &commonrepo.RenderSetFindOption{ + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, + EnvName: productInfo.EnvName, + ProductTmpl: productInfo.ProductName, + } renderSet, err := commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("renderset Find error, productName:%s, envName:%s, err:%s", productInfo.ProductName, productInfo.EnvName, err) @@ -1524,7 +1533,11 @@ func UpdateHelmProductDefaultValues(productName, envName, userName, requestID st log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) return err } - opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + opt := &commonrepo.RenderSetFindOption{ + Name: product.Namespace, + EnvName: envName, + ProductTmpl: productName, + } productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { @@ -1573,7 +1586,11 @@ func UpdateHelmProductCharts(productName, envName, userName, requestID string, a log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) return err } - opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + opt := &commonrepo.RenderSetFindOption{ + Name: product.Namespace, + EnvName: envName, + ProductTmpl: productName, + } productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { @@ -1665,7 +1682,11 @@ func SyncHelmProductEnvironment(productName, envName, requestID string, log *zap log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) return err } - opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + opt := &commonrepo.RenderSetFindOption{ + Name: product.Namespace, + EnvName: envName, + ProductTmpl: productName, + } productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { @@ -1725,7 +1746,11 @@ func UpdateHelmProductRenderset(productName, envName, userName, requestID string log.Errorf("UpdateHelmProductRenderset GetProductEnv envName:%s productName: %s error, error msg:%s", envName, productName, err) return err } - opt := &commonrepo.RenderSetFindOption{Name: product.Namespace} + opt := &commonrepo.RenderSetFindOption{ + Name: product.Namespace, + EnvName: envName, + ProductTmpl: productName, + } productRenderset, _, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil || productRenderset == nil { if err != nil { @@ -1811,7 +1836,7 @@ func UpdateHelmProductVariable(productName, envName, username, requestID string, if productResp.Render == nil { productResp.Render = &commonmodels.RenderInfo{ProductTmpl: productResp.ProductName} } - renderSet, err := FindHelmRenderSet(productResp.ProductName, productResp.Namespace, log) + renderSet, err := FindHelmRenderSet(productResp.ProductName, productResp.Namespace, envName, log) if err != nil { log.Errorf("[%s][P:%s] find product renderset error: %s", productResp.EnvName, productResp.ProductName, err) return e.ErrUpdateEnv.AddDesc(err.Error()) @@ -1918,7 +1943,9 @@ func UpdateMultipleHelmEnv(requestID, userName string, args *UpdateMultiHelmProd // extract values.yaml and update renderset for envName := range productMap { renderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ - Name: commonservice.GetProductEnvNamespace(envName, productName, ""), + Name: commonservice.GetProductEnvNamespace(envName, productName, ""), + EnvName: envName, + ProductTmpl: productName, }) if err != nil || renderSet == nil { if err != nil { @@ -1962,7 +1989,7 @@ func GetProductInfo(username, envName, productName string, log *zap.SugaredLogge } renderSetName := prod.Namespace - renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: prod.Render.Revision} + renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: prod.Render.Revision, ProductTmpl: productName} renderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) if err != nil { log.Errorf("find helm renderset[%s] error: %v", renderSetName, err) @@ -1993,7 +2020,7 @@ func GetHelmChartVersions(productName, envName string, log *zap.SugaredLogger) ( //当前环境的renderset renderSetName := prod.Namespace - renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: prod.Render.Revision} + renderSetOpt := &commonrepo.RenderSetFindOption{Name: renderSetName, Revision: prod.Render.Revision, ProductTmpl: prod.ProductName} renderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) if err != nil { log.Errorf("find helm renderset[%s] error: %v", renderSetName, err) @@ -2315,7 +2342,9 @@ func deleteHelmProductServices(userName, requestID string, productInfo *commonmo } } renderset, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Name: productInfo.Namespace, + Name: productInfo.Namespace, + EnvName: productInfo.EnvName, + ProductTmpl: productInfo.ProductName, }) if err != nil { log.Errorf("get renderSet error: %v", err) @@ -2400,6 +2429,7 @@ func deleteK8sProductServices(productInfo *commonmodels.Product, serviceNames [] } } rs, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + EnvName: productInfo.EnvName, Name: productInfo.Namespace, ProductTmpl: productInfo.ProductName, }) @@ -2906,7 +2936,12 @@ func removeOldResources( kubeClient client.Client, log *zap.SugaredLogger, ) error { - opt := &commonrepo.RenderSetFindOption{Name: oldService.Render.Name, Revision: oldService.Render.Revision} + opt := &commonrepo.RenderSetFindOption{ + Name: oldService.Render.Name, + Revision: oldService.Render.Revision, + EnvName: env.EnvName, + ProductTmpl: env.ProductName, + } resp, err := commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("find renderset[%s/%d] error: %v", opt.Name, opt.Revision, err) @@ -3198,11 +3233,15 @@ func ensureKubeEnv(namespace, registryId string, enableShare bool, kubeClient cl return nil } -func FindHelmRenderSet(productName, renderName string, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { +func FindHelmRenderSet(productName, renderName, envName string, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { resp := &commonmodels.RenderSet{ProductTmpl: productName} var err error if renderName != "" { - opt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName} + opt := &commonrepo.RenderSetFindOption{ + Name: renderName, + ProductTmpl: productName, + EnvName: envName, + } resp, err = commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("find helm renderset[%s] error: %v", renderName, err) @@ -3483,7 +3522,7 @@ func updateProductGroup(username, productName, envName string, productResp *comm // generate a new renderset and insert into db func diffRenderSet(username, productName, envName string, productResp *commonmodels.Product, overrideCharts []*commonservice.RenderChartArg, log *zap.SugaredLogger) (*commonmodels.RenderSet, error) { // default renderset - latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName}) + latestRenderSet, err := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: productName, IsDefault: true}) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err @@ -3503,7 +3542,11 @@ func diffRenderSet(username, productName, envName string, productResp *commonmod } } - renderSetOpt := &commonrepo.RenderSetFindOption{Name: productResp.Render.Name, Revision: productResp.Render.Revision} + renderSetOpt := &commonrepo.RenderSetFindOption{ + Name: productResp.Render.Name, + Revision: productResp.Render.Revision, + ProductTmpl: productName, + } currentEnvRenderSet, err := commonrepo.NewRenderSetColl().Find(renderSetOpt) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) @@ -3605,7 +3648,7 @@ func diffRenderSet(username, productName, envName string, productResp *commonmod return nil, err } - renderSet, err := FindHelmRenderSet(productName, productResp.Render.Name, log) + renderSet, err := FindHelmRenderSet(productName, productResp.Render.Name, envName, log) if err != nil { log.Errorf("[RenderSet.find] err: %v", err) return nil, err diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index b503fd49..1945442c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -172,7 +172,9 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P var renderSet *models.RenderSet if args.Render == nil || args.Render.Revision == 0 { renderSet, _, err = commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ - Name: args.Namespace, + EnvName: args.EnvName, + Name: args.Namespace, + ProductTmpl: args.ProductName, }) if err != nil { @@ -207,7 +209,7 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } - renderSet, err = FindHelmRenderSet(args.ProductName, args.Render.Name, log) + renderSet, err = FindHelmRenderSet(args.ProductName, args.Render.Name, args.EnvName, log) if err != nil { log.Errorf("[%s][P:%s] find product renderset error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddDesc(err.Error()) @@ -370,7 +372,7 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model // 如果是版本回滚,则args.Render.Revision > 0 if args.Render.Revision == 0 { // 检查renderset是否覆盖产品所有key - renderSet, err = commonservice.ValidateRenderSet(args.ProductName, args.Render.Name, nil, log) + renderSet, err = commonservice.ValidateRenderSet(args.ProductName, args.Render.Name, args.EnvName, nil, log) if err != nil { log.Errorf("[%s][P:%s] validate product renderset error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/environment_group.go b/pkg/microservice/aslan/core/environment/service/environment_group.go index 1771fc33..5809d02d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_group.go +++ b/pkg/microservice/aslan/core/environment/service/environment_group.go @@ -145,7 +145,7 @@ func GetIngressInfo(product *commonmodels.Product, service *commonmodels.Service renderSet := &commonmodels.RenderSet{} if product.Render != nil { - renderSet, err = commonservice.GetRenderSet(product.Render.Name, 0, log) + renderSet, err = commonservice.GetRenderSet(product.Render.Name, 0, false, product.EnvName, log) if err != nil { log.Errorf("GetRenderSet %s error: %v", product.ProductName, err) return ingressInfo diff --git a/pkg/microservice/aslan/core/environment/service/environment_update.go b/pkg/microservice/aslan/core/environment/service/environment_update.go index 9a19245b..b0a0bff4 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_update.go +++ b/pkg/microservice/aslan/core/environment/service/environment_update.go @@ -142,8 +142,10 @@ func reInstallHelmServiceInEnv(productInfo *commonmodels.Product, templateSvc *c }() renderInfo, errRender := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Name: productInfo.Render.Name, - Revision: productInfo.Render.Revision}) + ProductTmpl: productInfo.ProductName, + EnvName: productInfo.EnvName, + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision}) if errRender != nil { err = fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, errRender) return @@ -210,8 +212,10 @@ func updateHelmServiceInEnv(userName string, productInfo *commonmodels.Product, }() renderInfo, errFindRender := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ - Name: productInfo.Render.Name, - Revision: productInfo.Render.Revision}) + ProductTmpl: productInfo.ProductName, + EnvName: productInfo.EnvName, + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision}) if errFindRender != nil { err = fmt.Errorf("failed to find renderset, %s:%d, err: %s", productInfo.Render.Name, productInfo.Render.Revision, errFindRender) return diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index b6e3617f..557a0e7c 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -434,7 +434,7 @@ func GetChartInfos(productName, envName, serviceName string, log *zap.SugaredLog if err != nil { return nil, e.ErrGetHelmCharts.AddErr(err) } - renderSet, err := FindHelmRenderSet(productName, prod.Render.Name, log) + renderSet, err := FindHelmRenderSet(productName, prod.Render.Name, prod.EnvName, log) if err != nil { log.Errorf("[%s][P:%s] find product renderset error: %v", envName, productName, err) return nil, e.ErrGetHelmCharts.AddErr(err) diff --git a/pkg/microservice/aslan/core/environment/service/image.go b/pkg/microservice/aslan/core/environment/service/image.go index 296bcb3c..e623af0b 100644 --- a/pkg/microservice/aslan/core/environment/service/image.go +++ b/pkg/microservice/aslan/core/environment/service/image.go @@ -87,7 +87,12 @@ func prepareData(namespace, serviceName, containerName, image string, product *m return } - renderSet, err = commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: namespace, Revision: product.Render.Revision}) + renderSet, err = commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{ + ProductTmpl: product.ProductName, + Name: namespace, + EnvName: product.EnvName, + Revision: product.Render.Revision, + }) if err != nil { log.Errorf("[RenderSet.find] update product %s error: %s", product.ProductName, err.Error()) err = fmt.Errorf("failed to find redset name %s revision %d", namespace, product.Render.Revision) diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index bef8587b..565b8115 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -121,7 +121,7 @@ func (k *K8sService) updateService(args *SvcOptArgs) error { exitedProd.Render = &commonmodels.RenderInfo{ProductTmpl: exitedProd.ProductName} } // 检查renderset是否覆盖服务所有key - newRender, err := commonservice.ValidateRenderSet(args.ProductName, exitedProd.Render.Name, serviceInfo, k.log) + newRender, err := commonservice.ValidateRenderSet(args.ProductName, exitedProd.Render.Name, exitedProd.EnvName, serviceInfo, k.log) if err != nil { k.log.Errorf("[%s][P:%s] validate product renderset error: %v", args.EnvName, args.ProductName, err) return e.ErrUpdateProduct.AddDesc(err.Error()) diff --git a/pkg/microservice/aslan/core/environment/service/renderset.go b/pkg/microservice/aslan/core/environment/service/renderset.go index 6a26d09f..d97ca948 100644 --- a/pkg/microservice/aslan/core/environment/service/renderset.go +++ b/pkg/microservice/aslan/core/environment/service/renderset.go @@ -116,8 +116,10 @@ func GetDefaultValues(productName, envName string, log *zap.SugaredLogger) (*Def } opt := &commonrepo.RenderSetFindOption{ - Name: productInfo.Render.Name, - Revision: productInfo.Render.Revision, + Name: productInfo.Render.Name, + Revision: productInfo.Render.Revision, + ProductTmpl: productName, + EnvName: productInfo.EnvName, } rendersetObj, existed, err := commonrepo.NewRenderSetColl().FindRenderSet(opt) if err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/revision.go b/pkg/microservice/aslan/core/environment/service/revision.go index 5afbaa28..33fb130a 100644 --- a/pkg/microservice/aslan/core/environment/service/revision.go +++ b/pkg/microservice/aslan/core/environment/service/revision.go @@ -135,7 +135,7 @@ func GetProductRevision(product *commonmodels.Product, allServiceTmpls []*common rendersetName := "" if product.Render != nil { rendersetName = product.Render.Name - newRender, err = commonservice.GetRenderSet(product.Render.Name, 0, log) + newRender, err = commonservice.GetRenderSet(product.Render.Name, 0, false, product.EnvName, log) if err != nil { return prodRev, err } diff --git a/pkg/microservice/aslan/core/environment/service/service.go b/pkg/microservice/aslan/core/environment/service/service.go index e406032c..01eb20b4 100644 --- a/pkg/microservice/aslan/core/environment/service/service.go +++ b/pkg/microservice/aslan/core/environment/service/service.go @@ -283,7 +283,12 @@ func GetService(envName, productName, serviceName string, workLoadType string, l ) } - renderSetFindOpt := &commonrepo.RenderSetFindOption{Name: env.Render.Name, Revision: service.Render.Revision} + renderSetFindOpt := &commonrepo.RenderSetFindOption{ + Name: env.Render.Name, + Revision: service.Render.Revision, + ProductTmpl: env.ProductName, + EnvName: envName, + } rs, err := commonrepo.NewRenderSetColl().Find(renderSetFindOpt) if err != nil { log.Errorf("find renderset[%s] error: %v", service.Render.Name, err) @@ -477,7 +482,12 @@ func RestartService(envName string, args *SvcOptArgs, log *zap.SugaredLogger) (e err = e.ErrDeleteProduct.AddDesc(e.DeleteServiceContainerErrMsg + ": " + err.Error()) return } - opt := &commonrepo.RenderSetFindOption{Name: serviceObj.Render.Name, Revision: serviceObj.Render.Revision} + opt := &commonrepo.RenderSetFindOption{ + Name: serviceObj.Render.Name, + Revision: serviceObj.Render.Revision, + ProductTmpl: productObj.ProductName, + EnvName: productObj.EnvName, + } newRender, err = commonrepo.NewRenderSetColl().Find(opt) if err != nil { log.Errorf("[%s][P:%s]renderset Find error: %v", productObj.EnvName, productObj.ProductName, err) diff --git a/pkg/microservice/aslan/core/service/service/helm.go b/pkg/microservice/aslan/core/service/service/helm.go index c88f020c..d500707c 100644 --- a/pkg/microservice/aslan/core/service/service/helm.go +++ b/pkg/microservice/aslan/core/service/service/helm.go @@ -1623,7 +1623,7 @@ func EditHelmService(args *HelmServiceArgs, log *zap.SugaredLogger) error { func compareHelmVariable(chartInfos []*templatemodels.RenderChart, productName, createdBy string, log *zap.SugaredLogger) { // 对比上个版本的renderset,新增一个版本 latestChartInfos := make([]*templatemodels.RenderChart, 0) - renderOpt := &commonrepo.RenderSetFindOption{Name: productName} + renderOpt := &commonrepo.RenderSetFindOption{Name: productName, ProductTmpl: productName, IsDefault: true} if latestDefaultRenderSet, err := commonrepo.NewRenderSetColl().Find(renderOpt); err == nil { latestChartInfos = latestDefaultRenderSet.ChartInfos } diff --git a/pkg/microservice/aslan/core/service/service/service.go b/pkg/microservice/aslan/core/service/service/service.go index 7dc2f745..96b6970d 100644 --- a/pkg/microservice/aslan/core/service/service/service.go +++ b/pkg/microservice/aslan/core/service/service/service.go @@ -1167,7 +1167,7 @@ func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, if serviceType == setting.HelmDeployType { // 更新helm renderset - err = removeServiceFromRenderset(productName, productName, serviceName) + err = removeServiceFromRenderset(productName, productName, "", serviceName) if err != nil { log.Warnf("failed to update renderset: %s when deleting service: %s, err: %s", productName, serviceName, err.Error()) } @@ -1201,7 +1201,7 @@ func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, envNames := []string{"dev", "qa"} for _, envName := range envNames { rendersetName := commonservice.GetProductEnvNamespace(envName, productName, "") - err := removeServiceFromRenderset(productName, rendersetName, serviceName) + err := removeServiceFromRenderset(productName, rendersetName, envName, serviceName) if err != nil { log.Warnf("failed to update renderset: %s when deleting service: %s, err: %s", rendersetName, serviceName, err.Error()) } @@ -1215,8 +1215,8 @@ func DeleteServiceTemplate(serviceName, serviceType, productName, isEnvTemplate, } // remove specific services from rendersets.chartinfos -func removeServiceFromRenderset(productName, renderName, serviceName string) error { - renderOpt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName} +func removeServiceFromRenderset(productName, renderName, envName, serviceName string) error { + renderOpt := &commonrepo.RenderSetFindOption{Name: renderName, ProductTmpl: productName, EnvName: envName} if rs, err := commonrepo.NewRenderSetColl().Find(renderOpt); err == nil { chartInfos := make([]*templatemodels.RenderChart, 0) for _, chartInfo := range rs.ChartInfos { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go index cd3ff3ed..12c9b526 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_workflow_task.go @@ -707,7 +707,7 @@ func CreateEnvAndTaskByPR(workflowArgs *commonmodels.WorkflowTaskArgs, prID int, mutex.Unlock() }() if baseProduct.Render != nil { - if renderSet, _ := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: baseProduct.Render.Name, Revision: baseProduct.Render.Revision}); renderSet != nil { + if renderSet, _ := commonrepo.NewRenderSetColl().Find(&commonrepo.RenderSetFindOption{Name: baseProduct.Render.Name, Revision: baseProduct.Render.Revision, ProductTmpl: baseProduct.ProductName}); renderSet != nil { baseProduct.Vars = renderSet.KVs } } -- Gitee From 7858fd1ce4573ca0556d0b707f900b0cc762bf6a Mon Sep 17 00:00:00 2001 From: flyer Date: Tue, 28 Jun 2022 11:58:24 +0800 Subject: [PATCH 2488/2647] support dind persistent cache & clean up dind cache in attached clusters (#1716) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- .../common/repository/models/k8s_cluster.go | 18 +- .../common/repository/mongodb/k8s_cluster.go | 18 ++ .../aslan/core/common/service/kube/service.go | 147 ++++++---- .../aslan/core/multicluster/handler/cache.go | 4 +- .../aslan/core/multicluster/service/cache.go | 8 +- .../core/multicluster/service/clusters.go | 270 +++++++++++++++++- .../aslan/core/system/service/system_cache.go | 145 +++++++--- pkg/types/cache.go | 6 + pkg/types/workload.go | 13 + 9 files changed, 520 insertions(+), 109 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go index 1b0c3dd4..5339b5c3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/models/k8s_cluster.go @@ -83,8 +83,22 @@ type Resources struct { } type DindCfg struct { - Replicas int `json:"replicas" bson:"replicas"` - Resources *Resources `json:"resources" bson:"resources"` + Replicas int `json:"replicas" bson:"replicas"` + Resources *Resources `json:"resources" bson:"resources"` + Storage *DindStorage `json:"storage" bson:"storage"` +} + +type DindStorageType string + +const ( + DindStorageRootfs DindStorageType = "rootfs" + DindStorageDynamic DindStorageType = "dynamic" +) + +type DindStorage struct { + Type DindStorageType `json:"type" bson:"type"` + StorageClass string `json:"storage_class" bson:"storage_class"` + StorageSizeInGiB int64 `json:"storage_size_in_gib" bson:"storage_size_in_gib"` } func (K8SCluster) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go index 85e31388..b092550f 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/k8s_cluster.go @@ -278,3 +278,21 @@ func (c *K8SClusterColl) GetByToken(token string) (*models.K8SCluster, error) { return c.Get(id) } + +func (c *K8SClusterColl) FindActiveClusters() ([]*models.K8SCluster, error) { + connectedClusters, err := c.FindConnectedClusters() + if err != nil { + return nil, err + } + + activeClusters := []*models.K8SCluster{} + for _, cluster := range connectedClusters { + if cluster.Status != setting.Normal { + continue + } + + activeClusters = append(activeClusters, cluster) + } + + return activeClusters, nil +} diff --git a/pkg/microservice/aslan/core/common/service/kube/service.go b/pkg/microservice/aslan/core/common/service/kube/service.go index f0bd158e..6b5e36f6 100644 --- a/pkg/microservice/aslan/core/common/service/kube/service.go +++ b/pkg/microservice/aslan/core/common/service/kube/service.go @@ -104,6 +104,7 @@ func (s *Service) CreateCluster(cluster *models.K8SCluster, id string, logger *z logger.Errorf("failed to create cluster %s %v", cluster.Name, err) return nil, e.ErrCreateCluster.AddDesc(e.DuplicateClusterNameFound) } + cluster.Status = setting.Pending if id == setting.LocalClusterID { cluster.Status = setting.Normal @@ -203,6 +204,9 @@ func (s *Service) GetCluster(id string, logger *zap.SugaredLogger) (*models.K8SC Memory: DefaultDindLimitsMemory, }, }, + Storage: &commonmodels.DindStorage{ + Type: DefaultDindStorageType, + }, } } @@ -267,34 +271,40 @@ func (s *Service) GetYaml(id, agentImage, rsImage, aslanURL, hubURI string, useD return nil, err } - dindReplicas, dindLimitsCPU, dindLimitsMemory := setDindCfg(cluster) + dindReplicas, dindLimitsCPU, dindLimitsMemory, dindEnablePV, dindSCName, dindStorageSizeInGiB := getDindCfg(cluster) if cluster.Namespace == "" { err = YamlTemplate.Execute(buffer, TemplateSchema{ - HubAgentImage: agentImage, - ResourceServerImage: rsImage, - ClientToken: token, - HubServerBaseAddr: hubBase.String(), - AslanBaseAddr: config2.SystemAddress(), - UseDeployment: useDeployment, - DindReplicas: dindReplicas, - DindLimitsCPU: dindLimitsCPU, - DindLimitsMemory: dindLimitsMemory, - DindImage: config.DindImage(), + HubAgentImage: agentImage, + ResourceServerImage: rsImage, + ClientToken: token, + HubServerBaseAddr: hubBase.String(), + AslanBaseAddr: config2.SystemAddress(), + UseDeployment: useDeployment, + DindReplicas: dindReplicas, + DindLimitsCPU: dindLimitsCPU, + DindLimitsMemory: dindLimitsMemory, + DindImage: config.DindImage(), + DindEnablePV: dindEnablePV, + DindStorageClassName: dindSCName, + DindStorageSizeInGiB: dindStorageSizeInGiB, }) } else { err = YamlTemplateForNamespace.Execute(buffer, TemplateSchema{ - HubAgentImage: agentImage, - ResourceServerImage: rsImage, - ClientToken: token, - HubServerBaseAddr: hubBase.String(), - AslanBaseAddr: config2.SystemAddress(), - UseDeployment: useDeployment, - Namespace: cluster.Namespace, - DindReplicas: dindReplicas, - DindLimitsCPU: dindLimitsCPU, - DindLimitsMemory: dindLimitsMemory, - DindImage: config.DindImage(), + HubAgentImage: agentImage, + ResourceServerImage: rsImage, + ClientToken: token, + HubServerBaseAddr: hubBase.String(), + AslanBaseAddr: config2.SystemAddress(), + UseDeployment: useDeployment, + Namespace: cluster.Namespace, + DindReplicas: dindReplicas, + DindLimitsCPU: dindLimitsCPU, + DindLimitsMemory: dindLimitsMemory, + DindImage: config.DindImage(), + DindEnablePV: dindEnablePV, + DindStorageClassName: dindSCName, + DindStorageSizeInGiB: dindStorageSizeInGiB, }) } @@ -314,46 +324,63 @@ func (s *Service) UpdateUpgradeAgentInfo(id, updateHubagentErrorMsg string) erro return err } -func setDindCfg(cluster *models.K8SCluster) (int, string, string) { - var dindReplicas int = DefaultDindReplicas - var dindLimitsCPU string = strconv.Itoa(DefaultDindLimitsCPU) + setting.CpuUintM - var dindLimitsMemory string = strconv.Itoa(DefaultDindLimitsMemory) + setting.MemoryUintMi +func getDindCfg(cluster *models.K8SCluster) (replicas int, limitsCPU, limitsMemory string, enablePV bool, scName string, storageSizeInGiB int) { + replicas = DefaultDindReplicas + limitsCPU = strconv.Itoa(DefaultDindLimitsCPU) + setting.CpuUintM + limitsMemory = strconv.Itoa(DefaultDindLimitsMemory) + setting.MemoryUintMi + enablePV = DefaultDindEnablePV + scName = DefaultDindStorageClassName + storageSizeInGiB = DefaultDindStorageSizeInGiB if cluster.DindCfg != nil { if cluster.DindCfg.Replicas > 0 { - dindReplicas = cluster.DindCfg.Replicas + replicas = cluster.DindCfg.Replicas } if cluster.DindCfg.Resources != nil && cluster.DindCfg.Resources.Limits != nil { if cluster.DindCfg.Resources.Limits.CPU > 0 { - dindLimitsCPU = strconv.Itoa(cluster.DindCfg.Resources.Limits.CPU) + setting.CpuUintM + limitsCPU = strconv.Itoa(cluster.DindCfg.Resources.Limits.CPU) + setting.CpuUintM } if cluster.DindCfg.Resources.Limits.Memory > 0 { - dindLimitsMemory = strconv.Itoa(cluster.DindCfg.Resources.Limits.Memory) + setting.MemoryUintMi + limitsMemory = strconv.Itoa(cluster.DindCfg.Resources.Limits.Memory) + setting.MemoryUintMi } } + + if cluster.DindCfg.Storage != nil && cluster.DindCfg.Storage.Type == commonmodels.DindStorageDynamic { + enablePV = true + scName = cluster.DindCfg.Storage.StorageClass + storageSizeInGiB = int(cluster.DindCfg.Storage.StorageSizeInGiB) + } } - return dindReplicas, dindLimitsCPU, dindLimitsMemory + + return } type TemplateSchema struct { - HubAgentImage string - ResourceServerImage string - ClientToken string - HubServerBaseAddr string - Namespace string - UseDeployment bool - AslanBaseAddr string - DindReplicas int - DindLimitsCPU string - DindLimitsMemory string - DindImage string + HubAgentImage string + ResourceServerImage string + ClientToken string + HubServerBaseAddr string + Namespace string + UseDeployment bool + AslanBaseAddr string + DindReplicas int + DindLimitsCPU string + DindLimitsMemory string + DindImage string + DindEnablePV bool + DindStorageClassName string + DindStorageSizeInGiB int } const ( - DefaultDindReplicas int = 1 - DefaultDindLimitsCPU int = 4000 - DefaultDindLimitsMemory int = 8192 + DefaultDindReplicas int = 1 + DefaultDindLimitsCPU int = 4000 + DefaultDindLimitsMemory int = 8192 + DefaultDindStorageType commonmodels.DindStorageType = commonmodels.DindStorageRootfs + DefaultDindEnablePV bool = false + DefaultDindStorageClassName string = "" + DefaultDindStorageSizeInGiB int = 10 ) var YamlTemplate = template.Must(template.New("agentYaml").Parse(` @@ -577,8 +604,6 @@ spec: containers: - name: dind image: {{.DindImage}} - args: - - --mtu=1376 env: - name: DOCKER_TLS_CERTDIR value: "" @@ -594,6 +619,20 @@ spec: requests: cpu: 100m memory: 128Mi +{{- if .DindEnablePV }} + volumeMounts: + - name: zadig-docker + mountPath: /var/lib/docker + volumeClaimTemplates: + - metadata: + name: zadig-docker + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{.DindStorageClassName}} + resources: + requests: + storage: {{.DindStorageSizeInGiB}}Gi +{{- end }} --- @@ -829,8 +868,6 @@ spec: containers: - name: dind image: {{.DindImage}} - args: - - --mtu=1376 env: - name: DOCKER_TLS_CERTDIR value: "" @@ -846,6 +883,20 @@ spec: requests: cpu: 100m memory: 128Mi +{{- if .DindEnablePV }} + volumeMounts: + - name: zadig-docker + mountPath: /var/lib/docker + volumeClaimTemplates: + - metadata: + name: zadig-docker + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{.DindStorageClassName}} + resources: + requests: + storage: {{.DindStorageSizeInGiB}}Gi +{{- end }} --- diff --git a/pkg/microservice/aslan/core/multicluster/handler/cache.go b/pkg/microservice/aslan/core/multicluster/handler/cache.go index 4b9e1ba0..3714033a 100644 --- a/pkg/microservice/aslan/core/multicluster/handler/cache.go +++ b/pkg/microservice/aslan/core/multicluster/handler/cache.go @@ -18,15 +18,17 @@ package handler import ( "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" + "github.com/koderover/zadig/pkg/types" ) func ListStorageClasses(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = service.ListStorageClasses(c, c.Param("id")) + ctx.Resp, ctx.Err = service.ListStorageClasses(c, c.Param("id"), types.StorageClassType(c.Query("type"))) } func ListPVCs(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/multicluster/service/cache.go b/pkg/microservice/aslan/core/multicluster/service/cache.go index 15c88f90..7c846640 100644 --- a/pkg/microservice/aslan/core/multicluster/service/cache.go +++ b/pkg/microservice/aslan/core/multicluster/service/cache.go @@ -27,7 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" ) type PVC struct { @@ -35,7 +35,7 @@ type PVC struct { StorageSizeInBytes int64 `json:"storage_size_in_bytes"` } -func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) { +func ListStorageClasses(ctx context.Context, clusterID string, scType types.StorageClassType) ([]string, error) { kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) if err != nil { return nil, fmt.Errorf("failed to get kube client: %s", err) @@ -55,11 +55,10 @@ func ListStorageClasses(ctx context.Context, clusterID string) ([]string, error) if err != nil { return nil, fmt.Errorf("failed to list storageclasses: %s", err) } - log.Infof("StorageClass Length: %d", len(scList.Items)) storageclasses := make([]string, 0, len(scList.Items)) for _, item := range scList.Items { - if !StorageProvisioner(item.Provisioner).IsNFS() { + if scType != types.StorageClassAll && !StorageProvisioner(item.Provisioner).IsNFS() { continue } @@ -90,7 +89,6 @@ func ListPVCs(ctx context.Context, clusterID, namespace string) ([]PVC, error) { if err != nil { return nil, err } - log.Infof("PVC Length: %d", len(pvcList.Items)) pvcs := make([]PVC, 0, len(pvcList.Items)) for _, item := range pvcList.Items { diff --git a/pkg/microservice/aslan/core/multicluster/service/clusters.go b/pkg/microservice/aslan/core/multicluster/service/clusters.go index a1c04b32..22efd94e 100644 --- a/pkg/microservice/aslan/core/multicluster/service/clusters.go +++ b/pkg/microservice/aslan/core/multicluster/service/clusters.go @@ -28,12 +28,14 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" "helm.sh/helm/v3/pkg/releaseutil" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/discovery" "sigs.k8s.io/controller-runtime/pkg/client" @@ -49,6 +51,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" + "github.com/koderover/zadig/pkg/util" "github.com/koderover/zadig/pkg/util/ginzap" ) @@ -136,6 +139,9 @@ func ListClusters(ids []string, projectName string, logger *zap.SugaredLogger) ( Memory: kube.DefaultDindLimitsMemory, }, }, + Storage: &commonmodels.DindStorage{ + Type: kube.DefaultDindStorageType, + }, } } @@ -211,7 +217,11 @@ func convertToNodeSelectorRequirements(nodeLabels []string) []*commonmodels.Node } func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { - s, _ := kube.NewService("") + s, err := kube.NewService("") + if err != nil { + return nil, fmt.Errorf("failed to new kube service: %s", err) + } + var advancedConfig *commonmodels.AdvancedConfig if args.AdvancedConfig != nil { advancedConfig = &commonmodels.AdvancedConfig{ @@ -222,11 +232,17 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K } // If user does not configure a cache for the cluster, object storage is used by default. - err := setClusterCache(args) + err = setClusterCache(args) if err != nil { return nil, fmt.Errorf("failed to set cache for cluster %s: %s", args.ID, err) } + // If user does not set a dind config for the cluster, set the default values. + err = setClusterDind(args) + if err != nil { + return nil, fmt.Errorf("failed to set dind args for cluster %s: %s", args.ID, err) + } + cluster := &commonmodels.K8SCluster{ Name: args.Name, Description: args.Description, @@ -244,7 +260,11 @@ func CreateCluster(args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K } func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*commonmodels.K8SCluster, error) { - s, _ := kube.NewService("") + s, err := kube.NewService("") + if err != nil { + return nil, fmt.Errorf("failed to new kube service: %s", err) + } + advancedConfig := new(commonmodels.AdvancedConfig) if args.AdvancedConfig != nil { advancedConfig.Strategy = args.AdvancedConfig.Strategy @@ -267,11 +287,17 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com } // If user does not configure a cache for the cluster, object storage is used by default. - err := setClusterCache(args) + err = setClusterCache(args) if err != nil { return nil, fmt.Errorf("failed to set cache for cluster %s: %s", id, err) } + // If user does not set a dind config for the cluster, set the default values. + err = setClusterDind(args) + if err != nil { + return nil, fmt.Errorf("failed to set dind args for cluster %s: %s", args.ID, err) + } + // If the user chooses to use dynamically generated storage resources, the system automatically creates the PVC. // TODO: If the PVC is not successfully bound to the PV, it is necessary to consider how to expose this abnormal information. // Depends on product design. @@ -345,7 +371,13 @@ func UpdateCluster(id string, args *K8SCluster, logger *zap.SugaredLogger) (*com Cache: args.Cache, DindCfg: args.DindCfg, } - return s.UpdateCluster(id, cluster, logger) + + cluster, err = s.UpdateCluster(id, cluster, logger) + if err != nil { + return nil, fmt.Errorf("failed to update cluster %q: %s", id, err) + } + + return cluster, UpgradeAgent(id, logger) } func DeleteCluster(username, clusterID string, logger *zap.SugaredLogger) error { @@ -395,10 +427,11 @@ func GetYaml(id, hubURI string, useDeployment bool, logger *zap.SugaredLogger) ( } func UpgradeAgent(id string, logger *zap.SugaredLogger) error { - if id == setting.LocalClusterID { - return fmt.Errorf("local cluster %s not support Upgrade Agent", id) + s, err := kube.NewService("") + if err != nil { + return fmt.Errorf("failed to new kube service: %s", err) } - s, _ := kube.NewService("") + clusterInfo, err := s.GetCluster(id, logger) if err != nil { return err @@ -409,9 +442,15 @@ func UpgradeAgent(id string, logger *zap.SugaredLogger) error { kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), id) if err != nil { - return fmt.Errorf(" failed to get kube client: %s cluster: %s", err, clusterInfo.Name) + return fmt.Errorf("failed to get kube client: %s cluster: %s", err, clusterInfo.Name) + } + + // Upgrade local cluster. + if id == setting.LocalClusterID { + return UpgradeDind(kubeClient, clusterInfo, config.Namespace()) } + // Upgrade attached cluster. yamls, err := s.GetYaml(id, config.HubAgentImage(), config.ResourceServerImage(), configbase.SystemAddress(), "/api/hub", true, logger) if err != nil { return err @@ -431,7 +470,12 @@ func UpgradeAgent(id string, logger *zap.SugaredLogger) error { } for _, u := range resources { - err = updater.CreateOrPatchUnstructured(u, kubeClient) + if u.GetKind() == "StatefulSet" && u.GetName() == types.DindStatefulSetName { + err = UpgradeDind(kubeClient, clusterInfo, setting.AttachedClusterNamespace) + } else { + err = updater.CreateOrPatchUnstructured(u, kubeClient) + } + if err != nil { log.Errorf("[UpgradeAgent] Failed to create or update %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err) errList = multierror.Append(errList, err) @@ -475,6 +519,48 @@ func setClusterCache(args *K8SCluster) error { return nil } +func setClusterDind(cluster *K8SCluster) error { + if cluster.DindCfg == nil { + cluster.DindCfg = &commonmodels.DindCfg{ + Replicas: kube.DefaultDindReplicas, + Resources: &commonmodels.Resources{ + Limits: &commonmodels.Limits{ + CPU: kube.DefaultDindLimitsCPU, + Memory: kube.DefaultDindLimitsMemory, + }, + }, + Storage: &commonmodels.DindStorage{ + Type: kube.DefaultDindStorageType, + }, + } + + return nil + } + + if cluster.DindCfg.Replicas <= 0 { + cluster.DindCfg.Replicas = kube.DefaultDindReplicas + } + if cluster.DindCfg.Resources == nil { + cluster.DindCfg.Resources = &commonmodels.Resources{ + Limits: &commonmodels.Limits{ + CPU: kube.DefaultDindLimitsCPU, + Memory: kube.DefaultDindLimitsMemory, + }, + } + } + if cluster.DindCfg.Storage == nil { + cluster.DindCfg.Storage = &commonmodels.DindStorage{ + Type: kube.DefaultDindStorageType, + } + } + if cluster.DindCfg.Storage.Type == commonmodels.DindStorageRootfs { + cluster.DindCfg.Storage.StorageClass = "" + cluster.DindCfg.Storage.StorageSizeInGiB = 0 + } + + return nil +} + func ClusterApplyUpgradeAgent() { for i := 0; i < 3; i++ { time.Sleep(10 * time.Second) @@ -485,9 +571,6 @@ func ClusterApplyUpgradeAgent() { } for _, cluster := range k8sClusters { - if cluster.ID.Hex() == setting.LocalClusterID { - continue - } if cluster.Status != setting.Normal { log.Warnf("[ClusterApplyUpgradeAgent] not support apply. cluster %s status %s ", cluster.Name, cluster.Status) continue @@ -526,3 +609,164 @@ func CheckEphemeralContainers(ctx context.Context, clusterID string) (bool, erro return false, nil } + +func UpgradeDind(kclient client.Client, cluster *commonmodels.K8SCluster, ns string) error { + if cluster.DindCfg == nil { + return nil + } + + ctx := context.TODO() + dindSts := &appsv1.StatefulSet{} + err := kclient.Get(ctx, client.ObjectKey{ + Name: types.DindStatefulSetName, + Namespace: ns, + }, dindSts) + if err != nil { + return fmt.Errorf("failed to get dind StatefulSet in local cluster: %s", err) + } + + dindSts.Spec.Replicas = util.GetInt32Pointer(int32(cluster.DindCfg.Replicas)) + + if cluster.DindCfg.Resources != nil && cluster.DindCfg.Resources.Limits != nil { + cpuSize := fmt.Sprintf("%dm", cluster.DindCfg.Resources.Limits.CPU) + cpuQuantity, err := resource.ParseQuantity(cpuSize) + if err != nil { + return fmt.Errorf("failed to parse CPU resource %q: %s", cpuSize, err) + } + + memSize := fmt.Sprintf("%dMi", cluster.DindCfg.Resources.Limits.Memory) + memQuantity, err := resource.ParseQuantity(memSize) + if err != nil { + return fmt.Errorf("failed to parse Memory resource %q: %s", memSize, err) + } + + for i, container := range dindSts.Spec.Template.Spec.Containers { + if container.Name != types.DindContainerName { + continue + } + + container.Resources.Limits = corev1.ResourceList{ + corev1.ResourceCPU: cpuQuantity, + corev1.ResourceMemory: memQuantity, + } + dindSts.Spec.Template.Spec.Containers[i] = container + } + } + + if cluster.DindCfg.Storage != nil { + switch cluster.DindCfg.Storage.Type { + case commonmodels.DindStorageRootfs: + for i, container := range dindSts.Spec.Template.Spec.Containers { + if container.Name != types.DindContainerName { + continue + } + + volumeMounts := []corev1.VolumeMount{} + for _, volumeMount := range container.VolumeMounts { + if volumeMount.MountPath == types.DindMountPath { + continue + } + + volumeMounts = append(volumeMounts, volumeMount) + } + container.VolumeMounts = volumeMounts + dindSts.Spec.Template.Spec.Containers[i] = container + } + + volumeClaimTemplates := []corev1.PersistentVolumeClaim{} + for _, volumeClaimTemplate := range dindSts.Spec.VolumeClaimTemplates { + if volumeClaimTemplate.Name == types.DindMountName { + continue + } + + volumeClaimTemplates = append(volumeClaimTemplates, volumeClaimTemplate) + } + dindSts.Spec.VolumeClaimTemplates = volumeClaimTemplates + default: + for i, container := range dindSts.Spec.Template.Spec.Containers { + if container.Name != types.DindContainerName { + continue + } + + var exists bool + for _, volumeMount := range container.VolumeMounts { + if volumeMount.MountPath == types.DindMountPath { + exists = true + break + } + } + + if exists { + break + } + + container.VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ + Name: types.DindMountName, + MountPath: types.DindMountPath, + }) + dindSts.Spec.Template.Spec.Containers[i] = container + } + + var dockerStorageExists bool + for _, volumeClaimTemplate := range dindSts.Spec.VolumeClaimTemplates { + if volumeClaimTemplate.Name == types.DindMountName { + dockerStorageExists = true + break + } + } + + if !dockerStorageExists { + storageSize := fmt.Sprintf("%dGi", cluster.DindCfg.Storage.StorageSizeInGiB) + storageQuantity, err := resource.ParseQuantity(storageSize) + if err != nil { + return fmt.Errorf("failed to parse quantity %q: %s", storageSize, err) + } + + dindSts.Spec.VolumeClaimTemplates = append(dindSts.Spec.VolumeClaimTemplates, corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: types.DindMountName, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + StorageClassName: util.GetStrPointer(cluster.DindCfg.Storage.StorageClass), + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: storageQuantity, + }, + }, + }, + }) + } + } + } + + err = kclient.Update(ctx, dindSts) + if apierrors.IsInvalid(err) { + deleteOption := metav1.DeletePropagationForeground + err = kclient.Delete(ctx, dindSts, &client.DeleteOptions{ + PropagationPolicy: &deleteOption, + }) + if err != nil { + return fmt.Errorf("failed to delete StatefulSet `dind`: %s", err) + } + + newDindSts := &appsv1.StatefulSet{} + newDindSts.Name = dindSts.Name + newDindSts.Namespace = dindSts.Namespace + newDindSts.Labels = dindSts.Labels + newDindSts.Annotations = dindSts.Annotations + newDindSts.Spec = dindSts.Spec + + return wait.PollImmediate(3*time.Second, 30*time.Second, func() (bool, error) { + err := kclient.Create(ctx, newDindSts) + if err == nil { + return true, nil + } + + log.Warnf("Failed to create StatefulSet `dind`: %s", err) + return false, nil + }) + } + + return err +} diff --git a/pkg/microservice/aslan/core/system/service/system_cache.go b/pkg/microservice/aslan/core/system/service/system_cache.go index 08c4473e..ffa3646a 100644 --- a/pkg/microservice/aslan/core/system/service/system_cache.go +++ b/pkg/microservice/aslan/core/system/service/system_cache.go @@ -18,25 +18,30 @@ package service import ( "context" + "fmt" "sort" "strings" "sync" "time" "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/setting" + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" - kubetool "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/podexec" + "github.com/koderover/zadig/pkg/types" ) +var cleanCacheLock sync.Mutex + const ( CleanStatusUnStart = "unStart" CleanStatusSuccess = "success" @@ -76,17 +81,18 @@ func SetCron(cron string, cronEnabled bool, logger *zap.SugaredLogger) error { return nil } -// CleanImageCache 清理镜像缓存 func CleanImageCache(logger *zap.SugaredLogger) error { - //Get pod list by label and namespace - //判断当前的状态,cleaning状态下不做清理 - var mu sync.Mutex - mu.Lock() - defer func() { - mu.Unlock() - }() + // TODO: We should return immediately instead of waiting for the lock if a cleanup task is performed. + // Since `golang-1.18`, `sync.Mutex` provides a `TryLock()` method. For now, we can continue with the previous + // logic and replace it with `TryLock()` after upgrading to `golang-1.18+` to return immediately. + cleanCacheLock.Lock() + defer cleanCacheLock.Unlock() + + dindCleans, err := commonrepo.NewDindCleanColl().List() + if err != nil { + return fmt.Errorf("failed to list data in `dind_clean` table: %s", err) + } - dindCleans, _ := commonrepo.NewDindCleanColl().List() switch len(dindCleans) { case 0: dindClean := &commonmodels.DindClean{ @@ -111,11 +117,9 @@ func CleanImageCache(logger *zap.SugaredLogger) error { } } - namespace := config.Namespace() - selector := labels.Set{setting.ComponentLabel: "dind"}.AsSelector() - pods, err := getter.ListPods(namespace, selector, kubetool.Client()) + dindPods, err := getDindPods() if err != nil { - logger.Errorf("[%s]list dind pods error: %v", namespace, err) + logger.Errorf("Failed to list dind pods: %s", err) return commonrepo.NewDindCleanColl().Upsert(&commonmodels.DindClean{ Status: CleanStatusFailed, DindCleanInfos: []*commonmodels.DindCleanInfo{}, @@ -123,7 +127,11 @@ func CleanImageCache(logger *zap.SugaredLogger) error { CronEnabled: dindCleans[0].CronEnabled, }) } + logger.Infof("Total dind Pods to be cleaned up: %d", len(dindPods)) + // Note: Since the total number of dind instances of Zadig users will not exceed `50` within one or two years + // (at this time, the resource amount may be `200C400GiB`, and the resource cost is too high), concurrency can be + // left out of consideration. timeout, cancel := context.WithTimeout(context.Background(), 20*time.Minute) defer cancel() res := make(chan *commonmodels.DindClean) @@ -132,32 +140,35 @@ func CleanImageCache(logger *zap.SugaredLogger) error { status = CleanStatusSuccess ) - dindInfos := make([]*commonmodels.DindCleanInfo, 0, len(pods)) + dindInfos := make([]*commonmodels.DindCleanInfo, 0, len(dindPods)) var wg sync.WaitGroup - for _, pod := range pods { - if wrapper.Pod(pod).Ready() { - logger.Infof("[%s] pod [%s] dind cache cleaning", namespace, pod.Name) - wg.Add(1) - go func(podName string) { - defer wg.Done() - dindInfo := &commonmodels.DindCleanInfo{ - PodName: podName, - StartTime: time.Now().Unix(), - } - output, err := dockerPrune(namespace, podName, logger) - if err != nil { - logger.Errorf("[%s] pod [%s] dind cache clean error: %v", namespace, podName, err) - dindInfo.ErrorMessage = err.Error() - } - logger.Infof("[%s] pod [%s] dind cache finish", namespace, podName) - dindInfo.EndTime = time.Now().Unix() - dindInfo.CleanInfo = output - dindInfos = append(dindInfos, dindInfo) - }(pod.Name) + for _, dindPod := range dindPods { + if !wrapper.Pod(dindPod.Pod).Ready() { + continue } + + logger.Infof("Begin to clean up cache of dind %q in ns %q of cluster %q.", dindPod.Pod.Name, dindPod.Pod.Namespace, dindPod.ClusterID) + wg.Add(1) + go func(podInfo types.DindPod) { + defer wg.Done() + dindInfo := &commonmodels.DindCleanInfo{ + PodName: fmt.Sprintf("%s:%s", podInfo.ClusterName, podInfo.Pod.Name), + StartTime: time.Now().Unix(), + } + output, err := dockerPrune(podInfo.ClusterID, podInfo.Pod.Namespace, podInfo.Pod.Name, logger) + if err != nil { + logger.Warnf("Failed to clean up cache of dind %q in ns %q of cluster %q: %s", podInfo.Pod.Name, podInfo.Pod.Namespace, podInfo.ClusterID, err) + dindInfo.ErrorMessage = err.Error() + } + logger.Infof("Finish cleaning up cache of dind %q in ns %q of cluster %q.", podInfo.Pod.Name, podInfo.Pod.Namespace, podInfo.ClusterID) + + dindInfo.EndTime = time.Now().Unix() + dindInfo.CleanInfo = output + dindInfos = append(dindInfos, dindInfo) + }(dindPod) } - wg.Wait() + wg.Wait() for _, dindInfo := range dindInfos { if dindInfo.ErrorMessage != "" { status = CleanStatusFailed @@ -216,15 +227,24 @@ func GetOrCreateCleanCacheState() (*commonmodels.DindClean, error) { return dindClean, nil } -func dockerPrune(namespace, podName string, logger *zap.SugaredLogger) (string, error) { - cleanInfo, errString, _, err := podexec.ExecWithOptions(podexec.ExecOptions{ +func dockerPrune(clusterID, namespace, podName string, logger *zap.SugaredLogger) (string, error) { + kclient, err := kubeclient.GetClientset(config.HubServerAddress(), clusterID) + if err != nil { + return "", fmt.Errorf("failed to get clientset for cluster %q: %s", clusterID, err) + } + + restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) + if err != nil { + return "", fmt.Errorf("failed to get rest config for cluster %q: %s", clusterID, err) + } + + cleanInfo, errString, _, err := podexec.KubeExec(kclient, restConfig, podexec.ExecOptions{ Command: []string{"docker", "system", "prune", "--volumes", "-a", "-f"}, Namespace: namespace, PodName: podName, }) - if err != nil { - logger.Infof("Failed to execute docker prune: %s, err: %s", errString, err) + logger.Errorf("Failed to execute docker prune: %s, err: %s", errString, err) cleanInfo = errString } @@ -236,3 +256,48 @@ func dockerPrune(namespace, podName string, logger *zap.SugaredLogger) (string, return cleanInfo, err } + +func getDindPods() ([]types.DindPod, error) { + activeClusters, err := commonrepo.NewK8SClusterColl().FindActiveClusters() + if err != nil { + return nil, fmt.Errorf("failed to get active cluster: %s", err) + } + + dindPods := []types.DindPod{} + for _, cluster := range activeClusters { + clusterID := cluster.ID.Hex() + + var ns string + switch clusterID { + case setting.LocalClusterID: + ns = config.Namespace() + default: + ns = setting.AttachedClusterNamespace + } + + pods, err := getDindPodsInCluster(clusterID, ns) + if err != nil { + return nil, fmt.Errorf("failed to get dind pods in ns %q of cluster %q: %s", ns, clusterID, err) + } + + for _, pod := range pods { + dindPods = append(dindPods, types.DindPod{ + ClusterID: clusterID, + ClusterName: cluster.Name, + Pod: pod, + }) + } + } + + return dindPods, nil +} + +func getDindPodsInCluster(clusterID, ns string) ([]*corev1.Pod, error) { + kclient, err := kubeclient.GetKubeClient(config.HubServerAddress(), clusterID) + if err != nil { + return nil, fmt.Errorf("failed to get kube client for cluster %q: %s", clusterID, err) + } + + dindSelector := labels.Set{setting.ComponentLabel: "dind"}.AsSelector() + return getter.ListPods(ns, dindSelector, kclient) +} diff --git a/pkg/types/cache.go b/pkg/types/cache.go index 90a8ba2f..da0d8ad5 100644 --- a/pkg/types/cache.go +++ b/pkg/types/cache.go @@ -59,3 +59,9 @@ const ( WorkspaceCacheDir CacheDirType = "workspace" UserDefinedCacheDir CacheDirType = "user_defined" ) + +type StorageClassType string + +const ( + StorageClassAll StorageClassType = "all" +) diff --git a/pkg/types/workload.go b/pkg/types/workload.go index 69d75e3b..2b714c61 100644 --- a/pkg/types/workload.go +++ b/pkg/types/workload.go @@ -16,9 +16,22 @@ limitations under the License. package types +import corev1 "k8s.io/api/core/v1" + type WorkloadType string const ( DeploymentWorkload WorkloadType = "deployment" StatefulSetWorkload WorkloadType = "statefulset" ) + +type DindPod struct { + ClusterID string + ClusterName string + Pod *corev1.Pod +} + +const DindStatefulSetName = "dind" +const DindContainerName = "dind" +const DindMountName = "zadig-docker" +const DindMountPath = "/var/lib/docker" -- Gitee From 3cec0bb75452579ecc93d003624ca0bcd03b8687 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 28 Jun 2022 17:49:34 +0800 Subject: [PATCH 2489/2647] optimize the logic of query workflow lists (#1714) * optimize the logic of query workflow lists Signed-off-by: allenshen * update logic of code info Signed-off-by: allenshen * update service module struct Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * fix data Signed-off-by: allenshen --- .../core/common/repository/mongodb/task.go | 115 +++++++++----- .../service/workflow/pipeline_task.go | 140 +++++------------- .../service/workflow/pipeline_validation.go | 6 +- 3 files changed, 117 insertions(+), 144 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index 9d4ba725..0765e5a5 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -30,27 +30,30 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" mongotool "github.com/koderover/zadig/pkg/tool/mongo" + "github.com/koderover/zadig/pkg/types" ) type ListTaskOption struct { - PipelineName string - PipelineNames []string - Status config.Status - Statuses []string - Team string - TeamID int - Limit int - Skip int - Detail bool - Type config.PipelineType - CreateTime int64 - TaskCreator string - TaskCreators []string - Source string - Committers []string - MergeRequestID string - NeedTriggerBy bool - NeedAllData bool + PipelineName string + PipelineNames []string + Status config.Status + Statuses []string + Team string + TeamID int + Limit int + Skip int + Detail bool + ForWorkflowTaskList bool + Type config.PipelineType + CreateTime int64 + TaskCreator string + TaskCreators []string + Source string + Committers []string + ServiceModule []string + MergeRequestID string + NeedTriggerBy bool + NeedAllData bool } type FindTaskOption struct { @@ -59,32 +62,41 @@ type FindTaskOption struct { Type config.PipelineType } +type CodeInfo struct { + AuthorName string `json:"author_name"` + CommitId string `json:"commit_id"` + CommitMessage string `json:"commit_message"` +} + type ServiceModule struct { - ServiceModule string `json:"service_module"` - ServiceName string `json:"service_name"` + ServiceModule string `json:"service_module"` + ServiceName string `json:"service_name"` + CodeInfo []*types.Repository `json:"code_info"` +} + +type BuildStage struct { + SubTasks map[string]task.Build `bson:"sub_tasks" json:"-"` } type TaskPreview struct { - TaskID int64 `bson:"task_id" json:"task_id"` - TaskCreator string `bson:"task_creator" json:"task_creator"` - ProductName string `bson:"product_name" json:"product_name"` - PipelineName string `bson:"pipeline_name" json:"pipeline_name"` - Namespace string `bson:"namespace" json:"namespace"` - ServiceName string `bson:"service_name" json:"service_name"` - Status config.Status `bson:"status" json:"status"` - CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - SubTasks []*map[string]interface{} `bson:"sub_tasks,omitempty" json:"sub_tasks,omitempty"` - TaskArgs *models.TaskArgs `bson:"task_args" json:"task_args"` - WorkflowArgs *models.WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` - TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` - Type config.PipelineType `bson:"type" json:"type"` - Stages []*models.Stage `bson:"stages" json:"stages,omitempty"` - // 服务名称,用于任务列表的展示 - BuildServices []string `bson:"-" json:"build_services"` - ServiceModules []*ServiceModule `bson:"-" json:"service_modules"` - TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + TaskCreator string `bson:"task_creator" json:"task_creator"` + ProductName string `bson:"product_name" json:"product_name"` + PipelineName string `bson:"pipeline_name" json:"pipeline_name"` + Namespace string `bson:"namespace" json:"namespace"` + ServiceName string `bson:"service_name" json:"service_name"` + Status config.Status `bson:"status" json:"status"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + SubTasks []*map[string]interface{} `bson:"sub_tasks,omitempty" json:"sub_tasks,omitempty"` + TaskArgs *models.TaskArgs `bson:"task_args" json:"task_args"` + WorkflowArgs *models.WorkflowTaskArgs `bson:"workflow_args" json:"workflow_args"` + TestReports map[string]interface{} `bson:"test_reports,omitempty" json:"test_reports,omitempty"` + Type config.PipelineType `bson:"type" json:"type"` + Stages []*models.Stage `bson:"stages" json:"stages,omitempty"` + ServiceModules []*ServiceModule `bson:"-" json:"service_modules"` + TriggerBy *models.TriggerBy `bson:"trigger_by,omitempty" json:"trigger_by,omitempty"` } type ListAllTaskOption struct { @@ -290,6 +302,9 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) if len(option.Committers) > 0 { query["workflow_args.committer"] = bson.M{"$in": option.Committers} } + if len(option.ServiceModule) > 0 { + query["workflow_args.targets.name"] = bson.M{"$in": option.ServiceModule} + } if option.Source != "" { query["trigger_by.source"] = option.Source } @@ -330,8 +345,19 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) selector = append(selector, bson.E{"test_reports", 1}) selector = append(selector, bson.E{"stages", 1}) } + + // show necessary fields when listing workflow tasks + if option.ForWorkflowTaskList { + selector = append(selector, bson.E{"workflow_args.targets.name", 1}) + selector = append(selector, bson.E{"workflow_args.targets.service_name", 1}) + selector = append(selector, bson.E{"workflow_args.namespace", 1}) + selector = append(selector, bson.E{"test_reports", 1}) + selector = append(selector, bson.E{"stages", 1}) + } + opt := options.Find() opt.SetSort(bson.D{{"create_time", -1}}) + if option.Limit != 0 { opt.SetProjection(selector).SetSkip(int64(option.Skip)).SetLimit(int64(option.Limit)) } else if option.CreateTime > 0 { @@ -347,7 +373,10 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) if err != nil { return } - err = cursor.All(context.TODO(), &ret) + + if err = cursor.All(context.TODO(), &ret); err != nil { + return nil, err + } return } @@ -410,6 +439,7 @@ type CountTaskOption struct { Status config.TaskStatus TaskCreators []string Committers []string + ServiceModule []string Type config.PipelineType } @@ -425,6 +455,9 @@ func (c *TaskColl) Count(option *CountTaskOption) (ret int, err error) { if len(option.Committers) > 0 { query["workflow_args.committer"] = bson.M{"$in": option.Committers} } + if len(option.ServiceModule) > 0 { + query["workflow_args.targets.name"] = bson.M{"$in": option.ServiceModule} + } if option.PipelineNames != nil { query["pipeline_name"] = bson.M{"$in": option.PipelineNames} } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go index 6e64545d..ee63341a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_task.go @@ -302,105 +302,71 @@ func ListPipelineTasksV2Result(name string, typeString config.PipelineType, quer var listTaskOpt *commonrepo.ListTaskOption var countTaskOpt *commonrepo.CountTaskOption var restp []*commonrepo.TaskPreview - serviceNameFiltersMap := make(map[string]interface{}, len(filters)) if len(filters) == 0 || (len(filters) == 1 && filters[0] == "") { queryType = "" } switch queryType { case "creator": - listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, TaskCreators: filters, Detail: true, Type: typeString} + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, TaskCreators: filters, ForWorkflowTaskList: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, TaskCreators: filters, Type: typeString} case "committer": - listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Committers: filters, Detail: true, Type: typeString} + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Committers: filters, ForWorkflowTaskList: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Committers: filters, Type: typeString} case "serviceName": - listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Detail: true, Type: typeString} - countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} - for _, svc := range filters { - containerName := strings.Split(svc, "_")[0] - serviceNameFiltersMap[containerName] = nil - } + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, ServiceModule: filters, ForWorkflowTaskList: true, Type: typeString} + countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, ServiceModule: filters, Type: typeString} case "taskStatus": - listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuses: filters, Detail: true, Type: typeString} + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Statuses: filters, ForWorkflowTaskList: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Statuses: filters, Type: typeString} default: - listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, Detail: true, Type: typeString} + listTaskOpt = &commonrepo.ListTaskOption{PipelineName: name, Limit: maxResult, Skip: startAt, ForWorkflowTaskList: true, Type: typeString} countTaskOpt = &commonrepo.CountTaskOption{PipelineNames: []string{name}, Type: typeString} } restp, err = commonrepo.NewTaskColl().List(listTaskOpt) if err != nil { - log.Errorf("PipelineTaskV2.List:%s error: %s", listTaskOpt, err) + log.Errorf("PipelineTaskV2.List: %v error: %s", listTaskOpt, err) return ret, e.ErrListTasks } - var buildStage, deployStage *commonmodels.Stage + for _, t := range restp { - t.BuildServices = []string{} - for _, stage := range t.Stages { - if stage.TaskType == config.TaskBuild { - buildStage = stage - } - if stage.TaskType == config.TaskDeploy { - deployStage = stage + if t.WorkflowArgs == nil { + continue + } + t.Namespace = t.WorkflowArgs.Namespace + serviceModuleMap := make(map[string]*commonrepo.ServiceModule) + + for _, target := range t.WorkflowArgs.Target { + sm := &commonrepo.ServiceModule{ + ServiceName: target.ServiceName, + ServiceModule: target.Name, } + serviceModuleMap[fmt.Sprintf("%s_%s", target.Name, target.ServiceName)] = sm + t.ServiceModules = append(t.ServiceModules, sm) } - existSvc := false - if buildStage != nil { - for serviceName, subTask := range buildStage.SubTasks { - buildInfo, err := base.ToBuildTask(subTask) - if err != nil { - log.Errorf("get buildInfo failed ! err: %s", err) - return ret, e.ErrListTasks.AddDesc(fmt.Sprintf("failed to get build info for task: %d, err: %s", t.TaskID, err)) - } - serviceModule := strings.TrimSuffix(serviceName, "_"+buildInfo.Service) - if queryType == "serviceName" { - if _, ok := serviceNameFiltersMap[serviceModule]; ok { - existSvc = true - } - } - t.BuildServices = append(t.BuildServices, serviceName) - t.ServiceModules = append(t.ServiceModules, &commonrepo.ServiceModule{ - ServiceName: buildInfo.Service, - ServiceModule: serviceModule, - }) + + for _, stage := range t.Stages { + if stage.TaskType != config.TaskBuild { + continue } - } else if deployStage != nil { - for serviceName, subTask := range deployStage.SubTasks { - deployInfo, err := base.ToDeployTask(subTask) + for fullServiceName, sTask := range stage.SubTasks { + buildTask, err := base.ToBuildTask(sTask) if err != nil { - log.Errorf("get deployInfo failed ! err: %s", err) - return ret, e.ErrListTasks.AddDesc(fmt.Sprintf("failed to get deploy info for task: %d, err: %s", t.TaskID, err)) + log.Warnf("failed to get build task for task: %s, err: %s", name, err) + continue } - serviceModule := strings.TrimSuffix(serviceName, "_"+deployInfo.ServiceName) - if queryType == "serviceName" { - if _, ok := serviceNameFiltersMap[serviceModule]; ok { - existSvc = true + if sm, ok := serviceModuleMap[fullServiceName]; ok { + for _, buildInfo := range buildTask.JobCtx.Builds { + sm.CodeInfo = append(sm.CodeInfo, buildInfo) } } - t.BuildServices = append(t.BuildServices, serviceName) - t.ServiceModules = append(t.ServiceModules, &commonrepo.ServiceModule{ - ServiceName: deployInfo.ServiceName, - ServiceModule: serviceModule, - }) } + break } - buildStage, deployStage = nil, nil - if existSvc { - ret.Data = append(ret.Data, t) - } - } - if queryType == "serviceName" { - ret.Total = len(ret.Data) - if startAt > ret.Total { - ret.Data = []*commonrepo.TaskPreview{} - return ret, nil - } - if startAt+maxResult <= ret.Total { - ret.Data = ret.Data[startAt : startAt+maxResult] - } else { - ret.Data = ret.Data[startAt:ret.Total] - } - return ret, nil + + t.WorkflowArgs = nil + t.Stages = nil } + ret.Data = restp ret.Total, err = commonrepo.NewTaskColl().Count(countTaskOpt) if err != nil { @@ -441,37 +407,11 @@ func GetFiltersPipelineTaskV2(projectName, pipelineName, querytype string, typeS return resp, e.ErrGetTask } case "serviceName": - data, err := commonrepo.NewTaskColl().List(&commonrepo.ListTaskOption{PipelineName: pipelineName, Detail: true, Type: typeString}) + fieldName = "workflow_args.targets.name" + resp, err = commonrepo.NewTaskColl().DistinctFieldsPipelineTask(fieldName, projectName, pipelineName, typeString, false) if err != nil { - log.Errorf("PipelineTaskV2.List error: %s", err) - return resp, e.ErrListTasks - } - var buildStage, deployStage *commonmodels.Stage - svcSets := sets.NewString() - for _, t := range data { - for _, stage := range t.Stages { - if stage.TaskType == config.TaskBuild { - buildStage = stage - } - if stage.TaskType == config.TaskDeploy { - deployStage = stage - } - } - if buildStage != nil { - for serviceName := range buildStage.SubTasks { - containerName := strings.Split(serviceName, "_")[0] - svcSets.Insert(containerName) - } - } else if deployStage != nil { - for serviceName := range deployStage.SubTasks { - containerName := strings.Split(serviceName, "_")[0] - svcSets.Insert(containerName) - } - } - buildStage, deployStage = nil, nil - } - for svc := range svcSets { - resp = append(resp, svc) + log.Errorf("[%s] DistinctFeildsPipelineTask fieldName: %s error: %s", fieldName, pipelineName, err) + return resp, e.ErrGetTask } default: return resp, fmt.Errorf("queryType parameter is invalid") diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index d864dae1..e6382166 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -314,11 +314,11 @@ func setBuildInfo(build *types.Repository, buildArgs []*types.Repository, log *z var pr *PRCommit var err error if build.Tag != "" { - commit, err = QueryByTag(build.CodehostID, build.RepoOwner, build.RepoName, build.Tag, log) + commit, err = QueryByTag(build.CodehostID, build.GetRepoNamespace(), build.RepoName, build.Tag, log) } else if build.Branch != "" && build.PR == 0 { - commit, err = QueryByBranch(build.CodehostID, build.RepoOwner, build.RepoName, build.Branch, log) + commit, err = QueryByBranch(build.CodehostID, build.GetRepoNamespace(), build.RepoName, build.Branch, log) } else if build.PR > 0 { - pr, err = GetLatestPrCommit(build.CodehostID, build.PR, build.RepoOwner, build.RepoName, log) + pr, err = GetLatestPrCommit(build.CodehostID, build.PR, build.GetRepoNamespace(), build.RepoName, log) if err == nil && pr != nil { commit = &RepoCommit{ ID: pr.ID, -- Gitee From 71eabf30e7fd132f05aab0fbe2055c93154f2255 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 28 Jun 2022 21:24:11 +0800 Subject: [PATCH 2490/2647] add workflow mongo queue Signed-off-by: guoyu --- .../repository/models/wokflow_task_v4.go | 1 + .../repository/models/workflow_queue.go | 39 +++ .../repository/mongodb/workflow_queue.go | 125 ++++++++ .../repository/mongodb/workflow_task_v4.go | 38 ++- pkg/microservice/aslan/core/service.go | 3 + .../service/workflowcontroller/queue.go | 279 ++++++++++++++++++ .../service/workflowcontroller/workflow.go | 70 ++++- 7 files changed, 549 insertions(+), 6 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/workflow_queue.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 01e99739..92c62a15 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -40,6 +40,7 @@ type WorkflowTask struct { IsArchived bool `bson:"is_archived" json:"is_archived"` Error string `bson:"error,omitempty" json:"error,omitempty"` IsRestart bool `bson:"is_restart" json:"is_restart"` + MultiRun bool `bson:"multi_run" json:"multi_run"` } func (WorkflowTask) TableName() string { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go b/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go new file mode 100644 index 00000000..0251d9a3 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go @@ -0,0 +1,39 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" +) + +type WorkflowQueue struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TaskID int64 `bson:"task_id" json:"task_id"` + ProjectName string `bson:"project_name" json:"project_name"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + Status config.Status `bson:"status" json:"status,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + MultiRun bool `bson:"multi_run" json:"multi_run"` +} + +func (WorkflowQueue) TableName() string { + return "workflow_queue" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go new file mode 100644 index 00000000..04ff739b --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go @@ -0,0 +1,125 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "errors" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type ListWorfklowQueueOption struct { + WorkflowName string + Status config.Status +} + +type WorkflowQueueColl struct { + *mongo.Collection + + coll string +} + +func NewWorkflowQueueColl() *WorkflowQueueColl { + name := models.WorkflowQueue{}.TableName() + return &WorkflowQueueColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *WorkflowQueueColl) GetCollectionName() string { + return c.coll +} + +func (c *WorkflowQueueColl) EnsureIndex(ctx context.Context) error { + mod := mongo.IndexModel{ + Keys: bson.D{ + bson.E{Key: "task_id", Value: 1}, + bson.E{Key: "workflow_name", Value: 1}, + bson.E{Key: "create_time", Value: 1}, + }, + Options: options.Index().SetUnique(true), + } + + _, err := c.Indexes().CreateOne(ctx, mod) + + return err +} + +func (c *WorkflowQueueColl) List(opt *ListWorfklowQueueOption) ([]*models.WorkflowQueue, error) { + query := bson.M{} + if opt != nil { + if opt.WorkflowName != "" { + query["workflow_name"] = opt.WorkflowName + } + if opt.Status != "" { + query["status"] = opt.Status + } + } + + var resp []*models.WorkflowQueue + ctx := context.Background() + opts := options.Find().SetSort(bson.D{{"create_time", 1}}) + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err + } + + err = cursor.All(ctx, &resp) + if err != nil { + return nil, err + } + + return resp, nil +} + +func (c *WorkflowQueueColl) Delete(args *models.WorkflowQueue) error { + if args == nil { + return errors.New("nil workflow queue") + } + + query := bson.M{"task_id": args.TaskID, "workflow_name": args.WorkflowName, "create_time": args.CreateTime} + _, err := c.DeleteOne(context.TODO(), query) + return err +} + +func (c *WorkflowQueueColl) Create(args *models.WorkflowQueue) error { + if args == nil { + return errors.New("nil workflow queue") + } + + _, err := c.InsertOne(context.TODO(), args) + return err +} + +func (c *WorkflowQueueColl) Update(args *models.WorkflowQueue) error { + if args == nil { + return errors.New("nil workflow queue") + } + + query := bson.M{"task_id": args.TaskID, "workflow_name": args.WorkflowName, "create_time": args.CreateTime} + change := bson.M{"$set": bson.M{ + "status": args.Status, + }} + + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go index 2c46d59d..e7a8d237 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go @@ -124,9 +124,28 @@ func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option, pageNum, pageSi return resp, count, nil } -func (c *WorkflowTaskv4Coll) Find(name string) (*models.WorkflowTask, error) { +func (c *WorkflowTaskv4Coll) InCompletedTasks() ([]*models.WorkflowTask, error) { + ret := make([]*models.WorkflowTask, 0) + query := bson.M{"status": bson.M{"$in": []string{"created", "running"}}} + query["is_deleted"] = false + + opt := options.Find() + opt.SetSort(bson.D{{"create_time", 1}}) + + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &ret) + if err != nil { + return nil, err + } + return ret, nil +} + +func (c *WorkflowTaskv4Coll) Find(workflowName string, taskID int64) (*models.WorkflowTask, error) { resp := new(models.WorkflowTask) - query := bson.M{"name": name} + query := bson.M{"workflow_name": workflowName, "task_id": taskID} err := c.FindOne(context.TODO(), query).Decode(&resp) if err != nil { @@ -175,3 +194,18 @@ func (c *WorkflowTaskv4Coll) DeleteByWorkflowName(workflowName string) error { _, err := c.UpdateMany(context.TODO(), query, change) return err } + +func (c *WorkflowTaskv4Coll) ArchiveHistoryWorkflowTask(workflowName string, remain int) error { + query := bson.M{"workflow_name": workflowName, "is_deleted": false} + count, err := c.CountDocuments(context.TODO(), query) + if err != nil { + return err + } + query["task_id"] = bson.M{"$lt": int(count) - remain + 1} + change := bson.M{"$set": bson.M{ + "is_archived": true, + }} + _, err = c.UpdateMany(context.TODO(), query, change) + + return err +} diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 60bdd7cb..983197fc 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -39,6 +39,7 @@ import ( systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" @@ -122,6 +123,7 @@ func Start(ctx context.Context) { systemservice.SetProxyConfig() workflowservice.InitPipelineController() + workflowcontroller.InitWorkflowController() // 如果集群环境所属的项目不存在,则删除此集群环境 environmentservice.CleanProducts() @@ -236,6 +238,7 @@ func initDatabase() { commonrepo.NewScanningColl(), commonrepo.NewWorkflowV4Coll(), commonrepo.NewworkflowTaskv4Coll(), + commonrepo.NewWorkflowQueueColl(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go index 6e11d7f8..1624fadf 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go @@ -15,3 +15,282 @@ limitations under the License. */ package workflowcontroller + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" +) + +func RunningTasks() []*commonmodels.WorkflowTask { + tasks := make([]*commonmodels.WorkflowTask, 0) + queueTasks, err := commonrepo.NewWorkflowQueueColl().List(&commonrepo.ListWorfklowQueueOption{}) + if err != nil { + log.Errorf("list queue worklfow task failed, err:%v", err) + return tasks + } + for _, t := range queueTasks { + // task状态为TaskQueued说明task已经被send到nsq,wd已经开始处理但是没有返回ack + if t.Status == config.StatusRunning { + if t, err := commonrepo.NewworkflowTaskv4Coll().Find(t.WorkflowName, t.TaskID); err == nil { + tasks = append(tasks, t) + } + } + } + return tasks +} + +func PendingTasks() []*commonmodels.WorkflowTask { + tasks := make([]*commonmodels.WorkflowTask, 0) + queueTasks, err := commonrepo.NewWorkflowQueueColl().List(&commonrepo.ListWorfklowQueueOption{}) + if err != nil { + log.Errorf("list queue workflow task failed, err:%v", err) + return tasks + } + for _, t := range queueTasks { + if t.Status == config.StatusWaiting || t.Status == config.StatusBlocked || t.Status == config.StatusQueued { + if t, err := commonrepo.NewworkflowTaskv4Coll().Find(t.WorkflowName, t.TaskID); err == nil { + tasks = append(tasks, t) + } + } + } + return tasks +} + +type CancelMessage struct { + Revoker string `json:"revoker"` + PipelineName string `json:"pipeline_name"` + TaskID int64 `json:"task_id"` + ReqID string `json:"req_id"` +} + +// CreateTask 接受create task请求, 保存task到数据库, 发送task到queue +func CreateTask(t *commonmodels.WorkflowTask) error { + t.Status = config.StatusWaiting + if _, err := commonrepo.NewworkflowTaskv4Coll().Create(t); err != nil { + log.Errorf("create workflow task v4 error: %v", err) + return err + } + return Push(t) +} + +func UpdateTask(t *commonmodels.WorkflowTask) error { + t.Status = config.StatusWaiting + if err := commonrepo.NewworkflowTaskv4Coll().Update(t.ID.Hex(), t); err != nil { + log.Errorf("create workflow task v4 error: %v", err) + return err + } + return Push(t) +} + +func Push(t *commonmodels.WorkflowTask) error { + if t == nil { + return errors.New("nil task") + } + + if !t.MultiRun { + opt := &commonrepo.ListWorfklowQueueOption{ + WorkflowName: t.WorkflowName, + } + tasks, err := commonrepo.NewWorkflowQueueColl().List(opt) + if err == nil && len(tasks) > 0 { + log.Infof("blocked task recevied: %v %v %v", t.CreateTime, t.TaskID, t.WorkflowName) + t.Status = config.StatusBlocked + } + } + + if err := commonrepo.NewWorkflowQueueColl().Create(ConvertTaskToQueue(t)); err != nil { + log.Errorf("workflowTaskV4.Create error: %v", err) + return err + } + return nil +} + +func InitWorkflowController() { + InitQueue() + go WorfklowTaskSender() +} + +func InitQueue() error { + log := log.SugaredLogger() + + // 从数据库查找未完成的任务 + // status = created, running + tasks, err := commonrepo.NewworkflowTaskv4Coll().InCompletedTasks() + if err != nil { + log.Errorf("find [InCompletedTasks] error: %v", err) + return err + } + + for _, task := range tasks { + // 如果 Queue 重新初始化, 取消所有 running tasks + if err := CancelWorkflowTask(setting.DefaultTaskRevoker, task.WorkflowName, task.TaskID, log); err != nil { + log.Errorf("[CancelRunningTask] error: %v", err) + continue + } + } + return nil +} + +// WorfklowTaskSender 监控warpdrive空闲情况, 如果有空闲, 则发现下一个waiting task给warpdrive +// 并将task状态设置为queued +func WorfklowTaskSender() { + for { + time.Sleep(time.Second * 3) + + sysSetting, err := commonrepo.NewSystemSettingColl().Get() + if err != nil { + log.Errorf("get system stettings error: %v", err) + } + //c.checkAgents() + if !hasAgentAvaiable(int(sysSetting.WorkflowConcurrency)) { + continue + } + t, err := NextWaitingTask() + if err != nil { + // no waiting task found + blockTasks, err := BlockedTaskQueue() + if err != nil { + //no blocked task found + continue + } + for _, blockTask := range blockTasks { + if hasAgentAvaiable(int(sysSetting.WorkflowConcurrency)) { + //判断相同的工作流是否正在运行 + if ParallelRunningAndQueuedTasks(blockTask) { + continue + } + // update agent and queue + if err := updateQueueAndRunTask(blockTask, int(sysSetting.BuildConcurrency)); err != nil { + continue + } + } else { + break + } + } + continue + } + // update agent and queue + if err := updateQueueAndRunTask(t, int(sysSetting.BuildConcurrency)); err != nil { + continue + } + + } +} + +func hasAgentAvaiable(workflowConcurrency int) bool { + return len(RunningAndQueuedTasks()) < int(workflowConcurrency) +} + +func RunningAndQueuedTasks() []*commonmodels.WorkflowQueue { + tasks := make([]*commonmodels.WorkflowQueue, 0) + for _, t := range ListTasks() { + // task状态为TaskQueued说明task已经被send到nsq,wd已经开始处理但是没有返回ack + if t.Status == config.StatusRunning || t.Status == config.StatusQueued { + tasks = append(tasks, t) + } + } + return tasks +} + +func ListTasks() []*commonmodels.WorkflowQueue { + opt := new(commonrepo.ListWorfklowQueueOption) + queues, err := commonrepo.NewWorkflowQueueColl().List(opt) + if err != nil { + log.Errorf("pqColl.List error: %v", err) + } + return queues +} + +// NextWaitingTask 查询下一个等待的task +func NextWaitingTask() (*commonmodels.WorkflowQueue, error) { + opt := &commonrepo.ListWorfklowQueueOption{ + Status: config.StatusWaiting, + } + + tasks, err := commonrepo.NewWorkflowQueueColl().List(opt) + if err != nil { + return nil, err + } + + for _, t := range tasks { + return t, nil + } + + return nil, errors.New("no waiting task found") +} + +func BlockedTaskQueue() ([]*commonmodels.WorkflowQueue, error) { + opt := &commonrepo.ListWorfklowQueueOption{ + Status: config.StatusBlocked, + } + + queues, err := commonrepo.NewWorkflowQueueColl().List(opt) + if err != nil || len(queues) == 0 { + return nil, errors.New("no blocked task found") + } + + return queues, nil +} + +func ParallelRunningAndQueuedTasks(currentTask *commonmodels.WorkflowQueue) bool { + for _, t := range ListTasks() { + // task状态为TaskQueued说明task已经被send到nsq,wd已经开始处理但是没有返回ack + if t.Status != config.StatusRunning && t.Status != config.StatusQueued { + continue + } + if t.WorkflowName == currentTask.WorkflowName { + return true + } + } + return false +} + +func updateQueueAndRunTask(t *commonmodels.WorkflowQueue, jobConcurrency int) error { + logger := log.SugaredLogger() + // 更新队列状态为TaskQueued + workflowTask, err := commonrepo.NewworkflowTaskv4Coll().Find(t.WorkflowName, t.TaskID) + if err != nil { + logger.Errorf("%s:%d get workflow task error: %v", t.WorkflowName, t.TaskID, err) + return fmt.Errorf("%s:%d get workflow task error: %v", t.WorkflowName, t.TaskID, err) + } + workflowTask.Status = t.Status + if success := UpdateQueue(workflowTask); !success { + logger.Errorf("%s:%d update t status error", t.WorkflowName, t.TaskID) + return fmt.Errorf("%s:%d update t status error", t.WorkflowName, t.TaskID) + } + ctx := context.Background() + go NewWorkflowController(workflowTask, logger).Run(ctx, jobConcurrency) + return nil +} + +func UpdateQueue(task *commonmodels.WorkflowTask) bool { + if err := commonrepo.NewWorkflowQueueColl().Update(ConvertTaskToQueue(task)); err != nil { + return false + } + return true +} + +func ConvertTaskToQueue(task *commonmodels.WorkflowTask) *commonmodels.WorkflowQueue { + return &commonmodels.WorkflowQueue{ + TaskID: task.TaskID, + ProjectName: task.ProjectName, + WorkflowName: task.WorkflowName, + Status: task.Status, + TaskCreator: task.TaskCreator, + TaskRevoker: task.TaskRevoker, + CreateTime: task.CreateTime, + MultiRun: task.MultiRun, + } +} + +func Remove(taskQueue *commonmodels.WorkflowQueue) error { + return commonrepo.NewWorkflowQueueColl().Delete(taskQueue) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 5932abfd..8229a3d6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -24,6 +24,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" uuid "github.com/satori/go.uuid" "go.uber.org/zap" @@ -48,16 +49,43 @@ func NewWorkflowController(workflowTask *commonmodels.WorkflowTask, logger *zap. return ctl } -func CancelWorkflowTask(workflowName string, id int64) error { - value, ok := cancelChannelMap.Load(fmt.Sprintf("%s-%d", workflowName, id)) +func CancelWorkflowTask(userName, workflowName string, taskID int64, logger *zap.SugaredLogger) error { + t, err := mongodb.NewworkflowTaskv4Coll().Find(workflowName, taskID) + if err != nil { + logger.Errorf("[%s] task: %s:%d not found", userName, workflowName, taskID) + return err + } + + if t.Status == config.StatusPassed { + logger.Errorf("[%s] task: %s:%d is passed, cannot cancel", userName, workflowName, taskID) + return fmt.Errorf("task: %s:%d is passed, cannot cancel", workflowName, taskID) + } + + t.Status = config.StatusCancelled + t.TaskRevoker = userName + + logger.Infof("[%s] CancelRunningTask %s:%d", userName, taskID, taskID) + + if err := mongodb.NewworkflowTaskv4Coll().Update(t.ID.Hex(), t); err != nil { + logger.Errorf("[%s] update task: %s:%d error: %v", userName, workflowName, taskID, err) + return err + } + + q := ConvertTaskToQueue(t) + if err := Remove(q); err != nil { + logger.Errorf("[%s] remove queue task: %s:%d error: %v", userName, workflowName, taskID, err) + return err + } + + value, ok := cancelChannelMap.Load(fmt.Sprintf("%s-%d", workflowName, taskID)) if !ok { - return fmt.Errorf("no mactched task found, id: %d, workflow name: %s", id, workflowName) + return fmt.Errorf("no mactched task found, id: %d, workflow name: %s", taskID, workflowName) } if f, ok := value.(context.CancelFunc); ok { f() return nil } - return fmt.Errorf("cancel func type mismatched, id: %d, workflow name: %s", id, workflowName) + return fmt.Errorf("cancel func type mismatched, id: %d, workflow name: %s", taskID, workflowName) } func (c *workflowCtl) Run(ctx context.Context, concurrency int) { @@ -132,10 +160,44 @@ func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { } func (c *workflowCtl) updateWorkflowTask() { + taskInColl, err := commonrepo.NewworkflowTaskv4Coll().Find(c.workflowTask.WorkflowName, c.workflowTask.TaskID) + if err != nil { + c.logger.Errorf("find workflow task v4 %s failed,error: %v", c.workflowTask.WorkflowName, err) + return + } + // 如果当前状态已经通过或者失败, 不处理新接受到的ACK + if taskInColl.Status == config.StatusPassed || taskInColl.Status == config.StatusFailed || taskInColl.Status == config.StatusTimeout { + c.logger.Infof("%s:%d:%s task already done", c.workflowTask.WorkflowName, c.workflowTask.TaskID, taskInColl.Status) + return + } + // 如果当前状态已经是取消状态, 一般为用户取消了任务, 此时任务在短暂时间内会继续运行一段时间, + if taskInColl.Status == config.StatusCancelled { + // Task终止状态可能为Pass, Fail, Cancel, Timeout + // backend 会继续接受到ACK, 在这种情况下, 终止状态之外的ACK都无需处理,避免出现取消之后又被重置成运行态 + if c.workflowTask.Status != config.StatusFailed && c.workflowTask.Status != config.StatusPassed && c.workflowTask.Status != config.StatusCancelled && c.workflowTask.Status != config.StatusTimeout { + c.logger.Infof("%s:%d task has been cancelled, ACK dropped", c.workflowTask.WorkflowName, c.workflowTask.TaskID) + return + } + } + if success := UpdateQueue(c.workflowTask); !success { + c.logger.Errorf("%s:%d update t status error", c.workflowTask.WorkflowName, c.workflowTask.TaskID) + } // TODO update workflow task if err := commonrepo.NewworkflowTaskv4Coll().Update(c.workflowTask.ID.Hex(), c.workflowTask); err != nil { c.logger.Errorf("update workflow task v4 failed,error: %v", err) } + + if c.workflowTask.Status == config.StatusPassed || c.workflowTask.Status == config.StatusFailed || c.workflowTask.Status == config.StatusTimeout || c.workflowTask.Status == config.StatusCancelled { + c.logger.Infof("%s:%d:%v task done", c.workflowTask.WorkflowName, c.workflowTask.TaskID, c.workflowTask.Status) + q := ConvertTaskToQueue(c.workflowTask) + if err := Remove(q); err != nil { + c.logger.Errorf("remove queue task: %s:%d error: %v", c.workflowTask.WorkflowName, c.workflowTask.TaskID, err) + } + if err = commonrepo.NewworkflowTaskv4Coll().ArchiveHistoryWorkflowTask(c.workflowTask.WorkflowName, 100); err != nil { + c.logger.Errorf("ArchiveHistoryWorkflowTask error: %v", err) + } + } + } func (c *workflowCtl) getGlobalContext(key string) (string, bool) { -- Gitee From 6113997a2774ff3f85d6c6d2fa84ed6b1a790e58 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 28 Jun 2022 21:29:24 +0800 Subject: [PATCH 2491/2647] add common workflow log interface Signed-off-by: guoyu --- .../common/repository/models/workflow_v4.go | 24 +- .../aslan/core/log/handler/log.go | 13 + .../aslan/core/log/handler/router.go | 2 + .../aslan/core/log/handler/sse.go | 29 +++ .../aslan/core/log/service/log.go | 9 + .../aslan/core/log/service/sse.go | 24 ++ .../workflow/service/jobcontroller/job.go | 25 +- .../service/jobcontroller/job_freestyle.go | 37 ++- .../service/jobcontroller/kubernetes.go | 225 ++++++++++++++---- .../jobexecutor/executor/executor.go | 40 ++-- pkg/setting/consts.go | 4 + 11 files changed, 332 insertions(+), 100 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 85f3701b..92a8c4da 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -28,15 +28,16 @@ import ( type WorkflowV4 struct { ID primitive.ObjectID `bson:"_id,omitempty" yaml:"-" json:"id"` - Name string `bson:"name" yaml:"name" json:"name"` - KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` - Stages []*WorkflowStage `bson:"stages" yaml:"stages" json:"stages"` - Project string `bson:"project" yaml:"project" json:"project"` - Description string `bson:"description" yaml:"description" json:"description"` - CreatedBy string `bson:"created_by" yaml:"created_by" json:"created_by"` - CreateTime int64 `bson:"create_time" yaml:"create_time" json:"create_time"` - UpdatedBy string `bson:"updated_by" yaml:"updated_by" json:"updated_by"` - UpdateTime int64 `bson:"update_time" yaml:"update_time" json:"update_time"` + Name string `bson:"name" yaml:"name" json:"name"` + KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` + Stages []*WorkflowStage `bson:"stages" yaml:"stages" json:"stages"` + Project string `bson:"project" yaml:"project" json:"project"` + Description string `bson:"description" yaml:"description" json:"description"` + CreatedBy string `bson:"created_by" yaml:"created_by" json:"created_by"` + CreateTime int64 `bson:"create_time" yaml:"create_time" json:"create_time"` + UpdatedBy string `bson:"updated_by" yaml:"updated_by" json:"updated_by"` + UpdateTime int64 `bson:"update_time" yaml:"update_time" json:"update_time"` + MultiRun bool `bson:"multi_run" yaml:"multi_run" json:"multi_run"` } type WorkflowStage struct { @@ -83,7 +84,7 @@ type ServiceAndBuild struct { Image string `bson:"-" yaml:"-" json:"image"` Package string `bson:"-" yaml:"-" json:"package"` KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` - Repos []*types.Repository `bson:"-" yaml:"-" json:"repos"` + Repos []*types.Repository `bson:"-" yaml:"-" json:"repos"` } type ZadigDeployJobSpec struct { @@ -93,7 +94,7 @@ type ZadigDeployJobSpec struct { Source config.DeploySourceType `bson:"source" yaml:"source" json:"source"` // 当 source 为 fromjob 时需要,指定部署镜像来源是上游哪一个构建任务 JobName string `bson:"job_name" yaml:"job_name" json:"job_name"` - ServiceAndImages []*ServiceAndImage `bson:"-" yaml:"-" json:"service_and_images"` + ServiceAndImages []*ServiceAndImage `bson:"-" yaml:"-" json:"service_and_images"` } type ServiceAndImage struct { @@ -111,6 +112,7 @@ type JobProperties struct { Namespace string `bson:"namespace" json:"namespace"` Args []*KeyVal `bson:"args" json:"args"` Paths string `bson:"-" json:"-"` + LogFileName string `bson:"log_file_name" json:"log_file_name"` DockerHost string `bson:"-" json:"docker_host,omitempty"` } diff --git a/pkg/microservice/aslan/core/log/handler/log.go b/pkg/microservice/aslan/core/log/handler/log.go index 62ba19b0..e1cdda7a 100644 --- a/pkg/microservice/aslan/core/log/handler/log.go +++ b/pkg/microservice/aslan/core/log/handler/log.go @@ -60,6 +60,19 @@ func GetWorkflowBuildJobContainerLogs(c *gin.Context) { ctx.Resp, ctx.Err = logservice.GetWorkflowBuildJobContainerLogs(strings.ToLower(c.Param("pipelineName")), c.Param("serviceName"), c.Query("type"), taskID, ctx.Logger) } +func GetWorkflowV4JobContainerLogs(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("taskID"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + // Use all lowercase job names to avoid subdomain errors + ctx.Resp, ctx.Err = logservice.GetWorkflowV4JobContainerLogs(strings.ToLower(c.Param("workflowName")), c.Param("jobName"), taskID, ctx.Logger) +} + func GetTestJobContainerLogs(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/log/handler/router.go b/pkg/microservice/aslan/core/log/handler/router.go index 7b366080..aa762298 100644 --- a/pkg/microservice/aslan/core/log/handler/router.go +++ b/pkg/microservice/aslan/core/log/handler/router.go @@ -35,6 +35,7 @@ func (*Router) Inject(router *gin.RouterGroup) { log.GET("/workflow/:pipelineName/tasks/:taskId/tests/:testName/service/:serviceName", GetWorkflowTestJobContainerLogs) log.GET("/v3/workflow/:workflowName/tasks/:taskId", GetWorkflowBuildV3JobContainerLogs) log.GET("/scanning/:id/task/:scan_id", GetScanningContainerLogs) + log.GET("/v4/workflow/:workflowName/tasks/:taskID/jobs/:jobName", GetWorkflowV4JobContainerLogs) } sse := router.Group("sse") @@ -47,5 +48,6 @@ func (*Router) Inject(router *gin.RouterGroup) { sse.GET("/service/build/:serviceName/:envName/:productName", GetServiceJobContainerLogsSSE) sse.GET("/v3/workflow/build/:workflowName/:taskId/:lines", GetWorkflowBuildV3JobContainerLogsSSE) sse.GET("/scanning/:id/task/:scan_id", GetScanningContainerLogsSSE) + sse.GET("/v4/workflow/:workflowName/:taskID/:jobName/:lines", GetWorkflowJobContainerLogsSSE) } } diff --git a/pkg/microservice/aslan/core/log/handler/sse.go b/pkg/microservice/aslan/core/log/handler/sse.go index c022d6ba..6003d9db 100644 --- a/pkg/microservice/aslan/core/log/handler/sse.go +++ b/pkg/microservice/aslan/core/log/handler/sse.go @@ -79,6 +79,35 @@ func GetBuildJobContainerLogsSSE(c *gin.Context) { }, ctx.Logger) } +func GetWorkflowJobContainerLogsSSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + + taskID, err := strconv.ParseInt(c.Param("taskID"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + internalhandler.JSONResponse(c, ctx) + return + } + + tails, err := strconv.ParseInt(c.Param("lines"), 10, 64) + if err != nil { + tails = int64(10) + } + + internalhandler.Stream(c, func(ctx1 context.Context, streamChan chan interface{}) { + logservice.WorkflowTaskV4ContainerLogStream( + ctx1, streamChan, + &logservice.GetContainerOptions{ + Namespace: config.Namespace(), + PipelineName: c.Param("workflowName"), + SubTask: c.Param("jobName"), + TaskID: taskID, + TailLines: tails, + }, + ctx.Logger) + }, ctx.Logger) +} + func GetWorkflowBuildJobContainerLogsSSE(c *gin.Context) { ctx := internalhandler.NewContext(c) diff --git a/pkg/microservice/aslan/core/log/service/log.go b/pkg/microservice/aslan/core/log/service/log.go index cc1044ca..e7f6f138 100644 --- a/pkg/microservice/aslan/core/log/service/log.go +++ b/pkg/microservice/aslan/core/log/service/log.go @@ -55,6 +55,15 @@ func GetWorkflowBuildJobContainerLogs(pipelineName, serviceName, buildType strin return buildLog, nil } +func GetWorkflowV4JobContainerLogs(workflowName, jobName string, taskID int64, log *zap.SugaredLogger) (string, error) { + buildJobNamePrefix := jobName + buildLog, err := getContainerLogFromS3(workflowName, buildJobNamePrefix, taskID, log) + if err != nil { + return "", err + } + return buildLog, nil +} + func GetTestJobContainerLogs(pipelineName, serviceName string, taskID int64, log *zap.SugaredLogger) (string, error) { taskName := fmt.Sprintf("%s-%s-%d-%s-%s", config.SingleType, pipelineName, taskID, config.TaskTestingV2, serviceName) return getContainerLogFromS3(pipelineName, taskName, taskID, log) diff --git a/pkg/microservice/aslan/core/log/service/sse.go b/pkg/microservice/aslan/core/log/service/sse.go index 76f05b98..9482f406 100644 --- a/pkg/microservice/aslan/core/log/service/sse.go +++ b/pkg/microservice/aslan/core/log/service/sse.go @@ -187,6 +187,16 @@ func TaskContainerLogStream(ctx context.Context, streamChan chan interface{}, op waitAndGetLog(ctx, streamChan, selector, options, log) } +func WorkflowTaskV4ContainerLogStream(ctx context.Context, streamChan chan interface{}, options *GetContainerOptions, log *zap.SugaredLogger) { + if options == nil { + return + } + log.Debugf("Start to get task container log.") + + selector := getWorkflowSelector(options) + waitAndGetLog(ctx, streamChan, selector, options, log) +} + func TestJobContainerLogStream(ctx context.Context, streamChan chan interface{}, options *GetContainerOptions, log *zap.SugaredLogger) { options.SubTask = string(config.TaskTestingV2) selector := getPipelineSelector(options) @@ -276,3 +286,17 @@ func getPipelineSelector(options *GetContainerOptions) labels.Selector { return ret.AsSelector() } + +func getWorkflowSelector(options *GetContainerOptions) labels.Selector { + retMap := map[string]string{ + setting.JobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(options.PipelineName), options.TaskID), + setting.JobLabelNameKey: strings.Replace(options.SubTask, "_", "-", -1), + } + // no need to add labels with empty value to a job + for k, v := range retMap { + if len(v) == 0 { + delete(retMap, k) + } + } + return labels.Set(retMap).AsSelector() +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go index 04e3ab4b..e60eb6b7 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go @@ -18,6 +18,8 @@ package jobcontroller import ( "context" + "io" + "os" "sync" "time" @@ -30,11 +32,14 @@ import ( type JobCtl interface { Run(ctx context.Context) + Wait(ctx context.Context) + Complete(ctx context.Context) } func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger, ack func()) { job.Status = config.StatusRunning job.StartTime = time.Now().Unix() + ack() // set default timeout if job.Properties.Timeout <= 0 { job.Properties.Timeout = 600 @@ -69,10 +74,11 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm logger.Infof("finish job: %s,status: %s", job.Name, job.Status) ack() }() - var jobCtl JobCtl switch job.JobType { - case "deploy": + case string(config.JobZadigDeploy): + fallthrough + case string(config.JobDeploy): // do deploy inside aslan instead of jobexecutor. status, err := stepcontroller.RunSteps(ctx, workflowCtx, &job.Properties.Paths, job.Steps, logger) job.Status = status @@ -84,7 +90,10 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm default: jobCtl = NewFreestyleJobCtl(job, workflowCtx, ack, logger) } + jobCtl.Run(ctx) + jobCtl.Wait(ctx) + jobCtl.Complete(ctx) } func RunJobs(ctx context.Context, jobs []*commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, concurrency int, logger *zap.SugaredLogger, ack func()) { @@ -144,3 +153,15 @@ func (p *Pool) work() { p.wg.Done() } } + +func saveFile(src io.Reader, localFile string) error { + out, err := os.Create(localFile) + if err != nil { + return err + } + + defer out.Close() + + _, err = io.Copy(out, src) + return err +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 5a5ce875..b3da8fff 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -31,6 +31,8 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/updater" "go.uber.org/zap" "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" crClient "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -45,6 +47,8 @@ type FreestyleJobCtl struct { workflowCtx *commonmodels.WorkflowTaskCtx logger *zap.SugaredLogger kubeclient crClient.Client + clientset kubernetes.Interface + restConfig *rest.Config paths *string ack func() } @@ -62,22 +66,18 @@ func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.Wor } func (c *FreestyleJobCtl) Run(ctx context.Context) { - c.run(ctx) - c.wait(ctx) - c.complete(ctx) -} - -func (c *FreestyleJobCtl) run(ctx context.Context) { // get kube client hubServerAddr := config.HubServerAddress() switch c.job.Properties.ClusterID { case setting.LocalClusterID: c.job.Properties.Namespace = zadigconfig.Namespace() c.kubeclient = krkubeclient.Client() + c.clientset = krkubeclient.Clientset() + c.restConfig = krkubeclient.RESTConfig() default: c.job.Properties.Namespace = setting.AttachedClusterNamespace - crClient, _, _, err := GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) + crClient, clientset, restConfig, err := GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) if err != nil { c.job.Status = config.StatusFailed c.job.Error = err.Error() @@ -85,6 +85,8 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { return } c.kubeclient = crClient + c.clientset = clientset + c.restConfig = restConfig } // decide which docker host to use. @@ -195,12 +197,12 @@ func (c *FreestyleJobCtl) run(ctx context.Context) { c.logger.Infof("succeed to create job %s", c.jobName) } -func (c *FreestyleJobCtl) wait(ctx context.Context) { - status := waitJobEndWithFile(ctx, int(c.job.Properties.Timeout), c.job.Properties.Namespace, c.jobName, c.kubeclient, c.logger) +func (c *FreestyleJobCtl) Wait(ctx context.Context) { + status := waitJobEndWithFile(ctx, int(c.job.Properties.Timeout), c.job.Properties.Namespace, c.jobName, true, c.kubeclient, c.clientset, c.restConfig, c.logger) c.job.Status = status } -func (c *FreestyleJobCtl) complete(ctx context.Context) { +func (c *FreestyleJobCtl) Complete(ctx context.Context) { jobLabel := &JobLabel{ WorkflowName: c.workflowCtx.WorkflowName, TaskID: c.workflowCtx.TaskID, @@ -221,7 +223,7 @@ func (c *FreestyleJobCtl) complete(ctx context.Context) { }() // get job outputs info from pod terminate message. - outputs, err := getJobOutput(c.job.Properties.Namespace, jobLabel, c.kubeclient) + outputs, err := getJobOutput(c.job.Properties.Namespace, c.job.Name, jobLabel, c.kubeclient) if err != nil { c.logger.Error(err) c.job.Error = err.Error() @@ -232,14 +234,11 @@ func (c *FreestyleJobCtl) complete(ctx context.Context) { c.workflowCtx.GlobalContextSet(strings.Join([]string{c.job.Name, output.Name}, "."), output.Value) } - // err := saveContainerLog(pipelineTask, p.KubeNamespace, "", c.FileName, jobLabel, c.kubeclient) - // if err != nil { - // p.Log.Error(err) - // p.Task.Error = err.Error() - // return - // } - - // p.Task.LogFile = p.FileName + if err := saveContainerLog(c.job, c.workflowCtx.WorkflowName, c.workflowCtx.TaskID, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + c.job.Error = err.Error() + return + } } func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger) *JobContext { diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 6c813a19..7fe9fefb 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -17,14 +17,19 @@ limitations under the License. package jobcontroller import ( + "bytes" "context" "encoding/json" "fmt" + "os" "path" + "path/filepath" + "sort" "strconv" "strings" "time" + s3tool "github.com/koderover/zadig/pkg/tool/s3" "go.uber.org/zap" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -37,13 +42,19 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/warpdrive/core/service/types/task" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/shared/kube/wrapper" + "github.com/koderover/zadig/pkg/tool/kube/containerlog" "github.com/koderover/zadig/pkg/tool/kube/getter" + "github.com/koderover/zadig/pkg/tool/kube/podexec" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" + commontypes "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/types/job" + "github.com/koderover/zadig/pkg/util" ) const ( @@ -79,12 +90,6 @@ type JobLabel struct { JobType string } -const ( - jobLabelTaskKey = "s-task" - jobLabelNameKey = "s-name" - jobLabelSTypeKey = "s-type" -) - func ensureDeleteConfigMap(namespace string, jobLabel *JobLabel, kubeClient crClient.Client) error { ls := getJobLabels(jobLabel) return updater.DeleteConfigMapsAndWait(namespace, labels.Set(ls).AsSelector(), kubeClient) @@ -98,9 +103,9 @@ func ensureDeleteJob(namespace string, jobLabel *JobLabel, kubeClient crClient.C // getJobLabels get labels k-v map from JobLabel struct func getJobLabels(jobLabel *JobLabel) map[string]string { retMap := map[string]string{ - jobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.WorkflowName), jobLabel.TaskID), - jobLabelNameKey: strings.Replace(jobLabel.JobName, "_", "-", -1), - jobLabelSTypeKey: strings.Replace(jobLabel.JobType, "_", "-", -1), + setting.JobLabelTaskKey: fmt.Sprintf("%s-%d", strings.ToLower(jobLabel.WorkflowName), jobLabel.TaskID), + setting.JobLabelNameKey: strings.Replace(jobLabel.JobName, "_", "-", -1), + setting.JobLabelSTypeKey: strings.Replace(jobLabel.JobType, "_", "-", -1), } // no need to add labels with empty value to a job for k, v := range retMap { @@ -137,13 +142,13 @@ func getReaperImage(reaperImage, buildOS string) string { } func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, resReq setting.Request, resReqSpec setting.RequestSpec, jobTask *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, registries []*task.RegistryNamespace) (*batchv1.Job, error) { - tailLogCommandTemplate := `tail -f %s & -while [ -f %s ]; -do - sleep 1s; -done; -` - tailLogCommand := fmt.Sprintf(tailLogCommandTemplate, ZadigLogFile, ZadigLifeCycleFile) + // tailLogCommandTemplate := `tail -f %s & + // while [ -f %s ]; + // do + // sleep 1s; + // done; + // ` + // tailLogCommand := fmt.Sprintf(tailLogCommandTemplate, ZadigLogFile, ZadigLifeCycleFile) labels := getJobLabels(&JobLabel{ WorkflowName: workflowCtx.WorkflowName, @@ -186,28 +191,28 @@ done; Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, ImagePullSecrets: ImagePullSecrets, - InitContainers: []corev1.Container{ - { - ImagePullPolicy: corev1.PullIfNotPresent, - Name: "init-log-file", - Image: BusyBoxImage, - VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), - Command: []string{"/bin/sh", "-c", fmt.Sprintf("touch %s %s", ZadigLogFile, ZadigLifeCycleFile)}, - }, - }, + // InitContainers: []corev1.Container{ + // { + // ImagePullPolicy: corev1.PullIfNotPresent, + // Name: "init-log-file", + // Image: BusyBoxImage, + // VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), + // Command: []string{"/bin/sh", "-c", fmt.Sprintf("touch %s %s", ZadigLogFile, ZadigLifeCycleFile)}, + // }, + // }, Containers: []corev1.Container{ { ImagePullPolicy: corev1.PullAlways, - Name: labels[jobLabelNameKey], + Name: jobTask.Name, Image: jobImage, - Command: []string{"/bin/sh", "-c", "jobexecutor"}, - Lifecycle: &corev1.Lifecycle{ - PreStop: &corev1.Handler{ - Exec: &corev1.ExecAction{ - Command: []string{"/bin/sh", "-c", fmt.Sprintf("rm %s", ZadigLifeCycleFile)}, - }, - }, - }, + // Command: []string{"/bin/sh", "-c", "jobexecutor"}, + // Lifecycle: &corev1.Lifecycle{ + // PreStop: &corev1.Handler{ + // Exec: &corev1.ExecAction{ + // Command: []string{"/bin/sh", "-c", fmt.Sprintf("rm %s", ZadigLifeCycleFile)}, + // }, + // }, + // }, Env: []corev1.EnvVar{ { Name: "JOB_CONFIG_FILE", @@ -225,15 +230,15 @@ done; TerminationMessagePolicy: corev1.TerminationMessageReadFile, TerminationMessagePath: job.JobTerminationFile, }, - { - ImagePullPolicy: corev1.PullIfNotPresent, - Name: "log", - Image: BusyBoxImage, - VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), - Command: []string{"/bin/sh", "-c"}, - Args: []string{tailLogCommand}, - Lifecycle: &corev1.Lifecycle{}, - }, + // { + // ImagePullPolicy: corev1.PullIfNotPresent, + // Name: "log", + // Image: BusyBoxImage, + // VolumeMounts: getVolumeMounts(workflowCtx.ConfigMapMountDir), + // Command: []string{"/bin/sh", "-c"}, + // Args: []string{tailLogCommand}, + // Lifecycle: &corev1.Lifecycle{}, + // }, }, Volumes: getVolumes(jobName), }, @@ -374,12 +379,12 @@ func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSp func int32Ptr(i int32) *int32 { return &i } -func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, kubeClient crClient.Client, xl *zap.SugaredLogger) (status config.Status) { +func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, checkFile bool, kubeClient crClient.Client, clientset kubernetes.Interface, restConfig *rest.Config, xl *zap.SugaredLogger) (status config.Status) { xl.Infof("wait job to start: %s/%s", namespace, jobName) - timeout := time.After(time.Duration(taskTimeout) * time.Second) + timeout := time.After(time.Duration(taskTimeout) * time.Minute) podTimeout := time.After(120 * time.Second) - xl.Infof("Timeout of preparing Pod: %s. Timeout of running task: %s.", 120*time.Second, time.Duration(taskTimeout)*time.Second) + xl.Infof("Timeout of preparing Pod: %s. Timeout of running task: %s.", 120*time.Second, time.Duration(taskTimeout)*time.Minute) var started bool for { @@ -423,12 +428,18 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName } // pod is still running if job.Status.Active != 0 { + if !checkFile { + // break only break the select{}, not the outside for{} + break + } pods, err := getter.ListPods(namespace, labels.Set{"job-name": jobName}.AsSelector(), kubeClient) if err != nil { xl.Errorf("failed to find pod with label job-name=%s %v", jobName, err) return config.StatusFailed } + var done, exists bool + var jobStatus commontypes.JobStatus for _, pod := range pods { ipod := wrapper.Pod(pod) @@ -438,11 +449,32 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName if ipod.Failed() { return config.StatusFailed } - if ipod.Succeeded() { - return config.StatusPassed + if !ipod.Finished() { + jobStatus, exists, err = checkDogFoodExistsInContainer(clientset, restConfig, namespace, ipod.Name, ipod.ContainerNames()[0]) + if err != nil { + // Note: + // Currently, this error indicates "the target Pod cannot be accessed" or "the target Pod can be accessed, but the dog food file does not exist". + // In these two scenarios, `Info` is used to print logs because they are not business semantic exceptions. + xl.Infof("Result of checking dog food file %s: %s", pods[0].Name, err) + break + } + if !exists { + break + } } + done = true } + if done { + xl.Infof("Dog food is found, stop to wait %s. Job status: %s.", job.Name, jobStatus) + + switch jobStatus { + case commontypes.JobFail: + return config.StatusFailed + default: + return config.StatusPassed + } + } } else if job.Status.Succeeded != 0 { return config.StatusPassed } else { @@ -454,7 +486,7 @@ func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName } } -func getJobOutput(namespace string, jobLabel *JobLabel, kubeClient crClient.Client) ([]*job.JobOutput, error) { +func getJobOutput(namespace, containerName string, jobLabel *JobLabel, kubeClient crClient.Client) ([]*job.JobOutput, error) { resp := []*job.JobOutput{} ls := getJobLabels(jobLabel) pods, err := getter.ListPods(namespace, labels.Set(ls).AsSelector(), kubeClient) @@ -468,7 +500,7 @@ func getJobOutput(namespace string, jobLabel *JobLabel, kubeClient crClient.Clie return resp, nil } for _, containerStatus := range pod.Status.ContainerStatuses { - if containerStatus.Name != ls[jobLabelNameKey] { + if containerStatus.Name != ls[containerName] { continue } if containerStatus.State.Terminated != nil && len(containerStatus.State.Terminated.Message) != 0 { @@ -481,3 +513,96 @@ func getJobOutput(namespace string, jobLabel *JobLabel, kubeClient crClient.Clie } return resp, nil } + +func saveContainerLog(job *commonmodels.JobTask, workflowName string, taskID int64, jobLabel *JobLabel, kubeClient crClient.Client) error { + selector := labels.Set(getJobLabels(jobLabel)).AsSelector() + pods, err := getter.ListPods(job.Properties.Namespace, selector, kubeClient) + if err != nil { + return err + } + + if len(pods) < 1 { + return fmt.Errorf("no pod found with selector: %s", selector) + } + + if len(pods[0].Status.ContainerStatuses) < 1 { + return fmt.Errorf("no cotainer statuses : %s", selector) + } + + buf := new(bytes.Buffer) + // 默认取第一个build job的第一个pod的第一个container的日志 + sort.SliceStable(pods, func(i, j int) bool { + return pods[i].CreationTimestamp.Before(&pods[j].CreationTimestamp) + }) + + clientSet, err := kubeclient.GetClientset(config.HubServerAddress(), job.Properties.ClusterID) + if err != nil { + log.Errorf("saveContainerLog, get client set error: %s", err) + return err + } + + if err := containerlog.GetContainerLogs(job.Properties.Namespace, pods[0].Name, pods[0].Spec.Containers[0].Name, false, int64(0), buf, clientSet); err != nil { + return fmt.Errorf("failed to get container logs: %s", err) + } + + store, err := commonrepo.NewS3StorageColl().FindDefault() + if err != nil { + return fmt.Errorf("failed to get default s3 storage: %s", err) + } + + if tempFileName, err := util.GenerateTmpFile(); err == nil { + defer func() { + _ = os.Remove(tempFileName) + }() + if err = saveFile(buf, tempFileName); err == nil { + + if store.Subfolder != "" { + store.Subfolder = fmt.Sprintf("%s/%s/%d/%s", store.Subfolder, strings.ToLower(workflowName), taskID, "log") + } else { + store.Subfolder = fmt.Sprintf("%s/%d/%s", strings.ToLower(workflowName), taskID, "log") + } + forcedPathStyle := true + if store.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(store.Endpoint, store.Ak, store.Sk, store.Insecure, forcedPathStyle) + if err != nil { + return fmt.Errorf("saveContainerLog s3 create client error: %v", err) + } + fileName := strings.Replace(strings.ToLower(job.Name), "_", "-", -1) + objectKey := GetObjectPath(store.Subfolder, fileName+".log") + if err = s3client.Upload( + store.Bucket, + tempFileName, + objectKey, + ); err != nil { + return fmt.Errorf("saveContainerLog s3 Upload error: %v", err) + } + } else { + return fmt.Errorf("saveContainerLog saveFile error: %v", err) + } + } else { + return fmt.Errorf("saveContainerLog GenerateTmpFile error: %v", err) + } + return nil +} + +func GetObjectPath(subFolder, name string) string { + // target should not be started with / + if subFolder != "" { + return strings.TrimLeft(filepath.Join(subFolder, name), "/") + } + + return strings.TrimLeft(name, "/") +} + +func checkDogFoodExistsInContainer(clientset kubernetes.Interface, restConfig *rest.Config, namespace, pod, container string) (commontypes.JobStatus, bool, error) { + stdout, _, success, err := podexec.KubeExec(clientset, restConfig, podexec.ExecOptions{ + Command: []string{"/bin/sh", "-c", fmt.Sprintf("test -f %[1]s && cat %[1]s", setting.DogFood)}, + Namespace: namespace, + PodName: pod, + ContainerName: container, + }) + + return commontypes.JobStatus(stdout), success, err +} diff --git a/pkg/microservice/jobexecutor/executor/executor.go b/pkg/microservice/jobexecutor/executor/executor.go index f9804737..922bc090 100644 --- a/pkg/microservice/jobexecutor/executor/executor.go +++ b/pkg/microservice/jobexecutor/executor/executor.go @@ -19,7 +19,7 @@ package executor import ( "context" "fmt" - "os" + "io/ioutil" "time" commonconfig "github.com/koderover/zadig/pkg/config" @@ -29,45 +29,49 @@ import ( "github.com/koderover/zadig/pkg/types" ) -const ( - ZadigContextDir = "/zadig/" - ZadigLogFile = ZadigContextDir + "zadig.log" - ZadigLifeCycleFile = ZadigContextDir + "lifecycle" -) +// const ( +// ZadigContextDir = "/zadig/" +// ZadigLogFile = ZadigContextDir + "zadig.log" +// ZadigLifeCycleFile = ZadigContextDir + "lifecycle" +// ) func Execute(ctx context.Context) error { - f, err := os.OpenFile(ZadigLogFile, os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0755) - if err != nil { - fmt.Println("open log file error") - return err - } - os.Stdout = f - os.Stderr = f + // f, err := os.OpenFile(ZadigLogFile, os.O_WRONLY|os.O_CREATE|os.O_SYNC|os.O_APPEND, 0755) + // if err != nil { + // fmt.Println("open log file error") + // return err + // } + // os.Stdout = f + // os.Stderr = f log.Init(&log.Config{ Level: commonconfig.LogLevel(), NoCaller: true, NoLogLevel: true, Development: commonconfig.Mode() != setting.ReleaseMode, - SendToFile: true, - Filename: ZadigLogFile, + // SendToFile: true, + // Filename: ZadigLogFile, }) start := time.Now() excutor := "job-excutor" + var err error defer func() { - os.Remove(ZadigLifeCycleFile) + // os.Remove(ZadigLifeCycleFile) resultMsg := types.JobSuccess if err != nil { resultMsg = types.JobFail fmt.Printf("Failed to run: %s.\n", err) - fmt.Printf("====================== %s End. Duration: %.2f seconds ======================\n", excutor, time.Since(start).Seconds()) - os.Exit(1) } fmt.Printf("Job Status: %s\n", resultMsg) + dogFoodErr := ioutil.WriteFile(setting.DogFood, []byte(resultMsg), 0644) + if dogFoodErr != nil { + log.Errorf("Failed to create dog food: %s.", dogFoodErr) + } fmt.Printf("====================== %s End. Duration: %.2f seconds ======================\n", excutor, time.Since(start).Seconds()) + time.Sleep(30 * time.Second) }() var j *job.Job diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index c1a8945a..bcaa04e8 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -170,6 +170,10 @@ const ( EditorIDAnnotation = companyLabel + "/" + "editor-id" LastUpdateTimeAnnotation = companyLabel + "/" + "last-update-time" + JobLabelTaskKey = "s-task" + JobLabelNameKey = "s-name" + JobLabelSTypeKey = "s-type" + LabelValueTrue = "true" // Pod status -- Gitee From e2cac5eed1addafc1f09b32321e6dd0623944bbb Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 28 Jun 2022 21:30:34 +0800 Subject: [PATCH 2492/2647] add workflow task preview interface Signed-off-by: guoyu --- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_task_v4.go | 16 +- .../service/workflow/workflow_task_v4.go | 219 ++++++++++++++++-- 3 files changed, 214 insertions(+), 22 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index e006b99d..5abb9757 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -183,6 +183,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { taskV4.POST("", CreateWorkflowTaskV4) taskV4.GET("", ListWorkflowTaskV4) + taskV4.GET("/workflow/:workflowName/task/:taskID", GetWorkflowTaskV4) } bundles := router.Group("bundle-resources") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index 8658c9bf..bcc2d9a5 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "io/ioutil" + "strconv" "github.com/gin-gonic/gin" @@ -61,7 +62,7 @@ func CreateWorkflowTaskV4(c *gin.Context) { return } - ctx.Err = workflow.CreateWorkflowTaskV4(ctx.UserName, args, ctx.Logger) + ctx.Resp, ctx.Err = workflow.CreateWorkflowTaskV4(ctx.UserName, args, ctx.Logger) } func ListWorkflowTaskV4(c *gin.Context) { @@ -81,3 +82,16 @@ func ListWorkflowTaskV4(c *gin.Context) { ctx.Resp = resp ctx.Err = err } + +func GetWorkflowTaskV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("taskID"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + + ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV4(c.Param("workflowName"), taskID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 00d70aa5..f5448a31 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -17,20 +17,80 @@ limitations under the License. package workflow import ( - "context" "fmt" "time" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" - "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/types" + stepspec "github.com/koderover/zadig/pkg/types/step" "go.uber.org/zap" ) +type CreateTaskV4Resp struct { + ProjectName string `json:"project_name"` + WorkflowName string `json:"workflow_name"` + TaskID int64 `json:"task_id"` +} + +type WorkflowTaskPreview struct { + TaskID int64 `bson:"task_id" json:"task_id"` + WorkflowName string `bson:"workflow_name" json:"workflow_name"` + Status config.Status `bson:"status" json:"status,omitempty"` + TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` + TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` + CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Stages []*StageTaskPreview `bson:"stages" json:"stages"` + ProjectName string `bson:"project_name" json:"project_name"` + Error string `bson:"error,omitempty" json:"error,omitempty"` + IsRestart bool `bson:"is_restart" json:"is_restart"` +} + +type StageTaskPreview struct { + Name string `bson:"name" json:"name"` + Status config.Status `bson:"status" json:"status"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Parallel bool `bson:"parallel" json:"parallel"` + Approval *commonmodels.Approval `bson:"approval" json:"approval"` + Jobs []*JobTaskPreview `bson:"jobs" json:"jobs"` +} + +type JobTaskPreview struct { + Name string `bson:"name" json:"name"` + JobType string `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Error string `bson:"error" json:"error"` + Spec interface{} `bson:"spec" json:"spec"` +} + +type ZadigBuildJobSpec struct { + Repos []*types.Repository `bson:"repos" json:"repos"` + Image string `bson:"image" json:"image"` + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` +} + +type ZadigDeployJobSpec struct { + Env string `bson:"env" json:"env"` + ServiceAndImages []*ServiceAndImage `bson:"service_and_images" json:"service_and_images"` +} + +type ServiceAndImage struct { + ServiceName string `bson:"service_name" json:"service_name"` + ServiceModule string `bson:"service_module" json:"service_module"` + Image string `bson:"image" json:"image"` +} + func GetWorkflowv4Preset(workflowName string, log *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { workflow, err := commonrepo.NewWorkflowV4Coll().Find(workflowName) if err != nil { @@ -48,13 +108,19 @@ func GetWorkflowv4Preset(workflowName string, log *zap.SugaredLogger) (*commonmo return workflow, nil } -func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *zap.SugaredLogger) error { +func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *zap.SugaredLogger) (*CreateTaskV4Resp, error) { + resp := &CreateTaskV4Resp{ + ProjectName: workflow.Project, + WorkflowName: workflow.Name, + } workflowTask := &commonmodels.WorkflowTask{} nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.WorkflowTaskV4Fmt, workflow.Name)) if err != nil { log.Errorf("Counter.GetNextSeq error: %v", err) - return e.ErrGetCounter.AddDesc(err.Error()) + return resp, e.ErrGetCounter.AddDesc(err.Error()) } + resp.TaskID = nextTaskID + workflowTask.TaskID = nextTaskID workflowTask.TaskCreator = user workflowTask.TaskRevoker = user @@ -70,33 +136,37 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z } workflowTask.Stages = append(workflowTask.Stages, stageTask) for _, job := range stage.Jobs { - if err := setZadigBuildRepos(job, log); err != nil { - log.Errorf("set build info error: %v", err) - return e.ErrCreateTask.AddDesc(err.Error()) + if job.JobType == config.JobZadigBuild { + if err := setZadigBuildRepos(job, log); err != nil { + log.Errorf("set build info error: %v", err) + return resp, e.ErrCreateTask.AddDesc(err.Error()) + } } jobs, err := jobctl.ToJobs(job, workflow, nextTaskID) if err != nil { log.Errorf("cannot create workflow %s, the error is: %v", workflow.Name, err) - return e.ErrCreateTask.AddDesc(err.Error()) + return resp, e.ErrCreateTask.AddDesc(err.Error()) } stageTask.Jobs = append(stageTask.Jobs, jobs...) } } - idString, err := commonrepo.NewworkflowTaskv4Coll().Create(workflowTask) - if err != nil { - log.Errorf("cannot create workflow task %s, the error is: %v", workflow.Name, err) - return e.ErrCreateTask.AddDesc(err.Error()) + // idString, err := commonrepo.NewworkflowTaskv4Coll().Create(workflowTask) + // if err != nil { + // log.Errorf("cannot create workflow task %s, the error is: %v", workflow.Name, err) + // return resp, e.ErrCreateTask.AddDesc(err.Error()) + // } + // id, err := primitive.ObjectIDFromHex(idString) + // if err != nil { + // log.Errorf("transform task id error: %v", err) + // return resp, e.ErrCreateTask.AddDesc(err.Error()) + // } + // workflowTask.ID = id + if err := workflowcontroller.CreateTask(workflowTask); err != nil { + log.Errorf("create workflow task error: %v", err) + return resp, e.ErrCreateTask.AddDesc(err.Error()) } - id, err := primitive.ObjectIDFromHex(idString) - if err != nil { - log.Errorf("transform task id error: %v", err) - return e.ErrCreateTask.AddDesc(err.Error()) - } - ctx := context.Background() - workflowTask.ID = id - go workflowcontroller.NewWorkflowController(workflowTask, log).Run(ctx, 5) - return nil + return resp, nil } func UpdateWorkflowTaskV4(id string, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { @@ -120,6 +190,113 @@ func ListWorkflowTaskV4(workflowName string, pageNum, pageSize int64, logger *za return resp, total, nil } +func GetWorkflowTaskV4(workflowName string, taskID int64, logger *zap.SugaredLogger) (*WorkflowTaskPreview, error) { + task, err := commonrepo.NewworkflowTaskv4Coll().Find(workflowName, taskID) + if err != nil { + logger.Errorf("list workflowTaskV4 error: %s", err) + return nil, err + } + resp := &WorkflowTaskPreview{ + TaskID: task.TaskID, + WorkflowName: task.WorkflowName, + ProjectName: task.ProjectName, + Status: task.Status, + TaskCreator: task.TaskCreator, + TaskRevoker: task.TaskRevoker, + CreateTime: task.CreateTime, + StartTime: task.StartTime, + EndTime: task.EndTime, + Error: task.Error, + IsRestart: task.IsRestart, + } + for _, stage := range task.Stages { + resp.Stages = append(resp.Stages, &StageTaskPreview{ + Name: stage.Name, + Status: stage.Status, + StartTime: stage.StartTime, + EndTime: stage.EndTime, + Parallel: stage.Parallel, + Approval: stage.Approval, + Jobs: jobsToJobPreviews(stage.Jobs), + }) + } + return resp, nil +} + +func jobsToJobPreviews(jobs []*commonmodels.JobTask) []*JobTaskPreview { + resp := []*JobTaskPreview{} + for _, job := range jobs { + jobPreview := &JobTaskPreview{ + Name: job.Name, + Status: job.Status, + StartTime: job.StartTime, + EndTime: job.EndTime, + Error: job.Error, + JobType: job.JobType, + } + switch job.JobType { + case string(config.JobZadigBuild): + spec := ZadigBuildJobSpec{} + for _, arg := range job.Properties.Args { + if arg.Key == "IMAGE" { + spec.Image = arg.Value + continue + } + if arg.Key == "SERVICE" { + spec.ServiceName = arg.Value + continue + } + if arg.Key == "SERVICE_MODULE" { + spec.ServiceModule = arg.Value + continue + } + } + for _, step := range job.Steps { + if step.StepType == config.StepGit { + stepSpec := &stepspec.StepGitSpec{} + commonmodels.IToi(step.Spec, &stepSpec) + spec.Repos = stepSpec.Repos + continue + } + } + jobPreview.Spec = spec + case string(config.JobZadigDeploy): + spec := ZadigDeployJobSpec{} + for _, step := range job.Steps { + if step.StepType == config.StepDeploy { + stepSpec := &stepspec.StepDeploySpec{} + commonmodels.IToi(step.Spec, &stepSpec) + spec.Env = stepSpec.Env + spec.ServiceAndImages = append(spec.ServiceAndImages, &ServiceAndImage{ + ServiceName: stepSpec.ServiceName, + ServiceModule: stepSpec.ServiceModule, + Image: stepSpec.Image, + }) + continue + } + if step.StepType == config.StepHelmDeploy { + stepSpec := &stepspec.StepHelmDeploySpec{} + commonmodels.IToi(step.Spec, &stepSpec) + spec.Env = stepSpec.Env + for _, imageAndmodule := range stepSpec.ImageAndModules { + spec.ServiceAndImages = append(spec.ServiceAndImages, &ServiceAndImage{ + ServiceName: stepSpec.ServiceName, + ServiceModule: imageAndmodule.ServiceModule, + Image: imageAndmodule.Image, + }) + } + continue + } + } + jobPreview.Spec = spec + default: + jobPreview.Spec = job.Steps + } + resp = append(resp, jobPreview) + } + return resp +} + func setZadigBuildRepos(job *commonmodels.Job, logger *zap.SugaredLogger) error { spec := &commonmodels.ZadigBuildJobSpec{} if err := commonmodels.IToi(job.Spec, spec); err != nil { -- Gitee From fc9e5147f90244b2c89d15b9b0d0c1afaa1c2462 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 28 Jun 2022 21:31:12 +0800 Subject: [PATCH 2493/2647] add workflow lint Signed-off-by: guoyu --- .../workflow/service/workflow/workflow_v4.go | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 219957d2..952385cc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -18,6 +18,7 @@ package workflow import ( "fmt" + "regexp" "time" "go.uber.org/zap" @@ -25,16 +26,24 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" + "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) +const ( + JobNameRegx = "^[a-z][a-z0-9-]{1,32}$" +) + func CreateWorkflowV4(user string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { _, err := commonrepo.NewWorkflowV4Coll().Find(workflow.Name) if err == nil { errStr := fmt.Sprintf("workflow v4 [%s] 在项目 [%s] 中已经存在!", workflow.Name, workflow.Project) return e.ErrUpsertWorkflow.AddDesc(errStr) } + if err := workflowLint(workflow, logger); err != nil { + return err + } workflow.CreatedBy = user workflow.UpdatedBy = user @@ -64,6 +73,9 @@ func UpdateWorkflowV4(name, user string, inputWorkflow *commonmodels.WorkflowV4, logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) return e.ErrFindWorkflow.AddErr(err) } + if err := workflowLint(inputWorkflow, logger); err != nil { + return err + } inputWorkflow.UpdatedBy = user inputWorkflow.UpdateTime = time.Now().Unix() @@ -145,3 +157,38 @@ func ListWorkflowV4(projectName, userID string, pageNum, pageSize int64, logger } return resp, total, nil } + +func workflowLint(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(workflow.Project) + if err != nil { + logger.Errorf("Failed to get project %s services, error: %v", workflow.Project, err) + return e.ErrUpsertWorkflow.AddErr(err) + } + if len(services) > 0 { + if services[0].Type != setting.K8SDeployType && services[0].Type != setting.HelmDeployType { + logger.Error("common workflow do not support PM project yet") + return e.ErrUpsertWorkflow.AddDesc("common workflow do not support PM project yet") + } + } + jobNameMap := make(map[string]bool, 0) + reg, err := regexp.Compile(JobNameRegx) + if err != nil { + logger.Errorf("reg compile failed: %v", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + for _, stage := range workflow.Stages { + for _, job := range stage.Jobs { + if match := reg.MatchString(job.Name); !match { + logger.Errorf("job name [%s] did not match %s", job.Name, JobNameRegx) + return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("job name [%s] did not match %s", job.Name, JobNameRegx)) + } + if _, ok := jobNameMap[job.Name]; !ok { + jobNameMap[job.Name] = true + } else { + logger.Errorf("duplicated job name: %s", job.Name) + return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("duplicated job name: %s", job.Name)) + } + } + } + return nil +} -- Gitee From 42e9f2ef39476f9d2566bd333d99060f196c045e Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 28 Jun 2022 21:32:22 +0800 Subject: [PATCH 2494/2647] fix workflow running problems Signed-off-by: guoyu --- .../service/workflow/job/job_build.go | 7 ++- .../service/workflow/job/job_deploy.go | 16 +++-- pkg/types/repo.go | 58 +++++++++---------- pkg/types/step/common.go | 32 +++++----- pkg/types/step/step_archive.go | 10 ++-- pkg/types/step/step_deploy.go | 24 ++++---- pkg/types/step/step_docker_build.go | 30 +++++----- pkg/types/step/step_git.go | 4 +- pkg/types/step/step_helm_deploy.go | 20 +++---- pkg/types/step/step_shell.go | 2 +- pkg/types/step/step_tool_install.go | 14 ++--- 11 files changed, 112 insertions(+), 105 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 7d79ed09..d10e0bf4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -89,8 +89,10 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { imageTag = repo.GetReleaseCandidateTag(taskID) if len(registry.Namespace) > 0 { build.Image = fmt.Sprintf("%s/%s/%s:%s", registry.RegAddr, registry.Namespace, build.ServiceModule, imageTag) + } else { + build.Image = fmt.Sprintf("%s/%s:%s", registry.RegAddr, build.ServiceModule, imageTag) } - build.Image = fmt.Sprintf("%s/%s:%s", registry.RegAddr, build.ServiceModule, imageTag) + build.Image = strings.TrimPrefix(build.Image, "http://") build.Image = strings.TrimPrefix(build.Image, "https://") break @@ -98,7 +100,7 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { build.Package = fmt.Sprintf("%s-%s-%d.tar.gz", build.ServiceModule, time.Now().Format("20060102150405"), taskID) jobTask := &commonmodels.JobTask{ - Name: j.job.Name + "-" + build.ServiceName, + Name: j.job.Name + "-" + build.ServiceName + "-" + build.ServiceModule, JobType: string(config.JobZadigBuild), } buildInfo, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.BuildName}) @@ -255,6 +257,7 @@ func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, } ret = append(ret, &commonmodels.KeyVal{Key: "TASK_ID", Value: fmt.Sprintf("%d", taskID), IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE", Value: build.ServiceName, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE_MODULE", Value: build.ServiceModule, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "IMAGE", Value: build.Image, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "CI", Value: "true", IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "ZADIG", Value: "true", IsCredential: false}) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index 3fd206a1..aa63e157 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -46,15 +46,19 @@ func (j *DeployJob) SetPreset() error { } j.job.Spec = j.spec - if j.spec.Source != config.SourceRuntime { - return nil - } services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(j.workflow.Project) if err != nil { return err } + if len(services) > 0 { + j.spec.DeployType = services[0].Type + } + + if j.spec.Source != config.SourceRuntime { + return nil + } + for _, service := range services { - j.spec.DeployType = service.Type for _, container := range service.Containers { j.spec.ServiceAndImages = append(j.spec.ServiceAndImages, &commonmodels.ServiceAndImage{ServiceName: service.ServiceName, ServiceModule: container.Name}) } @@ -101,7 +105,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { for _, deploy := range j.spec.ServiceAndImages { jobTask := &commonmodels.JobTask{ Name: j.job.Name + "-" + deploy.ServiceName + "-" + deploy.ServiceModule, - JobType: string(config.JobDeploy), + JobType: string(config.JobZadigDeploy), } deployStep := &commonmodels.StepTask{ Name: deploy.ServiceName + "-deploy", @@ -128,7 +132,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { for serviceName, deploys := range deployServiceMap { jobTask := &commonmodels.JobTask{ Name: j.job.Name + "-" + serviceName, - JobType: string(config.JobDeploy), + JobType: string(config.JobZadigDeploy), } deployStep := &commonmodels.StepTask{ Name: serviceName + "-deploy", diff --git a/pkg/types/repo.go b/pkg/types/repo.go index c579ba5e..4de2c4a3 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -25,42 +25,42 @@ import ( // Repository struct type Repository struct { - Source string `bson:"source,omitempty" json:"source,omitempty"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` - RepoName string `bson:"repo_name" json:"repo_name"` - RemoteName string `bson:"remote_name,omitempty" json:"remote_name,omitempty"` - Branch string `bson:"branch" json:"branch"` - PR int `bson:"pr,omitempty" json:"pr,omitempty"` - Tag string `bson:"tag,omitempty" json:"tag,omitempty"` - CommitID string `bson:"commit_id,omitempty" json:"commit_id,omitempty"` - CommitMessage string `bson:"commit_message,omitempty" json:"commit_message,omitempty"` - CheckoutPath string `bson:"checkout_path,omitempty" json:"checkout_path,omitempty"` - SubModules bool `bson:"submodules,omitempty" json:"submodules,omitempty"` + Source string `bson:"source,omitempty" json:"source,omitempty" yaml:"source,omitempty"` + RepoOwner string `bson:"repo_owner" json:"repo_owner" yaml:"repo_owner"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace" yaml:"repo_namespace"` + RepoName string `bson:"repo_name" json:"repo_name" yaml:"repo_name"` + RemoteName string `bson:"remote_name,omitempty" json:"remote_name,omitempty" yaml:"remote_name,omitempty"` + Branch string `bson:"branch" json:"branch" yaml:"branch"` + PR int `bson:"pr,omitempty" json:"pr,omitempty" yaml:"pr,omitempty"` + Tag string `bson:"tag,omitempty" json:"tag,omitempty" yaml:"tag,omitempty"` + CommitID string `bson:"commit_id,omitempty" json:"commit_id,omitempty" yaml:"commit_id,omitempty"` + CommitMessage string `bson:"commit_message,omitempty" json:"commit_message,omitempty" yaml:"commit_message,omitempty"` + CheckoutPath string `bson:"checkout_path,omitempty" json:"checkout_path,omitempty" yaml:"checkout_path,omitempty"` + SubModules bool `bson:"submodules,omitempty" json:"submodules,omitempty" yaml:"submodules,omitempty"` // UseDefault defines if the repo can be configured in start pipeline task page - UseDefault bool `bson:"use_default,omitempty" json:"use_default,omitempty"` + UseDefault bool `bson:"use_default,omitempty" json:"use_default,omitempty" yaml:"use_default,omitempty"` // IsPrimary used to generated image and package name, each build has one primary repo - IsPrimary bool `bson:"is_primary" json:"is_primary"` - CodehostID int `bson:"codehost_id" json:"codehost_id"` + IsPrimary bool `bson:"is_primary" json:"is_primary" yaml:"is_primary"` + CodehostID int `bson:"codehost_id" json:"codehost_id" yaml:"codehost_id"` // add - OauthToken string `bson:"oauth_token" json:"oauth_token"` - Address string `bson:"address" json:"address"` - AuthorName string `bson:"author_name,omitempty" json:"author_name,omitempty"` - CheckoutRef string `bson:"checkout_ref,omitempty" json:"checkout_ref,omitempty"` + OauthToken string `bson:"oauth_token" json:"oauth_token" yaml:"oauth_token"` + Address string `bson:"address" json:"address" yaml:"address"` + AuthorName string `bson:"author_name,omitempty" json:"author_name,omitempty" yaml:"author_name,omitempty"` + CheckoutRef string `bson:"checkout_ref,omitempty" json:"checkout_ref,omitempty" yaml:"checkout_ref,omitempty"` // codehub - ProjectUUID string `bson:"project_uuid,omitempty" json:"project_uuid,omitempty"` - RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty"` - RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty"` - Username string `bson:"username,omitempty" json:"username,omitempty"` - Password string `bson:"password,omitempty" json:"password,omitempty"` + ProjectUUID string `bson:"project_uuid,omitempty" json:"project_uuid,omitempty" yaml:"project_uuid,omitempty"` + RepoUUID string `bson:"repo_uuid,omitempty" json:"repo_uuid,omitempty" yaml:"repo_uuid,omitempty"` + RepoID string `bson:"repo_id,omitempty" json:"repo_id,omitempty" yaml:"repo_id,omitempty"` + Username string `bson:"username,omitempty" json:"username,omitempty" yaml:"username,omitempty"` + Password string `bson:"password,omitempty" json:"password,omitempty" yaml:"password,omitempty"` // Now EnableProxy is not something we store. We decide this on runtime - EnableProxy bool `bson:"-" json:"enable_proxy,omitempty"` + EnableProxy bool `bson:"-" json:"enable_proxy,omitempty" yaml:"enable_proxy,omitempty"` // FilterRegexp is the regular expression filter for the branches and tags - FilterRegexp string `bson:"-" json:"filter_regexp,omitempty"` + FilterRegexp string `bson:"-" json:"filter_regexp,omitempty" yaml:"filter_regexp,omitempty"` // The address of the code base input of the other type - AuthType AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty"` - SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty"` - PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty"` + AuthType AuthType `bson:"auth_type,omitempty" json:"auth_type,omitempty" yaml:"auth_type,omitempty"` + SSHKey string `bson:"ssh_key,omitempty" json:"ssh_key,omitempty" yaml:"ssh_key,omitempty"` + PrivateAccessToken string `bson:"private_access_token,omitempty" json:"private_access_token,omitempty" yaml:"private_access_token,omitempty"` } type BranchFilterInfo struct { diff --git a/pkg/types/step/common.go b/pkg/types/step/common.go index f759222b..053c1dfe 100644 --- a/pkg/types/step/common.go +++ b/pkg/types/step/common.go @@ -17,23 +17,23 @@ limitations under the License. package step type Proxy struct { - Type string `yaml:"type"` - Address string `yaml:"address"` - Port int `yaml:"port"` - NeedPassword bool `yaml:"need_password"` - Username string `yaml:"username"` - Password string `yaml:"password"` - EnableRepoProxy bool `yaml:"enable_repo_proxy"` - EnableApplicationProxy bool `yaml:"enable_application_proxy"` + Type string `bson:"type" json:"type" yaml:"type"` + Address string `bson:"address" json:"address" yaml:"address"` + Port int `bson:"port" json:"port" yaml:"port"` + NeedPassword bool `bson:"need_password" json:"need_password" yaml:"need_password"` + Username string `bson:"username" json:"username" yaml:"username"` + Password string `bson:"password" json:"password" yaml:"password"` + EnableRepoProxy bool `bson:"enable_repo_proxy" json:"enable_repo_proxy" yaml:"enable_repo_proxy"` + EnableApplicationProxy bool `bson:"enable_application_proxy" json:"enable_application_proxy" yaml:"enable_application_proxy"` } type S3 struct { - Ak string `yaml:"ak"` - Sk string `yaml:"sk"` - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` - Subfolder string `yaml:"subfolder"` - Insecure bool `yaml:"insecure"` - Provider int `yaml:"provider"` - Protocol string `yaml:"protocol"` + Ak string `bson:"ak" json:"ak" yaml:"ak"` + Sk string `bson:"sk" json:"sk" yaml:"sk"` + Endpoint string `bson:"endpoint" json:"endpoint" yaml:"endpoint"` + Bucket string `bson:"bucket" json:"bucket" yaml:"bucket"` + Subfolder string `bson:"subfolder" json:"subfolder" yaml:"subfolder"` + Insecure bool `bson:"insecure" json:"insecure" yaml:"insecure"` + Provider int `bson:"provider" json:"provider" yaml:"provider"` + Protocol string `bson:"protocol" json:"protocol" yaml:"protocol"` } diff --git a/pkg/types/step/step_archive.go b/pkg/types/step/step_archive.go index b6d012b9..67f4f665 100644 --- a/pkg/types/step/step_archive.go +++ b/pkg/types/step/step_archive.go @@ -17,9 +17,9 @@ limitations under the License. package step type StepArchiveSpec struct { - FilePath string `yaml:"file_path"` - AbsFilePath string `yaml:"abs_file_path"` - DestinationPath string `yaml:"dest_path"` - S3StorageID string `yaml:"s3_storage_id"` - S3 *S3 `yaml:"s3_storage"` + FilePath string `bson:"file_path" json:"file_path" yaml:"file_path"` + AbsFilePath string `bson:"abs_file_path" json:"aabs_file_pathk" yaml:"abs_file_path"` + DestinationPath string `bson:"dest_path" json:"dest_path" yaml:"dest_path"` + S3StorageID string `bson:"s3_storage_id" json:"s3_storage_id" yaml:"s3_storage_id"` + S3 *S3 `bson:"s3_storage" json:"s3_storage" yaml:"s3_storage"` } diff --git a/pkg/types/step/step_deploy.go b/pkg/types/step/step_deploy.go index 214c26de..6943d19b 100644 --- a/pkg/types/step/step_deploy.go +++ b/pkg/types/step/step_deploy.go @@ -17,19 +17,19 @@ limitations under the License. package step type StepDeploySpec struct { - Env string `yaml:"env"` - ServiceName string `yaml:"service_name"` - ServiceType string `yaml:"service_type"` - ServiceModule string `yaml:"service_module"` - Image string `yaml:"image"` - ClusterID string `yaml:"cluster_id"` - Timeout int `yaml:"timeout"` - ReplaceResources []Resource `yaml:"replace_resources"` + Env string `bson:"env" json:"env" yaml:"env"` + ServiceName string `bson:"service_name" json:"service_name" yaml:"service_name"` + ServiceType string `bson:"service_type" json:"service_type" yaml:"service_type"` + ServiceModule string `bson:"service_module" json:"service_module" yaml:"service_module"` + Image string `bson:"image" json:"image" yaml:"image"` + ClusterID string `bson:"cluster_id" json:"cluster_id" yaml:"cluster_id"` + Timeout int `bson:"timeout" json:"timeout" yaml:"timeout"` + ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources" yaml:"replace_resources"` } type Resource struct { - Name string `yaml:"name"` - Kind string `yaml:"kind"` - Container string `yaml:"container"` - Origin string `yaml:"origin"` + Name string `bson:"name" json:"name" yaml:"name"` + Kind string `bson:"kind" json:"kind" yaml:"kind"` + Container string `bson:"container" json:"container" yaml:"container"` + Origin string `bson:"origin" json:"origin" yaml:"origin"` } diff --git a/pkg/types/step/step_docker_build.go b/pkg/types/step/step_docker_build.go index 16e6bc63..c6408dd2 100644 --- a/pkg/types/step/step_docker_build.go +++ b/pkg/types/step/step_docker_build.go @@ -23,24 +23,24 @@ import ( ) type StepDockerBuildSpec struct { - Source string `yaml:"source"` - WorkDir string `yaml:"work_dir"` - RegistryHost string `yaml:"registry_host"` - DockerFile string `yaml:"docker_file"` - ImageName string `yaml:"image_name"` - BuildArgs string `yaml:"build_args"` - ImageReleaseTag string `yaml:"image_release_tag"` - DockerTemplateContent string `yaml:"docker_template_content"` - Proxy *Proxy `yaml:"proxy"` - IgnoreCache bool `yaml:"ignore_cache"` - DockerRegistry *DockerRegistry `yaml:"docker_registry"` + Source string `bson:"source" json:"source" yaml:"source"` + WorkDir string `bson:"work_dir" json:"work_dir" yaml:"work_dir"` + RegistryHost string `bson:"registry_host" json:"registry_host" yaml:"registry_host"` + DockerFile string `bson:"docker_file" json:"docker_file" yaml:"docker_file"` + ImageName string `bson:"image_name" json:"image_name" yaml:"image_name"` + BuildArgs string `bson:"build_args" json:"build_args" yaml:"build_args"` + ImageReleaseTag string `bson:"image_release_tag" json:"image_release_tag" yaml:"image_release_tag"` + DockerTemplateContent string `bson:"docker_template_content" json:"docker_template_content" yaml:"docker_template_content"` + Proxy *Proxy `bson:"proxy" json:"proxy" yaml:"proxy"` + IgnoreCache bool `bson:"ignore_cache" json:"ignore_cache" yaml:"ignore_cache"` + DockerRegistry *DockerRegistry `bson:"docker_registry" json:"docker_registry" yaml:"docker_registry"` } type DockerRegistry struct { - Host string `yaml:"host"` - Namespace string `yaml:"namespace"` - UserName string `yaml:"username"` - Password string `yaml:"password"` + Host string `bson:"host" json:"host" yaml:"host"` + Namespace string `bson:"namespace" json:"namespace" yaml:"namespace"` + UserName string `bson:"username" json:"username" yaml:"username"` + Password string `bson:"password" json:"password" yaml:"password"` } func (s *StepDockerBuildSpec) GetDockerFile() string { diff --git a/pkg/types/step/step_git.go b/pkg/types/step/step_git.go index fdc13dca..cdbc83ab 100644 --- a/pkg/types/step/step_git.go +++ b/pkg/types/step/step_git.go @@ -23,8 +23,8 @@ import ( ) type StepGitSpec struct { - Repos []*types.Repository `yaml:"repos"` - Proxy *Proxy `yaml:"proxy"` + Repos []*types.Repository `bson:"repos" json:"repos" yaml:"repos"` + Proxy *Proxy `bson:"proxy" json:"proxy" yaml:"proxy"` } const ( diff --git a/pkg/types/step/step_helm_deploy.go b/pkg/types/step/step_helm_deploy.go index 97ab7c58..04c25ae2 100644 --- a/pkg/types/step/step_helm_deploy.go +++ b/pkg/types/step/step_helm_deploy.go @@ -17,17 +17,17 @@ limitations under the License. package step type StepHelmDeploySpec struct { - Env string `yaml:"env"` - ServiceName string `yaml:"service_name"` - ServiceType string `yaml:"service_type"` - ImageAndModules []*ImageAndServiceModule `yaml:"image_and_service_modules"` - ClusterID string `yaml:"cluster_id"` - ReleaseName string `yaml:"release_name"` - Timeout int `yaml:"timeout"` - ReplaceResources []Resource `yaml:"replace_resources"` + Env string `bson:"env" json:"env" yaml:"env"` + ServiceName string `bson:"service_name" json:"service_name" yaml:"service_name"` + ServiceType string `bson:"service_type" json:"service_type" yaml:"service_type"` + ImageAndModules []*ImageAndServiceModule `bson:"image_and_service_modules" json:"image_and_service_modules" yaml:"image_and_service_modules"` + ClusterID string `bson:"cluster_id" json:"cluster_id" yaml:"cluster_id"` + ReleaseName string `bson:"release_name" json:"release_name" yaml:"release_name"` + Timeout int `bson:"timeout" json:"timeout" yaml:"timeout"` + ReplaceResources []Resource `bson:"replace_resources" json:"replace_resources" yaml:"replace_resources"` } type ImageAndServiceModule struct { - ServiceModule string `yaml:"service_module"` - Image string `yaml:"image"` + ServiceModule string `bson:"service_module" json:"service_module" yaml:"service_module"` + Image string `bson:"image" json:"image" yaml:"image"` } diff --git a/pkg/types/step/step_shell.go b/pkg/types/step/step_shell.go index 5799b987..3818fd12 100644 --- a/pkg/types/step/step_shell.go +++ b/pkg/types/step/step_shell.go @@ -17,5 +17,5 @@ limitations under the License. package step type StepShellSpec struct { - Scripts []string `yaml:"scripts"` + Scripts []string `bson:"scripts" json:"scripts" yaml:"scripts"` } diff --git a/pkg/types/step/step_tool_install.go b/pkg/types/step/step_tool_install.go index 9ad31e01..9288c61d 100644 --- a/pkg/types/step/step_tool_install.go +++ b/pkg/types/step/step_tool_install.go @@ -1,11 +1,11 @@ package step type StepToolInstallSpec struct { - Name string `yaml:"name"` - Version string `yaml:"version"` - Scripts []string `yaml:"scripts"` - BinPath string `yaml:"bin_path"` - Envs []string `yaml:"envs"` - Download string `yaml:"download"` - S3Storage *S3 `yaml:"s3_storage"` + Name string `bson:"name" json:"name" yaml:"name"` + Version string `bson:"version" json:"version" yaml:"version"` + Scripts []string `bson:"scripts" json:"scripts" yaml:"scripts"` + BinPath string `bson:"bin_path" json:"bin_path" yaml:"bin_path"` + Envs []string `bson:"envs" json:"envs" yaml:"envs"` + Download string `bson:"download" json:"download" yaml:"download"` + S3Storage *S3 `bson:"s3_storage" json:"s3_storage" yaml:"s3_storage"` } -- Gitee From d6a0a65a656693bf2e6b3341f5b772fa142c6162 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 28 Jun 2022 21:41:13 +0800 Subject: [PATCH 2495/2647] add cancel workflow task api Signed-off-by: guoyu --- .../aslan/core/workflow/handler/router.go | 1 + .../aslan/core/workflow/handler/workflow_task_v4.go | 13 ++++++++++++- .../workflow/service/workflow/workflow_task_v4.go | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 5abb9757..fd8b7b07 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -184,6 +184,7 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV4.POST("", CreateWorkflowTaskV4) taskV4.GET("", ListWorkflowTaskV4) taskV4.GET("/workflow/:workflowName/task/:taskID", GetWorkflowTaskV4) + taskV4.DELETE("/workflow/:workflowName/task/:taskID", CancelWorkflowTaskV4) } bundles := router.Group("bundle-resources") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index bcc2d9a5..bb174476 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -92,6 +92,17 @@ func GetWorkflowTaskV4(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") return } - ctx.Resp, ctx.Err = workflow.GetWorkflowTaskV4(c.Param("workflowName"), taskID, ctx.Logger) } + +func CancelWorkflowTaskV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("taskID"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + ctx.Err = workflow.CancelWorkflowTaskV4(ctx.UserName, c.Param("workflowName"), taskID, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index f5448a31..25dea42e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -190,6 +190,14 @@ func ListWorkflowTaskV4(workflowName string, pageNum, pageSize int64, logger *za return resp, total, nil } +func CancelWorkflowTaskV4(userName, workflowName string, taskID int64, logger *zap.SugaredLogger) error { + if err := workflowcontroller.CancelWorkflowTask(userName, workflowName, taskID, logger); err != nil { + logger.Errorf("cancel workflowTaskV4 error: %s", err) + return e.ErrCancelTask.AddErr(err) + } + return nil +} + func GetWorkflowTaskV4(workflowName string, taskID int64, logger *zap.SugaredLogger) (*WorkflowTaskPreview, error) { task, err := commonrepo.NewworkflowTaskv4Coll().Find(workflowName, taskID) if err != nil { -- Gitee From 7becc1750e2d1f59d2b0280dacd67ee47ab98d4c Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 29 Jun 2022 10:45:44 +0800 Subject: [PATCH 2496/2647] fix status data of workloads in helm projects (#1720) Signed-off-by: allenshen --- .../aslan/core/common/service/environment.go | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/environment.go b/pkg/microservice/aslan/core/common/service/environment.go index 471e7080..a827d31c 100644 --- a/pkg/microservice/aslan/core/common/service/environment.go +++ b/pkg/microservice/aslan/core/common/service/environment.go @@ -188,8 +188,6 @@ func fillServiceInfo(svcList []*ServiceResp, productInfo *models.Product) { // A filter is in this format: a=b,c=d, and it is a fuzzy matching. Which means it will return all records with a field called // a and the value contain character b. func ListWorkloadsInEnv(envName, productName, filter string, perPage, page int, log *zap.SugaredLogger) (int, []*ServiceResp, error) { - log.Infof("Start to list workloads for env %s for project %s", envName, productName) - defer log.Info("Finish to list workloads") opt := &commonrepo.ProductFindOptions{Name: productName, EnvName: envName} productInfo, err := commonrepo.NewProductColl().Find(opt) @@ -375,7 +373,6 @@ func fillServiceName(envName, productName string, workloads []*Workload) error { } func ListWorkloads(envName, clusterID, namespace, productName string, perPage, page int, log *zap.SugaredLogger, filter ...FilterFunc) (int, []*ServiceResp, error) { - log.Infof("Start to list workloads in namespace %s", namespace) var resp = make([]*ServiceResp, 0) cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) @@ -421,15 +418,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p }) } - // Note: In some scenarios, such as environment sharing, there may be more containers in Pod than workload. - for _, workload := range workLoads { - selector := labels.SelectorFromSet(labels.Set(workload.Spec.Labels)) - _, _, images := kube.GetSelectedPodsInfo(selector, informer, log) - workload.Images = images - } - - log.Debugf("Found %d workloads in total", len(workLoads)) - err = fillServiceName(envName, productName, workLoads) // err of getting service name should not block the return of workloads if err != nil { @@ -443,8 +431,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p count := len(workLoads) - log.Debugf("%d matching workloads left after filtering", count) - //将获取到的所有服务按照名称进行排序 sort.SliceStable(workLoads, func(i, j int) bool { return workLoads[i].Name < workLoads[j].Name }) @@ -507,10 +493,11 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p Ready: setting.PodReady, Status: setting.PodRunning, } - if !workload.Ready { - productRespInfo.Status = setting.PodUnstable - productRespInfo.Ready = setting.PodNotReady - } + + selector := labels.SelectorFromSet(labels.Set(workload.Spec.Labels)) + // Note: In some scenarios, such as environment sharing, there may be more containers in Pod than workload. + // We call GetSelectedPodsInfo to get the status and readiness to keep same logic with k8s projects + productRespInfo.Status, productRespInfo.Ready, productRespInfo.Images = kube.GetSelectedPodsInfo(selector, informer, log) productRespInfo.Ingress = &IngressInfo{ HostInfo: findServiceFromIngress(hostInfos, workload, allServices), @@ -518,7 +505,6 @@ func ListWorkloads(envName, clusterID, namespace, productName string, perPage, p resp = append(resp, productRespInfo) } - log.Infof("Finish to list workloads in namespace %s", namespace) return count, resp, nil } -- Gitee From 5c1914854d8d9022323a16ce31e51d0737f80c9c Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Wed, 29 Jun 2022 10:46:06 +0800 Subject: [PATCH 2497/2647] branch commit associated jira information (#1721) Signed-off-by: panxunying Co-authored-by: panxunying --- .../warpdrive/core/service/taskplugin/jira.go | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go index bb488802..7ad0c063 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/jira.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/jira.go @@ -165,13 +165,31 @@ func (p *JiraPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC repoCommits = append(repoCommits, list...) opt.Page = resp.NextPage } - for _, rc := range repoCommits { - // parse commit message - if rc != nil && rc.Message != "" { - keys := util.GetJiraKeys(rc.Message) - if len(keys) > 0 { - jiraKeys = append(jiraKeys, keys...) - } + + } else { + opts := &gitlab.ListCommitsOptions{ + RefName: &build.Branch, + ListOptions: gitlab.ListOptions{ + PerPage: 1, + Page: 1, + }, + } + commits, _, err := gitlabCli.Commits.ListCommits(fmt.Sprintf("%s/%s", build.RepoOwner, build.RepoName), opts) + if err != nil { + p.Log.Errorf("gitlab ListCommits [%s/%s] error: %v", build.RepoOwner, build.RepoName, err) + p.Task.TaskStatus = config.StatusSkipped + p.Task.Error = err.Error() + return + } + repoCommits = append(repoCommits, commits...) + } + + for _, rc := range repoCommits { + // parse commit message + if rc != nil && rc.Message != "" { + keys := util.GetJiraKeys(rc.Message) + if len(keys) > 0 { + jiraKeys = append(jiraKeys, keys...) } } } @@ -230,6 +248,30 @@ func (p *JiraPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipelineC return } + for _, rc := range commits { + // parse commit message + if rc.Commit != nil && rc.Commit.Message != nil { + keys := util.GetJiraKeys(*rc.Commit.Message) + if len(keys) > 0 { + jiraKeys = append(jiraKeys, keys...) + } + } + } + } else { + opt := &github.CommitsListOptions{ + SHA: build.Branch, + ListOptions: github.ListOptions{ + Page: 1, + PerPage: 1, + }, + } + commits, _, err := gh.Client.Repositories.ListCommits(context.TODO(), build.RepoOwner, build.RepoName, opt) + if err != nil { + p.Log.Errorf("github ListCommits [%s/%s] error: %v", build.RepoOwner, build.RepoName, err) + p.Task.TaskStatus = config.StatusSkipped + p.Task.Error = err.Error() + return + } for _, rc := range commits { // parse commit message if rc.Commit != nil && rc.Commit.Message != nil { -- Gitee From 7e2325cd746807a9cb349304c3e911bbde481363 Mon Sep 17 00:00:00 2001 From: fansi Date: Wed, 29 Jun 2022 16:29:24 +0800 Subject: [PATCH 2498/2647] update Zadig product description & architecture image Signed-off-by: fansi --- README-zh-CN.md | 29 +++++++++++++++++------------ Zadig-Business-Architecture-zh.jpg | Bin 388176 -> 424374 bytes Zadig-Business-Architecture.jpg | Bin 403685 -> 452425 bytes 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/README-zh-CN.md b/README-zh-CN.md index cbfb9005..22566c74 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -33,7 +33,7 @@ ## Zadig 介绍 -Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery)产品,具备高可用 CI/CD 能力,提供云原生运行环境,支持开发者本地联调、微服务并行构建和部署、集成测试等。Zadig 不改变现有流程,无缝集成 Github/Gitlab、Jenkins、多家云厂商等,运维成本极低。我们的目标是通过云原生技术的运用和工程产品赋能,打造极致、高效、愉悦的开发者工作体验,让工程师成为企业创新的核心引擎。 +Zadig 是 KodeRover 公司基于 Kubernetes 自主设计、研发的开源分布式持续交付 (Continuous Delivery) 产品,具备灵活易用的高并发工作流、面向开发者的云原生环境、高效协同的测试管理、强大免运维的模板库、客观精确的效能洞察以及云原生 IDE 插件等重要特性,为工程师提供统一的协作平面。Zadig 内置了 K8s YAML、Helm Chart、主机等复杂场景最佳实践,适用大规模微服务、高频高质量交付等场景。我们的目标是通过云原生技术的运用和工程产品赋能,打造极致、高效、愉悦的开发者工作体验,让工程师成为企业创新的核心引擎。 我们的愿景:`工程师 + Zadig = 商业上的成功` @@ -47,29 +47,34 @@ Zadig 是一款面向开发者设计的云原生持续交付(Continuous Delivery 产品特性介绍:
    - 高并发的工作流 - 基于云原生设计,经过简单配置,系统自动生成工作流,实现多服务高并发执行构建部署测试任务,以解决微服务架构下带来的多服务构建部署效率低下问题。 + 灵活易用的高并发工作流 + 简单配置,可自动生成高并发工作流,多个微服务可并行构建、并行部署、并行测试,大大提升代码验证效率。自定义的工作流步骤,配合人工审核,灵活且可控的保障业务交付质量。
    - 以服务为核心的集成环境 - 一套服务配置,分钟级创建多套数据隔离的测试环境。为开发者进行日常调试、为测试人员做集成测试、为产品经理对外 Demo 提供强力支撑。 - - 对于现有的环境无需担心迁移成本,一键托管,轻松浏览、调试环境中的所有服务。 + 面向开发者的云原生环境 + 分钟级创建或复制一套完整的隔离环境,应对频繁的业务变更和产品迭代。基于全量基准环境,快速为开发者提供一套独立的自测环境。一键托管集群资源即可轻松调试已有服务,验证业务代码。
    - 无侵入的自动化测试 - 便捷且无侵入的对接已有自动化测试框架,通过 GitHub/GitLab Webhook 自动构建、部署及测试。 + 高效协同的测试管理 + 便捷对接 Jmeter、Pytest 等主流测试框架,跨项目管理和沉淀 UI、API、E2E 测试用例资产。通过工作流,向开发者提供前置测试验证能力。通过持续测试和质量分析,充分释放测试价值。 +
    - 通过办公通讯机器人为开发者提供第一时间质量反馈,精准高效。有效落地“测试左移”工程实践,让测试价值得到体现。 +
    + 强大免运维的模板库 + 跨项目共享 K8s YAML 模版、Helm Chart 模版、构建模版等,实现配置的统一化管理。基于一套模板可创建数百微服务,开发工程师少量配置可自助使用,大幅降低运维管理负担。
    - 开发本地联调 CLI/IDE Plugin 插件 - 开发本地编辑完代码,一键进行本地代码构建,部署到联调环境,无需再陷入复杂且繁琐的工作流程。解放工程师双手,去创造更多产品价值。 + 客观精确的效能洞察 + 全面了解系统运行状态,包括集群、项目、环境、工作流,关键过程通过率等数据概览。提供项目维度的构建、测试、部署等客观的效能度量数据,精准分析研发效能短板,促进稳步提升。
    +
    + 云原生 IDE 插件 + 开发者无需平台切换,在 VScode IDE 中即可获得 Zadig 产品核心能力。编写代码后,无需打包镜像,即可一键热部署到自测环境,快速完成自测、联调和集成验证,开发效率倍增。 +
    ## 快速上手 diff --git a/Zadig-Business-Architecture-zh.jpg b/Zadig-Business-Architecture-zh.jpg index a7edb8a09cec859d78ad097d9cb9f0e338c48b53..bca27de195439cd326197f0d8768082ab9b2102c 100644 GIT binary patch literal 424374 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%0AmYz}H%cxVrrgkc z3BNb+3PlVp0hHu1_)!qaHQiFbqA7ZE!Y4849%40BUFIgirYKY6ODY8s*=zQUuD0xb zy53OTrvRJ>+fREwx`R_b;JL7oF1E@U9`Fq?+e>-1-;B=?AP0PN({`@w?`43~S(Uef z9J3f)cWKfxCoh$GDJugipCMvt>F^r&ARfTEG3qF_w*rnBk`?CJNaB`x8!S<@Dui?y zW-~oDiY;}9gp`?2B`69}Dju^w#7OVl^>%WnnXYfBTL&lKQD$3$*Py&82RMWgn;A{) z$3@+nW?5cv zr0gyvalC)Bx*{C_&>{O5-YNh9+8gc+^O&?-wNA_Nw{Nt8c6hxc8Q?zk|VTBE>f^rw<0qhkq_JH7@;~F<4~4bsw2Cir5!OK z<-;5n^a6_>Vao0u<~cHYd~uSl+8W6+UICMfIxy@M6WtNBo2wZ-Yy1rGkBKB?m>C{( zd;$RcT_q*=-9M%xFpVdUQ3HNHMv*UpkqkRp&03951k}4~_eT(_?5#W3n!?}nL1$XK zm_@?oT3+h~ z-4G2{Nm-_$+k|n=46LTSG@ek|Z^{~==F>6%820H#_$fnXAv|Rq4maRvyIrOkkam)J zTqPV*Iu$OIWf&hu8LOsSo}6dl^}PY$tj59rz7hZ7`<3T45(&2IPhfk4 zNKoR@ZOhld)XCIC++yP$;t;1nH*%iXBTA1)9JkZQ+mPnUu^M{vTq%>w(GN2#ZXN%m zqSTzVR%(RWx*m$AvnFcvNb+*2${~TJ>n1YY;S-E-x8K~gVPfqxN=h>txU%Q~6#?jE%I> zS-Ng0CokXvD~gqWpXP#qiVa^+(^c{S|?P$`NstJQ0h%sGz*^aGv4Vr!?#{ zpl?BZEQ}3s`BMaj0fY@QONtwq?0fXPrgm~i(�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
    m;m~NcA>BaM<4+*)Kin7rIW=2(-{=&T4<< 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

    |z|@k) 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 literal 403685 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

    BGgvh3 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

    WimJdS()9 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 TIkfxDw1M1=O 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 }t&54(>4o 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

    )_igfO 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�z2~ve#iK-53SS0$K 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

    ! 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 -- Gitee From 8514a1c45bc5571c17a70202ac69439de4e62d76 Mon Sep 17 00:00:00 2001 From: fansi Date: Wed, 29 Jun 2022 17:03:16 +0800 Subject: [PATCH 2499/2647] revise architecture image Signed-off-by: fansi --- System-Architecture-Overview.md | Bin 1509 -> 452900 bytes Zadig-Business-Architecture.jpg | Bin 452425 -> 452687 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index 9c9414992911e9a950c1b7af560cf2d3b4c5779d..33df9cca9c5f905f2e9729ed20103b942e16be46 100644 GIT binary patch literal 452900 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%0D3=_!7?n%b65+1|V&{(R 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%0A&EFV-?{gc+yXStD(eN_+02x}sNB+y-=>Nvv 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 Hh;`W?=yo=Q diff --git a/Zadig-Business-Architecture.jpg b/Zadig-Business-Architecture.jpg index e40a6f19313187cc4bfc72c047ba55671b6aae1f..6140e9b3bb712901ebe4cbfef2e61cce533c4745 100644 GIT binary patch delta 275518 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 J5#PN@8a5jY_HSYAIkg(NZMS; zf>Q8)V#&0F=Gxq6-1^co^<6^if3-z{(#rZ73SM~sbbcZKaA;WK-i+d4`DGTrv)S24 zxIe_4`F%eh>iAXta02iD>3G!tZ3F+8$D{s7Jy0E;Uf=3c>>%Gp{Nj}-Csm)M#w16Ky%1eHBZnew*yJ6FygX?2;QXxnEM+^yhDDM=lUxrvg zeOkPWZ|BNTUXuXiD~`;xBq9lz!Uz4Tz1KG>Hxm`c@WL{-jBP)0&M1C_{5Ko%p zorST6&Z$+F-lP2ov;JXdfEn-O?If?mzgVp)>@VuVCH#02KR7&CRX`{pfa>lqIMTGJ zYsCSy5-%uXiRH3pHEZQf2>T@p0aj>%7;4Bz%!4_1fuP#!d3aGxL{Wz+iE-MUVKCoNmjD(0&kSKMoN4iUqvAMjyq%)f1ee%IIxy04UMP)v79D3RLt2_0wq zzp28^RB*hz2`8D)f(G->t=c2wQx(O}gsTqO@4tIb+PFODDzLtCri-+2KhADMdG(%w zf&uZ9TJt=Qs4n01d?0l`4=t>(ZDpg!M8(qF#&li23jB|fclTVcY6m(0Xo{kpm{X~# zasxKMSKjOFOetZl9R*2c4L{JsCde`Lj!P*;l#n8f5Z!FZg8t{($z6xRjb5 zbkgAu)enhu{-wCc^MUjD83W(+WB-V|(b^>< zgF-)}yXedQ`G|_fONL^2Y)|v08vi->;!6=lT>mLP?BBQQyHK}O?LeDb7aGy0@L3@7=>skC(0fe# zc}da_NUn(hRLm~L2RTvETzj>VSMV$`+2cS$S|nt&E!oP6lx$_K;L(=+V#ygF+${Pu zwM+~G3<`*q{)P^EP*yWl%85{%H=hZtT(Z_>Iu)C)Bj7|>c;TzqP5O6?Wwr= zk6rc8xrK{#eZ3BTTj(VA4ne@r5^|*z?ehww@ACfWMqT=2JMMs4w=*dTH>)ynJ$Z6_ z`h|5Phg890swNH!1uKslC8A~PO|q{vd=E*9H5#47ez8Z!IM*kIc7l$jc*K}n@8`L1tma9O^ym-m-SA&z)2u@C^W zqpGc{sOGZksSQvdr$Q&*==ry~!dhbvNDz<1hKQya9IeEj`qClza;*1cvNBDLxn%?J zgCFaNr_zS57#!Yy6>GC`*NDU9$1PR)#H9}Kj+%7V0ii6@S+S`ex`_n0%Lga+G_>91 z`}~*^y_PF>j(kzl^6VpX+AI*4SCqO+sjWgILMS#(Ezos3U#T}dD zR?PN|-N9QUi)oA~a{8QcG0#s(yXf0Ly}a|acRGF~QaoLf&LVQfOU&&Xp*RZ)q}~vv zK4ssiGaGO;>5A{FceSxk^OoqMH|n zlL!%B6$37Y7I;k0S9z@D%`9H}*w6MY8rS3X>kz$~87qPvMDHSjyLi6ug`0$~$>qtg zRisJD9w{roI8iJPZ^P&D#CaAfse$_DkaGWC=I39x)#|qLCz?PY@9O~+@-z5F5T2cL zFJ0atv)h0j=JDlP7T2|`3KYUE195KCKWeeR0isXjid8ETN|8>MdJF!D&db|x%1>kz z2tSD-`SaZK$3{DX91zNUJ~0qNNm06qdliLZfvbk<1x?O=KD#FePCJ`qL`@LbK4geN zJC}r4bU_@+Yh$Hl-0YfW;T)w_E8M@J=}|B<*TT(a^|^K*L|t8J%vy@7RvBiYuTU#e z#`u>ZULMSq#gHqV_Ke+aQeorsma(zY`ee6~eF6sYW zR9hhyVZL(?K^zGuZBFO1c=wh(4&6JwmK5KlU#6JeV{k`_7yL`jpB>(Vbw}9S@~v=T zLJM~Ls>hxeH8nd-XAx4dFavwNYes@4I#`hxs_8uK_&}VyO--oHU>o$vIQ_S);TfA@1ew@Bpz4j=z|ArK( zjjsVRV^o-IPeCZ_-)IU%I?I}fPO*JEOrP^QD`U$$%af2srP){r+=vz1W_QO>OK0C$ z(E6kcq%VP#(r<3-yoCw-x%9px@RoUG8XH>Lm^gcABIi_T1a~E3m-@6D3Y;cBttxXB z3A6k(%s0}%iwtCoMaY8ufZUcbh#?t>KF)0nBJTAbroBwCAzj_i?E7l6+Wp>+-eGqX z={jg#fYd-uOjnnk29Cw$KZ?oxU7#@1w1DGM;wB4(zv5MTDf$6#)mZ8r1QwrFe(RH) zFBxq8zNlYa%P>ukuZtx=@%w0ooA{fOxSboz?*o^{kEJtp>%kVRRdh5|1aBb%B?5z`L0A7ou z&H(RL6-eO=(fbLRYZsE)G(F9Zyvg>XrB zV9jIj8NHg0v7`5<2&sm+xhD5lVkG3aZ@#e(tAd(Iiu6n#pT4vc;1~driD`H#DZR@( zHvmfuf^nqaQ)jEpNs(sCo|7oYD!S1p;a>~a)Sz15ivW&I@4O0Q;)AZh0|dKp^D<>T zdR5o-k^3b~Tirw%4Wx6tU_M2$h}bk8)d7f!$3?O;vL~`HbG<^qBD+ODT z_3v%ts&TH6jCWb=jdCN@Jp^Rr?WsG4cCd>?GU<+LW$c3P8_lO1s)Zg4fcrv3@xtZ++n}c^)}Bh1yIINWoPtHM9ns z4g+qUr$@Waoyw%TwIlV);I@A~?X&dgo zw@qbz>*C6wU!eNu5gLXw-TT`^D4o z76cUhw8L#V;(R`u$Cv2#BmYgW{F8_9Qa&s0>r=f8CBljNh&33;^`R3c^VCqVax9OQ z%TuEG`}JB>^oAO+-|gL|S6R{2iO+a z*OeNIkeUo{*wL6nBCsFMN_x+J)4yu?o2oMV_Q6EGS<2e%&lQLEGFA~nj&lB2H?<|D zf`^0}K5T{&_IJ!6oj&xHWB`vCA9GC!hs#jL999eUw0(rG2&CQ@W_Hv{&ed^}i3d!2 z@TzEiApiLz`u+0{l`Mk_3zM)wQ$97&Uv+G;O6qpGcs|oEDbIA@k;65h^EXwT?WeAtQSpGX^XK!6 zPnvg4&#LREPy~T2x#t(oPd5Lin&m-aj4oeMrCAr?1$@L*l~P-w1h*h#mR)mG7_D0U z3Ac=5(^=|vf=i>222RfdmhZG8m z$(p1PLo>I4+ybB{TP8z#hmXDM7h~f6n^&nmP~gbRyQ~IJ4^aBXfFPPcG8pn(Z8Lc+F zJI#NX*JF|R>IYbYb)13E*K9O)T3YR^&YV?XjF#nYFR&M7zLIgmRNGRD!m105>>6uE zYbmbWSn6Dku`)qGLz5i|+uz<5WX;)2lhG4ddle;X!RL?a9@?@-JLtl=wURb1qM@=@ zD@@48iHtjp9NHAUu;Ylz>E@?oA*ov6ln)&tkvhyvLdza_n!IuZ@Vn>%bUo+X;MRpmYJUSdaZy5va0lzg}XK&fI2 zK79MWd8$PYFBmd~;a&E37{Hnel(>m3N*1V??v1qNc=AK_N zZ#r@bg7o-qafnL8Z@ae+AO@!gEs50g6W3Gv`*0lU;FmzSmJ;NV|aQ~Fu10V3k($4 zXNSKZ0%As*F0o=}*r*xI;z1)X)oZt~tz;D?%;LyL?Ls9IoN3O3URL|St=7zka zKu?e62gj0OoDCL*u__!arTN!+zuR)&&?T05W|pd;y2tWn9Q}>)fBjAMOl)1f*wj}c zHOo|7sFNAFu#hGcRLo;!=Ra!y(p9k-D*|b@4NxNNx3}WVS~2 zBsxAoY`wuALg4ngX_}C}+^-=;8Fkb0@vbI>_2+NiKRB<-GQ*WLuu2v`#5> z^96-ghqqQHLMxqNve2>vtS9{H=-3v%cGc0@?JbnF1*T0x0#?AHD`4JbC$~us^Kx_etU|O_vuN|!4YvW$sTQGYdWB-DI>{^qu5~Fv_oFLc-9{r zAmDSl0rzr3<-JOuYwdU~fV0`CZ;IRBRJj_3ss=biS0Tpi5NyNtUjD-$V|mgsp5bvO zJ@Fzuy&tHFs&I_j%A7ESduVs2%p4 z05Bum3VW^gUY09&fhe*oyIrDKq=F(lBn-^KEXnm%);1*)=x8R#zvS@)*g2-;Eo`HV|i@aq1+2%vR|OFWO3Z(MONIasG9Asof{xb-%2{E znMj#cjOyUf(M+k^w^==d|rF;?$5D} z*iMc7Jan26sE~XKVwA7dVF8Kut^`{9OsneF%It>~Xt~;$lXnzk9a0({p*L}$ z532(kMRQ<4d>m~IIp!X1Ow4}VDCK{@wW>@EgNxox7R-)KeRozGY2?in=4i1#t9iQ{5kd_eOXcWV2S zP>nbrEcEWH-S7n_)N?jHurg}C?#ieQNiOeo4^T&czVjB6tmy;|9@UMiDG1_kx+LjB zXKHBIPF_r* z)3Nau^-D;g1D6jA@=dN8Q*xPzbVEUE7<0I`=yOenfZo%(P|{d&SNGh+x*Pe#;+od_ z0Y@N*+?YbOQA#ft#rOu#pt-0A+|Iw^pqcdjsnVr`H<&b3!YDw?=Vt9X*a^l0fuQXv zUa+gYVu{JVJ7cc++Of|~4}Zo*3kS2awM3HX_X&4@4Ml!$GT=12F}gR4$YYN55bh8H ze>Oo#-pLw1c<;o)iuaZ+aT{Ybs4Mb_`gq{S+bAR$H3ePAdhJC(BO(Sxb|VIQj+eE3 zzfy9M49`_AaAWONO;l$D*$Q*9*i`AZg~k%8VlphOp7D%NW8dY5{75^mu7bek>T9%?6@JGz1eGKOQ-H?*9bU~q4HqPw}E@-a%TP~a- zvn@|~ZF*GOyMt>eSx#Llss@?c|C+mGS!JPO!4D^yb=^4G26D{=NO}FSA~i+PN#VU~ z*XPc#Xm|jRXxfK;^OXuKf@537;ImOIFdJ5{hc8SitR;Uo$@iB-02WYK62vIUF@@vC ztUhgWO#IfCCfQYXVjfZq(^D|97+vXq0mTuqsSe}7hw7RbP76SNL_^~3bwmoWh~pOy z8+jQ4p|<%rvZj-*U#6GOB5RSHnyGwL4^0-*<{5&j+qWRh!2TQ?(V;%21ih{HuqAJ| zvteZuoDu-JVye%G`qtTZ$O?8Yt3Y(B^@(kC)Fic_KEs`-wcVVr_t)$c4>UPfiaYIq z@%7x~q95PJe*6}OJZ94UqrT)#CJq2d$`9IH0=MJmz}D0+c8W^_SmcHk*3(xw5<2*p z`d`)?<}1W(bF^@8XE=0Bv9UB23kX(ym?N{l;j!xQ)F0vu8qn|=-oLQZ{b`&Gqc-C<~A(r+Y4{G&_~=WEf-$dZKn z4_(bDeD%Q9_1u_(0t@11l^JJT?HOsMFB+K+@NM?(*L~=;o9<>&*#%v|cL*>Dsa*Ry zc+)OTZT=Q45OUjhbYyzuyFlgm-c7TvUxm+h(XlA`7odC0Fo@7|UPVn{Sv9h6=SAC* zOdfH)AkB#_5q9T>U<`S!T6nY1%7+i(C5A@lS_I`v7YU1gKmNs|l+d?i;wo>@C}b)9 z(_UrE^~btyXKD)J7rWfFWf`ZS(S0WATGzZD3j5XnQy*oHqMF9}?htH_+|wczQY2L} zE!c#>nS1zw`Uygs?afssk0X2vKPP=}+W+lHp> za#LJcTYc=zf$MdqB@RI@N#1J^SBe;#OJ(v-4u8;)cgctM4&+J#XdGK)!QsEP2Dhq) zc}g#UfcIN-!HY}x?LDTEs4s!0ak+KF;Cqq1^FtigzPL{Xw zaU5a@;W$0gJ4YZl>aHA+21rDE)C^Y<2u`O^3Y6DKbLaE!xiu{461VMj^7OYX6SoD;8baCm@BdW%@+D z^h3{W{_LjaSMRlak%rn<5i6jtE0p5LK6{NyC^QwW(E=A(O0n|JLr1%cDA$`$|G85= zbcR=w{5poaJ5r)ho4X`XkWXW*sRtW#OSj}6>t2`gZCOiJG`1;ckR~>X{t?wge~~^G zO+x31j&q~x8H9NHQUjnZj&E)^d5;@z`fv8Pl)}y#BCi9a57ja@y@8q*vwT2+W-0vF z&{NK=mDDn&MncjUmc=)-aZN+7z&KNyX`D{1^I7T|V`5^eSy+~K&z~FjZ~SPU47_Od zDJPQ|8Q}=fXNlHf>p7XaK@~)d}LpA{0fT6s#iu5Ehe9uC6k+rcP=cPcN_Pv zaBFX=@10yq7FsS`^6|*zV9epDPR^=^W(f7^D*|iuROycOkDQ+;i5!&;BbB~7=S~+J z%_jG1KX2TfiNnLBB>D1E)sf(|`%DX;P$@b#KXN|d$X@RiLskb?s?^6senhu{V138c~ctD=IEd^6ghVCm=YA|Y| zPRQ9YkB{b?znl0#*fK09+`{mrF2O2xwTS5_&5_3G4L?2y@DH@OL`TUrnqu*;&F<^< zAzz;LT99pp>=5u(J>jA;Q`7m^MRj-UP{e{2wpP8g2`ZXj&HqFy|6YLn|5}c8{cn~d z|1(th=L^%Vb(M?rH8T}!$PjczkEnS+jc-g%LtxAR?nAqGjZT#-( zGj`TJ+imys|Ni2PIDFBK8p8ig1=Pv6lYv~0H>d<5M@|ALIFNZ_&ieDf2$~wtZ0=Dw4@);~|;cJ|^|r zti6cn&jvB?_e-YqEQ#DYg%eH%1?r&O_WCIBCO}Xje*;=K-p5(=7t5SVo=sI5RB)zY<9%yxhOMbNpTimQEd8a;%&Fi&W}771IkYnLKO&3fn$oMk98llagZgM0xD%b^t3lGoP-}7-WB_*WB*&!^@DD7xDFk?12Z#d8E=o`5VQs&Hi!tB=E zu8x|T=*z2}qMrtf?YZS;*wDQ)Dfp%i;n}6{dLjKQ#Sz57Ivm`lqh0oTXtOA`%eVt6 z)CDn3(JI)wJCeaGaL2W5O;Ny=&D8E+!qRl#uy^wt#NQY*niHc%IONW%rG!0;kKDdT|7OX` zq`>-M){>=bazhVR*8^fp(+etd567H6@#uyKL3(W=} zJ*wD1rU55vi({3G&^fhbV>YjHx{Tydd6>&)`{nln_UimQTlT^%h*?qKpM5q&g8=EJyIBba&sr_m%K_nItG;T)ckB_=%%C{`d zncx@rx$=$Yk$j+=LN(HTLv(e(>$`saaeE#^561{tB;POYwN%$=+j6Zq+>;8F@!GJ; z6q`mS5y}LIbR;gxFFsM580QPwnY?egEs|R$Tx5(f%=g?|k#ke{v61*KbLBqsXDC|y zLxityF-d3mi81~}Ip6O_vv0I6VaX)<5Ja@^jW6rIjr2ft6vj+}(!7N93rSjE?l68e z8B;K_VScqW4L1%sr$9h5RBSnD&0Yn>fR!sg-8bSIy@5`~M<^+UielFCQ)i?{vX&e@L1wMCD)4>inaC}|hQ@{oDt(x~fmY9Aa zZHX=H2g;x!mTLBLU@+rL9?Iw8sguE%kSr8zO?8^%jt{OJSCDQgUN!w;+H;U5rP_ca z43dm}i~W`!{mU)MIq;B!-W;LGyk z@_q6>$M^MJ6Y~SJnZl-(%H$g-aY@PvKG%epn7OcB0s4j`&CPL&lb^_5Kn38^X1rd3 z)eQ+AF2AWPkGbFCa)ZEnPEqd#1Y{uo4f;m>>*^Oac+IgtbsKM;*SU(_3Aer`Ih8Xe z>D9I5p;%t%|JiPM+u@G(OGfNgYr-YIb?V%@TR5lJ37)P0Eh1o-`F%f|IpO7j(1=%s zS;4(b#0H<4pQk}bc#J^qGI3f&t8<<(ui{#wx!$sxDKKnuWE$QHXec-FK;;t^~xKg}?A3TJpP{4dVO| zca#@vLxk`h@^`o`HY9JcZH3XPe*vaF1V`xr5@qzVh0%N*N_TGgsQXKN9mm%#4v#uJ zv@#nz$+osXDdntfo=f8EG@bNtZJH!(wt%08SCqK;~;}mCWj$e_GBEo%70DB zB$K18ZuqoBw#s#l@Hva>Qj&r;hC!D43F?MX{czic`Q~^#R`L_+^wsHhUC3b2ADk#X zEK5qcqysaebf1jbpny0Zw!Z$iwGkE7l^Xp+;YA`OBPxtR!WQYQ94`hS40Zuz7oplc z$M9WvFu#@dKwS4^T?9t&-U+ejhGZiv43hm0&&j(y0~V?=y07Ag%5eIdYBR2&FS%G* z3>0|Lute~Ho+*ghH-s87SG0thU;gnqwfkPE6myr@de@r_i@|re@V>iNZk$gGn>@a? z@AweD5eBnZ#})N~xisF+nV$7-eYJ%$Wa{y}AtNzM6aS2;sCmLZvE1* zcU(Sbt~)Ax6s`?sXFRsbT8s~u`z6^6U!3H^U_8Yj={ez{_{XDRl(P4BNiENgpAc9C z@4E$05`M&7@;8;%cCae`OSw*pUh&9kiAPk5q?YLClx{kdlq4^&Bsg0Fb9w30*YJhO z+|#;3vce&<=&=Kzj6~0zkj}tc05+B(@nd!Q-KEgpRH<&JpEXMRB|L@D!e0_~W+a*8 zL20Z`FcAT2wJM^rJ=ww^MiZ^3>b9;MJM5DtHZ1ZLVOP#am-&( zG^$=sVP>s$G?Zsn_Q zcO2$wWtM4HC13ZK=C7W(+9U^z{OD7uUhmi9hE!Cq(pp-7kE@I(osnp!6JfNcD;B+u}%k6eiB7Uk=<%*!` zbs6*aeNzTHQ#KoIGKHjSi(g62MO+!x9nKic{EE@sFTVTg%DS)oh2?;peKC2XY5QjB z5-@Qpw`d(tzvgPn8`PRQSv5f{?TxgVeDPjvCu`Nt#QmVr1ns!6Ew?zhs*=Hhim@`C z;HW@aSFQ!xJ*g?vlZ=YkyI;Xug+@eyA=%JDq|`7CqsYsiJx%9Dho@A5)m*i1OWqG=&u_Q+Qe&X#y&U}@9h)fvu zX4)b0%lw1jz$WVs|JW|>Dy!mst|vb$>r z0O0!43pwgq!(SWXdm&JgGc5LB+y{OK4#;$Lq(ivaQ!-R8uTXfXDeiq(K7a@d^c|U+ z+7KXg&}>MXXavi!8n+IJLDOaP^SOCo-g_!N7c3BcCzJ}-On8hl(YU#n0aJ?{);2 zn4UNI_oW5r^L=&nLCNzdiFnsX>^_OiN#}Et86pZ$)VTTu4~wx3MJbV^nd!=71EUV} zqI=1C9|!5AATKo$ypmEp5-(pX=NQ-8nx;>~qvL#OrF#9@vF?&Sbkg4tbqHb>A*8ReSU8(kn0|p+r|Dz({ z+NkD3>{9jB;dFKKvl#{4^ITWAjPZx}N^aWi7Vu@H?IAtF@@5{}KjqtT+Y#~O>`wnc z#rh*-^AC>00FQ!tV>^jcCiKuiYxNVa2zkNGKAgvw1bi&SpA-tn`jZ{1ZuM1NILL2e zZsKIJ2{-%FyK-5Wc|Cfi(`V52Pb9D(1+qg^LqM~fHz`%sV;r4Dvh?)D?QN&vo}^Q z&oRu+WmORnU7Cp&6y;Og7Sb_OLK)Aw6;Qy z+w+GDiV(0x!WTwM%A{+#_MDb4@!pL{3(>^;^0&)g8$Cq|!;sg{Wy~dR4{jzJFn064 zD=41V(J|*1X{{fv+X=33?YGpEq3D0}qw3>GHRXBrU`U1~qrEy(&92NbMW_a*OHAtB zUiIV0&Gx2ci~XFsY+eDWYqV|>7eF#^$wwC&&3jTT?HoyqT%eaOk@jsw2e8eaHd zSgSo5-}1=Y#oPj zu(vh3&hTmVS=ZlG1%K&rs%LomyMHoDwFG2yttx5R>iXu4S@V7r8>@BGJTH=hO-4!z z19&7!0U%I|g#lNIpJH>DV{?*}!bj?-fzY_(~KQH$2#u;ItA>i!({4HT2|k^V~VK>1*Bo2>R>P+ zB0}xamECuUvb!QYr}Xc>d`Af1g?YSVJ<m#ou|&9QC;c$RA16^N#YX9MuE3B z_!{KkF^QudjAc3KRQ`OF@1B3qHnY4v*MsIt(*1^6Gb{HZL|N#w0A#6RYahUxmw$oBQXTQ786T((fP1aCVhq(u%2QnGb!tN0rmnlCJ^h^ho*5E8@ z{vN}{OTqtVSWvLQea~&Dbo7Dg_nP6G=Xp`J`Wx{Rb%DuKZ_&$~SCb%E=T{^IFP~t2 zeSQRMPv1)hi@>oGZq_ z(-JK!mV@NafGH|F#kLm1UYLim)o94yPs(aQKWyeMpiQ1id~5bgC0BX}5Kl8aAjJK6 z>Q#CA=g$~73(;>|4D#t5*21BraiJdk+(#7o@fg%<{)`jv;T`(piPq1=UjXIRjhM_le*O?1`&*4*psw5!?>v5>+SfHOL?fiT8^e^9oE3)zG(DvL)SXG zAVCMjq1_7s-}{rugn5TfX|ecDqtBjY12ab~cq82hf7< zta54`P?8(74mb}p{0wWr)wA^syaU9$p9?o6I#w4_Cph3;tnen7yfu*;YqnRWlhXkx z0D;zq%0<*Owzq4Mv!ZZ{^_SQcIcn*bCrwEuA~x3CwAs9H_Ggme;=(Vatzp|L!U(|* zn25XLyg;+xBdSZDE*~ijnwOhJyM^tQ>!?Z0CX8D8R<5k!=jPi%v$%7@&fiq(7;#H# zevDn)B@asZ_&+nv{{NnZ#zp)pI(Oe3kg-0$xVD8kS9l*jH-6vA_f?ZLZ(WEj0a9YB z>KY{LSM{5f-7+JdKj_bd>Y^=zN{OX~+~q;0Q^7P*{1obs=lMVR%f2NK z#kASqDr{0iB8JSO%5wdBKvcc^4svdBJ@s+#frOH0rlJWkc_+Z181*i*!nFzv8#8MH zZ;3@+j6#bK^#CPL>X;d}M8mH0fQ=iWg-5D z1tQ(MOC(_kGz>{Hw%j5o=QCn^1{KQ$BO)StuiE?^6llp+nxfW1z0ExW4UGLBuAh5K z8oUu}Cd`EUUf9PrnwjAU6cSi)-OWO`O|(|kCFa{qc|kTI42an518cMVkfrcK&-J}^ zH?Wl^DTJb@6RT+>+3_)A^sRJ>cSJr6);xLsVNF78_U`)<-zO>NacR^k3OYVWonDXG zZ6ndzd?p6VF3l7a9DT$>(RkB&K&PBubP|bgsdkATt5H{fI~P#sIX3Mka(50&``ZL= z)n$)nDSn2_K?ZGSPWw%6w2s8~$){^9`1s$NLv*>B3K;dlgz`@{fN+>Wher@Axs2aA z7Vpk7LU=`_Wwm7EONVNFCEIWpPEUFQ?*0rG61e3ZlZ+}isxiW9_v%PW&hgz?C~8_W zYzMU}%z|@dH8by-&{gF$@>VBIS3xHqkjhBzj77OpqIXnOJg6Jog3#NAnJ+dPgiX`> z9?N(-_F4Tu)V+69lUuv)4Jazr1yZC-2}qaTVM$LQ^j?*oKxooimI^8z5<;XaJ%pe@ z=uH&qodl$J5Co(Nf`TV&y}P{cKHon3>~Fm1jB)iyi?5l}u)w>_lc`LYTs5?`06`Pw@8Q2FY4Do}qlu;~nK6uj+00 z>Q7sWVpe2pIrz*HQ>wsTAb9=^3h+Y-{?sS0uo7=}VAotb<5&lNC9XKBTevlpTzXx* z(3x==nD7)UyyzXytv_jFMmx7?54COv3c)E6Kq@*JNVHt5SOqf{PoLHlYFJkkA})=g zFI}^qb3B%7 z4R|otmBTj=qvBge#@Vxv%`9{%RXO5L^n~s5Y);$53kK)|sCa1Sce*stDBt6)7KZwb8^0m@6PXdQs#P64zd77W-Jf*GgEX28* z0{qL2{2}J>C#85##f9v7Dm&n?I>x!6WZ~yTbSY-+?vy(k7xHi+WOvodG7usou&VC? z9TIH#4bpQ(Rv{LV-qIYV%9eJxUDYv7%v12wJq|UR@|sB~kMxvX1CvBHPJDdgtETx| zVZt)|8J>+##K(>3lgU;xGLFZ;&FoEW)vtJMlp4UFiM#S&%SL1-WT4b!5$422Eua7* zWTX9}WCAbIZm)be#EoxLI?3L7(a&#O!B^a1pVm)h0~6arm@iin+x8OY*0r@=NB&*9 zNFC_TbQ7h2w@?$b**0bI-<10@)OEKNdJ2`>%GxoRWkiG+1z78Kr^2YtS ze~~x3skOu6hZUd;q_b3)C%R1El=a#qj@VF896vID`#9RIwep{^8qzIH$i|A>RxzLQ$ zSDEKVFO_Kox{IM{lQW=@IR?E&>g*4Z_sVCl}(tAYni>#s+9@NG= z_x=~zcID!{q<%@^IejO3AelT3ZHgf0hac6wQsD7cuJaqPPcV>P3%OFP*QtHL0T~%I zFk>d^LF_oRv{Oqb>D0SR#Iiyf{EcwUr8k3`1R9GVLK6ssB=q=bsm(;RPSTG+*IDz~ z;1a|rKAoXNJDgua^^B{fKMNP}*u(=9YA!T_*gT?U_-_EAH{37Lxe zC@)x!JJQ!KqfoWLc%7*q{^MmiR=gioXkfh3ov#UR3 z4HRr_@lYtkJ~mN_E;)>U{%IG^z;O6%v!(ki^DJwL_;~wm{uE3;V2_AF+NA=t%T`+Z zx!gZUAc05RitdW43@qCAelMr|m3U|npOwaYO!D-tlR*$7NCKtUXkI#>lZ42~8%LPc zUsQe_9lg+S!zOKa!$>Kkb-ON(%l*r}Ke`vf2M&FY;4eFJ;VMo82>4#n#Oua$!Uv`h zi&>ANc~Hh`PPfhwveWI{--(ZtW;bHDe6I#uk)w9v%9g{B1EG_iE&u{lH^-2DtIzb5 z_X@qmPc3vjz7&J=r0UJNBtlfc5Aj)9EF1!8<4H;5vjWI#`5_od-%_ii^4<@(X0#(^j&3duNh^C~*HA7J;x$dQ= zRi|JZ=Ih?~hKgLwT=CPdW>t$i?=yz+4D}QTt2NIE&1T2J-SDgD1;d6q3e&$>-CetFUQ{2rSqYfC37N8&f;$vp}Mnf-mUfSTFk3rc)_VQ&l2?bYtm8pBn(?w}Hbfd8p zT47wHFDTiw!0UZAz|5oSxlz=*ZsMn1R>7_8L1Md3JABpp9l^b4Lkw8x=d9!f`9*f? z`Pjr!#d#YEgWznC^KoppBswmL!t~$f<4yh0dmZOkg;RrZ-okU*0`$!ka>GAorXKk_ z^zXl}Fu}ESj&J6MbBu4r2&nWn;_IV}dy&EYSjL-aam@O1uFuW~;{iXY^v<74#}$SS z*=d@~QwygbEvxamdFUMR3VL`RFSQ4}+Og;6YN3{^^XoCMYF%c`bYE`Y+LsgkMK*C& zTg3cn+w{P9@W@Y;fq-RS7NGfxJly1moc%VtjIb)S7x1D7v^2U>v);;ozb!~*zN$ViLI}?=EdQWU_fB0mZ4X^-k8tU}tOSL&@kIEfQakX9%(KZMC^I}OjP^C5#zoUoSqazs!s_kmTyjI%qjoDejfrLHQR$afLzl4H_7QQ?+U z_Fll>V$#SmkExaQbun(pV`bKn0|xuZv#_P}g>zBhkFP<6-%|V#zqkBY4{`Wj?>CSA z_O#vFeIya?aWT_jqe&P<_vhXXm+PR5qU8NA5BlpEal&?>9=2 zu4cRXFA8VJ@&cwMSk4KN!|luaVylortVlj4O#2 zJ^xx~Uz8It(&&}$Ye#7`m%obZuJ{mfYrw5HGeh~QX0o80n_e8Zw65)EDV}jUN%$+T zs%-liYj4AHQ1PruKa*YsJ#Qe(6B*UUVqPklm8d?xv*%hS7j?T78z+f17d0pOeh*Is zPbCKqMc2;M7HQy-w|h8ODLSTU8ZJ+g)%Hqeufy^uie`O3Df#(?M}PA~um2(w?MRAj zSA7qN_b08WYq0rtyNHeqn%&~6C!l}EQ>c>Hpp_CRG|=~2$z|b9JD-l>B3ktN^K!f*(lTP{46A^l z|4%JUKSM*|Qe3EzK2sp$D#73^0|UHjcVwBeNQqP|eBQA7s?s4)yiU-z{CY zr8TH-VkZ9r7ooq{P_)C3Cj=uM&Eieg&Wt`rH#Ep3?-ChS)i~cXT7#Lsd>qaw;@Xw& z{yV}5O?w00vLw*5#M-<0I@G8@Dyry^JMBDkrPU++fZ^uNkX=<0CqYGJKx2y~!_z8H zv2!Al(S9l%*>|S?j6RI2!H6Ld8dN#wk~wn!Cc*rj1o!St_+?c{XeNA|YZiZW z;jr^njO5yBF=ct=hYQWRiK;=LMg0hBmnP9iPkkpwzCEgKt-Va!n=dQaM!$Pa&PBVu z=^5^M!8_k#tU*LOb5n595{HKmN6xicLh|8QbUKWJ^%l+Zzdq#Utrr|W|682s$NA*k zw0{ei{;wd=DO?oXw0|E9y3>b(o90hZV|1k~n z_J56KAwNXd@lZI>{=*1z-J#(5XAJ_^KUV$E|8ZE+I9>_{axA)omjuuv|IoB^E;q#` zPOvj`^hrfT*S;S+idsLqy+=k-4A&M!KL1pAVv&+!iUj^5>m7`V?t*r3Yac>czLSa2 zugE#4@`cM?4`6@OldhT_*G@+Lv@zLwwKpGpe@+*Bux2dy+V2mFS9|=H-w%b5CZP-! zKf`bJmD?|om8-lU#;C@_*9cty+#3IT6~g~FV*Kyb_}{Bgk~EBh$C`!Sd?9UY!dtR6 zB|JNQRBdGs#$c))oG3mQWHW|-RlrFAy@CFD86b%4oNJSIQLBt$U5nOKSe9zI#y``u z9HU3zkGaM9CH5EDTH-GMUiw6%;3ZtL7TpezC%ZE8yaY|&LC&H{zn=QF9{r1qv*U5{ zVzJYa{rQKEu4lhhU!08pBKyY_g}tB5a53CT!EXF!%58Y|=nsuk{p^H)celK%9N*iA zmYSe+Lf;4{7lcciGL_FO2i!8g_YTyU8HCQ!OGLuy9WpYziWWN^4-*T z62UzBm^bz#CUT-$qO2uG^R=(X)*KA+M`>BX7_VqrJVHQV2skG#ur_4dhkbSxzDM;a z+t*F;`M#@edRNFZSNnY zXQdyxz0lHhyr#D~>Fc!>&}%=t2j8lzpAA(@;JmsrFqU(E-X{1dD8h^bW0o6Gkx;A* z^kArO`w7Wb;%O>UguTt-+`pxN4J87CsC>T2W}D2ySC zVO6oulY(7hN;>#aBEY2U!*z28W;3gr_6tc?;xhc%kFzSvI}(Xn?sWRCIQY5R6m^;3 z$70Ksrn|-cnRq6*5^aP*NnbuwWmZ`zAT}SYDg#!Y#&6Ha0d(Wo1Cf2)s=rUz_kW_T z{i7cxV%kYGM=3x6m#iU`7omyMzsR0`CnLWi_}AI`8uC-=85kQdg_)TAS~v0Op~3rB zmRt})yEU3xS~Z|2^-8b|Nl(DWt$&sjwKP3)4t?{@>KEDVb5Wp-vV)AqF=-#4iY7Zo zc(sbvr6})ZqWCsXq0l;7nw7$UBA)VJ6}F=NZp;?}Xu`-@Cz1hVgWY<~Tl`}rCfnRCu4IdrQ&2l6e_7|Z(YBG5|M z6k;j(fbAVAgS@{eTo7VK+nA1~Jwj7-zB~!}H;6dj{EAEQs>kkV&OPQwsWe7)AcitC zBf6V9pC@t=Ax2X`+)yxjkwhV)VxGFyFdz6z?CF>D=A7KQbH5hlsk*W9A?dp7@_SIzP>@Ofr(xMzqa^b0F` zUy+>WTI%rJ^DPs!^PlgZ|MTB_{jJVjzCUl4a8RC}%+ST12)*+>R2|^GGrvV0x7lwE zbhMK*0g)IWMi*#gxUcQ`uK7xe$>HePY}(W>vO4;4;ZZWyYw!Q||6Ta>bYYigw*h|6 zA51G+eZ6f)W0oWbK1LgY$N^|kZ*osK8M)i^3}vL#3BkJtsr;1tYJ;PDZ5~NNm_4g1 z_O{iN4Y)tj%u9{p1v>fmmZkJ78fJklyDdx-Ny0blncXw#`oE7=tIgZwQnj}I7n$q0 z@Sl`#Dd&I0eE&zNHvIpK1^yok{huuJ|1x*{H_^*?Ra<@?O~ zlYDt<#JKfqKLDQc%1SX+sNDw2J3gqnH0+lPz70Ca-Hj9qb#F_5OU9f;-uPz%sy3Y7 zax7@}#E~A{7i7s+Hc==PJ~gjS+Ja(W2rx_f&OmyWI->n*K09K(Iv9}cSm2n&bv?GV zPImYg*)Xs#Up<#+P}3T~wb7YgYb~}(>#myf9Hrmm3hi?)+%17y?msH={!hbs zEn9A+QeS3B1Sh)wdoti}EpHpX8FF-Cz4|NsO{bR%)+# z2ihM%llU`2a^$W)@1uMoS9T3z7Z|k`E|#fZqbU*)c}r^ASnB9G|HB1Qj<^g6?yIZ8 zY~ef_w=yg01_eC|%Kx||6{=5BU`lR0H^XcB26o^5%c@*&viWYLZ;@6+eks9E zwMi5J=)%sWvnZw?u#y#4T{yLt&$>V^6E$Oa?X8*1EGvy*h77HN{5XN>lHs6C+C#@X zGz2u))oYS)@}fHLpNQi(UsBpPUKs~q8Fj*Tb0$C)=r<{ zLN^?u7*JdyjztO}qZnf|75u|i^Y3iDInA`ykdX>Al%tl(;CRMTa^2%+cq4x5>B>%M ztIoTd1VDBPM3)ljA49uC>Z%dHjCTvEQW0rDUp8TZB??G)k~<# zxoxrK_aJonh?EsdzT)YFjyro>LK(JS#p2;m)jLVw%er$nE3Dpbt1*5j$*^Zplim}A zA~dhKynY&}+pIobHLL6~w}QW}w);~)sVM^Z^*3YlEqBc((c}gL8Bp$Xy4bg~{lK;4 z!2oMU{?7b8{Zuxwc>LJ2ln3s2Es09P=J_RkU!VY1qkBo2Rl*|qfTg)2-{5MeP#j#2 zt#W6&=(x04T*_Ted^4MW->QB{;Hk!|T<2Z3&!JyJ*QWwz3HDaCmOBZKt!M_YYZ{F6 ziFNYI0tg5i-ijsAkfbMng~26BE7vxcPz?( zo$)E#Y735u&fB<4sbeeT1v3pg(f+Csg_0c_|B_tCf+g;QeIm?6B$V@HK2T1CW5P*G zK(~&!n4JLRm*!U$I6(4^0B>BTLF2C2;l~$<26kMU;)3hX%w1$mU^miY*|HvXz4iw3 zT-h*15C01q1pWIJ^4n;O2#RoYjTVIzJ!$fO%gp)CB)sfD%d>_)(4xShe}!M+NB<^G zaUbnJPHvHI&IFd=La8&eo^Toekp8Ip_aEA2zK8!{d*kVMDCRspFlYXbOoA@ z{rd$>cl)-r%gsnUyq<(M{EWWCLy_@+2V1J^I@IpV9uKS&w^}f}gK5}op60pE)yI1c zzL$Bau_%@PKch^!X|^;oA;n z3gV~mPWd>f=k9blqJHov2#66nx}B67eDfSy@U66ijbg!eo!SKV6bmYBb#Tkx0;?3* z+?8vzW){!JQ}PHN1c2JcnH;t>a-aWhbuO?noNDSth>S;|7{ZdqZ7;8iJ&uU_@z+@X z?M3O72Aym{@sqhna>+Gxi?wy~^6l3H-dHhsG`{0I`c4BmANrjoMIM?NMQ#SakJlYw zPH-oR?A5w|49T+3T~_!}$hn@BklG0TMRu7=Gi)%bo<1)& zi`epxdt{KzE!q={IYmq|Idr4gj?!?bhg)OiL+tUi$WcA1;NL%#&2yuMfKj#WxMg^Wwq$4Vdwnat*1owQ0nx<|(jNP(6?& zZr^>EPaQ8%nPQzOT`wuI3UxggAIFdk#O5jy4t)T(M|)_y3UUMTQ8aZ6x!#rdWC^7B zESUTR89B=b=d-O$D}o6?JNMG_Z;WiJE0V!~7r|-F7=ZmM*09n#+Pgn$uA+HCg_dA( zRpZyXC_ss4z)Bytf^pbDd~sZb5XLAdKZ%$)7-B?%88+uSDp2{&G*o#sWk(G;sy`s3 z&>Z{_(_8i0f`6MO@pCZTLWCVwrttG@yLnI1tc3wS0adnJTMfCt?@q1YUu()9B`a(T zV$yrpE`K|#%xYc5COQ=dY#iP%GkSRW&XEvrQLz&&u3F(_o*+p4@Il(_ltgRlJ}B;A znhCX(wKA%;$&uKlE6K>lVatD8$TDv9W=wlgT_GMAFUSZ2a`+akjT`Y2u+pRl+tu8V z)=%&wy?Tua>Z;Kk6)0+=#99Z?dpR|+YCRVyXA2Fd{M7XvY}6cCTJnID-C1J4CA-#3 zSAv^0h71o+&R;9REPTofTr+bW-|d(69mW_VV*``#jT^>CKEtP%6M+2)+kS*z@92Du z>>G!wzUE+-)DYXy%G>iV*XzQ}=yOaQV&G_w`xu}t>lJB$+l!+lw*j{8V0(K>wZ@Q` zCf7NFf#6p2aV7_$2~`0cddyz&CD1X4t9}Rwa#lOi)jx`p&sXW=eW3PW`zG8dMSl`k zyemRFlWHfL#1!bhnla;7zmN-D*R2$a|EfF?|wo1Fe>2@Ju2W zK4-0Bt(xPaBZAU}Z5H94+e6Gbe^vvez`njE;R`aFhX}sjCfS=_YN|6ThBm}xjTl!8 zYv*(}f`z@7WOo4SAtYGX43W=)mAvtjS>XF4+T9&5Q;biMm+0e0vC<+*8_~1{guaBS zk6a(Fe#l`X1G8zE*j;;&v{?ZUU3YeEK||WIVD&sg-#n{Dn?sqO$Y$qZn@t6IizEY! z83~!^@Xxsy0@qU7>Jg*hS6<30hl{y%y`@I7rp#LrKsXO43CMU`m4S_I zldG82?(2x?>U^X*RV}!wB7~H;QmkCnWJx|=jw{G&LF+0*a@97N&viLs)-s>B3FU0M90Y=Id0FyJ#| zE-6iOf{cX-P{j3+WG=~F z+4l1!gfq%4rT87P4P0XO$yT}OVlZOb;i)A+2pBRiPo z7gj=CXDSq0r7{fBmXp66P4I_imL4q}5h zb=eG|5&qF6LM44JBSQx!91K=E(-zj&_CS5t_Hc^|l$q?tHAR(yjQHf+^T zScBQC?IO{bcm1D$J!xN=B>{I*MNEG@8xqJjk?E~pdKF?nU1k_W$((@VJ4Qx1H{37P z=?fwXmTI3rd2#fF&#Xx>9yGjXI5^mlQ7Oc_k4^$pDAS`Qoxg}H?B6wi|0fI|O#qY9 zYb?V!%LY)pQoa!Eo&1RF%EVqV>jG z2$*FAXJ0x&01@n<%OYm2oU#c2At>N(Uu}Q#HQ%zUAYHwalOkw+&W>M+rMO_8h}c%( zdbP0W){udWcV=24zd>>(!oJHv|3xrdwttI|95yzj{TN?DE@|R*TryH-MataesO&c| z%Ku@H0WlRbcnb4LFUVpATIqs6b}CQW3!W^#KWJ8n@yxF<&1aMgtSGzj*=0QaSz2zlSV1n%efpH>;VAcs6(3qWF`KeYIP&s-6o~@di5G-N6$Ugz zw>B7|+-c78YN2{x1*#rPg%Yh)6F5Edg29K$`VSEC?v3{4W_h}ybWZfAMlug}X{a5| zJ=CsZ&0U)W1;UUInvZ~T0BPo4guQA^K#oMF#wRHaf*qyqlvOX2NUNu-QKH(Dv=8O7 zI5aa~b_p*~U7$E?X`oy4M>5>VG$C>}8y6s9V>i7VX!-qVw z*Ja)6sI))MuKAKUqV;Lj5=S`b#*b=oeXAN|d4Zik!j#C5NP=R>ue%`JLGdIN3=;LUfG3i2&rW>W_j2T?Rn2VM)0#++=db z-PCw?JNa?9D1+Oo58MObf566CLT4o-6$ZQVKv_ISl^Mm`Q!Vy6Q^wGl6Lr`dPen5F z2;blTy4n0+dON`X8|Ds(X8v$fF@xQTi|C$ol62i`sPvTBS}5kvkxiI=^E-rPhuWt< z*{dLBjMpt2npBJ2>YWU1XQ}hiMJd)-rpo!(<=DE4x_@7}rwAcDs}ea#gl`(P783TozvxE0y>^6^7Epilk z!dCyzlb*y%%~zMMik^I~e(&I0^&m&+ zD|1QZ#7Rms5{};kH)DZEPBN6EkE)TZX=e zdfQ|cXDAo88>h!wByidd+zE-a%!aA$^6%RGgS`jn^T|TQ>xY3%IlF3&V`H&j`yK^G zk~SXQcX)(XKag(2XS)Pu-Mlxlzr>*I4l5(xPe}m99SzV%8H)fBDGqAB1!G22&u52V z;OAlOPd<6hREbX!&F$ZIL^n3CpZUqvzM(R|Wo(2%UKEG)e9XKIyVw%l0l(ZQyEYmT zhIaBG$B{EO(0Y=aUm?e#11HH}l7DWXnIaFPfN%ZAH14M5%zTHq3hK?s-G7fBclmN1 zxhay(ytH$6PFEmXOT!W0PeYZtFXyUT=nd8>qw+Ir!})p!P0k+iXhY;8EF)}V@s z*6Duauag-lf%3V2S%^5ae%4k^;)$;;R^OKXd3q2Kjgvg<=aiambM~-YbwaYODGv~p zjO16CbWz3grs?TuB{uqdv%D)s#76B&I5*X?q(3X&$fZH1^|#ED?~J5{g*no8W0O1F z7V7+%FNMFm>|00ey{6F9?>zO+$J;CaIw z6@caq{bbU`MF8b0s2e8zTVBDpD;CF*Mxv)RK_>+vkZ6z{z{t~EKa{Wasu)|1xsUr$59cjd%IAfMzU5kcQN{r?kOc^MEKmuXB_)?0 z0;K^AS+weWmQ0)S>+Ve-=Oumo;FE!zZO%Lftcl$gSX;B$R_YAFG9yHi%UZ>#85sXn zc;28BIw>8eIoiBs5q3&vG+ohEJP;nRIugP0BIyVt$C&oquz{&iRxIho;LlqJw=X&B zszPC>65Y7|!p}Z!pY|*!=svu79u`Adty>m-Z+u1dK=L+WQjOhmz1A1I1OH(i&?I*u ziD|PgWed}39!OkA^c%z<_yRq902;qvBp9 zeO|mZdI&{mBbTBQ!yKpO@Ow_gZwAf-o}nv-6GIEy=#8-?n@qGg$~v zPA*R*PMCp;kKL(s1I!lPHaSXj#vmn8MLsqSbvq5SwLa(b!KU$oXXX!7gb)kEd%ws8 zk@Leh*)}^yI75IOPRQH%^$K2Uhx&!e`pt>U$?!dU3((;6XLA&o$RisC?9? zEl;-QvCo$}72qsxd=>{r)YGn63N%khRE}>6i|~Q+I?*67ejnr?6>V5K%?kGXeY=kU z0Bd7v z#B%Qyie*W@+c$jCJDB?Nc~%}t=g6{M_5Fmhd!0&L_y|*&dG=8-QDtBqsM1pMw7>Sr zyV*KAKLR|o{%d@hxI-X&-AcQO8umV;ZGgzrevG$R7-;=wSVok=Rhcz z{&^sH0uVkWheH%7m1XCJbn#D0U_xG|^hP(aF>r(VUW*a1hkcHqX(J!5!(| z`*4BJ25M!&A0NZnst3RR@!-ViQe?g%{Bd{lxCq>+>l_m@dc2oY#yVhGQOK0ii;_*} z$$+60Yh|>bO7pJ~llXH79tXK6mR^mp9I`Iy;uqu{N#`n*=|lXsg0R(i6<1pwdS*E7 zeBkkm%q7avmA_1fkO(QBscn$LZw6nF?_YO7uo%ZDG6bBHc6dH+Lt>%p&?MbBj0L6kWd!)^qVa zg2<0=_;kwGA;X;+=bn1Zpd!|unSp|vgM>7vo+;%P*J^7;{EBkCcC2aep;&`&` zafa;is&LFuzYMrX6FA`dBr|)qdZs&ghC^5I^RptOI(v_b3pQ1BT^%r;uGr}3Q-e^X zl>%#HL+H(*k!w1+w<~pHXDim;5KiJ6#b=UTEE3#VtJSJrtUfC(JREg(6<+|W_AQt) zjMCf_kkwAB8^LmfCz?-CYTaEeZ^- zW3PI-onfH|wata4XE7`b%d#A#GqAl0O}!<-=Ne(0y)pmRtjQ=#4>RG4*DaEU)oFVX zV!yu_A{l41r*>=P$OrYQgn16q#W46%ST@mY~Z!CD~WL?CZ^~V4=s!EpX_pVy=KfcjP=E>lG1&JyT3E0C%;$43TTxDbU;DV z^c~8CiLJ>Js`EL|+o^nZiy7uJXk?houbsLYf9n8bD8VPYavCxW9c@0mdfH*5n1VC8QNa5 z0%?W&FM4~EdM_Wcx5+A#ETyZO0qJIO+>X8-Y#0U?%}|sn`(*Jgs0<(QZn6-PUp75p zp@#ti%K|ldv$tsLNEb-B2_L`Q8A}j_2HfO69|HP?3PAf`3?`<~T_1Flh}ZY!CcvAc z**C)_hz#!H#mYsJ)DJmYwk31NjV{NyC?i=C)dK4|=(L21xkBs&tR?a8^apE>j}X$? zWL|JQ)M%bY)qn&aFt>3rH~Es8r&&#{1dNGiEQRLAW-+9*(_PIy_(i62{lm3zrpUn- zF5=rC2CzwjmmLcd0MHJTmR5J=hjUPm;I@*$p(y%^F>KxJ2IpBt^rlEpQ+wpv5mte7 ze$MpnV6wx_XvGyb842uw;jVFupdjnR93lp5&ROWNHt8oC)_`2Xz8_&+USaGLs7gl4 z*6dmI-)>qCe9}yOU+8-;wpDP#P6}hTAGs>z*{1SUmOI<220!4o?u5641Gr)LnZNar z)LF6S!PijP`bW*hSVaj=y;|L1$k1y)%B{=7Egg(<7T>Ke7Ui<*^=mQ$`?fd((^si; z>BZxPW+YqQg9%4X93-We1RdvlmoDD&*oRTD#<#AyP9W(NsMGU2DJ1ARgr>lH@AP>1 zxNz)K84)uMlrS4Twg$qr3vM*TlTz1pe~~dQlLkdfITAG-in?c+M~h1>dury7vp(rB zO4^zkK1cwab4R!G$ZXE?X}9dH3rqkNHBiJb#IBS_nT%^XKa5Qf5yio=c~>{$aPsSiNv&tD;DE} ztkni6bR@Fj_Mp!Mmoi;J2fSHH18D?xO-tC=GVggJBtNOjswtq$?F=gF+gjMOyEf&h zZDoHV1+!BIeD9U8RlXP!1_2#zq~`8xy%G9ZdTf85?>z0ELbLB3%vJEMS&b4y-)=p( z$pw2NWB1ZvjPO-~lkvuo^GV&$nqIJPRO+?~3$lg>w ztWEah6ZD&M3H{P5#bw#1V_Q$&dMgj0_|FnmIwvYNx7sJFZ0S6jGt*BFt7Zu~3D{t} zkcr?mzwuS83Xz9r5LI^YT>)-=lHcbw&!-Zc+C{o!%II2&N{gziirKWsdM?>U{+b(M z`eyJnM|H-c^elMRWna8U$!O4o(dMj`Q{imGv$Aei>r z&9$AGhGz+m%&qg)Y-6bM$$W!kOXas1#jW?*z5&UK;GK+BBMEv4xJi7L`789=>R8_W z`Z}t-dnnzV?47zhg$itLrE9Nk=L2X|SvKKMDzmFwnsGZ1x=u=}FAKF2m6(2HPGE^! zNg;+6veZwecfa=J# zzC~jCOWF3xDfVah$-?Pi%@W5nv{W!~Je9Uc^h0jxA#1CFZRU;fa8=PFfdF8JzY=^N zRnyL9oeZ^*3*xmh3h{C&#BE*Dk zrB&S$`7O4ril%4$3fVpFT9^6EO5u|t_g4)RU}knRp-6j{V~e=W-0{I{Lun>VCRO-Ps$lE_d%NY zI%yw1qQLS;YVi#J1*`e_dyN}4ao&X*W5F7g{QHS_oeI?6pS*bCQ zV{G6VSP-!Ryqk0g6`#vLed4C}!xnBl11Fwzus=V2HP}X@AMEi)wn;fE!L$qZF`u!a z@A*JbJG{T%`J@*fH0W@zv9j8kf8geg2~(t5l7%}fdjxRa&)N#IGZUE0Z0K~0@8RKG zfE>WIpQ_s|Fo{s%rTDpJdU{^2&Zhish~wfT{90n*B-UaEVvh|JP~g(EOj;r!r1^XF z6i;Vn;LcTxvOgqX>{u49!bY3(}2)>nD(%e!@UL33VxWZ}}n-R$h^0 z!WM5C5=&C(HCM)!%3jNyfY`^iASFck-s{5$PdjW4@IyePWuq4{IRN@2Bw9kNMi^G( ztLIgdfq^^<*BAI6*%Cj`4kvfC%s@oe zW^&&r5gF|5VGv4bc*e-sVEbda&usX#{7${-%R#d1%F1DX{ol9-cot!jmr8K-lTPwm z=cT_2dpvXW!xnSUBPeH#SdYgD5Piy7w5<}4I?`94T_x# zg-x4-Rs@U2#o2SW%_MD)&loZ3b6zPf%ddL#&7s{S+w4BKKN#PVpmxoZIU5}@+iHPW zhwRnj!KqbkH-g8D9bS(NUdlm-8?`Bq`U-CS_VCkR>^u11;CD5Vw#PJp<*kSQ7G9$X zNv^kxGuJ9f22@KFp9wgb+E-pWaTWEko}WTWYs-jddKon#rT}dypUiuj=;w_j{(t*J zsrcI9=957hf)HPOPMgK4sFxhj^iCn~{OZpFsq`N4omG{72|n1hA>I0jwLvv%%k3>4 zquSSS4-|or>x5|U#X6F%R?~wX4_J*5Tgk6&#^$vMUnXTz_!2sGiiM?_X3S-0Rs9$Z z8)_O{(;2=GTH#eFel#sh6v3v0XDW3GUgrCl5%uZ2)rbX6!`iOK8@5?cw$STN^?aF` z2}J-Zkd~n09bv*wc_4ZNA?fpyub7 z036+jnwRfbG0Sbtt;X(-jq7e1IV!EVA~pesIV~-vx;9q+G52cg`ZDc154M(s#M!#< zWlka@d_f{AAsyuN_C?`rqBxzxQKtXiwnYF7)p*z4@P5^ucen8E&!MTz)h-VxpV}PK z{mI3Xv>c;yb0HN&%>8smR~q@elpk;qY;w|J%gnGw?}+UR__y9Ozw79Gqg~7HtK#CU zFo8CRg6l8^*m8nS|MfW09%rM##2!Ht9_Ti~4a2kOzaW$d#0z-1C#~|MG#eyn3PbBM ziS)0nvKoB@e=8C{RnYHEwlC*bWid!qAdv%4ZtsZO`Qk-%I{hG6N$@b}(KW1t&4PNGP$nn_Wupuj%ILTPEbmXGdnw{6R*p^roJR(Enr@ zS4NzvK~rVOUWiLpjv!3S$LkTEgUEeDR#O5>TD=v@#YI?A!=*Gi%k8gR6REorN~s}K8L-*j5ocI7Dj zt<|t8e~XAA`>an*oZ`xGc`HD`Rz{us&RE~2MRVG}RPPz`Ze~KSf(0h%zK%J*ODuCm zl-8m60|pD|#w9rK32=Q=QH`n`Z7pnrn>w1Fuuo@p7^d15$=IN&?UZz#eamY>6RQ$e zrWYt{dp_2Uv@w6C<1AP=z|f1obb{ZSE_IKtci#dNqcsW9lC79vYv>?SF6@9M@^f?{ zpVlX732I%&unqa%k$L)%R2UXJW8Z5oQXA?0S}yj43ryq~D(WJnh#J3$x8H*EZ2)gh z_=wAm?cQnH3X#b9kh5EOTM6aq1~%^+x4r}v@-Km;0b-Y~dP!Xz!1`cfv}g2n&skr= zZ0LL-RzXb5FXiUpGbc7Q$OL2>_Ut1_F?^XHH%ku_|3!B6>X-%m!Q)a??rs!j`JqJ= zoAp>yhVOAmS*-_2kw51X%?2O4z#c!hiGRt|zgWlIS*WUQ+R?Ld?yX`--nL$G(TmK$s8Xexnm8-3<|`%y{%&-bA|=T2JPCuI zF>}xjHAnEjZ!ic;g_E8u*lMhv`s_*vyBb#3q}pL#&1Bi+2~XF~`wR)f4o8GRAlgx+ zY}r-PBqpVuvfd!$ZT2oY4=$#)UWE`_b`3(+e8C?rjonEy0yhgD0pyz)TkpArIpZfu zcd9!9U5`k6J9s~{Z`X)KgO#3C%Vr-m)eQ6fKxdy7afw^-co|KW%=vj+Mr1$0z6>eG z_OkkSa#{kIdm_&oV(+ugaGa%2S{G4#krlA(9^VN0T;F^l{=)T zS+Et|hYERi!O5+mK!)wN8yeIuw*P~>_l#<)>-xPRiedo-1Ox;GLT}PS2Sr-wp-LC2 z3B7lt^o|5V=tv0=Akqm{Y=EJ6q>A*8bg90%uIoPUd!G9|=Na!fXPhr59~NV<_gH)H zwbz<^t-0p>{qcF6B@axszS0_oS|0E0<2+genqCuILi-0Yg^SL7xWE+_a%lLIdM@;+ zPKjg~B4(yc;-S=S|Iv~JH8${W#&4iyu&du8u2g$PeYnRq9@ZZREdVmvPDbFv41D}5W?16dj!-?NkT#Ln#O(&rLF zUp8LfvR+r07UZ4lPx##M1pho!h{Bjm>(kn-qsrXvp~N0|gtNt+m*2JOZ<^LC7+QaN zQ%Ywx774BVmU2HQvHSki9Ng$2m+GFnNIxlpK>eoV03z5}ZxK;{rYx>|Q4zN#NKNsk zsMH$TdCz$AK4NO;m`|Y6>F6~PHQnD6MWX*Q@ZdjQL~;9 z=Vdsu1n>x@X>yH$@>=}({tq)2$^8NJ*ai3m@g6mNMx2EjJR|0Ydp6ZZ_Kq(z_i)D5BeX6vweVLnX?!p~LlXldL{Bn|IMFX5)y)#06zSV!o-@e1hi7m} z0!#b?9KJ}2ihMhpD3kWOX^1tPG|a49GstjjXbxrNe!Eqy8#I%6n!LlI0c41e<5aTy zBE_6Vr?urGT0}z*|Jv|qXu0rF8#%E)D;h|ldh7yUl6`OKiRSdF1Q!twtv#tYz zd+~nBDv+v%z#g)h#kpobpK>TNN+AE*@M35o$Q!$F-A*>5%DwqH+kGELb5Ppbx%}XK zlIyp0-G^zb{rg;>7oA>Kza5uD&Gt36^aUUENDbvQN~H41k#RW+ytTaB)O7iNcRZMo z2rKG@aKk6qh zC#ox6)=IG3IE{WaE}AYqEUHJ|x7|X{+&^Ugn)ZEi8V+?{>l_@rss)GKHoM}|4tHl$lGPMnx*!~V@BPZKdd%lFR;+;d+ z)S`Si7X@YitPvP1QoEofx(t zS;Fn@y=V2-n#$kujF!!I=$9%Mfr>GEXbrf>c>cQ+PlA%Sh;x#U6fl`Clt4TBq>^|e zM?AnKC}6hXN7iIjhpqFskMry$v8@|W!&hR5Mm2gZx}HPvb-;opM39_)G}&-?FNEqB%B=9-Puz;zCCOPm@y3Ho?L!VUYB7 z10TS7`E<1a19sUI^(+*u33D3BF*k~lnw@nVYkrPTcAqyVb-(WT^T>zG$f&hhno?fn zo5)AIyOsuD9h>WCf(?ctp>;-cejjvvcU}_#mL{JN5dnTq>i>kmtw;Z$+?4?Wmg6lP zP}khKLuvE4n$a+QpN-mh$&z7v*i+Y^TwyB<;=6$j11!K%At54r)6ywo#t%StP4dF)v{%yhR9gPcYvB@R$Z!MKE#1PMuBxZrDs5 zvLDcf+yQVsv8EV1nM)`}-PxNn(`KX3G>Tv_NWpv2d)=hRE`Ml{_gK!;DF25~X8pXPMoesGRfWmG2n${SEiakolY4ZpP$S#2a=ho8t zWO>|Q4KmQA{2B8zCfpbPZeQ~cfrgKLT}GmW&T>m<%ISjzWyOFo0-zq| z`IuYkYN^;|+Zq)Uy62isXnhk?Ep%VV*5rYXEX=m`UG=0HrBM-2@gj+ooRq6^OHJ(D zE<6O|9Hf>xywL=|ov8k5W1=h}^lmF(#s~iIE4TB%B1gLj`7jiQk(El&dy+yY9xS zU-o&srg@xZ5(z>$H`J`d6z+2+QHi^iCS@pz5mOR=FXGap&e&tS>-S`+hb->2Huk&T z7g_OuLc)*4tbV^ZuD{fqyY#IIx!EBD--@n`3y)hgf%ohFOk6(QU066bwK_)mij1Oq;xk_(A(m<@$?P?@OQ(G zBKQHG7Ogc)l&_YJq8QP-F))3@t~tlDXbBMsV`m2q)=N6{+Xj`XXykExwEIQC{|;lg zICE#uO?9Sl4E+*fB1ZD9o8$5APzT9QuXxU3Tofr@#f(mRdcX^^C80AK8;N5s!0DtO zwQ-ci(J@&ZzLeSpX=84?UogQByca9I8yC6aZM#2=GQqKipkx$wd>RiK*@&^=AxGe7 z$Pi1@1Sv^9ZIB9#?bC9H&CRdI20h0o1=Fg^n;VNOGN$XpHb;Z%t%HoDutu>&(=PW{V;sE zAuf1@tJnLeya*n>{l;)r73TSFr+b;KgPws3_8EoGuvUW3{LGGhMEt_BXe>`c=0o4O z-=v#vNrnmAeKfce?)f3K1q12kr5X+m&#rq{C{V6kY4Wb3t;jhrGJ9C8*7C5!56L@g zQkDb~fBJ2Bk^P3!Z~|wFo?4Q3RbASKu1bnnauUr^-H9_QB#d{adU4SCQYw$Zh4;rx z$d$oVz#UPK*&p5o&T%qjvvZK&Dutp2uQjW@4eGEX{oA+@H~}Ifssa2$$**3#Gn1Q` zeMp@N^Nbg(v*F>~8P;)XI5yXqVuVAfrl{-h!bfgf)iZzhFZ7gYZRh$!}KgltqzQMp-bA(>g=Go z258?LBP~eq0NYYmLqPyylh*oi~rV?495_`erVrWK~C|7-{ENn@WahPuUWK zg+;6_Slq_Hs)8;&y!_!~0a{S$P!F$`%F(bKu`>DZa(YS36 zSWn#p!T3xPjOJ10OdB;}V~23p?x-2O2GsL<6q1m-s+e&uZOMZ_e<6v`(hsfEcQvx0 zBLFo$jlWl;a6I#1FuJ*&XWqi+7;r_Tulg!Sx2;vLE%_ zX}$FIJCHajumz1=EZ&!=XwWtQXa`>y+Bs?Z;6?f9PTJA0^k9wn@~7a=<7Vv%Eg37H z5$JTFiz^4UxX4MV6?7Eu^Fa^3{H#xnr!5|D-8q>0=u||Ni=cCC<99QW{UQwGR}VTj zDpthq`?1XhDi$A$^7bU=tC{TEr?|x$6?E?0k$ucgL}$m6@>rUoO;gNU+;_-ma9_ZU z`phpdj-hMnra|4AkEzRz)~Q%R4z7bRo5(}E$vdLr(rTZm?guQkgvwIF>FFtL#uT6- zj^&Ej5!gv+@z=_)=2FgbG`U&=m>XeB!(cY&6U`o;POlXFe1|1KTBoQ6lV!qyb%@EO z%F4op|783PYMAyiG5g+3S;MXG8Yuu{50t69B+rB4ZWZ;|uO?IKV`BU7hwqB!j1p#* z+{4N(^SSHE$Y*u$OGeS$sdziAb&?v8|x*ONs9}v)EO2n*O;o0BgfC@E|!kR zCgikg1q0F3itt8o#u!fP3_&hx9*)jTP(h10&endGO6sP>w)7%5n#V;#f4d!v|0s|{5 zwrhG`u9-(+TsW*8&$5|hffOM?8F2Weg;o2)JD;uqJv?dVxz18_=(=JX| z?@gBFK+AKbP|Rpfc`sW8lhR=cX^fNH%D{Dhq3cz-4@r$*)7ipoA0kZ|n$uyfAoYGq z7`_3CZ30c1_u<-7w$&!&)W>`uMEDywISQk?>&az!>=0fLeu#kWR0+Fe4)M9dDNJiJ zhMOU9(6s$zj4xWC%Q+pUw;yw0J&sTzE;^Yh2!e8 z-loOFMFWdc{&0lTZyrn^&eRvlP9jUQ2)POWIE+Sfgo?}uwKNId?0;EIG#mo7%L-sH zZTShk>cg=BZT=$+;3zs(Nw2Fu}?!lu?M5oXGr{69om z`d4)mi;v8Df*pPx$(Nt7fa_Gx1WS&>Us2lKo=}1pY`~wZ{-o7ylMni*f`4i;&0f;y zsoBs5Iccgq3<0yFSp0~G;g(2j0l!oByC(0oRP~Bz%48Z`ZUuMz+uV3+*-89WHT|fT zJIa5$!o+B~`o=hp!9=q0-dI}?e-g)s0(G^XpwFGVG#;X&Y#lGJ$;I>sW8%WsM@w79 zq2i><^@Ga~Fyh0Nxa^6?O(h6-A67l2#Jo(0R6itc9g>=@zXo-NuLycOg~XZHa~oiv zjZnCi8I5{(grD*9S>w#uz;xW;#cWn7c0!ml{V)963pcG-asJ{kgT(G6Koe{<3(2$s zwn!L`&1n*0q!#?${LKAOQxR)kKvO3aQN!~?JMs3%kx?dD3!tmDV2 z{&Pnsy5Nanr0?=`9TWO44KQAg)I!3A&tFCi`_pt{b#B*2Js<Y8Sv^n4rPPg$U0I(#J$Ow-l|p#we*a3${_kC2J@dI4tNS~p)ky=wOc|v#VAIT%5_FB` zkx^UoGj)^Fw+1yqNR5DX@Qakgd!@w)Rj$qzBT*KlM|VL3gL^hcro3#-h7`ZsJmlWo ztnoR>54D0ZtAuj)H(Vjq5i_PaJ)U^;)w`{9&+V3h*$P=ClvYpgD4=XZukpcA;=AU> zP6>fohMtk${&nA?m`Za{lO)|d_UNXz#&}!Yod*~Fuh?D$JzMYC);Q zH6K9(LtV4vET7N6=ALmr=ioj-uz}}$6C99sMa}9~VxEx<;lGZm7*=J)po-cC%jG#6 zGN2h#h+a|z=^vs9?jHg2#l|b^oDmxraWtbJ2@(5RCHmv3yS_5^OZKfyTYEs;=mA;x zm%QZ#?WIACGh=4srSPMdKD3K=jh<-cb8Jz+#Hv@u&l3%@F$Ye6rPdl47}proX+M;Ed>ZuL^&wmuggu88Ah(&f;mG5i;#t*c=APLl zKdrEtB^*VNY?S=pr&$-ZvrDOp345DW^70z~p5I&wBmskk<#XJ0H_c3j%SYB- zq%Zi$Xht6)tWwkTXuA#UPP>78Z}NoET!YVmo30fnHcj*9uLgr4-^CiBtx0yigvdHh z98G&$BRt+AwexZC%!Sf>Ts?`WW!a#(N|+m4<~GCIS^70?O^NAryg-LgfKs@PZ2pTX ze?6+;fN<}zfkw3BiOk?h*G&jo5$$pwE0gx zSEpDlZItK3h82B_&xNelzr?Y*)Xs;3i<+8xfbf8CZ!HAN6~zP>O4rM}bEMmX2MXUi zsiF#%t=G9-NKN+mIps-Ddys@|oQwz2!gcn@DKC4AGlv2#J=cveZM5FfDD9SitouG% zY@+EB%=w!h1yWm>a;X$9HQKiPJlbQvb8e60UzHs}q|QXza(^QzDy_<9I9!LyZRMU& z!`XQ_YL#!b+8ghVuiVV`JT}+CwQ}L&J%QDW;~N?-e@G6B;)IU)5H7 z*!2^bCpNb5HhQ#}`5XhmAjI6x(R}0rQbs!DsOaif;QDPTI%UC)@NMbB(q#VPb1SsA z*S^cX*@{l4MvR-}9y_PqC*Ii=NSX?K)WSV~Y|9pEnf`peUcd%5i5SGFzfzhY{Wk2% zeMXAs3KM5}JxQ6&b#1`k<(f(51_!qE@b*)PgX!V-(c@_;l`|{Ow4bG;wyE?SP#-n1 z-?d#yH!_tQghcim^tGS+%gS;|ALzCQ({SrU!bfel%GAmFp0;rA9M_Np7`q0+5vv%( zT`4^)tn68=HC}sTa6!j=5c;zb+_?^njsZSr<0t3Nb|QZ_RIhmWY9DP%;wB?rcWiSj zYw}jWKAy2CI8#Ei$B!eCQ{}yv@Xh$UMf$aRXgYNtU2I-JvZ|F6iXt?Mh%5 z?zuUyUWyx^mBZshXo~(2DJ$YW(gGxI0M43Frw;+5|B?Io)EU4+R{nnswfetC8Ra5= zkE&t@SW_JSC*+s^yU3`&h_`T6Y=8;?@g&ZI1K4;H~hX85N(l;PO-eIsb;+!V#t{Iwb!|8a$Zz7{#m&U?sLiJ<$e$h>vl8+JIbQiXp~Q zYm5KS=ZsRXBIYD)&I4K~{!MoYcOL@qx&B|!Y;Xjc4HYpl9z|#-A-b>=>JjG6*|{JB z3wU#LD$ntzYn7F3I7uDVcA(ad)BUR4*%Cd zhVO~WqNr~4H2l`#gPQ}nms%cwh_nkYXY$niHk^{qd{A3yAFpEEo`saMqYeL<3{`*HMvIh)Y2P&E0{0lNmk9+F?aKHY4iB01`X}bdO{}Ek{ z0=fYn{jc$@|JRJ}{}y+f?*2a(;9tsU|Dtx|@a}{WO3Z*$@Bv`{XPq2({0GPfJNd{oIx<)`gP(DIFr|a zH^j7iJ-;8!o+kUAJm{=9X9tQ9yhT^ir0A$yQV{v7X0CNC* zsD$T^I9kbaH5hMi*L6oD-2azafcFk#{iW^{bAK_W-q6W?qgT<+3Ai6C!R(g(o6=1> zR8-*shrRhSe{fAMK-*_!TP3?p!j8M|r3*y8=acUA{0^847u4(O0d?#QpMSRMc*ap%`5S$i#q#?e zRyBJ*m;iLAcyl%;HzfFxh-N_IAMXK59tn5=c=ZLf9ZXCo`;MajjeLwjp;Dnb$$MB_L%Y+qWtfl`1K-+K3zf&-P_ra&kk!tcNT z&R~07vgTq>CaoZ#Uy$kaSXUXz4#XN#2pVpm117UvbkL#G3dMPsxZ_ncyd}@{?$xt$ zgriA6v67s9i(_UJe%BcjQ?W;5XLyiH-~X^yUs41ej5fBx@J;8t#-0}-&IxG$8xK%> zSb!E>3MJmA)#5UhQ~BHp8DTbA}(F2RqUiMeT?GqUFR zySmg!&Oxivv?IaFc?6Wi<1vn%p~kE}iy`m|9K}jM*xgH_C_H!Dnmb18Qe)D*MJ(`7 z6QL-ky5IUgQOplkJw9++ie<*dAk>2A^$bLjgk4v5_D{(uD&f`_9v+=~dS<7WB@MM) z(rXT`8j-vLPBwOWa#ZU$CER=L`fDP($a%t>2g0dB#A|_8xE$#jDDRSmdv(VYHbhnx=qVpmbxU@H7ftQu5|W`Chy0)PZ=n7eT;%YJ--*$)yBS_;ywUiqXLgz9+sw@`xN@$0%JwA1KH(&EF%RksxwKYEJ zYzUoZ#OMy|&^AoB5D{HRF*^}+p<>mEE#T5^Z?{DPe~hLxAjuUj0c-JTz>2s~`^N|! z7TN=)IoY7Ny}Rl1U7OlSNPU*}hA+npd?6y&=wY_)Lbr+*PNOSKmfjn!E||P8cvLGW zAm7YO8#1P2E}bL1Xty#D!x95l=vR-sp)PnsMFq5#9A{Wsrqh|!lGS)ray?^AgMoi3 zda1kSvYy1r`^$wukgPF*7LfRe=x)>a-(R=n7q{SRW_3Ih;=Uy9S}KPbyZMn9N-%j| zh4eU1xbTD31VWlvio&y`S-{U&m)^ATWhrFZ{SjV4!3=MPxHs_Hyn~4M5;PAvxFp)M zreICW*@vR~90r}EPTJ$)!5Fz8o^zKP-(dn2IQEb4^)PSEzMAq2jM7RWB2UFukKYcy zBrYGQ_uVdI9zEgw!t6S_E#ekxN^b%=scRH!4uU%4dKGTx?qSF|e+r=GTis$R1 z!h0{Svef!Wr#v<<7KiI1qTRUDmb_aFpfEujvDF)JFaEAaHBZbVBybapJnxMq`9;3oL+Llh*!dB~PG2*Dc}21oIlAVyk)BB5+w;uP}y z2}?@^d+F=zjE(pVkw|cU$FGxGSFMH_G>=RZ=%nEf(emrVr+LyD9HKn6Rxzy1%i|A0 z<9Yc?wzjhKHxwayrXt3w>uj1aZtpd5GXbj<0j`pTda18OMHg&_7^;mTDA(?7AigKC zUw8?~7d5P&!}08&uBsP_IJ|#yuhkLKA8viTnhaRm;aFiZbA7ar%vVt%1j$p(6NzQk zDva@HK(H`z9)jwiY)1MYLeC*S)Nruc(>Mny{9-+*3!$EzR2KBk;sp_3uG>HTO_t^~Nq^0dKE-d05)jmtB0NneO{C&oyoi}S^Lg6j z#OtCDqrA16Q0H1SmLf!@Qx?a z>#3yp^YeA~Zra>CE|y|>s=tJ@>){G^>Le}`_WkvRV=*T3J<%yJ2)wP)7R|DIZ@}7f z=3Sjr`PukEi+vZFPgUw-WKZ3!QUkPe%IczEE9UnP{9wF1Z3NUlU`7{S0BD zodM0vD$w1hyc!Yyx%&(!{ZntBEFRG#soH;*F@*Wmo>`kPYa*l{-d^K$z$CIpraTwr z>hl@B#7KT9E((Kh>P)4&V#c) zUg~KxrMjsRAKZFck5Kp1WuYF?w?A3paW!CqxlIRJ1sMpWz#+ZB=7M^HJzE4>fl@DEGW1|iV(s~a&Lo5_zb9eP0hG+W2YjVA=SR@-=oTQVX zouGFhr;yKVzRz;E;S#-8!tmY~!UPqEuAj~pH%a<0F1iDLLiknRs0i+b`w5xB7hd72 zy*PaNH%-M$)HH9TYRlIy1ErOBCJur-dabT*c+3*faw1S_CfC@L4er5-ujmO90cmQd z-(L1%jwNG^v)Il@I;UQruegGfS|W{V_ZDa9^F*o>!wA}GE>AD8q&~M@zH&I4Fc=_d z)xMp6`(~6Mjfu?7k+8QHVYcjASrM@D^wFZnUXpo(U7ZNo!nISuT8-GC$dTMKyDqg9 zfVx+BYdlq4C(?`igC|^+wTH#b=gw0{V_66N+t+#SHX#MCkK`O8UrJC zW9F(?Om&|trZe_Vm&Zk}_I||Tg!rrF^!N2~5NKg0$ z!Kk60x&pM}#Lu>$)f!Tpm14@3oD0ppR}zGmLGjqHUOU|Fve9Q!7pl2I^Cap%4l3&I zzK$R8lNvM#VcpIb#Xt1~*DFu!<-L!^tMeYXwTZNP?|2rNdzaknKh1A)NybFIim-X} zhp5J1AzfMZ`{seVBPvCwIV~Ngu^gqhXxm_QnyvE(PNXoUA~JSb!;~2{;~P>g_!cHF z!jqUuV4TFKPQ6MPKD+THs;3$@wunPBhkbh}X1`63P(F)<%OEDe!+m; zP8Kla?~AJ`3n#D`T~yE2qTx(yzN_ZEmQSmL_B*M&FESbh>AWE2o6FzxJWx+yg8vvC zR9-IN9zaC)A9DjQZ5iC|Z!~BPdtt^p9kA#(TK$KJxayy7~C(k860JUV?hXqDU@xtIEIe!ZWq478n}RoR3e zv`F-fX~x7{J52=cd|Kq_wN)KLupW^<+Z6y6&+44N7>#(A(+t)R&sl0z?rY6`7$~B8 zu7n6pI|%6$r1WfXgrqUvE|k{u8_En~kO3 zs<;S{iEAtba1p~3DN_h71ZTi7`t($Z=_^x;lj{CzP)nY$4ju$cIA5ivQ%N9>@3Yos+V!g@5spLs{AhIDB536op*Ui_mHT-e@wABvb8&TJwlrrn5vjf>1-nfOwQu zDZo~MENg77z3P(f2@$!HnaV_DfjR8@_0pzPXJBu;Nt!A__FV0+d>4L7x+i~#{#qeD z_{$@#{FiRe&EK{v1bxH@1mj8Y^uo+UQ=O1cU)SeJH(f_09VOca1U*H4qQ5bL%EAEd zay;a$aC(Y#W19_ixSCcfxQt4kNTA{eojyp;v?e*lzC&15MQx=J$D5;K4FJ`KDNeu) z*uje34rVXD-8!K`ByrNWu+NAaRmi`bLD`>s)|2lD!P1|B)h~zg-IJqof+KjI`Xl1= zYGGHaD)TrR9g1w>_}RKe?n>9L%lIjxYyD9rf89SVlzcf}#m|L5?r{0yuInY+N#>7n zrYoQ@2sw-bE%9__*OS!q4_u-ti{h^A;iWEZK*ePH$AyA-LdNKZ+LErGwYytH5f;(X zHD+9(iE3cmdS&B5)SdK9pPDgg-5_f%u5FhVAF-=_dz72Qb!OBPe_|#QX-Ief_ms!S zHYpaAoG6=4VleR*3foD{Myy77W(}09$_+@$y?8kW(pQxq4qe0=trGXAJnr=l*KrRQ z-LcXM*x;5A8RU_7g9Z@dQ#M~+5@vl<`XWqkA zE2jmKrL=XMrth=Gd6=(-o9GLo?(=`}RJgJQKH@RX*P&K7fR%TGi}R%IoaNcx(^fbQ zrNtMFXnBb@<$-oJK)9FYRM?FzFPGn&VrW~-YnU}touc^0#0O_L%K}ZnqtdS|V!MlL zvLB5<=RiddWG8*K;Ww9lP!`<9juW&{qSFgDL5v^Ehp`K?)Va4deaclJGaTTULQ6D( zQc>kDDaUhHg@Xa@pBuD0APKVAKttN&6pvSJD5puM!0vdGh#Ak4yB!;X+k!0{Mq81)RVq;bql@U>WBYh zHNlN@8~zUx;?`m+fpWnU=qKbMxAGE&DFrPd{% zN1DzKY>Fz(bEPep15ho1FfCD^kuG?s3htpo_Gzz!r^Jm69Xdk|Zd0Uu2nK&n)oeL_ z>@8~9lg+zS!Nm12D{xGzL7ZmWweOs2dN@fg1xiDH=YF)K1R8JR84LC>i~1P-;pGZL z!yKA3N-07eyjqX>VfZ2)fIr~zqGGtg1YwW zSikb%sMOV?@nC|!see2%4Qlog@k16;k^u#y(j$>KFHC3;n`K_}@&MRx`{`{;gv-5S?P}O#d&(*NHF66L`qJr=N3!_F zV?gyOk=R?@UMm1dg3|2=nBj!J5WLMQvE!>)SnX7Zk5u4vU6}}d$*yu>j+{1D+l^N3 z6zA{#T9AnT2Uw8FVOvmYDs7gto^*~td@j^8bKTFH(NB~mtxhWHSjJg*0n~5)Rpm_* z;z<;-0I{f?hk(No=ZDw?2?#b&ON4qap?D17(OREbgQQ$)^ylga`gSyF)^Xo+DHAc* z9-&vV)7tx z1O)b^a`5fq92H+q@`nE_lp{UD4q{u;s{XbDPC(tL<4RM-GNA^Y><&3# zop88%Qq0xR&|fy27{%%bkiSM$MTE+iAW0w$e03C5lY~8mBwiP#%mPqGJyE+6h2p*- zVL{Px0@zaYD49|Diq~YW&6EFSMaK<4yBC#yKnPF$w~CdIsPzxe!2#KBsv*#AAY;x zYAVSgfxn5l_K5nqOIBb#Q^Tz9-SzP4Z;B(W5ez!-dSZwK9^Ln1^l*+~U3xq>WF*Y< z1(@7q=yHqwbhLt!uHRnZ+;gR2_qLc*>~JGbeY57*%))^Z4OdOS=%gEt9<^enH7e#5 z?} ?Q&hnpgvE-TRWMmy=oXFkhf^LoDy}G1MfLC&DG{JKp-wxpchqK>ySc=*C6H6&ZGU72p+WZVIo&l2&XeEc z;Iy}7qva;}WMoij_9XHI7atK1eT%Deigx;?yziiq4b{u$VoRvcU%f5^fv{U-XJ`HG zcjyeiss`!Ifr%{NNggNsaJFigx%0XAy;21P=8`qIs^k@d8cNaLHyk!$7aEoMyITNi z+W&fuLai^?uin8TXrnx|T~Ib7XaS|o7$5t{-e`o~Po5i`%3be>?f;vg3)}zRmy*SZ zSWRQv&o-KBE=<%>MmU3VMqj(3EEcS0s&gBBv1DtjzeOG}!lx`ZHTK*Rr&1ezACCbG6@4=ZxnyMdLsX%a za)Nk5S4v8o445p$v?cuN3h=M*oIo!0U^D}-l1`Bj3t?cktF&AITheOoJZqUm2(3nd8MZNyKhq{1wiS2DD$MeJ%xV0syZls9$R zWcL9g62yNSyMO&v=-}ZYNa86GRC(oJKDv1Zh-fDRbXs=ojNaMht9;;C!%EP4uM z;z?VsujRSVnamzRJ10FBvt>sLm+;h3%{zcxHCgV6l@zMM!P;O3O-<(FwW5|Kq*l|@!jA9BQyx9r zQezo-j=4-WMdE$oL2O6T^i#VtJNg{chlE5-vyH7|B!f%`4KNpovIrUe3|9-0B66C( zGg&soa1>BxaZ>Cjhb1||7ma3yl!ZCJ8@RCf24=NY9#+$cT3}~zfRR;IFr|BU&W;6Q z*+NE?n@VbL1J)`<3Ooe6JPu_wOk;SR2Y*~+m))g2pvZ)`=;=4$rhG^9r)%YsRt0Vu z+yZ-YXvZmqP!vmW7Ckh75r+^J<72upX+tl(J@q~b(=bsTR?yIZ9fTPKg~kN5=tv@I zwJ>BIE|}$qsVNIeTCN-lTSc!KsCJHF-r9+M+}jqyP2~eUu`@Aio*KfGw_pp-yYxax zQ;pddF;Z|5Hpt%Bw6$YvUzdXQu^YPuL#_;Cz741*GmoOWk;4uUc4P~sr4o)-?YNO} zpy!{#mHtF*_t|0oz)McFRkF42j|y-dY~otI#G^Vd8mW>zGwuE9meZ%8a5@%ugbv7y z{P=-bp7#5*8zr+SpB&ZqIxtNJ{o5EtSFkXXRx=!@8)`yNd%CmhkH7Wk#+S)!ZaKVD z$I9wIaO>iXp6f+jTN7&s<56VaTkzo4p3?a`w3E7l3K+tW^5teSaa$X*k9;J-w&^hT zsCFls%Sj{ewicwW=0Hb|{*IpR)Gf14SIqS(zbYkkiT|*PNXJ!|mLN^_7GeV1&CbD) zM+~3Nymq%Fpm+5k+QLw=g_bPklxP$H4*J0R(4+d&S$kHkbNF!*+qqJAK z;lfEjCbRQym)F>37!+&345Uzf@=AV_CIWDrnT5!^A}sG;n_Ma?=1VW{Y;~>~C7YZ* zvxCXy*$!@R92ir3Qy-U6L#WxPlY!Ev;RSUDB$~(hbadxSdK&9u3Gmt!hhP}Ti8~ecc z`wH0QM6K3j38R>^ssGWA>kgU;(+DX_Mrg*UBz#Tg0zRS0 z7aOp(QQSNSnagOH{c<{hrWKXGEx#}Sw0Hx;{Px_rK_$g2MNbFGK{Z^<&ISo85JPph zg*Oz}Hh4^_v|~=}+D$M|nlN76Tf#b=2`jTH(xQ01Q)RSFk!ESq4Y)&!;5F@_n|q4` zUZy-F>8_f_j5tEHmpe(-p4a~RiOc+I|4PLA_9wUKC&AkY~mDo zsE${sjcv&--SZ{Tm-W2u+8@eqQca`||IqxZX!gzSj-n^*nbzY7*Yj*K6P-MAdI$LRClrm(?wkw%rAEzo zHmiN5nT!GXcpiB@^*(7<;_(nnHS<0$zuWUP|FB2nmPU4gPKJGfsK{PMJZ_5s-bC=b zZTiTZiacQ&c=_xiZq3TY5MK9SGK~J5ie<@4@f=ou`L45+FDrs>T)1Z<0%nLFg|dnAL{$@;G*G!wG}9t~Mmo;%24AHbgK)rWxDJj~edgBgbX{yLAYP z_vZq+`+BMajjqg6(WjBfDU?$Wi(Q-OyNw+C7~Lj}1dVcLumX(9@g(N^j(k*%1_U_h z==3IeZ;my-Eq(UZu!an{0;^W*981<>ru;dUI!ZlsR2v_?q&oX~a7qo1!k0zgwkFWTd zE#opbIccEXm$yp8r=U6vg!)yA73%r)%LBkoiX8T|*+3V*pH(NUGW4+N0+6+sOoo@+S zljh5^jH+4dyN^c@Ob%a`34&&mdP#*ho-OSR(rCfTT-=;3On(#`B*-?~aHL&fNjWXL=NL4n`xUO6p_Honu zxPFv1)Na5XQZXe~HjU)`VTa=EN<2{W=}fnxddk`%gT(UAIPW7wKUtVP+Ae&t9i`sH zWzukI6vO#FnJL44gAc0BLxHxRj#=vM@$SRjSy$U!hx>L0Nv#XKD>oep~ zl}pj^mFgrO1R-us-exyR$<_k10}m-FDes*IRduWd(QKELN?VkQrggB!`oJ>8Cmx6M zudkao0RIq8+eJyds(2LR&dT3G=sR2Tj4n_r}gM<=7Zy|&#C4_3DhTeOT zs#Fye1;po#&wlsb=Zt5cGtT(NcYb`|*!dCG7_7`S*FEof&pGdMU1w7|3(G6We3B?l z7+oVb&snVKWnASAoo%iO*+&ok=j=)TIMcjy=J=-7MXO6Euwk*(<9x$}@V|ixk|F*y zj&#K5Y?pTvwHkI%xrzE!&4}?*mAw_C9m1u&&NKXOZ!x-}Y0=WUPI$t#Hl@<=F#xHNpv#9N94#Kn z^bq1b69X;5b2~`17@X;zEw~$&f$Dpa3iOSW5PWs7CnoH4CLN+(Ml&TN8b{A|K%HY!C7!q18-~nW}Z#f zywV(zo;-dxk?3wH}%U>Q-mipZI@r}q%18>mmUf-dqnMt9@A z;uPo+p?rK$5iA~{+s0)LfihSQH}i@;c>K8k+%fw{+#Cy;tMZ$i+{wlt6f-X4!{71u z=W;1r?>{_xUt|?ZGwK_|h)9PjV8~ygdY+ueOXpnHs=P2 zWUem=nhA8=65KOC`uWhM|MKGa6(scxQC(*2v%G9UL!e=>u+zWVYo!w>b>$- z1DDlCBS@bYhc8o;(}kb8*bk$3I({9(RDZsBuy=m8O3{4J!Vj+SNs2%sf#Z^0(pBsA zny(GNSwA*E{@(n($sx15n;gZ<@%onQ_iGbP_U%)E31>(=yUVVh$7?s9tnjW!^DM!7 zMi1DzlbVwbIuEye+&d6uZLB_!YYIV+;@Eb6pA^z{iu(u+-5;tZ1Ydd$#q2(R$eR~d zKB6x?d?n3$40Zom?ijFySlr%$|@4lhU zi$kEGVR>ZIa4ow2SITq%U-6{G#mlj#=*Q(Xd!4Z_tb?bsHF{Qg+P|e>fZ}zj{2AxT zA)-{N+Y;jF#+7l~imHpxY91Jij8=;}C0Y45-<}sHlSymM*43G>#aO*?U0P|W^kQ@p z@JUQNOG%WPBN~5zC%$%CVB0h2l$^`DT-K<61xtk8P@6YcP59oQ27J(u5&3cnxGSU+ zuBdCr$g~|xo^%r8fcIE|!t*F(Sw_vAeJCv6f!oP9-P%bx3bA0eS&rZev=%|!503>% zAiDE>?=m8{(N$Yi36#A=g>9-)YUHmxv{7_0+jm1?pVDc}O2m)`8lIVLi4c4U6Kj6l zQ)p!{;>gp7fX1qa6W4C*NSA|x7@47bkSzJSNDQ0K6K?&+Cv&CmM)a@7*LZ29`dg=; zvln3hIMi58ax?al-e$ov>C@$K`3*RNs)l#RKPG|}i%k)^Qq4Q8R`CXkNKhVmh)VMy z!3gZdz>Z}r)HCl`VGw_nyt%KYq1)N(a`Wg~R-Jl+?_#;JsI({{(?bJahfv83M{UoM zxH-pwSth^wbXsT+cgLFBf z$x0?YR(f-#`oukKoSnaV~xsU2;V(20U5jl(Q;%hbN>jkjsk@x8@3HeE+u zS;??W8sqDMGf2-t&Z{v&pfECMAnztG_Y8R!iz5Ha_zmT4N{iip9lH5F<&V3nxH>(D z8mvL56TQTtVi6n_+O)c{KlOb!-3hzTDkMb%=;_6Fi5T-`Jdf{s=zrnvu=|U@V{WfN zh4LHUN%6g3-5-x*cW;LAZAZ_%P|bTum+nib`(De9W2)-*yzy!+J7|K{G+O(`dNA`c z?CSHhI@k@TMht|>7yCtOX#Yz}iux`(WS)v0`6Zc$KPu%KghzrqCc*KRqdEEPlGodA z@I0XPyJ8wC;7lU@uK7u;GFB_;izY-c7C3$X(aZcXZUc>aH)z1rsli;DVt`BS&y7Nn zr@|5%_)*v7!mr)y6A|$VjJ{gVQ4R(3Vh#cnCl!^aI$IKvTk`J8J3fW|0crp<@S{Ur z@ zk1tL?a~k5aSR%FR?|9XYV;lV%yY6N$8uOoJc6JJS^VN@g-fi|K(rf*0tG(FzF( z2j@lyiyHSw^A277AJ4hf*c5H^`5~?&x)BXJPnCbbKo}jXen0Pd<-KupU$P%mFX1z~ zR-)MM z{Ox69`K`MRzszcwlx6A#h+dO|tuRxOEOH8y8PN_#d{H`*I9ZaKJLyFBz_Cof^f!De z;aS^@rc)g17WLPYTO;Q=ODRONU@~A~7qdI>T&eh-Y==^ntL;x_wN21@$Y9|s=LN-09O!+N$#OkOXlXKTbB!_)=dQaytgK=5pU$`=Dh`K zq>I-<&2^#3gfT}HiKnxyHt`capn6upi`g;M+D+fMTp&@ud>kOJFFo6|M&JcY@4D{c z;wHSLxA>dvg~38&8pUGR-Nhx-Mrm@T|JSeiPG-V{Sa7Oqn%0^J=xqZO^bwkNoDwux z-y@k2MYw1IuBgrE%MibVt)(*qtu`eRFjyj51R1Q*M+{CF>+giCGJi;Z^nf9)r#`Tx zubLxR0T?K&^)X}NV?Qbkc_&C)_E!UZ(0EFe+D_*BB&Xgubj-~ZnwP(z5yh^5xo#W& z2{r3~uIn=Y4*>UoqqCM4Qt^7(p1hw&t>MUduAX=%*DE6!PuAcprKEnFbQ!3={zcwD zD3Bc3d=Zx%t}i7&nL*5FNz&7{4XF;|6&`!mFWslwh38em4U#5d?3o)S^3bdWEMMqi zkeCMF7w<1mlpnv_>JnF&mdZl2ax43l34S^K6x|KWX&3HE^wwH!8 z0?J?CE45n02ypfo<9c_y=e;MM^QAJ#wjj4NG*IBYPh3y?jsU+dfj|7>R6JE0WjSJ0 zx#rJ(LgYR*rjY3-sq}hc_IyGoD{t71RvU>tYO$Y$naKiRjv%zr+;@2}H%oTe`|xvz z*AEeVyYcYNp{$;Z8jf3MB!3%b-p-&@h&4@GJJ3zn`15MaUS+dUsuxp&^e%E*mZ zeLv-dfNG5r>_@u~-vWzE%`fPvJ$9Z6UnH~9{g5>@RCk(F3+OsBln8gum~)=b=rY&u z9WS-+wKg`*oG2QW`5wXf?n|mj&u!{(ge|c<$s?moT9FW!=(|(aqt{p_U3a%-oX(Bc z{{x%j^AR^_O!I`ZCI6a$X?02{$O_3AzVNcD;#Yt5J*?3S<9YmRApXU5Fc%YU!C-YB z^0|-5AUtI1hyt@+omS}D@;~KA`>Szf-7>0>o*&ZnM#Sov^p+7Q5-oVzD<{vdwG@7qlf8Bh4$6)4O%&7xt+V7l1zJ{enkNVJf4PuLil?I{+s?!^!lU^tSiGBS93!j;`mD?Qju{Npw1 zZ^tP{KR4I=6VMhUg(4}qu%JGTPR}zNUuWkBApzP)YSP2*72Wj?b5psVPfbcppk+FE zxxP4cnr&;#;#A`=&>Sy$a(ZHPvg>O{3&W$(sTeD^{k#RVZI_G^5{#9`U57%Bb)^a1 zmxYqU`cp09FY`ZW^~_%~*{zh+gm8#HtiLZ;$tG*1U~4Pq_wfO*0|(srPTW+)L%}Ta zFOSfKiXkr%AasBXQ=szD2ZJJx-M=eGPA_esARc)faZ69_jGlm(t7Xk*y83{Lb)2<;At?hGeI+sw24YJg?}Slgq+)r*}8M-vjTGpSygWfWW(}2938r`59PXt1E zzdT63t^!QBUhaixI`SAlvonaY3G7T;ipN~eGyT|r@kMSCPk&HFGf;*QBj~7;sgUK~ zo4)IB^)`^(L(Q$N{j&SDx%+^(YmX_YHw~V7_%wv0cT=YcC!6DW9WOU+Wo;pG#KzAY z%gMJ(13L3{=L#?DTNrGr9n^aT}1s`cj@?izpHKM-jr>f<2Vj%JDytUUFW zj5u`oOu68FUTE@L*R)h6u9dPnl~+%wFC*^Oag|TVTrS@Igad7dBnFzeA&s#8*~a+B z@o}3C8(;Od5*$SMuJ8a4T|MY784?E&Z#{D|W_x$!ECZ1!H%Wi4fyk<0rb)WpDjutp zWt+K^m@o8hYIcL3?BidPQ{tv9zC(G)H*+|%3};}NtKOW*g^kz1#rvYzaZJ+7i1&~U z&H(GnGI{#ZQ!7sAjn~JVHSWEA|5;72;Z;wziNbUDL!H?dMZGHF77T4uU|1agHTN%B{7ir{d?ZJt zp!Y-$b7`IUIB4a~KxE19J?ifa17##5=lz07&s<=8RUC^Q32(MHmv-L63(HZl-|fRt z9IOQ~FeMWSrmzKJE(z}5^GI)C8@g6?Y%vu|O_)i~ve6l_Ihta5W!9-g5UK@yV36aJ z5aH$d>Po?_be6*T)c=+RfeO>$IaD`dQ6*otm2V)AW&OUGP$V~2)Kbh3T))i72oTm0 zlZk+QVnpHT(GtI?#E?DHFDV{WRfZ>@{vtbOafTj=k`wglZr{gcN>=r)iq_m~SgI!b zfC>bz)xim%KGOso>KkEvEz+{6LifmGAHincKe@0*#N8-3N|qoS0A7(Ptdf!1b$5p z#lV6!tDq1Ekwx)Uh_Gex!>Vi>*yb@!>U(cJ4$;6Zf4{ zbBZg;C#f0-;>g+7K##i@;b+_h*5=G3LTF$lR1|&-uM<1dr|ufS{vDzfi5IJbn_r=a zlP#EJ}FC%n+m(2PQn_#s1| zp2-NLd!Q>q9BMQ0TV1nZ6E%xEuMt`GGz)PR$=c59$d{bXN-~_NB3|)tKMX{D1h8M; z!L(#_C{!MY0$Rm(UJW-2R2dUOx{sbY4aI0e-RI?eTlrF*VHVwr)l9cN3)=W3keQvx znflplu816ewr@4=GWh}RbTyjJ9J1k!5x3I)Z=n(uYnRC6+!?+_yxcl(D4&F<4JLs3 zT)$e%-W!UWLrhD=NxmirrgvvX+16=)nPCr590&pZCIhxDq2XUCIS&&09CqzJa)Uic zSc^+lCdBy?JG8ASS_I@I$v+dpd82m36GSK&m>^ESufTMYYfZWb(U%#iUC9M&D27D; z8uLm{wmNB%+1y~>chN>qRZLGN%uDbhZe)k>G*Dr-g~$-iQfv_PL5{>wTOiZrOBzX( ztCt8;j=EPcXBy(9CMyyx4-iv_6OCU*NQ?TzjhzG>gGc~0qcX+0epTl+=*?V3L;r5! zmkSLldlhoY9L0Nh(bVhxNZfg8=&W6ht%d+pPqwyvp8m*i54Cee7klTi&DN)JUb&)vj~*Xk zr{cGa_80jNgQ7=vc!vtMHgYvy?z!g1YZLLiCO7MIvMilwA{|c*kr%}BA?qi1IBT;T z*Jm~p8|kt-3&Ezznk{?ZrzN7yf>V9YgK?L80=XEaH@87>5>~(Po;ica*iDKb1ZVjD zx)1eEJcFR|5JX=WZO?+x-kX4I)40U7)kgDALS-c5ANM2-;}Qm9wH|(&(VbbDyclSf zIj8Lw3#d^;ZIEd#kdK4)GdDD$B)!Z7(-HtqprIrK%bqA=j0^O?um6PdP#v-O3_Lp+ z;^nr8+BiU8MDa}sm{?^l1gC5>r0}ob_5w(+xvQDH8k_9=-hUA~C|sRCI_wncw(~Cb zifIKQT)A*uUtiu>qft2FcIx5=$F8dW6RxvrDB z(xa^x)%NhyXx9Y2Xa&=g0kRg*4VcgW)`DdbLbfQF-LFT!TgP>OgSZ@Y%g505^~vGB z>Ku`fn>PFv*U+BW>pTAw{o~U)B+?HT%Axgyf{yw~xY2G}KWld4tGij^+{yy9m{ax- zia(cR9?44q5nZlHQaxS z3Hd*i)8E82RO1-oVCz0vaZa-`k-pKzPUmIe#7)C=6#^?>1ez83JlCIO(CAs!Vc#V* zo+m#9QulCWsI{qk#AhJZpLfOHTu3IJ3uBbnS2(1ldvy*m;dP~8kbcq1)RJ7C4bpPz zH2j^z3qT)D)98~^X<5)dl~jsU)QxEGBC7kTVw1w;TskAPtP1T7;-zXTH_DU;wCIX$ z%DaDN&>2(Hs8Ufuf8T43R`I0)@PDMVvda2~SOTBFif5}@(4R1eI6F+F>UnU5@p-2? zLRNKZ1~X&@V-oBs6hQc!SNPAVWIFZg1O5*bC6 zfaD?&lJ=??#u4fLL9Gk~d!YAFSIb#rO@e70^+60xac-YFZ?fagV+b0|LCu6zh5zec zQ*KlKP|u|=AVHo=#)<%E&!7B6J@4|(TaOa;&eBL@AG8x6Q(C7S zq1?zdDILFx7BM?N%2fZ(^c3%r_L!HHq5b{xBasN808KZ4gR`RnXhmFJ!k6St@%X(w zOrFTvmel;36qKZveK-GX&*2u4t)P|hEZIB(7}RcGr->2c_~XYL z&IeaTrS7?YZ#OG9Q<-Wh^c@slhve&;Fb?uteh&tbK13&kdZ8)#sX3`A^w3Yf(%zxI zb72N;ctm@LinX@rQ|?sx>Is#)<_H@8uvsor;rl{ z5-gwd`MQE>U!Wsa=zg=*ySn=?Dea%EkL?SHOwC(JnVv*kYUPB{OUG=+@@r^?mM+VS zM$3-ynWK$FsX6(dFu%Ndx@;mmB~~^nFdqoihh@t$wU~inM$h=VSsa}rj-%bsDWcR| z{7=r^qhT7E?6cjl)MoUcDOgyds!Nhx*??=Y%xzIAi^077@085P3^Yy(NnSw#RaCvJZUq`V^v5s!br{TEe zK*nD9+b3cc!*jX`OCaPhRhc?md2kjf#o_+hWY6P)mE{asf8Pt zTTBW|6nrj`{v-xhWjt`Kycnh>btNJxgSX_tYdrRo#LFfz0~sqhH{a}!ab63tW%O&W z1ho{tT^n5fM*WW~+T{LY!7=>N+S2`>3;imw7N0*X+L|KayZohKvkO!om`1`z@pKRch7j zp1H0~BtPl#?FSUJYGeZBXH=Z%MhQwDDvFEf+cR|1zf;#ue+}o_StdDaf}O`F+B)iA(V4b1+-P4JS|IWY=few=!cy>9?`(OWoCg_Bxr^&reo1*g zyBw#_tUn8Zg-1;xA=0vW9s~7JKsv{ut<^i1J@c%p?;V-(js>_mRiimFWyccV`O&%L zF67Doe5k>zpzA#;(Jo8eSD4=ldGsT4X9s$L2J6@_CqX+n0(|o~RgYa_30G*9Dt~ke zJ&2|%1nc-Z%K3uaq;b!ta)O$pA1*Wsu|-KrySt@zsW6%2E@bGC__2QZ+}BsoPgfOkij1CpENS%vUw%kvgF+?YNuzp#;EI@tTS;siz zQvbPCu6W;>0xOoz(W-J2mLCwS905x{epLEUSeQc@#rZJwGPxf&{Us`R$J^Ix*>(%H z@C1PKnow8*i5~P8^Wdj?6UAj)^bL7xfK|uS5(T(!713vWcRQ3-CQtr6Dl+fHMhR1q zpC23hghDRRECs&wWhOqrsMvHaq24D=MawaY90_{)nCBx=JdUox}cG^Ep71}tLk`ryjK}C0$ovXW zYdvB>;C`;T7NbV)=2sJe2_6sVl$XnZ77{aHKVH<(H{mh3FysKKF=5#EX9?3 zxN94i`tH3daue4NV@n5o*2Rt8-}C#??S!Pn^fm|Hc;Q3;iphK{SsJZ=e>r6Hc5tZd zOZl$nL1B&($Jk#&uhRaYX#6k~uF6NQlu$}Z{%WEKa&!A+Eh(+gka$vc?GpRMW9EUB zAbO=k%r)wh^IZeV>Zzb8F?<9rp?GyraChsz-%K!{sN-BS-wg}u``r=l^CM@8h&x&t zP1GD{dIwrAn)}xe|MVGJp~G!yu3K;w{#R==b@D@7{bjZ&kv%u2BHpsMDtf}9n@f8A zH)h1huJa(j|8{vMs|65^oTdTYq&yxG`5N+#9r$qh#r)ST#6ijef-Viu1tP!VSwsOJ zI`16~7tL=8qia$r)sYm}J1qC_J27#U*+@PzF?{qZJZS+XA{H01%MuM(Fwl3z3oCu{ z@+*0AL+M=qp4(jXtj_E{$E^XbtbYtgC(G}Be75w z8fP#$qi3|r>JN&79{k+0T7-Tstg=`C`bvkX^9F2eoIjdGkF-jTUqqpd*Z7}-#2ySV z@c-^vADhb9T&i~{uR=sUr}|v=xOjzaTpMR^DeRO-&m<#s|C2prfEeV7-u&@|y^WT! zeq)@#843f#@@v@46yo9IBI}ch5+c^X;fQ_`nP8jC5_14ac+6xJZ29>EZ~L3y&#<>8 z997L(BXt8(UrIB(%&=892UhXmWjBX#~dM5XS<#ASM915 zOR{Wj-PjpE-0XmnlL&c9C?hiCNS-_@bxGwRpAzHKujlDpXIwnWLhWY0#hQt_6Xsuy zy%>O=$b=+rEiBQ4>@}BXQHwA?;Thus#oJw=+qTNm%ezXAw zPY;lW6YXm4-lx31l z6b*F~uul49FH9!?19v?7gF>;V!{X24*WWJfKfT_2Ptf~SRBGtgiL^I)C2#-t7VXVN zbU)cpQJq^uU#XyF|I=WNYLngM*(BQYl&PkS@>JBr=(k1G*U+%9)HpOdgj$x0f+C}h z<|g{Ek=jXB#w*y%plxUTz9qhA+c(D;vwq-N)-z^*Xt4c#_}V{N%tr&3k4RW7_*vE&CZUsoKbCuxO)P4I8ZrapQXJ3La!etcs>Lf zGoga{R<&-kB_Sab$oGO@7`C9s{{D-v$>$Jl%f*i|zeA}2C@r-&M= zYRYQp`$3d9(TjCdT-1pRBk0@Xv^MAo8tK63Sh-!6;Q2k+meRHS%D)z(X3Cu8~)GjI^FQnyEXqRmK=|$!h`Up z?~MC1-PAn@Ey&!BAYBmOyP!O&XP{yEE1a+Gm#;Yn0_TqWJ*zuJq~&(6Uw#!S3Rnd5 z2xtWLTz~Tp@54Sf$`1P32L7Dmyi*z6e|P!N)t{>NgZL$T zBb=$QlwIlMgWf0!1tM264_0Q)?TfGoFGw}BCkI?7v4(uh1=knWiiiI}v2WNmclHN` zOtg_Gm+#2mTT^Gbz`24$W1p2lTKA4w#_-Z!8Ya)vdDhuLaF|~2m$dczJmA{RDIKf| zeZ5Ss&Sn8%u(kUKl^AMWCv2dvmXlSJWboFW({Bth*&U@f)La>@{3c*H-x$9Q#b(_e*X~qziWmYin&yrcY(` zF2Zf$s@HItQ|A?X?|=5ttcai94wrm@5yQ4{72Knw!7?y2Y!nEwL38zJ0&5B zIPc;KFI{te32B^uhK&!REUnEY@U8Kew}3BS&TkD~A5eMlIW)e!Y~{m+!F_1^{1H&G z-t@0ZV5DfeBSS_cwoX@U%+>Kf(FEw1J}SwAh~0Ru@eCwIS|4bRIqn3bKxRQg11A@t zRe~&Y)@~4lCkc^pbVNoeX8-;##`nq9soo0GW%zMyz=tKoN5zntWL2}fXV)QA zxb-q?p*(l0X|r->qsL-?GTZIPAa2`KvJjrFPiTqB1}KsXsqEvYZYtyI&Ps@Tjnp3 z1C!j_5)W5ho{3?SULNxhFOR=CYM9o!!9N$0u2I)86>BPOD=Yr6U_q{K{dP@Xo(&Ar zJu97M?WLL)`tG~1+Ss|qvBD+0zF%WQ%S6ZEuT?bT&Dva-SOJZGhWyp)Kr+aD7&2Bg z7_vHV9m7W|)P5yXl3FR_Jj@yr&_9LIX}db$X3HcdfPp@^?G9yILsXLL)@Om9jGy@y z-#gxu+ehsfbYuZ>4G78c#qja-EKq)5r?h}cnyKyNxFsK(La!zQ01qX@OnTZwLV-6d z`mwvM^lP&rvk=I~Fw6AO{^$v2{!Z*O%v5;oWOrClzT7V-&BUK z<=ZEVQi*2salF)4IckN;7GG6*&lhAP{doz7l^Lx8D0y)i3ixZfJ0evblC>MmwW{5; z$^x~@da)87aF|3f&|CmtLn)aVSJG5ulKEC>3eKne&0Yjlb7awgb91Rxtu|+=S|sz)tO^2l&#`act*JJ@D1{?48;OomB zHEeUn=}Tn+zaC0$@L|(8>=-{e+30nnjud1P$E@=m=hb2bjta-zj`ZcW%rd2LV9wso zzBHX6lCvcOv0oF`#*vkpQ}RV)7gIjfO*j=jX)eofEa^iQ4mK>4GxAI#NuDD3lxD|# zd8{+3N+2xIG{1mSamL|Z4IpcSu#l_$dS7Aiwc~iRq8ygFj zF|sauYkxuFp_qBrEvG%@-INIleV^vW=PeB#hHH}?xOr1WGSCr_Im{Q1m>P4Q6|R%b z(1lHTG9o>J{OaSiT@^B?6Sl8B;^39o1*vk=veWnl3!&3*8L3f^xXf)rX!>W$=FaKv z<>tG%Zkz^=iHnx@RSwI)5cQpZwwmcPWV9$@I-c8=omkmC;wrw%E4@zt2L;sqhWVCk z-QChtx$XTH8=~!Ns$9jFHm2XRldW*MDt-g6g;SB1sWrd}JRL4@0&33>H3o`soAmS@ z`A_vvzjo#`*s(&IIajDRZkZsaM8p8XvGH!XF#jHQWA5k6!aQ)`p{Sp5pI*YRG71%| zQ{L%;cIl(Lj}*=#+$KCNS)d$n{V`z@7?SBEW9f@XVM>??UU}1b@cqctUmyxGn6E#R zj5L1VrwbKuoSF-d@u=ZoTS*JU1Bc*z20n@FAAsERAu9!Qd8CYC3(isNr`jdsTRwJZ zg|Ia3WX`&5DN&y;4DiD7K=1RRvc1^4aETlBC3}3O74VX*w9M~H@|OW&gN>^h#m6xT zl&S5}Zce!p{S&s@ZohC-Uh&mX7F}1HsM}EJn5C@g;m(ql@iVB-AjDj8 zsZ*fv{#;|)g!qhyByTM=x_Q<=AFx}63$&>N=h5O`R5q`(o2w+`9~ zAa0q_&N*Ws(ICEy8h-ZCd5>-U>z@zA`|ApDg$4rdiqdJeDcf~Q;AYtUUAB{unC0(L zT=O;K)#`b>Zk1E3>==PqG)vy^(X1;nR~|a>OL##w`05+g&)8ied@5hFy<&H6T>RWR zq>u;;mj0eAutp^JZ;6p~KA8(FvYE6is>)uwf2O7WH2bg1J#ymAdn7_$R z8zy|>gQysnbyw@t_g8Zrz>wOk=cVbPjc!$R${+eIc) zEL|dQIX>8Hw6cA24OpkQ8o8@de0+-3nyRNDh50i*uVa=6=TaS@XQ0V>ZI95RhA;Jo z#X~77){&Odpb5OLRqc3W$$*A5ba2jVmalAKZrOkDoc~rvzNCq7F4A*kk0jBcg-8(< zh-`}FS`nwgnZ3eRPH8r!rChNaK1x(Kl8K*x*HMxc=iftL1xbJ#^B-GY%p8-;r}~^> zvkKpZx<^GAjFdl*UNrm2_HEf^>Vt?TsPx1^W#_UOx(_JvU|{5f(A=1%H2cNS8=Vwb zF(WQ2Zkg^vFwD1iX?96C9aw%*$p?33h;Qu#dI(+uJ{Lqyr4kPiGnv$^2$H9 zLXvmr*=Mm?saCto+tvb3f@9XBXEy?2-q@%K&0^#U4jodEhGGbIOdFjRH6 zLHy3-9Br|mw$F(Nna;Cu{`p*bGhmu|MKCrnmeyb)7l-aZ!A-cH9n7Lq z)iTBg>7_9wK2V<_fLH0!Rg8hY-DZ7SJ)hJ;fa3bc2cl;9g3@ok2%%=E$ag$fP+j+5 zIs(W-{)li|3iNOdVQ{u-kDUSby$#1g$a(LCtf{m{;uUgT)7A&Rm9a1&eB);vw?}`S z=vIlZ&1;uj@M<#2LGfql{{FtIqTJOfoxYLx(^4WPkvm4|^3bJA+hAbmEV%TjRnfBE z`mb9$e}(xbd|GchvOJxtm?__goExY6STwPo?-%<(tmv7R!%xM4_aZztIJL4+BhLd` zqg}HjS>azUqKS*2vgH{ZEj66Ss$R5T#U*j}X)rApkc9I0SOb4unC z_ppM(-NLeu8W(40+5Vvs|J)pxfIBqsY%Nt4HM;x*mGc&b*9evS*~q#Z;rFC#pNW+X zhh7l#+7N*@Wf;`wJJ_X_yMN2V7F$MBNu9-g#ay*Tlr9Z15QHJTK*o$q&Ao4jTw6$O z+|Cka9qPi)bn?V;g@sHd*@Q3%6r;59r3|dy!_xERlG|3qKi#PzYL_DY&lC&CoKE&gkB?1Z}@u!uf zxNrh`^A>NYPVFHV0R`Ju1s2LLb56JLCX2W#AZ4WZJN5pRw#Y1_h`yNk4l!;S2OQ|# z>vOZw&nLEezSoV!oAHmQXA>q`*v_WwmPW(>s$Qd&kF23+zj6C}ja5CGac7cI&Flou z?sa7zUCSFW(1d}JBK{PBw-gZhz(0C`a6xWR;D)KJTWI2)Nm3Z7`W>(W&4lw&)@r@&TS$tT1~iAF757I zrNYxIz<)1(|gs{=L4K6G@$$XaaZu`q#%R;uUmDO&%4t!$;?$ z?4_r0N32@erVA!1X~n!^?7<0%IxB5$sgc2A08SJ2m$zE;n6_mW(YEEC2(2hp`rA2& zd#HGT_&R^TlJ1j;oY8dB?9$!#*x^yWd74=fq{m=h@mS=G-PEqc~1mwk<`5F1M2*p0>h_1Hy^j?d;LY7g{(sj#NCnx zWmQU{316m>aQ~@-?nimXJYp5Kby`Ude^9W$Qu^5uRcWegfbkoDFw=hD^`M&h31gZjL^49pD}BlCZJaxSf^ z$1E+J{ONZ3&)y-&xgpH9cr#M}X!EuG0?Q0AaSMd6k)8tuiEjw=Lr8}%0Itx0u6EC_ z3VRiY_tNqq$+xi8*-s?-Tc* z28%R-AVZ~GBdB!edYP?EIQ1fkyww+@cuLbABblF%rifl31AsdvFy`MTIJsimaao&t z*+Ev*;XNJ!3hA_c;mYHW-TD-Dt+Lm@yvmqb!CIFFt)#HZ*K;Nf=ykpsHW7iPee*3V z29TPyWZK*Kv*&B?drmil7XEI%|G8Z5Cwp2{hh%M$D+r2BQ5x7ZWROqBX~{Ds$1!pR zl~{&hcFeDRQRr3!ke7AR({$@fi&p7;E~`g&EZ^I#z!+k@GVqPMC7&qC&e`4UoMDIC zi2})PX;3u#+}~H^fBN+tf6Uq9Q-KnfY?YdAU(D--BY08ARpK>@@t{Q#(7OEX*Y{rr zkaEmhhg&9gIJ29*(Gm)S#LO3C**=783a06d!aP2 z`qRHGPtM~Zn)xu|Er_gmt@otBJFM`W_1A8;wX69Heg}2>0vZ_yI!YD5XGX~;m5Qq` z7XK zV9kXFEr)uB&FlOBMXMzT0~VSP@TDo636UrP%YqyMK`rac?3V^&HPTZ6DHv`pPdfRx zl_{Zy&um7jXcZn=8~Tw=FBxUR7)_~9&4Q$4@QBNKv6-KV_6*-#j3p8h)+2clDK<{2 zO5)v%PF?(Mj;Ul4oJ4Ae`;FDbBkaxM^c8-Ea-ne`KIoixOu|S*K1*4h&6Q0uXzlf5 zHR;RUEzkDy%b4-ZoZeetSMC0S>uC;<<}HKXnhPVi)enNRumO;u(Sv5tn(Jdo9fWtqJ z<$z^;K3^WkA9MmjN+vR*Pu;0`jn-dIv|e@NOH;`89KtKrmk!A|f}Cq;qYqrAdhUu5 z&kvtP{#*?*Ma*VhIUjb-F(*0YLpsQ8MZ~<#CMPWw?zg9Jce>}k+uV4E!b0iboC=y- zixxmhcCts)T&JSo&7h;ZJp^8W>TdEdv+Y;B z>X-~Z_jDoR60N+`^**d6|N7S~2!ZTY@~~?qzMeol{)f|E@&8W2!Z-@usuavW1|W`7 zTG@24?dva%?%8t7Z!hMr-YIVo!&Nk6-9e?GtCsH<{b3T6y9Mt$4rKW$8| zl0hH3Q1c)aikUs`mL(Z&`?o5T91La4&AuRIwXt!N3Whmp>K6D^kC+b=>qNV)pdT_1 zdt|5FH?n3yps@sx)up47REuvFvnP{e;-o~!%~27AX;#{E2VHm*2z@>scoCW_(;KTR zzS@`Cjsp4s9r&HK6x8G4ixbvU8RhL4KCpH8a9Q$MWNQha+@I6E!=M`j{xbZHrfr_B6h3a{GY{ z8F5Z*3U1(+kk9;Ki<9Td7`x`fh|-L2Ta?H4Wf1Yf5~-ucg5QuDA!GMtZ#gPiD z$c-BKysn;Kq-INVpO~;jW$&;!G7kdhlL7+{;-rjI1Qw#8lkPPdU%M7_cSiA&4>|<4 z2iJ@7Bu5?Hc;$6~IL(%d+pQt6%f*$L#1+G6g53*nS(;F1#wNrA(lu+w!>^>KSUT}ur1jI zk>9d^JiK%nH5$DX+Kc zbMqdp0Pr`sE`r&r9*y7TA?J5>N)KfkBsWMT4vs2}?;VjtZGKg;5_t?$FYc`~XoYoZ zlkz#3Oe5YNI6KHqndocU`3eomZ773mh2p$lVx`FZT}YLEkeE1T!KW&qequiEE;475 ze^4;uZ9^UCaZ~0EMGKd$wErhBx#-niUWxHW0OG%;{MlQEiUR<4))bf?UhF zl1FDWL?@X*R{x@Wf2fnQGs^PXx=w%LJ^r=m_0iN@ed~~~A!|5~osxhkayagMnVWbx zjz4>eKC|>%MBjSzSX!3Ntskq!6G%n#!m?K5DFhp*zrmygP}2mnWTKanA{-WSbx!Zg zssU|rRjdAB?I*lKsLSkBlv=V!crTTgQzpIR<&UdL!v zs>Kt+qp%YeERR|*KmP`U8t+5gfs+$aG_T zvqws%q$^loVHN}6+8%RsLP<%S8*moiKI(1hL5!5kvKp`o6^^Yc%iJvrF)e~Ouuneg zVSZ=$1RKScwG>#1%ulcLw8hCjt6;CS1{O_ZEV=Ay;b!;GUR5ZVRTAjCwrq1nFzSez zTconKofLDAelis(EMX)VYAF2n2XK|g0`6e87dTN)m+!`T=x2x>utBQ|!>s$^`rZET zVDgBsDVTzo;lRuReHr+V|3le(1~u8f?Y=02qM}kn0RibG5L7~MJ|ZolhZ>rNPy&SB z5u+68AW0w)IwXV^kP=AY)CY&iG9yeQL2TEXmmIKfw~m$Jta}$ixyXI` z$Th;FVXF-40xPc())x~rA5aozv${&G)r;B`mZk;;bb!7V2u|3pelLa}Y@u@qn^u+@ z?bGmqoCi+fA*r1r@KT0*qOSk@;zrOQ&))bSbn~a`FmY(Rz|R4BFTN}lskNcLUrFPw z`S~IqcG<*E1HkAFV1V1xMTry!69elTcHJR_%)NrKhVEYVL=F3cn3#%!Z!2W^$8`MkDqhiGV7SdNhe$+!q}irTy0Lk;a2g8%oNqD z#7wNV82p}tBj6hS9n-+uPfZ_9*1-QV?YlD|sp*Lqmp(4Jv_8_W2;FGu`h%FfFI+w&3DR4!=c{Y8Y;2q)B@OGko8{IsU7Ry%K+`Cj?~OM7p$u&S8MoHwW(Vb_jtHRVGguZV|7?Xo*?5nYpwuVB?&*T1kl;eaxR_ z2e?t{)Atc)bIa9KY$PAh*l#xq5j!*D+@EA&1iYXZhVaYTDM0@bp1;;j<9x@4eX$eE z!;O^IAQ}mb)}p#`&nhUYJjo3BN$GjQh;4Ol=)*iJjnzM6xb4*&u7bMZ^SubJyA`W@1@1NNt$-TaqVK4!k;=bb(vQ>BbX6K*@&QSmw5G@c`uHkL7_7 zEmo`y_y^%+_JTPIVUyuPnDg~KJv&`8cVJ^TAI2xQfSgCx11Y(e}0@Ypm@uCLYI`%#9#z*6J0@;Qi1XAI%tw>&4w z)|9%^Al(eDVtbw#l>tjZdpHJTs0UX!5LX!3_Xg>R?=-Dg)Ge)4v2~@y*X_QZYw5S2 zWkn~u9cldW?F0rISI_L@2Ybb8F?zSj>^PsKQSO4&cb``(KfYk9_B3LmX@K9#E*RB< z?s$0Ft^e(-9@+VIC&(##%P~~R-W>JQz-+mm*!1&_w1Cr*hN8u^bobof1A2FzLq@Ir zrbY8NEkMbmoPB;XVFUF&IETznXT3DuP?)_eGm#Y0b@ zwQt0)HQ~88`Af1c!5rY;Z3bo>X&h<2N)XF>m>HhCjz<^vs z`yzU)Ci(X%cB6L$#FsKQt7KIQEuUz3O47$GtoOa%+IC;`?Jk`l39YFN4Go#_)T8_f zpRee3PY?bMed0bz{L*uS1c$jp{ZaR4=_q&=E~vqcGCO^RR#pz5%Cf`t0`8DnLZKGP z-CRg<=?HDTuKf^!;oQTHf0-sw0lAQ7w1=!g(d5dINC^nAqZ}3U#2~-X?Zrk!e2}%oX_~{ z^7ANl&QS+HyW`lu1|E>)U7EL68WSOTpzi(Bliyyqwb`O|YN$L}Hpb)>s+9G@(SarF zpvJKBSk$P6kI#TkgT|J5lHF+hEL+}LP}BP+|S zD$B&FfP~<<_b!X78INdXLVs>K1NUb0zml)BQo?8N7zb0U6;NdvyIq_sx>K>d805P6 zxHDMjK2f~BuJ=#0j9Flxf~vE7p%F#ctyCMg%}IM3U?T2fAo}Y#u6NrLJs+5;_4e%N z0z(Vi7=?qrCtU|pEtgwh47-3Pt1jZ%WyeEMQGoj%Cb8I5o>k^FmslL}lRhyKYAjaM zw&L5>^f(@|oW{Xpa`xh|P2VcOXN=-$I&(mwG^moaG6elRO*+QIjOoK zOR@u{=a=69go=qvv@Z=Cb82gAY4~oDY|AR-0^MV8Zi1&xjnF!;LcMGVFyBvvH@OFS zcby~qd0%Hk#1T)nHjbFf=kngP~ER*=^EXtw5k#>tXrw*idd)`9JMomCp$@Zo6 z_*N6Mf(I{6cU)0Xafn$_Qh50jowrc|gW>~DYQ32EPej<=I!kq@{+LyK>7wHdu^Y_k#$4eAsm7fD?bg(G9Bw`k5F{;uf14O8|O_D z!-V_nYZtxe4Piwf9C-{vbSptbF!$$W<_=x%}}DbLsK-Q)8X&AVWaYpec@#LJmUTq}S#pS>!mw3?QxYyMrBP9^wm6 z$Pe+9@`=e#QV+=ouK47_ z7=0n)`jQ^>2jH9UEBd9&XnRa`Lb{(KCv9z`4+H>OCc5ko6vTO<(x`feb+MT;cU+5S zGHJ!0t5E6QcCay*d30xLrxIO!f-|ng2c2Hp;RG|ui8F6i+}0#5*$yo<23xO918#_d zIZR`u;`_c5Gj03EW`2Um0I}UP4u;UL@8^YfYxjiJ6RPf{s(Af8bIJHz(Ez#i?xJ?z z+R9XAD6@$XT%+l@%DYM}qH% zmmIefUp}VUmC&r)2i;Z3>34r=mR#kEoSLZ6;$8*;QDzwb_PO*GEQa7iYqeTTQ)-a@ zaggZRZjN;tkorce&a(Kz$u72Bzj)x%m>902G4yFv-7raSI?$dkv8H|0vc24EO47_r zkuMf##j}kBr~aDX2#KKeuYE^X`g2^&%dHsc94G8z-fCOmr30fU@fP+U7PS)728`++ zeB;elSzNjkIQ$`1>%-K?pyz}^x_b^(jcJ(t}<)o1Ax~)xO%=8u(N7?^6 z_1;AKhb65Jt2+y*!wSd_Pd6uEZSo{zAX~e{ktCAwx1{dO8d3ej`q8mO+q;XWUgI;V z*5!?Dx(?ZZ6)@!Y7foxSohGgBS;du^k?gWmor%`7%N0`zw*qAi0lIU#Gikd#DhOYZ zU$2}x0)eO>W&4M~fOEBLS(W{U+O2Iy4dddV#7wCF^Lm=0Z}{AYsvm;)(ra$=KDA#iHm;}u`j3Up)3IC}`k+_T6Mt z9~F2Y{d9KrVs^>Itbhh4UQc;$z$b3PzT%a;teIqMl2lS{I+}NFXvo>t@lDNDZ6HBG z^`k{mw&_u)1_ay;PAO6x!uI>QcEC~a?}?Eo_tKO!3J+F}$TXsd6(+aeXKYmhd|m3t zRIf8h5+nIqSNoZtYwp21R1ON!)l$`ex8koS{Uk@UA{T10HSKY%ASpFr>XU70my~-@ zH2+&ZZ#d=+}Az*nA0I&EBWR-CZQO9bn(b$n-YgnhnI-u0Br z&~CIViY*1yof6A0Uy%B0Z(Y|VL?-LIh=_<(k_HKVus;_>3bC0538scmW(QswwR~Wr z`@~~~%#YHE?BA0o0rq&aT<_85cb|UQIFjJ(MwC6KPBu}cbd4<*OJIQk-jc<&YKon* zvuvQvTtbWlJF|;}eWCfKYn#)jHKG}N73_)!7UcLJ-$hnrdAZ$D58e3!pFWc}2#X=3 zMqi=F_pI;C7~jTWHo#&O=ibZ-z0sGi%T79f8{KM>FEY9g%d4JbfK*G%GGW}h{RE@- z4~t;Mh^b%URbIE8si<=4)UDwg7GHu^0}joR?BfXfe5SXE)47$YA_dGfBtxHOfAyl4 zNvG`Di~57>w?+mtV;$vJ8%#KmNO2O`I2!E~(P-<%CI!) z(oSo^#y)VlYexRH--Oa?ni4JFuMHb1!EP8>0`u5ax*_Jc1r}Ej_rhXo^i;RUeFqP} z$JX^Cp9>tWj%0z(3s^SHD36Syo%J{*sFJy2wZ0z~G`&oJKYd_aZB@?iJyGgkj(J3; zArp3P4*Co?%~whd`1`8&FPd@WsSKI6WYs&|H~ZQer1FuY^l;9~TN;{v1%k8Li=!cE zHt%nMJ@L2jYx{P@V*Op{Y~GAnIxKBjepvCbkJf&-hVE|Ld#6sk5Qh65U&WC|oHsk*z?iVQ z-#=3;Dq?)D1-j&$pW*F9N(orJag@r%H~;i|Z3!VvsUr`QFp2)+_GJ#WGvkGFs+>)9 zS_oRGabDH)(o37X~=4PkC;T zo~5a0Stpi4b5+YLY?a#T?E?tG853VUNLw;SSsj~f<{&NWy=sQ(X%7&^n7_@55LC-0*H_tNcU;<6Fl&rxZYw?fG z3?|g>t~C0$moM%#@45PYwPH&(1g+J?@K4ds^T`FwH_!Lw-#od!Ge(mzg;G7qT@y7%nu)^XBX@zDsuS_tJ1JOK~aNInHqpSH%r16Vq+a z0=!`nv#4+iJOwmo{JN3b-Ry%~Em}@L*3D-pHB3IWTLi4X=OeXgRGk(UC@wtR+D1M$1G{T-+~bnYv%<00~|A$i6RDPMDp?*{7L~xpz0) zCeAS@fV?bGl+p98hHw?+{Dcn(*JwzS zv+Azr+q6R&)_S=-cYH$sIdQaT!Juu{CPvi!s&TrJM!l5M_>OXtxaOgB-v*{=AhcC#!fiZP%`mhYT@9&;c2Ko}580vN4} z(-0=Ud&0Mi1O1)_`P1Gx&ns$|U0u?ls_hs#j=Rqv1qCnF_E?;04njYy`BIMp_>ti; z9J9F}{Yq+M38L^1sjm_vTi84l5G?}wgC`z^)5eTctsC<0B%W9;Qd0(TQd(|A-Opza z#~Cl#6%QHlapdq_xxy|6f9$_{CZXG@bm~v8x_mLa;*nARfFg9BmY6`@wf;vY0y>sB zX!sPiMFrF+P$b@bMvHTjpg8Yd*sp$xVyIzKU((>qv`^n%qqtlZ?@oM7zv{6yt&nSM zK0=CvVqw1GDjy(%+kmx39Ktn9R75@PQ&Ll$Tb;PITCD)z-7XWZ>%5z8t>4Cx(7}k6 z#mRQ=Yivut4*zJFSfxp1%vPOiZ9+;Kq^%t*{X+fAG`<@GNB4x?YyT9VY==1A5|F5X z6*&aO^!#DJWU0j>K_nAvk^N;7Q4d12wOw0Wpz4p~hUD#lE8u+5WoKiBntQfgB!Sjj z&b4wKYuWP)SioH#&CD+fU&{lUr&%O)B+_-srE_53?nzj?^Xt-vw@YZo8Ar5=!W@Sk6*t`gMaIwjp9R zBn>h8VMDY>PxjZ$t3Ndq03Ms3?N`T|xd}Ek0=nn<0h8^>9Z)TUmjwO3q@lXuNHE73 z8vg*A5{kH`V>3=ljKUETtmlF~0vFucX&cR^O!Ye&x!xWdAB{HFt(F?yQq}spv+Q$x z(*U9J%WdND?-=J^UiDSwd!H+JPD=yWwTYWl%Q!=$s!FU$_8`EN$fH~)%V}gIi&W|*K+!!y3k?nZ##qd(f^n|tSnkk2Q6sH4F8obWuKr%*M z6pf&X$UQ`w;bVK}rDLz$Gev#uUa8v_9_zH|rd5?oDVG#ef5OI%0w||e!>Ot{!|yaE z2W-Ctmd9}Pr#V;U13s3gag3P78c_*Gs0x<0s2=_Aa8co9&g=k5zBKcc&r~L+vyT}w zbG%F=i@2C0VP%1=6WGm{(O^pAJ;)-~YKZnj{wBm62K~##cGadu-ReBPn&sS0<}khg zp`f_Q{Nj8D{#`foE#@m@vH1JnnQyUiF~06Uk8}8&mgfYO&ei|lR1}P)*Z+L}e1G=f zQiT6k!2My<)f9a8CW|oIGa^H(AvuMp{XuzQ_6e-e<8nJ7fheN24Wd6$5w?L8t`mzXF4`fY50gv3>m}u%0ViWzin0E0 z*>Qe(cO>1K$}esi?GO^jOQ(hjoZlU%>2IQ##<_=6suMI^n5WS(@7*58e$6(_mUEZA zy`cydwfej^7?}tVQRmHnz>$_y+e)h2(I`X4mfWkPrkgdNHz^)?z1(=2@FK;!)-;-s ztw5NncM1>Be~}XP8RY)`W!gH-!YA-L1Q-aE$SwXPHg8|KGmBar7Rx=_k+WgXs{5J^ zYml*9G7~ngnQcb9gV{5yCd7oC%$3AISA!Ehz^K=oDmvCk0^`9Wo(B+q$bmgUwso; z>G6{^^7Jj;dT2_tpNe+>v3Ck+;irOk=nr@d8zHP%IZa+sc}A5bTr`yKx!u-%Dp{{d9~p3 zU^h#iiuO|0wo(5^&Q_Ps2D?f(?9>-K9$w1$*Z|;~H#c>tnz(XrfDuFK#Ub{SFRrf^ z;hL1j`*=k4GPN)-OR%DvY^eh)t6*%-3C5}hy3_4PloSY!E?DRGn@&!|BA?-I?3I>`D+NCQSgwo6k z0Sn^dT|bo{2;LiUUA@*Y2L{7-rhYy5ULhr7%4?K{S@ zsvwfPhQznx>so#Y`V>*|v$#Nr8)n52i%)+ytQRS(sru?l%vQ=6+ivsaukO$6BJI#^ zwVc~&v+?~LBm;S`3O2C!E$-rhT7(W3kfV}Txz=_#z06}aP+$D2V7{1FHNhI^EY&op z6DsH7HRm;J`U#Zn>T=q#Y7|zm-i?AuWgJ3g?G6b!KU_pd8rr-0p$lMG2${7ABDlxn zkCD|ab&FUhf#?!ejwKdllaZR8=u#Mwe@C_G{q7nKJ90Ux>G97`qvzuT9I8CI{jp*z z_Y7E!+Z?Q$ALTKq^DTC^ix~&j1$W$^_qHTUs7tt8i<9*$C*VpeH%4gdpllw7&IKYPPSOShiJRE0~^mucdc-G$rJlhbhWD)zSXo(=)QfpD96~d z4aKU^wPvZx>-v+}GGlhhW(xwD*nonxZ#z+|k`B&}fw&N>jD{dkBxCj`>t=sMF$OI*?dKlP{*|Lk|j=c@d%e)M{RUBwc$TpAnY2yTafL`w!Rr#`cJub?~ zTMm97@I;O(crUV)e2E&cTh&;=#PsMt8}5GwJfHt4Sf5m&lOk@EG&Lf}9ej036dXV| zLINq1n#&JiL>m}c{w-fx71BZ8A2;o&T3QzNH|SOm?mVV3&QexFg@SLGu3vG{lQ9s< ze>}caZ0_9mn*QTajt4q8Bx9>FDe;_6q~;UR>v*A{bHdDwHSrWE^X)Ue_~5JOgwDLc ztJ$A3`#&qRMDgz}&#IjZQTa88EnKse4|T6gz#C+lo4YJ)r>t&7s(X78CzB5RubTdL z>3aH1>C*tDEDHp{Ur6PJ#`O(q+`7zI>A`QtF+-&R-_d@K;h7m!dzN;T?;&`V1r}2G zFVp-hNg>#ws{!bbW4!}adfOMX&LtvK5|`XJN=%AJ=)D_cML5%RpY=w`C|S%9S`Evf zWZAVi_oV$Zf55@{yu^E#u*Fzi*%MabZ8-dUDS{YjSrtLHhj*mR2yowA`A?* zCkFEwZvr5q-!pVW<(Bgc^#602|NSai*d)VwGXGu!s{x+4f7)KK65_l`+2F511#j2(E zQooFX-xq1KWg&T~2j=9Y1cMX&QWi5Wx?;c!d4dQB|Aa<+XkhvV*ssQFK*p{0W=4qK zw@LFgn5k!AR19Cu4@BRqSL1_z;;fBYh2tNsoj$5zOINA0Q`QSX454nr zS9yl1Y@eBnReVa1__4Whd5@WR#yPesy)h+!Aa5d!QfwE++wE1QWidCO&v0If%I&R6 zu83~1Bf=n(?@IzZWldN>xmWJ&!&*<1m6`jY<>nQ*kJJS1p7l<`fKbY*E3Roa5nv`X zqO0-LUq46O*qF=8BaBfL7GSGBYMu3+!|TJD1@R`iXT-Fw{c)_Gq(sO!d0pi0=*eg0 zZ57xxlAgCRNZE>`J^Lf}^s&m}$sdX7VuL2>+Ec!BJip*q{oEZ_o`si*n*BK7c)^4J zH<^Y1?A0Fph#?E#9j42cjnf^i<~yJNWy*Z$p7h_|Y2`lR-pt{MkSRQLJ>gA%!DJ`c zm1cGWt++d4)lCib{FrT3BPb`>>}-Lo)GUFKusw5$(72&ynh_!KPbY7$mi&stG-V=O zw}u6xBj>kf$?JQrNK2Hr3{}|%wS_P?lxK#^ty4%F^`p)eah#Ja-z}qoQ*Y9B(lRJ9 zJke^dVJ;~B1v|B}G>A-TF@n)s99XppI5ZpViA1u4C5=*K6c*HE9MYai@xm!SA_vU70 z)5G;Giby1j>AL8;ez@76buA^4I`jHdmp#mQs zHMrzL=c+6Ka>K0SeNp0`Dor5+!LJb^>Nl=t`gf1|?vP6ZfFR>KV~U-UI>)$Q;LQN{ z$b9S#UIlMqeG^U*u-wQo^Ls&-ySM3fLwN=RS!&W{)BT5{YH$eU5AgSC?&L`j6Vo+0 z1`UQwgGcve_^I`T!*BiTO||lumS#JOzODR25R_B6_2&efa;g%ZT5P4)&ZjrY|ChN* zIq1J$%8p*i!C!sN%-eWPpZWY5JXZoU7|m>j0y3(pM&gLoa4lJcMa7(b-ph|g^26fj zWv|aFyoUPU9JJd4IkkDMlbz1IV^#Tv|m$z1g7iS#;r={ue#IMgX6(l*)czPt1 zvy-!SK@KkU26GJ847{rIAz(|IUw5z>L{Ul=K`9PPRB*d)dGdejR>E!UbI+7E8}4gE zI+Ob1YGTB?*}YJ3N>KNi*0AdXL$90~`+C9o#^59)&Ag{@Ed{W!nNH z3jnFEjONV0OtJ4RiY9_eHx;Cncc8~#`O=8C`G`m0gMi&RR+2GMMYilhiS+DdshDyV zWQkqdO-`K5>h@l02!qho0#)eSHaOChU{UhoGyMu2KB5X0Ecrsrz(21C?l=j{#+2AcjKIxr8Guv=DwobFdxoW!T#CB zJwkLj6!p*&==Z0hgd`d2cn?zxoz?$)aA!HA|1?PG2TcAel#l92p@i`d)LU{}@Ku|I zK3p*-fy6sSHQ9_|v;O}wRW+WHm;=w8VWa|(nK#djryMs}2^Iwc28wFB zwd#-%#}z+`x6d~;+LYQWWF4N=bscQwklkG%KCdC-Ei1zwR+rAf&9XivGIxoo%G}B^ zC)9mzSm>COKD_goiAjP{_+1Kjy}w!hcGB+m(?Xu35j5QrUEcV!>)!+^J~%sUOxozVhZjE#r4+O~XaU)9rjDy!CocKf-QQH{w+U0vqgO%ME(d;i zGSZ8S4Yg=WFJrB`1ZFgdv7(H7_jUPiZ5z0z`(20=8~04$uH|DAAHgpp%2MQz02~>f za7TK!H`Mk|LujP0?kmpyzFj(%uq;GIu^g(_;Cu__iwtA!O|yImI#D5GUa$7-NyaS3 z_%8H8)?=LGgjlU3!^8BPasK?=>cu~>k{)Cq$dzuDS+9J6L9z4Ua2OaOqaPr{X+ESg zBr$13R7j|?FPyLQA_~A3w4?vQWODS44;H-MBm^s2xI^`u@Rxnw?_GmE%6i00;MEEN z-n6}P!v>m(bf`z;NH=ZG>K&$VGT(}5!Lvs^3PPWl?f+$}2jnYtD+4FU=?l7?p5waj6w!s9(mCwiG@KE>+DATLmM7Wugbl z?|u9#giuY#^xMs~_I-~_Ws?&I)&^lFhwnEe&>LlWuqJ4a@EWX$s5em7M6#dI_Y*N1 zS*w$GP7A2-1h$rzGD(Eqfwq*=a>^P743Rsfg#axmb>0iUi_0TguiHQimSres%P7x2KZTL6Px|G5d4R>h zH-bUR=wdm!HCoT65VPoe^7*0L$E@vJ)Jabjx^@6b>VD6jTl1wa{OYMSB91bHc zs2}&6Qv~uv{N*C3GVo)mNiv!bf=*;79`t?+V_PlE?BQ~hR7{hbABGZ^(ofh1l@q0 zbWki5nJKh7KW?ZfC>zCUG%uZuiu zby||AIif!$H5#UhyyjHSVZBQl99XdI!=_+BQ7#^?1=b1G0I$A6a0|$TU&&tX( ziA!Mi#Hm-x}5 zr&XZjR3LXkGFS@2_fs))AZaxAK9|r?>&e$#=ZExWOVnvtzRYLc*gko?)V#me1DwI5 zzn}i(OKSSzBdmyNRTz?WUnyPt&BIA7TDZ7h0je62wM0-p{Twt_qgk7HZ7&8fxHh(} z>UPH&l|wDEs^YdK$J8VAq2>dHD{5=1~}0Ka;FF&dWY~ z)i%Jdy|86c<#&bSJwSAnohr+c|heFf=~o3-4Q#qu5CYa6z-@?BNF8SOMT!Cm|;& zwQaUGvA7qaqNzs4t^Fqp`+n!A=|46tUlT}${tgCZboXyX+R zTXY_yI!NN+;N1WsMOl)D=BdfO+wR7yR9o|oT90=GToBB3%sk0CSYp0pvL1uVU~Ms_ zvX)U3$501-UQ{6Byj!(b>Fa3mlHn`Fp`^xZNO);qQruYEkDFA_mV-gofysoU+9J?` zYN|cCsvt8P{c0i$VCeTa5ujz@`z+%)>VwZusloGF^rPx&D2iQP!duV`R6y`D-{TE} zUeihPOMnnk|B##?DMwOEr;8OAyvBjs6{LATGr3YarD0{QBb|hxsuHuK!}}q5-0YMtZ-S&9?lk126n?it5=r!5#x9utE#i%Oz|w`w#7)dnfQKKUj+09=3(o{tk&OTqmh?QJpL=ituC zQ^PRogZTX;WBLu$lz;=Le7xq&eyE(68JdE?!f)K;8~!UvBjO=0*gEgQYLm|fWwr{A z@35-;xL=pn_=BIT4g+C#S1fd}S|J%%W0gDVg->!Kg!0Qb(HiGX7$Nf?yJu?h& zfKIwp5{#gz5*#Ky?=T?S3MGwv!Ni$ez$k8*nErPO)jMwczbd6?TQj34?$w73&A`nQ z>HiwsTp~CERy%HVWLTfP`E%j$1yiX%ILc+wB5@<*&@my4?MarmmYHTGv{^U3rILg9 z`{>;XVT6J;>TW}HTkb6$y@j48h1`u>N?5|LA~}epvlsSx;s23nH^6VCQ|(E5oSKM zFIRi)Sk6DaTMp(hHH3|4)|uhkSfXHnRZ!8V|D>TALa=Z9@y6 zJhCDxFSNpJxv}p_I=ml5h4-5xjHm8LO0S+YGJ@zo{dy{8wU}!7{Td^!@jegZ1aP1D zA2$yJ-OBU^v~YIvv^J4vrLi&;i=@Lmo*cB$AYGVb2?P`_q$2cBbx*dP{KbFnH&hQML~%T;%XKLwp5FXvGrjiMGIgg$BeI7nGJ-uAdca`VO!{V zCYHq)4@U!x^t6MX(+SBu-IE5hs}7TvC=%2!tFK5veO_Ji*;JIAafqvgi%uKz0WWKm z?0R1FJ@cdjGN4hRpimMfn_GWSJX{z^+6Li&+9{-13q7Y zON$E%9C;>c2^*vLQlzG=TrwwQfUF&xlN!^~SI|ZIhT7NP%#`_Iwh(|?u?)Yr7chuN zIeIVQTdc{peCE!bF~L*iTnZwU5MP3V%0`Mh&xCV|{V_L5I;ZVA&weUfzu1&3Yx zc=kGZ1!kcWwInL?d7+;dAr-(k9J@ZWo_9&bOUw4UJ%q#d zUSZW6(m1#T+-w0*!`jcL?DPfP{DU_Oya?eAdQ-fpp2hne+FZ-9Z+N*K^8L#+nQPv5 zC;R2BK%wW9go>seT57P0Vq6{Au&)c3;mBtZw1~zSPGSg~3+MHpxGhqw^;$Ji^Gn3lqY9k9dp0S9+2vKgRprVqrlox2hw?m|mCKm+w<0e;biKdT&(Ns~-^7~Y4QYRh5$_{B zn_6o&*dgk?4LhAETLT7$DtsKmBl_0ahJWum3-WR-vtqvVD68^hTLm=XRtc?ee9-9vn{hf2+Nh`@lN+MkG4}NeayY>Ikyoi2s z)p-2i7yV#VTOf*k6A(1#4rC|t9p|CfM4#05eipiQL*>(NEgbO?BYtJNVg^^Z!?E&s99~OMmI-|lwWzw zU4L!e{AtZEieN3T}mQCdn$n#e`PjH#QWy#ZS(Z}z&iZuF}*7A?P2I>lFNW?nqd7Io&I#RTNMvbFJNwDBNd{h5i|- z3eeZ3$4uu+HW4YhzHMgE}g5-#q`%X02#@Jnt?0TniI|7E)HHLyLBRRZ?9|;QSlOwC|f?yw>_+?14 z*}8)(Zhl#Ep0{gpY;QD}IJs!g#KKJf#4viw;t6BtE~DMF7HsX17i}j(pfEW2KF76j zK5?hf27q7ru&j3C4wQ zyUc>ZXZxLl*!(xqu5b5v{YKJsz{a1l>8uyq4PU+Nup$Opey=iClA%vrf0batBBQE4 z&e#!vudBGLOZey8>oST~{q?>fkTNYZyXM>m^4Qcy^9!a6ygrO;1qVDA+OMBs$Q;9#@-vm$$$)eEzd+PuC-cVJ&ih=P}Cd!=v9 z(s+fo74Y0BW5}JNKVWMbvTi!d4temC-i8R-Bfh%9O*Yq=5vaTSQ@ZoB zE);IU?`TFt>FCZC?aZMxc27A^k9JN_OL(&(W}b6m)8HU{-4OFS-hYT0gElGH{+l>K zqOwX5$JsyQ^dHFTN$9!`!L2U+efnG%DgrZ1N{#2e`ugD)**>LT^R{rK0?W@$Uv&xO z;##jgCDB61GaLuI#ADwTkv+C$bI452zf94xm*313zj$T(ehRm;Ry0A0D66MK0^k)9 z>-*|~agr9q;S%vq&-EVPNPJ~BGn?T52dm&BSpc8gc`g-y`1jm3w8@l+0#aZXmzI^= zY%U^lQW3TOqR=i(1N=^Jf9KJ3>LwGDRPH?E-=c8%5b{mHb_Q)!X65NNsvo&Z6O>#s z(AHN_&0~G`+YoV$BeCI!!^q{txPfn1%`d@WxxAYt%O&|yQZp2(Q0qmH2*o#MwGpb# zWwW_(Qh3Orice|1UK~wDPtZ0A7y3!B8}fjcEQTW_jmBSFdDq}h*sBAzdfQa%<@(JW zLN1n?bD!|~_AL5Na>XyniS{dF7r`Q6N&QsusG*1WN4T>$(Af~Lh7ae}hr5lvrA)X< zNmE+xUOcfcdH)~k-aD#^E$;h8!G?kd1T-KhB|w0H^bQAvKoSBZp-5GM01482Ge-fX z1tbI_Re^*Siu8_+-aAsIBfTSndfuF;ulGLhUH4h{{&VM#VJ%opGJE#S?ETx{@8_#~ zUki4sBAfJ6UA*BdhJfI=+2&@0Rk{n6(~_%vbW7gDT#siwBRAE$dis5Om1LqxOYs+7 zWIE^e*2cgLbw_!oSvbxpF?q;v-9+T#kq+F&kJ~uB^8Gm&Q2dIPIFsHyAYpD_=9~lh z8RoeXBAjlT3Jx%@EAgp1y_nECZeCelvW${%xR~y-Vu0ck5dzk!uFe5LDAas@ z%!RO-RT=5__gxoDzl(GBpz)-uRh$(Trhu z!p?a`ATKV^Q0c`+&DGwvZDW%hLysg%NnTE#uPZ=q$ZzV9`B#aH@31+=n)~L(FQt=7 z`2;02l|NCb&!ICU_p~!W;A09qLdSuU;t&}h?=&rCrciQRuRFuM5JbQ>vHUQe&s9a> zo4SE;=Ngl2v+aGaK)N$j10)^!cuef@WJl+jX$l|;Sbt#9OyM8e6OG(09aFPY`F!>M z^KYonBcC=N<6wXer?9t~-v=%xO>o6%EYTy(XQ)|M(@fq9b2t=R z6)0NcP=EY$Zy`Ma`Ai9Z{)#^rI@$5vbt(b3Oa{C>ab`mmv5V^CgqICRZ@N$$i+^zSgCR~%TP4w4f}qmARvCN<~FvaerP0u9=we*qOWtdLGZ$8xR-pa z8^&&*vcBN?rE&u%9cztsZc~vLG*#qPRt_+JxL{@u4=j}c->LJ-`O_C^p6Iz?y1`Mr z_*WFTvHAg)mB^K&l^OyB_dM#KKH1mZ!2fP2UmZ37nV-YkMHX|}NK$~8^Lgyo@LCkZ zG}HT|;ydKF$^_CYZr+G4#-ne>wt`I3^F(ldaKo^-L_^nqhRxesPR%Q=&wE^T2_>m_ zjNb5pTak4Rrr6%*Y$MFRdlqEUp#SY+!kx(Ues;ljvVmOC&QR0M(Y&1(xAOXF1Gex# znBi{q$*Gvl`Vs`pkR$J&wfEec*cW=(@dv)w->px1Rsi{(kwhXM)8l~Yu|G^xafQKF zOdK`E?x&k=^l^`sQuSIsBi!F*+Al1^0wgp*kO(L`Juxp3J8W!wt#SyNTmsHbGxDy` zWME+Dz4gz_fPp2cT<F(=f%(l?ql*k9TfO+Px8`` z=Hn%DDm`po=`Z@xD5arXzP=)bCO94F-Yhad4aL?a|GPh8JEk>GQ&B(0A4ogm<->g$ z`fwp&f%eh4%~i^UJUGqBaSlQ58)H-@D~&M@{>M%Z{bej^{DDV?UC+{3rVvF>3-Gj@ z9qta3WR7i-thzmS9~L<+pHFBdM+sSfh5pUZV>5SwJITp(*7d#H@Chb4r+nJ)4}Dh( z@eksPP{N}~{38Z^@>~()E%HM_rb!*{t?KLDDDU5(t6HpSViULGEPfManxz zy#qkBv{ zV-<5Jhug+O*E=c336yl;uJDN+U-^Q_08AEZ9E;7JJT6bIl*=v^4n|9guEnrX*?W^j z32uuA9@edk{dX25jTuDgm-+uky3PM*j7zp>Yc_Sx+~F%C`;&rjM! zXO>c*^ufWkF$Kxd`mMrxmjch?GqLsKdPdR9$)Jaw#TJ09Fe~NN@tIO1!G( z%>SO5yBUlhD~;~2M%$YS=wfHvu^D4gbfUyOg%Tf)9|@G{X9JFl(V38f@cm z(7E0k#~UtEZwIPwZmLw+uHcQb~DcRb=h!9`)|KEoe=f?LD^^XU$@lg}!%52II7awo-|2S6m3 zz~R3R`rEAHIzuaFd`5yhWd)ERWi$MKQbSWy`vvsol-DpA>{>-LjnXGvqo@Mft<`|H zDT7fykc$Tq_O#gb7)BVbn!~Md##QhEB?4rlI}5P(@oIowHqtDc8;n{P$q21`Sf~ ze)-<~ko-B*fF5&DnVr%Lp-Q($beADFMNlFL zOB0iot33j=bysjqI})1_mHdzZq-bNHvZO*BOrENMvstu9C zIX6<|i!WOJ5F%bXNnWeRKbH~gINB07%~!6{VyBe;%}_wZf^`>vgH#Es&-Vh{Bz`AK zD=LN)+*z*E3rab9ik@I}gc=N$It+zS#oKsH=cpL2=|+wrcYD)D7LF0_xLkEg59*ca zNgww^Sh@fvw^OLZ0{-pE5G6kzpuUvAWe^D$PZOW5><%jS7{r%!YcJ_usH#H|^a|4Y z+lAOt_Y<|@Afc4G9#Vjefb5(U_h^dD@?`E&0i?e0@K$9ZlmALdoC8{@`bpIfA&2-k z-pU^{QAA04g(fqsf9SM`XA+XM=4(qhS9oKyv&cjQJgTv>mij=r7pBE0lOsoGAJ4Zu z)3R+q~QeJp0Z`K4vXa&r;LVG>vyN$|ikQcU(r?^5s$1^E~GCVI78~fv+&)P$NCC+8$bH0LE z&a^L{1{-liWIgvfp%!FWaumo2OPcsE>iTJh zPqC&D#~bIjw|6{*4v2ab`s4io#CF5BlAd<>n5tpoRd~lyX~KzFXxlCM`07O}-KUDZ zt)57qu+Ue}o;n>Xwj?J&n`fq^u{gDb{3_oD@UnOSL2mm&FRb%G&NKeXH@N~K>m_Y* zT!X|u;fbMk+gN7`H&soYTPZX&-KP|+oLjO~d^5Y`xQbqV%o}<7X`x8R@o;(l3#s4j0Fo7=ttlxR3rEq7d! z#8~;`EBTK-CVJ_0RA;h|?77d>RC9fg$?F`x?CkzTpJ#_&^-Xc6wh#^7F!_{#7?J0v z_$5UtFLA{~nV)qp|Ly0C5~0L-Z&i_-eD{)0`LBo5Dp65QZ)-3cC%i``YeBm zl%8_ZGh(3$AY4=CxN7$DNp&TP{|3zar+qZC0C#F|s$n;$vXIq>8_fvpD%Rr&gjP?@ zPR)L;WMJU<@NaEI&eCR7J-=8@K3BrTM{Z-hDD;s3wCN$u@)YYa%>fD}Xk3%^Q=LvV zuPq|;jzO9d90%2Nok<>F!jb{UQV#zd)3*4>@3~ZtIM`)Vxd=ZaXdF`m%qn+iGC>P)F znmw);TJ`>B5LlHJU}+6e;Hp-r^f+-=?lbHJ`h6`BFlLn0tZ5~ZNw2HHo^6poIh77X zk>oxdN8K}nr=kCm4v}cVm*`^+ShX@=@Y*0}Ri5KHCrBTjvhSmW-6R!^UXv;_k?oY>hD90f3t})L44ROTtWO`77*MFSW94msb_Jd< z%pv7>rDu+Y_J0{2KAPCn9H4hJGS?fnwP8vXpPb*wMqhn4j7J+ThKfC5V`EEs9Tf%b zoN(8C1ZZBJA{%@=5>dKqoSzglg%BO`ni7&3>ZwOI2e&;`ro0sVcSb(Di{OykNPuEr z;jXFR^Q1TM=ukv%^C5X~lM!%UY@eK!!B|HwtvqLK)ANVI;k!)Ilqru)17`$)J;T*i z#rel~uXOSTH}_y8xO@8<_evFxV3gM-b#BBVHnwhvLI=Uu(@o6bCZw8M1{J9(PKxu~ zDe&uJp2;mUsqb}%aleaK9T=F}8f)%=FYurHlR=!BQ4DVJhNcMipRzP~Up9gq?n4lfnD3q?x`u)jZ;PT<0 zIoo?A!~c+n{tpg4sZZSAaXeA>j*?d&uid)`7O=GGW1(NE2%Dg>&*aBDjGPy>xjPP1 z_-bBNoZ~-FzVn^&3ZAdfaW)7qVE<%P;&fAKLk+Y9%B#`vHq_rcbog!mkD0~k-_0zU zweLG&D;ejys;}llXwcm(jP!(K4SKOI6=7*AYLX;Y z=1Hq3*7gWpIl`goFpsd6VL6|(d#7jXzS;9)}B)ug-(*kbJ7of_|}^gN;pyiXJV^e6i3py=z2 zlztwTmoL!$t_h9%pk>gTFcjobZ#WK}T|YLjJ`>;86y=TxKue@1XK2&*{f;cPF$OQ` zN?NgXL_?>@ z$P4%$-9b58@2Gp;pXatBC{(^n7p*a#piIF$sse=4F_4hegcJ2b^XX_r`~4vD-X}WW zAJ5w(^}De5*U;h1|2$vpjUqP}J;><2-^|fg*iB>*5}pOIvUlyrO$IJLtZ`5kv(0EP z+_oCd<GEXgt1DSx;cK-* z$=HVFt7r);>J9tyuR@>l+U{>x+T8N!4y3y&_ac~ub60g%oGg&6O}a)-;B?KQ2ufOZ8$KUU7wCw)l0k+ z(HSzM=sU8!87-pu)h5me`MyqO{S>6e)!8Y z?Tos1Ri6e2t&Fnpn&Q|7W1=eyXu6YoR(`q@HsCnp?zbB{<2I<5PwNU?r`J`V9ist1 zcw@Va&xD`?uC7#}Fhlmt*rmt=Vn;&PWWSBWSV~dSY%;U5L;_pBtFSb&tPuv;dD;76 z#1WJDG1)+iTz1NM!|ncO>Cr6u^BrOuP?>I@U9$EhZkcm{_=POVWgRGFs=Efsn_r3>Vr8ZYZv zJ7x%?1`x$MIB2TDfR6R`w=Z9k*@)+E;KAVK>AjRt*@r#gXSPGm_twy(p<7dz86#(&;y{`cOW z(iFeLhxoAqJ8a4O7bVlEku8HHn~e0iv9{DNGu3jb->@?13ZPxWpZIYe$6}j8ojk3$ z0_&mzcWQE4j~>yg8%dwPbi|1CIcS>(Z`l;FZ72d8v>S6uQnpCdj}dmVV!qPQQT8h} zC~LU?t%sc60C9?G#{acX3ScNn3D4RFdad(FzftYxS;%h?W|<^qU=9clwb=~ zq_YJ8Spdar9JiW)p+Bcm3iZhReT)FU|D)0`dNoDAnQYU?$S1J+f17&$&j^k@UwU5q zzYKX97;eWjH>Q~l7tdUEN=EQy2nW`ryC)(*!S74`8CP_NX{iYTV|R+pcDM^?10j^^ z5o&ceRJL!?89EP=Vtd;7hD~VYtsqh`%p3B@aY0F)^{bkhN+Q&oO13BX37C3F7(a$*Q;utih%#rW;oA z^sHrmZ2J7~96>ceKu1T%>3!?^DK)SW|c3o|9fz`C89 z3k%8Rh{|{Fh|F}wx#~6ipyX)f$FyYMD6>%AQgT$iWG=E$_-KP>n4bQy8l3mzEdzt) zU;i`;eY=1sPg9uG|6k@E8Zz9v>PmZ_g&vc_-U+mcg5Lnkm2q zp2LuxDZ%-b(j$F*hd$N3RptA6QWjbn0}`(tO0&%ipdBeR?hzEyDa!m;<(7choL6PXJnLE`F60?G7@a zyy??>PviszwDZh2lYnU>h4NLVgg4c)?=9Y444Qg6pFemQEw8}Xh2>1GKsuxv@$^t@ zZp%Hb;=hw0TMvl{zb-Xdp9t<)-L(i66Jp;RfWSY#IRouo8vwgz{5mpu+ON5C z7h%)}iTPwBYT9dUm)ey5W|mUaPi0W!xWu)^oW2n)5B3D0VowbQEco{P0w@P2WV|4g z+<874`Hxrhf;!!Y$fx=*Oc;ij>t{QVVR*#J4mgkPTCcb^K@7T77hCt&4F=AqeW+!` zFyu}OaC}-hKKYC$FAk7Q)c3)LuF@BneI4#JY-=v8#Fsl_OHXV9}q9 z^7a??7CIP<=pEySX`y^qL}&gu&@p$f_Gff@Y|3vOsse_3C@mBj^X-nQ-hYuzjok!zu5>sYv;;nUcn>t$U%8y|mO?{-5Qaw^6k)j=0ZgTMwkxUJ3M5)`|MSYZL)A`F$9JTMjNh`kjB~= zE@j;lkz_9f;lqElX#W6eAl?Wk9c-tTJ=Xw!V2*>SPOLI;1*^)s(6;!GE7v`A4ozx>!QZl zwDTa{X-ZNTAR!)hf3V0m7KFFWmq)1ku~Tq*z6&Gxq*KYXKN%S6|7nx!+K&vafhOoH z2ayr`u#hwjg!t-=yggG0{I5T}khA?ttTTRH^{%O=d^I*X>Z~lGN&2~s6^23zI*IZk z7ags+GDCv1Z~qAzEQPbNU;rRJT)n@!VL$F0Hg8~*u3R(u=R^K&W6>((daL@UQPJ2| zP)xUq!vbc>;Y8*)LiMY5WJmlresXJ5#hb&?hwH_^rPVb7+}D{o7SVEhsjy?D%T(Tt zHHPb8Fw90lU@Q>z`bp1Er~3ONlJQ*6$j>@2aFaLnE<6SfW`QF;qWm~2de zC!awk5is8>Hlgoj#M8?yhve@jOm_^<{cDKR9dTdV`TQBnB(HI?cu@P2$r78xLG7d& zR{QD-jGaX8(H2&{Np3z}okZbNv)Z`~POl^uXveW8so(~Y(eNxGdOy8No>yQLPIz>M z%;n6;f9Z=+pF+ov_hjW{#@nir*ps|CB}*!^#YE(QS$){knaf-1cNxTPe3VCKQH9!Ke^$x4|e2;Rbpn_K>dwStmE-|`ccRtvChKjErS83)^**BD-V2$&a zmNpx&On$Ha#pAx(6g_Yu@4-Nz2@Ve|*^H^q*!nf{6S9aTT!drUm4K}@Eo0<=@Q}k9u$aPR#$C;RCsj!o>d%SR~+_#)VndrwY_OfDsS^U^H2^`dBkN!6 zKagE)=?6%SiQHxQKTV$jI7Im5ebFgnI#cNY94 z%DJnMb*zza^xrkFkiEA)f|=T>9yv7_ zO&!A0gDo9!sGfLM_Q+002vlZ9aHVj_<#GGHfFeI0fF5UDu?2RloCYYY#U+p?W92yB zJNb;5RJu{Csy&Z#?mwhVa~;XTCF)1D>dbmnNJ}l4HAkb`8vT(RynuQS@#=*SwI2NN zk%A;Kxz&QCppe(Hf;7)>`<7_K>XHzUn1GBXxD602%UZUou*WcrnKiavPROgR3532;)l<36O+^*y)QW+vzmFjDg>D&}3Wg2+% zr_47A#^>&j(Lzr6WMrqZ%nLy=EyMJJF(M8z;+FiCd3~0B#&N5BMJXkZJHm*P5fmEb z(U!}8<@4(!>BD>aGJ-(vUe#JP^6fc8dey&by+PTwK3+k*s|1PjwEO%|YAH)`#~PCR z>a(Q`^BX^2#AnSE4_DEB56hE51e4s((3AaQ>V3J&(j(ntL!OpSCizOi^EqF1%+<#l5ctfGoMS`T>oQGKenR#4$0RpHWhQc*NaW94gMJsyKZ=Ro1 zOVh;a@LJ=Wd2_}JuWM7#cNc{Y&7ETKGQ;JmV2Bj^cY6X~AW8sR2oPs9J7BiQ{}RuXhAI;WMD(*m9B)Rn&* zOEoM8$@5(`Yw(fiEScD z$LfpVe4pmX+RSf?+PnqlR>y8v_Ig}8e(#2-0SOxl!Ol$(Z$^0Yt<9a>DtESAEvRA5 zoUKmkh7>;JIE{6)=yXq=KF+XZk6J|XKhOe!kV3s6LH8F*F+A8_%f@2M3vV7z`AWIW zjk=og<`+~CxDa3d;kxfgWlKfrB%LKn?7WE#uO^f{@s;^)n?uM+6wywu_9719(LvnO zu^6h0vPf0CF4f;LGo}uXlc(xEeeaH}9BQp!)X#umRv-60-|HX?ewJ_SQUii3VBVzN zUDE|=9@})>4Ih8Wn-W&fn}N!k^Z-ngRWoj>029O{$lAwT_oKY#G+I^DmCdCl)< z+l+HXV-Mc;7Ig*1C{zg$3t@PobqsQdnsv))r9cVD9TT;k>1EAiZF>CLfYl#kE0)Yk zax54ahmNFIrVi()qX+$6@7o7+t(cw$zO4%0F^~-2d+l4&Nlnz#C-9w5N9gzB;Nf9y z=Ji+^dS<{u0=jmJpp=`T{i+f`g(4pfVfr987Zt!_HRcsXHh~mN{OAwlW6M!n!|2HE zRJ0*%Nr_tB|05hGE7%Ub=C@JOy#6(`RcSF=z8I&LN^+tbhHoW9#d##L%TN5wS{9um zT}<+LEmvbPup1Q@(er2ay*-!cmVdh_7^OIJ05eeK0Zf)4l!pqq%Zh2}PI*VOW zKPWGX08$R|7r&jM|7LI_XI;2(huk~N_!WOWB|=?6T9A~^yC;;$NOFDMWqN>d zT40;^YXX^V`5XwZr~43FG~6$d1;1kWLLWVlbKmfYLwE|E?y6i*8p^E%=W$Fm{iyy> zx$xC3tGxNGc4%aRQ^{i9XV49H#IbUrj!x!jPkDIq-fD}*jnL2<%oi&qI&O*CHFHMY zI7iuhDWj(__BmSENJs6aSeQit47oQWcj)j^rUooqdD8~JB7F04+IRujz$?l-(V~aY zXw|uEI`0(avBzpy+nx2H>X6yB*T5?WYBw8KSH|NpkT)Tmz4Mj?fj}_CK7B>0+K4OW zT>!fz3(ZRe!yqnHV@^b#Tw=ZXZ1#8I!)S4%Eni!5lH>#el6I-WRz`7MYG1m8m}o&v z)(MbjN8o{P=MIz+jrxWri%uV}ATyy>U*;SG z*kZ=E2I@CBUZ*Ub>9rg6v0!{ zbpLOLx7^);;Idsui`CsLL-PlbqPay*<=Y;Z+wPJYnW%6P!+W0>fCZ`X;dAp<-}+#E zYqQ^BQe7BcDf?fD$5Dv;C6>O-Euzc7Qz2MNp%=RYM8{c<-Mjtg=ACPRB^{qd?d@NB zFHL9_GD%t#r1g@M2@S<6%cA-jyz0{*5ZTca8yN#klt}iu@UIu$Yr$3~-|TmeK~rTx z;Y!+=JIG0$tx#0?o+D2baMj}^FObD8pA-v7=5WYn8;rsjsmi|8HjzkQDu7ACNFwuH zL&h(v+RR~xhg6=1;$rd9;cC|g=bMN!!vgc-muoi#qc>HiOmC=oeP?$;Xonuyabjo#erh*|pLmH|}F+tq3UrAY`Wge&Aw{>SO|L zV#p_ZYy>P*TN0T}BQpzTPZvQ^ujAGVdTP565S?rpTLV%fakM3tq{ladsnf>HhPm}@#tyewC{ZalBdp$rjGgw08_ zAju)WttUDrBM)nY>M4$ycEHAs^<^I5Hqq9(rz1X(iQfwTz$VW{*D?bZ^Ebo&;$lu% z5H}r(qLq?rtUF+s7zMql^+)m(*PXcI-vwR2?d^t#dEDL>hN~gs5Hr4lI5mYbBs! z#aa05-C?JG|XD?>L;`Cd_DAHJ1z0i4rH zswCwdU2dLY6Wf^4tcEkFv7|HN;h9N<5ZAEXO_(^Injsb6>6e z&2C04Roaax$oo^}-+RwVLAGa1MrrH3`7O8JVNFvuQdC?T!}TMVXJ&w+kv-*5H988H zjgDawOd*36m_+gMdPs^zuk-JcI*$P{`Fyt0!k8-Odv_GRTB`CFk1h*C5R@n}4W&<9 zp%(>0%Pm|cnoqVvsiyU=Wzx>~hj8+8;B0d9B&vF-DwX6=Vx&s#(;c$r7H#;r`qX^! zj|kDHZJZz^Hs2!emQ7uKw52<2FFFh%R9!vUC20F3@ozi>vgT=x$Vridbg2Se*PB1} z-YW5@89%&DWWM3sw8ofbuH&cwQViW47L;{e-NpWS=Bqg|%UKvuG&g-aRP19Y{M2y6iU!V5;XH-wmVynzoxtp5xt3TS@xmW3N$w<6U z4csS?#=dY|CeLFN0hMfdylp(lt*~=4B7Wo!R z>+de}hFFL1ZeNb4CLs#JRXC0b+1Ok1V#6IzO!KZw*#9W+hAw^ zvN;WNdmku9)2VS?;azH~DDK|$vqs|iIP}$r`>tO|{OI%ykOJVGp;$KMr&6gZEiv2k z^disHc#HkF7|UJC=ERq zwAEA%p>l;dX0J-iCzZI|dQy<|4O3*u>c&AcFE(U_9fY%gUOQ2N-6WAv=4SYYVBQ2P z99Q_)NpCa-z0Q_x%3WFj9$GvrP~#B}NDvSBPWRPGtiI4UNGkjNmN*V~+bo~1 zMe>v<{fT&Rrt0q>5aTo0kQtk@?XzPanTZyh9#D8Pl#jH)nBPw8jT+m;7B0nrJddhz zK!CeKj#|P}Wao%|`pbt?8F=Xto5x#jyJg#H*G+D}@{FV1&j%2#;Sim2OQTz8%yyk% zmr-r>*~Z=DhB{D=JqD}ZYFB`$awp>0|LpG@JNGTS$$bI(uV zE%ruMm7oz`nMv-iWD~DaNv7keAiLO+r7(D(yGpZgaC;c4+$pTj=&$bQ7UDv#fjLnv z3ww3i#g>yEzWmw64x|D!vFdno2sUPgPCo7(^~WYv6-W(Cn`mhn^xbdzS$Lc-p?8f45uf<#h?BCc$B9(Z6rgxsh4FQM zQt>=0DDf&AEDVKxn%L__4_ooc4+i!ZN{aVMN_YBY0EDsXZNu|#>Yyni@yk$H7xMYC z;kb*7sRS)|Y`k7xCe2ejvT8s?8WAW(4}I-Pv%P|I{|pcO4x+2c7Mbj$a42~+T+T0} znA-*)+G`D~Bm)YE0nf=iFPtq0yOdz^Z@c#$tX9HP*oWIAD-LPKI8_7V64hYy&1m#t zmkoG)qBbScHC->}_7zwsum?~oD!2QjW5~hIv?sO=odv)CkTV(AYL>^`ca)`kpAFgu z#f>=e0z^*=K~&KO%2XZUF9wwAEBcedQgcf6Y(4*1Ccfv-Lfztu_sxaz6RSU4t`wYf z3@PcKiW5%XZ0ijMd(R$8ts6W&&&$?VqrQXsO=+LwAH3-l6;DP^U${wrrNKCf{~;R~ zyi*cN75Pzk$KvJxwl49L5q~4@wDQ-!(j0>*U&6o6FzAkrs`K0RTe=!F+f^E}4=VIV z>4r_58_~O=sWyAI%?Pm4Ne~SDS~Ftvsz{tDzh>iDDpLn6xG|OAm?CvIJyj>E9!3u# z;=g@uZw^n6eDQ!=?3Av>A}IfU2mAeGUe5MFx9=wxcdoc_pU=W{nKWX;rr6Xbp8CW_ zZ($|#!EVtcSI!nNU-3y7b)?IN`uX|31JpoUK(2N%o_#-KG)u?AP_Zn6Prq^WS4vh> z$OOk3*QUvtB}sM#jbiZ;b?wo@N~6rUnM%niLYPL9B+! z^=cdnCRoV$65IP`haAg(?@Mu)5+y_{(7LzjKFD+n`&g*hI=l5_SjJ(MXSh@Lku;Tx zXStX?>=o2IWlc1mOpe=}{nctU9H2Pb>o#5KQ%f?eHRVn9-nrWi>ZFQ&JiHS|R7?Jw z!63RPo|5s6lVl=d-I<$VQDtf}WTBX7vb1<2u~dVI-48LHe>B?u+Com0yY)daqUt!T z+!Z#Oc%eYJA^Nc%wznl=TMY{9Ngzt%m9$xu`}P2j*5Rz?;1!IIcK_6_ zb@R{9j4rM>-=5|aDq^_i%2^d3wZDbDSQS@I&@h#3tk?~L`?l<{-}hEDI4gx;Pb&A> zFw28lzK<|dF`hBPROVD2xQhNVuBaO83xj)GZPZ+w^1^6UDt(;hO~%Qcz@}lzmDJ$0 zvqHHPw%;G+f*l@kcDH>=d2G#&UtQbsyW`YBQ=@ndT0vM^L_0xHkOfo()XDdIA200a z5!3F-i>n3=u5-RdE@%CS)qb2Sa-$ElnyjzIZq$||&@piQvsir?h7ob-eqIBd_zsl0 z@2k8Cm(j5xM%Dlzp`B8JR@CQo2tX<^q%hVXX?EnXSKq@HI;ctH)gIoMX1XCadJD{A zNmxeThnXcwT`0Z!$sN*nEb3yUTe*GIC^<=0STFc<*h-nH&J=4E2Tgpgxo4FxuuHfq zcfzdZ#l~t`y!e|S`(5D5r z95FGX_rt1ik~`Qk4`e#rSSs4-tUw3;|zN=_1&))Z>`8Ib`x!rcs@nWgwf!$GF_W2RB&PM6}}y2CYyw(5M(@Tp?7--k|3c6W_x#_d5JQD zf*s$mKw2JtQoK@Evh$g*vPOZ*j2|yz+{XQtTXvN~Ii`_~+n|#Q079JR-&SOv=Q5vp z7ZLJ4F4?F!DlhKM?ZJ{&U7=C=E2>ZElQfiBVspmq*$j$D6>D?JyfnqH^|MOIgF(7T z6*dpP;xQIcO%#-;>c(F?_^fs{vR0y6?$xnoQEmu+BSmi1G}Q));Lo&>H#F3wr>8#p z(l4^zD8XHOaOGn6DPzc*&7NhgRBo0wY^{Izty~nMzQECq9~a1!C@aKnwSr`ClE^HU0=Y@2mRAU@xH?*BsJ_rmoMSVys)tSZg9o|; zPKo%Om}c%IYhEe%%WAF;H<-8w(i9tesLlv@fL?Bohxhu2-h-|vy z2>(qe)o;XYwcrMG$3|_B*U%Hfqr5fBJ`Mz{C8y^=041_fuOM{~H2_g}md)JS%axB3C*w$hHZ!_aw`fr@xQV@3UrW{_r-t7-nEv5{U zvK$jT-#iP0ItheA8$sVkLaE!Ss%Zn%;T%QYPjiOV!|awoa!)p7FQjYX?vz3Z+s4Zy z-}g)1f0WN`N#&9DN3x>2(0GYo~xK7HpSt%ccfySI ztxJ*{hySqt%Pm)FDGDi~(`=m}46&A}fG<~<>cn-CgDLBp8HougsDPFRwXxF7MpQq>(_5{WZ;h^ z=&^S{c?H@gElaa^X~&D>ZJFk77llz)Vg~h3j^sHeznU)%kcx=7Pp=#utWlK#kwzF7 z4SQV+e^Ci1z7N8)DpUjjBAsUPZVkdMO`5;MQirJiX;YxSp(y)S<#`b8j^1kfOQYiM z`*H1l@WM?e%lj_gB-lU_fT}YYiH}XpSooL#;8RU z6CExXYN+N&grT5<3y@GI3)v3?PUb;R2UWC~Fl% z2Z3MJQVBLfc8Nogu;zzk$?CX2D})UT%ow9UPFk*oJDu zmC<3s#Jyf7dL$U6qYVY*k3{e*eH5Zo(UU&x>ODJ|edR9V!((_RnV@}~^~0OZz>iI- zFszS<1_Un1%&hjU3H2-C`CTUDn(mH46*<%;U;3?4zeL;b^ch&-bVN`%0kk2T=-&(? z&DgHXsq;P7*IsVm>I_^A75r?hF5E6L-*xIS^t<1fLPv`DOtTGIw_aJ*P+-q38yu!V z3f*L0gbxJ-4cSUiFZpxjh}M)txp331si$OHt?Ef*s*J&QLz2C)^&7DqDQ@%ubAw%)MvBTd z?V&%Tgcu9ki%0FtgQWCMw;Rqg{fuhvB7BM{aJWrbarY|3`3TX9+NOc!lwIo*wd>3# zmb!1_+Z-2+>-UX?+3Z@d0!m!sU0Pcm$^bHO@kuIvcq&=Ic^jdnCE z_W{pzR#i?tf$`OAE5{ShpSiuAAZTA@-&Im(W&;AEJAy zqCGoG6;QsbfjZWgdwO+0N6uMK$;`@$qp||=gWEBvZ+R&FQO$oc1FgNJ{BVL=TsOk# zAl=x#53OG%huq3aRl0plwk>B=V*bq4H8lkC>ZHhDG{2VY>hL#%qHoet%tgoSPl`voU$?jL zT^5=bBI4N1Bg6t8bUF){qVCnFFDrG^Cu^Wrme2H2 z6r=Q9n1F?+J}PtfS|mYV20UyI@lcw~$?`8DWq?jANT(F$3H*Q)DigigSaJ)30I4qu z3QFFh2gnDgJs>yMUE8$EORx-d#^H7S9*U7t5+sO+p9M4lbis;)E&q&Tyu!-F8%VzK zy2wS7jp4Fem>kE4%kDwH-KAF5olgs3IE;udFxr;o))My|ED@i=QHP$8SMt>nEU1CZUzqqkS zRg%MTi-SYXlAgRwkFidDcnd0E!!pPxOR3A)INF=W90Uc`sbGzu*!<3K2d-5B)WjXs zUDUd1$LJJ_4$A+WC~1-}4^#;+AnkzDy&Ou=02vAUXG`5L>@7Xt)?`xXp+(Y;gWr5^ zxh$m@nqktN*KOhd5?Vx9Sa5*$Ut9gFyxKBn+Ta0q*#pn0Yxu#;>LooO_#&bS2v)(= zJALTABG0y%7E;kCZ2G^bd+(?wyLR0d6$>CLRVkuCLIu@+kct^es(b;jn_R>TAz(ge-20<80YkEHH{id8UHgJ789(u;qJGR zksVcnHPbdn(%kes!x~}v>+-qZa#yRhg#itlp%E7YzjK^8Sj&kqn-=YIH`pQ76$+Tq zd(YYWyujdqU}A3Pu3=1IeX5-u%UO!`K)8gZo2UFoH4h4TsoN5H1w9{P)giGZ5gGE?Y>kM#!L%tG?si- zZE=a|nQMVajf-3X>ds||ag!_f^Wb!B_1R{D`Dt*&8U=Z9N%Q!D+xMzLJ?6q824E^l z$Wnve5tOk#s2{a*bpu^Q-wz&FO&B44$mCNU`35bLtey2*G$w`h1$Ta>X|NplSe{S= z1u~!aIF&n}EHxbB&DUz%J94h0!G+Y}+8Emt1Y`y>fi%W6sBICHZ-PBzHlvM_SDf2P zk_Q=&4>K&8YqBp&DbgO>Zg}v7Ev^hg;@y3X18U!`h^T(f2zEFu*F%HlE)-gS5XgYGdH z5SuaHo2nzef(*zov%zqF%Kw7TBNiJ-lAEN)|D3pM$EAk65l_V;+k7>1cT)>CYQ74|sl)&?3(uD~6NY*rGJiil0B1?aI zwI|i{i+J$h#E2NGMZhVwXs?9BQIt1M5sgTcAzYczDreRekO1@_|GqCjyEtw^wqT1) zxV-^2gi{WIe{MUaPq{ureOCziJZb;k_~W8Wbizc2yrjvDaBENrsdjU1W34iDKIql8 z>|(h8Bb#VbZ=e&X8$wjeDjvlRfQ_q`l8uAto^rU@Qj!hPf^YgLmYl>Lmq1Km0+ zbgYO>l;M7tZc7)3&n2t}a!`KbzPhJ@ zAs_V<+XRO|I#r)1V&U@>C&f(yk490!nR@m;k_wa}1~qrpdPlvs04=BZ4Uzt+{)#|k zgU98V4rC?XUd*8Rd4R%*>7k+%da-Ewr@!n}#ix;Or42qdt5$8uT7boV zJ!fa!+C{O0;wft5*^oEHOG2Pz$sS<&wn9}7 zL&}M8%a<(aIMxbB=WOqAK@EVZ>*U7<;LVFM<3rlLrlgu)Zbh#+1kFS#$KNlsb{q0J zL_=D;K`ildX+eG-$LG)oKx zPcwo_Xu~od=9*m6($#zYOpVKN``h*CYjctcn@1G+1u%Ka7bvSsgg!(MkLJByw{yMT zkdf37!R6ue#o|Ek6BxyVY0ICwS}h}4r(9$fv-U_~J~?=$B`z8LI~v$p>UGasD?|p! z9{UqQwsEcZaQ0~L&!06#2)7#Dk)Q1@s+8o#eTs zzxe&m`0l47t5Nh>NvV`LgWH^-0L0q2J8B~D^wsiS$2ddCrg*JQF(N-sRX3I|f8WVm zN|fAX=)?_XvJmiK@!*DPEh=SSRS5XPpRR^fCm~v_Y7dzAa~DeFXbN(iQ`cJ<+>%?* zi^2GpR-kOxpLoIUc*I6%sE-*hVoGi|Wl3vPjf400C$y6iIJ*&GAE3rkSidC5@rVl( z&~nwr|F&3Tv7cboP8{%S52?vO`pxDaj<`<23(4lfEMcFDL|_xt0?ArjvCfAu4~Jn~ zLFE=j%BE7H+)b(u#h5T?s+;VMt4EpfEJL2&&~hXUd^A7)`Q-hy#4^>{+Wt95yyH~q z`X|NEIy7esH#Ib(W8*bo&cxB3Ou?q~G6%Tf6E3&YEJLo398do>K6KA0|A!wu4-|Uj zlso4nb7O~WX#vDoa^1zbgvk*r!{ndD4YIkfs~4xMnl3^^O^^LGr);EHqbK`E^*oQ5 z`?;I|#AH*aBtnP7eRi)KnNVBIq*9$246f3EepaF>rXpoN40E?=tc>{hq$=J`UKcj; ztObHGF1bu1Ct#bu(b%t{%Gx5+gC`8!!j_HiiT-$~O0Z^K@nMmynCgCgkBej>)e-+B zYP{_lOVVIk|oD^E}eB5}W-OMYXz+mRKl4^04 zVW$BjoLhC&U9r+1@2PakDG-1Zb44?7*G(4>SdN6-3Yy^L_UjaIWS_CnHOW{eV+lr* zZCtuL!B^DDoV42x1z}zTwMInM7{p=Gmz%=AVmh_LwsPxq3e83R6NYeI5~Gg_!(p}W zC=COEdeS(zDa7!6Zz6+$%py`MXmGiO5<0UrUXaV7+2m$y<&{R9DsQbp4|=t`?-C=# z0lG4fU##B2?b=}mF6I$v14?b_!N>($k|}xgQ5Wkpf?%ya?&8%lE^-7_yPpTS9?2%q zbNd@-?Riqr4yh!LcLIkiYLdKCq{!7N<~vKt-k1)usH%;2h|R$v>??N3WZbx7-c9b} zpO(TLT$(V?!o1vaV|jF*D}|ci{uX{eV`Q(v%}Q*pscdD)#7A&~foa1$iV6k*ooNNS zfRZ`DP^h(`FGhUMN1Lho*;dC`YAp(3JYn_>8KeKn%c&7;?Z(HR@pHL5DzR-(+O4Oz$?4RDwCf%rPjpMn^lH2gLOU+ibty?9oU^ z+M2(c(68T!UL6@)Q%OAicTSR{^(wy~=*`@t4qANlABD_!zrY2x6Beq@^AhgO)AL>I z-1gb4C3xnZhZ90==x?F;*&y_@MfN2uP!^GdK`?|1D;$KEIxPnn}izaQpFZ9bLL} z;7z*{`AzY$U+Xm)LSpy^9p(lQMmBEhF13c3_~svU4FW?7JK4{aZ+uKxPsrC;&k8o$ zG9x=9VF{&w^#4H@V1n1a=}_n#d9nU<<3mUu_ejB|>G(0!j6<-bQ)v3n%8j+(WfxWy zeFt=o|7y@UKu=yF-{DzH+R)fr@4k+kBaF<@bKY*bNM(@$Y~(pqZW`qlGB||l zyZb_BStpuf4K|@`pG=6Iq!$t}Rj&MhZUb`t-ysh)Di(Z2Z)zucSxIx^9?)=_Vbl12 zd}Hrh5L&@h!)&6k#`SrTP_#|KgG(Wb%mtqVFX3~vm+t)u=^7R>|E4bd0VF_R?=%{P z2N^$Kts$eGhHlU?3WxqP8+`thxOv^thLa?O9-D;9ZNB@C!rm`Vaw>|}nqpxAg0PUd zZEfs=fccdHAt&rIlCybuJ(+vIT+dBNQq@`Ob$5F6@q2-%7zE6pn%ffqrd3zr@&JG; zupoHsFQHNAavj+!=v6J(kE|4@f?J3o3$FR!kgVh||LIz{V6#+>31-u+O0!(kvt0CH z`E-yhd;DPU%CRj}&q6e_eQOmxyVI%Mue-&Z_XAt*s&ZUc7(bdnE!SBTjp|W=BCZIA~xaS*Hp(F=YeUqCL{!F3~-=JG35f#nXaj(=8;60*%iFRP%I z%(UT9Gg$PcNbp34EgWEP)j6^=Z=l#v??tB5?x~t&PCXW6a?TNz`!a@G6{_)oFerm- zZnYF^+=DotSk#y_mQG(CdKOx8m=MP4Cu0D;?^J9-YJ)fkfNcd=NSmW(8_J^pA~ z%7re(XypM#;$N`F?S%?dThy&fnthkI#7*^kc*Alv@@+i=h4AiVArOrX!FN5SKQi-Z z0e6CNY_7`+bY4nO%7Z26simf7v&m{CDWq416{T3J)GAs6#xFwTsO+n^V|;M9G|Xrm zg%B>jB^iHz@^LLOxHlCI?`yhiyRJ8mX6OABR~>*`#F3W^G*}w2ZAnSQxCmpQTN7|n zP`aB^Qv|JPM$Uq>>6eeUtz{nEWv!k6g#753A#|DL?=@vy@%J@U&^>LD{Jc>1eHfHj zhH3pmcm9&r-yQuyZJOT^YoBCM7pEhR1jHI=KVTz5RdVA+PSM3>f5Enf5!)}8vX7sC(L1;#D&#?IQT>Z}(;gA(; z&9}sokWp6yW{M|mC-}Hu%JuhVGlBxq?lRw~Pu|cB*Y|jfXnhz)+nMQQ1(jVf(*5%DWbRqi3@Ub1SPaYEZvy@K~9)C?n@}B zDQloj%yLzIDQ)%fUCy94Gcw5;J|4ta-#@;rhN~>0G}y?a(HA|=!=?NzLWLRVq-%Pj zfsvfr@tb2bhj%nlv|?#yUks9cdM6 ziz`*#QfXkG<8=cocf8lvE+>hK>A1?4#w;;4PeEfEMlQ)g=z;1#=q&xNo2>0mqc*Lf z4~vb9jE5@IP{F+dg7Tsag2rFavDv?(bT7_Gi8$(~N0{Py)dY)MBXW}y2=RH<&II+k zJq(Y+Kj@k$fArUqHC!(1>ab(Tyg<7Vy;Sc(l{3?gc7d_jX=aNFF10G>P*tZ_%__x01hH^$2f7Vp2~e9gl(kYtoH=~DOQWSoO)ejzLk#y45Z*t zrGh~Cw4qD9BmQW{8NK)jMr9f;_kVtD)F9MkCye>+!g^$_Ikx4m=m{2}S+%mTcy?Zl ze&E^?nAUGT;xEhL)gN!rRZxM~$o(e2a!F^Og(c);8r`UC29_iJSon6y;|xjVV&q7( z0M$Yh>SjxBpC6eB)v4SH!q2$R>OBp0T6tg$l#7s+L|T>(+(2=;y97eol2{4?x+_2q zBZM3{J&-2dYpRL<^Qb5BCa?d^H??BDaB(o8l$QL9efr&s^Mm}io(Bg!{y)6YF`bXN z_Z+i-$2T1`Jm`2DWGmhO%l)_F^_s-B)QUIGGeciSUmdQatDEJz`)2}Axs5dD3=jPy zkXhnNikI&|D(vjeNyZwJ@Mn()K{US3NlA)T)5R}7cZT_^GpraFjZGKz0ft=h}Tsfv&a%vQmm zbviCD&B@Sm^5>h+u}{xhF77HAbZHW$<@n`|3&cCrh49#51xl^ZA*r4)|GTCT9%&Qp zWuqvO%7*BKgrQhuqTWD&PQ-Unm{N>k@EqT|w@Vn?rx_;eLBb}wZX0|yXjt7+?ph_i zzvO?g6=QS(T;@I9igI)_6Z3VoN1%JzPffq_=#YS@=sogKM*8ERfx>B&anZP>UW9&k zX{uOIwiBx=zavrx-k>QvF9;t}TWt>Pjs(Bm?~&?z+p=m0&wy8G-nRzO%5QFJ&Y=%s;+# z1)~+013-Sk?U)-zDjiy-l^1g2OU$ZJC(+#$$n%g=c|ME~E zq<94g%+DJ0tygG)hz4K!qoRiBRsFZdRGJEL1%>i4C&mbjN#m(5%V@8SVKij1`X0A` zMJV8D$+_FYuAF!ue}F}&M2-bBbi&Fv!yG@c8th>`6!eXb?ymD+1bRXGM}J#?-9LwR zKhR8XE*T&T;e`rB;u13w@kW2!BK%AFVZ~AR;!C=BLH~Ry{&NOQM|Ut*JPvtO`~nNZ zGmHaaKlA3>@2*qKbqy}`0}g=4xAGlBQp=)OIpfkwLgBz+KIbMabPD@ z(9v6CJi$MWHP`x&V9uXi-ZSt!WZrCpI+@B5_<6AyqKsWE#MJB z#rJ^UC;tIIl=wE>?~^47*sq=S^f+?+|0>uGqZqiT+l9|7PfIuGVVT8x)~j7@Z1@FvgPq8W}v0Xd>z^i4L6 zi-XP~--Hf9V?pnq45!zAKR9$WS<3SkH^~F;#o5qD+mty!v=>~u_J_EouiLlJ`Ya`laj^#~8UCt$4Oa621Fz(aS)c+c4K|HI zVf@*11pr^FKV4Gx6`xhs$x-Ze6j# zC+oL?Xb#^Ji|;04s>51xnmR0=C#?8aBRuBP&3@coc$nngU-~JM)z!JbI-FTjng7yb zW6==(D(mb1TvEJK{O5}7j{M-`L`yTqh6YfM(h}uYdmlo9vxXZNyatxEM9bf4G+t7p z51_YBpSOBabqUflV)W5Ihdv8Kfl*2)k~a@gcA@E3u^7%tl^>U4VpOE^ZLFRNjmUq8 zDg#9YI+>AROkyXArSQ0a_pTfY(kdce-UO}Z)2q|+e>y%FNYPlXogF+DH2{J@!M(jv zJ=THkju*hK-qsrK%W(qejA!-LI;d*5vi&}LAby-+o? zr|`|YV;LokN4ndi!S5lSXr;uLSGbhyzmFA(Rm8yXlr+fJ497pmGB4qBq|J_xRM-l# zv5w$YF&hLM{sa}1;hCEikORbntfCi7v#)urHOTuIZT+Z9MTg8Pvh zP)hECyU!lp-L95h#12U>*Ix;^IECWHzkHJre?^i5(0pqHwu70f)H?-xtV(c8wIDMY ze`ObXZkeczn_Y1an&c{jz+gBMpf(ziK#!rlRtda!*oFdwAO9%+;bgVhCOU_3%jdSQ zG6plssL^Ut>uGZ2kBMSZzznV=DxDy4QrXE$K}JXb;^&E5rkXzgs49TRSSbK%r6MZb z0QgYtX4hIG4_}dma3OBWpg;CTo?IAemAyvNY7H#0>t4F;>}dJL`U)CJ?OWbNswbzN zkVi$>LM!vD#-O$JqyEWd#!@7}H?V<3pO~6UBwG6E{qr|zESH`XNsin462x!GF;l|9 zeg1J9(iq`N@!q zg2JViTWb(A1{?7p5q6ba7Ct$}crkc<5g5kI1SUI{@?F^uSk&du`b$6mxSvl@wo0o4 zJ(d%m!zhDsg~)yN3%0TB$j`(eHeBtirJy?X8)UU+J@cBZ`Nk5zGzUtIy1>gHdP2;H z{qaW(S9IbM#Uxk&-EjlKdCG{ghIJ55=2RR)!F|BAnthhB)aw@w=-dJPBJRYc=~ssh zdTP?qeVft&I@=n2urIN%yb*6^Rsclj-1^i82HpZXp^m1vW?Fh`zLqw|hgwrks+Q_A zUik4Zzc+xuH7%nkg*ecg%BDdYS~qUUw>snc)dBsM^_$avGBzH#2BSR~qlbgcV=oHu ziiUnt&o%`1c&=?JD)wc>R8}5F<^>;Prq~T*V^6TQTe!d1++tk~d0^8q>l&IgPo3F> zr!v;{%*F<;F{Xd+FOgD-;CJasgSLvoV|nT&wm%$A{q8HA0QMO-ZZ<`Gt(wi5jGs5r zHIc!Q()uSxlDE7`Pr9RMj2e)ZGyh%w=b&2Mo?lTae$O5ja9qkT|6JgAdtNLUHk(B? zJP@}&5Hy>1=9qy)-s#whP|>%@z+~2Dhk#LQ(*W7F!XYj-2O+_uh$Fv+(Hv+KvL)+4 zh0XU@?=p`oHX+=YbPFXa*j8k1DBP9dNvBxFw9pKQTmRs8c1dh1JeAs1_g(t^_C-73gK9PBp#6- zwqK|y;}%f(W_tJ|w*W#67F7`@aHMCvy1Mg3`-A32S~BFra1kS=@S{u^Qc_!(eQw&^ ziY*xa7aiSu+N-IKkYq*AhIq7UK8PMOLl@Wm`tPu=)}uRyx3^pNtKN@Ce%C3byZ9(I zgGXVPXeU=!yl>jAceO8wHF}@btXGaa{v>MXO<-iee`f4Y7&5+~Md6NG< zEPt2YTfVDL*~L_UUVneSYWJy5gH&O>$*kv2tB!yGC*`!GM|GJh)}%0=VDhRYwueg` zxPp%@Q8vflqQ-q&9b>ukZY{=%-d7>)WqY`Rr5JXwSq;;fBiMYSKeVq*}sw$xlN+DPVNL zL2B|G>qIz9y|I0XSAqTv&_m+=ezbrLXuT?%j^=(jFv9pbXZ)abmRg+2fqrelS7~PP zf<^e6&{NZ1)~^H^oUR{g`MAzxQSinvbn50o^$FL@kFZ{)Sv5biwLxF1<9>1sEY8Kx zgHh6^oMQvAA$`$B|A2Eg|HQ|xVvEs@wR1n!bTSRarN+67!vL0#tK*`ROA(vZ2t-1P zHTCh3qQ>egDQ#`nKs{kjbb^k*pKzPf3dg`61M@42bD8N6e|mU_5UKFSk6}A^GZsLN zLN7;{eC=!gkTeD{xisBqA&A6|fHZpTprF8l1E0Hh50PYD_%Xj5dM%Y-yFm(qnWk17 zszg^gCMJgC3)G;1XB~Q8I`>=*%(~?Z&3kd9aX_JQE@jaIP@XZ`>jmow2HGIW6QJi) z%UcSsQ*WNtaXnmtr9`jEx0`mm%fMJ4_td%yEZk#I)6@9g>@a6Omih4Am_fC5r8{HD z`A#QDzS@{Jw#^9%W^wGV>`DM(M||htuWD?N&gB&b=HwI^yLc9@&_tGYl`#}nSA#bd zxFRXs-rrBa%A)64Pq;V3`L{!GratIxY+mt#%C{n!}n%P=4>*H6t z_@x=XS7iFN+C2Vb)dNmGUarV`0k{{+F?@@mwk&?U2Ehu3n>i4~Y5xVMYLpk0;>O)h9ia_ma>56z=K@bpKSfRyCK%_ekPnO#IQjF*$j>(R$tZ zr^vRozV(i^RijgNdYJ^ZW^Sg}I7`2OHA}w(jsGjpWNQ4(qxBm362UBACzAWNfu40S zqR3hXwjg)s!o72s_xZK^ok1h-PY*b~2Y(+6vzf5b*tjfZI9}pdS_cSc`)#AVs;B05 z%n3_$858#bWeTZ1^31$&Q;pm?n|*GAwZ=h75Uv}yt@G&{A2$!~jjQjG`=vWClB1zw zXditpwlW)bQmc<{4^oIg0wr)f|Yms!F@^+W|I2nUmpWO200+4 z8f)Fv*ifXl|UTt*-69hurKnwyoN~n^?xFUlUl!S7Zbqt*dX;XT?29j4J+_mM~cJ`%VqFp8dS^ zRv^q0$<=GA5|LUOHLR=GCogK*#v-gN!%BYhhyBO<@j$z2K}G`T&&^Ww51{Oc$y3)k zLHNffVo!EkmDS7wyOHrmICBtg!CQNGZ%{>=_o3 zYc-(oSZLmxt=H#)3eLIu_{jvL*a?#wDYF5dZy45217#w8*FT!B2#rpR#^s5VYKmi# zQ!S|bzvviUY%kbsgc{*5U6f>*gBc6CZqTrjaKo;BzUv29qvVr&&BtqgL6@<34SM&#EI z1J)W|jxX6Xz4Q0qZJdD+TY8VzRn(>`w)8A$pW~IJmyD;$a~Es~G>p#=tp^~v#`hc_ zgXA;ZO6ytE?HL|SKLXRS_Ho$iflN@rmgb23Y;#e(@8l1+=ZbeD*qFJaCP*UjLM{nU z+p-_<35?O5f0;ae9(+LyM@KOHdz+W;Z#@K|RR7`~AphMPXSU_zuH6 z?Q#N1 z4cyapUvCs$<6~ehP&)2Ej%xM!qLA=v#-@0%P6SZp)%~qTg}5h*gwW9M=|Ta=QNFor zq!FH7qfLujz&osuXhiVS({_nX<*~ZWu7qk!x#!5Y&k#hu9T}ZLXQvO;kXa=M4Gp%B z+?SPy23dBvRt>T^*=tOn?4!w{LMzT#_daj;9x4Clhwu*^?jye~WX<;np_eev4kwtt z7k+>Y(JvK3FsRgh4}PS8?AOfMZO*q0(~Z?~YNh#!y3_gF6E2 zxJEo{jsVso>{L0)+Qjfa4W^57{M(z{p?f!xq(UG@*cD2(fM0RzT9xetsrDH7zaM;9 z63K_zITL7A2bQudQG5fdpC%Mc_)n{;jSa(RnMPM@pxJ(l|MQDUv$jw2rC%Vp1J3iI z@8gP)0Dd8o$hMX?R+gLTQ7JN_<(k=x%G+McLl7$`d@|>os9opW@3Ly1J2wZV-pJLy z=lb|uJ`E+&fO~TTdv8M147sL_bemYVW;waWh6GA78uKJ9?dis*Boh0fj6qW0va>=Y z%?CrWB~l}DieGOMXDR96`M#%BjTQ?+p)*X*^McUxDR|DSIaM_L=oR0~zi>28EzM0P zmR`GG>%oZWQWv58BK;QnlH4@bIViDi)Q_`U&qLlc_=PWr$XlXDuB`mKp*QSK{Q-*( zPKolM%xh$Z>5xP41fRh=2HoE3C6P-k9f(w@&K7abmrYnc7O!zmYj$kpo;sF0bLq=k zPKzCnY%Z~_@CQ(BOVwB42XD^Ob0y0(@rLC*ZrQAlN!TjW%>x;T23njpr4>CMVwAmEmWJ4St}XEu(+kyn+GAKB_Qj<;RE`pxuMG_m($u*KFowTO2ib zioe{K+pa&As`3OQMB!_0(1M$Fqy-Ny&HJ9rBmesDwEwqvD0j9cfw19A|N8GiJ(zIn zN`ISh2SHy?n02M?C!=-$M}G9|To3Y0M<=cJ?}^Hnm{oDkK@;(B^QhMVnpN1Pf(bnh zzGu%`c$wZ$-+Ni$3+c*sgRPjATosMR3qAzmli6p$gNm{5`iGRh3*5_ivrYnx>A#^3 z;3;l#?>F8SNivI+^kh-{VpN{CBebf~P7pz>`qSB)(gAy2zhZIbF(c+WSNe`U*fK0Z zWhNan1|HtC7;|tC2K<7C_tsbb-$rLfS-)LlwIh_Do>%yr(JYT<(s5~GoS};6lfey3 zD*FKx7ZL^9m_Hyz>cr?^R=-q#?Z0b;vdS&uQ)@)knI^Z_CCFZ@WbV50gNQY*4F_|H zYT*ix^b)QNy|X$I2tF-GTR5VXlg8i8AIz{hMIA7C8+W8D)GxDlLv$y4&00yK%3nZ^ z;Rb+v zUONa;9SXSPTcT$=pi`(ytx3uBgekvTDV2+$+f3wY;fZ5D*LOdKa97SUEAy z&#yGBDye!xR{j2VKz+QHR#}(<0etC;RQBXN8}T&#iH4?ruW8Dh6vKY8j<|hKCD4!R z-tZ+YHhLMSy`(<6#{aaxVFYcBD?Uv5p(-a@$%DYb@y_G73D&F2xG8R^rQn=8wX|=~ z)LPo)dfG3s19-fpyvP{N;frXE>Ov>R5Hy2rYOFM5%lv$*PD`ca(j4RZh01b{mPsgpF0Dymof;AQy=* zfOb;9I_BE34Mp@@cMOfsOe~G2BRi_xxYKdy!mKfQqqnD~NxTH}5L2Ygnz1Ozy6b%x zFiy`xaeKeQsC{vHiO~zc{{%NP31LvJ8J=mSCP&NjF11f|4Kam>;npE4DkkoXDk)IP zXsD>T8#YMWs=!?EK=03V?i^L@br9;QWzDg3#*=J_PphtRlFQ{VHJ|H$!>zL9N6=mZ zQ`O=gBccb&UY|o#4ArLLsBz+Z(eObACD|bzmUrSc#SvZJ-+@8JS|(!5@A>xt7_gp+ zaX*!&1x(Doqmx_SuD7w$vt!s49njBragR{SLDQ-nXSx;NV&*jdgJ~v`s z3o)i#Z~u*R&ahO_f-p?;8TH66{PPa9y&s1Y)oSBYDhUf=U$Ld_?YH1L^||h8rr-X& zD_EbO^WxzjDr-=ZsiSthexh+ULw+&>t?%ETsbNfS zyL`&NhfcHQPZ1g*z*5dYA&~$uZaph-k^`@_7&icsU6YlSl`dcD&wTyi$#yR$sH+qf zG6Zs0Tv(r4U!Y8RUoz=*;(abU?rK`&E}n^a2$^XK4h^oo6Gg}TWQMj z^xG*#0SkNEJB(1gZ^IyC5TX_MP4JeSmDx_B-L^VB(|Uq*1+2&gl^>o>pE;8I@ZD)|= z)KKeDV2U)ru+}Y<6@hZWEP%L%t9)0Y8eTJ59LzTq$D0&Z77lODxW|VVjNZMoJZ;R$ z0FePC6-M=AUlTE)04X7t$1hWRs;zpCGn1;E;C>bRS$O$p2m>PZxmBA94)@%x5jm_S?J|1Ed1QrODvarSrhHX(58xN9N ztlfE?k2%%Lv_+E3bSh-C{u&>2!7eOxH3_KCa(>T$DK{m_+t*EzTclNbgN#RTB#}

      $Iy>juqc1%Md-bsTvx30e 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%0DZPY!!R|+=pne&&LYda<(QLX#^ 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

      xR`lo6Rz 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≫M)BCLt&H}&vf$tSE@ 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 z

      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⪻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 -- Gitee From 7aba701aaa352fe13cb22116e73367433ad7e3d2 Mon Sep 17 00:00:00 2001 From: guoyu Date: Wed, 29 Jun 2022 17:14:14 +0800 Subject: [PATCH 2500/2647] add approve api Signed-off-by: guoyu --- pkg/microservice/aslan/config/consts.go | 8 ++ .../repository/models/wokflow_task_v4.go | 1 + .../common/repository/models/workflow_v4.go | 9 +- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_task_v4.go | 23 ++- .../service/workflow/workflow_task_v4.go | 45 ++++-- .../workflow/service/workflow/workflow_v4.go | 32 ++++- .../service/workflowcontroller/stage.go | 134 +++++++++++++++++- .../service/workflowcontroller/workflow.go | 11 +- pkg/tool/errors/http_errors.go | 3 + 10 files changed, 242 insertions(+), 25 deletions(-) diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 05e7e0f0..2ac6ebde 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -110,6 +110,7 @@ const ( StatusChanged Status = "changed" StatusNotRun Status = "notRun" StatusPrepare Status = "prepare" + StatusReject Status = "reject" ) type TaskStatus string @@ -174,6 +175,13 @@ const ( JobFreestyle JobType = "freestyle" ) +type ApproveOrReject string + +const ( + Approve ApproveOrReject = "approve" + Reject ApproveOrReject = "reject" +) + type DeploySourceType string const ( diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 92c62a15..dc7688a4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -55,6 +55,7 @@ type StageTask struct { Parallel bool `bson:"parallel" json:"parallel"` Approval *Approval `bson:"approval" json:"approval"` Jobs []*JobTask `bson:"jobs" json:"jobs"` + Error string `bson:"error" json:"errror"` } type JobTask struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 92a8c4da..01842010 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -49,15 +49,16 @@ type WorkflowStage struct { type Approval struct { Enabled bool `bson:"enabled" yaml:"enabled" json:"enabled"` - ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` + ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"` - NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` + NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` Description string `bson:"description" yaml:"description" json:"description"` } type User struct { - UserID string `bson:"user_id" yaml:"user_id" json:"user_id"` - UserName string `bson:"user_name" yaml:"user_name" json:"user_name"` + UserID string `bson:"user_id" yaml:"user_id" json:"user_id"` + UserName string `bson:"user_name" yaml:"user_name" json:"user_name"` + RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"` } type Job struct { diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index fd8b7b07..a82e2d83 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -185,6 +185,7 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV4.GET("", ListWorkflowTaskV4) taskV4.GET("/workflow/:workflowName/task/:taskID", GetWorkflowTaskV4) taskV4.DELETE("/workflow/:workflowName/task/:taskID", CancelWorkflowTaskV4) + taskV4.POST("/approve", ApproveStage) } bundles := router.Group("bundle-resources") diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index bb174476..0f0b5172 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -42,6 +42,15 @@ type listWorkflowTaskV4Resp struct { Total int64 `json:"total"` } +type ApproveRequest struct { + StageName string `json:"stage_name"` + WorkflowName string `json:"workflow_name"` + TaskID int64 `json:"task_id"` + UserName string `json:"user_name"` + UserID string `json:"user_id"` + Approve bool `json:"approve"` +} + func CreateWorkflowTaskV4(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() @@ -61,7 +70,6 @@ func CreateWorkflowTaskV4(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Resp, ctx.Err = workflow.CreateWorkflowTaskV4(ctx.UserName, args, ctx.Logger) } @@ -106,3 +114,16 @@ func CancelWorkflowTaskV4(c *gin.Context) { } ctx.Err = workflow.CancelWorkflowTaskV4(ctx.UserName, c.Param("workflowName"), taskID, ctx.Logger) } + +func ApproveStage(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + args := &ApproveRequest{} + + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = err + return + } + + ctx.Err = workflow.ApproveStage(args.WorkflowName, args.StageName, ctx.UserName, ctx.UserID, args.TaskID, args.Approve, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 25dea42e..993dd8e0 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -113,6 +113,9 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z ProjectName: workflow.Project, WorkflowName: workflow.Name, } + if err := workflowLint(workflow, log); err != nil { + return resp, err + } workflowTask := &commonmodels.WorkflowTask{} nextTaskID, err := commonrepo.NewCounterColl().GetNextSeq(fmt.Sprintf(setting.WorkflowTaskV4Fmt, workflow.Name)) if err != nil { @@ -149,19 +152,12 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z } stageTask.Jobs = append(stageTask.Jobs, jobs...) } + } + if err := workflowTaskLint(workflowTask, log); err != nil { + return resp, err } - // idString, err := commonrepo.NewworkflowTaskv4Coll().Create(workflowTask) - // if err != nil { - // log.Errorf("cannot create workflow task %s, the error is: %v", workflow.Name, err) - // return resp, e.ErrCreateTask.AddDesc(err.Error()) - // } - // id, err := primitive.ObjectIDFromHex(idString) - // if err != nil { - // log.Errorf("transform task id error: %v", err) - // return resp, e.ErrCreateTask.AddDesc(err.Error()) - // } - // workflowTask.ID = id + if err := workflowcontroller.CreateTask(workflowTask); err != nil { log.Errorf("create workflow task error: %v", err) return resp, e.ErrCreateTask.AddDesc(err.Error()) @@ -231,6 +227,17 @@ func GetWorkflowTaskV4(workflowName string, taskID int64, logger *zap.SugaredLog return resp, nil } +func ApproveStage(workflowName, stageName, userName, userID string, taskID int64, approve bool, logger *zap.SugaredLogger) error { + if workflowName == "" || stageName == "" || taskID == 0 { + return nil + } + if err := workflowcontroller.ApproveStage(workflowName, stageName, userName, userID, taskID, approve); err != nil { + logger.Error(err) + return e.ErrApproveTask.AddErr(err) + } + return nil +} + func jobsToJobPreviews(jobs []*commonmodels.JobTask) []*JobTaskPreview { resp := []*JobTaskPreview{} for _, job := range jobs { @@ -318,3 +325,19 @@ func setZadigBuildRepos(job *commonmodels.Job, logger *zap.SugaredLogger) error job.Spec = spec return nil } + +func workflowTaskLint(workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { + if len(workflowTask.Stages) <= 0 { + errMsg := fmt.Sprintf("no stage found in workflow task: %s,taskID: %d", workflowTask.WorkflowName, workflowTask.TaskID) + logger.Error(errMsg) + return e.ErrCreateTask.AddDesc(errMsg) + } + for _, stage := range workflowTask.Stages { + if len(stage.Jobs) <= 0 { + errMsg := fmt.Sprintf("no job found in workflow task: %s,taskID: %d,stage: %s", workflowTask.WorkflowName, workflowTask.TaskID, stage.Name) + logger.Error(errMsg) + return e.ErrCreateTask.AddDesc(errMsg) + } + } + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 952385cc..38ad2029 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -23,6 +23,7 @@ import ( "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" @@ -32,7 +33,7 @@ import ( ) const ( - JobNameRegx = "^[a-z][a-z0-9-]{1,32}$" + JobNameRegx = "^[a-z][a-z0-9-]{0,31}$" ) func CreateWorkflowV4(user string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { @@ -170,24 +171,49 @@ func workflowLint(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) return e.ErrUpsertWorkflow.AddDesc("common workflow do not support PM project yet") } } - jobNameMap := make(map[string]bool, 0) + stageNameMap := make(map[string]bool, 0) + jobNameMap := make(map[string]string, 0) reg, err := regexp.Compile(JobNameRegx) if err != nil { logger.Errorf("reg compile failed: %v", err) return e.ErrUpsertWorkflow.AddErr(err) } for _, stage := range workflow.Stages { + if _, ok := stageNameMap[stage.Name]; !ok { + stageNameMap[stage.Name] = true + } else { + logger.Errorf("duplicated stage name: %s", stage.Name) + return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("duplicated job name: %s", stage.Name)) + } + for _, job := range stage.Jobs { if match := reg.MatchString(job.Name); !match { logger.Errorf("job name [%s] did not match %s", job.Name, JobNameRegx) return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("job name [%s] did not match %s", job.Name, JobNameRegx)) } if _, ok := jobNameMap[job.Name]; !ok { - jobNameMap[job.Name] = true + jobNameMap[job.Name] = string(job.JobType) } else { logger.Errorf("duplicated job name: %s", job.Name) return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("duplicated job name: %s", job.Name)) } + + if job.JobType == config.JobZadigDeploy { + spec := &commonmodels.ZadigDeployJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + logger.Errorf("decode job spec error: %v", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + if spec.Source != config.SourceFromJob { + continue + } + jobType, ok := jobNameMap[spec.JobName] + if !ok || jobType != string(config.JobZadigBuild) { + errMsg := fmt.Sprintf("build job %s not found", spec.JobName) + logger.Error(errMsg) + return e.ErrCreateTask.AddDesc(errMsg) + } + } } } return nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 42196d19..1a88f379 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -18,6 +18,8 @@ package workflowcontroller import ( "context" + "fmt" + "sync" "time" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -25,6 +27,22 @@ import ( "go.uber.org/zap" ) +type approveMap struct { + m map[string]*approveWithLock + sync.RWMutex +} + +type approveWithLock struct { + approval *commonmodels.Approval + sync.RWMutex +} + +var globalApproveMap approveMap + +func init() { + globalApproveMap.m = make(map[string]*approveWithLock, 0) +} + type StageCtl interface { Run(ctx context.Context, concurrency int) } @@ -34,6 +52,12 @@ func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *c stage.StartTime = time.Now().Unix() ack() logger.Infof("start stage: %s,status: %s", stage.Name, stage.Status) + if err := waitiForApprove(ctx, stage, workflowCtx, ack); err != nil { + stage.Error = err.Error() + stage.EndTime = time.Now().Unix() + logger.Errorf("finish stage: %s,status: %s", stage.Name, stage.Status) + ack() + } defer func() { updateStageStatus(stage) stage.EndTime = time.Now().Unix() @@ -55,8 +79,61 @@ func RunStages(ctx context.Context, stages []*commonmodels.StageTask, workflowCt } } +func ApproveStage(workflowName, stageName, userName, userID string, taskID int64, approve bool) error { + approveKey := fmt.Sprintf("%s-%d-%s", workflowName, taskID, stageName) + approveWithL, ok := globalApproveMap.getApproval(approveKey) + if !ok { + return fmt.Errorf("workflow %s ID %d stage %s do not need approve", workflowName, taskID, stageName) + } + return approveWithL.doApproval(userName, userID, approve) +} + +func waitiForApprove(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func()) error { + if stage.Approval == nil { + return nil + } + if !stage.Approval.Enabled { + return nil + } + if stage.Approval.Timeout == 0 { + stage.Approval.Timeout = 60 + } + approveKey := fmt.Sprintf("%s-%d-%s", workflowCtx.WorkflowName, workflowCtx.TaskID, stage.Name) + approveWithL := &approveWithLock{approval: stage.Approval} + globalApproveMap.setApproval(approveKey, approveWithL) + defer globalApproveMap.deleteApproval(approveKey) + + timeout := time.After(time.Duration(stage.Approval.Timeout) * time.Minute) + latestApproveCount := 0 + for { + time.Sleep(1 * time.Second) + select { + case <-ctx.Done(): + stage.Status = config.StatusCancelled + return fmt.Errorf("workflow was canceled") + + case <-timeout: + stage.Status = config.StatusCancelled + return fmt.Errorf("workflow timeout") + default: + approved, approveCount, err := approveWithL.isApproval() + if err != nil { + stage.Status = config.StatusReject + return err + } + if approved { + return nil + } + if approveCount > latestApproveCount { + ack() + latestApproveCount = approveCount + } + } + } +} + func statusFailed(status config.Status) bool { - if status == config.StatusCancelled || status == config.StatusFailed || status == config.StatusTimeout { + if status == config.StatusCancelled || status == config.StatusFailed || status == config.StatusTimeout || status == config.StatusReject { return true } return false @@ -98,3 +175,58 @@ func updateStageStatus(stage *commonmodels.StageTask) { } stage.Status = stageStatus } + +func (c *approveMap) setApproval(key string, value *approveWithLock) { + c.Lock() + defer c.Unlock() + c.m[key] = value +} + +func (c *approveMap) getApproval(key string) (*approveWithLock, bool) { + c.RLock() + defer c.RUnlock() + v, existed := c.m[key] + return v, existed +} +func (c *approveMap) deleteApproval(key string) { + c.Lock() + defer c.Unlock() + delete(c.m, key) +} + +func (c *approveWithLock) isApproval() (bool, int, error) { + c.RLock() + defer c.RUnlock() + approveCount := 0 + for _, user := range c.approval.ApproveUsers { + if user.RejectOrApprove == config.Reject { + return false, approveCount, fmt.Errorf("%s reject this task", user.UserName) + } + if user.RejectOrApprove == config.Approve { + approveCount++ + } + } + if approveCount >= c.approval.NeededApprovers { + return true, approveCount, nil + } + return false, approveCount, nil +} + +func (c *approveWithLock) doApproval(userName, userID string, appvove bool) error { + c.Lock() + defer c.Unlock() + for _, user := range c.approval.ApproveUsers { + if user.UserID != userID { + continue + } + if user.RejectOrApprove != "" { + return fmt.Errorf("%s have %s already", userName, user.RejectOrApprove) + } + if appvove { + user.RejectOrApprove = config.Approve + } else { + user.RejectOrApprove = config.Reject + } + } + return fmt.Errorf("user %s has no authority to approve", userName) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 8229a3d6..e0e0b56b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -24,13 +24,11 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" uuid "github.com/satori/go.uuid" "go.uber.org/zap" ) -var approveChannelMap sync.Map var cancelChannelMap sync.Map type workflowCtl struct { @@ -50,7 +48,7 @@ func NewWorkflowController(workflowTask *commonmodels.WorkflowTask, logger *zap. } func CancelWorkflowTask(userName, workflowName string, taskID int64, logger *zap.SugaredLogger) error { - t, err := mongodb.NewworkflowTaskv4Coll().Find(workflowName, taskID) + t, err := commonrepo.NewworkflowTaskv4Coll().Find(workflowName, taskID) if err != nil { logger.Errorf("[%s] task: %s:%d not found", userName, workflowName, taskID) return err @@ -66,7 +64,7 @@ func CancelWorkflowTask(userName, workflowName string, taskID int64, logger *zap logger.Infof("[%s] CancelRunningTask %s:%d", userName, taskID, taskID) - if err := mongodb.NewworkflowTaskv4Coll().Update(t.ID.Hex(), t); err != nil { + if err := commonrepo.NewworkflowTaskv4Coll().Update(t.ID.Hex(), t); err != nil { logger.Errorf("[%s] update task: %s:%d error: %v", userName, workflowName, taskID, err) return err } @@ -103,7 +101,9 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { }() ctx, cancel := context.WithCancel(ctx) defer cancel() - cancelChannelMap.Store(fmt.Sprintf("%s-%d", c.workflowTask.WorkflowName, c.workflowTask.TaskID), cancel) + cancelKey := fmt.Sprintf("%s-%d", c.workflowTask.WorkflowName, c.workflowTask.TaskID) + cancelChannelMap.Store(cancelKey, cancel) + defer cancelChannelMap.Delete(cancelKey) workflowCtx := &commonmodels.WorkflowTaskCtx{ WorkflowName: c.workflowTask.WorkflowName, @@ -124,6 +124,7 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { func updateworkflowStatus(workflow *commonmodels.WorkflowTask) { statusMap := map[config.Status]int{ + config.StatusReject: 5, config.StatusCancelled: 4, config.StatusTimeout: 3, config.StatusFailed: 2, diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index 237dff5b..f4b154e0 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -284,6 +284,9 @@ var ( ErrCreateTaskFailed = NewHTTPError(6168, "创建工作流任务失败") + // ErrApproveTask ... + ErrApproveTask = NewHTTPError(6169, "批准工作流任务失败") + //----------------------------------------------------------------------------------------------- // Keystore APIs Range: 6180 - 6189 //----------------------------------------------------------------------------------------------- -- Gitee From a9f785249c5f001da351c29710eb0017e6b76070 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 30 Jun 2022 14:46:49 +0800 Subject: [PATCH 2501/2647] add real time workflow task status api Signed-off-by: guoyu --- .../repository/models/workflow_queue.go | 1 + .../aslan/core/workflow/handler/router.go | 2 ++ .../aslan/core/workflow/handler/sse.go | 31 ++++++++++++++++++ .../service/workflow/job/job_deploy.go | 13 ++++++-- .../workflow/service/workflow/workflow_v4.go | 16 ++++++---- .../service/workflowcontroller/queue.go | 32 ++++++++++++------- 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go b/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go index 0251d9a3..625a854d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_queue.go @@ -28,6 +28,7 @@ type WorkflowQueue struct { ProjectName string `bson:"project_name" json:"project_name"` WorkflowName string `bson:"workflow_name" json:"workflow_name"` Status config.Status `bson:"status" json:"status,omitempty"` + Stages []*StageTask `bson:"stages" json:"stages"` TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` TaskRevoker string `bson:"task_revoker,omitempty" json:"task_revoker,omitempty"` CreateTime int64 `bson:"create_time" json:"create_time,omitempty"` diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index a82e2d83..156cb033 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -46,6 +46,8 @@ func (*Router) Inject(router *gin.RouterGroup) { sse.GET("/workflows/id/:id/pipelines/:name", GetWorkflowTaskSSE) sse.GET("/tasks/running", RunningPipelineTasksSSE) sse.GET("/tasks/pending", PendingPipelineTasksSSE) + sse.GET("/workflowTasks/running", RunningWorkflowTasksSSE) + sse.GET("/workflowTasks/pending", PendingWorkflowTasksSSE) sse.GET("/tasks/id/:id/pipelines/:name", GetPipelineTaskSSE) sse.GET("/workflowtask/v3/id/:id/name/:name", GetWorkflowTaskV3SSE) } diff --git a/pkg/microservice/aslan/core/workflow/handler/sse.go b/pkg/microservice/aslan/core/workflow/handler/sse.go index d5f555b3..42ea8401 100644 --- a/pkg/microservice/aslan/core/workflow/handler/sse.go +++ b/pkg/microservice/aslan/core/workflow/handler/sse.go @@ -28,6 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types/dto" @@ -97,6 +98,36 @@ func PendingPipelineTasksSSE(c *gin.Context) { }, ctx.Logger) } +func RunningWorkflowTasksSSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + + internalhandler.Stream(c, func(ctx1 context.Context, msgChan chan interface{}) { + startTime := time.Now() + wait.NonSlidingUntilWithContext(ctx1, func(_ context.Context) { + msgChan <- workflowcontroller.RunningTasks() + + if time.Since(startTime).Minutes() == float64(60) { + ctx.Logger.Warnf("[%s] Query RunningPipelineTasksSSE API over 60 minutes", ctx.UserName) + } + }, time.Second) + }, ctx.Logger) +} + +func PendingWorkflowTasksSSE(c *gin.Context) { + ctx := internalhandler.NewContext(c) + + internalhandler.Stream(c, func(ctx1 context.Context, msgChan chan interface{}) { + startTime := time.Now() + wait.NonSlidingUntilWithContext(ctx1, func(_ context.Context) { + msgChan <- workflowcontroller.PendingTasks() + + if time.Since(startTime).Minutes() == float64(60) { + ctx.Logger.Warnf("[%s] Query PendingPipelineTasksSSE API over 60 minutes", ctx.UserName) + } + }, time.Second) + }, ctx.Logger) +} + func GetWorkflowTaskSSE(c *gin.Context) { ctx := internalhandler.NewContext(c) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index aa63e157..2f25a949 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -20,6 +20,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types/step" ) @@ -46,12 +47,17 @@ func (j *DeployJob) SetPreset() error { } j.job.Spec = j.spec - services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(j.workflow.Project) + project, err := templaterepo.NewProductColl().Find(j.workflow.Project) if err != nil { return err } - if len(services) > 0 { - j.spec.DeployType = services[0].Type + if project.ProductFeature != nil { + j.spec.DeployType = project.ProductFeature.DeployType + } + + services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(j.workflow.Project) + if err != nil { + return err } if j.spec.Source != config.SourceRuntime { @@ -80,6 +86,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { if err != nil { return resp, err } + // get deploy info from previous build job if j.spec.Source == config.SourceFromJob { for _, stage := range j.workflow.Stages { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 38ad2029..2fb32670 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -26,6 +26,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -160,15 +161,16 @@ func ListWorkflowV4(projectName, userID string, pageNum, pageSize int64, logger } func workflowLint(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { - services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(workflow.Project) + project, err := templaterepo.NewProductColl().Find(workflow.Project) if err != nil { - logger.Errorf("Failed to get project %s services, error: %v", workflow.Project, err) + logger.Errorf("Failed to get project %s, error: %v", workflow.Project, err) return e.ErrUpsertWorkflow.AddErr(err) } - if len(services) > 0 { - if services[0].Type != setting.K8SDeployType && services[0].Type != setting.HelmDeployType { - logger.Error("common workflow do not support PM project yet") - return e.ErrUpsertWorkflow.AddDesc("common workflow do not support PM project yet") + + if project.ProductFeature != nil { + if project.ProductFeature.DeployType != setting.K8SDeployType && project.ProductFeature.DeployType != setting.HelmDeployType { + logger.Error("common workflow only support k8s and helm project") + return e.ErrUpsertWorkflow.AddDesc("common workflow only support k8s and helm project") } } stageNameMap := make(map[string]bool, 0) @@ -200,7 +202,7 @@ func workflowLint(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) if job.JobType == config.JobZadigDeploy { spec := &commonmodels.ZadigDeployJobSpec{} - if err := commonmodels.IToi(job.Spec, spec); err != nil { + if err := commonmodels.IToiYaml(job.Spec, spec); err != nil { logger.Errorf("decode job spec error: %v", err) return e.ErrUpsertWorkflow.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go index 1624fadf..7c2ba077 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go @@ -18,6 +18,7 @@ package workflowcontroller import ( "context" + "encoding/json" "errors" "fmt" "time" @@ -29,8 +30,8 @@ import ( "github.com/koderover/zadig/pkg/tool/log" ) -func RunningTasks() []*commonmodels.WorkflowTask { - tasks := make([]*commonmodels.WorkflowTask, 0) +func RunningTasks() []*commonmodels.WorkflowQueue { + tasks := make([]*commonmodels.WorkflowQueue, 0) queueTasks, err := commonrepo.NewWorkflowQueueColl().List(&commonrepo.ListWorfklowQueueOption{}) if err != nil { log.Errorf("list queue worklfow task failed, err:%v", err) @@ -39,16 +40,14 @@ func RunningTasks() []*commonmodels.WorkflowTask { for _, t := range queueTasks { // task状态为TaskQueued说明task已经被send到nsq,wd已经开始处理但是没有返回ack if t.Status == config.StatusRunning { - if t, err := commonrepo.NewworkflowTaskv4Coll().Find(t.WorkflowName, t.TaskID); err == nil { - tasks = append(tasks, t) - } + tasks = append(tasks, t) } } return tasks } -func PendingTasks() []*commonmodels.WorkflowTask { - tasks := make([]*commonmodels.WorkflowTask, 0) +func PendingTasks() []*commonmodels.WorkflowQueue { + tasks := make([]*commonmodels.WorkflowQueue, 0) queueTasks, err := commonrepo.NewWorkflowQueueColl().List(&commonrepo.ListWorfklowQueueOption{}) if err != nil { log.Errorf("list queue workflow task failed, err:%v", err) @@ -56,9 +55,7 @@ func PendingTasks() []*commonmodels.WorkflowTask { } for _, t := range queueTasks { if t.Status == config.StatusWaiting || t.Status == config.StatusBlocked || t.Status == config.StatusQueued { - if t, err := commonrepo.NewworkflowTaskv4Coll().Find(t.WorkflowName, t.TaskID); err == nil { - tasks = append(tasks, t) - } + tasks = append(tasks, t) } } return tasks @@ -281,9 +278,10 @@ func UpdateQueue(task *commonmodels.WorkflowTask) bool { func ConvertTaskToQueue(task *commonmodels.WorkflowTask) *commonmodels.WorkflowQueue { return &commonmodels.WorkflowQueue{ TaskID: task.TaskID, - ProjectName: task.ProjectName, WorkflowName: task.WorkflowName, + ProjectName: task.ProjectName, Status: task.Status, + Stages: cleanStages(task.Stages), TaskCreator: task.TaskCreator, TaskRevoker: task.TaskRevoker, CreateTime: task.CreateTime, @@ -291,6 +289,18 @@ func ConvertTaskToQueue(task *commonmodels.WorkflowTask) *commonmodels.WorkflowQ } } +func cleanStages(stages []*commonmodels.StageTask) []*commonmodels.StageTask { + resp := []*commonmodels.StageTask{} + data, _ := json.Marshal(stages) + json.Unmarshal(data, &resp) + for _, stage := range resp { + for _, job := range stage.Jobs { + job.Steps = nil + } + } + return resp +} + func Remove(taskQueue *commonmodels.WorkflowQueue) error { return commonrepo.NewWorkflowQueueColl().Delete(taskQueue) } -- Gitee From 6aa6858bd6cf05d3df65338eec339bedbed887e5 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 30 Jun 2022 19:05:07 +0800 Subject: [PATCH 2502/2647] fix docker login problem Signed-off-by: guoyu --- .../repository/mongodb/workflow_queue.go | 1 + .../repository/mongodb/workflow_task_v4.go | 1 + .../stepcontroller/step_docker_build.go | 19 +++++++--- .../service/workflow/job/job_build.go | 38 ++++++++++++++++--- pkg/types/step/step_docker_build.go | 9 +++-- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go index 04ff739b..4c9a1269 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_queue.go @@ -118,6 +118,7 @@ func (c *WorkflowQueueColl) Update(args *models.WorkflowQueue) error { query := bson.M{"task_id": args.TaskID, "workflow_name": args.WorkflowName, "create_time": args.CreateTime} change := bson.M{"$set": bson.M{ "status": args.Status, + "stages": args.Stages, }} _, err := c.UpdateOne(context.TODO(), query, change) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go index e7a8d237..a317f4f7 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go @@ -109,6 +109,7 @@ func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option, pageNum, pageSi findOption = options.Find() } else { findOption = options.Find(). + SetSort(bson.D{{"create_time", -1}}). SetSkip((pageNum - 1) * pageSize). SetLimit(pageSize) } diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go index 7a5a8b5b..12ba31a1 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go @@ -34,12 +34,21 @@ func NewDockerBuildCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) } func (s *dockerBuildCtl) PreRun(ctx context.Context) error { - s.dockerBuildSpec.DockerRegistry = &step.DockerRegistry{ - UserName: config.RegistryAccessKey(), - Password: config.RegistrySecretKey(), - Namespace: config.RegistryNamespace(), - Host: config.RegistryAddress(), + if s.dockerBuildSpec.DockerRegistry != nil && s.dockerBuildSpec.DockerRegistry.DockerRegistryID != "" { + reg, _ := mongodb.NewRegistryNamespaceColl().Find(&mongodb.FindRegOps{ID: s.dockerBuildSpec.DockerRegistry.DockerRegistryID}) + s.dockerBuildSpec.DockerRegistry.UserName = reg.AccessKey + s.dockerBuildSpec.DockerRegistry.Password = reg.SecretKey + s.dockerBuildSpec.DockerRegistry.Namespace = reg.Namespace + s.dockerBuildSpec.DockerRegistry.Host = reg.RegAddr + } else { + s.dockerBuildSpec.DockerRegistry = &step.DockerRegistry{ + UserName: config.RegistryAccessKey(), + Password: config.RegistrySecretKey(), + Namespace: config.RegistryNamespace(), + Host: config.RegistryAddress(), + } } + proxies, _ := mongodb.NewProxyColl().List(&mongodb.ProxyArgs{}) if len(proxies) != 0 { s.dockerBuildSpec.Proxy.Address = proxies[0].Address diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index d10e0bf4..10b0ec69 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -27,6 +27,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/types/step" ) @@ -110,10 +112,10 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { jobTask.Properties = commonmodels.JobProperties{ Timeout: int64(buildInfo.Timeout), ResourceRequest: buildInfo.PreBuild.ResReq, - Args: buildInfo.PreBuild.Envs, + Args: renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs), ClusterID: buildInfo.PreBuild.ClusterID, } - jobTask.Properties.Args = append(jobTask.Properties.Args, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name)...) + jobTask.Properties.Args = append(jobTask.Properties.Args, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) // init tools install step for _, tool := range buildInfo.PreBuild.Installs { @@ -130,16 +132,20 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { Name: build.ServiceName + "-git", JobName: jobTask.Name, StepType: config.StepGit, - Spec: step.StepGitSpec{Repos: build.Repos}, + Spec: step.StepGitSpec{Repos: renderRepos(build.Repos, buildInfo.Repos)}, } jobTask.Steps = append(jobTask.Steps, gitStep) // init shell step + dockerLoginCmd := `docker login -u "$DOCKER_REGISTRY_AK" -p "$DOCKER_REGISTRY_SK" "$DOCKER_REGISTRY_HOST" &> /dev/null` + scripts := append([]string{dockerLoginCmd}, strings.Split(replaceWrapLine(buildInfo.Scripts), "\n")...) shellStep := &commonmodels.StepTask{ Name: build.ServiceName + "-shell", JobName: jobTask.Name, StepType: config.StepShell, - Spec: step.StepShellSpec{Scripts: strings.Split(replaceWrapLine(buildInfo.Scripts), "\n")}, + Spec: &step.StepShellSpec{ + Scripts: scripts, + }, } jobTask.Steps = append(jobTask.Steps, shellStep) @@ -166,6 +172,9 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { ImageReleaseTag: imageTag, BuildArgs: buildInfo.PostBuild.DockerBuild.BuildArgs, DockerTemplateContent: dockefileContent, + DockerRegistry: &step.DockerRegistry{ + DockerRegistryID: j.spec.DockerRegistryID, + }, }, } jobTask.Steps = append(jobTask.Steps, dockerBuildStep) @@ -218,6 +227,17 @@ func renderKeyVals(input, origin []*commonmodels.KeyVal) []*commonmodels.KeyVal return origin } +func renderRepos(input, origin []*types.Repository) []*types.Repository { + for i, originRepo := range origin { + for _, inputRepo := range input { + if originRepo.RepoName == inputRepo.RepoName && originRepo.RepoOwner == inputRepo.RepoOwner { + origin[i] = inputRepo + } + } + } + return origin +} + func replaceWrapLine(script string) string { return strings.Replace(strings.Replace( script, @@ -227,7 +247,7 @@ func replaceWrapLine(script string) string { ), "\r", "\n", -1) } -func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, workflowName string) []*commonmodels.KeyVal { +func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, workflowName, dockerRegistryID string) []*commonmodels.KeyVal { ret := make([]*commonmodels.KeyVal, 0) for index, repo := range build.Repos { @@ -255,6 +275,14 @@ func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_COMMIT_ID", repoName), Value: repo.CommitID, IsCredential: false}) } } + reg, err := commonrepo.NewRegistryNamespaceColl().Find(&commonrepo.FindRegOps{ID: dockerRegistryID}) + if err != nil { + log.Errorf("find docker registry by ID %s error: %v", dockerRegistryID, err) + } + ret = append(ret, &commonmodels.KeyVal{Key: "DOCKER_REGISTRY_HOST", Value: reg.RegAddr, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "DOCKER_REGISTRY_AK", Value: reg.AccessKey, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "DOCKER_REGISTRY_SK", Value: reg.SecretKey, IsCredential: true}) + ret = append(ret, &commonmodels.KeyVal{Key: "TASK_ID", Value: fmt.Sprintf("%d", taskID), IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE", Value: build.ServiceName, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE_MODULE", Value: build.ServiceModule, IsCredential: false}) diff --git a/pkg/types/step/step_docker_build.go b/pkg/types/step/step_docker_build.go index c6408dd2..065a1c45 100644 --- a/pkg/types/step/step_docker_build.go +++ b/pkg/types/step/step_docker_build.go @@ -37,10 +37,11 @@ type StepDockerBuildSpec struct { } type DockerRegistry struct { - Host string `bson:"host" json:"host" yaml:"host"` - Namespace string `bson:"namespace" json:"namespace" yaml:"namespace"` - UserName string `bson:"username" json:"username" yaml:"username"` - Password string `bson:"password" json:"password" yaml:"password"` + DockerRegistryID string `bson:"docker_registry_id" json:"docker_registry_id" yaml:"docker_registry_id"` + Host string `bson:"host" json:"host" yaml:"host"` + Namespace string `bson:"namespace" json:"namespace" yaml:"namespace"` + UserName string `bson:"username" json:"username" yaml:"username"` + Password string `bson:"password" json:"password" yaml:"password"` } func (s *StepDockerBuildSpec) GetDockerFile() string { -- Gitee From 7f8ec4709b19920ce48c15f6e7cbd1cc9a929f84 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 30 Jun 2022 20:20:18 +0800 Subject: [PATCH 2503/2647] add jobexecutor to resource server Signed-off-by: guoyu --- Makefile | 1 + docker/service/resource-server.Dockerfile | 1 + .../core/common/repository/models/build.go | 4 +-- .../common/repository/models/workflow_v4.go | 1 + .../service/jobcontroller/job_freestyle.go | 3 ++- .../service/jobcontroller/kubernetes.go | 27 ++++++++++++++++--- .../service/workflow/job/job_build.go | 2 ++ 7 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 4d6926da..90f136b7 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ resource-server.build.amd64: @cp docker/service/resource-server.Dockerfile docker/dist/amd64/resource-server.Dockerfile @sed -i -e '/#nginx.Dockerfile/ {' -e 'r docker/base/amd64/nginx.Dockerfile' -e 'd' -e '}' docker/dist/amd64/resource-server.Dockerfile @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/reaper cmd/reaper/main.go + @CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o docker/dist/jobexecutor cmd/jobexecutor/main.go @docker build -f docker/dist/amd64/resource-server.Dockerfile --tag ${MAKE_IMAGE} . resource-server.upload.amd64: MAKE_IMAGE ?= ${IMAGE_REPOSITORY}/resource-server:${VERSION}-amd64 diff --git a/docker/service/resource-server.Dockerfile b/docker/service/resource-server.Dockerfile index e76ca0ec..201cd5b1 100644 --- a/docker/service/resource-server.Dockerfile +++ b/docker/service/resource-server.Dockerfile @@ -4,5 +4,6 @@ WORKDIR /app ADD resource-server-nginx.conf /etc/nginx/conf.d/default.conf ADD docker/dist/reaper . +ADD docker/dist/jobexecutor . EXPOSE 80 diff --git a/pkg/microservice/aslan/core/common/repository/models/build.go b/pkg/microservice/aslan/core/common/repository/models/build.go index e6296e0c..20b49265 100644 --- a/pkg/microservice/aslan/core/common/repository/models/build.go +++ b/pkg/microservice/aslan/core/common/repository/models/build.go @@ -171,8 +171,8 @@ type KeyVal struct { Key string `bson:"key" json:"key" yaml:"key"` Value string `bson:"value" json:"value" yaml:"value"` Type ParameterSettingType `bson:"type,omitempty" json:"type,omitempty" yaml:"type"` - ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty" yaml:"choiceOption,omitempty"` - IsCredential bool `bson:"is_credential" json:"is_credential" yaml:"isCredential"` + ChoiceOption []string `bson:"choice_option,omitempty" json:"choice_option,omitempty" yaml:"choice_option,omitempty"` + IsCredential bool `bson:"is_credential" json:"is_credential" yaml:"is_credential"` } type Item struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 01842010..bdb998db 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -110,6 +110,7 @@ type JobProperties struct { ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` ClusterID string `bson:"cluster_id" json:"cluster_id"` BuildOS string `bson:"build_os" json:"build_os"` + ImageFrom string `bson:"image_from" json:"image_from"` Namespace string `bson:"namespace" json:"namespace"` Args []*KeyVal `bson:"args" json:"args"` Paths string `bson:"-" json:"-"` diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index b3da8fff..688737d3 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -155,7 +155,8 @@ func (c *FreestyleJobCtl) Run(ctx context.Context) { c.logger.Infof("succeed to create cm for job %s", c.jobName) // TODO: do not use default image - jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test2" + jobImage := getBaseImage(c.job.Properties.BuildOS, c.job.Properties.ImageFrom) + // jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test2" // jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 7fe9fefb..06f3de31 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -62,6 +62,8 @@ const ( ZadigContextDir = "/zadig/" ZadigLogFile = ZadigContextDir + "zadig.log" ZadigLifeCycleFile = ZadigContextDir + "lifecycle" + JobExecutorFile = "http://resource-server/jobexecutor" + ResourceServer = "resource-server" ) func GetK8sClients(hubServerAddr, clusterID string) (crClient.Client, kubernetes.Interface, *rest.Config, error) { @@ -131,13 +133,15 @@ func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx st return updater.CreateConfigMap(cm, kubeClient) } -// getReaperImage generates the image used to run reaper -// depends on the setting on page 'Build' -func getReaperImage(reaperImage, buildOS string) string { +func getBaseImage(buildOS, imageFrom string) string { // for built-in image, reaperImage and buildOs can generate a complete image // reaperImage: ccr.ccs.tencentyun.com/koderover-public/build-base:${BuildOS}-amd64 // buildOS: focal xenial bionic - jobImage := strings.ReplaceAll(reaperImage, "${BuildOS}", buildOS) + jobImage := strings.ReplaceAll(config.ReaperImage(), "${BuildOS}", buildOS) + // for custom image, buildOS represents the exact custom image + if imageFrom == setting.ImageFromCustom { + jobImage = buildOS + } return jobImage } @@ -150,6 +154,19 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re // ` // tailLogCommand := fmt.Sprintf(tailLogCommandTemplate, ZadigLogFile, ZadigLifeCycleFile) + var ( + jobExecutorBootingScript string + jobExecutorBinaryFile = JobExecutorFile + ) + // not local cluster + if clusterID != "" && clusterID != setting.LocalClusterID { + jobExecutorBinaryFile = strings.Replace(jobExecutorBinaryFile, ResourceServer, ResourceServer+".koderover-agent", -1) + } else { + jobExecutorBinaryFile = strings.Replace(jobExecutorBinaryFile, ResourceServer, ResourceServer+"."+currentNamespace, -1) + } + + jobExecutorBootingScript = fmt.Sprintf("curl -m 10 --retry-delay 3 --retry 3 -sSL %s -o reaper && chmod +x reaper && mv reaper /usr/local/bin && /usr/local/bin/reaper", jobExecutorBinaryFile) + labels := getJobLabels(&JobLabel{ WorkflowName: workflowCtx.WorkflowName, TaskID: workflowCtx.TaskID, @@ -205,6 +222,8 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re ImagePullPolicy: corev1.PullAlways, Name: jobTask.Name, Image: jobImage, + Command: []string{"/bin/sh", "-c"}, + Args: []string{jobExecutorBootingScript}, // Command: []string{"/bin/sh", "-c", "jobexecutor"}, // Lifecycle: &corev1.Lifecycle{ // PreStop: &corev1.Handler{ diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 10b0ec69..68257b7b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -114,6 +114,8 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { ResourceRequest: buildInfo.PreBuild.ResReq, Args: renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs), ClusterID: buildInfo.PreBuild.ClusterID, + BuildOS: buildInfo.PreBuild.BuildOS, + ImageFrom: buildInfo.PreBuild.ImageFrom, } jobTask.Properties.Args = append(jobTask.Properties.Args, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) -- Gitee From 4514e8ca447924190059b1bd1b1e290b06b1f631 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 1 Jul 2022 10:48:46 +0800 Subject: [PATCH 2504/2647] add clone workflow task api Signed-off-by: guoyu --- .../common/repository/models/wokflow_task_v4.go | 5 +++-- .../aslan/core/workflow/handler/router.go | 1 + .../aslan/core/workflow/handler/workflow_task_v4.go | 12 ++++++++++++ .../workflow/service/workflow/workflow_task_v4.go | 13 ++++++++++++- .../workflow/service/workflowcontroller/workflow.go | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index dc7688a4..e4bb3155 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -26,7 +26,8 @@ type WorkflowTask struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` TaskID int64 `bson:"task_id" json:"task_id"` WorkflowName string `bson:"workflow_name" json:"workflow_name"` - Args []*KeyVal `bson:"args" json:"args"` + WorkflowArgs *WorkflowV4 `bson:"workflow_args" json:"workflow_args"` + KeyVals []*KeyVal `bson:"key_vals" json:"key_vals"` GlobalContext map[string]string `bson:"global_context" json:"global_context"` Status config.Status `bson:"status" json:"status,omitempty"` TaskCreator string `bson:"task_creator" json:"task_creator,omitempty"` @@ -90,7 +91,7 @@ type WorkflowTaskCtx struct { DistDir string DockerMountDir string ConfigMapMountDir string - WorkflowArgs []*KeyVal + WorkflowKeyVals []*KeyVal GlobalContextGet func(key string) (string, bool) GlobalContextSet func(key, value string) GlobalContextEach func(f func(k, v string) bool) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 156cb033..81375348 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -187,6 +187,7 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV4.GET("", ListWorkflowTaskV4) taskV4.GET("/workflow/:workflowName/task/:taskID", GetWorkflowTaskV4) taskV4.DELETE("/workflow/:workflowName/task/:taskID", CancelWorkflowTaskV4) + taskV4.GET("/clone/workflow/:workflowName/task/:taskID", CloneWorkflowTaskV4) taskV4.POST("/approve", ApproveStage) } diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index 0f0b5172..b619da33 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -115,6 +115,18 @@ func CancelWorkflowTaskV4(c *gin.Context) { ctx.Err = workflow.CancelWorkflowTaskV4(ctx.UserName, c.Param("workflowName"), taskID, ctx.Logger) } +func CloneWorkflowTaskV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + taskID, err := strconv.ParseInt(c.Param("taskID"), 10, 64) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc("invalid task id") + return + } + ctx.Resp, ctx.Err = workflow.CloneWorkflowTaskV4(c.Param("workflowName"), taskID, ctx.Logger) +} + func ApproveStage(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 993dd8e0..b9093660 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -158,6 +158,8 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z return resp, err } + workflowTask.WorkflowArgs = workflow + if err := workflowcontroller.CreateTask(workflowTask); err != nil { log.Errorf("create workflow task error: %v", err) return resp, e.ErrCreateTask.AddDesc(err.Error()) @@ -165,6 +167,15 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z return resp, nil } +func CloneWorkflowTaskV4(workflowName string, taskID int64, logger *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { + task, err := commonrepo.NewworkflowTaskv4Coll().Find(workflowName, taskID) + if err != nil { + logger.Errorf("find workflowTaskV4 error: %s", err) + return nil, e.ErrGetTask.AddErr(err) + } + return task.WorkflowArgs, nil +} + func UpdateWorkflowTaskV4(id string, workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { err := commonrepo.NewworkflowTaskv4Coll().Update( id, @@ -197,7 +208,7 @@ func CancelWorkflowTaskV4(userName, workflowName string, taskID int64, logger *z func GetWorkflowTaskV4(workflowName string, taskID int64, logger *zap.SugaredLogger) (*WorkflowTaskPreview, error) { task, err := commonrepo.NewworkflowTaskv4Coll().Find(workflowName, taskID) if err != nil { - logger.Errorf("list workflowTaskV4 error: %s", err) + logger.Errorf("find workflowTaskV4 error: %s", err) return nil, err } resp := &WorkflowTaskPreview{ diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index e0e0b56b..2064a05e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -112,7 +112,7 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { DistDir: fmt.Sprintf("%s/%s/dist/%d", config.S3StoragePath(), c.workflowTask.WorkflowName, c.workflowTask.TaskID), DockerMountDir: fmt.Sprintf("/tmp/%s/docker/%d", uuid.NewV4(), time.Now().Unix()), ConfigMapMountDir: fmt.Sprintf("/tmp/%s/cm/%d", uuid.NewV4(), time.Now().Unix()), - WorkflowArgs: c.workflowTask.Args, + WorkflowKeyVals: c.workflowTask.KeyVals, GlobalContextGet: c.getGlobalContext, GlobalContextSet: c.setGlobalContext, GlobalContextEach: c.globalContextEach, -- Gitee From a335641e3e00bcd82d0bfa0501151b38b671bea2 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 1 Jul 2022 11:15:16 +0800 Subject: [PATCH 2505/2647] add lint workflow api Signed-off-by: guoyu --- .../aslan/core/workflow/handler/router.go | 1 + .../core/workflow/handler/workflow_v4.go | 22 +++++++++++++++++++ .../service/workflow/workflow_task_v4.go | 2 +- .../workflow/service/workflow/workflow_v4.go | 11 +++++++--- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 81375348..9b6b80e1 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -172,6 +172,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { workflowV4.POST("", CreateWorkflowV4) workflowV4.GET("", ListWorkflowV4) + workflowV4.POST("/lint", LintWorkflowV4) workflowV4.GET("/name/:name", FindWorkflowV4) workflowV4.PUT("/:name", UpdateWorkflowV4) workflowV4.DELETE("/:name", DeleteWorkflowV4) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index b7cc0cbd..6ce030eb 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -63,6 +63,28 @@ func CreateWorkflowV4(c *gin.Context) { ctx.Err = workflow.CreateWorkflowV4(ctx.UserName, args, ctx.Logger) } +func LintWorkflowV4(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(commonmodels.WorkflowV4) + data, err := c.GetRawData() + if err != nil { + log.Errorf("CreateWorkflowv4 c.GetRawData() err : %s", err) + } + if err = yaml.Unmarshal(data, args); err != nil { + log.Errorf("CreateWorkflowv4 json.Unmarshal err : %s", err) + } + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindYAML(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) + return + } + ctx.Err = workflow.LintWorkflowV4(args, ctx.Logger) +} + func ListWorkflowV4(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index b9093660..2393f90f 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -113,7 +113,7 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z ProjectName: workflow.Project, WorkflowName: workflow.Name, } - if err := workflowLint(workflow, log); err != nil { + if err := LintWorkflowV4(workflow, log); err != nil { return resp, err } workflowTask := &commonmodels.WorkflowTask{} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 2fb32670..c8c3dbf9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -43,7 +43,7 @@ func CreateWorkflowV4(user string, workflow *commonmodels.WorkflowV4, logger *za errStr := fmt.Sprintf("workflow v4 [%s] 在项目 [%s] 中已经存在!", workflow.Name, workflow.Project) return e.ErrUpsertWorkflow.AddDesc(errStr) } - if err := workflowLint(workflow, logger); err != nil { + if err := LintWorkflowV4(workflow, logger); err != nil { return err } @@ -75,7 +75,7 @@ func UpdateWorkflowV4(name, user string, inputWorkflow *commonmodels.WorkflowV4, logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) return e.ErrFindWorkflow.AddErr(err) } - if err := workflowLint(inputWorkflow, logger); err != nil { + if err := LintWorkflowV4(inputWorkflow, logger); err != nil { return err } @@ -160,7 +160,12 @@ func ListWorkflowV4(projectName, userID string, pageNum, pageSize int64, logger return resp, total, nil } -func workflowLint(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { +func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { + if workflow.Project == "" { + err := fmt.Errorf("project should not be empty") + logger.Errorf(err.Error()) + return e.ErrUpsertWorkflow.AddErr(err) + } project, err := templaterepo.NewProductColl().Find(workflow.Project) if err != nil { logger.Errorf("Failed to get project %s, error: %v", workflow.Project, err) -- Gitee From 5358223f7ee46a8461b56e60d4e721834427ed38 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 1 Jul 2022 16:02:55 +0800 Subject: [PATCH 2506/2647] fix approve bug Signed-off-by: guoyu --- .../common/repository/models/workflow_v4.go | 13 ++++++----- .../core/workflow/handler/workflow_task_v4.go | 19 +++++++++++----- .../service/workflow/workflow_task_v4.go | 8 ++++--- .../service/workflowcontroller/stage.go | 22 ++++++++++++++----- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index bdb998db..05944bd3 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -48,17 +48,20 @@ type WorkflowStage struct { } type Approval struct { - Enabled bool `bson:"enabled" yaml:"enabled" json:"enabled"` - ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` - Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"` - NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` - Description string `bson:"description" yaml:"description" json:"description"` + Enabled bool `bson:"enabled" yaml:"enabled" json:"enabled"` + ApproveUsers []*User `bson:"approve_users" yaml:"approve_users" json:"approve_users"` + Timeout int `bson:"timeout" yaml:"timeout" json:"timeout"` + NeededApprovers int `bson:"needed_approvers" yaml:"needed_approvers" json:"needed_approvers"` + Description string `bson:"description" yaml:"description" json:"description"` + RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"` } type User struct { UserID string `bson:"user_id" yaml:"user_id" json:"user_id"` UserName string `bson:"user_name" yaml:"user_name" json:"user_name"` RejectOrApprove config.ApproveOrReject `bson:"reject_or_approve" yaml:"-" json:"reject_or_approve"` + Comment string `bson:"comment" yaml:"-" json:"comment"` + OperationTime int64 `bson:"operation_time" yaml:"-" json:"operation_time"` } type Job struct { diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go index b619da33..938ecf32 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_task_v4.go @@ -46,9 +46,8 @@ type ApproveRequest struct { StageName string `json:"stage_name"` WorkflowName string `json:"workflow_name"` TaskID int64 `json:"task_id"` - UserName string `json:"user_name"` - UserID string `json:"user_id"` Approve bool `json:"approve"` + Comment string `json:"comment"` } func CreateWorkflowTaskV4(c *gin.Context) { @@ -132,10 +131,20 @@ func ApproveStage(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() args := &ApproveRequest{} - if err := c.ShouldBindQuery(args); err != nil { - ctx.Err = err + data, err := c.GetRawData() + if err != nil { + log.Errorf("ApproveStage c.GetRawData() err : %s", err) + } + if err = json.Unmarshal(data, args); err != nil { + log.Errorf("ApproveStage json.Unmarshal err : %s", err) + } + + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) + + if err := c.ShouldBindJSON(&args); err != nil { + ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - ctx.Err = workflow.ApproveStage(args.WorkflowName, args.StageName, ctx.UserName, ctx.UserID, args.TaskID, args.Approve, ctx.Logger) + ctx.Err = workflow.ApproveStage(args.WorkflowName, args.StageName, ctx.UserName, ctx.UserID, args.Comment, args.TaskID, args.Approve, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 2393f90f..9d2fb258 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -238,11 +238,13 @@ func GetWorkflowTaskV4(workflowName string, taskID int64, logger *zap.SugaredLog return resp, nil } -func ApproveStage(workflowName, stageName, userName, userID string, taskID int64, approve bool, logger *zap.SugaredLogger) error { +func ApproveStage(workflowName, stageName, userName, userID, comment string, taskID int64, approve bool, logger *zap.SugaredLogger) error { if workflowName == "" || stageName == "" || taskID == 0 { - return nil + errMsg := fmt.Sprintf("can not find approved workflow: %s, taskID: %d,stage: %s", workflowName, taskID, stageName) + logger.Error(errMsg) + return e.ErrApproveTask.AddDesc(errMsg) } - if err := workflowcontroller.ApproveStage(workflowName, stageName, userName, userID, taskID, approve); err != nil { + if err := workflowcontroller.ApproveStage(workflowName, stageName, userName, userID, comment, taskID, approve); err != nil { logger.Error(err) return e.ErrApproveTask.AddErr(err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go index 1a88f379..ca3ab3ca 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go @@ -57,6 +57,7 @@ func runStage(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *c stage.EndTime = time.Now().Unix() logger.Errorf("finish stage: %s,status: %s", stage.Name, stage.Status) ack() + return } defer func() { updateStageStatus(stage) @@ -79,13 +80,13 @@ func RunStages(ctx context.Context, stages []*commonmodels.StageTask, workflowCt } } -func ApproveStage(workflowName, stageName, userName, userID string, taskID int64, approve bool) error { +func ApproveStage(workflowName, stageName, userName, userID, comment string, taskID int64, approve bool) error { approveKey := fmt.Sprintf("%s-%d-%s", workflowName, taskID, stageName) approveWithL, ok := globalApproveMap.getApproval(approveKey) if !ok { return fmt.Errorf("workflow %s ID %d stage %s do not need approve", workflowName, taskID, stageName) } - return approveWithL.doApproval(userName, userID, approve) + return approveWithL.doApproval(userName, userID, comment, approve) } func waitiForApprove(ctx context.Context, stage *commonmodels.StageTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func()) error { @@ -101,7 +102,10 @@ func waitiForApprove(ctx context.Context, stage *commonmodels.StageTask, workflo approveKey := fmt.Sprintf("%s-%d-%s", workflowCtx.WorkflowName, workflowCtx.TaskID, stage.Name) approveWithL := &approveWithLock{approval: stage.Approval} globalApproveMap.setApproval(approveKey, approveWithL) - defer globalApproveMap.deleteApproval(approveKey) + defer func() { + globalApproveMap.deleteApproval(approveKey) + ack() + }() timeout := time.After(time.Duration(stage.Approval.Timeout) * time.Minute) latestApproveCount := 0 @@ -195,11 +199,12 @@ func (c *approveMap) deleteApproval(key string) { } func (c *approveWithLock) isApproval() (bool, int, error) { - c.RLock() - defer c.RUnlock() + c.Lock() + defer c.Unlock() approveCount := 0 for _, user := range c.approval.ApproveUsers { if user.RejectOrApprove == config.Reject { + c.approval.RejectOrApprove = config.Reject return false, approveCount, fmt.Errorf("%s reject this task", user.UserName) } if user.RejectOrApprove == config.Approve { @@ -207,12 +212,13 @@ func (c *approveWithLock) isApproval() (bool, int, error) { } } if approveCount >= c.approval.NeededApprovers { + c.approval.RejectOrApprove = config.Approve return true, approveCount, nil } return false, approveCount, nil } -func (c *approveWithLock) doApproval(userName, userID string, appvove bool) error { +func (c *approveWithLock) doApproval(userName, userID, comment string, appvove bool) error { c.Lock() defer c.Unlock() for _, user := range c.approval.ApproveUsers { @@ -222,10 +228,14 @@ func (c *approveWithLock) doApproval(userName, userID string, appvove bool) erro if user.RejectOrApprove != "" { return fmt.Errorf("%s have %s already", userName, user.RejectOrApprove) } + user.Comment = comment + user.OperationTime = time.Now().Unix() if appvove { user.RejectOrApprove = config.Approve + return nil } else { user.RejectOrApprove = config.Reject + return nil } } return fmt.Errorf("user %s has no authority to approve", userName) -- Gitee From c782e513a94a2b8edfe52945a5a4a415c9273e48 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 1 Jul 2022 17:56:04 +0800 Subject: [PATCH 2507/2647] fix no images when build has no repo Signed-off-by: guoyu --- .../service/workflow/job/job_build.go | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 68257b7b..57240038 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -86,19 +86,21 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { } for _, build := range j.spec.ServiceAndBuilds { - imageTag := "" + imageTag := time.Now().Format("20060102150405") for _, repo := range build.Repos { imageTag = repo.GetReleaseCandidateTag(taskID) - if len(registry.Namespace) > 0 { - build.Image = fmt.Sprintf("%s/%s/%s:%s", registry.RegAddr, registry.Namespace, build.ServiceModule, imageTag) - } else { - build.Image = fmt.Sprintf("%s/%s:%s", registry.RegAddr, build.ServiceModule, imageTag) - } - - build.Image = strings.TrimPrefix(build.Image, "http://") - build.Image = strings.TrimPrefix(build.Image, "https://") break } + + if len(registry.Namespace) > 0 { + build.Image = fmt.Sprintf("%s/%s/%s:%s", registry.RegAddr, registry.Namespace, build.ServiceModule, imageTag) + } else { + build.Image = fmt.Sprintf("%s/%s:%s", registry.RegAddr, build.ServiceModule, imageTag) + } + + build.Image = strings.TrimPrefix(build.Image, "http://") + build.Image = strings.TrimPrefix(build.Image, "https://") + build.Package = fmt.Sprintf("%s-%s-%d.tar.gz", build.ServiceModule, time.Now().Format("20060102150405"), taskID) jobTask := &commonmodels.JobTask{ -- Gitee From 2276b4d7d2ddfcc3a4a19bdda7b2ee066063467d Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 1 Jul 2022 17:57:29 +0800 Subject: [PATCH 2508/2647] fix job executor log format Signed-off-by: guoyu --- pkg/microservice/jobexecutor/core/service/step/step.go | 7 +------ .../jobexecutor/core/service/step/step_archive.go | 8 ++++++++ .../jobexecutor/core/service/step/step_docker_build.go | 7 +++++++ .../jobexecutor/core/service/step/step_git.go | 6 ++++++ .../jobexecutor/core/service/step/step_shell.go | 8 ++++++++ .../jobexecutor/core/service/step/step_tool_install.go | 8 ++++++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/jobexecutor/core/service/step/step.go b/pkg/microservice/jobexecutor/core/service/step/step.go index 815fda3d..718f1fb6 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step.go +++ b/pkg/microservice/jobexecutor/core/service/step/step.go @@ -23,7 +23,6 @@ import ( "io" "os" "strings" - "time" "github.com/koderover/zadig/pkg/microservice/jobexecutor/config" "github.com/koderover/zadig/pkg/microservice/jobexecutor/core/service/cmd" @@ -45,13 +44,9 @@ func RunSteps(ctx context.Context, steps []*meta.Step, workspace, paths string, return nil } func runStep(ctx context.Context, step *meta.Step, workspace, paths string, envs, secretEnvs []string) error { - start := time.Now() var stepInstance Step var err error - fmt.Printf("====================== %s Start ======================\n", step.Name) - defer func() { - fmt.Printf("====================== %s End. Duration: %.2f seconds ======================\n", step.Name, time.Since(start).Seconds()) - }() + switch step.StepType { case "shell": stepInstance, err = NewShellStep(step.Spec, workspace, paths, envs, secretEnvs) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_archive.go b/pkg/microservice/jobexecutor/core/service/step/step_archive.go index d758cf2f..c4dce307 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_archive.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_archive.go @@ -22,8 +22,10 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/types/step" "gopkg.in/yaml.v3" @@ -49,6 +51,12 @@ func NewArchiveStep(spec interface{}, workspace string, envs, secretEnvs []strin } func (s *ArchiveStep) Run(ctx context.Context) error { + start := time.Now() + log.Infof("Start archive %s.", s.spec.FilePath) + defer func() { + log.Infof("Archive %s ended. Duration: %.2f seconds", s.spec.FilePath, time.Since(start).Seconds()) + }() + if s.spec.DestinationPath == "" || s.spec.FilePath == "" { return nil } diff --git a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go index 85555ad9..49380c19 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go @@ -27,6 +27,7 @@ import ( "time" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/step" "github.com/koderover/zadig/pkg/util/fs" "gopkg.in/yaml.v3" @@ -54,6 +55,12 @@ func NewDockerBuildStep(spec interface{}, workspace string, envs, secretEnvs []s } func (s *DockerBuildStep) Run(ctx context.Context) error { + start := time.Now() + log.Infof("Start docker build.") + defer func() { + log.Infof("Docker build ended. Duration: %.2f seconds.", time.Since(start).Seconds()) + }() + if err := s.dockerLogin(); err != nil { return err } diff --git a/pkg/microservice/jobexecutor/core/service/step/step_git.go b/pkg/microservice/jobexecutor/core/service/step/step_git.go index 75cb0c15..f0fdcc12 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_git.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_git.go @@ -27,6 +27,7 @@ import ( "path" "path/filepath" "strings" + "time" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/sets" @@ -58,6 +59,11 @@ func NewGitStep(spec interface{}, workspace string, envs, secretEnvs []string) ( } func (s *GitStep) Run(ctx context.Context) error { + start := time.Now() + log.Infof("Start git clone.") + defer func() { + log.Infof("Git clone ended. Duration: %.2f seconds.", time.Since(start).Seconds()) + }() return s.runGitCmds() } diff --git a/pkg/microservice/jobexecutor/core/service/step/step_shell.go b/pkg/microservice/jobexecutor/core/service/step/step_shell.go index 0a563530..fa679e36 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_shell.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_shell.go @@ -25,7 +25,9 @@ import ( "path/filepath" "strings" "sync" + "time" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/step" "github.com/koderover/zadig/pkg/util" "gopkg.in/yaml.v3" @@ -52,6 +54,12 @@ func NewShellStep(spec interface{}, workspace, paths string, envs, secretEnvs [] } func (s *ShellStep) Run(ctx context.Context) error { + start := time.Now() + log.Infof("Executing user script.") + defer func() { + log.Infof("Script Execution ended. Duration: %.2f seconds.", time.Since(start).Seconds()) + }() + if len(s.spec.Scripts) == 0 { return nil } diff --git a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go index d060afda..540caebb 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go @@ -9,11 +9,13 @@ import ( "path" "path/filepath" "strings" + "time" "github.com/google/uuid" "github.com/koderover/zadig/pkg/microservice/reaper/config" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" s3tool "github.com/koderover/zadig/pkg/tool/s3" "github.com/koderover/zadig/pkg/types/step" @@ -40,6 +42,12 @@ func NewToolInstallStep(spec interface{}, workspace string, envs, secretEnvs []s } func (s *ToolInstallStep) Run(ctx context.Context) error { + start := time.Now() + log.Infof("Installing %s %s.", s.spec.Name, s.spec.Version) + defer func() { + log.Infof("Install %s %s ended. Duration: %.2f seconds.", s.spec.Name, s.spec.Version, time.Since(start).Seconds()) + }() + return s.runIntallationScripts() } -- Gitee From b55b86bc2132aeb2044618d946977819768a1716 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 1 Jul 2022 19:28:03 +0800 Subject: [PATCH 2509/2647] add more info to workflow list Signed-off-by: guoyu --- .../aslan/core/workflow/service/workflow/workflow_v4.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index c8c3dbf9..69c43ead 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -154,7 +154,10 @@ func ListWorkflowV4(projectName, userID string, pageNum, pageSize int64, logger ProjectName: workflow.Project, EnabledStages: stages, CreateTime: workflow.CreateTime, + UpdateTime: workflow.UpdateTime, + UpdateBy: workflow.UpdatedBy, WorkflowType: "common_workflow", + Description: workflow.Description, }) } return resp, total, nil -- Gitee From 3bcc63820dc8cdd4a5283198dd45f617cf168876 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 4 Jul 2022 10:21:12 +0800 Subject: [PATCH 2510/2647] environment config gitops (#1722) * add data combine logic and git data save Signed-off-by: allenshen * update logic of adding env resource when creating envs Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * add rollback logic Signed-off-by: allenshen * add logic to filter services created by zadig Signed-off-by: allenshen * optimize code Signed-off-by: allenshen * add delete env resource logic Signed-off-by: allenshen * resolve comments Signed-off-by: allenshen --- pkg/cli/upgradeassistant/cmd/migrate/1130.go | 155 +++++++ .../internal/repository/models/env_configs.go | 100 +++++ .../repository/mongodb/env_resources.go} | 189 ++++---- .../common/repository/models/env_ingress.go | 35 -- .../core/common/repository/models/env_pvc.go | 35 -- .../{env_configmap.go => env_resource.go} | 14 +- .../common/repository/models/env_secret.go | 35 -- .../core/common/repository/models/product.go | 21 +- .../core/common/repository/models/service.go | 2 +- .../repository/models/template/product.go | 11 +- .../common/repository/mongodb/env_ingress.go | 101 ----- .../core/common/repository/mongodb/env_pvc.go | 157 ------- .../{env_configmap.go => env_resource.go} | 91 ++-- .../environment/handler/common_env_cfg.go | 23 +- .../environment/service/common_env_cfg.go | 410 +++++++++++------- .../core/environment/service/configmap.go | 70 +-- .../aslan/core/environment/service/env.go | 2 +- .../core/environment/service/environment.go | 32 +- .../service/environment_creator.go | 3 +- .../aslan/core/environment/service/helm.go | 141 +++--- .../aslan/core/environment/service/ingress.go | 54 ++- .../aslan/core/environment/service/k8s.go | 4 +- .../aslan/core/environment/service/pm.go | 9 +- .../aslan/core/environment/service/pvc.go | 50 ++- .../aslan/core/environment/service/secret.go | 36 +- pkg/microservice/aslan/core/service.go | 5 +- pkg/tool/kube/getter/ingress.go | 29 ++ pkg/tool/kube/getter/pvc.go | 9 + 28 files changed, 949 insertions(+), 874 deletions(-) create mode 100644 pkg/cli/upgradeassistant/cmd/migrate/1130.go create mode 100644 pkg/cli/upgradeassistant/internal/repository/models/env_configs.go rename pkg/{microservice/aslan/core/common/repository/mongodb/env_secret.go => cli/upgradeassistant/internal/repository/mongodb/env_resources.go} (38%) delete mode 100644 pkg/microservice/aslan/core/common/repository/models/env_ingress.go delete mode 100644 pkg/microservice/aslan/core/common/repository/models/env_pvc.go rename pkg/microservice/aslan/core/common/repository/models/{env_configmap.go => env_resource.go} (72%) delete mode 100644 pkg/microservice/aslan/core/common/repository/models/env_secret.go delete mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go delete mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go rename pkg/microservice/aslan/core/common/repository/mongodb/{env_configmap.go => env_resource.go} (68%) diff --git a/pkg/cli/upgradeassistant/cmd/migrate/1130.go b/pkg/cli/upgradeassistant/cmd/migrate/1130.go new file mode 100644 index 00000000..f6633b05 --- /dev/null +++ b/pkg/cli/upgradeassistant/cmd/migrate/1130.go @@ -0,0 +1,155 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package migrate + +import ( + "context" + + "go.mongodb.org/mongo-driver/bson/primitive" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + internalmongodb "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/mongodb" + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/upgradepath" + "github.com/koderover/zadig/pkg/tool/log" +) + +func init() { + upgradepath.RegisterHandler("1.12.0", "1.13.0", V1120ToV1130) + upgradepath.RegisterHandler("1.13.0", "1.12.0", V1130ToV1120) +} + +func V1120ToV1130() error { + return CombineEnvResources() +} + +func V1130ToV1120() error { + return RemoveEnvResource() +} + +func CombineEnvResources() error { + log.Info("start CombineEnvResources") + resourceList := make([]*models.EnvResource, 0) + + cms, err := internalmongodb.NewConfigMapColl().List() + if err != nil { + return err + } + resourceList = append(resourceList, ConvertCmToResource(cms)...) + + ings, err := internalmongodb.NewIngressColl().List() + if err != nil { + return err + } + resourceList = append(resourceList, ConvertIngToResource(ings)...) + + secrets, err := internalmongodb.NewSecretColl().List() + if err != nil { + return err + } + resourceList = append(resourceList, ConvertSecretToResource(secrets)...) + + pvcs, err := internalmongodb.NewPvcColl().List() + if err != nil { + return err + } + resourceList = append(resourceList, ConvertPVCToResource(pvcs)...) + + return internalmongodb.NewEnvResourceColl().BatchInsert(resourceList) +} + +func ConvertCmToResource(cms []*models.EnvConfigMap) []*models.EnvResource { + resourceList := make([]*models.EnvResource, 0, len(cms)) + for _, cm := range cms { + resourceList = append(resourceList, &models.EnvResource{ + ID: primitive.ObjectID{}, + Type: "ConfigMap", + ProductName: cm.ProductName, + CreateTime: cm.CreateTime, + UpdateUserName: cm.UpdateUserName, + Namespace: cm.Namespace, + EnvName: cm.EnvName, + Name: cm.Name, + YamlData: cm.YamlData, + DeletedAt: 0, + }) + } + return resourceList +} + +func ConvertIngToResource(ings []*models.EnvIngress) []*models.EnvResource { + resourceList := make([]*models.EnvResource, 0, len(ings)) + for _, ingress := range ings { + resourceList = append(resourceList, &models.EnvResource{ + ID: primitive.ObjectID{}, + Type: "Ingress", + ProductName: ingress.ProductName, + CreateTime: ingress.CreateTime, + UpdateUserName: ingress.UpdateUserName, + Namespace: ingress.Namespace, + EnvName: ingress.EnvName, + Status: ingress.Status, + Name: ingress.Name, + YamlData: ingress.YamlData, + DeletedAt: 0, + }) + } + return resourceList +} + +func ConvertSecretToResource(secrets []*models.EnvSecret) []*models.EnvResource { + resourceList := make([]*models.EnvResource, 0, len(secrets)) + for _, secret := range secrets { + resourceList = append(resourceList, &models.EnvResource{ + ID: primitive.ObjectID{}, + Type: "Secret", + ProductName: secret.ProductName, + CreateTime: secret.CreateTime, + UpdateUserName: secret.UpdateUserName, + Namespace: secret.Namespace, + EnvName: secret.EnvName, + Status: secret.Status, + Name: secret.Name, + YamlData: secret.YamlData, + DeletedAt: 0, + }) + } + return resourceList +} + +func ConvertPVCToResource(pvcs []*models.EnvPvc) []*models.EnvResource { + resourceList := make([]*models.EnvResource, 0, len(pvcs)) + for _, pvc := range pvcs { + resourceList = append(resourceList, &models.EnvResource{ + ID: primitive.ObjectID{}, + Type: "PVC", + ProductName: pvc.ProductName, + CreateTime: pvc.CreateTime, + UpdateUserName: pvc.UpdateUserName, + Namespace: pvc.Namespace, + EnvName: pvc.EnvName, + Status: pvc.Status, + Name: pvc.Name, + YamlData: pvc.YamlData, + DeletedAt: 0, + }) + } + return resourceList +} + +func RemoveEnvResource() error { + return internalmongodb.NewEnvResourceColl().Drop(context.TODO()) +} diff --git a/pkg/cli/upgradeassistant/internal/repository/models/env_configs.go b/pkg/cli/upgradeassistant/internal/repository/models/env_configs.go new file mode 100644 index 00000000..f3ae9785 --- /dev/null +++ b/pkg/cli/upgradeassistant/internal/repository/models/env_configs.go @@ -0,0 +1,100 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type EnvResource struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at" ` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvResource) TableName() string { + return "env_resource" +} + +type EnvConfigMap struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvConfigMap) TableName() string { + return "env_configmap" +} + +type EnvIngress struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvIngress) TableName() string { + return "env_ingress" +} + +type EnvPvc struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvPvc) TableName() string { + return "env_pvc" +} + +type EnvSecret struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + Status string `bson:"status" json:"status"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` +} + +func (EnvSecret) TableName() string { + return "env_secret" +} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go similarity index 38% rename from pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go rename to pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go index d22e0656..a2d1910d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/env_secret.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go @@ -18,140 +18,151 @@ package mongodb import ( "context" - "errors" - "time" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/cli/upgradeassistant/internal/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -type SecretColl struct { +type EnvResourceColl struct { *mongo.Collection + coll string +} + +func NewEnvResourceColl() *EnvResourceColl { + name := models.EnvResource{}.TableName() + return &EnvResourceColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} +func (c *EnvResourceColl) BatchInsert(resources []*models.EnvResource) error { + resList := make([]interface{}, 0, len(resources)) + for _, res := range resources { + resList = append(resList, res) + } + _, err := c.InsertMany(context.TODO(), resList) + return err +} + +type ConfigMapColl struct { + *mongo.Collection coll string } -func NewSecretColl() *SecretColl { - name := models.EnvSecret{}.TableName() - return &SecretColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +func NewConfigMapColl() *ConfigMapColl { + name := models.EnvConfigMap{}.TableName() + return &ConfigMapColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } -func (c *SecretColl) GetCollectionName() string { +func (c *ConfigMapColl) GetCollectionName() string { return c.coll } -func (c *SecretColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.D{ - bson.E{Key: "name", Value: 1}, - bson.E{Key: "create_time", Value: 1}, - bson.E{Key: "env_name", Value: 1}, - bson.E{Key: "product_name", Value: 1}, - }, - Options: options.Index().SetUnique(true), +func (c *ConfigMapColl) List() ([]*models.EnvConfigMap, error) { + query := bson.M{} + + var resp []*models.EnvConfigMap + ctx := context.Background() + opts := options.Find() + + cursor, err := c.Collection.Find(ctx, query, opts) + if err != nil { + return nil, err } - _, err := c.Indexes().CreateOne(ctx, mod) + err = cursor.All(ctx, &resp) + return resp, err +} - return err +type IngressColl struct { + *mongo.Collection + + coll string } -func (c *SecretColl) Create(args *models.EnvSecret, isCreateTime bool) error { - if isCreateTime { - args.CreateTime = time.Now().Unix() - } - _, err := c.InsertOne(context.TODO(), args) - return err +func NewIngressColl() *IngressColl { + name := models.EnvIngress{}.TableName() + return &IngressColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *IngressColl) GetCollectionName() string { + return c.coll } -func (c *SecretColl) List(opt *ListEnvCfgOption) ([]*models.EnvSecret, error) { +func (c *IngressColl) List() ([]*models.EnvIngress, error) { query := bson.M{} - if len(opt.ProductName) > 0 { - query["product_name"] = opt.ProductName - } - if len(opt.Namespace) > 0 { - query["namespace"] = opt.Namespace - } - if len(opt.EnvName) > 0 { - query["env_name"] = opt.EnvName - } - if len(opt.Name) > 0 { - query["name"] = opt.Name - } - var resp []*models.EnvSecret + var resp []*models.EnvIngress ctx := context.Background() opts := options.Find() - if opt.IsSort { - opts.SetSort(bson.D{{"create_time", -1}}) - } cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { return nil, err } err = cursor.All(ctx, &resp) + return resp, err +} + +type PvcColl struct { + *mongo.Collection + + coll string +} + +func NewPvcColl() *PvcColl { + name := models.EnvPvc{}.TableName() + return &PvcColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +} + +func (c *PvcColl) GetCollectionName() string { + return c.coll +} + +func (c *PvcColl) List() ([]*models.EnvPvc, error) { + query := bson.M{} + + var resp []*models.EnvPvc + ctx := context.Background() + opts := options.Find() + cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { return nil, err } - return resp, nil + + err = cursor.All(ctx, &resp) + return resp, err } -func (c *SecretColl) Update(id string, services []string) error { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return err - } +type SecretColl struct { + *mongo.Collection - query := bson.M{"_id": oid} - change := bson.M{"$set": bson.M{ - "services": services, - }} + coll string +} - _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) - return err +func NewSecretColl() *SecretColl { + name := models.EnvSecret{}.TableName() + return &SecretColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } -func (c *SecretColl) Find(opt *FindEnvCfgOption) (*models.EnvSecret, error) { - if opt == nil { - return nil, errors.New("FindEnvCfgOption cannot be nil") - } +func (c *SecretColl) GetCollectionName() string { + return c.coll +} + +func (c *SecretColl) List() ([]*models.EnvSecret, error) { query := bson.M{} - if len(opt.ProductName) > 0 { - query["product_name"] = opt.ProductName - } - if len(opt.Namespace) > 0 { - query["namespace"] = opt.Namespace - } - if len(opt.EnvName) > 0 { - query["env_name"] = opt.EnvName - } - if len(opt.Name) > 0 { - query["name"] = opt.Name - } - if len(opt.Id) > 0 { - oid, err := primitive.ObjectIDFromHex(opt.Id) - if err != nil { - return nil, err - } - query["_id"] = oid - } - opts := options.FindOne() - if len(opt.CreateTime) > 0 { - query["create_time"] = opt.CreateTime - } else { - opts.SetSort(bson.D{{"create_time", -1}}) - } - rs := &models.EnvSecret{} - err := c.FindOne(context.TODO(), query, opts).Decode(&rs) + var resp []*models.EnvSecret + ctx := context.Background() + opts := options.Find() + cursor, err := c.Collection.Find(ctx, query, opts) if err != nil { return nil, err } - return rs, err + + err = cursor.All(ctx, &resp) + return resp, err } diff --git a/pkg/microservice/aslan/core/common/repository/models/env_ingress.go b/pkg/microservice/aslan/core/common/repository/models/env_ingress.go deleted file mode 100644 index b532d9ec..00000000 --- a/pkg/microservice/aslan/core/common/repository/models/env_ingress.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package models - -import "go.mongodb.org/mongo-driver/bson/primitive" - -type EnvIngress struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateUserName string `bson:"update_user_name" json:"update_user_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Status string `bson:"status" json:"status"` - EnvName string `bson:"env_name" json:"env_name"` - Name string `bson:"name" json:"name"` - YamlData string `bson:"yaml_data" json:"yaml_data"` -} - -func (EnvIngress) TableName() string { - return "env_ingress" -} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_pvc.go b/pkg/microservice/aslan/core/common/repository/models/env_pvc.go deleted file mode 100644 index f5a962c8..00000000 --- a/pkg/microservice/aslan/core/common/repository/models/env_pvc.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package models - -import "go.mongodb.org/mongo-driver/bson/primitive" - -type EnvPvc struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateUserName string `bson:"update_user_name" json:"update_user_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Status string `bson:"status" json:"status"` - EnvName string `bson:"env_name" json:"env_name"` - Name string `bson:"name" json:"name"` - YamlData string `bson:"yaml_data" json:"yaml_data"` -} - -func (EnvPvc) TableName() string { - return "env_pvc" -} diff --git a/pkg/microservice/aslan/core/common/repository/models/env_configmap.go b/pkg/microservice/aslan/core/common/repository/models/env_resource.go similarity index 72% rename from pkg/microservice/aslan/core/common/repository/models/env_configmap.go rename to pkg/microservice/aslan/core/common/repository/models/env_resource.go index 48ab7779..bc71ce7e 100644 --- a/pkg/microservice/aslan/core/common/repository/models/env_configmap.go +++ b/pkg/microservice/aslan/core/common/repository/models/env_resource.go @@ -16,19 +16,25 @@ limitations under the License. package models -import "go.mongodb.org/mongo-driver/bson/primitive" +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) -type EnvConfigMap struct { +type EnvResource struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` ProductName string `bson:"product_name" json:"product_name"` CreateTime int64 `bson:"create_time" json:"create_time"` UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at" ` Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` EnvName string `bson:"env_name" json:"env_name"` Name string `bson:"name" json:"name"` YamlData string `bson:"yaml_data" json:"yaml_data"` + SourceDetail *CreateFromRepo `bson:"source_detail" json:"source_detail"` + AutoSync bool `bson:"auto_sync" json:"auto_sync"` } -func (EnvConfigMap) TableName() string { - return "env_configmap" +func (EnvResource) TableName() string { + return "env_resource" } diff --git a/pkg/microservice/aslan/core/common/repository/models/env_secret.go b/pkg/microservice/aslan/core/common/repository/models/env_secret.go deleted file mode 100644 index a91eedfc..00000000 --- a/pkg/microservice/aslan/core/common/repository/models/env_secret.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package models - -import "go.mongodb.org/mongo-driver/bson/primitive" - -type EnvSecret struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - ProductName string `bson:"product_name" json:"product_name"` - CreateTime int64 `bson:"create_time" json:"create_time"` - UpdateUserName string `bson:"update_user_name" json:"update_user_name"` - Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` - Status string `bson:"status" json:"status"` - EnvName string `bson:"env_name" json:"env_name"` - Name string `bson:"name" json:"name"` - YamlData string `bson:"yaml_data" json:"yaml_data"` -} - -func (EnvSecret) TableName() string { - return "env_secret" -} diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 5519b316..3781a797 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -19,6 +19,7 @@ package models import ( "go.mongodb.org/mongo-driver/bson/primitive" + "github.com/koderover/zadig/pkg/microservice/aslan/config" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" ) @@ -58,8 +59,24 @@ type Product struct { IsForkedProduct bool `bson:"-" json:"-"` // New Since v1.11.0. - ShareEnv ProductShareEnv `bson:"share_env" json:"share_env"` - EnvConfigYamls []string `bson:"env_config_yamls,omitempty" json:"env_config_yamls,omitempty"` + ShareEnv ProductShareEnv `bson:"share_env" json:"share_env"` + + // New Since v1.13.0. + EnvConfigs []*CreateUpdateCommonEnvCfgArgs `bson:"-" json:"env_configs,omitempty"` +} + +type CreateUpdateCommonEnvCfgArgs struct { + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + ServiceName string `json:"service_name"` + Name string `json:"name"` + YamlData string `json:"yaml_data"` + RestartAssociatedSvc bool `json:"restart_associated_svc"` + CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type"` + Services []string `json:"services"` + GitRepoConfig *templatemodels.GitRepoConfig `json:"git_repo_config"` + SourceDetail *CreateFromRepo `json:"-"` + AutoSync bool `json:"auto_sync"` } type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/service.go b/pkg/microservice/aslan/core/common/repository/models/service.go index 0bcdd757..8a154159 100644 --- a/pkg/microservice/aslan/core/common/repository/models/service.go +++ b/pkg/microservice/aslan/core/common/repository/models/service.go @@ -75,7 +75,7 @@ type Service struct { } type CreateFromRepo struct { - GitRepoConfig *templatemodels.GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` + GitRepoConfig *templatemodels.GitRepoConfig `bson:"git_repo_config,omitempty" json:"git_repo_config,omitempty"` LoadPath string `bson:"load_path,omitempty" json:"load_path,omitempty"` } diff --git a/pkg/microservice/aslan/core/common/repository/models/template/product.go b/pkg/microservice/aslan/core/common/repository/models/template/product.go index 075a7694..fbd7d047 100644 --- a/pkg/microservice/aslan/core/common/repository/models/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/template/product.go @@ -97,11 +97,12 @@ type EnvRenderKV struct { } type GitRepoConfig struct { - CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id"` - Owner string `bson:"owner,omitempty" json:"owner"` - Repo string `bson:"repo,omitempty" json:"repo"` - Branch string `bson:"branch,omitempty" json:"branch"` - Namespace string `bson:"namespace,omitempty" json:"namespace"` // records the actual namespace of repo, used to generate correct project name + CodehostID int `bson:"codehost_id,omitempty" json:"codehost_id"` + Owner string `bson:"owner,omitempty" json:"owner"` + Repo string `bson:"repo,omitempty" json:"repo"` + Branch string `bson:"branch,omitempty" json:"branch"` + Namespace string `bson:"namespace,omitempty" json:"namespace"` // records the actual namespace of repo, used to generate correct project name + ValuesPaths []string `bson:"values_paths,omitempty" json:"values_paths,omitempty"` } func (grc *GitRepoConfig) GetNamespace() string { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go deleted file mode 100644 index 9707d5d7..00000000 --- a/pkg/microservice/aslan/core/common/repository/mongodb/env_ingress.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package mongodb - -import ( - "context" - "time" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type IngressColl struct { - *mongo.Collection - - coll string -} - -func NewIngressColl() *IngressColl { - name := models.EnvIngress{}.TableName() - return &IngressColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} -} - -func (c *IngressColl) GetCollectionName() string { - return c.coll -} - -func (c *IngressColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.D{ - bson.E{Key: "name", Value: 1}, - bson.E{Key: "create_time", Value: 1}, - bson.E{Key: "env_name", Value: 1}, - bson.E{Key: "product_name", Value: 1}, - }, - Options: options.Index().SetUnique(true), - } - - _, err := c.Indexes().CreateOne(ctx, mod) - - return err -} - -func (c *IngressColl) Create(args *models.EnvIngress, isCreateTime bool) error { - if isCreateTime { - args.CreateTime = time.Now().Unix() - } - _, err := c.InsertOne(context.TODO(), args) - return err -} - -func (c *IngressColl) List(opt *ListEnvCfgOption) ([]*models.EnvIngress, error) { - query := bson.M{} - if len(opt.ProductName) > 0 { - query["product_name"] = opt.ProductName - } - if len(opt.Namespace) > 0 { - query["namespace"] = opt.Namespace - } - if len(opt.EnvName) > 0 { - query["env_name"] = opt.EnvName - } - if len(opt.Name) > 0 { - query["name"] = opt.Name - } - - var resp []*models.EnvIngress - ctx := context.Background() - opts := options.Find() - if opt.IsSort { - opts.SetSort(bson.D{{"create_time", -1}}) - } - cursor, err := c.Collection.Find(ctx, query, opts) - if err != nil { - return nil, err - } - - err = cursor.All(ctx, &resp) - if err != nil { - return nil, err - } - return resp, nil -} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go deleted file mode 100644 index 15409272..00000000 --- a/pkg/microservice/aslan/core/common/repository/mongodb/env_pvc.go +++ /dev/null @@ -1,157 +0,0 @@ -/* -Copyright 2022 The KodeRover Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package mongodb - -import ( - "context" - "errors" - "time" - - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -type PvcColl struct { - *mongo.Collection - - coll string -} - -func NewPvcColl() *PvcColl { - name := models.EnvPvc{}.TableName() - return &PvcColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} -} - -func (c *PvcColl) GetCollectionName() string { - return c.coll -} - -func (c *PvcColl) EnsureIndex(ctx context.Context) error { - mod := mongo.IndexModel{ - Keys: bson.D{ - bson.E{Key: "name", Value: 1}, - bson.E{Key: "create_time", Value: 1}, - bson.E{Key: "env_name", Value: 1}, - bson.E{Key: "product_name", Value: 1}, - }, - Options: options.Index().SetUnique(true), - } - - _, err := c.Indexes().CreateOne(ctx, mod) - - return err -} - -func (c *PvcColl) Create(args *models.EnvPvc, isCreateTime bool) error { - if isCreateTime { - args.CreateTime = time.Now().Unix() - } - _, err := c.InsertOne(context.TODO(), args) - return err -} - -func (c *PvcColl) List(opt *ListEnvCfgOption) ([]*models.EnvPvc, error) { - query := bson.M{} - if len(opt.ProductName) > 0 { - query["product_name"] = opt.ProductName - } - if len(opt.Namespace) > 0 { - query["namespace"] = opt.Namespace - } - if len(opt.EnvName) > 0 { - query["env_name"] = opt.EnvName - } - if len(opt.Name) > 0 { - query["name"] = opt.Name - } - - var resp []*models.EnvPvc - ctx := context.Background() - opts := options.Find() - if opt.IsSort { - opts.SetSort(bson.D{{"create_time", -1}}) - } - cursor, err := c.Collection.Find(ctx, query, opts) - if err != nil { - return nil, err - } - - err = cursor.All(ctx, &resp) - if err != nil { - return nil, err - } - return resp, nil -} - -func (c *PvcColl) Update(id string, services []string) error { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return err - } - - query := bson.M{"_id": oid} - change := bson.M{"$set": bson.M{ - "services": services, - }} - - _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) - return err -} - -func (c *PvcColl) Find(opt *FindEnvCfgOption) (*models.EnvPvc, error) { - if opt == nil { - return nil, errors.New("FindEnvCfgOption cannot be nil") - } - query := bson.M{} - if len(opt.ProductName) > 0 { - query["product_name"] = opt.ProductName - } - if len(opt.Namespace) > 0 { - query["namespace"] = opt.Namespace - } - if len(opt.EnvName) > 0 { - query["env_name"] = opt.EnvName - } - if len(opt.Name) > 0 { - query["name"] = opt.Name - } - if len(opt.Id) > 0 { - oid, err := primitive.ObjectIDFromHex(opt.Id) - if err != nil { - return nil, err - } - query["_id"] = oid - } - opts := options.FindOne() - if len(opt.CreateTime) > 0 { - query["create_time"] = opt.CreateTime - } else { - opts.SetSort(bson.D{{"create_time", -1}}) - } - - rs := &models.EnvPvc{} - err := c.FindOne(context.TODO(), query, opts).Decode(&rs) - if err != nil { - return nil, err - } - return rs, err -} diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go similarity index 68% rename from pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go rename to pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go index e2f547a6..77d9e5b4 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/env_configmap.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go @@ -21,36 +21,38 @@ import ( "errors" "time" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - mongotool "github.com/koderover/zadig/pkg/tool/mongo" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" ) -type ConfigMapColl struct { +type EnvResourceColl struct { *mongo.Collection coll string } -func NewConfigMapColl() *ConfigMapColl { - name := models.EnvConfigMap{}.TableName() - return &ConfigMapColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} +func NewEnvResourceColl() *EnvResourceColl { + name := models.EnvResource{}.TableName() + return &EnvResourceColl{Collection: mongotool.Database(config.MongoDatabase()).Collection(name), coll: name} } -func (c *ConfigMapColl) GetCollectionName() string { +func (c *EnvResourceColl) GetCollectionName() string { return c.coll } -func (c *ConfigMapColl) EnsureIndex(ctx context.Context) error { +func (c *EnvResourceColl) EnsureIndex(ctx context.Context) error { mod := mongo.IndexModel{ Keys: bson.D{ bson.E{Key: "name", Value: 1}, bson.E{Key: "create_time", Value: 1}, bson.E{Key: "env_name", Value: 1}, + bson.E{Key: "type", Value: 1}, bson.E{Key: "product_name", Value: 1}, }, Options: options.Index().SetUnique(true), @@ -61,23 +63,27 @@ func (c *ConfigMapColl) EnsureIndex(ctx context.Context) error { return err } -func (c *ConfigMapColl) Create(args *models.EnvConfigMap, isCreateTime bool) error { - if isCreateTime { +func (c *EnvResourceColl) Create(args *models.EnvResource) error { + if args.CreateTime == 0 { args.CreateTime = time.Now().Unix() } _, err := c.InsertOne(context.TODO(), args) return err } -type ListEnvCfgOption struct { - IsSort bool - ProductName string - Namespace string - EnvName string - Name string +type QueryEnvResourceOption struct { + Id string + CreateTime string + IsSort bool + ProductName string + Namespace string + EnvName string + Name string + Type string + IgnoreNotFound bool } -func (c *ConfigMapColl) List(opt *ListEnvCfgOption) ([]*models.EnvConfigMap, error) { +func (c *EnvResourceColl) List(opt *QueryEnvResourceOption) ([]*models.EnvResource, error) { query := bson.M{} if len(opt.ProductName) > 0 { query["product_name"] = opt.ProductName @@ -91,8 +97,11 @@ func (c *ConfigMapColl) List(opt *ListEnvCfgOption) ([]*models.EnvConfigMap, err if len(opt.Name) > 0 { query["name"] = opt.Name } + if len(opt.Type) > 0 { + query["type"] = opt.Type + } - var resp []*models.EnvConfigMap + var resp []*models.EnvResource ctx := context.Background() opts := options.Find() if opt.IsSort { @@ -111,31 +120,7 @@ func (c *ConfigMapColl) List(opt *ListEnvCfgOption) ([]*models.EnvConfigMap, err return resp, nil } -func (c *ConfigMapColl) Update(id string, services []string) error { - oid, err := primitive.ObjectIDFromHex(id) - if err != nil { - return err - } - - query := bson.M{"_id": oid} - change := bson.M{"$set": bson.M{ - "services": services, - }} - - _, err = c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) - return err -} - -type FindEnvCfgOption struct { - Id string - CreateTime string - ProductName string - Namespace string - EnvName string - Name string -} - -func (c *ConfigMapColl) Find(opt *FindEnvCfgOption) (*models.EnvConfigMap, error) { +func (c *EnvResourceColl) Find(opt *QueryEnvResourceOption) (*models.EnvResource, error) { if opt == nil { return nil, errors.New("FindEnvCfgOption cannot be nil") } @@ -152,6 +137,9 @@ func (c *ConfigMapColl) Find(opt *FindEnvCfgOption) (*models.EnvConfigMap, error if len(opt.Name) > 0 { query["name"] = opt.Name } + if len(opt.Type) > 0 { + query["type"] = opt.Type + } if len(opt.Id) > 0 { oid, err := primitive.ObjectIDFromHex(opt.Id) if err != nil { @@ -166,10 +154,23 @@ func (c *ConfigMapColl) Find(opt *FindEnvCfgOption) (*models.EnvConfigMap, error opts.SetSort(bson.D{{"create_time", -1}}) } - rs := &models.EnvConfigMap{} + rs := &models.EnvResource{} err := c.FindOne(context.TODO(), query, opts).Decode(&rs) if err != nil { + if err == mongo.ErrNoDocuments && opt.IgnoreNotFound { + return nil, nil + } return nil, err } return rs, err } + +func (c *EnvResourceColl) Delete(oid primitive.ObjectID) error { + query := bson.M{} + query["_id"] = oid + change := bson.M{"$set": bson.M{ + "deleted_at": time.Now().Unix(), + }} + _, err := c.UpdateOne(context.TODO(), query, change) + return err +} diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index 3e0af5ee..fa39d0bb 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -21,9 +21,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "strconv" "github.com/gin-gonic/gin" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" @@ -51,7 +54,7 @@ func CreateCommonEnvCfg(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(service.CreateCommonEnvCfgArgs) + args := new(models.CreateUpdateCommonEnvCfgArgs) data, err := c.GetRawData() if err != nil { log.Errorf("CreateCommonEnvCfg c.GetRawData() err : %v", err) @@ -76,14 +79,14 @@ func CreateCommonEnvCfg(c *gin.Context) { } args.EnvName = c.Param("envName") args.ProductName = c.Query("projectName") - ctx.Err = service.CreateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) + ctx.Err = service.CreateCommonEnvCfg(args, ctx.UserName, ctx.Logger) } func UpdateCommonEnvCfg(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - args := new(service.UpdateCommonEnvCfgArgs) + args := new(models.CreateUpdateCommonEnvCfgArgs) data, err := c.GetRawData() if err != nil { log.Errorf("UpdateCommonEnvCfg c.GetRawData() err : %v", err) @@ -99,13 +102,21 @@ func UpdateCommonEnvCfg(c *gin.Context) { return } if len(args.YamlData) == 0 { - ctx.Err = e.ErrInvalidParam + ctx.Err = e.ErrInvalidParam.AddDesc("yaml info can't be nil") return } args.EnvName = c.Param("envName") args.ProductName = c.Query("projectName") + isRollBack := false + if len(c.Query("rollback")) > 0 { + isRollBack, err = strconv.ParseBool(c.Query("rollback")) + if err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + } - ctx.Err = service.UpdateCommonEnvCfg(args, ctx.UserName, ctx.UserID, ctx.Logger) + ctx.Err = service.UpdateCommonEnvCfg(args, ctx.UserName, isRollBack, ctx.Logger) } func ListCommonEnvCfgHistory(c *gin.Context) { @@ -118,5 +129,5 @@ func ListCommonEnvCfgHistory(c *gin.Context) { args.CommonEnvCfgType = config.CommonEnvCfgType(c.Query("commonEnvCfgType")) args.Name = c.Param("objectName") - ctx.Resp, ctx.Err = service.ListCommonEnvCfgHistory(args, ctx.Logger) + ctx.Resp, ctx.Err = service.ListEnvResourceHistory(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index a9101c64..d77d33c6 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -19,6 +19,7 @@ package service import ( "encoding/json" "fmt" + "time" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" @@ -30,6 +31,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" @@ -44,6 +46,55 @@ import ( type ResourceWithLabel interface { GetLabels() map[string]string SetLabels(labels map[string]string) + SetNamespace(ns string) +} + +type ResourceResponseBase struct { + Name string `json:"-"` + Type config.CommonEnvCfgType `json:"-"` + EnvName string `json:"-"` + ProjectName string `json:"-"` + YamlData string `json:"yaml_data"` + UpdateUserName string `json:"update_username"` + CreateTime time.Time `json:"create_time"` + Services []string `json:"services"` + SourceDetail *models.CreateFromRepo `json:"source_detail"` + AutoSync bool `json:"auto_sync"` +} + +func resourceCreatedByZadig(resource ResourceWithLabel, projectName string) bool { + resourceLabels := resource.GetLabels() + if resourceLabels == nil { + return false + } + if productName, ok := resourceLabels[setting.ProductLabel]; !ok || productName != projectName { + return false + } + return true +} + +func (resp *ResourceResponseBase) setSourceDetailData(resource ResourceWithLabel) { + // make sure the resource is created by zadig + if !resourceCreatedByZadig(resource, resp.ProjectName) { + return + } + + envResource, err := commonrepo.NewEnvResourceColl().Find(&commonrepo.QueryEnvResourceOption{ + Name: resp.Name, + Type: string(resp.Type), + EnvName: resp.EnvName, + ProductName: resp.ProjectName, + IgnoreNotFound: true, + }) + if err != nil { + log.Warnf("failed to f get %v with name : %s, err: %s", string(resp.Type), resp.Name, err) + return + } + if envResource != nil { + resp.SourceDetail = envResource.SourceDetail + resp.AutoSync = envResource.AutoSync + resp.UpdateUserName = envResource.UpdateUserName + } } func DeleteCommonEnvCfg(envName, productName, objectName string, commonEnvCfgType config.CommonEnvCfgType, log *zap.SugaredLogger) error { @@ -81,24 +132,33 @@ func DeleteCommonEnvCfg(envName, productName, objectName string, commonEnvCfgTyp log.Error(err) return e.ErrDeleteResource.AddDesc(err.Error()) } - return nil -} -type CreateCommonEnvCfgArgs struct { - EnvName string `json:"env_name"` - ProductName string `json:"product_name"` - YamlData string `json:"yaml_data"` - CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type"` + resourceData, err := commonrepo.NewEnvResourceColl().Find(&commonrepo.QueryEnvResourceOption{ + ProductName: productName, + EnvName: product.EnvName, + Name: objectName, + Type: string(commonEnvCfgType), + IgnoreNotFound: true, + }) + if err != nil { + return err + } + + if resourceData != nil { + return commonrepo.NewEnvResourceColl().Delete(resourceData.ID) + } + return nil } -// ensureLabel ensure label 's-product' exists in particular resource(secret/configmap) deployed by zadig -func ensureLabel(res ResourceWithLabel, projectName string) (string, error) { +// ensureLabelAndNs ensure label 's-product' exists in particular resource(secret/configmap) deployed by zadig +func ensureLabelAndNs(res ResourceWithLabel, namespace, projectName string) (string, error) { resLabels := res.GetLabels() if resLabels == nil { resLabels = make(map[string]string) } resLabels[setting.ProductLabel] = projectName res.SetLabels(resLabels) + res.SetNamespace(namespace) jsonBytes, err := json.Marshal(res) if err != nil { @@ -108,7 +168,26 @@ func ensureLabel(res ResourceWithLabel, projectName string) (string, error) { return string(yamlBytes), err } -func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { +func geneSourceDetail(gitRepoConfig *templatemodels.GitRepoConfig) *models.CreateFromRepo { + if gitRepoConfig == nil { + return nil + } + ret := &models.CreateFromRepo{ + GitRepoConfig: &templatemodels.GitRepoConfig{ + CodehostID: gitRepoConfig.CodehostID, + Owner: gitRepoConfig.Owner, + Repo: gitRepoConfig.Repo, + Branch: gitRepoConfig.Branch, + Namespace: gitRepoConfig.GetNamespace(), + }, + } + if len(gitRepoConfig.ValuesPaths) > 0 { + ret.LoadPath = gitRepoConfig.ValuesPaths[0] + } + return ret +} + +func CreateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName string, log *zap.SugaredLogger) error { js, err := yaml.YAMLToJSON([]byte(args.YamlData)) if err != nil { return e.ErrUpdateResource.AddErr(err) @@ -144,6 +223,16 @@ func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, l return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) } + envResource := &models.EnvResource{ + ProductName: args.ProductName, + UpdateUserName: userName, + EnvName: args.EnvName, + Namespace: product.Namespace, + Type: string(args.CommonEnvCfgType), + SourceDetail: geneSourceDetail(args.GitRepoConfig), + AutoSync: args.AutoSync, + } + switch args.CommonEnvCfgType { case config.CommonEnvCfgTypeConfigMap: cm := &corev1.ConfigMap{} @@ -151,9 +240,7 @@ func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, l if err != nil { return e.ErrUpdateResource.AddErr(err) } - cm.Namespace = product.Namespace - - yamlData, err := ensureLabel(cm, args.ProductName) + yamlData, err := ensureLabelAndNs(cm, product.Namespace, args.ProductName) if err != nil { return e.ErrUpdateResource.AddErr(err) } @@ -162,26 +249,16 @@ func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, l log.Error(err) return e.ErrUpdateResource.AddErr(err) } + envResource.Name = cm.Name + envResource.YamlData = yamlData - envCM := &models.EnvConfigMap{ - ProductName: args.ProductName, - UpdateUserName: userName, - EnvName: args.EnvName, - Name: cm.Name, - YamlData: yamlData, - } - if err = commonrepo.NewConfigMapColl().Create(envCM, true); err != nil { - return e.ErrUpdateResource.AddDesc(err.Error()) - } case config.CommonEnvCfgTypeSecret: secret := &corev1.Secret{} err = json.Unmarshal(js, secret) if err != nil { return e.ErrUpdateResource.AddErr(err) } - secret.Namespace = product.Namespace - - yamlData, err := ensureLabel(secret, args.ProductName) + yamlData, err := ensureLabelAndNs(secret, product.Namespace, args.ProductName) if err != nil { return e.ErrUpdateResource.AddErr(err) } @@ -190,66 +267,71 @@ func CreateCommonEnvCfg(args *CreateCommonEnvCfgArgs, userName, userID string, l log.Error(err) return e.ErrUpdateResource.AddDesc(err.Error()) } + envResource.Name = secret.Name + envResource.YamlData = yamlData - envSecret := &models.EnvSecret{ - ProductName: args.ProductName, - UpdateUserName: userName, - EnvName: args.EnvName, - Name: secret.Name, - YamlData: yamlData, - } - if err = commonrepo.NewSecretColl().Create(envSecret, true); err != nil { - return e.ErrUpdateResource.AddDesc(err.Error()) - } case config.CommonEnvCfgTypeIngress: - uia := &UpdateCommonEnvCfgArgs{ - EnvName: args.EnvName, - ProductName: args.ProductName, - YamlData: args.YamlData, + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(args.YamlData)) + if err != nil { + log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %v", args.YamlData, err) + return e.ErrUpdateResource.AddDesc("ingress Yaml Name is incorrect") } - if err = UpdateOrCreateIngress(uia, userName, userID, true, log); err != nil { - log.Error(err) + + yamlData, err := ensureLabelAndNs(u, product.Namespace, args.ProductName) + if err != nil { return e.ErrUpdateResource.AddErr(err) } + + err = updater.UpdateOrCreateUnstructured(u, kubeClient) + if err != nil { + log.Errorf("Failed to UpdateOrCreateIngress %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err) + return e.ErrUpdateResource.AddErr(fmt.Errorf("Failed to UpdateOrCreateIngress %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err)) + } + envResource.Name = u.GetName() + envResource.YamlData = yamlData + case config.CommonEnvCfgTypePvc: pvc := &corev1.PersistentVolumeClaim{} err = json.Unmarshal(js, pvc) if err != nil { return e.ErrUpdateResource.AddErr(err) } - pvc.Namespace = product.Namespace + + yamlData, err := ensureLabelAndNs(pvc, product.Namespace, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + if err := updater.CreatePvc(product.Namespace, pvc, clientset); err != nil { log.Error(err) return e.ErrUpdateResource.AddErr(err) } - envPvc := &models.EnvPvc{ - ProductName: args.ProductName, - UpdateUserName: userName, - EnvName: args.EnvName, - Name: pvc.Name, - YamlData: args.YamlData, - } - if err = commonrepo.NewPvcColl().Create(envPvc, true); err != nil { - return e.ErrUpdateResource.AddDesc(err.Error()) - } + envResource.Name = pvc.Name + envResource.YamlData = yamlData + default: return e.ErrUpdateResource.AddDesc(fmt.Sprintf("%s is not support create", args.CommonEnvCfgType)) } + + if err = commonrepo.NewEnvResourceColl().Create(envResource); err != nil { + return e.ErrUpdateResource.AddDesc(err.Error()) + } + return nil } -type UpdateCommonEnvCfgArgs struct { - EnvName string `json:"env_name"` - ProductName string `json:"product_name"` - ServiceName string `json:"service_name"` - Name string `json:"name"` - YamlData string `json:"yaml_data"` - RestartAssociatedSvc bool `json:"restart_associated_svc"` - CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type"` - Services []string `json:"services"` +func getLatestEnvResource(name, resType, envName, productName string) (*models.EnvResource, error) { + return commonrepo.NewEnvResourceColl().Find( + &commonrepo.QueryEnvResourceOption{ + Name: name, + ProductName: productName, + Type: resType, + EnvName: envName, + IsSort: true, + }) } -func UpdateCommonEnvCfg(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { +func UpdateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName string, isRollback bool, log *zap.SugaredLogger) error { var err error u, err := serializer.NewDecoder().YamlToUnstructured([]byte(args.YamlData)) if err != nil { @@ -263,15 +345,30 @@ func UpdateCommonEnvCfg(args *UpdateCommonEnvCfgArgs, userName, userID string, l return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) } + // when rollback env configs, git config should not change + if isRollback { + latestResource, err := getLatestEnvResource(args.Name, string(args.CommonEnvCfgType), args.EnvName, args.ProductName) + if err != nil { + return err + } + if latestResource == nil { + return fmt.Errorf("failed to find env resource %s:%s", args.Name, string(args.CommonEnvCfgType)) + } + args.AutoSync = latestResource.AutoSync + args.SourceDetail = latestResource.SourceDetail + } else { + args.SourceDetail = geneSourceDetail(args.GitRepoConfig) + } + switch args.CommonEnvCfgType { case config.CommonEnvCfgTypeConfigMap: - err = UpdateConfigMap(args, userName, userID, log) + err = UpdateConfigMap(args, userName, log) case config.CommonEnvCfgTypeSecret: - err = UpdateSecret(args, userName, userID, log) + err = UpdateSecret(args, userName, log) case config.CommonEnvCfgTypeIngress: - err = UpdateOrCreateIngress(args, userName, userID, false, log) + err = UpdateOrCreateIngress(args, userName, false, log) case config.CommonEnvCfgTypePvc: - err = UpdatePvc(args, userName, userID, log) + err = UpdatePvc(args, userName, log) default: return e.ErrUpdateResource.AddDesc(fmt.Sprintf("%s is not support update", args.CommonEnvCfgType)) } @@ -283,113 +380,102 @@ func UpdateCommonEnvCfg(args *UpdateCommonEnvCfgArgs, userName, userID string, l } type ListCommonEnvCfgHistoryArgs struct { - EnvName string `json:"envName"` - ProductName string `json:"productName"` + EnvName string + ProductName string Name string - CommonEnvCfgType config.CommonEnvCfgType `json:"commonEnvCfgType"` + CommonEnvCfgType config.CommonEnvCfgType } + type ListCommonEnvCfgHistoryRes struct { - ID primitive.ObjectID `json:"id,omitempty"` - ProductName string `json:"product_name"` - CreateTime int64 `json:"create_time"` - UpdateUserName string `json:"update_user_name"` - Namespace string `json:"namespace,omitempty"` - EnvName string `json:"env_name"` - Name string `json:"name"` - YamlData string `json:"yaml_data"` + ID primitive.ObjectID `json:"id,omitempty"` + ProductName string `json:"product_name"` + CreateTime int64 `json:"create_time"` + UpdateUserName string `json:"update_user_name"` + Namespace string `json:"namespace,omitempty"` + EnvName string `json:"env_name"` + Name string `json:"name"` + YamlData string `json:"yaml_data"` + SourceDetail *models.CreateFromRepo `json:"source_detail"` +} + +func GetResourceByCfgType(namespace, name string, cfgType config.CommonEnvCfgType, client client.Client, clientset *kubernetes.Clientset) (ResourceWithLabel, error) { + switch cfgType { + case config.CommonEnvCfgTypeConfigMap: + cm, _, err := getter.GetConfigMap(namespace, name, client) + return cm, err + case config.CommonEnvCfgTypeSecret: + secret, _, err := getter.GetSecret(namespace, name, client) + return secret, err + case config.CommonEnvCfgTypePvc: + pvc, _, err := getter.GetPvc(namespace, name, client) + return pvc, err + case config.CommonEnvCfgTypeIngress: + ingress, _, err := getter.GetUnstructuredIngress(namespace, name, client, clientset) + return ingress, err + } + return nil, fmt.Errorf("unrecognized env config type: %s", cfgType) } -func ListCommonEnvCfgHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredLogger) ([]*ListCommonEnvCfgHistoryRes, error) { - opts := &commonrepo.ListEnvCfgOption{ +func ListEnvResourceHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredLogger) ([]*ListCommonEnvCfgHistoryRes, error) { + if len(args.CommonEnvCfgType) == 0 { + return nil, e.ErrListResources.AddDesc("env resource type can't be nil") + } + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + kubeClient, err := kubeclient.GetKubeClient(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + clientset, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), product.ClusterID) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + + envResource, err := GetResourceByCfgType(product.Namespace, args.Name, args.CommonEnvCfgType, kubeClient, clientset) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + if envResource == nil { + return nil, e.ErrListResources.AddDesc(fmt.Sprintf("failed to get resource %s:%s from namespace: %s", args.Name, args.CommonEnvCfgType, product.Namespace)) + } + + // only list history for resources created from environment config management page + if !resourceCreatedByZadig(envResource, product.ProductName) { + return nil, nil + } + + opts := &commonrepo.QueryEnvResourceOption{ IsSort: true, ProductName: args.ProductName, EnvName: args.EnvName, Name: args.Name, + Type: string(args.CommonEnvCfgType), } - switch args.CommonEnvCfgType { - case config.CommonEnvCfgTypeConfigMap: - res, err := commonrepo.NewConfigMapColl().List(opts) - if err != nil { - return nil, e.ErrListResources.AddErr(err) - } - var listRes []*ListCommonEnvCfgHistoryRes - for _, resElem := range res { - listElem := &ListCommonEnvCfgHistoryRes{ - ID: resElem.ID, - ProductName: resElem.ProductName, - CreateTime: resElem.CreateTime, - UpdateUserName: resElem.UpdateUserName, - Namespace: resElem.Namespace, - EnvName: resElem.EnvName, - Name: resElem.Name, - YamlData: resElem.YamlData, - } - listRes = append(listRes, listElem) - } - return listRes, nil - case config.CommonEnvCfgTypeSecret: - res, err := commonrepo.NewSecretColl().List(opts) - if err != nil { - return nil, e.ErrListResources.AddErr(err) - } - var listRes []*ListCommonEnvCfgHistoryRes - for _, resElem := range res { - listElem := &ListCommonEnvCfgHistoryRes{ - ID: resElem.ID, - ProductName: resElem.ProductName, - CreateTime: resElem.CreateTime, - UpdateUserName: resElem.UpdateUserName, - Namespace: resElem.Namespace, - EnvName: resElem.EnvName, - Name: resElem.Name, - YamlData: resElem.YamlData, - } - listRes = append(listRes, listElem) - } - return listRes, nil - case config.CommonEnvCfgTypeIngress: - res, err := commonrepo.NewIngressColl().List(opts) - if err != nil { - return nil, e.ErrListResources.AddErr(err) - } - var listRes []*ListCommonEnvCfgHistoryRes - for _, resElem := range res { - listElem := &ListCommonEnvCfgHistoryRes{ - ID: resElem.ID, - ProductName: resElem.ProductName, - CreateTime: resElem.CreateTime, - UpdateUserName: resElem.UpdateUserName, - Namespace: resElem.Namespace, - EnvName: resElem.EnvName, - Name: resElem.Name, - YamlData: resElem.YamlData, - } - listRes = append(listRes, listElem) - } - return listRes, nil - case config.CommonEnvCfgTypePvc: - res, err := commonrepo.NewPvcColl().List(opts) - if err != nil { - return nil, e.ErrListResources.AddErr(err) - } - var listRes []*ListCommonEnvCfgHistoryRes - for _, resElem := range res { - listElem := &ListCommonEnvCfgHistoryRes{ - ID: resElem.ID, - ProductName: resElem.ProductName, - CreateTime: resElem.CreateTime, - UpdateUserName: resElem.UpdateUserName, - Namespace: resElem.Namespace, - EnvName: resElem.EnvName, - Name: resElem.Name, - YamlData: resElem.YamlData, - } - listRes = append(listRes, listElem) + res, err := commonrepo.NewEnvResourceColl().List(opts) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + var listRes []*ListCommonEnvCfgHistoryRes + for _, resElem := range res { + listElem := &ListCommonEnvCfgHistoryRes{ + ID: resElem.ID, + ProductName: resElem.ProductName, + CreateTime: resElem.CreateTime, + UpdateUserName: resElem.UpdateUserName, + EnvName: resElem.EnvName, + Name: resElem.Name, + YamlData: resElem.YamlData, + SourceDetail: resElem.SourceDetail, } - return listRes, nil - default: - return nil, e.ErrUpdateResource.AddDesc(fmt.Sprintf("%s is not support update", args.CommonEnvCfgType)) + listRes = append(listRes, listElem) } + return listRes, nil } func restartPod(name, productName, envName, namespace string, commonEnvCfgType config.CommonEnvCfgType, clientset *kubernetes.Clientset, kubcli client.Client) error { diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index 298509d6..e7b98199 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -80,19 +80,11 @@ type configMap struct { lastUpdateTime time.Time } -type configMapWithHistory struct { - Current *configMap `json:"current"` - HistoricalRevisions []*configMap `json:"historicalRevisions"` -} - type ListConfigMapRes struct { - CmName string `json:"cm_name"` - Immutable bool `json:"immutable"` - CmData map[string]string `json:"cm_data"` - YamlData string `json:"yaml_data"` - UpdateUserName string `json:"update_username"` - CreateTime time.Time `json:"create_time"` - Services []string `json:"services"` + *ResourceResponseBase + CmName string `json:"cm_name"` + Immutable bool `json:"immutable"` + CmData map[string]string `json:"cm_data"` } func generateConfigMapResponse(cm *corev1.ConfigMap) *configMap { @@ -109,7 +101,8 @@ func generateConfigMapResponse(cm *corev1.ConfigMap) *configMap { } func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListConfigMapRes, error) { - selector := labels.Set{setting.ProductLabel: args.ProductName}.AsSelector() + selector := labels.Set{}.AsSelector() + // Note. when listing configs from [配置管理] on workload page, service name will be passed as query condition if args.ServiceName != "" { selector = labels.Set{setting.ProductLabel: args.ProductName, setting.ServiceLabel: args.ServiceName}.AsSelector() } @@ -175,13 +168,21 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListCon immutable = *cm.Immutable } resElem := &ListConfigMapRes{ - CmName: cm.Name, - Immutable: immutable, - CmData: cm.Data, - YamlData: string(yamlData), - Services: tempSvcs, - CreateTime: cm.GetCreationTimestamp().Time, + CmName: cm.Name, + Immutable: immutable, + CmData: cm.Data, + ResourceResponseBase: &ResourceResponseBase{ + Name: cm.Name, + Type: config.CommonEnvCfgTypeConfigMap, + EnvName: args.EnvName, + ProjectName: args.ProductName, + YamlData: string(yamlData), + Services: tempSvcs, + CreateTime: cm.GetCreationTimestamp().Time, + }, } + resElem.setSourceDetailData(cm) + mutex.Lock() res = append(res, resElem) mutex.Unlock() @@ -206,7 +207,7 @@ func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListCon return res, nil } -func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { +func UpdateConfigMap(args *models.CreateUpdateCommonEnvCfgArgs, userName string, log *zap.SugaredLogger) error { js, err := yaml.YAMLToJSON([]byte(args.YamlData)) cm := &corev1.ConfigMap{} err = json.Unmarshal(js, cm) @@ -244,7 +245,7 @@ func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log return e.ErrUpdateConfigMap.AddErr(err) } - yamlData, err := ensureLabel(cm, args.ProductName) + yamlData, err := ensureLabelAndNs(cm, product.Namespace, args.ProductName) if err != nil { return e.ErrUpdateResource.AddErr(err) } @@ -253,14 +254,18 @@ func UpdateConfigMap(args *UpdateCommonEnvCfgArgs, userName, userID string, log log.Error(err) return e.ErrUpdateConfigMap.AddDesc(err.Error()) } - envcm := &models.EnvConfigMap{ + envCM := &models.EnvResource{ ProductName: args.ProductName, UpdateUserName: userName, EnvName: args.EnvName, + Namespace: product.Namespace, Name: cm.Name, YamlData: yamlData, + Type: string(config.CommonEnvCfgTypeConfigMap), + SourceDetail: args.SourceDetail, + AutoSync: args.AutoSync, } - if err := commonrepo.NewConfigMapColl().Create(envcm, true); err != nil { + if err := commonrepo.NewEnvResourceColl().Create(envCM); err != nil { return e.ErrUpdateConfigMap.AddDesc(err.Error()) } tplProduct, err := commontpl.NewProductColl().Find(args.ProductName) @@ -412,9 +417,9 @@ func cleanArchiveConfigMap(namespace string, ls map[string]string, kubeClient cl type MigrateHistoryConfigMapsRes struct { } -func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogger) ([]*models.EnvConfigMap, error) { +func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogger) ([]*models.EnvResource, error) { - res := make([]*models.EnvConfigMap, 0) + res := make([]*models.EnvResource, 0) products := make([]*models.Product, 0) var err error if productName != "" { @@ -448,11 +453,12 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge for _, cm := range cms { cmNames := strings.Split(cm.Name, "-bak-") cmName := cmNames[0] - envCm := &models.EnvConfigMap{ + envResource := &models.EnvResource{ ProductName: product.ProductName, EnvName: product.EnvName, Namespace: product.Namespace, Name: cmName, + Type: string(config.CommonEnvCfgTypeConfigMap), } cmLables := cm.GetLabels() if _, ok := cmLables[setting.UpdateTime]; ok { @@ -460,7 +466,7 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge if err != nil { return nil, e.ErrListResources.AddErr(err) } - envCm.CreateTime = tm.Unix() + envResource.CreateTime = tm.Unix() } delete(cmLables, setting.UpdateTime) @@ -471,17 +477,17 @@ func MigrateHistoryConfigMaps(envName, productName string, log *zap.SugaredLogge cm.SetAnnotations(make(map[string]string)) cm.SetName(cmName) - yamlData, err := yaml.Marshal(cm) + yamlData, err := ensureLabelAndNs(cm, product.Namespace, productName) if err != nil { - log.Error(err) return nil, e.ErrListResources.AddDesc(err.Error()) } - envCm.YamlData = string(yamlData) - err = commonrepo.NewConfigMapColl().Create(envCm, false) + + envResource.YamlData = yamlData + err = commonrepo.NewEnvResourceColl().Create(envResource) if err != nil { return nil, e.ErrListResources.AddErr(err) } - res = append(res, envCm) + res = append(res, envResource) } } return res, nil diff --git a/pkg/microservice/aslan/core/environment/service/env.go b/pkg/microservice/aslan/core/environment/service/env.go index ba146709..3e6d37bf 100644 --- a/pkg/microservice/aslan/core/environment/service/env.go +++ b/pkg/microservice/aslan/core/environment/service/env.go @@ -31,7 +31,7 @@ type envHandle interface { listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp updateService(args *SvcOptArgs) error queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient informers.SharedInformerFactory) (string, string, []string) - initEnvConfigSet(envName, productName, username string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error + initEnvConfigSet(envName, namespace, productName, username string, envConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error } func envHandleFunc(projectType string, log *zap.SugaredLogger) envHandle { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 24e03d57..15547048 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -168,21 +168,21 @@ type EnvRendersetArg struct { } type CreateHelmProductArg struct { - ProductName string `json:"productName"` - EnvName string `json:"envName"` - Namespace string `json:"namespace"` - ClusterID string `json:"clusterID"` - DefaultValues string `json:"defaultValues"` - ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` - RegistryID string `json:"registry_id"` - ChartValues []*commonservice.RenderChartArg `json:"chartValues"` - BaseEnvName string `json:"baseEnvName"` - BaseName string `json:"base_name,omitempty"` - IsExisted bool `json:"is_existed"` - EnvConfigYamls []string `json:"env_config_yamls,omitempty"` - + ProductName string `json:"productName"` + EnvName string `json:"envName"` + Namespace string `json:"namespace"` + ClusterID string `json:"clusterID"` + DefaultValues string `json:"defaultValues"` + ValuesData *commonservice.ValuesDataArgs `json:"valuesData"` + RegistryID string `json:"registry_id"` + ChartValues []*commonservice.RenderChartArg `json:"chartValues"` + BaseEnvName string `json:"baseEnvName"` + BaseName string `json:"base_name,omitempty"` + IsExisted bool `json:"is_existed"` // New Since v1.12.0 ShareEnv commonmodels.ProductShareEnv `json:"share_env"` + // New Since v1.13.0 + EnvConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs `json:"env_configs"` } type UpdateMultiHelmProductArg struct { @@ -1013,7 +1013,7 @@ func createSingleHelmProduct(templateProduct *templatemodels.Product, requestID, IsForkedProduct: false, RegistryID: registryID, IsExisted: arg.IsExisted, - EnvConfigYamls: arg.EnvConfigYamls, + EnvConfigs: arg.EnvConfigs, ShareEnv: arg.ShareEnv, } @@ -1192,7 +1192,7 @@ func copySingleHelmProduct(templateProduct *templatemodels.Product, productName, productInfo.ClusterID = arg.ClusterID productInfo.BaseName = arg.BaseName productInfo.Namespace = commonservice.GetProductEnvNamespace(arg.EnvName, arg.ProductName, arg.Namespace) - productInfo.EnvConfigYamls = arg.EnvConfigYamls + productInfo.EnvConfigs = arg.EnvConfigs // merge chart infos, use chart info in product to override charts in template_project sourceRenderSet, _, err := commonrepo.NewRenderSetColl().FindRenderSet(&commonrepo.RenderSetFindOption{ @@ -2591,7 +2591,7 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e } }() - err = envHandleFunc(getProjectType(args.ProductName), log).initEnvConfigSet(envName, args.ProductName, user, args.EnvConfigYamls, informer, kubeClient) + err = envHandleFunc(getProjectType(args.ProductName), log).initEnvConfigSet(envName, args.Namespace, args.ProductName, user, args.EnvConfigs, informer, kubeClient) if err != nil { args.Status = setting.ProductStatusFailed log.Errorf("initEnvConfigSet error :%s", err) diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index 1945442c..bf999efb 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -238,7 +238,8 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P log.Errorf("failed to create informer from clientset for clusterID: %s, the error is: %s", clusterID, err) return nil } - err = helmInitEnvConfigSet(args.EnvName, args.ProductName, user, args.EnvConfigYamls, inf, kubeClient) + + err = helmInitEnvConfigSet(args.EnvName, args.Namespace, args.ProductName, user, args.EnvConfigs, inf, kubeClient) if err != nil { log.Errorf("failed to helmInitEnvConfigSet [%s][P:%s]: %s, the error is: %s", args.EnvName, args.ProductName, err) if err := commonrepo.NewProductColl().UpdateStatus(args.EnvName, args.ProductName, setting.ProductStatusFailed); err != nil { diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 557a0e7c..60e657b5 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -30,8 +30,6 @@ import ( "go.uber.org/zap" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" "sigs.k8s.io/controller-runtime/pkg/client" @@ -617,11 +615,11 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo return ret, nil } -func helmInitEnvConfigSet(envName, productName, userName string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { - return initEnvConfigSetAction(envName, productName, userName, envConfigYamls, inf, kubeClient) +func helmInitEnvConfigSet(envName, namespace, productName, userName string, envResources []*models.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { + return initEnvConfigSetAction(envName, namespace, productName, userName, envResources, inf, kubeClient) } -func initEnvConfigSetAction(envName, productName, userName string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { +func initEnvConfigSetAction(envName, namespace, productName, userName string, envResources []*models.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { errList := &multierror.Error{ ErrorFormat: func(es []error) string { format := "创建环境配置" @@ -638,91 +636,66 @@ func initEnvConfigSetAction(envName, productName, userName string, envConfigYaml clusterLabels := getPredefinedClusterLabels(productName, "", envName) delete(clusterLabels, "s-service") - for _, envConfigYaml := range envConfigYamls { - manifests := releaseutil.SplitManifests(envConfigYaml) - resources := make([]*unstructured.Unstructured, 0, len(manifests)) - for _, item := range manifests { - u, err := serializer.NewDecoder().YamlToUnstructured([]byte(item)) + + for _, envResource := range envResources { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(envResource.YamlData)) + if err != nil { + log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %s", envResource.YamlData, err) + errList = multierror.Append(errList, err) + continue + } + switch u.GetKind() { + case setting.ConfigMap, setting.Ingress, setting.Secret, setting.PersistentVolumeClaim: + ls := kube.MergeLabels(clusterLabels, u.GetLabels()) + u.SetNamespace(namespace) + u.SetLabels(ls) + _, err := ensureLabelAndNs(u, namespace, productName) if err != nil { - log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %s", item, err) errList = multierror.Append(errList, err) continue } - resources = append(resources, u) - } - for _, u := range resources { + + err = updater.CreateOrPatchUnstructuredNeverAnnotation(u, kubeClient) + if err != nil { + log.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + u.SetManagedFields(nil) + yamlData, err := yaml.Marshal(u.UnstructuredContent()) + if err != nil { + log.Errorf("Failed to initEnvConfigSet yaml.Marshal %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + envResourceObj := &models.EnvResource{ + ProductName: productName, + UpdateUserName: userName, + EnvName: envName, + Namespace: namespace, + Name: u.GetName(), + YamlData: string(yamlData), + SourceDetail: geneSourceDetail(envResource.GitRepoConfig), + AutoSync: envResource.AutoSync, + } + switch u.GetKind() { - case setting.ConfigMap, setting.Ingress, setting.Secret, setting.PersistentVolumeClaim: - ls := kube.MergeLabels(clusterLabels, u.GetLabels()) - u.SetNamespace(productName + "-env-" + envName) - u.SetLabels(ls) - - err := updater.CreateOrPatchUnstructuredNeverAnnotation(u, kubeClient) - if err != nil { - log.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) - errList = multierror.Append(errList, err) - continue - } - u.SetManagedFields(nil) - yamlData, err := yaml.Marshal(u.UnstructuredContent()) - if err != nil { - log.Errorf("Failed to initEnvConfigSet yaml.Marshal %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) - errList = multierror.Append(errList, err) - continue - } - switch u.GetKind() { - case setting.ConfigMap: - envCm := &models.EnvConfigMap{ - ProductName: productName, - UpdateUserName: userName, - EnvName: envName, - Name: u.GetName(), - YamlData: string(yamlData), - } - if err := commonrepo.NewConfigMapColl().Create(envCm, true); err != nil { - errList = multierror.Append(errList, err) - } - case setting.Ingress: - envIngress := &models.EnvIngress{ - ProductName: productName, - UpdateUserName: userName, - EnvName: envName, - Name: u.GetName(), - YamlData: string(yamlData), - } - if err := commonrepo.NewIngressColl().Create(envIngress, true); err != nil { - errList = multierror.Append(errList, err) - } - case setting.Secret: - envSecret := &models.EnvSecret{ - ProductName: productName, - UpdateUserName: userName, - EnvName: envName, - Name: u.GetName(), - YamlData: string(yamlData), - } - if err := commonrepo.NewSecretColl().Create(envSecret, true); err != nil { - errList = multierror.Append(errList, err) - } - case setting.PersistentVolumeClaim: - envPvc := &models.EnvPvc{ - ProductName: productName, - UpdateUserName: userName, - EnvName: envName, - Name: u.GetName(), - YamlData: string(yamlData), - } - if err := commonrepo.NewPvcColl().Create(envPvc, true); err != nil { - errList = multierror.Append(errList, err) - } - } - default: - errList = multierror.Append(errList, fmt.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, "kind not support")) + case setting.ConfigMap: + envResourceObj.Type = string(config.CommonEnvCfgTypeConfigMap) + case setting.Ingress: + envResourceObj.Type = string(config.CommonEnvCfgTypeIngress) + case setting.Secret: + envResourceObj.Type = string(config.CommonEnvCfgTypeSecret) + case setting.PersistentVolumeClaim: + envResourceObj.Type = string(config.CommonEnvCfgTypePvc) + } + if err := commonrepo.NewEnvResourceColl().Create(envResourceObj); err != nil { + errList = multierror.Append(errList, err) } + default: + errList = multierror.Append(errList, fmt.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, "kind not support")) } } - if len(errList.Errors) == 0 { - return nil - } - return errList + + return errList.ErrorOrNil() } diff --git a/pkg/microservice/aslan/core/environment/service/ingress.go b/pkg/microservice/aslan/core/environment/service/ingress.go index 435664fb..ee9aab6e 100644 --- a/pkg/microservice/aslan/core/environment/service/ingress.go +++ b/pkg/microservice/aslan/core/environment/service/ingress.go @@ -21,7 +21,6 @@ import ( "fmt" "sort" "strings" - "time" "go.uber.org/zap" extensionsv1beta1 "k8s.io/api/extensions/v1beta1" @@ -39,14 +38,12 @@ import ( ) type ListIngressesResponse struct { - IngressName string `json:"ingress_name"` - HostInfo string `json:"host_info"` - Address string `json:"address"` - Ports string `json:"ports"` - ErrorReason string `json:"error_reason"` - YamlData string `json:"yaml_data"` - UpdateUserName string `json:"update_username"` - CreateTime time.Time `json:"create_time"` + *ResourceResponseBase + IngressName string `json:"ingress_name"` + HostInfo string `json:"host_info"` + Address string `json:"address"` + Ports string `json:"ports"` + ErrorReason string `json:"error_reason"` } func ListIngresses(envName, productName string, log *zap.SugaredLogger) ([]*ListIngressesResponse, error) { @@ -121,9 +118,17 @@ func ListIngresses(envName, productName string, log *zap.SugaredLogger) ([]*List HostInfo: hostInfo, Ports: ports, ErrorReason: errorReason, - YamlData: string(yamlData), Address: address, + ResourceResponseBase: &ResourceResponseBase{ + Name: ingress.GetName(), + Type: config.CommonEnvCfgTypeIngress, + EnvName: envName, + ProjectName: productName, + YamlData: string(yamlData), + CreateTime: ingress.GetCreationTimestamp().Time, + }, } + resElem.setSourceDetailData(&ingress) res = append(res, resElem) } @@ -165,9 +170,17 @@ func ListIngresses(envName, productName string, log *zap.SugaredLogger) ([]*List HostInfo: hostInfo, Ports: ports, ErrorReason: errorReason, - YamlData: string(yamlData), Address: address, + ResourceResponseBase: &ResourceResponseBase{ + Name: ingress.GetName(), + Type: config.CommonEnvCfgTypeIngress, + EnvName: envName, + ProjectName: productName, + YamlData: string(yamlData), + CreateTime: ingress.GetCreationTimestamp().Time, + }, } + resElem.setSourceDetailData(&ingress) res = append(res, resElem) } } @@ -185,7 +198,7 @@ type UpdateIngressArgs struct { RestartAssociatedSvc bool `json:"restart_associated_svc"` } -func UpdateOrCreateIngress(args *UpdateCommonEnvCfgArgs, userName, userID string, isCreate bool, log *zap.SugaredLogger) error { +func UpdateOrCreateIngress(args *models.CreateUpdateCommonEnvCfgArgs, userName string, isCreate bool, log *zap.SugaredLogger) error { u, err := serializer.NewDecoder().YamlToUnstructured([]byte(args.YamlData)) if err != nil { log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %v", args.YamlData, err) @@ -207,20 +220,29 @@ func UpdateOrCreateIngress(args *UpdateCommonEnvCfgArgs, userName, userID string if err != nil { return e.ErrUpdateResource.AddErr(err) } - u.SetNamespace(product.Namespace) + + yamlData, err := ensureLabelAndNs(u, product.Namespace, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + err = updater.UpdateOrCreateUnstructured(u, kubeClient) if err != nil { log.Errorf("Failed to UpdateOrCreateIngress %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err) return e.ErrUpdateResource.AddErr(fmt.Errorf("Failed to UpdateOrCreateIngress %s, manifest is\n%v\n, error: %v", u.GetKind(), u, err)) } - envIngress := &models.EnvIngress{ + envIngress := &models.EnvResource{ ProductName: args.ProductName, UpdateUserName: userName, EnvName: args.EnvName, + Namespace: product.Namespace, Name: u.GetName(), - YamlData: args.YamlData, + YamlData: yamlData, + Type: string(config.CommonEnvCfgTypeIngress), + SourceDetail: args.SourceDetail, + AutoSync: args.AutoSync, } - if commonrepo.NewIngressColl().Create(envIngress, true) != nil { + if commonrepo.NewEnvResourceColl().Create(envIngress) != nil { return e.ErrUpdateResource.AddDesc(err.Error()) } return err diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index 565b8115..dfdf7aa2 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -292,6 +292,6 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] return nil } -func (k *K8sService) initEnvConfigSet(envName, productName, userName string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { - return initEnvConfigSetAction(envName, productName, userName, envConfigYamls, inf, kubeClient) +func (k *K8sService) initEnvConfigSet(envName, namespace, productName, userName string, envConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { + return initEnvConfigSetAction(envName, namespace, productName, userName, envConfigs, inf, kubeClient) } diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 8f5d2338..0f25667e 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -264,14 +264,9 @@ func (p *PMService) createGroup(envName, productName, username string, group []* errList = multierror.Append(errList, err) } } - // 如果创建依赖服务组有返回错误, 停止等待 - if err := errList.ErrorOrNil(); err != nil { - return err - } - - return nil + return errList.ErrorOrNil() } -func (p *PMService) initEnvConfigSet(envName, productName, username string, envConfigYamls []string, inf informers.SharedInformerFactory, kubeClient client.Client) error { +func (p *PMService) initEnvConfigSet(envName, namespace, productName, username string, envConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { return nil } diff --git a/pkg/microservice/aslan/core/environment/service/pvc.go b/pkg/microservice/aslan/core/environment/service/pvc.go index 3dfa07dd..8b910952 100644 --- a/pkg/microservice/aslan/core/environment/service/pvc.go +++ b/pkg/microservice/aslan/core/environment/service/pvc.go @@ -20,7 +20,6 @@ import ( "encoding/json" "sort" "sync" - "time" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -37,16 +36,13 @@ import ( ) type ListPvcsResponse struct { - PvcName string `json:"pvc_name"` - Status string `json:"status"` - Volume string `json:"volume"` - AccessModes string `json:"access_modes"` - StorageClass string `json:"storageclass"` - Capacity string `json:"capacity"` - YamlData string `json:"yaml_data"` - UpdateUserName string `json:"update_username"` - CreateTime time.Time `json:"create_time"` - Services []string `json:"services"` + *ResourceResponseBase + PvcName string `json:"pvc_name"` + Status string `json:"status"` + Volume string `json:"volume"` + AccessModes string `json:"access_modes"` + StorageClass string `json:"storageclass"` + Capacity string `json:"capacity"` } func ListPvcs(envName, productName string, log *zap.SugaredLogger) ([]*ListPvcsResponse, error) { @@ -123,10 +119,17 @@ func ListPvcs(envName, productName string, log *zap.SugaredLogger) ([]*ListPvcsR AccessModes: accessModes, StorageClass: storageClassName, Capacity: capacity, - YamlData: string(yamlData), - Services: tempSvcs, - CreateTime: pvc.GetCreationTimestamp().Time, + ResourceResponseBase: &ResourceResponseBase{ + Name: pvc.Name, + Type: config.CommonEnvCfgTypePvc, + EnvName: envName, + ProjectName: productName, + YamlData: string(yamlData), + Services: tempSvcs, + CreateTime: pvc.GetCreationTimestamp().Time, + }, } + resElem.setSourceDetailData(pvc) mutex.Lock() res = append(res, resElem) mutex.Unlock() @@ -157,7 +160,7 @@ type UpdatePvcArgs struct { RestartAssociatedSvc bool `json:"restart_associated_svc"` } -func UpdatePvc(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { +func UpdatePvc(args *models.CreateUpdateCommonEnvCfgArgs, userName string, log *zap.SugaredLogger) error { js, err := yaml.YAMLToJSON([]byte(args.YamlData)) pvc := &corev1.PersistentVolumeClaim{} err = json.Unmarshal(js, pvc) @@ -181,20 +184,29 @@ func UpdatePvc(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.S log.Errorf("failed to create kubernetes clientset for clusterID: %s, the error is: %s", product.ClusterID, err) return e.ErrUpdateResource.AddErr(err) } - pvc.Namespace = product.Namespace + + yamlData, err := ensureLabelAndNs(pvc, product.Namespace, args.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + err = updater.UpdatePvc(product.Namespace, pvc, clientset) if err != nil { log.Error(err) return e.ErrUpdateResource.AddDesc(err.Error()) } - envPvc := &models.EnvPvc{ + envPvc := &models.EnvResource{ ProductName: args.ProductName, UpdateUserName: userName, EnvName: args.EnvName, + Namespace: product.Namespace, Name: pvc.Name, - YamlData: args.YamlData, + YamlData: yamlData, + Type: string(config.CommonEnvCfgTypePvc), + SourceDetail: args.SourceDetail, + AutoSync: args.AutoSync, } - if commonrepo.NewPvcColl().Create(envPvc, true) != nil { + if commonrepo.NewEnvResourceColl().Create(envPvc) != nil { return e.ErrUpdateResource.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/environment/service/secret.go b/pkg/microservice/aslan/core/environment/service/secret.go index d6d6a591..03b8efb1 100644 --- a/pkg/microservice/aslan/core/environment/service/secret.go +++ b/pkg/microservice/aslan/core/environment/service/secret.go @@ -20,7 +20,6 @@ import ( "encoding/json" "sort" "sync" - "time" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -36,12 +35,9 @@ import ( ) type ListSecretsResponse struct { - SecretName string `json:"secret_name"` - SecretType string `json:"secret_type"` - YamlData string `json:"yaml_data"` - UpdateUserName string `json:"update_username"` - CreateTime time.Time `json:"create_time"` - Services []string `json:"services"` + *ResourceResponseBase + SecretName string `json:"secret_name"` + SecretType string `json:"secret_type"` } func ListSecrets(envName, productName string, log *zap.SugaredLogger) ([]*ListSecretsResponse, error) { @@ -97,10 +93,17 @@ func ListSecrets(envName, productName string, log *zap.SugaredLogger) ([]*ListSe resElem := &ListSecretsResponse{ SecretName: secret.Name, SecretType: string(secret.Type), - YamlData: string(yamlData), - Services: tempSvcs, - CreateTime: secret.GetCreationTimestamp().Time, + ResourceResponseBase: &ResourceResponseBase{ + Name: secret.Name, + Type: config.CommonEnvCfgTypeSecret, + EnvName: envName, + ProjectName: productName, + YamlData: string(yamlData), + Services: tempSvcs, + CreateTime: secret.GetCreationTimestamp().Time, + }, } + resElem.setSourceDetailData(secret) mutex.Lock() res = append(res, resElem) mutex.Unlock() @@ -124,7 +127,7 @@ func ListSecrets(envName, productName string, log *zap.SugaredLogger) ([]*ListSe return res, nil } -func UpdateSecret(args *UpdateCommonEnvCfgArgs, userName, userID string, log *zap.SugaredLogger) error { +func UpdateSecret(args *models.CreateUpdateCommonEnvCfgArgs, userName string, log *zap.SugaredLogger) error { js, err := yaml.YAMLToJSON([]byte(args.YamlData)) secret := &corev1.Secret{} err = json.Unmarshal(js, secret) @@ -147,9 +150,8 @@ func UpdateSecret(args *UpdateCommonEnvCfgArgs, userName, userID string, log *za if err != nil { return e.ErrUpdateResource.AddErr(err) } - secret.Namespace = product.Namespace - yamlData, err := ensureLabel(secret, args.ProductName) + yamlData, err := ensureLabelAndNs(secret, product.Namespace, args.ProductName) if err != nil { return e.ErrUpdateResource.AddErr(err) } @@ -159,14 +161,18 @@ func UpdateSecret(args *UpdateCommonEnvCfgArgs, userName, userID string, log *za log.Error(err) return e.ErrUpdateResource.AddDesc(err.Error()) } - envSecret := &models.EnvSecret{ + envSecret := &models.EnvResource{ ProductName: args.ProductName, UpdateUserName: userName, EnvName: args.EnvName, + Namespace: product.Namespace, Name: secret.Name, YamlData: yamlData, + Type: string(config.CommonEnvCfgTypeSecret), + SourceDetail: args.SourceDetail, + AutoSync: args.AutoSync, } - if commonrepo.NewSecretColl().Create(envSecret, true) != nil { + if commonrepo.NewEnvResourceColl().Create(envSecret) != nil { return e.ErrUpdateResource.AddDesc(err.Error()) } diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 8c868f7c..9ff56308 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -230,10 +230,7 @@ func initDatabase() { commonrepo.NewChartColl(), commonrepo.NewDockerfileTemplateColl(), commonrepo.NewProjectClusterRelationColl(), - commonrepo.NewConfigMapColl(), - commonrepo.NewIngressColl(), - commonrepo.NewSecretColl(), - commonrepo.NewPvcColl(), + commonrepo.NewEnvResourceColl(), commonrepo.NewEnvSvcDependColl(), commonrepo.NewBuildTemplateColl(), commonrepo.NewScanningColl(), diff --git a/pkg/tool/kube/getter/ingress.go b/pkg/tool/kube/getter/ingress.go index 60423cd2..2813a489 100644 --- a/pkg/tool/kube/getter/ingress.go +++ b/pkg/tool/kube/getter/ingress.go @@ -23,8 +23,11 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/apis/extensions" "sigs.k8s.io/controller-runtime/pkg/client" + + kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" ) func GetExtensionsV1Beta1Ingress(namespace, name string, lister informers.SharedInformerFactory) (*extensionsv1beta1.Ingress, bool, error) { @@ -39,6 +42,32 @@ func GetNetworkingV1Ingress(namespace, name string, lister informers.SharedInfor return lister.Networking().V1().Ingresses().Lister().Ingresses(namespace).Get(name) } +func GetUnstructuredIngress(namespace, name string, cl client.Client, clientset *kubernetes.Clientset) (*unstructured.Unstructured, bool, error) { + version, err := clientset.Discovery().ServerVersion() + if err != nil { + return nil, false, err + } + gvk := schema.GroupVersionKind{ + Group: "extensions", + Kind: "Ingress", + Version: "v1beta1", + } + if !kubeclient.VersionLessThan122(version) { + gvk = schema.GroupVersionKind{ + Group: "networking.k8s.io", + Kind: "Ingress", + Version: "v1", + } + } + u := &unstructured.Unstructured{} + u.SetGroupVersionKind(gvk) + found, err := GetResourceInCache(namespace, name, u, cl) + if err != nil || !found { + u = nil + } + return u, found, err +} + // ListExtensionsV1Beta1Ingresses gets the ingress (extensions/v1beta1) from the informer func ListExtensionsV1Beta1Ingresses(selector labels.Selector, lister informers.SharedInformerFactory) ([]*extensionsv1beta1.Ingress, error) { if selector == nil { diff --git a/pkg/tool/kube/getter/pvc.go b/pkg/tool/kube/getter/pvc.go index 2b4d024c..0d1a3fb0 100644 --- a/pkg/tool/kube/getter/pvc.go +++ b/pkg/tool/kube/getter/pvc.go @@ -52,6 +52,15 @@ func GetPVCYaml(ns string, name string, cl client.Client) ([]byte, bool, error) return GetResourceYamlInCache(ns, name, gvk, cl) } +func GetPvc(ns, name string, cl client.Client) (*corev1.PersistentVolumeClaim, bool, error) { + g := &corev1.PersistentVolumeClaim{} + found, err := GetResourceInCache(ns, name, g, cl) + if err != nil || !found { + g = nil + } + return g, found, err +} + func GetPVCYamlFormat(ns string, name string, cl client.Client) ([]byte, bool, error) { gvk := schema.GroupVersionKind{ Group: "", -- Gitee From a9c77a016f1245ddff5a4819397d4dc75bbbe7d2 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Mon, 4 Jul 2022 13:49:40 +0800 Subject: [PATCH 2511/2647] add filter when listing gitHub projects (#1727) * add filter for git hub projects Signed-off-by: allenshen * update comment Signed-off-by: allenshen --- pkg/microservice/aslan/core/code/client/github/github.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/microservice/aslan/core/code/client/github/github.go b/pkg/microservice/aslan/core/code/client/github/github.go index dee584d6..53854faf 100644 --- a/pkg/microservice/aslan/core/code/client/github/github.go +++ b/pkg/microservice/aslan/core/code/client/github/github.go @@ -140,6 +140,11 @@ func (c *Client) ListProjects(opt client.ListOpt) ([]*client.Project, error) { } var res []*client.Project for _, o := range repos { + // Note. when using gitHub api to filter repos, private repo will not be returned + // we need to filter the repos with query namespace when repo list is returned + if len(opt.Namespace) > 0 && o.GetOwner().GetLogin() != opt.Namespace { + continue + } res = append(res, &client.Project{ ID: int(o.GetID()), Name: o.GetName(), -- Gitee From 1925d260b8ababcc6195e33e7d8c94f5a4ceadb3 Mon Sep 17 00:00:00 2001 From: flyer Date: Mon, 4 Jul 2022 16:45:59 +0800 Subject: [PATCH 2512/2647] fix bug of keeping too many connections in hub-agent (#1729) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/tool/remotedialer/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/tool/remotedialer/server.go b/pkg/tool/remotedialer/server.go index 8503de3c..d9806abd 100644 --- a/pkg/tool/remotedialer/server.go +++ b/pkg/tool/remotedialer/server.go @@ -145,6 +145,7 @@ func (r *Server) GetTransport(clusterCaCert string, clientKey string) (http.Roun d := r.Dialer(clientKey) transport.DialContext = d transport.Proxy = http.ProxyFromEnvironment + transport.IdleConnTimeout = 30 * time.Second r.caCert = clusterCaCert if r.httpTransport != nil { -- Gitee From 65e0e9efdf11c17b13d9a7cd556ddce3455d2537 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 4 Jul 2022 21:50:41 +0800 Subject: [PATCH 2513/2647] fix release problem Signed-off-by: guoyu --- .../service/jobcontroller/job_freestyle.go | 16 +++--- .../service/stepcontroller/step_archive.go | 51 ++----------------- .../core/workflow/service/workflow/job/job.go | 7 +++ .../service/workflow/job/job_build.go | 39 +++++++++++--- .../service/workflow/job/job_deploy.go | 4 +- .../core/service/step/step_archive.go | 5 ++ pkg/types/step/common.go | 2 +- 7 files changed, 60 insertions(+), 64 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 688737d3..555feedf 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -213,14 +213,14 @@ func (c *FreestyleJobCtl) Complete(ctx context.Context) { // 清理用户取消和超时的任务 defer func() { - if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { - c.logger.Error(err) - c.job.Error = err.Error() - } - if err := ensureDeleteConfigMap(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { - c.logger.Error(err) - c.job.Error = err.Error() - } + go func() { + if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + } + if err := ensureDeleteConfigMap(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + } + }() }() // get job outputs info from pod terminate message. diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go index d29caa06..0f1accb9 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go @@ -6,16 +6,15 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/types/step" "go.uber.org/zap" "gopkg.in/yaml.v3" ) type archiveCtl struct { - step *commonmodels.StepTask - toolInstalldSpec *step.StepArchiveSpec - log *zap.SugaredLogger + step *commonmodels.StepTask + archiveSpec *step.StepArchiveSpec + log *zap.SugaredLogger } func NewArchiveCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*archiveCtl, error) { @@ -27,52 +26,10 @@ func NewArchiveCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*ar if err := yaml.Unmarshal(yamlString, &archiveSpec); err != nil { return nil, fmt.Errorf("unmarshal git spec error: %v", err) } - return &archiveCtl{toolInstalldSpec: archiveSpec, log: log, step: stepTask}, nil + return &archiveCtl{archiveSpec: archiveSpec, log: log, step: stepTask}, nil } func (s *archiveCtl) PreRun(ctx context.Context) error { - spec := &step.StepArchiveSpec{ - FilePath: s.toolInstalldSpec.FilePath, - AbsFilePath: s.toolInstalldSpec.AbsFilePath, - DestinationPath: s.toolInstalldSpec.DestinationPath, - S3: &step.S3{ - Ak: config.S3StorageAK(), - Sk: config.S3StorageSK(), - Endpoint: config.S3StorageEndpoint(), - Bucket: config.S3StorageBucket(), - Subfolder: config.S3StoragePath(), - Protocol: config.S3StorageProtocol(), - }, - } - if s.toolInstalldSpec.S3StorageID == "" { - objectStorage, _ := mongodb.NewS3StorageColl().FindDefault() - if objectStorage != nil { - spec.S3.Endpoint = objectStorage.Endpoint - spec.S3.Sk = objectStorage.Sk - spec.S3.Ak = objectStorage.Ak - spec.S3.Subfolder = objectStorage.Subfolder - spec.S3.Bucket = objectStorage.Bucket - spec.S3.Protocol = "https" - if objectStorage.Insecure { - spec.S3.Protocol = "http" - } - } - s.step.Spec = spec - return nil - } - objectStorage, _ := mongodb.NewS3StorageColl().Find(s.toolInstalldSpec.S3StorageID) - if objectStorage != nil { - spec.S3.Endpoint = objectStorage.Endpoint - spec.S3.Sk = objectStorage.Sk - spec.S3.Ak = objectStorage.Ak - spec.S3.Subfolder = objectStorage.Subfolder - spec.S3.Bucket = objectStorage.Bucket - spec.S3.Protocol = "https" - if objectStorage.Insecure { - spec.S3.Protocol = "http" - } - } - s.step.Spec = spec return nil } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go index f9e5b852..65a2af0e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -66,3 +66,10 @@ func ToJobs(job *commonmodels.Job, workflow *commonmodels.WorkflowV4, taskID int } return jobCtl.ToJobs(taskID) } + +func jobNameFormat(jobName string) string { + if len(jobName) > 50 { + return jobName[:50] + } + return jobName +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 57240038..7d7a6204 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -84,6 +84,10 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { if err != nil { return resp, err } + defaultS3, err := commonrepo.NewS3StorageColl().FindDefault() + if err != nil { + return resp, err + } for _, build := range j.spec.ServiceAndBuilds { imageTag := time.Now().Format("20060102150405") @@ -101,10 +105,10 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { build.Image = strings.TrimPrefix(build.Image, "http://") build.Image = strings.TrimPrefix(build.Image, "https://") - build.Package = fmt.Sprintf("%s-%s-%d.tar.gz", build.ServiceModule, time.Now().Format("20060102150405"), taskID) + build.Package = fmt.Sprintf("%s-%s-%s-%d.tar.gz", j.job.Name, build.ServiceModule, time.Now().Format("20060102150405"), taskID) jobTask := &commonmodels.JobTask{ - Name: j.job.Name + "-" + build.ServiceName + "-" + build.ServiceModule, + Name: jobNameFormat(build.ServiceName + "-" + build.ServiceModule + "-" + j.job.Name), JobType: string(config.JobZadigBuild), } buildInfo, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.BuildName}) @@ -192,7 +196,8 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { StepType: config.StepArchive, Spec: step.StepArchiveSpec{ FilePath: path.Join(buildInfo.PostBuild.FileArchive.FileLocation, build.Package), - DestinationPath: path.Join(j.workflow.Name, fmt.Sprint(taskID), j.job.Name, "archive"), + DestinationPath: path.Join(j.workflow.Name, fmt.Sprint(taskID), "archive"), + S3: modelS3toS3(defaultS3), }, } jobTask.Steps = append(jobTask.Steps, archiveStep) @@ -200,15 +205,21 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { // init object storage step if buildInfo.PostBuild.ObjectStorageUpload != nil && buildInfo.PostBuild.ObjectStorageUpload.Enabled { + modelS3, err := commonrepo.NewS3StorageColl().Find(buildInfo.PostBuild.ObjectStorageUpload.ObjectStorageID) + if err != nil { + return resp, err + } + s3 := modelS3toS3(modelS3) + s3.Subfolder = "" for _, detail := range buildInfo.PostBuild.ObjectStorageUpload.UploadDetail { archiveStep := &commonmodels.StepTask{ - Name: build.ServiceName + "-archive", + Name: build.ServiceName + "-object-storage", JobName: jobTask.Name, StepType: config.StepArchive, Spec: step.StepArchiveSpec{ FilePath: detail.FilePath, DestinationPath: detail.DestinationPath, - S3StorageID: buildInfo.PostBuild.ObjectStorageUpload.ObjectStorageID, + S3: s3, }, } jobTask.Steps = append(jobTask.Steps, archiveStep) @@ -293,8 +304,24 @@ func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, ret = append(ret, &commonmodels.KeyVal{Key: "IMAGE", Value: build.Image, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "CI", Value: "true", IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "ZADIG", Value: "true", IsCredential: false}) - buildURL := fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/%s/%d", configbase.SystemAddress(), project, workflowName, taskID) + buildURL := fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/custom/%s/%d", configbase.SystemAddress(), project, workflowName, taskID) ret = append(ret, &commonmodels.KeyVal{Key: "BUILD_URL", Value: buildURL, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "PKG_FILE", Value: build.Package, IsCredential: false}) return ret } + +func modelS3toS3(modelS3 *commonmodels.S3Storage) *step.S3 { + resp := &step.S3{ + Ak: modelS3.Ak, + Sk: modelS3.Sk, + Endpoint: modelS3.Endpoint, + Bucket: modelS3.Bucket, + Subfolder: modelS3.Subfolder, + Insecure: modelS3.Insecure, + Provider: modelS3.Provider, + } + if modelS3.Insecure { + resp.Protocol = "http" + } + return resp +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index 2f25a949..03185499 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -111,7 +111,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { if j.spec.DeployType == setting.K8SDeployType { for _, deploy := range j.spec.ServiceAndImages { jobTask := &commonmodels.JobTask{ - Name: j.job.Name + "-" + deploy.ServiceName + "-" + deploy.ServiceModule, + Name: jobNameFormat(deploy.ServiceName + "-" + deploy.ServiceModule + j.job.Name), JobType: string(config.JobZadigDeploy), } deployStep := &commonmodels.StepTask{ @@ -138,7 +138,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { } for serviceName, deploys := range deployServiceMap { jobTask := &commonmodels.JobTask{ - Name: j.job.Name + "-" + serviceName, + Name: jobNameFormat(serviceName + "-" + j.job.Name), JobType: string(config.JobZadigDeploy), } deployStep := &commonmodels.StepTask{ diff --git a/pkg/microservice/jobexecutor/core/service/step/step_archive.go b/pkg/microservice/jobexecutor/core/service/step/step_archive.go index c4dce307..4707b836 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_archive.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_archive.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "os" + "path" "path/filepath" "strings" "time" @@ -89,6 +90,10 @@ func (s *ArchiveStep) Run(ctx context.Context) error { s.spec.AbsFilePath = replaceEnvWithValue(s.spec.AbsFilePath, envmaps) s.spec.DestinationPath = replaceEnvWithValue(s.spec.DestinationPath, envmaps) + if len(s.spec.S3.Subfolder) > 0 { + s.spec.DestinationPath = strings.TrimLeft(path.Join(s.spec.S3.Subfolder, s.spec.DestinationPath), "/") + } + info, err := os.Stat(s.spec.AbsFilePath) if err != nil { return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", s.spec.AbsFilePath, s.spec.DestinationPath, err) diff --git a/pkg/types/step/common.go b/pkg/types/step/common.go index 053c1dfe..5ccc0d75 100644 --- a/pkg/types/step/common.go +++ b/pkg/types/step/common.go @@ -34,6 +34,6 @@ type S3 struct { Bucket string `bson:"bucket" json:"bucket" yaml:"bucket"` Subfolder string `bson:"subfolder" json:"subfolder" yaml:"subfolder"` Insecure bool `bson:"insecure" json:"insecure" yaml:"insecure"` - Provider int `bson:"provider" json:"provider" yaml:"provider"` + Provider int8 `bson:"provider" json:"provider" yaml:"provider"` Protocol string `bson:"protocol" json:"protocol" yaml:"protocol"` } -- Gitee From 0e527f0f9cea23a03e079c7e081cee50513726d3 Mon Sep 17 00:00:00 2001 From: allenshen Date: Mon, 4 Jul 2022 22:40:01 +0800 Subject: [PATCH 2514/2647] add env resource update logic Signed-off-by: allenshen --- .../scheduler/shedule_env_resource_update.go | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go diff --git a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go new file mode 100644 index 00000000..31e4927e --- /dev/null +++ b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go @@ -0,0 +1,106 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package scheduler + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/setting" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/koderover/zadig/pkg/microservice/cron/core/service" + "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" + "github.com/koderover/zadig/pkg/tool/log" + + "github.com/jasonlvhit/gocron" + "go.uber.org/zap" +) + +func buildEnvResourceCronKey(envResource *service.EnvResource) string { + return fmt.Sprintf("env-resource:%s:%s-type:%s-name:%s", envResource.ProductName, envResource.EnvName, envResource.Type, envResource.Name) +} + +func (c *CronClient) deleteEnvResourceScheduler(envResourceKey string) { + log.Infof("deleting single env resource scheduler: %s", envResourceKey) + if _, ok := c.SchedulerController[envResourceKey]; ok { + c.SchedulerController[envResourceKey] <- true + } + if _, ok := c.Schedulers[envResourceKey]; ok { + c.Schedulers[envResourceKey].Clear() + delete(c.Schedulers, envResourceKey) + } +} + +func (c *CronClient) UpsertEnvResourceSyncScheduler(log *zap.SugaredLogger) { + + envs, err := c.AslanCli.ListEnvs(log, &client.EvnListOption{DeployType: []string{setting.HelmDeployType, setting.K8SDeployType}}) + if err != nil { + log.Error(err) + return + } + + log.Info("start init env resource sync scheduler.") + + lastScheduler := sets.NewString() + for k := range c.lastEnvResourceSchedulerData { + lastScheduler.Insert(k) + } + + for _, env := range envs { + envResources, err := c.AslanCli.ListEnvResources(env.ProductName, env.EnvName, log) + if err != nil { + log.Error(err) + return + } + + for _, envResource := range envResources { + envResourceKey := buildEnvResourceCronKey(envResource) + + lastScheduler.Delete(envResourceKey) + + if lastEnvResConfig, ok := c.lastEnvResourceSchedulerData[envResourceKey]; ok { + if envResource.CreateTime == lastEnvResConfig.CreateTime { + continue + } + } + c.lastEnvResourceSchedulerData[envResourceKey] = envResource + + c.deleteEnvResourceScheduler(envResourceKey) + + newScheduler := gocron.NewScheduler() + newScheduler.Every(EnvUpdateInterval).Seconds().Do(c.RunScheduledEnvResourceUpdate, envResource.ProductName, envResource.EnvName, envResource.Type, envResource.Name, log) + + log.Infof("[%s] add env resource schedulers..", envResourceKey) + c.Schedulers[envResourceKey] = newScheduler + c.SchedulerController[envResourceKey] = c.Schedulers[envResourceKey].Start() + } + + } + + for _, k := range lastScheduler.List() { + c.deleteEnvResourceScheduler(k) + } +} + +func (c *CronClient) RunScheduledEnvResourceUpdate(productName, envName, resType, resName string, log *zap.SugaredLogger) { + log.Infof("start to Run RunScheduledEnvResourceUpdate, productName: %s, envName: %s, resType: %s, resName: %s", productName, envName, resType, resName) + err := c.AslanCli.SyncEnvResource(productName, envName, resType, resName, log) + if err != nil { + log.Errorf("failed to sync variables for env: %s:%s", productName, envName) + } +} -- Gitee From b356e139651992fdd4a203437ff2449d990b3b65 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 5 Jul 2022 10:04:07 +0800 Subject: [PATCH 2515/2647] add env resource update logic Signed-off-by: allenshen --- .../core/common/repository/models/product.go | 1 + .../common/repository/mongodb/env_resource.go | 85 ++++++++++ .../repository/mongodb/template/product.go | 3 +- .../environment/handler/common_env_cfg.go | 30 ++++ .../aslan/core/environment/handler/router.go | 2 + .../environment/service/common_env_cfg.go | 159 ++++++++++++++---- .../cron/core/service/client/env.go | 51 +++++- .../service/scheduler/schedule_env_update.go | 2 +- .../service/scheduler/schedule_workflow.go | 4 + .../cron/core/service/scheduler/scheduler.go | 50 ++++-- .../scheduler/shedule_env_resource_update.go | 8 +- pkg/microservice/cron/core/service/service.go | 14 ++ 12 files changed, 350 insertions(+), 59 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/product.go b/pkg/microservice/aslan/core/common/repository/models/product.go index 3781a797..15d64a08 100644 --- a/pkg/microservice/aslan/core/common/repository/models/product.go +++ b/pkg/microservice/aslan/core/common/repository/models/product.go @@ -77,6 +77,7 @@ type CreateUpdateCommonEnvCfgArgs struct { GitRepoConfig *templatemodels.GitRepoConfig `json:"git_repo_config"` SourceDetail *CreateFromRepo `json:"-"` AutoSync bool `json:"auto_sync"` + LatestEnvResource *EnvResource `json:"-"` } type RenderInfo struct { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go index 77d9e5b4..7621a3fd 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go @@ -81,6 +81,20 @@ type QueryEnvResourceOption struct { Name string Type string IgnoreNotFound bool + AutoSync bool + Active bool +} + +type EnvResourceBaseData struct { + ProjectName string `bson:"product_name"` + EnvName string `bson:"env_name"` + Name string `bson:"name"` + Type string `bson:"type"` +} + +type EnvResourceAggregateData struct { + ID EnvResourceBaseData `bson:"_id"` + CreateTime int64 `bson:"create_time"` } func (c *EnvResourceColl) List(opt *QueryEnvResourceOption) ([]*models.EnvResource, error) { @@ -140,6 +154,12 @@ func (c *EnvResourceColl) Find(opt *QueryEnvResourceOption) (*models.EnvResource if len(opt.Type) > 0 { query["type"] = opt.Type } + if opt.Active { + query["deleted_at"] = bson.M{"$or": []bson.M{ + bson.M{"deleted_at": bson.M{"$eq": 0}}, + bson.M{"deleted_at": bson.M{"$eq": nil}}, + }} + } if len(opt.Id) > 0 { oid, err := primitive.ObjectIDFromHex(opt.Id) if err != nil { @@ -165,6 +185,71 @@ func (c *EnvResourceColl) Find(opt *QueryEnvResourceOption) (*models.EnvResource return rs, err } +func (c *EnvResourceColl) ListLatestResource(opt *QueryEnvResourceOption) ([]*EnvResourceAggregateData, error) { + query := bson.M{} + if len(opt.ProductName) > 0 { + query["product_name"] = opt.ProductName + } + if len(opt.Namespace) > 0 { + query["namespace"] = opt.Namespace + } + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + if len(opt.Name) > 0 { + query["name"] = opt.Name + } + if len(opt.Type) > 0 { + query["type"] = opt.Type + } + + var pipeline []bson.M + if len(query) > 0 { + pipeline = append(pipeline, bson.M{"$match": query}) + } + + pipeline = append(pipeline, + bson.M{"$sort": bson.M{"create_time": -1}}, + bson.M{ + "$group": bson.M{ + "_id": bson.M{ + "product_name": "$product_name", + "env_name": "$env_name", + "name": "$name", + "type": "$type", + }, + "create_time": bson.M{"$first": "$create_time"}, + "deleted_at": bson.M{"$first": "$deleted_at"}, + "auto_sync": bson.M{"$first": "$auto_sync"}, + }, + }, + ) + + pipeline = append(pipeline, bson.M{ + "$match": bson.M{"$or": []bson.M{ + bson.M{"deleted_at": bson.M{"$eq": 0}}, + bson.M{"deleted_at": bson.M{"$eq": nil}}, + }}, + }) + + if opt.AutoSync { + pipeline = append(pipeline, bson.M{ + "$match": bson.M{"auto_sync": true}, + }) + } + + cursor, err := c.Aggregate(context.TODO(), pipeline) + if err != nil { + return nil, err + } + + result := make([]*EnvResourceAggregateData, 0) + if err := cursor.All(context.TODO(), &result); err != nil { + return nil, err + } + return result, err +} + func (c *EnvResourceColl) Delete(oid primitive.ObjectID) error { query := bson.M{} query["_id"] = oid diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index f9e5525b..37d87ef9 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -192,7 +192,8 @@ func (c *ProductColl) ListWithOption(opt *ProductListOpt) ([]*template.Product, query["product_feature.basic_facility"] = opt.BasicFacility } if opt.DeployType != "" { - query["product_feature.deploy_type"] = opt.DeployType + //query["product_feature.deploy_type"] = opt.DeployType + query["product_feature.deploy_type"] = bson.M{"$in": strings.Split(opt.DeployType, ",")} } cursor, err := c.Collection.Find(context.TODO(), query) diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index fa39d0bb..1c62359c 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -131,3 +131,33 @@ func ListCommonEnvCfgHistory(c *gin.Context) { ctx.Resp, ctx.Err = service.ListEnvResourceHistory(args, ctx.Logger) } + +func ListLatestEnvCfg(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + args := new(service.ListCommonEnvCfgHistoryArgs) + if err := c.ShouldBindQuery(args); err != nil { + ctx.Err = e.ErrInvalidParam.AddErr(err) + return + } + + ctx.Resp, ctx.Err = service.ListLatestEnvResources(args, ctx.Logger) +} + +func SyncEnvResource(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + envName := c.Param("envName") + resType := c.Param("type") + resName := c.Param("objectName") + + args := &service.SyncEnvResourceArg{ + EnvName: envName, + ProductName: c.Query("projectName"), + Name: resName, + Type: resType, + } + ctx.Err = service.SyncEnvResource(args, ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index a54f440c..58d56969 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -50,6 +50,8 @@ func (*Router) Inject(router *gin.RouterGroup) { commonEnvCfgs := router.Group("envcfgs") { commonEnvCfgs.GET("/:envName/cfg/:objectName", ListCommonEnvCfgHistory) + commonEnvCfgs.GET("", ListLatestEnvCfg) + commonEnvCfgs.PUT("/:envName/:type/:objectName/sync", SyncEnvResource) commonEnvCfgs.PUT("/:envName", gin2.UpdateOperationLogStatus, UpdateCommonEnvCfg) commonEnvCfgs.POST("/:envName", gin2.UpdateOperationLogStatus, CreateCommonEnvCfg) commonEnvCfgs.DELETE("/:envName/cfg/:objectName", gin2.UpdateOperationLogStatus, DeleteCommonEnvCfg) diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index d77d33c6..cf8696bb 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -21,6 +21,10 @@ import ( "fmt" "time" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" + + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" + "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -62,29 +66,14 @@ type ResourceResponseBase struct { AutoSync bool `json:"auto_sync"` } -func resourceCreatedByZadig(resource ResourceWithLabel, projectName string) bool { - resourceLabels := resource.GetLabels() - if resourceLabels == nil { - return false - } - if productName, ok := resourceLabels[setting.ProductLabel]; !ok || productName != projectName { - return false - } - return true -} - func (resp *ResourceResponseBase) setSourceDetailData(resource ResourceWithLabel) { - // make sure the resource is created by zadig - if !resourceCreatedByZadig(resource, resp.ProjectName) { - return - } - envResource, err := commonrepo.NewEnvResourceColl().Find(&commonrepo.QueryEnvResourceOption{ Name: resp.Name, Type: string(resp.Type), EnvName: resp.EnvName, ProductName: resp.ProjectName, IgnoreNotFound: true, + Active: true, }) if err != nil { log.Warnf("failed to f get %v with name : %s, err: %s", string(resp.Type), resp.Name, err) @@ -331,7 +320,7 @@ func getLatestEnvResource(name, resType, envName, productName string) (*models.E }) } -func UpdateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName string, isRollback bool, log *zap.SugaredLogger) error { +func UpdateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName string, updateContentOnly bool, log *zap.SugaredLogger) error { var err error u, err := serializer.NewDecoder().YamlToUnstructured([]byte(args.YamlData)) if err != nil { @@ -345,17 +334,19 @@ func UpdateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName stri return e.ErrUpdateResource.AddDesc(fmt.Sprintf("param commonEnvCfgType:%s not match yaml kind %s ", args.CommonEnvCfgType, u.GetKind())) } - // when rollback env configs, git config should not change - if isRollback { - latestResource, err := getLatestEnvResource(args.Name, string(args.CommonEnvCfgType), args.EnvName, args.ProductName) + // when rollback/autoSync env configs, git config should not change + if updateContentOnly { + if args.LatestEnvResource == nil { + args.LatestEnvResource, err = getLatestEnvResource(args.Name, string(args.CommonEnvCfgType), args.EnvName, args.ProductName) + } if err != nil { return err } - if latestResource == nil { + if args.LatestEnvResource == nil { return fmt.Errorf("failed to find env resource %s:%s", args.Name, string(args.CommonEnvCfgType)) } - args.AutoSync = latestResource.AutoSync - args.SourceDetail = latestResource.SourceDetail + args.AutoSync = args.LatestEnvResource.AutoSync + args.SourceDetail = args.LatestEnvResource.SourceDetail } else { args.SourceDetail = geneSourceDetail(args.GitRepoConfig) } @@ -380,10 +371,18 @@ func UpdateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName stri } type ListCommonEnvCfgHistoryArgs struct { - EnvName string - ProductName string - Name string - CommonEnvCfgType config.CommonEnvCfgType + EnvName string `json:"env_name" form:"envName"` + ProductName string `json:"product_name" form:"projectName"` + Name string `json:"name" form:"name"` + CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type" form:"CommonEnvCfgType"` + AutoSync bool `json:"auto_sync" form:"autoSync"` +} + +type SyncEnvResourceArg struct { + EnvName string `json:"env_name" form:"envName"` + ProductName string `json:"product_name" form:"projectName"` + Name string `json:"name" form:"name"` + Type string `json:"type" form:"type"` } type ListCommonEnvCfgHistoryRes struct { @@ -394,6 +393,7 @@ type ListCommonEnvCfgHistoryRes struct { Namespace string `json:"namespace,omitempty"` EnvName string `json:"env_name"` Name string `json:"name"` + Type string `json:"type"` YamlData string `json:"yaml_data"` SourceDetail *models.CreateFromRepo `json:"source_detail"` } @@ -445,11 +445,6 @@ func ListEnvResourceHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL return nil, e.ErrListResources.AddDesc(fmt.Sprintf("failed to get resource %s:%s from namespace: %s", args.Name, args.CommonEnvCfgType, product.Namespace)) } - // only list history for resources created from environment config management page - if !resourceCreatedByZadig(envResource, product.ProductName) { - return nil, nil - } - opts := &commonrepo.QueryEnvResourceOption{ IsSort: true, ProductName: args.ProductName, @@ -478,6 +473,106 @@ func ListEnvResourceHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL return listRes, nil } +func ListLatestEnvResources(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredLogger) ([]*ListCommonEnvCfgHistoryRes, error) { + opts := &commonrepo.QueryEnvResourceOption{ + ProductName: args.ProductName, + EnvName: args.EnvName, + Name: args.Name, + AutoSync: args.AutoSync, + } + res, err := commonrepo.NewEnvResourceColl().ListLatestResource(opts) + if err != nil { + return nil, e.ErrListResources.AddErr(err) + } + var listRes []*ListCommonEnvCfgHistoryRes + for _, resElem := range res { + listElem := &ListCommonEnvCfgHistoryRes{ + ProductName: resElem.ID.ProjectName, + EnvName: resElem.ID.EnvName, + Type: resElem.ID.Type, + Name: resElem.ID.Name, + CreateTime: resElem.CreateTime, + } + listRes = append(listRes, listElem) + } + return listRes, nil +} + +func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { + + log.Infof("########## syncing env resource: %+v", *args) + + product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ + Name: args.ProductName, + EnvName: args.EnvName, + }) + if err != nil { + return e.ErrUpdateResource.AddErr(fmt.Errorf("failed to find product: %s:%s, err: %s", args.ProductName, args.EnvName, err)) + } + envResource, err := commonrepo.NewEnvResourceColl().Find(&commonrepo.QueryEnvResourceOption{ + ProductName: args.ProductName, + EnvName: args.EnvName, + Name: args.Name, + Type: args.Type, + }) + if err != nil { + return e.ErrUpdateResource.AddErr(fmt.Errorf("failed to find env resource: %s:%s of env: %s:%s", args.Type, args.Name, args.ProductName, args.EnvName)) + } + + if !envResource.AutoSync { + return nil + } + + if envResource.SourceDetail == nil || envResource.SourceDetail.GitRepoConfig == nil { + return e.ErrUpdateResource.AddErr(fmt.Errorf("failed to parse gitops config for resource: %s:%s of env: %s:%s", args.Type, args.Name, args.ProductName, args.EnvName)) + } + + repoConfig := envResource.SourceDetail.GitRepoConfig + yamlData, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ + CodehostID: repoConfig.CodehostID, + Namespace: repoConfig.GetNamespace(), + Owner: repoConfig.Owner, + Repo: repoConfig.Repo, + Path: envResource.SourceDetail.LoadPath, + Branch: repoConfig.Branch, + }) + + u, err := serializer.NewDecoder().YamlToUnstructured(yamlData) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + yamlDataStr, err := ensureLabelAndNs(u, product.Namespace, product.ProductName) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + equal, err := yamlutil.Equal(yamlDataStr, envResource.YamlData) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + + if equal { + log.Infof("yaml data all the same, no need to update") + return nil + } + + err = UpdateCommonEnvCfg(&models.CreateUpdateCommonEnvCfgArgs{ + EnvName: args.EnvName, + ProductName: args.ProductName, + Name: args.Name, + YamlData: yamlDataStr, + RestartAssociatedSvc: false, + CommonEnvCfgType: config.CommonEnvCfgType(envResource.Type), + AutoSync: envResource.AutoSync, + LatestEnvResource: envResource, + }, "cron", true, log) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } + return nil +} + func restartPod(name, productName, envName, namespace string, commonEnvCfgType config.CommonEnvCfgType, clientset *kubernetes.Clientset, kubcli client.Client) error { opts := &commonrepo.ListEnvSvcDependOption{ ProductName: productName, diff --git a/pkg/microservice/cron/core/service/client/env.go b/pkg/microservice/cron/core/service/client/env.go index 2e7400c6..e743e019 100644 --- a/pkg/microservice/cron/core/service/client/env.go +++ b/pkg/microservice/cron/core/service/client/env.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "net/http" + "strings" "github.com/pkg/errors" "go.uber.org/zap" @@ -31,7 +32,7 @@ import ( type EvnListOption struct { BasicFacility string - DeployType string + DeployType []string } func (c *Client) ListEnvs(log *zap.SugaredLogger, option *EvnListOption) ([]*service.ProductRevision, error) { @@ -40,7 +41,7 @@ func (c *Client) ListEnvs(log *zap.SugaredLogger, option *EvnListOption) ([]*ser resp = make([]*service.ProductRevision, 0) ) - url := fmt.Sprintf("%s/environment/revision/products?basicFacility=%s&deployType=%s", c.APIBase, option.BasicFacility, option.DeployType) + url := fmt.Sprintf("%s/environment/revision/products?basicFacility=%s&deployType=%s", c.APIBase, option.BasicFacility, strings.Join(option.DeployType, ",")) request, err := http.NewRequest("GET", url, nil) if err != nil { log.Errorf("ListEnvs new http request error: %v", err) @@ -61,6 +62,33 @@ func (c *Client) ListEnvs(log *zap.SugaredLogger, option *EvnListOption) ([]*ser return resp, errors.WithMessage(err, "failed to list envs") } +func (c *Client) ListEnvResources(productName, envName string, log *zap.SugaredLogger) ([]*service.EnvResource, error) { + var ( + err error + resp = make([]*service.EnvResource, 0) + ) + url := fmt.Sprintf("%s/environment/envcfgs?autoSync=true&projectName=%s&envName=%s", c.APIBase, productName, envName) + request, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Errorf("ListEnvs new http request error: %v", err) + return nil, err + } + + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + var body []byte + body, err = ioutil.ReadAll(ret.Body) + if err == nil { + if err = json.Unmarshal(body, &resp); err == nil { + return resp, nil + } + } + } + + return resp, err +} + func (c *Client) GetEnvService(productName, envName string, log *zap.SugaredLogger) (*service.ProductResp, error) { var ( err error @@ -186,6 +214,25 @@ func (c *Client) SyncEnvVariables(productName, envName string, log *zap.SugaredL return nil } +func (c *Client) SyncEnvResource(productName, envName, resType, resName string, log *zap.SugaredLogger) error { + url := fmt.Sprintf("%s/environment/envcfgs/%s/%s/%s/sync?projectName=%s", c.APIBase, envName, resType, resName, productName) + request, err := http.NewRequest("PUT", url, nil) + if err != nil { + log.Errorf("SyncEnvVariables new http request error: %v", err) + return err + } + + var ret *http.Response + if ret, err = c.Conn.Do(request); err == nil { + defer func() { _ = ret.Body.Close() }() + _, err := ioutil.ReadAll(ret.Body) + if err != nil { + return errors.WithMessage(err, "failed to sync product variables ") + } + } + return nil +} + func (c *Client) GetHostInfo(hostID string, log *zap.SugaredLogger) (*service.PrivateKey, error) { var ( err error diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go b/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go index 92590dcd..f9601e10 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_env_update.go @@ -41,7 +41,7 @@ func needCreateSchedule(rendersetObj *service.ProductRenderset) bool { } func (c *CronClient) UpsertEnvValueSyncScheduler(log *zap.SugaredLogger) { - envs, err := c.AslanCli.ListEnvs(log, &client.EvnListOption{DeployType: setting.HelmDeployType}) + envs, err := c.AslanCli.ListEnvs(log, &client.EvnListOption{DeployType: []string{setting.HelmDeployType}}) if err != nil { log.Error(err) return diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go index f7998a25..8571e559 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go @@ -144,6 +144,10 @@ func (c *CronClient) UpsertWorkflowScheduler(log *zap.SugaredLogger) { if strings.HasPrefix(name, "helm-values-sync-") { continue } + // exclude env resource sync timers + if strings.HasPrefix(name, "env-resource") { + continue + } log.Warnf("[%s]deleted workflow detached", name) if _, ok := c.SchedulerController[name]; ok { c.SchedulerController[name] <- true diff --git a/pkg/microservice/cron/core/service/scheduler/scheduler.go b/pkg/microservice/cron/core/service/scheduler/scheduler.go index e6dccc40..9b5237cd 100644 --- a/pkg/microservice/cron/core/service/scheduler/scheduler.go +++ b/pkg/microservice/cron/core/service/scheduler/scheduler.go @@ -42,16 +42,17 @@ import ( // CronClient ... type CronClient struct { - AslanCli *client.Client - Schedulers map[string]*gocron.Scheduler - SchedulerController map[string]chan bool - lastSchedulers map[string][]*service.Schedule - lastServiceSchedulers map[string]*service.SvcRevision - lastEnvSchedulerData map[string]*service.ProductResp - enabledMap map[string]bool - lastPMProductRevisions []*service.ProductRevision - lastHelmProductRevisions []*service.ProductRevision - log *zap.SugaredLogger + AslanCli *client.Client + Schedulers map[string]*gocron.Scheduler + SchedulerController map[string]chan bool + lastSchedulers map[string][]*service.Schedule + lastServiceSchedulers map[string]*service.SvcRevision + lastEnvSchedulerData map[string]*service.ProductResp + lastEnvResourceSchedulerData map[string]*service.EnvResource + enabledMap map[string]bool + lastPMProductRevisions []*service.ProductRevision + lastHelmProductRevisions []*service.ProductRevision + log *zap.SugaredLogger } type CronV3Client struct { @@ -130,6 +131,8 @@ const ( InitHealthCheckPmHostScheduler = "InitHealthCheckPmHostScheduler" InitHelmEnvSyncValuesScheduler = "InitHelmEnvSyncValuesScheduler" + + EnvResourceSyncScheduler = "EnvResourceSyncScheduler" ) // NewCronClient ... @@ -164,14 +167,15 @@ func NewCronClient() *CronClient { } return &CronClient{ - AslanCli: aslanCli, - Schedulers: make(map[string]*gocron.Scheduler), - lastSchedulers: make(map[string][]*service.Schedule), - lastServiceSchedulers: make(map[string]*service.SvcRevision), - lastEnvSchedulerData: make(map[string]*service.ProductResp), - SchedulerController: make(map[string]chan bool), - enabledMap: make(map[string]bool), - log: log.SugaredLogger(), + AslanCli: aslanCli, + Schedulers: make(map[string]*gocron.Scheduler), + lastSchedulers: make(map[string][]*service.Schedule), + lastServiceSchedulers: make(map[string]*service.SvcRevision), + lastEnvSchedulerData: make(map[string]*service.ProductResp), + lastEnvResourceSchedulerData: make(map[string]*service.EnvResource), + SchedulerController: make(map[string]chan bool), + enabledMap: make(map[string]bool), + log: log.SugaredLogger(), } } @@ -202,6 +206,8 @@ func (c *CronClient) Init() { c.InitHealthCheckPmHostScheduler() // sync values from remote for helm envs at regular intervals c.InitHelmEnvSyncValuesScheduler() + // sync env resources from git at regular intervals + c.InitEnvResourceSyncScheduler() } func (c *CronClient) InitCleanJobScheduler() { @@ -310,3 +316,11 @@ func (c *CronClient) InitHelmEnvSyncValuesScheduler() { c.Schedulers[InitHelmEnvSyncValuesScheduler].Start() } + +func (c *CronClient) InitEnvResourceSyncScheduler() { + c.Schedulers[EnvResourceSyncScheduler] = gocron.NewScheduler() + + c.Schedulers[EnvResourceSyncScheduler].Every(20).Seconds().Do(c.UpsertEnvResourceSyncScheduler, c.log) + + c.Schedulers[EnvResourceSyncScheduler].Start() +} diff --git a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go index 31e4927e..c66df6c7 100644 --- a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go +++ b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go @@ -19,16 +19,14 @@ package scheduler import ( "fmt" - "github.com/koderover/zadig/pkg/setting" - + "github.com/jasonlvhit/gocron" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/cron/core/service" "github.com/koderover/zadig/pkg/microservice/cron/core/service/client" + "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/tool/log" - - "github.com/jasonlvhit/gocron" - "go.uber.org/zap" ) func buildEnvResourceCronKey(envResource *service.EnvResource) string { diff --git a/pkg/microservice/cron/core/service/service.go b/pkg/microservice/cron/core/service/service.go index 5c0a023d..fda848aa 100644 --- a/pkg/microservice/cron/core/service/service.go +++ b/pkg/microservice/cron/core/service/service.go @@ -61,6 +61,20 @@ type ProductRevision struct { IsPublic bool `json:"isPublic"` } +type EnvResource struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type" json:"type"` + ProductName string `bson:"product_name" json:"product_name"` + CreateTime int64 `bson:"create_time" json:"create_time"` + UpdateUserName string `bson:"update_user_name" json:"update_user_name"` + DeletedAt int64 `bson:"deleted_at" json:"deleted_at" ` + Namespace string `bson:"namespace,omitempty" json:"namespace,omitempty"` + EnvName string `bson:"env_name" json:"env_name"` + Name string `bson:"name" json:"name"` + YamlData string `bson:"yaml_data" json:"yaml_data"` + AutoSync bool `bson:"auto_sync" json:"auto_sync"` +} + type ProductResp struct { ID string `json:"id"` ProductName string `json:"product_name"` -- Gitee From 4cbe9d2572bc21bfd52aa9024c8ed1bb168a429f Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 5 Jul 2022 10:35:31 +0800 Subject: [PATCH 2516/2647] optimize code Signed-off-by: allenshen --- .../common/repository/mongodb/env_resource.go | 12 +++------- .../repository/mongodb/template/product.go | 1 - .../environment/handler/common_env_cfg.go | 10 +++------ .../environment/service/common_env_cfg.go | 22 +++++++++---------- .../cron/core/service/client/env.go | 4 ++-- .../scheduler/shedule_env_resource_update.go | 2 +- 6 files changed, 19 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go b/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go index 7621a3fd..4e097960 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/env_resource.go @@ -86,7 +86,7 @@ type QueryEnvResourceOption struct { } type EnvResourceBaseData struct { - ProjectName string `bson:"product_name"` + ProductName string `bson:"product_name"` EnvName string `bson:"env_name"` Name string `bson:"name"` Type string `bson:"type"` @@ -155,10 +155,7 @@ func (c *EnvResourceColl) Find(opt *QueryEnvResourceOption) (*models.EnvResource query["type"] = opt.Type } if opt.Active { - query["deleted_at"] = bson.M{"$or": []bson.M{ - bson.M{"deleted_at": bson.M{"$eq": 0}}, - bson.M{"deleted_at": bson.M{"$eq": nil}}, - }} + query["deleted_at"] = 0 } if len(opt.Id) > 0 { oid, err := primitive.ObjectIDFromHex(opt.Id) @@ -226,10 +223,7 @@ func (c *EnvResourceColl) ListLatestResource(opt *QueryEnvResourceOption) ([]*En ) pipeline = append(pipeline, bson.M{ - "$match": bson.M{"$or": []bson.M{ - bson.M{"deleted_at": bson.M{"$eq": 0}}, - bson.M{"deleted_at": bson.M{"$eq": nil}}, - }}, + "$match": bson.M{"deleted_at": 0}, }) if opt.AutoSync { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go index 37d87ef9..0b09255e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/template/product.go @@ -192,7 +192,6 @@ func (c *ProductColl) ListWithOption(opt *ProductListOpt) ([]*template.Product, query["product_feature.basic_facility"] = opt.BasicFacility } if opt.DeployType != "" { - //query["product_feature.deploy_type"] = opt.DeployType query["product_feature.deploy_type"] = bson.M{"$in": strings.Split(opt.DeployType, ",")} } diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index 1c62359c..22f694c0 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -149,15 +149,11 @@ func SyncEnvResource(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - envName := c.Param("envName") - resType := c.Param("type") - resName := c.Param("objectName") - args := &service.SyncEnvResourceArg{ - EnvName: envName, + EnvName: c.Param("envName"), ProductName: c.Query("projectName"), - Name: resName, - Type: resType, + Name: c.Param("objectName"), + Type: c.Param("type"), } ctx.Err = service.SyncEnvResource(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index cf8696bb..22911c57 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -21,10 +21,6 @@ import ( "fmt" "time" - yamlutil "github.com/koderover/zadig/pkg/util/yaml" - - fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" - "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" @@ -38,6 +34,7 @@ import ( templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + fsservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/fs" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" @@ -45,6 +42,7 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/serializer" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" + yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) type ResourceWithLabel interface { @@ -379,10 +377,10 @@ type ListCommonEnvCfgHistoryArgs struct { } type SyncEnvResourceArg struct { - EnvName string `json:"env_name" form:"envName"` - ProductName string `json:"product_name" form:"projectName"` - Name string `json:"name" form:"name"` - Type string `json:"type" form:"type"` + EnvName string `json:"env_name"` + ProductName string `json:"product_name"` + Name string `json:"name"` + Type string `json:"type"` } type ListCommonEnvCfgHistoryRes struct { @@ -487,7 +485,7 @@ func ListLatestEnvResources(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL var listRes []*ListCommonEnvCfgHistoryRes for _, resElem := range res { listElem := &ListCommonEnvCfgHistoryRes{ - ProductName: resElem.ID.ProjectName, + ProductName: resElem.ID.ProductName, EnvName: resElem.ID.EnvName, Type: resElem.ID.Type, Name: resElem.ID.Name, @@ -500,8 +498,6 @@ func ListLatestEnvResources(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { - log.Infof("########## syncing env resource: %+v", *args) - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: args.ProductName, EnvName: args.EnvName, @@ -536,6 +532,9 @@ func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { Path: envResource.SourceDetail.LoadPath, Branch: repoConfig.Branch, }) + if err != nil { + return e.ErrUpdateResource.AddErr(err) + } u, err := serializer.NewDecoder().YamlToUnstructured(yamlData) if err != nil { @@ -553,7 +552,6 @@ func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { } if equal { - log.Infof("yaml data all the same, no need to update") return nil } diff --git a/pkg/microservice/cron/core/service/client/env.go b/pkg/microservice/cron/core/service/client/env.go index e743e019..ce1dd41b 100644 --- a/pkg/microservice/cron/core/service/client/env.go +++ b/pkg/microservice/cron/core/service/client/env.go @@ -70,7 +70,7 @@ func (c *Client) ListEnvResources(productName, envName string, log *zap.SugaredL url := fmt.Sprintf("%s/environment/envcfgs?autoSync=true&projectName=%s&envName=%s", c.APIBase, productName, envName) request, err := http.NewRequest("GET", url, nil) if err != nil { - log.Errorf("ListEnvs new http request error: %v", err) + log.Errorf("ListEnvResources new http request error: %s", err) return nil, err } @@ -218,7 +218,7 @@ func (c *Client) SyncEnvResource(productName, envName, resType, resName string, url := fmt.Sprintf("%s/environment/envcfgs/%s/%s/%s/sync?projectName=%s", c.APIBase, envName, resType, resName, productName) request, err := http.NewRequest("PUT", url, nil) if err != nil { - log.Errorf("SyncEnvVariables new http request error: %v", err) + log.Errorf("SyncEnvResource new http request error: %v", err) return err } diff --git a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go index c66df6c7..6bea2a45 100644 --- a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go +++ b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go @@ -99,6 +99,6 @@ func (c *CronClient) RunScheduledEnvResourceUpdate(productName, envName, resType log.Infof("start to Run RunScheduledEnvResourceUpdate, productName: %s, envName: %s, resType: %s, resName: %s", productName, envName, resType, resName) err := c.AslanCli.SyncEnvResource(productName, envName, resType, resName, log) if err != nil { - log.Errorf("failed to sync variables for env: %s:%s", productName, envName) + log.Warnf("failed to sync variables for env: %s:%s", productName, envName) } } -- Gitee From fafb479a8e35b9f586783f29e2a06b9deae2d401 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 13:42:58 +0800 Subject: [PATCH 2517/2647] add workflow lint logic Signed-off-by: guoyu --- .../workflow/service/workflow/workflow_v4.go | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 69c43ead..4a3d5a5d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -34,7 +34,8 @@ import ( ) const ( - JobNameRegx = "^[a-z][a-z0-9-]{0,31}$" + JobNameRegx = "^[a-z][a-z0-9-]{0,31}$" + WorkflowRegx = "^[a-z0-9-]{1,32}$" ) func CreateWorkflowV4(user string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { @@ -169,6 +170,16 @@ func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger logger.Errorf(err.Error()) return e.ErrUpsertWorkflow.AddErr(err) } + match, err := regexp.MatchString(WorkflowRegx, workflow.Name) + if err != nil { + logger.Errorf("reg compile failed: %v", err) + return e.ErrUpsertWorkflow.AddErr(err) + } + if !match { + err := fmt.Errorf("workflow name should match %s", WorkflowRegx) + logger.Errorf(err.Error()) + return e.ErrUpsertWorkflow.AddErr(err) + } project, err := templaterepo.NewProductColl().Find(workflow.Project) if err != nil { logger.Errorf("Failed to get project %s, error: %v", workflow.Project, err) @@ -181,8 +192,11 @@ func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger return e.ErrUpsertWorkflow.AddDesc("common workflow only support k8s and helm project") } } - stageNameMap := make(map[string]bool, 0) - jobNameMap := make(map[string]string, 0) + stageNameMap := make(map[string]bool) + jobNameMap := make(map[string]string) + // deploy job can not qoute a build job which runs after it. + buildJobNameMap := make(map[string]string) + reg, err := regexp.Compile(JobNameRegx) if err != nil { logger.Errorf("reg compile failed: %v", err) @@ -195,8 +209,13 @@ func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger logger.Errorf("duplicated stage name: %s", stage.Name) return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("duplicated job name: %s", stage.Name)) } - + stageBuildJobNameMap := make(map[string]string) for _, job := range stage.Jobs { + if !stage.Parallel { + buildJobNameMap[job.Name] = string(job.JobType) + } else { + stageBuildJobNameMap[job.Name] = string(job.JobType) + } if match := reg.MatchString(job.Name); !match { logger.Errorf("job name [%s] did not match %s", job.Name, JobNameRegx) return e.ErrUpsertWorkflow.AddDesc(fmt.Sprintf("job name [%s] did not match %s", job.Name, JobNameRegx)) @@ -217,14 +236,17 @@ func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger if spec.Source != config.SourceFromJob { continue } - jobType, ok := jobNameMap[spec.JobName] + jobType, ok := buildJobNameMap[spec.JobName] if !ok || jobType != string(config.JobZadigBuild) { - errMsg := fmt.Sprintf("build job %s not found", spec.JobName) + errMsg := fmt.Sprintf("can not quote job %s in job %s", spec.JobName, job.Name) logger.Error(errMsg) - return e.ErrCreateTask.AddDesc(errMsg) + return e.ErrUpsertWorkflow.AddDesc(errMsg) } } } + for k, v := range stageBuildJobNameMap { + buildJobNameMap[k] = v + } } return nil } -- Gitee From 985bd0b650e04eee3e2caf0c550da3ddbd5ff6fc Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 15:56:17 +0800 Subject: [PATCH 2518/2647] fix helm deploy Signed-off-by: guoyu --- .../service/workflow/job/job_deploy.go | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index 03185499..85af10f9 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -17,12 +17,15 @@ limitations under the License. package job import ( + "fmt" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types/step" + "github.com/koderover/zadig/pkg/util" ) type DeployJob struct { @@ -84,7 +87,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{Name: j.workflow.Project, EnvName: j.spec.Env}) if err != nil { - return resp, err + return resp, fmt.Errorf("env %s not exists", j.spec.Env) } // get deploy info from previous build job @@ -137,6 +140,22 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { deployServiceMap[deploy.ServiceName] = append(deployServiceMap[deploy.ServiceName], deploy) } for serviceName, deploys := range deployServiceMap { + + var serviceRevision int64 + if pSvc, ok := product.GetServiceMap()[serviceName]; ok { + serviceRevision = pSvc.Revision + } + + revisionSvc, err := commonrepo.NewServiceColl().Find(&commonrepo.ServiceFindOption{ + ServiceName: serviceName, + Revision: serviceRevision, + ProductName: product.ProductName, + }) + if err != nil { + return nil, fmt.Errorf("failed to find service: %s with revision: %d, err: %s", serviceName, serviceRevision, err) + } + releaseName := util.GeneReleaseName(revisionSvc.GetReleaseNaming(), product.ProductName, product.Namespace, product.EnvName, serviceName) + jobTask := &commonmodels.JobTask{ Name: jobNameFormat(serviceName + "-" + j.job.Name), JobType: string(config.JobZadigDeploy), @@ -151,6 +170,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { ServiceName: serviceName, ServiceType: setting.HelmDeployType, ClusterID: product.ClusterID, + ReleaseName: releaseName, } for _, deploy := range deploys { helmDeploySpec.ImageAndModules = append(helmDeploySpec.ImageAndModules, &step.ImageAndServiceModule{ -- Gitee From 9050e3bd22783041e59c2ee7cacbaef7a9981058 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 15:56:42 +0800 Subject: [PATCH 2519/2647] fix log info Signed-off-by: guoyu --- .../aslan/core/workflow/service/stepcontroller/step_deploy.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go index fdc76111..38edcc63 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go @@ -237,8 +237,7 @@ func (s *deployCtl) run(ctx context.Context) error { } } if !replaced { - err = errors.Errorf( - "container %s is not found in resources with label %s", s.deploySpec.ServiceModule, selector) + err = errors.Errorf("service %s container name %s is not found in env %s", s.deploySpec.ServiceName, s.deploySpec.ServiceModule, s.deploySpec.Env) return err } s.step.Spec = s.deploySpec -- Gitee From 21f5bade41e8c2f79fc6ab2f896936bc7d9cc0b5 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 5 Jul 2022 16:39:11 +0800 Subject: [PATCH 2520/2647] fix resource sync bug Signed-off-by: allenshen --- .../environment/service/common_env_cfg.go | 68 +++++++++++++++++-- .../core/environment/service/configmap.go | 24 ------- .../service/scheduler/schedule_workflow.go | 3 +- 3 files changed, 65 insertions(+), 30 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index 22911c57..f051387d 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -174,6 +174,59 @@ func geneSourceDetail(gitRepoConfig *templatemodels.GitRepoConfig) *models.Creat return ret } +func getResourceYamlAndName(sourceYaml []byte, namespace, productName string, resType config.CommonEnvCfgType) (name, yamlData string, err error) { + switch resType { + case config.CommonEnvCfgTypeConfigMap: + cm := &corev1.ConfigMap{} + err = json.Unmarshal(sourceYaml, cm) + if err != nil { + return + } + yamlData, err = ensureLabelAndNs(cm, namespace, productName) + if err != nil { + return + } + name = cm.Name + case config.CommonEnvCfgTypeSecret: + secret := &corev1.Secret{} + err = json.Unmarshal(sourceYaml, secret) + if err != nil { + return + } + yamlData, err = ensureLabelAndNs(secret, namespace, productName) + if err != nil { + return + } + name = secret.Name + case config.CommonEnvCfgTypeIngress: + u, errDecode := serializer.NewDecoder().YamlToUnstructured(sourceYaml) + if errDecode != nil { + err = fmt.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %v", string(sourceYaml), errDecode) + return + } + yamlData, err = ensureLabelAndNs(u, namespace, productName) + if err != nil { + return + } + name = u.GetName() + case config.CommonEnvCfgTypePvc: + pvc := &corev1.PersistentVolumeClaim{} + err = json.Unmarshal(sourceYaml, pvc) + if err != nil { + return + } + yamlData, err = ensureLabelAndNs(pvc, namespace, productName) + if err != nil { + return + } + name = pvc.Name + default: + err = fmt.Errorf("%s is not support create", resType) + return + } + return +} + func CreateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName string, log *zap.SugaredLogger) error { js, err := yaml.YAMLToJSON([]byte(args.YamlData)) if err != nil { @@ -524,7 +577,7 @@ func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { } repoConfig := envResource.SourceDetail.GitRepoConfig - yamlData, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ + sourceYaml, err := fsservice.DownloadFileFromSource(&fsservice.DownloadFromSourceArgs{ CodehostID: repoConfig.CodehostID, Namespace: repoConfig.GetNamespace(), Owner: repoConfig.Owner, @@ -536,17 +589,22 @@ func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { return e.ErrUpdateResource.AddErr(err) } - u, err := serializer.NewDecoder().YamlToUnstructured(yamlData) + js, err := yaml.YAMLToJSON(sourceYaml) if err != nil { return e.ErrUpdateResource.AddErr(err) } - yamlDataStr, err := ensureLabelAndNs(u, product.Namespace, product.ProductName) + resName, yamlData, err := getResourceYamlAndName(js, product.Namespace, args.ProductName, config.CommonEnvCfgType(envResource.Type)) if err != nil { return e.ErrUpdateResource.AddErr(err) } - equal, err := yamlutil.Equal(yamlDataStr, envResource.YamlData) + // asset resource name be the same + if resName != envResource.Name { + return e.ErrUpdateResource.AddDesc(fmt.Sprintf("resource name not match, expect: %s while parsed: %s", envResource.Name, resName)) + } + + equal, err := yamlutil.Equal(yamlData, envResource.YamlData) if err != nil { return e.ErrUpdateResource.AddErr(err) } @@ -559,7 +617,7 @@ func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { EnvName: args.EnvName, ProductName: args.ProductName, Name: args.Name, - YamlData: yamlDataStr, + YamlData: yamlData, RestartAssociatedSvc: false, CommonEnvCfgType: config.CommonEnvCfgType(envResource.Type), AutoSync: envResource.AutoSync, diff --git a/pkg/microservice/aslan/core/environment/service/configmap.go b/pkg/microservice/aslan/core/environment/service/configmap.go index e7b98199..18ecb6bf 100644 --- a/pkg/microservice/aslan/core/environment/service/configmap.go +++ b/pkg/microservice/aslan/core/environment/service/configmap.go @@ -40,11 +40,9 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" - "github.com/koderover/zadig/pkg/shared/kube/wrapper" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/tool/kube/util" ) type ListConfigMapArgs struct { @@ -71,15 +69,6 @@ type UpdateConfigMapArgs struct { Services []string `json:"services"` } -type configMap struct { - Name string `json:"name"` - Data map[string]string `json:"data"` - ModifiedBy string `json:"modifiedBy"` - CreationTimestamp string `json:"creationTimestamp"` - LastUpdateTime string `json:"lastUpdateTime"` - lastUpdateTime time.Time -} - type ListConfigMapRes struct { *ResourceResponseBase CmName string `json:"cm_name"` @@ -87,19 +76,6 @@ type ListConfigMapRes struct { CmData map[string]string `json:"cm_data"` } -func generateConfigMapResponse(cm *corev1.ConfigMap) *configMap { - icm := wrapper.ConfigMap(cm) - u, _ := icm.LastUpdateTime() - return &configMap{ - Name: icm.Name, - Data: icm.Data, - ModifiedBy: icm.ModifiedBy(), - CreationTimestamp: util.FormatTime(icm.CreationTimestamp.Time), - LastUpdateTime: util.FormatTime(u), - lastUpdateTime: u, - } -} - func ListConfigMaps(args *ListConfigMapArgs, log *zap.SugaredLogger) ([]*ListConfigMapRes, error) { selector := labels.Set{}.AsSelector() // Note. when listing configs from [配置管理] on workload page, service name will be passed as query condition diff --git a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go index 8571e559..54e44077 100644 --- a/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go +++ b/pkg/microservice/cron/core/service/scheduler/schedule_workflow.go @@ -123,7 +123,8 @@ func (c *CronClient) UpsertWorkflowScheduler(log *zap.SugaredLogger) { ScheduleNames := sets.NewString( CleanJobScheduler, UpsertWorkflowScheduler, UpsertTestScheduler, InitStatScheduler, InitOperationStatScheduler, - CleanProductScheduler, InitHealthCheckScheduler, InitHealthCheckPmHostScheduler, UpsertColliePipelineScheduler) + CleanProductScheduler, InitHealthCheckScheduler, InitHealthCheckPmHostScheduler, + UpsertColliePipelineScheduler, InitHelmEnvSyncValuesScheduler, EnvResourceSyncScheduler) // 停掉已被删除的pipeline对应的scheduler for name := range c.Schedulers { -- Gitee From 733e212b383ace57e1c00084233ec0028aaa5945 Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 5 Jul 2022 16:55:48 +0800 Subject: [PATCH 2521/2647] optimize code Signed-off-by: allenshen --- .../aslan/core/environment/handler/common_env_cfg.go | 1 - .../aslan/core/environment/service/common_env_cfg.go | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index 22f694c0..72f716a3 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -141,7 +141,6 @@ func ListLatestEnvCfg(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } - ctx.Resp, ctx.Err = service.ListLatestEnvResources(args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index f051387d..97a5d105 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -550,7 +550,6 @@ func ListLatestEnvResources(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL } func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { - product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ Name: args.ProductName, EnvName: args.EnvName, @@ -565,7 +564,7 @@ func SyncEnvResource(args *SyncEnvResourceArg, log *zap.SugaredLogger) error { Type: args.Type, }) if err != nil { - return e.ErrUpdateResource.AddErr(fmt.Errorf("failed to find env resource: %s:%s of env: %s:%s", args.Type, args.Name, args.ProductName, args.EnvName)) + return e.ErrUpdateResource.AddErr(fmt.Errorf("failed to find env resource: %s:%s of env: %s:%s, err: %s", args.Type, args.Name, args.ProductName, args.EnvName, err)) } if !envResource.AutoSync { -- Gitee From 282390f12b7e5eb33d6d97a59a68584396ee0a60 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 19:19:08 +0800 Subject: [PATCH 2522/2647] check service exists before run workflow Signed-off-by: guoyu --- .../service/workflow/job/job_deploy.go | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index 85af10f9..e356ba42 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -90,6 +90,26 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { return resp, fmt.Errorf("env %s not exists", j.spec.Env) } + project, err := templaterepo.NewProductColl().Find(j.workflow.Project) + if err != nil { + return resp, err + } + + productServiceMap := product.GetServiceMap() + + if project.ProductFeature != nil && project.ProductFeature.CreateEnvType == setting.SourceFromExternal { + productServices, err := commonrepo.NewServiceColl().ListExternalWorkloadsBy(j.workflow.Project, j.spec.Env) + if err != nil { + return resp, err + } + for _, service := range productServices { + productServiceMap[service.ServiceName] = &commonmodels.ProductService{ + ServiceName: service.ServiceName, + Containers: service.Containers, + } + } + } + // get deploy info from previous build job if j.spec.Source == config.SourceFromJob { for _, stage := range j.workflow.Stages { @@ -113,6 +133,9 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { } if j.spec.DeployType == setting.K8SDeployType { for _, deploy := range j.spec.ServiceAndImages { + if err := checkServiceAndContainerExsistsInEnv(productServiceMap, deploy.ServiceName, deploy.ServiceModule, j.spec.Env); err != nil { + return resp, err + } jobTask := &commonmodels.JobTask{ Name: jobNameFormat(deploy.ServiceName + "-" + deploy.ServiceModule + j.job.Name), JobType: string(config.JobZadigDeploy), @@ -140,9 +163,8 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { deployServiceMap[deploy.ServiceName] = append(deployServiceMap[deploy.ServiceName], deploy) } for serviceName, deploys := range deployServiceMap { - var serviceRevision int64 - if pSvc, ok := product.GetServiceMap()[serviceName]; ok { + if pSvc, ok := productServiceMap[serviceName]; ok { serviceRevision = pSvc.Revision } @@ -173,6 +195,9 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { ReleaseName: releaseName, } for _, deploy := range deploys { + if err := checkServiceAndContainerExsistsInEnv(productServiceMap, serviceName, deploy.ServiceModule, j.spec.Env); err != nil { + return resp, err + } helmDeploySpec.ImageAndModules = append(helmDeploySpec.ImageAndModules, &step.ImageAndServiceModule{ ServiceModule: deploy.ServiceModule, Image: deploy.Image, @@ -188,3 +213,16 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { j.job.Spec = j.spec return resp, nil } + +func checkServiceAndContainerExsistsInEnv(serviceMap map[string]*commonmodels.ProductService, serviceName, serviceModule, env string) error { + if service, ok := serviceMap[serviceName]; !ok { + return fmt.Errorf("service %s not exists in env %s", serviceName, env) + } else { + for _, container := range service.Containers { + if container.Name == serviceModule { + return nil + } + } + } + return fmt.Errorf("service %s module %s not exists in env %s", serviceName, serviceModule, env) +} -- Gitee From a691614c1807579b27759295ed39985aab6856cf Mon Sep 17 00:00:00 2001 From: allenshen Date: Tue, 5 Jul 2022 19:56:36 +0800 Subject: [PATCH 2523/2647] fix comments and add permission controls Signed-off-by: allenshen --- .../aslan/core/environment/handler/common_env_cfg.go | 2 +- .../aslan/core/environment/service/common_env_cfg.go | 10 +++++----- pkg/microservice/cron/core/service/client/env.go | 4 ---- pkg/microservice/policy/core/yamlconfig/urls.yaml | 6 ++++++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index 72f716a3..34b4ae29 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -125,7 +125,7 @@ func ListCommonEnvCfgHistory(c *gin.Context) { args := new(service.ListCommonEnvCfgHistoryArgs) args.EnvName = c.Param("envName") - args.ProductName = c.Query("projectName") + args.ProjectName = c.Query("projectName") args.CommonEnvCfgType = config.CommonEnvCfgType(c.Query("commonEnvCfgType")) args.Name = c.Param("objectName") diff --git a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go index 97a5d105..507837b7 100644 --- a/pkg/microservice/aslan/core/environment/service/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/service/common_env_cfg.go @@ -423,9 +423,9 @@ func UpdateCommonEnvCfg(args *models.CreateUpdateCommonEnvCfgArgs, userName stri type ListCommonEnvCfgHistoryArgs struct { EnvName string `json:"env_name" form:"envName"` - ProductName string `json:"product_name" form:"projectName"` + ProjectName string `json:"project_name" form:"projectName"` Name string `json:"name" form:"name"` - CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type" form:"CommonEnvCfgType"` + CommonEnvCfgType config.CommonEnvCfgType `json:"common_env_cfg_type" form:"commonEnvCfgType"` AutoSync bool `json:"auto_sync" form:"autoSync"` } @@ -473,7 +473,7 @@ func ListEnvResourceHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL } product, err := commonrepo.NewProductColl().Find(&commonrepo.ProductFindOptions{ - Name: args.ProductName, + Name: args.ProjectName, EnvName: args.EnvName, }) if err != nil { @@ -498,7 +498,7 @@ func ListEnvResourceHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL opts := &commonrepo.QueryEnvResourceOption{ IsSort: true, - ProductName: args.ProductName, + ProductName: args.ProjectName, EnvName: args.EnvName, Name: args.Name, Type: string(args.CommonEnvCfgType), @@ -526,7 +526,7 @@ func ListEnvResourceHistory(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredL func ListLatestEnvResources(args *ListCommonEnvCfgHistoryArgs, log *zap.SugaredLogger) ([]*ListCommonEnvCfgHistoryRes, error) { opts := &commonrepo.QueryEnvResourceOption{ - ProductName: args.ProductName, + ProductName: args.ProjectName, EnvName: args.EnvName, Name: args.Name, AutoSync: args.AutoSync, diff --git a/pkg/microservice/cron/core/service/client/env.go b/pkg/microservice/cron/core/service/client/env.go index ce1dd41b..2bbda456 100644 --- a/pkg/microservice/cron/core/service/client/env.go +++ b/pkg/microservice/cron/core/service/client/env.go @@ -225,10 +225,6 @@ func (c *Client) SyncEnvResource(productName, envName, resType, resName string, var ret *http.Response if ret, err = c.Conn.Do(request); err == nil { defer func() { _ = ret.Body.Close() }() - _, err := ioutil.ReadAll(ret.Body) - if err != nil { - return errors.WithMessage(err, "failed to sync product variables ") - } } return nil } diff --git a/pkg/microservice/policy/core/yamlconfig/urls.yaml b/pkg/microservice/policy/core/yamlconfig/urls.yaml index be8bd9a6..64a3a7f4 100644 --- a/pkg/microservice/policy/core/yamlconfig/urls.yaml +++ b/pkg/microservice/policy/core/yamlconfig/urls.yaml @@ -300,6 +300,12 @@ exemption_urls: - endpoint: api/v1/system-policybindings/?* methods: - DELETE + - endpoint: api/aslan/environment/envcfgs + methods: + - GET + - endpoint: api/aslan/environment/envcfgs/?*/sync + methods: + - PUT project_admin: - endpoint: api/aslan/project/products methods: -- Gitee From 4365c9a4da7e6e2e207aa1f19313c1b5a2bfcca5 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Tue, 5 Jul 2022 20:14:28 +0800 Subject: [PATCH 2524/2647] accelerate list workflow by ignore archived task (#1730) * accelerate list workflow by ignore archived task Signed-off-by: guoyu * create index background Signed-off-by: guoyu * add log Signed-off-by: guoyu * fix error return Signed-off-by: guoyu * change error log to %s Signed-off-by: guoyu Co-authored-by: guoyu --- .../core/common/repository/models/strategy.go | 10 ++- .../core/common/repository/mongodb/task.go | 53 ++++++++++++++- .../aslan/core/system/service/capacity.go | 16 +---- .../workflow/service/workflow/nsq_handlers.go | 9 ++- .../workflow/service/workflow/workflow.go | 68 +++++++++++-------- 5 files changed, 109 insertions(+), 47 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/strategy.go b/pkg/microservice/aslan/core/common/repository/models/strategy.go index ed76876e..7e28ccb4 100644 --- a/pkg/microservice/aslan/core/common/repository/models/strategy.go +++ b/pkg/microservice/aslan/core/common/repository/models/strategy.go @@ -23,9 +23,17 @@ type CapacityTarget string const ( // 工作流任务的留存 - WorkflowTaskRetention CapacityTarget = "WorkflowTaskRetention" + WorkflowTaskRetention CapacityTarget = "WorkflowTaskRetention" + DefaultWorkflowRemainDays int = 365 ) +var DefaultWorkflowTaskRetention = &CapacityStrategy{ + Target: WorkflowTaskRetention, + Retention: &RetentionConfig{ + MaxDays: DefaultWorkflowRemainDays, + }, +} + // RetentionConfig 资源留存相关的配置 type RetentionConfig struct { MaxDays int `bson:"max_days" json:"max_days"` // 最多几天 diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/task.go b/pkg/microservice/aslan/core/common/repository/mongodb/task.go index 0765e5a5..ec94620e 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/task.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/task.go @@ -147,6 +147,14 @@ func (c *TaskColl) EnsureIndex(ctx context.Context) error { }, Options: options.Index().SetUnique(false), }, + { + Keys: bson.D{ + bson.E{Key: "pipeline_name", Value: 1}, + bson.E{Key: "is_archived", Value: 1}, + bson.E{Key: "is_deleted", Value: 1}, + }, + Options: options.Index().SetUnique(false).SetBackground(true), + }, } _, err := c.Indexes().CreateMany(ctx, mod) @@ -311,6 +319,7 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) if option.MergeRequestID != "" { query["trigger_by.merge_request_id"] = option.MergeRequestID } + query["is_archived"] = false query["is_deleted"] = false //是否需要subtask信息 @@ -371,7 +380,7 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) cursor, err := c.Collection.Find(context.TODO(), query, opt) if err != nil { - return + return ret, err } if err = cursor.All(context.TODO(), &ret); err != nil { @@ -380,6 +389,35 @@ func (c *TaskColl) List(option *ListTaskOption) (ret []*TaskPreview, err error) return } +func (c *TaskColl) ListPreview(pipelineNames []string) (ret []*TaskPreview, err error) { + ret = make([]*TaskPreview, 0) + query := bson.M{} + if pipelineNames != nil { + query["pipeline_name"] = bson.M{"$in": pipelineNames} + } + query["is_archived"] = false + query["is_deleted"] = false + selector := bson.D{ + {"task_id", 1}, + {"task_creator", 1}, + {"product_name", 1}, + {"pipeline_name", 1}, + {"status", 1}, + {"create_time", 1}, + {"start_time", 1}, + {"end_time", 1}, + {"type", 1}, + } + opt := options.Find() + opt.SetProjection(selector) + cursor, err := c.Collection.Find(context.TODO(), query, opt) + if err != nil { + return ret, err + } + err = cursor.All(context.TODO(), &ret) + return +} + func (c *TaskColl) Update(args *task.Task) error { if args == nil { return errors.New("nil PipelineTaskV2") @@ -469,6 +507,7 @@ func (c *TaskColl) Count(option *CountTaskOption) (ret int, err error) { if option.Type != "" { query["type"] = option.Type } + query["is_archived"] = false query["is_deleted"] = false // 仅支持查询最近三个月的数据 @@ -637,13 +676,21 @@ func (c *TaskColl) UpdateUnfinishedTask(args *task.Task) error { return err } -func (c *TaskColl) ArchiveHistoryPipelineTask(pipelineName string, taskType config.PipelineType, remain int) error { +func (c *TaskColl) ArchiveHistoryPipelineTask(pipelineName string, taskType config.PipelineType, remain, remainDays int) error { + if remain == 0 && remainDays == 0 { + return nil + } query := bson.M{"pipeline_name": pipelineName, "type": taskType, "is_deleted": false} count, err := c.CountDocuments(context.TODO(), query) if err != nil { return err } - query["task_id"] = bson.M{"$lt": int(count) - remain + 1} + if remain > 0 { + query["task_id"] = bson.M{"$lt": int(count) - remain + 1} + } + if remainDays > 0 { + query["create_time"] = bson.M{"$lt": time.Now().AddDate(0, 0, -remainDays).Unix()} + } change := bson.M{"$set": bson.M{ "is_archived": true, }} diff --git a/pkg/microservice/aslan/core/system/service/capacity.go b/pkg/microservice/aslan/core/system/service/capacity.go index 31419d11..a2f82d9a 100644 --- a/pkg/microservice/aslan/core/system/service/capacity.go +++ b/pkg/microservice/aslan/core/system/service/capacity.go @@ -32,18 +32,6 @@ import ( "github.com/koderover/zadig/pkg/util" ) -const ( - defaultWorkflowMaxDays int = 365 - //logTag = "SysCap" -) - -var defaultWorkflowTaskRetention = &commonmodels.CapacityStrategy{ - Target: commonmodels.WorkflowTaskRetention, - Retention: &commonmodels.RetentionConfig{ - MaxDays: defaultWorkflowMaxDays, - }, -} - func UpdateSysCapStrategy(strategy *commonmodels.CapacityStrategy) error { if err := validateStrategy(strategy); err != nil { return err @@ -63,7 +51,7 @@ func UpdateSysCapStrategy(strategy *commonmodels.CapacityStrategy) error { func GetCapacityStrategy(target commonmodels.CapacityTarget) (*commonmodels.CapacityStrategy, error) { result, err := commonrepo.NewStrategyColl().GetByTarget(target) if err != nil && target == commonmodels.WorkflowTaskRetention { - return defaultWorkflowTaskRetention, nil // Return default setup + return commonmodels.DefaultWorkflowTaskRetention, nil // Return default setup } return result, err } @@ -72,7 +60,7 @@ func HandleSystemGC(dryRun bool) error { // Find the strategy strategy, err := commonrepo.NewStrategyColl().GetByTarget(commonmodels.WorkflowTaskRetention) if err != nil { - strategy = defaultWorkflowTaskRetention + strategy = commonmodels.DefaultWorkflowTaskRetention } else if err = validateStrategy(strategy); err != nil { return err } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go index 9b04a803..626323db 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/nsq_handlers.go @@ -206,8 +206,13 @@ func (h *TaskAckHandler) handle(message *nsq.Message) error { } } - // 更新历史pipeline状态(默认留下前一百个) - if err = h.ptColl.ArchiveHistoryPipelineTask(pt.PipelineName, pt.Type, 100); err != nil { + // update history tasks status to archive + result, err := commonrepo.NewStrategyColl().GetByTarget(commonmodels.WorkflowTaskRetention) + if err != nil { + h.log.Errorf("get workflow task retention strategy error: %s", err) + result = commonmodels.DefaultWorkflowTaskRetention + } + if err = h.ptColl.ArchiveHistoryPipelineTask(pt.PipelineName, pt.Type, result.Retention.MaxItems, result.Retention.MaxDays); err != nil { h.log.Errorf("ArchiveHistoryPipelineTask error: %v", err) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 0f7f997f..74509dc4 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -643,19 +643,12 @@ func ListWorkflows(projects []string, userID string, names []string, log *zap.Su workflowStatMap[s.Name] = s } - recentTaskMap := getTaskMap(workflowNames, "", log) - recentSuccessfulTaskMap := getTaskMap(workflowNames, config.StatusPassed, log) - recentFailedTaskMap := getTaskMap(workflowNames, config.StatusFailed, log) + tasks, err := commonrepo.NewTaskColl().ListPreview(workflowNames) + if err != nil { + log.Warnf("Failed to list workflow tasks, err: %s", err) + } for _, r := range res { - if t, ok := recentTaskMap[r.Name]; ok { - r.RecentTask = t - } - if t, ok := recentSuccessfulTaskMap[r.Name]; ok { - r.RecentSuccessfulTask = t - } - if t, ok := recentFailedTaskMap[r.Name]; ok { - r.RecentFailedTask = t - } + getRecentTaskInfo(r, tasks) if favoriteSet.Has(r.Name) { r.IsFavorite = true @@ -674,24 +667,45 @@ func ListWorkflows(projects []string, userID string, names []string, log *zap.Su return res, nil } -func getTaskMap(names []string, status config.Status, logger *zap.SugaredLogger) map[string]*TaskInfo { - res := make(map[string]*TaskInfo) - - tasks, err := commonrepo.NewTaskColl().ListRecentTasks(&commonrepo.ListTaskOption{PipelineNames: names, Type: config.WorkflowType, Status: status}) - if err != nil { - logger.Warnf("Failed to list tasks, err: %s", err) - return res +func getRecentTaskInfo(workflow *Workflow, tasks []*commonrepo.TaskPreview) { + recentTask := &commonrepo.TaskPreview{} + recentFailedTask := &commonrepo.TaskPreview{} + recentSucceedTask := &commonrepo.TaskPreview{} + for _, task := range tasks { + if task.PipelineName != workflow.Name { + continue + } + if task.TaskID > recentTask.TaskID { + recentTask = task + } + if task.Status == config.StatusPassed && task.TaskID > recentSucceedTask.TaskID { + recentSucceedTask = task + } + if task.Status == config.StatusFailed && task.TaskID > recentFailedTask.TaskID { + recentFailedTask = task + } } - - for _, t := range tasks { - res[t.PipelineName] = &TaskInfo{ - TaskID: t.TaskID, - PipelineName: t.PipelineName, - Status: t.Status, + if recentTask.TaskID > 0 { + workflow.RecentTask = &TaskInfo{ + TaskID: recentTask.TaskID, + PipelineName: recentTask.PipelineName, + Status: string(recentTask.Status), + } + } + if recentSucceedTask.TaskID > 0 { + workflow.RecentSuccessfulTask = &TaskInfo{ + TaskID: recentSucceedTask.TaskID, + PipelineName: recentSucceedTask.PipelineName, + Status: string(recentSucceedTask.Status), + } + } + if recentFailedTask.TaskID > 0 { + workflow.RecentFailedTask = &TaskInfo{ + TaskID: recentFailedTask.TaskID, + PipelineName: recentFailedTask.PipelineName, + Status: string(recentFailedTask.Status), } } - - return res } func ListTestWorkflows(testName string, projects []string, log *zap.SugaredLogger) (workflows []*commonmodels.Workflow, err error) { -- Gitee From 076d81b0883c46c91aeccbd495c6c851cccc8f7e Mon Sep 17 00:00:00 2001 From: shendongdong Date: Tue, 5 Jul 2022 20:14:55 +0800 Subject: [PATCH 2525/2647] fix resource k8s resource remove fail bug (#1733) Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/service/environment.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 15547048..f3baa78d 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -2973,6 +2973,7 @@ func removeOldResources( for name, item := range oldItemsMap { _, exists := itemsMap[name] + item.SetNamespace(env.Namespace) if !exists { if err = updater.DeleteUnstructured(item, kubeClient); err != nil { log.Errorf( -- Gitee From 211f39b5b2c6f14d21a3b9e1d28ca715617a4431 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 20:34:57 +0800 Subject: [PATCH 2526/2647] optimize list workflow Signed-off-by: guoyu --- .../repository/mongodb/workflow_task_v4.go | 9 +- .../core/workflow/handler/workflow_v4.go | 4 +- .../workflow/service/workflow/workflow_v4.go | 89 +++++++++++++++---- 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go index a317f4f7..923dc4d9 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go @@ -31,7 +31,8 @@ import ( ) type ListWorkflowTaskV4Option struct { - WorkflowName string + WorkflowName string + WorkflowNames []string } type WorkflowTaskv4Coll struct { @@ -67,6 +68,7 @@ func (c *WorkflowTaskv4Coll) EnsureIndex(ctx context.Context) error { { Keys: bson.D{ bson.E{Key: "workflow_name", Value: 1}, + bson.E{Key: "is_archived", Value: 1}, bson.E{Key: "is_deleted", Value: 1}, }, Options: options.Index().SetUnique(false), @@ -100,6 +102,11 @@ func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option, pageNum, pageSi if opt.WorkflowName != "" { query["workflow_name"] = opt.WorkflowName } + if opt.WorkflowNames != nil { + query["workflow_name"] = bson.M{"$in": opt.WorkflowNames} + } + query["is_archived"] = false + query["is_deleted"] = false count, err := c.CountDocuments(context.TODO(), query) if err != nil { return nil, 0, err diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index 6ce030eb..d677dec0 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -94,10 +94,10 @@ func ListWorkflowV4(c *gin.Context) { return } - workflowList, total, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, args.PageNum, args.PageSize, ctx.Logger) + workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, ctx.Logger) resp := listWorkflowV4Resp{ WorkflowList: workflowList, - Total: total, + Total: 0, } ctx.Resp = resp ctx.Err = err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 4a3d5a5d..69678c96 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -122,46 +122,101 @@ func DeleteWorkflowV4(name string, logger *zap.SugaredLogger) error { logger.Errorf("Failed to delete WorkflowV4: %s, the error is: %v", name, err) return e.ErrDeleteWorkflow.AddErr(err) } - + if err := commonrepo.NewworkflowTaskv4Coll().DeleteByWorkflowName(name); err != nil { + logger.Errorf("Failed to delete WorkflowV4 task: %s, the error is: %v", name, err) + return e.ErrDeleteWorkflow.AddErr(err) + } if err := commonrepo.NewCounterColl().Delete("WorkflowTaskV4:" + name); err != nil { log.Errorf("Counter.Delete error: %s", err) } return nil } -func ListWorkflowV4(projectName, userID string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*Workflow, int64, error) { +func ListWorkflowV4(projectName, userID string, logger *zap.SugaredLogger) ([]*Workflow, error) { resp := make([]*Workflow, 0) - workflowV4List, total, err := commonrepo.NewWorkflowV4Coll().List(&commonrepo.ListWorkflowV4Option{ + workflowV4List, _, err := commonrepo.NewWorkflowV4Coll().List(&commonrepo.ListWorkflowV4Option{ ProjectName: projectName, - }, pageNum, pageSize) + }, 0, 0) if err != nil { logger.Errorf("Failed to list workflow v4, the error is: %s", err) - return resp, 0, err + return resp, err } workflow, err := ListWorkflows([]string{projectName}, userID, []string{}, logger) if err != nil { - return resp, 0, err + return resp, err + } + workflowList := []string{} + for _, wV4 := range workflowV4List { + workflowList = append(workflowList, wV4.Name) } resp = append(resp, workflow...) + tasks, _, err := commonrepo.NewworkflowTaskv4Coll().List(&commonrepo.ListWorkflowTaskV4Option{WorkflowNames: workflowList}, 0, 0) + if err != nil { + return resp, err + } - for _, workflow := range workflowV4List { + for _, workflowModel := range workflowV4List { stages := []string{} - for _, stage := range workflow.Stages { + for _, stage := range workflowModel.Stages { stages = append(stages, stage.Name) } - resp = append(resp, &Workflow{ - Name: workflow.Name, - ProjectName: workflow.Project, + workflow := &Workflow{ + Name: workflowModel.Name, + ProjectName: workflowModel.Project, EnabledStages: stages, - CreateTime: workflow.CreateTime, - UpdateTime: workflow.UpdateTime, - UpdateBy: workflow.UpdatedBy, + CreateTime: workflowModel.CreateTime, + UpdateTime: workflowModel.UpdateTime, + UpdateBy: workflowModel.UpdatedBy, WorkflowType: "common_workflow", - Description: workflow.Description, - }) + Description: workflowModel.Description, + } + getRecentTaskInfo(workflow, tasks) + + resp = append(resp, workflow) + } + return resp, nil +} + +func getRecentTaskInfo(workflow *Workflow, tasks []*commonmodels.WorkflowTask) { + recentTask := &commonmodels.WorkflowTask{} + recentFailedTask := &commonmodels.WorkflowTask{} + recentSucceedTask := &commonmodels.WorkflowTask{} + for _, task := range tasks { + if task.WorkflowName != workflow.Name { + continue + } + if task.TaskID > recentTask.TaskID { + recentTask = task + } + if task.Status == config.StatusPassed && task.TaskID > recentSucceedTask.TaskID { + recentSucceedTask = task + } + if task.Status == config.StatusFailed && task.TaskID > recentFailedTask.TaskID { + recentFailedTask = task + } + } + if recentTask.TaskID > 0 { + workflow.RecentTask = &TaskInfo{ + TaskID: recentTask.TaskID, + PipelineName: recentTask.WorkflowName, + Status: string(recentTask.Status), + } + } + if recentSucceedTask.TaskID > 0 { + workflow.RecentSuccessfulTask = &TaskInfo{ + TaskID: recentSucceedTask.TaskID, + PipelineName: recentSucceedTask.WorkflowName, + Status: string(recentSucceedTask.Status), + } + } + if recentFailedTask.TaskID > 0 { + workflow.RecentFailedTask = &TaskInfo{ + TaskID: recentFailedTask.TaskID, + PipelineName: recentFailedTask.WorkflowName, + Status: string(recentFailedTask.Status), + } } - return resp, total, nil } func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { -- Gitee From ecda3ce9c37374a4e2db115ab3382f1a22032e42 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 20:55:41 +0800 Subject: [PATCH 2527/2647] archive workflow task v4 Signed-off-by: guoyu --- .../common/repository/mongodb/workflow_task_v4.go | 13 +++++++++++-- .../core/workflow/service/workflow/workflow_v4.go | 4 ++-- .../workflow/service/workflowcontroller/workflow.go | 10 ++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go index 923dc4d9..60ec5978 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go @@ -20,6 +20,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -203,13 +204,21 @@ func (c *WorkflowTaskv4Coll) DeleteByWorkflowName(workflowName string) error { return err } -func (c *WorkflowTaskv4Coll) ArchiveHistoryWorkflowTask(workflowName string, remain int) error { +func (c *WorkflowTaskv4Coll) ArchiveHistoryWorkflowTask(workflowName string, remain, remainDays int) error { + if remain == 0 && remainDays == 0 { + return nil + } query := bson.M{"workflow_name": workflowName, "is_deleted": false} count, err := c.CountDocuments(context.TODO(), query) if err != nil { return err } - query["task_id"] = bson.M{"$lt": int(count) - remain + 1} + if remain > 0 { + query["task_id"] = bson.M{"$lt": int(count) - remain + 1} + } + if remainDays > 0 { + query["create_time"] = bson.M{"$lt": time.Now().AddDate(0, 0, -remainDays).Unix()} + } change := bson.M{"$set": bson.M{ "is_archived": true, }} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 69678c96..3e456c5e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -171,14 +171,14 @@ func ListWorkflowV4(projectName, userID string, logger *zap.SugaredLogger) ([]*W WorkflowType: "common_workflow", Description: workflowModel.Description, } - getRecentTaskInfo(workflow, tasks) + getRecentTaskV4Info(workflow, tasks) resp = append(resp, workflow) } return resp, nil } -func getRecentTaskInfo(workflow *Workflow, tasks []*commonmodels.WorkflowTask) { +func getRecentTaskV4Info(workflow *Workflow, tasks []*commonmodels.WorkflowTask) { recentTask := &commonmodels.WorkflowTask{} recentFailedTask := &commonmodels.WorkflowTask{} recentSucceedTask := &commonmodels.WorkflowTask{} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 2064a05e..0fa75009 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -77,7 +77,8 @@ func CancelWorkflowTask(userName, workflowName string, taskID int64, logger *zap value, ok := cancelChannelMap.Load(fmt.Sprintf("%s-%d", workflowName, taskID)) if !ok { - return fmt.Errorf("no mactched task found, id: %d, workflow name: %s", taskID, workflowName) + logger.Errorf("no mactched task found, id: %d, workflow name: %s", taskID, workflowName) + return nil } if f, ok := value.(context.CancelFunc); ok { f() @@ -194,7 +195,12 @@ func (c *workflowCtl) updateWorkflowTask() { if err := Remove(q); err != nil { c.logger.Errorf("remove queue task: %s:%d error: %v", c.workflowTask.WorkflowName, c.workflowTask.TaskID, err) } - if err = commonrepo.NewworkflowTaskv4Coll().ArchiveHistoryWorkflowTask(c.workflowTask.WorkflowName, 100); err != nil { + result, err := commonrepo.NewStrategyColl().GetByTarget(commonmodels.WorkflowTaskRetention) + if err != nil { + c.logger.Errorf("get workflow task retention strategy error: %s", err) + result = commonmodels.DefaultWorkflowTaskRetention + } + if err = commonrepo.NewworkflowTaskv4Coll().ArchiveHistoryWorkflowTask(c.workflowTask.WorkflowName, result.Retention.MaxItems, result.Retention.MaxDays); err != nil { c.logger.Errorf("ArchiveHistoryWorkflowTask error: %v", err) } } -- Gitee From 17830eb2327151a09bf096b4a5fcbf99c11c621f Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 21:03:45 +0800 Subject: [PATCH 2528/2647] add license file Signed-off-by: guoyu --- .../core/workflow/service/stepcontroller/step.go | 16 ++++++++++++++++ .../service/stepcontroller/step_archive.go | 16 ++++++++++++++++ .../service/stepcontroller/step_deploy.go | 16 ++++++++++++++++ .../service/stepcontroller/step_docker_build.go | 16 ++++++++++++++++ .../workflow/service/stepcontroller/step_git.go | 16 ++++++++++++++++ .../service/stepcontroller/step_helm_deploy.go | 16 ++++++++++++++++ .../service/stepcontroller/step_shell.go | 16 ++++++++++++++++ .../service/stepcontroller/step_tool_install.go | 16 ++++++++++++++++ 8 files changed, 128 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go index b5c234b3..d4e4ff56 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go index 0f1accb9..7bad70ca 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go index 38edcc63..40e2627d 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go index 12ba31a1..881f5834 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go index abc25da7..baa659c6 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go index d8ba81c5..2db826ad 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go index 2a3a8424..2a41f50c 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go index bb796b32..07bd72ef 100644 --- a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go +++ b/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package stepcontroller import ( -- Gitee From 10cb1e193c9d325ea49c10d8a91aee51e421157a Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 22:45:51 +0800 Subject: [PATCH 2529/2647] clean workflow v4 data Signed-off-by: guoyu --- .../repository/mongodb/workflow_task_v4.go | 31 ++++--- .../aslan/core/system/service/capacity.go | 83 +++++++++++++++++-- .../service/workflow/workflow_task_v4.go | 2 +- .../workflow/service/workflow/workflow_v4.go | 2 +- 4 files changed, 100 insertions(+), 18 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go index 60ec5978..80a5c0ed 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_task_v4.go @@ -32,8 +32,12 @@ import ( ) type ListWorkflowTaskV4Option struct { - WorkflowName string - WorkflowNames []string + WorkflowName string + WorkflowNames []string + CreateTime int64 + BeforeCreatTime bool + Limit int + Skip int } type WorkflowTaskv4Coll struct { @@ -97,7 +101,7 @@ func (c *WorkflowTaskv4Coll) Create(obj *models.WorkflowTask) (string, error) { return ID.Hex(), err } -func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option, pageNum, pageSize int64) ([]*models.WorkflowTask, int64, error) { +func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option) ([]*models.WorkflowTask, int64, error) { resp := make([]*models.WorkflowTask, 0) query := bson.M{} if opt.WorkflowName != "" { @@ -108,18 +112,23 @@ func (c *WorkflowTaskv4Coll) List(opt *ListWorkflowTaskV4Option, pageNum, pageSi } query["is_archived"] = false query["is_deleted"] = false + if opt.CreateTime > 0 { + comparison := "$gte" + if opt.BeforeCreatTime { + comparison = "$lte" + } + query["create_time"] = bson.M{comparison: opt.CreateTime} + } count, err := c.CountDocuments(context.TODO(), query) if err != nil { return nil, 0, err } - var findOption *options.FindOptions - if pageNum == 0 && pageSize == 0 { - findOption = options.Find() - } else { - findOption = options.Find(). - SetSort(bson.D{{"create_time", -1}}). - SetSkip((pageNum - 1) * pageSize). - SetLimit(pageSize) + + findOption := options.Find() + if opt.Limit > 0 { + findOption.SetSort(bson.D{{"create_time", -1}}) + findOption.SetSkip(int64(opt.Skip)) + findOption.SetLimit(int64(opt.Limit)) } cursor, err := c.Collection.Find(context.TODO(), query, findOption) diff --git a/pkg/microservice/aslan/core/system/service/capacity.go b/pkg/microservice/aslan/core/system/service/capacity.go index a2f82d9a..4da6d2c3 100644 --- a/pkg/microservice/aslan/core/system/service/capacity.go +++ b/pkg/microservice/aslan/core/system/service/capacity.go @@ -168,17 +168,25 @@ func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, defer sendSyscapNotify(handleErr, &totalCleanTasks) // Figuring out starting option based on capacity strategy - var option *commonrepo.ListAllTaskOption const batch = 100 retention := strategy.Retention if retention.MaxDays > 0 { retentionTime := time.Now().AddDate(0, 0, -retention.MaxDays).Unix() - option = &commonrepo.ListAllTaskOption{ + option := &commonrepo.ListAllTaskOption{ BeforeCreatTime: true, CreateTime: retentionTime, Limit: batch, } totalCleanTasks, handleErr = handleWorkflowTaskRetention(dryRun, batch, option) + if handleErr != nil { + return handleErr + } + v4Option := &commonrepo.ListWorkflowTaskV4Option{ + BeforeCreatTime: true, + CreateTime: retentionTime, + Limit: batch, + } + totalCleanTasks, handleErr = handleWorkflowTaskV4Retention(dryRun, batch, v4Option) return handleErr } @@ -191,7 +199,7 @@ func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, return err } for _, workflow := range workflows { - option = &commonrepo.ListAllTaskOption{ + option := &commonrepo.ListAllTaskOption{ ProductName: workflow.ProductTmplName, PipelineName: workflow.Name, Type: config.WorkflowType, @@ -205,6 +213,26 @@ func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, totalCleanTasks += count } + // clean workflow task v4 data + workflowV4s, _, err := commonrepo.NewWorkflowV4Coll().List(&commonrepo.ListWorkflowV4Option{}, 0, 0) + if err != nil { + log.Errorf("list workflowV4s failed, err:%v", err) + handleErr = err + return err + } + for _, workflow := range workflowV4s { + option := &commonrepo.ListWorkflowTaskV4Option{ + WorkflowName: workflow.Name, + Skip: retention.MaxItems, + Limit: batch, + } + count, err := handleWorkflowTaskV4Retention(dryRun, batch, option) + if err != nil { + continue + } + totalCleanTasks += count + } + // 清理服务工作流任务数据 pipelines, err := commonrepo.NewPipelineColl().List(&commonrepo.PipelineListOption{}) if err != nil { @@ -213,7 +241,7 @@ func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, return err } for _, pipeline := range pipelines { - option = &commonrepo.ListAllTaskOption{ + option := &commonrepo.ListAllTaskOption{ ProductName: pipeline.ProductName, PipelineName: pipeline.Name, Type: config.SingleType, @@ -235,7 +263,7 @@ func handleWorkflowTaskRetentionCenter(strategy *commonmodels.CapacityStrategy, return err } for _, testing := range testings { - option = &commonrepo.ListAllTaskOption{ + option := &commonrepo.ListAllTaskOption{ ProductName: testing.ProductName, PipelineName: fmt.Sprintf("%s-%s", testing.Name, "job"), Type: config.TestType, @@ -286,6 +314,33 @@ func handleWorkflowTaskRetention(dryRun bool, batch int, option *commonrepo.List return len(removeIds), nil } +func handleWorkflowTaskV4Retention(dryRun bool, batch int, option *commonrepo.ListWorkflowTaskV4Option) (int, error) { + s3Server, _ := s3.FindDefaultS3() + // Clean up in batches to prevent pressure of memory spike + var removeIds []string + + for { + staleTasks, _, err := commonrepo.NewworkflowTaskv4Coll().List(option) + if err != nil { + return 0, err + } + if len(staleTasks) == 0 { + break + } + ids := cleanStaleTaskV4s(staleTasks, s3Server, dryRun) + if ids != nil { + removeIds = append(removeIds, ids...) + } + if len(staleTasks) < batch { // last batch + break + } + option.Skip += batch + } + + log.Infof("%d stale workflow tasks will be cleaned up", len(removeIds)) + return len(removeIds), nil +} + // //func (s *Service) logInfo(format string, args ...interface{}) { // s.logger.Infof("[%v]: %v", logTag, fmt.Sprintf(format, args...)) @@ -312,6 +367,24 @@ func cleanStaleTasks(tasks []*task.Task, s3Server *s3.S3, dryRun bool) []string return ids } +func cleanStaleTaskV4s(tasks []*commonmodels.WorkflowTask, s3Server *s3.S3, dryRun bool) []string { + ids := make([]string, len(tasks)) + paths := make([]string, len(tasks)) + for i, task := range tasks { + ids[i] = task.ID.Hex() + paths[i] = fmt.Sprintf("%s/%d/", task.WorkflowName, task.TaskID) + } + forcedPathStyle := true + if s3Server.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + s3client, err := s3tool.NewClient(s3Server.Endpoint, s3Server.Ak, s3Server.Sk, s3Server.Insecure, forcedPathStyle) + if err == nil { + go s3client.RemoveFiles(s3Server.Bucket, paths) + } + return ids +} + func validateStrategy(strategy *commonmodels.CapacityStrategy) error { if strategy.Target == commonmodels.WorkflowTaskRetention { retention := strategy.Retention diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 9d2fb258..25b759b5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -189,7 +189,7 @@ func UpdateWorkflowTaskV4(id string, workflowTask *commonmodels.WorkflowTask, lo } func ListWorkflowTaskV4(workflowName string, pageNum, pageSize int64, logger *zap.SugaredLogger) ([]*commonmodels.WorkflowTask, int64, error) { - resp, total, err := commonrepo.NewworkflowTaskv4Coll().List(&commonrepo.ListWorkflowTaskV4Option{WorkflowName: workflowName}, pageNum, pageSize) + resp, total, err := commonrepo.NewworkflowTaskv4Coll().List(&commonrepo.ListWorkflowTaskV4Option{WorkflowName: workflowName, Limit: int(pageSize), Skip: int((pageNum - 1) * pageSize)}) if err != nil { logger.Errorf("list workflowTaskV4 error: %s", err) return resp, total, err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 3e456c5e..f5a79617 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -151,7 +151,7 @@ func ListWorkflowV4(projectName, userID string, logger *zap.SugaredLogger) ([]*W workflowList = append(workflowList, wV4.Name) } resp = append(resp, workflow...) - tasks, _, err := commonrepo.NewworkflowTaskv4Coll().List(&commonrepo.ListWorkflowTaskV4Option{WorkflowNames: workflowList}, 0, 0) + tasks, _, err := commonrepo.NewworkflowTaskv4Coll().List(&commonrepo.ListWorkflowTaskV4Option{WorkflowNames: workflowList}) if err != nil { return resp, err } -- Gitee From 2868fe74d68cdc6cec2b99d6deb8ee9df077310b Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 5 Jul 2022 22:49:24 +0800 Subject: [PATCH 2530/2647] fix spell error Signed-off-by: guoyu --- docker/service/resource-server.Dockerfile | 2 +- .../aslan/core/common/repository/models/wokflow_task_v4.go | 4 ++-- .../aslan/core/common/repository/models/workflow_v4.go | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docker/service/resource-server.Dockerfile b/docker/service/resource-server.Dockerfile index 201cd5b1..89b81306 100644 --- a/docker/service/resource-server.Dockerfile +++ b/docker/service/resource-server.Dockerfile @@ -4,6 +4,6 @@ WORKDIR /app ADD resource-server-nginx.conf /etc/nginx/conf.d/default.conf ADD docker/dist/reaper . -ADD docker/dist/jobexecutor . +COPY docker/dist/jobexecutor . EXPOSE 80 diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index e4bb3155..2bedfd4d 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -56,7 +56,7 @@ type StageTask struct { Parallel bool `bson:"parallel" json:"parallel"` Approval *Approval `bson:"approval" json:"approval"` Jobs []*JobTask `bson:"jobs" json:"jobs"` - Error string `bson:"error" json:"errror"` + Error string `bson:"error" json:"error"` } type JobTask struct { @@ -68,7 +68,7 @@ type JobTask struct { Error string `bson:"error" json:"error"` Properties JobProperties `bson:"properties" json:"properties"` Steps []*StepTask `bson:"steps" json:"steps"` - Outputs []*Output `bson:"outputs" son:"outputs"` + Outputs []*Output `bson:"outputs" json:"outputs"` } type StepTask struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 05944bd3..a959cc4f 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -19,11 +19,12 @@ package models import ( "fmt" + "go.mongodb.org/mongo-driver/bson/primitive" + "gopkg.in/yaml.v3" + "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/setting" "github.com/koderover/zadig/pkg/types" - "go.mongodb.org/mongo-driver/bson/primitive" - "gopkg.in/yaml.v3" ) type WorkflowV4 struct { -- Gitee From bf7d78f03f39560428ed53eaef58c53d9f56429b Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 6 Jul 2022 14:01:24 +0800 Subject: [PATCH 2531/2647] fix ua combine env resource data error bug (#1735) Signed-off-by: allenshen --- .../internal/repository/mongodb/env_resources.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go b/pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go index a2d1910d..7454f869 100644 --- a/pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go +++ b/pkg/cli/upgradeassistant/internal/repository/mongodb/env_resources.go @@ -39,6 +39,9 @@ func NewEnvResourceColl() *EnvResourceColl { } func (c *EnvResourceColl) BatchInsert(resources []*models.EnvResource) error { + if len(resources) == 0 { + return nil + } resList := make([]interface{}, 0, len(resources)) for _, res := range resources { resList = append(resList, res) -- Gitee From 6cf72ab648c38eb38cae142a5c9311801b3f7c22 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 6 Jul 2022 18:15:26 +0800 Subject: [PATCH 2532/2647] common workflow support build template (#1738) Signed-off-by: guoyu Co-authored-by: guoyu --- .../service/workflow/job/job_build.go | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 7d7a6204..f463f24d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -27,6 +27,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/types/step" @@ -59,6 +60,9 @@ func (j *BuildJob) SetPreset() error { if err != nil { return err } + if err := fillBuildDetail(buildInfo, build.ServiceName, build.ServiceModule); err != nil { + return err + } for _, target := range buildInfo.Targets { if target.ServiceName == build.ServiceName && target.ServiceModule == build.ServiceModule { build.Repos = buildInfo.Repos @@ -115,6 +119,9 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { if err != nil { return resp, err } + if err := fillBuildDetail(buildInfo, build.ServiceName, build.ServiceModule); err != nil { + return resp, err + } jobTask.Properties = commonmodels.JobProperties{ Timeout: int64(buildInfo.Timeout), ResourceRequest: buildInfo.PreBuild.ResReq, @@ -325,3 +332,40 @@ func modelS3toS3(modelS3 *commonmodels.S3Storage) *step.S3 { } return resp } + +func fillBuildDetail(moduleBuild *commonmodels.Build, serviceName, serviceModule string) error { + if moduleBuild.TemplateID == "" { + return nil + } + buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: moduleBuild.TemplateID, + }) + if err != nil { + return fmt.Errorf("failed to find build template with id: %s, err: %s", moduleBuild.TemplateID, err) + } + + moduleBuild.Timeout = buildTemplate.Timeout + moduleBuild.PreBuild = buildTemplate.PreBuild + moduleBuild.JenkinsBuild = buildTemplate.JenkinsBuild + moduleBuild.Scripts = buildTemplate.Scripts + moduleBuild.PostBuild = buildTemplate.PostBuild + moduleBuild.SSHs = buildTemplate.SSHs + moduleBuild.PMDeployScripts = buildTemplate.PMDeployScripts + moduleBuild.CacheEnable = buildTemplate.CacheEnable + moduleBuild.CacheDirType = buildTemplate.CacheDirType + moduleBuild.CacheUserDir = buildTemplate.CacheUserDir + moduleBuild.AdvancedSettingsModified = buildTemplate.AdvancedSettingsModified + + // repos are configured by service modules + for _, serviceConfig := range moduleBuild.Targets { + if serviceConfig.ServiceName == serviceName && serviceConfig.ServiceModule == serviceModule { + moduleBuild.Repos = serviceConfig.Repos + if moduleBuild.PreBuild == nil { + moduleBuild.PreBuild = &commonmodels.PreBuild{} + } + moduleBuild.PreBuild.Envs = commonservice.MergeBuildEnvs(moduleBuild.PreBuild.Envs, serviceConfig.Envs) + break + } + } + return nil +} -- Gitee From a30956487426629b68c919ac15c84436c0d24e41 Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Wed, 6 Jul 2022 19:40:15 +0800 Subject: [PATCH 2533/2647] Bugfix for webhook triggers when wrong target branch merge event is sent (#1740) * stuff Signed-off-by: Min Min * debug Signed-off-by: Min Min * debug Signed-off-by: Min Min * fix matcher for github Signed-off-by: Min Min Co-authored-by: Min Min --- .../core/workflow/service/webhook/github_scanning_task.go | 7 ++++++- .../core/workflow/service/webhook/gitlab_scanning_task.go | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go index 7eb567ed..e4531655 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_scanning_task.go @@ -17,6 +17,8 @@ limitations under the License. package webhook import ( + "strconv" + "github.com/google/go-github/v35/github" "github.com/hashicorp/go-multierror" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -25,7 +27,6 @@ import ( scanningservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/testing/service" "github.com/koderover/zadig/pkg/types" "go.uber.org/zap" - "strconv" ) type gitEventMatcherForScanning interface { @@ -117,6 +118,10 @@ func (gpem githubPushEventMatcherForScanning) Match(hookRepo *types.ScanningHook return false, nil } + if hookRepo.Branch != getBranchFromRef(*ev.Ref) { + return false, nil + } + hookRepo.Branch = getBranchFromRef(*ev.Ref) var changedFiles []string for _, commit := range ev.Commits { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go index bc38cd0d..957e5df0 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/gitlab_scanning_task.go @@ -151,6 +151,9 @@ func (gpem *gitlabPushEventMatcherForScanning) Match(hookRepo *types.ScanningHoo if !EventConfigured(matchRepo, config.HookEventPush) { return false, nil } + if hookRepo.Branch != getBranchFromRef(ev.Ref) { + return false, nil + } hookRepo.Branch = getBranchFromRef(ev.Ref) var changedFiles []string -- Gitee From 34dea424d35d38229a07d796c9d17556ecc255d9 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 6 Jul 2022 21:05:41 +0800 Subject: [PATCH 2534/2647] workflow preset api return all builds (#1742) Signed-off-by: guoyu Co-authored-by: guoyu --- .../workflow/service/workflow/workflow.go | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 8776d3ae..35e7d333 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -18,7 +18,6 @@ package workflow import ( "fmt" - "strings" "sync" "github.com/hashicorp/go-multierror" @@ -404,41 +403,29 @@ func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, log.Errorf("[Build.List] error: %v", err) return nil, e.ErrListBuildModule.AddErr(err) } - for k, v := range targets { - // 选择了一个特殊字符在项目名称、服务名称以及服务组件名称里面都不允许的特殊字符,避免出现异常 - targetArr := strings.Split(k, SplitSymbol) - if len(targetArr) != 3 { - continue - } - - preSet := &PreSetResp{ - Target: &commonmodels.ServiceModuleTarget{ - ProductName: targetArr[0], - ServiceName: targetArr[1], - ServiceModule: targetArr[2], - }, - Deploy: v, - BuildModuleVers: []string{}, - Repos: make([]*types.Repository, 0), - } - - for _, mo := range moList { - for _, moTarget := range mo.Targets { - moduleTargetStr := fmt.Sprintf("%s%s%s%s%s", moTarget.ProductName, SplitSymbol, moTarget.ServiceName, SplitSymbol, moTarget.ServiceModule) - if moduleTargetStr == k { - if mo.TemplateID != "" { - preSet.Repos = moTarget.Repos - } else { - preSet.Repos = mo.SafeRepos() - } - preSet.Target.BuildName = mo.Name + for _, mo := range moList { + for _, moTarget := range mo.Targets { + target := fmt.Sprintf("%s%s%s%s%s", moTarget.ProductName, SplitSymbol, moTarget.ServiceName, SplitSymbol, moTarget.ServiceModule) + if v, ok := targets[target]; ok { + preSet := &PreSetResp{ + Target: &commonmodels.ServiceModuleTarget{ + ProductName: moTarget.ProductName, + ServiceName: moTarget.ServiceName, + ServiceModule: moTarget.ServiceModule, + BuildName: mo.Name, + }, + Deploy: v, + BuildModuleVers: []string{}, + Repos: make([]*types.Repository, 0), } + if mo.TemplateID != "" { + preSet.Repos = moTarget.Repos + } else { + preSet.Repos = mo.SafeRepos() + } + resp = append(resp, preSet) } } - for _, repo := range preSet.Repos { - repo.RepoNamespace = repo.GetRepoNamespace() - } - resp = append(resp, preSet) } return resp, nil } -- Gitee From 0083d7c8db25349932605c09b99749de22aac9b6 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 6 Jul 2022 21:05:58 +0800 Subject: [PATCH 2535/2647] fix common workflow deploy job name (#1743) Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/service/workflow/job/job_deploy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index e356ba42..179c4c7a 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -137,7 +137,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { return resp, err } jobTask := &commonmodels.JobTask{ - Name: jobNameFormat(deploy.ServiceName + "-" + deploy.ServiceModule + j.job.Name), + Name: jobNameFormat(deploy.ServiceName + "-" + deploy.ServiceModule + "-" + j.job.Name), JobType: string(config.JobZadigDeploy), } deployStep := &commonmodels.StepTask{ -- Gitee From 9f2dff8dc3201edce3b056c2cecefcf7ab40e8ff Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 6 Jul 2022 21:10:32 +0800 Subject: [PATCH 2536/2647] fix pipeline render nil caused aslan panic (#1744) Signed-off-by: allenshen --- pkg/microservice/aslan/core/common/service/delivery.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/delivery.go b/pkg/microservice/aslan/core/common/service/delivery.go index 19ddb029..1e2c02bc 100644 --- a/pkg/microservice/aslan/core/common/service/delivery.go +++ b/pkg/microservice/aslan/core/common/service/delivery.go @@ -374,10 +374,12 @@ func getProductEnvInfo(pipelineTask *taskmodels.Task, log *zap.SugaredLogger) (* product.Namespace = productInfo.Namespace } - if renderSet, err := GetRenderSet(product.Namespace, pipelineTask.Render.Revision, false, product.Namespace, log); err == nil { - product.Vars = renderSet.KVs - } else { - log.Errorf("GetProductEnvInfo GetRenderSet namespace:%s pipelineTask.Render.Revision:%d err:%v", product.GetNamespace(), pipelineTask.Render.Revision, err) + if pipelineTask.Render != nil { + if renderSet, err := GetRenderSet(product.Namespace, pipelineTask.Render.Revision, false, product.EnvName, log); err == nil { + product.Vars = renderSet.KVs + } else { + log.Warnf("GetProductEnvInfo GetRenderSet namespace:%s pipelineTask.Render.Revision:%d err:%v", product.GetNamespace(), pipelineTask.Render.Revision, err) + } } //返回中的ProductName即产品模板的名称 -- Gitee From 93709ef4dfd8aab8c347a44a800625ce87e2b9a5 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Thu, 7 Jul 2022 09:47:05 +0800 Subject: [PATCH 2537/2647] skip get repo info when repo source from others (#1750) Signed-off-by: guoyu Co-authored-by: guoyu --- pkg/microservice/aslan/core/code/service/repo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/code/service/repo.go b/pkg/microservice/aslan/core/code/service/repo.go index d2187a35..8f2cd5fb 100644 --- a/pkg/microservice/aslan/core/code/service/repo.go +++ b/pkg/microservice/aslan/core/code/service/repo.go @@ -72,7 +72,7 @@ func ListRepoInfos(infos []*GitRepoInfo, log *zap.SugaredLogger) ([]*GitRepoInfo return nil, err } if ch.Type == setting.SourceFromOther { - return []*GitRepoInfo{}, nil + continue } codehostClient, err := open.OpenClient(ch, log) if err != nil { -- Gitee From dd164d62acbec7a459d7f50a3545c166fb9719e4 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Thu, 7 Jul 2022 11:02:24 +0800 Subject: [PATCH 2538/2647] get build envs when it's a template build (#1752) Signed-off-by: guoyu Co-authored-by: guoyu --- pkg/microservice/aslan/core/build/service/build.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 2692048c..060fcf02 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -141,6 +141,14 @@ func ListBuildModulesByServiceModule(productName string, log *zap.SugaredLogger) } var resp []*BuildResp for _, build := range buildModules { + // get build env vars when it's a template build + if build.TemplateID != "" { + for _, target := range build.Targets { + if target.ServiceModule == container.Name && target.ServiceName == serviceTmpl.ServiceName { + build.PreBuild.Envs = target.Envs + } + } + } resp = append(resp, &BuildResp{ ID: build.ID.Hex(), Name: build.Name, -- Gitee From 2956eaa08acf616d3efe437ea01ed432799280e5 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 7 Jul 2022 13:39:45 +0800 Subject: [PATCH 2539/2647] fix env resource cron bug (#1755) Signed-off-by: allenshen --- .../core/service/scheduler/shedule_env_resource_update.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go index 6bea2a45..e217daee 100644 --- a/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go +++ b/pkg/microservice/cron/core/service/scheduler/shedule_env_resource_update.go @@ -37,6 +37,7 @@ func (c *CronClient) deleteEnvResourceScheduler(envResourceKey string) { log.Infof("deleting single env resource scheduler: %s", envResourceKey) if _, ok := c.SchedulerController[envResourceKey]; ok { c.SchedulerController[envResourceKey] <- true + delete(c.SchedulerController, envResourceKey) } if _, ok := c.Schedulers[envResourceKey]; ok { c.Schedulers[envResourceKey].Clear() @@ -48,7 +49,7 @@ func (c *CronClient) UpsertEnvResourceSyncScheduler(log *zap.SugaredLogger) { envs, err := c.AslanCli.ListEnvs(log, &client.EvnListOption{DeployType: []string{setting.HelmDeployType, setting.K8SDeployType}}) if err != nil { - log.Error(err) + log.Errorf("failed to list envs for env resource update: %s", err) return } @@ -92,6 +93,7 @@ func (c *CronClient) UpsertEnvResourceSyncScheduler(log *zap.SugaredLogger) { for _, k := range lastScheduler.List() { c.deleteEnvResourceScheduler(k) + delete(c.lastEnvResourceSchedulerData, k) } } -- Gitee From 0ca3472c2716bcff36da893d170c85b5aec754cd Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 7 Jul 2022 15:40:34 +0800 Subject: [PATCH 2540/2647] fix product render version error bug (#1757) * fix product render version error bug Signed-off-by: allenshen * remove unexpected change Signed-off-by: allenshen --- .../core/environment/service/environment.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index f3baa78d..1d8b4b12 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -3505,17 +3505,18 @@ func updateProductGroup(username, productName, envName string, productResp *comm return svcNameSet.Has(svc.ServiceName) } + productResp.Render.Revision = renderSet.Revision + if err = commonrepo.NewProductColl().Update(productResp); err != nil { + log.Errorf("Failed to update env, err: %s", err) + return err + } + err = proceedHelmRelease(productName, envName, productResp, renderSet, helmClient, filter, log) if err != nil { log.Errorf("error occurred when upgrading services in env: %s/%s, err: %s ", productName, envName, err) return err } - productResp.Render.Revision = renderSet.Revision - if err = commonrepo.NewProductColl().Update(productResp); err != nil { - log.Errorf("Failed to update env, err: %s", err) - return err - } return nil } @@ -3807,11 +3808,8 @@ func proceedHelmRelease(productName, envName string, productResp *commonmodels.P log.Errorf("Failed to update service group %d. Error: %v", groupIndex, err) return err } - if errList.ErrorOrNil() != nil { - return errList.ErrorOrNil() - } } - return nil + return errList.ErrorOrNil() } func setFieldValueIsNotExist(obj map[string]interface{}, value interface{}, fields ...string) map[string]interface{} { -- Gitee From 5409a6e5fd5df0589c88294ee6473fe4ad464a35 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Thu, 7 Jul 2022 17:45:51 +0800 Subject: [PATCH 2541/2647] fix gitee fetch project bug (#1760) * fix gitee fetch project bug Signed-off-by: allenshen * add api reference Signed-off-by: allenshen --- pkg/tool/gitee/repositories.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/tool/gitee/repositories.go b/pkg/tool/gitee/repositories.go index d59cd937..124223ae 100644 --- a/pkg/tool/gitee/repositories.go +++ b/pkg/tool/gitee/repositories.go @@ -43,12 +43,13 @@ func (c *Client) ListRepositoriesForAuthenticatedUser(accessToken, keyword strin httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) + // api reference: https://gitee.com/api/v5/swagger#/getV5UserRepos url := "/v5/user/repos" queryParams := make(map[string]string) queryParams["access_token"] = accessToken //queryParams["visibility"] = "all" //queryParams["affiliation"] = "owner" - queryParams["type"] = "owner" + queryParams["type"] = "personal" queryParams["q"] = keyword queryParams["page"] = strconv.Itoa(page) queryParams["per_page"] = strconv.Itoa(perPage) @@ -66,6 +67,7 @@ func (c *Client) ListRepositoriesForOrg(accessToken, org string, page, perPage i httpClient := httpclient.New( httpclient.SetHostURL(GiteeHOSTURL), ) + // api reference: https://gitee.com/api/v5/swagger#/getV5OrgsOrgRepos url := fmt.Sprintf("/v5/orgs/%s/repos", org) queryParams := make(map[string]string) queryParams["access_token"] = accessToken -- Gitee From 1cd4b09dcccef0778a00f4218dcd10c45218e114 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Thu, 7 Jul 2022 17:53:42 +0800 Subject: [PATCH 2542/2647] add common workflow permission (#1754) * add common workflow permission Signed-off-by: guoyu * when no workflow names return all common workflow Signed-off-by: guoyu * add lint to create workflow permission Signed-off-by: guoyu Co-authored-by: guoyu --- .../common/repository/mongodb/workflow_v4.go | 1 + .../core/workflow/handler/workflow_v4.go | 3 +- .../workflow/service/workflow/workflow_v4.go | 12 ++++--- .../policy/core/yamlconfig/metas.yaml | 33 +++++++++++++++++++ 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go index 8398cc89..0e935a1d 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/workflow_v4.go @@ -86,6 +86,7 @@ func (c *WorkflowV4Coll) List(opt *ListWorkflowV4Option, pageNum, pageSize int64 if opt.ProjectName != "" { query["project"] = opt.ProjectName } + count, err := c.CountDocuments(context.TODO(), query) if err != nil { return nil, 0, err diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index d677dec0..20d4ee83 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -93,8 +93,9 @@ func ListWorkflowV4(c *gin.Context) { ctx.Err = err return } + workflowNames, _ := internalhandler.GetResourcesInHeader(c) - workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, ctx.Logger) + workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, workflowNames, ctx.Logger) resp := listWorkflowV4Resp{ WorkflowList: workflowList, Total: 0, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index f5a79617..d8daacc7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -132,7 +132,7 @@ func DeleteWorkflowV4(name string, logger *zap.SugaredLogger) error { return nil } -func ListWorkflowV4(projectName, userID string, logger *zap.SugaredLogger) ([]*Workflow, error) { +func ListWorkflowV4(projectName, userID string, names []string, logger *zap.SugaredLogger) ([]*Workflow, error) { resp := make([]*Workflow, 0) workflowV4List, _, err := commonrepo.NewWorkflowV4Coll().List(&commonrepo.ListWorkflowV4Option{ ProjectName: projectName, @@ -142,10 +142,14 @@ func ListWorkflowV4(projectName, userID string, logger *zap.SugaredLogger) ([]*W return resp, err } - workflow, err := ListWorkflows([]string{projectName}, userID, []string{}, logger) - if err != nil { - return resp, err + workflow := []*Workflow{} + if len(names) > 0 { + workflow, err = ListWorkflows([]string{projectName}, userID, names, logger) + if err != nil { + return resp, err + } } + workflowList := []string{} for _, wV4 := range workflowV4List { workflowList = append(workflowList, wV4.Name) diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index 47d11fd6..0597a11d 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -464,6 +464,23 @@ metas: endpoint: /api/aslan/workflow/v3/?*/args - method: GET endpoint: /api/aslan/workflow/servicetask/workflows/?*/?*/?*/?* + - method: GET + endpoint: api/aslan/workflow/v4 + filter: true + - method: GET + endpoint: /api/aslan/workflow/v4/name/?* + - method: GET + endpoint: /api/aslan/workflow/v4/preset/?* + - method: GET + endpoint: /api/aslan/workflow/v4/workflowtask + - method: GET + endpoint: /api/aslan/workflow/v4/workflowtask/workflow/?*/task/?* + - method: GET + endpoint: /api/aslan/workflow/v4/workflowtask/clone/workflow/?*/task/?* + - method: GET + endpoint: /api/aslan/logs/log/v4/workflow/?*/tasks/?*/jobs/?* + - method: GET + endpoint: /api/aslan/logs/sse/v4/workflow/?*/tasks/?*/jobs/?* - action: edit_workflow alias: 编辑 description: '' @@ -480,6 +497,10 @@ metas: endpoint: /api/aslan/workflow/v3/?* - method: GET endpoint: /api/aslan/build/build/serviceModule + - method: PUT + endpoint: /api/aslan/workflow/v4/?* + - method: POST + endpoint: /api/aslan/workflow/v4/lint - action: create_workflow alias: 新建 description: '' @@ -500,6 +521,10 @@ metas: endpoint: /api/aslan/testing/testdetail - method: POST endpoint: /api/aslan/workflow/v3 + - method: POST + endpoint: /api/aslan/workflow/v4 + - method: POST + endpoint: /api/aslan/workflow/v4/lint - action: delete_workflow alias: 删除 description: '' @@ -508,6 +533,8 @@ metas: endpoint: /api/aslan/workflow/workflow/?* - method: DELETE endpoint: /api/aslan/workflow/v3/?* + - method: DELETE + endpoint: /api/aslan/workflow/v4/?* - action: run_workflow alias: 执行 description: '' @@ -538,6 +565,12 @@ metas: endpoint: /api/aslan/workflow/v3/workflowtask/id/?*/name/?*/restart - method: DELETE endpoint: /api/aslan/workflow/v3/workflowtask/id/?*/name/?* + - method: POST + endpoint: /api/aslan/workflow/v4/workflowtask + - method: DELETE + endpoint: /api/aslan/workflow/v4/workflowtask/workflow/?*/task/?* + - method: POST + endpoint: /api/aslan/workflow/v4/workflowtask/approve - resource: Environment alias: 环境 description: '' -- Gitee From e2d4f93f5ce1197901c857d6ffc6dc89147d33ef Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Thu, 7 Jul 2022 18:21:59 +0800 Subject: [PATCH 2543/2647] filter jenkins build for service build (#1759) * filter jenkins build for service build Signed-off-by: guoyu * fix param name Signed-off-by: guoyu * fix params Signed-off-by: guoyu Co-authored-by: guoyu --- pkg/microservice/aslan/core/build/handler/build.go | 6 +++++- pkg/microservice/aslan/core/build/service/build.go | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/build/handler/build.go b/pkg/microservice/aslan/core/build/handler/build.go index c7f36201..83f7da2e 100644 --- a/pkg/microservice/aslan/core/build/handler/build.go +++ b/pkg/microservice/aslan/core/build/handler/build.go @@ -47,8 +47,12 @@ func ListBuildModules(c *gin.Context) { func ListBuildModulesByServiceModule(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + var excludeJenkins bool + if c.Query("excludeJenkins") == "true" { + excludeJenkins = true + } - ctx.Resp, ctx.Err = buildservice.ListBuildModulesByServiceModule(c.Query("projectName"), ctx.Logger) + ctx.Resp, ctx.Err = buildservice.ListBuildModulesByServiceModule(c.Query("projectName"), excludeJenkins, ctx.Logger) } func CreateBuildModule(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 060fcf02..2e2487aa 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -118,7 +118,7 @@ func ListBuild(name, targets, productName string, log *zap.SugaredLogger) ([]*Bu return resp, nil } -func ListBuildModulesByServiceModule(productName string, log *zap.SugaredLogger) ([]*ServiceModuleAndBuildResp, error) { +func ListBuildModulesByServiceModule(productName string, excludeJenkins bool, log *zap.SugaredLogger) ([]*ServiceModuleAndBuildResp, error) { services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) if err != nil { return nil, e.ErrListBuildModule.AddErr(err) @@ -141,6 +141,9 @@ func ListBuildModulesByServiceModule(productName string, log *zap.SugaredLogger) } var resp []*BuildResp for _, build := range buildModules { + if excludeJenkins && build.JenkinsBuild != nil { + continue + } // get build env vars when it's a template build if build.TemplateID != "" { for _, target := range build.Targets { -- Gitee From aedaed85fdc2f8f13a2257a60db83cd4e9a32a1f Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Thu, 7 Jul 2022 21:30:44 +0800 Subject: [PATCH 2544/2647] add count user by type api (#1764) * add count user by type api Signed-off-by: Min Min * minor bugfix Signed-off-by: Min Min * even more stuff Signed-off-by: Min Min * even more stuff Signed-off-by: Min Min * even more stuff Signed-off-by: Min Min * even more stuff Signed-off-by: Min Min * even more stuff Signed-off-by: Min Min Co-authored-by: Min Min --- pkg/microservice/user/core/handler/router.go | 2 ++ pkg/microservice/user/core/handler/user/user.go | 7 +++++++ pkg/microservice/user/core/repository/orm/user.go | 10 ++++++++++ pkg/microservice/user/core/service/user/user.go | 9 +++++++++ pkg/types/user.go | 5 +++++ 5 files changed, 33 insertions(+) diff --git a/pkg/microservice/user/core/handler/router.go b/pkg/microservice/user/core/handler/router.go index a764ce41..f48cfbd8 100644 --- a/pkg/microservice/user/core/handler/router.go +++ b/pkg/microservice/user/core/handler/router.go @@ -48,6 +48,8 @@ func (*Router) Inject(router *gin.RouterGroup) { users.POST("/users/ldap/:ldapId", user.SyncLdapUser) + users.GET("/user/count", user.CountSystemUsers) + router.GET("login", login.Login) router.GET("login-enabled", login.ThirdPartyLoginEnabled) diff --git a/pkg/microservice/user/core/handler/user/user.go b/pkg/microservice/user/core/handler/user/user.go index 76f9e4e2..d58f1f4c 100644 --- a/pkg/microservice/user/core/handler/user/user.go +++ b/pkg/microservice/user/core/handler/user/user.go @@ -34,6 +34,13 @@ func SyncLdapUser(c *gin.Context) { ctx.Err = user.SearchAndSyncUser(ldapID, ctx.Logger) } +func CountSystemUsers(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = user.GetUserCount(ctx.Logger) +} + func GetUser(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/user/core/repository/orm/user.go b/pkg/microservice/user/core/repository/orm/user.go index ad48dcc2..56ee417b 100644 --- a/pkg/microservice/user/core/repository/orm/user.go +++ b/pkg/microservice/user/core/repository/orm/user.go @@ -21,6 +21,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/models" + "github.com/koderover/zadig/pkg/types" ) // CreateUser create a user @@ -149,3 +150,12 @@ func UpdateUser(uid string, user *models.User, db *gorm.DB) error { } return nil } + +func CountUserByType(db *gorm.DB) ([]*types.UserCountByType, error) { + var resp []*types.UserCountByType + err := db.Model(&models.User{}).Select("count(*) as count, identity_type").Group("identity_type").Find(&resp).Error + if err != nil { + return nil, err + } + return resp, nil +} diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index 1e532988..d5b54e4b 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -589,3 +589,12 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us } return user, nil } + +func GetUserCount(logger *zap.SugaredLogger) ([]*types.UserCountByType, error) { + resp, err := orm.CountUserByType(core.DB) + if err != nil { + logger.Errorf("Failed to count user by type from db, the error is: %s", err.Error()) + return nil, err + } + return resp, nil +} diff --git a/pkg/types/user.go b/pkg/types/user.go index d046a54d..3eab93d4 100644 --- a/pkg/types/user.go +++ b/pkg/types/user.go @@ -45,3 +45,8 @@ type RoleBinding struct { Preset bool `json:"preset"` Type setting.ResourceType `json:"type"` } + +type UserCountByType struct { + IdentityType string `gorm:"default:'unknown'" json:"identity_type" gorm:"identity_type"` + Count int64 `json:"count" gorm:"count"` +} -- Gitee From 9e395cb4074d85d18ec92aa919b5d854a4fb6af2 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 8 Jul 2022 10:08:15 +0800 Subject: [PATCH 2545/2647] fix list common workflow permission (#1767) Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/handler/workflow_v4.go | 8 ++++++-- .../aslan/core/workflow/service/workflow/workflow_v4.go | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index 20d4ee83..79abfc03 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -93,9 +93,13 @@ func ListWorkflowV4(c *gin.Context) { ctx.Err = err return } - workflowNames, _ := internalhandler.GetResourcesInHeader(c) + ignoreWorkflow := false + workflowNames, found := internalhandler.GetResourcesInHeader(c) + if found && len(workflowNames) == 0 { + ignoreWorkflow = true + } - workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, workflowNames, ctx.Logger) + workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, workflowNames, ignoreWorkflow, ctx.Logger) resp := listWorkflowV4Resp{ WorkflowList: workflowList, Total: 0, diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index d8daacc7..40915689 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -132,7 +132,7 @@ func DeleteWorkflowV4(name string, logger *zap.SugaredLogger) error { return nil } -func ListWorkflowV4(projectName, userID string, names []string, logger *zap.SugaredLogger) ([]*Workflow, error) { +func ListWorkflowV4(projectName, userID string, names []string, ignoreWorkflow bool, logger *zap.SugaredLogger) ([]*Workflow, error) { resp := make([]*Workflow, 0) workflowV4List, _, err := commonrepo.NewWorkflowV4Coll().List(&commonrepo.ListWorkflowV4Option{ ProjectName: projectName, @@ -143,7 +143,8 @@ func ListWorkflowV4(projectName, userID string, names []string, logger *zap.Suga } workflow := []*Workflow{} - if len(names) > 0 { + + if !ignoreWorkflow { workflow, err = ListWorkflows([]string{projectName}, userID, names, logger) if err != nil { return resp, err -- Gitee From eeda51c62bbff0b9f9dc5e32e5e549f83bc9671a Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 8 Jul 2022 10:08:51 +0800 Subject: [PATCH 2546/2647] custom image name for common workflow (#1766) * custom image name for common workflow Signed-off-by: guoyu * fix dockerfile template content Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/common/service/service.go | 133 ++++++++++++++++++ .../service/workflow/job/job_build.go | 35 +++-- .../service/workflow/pipeline_validation.go | 129 ----------------- .../service/workflow/workflow_task.go | 4 +- 4 files changed, 157 insertions(+), 144 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/service.go b/pkg/microservice/aslan/core/common/service/service.go index d1321fe5..8272099b 100644 --- a/pkg/microservice/aslan/core/common/service/service.go +++ b/pkg/microservice/aslan/core/common/service/service.go @@ -23,14 +23,18 @@ import ( "fmt" "net/url" "regexp" + "strconv" "strings" templ "text/template" + "time" "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/sets" "sigs.k8s.io/yaml" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" @@ -38,10 +42,15 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util/converter" yamlutil "github.com/koderover/zadig/pkg/util/yaml" ) +const ( + InterceptCommitID = 8 +) + type yamlPreview struct { Kind string `json:"kind"` } @@ -787,6 +796,130 @@ type Variable struct { REPO_PR string } +type candidate struct { + Branch string + Tag string + CommitID string + PR int + TaskID int64 + Timestamp string + ProductName string + ServiceName string + ImageName string + EnvName string +} + +// releaseCandidate 根据 TaskID 生成编译镜像Tag或者二进制包后缀 +// TODO: max length of a tag is 128 +func ReleaseCandidate(builds []*types.Repository, taskID int64, productName, serviceName, envName, imageName, deliveryType string) string { + timeStamp := time.Now().Format("20060102150405") + + if imageName == "" { + imageName = serviceName + } + if len(builds) == 0 { + switch deliveryType { + case config.TarResourceType: + return fmt.Sprintf("%s-%s", serviceName, timeStamp) + default: + return fmt.Sprintf("%s:%s", imageName, timeStamp) + } + } + + first := builds[0] + for index, build := range builds { + if build.IsPrimary { + first = builds[index] + } + } + + // 替换 Tag 和 Branch 中的非法字符为 "-", 避免 docker build 失败 + var ( + reg = regexp.MustCompile(`[^\w.-]`) + customImageRule *template.CustomRule + customTarRule *template.CustomRule + commitID = first.CommitID + ) + + if project, err := templaterepo.NewProductColl().Find(productName); err != nil { + log.Errorf("find project err:%s", err) + } else { + customImageRule = project.CustomImageRule + customTarRule = project.CustomTarRule + } + + if len(commitID) > InterceptCommitID { + commitID = commitID[0:InterceptCommitID] + } + + candidate := &candidate{ + Branch: string(reg.ReplaceAll([]byte(first.Branch), []byte("-"))), + CommitID: commitID, + PR: first.PR, + Tag: string(reg.ReplaceAll([]byte(first.Tag), []byte("-"))), + EnvName: envName, + Timestamp: timeStamp, + TaskID: taskID, + ProductName: productName, + ServiceName: serviceName, + ImageName: imageName, + } + switch deliveryType { + case config.TarResourceType: + newTarRule := replaceVariable(customTarRule, candidate) + if strings.Contains(newTarRule, ":") { + return strings.Replace(newTarRule, ":", "-", -1) + } + return newTarRule + default: + return replaceVariable(customImageRule, candidate) + } +} + +// There are four situations in total +// 1.Execute workflow selection tag build +// 2.Execute workflow selection branch and pr build +// 3.Execute workflow selection branch pr build +// 4.Execute workflow selection branch build +func replaceVariable(customRule *template.CustomRule, candidate *candidate) string { + var currentRule string + if candidate.Tag != "" { + if customRule == nil { + return fmt.Sprintf("%s:%s-%s", candidate.ServiceName, candidate.Timestamp, candidate.Tag) + } + currentRule = customRule.TagRule + } else if candidate.Branch != "" && candidate.PR != 0 { + if customRule == nil { + return fmt.Sprintf("%s:%s-%d-%s-pr-%d", candidate.ServiceName, candidate.Timestamp, candidate.TaskID, candidate.Branch, candidate.PR) + } + currentRule = customRule.PRAndBranchRule + } else if candidate.Branch == "" && candidate.PR != 0 { + if customRule == nil { + return fmt.Sprintf("%s:%s-%d-pr-%d", candidate.ServiceName, candidate.Timestamp, candidate.TaskID, candidate.PR) + } + currentRule = customRule.PRRule + } else if candidate.Branch != "" && candidate.PR == 0 { + if customRule == nil { + return fmt.Sprintf("%s:%s-%d-%s", candidate.ServiceName, candidate.Timestamp, candidate.TaskID, candidate.Branch) + } + currentRule = customRule.BranchRule + } + + currentRule = ReplaceRuleVariable(currentRule, &Variable{ + SERVICE: candidate.ServiceName, + IMAGE_NAME: candidate.ImageName, + TIMESTAMP: candidate.Timestamp, + TASK_ID: strconv.FormatInt(candidate.TaskID, 10), + REPO_COMMIT_ID: candidate.CommitID, + PROJECT: candidate.ProductName, + ENV_NAME: candidate.EnvName, + REPO_TAG: candidate.Tag, + REPO_BRANCH: candidate.Branch, + REPO_PR: strconv.Itoa(candidate.PR), + }) + return currentRule +} + func ReplaceRuleVariable(rule string, replaceValue *Variable) string { template, err := templ.New("replaceRuleVariable").Parse(rule) if err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index f463f24d..cc4798ce 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -21,13 +21,13 @@ import ( "path" "strconv" "strings" - "time" configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + templ "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/template" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/types/step" @@ -76,6 +76,7 @@ func (j *BuildJob) SetPreset() error { } func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { + logger := log.SugaredLogger() resp := []*commonmodels.JobTask{} j.spec = &commonmodels.ZadigBuildJobSpec{} @@ -94,22 +95,18 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { } for _, build := range j.spec.ServiceAndBuilds { - imageTag := time.Now().Format("20060102150405") - for _, repo := range build.Repos { - imageTag = repo.GetReleaseCandidateTag(taskID) - break - } + imageTag := commonservice.ReleaseCandidate(build.Repos, taskID, j.workflow.Project, build.ServiceName, "", build.ServiceModule, "image") if len(registry.Namespace) > 0 { - build.Image = fmt.Sprintf("%s/%s/%s:%s", registry.RegAddr, registry.Namespace, build.ServiceModule, imageTag) + build.Image = fmt.Sprintf("%s/%s/%s", registry.RegAddr, registry.Namespace, imageTag) } else { - build.Image = fmt.Sprintf("%s/%s:%s", registry.RegAddr, build.ServiceModule, imageTag) + build.Image = fmt.Sprintf("%s/%s", registry.RegAddr, imageTag) } build.Image = strings.TrimPrefix(build.Image, "http://") build.Image = strings.TrimPrefix(build.Image, "https://") - build.Package = fmt.Sprintf("%s-%s-%s-%d.tar.gz", j.job.Name, build.ServiceModule, time.Now().Format("20060102150405"), taskID) + build.Package = fmt.Sprintf("%s.tar.gz", commonservice.ReleaseCandidate(build.Repos, taskID, j.workflow.Project, build.ServiceName, "", build.ServiceModule, "tar")) jobTask := &commonmodels.JobTask{ Name: jobNameFormat(build.ServiceName + "-" + build.ServiceModule + "-" + j.job.Name), @@ -168,11 +165,9 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { if buildInfo.PostBuild.DockerBuild != nil { dockefileContent := "" if buildInfo.PostBuild.DockerBuild.TemplateID != "" { - dockerfileTemplate, err := commonrepo.NewDockerfileTemplateColl().GetById(buildInfo.PostBuild.DockerBuild.TemplateID) - if err != nil { - return resp, nil + if dockerfileDetail, err := templ.GetDockerfileTemplateDetail(buildInfo.PostBuild.DockerBuild.TemplateID, logger); err == nil { + dockefileContent = dockerfileDetail.Content } - dockefileContent = dockerfileTemplate.Content } dockerBuildStep := &commonmodels.StepTask{ @@ -232,6 +227,20 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { jobTask.Steps = append(jobTask.Steps, archiveStep) } } + + // init psot build shell step + if buildInfo.PostBuild.Scripts != "" { + scripts := append([]string{dockerLoginCmd}, strings.Split(replaceWrapLine(buildInfo.PostBuild.Scripts), "\n")...) + shellStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-post-shell", + JobName: jobTask.Name, + StepType: config.StepShell, + Spec: &step.StepShellSpec{ + Scripts: scripts, + }, + } + jobTask.Steps = append(jobTask.Steps, shellStep) + } resp = append(resp, jobTask) } j.job.Spec = j.spec diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go index e6382166..cff9b4f2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/pipeline_validation.go @@ -22,10 +22,7 @@ import ( "fmt" "regexp" "sort" - "strconv" - "strings" "sync" - "time" "github.com/google/go-github/v35/github" "go.uber.org/zap" @@ -36,9 +33,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/task" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/base" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/codehub" @@ -525,130 +520,6 @@ func setManunalBuilds(builds []*types.Repository, buildArgs []*types.Repository, return nil } -// releaseCandidate 根据 TaskID 生成编译镜像Tag或者二进制包后缀 -// TODO: max length of a tag is 128 -func releaseCandidate(b *task.Build, taskID int64, productName, envName, imageName, deliveryType string) string { - timeStamp := time.Now().Format("20060102150405") - - if imageName == "" { - imageName = b.ServiceName - } - if len(b.JobCtx.Builds) == 0 { - switch deliveryType { - case config.TarResourceType: - return fmt.Sprintf("%s-%s", b.ServiceName, timeStamp) - default: - return fmt.Sprintf("%s:%s", imageName, timeStamp) - } - } - - first := b.JobCtx.Builds[0] - for index, build := range b.JobCtx.Builds { - if build.IsPrimary { - first = b.JobCtx.Builds[index] - } - } - - // 替换 Tag 和 Branch 中的非法字符为 "-", 避免 docker build 失败 - var ( - reg = regexp.MustCompile(`[^\w.-]`) - customImageRule *template.CustomRule - customTarRule *template.CustomRule - commitID = first.CommitID - ) - - if project, err := templaterepo.NewProductColl().Find(productName); err != nil { - log.Errorf("find project err:%s", err) - } else { - customImageRule = project.CustomImageRule - customTarRule = project.CustomTarRule - } - - if len(commitID) > InterceptCommitID { - commitID = commitID[0:InterceptCommitID] - } - - candidate := &candidate{ - Branch: string(reg.ReplaceAll([]byte(first.Branch), []byte("-"))), - CommitID: commitID, - PR: first.PR, - Tag: string(reg.ReplaceAll([]byte(first.Tag), []byte("-"))), - EnvName: envName, - Timestamp: timeStamp, - TaskID: taskID, - ProductName: productName, - ServiceName: b.ServiceName, - ImageName: imageName, - } - switch deliveryType { - case config.TarResourceType: - newTarRule := replaceVariable(customTarRule, candidate) - if strings.Contains(newTarRule, ":") { - return strings.Replace(newTarRule, ":", "-", -1) - } - return newTarRule - default: - return replaceVariable(customImageRule, candidate) - } -} - -type candidate struct { - Branch string - Tag string - CommitID string - PR int - TaskID int64 - Timestamp string - ProductName string - ServiceName string - ImageName string - EnvName string -} - -// There are four situations in total -// 1.Execute workflow selection tag build -// 2.Execute workflow selection branch and pr build -// 3.Execute workflow selection branch pr build -// 4.Execute workflow selection branch build -func replaceVariable(customRule *template.CustomRule, candidate *candidate) string { - var currentRule string - if candidate.Tag != "" { - if customRule == nil { - return fmt.Sprintf("%s:%s-%s", candidate.ServiceName, candidate.Timestamp, candidate.Tag) - } - currentRule = customRule.TagRule - } else if candidate.Branch != "" && candidate.PR != 0 { - if customRule == nil { - return fmt.Sprintf("%s:%s-%d-%s-pr-%d", candidate.ServiceName, candidate.Timestamp, candidate.TaskID, candidate.Branch, candidate.PR) - } - currentRule = customRule.PRAndBranchRule - } else if candidate.Branch == "" && candidate.PR != 0 { - if customRule == nil { - return fmt.Sprintf("%s:%s-%d-pr-%d", candidate.ServiceName, candidate.Timestamp, candidate.TaskID, candidate.PR) - } - currentRule = customRule.PRRule - } else if candidate.Branch != "" && candidate.PR == 0 { - if customRule == nil { - return fmt.Sprintf("%s:%s-%d-%s", candidate.ServiceName, candidate.Timestamp, candidate.TaskID, candidate.Branch) - } - currentRule = customRule.BranchRule - } - - currentRule = commonservice.ReplaceRuleVariable(currentRule, &commonservice.Variable{ - SERVICE: candidate.ServiceName, - IMAGE_NAME: candidate.ImageName, - TIMESTAMP: candidate.Timestamp, - TASK_ID: strconv.FormatInt(candidate.TaskID, 10), - REPO_COMMIT_ID: candidate.CommitID, - PROJECT: candidate.ProductName, - ENV_NAME: candidate.EnvName, - REPO_TAG: candidate.Tag, - REPO_BRANCH: candidate.Branch, - REPO_PR: strconv.Itoa(candidate.PR), - }) - return currentRule -} - // GetImage suffix 可以是 branch name 或者 pr number func GetImage(registry *commonmodels.RegistryNamespace, suffix string) string { if registry.RegProvider == config.RegistryTypeAWS { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index 65574943..fa10ec05 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -2500,7 +2500,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err return e.ErrFindRegistry.AddDesc(err.Error()) } } - t.JobCtx.Image = GetImage(reg, releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, taskOpt.ImageName, "image")) + t.JobCtx.Image = GetImage(reg, commonservice.ReleaseCandidate(t.JobCtx.Builds, taskOpt.Task.TaskID, taskOpt.Task.ProductName, t.ServiceName, taskOpt.EnvName, taskOpt.ImageName, "image")) taskOpt.Task.TaskArgs.Deploy.Image = t.JobCtx.Image if taskOpt.ServiceName != "" { @@ -2521,7 +2521,7 @@ func ensurePipelineTask(taskOpt *taskmodels.TaskOpt, log *zap.SugaredLogger) err // 二进制文件名称 // 编译任务使用 t.JobCtx.PackageFile // 注意: 其他任务从 pt.TaskArgs.Deploy.PackageFile 获取, 必须要有编译任务 - t.JobCtx.PackageFile = GetPackageFile(releaseCandidate(t, taskOpt.Task.TaskID, taskOpt.Task.ProductName, taskOpt.EnvName, taskOpt.ImageName, "tar")) + t.JobCtx.PackageFile = GetPackageFile(commonservice.ReleaseCandidate(t.JobCtx.Builds, taskOpt.Task.TaskID, taskOpt.Task.ProductName, t.ServiceName, taskOpt.EnvName, taskOpt.ImageName, "tar")) taskOpt.Task.TaskArgs.Deploy.PackageFile = t.JobCtx.PackageFile // 注入编译模块中用户定义环境变量 -- Gitee From 61e813f7efbc01454dce8e0dc792004425c24d1a Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 8 Jul 2022 10:11:41 +0800 Subject: [PATCH 2547/2647] auto clean zombie job by adding job ttl seconds (#1768) * auto clean zombie job by adding job ttl seconds Signed-off-by: guoyu * add job timeout Signed-off-by: guoyu * add notes Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/service/jobcontroller/kubernetes.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 06f3de31..94e15a49 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -201,6 +201,10 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re Completions: int32Ptr(1), Parallelism: int32Ptr(1), BackoffLimit: int32Ptr(0), + // in case finished zombie job not cleaned up by zadig + TTLSecondsAfterFinished: int32Ptr(3600), + // in case zombie job never stop + ActiveDeadlineSeconds: int64Ptr(jobTask.Properties.Timeout*60 + 3600), Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, @@ -397,6 +401,7 @@ func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSp } func int32Ptr(i int32) *int32 { return &i } +func int64Ptr(i int64) *int64 { return &i } func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, checkFile bool, kubeClient crClient.Client, clientset kubernetes.Interface, restConfig *rest.Config, xl *zap.SugaredLogger) (status config.Status) { xl.Infof("wait job to start: %s/%s", namespace, jobName) -- Gitee From c84f07375e847b26ef367b63b3ed915d2840ac60 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 8 Jul 2022 11:47:10 +0800 Subject: [PATCH 2548/2647] fix common workflow queue run workflow in parallel (#1772) Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/service/workflow/workflow_task_v4.go | 2 ++ .../aslan/core/workflow/service/workflowcontroller/queue.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 25b759b5..e7b334e2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -130,6 +130,8 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z workflowTask.CreateTime = time.Now().Unix() workflowTask.WorkflowName = workflow.Name workflowTask.ProjectName = workflow.Project + workflowTask.KeyVals = workflow.KeyVals + workflowTask.MultiRun = workflow.MultiRun for _, stage := range workflow.Stages { stageTask := &commonmodels.StageTask{ diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go index 7c2ba077..8da77535 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go @@ -258,7 +258,7 @@ func updateQueueAndRunTask(t *commonmodels.WorkflowQueue, jobConcurrency int) er logger.Errorf("%s:%d get workflow task error: %v", t.WorkflowName, t.TaskID, err) return fmt.Errorf("%s:%d get workflow task error: %v", t.WorkflowName, t.TaskID, err) } - workflowTask.Status = t.Status + workflowTask.Status = config.StatusQueued if success := UpdateQueue(workflowTask); !success { logger.Errorf("%s:%d update t status error", t.WorkflowName, t.TaskID) return fmt.Errorf("%s:%d update t status error", t.WorkflowName, t.TaskID) -- Gitee From 3160e34b1c591141faa395e64cb3d66193b99007 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Fri, 8 Jul 2022 11:47:35 +0800 Subject: [PATCH 2549/2647] remove env_name vars from common workflow (#1773) Signed-off-by: guoyu Co-authored-by: guoyu --- .../core/workflow/service/jobcontroller/job_freestyle.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 555feedf..c8fda429 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -117,10 +117,6 @@ func (c *FreestyleJobCtl) Run(ctx context.Context) { c.job.Properties.DockerHost = dockerHost - // TODO: inject vars like task_id and etc, passing them into c.job.Properties.Args - envNameVar := &commonmodels.KeyVal{Key: "ENV_NAME", Value: c.job.Properties.Namespace, IsCredential: false} - c.job.Properties.Args = append(c.job.Properties.Args, envNameVar) - jobCtxBytes, err := yaml.Marshal(BuildJobExcutorContext(c.job, c.workflowCtx, c.logger)) if err != nil { msg := fmt.Sprintf("cannot Jobexcutor.Context data: %v", err) -- Gitee From 5a7ff6012b01e1f8fce8de5390a0524560c9d9b4 Mon Sep 17 00:00:00 2001 From: flyer Date: Fri, 8 Jul 2022 14:14:58 +0800 Subject: [PATCH 2550/2647] fix bug of rendering pvc subpath (#1776) Signed-off-by: zhangyifei Co-authored-by: zhangyifei --- pkg/microservice/warpdrive/core/service/taskplugin/build.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/build.go b/pkg/microservice/warpdrive/core/service/taskplugin/build.go index 9f3ccf46..c37dac2e 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/build.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/build.go @@ -248,8 +248,7 @@ func (p *BuildTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, pipe } // Since we allow users to use custom environment variables, variable resolution is required. - if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == types.NFSMedium && - pipelineCtx.CacheDirType == types.UserDefinedCacheDir { + if pipelineCtx.CacheEnable && pipelineCtx.Cache.MediumType == types.NFSMedium { pipelineCtx.CacheUserDir = p.renderEnv(pipelineCtx.CacheUserDir) pipelineCtx.Cache.NFSProperties.Subpath = p.renderEnv(pipelineCtx.Cache.NFSProperties.Subpath) } -- Gitee From 3d39efc9182ed08d30c3547760cf92789c5abf94 Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 8 Jul 2022 17:51:58 +0800 Subject: [PATCH 2551/2647] add repo namespace saving logic (#1778) Signed-off-by: allenshen --- .../core/workflow/service/workflow/workflow.go | 6 ++++++ pkg/types/repo.go | 15 +++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 35e7d333..9808dbd2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -335,6 +335,12 @@ func FindWorkflow(workflowName string, log *zap.SugaredLogger) (*commonmodels.Wo } } + for _, module := range resp.BuildStage.Modules { + for _, bf := range module.BranchFilter { + bf.RepoNamespace = bf.GetNamespace() + } + } + return resp, nil } diff --git a/pkg/types/repo.go b/pkg/types/repo.go index 4de2c4a3..b8430a61 100644 --- a/pkg/types/repo.go +++ b/pkg/types/repo.go @@ -65,14 +65,21 @@ type Repository struct { type BranchFilterInfo struct { // repository identifier - CodehostID int `bson:"codehost_id" json:"codehost_id"` - RepoOwner string `bson:"repo_owner" json:"repo_owner"` - RepoName string `bson:"repo_name" json:"repo_name"` - + CodehostID int `bson:"codehost_id" json:"codehost_id"` + RepoOwner string `bson:"repo_owner" json:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace"` // actual regular expression filter FilterRegExp string `bson:"filter_regexp" json:"filter_regexp"` } +func (bf *BranchFilterInfo) GetNamespace() string { + if len(bf.RepoNamespace) > 0 { + return bf.RepoNamespace + } + return bf.RepoOwner +} + // GetReleaseCandidateTag 返回待发布对象Tag // Branch: 20060102150405-{TaskID}-master // PR: 20060102150405-{TaskID}-pr-1765 -- Gitee From 6daa13572248f0cbbc2e39834153857e33d78e3e Mon Sep 17 00:00:00 2001 From: shendongdong Date: Fri, 8 Jul 2022 21:51:54 +0800 Subject: [PATCH 2552/2647] optimize rendet set query (#1781) Signed-off-by: allenshen --- .../common/repository/mongodb/render_set.go | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go index 48742f2a..d000c703 100644 --- a/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go +++ b/pkg/microservice/aslan/core/common/repository/mongodb/render_set.go @@ -207,19 +207,24 @@ func (c *RenderSetColl) Find(opt *RenderSetFindOption) (*models.RenderSet, error query := bson.M{"name": opt.Name} opts := options.FindOne() if opt.Revision > 0 { + // revisionName + revision are enough to locate the target record + // there is no need to set other query condition + // Note. the query logic has been rolled back to 1.12.0 query["revision"] = opt.Revision } else { opts.SetSort(bson.D{{"revision", -1}}) - } - if len(opt.ProductTmpl) > 0 { - query["product_tmpl"] = opt.ProductTmpl - } - if len(opt.EnvName) > 0 { - query["env_name"] = opt.EnvName - } - if opt.IsDefault { - query["is_default"] = opt.IsDefault + if len(opt.EnvName) > 0 { + query["env_name"] = opt.EnvName + } + + if len(opt.ProductTmpl) > 0 { + query["product_tmpl"] = opt.ProductTmpl + } + + if opt.IsDefault { + query["is_default"] = opt.IsDefault + } } rs := &models.RenderSet{} -- Gitee From 82325280990ecc61c1b0d74c5398e51ac7af84af Mon Sep 17 00:00:00 2001 From: Min Min <37430175+jamsman94@users.noreply.github.com> Date: Mon, 11 Jul 2022 14:01:12 +0800 Subject: [PATCH 2553/2647] add skip verify tls certificate to external zadig client since it might be a self-signed certificate (#1783) Signed-off-by: Min Min Co-authored-by: Min Min --- pkg/shared/client/aslan/client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/shared/client/aslan/client.go b/pkg/shared/client/aslan/client.go index ab453625..1732af27 100644 --- a/pkg/shared/client/aslan/client.go +++ b/pkg/shared/client/aslan/client.go @@ -17,6 +17,8 @@ limitations under the License. package aslan import ( + "crypto/tls" + "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -45,6 +47,8 @@ func NewExternal(host, token string) *Client { httpclient.SetHostURL(host+"/api/aslan"), ) + c.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + return &Client{ Client: c, host: host, -- Gitee From b73ddf0e07cee16037d58b29618fd96f55969ea4 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 11 Jul 2022 14:11:42 +0800 Subject: [PATCH 2554/2647] fix testing artifacts has empty filename (#1785) Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/testing/service/workspace.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go index 51c2b23d..7e78d86c 100644 --- a/pkg/microservice/aslan/core/workflow/testing/service/workspace.go +++ b/pkg/microservice/aslan/core/workflow/testing/service/workspace.go @@ -102,6 +102,9 @@ func GetTestArtifactInfo(pipelineName, dir string, taskID int64, log *zap.Sugare if strings.HasSuffix(tarFile, "/") { continue } + if strings.TrimSpace(tarFile) == "" { + continue + } fis = append(fis, tarFile) } resp.FileNames = fis -- Gitee From 891c07d48c2b295883c92270b4f6efb610c80025 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 11 Jul 2022 14:18:51 +0800 Subject: [PATCH 2555/2647] fix error message when workflow name already exists (#1786) Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/service/workflow/workflow.go | 4 ++-- .../aslan/core/workflow/service/workflow/workflow_v4.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go index 9808dbd2..d3e51cd1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow.go @@ -437,9 +437,9 @@ func PreSetWorkflow(productName string, log *zap.SugaredLogger) ([]*PreSetResp, } func CreateWorkflow(workflow *commonmodels.Workflow, log *zap.SugaredLogger) error { - _, err := commonrepo.NewWorkflowColl().Find(workflow.Name) + existedWorkflow, err := commonrepo.NewWorkflowColl().Find(workflow.Name) if err == nil { - errStr := fmt.Sprintf("workflow [%s] 在项目 [%s] 中已经存在!", workflow.Name, workflow.ProductTmplName) + errStr := fmt.Sprintf("workflow [%s] 在项目 [%s] 中已经存在!", workflow.Name, existedWorkflow.ProductTmplName) return e.ErrUpsertWorkflow.AddDesc(errStr) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 40915689..c26fd656 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -39,9 +39,9 @@ const ( ) func CreateWorkflowV4(user string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { - _, err := commonrepo.NewWorkflowV4Coll().Find(workflow.Name) + existedWorkflow, err := commonrepo.NewWorkflowV4Coll().Find(workflow.Name) if err == nil { - errStr := fmt.Sprintf("workflow v4 [%s] 在项目 [%s] 中已经存在!", workflow.Name, workflow.Project) + errStr := fmt.Sprintf("workflow v4 [%s] 在项目 [%s] 中已经存在!", workflow.Name, existedWorkflow.Project) return e.ErrUpsertWorkflow.AddDesc(errStr) } if err := LintWorkflowV4(workflow, logger); err != nil { -- Gitee From c26f77cd19fc2dcc67e7be8e93d311e5369ebb3e Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 11 Jul 2022 14:19:03 +0800 Subject: [PATCH 2556/2647] add cache for common workflow (#1780) Signed-off-by: guoyu Co-authored-by: guoyu --- .../common/repository/models/workflow_v4.go | 26 +++++++----- .../service/jobcontroller/job_freestyle.go | 1 + .../service/jobcontroller/kubernetes.go | 42 +++++++++---------- .../service/workflow/job/job_build.go | 36 ++++++++++++++++ .../service/workflowcontroller/workflow.go | 1 + 5 files changed, 74 insertions(+), 32 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index a959cc4f..cf2f2414 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -109,17 +109,21 @@ type ServiceAndImage struct { } type JobProperties struct { - Timeout int64 `bson:"timeout" json:"timeout"` - Retry int64 `bson:"retry" json:"retry"` - ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` - ClusterID string `bson:"cluster_id" json:"cluster_id"` - BuildOS string `bson:"build_os" json:"build_os"` - ImageFrom string `bson:"image_from" json:"image_from"` - Namespace string `bson:"namespace" json:"namespace"` - Args []*KeyVal `bson:"args" json:"args"` - Paths string `bson:"-" json:"-"` - LogFileName string `bson:"log_file_name" json:"log_file_name"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` + Timeout int64 `bson:"timeout" json:"timeout"` + Retry int64 `bson:"retry" json:"retry"` + ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` + ClusterID string `bson:"cluster_id" json:"cluster_id"` + BuildOS string `bson:"build_os" json:"build_os"` + ImageFrom string `bson:"image_from" json:"image_from"` + Namespace string `bson:"namespace" json:"namespace"` + Args []*KeyVal `bson:"args" json:"args"` + Paths string `bson:"-" json:"-"` + LogFileName string `bson:"log_file_name" json:"log_file_name"` + DockerHost string `bson:"-" json:"docker_host,omitempty"` + Cache types.Cache `bson:"cache" json:"cache"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` } type Step struct { diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index c8fda429..54be600f 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -258,6 +258,7 @@ func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels Envs: envVars, SecretEnvs: secretEnvVars, WorkflowName: workflowCtx.WorkflowName, + Workspace: workflowCtx.Workspace, TaskID: workflowCtx.TaskID, Outputs: outputs, Steps: job.Steps, diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 94e15a49..474d62a2 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -269,28 +269,28 @@ func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, re }, } - // if ctx.CacheEnable && ctx.Cache.MediumType == commontypes.NFSMedium { - // volumeName := "build-cache" - // job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{ - // Name: volumeName, - // VolumeSource: corev1.VolumeSource{ - // PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - // ClaimName: ctx.Cache.NFSProperties.PVC, - // }, - // }, - // }) - - // mountPath := ctx.CacheUserDir - // if ctx.CacheDirType == commontypes.WorkspaceCacheDir { - // mountPath = "/workspace" - // } + if jobTask.Properties.CacheEnable && jobTask.Properties.Cache.MediumType == commontypes.NFSMedium { + volumeName := "build-cache" + job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: jobTask.Properties.Cache.NFSProperties.PVC, + }, + }, + }) - // job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ - // Name: volumeName, - // MountPath: mountPath, - // SubPath: ctx.Cache.NFSProperties.Subpath, - // }) - // } + mountPath := jobTask.Properties.CacheUserDir + if jobTask.Properties.CacheDirType == commontypes.WorkspaceCacheDir { + mountPath = workflowCtx.Workspace + } + + job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: mountPath, + SubPath: jobTask.Properties.Cache.NFSProperties.Subpath, + }) + } // if affinity := addNodeAffinity(clusterID, pipelineTask.ConfigPayload.K8SClusters); affinity != nil { // job.Spec.Template.Spec.Affinity = affinity diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index cc4798ce..6ccbffbd 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -18,6 +18,7 @@ package job import ( "fmt" + "os" "path" "strconv" "strings" @@ -127,8 +128,26 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { BuildOS: buildInfo.PreBuild.BuildOS, ImageFrom: buildInfo.PreBuild.ImageFrom, } + clusterInfo, err := commonrepo.NewK8SClusterColl().Get(buildInfo.PreBuild.ClusterID) + if err != nil { + return resp, err + } + + if clusterInfo.Cache.MediumType == "" { + jobTask.Properties.CacheEnable = false + } else { + jobTask.Properties.Cache = clusterInfo.Cache + jobTask.Properties.CacheEnable = buildInfo.CacheEnable + jobTask.Properties.CacheDirType = buildInfo.CacheDirType + jobTask.Properties.CacheUserDir = buildInfo.CacheUserDir + } jobTask.Properties.Args = append(jobTask.Properties.Args, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) + if jobTask.Properties.CacheEnable && jobTask.Properties.Cache.MediumType == types.NFSMedium { + jobTask.Properties.CacheUserDir = renderEnv(jobTask.Properties.CacheUserDir, jobTask.Properties.Args) + jobTask.Properties.Cache.NFSProperties.Subpath = renderEnv(jobTask.Properties.Cache.NFSProperties.Subpath, jobTask.Properties.Args) + } + // init tools install step for _, tool := range buildInfo.PreBuild.Installs { toolInstallStep := &commonmodels.StepTask{ @@ -317,6 +336,8 @@ func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, ret = append(ret, &commonmodels.KeyVal{Key: "TASK_ID", Value: fmt.Sprintf("%d", taskID), IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE", Value: build.ServiceName, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "SERVICE_MODULE", Value: build.ServiceModule, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "PROJECT", Value: project, IsCredential: false}) + ret = append(ret, &commonmodels.KeyVal{Key: "WORKFLOW", Value: workflowName, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "IMAGE", Value: build.Image, IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "CI", Value: "true", IsCredential: false}) ret = append(ret, &commonmodels.KeyVal{Key: "ZADIG", Value: "true", IsCredential: false}) @@ -378,3 +399,18 @@ func fillBuildDetail(moduleBuild *commonmodels.Build, serviceName, serviceModule } return nil } + +func renderEnv(data string, kvs []*commonmodels.KeyVal) string { + mapper := func(data string) string { + for _, envar := range kvs { + if data != envar.Key { + continue + } + + return envar.Value + } + + return fmt.Sprintf("$%s", data) + } + return os.Expand(data, mapper) +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go index 0fa75009..0edd8700 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go @@ -110,6 +110,7 @@ func (c *workflowCtl) Run(ctx context.Context, concurrency int) { WorkflowName: c.workflowTask.WorkflowName, ProjectName: c.workflowTask.ProjectName, TaskID: c.workflowTask.TaskID, + Workspace: "/workspace", DistDir: fmt.Sprintf("%s/%s/dist/%d", config.S3StoragePath(), c.workflowTask.WorkflowName, c.workflowTask.TaskID), DockerMountDir: fmt.Sprintf("/tmp/%s/docker/%d", uuid.NewV4(), time.Now().Unix()), ConfigMapMountDir: fmt.Sprintf("/tmp/%s/cm/%d", uuid.NewV4(), time.Now().Unix()), -- Gitee From 0e05ffd8930f36b0d477236f7ae4344d05cdc0ee Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 11 Jul 2022 17:54:46 +0800 Subject: [PATCH 2557/2647] fix auto generate job name was illegal (#1790) * fix auto generate job name was illegal Signed-off-by: guoyu * fix job name lenth Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/workflow/service/workflow/job/job.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go index 65a2af0e..a03998c1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -18,6 +18,7 @@ package job import ( "fmt" + "strings" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -67,9 +68,11 @@ func ToJobs(job *commonmodels.Job, workflow *commonmodels.WorkflowV4, taskID int return jobCtl.ToJobs(taskID) } +// use service name and service module hash to generate job name func jobNameFormat(jobName string) string { - if len(jobName) > 50 { - return jobName[:50] + if len(jobName) > 63 { + jobName = jobName[:63] } + jobName = strings.Trim(jobName, "-") return jobName } -- Gitee From 64a5582982ba8d829596809035d9fc07eb2736e6 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 11 Jul 2022 18:09:19 +0800 Subject: [PATCH 2558/2647] check if depracated external service exist in env (#1791) Signed-off-by: guoyu Co-authored-by: guoyu --- .../service/workflow/job/job_deploy.go | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go index 179c4c7a..aad667b3 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_deploy.go @@ -108,6 +108,15 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { Containers: service.Containers, } } + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: j.workflow.Project, + EnvName: j.spec.Env, + }) + for _, service := range servicesInExternalEnv { + productServiceMap[service.ServiceName] = &commonmodels.ProductService{ + ServiceName: service.ServiceName, + } + } } // get deploy info from previous build job @@ -133,7 +142,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { } if j.spec.DeployType == setting.K8SDeployType { for _, deploy := range j.spec.ServiceAndImages { - if err := checkServiceAndContainerExsistsInEnv(productServiceMap, deploy.ServiceName, deploy.ServiceModule, j.spec.Env); err != nil { + if err := checkServiceExsistsInEnv(productServiceMap, deploy.ServiceName, j.spec.Env); err != nil { return resp, err } jobTask := &commonmodels.JobTask{ @@ -195,7 +204,7 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { ReleaseName: releaseName, } for _, deploy := range deploys { - if err := checkServiceAndContainerExsistsInEnv(productServiceMap, serviceName, deploy.ServiceModule, j.spec.Env); err != nil { + if err := checkServiceExsistsInEnv(productServiceMap, serviceName, j.spec.Env); err != nil { return resp, err } helmDeploySpec.ImageAndModules = append(helmDeploySpec.ImageAndModules, &step.ImageAndServiceModule{ @@ -214,15 +223,9 @@ func (j *DeployJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { return resp, nil } -func checkServiceAndContainerExsistsInEnv(serviceMap map[string]*commonmodels.ProductService, serviceName, serviceModule, env string) error { - if service, ok := serviceMap[serviceName]; !ok { +func checkServiceExsistsInEnv(serviceMap map[string]*commonmodels.ProductService, serviceName, env string) error { + if _, ok := serviceMap[serviceName]; !ok { return fmt.Errorf("service %s not exists in env %s", serviceName, env) - } else { - for _, container := range service.Containers { - if container.Name == serviceModule { - return nil - } - } } - return fmt.Errorf("service %s module %s not exists in env %s", serviceName, serviceModule, env) + return nil } -- Gitee From bbd06454dc7bb6459dce79c06af8144636b72c87 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 11 Jul 2022 20:32:23 +0800 Subject: [PATCH 2559/2647] make common workflow job name unique Signed-off-by: guoyu --- .../core/workflow/service/jobcontroller/job_freestyle.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 54be600f..20916a3c 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/google/uuid" zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -55,13 +56,14 @@ type FreestyleJobCtl struct { func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { paths := "" + uid, _ := uuid.NewUUID() return &FreestyleJobCtl{ job: job, workflowCtx: workflowCtx, logger: logger, ack: ack, paths: &paths, - jobName: fmt.Sprintf("%s-%s-%d", job.Name, job.JobType, workflowCtx.TaskID), + jobName: uid.String(), } } -- Gitee From 6c086d0161d0e44b0c643a1e0f232c5ae8bd83dd Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 12 Jul 2022 12:02:05 +0800 Subject: [PATCH 2560/2647] make common worklow easy to debug Signed-off-by: guoyu --- .../service/jobcontroller/job_freestyle.go | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 20916a3c..87e230a4 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -22,7 +22,6 @@ import ( "strings" "time" - "github.com/google/uuid" zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -30,6 +29,7 @@ import ( "github.com/koderover/zadig/pkg/tool/dockerhost" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/util/rand" "go.uber.org/zap" "gopkg.in/yaml.v3" "k8s.io/client-go/kubernetes" @@ -56,17 +56,31 @@ type FreestyleJobCtl struct { func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func(), logger *zap.SugaredLogger) *FreestyleJobCtl { paths := "" - uid, _ := uuid.NewUUID() return &FreestyleJobCtl{ job: job, workflowCtx: workflowCtx, logger: logger, ack: ack, paths: &paths, - jobName: uid.String(), + jobName: getJobName(workflowCtx.WorkflowName, workflowCtx.TaskID), } } +func getJobName(workflowName string, taskID int64) string { + // max lenth of workflowName was 32, so job name was unique in one task. + base := strings.Replace( + strings.ToLower( + fmt.Sprintf( + "%s-%d-", + workflowName, + taskID, + ), + ), + "_", "-", -1, + ) + return rand.GenerateName(base) +} + func (c *FreestyleJobCtl) Run(ctx context.Context) { // get kube client hubServerAddr := config.HubServerAddress() -- Gitee From 89df54a94d09ec8c7e3c832fe23b654f52a7fce8 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 12 Jul 2022 13:36:08 +0800 Subject: [PATCH 2561/2647] fix import issue Signed-off-by: guoyu --- .../workflow/service/jobcontroller/job_freestyle.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 87e230a4..3587701a 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -22,6 +22,12 @@ import ( "strings" "time" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + crClient "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -30,11 +36,6 @@ import ( krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/util/rand" - "go.uber.org/zap" - "gopkg.in/yaml.v3" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - crClient "sigs.k8s.io/controller-runtime/pkg/client" ) const ( -- Gitee From 8f86377b7ef41150a7bde410fd73a410218dd9d4 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Jul 2022 14:48:56 +0800 Subject: [PATCH 2562/2647] fix multiple gitlab source in build will cause token refresh error Signed-off-by: Min Min --- pkg/tool/git/gitlab/client.go | 57 ++------------------- pkg/tool/git/gitlab/token.go | 96 +++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 53 deletions(-) create mode 100644 pkg/tool/git/gitlab/token.go diff --git a/pkg/tool/git/gitlab/client.go b/pkg/tool/git/gitlab/client.go index 9f379d4e..b8213d82 100644 --- a/pkg/tool/git/gitlab/client.go +++ b/pkg/tool/git/gitlab/client.go @@ -21,12 +21,9 @@ import ( "io" "net/http" "net/url" - "time" - "github.com/koderover/zadig/pkg/tool/log" "github.com/xanzy/go-gitlab" - "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/httpclient" ) @@ -61,29 +58,12 @@ func NewClient(id int, address, accessToken, proxyAddr string, enableProxy bool) client = http.DefaultClient } - if accessToken != "" { - ch, err := systemconfig.New().GetCodeHost(id) - // The normal expiration time is 7200 - if err == nil && (time.Now().Unix()-ch.UpdatedAt) >= 7000 { - token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.SecretKey, ch.RefreshToken) - if err == nil { - accessToken = token.AccessToken - ch.AccessToken = token.AccessToken - ch.RefreshToken = token.RefreshToken - ch.UpdatedAt = int64(token.CreatedAt) - - if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - log.Errorf("failed to update codeHost err:%s", err) - } - } else { - log.Errorf("failed to refresh accessToken, err:%s", err) - } - } else if err != nil { - log.Errorf("failed to get codeHost id: %d, err:%s", id, err) - } + token, err := UpdateGitlabToken(id, accessToken) + if err != nil { + return nil, fmt.Errorf("failed to refresh gitlab token, err: %s", err) } - cli, err := gitlab.NewOAuthClient(accessToken, gitlab.WithBaseURL(address), gitlab.WithHTTPClient(client)) + cli, err := gitlab.NewOAuthClient(token, gitlab.WithBaseURL(address), gitlab.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("failed to create gitlab client, err: %s", err) } @@ -144,32 +124,3 @@ func wrapError(res *gitlab.Response, err error) error { } return nil } - -type AccessToken struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` - RefreshToken string `json:"refresh_token"` - Scope string `json:"scope"` - CreatedAt int `json:"created_at"` -} - -func refreshAccessToken(address, clientID, clientSecret, refreshToken string) (*AccessToken, error) { - httpClient := httpclient.New( - httpclient.SetHostURL(address), - ) - url := "/oauth/token" - queryParams := make(map[string]string) - queryParams["grant_type"] = "refresh_token" - queryParams["refresh_token"] = refreshToken - queryParams["client_id"] = clientID - queryParams["client_secret"] = clientSecret - - var accessToken *AccessToken - _, err := httpClient.Post(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&accessToken)) - if err != nil { - return nil, err - } - - return accessToken, nil -} diff --git a/pkg/tool/git/gitlab/token.go b/pkg/tool/git/gitlab/token.go new file mode 100644 index 00000000..95c4ed34 --- /dev/null +++ b/pkg/tool/git/gitlab/token.go @@ -0,0 +1,96 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gitlab + +import ( + "fmt" + "sync" + "time" + + "github.com/koderover/zadig/pkg/shared/client/systemconfig" + "github.com/koderover/zadig/pkg/tool/httpclient" + "github.com/koderover/zadig/pkg/tool/log" +) + +type AccessToken struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + CreatedAt int `json:"created_at"` +} + +var CodeHostLock map[int]*sync.RWMutex + +const TokenExpirationThreshold int64 = 7000 + +func UpdateGitlabToken(id int, accessToken string) (string, error) { + // if accessToken is empty, then it is either of ssh token type or username/password, we return empty + if accessToken == "" { + return "", nil + } + + CodeHostLock[id].Lock() + defer CodeHostLock[id].Unlock() + ch, err := systemconfig.New().GetCodeHost(id) + + if err != nil { + return "", fmt.Errorf("get codehost info error: [%s]", err) + } + + if time.Now().Unix()-ch.UpdatedAt >= TokenExpirationThreshold { + token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.SecretKey, ch.RefreshToken) + if err != nil { + return "", err + } + + ch.AccessToken = token.AccessToken + ch.RefreshToken = token.RefreshToken + ch.UpdatedAt = int64(token.CreatedAt) + + // Since the new token is valid, we simply log the error + // No error will be returned, only the new token is returned + if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { + log.Errorf("failed to update codehost, err: %s", err) + } + + return token.AccessToken, nil + } + + return accessToken, nil +} + +func refreshAccessToken(address, clientID, clientSecret, refreshToken string) (*AccessToken, error) { + httpClient := httpclient.New( + httpclient.SetHostURL(address), + ) + url := "/oauth/token" + queryParams := make(map[string]string) + queryParams["grant_type"] = "refresh_token" + queryParams["refresh_token"] = refreshToken + queryParams["client_id"] = clientID + queryParams["client_secret"] = clientSecret + + var accessToken *AccessToken + _, err := httpClient.Post(url, httpclient.SetQueryParams(queryParams), httpclient.SetResult(&accessToken)) + if err != nil { + return nil, err + } + + return accessToken, nil +} -- Gitee From 2a94f7e644ed819647f99db666fe6afd84873ef6 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Jul 2022 14:59:13 +0800 Subject: [PATCH 2563/2647] minor stucture change to make code easier to read Signed-off-by: Min Min --- pkg/tool/git/gitlab/token.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/tool/git/gitlab/token.go b/pkg/tool/git/gitlab/token.go index 95c4ed34..367a626b 100644 --- a/pkg/tool/git/gitlab/token.go +++ b/pkg/tool/git/gitlab/token.go @@ -53,26 +53,26 @@ func UpdateGitlabToken(id int, accessToken string) (string, error) { return "", fmt.Errorf("get codehost info error: [%s]", err) } - if time.Now().Unix()-ch.UpdatedAt >= TokenExpirationThreshold { - token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.SecretKey, ch.RefreshToken) - if err != nil { - return "", err - } - - ch.AccessToken = token.AccessToken - ch.RefreshToken = token.RefreshToken - ch.UpdatedAt = int64(token.CreatedAt) - - // Since the new token is valid, we simply log the error - // No error will be returned, only the new token is returned - if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { - log.Errorf("failed to update codehost, err: %s", err) - } - - return token.AccessToken, nil + if time.Now().Unix()-ch.UpdatedAt <= TokenExpirationThreshold { + return accessToken, nil } - return accessToken, nil + token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.SecretKey, ch.RefreshToken) + if err != nil { + return "", err + } + + ch.AccessToken = token.AccessToken + ch.RefreshToken = token.RefreshToken + ch.UpdatedAt = int64(token.CreatedAt) + + // Since the new token is valid, we simply log the error + // No error will be returned, only the new token is returned + if err = systemconfig.New().UpdateCodeHost(ch.ID, ch); err != nil { + log.Errorf("failed to update codehost, err: %s", err) + } + + return token.AccessToken, nil } func refreshAccessToken(address, clientID, clientSecret, refreshToken string) (*AccessToken, error) { -- Gitee From 512ad9321660afeb46c0e302e40374f16629b4eb Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Jul 2022 15:14:57 +0800 Subject: [PATCH 2564/2647] concurrency problem fixed Signed-off-by: Min Min --- pkg/tool/git/gitlab/token.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/tool/git/gitlab/token.go b/pkg/tool/git/gitlab/token.go index 367a626b..e7058ba3 100644 --- a/pkg/tool/git/gitlab/token.go +++ b/pkg/tool/git/gitlab/token.go @@ -35,7 +35,7 @@ type AccessToken struct { CreatedAt int `json:"created_at"` } -var CodeHostLock map[int]*sync.RWMutex +var CodeHostLockMap sync.Map const TokenExpirationThreshold int64 = 7000 @@ -45,8 +45,10 @@ func UpdateGitlabToken(id int, accessToken string) (string, error) { return "", nil } - CodeHostLock[id].Lock() - defer CodeHostLock[id].Unlock() + lockInterface, _ := CodeHostLockMap.LoadOrStore(id, &sync.RWMutex{}) + lock := lockInterface.(*sync.RWMutex) + lock.Lock() + defer lock.Unlock() ch, err := systemconfig.New().GetCodeHost(id) if err != nil { -- Gitee From bf8ea1833b920c7e4f310cb7a43101a091721625 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Jul 2022 15:32:04 +0800 Subject: [PATCH 2565/2647] added debug logs Signed-off-by: Min Min --- pkg/tool/git/gitlab/token.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/tool/git/gitlab/token.go b/pkg/tool/git/gitlab/token.go index e7058ba3..a068201d 100644 --- a/pkg/tool/git/gitlab/token.go +++ b/pkg/tool/git/gitlab/token.go @@ -49,6 +49,9 @@ func UpdateGitlabToken(id int, accessToken string) (string, error) { lock := lockInterface.(*sync.RWMutex) lock.Lock() defer lock.Unlock() + + log.Infof("Starting to update gitlab token") + ch, err := systemconfig.New().GetCodeHost(id) if err != nil { @@ -74,6 +77,8 @@ func UpdateGitlabToken(id int, accessToken string) (string, error) { log.Errorf("failed to update codehost, err: %s", err) } + log.Infof("gitlab token for client [%d] changed from [%s] to [%s]", id, accessToken, token.AccessToken) + return token.AccessToken, nil } -- Gitee From 655c192460b4d9d50e359647a8b08573bf6773a9 Mon Sep 17 00:00:00 2001 From: Min Min Date: Tue, 12 Jul 2022 16:16:44 +0800 Subject: [PATCH 2566/2647] debug for gitlab authorization & logs refinement Signed-off-by: Min Min --- pkg/tool/git/gitlab/token.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/tool/git/gitlab/token.go b/pkg/tool/git/gitlab/token.go index a068201d..3aad5069 100644 --- a/pkg/tool/git/gitlab/token.go +++ b/pkg/tool/git/gitlab/token.go @@ -50,8 +50,6 @@ func UpdateGitlabToken(id int, accessToken string) (string, error) { lock.Lock() defer lock.Unlock() - log.Infof("Starting to update gitlab token") - ch, err := systemconfig.New().GetCodeHost(id) if err != nil { @@ -59,9 +57,11 @@ func UpdateGitlabToken(id int, accessToken string) (string, error) { } if time.Now().Unix()-ch.UpdatedAt <= TokenExpirationThreshold { - return accessToken, nil + return ch.AccessToken, nil } + log.Infof("Starting to refresh gitlab token") + token, err := refreshAccessToken(ch.Address, ch.AccessKey, ch.SecretKey, ch.RefreshToken) if err != nil { return "", err -- Gitee From 55222eba4a5e063b9e7be02a9f977643f2f4e098 Mon Sep 17 00:00:00 2001 From: lilianzhu Date: Tue, 12 Jul 2022 21:18:37 +0800 Subject: [PATCH 2567/2647] modify nginx image Signed-off-by: lilianzhu --- examples/nginx/Dockerfile | 2 +- examples/nginx/yaml/nginx.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nginx/Dockerfile b/examples/nginx/Dockerfile index 7fea54fd..9d06efce 100644 --- a/examples/nginx/Dockerfile +++ b/examples/nginx/Dockerfile @@ -1,2 +1,2 @@ -from ccr.ccs.tencentyun.com/shuhe/nginx:stable +from koderover.tencentcloudcr.com/koderover-demo/nginx add ./index.html /usr/share/nginx/html \ No newline at end of file diff --git a/examples/nginx/yaml/nginx.yaml b/examples/nginx/yaml/nginx.yaml index 2c16e8cf..3a0ae54f 100644 --- a/examples/nginx/yaml/nginx.yaml +++ b/examples/nginx/yaml/nginx.yaml @@ -35,7 +35,7 @@ spec: spec: containers: - name: nginx-test - image: ccr.ccs.tencentyun.com/shuhe/nginx:stable + image: koderover.tencentcloudcr.com/koderover-demo/nginx ports: - containerPort: 80 volumeMounts: -- Gitee From da5e2afb59cc2a8067cd400fe091bfd0f913917a Mon Sep 17 00:00:00 2001 From: lilianzhu Date: Wed, 13 Jul 2022 09:52:51 +0800 Subject: [PATCH 2568/2647] modify examples image registy Signed-off-by: lilianzhu --- examples/microservice-demo/chart/values.yaml | 4 ++-- examples/microservice-demo/frontend/Dockerfile | 2 +- examples/microservice-demo/k8s-yaml/backend/deployment.yaml | 2 +- examples/microservice-demo/k8s-yaml/frontend/deployment.yaml | 2 +- examples/multi-service-demo/base-chart/values.yaml | 2 +- examples/multi-service-demo/full-charts/service1/values.yaml | 2 +- examples/multi-service-demo/full-charts/service2/values.yaml | 2 +- examples/multi-service-demo/general-chart/values.yaml | 2 +- examples/multi-service-demo/k8s-yaml/service1/deployment.yaml | 2 +- examples/multi-service-demo/k8s-yaml/service2/deployment.yaml | 2 +- examples/multi-service-demo/k8s-yaml/service3/deployment.yaml | 2 +- examples/multi-service-demo/k8s-yaml/template.yaml | 2 +- examples/multi-service-demo/values/service1.yaml | 2 +- examples/multi-service-demo/values/service2.yaml | 2 +- examples/multi-service-demo/values/service3.yaml | 2 +- examples/simple-service/nginx-yaml/nginx.yaml | 2 +- examples/simple-service/service-yaml/a.yaml | 2 +- examples/simple-service/service-yaml/b.yaml | 2 +- examples/simple-service/service-yaml/c.yaml | 2 +- .../spring-boot-acme-financial/acme-financial-account.yaml | 4 ++-- .../spring-boot-acme-financial/acme-financial-customer.yaml | 4 ++-- .../spring-boot-acme-financial/acme-financial-office.yaml | 4 ++-- examples/spring-boot-acme-financial/acme-financial-ui.yaml | 4 ++-- .../account-mongodb-service/7-account-mongodb-service.yaml | 2 +- .../account-service/8-account-service.yaml | 4 ++-- .../auth-mongodb-service/5-auth-mongodb-service.yaml | 2 +- .../auth-service/6-auth-service.yaml | 4 ++-- .../config-service/2-config-service.yaml | 4 ++-- .../gateway-service/4-gateway-service.yaml | 4 ++-- .../monitoring-service/13-monitoring-service.yaml | 4 ++-- .../11-notification-mongodb-service.yaml | 2 +- .../notification-service/12-notification-service.yaml | 4 ++-- .../registry-service/3-registry-service.yaml | 4 ++-- .../9-statistics-mongodb-service.yaml | 2 +- .../statistics-services/10-statistics-service.yaml | 4 ++-- .../turbine-stream-service/14-turbine-stream-service.yaml | 4 ++-- examples/website/yaml/website-example.yaml | 2 +- 37 files changed, 51 insertions(+), 51 deletions(-) diff --git a/examples/microservice-demo/chart/values.yaml b/examples/microservice-demo/chart/values.yaml index 58fc3cee..b3b78d71 100644 --- a/examples/microservice-demo/chart/values.yaml +++ b/examples/microservice-demo/chart/values.yaml @@ -6,13 +6,13 @@ ingressClass: kr-nginx Backend: replicaCount: 1 image: - repository: ccr.ccs.tencentyun.com/koderover-public/backend + repository: koderover.tencentcloudcr.com/koderover-demo/backend tag: "latest" Frontend: replicaCount: 1 image: - repository: ccr.ccs.tencentyun.com/koderover-public/frontend + repository: koderover.tencentcloudcr.com/koderover-demo/frontend tag: "latest" domain: demo.domain.com diff --git a/examples/microservice-demo/frontend/Dockerfile b/examples/microservice-demo/frontend/Dockerfile index 1d287cf6..0c0dcfd6 100644 --- a/examples/microservice-demo/frontend/Dockerfile +++ b/examples/microservice-demo/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM ccr.ccs.tencentyun.com/trial-dist/frontend-base:0.1 As builder +FROM koderover.tencentcloudcr.com/koderover-demo/frontend-base:0.1 As builder WORKDIR /frontend diff --git a/examples/microservice-demo/k8s-yaml/backend/deployment.yaml b/examples/microservice-demo/k8s-yaml/backend/deployment.yaml index 63b62c65..a1ddb6b0 100644 --- a/examples/microservice-demo/k8s-yaml/backend/deployment.yaml +++ b/examples/microservice-demo/k8s-yaml/backend/deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: backend - image: ccr.ccs.tencentyun.com/koderover-public/backend:latest + image: koderover.tencentcloudcr.com/koderover-demo/backend:latest imagePullPolicy: Always command: - /workspace/backend diff --git a/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml b/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml index f752fa25..1ca9547a 100644 --- a/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml +++ b/examples/microservice-demo/k8s-yaml/frontend/deployment.yaml @@ -24,7 +24,7 @@ spec: spec: containers: - name: frontend - image: ccr.ccs.tencentyun.com/koderover-public/frontend:latest + image: koderover.tencentcloudcr.com/koderover-demo/frontend:latest imagePullPolicy: Always ports: - protocol: TCP diff --git a/examples/multi-service-demo/base-chart/values.yaml b/examples/multi-service-demo/base-chart/values.yaml index 005f6ebf..1851f7f9 100644 --- a/examples/multi-service-demo/base-chart/values.yaml +++ b/examples/multi-service-demo/base-chart/values.yaml @@ -5,7 +5,7 @@ port: 20221 imagePullSecretsName: "service-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service" + repository: "koderover.tencentcloudcr.com/koderover-demo/service" tag: "latest" resources: diff --git a/examples/multi-service-demo/full-charts/service1/values.yaml b/examples/multi-service-demo/full-charts/service1/values.yaml index f91bbe3c..0a1b782e 100644 --- a/examples/multi-service-demo/full-charts/service1/values.yaml +++ b/examples/multi-service-demo/full-charts/service1/values.yaml @@ -5,7 +5,7 @@ port: 20221 imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service1" + repository: "koderover.tencentcloudcr.com/koderover-demo/service1" tag: "latest" resources: diff --git a/examples/multi-service-demo/full-charts/service2/values.yaml b/examples/multi-service-demo/full-charts/service2/values.yaml index 99eb1f7f..62eca550 100644 --- a/examples/multi-service-demo/full-charts/service2/values.yaml +++ b/examples/multi-service-demo/full-charts/service2/values.yaml @@ -5,7 +5,7 @@ port: 20222 imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service2" + repository: "koderover.tencentcloudcr.com/koderover-demo/service2" tag: "latest" resources: diff --git a/examples/multi-service-demo/general-chart/values.yaml b/examples/multi-service-demo/general-chart/values.yaml index f6b6773f..f4247c21 100644 --- a/examples/multi-service-demo/general-chart/values.yaml +++ b/examples/multi-service-demo/general-chart/values.yaml @@ -5,7 +5,7 @@ port: {{.port}} imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/$T-Service$" + repository: "koderover.tencentcloudcr.com/koderover-demo/$T-Service$" tag: "latest" resources: diff --git a/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml index ae8d6e6d..027cf31c 100644 --- a/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service1/deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: service1 - image: ccr.ccs.tencentyun.com/koderover-public/service1:latest + image: koderover.tencentcloudcr.com/koderover-demo/service1:latest imagePullPolicy: Always command: - /workspace/service1 diff --git a/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml index 9c24eebf..87a6f842 100644 --- a/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service2/deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: service2 - image: ccr.ccs.tencentyun.com/koderover-public/service2:latest + image: koderover.tencentcloudcr.com/koderover-demo/service2:latest imagePullPolicy: Always command: - /workspace/service2 diff --git a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml index 4248ddf7..eb8bb107 100644 --- a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: service3 - image: ccr.ccs.tencentyun.com/koderover-public/service3:latest + image: koderover.tencentcloudcr.com/koderover-demo/service3:latest imagePullPolicy: Always command: - /workspace/service3 diff --git a/examples/multi-service-demo/k8s-yaml/template.yaml b/examples/multi-service-demo/k8s-yaml/template.yaml index d6f56181..520becd8 100644 --- a/examples/multi-service-demo/k8s-yaml/template.yaml +++ b/examples/multi-service-demo/k8s-yaml/template.yaml @@ -19,7 +19,7 @@ spec: spec: containers: - name: $T-Service$ - image: ccr.ccs.tencentyun.com/koderover-public/$T-Service$:latest + image: koderover.tencentcloudcr.com/koderover-demo/$T-Service$:latest imagePullPolicy: Always command: - /workspace/$T-Service$ diff --git a/examples/multi-service-demo/values/service1.yaml b/examples/multi-service-demo/values/service1.yaml index 72d49c5a..e069766a 100644 --- a/examples/multi-service-demo/values/service1.yaml +++ b/examples/multi-service-demo/values/service1.yaml @@ -5,7 +5,7 @@ port: 20221 imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service1" + repository: "koderover.tencentcloudcr.com/koderover-demo/service1" tag: "latest" resources: diff --git a/examples/multi-service-demo/values/service2.yaml b/examples/multi-service-demo/values/service2.yaml index d4299151..4a7e6111 100644 --- a/examples/multi-service-demo/values/service2.yaml +++ b/examples/multi-service-demo/values/service2.yaml @@ -5,7 +5,7 @@ port: 20222 imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service2" + repository: "koderover.tencentcloudcr.com/koderover-demo/service2" tag: "latest" resources: diff --git a/examples/multi-service-demo/values/service3.yaml b/examples/multi-service-demo/values/service3.yaml index 3aba4e0c..f01e84fc 100644 --- a/examples/multi-service-demo/values/service3.yaml +++ b/examples/multi-service-demo/values/service3.yaml @@ -5,7 +5,7 @@ port: 20223 imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service3" + repository: "koderover.tencentcloudcr.com/koderover-demo/service3" tag: "latest" resources: diff --git a/examples/simple-service/nginx-yaml/nginx.yaml b/examples/simple-service/nginx-yaml/nginx.yaml index b0f96974..c8d573c0 100644 --- a/examples/simple-service/nginx-yaml/nginx.yaml +++ b/examples/simple-service/nginx-yaml/nginx.yaml @@ -28,7 +28,7 @@ spec: spec: containers: - name: nginx - image: ccr.ccs.tencentyun.com/koderover-rc/nginx:v1 + image: koderover.tencentcloudcr.com/koderover-demo/nginx:v1 ports: - containerPort: 80 resources: diff --git a/examples/simple-service/service-yaml/a.yaml b/examples/simple-service/service-yaml/a.yaml index 5a555b3c..e2e8ef5c 100644 --- a/examples/simple-service/service-yaml/a.yaml +++ b/examples/simple-service/service-yaml/a.yaml @@ -32,7 +32,7 @@ spec: spec: containers: - name: myapp-1 - image: ccr.ccs.tencentyun.com/koderover-rc/myapp-1:v0.1__linux_amd64 + image: koderover.tencentcloudcr.com/koderover-demo/myapp-1:v0.1__linux_amd64 imagePullPolicy: Always command: ["/myapp-1"] args: ["--downstream-addr", "$(DOWNSTREAM_ADDR)", "--headers", "$(HEADERS)"] diff --git a/examples/simple-service/service-yaml/b.yaml b/examples/simple-service/service-yaml/b.yaml index 94e57b54..5e5ffb20 100644 --- a/examples/simple-service/service-yaml/b.yaml +++ b/examples/simple-service/service-yaml/b.yaml @@ -32,7 +32,7 @@ spec: spec: containers: - name: myapp-2 - image: ccr.ccs.tencentyun.com/koderover-rc/myapp-2:v0.1__linux_amd64 + image: koderover.tencentcloudcr.com/koderover-demo/myapp-2:v0.1__linux_amd64 imagePullPolicy: Always command: ["/myapp-2"] args: ["--downstream-addr", "$(DOWNSTREAM_ADDR)", "--headers", "$(HEADERS)"] diff --git a/examples/simple-service/service-yaml/c.yaml b/examples/simple-service/service-yaml/c.yaml index b9c7e551..60c751ba 100644 --- a/examples/simple-service/service-yaml/c.yaml +++ b/examples/simple-service/service-yaml/c.yaml @@ -32,7 +32,7 @@ spec: spec: containers: - name: myapp-3 - image: ccr.ccs.tencentyun.com/koderover-rc/myapp-3:v0.1__linux_amd64 + image: koderover.tencentcloudcr.com/koderover-demo/myapp-3:v0.1__linux_amd64 imagePullPolicy: Always command: ["/myapp-3"] ports: diff --git a/examples/spring-boot-acme-financial/acme-financial-account.yaml b/examples/spring-boot-acme-financial/acme-financial-account.yaml index c7180608..04c662d5 100644 --- a/examples/spring-boot-acme-financial/acme-financial-account.yaml +++ b/examples/spring-boot-acme-financial/acme-financial-account.yaml @@ -17,7 +17,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -40,7 +40,7 @@ spec: value: "acme-financial-account" - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES value: "skywalking-oap.skywalking.svc.cluster.local:11800" - image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-account:v0.1 + image: koderover.tencentcloudcr.com/koderover-demo/acme-financial-account:v0.1 imagePullPolicy: Always name: account ports: diff --git a/examples/spring-boot-acme-financial/acme-financial-customer.yaml b/examples/spring-boot-acme-financial/acme-financial-customer.yaml index 11e93bf7..2bba4aa8 100644 --- a/examples/spring-boot-acme-financial/acme-financial-customer.yaml +++ b/examples/spring-boot-acme-financial/acme-financial-customer.yaml @@ -16,7 +16,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -39,7 +39,7 @@ spec: value: "acme-financial-customer" - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES value: "skywalking-oap.skywalking.svc.cluster.local:11800" - image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-customer:v0.1 + image: koderover.tencentcloudcr.com/koderover-demo/acme-financial-customer:v0.1 imagePullPolicy: Always name: customer ports: diff --git a/examples/spring-boot-acme-financial/acme-financial-office.yaml b/examples/spring-boot-acme-financial/acme-financial-office.yaml index f7d97bdd..0c96e7fa 100644 --- a/examples/spring-boot-acme-financial/acme-financial-office.yaml +++ b/examples/spring-boot-acme-financial/acme-financial-office.yaml @@ -16,7 +16,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -39,7 +39,7 @@ spec: value: "acme-financial-office" - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES value: "skywalking-oap.skywalking.svc.cluster.local:11800" - image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-office:v0.1 + image: koderover.tencentcloudcr.com/koderover-demo/acme-financial-office:v0.1 imagePullPolicy: Always name: office ports: diff --git a/examples/spring-boot-acme-financial/acme-financial-ui.yaml b/examples/spring-boot-acme-financial/acme-financial-ui.yaml index c5508061..58ccd712 100644 --- a/examples/spring-boot-acme-financial/acme-financial-ui.yaml +++ b/examples/spring-boot-acme-financial/acme-financial-ui.yaml @@ -16,7 +16,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -39,7 +39,7 @@ spec: value: "acme-financial-ui" - name: SW_AGENT_COLLECTOR_BACKEND_SERVICES value: "skywalking-oap.skywalking.svc.cluster.local:11800" - image: ccr.ccs.tencentyun.com/koderover-public/acme-financial-ui:v0.1 + image: koderover.tencentcloudcr.com/koderover-demo/acme-financial-ui:v0.1 imagePullPolicy: Always name: ui ports: diff --git a/examples/spring-cloud-piggymetrics/account-mongodb-service/7-account-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/account-mongodb-service/7-account-mongodb-service.yaml index 2e6fa315..90d62cb3 100644 --- a/examples/spring-cloud-piggymetrics/account-mongodb-service/7-account-mongodb-service.yaml +++ b/examples/spring-cloud-piggymetrics/account-mongodb-service/7-account-mongodb-service.yaml @@ -52,7 +52,7 @@ spec: key: account_mongodb_password ports: - containerPort: 27017 - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-mongodb:v0.1.1 resources: limits: cpu: 500m diff --git a/examples/spring-cloud-piggymetrics/account-service/8-account-service.yaml b/examples/spring-cloud-piggymetrics/account-service/8-account-service.yaml index 3a0a2a0b..a6477665 100644 --- a/examples/spring-cloud-piggymetrics/account-service/8-account-service.yaml +++ b/examples/spring-cloud-piggymetrics/account-service/8-account-service.yaml @@ -42,7 +42,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -78,7 +78,7 @@ spec: name: special-config-env ports: - containerPort: 6000 - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-account-service:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-account-service:v0.3.0 name: account-service resources: limits: diff --git a/examples/spring-cloud-piggymetrics/auth-mongodb-service/5-auth-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/auth-mongodb-service/5-auth-mongodb-service.yaml index 8b346311..619915f0 100644 --- a/examples/spring-cloud-piggymetrics/auth-mongodb-service/5-auth-mongodb-service.yaml +++ b/examples/spring-cloud-piggymetrics/auth-mongodb-service/5-auth-mongodb-service.yaml @@ -41,7 +41,7 @@ spec: app: auth-mongodb spec: containers: - - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + - image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-mongodb:v0.1.1 name: auth-mongodb env: - name: MONGODB_PASSWORD diff --git a/examples/spring-cloud-piggymetrics/auth-service/6-auth-service.yaml b/examples/spring-cloud-piggymetrics/auth-service/6-auth-service.yaml index 34eaf6fb..d8fdde86 100644 --- a/examples/spring-cloud-piggymetrics/auth-service/6-auth-service.yaml +++ b/examples/spring-cloud-piggymetrics/auth-service/6-auth-service.yaml @@ -42,7 +42,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -86,7 +86,7 @@ spec: envFrom: - configMapRef: name: special-config-env - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-auth-service:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-auth-service:v0.3.0 name: auth-service resources: limits: diff --git a/examples/spring-cloud-piggymetrics/config-service/2-config-service.yaml b/examples/spring-cloud-piggymetrics/config-service/2-config-service.yaml index 5ab3faf2..279d0144 100644 --- a/examples/spring-cloud-piggymetrics/config-service/2-config-service.yaml +++ b/examples/spring-cloud-piggymetrics/config-service/2-config-service.yaml @@ -42,7 +42,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -66,7 +66,7 @@ spec: envFrom: - configMapRef: name: special-config-env - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-config:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-config:v0.3.0 name: config resources: limits: diff --git a/examples/spring-cloud-piggymetrics/gateway-service/4-gateway-service.yaml b/examples/spring-cloud-piggymetrics/gateway-service/4-gateway-service.yaml index 72cac37c..60bc3d99 100644 --- a/examples/spring-cloud-piggymetrics/gateway-service/4-gateway-service.yaml +++ b/examples/spring-cloud-piggymetrics/gateway-service/4-gateway-service.yaml @@ -50,7 +50,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -75,7 +75,7 @@ spec: envFrom: - configMapRef: name: special-config-env - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-gateway:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-gateway:v0.3.0 name: gateway resources: limits: diff --git a/examples/spring-cloud-piggymetrics/monitoring-service/13-monitoring-service.yaml b/examples/spring-cloud-piggymetrics/monitoring-service/13-monitoring-service.yaml index 35e5d564..df5efd73 100644 --- a/examples/spring-cloud-piggymetrics/monitoring-service/13-monitoring-service.yaml +++ b/examples/spring-cloud-piggymetrics/monitoring-service/13-monitoring-service.yaml @@ -44,7 +44,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -68,7 +68,7 @@ spec: envFrom: - configMapRef: name: special-config-env - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-monitoring:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-monitoring:v0.3.0 name: monitoring resources: limits: diff --git a/examples/spring-cloud-piggymetrics/notification-mongodb-service/11-notification-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/notification-mongodb-service/11-notification-mongodb-service.yaml index 640dfa83..813d6f62 100644 --- a/examples/spring-cloud-piggymetrics/notification-mongodb-service/11-notification-mongodb-service.yaml +++ b/examples/spring-cloud-piggymetrics/notification-mongodb-service/11-notification-mongodb-service.yaml @@ -49,7 +49,7 @@ spec: key: notification_mongodb_password ports: - containerPort: 27017 - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-mongodb:v0.1.1 name: notification-mongodb resources: limits: diff --git a/examples/spring-cloud-piggymetrics/notification-service/12-notification-service.yaml b/examples/spring-cloud-piggymetrics/notification-service/12-notification-service.yaml index d6158694..e0fae28b 100644 --- a/examples/spring-cloud-piggymetrics/notification-service/12-notification-service.yaml +++ b/examples/spring-cloud-piggymetrics/notification-service/12-notification-service.yaml @@ -42,7 +42,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -98,7 +98,7 @@ spec: name: special-config-env ports: - containerPort: 8000 - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-notification-service:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-notification-service:v0.3.0 name: notification-service resources: limits: diff --git a/examples/spring-cloud-piggymetrics/registry-service/3-registry-service.yaml b/examples/spring-cloud-piggymetrics/registry-service/3-registry-service.yaml index a8778f0b..1667036b 100644 --- a/examples/spring-cloud-piggymetrics/registry-service/3-registry-service.yaml +++ b/examples/spring-cloud-piggymetrics/registry-service/3-registry-service.yaml @@ -42,7 +42,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -66,7 +66,7 @@ spec: envFrom: - configMapRef: name: special-config-env - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-registry:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-registry:v0.3.0 name: registry resources: limits: diff --git a/examples/spring-cloud-piggymetrics/statistics-mongodb-service/9-statistics-mongodb-service.yaml b/examples/spring-cloud-piggymetrics/statistics-mongodb-service/9-statistics-mongodb-service.yaml index 30731a60..ee10305a 100644 --- a/examples/spring-cloud-piggymetrics/statistics-mongodb-service/9-statistics-mongodb-service.yaml +++ b/examples/spring-cloud-piggymetrics/statistics-mongodb-service/9-statistics-mongodb-service.yaml @@ -49,7 +49,7 @@ spec: key: statistics_mongodb_password ports: - containerPort: 27017 - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-mongodb:v0.1.1 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-mongodb:v0.1.1 name: statistics-mongodb resources: limits: diff --git a/examples/spring-cloud-piggymetrics/statistics-services/10-statistics-service.yaml b/examples/spring-cloud-piggymetrics/statistics-services/10-statistics-service.yaml index f10acc8d..0a830c2c 100644 --- a/examples/spring-cloud-piggymetrics/statistics-services/10-statistics-service.yaml +++ b/examples/spring-cloud-piggymetrics/statistics-services/10-statistics-service.yaml @@ -42,7 +42,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -78,7 +78,7 @@ spec: name: special-config-env ports: - containerPort: 7000 - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-statistics-service:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-statistics-service:v0.3.0 name: statistics-service resources: limits: diff --git a/examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml b/examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml index ffded7b4..8a84f384 100644 --- a/examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml +++ b/examples/spring-cloud-piggymetrics/turbine-stream-service/14-turbine-stream-service.yaml @@ -45,7 +45,7 @@ spec: spec: initContainers: - name: init-skywalking-agent - image: ccr.ccs.tencentyun.com/koderover-public/skywalking-agent:8.8.0 + image: koderover.tencentcloudcr.com/koderover-demo/skywalking-agent:8.8.0 command: - 'sh' - '-c' @@ -69,7 +69,7 @@ spec: envFrom: - configMapRef: name: special-config-env - image: ccr.ccs.tencentyun.com/koderover-public/piggymetrics-statistics-service:v0.3.0 + image: koderover.tencentcloudcr.com/koderover-demo/piggymetrics-statistics-service:v0.3.0 name: turbine-stream-service resources: limits: diff --git a/examples/website/yaml/website-example.yaml b/examples/website/yaml/website-example.yaml index 86f93ec5..7e343caa 100644 --- a/examples/website/yaml/website-example.yaml +++ b/examples/website/yaml/website-example.yaml @@ -28,6 +28,6 @@ spec: spec: containers: - name: website-example - image: ccr.ccs.tencentyun.com/koderover-public/website-example:1.0.0 + image: koderover.tencentcloudcr.com/koderover-demo/website-example:1.0.0 ports: - containerPort: 80 -- Gitee From 93973cc729aecf5b162b33f6e0dcd506628dcbb0 Mon Sep 17 00:00:00 2001 From: panxunying Date: Wed, 13 Jul 2022 15:37:39 +0800 Subject: [PATCH 2569/2647] fix collaboration labelbindings was deleted by mistake Signed-off-by: panxunying --- .../core/label/repository/mongodb/label.go | 5 ++-- .../label/repository/mongodb/labelbinding.go | 11 ++++++++ .../aslan/core/label/service/label.go | 28 ++----------------- .../aslan/core/project/service/product.go | 6 ++-- 4 files changed, 20 insertions(+), 30 deletions(-) diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/label.go b/pkg/microservice/aslan/core/label/repository/mongodb/label.go index 3f493657..364025b8 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/label.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/label.go @@ -111,12 +111,13 @@ func (c *LabelColl) BulkDeleteByProject(projectName string) error { } func (c *LabelColl) ListByProjectName(projectName string) ([]*models.Label, error) { + if len(projectName) == 0 { + return nil, nil + } query := bson.M{} var res []*models.Label query["project_name"] = projectName - opts := options.Find() ctx := context.Background() - opts.SetSort(bson.D{{"key", 1}}) cursor, err := c.Collection.Find(ctx, query) if err != nil { return nil, err diff --git a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go index f003130c..4be91140 100644 --- a/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go +++ b/pkg/microservice/aslan/core/label/repository/mongodb/labelbinding.go @@ -142,6 +142,17 @@ func (c *LabelBindingColl) ListByOpt(opt *LabelBindingCollFindOpt) ([]*models.La return ret, err } +func (c *LabelBindingColl) BulkDeleteByProject(projectName string) error { + if projectName == "" { + return nil + } + + query := bson.M{} + query["project_name"] = projectName + _, err := c.DeleteMany(context.TODO(), query) + return err +} + func (c *LabelBindingColl) BulkDeleteByLabelIds(labelIds []string) error { if len(labelIds) == 0 { return nil diff --git a/pkg/microservice/aslan/core/label/service/label.go b/pkg/microservice/aslan/core/label/service/label.go index eff028ad..b2af9dda 100644 --- a/pkg/microservice/aslan/core/label/service/label.go +++ b/pkg/microservice/aslan/core/label/service/label.go @@ -216,31 +216,9 @@ func DeleteLabels(ids []string, forceDelete bool, userName string, logger *zap.S return mongodb.NewLabelColl().BulkDelete(ids) } -func DeleteLabelsAndBindingsByProject(projectName, userName string, logger *zap.SugaredLogger) error { - - labels, err := mongodb.NewLabelColl().ListByProjectName(projectName) - if err != nil { - return err - } - - ids := []string{} - for _, label := range labels { - ids = append(ids, label.ID.Hex()) - } - - labelBindings, err := mongodb.NewLabelBindingColl().ListByOpt(&mongodb.LabelBindingCollFindOpt{LabelIDs: ids}) - if err != nil { - logger.Errorf("list labelbingding err:%s", err) - return err - } - - var labelBindingIDs []string - for _, labelBinding := range labelBindings { - labelBindingIDs = append(labelBindingIDs, labelBinding.ID.Hex()) - } - logger.Infof("user:%s DeleteLabelsAndBindingsByProject labelbindingIDs:%s", userName, labelBindingIDs) - if err := mongodb.NewLabelBindingColl().BulkDeleteByIds(labelBindingIDs); err != nil { - logger.Errorf("BulkDeleteByIds err :%s,ids:%v", err, labelBindingIDs) +func DeleteLabelsAndBindingsByProject(projectName string, logger *zap.SugaredLogger) error { + if err := mongodb.NewLabelBindingColl().BulkDeleteByProject(projectName); err != nil { + logger.Errorf("DeleteLabelsByProject err:%s", err) return err } diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index b3e4fba9..9ee4279b 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -403,7 +403,7 @@ func DeleteProductTemplate(userName, productName, requestID string, isDelete boo return err } - if err = DeleteLabels(productName, userName, log); err != nil { + if err = DeleteLabels(productName, log); err != nil { log.Errorf("DeleteLabels productName %s err: %s", productName, err) return err } @@ -980,8 +980,8 @@ func DeletePolicy(productName string, log *zap.SugaredLogger) error { return nil } -func DeleteLabels(productName, userName string, log *zap.SugaredLogger) error { - if err := service2.DeleteLabelsAndBindingsByProject(productName, userName, log); err != nil { +func DeleteLabels(productName string, log *zap.SugaredLogger) error { + if err := service2.DeleteLabelsAndBindingsByProject(productName, log); err != nil { log.Errorf("delete labels and bindings by project fail , err :%s", err) return err } -- Gitee From efa5eee5769b7719384a28d65bde5c5a39645b2a Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 13 Jul 2022 16:29:07 +0800 Subject: [PATCH 2570/2647] change used image repo url and namespace Signed-off-by: allenshen --- Makefile | 2 +- docker/base/amd64/ubuntu-bionic.Dockerfile | 2 +- docker/base/amd64/ubuntu-focal.Dockerfile | 2 +- docker/base/amd64/ubuntu-xenial.Dockerfile | 2 +- docker/base/arm64/ubuntu-bionic.Dockerfile | 2 +- docker/base/arm64/ubuntu-focal.Dockerfile | 2 +- docker/base/arm64/ubuntu-xenial.Dockerfile | 2 +- .../full-charts/service3/values.yaml | 2 +- .../service/jobcontroller/job_freestyle.go | 2 +- .../workflow/service/jobcontroller/kubernetes.go | 4 ++-- .../core/service/taskplugin/deploy_test.go | 4 ++-- .../warpdrive/core/service/taskplugin/utils.go | 2 +- pkg/microservice/zgctl/utils.go | 14 +++++++------- pkg/tool/kube/serializer/decoder_test.go | 2 +- pkg/types/env.go | 2 +- pkg/types/ide.go | 2 +- pkg/util/yaml/path_search_test.go | 4 ++-- 17 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 50e5f2cb..0341b1bb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # New Makefile for multi-architecture .PHONY: all -IMAGE_REPOSITORY = ccr.ccs.tencentyun.com/koderover-rc +IMAGE_REPOSITORY = koderover.tencentcloudcr.com/koderover-public VERSION ?= $(shell date +'%Y%m%d%H%M%S') VERSION := $(VERSION) TARGETS = aslan config cron hub-agent hub-server init jenkins-plugin packager-plugin picket podexec policy predator-plugin ua user warpdrive diff --git a/docker/base/amd64/ubuntu-bionic.Dockerfile b/docker/base/amd64/ubuntu-bionic.Dockerfile index 1a18b751..87608d6b 100644 --- a/docker/base/amd64/ubuntu-bionic.Dockerfile +++ b/docker/base/amd64/ubuntu-bionic.Dockerfile @@ -1 +1 @@ -FROM ccr.ccs.tencentyun.com/koderover-public/build-base:bionic-amd64 +FROM koderover.tencentcloudcr.com/koderover-public/build-base:bionic-amd64 diff --git a/docker/base/amd64/ubuntu-focal.Dockerfile b/docker/base/amd64/ubuntu-focal.Dockerfile index 789e5914..32eaf34b 100644 --- a/docker/base/amd64/ubuntu-focal.Dockerfile +++ b/docker/base/amd64/ubuntu-focal.Dockerfile @@ -1 +1 @@ -FROM ccr.ccs.tencentyun.com/koderover-public/build-base:focal-amd64 +FROM koderover.tencentcloudcr.com/koderover-public/build-base:focal-amd64 diff --git a/docker/base/amd64/ubuntu-xenial.Dockerfile b/docker/base/amd64/ubuntu-xenial.Dockerfile index 25232f62..05ba3257 100644 --- a/docker/base/amd64/ubuntu-xenial.Dockerfile +++ b/docker/base/amd64/ubuntu-xenial.Dockerfile @@ -1 +1 @@ -FROM ccr.ccs.tencentyun.com/koderover-public/build-base:xenial-amd64 +FROM koderover.tencentcloudcr.com/koderover-public/build-base:xenial-amd64 diff --git a/docker/base/arm64/ubuntu-bionic.Dockerfile b/docker/base/arm64/ubuntu-bionic.Dockerfile index 6a6b4aaf..ffeeda2e 100644 --- a/docker/base/arm64/ubuntu-bionic.Dockerfile +++ b/docker/base/arm64/ubuntu-bionic.Dockerfile @@ -1 +1 @@ -FROM ccr.ccs.tencentyun.com/koderover-public/build-base:bionic-arm64 +FROM koderover.tencentcloudcr.com/koderover-public/build-base:bionic-arm64 diff --git a/docker/base/arm64/ubuntu-focal.Dockerfile b/docker/base/arm64/ubuntu-focal.Dockerfile index da5e8899..0b9385e6 100644 --- a/docker/base/arm64/ubuntu-focal.Dockerfile +++ b/docker/base/arm64/ubuntu-focal.Dockerfile @@ -1 +1 @@ -FROM ccr.ccs.tencentyun.com/koderover-public/build-base:focal-arm64 +FROM koderover.tencentcloudcr.com/koderover-public/build-base:focal-arm64 diff --git a/docker/base/arm64/ubuntu-xenial.Dockerfile b/docker/base/arm64/ubuntu-xenial.Dockerfile index 593fdef5..75bb2bd8 100644 --- a/docker/base/arm64/ubuntu-xenial.Dockerfile +++ b/docker/base/arm64/ubuntu-xenial.Dockerfile @@ -1,2 +1,2 @@ -FROM ccr.ccs.tencentyun.com/koderover-public/build-base:xenial-arm64 +FROM koderover.tencentcloudcr.com/koderover-public/build-base:xenial-arm64 diff --git a/examples/multi-service-demo/full-charts/service3/values.yaml b/examples/multi-service-demo/full-charts/service3/values.yaml index 57a6f941..60a93100 100644 --- a/examples/multi-service-demo/full-charts/service3/values.yaml +++ b/examples/multi-service-demo/full-charts/service3/values.yaml @@ -5,7 +5,7 @@ port: 20223 imagePullSecretsName: "default-registry-secret" image: - repository: "ccr.ccs.tencentyun.com/koderover-public/service3" + repository: "koderover.tencentcloudcr.com/koderover-demo/service3" tag: "latest" resources: diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go index 3587701a..3f2156d9 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go @@ -169,7 +169,7 @@ func (c *FreestyleJobCtl) Run(ctx context.Context) { // TODO: do not use default image jobImage := getBaseImage(c.job.Properties.BuildOS, c.job.Properties.ImageFrom) - // jobImage := "ccr.ccs.tencentyun.com/koderover-rc/job-excutor:guoyu-test2" + // jobImage := "koderover.tencentcloudcr.com/test/job-excutor:guoyu-test2" // jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 474d62a2..88899dd8 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -58,7 +58,7 @@ import ( ) const ( - BusyBoxImage = "ccr.ccs.tencentyun.com/koderover-public/busybox:latest" + BusyBoxImage = "koderover.tencentcloudcr.com/koderover-public/busybox:latest" ZadigContextDir = "/zadig/" ZadigLogFile = ZadigContextDir + "zadig.log" ZadigLifeCycleFile = ZadigContextDir + "lifecycle" @@ -135,7 +135,7 @@ func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx st func getBaseImage(buildOS, imageFrom string) string { // for built-in image, reaperImage and buildOs can generate a complete image - // reaperImage: ccr.ccs.tencentyun.com/koderover-public/build-base:${BuildOS}-amd64 + // reaperImage: koderover.tencentcloudcr.com/koderover-public /build-base:${BuildOS}-amd64 // buildOS: focal xenial bionic jobImage := strings.ReplaceAll(config.ReaperImage(), "${BuildOS}", buildOS) // for custom image, buildOS represents the exact custom image diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_test.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_test.go index 9cb71c12..ddcca068 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_test.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_test.go @@ -33,12 +33,12 @@ env: dev ingressClassName: koderover-admin-nginx image: - - repository: ccr.ccs.tencentyun.com/trial/go-sample-site + - repository: koderover.tencentcloudcr.com/test/go-sample-site pullPolicy: IfNotPresent tag: "0.1.0" imageNormal: - repository: ccr.ccs.tencentyun.com/trial/go-sample-site + repository: koderover.tencentcloudcr.com/test/go-sample-site pullPolicy: IfNotPresent tag: "0.1.0" diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go index 5fb10ff8..4b5c3a91 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/utils.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/utils.go @@ -289,7 +289,7 @@ func InstantiateBuildSysVariables(jobCtx *task.JobCtx) []*task.KeyVal { // depends on the setting on page 'Build' func getReaperImage(reaperImage, buildOS, imageFrom string) string { // for built-in image, reaperImage and buildOs can generate a complete image - // reaperImage: ccr.ccs.tencentyun.com/koderover-public/build-base:${BuildOS}-amd64 + // reaperImage: koderover.tencentcloudcr.com/koderover-public/build-base:${BuildOS}-amd64 // buildOS: focal xenial bionic jobImage := strings.ReplaceAll(reaperImage, "${BuildOS}", buildOS) // for custom image, buildOS represents the exact custom image diff --git a/pkg/microservice/zgctl/utils.go b/pkg/microservice/zgctl/utils.go index 3fb8d564..2319ebd4 100644 --- a/pkg/microservice/zgctl/utils.go +++ b/pkg/microservice/zgctl/utils.go @@ -17,11 +17,11 @@ limitations under the License. package zgctl var devImages = []string{ - "ccr.ccs.tencentyun.com/koderover-rc/golang:1.18.1", - "ccr.ccs.tencentyun.com/koderover-rc/node:18.2", - "ccr.ccs.tencentyun.com/koderover-rc/python:3.10.4-buster", - "ccr.ccs.tencentyun.com/koderover-rc/openjdk:19-jdk-buster", - "ccr.ccs.tencentyun.com/koderover-rc/php:8.1.6-fpm", - "ccr.ccs.tencentyun.com/koderover-rc/ruby:3.1.2", - "ccr.ccs.tencentyun.com/koderover-rc/rust:1.61.0", + "koderover.tencentcloudcr.com/koderover-public/golang:1.18.1", + "koderover.tencentcloudcr.com/koderover-public/node:18.2", + "koderover.tencentcloudcr.com/koderover-public/python:3.10.4-buster", + "koderover.tencentcloudcr.com/koderover-public/openjdk:19-jdk-buster", + "koderover.tencentcloudcr.com/koderover-public/php:8.1.6-fpm", + "koderover.tencentcloudcr.com/koderover-public/ruby:3.1.2", + "koderover.tencentcloudcr.com/koderover-public/rust:1.61.0", } diff --git a/pkg/tool/kube/serializer/decoder_test.go b/pkg/tool/kube/serializer/decoder_test.go index efe0eccc..0685b3c1 100644 --- a/pkg/tool/kube/serializer/decoder_test.go +++ b/pkg/tool/kube/serializer/decoder_test.go @@ -42,7 +42,7 @@ spec: tier: backend spec: containers: - - image: ccr.ccs.tencentyun.com/koderover-public/library-postgres:9.6 + - image: koderover.tencentcloudcr.com/koderover-public/library-postgres:9.6 imagePullPolicy: IfNotPresent name: postgres ports: diff --git a/pkg/types/env.go b/pkg/types/env.go index 1afe53a5..8b75d7ea 100644 --- a/pkg/types/env.go +++ b/pkg/types/env.go @@ -23,4 +23,4 @@ const ( ShareEnv EnvType = "share" ) -const DebugImage = "ccr.ccs.tencentyun.com/koderover-rc/zadig-debug:v0.1.0" +const DebugImage = "koderover.tencentcloudcr.com/koderover-public/zadig-debug:v0.1.0" diff --git a/pkg/types/ide.go b/pkg/types/ide.go index 60b8fb25..c4a50881 100644 --- a/pkg/types/ide.go +++ b/pkg/types/ide.go @@ -28,6 +28,6 @@ type StartDevmodeInfo struct { DevImage string `json:"dev_image"` } -const IDESidecarImage = "ccr.ccs.tencentyun.com/koderover-rc/zgctl-sidecar:20220526172433-amd64" +const IDESidecarImage = "koderover.tencentcloudcr.com/koderover-public/zgctl-sidecar:20220526172433-amd64" const DevmodeWorkDir = "/home/zadig/" diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 2a3bb342..878a3439 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -73,7 +73,7 @@ env: dev repoData1: global: - hub: ccr.ccs.tencentyun.com/trial + hub: koderover.tencentcloudcr.com/koderover-public testSpec: image: @@ -109,7 +109,7 @@ var testYaml9 = ` - name: default-registry-secret global: - hub: ccr.ccs.tencentyun.com/trial + hub: koderover.tencentcloudcr.com/test deploy: image: -- Gitee From 4f7af572c2f35eb509f14c9921d4e8dcafbad88e Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 13 Jul 2022 16:55:30 +0800 Subject: [PATCH 2571/2647] remove blank Signed-off-by: allenshen --- .../aslan/core/workflow/service/jobcontroller/kubernetes.go | 2 +- pkg/util/yaml/path_search_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go index 88899dd8..8fcb42ba 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go @@ -135,7 +135,7 @@ func createJobConfigMap(namespace, jobName string, jobLabel *JobLabel, jobCtx st func getBaseImage(buildOS, imageFrom string) string { // for built-in image, reaperImage and buildOs can generate a complete image - // reaperImage: koderover.tencentcloudcr.com/koderover-public /build-base:${BuildOS}-amd64 + // reaperImage: koderover.tencentcloudcr.com/koderover-public/build-base:${BuildOS}-amd64 // buildOS: focal xenial bionic jobImage := strings.ReplaceAll(config.ReaperImage(), "${BuildOS}", buildOS) // for custom image, buildOS represents the exact custom image diff --git a/pkg/util/yaml/path_search_test.go b/pkg/util/yaml/path_search_test.go index 878a3439..bf285e6b 100644 --- a/pkg/util/yaml/path_search_test.go +++ b/pkg/util/yaml/path_search_test.go @@ -73,7 +73,7 @@ env: dev repoData1: global: - hub: koderover.tencentcloudcr.com/koderover-public + hub: koderover.tencentcloudcr.com/koderover-public testSpec: image: -- Gitee From 6b0578502a549b34cb35034e38cf3f8028a97e44 Mon Sep 17 00:00:00 2001 From: guoyu Date: Wed, 13 Jul 2022 19:45:42 +0800 Subject: [PATCH 2572/2647] delete workflowv4 when delete related project Signed-off-by: guoyu --- .../aslan/core/common/service/workflow_v4.go | 80 +++++++++++++++++++ .../workflowcontroller}/jobcontroller/job.go | 2 +- .../jobcontroller/job_freestyle.go | 0 .../jobcontroller/kubernetes.go | 0 .../jobcontroller/types.go | 0 .../service/workflowcontroller/queue.go | 0 .../service/workflowcontroller/stage.go | 0 .../workflowcontroller/stage_custom.go | 2 +- .../stepcontroller/step.go | 0 .../stepcontroller/step_archive.go | 0 .../stepcontroller/step_deploy.go | 0 .../stepcontroller/step_docker_build.go | 0 .../stepcontroller/step_git.go | 0 .../stepcontroller/step_helm_deploy.go | 0 .../stepcontroller/step_shell.go | 0 .../stepcontroller/step_tool_install.go | 0 .../service/workflowcontroller/workflow.go | 0 .../aslan/core/project/service/product.go | 5 ++ pkg/microservice/aslan/core/service.go | 2 +- .../aslan/core/workflow/handler/sse.go | 2 +- .../service/workflow/workflow_task_v4.go | 2 +- 21 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/service/workflow_v4.go rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/jobcontroller/job.go (97%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/jobcontroller/job_freestyle.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/jobcontroller/kubernetes.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/jobcontroller/types.go (100%) rename pkg/microservice/aslan/core/{workflow => common}/service/workflowcontroller/queue.go (100%) rename pkg/microservice/aslan/core/{workflow => common}/service/workflowcontroller/stage.go (100%) rename pkg/microservice/aslan/core/{workflow => common}/service/workflowcontroller/stage_custom.go (93%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_archive.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_deploy.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_docker_build.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_git.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_helm_deploy.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_shell.go (100%) rename pkg/microservice/aslan/core/{workflow/service => common/service/workflowcontroller}/stepcontroller/step_tool_install.go (100%) rename pkg/microservice/aslan/core/{workflow => common}/service/workflowcontroller/workflow.go (100%) diff --git a/pkg/microservice/aslan/core/common/service/workflow_v4.go b/pkg/microservice/aslan/core/common/service/workflow_v4.go new file mode 100644 index 00000000..96af58aa --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/workflow_v4.go @@ -0,0 +1,80 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "fmt" + + "github.com/hashicorp/go-multierror" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller" + e "github.com/koderover/zadig/pkg/tool/errors" + "github.com/koderover/zadig/pkg/tool/log" + "go.uber.org/zap" +) + +func DeleteWorkflowV4s(productName string, log *zap.SugaredLogger) error { + workflowV4s, _, err := mongodb.NewWorkflowV4Coll().List(&mongodb.ListWorkflowV4Option{ProjectName: productName}, 0, 0) + if err != nil { + log.Errorf("WorkflowV4.List error: %s", err) + return fmt.Errorf("DeleteWorkflowV4s productName %s WorkflowV4.List error: %s", productName, err) + } + errList := new(multierror.Error) + for _, workflowV4 := range workflowV4s { + if err = DeleteWorkflowV4(workflowV4.Name, log); err != nil { + errList = multierror.Append(errList, fmt.Errorf("productName %s workflowV4 delete %s error: %s", productName, workflowV4.Name, err)) + } + } + if err := errList.ErrorOrNil(); err != nil { + log.Error(err) + return err + } + return nil +} + +func DeleteWorkflowV4(name string, logger *zap.SugaredLogger) error { + taskQueue, err := mongodb.NewWorkflowQueueColl().List(&mongodb.ListWorfklowQueueOption{}) + if err != nil { + log.Errorf("List queued task error: %s", err) + return e.ErrDeletePipeline.AddErr(err) + } + // when task still running,cancel task first. + for _, task := range taskQueue { + if task.WorkflowName == name { + if err = workflowcontroller.CancelWorkflowTask("system", task.WorkflowName, task.TaskID, logger); err != nil { + log.Errorf("task still running, cancel pipelineV4 %s task %d", task.WorkflowName, task.TaskID) + } + } + } + workflow, err := mongodb.NewWorkflowV4Coll().Find(name) + if err != nil { + logger.Errorf("Failed to delete WorkflowV4: %s, the error is: %v", name, err) + return e.ErrDeleteWorkflow.AddErr(err) + } + if err := mongodb.NewWorkflowV4Coll().DeleteByID(workflow.ID.Hex()); err != nil { + logger.Errorf("Failed to delete WorkflowV4: %s, the error is: %v", name, err) + return e.ErrDeleteWorkflow.AddErr(err) + } + if err := mongodb.NewworkflowTaskv4Coll().DeleteByWorkflowName(name); err != nil { + logger.Errorf("Failed to delete WorkflowV4 task: %s, the error is: %v", name, err) + return e.ErrDeleteWorkflow.AddErr(err) + } + if err := mongodb.NewCounterColl().Delete("WorkflowTaskV4:" + name); err != nil { + log.Errorf("Counter.Delete error: %s", err) + } + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go similarity index 97% rename from pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go index e60eb6b7..8efd9f71 100644 --- a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go @@ -25,7 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/stepcontroller" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller" "github.com/koderover/zadig/pkg/setting" "go.uber.org/zap" ) diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_freestyle.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/jobcontroller/job_freestyle.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_freestyle.go diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/jobcontroller/kubernetes.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go diff --git a/pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/types.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/jobcontroller/types.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/types.go diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/queue.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/workflowcontroller/queue.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/queue.go diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go similarity index 93% rename from pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go index f96ba8c7..45c360a7 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/stage_custom.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go @@ -20,7 +20,7 @@ import ( "context" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/jobcontroller" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller" "go.uber.org/zap" ) diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_archive.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_deploy.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_docker_build.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_git.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_helm_deploy.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_shell.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go diff --git a/pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/stepcontroller/step_tool_install.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go diff --git a/pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/workflow.go similarity index 100% rename from pkg/microservice/aslan/core/workflow/service/workflowcontroller/workflow.go rename to pkg/microservice/aslan/core/common/service/workflowcontroller/workflow.go diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 9ee4279b..299dd645 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -418,6 +418,11 @@ func DeleteProductTemplate(userName, productName, requestID string, isDelete boo return err } + if err = commonservice.DeleteWorkflowV4s(productName, log); err != nil { + log.Errorf("DeleteProductTemplate Delete productName %s workflowV4 err: %s", productName, err) + return err + } + if err = commonservice.DeletePipelines(productName, requestID, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s pipeline err: %s", productName, err) return err diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 9112bfa6..01ab7f30 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -34,13 +34,13 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/nsq" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/webhook" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller" environmentservice "github.com/koderover/zadig/pkg/microservice/aslan/core/environment/service" labelMongodb "github.com/koderover/zadig/pkg/microservice/aslan/core/label/repository/mongodb" multiclusterservice "github.com/koderover/zadig/pkg/microservice/aslan/core/multicluster/service" systemrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/system/repository/mongodb" systemservice "github.com/koderover/zadig/pkg/microservice/aslan/core/system/service" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/log" diff --git a/pkg/microservice/aslan/core/workflow/handler/sse.go b/pkg/microservice/aslan/core/workflow/handler/sse.go index 42ea8401..6257f294 100644 --- a/pkg/microservice/aslan/core/workflow/handler/sse.go +++ b/pkg/microservice/aslan/core/workflow/handler/sse.go @@ -26,9 +26,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller" "github.com/koderover/zadig/pkg/microservice/aslan/core/delivery/service" "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" internalhandler "github.com/koderover/zadig/pkg/shared/handler" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types/dto" diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index e7b334e2..5a11f13c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -23,8 +23,8 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller" jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" - "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflowcontroller" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/types" -- Gitee From 075b6645d52a4c61a1a0347eaa044ee79ad17f94 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 14 Jul 2022 10:24:56 +0800 Subject: [PATCH 2573/2647] change function name Signed-off-by: guoyu --- pkg/microservice/aslan/core/common/service/workflow_v4.go | 8 ++++---- pkg/microservice/aslan/core/project/service/product.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow_v4.go b/pkg/microservice/aslan/core/common/service/workflow_v4.go index 96af58aa..df4bfa0a 100644 --- a/pkg/microservice/aslan/core/common/service/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/service/workflow_v4.go @@ -27,16 +27,16 @@ import ( "go.uber.org/zap" ) -func DeleteWorkflowV4s(productName string, log *zap.SugaredLogger) error { - workflowV4s, _, err := mongodb.NewWorkflowV4Coll().List(&mongodb.ListWorkflowV4Option{ProjectName: productName}, 0, 0) +func DeleteWorkflowV4sByProjectName(projectName string, log *zap.SugaredLogger) error { + workflowV4s, _, err := mongodb.NewWorkflowV4Coll().List(&mongodb.ListWorkflowV4Option{ProjectName: projectName}, 0, 0) if err != nil { log.Errorf("WorkflowV4.List error: %s", err) - return fmt.Errorf("DeleteWorkflowV4s productName %s WorkflowV4.List error: %s", productName, err) + return fmt.Errorf("DeleteWorkflowV4s productName %s WorkflowV4.List error: %s", projectName, err) } errList := new(multierror.Error) for _, workflowV4 := range workflowV4s { if err = DeleteWorkflowV4(workflowV4.Name, log); err != nil { - errList = multierror.Append(errList, fmt.Errorf("productName %s workflowV4 delete %s error: %s", productName, workflowV4.Name, err)) + errList = multierror.Append(errList, fmt.Errorf("productName %s workflowV4 delete %s error: %s", projectName, workflowV4.Name, err)) } } if err := errList.ErrorOrNil(); err != nil { diff --git a/pkg/microservice/aslan/core/project/service/product.go b/pkg/microservice/aslan/core/project/service/product.go index 299dd645..91558373 100644 --- a/pkg/microservice/aslan/core/project/service/product.go +++ b/pkg/microservice/aslan/core/project/service/product.go @@ -418,7 +418,7 @@ func DeleteProductTemplate(userName, productName, requestID string, isDelete boo return err } - if err = commonservice.DeleteWorkflowV4s(productName, log); err != nil { + if err = commonservice.DeleteWorkflowV4sByProjectName(productName, log); err != nil { log.Errorf("DeleteProductTemplate Delete productName %s workflowV4 err: %s", productName, err) return err } -- Gitee From 43a030390c85f0a5a0c1cdd70a1d95e45e0bb945 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 14 Jul 2022 10:39:05 +0800 Subject: [PATCH 2574/2647] fix import issues Signed-off-by: guoyu --- .../aslan/core/common/service/workflow_v4.go | 3 ++- .../workflowcontroller/jobcontroller/job.go | 3 ++- .../common/service/workflowcontroller/stage.go | 3 ++- .../service/workflowcontroller/stage_custom.go | 3 ++- .../workflowcontroller/stepcontroller/step.go | 5 +++-- .../stepcontroller/step_archive.go | 5 +++-- .../stepcontroller/step_deploy.go | 15 ++++++++------- .../stepcontroller/step_docker_build.go | 5 +++-- .../workflowcontroller/stepcontroller/step_git.go | 5 +++-- .../stepcontroller/step_helm_deploy.go | 13 +++++++------ .../stepcontroller/step_shell.go | 5 +++-- .../stepcontroller/step_tool_install.go | 5 +++-- .../common/service/workflowcontroller/workflow.go | 5 +++-- 13 files changed, 44 insertions(+), 31 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflow_v4.go b/pkg/microservice/aslan/core/common/service/workflow_v4.go index df4bfa0a..c1a9e37e 100644 --- a/pkg/microservice/aslan/core/common/service/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/service/workflow_v4.go @@ -20,11 +20,12 @@ import ( "fmt" "github.com/hashicorp/go-multierror" + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" - "go.uber.org/zap" ) func DeleteWorkflowV4sByProjectName(projectName string, log *zap.SugaredLogger) error { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go index 8efd9f71..742d1bae 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go @@ -23,11 +23,12 @@ import ( "sync" "time" + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller" "github.com/koderover/zadig/pkg/setting" - "go.uber.org/zap" ) type JobCtl interface { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go index ca3ab3ca..0ff589cf 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage.go @@ -22,9 +22,10 @@ import ( "sync" "time" + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - "go.uber.org/zap" ) type approveMap struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go index 45c360a7..a24cc298 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stage_custom.go @@ -19,9 +19,10 @@ package workflowcontroller import ( "context" + "go.uber.org/zap" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller" - "go.uber.org/zap" ) type CustomStageCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go index d4e4ff56..9110358c 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go @@ -21,10 +21,11 @@ import ( "fmt" "strings" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "go.uber.org/zap" "gopkg.in/yaml.v3" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" ) type StepCtl interface { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go index 7bad70ca..21caef7c 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go @@ -20,11 +20,12 @@ import ( "context" "fmt" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/types/step" - "go.uber.org/zap" - "gopkg.in/yaml.v3" ) type archiveCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go index 40e2627d..3b3b5bff 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go @@ -22,6 +22,14 @@ import ( "strings" "time" + "github.com/pkg/errors" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" @@ -32,13 +40,6 @@ import ( "github.com/koderover/zadig/pkg/tool/kube/getter" "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/types/step" - "github.com/pkg/errors" - "go.uber.org/zap" - "gopkg.in/yaml.v3" - appsv1 "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" ) type deployCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go index 881f5834..b1c35759 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go @@ -20,12 +20,13 @@ import ( "context" "fmt" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/types/step" - "go.uber.org/zap" - "gopkg.in/yaml.v3" ) type dockerBuildCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go index baa659c6..21e0a4f4 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go @@ -20,14 +20,15 @@ import ( "context" "fmt" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/shared/client/systemconfig" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types/step" - "go.uber.org/zap" - "gopkg.in/yaml.v3" ) type gitCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go index 2db826ad..3c0c2428 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go @@ -24,12 +24,6 @@ import ( "strings" "time" - "github.com/koderover/zadig/pkg/microservice/aslan/config" - commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" - templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" - commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" - "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" @@ -46,6 +40,13 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + templatemodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/s3" + "github.com/koderover/zadig/pkg/setting" ) const ( diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go index 2a41f50c..c191f50b 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go @@ -20,11 +20,12 @@ import ( "context" "fmt" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/types/step" - "go.uber.org/zap" - "gopkg.in/yaml.v3" ) type shellCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go index 07bd72ef..305d925b 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go @@ -21,13 +21,14 @@ import ( "fmt" "strings" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/types/step" - "go.uber.org/zap" - "gopkg.in/yaml.v3" ) type toolInstallCtl struct { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/workflow.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/workflow.go index 0edd8700..6cea6f25 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/workflow.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/workflow.go @@ -22,11 +22,12 @@ import ( "sync" "time" + uuid "github.com/satori/go.uuid" + "go.uber.org/zap" + "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" - uuid "github.com/satori/go.uuid" - "go.uber.org/zap" ) var cancelChannelMap sync.Map -- Gitee From 6728aef3fbc3fae8dd71c6ef086cfdd6617051d2 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 14 Jul 2022 12:06:14 +0800 Subject: [PATCH 2575/2647] fix webhook task lost deploy infos in hosting environment Signed-off-by: guoyu --- .../aslan/core/common/service/product.go | 79 +++++++++++++++++++ .../core/workflow/service/webhook/github.go | 47 ----------- .../service/webhook/github_workflow_task.go | 6 +- .../service/workflow/workflow_task.go | 77 +----------------- 4 files changed, 86 insertions(+), 123 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/product.go b/pkg/microservice/aslan/core/common/service/product.go index ca6952c1..b1b1c724 100644 --- a/pkg/microservice/aslan/core/common/service/product.go +++ b/pkg/microservice/aslan/core/common/service/product.go @@ -19,19 +19,25 @@ package service import ( "context" "fmt" + "strings" "github.com/hashicorp/go-multierror" "go.uber.org/zap" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/util/sets" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/util" ) +const SplitSymbol = "&" + func FilterWorkloadsByEnv(exist []commonmodels.Workload, env string) []commonmodels.Workload { result := make([]commonmodels.Workload, 0) for _, v := range exist { @@ -183,3 +189,76 @@ func GetProductEnvNamespace(envName, productName, namespace string) string { } return product.Namespace } + +func GetProductTargetMap(prod *commonmodels.Product) (map[string][]commonmodels.DeployEnv, map[string]string) { + resp := make(map[string][]commonmodels.DeployEnv) + imageNameM := make(map[string]string) + if prod.Source == setting.SourceFromExternal { + services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) + + currentServiceNames := sets.NewString() + for _, service := range services { + currentServiceNames.Insert(service.ServiceName) + } + + servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ + ProductName: prod.ProductName, + EnvName: prod.EnvName, + }) + + externalServiceNames := sets.NewString() + for _, serviceInExternalEnv := range servicesInExternalEnv { + if !currentServiceNames.Has(serviceInExternalEnv.ServiceName) { + externalServiceNames.Insert(serviceInExternalEnv.ServiceName) + } + } + + if len(externalServiceNames) > 0 { + newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, "", externalServiceNames.List()...) + for _, service := range newServices { + services = append(services, service) + } + } + + for _, service := range services { + for _, container := range service.Containers { + env := service.ServiceName + "/" + container.Name + deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} + target := strings.Join([]string{service.ProductName, service.ServiceName, container.Name}, SplitSymbol) + resp[target] = append(resp[target], deployEnv) + + imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) + } + } + return resp, imageNameM + } + for _, services := range prod.Services { + for _, serviceObj := range services { + switch serviceObj.Type { + case setting.K8SDeployType: + for _, container := range serviceObj.Containers { + env := serviceObj.ServiceName + "/" + container.Name + deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} + target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) + resp[target] = append(resp[target], deployEnv) + + imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) + } + case setting.PMDeployType: + deployEnv := commonmodels.DeployEnv{Type: setting.PMDeployType, Env: serviceObj.ServiceName} + target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, serviceObj.ServiceName}, SplitSymbol) + resp[target] = append(resp[target], deployEnv) + case setting.HelmDeployType: + for _, container := range serviceObj.Containers { + env := serviceObj.ServiceName + "/" + container.Name + deployEnv := commonmodels.DeployEnv{Type: setting.HelmDeployType, Env: env} + target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) + resp[target] = append(resp[target], deployEnv) + + imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) + } + } + } + } + return resp, imageNameM +} diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github.go b/pkg/microservice/aslan/core/workflow/service/webhook/github.go index f675b2b5..382136a3 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github.go @@ -524,53 +524,6 @@ type AutoCancelOpt struct { YamlHookPath string } -func getProductTargetMap(prod *commonmodels.Product, isYaml bool) map[string][]commonmodels.DeployEnv { - resp := make(map[string][]commonmodels.DeployEnv) - if prod.Source == setting.SourceFromExternal { - services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) - for _, service := range services { - for _, container := range service.Containers { - env := service.ServiceName + "/" + container.Name - deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} - target := strings.Join([]string{service.ProductName, service.ServiceName, container.Name}, SplitSymbol) - resp[target] = append(resp[target], deployEnv) - } - } - return resp - } - if isYaml { - return resp - } - for _, services := range prod.Services { - for _, serviceObj := range services { - switch serviceObj.Type { - case setting.K8SDeployType: - for _, container := range serviceObj.Containers { - env := serviceObj.ServiceName + "/" + container.Name - deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} - - target := fmt.Sprintf("%s%s%s%s%s", prod.ProductName, SplitSymbol, serviceObj.ServiceName, SplitSymbol, container.Name) - - resp[target] = append(resp[target], deployEnv) - } - case setting.PMDeployType: - deployEnv := commonmodels.DeployEnv{Type: setting.PMDeployType, Env: serviceObj.ServiceName} - target := fmt.Sprintf("%s%s%s%s%s", prod.ProductName, SplitSymbol, serviceObj.ServiceName, SplitSymbol, serviceObj.ServiceName) - resp[target] = append(resp[target], deployEnv) - case setting.HelmDeployType: - for _, container := range serviceObj.Containers { - env := serviceObj.ServiceName + "/" + container.Name - deployEnv := commonmodels.DeployEnv{Type: setting.HelmDeployType, Env: env} - - target := fmt.Sprintf("%s%s%s%s%s", prod.ProductName, SplitSymbol, serviceObj.ServiceName, SplitSymbol, container.Name) - resp[target] = append(resp[target], deployEnv) - } - } - } - } - return resp -} - func updateServiceTemplateByGithubPush(pushEvent *github.PushEvent, log *zap.SugaredLogger) error { changeFiles := make([]string, 0) for _, commit := range pushEvent.Commits { diff --git a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go index bea847ad..ed7faffd 100644 --- a/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/webhook/github_workflow_task.go @@ -30,6 +30,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" git "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/github" workflowservice "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" "github.com/koderover/zadig/pkg/setting" @@ -207,7 +208,10 @@ func (waf *workflowArgsFactory) Update(product *commonmodels.Product, args *comm target.Build.Repos = append(target.Build.Repos, repo) if len(target.Deploy) == 0 { if targetMap == nil { - targetMap = getProductTargetMap(product, waf.IsYaml) + targetMap, _ = commonservice.GetProductTargetMap(product) + } + if waf.IsYaml { + targetMap = make(map[string][]commonmodels.DeployEnv) } serviceModuleTarget := fmt.Sprintf("%s%s%s%s%s", args.ProductTmplName, SplitSymbol, target.ServiceName, SplitSymbol, target.Name) target.Deploy = targetMap[serviceModuleTarget] diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go index fa10ec05..27c6980b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task.go @@ -79,7 +79,7 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr return resp, e.ErrListBuildModule.AddDesc(err.Error()) } - targetMap, _ := getProductTargetMap(product) + targetMap, _ := commonservice.GetProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) targets := make([]*commonmodels.TargetArgs, 0) for _, container := range projectTargets { @@ -132,79 +132,6 @@ func GetWorkflowArgs(productName, namespace string, log *zap.SugaredLogger) (*Cr return resp, nil } -func getProductTargetMap(prod *commonmodels.Product) (map[string][]commonmodels.DeployEnv, map[string]string) { - resp := make(map[string][]commonmodels.DeployEnv) - imageNameM := make(map[string]string) - if prod.Source == setting.SourceFromExternal { - services, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, prod.EnvName) - - currentServiceNames := sets.NewString() - for _, service := range services { - currentServiceNames.Insert(service.ServiceName) - } - - servicesInExternalEnv, _ := commonrepo.NewServicesInExternalEnvColl().List(&commonrepo.ServicesInExternalEnvArgs{ - ProductName: prod.ProductName, - EnvName: prod.EnvName, - }) - - externalServiceNames := sets.NewString() - for _, serviceInExternalEnv := range servicesInExternalEnv { - if !currentServiceNames.Has(serviceInExternalEnv.ServiceName) { - externalServiceNames.Insert(serviceInExternalEnv.ServiceName) - } - } - - if len(externalServiceNames) > 0 { - newServices, _ := commonrepo.NewServiceColl().ListExternalWorkloadsBy(prod.ProductName, "", externalServiceNames.List()...) - for _, service := range newServices { - services = append(services, service) - } - } - - for _, service := range services { - for _, container := range service.Containers { - env := service.ServiceName + "/" + container.Name - deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} - target := strings.Join([]string{service.ProductName, service.ServiceName, container.Name}, SplitSymbol) - resp[target] = append(resp[target], deployEnv) - - imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) - } - } - return resp, imageNameM - } - for _, services := range prod.Services { - for _, serviceObj := range services { - switch serviceObj.Type { - case setting.K8SDeployType: - for _, container := range serviceObj.Containers { - env := serviceObj.ServiceName + "/" + container.Name - deployEnv := commonmodels.DeployEnv{Type: setting.K8SDeployType, Env: env} - target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) - resp[target] = append(resp[target], deployEnv) - - imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) - } - case setting.PMDeployType: - deployEnv := commonmodels.DeployEnv{Type: setting.PMDeployType, Env: serviceObj.ServiceName} - target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, serviceObj.ServiceName}, SplitSymbol) - resp[target] = append(resp[target], deployEnv) - case setting.HelmDeployType: - for _, container := range serviceObj.Containers { - env := serviceObj.ServiceName + "/" + container.Name - deployEnv := commonmodels.DeployEnv{Type: setting.HelmDeployType, Env: env} - target := strings.Join([]string{serviceObj.ProductName, serviceObj.ServiceName, container.Name}, SplitSymbol) - resp[target] = append(resp[target], deployEnv) - - imageNameM[target] = util.GetImageNameFromContainerInfo(container.ImageName, container.Name) - } - } - } - } - return resp, imageNameM -} - func getHideServiceModules(workflow *commonmodels.Workflow) sets.String { hideServiceModules := sets.NewString() if workflow.BuildStage != nil && workflow.BuildStage.Enabled { @@ -442,7 +369,7 @@ func PresetWorkflowArgs(namespace, workflowName string, log *zap.SugaredLogger) return resp, e.ErrListTestModule.AddDesc(err.Error()) } - targetMap, imageNameM := getProductTargetMap(product) + targetMap, imageNameM := commonservice.GetProductTargetMap(product) projectTargets := getProjectTargets(product.ProductName) hideServiceModules := getHideServiceModules(workflow) targets := make([]*commonmodels.TargetArgs, 0) -- Gitee From cf88bc2f70dad588fd7f721ec7956a172b9c75e3 Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 14 Jul 2022 17:49:44 +0800 Subject: [PATCH 2576/2647] save selected repo info so user can rerun task without select again Signed-off-by: guoyu --- .../aslan/core/common/repository/models/workflow_v4.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index cf2f2414..c5295fec 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -89,7 +89,7 @@ type ServiceAndBuild struct { Image string `bson:"-" yaml:"-" json:"image"` Package string `bson:"-" yaml:"-" json:"package"` KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` - Repos []*types.Repository `bson:"-" yaml:"-" json:"repos"` + Repos []*types.Repository `bson:"repos" yaml:"-" json:"repos"` } type ZadigDeployJobSpec struct { -- Gitee From da9defdcf3bd5ba44e0a80669308c150717a8a6f Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 14 Jul 2022 18:03:44 +0800 Subject: [PATCH 2577/2647] save runtime args Signed-off-by: guoyu --- .../aslan/core/common/repository/models/workflow_v4.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index c5295fec..da4ec123 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -93,13 +93,13 @@ type ServiceAndBuild struct { } type ZadigDeployJobSpec struct { - Env string `bson:"env" yaml:"env" json:"env"` - DeployType string `bson:"-" yaml:"-" json:"deploy_type"` + Env string `bson:"env" yaml:"env" json:"env"` + DeployType string `bson:"deploy_type" yaml:"-" json:"deploy_type"` // fromjob/runtime, runtime 表示运行时输入,fromjob 表示从上游构建任务中获取 Source config.DeploySourceType `bson:"source" yaml:"source" json:"source"` // 当 source 为 fromjob 时需要,指定部署镜像来源是上游哪一个构建任务 JobName string `bson:"job_name" yaml:"job_name" json:"job_name"` - ServiceAndImages []*ServiceAndImage `bson:"-" yaml:"-" json:"service_and_images"` + ServiceAndImages []*ServiceAndImage `bson:"service_and_images" yaml:"-" json:"service_and_images"` } type ServiceAndImage struct { -- Gitee From b39a9641ae1f218c2976b1f201558caa0569b46c Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 15 Jul 2022 16:31:30 +0800 Subject: [PATCH 2578/2647] encypt workflow v4 key values Signed-off-by: guoyu --- .../aslan/core/build/handler/build.go | 2 +- .../aslan/core/build/service/build.go | 5 +++- .../aslan/core/common/service/workflow_v4.go | 23 ++++++++++++++++ .../core/workflow/handler/workflow_v4.go | 4 +-- .../service/workflow/workflow_task_v4.go | 5 +++- .../workflow/service/workflow/workflow_v4.go | 27 ++++++++++++++++++- 6 files changed, 60 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/build/handler/build.go b/pkg/microservice/aslan/core/build/handler/build.go index 83f7da2e..7dae2b09 100644 --- a/pkg/microservice/aslan/core/build/handler/build.go +++ b/pkg/microservice/aslan/core/build/handler/build.go @@ -52,7 +52,7 @@ func ListBuildModulesByServiceModule(c *gin.Context) { excludeJenkins = true } - ctx.Resp, ctx.Err = buildservice.ListBuildModulesByServiceModule(c.Query("projectName"), excludeJenkins, ctx.Logger) + ctx.Resp, ctx.Err = buildservice.ListBuildModulesByServiceModule(c.Query("encryptedKey"), c.Query("projectName"), excludeJenkins, ctx.Logger) } func CreateBuildModule(c *gin.Context) { diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 2e2487aa..8c8002b0 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -118,7 +118,7 @@ func ListBuild(name, targets, productName string, log *zap.SugaredLogger) ([]*Bu return resp, nil } -func ListBuildModulesByServiceModule(productName string, excludeJenkins bool, log *zap.SugaredLogger) ([]*ServiceModuleAndBuildResp, error) { +func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJenkins bool, log *zap.SugaredLogger) ([]*ServiceModuleAndBuildResp, error) { services, err := commonrepo.NewServiceColl().ListMaxRevisionsByProduct(productName) if err != nil { return nil, e.ErrListBuildModule.AddErr(err) @@ -152,6 +152,9 @@ func ListBuildModulesByServiceModule(productName string, excludeJenkins bool, lo } } } + if err := commonservice.EncryptKeyVals(encryptedKey, build.PreBuild.Envs, log); err != nil { + return serviceModuleAndBuildResp, err + } resp = append(resp, &BuildResp{ ID: build.ID.Hex(), Name: build.Name, diff --git a/pkg/microservice/aslan/core/common/service/workflow_v4.go b/pkg/microservice/aslan/core/common/service/workflow_v4.go index c1a9e37e..1c7feafa 100644 --- a/pkg/microservice/aslan/core/common/service/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/service/workflow_v4.go @@ -22,8 +22,10 @@ import ( "github.com/hashicorp/go-multierror" "go.uber.org/zap" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller" + "github.com/koderover/zadig/pkg/tool/crypto" e "github.com/koderover/zadig/pkg/tool/errors" "github.com/koderover/zadig/pkg/tool/log" ) @@ -79,3 +81,24 @@ func DeleteWorkflowV4(name string, logger *zap.SugaredLogger) error { } return nil } + +func EncryptKeyVals(encryptedKey string, kvs []*commonmodels.KeyVal, logger *zap.SugaredLogger) error { + if encryptedKey == "" { + return nil + } + aesKey, err := GetAesKeyFromEncryptedKey(encryptedKey, logger) + if err != nil { + log.Errorf("EncypteKeyVals GetAesKeyFromEncryptedKey err:%v", err) + return err + } + for _, kv := range kvs { + if kv.IsCredential { + kv.Value, err = crypto.AesEncryptByKey(kv.Value, aesKey.PlainText) + if err != nil { + log.Errorf("aes encrypt by key err:%v", err) + return err + } + } + } + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index 79abfc03..73326c21 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -140,7 +140,7 @@ func DeleteWorkflowV4(c *gin.Context) { func FindWorkflowV4(c *gin.Context) { ctx := internalhandler.NewContext(c) - resp, err := workflow.FindWorkflowV4(c.Param("name"), ctx.Logger) + resp, err := workflow.FindWorkflowV4(c.Query("encryptedKey"), c.Param("name"), ctx.Logger) if err != nil { c.JSON(e.ErrorMessage(err)) c.Abort() @@ -153,5 +153,5 @@ func GetWorkflowV4Preset(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - ctx.Resp, ctx.Err = workflow.GetWorkflowv4Preset(c.Param("name"), ctx.Logger) + ctx.Resp, ctx.Err = workflow.GetWorkflowv4Preset(c.Query("encryptedKey"), c.Param("name"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 5a11f13c..90499a3e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -91,7 +91,7 @@ type ServiceAndImage struct { Image string `bson:"image" json:"image"` } -func GetWorkflowv4Preset(workflowName string, log *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { +func GetWorkflowv4Preset(encryptedKey, workflowName string, log *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { workflow, err := commonrepo.NewWorkflowV4Coll().Find(workflowName) if err != nil { log.Errorf("cannot find workflow %s, the error is: %v", workflowName, err) @@ -105,6 +105,9 @@ func GetWorkflowv4Preset(workflowName string, log *zap.SugaredLogger) (*commonmo } } } + if err := ensureWorkflowV4Resp(encryptedKey, workflow, log); err != nil { + return workflow, err + } return workflow, nil } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index c26fd656..d6cf64d6 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -27,6 +27,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" templaterepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb/template" + commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" jobctl "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow/job" "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" @@ -103,12 +104,15 @@ func UpdateWorkflowV4(name, user string, inputWorkflow *commonmodels.WorkflowV4, return nil } -func FindWorkflowV4(name string, logger *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { +func FindWorkflowV4(encryptedKey, name string, logger *zap.SugaredLogger) (*commonmodels.WorkflowV4, error) { workflow, err := commonrepo.NewWorkflowV4Coll().Find(name) if err != nil { logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) return workflow, e.ErrFindWorkflow.AddErr(err) } + if err := ensureWorkflowV4Resp(encryptedKey, workflow, logger); err != nil { + return workflow, err + } return workflow, err } @@ -224,6 +228,27 @@ func getRecentTaskV4Info(workflow *Workflow, tasks []*commonmodels.WorkflowTask) } } +func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { + for _, stage := range workflow.Stages { + for _, job := range stage.Jobs { + if job.JobType == config.JobZadigBuild { + spec := &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + for _, build := range spec.ServiceAndBuilds { + if err := commonservice.EncryptKeyVals(encryptedKey, build.KeyVals, logger); err != nil { + return e.ErrFindWorkflow.AddErr(err) + } + } + job.Spec = spec + } + } + } + return nil +} + func LintWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { if workflow.Project == "" { err := fmt.Errorf("project should not be empty") -- Gitee From dee2b39c88b8fe4f44eabf0ede4e1f8266e77845 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 18 Jul 2022 13:11:00 +0800 Subject: [PATCH 2579/2647] fix favorite workflow did not work Signed-off-by: guoyu --- pkg/microservice/aslan/core/workflow/handler/workflow_v4.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go index 79abfc03..30c01c78 100644 --- a/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/handler/workflow_v4.go @@ -99,7 +99,7 @@ func ListWorkflowV4(c *gin.Context) { ignoreWorkflow = true } - workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserName, workflowNames, ignoreWorkflow, ctx.Logger) + workflowList, err := workflow.ListWorkflowV4(args.Project, ctx.UserID, workflowNames, ignoreWorkflow, ctx.Logger) resp := listWorkflowV4Resp{ WorkflowList: workflowList, Total: 0, -- Gitee From 010634ba5fcbb1480cfa461f7f916ff6680df553 Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 18 Jul 2022 20:05:45 +0800 Subject: [PATCH 2580/2647] fix common workflow permission Signed-off-by: guoyu --- pkg/microservice/policy/core/yamlconfig/metas.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/microservice/policy/core/yamlconfig/metas.yaml b/pkg/microservice/policy/core/yamlconfig/metas.yaml index 0597a11d..d64ebea5 100644 --- a/pkg/microservice/policy/core/yamlconfig/metas.yaml +++ b/pkg/microservice/policy/core/yamlconfig/metas.yaml @@ -467,6 +467,8 @@ metas: - method: GET endpoint: api/aslan/workflow/v4 filter: true + - method: GET + endpoint: api/aslan/workflow/v4 - method: GET endpoint: /api/aslan/workflow/v4/name/?* - method: GET -- Gitee From 279ff3dc17b6ed3568b7e4a741d45f42b08f9724 Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 19 Jul 2022 10:58:45 +0800 Subject: [PATCH 2581/2647] fix common workflow failed to download tools when default s3 server was http Signed-off-by: guoyu --- .../workflowcontroller/stepcontroller/step_tool_install.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go index 305d925b..75ff98f7 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go @@ -78,6 +78,7 @@ func (s *toolInstallCtl) PreRun(ctx context.Context) error { spec.S3Storage.Ak = objectStorage.Ak spec.S3Storage.Subfolder = objectStorage.Subfolder spec.S3Storage.Bucket = objectStorage.Bucket + spec.S3Storage.Insecure = objectStorage.Insecure spec.S3Storage.Protocol = "https" if objectStorage.Insecure { spec.S3Storage.Protocol = "http" -- Gitee From 3454cf3772d9086a6bec194eb613f88f85a51c25 Mon Sep 17 00:00:00 2001 From: allenshen Date: Wed, 20 Jul 2022 11:48:08 +0800 Subject: [PATCH 2582/2647] remove remnant file before update chart templates Signed-off-by: allenshen --- .../aslan/core/templatestore/service/chart.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/microservice/aslan/core/templatestore/service/chart.go b/pkg/microservice/aslan/core/templatestore/service/chart.go index 03448762..2e78e399 100644 --- a/pkg/microservice/aslan/core/templatestore/service/chart.go +++ b/pkg/microservice/aslan/core/templatestore/service/chart.go @@ -402,6 +402,12 @@ func processChartFromSource(name string, args *fs.DownloadFromSourceArgs, logger localBase := configbase.LocalChartTemplatePath(name) s3Base := configbase.ObjectStorageChartTemplatePath(name) + err = os.RemoveAll(localBase) + if err != nil { + log.Errorf("failed to remove current template dir: %s, err: %s", localBase, err) + return + } + err1 := fs.SaveAndUploadFiles(tree, []string{name}, localBase, s3Base, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) @@ -465,6 +471,12 @@ func processChartFromGitRepo(name string, args *fs.DownloadFromSourceArgs, logge logger.Debug("Start to save and upload chart") localBase := configbase.LocalChartTemplatePath(name) + err = os.RemoveAll(path.Join(localBase, path.Base(args.Path))) + if err != nil { + log.Errorf("failed to remove current template dir: %s, err: %s", localBase, err) + return + } + err1 := fs.CopyAndUploadFiles([]string{}, path.Join(localBase, path.Base(args.Path)), "", currentChartPath, logger) if err1 != nil { logger.Errorf("Failed to save files to disk, err: %s", err1) -- Gitee From d73e4f9a0b91c479a29fcd61844b09d17575a631 Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:47:38 +0800 Subject: [PATCH 2583/2647] add ansible config for vm demo (#1817) * add ansible config for vm demo Signed-off-by: fansi * update ansible yaml config Signed-off-by: fansi Co-authored-by: fansi --- .../vm/ansible/ansible.Dockerfile | 22 +++++++++++++++++++ .../microservice-demo/vm/ansible/hosts-dev | 5 +++++ .../microservice-demo/vm/ansible/hosts-qa | 5 +++++ .../microservice-demo/vm/ansible/main.yaml | 14 ++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 examples/microservice-demo/vm/ansible/ansible.Dockerfile create mode 100644 examples/microservice-demo/vm/ansible/hosts-dev create mode 100644 examples/microservice-demo/vm/ansible/hosts-qa create mode 100644 examples/microservice-demo/vm/ansible/main.yaml diff --git a/examples/microservice-demo/vm/ansible/ansible.Dockerfile b/examples/microservice-demo/vm/ansible/ansible.Dockerfile new file mode 100644 index 00000000..b3cd73c6 --- /dev/null +++ b/examples/microservice-demo/vm/ansible/ansible.Dockerfile @@ -0,0 +1,22 @@ +FROM ubuntu:focal + +RUN apt update +RUN apt install -y software-properties-common +RUN add-apt-repository --yes --update ppa:ansible/ansible +RUN apt install -y ansible + +# 以下是 Zadig 所需要的依赖 + +# 安装工具 curl git tzdata +RUN sed -i -E "s/[a-zA-Z0-9]+.ubuntu.com/mirrors.aliyun.com/g" /etc/apt/sources.list +RUN apt-get clean && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y curl git tzdata + +# 修改时区 +RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime + +# 安装 docker client +RUN curl -fsSL "http://resources.koderover.com/docker-cli-v19.03.2.tar.gz" -o docker.tgz &&\ + tar -xvzf docker.tgz &&\ + mv docker/* /usr/local/bin + +RUN apt install -y build-essential net-tools diff --git a/examples/microservice-demo/vm/ansible/hosts-dev b/examples/microservice-demo/vm/ansible/hosts-dev new file mode 100644 index 00000000..e1595da8 --- /dev/null +++ b/examples/microservice-demo/vm/ansible/hosts-dev @@ -0,0 +1,5 @@ +[servers] +220.16.0.1 +220.16.0.2 +220.16.0.3 +220.16.0.4 diff --git a/examples/microservice-demo/vm/ansible/hosts-qa b/examples/microservice-demo/vm/ansible/hosts-qa new file mode 100644 index 00000000..703850ad --- /dev/null +++ b/examples/microservice-demo/vm/ansible/hosts-qa @@ -0,0 +1,5 @@ +[servers] +220.16.0.11 +220.16.0.12 +220.16.0.13 +220.16.0.14 diff --git a/examples/microservice-demo/vm/ansible/main.yaml b/examples/microservice-demo/vm/ansible/main.yaml new file mode 100644 index 00000000..af24bd40 --- /dev/null +++ b/examples/microservice-demo/vm/ansible/main.yaml @@ -0,0 +1,14 @@ +--- +- hosts: servers + remote_user: ubuntu + tasks: + - name: "copy file to remote servers" + copy: + src: "{{ PKG_FILE }}" + dest: "/home/ubuntu/microservice-demo/{{ PKG_FILE }}" + - name: "unzip file" + shell: + chdir: /home/ubuntu/microservice-demo + cmd: tar -xvf /home/ubuntu/microservice-demo/{{ PKG_FILE }} + - name: "deploy backend service" + command: /home/ubuntu/microservice-demo/restart.sh /home/ubuntu/microservice-demo/{{ PKG_FILE }} -- Gitee From 916e07f8b440e146535912fe8c57ec999c6935fa Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 21 Jul 2022 17:01:29 +0800 Subject: [PATCH 2584/2647] update operation log store logic Signed-off-by: allenshen --- .../aslan/core/build/handler/router.go | 10 ++-- .../aslan/core/code/handler/router.go | 4 +- .../core/collaboration/handler/router.go | 8 +-- .../aslan/core/delivery/handler/router.go | 5 +- .../aslan/core/delivery/handler/version.go | 7 ++- .../environment/handler/common_env_cfg.go | 6 +- .../aslan/core/environment/handler/debug.go | 2 + .../core/environment/handler/environment.go | 14 +++-- .../aslan/core/environment/handler/router.go | 47 +++++++-------- .../core/environment/handler/share_env.go | 4 ++ .../aslan/core/project/handler/router.go | 24 ++++---- .../aslan/core/service/handler/environment.go | 10 ++++ .../aslan/core/service/handler/helm.go | 11 ++++ .../aslan/core/service/handler/loader.go | 11 ++++ .../aslan/core/service/handler/router.go | 14 ++--- .../aslan/core/service/handler/service.go | 6 +- .../core/service/handler/yaml_template.go | 9 +++ .../aslan/core/system/handler/router.go | 58 +++++++++---------- .../aslan/core/templatestore/handler/build.go | 12 +++- .../aslan/core/templatestore/handler/chart.go | 12 ++++ .../core/templatestore/handler/dockerfile.go | 9 +++ .../aslan/core/templatestore/handler/yaml.go | 12 ++++ .../aslan/core/workflow/handler/router.go | 44 +++++++------- .../core/workflow/testing/handler/router.go | 24 ++++---- pkg/microservice/aslan/server/rest/server.go | 1 + pkg/middleware/gin/audit_log.go | 10 +++- .../gin/{reponse.go => response.go} | 0 27 files changed, 231 insertions(+), 143 deletions(-) rename pkg/middleware/gin/{reponse.go => response.go} (100%) diff --git a/pkg/microservice/aslan/core/build/handler/router.go b/pkg/microservice/aslan/core/build/handler/router.go index 837d084a..f52c2154 100644 --- a/pkg/microservice/aslan/core/build/handler/router.go +++ b/pkg/microservice/aslan/core/build/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -30,10 +28,10 @@ func (*Router) Inject(router *gin.RouterGroup) { build.GET("/:name", FindBuildModule) build.GET("", ListBuildModules) build.GET("/serviceModule", ListBuildModulesByServiceModule) - build.POST("", gin2.UpdateOperationLogStatus, CreateBuildModule) - build.PUT("", gin2.UpdateOperationLogStatus, UpdateBuildModule) - build.DELETE("", gin2.UpdateOperationLogStatus, DeleteBuildModule) - build.POST("/targets", gin2.UpdateOperationLogStatus, UpdateBuildTargets) + build.POST("", CreateBuildModule) + build.PUT("", UpdateBuildModule) + build.DELETE("", DeleteBuildModule) + build.POST("/targets", UpdateBuildTargets) } target := router.Group("targets") diff --git a/pkg/microservice/aslan/core/code/handler/router.go b/pkg/microservice/aslan/core/code/handler/router.go index 509c2be9..123a9108 100644 --- a/pkg/microservice/aslan/core/code/handler/router.go +++ b/pkg/microservice/aslan/core/code/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -42,7 +40,7 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- workspace := router.Group("workspace") { - workspace.DELETE("", GetProductNameByWorkspacePipeline, gin2.UpdateOperationLogStatus, CleanWorkspace) + workspace.DELETE("", GetProductNameByWorkspacePipeline, CleanWorkspace) workspace.GET("/file", GetWorkspaceFile) workspace.GET("/git/:codehostId", GetGitRepoInfo) diff --git a/pkg/microservice/aslan/core/collaboration/handler/router.go b/pkg/microservice/aslan/core/collaboration/handler/router.go index 1807156a..5092b894 100644 --- a/pkg/microservice/aslan/core/collaboration/handler/router.go +++ b/pkg/microservice/aslan/core/collaboration/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - middleware "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -29,9 +27,9 @@ func (*Router) Inject(router *gin.RouterGroup) { collaborations := router.Group("collaborations") { collaborations.GET("", GetCollaborationMode) - collaborations.PUT("/:name", middleware.UpdateOperationLogStatus, UpdateCollaborationMode) - collaborations.POST("", middleware.UpdateOperationLogStatus, CreateCollaborationMode) - collaborations.DELETE("/:name", middleware.UpdateOperationLogStatus, DeleteCollaborationMode) + collaborations.PUT("/:name", UpdateCollaborationMode) + collaborations.POST("", CreateCollaborationMode) + collaborations.DELETE("/:name", DeleteCollaborationMode) collaborations.GET("/cron/clean", CleanCIResources) collaborations.GET("/new", GetCollaborationNew) collaborations.POST("/sync", SyncCollaborationInstance) diff --git a/pkg/microservice/aslan/core/delivery/handler/router.go b/pkg/microservice/aslan/core/delivery/handler/router.go index 11141b24..4c29b15f 100644 --- a/pkg/microservice/aslan/core/delivery/handler/router.go +++ b/pkg/microservice/aslan/core/delivery/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -45,8 +43,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { deliveryRelease.GET("/:id", GetDeliveryVersion) deliveryRelease.GET("", ListDeliveryVersion) - deliveryRelease.DELETE("/:id", GetProductNameByDelivery, gin2.UpdateOperationLogStatus, DeleteDeliveryVersion) - + deliveryRelease.DELETE("/:id", GetProductNameByDelivery, DeleteDeliveryVersion) deliveryRelease.POST("/helm", CreateHelmDeliveryVersion) deliveryRelease.POST("/helm/global-variables", ApplyDeliveryGlobalVariables) deliveryRelease.GET("/helm/charts", DownloadDeliveryChart) diff --git a/pkg/microservice/aslan/core/delivery/handler/version.go b/pkg/microservice/aslan/core/delivery/handler/version.go index 22a0f959..0039a6f1 100644 --- a/pkg/microservice/aslan/core/delivery/handler/version.go +++ b/pkg/microservice/aslan/core/delivery/handler/version.go @@ -17,6 +17,7 @@ limitations under the License. package handler import ( + "encoding/json" "fmt" "net/http" "strings" @@ -175,13 +176,17 @@ func CreateHelmDeliveryVersion(c *gin.Context) { return } args.CreateBy = ctx.UserName + + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新建", "版本交付", fmt.Sprintf("%s-%s", args.EnvName, args.Version), string(bs), ctx.Logger) + ctx.Err = deliveryservice.CreateHelmDeliveryVersion(args, ctx.Logger) } func DeleteDeliveryVersion(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() - internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "删除", "版本交付", fmt.Sprintf("主键ID:%s", c.Param("id")), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.GetString("productName"), "删除", "版本交付", c.Param("id"), "", ctx.Logger) //params validate ID := c.Param("id") diff --git a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go index 34b4ae29..d178838e 100644 --- a/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go +++ b/pkg/microservice/aslan/core/environment/handler/common_env_cfg.go @@ -45,7 +45,7 @@ func DeleteCommonEnvCfg(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("param envName or projectName or objectName is invalid") return } - internalhandler.InsertOperationLog(c, ctx.UserName, productName, "删除", "环境-对象", fmt.Sprintf("环境名称:%s,%s名称:%s", envName, commonEnvCfgType, objectName), "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, productName, "删除", "环境配置", fmt.Sprintf("%s:%s:%s", envName, commonEnvCfgType, objectName), "", ctx.Logger) ctx.Err = service.DeleteCommonEnvCfg(envName, productName, objectName, config.CommonEnvCfgType(commonEnvCfgType), ctx.Logger) } @@ -66,7 +66,7 @@ func CreateCommonEnvCfg(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新建", "环境-对象", fmt.Sprintf("环境名称:%s,%s:", args.EnvName, args.CommonEnvCfgType), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "新建", "环境配置", fmt.Sprintf("%s:%s", args.EnvName, args.CommonEnvCfgType), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -94,7 +94,7 @@ func UpdateCommonEnvCfg(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("UpdateCommonEnvCfg json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", fmt.Sprintf("环境-%s", args.CommonEnvCfgType), fmt.Sprintf("环境名称:%s,%s名称:%s", args.EnvName, args.CommonEnvCfgType, args.Name), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "更新", "环境配置", fmt.Sprintf("%s:%s:%s", args.EnvName, args.CommonEnvCfgType, args.Name), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { diff --git a/pkg/microservice/aslan/core/environment/handler/debug.go b/pkg/microservice/aslan/core/environment/handler/debug.go index 7bc446e1..72e53caa 100644 --- a/pkg/microservice/aslan/core/environment/handler/debug.go +++ b/pkg/microservice/aslan/core/environment/handler/debug.go @@ -36,5 +36,7 @@ func PatchDebugContainer(c *gin.Context) { debugImage = types.DebugImage } + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "启动调试容器", "环境", envName, "", ctx.Logger) + ctx.Err = service.PatchDebugContainer(c, projectName, envName, podName, debugImage) } diff --git a/pkg/microservice/aslan/core/environment/handler/environment.go b/pkg/microservice/aslan/core/environment/handler/environment.go index 98da11c0..9380c3fa 100644 --- a/pkg/microservice/aslan/core/environment/handler/environment.go +++ b/pkg/microservice/aslan/core/environment/handler/environment.go @@ -110,7 +110,7 @@ func UpdateMultiProducts(c *gin.Context) { } } - internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "自动更新", "环境", strings.Join(envNames, ","), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "更新", "环境", strings.Join(envNames, ","), string(data), ctx.Logger) force, _ := strconv.ParseBool(c.Query("force")) ctx.Resp, ctx.Err = service.AutoUpdateProduct(args, envNames, c.Query("projectName"), ctx.RequestID, force, ctx.Logger) @@ -262,7 +262,7 @@ func UpdateProduct(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc(err.Error()) return } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境", envName, string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "环境变量", envName, string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -480,7 +480,13 @@ func UpdateHelmProductCharts(c *gin.Context) { if err = json.Unmarshal(data, arg); err != nil { log.Errorf("UpdateHelmProductCharts json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "更新服务", "", string(data), ctx.Logger) + + serviceName := make([]string, 0) + for _, cd := range arg.ChartValues { + serviceName = append(serviceName, cd.ServiceName) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "更新服务", fmt.Sprintf("%s:[%s]", envName, strings.Join(serviceName, ",")), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) err = c.BindJSON(arg) @@ -632,7 +638,7 @@ func DeleteProductServices(c *gin.Context) { return } - internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境的服务", envName, "", ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "删除", "环境的服务", fmt.Sprintf("%s:[%s]", envName, strings.Join(args.ServiceNames, ",")), "", ctx.Logger) ctx.Err = service.DeleteProductServices(ctx.UserName, ctx.RequestID, envName, projectName, args.ServiceNames, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index 58d56969..ee8ef786 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -31,7 +29,7 @@ func (*Router) Inject(router *gin.RouterGroup) { configmaps := router.Group("configmaps") { configmaps.GET("/:envName", ListConfigMaps) - configmaps.POST("", gin2.UpdateOperationLogStatus, RollBackConfigMap) + configmaps.POST("", RollBackConfigMap) configmaps.GET("/migrate", MigrateHistoryConfigMaps) } @@ -52,9 +50,9 @@ func (*Router) Inject(router *gin.RouterGroup) { commonEnvCfgs.GET("/:envName/cfg/:objectName", ListCommonEnvCfgHistory) commonEnvCfgs.GET("", ListLatestEnvCfg) commonEnvCfgs.PUT("/:envName/:type/:objectName/sync", SyncEnvResource) - commonEnvCfgs.PUT("/:envName", gin2.UpdateOperationLogStatus, UpdateCommonEnvCfg) - commonEnvCfgs.POST("/:envName", gin2.UpdateOperationLogStatus, CreateCommonEnvCfg) - commonEnvCfgs.DELETE("/:envName/cfg/:objectName", gin2.UpdateOperationLogStatus, DeleteCommonEnvCfg) + commonEnvCfgs.PUT("/:envName", UpdateCommonEnvCfg) + commonEnvCfgs.POST("/:envName", CreateCommonEnvCfg) + commonEnvCfgs.DELETE("/:envName/cfg/:objectName", DeleteCommonEnvCfg) } // --------------------------------------------------------------------------------------- @@ -88,8 +86,8 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- image := router.Group("image") { - image.POST("/deployment/:envName", gin2.UpdateOperationLogStatus, UpdateDeploymentContainerImage) - image.POST("/statefulset/:envName", gin2.UpdateOperationLogStatus, UpdateStatefulSetContainerImage) + image.POST("/deployment/:envName", UpdateDeploymentContainerImage) + image.POST("/statefulset/:envName", UpdateStatefulSetContainerImage) } // 查询环境创建时的服务和变量信息 @@ -105,7 +103,7 @@ func (*Router) Inject(router *gin.RouterGroup) { kube.GET("/events", ListKubeEvents) kube.POST("/pods", ListServicePods) - kube.DELETE("/:env/pods/:podName", gin2.UpdateOperationLogStatus, DeletePod) + kube.DELETE("/:env/pods/:podName", DeletePod) kube.GET("/pods/:podName/events", ListPodEvents) kube.GET("/workloads", ListWorkloads) kube.GET("/nodes", ListNodes) @@ -119,21 +117,20 @@ func (*Router) Inject(router *gin.RouterGroup) { environments := router.Group("environments") { environments.GET("", ListProducts) - environments.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProduct) - environments.PUT("/:name/registry", gin2.UpdateOperationLogStatus, UpdateProductRegistry) - environments.PUT("", gin2.UpdateOperationLogStatus, UpdateMultiProducts) - environments.POST("", gin2.UpdateOperationLogStatus, CreateProduct) + environments.PUT("/:name", UpdateProduct) + environments.PUT("", UpdateMultiProducts) + environments.POST("", CreateProduct) environments.GET("/:name", GetProduct) - environments.PUT("/:name/envRecycle", gin2.UpdateOperationLogStatus, UpdateProductRecycleDay) + environments.PUT("/:name/envRecycle", UpdateProductRecycleDay) environments.POST("/:name/estimated-values", EstimatedValues) - environments.PUT("/:name/renderset", gin2.UpdateOperationLogStatus, UpdateHelmProductRenderset) - environments.PUT("/:name/helm/default-values", gin2.UpdateOperationLogStatus, UpdateHelmProductDefaultValues) - environments.PUT("/:name/helm/charts", gin2.UpdateOperationLogStatus, UpdateHelmProductCharts) - environments.PUT("/:name/syncVariables", gin2.UpdateOperationLogStatus, SyncHelmProductRenderset) + environments.PUT("/:name/renderset", UpdateHelmProductRenderset) + environments.PUT("/:name/helm/default-values", UpdateHelmProductDefaultValues) + environments.PUT("/:name/helm/charts", UpdateHelmProductCharts) + environments.PUT("/:name/syncVariables", SyncHelmProductRenderset) environments.GET("/:name/helmChartVersions", GetHelmChartVersions) environments.GET("/:name/productInfo", GetProductInfo) - environments.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProduct) - environments.PUT("/:name/services", gin2.UpdateOperationLogStatus, DeleteProductServices) + environments.DELETE("/:name", DeleteProduct) + environments.PUT("/:name/services", DeleteProductServices) environments.GET("/:name/groups", ListGroups) environments.GET("/:name/workloads", ListWorkloadsInEnv) @@ -143,11 +140,11 @@ func (*Router) Inject(router *gin.RouterGroup) { environments.GET("/:name/helm/images", GetImageInfos) environments.GET("/:name/services/:serviceName", GetService) - environments.PUT("/:name/services/:serviceName", gin2.UpdateOperationLogStatus, UpdateService) - environments.POST("/:name/services/:serviceName/restart", gin2.UpdateOperationLogStatus, RestartService) - environments.POST("/:name/services/:serviceName/restartNew", gin2.UpdateOperationLogStatus, RestartNewService) - environments.POST("/:name/services/:serviceName/scale", gin2.UpdateOperationLogStatus, ScaleService) - environments.POST("/:name/services/:serviceName/scaleNew", gin2.UpdateOperationLogStatus, ScaleNewService) + environments.PUT("/:name/services/:serviceName", UpdateService) + environments.POST("/:name/services/:serviceName/restart", RestartService) + environments.POST("/:name/services/:serviceName/restartNew", RestartNewService) + environments.POST("/:name/services/:serviceName/scale", ScaleService) + environments.POST("/:name/services/:serviceName/scaleNew", ScaleNewService) environments.GET("/:name/services/:serviceName/containers/:container", GetServiceContainer) environments.GET("/:name/estimated-renderchart", GetEstimatedRenderCharts) diff --git a/pkg/microservice/aslan/core/environment/handler/share_env.go b/pkg/microservice/aslan/core/environment/handler/share_env.go index be12f755..a9812213 100644 --- a/pkg/microservice/aslan/core/environment/handler/share_env.go +++ b/pkg/microservice/aslan/core/environment/handler/share_env.go @@ -34,6 +34,8 @@ func EnableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "开启自测模式", "环境", c.Param("name"), "", ctx.Logger) + ctx.Err = service.EnableBaseEnv(c, c.Param("name"), c.Query("projectName")) } @@ -41,6 +43,8 @@ func DisableBaseEnv(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "关闭自测模式", "环境", c.Param("name"), "", ctx.Logger) + ctx.Err = service.DisableBaseEnv(c, c.Param("name"), c.Query("projectName")) } diff --git a/pkg/microservice/aslan/core/project/handler/router.go b/pkg/microservice/aslan/core/project/handler/router.go index 5eeef72a..793a02c3 100644 --- a/pkg/microservice/aslan/core/project/handler/router.go +++ b/pkg/microservice/aslan/core/project/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -40,13 +38,13 @@ func (*Router) Inject(router *gin.RouterGroup) { product.GET("/:name", GetProductTemplate) product.GET("/:name/services", GetProductTemplateServices) product.GET("/:name/searching-rules", GetCustomMatchRules) - product.PUT("/:name/searching-rules", gin2.UpdateOperationLogStatus, CreateOrUpdateMatchRules) - product.POST("", gin2.UpdateOperationLogStatus, CreateProductTemplate) - product.PUT("/:name", gin2.UpdateOperationLogStatus, UpdateProductTemplate) - product.PUT("/:name/:status", gin2.UpdateOperationLogStatus, UpdateProductTmplStatus) - product.PATCH("/:name", gin2.UpdateOperationLogStatus, UpdateServiceOrchestration) - product.PUT("", gin2.UpdateOperationLogStatus, UpdateProject) - product.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteProductTemplate) + product.PUT("/:name/searching-rules", CreateOrUpdateMatchRules) + product.POST("", CreateProductTemplate) + product.PUT("/:name", UpdateProductTemplate) + product.PUT("/:name/:status", UpdateProductTmplStatus) + product.PATCH("/:name", UpdateServiceOrchestration) + product.PUT("", UpdateProject) + product.DELETE("/:name", DeleteProductTemplate) } openSource := router.Group("opensource") @@ -70,9 +68,9 @@ func (*Router) Inject(router *gin.RouterGroup) { pms.GET("", ListPMHosts) pms.GET("/:id", GetPMHost) pms.GET("/labels", ListLabels) - pms.POST("", gin2.UpdateOperationLogStatus, CreatePMHost) - pms.POST("/batch", gin2.UpdateOperationLogStatus, BatchCreatePMHost) - pms.PUT("/:id", gin2.UpdateOperationLogStatus, UpdatePMHost) - pms.DELETE("/:id", gin2.UpdateOperationLogStatus, DeletePMHost) + pms.POST("", CreatePMHost) + pms.POST("/batch", BatchCreatePMHost) + pms.PUT("/:id", UpdatePMHost) + pms.DELETE("/:id", DeletePMHost) } } diff --git a/pkg/microservice/aslan/core/service/handler/environment.go b/pkg/microservice/aslan/core/service/handler/environment.go index b4af13f4..e76430f1 100644 --- a/pkg/microservice/aslan/core/service/handler/environment.go +++ b/pkg/microservice/aslan/core/service/handler/environment.go @@ -18,6 +18,8 @@ package handler import ( "encoding/json" + "fmt" + "strings" "github.com/gin-gonic/gin" @@ -55,5 +57,13 @@ func LoadKubeWorkloadsYaml(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddErr(err) return } + + serviceNames := make([]string, 0) + for _, svc := range args.Services { + serviceNames = append(serviceNames, svc.Name) + } + + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s", strings.Join(serviceNames, ",")), string(data), ctx.Logger) + ctx.Err = service.LoadKubeWorkloadsYaml(ctx.UserName, args, false, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/handler/helm.go b/pkg/microservice/aslan/core/service/handler/helm.go index b8601044..61cac46a 100644 --- a/pkg/microservice/aslan/core/service/handler/helm.go +++ b/pkg/microservice/aslan/core/service/handler/helm.go @@ -17,6 +17,8 @@ limitations under the License. package handler import ( + "encoding/json" + "fmt" "strconv" "github.com/gin-gonic/gin" @@ -89,6 +91,9 @@ func CreateOrUpdateHelmService(c *gin.Context) { } args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s", args.Name), string(bs), ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, false, ctx.Logger) } @@ -109,6 +114,9 @@ func UpdateHelmService(c *gin.Context) { } args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s", args.Name), string(bs), ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateHelmService(projectName, args, true, ctx.Logger) } @@ -129,6 +137,9 @@ func CreateOrUpdateBulkHelmServices(c *gin.Context) { } args.CreatedBy, args.RequestID = ctx.UserName, ctx.RequestID + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, c.Query("projectName"), "新增", "项目管理-服务", "", string(bs), ctx.Logger) + ctx.Resp, ctx.Err = svcservice.CreateOrUpdateBulkHelmService(projectName, args, false, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/handler/loader.go b/pkg/microservice/aslan/core/service/handler/loader.go index 94b2d01b..52dc80a9 100644 --- a/pkg/microservice/aslan/core/service/handler/loader.go +++ b/pkg/microservice/aslan/core/service/handler/loader.go @@ -17,6 +17,7 @@ limitations under the License. package handler import ( + "encoding/json" "strconv" "github.com/gin-gonic/gin" @@ -88,6 +89,11 @@ func LoadServiceTemplate(c *gin.Context) { if namespace == "" { namespace = repoOwner } + + // Note we can't get the service name from handler layer since it parsed from files on git repo + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-服务", "", string(bs), ctx.Logger) + ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, namespace, repoName, repoUUID, branchName, remoteName, args, false, ctx.Logger) } @@ -124,6 +130,11 @@ func SyncServiceTemplate(c *gin.Context) { if namespace == "" { namespace = repoOwner } + + // Note we can't get the service name from handler layer since it parsed from files on git repo + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "更新", "项目管理-服务", "", string(bs), ctx.Logger) + ctx.Err = svcservice.LoadServiceFromCodeHost(ctx.UserName, codehostID, repoOwner, namespace, repoName, repoUUID, branchName, remoteName, args, true, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/handler/router.go b/pkg/microservice/aslan/core/service/handler/router.go index 7f7ffaf5..60350bad 100644 --- a/pkg/microservice/aslan/core/service/handler/router.go +++ b/pkg/microservice/aslan/core/service/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -51,11 +49,11 @@ func (*Router) Inject(router *gin.RouterGroup) { k8s.GET("", ListServiceTemplate) k8s.GET("/:name/:type", GetServiceTemplate) k8s.GET("/:name", GetServiceTemplateOption) - k8s.POST("", GetServiceTemplateProductName, gin2.UpdateOperationLogStatus, CreateServiceTemplate) - k8s.PUT("", gin2.UpdateOperationLogStatus, UpdateServiceTemplate) + k8s.POST("", GetServiceTemplateProductName, CreateServiceTemplate) + k8s.PUT("", UpdateServiceTemplate) k8s.PUT("/yaml/validator", YamlValidator) k8s.PUT("/:name/yaml/view", YamlViewServiceTemplate) - k8s.DELETE("/:name/:type", gin2.UpdateOperationLogStatus, DeleteServiceTemplate) + k8s.DELETE("/:name/:type", DeleteServiceTemplate) k8s.GET("/:name/:type/ports", ListServicePort) k8s.GET("/:name/environments/deployable", GetDeployableEnvs) k8s.GET("/kube/workloads", GetKubeWorkloads) @@ -84,9 +82,9 @@ func (*Router) Inject(router *gin.RouterGroup) { pm := router.Group("pm") { - pm.PUT("/healthCheckUpdate", gin2.UpdateOperationLogStatus, UpdateServiceHealthCheckStatus) - pm.POST("/:productName", gin2.UpdateOperationLogStatus, CreatePMService) - pm.PUT("/:productName", gin2.UpdateOperationLogStatus, UpdatePmServiceTemplate) + pm.PUT("/healthCheckUpdate", UpdateServiceHealthCheckStatus) + pm.POST("/:productName", CreatePMService) + pm.PUT("/:productName", UpdatePmServiceTemplate) } template := router.Group("template") diff --git a/pkg/microservice/aslan/core/service/handler/service.go b/pkg/microservice/aslan/core/service/handler/service.go index 8bfc29dd..8e10426e 100644 --- a/pkg/microservice/aslan/core/service/handler/service.go +++ b/pkg/microservice/aslan/core/service/handler/service.go @@ -83,7 +83,7 @@ func CreateServiceTemplate(c *gin.Context) { if err = json.Unmarshal(data, args); err != nil { log.Errorf("CreateServiceTemplate json.Unmarshal err : %v", err) } - internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s,版本号:%d", args.ServiceName, args.Revision), string(data), ctx.Logger) + internalhandler.InsertOperationLog(c, ctx.UserName, args.ProductName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s", args.ServiceName), string(data), ctx.Logger) c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data)) if err := c.BindJSON(args); err != nil { @@ -188,6 +188,10 @@ func HelmReleaseNaming(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid yaml args") return } + + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, projectName, "修改", "项目管理-服务", args.ServiceName, string(bs), ctx.Logger) + ctx.Err = svcservice.UpdateReleaseNamingRule(ctx.UserName, ctx.RequestID, projectName, args, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/service/handler/yaml_template.go b/pkg/microservice/aslan/core/service/handler/yaml_template.go index f96b9d81..33267b40 100644 --- a/pkg/microservice/aslan/core/service/handler/yaml_template.go +++ b/pkg/microservice/aslan/core/service/handler/yaml_template.go @@ -17,6 +17,9 @@ limitations under the License. package handler import ( + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" svcservice "github.com/koderover/zadig/pkg/microservice/aslan/core/service/service" @@ -34,6 +37,9 @@ func LoadServiceFromYamlTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(req) + internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "新增", "项目管理-服务", fmt.Sprintf("服务名称:%s", req.ServiceName), string(bs), ctx.Logger) + ctx.Err = svcservice.LoadServiceFromYamlTemplate(ctx.UserName, req, false, ctx.Logger) } @@ -47,5 +53,8 @@ func ReloadServiceFromYamlTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(req) + internalhandler.InsertOperationLog(c, ctx.UserName, req.ProjectName, "更新", "项目管理-服务", fmt.Sprintf("服务名称:%s", req.ServiceName), string(bs), ctx.Logger) + ctx.Err = svcservice.ReloadServiceFromYamlTemplate(ctx.UserName, req, ctx.Logger) } diff --git a/pkg/microservice/aslan/core/system/handler/router.go b/pkg/microservice/aslan/core/system/handler/router.go index eecc30f4..e580c161 100644 --- a/pkg/microservice/aslan/core/system/handler/router.go +++ b/pkg/microservice/aslan/core/system/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -35,11 +33,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- install := router.Group("install") { - install.POST("", gin2.UpdateOperationLogStatus, CreateInstall) - install.PUT("", gin2.UpdateOperationLogStatus, UpdateInstall) + install.POST("", CreateInstall) + install.PUT("", UpdateInstall) install.GET("/:name/:version", GetInstall) install.GET("", ListInstalls) - install.PUT("/delete", gin2.UpdateOperationLogStatus, DeleteInstall) + install.PUT("/delete", DeleteInstall) } // --------------------------------------------------------------------------------------- @@ -49,9 +47,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { proxyManage.GET("", ListProxies) proxyManage.GET("/:id", GetProxy) - proxyManage.POST("", gin2.UpdateOperationLogStatus, CreateProxy) - proxyManage.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateProxy) - proxyManage.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteProxy) + proxyManage.POST("", CreateProxy) + proxyManage.PUT("/:id", UpdateProxy) + proxyManage.DELETE("/:id", DeleteProxy) proxyManage.POST("/connectionTest", TestConnection) } @@ -63,10 +61,10 @@ func (*Router) Inject(router *gin.RouterGroup) { registry.GET("/namespaces/default", GetDefaultRegistryNamespace) registry.GET("/namespaces/specific/:id", GetRegistryNamespace) registry.GET("/namespaces", ListRegistryNamespaces) - registry.POST("/namespaces", gin2.UpdateOperationLogStatus, CreateRegistryNamespace) - registry.PUT("/namespaces/:id", gin2.UpdateOperationLogStatus, UpdateRegistryNamespace) + registry.POST("/namespaces", CreateRegistryNamespace) + registry.PUT("/namespaces/:id", UpdateRegistryNamespace) - registry.DELETE("/namespaces/:id", gin2.UpdateOperationLogStatus, DeleteRegistryNamespace) + registry.DELETE("/namespaces/:id", DeleteRegistryNamespace) registry.GET("/release/repos", ListAllRepos) registry.POST("/images", ListImages) registry.GET("/images/repos/:name", ListRepoImages) @@ -75,10 +73,10 @@ func (*Router) Inject(router *gin.RouterGroup) { s3storage := router.Group("s3storage") { s3storage.GET("", ListS3Storage) - s3storage.POST("", gin2.UpdateOperationLogStatus, CreateS3Storage) + s3storage.POST("", CreateS3Storage) s3storage.GET("/:id", GetS3Storage) - s3storage.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateS3Storage) - s3storage.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteS3Storage) + s3storage.PUT("/:id", UpdateS3Storage) + s3storage.DELETE("/:id", DeleteS3Storage) s3storage.POST("/:id/releases/search", ListTars) } @@ -146,9 +144,9 @@ func (*Router) Inject(router *gin.RouterGroup) { { basicImages.GET("", ListBasicImages) basicImages.GET("/:id", GetBasicImage) - basicImages.POST("", gin2.UpdateOperationLogStatus, CreateBasicImage) - basicImages.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateBasicImage) - basicImages.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteBasicImage) + basicImages.POST("", CreateBasicImage) + basicImages.PUT("/:id", UpdateBasicImage) + basicImages.DELETE("/:id", DeleteBasicImage) } // --------------------------------------------------------------------------------------- @@ -172,10 +170,10 @@ func (*Router) Inject(router *gin.RouterGroup) { privateKey.GET("/internal", ListPrivateKeysInternal) privateKey.GET("/:id", GetPrivateKey) privateKey.GET("/labels", ListLabels) - privateKey.POST("", gin2.UpdateOperationLogStatus, CreatePrivateKey) - privateKey.POST("/batch", gin2.UpdateOperationLogStatus, BatchCreatePrivateKey) - privateKey.PUT("/:id", gin2.UpdateOperationLogStatus, UpdatePrivateKey) - privateKey.DELETE("/:id", gin2.UpdateOperationLogStatus, DeletePrivateKey) + privateKey.POST("", CreatePrivateKey) + privateKey.POST("/batch", BatchCreatePrivateKey) + privateKey.PUT("/:id", UpdatePrivateKey) + privateKey.DELETE("/:id", DeletePrivateKey) } rsaKey := router.Group("rsaKey") @@ -217,9 +215,9 @@ func (*Router) Inject(router *gin.RouterGroup) { externalLink := router.Group("externalLink") { externalLink.GET("", ListExternalLinks) - externalLink.POST("", gin2.UpdateOperationLogStatus, CreateExternalLink) - externalLink.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateExternalLink) - externalLink.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteExternalLink) + externalLink.POST("", CreateExternalLink) + externalLink.PUT("/:id", UpdateExternalLink) + externalLink.DELETE("/:id", DeleteExternalLink) } // --------------------------------------------------------------------------------------- @@ -227,11 +225,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- externalSystem := router.Group("external") { - externalSystem.POST("", gin2.UpdateOperationLogStatus, CreateExternalSystem) + externalSystem.POST("", CreateExternalSystem) externalSystem.GET("", ListExternalSystem) externalSystem.GET("/:id", GetExternalSystemDetail) - externalSystem.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateExternalSystem) - externalSystem.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteExternalSystem) + externalSystem.PUT("/:id", UpdateExternalSystem) + externalSystem.DELETE("/:id", DeleteExternalSystem) } // --------------------------------------------------------------------------------------- @@ -239,11 +237,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- sonar := router.Group("sonar") { - sonar.POST("/integration", gin2.UpdateOperationLogStatus, CreateSonarIntegration) - sonar.PUT("/integration/:id", gin2.UpdateOperationLogStatus, UpdateSonarIntegration) + sonar.POST("/integration", CreateSonarIntegration) + sonar.PUT("/integration/:id", UpdateSonarIntegration) sonar.GET("/integration", ListSonarIntegration) sonar.GET("/integration/:id", GetSonarIntegration) - sonar.DELETE("/integration/:id", gin2.UpdateOperationLogStatus, DeleteSonarIntegration) + sonar.DELETE("/integration/:id", DeleteSonarIntegration) sonar.POST("/validate", ValidateSonarInformation) } } diff --git a/pkg/microservice/aslan/core/templatestore/handler/build.go b/pkg/microservice/aslan/core/templatestore/handler/build.go index 03c26228..604663e1 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/build.go +++ b/pkg/microservice/aslan/core/templatestore/handler/build.go @@ -17,7 +17,7 @@ limitations under the License. package handler import ( - "fmt" + "encoding/json" "github.com/gin-gonic/gin" @@ -57,7 +57,10 @@ func AddBuildTemplate(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid Build args") return } - internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "模板-构建", args.Name, fmt.Sprintf("%+v", args), ctx.Logger) + + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新增", "模板-构建", args.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.AddBuildTemplate(ctx.UserName, args, ctx.Logger) } @@ -71,7 +74,9 @@ func UpdateBuildTemplate(c *gin.Context) { ctx.Err = e.ErrInvalidParam.AddDesc("invalid Build args") return } - internalhandler.InsertOperationLog(c, ctx.UserName, "", "编辑", "模板-构建", args.Name, fmt.Sprintf("%+v", args), ctx.Logger) + + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "模板-构建", args.Name, string(bs), ctx.Logger) ctx.Err = templateservice.UpdateBuildTemplate(c.Param("id"), args, ctx.Logger) } @@ -81,5 +86,6 @@ func RemoveBuildTemplate(c *gin.Context) { defer func() { internalhandler.JSONResponse(c, ctx) }() internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "模板-构建", c.Param("id"), "", ctx.Logger) + ctx.Err = templateservice.RemoveBuildTemplate(c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/templatestore/handler/chart.go b/pkg/microservice/aslan/core/templatestore/handler/chart.go index 9bf3da25..4e77f195 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/chart.go +++ b/pkg/microservice/aslan/core/templatestore/handler/chart.go @@ -17,6 +17,8 @@ limitations under the License. package handler import ( + "encoding/json" + "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -71,6 +73,9 @@ func AddChartTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新建", "模板库-Chart", args.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.AddChartTemplate(args.Name, args.DownloadFromSourceArgs, ctx.Logger) } @@ -84,6 +89,9 @@ func UpdateChartTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(args) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "模板库-Chart", args.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.UpdateChartTemplate(c.Param("name"), args.DownloadFromSourceArgs, ctx.Logger) } @@ -104,6 +112,8 @@ func SyncChartTemplateReference(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "同步", "模板库-Chart", c.Param("name"), "", ctx.Logger) + ctx.Err = templateservice.SyncHelmTemplateReference(ctx.UserName, c.Param("name"), ctx.Logger) } @@ -111,5 +121,7 @@ func RemoveChartTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "模板库-Chart", c.Param("name"), "", ctx.Logger) + ctx.Err = templateservice.RemoveChartTemplate(c.Param("name"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go index 455ee803..4d1cd3c1 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go +++ b/pkg/microservice/aslan/core/templatestore/handler/dockerfile.go @@ -17,6 +17,7 @@ limitations under the License. package handler import ( + "encoding/json" "errors" "github.com/gin-gonic/gin" @@ -44,6 +45,9 @@ func CreateDockerfileTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(req) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "新建", "模板库-Dockerfile", req.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.CreateDockerfileTemplate(req, ctx.Logger) } @@ -65,6 +69,9 @@ func UpdateDockerfileTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(req) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "模板库-Dockerfile", req.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.UpdateDockerfileTemplate(c.Param("id"), req, ctx.Logger) } @@ -109,6 +116,8 @@ func DeleteDockerfileTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "模板库-Dockerfile", c.Param("id"), "", ctx.Logger) + ctx.Err = templateservice.DeleteDockerfileTemplate(c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/templatestore/handler/yaml.go b/pkg/microservice/aslan/core/templatestore/handler/yaml.go index 600936eb..673a9a49 100644 --- a/pkg/microservice/aslan/core/templatestore/handler/yaml.go +++ b/pkg/microservice/aslan/core/templatestore/handler/yaml.go @@ -17,6 +17,8 @@ limitations under the License. package handler import ( + "encoding/json" + "github.com/gin-gonic/gin" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" @@ -36,6 +38,9 @@ func CreateYamlTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(req) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "创建", "模板-YAML", req.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.CreateYamlTemplate(req, ctx.Logger) } @@ -50,6 +55,9 @@ func UpdateYamlTemplate(c *gin.Context) { return } + bs, _ := json.Marshal(req) + internalhandler.InsertOperationLog(c, ctx.UserName, "", "更新", "模板-YAML", req.Name, string(bs), ctx.Logger) + ctx.Err = templateservice.UpdateYamlTemplate(c.Param("id"), req, ctx.Logger) } @@ -97,6 +105,8 @@ func DeleteYamlTemplate(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "删除", "模板-YAML", c.Param("id"), "", ctx.Logger) + ctx.Err = templateservice.DeleteYamlTemplate(c.Param("id"), ctx.Logger) } @@ -111,6 +121,8 @@ func SyncYamlTemplateReference(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() + internalhandler.InsertOperationLog(c, ctx.UserName, "", "同步", "模板-YAML", c.Param("id"), "", ctx.Logger) + ctx.Err = templateservice.SyncYamlTemplateReference(ctx.UserName, c.Param("id"), ctx.Logger) } diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 9b6b80e1..14fdceb6 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -59,10 +57,10 @@ func (*Router) Inject(router *gin.RouterGroup) { { pipeline.GET("", ListPipelines) pipeline.GET("/:name", GetPipeline) - pipeline.POST("", GetPipelineProductName, gin2.UpdateOperationLogStatus, UpsertPipeline) - pipeline.POST("/old/:old/new/:new", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, CopyPipeline) - pipeline.PUT("/rename/:old/:new", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, RenamePipeline) - pipeline.DELETE("/:name", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, DeletePipeline) + pipeline.POST("", GetPipelineProductName, UpsertPipeline) + pipeline.POST("/old/:old/new/:new", GetProductNameByPipeline, CopyPipeline) + pipeline.PUT("/rename/:old/:new", GetProductNameByPipeline, RenamePipeline) + pipeline.DELETE("/:name", GetProductNameByPipeline, DeletePipeline) } // --------------------------------------------------------------------------------------- @@ -79,11 +77,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- taskV2 := router.Group("v2/tasks") { - taskV2.POST("", GetProductNameByPipelineTask, gin2.UpdateOperationLogStatus, CreatePipelineTask) + taskV2.POST("", GetProductNameByPipelineTask, CreatePipelineTask) taskV2.GET("/max/:max/start/:start/pipelines/:name", ListPipelineTasksResult) taskV2.GET("/id/:id/pipelines/:name", GetPipelineTask) - taskV2.POST("/id/:id/pipelines/:name/restart", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, RestartPipelineTask) - taskV2.DELETE("/id/:id/pipelines/:name", GetProductNameByPipeline, gin2.UpdateOperationLogStatus, CancelTaskV2) + taskV2.POST("/id/:id/pipelines/:name/restart", GetProductNameByPipeline, RestartPipelineTask) + taskV2.DELETE("/id/:id/pipelines/:name", GetProductNameByPipeline, CancelTaskV2) taskV2.GET("/pipelines/:name/products", ListPipelineUpdatableProductNames) taskV2.GET("/file", GetPackageFile) taskV2.GET("/workflow/:pipelineName/taskId/:taskId", GetArtifactFile) @@ -94,8 +92,8 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- favorite := router.Group("favorite") { - favorite.POST("", gin2.UpdateOperationLogStatus, CreateFavoritePipeline) - favorite.DELETE("/:productName/:name/:type", gin2.UpdateOperationLogStatus, DeleteFavoritePipeline) + favorite.POST("", CreateFavoritePipeline) + favorite.DELETE("/:productName/:name/:type", DeleteFavoritePipeline) favorite.GET("", ListFavoritePipelines) } @@ -105,12 +103,12 @@ func (*Router) Inject(router *gin.RouterGroup) { workflow := router.Group("workflow") { workflow.POST("/:productName/auto", AutoCreateWorkflow) - workflow.POST("", GetWorkflowProductName, gin2.UpdateOperationLogStatus, CreateWorkflow) - workflow.PUT("/:workflowName", GetWorkflowProductName, gin2.UpdateOperationLogStatus, UpdateWorkflow) + workflow.POST("", GetWorkflowProductName, CreateWorkflow) + workflow.PUT("/:workflowName", GetWorkflowProductName, UpdateWorkflow) workflow.GET("", ListWorkflows) workflow.GET("/testName/:testName", ListTestWorkflows) workflow.GET("/find/:name", FindWorkflow) - workflow.DELETE("/:name", GetProductNameByWorkflow, gin2.UpdateOperationLogStatus, DeleteWorkflow) + workflow.DELETE("/:name", GetProductNameByWorkflow, DeleteWorkflow) workflow.GET("/preset/:productName", PreSetWorkflow) workflow.PUT("/old/:old/new/:new", CopyWorkflow) @@ -124,13 +122,13 @@ func (*Router) Inject(router *gin.RouterGroup) { //todo 修改权限的uuid workflowtask.GET("/targets/:productName/:namespace", GetWorkflowArgs) workflowtask.GET("/preset/:namespace/:workflowName", PresetWorkflowArgs) - workflowtask.POST("/:id", gin2.UpdateOperationLogStatus, CreateWorkflowTask) - workflowtask.PUT("/:id", gin2.UpdateOperationLogStatus, CreateArtifactWorkflowTask) + workflowtask.POST("/:id", CreateWorkflowTask) + workflowtask.PUT("/:id", CreateArtifactWorkflowTask) workflowtask.GET("/max/:max/start/:start/pipelines/:name", ListWorkflowTasksResult) workflowtask.GET("/filters/pipelines/:name", GetFiltersPipeline) workflowtask.GET("/id/:id/pipelines/:name", GetWorkflowTask) - workflowtask.POST("/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTask) - workflowtask.DELETE("/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV2) + workflowtask.POST("/id/:id/pipelines/:name/restart", RestartWorkflowTask) + workflowtask.DELETE("/id/:id/pipelines/:name", CancelWorkflowTaskV2) workflowtask.GET("/callback/id/:id/name/:name", GetWorkflowTaskCallback) } @@ -144,10 +142,10 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- workflowV3 := router.Group("v3") { - workflowV3.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowV3) + workflowV3.POST("", CreateWorkflowV3) workflowV3.GET("", ListWorkflowsV3) workflowV3.GET("/:id", GetWorkflowV3Detail) - workflowV3.PUT("/:id", gin2.UpdateOperationLogStatus, UpdateWorkflowV3) + workflowV3.PUT("/:id", UpdateWorkflowV3) workflowV3.DELETE("/:id", DeleteWorkflowV3) workflowV3.GET("/:id/args", GetWorkflowV3Args) } @@ -157,9 +155,9 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- taskV3 := router.Group("v3/workflowtask") { - taskV3.POST("", gin2.UpdateOperationLogStatus, CreateWorkflowTaskV3) - taskV3.POST("/id/:id/name/:name/restart", gin2.UpdateOperationLogStatus, RestartWorkflowTaskV3) - taskV3.DELETE("/id/:id/name/:name", gin2.UpdateOperationLogStatus, CancelWorkflowTaskV3) + taskV3.POST("", CreateWorkflowTaskV3) + taskV3.POST("/id/:id/name/:name/restart", RestartWorkflowTaskV3) + taskV3.DELETE("/id/:id/name/:name", CancelWorkflowTaskV3) taskV3.GET("/max/:max/start/:start/name/:name", ListWorkflowV3TasksResult) taskV3.GET("/id/:id/name/:name", GetWorkflowTaskV3) taskV3.GET("/callback/id/:id/name/:name", GetWorkflowTaskV3Callback) diff --git a/pkg/microservice/aslan/core/workflow/testing/handler/router.go b/pkg/microservice/aslan/core/workflow/testing/handler/router.go index c1345ef2..b01b4f34 100644 --- a/pkg/microservice/aslan/core/workflow/testing/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/testing/handler/router.go @@ -18,8 +18,6 @@ package handler import ( "github.com/gin-gonic/gin" - - gin2 "github.com/koderover/zadig/pkg/middleware/gin" ) type Router struct{} @@ -46,11 +44,11 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- tester := router.Group("test") { - tester.POST("", GetTestProductName, gin2.UpdateOperationLogStatus, CreateTestModule) - tester.PUT("", GetTestProductName, gin2.UpdateOperationLogStatus, UpdateTestModule) + tester.POST("", GetTestProductName, CreateTestModule) + tester.PUT("", GetTestProductName, UpdateTestModule) tester.GET("", ListTestModules) tester.GET("/:name", GetTestModule) - tester.DELETE("/:name", gin2.UpdateOperationLogStatus, DeleteTestModule) + tester.DELETE("/:name", DeleteTestModule) } // --------------------------------------------------------------------------------------- @@ -59,17 +57,17 @@ func (*Router) Inject(router *gin.RouterGroup) { scanner := router.Group("scanning") { // code scan config apis - scanner.POST("", GetScanningProductName, gin2.UpdateOperationLogStatus, CreateScanningModule) - scanner.PUT("/:id", GetScanningProductName, gin2.UpdateOperationLogStatus, UpdateScanningModule) + scanner.POST("", GetScanningProductName, CreateScanningModule) + scanner.PUT("/:id", GetScanningProductName, UpdateScanningModule) scanner.GET("", ListScanningModule) scanner.GET("/:id", GetScanningModule) - scanner.DELETE("/:id", gin2.UpdateOperationLogStatus, DeleteScanningModule) + scanner.DELETE("/:id", DeleteScanningModule) // code scan tasks apis - scanner.POST("/:id/task", gin2.UpdateOperationLogStatus, CreateScanningTask) + scanner.POST("/:id/task", CreateScanningTask) scanner.GET("/:id/task", ListScanningTask) scanner.GET("/:id/task/:scan_id", GetScanningTask) - scanner.DELETE("/:id/task/:scan_id", gin2.UpdateOperationLogStatus, CancelScanningTask) + scanner.DELETE("/:id/task/:scan_id", CancelScanningTask) scanner.GET("/:id/task/:scan_id/sse", GetScanningTaskSSE) } @@ -89,9 +87,9 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- testTask := router.Group("testtask") { - testTask.POST("", gin2.UpdateOperationLogStatus, CreateTestTask) - testTask.POST("/productName/:productName/id/:id/pipelines/:name/restart", gin2.UpdateOperationLogStatus, RestartTestTask) - testTask.DELETE("/productName/:productName/id/:id/pipelines/:name", gin2.UpdateOperationLogStatus, CancelTestTaskV2) + testTask.POST("", CreateTestTask) + testTask.POST("/productName/:productName/id/:id/pipelines/:name/restart", RestartTestTask) + testTask.DELETE("/productName/:productName/id/:id/pipelines/:name", CancelTestTaskV2) } // --------------------------------------------------------------------------------------- diff --git a/pkg/microservice/aslan/server/rest/server.go b/pkg/microservice/aslan/server/rest/server.go index 1ab48845..04fdce0b 100644 --- a/pkg/microservice/aslan/server/rest/server.go +++ b/pkg/microservice/aslan/server/rest/server.go @@ -52,6 +52,7 @@ func (s *engine) injectMiddlewares() { if s.mode == gin.TestMode { return } + g.Use(ginmiddleware.OperationLogStatus()) g.Use(ginmiddleware.Response()) g.Use(ginmiddleware.RequestID()) g.Use(ginmiddleware.RequestLog(log.NewFileLogger(config.RequestLogFile()))) diff --git a/pkg/middleware/gin/audit_log.go b/pkg/middleware/gin/audit_log.go index 879b3ed3..df1040ea 100644 --- a/pkg/middleware/gin/audit_log.go +++ b/pkg/middleware/gin/audit_log.go @@ -23,13 +23,21 @@ import ( "github.com/koderover/zadig/pkg/util/ginzap" ) +// OperationLogStatus update status of operation if necessary +func OperationLogStatus() gin.HandlerFunc { + return func(c *gin.Context) { + defer UpdateOperationLogStatus(c) + c.Next() + } +} + // 更新操作日志状态 func UpdateOperationLogStatus(c *gin.Context) { - log := ginzap.WithContext(c).Sugar() c.Next() if c.GetString("operationLogID") == "" { return } + log := ginzap.WithContext(c).Sugar() err := systemservice.UpdateOperation(c.GetString("operationLogID"), c.Writer.Status(), log) if err != nil { log.Errorf("UpdateOperation err:%v", err) diff --git a/pkg/middleware/gin/reponse.go b/pkg/middleware/gin/response.go similarity index 100% rename from pkg/middleware/gin/reponse.go rename to pkg/middleware/gin/response.go -- Gitee From 64a16256b3673889243607a5c1452dc1ab5d211d Mon Sep 17 00:00:00 2001 From: guoyu Date: Thu, 21 Jul 2022 20:01:26 +0800 Subject: [PATCH 2585/2647] common workflow: support plugin and freestyle jobs Signed-off-by: guoyu --- go.mod | 1 + go.sum | 30 +++ pkg/microservice/aslan/config/consts.go | 1 + .../common/repository/models/plugin_repo.go | 64 +++++++ .../repository/models/wokflow_task_v4.go | 19 +- .../common/repository/models/workflow_v4.go | 49 ++--- .../common/repository/mongodb/plugin_repo.go | 85 +++++++++ .../workflowcontroller/jobcontroller/job.go | 20 ++ .../jobcontroller/job_freestyle.go | 20 +- .../jobcontroller/job_plugin.go | 157 ++++++++++++++++ .../jobcontroller/kubernetes.go | 171 ++++++++++++++++++ .../workflowcontroller/stepcontroller/step.go | 3 +- .../stepcontroller/step_archive.go | 38 ++++ .../stepcontroller/step_deploy.go | 1 + .../stepcontroller/step_docker_build.go | 1 + .../stepcontroller/step_git.go | 1 + .../stepcontroller/step_helm_deploy.go | 1 + .../stepcontroller/step_shell.go | 7 + .../stepcontroller/step_tool_install.go | 36 ++-- pkg/microservice/aslan/core/service.go | 1 + .../core/workflow/handler/plugin_repo.go | 38 ++++ .../aslan/core/workflow/handler/router.go | 9 + .../core/workflow/service/workflow/job/job.go | 5 +- .../service/workflow/job/job_build.go | 66 ++++--- .../service/workflow/job/job_freestyle.go | 109 +++++++++++ .../service/workflow/job/job_plugin.go | 88 +++++++++ .../workflow/service/workflow/plugin_repo.go | 152 ++++++++++++++++ .../service/workflow/workflow_task_v4.go | 6 +- .../core/service/step/step_archive.go | 92 +++++----- .../core/service/step/step_tool_install.go | 37 ++-- pkg/tool/errors/http_errors.go | 7 + pkg/types/step/step_archive.go | 8 +- pkg/types/step/step_shell.go | 3 +- pkg/types/step/step_tool_install.go | 18 +- 34 files changed, 1183 insertions(+), 161 deletions(-) create mode 100644 pkg/microservice/aslan/core/common/repository/models/plugin_repo.go create mode 100644 pkg/microservice/aslan/core/common/repository/mongodb/plugin_repo.go create mode 100644 pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go create mode 100644 pkg/microservice/aslan/core/workflow/handler/plugin_repo.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/job/job_plugin.go create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go diff --git a/go.mod b/go.mod index 994f576c..7bfe3441 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 github.com/go-co-op/gocron v1.13.0 + github.com/go-git/go-git/v5 v5.4.2 // indirect github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/go-sql-driver/mysql v1.6.0 diff --git a/go.sum b/go.sum index 5a9dc0a0..096cdc3a 100644 --- a/go.sum +++ b/go.sum @@ -173,6 +173,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -189,6 +191,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -218,6 +222,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= @@ -515,6 +520,8 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7fo github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -566,6 +573,7 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= @@ -577,6 +585,14 @@ github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -997,6 +1013,8 @@ github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3 github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -1050,6 +1068,8 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1124,6 +1144,7 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1173,6 +1194,7 @@ github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMF github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -1590,6 +1612,8 @@ github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfD github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= @@ -1710,6 +1734,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1737,6 +1762,7 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1844,6 +1870,7 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1989,6 +2016,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2324,6 +2352,8 @@ gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/microservice/aslan/config/consts.go b/pkg/microservice/aslan/config/consts.go index 2ac6ebde..b708a13b 100644 --- a/pkg/microservice/aslan/config/consts.go +++ b/pkg/microservice/aslan/config/consts.go @@ -173,6 +173,7 @@ const ( JobZadigBuild JobType = "zadig-build" JobZadigDeploy JobType = "zadig-deploy" JobFreestyle JobType = "freestyle" + JobPlugin JobType = "plugin" ) type ApproveOrReject string diff --git a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go new file mode 100644 index 00000000..3bd83b97 --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package models + +import ( + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type PluginRepo struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty" yaml:"source,omitempty"` + IsOffical bool `bson:"is_offical" json:"is_offical" yaml:"is_offical"` + RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` + Source string `bson:"source,omitempty" json:"source,omitempty" yaml:"source,omitempty"` + RepoOwner string `bson:"repo_owner" json:"repo_owner" yaml:"repo_owner"` + RepoName string `bson:"repo_name" json:"repo_name" yaml:"repo_name"` + Branch string `bson:"branch" json:"branch" yaml:"branch"` + CommitID string `bson:"commit_id,omitempty" json:"commit_id,omitempty" yaml:"commit_id,omitempty"` + CodehostID int `bson:"codehost_id" json:"codehost_id" yaml:"codehost_id"` + UpdateTime int64 `bson:"update_time" json:"update_time" yaml:"update_time"` + PluginTemplates []*PluginTemplate `bson:"plugin_templates" json:"plugin_templates" yaml:"plugin_templates"` +} + +type PluginTemplate struct { + Name string `bson:"name" json:"name" yaml:"name"` + RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` + Version string `bson:"version" json:"version" yaml:"version"` + Image string `bson:"image" json:"image" yaml:"image"` + Args []string `bson:"args" json:"args" yaml:"args"` + Cmds []string `bson:"cmds" json:"cmds" yaml:"cmds"` + Envs []*Env `bson:"envs" json:"envs" yaml:"envs"` + Inputs []*Params `bson:"inputs" json:"inputs" yaml:"inputs"` + Outputs []*Params `bson:"outputs" json:"outputs" yaml:"outputs"` +} + +type Env struct { + Name string `bson:"name" json:"name" yaml:"name"` + Value string `bson:"value" json:"value" yaml:"value"` +} + +type Params struct { + Name string `bson:"name" json:"name" yaml:"name"` + Description string `bson:"description" json:"description" yaml:"description"` + ParamsType string `bson:"type" json:"type" yaml:"type"` + Value string `bson:"value" json:"value" yaml:"-"` + Default string `bson:"default" json:"default" yaml:"default"` +} + +func (PluginRepo) TableName() string { + return "plugin_repo" +} diff --git a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go index 2bedfd4d..0ba8de43 100644 --- a/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/wokflow_task_v4.go @@ -60,15 +60,16 @@ type StageTask struct { } type JobTask struct { - Name string `bson:"name" json:"name"` - JobType string `bson:"type" json:"type"` - Status config.Status `bson:"status" json:"status"` - StartTime int64 `bson:"start_time" json:"start_time,omitempty"` - EndTime int64 `bson:"end_time" json:"end_time,omitempty"` - Error string `bson:"error" json:"error"` - Properties JobProperties `bson:"properties" json:"properties"` - Steps []*StepTask `bson:"steps" json:"steps"` - Outputs []*Output `bson:"outputs" json:"outputs"` + Name string `bson:"name" json:"name"` + JobType string `bson:"type" json:"type"` + Status config.Status `bson:"status" json:"status"` + StartTime int64 `bson:"start_time" json:"start_time,omitempty"` + EndTime int64 `bson:"end_time" json:"end_time,omitempty"` + Error string `bson:"error" json:"error"` + Properties JobProperties `bson:"properties" json:"properties"` + Plugin *PluginTemplate `bson:"plugin" json:"plugin"` + Steps []*StepTask `bson:"steps" json:"steps"` + Outputs []*Output `bson:"outputs" json:"outputs"` } type StepTask struct { diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index da4ec123..1a399404 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -71,6 +71,11 @@ type Job struct { Spec interface{} `bson:"spec" yaml:"spec" json:"spec"` } +type PluginJobSpec struct { + Properties *JobProperties `bson:"properties" yaml:"properties" json:"properties"` + Plugin *PluginTemplate `bson:"plugin" yaml:"plugin" json:"plugin"` +} + type FreestyleJobSpec struct { Properties *JobProperties `bson:"properties" yaml:"properties" json:"properties"` Steps []*Step `bson:"steps" yaml:"steps" json:"steps"` @@ -109,33 +114,35 @@ type ServiceAndImage struct { } type JobProperties struct { - Timeout int64 `bson:"timeout" json:"timeout"` - Retry int64 `bson:"retry" json:"retry"` - ResourceRequest setting.Request `bson:"resource_request" json:"resource_request"` - ClusterID string `bson:"cluster_id" json:"cluster_id"` - BuildOS string `bson:"build_os" json:"build_os"` - ImageFrom string `bson:"image_from" json:"image_from"` - Namespace string `bson:"namespace" json:"namespace"` - Args []*KeyVal `bson:"args" json:"args"` - Paths string `bson:"-" json:"-"` - LogFileName string `bson:"log_file_name" json:"log_file_name"` - DockerHost string `bson:"-" json:"docker_host,omitempty"` - Cache types.Cache `bson:"cache" json:"cache"` - CacheEnable bool `bson:"cache_enable" json:"cache_enable"` - CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type"` - CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir"` + Timeout int64 `bson:"timeout" json:"timeout" yaml:"timeout"` + Retry int64 `bson:"retry" json:"retry" yaml:"retry"` + ResourceRequest setting.Request `bson:"res_req" json:"res_req" yaml:"res_req"` + ResReqSpec setting.RequestSpec `bson:"res_req_spec" json:"res_req_spec" yaml:"res_req_spec"` + ClusterID string `bson:"cluster_id" json:"cluster_id" yaml:"cluster_id"` + BuildOS string `bson:"build_os" json:"build_os" yaml:"build_os,omitempty"` + ImageFrom string `bson:"image_from" json:"image_from" yaml:"image_from,omitempty"` + ImageID string `bson:"image_id" json:"image_id" yaml:"image_id,omitempty"` + Namespace string `bson:"namespace" json:"namespace" yaml:"namespace"` + Envs []*KeyVal `bson:"envs" json:"envs" yaml:"envs,omitempty"` + Paths string `bson:"-" json:"-" yaml:"-"` + LogFileName string `bson:"log_file_name" json:"log_file_name" yaml:"log_file_name"` + DockerHost string `bson:"-" json:"docker_host,omitempty" yaml:"docker_host,omitempty"` + Cache types.Cache `bson:"cache" json:"cache" yaml:"cache"` + CacheEnable bool `bson:"cache_enable" json:"cache_enable" yaml:"cache_enable"` + CacheDirType types.CacheDirType `bson:"cache_dir_type" json:"cache_dir_type" yaml:"cache_dir_type"` + CacheUserDir string `bson:"cache_user_dir" json:"cache_user_dir" yaml:"cache_user_dir"` } type Step struct { - Name string `bson:"name" json:"name"` - Timeout int64 `bson:"timeout" json:"timeout"` - StepType config.StepType `bson:"step_type" json:"step_type"` - Spec interface{} `bson:"spec" json:"spec"` + Name string `bson:"name" json:"name" yaml:"name"` + Timeout int64 `bson:"timeout" json:"timeout" yaml:"timeout"` + StepType config.StepType `bson:"type" json:"type" yaml:"type"` + Spec interface{} `bson:"spec" json:"spec" yaml:"spec"` } type Output struct { - Name string `bson:"name" json:"name"` - Description string `bson:"description" json:"description"` + Name string `bson:"name" json:"name" yaml:"name"` + Description string `bson:"description" json:"description" yaml:"description"` } func IToiYaml(before interface{}, after interface{}) error { diff --git a/pkg/microservice/aslan/core/common/repository/mongodb/plugin_repo.go b/pkg/microservice/aslan/core/common/repository/mongodb/plugin_repo.go new file mode 100644 index 00000000..064e090d --- /dev/null +++ b/pkg/microservice/aslan/core/common/repository/mongodb/plugin_repo.go @@ -0,0 +1,85 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package mongodb + +import ( + "context" + "time" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + mongotool "github.com/koderover/zadig/pkg/tool/mongo" +) + +type PluginRepoColl struct { + *mongo.Collection + + coll string +} + +func NewPluginRepoColl() *PluginRepoColl { + name := models.PluginRepo{}.TableName() + return &PluginRepoColl{ + Collection: mongotool.Database(config.MongoDatabase()).Collection(name), + coll: name, + } +} + +func (c *PluginRepoColl) EnsureIndex(ctx context.Context) error { + return nil +} + +func (c *PluginRepoColl) GetCollectionName() string { + return c.coll +} + +func (c *PluginRepoColl) List() ([]*models.PluginRepo, error) { + var res []*models.PluginRepo + query := bson.M{} + cursor, err := c.Collection.Find(context.TODO(), query) + if err != nil { + return nil, err + } + err = cursor.All(context.TODO(), &res) + + return res, err +} + +func (c *PluginRepoColl) Delete(id string) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + query := bson.M{"_id": oid} + + _, err = c.DeleteOne(context.TODO(), query) + return err +} + +func (c *PluginRepoColl) Upsert(pluginRepo *models.PluginRepo) error { + query := bson.M{"repo_owner": pluginRepo.RepoOwner, "repo_name": pluginRepo.RepoName, "branch": pluginRepo.Branch} + change := bson.M{"$set": pluginRepo} + pluginRepo.UpdateTime = time.Now().Unix() + + _, err := c.UpdateOne(context.TODO(), query, change, options.Update().SetUpsert(true)) + return err +} diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go index 742d1bae..9499b693 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job.go @@ -18,8 +18,10 @@ package jobcontroller import ( "context" + "fmt" "io" "os" + "strings" "sync" "time" @@ -29,6 +31,7 @@ import ( commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/util/rand" ) type JobCtl interface { @@ -88,6 +91,8 @@ func runJob(ctx context.Context, job *commonmodels.JobTask, workflowCtx *commonm job.Error = err.Error() } return + case string(config.JobPlugin): + jobCtl = NewPluginsJobCtl(job, workflowCtx, ack, logger) default: jobCtl = NewFreestyleJobCtl(job, workflowCtx, ack, logger) } @@ -166,3 +171,18 @@ func saveFile(src io.Reader, localFile string) error { _, err = io.Copy(out, src) return err } + +func getJobName(workflowName string, taskID int64) string { + // max lenth of workflowName was 32, so job name was unique in one task. + base := strings.Replace( + strings.ToLower( + fmt.Sprintf( + "%s-%d-", + workflowName, + taskID, + ), + ), + "_", "-", -1, + ) + return rand.GenerateName(base) +} diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_freestyle.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_freestyle.go index 3f2156d9..d200afd7 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_freestyle.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_freestyle.go @@ -35,7 +35,6 @@ import ( "github.com/koderover/zadig/pkg/tool/dockerhost" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" - "github.com/koderover/zadig/pkg/util/rand" ) const ( @@ -67,21 +66,6 @@ func NewFreestyleJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.Wor } } -func getJobName(workflowName string, taskID int64) string { - // max lenth of workflowName was 32, so job name was unique in one task. - base := strings.Replace( - strings.ToLower( - fmt.Sprintf( - "%s-%d-", - workflowName, - taskID, - ), - ), - "_", "-", -1, - ) - return rand.GenerateName(base) -} - func (c *FreestyleJobCtl) Run(ctx context.Context) { // get kube client hubServerAddr := config.HubServerAddress() @@ -173,7 +157,7 @@ func (c *FreestyleJobCtl) Run(ctx context.Context) { // jobImage := getReaperImage(config.ReaperImage(), c.job.Properties.BuildOS) //Resource request default value is LOW - job, err := buildJob(c.job.JobType, jobImage, c.jobName, c.job.Properties.ClusterID, c.job.Properties.Namespace, c.job.Properties.ResourceRequest, setting.DefaultRequestSpec, c.job, c.workflowCtx, nil) + job, err := buildJob(c.job.JobType, jobImage, c.jobName, c.job.Properties.ClusterID, c.job.Properties.Namespace, c.job.Properties.ResourceRequest, c.job.Properties.ResReqSpec, c.job, c.workflowCtx, nil) if err != nil { msg := fmt.Sprintf("create job context error: %v", err) c.logger.Error(msg) @@ -257,7 +241,7 @@ func (c *FreestyleJobCtl) Complete(ctx context.Context) { func BuildJobExcutorContext(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, logger *zap.SugaredLogger) *JobContext { var envVars, secretEnvVars []string - for _, env := range job.Properties.Args { + for _, env := range job.Properties.Envs { if env.IsCredential { secretEnvVars = append(secretEnvVars, strings.Join([]string{env.Key, env.Value}, "=")) continue diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go new file mode 100644 index 00000000..2b3b4d41 --- /dev/null +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go @@ -0,0 +1,157 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jobcontroller + +import ( + "context" + "fmt" + "strings" + "time" + + zadigconfig "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/setting" + krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" + "github.com/koderover/zadig/pkg/tool/kube/updater" + "go.uber.org/zap" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + crClient "sigs.k8s.io/controller-runtime/pkg/client" +) + +type PluginJobCtl struct { + job *commonmodels.JobTask + jobName string + workflowCtx *commonmodels.WorkflowTaskCtx + logger *zap.SugaredLogger + kubeclient crClient.Client + clientset kubernetes.Interface + restConfig *rest.Config + ack func() +} + +func NewPluginsJobCtl(job *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, ack func(), logger *zap.SugaredLogger) *PluginJobCtl { + return &PluginJobCtl{ + job: job, + workflowCtx: workflowCtx, + logger: logger, + ack: ack, + jobName: getJobName(workflowCtx.WorkflowName, workflowCtx.TaskID), + } +} + +func (c *PluginJobCtl) Run(ctx context.Context) { + // get kube client + hubServerAddr := config.HubServerAddress() + switch c.job.Properties.ClusterID { + case setting.LocalClusterID: + c.job.Properties.Namespace = zadigconfig.Namespace() + c.kubeclient = krkubeclient.Client() + c.clientset = krkubeclient.Clientset() + c.restConfig = krkubeclient.RESTConfig() + default: + c.job.Properties.Namespace = setting.AttachedClusterNamespace + + crClient, clientset, restConfig, err := GetK8sClients(hubServerAddr, c.job.Properties.ClusterID) + if err != nil { + c.job.Status = config.StatusFailed + c.job.Error = err.Error() + c.job.EndTime = time.Now().Unix() + return + } + c.kubeclient = crClient + c.clientset = clientset + c.restConfig = restConfig + } + + jobLabel := &JobLabel{ + WorkflowName: c.workflowCtx.WorkflowName, + TaskID: c.workflowCtx.TaskID, + JobType: string(c.job.JobType), + JobName: c.job.Name, + } + job, err := buildPlainJob(c.jobName, c.job.Properties.ResourceRequest, c.job.Properties.ResReqSpec, c.job, c.workflowCtx) + if err != nil { + msg := fmt.Sprintf("create job context error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + + job.Namespace = c.job.Properties.Namespace + + if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + msg := fmt.Sprintf("delete job error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + if err := updater.CreateJob(job, c.kubeclient); err != nil { + msg := fmt.Sprintf("create job error: %v", err) + c.logger.Error(msg) + c.job.Status = config.StatusFailed + c.job.Error = msg + return + } + c.logger.Infof("succeed to create job %s", c.jobName) + +} + +func (c *PluginJobCtl) Wait(ctx context.Context) { + status := waitPlainJobEnd(ctx, int(c.job.Properties.Timeout), c.job.Properties.Namespace, c.jobName, c.kubeclient, c.logger) + c.job.Status = status + +} + +func (c *PluginJobCtl) Complete(ctx context.Context) { + jobLabel := &JobLabel{ + WorkflowName: c.workflowCtx.WorkflowName, + TaskID: c.workflowCtx.TaskID, + JobType: string(c.job.JobType), + JobName: c.job.Name, + } + + // 清理用户取消和超时的任务 + defer func() { + // go func() { + // if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + // c.logger.Error(err) + // } + // }() + }() + + // get job outputs info from pod terminate message. + outputs, err := getJobOutput(c.job.Properties.Namespace, c.job.Name, jobLabel, c.kubeclient) + if err != nil { + c.logger.Error(err) + c.job.Error = err.Error() + } + + // write jobs output info to globalcontext so other job can use like this $(jobName.outputName) + for _, output := range outputs { + c.workflowCtx.GlobalContextSet(strings.Join([]string{c.job.Name, output.Name}, "."), output.Value) + } + + if err := saveContainerLog(c.job, c.workflowCtx.WorkflowName, c.workflowCtx.TaskID, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + c.job.Error = err.Error() + return + } +} diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go index 8fcb42ba..bc5e1830 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go @@ -145,6 +145,117 @@ func getBaseImage(buildOS, imageFrom string) string { return jobImage } +func buildPlainJob(jobName string, resReq setting.Request, resReqSpec setting.RequestSpec, jobTask *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx) (*batchv1.Job, error) { + collectJobOutput := `OLD_IFS=$IFS +export IFS="," +files='%s' +outputs='%s' +file_arr=($files) +output_arr=($outputs) +IFS="$OLD_IFS" +result="{" +for i in ${!file_arr[@]}; +do + file_value=$(cat ${file_arr[$i]}) + output_value=${output_arr[$i]} + result="$result\"$output_value\":\"$file_value\"," +done +result=$(sed 's/,$/}/' <<< $result) +echo $result > %s +` + files := []string{} + outputs := []string{} + for _, output := range jobTask.Outputs { + outputFile := path.Join(job.JobOutputDir, output.Name) + files = append(files, outputFile) + outputs = append(outputs, output.Name) + } + collectJobOutputCommand := fmt.Sprintf(collectJobOutput, strings.Join(files, ","), strings.Join(outputs, ","), job.JobTerminationFile) + + labels := getJobLabels(&JobLabel{ + WorkflowName: workflowCtx.WorkflowName, + TaskID: workflowCtx.TaskID, + JobType: string(jobTask.JobType), + JobName: jobTask.Name, + }) + + ImagePullSecrets := []corev1.LocalObjectReference{ + { + Name: setting.DefaultImagePullSecret, + }, + } + envs := []corev1.EnvVar{} + for _, env := range jobTask.Plugin.Envs { + envs = append(envs, corev1.EnvVar{Name: env.Name, Value: env.Value}) + } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: jobName, + Labels: labels, + }, + Spec: batchv1.JobSpec{ + Completions: int32Ptr(1), + Parallelism: int32Ptr(1), + BackoffLimit: int32Ptr(0), + // in case finished zombie job not cleaned up by zadig + TTLSecondsAfterFinished: int32Ptr(3600), + // in case zombie job never stop + ActiveDeadlineSeconds: int64Ptr(jobTask.Properties.Timeout*60 + 3600), + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + ImagePullSecrets: ImagePullSecrets, + Containers: []corev1.Container{ + { + ImagePullPolicy: corev1.PullAlways, + Name: jobTask.Name, + Image: jobTask.Plugin.Image, + Args: jobTask.Plugin.Args, + Command: jobTask.Plugin.Cmds, + Lifecycle: &corev1.Lifecycle{ + PostStart: &corev1.Handler{ + Exec: &corev1.ExecAction{ + Command: []string{"/bin/sh", "-c", fmt.Sprintf("mkdir -p %s", job.JobOutputDir)}, + }, + }, + PreStop: &corev1.Handler{ + Exec: &corev1.ExecAction{ + Command: []string{"/bin/sh", "-c", collectJobOutputCommand}, + }, + }, + }, + Env: envs, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "zadig-context", + MountPath: ZadigContextDir, + }, + }, + Resources: getResourceRequirements(resReq, resReqSpec), + + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + TerminationMessagePath: job.JobTerminationFile, + }, + }, + Volumes: []corev1.Volume{ + { + Name: "zadig-context", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + }, + }, + } + return job, nil +} + func buildJob(jobType, jobImage, jobName, clusterID, currentNamespace string, resReq setting.Request, resReqSpec setting.RequestSpec, jobTask *commonmodels.JobTask, workflowCtx *commonmodels.WorkflowTaskCtx, registries []*task.RegistryNamespace) (*batchv1.Job, error) { // tailLogCommandTemplate := `tail -f %s & // while [ -f %s ]; @@ -403,6 +514,66 @@ func generateResourceRequirements(req setting.Request, reqSpec setting.RequestSp func int32Ptr(i int32) *int32 { return &i } func int64Ptr(i int64) *int64 { return &i } +func waitPlainJobEnd(ctx context.Context, taskTimeout int, namespace, jobName string, kubeClient crClient.Client, xl *zap.SugaredLogger) config.Status { + xl.Infof("wait job to start: %s/%s", namespace, jobName) + timeout := time.After(time.Duration(taskTimeout) * time.Minute) + podTimeout := time.After(120 * time.Second) + + xl.Infof("Timeout of preparing Pod: %s. Timeout of running task: %s.", 120*time.Second, time.Duration(taskTimeout)*time.Minute) + + var started bool + for { + select { + case <-ctx.Done(): + return config.StatusCancelled + + case <-podTimeout: + return config.StatusTimeout + default: + job, _, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil { + xl.Errorf("get job failed, namespace:%s, jobName:%s, err:%v", namespace, jobName, err) + } + if job != nil { + started = job.Status.Active > 0 + } + } + if started { + break + } + + time.Sleep(time.Second) + } + // 等待job 运行结束 + xl.Infof("wait job to end: %s %s", namespace, jobName) + for { + select { + case <-ctx.Done(): + return config.StatusCancelled + + case <-timeout: + return config.StatusTimeout + + default: + job, found, err := getter.GetJob(namespace, jobName, kubeClient) + if err != nil || !found { + xl.Errorf("failed to get pod with label job-name=%s %v", jobName, err) + return config.StatusFailed + } + + if job.Status.Succeeded != 0 { + return config.StatusPassed + } + if job.Status.Failed != 0 { + return config.StatusFailed + } + } + + time.Sleep(time.Second * 1) + } + +} + func waitJobEndWithFile(ctx context.Context, taskTimeout int, namespace, jobName string, checkFile bool, kubeClient crClient.Client, clientset kubernetes.Interface, restConfig *rest.Config, xl *zap.SugaredLogger) (status config.Status) { xl.Infof("wait job to start: %s/%s", namespace, jobName) timeout := time.After(time.Duration(taskTimeout) * time.Minute) diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go index 9110358c..621276b3 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step.go @@ -126,7 +126,8 @@ func instantiateStepCtl(step *commonmodels.StepTask, workflowCtx *commonmodels.W case config.StepHelmDeploy: stepCtl, err = NewHelmDeployCtl(step, workflowCtx, logger) default: - logger.Infof("unknown step type: %s", step.StepType) + logger.Errorf("unknown step type: %s", step.StepType) + return stepCtl, fmt.Errorf("unknown step type: %s", step.StepType) } return stepCtl, err } diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go index 21caef7c..15e7e593 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_archive.go @@ -25,6 +25,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/types/step" ) @@ -43,10 +44,31 @@ func NewArchiveCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*ar if err := yaml.Unmarshal(yamlString, &archiveSpec); err != nil { return nil, fmt.Errorf("unmarshal git spec error: %v", err) } + stepTask.Spec = archiveSpec return &archiveCtl{archiveSpec: archiveSpec, log: log, step: stepTask}, nil } func (s *archiveCtl) PreRun(ctx context.Context) error { + if s.archiveSpec.S3 != nil { + return nil + } + var modelS3 *commonmodels.S3Storage + var err error + if s.archiveSpec.ObjectStorageID == "" { + modelS3, err = commonrepo.NewS3StorageColl().FindDefault() + if err != nil { + return err + } + s.archiveSpec.S3 = modelS3toS3(modelS3) + } else { + modelS3, err = commonrepo.NewS3StorageColl().Find(s.archiveSpec.ObjectStorageID) + if err != nil { + return err + } + s.archiveSpec.S3 = modelS3toS3(modelS3) + s.archiveSpec.S3.Subfolder = "" + } + s.step.Spec = s.archiveSpec return nil } @@ -57,3 +79,19 @@ func (s *archiveCtl) Run(ctx context.Context) (config.Status, error) { func (s *archiveCtl) AfterRun(ctx context.Context) error { return nil } + +func modelS3toS3(modelS3 *commonmodels.S3Storage) *step.S3 { + resp := &step.S3{ + Ak: modelS3.Ak, + Sk: modelS3.Sk, + Endpoint: modelS3.Endpoint, + Bucket: modelS3.Bucket, + Subfolder: modelS3.Subfolder, + Insecure: modelS3.Insecure, + Provider: modelS3.Provider, + } + if modelS3.Insecure { + resp.Protocol = "http" + } + return resp +} diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go index 3b3b5bff..42ae355d 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_deploy.go @@ -61,6 +61,7 @@ func NewDeployCtl(stepTask *commonmodels.StepTask, workflowCtx *commonmodels.Wor if err := yaml.Unmarshal(yamlString, &deploySpec); err != nil { return nil, fmt.Errorf("unmarshal deploy spec error: %v", err) } + stepTask.Spec = deploySpec return &deployCtl{deploySpec: deploySpec, workflowCtx: workflowCtx, log: log, step: stepTask}, nil } diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go index b1c35759..ca982f09 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_docker_build.go @@ -47,6 +47,7 @@ func NewDockerBuildCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) if dockerBuildSpec.Proxy == nil { dockerBuildSpec.Proxy = &step.Proxy{} } + stepTask.Spec = dockerBuildSpec return &dockerBuildCtl{dockerBuildSpec: dockerBuildSpec, log: log, step: stepTask}, nil } diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go index 21e0a4f4..99998d6b 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_git.go @@ -49,6 +49,7 @@ func NewGitCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*gitCtl if gitSpec.Proxy == nil { gitSpec.Proxy = &step.Proxy{} } + stepTask.Spec = gitSpec return &gitCtl{gitSpec: gitSpec, log: log, step: stepTask}, nil } diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go index 3c0c2428..c2facba7 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_helm_deploy.go @@ -76,6 +76,7 @@ func NewHelmDeployCtl(stepTask *commonmodels.StepTask, workflowCtx *commonmodels if err := yaml.Unmarshal(yamlString, &helmDeploySpec); err != nil { return nil, fmt.Errorf("unmarshal helm deploy spec error: %v", err) } + stepTask.Spec = helmDeploySpec return &helmDeployCtl{helmDeploySpec: helmDeploySpec, workflowCtx: workflowCtx, log: log, step: stepTask}, nil } diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go index c191f50b..cecac78b 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_shell.go @@ -19,6 +19,7 @@ package stepcontroller import ( "context" "fmt" + "strings" "go.uber.org/zap" "gopkg.in/yaml.v3" @@ -43,10 +44,16 @@ func NewShellCtl(stepTask *commonmodels.StepTask, log *zap.SugaredLogger) (*shel if err := yaml.Unmarshal(yamlString, &shellSpec); err != nil { return nil, fmt.Errorf("unmarshal shell spec error: %v", err) } + stepTask.Spec = shellSpec return &shellCtl{shellSpec: shellSpec, log: log, step: stepTask}, nil } func (s *shellCtl) PreRun(ctx context.Context) error { + if len(s.shellSpec.Scripts) > 0 { + return nil + } + s.shellSpec.Scripts = strings.Split(replaceWrapLine(s.shellSpec.Script), "\n") + s.step.Spec = s.shellSpec return nil } diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go index 305d925b..f18c53d7 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/stepcontroller/step_tool_install.go @@ -47,21 +47,12 @@ func NewToolInstallCtl(stepTask *commonmodels.StepTask, jobPath *string, log *za if err := yaml.Unmarshal(yamlString, &toolInstallSpec); err != nil { return nil, fmt.Errorf("unmarshal tool spec error: %v", err) } + stepTask.Spec = toolInstallSpec return &toolInstallCtl{toolInstalldSpec: toolInstallSpec, jobPath: jobPath, log: log, step: stepTask}, nil } func (s *toolInstallCtl) PreRun(ctx context.Context) error { - install, err := buildInstallCtx(s.toolInstalldSpec.Name, s.toolInstalldSpec.Version) - if err != nil { - s.log.Error(err) - } spec := &step.StepToolInstallSpec{ - Name: s.toolInstalldSpec.Name, - Version: s.toolInstalldSpec.Version, - BinPath: install.BinPath, - Download: install.DownloadPath, - Envs: install.Envs, - Scripts: strings.Split(replaceWrapLine(install.Scripts), "\n"), S3Storage: &step.S3{ Ak: config.S3StorageAK(), Sk: config.S3StorageSK(), @@ -84,12 +75,27 @@ func (s *toolInstallCtl) PreRun(ctx context.Context) error { } } - s.step.Spec = spec - if *s.jobPath != "" { - *s.jobPath = strings.Join([]string{*s.jobPath, install.BinPath}, ":") - } else { - *s.jobPath = install.BinPath + for _, tool := range s.toolInstalldSpec.Installs { + install, err := buildInstallCtx(tool.Name, tool.Version) + if err != nil { + s.log.Error(err) + } + spec.Installs = append(spec.Installs, &step.Tool{ + Name: tool.Name, + Version: tool.Version, + BinPath: install.BinPath, + Download: install.DownloadPath, + Envs: install.Envs, + Scripts: strings.Split(replaceWrapLine(install.Scripts), "\n"), + }) + if *s.jobPath != "" { + *s.jobPath = strings.Join([]string{*s.jobPath, install.BinPath}, ":") + } else { + *s.jobPath = install.BinPath + } } + + s.step.Spec = spec return nil } diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index 01ab7f30..b31939f6 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -239,6 +239,7 @@ func initDatabase() { commonrepo.NewWorkflowV4Coll(), commonrepo.NewworkflowTaskv4Coll(), commonrepo.NewWorkflowQueueColl(), + commonrepo.NewPluginRepoColl(), systemrepo.NewAnnouncementColl(), systemrepo.NewOperationLogColl(), diff --git a/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go b/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go new file mode 100644 index 00000000..980f63b7 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go @@ -0,0 +1,38 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package handler + +import ( + "github.com/gin-gonic/gin" + + "github.com/koderover/zadig/pkg/microservice/aslan/core/workflow/service/workflow" + internalhandler "github.com/koderover/zadig/pkg/shared/handler" +) + +func UpdateOfficalPluginRepository(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Err = workflow.UpdateOfficalPluginRepository(ctx.Logger) +} + +func ListPluginTemplates(c *gin.Context) { + ctx := internalhandler.NewContext(c) + defer func() { internalhandler.JSONResponse(c, ctx) }() + + ctx.Resp, ctx.Err = workflow.ListPluginTemplates(ctx.Logger) +} diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 9b6b80e1..1a8fb603 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -192,6 +192,15 @@ func (*Router) Inject(router *gin.RouterGroup) { taskV4.POST("/approve", ApproveStage) } + // --------------------------------------------------------------------------------------- + // plugin repo 接口 + // --------------------------------------------------------------------------------------- + plugin := router.Group("plugin") + { + plugin.POST("/offical", UpdateOfficalPluginRepository) + plugin.GET("/template", ListPluginTemplates) + } + bundles := router.Group("bundle-resources") { bundles.GET("", GetBundleResources) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go index a03998c1..aff52e2e 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -35,9 +35,12 @@ func initJobCtl(job *commonmodels.Job, workflow *commonmodels.WorkflowV4) (JobCt switch job.JobType { case config.JobZadigBuild: resp = &BuildJob{job: job, workflow: workflow} - case config.JobZadigDeploy: resp = &DeployJob{job: job, workflow: workflow} + case config.JobPlugin: + resp = &PluginJob{job: job, workflow: workflow} + case config.JobFreestyle: + resp = &FreeStyleJob{job: job, workflow: workflow} default: return resp, fmt.Errorf("job type not found %s", job.JobType) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 6ccbffbd..10aa7edb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -123,7 +123,8 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { jobTask.Properties = commonmodels.JobProperties{ Timeout: int64(buildInfo.Timeout), ResourceRequest: buildInfo.PreBuild.ResReq, - Args: renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs), + ResReqSpec: buildInfo.PreBuild.ResReqSpec, + Envs: renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs), ClusterID: buildInfo.PreBuild.ClusterID, BuildOS: buildInfo.PreBuild.BuildOS, ImageFrom: buildInfo.PreBuild.ImageFrom, @@ -141,23 +142,28 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { jobTask.Properties.CacheDirType = buildInfo.CacheDirType jobTask.Properties.CacheUserDir = buildInfo.CacheUserDir } - jobTask.Properties.Args = append(jobTask.Properties.Args, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) + jobTask.Properties.Envs = append(jobTask.Properties.Envs, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) if jobTask.Properties.CacheEnable && jobTask.Properties.Cache.MediumType == types.NFSMedium { - jobTask.Properties.CacheUserDir = renderEnv(jobTask.Properties.CacheUserDir, jobTask.Properties.Args) - jobTask.Properties.Cache.NFSProperties.Subpath = renderEnv(jobTask.Properties.Cache.NFSProperties.Subpath, jobTask.Properties.Args) + jobTask.Properties.CacheUserDir = renderEnv(jobTask.Properties.CacheUserDir, jobTask.Properties.Envs) + jobTask.Properties.Cache.NFSProperties.Subpath = renderEnv(jobTask.Properties.Cache.NFSProperties.Subpath, jobTask.Properties.Envs) } // init tools install step + tools := []*step.Tool{} for _, tool := range buildInfo.PreBuild.Installs { - toolInstallStep := &commonmodels.StepTask{ - Name: fmt.Sprintf("%s-%s-%s", build.ServiceName, tool.Name, tool.Version), - JobName: jobTask.Name, - StepType: config.StepTools, - Spec: step.StepToolInstallSpec{Name: tool.Name, Version: tool.Version}, - } - jobTask.Steps = append(jobTask.Steps, toolInstallStep) + tools = append(tools, &step.Tool{ + Name: tool.Name, + Version: tool.Version, + }) } + toolInstallStep := &commonmodels.StepTask{ + Name: fmt.Sprintf("%s-%s", build.ServiceName, "tool-install"), + JobName: jobTask.Name, + StepType: config.StepTools, + Spec: step.StepToolInstallSpec{Installs: tools}, + } + jobTask.Steps = append(jobTask.Steps, toolInstallStep) // init git clone step gitStep := &commonmodels.StepTask{ Name: build.ServiceName + "-git", @@ -211,14 +217,19 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { // init archive step if buildInfo.PostBuild.FileArchive != nil && buildInfo.PostBuild.FileArchive.FileLocation != "" { + uploads := []*step.Upload{ + { + FilePath: path.Join(buildInfo.PostBuild.FileArchive.FileLocation, build.Package), + DestinationPath: path.Join(j.workflow.Name, fmt.Sprint(taskID), "archive"), + }, + } archiveStep := &commonmodels.StepTask{ Name: build.ServiceName + "-archive", JobName: jobTask.Name, StepType: config.StepArchive, Spec: step.StepArchiveSpec{ - FilePath: path.Join(buildInfo.PostBuild.FileArchive.FileLocation, build.Package), - DestinationPath: path.Join(j.workflow.Name, fmt.Sprint(taskID), "archive"), - S3: modelS3toS3(defaultS3), + UploadDetail: uploads, + S3: modelS3toS3(defaultS3), }, } jobTask.Steps = append(jobTask.Steps, archiveStep) @@ -232,19 +243,24 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { } s3 := modelS3toS3(modelS3) s3.Subfolder = "" + uploads := []*step.Upload{} + archiveStep := &commonmodels.StepTask{ + Name: build.ServiceName + "-object-storage", + JobName: jobTask.Name, + StepType: config.StepArchive, + Spec: step.StepArchiveSpec{ + UploadDetail: uploads, + ObjectStorageID: buildInfo.PostBuild.ObjectStorageUpload.ObjectStorageID, + S3: s3, + }, + } for _, detail := range buildInfo.PostBuild.ObjectStorageUpload.UploadDetail { - archiveStep := &commonmodels.StepTask{ - Name: build.ServiceName + "-object-storage", - JobName: jobTask.Name, - StepType: config.StepArchive, - Spec: step.StepArchiveSpec{ - FilePath: detail.FilePath, - DestinationPath: detail.DestinationPath, - S3: s3, - }, - } - jobTask.Steps = append(jobTask.Steps, archiveStep) + uploads = append(uploads, &step.Upload{ + FilePath: detail.FilePath, + DestinationPath: detail.DestinationPath, + }) } + jobTask.Steps = append(jobTask.Steps, archiveStep) } // init psot build shell step diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go new file mode 100644 index 00000000..8d17ef2c --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go @@ -0,0 +1,109 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + steptypes "github.com/koderover/zadig/pkg/types/step" +) + +type FreeStyleJob struct { + job *commonmodels.Job + workflow *commonmodels.WorkflowV4 + spec *commonmodels.FreestyleJobSpec +} + +func (j *FreeStyleJob) Instantiate() error { + j.spec = &commonmodels.FreestyleJobSpec{} + if err := commonmodels.IToiYaml(j.job.Spec, j.spec); err != nil { + return err + } + for _, step := range j.spec.Steps { + switch step.StepType { + case config.StepTools: + stepSpec := &steptypes.StepToolInstallSpec{} + if err := commonmodels.IToiYaml(step.Spec, stepSpec); err != nil { + return err + } + step.Spec = stepSpec + case config.StepGit: + stepSpec := &steptypes.StepGitSpec{} + if err := commonmodels.IToiYaml(step.Spec, stepSpec); err != nil { + return err + } + step.Spec = stepSpec + case config.StepShell: + stepSpec := &steptypes.StepShellSpec{} + if err := commonmodels.IToiYaml(step.Spec, stepSpec); err != nil { + return err + } + step.Spec = stepSpec + case config.StepArchive: + stepSpec := &steptypes.StepArchiveSpec{} + if err := commonmodels.IToiYaml(step.Spec, stepSpec); err != nil { + return err + } + step.Spec = stepSpec + default: + return fmt.Errorf("freestyle job step type %s not supported", step.StepType) + } + + } + j.job.Spec = j.spec + return nil +} + +func (j *FreeStyleJob) SetPreset() error { + j.spec = &commonmodels.FreestyleJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + return nil +} + +func (j *FreeStyleJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { + resp := []*commonmodels.JobTask{} + j.spec = &commonmodels.FreestyleJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return resp, err + } + j.job.Spec = j.spec + jobTask := &commonmodels.JobTask{ + Name: j.job.Name, + JobType: string(config.JobFreestyle), + Properties: *j.spec.Properties, + Steps: stepsToStepTasks(j.spec.Steps), + } + return []*commonmodels.JobTask{jobTask}, nil +} + +func stepsToStepTasks(step []*commonmodels.Step) []*commonmodels.StepTask { + resp := []*commonmodels.StepTask{} + for _, step := range step { + stepTask := &commonmodels.StepTask{ + Name: step.Name, + StepType: step.StepType, + Spec: step.Spec, + } + resp = append(resp, stepTask) + } + return resp +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_plugin.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_plugin.go new file mode 100644 index 00000000..1736b86f --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_plugin.go @@ -0,0 +1,88 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package job + +import ( + "strings" + + "github.com/koderover/zadig/pkg/microservice/aslan/config" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" +) + +type PluginJob struct { + job *commonmodels.Job + workflow *commonmodels.WorkflowV4 + spec *commonmodels.PluginJobSpec +} + +func (j *PluginJob) Instantiate() error { + j.spec = &commonmodels.PluginJobSpec{} + if err := commonmodels.IToiYaml(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + return nil +} + +func (j *PluginJob) SetPreset() error { + j.spec = &commonmodels.PluginJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return err + } + j.job.Spec = j.spec + return nil +} + +func (j *PluginJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { + resp := []*commonmodels.JobTask{} + j.spec = &commonmodels.PluginJobSpec{} + if err := commonmodels.IToi(j.job.Spec, j.spec); err != nil { + return resp, err + } + j.job.Spec = j.spec + jobTask := &commonmodels.JobTask{ + Name: j.job.Name, + JobType: string(config.JobPlugin), + Properties: *j.spec.Properties, + Plugin: j.spec.Plugin, + } + for _, output := range j.spec.Plugin.Outputs { + jobTask.Outputs = append(jobTask.Outputs, &commonmodels.Output{Name: output.Name, Description: output.Description}) + } + jobTask.Plugin = renderPlugin(jobTask.Plugin, j.spec.Plugin.Inputs) + return []*commonmodels.JobTask{jobTask}, nil +} + +func renderPlugin(plugin *commonmodels.PluginTemplate, inputs []*commonmodels.Params) *commonmodels.PluginTemplate { + for _, env := range plugin.Envs { + env.Value = renderString(env.Value, inputs) + } + for i, arg := range plugin.Args { + plugin.Args[i] = renderString(arg, inputs) + } + for i, cmd := range plugin.Cmds { + plugin.Cmds[i] = renderString(cmd, inputs) + } + return plugin +} + +func renderString(value string, inputs []*commonmodels.Params) string { + for _, input := range inputs { + value = strings.ReplaceAll(value, "$("+input.Name+")", input.Value) + } + return value +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go b/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go new file mode 100644 index 00000000..4d663eba --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go @@ -0,0 +1,152 @@ +/* +Copyright 2022 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package workflow + +import ( + "fmt" + "io/ioutil" + "os" + "path" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/google/uuid" + "go.uber.org/zap" + "gopkg.in/yaml.v3" + + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" + e "github.com/koderover/zadig/pkg/tool/errors" +) + +const ( + OfficalRepoOwner = "dianqihanwangzi" + OfficalRepoName = "zadig-plugins" + OfficalRepoURL = "https://github.com/" + OfficalRepoOwner + "/" + OfficalRepoName + OfficalBranch = "main" +) + +func UpsertPluginRepository(args *commonmodels.PluginRepo, log *zap.SugaredLogger) error { + args.PluginTemplates = []*commonmodels.PluginTemplate{} + // TODO: git clone and checkout the plugin + checkoutPath := fmt.Sprintf("/tmp/%s", uuid.New().String()) + defer os.RemoveAll(checkoutPath) + if args.IsOffical { + args.RepoOwner = OfficalRepoOwner + args.RepoName = OfficalRepoName + args.Branch = OfficalBranch + args.RepoURL = OfficalRepoURL + } + if _, err := git.PlainClone(checkoutPath, false, &git.CloneOptions{ + URL: args.RepoURL, + Depth: 1, + ReferenceName: plumbing.NewBranchReferenceName(args.Branch), + }); err != nil { + log.Errorf("clone plugin repo error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + + dirs, err := ioutil.ReadDir(checkoutPath) + if err != nil { + log.Errorf("loop plugin repo error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + + for _, dir := range dirs { + if !dir.IsDir() { + continue + } + subDirs, err := ioutil.ReadDir(path.Join(checkoutPath, dir.Name())) + if err != nil { + log.Errorf("loop plugin repo dirs error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + for _, subDir := range subDirs { + if !subDir.IsDir() { + continue + } + files, err := ioutil.ReadDir(path.Join(checkoutPath, dir.Name(), subDir.Name())) + if err != nil { + log.Errorf("loop plugin repo sub dirs error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + for _, file := range files { + if file.IsDir() { + continue + } + if file.Name() != dir.Name()+".yaml" { + continue + } + yamlFile, err := os.Open(path.Join(checkoutPath, dir.Name(), subDir.Name(), file.Name())) + if err != nil { + log.Errorf("load yaml files error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + defer yamlFile.Close() + content, err := ioutil.ReadAll(yamlFile) + if err != nil { + log.Errorf("read yaml files error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + pluginTemplate := &commonmodels.PluginTemplate{} + if err := yaml.Unmarshal(content, pluginTemplate); err != nil { + log.Errorf("unmarshal yaml files error: %v", err) + return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + } + args.PluginTemplates = append(args.PluginTemplates, pluginTemplate) + } + } + } + return commonrepo.NewPluginRepoColl().Upsert(args) +} + +func UpdateOfficalPluginRepository(log *zap.SugaredLogger) error { + return UpsertPluginRepository(&commonmodels.PluginRepo{IsOffical: true}, log) +} + +func ListPluginRepositories(log *zap.SugaredLogger) ([]*commonmodels.PluginRepo, error) { + resp, err := commonrepo.NewPluginRepoColl().List() + if err != nil { + log.Errorf("list Plugin repos error: %v", err) + return resp, e.ErrListPluginRepo.AddDesc(err.Error()) + } + return resp, nil +} + +func DeletePluginRepo(id string, log *zap.SugaredLogger) error { + if err := commonrepo.NewPluginRepoColl().Delete(id); err != nil { + log.Errorf("list Plugin repos error: %v", err) + return e.ErrListPluginRepo.AddDesc(err.Error()) + } + return nil +} + +func ListPluginTemplates(log *zap.SugaredLogger) ([]*commonmodels.PluginTemplate, error) { + resp := []*commonmodels.PluginTemplate{} + repos, err := commonrepo.NewPluginRepoColl().List() + if err != nil { + log.Errorf("list plugin templates error: %v", err) + return resp, e.ErrListPluginRepo.AddDesc(err.Error()) + } + for _, repo := range repos { + for _, template := range repo.PluginTemplates { + template.RepoURL = fmt.Sprintf("%s/%s", repo.RepoOwner, repo.RepoName) + resp = append(resp, template) + } + } + return resp, nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 5a11f13c..9bdb8d23 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -265,9 +265,11 @@ func jobsToJobPreviews(jobs []*commonmodels.JobTask) []*JobTaskPreview { JobType: job.JobType, } switch job.JobType { + case string(config.FreestyleType): + fallthrough case string(config.JobZadigBuild): spec := ZadigBuildJobSpec{} - for _, arg := range job.Properties.Args { + for _, arg := range job.Properties.Envs { if arg.Key == "IMAGE" { spec.Image = arg.Value continue @@ -319,6 +321,8 @@ func jobsToJobPreviews(jobs []*commonmodels.JobTask) []*JobTaskPreview { } } jobPreview.Spec = spec + case string(config.JobPlugin): + jobPreview.Spec = job.Plugin default: jobPreview.Spec = job.Steps } diff --git a/pkg/microservice/jobexecutor/core/service/step/step_archive.go b/pkg/microservice/jobexecutor/core/service/step/step_archive.go index 4707b836..c21422b0 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_archive.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_archive.go @@ -53,62 +53,64 @@ func NewArchiveStep(spec interface{}, workspace string, envs, secretEnvs []strin func (s *ArchiveStep) Run(ctx context.Context) error { start := time.Now() - log.Infof("Start archive %s.", s.spec.FilePath) defer func() { - log.Infof("Archive %s ended. Duration: %.2f seconds", s.spec.FilePath, time.Since(start).Seconds()) + log.Infof("Archive ended. Duration: %.2f seconds", time.Since(start).Seconds()) }() - if s.spec.DestinationPath == "" || s.spec.FilePath == "" { - return nil - } - forcedPathStyle := true - if s.spec.S3.Provider == setting.ProviderSourceAli { - forcedPathStyle = false - } - client, err := s3.NewClient(s.spec.S3.Endpoint, s.spec.S3.Ak, s.spec.S3.Sk, s.spec.S3.Insecure, forcedPathStyle) - if err != nil { - return fmt.Errorf("failed to create s3 client to upload file, err: %s", err) - } + for _, upload := range s.spec.UploadDetail { + log.Infof("Start archive %s.", upload.FilePath) + if upload.DestinationPath == "" || upload.FilePath == "" { + return nil + } + forcedPathStyle := true + if s.spec.S3.Provider == setting.ProviderSourceAli { + forcedPathStyle = false + } + client, err := s3.NewClient(s.spec.S3.Endpoint, s.spec.S3.Ak, s.spec.S3.Sk, s.spec.S3.Insecure, forcedPathStyle) + if err != nil { + return fmt.Errorf("failed to create s3 client to upload file, err: %s", err) + } - envmaps := make(map[string]string) - for _, env := range s.envs { - kv := strings.Split(env, "=") - if len(kv) != 2 { - continue + envmaps := make(map[string]string) + for _, env := range s.envs { + kv := strings.Split(env, "=") + if len(kv) != 2 { + continue + } + envmaps[kv[0]] = kv[1] } - envmaps[kv[0]] = kv[1] - } - for _, secretEnv := range s.secretEnvs { - kv := strings.Split(secretEnv, "=") - if len(kv) != 2 { - continue + for _, secretEnv := range s.secretEnvs { + kv := strings.Split(secretEnv, "=") + if len(kv) != 2 { + continue + } + envmaps[kv[0]] = kv[1] } - envmaps[kv[0]] = kv[1] - } - s.spec.AbsFilePath = fmt.Sprintf("$WORKSPACE/%s", s.spec.FilePath) - s.spec.AbsFilePath = replaceEnvWithValue(s.spec.AbsFilePath, envmaps) - s.spec.DestinationPath = replaceEnvWithValue(s.spec.DestinationPath, envmaps) + upload.AbsFilePath = fmt.Sprintf("$WORKSPACE/%s", upload.FilePath) + upload.AbsFilePath = replaceEnvWithValue(upload.AbsFilePath, envmaps) + upload.DestinationPath = replaceEnvWithValue(upload.DestinationPath, envmaps) - if len(s.spec.S3.Subfolder) > 0 { - s.spec.DestinationPath = strings.TrimLeft(path.Join(s.spec.S3.Subfolder, s.spec.DestinationPath), "/") - } + if len(s.spec.S3.Subfolder) > 0 { + upload.DestinationPath = strings.TrimLeft(path.Join(s.spec.S3.Subfolder, upload.DestinationPath), "/") + } - info, err := os.Stat(s.spec.AbsFilePath) - if err != nil { - return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", s.spec.AbsFilePath, s.spec.DestinationPath, err) - } - // if the given path is a directory - if info.IsDir() { - err := client.UploadDir(s.spec.S3.Bucket, s.spec.AbsFilePath, s.spec.DestinationPath) + info, err := os.Stat(upload.AbsFilePath) if err != nil { - return err + return fmt.Errorf("failed to upload file path [%s] to destination [%s], the error is: %s", upload.AbsFilePath, upload.DestinationPath, err) } - } else { - key := filepath.Join(s.spec.DestinationPath, info.Name()) - err := client.Upload(s.spec.S3.Bucket, s.spec.AbsFilePath, key) - if err != nil { - return err + // if the given path is a directory + if info.IsDir() { + err := client.UploadDir(s.spec.S3.Bucket, upload.AbsFilePath, upload.DestinationPath) + if err != nil { + return err + } + } else { + key := filepath.Join(upload.DestinationPath, info.Name()) + err := client.Upload(s.spec.S3.Bucket, upload.AbsFilePath, key) + if err != nil { + return err + } } } return nil diff --git a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go index 540caebb..81d26395 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go @@ -43,16 +43,23 @@ func NewToolInstallStep(spec interface{}, workspace string, envs, secretEnvs []s func (s *ToolInstallStep) Run(ctx context.Context) error { start := time.Now() - log.Infof("Installing %s %s.", s.spec.Name, s.spec.Version) + for _, tool := range s.spec.Installs { + log.Infof("Installing %s %s.", tool.Name, tool.Version) + } defer func() { - log.Infof("Install %s %s ended. Duration: %.2f seconds.", s.spec.Name, s.spec.Version, time.Since(start).Seconds()) + log.Infof("Install tools ended. Duration: %.2f seconds.", time.Since(start).Seconds()) }() - return s.runIntallationScripts() + for _, tool := range s.spec.Installs { + log.Infof("Installing %s %s.", tool.Name, tool.Version) + return s.runIntallationScripts(tool) + } + + return nil } -func (s *ToolInstallStep) runIntallationScripts() error { - if s.spec == nil { +func (s *ToolInstallStep) runIntallationScripts(tool *step.Tool) error { + if tool == nil { return nil } var ( @@ -65,16 +72,16 @@ func (s *ToolInstallStep) runIntallationScripts() error { scripts = append(scripts, "set -ex") // 获取用户指定环境变量 - s.envs = append(s.envs, Environs(s.spec.Envs)...) + s.envs = append(s.envs, Environs(tool.Envs)...) if openProxy { scripts = append(scripts, proxyScript) } // 如果应用有配置下载路径 - if s.spec.Download != "" { - s.spec.S3Storage.Subfolder = fmt.Sprintf("%s/%s-v%s", config.ConstructCachePath, s.spec.Name, s.spec.Version) - filepath := strings.Split(s.spec.Download, "/") + if tool.Download != "" { + s.spec.S3Storage.Subfolder = fmt.Sprintf("%s/%s-v%s", config.ConstructCachePath, tool.Name, tool.Version) + filepath := strings.Split(tool.Download, "/") fileName := filepath[len(filepath)-1] forcedPathStyle := true if s.spec.S3Storage.Provider == setting.ProviderSourceAli { @@ -93,7 +100,7 @@ func (s *ToolInstallStep) runIntallationScripts() error { // 缓存不存在 if err != nil { - err := httpclient.Download(s.spec.Download, tmpPath) + err := httpclient.Download(tool.Download, tmpPath) if err != nil { return err } @@ -102,22 +109,22 @@ func (s *ToolInstallStep) runIntallationScripts() error { tmpPath, objectKey, ) - fmt.Printf("Package loaded from url: %s\n", s.spec.Download) + fmt.Printf("Package loaded from url: %s\n", tool.Download) } } else { - err := httpclient.Download(s.spec.Download, tmpPath) + err := httpclient.Download(tool.Download, tmpPath) if err != nil { return err } } } - for j, command := range s.spec.Scripts { + for j, command := range tool.Scripts { realCommand := strings.ReplaceAll(command, config.FilepathParam, tmpPath) - s.spec.Scripts[j] = realCommand + tool.Scripts[j] = realCommand } - scripts = append(scripts, s.spec.Scripts...) + scripts = append(scripts, tool.Scripts...) if openProxy { scripts = append(scripts, disProxyScript) diff --git a/pkg/tool/errors/http_errors.go b/pkg/tool/errors/http_errors.go index f4b154e0..81b8a4ff 100644 --- a/pkg/tool/errors/http_errors.go +++ b/pkg/tool/errors/http_errors.go @@ -703,4 +703,11 @@ var ( //----------------------------------------------------------------------------------------------- ErrListHelmReleases = NewHTTPError(6850, "获取release失败") ErrGetHelmCharts = NewHTTPError(6851, "获取chart信息失败") + + //----------------------------------------------------------------------------------------------- + // plugins releated Error Range: 6870 - 6879 + //----------------------------------------------------------------------------------------------- + ErrListPluginRepo = NewHTTPError(6870, "获取plugin仓库失败") + ErrUpsertPluginRepo = NewHTTPError(6871, "更新plugin仓库失败") + ErrDeletePluginRepo = NewHTTPError(6872, "删除plugin仓库失败") ) diff --git a/pkg/types/step/step_archive.go b/pkg/types/step/step_archive.go index 67f4f665..0d8113a0 100644 --- a/pkg/types/step/step_archive.go +++ b/pkg/types/step/step_archive.go @@ -17,9 +17,13 @@ limitations under the License. package step type StepArchiveSpec struct { + UploadDetail []*Upload `bson:"upload_detail" json:"upload_detail" yaml:"upload_detail"` + ObjectStorageID string `bson:"object_storage_id" json:"object_storage_id" yaml:"object_storage_id"` + S3 *S3 `bson:"s3_storage" json:"s3_storage" yaml:"s3_storage"` +} + +type Upload struct { FilePath string `bson:"file_path" json:"file_path" yaml:"file_path"` AbsFilePath string `bson:"abs_file_path" json:"aabs_file_pathk" yaml:"abs_file_path"` DestinationPath string `bson:"dest_path" json:"dest_path" yaml:"dest_path"` - S3StorageID string `bson:"s3_storage_id" json:"s3_storage_id" yaml:"s3_storage_id"` - S3 *S3 `bson:"s3_storage" json:"s3_storage" yaml:"s3_storage"` } diff --git a/pkg/types/step/step_shell.go b/pkg/types/step/step_shell.go index 3818fd12..6ee3cb07 100644 --- a/pkg/types/step/step_shell.go +++ b/pkg/types/step/step_shell.go @@ -17,5 +17,6 @@ limitations under the License. package step type StepShellSpec struct { - Scripts []string `bson:"scripts" json:"scripts" yaml:"scripts"` + Scripts []string `bson:"scripts" json:"scripts" yaml:"scripts,omitempty"` + Script string `bson:"script" json:"script" yaml:"script"` } diff --git a/pkg/types/step/step_tool_install.go b/pkg/types/step/step_tool_install.go index 9288c61d..bd39c7a5 100644 --- a/pkg/types/step/step_tool_install.go +++ b/pkg/types/step/step_tool_install.go @@ -1,11 +1,15 @@ package step type StepToolInstallSpec struct { - Name string `bson:"name" json:"name" yaml:"name"` - Version string `bson:"version" json:"version" yaml:"version"` - Scripts []string `bson:"scripts" json:"scripts" yaml:"scripts"` - BinPath string `bson:"bin_path" json:"bin_path" yaml:"bin_path"` - Envs []string `bson:"envs" json:"envs" yaml:"envs"` - Download string `bson:"download" json:"download" yaml:"download"` - S3Storage *S3 `bson:"s3_storage" json:"s3_storage" yaml:"s3_storage"` + Installs []*Tool `bson:"installs" json:"installs" yaml:"installs"` + S3Storage *S3 `bson:"s3_storage" json:"s3_storage" yaml:"s3_storage"` +} + +type Tool struct { + Name string `bson:"name" json:"name" yaml:"name"` + Version string `bson:"version" json:"version" yaml:"version"` + Scripts []string `bson:"scripts" json:"scripts" yaml:"scripts"` + BinPath string `bson:"bin_path" json:"bin_path" yaml:"bin_path"` + Envs []string `bson:"envs" json:"envs" yaml:"envs"` + Download string `bson:"download" json:"download" yaml:"download"` } -- Gitee From 5a7b20db8bb4495379d0a3088530610007d297b6 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 10:19:38 +0800 Subject: [PATCH 2586/2647] fix for no request spec Signed-off-by: guoyu --- pkg/setting/types.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/setting/types.go b/pkg/setting/types.go index 46b08787..779f3c11 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -36,10 +36,10 @@ const ( ) type RequestSpec struct { - CpuLimit int `bson:"cpu_limit" json:"cpu_limit"` - MemoryLimit int `bson:"memory_limit" json:"memory_limit"` - CpuReq int `bson:"cpu_req,omitempty" json:"cpu_req,omitempty"` - MemoryReq int `bson:"memory_req,omitempty" json:"memory_req,omitempty"` + CpuLimit int `bson:"cpu_limit" json:"cpu_limit" yaml:"cpu_limit"` + MemoryLimit int `bson:"memory_limit" json:"memory_limit" yaml:"memory_limit"` + CpuReq int `bson:"cpu_req,omitempty" json:"cpu_req,omitempty" yaml:"cpu_req,omitempty"` + MemoryReq int `bson:"memory_req,omitempty" json:"memory_req,omitempty" yaml:"memory_req,omitempty"` } var ( -- Gitee From ad2c73108bc256593b0b2e035030fdeeeec0a7ab Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 14:39:46 +0800 Subject: [PATCH 2587/2647] move plugin config file to zadig itself Signed-off-by: guoyu --- .../common/repository/models/plugin_repo.go | 21 ++-- pkg/microservice/aslan/core/service.go | 2 + .../core/workflow/handler/plugin_repo.go | 7 -- .../aslan/core/workflow/handler/router.go | 1 - .../workflow/service/workflow/plugin_repo.go | 102 ++++++++++-------- .../hello-world/v1.0.0/hello-world.yaml | 15 +++ 6 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml diff --git a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go index 3bd83b97..fd1fab77 100644 --- a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go +++ b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go @@ -32,18 +32,21 @@ type PluginRepo struct { CodehostID int `bson:"codehost_id" json:"codehost_id" yaml:"codehost_id"` UpdateTime int64 `bson:"update_time" json:"update_time" yaml:"update_time"` PluginTemplates []*PluginTemplate `bson:"plugin_templates" json:"plugin_templates" yaml:"plugin_templates"` + Status string `bson:"status" json:"status" yaml:"status"` + Error string `bson:"error" json:"error" yaml:"error"` } type PluginTemplate struct { - Name string `bson:"name" json:"name" yaml:"name"` - RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` - Version string `bson:"version" json:"version" yaml:"version"` - Image string `bson:"image" json:"image" yaml:"image"` - Args []string `bson:"args" json:"args" yaml:"args"` - Cmds []string `bson:"cmds" json:"cmds" yaml:"cmds"` - Envs []*Env `bson:"envs" json:"envs" yaml:"envs"` - Inputs []*Params `bson:"inputs" json:"inputs" yaml:"inputs"` - Outputs []*Params `bson:"outputs" json:"outputs" yaml:"outputs"` + Name string `bson:"name" json:"name" yaml:"name"` + IsOffical bool `bson:"is_offical" json:"is_offical" yaml:"is_offical"` + RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` + Version string `bson:"version" json:"version" yaml:"version"` + Image string `bson:"image" json:"image" yaml:"image"` + Args []string `bson:"args" json:"args" yaml:"args"` + Cmds []string `bson:"cmds" json:"cmds" yaml:"cmds"` + Envs []*Env `bson:"envs" json:"envs" yaml:"envs"` + Inputs []*Params `bson:"inputs" json:"inputs" yaml:"inputs"` + Outputs []*Params `bson:"outputs" json:"outputs" yaml:"outputs"` } type Env struct { diff --git a/pkg/microservice/aslan/core/service.go b/pkg/microservice/aslan/core/service.go index b31939f6..37cd9584 100644 --- a/pkg/microservice/aslan/core/service.go +++ b/pkg/microservice/aslan/core/service.go @@ -124,6 +124,8 @@ func Start(ctx context.Context) { systemservice.SetProxyConfig() workflowservice.InitPipelineController() + // update offical plugins + workflowservice.UpdateOfficalPluginRepository(log.SugaredLogger()) workflowcontroller.InitWorkflowController() // 如果集群环境所属的项目不存在,则删除此集群环境 environmentservice.CleanProducts() diff --git a/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go b/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go index 980f63b7..9c66c515 100644 --- a/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go +++ b/pkg/microservice/aslan/core/workflow/handler/plugin_repo.go @@ -23,13 +23,6 @@ import ( internalhandler "github.com/koderover/zadig/pkg/shared/handler" ) -func UpdateOfficalPluginRepository(c *gin.Context) { - ctx := internalhandler.NewContext(c) - defer func() { internalhandler.JSONResponse(c, ctx) }() - - ctx.Err = workflow.UpdateOfficalPluginRepository(ctx.Logger) -} - func ListPluginTemplates(c *gin.Context) { ctx := internalhandler.NewContext(c) defer func() { internalhandler.JSONResponse(c, ctx) }() diff --git a/pkg/microservice/aslan/core/workflow/handler/router.go b/pkg/microservice/aslan/core/workflow/handler/router.go index 1a8fb603..1876d3c4 100644 --- a/pkg/microservice/aslan/core/workflow/handler/router.go +++ b/pkg/microservice/aslan/core/workflow/handler/router.go @@ -197,7 +197,6 @@ func (*Router) Inject(router *gin.RouterGroup) { // --------------------------------------------------------------------------------------- plugin := router.Group("plugin") { - plugin.POST("/offical", UpdateOfficalPluginRepository) plugin.GET("/template", ListPluginTemplates) } diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go b/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go index 4d663eba..dd5adc84 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/plugin_repo.go @@ -17,13 +17,12 @@ limitations under the License. package workflow import ( + "embed" "fmt" - "io/ioutil" + "io/fs" "os" "path" - "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" "github.com/google/uuid" "go.uber.org/zap" "gopkg.in/yaml.v3" @@ -34,55 +33,75 @@ import ( ) const ( - OfficalRepoOwner = "dianqihanwangzi" - OfficalRepoName = "zadig-plugins" + OfficalRepoOwner = "koderover" + OfficalRepoName = "zadig" OfficalRepoURL = "https://github.com/" + OfficalRepoOwner + "/" + OfficalRepoName OfficalBranch = "main" ) -func UpsertPluginRepository(args *commonmodels.PluginRepo, log *zap.SugaredLogger) error { +//go:embed plugins +var officalPluginRepoFiles embed.FS + +func UpsertUserPluginRepository(args *commonmodels.PluginRepo, log *zap.SugaredLogger) error { + args.IsOffical = false args.PluginTemplates = []*commonmodels.PluginTemplate{} - // TODO: git clone and checkout the plugin checkoutPath := fmt.Sprintf("/tmp/%s", uuid.New().String()) defer os.RemoveAll(checkoutPath) - if args.IsOffical { - args.RepoOwner = OfficalRepoOwner - args.RepoName = OfficalRepoName - args.Branch = OfficalBranch - args.RepoURL = OfficalRepoURL - } - if _, err := git.PlainClone(checkoutPath, false, &git.CloneOptions{ - URL: args.RepoURL, - Depth: 1, - ReferenceName: plumbing.NewBranchReferenceName(args.Branch), - }); err != nil { - log.Errorf("clone plugin repo error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + // TODO: git clone and checkout the plugin + plugins, err := loadPluginRepoInfos(checkoutPath, args.IsOffical, os.ReadDir, os.ReadFile) + if err != nil { + errMsg := fmt.Sprintf("load plugin from user user repo error: %s", err) + log.Error(errMsg) + return e.ErrUpsertPluginRepo.AddDesc(errMsg) } + args.PluginTemplates = plugins + return commonrepo.NewPluginRepoColl().Upsert(args) +} - dirs, err := ioutil.ReadDir(checkoutPath) +func UpdateOfficalPluginRepository(log *zap.SugaredLogger) { + officalPluginRepo := &commonmodels.PluginRepo{ + RepoOwner: OfficalRepoOwner, + RepoName: OfficalRepoName, + Branch: OfficalBranch, + RepoURL: OfficalRepoURL, + IsOffical: true, + } + plugins, err := loadPluginRepoInfos("plugins", officalPluginRepo.IsOffical, officalPluginRepoFiles.ReadDir, officalPluginRepoFiles.ReadFile) if err != nil { - log.Errorf("loop plugin repo error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + log.Errorf("load offical plugin repo error: %v", err) + return } + officalPluginRepo.PluginTemplates = plugins + if err := commonrepo.NewPluginRepoColl().Upsert(officalPluginRepo); err != nil { + log.Errorf("update offical plugin repo error: %v", err) + return + } +} +type readDir func(name string) ([]fs.DirEntry, error) +type readFile func(name string) ([]byte, error) + +func loadPluginRepoInfos(baseDir string, isOffical bool, readDir readDir, readFile readFile) ([]*commonmodels.PluginTemplate, error) { + resp := []*commonmodels.PluginTemplate{} + dirs, err := readDir(baseDir) + if err != nil { + return resp, fmt.Errorf("loop plugin repo error: %v", err) + } for _, dir := range dirs { if !dir.IsDir() { continue } - subDirs, err := ioutil.ReadDir(path.Join(checkoutPath, dir.Name())) + subDirs, err := readDir(path.Join(baseDir, dir.Name())) if err != nil { - log.Errorf("loop plugin repo dirs error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + return resp, fmt.Errorf("loop plugin repo dirs error: %v", err) } for _, subDir := range subDirs { if !subDir.IsDir() { continue } - files, err := ioutil.ReadDir(path.Join(checkoutPath, dir.Name(), subDir.Name())) + files, err := readDir(path.Join(baseDir, dir.Name(), subDir.Name())) if err != nil { - log.Errorf("loop plugin repo sub dirs error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + return resp, fmt.Errorf("loop plugin repo sub dirs error: %v", err) } for _, file := range files { if file.IsDir() { @@ -91,31 +110,20 @@ func UpsertPluginRepository(args *commonmodels.PluginRepo, log *zap.SugaredLogge if file.Name() != dir.Name()+".yaml" { continue } - yamlFile, err := os.Open(path.Join(checkoutPath, dir.Name(), subDir.Name(), file.Name())) - if err != nil { - log.Errorf("load yaml files error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) - } - defer yamlFile.Close() - content, err := ioutil.ReadAll(yamlFile) + yamlFilebyte, err := readFile(path.Join(baseDir, dir.Name(), subDir.Name(), file.Name())) if err != nil { - log.Errorf("read yaml files error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + return resp, fmt.Errorf("read yaml files error: %v", err) } pluginTemplate := &commonmodels.PluginTemplate{} - if err := yaml.Unmarshal(content, pluginTemplate); err != nil { - log.Errorf("unmarshal yaml files error: %v", err) - return e.ErrUpsertPluginRepo.AddDesc(err.Error()) + if err := yaml.Unmarshal(yamlFilebyte, pluginTemplate); err != nil { + return resp, fmt.Errorf("unmarshal yaml files error: %v", err) } - args.PluginTemplates = append(args.PluginTemplates, pluginTemplate) + pluginTemplate.IsOffical = isOffical + resp = append(resp, pluginTemplate) } } } - return commonrepo.NewPluginRepoColl().Upsert(args) -} - -func UpdateOfficalPluginRepository(log *zap.SugaredLogger) error { - return UpsertPluginRepository(&commonmodels.PluginRepo{IsOffical: true}, log) + return resp, nil } func ListPluginRepositories(log *zap.SugaredLogger) ([]*commonmodels.PluginRepo, error) { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml b/pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml new file mode 100644 index 00000000..2157d04c --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml @@ -0,0 +1,15 @@ +name: hello-world +version: "v0.0.1" +descrption: "hello world" +image: alpine/curl:3.14 +cmds: +- "/bin/sh" +- "-c" +args: +- "echo $(CONTENT)" + +inputs: +- name: CONTENT + description: "content to print" + type: string + default: "hello world" \ No newline at end of file -- Gitee From 4848ab20ebe134b14f8439052e9a45885aa5279d Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 22 Jul 2022 15:30:35 +0800 Subject: [PATCH 2588/2647] debug Signed-off-by: Min Min --- pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql | 2 +- pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql | 2 +- pkg/microservice/user/core/service/init/mysql.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql index 197aba07..ec2ad6da 100644 --- a/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql +++ b/pkg/cli/upgradeassistant/internal/repository/orm/addmysql.sql @@ -1,3 +1,3 @@ -use %s; +use `%s`; ALTER TABLE `user` ADD `api_token` varchar(1024) NOT NULL DEFAULT '' COMMENT 'openAPIToken'; \ No newline at end of file diff --git a/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql index d973c425..9d0db576 100644 --- a/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql +++ b/pkg/cli/upgradeassistant/internal/repository/orm/dropmysql.sql @@ -1,4 +1,4 @@ -use %s; +use `%s`; LOCK TABLES `user` WRITE; ALTER TABLE `user` DROP COLUMN `api_token`; diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql index cd01afa7..ab99b4d3 100644 --- a/pkg/microservice/user/core/service/init/mysql.sql +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -1,5 +1,5 @@ CREATE DATABASE IF NOT EXISTS %s; -use %s; +use `%s`; CREATE TABLE IF NOT EXISTS `user_login`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL DEFAULT '0' COMMENT '用户id', -- Gitee From 9e4ce2177940e7315423338375439d87501a2926 Mon Sep 17 00:00:00 2001 From: Min Min Date: Fri, 22 Jul 2022 15:35:41 +0800 Subject: [PATCH 2589/2647] fix possible error Signed-off-by: Min Min --- pkg/microservice/user/core/service/init/mysql.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/user/core/service/init/mysql.sql b/pkg/microservice/user/core/service/init/mysql.sql index ab99b4d3..a2fcd0a8 100644 --- a/pkg/microservice/user/core/service/init/mysql.sql +++ b/pkg/microservice/user/core/service/init/mysql.sql @@ -1,4 +1,4 @@ -CREATE DATABASE IF NOT EXISTS %s; +CREATE DATABASE IF NOT EXISTS `%s`; use `%s`; CREATE TABLE IF NOT EXISTS `user_login`( `id` bigint(20) NOT NULL AUTO_INCREMENT, -- Gitee From 7a94515f8e83b24705e8f639094914d482812ba0 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 17:33:50 +0800 Subject: [PATCH 2590/2647] add default envs for freestyle job Signed-off-by: guoyu --- .../core/workflow/service/workflow/job/job.go | 33 +++++++++++++++++++ .../service/workflow/job/job_build.go | 32 ++---------------- .../service/workflow/job/job_freestyle.go | 27 +++++++++++++++ .../service/workflow/workflow_task_v4.go | 32 +++++++++++++++++- 4 files changed, 94 insertions(+), 30 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go index aff52e2e..33c568e1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -18,10 +18,12 @@ package job import ( "fmt" + "strconv" "strings" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/types" ) type JobCtl interface { @@ -79,3 +81,34 @@ func jobNameFormat(jobName string) string { jobName = strings.Trim(jobName, "-") return jobName } + +func getBuildsVariables(repos []*types.Repository) []*commonmodels.KeyVal { + ret := make([]*commonmodels.KeyVal, 0) + for index, repo := range repos { + + repoNameIndex := fmt.Sprintf("REPONAME_%d", index) + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf(repoNameIndex), Value: repo.RepoName, IsCredential: false}) + + repoName := strings.Replace(repo.RepoName, "-", "_", -1) + + repoIndex := fmt.Sprintf("REPO_%d", index) + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf(repoIndex), Value: repoName, IsCredential: false}) + + if len(repo.Branch) > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false}) + } + + if len(repo.Tag) > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false}) + } + + if repo.PR > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false}) + } + + if len(repo.CommitID) > 0 { + ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_COMMIT_ID", repoName), Value: repo.CommitID, IsCredential: false}) + } + } + return ret +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 10aa7edb..0affda1b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -20,7 +20,6 @@ import ( "fmt" "os" "path" - "strconv" "strings" configbase "github.com/koderover/zadig/pkg/config" @@ -142,7 +141,7 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { jobTask.Properties.CacheDirType = buildInfo.CacheDirType jobTask.Properties.CacheUserDir = buildInfo.CacheUserDir } - jobTask.Properties.Envs = append(jobTask.Properties.Envs, getJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) + jobTask.Properties.Envs = append(jobTask.Properties.Envs, getBuildJobVariables(build, taskID, j.workflow.Project, j.workflow.Name, j.spec.DockerRegistryID)...) if jobTask.Properties.CacheEnable && jobTask.Properties.Cache.MediumType == types.NFSMedium { jobTask.Properties.CacheUserDir = renderEnv(jobTask.Properties.CacheUserDir, jobTask.Properties.Envs) @@ -313,34 +312,9 @@ func replaceWrapLine(script string) string { ), "\r", "\n", -1) } -func getJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, workflowName, dockerRegistryID string) []*commonmodels.KeyVal { +func getBuildJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, workflowName, dockerRegistryID string) []*commonmodels.KeyVal { ret := make([]*commonmodels.KeyVal, 0) - for index, repo := range build.Repos { - - repoNameIndex := fmt.Sprintf("REPONAME_%d", index) - ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf(repoNameIndex), Value: repo.RepoName, IsCredential: false}) - - repoName := strings.Replace(repo.RepoName, "-", "_", -1) - - repoIndex := fmt.Sprintf("REPO_%d", index) - ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf(repoIndex), Value: repoName, IsCredential: false}) - - if len(repo.Branch) > 0 { - ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_BRANCH", repoName), Value: repo.Branch, IsCredential: false}) - } - - if len(repo.Tag) > 0 { - ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_TAG", repoName), Value: repo.Tag, IsCredential: false}) - } - - if repo.PR > 0 { - ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_PR", repoName), Value: strconv.Itoa(repo.PR), IsCredential: false}) - } - - if len(repo.CommitID) > 0 { - ret = append(ret, &commonmodels.KeyVal{Key: fmt.Sprintf("%s_COMMIT_ID", repoName), Value: repo.CommitID, IsCredential: false}) - } - } + ret = append(ret, getBuildsVariables(build.Repos)...) reg, err := commonrepo.NewRegistryNamespaceColl().Find(&commonrepo.FindRegOps{ID: dockerRegistryID}) if err != nil { log.Errorf("find docker registry by ID %s error: %v", dockerRegistryID, err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go index 8d17ef2c..a09e7eac 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go @@ -19,8 +19,12 @@ package job import ( "fmt" + configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + "github.com/koderover/zadig/pkg/tool/log" + "github.com/koderover/zadig/pkg/types" + stepspec "github.com/koderover/zadig/pkg/types/step" steptypes "github.com/koderover/zadig/pkg/types/step" ) @@ -92,6 +96,7 @@ func (j *FreeStyleJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { Properties: *j.spec.Properties, Steps: stepsToStepTasks(j.spec.Steps), } + jobTask.Properties.Envs = getfreestyleJobVariables(jobTask.Steps, taskID, j.workflow.Project, j.workflow.Name) return []*commonmodels.JobTask{jobTask}, nil } @@ -107,3 +112,25 @@ func stepsToStepTasks(step []*commonmodels.Step) []*commonmodels.StepTask { } return resp } + +func getfreestyleJobVariables(steps []*commonmodels.StepTask, taskID int64, project, workflowName string) []*commonmodels.KeyVal { + ret := []*commonmodels.KeyVal{} + repos := []*types.Repository{} + for _, step := range steps { + if step.StepType != config.StepGit { + continue + } + stepSpec := &stepspec.StepGitSpec{} + if err := commonmodels.IToi(step.Spec, stepSpec); err != nil { + log.Errorf("failed to convert step spec error: %v", err) + continue + } + repos = append(repos, stepSpec.Repos...) + } + ret = append(ret, getBuildsVariables(repos)...) + + ret = append(ret, &commonmodels.KeyVal{Key: "TASK_ID", Value: fmt.Sprintf("%d", taskID), IsCredential: false}) + buildURL := fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/custom/%s/%d", configbase.SystemAddress(), project, workflowName, taskID) + ret = append(ret, &commonmodels.KeyVal{Key: "BUILD_URL", Value: buildURL, IsCredential: false}) + return ret +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 9bdb8d23..ec5fe6d5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -141,12 +141,20 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z } workflowTask.Stages = append(workflowTask.Stages, stageTask) for _, job := range stage.Jobs { + // TODO: move this logic to job controller if job.JobType == config.JobZadigBuild { if err := setZadigBuildRepos(job, log); err != nil { - log.Errorf("set build info error: %v", err) + log.Errorf("zadig build job set build info error: %v", err) return resp, e.ErrCreateTask.AddDesc(err.Error()) } } + if job.JobType == config.JobFreestyle { + if err := setFreeStyleRepos(job, log); err != nil { + log.Errorf("freestyle job set build info error: %v", err) + return resp, e.ErrCreateTask.AddDesc(err.Error()) + } + } + jobs, err := jobctl.ToJobs(job, workflow, nextTaskID) if err != nil { log.Errorf("cannot create workflow %s, the error is: %v", workflow.Name, err) @@ -345,6 +353,28 @@ func setZadigBuildRepos(job *commonmodels.Job, logger *zap.SugaredLogger) error return nil } +func setFreeStyleRepos(job *commonmodels.Job, logger *zap.SugaredLogger) error { + spec := &commonmodels.FreestyleJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + return err + } + for _, step := range spec.Steps { + if step.StepType != config.StepGit { + continue + } + stepSpec := &stepspec.StepGitSpec{} + if err := commonmodels.IToi(step.Spec, stepSpec); err != nil { + return err + } + if err := setManunalBuilds(stepSpec.Repos, stepSpec.Repos, logger); err != nil { + return err + } + step.Spec = stepSpec + } + job.Spec = spec + return nil +} + func workflowTaskLint(workflowTask *commonmodels.WorkflowTask, logger *zap.SugaredLogger) error { if len(workflowTask.Stages) <= 0 { errMsg := fmt.Sprintf("no stage found in workflow task: %s,taskID: %d", workflowTask.WorkflowName, workflowTask.TaskID) -- Gitee From 7a3af26e88c7d18f9ea159f4abfbc6e89ba3e50a Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 17:36:48 +0800 Subject: [PATCH 2591/2647] change func name Signed-off-by: guoyu --- .../aslan/core/workflow/service/workflow/job/job.go | 2 +- .../aslan/core/workflow/service/workflow/job/job_build.go | 2 +- .../aslan/core/workflow/service/workflow/job/job_freestyle.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go index 33c568e1..2178dfcb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job.go @@ -82,7 +82,7 @@ func jobNameFormat(jobName string) string { return jobName } -func getBuildsVariables(repos []*types.Repository) []*commonmodels.KeyVal { +func getReposVariables(repos []*types.Repository) []*commonmodels.KeyVal { ret := make([]*commonmodels.KeyVal, 0) for index, repo := range repos { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 0affda1b..8a9f98cc 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -314,7 +314,7 @@ func replaceWrapLine(script string) string { func getBuildJobVariables(build *commonmodels.ServiceAndBuild, taskID int64, project, workflowName, dockerRegistryID string) []*commonmodels.KeyVal { ret := make([]*commonmodels.KeyVal, 0) - ret = append(ret, getBuildsVariables(build.Repos)...) + ret = append(ret, getReposVariables(build.Repos)...) reg, err := commonrepo.NewRegistryNamespaceColl().Find(&commonrepo.FindRegOps{ID: dockerRegistryID}) if err != nil { log.Errorf("find docker registry by ID %s error: %v", dockerRegistryID, err) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go index a09e7eac..b88cee97 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go @@ -127,7 +127,7 @@ func getfreestyleJobVariables(steps []*commonmodels.StepTask, taskID int64, proj } repos = append(repos, stepSpec.Repos...) } - ret = append(ret, getBuildsVariables(repos)...) + ret = append(ret, getReposVariables(repos)...) ret = append(ret, &commonmodels.KeyVal{Key: "TASK_ID", Value: fmt.Sprintf("%d", taskID), IsCredential: false}) buildURL := fmt.Sprintf("%s/v1/projects/detail/%s/pipelines/custom/%s/%d", configbase.SystemAddress(), project, workflowName, taskID) -- Gitee From 0e665bb7c37bb223c08da84388db2666ebf80766 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 18:14:25 +0800 Subject: [PATCH 2592/2647] add self defined env Signed-off-by: guoyu --- .../aslan/core/workflow/service/workflow/job/job_freestyle.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go index b88cee97..cd28d490 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go @@ -96,7 +96,7 @@ func (j *FreeStyleJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { Properties: *j.spec.Properties, Steps: stepsToStepTasks(j.spec.Steps), } - jobTask.Properties.Envs = getfreestyleJobVariables(jobTask.Steps, taskID, j.workflow.Project, j.workflow.Name) + jobTask.Properties.Envs = append(jobTask.Properties.Envs, getfreestyleJobVariables(jobTask.Steps, taskID, j.workflow.Project, j.workflow.Name)...) return []*commonmodels.JobTask{jobTask}, nil } -- Gitee From d0bee0823921361aec73b31791199c1efe46c0bc Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 18:29:29 +0800 Subject: [PATCH 2593/2647] go mod tidy Signed-off-by: guoyu --- go.mod | 1 - go.sum | 30 ------------------------------ 2 files changed, 31 deletions(-) diff --git a/go.mod b/go.mod index 7bfe3441..994f576c 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/gin-contrib/sse v0.1.0 github.com/gin-gonic/gin v1.7.2 github.com/go-co-op/gocron v1.13.0 - github.com/go-git/go-git/v5 v5.4.2 // indirect github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-resty/resty/v2 v2.6.0 github.com/go-sql-driver/mysql v1.6.0 diff --git a/go.sum b/go.sum index 096cdc3a..5a9dc0a0 100644 --- a/go.sum +++ b/go.sum @@ -173,8 +173,6 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8 github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -191,8 +189,6 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -222,7 +218,6 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= @@ -520,8 +515,6 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7fo github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -573,7 +566,6 @@ github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/ github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= @@ -585,14 +577,6 @@ github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E github.com/go-critic/go-critic v0.4.3/go.mod h1:j4O3D4RoIwRqlZw5jJpx0BNfXWWbpcJoKu5cYSe4YmQ= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1013,8 +997,6 @@ github.com/jaguilar/vt100 v0.0.0-20150826170717-2703a27b14ea/go.mod h1:QMdK4dGB3 github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3 h1:s2ARkZWJfkyjrUi8Dj0mMbc1inxeOnIrO9zc3oQX8iU= github.com/jasonlvhit/gocron v0.0.0-20171226191223-3c914c8681c3/go.mod h1:rwi/esz/h+4oWLhbWWK7f6dtmgLzxeZhnwGr7MCsTNk= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= @@ -1068,8 +1050,6 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.15.8 h1:7+rWAZPn9zuRxaIqqT8Ohs2Q2Ac0msBqwRdxNCr2VVs= github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1144,7 +1124,6 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -1194,7 +1173,6 @@ github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMF github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -1612,8 +1590,6 @@ github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfD github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/xanzy/go-gitlab v0.50.0 h1:t7IoYTrnLSbdEZN7d8X/5zcr+ZM4TZQ2mXa8MqWlAZQ= github.com/xanzy/go-gitlab v0.50.0/go.mod h1:Q+hQhV508bDPoBijv7YjK/Lvlb4PhVhJdKqXVQrUoAE= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= @@ -1734,7 +1710,6 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -1762,7 +1737,6 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1870,7 +1844,6 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -2016,7 +1989,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2352,8 +2324,6 @@ gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -- Gitee From 7e699b351a1206335e379f9ed859e0e44087276e Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 18:38:14 +0800 Subject: [PATCH 2594/2647] remove debug code Signed-off-by: guoyu --- .../workflowcontroller/jobcontroller/job_plugin.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go index 2b3b4d41..354f8d38 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go @@ -130,11 +130,11 @@ func (c *PluginJobCtl) Complete(ctx context.Context) { // 清理用户取消和超时的任务 defer func() { - // go func() { - // if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { - // c.logger.Error(err) - // } - // }() + go func() { + if err := ensureDeleteJob(c.job.Properties.Namespace, jobLabel, c.kubeclient); err != nil { + c.logger.Error(err) + } + }() }() // get job outputs info from pod terminate message. -- Gitee From 102c69030a638664841a6f441f129c5c30cdc741 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 22 Jul 2022 18:45:16 +0800 Subject: [PATCH 2595/2647] fix import issue Signed-off-by: guoyu --- .../workflowcontroller/jobcontroller/job_plugin.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go index 354f8d38..82246d22 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go @@ -22,16 +22,17 @@ import ( "strings" "time" + "go.uber.org/zap" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + crClient "sigs.k8s.io/controller-runtime/pkg/client" + zadigconfig "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/setting" krkubeclient "github.com/koderover/zadig/pkg/tool/kube/client" "github.com/koderover/zadig/pkg/tool/kube/updater" - "go.uber.org/zap" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - crClient "sigs.k8s.io/controller-runtime/pkg/client" ) type PluginJobCtl struct { -- Gitee From 9e380cd69a8acc98353c11860792187a9a53360c Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 25 Jul 2022 10:48:29 +0800 Subject: [PATCH 2596/2647] add plugin template description Signed-off-by: guoyu --- .../common/repository/models/plugin_repo.go | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go index fd1fab77..0846f3cc 100644 --- a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go +++ b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go @@ -37,16 +37,17 @@ type PluginRepo struct { } type PluginTemplate struct { - Name string `bson:"name" json:"name" yaml:"name"` - IsOffical bool `bson:"is_offical" json:"is_offical" yaml:"is_offical"` - RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` - Version string `bson:"version" json:"version" yaml:"version"` - Image string `bson:"image" json:"image" yaml:"image"` - Args []string `bson:"args" json:"args" yaml:"args"` - Cmds []string `bson:"cmds" json:"cmds" yaml:"cmds"` - Envs []*Env `bson:"envs" json:"envs" yaml:"envs"` - Inputs []*Params `bson:"inputs" json:"inputs" yaml:"inputs"` - Outputs []*Params `bson:"outputs" json:"outputs" yaml:"outputs"` + Name string `bson:"name" json:"name" yaml:"name"` + IsOffical bool `bson:"is_offical" json:"is_offical" yaml:"is_offical"` + Description string `bson:"description" json:"description" yaml:"description"` + RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` + Version string `bson:"version" json:"version" yaml:"version"` + Image string `bson:"image" json:"image" yaml:"image"` + Args []string `bson:"args" json:"args" yaml:"args"` + Cmds []string `bson:"cmds" json:"cmds" yaml:"cmds"` + Envs []*Env `bson:"envs" json:"envs" yaml:"envs"` + Inputs []*Params `bson:"inputs" json:"inputs" yaml:"inputs"` + Outputs []*Params `bson:"outputs" json:"outputs" yaml:"outputs"` } type Env struct { -- Gitee From cf38dc35d140c4767b66ddf940dce8a3ee42940a Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 25 Jul 2022 11:46:41 +0800 Subject: [PATCH 2597/2647] fix only install first tool Signed-off-by: guoyu --- .../aslan/core/common/repository/models/plugin_repo.go | 1 + .../service/workflowcontroller/jobcontroller/job_plugin.go | 1 - .../service/workflowcontroller/jobcontroller/kubernetes.go | 2 +- .../jobexecutor/core/service/step/step_tool_install.go | 4 +++- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go index 0846f3cc..11640d42 100644 --- a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go +++ b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go @@ -24,6 +24,7 @@ type PluginRepo struct { ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty" yaml:"source,omitempty"` IsOffical bool `bson:"is_offical" json:"is_offical" yaml:"is_offical"` RepoURL string `bson:"repo_url" json:"repo_url" yaml:"-"` + RepoNamespace string `bson:"repo_namespace" json:"repo_namespace" yaml:"repo_namespace"` Source string `bson:"source,omitempty" json:"source,omitempty" yaml:"source,omitempty"` RepoOwner string `bson:"repo_owner" json:"repo_owner" yaml:"repo_owner"` RepoName string `bson:"repo_name" json:"repo_name" yaml:"repo_name"` diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go index 82246d22..983de426 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/job_plugin.go @@ -112,7 +112,6 @@ func (c *PluginJobCtl) Run(ctx context.Context) { return } c.logger.Infof("succeed to create job %s", c.jobName) - } func (c *PluginJobCtl) Wait(ctx context.Context) { diff --git a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go index bc5e1830..3f764d72 100644 --- a/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go +++ b/pkg/microservice/aslan/core/common/service/workflowcontroller/jobcontroller/kubernetes.go @@ -544,7 +544,7 @@ func waitPlainJobEnd(ctx context.Context, taskTimeout int, namespace, jobName st time.Sleep(time.Second) } - // 等待job 运行结束 + // wait for the job to end. xl.Infof("wait job to end: %s %s", namespace, jobName) for { select { diff --git a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go index 81d26395..19fb2e4e 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go @@ -52,7 +52,9 @@ func (s *ToolInstallStep) Run(ctx context.Context) error { for _, tool := range s.spec.Installs { log.Infof("Installing %s %s.", tool.Name, tool.Version) - return s.runIntallationScripts(tool) + if err := s.runIntallationScripts(tool); err != nil { + return err + } } return nil -- Gitee From 61bd4002b3bca588f6131b95121c247285e0c37e Mon Sep 17 00:00:00 2001 From: guoyu Date: Mon, 25 Jul 2022 12:14:05 +0800 Subject: [PATCH 2598/2647] fix extra log Signed-off-by: guoyu --- .../jobexecutor/core/service/step/step_tool_install.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go index 19fb2e4e..c341e026 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_tool_install.go @@ -43,9 +43,7 @@ func NewToolInstallStep(spec interface{}, workspace string, envs, secretEnvs []s func (s *ToolInstallStep) Run(ctx context.Context) error { start := time.Now() - for _, tool := range s.spec.Installs { - log.Infof("Installing %s %s.", tool.Name, tool.Version) - } + log.Infof("Installing tools.") defer func() { log.Infof("Install tools ended. Duration: %.2f seconds.", time.Since(start).Seconds()) }() -- Gitee From 027825b0daf99e60837a0507064f8a6ec690416e Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Mon, 25 Jul 2022 21:06:43 +0800 Subject: [PATCH 2599/2647] revise System-Architecture-Overview markdown file (#1829) Signed-off-by: fansi Co-authored-by: fansi --- System-Architecture-Overview.md | Bin 452900 -> 1509 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/System-Architecture-Overview.md b/System-Architecture-Overview.md index 33df9cca9c5f905f2e9729ed20103b942e16be46..9c9414992911e9a950c1b7af560cf2d3b4c5779d 100644 GIT binary patch literal 1509 zcmaJ>(QeZ~5PbJnEb2=uC62@s;w>bol(sZb5E4~|&i2N+#=bRoXQ%0>@GblkzrfsG zQc_VNS@PX^XLfdH&qwr4Rl26@t@GT{)Rrslx%9ndx*FIHnm1W?bo5+6mc6(+1bnZw zs;z2okMl`n9VgcDVNmXd`Xn-5Ds5;cZ6}6}cUktuv86NP*)CPdzkmJA#`F->jXSYk zH8@FPH(XG;2O?-K6}v8BQUt!X%2d>7!{kL8hp_bNGj?n3K{mJ&KTX^`X|zgMUP03*pl9(Ix7P<)}e`OrsOyHq6dpU8-rQ=pF2K5G=JWb6Qxr!zho4&7eV+)a+0r+K62*8`+Ei)J-)am+rt{ z>7;NA0yC2Nm3I4#LCGR|2Pu{Eik$Qo9hN1*@Mv1{mMquW`BXQRXb0PXo{3rN8t7fl zGVc3-;=b-Q(x={_eE?)cW7;U&RaOt7efu&{CB~RXl=M5KRviot6VzFTOtBH1&JU3` zlIOv|)cV*EZEASO&SODdxwXq9eoyvahALF8i{9h<9YEt%bBD-u`y7}$Z$9Ja9bF22<6>35*tCB9NfOD}*P0So7if zBaU0l5eArk^xSin&4bAc%-(KbFU8a{e}lkaf1zBuU5^tOPD7H*?e)it5XJXFF+AUS zJbHrbF_d|ho%aP>!{|B9njSyoX!!oe^+*n>j__}A9^NWj(fKR%BLQ5vHZ6A2S2W=E Hh;`W?=yo=Q literal 452900 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%0D3=_!7?n%b65+1|V&{(R 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%0A&EFV-?{gc+yXStD(eN_+02x}sNB+y-=>Nvv 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 Date: Tue, 26 Jul 2022 21:31:14 +0800 Subject: [PATCH 2600/2647] merge keyvals in common workflow when add new keyvals to build Signed-off-by: guoyu --- .../service/workflow/workflow_task_v4.go | 2 +- .../workflow/service/workflow/workflow_v4.go | 40 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 0f175d21..719f5102 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -105,7 +105,7 @@ func GetWorkflowv4Preset(encryptedKey, workflowName string, log *zap.SugaredLogg } } } - if err := ensureWorkflowV4Resp(encryptedKey, workflow, log); err != nil { + if err := encryptWorkflowV4Resp(encryptedKey, workflow, log); err != nil { return workflow, err } return workflow, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index d6cf64d6..3663fb3b 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -110,7 +110,10 @@ func FindWorkflowV4(encryptedKey, name string, logger *zap.SugaredLogger) (*comm logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) return workflow, e.ErrFindWorkflow.AddErr(err) } - if err := ensureWorkflowV4Resp(encryptedKey, workflow, logger); err != nil { + if err := encryptWorkflowV4Resp(encryptedKey, workflow, logger); err != nil { + return workflow, err + } + if err := mergeNewKeyValsWorkflowV4(workflow, logger); err != nil { return workflow, err } return workflow, err @@ -228,7 +231,39 @@ func getRecentTaskV4Info(workflow *Workflow, tasks []*commonmodels.WorkflowTask) } } -func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { +func mergeNewKeyValsWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { + for _, stage := range workflow.Stages { + for _, job := range stage.Jobs { + if job.JobType == config.JobZadigBuild { + spec := &commonmodels.ZadigBuildJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + for _, build := range spec.ServiceAndBuilds { + buildInfo, err := commonrepo.NewBuildColl().Find(&commonrepo.BuildFindOption{Name: build.BuildName}) + if err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + kvs := buildInfo.PreBuild.Envs + if buildInfo.TemplateID != "" { + for _, target := range buildInfo.Targets { + if target.ServiceName == build.ServiceName && target.ServiceModule == build.ServiceModule { + kvs = target.Envs + } + } + } + build.KeyVals = commonservice.MergeBuildEnvs(kvs, build.KeyVals) + } + job.Spec = spec + } + } + } + return nil +} + +func encryptWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { for _, stage := range workflow.Stages { for _, job := range stage.Jobs { if job.JobType == config.JobZadigBuild { @@ -239,6 +274,7 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 } for _, build := range spec.ServiceAndBuilds { if err := commonservice.EncryptKeyVals(encryptedKey, build.KeyVals, logger); err != nil { + logger.Errorf(err.Error()) return e.ErrFindWorkflow.AddErr(err) } } -- Gitee From 260022f210f601e95087265e542fe15bf541c3ed Mon Sep 17 00:00:00 2001 From: guoyu Date: Tue, 26 Jul 2022 21:40:55 +0800 Subject: [PATCH 2601/2647] merge two function Signed-off-by: guoyu --- .../service/workflow/workflow_task_v4.go | 2 +- .../workflow/service/workflow/workflow_v4.go | 25 ++----------------- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 719f5102..0f175d21 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -105,7 +105,7 @@ func GetWorkflowv4Preset(encryptedKey, workflowName string, log *zap.SugaredLogg } } } - if err := encryptWorkflowV4Resp(encryptedKey, workflow, log); err != nil { + if err := ensureWorkflowV4Resp(encryptedKey, workflow, log); err != nil { return workflow, err } return workflow, nil diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 3663fb3b..6f0370fb 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -110,10 +110,7 @@ func FindWorkflowV4(encryptedKey, name string, logger *zap.SugaredLogger) (*comm logger.Errorf("Failed to find WorkflowV4: %s, the error is: %v", name, err) return workflow, e.ErrFindWorkflow.AddErr(err) } - if err := encryptWorkflowV4Resp(encryptedKey, workflow, logger); err != nil { - return workflow, err - } - if err := mergeNewKeyValsWorkflowV4(workflow, logger); err != nil { + if err := ensureWorkflowV4Resp(encryptedKey, workflow, logger); err != nil { return workflow, err } return workflow, err @@ -231,7 +228,7 @@ func getRecentTaskV4Info(workflow *Workflow, tasks []*commonmodels.WorkflowTask) } } -func mergeNewKeyValsWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { +func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { for _, stage := range workflow.Stages { for _, job := range stage.Jobs { if job.JobType == config.JobZadigBuild { @@ -255,24 +252,6 @@ func mergeNewKeyValsWorkflowV4(workflow *commonmodels.WorkflowV4, logger *zap.Su } } build.KeyVals = commonservice.MergeBuildEnvs(kvs, build.KeyVals) - } - job.Spec = spec - } - } - } - return nil -} - -func encryptWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4, logger *zap.SugaredLogger) error { - for _, stage := range workflow.Stages { - for _, job := range stage.Jobs { - if job.JobType == config.JobZadigBuild { - spec := &commonmodels.ZadigBuildJobSpec{} - if err := commonmodels.IToi(job.Spec, spec); err != nil { - logger.Errorf(err.Error()) - return e.ErrFindWorkflow.AddErr(err) - } - for _, build := range spec.ServiceAndBuilds { if err := commonservice.EncryptKeyVals(encryptedKey, build.KeyVals, logger); err != nil { logger.Errorf(err.Error()) return e.ErrFindWorkflow.AddErr(err) -- Gitee From 794ea3661c7137962dff09e6a791560b3a784a4c Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Jul 2022 11:04:30 +0800 Subject: [PATCH 2602/2647] add code optimize Signed-off-by: allenshen --- .../core/service/step/step_docker_build.go | 2 +- .../reaper/core/service/reaper/reaper.go | 2 +- pkg/microservice/warpdrive/config/config.go | 12 ------------ .../warpdrive/core/service/taskplugin/deploy.go | 2 +- .../core/service/taskplugin/release_image.go | 2 +- pkg/microservice/zgctl/zgctl.go | 2 +- pkg/setting/consts.go | 3 --- pkg/tool/helmclient/helmclient.go | 6 +++--- 8 files changed, 8 insertions(+), 23 deletions(-) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go index 49380c19..23fe31cf 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go @@ -79,7 +79,7 @@ func (s DockerBuildStep) dockerLogin() error { cmd.Stdout = &out cmd.Stderr = &out if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to login docker registry: %s", err) + return fmt.Errorf("failed to login docker registry: %s:%s", err, string(out.Bytes())) } fmt.Printf("Login ended. Duration: %.2f seconds.\n", time.Since(startTimeDockerLogin).Seconds()) diff --git a/pkg/microservice/reaper/core/service/reaper/reaper.go b/pkg/microservice/reaper/core/service/reaper/reaper.go index f348490e..2973d99b 100644 --- a/pkg/microservice/reaper/core/service/reaper/reaper.go +++ b/pkg/microservice/reaper/core/service/reaper/reaper.go @@ -198,7 +198,7 @@ func (r *Reaper) BeforeExec() error { cmd.Stdout = &out cmd.Stderr = &out if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to login docker registry: %s", err) + return fmt.Errorf("failed to login docker registry: %s %s", err, out.String()) } log.Infof("Login ended. Duration: %.2f seconds.", time.Since(startTimeDockerLogin).Seconds()) diff --git a/pkg/microservice/warpdrive/config/config.go b/pkg/microservice/warpdrive/config/config.go index c4f31d1c..104124a4 100644 --- a/pkg/microservice/warpdrive/config/config.go +++ b/pkg/microservice/warpdrive/config/config.go @@ -41,15 +41,3 @@ func ReleaseImageTimeout() string { func Home() string { return viper.GetString(setting.Home) } - -func DefaultRegistryAddr() string { - return viper.GetString(setting.DefaultRegistryAddr) -} - -func DefaultRegistryAK() string { - return viper.GetString(setting.DefaultRegistryAK) -} - -func DefaultRegistrySK() string { - return viper.GetString(setting.DefaultRegistrySK) -} diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go index 4d3ee991..d50f2930 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy.go @@ -172,7 +172,7 @@ func (p *DeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, _ * } } if serviceInfo.WorkloadType == "" { - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() + selector = labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: p.Task.ServiceName}.AsSelector() var deployments []*appsv1.Deployment deployments, err = getter.ListDeployments(p.Task.Namespace, selector, p.kubeClient) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go index 43bb3889..1953efb4 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/release_image.go @@ -319,7 +319,7 @@ DistributeLoop: } } if serviceInfo.WorkloadType == "" { - selector := labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: distribute.DeployServiceName}.AsSelector() + selector = labels.Set{setting.ProductLabel: p.Task.ProductName, setting.ServiceLabel: distribute.DeployServiceName}.AsSelector() var deployments []*appsv1.Deployment deployments, err = getter.ListDeployments(distribute.DeployNamespace, selector, p.kubeClient) diff --git a/pkg/microservice/zgctl/zgctl.go b/pkg/microservice/zgctl/zgctl.go index fe29745c..77a25529 100644 --- a/pkg/microservice/zgctl/zgctl.go +++ b/pkg/microservice/zgctl/zgctl.go @@ -268,7 +268,7 @@ func (z *zgctl) configLocalSyncthing(configDir string) (deviceID string, err err cmd.Stderr = &errBuffer err = cmd.Run() if err != nil { - return "", err + return "", fmt.Errorf("%s %s", err, errBuffer.String()) } items := strings.Split(errBuffer.String(), "Device ID:") diff --git a/pkg/setting/consts.go b/pkg/setting/consts.go index 6c098839..f3eca369 100644 --- a/pkg/setting/consts.go +++ b/pkg/setting/consts.go @@ -88,9 +88,6 @@ const ( // warpdrive WarpDrivePodName = "WD_POD_NAME" ReleaseImageTimeout = "RELEASE_IMAGE_TIMEOUT" - DefaultRegistryAddr = "DEFAULT_REG_ADDRESS" - DefaultRegistryAK = "DEFAULT_REG_ACCESS_KEY" - DefaultRegistrySK = "DEFAULT_REG_SECRET_KEY" // reaper Home = "HOME" diff --git a/pkg/tool/helmclient/helmclient.go b/pkg/tool/helmclient/helmclient.go index 4b5ef009..1cbf8a3b 100644 --- a/pkg/tool/helmclient/helmclient.go +++ b/pkg/tool/helmclient/helmclient.go @@ -626,12 +626,12 @@ func (hClient *HelmClient) pushAcrChart(repoEntry *repo.Entry, chartPath string) prog.Env = os.Environ() buf := bytes.NewBuffer(nil) prog.Stdout = buf - prog.Stderr = os.Stderr + prog.Stderr = buf if err := prog.Run(); err != nil { if eErr, ok := err.(*exec.ExitError); ok { - return fmt.Errorf("plugin exited with error: %s", string(eErr.Stderr)) + return fmt.Errorf("plugin exited with error: %s %s", string(eErr.Stderr), buf.String()) } - return err + return fmt.Errorf("%s %s", err, buf.String()) } return nil } -- Gitee From e84eb1791b6c2bcc5dad0d435b05527dc2ef753e Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Jul 2022 11:10:13 +0800 Subject: [PATCH 2603/2647] add code optimize Signed-off-by: allenshen --- .../jobexecutor/core/service/step/step_docker_build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go index 23fe31cf..9791c69e 100644 --- a/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go +++ b/pkg/microservice/jobexecutor/core/service/step/step_docker_build.go @@ -79,7 +79,7 @@ func (s DockerBuildStep) dockerLogin() error { cmd.Stdout = &out cmd.Stderr = &out if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to login docker registry: %s:%s", err, string(out.Bytes())) + return fmt.Errorf("failed to login docker registry: %s %s", err, out.String()) } fmt.Printf("Login ended. Duration: %.2f seconds.\n", time.Since(startTimeDockerLogin).Seconds()) -- Gitee From a337f11283a48c0eff2f534e5fea3e7596e8667b Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Jul 2022 13:41:36 +0800 Subject: [PATCH 2604/2647] fix data Signed-off-by: allenshen --- pkg/microservice/aslan/core/environment/handler/router.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/aslan/core/environment/handler/router.go b/pkg/microservice/aslan/core/environment/handler/router.go index ee8ef786..2cf5f60c 100644 --- a/pkg/microservice/aslan/core/environment/handler/router.go +++ b/pkg/microservice/aslan/core/environment/handler/router.go @@ -118,6 +118,7 @@ func (*Router) Inject(router *gin.RouterGroup) { { environments.GET("", ListProducts) environments.PUT("/:name", UpdateProduct) + environments.PUT("/:name/registry", UpdateProductRegistry) environments.PUT("", UpdateMultiProducts) environments.POST("", CreateProduct) environments.GET("/:name", GetProduct) -- Gitee From 9efcebbda346c47176db590840b8fef6a7202d40 Mon Sep 17 00:00:00 2001 From: allenshen Date: Thu, 28 Jul 2022 20:01:21 +0800 Subject: [PATCH 2605/2647] add ifPassFilter for calls to aslan from wd Signed-off-by: allenshen --- .../warpdrive/core/service/taskplugin/deploy_helm.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go index 0cbc3dc2..4713719b 100644 --- a/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go +++ b/pkg/microservice/warpdrive/core/service/taskplugin/deploy_helm.go @@ -154,8 +154,8 @@ func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, helmClient helmclient.Client ) - p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %v namespace %s", p.Task.ProductName, - p.Task.ServiceName, containerNameSet.List(), p.Task.Namespace) + p.Log.Infof("start helm deploy, productName %s serviceName %s containerName %v envName: %s namespace %s", p.Task.ProductName, + p.Task.ServiceName, containerNameSet.List(), p.Task.EnvName, p.Task.Namespace) productInfo, err = p.getProductInfo(ctx, &EnvArgs{EnvName: p.Task.EnvName, ProductName: p.Task.ProductName}) if err != nil { @@ -403,7 +403,7 @@ func (p *HelmDeployTaskPlugin) Run(ctx context.Context, pipelineTask *task.Task, func (p *HelmDeployTaskPlugin) getProductInfo(ctx context.Context, args *EnvArgs) (*types.Product, error) { url := fmt.Sprintf("/api/environment/environments/%s/productInfo", args.EnvName) prod := &types.Product{} - _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("projectName", args.ProductName)) + _, err := p.httpClient.Get(url, httpclient.SetResult(prod), httpclient.SetQueryParam("projectName", args.ProductName), httpclient.SetQueryParam("ifPassFilter", "true")) if err != nil { return nil, err } @@ -463,7 +463,7 @@ func (p *HelmDeployTaskPlugin) downloadService(productName, serviceName, storage func (p *HelmDeployTaskPlugin) getRenderSet(ctx context.Context, name string, revision int64) (*types.RenderSet, error) { url := fmt.Sprintf("/api/project/renders/render/%s/revision/%d", name, revision) rs := &types.RenderSet{} - _, err := p.httpClient.Get(url, httpclient.SetResult(rs)) + _, err := p.httpClient.Get(url, httpclient.SetResult(rs), httpclient.SetQueryParam("ifPassFilter", "true")) if err != nil { return nil, err } @@ -561,7 +561,7 @@ func assignImageData(imageUrl string, matchData map[string]string) (map[string]i func (p *HelmDeployTaskPlugin) updateRenderSet(ctx context.Context, args *types.RenderSet) error { url := "/api/project/renders" - _, err := p.httpClient.Put(url, httpclient.SetBody(args)) + _, err := p.httpClient.Put(url, httpclient.SetBody(args), httpclient.SetQueryParam("ifPassFilter", "true")) return err } -- Gitee From b239dd9d2666a430cefd940ad36b625ab7815722 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 29 Jul 2022 11:21:41 +0800 Subject: [PATCH 2606/2647] merge build template key values Signed-off-by: guoyu --- .../core/workflow/service/workflow/workflow_v4.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 6f0370fb..c9b7e29d 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -245,11 +245,21 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 } kvs := buildInfo.PreBuild.Envs if buildInfo.TemplateID != "" { + buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: buildInfo.TemplateID, + }) + if err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + for _, target := range buildInfo.Targets { if target.ServiceName == build.ServiceName && target.ServiceModule == build.ServiceModule { kvs = target.Envs } } + // if build template update any keyvals, merge it. + kvs = commonservice.MergeBuildEnvs(buildTemplate.PreBuild.Envs, kvs) } build.KeyVals = commonservice.MergeBuildEnvs(kvs, build.KeyVals) if err := commonservice.EncryptKeyVals(encryptedKey, build.KeyVals, logger); err != nil { -- Gitee From 9db02d49b17b9fe32353ba5ecd3b471129d1cee7 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 29 Jul 2022 14:38:42 +0800 Subject: [PATCH 2607/2647] merge kvs for service module Signed-off-by: guoyu --- pkg/microservice/aslan/core/build/service/build.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 8c8002b0..9adaa517 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -146,11 +146,22 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe } // get build env vars when it's a template build if build.TemplateID != "" { + templateKeyVals := []*commonmodels.KeyVal{} + buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ + ID: build.TemplateID, + }) + if err != nil { + log.Errorf("build %s, build template not found", build.Name) + } else { + templateKeyVals = buildTemplate.PreBuild.Envs + } + for _, target := range build.Targets { if target.ServiceModule == container.Name && target.ServiceName == serviceTmpl.ServiceName { build.PreBuild.Envs = target.Envs } } + build.PreBuild.Envs = commonservice.MergeBuildEnvs(templateKeyVals, build.PreBuild.Envs) } if err := commonservice.EncryptKeyVals(encryptedKey, build.PreBuild.Envs, log); err != nil { return serviceModuleAndBuildResp, err -- Gitee From 532c341e4abcaa9bce81cafbfab09b8c878744e7 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 29 Jul 2022 14:48:14 +0800 Subject: [PATCH 2608/2647] typo: fix job executor name Signed-off-by: guoyu --- pkg/microservice/jobexecutor/executor/executor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/microservice/jobexecutor/executor/executor.go b/pkg/microservice/jobexecutor/executor/executor.go index 922bc090..b3bdd9ac 100644 --- a/pkg/microservice/jobexecutor/executor/executor.go +++ b/pkg/microservice/jobexecutor/executor/executor.go @@ -55,7 +55,7 @@ func Execute(ctx context.Context) error { start := time.Now() - excutor := "job-excutor" + excutor := "job-executor" var err error defer func() { // os.Remove(ZadigLifeCycleFile) -- Gitee From da3f6cecbd9f3fa6cd52536832d52d486dc3ba23 Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 29 Jul 2022 15:30:48 +0800 Subject: [PATCH 2609/2647] skip build when template not found Signed-off-by: guoyu --- pkg/microservice/aslan/core/build/service/build.go | 7 +++---- .../aslan/core/workflow/service/workflow/workflow_v4.go | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 9adaa517..7006aa39 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -146,14 +146,13 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe } // get build env vars when it's a template build if build.TemplateID != "" { - templateKeyVals := []*commonmodels.KeyVal{} buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ ID: build.TemplateID, }) if err != nil { log.Errorf("build %s, build template not found", build.Name) - } else { - templateKeyVals = buildTemplate.PreBuild.Envs + // if can not find build template, this build was broken, skip this build. + continue } for _, target := range build.Targets { @@ -161,7 +160,7 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe build.PreBuild.Envs = target.Envs } } - build.PreBuild.Envs = commonservice.MergeBuildEnvs(templateKeyVals, build.PreBuild.Envs) + build.PreBuild.Envs = commonservice.MergeBuildEnvs(buildTemplate.PreBuild.Envs, build.PreBuild.Envs) } if err := commonservice.EncryptKeyVals(encryptedKey, build.PreBuild.Envs, log); err != nil { return serviceModuleAndBuildResp, err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index c9b7e29d..90d8cee1 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -250,7 +250,8 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 }) if err != nil { logger.Errorf(err.Error()) - return e.ErrFindWorkflow.AddErr(err) + // if can not find build template, skip this build. + continue } for _, target := range buildInfo.Targets { -- Gitee From a976fc1050fd012add0f3174b89c86516fdc3d1f Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 29 Jul 2022 15:57:33 +0800 Subject: [PATCH 2610/2647] return error when build template not found Signed-off-by: guoyu --- pkg/microservice/aslan/core/build/service/build.go | 6 +++--- .../aslan/core/workflow/service/workflow/workflow_v4.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 7006aa39..e90cb772 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -150,9 +150,9 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe ID: build.TemplateID, }) if err != nil { - log.Errorf("build %s, build template not found", build.Name) - // if can not find build template, this build was broken, skip this build. - continue + errMsg := fmt.Sprintf("build job: %s, template not found", build.Name) + log.Error(errMsg) + return nil, e.ErrListBuildModule.AddDesc(errMsg) } for _, target := range build.Targets { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 90d8cee1..f4d383b2 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -249,9 +249,9 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 ID: buildInfo.TemplateID, }) if err != nil { - logger.Errorf(err.Error()) - // if can not find build template, skip this build. - continue + errMsg := fmt.Sprintf("build job: %s, template not found", buildInfo.Name) + logger.Error(errMsg) + return e.ErrFindWorkflow.AddDesc(errMsg) } for _, target := range buildInfo.Targets { -- Gitee From 39dff2b25ad4c2ac137c4deb307ab07d14923ebc Mon Sep 17 00:00:00 2001 From: guoyu Date: Fri, 29 Jul 2022 16:33:46 +0800 Subject: [PATCH 2611/2647] do not block user Signed-off-by: guoyu --- pkg/microservice/aslan/core/build/service/build.go | 10 ++++++---- .../core/workflow/service/workflow/workflow_v4.go | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index e90cb772..5cef9f9c 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -146,13 +146,15 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe } // get build env vars when it's a template build if build.TemplateID != "" { + templateEnvs := []*commonmodels.KeyVal{} buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ ID: build.TemplateID, }) + // if template not found, envs are empty, but do not block user. if err != nil { - errMsg := fmt.Sprintf("build job: %s, template not found", build.Name) - log.Error(errMsg) - return nil, e.ErrListBuildModule.AddDesc(errMsg) + log.Errorf("build job: %s, template not found", build.Name) + } else { + templateEnvs = buildTemplate.PreBuild.Envs } for _, target := range build.Targets { @@ -160,7 +162,7 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe build.PreBuild.Envs = target.Envs } } - build.PreBuild.Envs = commonservice.MergeBuildEnvs(buildTemplate.PreBuild.Envs, build.PreBuild.Envs) + build.PreBuild.Envs = commonservice.MergeBuildEnvs(templateEnvs, build.PreBuild.Envs) } if err := commonservice.EncryptKeyVals(encryptedKey, build.PreBuild.Envs, log); err != nil { return serviceModuleAndBuildResp, err diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index f4d383b2..62ba7874 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -245,13 +245,15 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 } kvs := buildInfo.PreBuild.Envs if buildInfo.TemplateID != "" { + templateEnvs := []*commonmodels.KeyVal{} buildTemplate, err := commonrepo.NewBuildTemplateColl().Find(&commonrepo.BuildTemplateQueryOption{ ID: buildInfo.TemplateID, }) + // if template not found, envs are empty, but do not block user. if err != nil { - errMsg := fmt.Sprintf("build job: %s, template not found", buildInfo.Name) - logger.Error(errMsg) - return e.ErrFindWorkflow.AddDesc(errMsg) + logger.Error("build job: %s, template not found", buildInfo.Name) + } else { + templateEnvs = buildTemplate.PreBuild.Envs } for _, target := range buildInfo.Targets { @@ -260,7 +262,7 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 } } // if build template update any keyvals, merge it. - kvs = commonservice.MergeBuildEnvs(buildTemplate.PreBuild.Envs, kvs) + kvs = commonservice.MergeBuildEnvs(templateEnvs, kvs) } build.KeyVals = commonservice.MergeBuildEnvs(kvs, build.KeyVals) if err := commonservice.EncryptKeyVals(encryptedKey, build.KeyVals, logger); err != nil { -- Gitee From 2b3f8ce395fdc146df4868c7c504fa06282780d9 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Mon, 1 Aug 2022 15:33:18 +0800 Subject: [PATCH 2612/2647] encrypt freestyle job and plugin job envs (#1831) * add update nacos config plugin Signed-off-by: guoyu * add dms approve plugin Signed-off-by: guoyu * update plugin log format Signed-off-by: guoyu * update plugin name Signed-off-by: guoyu * skip selected job Signed-off-by: guoyu * mask plugin and freestyle job credential Signed-off-by: guoyu * plugins surpport text input Signed-off-by: guoyu * skip empty stage Signed-off-by: guoyu * skip job again Signed-off-by: guoyu * remove skip params Signed-off-by: guoyu * move enterprise plugins to private repo Signed-off-by: guoyu * go mod tidy Signed-off-by: guoyu * go mod tidy again Signed-off-by: guoyu * fix go mod format Signed-off-by: guoyu Co-authored-by: guoyu --- .../common/repository/models/plugin_repo.go | 8 ++++--- .../aslan/core/common/service/workflow_v4.go | 21 ++++++++++++++++ .../service/workflow/plugins/README.md | 3 +++ .../hello-world/v1.0.0/hello-world.yaml | 15 ------------ .../service/workflow/workflow_task_v4.go | 4 +++- .../workflow/service/workflow/workflow_v4.go | 24 +++++++++++++++++++ 6 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/plugins/README.md delete mode 100644 pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml diff --git a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go index 11640d42..20724c66 100644 --- a/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go +++ b/pkg/microservice/aslan/core/common/repository/models/plugin_repo.go @@ -59,9 +59,11 @@ type Env struct { type Params struct { Name string `bson:"name" json:"name" yaml:"name"` Description string `bson:"description" json:"description" yaml:"description"` - ParamsType string `bson:"type" json:"type" yaml:"type"` - Value string `bson:"value" json:"value" yaml:"-"` - Default string `bson:"default" json:"default" yaml:"default"` + // support string/text type + ParamsType string `bson:"type" json:"type" yaml:"type"` + Value string `bson:"value" json:"value" yaml:"value,omitempty"` + Default string `bson:"default" json:"default" yaml:"default"` + IsCredential bool `bson:"is_credential" json:"is_credential" yaml:"is_credential"` } func (PluginRepo) TableName() string { diff --git a/pkg/microservice/aslan/core/common/service/workflow_v4.go b/pkg/microservice/aslan/core/common/service/workflow_v4.go index 1c7feafa..a716cbc5 100644 --- a/pkg/microservice/aslan/core/common/service/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/service/workflow_v4.go @@ -102,3 +102,24 @@ func EncryptKeyVals(encryptedKey string, kvs []*commonmodels.KeyVal, logger *zap } return nil } + +func EncryptParams(encryptedKey string, params []*commonmodels.Params, logger *zap.SugaredLogger) error { + if encryptedKey == "" { + return nil + } + aesKey, err := GetAesKeyFromEncryptedKey(encryptedKey, logger) + if err != nil { + log.Errorf("EncyptParams GetAesKeyFromEncryptedKey err:%v", err) + return err + } + for _, param := range params { + if param.IsCredential { + param.Value, err = crypto.AesEncryptByKey(param.Value, aesKey.PlainText) + if err != nil { + log.Errorf("aes encrypt by key err:%v", err) + return err + } + } + } + return nil +} diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/plugins/README.md b/pkg/microservice/aslan/core/workflow/service/workflow/plugins/README.md new file mode 100644 index 00000000..61e4f4e6 --- /dev/null +++ b/pkg/microservice/aslan/core/workflow/service/workflow/plugins/README.md @@ -0,0 +1,3 @@ +# zadig-plugin + +Zadig offical plugins diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml b/pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml deleted file mode 100644 index 2157d04c..00000000 --- a/pkg/microservice/aslan/core/workflow/service/workflow/plugins/hello-world/v1.0.0/hello-world.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: hello-world -version: "v0.0.1" -descrption: "hello world" -image: alpine/curl:3.14 -cmds: -- "/bin/sh" -- "-c" -args: -- "echo $(CONTENT)" - -inputs: -- name: CONTENT - description: "content to print" - type: string - default: "hello world" \ No newline at end of file diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go index 0f175d21..76188e85 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_task_v4.go @@ -142,7 +142,6 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z Parallel: stage.Parallel, Approval: stage.Approval, } - workflowTask.Stages = append(workflowTask.Stages, stageTask) for _, job := range stage.Jobs { // TODO: move this logic to job controller if job.JobType == config.JobZadigBuild { @@ -165,6 +164,9 @@ func CreateWorkflowTaskV4(user string, workflow *commonmodels.WorkflowV4, log *z } stageTask.Jobs = append(stageTask.Jobs, jobs...) } + if len(stageTask.Jobs) > 0 { + workflowTask.Stages = append(workflowTask.Stages, stageTask) + } } if err := workflowTaskLint(workflowTask, log); err != nil { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go index 62ba7874..6819ed67 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/workflow_v4.go @@ -272,6 +272,30 @@ func ensureWorkflowV4Resp(encryptedKey string, workflow *commonmodels.WorkflowV4 } job.Spec = spec } + if job.JobType == config.JobFreestyle { + spec := &commonmodels.FreestyleJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + if err := commonservice.EncryptKeyVals(encryptedKey, spec.Properties.Envs, logger); err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + job.Spec = spec + } + if job.JobType == config.JobPlugin { + spec := &commonmodels.PluginJobSpec{} + if err := commonmodels.IToi(job.Spec, spec); err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + if err := commonservice.EncryptParams(encryptedKey, spec.Plugin.Inputs, logger); err != nil { + logger.Errorf(err.Error()) + return e.ErrFindWorkflow.AddErr(err) + } + job.Spec = spec + } } } return nil -- Gitee From cf255af5b2dfec3e239e3868fe138cd199b77c3c Mon Sep 17 00:00:00 2001 From: shendongdong Date: Wed, 3 Aug 2022 10:13:00 +0800 Subject: [PATCH 2613/2647] add dryRun install services logic when creating environments (#1830) * add serice dry run logic Signed-off-by: allenshen * fix import issue Signed-off-by: allenshen * optimize code Signed-off-by: allenshen --- .../aslan/core/environment/service/env.go | 1 - .../core/environment/service/environment.go | 93 ++++++++-- .../service/environment_creator.go | 174 ++++++++++++++---- .../aslan/core/environment/service/helm.go | 89 --------- .../aslan/core/environment/service/k8s.go | 4 - .../aslan/core/environment/service/pm.go | 4 - 6 files changed, 224 insertions(+), 141 deletions(-) diff --git a/pkg/microservice/aslan/core/environment/service/env.go b/pkg/microservice/aslan/core/environment/service/env.go index 3e6d37bf..f56c2a54 100644 --- a/pkg/microservice/aslan/core/environment/service/env.go +++ b/pkg/microservice/aslan/core/environment/service/env.go @@ -31,7 +31,6 @@ type envHandle interface { listGroupServices(allServices []*commonmodels.ProductService, envName, productName string, informer informers.SharedInformerFactory, productInfo *commonmodels.Product) []*commonservice.ServiceResp updateService(args *SvcOptArgs) error queryServiceStatus(namespace, envName, productName string, serviceTmpl *commonmodels.Service, kubeClient informers.SharedInformerFactory) (string, string, []string) - initEnvConfigSet(envName, namespace, productName, username string, envConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error } func envHandleFunc(projectType string, log *zap.SugaredLogger) envHandle { diff --git a/pkg/microservice/aslan/core/environment/service/environment.go b/pkg/microservice/aslan/core/environment/service/environment.go index 1d8b4b12..3ecf901f 100644 --- a/pkg/microservice/aslan/core/environment/service/environment.go +++ b/pkg/microservice/aslan/core/environment/service/environment.go @@ -204,6 +204,7 @@ type ReleaseInstallParam struct { MergedValues string RenderChart *templatemodels.RenderChart serviceObj *commonmodels.Service + DryRun bool } type intervalExecutorHandler func(data *commonmodels.Service, isRetry bool, log *zap.SugaredLogger) error @@ -2591,7 +2592,7 @@ func createGroups(envName, user, requestID string, args *commonmodels.Product, e } }() - err = envHandleFunc(getProjectType(args.ProductName), log).initEnvConfigSet(envName, args.Namespace, args.ProductName, user, args.EnvConfigs, informer, kubeClient) + err = initEnvConfigSetAction(args.EnvName, args.Namespace, args.ProductName, user, args.EnvConfigs, false, kubeClient) if err != nil { args.Status = setting.ProductStatusFailed log.Errorf("initEnvConfigSet error :%s", err) @@ -2747,11 +2748,13 @@ func upsertService(isUpdate bool, env *commonmodels.Product, continue } - err = EnsureUpdateZadigService(context.TODO(), env, u.GetName(), kubeClient, istioClient) - if err != nil { - log.Errorf("Failed to update Zadig service %s for env %s of product %s: %s", u.GetName(), env.EnvName, env.ProductName, err) - errList = multierror.Append(errList, err) - continue + if istioClient != nil { + err = EnsureUpdateZadigService(context.TODO(), env, u.GetName(), kubeClient, istioClient) + if err != nil { + log.Errorf("Failed to update Zadig service %s for env %s of product %s: %s", u.GetName(), env.EnvName, env.ProductName, err) + errList = multierror.Append(errList, err) + continue + } } case setting.Deployment, setting.StatefulSet: // compatibility flag, We add a match label in spec.selector field pre 1.10. @@ -3298,6 +3301,7 @@ func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry boo UpgradeCRDs: true, CleanupOnFail: true, MaxHistory: 10, + DryRun: param.DryRun, } if isRetry { chartSpec.Replace = true @@ -3305,9 +3309,11 @@ func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry boo // If the target environment is a shared environment and a sub env, we need to clear the deployed K8s Service. ctx := context.TODO() - err = EnsureDeletePreCreatedServices(ctx, param.ProductName, param.Namespace, chartSpec, helmClient) - if err != nil { - return fmt.Errorf("failed to ensure deleting pre-created K8s Services for product %q in namespace %q: %s", param.ProductName, param.Namespace, err) + if !chartSpec.DryRun { + err = EnsureDeletePreCreatedServices(ctx, param.ProductName, param.Namespace, chartSpec, helmClient) + if err != nil { + return fmt.Errorf("failed to ensure deleting pre-created K8s Services for product %q in namespace %q: %s", param.ProductName, param.Namespace, err) + } } helmClient, err = helmClient.Clone() @@ -3323,9 +3329,11 @@ func installOrUpgradeHelmChartWithValues(param *ReleaseInstallParam, isRetry boo "failed to install or upgrade helm chart %s/%s", namespace, serviceObj.ServiceName) } else { - err = EnsureZadigServiceByManifest(ctx, param.ProductName, param.Namespace, release.Manifest) - if err != nil { - err = errors.WithMessagef(err, "failed to ensure Zadig Service %s", err) + if !chartSpec.DryRun { + err = EnsureZadigServiceByManifest(ctx, param.ProductName, param.Namespace, release.Manifest) + if err != nil { + err = errors.WithMessagef(err, "failed to ensure Zadig Service %s", err) + } } } @@ -3746,6 +3754,67 @@ func overrideValues(currentValuesYaml, latestValuesYaml []byte, imageRelatedKey return yaml.Marshal(latestValuesMap) } +func dryRunInstallRelease(productResp *commonmodels.Product, renderset *commonmodels.RenderSet, helmClient *helmtool.HelmClient, log *zap.SugaredLogger) error { + productName, _ := productResp.ProductName, productResp.EnvName + renderChartMap := make(map[string]*templatemodels.RenderChart) + for _, renderChart := range productResp.ChartInfos { + renderChartMap[renderChart.ServiceName] = renderChart + } + + handler := func(serviceObj *commonmodels.Service, log *zap.SugaredLogger) (err error) { + param, errBuildParam := buildInstallParam(productResp.Namespace, renderset.EnvName, renderset.DefaultValues, renderChartMap[serviceObj.ServiceName], serviceObj) + if errBuildParam != nil { + return errBuildParam + } + param.DryRun = true + err = installOrUpgradeHelmChartWithValues(param, false, helmClient) + return + } + + errList := new(multierror.Error) + var errLock sync.Mutex + appendErr := func(err error) { + errLock.Lock() + defer errLock.Unlock() + errList = multierror.Append(errList, err) + } + + var wg sync.WaitGroup + for _, groupServices := range productResp.Services { + serviceList := make([]*commonmodels.Service, 0) + for _, service := range groupServices { + if _, ok := renderChartMap[service.ServiceName]; !ok { + continue + } + opt := &commonrepo.ServiceFindOption{ + ServiceName: service.ServiceName, + Type: service.Type, + Revision: service.Revision, + ProductName: productName, + } + serviceObj, err := commonrepo.NewServiceColl().Find(opt) + if err != nil { + appendErr(fmt.Errorf("failed to find service %s, err %s", service.ServiceName, err.Error())) + continue + } + serviceList = append(serviceList, serviceObj) + } + + for _, svc := range serviceList { + wg.Add(1) + go func(service *models.Service) { + defer wg.Done() + err := handler(service, log) + if err != nil { + appendErr(fmt.Errorf("failed to dryRun install chart for service: %s, err: %s", service.ServiceName, err)) + } + }(svc) + } + } + wg.Wait() + return errList.ErrorOrNil() +} + func proceedHelmRelease(productName, envName string, productResp *commonmodels.Product, renderset *commonmodels.RenderSet, helmClient *helmtool.HelmClient, filter svcUpgradeFilter, log *zap.SugaredLogger) error { renderChartMap := make(map[string]*templatemodels.RenderChart) for _, renderChart := range productResp.ChartInfos { diff --git a/pkg/microservice/aslan/core/environment/service/environment_creator.go b/pkg/microservice/aslan/core/environment/service/environment_creator.go index bf999efb..c523a90c 100644 --- a/pkg/microservice/aslan/core/environment/service/environment_creator.go +++ b/pkg/microservice/aslan/core/environment/service/environment_creator.go @@ -19,21 +19,31 @@ package service import ( "errors" "fmt" + "strings" "time" + "github.com/hashicorp/go-multierror" "go.uber.org/zap" versionedclient "istio.io/client-go/pkg/clientset/versioned" + "k8s.io/client-go/informers" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models/template" commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" commonservice "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service" + "github.com/koderover/zadig/pkg/microservice/aslan/core/common/service/kube" "github.com/koderover/zadig/pkg/setting" kubeclient "github.com/koderover/zadig/pkg/shared/kube/client" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" "github.com/koderover/zadig/pkg/tool/kube/informer" + "github.com/koderover/zadig/pkg/tool/kube/serializer" + "github.com/koderover/zadig/pkg/tool/kube/updater" + "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" ) @@ -142,10 +152,6 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P log.Errorf("[%s][%s] GetKubeClient error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddErr(err) } - cls, err := kubeclient.GetKubeClientSet(config.HubServerAddress(), clusterID) - if err != nil { - return e.ErrCreateEnv.AddDesc(err.Error()) - } restConfig, err := kubeclient.GetRESTConfig(config.HubServerAddress(), clusterID) if err != nil { @@ -215,11 +221,22 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } - // 设置产品render revision + // before create product, do install -dryRun to expose errors earlier + err = dryRunInstallRelease(args, renderSet, helmClient, log) + if err != nil { + log.Errorf("error occurred when installing services in env: %s/%s, err: %s ", args.ProductName, args.EnvName, err) + return e.ErrCreateEnv.AddErr(err) + } + + dryRunClient := client.NewDryRunClient(kubeClient) + err = initEnvConfigSetAction(args.EnvName, args.Namespace, args.ProductName, user, args.EnvConfigs, true, dryRunClient) + if err != nil { + log.Errorf("failed to dyrRun env resource [%s][P:%s], the error is: %s", args.EnvName, args.ProductName, err) + return e.ErrCreateEnv.AddErr(err) + } + args.Render.Revision = renderSet.Revision - // 记录服务当前对应render版本 setServiceRender(args) - args.Status = setting.ProductStatusCreating args.RecycleDay = config.DefaultRecycleDay() args.ClusterID = clusterID @@ -232,24 +249,15 @@ func (creator *HelmProductCreator) Create(user, requestID string, args *models.P return e.ErrCreateEnv.AddDesc(err.Error()) } - eventStart := time.Now().Unix() - inf, err := informer.NewInformer(clusterID, args.Namespace, cls) + err = initEnvConfigSetAction(args.EnvName, args.Namespace, args.ProductName, user, args.EnvConfigs, false, kubeClient) if err != nil { - log.Errorf("failed to create informer from clientset for clusterID: %s, the error is: %s", clusterID, err) - return nil - } - - err = helmInitEnvConfigSet(args.EnvName, args.Namespace, args.ProductName, user, args.EnvConfigs, inf, kubeClient) - if err != nil { - log.Errorf("failed to helmInitEnvConfigSet [%s][P:%s]: %s, the error is: %s", args.EnvName, args.ProductName, err) - if err := commonrepo.NewProductColl().UpdateStatus(args.EnvName, args.ProductName, setting.ProductStatusFailed); err != nil { + log.Errorf("failed to helmInitEnvConfigSet [%s][P:%s], the error is: %s", args.EnvName, args.ProductName, err) + if err := commonrepo.NewProductColl().UpdateStatusAndError(args.EnvName, args.ProductName, setting.ProductStatusFailed, err.Error()); err != nil { log.Errorf("helmInitEnvConfigSet [%s][P:%s] Product.UpdateStatus error: %s", args.EnvName, args.ProductName, err) } - if err := commonrepo.NewProductColl().UpdateErrors(args.EnvName, args.ProductName, err.Error()); err != nil { - log.Errorf("helmInitEnvConfigSet [%s][P:%s] Product.UpdateErrors error: %s", args.EnvName, args.ProductName, err) - } } - go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, eventStart, helmClient, kubeClient, istioClient, log) + + go installProductHelmCharts(user, args.EnvName, requestID, args, renderSet, time.Now().Unix(), helmClient, kubeClient, istioClient, log) return nil } @@ -287,8 +295,6 @@ func (creator *PMProductCreator) Create(user, requestID string, args *models.Pro return e.ErrCreateEnv.AddDesc(err.Error()) } - eventStart := time.Now().Unix() - args.Status = setting.ProductStatusCreating args.RecycleDay = config.DefaultRecycleDay() err := commonrepo.NewProductColl().Create(args) @@ -297,7 +303,7 @@ func (creator *PMProductCreator) Create(user, requestID string, args *models.Pro return e.ErrCreateEnv.AddDesc(err.Error()) } // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, nil, nil, nil, nil, log) + go createGroups(args.EnvName, user, requestID, args, time.Now().Unix(), nil, nil, nil, nil, log) return nil } @@ -308,6 +314,19 @@ func newDefaultProductCreator() *DefaultProductCreator { return &DefaultProductCreator{} } +func dryRunServices(args *commonmodels.Product, renderSet *commonmodels.RenderSet, informer informers.SharedInformerFactory, kubeClient client.Client, log *zap.SugaredLogger) error { + errList := &multierror.Error{} + for _, group := range args.Services { + for _, svc := range group { + _, err := upsertService(false, args, svc, nil, renderSet, informer, kubeClient, nil, log) + if err != nil { + errList = multierror.Append(errList, fmt.Errorf("failed to dryRun apply service: %s, err: %s", svc.ServiceName, err)) + } + } + } + return errList.ErrorOrNil() +} + func (creator *DefaultProductCreator) Create(user, requestID string, args *models.Product, log *zap.SugaredLogger) error { // get project cluster relation clusterID := args.ClusterID @@ -359,8 +378,6 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model return e.ErrCreateEnv.AddDesc(err.Error()) } - eventStart := time.Now().Unix() - // 检查renderinfo是否为空,避免空指针 if args.Render == nil { args.Render = &models.RenderInfo{ProductTmpl: args.ProductName} } @@ -378,13 +395,22 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model log.Errorf("[%s][P:%s] validate product renderset error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddDesc(err.Error()) } - // 保存产品信息,并设置产品状态 - // 设置产品render revsion args.Render.Revision = renderSet.Revision - // 记录服务当前对应render版本 setServiceRender(args) } + // before we apply yaml to k8s, we run kubectl apply --dry-run to expose problems early + dryRunClient := client.NewDryRunClient(kubeClient) + err = dryRunServices(args, renderSet, inf, dryRunClient, log) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + + err = initEnvConfigSetAction(args.EnvName, args.Namespace, args.ProductName, user, args.EnvConfigs, true, dryRunClient) + if err != nil { + return e.ErrCreateEnv.AddErr(err) + } + args.Status = setting.ProductStatusCreating args.RecycleDay = config.DefaultRecycleDay() args.ClusterID = clusterID @@ -393,7 +419,93 @@ func (creator *DefaultProductCreator) Create(user, requestID string, args *model log.Errorf("[%s][%s] create product record error: %v", args.EnvName, args.ProductName, err) return e.ErrCreateEnv.AddDesc(err.Error()) } - // 异步创建产品 - go createGroups(args.EnvName, user, requestID, args, eventStart, renderSet, inf, kubeClient, istioClient, log) + + go createGroups(args.EnvName, user, requestID, args, time.Now().Unix(), renderSet, inf, kubeClient, istioClient, log) return nil } + +func initEnvConfigSetAction(envName, namespace, productName, userName string, envResources []*models.CreateUpdateCommonEnvCfgArgs, dryRun bool, kubeClient client.Client) error { + errList := &multierror.Error{ + ErrorFormat: func(es []error) string { + format := "创建环境配置" + if len(es) == 1 { + return fmt.Sprintf(format+" %s 失败:%s", envName, es[0]) + } + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %s", err) + } + return fmt.Sprintf(format+" %s 失败:\n%s", envName, strings.Join(points, "\n")) + }, + } + + clusterLabels := getPredefinedClusterLabels(productName, "", envName) + delete(clusterLabels, "s-service") + + for _, envResource := range envResources { + u, err := serializer.NewDecoder().YamlToUnstructured([]byte(envResource.YamlData)) + if err != nil { + log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %s", envResource.YamlData, err) + errList = multierror.Append(errList, err) + continue + } + switch u.GetKind() { + case setting.ConfigMap, setting.Ingress, setting.Secret, setting.PersistentVolumeClaim: + ls := kube.MergeLabels(clusterLabels, u.GetLabels()) + u.SetNamespace(namespace) + u.SetLabels(ls) + _, err := ensureLabelAndNs(u, namespace, productName) + if err != nil { + errList = multierror.Append(errList, err) + continue + } + + err = updater.CreateOrPatchUnstructuredNeverAnnotation(u, kubeClient) + if err != nil { + log.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + + if dryRun { + continue + } + + u.SetManagedFields(nil) + yamlData, err := yaml.Marshal(u.UnstructuredContent()) + if err != nil { + log.Errorf("Failed to initEnvConfigSet yaml.Marshal %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) + errList = multierror.Append(errList, err) + continue + } + envResourceObj := &models.EnvResource{ + ProductName: productName, + UpdateUserName: userName, + EnvName: envName, + Namespace: namespace, + Name: u.GetName(), + YamlData: string(yamlData), + SourceDetail: geneSourceDetail(envResource.GitRepoConfig), + AutoSync: envResource.AutoSync, + } + + switch u.GetKind() { + case setting.ConfigMap: + envResourceObj.Type = string(config.CommonEnvCfgTypeConfigMap) + case setting.Ingress: + envResourceObj.Type = string(config.CommonEnvCfgTypeIngress) + case setting.Secret: + envResourceObj.Type = string(config.CommonEnvCfgTypeSecret) + case setting.PersistentVolumeClaim: + envResourceObj.Type = string(config.CommonEnvCfgTypePvc) + } + if err := commonrepo.NewEnvResourceColl().Create(envResourceObj); err != nil { + errList = multierror.Append(errList, err) + } + default: + errList = multierror.Append(errList, fmt.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, "kind not support")) + } + } + + return errList.ErrorOrNil() +} diff --git a/pkg/microservice/aslan/core/environment/service/helm.go b/pkg/microservice/aslan/core/environment/service/helm.go index 60e657b5..fdfe2a7b 100644 --- a/pkg/microservice/aslan/core/environment/service/helm.go +++ b/pkg/microservice/aslan/core/environment/service/helm.go @@ -31,8 +31,6 @@ import ( "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/release" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/informers" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" "github.com/koderover/zadig/pkg/microservice/aslan/config" @@ -44,8 +42,6 @@ import ( "github.com/koderover/zadig/pkg/setting" e "github.com/koderover/zadig/pkg/tool/errors" helmtool "github.com/koderover/zadig/pkg/tool/helmclient" - "github.com/koderover/zadig/pkg/tool/kube/serializer" - "github.com/koderover/zadig/pkg/tool/kube/updater" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" "github.com/koderover/zadig/pkg/util" @@ -614,88 +610,3 @@ func GetImageInfos(productName, envName, serviceNames string, log *zap.SugaredLo } return ret, nil } - -func helmInitEnvConfigSet(envName, namespace, productName, userName string, envResources []*models.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { - return initEnvConfigSetAction(envName, namespace, productName, userName, envResources, inf, kubeClient) -} - -func initEnvConfigSetAction(envName, namespace, productName, userName string, envResources []*models.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { - errList := &multierror.Error{ - ErrorFormat: func(es []error) string { - format := "创建环境配置" - if len(es) == 1 { - return fmt.Sprintf(format+" %s 失败:%s", envName, es[0]) - } - points := make([]string, len(es)) - for i, err := range es { - points[i] = fmt.Sprintf("* %s", err) - } - return fmt.Sprintf(format+" %s 失败:\n%s", envName, strings.Join(points, "\n")) - }, - } - - clusterLabels := getPredefinedClusterLabels(productName, "", envName) - delete(clusterLabels, "s-service") - - for _, envResource := range envResources { - u, err := serializer.NewDecoder().YamlToUnstructured([]byte(envResource.YamlData)) - if err != nil { - log.Errorf("Failed to convert yaml to Unstructured, manifest is\n%s\n, error: %s", envResource.YamlData, err) - errList = multierror.Append(errList, err) - continue - } - switch u.GetKind() { - case setting.ConfigMap, setting.Ingress, setting.Secret, setting.PersistentVolumeClaim: - ls := kube.MergeLabels(clusterLabels, u.GetLabels()) - u.SetNamespace(namespace) - u.SetLabels(ls) - _, err := ensureLabelAndNs(u, namespace, productName) - if err != nil { - errList = multierror.Append(errList, err) - continue - } - - err = updater.CreateOrPatchUnstructuredNeverAnnotation(u, kubeClient) - if err != nil { - log.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) - errList = multierror.Append(errList, err) - continue - } - u.SetManagedFields(nil) - yamlData, err := yaml.Marshal(u.UnstructuredContent()) - if err != nil { - log.Errorf("Failed to initEnvConfigSet yaml.Marshal %s, manifest is\n%v\n, error: %s", u.GetKind(), u, err) - errList = multierror.Append(errList, err) - continue - } - envResourceObj := &models.EnvResource{ - ProductName: productName, - UpdateUserName: userName, - EnvName: envName, - Namespace: namespace, - Name: u.GetName(), - YamlData: string(yamlData), - SourceDetail: geneSourceDetail(envResource.GitRepoConfig), - AutoSync: envResource.AutoSync, - } - - switch u.GetKind() { - case setting.ConfigMap: - envResourceObj.Type = string(config.CommonEnvCfgTypeConfigMap) - case setting.Ingress: - envResourceObj.Type = string(config.CommonEnvCfgTypeIngress) - case setting.Secret: - envResourceObj.Type = string(config.CommonEnvCfgTypeSecret) - case setting.PersistentVolumeClaim: - envResourceObj.Type = string(config.CommonEnvCfgTypePvc) - } - if err := commonrepo.NewEnvResourceColl().Create(envResourceObj); err != nil { - errList = multierror.Append(errList, err) - } - default: - errList = multierror.Append(errList, fmt.Errorf("Failed to initEnvConfigSet %s, manifest is\n%v\n, error: %s", u.GetKind(), u, "kind not support")) - } - } - - return errList.ErrorOrNil() -} diff --git a/pkg/microservice/aslan/core/environment/service/k8s.go b/pkg/microservice/aslan/core/environment/service/k8s.go index dfdf7aa2..d2f6dd72 100644 --- a/pkg/microservice/aslan/core/environment/service/k8s.go +++ b/pkg/microservice/aslan/core/environment/service/k8s.go @@ -291,7 +291,3 @@ func (k *K8sService) createGroup(envName, productName, username string, group [] return nil } - -func (k *K8sService) initEnvConfigSet(envName, namespace, productName, userName string, envConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { - return initEnvConfigSetAction(envName, namespace, productName, userName, envConfigs, inf, kubeClient) -} diff --git a/pkg/microservice/aslan/core/environment/service/pm.go b/pkg/microservice/aslan/core/environment/service/pm.go index 0f25667e..253f4457 100644 --- a/pkg/microservice/aslan/core/environment/service/pm.go +++ b/pkg/microservice/aslan/core/environment/service/pm.go @@ -266,7 +266,3 @@ func (p *PMService) createGroup(envName, productName, username string, group []* } return errList.ErrorOrNil() } - -func (p *PMService) initEnvConfigSet(envName, namespace, productName, username string, envConfigs []*commonmodels.CreateUpdateCommonEnvCfgArgs, inf informers.SharedInformerFactory, kubeClient client.Client) error { - return nil -} -- Gitee From 0efa077a75ee45e21ed7c9e3c96f5055908897e2 Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 3 Aug 2022 14:29:59 +0800 Subject: [PATCH 2614/2647] fix custom workflow using wrong image (#1849) Signed-off-by: guoyu Co-authored-by: guoyu --- .../core/workflow/service/workflow/job/job_build.go | 6 +++++- .../core/workflow/service/workflow/job/job_freestyle.go | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index 8a9f98cc..e2015ff5 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -119,13 +119,17 @@ func (j *BuildJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { if err := fillBuildDetail(buildInfo, build.ServiceName, build.ServiceModule); err != nil { return resp, err } + basicImage, err := commonrepo.NewBasicImageColl().Find(buildInfo.PreBuild.ImageID) + if err != nil { + return resp, err + } jobTask.Properties = commonmodels.JobProperties{ Timeout: int64(buildInfo.Timeout), ResourceRequest: buildInfo.PreBuild.ResReq, ResReqSpec: buildInfo.PreBuild.ResReqSpec, Envs: renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs), ClusterID: buildInfo.PreBuild.ClusterID, - BuildOS: buildInfo.PreBuild.BuildOS, + BuildOS: basicImage.Value, ImageFrom: buildInfo.PreBuild.ImageFrom, } clusterInfo, err := commonrepo.NewK8SClusterColl().Get(buildInfo.PreBuild.ClusterID) diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go index cd28d490..255da894 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_freestyle.go @@ -22,9 +22,9 @@ import ( configbase "github.com/koderover/zadig/pkg/config" "github.com/koderover/zadig/pkg/microservice/aslan/config" commonmodels "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/models" + commonrepo "github.com/koderover/zadig/pkg/microservice/aslan/core/common/repository/mongodb" "github.com/koderover/zadig/pkg/tool/log" "github.com/koderover/zadig/pkg/types" - stepspec "github.com/koderover/zadig/pkg/types/step" steptypes "github.com/koderover/zadig/pkg/types/step" ) @@ -96,6 +96,11 @@ func (j *FreeStyleJob) ToJobs(taskID int64) ([]*commonmodels.JobTask, error) { Properties: *j.spec.Properties, Steps: stepsToStepTasks(j.spec.Steps), } + basicImage, err := commonrepo.NewBasicImageColl().Find(jobTask.Properties.ImageID) + if err != nil { + return resp, err + } + jobTask.Properties.BuildOS = basicImage.Value jobTask.Properties.Envs = append(jobTask.Properties.Envs, getfreestyleJobVariables(jobTask.Steps, taskID, j.workflow.Project, j.workflow.Name)...) return []*commonmodels.JobTask{jobTask}, nil } @@ -120,7 +125,7 @@ func getfreestyleJobVariables(steps []*commonmodels.StepTask, taskID int64, proj if step.StepType != config.StepGit { continue } - stepSpec := &stepspec.StepGitSpec{} + stepSpec := &steptypes.StepGitSpec{} if err := commonmodels.IToi(step.Spec, stepSpec); err != nil { log.Errorf("failed to convert step spec error: %v", err) continue -- Gitee From 29abdbe742e8c64803d8c4b3b67d1305c364869e Mon Sep 17 00:00:00 2001 From: dianqihanwangzi Date: Wed, 3 Aug 2022 15:26:27 +0800 Subject: [PATCH 2615/2647] set repos in common workflow default branches (#1844) * set repos in common workflow default branches Signed-off-by: guoyu * set default repo namespace Signed-off-by: guoyu * only merge default branch Signed-off-by: guoyu Co-authored-by: guoyu --- .../aslan/core/build/service/build.go | 3 +++ .../common/repository/models/workflow_v4.go | 2 +- .../service/workflow/job/job_build.go | 24 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/pkg/microservice/aslan/core/build/service/build.go b/pkg/microservice/aslan/core/build/service/build.go index 5cef9f9c..602933b9 100644 --- a/pkg/microservice/aslan/core/build/service/build.go +++ b/pkg/microservice/aslan/core/build/service/build.go @@ -41,6 +41,7 @@ type BuildResp struct { Name string `json:"name"` Targets []*commonmodels.ServiceModuleTarget `json:"targets"` KeyVals []*commonmodels.KeyVal `json:"key_vals"` + Repos []*types.Repository `json:"repos"` UpdateTime int64 `json:"update_time"` UpdateBy string `json:"update_by"` Pipelines []string `json:"pipelines"` @@ -160,6 +161,7 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe for _, target := range build.Targets { if target.ServiceModule == container.Name && target.ServiceName == serviceTmpl.ServiceName { build.PreBuild.Envs = target.Envs + build.Repos = target.Repos } } build.PreBuild.Envs = commonservice.MergeBuildEnvs(templateEnvs, build.PreBuild.Envs) @@ -171,6 +173,7 @@ func ListBuildModulesByServiceModule(encryptedKey, productName string, excludeJe ID: build.ID.Hex(), Name: build.Name, KeyVals: build.PreBuild.Envs, + Repos: build.Repos, }) } serviceModuleAndBuildResp = append(serviceModuleAndBuildResp, &ServiceModuleAndBuildResp{ diff --git a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go index 1a399404..b93dcf89 100644 --- a/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go +++ b/pkg/microservice/aslan/core/common/repository/models/workflow_v4.go @@ -94,7 +94,7 @@ type ServiceAndBuild struct { Image string `bson:"-" yaml:"-" json:"image"` Package string `bson:"-" yaml:"-" json:"package"` KeyVals []*KeyVal `bson:"key_vals" yaml:"key_vals" json:"key_vals"` - Repos []*types.Repository `bson:"repos" yaml:"-" json:"repos"` + Repos []*types.Repository `bson:"repos" yaml:"repos" json:"repos"` } type ZadigDeployJobSpec struct { diff --git a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go index e2015ff5..ada0b94c 100644 --- a/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go +++ b/pkg/microservice/aslan/core/workflow/service/workflow/job/job_build.go @@ -65,7 +65,7 @@ func (j *BuildJob) SetPreset() error { } for _, target := range buildInfo.Targets { if target.ServiceName == build.ServiceName && target.ServiceModule == build.ServiceModule { - build.Repos = buildInfo.Repos + build.Repos = mergeRepoBranches(buildInfo.Repos, build.Repos) build.KeyVals = renderKeyVals(build.KeyVals, buildInfo.PreBuild.Envs) break } @@ -408,3 +408,25 @@ func renderEnv(data string, kvs []*commonmodels.KeyVal) string { } return os.Expand(data, mapper) } + +func mergeRepoBranches(templateRepos []*types.Repository, customRepos []*types.Repository) []*types.Repository { + customRepoMap := make(map[string]*types.Repository) + for _, repo := range customRepos { + if repo.RepoNamespace == "" { + repo.RepoNamespace = repo.RepoOwner + } + repoKey := strings.Join([]string{repo.Source, repo.RepoNamespace, repo.RepoName}, "/") + customRepoMap[repoKey] = repo + } + for _, repo := range templateRepos { + if repo.RepoNamespace == "" { + repo.RepoNamespace = repo.RepoOwner + } + repoKey := strings.Join([]string{repo.Source, repo.RepoNamespace, repo.RepoName}, "/") + // user can only set default branch in custom workflow. + if cv, ok := customRepoMap[repoKey]; ok { + repo.Branch = cv.Branch + } + } + return templateRepos +} -- Gitee From 20acc258f68600cfc18491d85c7d1a233c6affa1 Mon Sep 17 00:00:00 2001 From: panxunying <641770806@qq.com> Date: Thu, 4 Aug 2022 18:00:10 +0800 Subject: [PATCH 2616/2647] add signature (#1828) * add signatrue Signed-off-by: panxunying * add log Signed-off-by: panxunying * add check signature Signed-off-by: panxunying Co-authored-by: panxunying --- pkg/config/config.go | 9 ++++ .../policy/core/yamlconfig/urls.yaml | 33 +++++++++++++++ .../user/core/repository/orm/user_login.go | 9 ++++ .../user/core/service/login/local.go | 38 +++++++++++++++++ .../user/core/service/user/user.go | 4 ++ pkg/setting/types.go | 5 +++ pkg/shared/client/plutusvendor/client.go | 41 +++++++++++++++++++ .../client/plutusvendor/plutusvendor.go | 40 ++++++++++++++++++ 8 files changed, 179 insertions(+) create mode 100644 pkg/shared/client/plutusvendor/client.go create mode 100644 pkg/shared/client/plutusvendor/plutusvendor.go diff --git a/pkg/config/config.go b/pkg/config/config.go index c6123172..ff97af75 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -186,6 +186,15 @@ func UserServiceAddress() string { return GetServiceAddress(s.Name, s.Port) } +func VendorServiceInfo() *setting.ServiceInfo { + return GetServiceByCode(setting.Vendor) +} + +func VendorServiceAddress() string { + s := VendorServiceInfo() + return GetServiceAddress(s.Name, s.Port) +} + func GetServiceAddress(name string, port int32) string { return fmt.Sprintf("http://%s:%d", name, port) } diff --git a/pkg/microservice/policy/core/yamlconfig/urls.yaml b/pkg/microservice/policy/core/yamlconfig/urls.yaml index 64a3a7f4..3339b848 100644 --- a/pkg/microservice/policy/core/yamlconfig/urls.yaml +++ b/pkg/microservice/policy/core/yamlconfig/urls.yaml @@ -1,6 +1,39 @@ description: "public urls: url not need to authentication ; system_admin urls: urls that only system admin has permission ; project_admin: urls that project admin has permission" exemption_urls: public: + - endpoint: api/plutus/version/new/check + methods: + - GET + - endpoint: api/plutus/license/sync + methods: + - POST + - endpoint: api/plutus/customer/uploadBaseUri + methods: + - POST + - endpoint: api/plutus/customer/uploadCurrentVersion + methods: + - POST + - endpoint: api/plutus/version/latest + methods: + - GET + - endpoint: api/plutus/license/check + methods: + - POST + - endpoint: license + methods: + - GET + - endpoint: api/plutus/signature + methods: + - POST + - endpoint: api/plutus/** + methods: + - GET + - endpoint: plutus-vendor/** + methods: + - GET + - endpoint: plutus/deliveryBoard + methods: + - GET - endpoint: api/aslan/environment/environments/?*/services/?*/devmode/patch methods: - POST diff --git a/pkg/microservice/user/core/repository/orm/user_login.go b/pkg/microservice/user/core/repository/orm/user_login.go index c63d54e8..361c485f 100644 --- a/pkg/microservice/user/core/repository/orm/user_login.go +++ b/pkg/microservice/user/core/repository/orm/user_login.go @@ -81,3 +81,12 @@ func UpdateUserLogin(uid string, userLogin *models.UserLogin, db *gorm.DB) error } return nil } + +func CountUser(db *gorm.DB) (int64, error) { + var count int64 + err := db.Model(&models.UserLogin{}).Select("count(*)").Where("last_login_time > 0").Find(&count).Error + if err != nil { + return 0, err + } + return count, nil +} diff --git a/pkg/microservice/user/core/service/login/local.go b/pkg/microservice/user/core/service/login/local.go index 4a39805c..eb125414 100644 --- a/pkg/microservice/user/core/service/login/local.go +++ b/pkg/microservice/user/core/service/login/local.go @@ -28,6 +28,7 @@ import ( "github.com/koderover/zadig/pkg/microservice/user/core" "github.com/koderover/zadig/pkg/microservice/user/core/repository/orm" "github.com/koderover/zadig/pkg/setting" + "github.com/koderover/zadig/pkg/shared/client/plutusvendor" ) type LoginArgs struct { @@ -45,6 +46,38 @@ type User struct { IdentityType string `json:"identityType"` } +type CheckSignatureRes struct { + Code int `json:"code"` + Description string `json:"description"` + Extra map[string]interface{} `json:"extra"` + Message string `json:"message"` + Type string `json:"type"` +} + +func CheckSignature(ifLoggedIn bool, logger *zap.SugaredLogger) error { + userNum, err := orm.CountUser(core.DB) + if err != nil { + return err + } + vendorClient := plutusvendor.New() + err = vendorClient.Health() + if err == nil { + res, checkErr := vendorClient.CheckSignature(userNum) + if checkErr != nil { + return checkErr + } + if res.Code == 6694 { + if ifLoggedIn { + return nil + } else { + return fmt.Errorf("系统使用用户数量已达授权人数上限,请联系系统管理员") + } + + } + } + return nil +} + func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { user, err := orm.GetUser(args.Account, config.SystemIdentityType, core.DB) if err != nil { @@ -72,6 +105,10 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { logger.Errorf("LocalLogin user:%s check password error, error msg:%s", args.Account, err) return nil, fmt.Errorf("check password error, error msg:%s", err) } + err = CheckSignature(userLogin.LastLoginTime > 0, logger) + if err != nil { + return nil, err + } userLogin.LastLoginTime = time.Now().Unix() err = orm.UpdateUserLogin(userLogin.UID, userLogin, core.DB) if err != nil { @@ -96,6 +133,7 @@ func LocalLogin(args *LoginArgs, logger *zap.SugaredLogger) (*User, error) { logger.Errorf("LocalLogin user:%s create token error, error msg:%s", args.Account, err.Error()) return nil, err } + return &User{ Uid: user.UID, Token: token, diff --git a/pkg/microservice/user/core/service/user/user.go b/pkg/microservice/user/core/service/user/user.go index d5b54e4b..0a6be6d6 100644 --- a/pkg/microservice/user/core/service/user/user.go +++ b/pkg/microservice/user/core/service/user/user.go @@ -561,6 +561,10 @@ func SyncUser(syncUserInfo *SyncUserInfo, logger *zap.SugaredLogger) (*models.Us logger.Error("UpdateLoginInfo get user:%s login error, error msg:%s", user.UID, err.Error()) return nil, err } + err = login.CheckSignature(userLogin.LastLoginTime > 0, logger) + if err != nil { + return nil, err + } if userLogin != nil { userLogin.LastLoginTime = time.Now().Unix() err = orm.UpdateUserLogin(user.UID, userLogin, tx) diff --git a/pkg/setting/types.go b/pkg/setting/types.go index 779f3c11..a02b516c 100644 --- a/pkg/setting/types.go +++ b/pkg/setting/types.go @@ -105,6 +105,7 @@ const ( Policy // 13 Config // 14 User // 15 + Vendor ) type ServiceInfo struct { @@ -169,4 +170,8 @@ var Services = map[int]*ServiceInfo{ Name: "user", Port: 80, }, + Vendor: { + Name: "plutus-vendor", + Port: 29000, + }, } diff --git a/pkg/shared/client/plutusvendor/client.go b/pkg/shared/client/plutusvendor/client.go new file mode 100644 index 00000000..0fecc59c --- /dev/null +++ b/pkg/shared/client/plutusvendor/client.go @@ -0,0 +1,41 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plutusvendor + +import ( + "github.com/koderover/zadig/pkg/config" + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type Client struct { + *httpclient.Client + + host string +} + +func New() *Client { + host := config.VendorServiceAddress() + + c := httpclient.New( + httpclient.SetHostURL(host + "/api/plutus"), + ) + + return &Client{ + Client: c, + host: host, + } +} diff --git a/pkg/shared/client/plutusvendor/plutusvendor.go b/pkg/shared/client/plutusvendor/plutusvendor.go new file mode 100644 index 00000000..1fccb841 --- /dev/null +++ b/pkg/shared/client/plutusvendor/plutusvendor.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 The KodeRover Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package plutusvendor + +import ( + "fmt" + + "github.com/koderover/zadig/pkg/tool/httpclient" +) + +type CheckSignatrueResp struct { + Code int64 `json:"code"` +} + +func (c *Client) CheckSignature(userNum int64) (*CheckSignatrueResp, error) { + url := fmt.Sprintf("/signature/check?user_num=%d", userNum) + res := &CheckSignatrueResp{} + _, err := c.Post(url, httpclient.SetResult(res)) + return res, err +} + +func (c *Client) Health() error { + url := "/health" + _, err := c.Get(url) + return err +} -- Gitee From b732879a26acca1b6c7d91ab685e602f50e4f9db Mon Sep 17 00:00:00 2001 From: fansi404 <89375976+fansi404@users.noreply.github.com> Date: Thu, 4 Aug 2022 20:39:39 +0800 Subject: [PATCH 2617/2647] update apiVersion for demo Ingress (#1855) Signed-off-by: fansi Co-authored-by: fansi --- .../microservice-demo/chart/templates/frontend.yaml | 12 ++++++------ .../microservice-demo/k8s-yaml/frontend/ingress.yaml | 9 ++++++--- examples/nginx/yaml/nginx.yaml | 9 ++++++--- .../result/result-service.yaml | 9 ++++++--- .../vote/vote-service.yaml | 9 ++++++--- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/examples/microservice-demo/chart/templates/frontend.yaml b/examples/microservice-demo/chart/templates/frontend.yaml index e3194bdf..fe38eae0 100644 --- a/examples/microservice-demo/chart/templates/frontend.yaml +++ b/examples/microservice-demo/chart/templates/frontend.yaml @@ -53,7 +53,7 @@ spec: port: 80 targetPort: 80 --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: frontend @@ -70,9 +70,9 @@ spec: http: paths: - path: / + pathType: Prefix backend: - serviceName: frontend - servicePort: 80 - - - + service: + name: frontend + port: + number: 80 diff --git a/examples/microservice-demo/k8s-yaml/frontend/ingress.yaml b/examples/microservice-demo/k8s-yaml/frontend/ingress.yaml index bf98292d..8596d5f6 100644 --- a/examples/microservice-demo/k8s-yaml/frontend/ingress.yaml +++ b/examples/microservice-demo/k8s-yaml/frontend/ingress.yaml @@ -1,4 +1,4 @@ -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: frontend @@ -13,6 +13,9 @@ spec: http: paths: - path: / + pathType: Prefix backend: - serviceName: frontend - servicePort: 80 \ No newline at end of file + service: + name: frontend + port: + number: 80 \ No newline at end of file diff --git a/examples/nginx/yaml/nginx.yaml b/examples/nginx/yaml/nginx.yaml index 3a0ae54f..3a770e90 100644 --- a/examples/nginx/yaml/nginx.yaml +++ b/examples/nginx/yaml/nginx.yaml @@ -50,7 +50,7 @@ spec: configMap: name: static-page --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-expose @@ -60,9 +60,12 @@ spec: http: paths: - backend: - serviceName: nginx - servicePort: 80 + service: + name: nginx + port: + number: 80 path: / + pathType: Prefix --- apiVersion: v1 kind: ConfigMap diff --git a/examples/voting-app/freestyle-k8s-specifications/result/result-service.yaml b/examples/voting-app/freestyle-k8s-specifications/result/result-service.yaml index b7af093f..4d3cccc0 100644 --- a/examples/voting-app/freestyle-k8s-specifications/result/result-service.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/result/result-service.yaml @@ -14,7 +14,7 @@ spec: selector: app: result --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: result @@ -26,6 +26,9 @@ spec: http: paths: - path: / + pathType: Prefix backend: - serviceName: result - servicePort: 5001 \ No newline at end of file + service: + name: result + port: + number: 5001 diff --git a/examples/voting-app/freestyle-k8s-specifications/vote/vote-service.yaml b/examples/voting-app/freestyle-k8s-specifications/vote/vote-service.yaml index 78643aa6..a598a818 100644 --- a/examples/voting-app/freestyle-k8s-specifications/vote/vote-service.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/vote/vote-service.yaml @@ -14,7 +14,7 @@ spec: app: vote version: rc-origin --- -apiVersion: extensions/v1beta1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: vote @@ -26,6 +26,9 @@ spec: http: paths: - path: / + pathType: Prefix backend: - serviceName: vote - servicePort: 5000 \ No newline at end of file + service: + name: vote + port: + number: 5000 -- Gitee From 888fe3ec3d3826c62b0c59055fc6075d91f3854d Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 16 Aug 2022 06:10:00 +0000 Subject: [PATCH 2618/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ut.file b/ut.file index 04628503..67f03fcd 100644 --- a/ut.file +++ b/ut.file @@ -4,3 +4,7 @@ ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry ./pkg/util/converter +./pkg/microservice/aslan/core/common/service/registry +./pkg/util/converter +./pkg/microservice/aslan/core/common/service/registry +./pkg/util/converter -- Gitee From 3d06bb2f60a6c00d03e11b5deb944c41f4b7ec10 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 16 Aug 2022 11:23:07 +0000 Subject: [PATCH 2619/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ut.file b/ut.file index 67f03fcd..04628503 100644 --- a/ut.file +++ b/ut.file @@ -4,7 +4,3 @@ ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry ./pkg/util/converter -./pkg/microservice/aslan/core/common/service/registry -./pkg/util/converter -./pkg/microservice/aslan/core/common/service/registry -./pkg/util/converter -- Gitee From 61c7c2e35a76798dde48b7b3c6a25defc33a268e Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 30 Aug 2022 05:48:05 +0000 Subject: [PATCH 2620/2647] update pkg/microservice/warpdrive/config/const.go. --- pkg/microservice/warpdrive/config/const.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index 5ee27d05..a67b11e5 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -38,6 +38,7 @@ const ( TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" TaskScanning TaskType = "scanning" + TaskArtifactV2 TaskType = "artifactv2" ) type Status string -- Gitee From 484f37eb477fdc14c3feba48d06118594f0302b3 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 30 Aug 2022 05:54:55 +0000 Subject: [PATCH 2621/2647] update pkg/microservice/warpdrive/config/config.go. --- pkg/microservice/warpdrive/config/config.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/microservice/warpdrive/config/config.go b/pkg/microservice/warpdrive/config/config.go index 104124a4..9d74ab6d 100644 --- a/pkg/microservice/warpdrive/config/config.go +++ b/pkg/microservice/warpdrive/config/config.go @@ -41,3 +41,7 @@ func ReleaseImageTimeout() string { func Home() string { return viper.GetString(setting.Home) } + +func HomeV2() string { + return viper.GetString(setting.HomeV2) +} -- Gitee From a13120997b95d8b63f2b5b29c700f89d38a5de54 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 30 Aug 2022 05:55:57 +0000 Subject: [PATCH 2622/2647] update pkg/microservice/warpdrive/config/const.go. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- pkg/microservice/warpdrive/config/const.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/microservice/warpdrive/config/const.go b/pkg/microservice/warpdrive/config/const.go index a67b11e5..5ee27d05 100644 --- a/pkg/microservice/warpdrive/config/const.go +++ b/pkg/microservice/warpdrive/config/const.go @@ -38,7 +38,6 @@ const ( TaskExtension TaskType = "extension" TaskArtifactPackage TaskType = "artifact_package" TaskScanning TaskType = "scanning" - TaskArtifactV2 TaskType = "artifactv2" ) type Status string -- Gitee From 8f14f5b8c006cce96f5560802f3a4c0d194511fa Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Wed, 28 Sep 2022 02:48:42 +0000 Subject: [PATCH 2623/2647] update examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml. --- .../result/result-deployment.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml b/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml index db31b845..b6d224cd 100644 --- a/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml +++ b/examples/voting-app/freestyle-k8s-specifications/result/result-deployment.yaml @@ -22,5 +22,5 @@ spec: name: result resources: limits: - memory: 50Mi - cpu: 50m + memory: 60Mi + cpu: 60m -- Gitee From 963c21358fba784b18ecac0909c12bf1e9c53f39 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 29 Sep 2022 04:01:49 +0000 Subject: [PATCH 2624/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ut.file b/ut.file index 04628503..c6d6fbf3 100644 --- a/ut.file +++ b/ut.file @@ -4,3 +4,5 @@ ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry ./pkg/util/converter +./pkg/util/converter +./pkg/util/converter -- Gitee From b37d68b8d17a51bd54b188ed5faa9101b3a8a10c Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Wed, 12 Oct 2022 03:47:15 +0000 Subject: [PATCH 2625/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 2 -- 1 file changed, 2 deletions(-) diff --git a/ut.file b/ut.file index c6d6fbf3..04628503 100644 --- a/ut.file +++ b/ut.file @@ -4,5 +4,3 @@ ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry ./pkg/util/converter -./pkg/util/converter -./pkg/util/converter -- Gitee From 20e367b62c470db0b8952b6fe0e42bf20bf823b2 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Fri, 31 Mar 2023 06:41:18 +0000 Subject: [PATCH 2626/2647] update action.yml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- action.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/action.yml b/action.yml index 16e71a8d..ac77245e 100644 --- a/action.yml +++ b/action.yml @@ -16,3 +16,5 @@ inputs: runs: using: 'node12' main: 'dist/index.js' + +hello world -- Gitee From f7630a4d5d66d98fe4c33cbaea2dae4a44f8d198 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Fri, 31 Mar 2023 07:04:39 +0000 Subject: [PATCH 2627/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 1 + 1 file changed, 1 insertion(+) diff --git a/ut.file b/ut.file index 04628503..8c7ac15d 100644 --- a/ut.file +++ b/ut.file @@ -4,3 +4,4 @@ ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry ./pkg/util/converter +./xxx/xxx \ No newline at end of file -- Gitee From bcebdbcbf4e96c72f3ff6ca2cbdd1cf8f168cd7d Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Fri, 31 Mar 2023 07:05:16 +0000 Subject: [PATCH 2628/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ut.file b/ut.file index 8c7ac15d..b02234ad 100644 --- a/ut.file +++ b/ut.file @@ -4,4 +4,4 @@ ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry ./pkg/util/converter -./xxx/xxx \ No newline at end of file +./xxx/xxx/xxx/xxxxx \ No newline at end of file -- Gitee From 720b84301284c45a6d814f59bf08991625e35f59 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Fri, 31 Mar 2023 07:07:00 +0000 Subject: [PATCH 2629/2647] update ut.file. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- ut.file | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ut.file b/ut.file index b02234ad..338593dd 100644 --- a/ut.file +++ b/ut.file @@ -3,5 +3,4 @@ ./pkg/microservice/reaper/core/service/meta ./pkg/microservice/reaper/core/service/reaper ./pkg/microservice/aslan/core/common/service/registry -./pkg/util/converter -./xxx/xxx/xxx/xxxxx \ No newline at end of file +./pkg/util/converter \ No newline at end of file -- Gitee From 95eda38e83926e8772a46075c2332cb9415972ab Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Fri, 31 Mar 2023 07:31:13 +0000 Subject: [PATCH 2630/2647] update examples/multi-service-demo/k8s-yaml/service3/deployment.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- examples/multi-service-demo/k8s-yaml/service3/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml index eb8bb107..d8c6c855 100644 --- a/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml +++ b/examples/multi-service-demo/k8s-yaml/service3/deployment.yaml @@ -28,6 +28,6 @@ spec: containerPort: 20223 resources: limits: - memory: 50Mi + memory: 100Mi cpu: 50m \ No newline at end of file -- Gitee From 3ef90bb8e42fc231366ad49f3a88af1e7a33cf44 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 8 Jun 2023 06:25:19 +0000 Subject: [PATCH 2631/2647] update examples/multi-service-demo/general-chart/templates/deployment.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- .../multi-service-demo/general-chart/templates/deployment.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/multi-service-demo/general-chart/templates/deployment.yaml b/examples/multi-service-demo/general-chart/templates/deployment.yaml index e441d6b8..238b960a 100644 --- a/examples/multi-service-demo/general-chart/templates/deployment.yaml +++ b/examples/multi-service-demo/general-chart/templates/deployment.yaml @@ -21,6 +21,9 @@ spec: - name: {{ .Values.fullnameOverride }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: Always + env: + - name: "x" + value: "x" command: - /workspace/{{ .Values.fullnameOverride }} ports: -- Gitee From 3db3e29e1bdd073ae6dd8695f1cad34d86d5634d Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 8 Jun 2023 06:30:12 +0000 Subject: [PATCH 2632/2647] update examples/multi-service-demo/general-chart/templates/deployment.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- .../multi-service-demo/general-chart/templates/deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/multi-service-demo/general-chart/templates/deployment.yaml b/examples/multi-service-demo/general-chart/templates/deployment.yaml index 238b960a..a45fb571 100644 --- a/examples/multi-service-demo/general-chart/templates/deployment.yaml +++ b/examples/multi-service-demo/general-chart/templates/deployment.yaml @@ -23,7 +23,7 @@ spec: imagePullPolicy: Always env: - name: "x" - value: "x" + value: "x2" command: - /workspace/{{ .Values.fullnameOverride }} ports: -- Gitee From c420bcca1137f4bbfb4b8957acef7e8d4cab6eab Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 8 Jun 2023 06:59:07 +0000 Subject: [PATCH 2633/2647] update examples/multi-service-demo/general-chart/templates/deployment.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- .../general-chart/templates/deployment.yaml | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/examples/multi-service-demo/general-chart/templates/deployment.yaml b/examples/multi-service-demo/general-chart/templates/deployment.yaml index a45fb571..ea2a4412 100644 --- a/examples/multi-service-demo/general-chart/templates/deployment.yaml +++ b/examples/multi-service-demo/general-chart/templates/deployment.yaml @@ -17,10 +17,21 @@ spec: app.kubernetes.io/name: demo app.kubernetes.io/instance: {{ .Values.fullnameOverride }} spec: + volumes: + - name: config + configMap: + name: game-demo + items: + - key: "game.properties" + path: "game.properties" containers: - name: {{ .Values.fullnameOverride }} image: {{ .Values.image.repository }}:{{ .Values.image.tag }} imagePullPolicy: Always + volumeMounts: + - name: config + mountPath: "/config" + readOnly: true env: - name: "x" value: "x2" @@ -37,4 +48,19 @@ spec: cpu: {{ .Values.resources.requests.cpu }} memory: {{ .Values.resources.requests.mem }} imagePullSecrets: - - name: {{ .Values.imagePullSecretsName }} \ No newline at end of file + - name: {{ .Values.imagePullSecretsName }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: game-demo +data: + player_initial_lives: "3" + ui_properties_file_name: "user-interface.properties" + game.properties: | + enemy.types=aliens,monsters + player.maximum-lives=5 + user-interface.properties: | + color.good=purple + color.bad=yellow + allow.textmode=true \ No newline at end of file -- Gitee From 656bcc0e91dd7622af8963e2169c849b5432e45b Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 8 Jun 2023 07:09:25 +0000 Subject: [PATCH 2634/2647] add examples/configmap-demo.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- examples/configmap-demo.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/configmap-demo.yaml diff --git a/examples/configmap-demo.yaml b/examples/configmap-demo.yaml new file mode 100644 index 00000000..8c259436 --- /dev/null +++ b/examples/configmap-demo.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: game-demo +data: + player_initial_lives: "3" + ui_properties_file_name: "user-interface.properties" + game.properties: | + enemy.types=aliens,monsters + player.maximum-lives=5 + user-interface.properties: | + color.good=purple + color.bad=yellow + allow.textmode=true \ No newline at end of file -- Gitee From 95485d78da4680059d139be2c600682e29cadcde Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 8 Jun 2023 07:09:51 +0000 Subject: [PATCH 2635/2647] update examples/configmap-demo.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- examples/configmap-demo.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/configmap-demo.yaml b/examples/configmap-demo.yaml index 8c259436..0f3a6ccf 100644 --- a/examples/configmap-demo.yaml +++ b/examples/configmap-demo.yaml @@ -7,8 +7,8 @@ data: ui_properties_file_name: "user-interface.properties" game.properties: | enemy.types=aliens,monsters - player.maximum-lives=5 + player.maximum-lives=1 user-interface.properties: | - color.good=purple - color.bad=yellow - allow.textmode=true \ No newline at end of file + color.good=purple1 + color.bad=yellow1 + allow.textmode=true1 \ No newline at end of file -- Gitee From c4f59727adab083337cc76f21fd0abbde3b25c90 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Thu, 15 Jun 2023 07:28:17 +0000 Subject: [PATCH 2636/2647] update examples/multi-service-demo/base-chart/values.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- examples/multi-service-demo/base-chart/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/multi-service-demo/base-chart/values.yaml b/examples/multi-service-demo/base-chart/values.yaml index 1851f7f9..0642e90d 100644 --- a/examples/multi-service-demo/base-chart/values.yaml +++ b/examples/multi-service-demo/base-chart/values.yaml @@ -2,7 +2,7 @@ fullnameOverride: service replicaCount: 1 port: 20221 -imagePullSecretsName: "service-registry-secret" +imagePullSecretsName: "default-registry-secret" image: repository: "koderover.tencentcloudcr.com/koderover-demo/service" -- Gitee From c006e9c54d96d0807b0630670073603ed2531cf6 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 20 Jun 2023 12:01:50 +0000 Subject: [PATCH 2637/2647] update examples/multi-service-demo/full-charts/service3/values.yaml. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- examples/multi-service-demo/full-charts/service3/values.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/multi-service-demo/full-charts/service3/values.yaml b/examples/multi-service-demo/full-charts/service3/values.yaml index 60a93100..4c0c508d 100644 --- a/examples/multi-service-demo/full-charts/service3/values.yaml +++ b/examples/multi-service-demo/full-charts/service3/values.yaml @@ -7,6 +7,8 @@ imagePullSecretsName: "default-registry-secret" image: repository: "koderover.tencentcloudcr.com/koderover-demo/service3" tag: "latest" + cronjobrepo: "koderover.tencentcloudcr.com/test/busybox" + cronjobtag: "20230620192216" resources: requests: -- Gitee From 4fda10ff1c5abbd99512208f79de0e908c26956a Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 22 Aug 2023 11:16:30 +0000 Subject: [PATCH 2638/2647] update examples/test-demo/go.mod. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- examples/test-demo/go.mod | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/test-demo/go.mod b/examples/test-demo/go.mod index 283c891b..dfd6ffbd 100644 --- a/examples/test-demo/go.mod +++ b/examples/test-demo/go.mod @@ -1,8 +1,17 @@ module test-demo -go 1.16 +go 1.18 require ( - github.com/onsi/ginkgo/v2 v2.0.0 - github.com/onsi/gomega v1.17.0 -) \ No newline at end of file + github.com/onsi/ginkgo/v2 v2.5.0 + github.com/onsi/gomega v1.24.0 +) + +require ( + github.com/go-logr/logr v1.2.3 // indirect + github.com/google/go-cmp v0.5.9 // indirect + golang.org/x/net v0.1.0 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) -- Gitee From 199000ae78855a0567cd34a3afec9048bbe454bd Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 22 Aug 2023 11:16:46 +0000 Subject: [PATCH 2639/2647] update examples/test-demo/go.sum. --- examples/test-demo/go.sum | 115 ++++++-------------------------------- 1 file changed, 17 insertions(+), 98 deletions(-) diff --git a/examples/test-demo/go.sum b/examples/test-demo/go.sum index 252fae99..9cf15e16 100644 --- a/examples/test-demo/go.sum +++ b/examples/test-demo/go.sum @@ -1,101 +1,20 @@ -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= -github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= -github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/onsi/ginkgo/v2 v2.5.0 h1:TRtrvv2vdQqzkwrQ1ke6vtXf7IK34RBUJafIy1wMwls= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= \ No newline at end of file +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -- Gitee From 57fce95745cd18f6f35efde73843f9bdbcaf54b3 Mon Sep 17 00:00:00 2001 From: fansi404 <10196377+fansi404@user.noreply.gitee.com> Date: Tue, 19 Sep 2023 07:05:03 +0000 Subject: [PATCH 2640/2647] update cmd/aslan/main.go. Signed-off-by: fansi404 <10196377+fansi404@user.noreply.gitee.com> --- cmd/aslan/main.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cmd/aslan/main.go b/cmd/aslan/main.go index 6c4e7a98..38956e3d 100644 --- a/cmd/aslan/main.go +++ b/cmd/aslan/main.go @@ -34,5 +34,22 @@ func main() { if err := server.Serve(ctx); err != nil { log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) + log.Fatal(err) } } -- Gitee From 77f192849cb7f8760fe3673e2ab0d292aa7b468c Mon Sep 17 00:00:00 2001 From: Cynthia Date: Mon, 20 Apr 2026 17:17:55 +0800 Subject: [PATCH 2641/2647] test webhook --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index ac77245e..e709bfad 100644 --- a/action.yml +++ b/action.yml @@ -1,3 +1,4 @@ +# webhook test name: 'Labeler' description: 'Automatically label new pull requests based on the paths of files being changed' author: 'GitHub' -- Gitee From bbf303aec36c3712aaa7cb23217a4aa3eac448ce Mon Sep 17 00:00:00 2001 From: Cynthia Date: Mon, 20 Apr 2026 17:23:52 +0800 Subject: [PATCH 2642/2647] test webhook --- action.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/action.yml b/action.yml index e709bfad..56fdbded 100644 --- a/action.yml +++ b/action.yml @@ -16,6 +16,4 @@ inputs: runs: using: 'node12' - main: 'dist/index.js' - -hello world + main: 'dist/index.js' \ No newline at end of file -- Gitee From f4c10b2226401006fb42082f95ec2568dae0a884 Mon Sep 17 00:00:00 2001 From: Cynthia Date: Mon, 20 Apr 2026 18:01:33 +0800 Subject: [PATCH 2643/2647] test --- action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/action.yml b/action.yml index 56fdbded..a3b84c3c 100644 --- a/action.yml +++ b/action.yml @@ -1,4 +1,5 @@ # webhook test +# webhook test name: 'Labeler' description: 'Automatically label new pull requests based on the paths of files being changed' author: 'GitHub' -- Gitee From 7835f5dec198f4bd2b12a29cb9cbe4ba12e64c19 Mon Sep 17 00:00:00 2001 From: Cynthia Date: Wed, 29 Apr 2026 10:49:05 +0800 Subject: [PATCH 2644/2647] test --- examples/configmap-demo.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/configmap-demo.yaml b/examples/configmap-demo.yaml index 0f3a6ccf..1fe741f5 100644 --- a/examples/configmap-demo.yaml +++ b/examples/configmap-demo.yaml @@ -1,3 +1,4 @@ +# test apiVersion: v1 kind: ConfigMap metadata: -- Gitee From 599af6fdb67bc6cc0fe2f5eb94043d96f2a4e0e2 Mon Sep 17 00:00:00 2001 From: lilianzhu <9208447+lilianzhu@user.noreply.gitee.com> Date: Thu, 30 Apr 2026 09:20:21 +0000 Subject: [PATCH 2645/2647] update examples/configmap-demo.yaml. Signed-off-by: lilianzhu <9208447+lilianzhu@user.noreply.gitee.com> --- examples/configmap-demo.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/configmap-demo.yaml b/examples/configmap-demo.yaml index 1fe741f5..eb628122 100644 --- a/examples/configmap-demo.yaml +++ b/examples/configmap-demo.yaml @@ -4,7 +4,7 @@ kind: ConfigMap metadata: name: game-demo data: - player_initial_lives: "3" + player_initial_lives: "1" ui_properties_file_name: "user-interface.properties" game.properties: | enemy.types=aliens,monsters -- Gitee From ee47f70e6cca7516026531cd10d7c842624dbb9e Mon Sep 17 00:00:00 2001 From: Cynthia Date: Thu, 14 May 2026 11:21:44 +0800 Subject: [PATCH 2646/2647] test webhook --- examples/multi-service-demo/k8s-yaml/service1/service.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/multi-service-demo/k8s-yaml/service1/service.yaml b/examples/multi-service-demo/k8s-yaml/service1/service.yaml index a49ac621..3dff1893 100644 --- a/examples/multi-service-demo/k8s-yaml/service1/service.yaml +++ b/examples/multi-service-demo/k8s-yaml/service1/service.yaml @@ -1,3 +1,4 @@ +# test webhook apiVersion: v1 kind: Service metadata: -- Gitee From 752ee1c1003ba5670d71902955928daad36e406f Mon Sep 17 00:00:00 2001 From: Cynthia Date: Thu, 18 Jun 2026 10:28:16 +0800 Subject: [PATCH 2647/2647] test git hook --- examples/multi-service-demo/k8s-yaml/service1/service.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/multi-service-demo/k8s-yaml/service1/service.yaml b/examples/multi-service-demo/k8s-yaml/service1/service.yaml index 3dff1893..a49ac621 100644 --- a/examples/multi-service-demo/k8s-yaml/service1/service.yaml +++ b/examples/multi-service-demo/k8s-yaml/service1/service.yaml @@ -1,4 +1,3 @@ -# test webhook apiVersion: v1 kind: Service metadata: -- Gitee

    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 HIq}~BxuTDH literal 388176 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 z%0D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�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 z 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

  • ^Uc!L#U+gMoPZf5-AaL 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 literal 66812 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